@rebasepro/server-postgres 0.23.0 → 0.24.1-canary.g19596ef

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 (208) hide show
  1. package/README.md +16 -0
  2. package/dist/{BranchService-DGPL6G_C.js → BranchService-UgKa416x.js} +47 -19
  3. package/dist/BranchService-UgKa416x.js.map +1 -0
  4. package/dist/PostgresBackendDriver.d.ts +187 -13
  5. package/dist/PostgresBootstrapper.d.ts +101 -2
  6. package/dist/auth/ensure-tables.d.ts +12 -0
  7. package/dist/auth/services.d.ts +78 -14
  8. package/dist/{auth-users-columns-D2LBFrMH.js → auth-users-columns-C72EMoDJ.js} +17 -1
  9. package/dist/{auth-users-columns-D2LBFrMH.js.map → auth-users-columns-C72EMoDJ.js.map} +1 -1
  10. package/dist/backup/backup-cli.d.ts +75 -2
  11. package/dist/backup/backup-service.d.ts +2 -2
  12. package/dist/backup/pg-binary.d.ts +5 -0
  13. package/dist/{backup-cli-24v4OlSp.js → backup-cli-yiKd1GsC.js} +168 -75
  14. package/dist/backup-cli-yiKd1GsC.js.map +1 -0
  15. package/dist/{backup-service-HQ9GC4tN.js → backup-service-AZWc2wvI.js} +104 -35
  16. package/dist/backup-service-AZWc2wvI.js.map +1 -0
  17. package/dist/boot-ddl-B9ZmBJ75.js +105 -0
  18. package/dist/boot-ddl-B9ZmBJ75.js.map +1 -0
  19. package/dist/cli-console-DGee3Zl7.js +225 -0
  20. package/dist/cli-console-DGee3Zl7.js.map +1 -0
  21. package/dist/cli-console.d.ts +39 -0
  22. package/dist/cli-errors-oVUuD8k0.js +2180 -0
  23. package/dist/cli-errors-oVUuD8k0.js.map +1 -0
  24. package/dist/cli-errors.d.ts +133 -9
  25. package/dist/{cli-flags-BglvpjHv.js → cli-flags-DnPHXjRH.js} +56 -6
  26. package/dist/cli-flags-DnPHXjRH.js.map +1 -0
  27. package/dist/cli-flags.d.ts +15 -0
  28. package/dist/cli-helpers.d.ts +88 -34
  29. package/dist/cli-output.d.ts +16 -28
  30. package/dist/{arg-Dni7MzLB.js → cli-prompt-egtNKkZl.js} +49 -3
  31. package/dist/cli-prompt-egtNKkZl.js.map +1 -0
  32. package/dist/cli-prompt.d.ts +19 -0
  33. package/dist/cli-push.d.ts +87 -0
  34. package/dist/cli-scratch-database.d.ts +33 -8
  35. package/dist/cli.js +826 -230
  36. package/dist/cli.js.map +1 -1
  37. package/dist/{column-plan-helpers-DF-8dTVa.js → column-plan-helpers-D_V4oaZh.js} +43 -27
  38. package/dist/column-plan-helpers-D_V4oaZh.js.map +1 -0
  39. package/dist/{databasePoolManager-Bj5FbeAs.js → connection-C938nR3E.js} +25 -113
  40. package/dist/connection-C938nR3E.js.map +1 -0
  41. package/dist/connection.d.ts +13 -2
  42. package/dist/data-transformer.d.ts +0 -8
  43. package/dist/databasePoolManager-DZgnBOgj.js +119 -0
  44. package/dist/databasePoolManager-DZgnBOgj.js.map +1 -0
  45. package/dist/deferred-index-build-BqmEHGaD.js +1051 -0
  46. package/dist/deferred-index-build-BqmEHGaD.js.map +1 -0
  47. package/dist/{doctor-Cb2thZ8s.js → doctor-CUAAOFZm.js} +281 -89
  48. package/dist/doctor-CUAAOFZm.js.map +1 -0
  49. package/dist/ensure-collection-policies-Bb51V2OV.js +541 -0
  50. package/dist/ensure-collection-policies-Bb51V2OV.js.map +1 -0
  51. package/dist/ensure-collection-tables-egjGBjhv.js +532 -0
  52. package/dist/ensure-collection-tables-egjGBjhv.js.map +1 -0
  53. package/dist/{ensure-tables-Dhn9KM3B.js → ensure-tables-DMPWhJ6R.js} +180 -77
  54. package/dist/ensure-tables-DMPWhJ6R.js.map +1 -0
  55. package/dist/enum-labels-kept-Msq5gMcb.js +56 -0
  56. package/dist/enum-labels-kept-Msq5gMcb.js.map +1 -0
  57. package/dist/generate-drizzle-schema-DrA-YgXv.js +57 -0
  58. package/dist/generate-drizzle-schema-DrA-YgXv.js.map +1 -0
  59. package/dist/{generate-drizzle-schema-logic-BfzK7UQd.js → generate-drizzle-schema-logic-BaT0H7Gq.js} +29 -11
  60. package/dist/generate-drizzle-schema-logic-BaT0H7Gq.js.map +1 -0
  61. package/dist/generate-files-OoRJLEdk.js +199 -0
  62. package/dist/generate-files-OoRJLEdk.js.map +1 -0
  63. package/dist/generate-postgres-ddl-logic-B4k9djAV.js +152 -0
  64. package/dist/generate-postgres-ddl-logic-B4k9djAV.js.map +1 -0
  65. package/dist/{generated-schema-staleness-Di9c4ZxN.js → generated-schema-staleness-BWN_PsMi.js} +2 -2
  66. package/dist/{generated-schema-staleness-Di9c4ZxN.js.map → generated-schema-staleness-BWN_PsMi.js.map} +1 -1
  67. package/dist/generated-sql.d.ts +34 -0
  68. package/dist/history/HistoryService.d.ts +64 -5
  69. package/dist/history/ensure-history-table.d.ts +63 -4
  70. package/dist/index.d.ts +1 -0
  71. package/dist/index.es.js +139 -16738
  72. package/dist/index.es.js.map +1 -1
  73. package/dist/{introspect-db-logic-WMuAfxvw.js → introspect-db-logic-BCc1Jym2.js} +654 -353
  74. package/dist/introspect-db-logic-BCc1Jym2.js.map +1 -0
  75. package/dist/introspect-db-queries-BhhJg0b8.js +364 -0
  76. package/dist/introspect-db-queries-BhhJg0b8.js.map +1 -0
  77. package/dist/open-rebase-CAjWpPjs.js +20895 -0
  78. package/dist/open-rebase-CAjWpPjs.js.map +1 -0
  79. package/dist/open-rebase.d.ts +94 -0
  80. package/dist/pg-error-utils-Bs_Ue4wn.js +395 -0
  81. package/dist/pg-error-utils-Bs_Ue4wn.js.map +1 -0
  82. package/dist/{plan-schema-C0fxM8dY.js → plan-schema-CsNgK0qi.js} +279 -534
  83. package/dist/plan-schema-CsNgK0qi.js.map +1 -0
  84. package/dist/{policy-drift-DljYdrpW.js → policy-drift-BKC50y2p.js} +33 -9
  85. package/dist/policy-drift-BKC50y2p.js.map +1 -0
  86. package/dist/push-index-prebuild-C_qhyoJa.js +45 -0
  87. package/dist/push-index-prebuild-C_qhyoJa.js.map +1 -0
  88. package/dist/push-legacy-renames-5g9h3HOa.js +34 -0
  89. package/dist/push-legacy-renames-5g9h3HOa.js.map +1 -0
  90. package/dist/{generate-postgres-ddl-logic-Bt2d2mRH.js → render-ddl-CrfEe__B.js} +24 -157
  91. package/dist/render-ddl-CrfEe__B.js.map +1 -0
  92. package/dist/{rls-bootstrap-sql-B8EclDyM.js → rls-bootstrap-sql-Bd-hJk08.js} +125 -1101
  93. package/dist/rls-bootstrap-sql-Bd-hJk08.js.map +1 -0
  94. package/dist/{rls-enforcement-C6Xk0lA6.js → rls-enforcement-D7P4-ZYX.js} +138 -18
  95. package/dist/rls-enforcement-D7P4-ZYX.js.map +1 -0
  96. package/dist/{__vite-browser-external-BnuHet1e.js → rolldown-runtime-V7rFbMRe.js} +1 -8
  97. package/dist/schema/atlas-exclude.d.ts +42 -0
  98. package/dist/schema/auth-schema.d.ts +172 -2
  99. package/dist/schema/boot-ddl.d.ts +47 -0
  100. package/dist/schema/carved-out-migration.d.ts +5 -4
  101. package/dist/schema/classify-change.d.ts +29 -1
  102. package/dist/schema/collection-index.d.ts +45 -0
  103. package/dist/schema/column-plan-helpers.d.ts +38 -21
  104. package/dist/schema/deferred-index-build.d.ts +131 -0
  105. package/dist/schema/destructive-sql.d.ts +50 -2
  106. package/dist/schema/doctor-cli.js +110 -6
  107. package/dist/schema/doctor-cli.js.map +1 -1
  108. package/dist/schema/doctor-search-scale.d.ts +45 -0
  109. package/dist/schema/doctor.d.ts +53 -1
  110. package/dist/schema/ensure-collection-policies.d.ts +95 -35
  111. package/dist/schema/ensure-collection-tables.d.ts +56 -3
  112. package/dist/schema/enum-labels-kept.d.ts +26 -0
  113. package/dist/schema/generate-drizzle-schema.js +1 -1
  114. package/dist/schema/generate-files.d.ts +44 -0
  115. package/dist/schema/generate-next-step.d.ts +0 -13
  116. package/dist/schema/generate-postgres-ddl-logic.d.ts +5 -5
  117. package/dist/schema/generate-postgres-ddl.js +10 -81
  118. package/dist/schema/generate-postgres-ddl.js.map +1 -1
  119. package/dist/schema/generate-schema-commit.d.ts +12 -0
  120. package/dist/schema/generated-schema-diff.d.ts +7 -0
  121. package/dist/schema/introspect-db-logic.d.ts +63 -0
  122. package/dist/schema/introspect-db-queries.d.ts +21 -2
  123. package/dist/schema/introspect-db-search.d.ts +22 -0
  124. package/dist/schema/introspect-db-storage.d.ts +83 -0
  125. package/dist/schema/introspect-db-structure.d.ts +10 -0
  126. package/dist/schema/introspect-db.js +29 -21
  127. package/dist/schema/introspect-db.js.map +1 -1
  128. package/dist/schema/migrate-baseline.d.ts +33 -0
  129. package/dist/schema/migration-post-process.d.ts +34 -0
  130. package/dist/schema/plan/diff-plan.d.ts +127 -1
  131. package/dist/schema/plan/plan-schema.d.ts +16 -8
  132. package/dist/schema/plan/render-ddl.d.ts +8 -1
  133. package/dist/schema/plan/types.d.ts +45 -11
  134. package/dist/schema/push-index-prebuild.d.ts +25 -0
  135. package/dist/schema/push-legacy-renames.d.ts +20 -0
  136. package/dist/schema/rls-bootstrap-sql.d.ts +15 -0
  137. package/dist/schema/undeclarable-constraints.d.ts +59 -0
  138. package/dist/search-column-BM-GV6vH.js +442 -0
  139. package/dist/search-column-BM-GV6vH.js.map +1 -0
  140. package/dist/security/policy-drift.d.ts +1 -1
  141. package/dist/security/rls-enforcement.d.ts +39 -4
  142. package/dist/services/BranchService.d.ts +29 -1
  143. package/dist/services/FetchService.d.ts +63 -12
  144. package/dist/services/PersistService.d.ts +15 -0
  145. package/dist/services/RelationService.d.ts +106 -3
  146. package/dist/services/cdc/CdcListener.d.ts +26 -10
  147. package/dist/services/cdc/identity-columns.d.ts +19 -0
  148. package/dist/services/cdc/trigger-cdc.d.ts +180 -18
  149. package/dist/services/channel-bus/PostgresChannelBus.d.ts +12 -7
  150. package/dist/services/channel-bus/index.d.ts +1 -1
  151. package/dist/services/channel-history.d.ts +32 -5
  152. package/dist/services/collection-helpers.d.ts +26 -1
  153. package/dist/services/field-viewer.d.ts +15 -1
  154. package/dist/services/pg-notify-listener.d.ts +95 -6
  155. package/dist/services/realtimeService.d.ts +467 -53
  156. package/dist/services/row-pipeline.d.ts +16 -9
  157. package/dist/services/socket-liveness.d.ts +85 -0
  158. package/dist/services/soft-delete.d.ts +12 -0
  159. package/dist/services/sql-script.d.ts +275 -0
  160. package/dist/services/vector-iterative-scan.d.ts +40 -0
  161. package/dist/services/write-depth.d.ts +22 -0
  162. package/dist/services/write-transaction-scope.d.ts +15 -3
  163. package/dist/socket-opt-ins.d.ts +64 -0
  164. package/dist/source-DB6xJv3p.js +873 -0
  165. package/dist/source-DB6xJv3p.js.map +1 -0
  166. package/dist/tsx-loader.d.ts +14 -0
  167. package/dist/utils/connection-string.d.ts +31 -0
  168. package/dist/utils/drizzle-conditions.d.ts +63 -3
  169. package/dist/utils/error-cause.d.ts +7 -0
  170. package/dist/utils/pg-error-utils.d.ts +2 -4
  171. package/dist/utils/result-rows.d.ts +10 -0
  172. package/dist/{vector-index-CMJKb3C9.js → vector-index-BFhyEaiz.js} +2 -2
  173. package/dist/{vector-index-CMJKb3C9.js.map → vector-index-BFhyEaiz.js.map} +1 -1
  174. package/dist/websocket-DUiiwQd2.js +1632 -0
  175. package/dist/websocket-DUiiwQd2.js.map +1 -0
  176. package/dist/websocket.d.ts +63 -18
  177. package/package.json +7 -7
  178. package/dist/BranchService-DGPL6G_C.js.map +0 -1
  179. package/dist/__vite-browser-external-BnuHet1e.js.map +0 -1
  180. package/dist/arg-Dni7MzLB.js.map +0 -1
  181. package/dist/backup-cli-24v4OlSp.js.map +0 -1
  182. package/dist/backup-service-HQ9GC4tN.js.map +0 -1
  183. package/dist/cli-errors-B8qHg02P.js +0 -798
  184. package/dist/cli-errors-B8qHg02P.js.map +0 -1
  185. package/dist/cli-flags-BglvpjHv.js.map +0 -1
  186. package/dist/cli-output-CNdMql-L.js +0 -47
  187. package/dist/cli-output-CNdMql-L.js.map +0 -1
  188. package/dist/collection-index-DdnsxiJ_.js +0 -286
  189. package/dist/collection-index-DdnsxiJ_.js.map +0 -1
  190. package/dist/column-plan-helpers-DF-8dTVa.js.map +0 -1
  191. package/dist/databasePoolManager-Bj5FbeAs.js.map +0 -1
  192. package/dist/doctor-Cb2thZ8s.js.map +0 -1
  193. package/dist/ensure-collection-policies-RHUcEp4v.js +0 -105
  194. package/dist/ensure-collection-policies-RHUcEp4v.js.map +0 -1
  195. package/dist/ensure-collection-tables-BEEjn5cn.js +0 -857
  196. package/dist/ensure-collection-tables-BEEjn5cn.js.map +0 -1
  197. package/dist/ensure-tables-Dhn9KM3B.js.map +0 -1
  198. package/dist/generate-drizzle-schema-logic-BfzK7UQd.js.map +0 -1
  199. package/dist/generate-drizzle-schema-yzY_BLhr.js +0 -123
  200. package/dist/generate-drizzle-schema-yzY_BLhr.js.map +0 -1
  201. package/dist/generate-postgres-ddl-logic-Bt2d2mRH.js.map +0 -1
  202. package/dist/introspect-db-logic-WMuAfxvw.js.map +0 -1
  203. package/dist/plan-schema-C0fxM8dY.js.map +0 -1
  204. package/dist/policy-drift-DljYdrpW.js.map +0 -1
  205. package/dist/rls-bootstrap-sql-B8EclDyM.js.map +0 -1
  206. package/dist/rls-enforcement-C6Xk0lA6.js.map +0 -1
  207. package/dist/source-Br7L7GOI.js +0 -410
  208. package/dist/source-Br7L7GOI.js.map +0 -1
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plan-schema-CsNgK0qi.js","names":[],"sources":["../src/schema/relation-names.ts","../src/schema/plan/updated-at-trigger.ts","../src/schema/plan/plan-schema.ts"],"sourcesContent":["import { CollectionConfig, ResolvedRelation } from \"@rebasepro/types\";\nimport { fieldKeyForColumn, getTableName } from \"@rebasepro/common\";\nimport { toSnakeCase } from \"@rebasepro/utils\";\n\n/**\n * The `relationName` both sides of a link must agree on.\n *\n * Drizzle pairs an owning `one()` with its inverse `many()` by this string and\n * by nothing else, and the two sides are computed by different callers holding\n * different collections — so the rule has to be derivable from either end. It\n * is: name the link after the table that carries the foreign key and the field\n * key that column is served under.\n *\n * owning (belongsTo) → `{thisTable}_{fieldKey(localKey)}`\n * inverse (hasMany/hasOne) → `{targetTable}_{fieldKey(foreignKeyOnTarget)}`\n *\n * Both spellings of `jobs.company` produce `jobs_companyId`. A many-to-many is\n * named through its junction wiring and a `via` chain is not a Drizzle relation\n * at all, so both keep the local name.\n *\n * The field key rather than the column: the Drizzle object is keyed by the wire\n * name (`fieldKeyForColumn` is the one definition of it), and a name built from\n * the column would differ between a collection that declares `columnName` and\n * one that does not, for the same link.\n *\n * **This is the one definition.** `generate-drizzle-schema-logic.ts` holds a\n * private copy (`computeSharedRelationName`) that this was lifted from verbatim;\n * the generator should import this instead, so the file it writes and the\n * relations the runtime builds from the live catalogue cannot drift apart.\n */\nexport function sharedRelationName(\n relation: ResolvedRelation,\n sourceCollection: CollectionConfig\n): string {\n const fallback = (): string => {\n if (relation.relationName) return relation.relationName;\n try {\n return toSnakeCase(relation.target().slug);\n } catch {\n return relation.relationName;\n }\n };\n\n if (relation.kind === \"belongsTo\") {\n return `${getTableName(sourceCollection)}_${fieldKeyForColumn(sourceCollection, relation.localKey)}`;\n }\n\n if (relation.kind === \"hasMany\" || relation.kind === \"hasOne\") {\n try {\n const target = relation.target();\n return `${getTableName(target)}_${fieldKeyForColumn(target, relation.foreignKeyOnTarget)}`;\n } catch {\n return fallback();\n }\n }\n\n return fallback();\n}\n","/**\n * `autoValue: \"on_update\"`, as a database trigger.\n *\n * ## Why a trigger and not only the driver\n *\n * The driver stamps `updated_at` on every write it makes, and it has to keep\n * doing so: a `beforeSave` hook reads the row it is about to persist, and a\n * value that only appears once Postgres has written it is a value the hook\n * cannot see.\n *\n * But the driver is not the only writer. A seed script, a migration backfill, a\n * `psql` session, an admin fixing one row by hand — every one of those leaves\n * `updated_at` at whatever it was, and the column then says the row has not\n * changed since a date that is simply wrong. Anything reading it to decide what\n * to re-index, re-sync or re-send skips the row. The declaration says \"this\n * column tracks the last write\"; only the database can make that true.\n *\n * So both: the app stamps so hooks see the value, and a `BEFORE UPDATE` trigger\n * stamps so every other writer does too. They agree because they both mean\n * \"now\" — the trigger's `now()` is the transaction's start time, which is the\n * same instant the driver's own timestamp is written under.\n *\n * ## Why it is carved out of Atlas\n *\n * Atlas's free tier refuses to *parse* a desired-state file containing a\n * function (\"functions and procedures are available to logged-in users only\"),\n * and a trigger is a function plus a binding. This is the same arrangement the\n * search helpers already have: excluded from Atlas's view by pattern, written\n * to a file Rebase applies itself, applied at boot by the schema ensure, and\n * appended to migrations by the CLI so a replay is complete.\n *\n * ## Why one function for every column\n *\n * The column name arrives as a trigger argument (`TG_ARGV[0]`), so a project\n * with forty `updated_at` columns installs one function and forty bindings\n * rather than forty near-identical functions. `jsonb_populate_record(NEW, …)`\n * is the documented way to set a field of a row whose type is not known until\n * run time; it takes `NEW` as the row template, so the value is cast back to\n * whatever the column actually is — a `timestamptz`, or a `date` if that is\n * what the property declared.\n */\nimport { REBASE_SCHEMA } from \"@rebasepro/types\";\nimport type { TriggerPlan } from \"./types\";\n\n/** The one trigger function, qualified. A frozen derived name. */\nexport const SET_UPDATED_AT_FN = `${REBASE_SCHEMA}.set_updated_at`;\n\n/**\n * `CREATE OR REPLACE FUNCTION`, so replaying it against a database that already\n * has it is a no-op — this is emitted into a file that runs on every push and\n * is appended to migrations that run against databases at any stage of life.\n */\nexport const setUpdatedAtFunction = (): string =>\n `CREATE OR REPLACE FUNCTION ${SET_UPDATED_AT_FN}() RETURNS trigger\\n` +\n \"LANGUAGE plpgsql AS $$\\n\" +\n \"BEGIN\\n\" +\n \" NEW := jsonb_populate_record(NEW, jsonb_build_object(TG_ARGV[0], now()));\\n\" +\n \" RETURN NEW;\\n\" +\n \"END;\\n\" +\n \"$$;\";\n\n/**\n * `DROP TRIGGER IF EXISTS` before the `CREATE`.\n *\n * `CREATE OR REPLACE TRIGGER` exists only on Postgres 14+, and this runs\n * against whatever a self-hosted project points at. Two statements rather than\n * one is also what the boot-time applier needs: it issues DDL one statement at\n * a time over the extended query protocol, which forbids multiple commands in\n * one execute.\n */\nexport const dropTriggerStatement = (plan: TriggerPlan): string =>\n `DROP TRIGGER IF EXISTS \"${plan.name}\" ON \"${plan.schema}\".\"${plan.table}\";`;\n\nexport const createTriggerStatement = (plan: TriggerPlan): string =>\n `CREATE TRIGGER \"${plan.name}\" BEFORE UPDATE ON \"${plan.schema}\".\"${plan.table}\" ` +\n `FOR EACH ROW EXECUTE FUNCTION ${SET_UPDATED_AT_FN}('${plan.column.replace(/'/g, \"''\")}');`;\n\n/** Both statements, in the order they must run. */\nexport const triggerStatements = (plan: TriggerPlan): string[] =>\n [dropTriggerStatement(plan), createTriggerStatement(plan)];\n","/**\n * The only place a `Property` is read for schema purposes.\n *\n * Everything that used to `switch (prop.type)` — the Drizzle generator, the DDL\n * generator, the boot-time ensure — now renders the {@link SchemaPlan} this\n * produces. See `./types.ts` for why.\n *\n * Pure and synchronous. It reads collections and nothing else: no database, no\n * filesystem, no process-wide registry. The one fact about the world it needs\n * (which extensions a project's databases gave Rebase leave to install) is\n * passed in, because a plan that depended on whatever module happened to be\n * imported would not be comparable with itself.\n *\n * It **throws** rather than guessing. A configuration no renderer can honour —\n * an empty enum, `isId: \"cuid\"`, a foreign key into a composite key, an unknown\n * `columnType`, a relation to a collection that is not in the bundle, a\n * `search` block on a collection Postgres does not store — used to fail\n * differently in each of the three emitters, or not at all, and the failure\n * landed at `CREATE TABLE` time on a managed tenant with nobody in the loop.\n * Here it lands once, with the collection and the property named.\n */\nimport {\n isPostgresCollectionConfig,\n isManyToMany,\n REBASE_SCHEMA,\n type ArrayProperty,\n type CollectionConfig,\n type DateProperty,\n type MapProperty,\n type NumberProperty,\n type Properties,\n type Property,\n type ReferenceProperty,\n type RelationProperty,\n type ResolvedForeignKeyOnTarget,\n type ResolvedRelation,\n type SecurityRule,\n type StringProperty,\n type VectorProperty,\n hasForeignKeyOnTarget\n} from \"@rebasepro/types\";\nimport {\n fieldKeyForColumn,\n findRelation,\n getEffectiveSecurityRules,\n getEnumVarName,\n getInjectedSecurityRules,\n getTenantConfig,\n TENANT_INDEX_REASON,\n getJunctionCollectionConfig,\n getJunctionSecurityRules,\n getTableName,\n type JunctionSpec,\n getTableVarName,\n compileRulePolicies,\n relationalCollections,\n resolveCollectionRelations,\n resolveJunctionSpecs,\n assertBeforeQueryIsPostgresOnly,\n resolveStringColumnLength\n} from \"@rebasepro/common\";\nimport {\n generateForeignKeyName,\n getPolicySupersededNames,\n legacyForeignKeyName,\n toPostgresIdentifier,\n toSnakeCase\n} from \"@rebasepro/utils\";\nimport {\n declaresEnumType,\n enumLabelsOf,\n getPrimaryKeyColumn,\n getPrimaryKeyName,\n getPrimaryKeyProp,\n idColumnDefault,\n isIdProperty,\n primaryKeyPropertyNames,\n resolveColumnName,\n sqlExpressionText\n} from \"../column-plan-helpers\";\nimport {\n AUTH_USERS_COLUMNS,\n authUsersColumnDefinition,\n authUsersColumnSql,\n isAuthCollection\n} from \"../auth-users-columns\";\nimport {\n assertSearchIsPostgresOnly,\n buildSearchColumnSpec,\n searchColumnTypeSql,\n searchExtensionStatements,\n searchHelperFunctions\n} from \"../search-column\";\nimport {\n buildVectorColumnSpecs,\n buildVectorIndexPlan,\n vectorExtensionDeclared,\n vectorExtensionStatement\n} from \"../vector-index\";\nimport {\n buildCollectionIndexSpecs,\n deriveIndexName,\n foreignKeyIndexSpec,\n leadsWithColumn,\n type CollectionIndexSpec\n} from \"../collection-index\";\nimport { sharedRelationName } from \"../relation-names\";\nimport { SET_UPDATED_AT_FN, setUpdatedAtFunction } from \"./updated-at-trigger\";\nimport type {\n ColumnDefault,\n ColumnPlan,\n EnumPlan,\n ForeignKeyPlan,\n PgType,\n PlanOptions,\n PolicyPlan,\n RelationPlan,\n SchemaPlan,\n TablePlan,\n TriggerPlan\n} from \"./types\";\n\ntype ResolveCollection = (slug: string) => CollectionConfig | undefined;\n\n/**\n * Single-quote escaping for a SQL string literal (PostgreSQL doubles the\n * quote). Enum labels and `defaultValue`s come straight from user-authored\n * collection config, so a value like `it's` closes the literal early and the\n * whole generated file stops parsing at that statement.\n */\nexport const quoteSqlLiteral = (value: string): string => `'${value.replace(/'/g, \"''\")}'`;\n\nconst schemaOf = (collection: CollectionConfig): string =>\n isPostgresCollectionConfig(collection) && collection.schema ? collection.schema : \"public\";\n\nconst bareTableName = (name: string): string => (name.includes(\".\") ? name.split(\".\").pop()! : name);\n\nconst describe = (collection: CollectionConfig): string =>\n collection.slug ?? collection.name ?? \"(unnamed)\";\n\n/** A link, as a refusal names it: `relation \"posts.author\"`. */\nconst linkLabel = (kind: string, collection: CollectionConfig, key: string): string =>\n `${kind} \"${describe(collection)}.${key}\"`;\n\n/**\n * The `ON DELETE` a foreign key gets when the author did not say.\n *\n * One rule for every property that emits one — `belongsTo` relations and\n * `reference` properties alike. `reference` kept its own `CASCADE` default for\n * a release after this rule was written, which is exactly the split this\n * function exists to prevent: two spellings of the same link, two\n * data-retention behaviours, and only one of them documented.\n *\n * An optional link is `SET NULL`: the column can hold NULL, so dropping the\n * parent leaves the child row with an empty pointer, which is what \"optional\"\n * already means.\n *\n * A required link is **`RESTRICT`**, not `CASCADE`. `NOT NULL` says the child\n * cannot exist without a parent; it does not say deleting the parent should\n * take the child with it. That second claim is a data-retention decision, and\n * defaulting to it meant `onDelete` — a field nobody has to write — silently\n * turned every `DELETE FROM authors` into a cascade through posts, comments and\n * anything else that hung off them.\n */\nexport const defaultBelongsToOnDelete = (required: boolean | undefined): \"RESTRICT\" | \"SET NULL\" =>\n required ? \"RESTRICT\" : \"SET NULL\";\n\nconst foreignKeyPlan = (\n args: Omit<ForeignKeyPlan, \"constraintName\" | \"sql\">\n): ForeignKeyPlan => {\n const constraintName = toPostgresIdentifier(`${args.table}_${args.column}_fkey`);\n const onUpdate = args.onUpdate ? ` ON UPDATE ${args.onUpdate.toUpperCase()}` : \"\";\n return {\n ...args,\n constraintName,\n sql:\n `ALTER TABLE \"${args.schema}\".\"${args.table}\" ADD CONSTRAINT \"${constraintName}\" ` +\n `FOREIGN KEY (\"${args.column}\") REFERENCES \"${args.targetSchema}\".\"${args.targetTable}\" ` +\n `(\"${args.targetColumn}\") ON DELETE ${args.onDelete.toUpperCase()}${onUpdate}`\n };\n};\n\n// ── Types ────────────────────────────────────────────────────────────────────\n\n/**\n * The type a column pointing at this collection's primary key must have — a\n * junction endpoint, a `belongsTo` foreign key, a `reference`.\n *\n * The key's own column type, from the same functions that typed the key: a\n * foreign key only holds between compatible types, so any second reading of\n * the key is a constraint waiting to fail. This used to look at the id\n * strategy alone, so `id: { isId: true, columnType: \"uuid\" }` was referenced by\n * TEXT columns (42804 — the constraint was never created) and a `bigint` key by\n * INTEGER ones, which overflow at 2^31.\n *\n * One exception: a `serial` key owns a sequence, and a column pointing at it is\n * the plain integer of the same width — `SERIAL` there would give every\n * referencing column a sequence of its own.\n *\n * @param via the link asking, named if the collection's key is composite and so\n * cannot be pointed at — see `getPrimaryKeyProp`.\n */\nexport const primaryKeyPgType = (collection: CollectionConfig, via: string): PgType => {\n const { name, prop } = getPrimaryKeyProp(collection, via);\n if (prop?.type === \"number\") {\n return withoutSequence(numberType(name, prop as NumberProperty, collection, true));\n }\n if (prop?.type === \"string\") return stringType(name, prop as StringProperty, collection);\n // The implicit `id TEXT PRIMARY KEY` of a collection that declares no key.\n return { kind: \"text\" };\n};\n\n/**\n * The DEFAULT of the implicit `id TEXT PRIMARY KEY` a collection that declares\n * no key gets: a uuid, as text. The same generator `isId: \"uuid\"` uses, cast to\n * the column the key has always been.\n */\nexport const IMPLICIT_ID_DEFAULT = \"gen_random_uuid()::text\";\n\nconst withoutSequence = (type: PgType): PgType => {\n switch (type.kind) {\n case \"smallserial\": return { kind: \"smallint\" };\n case \"serial\": return { kind: \"integer\" };\n case \"bigserial\": return { kind: \"bigint\" };\n default: return type;\n }\n};\n\n/** The `columnType`s a `number` property may name, and what each one is. */\nconst NUMBER_COLUMN_TYPES: Record<string, PgType> = {\n \"integer\": { kind: \"integer\" },\n // `smallint` and `smallserial` are not in `NumberProperty[\"columnType\"]`'s\n // union, and both have always reached the database: the DDL generator\n // upper-cased whatever it was given. Accepted here for that reason and\n // listed on purpose — the point of this table is that an unrecognised\n // `columnType` is refused with the property named rather than passed\n // through to fail at `CREATE TABLE`.\n \"smallint\": { kind: \"smallint\" },\n \"smallserial\": { kind: \"smallserial\" },\n \"bigint\": { kind: \"bigint\" },\n \"serial\": { kind: \"serial\" },\n \"bigserial\": { kind: \"bigserial\" },\n \"real\": { kind: \"real\" },\n \"double precision\": { kind: \"doublePrecision\" },\n \"numeric\": { kind: \"numeric\" }\n};\n\nconst numberType = (propName: string, prop: NumberProperty, collection: CollectionConfig, isId: boolean): PgType => {\n // An identity column is INTEGER, and `columnType` is not read beside it, on\n // purpose: it is the type every identity key in the field was created with,\n // and every column pointing at one takes it too (`primaryKeyPgType`). The\n // Drizzle generator used to honour `columnType` here while the DDL one\n // ignored it, so the same property was int8 in `schema.generated.ts` and\n // int4 in the database; with `columnType: \"bigserial\"` the emitted\n // `.generatedByDefaultAsIdentity()` is not a method that exists, so the file\n // did not compile at all.\n if (prop.isId === \"increment\") return { kind: \"integer\" };\n if (prop.columnType) {\n const mapped = NUMBER_COLUMN_TYPES[prop.columnType];\n if (!mapped) {\n throw new Error(\n `Property \"${propName}\" of collection \"${describe(collection)}\" declares ` +\n `\\`columnType: \"${prop.columnType}\"\\`, which is not a Postgres numeric type Rebase emits. ` +\n `Use one of: ${Object.keys(NUMBER_COLUMN_TYPES).join(\", \")}.`\n );\n }\n if (mapped.kind === \"numeric\") return numericType(prop);\n return mapped;\n }\n if (prop.validation?.integer || isId) return { kind: \"integer\" };\n return numericType(prop);\n};\n\n/** `NUMERIC`, with the precision and scale the property asks for. */\nconst numericType = (prop: NumberProperty): PgType => {\n const { precision, scale } = prop;\n if (precision === undefined) return { kind: \"numeric\" };\n return scale === undefined\n ? { kind: \"numeric\", precision }\n : { kind: \"numeric\", precision, scale };\n};\n\nconst stringType = (propName: string, prop: StringProperty, collection: CollectionConfig): PgType => {\n if (prop.enum) {\n // Throws on an empty list rather than naming a type nothing creates —\n // the column type and the `CREATE TYPE` come from one reading of `enum`.\n const labels = enumLabelsOf(propName, prop as Property, collection);\n const table = getTableName(collection);\n const column = resolveColumnName(propName, prop as Property);\n return {\n kind: \"enum\",\n schema: schemaOf(collection),\n name: `${table}_${column}`,\n varName: getEnumVarName(table, propName),\n labels\n };\n }\n if (prop.isId === \"uuid\" || prop.columnType === \"uuid\") return { kind: \"uuid\" };\n // The width comes from `validation.max` when the property states one. It\n // used to be a hardcoded 255 in the DDL generator and *absent* on the\n // Drizzle path, so the same property produced a bounded column down one\n // generator and an unbounded one down the other.\n if (prop.columnType === \"char\") return { kind: \"char\", length: resolveStringColumnLength(prop) };\n if (prop.columnType === \"varchar\") return { kind: \"varchar\", length: resolveStringColumnLength(prop) };\n if (prop.columnType !== undefined && prop.columnType !== \"text\") {\n throw new Error(\n `Property \"${propName}\" of collection \"${describe(collection)}\" declares ` +\n `\\`columnType: \"${String(prop.columnType)}\"\\`, which is not a Postgres string type Rebase emits. ` +\n \"Use one of: text, varchar, char, uuid.\"\n );\n }\n // `text` is the default, and the only length-unbounded choice.\n return { kind: \"text\" };\n};\n\nconst arrayElementType = (prop: ArrayProperty): PgType | undefined => {\n let colType = prop.columnType;\n if (!colType && prop.of && !Array.isArray(prop.of)) {\n const of = prop.of as Property;\n if (of.type === \"string\") colType = \"text[]\";\n else if (of.type === \"number\") colType = of.validation?.integer ? \"integer[]\" : \"numeric[]\";\n else if (of.type === \"boolean\") colType = \"boolean[]\";\n }\n switch (colType) {\n case \"text[]\": return { kind: \"text\" };\n case \"integer[]\": return { kind: \"integer\" };\n case \"boolean[]\": return { kind: \"boolean\" };\n case \"numeric[]\": return { kind: \"numeric\" };\n default: return undefined;\n }\n};\n\n/**\n * The Postgres type a property's column has.\n *\n * Every member of `DataType` has an arm. A type this does not know is a\n * generator that has not been taught it yet, and it says so — a silent `TEXT`\n * here is how `geopoint` ended up with a database column, no Drizzle key, and\n * every write to it discarded with a 201.\n */\nexport const columnPgType = (\n propName: string,\n prop: Property,\n collection: CollectionConfig,\n resolveCollection: ResolveCollection\n): PgType => {\n switch (prop.type) {\n case \"string\":\n return stringType(propName, prop as StringProperty, collection);\n case \"number\":\n return numberType(propName, prop as NumberProperty, collection, isIdProperty(propName, prop, collection));\n case \"boolean\":\n return { kind: \"boolean\" };\n case \"date\": {\n const dateProp = prop as DateProperty;\n if (dateProp.columnType === \"date\") return { kind: \"date\" };\n if (dateProp.columnType === \"time\") return { kind: \"time\" };\n return { kind: \"timestamptz\" };\n }\n case \"map\":\n return (prop as MapProperty).columnType === \"json\" ? { kind: \"json\" } : { kind: \"jsonb\" };\n // `{ latitude, longitude }` — a document, like `map`.\n case \"geopoint\":\n return { kind: \"jsonb\" };\n case \"array\": {\n const arrayProp = prop as ArrayProperty;\n if (arrayProp.columnType === \"json\") return { kind: \"json\" };\n const element = arrayElementType(arrayProp);\n return element ? { kind: \"array\", of: element } : { kind: \"jsonb\" };\n }\n case \"vector\":\n return { kind: \"vector\", dimensions: (prop as VectorProperty).dimensions };\n case \"binary\":\n return { kind: \"bytea\" };\n case \"relation\":\n case \"reference\": {\n const target = linkTarget(propName, prop, collection, resolveCollection);\n return target ? primaryKeyPgType(target, linkLabel(prop.type, collection, propName)) : { kind: \"text\" };\n }\n default:\n throw new Error(\n `No Postgres column type for property \"${propName}\" of type ` +\n `\"${(prop as Property).type}\" in collection \"${describe(collection)}\". ` +\n \"Add a case to `columnPgType` in schema/plan/plan-schema.ts.\"\n );\n }\n};\n\n/** The collection a `relation` or `reference` points at, when it resolves. */\nconst linkTarget = (\n propName: string,\n prop: Property,\n collection: CollectionConfig,\n resolveCollection: ResolveCollection\n): CollectionConfig | undefined => {\n if (prop.type === \"reference\") {\n const path = (prop as ReferenceProperty).path;\n return path ? resolveCollection(path) : undefined;\n }\n const relProp = prop as RelationProperty;\n const relation = findRelation(resolveCollectionRelations(collection), relProp.relation?.relationName ?? propName);\n if (relation?.kind !== \"belongsTo\") return undefined;\n try {\n return relation.target();\n } catch {\n return undefined;\n }\n};\n\n// ── Defaults ─────────────────────────────────────────────────────────────────\n\n/** A value already written as a day, or as a time of day. */\nconst BARE_DATE = /^\\d{4}-\\d{2}-\\d{2}$/;\nconst BARE_TIME = /^\\d{2}:\\d{2}(:\\d{2}(\\.\\d+)?)?$/;\n\n/**\n * A date default in the shape its column's type accepts: a day for `date`, a\n * time of day for `time`, an instant for a timestamp.\n *\n * Rendered as a full ISO timestamp whatever the column, a `time` column's\n * DEFAULT was `'2024-03-01T15:30:00.000Z'`, which Postgres refuses — \"invalid\n * input syntax for type time\" — so the schema would not apply. A value already\n * written in the column's shape (`\"2024-12-25\"`, `\"09:00\"`) is kept as written;\n * anything else is read as an instant and its UTC day or time of day taken, the\n * same zone the timestamp rendering has always used. A string that is no date\n * at all is no DEFAULT, rather than a schema that cannot be applied.\n */\nconst dateDefaultLiteral = (value: unknown, type: PgType): string | undefined => {\n if (typeof value === \"string\") {\n if (type.kind === \"date\" && BARE_DATE.test(value)) return value;\n if (type.kind === \"time\" && BARE_TIME.test(value)) return value;\n }\n const instant = value instanceof Date ? value : typeof value === \"string\" ? new Date(value) : undefined;\n if (!instant || Number.isNaN(instant.getTime())) return undefined;\n const iso = instant.toISOString();\n if (type.kind === \"date\") return iso.slice(0, 10);\n if (type.kind === \"time\") return iso.slice(11, 23);\n // A timestamp keeps a string default as it was written — its own offset\n // included — and a Date as the instant it is.\n return typeof value === \"string\" ? value : iso;\n};\n\n/**\n * A `defaultValue` as a database DEFAULT.\n *\n * `defaultValue` reached neither the schema nor — until recently — the write\n * path, so a documented, type-checked field did nothing at all: the panel\n * pre-filled it, an insert from anywhere else (the REST API, a seed, psql) did\n * not, and the column came out NULL. A DEFAULT is the one place that binds\n * every writer.\n *\n * Only literals. A `reference`'s default is an `EntityReference` object and a\n * `vector`'s is an embedding — neither is a column literal, and inventing one\n * would be a value the write path and the database disagree about.\n * `undefined` for those, which is what \"no DEFAULT\" means everywhere here.\n */\nconst literalDefault = (prop: Property, type: PgType): ColumnDefault | undefined => {\n const value = (prop as { defaultValue?: unknown }).defaultValue;\n if (value === undefined || value === null) return undefined;\n\n switch (prop.type) {\n case \"string\":\n // An enum default is its id, which is exactly the label the type\n // carries — the same string the write path stores.\n return typeof value === \"string\" ? { kind: \"literal\", value, sql: quoteSqlLiteral(value) } : undefined;\n case \"number\":\n return typeof value === \"number\" && Number.isFinite(value)\n ? { kind: \"literal\", value, sql: String(value) }\n : undefined;\n case \"boolean\":\n return typeof value === \"boolean\"\n ? { kind: \"literal\", value, sql: value ? \"TRUE\" : \"FALSE\" }\n : undefined;\n case \"date\": {\n const literal = dateDefaultLiteral(value, type);\n return literal === undefined ? undefined : { kind: \"literal\", value: literal, sql: quoteSqlLiteral(literal) };\n }\n case \"map\":\n case \"array\":\n case \"geopoint\": {\n // A document default is a JSON literal cast to the column's own\n // type, so `jsonb` and `json` columns each get a value Postgres\n // accepts without an implicit cast.\n if (type.kind !== \"jsonb\" && type.kind !== \"json\") return undefined;\n let json: string;\n try {\n json = JSON.stringify(value);\n } catch {\n return undefined; // circular, or a BigInt: not a literal.\n }\n if (json === undefined) return undefined;\n return { kind: \"literal\", value, sql: `${quoteSqlLiteral(json)}::${type.kind}` };\n }\n default:\n return undefined;\n }\n};\n\n/**\n * The DEFAULT a column carries: id strategy, then `autoValue`, then a\n * `databaseDefault` given as SQL, then `defaultValue`.\n */\nconst columnDefault = (\n propName: string,\n prop: Property,\n collection: CollectionConfig,\n type: PgType\n): ColumnDefault | undefined => {\n const idDefault = idColumnDefault(propName, prop, collection);\n if (idDefault) {\n if (idDefault.kind === \"uuid\") return { kind: \"sql\", expression: \"gen_random_uuid()\" };\n if (idDefault.kind === \"identity\") return { kind: \"identity\" };\n return { kind: \"sql\", expression: idDefault.expression };\n }\n if (prop.type === \"date\") {\n const autoValue = (prop as DateProperty).autoValue;\n if (autoValue === \"on_create\" || autoValue === \"on_update\") {\n return { kind: \"sql\", expression: \"now()\" };\n }\n }\n if (typeof prop.databaseDefault === \"string\") {\n const expression = sqlExpressionText(prop.databaseDefault).trim();\n if (expression) return { kind: \"sql\", expression };\n }\n return literalDefault(prop, type);\n};\n\n// ── Policies ─────────────────────────────────────────────────────────────────\n\n/**\n * One security rule, compiled to the clauses a policy is made of.\n *\n * The compilation is `compileRulePolicies` in `@rebasepro/common`, which the\n * Studio's RLS editor also compares the live database against — so the UI, the\n * DDL and the database agree about who can read a row. What this adds is only\n * the planner's bookkeeping: which rule a policy came from, and whether Rebase\n * injected it.\n */\nexport const compileSecurityRule = (\n collection: CollectionConfig,\n rule: SecurityRule,\n resolveCollection: ResolveCollection,\n injected: boolean,\n ruleKey = rule.name ?? \"(unnamed)\"\n): PolicyPlan[] => {\n const supersedes = getPolicySupersededNames(rule, getTableName(collection));\n return compileRulePolicies(collection, rule, { resolveCollection })\n .map((policy, index) => ({\n ...policy,\n ruleKey,\n injected,\n ...(index === 0 && supersedes.length > 0 ? { supersedes } : {})\n }));\n};\n\n/**\n * A table's policies, minus any superseded name Postgres would store one of\n * them under.\n *\n * A superseded name is a 63-byte cut, and another rule's over-long name can\n * cut to the very same bytes — `<table>_default_admin_read` and the old\n * `<table>_default_admin_write_<op>` both do on a 50-byte table. Dropping it\n * then would drop that policy right after its own `CREATE`. Its own `DROP\n * POLICY IF EXISTS` already removes the old policy, so nothing is lost.\n */\nconst withoutLiveSupersededNames = (policies: PolicyPlan[]): PolicyPlan[] => {\n const stored = new Set(policies.map(policy => toPostgresIdentifier(policy.name)));\n return policies.map(policy => {\n if (!policy.supersedes) return policy;\n const supersedes = policy.supersedes.filter(name => !stored.has(name));\n if (supersedes.length === policy.supersedes.length) return policy;\n const kept: PolicyPlan = { ...policy };\n if (supersedes.length > 0) kept.supersedes = supersedes;\n else delete kept.supersedes;\n return kept;\n });\n};\n\n// ── The planner ──────────────────────────────────────────────────────────────\n\nexport function planSchema(allCollections: CollectionConfig[], options: PlanOptions = {}): SchemaPlan {\n // Before the filter, deliberately: a `search` block on a collection this\n // engine does not store would otherwise be dropped without a word.\n assertSearchIsPostgresOnly(allCollections);\n // Same reasoning, and a stronger reason to be loud: a `beforeQuery` that\n // does nothing is a row filter that does nothing.\n assertBeforeQueryIsPostgresOnly(allCollections);\n\n // A Firestore or MongoDB collection has no table here, and generating one\n // is not merely wasted output: `db push` would create it, and the doctor\n // would then report the store the collection actually reads from as drift.\n const collections = relationalCollections(allCollections);\n\n // The first collection, in declaration order, whose slug or table name is\n // the key — what `collections.find(...)` answered, from a map built once\n // rather than a scan per lookup.\n const firstBySlugOrTable = new Map<string, CollectionConfig>();\n for (const c of collections) {\n for (const key of [c.slug, getTableName(c)]) {\n if (!firstBySlugOrTable.has(key)) firstBySlugOrTable.set(key, c);\n }\n }\n const resolveCollection: ResolveCollection = (slug) => firstBySlugOrTable.get(slug);\n\n const declaredSchemas = Array.from(new Set(\n collections.map(c => (isPostgresCollectionConfig(c) ? c.schema : undefined)).filter(Boolean) as string[]\n ));\n const schemas = Array.from(new Set([REBASE_SCHEMA, ...declaredSchemas]));\n\n // ── Enum types ───────────────────────────────────────────────────────────\n // The name is derived from table + column, so two collections mapped onto\n // the same table — or two properties whose `columnName` resolves to the\n // same column — land on the same type. `CREATE TYPE` has no IF NOT EXISTS,\n // so emitting it twice aborts the whole file; deduplicated by name here,\n // once, for every renderer.\n //\n // Deduplicated by the name *Postgres* holds, which is the first 63 bytes,\n // and only for the same column. Two different columns deriving one name —\n // `orders.item_status` and `orders_item.status`, or two long names that\n // share their first 63 bytes — cannot share a type: the second column would\n // be typed with the first one's labels and reject its own. The name is\n // frozen once a database holds it, so this refuses rather than renaming.\n const enums: EnumPlan[] = [];\n const enumOwners = new Map<string, { table: string; column: string; owner: string }>();\n /**\n * Every enum type one table's properties declare, appended once.\n *\n * `declaredSchema` is passed rather than read off the config because a\n * junction's synthetic collection carries `schema: \"public\"` as a resolved\n * fact and not as a declaration — emitting it as one would make the\n * generated file reach for a `pgSchema(\"public\")` variable that exists for\n * nobody.\n */\n const collectEnumTypes = (\n collection: CollectionConfig,\n properties: Properties,\n declaredSchema: string | undefined\n ): void => {\n for (const [propName, rawProp] of Object.entries(properties)) {\n const prop = rawProp as Property;\n if (!(\"enum\" in prop) || !prop.enum) continue;\n if (prop.type !== \"string\" && prop.type !== \"number\") continue;\n // Refuses an empty list whatever the property's type, then declares\n // a type only for the string ones: a `number` enum's column is\n // NUMERIC or INTEGER on every path, so the type used to be created\n // and referenced by nothing.\n const labels = enumLabelsOf(propName, prop, collection);\n if (!declaresEnumType(prop)) continue;\n const schema = schemaOf(collection);\n const table = getTableName(collection);\n const column = resolveColumnName(propName, prop);\n const name = `${table}_${column}`;\n const qualified = `${schema}.${toPostgresIdentifier(name)}`;\n const owner = `property \"${propName}\" of collection \"${describe(collection)}\"`;\n const claimed = enumOwners.get(qualified);\n if (claimed) {\n if (claimed.table === table && claimed.column === column) continue;\n throw new Error(\n `The enum ${owner} and the enum ${claimed.owner} both derive the Postgres type ` +\n `\"${schema}\".\"${toPostgresIdentifier(name)}\" (an enum type is named <table>_<column>, ` +\n \"and Postgres keeps the first 63 bytes). One type cannot hold both columns' values, and a \" +\n \"type's name cannot change once a database holds it. Give one of the two columns another \" +\n \"name with `columnName`, or its collection another `table` or `schema`.\"\n );\n }\n enumOwners.set(qualified, { table, column, owner });\n enums.push({\n schema,\n name,\n qualified,\n declaredSchema,\n varName: getEnumVarName(getTableName(collection), propName),\n labels\n });\n }\n };\n for (const collection of collections) {\n collectEnumTypes(\n collection,\n (collection.properties ?? {}) as Properties,\n isPostgresCollectionConfig(collection) ? collection.schema : undefined\n );\n }\n\n // ── The table set ────────────────────────────────────────────────────────\n // Each collection's table, then any junction its relations imply, in the\n // order the collections arrive. `schema.sql` and `schema.generated.ts` are\n // compared against their committed copies, so this order is the file's.\n const tableEntries = new Map<string, {\n collection: CollectionConfig;\n junction?: { relation: ResolvedRelation; source: CollectionConfig };\n }>();\n const junctionShapes = new Map<string, { keys: string; claim: string }>();\n for (const collection of collections) {\n const tableName = getTableName(collection);\n if (tableName) tableEntries.set(tableName, { collection });\n for (const [relationKey, relation] of Object.entries(resolveCollectionRelations(collection))) {\n if (!isManyToMany(relation)) continue;\n const junctionTable = relation.through.table;\n assertOneJunctionShape(junctionShapes, junctionTable, collection, relation.relationName ?? relationKey, relation);\n if (tableEntries.has(junctionTable)) continue;\n tableEntries.set(junctionTable, {\n collection: { table: junctionTable, properties: {} } as CollectionConfig,\n junction: { relation, source: collection }\n });\n }\n }\n\n const junctionSpecs = resolveJunctionSpecs(collections);\n\n // A junction's payload may declare an enum too, and the type it names is\n // created by nobody else: no collection owns the table, so the loop above\n // never sees the property. Appended after the collections' — the enum block\n // is compared byte-for-byte against its committed copy, and a junction\n // whose type interleaved with the collections' would reorder that file\n // whenever a `through.properties` block moved.\n for (const spec of junctionSpecs.values()) {\n if (Object.keys(spec.properties).length === 0) continue;\n collectEnumTypes(getJunctionCollectionConfig(spec), spec.properties, undefined);\n }\n\n const triggerFunctionNeeded = { value: false };\n\n const tables: TablePlan[] = [];\n for (const [tableName, entry] of tableEntries) {\n tables.push(entry.junction\n ? planJunctionTable(\n tableName, entry.junction.relation, entry.junction.source,\n junctionSpecs, resolveCollection, triggerFunctionNeeded)\n : planCollectionTable(entry.collection, resolveCollection, triggerFunctionNeeded));\n }\n\n assertNoTruncationCollisions(tables, enums);\n\n // ── Extensions and functions ─────────────────────────────────────────────\n const extensions: string[] = [];\n const functions: string[] = [];\n const seenStatements = new Set<string>();\n const add = (into: string[], statement: string): void => {\n if (seenStatements.has(statement)) return;\n seenStatements.add(statement);\n into.push(statement);\n };\n for (const table of tables) {\n if (!table.search) continue;\n for (const statement of searchExtensionStatements(table.search)) add(extensions, statement);\n }\n // pgvector is a separate build behind an image, a grant and a provider\n // allow-list, so whether Rebase may install it is not Rebase's to decide.\n // See `DatabaseOptions.extensions`.\n if (vectorExtensionDeclared(options.databaseExtensions)\n && tables.some(t => t.vectorColumns.length > 0)) {\n add(extensions, vectorExtensionStatement());\n }\n for (const table of tables) {\n if (!table.search) continue;\n for (const statement of searchHelperFunctions(table.search)) add(functions, statement);\n }\n if (triggerFunctionNeeded.value) add(functions, setUpdatedAtFunction());\n\n return {\n schemas,\n declaredSchemas,\n enums,\n tables,\n relations: planRelations(collections, tableEntries),\n extensions,\n functions,\n collections,\n options\n };\n}\n\n/**\n * Refuse two relations — tables, junctions and enum types share one namespace\n * per schema, because every table owns a row type of its own name — that are\n * distinct in full but one name after Postgres keeps the first 63 bytes.\n *\n * `a_table_with_a_really_long_name_that_goes_on_and_on_and_on_forever` with an\n * enum column and a many-to-many truncated its table, its enum type and its\n * junction to the same identifier: the push failed with `type \"…\" already\n * exists` and no validator mentioned the length. A name that already works is\n * never touched — only a collision that cannot be created refuses.\n */\nfunction assertNoTruncationCollisions(tables: TablePlan[], enums: EnumPlan[]): void {\n const owners = new Map<string, string>();\n const claim = (schema: string, name: string, what: string): void => {\n const key = `${schema}.${toPostgresIdentifier(name)}`;\n const first = owners.get(key);\n if (first === undefined) {\n owners.set(key, what);\n return;\n }\n throw new Error(\n `The ${what} and the ${first} both become \"${schema}\".\"${toPostgresIdentifier(name)}\" in Postgres, ` +\n \"which keeps only the first 63 bytes of an identifier — and a table, a junction table and an enum \" +\n \"type share one namespace (every table owns a row type of its name). The push would fail with \" +\n \"\\\"already exists\\\". Give the collection a shorter `table`, or the enum's property a shorter \" +\n \"`columnName` (an enum type is named <table>_<column>).\"\n );\n };\n for (const table of tables) {\n claim(table.schema, table.table, table.kind === \"junction\" ? `junction table \"${table.table}\"` : `table \"${table.table}\"`);\n }\n for (const e of enums) claim(e.schema, e.name, `enum type \"${e.name}\"`);\n}\n\n/**\n * Refuse two many-to-many relations that resolve to one junction table with\n * different key columns.\n *\n * A default `through` is named after the two tables, so `posts.tags` and\n * `posts.featuredTags` both resolve to `posts_tags` — one keyed by `tag_id`,\n * the other by `featured_tag_id`. One table is created, from whichever came\n * first, so the other relation read and wrote a column that was never there.\n * The two sides of one link (`posts.tags` and `tags.posts`) share a junction\n * legitimately, and they name the same two columns — so the comparison is of\n * the (column, endpoint) pairs, in either order.\n */\nfunction assertOneJunctionShape(\n seen: Map<string, { keys: string; claim: string }>,\n junctionTable: string,\n collection: CollectionConfig,\n relationName: string,\n relation: ResolvedRelation\n): void {\n if (!isManyToMany(relation)) return;\n // A target that does not resolve plans no junction here; `resolveJunctionSpecs` skips it the same way.\n const target: CollectionConfig | undefined = relation.target();\n if (!target) return;\n const sourceTable = getTableName(collection);\n const targetTable = getTableName(target);\n const { sourceColumn, targetColumn } = relation.through;\n const keys = JSON.stringify([[sourceColumn, sourceTable], [targetColumn, targetTable]].sort());\n const claim = `the relation \"${relationName}\" of collection \"${describe(collection)}\" ` +\n `(\"${sourceColumn}\" → ${sourceTable}, \"${targetColumn}\" → ${targetTable})`;\n const first = seen.get(junctionTable);\n if (!first) {\n seen.set(junctionTable, { keys, claim });\n return;\n }\n if (first.keys === keys) return;\n throw new Error(\n `Two many-to-many relations resolve to the junction table \"${junctionTable}\" with different key ` +\n `columns: ${first.claim} and ${claim}. Only one table is created, so one of them would read ` +\n \"and write columns that do not exist. Give the second its own junction — \" +\n `\\`through: { table: \"${sourceTable}_${toSnakeCase(relationName)}\" }\\` — or, if both are meant to ` +\n \"be one link, declare it once.\"\n );\n}\n\n// ── A collection's table ─────────────────────────────────────────────────────\n\nfunction planCollectionTable(\n collection: CollectionConfig,\n resolveCollection: ResolveCollection,\n triggerFunctionNeeded: { value: boolean }\n): TablePlan {\n const schema = schemaOf(collection);\n const table = bareTableName(getTableName(collection));\n const auth = isAuthCollection(collection);\n const columns: ColumnPlan[] = [];\n const triggers: TriggerPlan[] = [];\n const relations = resolveCollectionRelations(collection);\n\n for (const [propName, rawProp] of Object.entries(collection.properties ?? {})) {\n const prop = rawProp as Property;\n\n if (prop.type === \"relation\") {\n const column = planRelationColumn(propName, prop as RelationProperty, collection, relations, schema, table);\n if (column) columns.push(column);\n continue;\n }\n if (prop.type === \"reference\") {\n columns.push(planReferenceColumn(propName, prop as ReferenceProperty, collection, resolveCollection, schema, table));\n continue;\n }\n\n columns.push(planPropertyColumn(propName, prop, {\n collection,\n resolveCollection,\n schema,\n table,\n auth,\n triggers,\n triggerFunctionNeeded\n }));\n }\n\n // ── Auth columns the collection file never mentions ──────────────────────\n // `db push` is declarative: Atlas diffs the database against exactly what\n // is emitted and drops anything else. The scaffold's users collection\n // describes 12 columns while auth needs 14, so `is_anonymous` and\n // `tokens_valid_after` read as unmanaged drift and a push run after the\n // server had started once planned to DROP them.\n if (auth) {\n const declared = new Set(columns.map(c => c.column));\n for (const spec of AUTH_USERS_COLUMNS) {\n if (declared.has(spec.column)) continue;\n columns.push({\n key: spec.column,\n column: spec.column,\n type: authColumnType(spec.type),\n nullable: spec.notNull !== true,\n primaryKey: false,\n unique: false,\n sqlDefinition: authUsersColumnSql(spec),\n source: { kind: \"auth\", slug: collection.slug }\n });\n }\n }\n\n // ── The opt-in search column ─────────────────────────────────────────────\n // Last, so it reads as what it is: derived from the columns above it.\n // Postgres recomputes it on every write of a source column and rejects any\n // attempt to write it directly, which is what makes it impossible for the\n // index to drift from the row.\n const search = buildSearchColumnSpec(collection);\n if (search) {\n columns.push({\n key: search.column,\n column: search.column,\n type: { kind: \"tsvector\" },\n nullable: true,\n primaryKey: false,\n unique: false,\n generated: { expression: search.expression, stored: true },\n sqlDefinition: searchColumnTypeSql(search.expression, \"tsvector\"),\n source: { kind: \"search\", slug: collection.slug }\n });\n if (search.fuzzy) {\n columns.push({\n key: search.fuzzy.column,\n column: search.fuzzy.column,\n type: { kind: \"text\" },\n nullable: true,\n primaryKey: false,\n unique: false,\n generated: { expression: search.fuzzy.expression, stored: true },\n sqlDefinition: searchColumnTypeSql(search.fuzzy.expression, \"text\"),\n source: { kind: \"search\", slug: collection.slug }\n });\n }\n }\n\n const primaryKey = planPrimaryKey(collection, columns);\n\n const injected = new Set(getInjectedSecurityRules(collection).map(rule => rule.name));\n const policies = withoutLiveSupersededNames(getEffectiveSecurityRules(collection).flatMap((rule, index) =>\n compileSecurityRule(\n collection,\n rule,\n resolveCollection,\n Boolean(rule.name && injected.has(rule.name)),\n rule.name ?? `#${index}`\n )));\n\n // ── Tenancy ──────────────────────────────────────────────────────────────\n // The policy is already above — `getEffectiveSecurityRules` injects it, so\n // it compiles, names and renders like every other rule. What is left is the\n // half a rule cannot express: the column is NOT NULL, and it is indexed.\n const tenantIndexes = applyTenantColumnEffects(collection, columns, primaryKey, schema, table);\n\n // Last among the columns, because it reads a nullability the two passes\n // above can still change.\n settleDefaultOnDelete(collection, columns, relations);\n\n // After every other index, because the foreign-key ones defer to them: a\n // declared index or the tenant index that already leads with the column\n // serves the same lookups, and a second one would only cost writes.\n const indexes = [...buildCollectionIndexSpecs(collection, resolveColumnName), ...tenantIndexes];\n indexes.push(...planForeignKeyIndexes(schema, table, columns, primaryKey, indexes, column => {\n // A `belongsTo` whose author opted out keeps its constraint and loses\n // only the index. Asked of the relation, not the column: the column a\n // declared property owns carries no relation of its own.\n if (column.source.kind !== \"relation\" || !column.source.propName) return false;\n const prop = collection.properties?.[column.source.propName] as RelationProperty | undefined;\n const relation = findRelation(relations, prop?.relation?.relationName ?? column.source.propName);\n return relation?.kind === \"belongsTo\" && relation.index;\n }));\n\n return {\n schema,\n table,\n qualified: `${schema}.${table}`,\n declaredSchema: isPostgresCollectionConfig(collection) ? collection.schema : undefined,\n varName: getTableVarName(getTableName(collection)),\n kind: \"collection\",\n slug: collection.slug,\n columns,\n primaryKey,\n indexes,\n search,\n vector: buildVectorIndexPlan(collection, resolveColumnName),\n vectorColumns: buildVectorColumnSpecs(collection, resolveColumnName),\n policies,\n triggers,\n auth\n };\n}\n\n/**\n * The table's primary key, as the column names {@link TablePlan.primaryKey}\n * lists — and the columns adjusted to state it one way only.\n *\n * One `isId` property is a key column of its own, declared inline\n * (`\"id\" INTEGER PRIMARY KEY`, `.primaryKey()`), which is how\n * {@link planPropertyColumn} planned it. Several are one composite key, and a\n * composite key cannot be said column by column: two inline `PRIMARY KEY`\n * clauses are two primary keys, which Postgres refuses. So each member stops\n * being a key of its own, keeps NOT NULL (the constraint implies it, and every\n * emitter spells it on the column so the three read the same), and the table\n * gets one `PRIMARY KEY (a, b)` over the list returned here — the shape a\n * junction's key has always had.\n *\n * A collection that declares no key gets an implicit `id TEXT PRIMARY KEY`,\n * which is what `getPrimaryKeys` reads back.\n */\nfunction planPrimaryKey(collection: CollectionConfig, columns: ColumnPlan[]): string[] {\n const keyNames = primaryKeyPropertyNames(collection);\n\n if (keyNames.length > 1) {\n return keyNames.map(name => {\n const column = columns.find(c => c.source.propName === name && !c.columnOwnedByProperty);\n if (!column) {\n throw new Error(\n `Property \"${name}\" of collection \"${describe(collection)}\" is marked \\`isId\\`, and it puts ` +\n \"no column on this table, so it cannot be part of the table's primary key. Mark the \" +\n \"property that holds the column instead.\"\n );\n }\n column.primaryKey = false;\n column.nullable = false;\n return column.column;\n });\n }\n\n // With a default the database fills. Nothing else can: the key is not a\n // property, so the admin form has no field for it, and REST, the SDK and\n // the socket send none. Without one, every insert into a collection that\n // declares no key — the quickstart's first one among them — was refused\n // as a missing `id`. The column stays TEXT so a table created before this\n // keeps its type, and boot's ensure adds the default to it: a DEFAULT binds\n // only future writes, so it is the one change it makes to an existing\n // column. A key the caller does supply still wins over the default.\n if (!columns.some(c => c.primaryKey)) {\n columns.unshift({\n key: \"id\",\n column: \"id\",\n type: { kind: \"text\" },\n nullable: false,\n primaryKey: true,\n unique: false,\n default: { kind: \"sql\", expression: IMPLICIT_ID_DEFAULT },\n source: { kind: \"implicit-id\", slug: collection.slug }\n });\n }\n return columns.filter(c => c.primaryKey).map(c => c.column);\n}\n\n/** What {@link planPropertyColumn} needs about the table the column lands on. */\ninterface PropertyColumnContext {\n /** The collection, or the synthetic one standing in for a junction. */\n collection: CollectionConfig;\n resolveCollection: ResolveCollection;\n schema: string;\n /** Bare table name — the trigger name is derived from it. */\n table: string;\n /** Whether `auth-users-columns` owns this table's columns. */\n auth: boolean;\n /** Collected `BEFORE UPDATE` triggers, appended to. */\n triggers: TriggerPlan[];\n triggerFunctionNeeded: { value: boolean };\n}\n\n/**\n * One declared, non-linking property, as a column.\n *\n * Extracted from {@link planCollectionTable} the moment a second table needed\n * it: a `manyToMany`'s `through.properties` are columns on the junction, and\n * \"declared exactly like a collection's properties\" has to mean the same code\n * reads them, or it is a promise the next `columnType` or `defaultValue` change\n * quietly breaks. A junction payload column therefore gets the type, the\n * nullability, the default, the UNIQUE and the `on_update` trigger a collection\n * column with the same declaration gets — because it is the same function\n * answering.\n *\n * `relation` and `reference` do not come through here; they own a foreign key\n * and are planned by {@link planRelationColumn} / {@link planReferenceColumn}.\n * A junction payload may not declare either (config validation refuses it), so\n * this is the whole of a payload column.\n */\nfunction planPropertyColumn(\n propName: string,\n prop: Property,\n ctx: PropertyColumnContext\n): ColumnPlan {\n const { collection, resolveCollection, schema, table, auth } = ctx;\n\n const isId = isIdProperty(propName, prop, collection);\n const column = resolveColumnName(propName, prop);\n const type = columnPgType(propName, prop, collection, resolveCollection);\n const required = prop.validation?.required === true;\n const defaultValue = columnDefault(propName, prop, collection, type);\n // On an auth collection, the columns auth itself reads and writes have\n // exactly one definition wherever the table is created from — see\n // `auth-users-columns`. Anything else there is an ordinary field.\n const authDefinition = auth && !isId ? authUsersColumnDefinition(column) : undefined;\n\n const plan: ColumnPlan = {\n key: propName,\n column,\n type,\n // A primary key is NOT NULL whether or not anyone writes it.\n nullable: !(isId || required),\n primaryKey: isId,\n // `validation.unique` holds for every type. The Drizzle generator\n // honoured it on `string` and `number` only, so a unique `date` or\n // `map` was UNIQUE in the database and not in the file drizzle-kit\n // plans from. A primary key gets neither UNIQUE nor NOT NULL —\n // both are implied, and emitting them made drizzle-kit plan an\n // extra constraint against a database `db push` built without one.\n unique: !isId && prop.validation?.unique === true,\n default: defaultValue,\n sqlDefinition: authDefinition,\n source: { kind: \"property\", propName, slug: collection.slug }\n };\n if (prop.type === \"date\" && (prop as DateProperty).autoValue === \"on_update\") {\n plan.touchOnUpdate = true;\n ctx.triggerFunctionNeeded.value = true;\n ctx.triggers.push({\n schema,\n table,\n column,\n name: toPostgresIdentifier(`${table}_${column}_touch`)\n });\n }\n return plan;\n}\n\n/**\n * The two schema effects a `tenant` declaration has that a policy cannot state.\n *\n * The policy itself is injected as an ordinary `SecurityRule`\n * (`buildTenantSecurityRule`) and compiled above with every other rule, so\n * nothing here touches `policies`. What is left is the column:\n *\n * - **`NOT NULL`.** A row whose tenant is NULL belongs to nobody, and the\n * restrictive policy compares the column to the caller's tenant — a\n * comparison against NULL is never true. Such a row is therefore invisible to\n * every caller, forever, including the one who wrote it. The write path\n * refuses to create one (`TENANT_REQUIRED`); this is the same statement made\n * where a raw `INSERT`, a seed and a migration can also hear it.\n * - **An index.** The tenancy predicate is ANDed into *every* statement against\n * the table, so an unindexed tenant column turns every read into a sequential\n * scan filtered by tenant. It is the one index a tenant-scoped table cannot\n * be without, and the one people most reliably forget — which is the whole\n * argument for deriving it rather than documenting it.\n *\n * The index goes through `deriveIndexName` like every other index, so it is a\n * Rebase-managed name (`_ix_<fingerprint>`), it is in the Atlas diff, and the\n * doctor recognises it. Because that name is a fingerprint of the index's\n * semantics, an author who has *already* declared a plain btree index on the\n * tenant column derives byte-for-byte the same name — so the two are the same\n * object and this adds nothing rather than colliding.\n *\n * Throws when the field names no column. `planSchema` throws rather than\n * guessing, and the alternative here is a table with a tenancy policy over a\n * column that does not exist: `CREATE POLICY` fails, RLS stays enabled, and the\n * collection denies every row.\n */\nfunction applyTenantColumnEffects(\n collection: CollectionConfig,\n columns: ColumnPlan[],\n primaryKey: string[],\n schema: string,\n table: string\n): CollectionIndexSpec[] {\n const tenant = getTenantConfig(collection);\n if (!tenant) return [];\n\n const column = findTenantColumn(collection, columns, tenant.field);\n if (!column) {\n throw new Error(\n `Collection \"${describe(collection)}\" declares \\`tenant: { field: \"${tenant.field}\" }\\`, and ` +\n \"no property of that name reaches a column. `tenant` says what a column means; it does not \" +\n \"create one. Declare the property, or name the one that holds the tenant id.\"\n );\n }\n\n // The leading column of the primary key is already NOT NULL and already\n // indexed by `<table>_pkey`. Saying either again would make `db push` plan\n // a constraint and an index the database has no reason to hold. A later\n // column of a composite key is NOT NULL too, but `<table>_pkey` cannot\n // serve a lookup on it alone, so it still gets its index.\n if (primaryKey[0] === column.column) return [];\n\n column.nullable = false;\n\n const withoutName = {\n schema,\n table,\n method: \"btree\" as const,\n unique: false,\n keys: [{ column: column.column, direction: \"asc\" as const, nulls: \"last\" as const }],\n include: [],\n predicate: null,\n reason: TENANT_INDEX_REASON\n };\n return [{ ...withoutName, indexName: deriveIndexName(withoutName) }];\n}\n\n/**\n * A btree on each foreign-key column of one table, where nothing already\n * serves the lookup — see `foreignKeyIndexSpec` for why the column needs one.\n *\n * Skipped, so that no table ever carries two indexes doing one job, when the\n * column already leads:\n *\n * - the primary key — `<table>_pkey` is a btree over the key in key order, so\n * its first column is indexed and the rest are not (a junction's\n * `(post_id, tag_id)` key serves `post_id`, and `tag_id` gets an index);\n * - a UNIQUE on the column (`validation.unique`, on the relation or on the\n * property that owns the column) — Postgres backs it with a btree;\n * - another planned index that leads with it ({@link leadsWithColumn}) — one\n * the collection declares in `indexes:`, or the tenant index.\n *\n * Only the plan is consulted, never the catalogue: `db push` renders this from\n * the collections alone, and boot has to reach the same set with the same\n * names, or Atlas would plan to drop what boot built.\n */\nfunction planForeignKeyIndexes(\n schema: string,\n table: string,\n columns: ColumnPlan[],\n primaryKey: string[],\n planned: readonly CollectionIndexSpec[],\n wantsIndex: (column: ColumnPlan) => boolean\n): CollectionIndexSpec[] {\n const created = columns.filter(c => !c.columnOwnedByProperty);\n const derived: CollectionIndexSpec[] = [];\n for (const column of columns) {\n const fk = column.foreignKey;\n if (!fk || !wantsIndex(column)) continue;\n const name = fk.column;\n if (primaryKey[0] === name) continue;\n if (created.some(c => c.column === name && c.unique)) continue;\n if ([...planned, ...derived].some(spec => leadsWithColumn(spec, name))) continue;\n derived.push(foreignKeyIndexSpec(schema, table, name));\n }\n return derived;\n}\n\n/**\n * The planned column a `tenant.field` names.\n *\n * Not simply `columns.find(c => c.key === field)`: a `belongsTo` relation\n * property emits its column under the *foreign key's* field name, so\n * `tenant: { field: \"org\" }` beside `org: { type: \"relation\", … }` has to\n * resolve through the relation to `org_id`. Matching only on `key` found\n * nothing there and the declaration would have been refused as naming no\n * column — for the shape the documentation recommends.\n */\nfunction findTenantColumn(\n collection: CollectionConfig,\n columns: ColumnPlan[],\n field: string\n): ColumnPlan | undefined {\n // Only columns that are created. A relation whose column a declared\n // property owns keeps an entry for its constraint alone, under the same key\n // and column; when the relation was declared first, NOT NULL landed on that\n // entry, nothing rendered it, and the tenant column stayed nullable.\n const created = columns.filter(c => !c.columnOwnedByProperty);\n const direct = created.find(c => c.key === field);\n if (direct) return direct;\n\n const prop = collection.properties?.[field] as Property | undefined;\n if (prop?.type === \"relation\") {\n const relations = resolveCollectionRelations(collection);\n const relation = findRelation(relations, (prop as RelationProperty).relation?.relationName ?? field);\n if (relation?.kind === \"belongsTo\" && relation.localKey) {\n return created.find(c => c.column === relation.localKey);\n }\n return undefined;\n }\n if (!prop) return undefined;\n const columnName = resolveColumnName(field, prop);\n return created.find(c => c.column === columnName);\n}\n\n/**\n * The `ON DELETE` a link gets when the author did not say, decided from the\n * column as it will be created.\n *\n * {@link planRelationColumn} and {@link planReferenceColumn} see only their own\n * property's `required`, and two later facts change the premise: `tenant` makes\n * its column NOT NULL, and a declared property that owns a relation's column\n * decides that column's nullability itself. Defaulting before either produced\n * `NOT NULL … ON DELETE SET NULL` — a parent's delete that can only fail, and\n * fails with 23502 naming the child's column rather than as the foreign-key\n * refusal it is. An `onDelete` the author wrote is theirs and is kept.\n */\nfunction settleDefaultOnDelete(\n collection: CollectionConfig,\n columns: ColumnPlan[],\n relations: Record<string, ResolvedRelation>\n): void {\n for (const column of columns) {\n const fk = column.foreignKey;\n if (!fk || declaresOnDelete(collection, relations, column)) continue;\n const created = columns.find(c => c.column === column.column && !c.columnOwnedByProperty) ?? column;\n const onDelete = defaultBelongsToOnDelete(!created.nullable);\n if (onDelete === fk.onDelete) continue;\n column.foreignKey = foreignKeyPlan({\n schema: fk.schema,\n table: fk.table,\n column: fk.column,\n targetSchema: fk.targetSchema,\n targetTable: fk.targetTable,\n targetColumn: fk.targetColumn,\n onDelete,\n onUpdate: fk.onUpdate\n });\n }\n}\n\n/** Whether the author wrote this link's `onDelete`. A `reference` has none to write. */\nconst declaresOnDelete = (\n collection: CollectionConfig,\n relations: Record<string, ResolvedRelation>,\n column: ColumnPlan\n): boolean => {\n const propName = column.source.propName;\n if (column.source.kind !== \"relation\" || !propName) return false;\n const prop = collection.properties?.[propName] as RelationProperty | undefined;\n const relation = findRelation(relations, prop?.relation?.relationName ?? propName);\n return relation?.kind === \"belongsTo\" && relation.onDelete !== undefined;\n};\n\n/**\n * The column a `belongsTo` relation owns, or `undefined` when the relation puts\n * no column on this table.\n *\n * Every other kind is a column on the target, a junction row, or a join chain.\n */\nfunction planRelationColumn(\n propName: string,\n prop: RelationProperty,\n collection: CollectionConfig,\n relations: Record<string, ResolvedRelation>,\n schema: string,\n table: string\n): ColumnPlan | undefined {\n const relation = findRelation(relations, prop.relation?.relationName ?? propName);\n if (relation?.kind !== \"belongsTo\") return undefined;\n\n let target: CollectionConfig;\n try {\n target = relation.target();\n } catch {\n // A relation whose target is not in this bundle yields no column and no\n // constraint. Every emitter returned early here, and they must keep\n // agreeing: a column with no constraint down one path is a schema fork.\n return undefined;\n }\n if (!target) return undefined;\n\n const required = prop.validation?.required === true;\n // The relation and an explicit FK property can both be declared; the\n // explicit one owns the column, and the relation still owns the constraint.\n // Asked of the *field key* rather than the column — `properties[\"post_id\"]`\n // is not where a property declared `postId: { columnName: \"post_id\" }`\n // lives, so this used to emit the column twice and `CREATE TABLE` failed\n // with \"column specified more than once\".\n const fkFieldKey = fieldKeyForColumn(collection, relation.localKey);\n const columnOwnedByProperty = Boolean(collection.properties?.[fkFieldKey]) && propName !== fkFieldKey;\n\n // Only a *derived* column name can be affected by the change to\n // `generateForeignKeyName`; one the author wrote is theirs.\n const relationName = prop.relation?.relationName ?? propName;\n const legacyKey = legacyForeignKeyName(relationName);\n const derived = relation.localKey === generateForeignKeyName(relationName);\n const via = linkLabel(\"relation\", collection, propName);\n\n return {\n key: fkFieldKey,\n column: relation.localKey,\n type: primaryKeyPgType(target, via),\n nullable: !required,\n primaryKey: false,\n // `validation.unique` on a link is a one-to-one, and all three emitters\n // dropped it: the property said unique, the column was not, and a\n // second row could point at the same parent.\n unique: prop.validation?.unique === true,\n columnOwnedByProperty: columnOwnedByProperty || undefined,\n legacyColumn: derived && legacyKey !== relation.localKey ? legacyKey : undefined,\n foreignKey: foreignKeyPlan({\n schema,\n table,\n column: relation.localKey,\n targetSchema: schemaOf(target),\n targetTable: bareTableName(getTableName(target)),\n targetColumn: getPrimaryKeyColumn(target, via),\n onDelete: relation.onDelete ?? defaultBelongsToOnDelete(required),\n onUpdate: relation.onUpdate\n }),\n source: { kind: \"relation\", propName, slug: collection.slug }\n };\n}\n\n/** The column a `reference` property owns — a foreign key like any other. */\nfunction planReferenceColumn(\n propName: string,\n prop: ReferenceProperty,\n collection: CollectionConfig,\n resolveCollection: ResolveCollection,\n schema: string,\n table: string\n): ColumnPlan {\n // A `reference` with no `path` names no collection, so it keeps its column\n // (the id is still a string) and gets no constraint — the same shape as one\n // whose target is not in this bundle.\n const target = prop.path ? resolveCollection(prop.path) : undefined;\n const column = resolveColumnName(propName, prop as Property);\n const required = prop.validation?.required === true;\n const via = linkLabel(\"reference\", collection, propName);\n\n return {\n key: propName,\n column,\n // A `reference` whose target is not in the bundle keeps its column (the\n // id is still a string) and gets no constraint.\n type: target ? primaryKeyPgType(target, via) : { kind: \"text\" },\n nullable: !required,\n primaryKey: false,\n unique: prop.validation?.unique === true,\n foreignKey: target\n ? foreignKeyPlan({\n schema,\n table,\n column,\n targetSchema: schemaOf(target),\n targetTable: bareTableName(getTableName(target)),\n targetColumn: getPrimaryKeyColumn(target, via),\n // The same rule as `belongsTo`, from the same function. A\n // `reference` carries no `onDelete` of its own to override it.\n onDelete: defaultBelongsToOnDelete(required)\n })\n : undefined,\n source: { kind: \"reference\", propName, slug: collection.slug }\n };\n}\n\n/**\n * An auth-owned column's declared SQL type, as a tag.\n *\n * `auth-users-columns` states these as SQL because that is the grammar its three\n * creators write in; the plan needs the tag so nothing downstream has to parse a\n * type back apart. Deliberately exhaustive rather than a fallback: a column auth\n * adds with a type nobody mapped should say so here, not turn into `TEXT` in a\n * table the login flow reads.\n */\nconst AUTH_COLUMN_TYPES: Record<string, PgType> = {\n \"TEXT\": { kind: \"text\" },\n \"TEXT[]\": { kind: \"array\", of: { kind: \"text\" } },\n \"BOOLEAN\": { kind: \"boolean\" },\n \"JSONB\": { kind: \"jsonb\" },\n \"TIMESTAMP WITH TIME ZONE\": { kind: \"timestamptz\" }\n};\n\nconst authColumnType = (sqlType: string): PgType => {\n const mapped = AUTH_COLUMN_TYPES[sqlType];\n if (!mapped) {\n throw new Error(\n `\\`auth-users-columns\\` declares a column of type \"${sqlType}\", which the schema plan ` +\n \"has no tag for. Add it to `AUTH_COLUMN_TYPES` in schema/plan/plan-schema.ts.\"\n );\n }\n return mapped;\n};\n\n// ── A junction table ─────────────────────────────────────────────────────────\n\n/**\n * Which declaring side a junction is planned from.\n *\n * With one side, that side — as it always was, so a junction only one end\n * declares keeps the key every database already has. With both ends declaring\n * it, the planner used to take whichever it reached first, and the two doors\n * reach them in different orders: `db push` loads files by name, boot walks the\n * bundle's array. The same junction was keyed `(a_id, b_id)` by one and\n * `(b_id, a_id)` by the other, and a push against a boot-built database rebuilt\n * the key. Now the side whose table sorts first, which is the order `db push`\n * already used wherever a file is named after its table.\n */\nfunction canonicalJunctionSide(\n table: string,\n relation: ResolvedRelation,\n source: CollectionConfig,\n spec: JunctionSpec | undefined\n): { relation: ResolvedRelation; source: CollectionConfig } {\n if (!spec || spec.declaringSides.length < 2) return { relation, source };\n const ordered = [...spec.declaringSides].sort((a, b) => {\n const left = bareTableName(getTableName(a.collection));\n const right = bareTableName(getTableName(b.collection));\n if (left !== right) return left < right ? -1 : 1;\n return a.junctionColumn < b.junctionColumn ? -1 : a.junctionColumn > b.junctionColumn ? 1 : 0;\n });\n const chosen = ordered[0].collection;\n if (chosen === source) return { relation, source };\n const resolved = Object.values(resolveCollectionRelations(chosen))\n .find(r => isManyToMany(r) && bareTableName(r.through.table) === table);\n return resolved ? { relation: resolved, source: chosen } : { relation, source };\n}\n\nfunction planJunctionTable(\n tableName: string,\n reachedRelation: ResolvedRelation,\n reachedSource: CollectionConfig,\n junctionSpecs: ReturnType<typeof resolveJunctionSpecs>,\n resolveCollection: ResolveCollection,\n triggerFunctionNeeded: { value: boolean }\n): TablePlan {\n const { relation, source } = canonicalJunctionSide(\n bareTableName(tableName), reachedRelation, reachedSource, junctionSpecs.get(bareTableName(tableName)));\n if (!isManyToMany(relation)) {\n throw new Error(`Internal: junction table \"${tableName}\" was reached from a ${relation.kind} relation.`);\n }\n const table = bareTableName(tableName);\n // Junctions live in `public`, full stop — `resolveJunctionSpecs` hardcodes\n // that, so it is where the tables are created and where the derived RLS\n // policies are applied. Inheriting the endpoint's schema put the junction\n // of any m2m onto `users` in `rebase` while its policies were still created\n // against `public.<junction>`: RLS enabled on one table, rows in another.\n const schema = \"public\";\n const target = relation.target();\n const { sourceColumn, targetColumn } = relation.through;\n // Every declaring side agrees on the edge's lifetime; the first one wins.\n const onDelete = relation.onDelete ?? \"CASCADE\";\n\n const spec = junctionSpecs.get(table);\n const legacyFor = (collection: CollectionConfig, column: string): string | undefined => {\n // A junction column's default name is derived from the endpoint\n // collection's slug — normally plural, so this is where the\n // singularization change lands: a `categories` endpoint used to give\n // `categorie_id` and now gives `category_id`. Recorded, not used.\n const slug = toSnakeCase(collection.slug ?? collection.name ?? \"\");\n const legacy = legacyForeignKeyName(slug);\n return column === generateForeignKeyName(slug) && legacy !== column ? legacy : undefined;\n };\n\n const via = linkLabel(\"relation\", source, relation.relationName);\n const endpoint = (collection: CollectionConfig, column: string): ColumnPlan => ({\n key: column,\n column,\n type: primaryKeyPgType(collection, via),\n nullable: false,\n // Part of the composite key on {@link TablePlan.primaryKey}, not a\n // key of its own — an inline `PRIMARY KEY` here would be a second one.\n primaryKey: false,\n unique: false,\n legacyColumn: legacyFor(collection, column),\n foreignKey: foreignKeyPlan({\n schema,\n table,\n column,\n targetSchema: schemaOf(collection),\n targetTable: bareTableName(getTableName(collection)),\n targetColumn: getPrimaryKeyColumn(collection, via),\n onDelete\n }),\n source: { kind: \"junction-key\" }\n });\n\n const columns = [endpoint(source, sourceColumn), endpoint(target, targetColumn)];\n\n // ── The junction's own columns ───────────────────────────────────────────\n // `through.properties`: the `role` on a membership, the `position` on a\n // tag. Planned by the same {@link planPropertyColumn} the collection tables\n // use, against the synthetic collection {@link getJunctionCollectionConfig}\n // builds — so a payload property gets exactly the type, nullability,\n // default, UNIQUE and enum type it would get on a table, and there is no\n // second `switch (prop.type)` anywhere to disagree with the first.\n //\n // Not supported here, and deliberately: `indexes` (declared per collection,\n // and no collection declares a junction), `search`, `vector`, and any\n // linking property — a payload cannot be a `relation`, a `reference` or a\n // `vector`. Config validation refuses those with the relation named; this\n // would otherwise reach `columnPgType` and produce a column no writer knows\n // how to fill.\n const triggers: TriggerPlan[] = [];\n if (spec && Object.keys(spec.properties).length > 0) {\n const junctionCollection = getJunctionCollectionConfig(spec);\n const keyColumns = new Set([sourceColumn, targetColumn]);\n for (const [propName, rawProp] of Object.entries(spec.properties)) {\n const prop = rawProp as Property;\n const plan = planPropertyColumn(propName, prop, {\n collection: junctionCollection,\n resolveCollection,\n schema,\n table,\n auth: false,\n triggers,\n triggerFunctionNeeded\n });\n // A payload column that resolves onto a key column would be a\n // second definition of it — refused by `checkJunctionPayload` at\n // boot, and skipped here so a config that got past it still\n // produces a table rather than a duplicate-column CREATE.\n if (keyColumns.has(plan.column)) continue;\n columns.push(plan);\n }\n }\n\n // Junction tables are generated tables like any other: locked by default,\n // with derived policies — reads follow the endpoints' visibility, writes\n // follow the declaring side's update rules. Without them they were the one\n // kind of generated table with no RLS at all.\n const policies = spec\n ? withoutLiveSupersededNames(getJunctionSecurityRules(spec).flatMap((rule, index) =>\n compileSecurityRule(\n getJunctionCollectionConfig(spec), rule, resolveCollection, false, rule.name ?? `#${index}`)))\n : [];\n\n return {\n schema,\n table,\n qualified: `${schema}.${table}`,\n varName: getTableVarName(tableName),\n kind: \"junction\",\n declaringSlugs: spec?.declaringSides.map(side => side.collection.slug) ?? [],\n columns,\n primaryKey: [sourceColumn, targetColumn],\n // Always, and with no opt-out: the junction is the platform's table,\n // not the author's. The key covers lookups by `sourceColumn`; the\n // reverse side of the link — `tags → posts` — filters on\n // `targetColumn`, which only this index serves.\n indexes: planForeignKeyIndexes(schema, table, columns, [sourceColumn, targetColumn], [],\n column => column.source.kind === \"junction-key\"),\n vectorColumns: [],\n policies,\n triggers,\n auth: false\n };\n}\n\n// ── Drizzle relations ────────────────────────────────────────────────────────\n\n/**\n * The `relations(...)` entries the generated file carries.\n *\n * Both sides of a link must share a `relationName` or Drizzle cannot pair them;\n * `sharedRelationName` is the rule, and each side derives it independently.\n */\nfunction planRelations(\n collections: CollectionConfig[],\n tableEntries: Map<string, { collection: CollectionConfig; junction?: { relation: ResolvedRelation; source: CollectionConfig } }>\n): RelationPlan[] {\n const plans: RelationPlan[] = [];\n\n // Every relation whose foreign key lives on its target's table, grouped by\n // that target's slug, in the order the scan below used to meet them —\n // collections in declaration order, each one's relations in order. Built\n // once: the scan ran once per collection over every other collection's\n // relations, N² in the collection count (24 ms at 200, 204 ms at 1,000).\n const fkOnTargetBySlug = new Map<string, Array<{ other: CollectionConfig; otherRel: ResolvedForeignKeyOnTarget }>>();\n for (const other of collections) {\n for (const otherRel of Object.values(resolveCollectionRelations(other))) {\n if (!hasForeignKeyOnTarget(otherRel)) continue;\n let otherTarget: CollectionConfig;\n try {\n otherTarget = otherRel.target();\n } catch {\n continue;\n }\n const list = fkOnTargetBySlug.get(otherTarget.slug) ?? [];\n list.push({ other, otherRel });\n fkOnTargetBySlug.set(otherTarget.slug, list);\n }\n }\n\n for (const [tableName, entry] of tableEntries) {\n const tableVar = getTableVarName(tableName);\n\n if (entry.junction) {\n const { relation, source } = entry.junction;\n if (!isManyToMany(relation)) continue;\n const target = relation.target();\n const via = linkLabel(\"relation\", source, relation.relationName);\n // Each `one()` here pairs with a `many(junction, { relationName })`\n // on its endpoint, so each takes the name that endpoint's own\n // relation derives — the rule `addJunctionSides` in\n // `config-relations.ts` builds the runtime's relations by.\n //\n // The far side is looked up as the target's many-to-many through\n // this same junction. It used to be matched on the *owning* side's\n // name, which `tags.posts` never carries, so the synthesized name\n // won and `tags`' `many(…, { relationName: \"posts\" })` had nothing\n // to pair with: `with: { posts: true }` threw. Only a junction\n // nothing points back through needs the synthesized name.\n const owningRelationName = sharedRelationName(relation, source);\n let inverse: ResolvedRelation | undefined;\n try {\n inverse = Object.values(resolveCollectionRelations(target)).find(r =>\n r.kind === \"manyToMany\" && bareTableName(r.through.table) === bareTableName(tableName));\n } catch {\n // A target whose relations cannot be resolved declares no inverse.\n }\n const inverseRelationName = inverse\n ? sharedRelationName(inverse, target)\n : `${tableName}_${relation.through.targetColumn}`;\n plans.push({\n tableVar,\n key: relation.through.sourceColumn,\n kind: \"one\",\n targetVar: getTableVarName(getTableName(source)),\n relationName: owningRelationName,\n fields: [relation.through.sourceColumn],\n references: [getPrimaryKeyName(source, via)]\n });\n // A self-referencing link is its own inverse: one name, one\n // endpoint table, and drizzle refuses two relations named alike.\n if (inverseRelationName === owningRelationName) continue;\n plans.push({\n tableVar,\n key: relation.through.targetColumn,\n kind: \"one\",\n targetVar: getTableVarName(getTableName(target)),\n relationName: inverseRelationName,\n fields: [relation.through.targetColumn],\n references: [getPrimaryKeyName(target, via)]\n });\n continue;\n }\n\n const collection = entry.collection;\n // `resolveCollectionRelations` already deduplicates, but an alias entry\n // for the same foreign key would otherwise emit twice.\n const emitted = new Set<string>();\n for (const [key, relation] of Object.entries(resolveCollectionRelations(collection))) {\n let target: CollectionConfig;\n try {\n target = relation.target();\n } catch {\n // A link to a collection outside this bundle is not a relation\n // this file can describe.\n continue;\n }\n const targetVar = getTableVarName(getTableName(target));\n const relationName = sharedRelationName(relation, collection);\n const dedupe = `${relationName}::${relation.kind}`;\n if (emitted.has(dedupe)) continue;\n emitted.add(dedupe);\n\n switch (relation.kind) {\n case \"belongsTo\":\n plans.push({\n tableVar,\n key,\n kind: \"one\",\n targetVar,\n relationName,\n // `localKey` is a COLUMN and the generated object is\n // keyed by PROPERTY: `user_id` is exposed as `userId`,\n // and emitting the column produces a file that does not\n // compile.\n fields: [fieldKeyForColumn(collection, relation.localKey)],\n references: [getPrimaryKeyName(target, linkLabel(\"relation\", collection, key))]\n });\n break;\n case \"hasOne\":\n // The foreign key lives on the TARGET table, so this side\n // states the join from its own end: its source key against\n // the target's foreign key, under the owning side's name.\n // Drizzle has no `one()` paired by `relationName` alone:\n // `one(target, { relationName })` is not a `RelationConfig`\n // (TS2345) and `createOne` reads `config.fields.reduce(...)`.\n // A bare `one(target)` is paired by table, which throws once\n // the target has two links back here or the link is to its\n // own table.\n plans.push({\n tableVar,\n key,\n kind: \"one\",\n targetVar,\n relationName,\n fields: [relation.sourceKey\n ? fieldKeyForColumn(collection, relation.sourceKey)\n : getPrimaryKeyName(collection, linkLabel(\"relation\", collection, key))],\n references: [fieldKeyForColumn(target, relation.foreignKeyOnTarget)],\n nullable: true\n });\n break;\n case \"hasMany\":\n plans.push({ tableVar, key, kind: \"many\", targetVar, relationName });\n break;\n case \"manyToMany\":\n plans.push({\n tableVar,\n key,\n kind: \"many\",\n targetVar: getTableVarName(relation.through.table),\n relationName\n });\n break;\n case \"via\":\n // A join chain is resolved at query time, not modelled as a\n // Drizzle relation.\n break;\n }\n }\n\n // Reciprocals the far side declares and this one does not. Drizzle\n // needs both halves of a pair to exist for a relational query to work.\n for (const { other, otherRel } of fkOnTargetBySlug.get(collection.slug) ?? []) {\n if (other.slug === collection.slug) continue;\n const relationName = sharedRelationName(otherRel, other);\n const dedupe = `${relationName}::belongsTo`;\n if (emitted.has(dedupe)) continue;\n emitted.add(dedupe);\n const otherVar = getTableVarName(getTableName(other));\n const fieldKey = fieldKeyForColumn(collection, otherRel.foreignKeyOnTarget);\n plans.push({\n tableVar,\n key: `_synth_${otherVar}_${fieldKey}`,\n kind: \"one\",\n targetVar: otherVar,\n relationName,\n fields: [fieldKey],\n // The column the far side points at: its primary key,\n // unless the link names another one with `sourceKey`.\n references: [otherRel.sourceKey\n ? fieldKeyForColumn(other, otherRel.sourceKey)\n : getPrimaryKeyName(other, linkLabel(\"relation\", other, otherRel.relationName))]\n });\n }\n }\n\n return plans;\n}\n\nexport { SET_UPDATED_AT_FN };\n"],"mappings":"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"}
@@ -1,7 +1,7 @@
1
1
  import { createRequire as __createRequire } from "module";
2
2
  __createRequire(import.meta.url);
3
- import { r as __exportAll } from "./__vite-browser-external-BnuHet1e.js";
4
- import { n as generatePostgresPoliciesDdl } from "./generate-postgres-ddl-logic-Bt2d2mRH.js";
3
+ import { n as __exportAll } from "./rolldown-runtime-V7rFbMRe.js";
4
+ import { n as generatePostgresPoliciesDdl } from "./generate-postgres-ddl-logic-B4k9djAV.js";
5
5
  import { RLS_UID_SQL } from "@rebasepro/types";
6
6
  //#region src/security/policy-drift.ts
7
7
  /**
@@ -16,7 +16,7 @@ import { RLS_UID_SQL } from "@rebasepro/types";
16
16
  * table is indistinguishable from a table with no data.
17
17
  *
18
18
  * Expected policies are parsed from `generatePostgresPoliciesDdl`, the same
19
- * function `db push` uses to write `drizzle/policies.sql`, so this compares
19
+ * function `db push` uses to write `.rebase/sql/policies.sql`, so this compares
20
20
  * against exactly what would be applied rather than a reimplementation.
21
21
  */
22
22
  var policy_drift_exports = /* @__PURE__ */ __exportAll({
@@ -138,8 +138,9 @@ async function readTablesWithRlsOff(client, tables) {
138
138
  * `<> 'anonymous'` guard that makes it mean "signed in".
139
139
  *
140
140
  * Whitespace varies with Postgres's rewrite, so match on a collapsed form. The
141
- * guard clause (`<> 'anonymous'`, in any spelling) is what distinguishes the
142
- * corrected policy from the stale one, so its presence clears the text.
141
+ * guard clause ({@link EXCLUDES_ANONYMOUS}, in any spelling) is what
142
+ * distinguishes the corrected policy from the stale one, so its presence
143
+ * clears the text.
143
144
  *
144
145
  * Both schema spellings are matched. The helpers moved from `auth` to `rebase`
145
146
  * in 1.0, and this check reads policies *as the database stored them*: a
@@ -152,9 +153,25 @@ async function readTablesWithRlsOff(client, tables) {
152
153
  function isPermissiveAuthTautology(clause) {
153
154
  if (!clause) return false;
154
155
  const flat = clause.toLowerCase().replace(/\s+/g, " ");
155
- if (!/\b(?:rebase|auth)\.uid\s*\(\s*\)\s*is not null/.test(flat)) return false;
156
- return !/<>\s*'anonymous'/.test(flat) && !/!=\s*'anonymous'/.test(flat);
156
+ if (!/\b(?:rebase|auth)\.uid\s*\(\s*\)(?:\s+as\s+\w+\s*\))?\s*is not null/.test(flat)) return false;
157
+ return !EXCLUDES_ANONYMOUS.some((guard) => guard.test(flat));
157
158
  }
159
+ /**
160
+ * The guard that makes the null test mean "signed in": the anonymous
161
+ * sentinel excluded, in every spelling a policy reaches the catalogue in.
162
+ *
163
+ * `policy.authenticated()` compiles to `NOT IN ('anonymous', 'anon')`, which
164
+ * Postgres stores as `<> ALL (ARRAY['anonymous'::text, 'anon'::text])`. Only
165
+ * `<> 'anonymous'` was recognised, so `doctor` and every `db push` called each
166
+ * `authenticated()` and `registered()` policy the insecure tautology. A list
167
+ * that leaves out `'anonymous'` excludes nobody a signed-out request arrives
168
+ * as, and does not clear it.
169
+ */
170
+ var EXCLUDES_ANONYMOUS = [
171
+ /(?:<>|!=)\s*'anonymous'/,
172
+ /(?:<>|!=)\s*all\s*\(\s*array\s*\[[^\]]*'anonymous'/,
173
+ /\bnot in\s*\([^)]*'anonymous'/
174
+ ];
158
175
  var keyOf = (p) => `${p.schema}.${p.table}.${p.name}`;
159
176
  var sameRoles = (a, b) => a.length === b.length && [...a].sort().join(",") === [...b].sort().join(",");
160
177
  /**
@@ -287,9 +304,16 @@ function formatPolicyDrift(drift) {
287
304
  lines.push(" Run `rebase db push` to apply them.");
288
305
  }
289
306
  if (drift.orphaned.length > 0) {
307
+ const superseded = drift.orphaned.filter((p) => isGeneratedPolicyName(p.name, p.table));
308
+ const unowned = drift.orphaned.filter((p) => !isGeneratedPolicyName(p.name, p.table));
290
309
  lines.push(" Orphaned — in the database, described by no collection:");
291
310
  for (const p of drift.orphaned) lines.push(` • ${p.schema}.${p.table} → "${p.name}" (${p.command} TO ${p.roles.join(", ")})`);
292
- lines.push(" Left behind by an earlier push. These still filter rows.");
311
+ if (superseded.length > 0) lines.push(` ${superseded.map((p) => `"${p.name}"`).join(", ")}: left behind by an earlier push, when its rule changed. The next \`rebase db push\` drops it.`);
312
+ if (unowned.length > 0) {
313
+ lines.push(` ${unowned.map((p) => `"${p.name}"`).join(", ")}: written by hand or by another tool, and still in force.`);
314
+ lines.push(" To keep one, describe it in its collection's `securityRules` (Studio's RLS view imports it);");
315
+ lines.push(" if it is stale, drop it by hand.");
316
+ }
293
317
  }
294
318
  if (drift.diverged.length > 0) {
295
319
  lines.push(" Diverged — same policy, different definition:");
@@ -310,4 +334,4 @@ function formatPolicyDrift(drift) {
310
334
  //#endregion
311
335
  export { policy_drift_exports as a, isGeneratedPolicyName as i, formatPolicyDrift as n, hasDrift as r, checkPolicyDrift as t };
312
336
 
313
- //# sourceMappingURL=policy-drift-DljYdrpW.js.map
337
+ //# sourceMappingURL=policy-drift-BKC50y2p.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"policy-drift-BKC50y2p.js","names":[],"sources":["../src/security/policy-drift.ts"],"sourcesContent":["/**\n * Compare the RLS policies a database actually has against the ones the\n * collections describe.\n *\n * Policies live in Postgres; the collection config is only their *source*.\n * Nothing reconciled the two, which is how the demo database served empty\n * collections indefinitely: its policies granted `TO authenticated` (a Supabase\n * role name) while requests run as `rebase_user`, so RLS filtered every row.\n * The config was later corrected and the database never noticed — an empty\n * table is indistinguishable from a table with no data.\n *\n * Expected policies are parsed from `generatePostgresPoliciesDdl`, the same\n * function `db push` uses to write `.rebase/sql/policies.sql`, so this compares\n * against exactly what would be applied rather than a reimplementation.\n */\nimport { RLS_UID_SQL, type CollectionConfig } from \"@rebasepro/types\";\n\nimport { generatePostgresPoliciesDdl } from \"../schema/generate-postgres-ddl-logic\";\n\nexport interface PolicyRef {\n schema: string;\n table: string;\n name: string;\n /** Roles in the TO clause. */\n roles: string[];\n /** SELECT / INSERT / UPDATE / DELETE / ALL. */\n command: string;\n /** Whether a USING clause is present at all (not what it says). */\n hasUsing: boolean;\n /** Whether a WITH CHECK clause is present at all (not what it says). */\n hasWithCheck: boolean;\n /**\n * PERMISSIVE or RESTRICTIVE — the `AS` clause.\n *\n * An exact catalogue value on both sides, so it belongs with roles and\n * command rather than with the expression text. It matters more than either:\n * permissive policies are ORed together and restrictive ones ANDed, so a rule\n * declared `mode: \"restrictive\"` whose live policy is PERMISSIVE has had its\n * gate turned from a requirement into an alternative — the maximally\n * permissive way for this to be wrong.\n *\n * The DDL regex captured this from the start and the destructuring threw it\n * away; `pg_policies.permissive` was never selected.\n */\n mode?: \"PERMISSIVE\" | \"RESTRICTIVE\";\n /**\n * The live clause text, when read from `pg_policies`. Present only for live\n * policies (the expected side is parsed from DDL and does not carry it).\n * Used solely for the insecure-tautology scan, not for divergence — Postgres\n * rewrites this text, so it is not safe to diff against expected.\n */\n qual?: string | null;\n withCheck?: string | null;\n}\n\nexport interface PolicyDrift {\n /** Described by the collections, absent from the database. */\n missing: PolicyRef[];\n /** In the database, described by no collection — stale pushes live here. */\n orphaned: PolicyRef[];\n /** Same policy name, different roles or command. */\n diverged: { expected: PolicyRef; actual: PolicyRef; differences: string[] }[];\n /**\n * A live policy whose expression is the known-permissive tautology\n * `rebase.uid() IS NOT NULL` — true for anonymous visitors too, because the\n * user path coerces a blank id to the `'anonymous'` sentinel. This is what\n * `policy.authenticated()` used to compile to, so a database pushed before\n * that fix carries it, and neither the name, roles, command nor clause\n * *presence* differs from the corrected policy — the only thing that changed\n * is the expression text, which this checker otherwise (correctly) ignores.\n * So it is the one drift that hides from every other check here.\n *\n * @see reason a sentence naming the clause and what to do.\n */\n insecure: { policy: PolicyRef; reason: string }[];\n /**\n * A table the collections describe whose RLS switch is off.\n *\n * `ALTER TABLE posts DISABLE ROW LEVEL SECURITY` leaves every row in\n * `pg_policies` untouched, so before this category every expected policy\n * still matched on name, roles, command and clause presence and the checker\n * reported clean — on a table Postgres was applying no filter to at all.\n * Requests run as `rebase_user`, which holds full DML, so the table is wide\n * open while `doctor` certifies it.\n *\n * `forced` reports `relforcerowsecurity`, which is what also subjects the\n * table's *owner* to its policies. Its absence is not drift on its own —\n * Rebase does not connect as the owner in the request path — so it is\n * reported for context rather than raised as a failure.\n */\n rlsDisabled: { schema: string; table: string; forced: boolean }[];\n}\n\nexport interface Queryable {\n query<R>(text: string, values?: unknown[]): Promise<{ rows: R[] }>;\n}\n\n// The trailing group captures whichever clause follows the TO list, which is\n// what tells us the clause is present.\nconst CREATE_POLICY = /CREATE POLICY \"([^\"]+)\" ON \"([^\"]+)\"\\.\"([^\"]+)\"\\s+AS (\\w+)\\s+FOR (\\w+)\\s+TO ([^\\n]+?)(\\s+USING\\s*\\(|\\s+WITH CHECK\\s*\\(|;)/gi;\n\nconst WITH_CHECK_NEXT = /^\\s+WITH CHECK\\s*\\(/i;\n\n/**\n * Index just past the `)` closing a clause whose `(` ends at `open`.\n *\n * Needed because a policy expression nests parens and can contain a quoted\n * literal holding either character, so \"find the next `)`\" would stop early and\n * miss the `WITH CHECK` that follows.\n */\nfunction clauseEnd(ddl: string, open: number): number {\n let depth = 1;\n let inQuote = false;\n for (let i = open; i < ddl.length; i++) {\n const c = ddl[i];\n if (inQuote) {\n // '' is an escaped quote inside a string, not a close.\n if (c === \"'\") {\n if (ddl[i + 1] === \"'\") i++;\n else inQuote = false;\n }\n continue;\n }\n if (c === \"'\") inQuote = true;\n else if (c === \"(\") depth++;\n else if (c === \")\" && --depth === 0) return i + 1;\n }\n return ddl.length;\n}\n\n/**\n * `AS PERMISSIVE` / `AS RESTRICTIVE` from either side, or undefined.\n *\n * The DDL spells it as a bare word; `pg_policies.permissive` is the string\n * `\"PERMISSIVE\"`/`\"RESTRICTIVE\"` on modern Postgres but a boolean on some\n * drivers and older servers, so both are accepted. Anything unrecognised\n * becomes `undefined` and the comparison below skips it rather than inventing\n * drift from a shape we did not anticipate.\n */\nfunction normalizeMode(raw: unknown): \"PERMISSIVE\" | \"RESTRICTIVE\" | undefined {\n if (typeof raw === \"boolean\") return raw ? \"PERMISSIVE\" : \"RESTRICTIVE\";\n if (typeof raw !== \"string\") return undefined;\n const upper = raw.trim().toUpperCase();\n if (upper === \"PERMISSIVE\" || upper === \"TRUE\" || upper === \"T\") return \"PERMISSIVE\";\n if (upper === \"RESTRICTIVE\" || upper === \"FALSE\" || upper === \"F\") return \"RESTRICTIVE\";\n return undefined;\n}\n\n/** Parse the generated DDL rather than rebuilding the shape by hand. */\nexport function parseExpectedPolicies(ddl: string): PolicyRef[] {\n const found: PolicyRef[] = [];\n for (const m of ddl.matchAll(CREATE_POLICY)) {\n const [, name, schema, table, modeRaw, command, rolesRaw, clause] = m;\n const roles = rolesRaw\n .split(\",\")\n .map((r) => r.trim().replace(/^\"|\"$/g, \"\"))\n .filter(Boolean);\n\n // The generator emits USING before WITH CHECK, so what follows the TO\n // list settles USING; WITH CHECK is then whatever follows that clause.\n const hasUsing = /USING/i.test(clause);\n const hasWithCheck = hasUsing\n ? WITH_CHECK_NEXT.test(ddl.slice(clauseEnd(ddl, m.index + m[0].length)))\n : /WITH CHECK/i.test(clause);\n\n found.push({\n schema, table, name, roles,\n command: command.toUpperCase(),\n hasUsing, hasWithCheck,\n mode: normalizeMode(modeRaw)\n });\n }\n return found;\n}\n\nasync function readLivePolicies(client: Queryable, schemas: string[]): Promise<PolicyRef[]> {\n const { rows } = await client.query<{\n schemaname: string; tablename: string; policyname: string; roles: string[] | string; cmd: string;\n qual: string | null; with_check: string | null; permissive: string | boolean | null;\n }>(\n `SELECT schemaname, tablename, policyname, roles, cmd, qual, with_check, permissive\n FROM pg_policies\n WHERE schemaname = ANY($1)`,\n [schemas]\n );\n\n return rows.map((r) => ({\n schema: r.schemaname,\n table: r.tablename,\n name: r.policyname,\n // node-postgres yields text[] as an array; some drivers hand back \"{a,b}\".\n roles: Array.isArray(r.roles)\n ? r.roles\n : String(r.roles ?? \"\").replace(/^\\{|\\}$/g, \"\").split(\",\").filter(Boolean),\n command: (r.cmd ?? \"ALL\").toUpperCase(),\n // Presence only. Postgres rewrites the text, but it does not invent or\n // drop a clause: NULL here means the policy genuinely has none.\n hasUsing: r.qual != null,\n hasWithCheck: r.with_check != null,\n mode: normalizeMode(r.permissive),\n qual: r.qual,\n withCheck: r.with_check\n }));\n}\n\n/**\n * Tables the collections describe whose RLS switch is off.\n *\n * Read from `pg_class` because `pg_policies` cannot answer it: disabling RLS\n * leaves the policy rows in place, so every other comparison in this file keeps\n * matching while Postgres applies none of them.\n *\n * Only tables that expect at least one policy are considered. A table with no\n * declared rules is not asserting anything about RLS, and reporting it would\n * make the check noisy on exactly the projects least able to judge the noise.\n */\nasync function readTablesWithRlsOff(\n client: Queryable,\n tables: { schema: string; table: string }[]\n): Promise<{ schema: string; table: string; forced: boolean }[]> {\n if (tables.length === 0) return [];\n const { rows } = await client.query<{\n schemaname: string; tablename: string; relrowsecurity: boolean; relforcerowsecurity: boolean;\n }>(\n `SELECT n.nspname AS schemaname,\n c.relname AS tablename,\n c.relrowsecurity,\n c.relforcerowsecurity\n FROM pg_class c\n JOIN pg_namespace n ON n.oid = c.relnamespace\n WHERE c.relkind = 'r'\n AND (n.nspname || '.' || c.relname) = ANY($1)`,\n [tables.map((t) => `${t.schema}.${t.table}`)]\n );\n\n return rows\n .filter((r) => !r.relrowsecurity)\n .map((r) => ({\n schema: r.schemaname,\n table: r.tablename,\n forced: !!r.relforcerowsecurity\n }));\n}\n\n/**\n * The permissive tautology `rebase.uid() IS NOT NULL`, without the\n * `<> 'anonymous'` guard that makes it mean \"signed in\".\n *\n * Whitespace varies with Postgres's rewrite, so match on a collapsed form. The\n * guard clause ({@link EXCLUDES_ANONYMOUS}, in any spelling) is what\n * distinguishes the corrected policy from the stale one, so its presence\n * clears the text.\n *\n * Both schema spellings are matched. The helpers moved from `auth` to `rebase`\n * in 1.0, and this check reads policies *as the database stored them*: a\n * database pushed before the move still holds `auth.uid()` until it is\n * recompiled, while everything written since — including raw `securityRules`\n * SQL, which the compiler rewrites on the way in — stores `rebase.uid()`.\n * Matching only the pre-1.0 name made the check blind to every policy a current\n * release could write, which is precisely the set still worth scanning.\n */\nfunction isPermissiveAuthTautology(clause: string | null | undefined): boolean {\n if (!clause) return false;\n const flat = clause.toLowerCase().replace(/\\s+/g, \" \");\n // The call bare, or wrapped as an initPlan, which Postgres stores as\n // `( SELECT rebase.uid() AS uid)`.\n if (!/\\b(?:rebase|auth)\\.uid\\s*\\(\\s*\\)(?:\\s+as\\s+\\w+\\s*\\))?\\s*is not null/.test(flat)) return false;\n return !EXCLUDES_ANONYMOUS.some((guard) => guard.test(flat));\n}\n\n/**\n * The guard that makes the null test mean \"signed in\": the anonymous\n * sentinel excluded, in every spelling a policy reaches the catalogue in.\n *\n * `policy.authenticated()` compiles to `NOT IN ('anonymous', 'anon')`, which\n * Postgres stores as `<> ALL (ARRAY['anonymous'::text, 'anon'::text])`. Only\n * `<> 'anonymous'` was recognised, so `doctor` and every `db push` called each\n * `authenticated()` and `registered()` policy the insecure tautology. A list\n * that leaves out `'anonymous'` excludes nobody a signed-out request arrives\n * as, and does not clear it.\n */\nconst EXCLUDES_ANONYMOUS: RegExp[] = [\n /(?:<>|!=)\\s*'anonymous'/,\n /(?:<>|!=)\\s*all\\s*\\(\\s*array\\s*\\[[^\\]]*'anonymous'/,\n /\\bnot in\\s*\\([^)]*'anonymous'/\n];\n\nconst keyOf = (p: PolicyRef) => `${p.schema}.${p.table}.${p.name}`;\nconst sameRoles = (a: string[], b: string[]) =>\n a.length === b.length && [...a].sort().join(\",\") === [...b].sort().join(\",\");\n\n/**\n * Diff expected against live.\n *\n * Compares names, roles, command, and whether each clause exists — all exact\n * values. Policy expression *text* is deliberately not compared: Postgres\n * rewrites `qual`/`with_check` when storing them (parenthesising, casting,\n * schema-qualifying), so text comparison reports drift that does not exist, and\n * a check that cries wolf gets ignored.\n *\n * Presence is not text, though. A NULL `qual` is not a rewrite of an\n * expression, it is the absence of one, and absence has no false-positive risk:\n * either the generator emitted a clause or it did not. That distinction is worth\n * the extra comparison — a production database was found with a SELECT policy\n * whose `qual` was NULL, matching on every field this checked and denying 100%\n * of reads. The same blindness would hide a policy that fails open.\n */\nexport async function checkPolicyDrift(\n client: Queryable,\n collections: CollectionConfig[]\n): Promise<PolicyDrift> {\n const expected = parseExpectedPolicies(generatePostgresPoliciesDdl(collections));\n const schemas = [...new Set(expected.map((p) => p.schema))];\n // Nothing expected means nothing to reconcile against; scanning every\n // schema would report the whole database as orphaned.\n if (schemas.length === 0) return { missing: [], orphaned: [], diverged: [], insecure: [], rlsDisabled: [] };\n\n const live = await readLivePolicies(client, schemas);\n const liveByKey = new Map(live.map((p) => [keyOf(p), p]));\n const expectedByKey = new Map(expected.map((p) => [keyOf(p), p]));\n\n const drift: PolicyDrift = { missing: [], orphaned: [], diverged: [], insecure: [], rlsDisabled: [] };\n\n // Before comparing policy against policy: is the switch even on? A table\n // with RLS disabled matches every expected policy on every field compared\n // below, so this has to be asked separately or it cannot be asked at all.\n const expectedTables = [...new Map(\n expected.map((p) => [`${p.schema}.${p.table}`, { schema: p.schema, table: p.table }])\n ).values()];\n drift.rlsDisabled = await readTablesWithRlsOff(client, expectedTables);\n\n // Scan every live policy for the permissive tautology. This is deliberately\n // independent of the name-keyed diff below: a database pushed before the\n // `authenticated()` fix matches its expected policy on name, roles, command\n // and clause presence, so nothing else here would flag it.\n for (const p of live) {\n const clause = isPermissiveAuthTautology(p.qual)\n ? \"USING\"\n : isPermissiveAuthTautology(p.withCheck) ? \"WITH CHECK\" : null;\n if (clause) {\n drift.insecure.push({\n policy: p,\n reason: `${clause} is \\`${RLS_UID_SQL} IS NOT NULL\\`, which is true for anonymous ` +\n `visitors too — this grants access to signed-out requests. It predates the ` +\n `\\`policy.authenticated()\\` fix; re-run \\`rebase db push\\` to tighten it.`\n });\n }\n }\n\n for (const [key, want] of expectedByKey) {\n const got = liveByKey.get(key);\n if (!got) {\n drift.missing.push(want);\n continue;\n }\n const differences: string[] = [];\n if (!sameRoles(want.roles, got.roles)) {\n differences.push(`roles: expected [${want.roles.join(\", \")}], database has [${got.roles.join(\", \")}]`);\n }\n if (want.command !== got.command) {\n differences.push(`command: expected ${want.command}, database has ${got.command}`);\n }\n // Skipped when either side is unknown: an unrecognised catalogue shape\n // should not manufacture drift. See `normalizeMode`.\n if (want.mode && got.mode && want.mode !== got.mode) {\n differences.push(\n `mode: expected ${want.mode}, database has ${got.mode}` +\n (got.mode === \"PERMISSIVE\"\n ? \" — a RESTRICTIVE rule is ANDed with the others, a PERMISSIVE one is ORed, so this policy now widens access instead of narrowing it\"\n : \" — a PERMISSIVE rule is ORed with the others, a RESTRICTIVE one is ANDed, so this policy now narrows access instead of widening it\")\n );\n }\n for (const clause of [\"USING\", \"WITH CHECK\"] as const) {\n const key = clause === \"USING\" ? \"hasUsing\" : \"hasWithCheck\";\n if (want[key] === got[key]) continue;\n differences.push(want[key]\n ? `${clause}: expected an expression, database has none — this policy matches no rows`\n : `${clause}: expected none, database has an expression`);\n }\n if (differences.length > 0) drift.diverged.push({ expected: want, actual: got, differences });\n }\n\n for (const [key, got] of liveByKey) {\n if (!expectedByKey.has(key)) drift.orphaned.push(got);\n }\n\n return drift;\n}\n\n/**\n * Does this name look like one the generator produced for this table?\n *\n * Unnamed rules compile to `<table>_<op>_<sha1[0:7]>` (plus `_<idx>` when one\n * rule spans several operations), and the hash covers the rule's semantics — so\n * *editing* a rule renames its policy. The policy under the old name is left\n * behind by `db push`, which only DROPs the names it is about to CREATE, and\n * Postgres ORs PERMISSIVE policies together: a superseded `USING (true)` keeps\n * granting everything no matter how tight its replacement is.\n *\n * Matching the shape is what makes dropping them safe. A hand-written policy\n * would have to collide with a 7-hex digest to be mistaken for generated one;\n * a policy named anything else is left alone and merely reported, because a\n * custom name is indistinguishable from one someone wrote in SQL on purpose.\n */\nexport function isGeneratedPolicyName(name: string, table: string): boolean {\n return new RegExp(`^${table.replace(/[.*+?^${}()|[\\]\\\\]/g, \"\\\\$&\")}_(select|insert|update|delete|all)_[0-9a-f]{7}(_\\\\d+)?$`)\n .test(name);\n}\n\nexport interface OrphanCleanup {\n /** Superseded generated policies that were dropped. */\n dropped: PolicyRef[];\n /** Orphans left in place because their names are not generator-shaped. */\n kept: PolicyRef[];\n}\n\n/**\n * Drop the policies an earlier push superseded but never removed.\n *\n * Only touches tables the collections describe — a table with no expected\n * policy is not ours to reconcile, and scanning by schema alone would sweep up\n * policies belonging to something else sharing the database.\n */\nexport async function dropOrphanedPolicies(\n client: Queryable,\n drift: PolicyDrift,\n collections: CollectionConfig[]\n): Promise<OrphanCleanup> {\n const expected = parseExpectedPolicies(generatePostgresPoliciesDdl(collections));\n const managed = new Set(expected.map((p) => `${p.schema}.${p.table}`));\n\n const cleanup: OrphanCleanup = { dropped: [], kept: [] };\n for (const p of drift.orphaned) {\n if (!managed.has(`${p.schema}.${p.table}`) || !isGeneratedPolicyName(p.name, p.table)) {\n cleanup.kept.push(p);\n continue;\n }\n // Identifiers are quoted, and the name came from pg_policies rather than\n // from user input, so it is already a valid identifier.\n await client.query(`DROP POLICY IF EXISTS \"${p.name}\" ON \"${p.schema}\".\"${p.table}\"`);\n cleanup.dropped.push(p);\n }\n return cleanup;\n}\n\nexport const hasDrift = (d: PolicyDrift): boolean =>\n d.missing.length > 0 || d.orphaned.length > 0 || d.diverged.length > 0 || d.insecure.length > 0\n || d.rlsDisabled.length > 0;\n\n/** Human-readable report; empty string when the database matches the config. */\nexport function formatPolicyDrift(drift: PolicyDrift): string {\n if (!hasDrift(drift)) return \"\";\n const lines: string[] = [];\n\n // First, because it subsumes every other finding on the same table: if RLS\n // is off, the policies listed below are not being applied at all.\n if (drift.rlsDisabled.length > 0) {\n lines.push(\" RLS DISABLED — the table has policies, and Postgres is applying none of them:\");\n for (const t of drift.rlsDisabled) {\n lines.push(` • ${t.schema}.${t.table}${t.forced ? \"\" : \" (and FORCE is off)\"}`);\n }\n lines.push(\" Every row is readable and writable by any request that reaches the table.\");\n lines.push(` Fix: ALTER TABLE \"<schema>\".\"<table>\" ENABLE ROW LEVEL SECURITY; or re-run \\`rebase db push\\`.`);\n }\n if (drift.missing.length > 0) {\n lines.push(\" Missing — described by your collections, absent from the database:\");\n for (const p of drift.missing) lines.push(` • ${p.schema}.${p.table} → \"${p.name}\" (${p.command} TO ${p.roles.join(\", \")})`);\n lines.push(\" Run `rebase db push` to apply them.\");\n }\n if (drift.orphaned.length > 0) {\n // Two different things share this list, and they want opposite\n // advice. A name of the shape an unnamed rule compiles to is a rule\n // edited since: the next push drops it. Any other name was written by\n // hand or by another tool, and may be the table's whole access model —\n // \"left behind by an earlier push\" was false of it, and telling its\n // author to drop it would take that access away.\n const superseded = drift.orphaned.filter((p) => isGeneratedPolicyName(p.name, p.table));\n const unowned = drift.orphaned.filter((p) => !isGeneratedPolicyName(p.name, p.table));\n lines.push(\" Orphaned — in the database, described by no collection:\");\n for (const p of drift.orphaned) lines.push(` • ${p.schema}.${p.table} → \"${p.name}\" (${p.command} TO ${p.roles.join(\", \")})`);\n if (superseded.length > 0) {\n lines.push(` ${superseded.map((p) => `\"${p.name}\"`).join(\", \")}: left behind by an earlier push, when its rule changed. The next \\`rebase db push\\` drops it.`);\n }\n if (unowned.length > 0) {\n lines.push(` ${unowned.map((p) => `\"${p.name}\"`).join(\", \")}: written by hand or by another tool, and still in force.`);\n lines.push(\" To keep one, describe it in its collection's `securityRules` (Studio's RLS view imports it);\");\n lines.push(\" if it is stale, drop it by hand.\");\n }\n }\n if (drift.diverged.length > 0) {\n lines.push(\" Diverged — same policy, different definition:\");\n for (const d of drift.diverged) {\n lines.push(` • ${d.expected.schema}.${d.expected.table} → \"${d.expected.name}\"`);\n for (const diff of d.differences) lines.push(` ${diff}`);\n }\n }\n if (drift.insecure.length > 0) {\n lines.push(\" Insecure — a live policy grants access it should not:\");\n for (const i of drift.insecure) {\n lines.push(` • ${i.policy.schema}.${i.policy.table} → \"${i.policy.name}\"`);\n lines.push(` ${i.reason}`);\n }\n }\n return lines.join(\"\\n\");\n}\n"],"mappings":"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"}
@@ -0,0 +1,45 @@
1
+ import { createRequire as __createRequire } from "module";
2
+ __createRequire(import.meta.url);
3
+ import { n as planSchema } from "./plan-schema-CsNgK0qi.js";
4
+ import { s as diffPlanAgainstCatalogue } from "./deferred-index-build-BqmEHGaD.js";
5
+ import { i as readExistingSchema } from "./ensure-collection-tables-egjGBjhv.js";
6
+ import { declaredDatabaseExtensions } from "@rebasepro/types";
7
+ //#region src/schema/push-index-prebuild.ts
8
+ /**
9
+ * The foreign-key indexes `rebase db push` builds before Atlas plans.
10
+ *
11
+ * Rebase derives a btree for every `belongsTo` key and every junction's target
12
+ * column (15b00afcd). On a table that already exists — every table of a
13
+ * project upgraded across that release — the index is missing, and a push left
14
+ * it to Atlas, which builds it inside the push's one transaction: a plain
15
+ * `CREATE INDEX`, holding writes to the table for the whole build, about 0.4 s
16
+ * at a million rows and 6 s at ten million. Atlas's own concurrent mode would
17
+ * run the entire apply outside a transaction, which the push relies on.
18
+ *
19
+ * So the push builds those first, `CONCURRENTLY`, with the builder the boot
20
+ * uses for the same indexes (`deferred-index-build.ts`: one at a time, a lock
21
+ * budget for getting the table, an INVALID leftover dropped and rebuilt, a
22
+ * claim shared with booting instances). Atlas then finds them and plans
23
+ * nothing for them. One it could not build is still Atlas's, as before.
24
+ *
25
+ * Only an index whose columns are already there: a column this push adds is
26
+ * created by Atlas, and its index with it, on a column that holds no values.
27
+ */
28
+ /** The derived foreign-key indexes a live table lacks (or holds INVALID) whose columns it already has. */
29
+ async function missingForeignKeyIndexes(client, collections) {
30
+ const schema = planSchema(collections, { databaseExtensions: declaredDatabaseExtensions() });
31
+ const existing = await readExistingSchema(client, Array.from(new Set(schema.tables.map((table) => table.schema))), { probeTables: () => [] });
32
+ const plan = diffPlanAgainstCatalogue(schema, existing);
33
+ const specs = new Map(schema.tables.flatMap((table) => table.indexes).map((spec) => [`${spec.schema}.${spec.indexName}`, spec]));
34
+ return plan.deferredIndexes.filter((build) => {
35
+ const spec = specs.get(`${build.schema}.${build.indexName}`);
36
+ if (spec?.derived !== "foreign-key") return false;
37
+ const columns = existing.tables.get(`${build.schema}.${build.table}`);
38
+ if (!columns) return false;
39
+ return [...spec.keys.map((key) => key.column), ...spec.include].every((column) => columns.has(column));
40
+ });
41
+ }
42
+ //#endregion
43
+ export { missingForeignKeyIndexes };
44
+
45
+ //# sourceMappingURL=push-index-prebuild-C_qhyoJa.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"push-index-prebuild-C_qhyoJa.js","names":[],"sources":["../src/schema/push-index-prebuild.ts"],"sourcesContent":["/**\n * The foreign-key indexes `rebase db push` builds before Atlas plans.\n *\n * Rebase derives a btree for every `belongsTo` key and every junction's target\n * column (15b00afcd). On a table that already exists — every table of a\n * project upgraded across that release — the index is missing, and a push left\n * it to Atlas, which builds it inside the push's one transaction: a plain\n * `CREATE INDEX`, holding writes to the table for the whole build, about 0.4 s\n * at a million rows and 6 s at ten million. Atlas's own concurrent mode would\n * run the entire apply outside a transaction, which the push relies on.\n *\n * So the push builds those first, `CONCURRENTLY`, with the builder the boot\n * uses for the same indexes (`deferred-index-build.ts`: one at a time, a lock\n * budget for getting the table, an INVALID leftover dropped and rebuilt, a\n * claim shared with booting instances). Atlas then finds them and plans\n * nothing for them. One it could not build is still Atlas's, as before.\n *\n * Only an index whose columns are already there: a column this push adds is\n * created by Atlas, and its index with it, on a column that holds no values.\n */\nimport { declaredDatabaseExtensions, type CollectionConfig } from \"@rebasepro/types\";\nimport { planSchema } from \"./plan/plan-schema\";\nimport { diffPlanAgainstCatalogue, type DeferredIndexBuild } from \"./plan/diff-plan\";\nimport { readExistingSchema, type Queryable } from \"./ensure-collection-tables\";\n\n/** The derived foreign-key indexes a live table lacks (or holds INVALID) whose columns it already has. */\nexport async function missingForeignKeyIndexes(\n client: Queryable,\n collections: CollectionConfig[]\n): Promise<DeferredIndexBuild[]> {\n const schema = planSchema(collections, { databaseExtensions: declaredDatabaseExtensions() });\n const schemas = Array.from(new Set(schema.tables.map(table => table.schema)));\n // No row probes: whether a table holds rows decides only constraints on a\n // column being added, which this does not plan.\n const existing = await readExistingSchema(client, schemas, { probeTables: () => [] });\n const plan = diffPlanAgainstCatalogue(schema, existing);\n\n const specs = new Map(schema.tables.flatMap(table => table.indexes)\n .map(spec => [`${spec.schema}.${spec.indexName}`, spec]));\n return plan.deferredIndexes.filter(build => {\n const spec = specs.get(`${build.schema}.${build.indexName}`);\n if (spec?.derived !== \"foreign-key\") return false;\n const columns = existing.tables.get(`${build.schema}.${build.table}`);\n if (!columns) return false;\n return [...spec.keys.map(key => key.column), ...spec.include].every(column => columns.has(column));\n });\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;AA0BA,eAAsB,yBAClB,QACA,aAC6B;CAC7B,MAAM,SAAS,WAAW,aAAa,EAAE,oBAAoB,2BAA2B,EAAE,CAAC;CAI3F,MAAM,WAAW,MAAM,mBAAmB,QAH1B,MAAM,KAAK,IAAI,IAAI,OAAO,OAAO,KAAI,UAAS,MAAM,MAAM,CAAC,CAGzB,GAAS,EAAE,mBAAmB,CAAC,EAAE,CAAC;CACpF,MAAM,OAAO,yBAAyB,QAAQ,QAAQ;CAEtD,MAAM,QAAQ,IAAI,IAAI,OAAO,OAAO,SAAQ,UAAS,MAAM,OAAO,CAAC,CAC9D,KAAI,SAAQ,CAAC,GAAG,KAAK,OAAO,GAAG,KAAK,aAAa,IAAI,CAAC,CAAC;CAC5D,OAAO,KAAK,gBAAgB,QAAO,UAAS;EACxC,MAAM,OAAO,MAAM,IAAI,GAAG,MAAM,OAAO,GAAG,MAAM,WAAW;EAC3D,IAAI,MAAM,YAAY,eAAe,OAAO;EAC5C,MAAM,UAAU,SAAS,OAAO,IAAI,GAAG,MAAM,OAAO,GAAG,MAAM,OAAO;EACpE,IAAI,CAAC,SAAS,OAAO;EACrB,OAAO,CAAC,GAAG,KAAK,KAAK,KAAI,QAAO,IAAI,MAAM,GAAG,GAAG,KAAK,OAAO,CAAC,CAAC,OAAM,WAAU,QAAQ,IAAI,MAAM,CAAC;CACrG,CAAC;AACL"}
@@ -0,0 +1,34 @@
1
+ import { createRequire as __createRequire } from "module";
2
+ __createRequire(import.meta.url);
3
+ import { n as planSchema } from "./plan-schema-CsNgK0qi.js";
4
+ import { s as diffPlanAgainstCatalogue } from "./deferred-index-build-BqmEHGaD.js";
5
+ import { i as readExistingSchema } from "./ensure-collection-tables-egjGBjhv.js";
6
+ import { declaredDatabaseExtensions } from "@rebasepro/types";
7
+ //#region src/schema/push-legacy-renames.ts
8
+ /**
9
+ * The relation columns an aged database still holds under the name Rebase
10
+ * derived before 0.13 (`categorie_id`, `people_id`), for `db push` to rename
11
+ * before Atlas plans.
12
+ *
13
+ * Boot renames them losslessly (`diff-plan.ts`, `renameLegacyColumn`). Push
14
+ * runs Atlas, which sees only "drop `categorie_id`, add `category_id` NOT
15
+ * NULL": it listed the links as destructive changes to approve, and with
16
+ * `--allow-destructive` dropped them. A pipeline that runs only `db push`
17
+ * could not upgrade an aged database at all. Renaming first — the same
18
+ * metadata-only statement boot runs — leaves Atlas nothing to plan.
19
+ */
20
+ async function legacyForeignKeyRenames(client, collections) {
21
+ const schema = planSchema(collections, { databaseExtensions: declaredDatabaseExtensions() });
22
+ const plan = diffPlanAgainstCatalogue(schema, await readExistingSchema(client, Array.from(new Set(schema.tables.map((table) => table.schema))), { probeTables: () => [] }));
23
+ return plan.legacyForeignKeys.map((legacy) => {
24
+ const action = plan.actions.find((a) => a.kind === "rename-column" && a.target === `${legacy.table}.${legacy.expected}`);
25
+ return {
26
+ ...legacy,
27
+ sql: action?.sql ?? ""
28
+ };
29
+ }).filter((rename) => rename.sql !== "");
30
+ }
31
+ //#endregion
32
+ export { legacyForeignKeyRenames };
33
+
34
+ //# sourceMappingURL=push-legacy-renames-5g9h3HOa.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"push-legacy-renames-5g9h3HOa.js","names":[],"sources":["../src/schema/push-legacy-renames.ts"],"sourcesContent":["/**\n * The relation columns an aged database still holds under the name Rebase\n * derived before 0.13 (`categorie_id`, `people_id`), for `db push` to rename\n * before Atlas plans.\n *\n * Boot renames them losslessly (`diff-plan.ts`, `renameLegacyColumn`). Push\n * runs Atlas, which sees only \"drop `categorie_id`, add `category_id` NOT\n * NULL\": it listed the links as destructive changes to approve, and with\n * `--allow-destructive` dropped them. A pipeline that runs only `db push`\n * could not upgrade an aged database at all. Renaming first — the same\n * metadata-only statement boot runs — leaves Atlas nothing to plan.\n */\nimport { declaredDatabaseExtensions, type CollectionConfig } from \"@rebasepro/types\";\nimport { planSchema } from \"./plan/plan-schema\";\nimport { diffPlanAgainstCatalogue, type LegacyForeignKey } from \"./plan/diff-plan\";\nimport { readExistingSchema, type Queryable } from \"./ensure-collection-tables\";\n\nexport interface LegacyRename extends LegacyForeignKey {\n /** `ALTER TABLE … RENAME COLUMN …;` */\n sql: string;\n}\n\nexport async function legacyForeignKeyRenames(client: Queryable, collections: CollectionConfig[]): Promise<LegacyRename[]> {\n const schema = planSchema(collections, { databaseExtensions: declaredDatabaseExtensions() });\n const schemas = Array.from(new Set(schema.tables.map(table => table.schema)));\n const existing = await readExistingSchema(client, schemas, { probeTables: () => [] });\n const plan = diffPlanAgainstCatalogue(schema, existing);\n return plan.legacyForeignKeys.map(legacy => {\n const action = plan.actions.find(a => a.kind === \"rename-column\" && a.target === `${legacy.table}.${legacy.expected}`);\n return { ...legacy, sql: action?.sql ?? \"\" };\n }).filter(rename => rename.sql !== \"\");\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;AAsBA,eAAsB,wBAAwB,QAAmB,aAA0D;CACvH,MAAM,SAAS,WAAW,aAAa,EAAE,oBAAoB,2BAA2B,EAAE,CAAC;CAG3F,MAAM,OAAO,yBAAyB,QAAQ,MADvB,mBAAmB,QAD1B,MAAM,KAAK,IAAI,IAAI,OAAO,OAAO,KAAI,UAAS,MAAM,MAAM,CAAC,CACzB,GAAS,EAAE,mBAAmB,CAAC,EAAE,CAAC,CAC9B;CACtD,OAAO,KAAK,kBAAkB,KAAI,WAAU;EACxC,MAAM,SAAS,KAAK,QAAQ,MAAK,MAAK,EAAE,SAAS,mBAAmB,EAAE,WAAW,GAAG,OAAO,MAAM,GAAG,OAAO,UAAU;EACrH,OAAO;GAAE,GAAG;GAAQ,KAAK,QAAQ,OAAO;EAAG;CAC/C,CAAC,CAAC,CAAC,QAAO,WAAU,OAAO,QAAQ,EAAE;AACzC"}