@rebasepro/server-postgres 0.16.0 → 0.16.1-canary.g2d1aec8

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 (63) hide show
  1. package/dist/PostgresAdapter.d.ts +1 -1
  2. package/dist/PostgresBackendDriver.d.ts +16 -7
  3. package/dist/PostgresBootstrapper.d.ts +6 -6
  4. package/dist/auth/services.d.ts +1 -1
  5. package/dist/backup/backup-cron.d.ts +1 -1
  6. package/dist/backup/backup-service.d.ts +2 -2
  7. package/dist/backup/index.d.ts +4 -4
  8. package/dist/collections/PostgresCollectionRegistry.d.ts +1 -1
  9. package/dist/collections/buildRegistry.d.ts +1 -1
  10. package/dist/collections/validate-relations.d.ts +1 -1
  11. package/dist/data-transformer.d.ts +1 -1
  12. package/dist/{ensure-collection-policies-BVFb2olB.js → ensure-collection-policies-CVfVHS6o.js} +3 -3
  13. package/dist/{ensure-collection-policies-BVFb2olB.js.map → ensure-collection-policies-CVfVHS6o.js.map} +1 -1
  14. package/dist/{auth-users-columns-CgyPWQ18.js → ensure-collection-tables-jYsvOLZF.js} +1443 -11
  15. package/dist/ensure-collection-tables-jYsvOLZF.js.map +1 -0
  16. package/dist/index.d.ts +16 -16
  17. package/dist/index.es.js +2169 -1803
  18. package/dist/index.es.js.map +1 -1
  19. package/dist/{rls-enforcement-Ch0T6OwW.js → rls-enforcement-CsD7nZDn.js} +2 -2
  20. package/dist/{rls-enforcement-Ch0T6OwW.js.map → rls-enforcement-CsD7nZDn.js.map} +1 -1
  21. package/dist/schema/classify-change.d.ts +82 -0
  22. package/dist/schema/dynamic-tables.d.ts +1 -1
  23. package/dist/schema/ensure-collection-policies.d.ts +1 -1
  24. package/dist/schema/ensure-collection-tables.d.ts +93 -2
  25. package/dist/schema/generate-schema-commit.d.ts +136 -0
  26. package/dist/schema/introspect-db-constraints.d.ts +1 -1
  27. package/dist/schema/introspect-db-logic.d.ts +3 -3
  28. package/dist/schema/introspect-db-project.d.ts +1 -1
  29. package/dist/schema/introspect-db-queries.d.ts +1 -1
  30. package/dist/schema/introspect-db-structure.d.ts +2 -2
  31. package/dist/schema/introspect-runtime.d.ts +1 -1
  32. package/dist/schema/vector-index.d.ts +88 -0
  33. package/dist/services/BranchService.d.ts +2 -2
  34. package/dist/services/FetchService.d.ts +4 -4
  35. package/dist/services/PersistService.d.ts +5 -5
  36. package/dist/services/RelationService.d.ts +3 -3
  37. package/dist/services/RelationWriteService.d.ts +3 -3
  38. package/dist/services/cdc/junction-tables.d.ts +1 -1
  39. package/dist/services/cdc/trigger-cdc.d.ts +1 -1
  40. package/dist/services/channel-bus/PostgresChannelBus.d.ts +1 -1
  41. package/dist/services/channel-bus/index.d.ts +2 -2
  42. package/dist/services/collection-helpers.d.ts +1 -1
  43. package/dist/services/dataService.d.ts +10 -10
  44. package/dist/services/index.d.ts +4 -4
  45. package/dist/services/junction-writes.d.ts +2 -2
  46. package/dist/services/nested-path.d.ts +1 -1
  47. package/dist/services/realtimeService.d.ts +3 -3
  48. package/dist/services/row-pipeline.d.ts +1 -1
  49. package/dist/services/write-denial.d.ts +1 -1
  50. package/dist/utils/drizzle-conditions.d.ts +2 -2
  51. package/dist/websocket-BVgDVO-V.js.map +1 -1
  52. package/dist/websocket.d.ts +2 -2
  53. package/package.json +7 -7
  54. package/src/PostgresBackendDriver.ts +40 -0
  55. package/src/schema/classify-change.ts +436 -0
  56. package/src/schema/ensure-collection-tables.test.ts +168 -1
  57. package/src/schema/ensure-collection-tables.ts +344 -14
  58. package/src/schema/generate-postgres-ddl-logic.ts +15 -0
  59. package/src/schema/generate-schema-commit.ts +242 -0
  60. package/src/schema/vector-index.ts +278 -0
  61. package/dist/auth-users-columns-CgyPWQ18.js.map +0 -1
  62. package/dist/ensure-collection-tables-BY1pHRD_.js +0 -840
  63. package/dist/ensure-collection-tables-BY1pHRD_.js.map +0 -1
@@ -1,9 +1,10 @@
1
1
  import { createRequire as __createRequire } from "module";
2
2
  import "process";
3
3
  __createRequire(import.meta.url);
4
- import { l as __require, s as __commonJSMin } from "./connection-BuZ97wsr.js";
4
+ import { c as __exportAll, l as __require, s as __commonJSMin } from "./connection-BuZ97wsr.js";
5
5
  import { a as policy, i as ANONYMOUS_USER_IDS, r as ANONYMOUS_USER_ID } from "./data_driver-ULAyJEi9.js";
6
- import { _ as isRelationAggregateSort, a as rewriteLegacyRlsFunctions, b as toCanonicalOp, c as isPostgresCollectionConfig, d as getDataSourceCapabilities, f as ALL_WHERE_FILTER_OPS, h as REST_TO_CANONICAL, i as RLS_UID_SQL, l as isRelationalCollectionConfig, m as NULL_OPS, p as CANONICAL_TO_REST, r as RLS_ROLES_SQL, s as getDeclaredSubcollections, y as sortKeyToString } from "./src-BBFsDaeA.js";
6
+ import { _ as isRelationAggregateSort, a as rewriteLegacyRlsFunctions, b as toCanonicalOp, c as isPostgresCollectionConfig, d as getDataSourceCapabilities, f as ALL_WHERE_FILTER_OPS, h as REST_TO_CANONICAL, i as RLS_UID_SQL, l as isRelationalCollectionConfig, m as NULL_OPS, n as REBASE_SCHEMA, p as CANONICAL_TO_REST, r as RLS_ROLES_SQL, s as getDeclaredSubcollections, y as sortKeyToString } from "./src-BBFsDaeA.js";
7
+ import { isConcurrentDdlRace, isDuplicateObjectRace, logger } from "@rebasepro/server";
7
8
  import { createHash } from "node:crypto";
8
9
  //#region ../types/src/types/entities.ts
9
10
  /**
@@ -2482,7 +2483,7 @@ function compile(expr, scope) {
2482
2483
  case "authenticated": return `${RLS_UID_SQL} IS NOT NULL AND ${RLS_UID_SQL} NOT IN (${ANONYMOUS_USER_IDS.map(quoteLiteral).join(", ")})`;
2483
2484
  case "serverContext": return `${RLS_UID_SQL} IS NULL`;
2484
2485
  case "existsIn": return compileExistsIn(expr, scope);
2485
- case "raw": return rewriteLegacyRlsFunctions(expr.sql).replace(/\{(\w+)\}/g, (_, col) => `${outerQualifier(scope)}${resolveColumnName(col, scope.outerCollection)}`);
2486
+ case "raw": return rewriteLegacyRlsFunctions(expr.sql).replace(/\{(\w+)\}/g, (_, col) => `${outerQualifier(scope)}${resolveColumnName$1(col, scope.outerCollection)}`);
2486
2487
  }
2487
2488
  }
2488
2489
  /**
@@ -2493,7 +2494,7 @@ function compile(expr, scope) {
2493
2494
  function compileExistsIn(expr, scope) {
2494
2495
  const join = scope.resolveCollection?.(expr.collection);
2495
2496
  const joinTable = join ? getTableName(join) : toSnakeCase(expr.collection);
2496
- const joinSchema = schemaOf(join) ?? schemaOf(scope.outerCollection) ?? "public";
2497
+ const joinSchema = schemaOf$1(join) ?? schemaOf$1(scope.outerCollection) ?? "public";
2497
2498
  const alias = `_ex${scope.alias.n++}`;
2498
2499
  const outerPrefix = outerQualifier(scope);
2499
2500
  const innerScope = {
@@ -2516,8 +2517,8 @@ var COMPARE_SQL = {
2516
2517
  };
2517
2518
  function operandToSql(operand, scope) {
2518
2519
  switch (operand.kind) {
2519
- case "field": return `${scope.fieldPrefix}${resolveColumnName(operand.name, scope.fieldCollection)}`;
2520
- case "outerField": return `${scope.outerPrefix}${resolveColumnName(operand.name, scope.outerCollection)}`;
2520
+ case "field": return `${scope.fieldPrefix}${resolveColumnName$1(operand.name, scope.fieldCollection)}`;
2521
+ case "outerField": return `${scope.outerPrefix}${resolveColumnName$1(operand.name, scope.outerCollection)}`;
2521
2522
  case "literal": return quoteLiteral(operand.value);
2522
2523
  case "authUid": return RLS_UID_SQL;
2523
2524
  case "authRoles": return `string_to_array(${RLS_ROLES_SQL}, ',')`;
@@ -2530,12 +2531,12 @@ function operandToSql(operand, scope) {
2530
2531
  function outerQualifier(scope) {
2531
2532
  const table = scope.outerCollection ? getTableName(scope.outerCollection) : void 0;
2532
2533
  if (!table) return "";
2533
- return `"${schemaOf(scope.outerCollection) ?? "public"}"."${table}".`;
2534
+ return `"${schemaOf$1(scope.outerCollection) ?? "public"}"."${table}".`;
2534
2535
  }
2535
- function schemaOf(collection) {
2536
+ function schemaOf$1(collection) {
2536
2537
  return collection?.schema || void 0;
2537
2538
  }
2538
- function resolveColumnName(propName, collection) {
2539
+ function resolveColumnName$1(propName, collection) {
2539
2540
  const prop = collection?.properties?.[propName];
2540
2541
  if (prop && "columnName" in prop && typeof prop.columnName === "string") return quoteColumnIdentifier(prop.columnName);
2541
2542
  return quoteColumnIdentifier(toSnakeCase(propName));
@@ -2805,6 +2806,22 @@ function getEffectiveSecurityRules(collection) {
2805
2806
  }
2806
2807
  return [...explicit, ...injected];
2807
2808
  }
2809
+ /**
2810
+ * The framework defaults that {@link getEffectiveSecurityRules} would add to a
2811
+ * collection, without the author's own rules.
2812
+ *
2813
+ * These policies appear in the database under names the author never wrote, and
2814
+ * a permissive policy ORs with every other permissive policy — so someone
2815
+ * reading their `securityRules` and then the real ACL sees more access than they
2816
+ * declared. Dropping them by hand does nothing either: `db push` is declarative,
2817
+ * so the next push asserts them again. Callers use this to say, in the generated
2818
+ * DDL, which policies are injected and how to take them off.
2819
+ */
2820
+ function getInjectedSecurityRules(collection) {
2821
+ if (isPostgresCollectionConfig(collection) && collection.disableDefaultPolicies) return isAuthCollection$1(collection) ? [adminWriteGate(getTableName(collection))] : [];
2822
+ const explicitCount = (collection.securityRules ?? []).length;
2823
+ return getEffectiveSecurityRules(collection).slice(explicitCount);
2824
+ }
2808
2825
  //#endregion
2809
2826
  //#region ../common/src/util/junction-policies.ts
2810
2827
  var SERVER_OR_ADMIN_EXPR = policy.or(policy.serverContext(), policy.rolesOverlap(["admin"]));
@@ -5469,6 +5486,10 @@ var searchHelperFunctions = (spec) => {
5469
5486
  * with "text search dictionary does not exist". Observed, not theorised.
5470
5487
  */
5471
5488
  var searchExtensionStatements = (spec) => spec.extensions.map((e) => `CREATE EXTENSION IF NOT EXISTS ${e} WITH SCHEMA ${HELPER_SCHEMA};`);
5489
+ /** The column definition as it appears inside `CREATE TABLE`. */
5490
+ var searchColumnDefinition = (spec) => `"${spec.column}" tsvector GENERATED ALWAYS AS (${spec.expression}) STORED`;
5491
+ /** The fuzzy column definition, when the spec asks for one. */
5492
+ var fuzzyColumnDefinition = (spec) => spec.fuzzy ? `"${spec.fuzzy.column}" text GENERATED ALWAYS AS (${spec.fuzzy.expression}) STORED` : void 0;
5472
5493
  /**
5473
5494
  * Index statements for the spec.
5474
5495
  *
@@ -5526,6 +5547,30 @@ var searchColumnStamps = (spec) => {
5526
5547
  return stamps;
5527
5548
  };
5528
5549
  /**
5550
+ * The same drift check as the boot ensure, for the SQL file.
5551
+ *
5552
+ * Needed because {@link searchColumnStamps} would otherwise *launder* drift on
5553
+ * the migration path: `ADD COLUMN IF NOT EXISTS` does nothing to a column that
5554
+ * exists, so a re-generated `search.sql` would stamp a stale column with the
5555
+ * new block's fingerprint and the next boot would find them in agreement.
5556
+ * Guarding first means the file refuses instead — `rebase db push` is attended,
5557
+ * and the operator reading the failure is the person who changed the block.
5558
+ */
5559
+ var searchStampGuards = (spec) => searchColumnStamps(spec).map((stamp) => {
5560
+ const relation = quote(`"${spec.schema}"."${spec.table}"`);
5561
+ return `DO $rebase_search$
5562
+ DECLARE recorded text;
5563
+ BEGIN
5564
+ SELECT col_description(a.attrelid, a.attnum) INTO recorded
5565
+ FROM pg_attribute a
5566
+ WHERE a.attrelid = ${relation}::regclass AND a.attname = ${quote(stamp.column)} AND NOT a.attisdropped;
5567
+ IF recorded LIKE ${quote(`${SEARCH_STAMP_PREFIX}%`)} AND recorded <> ${quote(stamp.fingerprint)} THEN
5568
+ RAISE EXCEPTION 'Rebase: the search block for ${spec.schema}.${spec.table} changed after the generated column "${stamp.column}" was built (recorded %, expected ${stamp.fingerprint}). Postgres cannot alter a generated expression in place. Drop the column and re-apply this file — it rewrites the table and rebuilds the index: ALTER TABLE ${relation.slice(1, -1)} DROP COLUMN "${stamp.column}";', recorded;
5569
+ END IF;
5570
+ END
5571
+ $rebase_search$;`;
5572
+ });
5573
+ /**
5529
5574
  * The generated column names a collection's search block adds, if any.
5530
5575
  *
5531
5576
  * These are physical columns on the table, so `SELECT *` returns them. They are
@@ -5702,6 +5747,1393 @@ function isAuthCollection(collection) {
5702
5747
  return typeof auth === "object" && auth !== null && auth.enabled === true;
5703
5748
  }
5704
5749
  //#endregion
5705
- export { mergeDeep as $, fieldKeyForColumn as A, parseIdValues as B, getJunctionCollectionConfig as C, policyToPostgres as D, getEffectiveSecurityRules as E, getTableVarName as F, updateDateAutoValues as G, createRelationRef as H, resolveCollectionRelations as I, legacyForeignKeyName as J, firstFreeKey as K, buildCompositeId as L, getColumnName as M, getEnumVarName as N, securityRuleToConditions as O, getTableName as P, isPrototypePollutingKey as Q, getDeclaredPrimaryKeys as R, resolveStringColumnLength as S, resolveJunctionSpecs as T, createRelationRefWithData as U, sortCollectionsBySlug as V, normalizeToEntityRelation as W, toWireKey as X, toPostgresIdentifier as Y, getPolicyNamesForRule as Z, OrderBySpecError as _, SEARCH_STAMP_PREFIX as a, isManyToMany as at, CollectionRegistry as b, assertSearchIsPostgresOnly as c, searchColumnStamps as d, camelCase as et, searchExtensionStatements as f, buildSdkData as g, visibleColumnProjection as h, isAuthCollection as i, hasForeignKeyOnTarget as it, findRelation as j, findAnonymousGrants as k, buildSearchColumnSpec as l, searchIndexStatements as m, authUsersColumnDefinition as n, DEFAULT_ONE_OF_TYPE as nt, SEARCH_TEXT_FN as o, Vector as ot, searchHelperFunctions as p, generateForeignKeyName as q, authUsersColumnSql as r, DEFAULT_ONE_OF_VALUE as rt, SEARCH_UNACCENT_FN as s, AUTH_USERS_COLUMNS as t, toSnakeCase as tt, hiddenColumnsOption as u, normalizeDriverOrderBy as v, getJunctionSecurityRules as w, relationalCollections as x, parseOrderBySpecStrict as y, isAddressableId as z };
5750
+ //#region src/schema/vector-index.ts
5751
+ /**
5752
+ * The widest `vector` pgvector will build an HNSW or IVFFlat index over.
5753
+ * Storage and exact search are unaffected by this limit.
5754
+ */
5755
+ var MAX_INDEXABLE_VECTOR_DIMENSIONS = 2e3;
5756
+ /**
5757
+ * Operator class per distance. These strings are part of the database contract:
5758
+ * they appear in `CREATE INDEX`, so renaming one renames an index.
5759
+ */
5760
+ var OPERATOR_CLASS = {
5761
+ cosine: "vector_cosine_ops",
5762
+ l2: "vector_l2_ops",
5763
+ inner_product: "vector_ip_ops"
5764
+ };
5765
+ /** Short, stable tag per distance, used to name the index. */
5766
+ var DISTANCE_TAG = {
5767
+ cosine: "cosine",
5768
+ l2: "l2",
5769
+ inner_product: "ip"
5770
+ };
5771
+ var VectorIndexConfigError = class extends Error {
5772
+ constructor(message) {
5773
+ super(message);
5774
+ this.name = "VectorIndexConfigError";
5775
+ }
5776
+ };
5777
+ var isVectorProperty = (prop) => !!prop && typeof prop === "object" && prop.type === "vector";
5778
+ var asDistances = (config, label) => {
5779
+ const raw = config.distance ?? "cosine";
5780
+ const list = Array.isArray(raw) ? raw : [raw];
5781
+ if (list.length === 0) throw new VectorIndexConfigError(`${label}: \`index.distance\` is an empty array. Name at least one distance, or set \`index: false\` to create no index.`);
5782
+ const seen = /* @__PURE__ */ new Set();
5783
+ for (const distance of list) {
5784
+ if (!(distance in OPERATOR_CLASS)) throw new VectorIndexConfigError(`${label}: \`index.distance\` is "${distance}", which is not a pgvector distance. Use ${Object.keys(OPERATOR_CLASS).map((d) => `"${d}"`).join(", ")}.`);
5785
+ if (seen.has(distance)) throw new VectorIndexConfigError(`${label}: \`index.distance\` lists "${distance}" twice.`);
5786
+ seen.add(distance);
5787
+ }
5788
+ return list;
5789
+ };
5790
+ var assertPositiveInteger = (value, key, label) => {
5791
+ if (value === void 0) return;
5792
+ if (!Number.isInteger(value) || value <= 0) throw new VectorIndexConfigError(`${label}: \`index.${key}\` is ${JSON.stringify(value)}. It must be a positive integer.`);
5793
+ };
5794
+ /**
5795
+ * Index parameters for one method. Parameters belonging to the *other* method
5796
+ * are rejected rather than ignored, because a silently dropped `lists` on an
5797
+ * HNSW index reads, from the config, exactly like a tuned index.
5798
+ */
5799
+ var parametersFor = (method, config, label) => {
5800
+ assertPositiveInteger(config.m, "m", label);
5801
+ assertPositiveInteger(config.efConstruction, "efConstruction", label);
5802
+ assertPositiveInteger(config.lists, "lists", label);
5803
+ if (method === "hnsw") {
5804
+ if (config.lists !== void 0) throw new VectorIndexConfigError(`${label}: \`index.lists\` only applies to \`method: "ivfflat"\`. Remove it, or switch the method.`);
5805
+ const params = [];
5806
+ if (config.m !== void 0) params.push(["m", config.m]);
5807
+ if (config.efConstruction !== void 0) params.push(["ef_construction", config.efConstruction]);
5808
+ return params;
5809
+ }
5810
+ for (const key of ["m", "efConstruction"]) if (config[key] !== void 0) throw new VectorIndexConfigError(`${label}: \`index.${key}\` only applies to \`method: "hnsw"\`. Remove it, or switch the method.`);
5811
+ return config.lists !== void 0 ? [["lists", config.lists]] : [];
5812
+ };
5813
+ /**
5814
+ * Every ANN index a collection's vector properties call for.
5815
+ *
5816
+ * `resolveColumn` is passed in rather than imported so that this module stays
5817
+ * free of the DDL generator, which imports *it*. Both callers hand it the same
5818
+ * `resolveColumnName`, and a contract test asserts the names agree.
5819
+ */
5820
+ var buildVectorIndexPlan = (collection, resolveColumn) => {
5821
+ const specs = [];
5822
+ const skipped = [];
5823
+ const properties = collection.properties ?? {};
5824
+ const table = getTableName(collection);
5825
+ const schema = isPostgresCollectionConfig(collection) && collection.schema ? collection.schema : "public";
5826
+ for (const [propName, prop] of Object.entries(properties)) {
5827
+ if (!isVectorProperty(prop)) continue;
5828
+ if (prop.index === false) continue;
5829
+ const label = `${collection.slug}.${propName}`;
5830
+ const column = resolveColumn(propName, prop);
5831
+ const config = prop.index ?? {};
5832
+ const method = config.method ?? "hnsw";
5833
+ if (method !== "hnsw" && method !== "ivfflat") throw new VectorIndexConfigError(`${label}: \`index.method\` is "${method}". Use "hnsw" or "ivfflat".`);
5834
+ const distances = asDistances(config, label);
5835
+ const parameters = parametersFor(method, config, label);
5836
+ if (!Number.isInteger(prop.dimensions) || prop.dimensions <= 0) throw new VectorIndexConfigError(`${label}: \`dimensions\` is ${JSON.stringify(prop.dimensions)}. It must be a positive integer.`);
5837
+ if (prop.dimensions > 2e3) {
5838
+ skipped.push({
5839
+ schema,
5840
+ table,
5841
+ column,
5842
+ dimensions: prop.dimensions,
5843
+ reason: `pgvector cannot index a vector wider than ${MAX_INDEXABLE_VECTOR_DIMENSIONS} dimensions, and ${label} declares ${prop.dimensions}. The column works and \`vectorSearch\` still answers, as an exact scan. To index it, reduce the dimensions (many embedding models support a shorter output) or set \`index: false\` to state that the scan is intended.`
5844
+ });
5845
+ continue;
5846
+ }
5847
+ for (const distance of distances) specs.push({
5848
+ schema,
5849
+ table,
5850
+ column,
5851
+ indexName: toPostgresIdentifier(`${table}_${column}_${method}_${DISTANCE_TAG[distance]}`),
5852
+ method,
5853
+ distance,
5854
+ operatorClass: OPERATOR_CLASS[distance],
5855
+ parameters
5856
+ });
5857
+ }
5858
+ return {
5859
+ specs,
5860
+ skipped
5861
+ };
5862
+ };
5863
+ /**
5864
+ * The `CREATE INDEX` for one spec.
5865
+ *
5866
+ * `CONCURRENTLY` is deliberately absent, for the same reason it is absent from
5867
+ * `searchIndexStatements`: this form is replayed as part of a migration, where
5868
+ * a concurrent build is not allowed. The boot-time ensure rewrites it — see
5869
+ * `ensureCollectionTables`.
5870
+ */
5871
+ var vectorIndexStatement = (spec) => {
5872
+ const params = spec.parameters.length ? ` WITH (${spec.parameters.map(([key, value]) => `${key} = ${value}`).join(", ")})` : "";
5873
+ return `CREATE INDEX IF NOT EXISTS "${spec.indexName}" ON "${spec.schema}"."${spec.table}" USING ${spec.method} ("${spec.column}" ${spec.operatorClass})${params};`;
5874
+ };
5875
+ /** Every statement for a plan, in a stable order. */
5876
+ var vectorIndexStatements = (plan) => plan.specs.map(vectorIndexStatement);
5877
+ //#endregion
5878
+ //#region src/schema/generate-postgres-ddl-logic.ts
5879
+ var resolveColumnName = (propName, prop) => {
5880
+ if (prop && "columnName" in prop && typeof prop.columnName === "string") return prop.columnName;
5881
+ return toSnakeCase(propName);
5882
+ };
5883
+ var getPrimaryKeyProp = (collection) => {
5884
+ if (collection.properties) {
5885
+ const idPropEntry = Object.entries(collection.properties).find(([_, prop]) => "isId" in prop && Boolean(prop.isId));
5886
+ if (idPropEntry) {
5887
+ const prop = idPropEntry[1];
5888
+ const isUuid = prop.type === "string" && "isId" in prop && prop.isId === "uuid";
5889
+ return {
5890
+ name: idPropEntry[0],
5891
+ type: prop.type === "number" ? "number" : "string",
5892
+ isUuid
5893
+ };
5894
+ }
5895
+ }
5896
+ const idProp = collection.properties?.["id"];
5897
+ if (idProp?.type === "number") return {
5898
+ name: "id",
5899
+ type: "number",
5900
+ isUuid: false
5901
+ };
5902
+ return {
5903
+ name: "id",
5904
+ type: "string",
5905
+ isUuid: idProp?.type === "string" && "isId" in idProp && idProp.isId === "uuid"
5906
+ };
5907
+ };
5908
+ var isNumericId = (collection) => {
5909
+ return getPrimaryKeyProp(collection).type === "number";
5910
+ };
5911
+ var getPrimaryKeyName = (collection) => {
5912
+ return getPrimaryKeyProp(collection).name;
5913
+ };
5914
+ /** The column type a junction holds for one endpoint's primary key. */
5915
+ var junctionKeyType = (collection) => isNumericId(collection) ? "INTEGER" : getPrimaryKeyProp(collection).isUuid ? "UUID" : "TEXT";
5916
+ var isIdProperty = (propName, prop, collection) => {
5917
+ if ("isId" in prop && Boolean(prop.isId)) return true;
5918
+ return !Object.values(collection.properties ?? {}).some((p) => "isId" in p && Boolean(p.isId)) && propName === "id";
5919
+ };
5920
+ /**
5921
+ * Render statements produced by {@link generatePolicyStatements} back into the
5922
+ * exact string the DDL/policies files have always carried: each statement on
5923
+ * its own line, terminated by a newline. Keeping the string form derived from
5924
+ * the statement array means the two can never drift — the boot-time applier and
5925
+ * the generated `policies.sql` emit the same SQL, from the same source.
5926
+ */
5927
+ var statementsToDdl = (statements) => statements.map((s) => `${s}\n`).join("");
5928
+ var generatePolicyDdl = (collection, rule, resolveCollection) => statementsToDdl(generatePolicyStatements(collection, rule, resolveCollection));
5929
+ /**
5930
+ * The individual SQL statements a single security rule compiles to: a
5931
+ * `DROP POLICY IF EXISTS` / `CREATE POLICY` pair per operation, each a complete
5932
+ * statement (terminated by `;`, no trailing newline).
5933
+ *
5934
+ * This is the primitive the boot-time RLS applier runs one statement at a time
5935
+ * (the runtime's DB handle speaks the extended query protocol, which forbids
5936
+ * multiple commands in one execute), while `db push` writes the joined string.
5937
+ */
5938
+ var generatePolicyStatements = (collection, rule, resolveCollection) => {
5939
+ const tableName = getTableName(collection);
5940
+ const ops = rule.operations && rule.operations.length > 0 ? rule.operations : [rule.operation ?? "all"];
5941
+ const policyNames = getPolicyNamesForRule(rule, tableName);
5942
+ return ops.flatMap((op, opIdx) => {
5943
+ return generateSinglePolicyStatements(collection, rule, op, policyNames[opIdx], resolveCollection);
5944
+ });
5945
+ };
5946
+ var generateSinglePolicyStatements = (collection, rule, operation, policyName, resolveCollection) => {
5947
+ const schema = isPostgresCollectionConfig(collection) && collection.schema ? collection.schema : "public";
5948
+ const tableName = getTableName(collection);
5949
+ const mode = (rule.mode ?? "permissive").toUpperCase();
5950
+ const operationUpper = operation.toUpperCase();
5951
+ const pgRoles = rule.pgRoles ? [...rule.pgRoles].sort() : ["public"];
5952
+ const needsUsing = operation !== "insert";
5953
+ const needsWithCheck = operation !== "select" && operation !== "delete";
5954
+ const { usingExpr, withCheckExpr } = securityRuleToConditions(rule);
5955
+ let usingClause = needsUsing && usingExpr ? policyToPostgres(usingExpr, collection, { resolveCollection }) : null;
5956
+ let withCheckClause = needsWithCheck && withCheckExpr ? policyToPostgres(withCheckExpr, collection, { resolveCollection }) : null;
5957
+ if (!usingClause && needsUsing) usingClause = "false";
5958
+ if (!withCheckClause && needsWithCheck) withCheckClause = "false";
5959
+ const drop = `DROP POLICY IF EXISTS "${policyName}" ON "${schema}"."${tableName}";`;
5960
+ let create = `CREATE POLICY "${policyName}" ON "${schema}"."${tableName}" AS ${mode} FOR ${operationUpper} TO ${pgRoles.map((r) => `"${r}"`).join(", ")}`;
5961
+ if (usingClause) create += ` USING (${usingClause})`;
5962
+ if (withCheckClause) create += ` WITH CHECK (${withCheckClause})`;
5963
+ create += ";";
5964
+ return [drop, create];
5965
+ };
5966
+ /**
5967
+ * Single-quote escaping for a SQL string literal (PostgreSQL doubles the
5968
+ * quote). Enum labels come straight from user-authored collection config, so a
5969
+ * label like `it's` closes the literal early and the whole generated file stops
5970
+ * parsing at the `CREATE TYPE`. Lives here rather than next to its other caller
5971
+ * because ensure-collection-tables already imports from this module — the
5972
+ * reverse would be a cycle.
5973
+ */
5974
+ var quoteSqlLiteral = (value) => `'${value.replace(/'/g, "''")}'`;
5975
+ var getSqlColumnType = (propName, prop, collection, collections) => {
5976
+ switch (prop.type) {
5977
+ case "string": {
5978
+ const stringProp = prop;
5979
+ if (stringProp.enum) {
5980
+ const tableName = getTableName(collection);
5981
+ const colName = resolveColumnName(propName, prop);
5982
+ return `"${isPostgresCollectionConfig(collection) && collection.schema ? collection.schema : "public"}"."${tableName}_${colName}"`;
5983
+ }
5984
+ if (stringProp.isId === "uuid" || stringProp.columnType === "uuid") return "UUID";
5985
+ if (stringProp.columnType === "char") return `CHAR(${resolveStringColumnLength(stringProp)})`;
5986
+ if (stringProp.columnType === "varchar") return `VARCHAR(${resolveStringColumnLength(stringProp)})`;
5987
+ return "TEXT";
5988
+ }
5989
+ case "number": {
5990
+ const numProp = prop;
5991
+ const isId = isIdProperty(propName, prop, collection);
5992
+ if ("isId" in numProp && numProp.isId === "increment") return "INTEGER GENERATED BY DEFAULT AS IDENTITY";
5993
+ if (numProp.columnType) {
5994
+ if (numProp.columnType === "double precision") return "DOUBLE PRECISION";
5995
+ return numProp.columnType.toUpperCase();
5996
+ }
5997
+ return numProp.validation?.integer || isId ? "INTEGER" : "NUMERIC";
5998
+ }
5999
+ case "boolean": return "BOOLEAN";
6000
+ case "date": {
6001
+ const dateProp = prop;
6002
+ if (dateProp.columnType === "date") return "DATE";
6003
+ if (dateProp.columnType === "time") return "TIME";
6004
+ return "TIMESTAMP WITH TIME ZONE";
6005
+ }
6006
+ case "map": return prop.columnType === "json" ? "JSON" : "JSONB";
6007
+ case "geopoint": return "JSONB";
6008
+ case "array": {
6009
+ const arrayProp = prop;
6010
+ let colType = arrayProp.columnType;
6011
+ if (!colType && arrayProp.of && !Array.isArray(arrayProp.of)) {
6012
+ const ofProp = arrayProp.of;
6013
+ if (ofProp.type === "string") colType = "text[]";
6014
+ else if (ofProp.type === "number") colType = ofProp.validation?.integer ? "integer[]" : "numeric[]";
6015
+ else if (ofProp.type === "boolean") colType = "boolean[]";
6016
+ }
6017
+ if (colType === "json") return "JSON";
6018
+ if (colType === "text[]") return "TEXT[]";
6019
+ if (colType === "integer[]") return "INTEGER[]";
6020
+ if (colType === "boolean[]") return "BOOLEAN[]";
6021
+ if (colType === "numeric[]") return "NUMERIC[]";
6022
+ return "JSONB";
6023
+ }
6024
+ case "vector": return `VECTOR(${prop.dimensions})`;
6025
+ case "binary": return "BYTEA";
6026
+ case "relation": {
6027
+ const refProp = prop;
6028
+ const relation = findRelation(resolveCollectionRelations(collection), refProp.relation?.relationName ?? propName);
6029
+ if (relation?.kind !== "belongsTo") throw new Error(`Relation ${propName} does not put a column on this table (only \`belongsTo\` does)`);
6030
+ let targetCollection;
6031
+ try {
6032
+ targetCollection = relation.target();
6033
+ } catch {
6034
+ return "TEXT";
6035
+ }
6036
+ const pkProp = getPrimaryKeyProp(targetCollection);
6037
+ return pkProp.type === "number" ? "INTEGER" : pkProp.isUuid ? "UUID" : "TEXT";
6038
+ }
6039
+ case "reference": {
6040
+ const refProp = prop;
6041
+ const targetCollection = collections.find((c) => c.slug === refProp.path || getTableName(c) === refProp.path);
6042
+ if (!targetCollection) return "TEXT";
6043
+ const pkProp = getPrimaryKeyProp(targetCollection);
6044
+ return pkProp.type === "number" ? "INTEGER" : pkProp.isUuid ? "UUID" : "TEXT";
6045
+ }
6046
+ default: throw new Error(`No Postgres column type for property '${propName}' of type '${prop.type}' in collection '${collection.slug}'. Add a case to \`getSqlColumnType\` (and to \`getDrizzleColumn\`, which must agree).`);
6047
+ }
6048
+ };
6049
+ var generatePostgresSearchDdl = (allCollections) => {
6050
+ const collections = relationalCollections(allCollections);
6051
+ const specs = collections.map((c) => buildSearchColumnSpec(c)).filter((s) => s !== void 0);
6052
+ if (specs.length === 0) return "";
6053
+ const extensions = Array.from(new Set(specs.flatMap(searchExtensionStatements)));
6054
+ const helpers = Array.from(new Set(specs.flatMap(searchHelperFunctions)));
6055
+ let ddl = "-- This file is auto-generated by the Rebase DDL generator. Do not edit manually.\n";
6056
+ ddl += "--\n";
6057
+ ddl += "-- Full-text search for the collections declaring a `search` block.\n";
6058
+ ddl += "-- Applied by Rebase, not by Atlas — see generatePostgresSearchDdl.\n\n";
6059
+ extensions.forEach((s) => {
6060
+ ddl += `${s}\n`;
6061
+ });
6062
+ if (extensions.length > 0) ddl += "\n";
6063
+ helpers.forEach((s) => {
6064
+ ddl += `${s}\n\n`;
6065
+ });
6066
+ for (const collection of collections) {
6067
+ const spec = buildSearchColumnSpec(collection);
6068
+ if (!spec) continue;
6069
+ const table = `"${isPostgresCollectionConfig(collection) && collection.schema ? collection.schema : "public"}"."${getTableName(collection)}"`;
6070
+ searchStampGuards(spec).forEach((s) => {
6071
+ ddl += `${s}\n`;
6072
+ });
6073
+ ddl += `ALTER TABLE ${table} ADD COLUMN IF NOT EXISTS ${searchColumnDefinition(spec)};\n`;
6074
+ const fuzzyDef = fuzzyColumnDefinition(spec);
6075
+ if (fuzzyDef) ddl += `ALTER TABLE ${table} ADD COLUMN IF NOT EXISTS ${fuzzyDef};\n`;
6076
+ searchColumnStamps(spec).forEach((s) => {
6077
+ ddl += `${s.sql}\n`;
6078
+ });
6079
+ searchIndexStatements(spec).forEach((s) => {
6080
+ ddl += `${s}\n`;
6081
+ });
6082
+ ddl += "\n";
6083
+ }
6084
+ return ddl;
6085
+ };
6086
+ var generatePostgresDdl = async (allCollections, options = {
6087
+ includePolicies: true,
6088
+ includeSearch: true
6089
+ }) => {
6090
+ const collections = relationalCollections(allCollections);
6091
+ let ddl = "-- This file is auto-generated by the Rebase DDL generator. Do not edit manually.\n\n";
6092
+ const uniqueSchemas = Array.from(/* @__PURE__ */ new Set([REBASE_SCHEMA, ...collections.map((c) => isPostgresCollectionConfig(c) ? c.schema : void 0).filter(Boolean)]));
6093
+ uniqueSchemas.forEach((schema) => {
6094
+ if (schema) ddl += `CREATE SCHEMA IF NOT EXISTS "${schema}";\n`;
6095
+ });
6096
+ if (uniqueSchemas.length > 0) ddl += "\n";
6097
+ const searchSpecs = collections.map((c) => buildSearchColumnSpec(c)).filter((s) => s !== void 0);
6098
+ if (searchSpecs.length > 0) if (options.includeSearch === false) ddl += "-- Full-text search support lives in `search.sql`, applied separately.\n\n";
6099
+ else {
6100
+ const extensions = Array.from(new Set(searchSpecs.flatMap(searchExtensionStatements)));
6101
+ const helpers = Array.from(new Set(searchSpecs.flatMap(searchHelperFunctions)));
6102
+ ddl += "-- Full-text search support (collections declaring a `search` block)\n";
6103
+ extensions.forEach((s) => {
6104
+ ddl += `${s}\n`;
6105
+ });
6106
+ if (extensions.length > 0) ddl += "\n";
6107
+ helpers.forEach((s) => {
6108
+ ddl += `${s}\n\n`;
6109
+ });
6110
+ }
6111
+ const emittedEnums = /* @__PURE__ */ new Set();
6112
+ collections.forEach((collection) => {
6113
+ const collectionTable = getTableName(collection);
6114
+ const schema = isPostgresCollectionConfig(collection) && collection.schema ? collection.schema : "public";
6115
+ Object.entries(collection.properties ?? {}).forEach(([propName, prop]) => {
6116
+ if ("enum" in prop && (prop.type === "string" || prop.type === "number") && prop.enum) {
6117
+ const enumDbName = `${collectionTable}_${resolveColumnName(propName, prop)}`;
6118
+ const values = Array.isArray(prop.enum) ? prop.enum.map((v) => String(typeof v === "object" && v !== null && "id" in v ? v.id : v)) : Object.keys(prop.enum);
6119
+ if (values.length > 0 && !emittedEnums.has(`${schema}.${enumDbName}`)) {
6120
+ emittedEnums.add(`${schema}.${enumDbName}`);
6121
+ ddl += `CREATE TYPE "${schema}"."${enumDbName}" AS ENUM (${values.map(quoteSqlLiteral).join(", ")});\n`;
6122
+ }
6123
+ }
6124
+ });
6125
+ });
6126
+ if (ddl.endsWith(";\n")) ddl += "\n";
6127
+ const junctionSpecs = resolveJunctionSpecs(collections);
6128
+ const allTablesToGenerate = /* @__PURE__ */ new Map();
6129
+ for (const collection of collections) {
6130
+ const tableName = getTableName(collection);
6131
+ if (tableName) allTablesToGenerate.set(tableName, { collection });
6132
+ const resolvedRelations = resolveCollectionRelations(collection);
6133
+ for (const relation of Object.values(resolvedRelations)) if (isManyToMany(relation)) {
6134
+ const junctionTableName = relation.through.table;
6135
+ if (!allTablesToGenerate.has(junctionTableName)) allTablesToGenerate.set(junctionTableName, {
6136
+ collection: {
6137
+ table: junctionTableName,
6138
+ properties: {}
6139
+ },
6140
+ isJunction: true,
6141
+ relation,
6142
+ sourceCollection: collection
6143
+ });
6144
+ }
6145
+ }
6146
+ const fkStatements = [];
6147
+ const indexStatements = [];
6148
+ const policyStatements = [];
6149
+ for (const [tableName, { collection, isJunction, relation, sourceCollection }] of allTablesToGenerate.entries()) {
6150
+ const schema = isPostgresCollectionConfig(collection) && collection.schema ? collection.schema : "public";
6151
+ const baseTableName = tableName.includes(".") ? tableName.split(".").pop() : tableName;
6152
+ if (isJunction && relation && sourceCollection && isManyToMany(relation)) {
6153
+ const targetCollection = relation.target();
6154
+ const sourceTable = getTableName(sourceCollection);
6155
+ const targetTable = getTableName(targetCollection);
6156
+ const sourceSchema = isPostgresCollectionConfig(sourceCollection) && sourceCollection.schema ? sourceCollection.schema : "public";
6157
+ const targetSchema = isPostgresCollectionConfig(targetCollection) && targetCollection.schema ? targetCollection.schema : "public";
6158
+ const { sourceColumn, targetColumn } = relation.through;
6159
+ const sourceColType = isNumericId(sourceCollection) ? "INTEGER" : getPrimaryKeyProp(sourceCollection).isUuid ? "UUID" : "TEXT";
6160
+ const targetColType = isNumericId(targetCollection) ? "INTEGER" : getPrimaryKeyProp(targetCollection).isUuid ? "UUID" : "TEXT";
6161
+ const sourceId = getPrimaryKeyName(sourceCollection);
6162
+ const targetId = getPrimaryKeyName(targetCollection);
6163
+ const onDelete = relation.onDelete ?? "CASCADE";
6164
+ ddl += `CREATE TABLE "${schema}"."${baseTableName}" (\n`;
6165
+ ddl += ` "${sourceColumn}" ${sourceColType} NOT NULL,\n`;
6166
+ ddl += ` "${targetColumn}" ${targetColType} NOT NULL,\n`;
6167
+ ddl += ` PRIMARY KEY ("${sourceColumn}", "${targetColumn}")\n`;
6168
+ ddl += `);\n\n`;
6169
+ fkStatements.push(`ALTER TABLE "${schema}"."${baseTableName}" ADD CONSTRAINT "${toPostgresIdentifier(`${baseTableName}_${sourceColumn}_fkey`)}" FOREIGN KEY ("${sourceColumn}") REFERENCES "${sourceSchema}"."${sourceTable}" ("${sourceId}") ON DELETE ${onDelete.toUpperCase()};`);
6170
+ fkStatements.push(`ALTER TABLE "${schema}"."${baseTableName}" ADD CONSTRAINT "${toPostgresIdentifier(`${baseTableName}_${targetColumn}_fkey`)}" FOREIGN KEY ("${targetColumn}") REFERENCES "${targetSchema}"."${targetTable}" ("${targetId}") ON DELETE ${onDelete.toUpperCase()};`);
6171
+ if (options.includePolicies) {
6172
+ ddl += `ALTER TABLE "${schema}"."${baseTableName}" ENABLE ROW LEVEL SECURITY;\n`;
6173
+ ddl += `\n`;
6174
+ const spec = junctionSpecs.get(baseTableName);
6175
+ if (spec) {
6176
+ const junctionCollection = getJunctionCollectionConfig(spec);
6177
+ const resolveCollection = (slug) => collections.find((c) => c.slug === slug || getTableName(c) === slug);
6178
+ getJunctionSecurityRules(spec).forEach((rule) => {
6179
+ policyStatements.push(generatePolicyDdl(junctionCollection, rule, resolveCollection));
6180
+ });
6181
+ }
6182
+ }
6183
+ } else if (!isJunction) {
6184
+ ddl += `CREATE TABLE "${schema}"."${baseTableName}" (\n`;
6185
+ const columns = [];
6186
+ Object.entries(collection.properties ?? {}).forEach(([propName, prop]) => {
6187
+ if (prop.type === "relation") {
6188
+ const refProp = prop;
6189
+ const relInfo = findRelation(resolveCollectionRelations(collection), refProp.relation?.relationName ?? propName);
6190
+ if (relInfo?.kind !== "belongsTo") return;
6191
+ if (collection.properties[relInfo.localKey] && propName !== relInfo.localKey) return;
6192
+ let targetCollection;
6193
+ try {
6194
+ targetCollection = relInfo.target();
6195
+ } catch {
6196
+ return;
6197
+ }
6198
+ const targetTable = getTableName(targetCollection);
6199
+ const targetSchema = isPostgresCollectionConfig(targetCollection) && targetCollection.schema ? targetCollection.schema : "public";
6200
+ const targetId = getPrimaryKeyName(targetCollection);
6201
+ const fkColType = getSqlColumnType(propName, prop, collection, collections);
6202
+ const onUpdate = relInfo.onUpdate ? ` ON UPDATE ${relInfo.onUpdate.toUpperCase()}` : "";
6203
+ const required = prop.validation?.required;
6204
+ const onDeleteVal = relInfo.onDelete ?? (required ? "CASCADE" : "SET NULL");
6205
+ let colDef = ` "${relInfo.localKey}" ${fkColType}`;
6206
+ if (required) colDef += " NOT NULL";
6207
+ columns.push(colDef);
6208
+ fkStatements.push(`ALTER TABLE "${schema}"."${baseTableName}" ADD CONSTRAINT "${toPostgresIdentifier(`${baseTableName}_${relInfo.localKey}_fkey`)}" FOREIGN KEY ("${relInfo.localKey}") REFERENCES "${targetSchema}"."${targetTable}" ("${targetId}") ON DELETE ${onDeleteVal.toUpperCase()}${onUpdate};`);
6209
+ } else if (prop.type === "reference") {
6210
+ const refProp = prop;
6211
+ const targetCollection = collections.find((c) => c.slug === refProp.path || getTableName(c) === refProp.path);
6212
+ const colName = resolveColumnName(propName, prop);
6213
+ const colType = getSqlColumnType(propName, prop, collection, collections);
6214
+ const required = prop.validation?.required;
6215
+ if (!targetCollection) {
6216
+ let colDef = ` "${colName}" ${colType}`;
6217
+ if (required) colDef += " NOT NULL";
6218
+ columns.push(colDef);
6219
+ } else {
6220
+ const targetTable = getTableName(targetCollection);
6221
+ const targetSchema = isPostgresCollectionConfig(targetCollection) && targetCollection.schema ? targetCollection.schema : "public";
6222
+ const targetId = getPrimaryKeyName(targetCollection);
6223
+ const onDelete = required ? "CASCADE" : "SET NULL";
6224
+ let colDef = ` "${colName}" ${colType}`;
6225
+ if (required) colDef += " NOT NULL";
6226
+ columns.push(colDef);
6227
+ fkStatements.push(`ALTER TABLE "${schema}"."${baseTableName}" ADD CONSTRAINT "${toPostgresIdentifier(`${baseTableName}_${colName}_fkey`)}" FOREIGN KEY ("${colName}") REFERENCES "${targetSchema}"."${targetTable}" ("${targetId}") ON DELETE ${onDelete.toUpperCase()};`);
6228
+ }
6229
+ } else {
6230
+ const colName = resolveColumnName(propName, prop);
6231
+ const authDefinition = isAuthCollection(collection) ? authUsersColumnDefinition(colName) : void 0;
6232
+ if (authDefinition && !isIdProperty(propName, prop, collection)) {
6233
+ columns.push(` "${colName}" ${authDefinition}`);
6234
+ return;
6235
+ }
6236
+ let colDef = ` "${colName}" ${getSqlColumnType(propName, prop, collection, collections)}`;
6237
+ if (isIdProperty(propName, prop, collection)) colDef += " PRIMARY KEY";
6238
+ if ("isId" in prop && prop.isId !== "manual" && prop.isId !== true && prop.isId !== "increment") {
6239
+ if (prop.isId === "uuid") colDef += " DEFAULT gen_random_uuid()";
6240
+ else if (prop.isId === "cuid") colDef += " DEFAULT cuid()";
6241
+ else if (typeof prop.isId === "string") colDef += ` DEFAULT ${prop.isId}`;
6242
+ }
6243
+ if (!isIdProperty(propName, prop, collection) && prop.validation?.unique) colDef += " UNIQUE";
6244
+ if (prop.type === "date") {
6245
+ const dateProp = prop;
6246
+ if (dateProp.autoValue === "on_create" || dateProp.autoValue === "on_update") colDef += " DEFAULT now()";
6247
+ }
6248
+ if (prop.validation?.required && !colDef.includes("PRIMARY KEY")) colDef += " NOT NULL";
6249
+ columns.push(colDef);
6250
+ }
6251
+ });
6252
+ if (isAuthCollection(collection)) {
6253
+ const declared = new Set(Object.entries(collection.properties ?? {}).map(([name, prop]) => resolveColumnName(name, prop)));
6254
+ for (const spec of AUTH_USERS_COLUMNS) {
6255
+ if (declared.has(spec.column)) continue;
6256
+ columns.push(` "${spec.column}" ${authUsersColumnSql(spec)}`);
6257
+ }
6258
+ }
6259
+ const searchSpec = options.includeSearch === false ? void 0 : buildSearchColumnSpec(collection);
6260
+ if (searchSpec) {
6261
+ columns.push(` ${searchColumnDefinition(searchSpec)}`);
6262
+ const fuzzyDef = fuzzyColumnDefinition(searchSpec);
6263
+ if (fuzzyDef) columns.push(` ${fuzzyDef}`);
6264
+ indexStatements.push(...searchIndexStatements(searchSpec));
6265
+ }
6266
+ const vectorPlan = buildVectorIndexPlan(collection, resolveColumnName);
6267
+ indexStatements.push(...vectorIndexStatements(vectorPlan));
6268
+ for (const skip of vectorPlan.skipped) indexStatements.push(`-- No ANN index on "${skip.schema}"."${skip.table}"."${skip.column}": ${skip.reason}`);
6269
+ if (!columns.some((c) => c.includes("PRIMARY KEY"))) columns.unshift(" \"id\" TEXT PRIMARY KEY");
6270
+ ddl += columns.join(",\n");
6271
+ ddl += `\n);\n\n`;
6272
+ if (options.includePolicies) {
6273
+ ddl += `ALTER TABLE "${schema}"."${baseTableName}" ENABLE ROW LEVEL SECURITY;\n`;
6274
+ ddl += `\n`;
6275
+ const securityRules = getEffectiveSecurityRules(collection);
6276
+ if (securityRules.length > 0) {
6277
+ const resolveCollection = (slug) => collections.find((c) => c.slug === slug || getTableName(c) === slug);
6278
+ securityRules.forEach((rule) => {
6279
+ policyStatements.push(generatePolicyDdl(collection, rule, resolveCollection));
6280
+ });
6281
+ }
6282
+ }
6283
+ }
6284
+ }
6285
+ if (fkStatements.length > 0) {
6286
+ ddl += "-- Foreign Key Constraints\n";
6287
+ ddl += fkStatements.join("\n") + "\n\n";
6288
+ }
6289
+ if (indexStatements.length > 0) {
6290
+ ddl += "-- Indexes\n";
6291
+ ddl += indexStatements.join("\n") + "\n\n";
6292
+ }
6293
+ if (policyStatements.length > 0) {
6294
+ ddl += "-- Row Level Security Policies\n";
6295
+ ddl += policyStatements.join("");
6296
+ ddl += "\n";
6297
+ }
6298
+ return ddl;
6299
+ };
6300
+ var schemaOfCollection = (collection) => isPostgresCollectionConfig(collection) && collection.schema ? collection.schema : "public";
6301
+ var bareTableName = (name) => name.includes(".") ? name.split(".").pop() : name;
6302
+ /**
6303
+ * Truncate a derived identifier the way Postgres does: to 63 bytes, silently.
6304
+ *
6305
+ * This is not cosmetic, and not really a naming choice at all — it is agreeing
6306
+ * with the name the database ALREADY stored. `ADD CONSTRAINT` on a longer name
6307
+ * succeeds and records the truncated form, so the untruncated name this used to
6308
+ * derive matched nothing in the catalogue. Boot-ensure compares its planned
6309
+ * constraints against `readExistingSchema`, which reads catalogue names, so the
6310
+ * comparison could never hit: every boot re-issued `ADD CONSTRAINT` for the same
6311
+ * constraint, forever, and got "already exists" every time. Non-fatal (foreign
6312
+ * keys are the one action allowed to fail) and therefore permanent — an error in
6313
+ * the log on every restart of a project whose table and column names happened to
6314
+ * be long.
6315
+ *
6316
+ * Byte length, not string length: NAMEDATALEN is 64 bytes, and a multi-byte
6317
+ * character straddling the boundary would be cut mid-sequence by `slice(0, 63)`.
6318
+ */
6319
+ var foreignKeyPlan = (args) => {
6320
+ const constraintName = toPostgresIdentifier(`${args.table}_${args.column}_fkey`);
6321
+ const onUpdate = args.onUpdate ? ` ON UPDATE ${args.onUpdate.toUpperCase()}` : "";
6322
+ return {
6323
+ constraintName,
6324
+ schema: args.schema,
6325
+ table: args.table,
6326
+ column: args.column,
6327
+ targetSchema: args.targetSchema,
6328
+ targetTable: args.targetTable,
6329
+ targetColumn: args.targetColumn,
6330
+ 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};`
6331
+ };
6332
+ };
6333
+ /**
6334
+ * The FK columns the declared collections own — one entry per `relation`
6335
+ * (`belongsTo` side) or `reference` property.
6336
+ *
6337
+ * Split out of {@link generatePostgresDdl} so the boot-time schema ensure can
6338
+ * create the same columns with the same names, types and constraints. Before
6339
+ * this it skipped them outright, which was survivable only because `db push`
6340
+ * always followed; on a managed tenant nothing follows, so a table arrived
6341
+ * without the column its own collection reads and wrote 400 on every insert.
6342
+ *
6343
+ * A relation whose target is not in the bundle yields no column at all (the
6344
+ * generator returns early on an unresolvable target); a `reference` whose target
6345
+ * is unknown yields the column without a constraint. Both mirror the generator
6346
+ * exactly — a divergence here is a schema fork between boot and `db push`.
6347
+ */
6348
+ var planRelationalColumns = (allCollections) => {
6349
+ const collections = relationalCollections(allCollections);
6350
+ const plans = [];
6351
+ for (const collection of collections) {
6352
+ const tableName = getTableName(collection);
6353
+ if (!tableName) continue;
6354
+ const schema = schemaOfCollection(collection);
6355
+ const table = bareTableName(tableName);
6356
+ for (const [propName, rawProp] of Object.entries(collection.properties ?? {})) {
6357
+ const prop = rawProp;
6358
+ if (prop.type === "relation") {
6359
+ const refProp = prop;
6360
+ const relInfo = findRelation(resolveCollectionRelations(collection), refProp.relation?.relationName ?? propName);
6361
+ if (relInfo?.kind !== "belongsTo") continue;
6362
+ if (collection.properties[relInfo.localKey] && propName !== relInfo.localKey) continue;
6363
+ let targetCollection;
6364
+ try {
6365
+ targetCollection = relInfo.target();
6366
+ } catch {
6367
+ continue;
6368
+ }
6369
+ if (!targetCollection) continue;
6370
+ const required = prop.validation?.required;
6371
+ const relationName = refProp.relation?.relationName ?? propName;
6372
+ const legacyKey = legacyForeignKeyName(relationName);
6373
+ const derived = relInfo.localKey === generateForeignKeyName(relationName);
6374
+ plans.push({
6375
+ schema,
6376
+ table,
6377
+ column: relInfo.localKey,
6378
+ legacyColumn: derived && legacyKey !== relInfo.localKey ? legacyKey : void 0,
6379
+ type: getSqlColumnType(propName, prop, collection, collections),
6380
+ foreignKey: foreignKeyPlan({
6381
+ schema,
6382
+ table,
6383
+ column: relInfo.localKey,
6384
+ targetSchema: schemaOfCollection(targetCollection),
6385
+ targetTable: bareTableName(getTableName(targetCollection)),
6386
+ targetColumn: getPrimaryKeyName(targetCollection),
6387
+ onDelete: relInfo.onDelete ?? (required ? "CASCADE" : "SET NULL"),
6388
+ onUpdate: relInfo.onUpdate
6389
+ })
6390
+ });
6391
+ } else if (prop.type === "reference") {
6392
+ const refProp = prop;
6393
+ const targetCollection = collections.find((c) => c.slug === refProp.path || getTableName(c) === refProp.path);
6394
+ const column = resolveColumnName(propName, prop);
6395
+ const type = getSqlColumnType(propName, prop, collection, collections);
6396
+ const required = prop.validation?.required;
6397
+ plans.push({
6398
+ schema,
6399
+ table,
6400
+ column,
6401
+ type,
6402
+ foreignKey: targetCollection ? foreignKeyPlan({
6403
+ schema,
6404
+ table,
6405
+ column,
6406
+ targetSchema: schemaOfCollection(targetCollection),
6407
+ targetTable: bareTableName(getTableName(targetCollection)),
6408
+ targetColumn: getPrimaryKeyName(targetCollection),
6409
+ onDelete: required ? "CASCADE" : "SET NULL"
6410
+ }) : void 0
6411
+ });
6412
+ }
6413
+ }
6414
+ }
6415
+ return plans;
6416
+ };
6417
+ /**
6418
+ * The junction tables a bundle's many-to-many relations imply.
6419
+ *
6420
+ * Derived from {@link resolveJunctionSpecs}, the same source the junction RLS
6421
+ * comes from, so a table created here always has policies planned for it — a
6422
+ * junction with row-level security left off is readable and writable by every
6423
+ * signed-in user, which is why the two must ship together.
6424
+ */
6425
+ var planJunctionTables = (allCollections) => {
6426
+ const collections = relationalCollections(allCollections);
6427
+ const plans = [];
6428
+ for (const spec of resolveJunctionSpecs(collections).values()) {
6429
+ const [source, target] = spec.endpoints;
6430
+ const legacyFor = (endpoint) => {
6431
+ const slug = toSnakeCase(endpoint.collection.slug ?? endpoint.collection.name ?? "");
6432
+ const legacy = legacyForeignKeyName(slug);
6433
+ return endpoint.junctionColumn === generateForeignKeyName(slug) && legacy !== endpoint.junctionColumn ? legacy : void 0;
6434
+ };
6435
+ const columns = [{
6436
+ name: source.junctionColumn,
6437
+ type: junctionKeyType(source.collection),
6438
+ legacyName: legacyFor(source)
6439
+ }, {
6440
+ name: target.junctionColumn,
6441
+ type: junctionKeyType(target.collection),
6442
+ legacyName: legacyFor(target)
6443
+ }];
6444
+ const onDelete = spec.declaringSides[0]?.relation.onDelete ?? "CASCADE";
6445
+ plans.push({
6446
+ schema: spec.schema,
6447
+ table: spec.table,
6448
+ columns,
6449
+ 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(", ")}));`,
6450
+ foreignKeys: [source, target].map((endpoint, i) => foreignKeyPlan({
6451
+ schema: spec.schema,
6452
+ table: spec.table,
6453
+ column: columns[i].name,
6454
+ targetSchema: schemaOfCollection(endpoint.collection),
6455
+ targetTable: bareTableName(getTableName(endpoint.collection)),
6456
+ targetColumn: getPrimaryKeyName(endpoint.collection),
6457
+ onDelete
6458
+ }))
6459
+ });
6460
+ }
6461
+ return plans;
6462
+ };
6463
+ /**
6464
+ * The per-table RLS plan for the *declared* collections, as executable
6465
+ * statements — what the managed runtime applies at boot so a freshly
6466
+ * provisioned tenant database serves data instead of 401ing every read.
6467
+ *
6468
+ * Mirrors {@link generatePostgresPoliciesDdl} exactly (same
6469
+ * `generatePolicyStatements`, same enable-RLS, same effective rules, same
6470
+ * derived junction rules), so boot and `db push` produce identical policies from
6471
+ * identical collections.
6472
+ *
6473
+ * Junction tables are included, and have to be: boot creates them now
6474
+ * ({@link planJunctionTables}), and a junction with RLS left off is readable and
6475
+ * writable by every signed-in user. A junction whose table is still absent is
6476
+ * skipped by the applier, not planned away here.
6477
+ */
6478
+ var planCollectionPolicies = (allCollections) => {
6479
+ const collections = relationalCollections(allCollections);
6480
+ const resolveCollection = (slug) => collections.find((c) => c.slug === slug || getTableName(c) === slug);
6481
+ const plans = [];
6482
+ const seen = /* @__PURE__ */ new Set();
6483
+ for (const collection of collections) {
6484
+ const tableName = getTableName(collection);
6485
+ if (!tableName) continue;
6486
+ const schema = isPostgresCollectionConfig(collection) && collection.schema ? collection.schema : "public";
6487
+ const baseTableName = tableName.includes(".") ? tableName.split(".").pop() : tableName;
6488
+ const qualified = `${schema}.${baseTableName}`;
6489
+ if (seen.has(qualified)) continue;
6490
+ seen.add(qualified);
6491
+ const policyStatements = [];
6492
+ for (const rule of getEffectiveSecurityRules(collection)) policyStatements.push(...generatePolicyStatements(collection, rule, resolveCollection));
6493
+ plans.push({
6494
+ schema,
6495
+ table: baseTableName,
6496
+ qualified,
6497
+ enableRls: `ALTER TABLE "${schema}"."${baseTableName}" ENABLE ROW LEVEL SECURITY;`,
6498
+ policyStatements
6499
+ });
6500
+ }
6501
+ for (const spec of resolveJunctionSpecs(collections).values()) {
6502
+ const qualified = `${spec.schema}.${spec.table}`;
6503
+ if (seen.has(qualified)) continue;
6504
+ seen.add(qualified);
6505
+ const junctionCollection = getJunctionCollectionConfig(spec);
6506
+ const policyStatements = [];
6507
+ for (const rule of getJunctionSecurityRules(spec)) policyStatements.push(...generatePolicyStatements(junctionCollection, rule, resolveCollection));
6508
+ plans.push({
6509
+ schema: spec.schema,
6510
+ table: spec.table,
6511
+ qualified,
6512
+ enableRls: `ALTER TABLE "${spec.schema}"."${spec.table}" ENABLE ROW LEVEL SECURITY;`,
6513
+ policyStatements
6514
+ });
6515
+ }
6516
+ return plans;
6517
+ };
6518
+ var generatePostgresPoliciesDdl = (allCollections) => {
6519
+ const collections = relationalCollections(allCollections);
6520
+ let ddl = "-- This file contains RLS policies generated by Rebase. Applied separately from migrations.\n\n";
6521
+ const allTablesToGenerate = /* @__PURE__ */ new Map();
6522
+ for (const collection of collections) {
6523
+ const tableName = getTableName(collection);
6524
+ if (tableName) allTablesToGenerate.set(tableName, { collection });
6525
+ }
6526
+ for (const [tableName, { collection }] of allTablesToGenerate.entries()) {
6527
+ const schema = isPostgresCollectionConfig(collection) && collection.schema ? collection.schema : "public";
6528
+ const baseTableName = tableName.includes(".") ? tableName.split(".").pop() : tableName;
6529
+ ddl += `ALTER TABLE "${schema}"."${baseTableName}" ENABLE ROW LEVEL SECURITY;\n`;
6530
+ ddl += `\n`;
6531
+ const securityRules = getEffectiveSecurityRules(collection);
6532
+ if (securityRules.length > 0) {
6533
+ const resolveCollection = (slug) => collections.find((c) => c.slug === slug || getTableName(c) === slug);
6534
+ const injectedNames = new Set(getInjectedSecurityRules(collection).map((rule) => rule.name));
6535
+ securityRules.forEach((rule) => {
6536
+ if (rule.name && injectedNames.has(rule.name)) {
6537
+ ddl += `-- Injected by Rebase (not from this collection's securityRules).\n`;
6538
+ ddl += `-- Set \`disableDefaultPolicies: true\` on "${collection.slug}" to drop these and own its RLS outright.\n`;
6539
+ }
6540
+ ddl += generatePolicyDdl(collection, rule, resolveCollection);
6541
+ });
6542
+ ddl += "\n";
6543
+ }
6544
+ }
6545
+ const junctionSpecs = resolveJunctionSpecs(collections);
6546
+ for (const spec of junctionSpecs.values()) {
6547
+ ddl += `ALTER TABLE "${spec.schema}"."${spec.table}" ENABLE ROW LEVEL SECURITY;\n`;
6548
+ ddl += `\n`;
6549
+ const junctionRules = getJunctionSecurityRules(spec);
6550
+ if (junctionRules.length === 0) continue;
6551
+ const junctionCollection = getJunctionCollectionConfig(spec);
6552
+ const resolveCollection = (slug) => collections.find((c) => c.slug === slug || getTableName(c) === slug);
6553
+ const declaringSlugs = spec.declaringSides.map((s) => s.collection.slug).join("\", \"");
6554
+ ddl += `-- Derived by Rebase for the junction "${spec.table}" (no collection declares it).\n`;
6555
+ ddl += `-- Reads require both endpoint rows to be visible; writes follow the update\n`;
6556
+ ddl += `-- rules of "${declaringSlugs}". Set \`disableDefaultPolicies: true\` on the\n`;
6557
+ ddl += `-- declaring collection(s) to drop these and police the junction yourself.\n`;
6558
+ junctionRules.forEach((rule) => {
6559
+ ddl += generatePolicyDdl(junctionCollection, rule, resolveCollection);
6560
+ });
6561
+ ddl += "\n";
6562
+ }
6563
+ return ddl;
6564
+ };
6565
+ //#endregion
6566
+ //#region src/schema/ensure-collection-tables.ts
6567
+ /**
6568
+ * Bringing a database up to date with a bundle's collections, additively.
6569
+ *
6570
+ * ## Why this exists
6571
+ *
6572
+ * A managed runtime boots someone else's compiled project against a database it
6573
+ * has never seen. Auth tables are ensured at boot already, but collection tables
6574
+ * were not created by anything: the platform ran the app and every `/api/data/*`
6575
+ * request answered 500 on a missing relation. `rebase db push` cannot help — it
6576
+ * is an Atlas-driven CLI command, and the runtime image ships no CLI.
6577
+ *
6578
+ * ## Why additive-only, forever
6579
+ *
6580
+ * This runs unattended, against a database with customers' data in it, with no
6581
+ * human reading a diff. So it may only ever do things that cannot lose data:
6582
+ * create a missing table, add a missing column, create a missing enum type.
6583
+ *
6584
+ * It will **never** drop a table or a column, narrow a type, or alter a
6585
+ * constraint. A removed field leaves its column behind; a renamed field looks
6586
+ * like an addition and the old column stays. That is the correct trade for an
6587
+ * automated path — the alternative is an unattended process that can silently
6588
+ * destroy a column, which is precisely the failure `db push` was hardened
6589
+ * against. Destructive changes stay a deliberate, human-reviewed migration.
6590
+ *
6591
+ * Because of that, this is safe to run on every boot, and re-running it is a
6592
+ * no-op.
6593
+ */
6594
+ var ensure_collection_tables_exports = /* @__PURE__ */ __exportAll({
6595
+ ensureCollectionTables: () => ensureCollectionTables,
6596
+ planCollectionSchemaEnsure: () => planCollectionSchemaEnsure,
6597
+ readExistingSchema: () => readExistingSchema,
6598
+ readSchemaFactsFor: () => readSchemaFactsFor
6599
+ });
6600
+ /** Postgres identifiers this module is willing to interpolate. */
6601
+ var SAFE_IDENTIFIER = /^[A-Za-z_][A-Za-z0-9_$]*$/;
6602
+ function assertSafeIdentifier(value, what) {
6603
+ if (!SAFE_IDENTIFIER.test(value)) throw new Error(`Refusing to build SQL with an unsafe ${what}: ${JSON.stringify(value)}`);
6604
+ return value;
6605
+ }
6606
+ function schemaOf(collection) {
6607
+ return isPostgresCollectionConfig(collection) && collection.schema ? collection.schema : "public";
6608
+ }
6609
+ function qualified(collection) {
6610
+ return `${schemaOf(collection)}.${getTableName(collection)}`;
6611
+ }
6612
+ /**
6613
+ * Enum types a collection's properties require, as `schema.typename`.
6614
+ *
6615
+ * Named exactly as the DDL generator names them (`<table>_<column>`), because
6616
+ * a column added here has to reference the same type the generator would have
6617
+ * created — a second, differently-named type for the same field would be a
6618
+ * silent schema fork.
6619
+ */
6620
+ function requiredEnums(collection) {
6621
+ const table = getTableName(collection);
6622
+ const schema = schemaOf(collection);
6623
+ const out = [];
6624
+ for (const [propName, prop] of Object.entries(collection.properties ?? {})) {
6625
+ const p = prop;
6626
+ if (!("enum" in p) || !p.enum) continue;
6627
+ if (p.type !== "string" && p.type !== "number") continue;
6628
+ const values = p.enum.map((entry) => entry && typeof entry === "object" && "id" in entry ? String(entry.id) : String(entry)).filter((v) => v.length > 0);
6629
+ if (values.length === 0) continue;
6630
+ out.push({
6631
+ name: `${schema}.${table}_${resolveColumnName(propName, p)}`,
6632
+ values
6633
+ });
6634
+ }
6635
+ return out;
6636
+ }
6637
+ /**
6638
+ * Decide what to add. Pure — the caller supplies what exists and runs the result.
6639
+ *
6640
+ * Ordering matters and is deliberate: enum types before the tables and columns
6641
+ * that reference them, tables before the columns added to other tables (a new
6642
+ * table may be the target of a relation), and nothing is emitted twice.
6643
+ */
6644
+ function planCollectionSchemaEnsure(allCollections, existing, options = {}) {
6645
+ const constraintPolicy = options.constraints ?? "additive";
6646
+ const withheldConstraints = [];
6647
+ assertSearchIsPostgresOnly(allCollections);
6648
+ const collections = relationalCollections(allCollections);
6649
+ const actions = [];
6650
+ const plannedEnums = /* @__PURE__ */ new Set();
6651
+ for (const collection of collections) for (const { name, values } of requiredEnums(collection)) {
6652
+ if (existing.enums.has(name) || plannedEnums.has(name)) {
6653
+ const current = existing.enumValues?.get(name);
6654
+ if (!current || plannedEnums.has(name)) continue;
6655
+ const [schema, typeName] = name.split(".");
6656
+ for (const value of values) {
6657
+ if (current.includes(value)) continue;
6658
+ actions.push({
6659
+ kind: "add-enum-value",
6660
+ target: `${name}.${value}`,
6661
+ sql: `ALTER TYPE "${schema}"."${typeName}" ADD VALUE IF NOT EXISTS ${quoteSqlLiteral(value)};`
6662
+ });
6663
+ }
6664
+ continue;
6665
+ }
6666
+ plannedEnums.add(name);
6667
+ const [schema, typeName] = name.split(".");
6668
+ actions.push({
6669
+ kind: "create-enum",
6670
+ target: name,
6671
+ sql: `CREATE TYPE "${schema}"."${typeName}" AS ENUM (${values.map(quoteSqlLiteral).join(", ")});`
6672
+ });
6673
+ }
6674
+ const searchSpecs = collections.map((c) => buildSearchColumnSpec(c)).filter((spec) => spec !== void 0);
6675
+ const plannedExtensions = /* @__PURE__ */ new Set();
6676
+ for (const spec of searchSpecs) for (const statement of searchExtensionStatements(spec)) {
6677
+ if (plannedExtensions.has(statement)) continue;
6678
+ plannedExtensions.add(statement);
6679
+ actions.push({
6680
+ kind: "create-extension",
6681
+ target: statement.replace(/^CREATE EXTENSION IF NOT EXISTS |;$/g, ""),
6682
+ sql: statement
6683
+ });
6684
+ }
6685
+ const plannedFunctions = /* @__PURE__ */ new Set();
6686
+ for (const spec of searchSpecs) for (const statement of searchHelperFunctions(spec)) {
6687
+ if (plannedFunctions.has(statement)) continue;
6688
+ plannedFunctions.add(statement);
6689
+ actions.push({
6690
+ kind: "create-function",
6691
+ target: statement.includes("unaccent") ? SEARCH_UNACCENT_FN : SEARCH_TEXT_FN,
6692
+ sql: statement
6693
+ });
6694
+ }
6695
+ const created = /* @__PURE__ */ new Set();
6696
+ for (const collection of collections) {
6697
+ const key = qualified(collection);
6698
+ if (existing.tables.has(key) || created.has(key)) continue;
6699
+ created.add(key);
6700
+ const schema = schemaOf(collection);
6701
+ const table = getTableName(collection);
6702
+ const idEntry = Object.entries(collection.properties ?? {}).find(([n, p]) => isIdProperty(n, p, collection));
6703
+ const idName = idEntry ? resolveColumnName(idEntry[0], idEntry[1]) : "id";
6704
+ const idProp = idEntry?.[1];
6705
+ let idDef = `"${idName}" ${idProp ? getSqlColumnType(idEntry[0], idProp, collection, collections) : "TEXT"} PRIMARY KEY`;
6706
+ if (idProp?.type === "string" && idProp.isId === "uuid") idDef += " DEFAULT gen_random_uuid()";
6707
+ actions.push({
6708
+ kind: "create-table",
6709
+ target: key,
6710
+ sql: `CREATE TABLE IF NOT EXISTS "${schema}"."${table}" (${idDef});`
6711
+ });
6712
+ }
6713
+ const junctions = planJunctionTables(collections);
6714
+ for (const junction of junctions) {
6715
+ const key = `${junction.schema}.${junction.table}`;
6716
+ if (existing.tables.has(key) || created.has(key)) continue;
6717
+ created.add(key);
6718
+ actions.push({
6719
+ kind: "create-table",
6720
+ target: key,
6721
+ sql: junction.createTable
6722
+ });
6723
+ }
6724
+ const legacyForeignKeys = [];
6725
+ /**
6726
+ * Move a relation column that is only missing because it was renamed.
6727
+ *
6728
+ * Returns true when it handled the column, so the caller skips the ordinary
6729
+ * ADD. Adding here would be the wrong move and a quiet one: the data is in
6730
+ * the old column, `ADD COLUMN` creates the new one empty beside it, every
6731
+ * statement succeeds, and the relation reads the empty one. A rename is
6732
+ * metadata-only in Postgres, keeps the values, and carries the column's
6733
+ * indexes and constraints with it.
6734
+ *
6735
+ * Only ever reached when the new name is absent and the old name is
6736
+ * present, so there is nothing to overwrite and nothing to choose between.
6737
+ */
6738
+ const renameLegacyColumn = (key, schema, table, column, legacyName) => {
6739
+ const present = existing.tables.get(key);
6740
+ if (!legacyName || !present) return false;
6741
+ if (present.has(column) || !present.has(legacyName)) return false;
6742
+ legacyForeignKeys.push({
6743
+ table: key,
6744
+ expected: column,
6745
+ legacy: legacyName
6746
+ });
6747
+ actions.push({
6748
+ kind: "rename-column",
6749
+ target: `${key}.${column}`,
6750
+ sql: `ALTER TABLE "${schema}"."${table}" RENAME COLUMN "${legacyName}" TO "${column}";`
6751
+ });
6752
+ return true;
6753
+ };
6754
+ const addColumn = (key, schema, table, column, definition) => {
6755
+ if (existing.tables.get(key)?.has(column)) return;
6756
+ actions.push({
6757
+ kind: "add-column",
6758
+ target: `${key}.${column}`,
6759
+ sql: `ALTER TABLE "${schema}"."${table}" ADD COLUMN IF NOT EXISTS "${column}" ${definition};`
6760
+ });
6761
+ };
6762
+ for (const collection of collections) {
6763
+ const key = qualified(collection);
6764
+ const schema = schemaOf(collection);
6765
+ const table = getTableName(collection);
6766
+ const fresh = created.has(key);
6767
+ const auth = isAuthCollection(collection);
6768
+ for (const [propName, prop] of Object.entries(collection.properties ?? {})) {
6769
+ const p = prop;
6770
+ if (isIdProperty(propName, p, collection)) continue;
6771
+ if (p.type === "reference" || p.type === "relation") continue;
6772
+ const column = resolveColumnName(propName, p);
6773
+ const authDefinition = auth ? authUsersColumnDefinition(column) : void 0;
6774
+ if (authDefinition) {
6775
+ addColumn(key, schema, table, column, authDefinition);
6776
+ continue;
6777
+ }
6778
+ let definition = getSqlColumnType(propName, p, collection, collections);
6779
+ if (fresh && p.validation?.unique) definition += " UNIQUE";
6780
+ const autoValue = p.autoValue;
6781
+ const hasDefault = p.type === "date" && (autoValue === "on_create" || autoValue === "on_update");
6782
+ if (hasDefault) definition += " DEFAULT now()";
6783
+ const required = p.validation?.required === true;
6784
+ const columnKey = `${key}.${column}`;
6785
+ const columnExists = existing.tables.get(key)?.has(column) === true;
6786
+ const tableIsEmpty = existing.populatedTables !== void 0 && existing.tables.has(key) && !existing.populatedTables.has(key);
6787
+ const notNullIsSafe = fresh || tableIsEmpty || hasDefault;
6788
+ if (required && !columnExists) if (notNullIsSafe) definition += " NOT NULL";
6789
+ else withheldConstraints.push({
6790
+ target: columnKey,
6791
+ kind: "not-null",
6792
+ reason: `"${column}" is required, but "${key}" already holds rows and the column has no default to backfill them with, so NOT NULL would be checked against data that does not have a value yet.`,
6793
+ remedy: "Backfill the column, then add the constraint — or give the property a default so every existing row gets one."
6794
+ });
6795
+ addColumn(key, schema, table, column, definition);
6796
+ if (columnExists && constraintPolicy === "converge") {
6797
+ const isNotNull = existing.notNullColumns?.has(columnKey) === true;
6798
+ if (required && !isNotNull) if (tableIsEmpty) actions.push({
6799
+ kind: "set-not-null",
6800
+ target: columnKey,
6801
+ sql: `ALTER TABLE "${schema}"."${table}" ALTER COLUMN "${column}" SET NOT NULL;`
6802
+ });
6803
+ else withheldConstraints.push({
6804
+ target: columnKey,
6805
+ kind: "not-null",
6806
+ reason: `"${column}" became required, but "${key}" holds rows and any of them with no value would make SET NOT NULL fail.`,
6807
+ remedy: "Backfill the column first — `UPDATE … SET \"" + column + "\" = … WHERE \"" + column + "\" IS NULL` — then apply this again."
6808
+ });
6809
+ if (!required && isNotNull) actions.push({
6810
+ kind: "drop-not-null",
6811
+ target: columnKey,
6812
+ sql: `ALTER TABLE "${schema}"."${table}" ALTER COLUMN "${column}" DROP NOT NULL;`
6813
+ });
6814
+ }
6815
+ }
6816
+ if (auth) {
6817
+ const declared = new Set(Object.entries(collection.properties ?? {}).map(([name, prop]) => resolveColumnName(name, prop)));
6818
+ for (const spec of AUTH_USERS_COLUMNS) {
6819
+ if (declared.has(spec.column)) continue;
6820
+ addColumn(key, schema, table, spec.column, authUsersColumnSql(spec));
6821
+ }
6822
+ }
6823
+ }
6824
+ const searchDrift = [];
6825
+ const searchAdopted = [];
6826
+ for (const spec of searchSpecs) {
6827
+ const key = `${spec.schema}.${spec.table}`;
6828
+ const definitions = { [spec.column]: `tsvector GENERATED ALWAYS AS (${spec.expression}) STORED` };
6829
+ if (spec.fuzzy) definitions[spec.fuzzy.column] = `text GENERATED ALWAYS AS (${spec.fuzzy.expression}) STORED`;
6830
+ for (const stamp of searchColumnStamps(spec)) {
6831
+ const definition = definitions[stamp.column];
6832
+ const exists = existing.tables.get(key)?.has(stamp.column) === true;
6833
+ const recorded = existing.columnComments?.get(`${key}.${stamp.column}`);
6834
+ if (exists && recorded?.startsWith("rebase:search:v1:") && recorded !== stamp.fingerprint) {
6835
+ searchDrift.push({
6836
+ table: key,
6837
+ column: stamp.column,
6838
+ found: recorded,
6839
+ expected: stamp.fingerprint,
6840
+ rebuild: [
6841
+ `ALTER TABLE "${spec.schema}"."${spec.table}" DROP COLUMN "${stamp.column}";`,
6842
+ `ALTER TABLE "${spec.schema}"."${spec.table}" ADD COLUMN "${stamp.column}" ${definition};`,
6843
+ stamp.sql
6844
+ ]
6845
+ });
6846
+ continue;
6847
+ }
6848
+ addColumn(key, spec.schema, spec.table, stamp.column, definition);
6849
+ if (exists && recorded === void 0) searchAdopted.push({
6850
+ table: key,
6851
+ column: stamp.column
6852
+ });
6853
+ if (recorded !== stamp.fingerprint) actions.push({
6854
+ kind: "comment-column",
6855
+ target: `${key}.${stamp.column}`,
6856
+ sql: stamp.sql
6857
+ });
6858
+ }
6859
+ }
6860
+ for (const junction of junctions) {
6861
+ const key = `${junction.schema}.${junction.table}`;
6862
+ if (created.has(key)) continue;
6863
+ for (const column of junction.columns) {
6864
+ if (renameLegacyColumn(key, junction.schema, junction.table, column.name, column.legacyName)) continue;
6865
+ addColumn(key, junction.schema, junction.table, column.name, column.type);
6866
+ }
6867
+ }
6868
+ for (const relational of planRelationalColumns(collections)) {
6869
+ const relKey = `${relational.schema}.${relational.table}`;
6870
+ if (renameLegacyColumn(relKey, relational.schema, relational.table, relational.column, relational.legacyColumn)) continue;
6871
+ addColumn(relKey, relational.schema, relational.table, relational.column, relational.type);
6872
+ }
6873
+ const knownConstraints = existing.constraints ?? /* @__PURE__ */ new Set();
6874
+ const plannedConstraints = /* @__PURE__ */ new Set();
6875
+ const foreignKeys = [...planRelationalColumns(collections).map((r) => r.foreignKey), ...junctions.flatMap((j) => j.foreignKeys)];
6876
+ for (const fk of foreignKeys) {
6877
+ if (!fk) continue;
6878
+ const name = `${fk.schema}.${fk.table}.${fk.constraintName}`;
6879
+ if (knownConstraints.has(name) || plannedConstraints.has(name)) continue;
6880
+ plannedConstraints.add(name);
6881
+ actions.push({
6882
+ kind: "add-constraint",
6883
+ target: `${fk.schema}.${fk.table}.${fk.constraintName}`,
6884
+ sql: fk.sql
6885
+ });
6886
+ }
6887
+ for (const spec of searchSpecs) for (const statement of searchIndexStatements(spec)) actions.push({
6888
+ kind: "create-index",
6889
+ target: `${spec.schema}.${spec.table}`,
6890
+ sql: statement.replace("CREATE INDEX IF NOT EXISTS", "CREATE INDEX CONCURRENTLY IF NOT EXISTS")
6891
+ });
6892
+ const vectorIndexSkipped = [];
6893
+ for (const collection of collections) {
6894
+ const plan = buildVectorIndexPlan(collection, resolveColumnName);
6895
+ for (const spec of plan.specs) actions.push({
6896
+ kind: "create-index",
6897
+ target: `${spec.schema}.${spec.table}`,
6898
+ sql: vectorIndexStatement(spec).replace("CREATE INDEX IF NOT EXISTS", "CREATE INDEX CONCURRENTLY IF NOT EXISTS")
6899
+ });
6900
+ vectorIndexSkipped.push(...plan.skipped);
6901
+ }
6902
+ return {
6903
+ actions,
6904
+ statements: actions.map((a) => a.sql),
6905
+ legacyForeignKeys,
6906
+ searchDrift,
6907
+ searchAdopted,
6908
+ vectorIndexSkipped,
6909
+ withheldConstraints
6910
+ };
6911
+ }
6912
+ /** Read what the database has, for the schemas the collections live in. */
6913
+ async function readExistingSchema(client, schemas) {
6914
+ const tables = /* @__PURE__ */ new Map();
6915
+ const enums = /* @__PURE__ */ new Set();
6916
+ if (schemas.length === 0) return {
6917
+ tables,
6918
+ enums
6919
+ };
6920
+ const inList = schemas.map((schema) => `'${assertSafeIdentifier(schema, "schema name")}'`).join(", ");
6921
+ const notNullColumns = /* @__PURE__ */ new Set();
6922
+ const { rows: columns } = await client.query(`SELECT table_schema, table_name, column_name, is_nullable
6923
+ FROM information_schema.columns
6924
+ WHERE table_schema IN (${inList})`);
6925
+ for (const row of columns) {
6926
+ const key = `${row.table_schema}.${row.table_name}`;
6927
+ if (!tables.has(key)) tables.set(key, /* @__PURE__ */ new Set());
6928
+ tables.get(key).add(row.column_name);
6929
+ if (row.is_nullable === "NO") notNullColumns.add(`${key}.${row.column_name}`);
6930
+ }
6931
+ const populatedTables = /* @__PURE__ */ new Set();
6932
+ const { rows: realTables } = await client.query(`SELECT n.nspname AS schema, c.relname AS name
6933
+ FROM pg_class c
6934
+ JOIN pg_namespace n ON c.relnamespace = n.oid
6935
+ WHERE c.relkind IN ('r', 'p') AND n.nspname IN (${inList})`);
6936
+ if (realTables.length > 0) {
6937
+ const probes = realTables.map((row) => {
6938
+ const schema = assertSafeIdentifier(row.schema, "schema name");
6939
+ const table = assertSafeIdentifier(row.name, "table name");
6940
+ return `SELECT ${quoteSqlLiteral(`${schema}.${table}`)} AS key, EXISTS(SELECT 1 FROM "${schema}"."${table}" LIMIT 1) AS populated`;
6941
+ });
6942
+ const { rows: populationRows } = await client.query(probes.join(" UNION ALL "));
6943
+ for (const row of populationRows) if (row.populated) populatedTables.add(row.key);
6944
+ }
6945
+ const enumValues = /* @__PURE__ */ new Map();
6946
+ const { rows: enumValueRows } = await client.query(`SELECT n.nspname AS schema, t.typname AS name, e.enumlabel AS value
6947
+ FROM pg_enum e
6948
+ JOIN pg_type t ON e.enumtypid = t.oid
6949
+ JOIN pg_namespace n ON t.typnamespace = n.oid
6950
+ WHERE n.nspname IN (${inList})
6951
+ ORDER BY t.typname, e.enumsortorder`);
6952
+ for (const row of enumValueRows) {
6953
+ const key = `${row.schema}.${row.name}`;
6954
+ if (!enumValues.has(key)) enumValues.set(key, []);
6955
+ enumValues.get(key).push(row.value);
6956
+ }
6957
+ const { rows: enumRows } = await client.query(`SELECT n.nspname AS schema, t.typname AS name
6958
+ FROM pg_type t
6959
+ JOIN pg_namespace n ON t.typnamespace = n.oid
6960
+ WHERE t.typtype = 'e' AND n.nspname IN (${inList})`);
6961
+ for (const row of enumRows) enums.add(`${row.schema}.${row.name}`);
6962
+ const constraints = /* @__PURE__ */ new Set();
6963
+ const { rows: constraintRows } = await client.query(`SELECT n.nspname AS schema, c.relname AS table, con.conname AS name
6964
+ FROM pg_constraint con
6965
+ JOIN pg_class c ON con.conrelid = c.oid
6966
+ JOIN pg_namespace n ON c.relnamespace = n.oid
6967
+ WHERE n.nspname IN (${inList})`);
6968
+ for (const row of constraintRows) constraints.add(`${row.schema}.${row.table}.${row.name}`);
6969
+ const columnComments = /* @__PURE__ */ new Map();
6970
+ const { rows: commentRows } = await client.query(`SELECT n.nspname AS schema, c.relname AS table, a.attname AS column, d.description AS comment
6971
+ FROM pg_description d
6972
+ JOIN pg_class c ON d.objoid = c.oid
6973
+ JOIN pg_namespace n ON c.relnamespace = n.oid
6974
+ JOIN pg_attribute a ON a.attrelid = c.oid AND a.attnum = d.objsubid
6975
+ WHERE d.objsubid > 0 AND n.nspname IN (${inList})`);
6976
+ for (const row of commentRows) {
6977
+ if (row.comment == null) continue;
6978
+ columnComments.set(`${row.schema}.${row.table}.${row.column}`, row.comment);
6979
+ }
6980
+ return {
6981
+ tables,
6982
+ enums,
6983
+ constraints,
6984
+ columnComments,
6985
+ enumValues,
6986
+ notNullColumns,
6987
+ populatedTables
6988
+ };
6989
+ }
6990
+ /**
6991
+ * What to tell an operator whose `search` block no longer matches its column.
6992
+ *
6993
+ * Every line here is doing work: naming the collection is not enough, because
6994
+ * the symptom (a search that finds nothing) points at the data, not the schema;
6995
+ * and the remediation has to be exact, because it is a table rewrite the
6996
+ * operator is being asked to schedule rather than discover.
6997
+ */
6998
+ function searchDriftMessage(drift) {
6999
+ 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.";
7000
+ }
7001
+ /**
7002
+ * The missing-pgvector explanation, appended to the error that reveals it.
7003
+ *
7004
+ * A `{ type: "vector" }` property compiles to `VECTOR(n)`, and nothing in the
7005
+ * OSS pipeline installs pgvector — not this ensure, not `db push`. Installing
7006
+ * an extension on someone's database is a decision with a deployment behind it
7007
+ * (image, superuser, cloud allow-list), so this path stays a refusal; what it
7008
+ * must not stay is a bare `type "vector" does not exist` on a crash-looping
7009
+ * pod, which names nothing the reader can act on.
7010
+ *
7011
+ * The scaffold now ships `pgvector/pgvector:pg18`, so this is reached by a
7012
+ * project pointed at a database someone else provisioned — which is exactly
7013
+ * the case where naming the extension and the image is worth the words.
7014
+ */
7015
+ function vectorExtensionHint(message) {
7016
+ if (!/type "(vector|halfvec|sparsevec)" does not exist/i.test(message)) return "";
7017
+ return "\n pgvector is not installed on this database, and Rebase does not install it: it is a server extension, so it needs an image that ships it (the scaffold's `pgvector/pgvector:pg18` does; a stock `postgres:18` does not) and a role allowed to run `CREATE EXTENSION vector;`. Install it once, then boot again. Rebase then creates an ANN index for the column automatically — see the `index` option on the property.";
7018
+ }
7019
+ /**
7020
+ * Read what the database looks like, for the schemas a set of collections
7021
+ * lives in.
7022
+ *
7023
+ * The same read `ensureCollectionTables` does at boot, exposed on its own for
7024
+ * the callers that want to *plan* against a real database without changing it —
7025
+ * the live schema editor, which has to tell somebody what a change would do
7026
+ * before they agree to it.
7027
+ */
7028
+ async function readSchemaFactsFor(client, collections) {
7029
+ const relational = relationalCollections(collections);
7030
+ return readExistingSchema(client, Array.from(/* @__PURE__ */ new Set([...relational.map(schemaOf), ...planJunctionTables(relational).map((junction) => junction.schema)])));
7031
+ }
7032
+ /**
7033
+ * Bring the database up to date. Returns what it did.
7034
+ *
7035
+ * Each statement runs on its own rather than in one transaction: they are all
7036
+ * independently safe and idempotent, and a single failure (an enum label that
7037
+ * cannot be added, say) should not roll back the tables that were created fine.
7038
+ * The error is surfaced with the statement that caused it.
7039
+ */
7040
+ async function ensureCollectionTables(client, collections, log) {
7041
+ const schemas = Array.from(/* @__PURE__ */ new Set([...collections.map(schemaOf), ...planJunctionTables(collections).map((j) => j.schema)]));
7042
+ for (const schema of schemas) {
7043
+ assertSafeIdentifier(schema, "schema name");
7044
+ if (schema !== "public") await client.query(`CREATE SCHEMA IF NOT EXISTS "${schema}";`);
7045
+ }
7046
+ const plan = planCollectionSchemaEnsure(collections, await readExistingSchema(client, schemas));
7047
+ const failures = [];
7048
+ for (const legacy of plan.legacyForeignKeys) {
7049
+ 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.`;
7050
+ logger.info(`[schema] ${message}`);
7051
+ log?.(message);
7052
+ }
7053
+ if (plan.searchDrift.length > 0) throw new Error(searchDriftMessage(plan.searchDrift));
7054
+ for (const adopted of plan.searchAdopted) {
7055
+ 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.`;
7056
+ logger.info(`[schema] ${message}`);
7057
+ log?.(message);
7058
+ }
7059
+ for (const skip of plan.vectorIndexSkipped) {
7060
+ const message = `No ANN index on "${skip.table}"."${skip.column}": ${skip.reason}`;
7061
+ logger.warn(`[schema] ${message}`);
7062
+ log?.(message);
7063
+ }
7064
+ for (const withheld of plan.withheldConstraints) {
7065
+ const message = `No NOT NULL on "${withheld.target}": ${withheld.reason} ${withheld.remedy}`;
7066
+ logger.warn(`[schema] ${message}`);
7067
+ log?.(message);
7068
+ }
7069
+ if (plan.actions.length === 0) {
7070
+ log?.("Schema is up to date; nothing to create.");
7071
+ return {
7072
+ ...plan,
7073
+ failures
7074
+ };
7075
+ }
7076
+ for (const action of plan.actions) try {
7077
+ if (await applyAction(client, action)) log?.(`${action.kind}: ${action.target}`);
7078
+ else log?.(`${action.kind}: ${action.target} (already created by a peer)`);
7079
+ } catch (err) {
7080
+ const message = err instanceof Error ? err.message : String(err);
7081
+ if (action.kind === "add-constraint" || action.kind === "comment-column") {
7082
+ failures.push({
7083
+ kind: action.kind,
7084
+ target: action.target,
7085
+ error: message
7086
+ });
7087
+ continue;
7088
+ }
7089
+ throw new Error(`Failed to ${action.kind} ${action.target}: ${message}${vectorExtensionHint(message)}\n ${action.sql}`);
7090
+ }
7091
+ return {
7092
+ ...plan,
7093
+ failures
7094
+ };
7095
+ }
7096
+ /** Attempts per action, including the first. Matches the server's bootstraps. */
7097
+ var DDL_ATTEMPTS = 4;
7098
+ /**
7099
+ * Run one planned statement, surviving a simultaneous boot.
7100
+ *
7101
+ * Every statement in a plan is written to be idempotent, and that is not the
7102
+ * same as being safe to run concurrently: `CREATE … IF NOT EXISTS` reads the
7103
+ * catalog and then writes to it as two steps, so peers starting together both
7104
+ * see "absent" and the loser gets a duplicate key on a *catalog* index. Measured
7105
+ * against Postgres 18: five instances, 8 of 10 calls lost. `CREATE TYPE` is
7106
+ * worse, because Postgres has no `IF NOT EXISTS` for it at all.
7107
+ *
7108
+ * What made that fatal here rather than merely noisy is the loop this sits in.
7109
+ * A losing statement threw, and the throw abandoned **every remaining action in
7110
+ * the plan** — so a replica that lost one race came up missing tables it never
7111
+ * attempted, and the boot log blamed the one statement that failed.
7112
+ *
7113
+ * @returns `true` if this process applied the statement, `false` if a peer had
7114
+ * already created the object. The distinction is only for the log; both mean
7115
+ * the object is now there.
7116
+ * @throws the original error for anything that is not a race — a syntax error, a
7117
+ * permission failure, a unique constraint the customer's own rows violate.
7118
+ */
7119
+ async function applyAction(client, action) {
7120
+ for (let attempt = 1;; attempt++) try {
7121
+ await client.query(action.sql);
7122
+ return true;
7123
+ } catch (err) {
7124
+ if (isDuplicateObjectRace(err)) {
7125
+ logger.debug(`[schema] ${action.kind} ${action.target}: already created by another instance`);
7126
+ return false;
7127
+ }
7128
+ if (isConcurrentDdlRace(err) && attempt < DDL_ATTEMPTS) {
7129
+ logger.debug(`[schema] ${action.kind} ${action.target}: lost a race with another instance (attempt ${attempt}/${DDL_ATTEMPTS}) — retrying`);
7130
+ await new Promise((resolve) => setTimeout(resolve, 40 * attempt * (1 + Math.random())));
7131
+ continue;
7132
+ }
7133
+ throw err;
7134
+ }
7135
+ }
7136
+ //#endregion
7137
+ export { camelCase as $, fieldKeyForColumn as A, parseIdValues as B, getJunctionCollectionConfig as C, policyToPostgres as D, getEffectiveSecurityRules as E, getTableVarName as F, updateDateAutoValues as G, createRelationRef as H, resolveCollectionRelations as I, legacyForeignKeyName as J, firstFreeKey as K, buildCompositeId as L, getColumnName as M, getEnumVarName as N, securityRuleToConditions as O, getTableName as P, mergeDeep as Q, getDeclaredPrimaryKeys as R, resolveStringColumnLength as S, resolveJunctionSpecs as T, createRelationRefWithData as U, sortCollectionsBySlug as V, normalizeToEntityRelation as W, getPolicyNamesForRule as X, toWireKey as Y, isPrototypePollutingKey as Z, OrderBySpecError as _, generatePostgresDdl as a, Vector as at, CollectionRegistry as b, planCollectionPolicies as c, authUsersColumnSql as d, toSnakeCase as et, SEARCH_UNACCENT_FN as f, buildSdkData as g, visibleColumnProjection as h, readSchemaFactsFor as i, isManyToMany as it, findRelation as j, findAnonymousGrants as k, resolveColumnName as l, hiddenColumnsOption as m, planCollectionSchemaEnsure as n, DEFAULT_ONE_OF_VALUE as nt, generatePostgresPoliciesDdl as o, buildSearchColumnSpec as p, generateForeignKeyName as q, readExistingSchema as r, hasForeignKeyOnTarget as rt, generatePostgresSearchDdl as s, ensure_collection_tables_exports as t, DEFAULT_ONE_OF_TYPE as tt, AUTH_USERS_COLUMNS as u, normalizeDriverOrderBy as v, getJunctionSecurityRules as w, relationalCollections as x, parseOrderBySpecStrict as y, isAddressableId as z };
5706
7138
 
5707
- //# sourceMappingURL=auth-users-columns-CgyPWQ18.js.map
7139
+ //# sourceMappingURL=ensure-collection-tables-jYsvOLZF.js.map