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
0
0
0
0
0
0
0
0
0
0
0
1
0
0
0
0
0

Pas de chauffage central mais des lits chauffés… Ce que la ville la plus froide du monde nous enseigne

theconversation.com/pas-de-cha

Harbin se situe dans le nord-est de la Chine. Les températures hivernales y descendent régulièrement jusqu’à −30 °C et, en janvier, même les journées les plus douces dépassent rarement −10 °C. Avec environ 6 millions d’habitants aujourd’hui, Harbin est de loin la plus grande ville du monde à connaître un froid aussi constant.

0
0
0
0
0
0
0
0

월급 안 준다는 말에 안 간다니까 지 아들(나한텐 조카) 친구들도 다 그랬는데 너만 왜 그러냐 이러더라 아니 그럼 니 아들내미 친구들 등쳐드신거죠 뭐... 니 자식 주변에 친구가 그 수준이라는 건 니 자식도... 뭐 아시겠죠?

0

내가 눈치 없이 군건 맞는데 좀 싸한 거임 그러고선 그 다음날 수습 기간(3주)동안은 급여를 안 주겠다네? 그래서 걍 바로 안 나감 또 그걸로 난리부르스를 떨더라고 그러면서 또 고소할까봐 무서웠는지 바로 입금 들어오더라 지금 생각해보면 진짜 잘 튀었다고 생각함ㅋㅋㅋㅋ

0

"휘발유차에 경유를 탔어" 하니까 옛날에 친척형이 하는 주유소에서 알바할 뻔한 썰 생각남 사촌 형이 주유소 사장인데 알바 해보겠냐는 제안을 줘서 해보기로 함 근데 첫날에 조카(정확히는 5촌 당조카인데 걍 그렇게 불러옴 암튼)한테 교육 받고 있는데 걔가 잘못 넣음 그걸 형수가 "아휴 알바가 오늘 처음 와서..." 이러고 있는데 내가 "제가 안 넣었어요" 함(당연함. 교육 받고 있었음.) 그걸 가지고 나중에 난리를 떠는 거임 너는 눈치도 없냐면서

0
1
0
0
1
0
1
0

We have seen a recent wave of impersonation accounts which claim to be another fediverse user, often with a similar username and boosting some posts from the original account, before reaching out to followers over private mentions and claiming to have issues with their original account.

Please report them if you see them!

0
0
2

잇창명 EatChangmyeong💕🐱 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

RE: mastodon.social/@staff/1158165

I guess I should point out that my many catsalad accounts on Mastodon are chain verified to each other (usually alphabetical) with ties to main (catsalad@infosec.exchange) to avoid impersonation like this.

Of course, 99% of my activity is here anyway, and I'm not going anywhere :3

0
0
1
1

동의. 성욕이 아니라 마운트 잡고 싶은 거임. 몰카에 대한 페티쉬와 동일한 기작이다. 내 앞에서 넌 발가벗겨질 수밖에 없다는, 내 손아귀에 있다는 감각.

RE: https://bsky.app/profile/did:plc:3w3pnmhktm3hosp4chu2rscf/post/3mbgeeqblks2d

1
0

アンチ・アクションの図録の中嶋泉さんの論考読んだけど、うーん...。なんというか、本人が「アンチ・アクション」という理論的なフレーミングを提案したのが、「アクション・ペインティング」の理論的なフレームの抱えるジェンダー性を解体する目的だったのが、個別具体的な作品分析に入りこんでいけばほとんど役に立っていない。本人によってこの理論がほとんど無効化されているようで、残念である。

0

For the past 15 years I’ve loved making coffee every morning. Grinding the beans with the kids pouring the French press and welcoming the new day.

This Christmas my parents in law gifted us a fully automatic coffee machine and just like that the ritual was gone. One button and the coffee appears. Efficient yes, but the vibes gone.

I got replaced by a machine 🥺

0
1

Twitter、拡張機能で「おすすめ」を非表示にしても、通知におすすめを出してくる。なぜ?と思ったら、このトピックというののせいらしい。

0
0

サイトをどうするか考えてたんだけど更新止まってるサイトが複数あるよりは更新されているサイトが少数の方が良さそうなので2サイトにまとめて1か月に1回更新日を設定しようという結論に行きついた
やめることも大事を投稿してくれた人・BTしてくれた人ありがとう

1

Und noch einmal wurde ein Kältebus der Berliner Stadtmission angezündet. Auch dieses Fahrzeug ist nun fahruntauglich. Ein Sicherheitsdienst konnte aber einen Verdächtigen auf frischer Tat festsetzen. Man hofft nun, dass sich die Fälle aufklären, die Anschläge gestoppt sind.

rbb24.de/panorama/beitrag/2026

0
1
2
0
0
0
0
0

BT うちでも卒論・修論時期はPCが壊れるなどしてデータを失う可能性が高いという噂があったのでDropboxとiCloudとGoogleDriveとHDDとUSBにバックアップとってたな 必要にはならなかったけど

1
0
1

remember that time this time last year when I said I'm reading The Left Hand of Darkness by Ursula K LeGuin because the cover has the Cyrillic 'I' in it?

well, Carol was reading it the season finale of Pluribus and I'm gonna assume she was reading it for the same reason ;)

0