experimental-a2 0.16.1 → 0.17.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 (333) hide show
  1. package/CHANGELOG.md +352 -0
  2. package/dist/{actor-ohPC-81x.d.ts → actor-DBbe5xeE.d.ts} +21 -11
  3. package/dist/actor-DBbe5xeE.d.ts.map +1 -0
  4. package/dist/actor-client.d.ts +2 -5
  5. package/dist/actor-client.d.ts.map +1 -1
  6. package/dist/actor-client.js +13 -4
  7. package/dist/actor-client.js.map +1 -1
  8. package/dist/actor-react.d.ts +5 -3
  9. package/dist/actor-react.d.ts.map +1 -1
  10. package/dist/actor-react.js +20 -10
  11. package/dist/actor-react.js.map +1 -1
  12. package/dist/{actor-shared-B5tJfzt-.js → actor-shared-t7w8xIKP.js} +12 -7
  13. package/dist/actor-shared-t7w8xIKP.js.map +1 -0
  14. package/dist/actor.d.ts +1 -1
  15. package/dist/actor.js +42 -23
  16. package/dist/actor.js.map +1 -1
  17. package/dist/{ai-DgOBltJ_.js → ai-UrWfKeuv.js} +621 -216
  18. package/dist/ai-UrWfKeuv.js.map +1 -0
  19. package/dist/ai-client.d.ts +27 -0
  20. package/dist/ai-client.d.ts.map +1 -0
  21. package/dist/ai-client.js +29 -0
  22. package/dist/ai-client.js.map +1 -0
  23. package/dist/ai-react.d.ts +96 -0
  24. package/dist/ai-react.d.ts.map +1 -0
  25. package/dist/ai-react.js +142 -0
  26. package/dist/ai-react.js.map +1 -0
  27. package/dist/ai-server.browser.js +2 -2
  28. package/dist/ai-server.browser.js.map +1 -1
  29. package/dist/ai-server.d.ts +80 -36
  30. package/dist/ai-server.d.ts.map +1 -1
  31. package/dist/ai-server.js +432 -258
  32. package/dist/ai-server.js.map +1 -1
  33. package/dist/{ai-CrEf6p_W.d.ts → ai-vpGtcaCB.d.ts} +114 -100
  34. package/dist/ai-vpGtcaCB.d.ts.map +1 -0
  35. package/dist/ai.d.ts +2 -2
  36. package/dist/ai.js +2 -2
  37. package/dist/{client-BYzHjkwU.js → client-COaiaDub.js} +447 -80
  38. package/dist/client-COaiaDub.js.map +1 -0
  39. package/dist/{client-CzyacQpJ.d.ts → client-qRnRUzBS.d.ts} +14 -13
  40. package/dist/client-qRnRUzBS.d.ts.map +1 -0
  41. package/dist/client.d.ts +4 -2
  42. package/dist/client.js +1 -1
  43. package/dist/{contract-CKRg_E4q.js → contract-BYKASlCV.js} +12 -21
  44. package/dist/contract-BYKASlCV.js.map +1 -0
  45. package/dist/{idempotent-replay-DVOlyYbx.js → idempotent-replay-JdgMQRRm.js} +2 -2
  46. package/dist/{idempotent-replay-DVOlyYbx.js.map → idempotent-replay-JdgMQRRm.js.map} +1 -1
  47. package/dist/index.d.ts +6 -5
  48. package/dist/index.d.ts.map +1 -1
  49. package/dist/index.js +4 -3
  50. package/dist/{errors-DvhSXnxk.d.ts → initial-snapshot-BrLso5x_.d.ts} +7 -3
  51. package/dist/initial-snapshot-BrLso5x_.d.ts.map +1 -0
  52. package/dist/{internal-Dq2qYxou.js → internal-CbVvEwad.js} +23 -2
  53. package/dist/internal-CbVvEwad.js.map +1 -0
  54. package/dist/otel.d.ts +1 -1
  55. package/dist/react-BbJ-g6Uu.d.ts +86 -0
  56. package/dist/react-BbJ-g6Uu.d.ts.map +1 -0
  57. package/dist/react.d.ts +2 -122
  58. package/dist/react.js +107 -100
  59. package/dist/react.js.map +1 -1
  60. package/dist/reducer-BAXc8JyF.js +87 -0
  61. package/dist/reducer-BAXc8JyF.js.map +1 -0
  62. package/dist/{reducer-otzHjuJj.d.ts → reducer-BYu6DT7h.d.ts} +45 -29
  63. package/dist/reducer-BYu6DT7h.d.ts.map +1 -0
  64. package/dist/reducer-runtime-DQwz_J59.js +28 -0
  65. package/dist/reducer-runtime-DQwz_J59.js.map +1 -0
  66. package/dist/scheduler-qstash.d.ts +3 -3
  67. package/dist/scheduler-qstash.d.ts.map +1 -1
  68. package/dist/scheduler-qstash.js +33 -10
  69. package/dist/scheduler-qstash.js.map +1 -1
  70. package/dist/{scheduler-task-BpzhPnRS.js → scheduler-task-X1SToDjP.js} +18 -3
  71. package/dist/scheduler-task-X1SToDjP.js.map +1 -0
  72. package/dist/scheduler-vercel.d.ts +4 -9
  73. package/dist/scheduler-vercel.d.ts.map +1 -1
  74. package/dist/scheduler-vercel.js +25 -44
  75. package/dist/scheduler-vercel.js.map +1 -1
  76. package/dist/{server-DbD7IJVK.d.ts → server-CGM8WzRG.d.ts} +87 -26
  77. package/dist/server-CGM8WzRG.d.ts.map +1 -0
  78. package/dist/{server-BD5ckxZb.js → server-Rj64fNvX.js} +854 -202
  79. package/dist/server-Rj64fNvX.js.map +1 -0
  80. package/dist/server.d.ts +4 -4
  81. package/dist/server.js +2 -2
  82. package/dist/{errors-DCk6ch5n.js → session-clear-DGI4jTQ5.js} +17 -2
  83. package/dist/session-clear-DGI4jTQ5.js.map +1 -0
  84. package/dist/session-clear-Dshpuhff.d.ts +12 -0
  85. package/dist/session-clear-Dshpuhff.d.ts.map +1 -0
  86. package/dist/{store-DyZM6fS5.d.ts → store-Gpkq7WBo.d.ts} +44 -3
  87. package/dist/store-Gpkq7WBo.d.ts.map +1 -0
  88. package/dist/store-N8PXxDAS.js.map +1 -1
  89. package/dist/store-memory.d.ts +1 -1
  90. package/dist/store-memory.d.ts.map +1 -1
  91. package/dist/store-memory.js +182 -69
  92. package/dist/store-memory.js.map +1 -1
  93. package/dist/{store-polling-CmxUbV93.js → store-polling-4_y8lETm.js} +21 -4
  94. package/dist/store-polling-4_y8lETm.js.map +1 -0
  95. package/dist/store-postgres.d.ts +1 -1
  96. package/dist/store-postgres.d.ts.map +1 -1
  97. package/dist/store-postgres.js +399 -108
  98. package/dist/store-postgres.js.map +1 -1
  99. package/dist/{store-presence-polling-C7-XZyW9.js → store-presence-polling-Q1C-k2dd.js} +3 -3
  100. package/dist/{store-presence-polling-C7-XZyW9.js.map → store-presence-polling-Q1C-k2dd.js.map} +1 -1
  101. package/dist/store-redis-http.d.ts +1 -1
  102. package/dist/store-redis-http.d.ts.map +1 -1
  103. package/dist/store-redis-http.js +293 -11
  104. package/dist/store-redis-http.js.map +1 -1
  105. package/dist/store-redis-notify-tqWNnMRu.js +2374 -0
  106. package/dist/store-redis-notify-tqWNnMRu.js.map +1 -0
  107. package/dist/store-redis.d.ts +1 -1
  108. package/dist/store-redis.d.ts.map +1 -1
  109. package/dist/store-redis.js +11 -11
  110. package/dist/store-redis.js.map +1 -1
  111. package/dist/store-sqlite.d.ts +1 -1
  112. package/dist/store-sqlite.d.ts.map +1 -1
  113. package/dist/store-sqlite.js +166 -32
  114. package/dist/store-sqlite.js.map +1 -1
  115. package/dist/stream-buffer-Cx7AAPGq.js +23 -0
  116. package/dist/stream-buffer-Cx7AAPGq.js.map +1 -0
  117. package/dist/{telemetry-B5jzpy6y.d.ts → telemetry-cZH-cibj.d.ts} +2 -2
  118. package/dist/telemetry-cZH-cibj.d.ts.map +1 -0
  119. package/dist/{wire-BO5wWCb1.js → wire-DjfHOvdW.js} +132 -10
  120. package/dist/wire-DjfHOvdW.js.map +1 -0
  121. package/docs/01-quickstart.mdx +2 -2
  122. package/docs/actors/01-introduction.mdx +4 -0
  123. package/docs/actors/04-routes.mdx +14 -1
  124. package/docs/concepts/01-contracts.mdx +95 -0
  125. package/docs/concepts/04-state.mdx +3 -3
  126. package/docs/guides/01-timers.mdx +22 -14
  127. package/docs/guides/02-cancellation.mdx +1 -1
  128. package/docs/guides/03-react.mdx +268 -328
  129. package/docs/guides/04-local-first.mdx +32 -73
  130. package/docs/guides/05-production.mdx +40 -27
  131. package/docs/guides/06-ai-agents.mdx +671 -581
  132. package/docs/guides/09-presence.mdx +3 -7
  133. package/docs/guides/10-transports.mdx +5 -5
  134. package/docs/index.mdx +10 -18
  135. package/docs/reference/01-api.mdx +656 -194
  136. package/docs/reference/02-errors.mdx +2 -1
  137. package/examples/playground/DEPLOY.md +52 -6
  138. package/examples/playground/app/a0/[appId]/loading.tsx +16 -0
  139. package/examples/playground/app/a0/[appId]/page.tsx +48 -0
  140. package/examples/playground/app/a0/a0.css +818 -0
  141. package/examples/playground/app/a0/app-list.tsx +211 -0
  142. package/examples/playground/app/a0/app-view.tsx +55 -0
  143. package/examples/playground/app/a0/apps/[appId]/preview/route.ts +27 -0
  144. package/examples/playground/app/a0/apps/[appId]/route.ts +23 -0
  145. package/examples/playground/app/a0/apps.test.ts +116 -0
  146. package/examples/playground/app/a0/apps.ts +87 -0
  147. package/examples/playground/app/a0/chat.tsx +364 -0
  148. package/examples/playground/app/a0/columns.tsx +109 -0
  149. package/examples/playground/app/a0/context-menu.tsx +116 -0
  150. package/examples/playground/app/a0/cookies.ts +46 -0
  151. package/examples/playground/app/a0/crash-on-request.ts +72 -0
  152. package/examples/playground/app/a0/crashes/route.ts +3 -0
  153. package/examples/playground/app/a0/crashes.test.ts +33 -0
  154. package/examples/playground/app/a0/crashes.ts +28 -0
  155. package/examples/playground/app/a0/devtools.tsx +164 -0
  156. package/examples/playground/app/a0/events/route.ts +6 -0
  157. package/examples/playground/app/a0/flags.ts +30 -0
  158. package/examples/playground/app/a0/layout.tsx +38 -0
  159. package/examples/playground/app/a0/list/route.ts +10 -0
  160. package/examples/playground/app/a0/model.test.ts +56 -0
  161. package/examples/playground/app/a0/model.ts +58 -0
  162. package/examples/playground/app/a0/page.tsx +14 -0
  163. package/examples/playground/app/a0/preview-pane.tsx +145 -0
  164. package/examples/playground/app/a0/preview.test.ts +53 -0
  165. package/examples/playground/app/a0/preview.ts +26 -0
  166. package/examples/playground/app/a0/sandbox-log.ts +69 -0
  167. package/examples/playground/app/a0/sandbox.ts +191 -0
  168. package/examples/playground/app/a0/sandboxes/scheduler/route.ts +6 -0
  169. package/examples/playground/app/a0/scheduler/route.ts +7 -0
  170. package/examples/playground/app/a0/server.ts +158 -0
  171. package/examples/playground/app/a0/session.ts +47 -0
  172. package/examples/playground/app/a0/shell.tsx +150 -0
  173. package/examples/playground/app/a0/short/events/route.ts +9 -0
  174. package/examples/playground/app/a0/splitter.tsx +120 -0
  175. package/examples/playground/app/agent/[agentId]/agent-client.tsx +144 -174
  176. package/examples/playground/app/agent/[agentId]/command-receipt.test.tsx +65 -0
  177. package/examples/playground/app/agent/[agentId]/command-receipt.tsx +27 -0
  178. package/examples/playground/app/agent/[agentId]/compaction-event.tsx +25 -24
  179. package/examples/playground/app/agent/[agentId]/compaction-panel.test.tsx +119 -0
  180. package/examples/playground/app/agent/[agentId]/compaction-panel.tsx +208 -165
  181. package/examples/playground/app/agent/[agentId]/page.tsx +2 -16
  182. package/examples/playground/app/agent/events/route.ts +5 -2
  183. package/examples/playground/app/agent/ingress.test.ts +98 -0
  184. package/examples/playground/app/agent/model-input.test.tsx +58 -0
  185. package/examples/playground/app/agent/model-input.tsx +115 -0
  186. package/examples/playground/app/agent/model.test.ts +73 -0
  187. package/examples/playground/app/agent/model.ts +58 -26
  188. package/examples/playground/app/agent/new-agent-session.tsx +37 -19
  189. package/examples/playground/app/agent/page.tsx +4 -4
  190. package/examples/playground/app/agent/server.ts +15 -14
  191. package/examples/playground/app/agent/session.ts +5 -6
  192. package/examples/playground/app/agent/telemetry.test.ts +168 -0
  193. package/examples/playground/app/agent/telemetry.ts +30 -0
  194. package/examples/playground/app/agent/trace.test.ts +23 -0
  195. package/examples/playground/app/agent/trace.ts +18 -0
  196. package/examples/playground/app/canvas/[canvasId]/canvas-client.tsx +11 -11
  197. package/examples/playground/app/canvas/[canvasId]/page.tsx +1 -2
  198. package/examples/playground/app/canvas/[canvasId]/session.ts +2 -2
  199. package/examples/playground/app/canvas/model.ts +53 -53
  200. package/examples/playground/app/chat/[chatId]/agent-state.test.ts +176 -0
  201. package/examples/playground/app/chat/[chatId]/agent-stream-drawer.tsx +16 -1
  202. package/examples/playground/app/chat/[chatId]/chat-client.tsx +55 -77
  203. package/examples/playground/app/chat/[chatId]/chat-view.ts +37 -3
  204. package/examples/playground/app/chat/[chatId]/page.tsx +7 -15
  205. package/examples/playground/app/chat/[chatId]/session.ts +2 -19
  206. package/examples/playground/app/chat/agent/route.ts +9 -0
  207. package/examples/playground/app/chat/model.test.ts +46 -119
  208. package/examples/playground/app/chat/model.ts +82 -128
  209. package/examples/playground/app/chat/scheduler/route.ts +2 -2
  210. package/examples/playground/app/chat/server.test.ts +108 -0
  211. package/examples/playground/app/chat/server.ts +69 -73
  212. package/examples/playground/app/components/activity-feed.tsx +30 -1
  213. package/examples/playground/app/components/activity-history.test.tsx +35 -0
  214. package/examples/playground/app/components/demo-icons.tsx +89 -0
  215. package/examples/playground/app/components/demos.tsx +33 -0
  216. package/examples/playground/app/components/fast-link.test.tsx +58 -0
  217. package/examples/playground/app/components/fast-link.tsx +55 -0
  218. package/examples/playground/app/components/site-header.tsx +96 -0
  219. package/examples/playground/app/counter/counter-client.tsx +3 -3
  220. package/examples/playground/app/counter/model.test.ts +3 -3
  221. package/examples/playground/app/counter/model.ts +11 -21
  222. package/examples/playground/app/counter/page.tsx +2 -15
  223. package/examples/playground/app/counter/session.ts +2 -2
  224. package/examples/playground/app/documents/[documentId]/code-editor.tsx +12 -1
  225. package/examples/playground/app/documents/[documentId]/document-client.tsx +10 -11
  226. package/examples/playground/app/documents/[documentId]/page.tsx +1 -2
  227. package/examples/playground/app/documents/[documentId]/session.ts +2 -2
  228. package/examples/playground/app/documents/model.ts +27 -27
  229. package/examples/playground/app/globals.css +420 -207
  230. package/examples/playground/app/initial-state.test.tsx +54 -0
  231. package/examples/playground/app/layout.tsx +7 -23
  232. package/examples/playground/app/orders/[orderId]/page.tsx +1 -2
  233. package/examples/playground/app/orders/[orderId]/session.ts +2 -2
  234. package/examples/playground/app/orders/model.ts +49 -49
  235. package/examples/playground/app/page.tsx +13 -102
  236. package/examples/playground/app/recovery/[recoveryId]/page.tsx +1 -2
  237. package/examples/playground/app/recovery/[recoveryId]/session.ts +2 -2
  238. package/examples/playground/app/recovery/model.ts +30 -30
  239. package/examples/playground/instrumentation.ts +5 -0
  240. package/examples/playground/package.json +10 -2
  241. package/examples/playground/scripts/a0-template.ts +77 -0
  242. package/examples/playground/vercel.json +18 -0
  243. package/package.json +3 -1
  244. package/src/actor-client.ts +16 -4
  245. package/src/actor-react.ts +25 -11
  246. package/src/actor-shared.ts +21 -6
  247. package/src/actor.ts +76 -31
  248. package/src/ai-agent-actions.ts +219 -0
  249. package/src/ai-agent.ts +2343 -0
  250. package/src/ai-app-state.ts +91 -0
  251. package/src/ai-client-state.ts +6 -3
  252. package/src/ai-client.ts +62 -0
  253. package/src/ai-context-schema.ts +25 -5
  254. package/src/ai-context.ts +90 -40
  255. package/src/ai-control-server.ts +92 -72
  256. package/src/ai-coordinator.ts +23 -22
  257. package/src/ai-message-base.ts +209 -0
  258. package/src/ai-message-projection.ts +262 -64
  259. package/src/ai-progress-batches.ts +26 -0
  260. package/src/ai-projector.ts +95 -14
  261. package/src/ai-react.ts +146 -0
  262. package/src/ai-server.browser.ts +2 -2
  263. package/src/ai-server.ts +20 -1964
  264. package/src/ai-stored-state.ts +48 -163
  265. package/src/ai.ts +339 -155
  266. package/src/bootstrap-stream.ts +72 -0
  267. package/src/bounded-cache.ts +55 -0
  268. package/src/client-render-session.ts +49 -0
  269. package/src/client.ts +688 -151
  270. package/src/contract.ts +92 -42
  271. package/src/deterministic-id.ts +37 -0
  272. package/src/equal-json.ts +32 -0
  273. package/src/errors.ts +13 -1
  274. package/src/index.ts +5 -1
  275. package/src/initial-snapshot.ts +39 -0
  276. package/src/internal.ts +45 -0
  277. package/src/postgres-notification-scope.ts +14 -3
  278. package/src/react.ts +171 -206
  279. package/src/redis-http-reply.ts +256 -0
  280. package/src/redis-http-stream.ts +73 -0
  281. package/src/redis-http-subscriptions.ts +13 -5
  282. package/src/reducer-runtime.ts +52 -0
  283. package/src/reducer.ts +92 -10
  284. package/src/scheduler-qstash.ts +49 -20
  285. package/src/scheduler-task.ts +34 -2
  286. package/src/scheduler-vercel.ts +32 -84
  287. package/src/server-fetch.ts +139 -35
  288. package/src/server.ts +957 -237
  289. package/src/session-clear.ts +23 -0
  290. package/src/session-socket.ts +154 -49
  291. package/src/sse.ts +71 -4
  292. package/src/store-memory.ts +220 -78
  293. package/src/store-polling.ts +18 -4
  294. package/src/store-postgres.ts +461 -114
  295. package/src/store-redis-core.ts +1260 -434
  296. package/src/store-redis-http.ts +18 -2
  297. package/src/store-redis-notify.ts +260 -85
  298. package/src/store-redis.ts +19 -9
  299. package/src/store-sqlite.ts +259 -45
  300. package/src/store.ts +46 -1
  301. package/src/stream-buffer.ts +43 -0
  302. package/src/telemetry.ts +13 -1
  303. package/src/wire.ts +241 -13
  304. package/dist/actor-ohPC-81x.d.ts.map +0 -1
  305. package/dist/actor-shared-B5tJfzt-.js.map +0 -1
  306. package/dist/ai-CrEf6p_W.d.ts.map +0 -1
  307. package/dist/ai-DgOBltJ_.js.map +0 -1
  308. package/dist/client-BYzHjkwU.js.map +0 -1
  309. package/dist/client-CzyacQpJ.d.ts.map +0 -1
  310. package/dist/contract-CKRg_E4q.js.map +0 -1
  311. package/dist/errors-DCk6ch5n.js.map +0 -1
  312. package/dist/errors-DvhSXnxk.d.ts.map +0 -1
  313. package/dist/internal-Dq2qYxou.js.map +0 -1
  314. package/dist/react.d.ts.map +0 -1
  315. package/dist/reducer-DEMjEY_O.js +0 -29
  316. package/dist/reducer-DEMjEY_O.js.map +0 -1
  317. package/dist/reducer-otzHjuJj.d.ts.map +0 -1
  318. package/dist/scheduler-task-BpzhPnRS.js.map +0 -1
  319. package/dist/server-BD5ckxZb.js.map +0 -1
  320. package/dist/server-DbD7IJVK.d.ts.map +0 -1
  321. package/dist/store-DyZM6fS5.d.ts.map +0 -1
  322. package/dist/store-polling-CmxUbV93.js.map +0 -1
  323. package/dist/store-redis-notify-BVLUfI3j.js +0 -1583
  324. package/dist/store-redis-notify-BVLUfI3j.js.map +0 -1
  325. package/dist/telemetry-B5jzpy6y.d.ts.map +0 -1
  326. package/dist/validate-XKT4FSNn.js +0 -30
  327. package/dist/validate-XKT4FSNn.js.map +0 -1
  328. package/dist/wire-BO5wWCb1.js.map +0 -1
  329. package/examples/playground/app/agent/[agentId]/compaction/route.ts +0 -14
  330. package/examples/playground/app/agent/compaction-settings.test.ts +0 -144
  331. package/examples/playground/app/agent/compaction-settings.ts +0 -49
  332. package/examples/playground/app/agent/compaction-timeline.test.ts +0 -377
  333. package/examples/playground/app/agent/compaction-timeline.ts +0 -208
@@ -1,4 +1,4 @@
1
- import { r as Clock, s as IdSource, t as A2Store } from "./store-DyZM6fS5.js";
1
+ import { r as Clock, s as IdSource, t as A2Store } from "./store-Gpkq7WBo.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 +1 @@
1
- {"version":3,"file":"store-redis-http.d.ts","names":[],"sources":["../src/store-redis-http.ts"],"mappings":";;KAeY;;EAEV;;EAEA;;EAEA;;EAEA,QAAQ;;EAER,MAAM;;KAGI,iBAAiB;;EAE3B,SAAS;;iBAGK,UAAU,SAAS,wBAAwB"}
1
+ {"version":3,"file":"store-redis-http.d.ts","names":[],"sources":["../src/store-redis-http.ts"],"mappings":";;KAoBY;;EAEV;;EAEA;;EAEA;;EAEA,QAAQ;;EAER,MAAM;;KAGI,iBAAiB;;EAE3B,SAAS;;iBAGK,UAAU,SAAS,wBAAwB"}
@@ -1,8 +1,8 @@
1
- import { t as A2Error } from "./errors-DCk6ch5n.js";
2
- import { r as NOTIFY_TIMINGS } from "./internal-Dq2qYxou.js";
3
- import { t as defaultSleep } from "./store-polling-CmxUbV93.js";
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";
4
4
  import { n as SYSTEM_CLOCK, t as RANDOM_IDS } from "./store-N8PXxDAS.js";
5
- import { t as createRedisNotifyStore } from "./store-redis-notify-BVLUfI3j.js";
5
+ import { t as createRedisNotifyStore } from "./store-redis-notify-tqWNnMRu.js";
6
6
  //#region src/redis-http-subscriptions.ts
7
7
  function redisHttpSubscriptions({ url, token, onReconnect, fallback }) {
8
8
  const subscriptions = /* @__PURE__ */ new Map();
@@ -56,10 +56,14 @@ function redisHttpSubscriptions({ url, token, onReconnect, fallback }) {
56
56
  501
57
57
  ].includes(response.status)) {
58
58
  await response.body?.cancel();
59
- await fallback.enable();
60
- for (const active of subscriptions.values()) active.stop();
61
- subscriptions.clear();
62
- return;
59
+ await fallback.checkCommands();
60
+ if (stopped) return;
61
+ if (!supported) {
62
+ fallback.enable();
63
+ for (const active of subscriptions.values()) active.stop();
64
+ subscriptions.clear();
65
+ return;
66
+ }
63
67
  }
64
68
  if (!response.ok || !response.headers.get("content-type")?.startsWith("text/event-stream") || !response.body) {
65
69
  await response.body?.cancel();
@@ -149,6 +153,275 @@ function redisHttpSubscriptions({ url, token, onReconnect, fallback }) {
149
153
  return connection;
150
154
  }
151
155
  //#endregion
156
+ //#region src/redis-http-reply.ts
157
+ /**
158
+ * Index just past the array or string `scan` is inside, or -1 when `text`
159
+ * ends first; `scan` then carries the state into the next chunk. Strings
160
+ * are skipped with `indexOf`, so the per-character loop only walks
161
+ * structure. A quote is escaped when an odd run of backslashes precedes
162
+ * it; a run is counted back only to where the string scan resumed, and
163
+ * `escaped` carries an odd trailing run across the chunk boundary.
164
+ */
165
+ function scanValue(text, from, scan) {
166
+ let at = from;
167
+ while (at < text.length) {
168
+ if (scan.quoted) {
169
+ if (scan.escaped) {
170
+ scan.escaped = false;
171
+ at++;
172
+ continue;
173
+ }
174
+ const quote = text.indexOf("\"", at);
175
+ const end = quote < 0 ? text.length : quote;
176
+ let run = 0;
177
+ while (end - run - 1 >= at && text.charCodeAt(end - run - 1) === 92) run++;
178
+ if (quote < 0) {
179
+ scan.escaped = run % 2 === 1;
180
+ return -1;
181
+ }
182
+ at = quote + 1;
183
+ if (run % 2 === 1) continue;
184
+ scan.quoted = false;
185
+ if (scan.depth === 0) return at;
186
+ continue;
187
+ }
188
+ const code = text.charCodeAt(at++);
189
+ if (code === 34) scan.quoted = true;
190
+ else if (code === 91 || code === 123) scan.depth++;
191
+ else if ((code === 93 || code === 125) && --scan.depth === 0) return at;
192
+ }
193
+ return -1;
194
+ }
195
+ var JsonReply = class {
196
+ reader;
197
+ decoder = new TextDecoder("utf-8", { fatal: true });
198
+ signal;
199
+ text = "";
200
+ position = 0;
201
+ done = false;
202
+ abort;
203
+ array = "first";
204
+ scan = {
205
+ depth: 0,
206
+ quoted: false,
207
+ escaped: false
208
+ };
209
+ fragments = [];
210
+ failure;
211
+ constructor(options) {
212
+ const { body, signal } = options;
213
+ this.reader = body.getReader();
214
+ this.signal = signal;
215
+ this.abort = () => {
216
+ this.reader.cancel(signal.reason).catch(() => {});
217
+ };
218
+ signal.addEventListener("abort", this.abort, { once: true });
219
+ if (signal.aborted) this.abort();
220
+ }
221
+ async available() {
222
+ while (this.position === this.text.length && !this.done) {
223
+ this.signal.throwIfAborted();
224
+ const chunk = await this.reader.read();
225
+ this.signal.throwIfAborted();
226
+ this.done = Boolean(chunk.done);
227
+ this.text = chunk.done ? this.decoder.decode() : this.decoder.decode(chunk.value, { stream: true });
228
+ this.position = 0;
229
+ }
230
+ return this.position < this.text.length;
231
+ }
232
+ async peek() {
233
+ while (await this.available()) while (this.position < this.text.length) {
234
+ const value = this.text[this.position];
235
+ if (value !== " " && value !== "\n" && value !== "\r" && value !== " ") return value;
236
+ this.position++;
237
+ }
238
+ }
239
+ async expect(value) {
240
+ if (await this.peek() !== value) throw new Error("Invalid or truncated Redis HTTP reply");
241
+ this.position++;
242
+ }
243
+ async value() {
244
+ const first = await this.peek();
245
+ if (first !== "[" && first !== "\"") throw new Error("Invalid Redis HTTP reply value");
246
+ const scan = {
247
+ depth: 0,
248
+ quoted: false,
249
+ escaped: false
250
+ };
251
+ const fragments = [];
252
+ while (await this.available()) {
253
+ const start = this.position;
254
+ const end = scanValue(this.text, start, scan);
255
+ this.position = end < 0 ? this.text.length : end;
256
+ fragments.push(this.text.slice(start, this.position));
257
+ if (end >= 0) return JSON.parse(fragments.join(""));
258
+ }
259
+ throw new Error("Truncated Redis HTTP reply value");
260
+ }
261
+ /**
262
+ * The next entries of an open array, parsed together: every entry that
263
+ * completes in the current transport chunk, reading further chunks only
264
+ * until one has completed. Entries before a malformed one are returned
265
+ * first; the malformed one fails the next call. `undefined` once the
266
+ * array has closed.
267
+ */
268
+ async entries() {
269
+ if (this.failure !== void 0) throw this.failure;
270
+ const texts = [];
271
+ const fail = (message) => {
272
+ const error = new Error(message);
273
+ if (texts.length === 0) throw error;
274
+ this.failure = error;
275
+ };
276
+ while (this.array !== "closed") {
277
+ if (this.position === this.text.length) {
278
+ if (texts.length > 0) break;
279
+ if (!await this.available()) throw new Error(this.array === "entry" ? "Truncated Redis HTTP reply value" : this.array === "separator" ? "Invalid or truncated Redis HTTP reply" : "Invalid Redis HTTP reply value");
280
+ continue;
281
+ }
282
+ if (this.array === "entry") {
283
+ const start = this.position;
284
+ const end = scanValue(this.text, start, this.scan);
285
+ this.position = end < 0 ? this.text.length : end;
286
+ this.fragments.push(this.text.slice(start, this.position));
287
+ if (end < 0) continue;
288
+ texts.push(this.fragments.length === 1 ? this.fragments[0] : this.fragments.join(""));
289
+ this.fragments = [];
290
+ this.array = "separator";
291
+ continue;
292
+ }
293
+ const value = this.text[this.position];
294
+ if (value === " " || value === "\n" || value === "\r" || value === " ") {
295
+ this.position++;
296
+ continue;
297
+ }
298
+ if (this.array === "separator" || this.array === "first" && value === "]") {
299
+ if (value === "]") this.array = "closed";
300
+ else if (value === ",") this.array = "value";
301
+ else {
302
+ fail("Invalid or truncated Redis HTTP reply");
303
+ break;
304
+ }
305
+ this.position++;
306
+ continue;
307
+ }
308
+ if (value !== "[" && value !== "\"") {
309
+ fail("Invalid Redis HTTP reply value");
310
+ break;
311
+ }
312
+ this.scan = {
313
+ depth: 0,
314
+ quoted: false,
315
+ escaped: false
316
+ };
317
+ this.array = "entry";
318
+ }
319
+ return texts.length > 0 ? this.parse(texts) : void 0;
320
+ }
321
+ parse(texts) {
322
+ try {
323
+ return JSON.parse(`[${texts.join(",")}]`);
324
+ } catch {
325
+ const parsed = [];
326
+ for (const text of texts) try {
327
+ parsed.push(JSON.parse(text));
328
+ } catch (error) {
329
+ this.failure = error;
330
+ break;
331
+ }
332
+ if (parsed.length === 0) throw this.failure;
333
+ return parsed;
334
+ }
335
+ }
336
+ async end() {
337
+ await this.expect("}");
338
+ if (await this.peek() !== void 0) throw new Error("Trailing data in Redis HTTP reply");
339
+ }
340
+ async close() {
341
+ this.signal.removeEventListener("abort", this.abort);
342
+ try {
343
+ await this.reader.cancel();
344
+ } finally {
345
+ this.reader.releaseLock();
346
+ this.text = "";
347
+ }
348
+ }
349
+ };
350
+ /**
351
+ * The `result` array of an Upstash REST reply, in batches of the entries
352
+ * that complete in each transport chunk.
353
+ */
354
+ async function* redisHttpReply(options) {
355
+ const reply = new JsonReply(options);
356
+ try {
357
+ await reply.expect("{");
358
+ const key = await reply.value();
359
+ await reply.expect(":");
360
+ if (key === "error") {
361
+ const error = await reply.value();
362
+ if (typeof error !== "string") throw new Error("Invalid Redis HTTP error");
363
+ await reply.end();
364
+ throw new Error(`Redis HTTP command failed: ${String(error)}`);
365
+ }
366
+ if (key !== "result") throw new Error("Invalid Redis HTTP reply envelope");
367
+ await reply.expect("[");
368
+ for (;;) {
369
+ const entries = await reply.entries();
370
+ if (entries === void 0) break;
371
+ yield entries;
372
+ }
373
+ await reply.end();
374
+ } finally {
375
+ await reply.close();
376
+ }
377
+ }
378
+ //#endregion
379
+ //#region src/redis-http-stream.ts
380
+ const REDIS_HTTP_STREAM_READ_COUNT = 1e3;
381
+ async function* readRedisStreamPage({ key, from, signal, url, token, count }) {
382
+ signal.throwIfAborted();
383
+ const response = await fetch(url, {
384
+ method: "POST",
385
+ headers: {
386
+ authorization: `Bearer ${token}`,
387
+ "content-type": "application/json"
388
+ },
389
+ body: JSON.stringify([
390
+ "XRANGE",
391
+ key,
392
+ from,
393
+ "+",
394
+ "COUNT",
395
+ String(count)
396
+ ]),
397
+ signal
398
+ });
399
+ if (!response.ok || !response.body) {
400
+ const body = await response.json().catch(() => null);
401
+ throw new A2Error("STORE_UNAVAILABLE", `redis rest command failed: ${typeof body?.error === "string" ? body.error : `status ${response.status}`}`);
402
+ }
403
+ let last = Number(from.slice(0, -2)) - 1;
404
+ for await (const batch of redisHttpReply({
405
+ body: response.body,
406
+ signal
407
+ })) {
408
+ const entries = [];
409
+ try {
410
+ for (const entry of batch) {
411
+ if (!Array.isArray(entry) || entry.length !== 2 || typeof entry[0] !== "string" || !Array.isArray(entry[1]) || entry[1].length % 2 !== 0 || !entry[1].every((field) => typeof field === "string")) throw new Error("Invalid Redis stream entry");
412
+ const index = /^\d+-0$/.test(entry[0]) ? Number(entry[0].slice(0, -2)) : NaN;
413
+ if (!Number.isSafeInteger(index) || index <= last) throw new Error("Redis stream entry did not advance");
414
+ last = index;
415
+ entries.push(entry);
416
+ }
417
+ } catch (error) {
418
+ if (entries.length > 0) yield entries;
419
+ throw error;
420
+ }
421
+ yield entries;
422
+ }
423
+ }
424
+ //#endregion
152
425
  //#region src/store-redis-http.ts
153
426
  function redisHttp(options) {
154
427
  if (!options.url || !options.token) throw new TypeError("redisHttp() needs a url and a token");
@@ -174,6 +447,12 @@ function redisHttp(options) {
174
447
  if (body === null || !response.ok || body.error !== void 0) throw new A2Error("STORE_UNAVAILABLE", `redis rest command failed: ${body?.error ?? `status ${response.status}`}`);
175
448
  return body.result;
176
449
  };
450
+ const readStream = (read) => readRedisStreamPage({
451
+ ...read,
452
+ url,
453
+ token,
454
+ count: REDIS_HTTP_STREAM_READ_COUNT
455
+ });
177
456
  let subscriber;
178
457
  let onReconnect;
179
458
  const client = {
@@ -184,15 +463,17 @@ function redisHttp(options) {
184
463
  onReconnect: (channel) => onReconnect?.(channel),
185
464
  fallback: {
186
465
  enabled: () => polling,
187
- enable() {
466
+ checkCommands() {
188
467
  fallbackCheck ??= call("PING").then((result) => {
189
468
  if (result !== "PONG") throw new A2Error("STORE_UNAVAILABLE", "redis rest capability check failed");
190
- polling = true;
191
469
  }).catch((error) => {
192
470
  fallbackCheck = void 0;
193
471
  throw error;
194
472
  });
195
473
  return fallbackCheck;
474
+ },
475
+ enable() {
476
+ polling = true;
196
477
  }
197
478
  }
198
479
  }),
@@ -208,7 +489,8 @@ function redisHttp(options) {
208
489
  },
209
490
  clock: options.clock ?? SYSTEM_CLOCK,
210
491
  ids: options.ids ?? RANDOM_IDS,
211
- keyPrefix: options.keyPrefix ?? "a2"
492
+ keyPrefix: options.keyPrefix ?? "a2",
493
+ readStream
212
494
  });
213
495
  }
214
496
  //#endregion
@@ -1 +1 @@
1
- {"version":3,"file":"store-redis-http.js","names":[],"sources":["../src/redis-http-subscriptions.ts","../src/store-redis-http.ts"],"sourcesContent":["import { A2Error } from './errors.ts'\nimport { NOTIFY_TIMINGS } from './internal.ts'\nimport { defaultSleep, type Sleeper } from './store-polling.ts'\nimport type { RedisConnection } from './store-redis-notify.ts'\n\nexport function redisHttpSubscriptions({\n url,\n token,\n onReconnect,\n fallback,\n}: {\n url: string\n token: string\n onReconnect: (channel: string) => void\n fallback: { enabled(): boolean; enable(): Promise<void> }\n}): RedisConnection {\n const subscriptions = new Map<\n string,\n { ready: Promise<void>; stop(): void }\n >()\n const listeners = new Set<(channel: string, message: string) => void>()\n let closed = false\n let supported = false\n\n const subscribe = (channel: string): Promise<void> => {\n if (fallback.enabled()) return Promise.resolve()\n const existing = subscriptions.get(channel)\n if (existing) return existing.ready\n let resolve!: () => void\n let reject!: (error: unknown) => void\n const ready = new Promise<void>((yes, no) => {\n resolve = yes\n reject = no\n })\n let stopped = false\n let connected = false\n let request: AbortController | undefined\n let retry: Sleeper | undefined\n const subscription = {\n ready,\n stop() {\n stopped = true\n request?.abort()\n retry?.cancel()\n resolve()\n },\n }\n subscriptions.set(channel, subscription)\n\n const run = async (): Promise<void> => {\n let delay = 100\n for (;;) {\n if (stopped) return\n request = new AbortController()\n const timeout = setTimeout(\n () => request?.abort(),\n NOTIFY_TIMINGS.safetyReadMs,\n )\n let reader: ReadableStreamDefaultReader<Uint8Array> | undefined\n let connectedAt: number | undefined\n try {\n // oxlint-disable-next-line no-await-in-loop -- reconnect loop\n const response = await fetch(\n `${url.replace(/\\/$/, '')}/subscribe/${encodeURIComponent(channel)}`,\n {\n method: 'POST',\n headers: {\n authorization: `Bearer ${token}`,\n accept: 'text/event-stream',\n },\n signal: request.signal,\n },\n )\n if (!supported && [404, 405, 501].includes(response.status)) {\n // oxlint-disable-next-line no-await-in-loop -- finish capability detection before falling back\n await response.body?.cancel()\n // oxlint-disable-next-line no-await-in-loop -- confirm that ordinary commands work\n await fallback.enable()\n for (const active of subscriptions.values()) active.stop()\n subscriptions.clear()\n return\n }\n if (\n !response.ok ||\n !response.headers\n .get('content-type')\n ?.startsWith('text/event-stream') ||\n !response.body\n ) {\n // oxlint-disable-next-line no-await-in-loop -- release a failed subscription attempt\n await response.body?.cancel()\n throw new A2Error(\n 'STORE_UNAVAILABLE',\n `redis rest subscription failed: status ${response.status}`,\n )\n }\n reader = response.body.getReader()\n const decoder = new TextDecoder()\n let buffer = ''\n let data: string[] = []\n let acknowledged = false\n for (;;) {\n if (stopped) break\n // oxlint-disable-next-line no-await-in-loop -- consume SSE chunks\n const chunk = await reader.read()\n if (chunk.done) break\n buffer += decoder.decode(chunk.value, { stream: true })\n let newline: number\n while ((newline = buffer.indexOf('\\n')) !== -1) {\n const line = buffer.slice(0, newline).replace(/\\r$/, '')\n buffer = buffer.slice(newline + 1)\n if (line.startsWith('data:')) {\n data.push(line.slice(5).replace(/^ /, ''))\n } else if (line === '' && data.length > 0) {\n const message = data.join('\\n')\n data = []\n if (message.startsWith(`subscribe,${channel},`)) {\n if (acknowledged) continue\n acknowledged = true\n supported = true\n clearTimeout(timeout)\n connectedAt = Date.now()\n resolve()\n if (connected) onReconnect(channel)\n connected = true\n } else if (message.startsWith(`message,${channel},`)) {\n const payload = message.slice(`message,${channel},`.length)\n for (const listener of listeners) listener(channel, payload)\n }\n }\n }\n }\n if (!connected && !stopped) {\n throw new A2Error(\n 'STORE_UNAVAILABLE',\n 'redis rest subscription ended before acknowledgement',\n )\n }\n } catch (error) {\n if (!connected && !stopped) {\n reject(error)\n if (subscriptions.get(channel) === subscription)\n subscriptions.delete(channel)\n return\n }\n } finally {\n clearTimeout(timeout)\n request.abort()\n // oxlint-disable-next-line no-await-in-loop -- close this attempt before reconnecting\n await reader?.cancel().catch(() => {})\n reader?.releaseLock()\n }\n if (stopped) return\n if (\n connectedAt !== undefined &&\n Date.now() - connectedAt >= NOTIFY_TIMINGS.safetyReadMs\n )\n delay = 100\n retry = defaultSleep(delay)\n // oxlint-disable-next-line no-await-in-loop -- bounded reconnect backoff\n await retry.promise\n delay = Math.min(delay * 2, NOTIFY_TIMINGS.safetyReadMs)\n }\n }\n void run()\n return ready\n }\n\n const connection: RedisConnection = {\n call(command, ...args) {\n if (closed)\n return Promise.reject(\n new A2Error('STORE_UNAVAILABLE', 'the store was closed'),\n )\n const channel = String(args[0])\n if (command === 'echo') return Promise.resolve(args[0])\n if (command === 'subscribe') return subscribe(channel)\n if (command === 'unsubscribe') {\n subscriptions.get(channel)?.stop()\n subscriptions.delete(channel)\n return Promise.resolve(null)\n }\n return Promise.reject(\n new Error(`unsupported subscription command: ${command}`),\n )\n },\n duplicate: () => connection,\n on(_event, listener) {\n listeners.add(listener)\n },\n disconnect() {\n closed = true\n for (const subscription of subscriptions.values()) subscription.stop()\n subscriptions.clear()\n listeners.clear()\n },\n }\n return connection\n}\n","import { A2Error } from './errors.ts'\nimport { NOTIFY_TIMINGS } from './internal.ts'\nimport { redisHttpSubscriptions } from './redis-http-subscriptions.ts'\nimport {\n createRedisNotifyStore,\n type RedisConnection,\n} from './store-redis-notify.ts'\nimport {\n RANDOM_IDS,\n SYSTEM_CLOCK,\n type A2Store,\n type Clock,\n type IdSource,\n} from './store.ts'\n\nexport type RedisHttpStoreOptions = {\n /** REST endpoint, e.g. `process.env.UPSTASH_REDIS_REST_URL`. */\n url: string\n /** Bearer token, e.g. `process.env.UPSTASH_REDIS_REST_TOKEN`. */\n token: string\n /** Key prefix — isolates multiple apps on one Redis. Default `'a2'`. */\n keyPrefix?: string\n /** Injectable clock — every stored timestamp comes from here. */\n clock?: Clock\n /** Injectable id source for generated event ids. */\n ids?: IdSource\n}\n\nexport type RedisHttpStore = A2Store & {\n /** Close live subscriptions and pending reconnects. */\n close(): Promise<void>\n}\n\nexport function redisHttp(options: RedisHttpStoreOptions): RedisHttpStore {\n if (!options.url || !options.token) {\n throw new TypeError('redisHttp() needs a url and a token')\n }\n const { url, token } = options\n let polling = false\n let fallbackCheck: Promise<void> | undefined\n\n const call = async (\n command: string,\n ...args: Array<string | number>\n ): Promise<unknown> => {\n let response: Response\n try {\n response = await fetch(url, {\n method: 'POST',\n headers: {\n authorization: `Bearer ${token}`,\n 'content-type': 'application/json',\n },\n body: JSON.stringify([command, ...args.map(String)]),\n ...(command === 'PING'\n ? { signal: AbortSignal.timeout(NOTIFY_TIMINGS.safetyReadMs) }\n : {}),\n })\n } catch (err) {\n throw new A2Error('STORE_UNAVAILABLE', 'redis rest request failed', {\n cause: err,\n })\n }\n const body = (await response.json().catch(() => null)) as {\n result?: unknown\n error?: string\n } | null\n if (body === null || !response.ok || body.error !== undefined) {\n throw new A2Error(\n 'STORE_UNAVAILABLE',\n `redis rest command failed: ${body?.error ?? `status ${response.status}`}`,\n )\n }\n return body.result\n }\n\n let subscriber: RedisConnection | undefined\n let onReconnect: ((channel: string) => void) | undefined\n const client: RedisConnection = {\n call,\n duplicate: () =>\n (subscriber ??= redisHttpSubscriptions({\n url,\n token,\n onReconnect: (channel) => onReconnect?.(channel),\n fallback: {\n enabled: () => polling,\n enable() {\n fallbackCheck ??= call('PING')\n .then((result) => {\n if (result !== 'PONG')\n throw new A2Error(\n 'STORE_UNAVAILABLE',\n 'redis rest capability check failed',\n )\n polling = true\n return undefined\n })\n .catch((error: unknown) => {\n fallbackCheck = undefined\n throw error\n })\n return fallbackCheck\n },\n },\n })),\n on() {},\n disconnect: () => subscriber?.disconnect(),\n }\n return createRedisNotifyStore({\n client: () => Promise.resolve(client),\n closeClient: async () => client.disconnect(),\n polling: () => polling,\n onReconnect: (listener) => {\n onReconnect = listener\n },\n clock: options.clock ?? SYSTEM_CLOCK,\n ids: options.ids ?? RANDOM_IDS,\n keyPrefix: options.keyPrefix ?? 'a2',\n })\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"store-redis-http.js","names":[],"sources":["../src/redis-http-subscriptions.ts","../src/redis-http-reply.ts","../src/redis-http-stream.ts","../src/store-redis-http.ts"],"sourcesContent":["import { A2Error } from './errors.ts'\nimport { NOTIFY_TIMINGS } from './internal.ts'\nimport { defaultSleep, type Sleeper } from './store-polling.ts'\nimport type { RedisConnection } from './store-redis-notify.ts'\n\nexport function redisHttpSubscriptions({\n url,\n token,\n onReconnect,\n fallback,\n}: {\n url: string\n token: string\n onReconnect: (channel: string) => void\n fallback: {\n enabled(): boolean\n checkCommands(): Promise<void>\n enable(): void\n }\n}): RedisConnection {\n const subscriptions = new Map<\n string,\n { ready: Promise<void>; stop(): void }\n >()\n const listeners = new Set<(channel: string, message: string) => void>()\n let closed = false\n let supported = false\n\n const subscribe = (channel: string): Promise<void> => {\n if (fallback.enabled()) return Promise.resolve()\n const existing = subscriptions.get(channel)\n if (existing) return existing.ready\n let resolve!: () => void\n let reject!: (error: unknown) => void\n const ready = new Promise<void>((yes, no) => {\n resolve = yes\n reject = no\n })\n let stopped = false\n let connected = false\n let request: AbortController | undefined\n let retry: Sleeper | undefined\n const subscription = {\n ready,\n stop() {\n stopped = true\n request?.abort()\n retry?.cancel()\n resolve()\n },\n }\n subscriptions.set(channel, subscription)\n\n const run = async (): Promise<void> => {\n let delay = 100\n for (;;) {\n if (stopped) return\n request = new AbortController()\n const timeout = setTimeout(\n () => request?.abort(),\n NOTIFY_TIMINGS.safetyReadMs,\n )\n let reader: ReadableStreamDefaultReader<Uint8Array> | undefined\n let connectedAt: number | undefined\n try {\n // oxlint-disable-next-line no-await-in-loop -- reconnect loop\n const response = await fetch(\n `${url.replace(/\\/$/, '')}/subscribe/${encodeURIComponent(channel)}`,\n {\n method: 'POST',\n headers: {\n authorization: `Bearer ${token}`,\n accept: 'text/event-stream',\n },\n signal: request.signal,\n },\n )\n if (!supported && [404, 405, 501].includes(response.status)) {\n // oxlint-disable-next-line no-await-in-loop -- finish capability detection before falling back\n await response.body?.cancel()\n // oxlint-disable-next-line no-await-in-loop -- confirm that ordinary commands work\n await fallback.checkCommands()\n if (stopped) return\n if (!supported) {\n fallback.enable()\n for (const active of subscriptions.values()) active.stop()\n subscriptions.clear()\n return\n }\n }\n if (\n !response.ok ||\n !response.headers\n .get('content-type')\n ?.startsWith('text/event-stream') ||\n !response.body\n ) {\n // oxlint-disable-next-line no-await-in-loop -- release a failed subscription attempt\n await response.body?.cancel()\n throw new A2Error(\n 'STORE_UNAVAILABLE',\n `redis rest subscription failed: status ${response.status}`,\n )\n }\n reader = response.body.getReader()\n const decoder = new TextDecoder()\n let buffer = ''\n let data: string[] = []\n let acknowledged = false\n for (;;) {\n if (stopped) break\n // oxlint-disable-next-line no-await-in-loop -- consume SSE chunks\n const chunk = await reader.read()\n if (chunk.done) break\n buffer += decoder.decode(chunk.value, { stream: true })\n let newline: number\n while ((newline = buffer.indexOf('\\n')) !== -1) {\n const line = buffer.slice(0, newline).replace(/\\r$/, '')\n buffer = buffer.slice(newline + 1)\n if (line.startsWith('data:')) {\n data.push(line.slice(5).replace(/^ /, ''))\n } else if (line === '' && data.length > 0) {\n const message = data.join('\\n')\n data = []\n if (message.startsWith(`subscribe,${channel},`)) {\n if (acknowledged) continue\n acknowledged = true\n supported = true\n clearTimeout(timeout)\n connectedAt = Date.now()\n resolve()\n if (connected) onReconnect(channel)\n connected = true\n } else if (message.startsWith(`message,${channel},`)) {\n const payload = message.slice(`message,${channel},`.length)\n for (const listener of listeners) listener(channel, payload)\n }\n }\n }\n }\n if (!connected && !stopped) {\n throw new A2Error(\n 'STORE_UNAVAILABLE',\n 'redis rest subscription ended before acknowledgement',\n )\n }\n } catch (error) {\n if (!connected && !stopped) {\n reject(error)\n if (subscriptions.get(channel) === subscription)\n subscriptions.delete(channel)\n return\n }\n } finally {\n clearTimeout(timeout)\n request.abort()\n // oxlint-disable-next-line no-await-in-loop -- close this attempt before reconnecting\n await reader?.cancel().catch(() => {})\n reader?.releaseLock()\n }\n if (stopped) return\n if (\n connectedAt !== undefined &&\n Date.now() - connectedAt >= NOTIFY_TIMINGS.safetyReadMs\n )\n delay = 100\n retry = defaultSleep(delay)\n // oxlint-disable-next-line no-await-in-loop -- bounded reconnect backoff\n await retry.promise\n delay = Math.min(delay * 2, NOTIFY_TIMINGS.safetyReadMs)\n }\n }\n void run()\n return ready\n }\n\n const connection: RedisConnection = {\n call(command, ...args) {\n if (closed)\n return Promise.reject(\n new A2Error('STORE_UNAVAILABLE', 'the store was closed'),\n )\n const channel = String(args[0])\n if (command === 'echo') return Promise.resolve(args[0])\n if (command === 'subscribe') return subscribe(channel)\n if (command === 'unsubscribe') {\n subscriptions.get(channel)?.stop()\n subscriptions.delete(channel)\n return Promise.resolve(null)\n }\n return Promise.reject(\n new Error(`unsupported subscription command: ${command}`),\n )\n },\n duplicate: () => connection,\n on(_event, listener) {\n listeners.add(listener)\n },\n disconnect() {\n closed = true\n for (const subscription of subscriptions.values()) subscription.stop()\n subscriptions.clear()\n listeners.clear()\n },\n }\n return connection\n}\n","/* oxlint-disable no-await-in-loop -- response bytes and entries must be consumed in order */\n\ntype Scan = { depth: number; quoted: boolean; escaped: boolean }\n\n/**\n * Index just past the array or string `scan` is inside, or -1 when `text`\n * ends first; `scan` then carries the state into the next chunk. Strings\n * are skipped with `indexOf`, so the per-character loop only walks\n * structure. A quote is escaped when an odd run of backslashes precedes\n * it; a run is counted back only to where the string scan resumed, and\n * `escaped` carries an odd trailing run across the chunk boundary.\n */\nfunction scanValue(text: string, from: number, scan: Scan): number {\n let at = from\n while (at < text.length) {\n if (scan.quoted) {\n if (scan.escaped) {\n scan.escaped = false\n at++\n continue\n }\n const quote = text.indexOf('\"', at)\n const end = quote < 0 ? text.length : quote\n let run = 0\n while (end - run - 1 >= at && text.charCodeAt(end - run - 1) === 92) run++\n if (quote < 0) {\n scan.escaped = run % 2 === 1\n return -1\n }\n at = quote + 1\n if (run % 2 === 1) continue\n scan.quoted = false\n if (scan.depth === 0) return at\n continue\n }\n const code = text.charCodeAt(at++)\n if (code === 34) scan.quoted = true\n else if (code === 91 || code === 123) scan.depth++\n else if ((code === 93 || code === 125) && --scan.depth === 0) return at\n }\n return -1\n}\n\nclass JsonReply {\n private readonly reader: ReadableStreamDefaultReader<Uint8Array>\n private readonly decoder = new TextDecoder('utf-8', { fatal: true })\n private readonly signal: AbortSignal\n private text = ''\n private position = 0\n private done = false\n private readonly abort: () => void\n private array: 'first' | 'value' | 'entry' | 'separator' | 'closed' = 'first'\n private scan: Scan = { depth: 0, quoted: false, escaped: false }\n private fragments: string[] = []\n private failure: unknown\n\n constructor(options: {\n body: ReadableStream<Uint8Array>\n signal: AbortSignal\n }) {\n const { body, signal } = options\n this.reader = body.getReader()\n this.signal = signal\n this.abort = () => {\n void this.reader.cancel(signal.reason).catch(() => {})\n }\n signal.addEventListener('abort', this.abort, { once: true })\n if (signal.aborted) this.abort()\n }\n\n private async available(): Promise<boolean> {\n while (this.position === this.text.length && !this.done) {\n this.signal.throwIfAborted()\n const chunk = await this.reader.read()\n this.signal.throwIfAborted()\n this.done = Boolean(chunk.done)\n this.text = chunk.done\n ? this.decoder.decode()\n : this.decoder.decode(chunk.value, { stream: true })\n this.position = 0\n }\n return this.position < this.text.length\n }\n\n async peek(): Promise<string | undefined> {\n while (await this.available()) {\n while (this.position < this.text.length) {\n const value = this.text[this.position]!\n if (value !== ' ' && value !== '\\n' && value !== '\\r' && value !== '\\t')\n return value\n this.position++\n }\n }\n return undefined\n }\n\n async expect(value: string): Promise<void> {\n if ((await this.peek()) !== value)\n throw new Error('Invalid or truncated Redis HTTP reply')\n this.position++\n }\n\n async value(): Promise<unknown> {\n const first = await this.peek()\n if (first !== '[' && first !== '\"')\n throw new Error('Invalid Redis HTTP reply value')\n const scan: Scan = { depth: 0, quoted: false, escaped: false }\n const fragments: string[] = []\n while (await this.available()) {\n const start = this.position\n const end = scanValue(this.text, start, scan)\n this.position = end < 0 ? this.text.length : end\n fragments.push(this.text.slice(start, this.position))\n if (end >= 0) return JSON.parse(fragments.join('')) as unknown\n }\n throw new Error('Truncated Redis HTTP reply value')\n }\n\n /**\n * The next entries of an open array, parsed together: every entry that\n * completes in the current transport chunk, reading further chunks only\n * until one has completed. Entries before a malformed one are returned\n * first; the malformed one fails the next call. `undefined` once the\n * array has closed.\n */\n async entries(): Promise<unknown[] | undefined> {\n if (this.failure !== undefined) throw this.failure\n const texts: string[] = []\n const fail = (message: string): void => {\n const error = new Error(message)\n if (texts.length === 0) throw error\n this.failure = error\n }\n while (this.array !== 'closed') {\n if (this.position === this.text.length) {\n if (texts.length > 0) break\n if (!(await this.available()))\n throw new Error(\n this.array === 'entry'\n ? 'Truncated Redis HTTP reply value'\n : this.array === 'separator'\n ? 'Invalid or truncated Redis HTTP reply'\n : 'Invalid Redis HTTP reply value',\n )\n continue\n }\n if (this.array === 'entry') {\n const start = this.position\n const end = scanValue(this.text, start, this.scan)\n this.position = end < 0 ? this.text.length : end\n this.fragments.push(this.text.slice(start, this.position))\n if (end < 0) continue\n texts.push(\n this.fragments.length === 1\n ? this.fragments[0]!\n : this.fragments.join(''),\n )\n this.fragments = []\n this.array = 'separator'\n continue\n }\n const value = this.text[this.position]!\n if (value === ' ' || value === '\\n' || value === '\\r' || value === '\\t') {\n this.position++\n continue\n }\n if (\n this.array === 'separator' ||\n (this.array === 'first' && value === ']')\n ) {\n if (value === ']') this.array = 'closed'\n else if (value === ',') this.array = 'value'\n else {\n fail('Invalid or truncated Redis HTTP reply')\n break\n }\n this.position++\n continue\n }\n if (value !== '[' && value !== '\"') {\n fail('Invalid Redis HTTP reply value')\n break\n }\n this.scan = { depth: 0, quoted: false, escaped: false }\n this.array = 'entry'\n }\n return texts.length > 0 ? this.parse(texts) : undefined\n }\n\n private parse(texts: string[]): unknown[] {\n try {\n return JSON.parse(`[${texts.join(',')}]`) as unknown[]\n } catch {\n // The joined parse fails only if one entry does: keep those before it.\n const parsed: unknown[] = []\n for (const text of texts) {\n try {\n parsed.push(JSON.parse(text))\n } catch (error) {\n this.failure = error\n break\n }\n }\n if (parsed.length === 0) throw this.failure\n return parsed\n }\n }\n\n async end(): Promise<void> {\n await this.expect('}')\n if ((await this.peek()) !== undefined)\n throw new Error('Trailing data in Redis HTTP reply')\n }\n\n async close(): Promise<void> {\n this.signal.removeEventListener('abort', this.abort)\n try {\n await this.reader.cancel()\n } finally {\n this.reader.releaseLock()\n this.text = ''\n }\n }\n}\n\n/**\n * The `result` array of an Upstash REST reply, in batches of the entries\n * that complete in each transport chunk.\n */\nexport async function* redisHttpReply(options: {\n body: ReadableStream<Uint8Array>\n signal: AbortSignal\n}): AsyncGenerator<unknown[]> {\n const reply = new JsonReply(options)\n try {\n await reply.expect('{')\n const key = await reply.value()\n await reply.expect(':')\n if (key === 'error') {\n const error = await reply.value()\n if (typeof error !== 'string') throw new Error('Invalid Redis HTTP error')\n await reply.end()\n throw new Error(`Redis HTTP command failed: ${String(error)}`)\n }\n if (key !== 'result') throw new Error('Invalid Redis HTTP reply envelope')\n await reply.expect('[')\n for (;;) {\n const entries = await reply.entries()\n if (entries === undefined) break\n yield entries\n }\n await reply.end()\n } finally {\n await reply.close()\n }\n}\n","import { A2Error } from './errors.ts'\nimport { redisHttpReply } from './redis-http-reply.ts'\nimport type { StreamEntry } from './store-redis-core.ts'\n\nexport const REDIS_HTTP_STREAM_READ_COUNT = 1000\n\nexport async function* readRedisStreamPage({\n key,\n from,\n signal,\n url,\n token,\n count,\n}: {\n key: string\n from: string\n signal: AbortSignal\n url: string\n token: string\n count: number\n}): AsyncGenerator<StreamEntry[], void> {\n signal.throwIfAborted()\n const response = await fetch(url, {\n method: 'POST',\n headers: {\n authorization: `Bearer ${token}`,\n 'content-type': 'application/json',\n },\n body: JSON.stringify(['XRANGE', key, from, '+', 'COUNT', String(count)]),\n signal,\n })\n if (!response.ok || !response.body) {\n const body = (await response.json().catch(() => null)) as {\n error?: unknown\n } | null\n throw new A2Error(\n 'STORE_UNAVAILABLE',\n `redis rest command failed: ${\n typeof body?.error === 'string'\n ? body.error\n : `status ${response.status}`\n }`,\n )\n }\n let last = Number(from.slice(0, -2)) - 1\n for await (const batch of redisHttpReply({ body: response.body, signal })) {\n const entries: StreamEntry[] = []\n try {\n for (const entry of batch) {\n if (\n !Array.isArray(entry) ||\n entry.length !== 2 ||\n typeof entry[0] !== 'string' ||\n !Array.isArray(entry[1]) ||\n entry[1].length % 2 !== 0 ||\n !entry[1].every((field) => typeof field === 'string')\n )\n throw new Error('Invalid Redis stream entry')\n const index = /^\\d+-0$/.test(entry[0])\n ? Number(entry[0].slice(0, -2))\n : NaN\n if (!Number.isSafeInteger(index) || index <= last)\n throw new Error('Redis stream entry did not advance')\n last = index\n entries.push(entry as StreamEntry)\n }\n } catch (error) {\n if (entries.length > 0) yield entries\n throw error\n }\n yield entries\n }\n}\n","import { A2Error } from './errors.ts'\nimport { NOTIFY_TIMINGS } from './internal.ts'\nimport { redisHttpSubscriptions } from './redis-http-subscriptions.ts'\nimport {\n readRedisStreamPage,\n REDIS_HTTP_STREAM_READ_COUNT,\n} from './redis-http-stream.ts'\nimport type { RedisStreamRead } from './store-redis-core.ts'\nimport {\n createRedisNotifyStore,\n type RedisConnection,\n} from './store-redis-notify.ts'\nimport {\n RANDOM_IDS,\n SYSTEM_CLOCK,\n type A2Store,\n type Clock,\n type IdSource,\n} from './store.ts'\n\nexport type RedisHttpStoreOptions = {\n /** REST endpoint, e.g. `process.env.UPSTASH_REDIS_REST_URL`. */\n url: string\n /** Bearer token, e.g. `process.env.UPSTASH_REDIS_REST_TOKEN`. */\n token: string\n /** Key prefix — isolates multiple apps on one Redis. Default `'a2'`. */\n keyPrefix?: string\n /** Injectable clock — every stored timestamp comes from here. */\n clock?: Clock\n /** Injectable id source for generated event ids. */\n ids?: IdSource\n}\n\nexport type RedisHttpStore = A2Store & {\n /** Close live subscriptions and pending reconnects. */\n close(): Promise<void>\n}\n\nexport function redisHttp(options: RedisHttpStoreOptions): RedisHttpStore {\n if (!options.url || !options.token) {\n throw new TypeError('redisHttp() needs a url and a token')\n }\n const { url, token } = options\n let polling = false\n let fallbackCheck: Promise<void> | undefined\n\n const call = async (\n command: string,\n ...args: Array<string | number>\n ): Promise<unknown> => {\n let response: Response\n try {\n response = await fetch(url, {\n method: 'POST',\n headers: {\n authorization: `Bearer ${token}`,\n 'content-type': 'application/json',\n },\n body: JSON.stringify([command, ...args.map(String)]),\n ...(command === 'PING'\n ? { signal: AbortSignal.timeout(NOTIFY_TIMINGS.safetyReadMs) }\n : {}),\n })\n } catch (err) {\n throw new A2Error('STORE_UNAVAILABLE', 'redis rest request failed', {\n cause: err,\n })\n }\n const body = (await response.json().catch(() => null)) as {\n result?: unknown\n error?: string\n } | null\n if (body === null || !response.ok || body.error !== undefined) {\n throw new A2Error(\n 'STORE_UNAVAILABLE',\n `redis rest command failed: ${body?.error ?? `status ${response.status}`}`,\n )\n }\n return body.result\n }\n\n const readStream: RedisStreamRead = (read) =>\n readRedisStreamPage({\n ...read,\n url,\n token,\n count: REDIS_HTTP_STREAM_READ_COUNT,\n })\n\n let subscriber: RedisConnection | undefined\n let onReconnect: ((channel: string) => void) | undefined\n const client: RedisConnection = {\n call,\n duplicate: () =>\n (subscriber ??= redisHttpSubscriptions({\n url,\n token,\n onReconnect: (channel) => onReconnect?.(channel),\n fallback: {\n enabled: () => polling,\n checkCommands() {\n fallbackCheck ??= call('PING')\n .then((result) => {\n if (result !== 'PONG')\n throw new A2Error(\n 'STORE_UNAVAILABLE',\n 'redis rest capability check failed',\n )\n return undefined\n })\n .catch((error: unknown) => {\n fallbackCheck = undefined\n throw error\n })\n return fallbackCheck\n },\n enable() {\n polling = true\n },\n },\n })),\n on() {},\n disconnect: () => subscriber?.disconnect(),\n }\n return createRedisNotifyStore({\n client: () => Promise.resolve(client),\n closeClient: async () => client.disconnect(),\n polling: () => polling,\n onReconnect: (listener) => {\n onReconnect = listener\n },\n clock: options.clock ?? SYSTEM_CLOCK,\n ids: options.ids ?? RANDOM_IDS,\n keyPrefix: options.keyPrefix ?? 'a2',\n readStream,\n })\n}\n"],"mappings":"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"}