@rebasepro/server-postgres 0.12.1-canary.g35be8cb → 0.12.1-canary.g68f9a47

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.
@@ -1,7 +1,7 @@
1
1
  import { createRequire as __createRequire } from "module";
2
2
  import "process";
3
3
  __createRequire(import.meta.url);
4
- import { readExistingSchema, t as planCollectionPolicies } from "./ensure-collection-tables-DsDsNl6o.js";
4
+ import { readExistingSchema, t as planCollectionPolicies } from "./ensure-collection-tables-Vu-GRELM.js";
5
5
  //#region src/schema/ensure-collection-policies.ts
6
6
  var isCreatePolicy = (statement) => /^\s*CREATE POLICY/i.test(statement);
7
7
  /**
@@ -54,4 +54,4 @@ async function ensureCollectionPolicies(client, collections, log) {
54
54
  //#endregion
55
55
  export { ensureCollectionPolicies };
56
56
 
57
- //# sourceMappingURL=ensure-collection-policies-Vl3Cv1Q9.js.map
57
+ //# sourceMappingURL=ensure-collection-policies-CT-zIUWA.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"ensure-collection-policies-Vl3Cv1Q9.js","names":[],"sources":["../src/schema/ensure-collection-policies.ts"],"sourcesContent":["/**\n * Applying a bundle's RLS policies to a database at boot, idempotently.\n *\n * ## Why this exists\n *\n * {@link ensureCollectionTables} creates the collection *tables* a managed\n * runtime boots against, but a table with row-level security disabled and no\n * policies is not servable: authenticated requests run as the restricted\n * `rebase_user` role, so a read with no `SELECT` policy returns nothing (a\n * public collection answered 401) and a write with no `INSERT`/`UPDATE` policy\n * is denied. The policies live in the collections' `securityRules`; nothing at\n * boot applied them. `rebase db push` does — but it drives Atlas against a\n * local `DATABASE_URL`, and a managed tenant's database is reachable only from\n * inside the cluster, by the runtime that is already connected to it. So the\n * runtime is the only thing that *can* apply them, and this is where it does.\n *\n * ## Why this is safe to run on every boot\n *\n * Every statement is idempotent: `ENABLE ROW LEVEL SECURITY` is a no-op once\n * enabled, and each policy is a `DROP POLICY IF EXISTS` immediately followed by\n * a `CREATE POLICY`, so re-applying asserts exactly the declared state. It adds\n * and replaces; it never drops data. (It does not *reconcile* — a policy a\n * previous push left behind under an old name is not removed here; that stays a\n * `db push` / `db migrate` concern, alongside destructive schema changes.)\n *\n * Unlike table creation, a failure here is not fatal: RLS stays enabled, so a\n * table whose policies could not be applied fails **closed** (denies) rather\n * than leaking rows. One collection's policy failing (e.g. a rule that\n * references a table a real migration has not created yet) must not crash-loop\n * the whole deployment and take the other collections' working routes down with\n * it. Failures are reported loudly and per-table so the operator can see\n * exactly which collection is not yet servable and why.\n */\nimport { type CollectionConfig } from \"@rebasepro/types\";\nimport { planCollectionPolicies } from \"./generate-postgres-ddl-logic\";\nimport { readExistingSchema, type Queryable } from \"./ensure-collection-tables\";\n\nexport interface PolicyEnsureResult {\n /** `CREATE POLICY` statements that ran successfully. */\n policiesApplied: number;\n /** Tables that had RLS enabled. */\n tablesSecured: number;\n /** Declared tables absent from the database — left to a real migration. */\n skipped: { table: string; reason: string }[];\n /** Tables whose RLS could not be fully applied (fail closed). */\n failures: { table: string; error: string }[];\n}\n\nconst isCreatePolicy = (statement: string): boolean => /^\\s*CREATE POLICY/i.test(statement);\n\n/**\n * Bring the declared collections' RLS policies up to date. Returns what it did.\n *\n * Only tables that already exist are touched: the boot-time table creator runs\n * first, so anything still missing is a table this additive path is not allowed\n * to create (a junction, or a relation left to a migration). Enabling RLS on a\n * non-existent table would error, so those are recorded as skipped, not failed.\n */\nexport async function ensureCollectionPolicies(\n client: Queryable,\n collections: CollectionConfig[],\n log?: (message: string) => void\n): Promise<PolicyEnsureResult> {\n const result: PolicyEnsureResult = { policiesApplied: 0, tablesSecured: 0, skipped: [], failures: [] };\n\n const plans = planCollectionPolicies(collections);\n if (plans.length === 0) return result;\n\n const schemas = Array.from(new Set(plans.map(p => p.schema)));\n const existing = await readExistingSchema(client, schemas);\n\n for (const plan of plans) {\n if (!existing.tables.has(plan.qualified)) {\n result.skipped.push({\n table: plan.qualified,\n reason: \"table is not present in the database; create it with `rebase db push` / `rebase db migrate`\"\n });\n continue;\n }\n\n try {\n // Enable first: if a later policy statement fails, the table is left\n // locked (deny-all for the user role) rather than open.\n await client.query(plan.enableRls);\n result.tablesSecured++;\n\n let created = 0;\n for (const statement of plan.policyStatements) {\n await client.query(statement);\n if (isCreatePolicy(statement)) {\n result.policiesApplied++;\n created++;\n }\n }\n log?.(`${plan.qualified}: RLS enabled, ${created} policy(ies) applied`);\n } catch (err) {\n result.failures.push({\n table: plan.qualified,\n error: err instanceof Error ? err.message : String(err)\n });\n }\n }\n\n return result;\n}\n"],"mappings":";;;;;AAgDA,IAAM,kBAAkB,cAA+B,qBAAqB,KAAK,SAAS;;;;;;;;;AAU1F,eAAsB,yBAClB,QACA,aACA,KAC2B;CAC3B,MAAM,SAA6B;EAAE,iBAAiB;EAAG,eAAe;EAAG,SAAS,CAAC;EAAG,UAAU,CAAC;CAAE;CAErG,MAAM,QAAQ,uBAAuB,WAAW;CAChD,IAAI,MAAM,WAAW,GAAG,OAAO;CAG/B,MAAM,WAAW,MAAM,mBAAmB,QAD1B,MAAM,KAAK,IAAI,IAAI,MAAM,KAAI,MAAK,EAAE,MAAM,CAAC,CACT,CAAO;CAEzD,KAAK,MAAM,QAAQ,OAAO;EACtB,IAAI,CAAC,SAAS,OAAO,IAAI,KAAK,SAAS,GAAG;GACtC,OAAO,QAAQ,KAAK;IAChB,OAAO,KAAK;IACZ,QAAQ;GACZ,CAAC;GACD;EACJ;EAEA,IAAI;GAGA,MAAM,OAAO,MAAM,KAAK,SAAS;GACjC,OAAO;GAEP,IAAI,UAAU;GACd,KAAK,MAAM,aAAa,KAAK,kBAAkB;IAC3C,MAAM,OAAO,MAAM,SAAS;IAC5B,IAAI,eAAe,SAAS,GAAG;KAC3B,OAAO;KACP;IACJ;GACJ;GACA,MAAM,GAAG,KAAK,UAAU,iBAAiB,QAAQ,qBAAqB;EAC1E,SAAS,KAAK;GACV,OAAO,SAAS,KAAK;IACjB,OAAO,KAAK;IACZ,OAAO,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;GAC1D,CAAC;EACL;CACJ;CAEA,OAAO;AACX"}
1
+ {"version":3,"file":"ensure-collection-policies-CT-zIUWA.js","names":[],"sources":["../src/schema/ensure-collection-policies.ts"],"sourcesContent":["/**\n * Applying a bundle's RLS policies to a database at boot, idempotently.\n *\n * ## Why this exists\n *\n * {@link ensureCollectionTables} creates the collection *tables* a managed\n * runtime boots against, but a table with row-level security disabled and no\n * policies is not servable: authenticated requests run as the restricted\n * `rebase_user` role, so a read with no `SELECT` policy returns nothing (a\n * public collection answered 401) and a write with no `INSERT`/`UPDATE` policy\n * is denied. The policies live in the collections' `securityRules`; nothing at\n * boot applied them. `rebase db push` does — but it drives Atlas against a\n * local `DATABASE_URL`, and a managed tenant's database is reachable only from\n * inside the cluster, by the runtime that is already connected to it. So the\n * runtime is the only thing that *can* apply them, and this is where it does.\n *\n * ## Why this is safe to run on every boot\n *\n * Every statement is idempotent: `ENABLE ROW LEVEL SECURITY` is a no-op once\n * enabled, and each policy is a `DROP POLICY IF EXISTS` immediately followed by\n * a `CREATE POLICY`, so re-applying asserts exactly the declared state. It adds\n * and replaces; it never drops data. (It does not *reconcile* — a policy a\n * previous push left behind under an old name is not removed here; that stays a\n * `db push` / `db migrate` concern, alongside destructive schema changes.)\n *\n * Unlike table creation, a failure here is not fatal: RLS stays enabled, so a\n * table whose policies could not be applied fails **closed** (denies) rather\n * than leaking rows. One collection's policy failing (e.g. a rule that\n * references a table a real migration has not created yet) must not crash-loop\n * the whole deployment and take the other collections' working routes down with\n * it. Failures are reported loudly and per-table so the operator can see\n * exactly which collection is not yet servable and why.\n */\nimport { type CollectionConfig } from \"@rebasepro/types\";\nimport { planCollectionPolicies } from \"./generate-postgres-ddl-logic\";\nimport { readExistingSchema, type Queryable } from \"./ensure-collection-tables\";\n\nexport interface PolicyEnsureResult {\n /** `CREATE POLICY` statements that ran successfully. */\n policiesApplied: number;\n /** Tables that had RLS enabled. */\n tablesSecured: number;\n /** Declared tables absent from the database — left to a real migration. */\n skipped: { table: string; reason: string }[];\n /** Tables whose RLS could not be fully applied (fail closed). */\n failures: { table: string; error: string }[];\n}\n\nconst isCreatePolicy = (statement: string): boolean => /^\\s*CREATE POLICY/i.test(statement);\n\n/**\n * Bring the declared collections' RLS policies up to date. Returns what it did.\n *\n * Only tables that already exist are touched: the boot-time table creator runs\n * first, so anything still missing is a table this additive path is not allowed\n * to create (a junction, or a relation left to a migration). Enabling RLS on a\n * non-existent table would error, so those are recorded as skipped, not failed.\n */\nexport async function ensureCollectionPolicies(\n client: Queryable,\n collections: CollectionConfig[],\n log?: (message: string) => void\n): Promise<PolicyEnsureResult> {\n const result: PolicyEnsureResult = { policiesApplied: 0, tablesSecured: 0, skipped: [], failures: [] };\n\n const plans = planCollectionPolicies(collections);\n if (plans.length === 0) return result;\n\n const schemas = Array.from(new Set(plans.map(p => p.schema)));\n const existing = await readExistingSchema(client, schemas);\n\n for (const plan of plans) {\n if (!existing.tables.has(plan.qualified)) {\n result.skipped.push({\n table: plan.qualified,\n reason: \"table is not present in the database; create it with `rebase db push` / `rebase db migrate`\"\n });\n continue;\n }\n\n try {\n // Enable first: if a later policy statement fails, the table is left\n // locked (deny-all for the user role) rather than open.\n await client.query(plan.enableRls);\n result.tablesSecured++;\n\n let created = 0;\n for (const statement of plan.policyStatements) {\n await client.query(statement);\n if (isCreatePolicy(statement)) {\n result.policiesApplied++;\n created++;\n }\n }\n log?.(`${plan.qualified}: RLS enabled, ${created} policy(ies) applied`);\n } catch (err) {\n result.failures.push({\n table: plan.qualified,\n error: err instanceof Error ? err.message : String(err)\n });\n }\n }\n\n return result;\n}\n"],"mappings":";;;;;AAgDA,IAAM,kBAAkB,cAA+B,qBAAqB,KAAK,SAAS;;;;;;;;;AAU1F,eAAsB,yBAClB,QACA,aACA,KAC2B;CAC3B,MAAM,SAA6B;EAAE,iBAAiB;EAAG,eAAe;EAAG,SAAS,CAAC;EAAG,UAAU,CAAC;CAAE;CAErG,MAAM,QAAQ,uBAAuB,WAAW;CAChD,IAAI,MAAM,WAAW,GAAG,OAAO;CAG/B,MAAM,WAAW,MAAM,mBAAmB,QAD1B,MAAM,KAAK,IAAI,IAAI,MAAM,KAAI,MAAK,EAAE,MAAM,CAAC,CACT,CAAO;CAEzD,KAAK,MAAM,QAAQ,OAAO;EACtB,IAAI,CAAC,SAAS,OAAO,IAAI,KAAK,SAAS,GAAG;GACtC,OAAO,QAAQ,KAAK;IAChB,OAAO,KAAK;IACZ,QAAQ;GACZ,CAAC;GACD;EACJ;EAEA,IAAI;GAGA,MAAM,OAAO,MAAM,KAAK,SAAS;GACjC,OAAO;GAEP,IAAI,UAAU;GACd,KAAK,MAAM,aAAa,KAAK,kBAAkB;IAC3C,MAAM,OAAO,MAAM,SAAS;IAC5B,IAAI,eAAe,SAAS,GAAG;KAC3B,OAAO;KACP;IACJ;GACJ;GACA,MAAM,GAAG,KAAK,UAAU,iBAAiB,QAAQ,qBAAqB;EAC1E,SAAS,KAAK;GACV,OAAO,SAAS,KAAK;IACjB,OAAO,KAAK;IACZ,OAAO,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;GAC1D,CAAC;EACL;CACJ;CAEA,OAAO;AACX"}
@@ -1,7 +1,7 @@
1
1
  import { createRequire as __createRequire } from "module";
2
2
  import "process";
3
3
  __createRequire(import.meta.url);
4
- import { O as toSnakeCase, T as getPolicyNamesForRule, a as getJunctionSecurityRules, c as policyToPostgres, d as findRelation, g as resolveCollectionRelations, i as getJunctionCollectionConfig, l as securityRuleToConditions, m as getTableName, o as resolveJunctionSpecs, r as resolveStringColumnLength, s as getEffectiveSecurityRules } from "./src-DihrDFuP.js";
4
+ import { O as toSnakeCase, T as getPolicyNamesForRule, c as policyToPostgres, d as findRelation, g as resolveCollectionRelations, l as securityRuleToConditions, m as getTableName, r as resolveStringColumnLength, s as getEffectiveSecurityRules } from "./src-DihrDFuP.js";
5
5
  import { n as isPostgresCollectionConfig } from "./src-DoU9yPqq.js";
6
6
  //#region src/schema/generate-postgres-ddl-logic.ts
7
7
  var resolveColumnName = (propName, prop) => {
@@ -33,14 +33,6 @@ var getPrimaryKeyProp = (collection) => {
33
33
  isUuid: idProp?.type === "string" && "isId" in idProp && idProp.isId === "uuid"
34
34
  };
35
35
  };
36
- var isNumericId = (collection) => {
37
- return getPrimaryKeyProp(collection).type === "number";
38
- };
39
- var getPrimaryKeyName = (collection) => {
40
- return getPrimaryKeyProp(collection).name;
41
- };
42
- /** The column type a junction holds for one endpoint's primary key. */
43
- var junctionKeyType = (collection) => isNumericId(collection) ? "INTEGER" : getPrimaryKeyProp(collection).isUuid ? "UUID" : "TEXT";
44
36
  var isIdProperty = (propName, prop, collection) => {
45
37
  if ("isId" in prop && Boolean(prop.isId)) return true;
46
38
  return !Object.values(collection.properties ?? {}).some((p) => "isId" in p && Boolean(p.isId)) && propName === "id";
@@ -155,153 +147,21 @@ var getSqlColumnType = (propName, prop, collection, collections) => {
155
147
  default: return "TEXT";
156
148
  }
157
149
  };
158
- var schemaOfCollection = (collection) => isPostgresCollectionConfig(collection) && collection.schema ? collection.schema : "public";
159
- var bareTableName = (name) => name.includes(".") ? name.split(".").pop() : name;
160
- var foreignKeyPlan = (args) => {
161
- const constraintName = `${args.table}_${args.column}_fkey`;
162
- const onUpdate = args.onUpdate ? ` ON UPDATE ${args.onUpdate.toUpperCase()}` : "";
163
- return {
164
- constraintName,
165
- schema: args.schema,
166
- table: args.table,
167
- column: args.column,
168
- targetSchema: args.targetSchema,
169
- targetTable: args.targetTable,
170
- targetColumn: args.targetColumn,
171
- sql: `ALTER TABLE "${args.schema}"."${args.table}" ADD CONSTRAINT "${constraintName}" FOREIGN KEY ("${args.column}") REFERENCES "${args.targetSchema}"."${args.targetTable}" ("${args.targetColumn}") ON DELETE ${args.onDelete.toUpperCase()}${onUpdate};`
172
- };
173
- };
174
- /**
175
- * The FK columns the declared collections own — one entry per `relation`
176
- * (`belongsTo` side) or `reference` property.
177
- *
178
- * Split out of {@link generatePostgresDdl} so the boot-time schema ensure can
179
- * create the same columns with the same names, types and constraints. Before
180
- * this it skipped them outright, which was survivable only because `db push`
181
- * always followed; on a managed tenant nothing follows, so a table arrived
182
- * without the column its own collection reads and wrote 400 on every insert.
183
- *
184
- * A relation whose target is not in the bundle yields no column at all (the
185
- * generator returns early on an unresolvable target); a `reference` whose target
186
- * is unknown yields the column without a constraint. Both mirror the generator
187
- * exactly — a divergence here is a schema fork between boot and `db push`.
188
- */
189
- var planRelationalColumns = (collections) => {
190
- const plans = [];
191
- for (const collection of collections) {
192
- const tableName = getTableName(collection);
193
- if (!tableName) continue;
194
- const schema = schemaOfCollection(collection);
195
- const table = bareTableName(tableName);
196
- for (const [propName, rawProp] of Object.entries(collection.properties ?? {})) {
197
- const prop = rawProp;
198
- if (prop.type === "relation") {
199
- const refProp = prop;
200
- const relInfo = findRelation(resolveCollectionRelations(collection), refProp.relation?.relationName ?? propName);
201
- if (relInfo?.kind !== "belongsTo") continue;
202
- if (collection.properties[relInfo.localKey] && propName !== relInfo.localKey) continue;
203
- let targetCollection;
204
- try {
205
- targetCollection = relInfo.target();
206
- } catch {
207
- continue;
208
- }
209
- if (!targetCollection) continue;
210
- const required = prop.validation?.required;
211
- plans.push({
212
- schema,
213
- table,
214
- column: relInfo.localKey,
215
- type: getSqlColumnType(propName, prop, collection, collections),
216
- foreignKey: foreignKeyPlan({
217
- schema,
218
- table,
219
- column: relInfo.localKey,
220
- targetSchema: schemaOfCollection(targetCollection),
221
- targetTable: bareTableName(getTableName(targetCollection)),
222
- targetColumn: getPrimaryKeyName(targetCollection),
223
- onDelete: relInfo.onDelete ?? (required ? "CASCADE" : "SET NULL"),
224
- onUpdate: relInfo.onUpdate
225
- })
226
- });
227
- } else if (prop.type === "reference") {
228
- const refProp = prop;
229
- const targetCollection = collections.find((c) => c.slug === refProp.path || getTableName(c) === refProp.path);
230
- const column = resolveColumnName(propName, prop);
231
- const type = getSqlColumnType(propName, prop, collection, collections);
232
- const required = prop.validation?.required;
233
- plans.push({
234
- schema,
235
- table,
236
- column,
237
- type,
238
- foreignKey: targetCollection ? foreignKeyPlan({
239
- schema,
240
- table,
241
- column,
242
- targetSchema: schemaOfCollection(targetCollection),
243
- targetTable: bareTableName(getTableName(targetCollection)),
244
- targetColumn: getPrimaryKeyName(targetCollection),
245
- onDelete: required ? "CASCADE" : "SET NULL"
246
- }) : void 0
247
- });
248
- }
249
- }
250
- }
251
- return plans;
252
- };
253
- /**
254
- * The junction tables a bundle's many-to-many relations imply.
255
- *
256
- * Derived from {@link resolveJunctionSpecs}, the same source the junction RLS
257
- * comes from, so a table created here always has policies planned for it — a
258
- * junction with row-level security left off is readable and writable by every
259
- * signed-in user, which is why the two must ship together.
260
- */
261
- var planJunctionTables = (collections) => {
262
- const plans = [];
263
- for (const spec of resolveJunctionSpecs(collections).values()) {
264
- const [source, target] = spec.endpoints;
265
- const columns = [{
266
- name: source.junctionColumn,
267
- type: junctionKeyType(source.collection)
268
- }, {
269
- name: target.junctionColumn,
270
- type: junctionKeyType(target.collection)
271
- }];
272
- const onDelete = spec.declaringSides[0]?.relation.onDelete ?? "CASCADE";
273
- plans.push({
274
- schema: spec.schema,
275
- table: spec.table,
276
- columns,
277
- createTable: `CREATE TABLE IF NOT EXISTS "${spec.schema}"."${spec.table}" (` + columns.map((c) => `"${c.name}" ${c.type} NOT NULL`).join(", ") + `, PRIMARY KEY (${columns.map((c) => `"${c.name}"`).join(", ")}));`,
278
- foreignKeys: [source, target].map((endpoint, i) => foreignKeyPlan({
279
- schema: spec.schema,
280
- table: spec.table,
281
- column: columns[i].name,
282
- targetSchema: schemaOfCollection(endpoint.collection),
283
- targetTable: bareTableName(getTableName(endpoint.collection)),
284
- targetColumn: getPrimaryKeyName(endpoint.collection),
285
- onDelete
286
- }))
287
- });
288
- }
289
- return plans;
290
- };
291
150
  /**
292
151
  * The per-table RLS plan for the *declared* collections, as executable
293
152
  * statements — what the managed runtime applies at boot so a freshly
294
153
  * provisioned tenant database serves data instead of 401ing every read.
295
154
  *
296
- * Mirrors {@link generatePostgresPoliciesDdl} exactly (same
297
- * `generatePolicyStatements`, same enable-RLS, same effective rules, same
298
- * derived junction rules), so boot and `db push` produce identical policies from
299
- * identical collections.
155
+ * Mirrors the non-junction half of {@link generatePostgresPoliciesDdl} exactly
156
+ * (same `generatePolicyStatements`, same enable-RLS, same effective rules), so
157
+ * boot and `db push` produce identical policies from identical collections.
300
158
  *
301
- * Junction tables are included, and have to be: boot creates them now
302
- * ({@link planJunctionTables}), and a junction with RLS left off is readable and
303
- * writable by every signed-in user. A junction whose table is still absent is
304
- * skipped by the applier, not planned away here.
159
+ * Junction tables are deliberately excluded: they are derived from `through`
160
+ * relations, not declared collections, and the boot-time *table* creator
161
+ * (`ensureCollectionTables`) does not create them either — enabling RLS on a
162
+ * table that boot never created would fail. Their RLS stays a `db push` /
163
+ * `db migrate` concern, which is where those tables get created in the first
164
+ * place. `db push` still applies junction policies via the string generator.
305
165
  */
306
166
  var planCollectionPolicies = (collections) => {
307
167
  const resolveCollection = (slug) => collections.find((c) => c.slug === slug || getTableName(c) === slug);
@@ -325,21 +185,6 @@ var planCollectionPolicies = (collections) => {
325
185
  policyStatements
326
186
  });
327
187
  }
328
- for (const spec of resolveJunctionSpecs(collections).values()) {
329
- const qualified = `${spec.schema}.${spec.table}`;
330
- if (seen.has(qualified)) continue;
331
- seen.add(qualified);
332
- const junctionCollection = getJunctionCollectionConfig(spec);
333
- const policyStatements = [];
334
- for (const rule of getJunctionSecurityRules(spec)) policyStatements.push(...generatePolicyStatements(junctionCollection, rule, resolveCollection));
335
- plans.push({
336
- schema: spec.schema,
337
- table: spec.table,
338
- qualified,
339
- enableRls: `ALTER TABLE "${spec.schema}"."${spec.table}" ENABLE ROW LEVEL SECURITY;`,
340
- policyStatements
341
- });
342
- }
343
188
  return plans;
344
189
  };
345
190
  //#endregion
@@ -452,56 +297,25 @@ function planCollectionSchemaEnsure(collections, existing) {
452
297
  sql: `CREATE TABLE IF NOT EXISTS "${schema}"."${table}" (${idDef});`
453
298
  });
454
299
  }
455
- const junctions = planJunctionTables(collections);
456
- for (const junction of junctions) {
457
- const key = `${junction.schema}.${junction.table}`;
458
- if (existing.tables.has(key) || created.has(key)) continue;
459
- created.add(key);
460
- actions.push({
461
- kind: "create-table",
462
- target: key,
463
- sql: junction.createTable
464
- });
465
- }
466
- const addColumn = (key, schema, table, column, type) => {
467
- if (existing.tables.get(key)?.has(column)) return;
468
- actions.push({
469
- kind: "add-column",
470
- target: `${key}.${column}`,
471
- sql: `ALTER TABLE "${schema}"."${table}" ADD COLUMN IF NOT EXISTS "${column}" ${type};`
472
- });
473
- };
474
300
  for (const collection of collections) {
475
301
  const key = qualified(collection);
476
302
  const schema = schemaOf(collection);
477
303
  const table = getTableName(collection);
304
+ const present = existing.tables.get(key) ?? /* @__PURE__ */ new Set();
478
305
  for (const [propName, prop] of Object.entries(collection.properties ?? {})) {
479
306
  const p = prop;
480
307
  if (isIdProperty(propName, p, collection)) continue;
481
308
  if (p.type === "reference" || p.type === "relation") continue;
482
- addColumn(key, schema, table, resolveColumnName(propName, p), getSqlColumnType(propName, p, collection, collections));
309
+ const column = resolveColumnName(propName, p);
310
+ if (present.has(column)) continue;
311
+ const type = getSqlColumnType(propName, p, collection, collections);
312
+ actions.push({
313
+ kind: "add-column",
314
+ target: `${key}.${column}`,
315
+ sql: `ALTER TABLE "${schema}"."${table}" ADD COLUMN IF NOT EXISTS "${column}" ${type};`
316
+ });
483
317
  }
484
318
  }
485
- for (const junction of junctions) {
486
- const key = `${junction.schema}.${junction.table}`;
487
- if (created.has(key)) continue;
488
- for (const column of junction.columns) addColumn(key, junction.schema, junction.table, column.name, column.type);
489
- }
490
- for (const relational of planRelationalColumns(collections)) addColumn(`${relational.schema}.${relational.table}`, relational.schema, relational.table, relational.column, relational.type);
491
- const knownConstraints = existing.constraints ?? /* @__PURE__ */ new Set();
492
- const plannedConstraints = /* @__PURE__ */ new Set();
493
- const foreignKeys = [...planRelationalColumns(collections).map((r) => r.foreignKey), ...junctions.flatMap((j) => j.foreignKeys)];
494
- for (const fk of foreignKeys) {
495
- if (!fk) continue;
496
- const name = `${fk.schema}.${fk.table}.${fk.constraintName}`;
497
- if (knownConstraints.has(name) || plannedConstraints.has(name)) continue;
498
- plannedConstraints.add(name);
499
- actions.push({
500
- kind: "add-constraint",
501
- target: `${fk.schema}.${fk.table}.${fk.constraintName}`,
502
- sql: fk.sql
503
- });
504
- }
505
319
  return {
506
320
  actions,
507
321
  statements: actions.map((a) => a.sql)
@@ -529,17 +343,9 @@ async function readExistingSchema(client, schemas) {
529
343
  JOIN pg_namespace n ON t.typnamespace = n.oid
530
344
  WHERE t.typtype = 'e' AND n.nspname IN (${inList})`);
531
345
  for (const row of enumRows) enums.add(`${row.schema}.${row.name}`);
532
- const constraints = /* @__PURE__ */ new Set();
533
- const { rows: constraintRows } = await client.query(`SELECT n.nspname AS schema, c.relname AS table, con.conname AS name
534
- FROM pg_constraint con
535
- JOIN pg_class c ON con.conrelid = c.oid
536
- JOIN pg_namespace n ON c.relnamespace = n.oid
537
- WHERE n.nspname IN (${inList})`);
538
- for (const row of constraintRows) constraints.add(`${row.schema}.${row.table}.${row.name}`);
539
346
  return {
540
347
  tables,
541
- enums,
542
- constraints
348
+ enums
543
349
  };
544
350
  }
545
351
  /**
@@ -551,40 +357,25 @@ async function readExistingSchema(client, schemas) {
551
357
  * The error is surfaced with the statement that caused it.
552
358
  */
553
359
  async function ensureCollectionTables(client, collections, log) {
554
- const schemas = Array.from(/* @__PURE__ */ new Set([...collections.map(schemaOf), ...planJunctionTables(collections).map((j) => j.schema)]));
360
+ const schemas = Array.from(new Set(collections.map(schemaOf)));
555
361
  for (const schema of schemas) {
556
362
  assertSafeIdentifier(schema, "schema name");
557
363
  if (schema !== "public") await client.query(`CREATE SCHEMA IF NOT EXISTS "${schema}";`);
558
364
  }
559
365
  const plan = planCollectionSchemaEnsure(collections, await readExistingSchema(client, schemas));
560
- const failures = [];
561
366
  if (plan.actions.length === 0) {
562
367
  log?.("Schema is up to date; nothing to create.");
563
- return {
564
- ...plan,
565
- failures
566
- };
368
+ return plan;
567
369
  }
568
370
  for (const action of plan.actions) try {
569
371
  await client.query(action.sql);
570
372
  log?.(`${action.kind}: ${action.target}`);
571
373
  } catch (err) {
572
- const message = err instanceof Error ? err.message : String(err);
573
- if (action.kind === "add-constraint") {
574
- failures.push({
575
- target: action.target,
576
- error: message
577
- });
578
- continue;
579
- }
580
- throw new Error(`Failed to ${action.kind} ${action.target}: ${message}\n ${action.sql}`);
374
+ throw new Error(`Failed to ${action.kind} ${action.target}: ${err instanceof Error ? err.message : String(err)}\n ${action.sql}`);
581
375
  }
582
- return {
583
- ...plan,
584
- failures
585
- };
376
+ return plan;
586
377
  }
587
378
  //#endregion
588
379
  export { ensureCollectionTables, readExistingSchema, planCollectionPolicies as t };
589
380
 
590
- //# sourceMappingURL=ensure-collection-tables-DsDsNl6o.js.map
381
+ //# sourceMappingURL=ensure-collection-tables-Vu-GRELM.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ensure-collection-tables-Vu-GRELM.js","names":[],"sources":["../src/schema/generate-postgres-ddl-logic.ts","../src/schema/ensure-collection-tables.ts"],"sourcesContent":["import { CollectionConfig, NumberProperty, Property, ResolvedRelation, RelationProperty, SecurityOperation, SecurityRule, StringProperty, isPostgresCollectionConfig, DateProperty, ArrayProperty, MapProperty, ReferenceProperty, VectorProperty, BinaryProperty, isManyToMany, type ResolvedManyToMany, type ResolvedBelongsTo } from \"@rebasepro/types\";\nimport { getEnumVarName, getTableName, resolveCollectionRelations, findRelation, securityRuleToConditions, policyToPostgres, getEffectiveSecurityRules, getInjectedSecurityRules, resolveJunctionSpecs, getJunctionSecurityRules, getJunctionCollectionConfig, resolveStringColumnLength } from \"@rebasepro/common\";\nimport { toSnakeCase, getPolicyNamesForRule } from \"@rebasepro/utils\";\n\n// --- Helper Functions ---\n\nexport const resolveColumnName = (propName: string, prop?: Property | null): string => {\n if (prop && \"columnName\" in prop && typeof prop.columnName === \"string\") {\n return prop.columnName;\n }\n return toSnakeCase(propName);\n};\n\nconst getPrimaryKeyProp = (collection: CollectionConfig): { name: string, type: \"string\" | \"number\", isUuid: boolean } => {\n if (collection.properties) {\n const idPropEntry = Object.entries(collection.properties).find(([_, prop]) => \"isId\" in (prop as unknown as object) && Boolean((prop as unknown as Record<string, unknown>).isId));\n if (idPropEntry) {\n const prop = idPropEntry[1] as unknown as Property;\n const isUuid = prop.type === \"string\" && \"isId\" in prop && (prop as unknown as StringProperty).isId === \"uuid\";\n return { name: idPropEntry[0], type: prop.type === \"number\" ? \"number\" : \"string\", isUuid };\n }\n }\n const idProp = collection.properties?.[\"id\"] as unknown as Property | undefined;\n if (idProp?.type === \"number\") {\n return { name: \"id\", type: \"number\", isUuid: false };\n }\n const isUuid = idProp?.type === \"string\" && \"isId\" in idProp && (idProp as unknown as StringProperty).isId === \"uuid\";\n return { name: \"id\", type: \"string\", isUuid: isUuid ?? false };\n};\n\nconst isNumericId = (collection: CollectionConfig): boolean => {\n return getPrimaryKeyProp(collection).type === \"number\";\n};\n\nconst getPrimaryKeyName = (collection: CollectionConfig): string => {\n return getPrimaryKeyProp(collection).name;\n};\n\nexport const isIdProperty = (propName: string, prop: Property, collection: CollectionConfig): boolean => {\n if (\"isId\" in prop && Boolean(prop.isId)) return true;\n const hasExplicitId = Object.values(collection.properties ?? {}).some(p => \"isId\" in (p as unknown as object) && Boolean((p as unknown as Record<string, unknown>).isId));\n return !hasExplicitId && propName === \"id\";\n};\n\n\ntype ResolveCollection = (slug: string) => CollectionConfig | undefined;\n\n/**\n * Render statements produced by {@link generatePolicyStatements} back into the\n * exact string the DDL/policies files have always carried: each statement on\n * its own line, terminated by a newline. Keeping the string form derived from\n * the statement array means the two can never drift — the boot-time applier and\n * the generated `policies.sql` emit the same SQL, from the same source.\n */\nconst statementsToDdl = (statements: string[]): string => statements.map(s => `${s}\\n`).join(\"\");\n\nconst generatePolicyDdl = (collection: CollectionConfig, rule: SecurityRule, resolveCollection: ResolveCollection): string =>\n statementsToDdl(generatePolicyStatements(collection, rule, resolveCollection));\n\n/**\n * The individual SQL statements a single security rule compiles to: a\n * `DROP POLICY IF EXISTS` / `CREATE POLICY` pair per operation, each a complete\n * statement (terminated by `;`, no trailing newline).\n *\n * This is the primitive the boot-time RLS applier runs one statement at a time\n * (the runtime's DB handle speaks the extended query protocol, which forbids\n * multiple commands in one execute), while `db push` writes the joined string.\n */\nexport const generatePolicyStatements = (collection: CollectionConfig, rule: SecurityRule, resolveCollection: ResolveCollection): string[] => {\n const tableName = getTableName(collection);\n const ops: readonly SecurityOperation[] = rule.operations && rule.operations.length > 0\n ? rule.operations\n : [rule.operation ?? \"all\"];\n\n const policyNames = getPolicyNamesForRule(rule, tableName);\n\n return ops.flatMap((op, opIdx) => {\n return generateSinglePolicyStatements(collection, rule, op, policyNames[opIdx], resolveCollection);\n });\n};\n\nconst generateSinglePolicyStatements = (collection: CollectionConfig, rule: SecurityRule, operation: SecurityOperation, policyName: string, resolveCollection: ResolveCollection): string[] => {\n const schema = isPostgresCollectionConfig(collection) && collection.schema ? collection.schema : \"public\";\n const tableName = getTableName(collection);\n const mode = (rule.mode ?? \"permissive\").toUpperCase();\n const operationUpper = operation.toUpperCase();\n const pgRoles = rule.pgRoles ? [...rule.pgRoles].sort() : [\"public\"];\n\n const needsUsing = operation !== \"insert\";\n const needsWithCheck = operation !== \"select\" && operation !== \"delete\";\n\n // Desugar the rule (access / ownerField / roles / structured condition / raw\n // SQL) into the shared PolicyExpression model, then compile to SQL. This is\n // the same normalization the client-side evaluator uses, so DDL and UI agree.\n const { usingExpr, withCheckExpr } = securityRuleToConditions(rule);\n\n let usingClause = needsUsing && usingExpr ? policyToPostgres(usingExpr, collection, { resolveCollection }) : null;\n let withCheckClause = needsWithCheck && withCheckExpr ? policyToPostgres(withCheckExpr, collection, { resolveCollection }) : null;\n\n if (!usingClause && needsUsing) {\n usingClause = \"false\";\n }\n if (!withCheckClause && needsWithCheck) {\n withCheckClause = \"false\";\n }\n\n const drop = `DROP POLICY IF EXISTS \"${policyName}\" ON \"${schema}\".\"${tableName}\";`;\n let create = `CREATE POLICY \"${policyName}\" ON \"${schema}\".\"${tableName}\" AS ${mode} FOR ${operationUpper} TO ${pgRoles.map(r => `\"${r}\"`).join(\", \")}`;\n if (usingClause) create += ` USING (${usingClause})`;\n if (withCheckClause) create += ` WITH CHECK (${withCheckClause})`;\n create += \";\";\n return [drop, create];\n};\n\nexport const getSqlColumnType = (propName: string, prop: Property, collection: CollectionConfig, collections: CollectionConfig[]): string => {\n switch (prop.type) {\n case \"string\": {\n const stringProp = prop as StringProperty;\n if (stringProp.enum) {\n const tableName = getTableName(collection);\n const colName = resolveColumnName(propName, prop);\n const schema = isPostgresCollectionConfig(collection) && collection.schema ? collection.schema : \"public\";\n return `\"${schema}\".\"${tableName}_${colName}\"`;\n }\n if (stringProp.isId === \"uuid\" || stringProp.columnType === \"uuid\") {\n return \"UUID\";\n }\n // Width comes from `validation.max` when the property states one.\n // It used to be a hardcoded 255 here and *absent* on the Drizzle\n // path, so the same property produced a bounded column down one\n // generator and an unbounded one down the other.\n if (stringProp.columnType === \"char\") {\n return `CHAR(${resolveStringColumnLength(stringProp)})`;\n }\n if (stringProp.columnType === \"varchar\") {\n return `VARCHAR(${resolveStringColumnLength(stringProp)})`;\n }\n // `text` is the default. The two generators disagreed here before:\n // this one emitted VARCHAR(255) while the drizzle path emitted a bare\n // `varchar()`, which Postgres treats as unbounded — so the same\n // property produced a capped column down one path and an uncapped one\n // down the other.\n return \"TEXT\";\n }\n case \"number\": {\n const numProp = prop as NumberProperty;\n const isId = isIdProperty(propName, prop, collection);\n if (\"isId\" in numProp && numProp.isId === \"increment\") {\n return \"INTEGER GENERATED BY DEFAULT AS IDENTITY\";\n }\n if (numProp.columnType) {\n if (numProp.columnType === \"double precision\") return \"DOUBLE PRECISION\";\n return numProp.columnType.toUpperCase();\n }\n return (numProp.validation?.integer || isId) ? \"INTEGER\" : \"NUMERIC\";\n }\n case \"boolean\":\n return \"BOOLEAN\";\n case \"date\": {\n const dateProp = prop as DateProperty;\n if (dateProp.columnType === \"date\") return \"DATE\";\n if (dateProp.columnType === \"time\") return \"TIME\";\n return \"TIMESTAMP WITH TIME ZONE\";\n }\n case \"map\": {\n const mapProp = prop as MapProperty;\n return mapProp.columnType === \"json\" ? \"JSON\" : \"JSONB\";\n }\n case \"array\": {\n const arrayProp = prop as ArrayProperty;\n let colType = arrayProp.columnType;\n if (!colType && arrayProp.of && !Array.isArray(arrayProp.of)) {\n const ofProp = arrayProp.of as Property;\n if (ofProp.type === \"string\") {\n colType = \"text[]\";\n } else if (ofProp.type === \"number\") {\n colType = ofProp.validation?.integer ? \"integer[]\" : \"numeric[]\";\n } else if (ofProp.type === \"boolean\") {\n colType = \"boolean[]\";\n }\n }\n if (colType === \"json\") return \"JSON\";\n if (colType === \"text[]\") return \"TEXT[]\";\n if (colType === \"integer[]\") return \"INTEGER[]\";\n if (colType === \"boolean[]\") return \"BOOLEAN[]\";\n if (colType === \"numeric[]\") return \"NUMERIC[]\";\n return \"JSONB\";\n }\n case \"vector\": {\n const vp = prop as VectorProperty;\n return `VECTOR(${vp.dimensions})`;\n }\n case \"binary\": {\n return \"BYTEA\";\n }\n case \"relation\": {\n const refProp = prop as RelationProperty;\n const resolvedRelations = resolveCollectionRelations(collection);\n const relation = findRelation(resolvedRelations, refProp.relation?.relationName ?? propName);\n if (relation?.kind !== \"belongsTo\") {\n throw new Error(`Relation ${propName} does not put a column on this table (only \\`belongsTo\\` does)`);\n }\n let targetCollection: CollectionConfig;\n try {\n targetCollection = relation.target();\n } catch {\n return \"TEXT\";\n }\n const pkProp = getPrimaryKeyProp(targetCollection);\n return pkProp.type === \"number\" ? \"INTEGER\" : (pkProp.isUuid ? \"UUID\" : \"TEXT\");\n }\n case \"reference\": {\n const refProp = prop as ReferenceProperty;\n const targetCollection = collections.find(c => c.slug === refProp.path || getTableName(c) === refProp.path);\n if (!targetCollection) return \"TEXT\";\n const pkProp = getPrimaryKeyProp(targetCollection);\n return pkProp.type === \"number\" ? \"INTEGER\" : (pkProp.isUuid ? \"UUID\" : \"TEXT\");\n }\n default:\n return \"TEXT\";\n }\n};\n\nexport const generatePostgresDdl = async (\n collections: CollectionConfig[],\n options: { includePolicies?: boolean } = { includePolicies: true }\n): Promise<string> => {\n let ddl = \"-- This file is auto-generated by the Rebase DDL generator. Do not edit manually.\\n\\n\";\n\n // 1. Create custom schemas\n const uniqueSchemas = Array.from(new Set([\n \"auth\",\n ...collections.map(c => isPostgresCollectionConfig(c) ? c.schema : undefined).filter(Boolean)\n ]));\n uniqueSchemas.forEach(schema => {\n if (schema) ddl += `CREATE SCHEMA IF NOT EXISTS \"${schema}\";\\n`;\n });\n if (uniqueSchemas.length > 0) ddl += \"\\n\";\n\n // 2. Generate Enums\n collections.forEach(collection => {\n const collectionTable = getTableName(collection);\n const schema = isPostgresCollectionConfig(collection) && collection.schema ? collection.schema : \"public\";\n Object.entries(collection.properties ?? {}).forEach(([propName, prop]) => {\n if ((\"enum\" in prop) && (prop.type === \"string\" || prop.type === \"number\") && prop.enum) {\n const enumDbName = `${collectionTable}_${resolveColumnName(propName, prop)}`;\n const values = Array.isArray(prop.enum)\n ? (prop.enum as (string | number | { id: string | number })[]).map((v: string | number | { id: string | number }) =>\n String(typeof v === \"object\" && v !== null && \"id\" in v ? v.id : v)\n )\n : Object.keys(prop.enum);\n if (values.length > 0) {\n ddl += `CREATE TYPE \"${schema}\".\"${enumDbName}\" AS ENUM (${values.map(v => `'${v}'`).join(\", \")});\\n`;\n }\n }\n });\n });\n if (ddl.endsWith(\";\\n\")) ddl += \"\\n\";\n\n // Junction policy derivation needs every declaring side of each junction,\n // not just the first relation that reached it in the walk below.\n const junctionSpecs = resolveJunctionSpecs(collections);\n\n const allTablesToGenerate = new Map<string, {\n collection: CollectionConfig,\n isJunction?: boolean,\n relation?: ResolvedRelation,\n sourceCollection?: CollectionConfig\n }>();\n\n // Identify all tables\n for (const collection of collections) {\n const tableName = getTableName(collection);\n if (tableName) {\n allTablesToGenerate.set(tableName, { collection });\n }\n\n const resolvedRelations = resolveCollectionRelations(collection);\n for (const relation of Object.values(resolvedRelations)) {\n if (isManyToMany(relation)) {\n const junctionTableName = relation.through.table;\n if (!allTablesToGenerate.has(junctionTableName)) {\n allTablesToGenerate.set(junctionTableName, {\n collection: {\n table: junctionTableName,\n properties: {}\n } as CollectionConfig,\n isJunction: true,\n relation: relation,\n sourceCollection: collection\n });\n }\n }\n }\n }\n\n // 3. Generate tables\n const fkStatements: string[] = [];\n // Policies are emitted after every CREATE TABLE, like the FK constraints:\n // a policy may reference other tables (a junction's derived policies always\n // reference both endpoints; `policy.existsIn` references a join table), and\n // CREATE POLICY validates those relations at creation time.\n const policyStatements: string[] = [];\n for (const [tableName, {\n collection,\n isJunction,\n relation,\n sourceCollection\n }] of allTablesToGenerate.entries()) {\n const schema = isPostgresCollectionConfig(collection) && collection.schema ? collection.schema : \"public\";\n const baseTableName = tableName.includes(\".\") ? tableName.split(\".\").pop()! : tableName;\n\n if (isJunction && relation && sourceCollection && isManyToMany(relation)) {\n const targetCollection = relation.target();\n const sourceTable = getTableName(sourceCollection);\n const targetTable = getTableName(targetCollection);\n const sourceSchema = isPostgresCollectionConfig(sourceCollection) && sourceCollection.schema ? sourceCollection.schema : \"public\";\n const targetSchema = isPostgresCollectionConfig(targetCollection) && targetCollection.schema ? targetCollection.schema : \"public\";\n const { sourceColumn, targetColumn } = relation.through;\n\n // TEXT, matching the string default: a junction column has to have the\n // same type as the primary key it references.\n const sourceColType = isNumericId(sourceCollection) ? \"INTEGER\" : (getPrimaryKeyProp(sourceCollection).isUuid ? \"UUID\" : \"TEXT\");\n const targetColType = isNumericId(targetCollection) ? \"INTEGER\" : (getPrimaryKeyProp(targetCollection).isUuid ? \"UUID\" : \"TEXT\");\n const sourceId = getPrimaryKeyName(sourceCollection);\n const targetId = getPrimaryKeyName(targetCollection);\n\n const onDelete = relation.onDelete ?? \"CASCADE\";\n\n ddl += `CREATE TABLE \"${schema}\".\"${baseTableName}\" (\\n`;\n ddl += ` \"${sourceColumn}\" ${sourceColType} NOT NULL,\\n`;\n ddl += ` \"${targetColumn}\" ${targetColType} NOT NULL,\\n`;\n ddl += ` PRIMARY KEY (\"${sourceColumn}\", \"${targetColumn}\")\\n`;\n ddl += `);\\n\\n`;\n\n fkStatements.push(`ALTER TABLE \"${schema}\".\"${baseTableName}\" ADD CONSTRAINT \"${baseTableName}_${sourceColumn}_fkey\" FOREIGN KEY (\"${sourceColumn}\") REFERENCES \"${sourceSchema}\".\"${sourceTable}\" (\"${sourceId}\") ON DELETE ${onDelete.toUpperCase()};`);\n fkStatements.push(`ALTER TABLE \"${schema}\".\"${baseTableName}\" ADD CONSTRAINT \"${baseTableName}_${targetColumn}_fkey\" FOREIGN KEY (\"${targetColumn}\") REFERENCES \"${targetSchema}\".\"${targetTable}\" (\"${targetId}\") ON DELETE ${onDelete.toUpperCase()};`);\n\n if (options.includePolicies) {\n // Junction tables are generated tables like any other: locked by\n // default, with derived policies — reads follow the endpoints'\n // visibility, writes follow the declaring side's update rules.\n // Without this they were the one kind of generated table with no\n // RLS at all, readable and writable by every signed-in user.\n ddl += `ALTER TABLE \"${schema}\".\"${baseTableName}\" ENABLE ROW LEVEL SECURITY;\\n`;\n ddl += `\\n`;\n\n const spec = junctionSpecs.get(baseTableName);\n if (spec) {\n const junctionCollection = getJunctionCollectionConfig(spec);\n const resolveCollection: ResolveCollection = (slug) => collections.find(c => c.slug === slug || getTableName(c) === slug);\n getJunctionSecurityRules(spec).forEach((rule: SecurityRule) => {\n policyStatements.push(generatePolicyDdl(junctionCollection, rule, resolveCollection));\n });\n }\n }\n } else if (!isJunction) {\n ddl += `CREATE TABLE \"${schema}\".\"${baseTableName}\" (\\n`;\n const columns: string[] = [];\n\n Object.entries(collection.properties ?? {}).forEach(([propName, prop]) => {\n if (prop.type === \"relation\") {\n const refProp = prop as RelationProperty;\n const resolvedRelations = resolveCollectionRelations(collection);\n const relInfo = findRelation(resolvedRelations, refProp.relation?.relationName ?? propName);\n\n if (relInfo?.kind !== \"belongsTo\") {\n return;\n }\n\n if (collection.properties[relInfo.localKey] && propName !== relInfo.localKey) {\n return;\n }\n\n let targetCollection: CollectionConfig;\n try {\n targetCollection = relInfo.target();\n } catch {\n return;\n }\n\n const targetTable = getTableName(targetCollection);\n const targetSchema = isPostgresCollectionConfig(targetCollection) && targetCollection.schema ? targetCollection.schema : \"public\";\n const targetId = getPrimaryKeyName(targetCollection);\n const fkColType = getSqlColumnType(propName, prop, collection, collections);\n \n const onUpdate = relInfo.onUpdate ? ` ON UPDATE ${relInfo.onUpdate.toUpperCase()}` : \"\";\n const required = prop.validation?.required;\n const onDeleteVal = relInfo.onDelete ?? (required ? \"CASCADE\" : \"SET NULL\");\n \n let colDef = ` \"${relInfo.localKey}\" ${fkColType}`;\n if (required) colDef += \" NOT NULL\";\n columns.push(colDef);\n\n fkStatements.push(`ALTER TABLE \"${schema}\".\"${baseTableName}\" ADD CONSTRAINT \"${baseTableName}_${relInfo.localKey}_fkey\" FOREIGN KEY (\"${relInfo.localKey}\") REFERENCES \"${targetSchema}\".\"${targetTable}\" (\"${targetId}\") ON DELETE ${onDeleteVal.toUpperCase()}${onUpdate};`);\n } else if (prop.type === \"reference\") {\n const refProp = prop as ReferenceProperty;\n const targetCollection = collections.find(c => c.slug === refProp.path || getTableName(c) === refProp.path);\n const colName = resolveColumnName(propName, prop);\n const colType = getSqlColumnType(propName, prop, collection, collections);\n const required = prop.validation?.required;\n\n if (!targetCollection) {\n let colDef = ` \"${colName}\" ${colType}`;\n if (required) colDef += \" NOT NULL\";\n columns.push(colDef);\n } else {\n const targetTable = getTableName(targetCollection);\n const targetSchema = isPostgresCollectionConfig(targetCollection) && targetCollection.schema ? targetCollection.schema : \"public\";\n const targetId = getPrimaryKeyName(targetCollection);\n const onDelete = required ? \"CASCADE\" : \"SET NULL\";\n\n let colDef = ` \"${colName}\" ${colType}`;\n if (required) colDef += \" NOT NULL\";\n columns.push(colDef);\n\n fkStatements.push(`ALTER TABLE \"${schema}\".\"${baseTableName}\" ADD CONSTRAINT \"${baseTableName}_${colName}_fkey\" FOREIGN KEY (\"${colName}\") REFERENCES \"${targetSchema}\".\"${targetTable}\" (\"${targetId}\") ON DELETE ${onDelete.toUpperCase()};`);\n }\n } else {\n const colName = resolveColumnName(propName, prop);\n const colType = getSqlColumnType(propName, prop, collection, collections);\n let colDef = ` \"${colName}\" ${colType}`;\n\n if (isIdProperty(propName, prop, collection)) {\n colDef += \" PRIMARY KEY\";\n }\n\n if (\"isId\" in prop && prop.isId !== \"manual\" && prop.isId !== true && prop.isId !== \"increment\") {\n if (prop.isId === \"uuid\") {\n colDef += \" DEFAULT gen_random_uuid()\";\n } else if (prop.isId === \"cuid\") {\n colDef += \" DEFAULT cuid()\";\n } else if (typeof prop.isId === \"string\") {\n colDef += ` DEFAULT ${prop.isId}`;\n }\n }\n\n if (!isIdProperty(propName, prop, collection) && prop.validation?.unique) {\n colDef += \" UNIQUE\";\n }\n\n if (prop.type === \"date\") {\n const dateProp = prop as DateProperty;\n if (dateProp.autoValue === \"on_create\" || dateProp.autoValue === \"on_update\") {\n colDef += \" DEFAULT now()\";\n }\n }\n\n if (prop.validation?.required && !colDef.includes(\"PRIMARY KEY\")) {\n colDef += \" NOT NULL\";\n }\n\n columns.push(colDef);\n }\n });\n\n // Backwards compatibility: add default id primary key if missing\n const hasPk = columns.some(c => c.includes(\"PRIMARY KEY\"));\n if (!hasPk) {\n columns.unshift(' \"id\" TEXT PRIMARY KEY');\n }\n\n ddl += columns.join(\",\\n\");\n ddl += `\\n);\\n\\n`;\n\n if (options.includePolicies) {\n // Enable RLS and add Policies. No FORCE: authenticated requests\n // run as the non-owner `rebase_user` role, which plain ENABLE\n // already binds. The owner (server context) must bypass — it is\n // the trusted plane (auth flows, dataAsAdmin).\n ddl += `ALTER TABLE \"${schema}\".\"${baseTableName}\" ENABLE ROW LEVEL SECURITY;\\n`;\n ddl += `\\n`;\n\n const securityRules = getEffectiveSecurityRules(collection);\n if (securityRules.length > 0) {\n const resolveCollection: ResolveCollection = (slug) => collections.find(c => c.slug === slug || getTableName(c) === slug);\n securityRules.forEach((rule: SecurityRule) => {\n policyStatements.push(generatePolicyDdl(collection, rule, resolveCollection));\n });\n }\n }\n }\n }\n\n if (fkStatements.length > 0) {\n ddl += \"-- Foreign Key Constraints\\n\";\n ddl += fkStatements.join(\"\\n\") + \"\\n\\n\";\n }\n\n if (policyStatements.length > 0) {\n ddl += \"-- Row Level Security Policies\\n\";\n ddl += policyStatements.join(\"\");\n ddl += \"\\n\";\n }\n\n return ddl;\n};\n\n/** The RLS statements one declared collection's table needs, ready to run. */\nexport interface CollectionPolicyPlan {\n /** The table's schema (e.g. `public`, `rebase`). */\n schema: string;\n /** The bare table name, no schema prefix. */\n table: string;\n /** `schema.table` — matches the keys `readExistingSchema` returns. */\n qualified: string;\n /** `ALTER TABLE … ENABLE ROW LEVEL SECURITY;` — locked by default. */\n enableRls: string;\n /** `DROP POLICY IF EXISTS` / `CREATE POLICY` statements, in order. */\n policyStatements: string[];\n}\n\n/**\n * The per-table RLS plan for the *declared* collections, as executable\n * statements — what the managed runtime applies at boot so a freshly\n * provisioned tenant database serves data instead of 401ing every read.\n *\n * Mirrors the non-junction half of {@link generatePostgresPoliciesDdl} exactly\n * (same `generatePolicyStatements`, same enable-RLS, same effective rules), so\n * boot and `db push` produce identical policies from identical collections.\n *\n * Junction tables are deliberately excluded: they are derived from `through`\n * relations, not declared collections, and the boot-time *table* creator\n * (`ensureCollectionTables`) does not create them either — enabling RLS on a\n * table that boot never created would fail. Their RLS stays a `db push` /\n * `db migrate` concern, which is where those tables get created in the first\n * place. `db push` still applies junction policies via the string generator.\n */\nexport const planCollectionPolicies = (collections: CollectionConfig[]): CollectionPolicyPlan[] => {\n const resolveCollection: ResolveCollection = (slug) => collections.find(c => c.slug === slug || getTableName(c) === slug);\n const plans: CollectionPolicyPlan[] = [];\n const seen = new Set<string>();\n\n for (const collection of collections) {\n const tableName = getTableName(collection);\n if (!tableName) continue;\n const schema = isPostgresCollectionConfig(collection) && collection.schema ? collection.schema : \"public\";\n const baseTableName = tableName.includes(\".\") ? tableName.split(\".\").pop()! : tableName;\n const qualified = `${schema}.${baseTableName}`;\n if (seen.has(qualified)) continue;\n seen.add(qualified);\n\n const policyStatements: string[] = [];\n for (const rule of getEffectiveSecurityRules(collection)) {\n policyStatements.push(...generatePolicyStatements(collection, rule, resolveCollection));\n }\n\n plans.push({\n schema,\n table: baseTableName,\n qualified,\n enableRls: `ALTER TABLE \"${schema}\".\"${baseTableName}\" ENABLE ROW LEVEL SECURITY;`,\n policyStatements\n });\n }\n\n return plans;\n};\n\nexport const generatePostgresPoliciesDdl = (collections: CollectionConfig[]): string => {\n let ddl = \"-- This file contains RLS policies generated by Rebase. Applied separately from migrations.\\n\\n\";\n\n const allTablesToGenerate = new Map<string, {\n collection: CollectionConfig\n }>();\n\n for (const collection of collections) {\n const tableName = getTableName(collection);\n if (tableName) {\n allTablesToGenerate.set(tableName, { collection });\n }\n }\n\n for (const [tableName, { collection }] of allTablesToGenerate.entries()) {\n const schema = isPostgresCollectionConfig(collection) && collection.schema ? collection.schema : \"public\";\n const baseTableName = tableName.includes(\".\") ? tableName.split(\".\").pop()! : tableName;\n\n // No FORCE: user requests run as the non-owner `rebase_user` role\n // (plain ENABLE binds them); the owner is the trusted server context.\n ddl += `ALTER TABLE \"${schema}\".\"${baseTableName}\" ENABLE ROW LEVEL SECURITY;\\n`;\n ddl += `\\n`;\n\n const securityRules = getEffectiveSecurityRules(collection);\n if (securityRules.length > 0) {\n const resolveCollection: ResolveCollection = (slug) => collections.find(c => c.slug === slug || getTableName(c) === slug);\n const injectedNames = new Set(getInjectedSecurityRules(collection).map((rule) => rule.name));\n\n securityRules.forEach((rule: SecurityRule) => {\n // Say which policies the author did not write. They are permissive,\n // so they OR with the declared rules and widen the final ACL beyond\n // what `securityRules` reads like — and re-appear after any manual\n // DROP, because a push asserts the declared state.\n if (rule.name && injectedNames.has(rule.name)) {\n ddl += `-- Injected by Rebase (not from this collection's securityRules).\\n`;\n ddl += `-- Set \\`disableDefaultPolicies: true\\` on \"${collection.slug}\" to drop these and own its RLS outright.\\n`;\n }\n ddl += generatePolicyDdl(collection, rule, resolveCollection);\n });\n ddl += \"\\n\";\n }\n }\n\n // Junction tables are generated from `through` relations, not declared as\n // collections, so the walk above never sees them. They get the same\n // treatment as any generated table: locked by default, with derived\n // policies — reads follow the endpoints, writes follow the declaring\n // side's update rules.\n const junctionSpecs = resolveJunctionSpecs(collections);\n for (const spec of junctionSpecs.values()) {\n ddl += `ALTER TABLE \"${spec.schema}\".\"${spec.table}\" ENABLE ROW LEVEL SECURITY;\\n`;\n ddl += `\\n`;\n\n const junctionRules = getJunctionSecurityRules(spec);\n if (junctionRules.length === 0) continue;\n\n const junctionCollection = getJunctionCollectionConfig(spec);\n const resolveCollection: ResolveCollection = (slug) => collections.find(c => c.slug === slug || getTableName(c) === slug);\n const declaringSlugs = spec.declaringSides.map(s => s.collection.slug).join('\", \"');\n\n ddl += `-- Derived by Rebase for the junction \"${spec.table}\" (no collection declares it).\\n`;\n ddl += `-- Reads require both endpoint rows to be visible; writes follow the update\\n`;\n ddl += `-- rules of \"${declaringSlugs}\". Set \\`disableDefaultPolicies: true\\` on the\\n`;\n ddl += `-- declaring collection(s) to drop these and police the junction yourself.\\n`;\n junctionRules.forEach((rule: SecurityRule) => {\n ddl += generatePolicyDdl(junctionCollection, rule, resolveCollection);\n });\n ddl += \"\\n\";\n }\n\n return ddl;\n};\n\n","/**\n * Bringing a database up to date with a bundle's collections, additively.\n *\n * ## Why this exists\n *\n * A managed runtime boots someone else's compiled project against a database it\n * has never seen. Auth tables are ensured at boot already, but collection tables\n * were not created by anything: the platform ran the app and every `/api/data/*`\n * request answered 500 on a missing relation. `rebase db push` cannot help — it\n * is an Atlas-driven CLI command, and the runtime image ships no CLI.\n *\n * ## Why additive-only, forever\n *\n * This runs unattended, against a database with customers' data in it, with no\n * human reading a diff. So it may only ever do things that cannot lose data:\n * create a missing table, add a missing column, create a missing enum type.\n *\n * It will **never** drop a table or a column, narrow a type, or alter a\n * constraint. A removed field leaves its column behind; a renamed field looks\n * like an addition and the old column stays. That is the correct trade for an\n * automated path — the alternative is an unattended process that can silently\n * destroy a column, which is precisely the failure `db push` was hardened\n * against. Destructive changes stay a deliberate, human-reviewed migration.\n *\n * Because of that, this is safe to run on every boot, and re-running it is a\n * no-op.\n */\nimport { type CollectionConfig, type Property, isPostgresCollectionConfig } from \"@rebasepro/types\";\nimport { getTableName } from \"@rebasepro/common\";\nimport {\n getSqlColumnType,\n resolveColumnName,\n isIdProperty\n} from \"./generate-postgres-ddl-logic\";\n\n/**\n * The subset of a database handle this needs: run a statement, get rows back.\n *\n * Deliberately parameterless. Everything here is DDL or catalogue reads keyed by\n * schema name, and schema names are identifiers — they cannot be bound as\n * parameters anyway. They are validated against {@link SAFE_IDENTIFIER} before\n * they reach a statement, so a config that somehow carried a quote is refused\n * rather than concatenated.\n */\nexport interface Queryable {\n query<T = unknown>(sql: string): Promise<{ rows: T[] }>;\n}\n\n/** Postgres identifiers this module is willing to interpolate. */\nconst SAFE_IDENTIFIER = /^[A-Za-z_][A-Za-z0-9_$]*$/;\n\nfunction assertSafeIdentifier(value: string, what: string): string {\n if (!SAFE_IDENTIFIER.test(value)) {\n throw new Error(`Refusing to build SQL with an unsafe ${what}: ${JSON.stringify(value)}`);\n }\n return value;\n}\n\n/** What the database currently has, as the planner needs it. */\nexport interface ExistingSchema {\n /** `schema.table` → set of column names. */\n tables: Map<string, Set<string>>;\n /** `schema.typename` of every enum type that already exists. */\n enums: Set<string>;\n}\n\nexport interface EnsureAction {\n kind: \"create-enum\" | \"create-table\" | \"add-column\";\n /** Qualified target, for logging: `public.posts` or `public.posts.title`. */\n target: string;\n sql: string;\n}\n\nexport interface EnsurePlan {\n actions: EnsureAction[];\n /** Every statement, in dependency order. Empty when the schema is current. */\n statements: string[];\n}\n\nfunction schemaOf(collection: CollectionConfig): string {\n return isPostgresCollectionConfig(collection) && collection.schema ? collection.schema : \"public\";\n}\n\nfunction qualified(collection: CollectionConfig): string {\n return `${schemaOf(collection)}.${getTableName(collection)}`;\n}\n\n/**\n * Enum types a collection's properties require, as `schema.typename`.\n *\n * Named exactly as the DDL generator names them (`<table>_<column>`), because\n * a column added here has to reference the same type the generator would have\n * created — a second, differently-named type for the same field would be a\n * silent schema fork.\n */\nfunction requiredEnums(collection: CollectionConfig): { name: string; values: string[] }[] {\n const table = getTableName(collection);\n const schema = schemaOf(collection);\n const out: { name: string; values: string[] }[] = [];\n for (const [propName, prop] of Object.entries(collection.properties ?? {})) {\n const p = prop as Property;\n if (!(\"enum\" in p) || !p.enum) continue;\n if (p.type !== \"string\" && p.type !== \"number\") continue;\n const values = (p.enum as unknown[])\n .map(entry =>\n entry && typeof entry === \"object\" && \"id\" in (entry as Record<string, unknown>)\n ? String((entry as Record<string, unknown>).id)\n : String(entry)\n )\n .filter(v => v.length > 0);\n if (values.length === 0) continue;\n out.push({ name: `${schema}.${table}_${resolveColumnName(propName, p)}`, values });\n }\n return out;\n}\n\n/** Single-quote escaping for an enum label. */\nfunction quoteLiteral(value: string): string {\n return `'${value.replace(/'/g, \"''\")}'`;\n}\n\n/**\n * Decide what to add. Pure — the caller supplies what exists and runs the result.\n *\n * Ordering matters and is deliberate: enum types before the tables and columns\n * that reference them, tables before the columns added to other tables (a new\n * table may be the target of a relation), and nothing is emitted twice.\n */\nexport function planCollectionSchemaEnsure(\n collections: CollectionConfig[],\n existing: ExistingSchema\n): EnsurePlan {\n const actions: EnsureAction[] = [];\n const plannedEnums = new Set<string>();\n\n // 1. Enum types. `CREATE TYPE` has no IF NOT EXISTS, so an existing type is\n // skipped by name rather than guarded in SQL.\n for (const collection of collections) {\n for (const { name, values } of requiredEnums(collection)) {\n if (existing.enums.has(name) || plannedEnums.has(name)) continue;\n plannedEnums.add(name);\n const [schema, typeName] = name.split(\".\");\n actions.push({\n kind: \"create-enum\",\n target: name,\n sql: `CREATE TYPE \"${schema}\".\"${typeName}\" AS ENUM (${values.map(quoteLiteral).join(\", \")});`\n });\n }\n }\n\n // 2. Missing tables. Only the identity column is created here; every other\n // column is added by step 3, so a new table and an existing table that\n // gained a field travel the exact same code path. One way to build a\n // column means one way for it to be wrong.\n const created = new Set<string>();\n for (const collection of collections) {\n const key = qualified(collection);\n if (existing.tables.has(key) || created.has(key)) continue;\n created.add(key);\n const schema = schemaOf(collection);\n const table = getTableName(collection);\n const idEntry = Object.entries(collection.properties ?? {}).find(([n, p]) =>\n isIdProperty(n, p as Property, collection)\n );\n const idName = idEntry ? resolveColumnName(idEntry[0], idEntry[1] as Property) : \"id\";\n const idProp = idEntry?.[1] as Property | undefined;\n let idDef: string;\n if (idProp?.type === \"number\") {\n idDef = `\"${idName}\" BIGSERIAL PRIMARY KEY`;\n } else if (\n idProp &&\n idProp.type === \"string\" &&\n (idProp as { isId?: unknown }).isId === \"uuid\"\n ) {\n idDef = `\"${idName}\" UUID PRIMARY KEY DEFAULT gen_random_uuid()`;\n } else {\n idDef = `\"${idName}\" TEXT PRIMARY KEY`;\n }\n actions.push({\n kind: \"create-table\",\n target: key,\n sql: `CREATE TABLE IF NOT EXISTS \"${schema}\".\"${table}\" (${idDef});`\n });\n }\n\n // 3. Missing columns, on both brand-new and pre-existing tables.\n for (const collection of collections) {\n const key = qualified(collection);\n const schema = schemaOf(collection);\n const table = getTableName(collection);\n const present = existing.tables.get(key) ?? new Set<string>();\n for (const [propName, prop] of Object.entries(collection.properties ?? {})) {\n const p = prop as Property;\n if (isIdProperty(propName, p, collection)) continue;\n // A relation's own column is emitted by the DDL generator with a\n // foreign key; adding a bare column here would create the column\n // without the constraint and make the generator's later output\n // disagree with the database. Left to a real migration.\n if (p.type === \"reference\" || p.type === \"relation\") continue;\n const column = resolveColumnName(propName, p);\n if (present.has(column)) continue;\n const type = getSqlColumnType(propName, p, collection, collections);\n actions.push({\n kind: \"add-column\",\n target: `${key}.${column}`,\n // Never NOT NULL: an existing table with rows cannot take a\n // non-null column without a default, and inventing one would be\n // guessing at the customer's data.\n sql: `ALTER TABLE \"${schema}\".\"${table}\" ADD COLUMN IF NOT EXISTS \"${column}\" ${type};`\n });\n }\n }\n\n return { actions, statements: actions.map(a => a.sql) };\n}\n\n/** Read what the database has, for the schemas the collections live in. */\nexport async function readExistingSchema(\n client: Queryable,\n schemas: string[]\n): Promise<ExistingSchema> {\n const tables = new Map<string, Set<string>>();\n const enums = new Set<string>();\n if (schemas.length === 0) return { tables, enums };\n\n const inList = schemas\n .map(schema => `'${assertSafeIdentifier(schema, \"schema name\")}'`)\n .join(\", \");\n\n const { rows: columns } = await client.query<{\n table_schema: string;\n table_name: string;\n column_name: string;\n }>(\n `SELECT table_schema, table_name, column_name\n FROM information_schema.columns\n WHERE table_schema IN (${inList})`\n );\n for (const row of columns) {\n const key = `${row.table_schema}.${row.table_name}`;\n if (!tables.has(key)) tables.set(key, new Set());\n tables.get(key)!.add(row.column_name);\n }\n\n const { rows: enumRows } = await client.query<{ schema: string; name: string }>(\n `SELECT n.nspname AS schema, t.typname AS name\n FROM pg_type t\n JOIN pg_namespace n ON t.typnamespace = n.oid\n WHERE t.typtype = 'e' AND n.nspname IN (${inList})`\n );\n for (const row of enumRows) enums.add(`${row.schema}.${row.name}`);\n\n return { tables, enums };\n}\n\n/**\n * Bring the database up to date. Returns what it did.\n *\n * Each statement runs on its own rather than in one transaction: they are all\n * independently safe and idempotent, and a single failure (an enum label that\n * cannot be added, say) should not roll back the tables that were created fine.\n * The error is surfaced with the statement that caused it.\n */\nexport async function ensureCollectionTables(\n client: Queryable,\n collections: CollectionConfig[],\n log?: (message: string) => void\n): Promise<EnsurePlan> {\n const schemas = Array.from(new Set(collections.map(schemaOf)));\n for (const schema of schemas) {\n assertSafeIdentifier(schema, \"schema name\");\n if (schema !== \"public\") {\n await client.query(`CREATE SCHEMA IF NOT EXISTS \"${schema}\";`);\n }\n }\n\n const existing = await readExistingSchema(client, schemas);\n const plan = planCollectionSchemaEnsure(collections, existing);\n\n if (plan.actions.length === 0) {\n log?.(\"Schema is up to date; nothing to create.\");\n return plan;\n }\n\n for (const action of plan.actions) {\n try {\n await client.query(action.sql);\n log?.(`${action.kind}: ${action.target}`);\n } catch (err) {\n throw new Error(\n `Failed to ${action.kind} ${action.target}: ` +\n `${err instanceof Error ? err.message : String(err)}\\n ${action.sql}`\n );\n }\n }\n return plan;\n}\n"],"mappings":"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"}
package/dist/index.es.js CHANGED
@@ -11931,13 +11931,11 @@ function createPostgresBootstrapper(pgConfig) {
11931
11931
  */
11932
11932
  async ensureCollectionSchema(collections, driverResult, log) {
11933
11933
  const internals = driverResult.internals;
11934
- const { ensureCollectionTables } = await import("./ensure-collection-tables-DsDsNl6o.js");
11935
- const plan = await ensureCollectionTables({ async query(text) {
11934
+ const { ensureCollectionTables } = await import("./ensure-collection-tables-Vu-GRELM.js");
11935
+ return { applied: (await ensureCollectionTables({ async query(text) {
11936
11936
  const result = await internals.db.execute(sql.raw(text));
11937
11937
  return { rows: result.rows ?? (Array.isArray(result) ? result : []) };
11938
- } }, collections, log);
11939
- for (const failure of plan.failures) logger.warn(`🔗 [schema] Could not add foreign key "${failure.target}" — the column exists and the collection still serves, but rows are not policed by this constraint: ${failure.error}`);
11940
- return { applied: plan.actions.length - plan.failures.length };
11938
+ } }, collections, log)).actions.length };
11941
11939
  },
11942
11940
  /**
11943
11941
  * Apply the collections' RLS policies — ENABLE ROW LEVEL SECURITY and the
@@ -11956,7 +11954,7 @@ function createPostgresBootstrapper(pgConfig) {
11956
11954
  */
11957
11955
  async ensureCollectionPolicies(collections, driverResult, log) {
11958
11956
  const internals = driverResult.internals;
11959
- const { ensureCollectionPolicies } = await import("./ensure-collection-policies-Vl3Cv1Q9.js");
11957
+ const { ensureCollectionPolicies } = await import("./ensure-collection-policies-CT-zIUWA.js");
11960
11958
  const outcome = await ensureCollectionPolicies({ async query(text) {
11961
11959
  const result = await internals.db.execute(sql.raw(text));
11962
11960
  return { rows: result.rows ?? (Array.isArray(result) ? result : []) };