experimental-a2 0.18.1 → 0.20.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 +44 -0
- package/dist/actor.js +1 -1
- package/dist/{ai-BMLlI2Ff.d.ts → ai-Cz3hqVhk.d.ts} +33 -16
- package/dist/ai-Cz3hqVhk.d.ts.map +1 -0
- package/dist/ai-client.d.ts +1 -1
- package/dist/ai-client.js +1 -1
- package/dist/ai-control-CvLbW10B.js +46 -0
- package/dist/ai-control-CvLbW10B.js.map +1 -0
- package/dist/ai-fx.d.ts +1 -1
- package/dist/ai-fx.d.ts.map +1 -1
- package/dist/ai-fx.js +63 -13
- package/dist/ai-fx.js.map +1 -1
- package/dist/{ai-harness-B2suP2Qh.d.ts → ai-harness-CV6rXbB3.d.ts} +2 -2
- package/dist/ai-harness-CV6rXbB3.d.ts.map +1 -0
- package/dist/{ai-harness-BuLh_EYi.js → ai-harness-Clbg5ei7.js} +88 -15
- package/dist/ai-harness-Clbg5ei7.js.map +1 -0
- package/dist/{ai-D6mGlO8j.js → ai-mMiWul1T.js} +116 -77
- package/dist/ai-mMiWul1T.js.map +1 -0
- package/dist/ai-pi.d.ts +1 -1
- package/dist/ai-pi.d.ts.map +1 -1
- package/dist/ai-pi.js +56 -11
- package/dist/ai-pi.js.map +1 -1
- package/dist/ai-react.d.ts +1 -1
- package/dist/ai-react.js +1 -1
- package/dist/ai-server.d.ts +2 -2
- package/dist/ai-server.d.ts.map +1 -1
- package/dist/ai-server.js +123 -53
- package/dist/ai-server.js.map +1 -1
- package/dist/ai.d.ts +2 -2
- package/dist/ai.js +1 -1
- package/dist/internal-CscSY7K_.js.map +1 -1
- package/dist/scheduler-qstash.d.ts.map +1 -1
- package/dist/scheduler-qstash.js +5 -5
- package/dist/scheduler-qstash.js.map +1 -1
- package/dist/scheduler-vercel.d.ts.map +1 -1
- package/dist/scheduler-vercel.js +5 -7
- package/dist/scheduler-vercel.js.map +1 -1
- package/dist/server-DYAATFuT.d.ts.map +1 -1
- package/dist/{server-D-Y4pqwu.js → server-qyb5aJJG.js} +39 -25
- package/dist/server-qyb5aJJG.js.map +1 -0
- package/dist/server.js +1 -1
- package/dist/session-clear-DGI4jTQ5.js.map +1 -1
- package/dist/store-postgres.d.ts.map +1 -1
- package/dist/store-postgres.js +38 -136
- package/dist/store-postgres.js.map +1 -1
- package/docs/01-quickstart.mdx +7 -4
- package/docs/actors/01-introduction.mdx +1 -1
- package/docs/actors/meta.ts +1 -1
- package/docs/agents/02-tools.mdx +1 -1
- package/docs/agents/03-conversations.mdx +26 -14
- package/docs/agents/04-harnesses.mdx +51 -5
- package/docs/agents/meta.ts +1 -1
- package/docs/client/meta.ts +1 -1
- package/docs/contract/03-sessions.mdx +2 -2
- package/docs/index.mdx +2 -2
- package/docs/reference/01-core.mdx +8 -8
- package/docs/reference/02-server.mdx +3 -3
- package/docs/reference/04-ai.mdx +36 -19
- package/docs/reference/05-telemetry.mdx +132 -0
- package/docs/reference/06-errors.mdx +1 -1
- package/docs/reference/meta.ts +1 -1
- package/docs/schedulers/01-overview.mdx +112 -0
- package/docs/schedulers/02-vercel-queues.mdx +87 -0
- package/docs/schedulers/03-qstash.mdx +123 -0
- package/docs/schedulers/meta.ts +1 -0
- package/docs/server/01-handlers.mdx +29 -2
- package/docs/server/02-timers.mdx +3 -3
- package/docs/server/06-guarantees.mdx +6 -3
- package/docs/stores/01-overview.mdx +37 -0
- package/docs/stores/02-redis.mdx +49 -0
- package/docs/stores/03-redis-http.mdx +56 -0
- package/docs/stores/04-postgres.mdx +121 -0
- package/docs/stores/05-sqlite.mdx +34 -0
- package/docs/stores/06-memory.mdx +22 -0
- package/docs/stores/meta.ts +1 -0
- package/examples/playground/DEPLOY.md +19 -5
- package/examples/playground/app/a0/a0.css +244 -5
- package/examples/playground/app/a0/app-view.tsx +2 -1
- package/examples/playground/app/a0/apps/[appId]/route.ts +6 -10
- package/examples/playground/app/a0/attachments.ts +133 -0
- package/examples/playground/app/a0/chat.test.tsx +595 -0
- package/examples/playground/app/a0/chat.tsx +623 -63
- package/examples/playground/app/a0/context-menu.tsx +38 -11
- package/examples/playground/app/a0/model.test.ts +28 -0
- package/examples/playground/app/a0/model.ts +30 -0
- package/examples/playground/app/a0/server.ts +5 -0
- package/examples/playground/app/a0/shell.tsx +1 -1
- package/examples/playground/app/a0/uploads/route.test.ts +59 -0
- package/examples/playground/app/a0/uploads/route.ts +29 -0
- package/examples/playground/app/agent/[agentId]/agent-client.tsx +2 -1
- package/examples/playground/app/agent/[agentId]/agent-queue.test.tsx +116 -89
- package/examples/playground/app/agent/[agentId]/compaction-panel.test.tsx +107 -76
- package/examples/playground/app/agent/ingress.test.ts +41 -17
- package/examples/playground/app/agent/model-input.test.tsx +92 -43
- package/examples/playground/app/agent/model.test.ts +12 -3
- package/examples/playground/app/chat/[chatId]/agent-state.test.ts +1 -5
- package/examples/playground/app/chat/[chatId]/agent-stream-drawer.test.tsx +2 -2
- package/examples/playground/app/chat/server.test.ts +38 -65
- package/examples/playground/app/components/activity-history.test.tsx +60 -18
- package/examples/playground/app/components/fast-link.test.tsx +3 -5
- package/examples/playground/app/initial-state.test.tsx +86 -45
- package/examples/playground/next.config.ts +7 -4
- package/examples/playground/package.json +4 -1
- package/examples/playground/test/agent.ts +35 -0
- package/examples/playground/test/model.ts +149 -0
- package/examples/playground/test/serve.ts +184 -0
- package/examples/playground/test/server-only.ts +1 -0
- package/examples/playground/vitest.config.ts +27 -0
- package/package.json +3 -1
- package/sql/postgres/notifications.sql +24 -0
- package/sql/postgres/schema.sql +137 -0
- package/src/ai-agent.ts +9 -4
- package/src/ai-client-state.ts +21 -9
- package/src/ai-context-schema.ts +28 -4
- package/src/ai-context.ts +83 -3
- package/src/ai-control-server.ts +70 -43
- package/src/ai-control-state.ts +3 -4
- package/src/ai-control.ts +59 -10
- package/src/ai-coordinator.ts +7 -6
- package/src/ai-fx.ts +95 -23
- package/src/ai-harness.ts +171 -29
- package/src/ai-message-shape.ts +41 -0
- package/src/ai-pi.ts +88 -32
- package/src/ai-stored-state.ts +1 -3
- package/src/ai.ts +116 -77
- package/src/errors.ts +1 -1
- package/src/internal.ts +2 -1
- package/src/libfx.d.ts +8 -1
- package/src/postgres-notifications.ts +0 -22
- package/src/postgres-resources.ts +6 -9
- package/src/postgres-schema.ts +80 -0
- package/src/scheduler-qstash.ts +12 -5
- package/src/scheduler-vercel.ts +11 -16
- package/src/server.ts +66 -32
- package/src/store-postgres.ts +5 -123
- package/dist/ai-BMLlI2Ff.d.ts.map +0 -1
- package/dist/ai-D6mGlO8j.js.map +0 -1
- package/dist/ai-harness-B2suP2Qh.d.ts.map +0 -1
- package/dist/ai-harness-BuLh_EYi.js.map +0 -1
- package/dist/server-D-Y4pqwu.js.map +0 -1
- package/docs/reference/05-adapters.mdx +0 -351
- package/docs/server/05-production.mdx +0 -323
- package/examples/playground/app/a0/no-photon.cjs +0 -4
package/dist/server.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { i as handler, n as deliverSchedulerAppend, r as deliverSchedulerClear, t as createServer } from "./server-
|
|
1
|
+
import { i as handler, n as deliverSchedulerAppend, r as deliverSchedulerClear, t as createServer } from "./server-qyb5aJJG.js";
|
|
2
2
|
export { createServer, deliverSchedulerAppend, deliverSchedulerClear, handler };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"session-clear-DGI4jTQ5.js","names":[],"sources":["../src/errors.ts","../src/session-clear.ts"],"sourcesContent":["export type A2ServerErrorCode =\n | 'INVALID_PAYLOAD' // schema validation failed — thrown before anything is written\n | 'FORBIDDEN' // external ingress was denied by server.fetch authorization\n | 'UNKNOWN_EVENT_TYPE' // event type not in the machine's `events` map\n | 'PARTIAL_DUPLICATE_BATCH' // batch mixed already-appended and fresh events\n | 'SUPERSEDED_ATTEMPT' // handler append from an attempt a recovery claim or a clear replaced\n | 'SESSION_CLEARED' // a read or call reached across the session's latest clear\n | 'CLAIM_EXPIRED' // ctx.signal abort reason: the attempt's lease lapsed without renewal\n | 'STORE_UNAVAILABLE' // store backend failure — retryable, nothing was written\n | 'STORE_NOT_CONFIGURED' //
|
|
1
|
+
{"version":3,"file":"session-clear-DGI4jTQ5.js","names":[],"sources":["../src/errors.ts","../src/session-clear.ts"],"sourcesContent":["export type A2ServerErrorCode =\n | 'INVALID_PAYLOAD' // schema validation failed — thrown before anything is written\n | 'FORBIDDEN' // external ingress was denied by server.fetch authorization\n | 'UNKNOWN_EVENT_TYPE' // event type not in the machine's `events` map\n | 'PARTIAL_DUPLICATE_BATCH' // batch mixed already-appended and fresh events\n | 'SUPERSEDED_ATTEMPT' // handler append from an attempt a recovery claim or a clear replaced\n | 'SESSION_CLEARED' // a read or call reached across the session's latest clear\n | 'CLAIM_EXPIRED' // ctx.signal abort reason: the attempt's lease lapsed without renewal\n | 'STORE_UNAVAILABLE' // store backend failure — retryable, nothing was written\n | 'STORE_NOT_CONFIGURED' // no `store` in production, a missing peer dependency, or Postgres lacks A2's tables\n | 'UNKNOWN_PRESENCE_FIELD' // presence field not in the contract's `presence` map\n | 'PRESENCE_NOT_SUPPORTED' // contract declares presence but the store lacks the capability\n\nexport type A2ErrorCode =\n A2ServerErrorCode | 'TIMEOUT' | 'HTTP_ERROR' | 'CONNECTION_FAILED' | 'UNKNOWN'\n\nexport class A2Error extends Error {\n readonly code: A2ErrorCode\n /** e.g. the Standard Schema issues for INVALID_PAYLOAD */\n readonly details: unknown\n\n constructor(\n code: A2ErrorCode,\n message: string,\n options?: { details?: unknown; cause?: unknown },\n ) {\n super(\n message,\n options && 'cause' in options ? { cause: options.cause } : undefined,\n )\n this.name = 'A2Error'\n this.code = code\n this.details = options?.details\n }\n}\n\n/** A handler write whose attempt no longer owns its triggering event. */\nexport function supersededAttemptError(\n sessionId: string,\n cause: { index: number; attempt: number },\n): A2Error {\n return new A2Error(\n 'SUPERSEDED_ATTEMPT',\n `attempt ${cause.attempt} no longer owns event ${cause.index} in session '${sessionId}'`,\n )\n}\n\n/**\n * Wrap an arbitrary thrown value as STORE_UNAVAILABLE, preserving the\n * original as `cause`. A2Errors pass through untouched — they already\n * carry their own meaning.\n */\nexport function asStoreUnavailable(err: unknown): A2Error {\n if (err instanceof A2Error) return err\n return new A2Error('STORE_UNAVAILABLE', 'the store backend failed', {\n cause: err,\n })\n}\n\n/**\n * Throw from a handler to reject retrying: the failure is adjudicated\n * terminal immediately (dead-lettered on the first throw) instead of\n * consuming the ten-failure retry budget. For deterministic failures —\n * a guard that will refuse identically on every attempt — retrying is\n * waste and dead-lettering is the honest outcome. Matched by\n * `instanceof`, so subclasses carry domain meaning.\n */\nexport class NonRetriableError extends Error {\n constructor(message: string, options?: { cause?: unknown }) {\n super(message, options)\n this.name = 'NonRetriableError'\n }\n}\n","import { A2Error } from './errors.ts'\nimport type { Event } from './store.ts'\n\n/** A2's own event types start with this; application contracts cannot. */\nexport const RESERVED_EVENT_PREFIX = 'a2.'\n\n/** Reserved type of the marker a clear appends. */\nexport const CLEAR_EVENT_TYPE = 'a2.cleared'\n\n/** The marker `session.stream()` yields where a clear ended a lifetime. */\nexport type SessionCleared = Event & {\n type: typeof CLEAR_EVENT_TYPE\n payload: null\n}\n\n/** Accepts any stream item, including presence patches and snapshots. */\nexport function isSessionCleared(item: object): item is SessionCleared {\n return (item as { type?: unknown }).type === CLEAR_EVENT_TYPE\n}\n\nexport function sessionClearedError(message: string): A2Error {\n return new A2Error('SESSION_CLEARED', message)\n}\n"],"mappings":";AAgBA,IAAa,UAAb,cAA6B,MAAM;CACjC;;CAEA;CAEA,YACE,MACA,SACA,SACA;EACA,MACE,SACA,WAAW,WAAW,UAAU,EAAE,OAAO,QAAQ,MAAM,IAAI,KAAA,CAC7D;EACA,KAAK,OAAO;EACZ,KAAK,OAAO;EACZ,KAAK,UAAU,SAAS;CAC1B;AACF;;AAGA,SAAgB,uBACd,WACA,OACS;CACT,OAAO,IAAI,QACT,sBACA,WAAW,MAAM,QAAQ,wBAAwB,MAAM,MAAM,eAAe,UAAU,EACxF;AACF;;;;;;AAOA,SAAgB,mBAAmB,KAAuB;CACxD,IAAI,eAAe,SAAS,OAAO;CACnC,OAAO,IAAI,QAAQ,qBAAqB,4BAA4B,EAClE,OAAO,IACT,CAAC;AACH;;;;;;;;;AAUA,IAAa,oBAAb,cAAuC,MAAM;CAC3C,YAAY,SAAiB,SAA+B;EAC1D,MAAM,SAAS,OAAO;EACtB,KAAK,OAAO;CACd;AACF;;;;ACjEA,MAAa,mBAAmB;;AAShC,SAAgB,iBAAiB,MAAsC;CACrE,OAAQ,KAA4B,SAAS;AAC/C;AAEA,SAAgB,oBAAoB,SAA0B;CAC5D,OAAO,IAAI,QAAQ,mBAAmB,OAAO;AAC/C"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"store-postgres.d.ts","names":[],"sources":["../src/store-postgres.ts"],"mappings":";;;KAuDY;EAAwB,MAAM;;;KAG9B;EACV,MAAM,cAAc,qBAAqB,QAAQ;EACjD;;;;;;;;KASU;EACV,MAAM,cAAc,qBAAqB,QAAQ;EACjD,YAAY,QAAQ;EACpB,QAAQ;;KAGE;;EAEV;;EAEA;;EAEA,SAAS;;EAET,QAAQ;;EAER,MAAM;;KAGI,gBAAgB;;;EAG1B,SAAS;;
|
|
1
|
+
{"version":3,"file":"store-postgres.d.ts","names":[],"sources":["../src/store-postgres.ts"],"mappings":";;;KAuDY;EAAwB,MAAM;;;KAG9B;EACV,MAAM,cAAc,qBAAqB,QAAQ;EACjD;;;;;;;;KASU;EACV,MAAM,cAAc,qBAAqB,QAAQ;EACjD,YAAY,QAAQ;EACpB,QAAQ;;KAGE;;EAEV;;EAEA;;EAEA,SAAS;;EAET,QAAQ;;EAER,MAAM;;KAGI,gBAAgB;;;EAG1B,SAAS;;iBA6KK,SAAS,UAAS,uBAA4B"}
|
package/dist/store-postgres.js
CHANGED
|
@@ -171,27 +171,36 @@ function postgresNotifications(createConnection) {
|
|
|
171
171
|
}
|
|
172
172
|
};
|
|
173
173
|
}
|
|
174
|
-
const
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
174
|
+
const PACKAGE = "node_modules/experimental-a2";
|
|
175
|
+
const apply = (file) => `apply ${PACKAGE}/sql/postgres/${file} to this database`;
|
|
176
|
+
const SEE_DOCS = `See ${PACKAGE}/docs/stores/04-postgres.mdx.`;
|
|
177
|
+
const VERSION = "select version from a2_schema";
|
|
178
|
+
const VERSION_AND_TRIGGERS = `select version, (select count(*) from pg_trigger
|
|
179
|
+
where (tgname = 'a2_notify_events' and tgrelid = to_regclass('a2_events'))
|
|
180
|
+
or (tgname = 'a2_notify_presence' and tgrelid = to_regclass('a2_presence')))
|
|
181
|
+
as notify_triggers from a2_schema`;
|
|
182
|
+
/** Explains a failed version read, or returns undefined when the schema is not the cause. */
|
|
183
|
+
const diagnose = async (client) => {
|
|
184
|
+
const [found] = (await client.query(`select
|
|
185
|
+
to_regclass('a2_schema') is not null as marked,
|
|
186
|
+
to_regclass('a2_events') is not null as installed,
|
|
187
|
+
has_table_privilege(to_regclass('a2_schema'), 'select') as readable`)).rows;
|
|
188
|
+
if (!found) return void 0;
|
|
189
|
+
if (!found["marked"]) return new A2Error("STORE_NOT_CONFIGURED", found["installed"] ? `This Postgres database has A2's tables but no a2_schema version. That is expected once after upgrading from a release that created its tables at runtime, and your events are untouched. A2 doesn't create or change tables at runtime: ${apply("schema.sql")}. ${SEE_DOCS}` : `A2's tables aren't in this Postgres database, and A2 doesn't create tables at runtime: ${apply("schema.sql")}. ${SEE_DOCS}`);
|
|
190
|
+
if (found["readable"] === false) return new A2Error("STORE_NOT_CONFIGURED", `The role this store connects as can't read a2_schema. Apply schema.sql as that role, or grant it select on a2_schema and select, insert, update, and delete on A2's other tables. ${SEE_DOCS}`);
|
|
191
|
+
};
|
|
192
|
+
/** Reads the schema version (and, with a listener, the triggers) in one query that takes no lock a writer holds. */
|
|
193
|
+
const checkPostgresSchema = async ({ client, listen }) => {
|
|
194
|
+
let row;
|
|
195
|
+
try {
|
|
196
|
+
row = (await client.query(listen ? VERSION_AND_TRIGGERS : VERSION)).rows[0];
|
|
197
|
+
} catch (err) {
|
|
198
|
+
throw await diagnose(client).catch(() => void 0) ?? err;
|
|
199
|
+
}
|
|
200
|
+
const version = Number(row?.["version"] ?? 0);
|
|
201
|
+
if (version < 1) throw new A2Error("STORE_NOT_CONFIGURED", `This Postgres database has A2 schema version ${version}, and this release of experimental-a2 needs version 1: ${apply("schema.sql")}. ${SEE_DOCS}`);
|
|
202
|
+
if (listen && Number(row?.["notify_triggers"]) < 2) throw new A2Error("STORE_NOT_CONFIGURED", `listenConnectionString needs A2's notification triggers, which aren't in this Postgres database. After schema.sql, ${apply("notifications.sql")}. ${SEE_DOCS}`);
|
|
203
|
+
};
|
|
195
204
|
//#endregion
|
|
196
205
|
//#region src/postgres-notification-scope.ts
|
|
197
206
|
const failure = (error) => error instanceof A2Error ? error : new A2Error("STORE_UNAVAILABLE", "Postgres subscription failed", { cause: error });
|
|
@@ -446,7 +455,7 @@ function postgresConnectionKey({ connectionString, defaults }) {
|
|
|
446
455
|
}
|
|
447
456
|
const queries = (raw) => {
|
|
448
457
|
let pending = Promise.resolve();
|
|
449
|
-
const
|
|
458
|
+
const initialized = /* @__PURE__ */ new Map();
|
|
450
459
|
const exclusive = (fn) => {
|
|
451
460
|
const result = pending.then(fn);
|
|
452
461
|
pending = result.catch(() => {});
|
|
@@ -456,13 +465,13 @@ const queries = (raw) => {
|
|
|
456
465
|
raw,
|
|
457
466
|
client: raw.connect ? raw : { query: (text, values) => exclusive(() => raw.query(text, values)) },
|
|
458
467
|
exclusive,
|
|
459
|
-
initialize({
|
|
460
|
-
const existing =
|
|
468
|
+
initialize({ key, run }) {
|
|
469
|
+
const existing = initialized.get(key);
|
|
461
470
|
if (existing) return existing;
|
|
462
471
|
const attempt = run();
|
|
463
|
-
|
|
472
|
+
initialized.set(key, attempt);
|
|
464
473
|
attempt.catch(() => {
|
|
465
|
-
if (
|
|
474
|
+
if (initialized.get(key) === attempt) initialized.delete(key);
|
|
466
475
|
});
|
|
467
476
|
return attempt;
|
|
468
477
|
},
|
|
@@ -506,90 +515,6 @@ const sharePostgresListener = ({ key, create }) => acquire({
|
|
|
506
515
|
* `pg_advisory_xact_lock`, so concurrent writers can't race the index
|
|
507
516
|
* assignment.
|
|
508
517
|
*/
|
|
509
|
-
const SCHEMA = `
|
|
510
|
-
create table if not exists a2_events (
|
|
511
|
-
session_id text not null,
|
|
512
|
-
idx bigint not null,
|
|
513
|
-
event_type text not null,
|
|
514
|
-
payload jsonb not null,
|
|
515
|
-
event_id text not null,
|
|
516
|
-
created_at bigint not null,
|
|
517
|
-
cause jsonb,
|
|
518
|
-
lane text,
|
|
519
|
-
lane_ready boolean not null default false,
|
|
520
|
-
processed_at bigint,
|
|
521
|
-
processed_by_attempt integer,
|
|
522
|
-
returned_event_ids jsonb,
|
|
523
|
-
first_claimed_at bigint,
|
|
524
|
-
last_claimed_at bigint,
|
|
525
|
-
attempt_count integer not null default 0,
|
|
526
|
-
failure_count integer not null default 0,
|
|
527
|
-
last_failed_at bigint,
|
|
528
|
-
last_failed_attempt integer,
|
|
529
|
-
last_error text,
|
|
530
|
-
failed_at bigint,
|
|
531
|
-
claim_holder text,
|
|
532
|
-
claim_expires_at bigint,
|
|
533
|
-
primary key (session_id, idx)
|
|
534
|
-
);
|
|
535
|
-
|
|
536
|
-
create unique index if not exists a2_events_event_id on a2_events (event_id);
|
|
537
|
-
create index if not exists a2_events_unprocessed
|
|
538
|
-
on a2_events (session_id, idx) where processed_at is null;
|
|
539
|
-
create index if not exists a2_events_lane_pending
|
|
540
|
-
on a2_events (session_id, lane, idx) where processed_at is null;
|
|
541
|
-
create index if not exists a2_events_dispatch_ready
|
|
542
|
-
on a2_events (session_id, idx)
|
|
543
|
-
where lane_ready and processed_at is null and failed_at is null;
|
|
544
|
-
create index if not exists a2_events_claim_expiry
|
|
545
|
-
on a2_events (session_id, claim_expires_at)
|
|
546
|
-
where lane_ready and processed_at is null and failed_at is null;
|
|
547
|
-
|
|
548
|
-
create table if not exists a2_snapshots (
|
|
549
|
-
session_id text not null,
|
|
550
|
-
reducer_name text not null,
|
|
551
|
-
up_to_index bigint not null,
|
|
552
|
-
state jsonb not null,
|
|
553
|
-
updated_at bigint not null,
|
|
554
|
-
primary key (session_id, reducer_name)
|
|
555
|
-
);
|
|
556
|
-
|
|
557
|
-
create table if not exists a2_snapshot_history (
|
|
558
|
-
session_id text not null,
|
|
559
|
-
reducer_name text not null,
|
|
560
|
-
up_to_index bigint not null,
|
|
561
|
-
state jsonb not null,
|
|
562
|
-
updated_at bigint not null,
|
|
563
|
-
primary key (session_id, reducer_name, up_to_index)
|
|
564
|
-
);
|
|
565
|
-
|
|
566
|
-
create table if not exists a2_snapshot_pins (
|
|
567
|
-
session_id text not null,
|
|
568
|
-
event_index bigint not null,
|
|
569
|
-
reducer_name text not null,
|
|
570
|
-
up_to_index bigint not null,
|
|
571
|
-
primary key (session_id, event_index, reducer_name, up_to_index)
|
|
572
|
-
);
|
|
573
|
-
|
|
574
|
-
create index if not exists a2_snapshot_pins_checkpoint
|
|
575
|
-
on a2_snapshot_pins (session_id, reducer_name, up_to_index);
|
|
576
|
-
|
|
577
|
-
create table if not exists a2_clears (
|
|
578
|
-
session_id text primary key,
|
|
579
|
-
clear_index bigint not null
|
|
580
|
-
);
|
|
581
|
-
|
|
582
|
-
create table if not exists a2_presence (
|
|
583
|
-
ns text not null,
|
|
584
|
-
participant text not null,
|
|
585
|
-
field text not null,
|
|
586
|
-
value jsonb not null,
|
|
587
|
-
seen bigint not null,
|
|
588
|
-
at bigint not null,
|
|
589
|
-
expires_at bigint not null,
|
|
590
|
-
primary key (ns, participant, field)
|
|
591
|
-
);
|
|
592
|
-
`;
|
|
593
518
|
const asNumber = (value) => Number(value);
|
|
594
519
|
/**
|
|
595
520
|
* The session's latest clear marker, or 0, as a primary-key subquery. Read
|
|
@@ -651,25 +576,6 @@ const wrap = async (fn) => {
|
|
|
651
576
|
}
|
|
652
577
|
};
|
|
653
578
|
const jsonb = (value) => JSON.stringify(value) ?? "null";
|
|
654
|
-
const initializeSchema = async ({ client, listen }) => {
|
|
655
|
-
const schema = SCHEMA + (listen ? POSTGRES_NOTIFICATIONS_SQL : "");
|
|
656
|
-
if (!client.connect) {
|
|
657
|
-
await client.query(schema);
|
|
658
|
-
return;
|
|
659
|
-
}
|
|
660
|
-
const conn = await client.connect();
|
|
661
|
-
try {
|
|
662
|
-
await conn.query("begin");
|
|
663
|
-
await conn.query("select pg_advisory_xact_lock(hashtext('a2:postgres:schema'))");
|
|
664
|
-
await conn.query(schema);
|
|
665
|
-
await conn.query("commit");
|
|
666
|
-
} catch (err) {
|
|
667
|
-
await conn.query("rollback").catch(() => {});
|
|
668
|
-
throw err;
|
|
669
|
-
} finally {
|
|
670
|
-
conn.release();
|
|
671
|
-
}
|
|
672
|
-
};
|
|
673
579
|
const loadPg = async () => import("pg").catch(() => {
|
|
674
580
|
throw new A2Error("STORE_NOT_CONFIGURED", "Postgres connections created by A2 require the optional 'pg' package");
|
|
675
581
|
});
|
|
@@ -736,13 +642,9 @@ function postgres(options = {}) {
|
|
|
736
642
|
try {
|
|
737
643
|
const listen = Boolean(options.listenConnectionString);
|
|
738
644
|
const resource = lease.value;
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
});
|
|
743
|
-
else await resource.initialize({
|
|
744
|
-
schema: SCHEMA + (listen ? POSTGRES_NOTIFICATIONS_SQL : ""),
|
|
745
|
-
run: () => initializeSchema({
|
|
645
|
+
await resource.initialize({
|
|
646
|
+
key: listen ? "schema+triggers" : "schema",
|
|
647
|
+
run: () => checkPostgresSchema({
|
|
746
648
|
client: resource.client,
|
|
747
649
|
listen
|
|
748
650
|
})
|