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
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import 'server-only'
|
|
2
2
|
import { vercelQueues } from 'experimental-a2/scheduler-vercel'
|
|
3
|
-
import { createServer } from 'experimental-a2/server'
|
|
3
|
+
import { consumer, createServer } from 'experimental-a2/server'
|
|
4
4
|
import { store } from '@/lib/store'
|
|
5
5
|
import { orders } from './model'
|
|
6
6
|
|
|
@@ -14,37 +14,56 @@ export const ordersScheduler =
|
|
|
14
14
|
? vercelQueues({ topic: 'a2-orders' })
|
|
15
15
|
: undefined
|
|
16
16
|
|
|
17
|
+
const notifyShop = consumer({
|
|
18
|
+
contract: orders,
|
|
19
|
+
name: 'notify-shop',
|
|
20
|
+
types: ['created'],
|
|
21
|
+
}).run(async ({ session }) => {
|
|
22
|
+
await wait(800)
|
|
23
|
+
await session.append('notify-shop', {
|
|
24
|
+
type: 'shop.notified',
|
|
25
|
+
payload: {},
|
|
26
|
+
})
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
const notifyDriver = consumer({
|
|
30
|
+
contract: orders,
|
|
31
|
+
name: 'notify-driver',
|
|
32
|
+
types: ['shop.started'],
|
|
33
|
+
}).run(async ({ session }) => {
|
|
34
|
+
await wait(1200)
|
|
35
|
+
const driverId = drivers[Math.floor(Math.random() * drivers.length)]!
|
|
36
|
+
await session.append('notify-driver', {
|
|
37
|
+
type: 'driver.notified',
|
|
38
|
+
payload: { driverId },
|
|
39
|
+
})
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
const pickUp = consumer({
|
|
43
|
+
contract: orders,
|
|
44
|
+
name: 'pick-up',
|
|
45
|
+
types: ['driver.notified'],
|
|
46
|
+
}).run(async ({ session }) => {
|
|
47
|
+
await wait(3000)
|
|
48
|
+
const etaMinutes = 3 + Math.floor(Math.random() * 5)
|
|
49
|
+
await session.append('courier-picked-up', {
|
|
50
|
+
type: 'courier.picked_up',
|
|
51
|
+
payload: { etaMinutes },
|
|
52
|
+
})
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
const deliver = consumer({
|
|
56
|
+
contract: orders,
|
|
57
|
+
name: 'deliver',
|
|
58
|
+
types: ['courier.picked_up'],
|
|
59
|
+
}).run(async ({ session }) => {
|
|
60
|
+
await wait(3500)
|
|
61
|
+
await session.append('delivered', { type: 'delivered', payload: {} })
|
|
62
|
+
})
|
|
63
|
+
|
|
17
64
|
export const ordersServer = createServer({
|
|
18
65
|
...(store ? { store } : {}),
|
|
19
66
|
...(ordersScheduler ? { scheduler: ordersScheduler } : {}),
|
|
20
67
|
contract: orders,
|
|
21
|
-
|
|
22
|
-
created: async ({ session }) => {
|
|
23
|
-
await wait(800)
|
|
24
|
-
await session.append('notify-shop', {
|
|
25
|
-
type: 'shop.notified',
|
|
26
|
-
payload: {},
|
|
27
|
-
})
|
|
28
|
-
},
|
|
29
|
-
'shop.started': async ({ session }) => {
|
|
30
|
-
await wait(1200)
|
|
31
|
-
const driverId = drivers[Math.floor(Math.random() * drivers.length)]!
|
|
32
|
-
await session.append('notify-driver', {
|
|
33
|
-
type: 'driver.notified',
|
|
34
|
-
payload: { driverId },
|
|
35
|
-
})
|
|
36
|
-
},
|
|
37
|
-
'driver.notified': async ({ session }) => {
|
|
38
|
-
await wait(3000)
|
|
39
|
-
const etaMinutes = 3 + Math.floor(Math.random() * 5)
|
|
40
|
-
await session.append('courier-picked-up', {
|
|
41
|
-
type: 'courier.picked_up',
|
|
42
|
-
payload: { etaMinutes },
|
|
43
|
-
})
|
|
44
|
-
},
|
|
45
|
-
'courier.picked_up': async ({ session }) => {
|
|
46
|
-
await wait(3500)
|
|
47
|
-
await session.append('delivered', { type: 'delivered', payload: {} })
|
|
48
|
-
},
|
|
49
|
-
},
|
|
68
|
+
consumers: [notifyShop, notifyDriver, pickUp, deliver],
|
|
50
69
|
})
|
|
@@ -45,7 +45,7 @@ export function RecoveryClient({
|
|
|
45
45
|
return (
|
|
46
46
|
<article>
|
|
47
47
|
<header className="session-header">
|
|
48
|
-
<h1>Cross-invocation
|
|
48
|
+
<h1>Cross-invocation step chain</h1>
|
|
49
49
|
<ConnectionPill connection={connection} />
|
|
50
50
|
<span
|
|
51
51
|
className={`badge${completed ? ' done' : running ? ' waiting' : ''}`}
|
|
@@ -59,14 +59,14 @@ export function RecoveryClient({
|
|
|
59
59
|
</header>
|
|
60
60
|
|
|
61
61
|
<p className="lede">
|
|
62
|
-
Four
|
|
63
|
-
|
|
62
|
+
Four steps fit individually, but their 32-second chain does not fit in
|
|
63
|
+
one 20-second function. Every invocation has the same limit.
|
|
64
64
|
</p>
|
|
65
65
|
|
|
66
66
|
<dl className="facts">
|
|
67
67
|
<dt>Function limit</dt>
|
|
68
68
|
<dd>{FUNCTION_LIMIT_SECONDS} seconds</dd>
|
|
69
|
-
<dt>Each
|
|
69
|
+
<dt>Each step</dt>
|
|
70
70
|
<dd>{STEP_SECONDS} seconds</dd>
|
|
71
71
|
<dt>Whole chain</dt>
|
|
72
72
|
<dd>
|
|
@@ -94,15 +94,14 @@ export function RecoveryClient({
|
|
|
94
94
|
<h2>The chain is crossing function invocations</h2>
|
|
95
95
|
<p>
|
|
96
96
|
Each step waits eight seconds before appending its completion fact.
|
|
97
|
-
A2 does not predict whether the next
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
fresh function.
|
|
97
|
+
A2 does not predict whether the next step fits. The first invocation
|
|
98
|
+
completes two steps, starts the third, and is stopped by Vercel at
|
|
99
|
+
20 seconds. That event stays pending. Its claim watchdog acquires
|
|
100
|
+
the expired claim and retries the same event in a fresh function.
|
|
102
101
|
</p>
|
|
103
102
|
<p className="hint">
|
|
104
103
|
Keep this page open. Claim heartbeats keep scheduling watchdogs
|
|
105
|
-
while a
|
|
104
|
+
while a step is running. The known function deadline caps the last
|
|
106
105
|
claim window, so the scheduler arrives just after Vercel stops the
|
|
107
106
|
worker. Reloading can also trigger A2's separate
|
|
108
107
|
interaction-healing path.
|
|
@@ -112,8 +111,8 @@ export function RecoveryClient({
|
|
|
112
111
|
|
|
113
112
|
{completed ? (
|
|
114
113
|
<p className="proof">
|
|
115
|
-
All {state.completedSteps} bounded
|
|
116
|
-
|
|
114
|
+
All {state.completedSteps} bounded steps finished. The scheduler added
|
|
115
|
+
no event of its own; it only continued draining the pending
|
|
117
116
|
application events.
|
|
118
117
|
</p>
|
|
119
118
|
) : null}
|
|
@@ -127,12 +126,12 @@ export function RecoveryClient({
|
|
|
127
126
|
state.completedSteps < STEP_COUNT ? (
|
|
128
127
|
<>
|
|
129
128
|
Waiting for step {state.completedSteps + 1} of {STEP_COUNT}. The
|
|
130
|
-
event log stays quiet while its {STEP_SECONDS}-second
|
|
131
|
-
|
|
129
|
+
event log stays quiet while its {STEP_SECONDS}-second step runs;
|
|
130
|
+
the next row is the step's real completion fact.
|
|
132
131
|
</>
|
|
133
132
|
) : (
|
|
134
133
|
<>
|
|
135
|
-
All four
|
|
134
|
+
All four steps finished. Waiting for the final{' '}
|
|
136
135
|
<code>pipeline.completed</code> fact from the active drain.
|
|
137
136
|
</>
|
|
138
137
|
)
|
|
@@ -6,10 +6,10 @@ export default function RecoveryPage(): ReactNode {
|
|
|
6
6
|
<article>
|
|
7
7
|
<h1>Recovery lab</h1>
|
|
8
8
|
<p className="lede">
|
|
9
|
-
Run four 8-second
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
9
|
+
Run four 8-second steps in a chain. Each fits inside a 20-second Vercel
|
|
10
|
+
function, but all 32 seconds cannot. Watch Vercel stop one mid-step,
|
|
11
|
+
then let A2 retry that pending event in a fresh invocation with the same
|
|
12
|
+
limit.
|
|
13
13
|
</p>
|
|
14
14
|
<NewRecoverySession />
|
|
15
15
|
<p className="hint">
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import 'server-only'
|
|
2
2
|
import { vercelQueues } from 'experimental-a2/scheduler-vercel'
|
|
3
|
-
import { createServer } from 'experimental-a2/server'
|
|
3
|
+
import { consumer, createServer } from 'experimental-a2/server'
|
|
4
4
|
import { store } from '@/lib/store'
|
|
5
5
|
import { recovery } from './model'
|
|
6
6
|
|
|
@@ -15,39 +15,30 @@ export const recoveryScheduler =
|
|
|
15
15
|
? vercelQueues({ topic: 'a2-recovery' })
|
|
16
16
|
: undefined
|
|
17
17
|
|
|
18
|
+
/** Each step waits its 8 seconds, then returns the next one, in one lane. */
|
|
19
|
+
const runPipeline = consumer({
|
|
20
|
+
contract: recovery,
|
|
21
|
+
name: 'run-pipeline',
|
|
22
|
+
types: ['pipeline.started', 'step.completed'],
|
|
23
|
+
lane: 'pipeline',
|
|
24
|
+
}).run(async ({ event }) => {
|
|
25
|
+
const step = event.type === 'step.completed' ? event.payload.step : 0
|
|
26
|
+
if (step === stepCount) {
|
|
27
|
+
return {
|
|
28
|
+
type: 'pipeline.completed',
|
|
29
|
+
payload: { stepCount },
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
await wait(stepDurationSeconds * 1_000)
|
|
33
|
+
return {
|
|
34
|
+
type: 'step.completed',
|
|
35
|
+
payload: { step: step + 1, durationSeconds: stepDurationSeconds },
|
|
36
|
+
}
|
|
37
|
+
})
|
|
38
|
+
|
|
18
39
|
export const recoveryServer = createServer({
|
|
19
40
|
...(store ? { store } : {}),
|
|
20
41
|
...(recoveryScheduler ? { scheduler: recoveryScheduler } : {}),
|
|
21
42
|
contract: recovery,
|
|
22
|
-
|
|
23
|
-
'pipeline.started': {
|
|
24
|
-
lane: 'pipeline',
|
|
25
|
-
handler: async () => {
|
|
26
|
-
await wait(stepDurationSeconds * 1_000)
|
|
27
|
-
return {
|
|
28
|
-
type: 'step.completed',
|
|
29
|
-
payload: { step: 1, durationSeconds: stepDurationSeconds },
|
|
30
|
-
}
|
|
31
|
-
},
|
|
32
|
-
},
|
|
33
|
-
'step.completed': {
|
|
34
|
-
lane: 'pipeline',
|
|
35
|
-
handler: async ({ event }) => {
|
|
36
|
-
if (event.payload.step === stepCount) {
|
|
37
|
-
return {
|
|
38
|
-
type: 'pipeline.completed',
|
|
39
|
-
payload: { stepCount },
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
await wait(stepDurationSeconds * 1_000)
|
|
43
|
-
return {
|
|
44
|
-
type: 'step.completed',
|
|
45
|
-
payload: {
|
|
46
|
-
step: event.payload.step + 1,
|
|
47
|
-
durationSeconds: stepDurationSeconds,
|
|
48
|
-
},
|
|
49
|
-
}
|
|
50
|
-
},
|
|
51
|
-
},
|
|
52
|
-
},
|
|
43
|
+
consumers: [runPipeline],
|
|
53
44
|
})
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
// The Postgres at DATABASE_URL: a2slack's tables, and A2's (db/a2.ts, a copy
|
|
2
|
+
// of experimental-a2's drizzle-schema.ts). tablesFilter keeps push and
|
|
3
|
+
// studio to these, since other apps share the database.
|
|
4
|
+
import { defineConfig } from 'drizzle-kit'
|
|
5
|
+
|
|
6
|
+
export default defineConfig({
|
|
7
|
+
dialect: 'postgresql',
|
|
8
|
+
schema: './app/a2slack/db/schema.ts',
|
|
9
|
+
out: './app/a2slack/db/migrations',
|
|
10
|
+
casing: 'snake_case',
|
|
11
|
+
dbCredentials: { url: process.env.DATABASE_URL ?? '' },
|
|
12
|
+
tablesFilter: ['a2slack_*', 'a2_*'],
|
|
13
|
+
migrations: { table: 'a2slack_migrations', schema: 'public' },
|
|
14
|
+
})
|
|
@@ -9,7 +9,7 @@ const postgresUrl = process.env.DATABASE_URL?.replace(
|
|
|
9
9
|
)
|
|
10
10
|
|
|
11
11
|
export const store: RedisStore | PostgresStore | undefined = redisUrl
|
|
12
|
-
? redis({ url: redisUrl })
|
|
12
|
+
? redis({ url: redisUrl, keyPrefix: 'a2-playground-tagged' })
|
|
13
13
|
: postgresUrl
|
|
14
14
|
? postgres({ connectionString: postgresUrl })
|
|
15
15
|
: undefined
|
|
@@ -12,7 +12,12 @@ const nextConfig: NextConfig = {
|
|
|
12
12
|
// Turbopack panic on its WebAssembly), so it finds photon through this
|
|
13
13
|
// app: package.json depends on photon at the version Pi pins, and the two
|
|
14
14
|
// move together.
|
|
15
|
-
serverExternalPackages: [
|
|
15
|
+
serverExternalPackages: [
|
|
16
|
+
'libfx',
|
|
17
|
+
'@silvia-odwyer/photon-node',
|
|
18
|
+
// a2slack's development database, PGlite, loads its WebAssembly by path.
|
|
19
|
+
'@electric-sql/pglite',
|
|
20
|
+
],
|
|
16
21
|
turbopack: {
|
|
17
22
|
// WebAssembly Pi's code reaches that the bundler cannot trace.
|
|
18
23
|
resolveAlias: {
|
|
@@ -11,18 +11,26 @@
|
|
|
11
11
|
"scripts": {
|
|
12
12
|
"dev": "next dev",
|
|
13
13
|
"build": "next build",
|
|
14
|
+
"vercel-build": "node scripts/migrate.mjs && turbo run build",
|
|
14
15
|
"test": "vitest run",
|
|
16
|
+
"drills": "vitest run --config vitest.drills.config.ts",
|
|
15
17
|
"typecheck": "tsc --noEmit",
|
|
18
|
+
"db:generate": "drizzle-kit generate",
|
|
19
|
+
"db:migrate": "node scripts/migrate.mjs",
|
|
16
20
|
"a0:template": "node --env-file-if-exists=.env.local scripts/a0-template.ts"
|
|
17
21
|
},
|
|
18
22
|
"dependencies": {
|
|
19
23
|
"@ai-sdk/otel": "1.0.58",
|
|
24
|
+
"@base-ui/react": "^1.7.0",
|
|
20
25
|
"@codemirror/state": "^6.7.1",
|
|
21
26
|
"@codemirror/view": "^6.43.8",
|
|
27
|
+
"@electric-sql/pglite": "^0.3.14",
|
|
22
28
|
"@earendil-works/pi-ai": "1.0.3",
|
|
23
29
|
"@earendil-works/pi-coding-agent": "1.0.2",
|
|
24
30
|
"@opentelemetry/api": "^1.9.0",
|
|
31
|
+
"@paper-design/shaders-react": "^0.0.80",
|
|
25
32
|
"@silvia-odwyer/photon-node": "0.3.4",
|
|
33
|
+
"@upstash/redis": "^1.39.0",
|
|
26
34
|
"@vercel/blob": "^2.8.0",
|
|
27
35
|
"@vercel/functions": "^3.9.3",
|
|
28
36
|
"@vercel/otel": "^2.1.0",
|
|
@@ -30,7 +38,8 @@
|
|
|
30
38
|
"@vercel/sandbox": "^3.0.0",
|
|
31
39
|
"ai": "^7.0.58",
|
|
32
40
|
"codemirror": "^6.0.2",
|
|
33
|
-
"
|
|
41
|
+
"drizzle-orm": "^0.45.3",
|
|
42
|
+
"experimental-a2": "0.22.0",
|
|
34
43
|
"geist": "^1.7.2",
|
|
35
44
|
"ioredis": "^5.9.0",
|
|
36
45
|
"libfx": "0.0.12",
|
|
@@ -52,9 +61,11 @@
|
|
|
52
61
|
"@testing-library/react": "^16.1.0",
|
|
53
62
|
"jsdom": "^26.0.0",
|
|
54
63
|
"@types/node": "^24.0.0",
|
|
64
|
+
"@types/pg": "^8.15.0",
|
|
55
65
|
"@types/react": "^19.0.0",
|
|
56
66
|
"@types/react-dom": "^19.0.0",
|
|
57
67
|
"@types/ws": "^8.18.1",
|
|
68
|
+
"drizzle-kit": "^0.31.11",
|
|
58
69
|
"typescript": "^7.0.2",
|
|
59
70
|
"undici": "^8.10.2",
|
|
60
71
|
"vitest": "^4.1.10"
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Applies app/a2slack/db/migrations to the Postgres at DATABASE_URL_UNPOOLED
|
|
3
|
+
* (the Neon integration's direct connection) or DATABASE_URL. drizzle-kit
|
|
4
|
+
* migrate does the same, but hides the database's error when one fails.
|
|
5
|
+
*
|
|
6
|
+
* DATABASE_URL=postgres://… pnpm --filter playground db:migrate
|
|
7
|
+
*/
|
|
8
|
+
import { drizzle } from 'drizzle-orm/node-postgres'
|
|
9
|
+
import { migrate } from 'drizzle-orm/node-postgres/migrator'
|
|
10
|
+
|
|
11
|
+
const url = process.env.DATABASE_URL_UNPOOLED || process.env.DATABASE_URL
|
|
12
|
+
if (!url) {
|
|
13
|
+
console.error('error: set DATABASE_URL to the Postgres to migrate')
|
|
14
|
+
process.exit(1)
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const db = drizzle({ connection: url })
|
|
18
|
+
try {
|
|
19
|
+
await migrate(db, {
|
|
20
|
+
migrationsFolder: new URL('../app/a2slack/db/migrations', import.meta.url)
|
|
21
|
+
.pathname,
|
|
22
|
+
migrationsTable: 'a2slack_migrations',
|
|
23
|
+
migrationsSchema: 'public',
|
|
24
|
+
})
|
|
25
|
+
console.log('migrations applied')
|
|
26
|
+
} catch (error) {
|
|
27
|
+
console.error('error: migrations failed, and none were applied:', error)
|
|
28
|
+
process.exitCode = 1
|
|
29
|
+
} finally {
|
|
30
|
+
await db.$client.end()
|
|
31
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
// The a2slack drills (app/a2slack/drills): load, rebuild, and failure runs
|
|
2
|
+
// against a throwaway Redis and Postgres they start themselves. Not part of
|
|
3
|
+
// `pnpm test`; run them with `pnpm drills`.
|
|
4
|
+
import { fileURLToPath } from 'node:url'
|
|
5
|
+
import { defineConfig } from 'vitest/config'
|
|
6
|
+
|
|
7
|
+
export default defineConfig({
|
|
8
|
+
resolve: {
|
|
9
|
+
tsconfigPaths: true,
|
|
10
|
+
alias: {
|
|
11
|
+
'server-only': fileURLToPath(
|
|
12
|
+
new URL('./test/server-only.ts', import.meta.url),
|
|
13
|
+
),
|
|
14
|
+
},
|
|
15
|
+
},
|
|
16
|
+
test: {
|
|
17
|
+
include: ['app/a2slack/drills/*.drill.ts'],
|
|
18
|
+
fileParallelism: false,
|
|
19
|
+
testTimeout: 15 * 60_000,
|
|
20
|
+
hookTimeout: 120_000,
|
|
21
|
+
env: {
|
|
22
|
+
VERCEL: '',
|
|
23
|
+
A2_QUEUES: '',
|
|
24
|
+
AI_GATEWAY_API_KEY: '',
|
|
25
|
+
VERCEL_OIDC_TOKEN: '',
|
|
26
|
+
KV_REST_API_URL: '',
|
|
27
|
+
KV_REST_API_TOKEN: '',
|
|
28
|
+
REDIS_KV_REST_API_URL: '',
|
|
29
|
+
REDIS_KV_REST_API_TOKEN: '',
|
|
30
|
+
},
|
|
31
|
+
},
|
|
32
|
+
})
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "experimental-a2",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.22.0",
|
|
4
4
|
"description": "Syncs your immutable event log, from server to browser: one contract, handlers, derived state, and a live client per session.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -113,6 +113,9 @@
|
|
|
113
113
|
},
|
|
114
114
|
"devDependencies": {
|
|
115
115
|
"@electric-sql/pglite": "^0.3.14",
|
|
116
|
+
"experimental-a2-0-20": "npm:experimental-a2@0.20.0",
|
|
117
|
+
"drizzle-kit": "^0.31.11",
|
|
118
|
+
"drizzle-orm": "^0.45.3",
|
|
116
119
|
"@upstash/qstash": "^2.11.3",
|
|
117
120
|
"ai": "^7.0.58",
|
|
118
121
|
"ioredis": "^5.9.0",
|
|
@@ -146,7 +149,7 @@
|
|
|
146
149
|
"scripts": {
|
|
147
150
|
"build": "tsdown",
|
|
148
151
|
"dev": "tsdown --watch",
|
|
149
|
-
"typecheck": "tsc --noEmit",
|
|
152
|
+
"typecheck": "tsc --noEmit && tsc --noEmit -p tsconfig.reference.json",
|
|
150
153
|
"test": "vitest run",
|
|
151
154
|
"test:watch": "vitest"
|
|
152
155
|
}
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* An example: A2's tables for experimental-a2/store-postgres as Drizzle
|
|
3
|
+
* definitions, the same tables schema.sql next to this file creates. It isn't
|
|
4
|
+
* an API, and nothing imports it. Use it however fits your setup, for example
|
|
5
|
+
* as a copy next to your own schema, so your migrations create A2's tables
|
|
6
|
+
* with yours. Adapt it to your Drizzle version, your ORM, and how you lay out
|
|
7
|
+
* your schema.
|
|
8
|
+
*
|
|
9
|
+
* Whatever creates them, A2 relies on the table and column names, the column
|
|
10
|
+
* types, the primary keys, and the unique index on a2_events (session_id,
|
|
11
|
+
* event_id). The other indexes keep its queries fast. Every name is spelled
|
|
12
|
+
* out, so Drizzle's `casing` setting changes none of them.
|
|
13
|
+
*
|
|
14
|
+
* A2's tables change only when a release's changelog says so; compare your
|
|
15
|
+
* copy with that release's file. Drizzle can't declare functions or
|
|
16
|
+
* triggers: add schema.sql's `a2_batch` function, which A2 runs its writes
|
|
17
|
+
* through, as a custom migration (`drizzle-kit generate --custom`), and the
|
|
18
|
+
* triggers `listenConnectionString` needs are in notifications.sql.
|
|
19
|
+
*/
|
|
20
|
+
import { sql } from 'drizzle-orm'
|
|
21
|
+
import {
|
|
22
|
+
bigint,
|
|
23
|
+
boolean,
|
|
24
|
+
index,
|
|
25
|
+
integer,
|
|
26
|
+
jsonb,
|
|
27
|
+
pgTable,
|
|
28
|
+
primaryKey,
|
|
29
|
+
text,
|
|
30
|
+
uniqueIndex,
|
|
31
|
+
} from 'drizzle-orm/pg-core'
|
|
32
|
+
|
|
33
|
+
const int8 = (name: string) => bigint(name, { mode: 'number' })
|
|
34
|
+
|
|
35
|
+
export const a2Events = pgTable(
|
|
36
|
+
'a2_events',
|
|
37
|
+
{
|
|
38
|
+
sessionId: text('session_id').notNull(),
|
|
39
|
+
idx: int8('idx').notNull(),
|
|
40
|
+
eventType: text('event_type').notNull(),
|
|
41
|
+
payload: jsonb('payload').notNull(),
|
|
42
|
+
eventId: text('event_id').notNull(),
|
|
43
|
+
createdAt: int8('created_at').notNull(),
|
|
44
|
+
cause: jsonb('cause'),
|
|
45
|
+
},
|
|
46
|
+
(table) => [
|
|
47
|
+
primaryKey({
|
|
48
|
+
name: 'a2_events_pkey',
|
|
49
|
+
columns: [table.sessionId, table.idx],
|
|
50
|
+
}),
|
|
51
|
+
uniqueIndex('a2_events_session_event_id').on(
|
|
52
|
+
table.sessionId,
|
|
53
|
+
table.eventId,
|
|
54
|
+
),
|
|
55
|
+
index('a2_events_types').on(table.sessionId, table.eventType, table.idx),
|
|
56
|
+
],
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* One row per run of a cursor: an event cursor's one event, a lane's run of
|
|
61
|
+
* queued events, or a log cursor's position.
|
|
62
|
+
*/
|
|
63
|
+
export const a2Cursors = pgTable(
|
|
64
|
+
'a2_cursors',
|
|
65
|
+
{
|
|
66
|
+
sessionId: text('session_id').notNull(),
|
|
67
|
+
consumer: text('consumer').notNull(),
|
|
68
|
+
cursorKey: text('cursor_key').notNull(),
|
|
69
|
+
epoch: int8('epoch').notNull(),
|
|
70
|
+
lane: text('lane'),
|
|
71
|
+
kind: text('kind').notNull(),
|
|
72
|
+
position: int8('position').notNull(),
|
|
73
|
+
head: int8('head'),
|
|
74
|
+
types: jsonb('types'),
|
|
75
|
+
attemptCount: integer('attempt_count').notNull().default(0),
|
|
76
|
+
firstClaimedAt: int8('first_claimed_at'),
|
|
77
|
+
lastClaimedAt: int8('last_claimed_at'),
|
|
78
|
+
claimHolder: text('claim_holder'),
|
|
79
|
+
claimExpiresAt: int8('claim_expires_at'),
|
|
80
|
+
failureCount: integer('failure_count').notNull().default(0),
|
|
81
|
+
lastFailedAt: int8('last_failed_at'),
|
|
82
|
+
lastFailedAttempt: integer('last_failed_attempt'),
|
|
83
|
+
lastError: text('last_error'),
|
|
84
|
+
failedAt: int8('failed_at'),
|
|
85
|
+
completedIdx: int8('completed_idx'),
|
|
86
|
+
completedAttempt: integer('completed_attempt'),
|
|
87
|
+
returnedEventIds: jsonb('returned_event_ids'),
|
|
88
|
+
},
|
|
89
|
+
(table) => [
|
|
90
|
+
primaryKey({
|
|
91
|
+
name: 'a2_cursors_pkey',
|
|
92
|
+
columns: [table.sessionId, table.cursorKey, table.consumer, table.epoch],
|
|
93
|
+
}),
|
|
94
|
+
index('a2_cursors_heads')
|
|
95
|
+
.on(table.sessionId, table.head)
|
|
96
|
+
.where(sql`head is not null`),
|
|
97
|
+
index('a2_cursors_lanes')
|
|
98
|
+
.on(table.sessionId)
|
|
99
|
+
.where(sql`kind = 'lane'`),
|
|
100
|
+
],
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
/** A lane cursor's queue: the events appended for the lane, in log order. */
|
|
104
|
+
export const a2LaneEvents = pgTable(
|
|
105
|
+
'a2_lane_events',
|
|
106
|
+
{
|
|
107
|
+
sessionId: text('session_id').notNull(),
|
|
108
|
+
consumer: text('consumer').notNull(),
|
|
109
|
+
lane: text('lane').notNull(),
|
|
110
|
+
idx: int8('idx').notNull(),
|
|
111
|
+
done: boolean('done').notNull().default(false),
|
|
112
|
+
completedAttempt: integer('completed_attempt'),
|
|
113
|
+
returnedEventIds: jsonb('returned_event_ids'),
|
|
114
|
+
},
|
|
115
|
+
(table) => [
|
|
116
|
+
primaryKey({
|
|
117
|
+
name: 'a2_lane_events_pkey',
|
|
118
|
+
columns: [table.sessionId, table.consumer, table.lane, table.idx],
|
|
119
|
+
}),
|
|
120
|
+
],
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
export const a2Snapshots = pgTable(
|
|
124
|
+
'a2_snapshots',
|
|
125
|
+
{
|
|
126
|
+
sessionId: text('session_id').notNull(),
|
|
127
|
+
reducerName: text('reducer_name').notNull(),
|
|
128
|
+
upToIndex: int8('up_to_index').notNull(),
|
|
129
|
+
state: jsonb('state').notNull(),
|
|
130
|
+
updatedAt: int8('updated_at').notNull(),
|
|
131
|
+
},
|
|
132
|
+
(table) => [
|
|
133
|
+
primaryKey({
|
|
134
|
+
name: 'a2_snapshots_pkey',
|
|
135
|
+
columns: [table.sessionId, table.reducerName],
|
|
136
|
+
}),
|
|
137
|
+
],
|
|
138
|
+
)
|
|
139
|
+
|
|
140
|
+
export const a2SnapshotHistory = pgTable(
|
|
141
|
+
'a2_snapshot_history',
|
|
142
|
+
{
|
|
143
|
+
sessionId: text('session_id').notNull(),
|
|
144
|
+
reducerName: text('reducer_name').notNull(),
|
|
145
|
+
upToIndex: int8('up_to_index').notNull(),
|
|
146
|
+
state: jsonb('state').notNull(),
|
|
147
|
+
updatedAt: int8('updated_at').notNull(),
|
|
148
|
+
},
|
|
149
|
+
(table) => [
|
|
150
|
+
primaryKey({
|
|
151
|
+
name: 'a2_snapshot_history_pkey',
|
|
152
|
+
columns: [table.sessionId, table.reducerName, table.upToIndex],
|
|
153
|
+
}),
|
|
154
|
+
],
|
|
155
|
+
)
|
|
156
|
+
|
|
157
|
+
export const a2SnapshotPins = pgTable(
|
|
158
|
+
'a2_snapshot_pins',
|
|
159
|
+
{
|
|
160
|
+
sessionId: text('session_id').notNull(),
|
|
161
|
+
eventIndex: int8('event_index').notNull(),
|
|
162
|
+
consumer: text('consumer').notNull(),
|
|
163
|
+
cursorKey: text('cursor_key').notNull(),
|
|
164
|
+
reducerName: text('reducer_name').notNull(),
|
|
165
|
+
upToIndex: int8('up_to_index').notNull(),
|
|
166
|
+
},
|
|
167
|
+
(table) => [
|
|
168
|
+
primaryKey({
|
|
169
|
+
name: 'a2_snapshot_pins_pkey',
|
|
170
|
+
columns: [
|
|
171
|
+
table.sessionId,
|
|
172
|
+
table.eventIndex,
|
|
173
|
+
table.consumer,
|
|
174
|
+
table.cursorKey,
|
|
175
|
+
table.reducerName,
|
|
176
|
+
table.upToIndex,
|
|
177
|
+
],
|
|
178
|
+
}),
|
|
179
|
+
index('a2_snapshot_pins_checkpoint').on(
|
|
180
|
+
table.sessionId,
|
|
181
|
+
table.reducerName,
|
|
182
|
+
table.upToIndex,
|
|
183
|
+
),
|
|
184
|
+
],
|
|
185
|
+
)
|
|
186
|
+
|
|
187
|
+
export const a2Clears = pgTable('a2_clears', {
|
|
188
|
+
sessionId: text('session_id').primaryKey(),
|
|
189
|
+
clearIndex: int8('clear_index').notNull(),
|
|
190
|
+
})
|
|
191
|
+
|
|
192
|
+
export const a2Presence = pgTable(
|
|
193
|
+
'a2_presence',
|
|
194
|
+
{
|
|
195
|
+
ns: text('ns').notNull(),
|
|
196
|
+
participant: text('participant').notNull(),
|
|
197
|
+
field: text('field').notNull(),
|
|
198
|
+
value: jsonb('value').notNull(),
|
|
199
|
+
seen: int8('seen').notNull(),
|
|
200
|
+
at: int8('at').notNull(),
|
|
201
|
+
expiresAt: int8('expires_at').notNull(),
|
|
202
|
+
},
|
|
203
|
+
(table) => [
|
|
204
|
+
primaryKey({
|
|
205
|
+
name: 'a2_presence_pkey',
|
|
206
|
+
columns: [table.ns, table.participant, table.field],
|
|
207
|
+
}),
|
|
208
|
+
],
|
|
209
|
+
)
|