experimental-a2 0.20.0 → 0.22.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 +55 -0
- package/README.md +1 -1
- package/dist/{actor-C7b-1arn.d.ts → actor-CqibLgjQ.d.ts} +15 -7
- package/dist/actor-CqibLgjQ.d.ts.map +1 -0
- package/dist/actor-client.d.ts +1 -1
- package/dist/actor-client.js +2 -2
- package/dist/actor-react.d.ts +3 -3
- package/dist/actor-react.js +5 -5
- package/dist/{actor-shared-t7w8xIKP.js → actor-shared-DFET5q16.js} +3 -3
- package/dist/actor-shared-DFET5q16.js.map +1 -0
- package/dist/actor.d.ts +1 -1
- package/dist/actor.js +54 -43
- package/dist/actor.js.map +1 -1
- package/dist/{ai-Cz3hqVhk.d.ts → ai-DAu77CDr.d.ts} +8 -8
- package/dist/{ai-Cz3hqVhk.d.ts.map → ai-DAu77CDr.d.ts.map} +1 -1
- package/dist/{ai-mMiWul1T.js → ai-EEfKyc-z.js} +9 -9
- package/dist/ai-EEfKyc-z.js.map +1 -0
- package/dist/ai-client.d.ts +2 -2
- package/dist/ai-client.js +2 -2
- package/dist/ai-fx.d.ts +4 -2
- package/dist/ai-fx.d.ts.map +1 -1
- package/dist/ai-fx.js +214 -67
- package/dist/ai-fx.js.map +1 -1
- package/dist/{ai-harness-CV6rXbB3.d.ts → ai-harness-DTjkAr2T.d.ts} +2 -2
- package/dist/{ai-harness-CV6rXbB3.d.ts.map → ai-harness-DTjkAr2T.d.ts.map} +1 -1
- package/dist/{ai-harness-Clbg5ei7.js → ai-harness-dQeJaR1t.js} +7 -3
- package/dist/ai-harness-dQeJaR1t.js.map +1 -0
- package/dist/ai-pi.d.ts +1 -1
- package/dist/ai-pi.js +3 -3
- package/dist/ai-pi.js.map +1 -1
- package/dist/ai-react.d.ts +5 -5
- package/dist/ai-react.js +2 -2
- package/dist/ai-server.d.ts +14 -17
- package/dist/ai-server.d.ts.map +1 -1
- package/dist/ai-server.js +86 -73
- package/dist/ai-server.js.map +1 -1
- package/dist/ai.d.ts +2 -2
- package/dist/ai.js +2 -2
- package/dist/{client-BhHLoNKF.js → client-BQS4i_V9.js} +29 -6
- package/dist/client-BQS4i_V9.js.map +1 -0
- package/dist/{client-h_DKFAjj.d.ts → client-CUMbS_38.d.ts} +3 -3
- package/dist/{client-h_DKFAjj.d.ts.map → client-CUMbS_38.d.ts.map} +1 -1
- package/dist/client.d.ts +3 -3
- package/dist/client.js +1 -1
- package/dist/{contract-BYKASlCV.js → contract-CWukt01d.js} +2 -2
- package/dist/contract-CWukt01d.js.map +1 -0
- package/dist/index.d.ts +5 -5
- package/dist/index.js +3 -3
- package/dist/{initial-snapshot-Bx-c0pDP.d.ts → initial-snapshot-D3Lve43o.d.ts} +4 -4
- package/dist/initial-snapshot-D3Lve43o.d.ts.map +1 -0
- package/dist/{internal-CscSY7K_.js → internal-CvsrprnU.js} +40 -16
- package/dist/internal-CvsrprnU.js.map +1 -0
- package/dist/otel.d.ts +1 -1
- package/dist/otel.js +1 -1
- package/dist/otel.js.map +1 -1
- package/dist/{react-BBXgHWFc.d.ts → react-BoHvC3v9.d.ts} +4 -4
- package/dist/{react-BBXgHWFc.d.ts.map → react-BoHvC3v9.d.ts.map} +1 -1
- package/dist/react.d.ts +1 -1
- package/dist/react.js +1 -1
- package/dist/{reducer-CqA59Q6U.d.ts → reducer-CYPHsSmd.d.ts} +2 -2
- package/dist/{reducer-CqA59Q6U.d.ts.map → reducer-CYPHsSmd.d.ts.map} +1 -1
- package/dist/{reducer-BAXc8JyF.js → reducer-CjmvZSAb.js} +2 -2
- package/dist/{reducer-BAXc8JyF.js.map → reducer-CjmvZSAb.js.map} +1 -1
- package/dist/runtime-consumer-CIeL33wj.js +12 -0
- package/dist/runtime-consumer-CIeL33wj.js.map +1 -0
- package/dist/scheduler-qstash.d.ts +2 -2
- package/dist/scheduler-qstash.js +3 -3
- package/dist/scheduler-task-X1SToDjP.js.map +1 -1
- package/dist/scheduler-vercel.d.ts +2 -2
- package/dist/scheduler-vercel.js +3 -3
- package/dist/scheduler-vercel.js.map +1 -1
- package/dist/{server-qyb5aJJG.js → server-DtFrWxi3.js} +412 -116
- package/dist/server-DtFrWxi3.js.map +1 -0
- package/dist/{server-DYAATFuT.d.ts → server-qgez0Xny.d.ts} +108 -41
- package/dist/server-qgez0Xny.d.ts.map +1 -0
- package/dist/server.browser.js +1 -1
- package/dist/server.browser.js.map +1 -1
- package/dist/server.d.ts +4 -4
- package/dist/server.js +2 -2
- package/dist/{session-clear-C-QwTXV1.d.ts → session-clear-D1PFe7xl.d.ts} +2 -2
- package/dist/session-clear-D1PFe7xl.d.ts.map +1 -0
- package/dist/{session-clear-DGI4jTQ5.js → session-clear-wHd5GCpZ.js} +5 -4
- package/dist/session-clear-wHd5GCpZ.js.map +1 -0
- package/dist/store-D0MhBvg6.d.ts +532 -0
- package/dist/store-D0MhBvg6.d.ts.map +1 -0
- package/dist/store-N8PXxDAS.js.map +1 -1
- package/dist/store-codec-DwrWlgQx.js +65 -0
- package/dist/store-codec-DwrWlgQx.js.map +1 -0
- package/dist/store-memory.d.ts +1 -1
- package/dist/store-memory.d.ts.map +1 -1
- package/dist/store-memory.js +406 -225
- package/dist/store-memory.js.map +1 -1
- package/dist/{store-polling-7WJV0YWP.js → store-polling-BZt2Rf3D.js} +2 -2
- package/dist/{store-polling-7WJV0YWP.js.map → store-polling-BZt2Rf3D.js.map} +1 -1
- package/dist/store-postgres.d.ts +1 -1
- package/dist/store-postgres.d.ts.map +1 -1
- package/dist/store-postgres.js +1598 -587
- package/dist/store-postgres.js.map +1 -1
- package/dist/store-presence-polling-JuAy2Ck2.js +111 -0
- package/dist/store-presence-polling-JuAy2Ck2.js.map +1 -0
- package/dist/store-redis-http.d.ts +1 -1
- package/dist/store-redis-http.js +4 -4
- package/dist/store-redis-notify-CsJw_hfD.js +3060 -0
- package/dist/store-redis-notify-CsJw_hfD.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 +4 -5
- 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 +527 -299
- package/dist/store-sqlite.js.map +1 -1
- package/dist/stream-buffer-DrTyiIuo.js +57 -0
- package/dist/stream-buffer-DrTyiIuo.js.map +1 -0
- package/dist/{telemetry-Cyfqo6Qt.d.ts → telemetry-CWwsh-Gx.d.ts} +3 -3
- package/dist/telemetry-CWwsh-Gx.d.ts.map +1 -0
- package/dist/{wire-BKxH1vbP.js → wire-CV-Z7wz-.js} +6 -5
- package/dist/wire-CV-Z7wz-.js.map +1 -0
- package/docs/01-quickstart.mdx +21 -15
- package/docs/02-examples.mdx +1 -1
- package/docs/actors/01-introduction.mdx +13 -2
- package/docs/actors/02-concurrency.mdx +1 -1
- package/docs/agents/01-introduction.mdx +1 -1
- package/docs/agents/02-tools.mdx +12 -11
- package/docs/agents/03-conversations.mdx +36 -25
- package/docs/agents/04-harnesses.mdx +14 -7
- package/docs/client/03-presence.mdx +18 -14
- package/docs/contract/01-events.mdx +2 -2
- package/docs/contract/02-state.mdx +18 -14
- package/docs/contract/03-sessions.mdx +32 -25
- package/docs/index.mdx +29 -24
- package/docs/reference/01-core.mdx +7 -6
- package/docs/reference/02-server.mdx +250 -191
- package/docs/reference/04-ai.mdx +33 -23
- package/docs/reference/05-telemetry.mdx +27 -13
- package/docs/reference/06-errors.mdx +25 -19
- package/docs/reference/meta.ts +1 -1
- package/docs/schedulers/01-overview.mdx +8 -8
- package/docs/schedulers/02-vercel-queues.mdx +2 -2
- package/docs/schedulers/03-qstash.mdx +4 -4
- package/docs/server/01-consumers.mdx +418 -0
- package/docs/server/02-timers.mdx +31 -21
- package/docs/server/03-cancellation.mdx +52 -44
- package/docs/server/04-routes.mdx +4 -1
- package/docs/server/05-sql-projections.mdx +391 -0
- package/docs/server/06-guarantees.mdx +61 -51
- package/docs/stores/01-overview.mdx +3 -3
- package/docs/stores/02-redis.mdx +24 -10
- package/docs/stores/03-redis-http.mdx +8 -6
- package/docs/stores/04-postgres.mdx +65 -14
- package/docs/stores/05-sqlite.mdx +5 -3
- package/docs/upgrades/01-overview.mdx +31 -0
- package/docs/upgrades/78-0-22.mdx +115 -0
- package/docs/upgrades/79-0-21.mdx +165 -0
- package/docs/upgrades/80-0-20.mdx +43 -0
- package/docs/upgrades/81-0-19.mdx +33 -0
- package/docs/upgrades/82-0-18.mdx +33 -0
- package/docs/upgrades/83-0-17.mdx +69 -0
- package/docs/upgrades/meta.ts +3 -0
- package/examples/playground/DEPLOY.md +39 -7
- package/examples/playground/app/a0/chat.test.tsx +2 -2
- package/examples/playground/app/a0/sandbox-log.ts +17 -15
- package/examples/playground/app/a0/sandbox.ts +58 -13
- package/examples/playground/app/a0/server.ts +4 -4
- package/examples/playground/app/a2slack/a2slack.css +1067 -0
- package/examples/playground/app/a2slack/c/[channel]/page.tsx +20 -0
- package/examples/playground/app/a2slack/consumers/agent.ts +63 -0
- package/examples/playground/app/a2slack/consumers/feed.ts +164 -0
- package/examples/playground/app/a2slack/consumers/notify.ts +116 -0
- package/examples/playground/app/a2slack/consumers/search.ts +37 -0
- package/examples/playground/app/a2slack/consumers/triage.ts +31 -0
- package/examples/playground/app/a2slack/consumers/welcome.ts +25 -0
- package/examples/playground/app/a2slack/db/a2.ts +209 -0
- package/examples/playground/app/a2slack/db/index.ts +73 -0
- package/examples/playground/app/a2slack/db/migrations/0000_extensions.sql +4 -0
- package/examples/playground/app/a2slack/db/migrations/0001_workspace.sql +74 -0
- package/examples/playground/app/a2slack/db/migrations/0002_a2.sql +215 -0
- package/examples/playground/app/a2slack/db/migrations/0003_a2_batch.sql +67 -0
- package/examples/playground/app/a2slack/db/migrations/0004_a2_session_event_ids.sql +2 -0
- package/examples/playground/app/a2slack/db/migrations/meta/0000_snapshot.json +18 -0
- package/examples/playground/app/a2slack/db/migrations/meta/0001_snapshot.json +500 -0
- package/examples/playground/app/a2slack/db/migrations/meta/0002_snapshot.json +1159 -0
- package/examples/playground/app/a2slack/db/migrations/meta/0003_snapshot.json +1159 -0
- package/examples/playground/app/a2slack/db/migrations/meta/0004_snapshot.json +1165 -0
- package/examples/playground/app/a2slack/db/migrations/meta/_journal.json +41 -0
- package/examples/playground/app/a2slack/db/queries.ts +263 -0
- package/examples/playground/app/a2slack/db/schema.ts +145 -0
- package/examples/playground/app/a2slack/drills/a2slack.drill.ts +708 -0
- package/examples/playground/app/a2slack/drills/harness.ts +244 -0
- package/examples/playground/app/a2slack/events/route.ts +36 -0
- package/examples/playground/app/a2slack/identity.ts +44 -0
- package/examples/playground/app/a2slack/inbox/route.ts +8 -0
- package/examples/playground/app/a2slack/lag/data/route.ts +8 -0
- package/examples/playground/app/a2slack/lag/page.tsx +101 -0
- package/examples/playground/app/a2slack/lag.ts +73 -0
- package/examples/playground/app/a2slack/layout.tsx +43 -0
- package/examples/playground/app/a2slack/messages/route.ts +17 -0
- package/examples/playground/app/a2slack/model.test.ts +208 -0
- package/examples/playground/app/a2slack/model.ts +369 -0
- package/examples/playground/app/a2slack/models.test.ts +72 -0
- package/examples/playground/app/a2slack/page.tsx +10 -0
- package/examples/playground/app/a2slack/scheduler/route.ts +5 -0
- package/examples/playground/app/a2slack/search/route.ts +40 -0
- package/examples/playground/app/a2slack/search-index.ts +108 -0
- package/examples/playground/app/a2slack/server.test.ts +315 -0
- package/examples/playground/app/a2slack/server.ts +55 -0
- package/examples/playground/app/a2slack/sessions.ts +50 -0
- package/examples/playground/app/a2slack/thread/route.ts +8 -0
- package/examples/playground/app/a2slack/ui/ChannelView.tsx +87 -0
- package/examples/playground/app/a2slack/ui/Composer.tsx +117 -0
- package/examples/playground/app/a2slack/ui/DotsShadow.tsx +42 -0
- package/examples/playground/app/a2slack/ui/Feed.tsx +157 -0
- package/examples/playground/app/a2slack/ui/InboxContext.tsx +63 -0
- package/examples/playground/app/a2slack/ui/InboxView.tsx +82 -0
- package/examples/playground/app/a2slack/ui/Menu.tsx +128 -0
- package/examples/playground/app/a2slack/ui/MessageRow.tsx +314 -0
- package/examples/playground/app/a2slack/ui/NameGate.tsx +35 -0
- package/examples/playground/app/a2slack/ui/SearchContext.tsx +146 -0
- package/examples/playground/app/a2slack/ui/SearchResults.tsx +97 -0
- package/examples/playground/app/a2slack/ui/Shell.tsx +34 -0
- package/examples/playground/app/a2slack/ui/Sidebar.tsx +199 -0
- package/examples/playground/app/a2slack/ui/ThreadPanel.tsx +134 -0
- package/examples/playground/app/a2slack/ui/useInbox.ts +71 -0
- package/examples/playground/app/agent/server.ts +3 -3
- package/examples/playground/app/chat/[chatId]/chat-client.tsx +1 -1
- package/examples/playground/app/chat/server.ts +45 -35
- package/examples/playground/app/components/demo-icons.tsx +7 -0
- package/examples/playground/app/components/demos.tsx +7 -0
- package/examples/playground/app/orders/[orderId]/order-client.tsx +2 -2
- package/examples/playground/app/orders/page.tsx +1 -1
- package/examples/playground/app/orders/server.ts +49 -30
- package/examples/playground/app/recovery/[recoveryId]/recovery-client.tsx +14 -15
- package/examples/playground/app/recovery/page.tsx +4 -4
- package/examples/playground/app/recovery/server.ts +23 -32
- package/examples/playground/drizzle.config.ts +14 -0
- package/examples/playground/lib/store.ts +1 -1
- package/examples/playground/next.config.ts +6 -1
- package/examples/playground/package.json +12 -1
- package/examples/playground/scripts/migrate.mjs +31 -0
- package/examples/playground/vercel.json +9 -0
- package/examples/playground/vitest.drills.config.ts +32 -0
- package/package.json +5 -2
- package/sql/postgres/drizzle-schema.ts +209 -0
- package/sql/postgres/schema.sql +227 -67
- package/src/actor-shared.ts +10 -2
- package/src/actor.ts +101 -71
- package/src/ai-agent.ts +162 -111
- package/src/ai-control-server.ts +8 -8
- package/src/ai-fx.ts +324 -82
- package/src/ai-harness.ts +17 -2
- package/src/ai-pi.ts +2 -2
- package/src/ai-server.ts +2 -2
- package/src/ai.ts +7 -7
- package/src/client.ts +19 -2
- package/src/contract.ts +1 -1
- package/src/deterministic-id.ts +27 -4
- package/src/errors.ts +8 -5
- package/src/idempotent-replay.ts +0 -1
- package/src/index.ts +1 -1
- package/src/internal.ts +57 -16
- package/src/otel.ts +1 -1
- package/src/postgres-schema.ts +98 -57
- package/src/presence-recovery.ts +18 -11
- package/src/presence-tombstones.ts +52 -0
- package/src/push-envelope.ts +20 -0
- package/src/runtime-consumer.ts +10 -0
- package/src/scheduler-task.ts +2 -2
- package/src/scheduler-vercel.ts +1 -1
- package/src/server.browser.ts +1 -1
- package/src/server.ts +819 -218
- package/src/session-socket.ts +1 -1
- package/src/store-codec.ts +66 -0
- package/src/store-memory.ts +604 -286
- package/src/store-postgres.ts +2006 -857
- package/src/store-presence-polling.ts +59 -34
- package/src/store-redis-core.ts +1961 -1333
- package/src/store-redis-notify.ts +76 -26
- package/src/store-redis.ts +2 -3
- package/src/store-sqlite.ts +915 -516
- package/src/store.ts +241 -102
- package/src/telemetry.ts +2 -1
- package/src/wire.ts +1 -0
- package/dist/actor-C7b-1arn.d.ts.map +0 -1
- package/dist/actor-shared-t7w8xIKP.js.map +0 -1
- package/dist/ai-harness-Clbg5ei7.js.map +0 -1
- package/dist/ai-mMiWul1T.js.map +0 -1
- package/dist/client-BhHLoNKF.js.map +0 -1
- package/dist/contract-BYKASlCV.js.map +0 -1
- package/dist/idempotent-replay-JdgMQRRm.js +0 -21
- package/dist/idempotent-replay-JdgMQRRm.js.map +0 -1
- package/dist/initial-snapshot-Bx-c0pDP.d.ts.map +0 -1
- package/dist/internal-CscSY7K_.js.map +0 -1
- package/dist/server-DYAATFuT.d.ts.map +0 -1
- package/dist/server-qyb5aJJG.js.map +0 -1
- package/dist/session-clear-C-QwTXV1.d.ts.map +0 -1
- package/dist/session-clear-DGI4jTQ5.js.map +0 -1
- package/dist/store-CyLUy86v.d.ts +0 -404
- package/dist/store-CyLUy86v.d.ts.map +0 -1
- package/dist/store-codec-DTG0Ftek.js +0 -10
- package/dist/store-codec-DTG0Ftek.js.map +0 -1
- package/dist/store-presence-polling-B1ixeqIS.js +0 -94
- package/dist/store-presence-polling-B1ixeqIS.js.map +0 -1
- package/dist/store-redis-notify-CM0AfLY0.js +0 -2544
- package/dist/store-redis-notify-CM0AfLY0.js.map +0 -1
- package/dist/stream-buffer-Cx7AAPGq.js +0 -23
- package/dist/stream-buffer-Cx7AAPGq.js.map +0 -1
- package/dist/telemetry-Cyfqo6Qt.d.ts.map +0 -1
- package/dist/wire-BKxH1vbP.js.map +0 -1
- package/docs/server/01-handlers.mdx +0 -227
|
@@ -53,6 +53,9 @@ type Feed = {
|
|
|
53
53
|
inbox: string[]
|
|
54
54
|
bytes: number
|
|
55
55
|
dirty: boolean
|
|
56
|
+
/** Wake-ups past a gap, waiting up to NOTIFY_TIMINGS.reorderMs for it. */
|
|
57
|
+
held: Array<{ wake: Wake; bytes: number }>
|
|
58
|
+
gapSince: number | undefined
|
|
56
59
|
wake: (() => void) | null
|
|
57
60
|
stopRead: () => Promise<void>
|
|
58
61
|
}
|
|
@@ -61,15 +64,14 @@ type Feed = {
|
|
|
61
64
|
const INBOX_BYTES = 512 * 1024
|
|
62
65
|
|
|
63
66
|
type WakeEntry = [index: number, id: string, type: string, payload: unknown]
|
|
67
|
+
type Wake = { frontier: number; createdAt: number; entries: WakeEntry[] }
|
|
64
68
|
|
|
65
69
|
/**
|
|
66
70
|
* Parses a wake-up message: `{"f":frontier}` or a bare frontier number
|
|
67
71
|
* announces events without carrying them; `{"f","t","e"}` carries them.
|
|
68
72
|
* Returns null for anything else, which callers treat as a hint to catch up.
|
|
69
73
|
*/
|
|
70
|
-
const parseWake = (
|
|
71
|
-
message: string,
|
|
72
|
-
): { frontier: number; createdAt: number; entries: WakeEntry[] } | null => {
|
|
74
|
+
const parseWake = (message: string): Wake | null => {
|
|
73
75
|
let parsed: unknown
|
|
74
76
|
try {
|
|
75
77
|
parsed = JSON.parse(message)
|
|
@@ -187,6 +189,8 @@ export function createRedisNotifyStore(options: {
|
|
|
187
189
|
feed.bytes += message.length
|
|
188
190
|
} else {
|
|
189
191
|
feed.inbox.length = 0
|
|
192
|
+
feed.held.length = 0
|
|
193
|
+
feed.gapSince = undefined
|
|
190
194
|
feed.bytes = 0
|
|
191
195
|
feed.dirty = true
|
|
192
196
|
}
|
|
@@ -282,6 +286,7 @@ export function createRedisNotifyStore(options: {
|
|
|
282
286
|
|
|
283
287
|
const store: A2Store & { close(): Promise<void> } = {
|
|
284
288
|
append: core.append,
|
|
289
|
+
cursors: core.cursors,
|
|
285
290
|
read: core.read,
|
|
286
291
|
readHistory: core.readHistory!,
|
|
287
292
|
claimAvailable: core.claimAvailable,
|
|
@@ -294,11 +299,13 @@ export function createRedisNotifyStore(options: {
|
|
|
294
299
|
reclaim: core.reclaim!,
|
|
295
300
|
deadLetters: core.deadLetters!,
|
|
296
301
|
redrive: core.redrive!,
|
|
302
|
+
wakeLog: core.wakeLog,
|
|
297
303
|
|
|
298
304
|
presence: {
|
|
299
305
|
async set(ns, participant, values, meta) {
|
|
300
|
-
const
|
|
301
|
-
if (!
|
|
306
|
+
const applied = await core.presence.set(ns, participant, values, meta)
|
|
307
|
+
if (!applied) return
|
|
308
|
+
const { patch } = applied
|
|
302
309
|
const channel = presenceChannel(ns)
|
|
303
310
|
const subscription = presenceSubscriptions.get(channel)
|
|
304
311
|
if (subscription) {
|
|
@@ -306,13 +313,16 @@ export function createRedisNotifyStore(options: {
|
|
|
306
313
|
subscription.recovery?.update(patch)
|
|
307
314
|
for (const listener of subscription.listeners) listener(patch)
|
|
308
315
|
}
|
|
316
|
+
await applied.published
|
|
309
317
|
},
|
|
310
318
|
|
|
311
319
|
async read(ns) {
|
|
312
320
|
const channel = presenceChannel(ns)
|
|
313
321
|
await subscriptions.get(channel)?.ready
|
|
322
|
+
const subscription = presenceSubscriptions.get(channel)
|
|
314
323
|
return (
|
|
315
|
-
|
|
324
|
+
subscription?.recovery?.read() ??
|
|
325
|
+
subscription?.poll?.snapshot(() => core.presence.read(ns)) ??
|
|
316
326
|
core.presence.read(ns)
|
|
317
327
|
)
|
|
318
328
|
},
|
|
@@ -415,6 +425,8 @@ export function createRedisNotifyStore(options: {
|
|
|
415
425
|
inbox: [],
|
|
416
426
|
bytes: 0,
|
|
417
427
|
dirty: true,
|
|
428
|
+
held: [],
|
|
429
|
+
gapSince: undefined,
|
|
418
430
|
wake: null,
|
|
419
431
|
stopRead,
|
|
420
432
|
}
|
|
@@ -439,36 +451,51 @@ export function createRedisNotifyStore(options: {
|
|
|
439
451
|
|
|
440
452
|
/**
|
|
441
453
|
* Moves carried events that continue this feed's sequence into
|
|
442
|
-
* the buffer.
|
|
443
|
-
*
|
|
454
|
+
* the buffer. Writers publish after their scripts, so concurrent
|
|
455
|
+
* writers' wake-ups can arrive out of order: they apply in index
|
|
456
|
+
* order, and one past a gap is held until the gap fills or
|
|
457
|
+
* NOTIFY_TIMINGS.reorderMs passes. A message without events past
|
|
458
|
+
* the buffer, or an unreadable one, marks the feed dirty so it
|
|
459
|
+
* catches up with XRANGE.
|
|
444
460
|
*/
|
|
445
461
|
const applyInbox = (): void => {
|
|
446
|
-
|
|
462
|
+
const carried = feed.held.splice(0)
|
|
463
|
+
let announced = 0
|
|
447
464
|
for (const message of feed.inbox.splice(0)) {
|
|
448
465
|
const wake = parseWake(message)
|
|
449
|
-
if (!wake)
|
|
450
|
-
|
|
466
|
+
if (!wake) feed.dirty = true
|
|
467
|
+
else if (wake.entries.length === 0)
|
|
468
|
+
announced = Math.max(announced, wake.frontier)
|
|
469
|
+
else carried.push({ wake, bytes: message.length })
|
|
470
|
+
}
|
|
471
|
+
carried.sort(
|
|
472
|
+
(a, b) => a.wake.entries[0]![0] - b.wake.entries[0]![0],
|
|
473
|
+
)
|
|
474
|
+
feed.bytes = 0
|
|
475
|
+
for (const held of carried) {
|
|
476
|
+
if (
|
|
477
|
+
held.wake.entries[0]![0] >
|
|
478
|
+
(buffer.at(-1)?.index ?? last) + 1
|
|
479
|
+
) {
|
|
480
|
+
feed.held.push(held)
|
|
481
|
+
feed.bytes += held.bytes
|
|
451
482
|
continue
|
|
452
483
|
}
|
|
453
|
-
for (const [index, id, type, payload] of wake.entries) {
|
|
454
|
-
|
|
455
|
-
if (index < expected) continue
|
|
456
|
-
if (index > expected) {
|
|
457
|
-
feed.dirty = true
|
|
458
|
-
break
|
|
459
|
-
}
|
|
484
|
+
for (const [index, id, type, payload] of held.wake.entries) {
|
|
485
|
+
if (index <= (buffer.at(-1)?.index ?? last)) continue
|
|
460
486
|
buffer.push({
|
|
461
487
|
id,
|
|
462
488
|
type,
|
|
463
489
|
payload,
|
|
464
490
|
index,
|
|
465
491
|
sessionId,
|
|
466
|
-
createdAt: new Date(wake.createdAt),
|
|
492
|
+
createdAt: new Date(held.wake.createdAt),
|
|
467
493
|
})
|
|
468
494
|
}
|
|
469
|
-
if (wake.frontier > (buffer.at(-1)?.index ?? last))
|
|
470
|
-
feed.dirty = true
|
|
471
495
|
}
|
|
496
|
+
if (announced > (buffer.at(-1)?.index ?? last)) feed.dirty = true
|
|
497
|
+
if (feed.held.length === 0) feed.gapSince = undefined
|
|
498
|
+
else feed.gapSince ??= Date.now()
|
|
472
499
|
}
|
|
473
500
|
|
|
474
501
|
const iterator: AsyncIterator<Event> = {
|
|
@@ -515,8 +542,14 @@ export function createRedisNotifyStore(options: {
|
|
|
515
542
|
feeds.set(channel, set)
|
|
516
543
|
}
|
|
517
544
|
set.add(feed)
|
|
545
|
+
// A feed starts at the latest clear at the earliest:
|
|
546
|
+
// entries below it await reclaim.
|
|
518
547
|
// oxlint-disable-next-line no-await-in-loop -- one-time setup
|
|
519
|
-
await
|
|
548
|
+
const [, clearIndex] = await Promise.all([
|
|
549
|
+
acquireChannel(channel),
|
|
550
|
+
core.clearIndex(sessionId),
|
|
551
|
+
])
|
|
552
|
+
if (clearIndex - 1 > last) last = clearIndex - 1
|
|
520
553
|
if (closed || severed) continue
|
|
521
554
|
}
|
|
522
555
|
if (options.polling?.()) {
|
|
@@ -552,8 +585,16 @@ export function createRedisNotifyStore(options: {
|
|
|
552
585
|
}
|
|
553
586
|
applyInbox()
|
|
554
587
|
if (buffer.length > 0) continue
|
|
588
|
+
if (
|
|
589
|
+
feed.gapSince !== undefined &&
|
|
590
|
+
Date.now() - feed.gapSince >= NOTIFY_TIMINGS.reorderMs
|
|
591
|
+
)
|
|
592
|
+
feed.dirty = true
|
|
555
593
|
if (feed.dirty) {
|
|
556
594
|
feed.dirty = false
|
|
595
|
+
// A read covers whatever the held wake-ups carry.
|
|
596
|
+
feed.held.length = 0
|
|
597
|
+
feed.gapSince = undefined
|
|
557
598
|
if (core.streamEvents) {
|
|
558
599
|
// oxlint-disable-next-line no-await-in-loop -- begin an incremental catch-up
|
|
559
600
|
const result = await nextRead(last)
|
|
@@ -566,12 +607,21 @@ export function createRedisNotifyStore(options: {
|
|
|
566
607
|
if (buffer.length >= FEED_PAGE_SIZE) feed.dirty = true
|
|
567
608
|
continue
|
|
568
609
|
}
|
|
569
|
-
// Caught up: park until a message, a reconnect,
|
|
570
|
-
// safety read that heals a lost message.
|
|
610
|
+
// Caught up: park until a message, a reconnect, the end of
|
|
611
|
+
// a held gap, or the safety read that heals a lost message.
|
|
571
612
|
const woken = new Promise<'wake'>((resolve) => {
|
|
572
613
|
feed.wake = () => resolve('wake')
|
|
573
614
|
})
|
|
574
|
-
|
|
615
|
+
const holding = feed.gapSince !== undefined
|
|
616
|
+
pending = defaultSleep(
|
|
617
|
+
holding
|
|
618
|
+
? Math.max(
|
|
619
|
+
0,
|
|
620
|
+
NOTIFY_TIMINGS.reorderMs -
|
|
621
|
+
(Date.now() - feed.gapSince!),
|
|
622
|
+
)
|
|
623
|
+
: NOTIFY_TIMINGS.safetyReadMs,
|
|
624
|
+
)
|
|
575
625
|
// oxlint-disable-next-line no-await-in-loop -- live-feed loop
|
|
576
626
|
const outcome = await Promise.race([
|
|
577
627
|
woken,
|
|
@@ -579,7 +629,7 @@ export function createRedisNotifyStore(options: {
|
|
|
579
629
|
])
|
|
580
630
|
pending.cancel()
|
|
581
631
|
pending = null
|
|
582
|
-
if (outcome === 'timer') feed.dirty = true
|
|
632
|
+
if (outcome === 'timer' && !holding) feed.dirty = true
|
|
583
633
|
} catch (err) {
|
|
584
634
|
release()
|
|
585
635
|
if (closed) return { value: undefined, done: true }
|
package/src/store-redis.ts
CHANGED
|
@@ -15,9 +15,8 @@
|
|
|
15
15
|
*
|
|
16
16
|
* Works with any Redis-protocol server on a single instance or a
|
|
17
17
|
* non-cluster provider (Upstash — durable by default — Redis, Valkey).
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
* `call`/`duplicate`/`on`/`disconnect`.
|
|
18
|
+
* `ioredis` is an optional peer dependency; pass `url`, or inject any client
|
|
19
|
+
* exposing `call`/`duplicate`/`on`/`disconnect`.
|
|
21
20
|
*/
|
|
22
21
|
|
|
23
22
|
import { A2Error } from './errors.ts'
|