Files
pubsub/docs/architecture/build-distribution.md
glm-5.1 a12c52b407 fix: add close() lifecycle methods to all adapters, fix WS client handler preservation, add Worker thread context guard
- Add close() to Redis, WS Client, WS Server, Worker Host, Worker Thread adapters
  for graceful teardown (cleanup subscriptions, restore handlers, clear maps)
- WS Client now saves/restores original onmessage (consistent with WS Server)
- WS Client dispatchEvent/addEventListener/removeEventListener are no-ops after close()
- WS Server close() removes all connections and clears local listeners
- Redis close() unsubscribes all channels and removes message listener
- Worker Host/Thread close() restore original onmessage and clear callbacks
- Worker Thread throws clear error if globalThis.postMessage is unavailable
- Add double-call guard to WS Server removeConnection
- Export new adapter interface types (RedisEventTarget, WebSocketClientEventTarget, etc.)
- Add sideEffects: false to package.json for tree-shaking
- Update architecture docs: lifecycle section, close() contract, adapter status updates
- 22 new tests covering close(), handler restoration, idempotency, and context guard
2026-05-08 16:19:16 +00:00

4.1 KiB

status, last_updated
status last_updated
draft 2026-05-01

Build & Distribution

Dependencies, project structure, tree-shaking, sub-path exports, and build targets.

Dependencies

No runtime dependencies. The Repeater class is inlined from @repeaterjs/repeater (MIT) — no external package required.

Package Type Purpose
(none) runtime
ioredis peer (optional) Redis client. Only imported by event-target-redis.ts. Type-only import at compile time.
@rayhanadev/iroh peer (optional, future) Iroh NAPI-RS binding. Only imported by event-target-iroh.ts.

No logger dependency. No TypeBox dependency (call protocol and schemas moved to @alkdev/operations).

Project Structure

@alkdev/pubsub/
  src/
    index.ts                 # Barrel: re-exports core API + operators
    types.ts                 # TypedEvent, TypedEventTarget, EventEnvelope
    create_pubsub.ts         # createPubSub factory (adapted from graphql-yoga)
    operators.ts             # filter, map, pipe, take, reduce, toArray,
                              #   batch, dedupe, window, flat, groupBy, chain, join
    repeater.ts              # Inlined from @repeaterjs/repeater (MIT)
    event-target-redis.ts    # createRedisEventTarget (peer dep: ioredis)
    event-target-websocket-client.ts  # createWebSocketClientEventTarget
    event-target-websocket-server.ts  # createWebSocketServerEventTarget, WebSocketLike, SpokeEventTarget
    event-target-worker.ts   # createWorkerHostEventTarget, createWorkerThreadEventTarget
    # Future adapters:
    # event-target-iroh.ts       # (peer dep: @rayhanadev/iroh)
  test/
    create_pubsub.test.ts
    operators.test.ts
    event-target-redis.test.ts
    event-target-websocket-client.test.ts
    event-target-websocket-server.test.ts
    event-target-worker.test.ts
    integration-pubsub-redis.test.ts
    integration-websocket.test.ts
  docs/
    architecture/
    architecture/
    research/
  package.json
  tsconfig.json
  tsup.config.ts
  vitest.config.ts

Sub-Path Exports

We use explicit sub-path exports rather than barrel-only + tree-shaking. Each adapter is importable by its own path:

{
  "exports": {
    ".": { ... },
    "./event-target-redis": { ... },
    "./event-target-websocket-client": { ... },
    "./event-target-websocket-server": { ... },
    "./event-target-worker": { ... },
    "./event-target-iroh": { ... }
  }
}

Why Sub-Path Exports

  • Explicit — doesn't rely on bundler tree-shaking behavior
  • Peer dep isolationimport from '@alkdev/pubsub/event-target-redis' makes the dependency on ioredis explicit at the import site
  • Consistent with typemap pattern — typemap's peer deps (zod, valibot, typebox) are each their own module; sub-path exports make this explicit at the package boundary

Sub-path entries are added as adapters are implemented. The barrel index.ts also re-exports everything for convenience — consumers who want tree-shaking can import from the barrel and rely on their bundler.

Peer Dependencies

Peer Dep Required By Optional
ioredis@^5.0.0 event-target-redis Yes
@rayhanadev/iroh event-target-iroh (future) Yes

Optional peer deps means npm install @alkdev/pubsub does NOT install ioredis or iroh. Consumers opt in by installing the peer dep and importing from the sub-path.

Build

  • Tool: tsup — produces dual ESM + CJS with declarations automatically
  • Entry points: src/index.ts, src/event-target-redis.ts, plus future adapters
  • Format: ESM + CJS
  • Target: es2022
  • Splitting: enabled (tsup code splitting for shared chunks)

Testing

  • Runner: vitest — matches taskgraph, natural fit with tsup/Node build pipeline
  • Config: vitest.config.ts with globals: true

Targets

  • Publish: npm (@alkdev/pubsub)
  • Runtime: Node 18+, Deno, Bun — pure JS (except iroh adapter which requires NAPI-RS)
  • Deno compatibility: Source is standard TypeScript with no Deno-specific APIs. Deno can import from npm or JSR.