@rebasepro/server-postgres 0.24.0 → 0.24.1-canary.gcb918fc

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.
Files changed (172) hide show
  1. package/README.md +16 -0
  2. package/dist/{BranchService-BRt78gfa.js → BranchService-Bsn2wfaj.js} +3 -2
  3. package/dist/{BranchService-BRt78gfa.js.map → BranchService-Bsn2wfaj.js.map} +1 -1
  4. package/dist/PostgresBackendDriver.d.ts +91 -2
  5. package/dist/PostgresBootstrapper.d.ts +101 -2
  6. package/dist/auth/ensure-tables.d.ts +12 -0
  7. package/dist/auth/services.d.ts +6 -0
  8. package/dist/backup/backup-cli.d.ts +14 -0
  9. package/dist/backup/backup-service.d.ts +2 -2
  10. package/dist/backup/pg-binary.d.ts +5 -0
  11. package/dist/{backup-cli-DW5p9_zv.js → backup-cli-DnpJi6I7.js} +37 -29
  12. package/dist/backup-cli-DnpJi6I7.js.map +1 -0
  13. package/dist/{backup-service-EwcDVG-8.js → backup-service-BTBB7RXn.js} +55 -21
  14. package/dist/backup-service-BTBB7RXn.js.map +1 -0
  15. package/dist/boot-ddl-DVpsFN6r.js +105 -0
  16. package/dist/boot-ddl-DVpsFN6r.js.map +1 -0
  17. package/dist/cli-console-Ca6ZlAVU.js +154 -0
  18. package/dist/cli-console-Ca6ZlAVU.js.map +1 -0
  19. package/dist/cli-console.d.ts +39 -0
  20. package/dist/cli-errors-CaI4ntyD.js +1919 -0
  21. package/dist/cli-errors-CaI4ntyD.js.map +1 -0
  22. package/dist/cli-errors.d.ts +51 -5
  23. package/dist/{cli-flags-BglvpjHv.js → cli-flags-JNI-pwSf.js} +6 -3
  24. package/dist/cli-flags-JNI-pwSf.js.map +1 -0
  25. package/dist/cli-helpers.d.ts +40 -0
  26. package/dist/cli-output.d.ts +16 -28
  27. package/dist/cli-push.d.ts +87 -0
  28. package/dist/cli.js +566 -182
  29. package/dist/cli.js.map +1 -1
  30. package/dist/{column-plan-helpers-1-LQD0yI.js → column-plan-helpers-drZIsSNf.js} +10 -4
  31. package/dist/{column-plan-helpers-1-LQD0yI.js.map → column-plan-helpers-drZIsSNf.js.map} +1 -1
  32. package/dist/{databasePoolManager-Bj5FbeAs.js → connection-C9mT8bgf.js} +24 -112
  33. package/dist/connection-C9mT8bgf.js.map +1 -0
  34. package/dist/connection.d.ts +13 -2
  35. package/dist/databasePoolManager-BIuewidb.js +119 -0
  36. package/dist/databasePoolManager-BIuewidb.js.map +1 -0
  37. package/dist/{ensure-collection-tables-kkkHk8oo.js → deferred-index-build-DP1_SNFo.js} +484 -346
  38. package/dist/deferred-index-build-DP1_SNFo.js.map +1 -0
  39. package/dist/{doctor-C-sYWbmt.js → doctor-C4WiftHm.js} +63 -52
  40. package/dist/doctor-C4WiftHm.js.map +1 -0
  41. package/dist/ensure-collection-policies-CXpbeNag.js +541 -0
  42. package/dist/ensure-collection-policies-CXpbeNag.js.map +1 -0
  43. package/dist/ensure-collection-tables-BhsU6qUE.js +532 -0
  44. package/dist/ensure-collection-tables-BhsU6qUE.js.map +1 -0
  45. package/dist/{ensure-tables-BmI_tRxc.js → ensure-tables-CVm_yvzb.js} +173 -77
  46. package/dist/ensure-tables-CVm_yvzb.js.map +1 -0
  47. package/dist/enum-labels-kept-D8fI1MoX.js +56 -0
  48. package/dist/enum-labels-kept-D8fI1MoX.js.map +1 -0
  49. package/dist/generate-drizzle-schema-Bz52jH2h.js +57 -0
  50. package/dist/generate-drizzle-schema-Bz52jH2h.js.map +1 -0
  51. package/dist/{generate-drizzle-schema-logic-sSFDp6LR.js → generate-drizzle-schema-logic-B74WN37S.js} +5 -5
  52. package/dist/generate-drizzle-schema-logic-B74WN37S.js.map +1 -0
  53. package/dist/generate-files-CdEckX6c.js +199 -0
  54. package/dist/generate-files-CdEckX6c.js.map +1 -0
  55. package/dist/{generate-postgres-ddl-logic-BJsLaVNX.js → generate-postgres-ddl-logic-B64O2oZF.js} +3 -3
  56. package/dist/{generate-postgres-ddl-logic-BJsLaVNX.js.map → generate-postgres-ddl-logic-B64O2oZF.js.map} +1 -1
  57. package/dist/generated-sql.d.ts +8 -2
  58. package/dist/history/HistoryService.d.ts +64 -5
  59. package/dist/history/ensure-history-table.d.ts +63 -4
  60. package/dist/index.d.ts +1 -0
  61. package/dist/index.es.js +139 -19236
  62. package/dist/index.es.js.map +1 -1
  63. package/dist/{introspect-db-logic-kCETE8TY.js → introspect-db-logic-C-Di8dA9.js} +44 -6
  64. package/dist/introspect-db-logic-C-Di8dA9.js.map +1 -0
  65. package/dist/{introspect-db-queries-C_Q5VgQw.js → introspect-db-queries-D6kAqReI.js} +23 -1
  66. package/dist/introspect-db-queries-D6kAqReI.js.map +1 -0
  67. package/dist/open-rebase-CbOur0TU.js +20858 -0
  68. package/dist/open-rebase-CbOur0TU.js.map +1 -0
  69. package/dist/open-rebase.d.ts +86 -0
  70. package/dist/pg-error-utils-vJezlXdI.js +388 -0
  71. package/dist/pg-error-utils-vJezlXdI.js.map +1 -0
  72. package/dist/{plan-schema-DU9exq6C.js → plan-schema-B1YF_tYq.js} +151 -41
  73. package/dist/plan-schema-B1YF_tYq.js.map +1 -0
  74. package/dist/{policy-drift-B-J2hhm0.js → policy-drift-Dm8i-s1J.js} +23 -6
  75. package/dist/policy-drift-Dm8i-s1J.js.map +1 -0
  76. package/dist/push-index-prebuild-Db4AQF4i.js +45 -0
  77. package/dist/push-index-prebuild-Db4AQF4i.js.map +1 -0
  78. package/dist/push-legacy-renames-BK6ilQq2.js +34 -0
  79. package/dist/push-legacy-renames-BK6ilQq2.js.map +1 -0
  80. package/dist/{render-ddl-Ds2t_d9V.js → render-ddl-CtC3y9Ol.js} +8 -4
  81. package/dist/render-ddl-CtC3y9Ol.js.map +1 -0
  82. package/dist/{rls-bootstrap-sql-_KNnjanK.js → rls-bootstrap-sql-RQUljnJ1.js} +125 -1270
  83. package/dist/rls-bootstrap-sql-RQUljnJ1.js.map +1 -0
  84. package/dist/{rls-enforcement-CfXOJJaW.js → rls-enforcement-DvzqlaL1.js} +129 -17
  85. package/dist/rls-enforcement-DvzqlaL1.js.map +1 -0
  86. package/dist/schema/auth-schema.d.ts +2 -2
  87. package/dist/schema/boot-ddl.d.ts +47 -0
  88. package/dist/schema/collection-index.d.ts +45 -0
  89. package/dist/schema/column-plan-helpers.d.ts +5 -0
  90. package/dist/schema/deferred-index-build.d.ts +131 -0
  91. package/dist/schema/destructive-sql.d.ts +7 -0
  92. package/dist/schema/doctor-cli.js +110 -6
  93. package/dist/schema/doctor-cli.js.map +1 -1
  94. package/dist/schema/doctor-search-scale.d.ts +45 -0
  95. package/dist/schema/doctor.d.ts +20 -1
  96. package/dist/schema/ensure-collection-policies.d.ts +73 -35
  97. package/dist/schema/ensure-collection-tables.d.ts +56 -3
  98. package/dist/schema/enum-labels-kept.d.ts +26 -0
  99. package/dist/schema/generate-drizzle-schema.js +1 -1
  100. package/dist/schema/generate-files.d.ts +44 -0
  101. package/dist/schema/generate-next-step.d.ts +0 -13
  102. package/dist/schema/generate-postgres-ddl.js +10 -81
  103. package/dist/schema/generate-postgres-ddl.js.map +1 -1
  104. package/dist/schema/introspect-db-logic.d.ts +12 -0
  105. package/dist/schema/introspect-db-queries.d.ts +11 -1
  106. package/dist/schema/introspect-db-structure.d.ts +10 -0
  107. package/dist/schema/introspect-db.js +4 -5
  108. package/dist/schema/introspect-db.js.map +1 -1
  109. package/dist/schema/migration-post-process.d.ts +34 -0
  110. package/dist/schema/plan/diff-plan.d.ts +126 -0
  111. package/dist/schema/plan/types.d.ts +8 -0
  112. package/dist/schema/push-index-prebuild.d.ts +25 -0
  113. package/dist/schema/push-legacy-renames.d.ts +20 -0
  114. package/dist/schema/rls-bootstrap-sql.d.ts +15 -0
  115. package/dist/security/rls-enforcement.d.ts +32 -4
  116. package/dist/services/FetchService.d.ts +58 -8
  117. package/dist/services/PersistService.d.ts +15 -0
  118. package/dist/services/RelationService.d.ts +88 -3
  119. package/dist/services/cdc/CdcListener.d.ts +17 -6
  120. package/dist/services/cdc/trigger-cdc.d.ts +151 -27
  121. package/dist/services/channel-bus/index.d.ts +1 -1
  122. package/dist/services/channel-history.d.ts +15 -4
  123. package/dist/services/collection-helpers.d.ts +15 -0
  124. package/dist/services/field-viewer.d.ts +15 -1
  125. package/dist/services/pg-notify-listener.d.ts +7 -0
  126. package/dist/services/realtimeService.d.ts +314 -14
  127. package/dist/services/row-pipeline.d.ts +16 -9
  128. package/dist/services/socket-liveness.d.ts +42 -2
  129. package/dist/services/sql-script.d.ts +207 -3
  130. package/dist/services/vector-iterative-scan.d.ts +40 -0
  131. package/dist/services/write-depth.d.ts +6 -0
  132. package/dist/services/write-transaction-scope.d.ts +9 -0
  133. package/dist/socket-opt-ins.d.ts +64 -0
  134. package/dist/source-DBDbBTL4.js +811 -0
  135. package/dist/source-DBDbBTL4.js.map +1 -0
  136. package/dist/tsx-loader.d.ts +14 -0
  137. package/dist/utils/drizzle-conditions.d.ts +21 -1
  138. package/dist/utils/error-cause.d.ts +7 -0
  139. package/dist/utils/pg-error-utils.d.ts +2 -4
  140. package/dist/utils/result-rows.d.ts +10 -0
  141. package/dist/websocket-BLGc5yhs.js +1604 -0
  142. package/dist/websocket-BLGc5yhs.js.map +1 -0
  143. package/dist/websocket.d.ts +13 -0
  144. package/package.json +7 -7
  145. package/dist/backup-cli-DW5p9_zv.js.map +0 -1
  146. package/dist/backup-service-EwcDVG-8.js.map +0 -1
  147. package/dist/cli-errors-DsA-K9uP.js +0 -893
  148. package/dist/cli-errors-DsA-K9uP.js.map +0 -1
  149. package/dist/cli-flags-BglvpjHv.js.map +0 -1
  150. package/dist/cli-output-CNdMql-L.js +0 -47
  151. package/dist/cli-output-CNdMql-L.js.map +0 -1
  152. package/dist/collection-index-DdnsxiJ_.js +0 -286
  153. package/dist/collection-index-DdnsxiJ_.js.map +0 -1
  154. package/dist/databasePoolManager-Bj5FbeAs.js.map +0 -1
  155. package/dist/doctor-C-sYWbmt.js.map +0 -1
  156. package/dist/ensure-collection-policies-B1ureSIV.js +0 -160
  157. package/dist/ensure-collection-policies-B1ureSIV.js.map +0 -1
  158. package/dist/ensure-collection-tables-kkkHk8oo.js.map +0 -1
  159. package/dist/ensure-tables-BmI_tRxc.js.map +0 -1
  160. package/dist/generate-drizzle-schema-93M0lUxK.js +0 -123
  161. package/dist/generate-drizzle-schema-93M0lUxK.js.map +0 -1
  162. package/dist/generate-drizzle-schema-logic-sSFDp6LR.js.map +0 -1
  163. package/dist/introspect-db-logic-kCETE8TY.js.map +0 -1
  164. package/dist/introspect-db-queries-C_Q5VgQw.js.map +0 -1
  165. package/dist/plan-schema-DU9exq6C.js.map +0 -1
  166. package/dist/policy-drift-B-J2hhm0.js.map +0 -1
  167. package/dist/render-ddl-Ds2t_d9V.js.map +0 -1
  168. package/dist/rls-bootstrap-sql-_KNnjanK.js.map +0 -1
  169. package/dist/rls-enforcement-CfXOJJaW.js.map +0 -1
  170. package/dist/source-Br7L7GOI.js +0 -410
  171. package/dist/source-Br7L7GOI.js.map +0 -1
  172. package/dist/utils/sql-redaction.d.ts +0 -21
@@ -0,0 +1,532 @@
1
+ import { createRequire as __createRequire } from "module";
2
+ __createRequire(import.meta.url);
3
+ import { r as __exportAll } from "./__vite-browser-external-BnuHet1e.js";
4
+ import { K as extractCauseMessage } from "./cli-errors-CaI4ntyD.js";
5
+ import { n as extractPgError } from "./pg-error-utils-vJezlXdI.js";
6
+ import { s as vectorExtensionHint } from "./vector-index-CMJKb3C9.js";
7
+ import { n as planSchema, r as quoteSqlLiteral } from "./plan-schema-B1YF_tYq.js";
8
+ import { c as planJunctionTables } from "./generate-postgres-ddl-logic-B64O2oZF.js";
9
+ import { r as quoteRelation } from "./introspect-db-queries-D6kAqReI.js";
10
+ import { a as assertSafeIdentifier, c as helperFunctionKey, i as HELPER_FUNCTIONS, l as columnTypeDriftMessage, n as buildDeferredIndexes, o as describeHelperFunction, s as diffPlanAgainstCatalogue } from "./deferred-index-build-DP1_SNFo.js";
11
+ import { a as isLockTimeout, l as sleep, o as isMultiStatementRefusal, r as atomicBatch, s as joinStatements } from "./boot-ddl-DVpsFN6r.js";
12
+ import { isConcurrentDdlRace, isDuplicateObjectRace, logger } from "@rebasepro/server";
13
+ import { REBASE_SCHEMA, declaredDatabaseExtensions } from "@rebasepro/types";
14
+ import { getTableName, relationalCollections } from "@rebasepro/common";
15
+ //#region src/schema/ensure-collection-tables.ts
16
+ /**
17
+ * Applying a schema plan to a database that is already running.
18
+ *
19
+ * ## What this module is
20
+ *
21
+ * A managed runtime boots someone else's compiled project against a database it
22
+ * has never seen. Auth tables are ensured at boot already, but collection tables
23
+ * were not created by anything: the platform ran the app and every `/api/data/*`
24
+ * request answered 500 on a missing relation. `rebase db push` cannot help — it
25
+ * is an Atlas-driven CLI command, and the runtime image ships no CLI.
26
+ *
27
+ * Two halves, and only one of them is here. *What the collections ask for* is
28
+ * `schema/plan/plan-schema.ts`, shared with `db push` and the generated Drizzle
29
+ * file; *what the database already has, and which of those statements are safe
30
+ * to run against it* is `schema/plan/diff-plan.ts`. This module reads the
31
+ * catalogue, runs the statements, and reports what happened.
32
+ *
33
+ * ## Why additive-only, forever
34
+ *
35
+ * This runs unattended, against a database with customers' data in it, with no
36
+ * human reading a diff. So it may only ever do things that cannot lose data.
37
+ * See `diff-plan.ts` for the whole of that argument.
38
+ */
39
+ var ensure_collection_tables_exports = /* @__PURE__ */ __exportAll({
40
+ describeDriverError: () => describeDriverError,
41
+ ensureCollectionTables: () => ensureCollectionTables,
42
+ planCollectionSchemaEnsure: () => planCollectionSchemaEnsure,
43
+ readExistingSchema: () => readExistingSchema,
44
+ readSchemaFactsFor: () => readSchemaFactsFor
45
+ });
46
+ /**
47
+ * Most statements in one new-table transaction. Bounds how many locks one
48
+ * transaction holds — each new table is several relations (the heap, its TOAST
49
+ * table, its key index) — well inside the default `max_locks_per_transaction`.
50
+ */
51
+ var NEW_TABLE_BATCH = 100;
52
+ /** The schema a collection lives in, validated before it reaches any DDL. */
53
+ function schemaOf(collection) {
54
+ return assertSafeIdentifier(collection.schema || "public", "schema name");
55
+ }
56
+ /**
57
+ * Decide what to add. Pure — the caller supplies what exists and runs the
58
+ * result.
59
+ *
60
+ * A plan and a diff: the collections are read once, by `planSchema`, into the
61
+ * same {@link SchemaPlan} `db push` renders `schema.sql` from, and the diff
62
+ * subtracts what the database already has. Before that split this function had
63
+ * its own reading of every `Property`, and the audit found twelve places where
64
+ * it disagreed with the two generators — a required `author_id` that was
65
+ * NOT NULL after a push and nullable after a boot among them, on the one path
66
+ * with no developer in the loop.
67
+ */
68
+ function planCollectionSchemaEnsure(allCollections, existing, options = {}) {
69
+ return diffPlanAgainstCatalogue(planSchema(allCollections, { databaseExtensions: options.databaseExtensions }), existing, { constraints: options.constraints });
70
+ }
71
+ /** Read what the database has, for the schemas the collections live in. */
72
+ async function readExistingSchema(client, schemas, options = {}) {
73
+ const tables = /* @__PURE__ */ new Map();
74
+ const enums = /* @__PURE__ */ new Set();
75
+ if (schemas.length === 0) return {
76
+ tables,
77
+ enums
78
+ };
79
+ const inList = schemas.map((schema) => `'${assertSafeIdentifier(schema, "schema name")}'`).join(", ");
80
+ const notNullColumns = /* @__PURE__ */ new Set();
81
+ const columnTypes = /* @__PURE__ */ new Map();
82
+ const columnDefaults = /* @__PURE__ */ new Set();
83
+ const { rows: columns } = await client.query(`SELECT table_schema, table_name, column_name, is_nullable, udt_name, column_default,
84
+ numeric_precision, numeric_scale
85
+ FROM information_schema.columns
86
+ WHERE table_schema IN (${inList})`);
87
+ for (const row of columns) {
88
+ const key = `${row.table_schema}.${row.table_name}`;
89
+ if (!tables.has(key)) tables.set(key, /* @__PURE__ */ new Set());
90
+ tables.get(key).add(row.column_name);
91
+ if (row.is_nullable === "NO") notNullColumns.add(`${key}.${row.column_name}`);
92
+ if (row.udt_name) {
93
+ const modifier = row.udt_name === "numeric" && row.numeric_precision !== null ? `(${row.numeric_precision},${row.numeric_scale ?? 0})` : "";
94
+ columnTypes.set(`${key}.${row.column_name}`, `${row.udt_name}${modifier}`);
95
+ }
96
+ if (row.column_default !== null) columnDefaults.add(`${key}.${row.column_name}`);
97
+ }
98
+ const triggers = /* @__PURE__ */ new Set();
99
+ const { rows: triggerRows } = await client.query(`SELECT n.nspname AS schema, c.relname AS table, t.tgname AS name
100
+ FROM pg_trigger t
101
+ JOIN pg_class c ON t.tgrelid = c.oid
102
+ JOIN pg_namespace n ON c.relnamespace = n.oid
103
+ WHERE NOT t.tgisinternal AND n.nspname IN (${inList})`);
104
+ for (const row of triggerRows) triggers.add(`${row.schema}.${row.table}.${row.name}`);
105
+ const populatedTables = /* @__PURE__ */ new Set();
106
+ const indexes = /* @__PURE__ */ new Set();
107
+ const { rows: relations } = await client.query(`SELECT n.nspname AS schema, c.relname AS name, c.relkind::text AS kind
108
+ FROM pg_class c
109
+ JOIN pg_namespace n ON c.relnamespace = n.oid
110
+ WHERE c.relkind IN ('r', 'p', 'i', 'I') AND n.nspname IN (${inList})`);
111
+ const allRealTables = relations.filter((row) => row.kind === "r" || row.kind === "p");
112
+ for (const row of relations) if (row.kind === "i" || row.kind === "I") indexes.add(`${row.schema}.${row.name}`);
113
+ const wanted = options.probeTables ? new Set(options.probeTables(tables)) : void 0;
114
+ const realTables = wanted ? allRealTables.filter((row) => wanted.has(`${row.schema}.${row.name}`)) : allRealTables;
115
+ if (wanted) for (const row of allRealTables) {
116
+ const key = `${row.schema}.${row.name}`;
117
+ if (!wanted.has(key)) populatedTables.add(key);
118
+ }
119
+ if (realTables.length > 0) {
120
+ const probes = realTables.map((row) => `SELECT ${quoteSqlLiteral(`${row.schema}.${row.name}`)} AS key, EXISTS(SELECT 1 FROM ${quoteRelation(row.schema, row.name)} LIMIT 1) AS populated`);
121
+ const { rows: populationRows } = await client.query(probes.join(" UNION ALL "));
122
+ for (const row of populationRows) if (row.populated) populatedTables.add(row.key);
123
+ }
124
+ const enumValues = /* @__PURE__ */ new Map();
125
+ const { rows: enumValueRows } = await client.query(`SELECT n.nspname AS schema, t.typname AS name, e.enumlabel AS value
126
+ FROM pg_enum e
127
+ JOIN pg_type t ON e.enumtypid = t.oid
128
+ JOIN pg_namespace n ON t.typnamespace = n.oid
129
+ WHERE n.nspname IN (${inList})
130
+ ORDER BY t.typname, e.enumsortorder`);
131
+ for (const row of enumValueRows) {
132
+ const key = `${row.schema}.${row.name}`;
133
+ if (!enumValues.has(key)) enumValues.set(key, []);
134
+ enumValues.get(key).push(row.value);
135
+ }
136
+ const { rows: enumRows } = await client.query(`SELECT n.nspname AS schema, t.typname AS name
137
+ FROM pg_type t
138
+ JOIN pg_namespace n ON t.typnamespace = n.oid
139
+ WHERE t.typtype = 'e' AND n.nspname IN (${inList})`);
140
+ for (const row of enumRows) enums.add(`${row.schema}.${row.name}`);
141
+ const constraints = /* @__PURE__ */ new Set();
142
+ const { rows: constraintRows } = await client.query(`SELECT n.nspname AS schema, c.relname AS table, con.conname AS name
143
+ FROM pg_constraint con
144
+ JOIN pg_class c ON con.conrelid = c.oid
145
+ JOIN pg_namespace n ON c.relnamespace = n.oid
146
+ WHERE n.nspname IN (${inList})`);
147
+ for (const row of constraintRows) constraints.add(`${row.schema}.${row.table}.${row.name}`);
148
+ const invalidIndexes = /* @__PURE__ */ new Set();
149
+ const { rows: invalidRows } = await client.query(`SELECT n.nspname AS schema, c.relname AS name
150
+ FROM pg_index i
151
+ JOIN pg_class c ON c.oid = i.indexrelid
152
+ JOIN pg_namespace n ON n.oid = c.relnamespace
153
+ WHERE NOT i.indisvalid AND n.nspname IN (${inList})`);
154
+ for (const row of invalidRows) invalidIndexes.add(`${row.schema}.${row.name}`);
155
+ const columnComments = /* @__PURE__ */ new Map();
156
+ const { rows: commentRows } = await client.query(`SELECT n.nspname AS schema, c.relname AS table, a.attname AS column, d.description AS comment
157
+ FROM pg_description d
158
+ JOIN pg_class c ON d.objoid = c.oid
159
+ JOIN pg_namespace n ON c.relnamespace = n.oid
160
+ JOIN pg_attribute a ON a.attrelid = c.oid AND a.attnum = d.objsubid
161
+ WHERE d.objsubid > 0 AND n.nspname IN (${inList})`);
162
+ for (const row of commentRows) {
163
+ if (row.comment == null) continue;
164
+ columnComments.set(`${row.schema}.${row.table}.${row.column}`, row.comment);
165
+ }
166
+ const functions = /* @__PURE__ */ new Map();
167
+ const helperNames = HELPER_FUNCTIONS.map((qualified) => qualified.split(".")).map(([schema, name]) => `(${quoteSqlLiteral(schema)}, ${quoteSqlLiteral(name)})`).join(", ");
168
+ const { rows: functionRows } = await client.query(`SELECT n.nspname AS schema, p.proname AS name, pg_get_function_identity_arguments(p.oid) AS args,
169
+ format_type(p.prorettype, NULL) AS returns, l.lanname AS language,
170
+ p.provolatile::text AS volatility, p.proisstrict AS strict, p.proparallel::text AS parallel,
171
+ p.prosecdef AS security_definer, p.proleakproof AS leakproof, p.procost AS cost,
172
+ p.proconfig IS NOT NULL AS has_config, p.prosrc AS body
173
+ FROM pg_proc p
174
+ JOIN pg_namespace n ON p.pronamespace = n.oid
175
+ JOIN pg_language l ON p.prolang = l.oid
176
+ WHERE (n.nspname, p.proname) IN (${helperNames})`);
177
+ for (const row of functionRows) functions.set(helperFunctionKey(row.schema, row.name, row.args), describeHelperFunction({
178
+ returns: row.returns,
179
+ language: row.language,
180
+ volatility: row.volatility,
181
+ strict: row.strict,
182
+ parallel: row.parallel,
183
+ securityDefiner: row.security_definer,
184
+ leakproof: row.leakproof,
185
+ cost: Number(row.cost),
186
+ hasConfig: row.has_config,
187
+ body: row.body
188
+ }));
189
+ const extensions = /* @__PURE__ */ new Set();
190
+ const { rows: extensionRows } = await client.query("SELECT extname AS name FROM pg_extension");
191
+ for (const row of extensionRows) extensions.add(row.name);
192
+ return {
193
+ tables,
194
+ enums,
195
+ constraints,
196
+ columnComments,
197
+ enumValues,
198
+ notNullColumns,
199
+ populatedTables,
200
+ columnTypes,
201
+ columnDefaults,
202
+ triggers,
203
+ indexes,
204
+ invalidIndexes,
205
+ extensions,
206
+ functions
207
+ };
208
+ }
209
+ /**
210
+ * The planned tables that exist and lack at least one planned column — the
211
+ * only ones whose emptiness the diff consults (a NOT NULL or UNIQUE arriving
212
+ * with a new column). A superset, never a subset: a column the plan renames
213
+ * from a legacy name reads as missing here too.
214
+ */
215
+ function tablesGainingColumns(plan, catalogue) {
216
+ const gaining = [];
217
+ for (const table of plan.tables) {
218
+ const columns = catalogue.get(table.qualified);
219
+ if (columns && table.columns.some((column) => !columns.has(column.column))) gaining.push(table.qualified);
220
+ }
221
+ return gaining;
222
+ }
223
+ /**
224
+ * What to tell an operator whose `search` block no longer matches its column.
225
+ *
226
+ * Every line here is doing work: naming the collection is not enough, because
227
+ * the symptom (a search that finds nothing) points at the data, not the schema;
228
+ * and the remediation has to be exact, because it is a table rewrite the
229
+ * operator is being asked to schedule rather than discover.
230
+ */
231
+ function searchDriftMessage(drift) {
232
+ return "The `search` block changed after its generated column was created, and Postgres cannot alter a generated expression in place.\nRebase will not rebuild it for you: dropping and re-adding a STORED generated column rewrites the whole table under an ACCESS EXCLUSIVE lock and rebuilds its GIN index, which is an outage this unattended path may not schedule on your behalf.\nUntil it is rebuilt the column keeps indexing the previous fields, weights and language — searches for anything added since return nothing, which reads from outside as \"no such row\".\nRun these (or revert the block to what the column was built from), then boot again:\n" + drift.map((d) => ` "${d.table}"."${d.column}" was generated from a different \`search\` block (recorded ${d.found}, current ${d.expected}).\n` + d.rebuild.map((s) => ` ${s}`).join("\n")).join("\n") + "\n The GIN index is dropped with the column and recreated concurrently on the next boot.";
233
+ }
234
+ /**
235
+ * Read what the database looks like, for the schemas a set of collections
236
+ * lives in.
237
+ *
238
+ * The same read `ensureCollectionTables` does at boot, exposed on its own for
239
+ * the callers that want to *plan* against a real database without changing it —
240
+ * the live schema editor, which has to tell somebody what a change would do
241
+ * before they agree to it.
242
+ */
243
+ async function readSchemaFactsFor(client, collections) {
244
+ const relational = relationalCollections(collections);
245
+ const junctions = planJunctionTables(relational);
246
+ const schemas = Array.from(/* @__PURE__ */ new Set([...relational.map(schemaOf), ...junctions.map((junction) => junction.schema)]));
247
+ const declared = [...relational.map((collection) => `${schemaOf(collection)}.${getTableName(collection)}`), ...junctions.map((junction) => `${junction.schema}.${junction.table}`)];
248
+ return readExistingSchema(client, schemas, { probeTables: () => declared });
249
+ }
250
+ /**
251
+ * Bring the database up to date. Returns what it did.
252
+ *
253
+ * Each statement runs on its own rather than in one transaction: they are all
254
+ * independently safe and idempotent, and a single failure (an enum label that
255
+ * cannot be added, say) should not roll back the tables that were created fine.
256
+ * The error is surfaced with the statement that caused it.
257
+ */
258
+ async function ensureCollectionTables(client, collections, log, options = {}) {
259
+ const schema = planSchema(collections, { databaseExtensions: options.databaseExtensions ?? declaredDatabaseExtensions() });
260
+ const schemas = Array.from(new Set(schema.tables.map((table) => table.schema)));
261
+ for (const name of schemas) {
262
+ assertSafeIdentifier(name, "schema name");
263
+ if (name !== "public") await client.query(`CREATE SCHEMA IF NOT EXISTS "${name}";`);
264
+ }
265
+ if (schema.tables.some((table) => table.triggers.length > 0)) await client.query(`CREATE SCHEMA IF NOT EXISTS "${REBASE_SCHEMA}";`);
266
+ const plan = diffPlanAgainstCatalogue(schema, await readExistingSchema(client, schemas, { probeTables: (catalogue) => tablesGainingColumns(schema, catalogue) }), {
267
+ constraints: options.constraints,
268
+ deferLiveIndexes: true
269
+ });
270
+ const failures = [];
271
+ for (const legacy of plan.legacyForeignKeys) {
272
+ const message = `Renaming "${legacy.table}"."${legacy.legacy}" to "${legacy.expected}". The old name is the one Rebase derived for this relation before it singularized properly; the column keeps its data, indexes and constraints. To keep the old name instead, set \`localKey: "${legacy.legacy}"\` on the relation and this will stop.`;
273
+ logger.info(`[schema] ${message}`);
274
+ log?.(message);
275
+ }
276
+ if (plan.searchDrift.length > 0) throw new Error(searchDriftMessage(plan.searchDrift));
277
+ for (const adopted of plan.searchAdopted) {
278
+ const message = `Adopting the existing generated column "${adopted.table}"."${adopted.column}" and recording what the current \`search\` block would generate. Any later change to that block will be detected and refused; a change made *before* this version was deployed cannot be, so if search has been missing content, rebuild the column once: ALTER TABLE "${adopted.table.split(".").join("\".\"")}" DROP COLUMN "${adopted.column}"; and boot again.`;
279
+ logger.info(`[schema] ${message}`);
280
+ log?.(message);
281
+ }
282
+ for (const skip of plan.vectorIndexSkipped) {
283
+ const message = `No ANN index on "${skip.table}"."${skip.column}": ${skip.reason}`;
284
+ logger.warn(`[schema] ${message}`);
285
+ log?.(message);
286
+ }
287
+ for (const withheld of plan.withheldConstraints) {
288
+ const message = `No ${withheld.kind === "unique" ? "UNIQUE" : "NOT NULL"} on "${withheld.target}": ${withheld.reason} ${withheld.remedy}`;
289
+ logger.warn(`[schema] ${message}`);
290
+ log?.(message);
291
+ }
292
+ for (const drift of plan.columnTypeDrift) {
293
+ const message = columnTypeDriftMessage(drift);
294
+ logger.warn(`[schema] ${message}`);
295
+ log?.(message);
296
+ }
297
+ for (const orphan of plan.orphanedRequiredColumns) {
298
+ const [schema, table] = orphan.table.split(".");
299
+ const message = `"${orphan.table}"."${orphan.column}" is NOT NULL with no default, and the "${orphan.slug}" collection no longer declares it. Every insert will be rejected for a field you cannot set (PG_23502, naming "${orphan.column}"). This is what a renamed property looks like: the new column was added, and the old one cannot be dropped unattended. Fix it with ONE of:
300
+ ALTER TABLE "${schema}"."${table}" DROP COLUMN "${orphan.column}"; -- the rename is complete\n ALTER TABLE "${schema}"."${table}" ALTER COLUMN "${orphan.column}" DROP NOT NULL; -- keep the data, stop requiring it\n ...or declare the property again, if the column is still wanted.`;
301
+ logger.warn(`[schema] ${message}`);
302
+ log?.(message);
303
+ }
304
+ const builtHere = options.deferIndexBuilds ? plan.deferredIndexes.filter((index) => index.unique === true) : plan.deferredIndexes;
305
+ const leftToCaller = options.deferIndexBuilds ? plan.deferredIndexes.filter((index) => index.unique !== true) : plan.deferredIndexes;
306
+ const buildIndexes = async () => {
307
+ if (builtHere.length === 0) return void 0;
308
+ const outcome = await buildDeferredIndexes(options.openIndexBuildSession ?? (async () => ({
309
+ query: (sql) => client.query(sql),
310
+ dedicated: false,
311
+ release: () => void 0
312
+ })), builtHere, {
313
+ log,
314
+ lockTimeoutMs: options.lockTimeoutMs
315
+ });
316
+ assertUniqueIndexesBuilt(builtHere, outcome);
317
+ return outcome;
318
+ };
319
+ const finish = async () => {
320
+ const indexBuilds = await buildIndexes();
321
+ return {
322
+ ...plan,
323
+ deferredIndexes: leftToCaller,
324
+ failures,
325
+ indexBuilds
326
+ };
327
+ };
328
+ if (plan.actions.length === 0) {
329
+ if (plan.deferredIndexes.length === 0) log?.("Schema is up to date; nothing to create.");
330
+ return finish();
331
+ }
332
+ const lockBudget = options.lockTimeoutMs !== void 0 ? {
333
+ lockTimeoutMs: options.lockTimeoutMs,
334
+ atomic: true
335
+ } : void 0;
336
+ const multi = lockBudget ?? { atomic: true };
337
+ const createdTables = new Set(plan.actions.filter((a) => a.kind === "create-table").map((a) => a.target));
338
+ const batchable = (action) => options.batchNewTables === true && multi.atomic && (action.kind === "create-enum" || createdTables.has(action.target.split(".").slice(0, 2).join("."))) && action.kind !== "add-enum-value" && !CONCURRENTLY_RE.test(action.sql);
339
+ const applyBatch = async (group) => {
340
+ const statements = group.map((action) => action.sql);
341
+ try {
342
+ await client.query(lockBudget ? atomicBatch(statements, lockBudget.lockTimeoutMs) : joinStatements(statements));
343
+ return true;
344
+ } catch (err) {
345
+ if (isMultiStatementRefusal(err)) multi.atomic = false;
346
+ else log?.(`${group.length} statement(s) for new tables failed as one transaction (${describeDriverError(err).split("\n")[0]}); applying them one at a time`);
347
+ return false;
348
+ }
349
+ };
350
+ const applyOne = async (action) => {
351
+ try {
352
+ if (await applyAction(client, action, lockBudget)) log?.(`${action.kind}: ${action.target}`);
353
+ else log?.(`${action.kind}: ${action.target} (already created by a peer)`);
354
+ } catch (err) {
355
+ const message = describeDriverError(err);
356
+ if (action.kind === "add-constraint" || action.kind === "comment-column" || action.kind === "create-index" && !/CREATE\s+UNIQUE\s+INDEX/i.test(action.sql)) {
357
+ failures.push({
358
+ kind: action.kind,
359
+ target: action.target,
360
+ error: message
361
+ });
362
+ return;
363
+ }
364
+ const lockHint = isLockTimeout(err) ? "\n hint: another session held this table's lock for every attempt (a long transaction, a report or a dump). Nothing was changed; boot again once it has finished." : "";
365
+ throw new Error(`Failed to ${action.kind} ${action.target}: ${message}${vectorExtensionHint(message)}${lockHint}\n ${action.sql}`);
366
+ }
367
+ };
368
+ for (let i = 0; i < plan.actions.length;) {
369
+ let end = i;
370
+ while (end < plan.actions.length && end - i < NEW_TABLE_BATCH && batchable(plan.actions[end])) end++;
371
+ if (end - i > 1 && await applyBatch(plan.actions.slice(i, end))) {
372
+ for (const action of plan.actions.slice(i, end)) log?.(`${action.kind}: ${action.target}`);
373
+ i = end;
374
+ continue;
375
+ }
376
+ const last = Math.max(end, i + 1);
377
+ for (; i < last; i++) await applyOne(plan.actions[i]);
378
+ }
379
+ return finish();
380
+ }
381
+ /**
382
+ * Fail the ensure for a UNIQUE index on a live table that could not be built,
383
+ * exactly as a failed UNIQUE `create-index` action always has (see `applyOne`).
384
+ *
385
+ * "Another index on the table is being built" counts: the table's lock is
386
+ * held by a build that may run for minutes, and serving the collection
387
+ * meanwhile would be serving a promise the database is not keeping. Another
388
+ * instance building *this* index does not: once its build commits the
389
+ * constraint holds, which is also what a peer's `IF NOT EXISTS` race meant.
390
+ */
391
+ function assertUniqueIndexesBuilt(indexes, outcome) {
392
+ for (const index of indexes) {
393
+ if (index.unique !== true) continue;
394
+ const qualified = `${index.schema}.${index.indexName}`;
395
+ const reason = outcome.failed.find((failure) => failure.index === qualified)?.error ?? outcome.skipped.find((skip) => skip.index === qualified && skip.reason === "another index on the table is being built")?.reason;
396
+ if (reason === void 0) continue;
397
+ throw new Error(`Failed to create-index ${index.schema}.${index.table}: ${qualified}: ${reason}\n ${index.sql}`);
398
+ }
399
+ }
400
+ /**
401
+ * What actually went wrong, rather than what the ORM said about it.
402
+ *
403
+ * Drizzle wraps every driver error, and its `message` is literally
404
+ * ``Failed query: ${sql}\nparams: ${params}`` — the statement we already know,
405
+ * and not one word of Postgres's answer. Reporting `err.message` therefore
406
+ * produced a failure that named the statement twice and the *reason* zero
407
+ * times, so four unrelated boot failures — a missing extension, a wrong
408
+ * privilege, a bad index method, a constraint the rows violate — all read
409
+ * identically to whoever had to fix them.
410
+ *
411
+ * Two consequences, both observed in the field before this existed:
412
+ *
413
+ * 1. On a managed deployment the reason never reaches anybody. Boot dies, the
414
+ * pod never goes ready, and the deploy reports a readiness timeout — so the
415
+ * one line that says why is the one line that was thrown away here.
416
+ * 2. {@link vectorExtensionHint} matches on the error text. Against the
417
+ * wrapper it matched nothing, so the hint written specifically to explain a
418
+ * missing pgvector never fired on the path that raises it.
419
+ *
420
+ * `detail` and `hint` come along because they are the fields Postgres uses to
421
+ * say which object it meant and what to do about it — `CREATE EXTENSION` names
422
+ * the missing library there, and a rejected constraint names the row. This
423
+ * writes into the tenant's own boot log, about the tenant's own database.
424
+ */
425
+ function describeDriverError(err) {
426
+ const pg = extractPgError(err);
427
+ if (pg) {
428
+ const parts = [pg.code ? `${pg.message} [${pg.code}]` : pg.message];
429
+ if (pg.detail) parts.push(` detail: ${pg.detail}`);
430
+ if (pg.hint) parts.push(` hint: ${pg.hint}`);
431
+ return parts.join("\n");
432
+ }
433
+ const cause = extractCauseMessage(err);
434
+ if (cause) return cause;
435
+ return err instanceof Error ? err.message : String(err);
436
+ }
437
+ /** ` CONCURRENTLY ` as it appears in a rendered `CREATE [UNIQUE] INDEX`. */
438
+ var CONCURRENTLY_RE = /\sCONCURRENTLY\s/i;
439
+ /**
440
+ * `25001 active_sql_transaction` — "cannot run inside a transaction block".
441
+ *
442
+ * Only ever raised here by `CREATE INDEX CONCURRENTLY`, and only when the
443
+ * connection we were handed is inside a transaction. Deliberately narrow: it
444
+ * decides to retry a *different* statement, so it must not fire on anything it
445
+ * has not been reasoned about.
446
+ */
447
+ function isTransactionBlockRefusal(err) {
448
+ return extractPgError(err)?.code === "25001";
449
+ }
450
+ /** Attempts per action, including the first. Matches the server's bootstraps. */
451
+ var DDL_ATTEMPTS = 4;
452
+ /**
453
+ * Attempts at an action whose table lock is held, including the first.
454
+ *
455
+ * Unlike a policy, a schema change cannot be deferred — the collection's code
456
+ * expects the column — so a lock that never frees still fails the boot, as a
457
+ * statement timeout did. Ten one-second attempts with backoff between them wait
458
+ * about as long as the old 30 s statement timeout did, but a live query on that
459
+ * table now waits at most one second at a time instead of thirty.
460
+ */
461
+ var LOCK_ATTEMPTS = 10;
462
+ /**
463
+ * Whether an action may be sent as `SET LOCAL lock_timeout; <statement>` — one
464
+ * implicit transaction. Not `CREATE INDEX CONCURRENTLY`, which Postgres refuses
465
+ * inside any transaction block (and which waits on no lock a reader holds), and
466
+ * not `ALTER TYPE … ADD VALUE`, which Postgres before 12 refuses there too and
467
+ * which locks a type, not a table.
468
+ */
469
+ var takesLockBudget = (action, sql) => action.kind !== "add-enum-value" && !CONCURRENTLY_RE.test(sql);
470
+ /**
471
+ * Run one planned statement, surviving a simultaneous boot.
472
+ *
473
+ * Every statement in a plan is written to be idempotent, and that is not the
474
+ * same as being safe to run concurrently: `CREATE … IF NOT EXISTS` reads the
475
+ * catalog and then writes to it as two steps, so peers starting together both
476
+ * see "absent" and the loser gets a duplicate key on a *catalog* index. Measured
477
+ * against Postgres 18: five instances, 8 of 10 calls lost. `CREATE TYPE` is
478
+ * worse, because Postgres has no `IF NOT EXISTS` for it at all.
479
+ *
480
+ * What made that fatal here rather than merely noisy is the loop this sits in.
481
+ * A losing statement threw, and the throw abandoned **every remaining action in
482
+ * the plan** — so a replica that lost one race came up missing tables it never
483
+ * attempted, and the boot log blamed the one statement that failed.
484
+ *
485
+ * @returns `true` if this process applied the statement, `false` if a peer had
486
+ * already created the object. The distinction is only for the log; both mean
487
+ * the object is now there.
488
+ * @throws the original error for anything that is not a race — a syntax error, a
489
+ * permission failure, a unique constraint the customer's own rows violate.
490
+ */
491
+ async function applyAction(client, action, lockBudget) {
492
+ let sqlText = action.sql;
493
+ let lockAttempts = 0;
494
+ for (let attempt = 1;; attempt++) {
495
+ const budgeted = lockBudget?.atomic === true && takesLockBudget(action, sqlText);
496
+ try {
497
+ await client.query(budgeted ? atomicBatch([sqlText], lockBudget.lockTimeoutMs) : sqlText);
498
+ return true;
499
+ } catch (err) {
500
+ if (budgeted && isMultiStatementRefusal(err)) {
501
+ lockBudget.atomic = false;
502
+ attempt--;
503
+ continue;
504
+ }
505
+ if (budgeted && isLockTimeout(err) && ++lockAttempts < LOCK_ATTEMPTS) {
506
+ logger.debug(`[schema] ${action.kind} ${action.target}: the table's lock is held by another session (attempt ${lockAttempts}/${LOCK_ATTEMPTS}) — retrying`);
507
+ attempt--;
508
+ await sleep(Math.min(500 * lockAttempts, 2e3) * (1 + Math.random()));
509
+ continue;
510
+ }
511
+ if (isTransactionBlockRefusal(err) && CONCURRENTLY_RE.test(sqlText)) {
512
+ sqlText = sqlText.replace(CONCURRENTLY_RE, " ");
513
+ logger.warn(`[schema] ${action.kind} ${action.target}: this connection is inside a transaction, so the index is being built with a write lock rather than concurrently. Writes to this table block until it finishes.`);
514
+ continue;
515
+ }
516
+ if (isDuplicateObjectRace(err)) {
517
+ logger.debug(`[schema] ${action.kind} ${action.target}: already created by another instance`);
518
+ return false;
519
+ }
520
+ if (isConcurrentDdlRace(err) && attempt < DDL_ATTEMPTS) {
521
+ logger.debug(`[schema] ${action.kind} ${action.target}: lost a race with another instance (attempt ${attempt}/${DDL_ATTEMPTS}) — retrying`);
522
+ await new Promise((resolve) => setTimeout(resolve, 40 * attempt * (1 + Math.random())));
523
+ continue;
524
+ }
525
+ throw err;
526
+ }
527
+ }
528
+ }
529
+ //#endregion
530
+ export { readSchemaFactsFor as a, readExistingSchema as i, ensure_collection_tables_exports as n, planCollectionSchemaEnsure as r, describeDriverError as t };
531
+
532
+ //# sourceMappingURL=ensure-collection-tables-BhsU6qUE.js.map