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.
- package/CHANGELOG.md +352 -0
- package/dist/{actor-ohPC-81x.d.ts → actor-DBbe5xeE.d.ts} +21 -11
- package/dist/actor-DBbe5xeE.d.ts.map +1 -0
- package/dist/actor-client.d.ts +2 -5
- package/dist/actor-client.d.ts.map +1 -1
- package/dist/actor-client.js +13 -4
- package/dist/actor-client.js.map +1 -1
- package/dist/actor-react.d.ts +5 -3
- package/dist/actor-react.d.ts.map +1 -1
- package/dist/actor-react.js +20 -10
- package/dist/actor-react.js.map +1 -1
- package/dist/{actor-shared-B5tJfzt-.js → actor-shared-t7w8xIKP.js} +12 -7
- package/dist/actor-shared-t7w8xIKP.js.map +1 -0
- package/dist/actor.d.ts +1 -1
- package/dist/actor.js +42 -23
- package/dist/actor.js.map +1 -1
- package/dist/{ai-DgOBltJ_.js → ai-UrWfKeuv.js} +621 -216
- package/dist/ai-UrWfKeuv.js.map +1 -0
- package/dist/ai-client.d.ts +27 -0
- package/dist/ai-client.d.ts.map +1 -0
- package/dist/ai-client.js +29 -0
- package/dist/ai-client.js.map +1 -0
- package/dist/ai-react.d.ts +96 -0
- package/dist/ai-react.d.ts.map +1 -0
- package/dist/ai-react.js +142 -0
- package/dist/ai-react.js.map +1 -0
- package/dist/ai-server.browser.js +2 -2
- package/dist/ai-server.browser.js.map +1 -1
- package/dist/ai-server.d.ts +80 -36
- package/dist/ai-server.d.ts.map +1 -1
- package/dist/ai-server.js +432 -258
- package/dist/ai-server.js.map +1 -1
- package/dist/{ai-CrEf6p_W.d.ts → ai-vpGtcaCB.d.ts} +114 -100
- package/dist/ai-vpGtcaCB.d.ts.map +1 -0
- package/dist/ai.d.ts +2 -2
- package/dist/ai.js +2 -2
- package/dist/{client-BYzHjkwU.js → client-COaiaDub.js} +447 -80
- package/dist/client-COaiaDub.js.map +1 -0
- package/dist/{client-CzyacQpJ.d.ts → client-qRnRUzBS.d.ts} +14 -13
- package/dist/client-qRnRUzBS.d.ts.map +1 -0
- package/dist/client.d.ts +4 -2
- package/dist/client.js +1 -1
- package/dist/{contract-CKRg_E4q.js → contract-BYKASlCV.js} +12 -21
- package/dist/contract-BYKASlCV.js.map +1 -0
- package/dist/{idempotent-replay-DVOlyYbx.js → idempotent-replay-JdgMQRRm.js} +2 -2
- package/dist/{idempotent-replay-DVOlyYbx.js.map → idempotent-replay-JdgMQRRm.js.map} +1 -1
- package/dist/index.d.ts +6 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -3
- package/dist/{errors-DvhSXnxk.d.ts → initial-snapshot-BrLso5x_.d.ts} +7 -3
- package/dist/initial-snapshot-BrLso5x_.d.ts.map +1 -0
- package/dist/{internal-Dq2qYxou.js → internal-CbVvEwad.js} +23 -2
- package/dist/internal-CbVvEwad.js.map +1 -0
- package/dist/otel.d.ts +1 -1
- package/dist/react-BbJ-g6Uu.d.ts +86 -0
- package/dist/react-BbJ-g6Uu.d.ts.map +1 -0
- package/dist/react.d.ts +2 -122
- package/dist/react.js +107 -100
- package/dist/react.js.map +1 -1
- package/dist/reducer-BAXc8JyF.js +87 -0
- package/dist/reducer-BAXc8JyF.js.map +1 -0
- package/dist/{reducer-otzHjuJj.d.ts → reducer-BYu6DT7h.d.ts} +45 -29
- package/dist/reducer-BYu6DT7h.d.ts.map +1 -0
- package/dist/reducer-runtime-DQwz_J59.js +28 -0
- package/dist/reducer-runtime-DQwz_J59.js.map +1 -0
- package/dist/scheduler-qstash.d.ts +3 -3
- package/dist/scheduler-qstash.d.ts.map +1 -1
- package/dist/scheduler-qstash.js +33 -10
- package/dist/scheduler-qstash.js.map +1 -1
- package/dist/{scheduler-task-BpzhPnRS.js → scheduler-task-X1SToDjP.js} +18 -3
- package/dist/scheduler-task-X1SToDjP.js.map +1 -0
- package/dist/scheduler-vercel.d.ts +4 -9
- package/dist/scheduler-vercel.d.ts.map +1 -1
- package/dist/scheduler-vercel.js +25 -44
- package/dist/scheduler-vercel.js.map +1 -1
- package/dist/{server-DbD7IJVK.d.ts → server-CGM8WzRG.d.ts} +87 -26
- package/dist/server-CGM8WzRG.d.ts.map +1 -0
- package/dist/{server-BD5ckxZb.js → server-Rj64fNvX.js} +854 -202
- package/dist/server-Rj64fNvX.js.map +1 -0
- package/dist/server.d.ts +4 -4
- package/dist/server.js +2 -2
- package/dist/{errors-DCk6ch5n.js → session-clear-DGI4jTQ5.js} +17 -2
- package/dist/session-clear-DGI4jTQ5.js.map +1 -0
- package/dist/session-clear-Dshpuhff.d.ts +12 -0
- package/dist/session-clear-Dshpuhff.d.ts.map +1 -0
- package/dist/{store-DyZM6fS5.d.ts → store-Gpkq7WBo.d.ts} +44 -3
- package/dist/store-Gpkq7WBo.d.ts.map +1 -0
- package/dist/store-N8PXxDAS.js.map +1 -1
- package/dist/store-memory.d.ts +1 -1
- package/dist/store-memory.d.ts.map +1 -1
- package/dist/store-memory.js +182 -69
- package/dist/store-memory.js.map +1 -1
- package/dist/{store-polling-CmxUbV93.js → store-polling-4_y8lETm.js} +21 -4
- package/dist/store-polling-4_y8lETm.js.map +1 -0
- package/dist/store-postgres.d.ts +1 -1
- package/dist/store-postgres.d.ts.map +1 -1
- package/dist/store-postgres.js +399 -108
- package/dist/store-postgres.js.map +1 -1
- package/dist/{store-presence-polling-C7-XZyW9.js → store-presence-polling-Q1C-k2dd.js} +3 -3
- package/dist/{store-presence-polling-C7-XZyW9.js.map → store-presence-polling-Q1C-k2dd.js.map} +1 -1
- package/dist/store-redis-http.d.ts +1 -1
- package/dist/store-redis-http.d.ts.map +1 -1
- package/dist/store-redis-http.js +293 -11
- package/dist/store-redis-http.js.map +1 -1
- package/dist/store-redis-notify-tqWNnMRu.js +2374 -0
- package/dist/store-redis-notify-tqWNnMRu.js.map +1 -0
- package/dist/store-redis.d.ts +1 -1
- package/dist/store-redis.d.ts.map +1 -1
- package/dist/store-redis.js +11 -11
- package/dist/store-redis.js.map +1 -1
- package/dist/store-sqlite.d.ts +1 -1
- package/dist/store-sqlite.d.ts.map +1 -1
- package/dist/store-sqlite.js +166 -32
- package/dist/store-sqlite.js.map +1 -1
- package/dist/stream-buffer-Cx7AAPGq.js +23 -0
- package/dist/stream-buffer-Cx7AAPGq.js.map +1 -0
- package/dist/{telemetry-B5jzpy6y.d.ts → telemetry-cZH-cibj.d.ts} +2 -2
- package/dist/telemetry-cZH-cibj.d.ts.map +1 -0
- package/dist/{wire-BO5wWCb1.js → wire-DjfHOvdW.js} +132 -10
- package/dist/wire-DjfHOvdW.js.map +1 -0
- package/docs/01-quickstart.mdx +2 -2
- package/docs/actors/01-introduction.mdx +4 -0
- package/docs/actors/04-routes.mdx +14 -1
- package/docs/concepts/01-contracts.mdx +95 -0
- package/docs/concepts/04-state.mdx +3 -3
- package/docs/guides/01-timers.mdx +22 -14
- package/docs/guides/02-cancellation.mdx +1 -1
- package/docs/guides/03-react.mdx +268 -328
- package/docs/guides/04-local-first.mdx +32 -73
- package/docs/guides/05-production.mdx +40 -27
- package/docs/guides/06-ai-agents.mdx +671 -581
- package/docs/guides/09-presence.mdx +3 -7
- package/docs/guides/10-transports.mdx +5 -5
- package/docs/index.mdx +10 -18
- package/docs/reference/01-api.mdx +656 -194
- package/docs/reference/02-errors.mdx +2 -1
- package/examples/playground/DEPLOY.md +52 -6
- package/examples/playground/app/a0/[appId]/loading.tsx +16 -0
- package/examples/playground/app/a0/[appId]/page.tsx +48 -0
- package/examples/playground/app/a0/a0.css +818 -0
- package/examples/playground/app/a0/app-list.tsx +211 -0
- package/examples/playground/app/a0/app-view.tsx +55 -0
- package/examples/playground/app/a0/apps/[appId]/preview/route.ts +27 -0
- package/examples/playground/app/a0/apps/[appId]/route.ts +23 -0
- package/examples/playground/app/a0/apps.test.ts +116 -0
- package/examples/playground/app/a0/apps.ts +87 -0
- package/examples/playground/app/a0/chat.tsx +364 -0
- package/examples/playground/app/a0/columns.tsx +109 -0
- package/examples/playground/app/a0/context-menu.tsx +116 -0
- package/examples/playground/app/a0/cookies.ts +46 -0
- package/examples/playground/app/a0/crash-on-request.ts +72 -0
- package/examples/playground/app/a0/crashes/route.ts +3 -0
- package/examples/playground/app/a0/crashes.test.ts +33 -0
- package/examples/playground/app/a0/crashes.ts +28 -0
- package/examples/playground/app/a0/devtools.tsx +164 -0
- package/examples/playground/app/a0/events/route.ts +6 -0
- package/examples/playground/app/a0/flags.ts +30 -0
- package/examples/playground/app/a0/layout.tsx +38 -0
- package/examples/playground/app/a0/list/route.ts +10 -0
- package/examples/playground/app/a0/model.test.ts +56 -0
- package/examples/playground/app/a0/model.ts +58 -0
- package/examples/playground/app/a0/page.tsx +14 -0
- package/examples/playground/app/a0/preview-pane.tsx +145 -0
- package/examples/playground/app/a0/preview.test.ts +53 -0
- package/examples/playground/app/a0/preview.ts +26 -0
- package/examples/playground/app/a0/sandbox-log.ts +69 -0
- package/examples/playground/app/a0/sandbox.ts +191 -0
- package/examples/playground/app/a0/sandboxes/scheduler/route.ts +6 -0
- package/examples/playground/app/a0/scheduler/route.ts +7 -0
- package/examples/playground/app/a0/server.ts +158 -0
- package/examples/playground/app/a0/session.ts +47 -0
- package/examples/playground/app/a0/shell.tsx +150 -0
- package/examples/playground/app/a0/short/events/route.ts +9 -0
- package/examples/playground/app/a0/splitter.tsx +120 -0
- package/examples/playground/app/agent/[agentId]/agent-client.tsx +144 -174
- package/examples/playground/app/agent/[agentId]/command-receipt.test.tsx +65 -0
- package/examples/playground/app/agent/[agentId]/command-receipt.tsx +27 -0
- package/examples/playground/app/agent/[agentId]/compaction-event.tsx +25 -24
- package/examples/playground/app/agent/[agentId]/compaction-panel.test.tsx +119 -0
- package/examples/playground/app/agent/[agentId]/compaction-panel.tsx +208 -165
- package/examples/playground/app/agent/[agentId]/page.tsx +2 -16
- package/examples/playground/app/agent/events/route.ts +5 -2
- package/examples/playground/app/agent/ingress.test.ts +98 -0
- package/examples/playground/app/agent/model-input.test.tsx +58 -0
- package/examples/playground/app/agent/model-input.tsx +115 -0
- package/examples/playground/app/agent/model.test.ts +73 -0
- package/examples/playground/app/agent/model.ts +58 -26
- package/examples/playground/app/agent/new-agent-session.tsx +37 -19
- package/examples/playground/app/agent/page.tsx +4 -4
- package/examples/playground/app/agent/server.ts +15 -14
- package/examples/playground/app/agent/session.ts +5 -6
- package/examples/playground/app/agent/telemetry.test.ts +168 -0
- package/examples/playground/app/agent/telemetry.ts +30 -0
- package/examples/playground/app/agent/trace.test.ts +23 -0
- package/examples/playground/app/agent/trace.ts +18 -0
- package/examples/playground/app/canvas/[canvasId]/canvas-client.tsx +11 -11
- package/examples/playground/app/canvas/[canvasId]/page.tsx +1 -2
- package/examples/playground/app/canvas/[canvasId]/session.ts +2 -2
- package/examples/playground/app/canvas/model.ts +53 -53
- package/examples/playground/app/chat/[chatId]/agent-state.test.ts +176 -0
- package/examples/playground/app/chat/[chatId]/agent-stream-drawer.tsx +16 -1
- package/examples/playground/app/chat/[chatId]/chat-client.tsx +55 -77
- package/examples/playground/app/chat/[chatId]/chat-view.ts +37 -3
- package/examples/playground/app/chat/[chatId]/page.tsx +7 -15
- package/examples/playground/app/chat/[chatId]/session.ts +2 -19
- package/examples/playground/app/chat/agent/route.ts +9 -0
- package/examples/playground/app/chat/model.test.ts +46 -119
- package/examples/playground/app/chat/model.ts +82 -128
- package/examples/playground/app/chat/scheduler/route.ts +2 -2
- package/examples/playground/app/chat/server.test.ts +108 -0
- package/examples/playground/app/chat/server.ts +69 -73
- package/examples/playground/app/components/activity-feed.tsx +30 -1
- package/examples/playground/app/components/activity-history.test.tsx +35 -0
- package/examples/playground/app/components/demo-icons.tsx +89 -0
- package/examples/playground/app/components/demos.tsx +33 -0
- package/examples/playground/app/components/fast-link.test.tsx +58 -0
- package/examples/playground/app/components/fast-link.tsx +55 -0
- package/examples/playground/app/components/site-header.tsx +96 -0
- package/examples/playground/app/counter/counter-client.tsx +3 -3
- package/examples/playground/app/counter/model.test.ts +3 -3
- package/examples/playground/app/counter/model.ts +11 -21
- package/examples/playground/app/counter/page.tsx +2 -15
- package/examples/playground/app/counter/session.ts +2 -2
- package/examples/playground/app/documents/[documentId]/code-editor.tsx +12 -1
- package/examples/playground/app/documents/[documentId]/document-client.tsx +10 -11
- package/examples/playground/app/documents/[documentId]/page.tsx +1 -2
- package/examples/playground/app/documents/[documentId]/session.ts +2 -2
- package/examples/playground/app/documents/model.ts +27 -27
- package/examples/playground/app/globals.css +420 -207
- package/examples/playground/app/initial-state.test.tsx +54 -0
- package/examples/playground/app/layout.tsx +7 -23
- package/examples/playground/app/orders/[orderId]/page.tsx +1 -2
- package/examples/playground/app/orders/[orderId]/session.ts +2 -2
- package/examples/playground/app/orders/model.ts +49 -49
- package/examples/playground/app/page.tsx +13 -102
- package/examples/playground/app/recovery/[recoveryId]/page.tsx +1 -2
- package/examples/playground/app/recovery/[recoveryId]/session.ts +2 -2
- package/examples/playground/app/recovery/model.ts +30 -30
- package/examples/playground/instrumentation.ts +5 -0
- package/examples/playground/package.json +10 -2
- package/examples/playground/scripts/a0-template.ts +77 -0
- package/examples/playground/vercel.json +18 -0
- package/package.json +3 -1
- package/src/actor-client.ts +16 -4
- package/src/actor-react.ts +25 -11
- package/src/actor-shared.ts +21 -6
- package/src/actor.ts +76 -31
- package/src/ai-agent-actions.ts +219 -0
- package/src/ai-agent.ts +2343 -0
- package/src/ai-app-state.ts +91 -0
- package/src/ai-client-state.ts +6 -3
- package/src/ai-client.ts +62 -0
- package/src/ai-context-schema.ts +25 -5
- package/src/ai-context.ts +90 -40
- package/src/ai-control-server.ts +92 -72
- package/src/ai-coordinator.ts +23 -22
- package/src/ai-message-base.ts +209 -0
- package/src/ai-message-projection.ts +262 -64
- package/src/ai-progress-batches.ts +26 -0
- package/src/ai-projector.ts +95 -14
- package/src/ai-react.ts +146 -0
- package/src/ai-server.browser.ts +2 -2
- package/src/ai-server.ts +20 -1964
- package/src/ai-stored-state.ts +48 -163
- package/src/ai.ts +339 -155
- package/src/bootstrap-stream.ts +72 -0
- package/src/bounded-cache.ts +55 -0
- package/src/client-render-session.ts +49 -0
- package/src/client.ts +688 -151
- package/src/contract.ts +92 -42
- package/src/deterministic-id.ts +37 -0
- package/src/equal-json.ts +32 -0
- package/src/errors.ts +13 -1
- package/src/index.ts +5 -1
- package/src/initial-snapshot.ts +39 -0
- package/src/internal.ts +45 -0
- package/src/postgres-notification-scope.ts +14 -3
- package/src/react.ts +171 -206
- package/src/redis-http-reply.ts +256 -0
- package/src/redis-http-stream.ts +73 -0
- package/src/redis-http-subscriptions.ts +13 -5
- package/src/reducer-runtime.ts +52 -0
- package/src/reducer.ts +92 -10
- package/src/scheduler-qstash.ts +49 -20
- package/src/scheduler-task.ts +34 -2
- package/src/scheduler-vercel.ts +32 -84
- package/src/server-fetch.ts +139 -35
- package/src/server.ts +957 -237
- package/src/session-clear.ts +23 -0
- package/src/session-socket.ts +154 -49
- package/src/sse.ts +71 -4
- package/src/store-memory.ts +220 -78
- package/src/store-polling.ts +18 -4
- package/src/store-postgres.ts +461 -114
- package/src/store-redis-core.ts +1260 -434
- package/src/store-redis-http.ts +18 -2
- package/src/store-redis-notify.ts +260 -85
- package/src/store-redis.ts +19 -9
- package/src/store-sqlite.ts +259 -45
- package/src/store.ts +46 -1
- package/src/stream-buffer.ts +43 -0
- package/src/telemetry.ts +13 -1
- package/src/wire.ts +241 -13
- package/dist/actor-ohPC-81x.d.ts.map +0 -1
- package/dist/actor-shared-B5tJfzt-.js.map +0 -1
- package/dist/ai-CrEf6p_W.d.ts.map +0 -1
- package/dist/ai-DgOBltJ_.js.map +0 -1
- package/dist/client-BYzHjkwU.js.map +0 -1
- package/dist/client-CzyacQpJ.d.ts.map +0 -1
- package/dist/contract-CKRg_E4q.js.map +0 -1
- package/dist/errors-DCk6ch5n.js.map +0 -1
- package/dist/errors-DvhSXnxk.d.ts.map +0 -1
- package/dist/internal-Dq2qYxou.js.map +0 -1
- package/dist/react.d.ts.map +0 -1
- package/dist/reducer-DEMjEY_O.js +0 -29
- package/dist/reducer-DEMjEY_O.js.map +0 -1
- package/dist/reducer-otzHjuJj.d.ts.map +0 -1
- package/dist/scheduler-task-BpzhPnRS.js.map +0 -1
- package/dist/server-BD5ckxZb.js.map +0 -1
- package/dist/server-DbD7IJVK.d.ts.map +0 -1
- package/dist/store-DyZM6fS5.d.ts.map +0 -1
- package/dist/store-polling-CmxUbV93.js.map +0 -1
- package/dist/store-redis-notify-BVLUfI3j.js +0 -1583
- package/dist/store-redis-notify-BVLUfI3j.js.map +0 -1
- package/dist/telemetry-B5jzpy6y.d.ts.map +0 -1
- package/dist/validate-XKT4FSNn.js +0 -30
- package/dist/validate-XKT4FSNn.js.map +0 -1
- package/dist/wire-BO5wWCb1.js.map +0 -1
- package/examples/playground/app/agent/[agentId]/compaction/route.ts +0 -14
- package/examples/playground/app/agent/compaction-settings.test.ts +0 -144
- package/examples/playground/app/agent/compaction-settings.ts +0 -49
- package/examples/playground/app/agent/compaction-timeline.test.ts +0 -377
- 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-
|
|
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":";;
|
|
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"}
|
package/dist/store-redis-http.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { r as NOTIFY_TIMINGS } from "./internal-
|
|
3
|
-
import { t as defaultSleep } from "./store-polling-
|
|
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-
|
|
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.
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
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
|
-
|
|
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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"}
|