diff --git a/tasks/001-core-pubsub-tests.md b/tasks/001-core-pubsub-tests.md new file mode 100644 index 0000000..b22c94e --- /dev/null +++ b/tasks/001-core-pubsub-tests.md @@ -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` (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 \ No newline at end of file diff --git a/tasks/002-core-operators-tests.md b/tasks/002-core-operators-tests.md new file mode 100644 index 0000000..4c4bd26 --- /dev/null +++ b/tasks/002-core-operators-tests.md @@ -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` into `AsyncIterable` +- [ ] `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 \ No newline at end of file diff --git a/tasks/003-redis-adapter-tests.md b/tasks/003-redis-adapter-tests.md new file mode 100644 index 0000000..8577294 --- /dev/null +++ b/tasks/003-redis-adapter-tests.md @@ -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 \ No newline at end of file diff --git a/tasks/004-redis-channel-prefix-and-error-handling.md b/tasks/004-redis-channel-prefix-and-error-handling.md new file mode 100644 index 0000000..5bef7dc --- /dev/null +++ b/tasks/004-redis-channel-prefix-and-error-handling.md @@ -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 \ No newline at end of file diff --git a/tasks/005-review-core-and-redis.md b/tasks/005-review-core-and-redis.md new file mode 100644 index 0000000..0a43e23 --- /dev/null +++ b/tasks/005-review-core-and-redis.md @@ -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 \ No newline at end of file diff --git a/tasks/006-websocket-client-adapter.md b/tasks/006-websocket-client-adapter.md new file mode 100644 index 0000000..4f247b4 --- /dev/null +++ b/tasks/006-websocket-client-adapter.md @@ -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` +- [ ] `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 \ No newline at end of file diff --git a/tasks/007-websocket-client-tests.md b/tasks/007-websocket-client-tests.md new file mode 100644 index 0000000..38d6090 --- /dev/null +++ b/tasks/007-websocket-client-tests.md @@ -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 \ No newline at end of file diff --git a/tasks/008-websocket-server-adapter.md b/tasks/008-websocket-server-adapter.md new file mode 100644 index 0000000..79934f8 --- /dev/null +++ b/tasks/008-websocket-server-adapter.md @@ -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>` 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 \ No newline at end of file diff --git a/tasks/009-websocket-server-tests.md b/tasks/009-websocket-server-tests.md new file mode 100644 index 0000000..2d01e1e --- /dev/null +++ b/tasks/009-websocket-server-tests.md @@ -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 \ No newline at end of file diff --git a/tasks/010-review-websocket-adapters.md b/tasks/010-review-websocket-adapters.md new file mode 100644 index 0000000..54b4bb3 --- /dev/null +++ b/tasks/010-review-websocket-adapters.md @@ -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 \ No newline at end of file diff --git a/tasks/011-worker-adapter-rd.md b/tasks/011-worker-adapter-rd.md new file mode 100644 index 0000000..5531e03 --- /dev/null +++ b/tasks/011-worker-adapter-rd.md @@ -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 \ No newline at end of file diff --git a/tasks/012-worker-adapter-implementation.md b/tasks/012-worker-adapter-implementation.md new file mode 100644 index 0000000..8eba1d4 --- /dev/null +++ b/tasks/012-worker-adapter-implementation.md @@ -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` — wraps a Worker instance on the main thread side +- `createWorkerThreadEventTarget(): TypedEventTarget` — 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 \ No newline at end of file diff --git a/tasks/013-worker-adapter-tests.md b/tasks/013-worker-adapter-tests.md new file mode 100644 index 0000000..a4547de --- /dev/null +++ b/tasks/013-worker-adapter-tests.md @@ -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 \ No newline at end of file diff --git a/tasks/014-review-worker-adapter.md b/tasks/014-review-worker-adapter.md new file mode 100644 index 0000000..3ec7da5 --- /dev/null +++ b/tasks/014-review-worker-adapter.md @@ -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 \ No newline at end of file diff --git a/tasks/015-deferred-iroh-adapters.md b/tasks/015-deferred-iroh-adapters.md new file mode 100644 index 0000000..fc015b6 --- /dev/null +++ b/tasks/015-deferred-iroh-adapters.md @@ -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 \ No newline at end of file diff --git a/tasks/016-build-and-exports-validation.md b/tasks/016-build-and-exports-validation.md new file mode 100644 index 0000000..edecda2 --- /dev/null +++ b/tasks/016-build-and-exports-validation.md @@ -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 \ No newline at end of file diff --git a/tasks/017-integration-test-pubsub-with-redis.md b/tasks/017-integration-test-pubsub-with-redis.md new file mode 100644 index 0000000..696161c --- /dev/null +++ b/tasks/017-integration-test-pubsub-with-redis.md @@ -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({ + 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 \ No newline at end of file diff --git a/tasks/018-integration-test-ws-client-server.md b/tasks/018-integration-test-ws-client-server.md new file mode 100644 index 0000000..1e1244e --- /dev/null +++ b/tasks/018-integration-test-ws-client-server.md @@ -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 \ No newline at end of file diff --git a/tasks/019-final-review-and-ci-validation.md b/tasks/019-final-review-and-ci-validation.md new file mode 100644 index 0000000..75a38dc --- /dev/null +++ b/tasks/019-final-review-and-ci-validation.md @@ -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 \ No newline at end of file