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:
50
tasks/001-core-pubsub-tests.md
Normal file
50
tasks/001-core-pubsub-tests.md
Normal 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
|
||||
49
tasks/002-core-operators-tests.md
Normal file
49
tasks/002-core-operators-tests.md
Normal 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
|
||||
50
tasks/003-redis-adapter-tests.md
Normal file
50
tasks/003-redis-adapter-tests.md
Normal 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
|
||||
45
tasks/004-redis-channel-prefix-and-error-handling.md
Normal file
45
tasks/004-redis-channel-prefix-and-error-handling.md
Normal 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
|
||||
45
tasks/005-review-core-and-redis.md
Normal file
45
tasks/005-review-core-and-redis.md
Normal 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
|
||||
56
tasks/006-websocket-client-adapter.md
Normal file
56
tasks/006-websocket-client-adapter.md
Normal 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
|
||||
52
tasks/007-websocket-client-tests.md
Normal file
52
tasks/007-websocket-client-tests.md
Normal 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
|
||||
69
tasks/008-websocket-server-adapter.md
Normal file
69
tasks/008-websocket-server-adapter.md
Normal 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
|
||||
55
tasks/009-websocket-server-tests.md
Normal file
55
tasks/009-websocket-server-tests.md
Normal 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
|
||||
45
tasks/010-review-websocket-adapters.md
Normal file
45
tasks/010-review-websocket-adapters.md
Normal 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
|
||||
41
tasks/011-worker-adapter-rd.md
Normal file
41
tasks/011-worker-adapter-rd.md
Normal 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
|
||||
57
tasks/012-worker-adapter-implementation.md
Normal file
57
tasks/012-worker-adapter-implementation.md
Normal 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
|
||||
46
tasks/013-worker-adapter-tests.md
Normal file
46
tasks/013-worker-adapter-tests.md
Normal 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
|
||||
38
tasks/014-review-worker-adapter.md
Normal file
38
tasks/014-review-worker-adapter.md
Normal 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
|
||||
47
tasks/015-deferred-iroh-adapters.md
Normal file
47
tasks/015-deferred-iroh-adapters.md
Normal 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
|
||||
45
tasks/016-build-and-exports-validation.md
Normal file
45
tasks/016-build-and-exports-validation.md
Normal 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
|
||||
43
tasks/017-integration-test-pubsub-with-redis.md
Normal file
43
tasks/017-integration-test-pubsub-with-redis.md
Normal 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
|
||||
47
tasks/018-integration-test-ws-client-server.md
Normal file
47
tasks/018-integration-test-ws-client-server.md
Normal 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
|
||||
51
tasks/019-final-review-and-ci-validation.md
Normal file
51
tasks/019-final-review-and-ci-validation.md
Normal 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
|
||||
Reference in New Issue
Block a user