experimental-a2 0.16.0 → 0.17.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +360 -0
- package/dist/{actor-ohPC-81x.d.ts → actor-DBbe5xeE.d.ts} +21 -11
- package/dist/actor-DBbe5xeE.d.ts.map +1 -0
- package/dist/actor-client.d.ts +2 -5
- package/dist/actor-client.d.ts.map +1 -1
- package/dist/actor-client.js +13 -4
- package/dist/actor-client.js.map +1 -1
- package/dist/actor-react.d.ts +5 -3
- package/dist/actor-react.d.ts.map +1 -1
- package/dist/actor-react.js +20 -10
- package/dist/actor-react.js.map +1 -1
- package/dist/{actor-shared-B5tJfzt-.js → actor-shared-t7w8xIKP.js} +12 -7
- package/dist/actor-shared-t7w8xIKP.js.map +1 -0
- package/dist/actor.d.ts +1 -1
- package/dist/actor.js +42 -23
- package/dist/actor.js.map +1 -1
- package/dist/{ai-CrLYwNEx.js → ai-UrWfKeuv.js} +966 -541
- package/dist/ai-UrWfKeuv.js.map +1 -0
- package/dist/ai-client.d.ts +27 -0
- package/dist/ai-client.d.ts.map +1 -0
- package/dist/ai-client.js +29 -0
- package/dist/ai-client.js.map +1 -0
- package/dist/ai-react.d.ts +96 -0
- package/dist/ai-react.d.ts.map +1 -0
- package/dist/ai-react.js +142 -0
- package/dist/ai-react.js.map +1 -0
- package/dist/ai-server.browser.js +2 -2
- package/dist/ai-server.browser.js.map +1 -1
- package/dist/ai-server.d.ts +80 -36
- package/dist/ai-server.d.ts.map +1 -1
- package/dist/ai-server.js +466 -265
- package/dist/ai-server.js.map +1 -1
- package/dist/{ai-HJ9fHfYI.d.ts → ai-vpGtcaCB.d.ts} +115 -101
- package/dist/ai-vpGtcaCB.d.ts.map +1 -0
- package/dist/ai.d.ts +2 -2
- package/dist/ai.js +2 -2
- package/dist/{client-BYzHjkwU.js → client-COaiaDub.js} +447 -80
- package/dist/client-COaiaDub.js.map +1 -0
- package/dist/{client-CzyacQpJ.d.ts → client-qRnRUzBS.d.ts} +14 -13
- package/dist/client-qRnRUzBS.d.ts.map +1 -0
- package/dist/client.d.ts +4 -2
- package/dist/client.js +1 -1
- package/dist/{contract-CKRg_E4q.js → contract-BYKASlCV.js} +12 -21
- package/dist/contract-BYKASlCV.js.map +1 -0
- package/dist/{idempotent-replay-DVOlyYbx.js → idempotent-replay-JdgMQRRm.js} +2 -2
- package/dist/{idempotent-replay-DVOlyYbx.js.map → idempotent-replay-JdgMQRRm.js.map} +1 -1
- package/dist/index.d.ts +6 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -3
- package/dist/{errors-DvhSXnxk.d.ts → initial-snapshot-BrLso5x_.d.ts} +7 -3
- package/dist/initial-snapshot-BrLso5x_.d.ts.map +1 -0
- package/dist/{internal-Dq2qYxou.js → internal-CbVvEwad.js} +23 -2
- package/dist/internal-CbVvEwad.js.map +1 -0
- package/dist/otel.d.ts +1 -1
- package/dist/react-BbJ-g6Uu.d.ts +86 -0
- package/dist/react-BbJ-g6Uu.d.ts.map +1 -0
- package/dist/react.d.ts +2 -122
- package/dist/react.js +107 -100
- package/dist/react.js.map +1 -1
- package/dist/reducer-BAXc8JyF.js +87 -0
- package/dist/reducer-BAXc8JyF.js.map +1 -0
- package/dist/{reducer-otzHjuJj.d.ts → reducer-BYu6DT7h.d.ts} +45 -29
- package/dist/reducer-BYu6DT7h.d.ts.map +1 -0
- package/dist/reducer-runtime-DQwz_J59.js +28 -0
- package/dist/reducer-runtime-DQwz_J59.js.map +1 -0
- package/dist/scheduler-qstash.d.ts +3 -3
- package/dist/scheduler-qstash.d.ts.map +1 -1
- package/dist/scheduler-qstash.js +33 -10
- package/dist/scheduler-qstash.js.map +1 -1
- package/dist/{scheduler-task-BpzhPnRS.js → scheduler-task-X1SToDjP.js} +18 -3
- package/dist/scheduler-task-X1SToDjP.js.map +1 -0
- package/dist/scheduler-vercel.d.ts +4 -9
- package/dist/scheduler-vercel.d.ts.map +1 -1
- package/dist/scheduler-vercel.js +25 -44
- package/dist/scheduler-vercel.js.map +1 -1
- package/dist/{server-DbD7IJVK.d.ts → server-CGM8WzRG.d.ts} +87 -26
- package/dist/server-CGM8WzRG.d.ts.map +1 -0
- package/dist/{server-BD5ckxZb.js → server-Rj64fNvX.js} +854 -202
- package/dist/server-Rj64fNvX.js.map +1 -0
- package/dist/server.d.ts +4 -4
- package/dist/server.js +2 -2
- package/dist/{errors-DCk6ch5n.js → session-clear-DGI4jTQ5.js} +17 -2
- package/dist/session-clear-DGI4jTQ5.js.map +1 -0
- package/dist/session-clear-Dshpuhff.d.ts +12 -0
- package/dist/session-clear-Dshpuhff.d.ts.map +1 -0
- package/dist/{store-DyZM6fS5.d.ts → store-Gpkq7WBo.d.ts} +44 -3
- package/dist/store-Gpkq7WBo.d.ts.map +1 -0
- package/dist/store-N8PXxDAS.js.map +1 -1
- package/dist/store-memory.d.ts +1 -1
- package/dist/store-memory.d.ts.map +1 -1
- package/dist/store-memory.js +182 -69
- package/dist/store-memory.js.map +1 -1
- package/dist/{store-polling-CmxUbV93.js → store-polling-4_y8lETm.js} +21 -4
- package/dist/store-polling-4_y8lETm.js.map +1 -0
- package/dist/store-postgres.d.ts +1 -1
- package/dist/store-postgres.d.ts.map +1 -1
- package/dist/store-postgres.js +399 -108
- package/dist/store-postgres.js.map +1 -1
- package/dist/{store-presence-polling-C7-XZyW9.js → store-presence-polling-Q1C-k2dd.js} +3 -3
- package/dist/{store-presence-polling-C7-XZyW9.js.map → store-presence-polling-Q1C-k2dd.js.map} +1 -1
- package/dist/store-redis-http.d.ts +1 -1
- package/dist/store-redis-http.d.ts.map +1 -1
- package/dist/store-redis-http.js +293 -11
- package/dist/store-redis-http.js.map +1 -1
- package/dist/store-redis-notify-tqWNnMRu.js +2374 -0
- package/dist/store-redis-notify-tqWNnMRu.js.map +1 -0
- package/dist/store-redis.d.ts +1 -1
- package/dist/store-redis.d.ts.map +1 -1
- package/dist/store-redis.js +11 -11
- package/dist/store-redis.js.map +1 -1
- package/dist/store-sqlite.d.ts +1 -1
- package/dist/store-sqlite.d.ts.map +1 -1
- package/dist/store-sqlite.js +166 -32
- package/dist/store-sqlite.js.map +1 -1
- package/dist/stream-buffer-Cx7AAPGq.js +23 -0
- package/dist/stream-buffer-Cx7AAPGq.js.map +1 -0
- package/dist/{telemetry-B5jzpy6y.d.ts → telemetry-cZH-cibj.d.ts} +2 -2
- package/dist/telemetry-cZH-cibj.d.ts.map +1 -0
- package/dist/{wire-BO5wWCb1.js → wire-DjfHOvdW.js} +132 -10
- package/dist/wire-DjfHOvdW.js.map +1 -0
- package/docs/01-quickstart.mdx +2 -2
- package/docs/actors/01-introduction.mdx +4 -0
- package/docs/actors/04-routes.mdx +14 -1
- package/docs/concepts/01-contracts.mdx +95 -0
- package/docs/concepts/04-state.mdx +3 -3
- package/docs/guides/01-timers.mdx +22 -14
- package/docs/guides/02-cancellation.mdx +1 -1
- package/docs/guides/03-react.mdx +268 -328
- package/docs/guides/04-local-first.mdx +32 -73
- package/docs/guides/05-production.mdx +40 -27
- package/docs/guides/06-ai-agents.mdx +680 -580
- package/docs/guides/09-presence.mdx +3 -7
- package/docs/guides/10-transports.mdx +5 -5
- package/docs/index.mdx +10 -18
- package/docs/reference/01-api.mdx +662 -195
- package/docs/reference/02-errors.mdx +2 -1
- package/examples/playground/DEPLOY.md +52 -6
- package/examples/playground/app/a0/[appId]/loading.tsx +16 -0
- package/examples/playground/app/a0/[appId]/page.tsx +48 -0
- package/examples/playground/app/a0/a0.css +818 -0
- package/examples/playground/app/a0/app-list.tsx +211 -0
- package/examples/playground/app/a0/app-view.tsx +55 -0
- package/examples/playground/app/a0/apps/[appId]/preview/route.ts +27 -0
- package/examples/playground/app/a0/apps/[appId]/route.ts +23 -0
- package/examples/playground/app/a0/apps.test.ts +116 -0
- package/examples/playground/app/a0/apps.ts +87 -0
- package/examples/playground/app/a0/chat.tsx +364 -0
- package/examples/playground/app/a0/columns.tsx +109 -0
- package/examples/playground/app/a0/context-menu.tsx +116 -0
- package/examples/playground/app/a0/cookies.ts +46 -0
- package/examples/playground/app/a0/crash-on-request.ts +72 -0
- package/examples/playground/app/a0/crashes/route.ts +3 -0
- package/examples/playground/app/a0/crashes.test.ts +33 -0
- package/examples/playground/app/a0/crashes.ts +28 -0
- package/examples/playground/app/a0/devtools.tsx +164 -0
- package/examples/playground/app/a0/events/route.ts +6 -0
- package/examples/playground/app/a0/flags.ts +30 -0
- package/examples/playground/app/a0/layout.tsx +38 -0
- package/examples/playground/app/a0/list/route.ts +10 -0
- package/examples/playground/app/a0/model.test.ts +56 -0
- package/examples/playground/app/a0/model.ts +58 -0
- package/examples/playground/app/a0/page.tsx +14 -0
- package/examples/playground/app/a0/preview-pane.tsx +145 -0
- package/examples/playground/app/a0/preview.test.ts +53 -0
- package/examples/playground/app/a0/preview.ts +26 -0
- package/examples/playground/app/a0/sandbox-log.ts +69 -0
- package/examples/playground/app/a0/sandbox.ts +191 -0
- package/examples/playground/app/a0/sandboxes/scheduler/route.ts +6 -0
- package/examples/playground/app/a0/scheduler/route.ts +7 -0
- package/examples/playground/app/a0/server.ts +158 -0
- package/examples/playground/app/a0/session.ts +47 -0
- package/examples/playground/app/a0/shell.tsx +150 -0
- package/examples/playground/app/a0/short/events/route.ts +9 -0
- package/examples/playground/app/a0/splitter.tsx +120 -0
- package/examples/playground/app/agent/[agentId]/agent-client.tsx +144 -174
- package/examples/playground/app/agent/[agentId]/command-receipt.test.tsx +65 -0
- package/examples/playground/app/agent/[agentId]/command-receipt.tsx +27 -0
- package/examples/playground/app/agent/[agentId]/compaction-event.tsx +25 -24
- package/examples/playground/app/agent/[agentId]/compaction-panel.test.tsx +119 -0
- package/examples/playground/app/agent/[agentId]/compaction-panel.tsx +208 -165
- package/examples/playground/app/agent/[agentId]/page.tsx +2 -16
- package/examples/playground/app/agent/events/route.ts +5 -2
- package/examples/playground/app/agent/ingress.test.ts +98 -0
- package/examples/playground/app/agent/model-input.test.tsx +58 -0
- package/examples/playground/app/agent/model-input.tsx +115 -0
- package/examples/playground/app/agent/model.test.ts +73 -0
- package/examples/playground/app/agent/model.ts +58 -26
- package/examples/playground/app/agent/new-agent-session.tsx +37 -19
- package/examples/playground/app/agent/page.tsx +4 -4
- package/examples/playground/app/agent/server.ts +15 -14
- package/examples/playground/app/agent/session.ts +5 -6
- package/examples/playground/app/agent/telemetry.test.ts +168 -0
- package/examples/playground/app/agent/telemetry.ts +30 -0
- package/examples/playground/app/agent/trace.test.ts +23 -0
- package/examples/playground/app/agent/trace.ts +18 -0
- package/examples/playground/app/canvas/[canvasId]/canvas-client.tsx +11 -11
- package/examples/playground/app/canvas/[canvasId]/page.tsx +1 -2
- package/examples/playground/app/canvas/[canvasId]/session.ts +2 -2
- package/examples/playground/app/canvas/model.ts +53 -53
- package/examples/playground/app/chat/[chatId]/agent-state.test.ts +176 -0
- package/examples/playground/app/chat/[chatId]/agent-stream-drawer.tsx +16 -1
- package/examples/playground/app/chat/[chatId]/chat-client.tsx +55 -77
- package/examples/playground/app/chat/[chatId]/chat-view.ts +37 -3
- package/examples/playground/app/chat/[chatId]/page.tsx +7 -15
- package/examples/playground/app/chat/[chatId]/session.ts +2 -19
- package/examples/playground/app/chat/agent/route.ts +9 -0
- package/examples/playground/app/chat/model.test.ts +46 -119
- package/examples/playground/app/chat/model.ts +82 -128
- package/examples/playground/app/chat/scheduler/route.ts +2 -2
- package/examples/playground/app/chat/server.test.ts +108 -0
- package/examples/playground/app/chat/server.ts +69 -73
- package/examples/playground/app/components/activity-feed.tsx +30 -1
- package/examples/playground/app/components/activity-history.test.tsx +35 -0
- package/examples/playground/app/components/demo-icons.tsx +89 -0
- package/examples/playground/app/components/demos.tsx +33 -0
- package/examples/playground/app/components/fast-link.test.tsx +58 -0
- package/examples/playground/app/components/fast-link.tsx +55 -0
- package/examples/playground/app/components/site-header.tsx +96 -0
- package/examples/playground/app/counter/counter-client.tsx +3 -3
- package/examples/playground/app/counter/model.test.ts +3 -3
- package/examples/playground/app/counter/model.ts +11 -21
- package/examples/playground/app/counter/page.tsx +2 -15
- package/examples/playground/app/counter/session.ts +2 -2
- package/examples/playground/app/documents/[documentId]/code-editor.tsx +12 -1
- package/examples/playground/app/documents/[documentId]/document-client.tsx +10 -11
- package/examples/playground/app/documents/[documentId]/page.tsx +1 -2
- package/examples/playground/app/documents/[documentId]/session.ts +2 -2
- package/examples/playground/app/documents/model.ts +27 -27
- package/examples/playground/app/globals.css +420 -207
- package/examples/playground/app/initial-state.test.tsx +54 -0
- package/examples/playground/app/layout.tsx +7 -23
- package/examples/playground/app/orders/[orderId]/page.tsx +1 -2
- package/examples/playground/app/orders/[orderId]/session.ts +2 -2
- package/examples/playground/app/orders/model.ts +49 -49
- package/examples/playground/app/page.tsx +13 -102
- package/examples/playground/app/recovery/[recoveryId]/page.tsx +1 -2
- package/examples/playground/app/recovery/[recoveryId]/session.ts +2 -2
- package/examples/playground/app/recovery/model.ts +30 -30
- package/examples/playground/instrumentation.ts +5 -0
- package/examples/playground/package.json +10 -2
- package/examples/playground/scripts/a0-template.ts +77 -0
- package/examples/playground/vercel.json +18 -0
- package/package.json +3 -1
- package/src/actor-client.ts +16 -4
- package/src/actor-react.ts +25 -11
- package/src/actor-shared.ts +21 -6
- package/src/actor.ts +76 -31
- package/src/ai-agent-actions.ts +219 -0
- package/src/ai-agent.ts +2343 -0
- package/src/ai-app-state.ts +91 -0
- package/src/ai-client-state.ts +39 -2
- 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 +94 -55
- package/src/ai-control.ts +2 -0
- package/src/ai-coordinator.ts +23 -22
- package/src/ai-message-base.ts +209 -0
- package/src/ai-message-projection.ts +382 -58
- package/src/ai-progress-batches.ts +26 -0
- package/src/ai-projector.ts +95 -14
- package/src/ai-react.ts +146 -0
- package/src/ai-server.browser.ts +2 -2
- package/src/ai-server.ts +20 -1951
- package/src/ai-stored-state.ts +48 -163
- package/src/ai.ts +343 -276
- package/src/bootstrap-stream.ts +72 -0
- package/src/bounded-cache.ts +55 -0
- package/src/client-render-session.ts +49 -0
- package/src/client.ts +688 -151
- package/src/contract.ts +92 -42
- package/src/deterministic-id.ts +37 -0
- package/src/equal-json.ts +32 -0
- package/src/errors.ts +13 -1
- package/src/index.ts +5 -1
- package/src/initial-snapshot.ts +39 -0
- package/src/internal.ts +45 -0
- package/src/postgres-notification-scope.ts +14 -3
- package/src/react.ts +171 -206
- package/src/redis-http-reply.ts +256 -0
- package/src/redis-http-stream.ts +73 -0
- package/src/redis-http-subscriptions.ts +13 -5
- package/src/reducer-runtime.ts +52 -0
- package/src/reducer.ts +92 -10
- package/src/scheduler-qstash.ts +49 -20
- package/src/scheduler-task.ts +34 -2
- package/src/scheduler-vercel.ts +32 -84
- package/src/server-fetch.ts +139 -35
- package/src/server.ts +957 -237
- package/src/session-clear.ts +23 -0
- package/src/session-socket.ts +154 -49
- package/src/sse.ts +71 -4
- package/src/store-memory.ts +220 -78
- package/src/store-polling.ts +18 -4
- package/src/store-postgres.ts +461 -114
- package/src/store-redis-core.ts +1260 -434
- package/src/store-redis-http.ts +18 -2
- package/src/store-redis-notify.ts +260 -85
- package/src/store-redis.ts +19 -9
- package/src/store-sqlite.ts +259 -45
- package/src/store.ts +46 -1
- package/src/stream-buffer.ts +43 -0
- package/src/telemetry.ts +13 -1
- package/src/wire.ts +241 -13
- package/dist/actor-ohPC-81x.d.ts.map +0 -1
- package/dist/actor-shared-B5tJfzt-.js.map +0 -1
- package/dist/ai-CrLYwNEx.js.map +0 -1
- package/dist/ai-HJ9fHfYI.d.ts.map +0 -1
- package/dist/client-BYzHjkwU.js.map +0 -1
- package/dist/client-CzyacQpJ.d.ts.map +0 -1
- package/dist/contract-CKRg_E4q.js.map +0 -1
- package/dist/errors-DCk6ch5n.js.map +0 -1
- package/dist/errors-DvhSXnxk.d.ts.map +0 -1
- package/dist/internal-Dq2qYxou.js.map +0 -1
- package/dist/react.d.ts.map +0 -1
- package/dist/reducer-DEMjEY_O.js +0 -29
- package/dist/reducer-DEMjEY_O.js.map +0 -1
- package/dist/reducer-otzHjuJj.d.ts.map +0 -1
- package/dist/scheduler-task-BpzhPnRS.js.map +0 -1
- package/dist/server-BD5ckxZb.js.map +0 -1
- package/dist/server-DbD7IJVK.d.ts.map +0 -1
- package/dist/store-DyZM6fS5.d.ts.map +0 -1
- package/dist/store-polling-CmxUbV93.js.map +0 -1
- package/dist/store-redis-notify-BVLUfI3j.js +0 -1583
- package/dist/store-redis-notify-BVLUfI3j.js.map +0 -1
- package/dist/telemetry-B5jzpy6y.d.ts.map +0 -1
- package/dist/validate-XKT4FSNn.js +0 -30
- package/dist/validate-XKT4FSNn.js.map +0 -1
- package/dist/wire-BO5wWCb1.js.map +0 -1
- package/examples/playground/app/agent/[agentId]/compaction/route.ts +0 -14
- package/examples/playground/app/agent/compaction-settings.test.ts +0 -144
- package/examples/playground/app/agent/compaction-settings.ts +0 -49
- package/examples/playground/app/agent/compaction-timeline.test.ts +0 -377
- package/examples/playground/app/agent/compaction-timeline.ts +0 -208
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,12 +259,26 @@ export function memory(options: MemoryStoreOptions = {}): A2Store {
|
|
|
226
259
|
return row
|
|
227
260
|
}
|
|
228
261
|
|
|
229
|
-
const appendResult = (sessionId: string, events: StoredEvent[]) =>
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
262
|
+
const appendResult = (sessionId: string, events: StoredEvent[]) => {
|
|
263
|
+
const log = sessions.get(sessionId)
|
|
264
|
+
return {
|
|
265
|
+
events,
|
|
266
|
+
hasPending: (log?.rows ?? []).some(
|
|
267
|
+
(row) => row.index > log!.clearIndex && row.processedAt === null,
|
|
268
|
+
),
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
/** The attempt-currency fence shared by handler appends and clears. */
|
|
273
|
+
const assertCurrentCause = (sessionId: string, cause: EventCause): void => {
|
|
274
|
+
if (cause.index <= clearIndexOf(sessionId)) {
|
|
275
|
+
throw supersededAttemptError(sessionId, cause)
|
|
276
|
+
}
|
|
277
|
+
const parent = find(sessionId, cause.index)
|
|
278
|
+
if (parent.attemptCount !== cause.attempt || parent.failedAt !== null) {
|
|
279
|
+
throw supersededAttemptError(sessionId, cause)
|
|
280
|
+
}
|
|
281
|
+
}
|
|
235
282
|
|
|
236
283
|
const claim = (
|
|
237
284
|
row: Row,
|
|
@@ -263,7 +310,7 @@ export function memory(options: MemoryStoreOptions = {}): A2Store {
|
|
|
263
310
|
)
|
|
264
311
|
}
|
|
265
312
|
const existing = supplied
|
|
266
|
-
.map((candidate) =>
|
|
313
|
+
.map((candidate) => ownerOf(sessionId, candidate.id))
|
|
267
314
|
.filter((row): row is Row => row !== undefined)
|
|
268
315
|
const foreign = existing.find((row) => row.sessionId !== sessionId)
|
|
269
316
|
if (foreign) {
|
|
@@ -281,8 +328,8 @@ export function memory(options: MemoryStoreOptions = {}): A2Store {
|
|
|
281
328
|
now: Date,
|
|
282
329
|
cause?: EventCause,
|
|
283
330
|
): Row[] => {
|
|
284
|
-
const
|
|
285
|
-
const base =
|
|
331
|
+
const log = logOf(sessionId)
|
|
332
|
+
const base = headOf(log)
|
|
286
333
|
const inserted = events.map((candidate, offset): Row => ({
|
|
287
334
|
id: candidate.id ?? generateId(),
|
|
288
335
|
type: candidate.type,
|
|
@@ -311,7 +358,7 @@ export function memory(options: MemoryStoreOptions = {}): A2Store {
|
|
|
311
358
|
failedAt: null,
|
|
312
359
|
}))
|
|
313
360
|
for (const row of inserted) {
|
|
314
|
-
if (
|
|
361
|
+
if (ownerOf(sessionId, row.id)) {
|
|
315
362
|
throw new A2Error(
|
|
316
363
|
'PARTIAL_DUPLICATE_BATCH',
|
|
317
364
|
`event id '${row.id}' already exists`,
|
|
@@ -319,13 +366,31 @@ export function memory(options: MemoryStoreOptions = {}): A2Store {
|
|
|
319
366
|
}
|
|
320
367
|
}
|
|
321
368
|
for (const row of inserted) {
|
|
322
|
-
rows.push(row)
|
|
369
|
+
log.rows.push(row)
|
|
323
370
|
byEventId.set(row.id, row)
|
|
324
371
|
enqueue(sessionId, row)
|
|
325
372
|
}
|
|
326
373
|
return inserted
|
|
327
374
|
}
|
|
328
375
|
|
|
376
|
+
/** Every cached fold of this session below its clear, head or historical. */
|
|
377
|
+
const dropCheckpointsBelow = (
|
|
378
|
+
sessionId: string,
|
|
379
|
+
clearIndex: number,
|
|
380
|
+
): void => {
|
|
381
|
+
const prefix = `${sessionId}\u0000`
|
|
382
|
+
for (const [key, snapshot] of snapshots) {
|
|
383
|
+
if (!key.startsWith(prefix) || snapshot.index >= clearIndex) continue
|
|
384
|
+
snapshots.delete(key)
|
|
385
|
+
snapshotPinCounts.delete(`${key}\u0000${snapshot.index}`)
|
|
386
|
+
}
|
|
387
|
+
for (const [key, snapshot] of historicalSnapshots) {
|
|
388
|
+
if (!key.startsWith(prefix) || snapshot.index >= clearIndex) continue
|
|
389
|
+
historicalSnapshots.delete(key)
|
|
390
|
+
snapshotPinCounts.delete(key)
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
|
|
329
394
|
const readStateOf = (
|
|
330
395
|
sessionId: string,
|
|
331
396
|
reducerName: string,
|
|
@@ -333,8 +398,10 @@ export function memory(options: MemoryStoreOptions = {}): A2Store {
|
|
|
333
398
|
snapshotThroughIndex?: number,
|
|
334
399
|
): StoreStateRead => {
|
|
335
400
|
const key = snapshotKey(sessionId, reducerName)
|
|
401
|
+
const clearIndex = clearIndexOf(sessionId)
|
|
336
402
|
const snapshotFrontier = snapshotThroughIndex ?? throughIndex
|
|
337
|
-
const
|
|
403
|
+
const stored = snapshots.get(key)
|
|
404
|
+
const head = stored && stored.index >= clearIndex ? stored : undefined
|
|
338
405
|
let candidate =
|
|
339
406
|
head && (snapshotFrontier === undefined || head.index <= snapshotFrontier)
|
|
340
407
|
? head
|
|
@@ -344,6 +411,7 @@ export function memory(options: MemoryStoreOptions = {}): A2Store {
|
|
|
344
411
|
for (const [historicalKey, snapshot] of historicalSnapshots) {
|
|
345
412
|
if (
|
|
346
413
|
historicalKey.startsWith(prefix) &&
|
|
414
|
+
snapshot.index >= clearIndex &&
|
|
347
415
|
snapshot.index <= snapshotFrontier &&
|
|
348
416
|
(!candidate || snapshot.index > candidate.index)
|
|
349
417
|
) {
|
|
@@ -354,22 +422,23 @@ export function memory(options: MemoryStoreOptions = {}): A2Store {
|
|
|
354
422
|
const snapshot = candidate
|
|
355
423
|
? { index: candidate.index, state: structuredClone(candidate.state) }
|
|
356
424
|
: null
|
|
357
|
-
const afterIndex = snapshot?.index ?? 0
|
|
425
|
+
const afterIndex = Math.max(snapshot?.index ?? 0, clearIndex - 1)
|
|
358
426
|
return {
|
|
359
427
|
headIndex: head?.index ?? null,
|
|
360
428
|
snapshot,
|
|
361
|
-
events: (sessions.get(sessionId) ?? [])
|
|
429
|
+
events: (sessions.get(sessionId)?.rows ?? [])
|
|
362
430
|
.filter(
|
|
363
431
|
(row) =>
|
|
364
432
|
row.index > afterIndex &&
|
|
365
433
|
(throughIndex === undefined || row.index <= throughIndex),
|
|
366
434
|
)
|
|
367
435
|
.map(toEvent),
|
|
436
|
+
...(clearIndex > 0 ? { clearIndex } : {}),
|
|
368
437
|
}
|
|
369
438
|
}
|
|
370
439
|
|
|
371
440
|
return {
|
|
372
|
-
async append(sessionId, events) {
|
|
441
|
+
async append(sessionId, events, appendOptions) {
|
|
373
442
|
if (events.length === 0) return appendResult(sessionId, [])
|
|
374
443
|
|
|
375
444
|
// Batch-scoped idempotency (a2-implementation.md §3): all ids
|
|
@@ -389,26 +458,16 @@ export function memory(options: MemoryStoreOptions = {}): A2Store {
|
|
|
389
458
|
)
|
|
390
459
|
}
|
|
391
460
|
|
|
461
|
+
const originIndex = appendOptions?.originIndex
|
|
462
|
+
if (originIndex !== undefined && originIndex <= clearIndexOf(sessionId)) {
|
|
463
|
+
throw sessionClearedError(
|
|
464
|
+
`scheduled work from event ${originIndex} was cleared in session '${sessionId}'`,
|
|
465
|
+
)
|
|
466
|
+
}
|
|
392
467
|
// Attempt-currency fence: a fresh handler append commits only while
|
|
393
468
|
// its causal attempt is still the parent's latest (store.ts `append`).
|
|
394
|
-
const rows = sessions.get(sessionId) ?? []
|
|
395
469
|
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
|
-
}
|
|
470
|
+
if (event.cause) assertCurrentCause(sessionId, event.cause)
|
|
412
471
|
}
|
|
413
472
|
|
|
414
473
|
const now = clock.now()
|
|
@@ -417,11 +476,67 @@ export function memory(options: MemoryStoreOptions = {}): A2Store {
|
|
|
417
476
|
return appendResult(sessionId, inserted.map(toStored))
|
|
418
477
|
},
|
|
419
478
|
|
|
479
|
+
async clear({ sessionId, ifLastIndex, cause }) {
|
|
480
|
+
if (cause) assertCurrentCause(sessionId, cause)
|
|
481
|
+
const log = sessions.get(sessionId)
|
|
482
|
+
const head = headOf(log)
|
|
483
|
+
const clearIndex = log?.clearIndex ?? 0
|
|
484
|
+
if (ifLastIndex !== undefined && clearIndex === ifLastIndex + 1) {
|
|
485
|
+
return { cleared: true, index: head, clearIndex }
|
|
486
|
+
}
|
|
487
|
+
if (ifLastIndex !== undefined && ifLastIndex !== head) {
|
|
488
|
+
return { cleared: false, index: head, clearIndex }
|
|
489
|
+
}
|
|
490
|
+
if (head === 0 || clearIndex === head) {
|
|
491
|
+
return { cleared: true, index: head, clearIndex }
|
|
492
|
+
}
|
|
493
|
+
const [marker] = insert(
|
|
494
|
+
sessionId,
|
|
495
|
+
[{ type: CLEAR_EVENT_TYPE, payload: null, settled: true }],
|
|
496
|
+
clock.now(),
|
|
497
|
+
)
|
|
498
|
+
logOf(sessionId).clearIndex = marker!.index
|
|
499
|
+
// Every row is at or below the marker, so no dispatch state survives.
|
|
500
|
+
dispatch.delete(sessionId)
|
|
501
|
+
notifyStreams(sessionId, [marker!])
|
|
502
|
+
return { cleared: true, index: marker!.index, clearIndex: marker!.index }
|
|
503
|
+
},
|
|
504
|
+
|
|
505
|
+
async reclaim(sessionId) {
|
|
506
|
+
const log = sessions.get(sessionId)
|
|
507
|
+
if (!log || log.offset >= log.clearIndex - 1) {
|
|
508
|
+
return { deleted: 0, done: true }
|
|
509
|
+
}
|
|
510
|
+
const removed = log.rows.splice(
|
|
511
|
+
0,
|
|
512
|
+
Math.min(RECLAIM_BATCH, log.clearIndex - 1 - log.offset),
|
|
513
|
+
)
|
|
514
|
+
log.offset += removed.length
|
|
515
|
+
for (const row of removed) {
|
|
516
|
+
// A reused id belongs to its new row now.
|
|
517
|
+
if (byEventId.get(row.id) === row) byEventId.delete(row.id)
|
|
518
|
+
releaseSnapshotPins(sessionId, row.index)
|
|
519
|
+
}
|
|
520
|
+
const done = log.offset === log.clearIndex - 1
|
|
521
|
+
// Writes below the marker are dropped, so one sweep at the end suffices.
|
|
522
|
+
if (done) dropCheckpointsBelow(sessionId, log.clearIndex)
|
|
523
|
+
return { deleted: removed.length, done }
|
|
524
|
+
},
|
|
525
|
+
|
|
420
526
|
async read(sessionId, opts) {
|
|
421
|
-
const
|
|
422
|
-
|
|
423
|
-
const
|
|
424
|
-
|
|
527
|
+
const log = sessions.get(sessionId)
|
|
528
|
+
if (!log) return []
|
|
529
|
+
const head = headOf(log)
|
|
530
|
+
const after = Math.max(
|
|
531
|
+
opts?.afterIndex ?? 0,
|
|
532
|
+
log.clearIndex - 1,
|
|
533
|
+
log.offset,
|
|
534
|
+
)
|
|
535
|
+
const through = Math.min(opts?.throughIndex ?? head, head)
|
|
536
|
+
if (through <= after) return []
|
|
537
|
+
return log.rows
|
|
538
|
+
.slice(after - log.offset, through - log.offset)
|
|
539
|
+
.map(toStored)
|
|
425
540
|
},
|
|
426
541
|
|
|
427
542
|
async claimAvailable({
|
|
@@ -453,21 +568,32 @@ export function memory(options: MemoryStoreOptions = {}): A2Store {
|
|
|
453
568
|
return retryAt ? { outcome: 'busy', retryAt } : { outcome: 'settled' }
|
|
454
569
|
}
|
|
455
570
|
eligible.sort((a, b) => a.index - b.index)
|
|
571
|
+
const clearIndex = clearIndexOf(sessionId)
|
|
456
572
|
return {
|
|
457
573
|
outcome: 'claimed',
|
|
458
|
-
events: eligible.map((row) =>
|
|
574
|
+
events: eligible.map((row) => {
|
|
575
|
+
const claimed = claim(row, holder, claimedAt, expiresAt)
|
|
576
|
+
if (clearIndex > 0) claimed.clearIndex = clearIndex
|
|
577
|
+
return claimed
|
|
578
|
+
}),
|
|
459
579
|
}
|
|
460
580
|
},
|
|
461
581
|
|
|
462
582
|
async renewClaims({ sessionId, holder, claims, ttlMs, expiresAtMs }) {
|
|
463
583
|
const now = clock.now()
|
|
464
584
|
const expiresAt = new Date(expiresAtMs ?? now.getTime() + ttlMs)
|
|
465
|
-
const
|
|
585
|
+
const clearIndex = clearIndexOf(sessionId)
|
|
466
586
|
const renewed: number[] = []
|
|
467
587
|
const superseded: number[] = []
|
|
468
|
-
for (const
|
|
469
|
-
|
|
470
|
-
|
|
588
|
+
for (const { index, attempt } of claims.toSorted(
|
|
589
|
+
(a, b) => a.index - b.index,
|
|
590
|
+
)) {
|
|
591
|
+
if (index <= clearIndex) {
|
|
592
|
+
superseded.push(index)
|
|
593
|
+
continue
|
|
594
|
+
}
|
|
595
|
+
const row = rowAt(sessionId, index)
|
|
596
|
+
if (!row) continue
|
|
471
597
|
if (row.attemptCount > attempt) {
|
|
472
598
|
superseded.push(row.index)
|
|
473
599
|
continue
|
|
@@ -487,6 +613,7 @@ export function memory(options: MemoryStoreOptions = {}): A2Store {
|
|
|
487
613
|
},
|
|
488
614
|
|
|
489
615
|
async completeAttempt({ sessionId, index, attempt, events }) {
|
|
616
|
+
if (index <= clearIndexOf(sessionId)) return { outcome: 'superseded' }
|
|
490
617
|
const parent = find(sessionId, index)
|
|
491
618
|
const cause = { index, attempt }
|
|
492
619
|
if (parent.processedAt !== null) {
|
|
@@ -553,6 +680,9 @@ export function memory(options: MemoryStoreOptions = {}): A2Store {
|
|
|
553
680
|
},
|
|
554
681
|
|
|
555
682
|
async failAttempt({ sessionId, index, attempt, error, maxFailures }) {
|
|
683
|
+
if (index <= clearIndexOf(sessionId)) {
|
|
684
|
+
return { outcome: 'superseded', failureCount: 0 }
|
|
685
|
+
}
|
|
556
686
|
const row = find(sessionId, index)
|
|
557
687
|
if (
|
|
558
688
|
row.processedAt ||
|
|
@@ -611,11 +741,18 @@ export function memory(options: MemoryStoreOptions = {}): A2Store {
|
|
|
611
741
|
|
|
612
742
|
async putSnapshots(sessionId, reducerName, writes) {
|
|
613
743
|
const key = snapshotKey(sessionId, reducerName)
|
|
744
|
+
const clearIndex = clearIndexOf(sessionId)
|
|
614
745
|
for (const write of writes.toSorted((a, b) => a.index - b.index)) {
|
|
746
|
+
if (write.index < clearIndex) continue
|
|
615
747
|
const checkpoint = checkpointKey(sessionId, reducerName, write.index)
|
|
616
748
|
for (const eventIndex of write.pinEventIndexes ?? []) {
|
|
617
|
-
const event =
|
|
618
|
-
if (
|
|
749
|
+
const event = rowAt(sessionId, eventIndex)
|
|
750
|
+
if (
|
|
751
|
+
!event ||
|
|
752
|
+
event.index <= clearIndex ||
|
|
753
|
+
event.processedAt !== null ||
|
|
754
|
+
event.failedAt !== null
|
|
755
|
+
) {
|
|
619
756
|
continue
|
|
620
757
|
}
|
|
621
758
|
const pinKey = eventPinKey(sessionId, eventIndex)
|
|
@@ -635,7 +772,11 @@ export function memory(options: MemoryStoreOptions = {}): A2Store {
|
|
|
635
772
|
|
|
636
773
|
const current = snapshots.get(key)
|
|
637
774
|
if (!current || write.index >= current.index) {
|
|
638
|
-
if (
|
|
775
|
+
if (
|
|
776
|
+
current &&
|
|
777
|
+
write.index > current.index &&
|
|
778
|
+
current.index >= clearIndex
|
|
779
|
+
) {
|
|
639
780
|
const currentCheckpoint = checkpointKey(
|
|
640
781
|
sessionId,
|
|
641
782
|
reducerName,
|
|
@@ -769,13 +910,11 @@ export function memory(options: MemoryStoreOptions = {}): A2Store {
|
|
|
769
910
|
const startAfter = opts?.startAfter ?? 0
|
|
770
911
|
return {
|
|
771
912
|
[Symbol.asyncIterator](): AsyncIterator<Event> {
|
|
772
|
-
let last = startAfter
|
|
773
|
-
const buffer: Row[] = []
|
|
913
|
+
let last = Math.max(startAfter, clearIndexOf(sessionId) - 1)
|
|
774
914
|
let wake: (() => void) | null = null
|
|
775
915
|
let closed = false
|
|
776
916
|
|
|
777
|
-
const onRow = (
|
|
778
|
-
buffer.push(row)
|
|
917
|
+
const onRow = (): void => {
|
|
779
918
|
wake?.()
|
|
780
919
|
}
|
|
781
920
|
let subs = streamSubscribers.get(sessionId)
|
|
@@ -784,37 +923,40 @@ export function memory(options: MemoryStoreOptions = {}): A2Store {
|
|
|
784
923
|
streamSubscribers.set(sessionId, subs)
|
|
785
924
|
}
|
|
786
925
|
subs.add(onRow)
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
buffer.unshift(...existing.filter((row) => row.index > startAfter))
|
|
791
|
-
|
|
792
|
-
const unsubscribe = (): void => {
|
|
926
|
+
const stop = (): void => {
|
|
927
|
+
if (closed) return
|
|
928
|
+
closed = true
|
|
793
929
|
subs.delete(onRow)
|
|
794
930
|
if (subs.size === 0) streamSubscribers.delete(sessionId)
|
|
931
|
+
wake?.()
|
|
795
932
|
}
|
|
796
933
|
|
|
797
934
|
return {
|
|
798
935
|
async next(): Promise<IteratorResult<Event>> {
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
936
|
+
try {
|
|
937
|
+
for (;;) {
|
|
938
|
+
if (closed) return { value: undefined, done: true }
|
|
939
|
+
const log = sessions.get(sessionId)
|
|
940
|
+
// Rows reclaimed behind a running stream are skipped.
|
|
941
|
+
const row = log?.rows[Math.max(0, last - log.offset)]
|
|
942
|
+
if (row) {
|
|
943
|
+
const value = toStored(row)
|
|
944
|
+
last = row.index
|
|
945
|
+
return { value, done: false }
|
|
946
|
+
}
|
|
947
|
+
// oxlint-disable-next-line no-await-in-loop -- wait-for-wake
|
|
948
|
+
await new Promise<void>((resolve) => {
|
|
949
|
+
wake = resolve
|
|
950
|
+
})
|
|
951
|
+
wake = null
|
|
805
952
|
}
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
wake = resolve
|
|
810
|
-
})
|
|
811
|
-
wake = null
|
|
953
|
+
} catch (error) {
|
|
954
|
+
stop()
|
|
955
|
+
throw error
|
|
812
956
|
}
|
|
813
957
|
},
|
|
814
958
|
async return(): Promise<IteratorResult<Event>> {
|
|
815
|
-
|
|
816
|
-
unsubscribe()
|
|
817
|
-
wake?.()
|
|
959
|
+
stop()
|
|
818
960
|
return { value: undefined, done: true }
|
|
819
961
|
},
|
|
820
962
|
}
|
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
|
},
|