@stacksjs/realtime 0.70.86 → 0.70.88

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@stacksjs/realtime",
3
3
  "type": "module",
4
4
  "sideEffects": false,
5
- "version": "0.70.86",
5
+ "version": "0.70.88",
6
6
  "description": "The Stacks realtime integration. Built on top of ts-broadcasting.",
7
7
  "author": "Chris Breuer",
8
8
  "contributors": [
@@ -53,7 +53,7 @@
53
53
  "prepublishOnly": "bun run build"
54
54
  },
55
55
  "dependencies": {
56
- "ts-broadcasting": "^0.0.5"
56
+ "ts-broadcasting": "^0.0.8"
57
57
  },
58
58
  "devDependencies": {
59
59
  "better-dx": "^0.2.16"
@@ -1,62 +0,0 @@
1
- import type { ChannelType } from 'ts-broadcasting';
2
- /**
3
- * Install (or clear) the backpressure guard. Pass `null` to disable.
4
- */
5
- export declare function setBackpressureGuard(cfg: BackpressureGuardConfig | null): void;
6
- /**
7
- * Read the currently-installed guard config (useful for tests).
8
- */
9
- export declare function getBackpressureGuard(): Required<BackpressureGuardConfig> | null;
10
- /**
11
- * Run a broadcast from a broadcast file
12
- *
13
- * @example
14
- * await runBroadcast('OrderCreated', { orderId: 123 })
15
- */
16
- export declare function runBroadcast(name: string, payload?: any): Promise<void>;
17
- /**
18
- * Alias for runBroadcast.
19
- *
20
- * @example
21
- * await broadcast('OrderCreated', { orderId: 123 })
22
- */
23
- export declare function broadcast(name: string, payload?: any): Promise<void>;
24
- /**
25
- * Backpressure guard config (stacksjs/stacks#1877 R-2). The default
26
- * threshold is 1 MiB of buffered-bytes per socket — above this, the
27
- * configured `onSlow` callback fires once per offending socket per
28
- * broadcast. Apps install via `setBackpressureGuard({...})`; the
29
- * default is "no guard" for backwards-compat, so existing callers
30
- * see no behavior change until they opt in.
31
- *
32
- * Why opt-in: the underlying ts-broadcasting `server.broadcast()` is
33
- * synchronous and we can't inject between message-serialize and
34
- * socket-write. The best we can do at the Stacks layer is detect
35
- * slow consumers AROUND the broadcast call and let the app decide
36
- * what to do (close socket, drop client from channel, scale up).
37
- */
38
- export declare interface BackpressureGuardConfig {
39
- maxPerSocketBytes?: number
40
- onSlow?: (info: { channelName: string, backpressure: number, socket: unknown }) => void
41
- }
42
- export declare interface BroadcastInstance {
43
- channel?: () => string | string[]
44
- broadcastOn?: () => string | string[]
45
- event?: () => string
46
- broadcastAs?: () => string
47
- data?: () => any
48
- broadcastWith?: () => any
49
- handle?: (payload?: any) => Promise<void> | void
50
- }
51
- /**
52
- * Stacks Broadcast class for backward compatibility
53
- * Wraps ts-broadcasting's BroadcastServer
54
- */
55
- export declare class Broadcast {
56
- connect(): Promise<void>;
57
- disconnect(): Promise<void>;
58
- subscribe(channel: string, callback: (data: any) => void): void;
59
- unsubscribe(channel: string): void;
60
- broadcast(channel: string, event: string, data?: any, type?: ChannelType): void;
61
- isConnected(): boolean;
62
- }
package/dist/channel.d.ts DELETED
@@ -1,24 +0,0 @@
1
- import type { ChannelType } from 'ts-broadcasting';
2
- /**
3
- * Create a new channel instance
4
- *
5
- * @example
6
- * // Broadcast to a public channel
7
- * await channel('orders').public('created', { id: 1 })
8
- *
9
- * // Broadcast to a private channel
10
- * await channel('orders.123').private('updated', { status: 'shipped' })
11
- *
12
- * // Broadcast to a presence channel
13
- * await channel('chat.room.1').presence('message', { text: 'Hello' })
14
- */
15
- export declare function channel(name: string): Channel;
16
- /**
17
- * Stacks Channel class for backward compatibility
18
- * Provides a fluent API for broadcasting to channels
19
- */
20
- export declare class Channel {
21
- constructor(channel: string);
22
- presence(event: string, data?: any): Promise<void>;
23
- broadcast(event: string, data?: any, type?: ChannelType): Promise<void>;
24
- }
package/dist/emit.d.ts DELETED
@@ -1,37 +0,0 @@
1
- /**
2
- * Emit an event to a channel
3
- *
4
- * @example
5
- * // Simple emit to public channel
6
- * emit('orders', 'created', { id: 1, total: 99.99 })
7
- *
8
- * // Emit to private channel
9
- * emit('orders.123', 'updated', { status: 'shipped' }, { private: true })
10
- *
11
- * // Emit to presence channel
12
- * emit('chat.room.1', 'message', { text: 'Hello' }, { presence: true })
13
- *
14
- * // Exclude specific users
15
- * emit('chat.room.1', 'message', { text: 'Hello' }, { exclude: 'user-123' })
16
- */
17
- export declare function emit<T = unknown>(channel: string, event: string, data?: T, options?: EmitOptions): void;
18
- /**
19
- * Emit an event to a specific user
20
- *
21
- * @example
22
- * emitToUser('user-123', 'notification', { message: 'You have a new order!' })
23
- */
24
- export declare function emitToUser<T = unknown>(userId: string | number, event: string, data?: T, options?: Omit<EmitOptions, 'private'>): void;
25
- /**
26
- * Emit an event to multiple users
27
- *
28
- * @example
29
- * emitToUsers(['user-1', 'user-2'], 'announcement', { message: 'Server maintenance!' })
30
- */
31
- export declare function emitToUsers<T = unknown>(userIds: (string | number)[], event: string, data?: T, options?: Omit<EmitOptions, 'private'>): void;
32
- export declare interface EmitOptions {
33
- private?: boolean
34
- presence?: boolean
35
- exclude?: string | string[]
36
- driver?: string
37
- }
@@ -1,32 +0,0 @@
1
- /**
2
- * Install (or replace) the heartbeat config. Pass `null` to stop
3
- * the heartbeat loop. Safe to call multiple times — the previous
4
- * timer is cleared before the new one starts.
5
- */
6
- export declare function setHeartbeatConfig(cfg: HeartbeatConfig | null): void;
7
- /** Read the current config — useful for tests. */
8
- export declare function getHeartbeatConfig(): Readonly<HeartbeatState> | null;
9
- /**
10
- * Manually fire a single heartbeat tick. Exposed for tests; in
11
- * production it's invoked by the internal interval.
12
- */
13
- export declare function runOneTick(): void;
14
- /**
15
- * Called from the server's pong handler (or message handler when
16
- * fallback `__stacks_ping__` text frames are in use). Resets the
17
- * missed-pong counter for the given socket so it doesn't get
18
- * declared dead.
19
- */
20
- export declare function markPong(socket: object): void;
21
- export declare interface HeartbeatConfig {
22
- intervalMs?: number
23
- maxMissedPongs?: number
24
- onDead?: (socket: unknown) => void
25
- }
26
- declare interface HeartbeatState {
27
- intervalMs: number
28
- maxMissedPongs: number
29
- onDead: (socket: unknown) => void
30
- missed: WeakMap<object, number>
31
- timer: ReturnType<typeof setInterval> | null
32
- }
package/dist/index.d.ts DELETED
@@ -1,37 +0,0 @@
1
- export type { EmitOptions } from './emit';
2
- export type { BroadcastInstance } from './broadcast';
3
- export type { BackpressureGuardConfig } from './broadcast';
4
- export type { HeartbeatConfig } from './heartbeat';
5
- export type { BufferedMessage, ReplayBufferConfig } from './replay-buffer';
6
- export type { WsAuthenticator, WsAuthResult } from './ws';
7
- /**
8
- * Stacks Realtime Module
9
- *
10
- * This module provides real-time broadcasting capabilities for Stacks applications.
11
- * It's built on top of ts-broadcasting and provides a familiar Laravel-like API.
12
- */
13
- // Re-export everything from ts-broadcasting
14
- export * from 'ts-broadcasting';
15
- // Note: all exports are already provided by `export * from 'ts-broadcasting'` above.
16
- // Aliases are provided below for convenience.
17
- // Server instance management
18
- export { getServer, setServer, createServer, stopServer } from './server-instance';
19
- // Stacks-specific exports
20
- export { emit, emitToUser, emitToUsers } from './emit';
21
- export { channel, channel as createChannel, Channel as StacksChannel } from './channel';
22
- export { broadcast as dispatchBroadcast, runBroadcast, Broadcast as LegacyBroadcast } from './broadcast';
23
- // Backpressure guard for slow consumers (stacksjs/stacks#1877 R-2).
24
- // Opt-in via setBackpressureGuard; default is no-op.
25
- export { setBackpressureGuard, getBackpressureGuard } from './broadcast';
26
- // Heartbeat ping/pong for detecting half-closed sockets
27
- // (stacksjs/stacks#1877 R-5). Opt-in via setHeartbeatConfig.
28
- export { getHeartbeatConfig, markPong, runOneTick, setHeartbeatConfig } from './heartbeat';
29
- // At-least-once replay buffer for reconnect (stacksjs/stacks#1877 R-3).
30
- // Opt-in via setReplayBuffer. Apps wire `replaySince(channel, seq)`
31
- // into their reconnect handler to re-send missed messages.
32
- export { debugSnapshot, getReplayBuffer, pruneExpired, recordBroadcast, replaySince, setReplayBuffer } from './replay-buffer';
33
- export { setBunSocket, handleWebSocketRequest, storeWebSocketEvent } from './ws';
34
- // WebSocket authenticator wiring (stacksjs/stacks#1877 R-1). Install
35
- // once at server boot to require a valid token / cookie at the
36
- // handshake boundary — without it, the upgrade proceeds unauthed.
37
- export { setWsAuthenticator, getWsAuthenticator } from './ws';
package/dist/index.js DELETED
@@ -1,2 +0,0 @@
1
- // @bun
2
- export*from"ts-broadcasting";export{l as storeWebSocketEvent,C as stopServer,p as setWsAuthenticator,A as setServer,m as setReplayBuffer,W as setHeartbeatConfig,f as setBunSocket,Q as setBackpressureGuard,V as runOneTick,N as runBroadcast,k as replaySince,$ as recordBroadcast,_ as pruneExpired,U as markPong,g as handleWebSocketRequest,w as getWsAuthenticator,z as getServer,Z as getReplayBuffer,T as getHeartbeatConfig,R as getBackpressureGuard,G as emitToUsers,F as emitToUser,E as emit,M as dispatchBroadcast,Y as debugSnapshot,B as createServer,J as createChannel,I as channel,K as StacksChannel,O as LegacyBroadcast};
@@ -1,83 +0,0 @@
1
- /**
2
- * Install (or replace) the replay-buffer config. Pass `null` to disable
3
- * and drop all buffered state. Safe to call multiple times.
4
- */
5
- export declare function setReplayBuffer(cfg: ReplayBufferConfig | null): void;
6
- /** Read the current config — useful for tests. */
7
- export declare function getReplayBuffer(): Readonly<BufferRegistry> | null;
8
- /**
9
- * Called by the broadcast wrapper for every outbound message on a
10
- * matched channel. Records the message and assigns a monotonic seq.
11
- * Returns the seq for the caller to optionally include in the
12
- * outbound payload — clients store the latest seq locally and send
13
- * it back on reconnect via `replaySince`.
14
- */
15
- export declare function recordBroadcast(channel: string, event: string, data: unknown): number | null;
16
- /**
17
- * Replay every buffered message on `channel` with `seq > sinceSeq`.
18
- * Stale entries (older than `ttlMs`) are evicted on the way through
19
- * so callers don't see them. Returns the array of messages the
20
- * caller should re-send to the reconnecting client.
21
- *
22
- * @example
23
- * ```ts
24
- * // Inside the reconnect handler:
25
- * const missed = replaySince('orders', lastSeenSeq)
26
- * for (const msg of missed) {
27
- * socket.send(JSON.stringify({ event: msg.event, data: msg.data, seq: msg.seq }))
28
- * }
29
- * ```
30
- */
31
- export declare function replaySince(channel: string, sinceSeq: number): BufferedMessage[];
32
- /**
33
- * Drop expired entries across every tracked channel. Called by apps
34
- * that want eager memory reclaim — the default lazy-on-read path is
35
- * adequate for most workloads.
36
- */
37
- export declare function pruneExpired(): void;
38
- /**
39
- * Snapshot the buffer state — debugging only. Don't depend on this
40
- * shape in production code; the internals may change.
41
- */
42
- export declare function debugSnapshot(): Record<string, { count: number, firstSeq: number | null, lastSeq: number | null }>;
43
- /**
44
- * Per-channel message replay buffer (stacksjs/stacks#1877 R-3).
45
- *
46
- * Background: ts-broadcasting delivers messages at-most-once — a client
47
- * that drops between two broadcasts loses everything in flight. For
48
- * channels where the app needs every message (chat, presence, order
49
- * updates), reconnect-after-network-blip becomes a silent data loss.
50
- *
51
- * Fix: opt-in per-channel ring buffer that retains the most-recent N
52
- * messages with monotonic sequence IDs. On reconnect, the client sends
53
- * its last-seen seq; the server replays everything stored after that
54
- * point. Apps install via `setReplayBuffer({ channels, maxPerChannel,
55
- * ttlMs })`. Buffer is in-process — for cross-instance replay, route
56
- * through a shared store (Redis Streams, Postgres LISTEN/NOTIFY, etc.).
57
- *
58
- * Memory shape: `Map<channel, RingBuffer<BufferedMessage>>`. Bounded by
59
- * `maxPerChannel` (default 100) so a chatty channel can't OOM the
60
- * server. Entries past `ttlMs` are evicted lazily on read — apps that
61
- * want eager eviction can call `pruneExpired()` from their own timer.
62
- */
63
- export declare interface ReplayBufferConfig {
64
- channels?: string[]
65
- maxPerChannel?: number
66
- ttlMs?: number
67
- }
68
- export declare interface BufferedMessage {
69
- seq: number
70
- ts: number
71
- event: string
72
- data: unknown
73
- }
74
- declare interface ChannelState {
75
- messages: BufferedMessage[]
76
- nextSeq: number
77
- }
78
- declare interface BufferRegistry {
79
- channels: string[]
80
- maxPerChannel: number
81
- ttlMs: number
82
- state: Map<string, ChannelState>
83
- }
@@ -1,17 +0,0 @@
1
- import type { BroadcastServer, ServerConfig } from 'ts-broadcasting';
2
- /**
3
- * Set the global broadcast server instance
4
- */
5
- export declare function setServer(server: BroadcastServer): void;
6
- /**
7
- * Get the global broadcast server instance
8
- */
9
- export declare function getServer(): BroadcastServer | null;
10
- /**
11
- * Create and start a new broadcast server
12
- */
13
- export declare function createServer(config: ServerConfig): Promise<BroadcastServer>;
14
- /**
15
- * Stop the current broadcast server
16
- */
17
- export declare function stopServer(): Promise<void>;
package/dist/ws.d.ts DELETED
@@ -1,44 +0,0 @@
1
- import type { Server } from 'bun';
2
- import type { BroadcastServer } from 'ts-broadcasting';
3
- /**
4
- * Set the broadcast server instance
5
- * @deprecated Use setServer from './server-instance' instead
6
- */
7
- export declare function setBunSocket(server: BroadcastServer | null): void;
8
- /**
9
- * Store WebSocket event in the database
10
- * Note: This function is now a no-op. WebSocket events are tracked internally by ts-broadcasting.
11
- */
12
- export declare function storeWebSocketEvent(_type: 'disconnection' | 'error' | 'success', _socket: string, _details: string): Promise<void>;
13
- /**
14
- * Install (or clear) the global WebSocket authenticator. Called once
15
- * at server boot; pass `null` to disable auth (the unauthed default).
16
- */
17
- export declare function setWsAuthenticator(fn: WsAuthenticator | null): void;
18
- /** Read the currently-installed authenticator. Useful for tests. */
19
- export declare function getWsAuthenticator(): WsAuthenticator | null;
20
- /**
21
- * Handle WebSocket request upgrade. If an authenticator is installed
22
- * (see `setWsAuthenticator`), it runs FIRST and a 401 is returned on
23
- * failure (stacksjs/stacks#1877 R-1). Without an authenticator the
24
- * upgrade proceeds for backwards-compat — the function still works
25
- * the same way it did before.
26
- */
27
- export declare function handleWebSocketRequest(req: Request, server: Server<any>): Promise<Response | undefined>;
28
- /**
29
- * Optional authenticator invoked at WebSocket handshake time.
30
- *
31
- * Apps install one via `setWsAuthenticator(fn)` to require a valid
32
- * token / cookie / signed query param BEFORE the upgrade goes through
33
- * (stacksjs/stacks#1877 R-1). Without an authenticator, the upgrade
34
- * proceeds as before — useful for local-dev / public-broadcast apps,
35
- * but production apps should always install one.
36
- *
37
- * The returned `data` is attached to the upgraded socket as `ws.data`
38
- * so per-message authorization can read it back without re-parsing
39
- * the auth token on every frame.
40
- */
41
- export type WsAuthenticator = (req: Request) => Promise<WsAuthResult> | WsAuthResult;
42
- /** Result returned from a `WsAuthenticator`. */
43
- export type WsAuthResult = | { ok: true, data?: Record<string, unknown> }
44
- | { ok: false, status?: number, message?: string }