@parlor/sync 0.0.0-stage → 0.2.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 +10 -2
- package/dist/channel.d.ts +21 -0
- package/dist/channel.d.ts.map +1 -0
- package/dist/channel.js +2 -0
- package/dist/channel.js.map +1 -0
- package/dist/client/index.d.ts +3 -0
- package/dist/client/index.d.ts.map +1 -0
- package/dist/client/index.js +3 -0
- package/dist/client/index.js.map +1 -0
- package/dist/client/sse.d.ts +10 -0
- package/dist/client/sse.d.ts.map +1 -0
- package/dist/client/sse.js +48 -0
- package/dist/client/sse.js.map +1 -0
- package/dist/client/sync-client.d.ts +84 -0
- package/dist/client/sync-client.d.ts.map +1 -0
- package/dist/client/sync-client.js +459 -0
- package/dist/client/sync-client.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -0
- package/dist/protocol.d.ts +39 -0
- package/dist/protocol.d.ts.map +1 -0
- package/dist/protocol.js +30 -0
- package/dist/protocol.js.map +1 -0
- package/dist/server/hub.d.ts +15 -0
- package/dist/server/hub.d.ts.map +1 -0
- package/dist/server/hub.js +75 -0
- package/dist/server/hub.js.map +1 -0
- package/dist/server/index.d.ts +5 -0
- package/dist/server/index.d.ts.map +1 -0
- package/dist/server/index.js +5 -0
- package/dist/server/index.js.map +1 -0
- package/dist/server/pruner.d.ts +11 -0
- package/dist/server/pruner.d.ts.map +1 -0
- package/dist/server/pruner.js +23 -0
- package/dist/server/pruner.js.map +1 -0
- package/dist/server/routes.d.ts +36 -0
- package/dist/server/routes.d.ts.map +1 -0
- package/dist/server/routes.js +127 -0
- package/dist/server/routes.js.map +1 -0
- package/dist/server/store.d.ts +42 -0
- package/dist/server/store.d.ts.map +1 -0
- package/dist/server/store.js +155 -0
- package/dist/server/store.js.map +1 -0
- package/package.json +43 -3
|
@@ -0,0 +1,459 @@
|
|
|
1
|
+
import { compareCursors, cursorOf, decodeCursor, encodeCursor, INITIAL_CURSOR, LEGACY_KEEP_ALIVE_MS, streamLivenessMs, } from "../protocol.js";
|
|
2
|
+
import { readSse } from "./sse.js";
|
|
3
|
+
export class SyncUnauthorized extends Error {
|
|
4
|
+
status;
|
|
5
|
+
name = "SyncUnauthorized";
|
|
6
|
+
constructor(status) {
|
|
7
|
+
super(`sync was refused with HTTP ${status}`);
|
|
8
|
+
this.status = status;
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
export class SyncRequestTimeout extends Error {
|
|
12
|
+
ms;
|
|
13
|
+
name = "SyncRequestTimeout";
|
|
14
|
+
constructor(ms) {
|
|
15
|
+
super(`sync request timed out after ${ms} ms`);
|
|
16
|
+
this.ms = ms;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
class StaleRequest extends Error {
|
|
20
|
+
name = "StaleRequest";
|
|
21
|
+
}
|
|
22
|
+
const refused = (response) => response.status === 401 || response.status === 403;
|
|
23
|
+
const DEFAULTS = {
|
|
24
|
+
coalesceMs: 250,
|
|
25
|
+
safetyPollMs: 60_000,
|
|
26
|
+
hiddenPollMs: 10_000,
|
|
27
|
+
reconnectBaseMs: 1000,
|
|
28
|
+
reconnectMaxMs: 30_000,
|
|
29
|
+
requestTimeoutMs: 10_000,
|
|
30
|
+
};
|
|
31
|
+
function parseHandshake(handshake) {
|
|
32
|
+
try {
|
|
33
|
+
const { capabilities, keepAliveMs } = JSON.parse(handshake);
|
|
34
|
+
return {
|
|
35
|
+
capabilities: Array.isArray(capabilities)
|
|
36
|
+
? capabilities.filter((c) => typeof c === "string")
|
|
37
|
+
: [],
|
|
38
|
+
keepAliveMs: typeof keepAliveMs === "number" && keepAliveMs > 0 ? keepAliveMs : undefined,
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
catch {
|
|
42
|
+
return { capabilities: [], keepAliveMs: undefined };
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
export function createSyncClient(options) {
|
|
46
|
+
const basePath = options.basePath ?? "/_parlor";
|
|
47
|
+
const settings = {
|
|
48
|
+
coalesceMs: options.coalesceMs ?? DEFAULTS.coalesceMs,
|
|
49
|
+
safetyPollMs: options.safetyPollMs ?? DEFAULTS.safetyPollMs,
|
|
50
|
+
hiddenPollMs: options.hiddenPollMs ?? DEFAULTS.hiddenPollMs,
|
|
51
|
+
reconnectBaseMs: options.reconnectBaseMs ?? DEFAULTS.reconnectBaseMs,
|
|
52
|
+
reconnectMaxMs: options.reconnectMaxMs ?? DEFAULTS.reconnectMaxMs,
|
|
53
|
+
requestTimeoutMs: options.requestTimeoutMs ?? DEFAULTS.requestTimeoutMs,
|
|
54
|
+
clientId: options.clientId,
|
|
55
|
+
onError: options.onError,
|
|
56
|
+
};
|
|
57
|
+
const schedule = options.setTimeout ?? ((fn, ms) => globalThis.setTimeout(fn, ms));
|
|
58
|
+
const cancel = options.clearTimeout ?? ((id) => globalThis.clearTimeout(id));
|
|
59
|
+
const isHidden = options.isHidden ?? (() => false);
|
|
60
|
+
const channels = new Map((options.channels ?? []).map((c) => [c.event, c]));
|
|
61
|
+
const guarded = (run) => {
|
|
62
|
+
try {
|
|
63
|
+
run();
|
|
64
|
+
}
|
|
65
|
+
catch (error) {
|
|
66
|
+
settings.onError?.(error);
|
|
67
|
+
}
|
|
68
|
+
};
|
|
69
|
+
let cursor = INITIAL_CURSOR;
|
|
70
|
+
let transport;
|
|
71
|
+
let negotiated;
|
|
72
|
+
let interrupted = false;
|
|
73
|
+
let running = false;
|
|
74
|
+
let controller;
|
|
75
|
+
let pollTimer;
|
|
76
|
+
let flushTimer;
|
|
77
|
+
let pending = [];
|
|
78
|
+
let reconnectDelay = settings.reconnectBaseMs;
|
|
79
|
+
let endReconnectWait;
|
|
80
|
+
let connectTimer;
|
|
81
|
+
let epoch = 0;
|
|
82
|
+
const inflight = new Set();
|
|
83
|
+
const withTimeout = async (run) => {
|
|
84
|
+
const mine = epoch;
|
|
85
|
+
const own = new AbortController();
|
|
86
|
+
inflight.add(own);
|
|
87
|
+
let timer;
|
|
88
|
+
const expired = new Promise((_, reject) => {
|
|
89
|
+
timer = schedule(() => {
|
|
90
|
+
own.abort();
|
|
91
|
+
reject(new SyncRequestTimeout(settings.requestTimeoutMs));
|
|
92
|
+
}, settings.requestTimeoutMs);
|
|
93
|
+
});
|
|
94
|
+
try {
|
|
95
|
+
const result = await Promise.race([run(own.signal), expired]);
|
|
96
|
+
if (mine !== epoch)
|
|
97
|
+
throw new StaleRequest();
|
|
98
|
+
return result;
|
|
99
|
+
}
|
|
100
|
+
catch (error) {
|
|
101
|
+
if (mine !== epoch)
|
|
102
|
+
throw new StaleRequest();
|
|
103
|
+
throw error;
|
|
104
|
+
}
|
|
105
|
+
finally {
|
|
106
|
+
inflight.delete(own);
|
|
107
|
+
if (timer !== undefined)
|
|
108
|
+
cancel(timer);
|
|
109
|
+
}
|
|
110
|
+
};
|
|
111
|
+
const resumed = () => {
|
|
112
|
+
if (!interrupted)
|
|
113
|
+
return;
|
|
114
|
+
interrupted = false;
|
|
115
|
+
guarded(() => options.onResume?.());
|
|
116
|
+
};
|
|
117
|
+
const accept = (events) => {
|
|
118
|
+
for (const event of events) {
|
|
119
|
+
const at = cursorOf(event);
|
|
120
|
+
if (compareCursors(at, cursor) <= 0)
|
|
121
|
+
continue;
|
|
122
|
+
cursor = at;
|
|
123
|
+
if (event.source && event.source === settings.clientId)
|
|
124
|
+
continue;
|
|
125
|
+
pending.push(event);
|
|
126
|
+
}
|
|
127
|
+
if (pending.length > 0 && flushTimer === undefined) {
|
|
128
|
+
flushTimer = schedule(() => {
|
|
129
|
+
flushTimer = undefined;
|
|
130
|
+
const batch = pending;
|
|
131
|
+
pending = [];
|
|
132
|
+
options.onEvents(batch);
|
|
133
|
+
}, settings.coalesceMs);
|
|
134
|
+
}
|
|
135
|
+
};
|
|
136
|
+
const held = () => (cursor === INITIAL_CURSOR ? "" : encodeCursor(cursor));
|
|
137
|
+
const since = () => cursor === INITIAL_CURSOR ? (options.startAt?.() ?? "") : encodeCursor(cursor);
|
|
138
|
+
const missedSince = () => `since=${encodeURIComponent(since())}${options.onReset ? "&reset=1" : ""}`;
|
|
139
|
+
const take = (batch) => {
|
|
140
|
+
if (!batch.reset) {
|
|
141
|
+
accept(batch.events);
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
cursor = decodeCursor(batch.cursor);
|
|
145
|
+
guarded(() => options.onReset?.(batch.cursor));
|
|
146
|
+
};
|
|
147
|
+
const poll = async (mine) => {
|
|
148
|
+
const batch = await withTimeout(async (signal) => {
|
|
149
|
+
const response = await options.request(`${basePath}/sync?${missedSince()}`, {
|
|
150
|
+
signal,
|
|
151
|
+
});
|
|
152
|
+
if (refused(response))
|
|
153
|
+
throw new SyncUnauthorized(response.status);
|
|
154
|
+
if (!response.ok)
|
|
155
|
+
throw new Error(`sync poll failed with HTTP ${response.status}`);
|
|
156
|
+
return (await response.json());
|
|
157
|
+
});
|
|
158
|
+
if (mine !== epoch)
|
|
159
|
+
throw new StaleRequest();
|
|
160
|
+
take(batch);
|
|
161
|
+
if (running && negotiated === "poll")
|
|
162
|
+
resumed();
|
|
163
|
+
};
|
|
164
|
+
const schedulePoll = (ms) => {
|
|
165
|
+
if (!running)
|
|
166
|
+
return;
|
|
167
|
+
if (pollTimer !== undefined)
|
|
168
|
+
cancel(pollTimer);
|
|
169
|
+
pollTimer = schedule(() => {
|
|
170
|
+
pollTimer = undefined;
|
|
171
|
+
let halted = false;
|
|
172
|
+
const mine = epoch;
|
|
173
|
+
void poll(mine)
|
|
174
|
+
.catch((error) => {
|
|
175
|
+
if (error instanceof StaleRequest || mine !== epoch) {
|
|
176
|
+
halted = true;
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
if (negotiated === "poll")
|
|
180
|
+
interrupted = true;
|
|
181
|
+
if (error instanceof SyncUnauthorized) {
|
|
182
|
+
halted = true;
|
|
183
|
+
unauthorized(error);
|
|
184
|
+
if (transport === "poll") {
|
|
185
|
+
transport = undefined;
|
|
186
|
+
connectTimer = schedule(() => {
|
|
187
|
+
connectTimer = undefined;
|
|
188
|
+
void connect();
|
|
189
|
+
}, backoff());
|
|
190
|
+
}
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
193
|
+
settings.onError?.(error);
|
|
194
|
+
})
|
|
195
|
+
.finally(() => {
|
|
196
|
+
if (!halted)
|
|
197
|
+
schedulePoll(nextPollDelay());
|
|
198
|
+
});
|
|
199
|
+
}, ms);
|
|
200
|
+
};
|
|
201
|
+
const nextPollDelay = () => {
|
|
202
|
+
if (transport === "sse")
|
|
203
|
+
return settings.safetyPollMs;
|
|
204
|
+
const base = Math.max(negotiatedPollMs, options.pollEveryMs?.() ?? 0);
|
|
205
|
+
return isHidden() ? Math.max(base, settings.hiddenPollMs) : base;
|
|
206
|
+
};
|
|
207
|
+
let negotiatedPollMs = 2000;
|
|
208
|
+
const negotiate = async () => {
|
|
209
|
+
return withTimeout(async (signal) => {
|
|
210
|
+
const response = await options.request(`${basePath}/sync/token`, {
|
|
211
|
+
method: "POST",
|
|
212
|
+
signal,
|
|
213
|
+
});
|
|
214
|
+
if (refused(response))
|
|
215
|
+
throw new SyncUnauthorized(response.status);
|
|
216
|
+
if (!response.ok)
|
|
217
|
+
throw new Error(`sync negotiation failed with HTTP ${response.status}`);
|
|
218
|
+
return (await response.json());
|
|
219
|
+
});
|
|
220
|
+
};
|
|
221
|
+
let live;
|
|
222
|
+
const stream = async (endpoint) => {
|
|
223
|
+
const own = new AbortController();
|
|
224
|
+
controller = own;
|
|
225
|
+
let keepAliveMs = LEGACY_KEEP_ALIVE_MS;
|
|
226
|
+
let silent = false;
|
|
227
|
+
let watchdog;
|
|
228
|
+
const rearm = () => {
|
|
229
|
+
if (watchdog !== undefined)
|
|
230
|
+
cancel(watchdog);
|
|
231
|
+
watchdog = schedule(() => {
|
|
232
|
+
silent = true;
|
|
233
|
+
own.abort();
|
|
234
|
+
}, streamLivenessMs(keepAliveMs));
|
|
235
|
+
};
|
|
236
|
+
const open = new Set();
|
|
237
|
+
const current = { listed: new Set(), open };
|
|
238
|
+
live = current;
|
|
239
|
+
rearm();
|
|
240
|
+
try {
|
|
241
|
+
const response = await options.request(`${endpoint}?${missedSince()}`, {
|
|
242
|
+
headers: { accept: "text/event-stream" },
|
|
243
|
+
signal: own.signal,
|
|
244
|
+
});
|
|
245
|
+
if (refused(response))
|
|
246
|
+
throw new SyncUnauthorized(response.status);
|
|
247
|
+
if (!response.ok || !response.body) {
|
|
248
|
+
throw new Error(`sync stream failed with HTTP ${response.status}`);
|
|
249
|
+
}
|
|
250
|
+
reconnectDelay = settings.reconnectBaseMs;
|
|
251
|
+
resumed();
|
|
252
|
+
await readSse(response.body, (frame) => {
|
|
253
|
+
if (frame.event === "message") {
|
|
254
|
+
const batch = JSON.parse(frame.data);
|
|
255
|
+
take(batch);
|
|
256
|
+
const parsed = decodeCursor(batch.cursor);
|
|
257
|
+
if (compareCursors(parsed, cursor) > 0)
|
|
258
|
+
cursor = parsed;
|
|
259
|
+
return;
|
|
260
|
+
}
|
|
261
|
+
if (frame.event === "handshake") {
|
|
262
|
+
const handshake = parseHandshake(frame.data);
|
|
263
|
+
keepAliveMs = handshake.keepAliveMs ?? LEGACY_KEEP_ALIVE_MS;
|
|
264
|
+
rearm();
|
|
265
|
+
current.listed = new Set(handshake.capabilities);
|
|
266
|
+
for (const event of handshake.capabilities) {
|
|
267
|
+
const channel = channels.get(event);
|
|
268
|
+
if (!channel || open.has(channel))
|
|
269
|
+
continue;
|
|
270
|
+
open.add(channel);
|
|
271
|
+
guarded(() => channel.opened());
|
|
272
|
+
}
|
|
273
|
+
return;
|
|
274
|
+
}
|
|
275
|
+
const channel = channels.get(frame.event);
|
|
276
|
+
if (channel && open.has(channel))
|
|
277
|
+
guarded(() => channel.receive(frame.data));
|
|
278
|
+
}, own.signal, rearm);
|
|
279
|
+
}
|
|
280
|
+
catch (error) {
|
|
281
|
+
if (!silent)
|
|
282
|
+
throw error;
|
|
283
|
+
}
|
|
284
|
+
finally {
|
|
285
|
+
if (watchdog !== undefined)
|
|
286
|
+
cancel(watchdog);
|
|
287
|
+
if (live === current)
|
|
288
|
+
live = undefined;
|
|
289
|
+
for (const channel of open)
|
|
290
|
+
guarded(() => channel.closed());
|
|
291
|
+
}
|
|
292
|
+
};
|
|
293
|
+
const unauthorized = (error) => {
|
|
294
|
+
if (pollTimer !== undefined)
|
|
295
|
+
cancel(pollTimer);
|
|
296
|
+
pollTimer = undefined;
|
|
297
|
+
(options.onUnauthorized ?? settings.onError)?.(error);
|
|
298
|
+
};
|
|
299
|
+
const backoff = () => {
|
|
300
|
+
const delay = reconnectDelay;
|
|
301
|
+
reconnectDelay = Math.min(reconnectDelay * 2, settings.reconnectMaxMs);
|
|
302
|
+
return delay;
|
|
303
|
+
};
|
|
304
|
+
let connecting = false;
|
|
305
|
+
const connect = async () => {
|
|
306
|
+
if (connecting)
|
|
307
|
+
return;
|
|
308
|
+
connecting = true;
|
|
309
|
+
const mine = epoch;
|
|
310
|
+
try {
|
|
311
|
+
await connectLoop(mine);
|
|
312
|
+
}
|
|
313
|
+
finally {
|
|
314
|
+
if (mine === epoch)
|
|
315
|
+
connecting = false;
|
|
316
|
+
}
|
|
317
|
+
};
|
|
318
|
+
const connectLoop = async (mine) => {
|
|
319
|
+
const active = () => running && mine === epoch;
|
|
320
|
+
for (;;) {
|
|
321
|
+
if (!active())
|
|
322
|
+
return;
|
|
323
|
+
let endpoint;
|
|
324
|
+
try {
|
|
325
|
+
const negotiation = await negotiate();
|
|
326
|
+
if (!active())
|
|
327
|
+
return;
|
|
328
|
+
negotiatedPollMs = negotiation.pollIntervalMs;
|
|
329
|
+
negotiated = negotiation.transport;
|
|
330
|
+
guarded(() => options.onNegotiated?.(negotiation.transport));
|
|
331
|
+
if (negotiation.transport === "poll") {
|
|
332
|
+
transport = "poll";
|
|
333
|
+
schedulePoll(0);
|
|
334
|
+
return;
|
|
335
|
+
}
|
|
336
|
+
endpoint = negotiation.endpoint;
|
|
337
|
+
}
|
|
338
|
+
catch (error) {
|
|
339
|
+
if (!active())
|
|
340
|
+
return;
|
|
341
|
+
interrupted = true;
|
|
342
|
+
if (error instanceof SyncUnauthorized) {
|
|
343
|
+
transport = undefined;
|
|
344
|
+
unauthorized(error);
|
|
345
|
+
}
|
|
346
|
+
else {
|
|
347
|
+
settings.onError?.(error);
|
|
348
|
+
if (transport !== "poll") {
|
|
349
|
+
transport = "poll";
|
|
350
|
+
schedulePoll(negotiatedPollMs);
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
if (endpoint !== undefined) {
|
|
355
|
+
transport = "sse";
|
|
356
|
+
schedulePoll(settings.safetyPollMs);
|
|
357
|
+
try {
|
|
358
|
+
await stream(endpoint);
|
|
359
|
+
}
|
|
360
|
+
catch (error) {
|
|
361
|
+
if (!active())
|
|
362
|
+
return;
|
|
363
|
+
if (error instanceof SyncUnauthorized)
|
|
364
|
+
unauthorized(error);
|
|
365
|
+
else
|
|
366
|
+
settings.onError?.(error);
|
|
367
|
+
}
|
|
368
|
+
interrupted = true;
|
|
369
|
+
}
|
|
370
|
+
if (!active())
|
|
371
|
+
return;
|
|
372
|
+
const delay = backoff();
|
|
373
|
+
await new Promise((resolve) => {
|
|
374
|
+
const timer = schedule(() => {
|
|
375
|
+
endReconnectWait = undefined;
|
|
376
|
+
resolve();
|
|
377
|
+
}, delay);
|
|
378
|
+
endReconnectWait = () => {
|
|
379
|
+
cancel(timer);
|
|
380
|
+
endReconnectWait = undefined;
|
|
381
|
+
resolve();
|
|
382
|
+
};
|
|
383
|
+
});
|
|
384
|
+
}
|
|
385
|
+
};
|
|
386
|
+
const detachChannel = (channel) => {
|
|
387
|
+
if (channels.get(channel.event) !== channel)
|
|
388
|
+
return;
|
|
389
|
+
channels.delete(channel.event);
|
|
390
|
+
if (live?.open.delete(channel))
|
|
391
|
+
guarded(() => channel.closed());
|
|
392
|
+
};
|
|
393
|
+
const start = () => {
|
|
394
|
+
if (running)
|
|
395
|
+
return;
|
|
396
|
+
running = true;
|
|
397
|
+
void connect();
|
|
398
|
+
};
|
|
399
|
+
const reconnectNow = () => {
|
|
400
|
+
if (!running)
|
|
401
|
+
return;
|
|
402
|
+
if (endReconnectWait) {
|
|
403
|
+
reconnectDelay = settings.reconnectBaseMs;
|
|
404
|
+
endReconnectWait();
|
|
405
|
+
}
|
|
406
|
+
else if (connectTimer !== undefined) {
|
|
407
|
+
cancel(connectTimer);
|
|
408
|
+
connectTimer = undefined;
|
|
409
|
+
reconnectDelay = settings.reconnectBaseMs;
|
|
410
|
+
void connect();
|
|
411
|
+
}
|
|
412
|
+
if (transport === "poll" && pollTimer !== undefined)
|
|
413
|
+
schedulePoll(0);
|
|
414
|
+
};
|
|
415
|
+
const stop = () => {
|
|
416
|
+
running = false;
|
|
417
|
+
epoch += 1;
|
|
418
|
+
for (const request of inflight)
|
|
419
|
+
request.abort();
|
|
420
|
+
inflight.clear();
|
|
421
|
+
controller?.abort();
|
|
422
|
+
controller = undefined;
|
|
423
|
+
connecting = false;
|
|
424
|
+
endReconnectWait?.();
|
|
425
|
+
if (pollTimer !== undefined)
|
|
426
|
+
cancel(pollTimer);
|
|
427
|
+
if (flushTimer !== undefined)
|
|
428
|
+
cancel(flushTimer);
|
|
429
|
+
if (connectTimer !== undefined)
|
|
430
|
+
cancel(connectTimer);
|
|
431
|
+
pollTimer = flushTimer = connectTimer = undefined;
|
|
432
|
+
pending = [];
|
|
433
|
+
};
|
|
434
|
+
return {
|
|
435
|
+
start,
|
|
436
|
+
stop,
|
|
437
|
+
addChannel(channel) {
|
|
438
|
+
const replaced = channels.get(channel.event);
|
|
439
|
+
if (replaced === channel)
|
|
440
|
+
return () => detachChannel(channel);
|
|
441
|
+
if (replaced)
|
|
442
|
+
detachChannel(replaced);
|
|
443
|
+
channels.set(channel.event, channel);
|
|
444
|
+
if (live?.listed.has(channel.event)) {
|
|
445
|
+
live.open.add(channel);
|
|
446
|
+
guarded(() => channel.opened());
|
|
447
|
+
}
|
|
448
|
+
return () => detachChannel(channel);
|
|
449
|
+
},
|
|
450
|
+
reconnectNow,
|
|
451
|
+
get cursor() {
|
|
452
|
+
return held();
|
|
453
|
+
},
|
|
454
|
+
get transport() {
|
|
455
|
+
return transport;
|
|
456
|
+
},
|
|
457
|
+
};
|
|
458
|
+
}
|
|
459
|
+
//# sourceMappingURL=sync-client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sync-client.js","sourceRoot":"","sources":["../../src/client/sync-client.ts"],"names":[],"mappings":"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","sourcesContent":["import type { ClientChannel } from \"../channel.js\";\nimport {\n compareCursors,\n cursorOf,\n decodeCursor,\n encodeCursor,\n INITIAL_CURSOR,\n LEGACY_KEEP_ALIVE_MS,\n type SyncBatch,\n type SyncCursor,\n type SyncEvent,\n type SyncNegotiation,\n streamLivenessMs,\n} from \"../protocol.js\";\nimport { readSse } from \"./sse.js\";\n\nexport type SyncRequest = (path: string, init?: RequestInit) => Promise<Response>;\n\nexport interface SyncClientOptions {\n readonly request: SyncRequest;\n readonly basePath?: string | undefined;\n readonly clientId?: string | undefined;\n readonly onEvents: (events: SyncEvent[]) => void;\n readonly onError?: ((error: unknown) => void) | undefined;\n readonly coalesceMs?: number | undefined;\n readonly safetyPollMs?: number | undefined;\n readonly hiddenPollMs?: number | undefined;\n readonly reconnectBaseMs?: number | undefined;\n readonly reconnectMaxMs?: number | undefined;\n readonly isHidden?: (() => boolean) | undefined;\n readonly setTimeout?: typeof globalThis.setTimeout | undefined;\n readonly clearTimeout?: typeof globalThis.clearTimeout | undefined;\n readonly channels?: readonly ClientChannel[] | undefined;\n /**\n * Read each time the next poll is scheduled; the interval is the larger of this and the\n * negotiated one, so a caller can only slow polling down.\n */\n readonly pollEveryMs?: (() => number) | undefined;\n /**\n * Where to read from while the client has no cursor of its own: the encoded cursor of the\n * oldest data the page holds, so the first stream does not replay what that data already\n * shows. Defaults to the start of the retained log.\n */\n readonly startAt?: (() => string) | undefined;\n /**\n * The server could not replay every event since the client's cursor: the client fell\n * more than one batch behind, or behind what the server keeps. Sync continues from\n * `cursor`, where the client's streams stand now; reread whatever predates it. Without\n * it, the client is never reset and a poll pages through the backlog.\n */\n readonly onReset?: ((cursor: string) => void) | undefined;\n /** Bounds a negotiation or a poll, not the SSE stream. Defaults to 10 s. */\n readonly requestTimeoutMs?: number | undefined;\n /**\n * What the server negotiated, each time it does; `SyncClient.transport` reads `poll`\n * during the fallback poll of a failed negotiation, this never does.\n */\n readonly onNegotiated?: ((transport: \"sse\" | \"poll\") => void) | undefined;\n /**\n * The negotiated transport's connection came back after a failure: a stream reopened\n * after it dropped, or a poll succeeded after a failed one. A fallback poll on an SSE\n * deployment does not count.\n */\n readonly onResume?: (() => void) | undefined;\n /**\n * The server refused the credential (401 or 403). The client never degrades to polling\n * on it; it keeps retrying the negotiation with backoff, unless this calls `stop`.\n * Defaults to `onError`.\n */\n readonly onUnauthorized?: ((error: SyncUnauthorized) => void) | undefined;\n}\n\nexport class SyncUnauthorized extends Error {\n override readonly name = \"SyncUnauthorized\";\n constructor(readonly status: number) {\n super(`sync was refused with HTTP ${status}`);\n }\n}\n\nexport class SyncRequestTimeout extends Error {\n override readonly name = \"SyncRequestTimeout\";\n constructor(readonly ms: number) {\n super(`sync request timed out after ${ms} ms`);\n }\n}\n\nclass StaleRequest extends Error {\n override readonly name = \"StaleRequest\";\n}\n\nconst refused = (response: Response) =>\n response.status === 401 || response.status === 403;\n\nexport interface SyncClient {\n start(): void;\n stop(): void;\n /**\n * Registers a channel after `start`, for features loaded lazily. It opens at once when\n * the live stream's handshake lists it, else on the next stream. Returns the detach.\n */\n addChannel(channel: ClientChannel): () => void;\n /**\n * Skips the remaining reconnect backoff, for `online` and visibility events. While\n * polling it polls at once; with a stream open or a request in flight it does nothing.\n */\n reconnectNow(): void;\n readonly cursor: string;\n readonly transport: \"sse\" | \"poll\" | undefined;\n}\n\nconst DEFAULTS = {\n coalesceMs: 250,\n safetyPollMs: 60_000,\n hiddenPollMs: 10_000,\n reconnectBaseMs: 1000,\n reconnectMaxMs: 30_000,\n requestTimeoutMs: 10_000,\n};\n\ninterface Handshake {\n readonly capabilities: readonly string[];\n readonly keepAliveMs: number | undefined;\n}\n\nfunction parseHandshake(handshake: string): Handshake {\n try {\n const { capabilities, keepAliveMs } = JSON.parse(handshake) as {\n capabilities?: unknown;\n keepAliveMs?: unknown;\n };\n return {\n capabilities: Array.isArray(capabilities)\n ? capabilities.filter((c): c is string => typeof c === \"string\")\n : [],\n keepAliveMs:\n typeof keepAliveMs === \"number\" && keepAliveMs > 0 ? keepAliveMs : undefined,\n };\n } catch {\n return { capabilities: [], keepAliveMs: undefined };\n }\n}\n\nexport function createSyncClient(options: SyncClientOptions): SyncClient {\n const basePath = options.basePath ?? \"/_parlor\";\n const settings = {\n coalesceMs: options.coalesceMs ?? DEFAULTS.coalesceMs,\n safetyPollMs: options.safetyPollMs ?? DEFAULTS.safetyPollMs,\n hiddenPollMs: options.hiddenPollMs ?? DEFAULTS.hiddenPollMs,\n reconnectBaseMs: options.reconnectBaseMs ?? DEFAULTS.reconnectBaseMs,\n reconnectMaxMs: options.reconnectMaxMs ?? DEFAULTS.reconnectMaxMs,\n requestTimeoutMs: options.requestTimeoutMs ?? DEFAULTS.requestTimeoutMs,\n clientId: options.clientId,\n onError: options.onError,\n };\n const schedule: (fn: () => void, ms: number) => ReturnType<typeof setTimeout> =\n options.setTimeout ?? ((fn, ms) => globalThis.setTimeout(fn, ms));\n const cancel: (id: ReturnType<typeof setTimeout>) => void =\n options.clearTimeout ?? ((id) => globalThis.clearTimeout(id));\n const isHidden = options.isHidden ?? (() => false);\n const channels = new Map((options.channels ?? []).map((c) => [c.event, c]));\n const guarded = (run: () => void) => {\n try {\n run();\n } catch (error) {\n settings.onError?.(error);\n }\n };\n\n let cursor: SyncCursor = INITIAL_CURSOR;\n let transport: \"sse\" | \"poll\" | undefined;\n let negotiated: \"sse\" | \"poll\" | undefined;\n let interrupted = false;\n let running = false;\n let controller: AbortController | undefined;\n let pollTimer: ReturnType<typeof setTimeout> | undefined;\n let flushTimer: ReturnType<typeof setTimeout> | undefined;\n let pending: SyncEvent[] = [];\n let reconnectDelay = settings.reconnectBaseMs;\n let endReconnectWait: (() => void) | undefined;\n let connectTimer: ReturnType<typeof setTimeout> | undefined;\n let epoch = 0;\n const inflight = new Set<AbortController>();\n\n const withTimeout = async <T>(run: (signal: AbortSignal) => Promise<T>): Promise<T> => {\n const mine = epoch;\n const own = new AbortController();\n inflight.add(own);\n let timer: ReturnType<typeof setTimeout> | undefined;\n const expired = new Promise<never>((_, reject) => {\n timer = schedule(() => {\n own.abort();\n reject(new SyncRequestTimeout(settings.requestTimeoutMs));\n }, settings.requestTimeoutMs);\n });\n try {\n const result = await Promise.race([run(own.signal), expired]);\n if (mine !== epoch) throw new StaleRequest();\n return result;\n } catch (error) {\n if (mine !== epoch) throw new StaleRequest();\n throw error;\n } finally {\n inflight.delete(own);\n if (timer !== undefined) cancel(timer);\n }\n };\n\n const resumed = () => {\n if (!interrupted) return;\n interrupted = false;\n guarded(() => options.onResume?.());\n };\n\n const accept = (events: readonly SyncEvent[]) => {\n for (const event of events) {\n const at = cursorOf(event);\n if (compareCursors(at, cursor) <= 0) continue;\n cursor = at;\n if (event.source && event.source === settings.clientId) continue;\n pending.push(event);\n }\n if (pending.length > 0 && flushTimer === undefined) {\n flushTimer = schedule(() => {\n flushTimer = undefined;\n const batch = pending;\n pending = [];\n options.onEvents(batch);\n }, settings.coalesceMs);\n }\n };\n\n const held = () => (cursor === INITIAL_CURSOR ? \"\" : encodeCursor(cursor));\n const since = () =>\n cursor === INITIAL_CURSOR ? (options.startAt?.() ?? \"\") : encodeCursor(cursor);\n const missedSince = () =>\n `since=${encodeURIComponent(since())}${options.onReset ? \"&reset=1\" : \"\"}`;\n\n const take = (batch: SyncBatch) => {\n if (!batch.reset) {\n accept(batch.events);\n return;\n }\n cursor = decodeCursor(batch.cursor);\n guarded(() => options.onReset?.(batch.cursor));\n };\n\n const poll = async (mine: number) => {\n const batch = await withTimeout(async (signal) => {\n const response = await options.request(`${basePath}/sync?${missedSince()}`, {\n signal,\n });\n if (refused(response)) throw new SyncUnauthorized(response.status);\n if (!response.ok) throw new Error(`sync poll failed with HTTP ${response.status}`);\n return (await response.json()) as SyncBatch;\n });\n if (mine !== epoch) throw new StaleRequest();\n take(batch);\n if (running && negotiated === \"poll\") resumed();\n };\n\n const schedulePoll = (ms: number) => {\n if (!running) return;\n if (pollTimer !== undefined) cancel(pollTimer);\n pollTimer = schedule(() => {\n pollTimer = undefined;\n let halted = false;\n const mine = epoch;\n void poll(mine)\n .catch((error: unknown) => {\n if (error instanceof StaleRequest || mine !== epoch) {\n halted = true;\n return;\n }\n if (negotiated === \"poll\") interrupted = true;\n if (error instanceof SyncUnauthorized) {\n halted = true;\n unauthorized(error);\n if (transport === \"poll\") {\n transport = undefined;\n connectTimer = schedule(() => {\n connectTimer = undefined;\n void connect();\n }, backoff());\n }\n return;\n }\n settings.onError?.(error);\n })\n .finally(() => {\n if (!halted) schedulePoll(nextPollDelay());\n });\n }, ms);\n };\n\n const nextPollDelay = () => {\n if (transport === \"sse\") return settings.safetyPollMs;\n const base = Math.max(negotiatedPollMs, options.pollEveryMs?.() ?? 0);\n return isHidden() ? Math.max(base, settings.hiddenPollMs) : base;\n };\n let negotiatedPollMs = 2000;\n\n const negotiate = async (): Promise<SyncNegotiation> => {\n return withTimeout(async (signal) => {\n const response = await options.request(`${basePath}/sync/token`, {\n method: \"POST\",\n signal,\n });\n if (refused(response)) throw new SyncUnauthorized(response.status);\n if (!response.ok)\n throw new Error(`sync negotiation failed with HTTP ${response.status}`);\n return (await response.json()) as SyncNegotiation;\n });\n };\n\n interface LiveStream {\n listed: ReadonlySet<string>;\n readonly open: Set<ClientChannel>;\n }\n let live: LiveStream | undefined;\n\n const stream = async (endpoint: string) => {\n const own = new AbortController();\n controller = own;\n let keepAliveMs = LEGACY_KEEP_ALIVE_MS;\n let silent = false;\n let watchdog: ReturnType<typeof setTimeout> | undefined;\n const rearm = () => {\n if (watchdog !== undefined) cancel(watchdog);\n watchdog = schedule(() => {\n silent = true;\n own.abort();\n }, streamLivenessMs(keepAliveMs));\n };\n const open = new Set<ClientChannel>();\n const current: LiveStream = { listed: new Set(), open };\n live = current;\n rearm();\n try {\n const response = await options.request(`${endpoint}?${missedSince()}`, {\n headers: { accept: \"text/event-stream\" },\n signal: own.signal,\n });\n if (refused(response)) throw new SyncUnauthorized(response.status);\n if (!response.ok || !response.body) {\n throw new Error(`sync stream failed with HTTP ${response.status}`);\n }\n reconnectDelay = settings.reconnectBaseMs;\n resumed();\n await readSse(\n response.body,\n (frame) => {\n if (frame.event === \"message\") {\n const batch = JSON.parse(frame.data) as SyncBatch;\n take(batch);\n const parsed = decodeCursor(batch.cursor);\n if (compareCursors(parsed, cursor) > 0) cursor = parsed;\n return;\n }\n if (frame.event === \"handshake\") {\n const handshake = parseHandshake(frame.data);\n keepAliveMs = handshake.keepAliveMs ?? LEGACY_KEEP_ALIVE_MS;\n rearm();\n current.listed = new Set(handshake.capabilities);\n for (const event of handshake.capabilities) {\n const channel = channels.get(event);\n if (!channel || open.has(channel)) continue;\n open.add(channel);\n guarded(() => channel.opened());\n }\n return;\n }\n const channel = channels.get(frame.event);\n if (channel && open.has(channel)) guarded(() => channel.receive(frame.data));\n },\n own.signal,\n rearm,\n );\n } catch (error) {\n if (!silent) throw error;\n } finally {\n if (watchdog !== undefined) cancel(watchdog);\n if (live === current) live = undefined;\n for (const channel of open) guarded(() => channel.closed());\n }\n };\n\n const unauthorized = (error: SyncUnauthorized) => {\n if (pollTimer !== undefined) cancel(pollTimer);\n pollTimer = undefined;\n (options.onUnauthorized ?? settings.onError)?.(error);\n };\n\n const backoff = () => {\n const delay = reconnectDelay;\n reconnectDelay = Math.min(reconnectDelay * 2, settings.reconnectMaxMs);\n return delay;\n };\n\n let connecting = false;\n const connect = async () => {\n if (connecting) return;\n connecting = true;\n const mine = epoch;\n try {\n await connectLoop(mine);\n } finally {\n if (mine === epoch) connecting = false;\n }\n };\n\n const connectLoop = async (mine: number) => {\n const active = () => running && mine === epoch;\n for (;;) {\n if (!active()) return;\n let endpoint: string | undefined;\n try {\n const negotiation = await negotiate();\n if (!active()) return;\n negotiatedPollMs = negotiation.pollIntervalMs;\n negotiated = negotiation.transport;\n guarded(() => options.onNegotiated?.(negotiation.transport));\n if (negotiation.transport === \"poll\") {\n transport = \"poll\";\n schedulePoll(0);\n return;\n }\n endpoint = negotiation.endpoint;\n } catch (error) {\n if (!active()) return;\n interrupted = true;\n if (error instanceof SyncUnauthorized) {\n transport = undefined;\n unauthorized(error);\n } else {\n settings.onError?.(error);\n if (transport !== \"poll\") {\n transport = \"poll\";\n schedulePoll(negotiatedPollMs);\n }\n }\n }\n if (endpoint !== undefined) {\n transport = \"sse\";\n schedulePoll(settings.safetyPollMs);\n try {\n await stream(endpoint);\n } catch (error) {\n if (!active()) return;\n if (error instanceof SyncUnauthorized) unauthorized(error);\n else settings.onError?.(error);\n }\n interrupted = true;\n }\n if (!active()) return;\n const delay = backoff();\n await new Promise<void>((resolve) => {\n const timer = schedule(() => {\n endReconnectWait = undefined;\n resolve();\n }, delay);\n endReconnectWait = () => {\n cancel(timer);\n endReconnectWait = undefined;\n resolve();\n };\n });\n }\n };\n\n const detachChannel = (channel: ClientChannel) => {\n if (channels.get(channel.event) !== channel) return;\n channels.delete(channel.event);\n if (live?.open.delete(channel)) guarded(() => channel.closed());\n };\n\n const start = () => {\n if (running) return;\n running = true;\n void connect();\n };\n\n const reconnectNow = () => {\n if (!running) return;\n if (endReconnectWait) {\n reconnectDelay = settings.reconnectBaseMs;\n endReconnectWait();\n } else if (connectTimer !== undefined) {\n cancel(connectTimer);\n connectTimer = undefined;\n reconnectDelay = settings.reconnectBaseMs;\n void connect();\n }\n if (transport === \"poll\" && pollTimer !== undefined) schedulePoll(0);\n };\n\n const stop = () => {\n running = false;\n epoch += 1;\n for (const request of inflight) request.abort();\n inflight.clear();\n controller?.abort();\n controller = undefined;\n connecting = false;\n endReconnectWait?.();\n if (pollTimer !== undefined) cancel(pollTimer);\n if (flushTimer !== undefined) cancel(flushTimer);\n if (connectTimer !== undefined) cancel(connectTimer);\n pollTimer = flushTimer = connectTimer = undefined;\n pending = [];\n };\n\n return {\n start,\n stop,\n addChannel(channel) {\n const replaced = channels.get(channel.event);\n if (replaced === channel) return () => detachChannel(channel);\n if (replaced) detachChannel(replaced);\n channels.set(channel.event, channel);\n if (live?.listed.has(channel.event)) {\n live.open.add(channel);\n guarded(() => channel.opened());\n }\n return () => detachChannel(channel);\n },\n reconnectNow,\n get cursor() {\n return held();\n },\n get transport() {\n return transport;\n },\n };\n}\n"]}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
export type { ChannelConnection, ClientChannel, ServerChannel } from "./channel.js";
|
|
2
|
+
export { compareCursors, cursorOf, decodeCursor, encodeCursor, INITIAL_CURSOR, SYNC_CHANNEL, SYNC_PROTOCOL_VERSION, type SyncBatch, type SyncCursor, type SyncEvent, type SyncNegotiation, } from "./protocol.js";
|
|
3
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,YAAY,EAAE,iBAAiB,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AACpF,OAAO,EACL,cAAc,EACd,QAAQ,EACR,YAAY,EACZ,YAAY,EACZ,cAAc,EACd,YAAY,EACZ,qBAAqB,EACrB,KAAK,SAAS,EACd,KAAK,UAAU,EACf,KAAK,SAAS,EACd,KAAK,eAAe,GACrB,MAAM,eAAe,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EACL,cAAc,EACd,QAAQ,EACR,YAAY,EACZ,YAAY,EACZ,cAAc,EACd,YAAY,EACZ,qBAAqB,GAKtB,MAAM,eAAe,CAAC","sourcesContent":["export type { ChannelConnection, ClientChannel, ServerChannel } from \"./channel.js\";\nexport {\n compareCursors,\n cursorOf,\n decodeCursor,\n encodeCursor,\n INITIAL_CURSOR,\n SYNC_CHANNEL,\n SYNC_PROTOCOL_VERSION,\n type SyncBatch,\n type SyncCursor,\n type SyncEvent,\n type SyncNegotiation,\n} from \"./protocol.js\";\n"]}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import type { Tag } from "@parlor/core";
|
|
2
|
+
export declare const SYNC_PROTOCOL_VERSION = 1;
|
|
3
|
+
export declare const SYNC_CHANNEL = "parlor_sync";
|
|
4
|
+
export declare const DEFAULT_KEEP_ALIVE_MS = 10000;
|
|
5
|
+
export declare const LEGACY_KEEP_ALIVE_MS = 25000;
|
|
6
|
+
export declare function streamLivenessMs(keepAliveMs: number): number;
|
|
7
|
+
export interface SyncEvent {
|
|
8
|
+
readonly version: number;
|
|
9
|
+
readonly id: string;
|
|
10
|
+
readonly tags: readonly Tag[];
|
|
11
|
+
readonly source?: string | undefined;
|
|
12
|
+
}
|
|
13
|
+
export interface SyncCursor {
|
|
14
|
+
readonly version: number;
|
|
15
|
+
readonly id: string;
|
|
16
|
+
}
|
|
17
|
+
export declare const INITIAL_CURSOR: SyncCursor;
|
|
18
|
+
export declare function encodeCursor(cursor: SyncCursor): string;
|
|
19
|
+
export declare function decodeCursor(value: unknown): SyncCursor;
|
|
20
|
+
export declare function compareCursors(a: SyncCursor, b: SyncCursor): number;
|
|
21
|
+
export declare function cursorOf(event: SyncEvent): SyncCursor;
|
|
22
|
+
export interface SyncBatch {
|
|
23
|
+
readonly events: readonly SyncEvent[];
|
|
24
|
+
readonly cursor: string;
|
|
25
|
+
/**
|
|
26
|
+
* The server cannot replay every event after the cursor the client sent: there are more
|
|
27
|
+
* than one batch of them, or pruning may have removed some. `events` is empty and
|
|
28
|
+
* `cursor` is where the client's streams stand now, so anything derived from events
|
|
29
|
+
* before it may be stale. Sent only to a client that asks with `reset=1`.
|
|
30
|
+
*/
|
|
31
|
+
readonly reset?: true | undefined;
|
|
32
|
+
}
|
|
33
|
+
export interface SyncNegotiation {
|
|
34
|
+
readonly protocol: typeof SYNC_PROTOCOL_VERSION;
|
|
35
|
+
readonly transport: "sse" | "poll";
|
|
36
|
+
readonly endpoint: string;
|
|
37
|
+
readonly pollIntervalMs: number;
|
|
38
|
+
}
|
|
39
|
+
//# sourceMappingURL=protocol.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"protocol.d.ts","sourceRoot":"","sources":["../src/protocol.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,cAAc,CAAC;AAExC,eAAO,MAAM,qBAAqB,IAAI,CAAC;AACvC,eAAO,MAAM,YAAY,gBAAgB,CAAC;AAC1C,eAAO,MAAM,qBAAqB,QAAS,CAAC;AAC5C,eAAO,MAAM,oBAAoB,QAAS,CAAC;AAI3C,wBAAgB,gBAAgB,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,CAE5D;AAED,MAAM,WAAW,SAAS;IACxB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,IAAI,EAAE,SAAS,GAAG,EAAE,CAAC;IAC9B,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CACtC;AAED,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;CACrB;AAED,eAAO,MAAM,cAAc,EAAE,UAAmC,CAAC;AAEjE,wBAAgB,YAAY,CAAC,MAAM,EAAE,UAAU,GAAG,MAAM,CAEvD;AAED,wBAAgB,YAAY,CAAC,KAAK,EAAE,OAAO,GAAG,UAAU,CAMvD;AAED,wBAAgB,cAAc,CAAC,CAAC,EAAE,UAAU,EAAE,CAAC,EAAE,UAAU,GAAG,MAAM,CAGnE;AAED,wBAAgB,QAAQ,CAAC,KAAK,EAAE,SAAS,GAAG,UAAU,CAErD;AAED,MAAM,WAAW,SAAS;IACxB,QAAQ,CAAC,MAAM,EAAE,SAAS,SAAS,EAAE,CAAC;IACtC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB;;;;;OAKG;IACH,QAAQ,CAAC,KAAK,CAAC,EAAE,IAAI,GAAG,SAAS,CAAC;CACnC;AAED,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,QAAQ,EAAE,OAAO,qBAAqB,CAAC;IAChD,QAAQ,CAAC,SAAS,EAAE,KAAK,GAAG,MAAM,CAAC;IACnC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;CACjC"}
|
package/dist/protocol.js
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
export const SYNC_PROTOCOL_VERSION = 1;
|
|
2
|
+
export const SYNC_CHANNEL = "parlor_sync";
|
|
3
|
+
export const DEFAULT_KEEP_ALIVE_MS = 10_000;
|
|
4
|
+
export const LEGACY_KEEP_ALIVE_MS = 25_000;
|
|
5
|
+
const STREAM_LIVENESS_MARGIN_MS = 5000;
|
|
6
|
+
export function streamLivenessMs(keepAliveMs) {
|
|
7
|
+
return keepAliveMs * 2 + STREAM_LIVENESS_MARGIN_MS;
|
|
8
|
+
}
|
|
9
|
+
export const INITIAL_CURSOR = { version: 0, id: "" };
|
|
10
|
+
export function encodeCursor(cursor) {
|
|
11
|
+
return `${cursor.version}.${cursor.id}`;
|
|
12
|
+
}
|
|
13
|
+
export function decodeCursor(value) {
|
|
14
|
+
if (typeof value !== "string" || value === "")
|
|
15
|
+
return INITIAL_CURSOR;
|
|
16
|
+
const separator = value.indexOf(".");
|
|
17
|
+
const version = Number(separator === -1 ? value : value.slice(0, separator));
|
|
18
|
+
if (!Number.isSafeInteger(version) || version < 0)
|
|
19
|
+
return INITIAL_CURSOR;
|
|
20
|
+
return { version, id: separator === -1 ? "" : value.slice(separator + 1) };
|
|
21
|
+
}
|
|
22
|
+
export function compareCursors(a, b) {
|
|
23
|
+
if (a.version !== b.version)
|
|
24
|
+
return a.version - b.version;
|
|
25
|
+
return a.id < b.id ? -1 : a.id > b.id ? 1 : 0;
|
|
26
|
+
}
|
|
27
|
+
export function cursorOf(event) {
|
|
28
|
+
return { version: event.version, id: event.id };
|
|
29
|
+
}
|
|
30
|
+
//# sourceMappingURL=protocol.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"protocol.js","sourceRoot":"","sources":["../src/protocol.ts"],"names":[],"mappings":"AAEA,MAAM,CAAC,MAAM,qBAAqB,GAAG,CAAC,CAAC;AACvC,MAAM,CAAC,MAAM,YAAY,GAAG,aAAa,CAAC;AAC1C,MAAM,CAAC,MAAM,qBAAqB,GAAG,MAAM,CAAC;AAC5C,MAAM,CAAC,MAAM,oBAAoB,GAAG,MAAM,CAAC;AAE3C,MAAM,yBAAyB,GAAG,IAAI,CAAC;AAEvC,MAAM,UAAU,gBAAgB,CAAC,WAAmB;IAClD,OAAO,WAAW,GAAG,CAAC,GAAG,yBAAyB,CAAC;AACrD,CAAC;AAcD,MAAM,CAAC,MAAM,cAAc,GAAe,EAAE,OAAO,EAAE,CAAC,EAAE,EAAE,EAAE,EAAE,EAAE,CAAC;AAEjE,MAAM,UAAU,YAAY,CAAC,MAAkB;IAC7C,OAAO,GAAG,MAAM,CAAC,OAAO,IAAI,MAAM,CAAC,EAAE,EAAE,CAAC;AAC1C,CAAC;AAED,MAAM,UAAU,YAAY,CAAC,KAAc;IACzC,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,EAAE;QAAE,OAAO,cAAc,CAAC;IACrE,MAAM,SAAS,GAAG,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;IACrC,MAAM,OAAO,GAAG,MAAM,CAAC,SAAS,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,SAAS,CAAC,CAAC,CAAC;IAC7E,IAAI,CAAC,MAAM,CAAC,aAAa,CAAC,OAAO,CAAC,IAAI,OAAO,GAAG,CAAC;QAAE,OAAO,cAAc,CAAC;IACzE,OAAO,EAAE,OAAO,EAAE,EAAE,EAAE,SAAS,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,SAAS,GAAG,CAAC,CAAC,EAAE,CAAC;AAC7E,CAAC;AAED,MAAM,UAAU,cAAc,CAAC,CAAa,EAAE,CAAa;IACzD,IAAI,CAAC,CAAC,OAAO,KAAK,CAAC,CAAC,OAAO;QAAE,OAAO,CAAC,CAAC,OAAO,GAAG,CAAC,CAAC,OAAO,CAAC;IAC1D,OAAO,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AAChD,CAAC;AAED,MAAM,UAAU,QAAQ,CAAC,KAAgB;IACvC,OAAO,EAAE,OAAO,EAAE,KAAK,CAAC,OAAO,EAAE,EAAE,EAAE,KAAK,CAAC,EAAE,EAAE,CAAC;AAClD,CAAC","sourcesContent":["import type { Tag } from \"@parlor/core\";\n\nexport const SYNC_PROTOCOL_VERSION = 1;\nexport const SYNC_CHANNEL = \"parlor_sync\";\nexport const DEFAULT_KEEP_ALIVE_MS = 10_000;\nexport const LEGACY_KEEP_ALIVE_MS = 25_000;\n\nconst STREAM_LIVENESS_MARGIN_MS = 5000;\n\nexport function streamLivenessMs(keepAliveMs: number): number {\n return keepAliveMs * 2 + STREAM_LIVENESS_MARGIN_MS;\n}\n\nexport interface SyncEvent {\n readonly version: number;\n readonly id: string;\n readonly tags: readonly Tag[];\n readonly source?: string | undefined;\n}\n\nexport interface SyncCursor {\n readonly version: number;\n readonly id: string;\n}\n\nexport const INITIAL_CURSOR: SyncCursor = { version: 0, id: \"\" };\n\nexport function encodeCursor(cursor: SyncCursor): string {\n return `${cursor.version}.${cursor.id}`;\n}\n\nexport function decodeCursor(value: unknown): SyncCursor {\n if (typeof value !== \"string\" || value === \"\") return INITIAL_CURSOR;\n const separator = value.indexOf(\".\");\n const version = Number(separator === -1 ? value : value.slice(0, separator));\n if (!Number.isSafeInteger(version) || version < 0) return INITIAL_CURSOR;\n return { version, id: separator === -1 ? \"\" : value.slice(separator + 1) };\n}\n\nexport function compareCursors(a: SyncCursor, b: SyncCursor): number {\n if (a.version !== b.version) return a.version - b.version;\n return a.id < b.id ? -1 : a.id > b.id ? 1 : 0;\n}\n\nexport function cursorOf(event: SyncEvent): SyncCursor {\n return { version: event.version, id: event.id };\n}\n\nexport interface SyncBatch {\n readonly events: readonly SyncEvent[];\n readonly cursor: string;\n /**\n * The server cannot replay every event after the cursor the client sent: there are more\n * than one batch of them, or pruning may have removed some. `events` is empty and\n * `cursor` is where the client's streams stand now, so anything derived from events\n * before it may be stale. Sent only to a client that asks with `reset=1`.\n */\n readonly reset?: true | undefined;\n}\n\nexport interface SyncNegotiation {\n readonly protocol: typeof SYNC_PROTOCOL_VERSION;\n readonly transport: \"sse\" | \"poll\";\n readonly endpoint: string;\n readonly pollIntervalMs: number;\n}\n"]}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { DataProvider } from "@parlor/core";
|
|
2
|
+
import { type SyncEvent } from "../protocol.js";
|
|
3
|
+
export type SyncSubscriber = (event: SyncEvent) => void;
|
|
4
|
+
export declare class SyncHub {
|
|
5
|
+
#private;
|
|
6
|
+
private readonly data;
|
|
7
|
+
constructor(data: DataProvider);
|
|
8
|
+
get canPush(): boolean;
|
|
9
|
+
start(): Promise<void>;
|
|
10
|
+
publish(scope: string, event: SyncEvent): void;
|
|
11
|
+
subscribe(scope: string, notify: SyncSubscriber, onStop?: () => void): () => void;
|
|
12
|
+
subscriberCount(scope?: string): number;
|
|
13
|
+
stop(): Promise<void>;
|
|
14
|
+
}
|
|
15
|
+
//# sourceMappingURL=hub.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hub.d.ts","sourceRoot":"","sources":["../../src/server/hub.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAY,MAAM,cAAc,CAAC;AAE3D,OAAO,EAAgB,KAAK,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAG9D,MAAM,MAAM,cAAc,GAAG,CAAC,KAAK,EAAE,SAAS,KAAK,IAAI,CAAC;AAOxD,qBAAa,OAAO;;IAKN,OAAO,CAAC,QAAQ,CAAC,IAAI;IAAjC,YAA6B,IAAI,EAAE,YAAY,EAAI;IAEnD,IAAI,OAAO,IAAI,OAAO,CAErB;IAEK,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAsB3B;IASD,OAAO,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,SAAS,GAAG,IAAI,CAE7C;IAED,SAAS,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,cAAc,EAAE,MAAM,CAAC,EAAE,MAAM,IAAI,GAAG,MAAM,IAAI,CAShF;IAED,eAAe,CAAC,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,CAKtC;IAEK,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,CAO1B;CACF"}
|