Skip to content

feat: createHierarchicalPubSub<E> — typed + hierarchical bus#93

Merged
AZagatti merged 2 commits into
mainfrom
feat/hierarchical-typed-bus
Jun 25, 2026
Merged

feat: createHierarchicalPubSub<E> — typed + hierarchical bus#93
AZagatti merged 2 commits into
mainfrom
feat/hierarchical-typed-bus

Conversation

@AZagatti

Copy link
Copy Markdown
Collaborator

Fills the last capability gap: createPubSub is flat-typed, the PubSub singleton is hierarchical-untyped — this is both. Publishing order.created also notifies order, with full payload typing. Additive/non-breaking.

  • DescendantKey<E, K> (template-literal type): the keys a subscription to K receives — K + dotted descendants (order matches order.created, not orders); symbols match only themselves. Design adversarially reviewed with tsc proofs.
  • HierarchicalPubSub<E> + createHierarchicalPubSub<E>(options?) (reuses the existing hierarchical runtime; subscribe/on/subscribeOnce use a defaulted D param so the descendant union is computed once).
  • Documented caveats: a handler's data is narrowed by a property check, not by token (TS can't correlate the two params); only declared keys publish/subscribe.

New CI infra: vitest --typecheck type-level tests (test:types script + CI step, no new deps). hierarchical.test-d.ts pins DescendantKey edge cases + publish/subscribe/getSnapshot typing; runtime spec covers delivery, retained getSnapshot, and onError.

146 runtime tests + 7 type tests, 100% coverage, lint clean, e2e 12/12, attw 🌟 + publint clean, bundle within budget. README documented.

🤖 Generated with Claude Code

AZagatti and others added 2 commits June 24, 2026 20:57
Fills the last capability gap: createPubSub is flat-typed, the PubSub singleton
is hierarchical-untyped — this is both. Publishing 'a.b.c' also notifies 'a.b'
and 'a' subscribers, with full payload typing.

- `DescendantKey<E, K>` (template-literal type): the keys a subscription to K
  receives — K plus dotted descendants ('order' matches 'order.created', not
  'orders'); symbol keys match only themselves. Adversarially reviewed with tsc.
- `HierarchicalPubSub<E>` interface + `createHierarchicalPubSub<E>(options?)`
  factory (reuses the existing hierarchical runtime). subscribe/on/subscribeOnce
  use a defaulted `D` param so the descendant union is computed once.
- Caveats (documented): a handler's `data` is narrowed by a property check, not
  by `token` (TS can't correlate the two params); only declared keys publish/sub.

Testing infra: `vitest --typecheck` type-level tests (`test:types` script, CI
step, no new deps) — `hierarchical.test-d.ts` pins DescendantKey edge cases
(prefix exclusion, symbols, leaf) + publish/subscribe/getSnapshot typing. Plus
runtime tests for delivery, retained getSnapshot, and onError.

Additive/non-breaking. 146 runtime tests + 7 type tests, 100% coverage, lint
clean, e2e 12/12, attw + publint clean, size within budget. README documented.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Adversarial review fixes:
- MAJOR: tsdown entry excluded *.spec.ts but not *.test-d.ts, so the type-test
  file (and its bundled vitest/expect-type chunks) was shipped to npm. Add
  '!src/**/*.test-d.ts' to the entry — dist is clean again, tarball back to size.
  (attw/publint didn't catch it — they only validate declared exports.)
- index.spec asserts createHierarchicalPubSub + useBusState in the public API.
- e2e (CJS+ESM) import-smoke createHierarchicalPubSub + useBusState.
- hierarchical.spec adds subscribeOnce + on()/AbortSignal coverage so the spec
  is self-contained (not only via the singleton tests).

148 runtime tests + 7 type tests, 100% coverage, lint clean, e2e 12/12, attw +
publint clean, no test artifacts in dist.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@AZagatti AZagatti merged commit d664ac8 into main Jun 25, 2026
2 checks passed
@AZagatti AZagatti deleted the feat/hierarchical-typed-bus branch June 25, 2026 00:11
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant