What is Hackers' Pub?

Hackers' Pub is a place for software engineers to share their knowledge and experience with each other. It's also an ActivityPub-enabled social network, so you can follow your favorite hackers in the fediverse and get their latest posts in your feed.

블루스카이 관리 앱 소개를 받아서 설치하고 혹시 블록 리스트 올라갔나 봤는데, 딱히 그런 건 없지만 날 블록한 사람들이 수도 없이 있었다. 근데 이름으로 봐서 섹계가 그 중 상당수였다. 그쪽도 목적이 목적인 만큼 다른 계정들 블록하고 있는 모양... 서로 블록하다 보면 분리되겠지.

0
1
0
1
1
0
2
1

最近Xでやたら素人のバズってる性的な動画が流れてくるようになったけど、なんか違和感がある場合があるのと共通して映像の揺れ方やズームの感じが特徴的(手ブレという感じではなく、ジンバルというわけでもなく、なんかゆっくりユラユラする感じ)だから、おそらく非性的な一般の人の写真を元にGrokとかで生成された映像なのではと思っている​:nullcatchan_goodnight:

1

als es um die chatkontrolle ging, konnte nicht genug getrommelt werden. Hier geht es um viel mehr. An fehlenden Accounts kann es nicht liegen, dass die 30000 immer noch nicht voll sind. Mitzeichnung ist auch ohne deutschen Wohnsitz möglich. Keine Führung eigener Register zur Erfassung von trans* und nichtbinärer Personen epetitionen.bundestag.de/petit

0
0
0

今日、高速道路を利用したんですが
追突事故による渋滞の中で、さらに追突事故が起きる
二重の事故現場を見ました。

事故の当事者の方々は寒空の下で車の外に出て
憂鬱そうな顔で話し合ったり電話をしていましたよ。

正月早々事故なんて嫌ですよね。
これから車でお出かけの皆さんは十分にご注意ください。

1
1

초무 shared the below article:

Designing type-safe sync/async mode support in TypeScript

洪 民憙 (Hong Minhee) @hongminhee@hackers.pub

I recently added sync/async mode support to Optique, a type-safe CLI parser for TypeScript. It turned out to be one of the trickier features I've implemented—the object() combinator alone needed to compute a combined mode from all its child parsers, and TypeScript's inference kept hitting edge cases.

What is Optique?

Optique is a type-safe, combinatorial CLI parser for TypeScript, inspired by Haskell's optparse-applicative. Instead of decorators or builder patterns, you compose small parsers into larger ones using combinators, and TypeScript infers the result types.

Here's a quick taste:

import { object } from "@optique/core/constructs";
import { argument, option } from "@optique/core/primitives";
import { string, integer } from "@optique/core/valueparser";
import { run } from "@optique/run";

const cli = object({
  name: argument(string()),
  count: option("-n", "--count", integer()),
});

// TypeScript infers: { name: string; count: number | undefined }
const result = run(cli);  // sync by default

The type inference works through arbitrarily deep compositions—in most cases, you don't need explicit type annotations.

How it started

Lucas Garron (@lgarron) opened an issue requesting async support for shell completions. He wanted to provide Tab-completion suggestions by running shell commands like git for-each-ref to list branches and tags.

// Lucas's example: fetching Git branches and tags in parallel
const [branches, tags] = await Promise.all([
  $`git for-each-ref --format='%(refname:short)' refs/heads/`.text(),
  $`git for-each-ref --format='%(refname:short)' refs/tags/`.text(),
]);

At first, I didn't like the idea. Optique's entire API was synchronous, which made it simpler to reason about and avoided the “async infection” problem where one async function forces everything upstream to become async. I argued that shell completion should be near-instantaneous, and if you need async data, you should cache it at startup.

But Lucas pushed back. The filesystem is a database, and many useful completions inherently require async work—Git refs change constantly, and pre-caching everything at startup doesn't scale for large repos. Fair point.

What I needed to solve

So, how do you support both sync and async execution modes in a composable parser library while maintaining type safety?

The key requirements were:

  • parse() returns T or Promise<T>
  • complete() returns T or Promise<T>
  • suggest() returns Iterable<T> or AsyncIterable<T>
  • When combining parsers, if any parser is async, the combined result must be async
  • Existing sync code should continue to work unchanged

The fourth requirement is the tricky one. Consider this:

const syncParser = flag("--verbose");
const asyncParser = option("--branch", asyncValueParser);

// What's the type of this?
const combined = object({ verbose: syncParser, branch: asyncParser });

The combined parser should be async because one of its fields is async. This means we need type-level logic to compute the combined mode.

Five design options

I explored five different approaches, each with its own trade-offs.

Option A: conditional types with mode parameter

Add a mode type parameter to Parser and use conditional types:

type Mode = "sync" | "async";

type ModeValue<M extends Mode, T> = M extends "async" ? Promise<T> : T;

interface Parser<M extends Mode, TValue, TState> {
  parse(context: ParserContext<TState>): ModeValue<M, ParserResult<TState>>;
  // ...
}

The challenge is computing combined modes:

type CombineModes<T extends Record<string, Parser<any, any, any>>> =
  T[keyof T] extends Parser<infer M, any, any>
    ? M extends "async" ? "async" : "sync"
    : never;

Option B: mode parameter with default value

A variant of Option A, but place the mode parameter first with a default of "sync":

interface Parser<M extends Mode = "sync", TValue, TState> {
  readonly $mode: M;
  // ...
}

The default value maintains backward compatibility—existing user code keeps working without changes.

Option C: separate interfaces

Define completely separate Parser and AsyncParser interfaces with explicit conversion:

interface Parser<TValue, TState> { /* sync methods */ }
interface AsyncParser<TValue, TState> { /* async methods */ }

function toAsync<T, S>(parser: Parser<T, S>): AsyncParser<T, S>;

Simpler to understand, but requires code duplication and explicit conversions.

Option D: union return types for suggest() only

The minimal approach. Only allow suggest() to be async:

interface Parser<TValue, TState> {
  parse(context: ParserContext<TState>): ParserResult<TState>;  // always sync
  suggest(context: ParserContext<TState>, prefix: string):
    Iterable<Suggestion> | AsyncIterable<Suggestion>;  // can be either
}

This addresses the original use case but doesn't help if async parse() is ever needed.

Option E: fp-ts style HKT simulation

Use the technique from fp-ts to simulate Higher-Kinded Types:

interface URItoKind<A> {
  Identity: A;
  Promise: Promise<A>;
}

type Kind<F extends keyof URItoKind<any>, A> = URItoKind<A>[F];

interface Parser<F extends keyof URItoKind<any>, TValue, TState> {
  parse(context: ParserContext<TState>): Kind<F, ParserResult<TState>>;
}

The most flexible approach, but with a steep learning curve.

Testing the idea

Rather than commit to an approach based on theoretical analysis, I created a prototype to test how well TypeScript handles the type inference in practice. I published my findings in the GitHub issue:

Both approaches correctly handle the “any async → all async” rule at the type level. (…) Complex conditional types like ModeValue<CombineParserModes<T>, ParserResult<TState>> sometimes require explicit type casting in the implementation. This only affects library internals. The user-facing API remains clean.

The prototype validated that Option B (explicit mode parameter with default) would work. I chose it for these reasons:

  • Backward compatible: The default "sync" keeps existing code working
  • Explicit: The mode is visible in both types and runtime (via a $mode property)
  • Debuggable: Easy to inspect the current mode at runtime
  • Better IDE support: Type information is more predictable

How CombineModes works

The CombineModes type computes whether a combined parser should be sync or async:

type CombineModes<T extends readonly Mode[]> = "async" extends T[number]
  ? "async"
  : "sync";

This type checks if "async" is present anywhere in the tuple of modes. If so, the result is "async"; otherwise, it's "sync".

For combinators like object(), I needed to extract modes from parser objects and combine them:

// Extract the mode from a single parser
type ParserMode<T> = T extends Parser<infer M, unknown, unknown> ? M : never;

// Combine modes from all values in a record of parsers
type CombineObjectModes<T extends Record<string, Parser<Mode, unknown, unknown>>> =
  CombineModes<{ [K in keyof T]: ParserMode<T[K]> }[keyof T][]>;

Runtime implementation

The type system handles compile-time safety, but the implementation also needs runtime logic. Each parser has a $mode property that indicates its execution mode:

const syncParser = option("-n", "--name", string());
console.log(syncParser.$mode);  // "sync"

const asyncParser = option("-b", "--branch", asyncValueParser);
console.log(asyncParser.$mode);  // "async"

Combinators compute their mode at construction time:

function object<T extends Record<string, Parser<Mode, unknown, unknown>>>(
  parsers: T
): Parser<CombineObjectModes<T>, ObjectValue<T>, ObjectState<T>> {
  const parserKeys = Reflect.ownKeys(parsers);
  const combinedMode: Mode = parserKeys.some(
    (k) => parsers[k as keyof T].$mode === "async"
  ) ? "async" : "sync";

  // ... implementation
}

Refining the API

Lucas suggested an important refinement during our discussion. Instead of having run() automatically choose between sync and async based on the parser mode, he proposed separate functions:

Perhaps run(…) could be automatic, and runSync(…) and runAsync(…) could enforce that the inferred type matches what is expected.

So we ended up with:

  • run(): automatic based on parser mode
  • runSync(): enforces sync mode at compile time
  • runAsync(): enforces async mode at compile time
// Automatic: returns T for sync parsers, Promise<T> for async
const result1 = run(syncParser);  // string
const result2 = run(asyncParser);  // Promise<string>

// Explicit: compile-time enforcement
const result3 = runSync(syncParser);  // string
const result4 = runAsync(asyncParser);  // Promise<string>

// Compile error: can't use runSync with async parser
const result5 = runSync(asyncParser);  // Type error!

I applied the same pattern to parse()/parseSync()/parseAsync() and suggest()/suggestSync()/suggestAsync() in the facade functions.

Creating async value parsers

With the new API, creating an async value parser for Git branches looks like this:

import type { Suggestion } from "@optique/core/parser";
import type { ValueParser, ValueParserResult } from "@optique/core/valueparser";

function gitRef(): ValueParser<"async", string> {
  return {
    $mode: "async",
    metavar: "REF",
    parse(input: string): Promise<ValueParserResult<string>> {
      return Promise.resolve({ success: true, value: input });
    },
    format(value: string): string {
      return value;
    },
    async *suggest(prefix: string): AsyncIterable<Suggestion> {
      const { $ } = await import("bun");
      const [branches, tags] = await Promise.all([
        $`git for-each-ref --format='%(refname:short)' refs/heads/`.text(),
        $`git for-each-ref --format='%(refname:short)' refs/tags/`.text(),
      ]);
      for (const ref of [...branches.split("\n"), ...tags.split("\n")]) {
        const trimmed = ref.trim();
        if (trimmed && trimmed.startsWith(prefix)) {
          yield { kind: "literal", text: trimmed };
        }
      }
    },
  };
}

Notice that parse() returns Promise.resolve() even though it's synchronous. This is because the ValueParser<"async", T> type requires all methods to use async signatures. Lucas pointed out this is a minor ergonomic issue. If only suggest() needs to be async, you still have to wrap parse() in a Promise.

I considered per-method mode granularity (e.g., ValueParser<ParseMode, SuggestMode, T>), but the implementation complexity would multiply substantially. For now, the workaround is simple enough:

// Option 1: Use Promise.resolve()
parse(input) {
  return Promise.resolve({ success: true, value: input });
}

// Option 2: Mark as async and suppress the linter
// biome-ignore lint/suspicious/useAwait: sync implementation in async ValueParser
async parse(input) {
  return { success: true, value: input };
}

What it cost

Supporting dual modes added significant complexity to Optique's internals. Every combinator needed updates:

  • Type signatures grew more complex with mode parameters
  • Mode propagation logic had to be added to every combinator
  • Dual implementations were needed for sync and async code paths
  • Type casts were sometimes necessary in the implementation to satisfy TypeScript

For example, the object() combinator went from around 100 lines to around 250 lines. The internal implementation uses conditional logic based on the combined mode:

if (combinedMode === "async") {
  return {
    $mode: "async" as M,
    // ... async implementation with Promise chains
    async parse(context) {
      // ... await each field's parse result
    },
  };
} else {
  return {
    $mode: "sync" as M,
    // ... sync implementation
    parse(context) {
      // ... directly call each field's parse
    },
  };
}

This duplication is the cost of supporting both modes without runtime overhead for sync-only use cases.

Lessons learned

Listen to users, but validate with prototypes

My initial instinct was to resist async support. Lucas's persistence and concrete examples changed my mind, but I validated the approach with a prototype before committing. The prototype revealed practical issues (like TypeScript inference limits) that pure design analysis would have missed.

Backward compatibility is worth the complexity

Making "sync" the default mode meant existing code continued to work unchanged. This was a deliberate choice. Breaking changes should require user action, not break silently.

Unified mode vs per-method granularity

I chose unified mode (all methods share the same sync/async mode) over per-method granularity. This means users occasionally write Promise.resolve() for methods that don't actually need async, but the alternative was multiplicative complexity in the type system.

Designing in public

The entire design process happened in a public GitHub issue. Lucas, Giuseppe, and others contributed ideas that shaped the final API. The runSync()/runAsync() distinction came directly from Lucas's feedback.

Conclusion

This was one of the more challenging features I've implemented in Optique. TypeScript's type system is powerful enough to encode the “any async means all async” rule at compile time, but getting there required careful design work and prototyping.

What made it work: conditional types like ModeValue<M, T> can bridge the gap between sync and async worlds. You pay for it with implementation complexity, but the user-facing API stays clean and type-safe.

Optique 0.9.0 with async support is currently in pre-release testing. If you'd like to try it, check out PR #70 or install the pre-release:

npm  add       @optique/core@0.9.0-dev.212 @optique/run@0.9.0-dev.212
deno add --jsr @optique/core@0.9.0-dev.212 @optique/run@0.9.0-dev.212

Feedback is welcome!

Read more →
4
1
3
0
0
0
0
0
0

바이브코딩이 취미가 되어가는 것일까요? 그새 또 뭔가 하나를 뚝딱여왔습니다... be-music-script라는 동인 리듬게임 에뮬레이터 비슷한 친구의 플레이 로그를 정리해주는 서비스를 만들어봤어요. 많은 리듬게임 유저들은 자기가 얼마나 잘했는지 자랑하고 싶어하는데, db 파일을 읽은 뒤 당일의 멋진 성과들을 정리해주는 서비스입니다. 제가 쓰려고 만든건데 이것도 결국 엔지니어링? 결과물인걸까? 싶어 올려봅니다.

요즘 AI의 도움 덕분에 아이디어를 구현하는게 두렵지 않아졌다는 기분이 드네요. https://sonohoshi.github.io/gosubms/

be-music-script라는 동인 리듬게임 에뮬레이터 비슷한 친구의 플레이 로그를 정리해주는 서비스, '고수븜스'의 스크린샷입니다.
제 플레이 데이터를 이용, 오늘 어떤 기록을 경신하였는지 보여주고 있습니다.
3
1
0
1
0
0
0
1
1

今天去吃doner然後那個老闆看起來超兇,我們又在門口摸了半小時的貓,他突然過來趕貓,我下意識以為他覺得貓打擾他做生意了,結果他把貓趕到小巷子裡面之後放了一盤吃的給他 :gblackcat_wagging_faster:

0
0
1

昨日の牛すじ赤ワイン煮込みをパスタにアレンジして、自家製のコーングリッツ食パン(これも余り物)とインスタントのオニオンスープを合わせました

1

カバレッジに対して利用者数は少なく感じるけど採算どうなんだろう。

スペースX、2025年は165回の「ファルコン 9」打ち上げ–スターリンク利用者は900万人超 - UchuBiz uchubiz.com/article/new68549/

0
0
1

정교유착에 대한 마땅한 후과가 뒤따라야 할 것이다. 그리고 그와 별개로, 이게 통일교가 실행해서 이상한 일이라면 다른 무슨 종교단체나 이익집단이 실행한다고 좋게 넘어갈 수 있는 일이 아니라는 점도 좀 명확해졌으면 좋겠다.

RE: https://bsky.app/profile/did:plc:uabkr6tn7ru4b4e5e6udleuf/post/3mbgkta7ntk22

0
0

『ネタニヤフ調書』観てきた。

これ、統一教会との癒着や裏金の問題をネタニヤフの汚職裁判と重ねると、今日本で起こっていることがまんまイスラエルの後追いに見えるのが怖い 。

それでもイスラエルにはまだ骨のあるメディアがあって、39週にも及ぶデモが起こる。それでも虐殺は止まらない。じゃあ日本はこのままだとどうなる?それが心底怖い。

ドキュメンタリー映画「ネタニヤフ調書」のチラシとパンフレット。葉巻を持った白人男性の背後に爆炎と煙。映画館、ポレポレ東中野の看板。入口を示す右向きの矢印。
0
0
0
0
0

Are there any glasses frames which have good quality cameras in them for POV shots which are not META connected? Feels like not an impossibility if you were not caring about the "hiddenness" of the cameras.

There are some videos I'd like to make of stuff like soldering, woodwork, etc, where a POV shot would be really good. Feels like an ideal way to shoot these would be with camera-glasses. I see general aviation content where this has been done, but with meta/rayban.

Is there a non-meta alt?

0
1

자신들이 하는 행동 하나하나가 사회에 어떤 영향을 끼치고 변화를 이끌 수 있는 위치라는 자각이 없는 모습만 보여준다. 비단 일론 머스크뿐만 아니라 다들 머스크가 되지 못했지 전형적인 기술 자유주의자의 모습을 띄고 있다. 책임은 회피하고 이익을 극대화하는데만 집중되어 있으며 규제 기관은 자신들이 하는 기술적 구원을 방해하는 세력이라고 생각하고 있다. FSD가 인간을 교통사고라는 위험에서, AI가 인간을 노동으로부터 해방시켜 줄 것이라는 착각은 제발 꿈속에서만 꾸고 현실을 바라봐야 한다.

0

엑스의 AI 이미지 편집 기능은 여러모로 충격적이었다. 창작자를 엿먹이는 행위일 뿐만 아니라 소셜 미디어 운영자로서 안전을 고려하지 않은 결정이며, 이는 머스크 인수 후 트위터 블루 개편 과정에서 보여준 모습과도 일맥상통한다. 머스크는 일단 저지르고 보는 스타일이며 이러한 행동은 엑스뿐만 아니라 테슬라에서도 나타나고 지키지도 못할 말들을 내뱉는 행위가 이를 뒷받침한다. 요즘 들어 더 크게 느껴지고 있는 것 중 하나인데, 테크 브로들은 자신들이 인류를 구원하는 존재라고 생각하는 착각 좀 버렸으면 좋겠다. 🧵

0
0
0

바이브코딩이 취미가 되어가는 것일까요? 그새 또 뭔가 하나를 뚝딱여왔습니다... be-music-script라는 동인 리듬게임 에뮬레이터 비슷한 친구의 플레이 로그를 정리해주는 서비스를 만들어봤어요. 많은 리듬게임 유저들은 자기가 얼마나 잘했는지 자랑하고 싶어하는데, db 파일을 읽은 뒤 당일의 멋진 성과들을 정리해주는 서비스입니다. 제가 쓰려고 만든건데 이것도 결국 엔지니어링? 결과물인걸까? 싶어 올려봅니다.

요즘 AI의 도움 덕분에 아이디어를 구현하는게 두렵지 않아졌다는 기분이 드네요. https://sonohoshi.github.io/gosubms/

be-music-script라는 동인 리듬게임 에뮬레이터 비슷한 친구의 플레이 로그를 정리해주는 서비스, '고수븜스'의 스크린샷입니다.
제 플레이 데이터를 이용, 오늘 어떤 기록을 경신하였는지 보여주고 있습니다.
3

I try to tell stories through my pictures. Usually they are snapshots of something that caught my attention, and connected with a song, a word, a feeling in that moment or a motif that tells me a story. And I'm thrilled, when people like them and even comment, and I don't blame them, when they don't, because they are my spurs of the moment. But in this case, something truly special happened. A dear friend not only liked my pictures, but they got inspired to write a poem. Not once, but for 11 of my pictures, and one of their own, for every month of this year. Now the first sheet hangs over my kettle to see and read every time I make myself a tea ❤️ See alt-txt for the translation of the poem

A calendar sheet for January, with the days of the month and underneath a picture of a heart drawn in a very dirty window. On the other side of the window, hangs a lamp that shines on the heart. On the side and on the bottom of the picture are each a verse of a poem. The poem is in german and goes like this: 
Januar:

Du bist das Licht
das mich wärmt
du bist der Schimmer
der meine Angst vertreibt
du bist das Strahlen
das mir Hoffnung gibt

Ich bin dein Herz
du leuchtest in mir
Ich bin dein Herz
du bist sicher hier
Ich bin dein Herz
Ich bin dein.

In English ist would be something to the effect of:
You are the light
that warms me
you are the glow
that dispels my fear
you are the radiance
that gives me hope

I am your heart
you shine within me
I am your heart
you are safe here
I am your heart
I am yours.
0

說話或寫文句前無需使用“我想”或“我猜”是霹靂力距。

0
0
0
0
0
1