experimental-a2 0.16.1 → 0.18.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 +373 -0
- package/README.md +1 -1
- package/dist/{actor-ohPC-81x.d.ts → actor-Bjce9_5U.d.ts} +21 -11
- package/dist/actor-Bjce9_5U.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-BpOGm-zB.js} +621 -216
- package/dist/ai-BpOGm-zB.js.map +1 -0
- package/dist/{ai-CrEf6p_W.d.ts → ai-DrUaMyRt.d.ts} +114 -100
- package/dist/ai-DrUaMyRt.d.ts.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-fx.browser.d.ts +1 -0
- package/dist/ai-fx.browser.js +6 -0
- package/dist/ai-fx.browser.js.map +1 -0
- package/dist/ai-fx.d.ts +10 -0
- package/dist/ai-fx.d.ts.map +1 -0
- package/dist/ai-fx.js +634 -0
- package/dist/ai-fx.js.map +1 -0
- package/dist/ai-harness-BuLh_EYi.js +273 -0
- package/dist/ai-harness-BuLh_EYi.js.map +1 -0
- package/dist/ai-harness-DvsPeJeB.d.ts +26 -0
- package/dist/ai-harness-DvsPeJeB.d.ts.map +1 -0
- package/dist/ai-pi.browser.d.ts +1 -0
- package/dist/ai-pi.browser.js +6 -0
- package/dist/ai-pi.browser.js.map +1 -0
- package/dist/ai-pi.d.ts +17 -0
- package/dist/ai-pi.d.ts.map +1 -0
- package/dist/ai-pi.js +904 -0
- package/dist/ai-pi.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 +109 -43
- package/dist/ai-server.d.ts.map +1 -1
- package/dist/ai-server.js +526 -283
- package/dist/ai-server.js.map +1 -1
- package/dist/ai.d.ts +2 -2
- package/dist/ai.js +2 -2
- package/dist/bounded-cache-CPHWF-8h.js +41 -0
- package/dist/bounded-cache-CPHWF-8h.js.map +1 -0
- package/dist/{client-BYzHjkwU.js → client-BReKqwZC.js} +447 -80
- package/dist/client-BReKqwZC.js.map +1 -0
- package/dist/{client-CzyacQpJ.d.ts → client-h_DKFAjj.d.ts} +14 -13
- package/dist/client-h_DKFAjj.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-Bx-c0pDP.d.ts} +7 -3
- package/dist/initial-snapshot-Bx-c0pDP.d.ts.map +1 -0
- package/dist/{internal-Dq2qYxou.js → internal-B2T3U4Rq.js} +29 -2
- package/dist/internal-B2T3U4Rq.js.map +1 -0
- package/dist/otel.d.ts +1 -1
- package/dist/react-BBXgHWFc.d.ts +86 -0
- package/dist/react-BBXgHWFc.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-CqA59Q6U.d.ts} +46 -30
- package/dist/reducer-CqA59Q6U.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-BD5ckxZb.js → server-Dw_k_7kO.js} +904 -219
- package/dist/server-Dw_k_7kO.js.map +1 -0
- package/dist/{server-DbD7IJVK.d.ts → server-En7cbi3v.d.ts} +116 -26
- package/dist/server-En7cbi3v.d.ts.map +1 -0
- package/dist/server.d.ts +4 -4
- package/dist/server.js +2 -2
- package/dist/session-clear-Bo-XTFEy.d.ts +12 -0
- package/dist/session-clear-Bo-XTFEy.d.ts.map +1 -0
- package/dist/{errors-DCk6ch5n.js → session-clear-DGI4jTQ5.js} +17 -2
- package/dist/session-clear-DGI4jTQ5.js.map +1 -0
- package/dist/{store-DyZM6fS5.d.ts → store-Djdv0bnZ.d.ts} +73 -3
- package/dist/store-Djdv0bnZ.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 +205 -66
- package/dist/store-memory.js.map +1 -1
- package/dist/{store-polling-CmxUbV93.js → store-polling-B5FcwJfo.js} +21 -4
- package/dist/store-polling-B5FcwJfo.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 +450 -108
- package/dist/store-postgres.js.map +1 -1
- package/dist/{store-presence-polling-C7-XZyW9.js → store-presence-polling-BAwS8RWn.js} +3 -3
- package/dist/{store-presence-polling-C7-XZyW9.js.map → store-presence-polling-BAwS8RWn.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-BRITti_o.js +2493 -0
- package/dist/store-redis-notify-BRITti_o.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 +197 -33
- 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-Cyfqo6Qt.d.ts} +2 -2
- package/dist/telemetry-Cyfqo6Qt.d.ts.map +1 -0
- package/dist/{wire-BO5wWCb1.js → wire-rSUglYo8.js} +132 -10
- package/dist/wire-rSUglYo8.js.map +1 -0
- package/docs/01-quickstart.mdx +50 -84
- package/docs/{guides/07-examples.mdx → 02-examples.mdx} +10 -20
- package/docs/actors/01-introduction.mdx +48 -82
- package/docs/actors/02-concurrency.mdx +43 -70
- package/docs/actors/03-timers.mdx +28 -42
- package/docs/actors/04-routes.mdx +110 -137
- package/docs/actors/meta.ts +1 -1
- package/docs/agents/01-introduction.mdx +257 -0
- package/docs/agents/02-tools.mdx +247 -0
- package/docs/agents/03-conversations.mdx +538 -0
- package/docs/agents/04-harnesses.mdx +136 -0
- package/docs/agents/meta.ts +1 -0
- package/docs/client/01-react.mdx +294 -0
- package/docs/client/02-transports.mdx +86 -0
- package/docs/client/03-presence.mdx +224 -0
- package/docs/client/meta.ts +1 -0
- package/docs/contract/01-events.mdx +57 -0
- package/docs/contract/02-state.mdx +142 -0
- package/docs/contract/03-sessions.mdx +175 -0
- package/docs/index.mdx +112 -156
- package/docs/reference/01-core.mdx +139 -0
- package/docs/reference/02-server.mdx +855 -0
- package/docs/reference/03-client.mdx +301 -0
- package/docs/reference/04-ai.mdx +722 -0
- package/docs/reference/05-adapters.mdx +351 -0
- package/docs/reference/06-errors.mdx +138 -0
- package/docs/reference/meta.ts +1 -1
- package/docs/server/01-handlers.mdx +200 -0
- package/docs/server/02-timers.mdx +130 -0
- package/docs/server/03-cancellation.mdx +141 -0
- package/docs/server/04-routes.mdx +243 -0
- package/docs/server/05-production.mdx +322 -0
- package/docs/server/06-guarantees.mdx +123 -0
- package/examples/playground/DEPLOY.md +53 -7
- package/examples/playground/app/a0/[appId]/loading.tsx +16 -0
- package/examples/playground/app/a0/[appId]/page.tsx +53 -0
- package/examples/playground/app/a0/a0.css +818 -0
- package/examples/playground/app/a0/app-list.tsx +218 -0
- package/examples/playground/app/a0/app-view.tsx +63 -0
- package/examples/playground/app/a0/apps/[appId]/preview/route.ts +27 -0
- package/examples/playground/app/a0/apps/[appId]/route.ts +28 -0
- package/examples/playground/app/a0/apps.test.ts +128 -0
- package/examples/playground/app/a0/apps.ts +102 -0
- package/examples/playground/app/a0/chat.tsx +410 -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/fx/events/route.ts +9 -0
- package/examples/playground/app/a0/fx/scheduler/route.ts +7 -0
- package/examples/playground/app/a0/fx/short/events/route.ts +11 -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 +73 -0
- package/examples/playground/app/a0/model.ts +95 -0
- package/examples/playground/app/a0/no-photon.cjs +4 -0
- package/examples/playground/app/a0/no-quickjs.cjs +3 -0
- package/examples/playground/app/a0/page.tsx +14 -0
- package/examples/playground/app/a0/pi/events/route.ts +9 -0
- package/examples/playground/app/a0/pi/scheduler/route.ts +7 -0
- package/examples/playground/app/a0/pi/short/events/route.ts +11 -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 +75 -0
- package/examples/playground/app/a0/sandbox-session.ts +152 -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 +213 -0
- package/examples/playground/app/a0/session.ts +59 -0
- package/examples/playground/app/a0/shell.tsx +158 -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 +101 -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 +19 -15
- package/examples/playground/app/agent/session.ts +5 -6
- package/examples/playground/app/agent/telemetry.test.ts +189 -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 +12 -12
- 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 +112 -0
- package/examples/playground/app/chat/server.ts +113 -117
- 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/next.config.ts +17 -5
- package/examples/playground/package.json +13 -2
- package/examples/playground/scripts/a0-template.ts +77 -0
- package/examples/playground/vercel.json +36 -0
- package/package.json +29 -3
- 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 +2517 -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-fx.browser.ts +5 -0
- package/src/ai-fx.ts +833 -0
- package/src/ai-harness.ts +518 -0
- package/src/ai-message-base.ts +209 -0
- package/src/ai-message-projection.ts +262 -64
- package/src/ai-model-metadata.ts +11 -6
- package/src/ai-pi.browser.ts +5 -0
- package/src/ai-pi.ts +1263 -0
- 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-sdk-step.ts +2 -2
- package/src/ai-server.browser.ts +2 -2
- package/src/ai-server.ts +23 -1965
- 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 +52 -0
- package/src/libfx.d.ts +34 -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 +93 -11
- 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 +1133 -255
- 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 +252 -75
- package/src/store-polling.ts +18 -4
- package/src/store-postgres.ts +541 -114
- package/src/store-redis-core.ts +1450 -493
- package/src/store-redis-http.ts +18 -2
- package/src/store-redis-notify.ts +262 -85
- package/src/store-redis.ts +19 -9
- package/src/store-sqlite.ts +319 -45
- package/src/store.ts +79 -1
- package/src/stream-buffer.ts +43 -0
- package/src/telemetry.ts +14 -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/docs/concepts/01-contracts.mdx +0 -142
- package/docs/concepts/02-handlers.mdx +0 -284
- package/docs/concepts/03-durability.mdx +0 -314
- package/docs/concepts/04-state.mdx +0 -209
- package/docs/guides/01-timers.mdx +0 -185
- package/docs/guides/02-cancellation.mdx +0 -161
- package/docs/guides/03-react.mdx +0 -384
- package/docs/guides/04-local-first.mdx +0 -88
- package/docs/guides/05-production.mdx +0 -521
- package/docs/guides/06-ai-agents.mdx +0 -1318
- package/docs/guides/08-application-data.mdx +0 -113
- package/docs/guides/09-presence.mdx +0 -266
- package/docs/guides/10-transports.mdx +0 -221
- package/docs/reference/01-api.mdx +0 -1896
- package/docs/reference/02-errors.mdx +0 -149
- 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
- /package/docs/{concepts → contract}/meta.ts +0 -0
- /package/docs/{guides → server}/meta.ts +0 -0
package/src/store-memory.ts
CHANGED
|
@@ -7,9 +7,10 @@
|
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
9
|
import type { PresencePatch } from './contract.ts'
|
|
10
|
-
import { A2Error } from './errors.ts'
|
|
10
|
+
import { A2Error, supersededAttemptError } from './errors.ts'
|
|
11
11
|
import { idempotentReplay } from './idempotent-replay.ts'
|
|
12
12
|
import { nullProtoRecord } from './internal.ts'
|
|
13
|
+
import { CLEAR_EVENT_TYPE, sessionClearedError } from './session-clear.ts'
|
|
13
14
|
import {
|
|
14
15
|
RANDOM_IDS,
|
|
15
16
|
SYSTEM_CLOCK,
|
|
@@ -68,6 +69,20 @@ type SessionDispatch = {
|
|
|
68
69
|
lanes: Map<string, LaneQueue>
|
|
69
70
|
}
|
|
70
71
|
|
|
72
|
+
/** Contiguous rows; `reclaim` removes from the front below `clearIndex`. */
|
|
73
|
+
type SessionLog = {
|
|
74
|
+
rows: Row[]
|
|
75
|
+
/** Index of the event before `rows[0]`. */
|
|
76
|
+
offset: number
|
|
77
|
+
/** Latest clear marker, or 0. */
|
|
78
|
+
clearIndex: number
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const RECLAIM_BATCH = 256
|
|
82
|
+
|
|
83
|
+
const headOf = (log: SessionLog | undefined): number =>
|
|
84
|
+
log ? log.offset + log.rows.length : 0
|
|
85
|
+
|
|
71
86
|
const toStored = (row: Row): StoredEvent => ({
|
|
72
87
|
id: row.id,
|
|
73
88
|
type: row.type,
|
|
@@ -111,7 +126,7 @@ export function memory(options: MemoryStoreOptions = {}): A2Store {
|
|
|
111
126
|
const clock = options.clock ?? SYSTEM_CLOCK
|
|
112
127
|
const generateId = options.ids ?? RANDOM_IDS
|
|
113
128
|
|
|
114
|
-
const sessions = new Map<string,
|
|
129
|
+
const sessions = new Map<string, SessionLog>()
|
|
115
130
|
const dispatch = new Map<string, SessionDispatch>()
|
|
116
131
|
/** Global unique index on event ids, like the SQL schema's. */
|
|
117
132
|
const byEventId = new Map<string, Row>()
|
|
@@ -145,13 +160,31 @@ export function memory(options: MemoryStoreOptions = {}): A2Store {
|
|
|
145
160
|
}
|
|
146
161
|
}
|
|
147
162
|
|
|
148
|
-
const
|
|
149
|
-
let
|
|
150
|
-
if (!
|
|
151
|
-
|
|
152
|
-
sessions.set(sessionId,
|
|
163
|
+
const logOf = (sessionId: string): SessionLog => {
|
|
164
|
+
let log = sessions.get(sessionId)
|
|
165
|
+
if (!log) {
|
|
166
|
+
log = { rows: [], offset: 0, clearIndex: 0 }
|
|
167
|
+
sessions.set(sessionId, log)
|
|
153
168
|
}
|
|
154
|
-
return
|
|
169
|
+
return log
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
const rowAt = (sessionId: string, index: number): Row | undefined => {
|
|
173
|
+
const log = sessions.get(sessionId)
|
|
174
|
+
return log && index > log.offset
|
|
175
|
+
? log.rows[index - log.offset - 1]
|
|
176
|
+
: undefined
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
const clearIndexOf = (sessionId: string): number =>
|
|
180
|
+
sessions.get(sessionId)?.clearIndex ?? 0
|
|
181
|
+
|
|
182
|
+
/** The row owning `id`, ignoring this session's rows below its clear. */
|
|
183
|
+
const ownerOf = (sessionId: string, id: string): Row | undefined => {
|
|
184
|
+
const row = byEventId.get(id)
|
|
185
|
+
return row?.sessionId === sessionId && row.index < clearIndexOf(sessionId)
|
|
186
|
+
? undefined
|
|
187
|
+
: row
|
|
155
188
|
}
|
|
156
189
|
|
|
157
190
|
const checkpointKey = (
|
|
@@ -217,7 +250,7 @@ export function memory(options: MemoryStoreOptions = {}): A2Store {
|
|
|
217
250
|
}
|
|
218
251
|
|
|
219
252
|
const find = (sessionId: string, index: number): Row => {
|
|
220
|
-
const row =
|
|
253
|
+
const row = rowAt(sessionId, index)
|
|
221
254
|
if (!row) {
|
|
222
255
|
throw new TypeError(
|
|
223
256
|
`no event at index ${index} in session '${sessionId}'`,
|
|
@@ -226,13 +259,29 @@ export function memory(options: MemoryStoreOptions = {}): A2Store {
|
|
|
226
259
|
return row
|
|
227
260
|
}
|
|
228
261
|
|
|
262
|
+
const hasPending = (sessionId: string): boolean => {
|
|
263
|
+
const log = sessions.get(sessionId)
|
|
264
|
+
return (log?.rows ?? []).some(
|
|
265
|
+
(row) => row.index > log!.clearIndex && row.processedAt === null,
|
|
266
|
+
)
|
|
267
|
+
}
|
|
268
|
+
|
|
229
269
|
const appendResult = (sessionId: string, events: StoredEvent[]) => ({
|
|
230
270
|
events,
|
|
231
|
-
hasPending: (
|
|
232
|
-
(row) => row.processedAt === null,
|
|
233
|
-
),
|
|
271
|
+
hasPending: hasPending(sessionId),
|
|
234
272
|
})
|
|
235
273
|
|
|
274
|
+
/** The attempt-currency fence shared by handler appends and clears. */
|
|
275
|
+
const assertCurrentCause = (sessionId: string, cause: EventCause): void => {
|
|
276
|
+
if (cause.index <= clearIndexOf(sessionId)) {
|
|
277
|
+
throw supersededAttemptError(sessionId, cause)
|
|
278
|
+
}
|
|
279
|
+
const parent = find(sessionId, cause.index)
|
|
280
|
+
if (parent.attemptCount !== cause.attempt || parent.failedAt !== null) {
|
|
281
|
+
throw supersededAttemptError(sessionId, cause)
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
|
|
236
285
|
const claim = (
|
|
237
286
|
row: Row,
|
|
238
287
|
holder: string,
|
|
@@ -263,7 +312,7 @@ export function memory(options: MemoryStoreOptions = {}): A2Store {
|
|
|
263
312
|
)
|
|
264
313
|
}
|
|
265
314
|
const existing = supplied
|
|
266
|
-
.map((candidate) =>
|
|
315
|
+
.map((candidate) => ownerOf(sessionId, candidate.id))
|
|
267
316
|
.filter((row): row is Row => row !== undefined)
|
|
268
317
|
const foreign = existing.find((row) => row.sessionId !== sessionId)
|
|
269
318
|
if (foreign) {
|
|
@@ -281,8 +330,8 @@ export function memory(options: MemoryStoreOptions = {}): A2Store {
|
|
|
281
330
|
now: Date,
|
|
282
331
|
cause?: EventCause,
|
|
283
332
|
): Row[] => {
|
|
284
|
-
const
|
|
285
|
-
const base =
|
|
333
|
+
const log = logOf(sessionId)
|
|
334
|
+
const base = headOf(log)
|
|
286
335
|
const inserted = events.map((candidate, offset): Row => ({
|
|
287
336
|
id: candidate.id ?? generateId(),
|
|
288
337
|
type: candidate.type,
|
|
@@ -311,7 +360,7 @@ export function memory(options: MemoryStoreOptions = {}): A2Store {
|
|
|
311
360
|
failedAt: null,
|
|
312
361
|
}))
|
|
313
362
|
for (const row of inserted) {
|
|
314
|
-
if (
|
|
363
|
+
if (ownerOf(sessionId, row.id)) {
|
|
315
364
|
throw new A2Error(
|
|
316
365
|
'PARTIAL_DUPLICATE_BATCH',
|
|
317
366
|
`event id '${row.id}' already exists`,
|
|
@@ -319,13 +368,31 @@ export function memory(options: MemoryStoreOptions = {}): A2Store {
|
|
|
319
368
|
}
|
|
320
369
|
}
|
|
321
370
|
for (const row of inserted) {
|
|
322
|
-
rows.push(row)
|
|
371
|
+
log.rows.push(row)
|
|
323
372
|
byEventId.set(row.id, row)
|
|
324
373
|
enqueue(sessionId, row)
|
|
325
374
|
}
|
|
326
375
|
return inserted
|
|
327
376
|
}
|
|
328
377
|
|
|
378
|
+
/** Every cached fold of this session below its clear, head or historical. */
|
|
379
|
+
const dropCheckpointsBelow = (
|
|
380
|
+
sessionId: string,
|
|
381
|
+
clearIndex: number,
|
|
382
|
+
): void => {
|
|
383
|
+
const prefix = `${sessionId}\u0000`
|
|
384
|
+
for (const [key, snapshot] of snapshots) {
|
|
385
|
+
if (!key.startsWith(prefix) || snapshot.index >= clearIndex) continue
|
|
386
|
+
snapshots.delete(key)
|
|
387
|
+
snapshotPinCounts.delete(`${key}\u0000${snapshot.index}`)
|
|
388
|
+
}
|
|
389
|
+
for (const [key, snapshot] of historicalSnapshots) {
|
|
390
|
+
if (!key.startsWith(prefix) || snapshot.index >= clearIndex) continue
|
|
391
|
+
historicalSnapshots.delete(key)
|
|
392
|
+
snapshotPinCounts.delete(key)
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
|
|
329
396
|
const readStateOf = (
|
|
330
397
|
sessionId: string,
|
|
331
398
|
reducerName: string,
|
|
@@ -333,8 +400,10 @@ export function memory(options: MemoryStoreOptions = {}): A2Store {
|
|
|
333
400
|
snapshotThroughIndex?: number,
|
|
334
401
|
): StoreStateRead => {
|
|
335
402
|
const key = snapshotKey(sessionId, reducerName)
|
|
403
|
+
const clearIndex = clearIndexOf(sessionId)
|
|
336
404
|
const snapshotFrontier = snapshotThroughIndex ?? throughIndex
|
|
337
|
-
const
|
|
405
|
+
const stored = snapshots.get(key)
|
|
406
|
+
const head = stored && stored.index >= clearIndex ? stored : undefined
|
|
338
407
|
let candidate =
|
|
339
408
|
head && (snapshotFrontier === undefined || head.index <= snapshotFrontier)
|
|
340
409
|
? head
|
|
@@ -344,6 +413,7 @@ export function memory(options: MemoryStoreOptions = {}): A2Store {
|
|
|
344
413
|
for (const [historicalKey, snapshot] of historicalSnapshots) {
|
|
345
414
|
if (
|
|
346
415
|
historicalKey.startsWith(prefix) &&
|
|
416
|
+
snapshot.index >= clearIndex &&
|
|
347
417
|
snapshot.index <= snapshotFrontier &&
|
|
348
418
|
(!candidate || snapshot.index > candidate.index)
|
|
349
419
|
) {
|
|
@@ -354,22 +424,23 @@ export function memory(options: MemoryStoreOptions = {}): A2Store {
|
|
|
354
424
|
const snapshot = candidate
|
|
355
425
|
? { index: candidate.index, state: structuredClone(candidate.state) }
|
|
356
426
|
: null
|
|
357
|
-
const afterIndex = snapshot?.index ?? 0
|
|
427
|
+
const afterIndex = Math.max(snapshot?.index ?? 0, clearIndex - 1)
|
|
358
428
|
return {
|
|
359
429
|
headIndex: head?.index ?? null,
|
|
360
430
|
snapshot,
|
|
361
|
-
events: (sessions.get(sessionId) ?? [])
|
|
431
|
+
events: (sessions.get(sessionId)?.rows ?? [])
|
|
362
432
|
.filter(
|
|
363
433
|
(row) =>
|
|
364
434
|
row.index > afterIndex &&
|
|
365
435
|
(throughIndex === undefined || row.index <= throughIndex),
|
|
366
436
|
)
|
|
367
437
|
.map(toEvent),
|
|
438
|
+
...(clearIndex > 0 ? { clearIndex } : {}),
|
|
368
439
|
}
|
|
369
440
|
}
|
|
370
441
|
|
|
371
442
|
return {
|
|
372
|
-
async append(sessionId, events) {
|
|
443
|
+
async append(sessionId, events, appendOptions) {
|
|
373
444
|
if (events.length === 0) return appendResult(sessionId, [])
|
|
374
445
|
|
|
375
446
|
// Batch-scoped idempotency (a2-implementation.md §3): all ids
|
|
@@ -389,26 +460,16 @@ export function memory(options: MemoryStoreOptions = {}): A2Store {
|
|
|
389
460
|
)
|
|
390
461
|
}
|
|
391
462
|
|
|
463
|
+
const originIndex = appendOptions?.originIndex
|
|
464
|
+
if (originIndex !== undefined && originIndex <= clearIndexOf(sessionId)) {
|
|
465
|
+
throw sessionClearedError(
|
|
466
|
+
`scheduled work from event ${originIndex} was cleared in session '${sessionId}'`,
|
|
467
|
+
)
|
|
468
|
+
}
|
|
392
469
|
// Attempt-currency fence: a fresh handler append commits only while
|
|
393
470
|
// its causal attempt is still the parent's latest (store.ts `append`).
|
|
394
|
-
const rows = sessions.get(sessionId) ?? []
|
|
395
471
|
for (const event of events) {
|
|
396
|
-
if (
|
|
397
|
-
const parent = rows[event.cause.index - 1]
|
|
398
|
-
if (!parent) {
|
|
399
|
-
throw new TypeError(
|
|
400
|
-
`no event at index ${event.cause.index} in session '${sessionId}'`,
|
|
401
|
-
)
|
|
402
|
-
}
|
|
403
|
-
if (
|
|
404
|
-
parent.attemptCount !== event.cause.attempt ||
|
|
405
|
-
parent.failedAt !== null
|
|
406
|
-
) {
|
|
407
|
-
throw new A2Error(
|
|
408
|
-
'SUPERSEDED_ATTEMPT',
|
|
409
|
-
`attempt ${event.cause.attempt} no longer owns event ${event.cause.index} in session '${sessionId}'`,
|
|
410
|
-
)
|
|
411
|
-
}
|
|
472
|
+
if (event.cause) assertCurrentCause(sessionId, event.cause)
|
|
412
473
|
}
|
|
413
474
|
|
|
414
475
|
const now = clock.now()
|
|
@@ -417,11 +478,67 @@ export function memory(options: MemoryStoreOptions = {}): A2Store {
|
|
|
417
478
|
return appendResult(sessionId, inserted.map(toStored))
|
|
418
479
|
},
|
|
419
480
|
|
|
481
|
+
async clear({ sessionId, ifLastIndex, cause }) {
|
|
482
|
+
if (cause) assertCurrentCause(sessionId, cause)
|
|
483
|
+
const log = sessions.get(sessionId)
|
|
484
|
+
const head = headOf(log)
|
|
485
|
+
const clearIndex = log?.clearIndex ?? 0
|
|
486
|
+
if (ifLastIndex !== undefined && clearIndex === ifLastIndex + 1) {
|
|
487
|
+
return { cleared: true, index: head, clearIndex }
|
|
488
|
+
}
|
|
489
|
+
if (ifLastIndex !== undefined && ifLastIndex !== head) {
|
|
490
|
+
return { cleared: false, index: head, clearIndex }
|
|
491
|
+
}
|
|
492
|
+
if (head === 0 || clearIndex === head) {
|
|
493
|
+
return { cleared: true, index: head, clearIndex }
|
|
494
|
+
}
|
|
495
|
+
const [marker] = insert(
|
|
496
|
+
sessionId,
|
|
497
|
+
[{ type: CLEAR_EVENT_TYPE, payload: null, settled: true }],
|
|
498
|
+
clock.now(),
|
|
499
|
+
)
|
|
500
|
+
logOf(sessionId).clearIndex = marker!.index
|
|
501
|
+
// Every row is at or below the marker, so no dispatch state survives.
|
|
502
|
+
dispatch.delete(sessionId)
|
|
503
|
+
notifyStreams(sessionId, [marker!])
|
|
504
|
+
return { cleared: true, index: marker!.index, clearIndex: marker!.index }
|
|
505
|
+
},
|
|
506
|
+
|
|
507
|
+
async reclaim(sessionId) {
|
|
508
|
+
const log = sessions.get(sessionId)
|
|
509
|
+
if (!log || log.offset >= log.clearIndex - 1) {
|
|
510
|
+
return { deleted: 0, done: true }
|
|
511
|
+
}
|
|
512
|
+
const removed = log.rows.splice(
|
|
513
|
+
0,
|
|
514
|
+
Math.min(RECLAIM_BATCH, log.clearIndex - 1 - log.offset),
|
|
515
|
+
)
|
|
516
|
+
log.offset += removed.length
|
|
517
|
+
for (const row of removed) {
|
|
518
|
+
// A reused id belongs to its new row now.
|
|
519
|
+
if (byEventId.get(row.id) === row) byEventId.delete(row.id)
|
|
520
|
+
releaseSnapshotPins(sessionId, row.index)
|
|
521
|
+
}
|
|
522
|
+
const done = log.offset === log.clearIndex - 1
|
|
523
|
+
// Writes below the marker are dropped, so one sweep at the end suffices.
|
|
524
|
+
if (done) dropCheckpointsBelow(sessionId, log.clearIndex)
|
|
525
|
+
return { deleted: removed.length, done }
|
|
526
|
+
},
|
|
527
|
+
|
|
420
528
|
async read(sessionId, opts) {
|
|
421
|
-
const
|
|
422
|
-
|
|
423
|
-
const
|
|
424
|
-
|
|
529
|
+
const log = sessions.get(sessionId)
|
|
530
|
+
if (!log) return []
|
|
531
|
+
const head = headOf(log)
|
|
532
|
+
const after = Math.max(
|
|
533
|
+
opts?.afterIndex ?? 0,
|
|
534
|
+
log.clearIndex - 1,
|
|
535
|
+
log.offset,
|
|
536
|
+
)
|
|
537
|
+
const through = Math.min(opts?.throughIndex ?? head, head)
|
|
538
|
+
if (through <= after) return []
|
|
539
|
+
return log.rows
|
|
540
|
+
.slice(after - log.offset, through - log.offset)
|
|
541
|
+
.map(toStored)
|
|
425
542
|
},
|
|
426
543
|
|
|
427
544
|
async claimAvailable({
|
|
@@ -453,21 +570,32 @@ export function memory(options: MemoryStoreOptions = {}): A2Store {
|
|
|
453
570
|
return retryAt ? { outcome: 'busy', retryAt } : { outcome: 'settled' }
|
|
454
571
|
}
|
|
455
572
|
eligible.sort((a, b) => a.index - b.index)
|
|
573
|
+
const clearIndex = clearIndexOf(sessionId)
|
|
456
574
|
return {
|
|
457
575
|
outcome: 'claimed',
|
|
458
|
-
events: eligible.map((row) =>
|
|
576
|
+
events: eligible.map((row) => {
|
|
577
|
+
const claimed = claim(row, holder, claimedAt, expiresAt)
|
|
578
|
+
if (clearIndex > 0) claimed.clearIndex = clearIndex
|
|
579
|
+
return claimed
|
|
580
|
+
}),
|
|
459
581
|
}
|
|
460
582
|
},
|
|
461
583
|
|
|
462
584
|
async renewClaims({ sessionId, holder, claims, ttlMs, expiresAtMs }) {
|
|
463
585
|
const now = clock.now()
|
|
464
586
|
const expiresAt = new Date(expiresAtMs ?? now.getTime() + ttlMs)
|
|
465
|
-
const
|
|
587
|
+
const clearIndex = clearIndexOf(sessionId)
|
|
466
588
|
const renewed: number[] = []
|
|
467
589
|
const superseded: number[] = []
|
|
468
|
-
for (const
|
|
469
|
-
|
|
470
|
-
|
|
590
|
+
for (const { index, attempt } of claims.toSorted(
|
|
591
|
+
(a, b) => a.index - b.index,
|
|
592
|
+
)) {
|
|
593
|
+
if (index <= clearIndex) {
|
|
594
|
+
superseded.push(index)
|
|
595
|
+
continue
|
|
596
|
+
}
|
|
597
|
+
const row = rowAt(sessionId, index)
|
|
598
|
+
if (!row) continue
|
|
471
599
|
if (row.attemptCount > attempt) {
|
|
472
600
|
superseded.push(row.index)
|
|
473
601
|
continue
|
|
@@ -487,6 +615,7 @@ export function memory(options: MemoryStoreOptions = {}): A2Store {
|
|
|
487
615
|
},
|
|
488
616
|
|
|
489
617
|
async completeAttempt({ sessionId, index, attempt, events }) {
|
|
618
|
+
if (index <= clearIndexOf(sessionId)) return { outcome: 'superseded' }
|
|
490
619
|
const parent = find(sessionId, index)
|
|
491
620
|
const cause = { index, attempt }
|
|
492
621
|
if (parent.processedAt !== null) {
|
|
@@ -553,6 +682,9 @@ export function memory(options: MemoryStoreOptions = {}): A2Store {
|
|
|
553
682
|
},
|
|
554
683
|
|
|
555
684
|
async failAttempt({ sessionId, index, attempt, error, maxFailures }) {
|
|
685
|
+
if (index <= clearIndexOf(sessionId)) {
|
|
686
|
+
return { outcome: 'superseded', failureCount: 0 }
|
|
687
|
+
}
|
|
556
688
|
const row = find(sessionId, index)
|
|
557
689
|
if (
|
|
558
690
|
row.processedAt ||
|
|
@@ -589,6 +721,39 @@ export function memory(options: MemoryStoreOptions = {}): A2Store {
|
|
|
589
721
|
return { outcome: 'failed', failureCount: row.failureCount }
|
|
590
722
|
},
|
|
591
723
|
|
|
724
|
+
async deadLetters(sessionId) {
|
|
725
|
+
const log = sessions.get(sessionId)
|
|
726
|
+
return (log?.rows ?? [])
|
|
727
|
+
.filter(
|
|
728
|
+
(row) =>
|
|
729
|
+
row.index > log!.clearIndex &&
|
|
730
|
+
row.processedAt === null &&
|
|
731
|
+
row.failedAt !== null,
|
|
732
|
+
)
|
|
733
|
+
.map(toStored)
|
|
734
|
+
},
|
|
735
|
+
|
|
736
|
+
async redrive({ sessionId, index, action }) {
|
|
737
|
+
const row =
|
|
738
|
+
index > clearIndexOf(sessionId) ? rowAt(sessionId, index) : undefined
|
|
739
|
+
if (!row || row.processedAt !== null || row.failedAt === null) {
|
|
740
|
+
return { redriven: false, hasPending: hasPending(sessionId) }
|
|
741
|
+
}
|
|
742
|
+
if (action === 'skip') {
|
|
743
|
+
row.processedAt = clock.now()
|
|
744
|
+
settle(sessionId, row)
|
|
745
|
+
} else {
|
|
746
|
+
row.failedAt = null
|
|
747
|
+
row.failureCount = 0
|
|
748
|
+
const state = dispatchOf(sessionId)
|
|
749
|
+
const queue = row.lane === null ? undefined : state.lanes.get(row.lane)
|
|
750
|
+
if (row.lane === null || queue?.rows[queue.head] === row) {
|
|
751
|
+
state.ready.set(row.index, row)
|
|
752
|
+
}
|
|
753
|
+
}
|
|
754
|
+
return { redriven: true, hasPending: hasPending(sessionId) }
|
|
755
|
+
},
|
|
756
|
+
|
|
592
757
|
async readState(sessionId, reducerName, stateOptions) {
|
|
593
758
|
return readStateOf(
|
|
594
759
|
sessionId,
|
|
@@ -611,11 +776,18 @@ export function memory(options: MemoryStoreOptions = {}): A2Store {
|
|
|
611
776
|
|
|
612
777
|
async putSnapshots(sessionId, reducerName, writes) {
|
|
613
778
|
const key = snapshotKey(sessionId, reducerName)
|
|
779
|
+
const clearIndex = clearIndexOf(sessionId)
|
|
614
780
|
for (const write of writes.toSorted((a, b) => a.index - b.index)) {
|
|
781
|
+
if (write.index < clearIndex) continue
|
|
615
782
|
const checkpoint = checkpointKey(sessionId, reducerName, write.index)
|
|
616
783
|
for (const eventIndex of write.pinEventIndexes ?? []) {
|
|
617
|
-
const event =
|
|
618
|
-
if (
|
|
784
|
+
const event = rowAt(sessionId, eventIndex)
|
|
785
|
+
if (
|
|
786
|
+
!event ||
|
|
787
|
+
event.index <= clearIndex ||
|
|
788
|
+
event.processedAt !== null ||
|
|
789
|
+
event.failedAt !== null
|
|
790
|
+
) {
|
|
619
791
|
continue
|
|
620
792
|
}
|
|
621
793
|
const pinKey = eventPinKey(sessionId, eventIndex)
|
|
@@ -635,7 +807,11 @@ export function memory(options: MemoryStoreOptions = {}): A2Store {
|
|
|
635
807
|
|
|
636
808
|
const current = snapshots.get(key)
|
|
637
809
|
if (!current || write.index >= current.index) {
|
|
638
|
-
if (
|
|
810
|
+
if (
|
|
811
|
+
current &&
|
|
812
|
+
write.index > current.index &&
|
|
813
|
+
current.index >= clearIndex
|
|
814
|
+
) {
|
|
639
815
|
const currentCheckpoint = checkpointKey(
|
|
640
816
|
sessionId,
|
|
641
817
|
reducerName,
|
|
@@ -769,13 +945,11 @@ export function memory(options: MemoryStoreOptions = {}): A2Store {
|
|
|
769
945
|
const startAfter = opts?.startAfter ?? 0
|
|
770
946
|
return {
|
|
771
947
|
[Symbol.asyncIterator](): AsyncIterator<Event> {
|
|
772
|
-
let last = startAfter
|
|
773
|
-
const buffer: Row[] = []
|
|
948
|
+
let last = Math.max(startAfter, clearIndexOf(sessionId) - 1)
|
|
774
949
|
let wake: (() => void) | null = null
|
|
775
950
|
let closed = false
|
|
776
951
|
|
|
777
|
-
const onRow = (
|
|
778
|
-
buffer.push(row)
|
|
952
|
+
const onRow = (): void => {
|
|
779
953
|
wake?.()
|
|
780
954
|
}
|
|
781
955
|
let subs = streamSubscribers.get(sessionId)
|
|
@@ -784,37 +958,40 @@ export function memory(options: MemoryStoreOptions = {}): A2Store {
|
|
|
784
958
|
streamSubscribers.set(sessionId, subs)
|
|
785
959
|
}
|
|
786
960
|
subs.add(onRow)
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
buffer.unshift(...existing.filter((row) => row.index > startAfter))
|
|
791
|
-
|
|
792
|
-
const unsubscribe = (): void => {
|
|
961
|
+
const stop = (): void => {
|
|
962
|
+
if (closed) return
|
|
963
|
+
closed = true
|
|
793
964
|
subs.delete(onRow)
|
|
794
965
|
if (subs.size === 0) streamSubscribers.delete(sessionId)
|
|
966
|
+
wake?.()
|
|
795
967
|
}
|
|
796
968
|
|
|
797
969
|
return {
|
|
798
970
|
async next(): Promise<IteratorResult<Event>> {
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
971
|
+
try {
|
|
972
|
+
for (;;) {
|
|
973
|
+
if (closed) return { value: undefined, done: true }
|
|
974
|
+
const log = sessions.get(sessionId)
|
|
975
|
+
// Rows reclaimed behind a running stream are skipped.
|
|
976
|
+
const row = log?.rows[Math.max(0, last - log.offset)]
|
|
977
|
+
if (row) {
|
|
978
|
+
const value = toStored(row)
|
|
979
|
+
last = row.index
|
|
980
|
+
return { value, done: false }
|
|
981
|
+
}
|
|
982
|
+
// oxlint-disable-next-line no-await-in-loop -- wait-for-wake
|
|
983
|
+
await new Promise<void>((resolve) => {
|
|
984
|
+
wake = resolve
|
|
985
|
+
})
|
|
986
|
+
wake = null
|
|
805
987
|
}
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
wake = resolve
|
|
810
|
-
})
|
|
811
|
-
wake = null
|
|
988
|
+
} catch (error) {
|
|
989
|
+
stop()
|
|
990
|
+
throw error
|
|
812
991
|
}
|
|
813
992
|
},
|
|
814
993
|
async return(): Promise<IteratorResult<Event>> {
|
|
815
|
-
|
|
816
|
-
unsubscribe()
|
|
817
|
-
wake?.()
|
|
994
|
+
stop()
|
|
818
995
|
return { value: undefined, done: true }
|
|
819
996
|
},
|
|
820
997
|
}
|
package/src/store-polling.ts
CHANGED
|
@@ -66,7 +66,8 @@ export function pollingStream(
|
|
|
66
66
|
return {
|
|
67
67
|
[Symbol.asyncIterator](): AsyncIterator<Event> {
|
|
68
68
|
let last = startAfter
|
|
69
|
-
let buffer: Event
|
|
69
|
+
let buffer: Array<Event | undefined> = []
|
|
70
|
+
let position = 0
|
|
70
71
|
let closed = false
|
|
71
72
|
let pending: Sleeper | null = null
|
|
72
73
|
let interval = floor()
|
|
@@ -83,18 +84,29 @@ export function pollingStream(
|
|
|
83
84
|
for (;;) {
|
|
84
85
|
if (closed) return { value: undefined, done: true }
|
|
85
86
|
try {
|
|
86
|
-
|
|
87
|
-
|
|
87
|
+
if (position === buffer.length) {
|
|
88
|
+
// oxlint-disable-next-line no-await-in-loop -- poll loop
|
|
89
|
+
const rows = await readAfter(last)
|
|
90
|
+
if (closed) return { value: undefined, done: true }
|
|
91
|
+
buffer = rows
|
|
92
|
+
position = 0
|
|
93
|
+
}
|
|
88
94
|
} catch (error) {
|
|
89
95
|
if (closed) return { value: undefined, done: true }
|
|
90
96
|
throw error
|
|
91
97
|
}
|
|
92
98
|
if (closed) return { value: undefined, done: true }
|
|
93
|
-
const row = buffer
|
|
99
|
+
const row = buffer[position]
|
|
94
100
|
if (row) {
|
|
95
101
|
// Activity: tighten back to the floor.
|
|
96
102
|
activity.activate()
|
|
97
103
|
last = row.index
|
|
104
|
+
buffer[position] = undefined
|
|
105
|
+
position += 1
|
|
106
|
+
if (position === buffer.length) {
|
|
107
|
+
buffer = []
|
|
108
|
+
position = 0
|
|
109
|
+
}
|
|
98
110
|
return { value: row, done: false }
|
|
99
111
|
}
|
|
100
112
|
// Empty poll: wait, then back off toward the idle ceiling.
|
|
@@ -111,6 +123,8 @@ export function pollingStream(
|
|
|
111
123
|
},
|
|
112
124
|
async return(): Promise<IteratorResult<Event>> {
|
|
113
125
|
closed = true
|
|
126
|
+
buffer = []
|
|
127
|
+
position = 0
|
|
114
128
|
pending?.cancel()
|
|
115
129
|
return { value: undefined, done: true }
|
|
116
130
|
},
|