- 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
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 isolation —
import 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.tswithglobals: 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.