@rebasepro/server-postgres 0.25.0-canary.ge06257e → 0.25.1

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 (126) hide show
  1. package/dist/{BranchService-DNg4txyf.js → BranchService-ClklxyAQ.js} +283 -20
  2. package/dist/BranchService-ClklxyAQ.js.map +1 -0
  3. package/dist/backup/backup-cli.d.ts +70 -1
  4. package/dist/backup/backup-service.d.ts +79 -0
  5. package/dist/backup/pg-binary.d.ts +2 -2
  6. package/dist/backup/pg-tools.d.ts +60 -1
  7. package/dist/backup-cli-gijeXe0u.js +1961 -0
  8. package/dist/backup-cli-gijeXe0u.js.map +1 -0
  9. package/dist/{boot-ddl-Dnvpmm_T.js → boot-ddl-DYH95Qeg.js} +3 -3
  10. package/dist/{boot-ddl-Dnvpmm_T.js.map → boot-ddl-DYH95Qeg.js.map} +1 -1
  11. package/dist/branch-argv.d.ts +15 -0
  12. package/dist/branch-prune.d.ts +18 -0
  13. package/dist/{cli-console-C_ISGwJo.js → cli-console-CQrQ0lAY.js} +19 -13
  14. package/dist/cli-console-CQrQ0lAY.js.map +1 -0
  15. package/dist/cli-console.d.ts +4 -3
  16. package/dist/{cli-errors-mx4sSjB8.js → cli-errors-DiA0XFIS.js} +110 -14
  17. package/dist/{cli-errors-mx4sSjB8.js.map → cli-errors-DiA0XFIS.js.map} +1 -1
  18. package/dist/cli-errors.d.ts +26 -1
  19. package/dist/cli-flags.d.ts +51 -0
  20. package/dist/{cli-prompt-egtNKkZl.js → cli-prompt-DXG0yk-h.js} +39 -4
  21. package/dist/{cli-prompt-egtNKkZl.js.map → cli-prompt-DXG0yk-h.js.map} +1 -1
  22. package/dist/cli.js +120 -184
  23. package/dist/cli.js.map +1 -1
  24. package/dist/collections/validate-relations.d.ts +8 -2
  25. package/dist/{column-plan-helpers-D_V4oaZh.js → column-plan-helpers-IG6uFb1s.js} +2 -2
  26. package/dist/{column-plan-helpers-D_V4oaZh.js.map → column-plan-helpers-IG6uFb1s.js.map} +1 -1
  27. package/dist/{connection-C938nR3E.js → connection-czvI7TZL.js} +57 -10
  28. package/dist/connection-czvI7TZL.js.map +1 -0
  29. package/dist/{databasePoolManager-Yqdw14wY.js → databasePoolManager-3stMNFL-.js} +4 -6
  30. package/dist/databasePoolManager-3stMNFL-.js.map +1 -0
  31. package/dist/db-command-line.d.ts +14 -0
  32. package/dist/{rls-bootstrap-sql-Bd-hJk08.js → db-subcommands-BPoHCxJR.js} +455 -3
  33. package/dist/db-subcommands-BPoHCxJR.js.map +1 -0
  34. package/dist/db-subcommands.d.ts +11 -0
  35. package/dist/{deferred-index-build-Ca6OqKyR.js → deferred-index-build-BE0I6sYU.js} +8 -8
  36. package/dist/{deferred-index-build-Ca6OqKyR.js.map → deferred-index-build-BE0I6sYU.js.map} +1 -1
  37. package/dist/{doctor-CJyuhV1n.js → doctor-BT--1kq3.js} +107 -11
  38. package/dist/doctor-BT--1kq3.js.map +1 -0
  39. package/dist/{ensure-collection-policies-C1ux3V_z.js → ensure-collection-policies-BnGOURpG.js} +60 -11
  40. package/dist/ensure-collection-policies-BnGOURpG.js.map +1 -0
  41. package/dist/{ensure-collection-tables-Do-Bvb64.js → ensure-collection-tables-Cet4650m.js} +9 -9
  42. package/dist/{ensure-collection-tables-Do-Bvb64.js.map → ensure-collection-tables-Cet4650m.js.map} +1 -1
  43. package/dist/{ensure-tables-Dl_Eh9ow.js → ensure-tables-l_OgFQjN.js} +5 -5
  44. package/dist/{ensure-tables-Dl_Eh9ow.js.map → ensure-tables-l_OgFQjN.js.map} +1 -1
  45. package/dist/{enum-labels-kept-D9eTIWCz.js → enum-labels-kept-nheku1pB.js} +3 -3
  46. package/dist/{enum-labels-kept-D9eTIWCz.js.map → enum-labels-kept-nheku1pB.js.map} +1 -1
  47. package/dist/{generate-drizzle-schema-Bf8canaR.js → generate-drizzle-schema-DC0BOUpn.js} +3 -3
  48. package/dist/{generate-drizzle-schema-Bf8canaR.js.map → generate-drizzle-schema-DC0BOUpn.js.map} +1 -1
  49. package/dist/{generate-drizzle-schema-logic-BaT0H7Gq.js → generate-drizzle-schema-logic-CPzCRD-8.js} +4 -4
  50. package/dist/{generate-drizzle-schema-logic-BaT0H7Gq.js.map → generate-drizzle-schema-logic-CPzCRD-8.js.map} +1 -1
  51. package/dist/{generate-files-D50Pxw1G.js → generate-files-BP-4W_gm.js} +6 -6
  52. package/dist/{generate-files-D50Pxw1G.js.map → generate-files-BP-4W_gm.js.map} +1 -1
  53. package/dist/{generate-next-step-DCd7yiJ2.js → generate-next-step-DgK5BlBn.js} +22 -14
  54. package/dist/{generate-next-step-DCd7yiJ2.js.map → generate-next-step-DgK5BlBn.js.map} +1 -1
  55. package/dist/{generate-postgres-ddl-logic-B4k9djAV.js → generate-postgres-ddl-logic-DPl1oH_z.js} +4 -4
  56. package/dist/{generate-postgres-ddl-logic-B4k9djAV.js.map → generate-postgres-ddl-logic-DPl1oH_z.js.map} +1 -1
  57. package/dist/generated-schema-diff-eZCklRZQ.js +1025 -0
  58. package/dist/generated-schema-diff-eZCklRZQ.js.map +1 -0
  59. package/dist/{generated-schema-staleness-FlxM2i4i.js → generated-schema-staleness-BvNF3i-s.js} +2 -2
  60. package/dist/{generated-schema-staleness-FlxM2i4i.js.map → generated-schema-staleness-BvNF3i-s.js.map} +1 -1
  61. package/dist/index.d.ts +1 -0
  62. package/dist/index.es.js +70 -11
  63. package/dist/index.es.js.map +1 -1
  64. package/dist/{introspect-db-logic-j2vYhmdP.js → introspect-db-logic-DgQVzccW.js} +2 -2
  65. package/dist/{introspect-db-logic-j2vYhmdP.js.map → introspect-db-logic-DgQVzccW.js.map} +1 -1
  66. package/dist/migrate-policy-stamps-3eqfhes6.js +89 -0
  67. package/dist/migrate-policy-stamps-3eqfhes6.js.map +1 -0
  68. package/dist/{open-rebase-D8rytTP0.js → open-rebase-EmqVmS14.js} +78 -1027
  69. package/dist/open-rebase-EmqVmS14.js.map +1 -0
  70. package/dist/{pg-error-utils-CdsGYMTm.js → pg-error-utils-CFEnm2m9.js} +11 -4
  71. package/dist/pg-error-utils-CFEnm2m9.js.map +1 -0
  72. package/dist/{plan-schema-CsNgK0qi.js → plan-schema-Czf3q4-Q.js} +4 -4
  73. package/dist/{plan-schema-CsNgK0qi.js.map → plan-schema-Czf3q4-Q.js.map} +1 -1
  74. package/dist/{policy-drift-CY5P1ZRu.js → policy-drift-X4A-xAzv.js} +3 -3
  75. package/dist/{policy-drift-CY5P1ZRu.js.map → policy-drift-X4A-xAzv.js.map} +1 -1
  76. package/dist/pool-disconnects.d.ts +28 -0
  77. package/dist/{push-index-prebuild-BDFcfRPo.js → push-index-prebuild-COd7i6wm.js} +4 -4
  78. package/dist/{push-index-prebuild-BDFcfRPo.js.map → push-index-prebuild-COd7i6wm.js.map} +1 -1
  79. package/dist/{push-legacy-renames-D4bvMWAb.js → push-legacy-renames-CLgYYwPE.js} +4 -4
  80. package/dist/{push-legacy-renames-D4bvMWAb.js.map → push-legacy-renames-CLgYYwPE.js.map} +1 -1
  81. package/dist/{push-policy-plan-DTXE6aVa.js → push-policy-plan-D2gpQsdv.js} +23 -7
  82. package/dist/push-policy-plan-D2gpQsdv.js.map +1 -0
  83. package/dist/{render-ddl-CrfEe__B.js → render-ddl-4v88y-Gv.js} +4 -4
  84. package/dist/{render-ddl-CrfEe__B.js.map → render-ddl-4v88y-Gv.js.map} +1 -1
  85. package/dist/{rls-enforcement-D7P4-ZYX.js → rls-enforcement-DDyNoOSy.js} +2 -2
  86. package/dist/{rls-enforcement-D7P4-ZYX.js.map → rls-enforcement-DDyNoOSy.js.map} +1 -1
  87. package/dist/schema/atlas-argv.d.ts +3 -1
  88. package/dist/schema/doctor-cli.js +5 -5
  89. package/dist/schema/doctor.d.ts +20 -2
  90. package/dist/schema/ensure-collection-policies.d.ts +28 -1
  91. package/dist/schema/generate-drizzle-schema.js +1 -1
  92. package/dist/schema/generate-next-step.d.ts +20 -8
  93. package/dist/schema/generate-postgres-ddl.js +2 -2
  94. package/dist/schema/generated-schema-diff.d.ts +21 -0
  95. package/dist/schema/introspect-db.js +6 -6
  96. package/dist/schema/introspect-db.js.map +1 -1
  97. package/dist/schema/migrate-policy-stamps.d.ts +23 -0
  98. package/dist/schema/push-policy-plan.d.ts +10 -2
  99. package/dist/services/BranchService.d.ts +85 -0
  100. package/dist/services/pg-notify-listener.d.ts +2 -0
  101. package/dist/{source-DB6xJv3p.js → source-fZb92pN8.js} +46 -3
  102. package/dist/source-fZb92pN8.js.map +1 -0
  103. package/dist/{vector-index-BFhyEaiz.js → vector-index-DsQ3V2d2.js} +2 -2
  104. package/dist/{vector-index-BFhyEaiz.js.map → vector-index-DsQ3V2d2.js.map} +1 -1
  105. package/dist/{websocket-DMeXOoyv.js → websocket-BKZwsjmf.js} +18 -5
  106. package/dist/websocket-BKZwsjmf.js.map +1 -0
  107. package/package.json +7 -7
  108. package/dist/BranchService-DNg4txyf.js.map +0 -1
  109. package/dist/backup-cli-HCEkPcQj.js +0 -556
  110. package/dist/backup-cli-HCEkPcQj.js.map +0 -1
  111. package/dist/backup-service-D-UpT7DH.js +0 -857
  112. package/dist/backup-service-D-UpT7DH.js.map +0 -1
  113. package/dist/cli-console-C_ISGwJo.js.map +0 -1
  114. package/dist/cli-flags-DUozBFyK.js +0 -205
  115. package/dist/cli-flags-DUozBFyK.js.map +0 -1
  116. package/dist/connection-C938nR3E.js.map +0 -1
  117. package/dist/databasePoolManager-Yqdw14wY.js.map +0 -1
  118. package/dist/doctor-CJyuhV1n.js.map +0 -1
  119. package/dist/ensure-collection-policies-C1ux3V_z.js.map +0 -1
  120. package/dist/open-rebase-D8rytTP0.js.map +0 -1
  121. package/dist/pg-error-utils-CdsGYMTm.js.map +0 -1
  122. package/dist/push-policy-plan-DTXE6aVa.js.map +0 -1
  123. package/dist/rls-bootstrap-sql-Bd-hJk08.js.map +0 -1
  124. package/dist/rolldown-runtime-V7rFbMRe.js +0 -39
  125. package/dist/source-DB6xJv3p.js.map +0 -1
  126. package/dist/websocket-DMeXOoyv.js.map +0 -1
@@ -0,0 +1 @@
1
+ {"version":3,"file":"generated-schema-diff-eZCklRZQ.js","names":[],"sources":["../src/schema/introspect-runtime.ts","../src/schema/dynamic-tables.ts","../src/schema/config-relations.ts","../src/schema/catalogue-schema.ts","../src/schema/generated-schema-diff.ts"],"sourcesContent":["/**\n * Runtime introspection — builds collections in memory from the live database.\n *\n * This is what makes BaaS mode work with zero configuration: instead of loading\n * collection files from disk, the server reads `information_schema` at boot and\n * derives a collection per table, so any database is served over REST without a\n * single config file.\n *\n * Distinct from `introspect-db.ts`, which runs the same queries but emits\n * TypeScript *source* for a developer to edit and commit (declared collections). The two\n * share the mapping helpers in `introspect-db-logic.ts` so a table is described\n * the same way whether it was generated or introspected.\n */\nimport type { PostgresCollectionConfig } from \"@rebasepro/types\";\n\nimport {\n TableRow,\n TableColumn,\n EnumValue,\n PrimaryKeyRow,\n ForeignKeyRow,\n TableMeta,\n buildTablesMap,\n buildEnumMap,\n identifyJoinTables,\n singularize,\n mapPgType,\n getIconForTable\n} from \"./introspect-db-logic\";\nimport { humanize } from \"./introspect-db-naming\";\nimport { firstFreeKey, toWireKey } from \"@rebasepro/utils\";\n\nexport interface IntrospectedSchema {\n tablesMap: Map<string, TableMeta>;\n enumMap: Map<string, string[]>;\n joinTables: Set<string>;\n}\n\n/** Whether a table carries an authorization model of its own. */\nexport interface TableRlsStatus {\n table: string;\n /** ALTER TABLE … ENABLE ROW LEVEL SECURITY has been run. */\n rlsEnabled: boolean;\n /** Policies attached to it. RLS enabled with none = nothing is visible. */\n policyCount: number;\n}\n\n/**\n * Read the RLS posture of each table in a schema.\n *\n * This is what decides whether baas mode may serve a table. A table with RLS\n * disabled has no authorization model: since every authenticated request runs\n * as `rebase_user`, and that role is granted DML on the schema, serving such a\n * table hands every row to every logged-in user.\n */\nexport async function readRlsStatus(client: Queryable, pgSchema: string): Promise<Map<string, TableRlsStatus>> {\n const { rows } = await client.query<{ table: string; rls_enabled: boolean; policy_count: string | number }>(\n `SELECT c.relname AS table,\n c.relrowsecurity AS rls_enabled,\n (SELECT count(*) FROM pg_policy p WHERE p.polrelid = c.oid) AS policy_count\n FROM pg_class c\n JOIN pg_namespace n ON n.oid = c.relnamespace\n WHERE n.nspname = $1 AND c.relkind = 'r'`,\n [pgSchema]\n );\n\n return new Map(\n rows.map((r) => [\n r.table,\n { table: r.table, rlsEnabled: r.rls_enabled === true, policyCount: Number(r.policy_count ?? 0) }\n ])\n );\n}\n\n/** Minimal query surface — satisfied by pg.Client and pg.Pool alike. */\nexport interface Queryable {\n query<R>(text: string, values?: unknown[]): Promise<{ rows: R[] }>;\n}\n\n/**\n * Read tables, columns, enums, primary keys and foreign keys for a schema.\n * Mirrors the queries in introspect-db.ts.\n */\nexport async function introspectSchema(client: Queryable, pgSchema: string): Promise<IntrospectedSchema> {\n const { rows: tables } = await client.query<TableRow>(\n `SELECT table_name\n FROM information_schema.tables\n WHERE table_schema = $1 AND table_type = 'BASE TABLE'\n AND table_name NOT LIKE 'drizzle_%'\n AND table_name NOT LIKE 'rebase_%'\n ORDER BY table_name`,\n [pgSchema]\n );\n\n const { rows: columns } = await client.query<TableColumn>(\n `SELECT\n c.table_name,\n c.column_name,\n c.data_type,\n c.udt_name,\n c.is_nullable,\n c.column_default,\n -- A stored generated column must be declared as one, or drizzle\n -- writes DEFAULT into it on every INSERT. Postgres tolerates that\n -- today; the generated module has always declared them properly,\n -- and the runtime tables are built to match it.\n c.is_generated,\n c.generation_expression,\n (SELECT a.atttypmod FROM pg_attribute a\n JOIN pg_class pc ON a.attrelid = pc.oid\n WHERE pc.relname = c.table_name\n AND a.attname = c.column_name\n AND pc.relnamespace = (SELECT oid FROM pg_namespace WHERE nspname = c.table_schema)) as atttypmod\n FROM information_schema.columns c\n WHERE c.table_schema = $1`,\n [pgSchema]\n );\n\n const { rows: enumValues } = await client.query<EnumValue>(\n `SELECT t.typname AS enum_name,\n e.enumlabel AS enum_value,\n e.enumsortorder AS sort_order\n FROM pg_type t\n JOIN pg_enum e ON t.oid = e.enumtypid\n JOIN pg_namespace n ON t.typnamespace = n.oid\n WHERE n.nspname = $1\n ORDER BY t.typname, e.enumsortorder`,\n [pgSchema]\n );\n\n const { rows: pks } = await client.query<PrimaryKeyRow>(\n `SELECT t.relname as table_name, a.attname as column_name\n FROM pg_index i\n JOIN pg_attribute a ON a.attrelid = i.indrelid\n AND a.attnum = ANY(i.indkey)\n JOIN pg_class t ON t.oid = i.indrelid\n JOIN pg_namespace n ON n.oid = t.relnamespace\n WHERE i.indisprimary AND n.nspname = $1`,\n [pgSchema]\n );\n\n const { rows: fks } = await client.query<ForeignKeyRow>(\n `SELECT\n tc.table_name,\n kcu.column_name,\n ccu.table_name AS foreign_table_name,\n ccu.column_name AS foreign_column_name\n FROM information_schema.table_constraints AS tc\n JOIN information_schema.key_column_usage AS kcu\n ON tc.constraint_name = kcu.constraint_name\n AND tc.table_schema = kcu.table_schema\n JOIN information_schema.constraint_column_usage AS ccu\n ON ccu.constraint_name = tc.constraint_name\n AND ccu.table_schema = tc.table_schema\n WHERE tc.constraint_type = 'FOREIGN KEY' AND tc.table_schema = $1`,\n [pgSchema]\n );\n\n const tablesMap = buildTablesMap(tables, columns, pks, fks);\n return {\n tablesMap,\n enumMap: buildEnumMap(enumValues),\n joinTables: identifyJoinTables(tablesMap)\n };\n}\n\n/** Derive the `isId` flavour for a primary-key column. */\nfunction idKindFor(col: TableColumn, propType: string): \"uuid\" | \"increment\" | true {\n if (col.data_type.toLowerCase() === \"uuid\") return \"uuid\";\n if (propType === \"number\") return \"increment\";\n return true;\n}\n\nfunction buildProperties(\n meta: TableMeta,\n enumMap: Map<string, string[]>\n): Record<string, Record<string, unknown>> {\n const properties: Record<string, Record<string, unknown>> = {};\n const takenKeys = new Set<string>();\n\n for (const col of meta.columns) {\n const isPk = meta.pks.includes(col.column_name);\n // Foreign keys surface as relations below, unless they are also part of\n // the primary key, in which case the column itself must stay.\n const isFk = meta.fks.some((fk) => fk.column_name === col.column_name);\n if (isFk && !isPk) continue;\n\n const enumValues = enumMap.get(col.udt_name);\n const isEnum = col.data_type === \"USER-DEFINED\" && enumValues !== undefined;\n const isVector = col.udt_name === \"vector\";\n const propType = isEnum ? \"string\" : isVector ? \"vector\" : mapPgType(col.data_type);\n\n const property: Record<string, unknown> = {\n name: humanize(col.column_name),\n columnName: col.column_name,\n type: propType\n };\n\n // The wire name, with `columnName` above carrying the column. The two\n // are different names for different things — this used to serve the\n // column, so a runtime-introspected collection answered `user_id` while\n // every authored one beside it answered `displayName`.\n //\n // `user_id` and `userId` as two real columns camel-case to one key, so\n // the first free candidate wins and the second falls back to its own\n // column name, then to a numbered tail. Never dropped, and stable for a\n // given database: columns arrive in ordinal order.\n const key = firstFreeKey([toWireKey(col.column_name), col.column_name], takenKeys);\n takenKeys.add(key);\n\n if (isPk) {\n property.isId = idKindFor(col, propType);\n } else if (col.is_nullable === \"NO\" && col.column_default === null) {\n property.validation = { required: true };\n }\n\n if (isEnum && enumValues) {\n property.enum = enumValues.map((value) => ({ id: value, label: humanize(value) }));\n }\n\n properties[key] = property;\n }\n\n return properties;\n}\n\n/**\n * Owning relations, derived from this table's foreign keys — the same shape\n * `generateCollectionFile` writes into a collection file: a `relation` property\n * whose nested descriptor carries `kind`, a `target` thunk and the `localKey`.\n *\n * The shape is load-bearing, not cosmetic. `resolveCollectionRelations` reads\n * relations from `property.relation` and `resolveRelation` requires `target` to\n * be a thunk; this used to emit `target`/`cardinality`/`localKey` flat on the\n * property with the slug as a bare string, which satisfies neither. Nothing\n * threw — the resolver simply skipped every such property and reported that the\n * collection had no relations. So an introspected BaaS collection had its FK\n * columns removed from `properties` (they \"surface as relations\") and then no\n * resolvable relation to surface as, which is why writing the FK column\n * directly came back as `has no field 'product_id'`: `assertKnownWriteFields`\n * learns that column from the resolved relation's `localKey`.\n *\n * The thunk closes over the collections being built in this same pass rather\n * than importing a module, which is what a runtime introspection has instead of\n * generated files. It is called lazily, after the map is fully populated, so a\n * table may reference one introspected later.\n */\nfunction buildRelations(\n meta: TableMeta,\n slugByTable: Map<string, string>,\n collectionBySlug: Map<string, PostgresCollectionConfig>\n): Record<string, Record<string, unknown>> {\n const relations: Record<string, Record<string, unknown>> = {};\n\n for (const fk of meta.fks) {\n const targetSlug = slugByTable.get(fk.foreign_table_name);\n if (!targetSlug) continue;\n\n // Strip the conventional _id suffix: author_id -> author\n let key = toWireKey(fk.column_name.replace(/_id$/, \"\"));\n if (meta.pks.includes(fk.column_name) && key === fk.column_name) {\n // The FK is also the PK and its name doesn't imply a relation (e.g.\n // \"id\"), so naming the relation after the column would collide with\n // the primary-key property.\n key = fk.foreign_table_name;\n }\n\n relations[key] = {\n name: humanize(key),\n type: \"relation\",\n relation: {\n kind: \"belongsTo\",\n target: () => collectionBySlug.get(targetSlug),\n localKey: fk.column_name\n }\n };\n }\n\n return relations;\n}\n\n/**\n * Turn an introspected schema into collections.\n *\n * Join tables are skipped: they carry no identity of their own and exist to\n * express a many-to-many edge between two other tables.\n */\nexport function buildCollectionsFromSchema(\n { tablesMap, enumMap, joinTables }: IntrospectedSchema,\n pgSchema: string\n): PostgresCollectionConfig[] {\n const slugByTable = new Map<string, string>();\n for (const tableName of tablesMap.keys()) {\n if (!joinTables.has(tableName)) slugByTable.set(tableName, tableName);\n }\n\n const collections: PostgresCollectionConfig[] = [];\n // Filled as we go; the relation thunks read it lazily, so a table may point\n // at one that has not been built yet at the moment its relation is created.\n const collectionBySlug = new Map<string, PostgresCollectionConfig>();\n\n for (const [tableName, meta] of tablesMap) {\n if (joinTables.has(tableName)) continue;\n\n const collectionName = humanize(tableName);\n const collection = {\n name: collectionName,\n singularName: singularize(collectionName),\n slug: tableName,\n table: tableName,\n schema: pgSchema,\n icon: getIconForTable(tableName),\n properties: {\n ...buildProperties(meta, enumMap),\n ...buildRelations(meta, slugByTable, collectionBySlug)\n }\n // A collection synthesised from a catalogue read, not authored.\n // `PostgresCollectionConfig` is generic over the row type `M`, and\n // there is no `M` here — the columns were discovered a moment ago —\n // so the object is built from the fields introspection can supply\n // and asserted into the config the rest of the server consumes.\n } as unknown as PostgresCollectionConfig;\n\n collections.push(collection);\n collectionBySlug.set(tableName, collection);\n }\n\n return collections;\n}\n\n/** Introspect the database and return ready-to-serve collections. */\nexport async function introspectCollections(\n client: Queryable,\n pgSchema: string\n): Promise<PostgresCollectionConfig[]> {\n const schema = await introspectSchema(client, pgSchema);\n return buildCollectionsFromSchema(schema, pgSchema);\n}\n","/**\n * Build drizzle tables at runtime from an introspected schema.\n *\n * **Every** data source's tables are built here, from `information_schema`,\n * after boot-ensure has run — a project with declared collections included. The\n * committed `schema.generated.ts` is a build artefact for Atlas, `db push`,\n * `eject` and user code; the runtime does not read it for its tables, because a\n * file that is one commit behind the database used to change what the server\n * served (a renamed foreign-key column 500'd every request, a lost relation\n * silently emptied a list). See `catalogue-schema.ts` for the caller.\n *\n * These are handed to drizzle as its schema, which keeps the relational query\n * path (`db.query.*`) working; without them FetchService would fall back to\n * plain selects and lose relation loading.\n *\n * Two things a caller has to supply for the tables to match what the generated\n * module used to produce, both about *names*: the Drizzle object is keyed by the\n * wire name of a column and not by the column (`authorId`, not `author_id`), and\n * a relation is keyed by the collection's own relation key rather than by a\n * guess made from the foreign key. Both come from the collections — see\n * `columnKeysFromCollections` and `buildDrizzleRelationsFromCollections`.\n */\nimport {\n bigint,\n boolean,\n char,\n cidr,\n customType,\n date,\n doublePrecision,\n geometry,\n inet,\n integer,\n interval,\n json,\n jsonb,\n line,\n macaddr,\n macaddr8,\n numeric,\n pgSchema,\n pgTable,\n point,\n primaryKey,\n real,\n smallint,\n text,\n time,\n timestamp,\n uuid,\n varchar,\n vector,\n type PgColumnBuilderBase,\n type PgTable\n} from \"drizzle-orm/pg-core\";\n\nimport { getTableColumns, relations, sql, type Relations } from \"drizzle-orm\";\nimport { toWireKey } from \"@rebasepro/utils\";\n\nimport type { TableColumn, TableMeta } from \"./introspect-db-logic\";\n\n/**\n * The Drizzle object key a column is served under, given its table.\n *\n * Defaults to the column name — right for a junction table and for a database\n * nothing declares — and is supplied from the collections wherever there is\n * one, because there the wire name is the property key. Every read and write\n * path in this driver indexes tables and rows by that key.\n */\nexport type ColumnKeyResolver = (tableName: string, columnName: string) => string;\n\nconst columnNameAsKey: ColumnKeyResolver = (_table, column) => column;\n\n/** drizzle ships no bytea builder; binary must round-trip as a Buffer. */\nconst bytea = customType<{ data: Buffer; driverData: Buffer }>({\n dataType: () => \"bytea\"\n});\n\n/**\n * A full-text search column, declared with its real SQL type.\n *\n * Not cosmetic: `isSearchIndexColumn` asks the column for its type and drops\n * every `tsvector` from the projection, so a search column that fell through to\n * `text` (the old default for an unrecognised type) would be selected, returned\n * to callers, and rendered in the admin as a wall of lexemes. drizzle ships no\n * builder for these two.\n */\nconst tsvector = customType<{ data: string; driverData: string }>({\n dataType: () => \"tsvector\"\n});\n\nconst tsquery = customType<{ data: string; driverData: string }>({\n dataType: () => \"tsquery\"\n});\n\n/**\n * Postgres stores a column's type modifier (varchar length, vector dimensions)\n * in `atttypmod`. For length-carrying string types it is length + VARHDRSZ(4);\n * for pgvector it is the dimension count as-is. -1 means unspecified.\n */\nfunction varlenLength(col: TableColumn): number | undefined {\n return col.atttypmod && col.atttypmod > 4 ? col.atttypmod - 4 : undefined;\n}\n\n/**\n * Map a Postgres type to a drizzle column builder, keyed on `udt_name` — the\n * concrete underlying type, which is exact where `data_type` reports umbrella\n * values like \"ARRAY\" or \"USER-DEFINED\".\n *\n * Unknown types fall back to `text`: the driver still reads and writes them,\n * with the value passing through as a string, which beats dropping the column.\n */\nfunction scalarBuilder(udtName: string, name: string, col: TableColumn): PgColumnBuilderBase {\n switch (udtName) {\n case \"uuid\":\n return uuid(name);\n case \"bool\":\n return boolean(name);\n case \"int2\":\n return smallint(name);\n case \"int4\":\n return integer(name);\n case \"int8\":\n return bigint(name, { mode: \"number\" });\n case \"float4\":\n return real(name);\n case \"float8\":\n return doublePrecision(name);\n case \"numeric\":\n case \"money\":\n return numeric(name);\n case \"json\":\n return json(name);\n case \"jsonb\":\n return jsonb(name);\n case \"date\":\n return date(name);\n case \"time\":\n return time(name);\n case \"timetz\":\n return time(name, { withTimezone: true });\n // `mode: \"string\"`, both of them, because that is what the generated\n // module declared for every `date` property: the value Postgres sent,\n // untouched. Drizzle's default mode is `date`, which parses it into a\n // `Date` — the same instant in a different shape, and a difference that\n // would have appeared only on the read path of a project that changed\n // nothing. (`parsePropertyFromServer` normalises either shape to an ISO\n // string for a declared `date` property, so what a REST caller sees is\n // the same; a raw `db.select()` is where the two differ.)\n case \"timestamp\":\n return timestamp(name, { mode: \"string\" });\n case \"timestamptz\":\n return timestamp(name, { withTimezone: true, mode: \"string\" });\n case \"interval\":\n return interval(name);\n case \"bytea\":\n return bytea(name);\n case \"inet\":\n return inet(name);\n case \"cidr\":\n return cidr(name);\n case \"macaddr\":\n return macaddr(name);\n case \"macaddr8\":\n return macaddr8(name);\n case \"point\":\n return point(name);\n case \"line\":\n return line(name);\n case \"geometry\":\n return geometry(name);\n case \"vector\": {\n // drizzle requires the dimension count; without it fall back to text\n // rather than declaring a vector of unknown width.\n const dimensions = col.atttypmod && col.atttypmod > 0 ? col.atttypmod : undefined;\n return dimensions ? vector(name, { dimensions }) : text(name);\n }\n case \"bpchar\": {\n const length = varlenLength(col);\n return length ? char(name, { length }) : char(name);\n }\n case \"varchar\": {\n const length = varlenLength(col);\n return length ? varchar(name, { length }) : text(name);\n }\n case \"tsvector\":\n return tsvector(name);\n case \"tsquery\":\n return tsquery(name);\n default:\n // Includes text, enums (pg enums are strings on the wire), citext,\n // geography, and anything else this driver hasn't met yet.\n //\n // An enum deliberately stays `text`, which is what the generated\n // module's `pgEnum` column also serves: drizzle does no runtime\n // validation of enum members either way, and Postgres rejects a\n // label it does not have. The difference is only in the type the\n // generated *types* carry, and those come from the collection.\n return text(name);\n }\n}\n\n/**\n * The fluent methods this file calls on a column builder.\n *\n * Drizzle's `PgColumnBuilderBase` exposes none of them by design: they live on\n * the concrete `PgColumnBuilder` subclasses, each generic over the column's\n * TypeScript type, its name and whether it is notNull / hasDefault — a chain\n * resolved when you *write* a schema, and not resolvable when you build one\n * from a catalogue read at runtime, which is all this file does.\n *\n * So a conversion here is irreducible; what was avoidable was doing it four\n * separate times, each declaring its own one-method slice of the builder and\n * each free to disagree with the others about a name or a signature. Declaring\n * the methods as an *extension* of the base also drops the `as unknown as`: the\n * two types overlap, so tsc will still refuse this if drizzle ever gives\n * `PgColumnBuilderBase` a conflicting member.\n */\ninterface FluentColumnBuilder extends PgColumnBuilderBase {\n array(): PgColumnBuilderBase;\n notNull(): PgColumnBuilderBase;\n primaryKey(): PgColumnBuilderBase;\n generatedAlwaysAs(expr: unknown): PgColumnBuilderBase;\n}\n\n/** See {@link FluentColumnBuilder}. */\nconst fluent = (builder: PgColumnBuilderBase): FluentColumnBuilder => builder as FluentColumnBuilder;\n\nfunction columnBuilderFor(col: TableColumn): PgColumnBuilderBase {\n // Array types are named after their element with a leading underscore\n // (_int4 = int4[]), so the element mapping is reused verbatim.\n if (col.udt_name.startsWith(\"_\")) {\n const element = scalarBuilder(col.udt_name.slice(1), col.column_name, col);\n return fluent(element).array();\n }\n return scalarBuilder(col.udt_name, col.column_name, col);\n}\n\n/**\n * Build one drizzle table per introspected table, keyed by table name.\n *\n * @param columnKey how a column is named on the wire — see {@link ColumnKeyResolver}.\n */\nexport function buildDrizzleTablesFromSchema(\n tablesMap: Map<string, TableMeta>,\n pgSchemaName = \"public\",\n columnKey: ColumnKeyResolver = columnNameAsKey\n): Record<string, PgTable> {\n const schema = pgSchemaName === \"public\" ? null : pgSchema(pgSchemaName);\n // The column set is only known at runtime, so drizzle's generic table\n // signature can't be satisfied statically; call it through a loose type.\n const createTable = (schema ? schema.table.bind(schema) : pgTable) as (\n name: string,\n columns: Record<string, PgColumnBuilderBase>,\n extras?: (self: Record<string, unknown>) => unknown[]\n ) => PgTable;\n\n const tables: Record<string, PgTable> = {};\n\n for (const [tableName, meta] of tablesMap) {\n const columns: Record<string, PgColumnBuilderBase> = {};\n const keyOf = new Map<string, string>();\n\n for (const col of meta.columns) {\n let builder = columnBuilderFor(col);\n\n if (col.is_nullable === \"NO\") {\n builder = fluent(builder).notNull();\n }\n // Single-column primary keys are marked inline; composite keys are\n // declared in the table extras below.\n if (meta.pks.length === 1 && meta.pks[0] === col.column_name) {\n builder = fluent(builder).primaryKey();\n }\n // A stored generated column must be left out of every INSERT, which\n // is what `.generatedAlwaysAs` tells drizzle. The expression is\n // carried through so a drizzle-kit run over these tables describes\n // the column it actually found; nothing at runtime evaluates it.\n if (col.is_generated === \"ALWAYS\") {\n builder = fluent(builder).generatedAlwaysAs(sql.raw(col.generation_expression ?? \"\"));\n }\n\n const key = columnKey(tableName, col.column_name);\n keyOf.set(col.column_name, key);\n columns[key] = builder;\n }\n\n const isComposite = meta.pks.length > 1;\n tables[tableName] = createTable(\n tableName,\n columns,\n isComposite\n ? (t) => [primaryKey({ columns: meta.pks.map((pk) => t[keyOf.get(pk) ?? pk]) as never })]\n : undefined\n );\n }\n\n return tables;\n}\n\n/**\n * Build drizzle `relations()` for the foreign keys, so the relational query\n * path can actually load them.\n *\n * FetchService asks drizzle for `with: { <key>: true }`, keyed by the relation\n * property on the collection. Tables alone don't satisfy that — without these,\n * `?include=author` silently returns the raw `author_id` and no author.\n *\n * The keys here must match `buildRelations` in introspect-runtime.ts, which is\n * what names the collection's relation properties.\n */\nexport function buildDrizzleRelationsFromSchema(\n tablesMap: Map<string, TableMeta>,\n tables: Record<string, PgTable>,\n columnKey: ColumnKeyResolver = columnNameAsKey\n): Record<string, Relations> {\n /** Owning side, per table: the `one()` relations from its foreign keys. */\n const owning = new Map<string, { key: string; targetTable: string; fkColumn: string; targetColumn: string; relationName: string }[]>();\n /** Inverse side, per referenced table: the matching `many()` back-references. */\n const inverse = new Map<string, { key: string; sourceTable: string; relationName: string }[]>();\n\n for (const [tableName, meta] of tablesMap) {\n if (!tables[tableName]) continue;\n // The keys the table actually carries — what a relation key must not\n // collide with, since both live in one Drizzle object.\n const columnNames = new Set(meta.columns.map((c) => columnKey(tableName, c.column_name)));\n\n for (const fk of meta.fks) {\n if (!tables[fk.foreign_table_name]) continue;\n\n // Mirrors buildRelations in introspect-runtime: author_id -> author,\n // falling back to the target table when the column name carries no\n // hint. `toWireKey` for the same reason it is there — the two keys\n // must be the same string or the collection advertises a relation key\n // drizzle has never heard of (`author_ref` here, `authorRef` there).\n let key = toWireKey(fk.column_name.replace(/_id$/, \"\"));\n if (meta.pks.includes(fk.column_name) && key === fk.column_name) {\n key = fk.foreign_table_name;\n }\n // A relation key must not shadow a real column.\n if (columnNames.has(key)) continue;\n\n // Pairs the two sides. Drizzle matches a named one() to the many()\n // carrying the same name, and disambiguates multiple foreign keys\n // into the same table.\n const relationName = `${tableName}_${fk.column_name}`;\n\n owning.set(tableName, [\n ...(owning.get(tableName) ?? []),\n {\n key,\n targetTable: fk.foreign_table_name,\n // The Drizzle keys, not the columns: `fields`/`references`\n // index the built table objects, which are keyed by the\n // wire name wherever a collection supplies one.\n fkColumn: columnKey(tableName, fk.column_name),\n targetColumn: columnKey(fk.foreign_table_name, fk.foreign_column_name),\n relationName\n }\n ]);\n\n const backKey = tableName;\n const targetColumns = new Set(\n (tablesMap.get(fk.foreign_table_name)?.columns ?? [])\n .map((c) => columnKey(fk.foreign_table_name, c.column_name))\n );\n if (targetColumns.has(backKey)) continue;\n\n inverse.set(fk.foreign_table_name, [\n ...(inverse.get(fk.foreign_table_name) ?? []),\n { key: backKey, sourceTable: tableName, relationName }\n ]);\n }\n }\n\n const built: Record<string, Relations> = {};\n\n for (const tableName of tablesMap.keys()) {\n const table = tables[tableName];\n const ones = owning.get(tableName) ?? [];\n const manys = inverse.get(tableName) ?? [];\n if (!table || (ones.length === 0 && manys.length === 0)) continue;\n\n built[`${tableName}Relations`] = relations(table, ({ one, many }) => {\n const map: Record<string, unknown> = {};\n\n for (const rel of ones) {\n map[rel.key] = one(tables[rel.targetTable], {\n // `getTableColumns` is drizzle's own accessor for this.\n // Indexing the table object — which is what the casts here\n // were for — also reaches its methods and symbols, so a\n // foreign key named after one of them yields something that\n // is not a column; and the target type they used,\n // `Record<string, never>`, makes every lookup `never`,\n // which is assignable to anything and therefore checks\n // nothing at all.\n fields: [getTableColumns(table)[rel.fkColumn]],\n references: [getTableColumns(tables[rel.targetTable])[rel.targetColumn]],\n relationName: rel.relationName\n });\n }\n\n // Drizzle needs the inverse of every named one(); without it,\n // normalizeRelation throws and the relational path is dead.\n for (const rel of manys) {\n // An inverse must not collide with an owning key on this table.\n if (map[rel.key]) continue;\n map[rel.key] = many(tables[rel.sourceTable], { relationName: rel.relationName });\n }\n\n return map as never;\n });\n }\n\n return built;\n}\n","/**\n * Build drizzle `relations()` for declared collections, from the collections.\n *\n * The heuristic builder in `dynamic-tables.ts` names a relation after the\n * foreign-key column (`author_id` → `author`), which is the only evidence a\n * database offers and is right for BaaS mode. It is wrong for a project that\n * declares its collections: there the relation's name is whatever the author\n * called it, `include=writer` addresses that name, and a relation whose key was\n * guessed from the column simply does not answer to it — the read returns the\n * bare foreign key and no target, silently, which is how a demo lost every\n * relation in its list view while the detail view (a different code path) kept\n * working.\n *\n * So when there are collections, they are the authority for *which* relations\n * exist and what they are called; the live catalogue is the authority for the\n * columns underneath. This is the same pairing `generate-drizzle-schema-logic`\n * emits into `schema.generated.ts` — kind for kind, key for key, and paired by\n * the same {@link sharedRelationName} — only built against tables read back from\n * `information_schema` rather than against a file.\n */\nimport { getTableColumns, relations, type Relations } from \"drizzle-orm\";\nimport type { PgTable } from \"drizzle-orm/pg-core\";\nimport { CollectionConfig, ResolvedRelation, type ResolvedForeignKeyOnTarget } from \"@rebasepro/types\";\nimport { fieldKeyForColumn, getTableName, resolveCollectionRelations } from \"@rebasepro/common\";\nimport { logger } from \"@rebasepro/server\";\n\nimport { sharedRelationName } from \"./relation-names\";\n\n/** A collection's table, with any schema prefix stripped — as the DDL creates it. */\nexport function bareTableName(name: string): string {\n return name.includes(\".\") ? name.split(\".\").pop()! : name;\n}\n\n/**\n * The field key a collection's rows are addressed by, as the generated module\n * spells it: the property marked `isId`, else `id`.\n *\n * Deliberately the config's answer rather than the catalogue's primary key —\n * `references: [users.id]` in the generated file is keyed the same way, and the\n * two have to name the same Drizzle key for a relation to compile at all.\n */\nfunction primaryKeyFieldKey(collection: CollectionConfig): string {\n for (const [key, prop] of Object.entries(collection.properties ?? {})) {\n if (prop && typeof prop === \"object\" && \"isId\" in prop && Boolean((prop as { isId?: unknown }).isId)) {\n return key;\n }\n }\n return \"id\";\n}\n\n/**\n * The field key on the source table that a `hasOne`/`hasMany` foreign key\n * holds: its `sourceKey` when the link names one, else the primary key.\n */\nfunction sourceKeyFieldKey(\n relation: ResolvedForeignKeyOnTarget,\n collection: CollectionConfig\n): string {\n return relation.sourceKey\n ? fieldKeyForColumn(collection, relation.sourceKey)\n : primaryKeyFieldKey(collection);\n}\n\n/**\n * A column of a built table, by its Drizzle key or by its column name.\n *\n * The key is the answer in the ordinary case, and the name is the fallback for\n * the one place a caller holds a column and not a field: a junction table's two\n * foreign keys, which are keyed by column *unless* the project also declares a\n * collection over that table, in which case they are keyed by its property\n * names. Looking both up costs a scan of one table and removes the difference.\n */\nfunction columnOf(table: PgTable | undefined, key: string): unknown {\n if (!table) return undefined;\n // Drizzle's column map is keyed by the JS property name, which is exactly\n // the \"keyed by its property names\" case this function's docblock\n // describes — so `getTableColumns` answers it, and indexing the table\n // object (which also reaches its methods and symbols) was never needed.\n const direct = getTableColumns(table)[key];\n if (direct) return direct;\n for (const column of Object.values(getTableColumns(table))) {\n if ((column as { name?: unknown })?.name === key) return column;\n }\n return undefined;\n}\n\ninterface OneSpec {\n key: string;\n target: PgTable;\n fields: unknown[];\n references: unknown[];\n relationName: string;\n}\n\ninterface ManySpec {\n key: string;\n target: PgTable;\n relationName: string;\n}\n\n/**\n * Drizzle relations for every declared collection, keyed `<table>Relations`.\n *\n * @param collections every collection backed by a table in `tables`\n * @param tables catalogue-built tables, keyed by bare table name\n */\nexport function buildDrizzleRelationsFromCollections(\n collections: CollectionConfig[],\n tables: Record<string, PgTable>\n): Record<string, Relations> {\n const ones = new Map<string, OneSpec[]>();\n const manys = new Map<string, ManySpec[]>();\n /** `<relationName>::<kind>` already emitted on a table — the generator dedupes the same way. */\n const emitted = new Map<string, Set<string>>();\n\n const claim = (table: string, name: string, kind: string): boolean => {\n const seen = emitted.get(table) ?? new Set<string>();\n emitted.set(table, seen);\n const key = `${name}::${kind}`;\n if (seen.has(key)) return false;\n seen.add(key);\n return true;\n };\n\n const addOne = (table: string, spec: OneSpec) => ones.set(table, [...(ones.get(table) ?? []), spec]);\n const addMany = (table: string, spec: ManySpec) => manys.set(table, [...(manys.get(table) ?? []), spec]);\n\n const byTable = new Map<string, CollectionConfig>();\n for (const collection of collections) {\n const table = bareTableName(getTableName(collection));\n if (table && tables[table]) byTable.set(table, collection);\n }\n\n for (const [tableName, collection] of byTable) {\n const table = tables[tableName];\n\n for (const [relationKey, relation] of Object.entries(resolveCollectionRelations(collection))) {\n let target: CollectionConfig;\n try {\n target = relation.target();\n } catch (err) {\n // Nothing to build against, and nothing to fail over: the boot\n // relation check reports an unresolvable target by name.\n logger.debug(`[schema] Relation ${collection.slug}.${relationKey} has no resolvable target`, { error: err });\n continue;\n }\n const targetTableName = bareTableName(getTableName(target));\n const targetTable = tables[targetTableName];\n const name = sharedRelationName(relation, collection);\n\n switch (relation.kind) {\n case \"belongsTo\": {\n if (!targetTable) continue;\n if (!claim(tableName, name, relation.kind)) continue;\n const localKey = fieldKeyForColumn(collection, relation.localKey);\n const referenced = primaryKeyFieldKey(target);\n const field = columnOf(table, localKey);\n const reference = columnOf(targetTable, referenced);\n // A column the catalogue does not have would make drizzle\n // throw `Cannot read properties of undefined` from inside\n // `normalizeRelation`, three frames from anything nameable.\n // The boot relation check reports the same defect in terms\n // of the collection; this only declines to build it.\n if (!field || !reference) continue;\n addOne(tableName, {\n key: relationKey,\n target: targetTable,\n fields: [field],\n references: [reference],\n relationName: name\n });\n break;\n }\n\n case \"hasOne\": {\n if (!targetTable) continue;\n if (!claim(tableName, name, relation.kind)) continue;\n // The foreign key lives on the target, so this side states\n // the join from its own end: its source key against the\n // target's foreign key. Drizzle has no `one()` that is paired\n // by `relationName` alone — `createOne` reads\n // `config.fields.reduce(...)` as the relations are evaluated,\n // which is at `drizzle({ schema })` — and a bare `one(target)`\n // is paired by table, which is ambiguous as soon as the target\n // has two links back here or the link is to its own table.\n // These are the columns drizzle would infer from the owning\n // side, given outright.\n const field = columnOf(table, sourceKeyFieldKey(relation, collection));\n const reference = columnOf(targetTable, fieldKeyForColumn(target, relation.foreignKeyOnTarget));\n if (!field || !reference) continue;\n addOne(tableName, {\n key: relationKey,\n target: targetTable,\n fields: [field],\n references: [reference],\n relationName: name\n });\n break;\n }\n\n case \"hasMany\": {\n if (!targetTable) continue;\n if (!claim(tableName, name, relation.kind)) continue;\n addMany(tableName, { key: relationKey, target: targetTable, relationName: name });\n break;\n }\n\n case \"manyToMany\": {\n const junctionName = bareTableName(relation.through.table);\n const junction = tables[junctionName];\n if (!junction) continue;\n if (!claim(tableName, name, relation.kind)) continue;\n addMany(tableName, { key: relationKey, target: junction, relationName: name });\n addJunctionSides(relation, collection, target, junctionName, tables, addOne, claim);\n break;\n }\n\n case \"via\":\n // A join chain is resolved at query time, not modelled as a\n // drizzle relation — same as the generator.\n break;\n }\n }\n }\n\n // The reciprocal a `many()` needs. A `hasMany` gives drizzle no columns to\n // join on; it finds them on the owning side, which exists only if the other\n // collection happens to declare a `belongsTo` back. Where it does not, the\n // generated file synthesizes one under a `_synth_` key, and so does this.\n // A `hasOne` names its own columns, and gets the same owning side so the\n // target's table carries the link back under the same `relationName`.\n for (const [tableName, collection] of byTable) {\n const table = tables[tableName];\n for (const [, relation] of Object.entries(resolveCollectionRelations(collection))) {\n if (relation.kind !== \"hasMany\" && relation.kind !== \"hasOne\") continue;\n let target: CollectionConfig;\n try {\n target = relation.target();\n } catch {\n continue;\n }\n const targetTableName = bareTableName(getTableName(target));\n const targetTable = tables[targetTableName];\n if (!targetTable) continue;\n\n const name = sharedRelationName(relation, collection);\n if (!claim(targetTableName, name, \"belongsTo\")) continue;\n\n const fkKey = fieldKeyForColumn(target, relation.foreignKeyOnTarget);\n const field = columnOf(targetTable, fkKey);\n const reference = columnOf(table, sourceKeyFieldKey(relation, collection));\n if (!field || !reference) continue;\n\n addOne(targetTableName, {\n key: `_synth_${targetTableName}_${fkKey}`,\n target: tables[tableName],\n fields: [field],\n references: [reference],\n relationName: name\n });\n }\n }\n\n const built: Record<string, Relations> = {};\n for (const tableName of new Set([...ones.keys(), ...manys.keys()])) {\n const table = tables[tableName];\n if (!table) continue;\n const tableOnes = ones.get(tableName) ?? [];\n const tableManys = manys.get(tableName) ?? [];\n\n built[`${tableName}Relations`] = relations(table, ({ one, many }) => {\n const map: Record<string, unknown> = {};\n for (const spec of tableOnes) {\n map[spec.key] = one(spec.target, {\n fields: spec.fields as never,\n references: spec.references as never,\n relationName: spec.relationName\n });\n }\n for (const spec of tableManys) {\n if (map[spec.key]) continue;\n map[spec.key] = many(spec.target, { relationName: spec.relationName });\n }\n return map as never;\n });\n }\n\n return built;\n}\n\n/**\n * The junction table's own two `one()` sides.\n *\n * A many-to-many is a `many(junction)` on each endpoint, so the junction has to\n * carry the matching `one()` back to each — without them drizzle can join a\n * post to its `posts_tags` rows and no further, and the tags come back as\n * junction rows. Keyed by the junction's own columns, which is how the\n * generated file keys them too.\n */\nfunction addJunctionSides(\n relation: Extract<ResolvedRelation, { kind: \"manyToMany\" }>,\n sourceCollection: CollectionConfig,\n targetCollection: CollectionConfig,\n junctionName: string,\n tables: Record<string, PgTable>,\n addOne: (table: string, spec: OneSpec) => void,\n claim: (table: string, name: string, kind: string) => boolean\n): void {\n const junction = tables[junctionName];\n const sourceTable = tables[bareTableName(getTableName(sourceCollection))];\n const targetTable = tables[bareTableName(getTableName(targetCollection))];\n if (!junction || !sourceTable || !targetTable) return;\n\n const sourceName = sharedRelationName(relation, sourceCollection);\n // The far endpoint pairs with whatever *it* calls this junction, so the two\n // sides are looked up rather than assumed: an inverse declared on the target\n // owns the name, and only a junction nothing points back through needs a\n // synthesized one.\n const inverse = Object.values(resolveCollectionRelations(targetCollection))\n .find(r => r.kind === \"manyToMany\"\n && bareTableName(r.through.table) === junctionName);\n const targetName = inverse\n ? sharedRelationName(inverse, targetCollection)\n : `${junctionName}_${relation.through.targetColumn}`;\n\n const pairs: [string, PgTable, string, CollectionConfig, string][] = [\n [relation.through.sourceColumn, sourceTable, sourceName, sourceCollection, \"source\"],\n [relation.through.targetColumn, targetTable, targetName, targetCollection, \"target\"]\n ];\n\n for (const [column, endpointTable, relationName, endpointCollection] of pairs) {\n if (!claim(junctionName, relationName, \"junction\")) continue;\n const field = columnOf(junction, column);\n const reference = columnOf(endpointTable, primaryKeyFieldKey(endpointCollection));\n if (!field || !reference) continue;\n addOne(junctionName, {\n key: column,\n target: endpointTable,\n fields: [field],\n references: [reference],\n relationName\n });\n }\n}\n","/**\n * The runtime's Drizzle schema, read back from the database it is about to\n * serve.\n *\n * This is the whole point of the module: **the live catalogue is the schema**.\n * Boot-ensure has already created and altered whatever the collections describe\n * by the time this runs, so `information_schema` is both current and complete,\n * and the committed `schema.generated.ts` — which is a build artefact for Atlas,\n * `db push`, `eject` and user code — never decides what the server serves.\n *\n * It used to. The default data source was handed the bundle's generated module\n * and nothing reconciled it, so a file one commit behind the database changed\n * behaviour in ways that looked nothing like \"your schema file is old\":\n *\n * - a foreign-key column renamed by boot-ensure (`categorie_id` → `category_id`)\n * left every request 500ing, because the module still declared the old name;\n * - a property added to a collection answered `400 VALIDATION_UNKNOWN_FIELDS`\n * on first save, because the write check reads the table and the table came\n * from the file, while the column itself existed;\n * - relations missing from the module were silently missing from list reads.\n *\n * The catalogue cannot drift from itself. What the generated module is still\n * good for is telling the developer their file is stale — see\n * `generated-schema-diff.ts`, which is now its only reader at boot.\n */\nimport type { PgTable } from \"drizzle-orm/pg-core\";\nimport type { Relations } from \"drizzle-orm\";\nimport { CollectionConfig } from \"@rebasepro/types\";\nimport { fieldKeyForColumn, getJunctionCollectionConfig, getTableName, resolveJunctionSpecs } from \"@rebasepro/common\";\n\nimport { buildDrizzleRelationsFromSchema, buildDrizzleTablesFromSchema, type ColumnKeyResolver } from \"./dynamic-tables\";\nimport { bareTableName, buildDrizzleRelationsFromCollections } from \"./config-relations\";\nimport { introspectSchema, type Queryable } from \"./introspect-runtime\";\nimport type { TableMeta } from \"./introspect-db-logic\";\nimport { searchColumnNames } from \"./search-column\";\n\n/** The Postgres schema a collection's table lives in. */\nexport function schemaOfCollection(collection: CollectionConfig, fallback: string): string {\n const declared = (collection as { schema?: unknown }).schema;\n return typeof declared === \"string\" && declared.length > 0 ? declared : fallback;\n}\n\n/**\n * How a column of each table is keyed in the Drizzle object.\n *\n * The wire name, which is the property key — `authorId`, never `author_id` —\n * because that is what every read and write path in this driver indexes tables\n * and rows by, and what the generated module has always emitted. Getting this\n * wrong does not throw: drizzle leaves an unknown key out of the statement and\n * the write reports success having stored nothing.\n *\n * Two exceptions, both columns no property declares:\n *\n * - the generated search columns keep their raw names, because `search_vector`\n * is how `searchColumnNames` excludes them from responses and how the\n * condition builder finds the one to match against;\n * - a table no collection claims (a junction) is keyed by its **key** columns'\n * names, which is also what the generated file does for them — but a\n * junction that declares `through.properties` keys those payload columns by\n * their property key, for the same reason a collection does. `joinedAt` with\n * `columnName: \"joined_at\"` has to be one name on both sides or a `_pivot`\n * write is silently dropped by drizzle and the read serves a key the caller\n * never declared.\n */\nexport function columnKeysFromCollections(\n collections: CollectionConfig[]\n): ColumnKeyResolver {\n const byTable = new Map<string, CollectionConfig>();\n const rawColumns = new Map<string, Set<string>>();\n for (const collection of collections) {\n const table = bareTableName(getTableName(collection));\n if (!table) continue;\n byTable.set(table, collection);\n rawColumns.set(table, new Set(searchColumnNames(collection)));\n }\n // The junctions' payload columns, as the synthetic collection that owns\n // them — the same one the planner emitted the columns from, so the runtime\n // table and the generated one cannot key them differently.\n const junctionByTable = new Map<string, CollectionConfig>();\n for (const spec of resolveJunctionSpecs(collections).values()) {\n if (Object.keys(spec.properties).length === 0) continue;\n junctionByTable.set(bareTableName(spec.table), getJunctionCollectionConfig(spec));\n }\n return (tableName, columnName) => {\n const collection = byTable.get(tableName);\n if (!collection) {\n const junction = junctionByTable.get(tableName);\n return junction ? fieldKeyForColumn(junction, columnName) : columnName;\n }\n if (rawColumns.get(tableName)?.has(columnName)) return columnName;\n return fieldKeyForColumn(collection, columnName);\n };\n}\n\nexport interface CatalogueSchema {\n /** Drizzle tables, keyed by bare table name. */\n tables: Record<string, PgTable>;\n /** Drizzle relations, keyed `<table>Relations`. */\n relations: Record<string, Relations>;\n /** Introspected metadata per table — what the boot diff compares against. */\n meta: Map<string, TableMeta>;\n /** Tables a collection declares that the database does not have. */\n missing: string[];\n}\n\n/**\n * Read the tables this data source serves out of `information_schema`.\n *\n * Restricted to the collections handed in, because the driver is given every\n * collection the project declares while each database holds only its own: an\n * unfiltered read had a second source (`database(\"analytics\")`) reporting the\n * default's tables as its own. Junction tables come too — they back no\n * collection and a many-to-many is unreadable without them.\n *\n * Every schema the collections name is read, not just `public`. The users\n * collection lives in `rebase`, and a run restricted to `public` would leave the\n * registry with no table for it at all.\n */\nexport async function readCatalogueSchema(options: {\n client: Queryable;\n collections: CollectionConfig[];\n defaultSchema: string;\n}): Promise<CatalogueSchema> {\n const { client, collections, defaultSchema } = options;\n\n /** schema → the tables wanted from it. */\n const wanted = new Map<string, Set<string>>();\n const want = (schema: string, table: string) => {\n const set = wanted.get(schema) ?? new Set<string>();\n wanted.set(schema, set);\n set.add(table);\n };\n\n for (const collection of collections) {\n const table = bareTableName(getTableName(collection));\n if (table) want(schemaOfCollection(collection, defaultSchema), table);\n }\n // Junctions are created in `public`, full stop — `resolveJunctionSpecs`\n // hardcodes it and the three generators agree with it.\n for (const spec of resolveJunctionSpecs(collections).values()) {\n want(spec.schema, bareTableName(spec.table));\n }\n\n const columnKey = columnKeysFromCollections(collections);\n const tables: Record<string, PgTable> = {};\n const meta = new Map<string, TableMeta>();\n const missing: string[] = [];\n\n for (const [schema, names] of wanted) {\n const live = await introspectSchema(client, schema);\n const present = new Map<string, TableMeta>();\n for (const name of names) {\n const found = live.tablesMap.get(name);\n if (!found) {\n missing.push(schema === defaultSchema ? name : `${schema}.${name}`);\n continue;\n }\n // First schema to carry a name wins. Two schemas holding a table of\n // the same name is the `search_path` hazard the drift report\n // explains; the registry is keyed by bare name either way, so this\n // only picks which one is served — the one whose collection asked.\n if (tables[name]) continue;\n present.set(name, found);\n meta.set(name, found);\n }\n Object.assign(tables, buildDrizzleTablesFromSchema(present, schema, columnKey));\n }\n\n return {\n tables,\n relations: buildDrizzleRelationsFromCollections(collections, tables),\n meta,\n missing\n };\n}\n\n/**\n * The BaaS variant: no collections are declared, so every table in one schema is\n * served and both the collections and the relations are derived from the\n * foreign keys.\n *\n * Kept apart from {@link readCatalogueSchema} because the two answer different\n * questions. Here the database is the entire specification; there it supplies\n * the columns for a specification the project already wrote down.\n */\nexport function buildBaasSchema(\n tablesMap: Map<string, TableMeta>,\n pgSchemaName: string,\n collections: CollectionConfig[]\n): { tables: Record<string, PgTable>; relations: Record<string, Relations> } {\n const columnKey = columnKeysFromCollections(collections);\n const tables = buildDrizzleTablesFromSchema(tablesMap, pgSchemaName, columnKey);\n return {\n tables,\n relations: buildDrizzleRelationsFromSchema(tablesMap, tables, columnKey)\n };\n}\n","/**\n * Compare the committed `schema.generated.ts` against the live catalogue, and\n * say so — once per difference, at boot, as a warning and nothing more.\n *\n * The runtime stopped reading that file for its tables (see\n * `catalogue-schema.ts`), which removes an entire class of production failure\n * and creates a smaller one: a stale file is now *invisible*. It is still real —\n * it is what Atlas plans migrations from, what `db push` diffs, what `eject`\n * writes out and what user code imports — so a project whose file no longer\n * describes its database will hit it later, in a tool, with no clue that boot\n * already knew.\n *\n * Hence this: a guard rather than a doc. It never fails boot and never changes\n * what is served — the previous arrangement, where the file could do both,\n * is exactly what was removed.\n *\n * `generated-schema-staleness.ts` answers a narrower question (does the file\n * name every table the collections do) from the file's *source text*. This one\n * compares the loaded module against the database, column by column.\n */\nimport { getTableColumns, getTableName as drizzleTableName, isTable } from \"drizzle-orm\";\nimport type { PgTable } from \"drizzle-orm/pg-core\";\nimport { CollectionConfig } from \"@rebasepro/types\";\nimport { fieldKeyForColumn, getTableName } from \"@rebasepro/common\";\nimport { logger } from \"@rebasepro/server\";\n\nimport { AUTH_USERS_COLUMNS, isAuthCollection } from \"./auth-users-columns\";\nimport type { TableMeta } from \"./introspect-db-logic\";\nimport { bareTableName } from \"./config-relations\";\nimport { readCatalogueSchema } from \"./catalogue-schema\";\nimport type { Queryable } from \"./introspect-runtime\";\n\nexport type SchemaDifferenceKind =\n | \"missing-table\"\n | \"missing-column\"\n | \"extra-column\"\n | \"type\"\n | \"nullability\";\n\nexport interface SchemaDifference {\n kind: SchemaDifferenceKind;\n /** Bare table name, as both sides spell it. */\n table: string;\n /** The collection backed by that table, when one is. */\n collection?: string;\n /** The column, in database terms. */\n column?: string;\n /** The field it is served under — the property key, where there is one. */\n property?: string;\n /** What `schema.generated.ts` says. */\n generated?: string;\n /** What the database says. */\n live?: string;\n}\n\n/**\n * The families a boot warning is worth raising over.\n *\n * Deliberately coarse. `varchar(120)` and `text` are the same family because the\n * runtime treats them identically and a warning about the difference would fire\n * on every boot of a correct project — the widths still reach Postgres, which\n * enforces them. What this is looking for is the kind of drift that changes what\n * a value *is*: a number that became text, a timestamp that became a date, a\n * column that stopped existing.\n */\nexport function typeFamily(sqlType: string | undefined): string {\n if (!sqlType) return \"unknown\";\n let type = sqlType.trim().toLowerCase();\n const isArray = type.endsWith(\"[]\") || type.startsWith(\"_\");\n type = type.replace(/\\[\\]$/, \"\").replace(/^_/, \"\");\n // Strip the modifier: varchar(255), numeric(10,2), vector(1536), char(2).\n type = type.replace(/\\s*\\([^)]*\\)\\s*$/, \"\").trim();\n\n const family = FAMILIES[type] ?? type;\n return isArray ? `${family}[]` : family;\n}\n\nconst FAMILIES: Record<string, string> = {\n // strings\n text: \"text\",\n varchar: \"text\",\n \"character varying\": \"text\",\n bpchar: \"text\",\n char: \"text\",\n character: \"text\",\n citext: \"text\",\n name: \"text\",\n // integers\n int2: \"integer\",\n smallint: \"integer\",\n smallserial: \"integer\",\n int4: \"integer\",\n int: \"integer\",\n integer: \"integer\",\n serial: \"integer\",\n int8: \"bigint\",\n bigint: \"bigint\",\n bigserial: \"bigint\",\n // approximate and exact numerics: kept apart, because one rounds\n float4: \"float\",\n real: \"float\",\n float8: \"float\",\n \"double precision\": \"float\",\n numeric: \"numeric\",\n decimal: \"numeric\",\n money: \"numeric\",\n // the rest\n bool: \"boolean\",\n boolean: \"boolean\",\n uuid: \"uuid\",\n json: \"json\",\n jsonb: \"jsonb\",\n date: \"date\",\n time: \"time\",\n \"time without time zone\": \"time\",\n timetz: \"time\",\n \"time with time zone\": \"time\",\n timestamp: \"timestamp\",\n \"timestamp without time zone\": \"timestamp\",\n timestamptz: \"timestamptz\",\n \"timestamp with time zone\": \"timestamptz\",\n interval: \"interval\",\n bytea: \"bytea\",\n tsvector: \"tsvector\",\n tsquery: \"tsquery\",\n vector: \"vector\"\n};\n\n/** One catalogue column, reduced to the three facts this compares. */\ninterface CatalogueColumn {\n name: string;\n family: string;\n notNull: boolean;\n}\n\nfunction catalogueColumns(meta: TableMeta): Map<string, CatalogueColumn> {\n const columns = new Map<string, CatalogueColumn>();\n for (const col of meta.columns) {\n columns.set(col.column_name, {\n name: col.column_name,\n // `udt_name` over `data_type`: the latter reports \"ARRAY\" and\n // \"USER-DEFINED\" where the former is the concrete type, and an enum\n // has to compare by its own type name against the module's pgEnum.\n family: typeFamily(col.udt_name || col.data_type),\n notNull: col.is_nullable === \"NO\"\n });\n }\n return columns;\n}\n\n/**\n * Every way the generated module and the database disagree, for the tables both\n * describe.\n *\n * One-directional in one respect only: a table the module has and the database\n * does not is reported (that is the stale-file case), while a table the database\n * has and the module does not is left alone — a database may hold anything, and\n * the collections-vs-file question is `generated-schema-staleness`'s.\n */\nexport function diffGeneratedSchemaAgainstCatalogue(options: {\n /** The `tables` export of the bundle's schema module. */\n generated: Record<string, unknown> | undefined;\n /** What was introspected for this data source, keyed by bare table name. */\n catalogue: Map<string, TableMeta>;\n /** Collections of this source, so a column can be named as a property. */\n collections: CollectionConfig[];\n}): SchemaDifference[] {\n const { generated, catalogue, collections } = options;\n if (!generated) return [];\n\n const collectionByTable = new Map<string, CollectionConfig>();\n for (const collection of collections) {\n const table = getTableName(collection);\n if (table) collectionByTable.set(bareTableName(table), collection);\n }\n\n const differences: SchemaDifference[] = [];\n\n for (const value of Object.values(generated)) {\n if (!isTable(value)) continue;\n const table = drizzleTableName(value as PgTable);\n const collection = collectionByTable.get(table);\n const at = { table, collection: collection?.slug };\n\n const meta = catalogue.get(table);\n if (!meta) {\n // Only for a table a collection of this source claims: the module\n // carries every table of the project, including the ones another\n // data source holds, and those are legitimately absent here.\n if (collection) differences.push({ ...at, kind: \"missing-table\" });\n continue;\n }\n\n const live = catalogueColumns(meta);\n const seen = new Set<string>();\n\n // On the auth table, auth owns its own columns' contract and the\n // generated file deliberately does not carry it: `render-drizzle` drops\n // the columns a users collection does not declare (`is_anonymous`,\n // `tokens_valid_after`) because drizzle-kit creates no auth table, and\n // the collection's own property definitions say nothing about the\n // `NOT NULL DEFAULT` that `ensureAuthTablesExist` applies.\n //\n // Comparing them anyway reported seven differences on a project nobody\n // had touched yet, and prescribed `rebase schema generate` — which\n // regenerates the file from the same collection and cannot change any\n // of them. A warning that survives its own remedy teaches the reader to\n // ignore the next one, so these are not differences. A *type* mismatch\n // still is: that is the file and the database disagreeing about\n // something neither side intends.\n const authOwned = isAuthCollection(collection)\n ? new Set(AUTH_USERS_COLUMNS.map(c => c.column))\n : undefined;\n\n for (const [key, column] of Object.entries(getTableColumns(value as PgTable))) {\n const name = (column as { name?: string }).name ?? key;\n seen.add(name);\n const property = collection ? fieldKeyForColumn(collection, name) : key;\n const generatedFamily = typeFamily((column as { getSQLType?: () => string }).getSQLType?.());\n const generatedNotNull = Boolean((column as { notNull?: boolean }).notNull);\n\n const found = live.get(name);\n if (!found) {\n differences.push({ ...at, kind: \"missing-column\", column: name, property, generated: generatedFamily });\n continue;\n }\n if (found.family !== generatedFamily) {\n differences.push({\n ...at,\n kind: \"type\",\n column: name,\n property,\n generated: generatedFamily,\n live: found.family\n });\n }\n if (found.notNull !== generatedNotNull && !authOwned?.has(name)) {\n differences.push({\n ...at,\n kind: \"nullability\",\n column: name,\n property,\n generated: generatedNotNull ? \"NOT NULL\" : \"nullable\",\n live: found.notNull ? \"NOT NULL\" : \"nullable\"\n });\n }\n }\n\n for (const [name, column] of live) {\n if (seen.has(name)) continue;\n if (authOwned?.has(name)) continue;\n differences.push({\n ...at,\n kind: \"extra-column\",\n column: name,\n property: collection ? fieldKeyForColumn(collection, name) : name,\n live: column.family\n });\n }\n }\n\n return differences;\n}\n\n/**\n * Boot's drift question, asked of a database from outside a boot.\n *\n * The same two calls boot makes before it warns, with the arguments it makes\n * them with: the catalogue read the way the registry reads it\n * ({@link readCatalogueSchema}), then {@link diffGeneratedSchemaAgainstCatalogue}\n * over its metadata. `rebase doctor` asks through here, because the boot\n * warning sends the reader to the doctor for \"the full report\" — and while the\n * doctor compared on its own terms, it answered \"In sync\" over a NOT NULL and a\n * left-behind column that boot had just warned about.\n *\n * `collections` are the data source's own, as boot filters them.\n */\nexport async function readGeneratedSchemaDrift(options: {\n client: Queryable;\n /** The `tables` export of `schema.generated.ts`. */\n generated: Record<string, unknown> | undefined;\n collections: CollectionConfig[];\n defaultSchema: string;\n}): Promise<SchemaDifference[]> {\n const { client, generated, collections, defaultSchema } = options;\n if (!generated || collections.length === 0) return [];\n const catalogue = await readCatalogueSchema({ client, collections, defaultSchema });\n return diffGeneratedSchemaAgainstCatalogue({ generated, catalogue: catalogue.meta, collections });\n}\n\n/** One line, in the terms the reader has: collection, property, column. */\nexport function describeSchemaDifference(difference: SchemaDifference): string {\n const where = difference.collection\n ? `${difference.collection} (table \"${difference.table}\")`\n : `table \"${difference.table}\"`;\n const field = difference.column\n ? `${difference.property && difference.property !== difference.column\n ? `${difference.property} → \"${difference.column}\"`\n : `\"${difference.column}\"`}`\n : undefined;\n\n switch (difference.kind) {\n case \"missing-table\":\n return `${where} is in schema.generated.ts but not in the database`;\n case \"missing-column\":\n return `${where}: ${field} is in schema.generated.ts but not in the database`;\n case \"extra-column\":\n return `${where}: ${field} is in the database but not in schema.generated.ts (${difference.live})`;\n case \"type\":\n return `${where}: ${field} is ${difference.generated} in schema.generated.ts and ${difference.live} in the database`;\n case \"nullability\":\n return `${where}: ${field} is ${difference.generated} in schema.generated.ts and ${difference.live} in the database`;\n }\n}\n\n/**\n * Report the differences, one warning each, and never throw.\n *\n * Per difference rather than one summary block on purpose: these are read out of\n * a log aggregator as often as a terminal, and a single multi-line message\n * cannot be filtered down to the one collection somebody is looking at.\n *\n * The fix it names is `rebase db push`. `rebase dev` rewrites\n * schema.generated.ts from the collections on every change, and boot applies\n * only additive changes, so what is still different at boot is the database\n * behind the file: a rename, a dropped column, a narrowed type, a NOT NULL.\n * `rebase schema generate` rewrites the file and leaves the database as it is,\n * so it is named only for collections that changed since the file was written.\n */\nexport function warnOnGeneratedSchemaDrift(differences: SchemaDifference[]): void {\n if (differences.length === 0) return;\n\n logger.warn(\n `📄 [schema] schema.generated.ts and the database disagree (${differences.length} difference` +\n `${differences.length === 1 ? \"\" : \"s\"}). Boot applies only additive changes; these are the ones it ` +\n \"left for you. Run `rebase db push` to apply them (it shows the plan and asks before anything \" +\n \"destructive), or `rebase db generate` for a migration. If the collections changed since \" +\n \"schema.generated.ts was written, run `rebase schema generate` first.\"\n );\n\n for (const difference of differences) {\n logger.warn(` • ${describeSchemaDifference(difference)}`, {\n schemaDrift: {\n kind: difference.kind,\n table: difference.table,\n collection: difference.collection,\n column: difference.column,\n property: difference.property,\n generated: difference.generated,\n live: difference.live\n },\n fix: \"rebase db push\"\n });\n }\n}\n"],"mappings":"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"}
@@ -1,6 +1,6 @@
1
1
  import { createRequire as __createRequire } from "module";
2
2
  __createRequire(import.meta.url);
3
- import { n as __exportAll } from "./rolldown-runtime-V7rFbMRe.js";
3
+ import { i as __exportAll } from "./cli-prompt-DXG0yk-h.js";
4
4
  import { getTableName, relationalCollections, resolveCollectionRelations } from "@rebasepro/common";
5
5
  import { generateForeignKeyName, legacyForeignKeyName } from "@rebasepro/utils";
6
6
  //#region src/schema/generated-schema-staleness.ts
@@ -326,4 +326,4 @@ function describeDeclarationDifferences(differences) {
326
326
  //#endregion
327
327
  export { describeDeclarationDifferences as n, generated_schema_staleness_exports as r, compareGeneratedDeclarations as t };
328
328
 
329
- //# sourceMappingURL=generated-schema-staleness-FlxM2i4i.js.map
329
+ //# sourceMappingURL=generated-schema-staleness-BvNF3i-s.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"generated-schema-staleness-FlxM2i4i.js","names":[],"sources":["../src/schema/generated-schema-staleness.ts"],"sourcesContent":["import { CollectionConfig } from \"@rebasepro/types\";\nimport { getTableName, resolveCollectionRelations, relationalCollections } from \"@rebasepro/common\";\nimport { generateForeignKeyName, legacyForeignKeyName } from \"@rebasepro/utils\";\n\n/**\n * Notice a generated Drizzle schema that a *library upgrade* invalidated.\n *\n * `rebase dev` already watches `config/collections` and warns when a collection\n * file changes. That covers drift the developer caused. It cannot cover this one,\n * because nothing the developer owns changed: 0.13 derives `category_id` where\n * 0.12 derived `categorie_id`, from the same unedited collection. The watcher\n * never fires, and `backend/src/schema.generated.ts` quietly stops describing the\n * schema the runtime expects.\n *\n * The consequence is not cosmetic. Boot-ensure renames the column in the\n * database, then relation validation reads the stale module and refuses to\n * start — on that boot and every boot after it, because the rename is already\n * applied and will not be attempted again.\n *\n * Deliberately narrow: this answers \"does the generated schema name a foreign key\n * the way the previous rule did\", not \"is this file what we would generate now\".\n * The wide question would report every whitespace change in the generator as a\n * fatal staleness, and a check that cries wolf gets switched off.\n */\n\n/** One column the generated schema names under the pre-0.13 rule. */\nexport interface LegacyForeignKeyName {\n /** Table whose column declaration is stale. */\n table: string;\n /** The name the generated schema declares. */\n legacy: string;\n /** The name this release derives, and which the database now carries. */\n current: string;\n /** `<collection>.<relation>` that derives it, for the message. */\n relation: string;\n}\n\n/**\n * The slice of the generated source declaring one table.\n *\n * Scoping matters: two junctions in one file can carry columns of the same name,\n * and a whole-file match would attribute a stale column to whichever table the\n * reader looks at first. Returns \"\" when the table is not in the file at all,\n * which is not staleness — it is a table the generator has not been asked about.\n */\nfunction tableBlock(source: string, table: string): string {\n // Both forms the generator emits. A collection with `schema: \"rebase\"` — the\n // auth users collection, in every scaffold — is written as\n // `rebaseSchema.table(\"users\", …)`, so matching only `pgTable(` made this\n // blind to every table outside `public`: the block came back empty, and an\n // empty block reads as \"a table the generator has not been asked about\".\n // The rename check therefore never inspected them.\n const literal = table.replace(/[.*+?^${}()|[\\]\\\\]/g, \"\\\\$&\");\n const start = source.search(new RegExp(`(?:pgTable|\\\\.table)\\\\(\\\\s*[\"']${literal}[\"']`));\n if (start === -1) return \"\";\n // Generated files put every table in its own `export const`, so the next one\n // is the end of this block. No brace counting, nothing to get wrong.\n const next = source.indexOf(\"\\nexport \", start);\n return next === -1 ? source.slice(start) : source.slice(start, next);\n}\n\n/**\n * Whether a block *declares* the column, rather than merely mentioning it.\n *\n * A comment explaining the rename, or a policy expression naming the old column,\n * must not read as a declaration — otherwise regenerating the file would not\n * clear the finding and the check would be permanently red.\n */\nfunction declaresColumn(block: string, column: string): boolean {\n // `categorieId: integer(\"categorie_id\")` — the Drizzle column shape. Only\n // the string argument is the column; the key beside it is the *wire* name\n // and no longer agrees with it (`authorId: integer(\"author_id\")`). Matching\n // both, as this did, made every camelCased key look like a column the\n // generated file did not declare.\n const literal = column.replace(/[.*+?^${}()|[\\]\\\\]/g, \"\\\\$&\");\n return new RegExp(`(^|[\\\\s,{])[\\\\w$\"']+\\\\s*:\\\\s*\\\\w+\\\\(\\\\s*[\"']${literal}[\"']`, \"m\").test(block);\n}\n\n/**\n * @param generatedSource contents of `backend/src/schema.generated.ts`\n * @param collections the project's collections, as this release reads them\n */\nexport function findLegacyForeignKeyNames(\n generatedSource: string,\n collections: CollectionConfig[]\n): LegacyForeignKeyName[] {\n const found: LegacyForeignKeyName[] = [];\n const seen = new Set<string>();\n\n /**\n * Report `wanted` as stale when the generated schema declares what the\n * previous rule would have derived from `source` instead.\n *\n * The `wanted !== current` guard is what honours an explicitly named column.\n * An author who pinned `categorie_id` has said so on the relation, so the\n * generated file agreeing with them is correct, not stale — and the rename\n * note documents exactly that opt-out.\n */\n const consider = (\n table: string,\n wanted: string,\n sourceName: string | undefined,\n relation: string\n ): void => {\n if (!table || !wanted || !sourceName) return;\n\n const current = generateForeignKeyName(sourceName);\n const legacy = legacyForeignKeyName(sourceName);\n if (legacy === current) return; // this name never moved\n if (wanted !== current) return; // pinned by the author, or unrelated\n\n const block = tableBlock(generatedSource, table);\n if (!block) return;\n if (!declaresColumn(block, legacy)) return;\n if (declaresColumn(block, current)) return; // already regenerated\n\n const key = `${table}.${legacy}`;\n if (seen.has(key)) return;\n seen.add(key);\n found.push({ table, legacy, current, relation });\n };\n\n // Same rule as the generator whose output this reads: a collection that\n // gets no table cannot have named a column in it. Inert in practice today —\n // the finding also requires the generated file to declare the table — but\n // the guard is what keeps that true when a non-SQL collection's slug happens\n // to match a SQL one's table.\n for (const collection of relationalCollections(collections)) {\n const sourceTable = getTableName(collection);\n\n for (const [name, relation] of Object.entries(resolveCollectionRelations(collection))) {\n const at = `${collection.slug}.${name}`;\n\n let target: CollectionConfig | undefined;\n try {\n target = relation.target?.();\n } catch {\n // A throwing target thunk is its own defect, reported at boot by\n // `validate-relations`. Nothing to say about its column names.\n continue;\n }\n\n switch (relation.kind) {\n case \"belongsTo\":\n consider(sourceTable, relation.localKey, relation.relationName, at);\n break;\n\n case \"hasOne\":\n case \"hasMany\":\n if (target) {\n consider(getTableName(target), relation.foreignKeyOnTarget, collection.slug, at);\n }\n break;\n\n case \"manyToMany\":\n consider(relation.through.table, relation.through.sourceColumn, collection.slug, at);\n if (target) {\n consider(relation.through.table, relation.through.targetColumn, target.slug, at);\n }\n break;\n\n default:\n // `via` joins are written by hand — there is no derived name\n // for the rule change to have moved.\n break;\n }\n }\n }\n\n return found;\n}\n\n/** One-line summary for a log or a CLI notice. */\nexport function describeLegacyForeignKeyNames(found: LegacyForeignKeyName[]): string {\n return found\n .map(f => ` • ${f.table}.${f.legacy} → ${f.current} (${f.relation})`)\n .join(\"\\n\");\n}\n\n// ── The other way a generated schema goes stale ──────────────────────────────\n//\n// The rename above is the subtle one: nothing the developer owns changed. The\n// ordinary one is the opposite — a collection or a relation was added and the\n// generator was not re-run, so the checked-in module simply does not describe\n// part of the schema. It fails later and elsewhere: relation validation, or a\n// query against a table the module has never heard of.\n//\n// Still not \"is this byte-for-byte what the generator would emit now\". This asks\n// only about *names*, which is what the rest of the system reads out of the\n// file, so reformatting the generator cannot make it red.\n\n/** Something the collections derive that the generated schema does not declare. */\nexport interface MissingGeneratedName {\n /** The table it belongs to, or is. */\n table: string;\n /** The column, when a column is what is missing. */\n column?: string;\n /** `<collection>` or `<collection>.<relation>`, for the message. */\n source: string;\n}\n\n/**\n * Tables and derived foreign-key columns the collections name and the generated\n * schema does not.\n *\n * @param generatedSource contents of `backend/src/schema.generated.ts`\n * @param collections the project's collections, as this release reads them\n */\nexport function findMissingGeneratedNames(\n generatedSource: string,\n collections: CollectionConfig[]\n): MissingGeneratedName[] {\n const missing: MissingGeneratedName[] = [];\n const seen = new Set<string>();\n\n const report = (table: string, source: string, column?: string): void => {\n const key = `${table}.${column ?? \"\"}`;\n if (seen.has(key)) return;\n seen.add(key);\n missing.push({ table, source, ...(column ? { column } : {}) });\n };\n\n const requireColumn = (table: string, column: string | undefined, source: string): void => {\n if (!table || !column) return;\n const block = tableBlock(generatedSource, table);\n // A table that is absent is reported once, as a table. Listing each of\n // its columns as separately missing would bury the one fact that\n // explains all of them.\n if (!block) return report(table, source);\n if (!declaresColumn(block, column)) report(table, source, column);\n };\n\n for (const collection of relationalCollections(collections)) {\n const sourceTable = getTableName(collection);\n if (!tableBlock(generatedSource, sourceTable)) {\n report(sourceTable, collection.slug);\n continue;\n }\n\n for (const [name, relation] of Object.entries(resolveCollectionRelations(collection))) {\n const at = `${collection.slug}.${name}`;\n\n let target: CollectionConfig | undefined;\n try {\n target = relation.target?.();\n } catch {\n // Its own defect, reported at boot by `validate-relations`.\n continue;\n }\n\n switch (relation.kind) {\n case \"belongsTo\":\n requireColumn(sourceTable, relation.localKey, at);\n break;\n\n case \"hasOne\":\n case \"hasMany\":\n if (target) requireColumn(getTableName(target), relation.foreignKeyOnTarget, at);\n break;\n\n case \"manyToMany\":\n requireColumn(relation.through.table, relation.through.sourceColumn, at);\n requireColumn(relation.through.table, relation.through.targetColumn, at);\n break;\n\n default:\n // `via` joins are written by hand; there is no derived name\n // to be missing.\n break;\n }\n }\n }\n\n return missing;\n}\n\n/** One-line summary for a log or a CLI notice. */\nexport function describeMissingGeneratedNames(missing: MissingGeneratedName[]): string {\n return missing\n .map(m => (m.column\n ? ` • ${m.table}.${m.column} is not declared (${m.source})`\n : ` • table ${m.table} is not declared (${m.source})`))\n .join(\"\\n\");\n}\n\n/** What `rebase schema stale` found, and what that costs. */\nexport interface StaleVerdict {\n /** The lines to print, in order. Empty only under `--fix` with nothing to fix. */\n lines: string[];\n /** Non-zero only when the reader must act before the server will boot. */\n exitCode: 0 | 1;\n /** True when `--fix` should regenerate. */\n regenerate: boolean;\n}\n\n/**\n * The whole of `schema stale`'s decision, separated from doing it.\n *\n * Separated because of what the command used to do on the clean path: **exit 0\n * having written zero bytes to stdout and zero to stderr.** For a command the\n * top-level help describes as \"Report generated schema files the collections\n * have moved past\", that is indistinguishable from a no-op, a crash, and a\n * subcommand the driver does not implement. Three of its four paths were\n * silent, and none of them could be tested where they stood: `cli.ts` uses\n * `import.meta`, which the driver's jest suite cannot load at all.\n *\n * `--fix` is the exception, and stays silent when there is nothing to fix.\n * `rebase dev` runs `schema stale --fix` before every boot with inherited\n * stdio, and \"nothing was wrong\" is not news a hundred lines into a start-up\n * transcript. The flag is the automated caller's; its silence is the point.\n */\nexport function staleVerdict(input: {\n /** `--output`, as the reader typed it. */\n outputPath: string;\n /** Whether the generated schema exists at all. */\n generatedExists: boolean;\n /** The legacy foreign-key names found in it, if it was read. */\n stale: LegacyForeignKeyName[];\n /**\n * How the file differs from what the current collections generate.\n *\n * The check this command's own summary promises — \"generated schema files\n * that no longer match the collections\" — and for a long time the one it did\n * not make. Staleness was computed from {@link findLegacyForeignKeyNames}\n * alone, which finds one specific historical defect: a junction column named\n * the way a previous release derived it. Every other way a file can fall\n * behind its collections — a table added, a column retyped, an enum label\n * dropped from the offer — left the command printing \"Nothing stale\" about a\n * file that regenerating visibly changes.\n *\n * `projects_database_mode` is the one that found this. The shared database\n * tier was retired, the label came off the collection, and `schema stale`\n * called the file a match for weeks while every `pnpm run build` silently\n * rewrote it.\n *\n * Optional so a caller that has not loaded the collections — the only way to\n * know what they would generate — reports what it does know rather than\n * claiming a clean bill it never checked.\n */\n differences?: DeclarationDifference[];\n /** Why the comparison could not be made, when it could not. */\n unreadable?: string;\n /** Whether `--fix` was given. */\n fix: boolean;\n}): StaleVerdict {\n const quiet = (lines: string[]): string[] => (input.fix ? [] : lines);\n\n if (!input.generatedExists) {\n // Not staleness: a fresh project has not run the generator, and saying\n // \"stale\" about a file that does not exist sends the reader looking for\n // something to fix.\n return {\n lines: quiet([` No generated schema yet at ${input.outputPath} — nothing to compare.`,\n \" `rebase schema generate` creates it.\"]),\n exitCode: 0,\n regenerate: false\n };\n }\n\n if (input.unreadable !== undefined) {\n // Best-effort by design: a collections directory that will not load is a\n // real error, but boot reports it far better than this does. Saying so\n // is still better than exiting 0 in silence about a check that did not\n // run — this file's whole subject.\n return {\n lines: quiet([` ⏭ Not checked: ${input.unreadable}`]),\n exitCode: 0,\n regenerate: false\n };\n }\n\n // Both checks, and both reported. They answer different questions and a file\n // can fail either: `stale` names a specific historical defect with a\n // specific remedy, `differences` says the file is simply not what the\n // collections now generate. Reporting only the first is what let this\n // command pass a file that had visibly moved.\n const problems: string[] = [];\n if (input.stale.length > 0) {\n problems.push(\n ` ⚠️ ${input.outputPath} names ${input.stale.length} foreign key(s) the way an earlier release did:`,\n describeLegacyForeignKeyNames(input.stale)\n );\n }\n const differences = input.differences ?? [];\n if (differences.length > 0) {\n problems.push(\n ` ⚠️ ${input.outputPath} is not what the current collections generate.`,\n ` Differs in: ${describeDeclarationDifferences(differences)}.`\n );\n }\n\n if (problems.length === 0) {\n return {\n lines: quiet([` ✓ Nothing stale — 1 generated file matches (${input.outputPath}).`]),\n exitCode: 0,\n regenerate: false\n };\n }\n\n const found = [\"\", ...problems, \"\"];\n\n if (input.fix) {\n return { lines: [...found, \" Regenerating the Drizzle schema so it matches...\"], exitCode: 0, regenerate: true };\n }\n\n return {\n lines: [\n ...found,\n // Not \"the server will refuse to start\" any more. The runtime builds\n // its tables from `information_schema`, so a stale file changes\n // nothing it serves — it warns once at boot and carries on. What the\n // file still decides is everything around the server: Atlas plans\n // migrations from it, `db push` diffs it, `eject` writes it out, and\n // user code imports it.\n //\n // The first sentence is the renamed-column one and belongs only to\n // the legacy foreign-key finding. Printed unconditionally it told a\n // reader whose enum label had simply moved that a column had been\n // renamed at boot, which sends them looking for a rename that never\n // happened. The consequence below is the same either way.\n ...(input.stale.length > 0\n ? [\" The database column has already been renamed at boot.\"]\n : []),\n \" The server reads the database, so it will still start — but `db push`, Atlas, `eject` \"\n + \"and any code importing this file are all reading a description that no longer matches.\",\n \" Run `rebase schema generate` to regenerate it.\"\n ],\n exitCode: 1,\n regenerate: false\n };\n}\n\n\n// ── Is this file what the current collections generate? ──────────────────────\n\n/** One exported declaration that differs between two generated schemas. */\nexport interface DeclarationDifference {\n /** The exported symbol: a table, an enum, a relations block. */\n name: string;\n kind: \"missing\" | \"unexpected\" | \"changed\";\n /**\n * For a `changed` table or enum, what moved inside it, in the reader's\n * words: `column subtitle is in the file, no longer generated`, `label\n * \"archived\" is not in the file`. Absent for anything else.\n */\n details?: string[];\n}\n\n/**\n * `export const NAME = …` → the declaration, with formatting and the\n * generator's own comments removed.\n *\n * Every generated declaration starts at column 0 and runs to the next one, so\n * splitting on them needs no brace counting and cannot get out of step with a\n * policy clause that happens to contain a brace.\n */\nfunction declarations(source: string): Map<string, string> {\n const out = new Map<string, string>();\n const starts = [...source.matchAll(/^export const (\\w+) =/gm)];\n starts.forEach((match, index) => {\n const from = match.index! + match[0].length;\n const to = index + 1 < starts.length ? starts[index + 1].index! : source.length;\n out.set(\n match[1],\n source.slice(from, to)\n // The `// INCLUDE (...)` note on a covering index, and any other\n // remark the renderer leaves: they describe the declaration,\n // they are not part of it.\n .replace(/\\/\\/[^\\n]*/g, \"\")\n .replace(/\\s+/g, \" \")\n .trim()\n );\n });\n return out;\n}\n\n/**\n * What differs between the schema the current collections generate and the one\n * on disk — by declaration, not by byte.\n *\n * `rebase doctor` used to whitespace-normalise both files and compare the\n * strings, which answers \"are these the same text\" when the question is \"does\n * this file still describe the same schema\". The difference is not academic:\n * the answer was a single yes/no with nothing to act on, so a developer told\n * their schema was stale had to regenerate and read the diff to find out what\n * had changed.\n *\n * Comparing declarations answers the question the reader actually has. The\n * generated file is a flat list of `export const`s — one per table, enum and\n * relations block, each the rendering of a piece of the schema plan — and two\n * files declaring the same symbols with the same bodies describe the same\n * database whatever their formatting.\n *\n * Not a plan-to-plan comparison, and deliberately not: reading a plan back out\n * of `schema.generated.ts` means interpreting drizzle-orm builder calls, which\n * is a second interpreter — the exact thing this area was just rid of. The\n * rendered declarations are the closest honest proxy, and the renderer is\n * deterministic, so the only things normalised away are what it is free to vary\n * independently of the plan: whitespace and its own comments.\n */\nexport function compareGeneratedDeclarations(expected: string, actual: string): DeclarationDifference[] {\n const want = declarations(expected);\n const have = declarations(actual);\n const differences: DeclarationDifference[] = [];\n for (const [name, body] of want) {\n const onDisk = have.get(name);\n if (onDisk === undefined) differences.push({ name, kind: \"missing\" });\n else if (onDisk !== body) {\n const details = changesWithin(body, onDisk);\n differences.push(details.length > 0 ? { name, kind: \"changed\", details } : { name, kind: \"changed\" });\n }\n }\n for (const name of have.keys()) {\n if (!want.has(name)) differences.push({ name, kind: \"unexpected\" });\n }\n return differences;\n}\n\n/**\n * The entries between the bracket at `from` and the one that closes it — a\n * column object's columns, an enum's labels — split at their top-level commas.\n * Strings, template literals and nested brackets are stepped over, so\n * `.references((): AnyPgColumn => authors.id, { onDelete: \"set null\" })` stays\n * one entry. Null when the bracket is never closed.\n */\nfunction topLevelEntries(text: string, from: number): string[] | null {\n const entries: string[] = [];\n let depth = 0;\n let start = from + 1;\n let quote: string | null = null;\n for (let index = from; index < text.length; index++) {\n const char = text[index];\n if (quote) {\n if (char === \"\\\\\") index++;\n else if (char === quote) quote = null;\n continue;\n }\n if (char === \"\\\"\" || char === \"'\" || char === \"`\") quote = char;\n else if (char === \"(\" || char === \"[\" || char === \"{\") depth++;\n else if (char === \")\" || char === \"]\" || char === \"}\") {\n depth--;\n if (depth === 0) {\n const last = text.slice(start, index).trim();\n if (last) entries.push(last);\n return entries;\n }\n } else if (char === \",\" && depth === 1) {\n entries.push(text.slice(start, index).trim());\n start = index + 1;\n }\n }\n return null;\n}\n\n/** A table declaration's columns, by database name, each with its declaration. */\nfunction tableColumns(body: string): Map<string, string> | null {\n const table = /^(?:pgTable|[\\w$]+\\.table)\\(\\s*[\"'][^\"']+[\"']\\s*,\\s*/.exec(body);\n if (!table || body[table[0].length] !== \"{\") return null;\n const entries = topLevelEntries(body, table[0].length);\n if (!entries) return null;\n const columns = new Map<string, string>();\n for (const entry of entries) {\n // `authorId: integer(\"author_id\")…`: the string argument is the column.\n const column = /^[^:]+:\\s*[\\w$.]+\\(\\s*[\"']([^\"']+)[\"']/.exec(entry);\n if (column) columns.set(column[1], entry.slice(entry.indexOf(\":\") + 1).trim());\n }\n return columns;\n}\n\n/** An enum declaration's labels, in order. */\nfunction enumLabels(body: string): string[] | null {\n const declaration = /^(?:pgEnum|[\\w$]+\\.enum)\\(\\s*[\"'][^\"']+[\"']\\s*,\\s*/.exec(body);\n if (!declaration || body[declaration[0].length] !== \"[\") return null;\n const entries = topLevelEntries(body, declaration[0].length);\n return entries ? entries.map(entry => entry.replace(/^[\"']|[\"']$/g, \"\")) : null;\n}\n\n/**\n * What moved inside one table or enum, for the message only. Whether a file is\n * stale is still the declaration comparison above; this reads the rendered\n * text just far enough to name the column or label, and names nothing for a\n * declaration of any other shape.\n */\nfunction changesWithin(expected: string, actual: string): string[] {\n const wantedLabels = enumLabels(expected);\n const labelsOnDisk = enumLabels(actual);\n if (wantedLabels && labelsOnDisk) {\n const changes = [\n ...wantedLabels.filter(label => !labelsOnDisk.includes(label)).map(label => `label \"${label}\" is not in the file`),\n ...labelsOnDisk.filter(label => !wantedLabels.includes(label)).map(label => `label \"${label}\" is in the file, no longer generated`)\n ];\n return changes.length > 0 ? changes : [\"its labels are in another order\"];\n }\n\n const wantedColumns = tableColumns(expected);\n const columnsOnDisk = tableColumns(actual);\n if (!wantedColumns || !columnsOnDisk) return [];\n const changes: string[] = [];\n for (const [column, declaration] of wantedColumns) {\n const onDisk = columnsOnDisk.get(column);\n if (onDisk === undefined) changes.push(`column ${column} is not in the file`);\n else if (onDisk !== declaration) changes.push(`column ${column} differs`);\n }\n for (const column of columnsOnDisk.keys()) {\n if (!wantedColumns.has(column)) changes.push(`column ${column} is in the file, no longer generated`);\n }\n return changes.length > 0 ? changes : [\"its indexes, constraints or policies differ\"];\n}\n\n/** The differences, as the line a developer reads in `rebase doctor` and `rebase schema stale`. */\nexport function describeDeclarationDifferences(differences: DeclarationDifference[]): string {\n const shown = differences.slice(0, 5).map(difference => {\n if (difference.kind === \"missing\") return `${difference.name} (not in the file)`;\n if (difference.kind === \"unexpected\") return `${difference.name} (in the file, no longer generated)`;\n return `${difference.name} (${difference.details?.join(\"; \") ?? \"differs\"})`;\n });\n const rest = differences.length - shown.length;\n return shown.join(\", \") + (rest > 0 ? `, and ${rest} more` : \"\");\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"generated-schema-staleness-BvNF3i-s.js","names":[],"sources":["../src/schema/generated-schema-staleness.ts"],"sourcesContent":["import { CollectionConfig } from \"@rebasepro/types\";\nimport { getTableName, resolveCollectionRelations, relationalCollections } from \"@rebasepro/common\";\nimport { generateForeignKeyName, legacyForeignKeyName } from \"@rebasepro/utils\";\n\n/**\n * Notice a generated Drizzle schema that a *library upgrade* invalidated.\n *\n * `rebase dev` already watches `config/collections` and warns when a collection\n * file changes. That covers drift the developer caused. It cannot cover this one,\n * because nothing the developer owns changed: 0.13 derives `category_id` where\n * 0.12 derived `categorie_id`, from the same unedited collection. The watcher\n * never fires, and `backend/src/schema.generated.ts` quietly stops describing the\n * schema the runtime expects.\n *\n * The consequence is not cosmetic. Boot-ensure renames the column in the\n * database, then relation validation reads the stale module and refuses to\n * start — on that boot and every boot after it, because the rename is already\n * applied and will not be attempted again.\n *\n * Deliberately narrow: this answers \"does the generated schema name a foreign key\n * the way the previous rule did\", not \"is this file what we would generate now\".\n * The wide question would report every whitespace change in the generator as a\n * fatal staleness, and a check that cries wolf gets switched off.\n */\n\n/** One column the generated schema names under the pre-0.13 rule. */\nexport interface LegacyForeignKeyName {\n /** Table whose column declaration is stale. */\n table: string;\n /** The name the generated schema declares. */\n legacy: string;\n /** The name this release derives, and which the database now carries. */\n current: string;\n /** `<collection>.<relation>` that derives it, for the message. */\n relation: string;\n}\n\n/**\n * The slice of the generated source declaring one table.\n *\n * Scoping matters: two junctions in one file can carry columns of the same name,\n * and a whole-file match would attribute a stale column to whichever table the\n * reader looks at first. Returns \"\" when the table is not in the file at all,\n * which is not staleness — it is a table the generator has not been asked about.\n */\nfunction tableBlock(source: string, table: string): string {\n // Both forms the generator emits. A collection with `schema: \"rebase\"` — the\n // auth users collection, in every scaffold — is written as\n // `rebaseSchema.table(\"users\", …)`, so matching only `pgTable(` made this\n // blind to every table outside `public`: the block came back empty, and an\n // empty block reads as \"a table the generator has not been asked about\".\n // The rename check therefore never inspected them.\n const literal = table.replace(/[.*+?^${}()|[\\]\\\\]/g, \"\\\\$&\");\n const start = source.search(new RegExp(`(?:pgTable|\\\\.table)\\\\(\\\\s*[\"']${literal}[\"']`));\n if (start === -1) return \"\";\n // Generated files put every table in its own `export const`, so the next one\n // is the end of this block. No brace counting, nothing to get wrong.\n const next = source.indexOf(\"\\nexport \", start);\n return next === -1 ? source.slice(start) : source.slice(start, next);\n}\n\n/**\n * Whether a block *declares* the column, rather than merely mentioning it.\n *\n * A comment explaining the rename, or a policy expression naming the old column,\n * must not read as a declaration — otherwise regenerating the file would not\n * clear the finding and the check would be permanently red.\n */\nfunction declaresColumn(block: string, column: string): boolean {\n // `categorieId: integer(\"categorie_id\")` — the Drizzle column shape. Only\n // the string argument is the column; the key beside it is the *wire* name\n // and no longer agrees with it (`authorId: integer(\"author_id\")`). Matching\n // both, as this did, made every camelCased key look like a column the\n // generated file did not declare.\n const literal = column.replace(/[.*+?^${}()|[\\]\\\\]/g, \"\\\\$&\");\n return new RegExp(`(^|[\\\\s,{])[\\\\w$\"']+\\\\s*:\\\\s*\\\\w+\\\\(\\\\s*[\"']${literal}[\"']`, \"m\").test(block);\n}\n\n/**\n * @param generatedSource contents of `backend/src/schema.generated.ts`\n * @param collections the project's collections, as this release reads them\n */\nexport function findLegacyForeignKeyNames(\n generatedSource: string,\n collections: CollectionConfig[]\n): LegacyForeignKeyName[] {\n const found: LegacyForeignKeyName[] = [];\n const seen = new Set<string>();\n\n /**\n * Report `wanted` as stale when the generated schema declares what the\n * previous rule would have derived from `source` instead.\n *\n * The `wanted !== current` guard is what honours an explicitly named column.\n * An author who pinned `categorie_id` has said so on the relation, so the\n * generated file agreeing with them is correct, not stale — and the rename\n * note documents exactly that opt-out.\n */\n const consider = (\n table: string,\n wanted: string,\n sourceName: string | undefined,\n relation: string\n ): void => {\n if (!table || !wanted || !sourceName) return;\n\n const current = generateForeignKeyName(sourceName);\n const legacy = legacyForeignKeyName(sourceName);\n if (legacy === current) return; // this name never moved\n if (wanted !== current) return; // pinned by the author, or unrelated\n\n const block = tableBlock(generatedSource, table);\n if (!block) return;\n if (!declaresColumn(block, legacy)) return;\n if (declaresColumn(block, current)) return; // already regenerated\n\n const key = `${table}.${legacy}`;\n if (seen.has(key)) return;\n seen.add(key);\n found.push({ table, legacy, current, relation });\n };\n\n // Same rule as the generator whose output this reads: a collection that\n // gets no table cannot have named a column in it. Inert in practice today —\n // the finding also requires the generated file to declare the table — but\n // the guard is what keeps that true when a non-SQL collection's slug happens\n // to match a SQL one's table.\n for (const collection of relationalCollections(collections)) {\n const sourceTable = getTableName(collection);\n\n for (const [name, relation] of Object.entries(resolveCollectionRelations(collection))) {\n const at = `${collection.slug}.${name}`;\n\n let target: CollectionConfig | undefined;\n try {\n target = relation.target?.();\n } catch {\n // A throwing target thunk is its own defect, reported at boot by\n // `validate-relations`. Nothing to say about its column names.\n continue;\n }\n\n switch (relation.kind) {\n case \"belongsTo\":\n consider(sourceTable, relation.localKey, relation.relationName, at);\n break;\n\n case \"hasOne\":\n case \"hasMany\":\n if (target) {\n consider(getTableName(target), relation.foreignKeyOnTarget, collection.slug, at);\n }\n break;\n\n case \"manyToMany\":\n consider(relation.through.table, relation.through.sourceColumn, collection.slug, at);\n if (target) {\n consider(relation.through.table, relation.through.targetColumn, target.slug, at);\n }\n break;\n\n default:\n // `via` joins are written by hand — there is no derived name\n // for the rule change to have moved.\n break;\n }\n }\n }\n\n return found;\n}\n\n/** One-line summary for a log or a CLI notice. */\nexport function describeLegacyForeignKeyNames(found: LegacyForeignKeyName[]): string {\n return found\n .map(f => ` • ${f.table}.${f.legacy} → ${f.current} (${f.relation})`)\n .join(\"\\n\");\n}\n\n// ── The other way a generated schema goes stale ──────────────────────────────\n//\n// The rename above is the subtle one: nothing the developer owns changed. The\n// ordinary one is the opposite — a collection or a relation was added and the\n// generator was not re-run, so the checked-in module simply does not describe\n// part of the schema. It fails later and elsewhere: relation validation, or a\n// query against a table the module has never heard of.\n//\n// Still not \"is this byte-for-byte what the generator would emit now\". This asks\n// only about *names*, which is what the rest of the system reads out of the\n// file, so reformatting the generator cannot make it red.\n\n/** Something the collections derive that the generated schema does not declare. */\nexport interface MissingGeneratedName {\n /** The table it belongs to, or is. */\n table: string;\n /** The column, when a column is what is missing. */\n column?: string;\n /** `<collection>` or `<collection>.<relation>`, for the message. */\n source: string;\n}\n\n/**\n * Tables and derived foreign-key columns the collections name and the generated\n * schema does not.\n *\n * @param generatedSource contents of `backend/src/schema.generated.ts`\n * @param collections the project's collections, as this release reads them\n */\nexport function findMissingGeneratedNames(\n generatedSource: string,\n collections: CollectionConfig[]\n): MissingGeneratedName[] {\n const missing: MissingGeneratedName[] = [];\n const seen = new Set<string>();\n\n const report = (table: string, source: string, column?: string): void => {\n const key = `${table}.${column ?? \"\"}`;\n if (seen.has(key)) return;\n seen.add(key);\n missing.push({ table, source, ...(column ? { column } : {}) });\n };\n\n const requireColumn = (table: string, column: string | undefined, source: string): void => {\n if (!table || !column) return;\n const block = tableBlock(generatedSource, table);\n // A table that is absent is reported once, as a table. Listing each of\n // its columns as separately missing would bury the one fact that\n // explains all of them.\n if (!block) return report(table, source);\n if (!declaresColumn(block, column)) report(table, source, column);\n };\n\n for (const collection of relationalCollections(collections)) {\n const sourceTable = getTableName(collection);\n if (!tableBlock(generatedSource, sourceTable)) {\n report(sourceTable, collection.slug);\n continue;\n }\n\n for (const [name, relation] of Object.entries(resolveCollectionRelations(collection))) {\n const at = `${collection.slug}.${name}`;\n\n let target: CollectionConfig | undefined;\n try {\n target = relation.target?.();\n } catch {\n // Its own defect, reported at boot by `validate-relations`.\n continue;\n }\n\n switch (relation.kind) {\n case \"belongsTo\":\n requireColumn(sourceTable, relation.localKey, at);\n break;\n\n case \"hasOne\":\n case \"hasMany\":\n if (target) requireColumn(getTableName(target), relation.foreignKeyOnTarget, at);\n break;\n\n case \"manyToMany\":\n requireColumn(relation.through.table, relation.through.sourceColumn, at);\n requireColumn(relation.through.table, relation.through.targetColumn, at);\n break;\n\n default:\n // `via` joins are written by hand; there is no derived name\n // to be missing.\n break;\n }\n }\n }\n\n return missing;\n}\n\n/** One-line summary for a log or a CLI notice. */\nexport function describeMissingGeneratedNames(missing: MissingGeneratedName[]): string {\n return missing\n .map(m => (m.column\n ? ` • ${m.table}.${m.column} is not declared (${m.source})`\n : ` • table ${m.table} is not declared (${m.source})`))\n .join(\"\\n\");\n}\n\n/** What `rebase schema stale` found, and what that costs. */\nexport interface StaleVerdict {\n /** The lines to print, in order. Empty only under `--fix` with nothing to fix. */\n lines: string[];\n /** Non-zero only when the reader must act before the server will boot. */\n exitCode: 0 | 1;\n /** True when `--fix` should regenerate. */\n regenerate: boolean;\n}\n\n/**\n * The whole of `schema stale`'s decision, separated from doing it.\n *\n * Separated because of what the command used to do on the clean path: **exit 0\n * having written zero bytes to stdout and zero to stderr.** For a command the\n * top-level help describes as \"Report generated schema files the collections\n * have moved past\", that is indistinguishable from a no-op, a crash, and a\n * subcommand the driver does not implement. Three of its four paths were\n * silent, and none of them could be tested where they stood: `cli.ts` uses\n * `import.meta`, which the driver's jest suite cannot load at all.\n *\n * `--fix` is the exception, and stays silent when there is nothing to fix.\n * `rebase dev` runs `schema stale --fix` before every boot with inherited\n * stdio, and \"nothing was wrong\" is not news a hundred lines into a start-up\n * transcript. The flag is the automated caller's; its silence is the point.\n */\nexport function staleVerdict(input: {\n /** `--output`, as the reader typed it. */\n outputPath: string;\n /** Whether the generated schema exists at all. */\n generatedExists: boolean;\n /** The legacy foreign-key names found in it, if it was read. */\n stale: LegacyForeignKeyName[];\n /**\n * How the file differs from what the current collections generate.\n *\n * The check this command's own summary promises — \"generated schema files\n * that no longer match the collections\" — and for a long time the one it did\n * not make. Staleness was computed from {@link findLegacyForeignKeyNames}\n * alone, which finds one specific historical defect: a junction column named\n * the way a previous release derived it. Every other way a file can fall\n * behind its collections — a table added, a column retyped, an enum label\n * dropped from the offer — left the command printing \"Nothing stale\" about a\n * file that regenerating visibly changes.\n *\n * `projects_database_mode` is the one that found this. The shared database\n * tier was retired, the label came off the collection, and `schema stale`\n * called the file a match for weeks while every `pnpm run build` silently\n * rewrote it.\n *\n * Optional so a caller that has not loaded the collections — the only way to\n * know what they would generate — reports what it does know rather than\n * claiming a clean bill it never checked.\n */\n differences?: DeclarationDifference[];\n /** Why the comparison could not be made, when it could not. */\n unreadable?: string;\n /** Whether `--fix` was given. */\n fix: boolean;\n}): StaleVerdict {\n const quiet = (lines: string[]): string[] => (input.fix ? [] : lines);\n\n if (!input.generatedExists) {\n // Not staleness: a fresh project has not run the generator, and saying\n // \"stale\" about a file that does not exist sends the reader looking for\n // something to fix.\n return {\n lines: quiet([` No generated schema yet at ${input.outputPath} — nothing to compare.`,\n \" `rebase schema generate` creates it.\"]),\n exitCode: 0,\n regenerate: false\n };\n }\n\n if (input.unreadable !== undefined) {\n // Best-effort by design: a collections directory that will not load is a\n // real error, but boot reports it far better than this does. Saying so\n // is still better than exiting 0 in silence about a check that did not\n // run — this file's whole subject.\n return {\n lines: quiet([` ⏭ Not checked: ${input.unreadable}`]),\n exitCode: 0,\n regenerate: false\n };\n }\n\n // Both checks, and both reported. They answer different questions and a file\n // can fail either: `stale` names a specific historical defect with a\n // specific remedy, `differences` says the file is simply not what the\n // collections now generate. Reporting only the first is what let this\n // command pass a file that had visibly moved.\n const problems: string[] = [];\n if (input.stale.length > 0) {\n problems.push(\n ` ⚠️ ${input.outputPath} names ${input.stale.length} foreign key(s) the way an earlier release did:`,\n describeLegacyForeignKeyNames(input.stale)\n );\n }\n const differences = input.differences ?? [];\n if (differences.length > 0) {\n problems.push(\n ` ⚠️ ${input.outputPath} is not what the current collections generate.`,\n ` Differs in: ${describeDeclarationDifferences(differences)}.`\n );\n }\n\n if (problems.length === 0) {\n return {\n lines: quiet([` ✓ Nothing stale — 1 generated file matches (${input.outputPath}).`]),\n exitCode: 0,\n regenerate: false\n };\n }\n\n const found = [\"\", ...problems, \"\"];\n\n if (input.fix) {\n return { lines: [...found, \" Regenerating the Drizzle schema so it matches...\"], exitCode: 0, regenerate: true };\n }\n\n return {\n lines: [\n ...found,\n // Not \"the server will refuse to start\" any more. The runtime builds\n // its tables from `information_schema`, so a stale file changes\n // nothing it serves — it warns once at boot and carries on. What the\n // file still decides is everything around the server: Atlas plans\n // migrations from it, `db push` diffs it, `eject` writes it out, and\n // user code imports it.\n //\n // The first sentence is the renamed-column one and belongs only to\n // the legacy foreign-key finding. Printed unconditionally it told a\n // reader whose enum label had simply moved that a column had been\n // renamed at boot, which sends them looking for a rename that never\n // happened. The consequence below is the same either way.\n ...(input.stale.length > 0\n ? [\" The database column has already been renamed at boot.\"]\n : []),\n \" The server reads the database, so it will still start — but `db push`, Atlas, `eject` \"\n + \"and any code importing this file are all reading a description that no longer matches.\",\n \" Run `rebase schema generate` to regenerate it.\"\n ],\n exitCode: 1,\n regenerate: false\n };\n}\n\n\n// ── Is this file what the current collections generate? ──────────────────────\n\n/** One exported declaration that differs between two generated schemas. */\nexport interface DeclarationDifference {\n /** The exported symbol: a table, an enum, a relations block. */\n name: string;\n kind: \"missing\" | \"unexpected\" | \"changed\";\n /**\n * For a `changed` table or enum, what moved inside it, in the reader's\n * words: `column subtitle is in the file, no longer generated`, `label\n * \"archived\" is not in the file`. Absent for anything else.\n */\n details?: string[];\n}\n\n/**\n * `export const NAME = …` → the declaration, with formatting and the\n * generator's own comments removed.\n *\n * Every generated declaration starts at column 0 and runs to the next one, so\n * splitting on them needs no brace counting and cannot get out of step with a\n * policy clause that happens to contain a brace.\n */\nfunction declarations(source: string): Map<string, string> {\n const out = new Map<string, string>();\n const starts = [...source.matchAll(/^export const (\\w+) =/gm)];\n starts.forEach((match, index) => {\n const from = match.index! + match[0].length;\n const to = index + 1 < starts.length ? starts[index + 1].index! : source.length;\n out.set(\n match[1],\n source.slice(from, to)\n // The `// INCLUDE (...)` note on a covering index, and any other\n // remark the renderer leaves: they describe the declaration,\n // they are not part of it.\n .replace(/\\/\\/[^\\n]*/g, \"\")\n .replace(/\\s+/g, \" \")\n .trim()\n );\n });\n return out;\n}\n\n/**\n * What differs between the schema the current collections generate and the one\n * on disk — by declaration, not by byte.\n *\n * `rebase doctor` used to whitespace-normalise both files and compare the\n * strings, which answers \"are these the same text\" when the question is \"does\n * this file still describe the same schema\". The difference is not academic:\n * the answer was a single yes/no with nothing to act on, so a developer told\n * their schema was stale had to regenerate and read the diff to find out what\n * had changed.\n *\n * Comparing declarations answers the question the reader actually has. The\n * generated file is a flat list of `export const`s — one per table, enum and\n * relations block, each the rendering of a piece of the schema plan — and two\n * files declaring the same symbols with the same bodies describe the same\n * database whatever their formatting.\n *\n * Not a plan-to-plan comparison, and deliberately not: reading a plan back out\n * of `schema.generated.ts` means interpreting drizzle-orm builder calls, which\n * is a second interpreter — the exact thing this area was just rid of. The\n * rendered declarations are the closest honest proxy, and the renderer is\n * deterministic, so the only things normalised away are what it is free to vary\n * independently of the plan: whitespace and its own comments.\n */\nexport function compareGeneratedDeclarations(expected: string, actual: string): DeclarationDifference[] {\n const want = declarations(expected);\n const have = declarations(actual);\n const differences: DeclarationDifference[] = [];\n for (const [name, body] of want) {\n const onDisk = have.get(name);\n if (onDisk === undefined) differences.push({ name, kind: \"missing\" });\n else if (onDisk !== body) {\n const details = changesWithin(body, onDisk);\n differences.push(details.length > 0 ? { name, kind: \"changed\", details } : { name, kind: \"changed\" });\n }\n }\n for (const name of have.keys()) {\n if (!want.has(name)) differences.push({ name, kind: \"unexpected\" });\n }\n return differences;\n}\n\n/**\n * The entries between the bracket at `from` and the one that closes it — a\n * column object's columns, an enum's labels — split at their top-level commas.\n * Strings, template literals and nested brackets are stepped over, so\n * `.references((): AnyPgColumn => authors.id, { onDelete: \"set null\" })` stays\n * one entry. Null when the bracket is never closed.\n */\nfunction topLevelEntries(text: string, from: number): string[] | null {\n const entries: string[] = [];\n let depth = 0;\n let start = from + 1;\n let quote: string | null = null;\n for (let index = from; index < text.length; index++) {\n const char = text[index];\n if (quote) {\n if (char === \"\\\\\") index++;\n else if (char === quote) quote = null;\n continue;\n }\n if (char === \"\\\"\" || char === \"'\" || char === \"`\") quote = char;\n else if (char === \"(\" || char === \"[\" || char === \"{\") depth++;\n else if (char === \")\" || char === \"]\" || char === \"}\") {\n depth--;\n if (depth === 0) {\n const last = text.slice(start, index).trim();\n if (last) entries.push(last);\n return entries;\n }\n } else if (char === \",\" && depth === 1) {\n entries.push(text.slice(start, index).trim());\n start = index + 1;\n }\n }\n return null;\n}\n\n/** A table declaration's columns, by database name, each with its declaration. */\nfunction tableColumns(body: string): Map<string, string> | null {\n const table = /^(?:pgTable|[\\w$]+\\.table)\\(\\s*[\"'][^\"']+[\"']\\s*,\\s*/.exec(body);\n if (!table || body[table[0].length] !== \"{\") return null;\n const entries = topLevelEntries(body, table[0].length);\n if (!entries) return null;\n const columns = new Map<string, string>();\n for (const entry of entries) {\n // `authorId: integer(\"author_id\")…`: the string argument is the column.\n const column = /^[^:]+:\\s*[\\w$.]+\\(\\s*[\"']([^\"']+)[\"']/.exec(entry);\n if (column) columns.set(column[1], entry.slice(entry.indexOf(\":\") + 1).trim());\n }\n return columns;\n}\n\n/** An enum declaration's labels, in order. */\nfunction enumLabels(body: string): string[] | null {\n const declaration = /^(?:pgEnum|[\\w$]+\\.enum)\\(\\s*[\"'][^\"']+[\"']\\s*,\\s*/.exec(body);\n if (!declaration || body[declaration[0].length] !== \"[\") return null;\n const entries = topLevelEntries(body, declaration[0].length);\n return entries ? entries.map(entry => entry.replace(/^[\"']|[\"']$/g, \"\")) : null;\n}\n\n/**\n * What moved inside one table or enum, for the message only. Whether a file is\n * stale is still the declaration comparison above; this reads the rendered\n * text just far enough to name the column or label, and names nothing for a\n * declaration of any other shape.\n */\nfunction changesWithin(expected: string, actual: string): string[] {\n const wantedLabels = enumLabels(expected);\n const labelsOnDisk = enumLabels(actual);\n if (wantedLabels && labelsOnDisk) {\n const changes = [\n ...wantedLabels.filter(label => !labelsOnDisk.includes(label)).map(label => `label \"${label}\" is not in the file`),\n ...labelsOnDisk.filter(label => !wantedLabels.includes(label)).map(label => `label \"${label}\" is in the file, no longer generated`)\n ];\n return changes.length > 0 ? changes : [\"its labels are in another order\"];\n }\n\n const wantedColumns = tableColumns(expected);\n const columnsOnDisk = tableColumns(actual);\n if (!wantedColumns || !columnsOnDisk) return [];\n const changes: string[] = [];\n for (const [column, declaration] of wantedColumns) {\n const onDisk = columnsOnDisk.get(column);\n if (onDisk === undefined) changes.push(`column ${column} is not in the file`);\n else if (onDisk !== declaration) changes.push(`column ${column} differs`);\n }\n for (const column of columnsOnDisk.keys()) {\n if (!wantedColumns.has(column)) changes.push(`column ${column} is in the file, no longer generated`);\n }\n return changes.length > 0 ? changes : [\"its indexes, constraints or policies differ\"];\n}\n\n/** The differences, as the line a developer reads in `rebase doctor` and `rebase schema stale`. */\nexport function describeDeclarationDifferences(differences: DeclarationDifference[]): string {\n const shown = differences.slice(0, 5).map(difference => {\n if (difference.kind === \"missing\") return `${difference.name} (not in the file)`;\n if (difference.kind === \"unexpected\") return `${difference.name} (in the file, no longer generated)`;\n return `${difference.name} (${difference.details?.join(\"; \") ?? \"differs\"})`;\n });\n const rest = differences.length - shown.length;\n return shown.join(\", \") + (rest > 0 ? `, and ${rest} more` : \"\");\n}\n"],"mappings":"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"}
package/dist/index.d.ts CHANGED
@@ -15,6 +15,7 @@ export * from "./backup/index.js";
15
15
  export * from "./PostgresBootstrapper.js";
16
16
  export * from "./PostgresAdapter.js";
17
17
  export * from "./open-rebase.js";
18
+ export { assertDbCommandLine } from "./db-command-line.js";
18
19
  /**
19
20
  * The app-role provisioning boot performs, for callers that restore a database
20
21
  * outside a boot.
package/dist/index.es.js CHANGED
@@ -1,14 +1,15 @@
1
1
  import { createRequire as __createRequire } from "module";
2
2
  __createRequire(import.meta.url);
3
- import { a as createReadReplicaConnection, c as pinSearchPath, i as createPostgresDatabaseConnection, l as poolMaxCeiling, o as defaultPoolTimeouts, r as createDirectDatabaseConnection, s as guardPoolAgainstDirtyRelease, t as cappedPoolMax } from "./connection-C938nR3E.js";
4
- import { $ as AuthenticatedPostgresBackendDriver, B as mfaFactors, C as PG_NOTIFY_MAX_PAYLOAD_BYTES, D as MemoryChannelBus, E as parseChannelBusPayload, F as createAuthSchema, G as recoveryCodesRelations, H as passwordResetTokens, I as magicLinkTokens, J as userIdentities, K as refreshTokens, L as magicLinkTokensRelations, O as frameByteLength, P as appConfig, Q as usersSchema, R as mfaChallenges, S as DEFAULT_BATCH_WINDOW_MS, T as parseChannelBusFrame, U as passwordResetTokensRelations, V as mfaFactorsRelations, W as recoveryCodes, X as users, Y as userIdentitiesRelations, Z as usersRelations, _ as parseRealtimeTuning, _t as listedColumnKeys, a as openPostgresDriver, at as isRoleSwitchingOptedOut, b as resolveChannelBusSetting, c as resolveSourceUrls, d as DEFAULT_RECOUNT_MIN_ROWS, dt as DrizzleConditionBuilder, et as CONNECTION_OWNER, f as PostgresRealtimeProvider, ft as PostgresConditionBuilder, g as parseMaxSubscriptionsPerSocket, gt as joinColumn, h as parseMaxSocketsPerUser, ht as getUnknownFilterFieldsMode, i as isScaffoldedLocalDatabase, it as effectiveSqlRole, k as isChannelBusInstance, l as PostgresCollectionRegistry, m as packEntityChanges, mt as escapeLikePattern, n as createPostgresBootstrapper, nt as RoleSwitchUnavailableError, o as postgresDriverConfigFromEnv, ot as sqlRoleRanAs, p as RealtimeService, pt as configureUnknownFilterFields, q as refreshTokensRelations, r as describeSchemaDriftCause, rt as branchParentDatabase, s as resolveDriftCheckName, t as openRebase, tt as PostgresBackendDriver, u as DEFAULT_MAX_SUBSCRIPTIONS_PER_SOCKET, v as refetchConcurrencyFor, w as PostgresChannelBus, x as CHANNEL_BUS_NOTIFY_CHANNEL, y as createChannelBus, z as mfaChallengesRelations } from "./open-rebase-D8rytTP0.js";
5
- import { r as BranchingUnsupportedError, t as BranchService } from "./BranchService-DNg4txyf.js";
6
- import { i as ensureAppRole, r as detectConnectionPosture, t as REBASE_USER_ROLE } from "./rls-enforcement-D7P4-ZYX.js";
7
- import { t as generateSchema } from "./generate-drizzle-schema-logic-BaT0H7Gq.js";
8
- import { t as DatabasePoolManager } from "./databasePoolManager-Yqdw14wY.js";
9
- import { t as main } from "./generate-drizzle-schema-Bf8canaR.js";
10
- import { a as createPostgresWebSocket, i as TOO_MANY_SOCKETS_CLOSE_CODE, n as PUBLIC_TYPES, o as isSocketRealtimeService, r as SESSION_ENDED_CLOSE_CODE, s as socketRealtimeMissing, t as ADMIN_PLANE_TYPES } from "./websocket-DMeXOoyv.js";
11
- import { A as parseBackupDestination, C as buildPgRestoreArgs, D as diagnoseRowSecurityDumpFailure, E as checkToolServerCompatibility, F as resolveConnectionString, I as serverVersionNumToMajor, L as splitGlobalsStatements, M as parseDbNameFromUrl, N as parseDumpObjects, O as globalsFileForDump, P as parsePgToolMajor, R as withDatabaseName, S as buildPgDumpallGlobalsArgs, T as buildRowSecurityPgOptions, _ as pgToolVariable, a as detectToolMajor, b as buildBackupFilename, c as findRestoreConflicts, d as preflight, f as pruneBackups, g as validateDump, h as uploadBackup, i as createDump, j as parseBackupTimestamp, k as joinStorageKey, l as getServerVersionMajor, m as restoreDump, n as applyGlobals, o as discardPartialDump, p as readDumpObjects, s as ensureDatabaseExists, t as BackupToolError, u as listBackups, v as resolvePgBinary, w as buildPgRestoreListArgs, x as buildPgDumpArgs, y as selectBackupsToPrune } from "./backup-service-D-UpT7DH.js";
3
+ import { a as createReadReplicaConnection, c as pinSearchPath, i as createPostgresDatabaseConnection, l as poolMaxCeiling, o as defaultPoolTimeouts, r as createDirectDatabaseConnection, s as guardPoolAgainstDirtyRelease, t as cappedPoolMax } from "./connection-czvI7TZL.js";
4
+ import { $ as AuthenticatedPostgresBackendDriver, B as mfaFactors, C as PG_NOTIFY_MAX_PAYLOAD_BYTES, D as MemoryChannelBus, E as parseChannelBusPayload, F as createAuthSchema, G as recoveryCodesRelations, H as passwordResetTokens, I as magicLinkTokens, J as userIdentities, K as refreshTokens, L as magicLinkTokensRelations, O as frameByteLength, P as appConfig, Q as usersSchema, R as mfaChallenges, S as DEFAULT_BATCH_WINDOW_MS, T as parseChannelBusFrame, U as passwordResetTokensRelations, V as mfaFactorsRelations, W as recoveryCodes, X as users, Y as userIdentitiesRelations, Z as usersRelations, _ as parseRealtimeTuning, _t as listedColumnKeys, a as openPostgresDriver, at as isRoleSwitchingOptedOut, b as resolveChannelBusSetting, c as resolveSourceUrls, d as DEFAULT_RECOUNT_MIN_ROWS, dt as DrizzleConditionBuilder, et as CONNECTION_OWNER, f as PostgresRealtimeProvider, ft as PostgresConditionBuilder, g as parseMaxSubscriptionsPerSocket, gt as joinColumn, h as parseMaxSocketsPerUser, ht as getUnknownFilterFieldsMode, i as isScaffoldedLocalDatabase, it as effectiveSqlRole, k as isChannelBusInstance, l as PostgresCollectionRegistry, m as packEntityChanges, mt as escapeLikePattern, n as createPostgresBootstrapper, nt as RoleSwitchUnavailableError, o as postgresDriverConfigFromEnv, ot as sqlRoleRanAs, p as RealtimeService, pt as configureUnknownFilterFields, q as refreshTokensRelations, r as describeSchemaDriftCause, rt as branchParentDatabase, s as resolveDriftCheckName, t as openRebase, tt as PostgresBackendDriver, u as DEFAULT_MAX_SUBSCRIPTIONS_PER_SOCKET, v as refetchConcurrencyFor, w as PostgresChannelBus, x as CHANNEL_BUS_NOTIFY_CHANNEL, y as createChannelBus, z as mfaChallengesRelations } from "./open-rebase-EmqVmS14.js";
5
+ import { a as reopenedDatabasesNotice, i as reopenClosedDatabases, r as BranchingUnsupportedError, t as BranchService } from "./BranchService-ClklxyAQ.js";
6
+ import { i as ensureAppRole, r as detectConnectionPosture, t as REBASE_USER_ROLE } from "./rls-enforcement-DDyNoOSy.js";
7
+ import { t as generateSchema } from "./generate-drizzle-schema-logic-CPzCRD-8.js";
8
+ import { t as DatabasePoolManager } from "./databasePoolManager-3stMNFL-.js";
9
+ import { t as main } from "./generate-drizzle-schema-DC0BOUpn.js";
10
+ import { a as createPostgresWebSocket, i as TOO_MANY_SOCKETS_CLOSE_CODE, n as PUBLIC_TYPES, o as isSocketRealtimeService, r as SESSION_ENDED_CLOSE_CODE, s as socketRealtimeMissing, t as ADMIN_PLANE_TYPES } from "./websocket-BKZwsjmf.js";
11
+ import { A as buildBackupFilename, B as diagnoseRowSecurityDumpFailure, C as restoreDumpReplacing, D as resolvePgBinary, E as pgToolVariable, F as buildPgRestoreScriptArgs, G as parseDbNameFromUrl, H as joinStorageKey, I as buildPsqlScriptArgs, J as resolveConnectionString, K as parseDumpObjects, L as buildRowSecurityPgOptions, M as buildPgDumpallGlobalsArgs, N as buildPgRestoreArgs, O as selectBackupsToPrune, P as buildPgRestoreListArgs, R as checkToolServerCompatibility, S as restoreDump, T as validateDump, U as parseBackupDestination, V as globalsFileForDump, W as parseBackupTimestamp, X as splitGlobalsStatements, Y as serverVersionNumToMajor, Z as withDatabaseName, _ as listBackups, a as BackupToolError, b as readDumpObjects, d as detectToolMajor, f as discardPartialDump, g as getServerVersionMajor, h as findRestoreConflicts, i as parseRestoreLine, j as buildPgDumpArgs, k as CLEAN_LOCK_TIMEOUT, l as createDump, m as findCleanExtras, n as parseBackupLine, o as CleanRestoreFailed, p as ensureDatabaseExists, q as parsePgToolMajor, r as parseBackupsListLine, s as applyGlobals, u as describeUnreadableDump, v as preflight, w as uploadBackup, x as resetSharedSession, y as pruneBackups, z as cleanRestorePrelude } from "./backup-cli-gijeXe0u.js";
12
+ import { c as assertKnownFlags, i as assertKnownBranchFlags, n as isDriverDbSubcommand, r as backupActionOf } from "./db-subcommands-BPoHCxJR.js";
12
13
  import { logger } from "@rebasepro/server";
13
14
  import fs from "fs";
14
15
  //#region src/backup/backup-cron.ts
@@ -95,7 +96,7 @@ function createBackupCron(config) {
95
96
  enabled: config.enabled ?? true,
96
97
  timeoutSeconds: 3600,
97
98
  async handler({ log }) {
98
- const { createDump, discardPartialDump, pruneBackups, uploadBackup, validateDump } = await import("./backup-service-D-UpT7DH.js").then((n) => n.r);
99
+ const { createDump, discardPartialDump, pruneBackups, uploadBackup, validateDump } = await import("./backup-cli-gijeXe0u.js").then((n) => n.c);
99
100
  const { destination } = config;
100
101
  if (destination.kind !== "local" && !config.storage) throw new Error(`Backup destination is ${destination.kind} but no storage controller was provided. Pass the backend's configured StorageController to createBackupCron({ storage }).`);
101
102
  log(`Starting backup of "${dbName}"…`);
@@ -199,6 +200,64 @@ function createPostgresAdapter(pgConfig) {
199
200
  };
200
201
  }
201
202
  //#endregion
202
- export { ADMIN_PLANE_TYPES, AuthenticatedPostgresBackendDriver, BackupToolError, BranchService, BranchingUnsupportedError, CHANNEL_BUS_NOTIFY_CHANNEL, CONNECTION_OWNER, DEFAULT_BATCH_WINDOW_MS, DEFAULT_MAX_SUBSCRIPTIONS_PER_SOCKET, DEFAULT_RECOUNT_MIN_ROWS, DatabasePoolManager, DrizzleConditionBuilder, MemoryChannelBus, PG_NOTIFY_MAX_PAYLOAD_BYTES, PUBLIC_TYPES, PostgresBackendDriver, PostgresChannelBus, PostgresCollectionRegistry, PostgresConditionBuilder, PostgresRealtimeProvider, REBASE_USER_ROLE, RealtimeService, RoleSwitchUnavailableError, SESSION_ENDED_CLOSE_CODE, TOO_MANY_SOCKETS_CLOSE_CODE, appConfig, applyGlobals, backupCronConfigFromEnv, branchParentDatabase, buildBackupFilename, buildPgDumpArgs, buildPgDumpallGlobalsArgs, buildPgRestoreArgs, buildPgRestoreListArgs, buildRowSecurityPgOptions, cappedPoolMax, checkToolServerCompatibility, configureUnknownFilterFields, createAuthSchema, createBackupCron, createChannelBus, createDirectDatabaseConnection, createDump, createPostgresAdapter, createPostgresBootstrapper, createPostgresDatabaseConnection, createPostgresWebSocket, createReadReplicaConnection, defaultPoolTimeouts, describeSchemaDriftCause, detectConnectionPosture, detectToolMajor, diagnoseRowSecurityDumpFailure, discardPartialDump, effectiveSqlRole, ensureAppRole, ensureDatabaseExists, escapeLikePattern, findRestoreConflicts, frameByteLength, generateSchema, getServerVersionMajor, getUnknownFilterFieldsMode, globalsFileForDump, guardPoolAgainstDirtyRelease, isChannelBusInstance, isRoleSwitchingOptedOut, isScaffoldedLocalDatabase, isSocketRealtimeService, joinColumn, joinStorageKey, listBackups, listedColumnKeys, magicLinkTokens, magicLinkTokensRelations, main, mfaChallenges, mfaChallengesRelations, mfaFactors, mfaFactorsRelations, openPostgresDriver, openRebase, packEntityChanges, parseBackupDestination, parseBackupTimestamp, parseChannelBusFrame, parseChannelBusPayload, parseDbNameFromUrl, parseDumpObjects, parseMaxSocketsPerUser, parseMaxSubscriptionsPerSocket, parsePgToolMajor, parseRealtimeTuning, passwordResetTokens, passwordResetTokensRelations, pinSearchPath, poolMaxCeiling, postgresDriverConfigFromEnv, preflight, pruneBackups, readDumpObjects, recoveryCodes, recoveryCodesRelations, refetchConcurrencyFor, refreshTokens, refreshTokensRelations, resolveChannelBusSetting, resolveConnectionString, resolveDriftCheckName, resolvePgBinary, resolveSourceUrls, restoreDump, selectBackupsToPrune, serverVersionNumToMajor, socketRealtimeMissing, splitGlobalsStatements, sqlRoleRanAs, uploadBackup, userIdentities, userIdentitiesRelations, users, usersRelations, usersSchema, validateDump, withDatabaseName };
203
+ //#region src/db-command-line.ts
204
+ /**
205
+ * Every `rebase db …` line this driver answers, judged without connecting to
206
+ * anything.
207
+ *
208
+ * The driver refuses a flag a command does not take, but it is spawned only
209
+ * once the CLI has a database for it, and getting one means starting one: the
210
+ * remembered compose container, or the managed PGlite daemon. So `rebase db
211
+ * push --dry-rn` booted the database and then said the flag did not exist.
212
+ * The CLI runs this first, from the driver's own code, so the answer it gives
213
+ * before starting anything and the one the driver would give after are one
214
+ * check, not two lists to keep in step.
215
+ *
216
+ * Each entry calls the parse its command runs itself, never a copy of it.
217
+ */
218
+ /**
219
+ * The check each subcommand's line gets. A `Record` over the union, so a
220
+ * subcommand added to `DRIVER_DB_SUBCOMMANDS` does not compile until it says
221
+ * how its line is judged.
222
+ */
223
+ var LINE_CHECKS = {
224
+ push: (args) => assertKnownFlags("db", "push", args),
225
+ generate: (args) => assertKnownFlags("db", "generate", args),
226
+ migrate: (args) => assertKnownFlags("db", "migrate", args),
227
+ branch: (args) => {
228
+ if (args[2] !== void 0) assertKnownBranchFlags(args[2], args.slice(3));
229
+ },
230
+ backup: (args) => {
231
+ if (backupActionOf(args) === "list") parseBackupsListLine(args);
232
+ else parseBackupLine(args);
233
+ },
234
+ restore: (args) => {
235
+ parseRestoreLine(args);
236
+ },
237
+ backups: (args) => {
238
+ if (args[2] === "list") parseBackupsListLine(args);
239
+ }
240
+ };
241
+ /**
242
+ * Throw what the driver would say of a `rebase db …` line it refuses on its
243
+ * flags, before it would connect: an option the command does not take, and
244
+ * the checks `db backup` and `db restore` make of their own line (a stray
245
+ * word, a pattern that leaves out the rebase schema).
246
+ *
247
+ * Reads no environment and opens nothing, so it is safe to run before a
248
+ * database exists.
249
+ *
250
+ * @param args the driver's line: `["db", "<subcommand>", …]`, without the
251
+ * CLI's own words in front. A line for a subcommand this driver does not
252
+ * answer passes, for the dispatch to refuse.
253
+ */
254
+ function assertDbCommandLine(args) {
255
+ const [domain, subcommand] = args;
256
+ if (domain !== "db" || !isDriverDbSubcommand(subcommand)) return;
257
+ if (args.includes("--help") || args.includes("-h")) return;
258
+ LINE_CHECKS[subcommand]([...args]);
259
+ }
260
+ //#endregion
261
+ export { ADMIN_PLANE_TYPES, AuthenticatedPostgresBackendDriver, BackupToolError, BranchService, BranchingUnsupportedError, CHANNEL_BUS_NOTIFY_CHANNEL, CLEAN_LOCK_TIMEOUT, CONNECTION_OWNER, CleanRestoreFailed, DEFAULT_BATCH_WINDOW_MS, DEFAULT_MAX_SUBSCRIPTIONS_PER_SOCKET, DEFAULT_RECOUNT_MIN_ROWS, DatabasePoolManager, DrizzleConditionBuilder, MemoryChannelBus, PG_NOTIFY_MAX_PAYLOAD_BYTES, PUBLIC_TYPES, PostgresBackendDriver, PostgresChannelBus, PostgresCollectionRegistry, PostgresConditionBuilder, PostgresRealtimeProvider, REBASE_USER_ROLE, RealtimeService, RoleSwitchUnavailableError, SESSION_ENDED_CLOSE_CODE, TOO_MANY_SOCKETS_CLOSE_CODE, appConfig, applyGlobals, assertDbCommandLine, backupCronConfigFromEnv, branchParentDatabase, buildBackupFilename, buildPgDumpArgs, buildPgDumpallGlobalsArgs, buildPgRestoreArgs, buildPgRestoreListArgs, buildPgRestoreScriptArgs, buildPsqlScriptArgs, buildRowSecurityPgOptions, cappedPoolMax, checkToolServerCompatibility, cleanRestorePrelude, configureUnknownFilterFields, createAuthSchema, createBackupCron, createChannelBus, createDirectDatabaseConnection, createDump, createPostgresAdapter, createPostgresBootstrapper, createPostgresDatabaseConnection, createPostgresWebSocket, createReadReplicaConnection, defaultPoolTimeouts, describeSchemaDriftCause, describeUnreadableDump, detectConnectionPosture, detectToolMajor, diagnoseRowSecurityDumpFailure, discardPartialDump, effectiveSqlRole, ensureAppRole, ensureDatabaseExists, escapeLikePattern, findCleanExtras, findRestoreConflicts, frameByteLength, generateSchema, getServerVersionMajor, getUnknownFilterFieldsMode, globalsFileForDump, guardPoolAgainstDirtyRelease, isChannelBusInstance, isRoleSwitchingOptedOut, isScaffoldedLocalDatabase, isSocketRealtimeService, joinColumn, joinStorageKey, listBackups, listedColumnKeys, magicLinkTokens, magicLinkTokensRelations, main, mfaChallenges, mfaChallengesRelations, mfaFactors, mfaFactorsRelations, openPostgresDriver, openRebase, packEntityChanges, parseBackupDestination, parseBackupTimestamp, parseChannelBusFrame, parseChannelBusPayload, parseDbNameFromUrl, parseDumpObjects, parseMaxSocketsPerUser, parseMaxSubscriptionsPerSocket, parsePgToolMajor, parseRealtimeTuning, passwordResetTokens, passwordResetTokensRelations, pinSearchPath, poolMaxCeiling, postgresDriverConfigFromEnv, preflight, pruneBackups, readDumpObjects, recoveryCodes, recoveryCodesRelations, refetchConcurrencyFor, refreshTokens, refreshTokensRelations, reopenClosedDatabases, reopenedDatabasesNotice, resetSharedSession, resolveChannelBusSetting, resolveConnectionString, resolveDriftCheckName, resolvePgBinary, resolveSourceUrls, restoreDump, restoreDumpReplacing, selectBackupsToPrune, serverVersionNumToMajor, socketRealtimeMissing, splitGlobalsStatements, sqlRoleRanAs, uploadBackup, userIdentities, userIdentitiesRelations, users, usersRelations, usersSchema, validateDump, withDatabaseName };
203
262
 
204
263
  //# sourceMappingURL=index.es.js.map