experimental-a2 0.9.0 → 0.11.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/CHANGELOG.md +53 -0
- package/dist/actor-D_54lz_1.d.ts +310 -0
- package/dist/actor-D_54lz_1.d.ts.map +1 -0
- package/dist/actor-client.d.ts +13 -4
- package/dist/actor-client.d.ts.map +1 -1
- package/dist/actor-client.js +63 -7
- package/dist/actor-client.js.map +1 -1
- package/dist/actor-react.d.ts +5 -4
- package/dist/actor-react.d.ts.map +1 -1
- package/dist/actor-react.js +16 -2
- package/dist/actor-react.js.map +1 -1
- package/dist/actor.d.ts +2 -176
- package/dist/actor.js +13 -2
- package/dist/actor.js.map +1 -1
- package/dist/ai-server.d.ts +1 -1
- package/dist/ai-server.js +2 -2
- package/dist/ai.d.ts +1 -1
- package/dist/ai.js +1 -1
- package/dist/client-Bf6uSEAk.js +1342 -0
- package/dist/client-Bf6uSEAk.js.map +1 -0
- package/dist/client-P_NNNRM-.d.ts +243 -0
- package/dist/client-P_NNNRM-.d.ts.map +1 -0
- package/dist/client.d.ts +2 -202
- package/dist/client.js +2 -1026
- package/dist/errors-DCk6ch5n.js.map +1 -1
- package/dist/errors-DvhSXnxk.d.ts +28 -0
- package/dist/errors-DvhSXnxk.d.ts.map +1 -0
- package/dist/index.d.ts +3 -35
- package/dist/{internal-DRXJ56EI.js → internal-Dq2qYxou.js} +2 -2
- package/dist/{internal-DRXJ56EI.js.map → internal-Dq2qYxou.js.map} +1 -1
- package/dist/platform-B4TnJtWu.js +34 -0
- package/dist/platform-B4TnJtWu.js.map +1 -0
- package/dist/react.d.ts +3 -1
- package/dist/react.d.ts.map +1 -1
- package/dist/react.js +2 -1
- package/dist/react.js.map +1 -1
- package/dist/scheduler-qstash.d.ts +2 -2
- package/dist/scheduler-qstash.js +3 -2
- package/dist/scheduler-qstash.js.map +1 -1
- package/dist/scheduler-vercel.d.ts +2 -2
- package/dist/scheduler-vercel.js +2 -2
- package/dist/{server-CBET-jSz.js → server-Dkz2a84E.js} +295 -133
- package/dist/server-Dkz2a84E.js.map +1 -0
- package/dist/{server-CKY3_lbw.d.ts → server-DwPrMqHB.d.ts} +4 -2
- package/dist/server-DwPrMqHB.d.ts.map +1 -0
- package/dist/server.d.ts +2 -2
- package/dist/server.js +1 -1
- package/dist/{store-DGHeBtIQ.d.ts → store-DtDOWLSn.d.ts} +4 -5
- package/dist/{store-DGHeBtIQ.d.ts.map → store-DtDOWLSn.d.ts.map} +1 -1
- package/dist/store-N8PXxDAS.js.map +1 -1
- package/dist/store-memory.d.ts +1 -1
- package/dist/store-memory.js +1 -1
- package/dist/{store-polling-6DW7F1DT.js → store-polling-CmxUbV93.js} +56 -7
- package/dist/store-polling-CmxUbV93.js.map +1 -0
- package/dist/store-postgres.d.ts +6 -4
- package/dist/store-postgres.d.ts.map +1 -1
- package/dist/store-postgres.js +701 -53
- package/dist/store-postgres.js.map +1 -1
- package/dist/store-presence-polling-C7-XZyW9.js +94 -0
- package/dist/store-presence-polling-C7-XZyW9.js.map +1 -0
- package/dist/store-redis-http.d.ts +2 -2
- package/dist/store-redis-http.d.ts.map +1 -1
- package/dist/store-redis-http.js +184 -38
- package/dist/store-redis-http.js.map +1 -1
- package/dist/{store-redis-core-z-ykbyMg.js → store-redis-notify-BUCyXOn0.js} +491 -27
- package/dist/store-redis-notify-BUCyXOn0.js.map +1 -0
- package/dist/store-redis.d.ts +5 -13
- package/dist/store-redis.d.ts.map +1 -1
- package/dist/store-redis.js +27 -271
- package/dist/store-redis.js.map +1 -1
- package/dist/store-sqlite.d.ts +1 -1
- package/dist/store-sqlite.js +1 -1
- package/dist/{wire--yji6mO3.js → wire-BO5wWCb1.js} +18 -16
- package/dist/wire-BO5wWCb1.js.map +1 -0
- package/docs/actors/04-routes.mdx +40 -8
- package/docs/guides/03-react.mdx +52 -10
- package/docs/guides/05-production.mdx +54 -5
- package/docs/guides/09-presence.mdx +12 -7
- package/docs/guides/10-transports.mdx +85 -22
- package/docs/reference/01-api.mdx +90 -28
- package/docs/reference/02-errors.mdx +45 -11
- package/examples/playground/package.json +1 -1
- package/package.json +1 -1
- package/src/actor-client.ts +114 -14
- package/src/actor-react.ts +18 -3
- package/src/actor.ts +16 -1
- package/src/client-errors.ts +51 -0
- package/src/client.ts +577 -175
- package/src/errors.ts +4 -9
- package/src/index.ts +1 -1
- package/src/internal.ts +1 -1
- package/src/postgres-notification-scope.ts +134 -0
- package/src/postgres-notifications.ts +244 -0
- package/src/postgres-pool.ts +65 -0
- package/src/postgres-resources.ts +185 -0
- package/src/presence-recovery.ts +120 -0
- package/src/react.ts +3 -0
- package/src/redis-http-subscriptions.ts +199 -0
- package/src/server-fetch.ts +51 -23
- package/src/server.ts +146 -19
- package/src/session-socket.ts +241 -88
- package/src/sse.ts +17 -0
- package/src/store-polling.ts +32 -12
- package/src/store-postgres.ts +292 -75
- package/src/store-presence-polling.ts +125 -0
- package/src/store-redis-core.ts +37 -30
- package/src/store-redis-http.ts +51 -45
- package/src/store-redis-notify.ts +464 -0
- package/src/store-redis.ts +9 -364
- package/src/store.ts +3 -4
- package/src/stream-activity.ts +32 -0
- package/src/wire.ts +39 -15
- package/dist/actor-shared-BACubf4x.d.ts +0 -136
- package/dist/actor-shared-BACubf4x.d.ts.map +0 -1
- package/dist/actor.d.ts.map +0 -1
- package/dist/client.d.ts.map +0 -1
- package/dist/client.js.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/server-CBET-jSz.js.map +0 -1
- package/dist/server-CKY3_lbw.d.ts.map +0 -1
- package/dist/store-polling-6DW7F1DT.js.map +0 -1
- package/dist/store-redis-core-z-ykbyMg.js.map +0 -1
- package/dist/wire--yji6mO3.js.map +0 -1
|
@@ -1,40 +1,10 @@
|
|
|
1
1
|
import { n as validateSync } from "./validate-XKT4FSNn.js";
|
|
2
2
|
import { n as NonRetriableError, r as asStoreUnavailable, t as A2Error } from "./errors-DCk6ch5n.js";
|
|
3
|
-
import { c as STREAM_TIMINGS, g as serverInternals, h as nullProtoRecord, i as POLL_TIMINGS, m as markSchedulerSendFailure, o as RESERVED_PARTICIPANT_IDS, s as SOCKET_TIMINGS, t as DRAIN_TIMINGS } from "./internal-
|
|
4
|
-
import { t as defaultSleep } from "./store-polling-
|
|
5
|
-
import {
|
|
3
|
+
import { c as STREAM_TIMINGS, g as serverInternals, h as nullProtoRecord, i as POLL_TIMINGS, m as markSchedulerSendFailure, o as RESERVED_PARTICIPANT_IDS, s as SOCKET_TIMINGS, t as DRAIN_TIMINGS } from "./internal-Dq2qYxou.js";
|
|
4
|
+
import { i as streamActivities, r as createStreamActivity, t as defaultSleep } from "./store-polling-CmxUbV93.js";
|
|
5
|
+
import { r as platformWaitUntil, t as invocationDeadlineMs } from "./platform-B4TnJtWu.js";
|
|
6
|
+
import { _ as socketErrorAckFor, a as errorToWire, b as socketUnsubscribedFor, g as socketAckFor, h as presenceSnapshotToWire, i as errorStatus, n as asA2Error, p as presencePatchToWire, s as eventToWire, t as SOCKET_PING_FRAME, v as socketFrameFor, y as socketSubscribedFor } from "./wire-BO5wWCb1.js";
|
|
6
7
|
import { t as retryableLazy } from "./retryable-lazy-DZWmHpii.js";
|
|
7
|
-
//#region src/platform.ts
|
|
8
|
-
const SYMBOL_FOR_REQ_CONTEXT = Symbol.for("@vercel/request-context");
|
|
9
|
-
function requestContext() {
|
|
10
|
-
try {
|
|
11
|
-
return Reflect.get(globalThis, SYMBOL_FOR_REQ_CONTEXT)?.get?.() ?? {};
|
|
12
|
-
} catch {
|
|
13
|
-
return {};
|
|
14
|
-
}
|
|
15
|
-
}
|
|
16
|
-
/** The current invocation's Vercel deployment identity, when available. */
|
|
17
|
-
function platformVercelOidcToken() {
|
|
18
|
-
try {
|
|
19
|
-
const token = requestContext().headers?.["x-vercel-oidc-token"];
|
|
20
|
-
return typeof token === "string" && token.length > 0 ? token : null;
|
|
21
|
-
} catch {
|
|
22
|
-
return null;
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
function platformWaitUntil(promise) {
|
|
26
|
-
try {
|
|
27
|
-
requestContext().waitUntil?.(promise);
|
|
28
|
-
} catch {}
|
|
29
|
-
}
|
|
30
|
-
/** The invocation's termination time in epoch ms, or null if unknowable. */
|
|
31
|
-
function invocationDeadlineMs() {
|
|
32
|
-
const deadline = requestContext().deadline;
|
|
33
|
-
if (deadline === void 0 || deadline === null) return null;
|
|
34
|
-
const ms = new Date(deadline).getTime();
|
|
35
|
-
return Number.isNaN(ms) ? null : ms;
|
|
36
|
-
}
|
|
37
|
-
//#endregion
|
|
38
8
|
//#region src/push-envelope.ts
|
|
39
9
|
/**
|
|
40
10
|
* The push envelope's trust boundary for `{ sessionId, events, presence? }`,
|
|
@@ -116,10 +86,9 @@ async function parsePushBody(req) {
|
|
|
116
86
|
/**
|
|
117
87
|
* The A2 socket protocol's server half (specs/a2-api.md §13): the
|
|
118
88
|
* per-socket shell — teardown, backpressure, heartbeat, the malformed
|
|
119
|
-
* budget, the in-flight bound
|
|
120
|
-
*
|
|
121
|
-
*
|
|
122
|
-
* platform hands over) is re-exported as public surface.
|
|
89
|
+
* budget, and the in-flight bound. `sessionSocket` binds one session to
|
|
90
|
+
* one socket; `sessionsSocket` is the opt-in multiplexed handler. Only
|
|
91
|
+
* `A2Socket`, the structural socket the platform hands over, is public.
|
|
123
92
|
*/
|
|
124
93
|
const messageText = (data) => {
|
|
125
94
|
if (typeof data === "string") return data;
|
|
@@ -222,10 +191,103 @@ function socketShell(socket, release, onFrame, deadlineAt) {
|
|
|
222
191
|
track
|
|
223
192
|
};
|
|
224
193
|
}
|
|
194
|
+
const socketSubscription = (target, startAfter, presence) => ({
|
|
195
|
+
target,
|
|
196
|
+
iterator: (presence ? target.stream({
|
|
197
|
+
startAfter,
|
|
198
|
+
presence: true
|
|
199
|
+
}) : target.stream({ startAfter }))[Symbol.asyncIterator](),
|
|
200
|
+
stopped: false
|
|
201
|
+
});
|
|
225
202
|
const stopSubscription = (subscription) => {
|
|
226
203
|
subscription.stopped = true;
|
|
227
204
|
subscription.iterator.return?.()?.catch(() => {});
|
|
228
205
|
};
|
|
206
|
+
const pumpSubscription = async (shell, subscription, frameFor) => {
|
|
207
|
+
for (;;) {
|
|
208
|
+
while (!shell.torn() && !subscription.stopped && shell.buffered() > SOCKET_TIMINGS.highWaterMarkBytes) await new Promise((wake) => setTimeout(wake, SOCKET_TIMINGS.resumePollMs));
|
|
209
|
+
if (shell.torn() || subscription.stopped) return;
|
|
210
|
+
const { value, done } = await subscription.iterator.next();
|
|
211
|
+
if (shell.torn() || subscription.stopped || done) return;
|
|
212
|
+
shell.send(frameFor(value));
|
|
213
|
+
}
|
|
214
|
+
};
|
|
215
|
+
const handleSocketPush = async (shell, target, sessionId, frame, req, ackSessionId, gatePush, activate) => {
|
|
216
|
+
try {
|
|
217
|
+
const events = parsePushEvents(frame["events"]);
|
|
218
|
+
const denial = await gatePush?.(sessionId, events, void 0) ?? null;
|
|
219
|
+
if (denial !== null) {
|
|
220
|
+
shell.send(socketErrorAckFor(req, denial, ackSessionId));
|
|
221
|
+
return;
|
|
222
|
+
}
|
|
223
|
+
activate();
|
|
224
|
+
const appended = await target.append(...events);
|
|
225
|
+
activate();
|
|
226
|
+
shell.send(socketAckFor(req, appended, ackSessionId));
|
|
227
|
+
} catch (error) {
|
|
228
|
+
shell.send(socketErrorAckFor(req, asA2Error(error), ackSessionId));
|
|
229
|
+
}
|
|
230
|
+
};
|
|
231
|
+
const applySocketPresence = async (shell, target, sessionId, frame, gatePush) => {
|
|
232
|
+
if (typeof target.setPresence !== "function") {
|
|
233
|
+
shell.malformedFrame("presence frame on a presence-less session");
|
|
234
|
+
return;
|
|
235
|
+
}
|
|
236
|
+
let patch;
|
|
237
|
+
try {
|
|
238
|
+
patch = parsePresenceSibling(frame);
|
|
239
|
+
} catch {
|
|
240
|
+
shell.malformedFrame("invalid presence frame");
|
|
241
|
+
return;
|
|
242
|
+
}
|
|
243
|
+
if (!patch) return;
|
|
244
|
+
try {
|
|
245
|
+
if ((await gatePush?.(sessionId, [], patch) ?? null) !== null) return;
|
|
246
|
+
await target.setPresence(patch);
|
|
247
|
+
} catch (error) {
|
|
248
|
+
const failure = asA2Error(error);
|
|
249
|
+
if (failure.code === "INVALID_PAYLOAD" || failure.code === "UNKNOWN_PRESENCE_FIELD") {
|
|
250
|
+
shell.malformedFrame("invalid presence frame");
|
|
251
|
+
return;
|
|
252
|
+
}
|
|
253
|
+
shell.shutdown(1011, "internal error");
|
|
254
|
+
}
|
|
255
|
+
};
|
|
256
|
+
function sessionSocket(sessionId, session, socket, options) {
|
|
257
|
+
const target = session;
|
|
258
|
+
const subscription = socketSubscription(target, options?.startAfter ?? 0, options?.presence === true);
|
|
259
|
+
const handleFrame = (frame) => {
|
|
260
|
+
switch (frame["kind"]) {
|
|
261
|
+
case "push": {
|
|
262
|
+
const req = frame["req"];
|
|
263
|
+
if (typeof req !== "number") {
|
|
264
|
+
shell.malformedFrame("push frame without a numeric req");
|
|
265
|
+
return;
|
|
266
|
+
}
|
|
267
|
+
if (frame["sessionId"] !== void 0) {
|
|
268
|
+
shell.send(socketErrorAckFor(req, new A2Error("INVALID_PAYLOAD", "sessionId is not allowed on a single-session socket")));
|
|
269
|
+
return;
|
|
270
|
+
}
|
|
271
|
+
if (shell.overCap()) {
|
|
272
|
+
shell.send(socketErrorAckFor(req, new A2Error("STORE_UNAVAILABLE", "push shed: too many in flight on this socket")));
|
|
273
|
+
return;
|
|
274
|
+
}
|
|
275
|
+
shell.track(handleSocketPush(shell, target, sessionId, frame, req, void 0, options?.gatePush, () => streamActivities.get(subscription.iterator)?.activate()));
|
|
276
|
+
return;
|
|
277
|
+
}
|
|
278
|
+
case "presence":
|
|
279
|
+
if (frame["sessionId"] !== void 0) {
|
|
280
|
+
shell.malformedFrame("sessionId is not allowed on a single-session socket");
|
|
281
|
+
return;
|
|
282
|
+
}
|
|
283
|
+
if (!shell.overCap()) shell.track(applySocketPresence(shell, target, sessionId, frame, options?.gatePush));
|
|
284
|
+
return;
|
|
285
|
+
default: return;
|
|
286
|
+
}
|
|
287
|
+
};
|
|
288
|
+
const shell = socketShell(socket, () => stopSubscription(subscription), handleFrame, options?.deadline);
|
|
289
|
+
pumpSubscription(shell, subscription, socketFrameFor).then(() => shell.shutdown(1e3), () => shell.shutdown(1011, "stream failed"));
|
|
290
|
+
}
|
|
229
291
|
const parseSubscribeEntries = (value) => {
|
|
230
292
|
if (!Array.isArray(value) || value.length === 0) return null;
|
|
231
293
|
const entries = [];
|
|
@@ -259,55 +321,37 @@ function sessionsSocket(resolve, socket, options) {
|
|
|
259
321
|
const retire = (id, subscription) => {
|
|
260
322
|
if (subscriptions.get(id) === subscription) subscriptions.delete(id);
|
|
261
323
|
};
|
|
262
|
-
const pump = async (id, subscription) => {
|
|
263
|
-
for (;;) {
|
|
264
|
-
for (;;) {
|
|
265
|
-
if (shell.torn() || subscription.stopped || shell.buffered() <= SOCKET_TIMINGS.highWaterMarkBytes) break;
|
|
266
|
-
await new Promise((wake) => setTimeout(wake, SOCKET_TIMINGS.resumePollMs));
|
|
267
|
-
}
|
|
268
|
-
if (shell.torn() || subscription.stopped) return;
|
|
269
|
-
const { value, done } = await subscription.iterator.next();
|
|
270
|
-
if (shell.torn() || subscription.stopped) return;
|
|
271
|
-
if (done) return;
|
|
272
|
-
shell.send(socketFrameFor(value, id));
|
|
273
|
-
}
|
|
274
|
-
};
|
|
275
324
|
const runSubscription = async (id, subscription) => {
|
|
276
325
|
try {
|
|
277
|
-
await
|
|
326
|
+
await pumpSubscription(shell, subscription, (item) => socketFrameFor(item, id));
|
|
278
327
|
if (shell.torn() || subscription.stopped) return;
|
|
279
328
|
retire(id, subscription);
|
|
280
329
|
shell.send(socketUnsubscribedFor(id));
|
|
281
|
-
} catch {
|
|
330
|
+
} catch (error) {
|
|
282
331
|
if (shell.torn() || subscription.stopped) return;
|
|
283
332
|
retire(id, subscription);
|
|
284
|
-
shell.send(socketUnsubscribedFor(id, "stream failed"));
|
|
333
|
+
shell.send(socketUnsubscribedFor(id, "stream failed", asA2Error(error)));
|
|
285
334
|
}
|
|
286
335
|
};
|
|
287
336
|
const startSubscription = async (id, index) => {
|
|
337
|
+
const existing = subscriptions.get(id);
|
|
338
|
+
if (existing) {
|
|
339
|
+
stopSubscription(existing);
|
|
340
|
+
subscriptions.delete(id);
|
|
341
|
+
}
|
|
288
342
|
let session;
|
|
289
343
|
try {
|
|
290
344
|
session = await resolve(id, index);
|
|
291
|
-
} catch {
|
|
292
|
-
if (!shell.torn()) shell.send(socketUnsubscribedFor(id, "stream failed"));
|
|
345
|
+
} catch (error) {
|
|
346
|
+
if (!shell.torn()) shell.send(socketUnsubscribedFor(id, "stream failed", asA2Error(error)));
|
|
293
347
|
return;
|
|
294
348
|
}
|
|
295
349
|
if (shell.torn()) return;
|
|
296
350
|
if (session === null) {
|
|
297
|
-
shell.send(socketUnsubscribedFor(id, "subscribe rejected"));
|
|
351
|
+
shell.send(socketUnsubscribedFor(id, "subscribe rejected", new A2Error("FORBIDDEN", "the subscription is not authorized")));
|
|
298
352
|
return;
|
|
299
353
|
}
|
|
300
|
-
const
|
|
301
|
-
const existing = subscriptions.get(id);
|
|
302
|
-
if (existing) stopSubscription(existing);
|
|
303
|
-
const subscription = {
|
|
304
|
-
target,
|
|
305
|
-
iterator: (presence ? target.stream({
|
|
306
|
-
startAfter: index,
|
|
307
|
-
presence: true
|
|
308
|
-
}) : target.stream({ startAfter: index }))[Symbol.asyncIterator](),
|
|
309
|
-
stopped: false
|
|
310
|
-
};
|
|
354
|
+
const subscription = socketSubscription(session, index, presence);
|
|
311
355
|
subscriptions.set(id, subscription);
|
|
312
356
|
shell.send(socketSubscribedFor(id));
|
|
313
357
|
runSubscription(id, subscription);
|
|
@@ -322,45 +366,15 @@ function sessionsSocket(resolve, socket, options) {
|
|
|
322
366
|
shell.send(socketErrorAckFor(req, new A2Error("STORE_UNAVAILABLE", "session is not subscribed on this socket"), sessionId));
|
|
323
367
|
return;
|
|
324
368
|
}
|
|
325
|
-
|
|
326
|
-
const
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
shell.send(socketErrorAckFor(req, denial, sessionId));
|
|
330
|
-
return;
|
|
331
|
-
}
|
|
332
|
-
const appended = await subscription.target.append(...events);
|
|
333
|
-
shell.send(socketAckFor(req, appended, sessionId));
|
|
334
|
-
} catch (err) {
|
|
335
|
-
shell.send(socketErrorAckFor(req, asA2Error(err), sessionId));
|
|
336
|
-
}
|
|
369
|
+
await handleSocketPush(shell, subscription.target, sessionId, frame, req, sessionId, options?.gatePush, () => {
|
|
370
|
+
const current = subscriptions.get(sessionId);
|
|
371
|
+
if (current) streamActivities.get(current.iterator)?.activate();
|
|
372
|
+
});
|
|
337
373
|
};
|
|
338
374
|
const handlePresence = async (frame, sessionId) => {
|
|
339
375
|
const subscription = subscriptions.get(sessionId);
|
|
340
376
|
if (subscription === void 0) return;
|
|
341
|
-
|
|
342
|
-
shell.malformedFrame("presence frame on a presence-less session");
|
|
343
|
-
return;
|
|
344
|
-
}
|
|
345
|
-
let patch;
|
|
346
|
-
try {
|
|
347
|
-
patch = parsePresenceSibling(frame);
|
|
348
|
-
} catch {
|
|
349
|
-
shell.malformedFrame("invalid presence frame");
|
|
350
|
-
return;
|
|
351
|
-
}
|
|
352
|
-
if (!patch) return;
|
|
353
|
-
try {
|
|
354
|
-
if ((await options?.gatePush?.(sessionId, [], patch) ?? null) !== null) return;
|
|
355
|
-
await subscription.target.setPresence(patch);
|
|
356
|
-
} catch (error) {
|
|
357
|
-
const failure = asA2Error(error);
|
|
358
|
-
if (failure.code === "INVALID_PAYLOAD" || failure.code === "UNKNOWN_PRESENCE_FIELD") {
|
|
359
|
-
shell.malformedFrame("invalid presence frame");
|
|
360
|
-
return;
|
|
361
|
-
}
|
|
362
|
-
shell.shutdown(1011, "internal error");
|
|
363
|
-
}
|
|
377
|
+
await applySocketPresence(shell, subscription.target, sessionId, frame, options?.gatePush);
|
|
364
378
|
};
|
|
365
379
|
const handleFrame = (frame) => {
|
|
366
380
|
switch (frame["kind"]) {
|
|
@@ -456,7 +470,10 @@ function sseResponse(iterable) {
|
|
|
456
470
|
let deadlineTimer;
|
|
457
471
|
let closed = false;
|
|
458
472
|
let iteratorReturned = false;
|
|
473
|
+
let stopActivity;
|
|
459
474
|
const stopTimers = () => {
|
|
475
|
+
stopActivity?.();
|
|
476
|
+
stopActivity = void 0;
|
|
460
477
|
if (heartbeat !== void 0) clearInterval(heartbeat);
|
|
461
478
|
if (deadlineTimer !== void 0) clearTimeout(deadlineTimer);
|
|
462
479
|
heartbeat = void 0;
|
|
@@ -470,6 +487,15 @@ function sseResponse(iterable) {
|
|
|
470
487
|
const stream = new ReadableStream({
|
|
471
488
|
start(controller) {
|
|
472
489
|
controller.enqueue(encoder.encode(": connected\n\n"));
|
|
490
|
+
const activity = streamActivities.get(iterator);
|
|
491
|
+
if (activity) {
|
|
492
|
+
const announce = (idle) => {
|
|
493
|
+
if (closed) return;
|
|
494
|
+
controller.enqueue(encoder.encode(`event: a2-activity\ndata: ${idle ? "idle" : "active"}\n\n`));
|
|
495
|
+
};
|
|
496
|
+
stopActivity = activity.subscribe(announce);
|
|
497
|
+
if (activity.idle) announce(true);
|
|
498
|
+
}
|
|
473
499
|
heartbeat = setInterval(() => {
|
|
474
500
|
try {
|
|
475
501
|
controller.enqueue(encoder.encode(": ping\n\n"));
|
|
@@ -591,7 +617,20 @@ function createServerFetch(server) {
|
|
|
591
617
|
const validateIngress = async (context) => {
|
|
592
618
|
await serverFetchHooks.get(fetch)?.validateIngress?.(context);
|
|
593
619
|
};
|
|
594
|
-
const
|
|
620
|
+
const gateSocketPush = async (sessionId, events, pushedPresence) => {
|
|
621
|
+
try {
|
|
622
|
+
await authorize(freezePush(sessionId, events, pushedPresence, "websocket"));
|
|
623
|
+
await validateIngress({
|
|
624
|
+
sessionId,
|
|
625
|
+
events,
|
|
626
|
+
...pushedPresence === void 0 ? {} : { presence: pushedPresence }
|
|
627
|
+
});
|
|
628
|
+
return null;
|
|
629
|
+
} catch (error) {
|
|
630
|
+
return asA2Error(error);
|
|
631
|
+
}
|
|
632
|
+
};
|
|
633
|
+
const attachMultiplexed = (socket) => {
|
|
595
634
|
const deadline = invocationDeadlineMs();
|
|
596
635
|
sessionsSocket(async (sessionId, startAfter) => {
|
|
597
636
|
return await authorized({
|
|
@@ -603,25 +642,34 @@ function createServerFetch(server) {
|
|
|
603
642
|
}, socket, {
|
|
604
643
|
presence,
|
|
605
644
|
...deadline === null ? {} : { deadline },
|
|
606
|
-
gatePush:
|
|
607
|
-
try {
|
|
608
|
-
await authorize(freezePush(sessionId, events, pushedPresence, "websocket"));
|
|
609
|
-
await validateIngress({
|
|
610
|
-
sessionId,
|
|
611
|
-
events,
|
|
612
|
-
...pushedPresence === void 0 ? {} : { presence: pushedPresence }
|
|
613
|
-
});
|
|
614
|
-
return null;
|
|
615
|
-
} catch (error) {
|
|
616
|
-
return asA2Error(error);
|
|
617
|
-
}
|
|
618
|
-
}
|
|
645
|
+
gatePush: gateSocketPush
|
|
619
646
|
});
|
|
620
647
|
};
|
|
621
648
|
try {
|
|
622
649
|
if (request.method === "GET") {
|
|
623
650
|
if (request.headers.get("upgrade")?.toLowerCase() === "websocket") {
|
|
624
651
|
if (options?.upgradeWebSocket === void 0) return new Response("WebSocket upgrade requested, but server.fetch() has no upgrade implementation", { status: 426 });
|
|
652
|
+
if (options.multiplexWebSocket === true) return await options.upgradeWebSocket(attachMultiplexed);
|
|
653
|
+
const { searchParams } = new URL(request.url);
|
|
654
|
+
const sessionId = searchParams.get("sessionId");
|
|
655
|
+
if (sessionId === null || sessionId.length === 0) throw new A2Error("INVALID_PAYLOAD", "missing sessionId");
|
|
656
|
+
const startAfter = parseResumeIndex(searchParams.get("index"));
|
|
657
|
+
await authorize({
|
|
658
|
+
type: "stream",
|
|
659
|
+
sessionId,
|
|
660
|
+
startAfter,
|
|
661
|
+
transport: "websocket"
|
|
662
|
+
});
|
|
663
|
+
const target = server.session(sessionId);
|
|
664
|
+
const attach = (socket) => {
|
|
665
|
+
const deadline = invocationDeadlineMs();
|
|
666
|
+
sessionSocket(sessionId, target, socket, {
|
|
667
|
+
startAfter,
|
|
668
|
+
presence,
|
|
669
|
+
...deadline === null ? {} : { deadline },
|
|
670
|
+
gatePush: gateSocketPush
|
|
671
|
+
});
|
|
672
|
+
};
|
|
625
673
|
return await options.upgradeWebSocket(attach);
|
|
626
674
|
}
|
|
627
675
|
const { searchParams } = new URL(request.url);
|
|
@@ -1346,12 +1394,57 @@ function createServer(options) {
|
|
|
1346
1394
|
createdAt: event.createdAt
|
|
1347
1395
|
});
|
|
1348
1396
|
const streamEvents = (id, startAfter) => {
|
|
1349
|
-
|
|
1397
|
+
let closed = false;
|
|
1398
|
+
let inner;
|
|
1399
|
+
let stopActivity;
|
|
1400
|
+
const activity = createStreamActivity(() => {
|
|
1401
|
+
if (inner && !closed) streamActivities.get(inner)?.activate();
|
|
1402
|
+
});
|
|
1403
|
+
let closing;
|
|
1404
|
+
const close = () => {
|
|
1405
|
+
closed = true;
|
|
1406
|
+
closing ??= (async () => {
|
|
1407
|
+
stopActivity?.();
|
|
1408
|
+
await inner?.return?.();
|
|
1409
|
+
})();
|
|
1410
|
+
return closing;
|
|
1411
|
+
};
|
|
1350
1412
|
const outer = async function* () {
|
|
1351
|
-
const
|
|
1352
|
-
|
|
1413
|
+
const store = await resolveStore();
|
|
1414
|
+
if (closed) return;
|
|
1415
|
+
inner = store.stream(nsId(id), { startAfter })[Symbol.asyncIterator]();
|
|
1416
|
+
const sourceActivity = streamActivities.get(inner);
|
|
1417
|
+
if (sourceActivity) {
|
|
1418
|
+
stopActivity = sourceActivity.subscribe(activity.setIdle);
|
|
1419
|
+
activity.setIdle(sourceActivity.idle);
|
|
1420
|
+
}
|
|
1421
|
+
try {
|
|
1422
|
+
for (;;) {
|
|
1423
|
+
if (closed) return;
|
|
1424
|
+
const result = await inner.next();
|
|
1425
|
+
if (closed || result.done) return;
|
|
1426
|
+
yield toSessionEvent(id, result.value);
|
|
1427
|
+
}
|
|
1428
|
+
} finally {
|
|
1429
|
+
await close();
|
|
1430
|
+
}
|
|
1353
1431
|
};
|
|
1354
|
-
|
|
1432
|
+
const iterator = outer();
|
|
1433
|
+
return { [Symbol.asyncIterator]() {
|
|
1434
|
+
const stream = {
|
|
1435
|
+
next: () => iterator.next(),
|
|
1436
|
+
async return() {
|
|
1437
|
+
await close();
|
|
1438
|
+
return iterator.return(void 0);
|
|
1439
|
+
},
|
|
1440
|
+
async throw(error) {
|
|
1441
|
+
await close();
|
|
1442
|
+
return iterator.throw(error);
|
|
1443
|
+
}
|
|
1444
|
+
};
|
|
1445
|
+
streamActivities.set(stream, activity);
|
|
1446
|
+
return stream;
|
|
1447
|
+
} };
|
|
1355
1448
|
};
|
|
1356
1449
|
const requirePresence = (store) => {
|
|
1357
1450
|
if (!store.presence) throw presenceNotSupported();
|
|
@@ -1403,8 +1496,39 @@ function createServer(options) {
|
|
|
1403
1496
|
*/
|
|
1404
1497
|
const streamWithPresence = (id, startAfter) => {
|
|
1405
1498
|
const ns = nsId(id);
|
|
1499
|
+
let closed = false;
|
|
1500
|
+
let events;
|
|
1501
|
+
let unsubscribe;
|
|
1502
|
+
let sleeper = null;
|
|
1503
|
+
let wakeQueue = null;
|
|
1504
|
+
let presenceInterval = Math.min(POLL_TIMINGS.activeFloorMs, POLL_TIMINGS.idleCeilingMs);
|
|
1505
|
+
let presenceActivityVersion = 0;
|
|
1506
|
+
let stopActivity;
|
|
1507
|
+
const activity = createStreamActivity(() => {
|
|
1508
|
+
if (closed) return;
|
|
1509
|
+
presenceActivityVersion += 1;
|
|
1510
|
+
presenceInterval = Math.min(POLL_TIMINGS.activeFloorMs, POLL_TIMINGS.idleCeilingMs);
|
|
1511
|
+
sleeper?.cancel();
|
|
1512
|
+
if (events) streamActivities.get(events)?.activate();
|
|
1513
|
+
});
|
|
1514
|
+
let closing;
|
|
1515
|
+
const close = () => {
|
|
1516
|
+
closed = true;
|
|
1517
|
+
sleeper?.cancel();
|
|
1518
|
+
wakeQueue?.();
|
|
1519
|
+
closing ??= (async () => {
|
|
1520
|
+
try {
|
|
1521
|
+
stopActivity?.();
|
|
1522
|
+
unsubscribe?.();
|
|
1523
|
+
} finally {
|
|
1524
|
+
await events?.return?.();
|
|
1525
|
+
}
|
|
1526
|
+
})();
|
|
1527
|
+
return closing;
|
|
1528
|
+
};
|
|
1406
1529
|
const outer = async function* () {
|
|
1407
1530
|
const store = await resolveStore();
|
|
1531
|
+
if (closed) return;
|
|
1408
1532
|
const api = requirePresence(store);
|
|
1409
1533
|
const readRows = async () => {
|
|
1410
1534
|
try {
|
|
@@ -1479,8 +1603,9 @@ function createServer(options) {
|
|
|
1479
1603
|
if (fields?.size === 0) painted.delete(patch.participant);
|
|
1480
1604
|
};
|
|
1481
1605
|
const queue = /* @__PURE__ */ new Map();
|
|
1482
|
-
let wakeQueue = null;
|
|
1483
1606
|
const enqueue = (patch) => {
|
|
1607
|
+
if (closed) return;
|
|
1608
|
+
activity.activate();
|
|
1484
1609
|
for (const [field, value] of Object.entries(patch.values)) {
|
|
1485
1610
|
if (value === void 0) continue;
|
|
1486
1611
|
let fields = queue.get(patch.participant);
|
|
@@ -1514,11 +1639,17 @@ function createServer(options) {
|
|
|
1514
1639
|
}
|
|
1515
1640
|
return null;
|
|
1516
1641
|
};
|
|
1517
|
-
const unsubscribe = api.subscribe?.(ns, enqueue);
|
|
1518
|
-
const events = store.stream(ns, { startAfter })[Symbol.asyncIterator]();
|
|
1519
1642
|
let pendingEvent = null;
|
|
1520
1643
|
try {
|
|
1644
|
+
unsubscribe = api.subscribe?.(ns, enqueue);
|
|
1645
|
+
events = store.stream(ns, { startAfter })[Symbol.asyncIterator]();
|
|
1646
|
+
const sourceActivity = streamActivities.get(events);
|
|
1647
|
+
if (sourceActivity) {
|
|
1648
|
+
stopActivity = sourceActivity.subscribe(activity.setIdle);
|
|
1649
|
+
activity.setIdle(sourceActivity.idle);
|
|
1650
|
+
}
|
|
1521
1651
|
const snapshotRows = await readRows();
|
|
1652
|
+
if (closed) return;
|
|
1522
1653
|
for (const row of snapshotRows) {
|
|
1523
1654
|
let fields = painted.get(row.participant);
|
|
1524
1655
|
if (!fields) {
|
|
@@ -1533,9 +1664,11 @@ function createServer(options) {
|
|
|
1533
1664
|
}
|
|
1534
1665
|
yield { snapshot: foldPresenceRows(snapshotRows) };
|
|
1535
1666
|
for (;;) {
|
|
1667
|
+
if (closed) return;
|
|
1536
1668
|
for (;;) {
|
|
1537
1669
|
const patch = dequeue();
|
|
1538
1670
|
if (!patch) break;
|
|
1671
|
+
if (closed) return;
|
|
1539
1672
|
paint(patch);
|
|
1540
1673
|
yield patch;
|
|
1541
1674
|
}
|
|
@@ -1547,28 +1680,57 @@ function createServer(options) {
|
|
|
1547
1680
|
});
|
|
1548
1681
|
woke = await Promise.race([pendingEvent.then(() => "event"), arrival.then(() => "patch")]);
|
|
1549
1682
|
wakeQueue = null;
|
|
1683
|
+
if (closed) return;
|
|
1550
1684
|
if (woke === "patch") continue;
|
|
1551
1685
|
} else {
|
|
1552
|
-
|
|
1686
|
+
sleeper = defaultSleep(presenceInterval);
|
|
1553
1687
|
woke = await Promise.race([pendingEvent.then(() => "event"), sleeper.promise.then(() => "tick")]);
|
|
1554
1688
|
sleeper.cancel();
|
|
1689
|
+
sleeper = null;
|
|
1555
1690
|
}
|
|
1691
|
+
if (closed) return;
|
|
1556
1692
|
if (woke === "event") {
|
|
1557
1693
|
const result = await pendingEvent;
|
|
1558
1694
|
pendingEvent = null;
|
|
1559
|
-
if (result.done) return;
|
|
1695
|
+
if (closed || result.done) return;
|
|
1696
|
+
activity.activate();
|
|
1560
1697
|
yield toSessionEvent(id, result.value);
|
|
1561
1698
|
}
|
|
1562
|
-
if (!
|
|
1699
|
+
if (!closed && !unsubscribe) {
|
|
1700
|
+
const version = presenceActivityVersion;
|
|
1701
|
+
const rows = await readRows();
|
|
1702
|
+
if (closed) return;
|
|
1703
|
+
const patches = diffRows(rows);
|
|
1704
|
+
if (patches.length > 0) activity.activate();
|
|
1705
|
+
else if (version === presenceActivityVersion) presenceInterval = Math.min(presenceInterval * 2, POLL_TIMINGS.idleCeilingMs);
|
|
1706
|
+
for (const patch of patches) {
|
|
1707
|
+
if (closed) return;
|
|
1708
|
+
yield patch;
|
|
1709
|
+
}
|
|
1710
|
+
}
|
|
1563
1711
|
}
|
|
1564
1712
|
} finally {
|
|
1565
|
-
wakeQueue = null;
|
|
1566
|
-
unsubscribe?.();
|
|
1567
1713
|
pendingEvent?.catch(() => {});
|
|
1568
|
-
await
|
|
1714
|
+
await close();
|
|
1715
|
+
wakeQueue = null;
|
|
1569
1716
|
}
|
|
1570
1717
|
};
|
|
1571
|
-
|
|
1718
|
+
const iterator = outer();
|
|
1719
|
+
return { [Symbol.asyncIterator]() {
|
|
1720
|
+
const stream = {
|
|
1721
|
+
next: () => iterator.next(),
|
|
1722
|
+
async return() {
|
|
1723
|
+
await close();
|
|
1724
|
+
return iterator.return(void 0);
|
|
1725
|
+
},
|
|
1726
|
+
async throw(error) {
|
|
1727
|
+
await close();
|
|
1728
|
+
return iterator.throw(error);
|
|
1729
|
+
}
|
|
1730
|
+
};
|
|
1731
|
+
streamActivities.set(stream, activity);
|
|
1732
|
+
return stream;
|
|
1733
|
+
} };
|
|
1572
1734
|
};
|
|
1573
1735
|
const makeSession = (id, append, trigger) => ({
|
|
1574
1736
|
id,
|
|
@@ -2157,6 +2319,6 @@ function cloneInitial(initial) {
|
|
|
2157
2319
|
}
|
|
2158
2320
|
}
|
|
2159
2321
|
//#endregion
|
|
2160
|
-
export { parsePresenceSibling as a, sseResponse as i, deliverSchedulerAppend as n,
|
|
2322
|
+
export { parsePresenceSibling as a, sseResponse as i, deliverSchedulerAppend as n, setServerFetchHooks as r, createServer as t };
|
|
2161
2323
|
|
|
2162
|
-
//# sourceMappingURL=server-
|
|
2324
|
+
//# sourceMappingURL=server-Dkz2a84E.js.map
|