Decompose architecture into atomic, dependency-ordered tasks

19 tasks covering core testing, Redis hardening, WebSocket client/server
adapters, Worker adapter, and final review gates. Iroh adapters are tracked
as a deferred placeholder blocked on the @alkdev/iroh fork.

Phases: core validation → Redis hardening → review gate → WebSocket
adapters → review gate → Worker adapter → review gate → final validation.
This commit is contained in:
2026-05-08 05:50:43 +00:00
parent be7fe67145
commit 1306716897
19 changed files with 931 additions and 0 deletions

View File

@@ -0,0 +1,50 @@
---
id: core-pubsub-tests
name: Write tests for createPubSub, EventEnvelope, and in-process event target
status: pending
depends_on: []
scope: moderate
risk: low
impact: component
level: implementation
---
## Description
The core `createPubSub` factory, `EventEnvelope` type, and the in-process (default `EventTarget`) path have no tests. These are the foundation of the entire package — every adapter builds on `createPubSub`. Write comprehensive tests that verify the core contract.
The architecture specifies these behaviors:
- `createPubSub()` with no config uses `new EventTarget()` (in-process)
- `createPubSub({ eventTarget })` uses a custom event target
- `publish(type, id, payload)` dispatches a `CustomEvent` with type `"type:id"` and `detail` as `EventEnvelope`
- `subscribe(type, id)` returns a `Repeater<EventEnvelope>` (async iterable)
- `publish` throws on event types starting with `__` (reserved for adapter control)
- Topic scoping uses the `type:id` convention
- `subscribe` cleanup: breaking out of the `for await` loop removes the listener
## Acceptance Criteria
- [ ] `test/create_pubsub.test.ts` exists and passes
- [ ] Test: `publish` dispatches event with correct `type:id` topic
- [ ] Test: `publish` throws on `__`-prefixed event types
- [ ] Test: `subscribe` returns async iterable that yields `EventEnvelope` objects
- [ ] Test: `subscribe` envelope has correct `type`, `id`, `payload` fields
- [ ] Test: subscriber receives events only for the subscribed topic (type:id matching)
- [ ] Test: multiple subscribers on the same topic all receive events
- [ ] Test: subscriber cleanup — breaking out of `for await` loop removes the listener
- [ ] Test: `createPubSub` with custom `eventTarget` dispatches to that target
- [ ] Test: `createPubSub` without `eventTarget` uses `new EventTarget()` (in-process)
## References
- docs/architecture/api-surface.md
- src/create_pubsub.ts
- src/types.ts
## Notes
> To be filled by implementation agent
## Summary
> To be filled on completion

View File

@@ -0,0 +1,49 @@
---
id: core-operators-tests
name: Write tests for all stream operators
status: pending
depends_on: []
scope: moderate
risk: low
impact: component
level: implementation
---
## Description
The `operators.ts` module exports 13 operators (`filter`, `map`, `pipe`, `take`, `reduce`, `toArray`, `batch`, `dedupe`, `window`, `flat`, `groupBy`, `chain`, `join`). None have tests. Each operator works with `AsyncIterable` input; Repeater-returning operators (`filter`, `map`) provide backpressure-aware push semantics.
The operators are adapted from graphql-yoga (`filter`, `map`, `pipe`) and added from the async-utility reference (`take`, `reduce`, `toArray`, `batch`, `dedupe`, `window`, `flat`, `groupBy`, `chain`, `join`).
## Acceptance Criteria
- [ ] `test/operators.test.ts` exists and passes
- [ ] `filter` — filters items by predicate; type-narrowing overload works
- [ ] `filter` — async predicate support
- [ ] `map` — transforms items; async mapper support
- [ ] `pipe` — composes 1-5 functions
- [ ] `pipe` — compose with `subscribe`: `pipe(pubsub.subscribe("myEvent", id), filter(...), map(...))`
- [ ] `take` — yields first N items, then stops
- [ ] `reduce` — reduces to single value
- [ ] `toArray` — collects all items into array
- [ ] `batch` — groups into arrays of `size`
- [ ] `batch` — yields remaining items if not a full batch
- [ ] `dedupe` — yields only unique items
- [ ] `window` — sliding window of `size` items, advancing by `step`
- [ ] `flat` — flattens `AsyncIterable<T[]>` into `AsyncIterable<T>`
- [ ] `groupBy` — groups items by key into `Map` (terminal operation)
- [ ] `chain` — concatenates multiple async iterables
- [ ] `join` — streaming join between two sources on matching keys
## References
- docs/architecture/api-surface.md (Operators section)
- src/operators.ts
## Notes
> To be filled by implementation agent
## Summary
> To be filled on completion

View File

@@ -0,0 +1,50 @@
---
id: redis-adapter-tests
name: Write tests for Redis event target adapter
status: pending
depends_on: [core-pubsub-tests]
scope: moderate
risk: medium
impact: component
level: implementation
---
## Description
The Redis event target adapter (`event-target-redis.ts`) is implemented but has zero tests. The architecture doc lists 7 specific test scenarios. Tests need a running Redis instance or a mock. Since this is a dev dependency, we can use `ioredis-mock` or start a Redis container in CI. The test strategy needs to handle the Redis dependency.
The architecture specifies these test scenarios:
1. Publish path — dispatchEvent sends to Redis with correct channel and serialized envelope
2. Subscribe path — addEventListener subscribes to Redis, onMessage dispatches to local listeners
3. Unsubscribe — removeEventListener unsubscribes from Redis when no listeners remain for a topic
4. Topic scoping — type:id topics are correctly formed
5. Envelope serialization — full `{ type, id, payload }` round-trips through JSON
6. Multiple listeners — multiple listeners on same topic, single Redis subscribe
7. Error propagation — what happens on connection failure
Note: test 7 (error propagation) is partially blocked by the missing error handling in the current implementation (tracked in task `redis-channel-prefix-and-error-handling`). The test should exist but may need to be adjusted once error handling is added.
## Acceptance Criteria
- [ ] `test/event-target-redis.test.ts` exists and passes
- [ ] Test approach for Redis dependency is decided (mock, container, or ioredis-mock)
- [ ] Test: dispatchEvent publishes correct channel and serialized envelope
- [ ] Test: addEventListener subscribes to Redis and dispatches to local listeners
- [ ] Test: removeEventListener unsubscribes from Redis when no listeners remain
- [ ] Test: type:id topic scoping works correctly
- [ ] Test: EventEnvelope round-trips through JSON serialization
- [ ] Test: multiple listeners on same topic result in single Redis subscribe
- [ ] Test: custom serializer option works
## References
- docs/architecture/event-targets/redis.md
- src/event-target-redis.ts
## Notes
> To be filled by implementation agent
## Summary
> To be filled on completion

View File

@@ -0,0 +1,45 @@
---
id: redis-channel-prefix-and-error-handling
name: Add channel prefix and error handling to Redis event target
status: pending
depends_on: [redis-adapter-tests]
scope: narrow
risk: low
impact: component
level: implementation
---
## Description
The architecture doc for the Redis adapter lists two limitations that need to be addressed:
1. **No channel prefix** — raw event types as channel names risk collision in shared Redis instances. The adapter should support a configurable prefix like `createRedisEventTarget({ ..., prefix: "alk:events:" })`.
2. **No error handling** — connection failures, reconnection, and message parse errors are not handled. The adapter should:
- Handle serializer parse errors gracefully (log warning, skip message)
- Handle subscribe client disconnect gracefully (registered callbacks remain but won't fire)
- Note that in-flight messages after unsubscribe are harmless (empty callback set = no-op)
These are quality-of-life improvements that make the adapter production-ready.
## Acceptance Criteria
- [ ] `CreateRedisEventTargetArgs` accepts an optional `prefix` field (string, default `""`)
- [ ] When `prefix` is set, channel names are prefixed: `"alk:events:call.responded:uuid-123"`
- [ ] Both publish and subscribe use the same prefix convention
- [ ] Malformed messages from Redis (serializer parse errors) are caught and logged, not thrown
- [ ] Existing tests updated to cover prefix behavior
- [ ] New tests for prefix behavior and error handling
## References
- docs/architecture/event-targets/redis.md (Limitations section)
- src/event-target-redis.ts
## Notes
> To be filled by implementation agent
## Summary
> To be filled on completion

View File

@@ -0,0 +1,45 @@
---
id: review-core-and-redis
name: Review core module tests and Redis adapter
status: pending
depends_on: [core-pubsub-tests, core-operators-tests, redis-adapter-tests]
scope: narrow
risk: low
impact: phase
level: review
---
## Description
Review checkpoint before moving to new adapter implementations. Verify that:
- Core tests cover the `createPubSub` contract thoroughly (publish, subscribe, topic scoping, `__` rejection, cleanup)
- Operator tests cover all 13 operators with edge cases
- Redis adapter tests pass reliably with real Redis or mock
- Code follows architecture conventions (no unnecessary comments, MIT headers on forked files)
- Build passes (`npm run build`)
- Type-check passes (`npm run lint / tsc --noEmit`)
- Test suite passes (`npm test`)
This is a quality gate before implementing new adapters. Mistakes in the core types or contract will cascade to every adapter.
## Acceptance Criteria
- [ ] `npm run build` passes cleanly
- [ ] `npm run lint` passes (tsc --noEmit)
- [ ] `npm test` passes with all core + Redis tests
- [ ] No regressions in existing functionality
- [ ] Core tests align with architecture spec (api-surface.md)
- [ ] Code follows project conventions (no comments in source, license headers on forked files)
## References
- docs/architecture/api-surface.md
- docs/architecture/event-targets/redis.md
## Notes
> To be filled by implementation agent
## Summary
> To be filled on completion

View File

@@ -0,0 +1,56 @@
---
id: websocket-client-adapter
name: Implement WebSocket client event target adapter
status: pending
depends_on: [review-core-and-redis]
scope: moderate
risk: medium
impact: component
level: implementation
---
## Description
Implement the `createWebSocketClientEventTarget` adapter as specified in `docs/architecture/event-targets/websocket-client.md`.
This is a symmetric (single-connection) adapter that wraps a `WebSocket` connection for the spoke/client side. It's bidirectional — can both send and receive events. It must implement subscription forwarding using `__subscribe`/`__unsubscribe` control events per ADR-003.
Key requirements from the architecture:
- Takes an already-connected `WebSocket` (caller handles connection lifecycle)
- `dispatchEvent``ws.send(JSON.stringify(event.detail))`
- `addEventListener` → register local listener + send `__subscribe` control event on first listener for topic
- `removeEventListener` → remove local listener + send `__unsubscribe` when no listeners remain
- Subscription reference counting: `__subscribe` sent only on first `addEventListener` per topic
- Malformed JSON from server → silently ignored, log warning
- Control events (`__subscribe`, `__unsubscribe`) received from server → silently ignored
- `ws.send()` failure → error propagates to caller, no retry
## Acceptance Criteria
- [ ] `src/event-target-websocket-client.ts` exists
- [ ] Implements `createWebSocketClientEventTarget(ws: WebSocket): TypedEventTarget<TEvent>`
- [ ] `dispatchEvent` serializes envelope and calls `ws.send()`
- [ ] `addEventListener` registers local listener and sends `__subscribe` on first listener for topic
- [ ] Subscription reference counting: only one `__subscribe` per topic regardless of listener count
- [ ] `removeEventListener` removes local listener and sends `__unsubscribe` when no listeners remain
- [ ] Malformed JSON messages from server are silently ignored (logged)
- [ ] Control events received from server are silently ignored
- [ ] `ws.onmessage` parses envelope, creates `CustomEvent` with `type:id` topic, dispatches to listeners
- [ ] `ws.send()` failure propagates to caller
- [ ] No comments in source code (project convention)
- [ ] Sub-path export added to `package.json` and `tsup.config.ts`
- [ ] Barrel re-export added to `src/index.ts`
## References
- docs/architecture/event-targets/websocket-client.md
- docs/architecture/decisions/003-subscription-control-protocol.md
- src/types.ts (TypedEventTarget, TypedEvent, EventEnvelope)
## Notes
> To be filled by implementation agent
## Summary
> To be filled on completion

View File

@@ -0,0 +1,52 @@
---
id: websocket-client-tests
name: Write tests for WebSocket client event target adapter
status: pending
depends_on: [websocket-client-adapter]
scope: moderate
risk: medium
impact: component
level: implementation
---
## Description
Write tests for `createWebSocketClientEventTarget`. Since WebSocket is a browser API, we need to mock `WebSocket` in tests. Use a lightweight mock or `vitest` mocking to simulate WebSocket behavior.
Test scenarios from the architecture doc:
1. Send path — dispatchEvent serializes envelope and calls ws.send
2. Receive path — ws.onmessage parses envelope, creates CustomEvent, dispatches to listeners
3. Topic scoping — type:id topics correctly formed from envelope
4. Subscription forwarding — addEventListener sends __subscribe on first listener for a topic
5. Subscription dedup — multiple addEventListener for the same topic sends only one __subscribe
6. Unsubscription forwarding — removeEventListener sends __unsubscribe when no listeners remain
7. Connection close — ws.onclose behavior
8. Reconnection — new connection + new event target + re-subscribe restores subscriptions
9. Multiple listeners — multiple addEventListener on same topic receives events correctly
## Acceptance Criteria
- [ ] `test/event-target-websocket-client.test.ts` exists and passes
- [ ] Mock strategy for WebSocket is established (vitest mock, mock WebSocket class, or similar)
- [ ] Test: dispatchEvent sends JSON-stringified envelope via ws.send
- [ ] Test: addEventListener with first listener for a topic sends `__subscribe` control event
- [ ] Test: multiple addEventListener for same topic sends only one `__subscribe`
- [ ] Test: removeEventListener with last listener sends `__unsubscribe`
- [ ] Test: removeEventListener with remaining listeners does NOT send `__unsubscribe`
- [ ] Test: malformed JSON from server is silently ignored
- [ ] Test: control events received from server are silently ignored
- [ ] Test: ws.onmessage correctly dispatches to local listeners with type:id topic
- [ ] Test: multiple listeners on same topic all receive events
## References
- docs/architecture/event-targets/websocket-client.md (Test Plan section)
- src/event-target-websocket-client.ts
## Notes
> To be filled by implementation agent
## Summary
> To be filled on completion

View File

@@ -0,0 +1,69 @@
---
id: websocket-server-adapter
name: Implement WebSocket server event target adapter
status: pending
depends_on: [websocket-client-adapter]
scope: broad
risk: medium
impact: component
level: implementation
---
## Description
Implement the `createWebSocketServerEventTarget` adapter as specified in `docs/architecture/event-targets/websocket-server.md`.
This is a fan-out (multi-connection) adapter that manages multiple WebSocket connections for the hub/server side. It must implement topic-based fan-out with subscription tracking using `__subscribe`/`__unsubscribe` control events.
Key requirements from the architecture:
- Factory returns `WebSocketServerEventTarget` which extends `TypedEventTarget` with `addConnection(ws)` and `removeConnection(ws)`
- `addConnection` sets up `onmessage` and `onclose` handlers on the WebSocket
- `removeConnection` cleans up subscription maps and event handlers (but does NOT close the WebSocket)
- `dispatchEvent` sends to only connections subscribed to the event type (topic-based fan-out)
- `addEventListener` registers local listeners (aggregate from all spokes)
- Subscription tracking: `Map<string, Set<WebSocket>>` from topic to subscribed connections
- Control protocol: `__subscribe`/`__unsubscribe` messages from spokes update subscription map
- Backpressure: configurable threshold (default 1MB) — disconnect slow consumers
- `onConnection` and `onDisconnection` callbacks for lifecycle events
- Framework-agnostic: takes raw `WebSocket` instances, doesn't handle HTTP upgrade
- `dispatchEvent` always returns `true` (errors handled via side effects)
## Acceptance Criteria
- [ ] `src/event-target-websocket-server.ts` exists
- [ ] `createWebSocketServerEventTarget(options)` returns `WebSocketServerEventTarget`
- [ ] `WebSocketServerEventTarget` extends `TypedEventTarget` with `addConnection` and `removeConnection`
- [ ] `addConnection(ws)` sets up `onmessage` and `onclose` handlers
- [ ] `removeConnection(ws)` cleans up internal state but does not close the WebSocket
- [ ] `addConnection` `onclose` handler automatically calls `removeConnection`
- [ ] `dispatchEvent` sends only to connections subscribed to the event type
- [ ] `addEventListener` registers local listeners (aggregate events from all spokes)
- [ ] `__subscribe` control events from spokes add connection to topic's subscriber set
- [ ] `__unsubscribe` control events from spokes remove connection from topic's subscriber set
- [ ] Duplicate `__subscribe` is idempotent (Set handles naturally)
- [ ] Invalid topic format in control events is silently ignored
- [ ] Malformed JSON from spokes is silently ignored (logged)
- [ ] Backpressure: before `ws.send()`, check `bufferedAmount` against threshold
- [ ] Backpressure: exceeding threshold closes connection with code 1013
- [ ] `onBackpressure` callback called before disconnecting
- [ ] `onConnection` callback receives spoke event target and raw WebSocket
- [ ] `onDisconnection` callback receives spoke event target and raw WebSocket
- [ ] `dispatchEvent` always returns `true`
- [ ] Send failure (ws.send throws) catches error, removes connection, fires `onDisconnection`
- [ ] No comments in source code (project convention)
- [ ] Sub-path export added to `package.json` and `tsup.config.ts`
- [ ] Barrel re-export added to `src/index.ts`
## References
- docs/architecture/event-targets/websocket-server.md
- docs/architecture/decisions/003-subscription-control-protocol.md
- src/types.ts
## Notes
> To be filled by implementation agent
## Summary
> To be filled on completion

View File

@@ -0,0 +1,55 @@
---
id: websocket-server-tests
name: Write tests for WebSocket server event target adapter
status: pending
depends_on: [websocket-server-adapter, websocket-client-tests]
scope: moderate
risk: medium
impact: component
level: implementation
---
## Description
Write tests for `createWebSocketServerEventTarget`. This requires mocking multiple WebSocket connections and simulating the fan-out behavior.
Test scenarios from the architecture doc:
1. Topic-based fan-out — dispatchEvent sends only to connections subscribed to that event type
2. Subscription protocol — `__subscribe`/`__unsubscribe` control events correctly update the subscription map
3. Incoming aggregation — messages from any spoke dispatch to local listeners
4. Connection add/remove — new connections are tracked, disconnections clean up all subscriptions
5. Backpressure disconnect — slow consumers exceeding threshold are disconnected
6. Backpressure callback — `onBackpressure` is called before disconnecting
7. Direct messaging — events dispatched to `"direct:${spokeId}"` reach only the target spoke
8. Mixed topology — server adapter and client adapters can communicate bidirectionally
## Acceptance Criteria
- [ ] `test/event-target-websocket-server.test.ts` exists and passes
- [ ] Mock strategy for multiple WebSocket connections established
- [ ] Test: `addConnection`/`removeConnection` track connections correctly
- [ ] Test: `__subscribe` control event adds connection to topic's subscriber set
- [ ] Test: `__unsubscribe` control event removes connection from topic's subscriber set
- [ ] Test: `dispatchEvent` sends only to connections subscribed to that topic
- [ ] Test: connections NOT subscribed to a topic do NOT receive events for that topic
- [ ] Test: malformed JSON from spokes is silently ignored
- [ ] Test: invalid `__subscribe` with empty/malformed topic is ignored
- [ ] Test: backpressure threshold disconnect — connections exceeding `maxBufferedAmount` are closed with code 1013
- [ ] Test: `onBackpressure` callback fires before disconnect
- [ ] Test: `onConnection` callback receives spoke target and WebSocket
- [ ] Test: `onDisconnection` callback fires on connection close
- [ ] Test: `dispatchEvent` always returns `true`
- [ ] Test: send failure (ws.send throws) removes connection and fires `onDisconnection`
## References
- docs/architecture/event-targets/websocket-server.md (Test Plan section)
- src/event-target-websocket-server.ts
## Notes
> To be filled by implementation agent
## Summary
> To be filled on completion

View File

@@ -0,0 +1,45 @@
---
id: review-websocket-adapters
name: Review WebSocket client and server adapters
status: pending
depends_on: [websocket-client-tests, websocket-server-tests]
scope: narrow
risk: low
impact: phase
level: review
---
## Description
Review checkpoint after implementing both WebSocket adapters. These are the first fan-out adapter (server) and the first subscription-control adapter (client), so they establish patterns that the Worker and Iroh adapters will follow.
Verify:
- Subscription control protocol (`__subscribe`/`__unsubscribe`) is correctly implemented in both client and server
- Topic-based fan-out works end-to-end: client subscribes → server tracks → server fans out
- Backpressure protection works correctly
- Error handling matches the architecture spec
- Build, type-check, and test suite all pass
## Acceptance Criteria
- [ ] `npm run build` passes cleanly
- [ ] `npm run lint` passes
- [ ] `npm test` passes with all core + Redis + WebSocket tests
- [ ] WebSocket client subscription forwarding matches ADR-003
- [ ] WebSocket server fan-out and subscription tracking matches architecture spec
- [ ] No unnecessary comments in source (project convention)
- [ ] License headers present where needed
## References
- docs/architecture/event-targets/websocket-client.md
- docs/architecture/event-targets/websocket-server.md
- docs/architecture/decisions/003-subscription-control-protocol.md
## Notes
> To be filled by implementation agent
## Summary
> To be filled on completion

View File

@@ -0,0 +1,41 @@
---
id: worker-adapter-rd
name: R&D on Worker adapter: Node vs Web Worker API differences
status: pending
depends_on: [review-core-and-redis]
scope: narrow
risk: medium
impact: component
level: implementation
---
## Description
The Worker adapter has an open question documented in `docs/architecture/event-targets/worker.md`: should we implement one adapter that targets Web Workers (browser + Deno + Bun), two separate adapters for Node `worker_threads` and Web Workers, or one adapter abstracting both?
The architecture doc recommends starting with a single adapter targeting Web Workers (browser + Deno + Bun all support this API). Node `worker_threads` support would be added later.
This R&D task should:
1. Evaluate the API differences between Web Worker (`self.onmessage`/`self.postMessage`) and Node `worker_threads` (`parentPort.on('message')`/`parentPort.postMessage()`)
2. Determine if a single adapter abstraction is feasible and worth the complexity
3. Decide on the initial scope: Web Worker only, or both from the start
4. Identify any polyfills or compatibility shims needed
## Acceptance Criteria
- [ ] R&D notes documented on the API differences between Web Worker and Node worker_threads
- [ ] Decision made on scope: single Web Worker adapter, or dual adapter from the start
- [ ] If dual adapter, create separate task for the Node worker_threads variant
- [ ] If single adapter, identify what runtime detection or abstraction is needed
## References
- docs/architecture/event-targets/worker.md (Open Questions section)
## Notes
> To be filled by implementation agent
## Summary
> To be filled on completion

View File

@@ -0,0 +1,57 @@
---
id: worker-adapter-implementation
name: Implement Worker event target adapter(s)
status: pending
depends_on: [worker-adapter-rd, review-websocket-adapters]
scope: moderate
risk: medium
impact: component
level: implementation
---
## Description
Implement the Worker event target adapter(s) based on the outcome of `worker-adapter-rd`. The architecture specifies two factory functions:
- `createWorkerHostEventTarget(worker: Worker): TypedEventTarget<TEvent>` — wraps a Worker instance on the main thread side
- `createWorkerThreadEventTarget(): TypedEventTarget<TEvent>` — wraps parent message port on the worker thread side
The naming convention: `Host` is the side that owns the `Worker` object, `Thread` is the side that runs inside the worker.
Key requirements from architecture:
- Bidirectional — both sides can publish and subscribe
- Per-worker — each worker gets its own event target on the main thread
- Messages use `EventEnvelope` format over `postMessage`
- Structured clone for serialization (but `EventEnvelope` is JSON-serializable for cross-platform)
- No native deps — works in any environment with Worker support
- `createWorkerHostEventTarget` wraps a `Worker` instance
- `createWorkerThreadEventTarget` wraps `self.onmessage`/`self.postMessage` (Web) or `parentPort` (Node)
The scope depends on `worker-adapter-rd` outcome. At minimum, implement Web Worker support.
## Acceptance Criteria
- [ ] `src/event-target-worker.ts` (or split files) exists
- [ ] `createWorkerHostEventTarget(worker)` creates event target for main thread side
- [ ] `createWorkerThreadEventTarget()` creates event target for worker thread side
- [ ] `dispatchEvent` on host side calls `worker.postMessage(event.detail)`
- [ ] `dispatchEvent` on thread side calls `globalThis.postMessage(event.detail)` (Web) or `parentPort.postMessage(event.detail)` (Node, if supported)
- [ ] Receiving on host side: `worker.onmessage` parses envelope, creates `CustomEvent` with `type:id`
- [ ] Receiving on thread side: `globalThis.onmessage` (or `parentPort.on('message')`) parses envelope, creates `CustomEvent`
- [ ] Both sides support `addEventListener` and `removeEventListener`
- [ ] No comments in source code (project convention)
- [ ] Sub-path export added to `package.json` and `tsup.config.ts`
- [ ] Barrel re-export added to `src/index.ts`
## References
- docs/architecture/event-targets/worker.md
- src/types.ts
## Notes
> To be filled by implementation agent
## Summary
> To be filled on completion

View File

@@ -0,0 +1,46 @@
---
id: worker-adapter-tests
name: Write tests for Worker event target adapter
status: pending
depends_on: [worker-adapter-implementation]
scope: moderate
risk: medium
impact: component
level: implementation
---
## Description
Write tests for the Worker event target adapter(s). Testing Workers is non-trivial because it requires creating actual Worker instances or mocking the `postMessage`/`onmessage` API.
Test scenarios from the architecture doc:
1. Main → Worker send — dispatchEvent from main posts message to worker
2. Worker → Main send — dispatchEvent from worker posts message to main
3. Bidirectional — both sides can subscribe and publish
4. Topic scoping — type:id topics correctly formed
5. Envelope round-trip — full envelope survives serialization
6. Worker termination — cleanup when worker exits
## Acceptance Criteria
- [ ] `test/event-target-worker.test.ts` exists and passes
- [ ] Mock strategy for Worker environment established (actual workers, vitest worker threads, or mocked postMessage)
- [ ] Test: host dispatchEvent posts message to worker via postMessage
- [ ] Test: worker thread dispatchEvent posts message to parent
- [ ] Test: bidirectional communication works
- [ ] Test: type:id topic scoping works correctly
- [ ] Test: EventEnvelope round-trips through serialization
- [ ] Test: addEventListener and removeEventListener work on both sides
## References
- docs/architecture/event-targets/worker.md (Test Plan section)
- src/event-target-worker.ts
## Notes
> To be filled by implementation agent
## Summary
> To be filled on completion

View File

@@ -0,0 +1,38 @@
---
id: review-worker-adapter
name: Review Worker adapter implementation
status: pending
depends_on: [worker-adapter-tests]
scope: narrow
risk: low
impact: phase
level: review
---
## Description
Review checkpoint after Worker adapter implementation. Verify:
- Build, type-check, and full test suite pass
- Worker adapter matches architecture spec
- Test strategy for Workers is sound (actual workers or reliable mocks)
- Package exports, tsup config, and barrel re-exports are correct and consistent
## Acceptance Criteria
- [ ] `npm run build` passes cleanly
- [ ] `npm run lint` passes
- [ ] `npm test` passes with all tests (core + Redis + WebSocket + Worker)
- [ ] Worker adapter API matches architecture spec
- [ ] Sub-path exports are correctly configured
## References
- docs/architecture/event-targets/worker.md
## Notes
> To be filled by implementation agent
## Summary
> To be filled on completion

View File

@@ -0,0 +1,47 @@
---
id: deferred-iroh-adapters
name: Iroh spoke and hub adapters (deferred)
status: pending
depends_on: [review-worker-adapter]
scope: system
risk: critical
impact: project
level: implementation
---
## Description
The Iroh adapters (spoke and hub) are explicitly deferred pending the fork of `@rayhanadev/iroh` as `@alkdev/iroh` with Linux + WASM platform targets. The current `@rayhanadev/iroh` has one author and no tests, making it unsuitable for production use.
This task is a placeholder to track the dependency. It cannot proceed until:
1. The iroh-ts fork exists as `@alkdev/iroh` with Linux native + WASM builds
2. The API surface (10 methods documented in `iroh-transport.md`) is validated against the fork
3. The fork has basic test coverage
The architecture docs specify:
- **Iroh Spoke**: `createIrohSpokeEventTarget({ endpoint, hubNodeId, alpn? })` — connects to hub, opens bidirectional QUIC stream, implements subscription forwarding
- **Iroh Hub**: `createIrohHubEventTarget({ endpoint, alpn? })` — accepts connections, manages fan-out with subscription tracking
- Both use length-prefixed JSON framing (4-byte big-endian length prefix)
- Both use `__subscribe`/`__unsubscribe` control protocol
Do NOT start implementation until the fork is ready.
## Acceptance Criteria
- [ ] `@alkdev/iroh` package is available with Linux + WASM builds
- [ ] Basic iroh API validation (connect, openBi, acceptBi, sendStream, recvStream)
- [ ] This task should be split into separate tasks for spoke and hub once the fork is ready
## References
- docs/architecture/iroh-transport.md
- docs/architecture/event-targets/iroh-spoke.md
- docs/architecture/event-targets/iroh-hub.md
## Notes
> Blocked on @alkdev/iroh fork. Do not start until fork is ready.
## Summary
> To be filled on completion

View File

@@ -0,0 +1,45 @@
---
id: build-and-exports-validation
name: Validate build, package.json exports, and tsup config for all adapters
status: pending
depends_on: []
scope: narrow
risk: low
impact: component
level: implementation
---
## Description
Verify that the build pipeline correctly produces dual ESM + CJS output with declarations for all entry points, and that `package.json` exports map and `tsup.config.ts` entries are consistent.
Currently only `src/index.ts` and `src/event-target-redis.ts` are entry points. As new adapters are added, each needs:
1. An entry in `tsup.config.ts` entry array
2. A sub-path export in `package.json` exports map (ESM + CJS + declarations)
3. A barrel re-export in `src/index.ts`
This task validates the current setup and serves as a checklist item for each adapter task. It can be done early and re-verified as adapters are added.
## Acceptance Criteria
- [ ] `npm run build` produces correct output for existing entry points (index, event-target-redis)
- [ ] `npm run lint` (tsc --noEmit) passes
- [ ] `package.json` exports map has correct ESM/CJS/dts paths for each entry point
- [ ] `tsup.config.ts` lists all current entry points
- [ ] `src/index.ts` re-exports everything from all modules
- [ ] Peer dependencies and peerDependenciesMeta are correct
- [ ] No runtime dependencies (Repeater is inlined)
## References
- docs/architecture/build-distribution.md
- package.json
- tsup.config.ts
## Notes
> To be filled by implementation agent
## Summary
> To be filled on completion

View File

@@ -0,0 +1,43 @@
---
id: integration-test-pubsub-with-redis
name: Integration test: createPubSub with Redis event target
status: pending
depends_on: [redis-adapter-tests, core-pubsub-tests]
scope: narrow
risk: low
impact: component
level: implementation
---
## Description
Test that `createPubSub` works correctly when configured with the Redis event target. This is an integration test that verifies the core factory and the adapter work together:
```ts
const pubsub = createPubSub<MyEventMap>({
eventTarget: createRedisEventTarget({ publishClient, subscribeClient }),
});
```
This tests the full round-trip: publish through createPubSub → dispatch through Redis → receive on the other side.
## Acceptance Criteria
- [ ] Integration test: publish via `createPubSub` + Redis event target
- [ ] Integration test: subscribe via `createPubSub` + Redis event target
- [ ] Integration test: full envelope round-trip (type, id, payload)
- [ ] Integration test: topic scoping works with `type:id` through Redis
- [ ] Integration test: channel prefix is correctly applied when configured
## References
- docs/architecture/event-targets/redis.md
- docs/architecture/api-surface.md
## Notes
> To be filled by implementation agent
## Summary
> To be filled on completion

View File

@@ -0,0 +1,47 @@
---
id: integration-test-ws-client-server
name: Integration test: WebSocket client and server event targets together
status: pending
depends_on: [websocket-client-tests, websocket-server-tests]
scope: moderate
risk: medium
impact: component
level: implementation
---
## Description
Test the WebSocket client and server adapters working together — a spoke subscribes via the client adapter, the hub fans out via the server adapter. This validates the full subscription control protocol end-to-end:
1. Client connects to server
2. Client calls `addEventListener` → sends `__subscribe` to server
3. Server receives `__subscribe` → adds client to subscription map
4. Server `dispatchEvent` on that topic → message sent only to subscribed client
5. Client receives message → dispatches to local listeners
6. Client calls `removeEventListener` → sends `__unsubscribe` to server
7. Server removes client from subscription map
This is also where we validate that `createPubSub` works with the WebSocket adapters.
## Acceptance Criteria
- [ ] Integration test: bidirectional communication between WS client and server
- [ ] Integration test: subscription control protocol end-to-end
- [ ] Integration test: topic-based fan-out — subscribed clients receive events, unsubscribed don't
- [ ] Integration test: direct messaging via topic scoping `"direct:${spokeId}"`
- [ ] Integration test: `createPubSub` works with WS client event target
- [ ] Integration test: `createPubSub` works with WS server event target
## References
- docs/architecture/event-targets/websocket-client.md
- docs/architecture/event-targets/websocket-server.md
- docs/architecture/decisions/003-subscription-control-protocol.md
## Notes
> To be filled by implementation agent
## Summary
> To be filled on completion

View File

@@ -0,0 +1,51 @@
---
id: final-review-and-ci-validation
name: Final review: full test suite, build, and CI readiness
status: pending
depends_on: [review-worker-adapter, integration-test-ws-client-server]
scope: moderate
risk: low
impact: project
level: review
---
## Description
Final review before considering the package ready for use (excluding Iroh, which is deferred). Verify:
1. Full test suite passes (core + operators + Redis + WS client + WS server + Worker + integration)
2. Build produces correct dual ESM/CJS output with declarations for all entry points
3. Type-check passes for all source files
4. Package.json exports map covers all adapters
5. Each adapter has its own sub-path export configured correctly
6. Barrel re-export in `src/index.ts` includes everything
7. No test regressions
8. No extraneous files in npm package (check `npm pack` output)
## Acceptance Criteria
- [ ] `npm run build` passes cleanly
- [ ] `npm run lint` passes (tsc --noEmit)
- [ ] `npm test` passes with full test suite
- [ ] `npm run test:coverage` reports reasonable coverage (core > 80%)
- [ ] All sub-path exports resolve correctly
- [ ] Package can be consumed as `import { createPubSub } from '@alkdev/pubsub'`
- [ ] Package can be consumed as `import { createRedisEventTarget } from '@alkdev/pubsub/event-target-redis'`
- [ ] Package can be consumed as `import { createWebSocketClientEventTarget } from '@alkdev/pubsub/event-target-websocket-client'`
- [ ] Package can be consumed as `import { createWebSocketServerEventTarget } from '@alkdev/pubsub/event-target-websocket-server'`
- [ ] Package can be consumed as `import { createWorkerHostEventTarget } from '@alkdev/pubsub/event-target-worker'`
- [ ] No runtime dependencies (Repeater is inlined)
- [ ] Peer deps are optional and correct
## References
- docs/architecture/build-distribution.md
- All architecture docs
## Notes
> To be filled by implementation agent
## Summary
> To be filled on completion