- 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
109 lines
4.1 KiB
Markdown
109 lines
4.1 KiB
Markdown
---
|
|
status: draft
|
|
last_updated: 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:
|
|
|
|
```json
|
|
{
|
|
"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.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. |