Move docs/ package mirror into document-navigator plugin
Relocate the ~6k-file docs tree from the repo root into plugins/document-navigator/docs/ so the plugin is self-contained and portable. docs/superpowers/ (specs and plans) stays at the repo root. Moved via cp+rm rather than git mv because directory rename() fails with EPERM on this WSL /mnt DrvFs mount; git rename-detection pairs the identical-content files at commit time. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,71 @@
|
||||
# in-process
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
|
||||
## Purpose
|
||||
|
||||
Single-process publish/subscribe bus consumed by the SSE service to deliver every broadcast to every active subscriber on the matching channel. The `Fanout` interface is intentionally minimal so a future cross-node implementation (Redis pub/sub, NATS) can drop in without changing the service surface. The in-process implementation is the default — sufficient for any deployment that runs on a single process or behind a sticky load balancer.
|
||||
|
||||
## Features
|
||||
|
||||
- Per-channel listener sets with O(1) subscribe / unsubscribe.
|
||||
- Synchronous publish — listeners run in registration order during the `publish` call.
|
||||
- Unsubscribe is idempotent; calling the returned function twice is a no-op.
|
||||
- Empty channels are pruned automatically so `Map` memory does not grow unbounded across short-lived topics.
|
||||
- `clear()` for explicit eviction in test teardown.
|
||||
|
||||
## Usage: Basic
|
||||
|
||||
```ts
|
||||
import { createInProcessFanout } from "@suaveplan/api-plugin-sse/fanout/in-process";
|
||||
|
||||
const fanout = createInProcessFanout();
|
||||
const unsubscribe = fanout.subscribe("notifications", (event) => {
|
||||
console.log(event.id, event.data);
|
||||
});
|
||||
|
||||
fanout.publish("notifications", { id: "1", data: { kind: "ping" } });
|
||||
unsubscribe();
|
||||
```
|
||||
|
||||
## Usage: Advanced
|
||||
|
||||
```ts
|
||||
import { createInProcessFanout, type Fanout } from "@suaveplan/api-plugin-sse/fanout/in-process";
|
||||
|
||||
// Wrap the in-process bus with a metering layer.
|
||||
function meter(inner: Fanout): Fanout {
|
||||
let published = 0;
|
||||
return {
|
||||
publish(channel, event) {
|
||||
published++;
|
||||
inner.publish(channel, event);
|
||||
},
|
||||
subscribe(channel, listener) {
|
||||
return inner.subscribe(channel, listener);
|
||||
},
|
||||
clear() {
|
||||
inner.clear();
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
const fanout = meter(createInProcessFanout());
|
||||
fanout.publish("a", { id: "1", data: 1 });
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
- `createInProcessFanout(): Fanout` — constructs a single-process bus.
|
||||
- `Fanout.publish(channel: string, event: SseEvent): void` — invokes every listener registered against the channel.
|
||||
- `Fanout.subscribe(channel: string, listener: (event: SseEvent) => void): () => void` — registers a listener; returns an unsubscribe function. Calling the unsubscribe function more than once is safe.
|
||||
- `Fanout.clear(): void` — removes every listener from every channel.
|
||||
- `type FanoutListener = (event: SseEvent) => void` — listener signature.
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
- Listeners run synchronously on the publishing call stack. A long-running listener will block other listeners and the broadcaster — keep listeners O(microseconds) or hand off to a queue.
|
||||
- Subscribers added during a `publish` invocation are NOT invoked for that publish (set iteration semantics in V8 / JavaScriptCore guarantee the snapshot is taken at iteration start).
|
||||
- Errors thrown by a listener propagate to the publisher — wrap in `try/catch` per listener if isolated error handling is needed. (The SSE service's listener is a closure over a per-connection queue and never throws.)
|
||||
Reference in New Issue
Block a user