first commit
This commit is contained in:
@@ -0,0 +1,129 @@
|
||||
# @wrnexus/pubsub
|
||||
|
||||
> Topic-based publish/subscribe with a pluggable driver — in-process by default, Redis for cross-process messaging.
|
||||
|
||||
Part of the **WrNexus** framework — an SSR-first, Bun-native full-stack web framework.
|
||||
|
||||
## Overview
|
||||
|
||||
`@wrnexus/pubsub` is a small server-side pub/sub bus. You publish messages to a
|
||||
topic and subscribe with topic patterns; handlers fire for matching topics. The
|
||||
default driver keeps everything in-process, and you can swap in the Redis driver
|
||||
(`@wrnexus/pubsub/redis`) to fan messages out across processes or hosts. It also
|
||||
backs `@wrnexus/core`'s realtime bridge for horizontal scaling.
|
||||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
bun add @wrnexus/pubsub
|
||||
```
|
||||
|
||||
> Private package — the machine must be authenticated to the `wrnexus` npm org
|
||||
> (a read token in `~/.npmrc`). Requires **Bun** (Node is not supported).
|
||||
|
||||
## API
|
||||
|
||||
### `createPubSub(driver?): PubSub`
|
||||
|
||||
Creates a bus over a driver. Defaults to `memoryDriver()` (in-process).
|
||||
|
||||
```ts
|
||||
interface PubSub {
|
||||
publish<T = unknown>(topic: string, message: T): Promise<void>;
|
||||
subscribe<T = unknown>(pattern: string, handler: Handler<T>): () => void;
|
||||
}
|
||||
|
||||
type Handler<T = unknown> = (message: T, topic: string) => void | Promise<void>;
|
||||
```
|
||||
|
||||
- `publish(topic, message)` — resolves once the driver has dispatched the message.
|
||||
- `subscribe(pattern, handler)` — returns an unsubscribe function.
|
||||
|
||||
### Pattern matching
|
||||
|
||||
Subscription patterns match in three ways:
|
||||
|
||||
- **Exact** — `"order:created"` matches only that topic.
|
||||
- **Prefix** — `"order:*"` matches any topic starting with `"order:"`.
|
||||
- **Everything** — `"*"` matches all topics.
|
||||
|
||||
### `memoryDriver(): PubSubDriver`
|
||||
|
||||
The default in-process driver. Handlers are invoked synchronously (fire-and-forget
|
||||
for async handlers) whenever a published topic matches a registered pattern.
|
||||
|
||||
```ts
|
||||
interface PubSubDriver {
|
||||
publish(topic: string, message: unknown): void | Promise<void>;
|
||||
subscribe(pattern: string, handler: Handler): () => void;
|
||||
}
|
||||
```
|
||||
|
||||
### `@wrnexus/pubsub/redis` — `redisDriver(url?)`
|
||||
|
||||
A cross-process driver backed by Redis. It speaks RESP over a raw TCP socket via
|
||||
`Bun.connect`, so it adds **no npm dependency**. `url` defaults to `$REDIS_URL`,
|
||||
then `redis://localhost:6379`. The URL may carry a password and a database index
|
||||
(e.g. `redis://:secret@host:6379/2`).
|
||||
|
||||
```ts
|
||||
function redisDriver(url?: string): PubSubDriver & { close(): void };
|
||||
```
|
||||
|
||||
- Exact topics use Redis `SUBSCRIBE`; wildcard patterns (`ns:*`, `*`) use
|
||||
`PSUBSCRIBE`, whose glob semantics line up with this library's matching.
|
||||
- Messages are JSON-stringified on publish and `JSON.parse`d on receipt; a payload
|
||||
that isn't valid JSON is delivered as the raw string.
|
||||
- `close()` tears down both the subscriber and publisher connections.
|
||||
|
||||
### RESP codec (internal)
|
||||
|
||||
`redis.ts` uses a minimal RESP implementation exported from `resp.ts`
|
||||
(`encodeCommand`, `parseReply`, `concat`, and the `RespValue` type). These are
|
||||
implementation details of the Redis driver, not part of the public package entry.
|
||||
|
||||
## Usage
|
||||
|
||||
In-process (default):
|
||||
|
||||
```ts
|
||||
import { createPubSub } from "@wrnexus/pubsub";
|
||||
|
||||
const bus = createPubSub();
|
||||
|
||||
const off = bus.subscribe("order:*", (msg, topic) => {
|
||||
console.log(topic, msg);
|
||||
});
|
||||
|
||||
await bus.publish("order:created", { id: 7 });
|
||||
|
||||
off(); // unsubscribe
|
||||
```
|
||||
|
||||
Cross-process with Redis:
|
||||
|
||||
```ts
|
||||
import { createPubSub } from "@wrnexus/pubsub";
|
||||
import { redisDriver } from "@wrnexus/pubsub/redis";
|
||||
|
||||
const driver = redisDriver("redis://localhost:6379");
|
||||
const bus = createPubSub(driver);
|
||||
|
||||
bus.subscribe("order:*", (msg, topic) => {
|
||||
// received on any app process subscribed to this pattern
|
||||
});
|
||||
|
||||
await bus.publish("order:created", { id: 7 });
|
||||
|
||||
// on shutdown
|
||||
driver.close();
|
||||
```
|
||||
|
||||
## Requirements / Notes
|
||||
|
||||
- **Bun-only.** The Redis driver depends on `Bun.connect`; it throws
|
||||
`redisDriver requires the Bun runtime (Bun.connect).` outside Bun. The default
|
||||
in-memory driver has no runtime dependencies.
|
||||
- The Redis driver reads `REDIS_URL` from the environment when no `url` is passed.
|
||||
- Backs [`@wrnexus/core`](../core)'s realtime bridge for horizontal scaling.
|
||||
- No external npm dependencies — the Redis client is a self-contained RESP codec.
|
||||
Reference in New Issue
Block a user