@rotorsoft/act-sse 1.0.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/LICENSE +21 -0
- package/README.md +180 -0
- package/dist/.tsbuildinfo +1 -0
- package/dist/@types/apply-patch.d.ts +43 -0
- package/dist/@types/apply-patch.d.ts.map +1 -0
- package/dist/@types/broadcast.d.ts +73 -0
- package/dist/@types/broadcast.d.ts.map +1 -0
- package/dist/@types/index.d.ts +48 -0
- package/dist/@types/index.d.ts.map +1 -0
- package/dist/@types/presence.d.ts +34 -0
- package/dist/@types/presence.d.ts.map +1 -0
- package/dist/@types/state-cache.d.ts +29 -0
- package/dist/@types/state-cache.d.ts.map +1 -0
- package/dist/@types/types.d.ts +46 -0
- package/dist/@types/types.d.ts.map +1 -0
- package/dist/index.cjs +225 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.js +195 -0
- package/dist/index.js.map +1 -0
- package/package.json +41 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2021 Roger Torres
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
# @rotorsoft/act-sse
|
|
2
|
+
|
|
3
|
+
Incremental state broadcast over SSE for [act](https://github.com/rotorsoft/act-root) event-sourced apps.
|
|
4
|
+
|
|
5
|
+
Instead of sending the full aggregate state to every connected client after each action, `act-sse` computes RFC 6902 JSON Patches between consecutive states and sends only the diff — falling back to full state when the patch is too large or the client needs to resync.
|
|
6
|
+
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm install @rotorsoft/act-sse
|
|
11
|
+
# or
|
|
12
|
+
pnpm add @rotorsoft/act-sse
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## Architecture
|
|
16
|
+
|
|
17
|
+
```
|
|
18
|
+
app.do() → snap
|
|
19
|
+
│
|
|
20
|
+
▼
|
|
21
|
+
deriveState(snap) ← app-specific (overlay presence, deadlines, etc.)
|
|
22
|
+
state._v = snap.event.version ← event store version is the single source of truth
|
|
23
|
+
│
|
|
24
|
+
▼
|
|
25
|
+
broadcast.publish(streamId, state)
|
|
26
|
+
│
|
|
27
|
+
├── compare(prev, state) → RFC 6902 ops
|
|
28
|
+
├── ops ≤ threshold → PatchMessage { _type: "patch", _baseV, _v, _patch }
|
|
29
|
+
├── ops > threshold → FullStateMessage { _type: "full", _v, ...state }
|
|
30
|
+
└── push to all SSE subscribers
|
|
31
|
+
│
|
|
32
|
+
▼
|
|
33
|
+
Client: applyBroadcastMessage(msg, cached)
|
|
34
|
+
│
|
|
35
|
+
├── full → accept if _v ≥ cachedV
|
|
36
|
+
├── patch → apply if _baseV === cachedV
|
|
37
|
+
├── stale → skip (client ahead, mutation response arrived first)
|
|
38
|
+
└── behind → invalidate + refetch (client missed a version)
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## Server Usage
|
|
42
|
+
|
|
43
|
+
```typescript
|
|
44
|
+
import { BroadcastChannel, PresenceTracker } from "@rotorsoft/act-sse";
|
|
45
|
+
|
|
46
|
+
// Create a typed broadcast channel for your app state
|
|
47
|
+
const broadcast = new BroadcastChannel<MyAppState>({
|
|
48
|
+
maxPatchOps: 50, // fall back to full state above this (default: 50)
|
|
49
|
+
cacheSize: 50, // LRU cache entries (default: 50)
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
const presence = new PresenceTracker();
|
|
53
|
+
|
|
54
|
+
// After every app.do():
|
|
55
|
+
const snap = await doAction(action, { stream: streamId, actor }, payload);
|
|
56
|
+
const state = deriveState(snap); // your app-specific state derivation
|
|
57
|
+
state._v = snap.event.version; // MUST set from event store version
|
|
58
|
+
broadcast.publish(streamId, state);
|
|
59
|
+
|
|
60
|
+
// For non-event state changes (e.g. presence overlay):
|
|
61
|
+
broadcast.publishOverlay(streamId, stateWithPresence);
|
|
62
|
+
|
|
63
|
+
// SSE subscription (tRPC example):
|
|
64
|
+
onStateChange: publicProcedure
|
|
65
|
+
.input(z.object({ streamId: z.string(), identityId: z.string().optional() }))
|
|
66
|
+
.subscription(async function* ({ input, signal }) {
|
|
67
|
+
const { streamId, identityId } = input;
|
|
68
|
+
|
|
69
|
+
let resolve: (() => void) | null = null;
|
|
70
|
+
let pending: BroadcastMessage | null = null;
|
|
71
|
+
|
|
72
|
+
const cleanup = broadcast.subscribe(streamId, (msg) => {
|
|
73
|
+
pending = msg;
|
|
74
|
+
if (resolve) { resolve(); resolve = null; }
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
if (identityId) presence.add(streamId, identityId);
|
|
78
|
+
|
|
79
|
+
try {
|
|
80
|
+
// Yield current state on connect
|
|
81
|
+
const cached = broadcast.getState(streamId);
|
|
82
|
+
if (cached) yield { _type: "full" as const, ...cached, serverTime: new Date().toISOString() };
|
|
83
|
+
|
|
84
|
+
while (!signal?.aborted) {
|
|
85
|
+
if (!pending) {
|
|
86
|
+
await new Promise<void>((r) => {
|
|
87
|
+
resolve = r;
|
|
88
|
+
signal?.addEventListener("abort", () => r(), { once: true });
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
if (signal?.aborted) break;
|
|
92
|
+
if (pending) {
|
|
93
|
+
const msg = pending;
|
|
94
|
+
pending = null;
|
|
95
|
+
yield msg;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
} finally {
|
|
99
|
+
cleanup();
|
|
100
|
+
if (identityId) presence.remove(streamId, identityId);
|
|
101
|
+
}
|
|
102
|
+
}),
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
## Client Usage
|
|
106
|
+
|
|
107
|
+
```typescript
|
|
108
|
+
import { applyBroadcastMessage } from "@rotorsoft/act-sse";
|
|
109
|
+
|
|
110
|
+
// In your SSE onData handler (React Query example):
|
|
111
|
+
onData: (msg) => {
|
|
112
|
+
const cached = utils.getState.getData({ streamId });
|
|
113
|
+
const result = applyBroadcastMessage(msg, cached);
|
|
114
|
+
|
|
115
|
+
if (result.ok) {
|
|
116
|
+
utils.getState.setData({ streamId }, result.state);
|
|
117
|
+
} else if (result.reason === "behind") {
|
|
118
|
+
// Client missed a version — trigger full refetch
|
|
119
|
+
utils.getState.invalidate({ streamId });
|
|
120
|
+
}
|
|
121
|
+
// "stale" → no-op (client already has newer state from mutation response)
|
|
122
|
+
// "patch-failed" → same as behind, trigger resync
|
|
123
|
+
}
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
## API
|
|
127
|
+
|
|
128
|
+
### `BroadcastChannel<S>`
|
|
129
|
+
|
|
130
|
+
Server-side broadcast manager with per-stream subscriber channels and LRU state cache.
|
|
131
|
+
|
|
132
|
+
| Method | Description |
|
|
133
|
+
|--------|-------------|
|
|
134
|
+
| `publish(streamId, state)` | Compute patch, cache state, push to subscribers |
|
|
135
|
+
| `publishOverlay(streamId, state)` | Same-version update (e.g. presence change) |
|
|
136
|
+
| `subscribe(streamId, cb)` | Register subscriber, returns cleanup function |
|
|
137
|
+
| `getState(streamId)` | Get cached state (for reconnects) |
|
|
138
|
+
| `getSubscriberCount(streamId)` | Number of active subscribers |
|
|
139
|
+
| `cache` | Direct access to `StateCache` instance |
|
|
140
|
+
|
|
141
|
+
### `applyBroadcastMessage(msg, cached)`
|
|
142
|
+
|
|
143
|
+
Client-side patch applicator. Returns `{ ok: true, state }` or `{ ok: false, reason }`.
|
|
144
|
+
|
|
145
|
+
Reasons: `"stale"` (skip), `"behind"` (resync), `"patch-failed"` (resync).
|
|
146
|
+
|
|
147
|
+
### `StateCache<S>`
|
|
148
|
+
|
|
149
|
+
Generic LRU cache. Methods: `get`, `set`, `delete`, `has`, `size`, `entries`.
|
|
150
|
+
|
|
151
|
+
### `PresenceTracker`
|
|
152
|
+
|
|
153
|
+
Ref-counted presence tracking. Methods: `add`, `remove`, `getOnline`, `isOnline`.
|
|
154
|
+
|
|
155
|
+
### Message Types
|
|
156
|
+
|
|
157
|
+
```typescript
|
|
158
|
+
type FullStateMessage<S> = S & { _type: "full"; serverTime: string };
|
|
159
|
+
type PatchMessage = { _type: "patch"; _v: number; _baseV: number; _patch: Operation[]; serverTime: string };
|
|
160
|
+
type BroadcastMessage<S> = FullStateMessage<S> | PatchMessage;
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
## Version Contract
|
|
164
|
+
|
|
165
|
+
The `_v` field **must** be set from `snap.event.version` — the event store's monotonic stream version. This is the single source of truth for ordering. No separate version counters.
|
|
166
|
+
|
|
167
|
+
## Bandwidth Savings
|
|
168
|
+
|
|
169
|
+
Typical savings for a game/collaborative app with ~5KB state:
|
|
170
|
+
|
|
171
|
+
| Action | Full (bytes) | Patch (bytes) | Savings |
|
|
172
|
+
|--------|-------------|--------------|---------|
|
|
173
|
+
| Single field change | ~5,000 | ~150 | 97% |
|
|
174
|
+
| Multi-field update | ~5,000 | ~400 | 92% |
|
|
175
|
+
| Presence toggle | ~5,000 | ~80 | 98% |
|
|
176
|
+
| Large structural change | ~5,000 | ~5,000 (fallback) | 0% |
|
|
177
|
+
|
|
178
|
+
## License
|
|
179
|
+
|
|
180
|
+
MIT
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"fileNames":["../../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es5.d.ts","../../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2015.d.ts","../../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2016.d.ts","../../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2017.d.ts","../../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2018.d.ts","../../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2019.d.ts","../../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2020.d.ts","../../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2021.d.ts","../../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2022.d.ts","../../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.dom.d.ts","../../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.dom.iterable.d.ts","../../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.dom.asynciterable.d.ts","../../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.webworker.importscripts.d.ts","../../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.scripthost.d.ts","../../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2015.core.d.ts","../../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2015.collection.d.ts","../../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2015.generator.d.ts","../../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2015.iterable.d.ts","../../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2015.promise.d.ts","../../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2015.proxy.d.ts","../../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2015.reflect.d.ts","../../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2015.symbol.d.ts","../../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2015.symbol.wellknown.d.ts","../../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2016.array.include.d.ts","../../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2016.intl.d.ts","../../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2017.arraybuffer.d.ts","../../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2017.date.d.ts","../../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2017.object.d.ts","../../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2017.sharedmemory.d.ts","../../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2017.string.d.ts","../../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2017.intl.d.ts","../../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2017.typedarrays.d.ts","../../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2018.asyncgenerator.d.ts","../../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2018.asynciterable.d.ts","../../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2018.intl.d.ts","../../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2018.promise.d.ts","../../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2018.regexp.d.ts","../../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2019.array.d.ts","../../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2019.object.d.ts","../../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2019.string.d.ts","../../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2019.symbol.d.ts","../../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2019.intl.d.ts","../../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2020.bigint.d.ts","../../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2020.date.d.ts","../../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2020.promise.d.ts","../../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2020.sharedmemory.d.ts","../../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2020.string.d.ts","../../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2020.symbol.wellknown.d.ts","../../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2020.intl.d.ts","../../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2020.number.d.ts","../../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2021.promise.d.ts","../../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2021.string.d.ts","../../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2021.weakref.d.ts","../../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2021.intl.d.ts","../../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2022.array.d.ts","../../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2022.error.d.ts","../../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2022.intl.d.ts","../../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2022.object.d.ts","../../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2022.string.d.ts","../../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2022.regexp.d.ts","../../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.esnext.disposable.d.ts","../../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.esnext.float16.d.ts","../../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.decorators.d.ts","../../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.decorators.legacy.d.ts","../../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2022.full.d.ts","../../../node_modules/.pnpm/fast-json-patch@3.1.1/node_modules/fast-json-patch/module/helpers.d.ts","../../../node_modules/.pnpm/fast-json-patch@3.1.1/node_modules/fast-json-patch/module/core.d.ts","../../../node_modules/.pnpm/fast-json-patch@3.1.1/node_modules/fast-json-patch/module/duplex.d.ts","../../../node_modules/.pnpm/fast-json-patch@3.1.1/node_modules/fast-json-patch/index.d.ts","../src/types.ts","../src/apply-patch.ts","../src/state-cache.ts","../src/broadcast.ts","../src/presence.ts","../src/index.ts","../../../node_modules/.pnpm/@types+node@25.4.0/node_modules/@types/node/compatibility/iterators.d.ts","../../../node_modules/.pnpm/@types+node@25.4.0/node_modules/@types/node/globals.typedarray.d.ts","../../../node_modules/.pnpm/@types+node@25.4.0/node_modules/@types/node/buffer.buffer.d.ts","../../../node_modules/.pnpm/@types+node@25.4.0/node_modules/@types/node/globals.d.ts","../../../node_modules/.pnpm/@types+node@25.4.0/node_modules/@types/node/web-globals/abortcontroller.d.ts","../../../node_modules/.pnpm/@types+node@25.4.0/node_modules/@types/node/web-globals/blob.d.ts","../../../node_modules/.pnpm/@types+node@25.4.0/node_modules/@types/node/web-globals/console.d.ts","../../../node_modules/.pnpm/@types+node@25.4.0/node_modules/@types/node/web-globals/crypto.d.ts","../../../node_modules/.pnpm/@types+node@25.4.0/node_modules/@types/node/web-globals/domexception.d.ts","../../../node_modules/.pnpm/@types+node@25.4.0/node_modules/@types/node/web-globals/encoding.d.ts","../../../node_modules/.pnpm/@types+node@25.4.0/node_modules/@types/node/web-globals/events.d.ts","../../../node_modules/.pnpm/undici-types@7.18.2/node_modules/undici-types/utility.d.ts","../../../node_modules/.pnpm/undici-types@7.18.2/node_modules/undici-types/header.d.ts","../../../node_modules/.pnpm/undici-types@7.18.2/node_modules/undici-types/readable.d.ts","../../../node_modules/.pnpm/undici-types@7.18.2/node_modules/undici-types/fetch.d.ts","../../../node_modules/.pnpm/undici-types@7.18.2/node_modules/undici-types/formdata.d.ts","../../../node_modules/.pnpm/undici-types@7.18.2/node_modules/undici-types/connector.d.ts","../../../node_modules/.pnpm/undici-types@7.18.2/node_modules/undici-types/client-stats.d.ts","../../../node_modules/.pnpm/undici-types@7.18.2/node_modules/undici-types/client.d.ts","../../../node_modules/.pnpm/undici-types@7.18.2/node_modules/undici-types/errors.d.ts","../../../node_modules/.pnpm/undici-types@7.18.2/node_modules/undici-types/dispatcher.d.ts","../../../node_modules/.pnpm/undici-types@7.18.2/node_modules/undici-types/global-dispatcher.d.ts","../../../node_modules/.pnpm/undici-types@7.18.2/node_modules/undici-types/global-origin.d.ts","../../../node_modules/.pnpm/undici-types@7.18.2/node_modules/undici-types/pool-stats.d.ts","../../../node_modules/.pnpm/undici-types@7.18.2/node_modules/undici-types/pool.d.ts","../../../node_modules/.pnpm/undici-types@7.18.2/node_modules/undici-types/handlers.d.ts","../../../node_modules/.pnpm/undici-types@7.18.2/node_modules/undici-types/balanced-pool.d.ts","../../../node_modules/.pnpm/undici-types@7.18.2/node_modules/undici-types/round-robin-pool.d.ts","../../../node_modules/.pnpm/undici-types@7.18.2/node_modules/undici-types/h2c-client.d.ts","../../../node_modules/.pnpm/undici-types@7.18.2/node_modules/undici-types/agent.d.ts","../../../node_modules/.pnpm/undici-types@7.18.2/node_modules/undici-types/mock-interceptor.d.ts","../../../node_modules/.pnpm/undici-types@7.18.2/node_modules/undici-types/mock-call-history.d.ts","../../../node_modules/.pnpm/undici-types@7.18.2/node_modules/undici-types/mock-agent.d.ts","../../../node_modules/.pnpm/undici-types@7.18.2/node_modules/undici-types/mock-client.d.ts","../../../node_modules/.pnpm/undici-types@7.18.2/node_modules/undici-types/mock-pool.d.ts","../../../node_modules/.pnpm/undici-types@7.18.2/node_modules/undici-types/snapshot-agent.d.ts","../../../node_modules/.pnpm/undici-types@7.18.2/node_modules/undici-types/mock-errors.d.ts","../../../node_modules/.pnpm/undici-types@7.18.2/node_modules/undici-types/proxy-agent.d.ts","../../../node_modules/.pnpm/undici-types@7.18.2/node_modules/undici-types/env-http-proxy-agent.d.ts","../../../node_modules/.pnpm/undici-types@7.18.2/node_modules/undici-types/retry-handler.d.ts","../../../node_modules/.pnpm/undici-types@7.18.2/node_modules/undici-types/retry-agent.d.ts","../../../node_modules/.pnpm/undici-types@7.18.2/node_modules/undici-types/api.d.ts","../../../node_modules/.pnpm/undici-types@7.18.2/node_modules/undici-types/cache-interceptor.d.ts","../../../node_modules/.pnpm/undici-types@7.18.2/node_modules/undici-types/interceptors.d.ts","../../../node_modules/.pnpm/undici-types@7.18.2/node_modules/undici-types/util.d.ts","../../../node_modules/.pnpm/undici-types@7.18.2/node_modules/undici-types/cookies.d.ts","../../../node_modules/.pnpm/undici-types@7.18.2/node_modules/undici-types/patch.d.ts","../../../node_modules/.pnpm/undici-types@7.18.2/node_modules/undici-types/websocket.d.ts","../../../node_modules/.pnpm/undici-types@7.18.2/node_modules/undici-types/eventsource.d.ts","../../../node_modules/.pnpm/undici-types@7.18.2/node_modules/undici-types/diagnostics-channel.d.ts","../../../node_modules/.pnpm/undici-types@7.18.2/node_modules/undici-types/content-type.d.ts","../../../node_modules/.pnpm/undici-types@7.18.2/node_modules/undici-types/cache.d.ts","../../../node_modules/.pnpm/undici-types@7.18.2/node_modules/undici-types/index.d.ts","../../../node_modules/.pnpm/@types+node@25.4.0/node_modules/@types/node/web-globals/fetch.d.ts","../../../node_modules/.pnpm/@types+node@25.4.0/node_modules/@types/node/web-globals/importmeta.d.ts","../../../node_modules/.pnpm/@types+node@25.4.0/node_modules/@types/node/web-globals/messaging.d.ts","../../../node_modules/.pnpm/@types+node@25.4.0/node_modules/@types/node/web-globals/navigator.d.ts","../../../node_modules/.pnpm/@types+node@25.4.0/node_modules/@types/node/web-globals/performance.d.ts","../../../node_modules/.pnpm/@types+node@25.4.0/node_modules/@types/node/web-globals/storage.d.ts","../../../node_modules/.pnpm/@types+node@25.4.0/node_modules/@types/node/web-globals/streams.d.ts","../../../node_modules/.pnpm/@types+node@25.4.0/node_modules/@types/node/web-globals/timers.d.ts","../../../node_modules/.pnpm/@types+node@25.4.0/node_modules/@types/node/web-globals/url.d.ts","../../../node_modules/.pnpm/@types+node@25.4.0/node_modules/@types/node/assert.d.ts","../../../node_modules/.pnpm/@types+node@25.4.0/node_modules/@types/node/assert/strict.d.ts","../../../node_modules/.pnpm/@types+node@25.4.0/node_modules/@types/node/async_hooks.d.ts","../../../node_modules/.pnpm/@types+node@25.4.0/node_modules/@types/node/buffer.d.ts","../../../node_modules/.pnpm/@types+node@25.4.0/node_modules/@types/node/child_process.d.ts","../../../node_modules/.pnpm/@types+node@25.4.0/node_modules/@types/node/cluster.d.ts","../../../node_modules/.pnpm/@types+node@25.4.0/node_modules/@types/node/console.d.ts","../../../node_modules/.pnpm/@types+node@25.4.0/node_modules/@types/node/constants.d.ts","../../../node_modules/.pnpm/@types+node@25.4.0/node_modules/@types/node/crypto.d.ts","../../../node_modules/.pnpm/@types+node@25.4.0/node_modules/@types/node/dgram.d.ts","../../../node_modules/.pnpm/@types+node@25.4.0/node_modules/@types/node/diagnostics_channel.d.ts","../../../node_modules/.pnpm/@types+node@25.4.0/node_modules/@types/node/dns.d.ts","../../../node_modules/.pnpm/@types+node@25.4.0/node_modules/@types/node/dns/promises.d.ts","../../../node_modules/.pnpm/@types+node@25.4.0/node_modules/@types/node/domain.d.ts","../../../node_modules/.pnpm/@types+node@25.4.0/node_modules/@types/node/events.d.ts","../../../node_modules/.pnpm/@types+node@25.4.0/node_modules/@types/node/fs.d.ts","../../../node_modules/.pnpm/@types+node@25.4.0/node_modules/@types/node/fs/promises.d.ts","../../../node_modules/.pnpm/@types+node@25.4.0/node_modules/@types/node/http.d.ts","../../../node_modules/.pnpm/@types+node@25.4.0/node_modules/@types/node/http2.d.ts","../../../node_modules/.pnpm/@types+node@25.4.0/node_modules/@types/node/https.d.ts","../../../node_modules/.pnpm/@types+node@25.4.0/node_modules/@types/node/inspector.d.ts","../../../node_modules/.pnpm/@types+node@25.4.0/node_modules/@types/node/inspector.generated.d.ts","../../../node_modules/.pnpm/@types+node@25.4.0/node_modules/@types/node/inspector/promises.d.ts","../../../node_modules/.pnpm/@types+node@25.4.0/node_modules/@types/node/module.d.ts","../../../node_modules/.pnpm/@types+node@25.4.0/node_modules/@types/node/net.d.ts","../../../node_modules/.pnpm/@types+node@25.4.0/node_modules/@types/node/os.d.ts","../../../node_modules/.pnpm/@types+node@25.4.0/node_modules/@types/node/path.d.ts","../../../node_modules/.pnpm/@types+node@25.4.0/node_modules/@types/node/path/posix.d.ts","../../../node_modules/.pnpm/@types+node@25.4.0/node_modules/@types/node/path/win32.d.ts","../../../node_modules/.pnpm/@types+node@25.4.0/node_modules/@types/node/perf_hooks.d.ts","../../../node_modules/.pnpm/@types+node@25.4.0/node_modules/@types/node/process.d.ts","../../../node_modules/.pnpm/@types+node@25.4.0/node_modules/@types/node/punycode.d.ts","../../../node_modules/.pnpm/@types+node@25.4.0/node_modules/@types/node/querystring.d.ts","../../../node_modules/.pnpm/@types+node@25.4.0/node_modules/@types/node/quic.d.ts","../../../node_modules/.pnpm/@types+node@25.4.0/node_modules/@types/node/readline.d.ts","../../../node_modules/.pnpm/@types+node@25.4.0/node_modules/@types/node/readline/promises.d.ts","../../../node_modules/.pnpm/@types+node@25.4.0/node_modules/@types/node/repl.d.ts","../../../node_modules/.pnpm/@types+node@25.4.0/node_modules/@types/node/sea.d.ts","../../../node_modules/.pnpm/@types+node@25.4.0/node_modules/@types/node/sqlite.d.ts","../../../node_modules/.pnpm/@types+node@25.4.0/node_modules/@types/node/stream.d.ts","../../../node_modules/.pnpm/@types+node@25.4.0/node_modules/@types/node/stream/consumers.d.ts","../../../node_modules/.pnpm/@types+node@25.4.0/node_modules/@types/node/stream/promises.d.ts","../../../node_modules/.pnpm/@types+node@25.4.0/node_modules/@types/node/stream/web.d.ts","../../../node_modules/.pnpm/@types+node@25.4.0/node_modules/@types/node/string_decoder.d.ts","../../../node_modules/.pnpm/@types+node@25.4.0/node_modules/@types/node/test.d.ts","../../../node_modules/.pnpm/@types+node@25.4.0/node_modules/@types/node/test/reporters.d.ts","../../../node_modules/.pnpm/@types+node@25.4.0/node_modules/@types/node/timers.d.ts","../../../node_modules/.pnpm/@types+node@25.4.0/node_modules/@types/node/timers/promises.d.ts","../../../node_modules/.pnpm/@types+node@25.4.0/node_modules/@types/node/tls.d.ts","../../../node_modules/.pnpm/@types+node@25.4.0/node_modules/@types/node/trace_events.d.ts","../../../node_modules/.pnpm/@types+node@25.4.0/node_modules/@types/node/tty.d.ts","../../../node_modules/.pnpm/@types+node@25.4.0/node_modules/@types/node/url.d.ts","../../../node_modules/.pnpm/@types+node@25.4.0/node_modules/@types/node/util.d.ts","../../../node_modules/.pnpm/@types+node@25.4.0/node_modules/@types/node/util/types.d.ts","../../../node_modules/.pnpm/@types+node@25.4.0/node_modules/@types/node/v8.d.ts","../../../node_modules/.pnpm/@types+node@25.4.0/node_modules/@types/node/vm.d.ts","../../../node_modules/.pnpm/@types+node@25.4.0/node_modules/@types/node/wasi.d.ts","../../../node_modules/.pnpm/@types+node@25.4.0/node_modules/@types/node/worker_threads.d.ts","../../../node_modules/.pnpm/@types+node@25.4.0/node_modules/@types/node/zlib.d.ts","../../../node_modules/.pnpm/@types+node@25.4.0/node_modules/@types/node/index.d.ts","../../../node_modules/.pnpm/@vitest+pretty-format@4.0.18/node_modules/@vitest/pretty-format/dist/index.d.ts","../../../node_modules/.pnpm/@vitest+utils@4.0.18/node_modules/@vitest/utils/dist/display.d.ts","../../../node_modules/.pnpm/@vitest+utils@4.0.18/node_modules/@vitest/utils/dist/types.d.ts","../../../node_modules/.pnpm/@vitest+utils@4.0.18/node_modules/@vitest/utils/dist/helpers.d.ts","../../../node_modules/.pnpm/@vitest+utils@4.0.18/node_modules/@vitest/utils/dist/timers.d.ts","../../../node_modules/.pnpm/@vitest+utils@4.0.18/node_modules/@vitest/utils/dist/index.d.ts","../../../node_modules/.pnpm/@vitest+runner@4.0.18/node_modules/@vitest/runner/dist/tasks.d-C7UxawJ9.d.ts","../../../node_modules/.pnpm/@vitest+utils@4.0.18/node_modules/@vitest/utils/dist/types.d-BCElaP-c.d.ts","../../../node_modules/.pnpm/@vitest+utils@4.0.18/node_modules/@vitest/utils/dist/diff.d.ts","../../../node_modules/.pnpm/@vitest+runner@4.0.18/node_modules/@vitest/runner/dist/types.d.ts","../../../node_modules/.pnpm/@vitest+runner@4.0.18/node_modules/@vitest/runner/dist/index.d.ts","../../../node_modules/.pnpm/vitest@4.0.18_@opentelemetry+api@1.9.0_@types+node@25.4.0_jiti@2.6.1_lightningcss@1.30._9f363de115829bbe425049fc0357920d/node_modules/vitest/dist/chunks/traces.d.402V_yFI.d.ts","../../../node_modules/.pnpm/vite@7.3.1_@types+node@25.4.0_jiti@2.6.1_lightningcss@1.30.2_terser@5.44.1_tsx@4.21.0_yaml@2.8.2/node_modules/vite/types/hmrPayload.d.ts","../../../node_modules/.pnpm/vite@7.3.1_@types+node@25.4.0_jiti@2.6.1_lightningcss@1.30.2_terser@5.44.1_tsx@4.21.0_yaml@2.8.2/node_modules/vite/dist/node/chunks/moduleRunnerTransport.d.ts","../../../node_modules/.pnpm/vite@7.3.1_@types+node@25.4.0_jiti@2.6.1_lightningcss@1.30.2_terser@5.44.1_tsx@4.21.0_yaml@2.8.2/node_modules/vite/types/customEvent.d.ts","../../../node_modules/.pnpm/vite@7.3.1_@types+node@25.4.0_jiti@2.6.1_lightningcss@1.30.2_terser@5.44.1_tsx@4.21.0_yaml@2.8.2/node_modules/vite/types/hot.d.ts","../../../node_modules/.pnpm/vite@7.3.1_@types+node@25.4.0_jiti@2.6.1_lightningcss@1.30.2_terser@5.44.1_tsx@4.21.0_yaml@2.8.2/node_modules/vite/dist/node/module-runner.d.ts","../../../node_modules/.pnpm/@vitest+snapshot@4.0.18/node_modules/@vitest/snapshot/dist/environment.d-DHdQ1Csl.d.ts","../../../node_modules/.pnpm/@vitest+snapshot@4.0.18/node_modules/@vitest/snapshot/dist/rawSnapshot.d-lFsMJFUd.d.ts","../../../node_modules/.pnpm/@vitest+snapshot@4.0.18/node_modules/@vitest/snapshot/dist/index.d.ts","../../../node_modules/.pnpm/vitest@4.0.18_@opentelemetry+api@1.9.0_@types+node@25.4.0_jiti@2.6.1_lightningcss@1.30._9f363de115829bbe425049fc0357920d/node_modules/vitest/dist/chunks/config.d.Cy95HiCx.d.ts","../../../node_modules/.pnpm/vitest@4.0.18_@opentelemetry+api@1.9.0_@types+node@25.4.0_jiti@2.6.1_lightningcss@1.30._9f363de115829bbe425049fc0357920d/node_modules/vitest/dist/chunks/environment.d.CrsxCzP1.d.ts","../../../node_modules/.pnpm/vitest@4.0.18_@opentelemetry+api@1.9.0_@types+node@25.4.0_jiti@2.6.1_lightningcss@1.30._9f363de115829bbe425049fc0357920d/node_modules/vitest/dist/chunks/rpc.d.RH3apGEf.d.ts","../../../node_modules/.pnpm/vitest@4.0.18_@opentelemetry+api@1.9.0_@types+node@25.4.0_jiti@2.6.1_lightningcss@1.30._9f363de115829bbe425049fc0357920d/node_modules/vitest/dist/chunks/worker.d.Dyxm8DEL.d.ts","../../../node_modules/.pnpm/vitest@4.0.18_@opentelemetry+api@1.9.0_@types+node@25.4.0_jiti@2.6.1_lightningcss@1.30._9f363de115829bbe425049fc0357920d/node_modules/vitest/dist/chunks/browser.d.ChKACdzH.d.ts","../../../node_modules/.pnpm/@vitest+spy@4.0.18/node_modules/@vitest/spy/dist/index.d.ts","../../../node_modules/.pnpm/tinyrainbow@3.0.3/node_modules/tinyrainbow/dist/index.d.ts","../../../node_modules/.pnpm/@standard-schema+spec@1.1.0/node_modules/@standard-schema/spec/dist/index.d.ts","../../../node_modules/.pnpm/@types+deep-eql@4.0.2/node_modules/@types/deep-eql/index.d.ts","../../../node_modules/.pnpm/assertion-error@2.0.1/node_modules/assertion-error/index.d.ts","../../../node_modules/.pnpm/@types+chai@5.2.3/node_modules/@types/chai/index.d.ts","../../../node_modules/.pnpm/@vitest+expect@4.0.18/node_modules/@vitest/expect/dist/index.d.ts","../../../node_modules/.pnpm/@vitest+runner@4.0.18/node_modules/@vitest/runner/dist/utils.d.ts","../../../node_modules/.pnpm/tinybench@2.9.0/node_modules/tinybench/dist/index.d.ts","../../../node_modules/.pnpm/vitest@4.0.18_@opentelemetry+api@1.9.0_@types+node@25.4.0_jiti@2.6.1_lightningcss@1.30._9f363de115829bbe425049fc0357920d/node_modules/vitest/dist/chunks/benchmark.d.DAaHLpsq.d.ts","../../../node_modules/.pnpm/vitest@4.0.18_@opentelemetry+api@1.9.0_@types+node@25.4.0_jiti@2.6.1_lightningcss@1.30._9f363de115829bbe425049fc0357920d/node_modules/vitest/dist/chunks/global.d.B15mdLcR.d.ts","../../../node_modules/.pnpm/vitest@4.0.18_@opentelemetry+api@1.9.0_@types+node@25.4.0_jiti@2.6.1_lightningcss@1.30._9f363de115829bbe425049fc0357920d/node_modules/vitest/dist/chunks/suite.d.BJWk38HB.d.ts","../../../node_modules/.pnpm/vitest@4.0.18_@opentelemetry+api@1.9.0_@types+node@25.4.0_jiti@2.6.1_lightningcss@1.30._9f363de115829bbe425049fc0357920d/node_modules/vitest/dist/chunks/evaluatedModules.d.BxJ5omdx.d.ts","../../../node_modules/.pnpm/expect-type@1.3.0/node_modules/expect-type/dist/utils.d.ts","../../../node_modules/.pnpm/expect-type@1.3.0/node_modules/expect-type/dist/overloads.d.ts","../../../node_modules/.pnpm/expect-type@1.3.0/node_modules/expect-type/dist/branding.d.ts","../../../node_modules/.pnpm/expect-type@1.3.0/node_modules/expect-type/dist/messages.d.ts","../../../node_modules/.pnpm/expect-type@1.3.0/node_modules/expect-type/dist/index.d.ts","../../../node_modules/.pnpm/vitest@4.0.18_@opentelemetry+api@1.9.0_@types+node@25.4.0_jiti@2.6.1_lightningcss@1.30._9f363de115829bbe425049fc0357920d/node_modules/vitest/dist/index.d.ts","../../../node_modules/.pnpm/vitest@4.0.18_@opentelemetry+api@1.9.0_@types+node@25.4.0_jiti@2.6.1_lightningcss@1.30._9f363de115829bbe425049fc0357920d/node_modules/vitest/globals.d.ts"],"fileIdsList":[[69,70,78,141,149,153,156,158,159,160,172],[69,70,72,78,141,149,153,156,158,159,160,172],[70,71,72,73,74,78,141,149,153,156,158,159,160,172],[78,141,149,153,156,158,159,160,172],[70,78,141,149,153,156,158,159,160,172],[69,78,141,149,153,156,158,159,160,172],[78,141,149,153,156,158,159,160,172,226,227],[78,138,139,141,149,153,156,158,159,160,172],[78,140,141,149,153,156,158,159,160,172],[141,149,153,156,158,159,160,172],[78,141,149,153,156,158,159,160,172,180],[78,141,142,147,149,152,153,156,158,159,160,162,172,177,189],[78,141,142,143,149,152,153,156,158,159,160,172],[78,141,144,149,153,156,158,159,160,172,190],[78,141,145,146,149,153,156,158,159,160,163,172],[78,141,146,149,153,156,158,159,160,172,177,186],[78,141,147,149,152,153,156,158,159,160,162,172],[78,140,141,148,149,153,156,158,159,160,172],[78,141,149,150,153,156,158,159,160,172],[78,141,149,151,152,153,156,158,159,160,172],[78,140,141,149,152,153,156,158,159,160,172],[78,141,149,152,153,154,156,158,159,160,172,177,189],[78,141,149,152,153,154,156,158,159,160,172,177,180],[78,128,141,149,152,153,155,156,158,159,160,162,172,177,189],[78,141,149,152,153,155,156,158,159,160,162,172,177,186,189],[78,141,149,153,155,156,157,158,159,160,172,177,186,189],[76,77,78,79,80,81,82,83,84,85,86,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143,144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159,160,161,162,163,164,165,166,167,168,169,170,171,172,173,174,175,176,177,178,179,180,181,182,183,184,185,186,187,188,189,190,191,192,193,194,195,196],[78,141,149,152,153,156,158,159,160,172],[78,141,149,153,156,158,160,172],[78,141,149,153,156,158,159,160,161,172,189],[78,141,149,152,153,156,158,159,160,162,172,177],[78,141,149,153,156,158,159,160,163,172],[78,141,149,153,156,158,159,160,164,172],[78,141,149,152,153,156,158,159,160,167,172],[78,138,139,140,141,142,143,144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159,160,161,162,163,164,165,166,167,168,169,170,171,172,173,174,175,176,177,178,179,180,181,182,183,184,185,186,187,188,189,190,191,192,193,194,195,196],[78,141,149,153,156,158,159,160,169,172],[78,141,149,153,156,158,159,160,170,172],[78,141,146,149,153,156,158,159,160,162,172,180],[78,141,149,152,153,156,158,159,160,172,173],[78,141,149,153,156,158,159,160,172,174,190,193],[78,141,149,152,153,156,158,159,160,172,177,179,180],[78,141,149,153,156,158,159,160,172,178,180],[78,141,149,153,156,158,159,160,172,180,190],[78,141,149,153,156,158,159,160,172,181],[78,138,141,149,153,156,158,159,160,172,177,183,189],[78,141,149,153,156,158,159,160,172,177,182],[78,141,149,152,153,156,158,159,160,172,184,185],[78,141,149,153,156,158,159,160,172,184,185],[78,141,146,149,153,156,158,159,160,162,172,177,186],[78,141,149,153,156,158,159,160,172,187],[78,141,149,153,156,158,159,160,162,172,188],[78,141,149,153,155,156,158,159,160,170,172,189],[78,141,149,153,156,158,159,160,172,190,191],[78,141,146,149,153,156,158,159,160,172,191],[78,141,149,153,156,158,159,160,172,177,192],[78,141,149,153,156,158,159,160,161,172,193],[78,141,149,153,156,158,159,160,172,194],[78,141,144,149,153,156,158,159,160,172],[78,141,146,149,153,156,158,159,160,172],[78,141,149,153,156,158,159,160,172,190],[78,128,141,149,153,156,158,159,160,172],[78,141,149,153,156,158,159,160,172,189],[78,141,149,153,156,158,159,160,172,195],[78,141,149,153,156,158,159,160,167,172],[78,141,149,153,156,158,159,160,172,185],[78,128,141,149,152,153,154,156,158,159,160,167,172,177,180,189,192,193,195],[78,141,149,153,156,158,159,160,172,177,196],[78,141,149,153,156,158,159,160,172,199,203,206,208,223,224,225,228,233],[78,141,149,153,156,158,159,160,172,203,204,206,207],[78,141,149,153,156,158,159,160,172,203],[78,141,149,153,156,158,159,160,172,203,204,206],[78,141,149,153,156,158,159,160,172,203,204],[78,141,149,153,156,158,159,160,172,198,215,216],[78,141,149,153,156,158,159,160,172,198,215],[78,141,149,153,156,158,159,160,172,198,205],[78,141,149,153,156,158,159,160,172,198],[78,141,149,153,156,158,159,160,172,200],[78,141,149,153,156,158,159,160,172,198,199,200,201,202],[78,141,149,153,156,158,159,160,172,236,237],[78,141,149,153,156,158,159,160,172,236,237,238,239],[78,141,149,153,156,158,159,160,172,236,238],[78,141,149,153,156,158,159,160,172,236],[66,67,68,78,141,149,153,156,158,159,160,172],[66,78,141,149,153,156,158,159,160,172],[67,78,141,149,153,156,158,159,160,172],[78,93,96,99,100,141,149,153,156,158,159,160,172,189],[78,96,141,149,153,156,158,159,160,172,177,189],[78,96,100,141,149,153,156,158,159,160,172,189],[78,141,149,153,156,158,159,160,172,177],[78,90,141,149,153,156,158,159,160,172],[78,94,141,149,153,156,158,159,160,172],[78,92,93,96,141,149,153,156,158,159,160,172,189],[78,141,149,153,156,158,159,160,162,172,186],[78,141,149,153,156,158,159,160,172,197],[78,90,141,149,153,156,158,159,160,172,197],[78,92,96,141,149,153,156,158,159,160,162,172,189],[78,87,88,89,91,95,141,149,152,153,156,158,159,160,172,177,189],[78,96,105,113,141,149,153,156,158,159,160,172],[78,88,94,141,149,153,156,158,159,160,172],[78,96,122,123,141,149,153,156,158,159,160,172],[78,88,91,96,141,149,153,156,158,159,160,172,180,189,197],[78,96,141,149,153,156,158,159,160,172],[78,92,96,141,149,153,156,158,159,160,172,189],[78,87,141,149,153,156,158,159,160,172],[78,90,91,92,94,95,96,97,98,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,123,124,125,126,127,141,149,153,156,158,159,160,172],[78,96,115,118,141,149,153,156,158,159,160,172],[78,96,105,106,107,141,149,153,156,158,159,160,172],[78,94,96,106,108,141,149,153,156,158,159,160,172],[78,95,141,149,153,156,158,159,160,172],[78,88,90,96,141,149,153,156,158,159,160,172],[78,96,100,106,108,141,149,153,156,158,159,160,172],[78,100,141,149,153,156,158,159,160,172],[78,94,96,99,141,149,153,156,158,159,160,172,189],[78,88,92,96,105,141,149,153,156,158,159,160,172],[78,96,115,141,149,153,156,158,159,160,172],[78,108,141,149,153,156,158,159,160,172],[78,90,96,122,141,149,153,156,158,159,160,172,180,195,197],[78,141,149,153,156,158,159,160,172,210],[78,141,149,153,156,158,159,160,172,210,211,212,213],[78,141,149,153,156,158,159,160,172,212],[78,141,149,153,156,158,159,160,172,208,230,231,233],[78,141,149,153,156,158,159,160,172,208,209,221,233],[78,141,149,153,156,158,159,160,172,198,206,208,217,233],[78,141,149,153,156,158,159,160,172,214],[78,141,149,153,156,158,159,160,172,198,208,217,220,229,232,233],[78,141,149,153,156,158,159,160,172,208,209,214,217,233],[78,141,149,153,156,158,159,160,172,208,230,231,232,233],[78,141,149,153,156,158,159,160,172,208,214,218,219,220,233],[78,141,149,153,156,158,159,160,172,198,203,206,208,209,214,217,218,219,220,221,222,223,229,230,231,232,233,234,235,240],[78,141,149,153,156,158,159,160,172,241]],"fileInfos":[{"version":"c430d44666289dae81f30fa7b2edebf186ecc91a2d4c71266ea6ae76388792e1","affectsGlobalScope":true,"impliedFormat":1},{"version":"45b7ab580deca34ae9729e97c13cfd999df04416a79116c3bfb483804f85ded4","impliedFormat":1},{"version":"3facaf05f0c5fc569c5649dd359892c98a85557e3e0c847964caeb67076f4d75","impliedFormat":1},{"version":"e44bb8bbac7f10ecc786703fe0a6a4b952189f908707980ba8f3c8975a760962","impliedFormat":1},{"version":"5e1c4c362065a6b95ff952c0eab010f04dcd2c3494e813b493ecfd4fcb9fc0d8","impliedFormat":1},{"version":"68d73b4a11549f9c0b7d352d10e91e5dca8faa3322bfb77b661839c42b1ddec7","impliedFormat":1},{"version":"5efce4fc3c29ea84e8928f97adec086e3dc876365e0982cc8479a07954a3efd4","impliedFormat":1},{"version":"feecb1be483ed332fad555aff858affd90a48ab19ba7272ee084704eb7167569","impliedFormat":1},{"version":"ee7bad0c15b58988daa84371e0b89d313b762ab83cb5b31b8a2d1162e8eb41c2","impliedFormat":1},{"version":"080941d9f9ff9307f7e27a83bcd888b7c8270716c39af943532438932ec1d0b9","affectsGlobalScope":true,"impliedFormat":1},{"version":"2e80ee7a49e8ac312cc11b77f1475804bee36b3b2bc896bead8b6e1266befb43","affectsGlobalScope":true,"impliedFormat":1},{"version":"d7a3c8b952931daebdfc7a2897c53c0a1c73624593fa070e46bd537e64dcd20a","affectsGlobalScope":true,"impliedFormat":1},{"version":"80e18897e5884b6723488d4f5652167e7bb5024f946743134ecc4aa4ee731f89","affectsGlobalScope":true,"impliedFormat":1},{"version":"cd034f499c6cdca722b60c04b5b1b78e058487a7085a8e0d6fb50809947ee573","affectsGlobalScope":true,"impliedFormat":1},{"version":"c57796738e7f83dbc4b8e65132f11a377649c00dd3eee333f672b8f0a6bea671","affectsGlobalScope":true,"impliedFormat":1},{"version":"dc2df20b1bcdc8c2d34af4926e2c3ab15ffe1160a63e58b7e09833f616efff44","affectsGlobalScope":true,"impliedFormat":1},{"version":"515d0b7b9bea2e31ea4ec968e9edd2c39d3eebf4a2d5cbd04e88639819ae3b71","affectsGlobalScope":true,"impliedFormat":1},{"version":"0559b1f683ac7505ae451f9a96ce4c3c92bdc71411651ca6ddb0e88baaaad6a3","affectsGlobalScope":true,"impliedFormat":1},{"version":"0dc1e7ceda9b8b9b455c3a2d67b0412feab00bd2f66656cd8850e8831b08b537","affectsGlobalScope":true,"impliedFormat":1},{"version":"ce691fb9e5c64efb9547083e4a34091bcbe5bdb41027e310ebba8f7d96a98671","affectsGlobalScope":true,"impliedFormat":1},{"version":"8d697a2a929a5fcb38b7a65594020fcef05ec1630804a33748829c5ff53640d0","affectsGlobalScope":true,"impliedFormat":1},{"version":"4ff2a353abf8a80ee399af572debb8faab2d33ad38c4b4474cff7f26e7653b8d","affectsGlobalScope":true,"impliedFormat":1},{"version":"fb0f136d372979348d59b3f5020b4cdb81b5504192b1cacff5d1fbba29378aa1","affectsGlobalScope":true,"impliedFormat":1},{"version":"d15bea3d62cbbdb9797079416b8ac375ae99162a7fba5de2c6c505446486ac0a","affectsGlobalScope":true,"impliedFormat":1},{"version":"68d18b664c9d32a7336a70235958b8997ebc1c3b8505f4f1ae2b7e7753b87618","affectsGlobalScope":true,"impliedFormat":1},{"version":"eb3d66c8327153d8fa7dd03f9c58d351107fe824c79e9b56b462935176cdf12a","affectsGlobalScope":true,"impliedFormat":1},{"version":"38f0219c9e23c915ef9790ab1d680440d95419ad264816fa15009a8851e79119","affectsGlobalScope":true,"impliedFormat":1},{"version":"69ab18c3b76cd9b1be3d188eaf8bba06112ebbe2f47f6c322b5105a6fbc45a2e","affectsGlobalScope":true,"impliedFormat":1},{"version":"a680117f487a4d2f30ea46f1b4b7f58bef1480456e18ba53ee85c2746eeca012","affectsGlobalScope":true,"impliedFormat":1},{"version":"2f11ff796926e0832f9ae148008138ad583bd181899ab7dd768a2666700b1893","affectsGlobalScope":true,"impliedFormat":1},{"version":"4de680d5bb41c17f7f68e0419412ca23c98d5749dcaaea1896172f06435891fc","affectsGlobalScope":true,"impliedFormat":1},{"version":"954296b30da6d508a104a3a0b5d96b76495c709785c1d11610908e63481ee667","affectsGlobalScope":true,"impliedFormat":1},{"version":"ac9538681b19688c8eae65811b329d3744af679e0bdfa5d842d0e32524c73e1c","affectsGlobalScope":true,"impliedFormat":1},{"version":"0a969edff4bd52585473d24995c5ef223f6652d6ef46193309b3921d65dd4376","affectsGlobalScope":true,"impliedFormat":1},{"version":"9e9fbd7030c440b33d021da145d3232984c8bb7916f277e8ffd3dc2e3eae2bdb","affectsGlobalScope":true,"impliedFormat":1},{"version":"811ec78f7fefcabbda4bfa93b3eb67d9ae166ef95f9bff989d964061cbf81a0c","affectsGlobalScope":true,"impliedFormat":1},{"version":"717937616a17072082152a2ef351cb51f98802fb4b2fdabd32399843875974ca","affectsGlobalScope":true,"impliedFormat":1},{"version":"d7e7d9b7b50e5f22c915b525acc5a49a7a6584cf8f62d0569e557c5cfc4b2ac2","affectsGlobalScope":true,"impliedFormat":1},{"version":"71c37f4c9543f31dfced6c7840e068c5a5aacb7b89111a4364b1d5276b852557","affectsGlobalScope":true,"impliedFormat":1},{"version":"576711e016cf4f1804676043e6a0a5414252560eb57de9faceee34d79798c850","affectsGlobalScope":true,"impliedFormat":1},{"version":"89c1b1281ba7b8a96efc676b11b264de7a8374c5ea1e6617f11880a13fc56dc6","affectsGlobalScope":true,"impliedFormat":1},{"version":"74f7fa2d027d5b33eb0471c8e82a6c87216223181ec31247c357a3e8e2fddc5b","affectsGlobalScope":true,"impliedFormat":1},{"version":"d6d7ae4d1f1f3772e2a3cde568ed08991a8ae34a080ff1151af28b7f798e22ca","affectsGlobalScope":true,"impliedFormat":1},{"version":"063600664504610fe3e99b717a1223f8b1900087fab0b4cad1496a114744f8df","affectsGlobalScope":true,"impliedFormat":1},{"version":"934019d7e3c81950f9a8426d093458b65d5aff2c7c1511233c0fd5b941e608ab","affectsGlobalScope":true,"impliedFormat":1},{"version":"52ada8e0b6e0482b728070b7639ee42e83a9b1c22d205992756fe020fd9f4a47","affectsGlobalScope":true,"impliedFormat":1},{"version":"3bdefe1bfd4d6dee0e26f928f93ccc128f1b64d5d501ff4a8cf3c6371200e5e6","affectsGlobalScope":true,"impliedFormat":1},{"version":"59fb2c069260b4ba00b5643b907ef5d5341b167e7d1dbf58dfd895658bda2867","affectsGlobalScope":true,"impliedFormat":1},{"version":"639e512c0dfc3fad96a84caad71b8834d66329a1f28dc95e3946c9b58176c73a","affectsGlobalScope":true,"impliedFormat":1},{"version":"368af93f74c9c932edd84c58883e736c9e3d53cec1fe24c0b0ff451f529ceab1","affectsGlobalScope":true,"impliedFormat":1},{"version":"af3dd424cf267428f30ccfc376f47a2c0114546b55c44d8c0f1d57d841e28d74","affectsGlobalScope":true,"impliedFormat":1},{"version":"995c005ab91a498455ea8dfb63aa9f83fa2ea793c3d8aa344be4a1678d06d399","affectsGlobalScope":true,"impliedFormat":1},{"version":"959d36cddf5e7d572a65045b876f2956c973a586da58e5d26cde519184fd9b8a","affectsGlobalScope":true,"impliedFormat":1},{"version":"965f36eae237dd74e6cca203a43e9ca801ce38824ead814728a2807b1910117d","affectsGlobalScope":true,"impliedFormat":1},{"version":"3925a6c820dcb1a06506c90b1577db1fdbf7705d65b62b99dce4be75c637e26b","affectsGlobalScope":true,"impliedFormat":1},{"version":"0a3d63ef2b853447ec4f749d3f368ce642264246e02911fcb1590d8c161b8005","affectsGlobalScope":true,"impliedFormat":1},{"version":"8cdf8847677ac7d20486e54dd3fcf09eda95812ac8ace44b4418da1bbbab6eb8","affectsGlobalScope":true,"impliedFormat":1},{"version":"8444af78980e3b20b49324f4a16ba35024fef3ee069a0eb67616ea6ca821c47a","affectsGlobalScope":true,"impliedFormat":1},{"version":"3287d9d085fbd618c3971944b65b4be57859f5415f495b33a6adc994edd2f004","affectsGlobalScope":true,"impliedFormat":1},{"version":"b4b67b1a91182421f5df999988c690f14d813b9850b40acd06ed44691f6727ad","affectsGlobalScope":true,"impliedFormat":1},{"version":"51ad4c928303041605b4d7ae32e0c1ee387d43a24cd6f1ebf4a2699e1076d4fa","affectsGlobalScope":true,"impliedFormat":1},{"version":"196cb558a13d4533a5163286f30b0509ce0210e4b316c56c38d4c0fd2fb38405","affectsGlobalScope":true,"impliedFormat":1},{"version":"8e7f8264d0fb4c5339605a15daadb037bf238c10b654bb3eee14208f860a32ea","affectsGlobalScope":true,"impliedFormat":1},{"version":"782dec38049b92d4e85c1585fbea5474a219c6984a35b004963b00beb1aab538","affectsGlobalScope":true,"impliedFormat":1},{"version":"3cbad9a1ba4453443026ed38e4b8be018abb26565fa7c944376463ad9df07c41","impliedFormat":1},{"version":"ebf6e19cb84d78da20d022a95f05e8aef12e56f816a1ee12835a4da40d7b14cf","impliedFormat":1},{"version":"589357c2f88f1188a0dfc48c4c4cf4d22fac9f654805df5f2789a01b5616b74f","impliedFormat":1},{"version":"6abe62ec5b9b6a747c1a7687d58ff179cdfb61adee717b6e4882120f7da4399f","impliedFormat":1},{"version":"5c1301f550be26133f4fd34eadf38815db096ecaf9b75948b444a063097f496d","impliedFormat":1},{"version":"0db344d1aaf274e3c3a8ce317f43375e79933286a3275ba83f3abe0c4b098b8c","signature":"2421ccaf3d8e3e8746d874241d7cd464e3555effdc31eafbfc1a0373f3cd57e5","impliedFormat":99},{"version":"a4431be9a918edd87c2d3fe4041cecf29a0c42881560e0ff0a63118e6bc45445","signature":"aba53b5987ec3c2e65e951461eac68995268991f57fde8149a6313db11b8064e","impliedFormat":99},{"version":"04590593b4dfa6e8af32712e6c64681b3a075ad4979c18a4ac4a38a6d9259ab0","signature":"295489981f12851f4146c951dff875bb6b025a8e76abdc500bf553ede7ad147b","impliedFormat":99},{"version":"57f549f481731888e117cc6097b55be6c054dd06e3aa6d9bcc9b1bc119e780bf","signature":"d6bc972b179ee8ea140607176245819804973a5eba9d22993c681f31ca6c97f1","impliedFormat":99},{"version":"f908e58022b309ca2b888b30ae38dae0a8d8bd795e61a4e1309b1fb619342ab1","signature":"3fa88fa390e72293939cc01e238742f469f09fa828ef1fa9a21dd16c4b063243","impliedFormat":99},{"version":"b07ef3ebb831789bd8ac2b9860b832f3995e3f6809bbfd92e87ef5c589d28287","signature":"bb30555c094938b6d823fdb4d5afb181dfa035e0d39c4a6cc4492a1cc3fe42cc","impliedFormat":99},{"version":"d153a11543fd884b596587ccd97aebbeed950b26933ee000f94009f1ab142848","affectsGlobalScope":true,"impliedFormat":1},{"version":"0ccdaa19852d25ecd84eec365c3bfa16e7859cadecf6e9ca6d0dbbbee439743f","affectsGlobalScope":true,"impliedFormat":1},{"version":"438b41419b1df9f1fbe33b5e1b18f5853432be205991d1b19f5b7f351675541e","affectsGlobalScope":true,"impliedFormat":1},{"version":"096116f8fedc1765d5bd6ef360c257b4a9048e5415054b3bf3c41b07f8951b0b","affectsGlobalScope":true,"impliedFormat":1},{"version":"e5e01375c9e124a83b52ee4b3244ed1a4d214a6cfb54ac73e164a823a4a7860a","affectsGlobalScope":true,"impliedFormat":1},{"version":"f90ae2bbce1505e67f2f6502392e318f5714bae82d2d969185c4a6cecc8af2fc","affectsGlobalScope":true,"impliedFormat":1},{"version":"4b58e207b93a8f1c88bbf2a95ddc686ac83962b13830fe8ad3f404ffc7051fb4","affectsGlobalScope":true,"impliedFormat":1},{"version":"1fefabcb2b06736a66d2904074d56268753654805e829989a46a0161cd8412c5","affectsGlobalScope":true,"impliedFormat":1},{"version":"9798340ffb0d067d69b1ae5b32faa17ab31b82466a3fc00d8f2f2df0c8554aaa","affectsGlobalScope":true,"impliedFormat":1},{"version":"c18a99f01eb788d849ad032b31cafd49de0b19e083fe775370834c5675d7df8e","affectsGlobalScope":true,"impliedFormat":1},{"version":"5247874c2a23b9a62d178ae84f2db6a1d54e6c9a2e7e057e178cc5eea13757fc","affectsGlobalScope":true,"impliedFormat":1},{"version":"cdcf9ea426ad970f96ac930cd176d5c69c6c24eebd9fc580e1572d6c6a88f62c","impliedFormat":1},{"version":"23cd712e2ce083d68afe69224587438e5914b457b8acf87073c22494d706a3d0","impliedFormat":1},{"version":"156a859e21ef3244d13afeeba4e49760a6afa035c149dda52f0c45ea8903b338","impliedFormat":1},{"version":"10ec5e82144dfac6f04fa5d1d6c11763b3e4dbbac6d99101427219ab3e2ae887","impliedFormat":1},{"version":"615754924717c0b1e293e083b83503c0a872717ad5aa60ed7f1a699eb1b4ea5c","impliedFormat":1},{"version":"074de5b2fdead0165a2757e3aaef20f27a6347b1c36adea27d51456795b37682","impliedFormat":1},{"version":"68834d631c8838c715f225509cfc3927913b9cc7a4870460b5b60c8dbdb99baf","impliedFormat":1},{"version":"24371e69a38fc33e268d4a8716dbcda430d6c2c414a99ff9669239c4b8f40dea","impliedFormat":1},{"version":"ccab02f3920fc75c01174c47fcf67882a11daf16baf9e81701d0a94636e94556","impliedFormat":1},{"version":"3e11fce78ad8c0e1d1db4ba5f0652285509be3acdd519529bc8fcef85f7dafd9","impliedFormat":1},{"version":"ea6bc8de8b59f90a7a3960005fd01988f98fd0784e14bc6922dde2e93305ec7d","impliedFormat":1},{"version":"36107995674b29284a115e21a0618c4c2751b32a8766dd4cb3ba740308b16d59","impliedFormat":1},{"version":"914a0ae30d96d71915fc519ccb4efbf2b62c0ddfb3a3fc6129151076bc01dc60","impliedFormat":1},{"version":"9c32412007b5662fd34a8eb04292fb5314ec370d7016d1c2fb8aa193c807fe22","impliedFormat":1},{"version":"7fd1b31fd35876b0aa650811c25ec2c97a3c6387e5473eb18004bed86cdd76b6","impliedFormat":1},{"version":"4d327f7d72ad0918275cea3eee49a6a8dc8114ae1d5b7f3f5d0774de75f7439a","impliedFormat":1},{"version":"6ebe8ebb8659aaa9d1acbf3710d7dae3e923e97610238b9511c25dc39023a166","impliedFormat":1},{"version":"e85d7f8068f6a26710bff0cc8c0fc5e47f71089c3780fbede05857331d2ddec9","impliedFormat":1},{"version":"7befaf0e76b5671be1d47b77fcc65f2b0aad91cc26529df1904f4a7c46d216e9","impliedFormat":1},{"version":"0a60a292b89ca7218b8616f78e5bbd1c96b87e048849469cccb4355e98af959a","impliedFormat":1},{"version":"0b6e25234b4eec6ed96ab138d96eb70b135690d7dd01f3dd8a8ab291c35a683a","impliedFormat":1},{"version":"9666f2f84b985b62400d2e5ab0adae9ff44de9b2a34803c2c5bd3c8325b17dc0","impliedFormat":1},{"version":"40cd35c95e9cf22cfa5bd84e96408b6fcbca55295f4ff822390abb11afbc3dca","impliedFormat":1},{"version":"b1616b8959bf557feb16369c6124a97a0e74ed6f49d1df73bb4b9ddf68acf3f3","impliedFormat":1},{"version":"5b03a034c72146b61573aab280f295b015b9168470f2df05f6080a2122f9b4df","impliedFormat":1},{"version":"40b463c6766ca1b689bfcc46d26b5e295954f32ad43e37ee6953c0a677e4ae2b","impliedFormat":1},{"version":"249b9cab7f5d628b71308c7d9bb0a808b50b091e640ba3ed6e2d0516f4a8d91d","impliedFormat":1},{"version":"80aae6afc67faa5ac0b32b5b8bc8cc9f7fa299cff15cf09cc2e11fd28c6ae29e","impliedFormat":1},{"version":"f473cd2288991ff3221165dcf73cd5d24da30391f87e85b3dd4d0450c787a391","impliedFormat":1},{"version":"499e5b055a5aba1e1998f7311a6c441a369831c70905cc565ceac93c28083d53","impliedFormat":1},{"version":"8aee8b6d4f9f62cf3776cda1305fb18763e2aade7e13cea5bbe699112df85214","impliedFormat":1},{"version":"c63b9ada8c72f95aac5db92aea07e5e87ec810353cdf63b2d78f49a58662cf6c","impliedFormat":1},{"version":"1cc2a09e1a61a5222d4174ab358a9f9de5e906afe79dbf7363d871a7edda3955","impliedFormat":1},{"version":"5d0375ca7310efb77e3ef18d068d53784faf62705e0ad04569597ae0e755c401","impliedFormat":1},{"version":"59af37caec41ecf7b2e76059c9672a49e682c1a2aa6f9d7dc78878f53aa284d6","impliedFormat":1},{"version":"addf417b9eb3f938fddf8d81e96393a165e4be0d4a8b6402292f9c634b1cb00d","impliedFormat":1},{"version":"b64d4d1c5f877f9c666e98e833f0205edb9384acc46e98a1fef344f64d6aba44","impliedFormat":1},{"version":"adf27937dba6af9f08a68c5b1d3fce0ca7d4b960c57e6d6c844e7d1a8e53adae","impliedFormat":1},{"version":"12950411eeab8563b349cb7959543d92d8d02c289ed893d78499a19becb5a8cc","impliedFormat":1},{"version":"2e85db9e6fd73cfa3d7f28e0ab6b55417ea18931423bd47b409a96e4a169e8e6","impliedFormat":1},{"version":"c46e079fe54c76f95c67fb89081b3e399da2c7d109e7dca8e4b58d83e332e605","impliedFormat":1},{"version":"c9381908473a1c92cb8c516b184e75f4d226dad95c3a85a5af35f670064d9a2f","impliedFormat":1},{"version":"c3f5289820990ab66b70c7fb5b63cb674001009ff84b13de40619619a9c8175f","affectsGlobalScope":true,"impliedFormat":1},{"version":"b3275d55fac10b799c9546804126239baf020d220136163f763b55a74e50e750","affectsGlobalScope":true,"impliedFormat":1},{"version":"fa68a0a3b7cb32c00e39ee3cd31f8f15b80cac97dce51b6ee7fc14a1e8deb30b","affectsGlobalScope":true,"impliedFormat":1},{"version":"1cf059eaf468efcc649f8cf6075d3cb98e9a35a0fe9c44419ec3d2f5428d7123","affectsGlobalScope":true,"impliedFormat":1},{"version":"6c36e755bced82df7fb6ce8169265d0a7bb046ab4e2cb6d0da0cb72b22033e89","affectsGlobalScope":true,"impliedFormat":1},{"version":"e7721c4f69f93c91360c26a0a84ee885997d748237ef78ef665b153e622b36c1","affectsGlobalScope":true,"impliedFormat":1},{"version":"7a93de4ff8a63bafe62ba86b89af1df0ccb5e40bb85b0c67d6bbcfdcf96bf3d4","affectsGlobalScope":true,"impliedFormat":1},{"version":"90e85f9bc549dfe2b5749b45fe734144e96cd5d04b38eae244028794e142a77e","affectsGlobalScope":true,"impliedFormat":1},{"version":"e0a5deeb610b2a50a6350bd23df6490036a1773a8a71d70f2f9549ab009e67ee","affectsGlobalScope":true,"impliedFormat":1},{"version":"3fad5618174d74a34ee006406d4eb37e8d07dd62eb1315dbf52f48d31a337547","impliedFormat":1},{"version":"7e49f52a159435fc8df4de9dc377ef5860732ca2dc9efec1640531d3cf5da7a3","impliedFormat":1},{"version":"dd4bde4bdc2e5394aed6855e98cf135dfdf5dd6468cad842e03116d31bbcc9bc","impliedFormat":1},{"version":"4d4e879009a84a47c05350b8dca823036ba3a29a3038efed1be76c9f81e45edf","affectsGlobalScope":true,"impliedFormat":1},{"version":"8b50a819485ffe0d237bf0d131e92178d14d11e2aa873d73615a9ec578b341f5","impliedFormat":1},{"version":"9ba13b47cb450a438e3076c4a3f6afb9dc85e17eae50f26d4b2d72c0688c9251","impliedFormat":1},{"version":"b64cd4401633ea4ecadfd700ddc8323a13b63b106ac7127c1d2726f32424622c","impliedFormat":1},{"version":"37c6e5fe5715814412b43cc9b50b24c67a63c4e04e753e0d1305970d65417a60","impliedFormat":1},{"version":"1d024184fb57c58c5c91823f9d10b4915a4867b7934e89115fd0d861a9df27c8","impliedFormat":1},{"version":"ee0e4946247f842c6dd483cbb60a5e6b484fee07996e3a7bc7343dfb68a04c5d","impliedFormat":1},{"version":"ef051f42b7e0ef5ca04552f54c4552eac84099d64b6c5ad0ef4033574b6035b8","impliedFormat":1},{"version":"853a43154f1d01b0173d9cbd74063507ece57170bad7a3b68f3fa1229ad0a92f","impliedFormat":1},{"version":"56231e3c39a031bfb0afb797690b20ed4537670c93c0318b72d5180833d98b72","impliedFormat":1},{"version":"5cc7c39031bfd8b00ad58f32143d59eb6ffc24f5d41a20931269011dccd36c5e","impliedFormat":1},{"version":"12d602a8fe4c2f2ba4f7804f5eda8ba07e0c83bf5cf0cda8baffa2e9967bfb77","affectsGlobalScope":true,"impliedFormat":1},{"version":"f96a48183254c00d24575401f1a761b4ce4927d927407e7862a83e06ce5d6964","impliedFormat":1},{"version":"cc25940cfb27aa538e60d465f98bb5068d4d7d33131861ace43f04fe6947d68f","impliedFormat":1},{"version":"8db46b61a690f15b245cf16270db044dc047dce9f93b103a59f50262f677ea1f","impliedFormat":1},{"version":"01ff95aa1443e3f7248974e5a771f513cb2ac158c8898f470a1792f817bee497","impliedFormat":1},{"version":"757227c8b345c57d76f7f0e3bbad7a91ffca23f1b2547cbed9e10025816c9cb7","impliedFormat":1},{"version":"42a05d8f239f74587d4926aba8cc54792eed8e8a442c7adc9b38b516642aadfe","impliedFormat":1},{"version":"e843c4c3582948689477a98129c080d2a6919cf44b6b1eed8f992642fe141cf5","impliedFormat":1},{"version":"101f482fd48cb4c7c0468dcc6d62c843d842977aea6235644b1edd05e81fbf22","impliedFormat":1},{"version":"266bee0a41e9c3ba335583e21e9277ae03822402cf5e8e1d99f5196853613b98","affectsGlobalScope":true,"impliedFormat":1},{"version":"386606f8a297988535cb1401959041cfa7f59d54b8a9ed09738e65c98684c976","impliedFormat":1},{"version":"3ef397f12387eff17f550bc484ea7c27d21d43816bbe609d495107f44b97e933","impliedFormat":1},{"version":"1023282e2ba810bc07905d3668349fbd37a26411f0c8f94a70ef3c05fe523fcf","impliedFormat":1},{"version":"b214ebcf76c51b115453f69729ee8aa7b7f8eccdae2a922b568a45c2d7ff52f7","impliedFormat":1},{"version":"429c9cdfa7d126255779efd7e6d9057ced2d69c81859bbab32073bad52e9ba76","impliedFormat":1},{"version":"e236b5eba291f51bdf32c231673e6cab81b5410850e61f51a7a524dddadc0f95","impliedFormat":1},{"version":"9cc9d479fb2283d21495e1eb22dccce6cbeaa1e2d87832fe390f6b61b1ff537d","affectsGlobalScope":true,"impliedFormat":1},{"version":"7f2c62938251b45715fd2a9887060ec4fbc8724727029d1cbce373747252bdd7","impliedFormat":1},{"version":"e3ace08b6bbd84655d41e244677b474fd995923ffef7149ddb68af8848b60b05","impliedFormat":1},{"version":"132580b0e86c48fab152bab850fc57a4b74fe915c8958d2ccb052b809a44b61c","impliedFormat":1},{"version":"90a278f5fab7557e69e97056c0841adf269c42697194f0bd5c5e69152637d4b3","impliedFormat":1},{"version":"69c9a5a9392e8564bd81116e1ed93b13205201fb44cb35a7fde8c9f9e21c4b23","impliedFormat":1},{"version":"5f8fc37f8434691ffac1bfd8fc2634647da2c0e84253ab5d2dd19a7718915b35","impliedFormat":1},{"version":"5981c2340fd8b076cae8efbae818d42c11ffc615994cb060b1cd390795f1be2b","impliedFormat":1},{"version":"3e4e0959c67965a12a0976d58ba1ef64c49d852aaaf0e91148a64d3681ca22c9","impliedFormat":1},{"version":"1edcf2f36fc332615846bde6dcc71a8fe526065505bc5e3dcfd65a14becdf698","affectsGlobalScope":true,"impliedFormat":1},{"version":"0250da3eb85c99624f974e77ef355cdf86f43980251bc371475c2b397ba55bcd","impliedFormat":1},{"version":"f1c93e046fb3d9b7f8249629f4b63dc068dd839b824dd0aa39a5e68476dc9420","impliedFormat":1},{"version":"3d3a5f27ffbc06c885dd4d5f9ee20de61faf877fe2c3a7051c4825903d9a7fdc","impliedFormat":1},{"version":"12806f9f085598ef930edaf2467a5fa1789a878fba077cd27e85dc5851e11834","impliedFormat":1},{"version":"1c7573c37465af751be31717e70588b16a272a974e790427fc9558b8e9b199d1","impliedFormat":1},{"version":"a43fe41c33d0a192a0ecaf9b92e87bef3709c9972e6d53c42c49251ccb962d69","impliedFormat":1},{"version":"a177959203c017fad3ecc4f3d96c8757a840957a4959a3ae00dab9d35961ca6c","affectsGlobalScope":true,"impliedFormat":1},{"version":"6fc727ccf9b36e257ff982ea0badeffbfc2c151802f741bddff00c6af3b784cf","impliedFormat":1},{"version":"ca279fadaa088b63f123c86ffb4dda5116f8dba23e6e93e63a2b48262320be38","impliedFormat":1},{"version":"4844a4c9b4b1e812b257676ed8a80b3f3be0e29bf05e742cc2ea9c3c6865e6c6","impliedFormat":1},{"version":"064878a60367e0407c42fb7ba02a2ea4d83257357dc20088e549bd4d89433e9c","impliedFormat":1},{"version":"cca8917838a876e2d7016c9b6af57cbf11fdf903c5fdd8e613fa31840b2957bf","impliedFormat":1},{"version":"d91ae55e4282c22b9c21bc26bd3ef637d3fe132507b10529ae68bf76f5de785b","impliedFormat":1},{"version":"b484ec11ba00e3a2235562a41898d55372ccabe607986c6fa4f4aba72093749f","impliedFormat":1},{"version":"bc9b17634d5e75b9040d8b414bb5bc936273e8100212816e905e39948cd9de96","impliedFormat":1},{"version":"41ef7992c555671a8fe54db302788adefa191ded810a50329b79d20a6772d14c","impliedFormat":1},{"version":"041a7781b9127ab568d2cdcce62c58fdea7c7407f40b8c50045d7866a2727130","impliedFormat":1},{"version":"4c5e90ddbcd177ad3f2ffc909ae217c87820f1e968f6959e4b6ba38a8cec935e","impliedFormat":1},{"version":"b70dd9a44e1ac42f030bb12e7d79117eac7cb74170d72d381a1e7913320af23a","impliedFormat":1},{"version":"55cdbeebe76a1fa18bbd7e7bf73350a2173926bd3085bb050cf5a5397025ee4e","impliedFormat":1},{"version":"acfb723d81eda39156251aed414c553294870bf53062429ebfcfba8a68cb4753","impliedFormat":99},{"version":"fa69a90381c2f85889722a911a732a5ee3596dc3acecda8a9aa2fa89b9615d8d","impliedFormat":99},{"version":"b5ce343886d23392be9c8280e9f24a87f1d7d3667f6672c2fe4aa61fa4ece7d4","impliedFormat":99},{"version":"57e9e1b0911874c62d743af24b5d56032759846533641d550b12a45ff404bf07","impliedFormat":99},{"version":"b0857bb28fd5236ace84280f79a25093f919fd0eff13e47cc26ea03de60a7294","impliedFormat":99},{"version":"5e43e0824f10cd8c48e7a8c5c673638488925a12c31f0f9e0957965c290eb14c","impliedFormat":99},{"version":"854cd3a3375ffc4e7a92b2168dd065d7ff2614b43341038a65cca865a44c00c5","impliedFormat":99},{"version":"ef13c73d6157a32933c612d476c1524dd674cf5b9a88571d7d6a0d147544d529","impliedFormat":99},{"version":"3b0a56d056d81a011e484b9c05d5e430711aaecd561a788bad1d0498aad782c7","impliedFormat":99},{"version":"2f863ee9b873a65d9c3338ea7aaddbdb41a9673f062f06983d712bd01c25dc6b","impliedFormat":99},{"version":"67aa128c2bc170b93794f191feffc65a4b33e878db211cfcb7658c4b72f7a1f5","impliedFormat":99},{"version":"ac3d263474022e9a14c43f588f485d549641d839b159ecc971978b90f34bdf6b","impliedFormat":99},{"version":"a7ca8df4f2931bef2aa4118078584d84a0b16539598eaadf7dce9104dfaa381c","impliedFormat":1},{"version":"10073cdcf56982064c5337787cc59b79586131e1b28c106ede5bff362f912b70","impliedFormat":99},{"version":"72950913f4900b680f44d8cab6dd1ea0311698fc1eefb014eb9cdfc37ac4a734","impliedFormat":1},{"version":"36977c14a7f7bfc8c0426ae4343875689949fb699f3f84ecbe5b300ebf9a2c55","impliedFormat":1},{"version":"ff0a83c9a0489a627e264ffcb63f2264b935b20a502afa3a018848139e3d8575","impliedFormat":99},{"version":"324ac98294dab54fbd580c7d0e707d94506d7b2c3d5efe981a8495f02cf9ad96","impliedFormat":99},{"version":"9ec72eb493ff209b470467e24264116b6a8616484bca438091433a545dfba17e","impliedFormat":99},{"version":"c35b8117804c639c53c87f2c23e0c786df61d552e513bd5179f5b88e29964838","impliedFormat":99},{"version":"c609331c6ed4ad4af54e101088c6a4dcb48f8db7b0b97e44a6efeb130f4331bd","impliedFormat":99},{"version":"bcbd3becd08b4515225880abea0dbfbbf0d1181ce3af8f18f72f61edbe4febfb","impliedFormat":99},{"version":"67acaedb46832d66c15f1b09fb7b6a0b7f41bdbf8eaa586ec70459b3e8896eb9","impliedFormat":99},{"version":"4535ab977ee871e956eb7bebe2db5de79f5d5ec7dfbbf1d35e08f4a2d6630dac","impliedFormat":99},{"version":"b79b5ed99f26ffb2f8ae4bdcc4b34a9542197dc3fa96cfb425c2a81e618cff28","impliedFormat":99},{"version":"31fd7c12f6e27154efb52a916b872509a771880f3b20f2dfd045785c13aa813f","impliedFormat":99},{"version":"b481de4ab5379bd481ca12fc0b255cdc47341629a22c240a89cdb4e209522be2","impliedFormat":99},{"version":"bdd14f07b4eca0b4b5203b85b8dbc4d084c749fa590bee5ea613e1641dcd3b29","impliedFormat":99},{"version":"427fe2004642504828c1476d0af4270e6ad4db6de78c0b5da3e4c5ca95052a99","impliedFormat":1},{"version":"2eeffcee5c1661ddca53353929558037b8cf305ffb86a803512982f99bcab50d","impliedFormat":99},{"version":"9afb4cb864d297e4092a79ee2871b5d3143ea14153f62ef0bb04ede25f432030","affectsGlobalScope":true,"impliedFormat":99},{"version":"4e258d11c899cb9ff36b4b5c53df59cf4a5ccae9a9931529686e77431e0a3518","affectsGlobalScope":true,"impliedFormat":99},{"version":"a5ae67a67f786ffe92d34b55467a40fb50fb0093e92388cadce6168fa42690fd","impliedFormat":99},{"version":"69bf2422313487956e4dacf049f30cb91b34968912058d244cb19e4baa24da97","impliedFormat":99},{"version":"6987dfb4b0c4e02112cc4e548e7a77b3d9ddfeffa8c8a2db13ceac361a4567d9","impliedFormat":99},{"version":"a534e61c2f06a147d97aebad720db97dffd8066b7142212e46bcbcdcb640b81a","impliedFormat":99},{"version":"ddf569d04470a4d629090d43a16735185001f3fcf0ae036ead99f2ceab62be48","impliedFormat":99},{"version":"b413fbc6658fe2774f8bf9a15cf4c53e586fc38a2d5256b3b9647da242c14389","impliedFormat":99},{"version":"c30a41267fc04c6518b17e55dcb2b810f267af4314b0b6d7df1c33a76ce1b330","impliedFormat":1},{"version":"72422d0bac4076912385d0c10911b82e4694fc106e2d70added091f88f0824ba","impliedFormat":1},{"version":"da251b82c25bee1d93f9fd80c5a61d945da4f708ca21285541d7aff83ecb8200","impliedFormat":1},{"version":"64db14db2bf37ac089766fdb3c7e1160fabc10e9929bc2deeede7237e4419fc8","impliedFormat":1},{"version":"98b94085c9f78eba36d3d2314affe973e8994f99864b8708122750788825c771","impliedFormat":1},{"version":"53c448183c7177c83d3eb0b40824cf8952721a6584cf22052adc24f778986732","impliedFormat":99},{"version":"0a5bc32362b0559b9bcf0a6a83136c4442dbbd0edecd671538a5e03454b6dff0","affectsGlobalScope":true,"impliedFormat":99}],"root":[[70,75]],"options":{"composite":true,"declaration":true,"declarationDir":"./@types","declarationMap":true,"emitDeclarationOnly":true,"esModuleInterop":true,"module":199,"outDir":"./","rootDir":"../src","skipLibCheck":true,"sourceMap":true,"strict":true,"target":9,"tsBuildInfoFile":"./.tsbuildinfo"},"referencedMap":[[71,1],[73,2],[75,3],[74,4],[72,5],[70,6],[225,4],[228,7],[226,4],[138,8],[139,8],[140,9],[78,10],[141,11],[142,12],[143,13],[76,4],[144,14],[145,15],[146,16],[147,17],[148,18],[149,19],[150,19],[151,20],[152,21],[153,22],[154,23],[79,4],[77,4],[155,24],[156,25],[157,26],[197,27],[158,28],[159,29],[160,28],[161,30],[162,31],[163,32],[164,33],[165,33],[166,33],[167,34],[168,35],[169,36],[170,37],[171,38],[172,39],[173,39],[174,40],[175,4],[176,4],[177,41],[178,42],[179,41],[180,43],[181,44],[182,45],[183,46],[184,47],[185,48],[186,49],[187,50],[188,51],[189,52],[190,53],[191,54],[192,55],[193,56],[194,57],[80,28],[81,4],[82,58],[83,59],[84,4],[85,60],[86,4],[129,61],[130,62],[131,63],[132,63],[133,64],[134,4],[135,11],[136,65],[137,62],[195,66],[196,67],[229,68],[198,4],[208,69],[204,70],[207,71],[230,72],[215,4],[217,73],[216,74],[223,4],[206,75],[199,76],[201,77],[203,78],[202,4],[205,76],[200,4],[227,4],[238,79],[240,80],[239,81],[237,82],[236,4],[69,83],[67,84],[68,85],[66,4],[231,4],[224,4],[63,4],[64,4],[12,4],[10,4],[11,4],[16,4],[15,4],[2,4],[17,4],[18,4],[19,4],[20,4],[21,4],[22,4],[23,4],[24,4],[3,4],[25,4],[26,4],[4,4],[27,4],[31,4],[28,4],[29,4],[30,4],[32,4],[33,4],[34,4],[5,4],[35,4],[36,4],[37,4],[38,4],[6,4],[42,4],[39,4],[40,4],[41,4],[43,4],[7,4],[44,4],[49,4],[50,4],[45,4],[46,4],[47,4],[48,4],[8,4],[54,4],[51,4],[52,4],[53,4],[55,4],[9,4],[56,4],[65,4],[57,4],[58,4],[60,4],[59,4],[1,4],[61,4],[62,4],[14,4],[13,4],[105,86],[117,87],[102,88],[118,89],[127,90],[93,91],[94,92],[92,93],[126,94],[121,95],[125,96],[96,97],[114,98],[95,99],[124,100],[90,101],[91,95],[97,102],[98,4],[104,103],[101,102],[88,104],[128,105],[119,106],[108,107],[107,102],[109,108],[112,109],[106,110],[110,111],[122,94],[99,112],[100,113],[113,114],[89,89],[116,115],[115,102],[103,113],[111,116],[120,4],[87,4],[123,117],[211,118],[214,119],[212,118],[210,4],[213,120],[232,121],[222,122],[218,123],[219,70],[235,124],[233,125],[220,126],[234,127],[209,4],[221,128],[241,129],[242,130]],"latestChangedDtsFile":"./@types/index.d.ts","version":"5.9.3"}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import type { BroadcastMessage, BroadcastState } from "./types.js";
|
|
2
|
+
/**
|
|
3
|
+
* Result of applying a broadcast message to cached client state.
|
|
4
|
+
*/
|
|
5
|
+
export type ApplyResult<S extends BroadcastState = BroadcastState> = {
|
|
6
|
+
ok: true;
|
|
7
|
+
state: S;
|
|
8
|
+
} | {
|
|
9
|
+
ok: false;
|
|
10
|
+
reason: "stale" | "behind" | "patch-failed";
|
|
11
|
+
};
|
|
12
|
+
/**
|
|
13
|
+
* Apply a broadcast message to the client's cached state.
|
|
14
|
+
*
|
|
15
|
+
* Handles both full state and incremental patches with version validation.
|
|
16
|
+
* Returns the new state on success, or a failure reason that the client
|
|
17
|
+
* can use to decide whether to resync.
|
|
18
|
+
*
|
|
19
|
+
* ## Version logic
|
|
20
|
+
*
|
|
21
|
+
* - **Full state**: accepted if `msg._v >= cachedV` (or no cached state)
|
|
22
|
+
* - **Patch**:
|
|
23
|
+
* - `_baseV < cachedV` → "stale" (client ahead, skip — mutation response arrived first)
|
|
24
|
+
* - `_baseV > cachedV` → "behind" (client missed a version, must resync)
|
|
25
|
+
* - `_baseV === cachedV` → apply patch ops
|
|
26
|
+
*
|
|
27
|
+
* ## Usage (React Query)
|
|
28
|
+
*
|
|
29
|
+
* ```typescript
|
|
30
|
+
* onData: (msg) => {
|
|
31
|
+
* const cached = utils.getState.getData({ streamId });
|
|
32
|
+
* const result = applyBroadcastMessage(msg, cached);
|
|
33
|
+
* if (result.ok) {
|
|
34
|
+
* utils.getState.setData({ streamId }, result.state);
|
|
35
|
+
* } else if (result.reason === "behind") {
|
|
36
|
+
* utils.getState.invalidate({ streamId }); // trigger full refetch
|
|
37
|
+
* }
|
|
38
|
+
* // "stale" → no-op, client already has newer state
|
|
39
|
+
* }
|
|
40
|
+
* ```
|
|
41
|
+
*/
|
|
42
|
+
export declare function applyBroadcastMessage<S extends BroadcastState>(msg: BroadcastMessage<S>, cached: S | null | undefined): ApplyResult<S>;
|
|
43
|
+
//# sourceMappingURL=apply-patch.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"apply-patch.d.ts","sourceRoot":"","sources":["../../src/apply-patch.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,gBAAgB,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAEnE;;GAEG;AACH,MAAM,MAAM,WAAW,CAAC,CAAC,SAAS,cAAc,GAAG,cAAc,IAC7D;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,KAAK,EAAE,CAAC,CAAA;CAAE,GACtB;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,OAAO,GAAG,QAAQ,GAAG,cAAc,CAAA;CAAE,CAAC;AAE/D;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,wBAAgB,qBAAqB,CAAC,CAAC,SAAS,cAAc,EAC5D,GAAG,EAAE,gBAAgB,CAAC,CAAC,CAAC,EACxB,MAAM,EAAE,CAAC,GAAG,IAAI,GAAG,SAAS,GAC3B,WAAW,CAAC,CAAC,CAAC,CAyBhB"}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { StateCache } from "./state-cache.js";
|
|
2
|
+
import type { BroadcastMessage, BroadcastOptions, BroadcastState, Subscriber } from "./types.js";
|
|
3
|
+
/**
|
|
4
|
+
* Server-side broadcast channel for incremental state sync over SSE.
|
|
5
|
+
*
|
|
6
|
+
* Manages per-stream subscriber sets and an LRU state cache. When state
|
|
7
|
+
* changes, computes an RFC 6902 JSON Patch against the previous cached
|
|
8
|
+
* state and pushes either a patch (small diff) or full state (large diff
|
|
9
|
+
* or first broadcast) to all subscribers.
|
|
10
|
+
*
|
|
11
|
+
* ## Usage
|
|
12
|
+
*
|
|
13
|
+
* ```typescript
|
|
14
|
+
* const broadcast = new BroadcastChannel<MyState>();
|
|
15
|
+
*
|
|
16
|
+
* // After every app.do():
|
|
17
|
+
* const snap = await doAction(...);
|
|
18
|
+
* const state = deriveState(snap); // app-specific state derivation
|
|
19
|
+
* broadcast.publish(streamId, state); // computes patch + pushes to SSE
|
|
20
|
+
*
|
|
21
|
+
* // In SSE subscription:
|
|
22
|
+
* const cleanup = broadcast.subscribe(streamId, (msg) => {
|
|
23
|
+
* pending = msg;
|
|
24
|
+
* resolve?.();
|
|
25
|
+
* });
|
|
26
|
+
*
|
|
27
|
+
* // Initial state for reconnects:
|
|
28
|
+
* const cached = broadcast.getState(streamId);
|
|
29
|
+
* if (cached) yield { _type: "full", ...cached, serverTime: ... };
|
|
30
|
+
* ```
|
|
31
|
+
*
|
|
32
|
+
* ## Version Contract
|
|
33
|
+
*
|
|
34
|
+
* The `_v` field on state MUST be set from `snap.event.version` (the event
|
|
35
|
+
* store's monotonic stream version) BEFORE calling `publish()`. This is the
|
|
36
|
+
* single source of truth for ordering — no separate version counters.
|
|
37
|
+
*/
|
|
38
|
+
export declare class BroadcastChannel<S extends BroadcastState = BroadcastState> {
|
|
39
|
+
private channels;
|
|
40
|
+
private stateCache;
|
|
41
|
+
private maxPatchOps;
|
|
42
|
+
constructor(options?: BroadcastOptions & {
|
|
43
|
+
cacheSize?: number;
|
|
44
|
+
});
|
|
45
|
+
/**
|
|
46
|
+
* Publish new state for a stream. Computes a patch against the previously
|
|
47
|
+
* cached state and pushes to all subscribers.
|
|
48
|
+
*
|
|
49
|
+
* @param streamId - The event store stream ID
|
|
50
|
+
* @param state - Full state with `_v` set from `snap.event.version`
|
|
51
|
+
* @returns The broadcast message that was sent (or undefined if no subscribers and no cache change)
|
|
52
|
+
*/
|
|
53
|
+
publish(streamId: string, state: S): BroadcastMessage<S>;
|
|
54
|
+
/**
|
|
55
|
+
* Publish a state update that doesn't change the event version
|
|
56
|
+
* (e.g. presence overlay, computed field refresh).
|
|
57
|
+
* Uses the same version as the cached state for _baseV and _v.
|
|
58
|
+
*/
|
|
59
|
+
publishOverlay(streamId: string, state: S): BroadcastMessage<S> | undefined;
|
|
60
|
+
/**
|
|
61
|
+
* Subscribe to broadcast messages for a stream.
|
|
62
|
+
* Returns a cleanup function that removes the subscription.
|
|
63
|
+
*/
|
|
64
|
+
subscribe(streamId: string, cb: Subscriber<S>): () => void;
|
|
65
|
+
/** Get the number of subscribers for a stream. */
|
|
66
|
+
getSubscriberCount(streamId: string): number;
|
|
67
|
+
/** Get the cached state for a stream (for reconnects / initial SSE yield). */
|
|
68
|
+
getState(streamId: string): S | undefined;
|
|
69
|
+
/** Direct access to the state cache (for app-specific reads like presence). */
|
|
70
|
+
get cache(): StateCache<S>;
|
|
71
|
+
private computeMessage;
|
|
72
|
+
}
|
|
73
|
+
//# sourceMappingURL=broadcast.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"broadcast.d.ts","sourceRoot":"","sources":["../../src/broadcast.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAC9C,OAAO,KAAK,EACV,gBAAgB,EAChB,gBAAgB,EAChB,cAAc,EACd,UAAU,EACX,MAAM,YAAY,CAAC;AAIpB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AACH,qBAAa,gBAAgB,CAAC,CAAC,SAAS,cAAc,GAAG,cAAc;IACrE,OAAO,CAAC,QAAQ,CAAyC;IACzD,OAAO,CAAC,UAAU,CAAgB;IAClC,OAAO,CAAC,WAAW,CAAS;gBAEhB,OAAO,CAAC,EAAE,gBAAgB,GAAG;QAAE,SAAS,CAAC,EAAE,MAAM,CAAA;KAAE;IAK/D;;;;;;;OAOG;IACH,OAAO,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,GAAG,gBAAgB,CAAC,CAAC,CAAC;IAYxD;;;;OAIG;IACH,cAAc,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,GAAG,gBAAgB,CAAC,CAAC,CAAC,GAAG,SAAS;IAc3E;;;OAGG;IACH,SAAS,CAAC,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,UAAU,CAAC,CAAC,CAAC,GAAG,MAAM,IAAI;IAW1D,kDAAkD;IAClD,kBAAkB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM;IAI5C,8EAA8E;IAC9E,QAAQ,CAAC,QAAQ,EAAE,MAAM,GAAG,CAAC,GAAG,SAAS;IAIzC,+EAA+E;IAC/E,IAAI,KAAK,IAAI,UAAU,CAAC,CAAC,CAAC,CAEzB;IAID,OAAO,CAAC,cAAc;CAwBvB"}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @packageDocumentation
|
|
3
|
+
* @module act-sse
|
|
4
|
+
*
|
|
5
|
+
* Incremental state broadcast over SSE for act event-sourced apps.
|
|
6
|
+
*
|
|
7
|
+
* Provides server-side broadcast with automatic RFC 6902 JSON Patch
|
|
8
|
+
* computation, an LRU state cache, presence tracking, and a client-side
|
|
9
|
+
* patch applicator with version validation and resync detection.
|
|
10
|
+
*
|
|
11
|
+
* ## Architecture
|
|
12
|
+
*
|
|
13
|
+
* ```
|
|
14
|
+
* app.do() → snap
|
|
15
|
+
* │
|
|
16
|
+
* ▼
|
|
17
|
+
* deriveState(snap) ← app-specific (overlay presence, deadlines, etc.)
|
|
18
|
+
* state._v = snap.event.version
|
|
19
|
+
* │
|
|
20
|
+
* ▼
|
|
21
|
+
* broadcast.publish(streamId, state)
|
|
22
|
+
* │
|
|
23
|
+
* ├── compare(prev, state) → RFC 6902 ops
|
|
24
|
+
* ├── if ops ≤ threshold → PatchMessage { _baseV, _v, _patch }
|
|
25
|
+
* ├── if ops > threshold → FullStateMessage { _v, ...state }
|
|
26
|
+
* └── push to all SSE subscribers
|
|
27
|
+
* │
|
|
28
|
+
* ▼
|
|
29
|
+
* Client: applyBroadcastMessage(msg, cached)
|
|
30
|
+
* │
|
|
31
|
+
* ├── full → accept if _v ≥ cachedV
|
|
32
|
+
* ├── patch → apply if _baseV === cachedV
|
|
33
|
+
* ├── stale → skip (_baseV < cachedV, mutation response arrived first)
|
|
34
|
+
* └── behind → resync (_baseV > cachedV, client missed a version)
|
|
35
|
+
* ```
|
|
36
|
+
*
|
|
37
|
+
* ## Version Contract
|
|
38
|
+
*
|
|
39
|
+
* `_v` is always the event store stream version (`snap.event.version`).
|
|
40
|
+
* No separate version counters. The event store is the single source of truth.
|
|
41
|
+
*/
|
|
42
|
+
export { applyBroadcastMessage } from "./apply-patch.js";
|
|
43
|
+
export type { ApplyResult } from "./apply-patch.js";
|
|
44
|
+
export { BroadcastChannel } from "./broadcast.js";
|
|
45
|
+
export { PresenceTracker } from "./presence.js";
|
|
46
|
+
export { StateCache } from "./state-cache.js";
|
|
47
|
+
export type { BroadcastMessage, BroadcastOptions, BroadcastState, FullStateMessage, PatchMessage, Subscriber, } from "./types.js";
|
|
48
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwCG;AAEH,OAAO,EAAE,qBAAqB,EAAE,MAAM,kBAAkB,CAAC;AACzD,YAAY,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AACpD,OAAO,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AAClD,OAAO,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAChD,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAC9C,YAAY,EACV,gBAAgB,EAChB,gBAAgB,EAChB,cAAc,EACd,gBAAgB,EAChB,YAAY,EACZ,UAAU,GACX,MAAM,YAAY,CAAC"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generic presence tracker — ref-counted online status per stream per identity.
|
|
3
|
+
*
|
|
4
|
+
* Supports multi-tab: each subscribe increments the ref count, each
|
|
5
|
+
* unsubscribe decrements it. An identity is considered online when
|
|
6
|
+
* ref count > 0.
|
|
7
|
+
*
|
|
8
|
+
* ## Usage
|
|
9
|
+
*
|
|
10
|
+
* ```typescript
|
|
11
|
+
* const presence = new PresenceTracker();
|
|
12
|
+
*
|
|
13
|
+
* // On SSE connect:
|
|
14
|
+
* presence.add(gameId, playerId);
|
|
15
|
+
*
|
|
16
|
+
* // On SSE disconnect:
|
|
17
|
+
* presence.remove(gameId, playerId);
|
|
18
|
+
*
|
|
19
|
+
* // Query:
|
|
20
|
+
* presence.getOnline(gameId); // Set<string>
|
|
21
|
+
* ```
|
|
22
|
+
*/
|
|
23
|
+
export declare class PresenceTracker {
|
|
24
|
+
private streams;
|
|
25
|
+
/** Increment ref count for an identity on a stream. */
|
|
26
|
+
add(streamId: string, identityId: string): void;
|
|
27
|
+
/** Decrement ref count. Removes the identity when count reaches 0. */
|
|
28
|
+
remove(streamId: string, identityId: string): void;
|
|
29
|
+
/** Get the set of online identity IDs for a stream. */
|
|
30
|
+
getOnline(streamId: string): Set<string>;
|
|
31
|
+
/** Check if a specific identity is online for a stream. */
|
|
32
|
+
isOnline(streamId: string, identityId: string): boolean;
|
|
33
|
+
}
|
|
34
|
+
//# sourceMappingURL=presence.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"presence.d.ts","sourceRoot":"","sources":["../../src/presence.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,qBAAa,eAAe;IAC1B,OAAO,CAAC,OAAO,CAA0C;IAEzD,uDAAuD;IACvD,GAAG,CAAC,QAAQ,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,IAAI;IAM/C,sEAAsE;IACtE,MAAM,CAAC,QAAQ,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,IAAI;IASlD,uDAAuD;IACvD,SAAS,CAAC,QAAQ,EAAE,MAAM,GAAG,GAAG,CAAC,MAAM,CAAC;IAKxC,2DAA2D;IAC3D,QAAQ,CAAC,QAAQ,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO;CAGxD"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { BroadcastState } from "./types.js";
|
|
2
|
+
/**
|
|
3
|
+
* Generic LRU cache for aggregate state objects.
|
|
4
|
+
*
|
|
5
|
+
* Keyed by stream ID. Each entry stores the full state (with `_v` from the
|
|
6
|
+
* event store). Used as the "previous state" baseline for computing patches,
|
|
7
|
+
* and as the fast path for reconnects.
|
|
8
|
+
*
|
|
9
|
+
* The cache is shared between the broadcast hot path and read queries.
|
|
10
|
+
* Projections should maintain their own cache to avoid double-apply bugs.
|
|
11
|
+
*/
|
|
12
|
+
export declare class StateCache<S extends BroadcastState = BroadcastState> {
|
|
13
|
+
private cache;
|
|
14
|
+
private maxSize;
|
|
15
|
+
constructor(maxSize?: number);
|
|
16
|
+
/** Get a cached state, promoting it to MRU position. */
|
|
17
|
+
get(key: string): S | undefined;
|
|
18
|
+
/** Set a cached state, evicting the LRU entry if at capacity. */
|
|
19
|
+
set(key: string, state: S): void;
|
|
20
|
+
/** Remove a cached entry. */
|
|
21
|
+
delete(key: string): void;
|
|
22
|
+
/** Check if a key exists in the cache. */
|
|
23
|
+
has(key: string): boolean;
|
|
24
|
+
/** Current number of cached entries. */
|
|
25
|
+
get size(): number;
|
|
26
|
+
/** Direct access to the underlying map (for iteration). */
|
|
27
|
+
entries(): IterableIterator<[string, S]>;
|
|
28
|
+
}
|
|
29
|
+
//# sourceMappingURL=state-cache.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"state-cache.d.ts","sourceRoot":"","sources":["../../src/state-cache.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAEjD;;;;;;;;;GASG;AACH,qBAAa,UAAU,CAAC,CAAC,SAAS,cAAc,GAAG,cAAc;IAC/D,OAAO,CAAC,KAAK,CAAwB;IACrC,OAAO,CAAC,OAAO,CAAS;gBAEZ,OAAO,SAAK;IAIxB,wDAAwD;IACxD,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,CAAC,GAAG,SAAS;IAU/B,iEAAiE;IACjE,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,GAAG,IAAI;IAShC,6BAA6B;IAC7B,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI;IAIzB,0CAA0C;IAC1C,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO;IAIzB,wCAAwC;IACxC,IAAI,IAAI,IAAI,MAAM,CAEjB;IAED,2DAA2D;IAC3D,OAAO,IAAI,gBAAgB,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC;CAGzC"}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import type { Operation } from "fast-json-patch";
|
|
2
|
+
/**
|
|
3
|
+
* Base constraint for state objects managed by the broadcast system.
|
|
4
|
+
* Apps extend this with their own domain state shape.
|
|
5
|
+
*/
|
|
6
|
+
export type BroadcastState = Record<string, unknown> & {
|
|
7
|
+
/** Event store stream version — set by the broadcast layer from snap.event.version */
|
|
8
|
+
_v: number;
|
|
9
|
+
};
|
|
10
|
+
/**
|
|
11
|
+
* Full state message — sent on initial connect, resync, or when patch is too large.
|
|
12
|
+
*/
|
|
13
|
+
export type FullStateMessage<S extends BroadcastState = BroadcastState> = S & {
|
|
14
|
+
_type: "full";
|
|
15
|
+
serverTime: string;
|
|
16
|
+
};
|
|
17
|
+
/**
|
|
18
|
+
* Incremental patch message — sent when the diff is small enough.
|
|
19
|
+
* Client applies RFC 6902 operations to its cached state at _baseV to reach _v.
|
|
20
|
+
*/
|
|
21
|
+
export type PatchMessage = {
|
|
22
|
+
_type: "patch";
|
|
23
|
+
/** Target version after applying the patch */
|
|
24
|
+
_v: number;
|
|
25
|
+
/** Version the patch applies to (client must have this version cached) */
|
|
26
|
+
_baseV: number;
|
|
27
|
+
/** RFC 6902 JSON Patch operations */
|
|
28
|
+
_patch: Operation[];
|
|
29
|
+
serverTime: string;
|
|
30
|
+
};
|
|
31
|
+
/**
|
|
32
|
+
* Discriminated union sent over SSE — client switches on `_type`.
|
|
33
|
+
*/
|
|
34
|
+
export type BroadcastMessage<S extends BroadcastState = BroadcastState> = FullStateMessage<S> | PatchMessage;
|
|
35
|
+
/**
|
|
36
|
+
* Subscriber callback — receives either a patch or full state message.
|
|
37
|
+
*/
|
|
38
|
+
export type Subscriber<S extends BroadcastState = BroadcastState> = (msg: BroadcastMessage<S>) => void;
|
|
39
|
+
/**
|
|
40
|
+
* Options for creating a broadcast channel.
|
|
41
|
+
*/
|
|
42
|
+
export type BroadcastOptions = {
|
|
43
|
+
/** Max RFC 6902 operations before falling back to full state (default: 50) */
|
|
44
|
+
maxPatchOps?: number;
|
|
45
|
+
};
|
|
46
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAEjD;;;GAGG;AACH,MAAM,MAAM,cAAc,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG;IACrD,sFAAsF;IACtF,EAAE,EAAE,MAAM,CAAC;CACZ,CAAC;AAEF;;GAEG;AACH,MAAM,MAAM,gBAAgB,CAAC,CAAC,SAAS,cAAc,GAAG,cAAc,IAAI,CAAC,GAAG;IAC5E,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,EAAE,MAAM,CAAC;CACpB,CAAC;AAEF;;;GAGG;AACH,MAAM,MAAM,YAAY,GAAG;IACzB,KAAK,EAAE,OAAO,CAAC;IACf,8CAA8C;IAC9C,EAAE,EAAE,MAAM,CAAC;IACX,0EAA0E;IAC1E,MAAM,EAAE,MAAM,CAAC;IACf,qCAAqC;IACrC,MAAM,EAAE,SAAS,EAAE,CAAC;IACpB,UAAU,EAAE,MAAM,CAAC;CACpB,CAAC;AAEF;;GAEG;AACH,MAAM,MAAM,gBAAgB,CAAC,CAAC,SAAS,cAAc,GAAG,cAAc,IAClE,gBAAgB,CAAC,CAAC,CAAC,GACnB,YAAY,CAAC;AAEjB;;GAEG;AACH,MAAM,MAAM,UAAU,CAAC,CAAC,SAAS,cAAc,GAAG,cAAc,IAAI,CAClE,GAAG,EAAE,gBAAgB,CAAC,CAAC,CAAC,KACrB,IAAI,CAAC;AAEV;;GAEG;AACH,MAAM,MAAM,gBAAgB,GAAG;IAC7B,8EAA8E;IAC9E,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB,CAAC"}
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/index.ts
|
|
21
|
+
var index_exports = {};
|
|
22
|
+
__export(index_exports, {
|
|
23
|
+
BroadcastChannel: () => BroadcastChannel,
|
|
24
|
+
PresenceTracker: () => PresenceTracker,
|
|
25
|
+
StateCache: () => StateCache,
|
|
26
|
+
applyBroadcastMessage: () => applyBroadcastMessage
|
|
27
|
+
});
|
|
28
|
+
module.exports = __toCommonJS(index_exports);
|
|
29
|
+
|
|
30
|
+
// src/apply-patch.ts
|
|
31
|
+
var import_fast_json_patch = require("fast-json-patch");
|
|
32
|
+
function applyBroadcastMessage(msg, cached) {
|
|
33
|
+
const cachedV = cached?._v ?? 0;
|
|
34
|
+
if (msg._type === "full") {
|
|
35
|
+
if (msg._v < cachedV) return { ok: false, reason: "stale" };
|
|
36
|
+
const { _type, ...state } = msg;
|
|
37
|
+
return { ok: true, state };
|
|
38
|
+
}
|
|
39
|
+
if (msg._baseV < cachedV) return { ok: false, reason: "stale" };
|
|
40
|
+
if (msg._baseV > cachedV) return { ok: false, reason: "behind" };
|
|
41
|
+
if (!cached) return { ok: false, reason: "behind" };
|
|
42
|
+
try {
|
|
43
|
+
const clone = structuredClone(cached);
|
|
44
|
+
(0, import_fast_json_patch.applyPatch)(clone, msg._patch, true);
|
|
45
|
+
clone._v = msg._v;
|
|
46
|
+
return { ok: true, state: clone };
|
|
47
|
+
} catch {
|
|
48
|
+
return { ok: false, reason: "patch-failed" };
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// src/broadcast.ts
|
|
53
|
+
var import_fast_json_patch2 = require("fast-json-patch");
|
|
54
|
+
|
|
55
|
+
// src/state-cache.ts
|
|
56
|
+
var StateCache = class {
|
|
57
|
+
cache = /* @__PURE__ */ new Map();
|
|
58
|
+
maxSize;
|
|
59
|
+
constructor(maxSize = 50) {
|
|
60
|
+
this.maxSize = maxSize;
|
|
61
|
+
}
|
|
62
|
+
/** Get a cached state, promoting it to MRU position. */
|
|
63
|
+
get(key) {
|
|
64
|
+
const s = this.cache.get(key);
|
|
65
|
+
if (s) {
|
|
66
|
+
this.cache.delete(key);
|
|
67
|
+
this.cache.set(key, s);
|
|
68
|
+
}
|
|
69
|
+
return s;
|
|
70
|
+
}
|
|
71
|
+
/** Set a cached state, evicting the LRU entry if at capacity. */
|
|
72
|
+
set(key, state) {
|
|
73
|
+
this.cache.delete(key);
|
|
74
|
+
this.cache.set(key, state);
|
|
75
|
+
if (this.cache.size > this.maxSize) {
|
|
76
|
+
this.cache.delete(this.cache.keys().next().value);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
/** Remove a cached entry. */
|
|
80
|
+
delete(key) {
|
|
81
|
+
this.cache.delete(key);
|
|
82
|
+
}
|
|
83
|
+
/** Check if a key exists in the cache. */
|
|
84
|
+
has(key) {
|
|
85
|
+
return this.cache.has(key);
|
|
86
|
+
}
|
|
87
|
+
/** Current number of cached entries. */
|
|
88
|
+
get size() {
|
|
89
|
+
return this.cache.size;
|
|
90
|
+
}
|
|
91
|
+
/** Direct access to the underlying map (for iteration). */
|
|
92
|
+
entries() {
|
|
93
|
+
return this.cache.entries();
|
|
94
|
+
}
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
// src/broadcast.ts
|
|
98
|
+
var DEFAULT_MAX_PATCH_OPS = 50;
|
|
99
|
+
var BroadcastChannel = class {
|
|
100
|
+
channels = /* @__PURE__ */ new Map();
|
|
101
|
+
stateCache;
|
|
102
|
+
maxPatchOps;
|
|
103
|
+
constructor(options) {
|
|
104
|
+
this.stateCache = new StateCache(options?.cacheSize ?? 50);
|
|
105
|
+
this.maxPatchOps = options?.maxPatchOps ?? DEFAULT_MAX_PATCH_OPS;
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Publish new state for a stream. Computes a patch against the previously
|
|
109
|
+
* cached state and pushes to all subscribers.
|
|
110
|
+
*
|
|
111
|
+
* @param streamId - The event store stream ID
|
|
112
|
+
* @param state - Full state with `_v` set from `snap.event.version`
|
|
113
|
+
* @returns The broadcast message that was sent (or undefined if no subscribers and no cache change)
|
|
114
|
+
*/
|
|
115
|
+
publish(streamId, state) {
|
|
116
|
+
const prev = this.stateCache.get(streamId);
|
|
117
|
+
this.stateCache.set(streamId, state);
|
|
118
|
+
const msg = this.computeMessage(prev, state);
|
|
119
|
+
const subs = this.channels.get(streamId);
|
|
120
|
+
if (subs?.size) {
|
|
121
|
+
for (const cb of subs) cb(msg);
|
|
122
|
+
}
|
|
123
|
+
return msg;
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Publish a state update that doesn't change the event version
|
|
127
|
+
* (e.g. presence overlay, computed field refresh).
|
|
128
|
+
* Uses the same version as the cached state for _baseV and _v.
|
|
129
|
+
*/
|
|
130
|
+
publishOverlay(streamId, state) {
|
|
131
|
+
const prev = this.stateCache.get(streamId);
|
|
132
|
+
if (!prev) return void 0;
|
|
133
|
+
this.stateCache.set(streamId, state);
|
|
134
|
+
const msg = this.computeMessage(prev, state);
|
|
135
|
+
const subs = this.channels.get(streamId);
|
|
136
|
+
if (subs?.size) {
|
|
137
|
+
for (const cb of subs) cb(msg);
|
|
138
|
+
}
|
|
139
|
+
return msg;
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* Subscribe to broadcast messages for a stream.
|
|
143
|
+
* Returns a cleanup function that removes the subscription.
|
|
144
|
+
*/
|
|
145
|
+
subscribe(streamId, cb) {
|
|
146
|
+
if (!this.channels.has(streamId)) this.channels.set(streamId, /* @__PURE__ */ new Set());
|
|
147
|
+
this.channels.get(streamId).add(cb);
|
|
148
|
+
return () => {
|
|
149
|
+
this.channels.get(streamId)?.delete(cb);
|
|
150
|
+
if (this.channels.get(streamId)?.size === 0) {
|
|
151
|
+
this.channels.delete(streamId);
|
|
152
|
+
}
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
/** Get the number of subscribers for a stream. */
|
|
156
|
+
getSubscriberCount(streamId) {
|
|
157
|
+
return this.channels.get(streamId)?.size ?? 0;
|
|
158
|
+
}
|
|
159
|
+
/** Get the cached state for a stream (for reconnects / initial SSE yield). */
|
|
160
|
+
getState(streamId) {
|
|
161
|
+
return this.stateCache.get(streamId);
|
|
162
|
+
}
|
|
163
|
+
/** Direct access to the state cache (for app-specific reads like presence). */
|
|
164
|
+
get cache() {
|
|
165
|
+
return this.stateCache;
|
|
166
|
+
}
|
|
167
|
+
// --- internals ---
|
|
168
|
+
computeMessage(prev, next) {
|
|
169
|
+
const serverTime = (/* @__PURE__ */ new Date()).toISOString();
|
|
170
|
+
if (!prev) {
|
|
171
|
+
return { _type: "full", ...next, serverTime };
|
|
172
|
+
}
|
|
173
|
+
const ops = (0, import_fast_json_patch2.compare)(prev, next);
|
|
174
|
+
if (ops.length === 0) {
|
|
175
|
+
return { _type: "full", ...next, serverTime };
|
|
176
|
+
}
|
|
177
|
+
if (ops.length > this.maxPatchOps) {
|
|
178
|
+
return { _type: "full", ...next, serverTime };
|
|
179
|
+
}
|
|
180
|
+
return {
|
|
181
|
+
_type: "patch",
|
|
182
|
+
_v: next._v,
|
|
183
|
+
_baseV: prev._v,
|
|
184
|
+
_patch: ops,
|
|
185
|
+
serverTime
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
};
|
|
189
|
+
|
|
190
|
+
// src/presence.ts
|
|
191
|
+
var PresenceTracker = class {
|
|
192
|
+
streams = /* @__PURE__ */ new Map();
|
|
193
|
+
/** Increment ref count for an identity on a stream. */
|
|
194
|
+
add(streamId, identityId) {
|
|
195
|
+
if (!this.streams.has(streamId)) this.streams.set(streamId, /* @__PURE__ */ new Map());
|
|
196
|
+
const counts = this.streams.get(streamId);
|
|
197
|
+
counts.set(identityId, (counts.get(identityId) ?? 0) + 1);
|
|
198
|
+
}
|
|
199
|
+
/** Decrement ref count. Removes the identity when count reaches 0. */
|
|
200
|
+
remove(streamId, identityId) {
|
|
201
|
+
const counts = this.streams.get(streamId);
|
|
202
|
+
if (!counts) return;
|
|
203
|
+
const n = (counts.get(identityId) ?? 1) - 1;
|
|
204
|
+
if (n <= 0) counts.delete(identityId);
|
|
205
|
+
else counts.set(identityId, n);
|
|
206
|
+
if (counts.size === 0) this.streams.delete(streamId);
|
|
207
|
+
}
|
|
208
|
+
/** Get the set of online identity IDs for a stream. */
|
|
209
|
+
getOnline(streamId) {
|
|
210
|
+
const counts = this.streams.get(streamId);
|
|
211
|
+
return counts ? new Set(counts.keys()) : /* @__PURE__ */ new Set();
|
|
212
|
+
}
|
|
213
|
+
/** Check if a specific identity is online for a stream. */
|
|
214
|
+
isOnline(streamId, identityId) {
|
|
215
|
+
return (this.streams.get(streamId)?.get(identityId) ?? 0) > 0;
|
|
216
|
+
}
|
|
217
|
+
};
|
|
218
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
219
|
+
0 && (module.exports = {
|
|
220
|
+
BroadcastChannel,
|
|
221
|
+
PresenceTracker,
|
|
222
|
+
StateCache,
|
|
223
|
+
applyBroadcastMessage
|
|
224
|
+
});
|
|
225
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/apply-patch.ts","../src/broadcast.ts","../src/state-cache.ts","../src/presence.ts"],"sourcesContent":["/**\n * @packageDocumentation\n * @module act-sse\n *\n * Incremental state broadcast over SSE for act event-sourced apps.\n *\n * Provides server-side broadcast with automatic RFC 6902 JSON Patch\n * computation, an LRU state cache, presence tracking, and a client-side\n * patch applicator with version validation and resync detection.\n *\n * ## Architecture\n *\n * ```\n * app.do() → snap\n * │\n * ▼\n * deriveState(snap) ← app-specific (overlay presence, deadlines, etc.)\n * state._v = snap.event.version\n * │\n * ▼\n * broadcast.publish(streamId, state)\n * │\n * ├── compare(prev, state) → RFC 6902 ops\n * ├── if ops ≤ threshold → PatchMessage { _baseV, _v, _patch }\n * ├── if ops > threshold → FullStateMessage { _v, ...state }\n * └── push to all SSE subscribers\n * │\n * ▼\n * Client: applyBroadcastMessage(msg, cached)\n * │\n * ├── full → accept if _v ≥ cachedV\n * ├── patch → apply if _baseV === cachedV\n * ├── stale → skip (_baseV < cachedV, mutation response arrived first)\n * └── behind → resync (_baseV > cachedV, client missed a version)\n * ```\n *\n * ## Version Contract\n *\n * `_v` is always the event store stream version (`snap.event.version`).\n * No separate version counters. The event store is the single source of truth.\n */\n\nexport { applyBroadcastMessage } from \"./apply-patch.js\";\nexport type { ApplyResult } from \"./apply-patch.js\";\nexport { BroadcastChannel } from \"./broadcast.js\";\nexport { PresenceTracker } from \"./presence.js\";\nexport { StateCache } from \"./state-cache.js\";\nexport type {\n BroadcastMessage,\n BroadcastOptions,\n BroadcastState,\n FullStateMessage,\n PatchMessage,\n Subscriber,\n} from \"./types.js\";\n","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":["import_fast_json_patch"]}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
// src/apply-patch.ts
|
|
2
|
+
import { applyPatch } from "fast-json-patch";
|
|
3
|
+
function applyBroadcastMessage(msg, cached) {
|
|
4
|
+
const cachedV = cached?._v ?? 0;
|
|
5
|
+
if (msg._type === "full") {
|
|
6
|
+
if (msg._v < cachedV) return { ok: false, reason: "stale" };
|
|
7
|
+
const { _type, ...state } = msg;
|
|
8
|
+
return { ok: true, state };
|
|
9
|
+
}
|
|
10
|
+
if (msg._baseV < cachedV) return { ok: false, reason: "stale" };
|
|
11
|
+
if (msg._baseV > cachedV) return { ok: false, reason: "behind" };
|
|
12
|
+
if (!cached) return { ok: false, reason: "behind" };
|
|
13
|
+
try {
|
|
14
|
+
const clone = structuredClone(cached);
|
|
15
|
+
applyPatch(clone, msg._patch, true);
|
|
16
|
+
clone._v = msg._v;
|
|
17
|
+
return { ok: true, state: clone };
|
|
18
|
+
} catch {
|
|
19
|
+
return { ok: false, reason: "patch-failed" };
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// src/broadcast.ts
|
|
24
|
+
import { compare } from "fast-json-patch";
|
|
25
|
+
|
|
26
|
+
// src/state-cache.ts
|
|
27
|
+
var StateCache = class {
|
|
28
|
+
cache = /* @__PURE__ */ new Map();
|
|
29
|
+
maxSize;
|
|
30
|
+
constructor(maxSize = 50) {
|
|
31
|
+
this.maxSize = maxSize;
|
|
32
|
+
}
|
|
33
|
+
/** Get a cached state, promoting it to MRU position. */
|
|
34
|
+
get(key) {
|
|
35
|
+
const s = this.cache.get(key);
|
|
36
|
+
if (s) {
|
|
37
|
+
this.cache.delete(key);
|
|
38
|
+
this.cache.set(key, s);
|
|
39
|
+
}
|
|
40
|
+
return s;
|
|
41
|
+
}
|
|
42
|
+
/** Set a cached state, evicting the LRU entry if at capacity. */
|
|
43
|
+
set(key, state) {
|
|
44
|
+
this.cache.delete(key);
|
|
45
|
+
this.cache.set(key, state);
|
|
46
|
+
if (this.cache.size > this.maxSize) {
|
|
47
|
+
this.cache.delete(this.cache.keys().next().value);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
/** Remove a cached entry. */
|
|
51
|
+
delete(key) {
|
|
52
|
+
this.cache.delete(key);
|
|
53
|
+
}
|
|
54
|
+
/** Check if a key exists in the cache. */
|
|
55
|
+
has(key) {
|
|
56
|
+
return this.cache.has(key);
|
|
57
|
+
}
|
|
58
|
+
/** Current number of cached entries. */
|
|
59
|
+
get size() {
|
|
60
|
+
return this.cache.size;
|
|
61
|
+
}
|
|
62
|
+
/** Direct access to the underlying map (for iteration). */
|
|
63
|
+
entries() {
|
|
64
|
+
return this.cache.entries();
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
// src/broadcast.ts
|
|
69
|
+
var DEFAULT_MAX_PATCH_OPS = 50;
|
|
70
|
+
var BroadcastChannel = class {
|
|
71
|
+
channels = /* @__PURE__ */ new Map();
|
|
72
|
+
stateCache;
|
|
73
|
+
maxPatchOps;
|
|
74
|
+
constructor(options) {
|
|
75
|
+
this.stateCache = new StateCache(options?.cacheSize ?? 50);
|
|
76
|
+
this.maxPatchOps = options?.maxPatchOps ?? DEFAULT_MAX_PATCH_OPS;
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Publish new state for a stream. Computes a patch against the previously
|
|
80
|
+
* cached state and pushes to all subscribers.
|
|
81
|
+
*
|
|
82
|
+
* @param streamId - The event store stream ID
|
|
83
|
+
* @param state - Full state with `_v` set from `snap.event.version`
|
|
84
|
+
* @returns The broadcast message that was sent (or undefined if no subscribers and no cache change)
|
|
85
|
+
*/
|
|
86
|
+
publish(streamId, state) {
|
|
87
|
+
const prev = this.stateCache.get(streamId);
|
|
88
|
+
this.stateCache.set(streamId, state);
|
|
89
|
+
const msg = this.computeMessage(prev, state);
|
|
90
|
+
const subs = this.channels.get(streamId);
|
|
91
|
+
if (subs?.size) {
|
|
92
|
+
for (const cb of subs) cb(msg);
|
|
93
|
+
}
|
|
94
|
+
return msg;
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Publish a state update that doesn't change the event version
|
|
98
|
+
* (e.g. presence overlay, computed field refresh).
|
|
99
|
+
* Uses the same version as the cached state for _baseV and _v.
|
|
100
|
+
*/
|
|
101
|
+
publishOverlay(streamId, state) {
|
|
102
|
+
const prev = this.stateCache.get(streamId);
|
|
103
|
+
if (!prev) return void 0;
|
|
104
|
+
this.stateCache.set(streamId, state);
|
|
105
|
+
const msg = this.computeMessage(prev, state);
|
|
106
|
+
const subs = this.channels.get(streamId);
|
|
107
|
+
if (subs?.size) {
|
|
108
|
+
for (const cb of subs) cb(msg);
|
|
109
|
+
}
|
|
110
|
+
return msg;
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Subscribe to broadcast messages for a stream.
|
|
114
|
+
* Returns a cleanup function that removes the subscription.
|
|
115
|
+
*/
|
|
116
|
+
subscribe(streamId, cb) {
|
|
117
|
+
if (!this.channels.has(streamId)) this.channels.set(streamId, /* @__PURE__ */ new Set());
|
|
118
|
+
this.channels.get(streamId).add(cb);
|
|
119
|
+
return () => {
|
|
120
|
+
this.channels.get(streamId)?.delete(cb);
|
|
121
|
+
if (this.channels.get(streamId)?.size === 0) {
|
|
122
|
+
this.channels.delete(streamId);
|
|
123
|
+
}
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
/** Get the number of subscribers for a stream. */
|
|
127
|
+
getSubscriberCount(streamId) {
|
|
128
|
+
return this.channels.get(streamId)?.size ?? 0;
|
|
129
|
+
}
|
|
130
|
+
/** Get the cached state for a stream (for reconnects / initial SSE yield). */
|
|
131
|
+
getState(streamId) {
|
|
132
|
+
return this.stateCache.get(streamId);
|
|
133
|
+
}
|
|
134
|
+
/** Direct access to the state cache (for app-specific reads like presence). */
|
|
135
|
+
get cache() {
|
|
136
|
+
return this.stateCache;
|
|
137
|
+
}
|
|
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
|
+
};
|
|
160
|
+
|
|
161
|
+
// src/presence.ts
|
|
162
|
+
var PresenceTracker = class {
|
|
163
|
+
streams = /* @__PURE__ */ new Map();
|
|
164
|
+
/** Increment ref count for an identity on a stream. */
|
|
165
|
+
add(streamId, identityId) {
|
|
166
|
+
if (!this.streams.has(streamId)) this.streams.set(streamId, /* @__PURE__ */ new Map());
|
|
167
|
+
const counts = this.streams.get(streamId);
|
|
168
|
+
counts.set(identityId, (counts.get(identityId) ?? 0) + 1);
|
|
169
|
+
}
|
|
170
|
+
/** Decrement ref count. Removes the identity when count reaches 0. */
|
|
171
|
+
remove(streamId, identityId) {
|
|
172
|
+
const counts = this.streams.get(streamId);
|
|
173
|
+
if (!counts) return;
|
|
174
|
+
const n = (counts.get(identityId) ?? 1) - 1;
|
|
175
|
+
if (n <= 0) counts.delete(identityId);
|
|
176
|
+
else counts.set(identityId, n);
|
|
177
|
+
if (counts.size === 0) this.streams.delete(streamId);
|
|
178
|
+
}
|
|
179
|
+
/** Get the set of online identity IDs for a stream. */
|
|
180
|
+
getOnline(streamId) {
|
|
181
|
+
const counts = this.streams.get(streamId);
|
|
182
|
+
return counts ? new Set(counts.keys()) : /* @__PURE__ */ new Set();
|
|
183
|
+
}
|
|
184
|
+
/** Check if a specific identity is online for a stream. */
|
|
185
|
+
isOnline(streamId, identityId) {
|
|
186
|
+
return (this.streams.get(streamId)?.get(identityId) ?? 0) > 0;
|
|
187
|
+
}
|
|
188
|
+
};
|
|
189
|
+
export {
|
|
190
|
+
BroadcastChannel,
|
|
191
|
+
PresenceTracker,
|
|
192
|
+
StateCache,
|
|
193
|
+
applyBroadcastMessage
|
|
194
|
+
};
|
|
195
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +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":[]}
|
package/package.json
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@rotorsoft/act-sse",
|
|
3
|
+
"type": "module",
|
|
4
|
+
"version": "1.0.0",
|
|
5
|
+
"description": "Incremental state broadcast over SSE for act apps",
|
|
6
|
+
"author": "rotorsoft",
|
|
7
|
+
"license": "MIT",
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "git+https://github.com/rotorsoft/act-root.git",
|
|
11
|
+
"directory": "libs/act-sse"
|
|
12
|
+
},
|
|
13
|
+
"files": [
|
|
14
|
+
"dist"
|
|
15
|
+
],
|
|
16
|
+
"main": "./dist/index.cjs",
|
|
17
|
+
"module": "./dist/index.js",
|
|
18
|
+
"types": "./dist/@types/index.d.ts",
|
|
19
|
+
"exports": {
|
|
20
|
+
".": {
|
|
21
|
+
"types": "./dist/@types/index.d.ts",
|
|
22
|
+
"import": "./dist/index.js",
|
|
23
|
+
"require": "./dist/index.cjs"
|
|
24
|
+
}
|
|
25
|
+
},
|
|
26
|
+
"sideEffects": false,
|
|
27
|
+
"engines": {
|
|
28
|
+
"node": ">=22.18.0"
|
|
29
|
+
},
|
|
30
|
+
"publishConfig": {
|
|
31
|
+
"access": "public"
|
|
32
|
+
},
|
|
33
|
+
"dependencies": {
|
|
34
|
+
"fast-json-patch": "^3.1.1"
|
|
35
|
+
},
|
|
36
|
+
"scripts": {
|
|
37
|
+
"clean": "rm -rf dist",
|
|
38
|
+
"types": "tsc --build tsconfig.build.json --emitDeclarationOnly",
|
|
39
|
+
"build": "pnpm clean && tsup && pnpm types"
|
|
40
|
+
}
|
|
41
|
+
}
|