@rotorsoft/act-sse 1.0.0 → 1.1.0
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/README.md +67 -62
- package/dist/.tsbuildinfo +1 -1
- package/dist/@types/apply-patch.d.ts +9 -15
- package/dist/@types/apply-patch.d.ts.map +1 -1
- package/dist/@types/broadcast.d.ts +14 -17
- package/dist/@types/broadcast.d.ts.map +1 -1
- package/dist/@types/index.d.ts +12 -14
- package/dist/@types/index.d.ts.map +1 -1
- package/dist/@types/patch.d.ts +12 -0
- package/dist/@types/patch.d.ts.map +1 -0
- package/dist/@types/types.d.ts +10 -30
- package/dist/@types/types.d.ts.map +1 -1
- package/dist/index.cjs +66 -56
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +64 -55
- package/dist/index.js.map +1 -1
- package/package.json +2 -4
package/dist/index.js
CHANGED
|
@@ -1,28 +1,56 @@
|
|
|
1
|
+
// src/patch.ts
|
|
2
|
+
var UNMERGEABLES = [
|
|
3
|
+
RegExp,
|
|
4
|
+
Date,
|
|
5
|
+
Array,
|
|
6
|
+
Map,
|
|
7
|
+
Set,
|
|
8
|
+
WeakMap,
|
|
9
|
+
WeakSet,
|
|
10
|
+
ArrayBuffer,
|
|
11
|
+
SharedArrayBuffer,
|
|
12
|
+
DataView,
|
|
13
|
+
Int8Array,
|
|
14
|
+
Uint8Array,
|
|
15
|
+
Uint8ClampedArray,
|
|
16
|
+
Int16Array,
|
|
17
|
+
Uint16Array,
|
|
18
|
+
Int32Array,
|
|
19
|
+
Uint32Array,
|
|
20
|
+
Float32Array,
|
|
21
|
+
Float64Array
|
|
22
|
+
];
|
|
23
|
+
var is_mergeable = (value) => !!value && typeof value === "object" && !UNMERGEABLES.some((t) => value instanceof t);
|
|
24
|
+
var patch = (original, patches) => {
|
|
25
|
+
const copy = {};
|
|
26
|
+
Object.keys({ ...original, ...patches }).forEach((key) => {
|
|
27
|
+
const patched_value = patches[key];
|
|
28
|
+
const original_value = original[key];
|
|
29
|
+
const patched = patches && key in patches;
|
|
30
|
+
const deleted = patched && (typeof patched_value === "undefined" || patched_value === null);
|
|
31
|
+
const value = patched && !deleted ? patched_value : original_value;
|
|
32
|
+
!deleted && (copy[key] = is_mergeable(value) ? patch(original_value || {}, patched_value || {}) : value);
|
|
33
|
+
});
|
|
34
|
+
return copy;
|
|
35
|
+
};
|
|
36
|
+
|
|
1
37
|
// src/apply-patch.ts
|
|
2
|
-
|
|
3
|
-
function applyBroadcastMessage(msg, cached) {
|
|
38
|
+
function applyPatchMessage(msg, cached) {
|
|
4
39
|
const cachedV = cached?._v ?? 0;
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
}
|
|
10
|
-
if (
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
return { ok: true, state: clone };
|
|
18
|
-
} catch {
|
|
19
|
-
return { ok: false, reason: "patch-failed" };
|
|
20
|
-
}
|
|
40
|
+
const versions = Object.keys(msg).map(Number).sort((a, b) => a - b);
|
|
41
|
+
if (!versions.length) return { ok: false, reason: "stale" };
|
|
42
|
+
const minV = versions[0];
|
|
43
|
+
const maxV = versions[versions.length - 1];
|
|
44
|
+
if (maxV <= cachedV) return { ok: false, reason: "stale" };
|
|
45
|
+
if (!cached || minV > cachedV + 1) return { ok: false, reason: "behind" };
|
|
46
|
+
let state = cached;
|
|
47
|
+
for (const v of versions) {
|
|
48
|
+
if (v <= cachedV) continue;
|
|
49
|
+
state = { ...patch(state, msg[v]), _v: v };
|
|
50
|
+
}
|
|
51
|
+
return { ok: true, state };
|
|
21
52
|
}
|
|
22
53
|
|
|
23
|
-
// src/broadcast.ts
|
|
24
|
-
import { compare } from "fast-json-patch";
|
|
25
|
-
|
|
26
54
|
// src/state-cache.ts
|
|
27
55
|
var StateCache = class {
|
|
28
56
|
cache = /* @__PURE__ */ new Map();
|
|
@@ -66,27 +94,27 @@ var StateCache = class {
|
|
|
66
94
|
};
|
|
67
95
|
|
|
68
96
|
// src/broadcast.ts
|
|
69
|
-
var DEFAULT_MAX_PATCH_OPS = 50;
|
|
70
97
|
var BroadcastChannel = class {
|
|
71
98
|
channels = /* @__PURE__ */ new Map();
|
|
72
99
|
stateCache;
|
|
73
|
-
maxPatchOps;
|
|
74
100
|
constructor(options) {
|
|
75
101
|
this.stateCache = new StateCache(options?.cacheSize ?? 50);
|
|
76
|
-
this.maxPatchOps = options?.maxPatchOps ?? DEFAULT_MAX_PATCH_OPS;
|
|
77
102
|
}
|
|
78
103
|
/**
|
|
79
|
-
* Publish
|
|
80
|
-
*
|
|
104
|
+
* Publish domain patches from a commit.
|
|
105
|
+
* patches[i] corresponds to version baseV + i + 1.
|
|
81
106
|
*
|
|
82
107
|
* @param streamId - The event store stream ID
|
|
83
108
|
* @param state - Full state with `_v` set from `snap.event.version`
|
|
84
|
-
* @
|
|
109
|
+
* @param patches - Array of domain patches, one per emitted event
|
|
85
110
|
*/
|
|
86
|
-
publish(streamId, state) {
|
|
87
|
-
const prev = this.stateCache.get(streamId);
|
|
111
|
+
publish(streamId, state, patches = []) {
|
|
88
112
|
this.stateCache.set(streamId, state);
|
|
89
|
-
const
|
|
113
|
+
const baseV = state._v - patches.length;
|
|
114
|
+
const msg = {};
|
|
115
|
+
patches.forEach((p, i) => {
|
|
116
|
+
msg[baseV + i + 1] = p;
|
|
117
|
+
});
|
|
90
118
|
const subs = this.channels.get(streamId);
|
|
91
119
|
if (subs?.size) {
|
|
92
120
|
for (const cb of subs) cb(msg);
|
|
@@ -96,13 +124,14 @@ var BroadcastChannel = class {
|
|
|
96
124
|
/**
|
|
97
125
|
* Publish a state update that doesn't change the event version
|
|
98
126
|
* (e.g. presence overlay, computed field refresh).
|
|
99
|
-
* Uses the same version as the cached state
|
|
127
|
+
* Uses the same version as the cached state, single entry.
|
|
100
128
|
*/
|
|
101
|
-
publishOverlay(streamId,
|
|
129
|
+
publishOverlay(streamId, overlayPatch) {
|
|
102
130
|
const prev = this.stateCache.get(streamId);
|
|
103
131
|
if (!prev) return void 0;
|
|
132
|
+
const state = patch(prev, overlayPatch);
|
|
104
133
|
this.stateCache.set(streamId, state);
|
|
105
|
-
const msg =
|
|
134
|
+
const msg = { [state._v]: overlayPatch };
|
|
106
135
|
const subs = this.channels.get(streamId);
|
|
107
136
|
if (subs?.size) {
|
|
108
137
|
for (const cb of subs) cb(msg);
|
|
@@ -135,27 +164,6 @@ var BroadcastChannel = class {
|
|
|
135
164
|
get cache() {
|
|
136
165
|
return this.stateCache;
|
|
137
166
|
}
|
|
138
|
-
// --- internals ---
|
|
139
|
-
computeMessage(prev, next) {
|
|
140
|
-
const serverTime = (/* @__PURE__ */ new Date()).toISOString();
|
|
141
|
-
if (!prev) {
|
|
142
|
-
return { _type: "full", ...next, serverTime };
|
|
143
|
-
}
|
|
144
|
-
const ops = compare(prev, next);
|
|
145
|
-
if (ops.length === 0) {
|
|
146
|
-
return { _type: "full", ...next, serverTime };
|
|
147
|
-
}
|
|
148
|
-
if (ops.length > this.maxPatchOps) {
|
|
149
|
-
return { _type: "full", ...next, serverTime };
|
|
150
|
-
}
|
|
151
|
-
return {
|
|
152
|
-
_type: "patch",
|
|
153
|
-
_v: next._v,
|
|
154
|
-
_baseV: prev._v,
|
|
155
|
-
_patch: ops,
|
|
156
|
-
serverTime
|
|
157
|
-
};
|
|
158
|
-
}
|
|
159
167
|
};
|
|
160
168
|
|
|
161
169
|
// src/presence.ts
|
|
@@ -190,6 +198,7 @@ export {
|
|
|
190
198
|
BroadcastChannel,
|
|
191
199
|
PresenceTracker,
|
|
192
200
|
StateCache,
|
|
193
|
-
|
|
201
|
+
applyPatchMessage,
|
|
202
|
+
patch
|
|
194
203
|
};
|
|
195
204
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/apply-patch.ts","../src/broadcast.ts","../src/state-cache.ts","../src/presence.ts"],"sourcesContent":["import { applyPatch } from \"fast-json-patch\";\nimport type { BroadcastMessage, BroadcastState } from \"./types.js\";\n\n/**\n * Result of applying a broadcast message to cached client state.\n */\nexport type ApplyResult<S extends BroadcastState = BroadcastState> =\n | { ok: true; state: S }\n | { ok: false; reason: \"stale\" | \"behind\" | \"patch-failed\" };\n\n/**\n * Apply a broadcast message to the client's cached state.\n *\n * Handles both full state and incremental patches with version validation.\n * Returns the new state on success, or a failure reason that the client\n * can use to decide whether to resync.\n *\n * ## Version logic\n *\n * - **Full state**: accepted if `msg._v >= cachedV` (or no cached state)\n * - **Patch**:\n * - `_baseV < cachedV` → \"stale\" (client ahead, skip — mutation response arrived first)\n * - `_baseV > cachedV` → \"behind\" (client missed a version, must resync)\n * - `_baseV === cachedV` → apply patch ops\n *\n * ## Usage (React Query)\n *\n * ```typescript\n * onData: (msg) => {\n * const cached = utils.getState.getData({ streamId });\n * const result = applyBroadcastMessage(msg, cached);\n * if (result.ok) {\n * utils.getState.setData({ streamId }, result.state);\n * } else if (result.reason === \"behind\") {\n * utils.getState.invalidate({ streamId }); // trigger full refetch\n * }\n * // \"stale\" → no-op, client already has newer state\n * }\n * ```\n */\nexport function applyBroadcastMessage<S extends BroadcastState>(\n msg: BroadcastMessage<S>,\n cached: S | null | undefined\n): ApplyResult<S> {\n const cachedV = cached?._v ?? 0;\n\n if (msg._type === \"full\") {\n if (msg._v < cachedV) return { ok: false, reason: \"stale\" };\n // Strip _type from the state stored in cache\n const { _type, ...state } = msg;\n return { ok: true, state: state as S };\n }\n\n // Patch message\n if (msg._baseV < cachedV) return { ok: false, reason: \"stale\" };\n if (msg._baseV > cachedV) return { ok: false, reason: \"behind\" };\n\n // _baseV === cachedV — apply\n if (!cached) return { ok: false, reason: \"behind\" };\n\n try {\n const clone = structuredClone(cached);\n applyPatch(clone, msg._patch, true); // mutates clone in-place, throws on validation failure\n clone._v = msg._v;\n return { ok: true, state: clone };\n } catch {\n return { ok: false, reason: \"patch-failed\" };\n }\n}\n","import { compare } from \"fast-json-patch\";\nimport { StateCache } from \"./state-cache.js\";\nimport type {\n BroadcastMessage,\n BroadcastOptions,\n BroadcastState,\n Subscriber,\n} from \"./types.js\";\n\nconst DEFAULT_MAX_PATCH_OPS = 50;\n\n/**\n * Server-side broadcast channel for incremental state sync over SSE.\n *\n * Manages per-stream subscriber sets and an LRU state cache. When state\n * changes, computes an RFC 6902 JSON Patch against the previous cached\n * state and pushes either a patch (small diff) or full state (large diff\n * or first broadcast) to all subscribers.\n *\n * ## Usage\n *\n * ```typescript\n * const broadcast = new BroadcastChannel<MyState>();\n *\n * // After every app.do():\n * const snap = await doAction(...);\n * const state = deriveState(snap); // app-specific state derivation\n * broadcast.publish(streamId, state); // computes patch + pushes to SSE\n *\n * // In SSE subscription:\n * const cleanup = broadcast.subscribe(streamId, (msg) => {\n * pending = msg;\n * resolve?.();\n * });\n *\n * // Initial state for reconnects:\n * const cached = broadcast.getState(streamId);\n * if (cached) yield { _type: \"full\", ...cached, serverTime: ... };\n * ```\n *\n * ## Version Contract\n *\n * The `_v` field on state MUST be set from `snap.event.version` (the event\n * store's monotonic stream version) BEFORE calling `publish()`. This is the\n * single source of truth for ordering — no separate version counters.\n */\nexport class BroadcastChannel<S extends BroadcastState = BroadcastState> {\n private channels = new Map<string, Set<Subscriber<S>>>();\n private stateCache: StateCache<S>;\n private maxPatchOps: number;\n\n constructor(options?: BroadcastOptions & { cacheSize?: number }) {\n this.stateCache = new StateCache<S>(options?.cacheSize ?? 50);\n this.maxPatchOps = options?.maxPatchOps ?? DEFAULT_MAX_PATCH_OPS;\n }\n\n /**\n * Publish new state for a stream. Computes a patch against the previously\n * cached state and pushes to all subscribers.\n *\n * @param streamId - The event store stream ID\n * @param state - Full state with `_v` set from `snap.event.version`\n * @returns The broadcast message that was sent (or undefined if no subscribers and no cache change)\n */\n publish(streamId: string, state: S): BroadcastMessage<S> {\n const prev = this.stateCache.get(streamId);\n this.stateCache.set(streamId, state);\n\n const msg = this.computeMessage(prev, state);\n const subs = this.channels.get(streamId);\n if (subs?.size) {\n for (const cb of subs) cb(msg);\n }\n return msg;\n }\n\n /**\n * Publish a state update that doesn't change the event version\n * (e.g. presence overlay, computed field refresh).\n * Uses the same version as the cached state for _baseV and _v.\n */\n publishOverlay(streamId: string, state: S): BroadcastMessage<S> | undefined {\n const prev = this.stateCache.get(streamId);\n if (!prev) return undefined;\n\n this.stateCache.set(streamId, state);\n\n const msg = this.computeMessage(prev, state);\n const subs = this.channels.get(streamId);\n if (subs?.size) {\n for (const cb of subs) cb(msg);\n }\n return msg;\n }\n\n /**\n * Subscribe to broadcast messages for a stream.\n * Returns a cleanup function that removes the subscription.\n */\n subscribe(streamId: string, cb: Subscriber<S>): () => void {\n if (!this.channels.has(streamId)) this.channels.set(streamId, new Set());\n this.channels.get(streamId)!.add(cb);\n return () => {\n this.channels.get(streamId)?.delete(cb);\n if (this.channels.get(streamId)?.size === 0) {\n this.channels.delete(streamId);\n }\n };\n }\n\n /** Get the number of subscribers for a stream. */\n getSubscriberCount(streamId: string): number {\n return this.channels.get(streamId)?.size ?? 0;\n }\n\n /** Get the cached state for a stream (for reconnects / initial SSE yield). */\n getState(streamId: string): S | undefined {\n return this.stateCache.get(streamId);\n }\n\n /** Direct access to the state cache (for app-specific reads like presence). */\n get cache(): StateCache<S> {\n return this.stateCache;\n }\n\n // --- internals ---\n\n private computeMessage(prev: S | undefined, next: S): BroadcastMessage<S> {\n const serverTime = new Date().toISOString();\n\n if (!prev) {\n return { _type: \"full\", ...next, serverTime };\n }\n\n const ops = compare(prev, next);\n if (ops.length === 0) {\n // No actual diff — still send full state so subscribers get serverTime refresh\n return { _type: \"full\", ...next, serverTime };\n }\n if (ops.length > this.maxPatchOps) {\n return { _type: \"full\", ...next, serverTime };\n }\n\n return {\n _type: \"patch\",\n _v: next._v,\n _baseV: prev._v,\n _patch: ops,\n serverTime,\n };\n }\n}\n","import type { BroadcastState } from \"./types.js\";\n\n/**\n * Generic LRU cache for aggregate state objects.\n *\n * Keyed by stream ID. Each entry stores the full state (with `_v` from the\n * event store). Used as the \"previous state\" baseline for computing patches,\n * and as the fast path for reconnects.\n *\n * The cache is shared between the broadcast hot path and read queries.\n * Projections should maintain their own cache to avoid double-apply bugs.\n */\nexport class StateCache<S extends BroadcastState = BroadcastState> {\n private cache = new Map<string, S>();\n private maxSize: number;\n\n constructor(maxSize = 50) {\n this.maxSize = maxSize;\n }\n\n /** Get a cached state, promoting it to MRU position. */\n get(key: string): S | undefined {\n const s = this.cache.get(key);\n if (s) {\n // Move to end (MRU)\n this.cache.delete(key);\n this.cache.set(key, s);\n }\n return s;\n }\n\n /** Set a cached state, evicting the LRU entry if at capacity. */\n set(key: string, state: S): void {\n this.cache.delete(key);\n this.cache.set(key, state);\n if (this.cache.size > this.maxSize) {\n // Size > max guarantees at least one entry exists\n this.cache.delete(this.cache.keys().next().value!);\n }\n }\n\n /** Remove a cached entry. */\n delete(key: string): void {\n this.cache.delete(key);\n }\n\n /** Check if a key exists in the cache. */\n has(key: string): boolean {\n return this.cache.has(key);\n }\n\n /** Current number of cached entries. */\n get size(): number {\n return this.cache.size;\n }\n\n /** Direct access to the underlying map (for iteration). */\n entries(): IterableIterator<[string, S]> {\n return this.cache.entries();\n }\n}\n","/**\n * Generic presence tracker — ref-counted online status per stream per identity.\n *\n * Supports multi-tab: each subscribe increments the ref count, each\n * unsubscribe decrements it. An identity is considered online when\n * ref count > 0.\n *\n * ## Usage\n *\n * ```typescript\n * const presence = new PresenceTracker();\n *\n * // On SSE connect:\n * presence.add(gameId, playerId);\n *\n * // On SSE disconnect:\n * presence.remove(gameId, playerId);\n *\n * // Query:\n * presence.getOnline(gameId); // Set<string>\n * ```\n */\nexport class PresenceTracker {\n private streams = new Map<string, Map<string, number>>();\n\n /** Increment ref count for an identity on a stream. */\n add(streamId: string, identityId: string): void {\n if (!this.streams.has(streamId)) this.streams.set(streamId, new Map());\n const counts = this.streams.get(streamId)!;\n counts.set(identityId, (counts.get(identityId) ?? 0) + 1);\n }\n\n /** Decrement ref count. Removes the identity when count reaches 0. */\n remove(streamId: string, identityId: string): void {\n const counts = this.streams.get(streamId);\n if (!counts) return;\n const n = (counts.get(identityId) ?? 1) - 1;\n if (n <= 0) counts.delete(identityId);\n else counts.set(identityId, n);\n if (counts.size === 0) this.streams.delete(streamId);\n }\n\n /** Get the set of online identity IDs for a stream. */\n getOnline(streamId: string): Set<string> {\n const counts = this.streams.get(streamId);\n return counts ? new Set(counts.keys()) : new Set();\n }\n\n /** Check if a specific identity is online for a stream. */\n isOnline(streamId: string, identityId: string): boolean {\n return (this.streams.get(streamId)?.get(identityId) ?? 0) > 0;\n }\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/patch.ts","../src/apply-patch.ts","../src/state-cache.ts","../src/broadcast.ts","../src/presence.ts"],"sourcesContent":["/**\n * Browser-safe deep merge utility — identical semantics to @rotorsoft/act's patch().\n * Inlined here so act-sse has zero Node dependencies.\n */\n\ntype Schema = Record<string, any>;\ntype Patch<T> = {\n [K in keyof T]?: T[K] extends Schema ? Patch<T[K]> : T[K];\n};\n\n/** These objects are copied instead of deep merged */\nconst UNMERGEABLES = [\n RegExp,\n Date,\n Array,\n Map,\n Set,\n WeakMap,\n WeakSet,\n ArrayBuffer,\n SharedArrayBuffer,\n DataView,\n Int8Array,\n Uint8Array,\n Uint8ClampedArray,\n Int16Array,\n Uint16Array,\n Int32Array,\n Uint32Array,\n Float32Array,\n Float64Array,\n];\n\nconst is_mergeable = (value: any): boolean =>\n !!value &&\n typeof value === \"object\" &&\n !UNMERGEABLES.some((t) => value instanceof t);\n\n/** Immutably deep-merge `patches` into `original`. */\nexport const patch = <S extends Schema>(\n original: Readonly<S>,\n patches: Readonly<Patch<S>>\n): Readonly<S> => {\n const copy = {} as Record<string, any>;\n Object.keys({ ...original, ...patches }).forEach((key) => {\n const patched_value = patches[key as keyof typeof patches];\n const original_value = original[key as keyof typeof original];\n const patched = patches && key in patches;\n const deleted =\n patched &&\n (typeof patched_value === \"undefined\" || patched_value === null);\n const value = patched && !deleted ? patched_value : original_value;\n !deleted &&\n (copy[key] = is_mergeable(value)\n ? patch(original_value || {}, patched_value || {})\n : value);\n });\n return copy as S;\n};\n","import { patch as deepMerge } from \"./patch.js\";\nimport type { BroadcastState, PatchMessage } from \"./types.js\";\n\n/**\n * Result of applying a patch message to cached client state.\n */\nexport type ApplyResult<S extends BroadcastState = BroadcastState> =\n | { ok: true; state: S }\n | { ok: false; reason: \"stale\" | \"behind\" };\n\n/**\n * Apply a version-keyed patch message to the client's cached state.\n *\n * ## Version logic\n *\n * - All patches older than cached → \"stale\" (client already ahead)\n * - Gap between cached version and first patch → \"behind\" (client missed versions, must resync)\n * - Contiguous from cached version → apply in order\n *\n * ## Usage (React Query)\n *\n * ```typescript\n * onData: (msg) => {\n * const cached = utils.getState.getData({ streamId });\n * const result = applyPatchMessage(msg, cached);\n * if (result.ok) {\n * utils.getState.setData({ streamId }, result.state);\n * } else if (result.reason === \"behind\") {\n * utils.getState.invalidate({ streamId }); // trigger full refetch\n * }\n * // \"stale\" → no-op, client already has newer state\n * }\n * ```\n */\nexport function applyPatchMessage<S extends BroadcastState>(\n msg: PatchMessage<S>,\n cached: S | null | undefined\n): ApplyResult<S> {\n const cachedV = cached?._v ?? 0;\n const versions = Object.keys(msg)\n .map(Number)\n .sort((a, b) => a - b);\n\n if (!versions.length) return { ok: false, reason: \"stale\" };\n\n const minV = versions[0];\n const maxV = versions[versions.length - 1];\n\n // All patches are older than what we have\n if (maxV <= cachedV) return { ok: false, reason: \"stale\" };\n // Gap — we missed versions\n if (!cached || minV > cachedV + 1) return { ok: false, reason: \"behind\" };\n\n // Apply patches in version order, skipping any we already have\n let state = cached;\n for (const v of versions) {\n if (v <= cachedV) continue; // already applied\n state = { ...deepMerge(state, msg[v]), _v: v } as S;\n }\n return { ok: true, state };\n}\n","import type { BroadcastState } from \"./types.js\";\n\n/**\n * Generic LRU cache for aggregate state objects.\n *\n * Keyed by stream ID. Each entry stores the full state (with `_v` from the\n * event store). Used as the \"previous state\" baseline for computing patches,\n * and as the fast path for reconnects.\n *\n * The cache is shared between the broadcast hot path and read queries.\n * Projections should maintain their own cache to avoid double-apply bugs.\n */\nexport class StateCache<S extends BroadcastState = BroadcastState> {\n private cache = new Map<string, S>();\n private maxSize: number;\n\n constructor(maxSize = 50) {\n this.maxSize = maxSize;\n }\n\n /** Get a cached state, promoting it to MRU position. */\n get(key: string): S | undefined {\n const s = this.cache.get(key);\n if (s) {\n // Move to end (MRU)\n this.cache.delete(key);\n this.cache.set(key, s);\n }\n return s;\n }\n\n /** Set a cached state, evicting the LRU entry if at capacity. */\n set(key: string, state: S): void {\n this.cache.delete(key);\n this.cache.set(key, state);\n if (this.cache.size > this.maxSize) {\n // Size > max guarantees at least one entry exists\n this.cache.delete(this.cache.keys().next().value!);\n }\n }\n\n /** Remove a cached entry. */\n delete(key: string): void {\n this.cache.delete(key);\n }\n\n /** Check if a key exists in the cache. */\n has(key: string): boolean {\n return this.cache.has(key);\n }\n\n /** Current number of cached entries. */\n get size(): number {\n return this.cache.size;\n }\n\n /** Direct access to the underlying map (for iteration). */\n entries(): IterableIterator<[string, S]> {\n return this.cache.entries();\n }\n}\n","import { patch as applyPatch } from \"./patch.js\";\nimport { StateCache } from \"./state-cache.js\";\nimport type { BroadcastState, PatchMessage, Subscriber } from \"./types.js\";\n\n/**\n * Server-side broadcast channel for incremental state sync over SSE.\n *\n * Manages per-stream subscriber sets and an LRU state cache. When state\n * changes, forwards domain patches (from event handlers) to all subscribers\n * as version-keyed messages.\n *\n * ## Usage\n *\n * ```typescript\n * const broadcast = new BroadcastChannel<MyState>();\n *\n * // After every app.do():\n * const snaps = await app.do(...);\n * const patches = snaps.map(s => s.patch).filter(Boolean);\n * const state = deriveState(snaps.at(-1));\n * broadcast.publish(streamId, state, patches);\n *\n * // In SSE subscription:\n * const cleanup = broadcast.subscribe(streamId, (msg) => {\n * pending = msg;\n * resolve?.();\n * });\n *\n * // Initial state for reconnects:\n * const cached = broadcast.getState(streamId);\n * ```\n *\n * ## Version Contract\n *\n * The `_v` field on state MUST be set from `snap.event.version` (the event\n * store's monotonic stream version) BEFORE calling `publish()`. This is the\n * single source of truth for ordering — no separate version counters.\n */\nexport class BroadcastChannel<S extends BroadcastState = BroadcastState> {\n private channels = new Map<string, Set<Subscriber<S>>>();\n private stateCache: StateCache<S>;\n\n constructor(options?: { cacheSize?: number }) {\n this.stateCache = new StateCache<S>(options?.cacheSize ?? 50);\n }\n\n /**\n * Publish domain patches from a commit.\n * patches[i] corresponds to version baseV + i + 1.\n *\n * @param streamId - The event store stream ID\n * @param state - Full state with `_v` set from `snap.event.version`\n * @param patches - Array of domain patches, one per emitted event\n */\n publish(\n streamId: string,\n state: S,\n patches: Partial<S>[] = []\n ): PatchMessage<S> {\n this.stateCache.set(streamId, state);\n\n const baseV = state._v - patches.length;\n const msg: PatchMessage<S> = {};\n patches.forEach((p, i) => {\n msg[baseV + i + 1] = p;\n });\n\n const subs = this.channels.get(streamId);\n if (subs?.size) {\n for (const cb of subs) cb(msg);\n }\n return msg;\n }\n\n /**\n * Publish a state update that doesn't change the event version\n * (e.g. presence overlay, computed field refresh).\n * Uses the same version as the cached state, single entry.\n */\n publishOverlay(\n streamId: string,\n overlayPatch: Partial<S>\n ): PatchMessage<S> | undefined {\n const prev = this.stateCache.get(streamId);\n if (!prev) return undefined;\n\n const state = applyPatch(prev, overlayPatch) as S;\n this.stateCache.set(streamId, state);\n\n const msg: PatchMessage<S> = { [state._v]: overlayPatch };\n const subs = this.channels.get(streamId);\n if (subs?.size) {\n for (const cb of subs) cb(msg);\n }\n return msg;\n }\n\n /**\n * Subscribe to broadcast messages for a stream.\n * Returns a cleanup function that removes the subscription.\n */\n subscribe(streamId: string, cb: Subscriber<S>): () => void {\n if (!this.channels.has(streamId)) this.channels.set(streamId, new Set());\n this.channels.get(streamId)!.add(cb);\n return () => {\n this.channels.get(streamId)?.delete(cb);\n if (this.channels.get(streamId)?.size === 0) {\n this.channels.delete(streamId);\n }\n };\n }\n\n /** Get the number of subscribers for a stream. */\n getSubscriberCount(streamId: string): number {\n return this.channels.get(streamId)?.size ?? 0;\n }\n\n /** Get the cached state for a stream (for reconnects / initial SSE yield). */\n getState(streamId: string): S | undefined {\n return this.stateCache.get(streamId);\n }\n\n /** Direct access to the state cache (for app-specific reads like presence). */\n get cache(): StateCache<S> {\n return this.stateCache;\n }\n}\n","/**\n * Generic presence tracker — ref-counted online status per stream per identity.\n *\n * Supports multi-tab: each subscribe increments the ref count, each\n * unsubscribe decrements it. An identity is considered online when\n * ref count > 0.\n *\n * ## Usage\n *\n * ```typescript\n * const presence = new PresenceTracker();\n *\n * // On SSE connect:\n * presence.add(gameId, playerId);\n *\n * // On SSE disconnect:\n * presence.remove(gameId, playerId);\n *\n * // Query:\n * presence.getOnline(gameId); // Set<string>\n * ```\n */\nexport class PresenceTracker {\n private streams = new Map<string, Map<string, number>>();\n\n /** Increment ref count for an identity on a stream. */\n add(streamId: string, identityId: string): void {\n if (!this.streams.has(streamId)) this.streams.set(streamId, new Map());\n const counts = this.streams.get(streamId)!;\n counts.set(identityId, (counts.get(identityId) ?? 0) + 1);\n }\n\n /** Decrement ref count. Removes the identity when count reaches 0. */\n remove(streamId: string, identityId: string): void {\n const counts = this.streams.get(streamId);\n if (!counts) return;\n const n = (counts.get(identityId) ?? 1) - 1;\n if (n <= 0) counts.delete(identityId);\n else counts.set(identityId, n);\n if (counts.size === 0) this.streams.delete(streamId);\n }\n\n /** Get the set of online identity IDs for a stream. */\n getOnline(streamId: string): Set<string> {\n const counts = this.streams.get(streamId);\n return counts ? new Set(counts.keys()) : new Set();\n }\n\n /** Check if a specific identity is online for a stream. */\n isOnline(streamId: string, identityId: string): boolean {\n return (this.streams.get(streamId)?.get(identityId) ?? 0) > 0;\n }\n}\n"],"mappings":"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","names":[]}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rotorsoft/act-sse",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "1.
|
|
4
|
+
"version": "1.1.0",
|
|
5
5
|
"description": "Incremental state broadcast over SSE for act apps",
|
|
6
6
|
"author": "rotorsoft",
|
|
7
7
|
"license": "MIT",
|
|
@@ -30,9 +30,7 @@
|
|
|
30
30
|
"publishConfig": {
|
|
31
31
|
"access": "public"
|
|
32
32
|
},
|
|
33
|
-
"dependencies": {
|
|
34
|
-
"fast-json-patch": "^3.1.1"
|
|
35
|
-
},
|
|
33
|
+
"dependencies": {},
|
|
36
34
|
"scripts": {
|
|
37
35
|
"clean": "rm -rf dist",
|
|
38
36
|
"types": "tsc --build tsconfig.build.json --emitDeclarationOnly",
|