experimental-a2 0.17.0 → 0.18.1

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 (221) hide show
  1. package/CHANGELOG.md +32 -0
  2. package/README.md +1 -1
  3. package/dist/{actor-DBbe5xeE.d.ts → actor-C7b-1arn.d.ts} +5 -5
  4. package/dist/{actor-DBbe5xeE.d.ts.map → actor-C7b-1arn.d.ts.map} +1 -1
  5. package/dist/actor-client.d.ts +1 -1
  6. package/dist/actor-react.d.ts +3 -3
  7. package/dist/actor-react.js +1 -1
  8. package/dist/actor.d.ts +1 -1
  9. package/dist/actor.js +2 -2
  10. package/dist/{ai-vpGtcaCB.d.ts → ai-BMLlI2Ff.d.ts} +3 -3
  11. package/dist/{ai-vpGtcaCB.d.ts.map → ai-BMLlI2Ff.d.ts.map} +1 -1
  12. package/dist/{ai-UrWfKeuv.js → ai-D6mGlO8j.js} +2 -2
  13. package/dist/{ai-UrWfKeuv.js.map → ai-D6mGlO8j.js.map} +1 -1
  14. package/dist/ai-client.d.ts +2 -2
  15. package/dist/ai-client.js +2 -2
  16. package/dist/ai-fx.browser.d.ts +1 -0
  17. package/dist/ai-fx.browser.js +6 -0
  18. package/dist/ai-fx.browser.js.map +1 -0
  19. package/dist/ai-fx.d.ts +10 -0
  20. package/dist/ai-fx.d.ts.map +1 -0
  21. package/dist/ai-fx.js +692 -0
  22. package/dist/ai-fx.js.map +1 -0
  23. package/dist/ai-harness-B2suP2Qh.d.ts +26 -0
  24. package/dist/ai-harness-B2suP2Qh.d.ts.map +1 -0
  25. package/dist/ai-harness-BuLh_EYi.js +273 -0
  26. package/dist/ai-harness-BuLh_EYi.js.map +1 -0
  27. package/dist/ai-pi.browser.d.ts +1 -0
  28. package/dist/ai-pi.browser.js +6 -0
  29. package/dist/ai-pi.browser.js.map +1 -0
  30. package/dist/ai-pi.d.ts +17 -0
  31. package/dist/ai-pi.d.ts.map +1 -0
  32. package/dist/ai-pi.js +950 -0
  33. package/dist/ai-pi.js.map +1 -0
  34. package/dist/ai-react.d.ts +5 -5
  35. package/dist/ai-react.js +2 -2
  36. package/dist/ai-server.d.ts +47 -25
  37. package/dist/ai-server.d.ts.map +1 -1
  38. package/dist/ai-server.js +113 -40
  39. package/dist/ai-server.js.map +1 -1
  40. package/dist/ai-tool-input-DmSqMUQi.js +40 -0
  41. package/dist/ai-tool-input-DmSqMUQi.js.map +1 -0
  42. package/dist/ai.d.ts +1 -1
  43. package/dist/ai.js +1 -1
  44. package/dist/bounded-cache-CPHWF-8h.js +41 -0
  45. package/dist/bounded-cache-CPHWF-8h.js.map +1 -0
  46. package/dist/{client-COaiaDub.js → client-BhHLoNKF.js} +3 -3
  47. package/dist/{client-COaiaDub.js.map → client-BhHLoNKF.js.map} +1 -1
  48. package/dist/{client-qRnRUzBS.d.ts → client-h_DKFAjj.d.ts} +3 -3
  49. package/dist/{client-qRnRUzBS.d.ts.map → client-h_DKFAjj.d.ts.map} +1 -1
  50. package/dist/client.d.ts +3 -3
  51. package/dist/client.js +1 -1
  52. package/dist/index.d.ts +5 -5
  53. package/dist/{initial-snapshot-BrLso5x_.d.ts → initial-snapshot-Bx-c0pDP.d.ts} +2 -2
  54. package/dist/{initial-snapshot-BrLso5x_.d.ts.map → initial-snapshot-Bx-c0pDP.d.ts.map} +1 -1
  55. package/dist/{internal-CbVvEwad.js → internal-CscSY7K_.js} +11 -3
  56. package/dist/internal-CscSY7K_.js.map +1 -0
  57. package/dist/otel.d.ts +1 -1
  58. package/dist/{react-BbJ-g6Uu.d.ts → react-BBXgHWFc.d.ts} +4 -4
  59. package/dist/{react-BbJ-g6Uu.d.ts.map → react-BBXgHWFc.d.ts.map} +1 -1
  60. package/dist/react.d.ts +1 -1
  61. package/dist/react.js +1 -1
  62. package/dist/reducer-BAXc8JyF.js.map +1 -1
  63. package/dist/{reducer-BYu6DT7h.d.ts → reducer-CqA59Q6U.d.ts} +2 -2
  64. package/dist/{reducer-BYu6DT7h.d.ts.map → reducer-CqA59Q6U.d.ts.map} +1 -1
  65. package/dist/scheduler-qstash.d.ts +2 -2
  66. package/dist/scheduler-qstash.js +3 -3
  67. package/dist/scheduler-qstash.js.map +1 -1
  68. package/dist/scheduler-vercel.d.ts +7 -7
  69. package/dist/scheduler-vercel.d.ts.map +1 -1
  70. package/dist/scheduler-vercel.js +9 -10
  71. package/dist/scheduler-vercel.js.map +1 -1
  72. package/dist/{server-Rj64fNvX.js → server-D-Y4pqwu.js} +126 -67
  73. package/dist/server-D-Y4pqwu.js.map +1 -0
  74. package/dist/{server-CGM8WzRG.d.ts → server-DYAATFuT.d.ts} +42 -7
  75. package/dist/server-DYAATFuT.d.ts.map +1 -0
  76. package/dist/server.d.ts +4 -4
  77. package/dist/server.js +1 -1
  78. package/dist/{session-clear-Dshpuhff.d.ts → session-clear-C-QwTXV1.d.ts} +2 -2
  79. package/dist/session-clear-C-QwTXV1.d.ts.map +1 -0
  80. package/dist/{store-Gpkq7WBo.d.ts → store-CyLUy86v.d.ts} +53 -5
  81. package/dist/store-CyLUy86v.d.ts.map +1 -0
  82. package/dist/store-N8PXxDAS.js.map +1 -1
  83. package/dist/store-memory.d.ts +1 -1
  84. package/dist/store-memory.js +56 -11
  85. package/dist/store-memory.js.map +1 -1
  86. package/dist/{store-polling-4_y8lETm.js → store-polling-7WJV0YWP.js} +2 -2
  87. package/dist/{store-polling-4_y8lETm.js.map → store-polling-7WJV0YWP.js.map} +1 -1
  88. package/dist/store-postgres.d.ts +1 -1
  89. package/dist/store-postgres.d.ts.map +1 -1
  90. package/dist/store-postgres.js +106 -25
  91. package/dist/store-postgres.js.map +1 -1
  92. package/dist/{store-presence-polling-Q1C-k2dd.js → store-presence-polling-B1ixeqIS.js} +3 -3
  93. package/dist/{store-presence-polling-Q1C-k2dd.js.map → store-presence-polling-B1ixeqIS.js.map} +1 -1
  94. package/dist/store-redis-http.d.ts +1 -1
  95. package/dist/store-redis-http.js +3 -3
  96. package/dist/{store-redis-notify-tqWNnMRu.js → store-redis-notify-CM0AfLY0.js} +271 -101
  97. package/dist/store-redis-notify-CM0AfLY0.js.map +1 -0
  98. package/dist/store-redis.d.ts +1 -1
  99. package/dist/store-redis.js +1 -1
  100. package/dist/store-sqlite.d.ts +1 -1
  101. package/dist/store-sqlite.js +66 -21
  102. package/dist/store-sqlite.js.map +1 -1
  103. package/dist/{telemetry-cZH-cibj.d.ts → telemetry-Cyfqo6Qt.d.ts} +2 -2
  104. package/dist/telemetry-Cyfqo6Qt.d.ts.map +1 -0
  105. package/dist/{wire-DjfHOvdW.js → wire-BKxH1vbP.js} +2 -2
  106. package/dist/{wire-DjfHOvdW.js.map → wire-BKxH1vbP.js.map} +1 -1
  107. package/docs/01-quickstart.mdx +49 -83
  108. package/docs/{guides/07-examples.mdx → 02-examples.mdx} +10 -20
  109. package/docs/actors/01-introduction.mdx +48 -86
  110. package/docs/actors/02-concurrency.mdx +43 -70
  111. package/docs/actors/03-timers.mdx +28 -42
  112. package/docs/actors/04-routes.mdx +110 -150
  113. package/docs/actors/meta.ts +1 -1
  114. package/docs/agents/01-introduction.mdx +257 -0
  115. package/docs/agents/02-tools.mdx +247 -0
  116. package/docs/agents/03-conversations.mdx +538 -0
  117. package/docs/agents/04-harnesses.mdx +136 -0
  118. package/docs/agents/meta.ts +1 -0
  119. package/docs/client/01-react.mdx +294 -0
  120. package/docs/client/02-transports.mdx +86 -0
  121. package/docs/client/03-presence.mdx +224 -0
  122. package/docs/client/meta.ts +1 -0
  123. package/docs/contract/01-events.mdx +57 -0
  124. package/docs/contract/02-state.mdx +142 -0
  125. package/docs/contract/03-sessions.mdx +175 -0
  126. package/docs/index.mdx +109 -145
  127. package/docs/reference/01-core.mdx +139 -0
  128. package/docs/reference/02-server.mdx +860 -0
  129. package/docs/reference/03-client.mdx +301 -0
  130. package/docs/reference/04-ai.mdx +731 -0
  131. package/docs/reference/05-adapters.mdx +351 -0
  132. package/docs/reference/06-errors.mdx +138 -0
  133. package/docs/reference/meta.ts +1 -1
  134. package/docs/server/01-handlers.mdx +200 -0
  135. package/docs/server/02-timers.mdx +130 -0
  136. package/docs/server/03-cancellation.mdx +142 -0
  137. package/docs/server/04-routes.mdx +243 -0
  138. package/docs/server/05-production.mdx +323 -0
  139. package/docs/server/06-guarantees.mdx +132 -0
  140. package/examples/playground/DEPLOY.md +3 -5
  141. package/examples/playground/app/a0/[appId]/page.tsx +12 -13
  142. package/examples/playground/app/a0/app-list.tsx +9 -2
  143. package/examples/playground/app/a0/app-view.tsx +9 -1
  144. package/examples/playground/app/a0/apps/[appId]/route.ts +10 -5
  145. package/examples/playground/app/a0/apps.test.ts +12 -0
  146. package/examples/playground/app/a0/apps.ts +22 -7
  147. package/examples/playground/app/a0/chat.tsx +47 -1
  148. package/examples/playground/app/a0/devtools.tsx +2 -8
  149. package/examples/playground/app/a0/fx/events/route.ts +9 -0
  150. package/examples/playground/app/a0/fx/scheduler/route.ts +7 -0
  151. package/examples/playground/app/a0/fx/short/events/route.ts +11 -0
  152. package/examples/playground/app/a0/model.test.ts +18 -1
  153. package/examples/playground/app/a0/model.ts +47 -10
  154. package/examples/playground/app/a0/no-photon.cjs +4 -0
  155. package/examples/playground/app/a0/no-quickjs.cjs +3 -0
  156. package/examples/playground/app/a0/pi/events/route.ts +9 -0
  157. package/examples/playground/app/a0/pi/scheduler/route.ts +7 -0
  158. package/examples/playground/app/a0/pi/short/events/route.ts +11 -0
  159. package/examples/playground/app/a0/sandbox-log.ts +10 -4
  160. package/examples/playground/app/a0/sandbox-session.ts +152 -0
  161. package/examples/playground/app/a0/scheduler/route.ts +2 -2
  162. package/examples/playground/app/a0/server.ts +98 -54
  163. package/examples/playground/app/a0/session.ts +17 -5
  164. package/examples/playground/app/a0/shell.tsx +11 -3
  165. package/examples/playground/app/agent/ingress.test.ts +6 -3
  166. package/examples/playground/app/agent/server.ts +9 -6
  167. package/examples/playground/app/agent/telemetry.test.ts +71 -50
  168. package/examples/playground/app/canvas/[canvasId]/canvas-client.tsx +1 -1
  169. package/examples/playground/app/chat/server.test.ts +30 -26
  170. package/examples/playground/app/chat/server.ts +46 -46
  171. package/examples/playground/next.config.ts +17 -5
  172. package/examples/playground/package.json +4 -1
  173. package/examples/playground/vercel.json +18 -0
  174. package/package.json +27 -3
  175. package/src/ai-agent.ts +271 -90
  176. package/src/ai-fx.browser.ts +5 -0
  177. package/src/ai-fx.ts +912 -0
  178. package/src/ai-harness.ts +518 -0
  179. package/src/ai-model-metadata.ts +11 -6
  180. package/src/ai-pi.browser.ts +5 -0
  181. package/src/ai-pi.ts +1322 -0
  182. package/src/ai-sdk-step.ts +2 -2
  183. package/src/ai-server.ts +3 -1
  184. package/src/ai-tool-input.ts +45 -0
  185. package/src/internal.ts +13 -0
  186. package/src/libfx.d.ts +34 -0
  187. package/src/reducer.ts +1 -1
  188. package/src/scheduler-qstash.ts +5 -5
  189. package/src/scheduler-vercel.ts +14 -15
  190. package/src/server.ts +233 -29
  191. package/src/store-memory.ts +92 -16
  192. package/src/store-postgres.ts +156 -25
  193. package/src/store-redis-core.ts +322 -110
  194. package/src/store-redis-notify.ts +2 -0
  195. package/src/store-sqlite.ts +121 -28
  196. package/src/store.ts +55 -3
  197. package/src/telemetry.ts +1 -0
  198. package/dist/internal-CbVvEwad.js.map +0 -1
  199. package/dist/server-CGM8WzRG.d.ts.map +0 -1
  200. package/dist/server-Rj64fNvX.js.map +0 -1
  201. package/dist/session-clear-Dshpuhff.d.ts.map +0 -1
  202. package/dist/store-Gpkq7WBo.d.ts.map +0 -1
  203. package/dist/store-redis-notify-tqWNnMRu.js.map +0 -1
  204. package/dist/telemetry-cZH-cibj.d.ts.map +0 -1
  205. package/docs/concepts/01-contracts.mdx +0 -237
  206. package/docs/concepts/02-handlers.mdx +0 -284
  207. package/docs/concepts/03-durability.mdx +0 -314
  208. package/docs/concepts/04-state.mdx +0 -209
  209. package/docs/guides/01-timers.mdx +0 -193
  210. package/docs/guides/02-cancellation.mdx +0 -161
  211. package/docs/guides/03-react.mdx +0 -324
  212. package/docs/guides/04-local-first.mdx +0 -47
  213. package/docs/guides/05-production.mdx +0 -534
  214. package/docs/guides/06-ai-agents.mdx +0 -1408
  215. package/docs/guides/08-application-data.mdx +0 -113
  216. package/docs/guides/09-presence.mdx +0 -262
  217. package/docs/guides/10-transports.mdx +0 -221
  218. package/docs/reference/01-api.mdx +0 -2358
  219. package/docs/reference/02-errors.mdx +0 -150
  220. /package/docs/{concepts → contract}/meta.ts +0 -0
  221. /package/docs/{guides → server}/meta.ts +0 -0
@@ -1,5 +1,5 @@
1
- import { i as POLL_TIMINGS } from "./internal-CbVvEwad.js";
2
- import { t as defaultSleep } from "./store-polling-4_y8lETm.js";
1
+ import { i as POLL_TIMINGS } from "./internal-CscSY7K_.js";
2
+ import { t as defaultSleep } from "./store-polling-7WJV0YWP.js";
3
3
  //#region src/store-presence-polling.ts
4
4
  const key = ({ participant, field }) => JSON.stringify([participant, field]);
5
5
  function pollPresence({ read, deliver, sleep = defaultSleep }) {
@@ -91,4 +91,4 @@ function pollPresence({ read, deliver, sleep = defaultSleep }) {
91
91
  //#endregion
92
92
  export { pollPresence as t };
93
93
 
94
- //# sourceMappingURL=store-presence-polling-Q1C-k2dd.js.map
94
+ //# sourceMappingURL=store-presence-polling-B1ixeqIS.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"store-presence-polling-Q1C-k2dd.js","names":[],"sources":["../src/store-presence-polling.ts"],"sourcesContent":["import type { PresencePatch } from './contract.ts'\nimport { POLL_TIMINGS } from './internal.ts'\nimport { defaultSleep, type Sleeper } from './store-polling.ts'\nimport type { PresenceRow } from './store.ts'\n\nconst key = ({\n participant,\n field,\n}: Pick<PresenceRow, 'participant' | 'field'>): string =>\n JSON.stringify([participant, field])\n\nexport function pollPresence({\n read,\n deliver,\n sleep = defaultSleep,\n}: {\n read: () => Promise<PresenceRow[]>\n deliver: (patch: PresencePatch) => void\n sleep?: (ms: number) => Sleeper\n}): {\n ready: Promise<void>\n update(patch: PresencePatch): void\n wake(): void\n stop(): void\n} {\n let stopped = false\n let pending: Sleeper | undefined\n let interval = Math.min(\n POLL_TIMINGS.activeFloorMs,\n POLL_TIMINGS.idleCeilingMs,\n )\n let version = 0\n const seen = new Map<string, Omit<PresenceRow, 'expiresAt'>>()\n const activate = (): void => {\n version += 1\n interval = Math.min(POLL_TIMINGS.activeFloorMs, POLL_TIMINGS.idleCeilingMs)\n pending?.cancel()\n }\n const ready = read()\n .then((rows) => {\n if (stopped) return\n for (const row of rows) seen.set(key(row), row)\n return undefined\n })\n // Seeding is best effort; foreground snapshots still report read errors.\n .catch(() => undefined)\n\n const run = async (): Promise<void> => {\n await ready\n for (;;) {\n if (stopped) return\n const before = version\n pending = sleep(interval)\n // oxlint-disable-next-line no-await-in-loop -- adaptive presence polling\n await pending.promise\n if (stopped) return\n try {\n const reading = version\n // oxlint-disable-next-line no-await-in-loop -- read each new presence snapshot\n const rows = await read()\n if (stopped) return\n if (version !== reading) continue\n const live = new Set<string>()\n let changed = false\n for (const row of rows) {\n const id = key(row)\n live.add(id)\n const prior = seen.get(id)\n seen.set(id, row)\n if (\n prior?.seen === row.seen &&\n prior.at.getTime() === row.at.getTime() &&\n JSON.stringify(prior.value) === JSON.stringify(row.value)\n )\n continue\n changed = true\n deliver({\n participant: row.participant,\n values: { [row.field]: row.value },\n seen: row.seen,\n at: row.at,\n })\n }\n for (const [id, row] of seen) {\n if (live.has(id)) continue\n seen.delete(id)\n changed = true\n deliver({\n participant: row.participant,\n values: { [row.field]: null },\n seen: row.seen,\n at: new Date(),\n })\n }\n if (changed) activate()\n else if (version === before)\n interval = Math.min(interval * 2, POLL_TIMINGS.idleCeilingMs)\n } catch {\n interval = POLL_TIMINGS.idleCeilingMs\n }\n }\n }\n void run()\n return {\n ready,\n wake: activate,\n update(patch) {\n for (const [field, value] of Object.entries(patch.values)) {\n seen.set(key({ participant: patch.participant, field }), {\n participant: patch.participant,\n field,\n value,\n seen: patch.seen,\n at: patch.at,\n })\n }\n activate()\n },\n stop() {\n stopped = true\n pending?.cancel()\n seen.clear()\n },\n }\n}\n"],"mappings":";;;AAKA,MAAM,OAAO,EACX,aACA,YAEA,KAAK,UAAU,CAAC,aAAa,KAAK,CAAC;AAErC,SAAgB,aAAa,EAC3B,MACA,SACA,QAAQ,gBAUR;CACA,IAAI,UAAU;CACd,IAAI;CACJ,IAAI,WAAW,KAAK,IAClB,aAAa,eACb,aAAa,aACf;CACA,IAAI,UAAU;CACd,MAAM,uBAAO,IAAI,IAA4C;CAC7D,MAAM,iBAAuB;EAC3B,WAAW;EACX,WAAW,KAAK,IAAI,aAAa,eAAe,aAAa,aAAa;EAC1E,SAAS,OAAO;CAClB;CACA,MAAM,QAAQ,KAAK,CAAC,CACjB,MAAM,SAAS;EACd,IAAI,SAAS;EACb,KAAK,MAAM,OAAO,MAAM,KAAK,IAAI,IAAI,GAAG,GAAG,GAAG;CAEhD,CAAC,CAAC,CAED,YAAY,KAAA,CAAS;CAExB,MAAM,MAAM,YAA2B;EACrC,MAAM;EACN,SAAS;GACP,IAAI,SAAS;GACb,MAAM,SAAS;GACf,UAAU,MAAM,QAAQ;GAExB,MAAM,QAAQ;GACd,IAAI,SAAS;GACb,IAAI;IACF,MAAM,UAAU;IAEhB,MAAM,OAAO,MAAM,KAAK;IACxB,IAAI,SAAS;IACb,IAAI,YAAY,SAAS;IACzB,MAAM,uBAAO,IAAI,IAAY;IAC7B,IAAI,UAAU;IACd,KAAK,MAAM,OAAO,MAAM;KACtB,MAAM,KAAK,IAAI,GAAG;KAClB,KAAK,IAAI,EAAE;KACX,MAAM,QAAQ,KAAK,IAAI,EAAE;KACzB,KAAK,IAAI,IAAI,GAAG;KAChB,IACE,OAAO,SAAS,IAAI,QACpB,MAAM,GAAG,QAAQ,MAAM,IAAI,GAAG,QAAQ,KACtC,KAAK,UAAU,MAAM,KAAK,MAAM,KAAK,UAAU,IAAI,KAAK,GAExD;KACF,UAAU;KACV,QAAQ;MACN,aAAa,IAAI;MACjB,QAAQ,GAAG,IAAI,QAAQ,IAAI,MAAM;MACjC,MAAM,IAAI;MACV,IAAI,IAAI;KACV,CAAC;IACH;IACA,KAAK,MAAM,CAAC,IAAI,QAAQ,MAAM;KAC5B,IAAI,KAAK,IAAI,EAAE,GAAG;KAClB,KAAK,OAAO,EAAE;KACd,UAAU;KACV,QAAQ;MACN,aAAa,IAAI;MACjB,QAAQ,GAAG,IAAI,QAAQ,KAAK;MAC5B,MAAM,IAAI;MACV,oBAAI,IAAI,KAAK;KACf,CAAC;IACH;IACA,IAAI,SAAS,SAAS;SACjB,IAAI,YAAY,QACnB,WAAW,KAAK,IAAI,WAAW,GAAG,aAAa,aAAa;GAChE,QAAQ;IACN,WAAW,aAAa;GAC1B;EACF;CACF;CACA,IAAS;CACT,OAAO;EACL;EACA,MAAM;EACN,OAAO,OAAO;GACZ,KAAK,MAAM,CAAC,OAAO,UAAU,OAAO,QAAQ,MAAM,MAAM,GACtD,KAAK,IAAI,IAAI;IAAE,aAAa,MAAM;IAAa;GAAM,CAAC,GAAG;IACvD,aAAa,MAAM;IACnB;IACA;IACA,MAAM,MAAM;IACZ,IAAI,MAAM;GACZ,CAAC;GAEH,SAAS;EACX;EACA,OAAO;GACL,UAAU;GACV,SAAS,OAAO;GAChB,KAAK,MAAM;EACb;CACF;AACF"}
1
+ {"version":3,"file":"store-presence-polling-B1ixeqIS.js","names":[],"sources":["../src/store-presence-polling.ts"],"sourcesContent":["import type { PresencePatch } from './contract.ts'\nimport { POLL_TIMINGS } from './internal.ts'\nimport { defaultSleep, type Sleeper } from './store-polling.ts'\nimport type { PresenceRow } from './store.ts'\n\nconst key = ({\n participant,\n field,\n}: Pick<PresenceRow, 'participant' | 'field'>): string =>\n JSON.stringify([participant, field])\n\nexport function pollPresence({\n read,\n deliver,\n sleep = defaultSleep,\n}: {\n read: () => Promise<PresenceRow[]>\n deliver: (patch: PresencePatch) => void\n sleep?: (ms: number) => Sleeper\n}): {\n ready: Promise<void>\n update(patch: PresencePatch): void\n wake(): void\n stop(): void\n} {\n let stopped = false\n let pending: Sleeper | undefined\n let interval = Math.min(\n POLL_TIMINGS.activeFloorMs,\n POLL_TIMINGS.idleCeilingMs,\n )\n let version = 0\n const seen = new Map<string, Omit<PresenceRow, 'expiresAt'>>()\n const activate = (): void => {\n version += 1\n interval = Math.min(POLL_TIMINGS.activeFloorMs, POLL_TIMINGS.idleCeilingMs)\n pending?.cancel()\n }\n const ready = read()\n .then((rows) => {\n if (stopped) return\n for (const row of rows) seen.set(key(row), row)\n return undefined\n })\n // Seeding is best effort; foreground snapshots still report read errors.\n .catch(() => undefined)\n\n const run = async (): Promise<void> => {\n await ready\n for (;;) {\n if (stopped) return\n const before = version\n pending = sleep(interval)\n // oxlint-disable-next-line no-await-in-loop -- adaptive presence polling\n await pending.promise\n if (stopped) return\n try {\n const reading = version\n // oxlint-disable-next-line no-await-in-loop -- read each new presence snapshot\n const rows = await read()\n if (stopped) return\n if (version !== reading) continue\n const live = new Set<string>()\n let changed = false\n for (const row of rows) {\n const id = key(row)\n live.add(id)\n const prior = seen.get(id)\n seen.set(id, row)\n if (\n prior?.seen === row.seen &&\n prior.at.getTime() === row.at.getTime() &&\n JSON.stringify(prior.value) === JSON.stringify(row.value)\n )\n continue\n changed = true\n deliver({\n participant: row.participant,\n values: { [row.field]: row.value },\n seen: row.seen,\n at: row.at,\n })\n }\n for (const [id, row] of seen) {\n if (live.has(id)) continue\n seen.delete(id)\n changed = true\n deliver({\n participant: row.participant,\n values: { [row.field]: null },\n seen: row.seen,\n at: new Date(),\n })\n }\n if (changed) activate()\n else if (version === before)\n interval = Math.min(interval * 2, POLL_TIMINGS.idleCeilingMs)\n } catch {\n interval = POLL_TIMINGS.idleCeilingMs\n }\n }\n }\n void run()\n return {\n ready,\n wake: activate,\n update(patch) {\n for (const [field, value] of Object.entries(patch.values)) {\n seen.set(key({ participant: patch.participant, field }), {\n participant: patch.participant,\n field,\n value,\n seen: patch.seen,\n at: patch.at,\n })\n }\n activate()\n },\n stop() {\n stopped = true\n pending?.cancel()\n seen.clear()\n },\n }\n}\n"],"mappings":";;;AAKA,MAAM,OAAO,EACX,aACA,YAEA,KAAK,UAAU,CAAC,aAAa,KAAK,CAAC;AAErC,SAAgB,aAAa,EAC3B,MACA,SACA,QAAQ,gBAUR;CACA,IAAI,UAAU;CACd,IAAI;CACJ,IAAI,WAAW,KAAK,IAClB,aAAa,eACb,aAAa,aACf;CACA,IAAI,UAAU;CACd,MAAM,uBAAO,IAAI,IAA4C;CAC7D,MAAM,iBAAuB;EAC3B,WAAW;EACX,WAAW,KAAK,IAAI,aAAa,eAAe,aAAa,aAAa;EAC1E,SAAS,OAAO;CAClB;CACA,MAAM,QAAQ,KAAK,CAAC,CACjB,MAAM,SAAS;EACd,IAAI,SAAS;EACb,KAAK,MAAM,OAAO,MAAM,KAAK,IAAI,IAAI,GAAG,GAAG,GAAG;CAEhD,CAAC,CAAC,CAED,YAAY,KAAA,CAAS;CAExB,MAAM,MAAM,YAA2B;EACrC,MAAM;EACN,SAAS;GACP,IAAI,SAAS;GACb,MAAM,SAAS;GACf,UAAU,MAAM,QAAQ;GAExB,MAAM,QAAQ;GACd,IAAI,SAAS;GACb,IAAI;IACF,MAAM,UAAU;IAEhB,MAAM,OAAO,MAAM,KAAK;IACxB,IAAI,SAAS;IACb,IAAI,YAAY,SAAS;IACzB,MAAM,uBAAO,IAAI,IAAY;IAC7B,IAAI,UAAU;IACd,KAAK,MAAM,OAAO,MAAM;KACtB,MAAM,KAAK,IAAI,GAAG;KAClB,KAAK,IAAI,EAAE;KACX,MAAM,QAAQ,KAAK,IAAI,EAAE;KACzB,KAAK,IAAI,IAAI,GAAG;KAChB,IACE,OAAO,SAAS,IAAI,QACpB,MAAM,GAAG,QAAQ,MAAM,IAAI,GAAG,QAAQ,KACtC,KAAK,UAAU,MAAM,KAAK,MAAM,KAAK,UAAU,IAAI,KAAK,GAExD;KACF,UAAU;KACV,QAAQ;MACN,aAAa,IAAI;MACjB,QAAQ,GAAG,IAAI,QAAQ,IAAI,MAAM;MACjC,MAAM,IAAI;MACV,IAAI,IAAI;KACV,CAAC;IACH;IACA,KAAK,MAAM,CAAC,IAAI,QAAQ,MAAM;KAC5B,IAAI,KAAK,IAAI,EAAE,GAAG;KAClB,KAAK,OAAO,EAAE;KACd,UAAU;KACV,QAAQ;MACN,aAAa,IAAI;MACjB,QAAQ,GAAG,IAAI,QAAQ,KAAK;MAC5B,MAAM,IAAI;MACV,oBAAI,IAAI,KAAK;KACf,CAAC;IACH;IACA,IAAI,SAAS,SAAS;SACjB,IAAI,YAAY,QACnB,WAAW,KAAK,IAAI,WAAW,GAAG,aAAa,aAAa;GAChE,QAAQ;IACN,WAAW,aAAa;GAC1B;EACF;CACF;CACA,IAAS;CACT,OAAO;EACL;EACA,MAAM;EACN,OAAO,OAAO;GACZ,KAAK,MAAM,CAAC,OAAO,UAAU,OAAO,QAAQ,MAAM,MAAM,GACtD,KAAK,IAAI,IAAI;IAAE,aAAa,MAAM;IAAa;GAAM,CAAC,GAAG;IACvD,aAAa,MAAM;IACnB;IACA;IACA,MAAM,MAAM;IACZ,IAAI,MAAM;GACZ,CAAC;GAEH,SAAS;EACX;EACA,OAAO;GACL,UAAU;GACV,SAAS,OAAO;GAChB,KAAK,MAAM;EACb;CACF;AACF"}
@@ -1,4 +1,4 @@
1
- import { r as Clock, s as IdSource, t as A2Store } from "./store-Gpkq7WBo.js";
1
+ import { r as Clock, s as IdSource, t as A2Store } from "./store-CyLUy86v.js";
2
2
  //#region src/store-redis-http.d.ts
3
3
  type RedisHttpStoreOptions = {
4
4
  /** REST endpoint, e.g. `process.env.UPSTASH_REDIS_REST_URL`. */
@@ -1,8 +1,8 @@
1
1
  import { i as A2Error } from "./session-clear-DGI4jTQ5.js";
2
- import { r as NOTIFY_TIMINGS } from "./internal-CbVvEwad.js";
3
- import { t as defaultSleep } from "./store-polling-4_y8lETm.js";
2
+ import { r as NOTIFY_TIMINGS } from "./internal-CscSY7K_.js";
3
+ import { t as defaultSleep } from "./store-polling-7WJV0YWP.js";
4
4
  import { n as SYSTEM_CLOCK, t as RANDOM_IDS } from "./store-N8PXxDAS.js";
5
- import { t as createRedisNotifyStore } from "./store-redis-notify-tqWNnMRu.js";
5
+ import { t as createRedisNotifyStore } from "./store-redis-notify-CM0AfLY0.js";
6
6
  //#region src/redis-http-subscriptions.ts
7
7
  function redisHttpSubscriptions({ url, token, onReconnect, fallback }) {
8
8
  const subscriptions = /* @__PURE__ */ new Map();
@@ -1,10 +1,10 @@
1
1
  import { i as A2Error, r as sessionClearedError, s as supersededAttemptError, t as CLEAR_EVENT_TYPE } from "./session-clear-DGI4jTQ5.js";
2
- import { g as nullProtoRecord, r as NOTIFY_TIMINGS, v as stateTailReaders } from "./internal-CbVvEwad.js";
3
- import { i as streamActivities, n as pollingStream, r as createStreamActivity, t as defaultSleep } from "./store-polling-4_y8lETm.js";
2
+ import { _ as nullProtoRecord, r as NOTIFY_TIMINGS, y as stateTailReaders } from "./internal-CscSY7K_.js";
3
+ import { i as streamActivities, n as pollingStream, r as createStreamActivity, t as defaultSleep } from "./store-polling-7WJV0YWP.js";
4
4
  import { n as isSerializedEvent, r as parseSerializedEvent, t as bufferedEvents } from "./stream-buffer-Cx7AAPGq.js";
5
5
  import { t as idempotentReplay } from "./idempotent-replay-JdgMQRRm.js";
6
6
  import { t as decodeReturnedEventIds } from "./store-codec-DTG0Ftek.js";
7
- import { t as pollPresence } from "./store-presence-polling-Q1C-k2dd.js";
7
+ import { t as pollPresence } from "./store-presence-polling-B1ixeqIS.js";
8
8
  //#region src/presence-recovery.ts
9
9
  const key = ({ participant, field }) => JSON.stringify([participant, field]);
10
10
  function presenceRecovery({ read, deliver, isActive }) {
@@ -102,6 +102,22 @@ function presenceRecovery({ read, deliver, isActive }) {
102
102
  const FEED_PAGE_SIZE = 1e3;
103
103
  const READ_CONCURRENCY = 16;
104
104
  const META_CHUNK_SIZE = 1e3;
105
+ /** The metadata columns of a full stored row. */
106
+ const STORED_EVENT_META_SUFFIXES = [
107
+ "p",
108
+ "pa",
109
+ "ri",
110
+ "ch",
111
+ "ce",
112
+ "f",
113
+ "fc",
114
+ "lc",
115
+ "lf",
116
+ "lfa",
117
+ "d",
118
+ "a",
119
+ "e"
120
+ ];
105
121
  /**
106
122
  * Upstash bills every call a script makes, so scripts read the counter and
107
123
  * split with one MGET, read metadata with one HMGET per hash (`read_fields`),
@@ -256,6 +272,32 @@ local function flush_queue()
256
272
  end
257
273
  `;
258
274
  /**
275
+ * Settles one unfinished event for scripts that include ENQUEUE_LUA: it
276
+ * leaves the pending and ready sets, and a lane head hands its lane to the
277
+ * next unfinished event. A dead-lettered successor stays out of the ready set.
278
+ */
279
+ const SETTLE_LUA = `
280
+ local function settle(field, lane, next_field)
281
+ redis.call('ZREM', PENDING, field)
282
+ redis.call('ZREM', READY, field)
283
+ if lane == '' then return end
284
+ local head_key = lane_field(lane, 'head')
285
+ if redis.call('HGET', META, head_key) ~= field then return end
286
+ while next_field do
287
+ local row = read_fields({ next_field }, { 'p', 'f', 'next' })[next_field]
288
+ if not row[1] then
289
+ redis.call('HSET', META, head_key, next_field)
290
+ if not row[2] then
291
+ redis.call('ZADD', READY, tonumber(next_field), next_field)
292
+ end
293
+ return
294
+ end
295
+ next_field = row[3]
296
+ end
297
+ clear_lane(lane)
298
+ end
299
+ `;
300
+ /**
259
301
  * Event-id ownership for scripts that add events. Each script reads the
260
302
  * counter, split, clear boundary, and per-id keys with one MGET and applies
261
303
  * the first three. Returns the owner record, or false, per event, and the
@@ -422,7 +464,7 @@ return { 'ok', tostring(base + 1), pending_after }
422
464
  /**
423
465
  * KEYS: counter, meta, pending, ready, event snapshot pins, snapshot pin counts,
424
466
  * metadata split.
425
- * ARGV: idx, attempt, error, maxFailures, nowMs.
467
+ * ARGV: idx, attempt, error, maxFailures, nowMs, retryAtMs ('' for none).
426
468
  */
427
469
  const FAIL_ATTEMPT_LUA = `
428
470
  local COUNTER, META, SPLIT = KEYS[1], KEYS[2], KEYS[7]
@@ -454,8 +496,14 @@ put(meta_key, field .. ':e', ARGV[3])
454
496
  put(meta_key, field .. ':lf', ARGV[5])
455
497
  put(meta_key, field .. ':lfa', ARGV[2])
456
498
  if dead then put(meta_key, field .. ':f', ARGV[5]) end
499
+ local hold = not dead and ARGV[6] ~= ''
500
+ if hold then put(meta_key, field .. ':ce', ARGV[6]) end
457
501
  flush()
458
- redis.call('HDEL', meta_key, field .. ':ch', field .. ':ce')
502
+ if hold then
503
+ redis.call('HDEL', meta_key, field .. ':ch')
504
+ else
505
+ redis.call('HDEL', meta_key, field .. ':ch', field .. ':ce')
506
+ end
459
507
  if dead then
460
508
  redis.call('ZREM', KEYS[4], field)
461
509
  release_snapshot_pins(KEYS[5], KEYS[6])
@@ -464,23 +512,29 @@ end
464
512
  return { 'failed', tostring(failures) }
465
513
  `;
466
514
  /**
467
- * KEYS: store, counter, meta, pending, ready, metadata split.
468
- * ARGV: holder, nowMs, expiresAtMs, excludedCount, excluded indexes.
515
+ * KEYS: store, counter, meta, pending, ready, metadata split, snapshot pin
516
+ * counts.
517
+ * ARGV: holder, nowMs, expiresAtMs, excludedCount, excluded indexes, then
518
+ * maxFailures ('' for none) and the event snapshot-pins key prefix.
469
519
  * The ready set is a subset of the pending set, so an empty ready set is
470
520
  * enough to report 'settled'. A claimed result ends with the clear boundary.
471
521
  */
472
522
  const CLAIM_AVAILABLE_LUA = `
473
523
  local COUNTER, META, SPLIT = KEYS[2], KEYS[3], KEYS[6]
474
524
  ${META_LUA}
525
+ ${RELEASE_PINS_LUA}
475
526
  local ready = redis.call('ZRANGE', KEYS[5], 0, -1)
476
527
  local excluded = {}
528
+ local tail = 5 + tonumber(ARGV[4])
477
529
  for i = 1, tonumber(ARGV[4]) do excluded[ARGV[4 + i]] = true end
530
+ local max_failures = tonumber(ARGV[tail])
478
531
  local candidates = {}
479
532
  for _, field in ipairs(ready) do
480
533
  if not excluded[field] then candidates[#candidates + 1] = field end
481
534
  end
482
535
  local rows = read_fields(candidates, { 'ce', 'd', 'fc', 'a', 'e', 'lf', 'lfa' })
483
536
  local claimed = {}
537
+ local dead = {}
484
538
  local retry_at = nil
485
539
  for _, field in ipairs(candidates) do
486
540
  local row = rows[field]
@@ -490,29 +544,63 @@ for _, field in ipairs(candidates) do
490
544
  else
491
545
  local entries = redis.call('XRANGE', KEYS[1], field .. '-0', field .. '-0')
492
546
  if #entries == 0 then return { 'missing', field } end
493
- local attempt = tostring(tonumber(row[2] or '0') + 1)
494
- local first_claimed = row[3] or ARGV[2]
495
547
  local meta_key = mkey(field)
548
+ local attempt = tostring(tonumber(row[2] or '0') + 1)
549
+ -- An expiry that is not the current attempt's retry hold is a lapsed
550
+ -- claim: its attempt never finished.
551
+ local lapsed = row[1] and row[7] ~= row[2]
552
+ local failures = tonumber(row[4] or '0')
553
+ if lapsed then
554
+ failures = failures + 1
555
+ put(meta_key, field .. ':a', tostring(failures))
556
+ end
496
557
  put(meta_key, field .. ':d', attempt)
497
- put(meta_key, field .. ':fc', first_claimed)
498
- put(meta_key, field .. ':lc', ARGV[2])
499
- put(meta_key, field .. ':ch', ARGV[1])
500
- put(meta_key, field .. ':ce', ARGV[3])
501
- claimed[#claimed + 1] = {
502
- attempt,
503
- row[4] or '0',
504
- row[5] or false,
505
- first_claimed,
506
- ARGV[2],
507
- row[6] or false,
508
- row[7] or false,
509
- ARGV[1],
510
- ARGV[3],
511
- entries[1]
512
- }
558
+ if lapsed and max_failures and failures >= max_failures then
559
+ -- That takeover spent the last failed attempt: dead-letter, unclaimed.
560
+ -- Advancing the attempt fences the lapsed one; nothing runs.
561
+ put(meta_key, field .. ':f', ARGV[2])
562
+ dead[#dead + 1] = field
563
+ claimed[#claimed + 1] = {
564
+ attempt,
565
+ tostring(failures),
566
+ row[5] or false,
567
+ row[3] or false,
568
+ read_fields({ field }, { 'lc' })[field][1] or false,
569
+ row[6] or false,
570
+ row[7] or false,
571
+ false,
572
+ false,
573
+ entries[1],
574
+ ARGV[2]
575
+ }
576
+ else
577
+ local first_claimed = row[3] or ARGV[2]
578
+ put(meta_key, field .. ':fc', first_claimed)
579
+ put(meta_key, field .. ':lc', ARGV[2])
580
+ put(meta_key, field .. ':ch', ARGV[1])
581
+ put(meta_key, field .. ':ce', ARGV[3])
582
+ claimed[#claimed + 1] = {
583
+ attempt,
584
+ tostring(failures),
585
+ row[5] or false,
586
+ first_claimed,
587
+ ARGV[2],
588
+ row[6] or false,
589
+ row[7] or false,
590
+ ARGV[1],
591
+ ARGV[3],
592
+ entries[1],
593
+ false
594
+ }
595
+ end
513
596
  end
514
597
  end
515
598
  flush()
599
+ for _, field in ipairs(dead) do
600
+ redis.call('HDEL', mkey(field), field .. ':ch', field .. ':ce')
601
+ redis.call('ZREM', KEYS[5], field)
602
+ release_snapshot_pins(ARGV[tail + 1] .. field, KEYS[7])
603
+ end
516
604
  if #claimed > 0 then
517
605
  local result = { 'claimed', tostring(#claimed) }
518
606
  for _, claim in ipairs(claimed) do
@@ -580,25 +668,7 @@ ${ENQUEUE_LUA}
580
668
  ${ID_OWNERS_LUA}
581
669
  ${RELEASE_PINS_LUA}
582
670
  ${WAKE_LUA}
583
- local function settle(field, lane, next_field)
584
- redis.call('ZREM', PENDING, field)
585
- redis.call('ZREM', READY, field)
586
- if lane == '' then return end
587
- local head_key = lane_field(lane, 'head')
588
- if redis.call('HGET', META, head_key) ~= field then return end
589
- while next_field do
590
- local row = read_fields({ next_field }, { 'p', 'f', 'next' })[next_field]
591
- if not row[1] then
592
- redis.call('HSET', META, head_key, next_field)
593
- if not row[2] then
594
- redis.call('ZADD', READY, tonumber(next_field), next_field)
595
- end
596
- return
597
- end
598
- next_field = row[3]
599
- end
600
- clear_lane(lane)
601
- end
671
+ ${SETTLE_LUA}
602
672
  local parent = ARGV[3]
603
673
  local attempt = tonumber(ARGV[4])
604
674
  local n = tonumber(ARGV[5])
@@ -684,6 +754,76 @@ if n > 0 then wake(ARGV[6 + n * 5], tostring(total + n), ARGV[2]) end
684
754
  return { 'ok', tostring(total + 1) }
685
755
  `;
686
756
  /**
757
+ * KEYS: counter, meta, pending, ready, metadata split.
758
+ * ARGV: idx, action ('retry' | 'skip'), nowMs.
759
+ * Returns [redriven, hasPending]. Acts only on an unfinished, dead-lettered
760
+ * event above the clear boundary. A retry keeps the event pending and makes
761
+ * it ready again when it is unlaned or its lane's head.
762
+ */
763
+ const REDRIVE_LUA = `
764
+ local COUNTER, META, SPLIT, PENDING, READY = KEYS[1], KEYS[2], KEYS[5], KEYS[3], KEYS[4]
765
+ ${META_LUA}
766
+ ${ENQUEUE_LUA}
767
+ ${SETTLE_LUA}
768
+ local function result(redriven)
769
+ return { redriven, redis.call('ZCARD', PENDING) > 0 and '1' or '0' }
770
+ end
771
+ local counts = redis.call('MGET', COUNTER, SPLIT, CLEAR)
772
+ local total = tonumber(counts[1] or '0')
773
+ use_split(counts[2], counts[1], counts[3])
774
+ local idx = tonumber(ARGV[1])
775
+ if idx <= boundary or idx > total then return result('0') end
776
+ local field = tostring(idx)
777
+ local row = read_fields({ field }, { 'p', 'f', 'l', 'next' })[field]
778
+ if row[1] or not row[2] then return result('0') end
779
+ local meta_key = mkey(field)
780
+ local lane = row[3] or ''
781
+ if ARGV[2] == 'skip' then
782
+ put(meta_key, field .. ':p', ARGV[3])
783
+ settle(field, lane, row[4])
784
+ flush_queue()
785
+ flush()
786
+ else
787
+ redis.call('HDEL', meta_key, field .. ':f', field .. ':a')
788
+ if lane == '' or redis.call('HGET', META, lane_field(lane, 'head')) == field then
789
+ redis.call('ZADD', READY, idx, field)
790
+ end
791
+ end
792
+ return result('1')
793
+ `;
794
+ /**
795
+ * KEYS: store, counter, meta, pending, metadata split.
796
+ * Returns the dead-lettered events above the clear boundary, oldest first,
797
+ * as pairs of a stream entry and its STORED_EVENT_META_SUFFIXES columns.
798
+ * Every unfinished event is in the pending set, so the scan is bounded by
799
+ * unfinished work, not history; only dead letters read their full columns.
800
+ */
801
+ const DEAD_LETTERS_LUA = `
802
+ local COUNTER, META, SPLIT = KEYS[2], KEYS[3], KEYS[5]
803
+ ${META_LUA}
804
+ local counts = redis.call('MGET', COUNTER, SPLIT, CLEAR)
805
+ use_split(counts[2], counts[1], counts[3])
806
+ local candidates = {}
807
+ for _, field in ipairs(redis.call('ZRANGE', KEYS[4], 0, -1)) do
808
+ if tonumber(field) > boundary then candidates[#candidates + 1] = field end
809
+ end
810
+ local status = read_fields(candidates, { 'p', 'f' })
811
+ local dead = {}
812
+ for _, field in ipairs(candidates) do
813
+ if status[field][2] and not status[field][1] then dead[#dead + 1] = field end
814
+ end
815
+ local rows = read_fields(dead, { ${STORED_EVENT_META_SUFFIXES.map((suffix) => `'${suffix}'`).join(", ")} })
816
+ local result = {}
817
+ for _, field in ipairs(dead) do
818
+ local entries = redis.call('XRANGE', KEYS[1], field .. '-0', field .. '-0')
819
+ if #entries > 0 then
820
+ result[#result + 1] = entries[1]
821
+ result[#result + 1] = rows[field]
822
+ end
823
+ end
824
+ return result
825
+ `;
826
+ /**
687
827
  * KEYS: head, history, event counter, event meta, pin counts, metadata split.
688
828
  * ARGV: nowMs, event pin key prefix, write count, then per write:
689
829
  * index, snapshot JSON, pin count, pin event indexes; finally the reducer
@@ -1070,21 +1210,6 @@ const fieldMap = (fields) => {
1070
1210
  for (let i = 0; i + 1 < fields.length; i += 2) map[fields[i]] = fields[i + 1];
1071
1211
  return map;
1072
1212
  };
1073
- const STORED_EVENT_META_SUFFIXES = [
1074
- "p",
1075
- "pa",
1076
- "ri",
1077
- "ch",
1078
- "ce",
1079
- "f",
1080
- "fc",
1081
- "lc",
1082
- "lf",
1083
- "lfa",
1084
- "d",
1085
- "a",
1086
- "e"
1087
- ];
1088
1213
  const entryIndex = (entryId) => Number(entryId.slice(0, entryId.indexOf("-")));
1089
1214
  const toEvent = (sessionId, entry) => {
1090
1215
  const fields = fieldMap(entry[1]);
@@ -1153,13 +1278,43 @@ const toCause = (raw) => {
1153
1278
  ...value.batchSize === void 0 ? {} : { batchSize: Number(value.batchSize) }
1154
1279
  };
1155
1280
  };
1281
+ /** A stream entry joined with its metadata columns; absent columns are unset. */
1282
+ const toStoredEvent = (sessionId, entry, column) => {
1283
+ const fields = fieldMap(entry[1]);
1284
+ const date = (suffix) => {
1285
+ const value = column(suffix);
1286
+ return value === void 0 ? null : new Date(Number(value));
1287
+ };
1288
+ const count = (suffix) => {
1289
+ const value = column(suffix);
1290
+ return value === void 0 ? null : Number(value);
1291
+ };
1292
+ const returnedEventIds = column("ri");
1293
+ return Object.assign(toEvent(sessionId, entry), {
1294
+ cause: toCause(fields["cause"]),
1295
+ lane: fields["lane"] === "" ? null : fields["lane"],
1296
+ processedAt: date("p"),
1297
+ processedByAttempt: count("pa"),
1298
+ returnedEventIds: returnedEventIds === void 0 ? null : decodeReturnedEventIds(returnedEventIds),
1299
+ firstClaimedAt: date("fc"),
1300
+ lastClaimedAt: date("lc"),
1301
+ attemptCount: count("d") ?? 0,
1302
+ claimHolder: column("ch") ?? null,
1303
+ claimExpiresAt: date("ce"),
1304
+ failureCount: count("a") ?? 0,
1305
+ lastFailedAt: date("lf"),
1306
+ lastFailedAttempt: count("lfa"),
1307
+ lastError: column("e") ?? null,
1308
+ failedAt: date("f")
1309
+ });
1310
+ };
1156
1311
  const toClaimAvailableResult = (sessionId, reply) => {
1157
1312
  const outcome = String(reply[0]);
1158
1313
  if (outcome === "claimed") {
1159
1314
  const count = Number(reply[1]);
1160
1315
  const events = [];
1161
1316
  for (let i = 0; i < count; i++) {
1162
- const offset = 2 + i * 10;
1317
+ const offset = 2 + i * 11;
1163
1318
  const entry = reply[offset + 9];
1164
1319
  const event = toEvent(sessionId, entry);
1165
1320
  const fields = fieldMap(entry[1]);
@@ -1170,18 +1325,18 @@ const toClaimAvailableResult = (sessionId, reply) => {
1170
1325
  processedByAttempt: null,
1171
1326
  returnedEventIds: null,
1172
1327
  firstClaimedAt: reply[offset + 3] === null ? null : new Date(Number(reply[offset + 3])),
1173
- lastClaimedAt: new Date(Number(reply[offset + 4])),
1328
+ lastClaimedAt: reply[offset + 4] === null ? null : new Date(Number(reply[offset + 4])),
1174
1329
  attemptCount: Number(reply[offset]),
1175
- claimHolder: String(reply[offset + 7]),
1176
- claimExpiresAt: new Date(Number(reply[offset + 8])),
1330
+ claimHolder: reply[offset + 7] === null ? null : String(reply[offset + 7]),
1331
+ claimExpiresAt: reply[offset + 8] === null ? null : new Date(Number(reply[offset + 8])),
1177
1332
  failureCount: Number(reply[offset + 1]),
1178
1333
  lastFailedAt: reply[offset + 5] === null ? null : new Date(Number(reply[offset + 5])),
1179
1334
  lastFailedAttempt: reply[offset + 6] === null ? null : Number(reply[offset + 6]),
1180
1335
  lastError: reply[offset + 2] === null ? null : String(reply[offset + 2]),
1181
- failedAt: null
1336
+ failedAt: reply[offset + 10] === null ? null : new Date(Number(reply[offset + 10]))
1182
1337
  }));
1183
1338
  }
1184
- const clearIndex = Number(reply[2 + count * 10] ?? 0);
1339
+ const clearIndex = Number(reply[2 + count * 11] ?? 0);
1185
1340
  if (clearIndex > 0) for (const event of events) event.clearIndex = clearIndex;
1186
1341
  return {
1187
1342
  outcome: "claimed",
@@ -1295,36 +1450,8 @@ function createRedisStoreCore(options) {
1295
1450
  }
1296
1451
  }
1297
1452
  return entries.map((entry) => {
1298
- const event = toEvent(sessionId, entry);
1299
- const idx = event.index;
1300
- const fields = fieldMap(entry[1]);
1301
- const processedAt = meta[`${idx}:p`];
1302
- const processedByAttempt = meta[`${idx}:pa`];
1303
- const returnedEventIds = meta[`${idx}:ri`];
1304
- const claimHolder = meta[`${idx}:ch`];
1305
- const claimExpiresAt = meta[`${idx}:ce`];
1306
- const failedAt = meta[`${idx}:f`];
1307
- const firstClaimedAt = meta[`${idx}:fc`];
1308
- const lastClaimedAt = meta[`${idx}:lc`];
1309
- const lastFailedAt = meta[`${idx}:lf`];
1310
- const lastFailedAttempt = meta[`${idx}:lfa`];
1311
- return Object.assign(event, {
1312
- cause: toCause(fields["cause"]),
1313
- lane: fields["lane"] === "" ? null : fields["lane"],
1314
- processedAt: processedAt === void 0 ? null : new Date(Number(processedAt)),
1315
- processedByAttempt: processedByAttempt === void 0 ? null : Number(processedByAttempt),
1316
- returnedEventIds: returnedEventIds === void 0 ? null : decodeReturnedEventIds(returnedEventIds),
1317
- firstClaimedAt: firstClaimedAt === void 0 ? null : new Date(Number(firstClaimedAt)),
1318
- lastClaimedAt: lastClaimedAt === void 0 ? null : new Date(Number(lastClaimedAt)),
1319
- attemptCount: Number(meta[`${idx}:d`] ?? 0),
1320
- claimHolder: claimHolder ?? null,
1321
- claimExpiresAt: claimExpiresAt === void 0 ? null : new Date(Number(claimExpiresAt)),
1322
- failureCount: Number(meta[`${idx}:a`] ?? 0),
1323
- lastFailedAt: lastFailedAt === void 0 ? null : new Date(Number(lastFailedAt)),
1324
- lastFailedAttempt: lastFailedAttempt === void 0 ? null : Number(lastFailedAttempt),
1325
- lastError: meta[`${idx}:e`] ?? null,
1326
- failedAt: failedAt === void 0 ? null : new Date(Number(failedAt))
1327
- });
1453
+ const idx = entryIndex(entry[0]);
1454
+ return toStoredEvent(sessionId, entry, (suffix) => meta[`${idx}:${suffix}`]);
1328
1455
  });
1329
1456
  };
1330
1457
  const readEventPages = async (sessionId, afterIndex, throughIndex) => {
@@ -1518,7 +1645,7 @@ function createRedisStoreCore(options) {
1518
1645
  return readEventRange(sessionId, Math.max(after, clearIndex - 1), Math.min(opts?.throughIndex ?? frontier, frontier));
1519
1646
  }));
1520
1647
  },
1521
- async claimAvailable({ sessionId, holder, ttlMs, expiresAtMs, excludeIndexes }) {
1648
+ async claimAvailable({ sessionId, holder, ttlMs, expiresAtMs, excludeIndexes, maxFailures }) {
1522
1649
  return wrap(async () => {
1523
1650
  const now = clock.now().getTime();
1524
1651
  const expiresAt = expiresAtMs ?? now + ttlMs;
@@ -1529,13 +1656,16 @@ function createRedisStoreCore(options) {
1529
1656
  metaKey(sessionId),
1530
1657
  pendingKey(sessionId),
1531
1658
  readyKey(sessionId),
1532
- metaSplitKey(sessionId)
1659
+ metaSplitKey(sessionId),
1660
+ snapshotPinCountsKey(sessionId)
1533
1661
  ], [
1534
1662
  holder,
1535
1663
  now,
1536
1664
  expiresAt,
1537
1665
  excluded.length,
1538
- ...excluded
1666
+ ...excluded,
1667
+ maxFailures ?? "",
1668
+ snapshotEventPinsPrefix(sessionId)
1539
1669
  ]);
1540
1670
  return toClaimAvailableResult(sessionId, reply);
1541
1671
  });
@@ -1646,8 +1776,9 @@ function createRedisStoreCore(options) {
1646
1776
  throw new TypeError(`unexpected completion outcome '${outcome}'`);
1647
1777
  });
1648
1778
  },
1649
- async failAttempt({ sessionId, index, attempt, error, maxFailures }) {
1779
+ async failAttempt({ sessionId, index, attempt, error, maxFailures, retryAfterMs }) {
1650
1780
  return wrap(async () => {
1781
+ const now = clock.now().getTime();
1651
1782
  const reply = await evalScript(FAIL_ATTEMPT_LUA, [
1652
1783
  countKey(sessionId),
1653
1784
  metaKey(sessionId),
@@ -1661,7 +1792,8 @@ function createRedisStoreCore(options) {
1661
1792
  attempt,
1662
1793
  error,
1663
1794
  maxFailures,
1664
- clock.now().getTime()
1795
+ now,
1796
+ retryAfterMs === void 0 ? "" : now + retryAfterMs
1665
1797
  ]);
1666
1798
  const outcome = reply[0];
1667
1799
  if (outcome === "cleared") return {
@@ -1675,6 +1807,42 @@ function createRedisStoreCore(options) {
1675
1807
  };
1676
1808
  });
1677
1809
  },
1810
+ async deadLetters(sessionId) {
1811
+ return wrap(async () => {
1812
+ const reply = await evalScript(DEAD_LETTERS_LUA, [
1813
+ storeKey(sessionId),
1814
+ countKey(sessionId),
1815
+ metaKey(sessionId),
1816
+ pendingKey(sessionId),
1817
+ metaSplitKey(sessionId)
1818
+ ], []);
1819
+ const events = [];
1820
+ for (let i = 0; i + 1 < reply.length; i += 2) {
1821
+ const columns = reply[i + 1];
1822
+ events.push(toStoredEvent(sessionId, reply[i], (suffix) => columns[STORED_EVENT_META_SUFFIXES.indexOf(suffix)] ?? void 0));
1823
+ }
1824
+ return events;
1825
+ });
1826
+ },
1827
+ async redrive({ sessionId, index, action }) {
1828
+ return wrap(async () => {
1829
+ const [redriven, hasPending] = await evalScript(REDRIVE_LUA, [
1830
+ countKey(sessionId),
1831
+ metaKey(sessionId),
1832
+ pendingKey(sessionId),
1833
+ readyKey(sessionId),
1834
+ metaSplitKey(sessionId)
1835
+ ], [
1836
+ index,
1837
+ action,
1838
+ clock.now().getTime()
1839
+ ]);
1840
+ return {
1841
+ redriven: redriven === "1",
1842
+ hasPending: hasPending === "1"
1843
+ };
1844
+ });
1845
+ },
1678
1846
  async readState(sessionId, reducerName, stateOptions) {
1679
1847
  return wrap(() => retryAfterGap(async () => {
1680
1848
  const reply = await evalScript(READ_STATE_LUA, [
@@ -2084,6 +2252,8 @@ function createRedisNotifyStore(options) {
2084
2252
  putSnapshots: core.putSnapshots,
2085
2253
  clear: core.clear,
2086
2254
  reclaim: core.reclaim,
2255
+ deadLetters: core.deadLetters,
2256
+ redrive: core.redrive,
2087
2257
  presence: {
2088
2258
  async set(ns, participant, values, meta) {
2089
2259
  const patch = await core.presence.set(ns, participant, values, meta);
@@ -2371,4 +2541,4 @@ function createRedisNotifyStore(options) {
2371
2541
  //#endregion
2372
2542
  export { createRedisNotifyStore as t };
2373
2543
 
2374
- //# sourceMappingURL=store-redis-notify-tqWNnMRu.js.map
2544
+ //# sourceMappingURL=store-redis-notify-CM0AfLY0.js.map