@agent-native/core 0.77.8 → 0.77.10
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/corpus/README.md +1 -1
- package/corpus/core/CHANGELOG.md +29 -0
- package/corpus/core/package.json +1 -1
- package/corpus/core/src/a2a/task-store.ts +25 -3
- package/corpus/core/src/agent/harness/store.ts +89 -55
- package/corpus/core/src/agent/observational-memory/store.ts +40 -14
- package/corpus/core/src/agent/run-store.ts +116 -59
- package/corpus/core/src/application-state/store.ts +36 -2
- package/corpus/core/src/audit/store.ts +37 -6
- package/corpus/core/src/browser-sessions/store.ts +31 -12
- package/corpus/core/src/chat-threads/store.ts +67 -3
- package/corpus/core/src/checkpoints/store.ts +14 -3
- package/corpus/core/src/cli/create.ts +9 -0
- package/corpus/core/src/collab/storage.ts +20 -2
- package/corpus/core/src/db/ddl-guard.ts +112 -0
- package/corpus/core/src/extensions/store.ts +76 -3
- package/corpus/core/src/integrations/a2a-continuations-store.ts +69 -26
- package/corpus/core/src/integrations/config-store.ts +19 -18
- package/corpus/core/src/integrations/pending-tasks-store.ts +40 -16
- package/corpus/core/src/integrations/remote-commands-store.ts +37 -23
- package/corpus/core/src/integrations/remote-devices-store.ts +64 -20
- package/corpus/core/src/integrations/remote-push-store.ts +75 -35
- package/corpus/core/src/integrations/remote-run-events-store.ts +38 -12
- package/corpus/core/src/integrations/thread-mapping-store.ts +11 -2
- package/corpus/core/src/mcp/connect-store.ts +53 -16
- package/corpus/core/src/mcp/oauth-store.ts +32 -7
- package/corpus/core/src/notifications/store.ts +20 -4
- package/corpus/core/src/oauth-tokens/store.ts +45 -2
- package/corpus/core/src/observability/store.ts +154 -47
- package/corpus/core/src/progress/store.ts +36 -21
- package/corpus/core/src/provider-api/corpus-jobs-store.ts +25 -5
- package/corpus/core/src/provider-api/custom-registry.ts +15 -4
- package/corpus/core/src/provider-api/quota-governor.ts +15 -3
- package/corpus/core/src/provider-api/staged-datasets-store.ts +22 -5
- package/corpus/core/src/resources/store.ts +66 -31
- package/corpus/core/src/secrets/storage.ts +18 -22
- package/corpus/core/src/server/agent-teams-run-queue.ts +22 -10
- package/corpus/core/src/server/auth.ts +22 -4
- package/corpus/core/src/server/better-auth-instance.ts +49 -15
- package/corpus/core/src/server/embed-session.ts +19 -4
- package/corpus/core/src/server/identity-sso-store.ts +40 -11
- package/corpus/core/src/server/recap-image-store.ts +28 -9
- package/corpus/core/src/settings/store.ts +14 -19
- package/corpus/core/src/usage/store.ts +51 -16
- package/corpus/core/src/workspace-connections/store.ts +200 -22
- package/corpus/templates/calendar/changelog/2026-06-25-booking-link-previews-now-show-the-content-directly-on-the-g.md +6 -0
- package/corpus/templates/calendar/server/lib/booking-og-image.ts +0 -2
- package/corpus/templates/forms/changelog/2026-06-25-form-previews-now-show-the-content-directly-on-the-grid-back.md +6 -0
- package/corpus/templates/forms/server/lib/form-og-image.ts +0 -2
- package/corpus/templates/plan/app/global.css +6 -11
- package/corpus/templates/plan/app/pages/PlansPage.tsx +12 -10
- package/corpus/templates/plan/changelog/2026-06-25-plan-access-request-pages-stay-visible-during-background-ref.md +6 -0
- package/corpus/templates/plan/changelog/2026-06-25-plan-documents-keep-their-full-reading-width-below-1200px-an.md +6 -0
- package/dist/a2a/task-store.d.ts.map +1 -1
- package/dist/a2a/task-store.js +19 -3
- package/dist/a2a/task-store.js.map +1 -1
- package/dist/agent/harness/store.d.ts.map +1 -1
- package/dist/agent/harness/store.js +63 -34
- package/dist/agent/harness/store.js.map +1 -1
- package/dist/agent/observational-memory/store.d.ts.map +1 -1
- package/dist/agent/observational-memory/store.js +30 -8
- package/dist/agent/observational-memory/store.js.map +1 -1
- package/dist/agent/run-store.d.ts.map +1 -1
- package/dist/agent/run-store.js +103 -46
- package/dist/agent/run-store.js.map +1 -1
- package/dist/application-state/store.d.ts.map +1 -1
- package/dist/application-state/store.js +28 -2
- package/dist/application-state/store.js.map +1 -1
- package/dist/audit/store.d.ts.map +1 -1
- package/dist/audit/store.js +18 -3
- package/dist/audit/store.js.map +1 -1
- package/dist/browser-sessions/store.d.ts.map +1 -1
- package/dist/browser-sessions/store.js +22 -7
- package/dist/browser-sessions/store.js.map +1 -1
- package/dist/chat-threads/store.d.ts.map +1 -1
- package/dist/chat-threads/store.js +51 -3
- package/dist/chat-threads/store.js.map +1 -1
- package/dist/checkpoints/store.d.ts.map +1 -1
- package/dist/checkpoints/store.js +12 -3
- package/dist/checkpoints/store.js.map +1 -1
- package/dist/cli/create.d.ts.map +1 -1
- package/dist/cli/create.js +9 -0
- package/dist/cli/create.js.map +1 -1
- package/dist/collab/routes.d.ts +2 -2
- package/dist/collab/storage.d.ts.map +1 -1
- package/dist/collab/storage.js +14 -2
- package/dist/collab/storage.js.map +1 -1
- package/dist/db/ddl-guard.d.ts +63 -0
- package/dist/db/ddl-guard.d.ts.map +1 -1
- package/dist/db/ddl-guard.js +85 -0
- package/dist/db/ddl-guard.js.map +1 -1
- package/dist/extensions/store.d.ts.map +1 -1
- package/dist/extensions/store.js +32 -3
- package/dist/extensions/store.js.map +1 -1
- package/dist/file-upload/actions/upload-image.d.ts +2 -2
- package/dist/integrations/a2a-continuations-store.d.ts.map +1 -1
- package/dist/integrations/a2a-continuations-store.js +44 -24
- package/dist/integrations/a2a-continuations-store.js.map +1 -1
- package/dist/integrations/config-store.d.ts.map +1 -1
- package/dist/integrations/config-store.js +17 -11
- package/dist/integrations/config-store.js.map +1 -1
- package/dist/integrations/pending-tasks-store.d.ts.map +1 -1
- package/dist/integrations/pending-tasks-store.js +24 -16
- package/dist/integrations/pending-tasks-store.js.map +1 -1
- package/dist/integrations/remote-commands-store.d.ts.map +1 -1
- package/dist/integrations/remote-commands-store.js +29 -21
- package/dist/integrations/remote-commands-store.js.map +1 -1
- package/dist/integrations/remote-devices-store.d.ts.map +1 -1
- package/dist/integrations/remote-devices-store.js +33 -18
- package/dist/integrations/remote-devices-store.js.map +1 -1
- package/dist/integrations/remote-push-store.d.ts.map +1 -1
- package/dist/integrations/remote-push-store.js +53 -31
- package/dist/integrations/remote-push-store.js.map +1 -1
- package/dist/integrations/remote-run-events-store.d.ts.map +1 -1
- package/dist/integrations/remote-run-events-store.js +26 -10
- package/dist/integrations/remote-run-events-store.js.map +1 -1
- package/dist/integrations/thread-mapping-store.d.ts.map +1 -1
- package/dist/integrations/thread-mapping-store.js +10 -2
- package/dist/integrations/thread-mapping-store.js.map +1 -1
- package/dist/mcp/connect-store.d.ts.map +1 -1
- package/dist/mcp/connect-store.js +34 -16
- package/dist/mcp/connect-store.js.map +1 -1
- package/dist/mcp/oauth-store.d.ts.map +1 -1
- package/dist/mcp/oauth-store.js +22 -7
- package/dist/mcp/oauth-store.js.map +1 -1
- package/dist/notifications/routes.d.ts +2 -2
- package/dist/notifications/store.d.ts.map +1 -1
- package/dist/notifications/store.js +15 -3
- package/dist/notifications/store.js.map +1 -1
- package/dist/oauth-tokens/store.d.ts.map +1 -1
- package/dist/oauth-tokens/store.js +33 -2
- package/dist/oauth-tokens/store.js.map +1 -1
- package/dist/observability/routes.d.ts +7 -7
- package/dist/observability/store.d.ts.map +1 -1
- package/dist/observability/store.js +72 -29
- package/dist/observability/store.js.map +1 -1
- package/dist/progress/routes.d.ts +1 -1
- package/dist/progress/store.d.ts.map +1 -1
- package/dist/progress/store.js +31 -16
- package/dist/progress/store.js.map +1 -1
- package/dist/provider-api/corpus-jobs-store.d.ts.map +1 -1
- package/dist/provider-api/corpus-jobs-store.js +16 -5
- package/dist/provider-api/corpus-jobs-store.js.map +1 -1
- package/dist/provider-api/custom-registry.d.ts.map +1 -1
- package/dist/provider-api/custom-registry.js +14 -4
- package/dist/provider-api/custom-registry.js.map +1 -1
- package/dist/provider-api/quota-governor.d.ts.map +1 -1
- package/dist/provider-api/quota-governor.js +12 -3
- package/dist/provider-api/quota-governor.js.map +1 -1
- package/dist/provider-api/staged-datasets-store.d.ts.map +1 -1
- package/dist/provider-api/staged-datasets-store.js +16 -5
- package/dist/provider-api/staged-datasets-store.js.map +1 -1
- package/dist/resources/handlers.d.ts +3 -3
- package/dist/resources/store.d.ts.map +1 -1
- package/dist/resources/store.js +56 -25
- package/dist/resources/store.js.map +1 -1
- package/dist/secrets/storage.d.ts.map +1 -1
- package/dist/secrets/storage.js +10 -14
- package/dist/secrets/storage.js.map +1 -1
- package/dist/server/agent-engine-api-key-route.d.ts +1 -1
- package/dist/server/agent-teams-run-queue.d.ts.map +1 -1
- package/dist/server/agent-teams-run-queue.js +15 -4
- package/dist/server/agent-teams-run-queue.js.map +1 -1
- package/dist/server/auth.d.ts.map +1 -1
- package/dist/server/auth.js +16 -2
- package/dist/server/auth.js.map +1 -1
- package/dist/server/better-auth-instance.d.ts.map +1 -1
- package/dist/server/better-auth-instance.js +55 -21
- package/dist/server/better-auth-instance.js.map +1 -1
- package/dist/server/embed-session.d.ts.map +1 -1
- package/dist/server/embed-session.js +15 -4
- package/dist/server/embed-session.js.map +1 -1
- package/dist/server/identity-sso-store.d.ts.map +1 -1
- package/dist/server/identity-sso-store.js +30 -11
- package/dist/server/identity-sso-store.js.map +1 -1
- package/dist/server/recap-image-store.d.ts.map +1 -1
- package/dist/server/recap-image-store.js +21 -7
- package/dist/server/recap-image-store.js.map +1 -1
- package/dist/server/transcribe-voice.d.ts +1 -1
- package/dist/settings/store.d.ts.map +1 -1
- package/dist/settings/store.js +11 -13
- package/dist/settings/store.js.map +1 -1
- package/dist/usage/store.d.ts.map +1 -1
- package/dist/usage/store.js +36 -11
- package/dist/usage/store.js.map +1 -1
- package/dist/workspace-connections/store.d.ts.map +1 -1
- package/dist/workspace-connections/store.js +57 -7
- package/dist/workspace-connections/store.js.map +1 -1
- package/package.json +1 -1
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
*/
|
|
15
15
|
|
|
16
16
|
import { getDbExec, intType, isPostgres, type DbExec } from "../db/client.js";
|
|
17
|
+
import { ensureTableExists, ensureIndexExists } from "../db/ddl-guard.js";
|
|
17
18
|
|
|
18
19
|
// ---------------------------------------------------------------------------
|
|
19
20
|
// Caps
|
|
@@ -32,7 +33,7 @@ async function ensureTables(): Promise<void> {
|
|
|
32
33
|
_initPromise = (async () => {
|
|
33
34
|
const db = getDbExec();
|
|
34
35
|
const integerType = intType();
|
|
35
|
-
|
|
36
|
+
const createDatasetsSql = `
|
|
36
37
|
CREATE TABLE IF NOT EXISTS staged_datasets (
|
|
37
38
|
id TEXT NOT NULL,
|
|
38
39
|
app_id TEXT NOT NULL,
|
|
@@ -45,18 +46,34 @@ async function ensureTables(): Promise<void> {
|
|
|
45
46
|
updated_at ${integerType} NOT NULL,
|
|
46
47
|
PRIMARY KEY (id)
|
|
47
48
|
)
|
|
48
|
-
|
|
49
|
-
|
|
49
|
+
`;
|
|
50
|
+
const createRowsSql = `
|
|
50
51
|
CREATE TABLE IF NOT EXISTS staged_dataset_rows (
|
|
51
52
|
dataset_id TEXT NOT NULL,
|
|
52
53
|
row_index ${integerType} NOT NULL,
|
|
53
54
|
row_data TEXT NOT NULL,
|
|
54
55
|
PRIMARY KEY (dataset_id, row_index)
|
|
55
56
|
)
|
|
56
|
-
|
|
57
|
+
`;
|
|
57
58
|
if (isPostgres()) {
|
|
58
|
-
|
|
59
|
+
// PG guard: probe via information_schema, only issue DDL if missing, bounded lock_timeout
|
|
60
|
+
await ensureTableExists("staged_datasets", createDatasetsSql);
|
|
61
|
+
await ensureTableExists("staged_dataset_rows", createRowsSql);
|
|
62
|
+
await widenPostgresIntegerColumns(db); // best-effort type widening — unchanged
|
|
63
|
+
await ensureIndexExists(
|
|
64
|
+
"staged_datasets_scope_idx",
|
|
65
|
+
`CREATE INDEX IF NOT EXISTS staged_datasets_scope_idx ON staged_datasets (app_id, owner_email)`,
|
|
66
|
+
);
|
|
67
|
+
await ensureIndexExists(
|
|
68
|
+
"staged_dataset_rows_dataset_idx",
|
|
69
|
+
`CREATE INDEX IF NOT EXISTS staged_dataset_rows_dataset_idx ON staged_dataset_rows (dataset_id)`,
|
|
70
|
+
);
|
|
71
|
+
return;
|
|
59
72
|
}
|
|
73
|
+
// SQLite (local dev): keep existing behavior
|
|
74
|
+
await db.execute(createDatasetsSql);
|
|
75
|
+
await db.execute(createRowsSql);
|
|
76
|
+
// Note: widenPostgresIntegerColumns is Postgres-only, skip on SQLite
|
|
60
77
|
for (const ddl of [
|
|
61
78
|
`CREATE INDEX IF NOT EXISTS staged_datasets_scope_idx ON staged_datasets (app_id, owner_email)`,
|
|
62
79
|
`CREATE INDEX IF NOT EXISTS staged_dataset_rows_dataset_idx ON staged_dataset_rows (dataset_id)`,
|
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
retryOnDdlRace,
|
|
8
8
|
type DbExec,
|
|
9
9
|
} from "../db/client.js";
|
|
10
|
+
import { ensureColumnExists, ensureTableExists } from "../db/ddl-guard.js";
|
|
10
11
|
import { widenIntColumnsToBigInt } from "../db/widen-columns.js";
|
|
11
12
|
import {
|
|
12
13
|
canUseLocalWorkspaceResourcePath,
|
|
@@ -751,37 +752,71 @@ async function ensureTable(): Promise<void> {
|
|
|
751
752
|
|
|
752
753
|
async function _doEnsureTable(): Promise<void> {
|
|
753
754
|
const client = getDbExec();
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
)
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
755
|
+
const createSql = `
|
|
756
|
+
CREATE TABLE IF NOT EXISTS resources (
|
|
757
|
+
id TEXT PRIMARY KEY,
|
|
758
|
+
path TEXT NOT NULL,
|
|
759
|
+
owner TEXT NOT NULL,
|
|
760
|
+
content TEXT NOT NULL DEFAULT '',
|
|
761
|
+
mime_type TEXT NOT NULL DEFAULT 'text/markdown',
|
|
762
|
+
size ${intType()} NOT NULL DEFAULT 0,
|
|
763
|
+
created_at ${intType()} NOT NULL,
|
|
764
|
+
updated_at ${intType()} NOT NULL,
|
|
765
|
+
created_by TEXT NOT NULL DEFAULT 'user',
|
|
766
|
+
visibility TEXT NOT NULL DEFAULT 'workspace',
|
|
767
|
+
thread_id TEXT,
|
|
768
|
+
run_id TEXT,
|
|
769
|
+
expires_at ${intType()},
|
|
770
|
+
metadata TEXT,
|
|
771
|
+
UNIQUE(path, owner)
|
|
772
|
+
)
|
|
773
|
+
`;
|
|
774
|
+
|
|
775
|
+
if (isPostgres()) {
|
|
776
|
+
// Hot path: the `resources` table and its additive columns are virtually
|
|
777
|
+
// always already present in production. Issuing `CREATE TABLE`/`ALTER
|
|
778
|
+
// TABLE` still takes an ACCESS EXCLUSIVE lock that, in a fresh
|
|
779
|
+
// background-worker process behind a concurrent connection on the shared
|
|
780
|
+
// Neon DB, can block ~indefinitely. The ensure* wrappers probe
|
|
781
|
+
// `information_schema` first (plain reads, no lock) and run DDL ONLY for
|
|
782
|
+
// what is actually missing, bounded by a transaction-scoped `lock_timeout`.
|
|
783
|
+
// If a swallowed lock-timeout leaves the schema still missing they RE-PROBE
|
|
784
|
+
// and THROW rather than letting init memoize success against absent schema.
|
|
785
|
+
await ensureTableExists("resources", createSql);
|
|
786
|
+
// Additive columns — guarded so the hot path (columns already present)
|
|
787
|
+
// skips the ACCESS EXCLUSIVE ALTER entirely.
|
|
788
|
+
const pgColumns: Array<[string, string]> = [
|
|
789
|
+
["created_by", "TEXT NOT NULL DEFAULT 'user'"],
|
|
790
|
+
["visibility", "TEXT NOT NULL DEFAULT 'workspace'"],
|
|
791
|
+
["thread_id", "TEXT"],
|
|
792
|
+
["run_id", "TEXT"],
|
|
793
|
+
["expires_at", intType()],
|
|
794
|
+
["metadata", "TEXT"],
|
|
795
|
+
];
|
|
796
|
+
for (const [col, def] of pgColumns) {
|
|
797
|
+
await ensureColumnExists(
|
|
798
|
+
"resources",
|
|
799
|
+
col,
|
|
800
|
+
`ALTER TABLE resources ADD COLUMN IF NOT EXISTS ${col} ${def}`,
|
|
801
|
+
);
|
|
802
|
+
}
|
|
803
|
+
} else {
|
|
804
|
+
// SQLite (local dev): no lock problem — keep the original behaviour using
|
|
805
|
+
// `retryOnDdlRace` + `ensureResourceColumn` (duplicate-column catch).
|
|
806
|
+
await retryOnDdlRace(() => client.execute(createSql));
|
|
807
|
+
await ensureResourceColumn(
|
|
808
|
+
client,
|
|
809
|
+
"created_by TEXT NOT NULL DEFAULT 'user'",
|
|
810
|
+
);
|
|
811
|
+
await ensureResourceColumn(
|
|
812
|
+
client,
|
|
813
|
+
"visibility TEXT NOT NULL DEFAULT 'workspace'",
|
|
814
|
+
);
|
|
815
|
+
await ensureResourceColumn(client, "thread_id TEXT");
|
|
816
|
+
await ensureResourceColumn(client, "run_id TEXT");
|
|
817
|
+
await ensureResourceColumn(client, `expires_at ${intType()}`);
|
|
818
|
+
await ensureResourceColumn(client, "metadata TEXT");
|
|
819
|
+
}
|
|
785
820
|
|
|
786
821
|
// Older deployments have 32-bit timestamp columns; on Postgres the
|
|
787
822
|
// `Date.now()` written on insert/update overflows int4. Widen in place
|
|
@@ -15,11 +15,7 @@
|
|
|
15
15
|
import { randomUUID } from "node:crypto";
|
|
16
16
|
|
|
17
17
|
import { getDbExec, isPostgres } from "../db/client.js";
|
|
18
|
-
import {
|
|
19
|
-
pgColumnExists,
|
|
20
|
-
pgTableExists,
|
|
21
|
-
runGuardedDdl,
|
|
22
|
-
} from "../db/ddl-guard.js";
|
|
18
|
+
import { ensureColumnExists, ensureTableExists } from "../db/ddl-guard.js";
|
|
23
19
|
import {
|
|
24
20
|
encryptSecretValue as encryptValue,
|
|
25
21
|
decryptSecretValue as decryptValue,
|
|
@@ -48,23 +44,23 @@ async function ensureTable(): Promise<void> {
|
|
|
48
44
|
// virtually always already present. Issuing `CREATE`/`ALTER` would
|
|
49
45
|
// still take an ACCESS EXCLUSIVE lock — which, in a fresh background
|
|
50
46
|
// worker process behind a concurrent connection on the shared Neon DB,
|
|
51
|
-
// can block ~indefinitely.
|
|
52
|
-
// read, no lock) and run DDL
|
|
53
|
-
//
|
|
54
|
-
// `lock_timeout` so a contended lock fails fast
|
|
55
|
-
if
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
47
|
+
// can block ~indefinitely. `ensureTableExists` / `ensureColumnExists`
|
|
48
|
+
// check `information_schema` first (a plain read, no lock) and run DDL
|
|
49
|
+
// ONLY for what is actually missing, wrapping any DDL that must run in a
|
|
50
|
+
// transaction-scoped `lock_timeout` so a contended lock fails fast. They
|
|
51
|
+
// also re-probe after a swallowed lock-timeout and THROW if the schema
|
|
52
|
+
// is still missing, so a timed-out DDL never poisons this init memo.
|
|
53
|
+
await ensureTableExists("app_secrets", createSql);
|
|
54
|
+
await ensureColumnExists(
|
|
55
|
+
"app_secrets",
|
|
56
|
+
"description",
|
|
57
|
+
`ALTER TABLE app_secrets ADD COLUMN IF NOT EXISTS description TEXT`,
|
|
58
|
+
);
|
|
59
|
+
await ensureColumnExists(
|
|
60
|
+
"app_secrets",
|
|
61
|
+
"url_allowlist",
|
|
62
|
+
`ALTER TABLE app_secrets ADD COLUMN IF NOT EXISTS url_allowlist TEXT`,
|
|
63
|
+
);
|
|
68
64
|
return;
|
|
69
65
|
}
|
|
70
66
|
|
|
@@ -14,7 +14,13 @@
|
|
|
14
14
|
* for UI/status; this table only drives dispatch, idempotent claiming, and
|
|
15
15
|
* cross-invocation continuation.
|
|
16
16
|
*/
|
|
17
|
-
import {
|
|
17
|
+
import {
|
|
18
|
+
getDbExec,
|
|
19
|
+
intType,
|
|
20
|
+
isPostgres,
|
|
21
|
+
retryOnDdlRace,
|
|
22
|
+
} from "../db/client.js";
|
|
23
|
+
import { ensureIndexExists, ensureTableExists } from "../db/ddl-guard.js";
|
|
18
24
|
|
|
19
25
|
/** Max cross-invocation continuations for one sub-agent run. Each continuation
|
|
20
26
|
* is one ~40s soft-timeout chunk, so ~60 ≈ ~40 minutes of wall-clock work. Two
|
|
@@ -77,8 +83,7 @@ async function ensureTable(): Promise<void> {
|
|
|
77
83
|
if (!_initPromise) {
|
|
78
84
|
_initPromise = (async () => {
|
|
79
85
|
const client = getDbExec();
|
|
80
|
-
|
|
81
|
-
client.execute(`
|
|
86
|
+
const createSql = `
|
|
82
87
|
CREATE TABLE IF NOT EXISTS agent_team_run_queue (
|
|
83
88
|
task_id TEXT PRIMARY KEY,
|
|
84
89
|
thread_id TEXT NOT NULL,
|
|
@@ -92,13 +97,20 @@ async function ensureTable(): Promise<void> {
|
|
|
92
97
|
created_at ${intType()} NOT NULL,
|
|
93
98
|
updated_at ${intType()} NOT NULL
|
|
94
99
|
)
|
|
95
|
-
|
|
96
|
-
)
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
100
|
+
`;
|
|
101
|
+
const indexSql = `CREATE INDEX IF NOT EXISTS idx_agent_team_run_queue_status ON agent_team_run_queue (status, updated_at)`;
|
|
102
|
+
|
|
103
|
+
// PG guard: probe information_schema / pg_indexes first (no lock), run
|
|
104
|
+
// DDL only when missing, bounded by a transaction-scoped lock_timeout.
|
|
105
|
+
if (isPostgres()) {
|
|
106
|
+
await ensureTableExists("agent_team_run_queue", createSql);
|
|
107
|
+
await ensureIndexExists("idx_agent_team_run_queue_status", indexSql);
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// SQLite (local dev): no lock problem — keep the original behaviour.
|
|
112
|
+
await retryOnDdlRace(() => client.execute(createSql));
|
|
113
|
+
await retryOnDdlRace(() => client.execute(indexSql));
|
|
102
114
|
})().catch((err) => {
|
|
103
115
|
_initPromise = undefined;
|
|
104
116
|
throw err;
|
|
@@ -72,6 +72,7 @@ import {
|
|
|
72
72
|
retryOnDdlRace,
|
|
73
73
|
describeDbError,
|
|
74
74
|
} from "../db/client.js";
|
|
75
|
+
import { ensureColumnExists, ensureTableExists } from "../db/ddl-guard.js";
|
|
75
76
|
import { widenIntColumnsToBigInt } from "../db/widen-columns.js";
|
|
76
77
|
import { readBody } from "../server/h3-helpers.js";
|
|
77
78
|
import { putSetting } from "../settings/store.js";
|
|
@@ -865,15 +866,32 @@ async function ensureSessionTable(): Promise<void> {
|
|
|
865
866
|
if (!_sessionInitPromise) {
|
|
866
867
|
_sessionInitPromise = (async () => {
|
|
867
868
|
const client = getDbExec();
|
|
868
|
-
|
|
869
|
-
client.execute(`
|
|
869
|
+
const createSql = `
|
|
870
870
|
CREATE TABLE IF NOT EXISTS sessions (
|
|
871
871
|
token TEXT PRIMARY KEY,
|
|
872
872
|
email TEXT,
|
|
873
873
|
created_at ${intType()} NOT NULL
|
|
874
874
|
)
|
|
875
|
-
|
|
876
|
-
|
|
875
|
+
`;
|
|
876
|
+
|
|
877
|
+
// PG guard: probe information_schema first (no lock), run DDL only when
|
|
878
|
+
// missing, bounded by a transaction-scoped lock_timeout.
|
|
879
|
+
if (isPostgres()) {
|
|
880
|
+
await ensureTableExists("sessions", createSql);
|
|
881
|
+
await ensureColumnExists(
|
|
882
|
+
"sessions",
|
|
883
|
+
"email",
|
|
884
|
+
`ALTER TABLE sessions ADD COLUMN IF NOT EXISTS email TEXT`,
|
|
885
|
+
);
|
|
886
|
+
// Older deployments have a 32-bit `created_at`; on Postgres the
|
|
887
|
+
// `Date.now()` written on session create overflows int4. Widen in place
|
|
888
|
+
// (no-op once done / on fresh DBs).
|
|
889
|
+
await widenIntColumnsToBigInt("sessions", ["created_at"]);
|
|
890
|
+
return;
|
|
891
|
+
}
|
|
892
|
+
|
|
893
|
+
// SQLite (local dev): no lock problem — keep the original behaviour.
|
|
894
|
+
await retryOnDdlRace(() => client.execute(createSql));
|
|
877
895
|
try {
|
|
878
896
|
await client.execute(`ALTER TABLE sessions ADD COLUMN email TEXT`);
|
|
879
897
|
} catch {
|
|
@@ -39,6 +39,7 @@ import {
|
|
|
39
39
|
neonPoolMax,
|
|
40
40
|
attachNeonPoolErrorLogger,
|
|
41
41
|
} from "../db/client.js";
|
|
42
|
+
import { ensureTableExists } from "../db/ddl-guard.js";
|
|
42
43
|
import { saveOAuthTokens } from "../oauth-tokens/store.js";
|
|
43
44
|
import { acceptPendingInvitationsForEmail } from "../org/accept-pending.js";
|
|
44
45
|
import { autoJoinDomainMatchingOrgs } from "../org/auto-join-domain.js";
|
|
@@ -693,29 +694,62 @@ async function mirrorGoogleAccountToOAuthTokens(account: {
|
|
|
693
694
|
|
|
694
695
|
async function ensureBetterAuthTables(): Promise<void> {
|
|
695
696
|
const db = getDbExec();
|
|
696
|
-
|
|
697
|
-
|
|
697
|
+
|
|
698
|
+
// PG guard: probe information_schema first (no lock) for each table; run
|
|
699
|
+
// DDL only when missing, bounded by a transaction-scoped lock_timeout.
|
|
700
|
+
// Probe names are UNQUOTED (what information_schema.tables.table_name stores);
|
|
701
|
+
// createSql keeps the QUOTED "user"/"session"/… form required by Postgres.
|
|
702
|
+
if (isPostgres()) {
|
|
703
|
+
const pgTables: Array<[name: string, createSql: string]> = [
|
|
704
|
+
[
|
|
705
|
+
"user",
|
|
698
706
|
`CREATE TABLE IF NOT EXISTS "user" (id TEXT PRIMARY KEY, name TEXT NOT NULL, email TEXT NOT NULL UNIQUE, email_verified BOOLEAN NOT NULL DEFAULT FALSE, image TEXT, created_at TIMESTAMPTZ NOT NULL, updated_at TIMESTAMPTZ NOT NULL)`,
|
|
707
|
+
],
|
|
708
|
+
[
|
|
709
|
+
"session",
|
|
699
710
|
`CREATE TABLE IF NOT EXISTS "session" (id TEXT PRIMARY KEY, expires_at TIMESTAMPTZ NOT NULL, token TEXT NOT NULL UNIQUE, created_at TIMESTAMPTZ NOT NULL, updated_at TIMESTAMPTZ NOT NULL, ip_address TEXT, user_agent TEXT, user_id TEXT NOT NULL, active_organization_id TEXT)`,
|
|
711
|
+
],
|
|
712
|
+
[
|
|
713
|
+
"account",
|
|
700
714
|
`CREATE TABLE IF NOT EXISTS "account" (id TEXT PRIMARY KEY, account_id TEXT NOT NULL, provider_id TEXT NOT NULL, user_id TEXT NOT NULL, access_token TEXT, refresh_token TEXT, id_token TEXT, access_token_expires_at TIMESTAMPTZ, refresh_token_expires_at TIMESTAMPTZ, scope TEXT, password TEXT, created_at TIMESTAMPTZ NOT NULL, updated_at TIMESTAMPTZ NOT NULL)`,
|
|
715
|
+
],
|
|
716
|
+
[
|
|
717
|
+
"verification",
|
|
701
718
|
`CREATE TABLE IF NOT EXISTS "verification" (id TEXT PRIMARY KEY, identifier TEXT NOT NULL, value TEXT NOT NULL, expires_at TIMESTAMPTZ NOT NULL, created_at TIMESTAMPTZ NOT NULL, updated_at TIMESTAMPTZ NOT NULL)`,
|
|
719
|
+
],
|
|
720
|
+
[
|
|
721
|
+
"organization",
|
|
702
722
|
`CREATE TABLE IF NOT EXISTS "organization" (id TEXT PRIMARY KEY, name TEXT NOT NULL, slug TEXT NOT NULL UNIQUE, logo TEXT, metadata TEXT, created_at TIMESTAMPTZ NOT NULL, updated_at TIMESTAMPTZ NOT NULL)`,
|
|
723
|
+
],
|
|
724
|
+
[
|
|
725
|
+
"member",
|
|
703
726
|
`CREATE TABLE IF NOT EXISTS "member" (id TEXT PRIMARY KEY, organization_id TEXT NOT NULL, user_id TEXT NOT NULL, role TEXT NOT NULL DEFAULT 'member', created_at TIMESTAMPTZ NOT NULL, updated_at TIMESTAMPTZ NOT NULL)`,
|
|
727
|
+
],
|
|
728
|
+
[
|
|
729
|
+
"invitation",
|
|
704
730
|
`CREATE TABLE IF NOT EXISTS "invitation" (id TEXT PRIMARY KEY, organization_id TEXT NOT NULL, email TEXT NOT NULL, role TEXT, status TEXT NOT NULL DEFAULT 'pending', expires_at TIMESTAMPTZ NOT NULL, inviter_id TEXT NOT NULL, created_at TIMESTAMPTZ NOT NULL, updated_at TIMESTAMPTZ NOT NULL)`,
|
|
731
|
+
],
|
|
732
|
+
[
|
|
733
|
+
"jwks",
|
|
705
734
|
`CREATE TABLE IF NOT EXISTS "jwks" (id TEXT PRIMARY KEY, public_key TEXT NOT NULL, private_key TEXT NOT NULL, created_at TIMESTAMPTZ NOT NULL, expires_at TIMESTAMPTZ)`,
|
|
706
|
-
]
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
735
|
+
],
|
|
736
|
+
];
|
|
737
|
+
for (const [name, sql] of pgTables) await ensureTableExists(name, sql);
|
|
738
|
+
return;
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
// SQLite (local dev): no lock problem — keep the original behaviour.
|
|
742
|
+
const sqliteStatements = [
|
|
743
|
+
`CREATE TABLE IF NOT EXISTS user (id TEXT PRIMARY KEY, name TEXT NOT NULL, email TEXT NOT NULL UNIQUE, email_verified INTEGER NOT NULL DEFAULT 0, image TEXT, created_at INTEGER NOT NULL, updated_at INTEGER NOT NULL)`,
|
|
744
|
+
`CREATE TABLE IF NOT EXISTS session (id TEXT PRIMARY KEY, expires_at INTEGER NOT NULL, token TEXT NOT NULL UNIQUE, created_at INTEGER NOT NULL, updated_at INTEGER NOT NULL, ip_address TEXT, user_agent TEXT, user_id TEXT NOT NULL, active_organization_id TEXT)`,
|
|
745
|
+
`CREATE TABLE IF NOT EXISTS account (id TEXT PRIMARY KEY, account_id TEXT NOT NULL, provider_id TEXT NOT NULL, user_id TEXT NOT NULL, access_token TEXT, refresh_token TEXT, id_token TEXT, access_token_expires_at INTEGER, refresh_token_expires_at INTEGER, scope TEXT, password TEXT, created_at INTEGER NOT NULL, updated_at INTEGER NOT NULL)`,
|
|
746
|
+
`CREATE TABLE IF NOT EXISTS verification (id TEXT PRIMARY KEY, identifier TEXT NOT NULL, value TEXT NOT NULL, expires_at INTEGER NOT NULL, created_at INTEGER NOT NULL, updated_at INTEGER NOT NULL)`,
|
|
747
|
+
`CREATE TABLE IF NOT EXISTS organization (id TEXT PRIMARY KEY, name TEXT NOT NULL, slug TEXT NOT NULL UNIQUE, logo TEXT, metadata TEXT, created_at INTEGER NOT NULL, updated_at INTEGER NOT NULL)`,
|
|
748
|
+
`CREATE TABLE IF NOT EXISTS member (id TEXT PRIMARY KEY, organization_id TEXT NOT NULL, user_id TEXT NOT NULL, role TEXT NOT NULL DEFAULT 'member', created_at INTEGER NOT NULL, updated_at INTEGER NOT NULL)`,
|
|
749
|
+
`CREATE TABLE IF NOT EXISTS invitation (id TEXT PRIMARY KEY, organization_id TEXT NOT NULL, email TEXT NOT NULL, role TEXT, status TEXT NOT NULL DEFAULT 'pending', expires_at INTEGER NOT NULL, inviter_id TEXT NOT NULL, created_at INTEGER NOT NULL, updated_at INTEGER NOT NULL)`,
|
|
750
|
+
`CREATE TABLE IF NOT EXISTS jwks (id TEXT PRIMARY KEY, public_key TEXT NOT NULL, private_key TEXT NOT NULL, created_at INTEGER NOT NULL, expires_at INTEGER)`,
|
|
751
|
+
];
|
|
752
|
+
for (const sql of sqliteStatements) await db.execute(sql);
|
|
719
753
|
}
|
|
720
754
|
|
|
721
755
|
/**
|
|
@@ -9,7 +9,8 @@ import {
|
|
|
9
9
|
setResponseHeader,
|
|
10
10
|
} from "h3";
|
|
11
11
|
|
|
12
|
-
import { getDbExec, intType } from "../db/client.js";
|
|
12
|
+
import { getDbExec, intType, isPostgres } from "../db/client.js";
|
|
13
|
+
import { ensureTableExists } from "../db/ddl-guard.js";
|
|
13
14
|
import {
|
|
14
15
|
EMBED_MODE_QUERY_PARAM,
|
|
15
16
|
EMBED_SESSION_COOKIE,
|
|
@@ -103,8 +104,10 @@ export type ResolvedEmbedSession = {
|
|
|
103
104
|
async function ensureTable(): Promise<void> {
|
|
104
105
|
if (!_initPromise) {
|
|
105
106
|
_initPromise = (async () => {
|
|
106
|
-
|
|
107
|
-
|
|
107
|
+
// Build the CREATE SQL here (not at module scope) so intType() runs at
|
|
108
|
+
// RUNTIME, not import time — a module-scope call breaks any consumer whose
|
|
109
|
+
// db/client mock doesn't stub intType (e.g. db-admin specs).
|
|
110
|
+
const embedTicketsCreateSql = `
|
|
108
111
|
CREATE TABLE IF NOT EXISTS agent_native_embed_tickets (
|
|
109
112
|
ticket_hash TEXT PRIMARY KEY,
|
|
110
113
|
owner_email TEXT NOT NULL,
|
|
@@ -115,7 +118,19 @@ async function ensureTable(): Promise<void> {
|
|
|
115
118
|
expires_at ${intType()} NOT NULL,
|
|
116
119
|
consumed_at ${intType()}
|
|
117
120
|
)
|
|
118
|
-
|
|
121
|
+
`;
|
|
122
|
+
if (isPostgres()) {
|
|
123
|
+
// PG guard: probe → guarded DDL → re-probe; skips lock on already-migrated path
|
|
124
|
+
await ensureTableExists(
|
|
125
|
+
"agent_native_embed_tickets",
|
|
126
|
+
embedTicketsCreateSql,
|
|
127
|
+
);
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// SQLite (local dev): no lock problem — keep the original behaviour.
|
|
132
|
+
const client = getDbExec();
|
|
133
|
+
await client.execute(embedTicketsCreateSql);
|
|
119
134
|
})().catch((err) => {
|
|
120
135
|
_initPromise = undefined;
|
|
121
136
|
throw err;
|
|
@@ -24,7 +24,13 @@
|
|
|
24
24
|
|
|
25
25
|
import { randomBytes } from "node:crypto";
|
|
26
26
|
|
|
27
|
-
import {
|
|
27
|
+
import {
|
|
28
|
+
getDbExec,
|
|
29
|
+
isConnectionError,
|
|
30
|
+
intType,
|
|
31
|
+
isPostgres,
|
|
32
|
+
} from "../db/client.js";
|
|
33
|
+
import { ensureTableExists } from "../db/ddl-guard.js";
|
|
28
34
|
|
|
29
35
|
let _initPromise: Promise<void> | undefined;
|
|
30
36
|
|
|
@@ -98,13 +104,11 @@ export const SSO_STATE_TTL_MS = 10 * 60_000;
|
|
|
98
104
|
export const SSO_LOGIN_MAX = 60;
|
|
99
105
|
export const SSO_LOGIN_WINDOW_MS = 60_000;
|
|
100
106
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
// deploy context (preview/branch/prod) for hosted templates.
|
|
107
|
-
await client.execute(`
|
|
107
|
+
// Build the CREATE SQL lazily (not at module scope) so intType() runs at
|
|
108
|
+
// RUNTIME, not import time — a module-scope call breaks any consumer whose
|
|
109
|
+
// db/client mock doesn't stub intType (e.g. db-admin specs).
|
|
110
|
+
function buildIdentitySsoStateCreateSql(): string {
|
|
111
|
+
return `
|
|
108
112
|
CREATE TABLE IF NOT EXISTS identity_sso_state (
|
|
109
113
|
state TEXT PRIMARY KEY,
|
|
110
114
|
return_path TEXT,
|
|
@@ -112,13 +116,38 @@ async function ensureTable(): Promise<void> {
|
|
|
112
116
|
expires_at ${intType()},
|
|
113
117
|
consumed_at ${intType()}
|
|
114
118
|
)
|
|
115
|
-
|
|
116
|
-
|
|
119
|
+
`;
|
|
120
|
+
}
|
|
121
|
+
function buildIdentitySsoJtiCreateSql(): string {
|
|
122
|
+
return `
|
|
117
123
|
CREATE TABLE IF NOT EXISTS identity_sso_jti (
|
|
118
124
|
jti TEXT PRIMARY KEY,
|
|
119
125
|
seen_at ${intType()}
|
|
120
126
|
)
|
|
121
|
-
|
|
127
|
+
`;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
async function ensureTable(): Promise<void> {
|
|
131
|
+
if (!_initPromise) {
|
|
132
|
+
_initPromise = (async () => {
|
|
133
|
+
const client = getDbExec();
|
|
134
|
+
const identitySsoStateCreateSql = buildIdentitySsoStateCreateSql();
|
|
135
|
+
const identitySsoJtiCreateSql = buildIdentitySsoJtiCreateSql();
|
|
136
|
+
// Additive only. Never DROP / ALTER — this DB is shared across every
|
|
137
|
+
// deploy context (preview/branch/prod) for hosted templates.
|
|
138
|
+
if (isPostgres()) {
|
|
139
|
+
// PG guard: probe → guarded DDL → re-probe; skips lock on already-migrated path
|
|
140
|
+
await ensureTableExists(
|
|
141
|
+
"identity_sso_state",
|
|
142
|
+
identitySsoStateCreateSql,
|
|
143
|
+
);
|
|
144
|
+
await ensureTableExists("identity_sso_jti", identitySsoJtiCreateSql);
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// SQLite (local dev): no lock problem — keep the original behaviour.
|
|
149
|
+
await client.execute(identitySsoStateCreateSql);
|
|
150
|
+
await client.execute(identitySsoJtiCreateSql);
|
|
122
151
|
})().catch((err) => {
|
|
123
152
|
// Don't cache a rejection — let the next caller retry a fresh init.
|
|
124
153
|
_initPromise = undefined;
|
|
@@ -19,7 +19,13 @@
|
|
|
19
19
|
*/
|
|
20
20
|
import { randomBytes } from "node:crypto";
|
|
21
21
|
|
|
22
|
-
import {
|
|
22
|
+
import {
|
|
23
|
+
getDbExec,
|
|
24
|
+
intType,
|
|
25
|
+
isPostgres,
|
|
26
|
+
retryOnDdlRace,
|
|
27
|
+
} from "../db/client.js";
|
|
28
|
+
import { ensureTableExists } from "../db/ddl-guard.js";
|
|
23
29
|
|
|
24
30
|
/** Maximum stored image size (~5 MB of raw PNG bytes). */
|
|
25
31
|
export const RECAP_IMAGE_MAX_BYTES = 5 * 1024 * 1024;
|
|
@@ -44,12 +50,11 @@ const TOKEN_PATTERN = /^[0-9a-f]{32,128}$/;
|
|
|
44
50
|
|
|
45
51
|
let _initPromise: Promise<void> | undefined;
|
|
46
52
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
client.execute(`
|
|
53
|
+
// Build the CREATE SQL lazily (not at module scope) so intType() runs at
|
|
54
|
+
// RUNTIME, not import time — a module-scope call breaks any consumer whose
|
|
55
|
+
// db/client mock doesn't stub intType (e.g. db-admin specs).
|
|
56
|
+
function buildRecapImagesCreateSql(): string {
|
|
57
|
+
return `
|
|
53
58
|
CREATE TABLE IF NOT EXISTS recap_images (
|
|
54
59
|
token TEXT PRIMARY KEY,
|
|
55
60
|
png_base64 TEXT NOT NULL,
|
|
@@ -58,8 +63,22 @@ export async function ensureRecapImageTable(): Promise<void> {
|
|
|
58
63
|
owner_email TEXT,
|
|
59
64
|
created_at ${intType()} NOT NULL
|
|
60
65
|
)
|
|
61
|
-
|
|
62
|
-
|
|
66
|
+
`;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export async function ensureRecapImageTable(): Promise<void> {
|
|
70
|
+
if (!_initPromise) {
|
|
71
|
+
_initPromise = (async () => {
|
|
72
|
+
const recapImagesCreateSql = buildRecapImagesCreateSql();
|
|
73
|
+
if (isPostgres()) {
|
|
74
|
+
// PG guard: probe → guarded DDL → re-probe; skips lock on already-migrated path
|
|
75
|
+
await ensureTableExists("recap_images", recapImagesCreateSql);
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// SQLite (local dev): no lock problem — keep the original behaviour.
|
|
80
|
+
const client = getDbExec();
|
|
81
|
+
await retryOnDdlRace(() => client.execute(recapImagesCreateSql));
|
|
63
82
|
})().catch((error) => {
|
|
64
83
|
// Allow a later call to retry if the first init lost a DDL race.
|
|
65
84
|
_initPromise = undefined;
|
|
@@ -1,11 +1,7 @@
|
|
|
1
1
|
import { EventEmitter } from "events";
|
|
2
2
|
|
|
3
3
|
import { getDbExec, isPostgres, intType } from "../db/client.js";
|
|
4
|
-
import {
|
|
5
|
-
pgIndexExists,
|
|
6
|
-
pgTableExists,
|
|
7
|
-
runGuardedDdl,
|
|
8
|
-
} from "../db/ddl-guard.js";
|
|
4
|
+
import { ensureIndexExists, ensureTableExists } from "../db/ddl-guard.js";
|
|
9
5
|
import { widenIntColumnsToBigInt } from "../db/widen-columns.js";
|
|
10
6
|
|
|
11
7
|
let _initPromise: Promise<void> | undefined;
|
|
@@ -39,26 +35,25 @@ async function ensureTable(): Promise<void> {
|
|
|
39
35
|
// `CREATE INDEX` still takes a lock that, in a fresh background-worker
|
|
40
36
|
// process behind a concurrent connection on the shared Neon DB, can
|
|
41
37
|
// block ~indefinitely (ACCESS EXCLUSIVE for CREATE TABLE; a write-
|
|
42
|
-
// blocking SHARE lock for CREATE INDEX).
|
|
43
|
-
// `pg_indexes` first
|
|
44
|
-
//
|
|
45
|
-
// with a transaction-scoped `lock_timeout
|
|
46
|
-
//
|
|
47
|
-
//
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
38
|
+
// blocking SHARE lock for CREATE INDEX). `ensureTableExists` /
|
|
39
|
+
// `ensureIndexExists` probe `information_schema`/`pg_indexes` first
|
|
40
|
+
// (plain reads, no lock) and run DDL ONLY for what is actually missing,
|
|
41
|
+
// bounding any DDL with a transaction-scoped `lock_timeout`. They also
|
|
42
|
+
// re-probe after a swallowed lock-timeout and THROW if the schema is
|
|
43
|
+
// still missing, so a timed-out DDL never poisons this init memo with
|
|
44
|
+
// missing schema. `settingsTable()` is `public.settings` on Postgres;
|
|
45
|
+
// the existence checks use the unqualified table name.
|
|
46
|
+
await ensureTableExists("settings", createSql);
|
|
51
47
|
// Older deployments (pre BIGINT-compat) have a 32-bit `updated_at`; on
|
|
52
48
|
// Postgres the `Date.now()` written on every setSetting overflows int4.
|
|
53
49
|
// widenIntColumnsToBigInt already probes information_schema and only
|
|
54
50
|
// ALTERs columns that are still int4 — a no-op on fresh/widened DBs.
|
|
55
51
|
await widenIntColumnsToBigInt("settings", ["updated_at"]);
|
|
56
52
|
// Index for the poll watermark query: `SELECT MAX(updated_at)`.
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
}
|
|
53
|
+
await ensureIndexExists(
|
|
54
|
+
"settings_updated_at_idx",
|
|
55
|
+
`CREATE INDEX IF NOT EXISTS settings_updated_at_idx ON ${table} (updated_at)`,
|
|
56
|
+
);
|
|
62
57
|
return;
|
|
63
58
|
}
|
|
64
59
|
|