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-sqlite.ts
CHANGED
|
@@ -15,7 +15,8 @@
|
|
|
15
15
|
import { mkdirSync } from 'node:fs'
|
|
16
16
|
import { dirname, resolve } from 'node:path'
|
|
17
17
|
import { DatabaseSync } from 'node:sqlite'
|
|
18
|
-
import { A2Error } from './errors.ts'
|
|
18
|
+
import { A2Error, supersededAttemptError } from './errors.ts'
|
|
19
|
+
import { CLEAR_EVENT_TYPE, sessionClearedError } from './session-clear.ts'
|
|
19
20
|
import { idempotentReplay } from './idempotent-replay.ts'
|
|
20
21
|
import { decodeReturnedEventIds } from './store-codec.ts'
|
|
21
22
|
import { pollingStream } from './store-polling.ts'
|
|
@@ -112,6 +113,11 @@ create table if not exists a2_snapshot_pins (
|
|
|
112
113
|
create index if not exists a2_snapshot_pins_checkpoint
|
|
113
114
|
on a2_snapshot_pins (session_id, reducer_name, up_to_index);
|
|
114
115
|
|
|
116
|
+
create table if not exists a2_clears (
|
|
117
|
+
session_id text not null primary key,
|
|
118
|
+
clear_index integer not null
|
|
119
|
+
) strict;
|
|
120
|
+
|
|
115
121
|
create table if not exists a2_presence (
|
|
116
122
|
ns text not null,
|
|
117
123
|
participant text not null,
|
|
@@ -262,6 +268,7 @@ type PresenceDbRow = {
|
|
|
262
268
|
|
|
263
269
|
type StateReadRow = Partial<EventRow> & {
|
|
264
270
|
row_kind: 'snapshot' | 'event'
|
|
271
|
+
clear_index: number | bigint | null
|
|
265
272
|
head_index: number | bigint | null
|
|
266
273
|
snapshot_index: number | bigint | null
|
|
267
274
|
snapshot_state: string | null
|
|
@@ -282,6 +289,8 @@ type ExistingEventRow = EventRow & {
|
|
|
282
289
|
session_has_pending: number | bigint
|
|
283
290
|
}
|
|
284
291
|
|
|
292
|
+
const RECLAIM_BATCH = 256
|
|
293
|
+
|
|
285
294
|
export function sqlite(options: SqliteStoreOptions = {}): SqliteStore {
|
|
286
295
|
const path = options.path ?? '.a2/dev.db'
|
|
287
296
|
const clock = options.clock ?? SYSTEM_CLOCK
|
|
@@ -314,6 +323,7 @@ export function sqlite(options: SqliteStoreOptions = {}): SqliteStore {
|
|
|
314
323
|
when not exists (
|
|
315
324
|
select 1 from a2_events
|
|
316
325
|
where session_id = ? and lane = ? and processed_at is null
|
|
326
|
+
and idx > ?
|
|
317
327
|
) then 1
|
|
318
328
|
else 0
|
|
319
329
|
end,
|
|
@@ -325,6 +335,7 @@ export function sqlite(options: SqliteStoreOptions = {}): SqliteStore {
|
|
|
325
335
|
and idx = (
|
|
326
336
|
select idx from a2_events
|
|
327
337
|
where session_id = ? and lane = ? and processed_at is null
|
|
338
|
+
and idx > ?
|
|
328
339
|
order by idx limit 1
|
|
329
340
|
)`,
|
|
330
341
|
)
|
|
@@ -335,7 +346,7 @@ export function sqlite(options: SqliteStoreOptions = {}): SqliteStore {
|
|
|
335
346
|
) as max,
|
|
336
347
|
exists(
|
|
337
348
|
select 1 from a2_events
|
|
338
|
-
where session_id = ? and processed_at is null
|
|
349
|
+
where session_id = ? and processed_at is null and idx > ?
|
|
339
350
|
) as has_pending`,
|
|
340
351
|
)
|
|
341
352
|
const upsertPresence = db.prepare(
|
|
@@ -368,12 +379,80 @@ export function sqlite(options: SqliteStoreOptions = {}): SqliteStore {
|
|
|
368
379
|
select 1 from a2_events pending
|
|
369
380
|
where pending.session_id = ?
|
|
370
381
|
and pending.processed_at is null
|
|
382
|
+
and pending.idx > ?
|
|
371
383
|
) as session_has_pending
|
|
372
384
|
from a2_events stored
|
|
373
385
|
where stored.event_id in (${Array.from({ length: count }, () => '?').join(', ')})
|
|
374
386
|
order by stored.idx`,
|
|
375
387
|
)
|
|
376
388
|
|
|
389
|
+
const selectClearIndex = db.prepare(
|
|
390
|
+
'select clear_index from a2_clears where session_id = ?',
|
|
391
|
+
)
|
|
392
|
+
const clearIndexOf = (sessionId: string): number =>
|
|
393
|
+
Number(
|
|
394
|
+
(
|
|
395
|
+
selectClearIndex.get(sessionId) as
|
|
396
|
+
{ clear_index: number | bigint } | undefined
|
|
397
|
+
)?.clear_index ?? 0,
|
|
398
|
+
)
|
|
399
|
+
const deleteEventById = db.prepare(
|
|
400
|
+
'delete from a2_events where session_id = ? and event_id = ?',
|
|
401
|
+
)
|
|
402
|
+
|
|
403
|
+
/**
|
|
404
|
+
* The rows owning `ids`. This session's rows below its clear marker do
|
|
405
|
+
* not own their ids: they are deleted here so the ids can be reused.
|
|
406
|
+
*/
|
|
407
|
+
const liveOwnersOf = (
|
|
408
|
+
sessionId: string,
|
|
409
|
+
clearIndex: number,
|
|
410
|
+
ids: readonly string[],
|
|
411
|
+
): ExistingEventRow[] => {
|
|
412
|
+
if (ids.length === 0) return []
|
|
413
|
+
const rows = selectByIds(ids.length).all(
|
|
414
|
+
sessionId,
|
|
415
|
+
clearIndex,
|
|
416
|
+
...ids,
|
|
417
|
+
) as unknown as ExistingEventRow[]
|
|
418
|
+
return rows.filter((row) => {
|
|
419
|
+
if (row.session_id !== sessionId || Number(row.idx) >= clearIndex)
|
|
420
|
+
return true
|
|
421
|
+
deleteEventById.run(sessionId, row.event_id)
|
|
422
|
+
return false
|
|
423
|
+
})
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
const selectCauseParent = db.prepare(
|
|
427
|
+
'select attempt_count, failed_at from a2_events where session_id = ? and idx = ?',
|
|
428
|
+
)
|
|
429
|
+
/** The attempt-currency fence shared by handler appends and clears. */
|
|
430
|
+
const assertCurrentCause = ({
|
|
431
|
+
sessionId,
|
|
432
|
+
cause,
|
|
433
|
+
clearIndex,
|
|
434
|
+
}: {
|
|
435
|
+
sessionId: string
|
|
436
|
+
cause: EventCause
|
|
437
|
+
clearIndex: number
|
|
438
|
+
}): void => {
|
|
439
|
+
if (cause.index <= clearIndex)
|
|
440
|
+
throw supersededAttemptError(sessionId, cause)
|
|
441
|
+
const parent = selectCauseParent.get(sessionId, cause.index) as
|
|
442
|
+
{ attempt_count: number | bigint; failed_at: unknown } | undefined
|
|
443
|
+
if (!parent) {
|
|
444
|
+
throw new TypeError(
|
|
445
|
+
`no event at index ${cause.index} in session '${sessionId}'`,
|
|
446
|
+
)
|
|
447
|
+
}
|
|
448
|
+
if (
|
|
449
|
+
Number(parent.attempt_count) !== cause.attempt ||
|
|
450
|
+
parent.failed_at !== null
|
|
451
|
+
) {
|
|
452
|
+
throw supersededAttemptError(sessionId, cause)
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
|
|
377
456
|
const tx = <T>(fn: () => T): T => {
|
|
378
457
|
db.exec('begin immediate')
|
|
379
458
|
try {
|
|
@@ -392,12 +471,13 @@ export function sqlite(options: SqliteStoreOptions = {}): SqliteStore {
|
|
|
392
471
|
}
|
|
393
472
|
|
|
394
473
|
return {
|
|
395
|
-
async append(sessionId, events) {
|
|
474
|
+
async append(sessionId, events, appendOptions) {
|
|
396
475
|
if (events.length === 0) {
|
|
397
476
|
return wrap(() => {
|
|
398
477
|
const status = appendStatus.get(
|
|
399
478
|
sessionId,
|
|
400
479
|
sessionId,
|
|
480
|
+
clearIndexOf(sessionId),
|
|
401
481
|
) as AppendStatusRow
|
|
402
482
|
return {
|
|
403
483
|
events: [],
|
|
@@ -407,6 +487,7 @@ export function sqlite(options: SqliteStoreOptions = {}): SqliteStore {
|
|
|
407
487
|
}
|
|
408
488
|
return wrap(() =>
|
|
409
489
|
tx(() => {
|
|
490
|
+
const clearIndex = clearIndexOf(sessionId)
|
|
410
491
|
const supplied = events.filter((e) => e.id !== undefined)
|
|
411
492
|
const suppliedIds = supplied.map((e) => e.id as string)
|
|
412
493
|
if (new Set(suppliedIds).size !== suppliedIds.length) {
|
|
@@ -416,10 +497,7 @@ export function sqlite(options: SqliteStoreOptions = {}): SqliteStore {
|
|
|
416
497
|
)
|
|
417
498
|
}
|
|
418
499
|
if (suppliedIds.length > 0) {
|
|
419
|
-
const existing =
|
|
420
|
-
sessionId,
|
|
421
|
-
...suppliedIds,
|
|
422
|
-
) as unknown as ExistingEventRow[]
|
|
500
|
+
const existing = liveOwnersOf(sessionId, clearIndex, suppliedIds)
|
|
423
501
|
if (existing.length > 0) {
|
|
424
502
|
const foreign = existing.find(
|
|
425
503
|
(row) => row.session_id !== sessionId,
|
|
@@ -443,37 +521,25 @@ export function sqlite(options: SqliteStoreOptions = {}): SqliteStore {
|
|
|
443
521
|
}
|
|
444
522
|
}
|
|
445
523
|
|
|
524
|
+
const originIndex = appendOptions?.originIndex
|
|
525
|
+
if (originIndex !== undefined && originIndex <= clearIndex) {
|
|
526
|
+
throw sessionClearedError(
|
|
527
|
+
`scheduled work from event ${originIndex} was cleared in session '${sessionId}'`,
|
|
528
|
+
)
|
|
529
|
+
}
|
|
446
530
|
// Attempt-currency fence: a fresh handler append commits only
|
|
447
531
|
// while its causal attempt is still the parent's latest
|
|
448
532
|
// (store.ts `append`). `begin immediate` serializes this read
|
|
449
533
|
// against a concurrent claim.
|
|
450
534
|
for (const e of events) {
|
|
451
|
-
if (
|
|
452
|
-
|
|
453
|
-
.prepare(
|
|
454
|
-
'select attempt_count, failed_at from a2_events where session_id = ? and idx = ?',
|
|
455
|
-
)
|
|
456
|
-
.get(sessionId, e.cause.index) as
|
|
457
|
-
{ attempt_count: number | bigint; failed_at: unknown } | undefined
|
|
458
|
-
if (!parent) {
|
|
459
|
-
throw new TypeError(
|
|
460
|
-
`no event at index ${e.cause.index} in session '${sessionId}'`,
|
|
461
|
-
)
|
|
462
|
-
}
|
|
463
|
-
if (
|
|
464
|
-
Number(parent.attempt_count) !== e.cause.attempt ||
|
|
465
|
-
parent.failed_at !== null
|
|
466
|
-
) {
|
|
467
|
-
throw new A2Error(
|
|
468
|
-
'SUPERSEDED_ATTEMPT',
|
|
469
|
-
`attempt ${e.cause.attempt} no longer owns event ${e.cause.index} in session '${sessionId}'`,
|
|
470
|
-
)
|
|
471
|
-
}
|
|
535
|
+
if (e.cause)
|
|
536
|
+
assertCurrentCause({ sessionId, cause: e.cause, clearIndex })
|
|
472
537
|
}
|
|
473
538
|
|
|
474
539
|
const status = appendStatus.get(
|
|
475
540
|
sessionId,
|
|
476
541
|
sessionId,
|
|
542
|
+
clearIndex,
|
|
477
543
|
) as AppendStatusRow
|
|
478
544
|
const base = Number(status.max)
|
|
479
545
|
const now = clock.now().getTime()
|
|
@@ -492,6 +558,7 @@ export function sqlite(options: SqliteStoreOptions = {}): SqliteStore {
|
|
|
492
558
|
e.lane ?? null,
|
|
493
559
|
sessionId,
|
|
494
560
|
e.lane ?? null,
|
|
561
|
+
clearIndex,
|
|
495
562
|
e.settled ? now : null,
|
|
496
563
|
)
|
|
497
564
|
return {
|
|
@@ -530,8 +597,12 @@ export function sqlite(options: SqliteStoreOptions = {}): SqliteStore {
|
|
|
530
597
|
|
|
531
598
|
async read(sessionId, opts) {
|
|
532
599
|
return wrap(() => {
|
|
533
|
-
|
|
534
|
-
const
|
|
600
|
+
// Reads start at the latest clear marker.
|
|
601
|
+
const conditions = [
|
|
602
|
+
'session_id = ?',
|
|
603
|
+
'idx >= coalesce((select clear_index from a2_clears where session_id = ?), 0)',
|
|
604
|
+
]
|
|
605
|
+
const params: (string | number)[] = [sessionId, sessionId]
|
|
535
606
|
if (opts?.afterIndex !== undefined) {
|
|
536
607
|
conditions.push('idx > ?')
|
|
537
608
|
params.push(opts.afterIndex)
|
|
@@ -560,6 +631,7 @@ export function sqlite(options: SqliteStoreOptions = {}): SqliteStore {
|
|
|
560
631
|
tx(() => {
|
|
561
632
|
const now = clock.now().getTime()
|
|
562
633
|
const expiresAt = expiresAtMs ?? now + ttlMs
|
|
634
|
+
const clearIndex = clearIndexOf(sessionId)
|
|
563
635
|
const exclusion =
|
|
564
636
|
excludeIndexes.length === 0
|
|
565
637
|
? ''
|
|
@@ -572,11 +644,17 @@ export function sqlite(options: SqliteStoreOptions = {}): SqliteStore {
|
|
|
572
644
|
and event.processed_at is null
|
|
573
645
|
and event.failed_at is null
|
|
574
646
|
and event.lane_ready = 1
|
|
647
|
+
and event.idx > ?
|
|
575
648
|
and (event.claim_expires_at is null or event.claim_expires_at <= ?)
|
|
576
649
|
${exclusion}
|
|
577
650
|
order by event.idx`,
|
|
578
651
|
)
|
|
579
|
-
.all(
|
|
652
|
+
.all(
|
|
653
|
+
sessionId,
|
|
654
|
+
clearIndex,
|
|
655
|
+
now,
|
|
656
|
+
...excludeIndexes,
|
|
657
|
+
) as unknown as Array<{
|
|
580
658
|
idx: number | bigint
|
|
581
659
|
}>
|
|
582
660
|
if (eligible.length > 0) {
|
|
@@ -599,7 +677,9 @@ export function sqlite(options: SqliteStoreOptions = {}): SqliteStore {
|
|
|
599
677
|
sessionId,
|
|
600
678
|
row.idx,
|
|
601
679
|
) as EventRow
|
|
602
|
-
|
|
680
|
+
const stored = toStored(claimed)
|
|
681
|
+
if (clearIndex > 0) stored.clearIndex = clearIndex
|
|
682
|
+
return stored
|
|
603
683
|
})
|
|
604
684
|
return { outcome: 'claimed', events }
|
|
605
685
|
}
|
|
@@ -612,9 +692,10 @@ export function sqlite(options: SqliteStoreOptions = {}): SqliteStore {
|
|
|
612
692
|
and processed_at is null
|
|
613
693
|
and failed_at is null
|
|
614
694
|
and lane_ready = 1
|
|
695
|
+
and idx > ?
|
|
615
696
|
and claim_expires_at > ?`,
|
|
616
697
|
)
|
|
617
|
-
.get(sessionId, now) as {
|
|
698
|
+
.get(sessionId, clearIndex, now) as {
|
|
618
699
|
retry_at: number | bigint | null
|
|
619
700
|
}
|
|
620
701
|
return active.retry_at === null
|
|
@@ -645,7 +726,12 @@ export function sqlite(options: SqliteStoreOptions = {}): SqliteStore {
|
|
|
645
726
|
and claim_holder = ?
|
|
646
727
|
and claim_expires_at > ?`,
|
|
647
728
|
)
|
|
729
|
+
const clearIndex = clearIndexOf(sessionId)
|
|
648
730
|
for (const claim of claims) {
|
|
731
|
+
if (claim.index <= clearIndex) {
|
|
732
|
+
superseded.push(claim.index)
|
|
733
|
+
continue
|
|
734
|
+
}
|
|
649
735
|
const row = currentAttempt.get(sessionId, claim.index) as
|
|
650
736
|
{ attempt_count: number | bigint } | undefined
|
|
651
737
|
if (!row) continue
|
|
@@ -674,6 +760,8 @@ export function sqlite(options: SqliteStoreOptions = {}): SqliteStore {
|
|
|
674
760
|
async completeAttempt({ sessionId, index, attempt, events }) {
|
|
675
761
|
return wrap(() =>
|
|
676
762
|
tx(() => {
|
|
763
|
+
const clearIndex = clearIndexOf(sessionId)
|
|
764
|
+
if (index <= clearIndex) return { outcome: 'superseded' }
|
|
677
765
|
const parent = db
|
|
678
766
|
.prepare('select * from a2_events where session_id = ? and idx = ?')
|
|
679
767
|
.get(sessionId, index) as EventRow | undefined
|
|
@@ -713,6 +801,7 @@ export function sqlite(options: SqliteStoreOptions = {}): SqliteStore {
|
|
|
713
801
|
? []
|
|
714
802
|
: (selectByIds(ids.length).all(
|
|
715
803
|
sessionId,
|
|
804
|
+
clearIndex,
|
|
716
805
|
...ids,
|
|
717
806
|
) as unknown as ExistingEventRow[])
|
|
718
807
|
const byId = new Map(existing.map((row) => [row.event_id, row]))
|
|
@@ -748,11 +837,7 @@ export function sqlite(options: SqliteStoreOptions = {}): SqliteStore {
|
|
|
748
837
|
return { outcome: 'superseded' }
|
|
749
838
|
}
|
|
750
839
|
|
|
751
|
-
if (
|
|
752
|
-
ids.length > 0 &&
|
|
753
|
-
(selectByIds(ids.length).all(sessionId, ...ids) as unknown[])
|
|
754
|
-
.length > 0
|
|
755
|
-
) {
|
|
840
|
+
if (liveOwnersOf(sessionId, clearIndex, ids).length > 0) {
|
|
756
841
|
throw new A2Error(
|
|
757
842
|
'PARTIAL_DUPLICATE_BATCH',
|
|
758
843
|
'returned event batch contains an already-appended event id',
|
|
@@ -762,6 +847,7 @@ export function sqlite(options: SqliteStoreOptions = {}): SqliteStore {
|
|
|
762
847
|
const status = appendStatus.get(
|
|
763
848
|
sessionId,
|
|
764
849
|
sessionId,
|
|
850
|
+
clearIndex,
|
|
765
851
|
) as AppendStatusRow
|
|
766
852
|
const now = clock.now().getTime()
|
|
767
853
|
db.prepare(
|
|
@@ -788,7 +874,7 @@ export function sqlite(options: SqliteStoreOptions = {}): SqliteStore {
|
|
|
788
874
|
)`,
|
|
789
875
|
).run(sessionId)
|
|
790
876
|
if (parent.lane !== null) {
|
|
791
|
-
promoteLaneHead.run(sessionId, sessionId, parent.lane)
|
|
877
|
+
promoteLaneHead.run(sessionId, sessionId, parent.lane, clearIndex)
|
|
792
878
|
}
|
|
793
879
|
const inserted = events.map((event, offset) => {
|
|
794
880
|
const id = event.id
|
|
@@ -806,6 +892,7 @@ export function sqlite(options: SqliteStoreOptions = {}): SqliteStore {
|
|
|
806
892
|
event.lane ?? null,
|
|
807
893
|
sessionId,
|
|
808
894
|
event.lane ?? null,
|
|
895
|
+
clearIndex,
|
|
809
896
|
event.settled ? now : null,
|
|
810
897
|
)
|
|
811
898
|
return toStored(
|
|
@@ -824,6 +911,9 @@ export function sqlite(options: SqliteStoreOptions = {}): SqliteStore {
|
|
|
824
911
|
async failAttempt({ sessionId, index, attempt, error, maxFailures }) {
|
|
825
912
|
return wrap(() =>
|
|
826
913
|
tx(() => {
|
|
914
|
+
if (index <= clearIndexOf(sessionId)) {
|
|
915
|
+
return { outcome: 'superseded', failureCount: 0 }
|
|
916
|
+
}
|
|
827
917
|
const current = db
|
|
828
918
|
.prepare('select * from a2_events where session_id = ? and idx = ?')
|
|
829
919
|
.get(sessionId, index) as EventRow | undefined
|
|
@@ -898,6 +988,66 @@ export function sqlite(options: SqliteStoreOptions = {}): SqliteStore {
|
|
|
898
988
|
)
|
|
899
989
|
},
|
|
900
990
|
|
|
991
|
+
async deadLetters(sessionId) {
|
|
992
|
+
return wrap(() => {
|
|
993
|
+
const rows = db
|
|
994
|
+
.prepare(
|
|
995
|
+
`select * from a2_events
|
|
996
|
+
where session_id = ?
|
|
997
|
+
and processed_at is null
|
|
998
|
+
and failed_at is not null
|
|
999
|
+
and idx > coalesce(
|
|
1000
|
+
(select clear_index from a2_clears where session_id = ?), 0
|
|
1001
|
+
)
|
|
1002
|
+
order by idx`,
|
|
1003
|
+
)
|
|
1004
|
+
.all(sessionId, sessionId) as unknown as EventRow[]
|
|
1005
|
+
return rows.map(toStored)
|
|
1006
|
+
})
|
|
1007
|
+
},
|
|
1008
|
+
|
|
1009
|
+
async redrive({ sessionId, index, action }) {
|
|
1010
|
+
return wrap(() =>
|
|
1011
|
+
tx(() => {
|
|
1012
|
+
const clearIndex = clearIndexOf(sessionId)
|
|
1013
|
+
const row =
|
|
1014
|
+
index > clearIndex
|
|
1015
|
+
? (db
|
|
1016
|
+
.prepare(
|
|
1017
|
+
'select lane, processed_at, failed_at from a2_events where session_id = ? and idx = ?',
|
|
1018
|
+
)
|
|
1019
|
+
.get(sessionId, index) as
|
|
1020
|
+
| Pick<EventRow, 'lane' | 'processed_at' | 'failed_at'>
|
|
1021
|
+
| undefined)
|
|
1022
|
+
: undefined
|
|
1023
|
+
const redriven =
|
|
1024
|
+
row !== undefined &&
|
|
1025
|
+
row.processed_at === null &&
|
|
1026
|
+
row.failed_at !== null
|
|
1027
|
+
if (redriven && action === 'skip') {
|
|
1028
|
+
db.prepare(
|
|
1029
|
+
`update a2_events set processed_at = ?, lane_ready = 0
|
|
1030
|
+
where session_id = ? and idx = ?`,
|
|
1031
|
+
).run(clock.now().getTime(), sessionId, index)
|
|
1032
|
+
if (row.lane !== null) {
|
|
1033
|
+
promoteLaneHead.run(sessionId, sessionId, row.lane, clearIndex)
|
|
1034
|
+
}
|
|
1035
|
+
} else if (redriven) {
|
|
1036
|
+
db.prepare(
|
|
1037
|
+
`update a2_events set failed_at = null, failure_count = 0
|
|
1038
|
+
where session_id = ? and idx = ?`,
|
|
1039
|
+
).run(sessionId, index)
|
|
1040
|
+
}
|
|
1041
|
+
const status = appendStatus.get(
|
|
1042
|
+
sessionId,
|
|
1043
|
+
sessionId,
|
|
1044
|
+
clearIndex,
|
|
1045
|
+
) as AppendStatusRow
|
|
1046
|
+
return { redriven, hasPending: Number(status.has_pending) === 1 }
|
|
1047
|
+
}),
|
|
1048
|
+
)
|
|
1049
|
+
},
|
|
1050
|
+
|
|
901
1051
|
async readState(sessionId, reducerName, stateOptions) {
|
|
902
1052
|
return wrap(() => {
|
|
903
1053
|
const throughIndex = stateOptions?.throughIndex ?? null
|
|
@@ -905,10 +1055,15 @@ export function sqlite(options: SqliteStoreOptions = {}): SqliteStore {
|
|
|
905
1055
|
stateOptions?.snapshotThroughIndex ?? throughIndex
|
|
906
1056
|
const rows = db
|
|
907
1057
|
.prepare(
|
|
908
|
-
`with
|
|
1058
|
+
`with boundary as materialized (
|
|
1059
|
+
select coalesce(
|
|
1060
|
+
(select clear_index from a2_clears where session_id = ?), 0
|
|
1061
|
+
) as clear_index
|
|
1062
|
+
), head as materialized (
|
|
909
1063
|
select up_to_index, state, updated_at
|
|
910
1064
|
from a2_snapshots
|
|
911
1065
|
where session_id = ? and reducer_name = ?
|
|
1066
|
+
and up_to_index >= (select clear_index from boundary)
|
|
912
1067
|
), snapshot as materialized (
|
|
913
1068
|
select up_to_index, state from (
|
|
914
1069
|
select up_to_index, state from head
|
|
@@ -917,10 +1072,12 @@ export function sqlite(options: SqliteStoreOptions = {}): SqliteStore {
|
|
|
917
1072
|
select up_to_index, state
|
|
918
1073
|
from a2_snapshot_history
|
|
919
1074
|
where session_id = ? and reducer_name = ?
|
|
1075
|
+
and up_to_index >= (select clear_index from boundary)
|
|
920
1076
|
and (? is null or up_to_index <= ?)
|
|
921
1077
|
) order by up_to_index desc limit 1
|
|
922
1078
|
)
|
|
923
1079
|
select 0 as row_order, 'snapshot' as row_kind,
|
|
1080
|
+
(select clear_index from boundary) as clear_index,
|
|
924
1081
|
(select up_to_index from head) as head_index,
|
|
925
1082
|
(select up_to_index from snapshot) as snapshot_index,
|
|
926
1083
|
(select state from snapshot) as snapshot_state,
|
|
@@ -937,16 +1094,21 @@ export function sqlite(options: SqliteStoreOptions = {}): SqliteStore {
|
|
|
937
1094
|
null as claim_expires_at
|
|
938
1095
|
union all
|
|
939
1096
|
select 1 as row_order, 'event' as row_kind,
|
|
1097
|
+
null as clear_index,
|
|
940
1098
|
(select up_to_index from head) as head_index,
|
|
941
1099
|
null as snapshot_index, null as snapshot_state,
|
|
942
1100
|
event.*
|
|
943
1101
|
from a2_events as event
|
|
944
1102
|
where event.session_id = ?
|
|
945
|
-
and event.idx >
|
|
1103
|
+
and event.idx > max(
|
|
1104
|
+
coalesce((select up_to_index from snapshot), 0),
|
|
1105
|
+
(select clear_index from boundary) - 1
|
|
1106
|
+
)
|
|
946
1107
|
and (? is null or event.idx <= ?)
|
|
947
1108
|
order by row_order, idx`,
|
|
948
1109
|
)
|
|
949
1110
|
.all(
|
|
1111
|
+
sessionId,
|
|
950
1112
|
sessionId,
|
|
951
1113
|
reducerName,
|
|
952
1114
|
snapshotThroughIndex,
|
|
@@ -960,7 +1122,9 @@ export function sqlite(options: SqliteStoreOptions = {}): SqliteStore {
|
|
|
960
1122
|
throughIndex,
|
|
961
1123
|
) as unknown as StateReadRow[]
|
|
962
1124
|
const snapshot = rows.find((row) => row.row_kind === 'snapshot')
|
|
1125
|
+
const clearIndex = Number(snapshot?.clear_index ?? 0)
|
|
963
1126
|
return {
|
|
1127
|
+
...(clearIndex > 0 ? { clearIndex } : {}),
|
|
964
1128
|
headIndex:
|
|
965
1129
|
snapshot?.head_index === null || snapshot?.head_index === undefined
|
|
966
1130
|
? null
|
|
@@ -991,7 +1155,7 @@ export function sqlite(options: SqliteStoreOptions = {}): SqliteStore {
|
|
|
991
1155
|
)
|
|
992
1156
|
const unfinished = db.prepare(
|
|
993
1157
|
`select 1 from a2_events
|
|
994
|
-
where session_id = ? and idx = ?
|
|
1158
|
+
where session_id = ? and idx = ? and idx > ?
|
|
995
1159
|
and processed_at is null and failed_at is null`,
|
|
996
1160
|
)
|
|
997
1161
|
const insertPin = db.prepare(
|
|
@@ -1025,9 +1189,12 @@ export function sqlite(options: SqliteStoreOptions = {}): SqliteStore {
|
|
|
1025
1189
|
`delete from a2_snapshot_history
|
|
1026
1190
|
where session_id = ? and reducer_name = ? and up_to_index = ?`,
|
|
1027
1191
|
)
|
|
1192
|
+
const clearIndex = clearIndexOf(sessionId)
|
|
1028
1193
|
for (const write of writes.toSorted((a, b) => a.index - b.index)) {
|
|
1194
|
+
// A fold from before the latest clear describes a lifetime that is gone.
|
|
1195
|
+
if (write.index < clearIndex) continue
|
|
1029
1196
|
for (const eventIndex of write.pinEventIndexes ?? []) {
|
|
1030
|
-
if (unfinished.get(sessionId, eventIndex)) {
|
|
1197
|
+
if (unfinished.get(sessionId, eventIndex, clearIndex)) {
|
|
1031
1198
|
insertPin.run(sessionId, eventIndex, reducerName, write.index)
|
|
1032
1199
|
}
|
|
1033
1200
|
}
|
|
@@ -1038,6 +1205,7 @@ export function sqlite(options: SqliteStoreOptions = {}): SqliteStore {
|
|
|
1038
1205
|
if (
|
|
1039
1206
|
head &&
|
|
1040
1207
|
write.index > Number(head.up_to_index) &&
|
|
1208
|
+
Number(head.up_to_index) >= clearIndex &&
|
|
1041
1209
|
hasPin.get(sessionId, reducerName, Number(head.up_to_index))
|
|
1042
1210
|
) {
|
|
1043
1211
|
putHistory.run(
|
|
@@ -1126,11 +1294,15 @@ export function sqlite(options: SqliteStoreOptions = {}): SqliteStore {
|
|
|
1126
1294
|
stream(sessionId, opts) {
|
|
1127
1295
|
const readAfter = (afterIndex: number): StoredEvent[] =>
|
|
1128
1296
|
wrap(() => {
|
|
1297
|
+
// A cursor below the latest marker resumes at it.
|
|
1129
1298
|
const rows = db
|
|
1130
1299
|
.prepare(
|
|
1131
|
-
|
|
1300
|
+
`select * from a2_events
|
|
1301
|
+
where session_id = ? and idx > ?
|
|
1302
|
+
and idx >= coalesce((select clear_index from a2_clears where session_id = ?), 0)
|
|
1303
|
+
order by idx`,
|
|
1132
1304
|
)
|
|
1133
|
-
.all(sessionId, afterIndex) as unknown as EventRow[]
|
|
1305
|
+
.all(sessionId, afterIndex, sessionId) as unknown as EventRow[]
|
|
1134
1306
|
return rows.map(toStored)
|
|
1135
1307
|
})
|
|
1136
1308
|
return pollingStream(
|
|
@@ -1139,6 +1311,108 @@ export function sqlite(options: SqliteStoreOptions = {}): SqliteStore {
|
|
|
1139
1311
|
)
|
|
1140
1312
|
},
|
|
1141
1313
|
|
|
1314
|
+
async clear({ sessionId, ifLastIndex, cause }) {
|
|
1315
|
+
return wrap(() =>
|
|
1316
|
+
tx(() => {
|
|
1317
|
+
const clearIndex = clearIndexOf(sessionId)
|
|
1318
|
+
if (cause) assertCurrentCause({ sessionId, cause, clearIndex })
|
|
1319
|
+
const head = Number(
|
|
1320
|
+
(
|
|
1321
|
+
appendStatus.get(
|
|
1322
|
+
sessionId,
|
|
1323
|
+
sessionId,
|
|
1324
|
+
clearIndex,
|
|
1325
|
+
) as AppendStatusRow
|
|
1326
|
+
).max,
|
|
1327
|
+
)
|
|
1328
|
+
if (ifLastIndex !== undefined && clearIndex === ifLastIndex + 1) {
|
|
1329
|
+
return { cleared: true, index: head, clearIndex }
|
|
1330
|
+
}
|
|
1331
|
+
if (ifLastIndex !== undefined && ifLastIndex !== head) {
|
|
1332
|
+
return { cleared: false, index: head, clearIndex }
|
|
1333
|
+
}
|
|
1334
|
+
if (head === 0 || clearIndex === head) {
|
|
1335
|
+
return { cleared: true, index: head, clearIndex }
|
|
1336
|
+
}
|
|
1337
|
+
const marker = head + 1
|
|
1338
|
+
const now = clock.now().getTime()
|
|
1339
|
+
insertEvent.run(
|
|
1340
|
+
sessionId,
|
|
1341
|
+
marker,
|
|
1342
|
+
CLEAR_EVENT_TYPE,
|
|
1343
|
+
'null',
|
|
1344
|
+
generateId(),
|
|
1345
|
+
now,
|
|
1346
|
+
null,
|
|
1347
|
+
null,
|
|
1348
|
+
null,
|
|
1349
|
+
sessionId,
|
|
1350
|
+
null,
|
|
1351
|
+
clearIndex,
|
|
1352
|
+
now,
|
|
1353
|
+
)
|
|
1354
|
+
db.prepare(
|
|
1355
|
+
`insert into a2_clears (session_id, clear_index) values (?, ?)
|
|
1356
|
+
on conflict (session_id) do update set clear_index = excluded.clear_index`,
|
|
1357
|
+
).run(sessionId, marker)
|
|
1358
|
+
return { cleared: true, index: marker, clearIndex: marker }
|
|
1359
|
+
}),
|
|
1360
|
+
)
|
|
1361
|
+
},
|
|
1362
|
+
|
|
1363
|
+
async reclaim(sessionId) {
|
|
1364
|
+
return wrap(() =>
|
|
1365
|
+
tx(() => {
|
|
1366
|
+
const clearIndex = clearIndexOf(sessionId)
|
|
1367
|
+
if (clearIndex === 0) return { deleted: 0, done: true }
|
|
1368
|
+
const last = db
|
|
1369
|
+
.prepare(
|
|
1370
|
+
`select max(idx) as last from (
|
|
1371
|
+
select idx from a2_events
|
|
1372
|
+
where session_id = ? and idx < ?
|
|
1373
|
+
order by idx limit ?
|
|
1374
|
+
)`,
|
|
1375
|
+
)
|
|
1376
|
+
.get(sessionId, clearIndex, RECLAIM_BATCH) as {
|
|
1377
|
+
last: number | bigint | null
|
|
1378
|
+
}
|
|
1379
|
+
let deleted = 0
|
|
1380
|
+
if (last.last !== null) {
|
|
1381
|
+
const through = Number(last.last)
|
|
1382
|
+
deleted = Number(
|
|
1383
|
+
db
|
|
1384
|
+
.prepare(
|
|
1385
|
+
'delete from a2_events where session_id = ? and idx <= ?',
|
|
1386
|
+
)
|
|
1387
|
+
.run(sessionId, through).changes,
|
|
1388
|
+
)
|
|
1389
|
+
db.prepare(
|
|
1390
|
+
'delete from a2_snapshot_pins where session_id = ? and event_index <= ?',
|
|
1391
|
+
).run(sessionId, through)
|
|
1392
|
+
}
|
|
1393
|
+
const done =
|
|
1394
|
+
db
|
|
1395
|
+
.prepare(
|
|
1396
|
+
'select 1 from a2_events where session_id = ? and idx < ? limit 1',
|
|
1397
|
+
)
|
|
1398
|
+
.get(sessionId, clearIndex) === undefined
|
|
1399
|
+
// Writes serialize with the clear, so none lands below the marker later.
|
|
1400
|
+
if (done) {
|
|
1401
|
+
for (const table of [
|
|
1402
|
+
'a2_snapshot_pins',
|
|
1403
|
+
'a2_snapshot_history',
|
|
1404
|
+
'a2_snapshots',
|
|
1405
|
+
]) {
|
|
1406
|
+
db.prepare(
|
|
1407
|
+
`delete from ${table} where session_id = ? and up_to_index < ?`,
|
|
1408
|
+
).run(sessionId, clearIndex)
|
|
1409
|
+
}
|
|
1410
|
+
}
|
|
1411
|
+
return { deleted, done }
|
|
1412
|
+
}),
|
|
1413
|
+
)
|
|
1414
|
+
},
|
|
1415
|
+
|
|
1142
1416
|
close() {
|
|
1143
1417
|
db.close()
|
|
1144
1418
|
},
|