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
package/src/presence-recovery.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { PresencePatch } from './contract.ts'
|
|
2
|
+
import { presenceTombstones } from './presence-tombstones.ts'
|
|
2
3
|
import type { PresenceRow } from './store.ts'
|
|
3
4
|
|
|
4
5
|
type Field = Pick<PresenceRow, 'participant' | 'field' | 'seen' | 'at'>
|
|
@@ -22,6 +23,7 @@ export function presenceRecovery({
|
|
|
22
23
|
update(patch: PresencePatch): void
|
|
23
24
|
} {
|
|
24
25
|
const fields = new Map<string, Field>()
|
|
26
|
+
const deleted = presenceTombstones()
|
|
25
27
|
const reading = new Set<Set<string>>()
|
|
26
28
|
let generation = 0
|
|
27
29
|
let latest: Promise<PresenceRow[]> | undefined
|
|
@@ -49,11 +51,14 @@ export function presenceRecovery({
|
|
|
49
51
|
const rows = await read()
|
|
50
52
|
if (!isActive()) return rows
|
|
51
53
|
if (run !== generation) return latest!
|
|
54
|
+
// A row older than its field's deletion must not resurrect it.
|
|
55
|
+
const current = rows.filter((row) => !deleted.outranks(row))
|
|
52
56
|
const live = new Set<string>()
|
|
53
|
-
for (const row of
|
|
57
|
+
for (const row of current) {
|
|
54
58
|
const id = key(row)
|
|
55
59
|
live.add(id)
|
|
56
60
|
if (changed.has(id)) continue
|
|
61
|
+
deleted.drop(row)
|
|
57
62
|
remember(row)
|
|
58
63
|
if (reconcile)
|
|
59
64
|
deliver({
|
|
@@ -68,6 +73,7 @@ export function presenceRecovery({
|
|
|
68
73
|
if (live.has(id) || changed.has(id) || fields.get(id) !== prior)
|
|
69
74
|
continue
|
|
70
75
|
fields.delete(id)
|
|
76
|
+
deleted.keep(prior)
|
|
71
77
|
deliver({
|
|
72
78
|
participant: prior.participant,
|
|
73
79
|
values: { [prior.field]: null },
|
|
@@ -76,7 +82,7 @@ export function presenceRecovery({
|
|
|
76
82
|
})
|
|
77
83
|
}
|
|
78
84
|
}
|
|
79
|
-
return
|
|
85
|
+
return current
|
|
80
86
|
} finally {
|
|
81
87
|
reading.delete(changed)
|
|
82
88
|
if (reading.size === 0) latest = undefined
|
|
@@ -102,18 +108,19 @@ export function presenceRecovery({
|
|
|
102
108
|
update(patch) {
|
|
103
109
|
for (const [field, value] of Object.entries(patch.values)) {
|
|
104
110
|
if (value === undefined) continue
|
|
105
|
-
const
|
|
111
|
+
const write = { participant: patch.participant, field, at: patch.at }
|
|
112
|
+
const id = key(write)
|
|
106
113
|
const previous = fields.get(id)
|
|
107
114
|
if (previous && previous.at.getTime() > patch.at.getTime()) continue
|
|
115
|
+
if (deleted.outranks(write)) continue
|
|
108
116
|
for (const changed of reading) changed.add(id)
|
|
109
|
-
if (value === null)
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
})
|
|
117
|
+
if (value === null) {
|
|
118
|
+
fields.delete(id)
|
|
119
|
+
deleted.keep(write)
|
|
120
|
+
} else {
|
|
121
|
+
deleted.drop(write)
|
|
122
|
+
remember({ ...write, seen: patch.seen })
|
|
123
|
+
}
|
|
117
124
|
}
|
|
118
125
|
},
|
|
119
126
|
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { PRESENCE_TIMINGS } from './internal.ts'
|
|
2
|
+
|
|
3
|
+
type PresenceField = { participant: string; field: string }
|
|
4
|
+
type PresenceWrite = PresenceField & { at: Date }
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* The deletion stamps a presence applier keeps per (participant, field),
|
|
8
|
+
* so a null keeps beating older sets that arrive after it. Each lives
|
|
9
|
+
* `PRESENCE_TIMINGS.tombstoneMs` of local receipt time and is pruned
|
|
10
|
+
* lazily on use, without timers.
|
|
11
|
+
*/
|
|
12
|
+
export type PresenceTombstones = {
|
|
13
|
+
/** Whether the field's deletion is strictly newer than the write. */
|
|
14
|
+
outranks(write: PresenceWrite): boolean
|
|
15
|
+
/** Keep the deletion; the caller has already ordered it past any prior one. */
|
|
16
|
+
keep(deletion: PresenceWrite): void
|
|
17
|
+
/** Forget the field's deletion once a write at or past it applies. */
|
|
18
|
+
drop(field: PresenceField): void
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const key = ({ participant, field }: PresenceField): string =>
|
|
22
|
+
JSON.stringify([participant, field])
|
|
23
|
+
|
|
24
|
+
export function presenceTombstones(): PresenceTombstones {
|
|
25
|
+
// Insertion order is receipt order, so pruning stops at the first
|
|
26
|
+
// tombstone still inside the window.
|
|
27
|
+
const stamps = new Map<string, { atMs: number; keptAtMs: number }>()
|
|
28
|
+
const prune = (): void => {
|
|
29
|
+
const cutoff = Date.now() - PRESENCE_TIMINGS.tombstoneMs
|
|
30
|
+
for (const [id, stamp] of stamps) {
|
|
31
|
+
if (stamp.keptAtMs > cutoff) return
|
|
32
|
+
stamps.delete(id)
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
return {
|
|
36
|
+
outranks(write) {
|
|
37
|
+
if (stamps.size === 0) return false
|
|
38
|
+
prune()
|
|
39
|
+
const stamp = stamps.get(key(write))
|
|
40
|
+
return stamp !== undefined && stamp.atMs > write.at.getTime()
|
|
41
|
+
},
|
|
42
|
+
keep(deletion) {
|
|
43
|
+
prune()
|
|
44
|
+
const id = key(deletion)
|
|
45
|
+
stamps.delete(id)
|
|
46
|
+
stamps.set(id, { atMs: deletion.at.getTime(), keptAtMs: Date.now() })
|
|
47
|
+
},
|
|
48
|
+
drop(field) {
|
|
49
|
+
if (stamps.size > 0) stamps.delete(key(field))
|
|
50
|
+
},
|
|
51
|
+
}
|
|
52
|
+
}
|
package/src/push-envelope.ts
CHANGED
|
@@ -77,6 +77,13 @@ export function parsePresenceSibling(
|
|
|
77
77
|
return out
|
|
78
78
|
}
|
|
79
79
|
|
|
80
|
+
/** The longest event id a push may carry: ids are indexed, and Postgres's index rows have a limit. */
|
|
81
|
+
const MAX_PUSHED_ID = 256
|
|
82
|
+
|
|
83
|
+
/** A UUIDv5, the shape of the ids A2 derives (deterministic-id.ts). */
|
|
84
|
+
const DERIVED_ID =
|
|
85
|
+
/^[0-9a-f]{8}-[0-9a-f]{4}-5[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i
|
|
86
|
+
|
|
80
87
|
/** The events half of the envelope, shared by the POST body and socket
|
|
81
88
|
* `push` frames so INVALID_PAYLOAD shapes have one implementation. */
|
|
82
89
|
export function parsePushEvents(events: unknown): ParsedPushEvent[] {
|
|
@@ -94,6 +101,19 @@ export function parsePushEvents(events: unknown): ParsedPushEvent[] {
|
|
|
94
101
|
if (id !== undefined && typeof id !== 'string') {
|
|
95
102
|
throw invalid(`events[${i}].id must be a string when present`)
|
|
96
103
|
}
|
|
104
|
+
if (id !== undefined && (id.length === 0 || id.length > MAX_PUSHED_ID)) {
|
|
105
|
+
throw invalid(
|
|
106
|
+
`events[${i}].id must be 1-${MAX_PUSHED_ID} characters when present`,
|
|
107
|
+
)
|
|
108
|
+
}
|
|
109
|
+
// A2 derives the ids of what consumers append, return, and schedule as
|
|
110
|
+
// UUIDv5s, from inputs a caller can know: one pushed first into the same
|
|
111
|
+
// session would take its place. Callers' own ids are v4 (crypto.randomUUID) or v8 (idempotentId).
|
|
112
|
+
if (id !== undefined && DERIVED_ID.test(id)) {
|
|
113
|
+
throw invalid(
|
|
114
|
+
`events[${i}].id is shaped like the ids A2 derives for consumers (a UUIDv5); use crypto.randomUUID() or idempotentId()`,
|
|
115
|
+
)
|
|
116
|
+
}
|
|
97
117
|
const out: { type: string; payload: unknown; id?: string } = {
|
|
98
118
|
type,
|
|
99
119
|
payload,
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `consumer()` for A2's own runtimes (actors, the AI runtime): their names
|
|
3
|
+
* sit under the reserved `a2.` prefix, and their child ids leave the name
|
|
4
|
+
* out, as the handler tables they replaced had. Not a public entry point.
|
|
5
|
+
*/
|
|
6
|
+
import { runtimeConsumerFactory } from './internal.ts'
|
|
7
|
+
import type { consumer } from './server.ts'
|
|
8
|
+
|
|
9
|
+
export const runtimeConsumer = ((options: never) =>
|
|
10
|
+
runtimeConsumerFactory.define!(options)) as typeof consumer
|
package/src/scheduler-task.ts
CHANGED
|
@@ -5,7 +5,7 @@ export type ScheduledEvent = {
|
|
|
5
5
|
payload: unknown
|
|
6
6
|
}
|
|
7
7
|
|
|
8
|
-
/** A durable wake-up for pending
|
|
8
|
+
/** A durable wake-up for pending consumer work. */
|
|
9
9
|
export type SchedulerDrainTask = {
|
|
10
10
|
version: 1
|
|
11
11
|
kind: 'drain'
|
|
@@ -26,7 +26,7 @@ export type SchedulerAppendTask = {
|
|
|
26
26
|
/** Earliest useful delivery as epoch milliseconds. */
|
|
27
27
|
dueAt: number
|
|
28
28
|
events: readonly ScheduledEvent[]
|
|
29
|
-
/** The scheduling
|
|
29
|
+
/** The scheduling consumer's event; delivery skips once it is cleared. */
|
|
30
30
|
originIndex?: number
|
|
31
31
|
}
|
|
32
32
|
|
package/src/scheduler-vercel.ts
CHANGED
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
* the configured deployment scope for sending and receiving.
|
|
15
15
|
* - `handler(...servers)` is the delivery route: look up the server for
|
|
16
16
|
* the contract named in the message and drain it. Settled drains ack;
|
|
17
|
-
* contention,
|
|
17
|
+
* contention, consumer failure, and a drain that throws after arming its
|
|
18
18
|
* successor all ack once a later watchdog is durable.
|
|
19
19
|
*
|
|
20
20
|
* `@vercel/queue` is an optional peer dependency, imported lazily. The
|
package/src/server.browser.ts
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* The browser build of experimental-a2/server. There isn't one — on purpose.
|
|
3
3
|
* Contracts and reducers (from `experimental-a2`) and the session client (from
|
|
4
4
|
* `experimental-a2/client` / `experimental-a2/react`) are the isomorphic surface; the server is
|
|
5
|
-
* where
|
|
5
|
+
* where consumers and storage live. This module existing in a client
|
|
6
6
|
* bundle means a `'use client'` file (or something it imports)
|
|
7
7
|
* value-imported your server module.
|
|
8
8
|
*/
|