New sub-path export @alkdev/pubsub/call providing:
- CallEventSchema (TypeBox schemas) for call.requested/responded/part/completed/aborted/error
- PendingRequestMap with call() (request/response) and subscribe() (streaming via Repeater)
- CallError class and CallErrorCode constants
- Scoped topic subscriptions (call.responded:{requestId}) to avoid O(n) fanout
- subscribe() yields call.part events until call.completed or call.error,
with automatic call.aborted on consumer break
Also adds @alkdev/typebox as runtime dependency and architecture doc.
96 lines
5.2 KiB
Markdown
96 lines
5.2 KiB
Markdown
---
|
|
status: draft
|
|
last_updated: 2026-04-30
|
|
---
|
|
|
|
# @alkdev/pubsub Architecture
|
|
|
|
Type-safe publish/subscribe with pluggable event target adapters. The core (`createPubSub` + `TypedEventTarget` + operators) has no transport dependency. Each adapter (Redis, WebSocket, Iroh) is an isolated module that only imports its own peer dependency.
|
|
|
|
## Why This Exists
|
|
|
|
Extracted from `@alkdev/alkhub_ts/packages/core/pubsub/`, which itself was adapted from `@graphql-yoga/subscription` and `@graphql-yoga/typed-event-target`. The pubsub module was already self-contained within alkhub — zero cross-module imports from operations, config, logger, or MCP. Extracting it into a standalone package:
|
|
|
|
1. **Reduces coupling** — alkhub depends on pubsub, not the other way around
|
|
2. **Enables reuse** — multiple alkhub packages can share the same pubsub instance
|
|
3. **Isolates peer deps** — Redis and Iroh are heavy native dependencies; consumers that don't need them shouldn't carry them
|
|
4. **Matches established pattern** — `@alkdev/taskgraph` and `@alkdev/typemap` already use the standalone-package pattern
|
|
|
|
## Core Principle
|
|
|
|
**The TypedEventTarget interface is the contract.** All transports implement the same `addEventListener` / `dispatchEvent` / `removeEventListener` surface. `createPubSub` doesn't know or care which transport is in use — it just dispatches events to whatever `TypedEventTarget` it was given.
|
|
|
|
This means swapping from in-process to Redis to WebSocket to Iroh is a one-line config change:
|
|
|
|
```ts
|
|
const pubsub = createPubSub<MyEventMap>({
|
|
eventTarget: createRedisEventTarget({ publishClient, subscribeClient }),
|
|
});
|
|
```
|
|
|
|
## What This Package Provides
|
|
|
|
- **Core** — `createPubSub`, `TypedEventTarget`, `TypedEvent`, topic scoping, `filter`/`map`/`pipe` operators, `Repeater` (inlined from @repeaterjs/repeater)
|
|
- **Call protocol** (`@alkdev/pubsub/call`) — `PendingRequestMap`, `CallEventSchema`, `CallError`, event types for request/response and streaming operations
|
|
- **Adapters** (each is a peer-dep island, importable via sub-path export):
|
|
- In-process (default `EventTarget`, no adapter needed)
|
|
- Redis (`@alkdev/pubsub/event-target-redis`, peer dep: `ioredis`)
|
|
- WebSocket (future: `@alkdev/pubsub/event-target-websocket`)
|
|
- Iroh (future: `@alkdev/pubsub/event-target-iroh`, peer dep: `@rayhanadev/iroh`)
|
|
|
|
## Consumer Context
|
|
|
|
### alkhub (hub-spoke coordinator)
|
|
|
|
The hub uses pubsub for event routing between operations, runners, and the SSE interface. The event map is the call protocol — typed JSON events (`call.requested`, `call.responded`, `session.status`, etc.). Transport choice depends on deployment:
|
|
|
|
| Deployment | Transport |
|
|
|------------|-----------|
|
|
| Single-process hub | In-process (default) |
|
|
| Hub + worker processes | Redis |
|
|
| Hub + remote spokes | WebSocket or Iroh |
|
|
|
|
### Future: standalone spoke SDK
|
|
|
|
Spokes will import `@alkdev/pubsub` directly to create their event target (WebSocket or Iroh) and wire it into `createPubSub`. Call protocol types and `PendingRequestMap` are available from `@alkdev/pubsub/call`.
|
|
|
|
## Threat Model
|
|
|
|
- **Fork provenance** — core pubsub and typed event target are adapted from graphql-yoga (MIT). All original copyright notices are preserved in file headers. See [ADR-001](decisions/001-graphql-yoga-fork.md).
|
|
- **Peer dep isolation** — Redis and Iroh are optional peer dependencies. A consumer that only needs in-process transport installs zero extra packages. A consumer using Redis but not Iroh installs `ioredis` only.
|
|
- **Type-only imports** — `event-target-redis.ts` imports `ioredis` types only at compile time. At runtime, the consumer must provide the actual `Redis`/`Cluster` instances.
|
|
|
|
## Architecture Documents
|
|
|
|
| Document | Content |
|
|
|----------|---------|
|
|
| [api-surface.md](api-surface.md) | createPubSub factory, PubSub types, operators, TypedEventTarget types |
|
|
| [call-protocol.md](call-protocol.md) | Call/subscribe protocol — event types, PendingRequestMap, streaming, error model, transport mapping |
|
|
| [event-targets.md](event-targets.md) | In-process, Redis, WebSocket adapters — interface, configuration, limitations |
|
|
| [iroh-transport.md](iroh-transport.md) | Iroh P2P QUIC transport — protocol, framing, identity, hub/spoke sides, reconnection |
|
|
| [build-distribution.md](build-distribution.md) | Dependencies, project structure, tree-shaking, sub-path exports, targets |
|
|
|
|
## Document Lifecycle
|
|
|
|
Architecture documents use YAML frontmatter with `status` and `last_updated` fields:
|
|
|
|
```yaml
|
|
---
|
|
status: draft | stable | deprecated
|
|
last_updated: YYYY-MM-DD
|
|
---
|
|
```
|
|
|
|
| Status | Meaning | Transitions |
|
|
|--------|---------|-------------|
|
|
| `draft` | Under active development. Content may change. | → `stable` when implementation is complete and tests verify API contract. |
|
|
| `stable` | API contracts are locked. Changes require review cycle. | → `deprecated` when superseded. |
|
|
| `deprecated` | Superseded. Kept for reference. | Removed when no longer referenced. |
|
|
|
|
## References
|
|
|
|
- Source: `@alkdev/alkhub_ts/packages/core/pubsub/`
|
|
- Upstream: `@graphql-yoga/subscription` and `@graphql-yoga/typed-event-target` (MIT)
|
|
- alkhub pubsub-redis doc: `@alkdev/alkhub_ts/docs/architecture/pubsub-redis.md`
|
|
- alkhub spoke-runner doc: `@alkdev/alkhub_ts/docs/architecture/spoke-runner.md`
|
|
- Migration research: `docs/research/migration.md` |