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.
Files changed (123) hide show
  1. package/CHANGELOG.md +53 -0
  2. package/dist/actor-D_54lz_1.d.ts +310 -0
  3. package/dist/actor-D_54lz_1.d.ts.map +1 -0
  4. package/dist/actor-client.d.ts +13 -4
  5. package/dist/actor-client.d.ts.map +1 -1
  6. package/dist/actor-client.js +63 -7
  7. package/dist/actor-client.js.map +1 -1
  8. package/dist/actor-react.d.ts +5 -4
  9. package/dist/actor-react.d.ts.map +1 -1
  10. package/dist/actor-react.js +16 -2
  11. package/dist/actor-react.js.map +1 -1
  12. package/dist/actor.d.ts +2 -176
  13. package/dist/actor.js +13 -2
  14. package/dist/actor.js.map +1 -1
  15. package/dist/ai-server.d.ts +1 -1
  16. package/dist/ai-server.js +2 -2
  17. package/dist/ai.d.ts +1 -1
  18. package/dist/ai.js +1 -1
  19. package/dist/client-Bf6uSEAk.js +1342 -0
  20. package/dist/client-Bf6uSEAk.js.map +1 -0
  21. package/dist/client-P_NNNRM-.d.ts +243 -0
  22. package/dist/client-P_NNNRM-.d.ts.map +1 -0
  23. package/dist/client.d.ts +2 -202
  24. package/dist/client.js +2 -1026
  25. package/dist/errors-DCk6ch5n.js.map +1 -1
  26. package/dist/errors-DvhSXnxk.d.ts +28 -0
  27. package/dist/errors-DvhSXnxk.d.ts.map +1 -0
  28. package/dist/index.d.ts +3 -35
  29. package/dist/{internal-DRXJ56EI.js → internal-Dq2qYxou.js} +2 -2
  30. package/dist/{internal-DRXJ56EI.js.map → internal-Dq2qYxou.js.map} +1 -1
  31. package/dist/platform-B4TnJtWu.js +34 -0
  32. package/dist/platform-B4TnJtWu.js.map +1 -0
  33. package/dist/react.d.ts +3 -1
  34. package/dist/react.d.ts.map +1 -1
  35. package/dist/react.js +2 -1
  36. package/dist/react.js.map +1 -1
  37. package/dist/scheduler-qstash.d.ts +2 -2
  38. package/dist/scheduler-qstash.js +3 -2
  39. package/dist/scheduler-qstash.js.map +1 -1
  40. package/dist/scheduler-vercel.d.ts +2 -2
  41. package/dist/scheduler-vercel.js +2 -2
  42. package/dist/{server-CBET-jSz.js → server-Dkz2a84E.js} +295 -133
  43. package/dist/server-Dkz2a84E.js.map +1 -0
  44. package/dist/{server-CKY3_lbw.d.ts → server-DwPrMqHB.d.ts} +4 -2
  45. package/dist/server-DwPrMqHB.d.ts.map +1 -0
  46. package/dist/server.d.ts +2 -2
  47. package/dist/server.js +1 -1
  48. package/dist/{store-DGHeBtIQ.d.ts → store-DtDOWLSn.d.ts} +4 -5
  49. package/dist/{store-DGHeBtIQ.d.ts.map → store-DtDOWLSn.d.ts.map} +1 -1
  50. package/dist/store-N8PXxDAS.js.map +1 -1
  51. package/dist/store-memory.d.ts +1 -1
  52. package/dist/store-memory.js +1 -1
  53. package/dist/{store-polling-6DW7F1DT.js → store-polling-CmxUbV93.js} +56 -7
  54. package/dist/store-polling-CmxUbV93.js.map +1 -0
  55. package/dist/store-postgres.d.ts +6 -4
  56. package/dist/store-postgres.d.ts.map +1 -1
  57. package/dist/store-postgres.js +701 -53
  58. package/dist/store-postgres.js.map +1 -1
  59. package/dist/store-presence-polling-C7-XZyW9.js +94 -0
  60. package/dist/store-presence-polling-C7-XZyW9.js.map +1 -0
  61. package/dist/store-redis-http.d.ts +2 -2
  62. package/dist/store-redis-http.d.ts.map +1 -1
  63. package/dist/store-redis-http.js +184 -38
  64. package/dist/store-redis-http.js.map +1 -1
  65. package/dist/{store-redis-core-z-ykbyMg.js → store-redis-notify-BUCyXOn0.js} +491 -27
  66. package/dist/store-redis-notify-BUCyXOn0.js.map +1 -0
  67. package/dist/store-redis.d.ts +5 -13
  68. package/dist/store-redis.d.ts.map +1 -1
  69. package/dist/store-redis.js +27 -271
  70. package/dist/store-redis.js.map +1 -1
  71. package/dist/store-sqlite.d.ts +1 -1
  72. package/dist/store-sqlite.js +1 -1
  73. package/dist/{wire--yji6mO3.js → wire-BO5wWCb1.js} +18 -16
  74. package/dist/wire-BO5wWCb1.js.map +1 -0
  75. package/docs/actors/04-routes.mdx +40 -8
  76. package/docs/guides/03-react.mdx +52 -10
  77. package/docs/guides/05-production.mdx +54 -5
  78. package/docs/guides/09-presence.mdx +12 -7
  79. package/docs/guides/10-transports.mdx +85 -22
  80. package/docs/reference/01-api.mdx +90 -28
  81. package/docs/reference/02-errors.mdx +45 -11
  82. package/examples/playground/package.json +1 -1
  83. package/package.json +1 -1
  84. package/src/actor-client.ts +114 -14
  85. package/src/actor-react.ts +18 -3
  86. package/src/actor.ts +16 -1
  87. package/src/client-errors.ts +51 -0
  88. package/src/client.ts +577 -175
  89. package/src/errors.ts +4 -9
  90. package/src/index.ts +1 -1
  91. package/src/internal.ts +1 -1
  92. package/src/postgres-notification-scope.ts +134 -0
  93. package/src/postgres-notifications.ts +244 -0
  94. package/src/postgres-pool.ts +65 -0
  95. package/src/postgres-resources.ts +185 -0
  96. package/src/presence-recovery.ts +120 -0
  97. package/src/react.ts +3 -0
  98. package/src/redis-http-subscriptions.ts +199 -0
  99. package/src/server-fetch.ts +51 -23
  100. package/src/server.ts +146 -19
  101. package/src/session-socket.ts +241 -88
  102. package/src/sse.ts +17 -0
  103. package/src/store-polling.ts +32 -12
  104. package/src/store-postgres.ts +292 -75
  105. package/src/store-presence-polling.ts +125 -0
  106. package/src/store-redis-core.ts +37 -30
  107. package/src/store-redis-http.ts +51 -45
  108. package/src/store-redis-notify.ts +464 -0
  109. package/src/store-redis.ts +9 -364
  110. package/src/store.ts +3 -4
  111. package/src/stream-activity.ts +32 -0
  112. package/src/wire.ts +39 -15
  113. package/dist/actor-shared-BACubf4x.d.ts +0 -136
  114. package/dist/actor-shared-BACubf4x.d.ts.map +0 -1
  115. package/dist/actor.d.ts.map +0 -1
  116. package/dist/client.d.ts.map +0 -1
  117. package/dist/client.js.map +0 -1
  118. package/dist/index.d.ts.map +0 -1
  119. package/dist/server-CBET-jSz.js.map +0 -1
  120. package/dist/server-CKY3_lbw.d.ts.map +0 -1
  121. package/dist/store-polling-6DW7F1DT.js.map +0 -1
  122. package/dist/store-redis-core-z-ykbyMg.js.map +0 -1
  123. 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-DRXJ56EI.js";
4
- import { t as defaultSleep } from "./store-polling-6DW7F1DT.js";
5
- 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--yji6mO3.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-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 — and `sessionsSocket`, the multiplexed
120
- * frame handler riding it. Internal module: `handle`'s upgrade lane is
121
- * the sanctioned mount; only `A2Socket` (the structural socket the
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 pump(id, subscription);
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 target = session;
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
- try {
326
- const events = parsePushEvents(frame["events"]);
327
- const denial = await options?.gatePush?.(sessionId, events, void 0) ?? null;
328
- if (denial !== null) {
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
- if (typeof subscription.target.setPresence !== "function") {
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 attach = (socket) => {
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: async (sessionId, events, pushedPresence) => {
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
- const store = resolveStore();
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 inner = (await store).stream(nsId(id), { startAfter });
1352
- for await (const event of inner) yield toSessionEvent(id, event);
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
- return outer();
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
- const sleeper = defaultSleep(POLL_TIMINGS.idleCeilingMs);
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 (!unsubscribe) for (const patch of diffRows(await readRows())) yield patch;
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 events.return?.();
1714
+ await close();
1715
+ wakeQueue = null;
1569
1716
  }
1570
1717
  };
1571
- return outer();
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, platformVercelOidcToken as o, setServerFetchHooks as r, createServer as t };
2322
+ export { parsePresenceSibling as a, sseResponse as i, deliverSchedulerAppend as n, setServerFetchHooks as r, createServer as t };
2161
2323
 
2162
- //# sourceMappingURL=server-CBET-jSz.js.map
2324
+ //# sourceMappingURL=server-Dkz2a84E.js.map