- Copy core source from alkhub_ts/packages/core/pubsub/ with import path fixups (typed_event_target.ts → types.ts, .ts → .js extensions) - Make PubSubPublishArgsByKey exported (was private type, needed by barrel) - Add package.json with sub-path exports and optional peer deps (ioredis) - Add tsup.config.ts with multi-entry + splitting for tree-shaking - Add tsconfig.json, vitest.config.ts, .gitignore - Add AGENTS.md with project conventions and adapter checklist - Add architecture docs following taskgraph/alkhub pattern: docs/architecture/README.md, api-surface.md, event-targets.md, iroh-transport.md, build-distribution.md - Add ADRs: 001-graphql-yoga-fork, 002-tree-shake-pattern - Copy migration research doc to docs/research/migration.md - Dual-license MIT OR Apache-2.0 (matching taskgraph)
4.9 KiB
status, last_updated
| status | last_updated |
|---|---|
| draft | 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:
- Reduces coupling — alkhub depends on pubsub, not the other way around
- Enables reuse — multiple alkhub packages can share the same pubsub instance
- Isolates peer deps — Redis and Iroh are heavy native dependencies; consumers that don't need them shouldn't carry them
- Matches established pattern —
@alkdev/taskgraphand@alkdev/typemapalready 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:
const pubsub = createPubSub<MyEventMap>({
eventTarget: createRedisEventTarget({ publishClient, subscribeClient }),
});
What This Package Provides
- Core —
createPubSub,TypedEventTarget,TypedEvent, topic scoping,filter/map/pipeoperators - 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)
- In-process (default
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. The call protocol types live in a separate @alkdev/call-protocol package (not yet extracted).
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.
- 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
ioredisonly. - Type-only imports —
event-target-redis.tsimportsioredistypes only at compile time. At runtime, the consumer must provide the actualRedis/Clusterinstances.
Architecture Documents
| Document | Content |
|---|---|
| api-surface.md | createPubSub factory, PubSub types, operators, TypedEventTarget types |
| event-targets.md | In-process, Redis, WebSocket adapters — interface, configuration, limitations |
| iroh-transport.md | Iroh P2P QUIC transport — protocol, framing, identity, hub/spoke sides, reconnection |
| 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:
---
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/subscriptionand@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