@rebasepro/server-postgres 0.23.0 → 0.24.0

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 (108) hide show
  1. package/dist/{BranchService-DGPL6G_C.js → BranchService-BRt78gfa.js} +35 -18
  2. package/dist/BranchService-BRt78gfa.js.map +1 -0
  3. package/dist/PostgresBackendDriver.d.ts +84 -12
  4. package/dist/auth/services.d.ts +72 -14
  5. package/dist/{auth-users-columns-D2LBFrMH.js → auth-users-columns-C72EMoDJ.js} +17 -1
  6. package/dist/{auth-users-columns-D2LBFrMH.js.map → auth-users-columns-C72EMoDJ.js.map} +1 -1
  7. package/dist/{backup-cli-24v4OlSp.js → backup-cli-DW5p9_zv.js} +2 -2
  8. package/dist/{backup-cli-24v4OlSp.js.map → backup-cli-DW5p9_zv.js.map} +1 -1
  9. package/dist/{backup-service-HQ9GC4tN.js → backup-service-EwcDVG-8.js} +7 -9
  10. package/dist/{backup-service-HQ9GC4tN.js.map → backup-service-EwcDVG-8.js.map} +1 -1
  11. package/dist/{cli-errors-B8qHg02P.js → cli-errors-DsA-K9uP.js} +96 -1
  12. package/dist/{cli-errors-B8qHg02P.js.map → cli-errors-DsA-K9uP.js.map} +1 -1
  13. package/dist/cli-errors.d.ts +42 -0
  14. package/dist/cli-helpers.d.ts +12 -12
  15. package/dist/cli.js +91 -37
  16. package/dist/cli.js.map +1 -1
  17. package/dist/{column-plan-helpers-DF-8dTVa.js → column-plan-helpers-1-LQD0yI.js} +34 -24
  18. package/dist/column-plan-helpers-1-LQD0yI.js.map +1 -0
  19. package/dist/data-transformer.d.ts +0 -8
  20. package/dist/{doctor-Cb2thZ8s.js → doctor-C-sYWbmt.js} +224 -43
  21. package/dist/doctor-C-sYWbmt.js.map +1 -0
  22. package/dist/{ensure-collection-policies-RHUcEp4v.js → ensure-collection-policies-B1ureSIV.js} +73 -18
  23. package/dist/ensure-collection-policies-B1ureSIV.js.map +1 -0
  24. package/dist/{ensure-collection-tables-BEEjn5cn.js → ensure-collection-tables-kkkHk8oo.js} +76 -20
  25. package/dist/ensure-collection-tables-kkkHk8oo.js.map +1 -0
  26. package/dist/{ensure-tables-Dhn9KM3B.js → ensure-tables-BmI_tRxc.js} +10 -3
  27. package/dist/ensure-tables-BmI_tRxc.js.map +1 -0
  28. package/dist/{generate-drizzle-schema-yzY_BLhr.js → generate-drizzle-schema-93M0lUxK.js} +2 -2
  29. package/dist/{generate-drizzle-schema-yzY_BLhr.js.map → generate-drizzle-schema-93M0lUxK.js.map} +1 -1
  30. package/dist/{generate-drizzle-schema-logic-BfzK7UQd.js → generate-drizzle-schema-logic-sSFDp6LR.js} +26 -8
  31. package/dist/generate-drizzle-schema-logic-sSFDp6LR.js.map +1 -0
  32. package/dist/generate-postgres-ddl-logic-BJsLaVNX.js +152 -0
  33. package/dist/generate-postgres-ddl-logic-BJsLaVNX.js.map +1 -0
  34. package/dist/generated-sql.d.ts +28 -0
  35. package/dist/index.es.js +3415 -917
  36. package/dist/index.es.js.map +1 -1
  37. package/dist/{introspect-db-logic-WMuAfxvw.js → introspect-db-logic-kCETE8TY.js} +523 -349
  38. package/dist/introspect-db-logic-kCETE8TY.js.map +1 -0
  39. package/dist/introspect-db-queries-C_Q5VgQw.js +317 -0
  40. package/dist/introspect-db-queries-C_Q5VgQw.js.map +1 -0
  41. package/dist/{plan-schema-C0fxM8dY.js → plan-schema-DU9exq6C.js} +139 -504
  42. package/dist/plan-schema-DU9exq6C.js.map +1 -0
  43. package/dist/{policy-drift-DljYdrpW.js → policy-drift-B-J2hhm0.js} +3 -3
  44. package/dist/policy-drift-B-J2hhm0.js.map +1 -0
  45. package/dist/{generate-postgres-ddl-logic-Bt2d2mRH.js → render-ddl-Ds2t_d9V.js} +11 -149
  46. package/dist/render-ddl-Ds2t_d9V.js.map +1 -0
  47. package/dist/{rls-bootstrap-sql-B8EclDyM.js → rls-bootstrap-sql-_KNnjanK.js} +263 -39
  48. package/dist/rls-bootstrap-sql-_KNnjanK.js.map +1 -0
  49. package/dist/{rls-enforcement-C6Xk0lA6.js → rls-enforcement-CfXOJJaW.js} +10 -2
  50. package/dist/rls-enforcement-CfXOJJaW.js.map +1 -0
  51. package/dist/schema/auth-schema.d.ts +170 -0
  52. package/dist/schema/classify-change.d.ts +29 -1
  53. package/dist/schema/column-plan-helpers.d.ts +33 -21
  54. package/dist/schema/destructive-sql.d.ts +20 -1
  55. package/dist/schema/doctor-cli.js +4 -4
  56. package/dist/schema/doctor.d.ts +34 -1
  57. package/dist/schema/ensure-collection-policies.d.ts +22 -0
  58. package/dist/schema/generate-drizzle-schema.js +1 -1
  59. package/dist/schema/generate-postgres-ddl-logic.d.ts +5 -5
  60. package/dist/schema/generate-postgres-ddl.js +1 -1
  61. package/dist/schema/generate-schema-commit.d.ts +12 -0
  62. package/dist/schema/introspect-db-logic.d.ts +31 -0
  63. package/dist/schema/introspect-db-queries.d.ts +1 -1
  64. package/dist/schema/introspect-db-search.d.ts +22 -0
  65. package/dist/schema/introspect-db-storage.d.ts +83 -0
  66. package/dist/schema/introspect-db.js +15 -3
  67. package/dist/schema/introspect-db.js.map +1 -1
  68. package/dist/schema/plan/diff-plan.d.ts +1 -1
  69. package/dist/schema/plan/plan-schema.d.ts +16 -8
  70. package/dist/schema/plan/render-ddl.d.ts +6 -0
  71. package/dist/schema/plan/types.d.ts +37 -11
  72. package/dist/search-column-BM-GV6vH.js +442 -0
  73. package/dist/search-column-BM-GV6vH.js.map +1 -0
  74. package/dist/security/policy-drift.d.ts +1 -1
  75. package/dist/security/rls-enforcement.d.ts +7 -0
  76. package/dist/services/BranchService.d.ts +22 -1
  77. package/dist/services/FetchService.d.ts +5 -4
  78. package/dist/services/RelationService.d.ts +18 -0
  79. package/dist/services/cdc/CdcListener.d.ts +9 -4
  80. package/dist/services/cdc/identity-columns.d.ts +19 -0
  81. package/dist/services/cdc/trigger-cdc.d.ts +45 -7
  82. package/dist/services/channel-bus/PostgresChannelBus.d.ts +12 -7
  83. package/dist/services/channel-history.d.ts +17 -1
  84. package/dist/services/collection-helpers.d.ts +11 -1
  85. package/dist/services/pg-notify-listener.d.ts +88 -6
  86. package/dist/services/realtimeService.d.ts +159 -45
  87. package/dist/services/socket-liveness.d.ts +45 -0
  88. package/dist/services/soft-delete.d.ts +12 -0
  89. package/dist/services/sql-script.d.ts +71 -0
  90. package/dist/services/write-depth.d.ts +16 -0
  91. package/dist/services/write-transaction-scope.d.ts +6 -3
  92. package/dist/utils/drizzle-conditions.d.ts +42 -2
  93. package/dist/utils/sql-redaction.d.ts +21 -0
  94. package/dist/websocket.d.ts +50 -18
  95. package/package.json +7 -7
  96. package/dist/BranchService-DGPL6G_C.js.map +0 -1
  97. package/dist/column-plan-helpers-DF-8dTVa.js.map +0 -1
  98. package/dist/doctor-Cb2thZ8s.js.map +0 -1
  99. package/dist/ensure-collection-policies-RHUcEp4v.js.map +0 -1
  100. package/dist/ensure-collection-tables-BEEjn5cn.js.map +0 -1
  101. package/dist/ensure-tables-Dhn9KM3B.js.map +0 -1
  102. package/dist/generate-drizzle-schema-logic-BfzK7UQd.js.map +0 -1
  103. package/dist/generate-postgres-ddl-logic-Bt2d2mRH.js.map +0 -1
  104. package/dist/introspect-db-logic-WMuAfxvw.js.map +0 -1
  105. package/dist/plan-schema-C0fxM8dY.js.map +0 -1
  106. package/dist/policy-drift-DljYdrpW.js.map +0 -1
  107. package/dist/rls-bootstrap-sql-B8EclDyM.js.map +0 -1
  108. package/dist/rls-enforcement-C6Xk0lA6.js.map +0 -1
@@ -1 +0,0 @@
1
- {"version":3,"file":"generate-postgres-ddl-logic-Bt2d2mRH.js","names":[],"sources":["../src/schema/plan/render-ddl.ts","../src/schema/generate-postgres-ddl-logic.ts"],"sourcesContent":["/**\n * The SQL files, rendered from a {@link SchemaPlan}.\n *\n * Four files, because Atlas owns exactly one of them:\n *\n * - `schema.sql` — the desired state Atlas diffs the database against.\n * - `policies.sql` — the RLS policies. Atlas has no model for a policy.\n * - `search.sql` — the generated `tsvector` column, its GIN index and the\n * IMMUTABLE helpers its expression calls. Atlas's free tier refuses to\n * *parse* a file containing a function, and it wipes the dev database it\n * diffs against, so a helper seeded there is gone by the time the plan runs.\n * - `vector.sql` — the `vector` columns and their ANN indexes. Atlas cannot\n * *execute* these: it materialises `schema.sql` in a dev database that is\n * created empty and emptied again on every run, so `VECTOR(384)` resolves\n * against a database that structurally cannot have pgvector.\n * - `triggers.sql` — `autoValue: \"on_update\"`, which is a function plus a\n * binding, so it is out for the same reason search is.\n *\n * Everything not in `schema.sql` is excluded from Atlas's view by pattern\n * (`…ExcludePatterns` below) and applied by Rebase — at push time by the CLI,\n * at boot by the schema ensure. Without the excludes a desired state that omits\n * an object reads to Atlas as an instruction to drop it.\n */\nimport {\n fuzzyColumnDefinition,\n searchColumnDefinition,\n searchColumnStamps,\n searchExtensionStatements,\n searchHelperFunctions,\n searchIndexNames,\n searchIndexStatements,\n searchStampGuards\n} from \"../search-column\";\nimport {\n VECTOR_EXTENSION_OPT_IN,\n vectorColumnDefinition,\n vectorDimensionGuard,\n vectorExtensionDeclared,\n vectorExtensionStatement,\n vectorIndexNames,\n vectorIndexStatements\n} from \"../vector-index\";\nimport { collectionIndexStatements } from \"../collection-index\";\nimport { quoteSqlLiteral } from \"./plan-schema\";\nimport { triggerStatements } from \"./updated-at-trigger\";\nimport type { ColumnPlan, PgType, PolicyPlan, SchemaPlan, TablePlan } from \"./types\";\n\n/** What `schema.sql` may leave out, because another file owns it. */\nexport interface DdlRenderOptions {\n /** RLS policies. `false` puts them in `policies.sql` alone. */\n includePolicies?: boolean;\n /** The search column, its helpers and its index. See `search.sql`. */\n includeSearch?: boolean;\n /** The vector columns and their ANN indexes. See `vector.sql`. */\n includeVector?: boolean;\n}\n\n/**\n * A {@link PgType} as Postgres spells it.\n *\n * The one place. `getSqlColumnType` and `getDrizzleColumn` each carried their\n * own ladder and disagreed about `VARCHAR` widths, about `geopoint`, and about\n * whether an `increment` id honours `columnType`.\n */\nexport const renderPgType = (type: PgType): string => {\n switch (type.kind) {\n case \"text\": return \"TEXT\";\n case \"varchar\": return `VARCHAR(${type.length})`;\n case \"char\": return `CHAR(${type.length})`;\n case \"uuid\": return \"UUID\";\n case \"enum\": return `\"${type.schema}\".\"${type.name}\"`;\n case \"smallint\": return \"SMALLINT\";\n case \"integer\": return \"INTEGER\";\n case \"bigint\": return \"BIGINT\";\n case \"smallserial\": return \"SMALLSERIAL\";\n case \"serial\": return \"SERIAL\";\n case \"bigserial\": return \"BIGSERIAL\";\n case \"real\": return \"REAL\";\n case \"doublePrecision\": return \"DOUBLE PRECISION\";\n case \"numeric\":\n if (type.precision === undefined) return \"NUMERIC\";\n return type.scale === undefined\n ? `NUMERIC(${type.precision})`\n : `NUMERIC(${type.precision}, ${type.scale})`;\n case \"boolean\": return \"BOOLEAN\";\n case \"timestamptz\": return \"TIMESTAMP WITH TIME ZONE\";\n case \"date\": return \"DATE\";\n case \"time\": return \"TIME\";\n case \"json\": return \"JSON\";\n case \"jsonb\": return \"JSONB\";\n case \"vector\": return `VECTOR(${type.dimensions})`;\n case \"bytea\": return \"BYTEA\";\n case \"tsvector\": return \"tsvector\";\n case \"array\": return `${renderPgType(type.of)}[]`;\n }\n};\n\n/**\n * Everything after the column name, in one order for every SQL emitter.\n *\n * `CREATE TABLE` and `ADD COLUMN` used to assemble this separately, which is\n * how a required relation column came out `NOT NULL` after `db push` and\n * nullable after a boot-ensure. `identity` is part of the type rather than a\n * DEFAULT, which is why it is rendered here and not in the DEFAULT slot.\n */\nexport const renderColumnDefinition = (column: ColumnPlan): string => {\n // `auth-users-columns` is the single description of what an auth user\n // table's columns must be, and it hands over a rendered definition.\n if (column.sqlDefinition) return column.sqlDefinition;\n\n if (column.generated) {\n return `${renderPgType(column.type)} GENERATED ALWAYS AS (${column.generated.expression}) STORED`;\n }\n\n let definition = renderPgType(column.type);\n if (column.default?.kind === \"identity\") definition += \" GENERATED BY DEFAULT AS IDENTITY\";\n if (column.primaryKey) definition += \" PRIMARY KEY\";\n if (column.unique) definition += \" UNIQUE\";\n if (column.default && column.default.kind !== \"identity\") {\n definition += ` DEFAULT ${column.default.kind === \"sql\" ? column.default.expression : column.default.sql}`;\n }\n if (!column.nullable && !column.primaryKey) definition += \" NOT NULL\";\n return definition;\n};\n\n/** `\"col\" TYPE …`, as it appears inside a `CREATE TABLE`. */\nconst renderColumn = (column: ColumnPlan): string =>\n `\"${column.column}\" ${renderColumnDefinition(column)}`;\n\n// ── Policies ─────────────────────────────────────────────────────────────────\n\n/**\n * One policy as its `DROP` / `CREATE` pair — each a complete statement.\n *\n * This is the primitive the boot-time RLS applier runs one statement at a time\n * (the runtime's DB handle speaks the extended query protocol, which forbids\n * multiple commands in one execute), while `db push` writes the joined string.\n */\nexport const renderPolicyStatements = (\n table: Pick<TablePlan, \"schema\" | \"table\">,\n policy: PolicyPlan\n): string[] => {\n const target = `\"${table.schema}\".\"${table.table}\"`;\n let create = `CREATE POLICY \"${policy.name}\" ON ${target} AS ${policy.mode.toUpperCase()} ` +\n `FOR ${policy.operation.toUpperCase()} TO ${policy.roles.map(r => `\"${r}\"`).join(\", \")}`;\n if (policy.using) create += ` USING (${policy.using})`;\n if (policy.withCheck) create += ` WITH CHECK (${policy.withCheck})`;\n return [`DROP POLICY IF EXISTS \"${policy.name}\" ON ${target};`, `${create};`];\n};\n\n/** `ALTER TABLE … ENABLE ROW LEVEL SECURITY;` — every generated table is locked. */\nexport const renderEnableRls = (table: Pick<TablePlan, \"schema\" | \"table\">): string =>\n `ALTER TABLE \"${table.schema}\".\"${table.table}\" ENABLE ROW LEVEL SECURITY;`;\n\nconst statementsToDdl = (statements: string[]): string => statements.map(s => `${s}\\n`).join(\"\");\n\n// ── schema.sql ───────────────────────────────────────────────────────────────\n\nexport function renderPostgresDdl(plan: SchemaPlan, options: DdlRenderOptions = {}): string {\n const includePolicies = options.includePolicies !== false;\n const includeSearch = options.includeSearch !== false;\n const includeVector = options.includeVector !== false;\n\n let ddl = \"-- This file is auto-generated by the Rebase DDL generator. Do not edit manually.\\n\\n\";\n\n // 1. Custom schemas.\n //\n // `rebase` is unconditional and load-bearing. The RLS helper functions live\n // in it, so the migration preamble creates it — which puts it in Atlas's\n // replayed state, and anything in that state but absent from this desired\n // schema gets a `DROP SCHEMA … CASCADE` planned against it. That would take\n // the auth tables.\n plan.schemas.forEach(schema => { ddl += `CREATE SCHEMA IF NOT EXISTS \"${schema}\";\\n`; });\n if (plan.schemas.length > 0) ddl += \"\\n\";\n\n // 1b. Search support, for collections that opted in.\n //\n // Extensions and helper functions come before every CREATE TABLE because a\n // generated column's expression is resolved at creation time: a table whose\n // search column calls `rebase_search_text` cannot be created before that\n // function exists. Both are `IF NOT EXISTS` / `OR REPLACE`, so a file\n // replayed against a live database is a no-op here.\n const searchTables = plan.tables.filter(t => t.search);\n if (searchTables.length > 0) {\n if (!includeSearch) {\n ddl += \"-- Full-text search support lives in `search.sql`, applied separately.\\n\\n\";\n } else {\n const extensions = Array.from(new Set(searchTables.flatMap(t => searchExtensionStatements(t.search!))));\n const helpers = Array.from(new Set(searchTables.flatMap(t => searchHelperFunctions(t.search!))));\n ddl += \"-- Full-text search support (collections declaring a `search` block)\\n\";\n extensions.forEach(s => { ddl += `${s}\\n`; });\n if (extensions.length > 0) ddl += \"\\n\";\n helpers.forEach(s => { ddl += `${s}\\n\\n`; });\n }\n }\n\n // 1c. Vector support. Only ever a note, never a statement — unlike 1b.\n // `CREATE EXTENSION vector` is the one thing this file must never carry\n // whichever way the flag goes: Atlas rejects an extension in a desired\n // state as a paid feature.\n if (!includeVector && plan.tables.some(t => t.vectorColumns.length > 0)) {\n ddl += \"-- Vector columns and their ANN indexes live in `vector.sql`, applied separately.\\n\\n\";\n }\n\n // 1d. `autoValue: \"on_update\"`. A trigger is a function plus a binding, so\n // it is out of Atlas's sight for the reason search is — and out\n // unconditionally, because `schema.sql` has no flag that would make Atlas\n // able to read one.\n if (plan.tables.some(t => t.triggers.length > 0)) {\n ddl += \"-- `autoValue: \\\"on_update\\\"` triggers live in `triggers.sql`, applied separately.\\n\\n\";\n }\n\n // 2. Enum types.\n plan.enums.forEach(enumPlan => {\n ddl += `CREATE TYPE \"${enumPlan.schema}\".\"${enumPlan.name}\" AS ENUM (${enumPlan.labels.map(quoteSqlLiteral).join(\", \")});\\n`;\n });\n if (ddl.endsWith(\";\\n\")) ddl += \"\\n\";\n\n // 3. Tables, in the plan's order — each collection, then any junction its\n // relations imply.\n const fkStatements: string[] = [];\n // Indexes follow the tables for the same reason the FK constraints do: the\n // table has to exist first.\n const indexStatements: string[] = [];\n // Policies are emitted after every CREATE TABLE, like the FK constraints: a\n // policy may reference other tables (a junction's derived policies always\n // reference both endpoints; `policy.existsIn` references a join table), and\n // CREATE POLICY validates those relations at creation time.\n const policyStatements: string[] = [];\n\n for (const table of plan.tables) {\n const emitted = table.columns.filter(column => {\n // A vector column is not Atlas's — `vector.sql` adds it. Filtered\n // here rather than upstream so the column keeps its place in the\n // generator's order when it *is* included, which is what the\n // derived-names contract renders.\n if (!includeVector && column.type.kind === \"vector\") return false;\n if (!includeSearch && column.source.kind === \"search\") return false;\n // A declared property already emits this column (an explicit\n // `postId` beside the `belongsTo` that uses it); the relation\n // contributes only the constraint.\n if (column.columnOwnedByProperty) return false;\n return true;\n });\n\n ddl += `CREATE TABLE \"${table.schema}\".\"${table.table}\" (\\n`;\n const lines = emitted.map(column => ` ${renderColumn(column)}`);\n if (table.kind === \"junction\") {\n lines.push(` PRIMARY KEY (${table.primaryKey.map(c => `\"${c}\"`).join(\", \")})`);\n }\n ddl += lines.join(\",\\n\");\n ddl += \"\\n);\\n\\n\";\n\n for (const column of table.columns) {\n if (column.foreignKey) fkStatements.push(`${column.foreignKey.sql};`);\n }\n\n if (includeSearch && table.search) {\n indexStatements.push(...searchIndexStatements(table.search));\n }\n // ANN indexes are emitted with the other indexes rather than inline,\n // because `CREATE INDEX` is a statement and a column definition is not\n // — and because a column too wide for pgvector to index still needs its\n // column. Absent alongside their column when vector is carved out.\n if (includeVector && table.vector) {\n indexStatements.push(...vectorIndexStatements(table.vector));\n for (const skip of table.vector.skipped) {\n indexStatements.push(`-- No ANN index on \"${skip.schema}\".\"${skip.table}\".\"${skip.column}\": ${skip.reason}`);\n }\n }\n // The collection's own `indexes:` block. Last of the three index\n // producers, and the only one the developer wrote deliberately rather\n // than getting as a side effect of another feature.\n indexStatements.push(...collectionIndexStatements(table.indexes));\n\n if (includePolicies) {\n // No FORCE: authenticated requests run as the non-owner\n // `rebase_user` role, which plain ENABLE already binds. The owner\n // (server context) must bypass — it is the trusted plane.\n ddl += `${renderEnableRls(table)}\\n`;\n ddl += \"\\n\";\n for (const policy of table.policies) {\n policyStatements.push(statementsToDdl(renderPolicyStatements(table, policy)));\n }\n }\n }\n\n // Indexes before foreign keys. Today every FK targets a primary key created\n // inline, so the order is safe by accident; the moment a declared\n // `unique: true` index can back an FK target, a constraint emitted first\n // would reference an index that does not exist yet.\n if (indexStatements.length > 0) {\n ddl += \"-- Indexes\\n\";\n ddl += indexStatements.join(\"\\n\") + \"\\n\\n\";\n }\n\n if (fkStatements.length > 0) {\n ddl += \"-- Foreign Key Constraints\\n\";\n ddl += fkStatements.join(\"\\n\") + \"\\n\\n\";\n }\n\n if (policyStatements.length > 0) {\n ddl += \"-- Row Level Security Policies\\n\";\n ddl += policyStatements.join(\"\");\n ddl += \"\\n\";\n }\n\n return ddl;\n}\n\n// ── policies.sql ─────────────────────────────────────────────────────────────\n\nexport function renderPoliciesDdl(plan: SchemaPlan): string {\n let ddl = \"-- This file contains RLS policies generated by Rebase. Applied separately from migrations.\\n\\n\";\n\n // Collections first, then the junctions no collection declares — the order\n // the boot-time applier plans them in.\n for (const table of plan.tables.filter(t => t.kind === \"collection\")) {\n ddl += `${renderEnableRls(table)}\\n`;\n ddl += \"\\n\";\n if (table.policies.length === 0) continue;\n let announced: string | undefined;\n for (const policy of table.policies) {\n // Say which policies the author did not write. They are permissive,\n // so they OR with the declared rules and widen the final ACL beyond\n // what `securityRules` reads like — and re-appear after any manual\n // DROP, because a push asserts the declared state. Once per *rule*:\n // one rule with four operations is one thing the author did not\n // write, not four.\n if (policy.injected && policy.ruleKey !== announced) {\n ddl += \"-- Injected by Rebase (not from this collection's securityRules).\\n\";\n ddl += `-- Set \\`disableDefaultPolicies: true\\` on \"${table.slug}\" to drop these and own its RLS outright.\\n`;\n }\n announced = policy.ruleKey;\n ddl += statementsToDdl(renderPolicyStatements(table, policy));\n }\n ddl += \"\\n\";\n }\n\n for (const table of plan.tables.filter(t => t.kind === \"junction\")) {\n ddl += `${renderEnableRls(table)}\\n`;\n ddl += \"\\n\";\n if (table.policies.length === 0) continue;\n const declaringSlugs = (table.declaringSlugs ?? []).join('\", \"');\n ddl += `-- Derived by Rebase for the junction \"${table.table}\" (no collection declares it).\\n`;\n ddl += \"-- Reads require both endpoint rows to be visible; writes follow the update\\n\";\n ddl += `-- rules of \"${declaringSlugs}\". Set \\`disableDefaultPolicies: true\\` on the\\n`;\n ddl += \"-- declaring collection(s) to drop these and police the junction yourself.\\n\";\n for (const policy of table.policies) {\n ddl += statementsToDdl(renderPolicyStatements(table, policy));\n }\n ddl += \"\\n\";\n }\n\n return ddl;\n}\n\n// ── search.sql ───────────────────────────────────────────────────────────────\n\nexport function renderSearchDdl(plan: SchemaPlan): string {\n const tables = plan.tables.filter(t => t.search);\n if (tables.length === 0) return \"\";\n\n const extensions = Array.from(new Set(tables.flatMap(t => searchExtensionStatements(t.search!))));\n const helpers = Array.from(new Set(tables.flatMap(t => searchHelperFunctions(t.search!))));\n\n let ddl = \"-- This file is auto-generated by the Rebase DDL generator. Do not edit manually.\\n\";\n ddl += \"--\\n\";\n ddl += \"-- Full-text search for the collections declaring a `search` block.\\n\";\n ddl += \"-- Applied by Rebase, not by Atlas — see generatePostgresSearchDdl.\\n\\n\";\n\n extensions.forEach(s => { ddl += `${s}\\n`; });\n if (extensions.length > 0) ddl += \"\\n\";\n helpers.forEach(s => { ddl += `${s}\\n\\n`; });\n\n for (const table of tables) {\n const spec = table.search!;\n const target = `\"${table.schema}\".\"${table.table}\"`;\n // ADD COLUMN IF NOT EXISTS rather than the inline definition the\n // CREATE TABLE would use: by the time this runs the table exists,\n // whether Atlas just created it or it has been live for a year. The\n // guard first, because `ADD COLUMN IF NOT EXISTS` is a no-op against an\n // existing column — without it the file would report success, leave the\n // old expression in place, and then stamp it as current.\n searchStampGuards(spec).forEach(s => { ddl += `${s}\\n`; });\n ddl += `ALTER TABLE ${target} ADD COLUMN IF NOT EXISTS ${searchColumnDefinition(spec)};\\n`;\n const fuzzy = fuzzyColumnDefinition(spec);\n if (fuzzy) ddl += `ALTER TABLE ${target} ADD COLUMN IF NOT EXISTS ${fuzzy};\\n`;\n searchColumnStamps(spec).forEach(s => { ddl += `${s.sql}\\n`; });\n searchIndexStatements(spec).forEach(s => { ddl += `${s}\\n`; });\n ddl += \"\\n\";\n }\n\n return ddl;\n}\n\n/**\n * Glob patterns telling Atlas to leave an object alone.\n *\n * Fully qualified, `schema.table.object`, matching the include list. The\n * two-part form is what Atlas wants when the connection URL scopes it to one\n * schema and is *silently ignored* otherwise: it reads `posts.search_vector` as\n * a table named `search_vector` in a schema named `posts`, matches nothing, and\n * reports no error for the pattern that never fired.\n */\nexport const searchExcludePatternsOf = (plan: SchemaPlan): string[] => {\n const patterns: string[] = [];\n for (const table of plan.tables) {\n if (!table.search) continue;\n for (const column of table.columns) {\n if (column.source.kind !== \"search\") continue;\n patterns.push(`${table.schema}.${table.table}.${column.column}`);\n }\n for (const index of searchIndexNames(table.search)) {\n patterns.push(`${table.schema}.${table.table}.${index}`);\n }\n }\n return patterns;\n};\n\n// ── vector.sql ───────────────────────────────────────────────────────────────\n\nexport function renderVectorDdl(plan: SchemaPlan): string {\n const tables = plan.tables.filter(t => t.vectorColumns.length > 0);\n if (tables.length === 0) return \"\";\n\n let ddl = \"-- This file is auto-generated by the Rebase DDL generator. Do not edit manually.\\n\";\n ddl += \"--\\n\";\n ddl += \"-- Vector columns and their ANN indexes, for the collections declaring a\\n\";\n ddl += \"-- `vector` property. Applied by Rebase, not by Atlas — see\\n\";\n ddl += \"-- generatePostgresVectorDdl.\\n\\n\";\n // The note is not decoration. Someone reading this file after a failed push\n // is looking for the reason the type does not exist, and \"no CREATE\n // EXTENSION here\" is invisible unless it is written down.\n ddl += vectorExtensionDeclared(plan.options.databaseExtensions)\n ? `${vectorExtensionStatement()}\\n\\n`\n : \"-- pgvector is not installed from here: no database declared it. To let Rebase\\n\" +\n `-- install it, add ${VECTOR_EXTENSION_OPT_IN} in config/resources.ts.\\n` +\n \"-- Otherwise install it once by hand; the column below needs the type either way.\\n\\n\";\n\n for (const table of tables) {\n const target = `\"${table.schema}\".\"${table.table}\"`;\n for (const spec of table.vectorColumns) {\n // Guard before the ADD, so a changed `dimensions` is reported\n // rather than skipped over — see `vectorDimensionGuard`.\n ddl += `${vectorDimensionGuard(spec)}\\n`;\n ddl += `ALTER TABLE ${target} ADD COLUMN IF NOT EXISTS ${vectorColumnDefinition(spec)};\\n`;\n }\n if (table.vector) {\n vectorIndexStatements(table.vector).forEach(s => { ddl += `${s}\\n`; });\n for (const skip of table.vector.skipped) {\n ddl += `-- No ANN index on \"${skip.schema}\".\"${skip.table}\".\"${skip.column}\": ${skip.reason}\\n`;\n }\n }\n ddl += \"\\n\";\n }\n\n return ddl;\n}\n\n/** @see searchExcludePatternsOf */\nexport const vectorExcludePatternsOf = (plan: SchemaPlan): string[] => {\n const patterns: string[] = [];\n for (const table of plan.tables) {\n const names = [\n ...table.vectorColumns.map(spec => spec.column),\n ...(table.vector ? vectorIndexNames(table.vector) : [])\n ];\n for (const name of names) patterns.push(`${table.schema}.${table.table}.${name}`);\n }\n return patterns;\n};\n\n// ── triggers.sql ─────────────────────────────────────────────────────────────\n\n/**\n * `autoValue: \"on_update\"`, as the file Rebase applies itself.\n *\n * Empty when nothing declares one — the caller writes no file then, and removes\n * a stale one.\n */\nexport function renderTriggersDdl(plan: SchemaPlan): string {\n const tables = plan.tables.filter(t => t.triggers.length > 0);\n if (tables.length === 0) return \"\";\n\n let ddl = \"-- This file is auto-generated by the Rebase DDL generator. Do not edit manually.\\n\";\n ddl += \"--\\n\";\n ddl += \"-- `updated_at` triggers, for the properties declaring `autoValue: \\\"on_update\\\"`.\\n\";\n ddl += \"-- Applied by Rebase, not by Atlas — a trigger is a function plus a binding,\\n\";\n ddl += \"-- and Atlas's free tier refuses to parse a desired state containing one.\\n\\n\";\n\n for (const statement of plan.functions.filter(isTriggerFunction)) {\n ddl += `${statement}\\n\\n`;\n }\n for (const table of tables) {\n for (const trigger of table.triggers) {\n triggerStatements(trigger).forEach(s => { ddl += `${s}\\n`; });\n }\n ddl += \"\\n\";\n }\n return ddl;\n}\n\nconst isTriggerFunction = (statement: string): boolean => statement.includes(\"RETURNS trigger\");\n\n/** @see searchExcludePatternsOf */\nexport const triggerExcludePatternsOf = (plan: SchemaPlan): string[] =>\n plan.tables.flatMap(table => table.triggers.map(t => `${table.schema}.${table.table}.${t.name}`));\n","/**\n * The SQL half of `rebase db generate`, as a plan and a renderer.\n *\n * There is no interpretation left in this file. `schema/plan/plan-schema.ts`\n * reads the collections into a {@link SchemaPlan} and `schema/plan/render-ddl.ts`\n * writes the files; what remains here is the public entry points those two are\n * reached through, plus the smaller plans (`planRelationalColumns`,\n * `planJunctionTables`, `planCollectionPolicies`) that other modules ask for as\n * slices of the same plan.\n *\n * It used to hold `getSqlColumnType` and a `CREATE TABLE` walk with its own\n * reading of relations, junctions, enums and search — one of three such\n * readings. See `schema/plan/types.ts` for what that cost.\n */\nimport { CollectionConfig, SecurityRule, isPostgresCollectionConfig } from \"@rebasepro/types\";\nimport { getTableName } from \"@rebasepro/common\";\nimport { planSchema, compileSecurityRule, quoteSqlLiteral, defaultBelongsToOnDelete } from \"./plan/plan-schema\";\nimport {\n renderPgType,\n renderPoliciesDdl,\n renderPolicyStatements,\n renderPostgresDdl,\n renderSearchDdl,\n renderTriggersDdl,\n renderVectorDdl,\n searchExcludePatternsOf,\n triggerExcludePatternsOf,\n vectorExcludePatternsOf,\n type DdlRenderOptions\n} from \"./plan/render-ddl\";\nimport type { ForeignKeyPlan, SchemaPlan } from \"./plan/types\";\nimport type { GeneratedColumnDependency } from \"./generated-column-conflicts\";\n\nexport { quoteSqlLiteral, defaultBelongsToOnDelete };\nexport type { ForeignKeyPlan };\n\ntype ResolveCollection = (slug: string) => CollectionConfig | undefined;\n\n/**\n * The individual SQL statements a single security rule compiles to: a\n * `DROP POLICY IF EXISTS` / `CREATE POLICY` pair per operation, each a complete\n * statement (terminated by `;`, no trailing newline).\n *\n * This is the primitive the boot-time RLS applier runs one statement at a time\n * (the runtime's DB handle speaks the extended query protocol, which forbids\n * multiple commands in one execute), while `db push` writes the joined string.\n */\nexport const generatePolicyStatements = (\n collection: CollectionConfig,\n rule: SecurityRule,\n resolveCollection: ResolveCollection\n): string[] => {\n const table = {\n schema: isPostgresCollectionConfig(collection) && collection.schema ? collection.schema : \"public\",\n table: getTableName(collection)\n };\n return compileSecurityRule(collection, rule, resolveCollection, false)\n .flatMap(policy => renderPolicyStatements(table, policy));\n};\n\n/**\n * `drizzle/schema.sql` — the desired state Atlas diffs the database against.\n *\n * Synchronous. It was `async` with nothing to await, which made every caller\n * (the writer script, the doctor, the live editor, two gates) carry a promise\n * for no reason and made the one place that forgot to await it silently compare\n * `[object Promise]`.\n */\nexport const generatePostgresDdl = (\n allCollections: CollectionConfig[],\n options: DdlRenderOptions = {}\n): string => renderPostgresDdl(planSchema(allCollections), options);\n\n/** `drizzle/policies.sql` — the RLS policies. Atlas has no model for a policy. */\nexport const generatePostgresPoliciesDdl = (allCollections: CollectionConfig[]): string =>\n renderPoliciesDdl(planSchema(allCollections));\n\n/**\n * `drizzle/search.sql` — everything a `search` block needs, as a file Rebase\n * applies itself.\n *\n * Search is one of the parts of the schema Atlas does not own, for two\n * independent reasons and either alone would be enough:\n *\n * 1. Its free tier refuses to *parse* a desired-state file that so much as\n * contains a function — \"functions and procedures are available to\n * logged-in users only\". A generated `tsvector` column cannot avoid one:\n * `unaccent` is STABLE and jsonb flattening needs a set-returning function,\n * so both have to be wrapped in an IMMUTABLE helper to be legal in a\n * generated column at all.\n * 2. Even with the file accepted, Atlas *wipes* the dev database it diffs\n * against, so a helper seeded there beforehand is gone by the time the plan\n * is analysed. There is no hook to reinstate it.\n *\n * Empty when nothing opted in — the caller writes no file then.\n */\nexport const generatePostgresSearchDdl = (allCollections: CollectionConfig[]): string =>\n renderSearchDdl(planSchema(allCollections));\n\n/**\n * Glob patterns telling Atlas to leave the search column and its index alone.\n *\n * Without these, a desired state that omits search reads to Atlas as an\n * instruction to drop the column — taking the index and the whole search\n * feature with it on the next push.\n */\nexport const searchExcludePatterns = (allCollections: CollectionConfig[]): string[] =>\n searchExcludePatternsOf(planSchema(allCollections));\n\n/**\n * `drizzle/vector.sql` — the `vector` columns, their ANN indexes and, when a\n * database declared it, the extension.\n *\n * Vector is out of Atlas's hands for a narrower reason than search: not that\n * Atlas refuses to parse the file, but that it cannot *execute* it. To compute\n * a desired state Atlas materialises `schema.sql` in a dev database, that\n * database is created empty, and Atlas empties it again at the start of every\n * run — so `VECTOR(384)` resolves against a database that structurally cannot\n * have pgvector, and every push dies with `type \"vector\" does not exist`.\n */\nexport const generatePostgresVectorDdl = (\n allCollections: CollectionConfig[],\n options: { extensions?: readonly string[] } = {}\n): string => renderVectorDdl(planSchema(allCollections, { databaseExtensions: options.extensions }));\n\n/** @see searchExcludePatterns */\nexport const vectorExcludePatterns = (allCollections: CollectionConfig[]): string[] =>\n vectorExcludePatternsOf(planSchema(allCollections));\n\n/**\n * `drizzle/triggers.sql` — the `BEFORE UPDATE` triggers behind\n * `autoValue: \"on_update\"`, and the one function they share.\n *\n * A trigger is a function plus a binding, so it is carved out of Atlas's view\n * for the first of the two reasons search is. Empty when no property declares\n * `on_update` — the caller writes no file then, and removes a stale one.\n */\nexport const generatePostgresTriggersDdl = (allCollections: CollectionConfig[]): string =>\n renderTriggersDdl(planSchema(allCollections));\n\n/** @see searchExcludePatterns */\nexport const triggerExcludePatterns = (allCollections: CollectionConfig[]): string[] =>\n triggerExcludePatternsOf(planSchema(allCollections));\n\n// ── Slices of the plan other modules ask for ─────────────────────────────────\n\n/** A column a `relation` or `reference` property owns on its own table. */\nexport interface RelationalColumnPlan {\n schema: string;\n /** Bare table name, no schema prefix. */\n table: string;\n column: string;\n /** Postgres type, exactly as the DDL renderer declares it. */\n type: string;\n /** `validation.required` on the property that owns the link. */\n required: boolean;\n /**\n * True when a declared property emits this column and the relation\n * contributes only the constraint — `postId` with `columnName: \"post_id\"`\n * beside a `belongsTo` on `post_id`.\n */\n columnOwnedByProperty?: boolean;\n /** Absent when the target collection is not part of this bundle. */\n foreignKey?: ForeignKeyPlan;\n /**\n * What this column would have been called before `generateForeignKeyName`\n * learned to singularize. Carried so the boot-time ensure can notice a\n * database provisioned under the old rule; never used to name anything.\n */\n legacyColumn?: string;\n}\n\n/** The table behind a many-to-many `through` relation. */\nexport interface JunctionTablePlan {\n schema: string;\n /** Bare table name, no schema prefix. */\n table: string;\n columns: { name: string; type: string; legacyName?: string }[];\n /** Both endpoint columns plus the composite primary key. */\n createTable: string;\n foreignKeys: ForeignKeyPlan[];\n}\n\nconst planOf = (collections: CollectionConfig[]): SchemaPlan => planSchema(collections);\n\n/**\n * The FK columns the declared collections own — one entry per `relation`\n * (`belongsTo` side) or `reference` property.\n *\n * A view over the schema plan. It was a second walk of the collections beside\n * the `CREATE TABLE` one, which is how boot-ensure came to add every relation\n * column bare while `db push` made the same column `NOT NULL`.\n */\nexport const planRelationalColumns = (allCollections: CollectionConfig[]): RelationalColumnPlan[] => {\n const plan = planOf(allCollections);\n const out: RelationalColumnPlan[] = [];\n for (const table of plan.tables) {\n if (table.kind !== \"collection\") continue;\n for (const column of table.columns) {\n if (column.source.kind !== \"relation\" && column.source.kind !== \"reference\") continue;\n out.push({\n schema: table.schema,\n table: table.table,\n column: column.column,\n type: renderPgType(column.type),\n required: !column.nullable,\n columnOwnedByProperty: column.columnOwnedByProperty,\n legacyColumn: column.legacyColumn,\n foreignKey: column.foreignKey\n });\n }\n }\n return out;\n};\n\n/**\n * The junction tables a bundle's many-to-many relations imply.\n *\n * Derived from the same plan the derived RLS comes from, so a table created\n * here always has policies planned for it — a junction with row-level security\n * left off is readable and writable by every signed-in user.\n */\nexport const planJunctionTables = (allCollections: CollectionConfig[]): JunctionTablePlan[] =>\n planOf(allCollections).tables\n .filter(table => table.kind === \"junction\")\n .map(table => ({\n schema: table.schema,\n table: table.table,\n columns: table.columns.map(c => ({\n name: c.column,\n type: renderPgType(c.type),\n legacyName: c.legacyColumn\n })),\n createTable:\n `CREATE TABLE IF NOT EXISTS \"${table.schema}\".\"${table.table}\" (` +\n table.columns.map(c => `\"${c.column}\" ${renderPgType(c.type)} NOT NULL`).join(\", \") +\n `, PRIMARY KEY (${table.primaryKey.map(c => `\"${c}\"`).join(\", \")}));`,\n foreignKeys: table.columns.map(c => c.foreignKey).filter((fk): fk is ForeignKeyPlan => fk !== undefined)\n }));\n\nexport interface CollectionPolicyPlan {\n /** The table's schema (e.g. `public`, `rebase`). */\n schema: string;\n /** The bare table name, no schema prefix. */\n table: string;\n /** `schema.table` — matches the keys `readExistingSchema` returns. */\n qualified: string;\n /** `ALTER TABLE … ENABLE ROW LEVEL SECURITY;` — locked by default. */\n enableRls: string;\n /** `DROP POLICY IF EXISTS` / `CREATE POLICY` statements, in order. */\n policyStatements: string[];\n}\n\n/**\n * The per-table RLS plan for the declared collections and their junctions, as\n * executable statements — what the managed runtime applies at boot so a freshly\n * provisioned tenant database serves data instead of 401ing every read.\n *\n * The same policies `policies.sql` carries, from the same plan, so boot and\n * `db push` produce identical RLS from identical collections.\n */\nexport const planCollectionPolicies = (allCollections: CollectionConfig[]): CollectionPolicyPlan[] => {\n const plan = planOf(allCollections);\n // Collections first, then the junctions no collection declares.\n const ordered = [\n ...plan.tables.filter(t => t.kind === \"collection\"),\n ...plan.tables.filter(t => t.kind === \"junction\")\n ];\n return ordered.map(table => ({\n schema: table.schema,\n table: table.table,\n qualified: table.qualified,\n enableRls: `ALTER TABLE \"${table.schema}\".\"${table.table}\" ENABLE ROW LEVEL SECURITY;`,\n policyStatements: table.policies.flatMap(policy => renderPolicyStatements(table, policy))\n }));\n};\n\n/**\n * The `generated column → column it reads` edges a project's `search` blocks\n * imply, derived from the collections alone.\n *\n * The same edges `queryGeneratedColumnDependencies` reads out of `pg_depend`,\n * but available where no database is: `rebase db generate` writes a migration\n * that will run somewhere else, later, against a database it cannot inspect.\n * A spec's `fields[].column` is the physical column each search field starts\n * at, which is exactly what the generated expression depends on.\n */\nexport const searchColumnDependencies = (\n allCollections: CollectionConfig[]\n): GeneratedColumnDependency[] => {\n const dependencies: GeneratedColumnDependency[] = [];\n for (const table of planOf(allCollections).tables) {\n const spec = table.search;\n if (!spec) continue;\n const generated = [spec.column, ...(spec.fuzzy ? [spec.fuzzy.column] : [])];\n const sources = new Set(spec.fields.map(field => field.column));\n for (const column of generated) {\n for (const dependsOn of sources) {\n dependencies.push({ schema: table.schema, table: table.table, column, dependsOn });\n }\n }\n }\n return dependencies;\n};\n"],"mappings":"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"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"introspect-db-logic-WMuAfxvw.js","names":[],"sources":["../src/schema/introspect-db-queries.ts","../src/schema/introspect-db-naming.ts","../src/schema/introspect-db-inference.ts","../src/schema/introspect-db-types.ts","../src/schema/introspect-db-structure.ts","../src/schema/introspect-db-logic.ts"],"sourcesContent":["/**\n * The catalog queries introspection runs, and the shape they produce.\n *\n * These live apart from {@link ./introspect-db.ts} (the CLI entry point) on\n * purpose: the fixture-capture script and the live e2e read a database through\n * this module too, so what the tests see is what the CLI sees. A query that\n * only exists inside the CLI's `main()` can only be tested by running the CLI.\n *\n * No side effects beyond `SELECT`. Introspection reads a database it does not\n * own — it must never `ANALYZE`, create a temp table, or otherwise write.\n */\nimport type {\n TableRow,\n TableColumn,\n EnumValue,\n PrimaryKeyRow,\n ForeignKeyRow,\n UniqueConstraintRow,\n CheckConstraintRow,\n CommentRow,\n SchemaMetadata\n} from \"./introspect-db-logic\";\n\n/** The subset of `pg.Client` this module needs, so tests can pass a fake. */\nexport interface QueryableClient {\n query<R = Record<string, unknown>>(text: string, values?: unknown[]): Promise<{ rows: R[] }>;\n}\n\n/**\n * A Postgres identifier, quoted so that whatever is inside it stays inside it.\n *\n * Introspection reads table and schema names out of `pg_class` and puts them\n * straight back into SQL, and wrapping a name in double quotes is not the same\n * as escaping it: a table named `x\" ; DROP TABLE users; --` closes the quote\n * and then the statement. Postgres's own escape for a quote inside a quoted\n * identifier is to double it, which is what this does.\n *\n * \"The catalogue is trusted\" is true of your own database, and is exactly the\n * assumption this tool cannot make. Introspection exists to be pointed at\n * databases somebody else built, and it runs with the privileges of whoever\n * ran it — which is usually a superuser, because that is who has a connection\n * string to a database they are adopting.\n */\nexport function quoteIdentifier(name: string): string {\n return `\"${name.replace(/\"/g, \"\\\"\\\"\")}\"`;\n}\n\n/** `\"schema\".\"table\"`, both halves quoted. */\nexport function quoteRelation(schema: string, table: string): string {\n return `${quoteIdentifier(schema)}.${quoteIdentifier(table)}`;\n}\n\n/**\n * Can this table's name be the name of the file describing it?\n *\n * Introspection writes `<collectionsDir>/<tableName>.ts`, and the table name\n * came from a database this tool did not create. Postgres will hold a table\n * called `../../../etc/cron.d/x`, and `path.join` resolves it — so without this\n * the generator writes a file wherever the name points, as whoever ran the\n * command.\n *\n * Deliberately narrow, and deliberately not a blocklist of `..` and `/`: the\n * question is \"is this a plain file name?\", and every answer that is not\n * obviously yes is a table better written by hand. NUL and the Windows\n * separator are in scope because a collections directory is checked out on\n * whatever machine the developer has.\n */\nexport function isUsableFileName(name: string): boolean {\n if (name.length === 0 || name.length > 200) return false;\n if (name === \".\" || name === \"..\") return false;\n // Path separators (both platforms'), the Windows drive separator, and any\n // control character — a NUL truncates the path at the OS boundary, so a\n // name that reads as harmless in a log can be something else on disk.\n // eslint-disable-next-line no-control-regex\n if (/[/\\\\:\\u0000-\\u001f]/.test(name)) return false;\n // A leading dot makes a hidden file, and a trailing one is invalid on\n // Windows; neither is a table anyone means to generate.\n return !name.startsWith(\".\") && !name.endsWith(\".\");\n}\n\n/**\n * Base tables, excluding partitions.\n *\n * `relispartition` is the reason this reads `pg_class` rather than\n * `information_schema.tables`, which reports every partition as a base table of\n * its own. Pagila partitions `payment` by month; introspected through\n * information_schema it yields `payment` plus 26 near-identical\n * `payment_p2022_*` collections, each with its own nav entry.\n *\n * `relkind = 'p'` (the partitioned parent) is included and `'r'` partitions are\n * dropped, which is the right way round: the parent is the queryable table.\n */\nexport const TABLES_QUERY = `\n SELECT c.relname AS table_name,\n c.relkind = 'p' AS is_partitioned\n FROM pg_class c\n JOIN pg_namespace n ON n.oid = c.relnamespace\n WHERE n.nspname = $1\n AND c.relkind IN ('r', 'p')\n AND NOT c.relispartition\n AND c.relname NOT LIKE 'drizzle_%'\n AND c.relname NOT LIKE 'rebase_%'\n ORDER BY c.relname\n`;\n\n/**\n * Columns, with the catalog facts that say a column is not the user's to edit.\n *\n * `is_generated`/`is_identity` are read for that reason: a generated column\n * rejects any write, so a form that offers it is offering a field that always\n * errors. `character_maximum_length` is the declared `varchar(n)` bound —\n * already enforced by the database, and free to surface as validation.\n */\nexport const COLUMNS_QUERY = `\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 c.ordinal_position,\n c.is_generated,\n c.is_identity,\n c.identity_generation,\n c.character_maximum_length,\n c.numeric_precision,\n c.numeric_scale,\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 ORDER BY c.table_name, c.ordinal_position\n`;\n\nexport const ENUMS_QUERY = `\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`;\n\n/**\n * Primary key columns, in key order.\n *\n * `ORDER BY k.ord` matters for composite keys: the position of a column inside\n * the key is what tells a two-column PK made of two foreign keys apart from an\n * ordinary composite key, and `= ANY(i.indkey)` (the shape this replaced)\n * returns catalog order instead.\n */\nexport const PRIMARY_KEYS_QUERY = `\n SELECT t.relname AS table_name,\n a.attname AS column_name\n FROM pg_index i\n JOIN pg_class t ON t.oid = i.indrelid\n JOIN pg_namespace n ON n.oid = t.relnamespace\n CROSS JOIN LATERAL unnest(i.indkey) WITH ORDINALITY AS k(attnum, ord)\n JOIN pg_attribute a ON a.attrelid = i.indrelid AND a.attnum = k.attnum\n WHERE i.indisprimary AND n.nspname = $1\n ORDER BY t.relname, k.ord\n`;\n\n/**\n * Foreign keys, one row per referencing column, with the delete rule.\n *\n * Read from `pg_constraint` rather than through\n * `information_schema.constraint_column_usage`, which does not preserve the\n * pairing on a composite foreign key: it reports the cross product of the\n * constraint's columns, so a two-column FK comes back as four rows, two of them\n * pairing the wrong columns. `unnest(...) WITH ORDINALITY` on both sides joined\n * on the ordinal keeps each referencing column with the column it references.\n *\n * `confdeltype` is carried because `ON DELETE CASCADE` is the schema author\n * saying, in the database, that the child cannot outlive the parent — the one\n * unambiguous declaration of ownership a schema contains.\n *\n * Constraints declared on a *partition* are attributed to the partition root,\n * because that is the table this run generates a collection for. Pagila declares\n * `payment`'s three foreign keys on each monthly partition and none on the\n * parent, so reading `pg_constraint` at face value gives the `payment`\n * collection no relations at all. `pick = 1` then keeps one row per logical key,\n * preferring the root's own constraint when it has one, so a schema that\n * declares the key on the parent *and* inherits it onto 26 partitions does not\n * yield 27 copies of the same relation.\n */\nexport const FOREIGN_KEYS_QUERY = `\n WITH fk AS (\n SELECT con.oid,\n con.conname,\n con.conrelid,\n con.confrelid,\n con.conkey,\n con.confkey,\n con.confdeltype,\n COALESCE(pg_partition_root(con.conrelid), con.conrelid) AS root_oid,\n row_number() OVER (\n PARTITION BY COALESCE(pg_partition_root(con.conrelid), con.conrelid),\n con.confrelid, con.conkey, con.confkey\n ORDER BY (con.conrelid = COALESCE(pg_partition_root(con.conrelid), con.conrelid)) DESC,\n con.oid\n ) AS pick\n FROM pg_constraint con\n JOIN pg_namespace n ON n.oid = con.connamespace\n WHERE con.contype = 'f' AND n.nspname = $1\n )\n SELECT root.relname AS table_name,\n sa.attname AS column_name,\n tgt.relname AS foreign_table_name,\n ta.attname AS foreign_column_name,\n fk.conname AS constraint_name,\n s.ord::int AS ordinal,\n CASE fk.confdeltype\n WHEN 'a' THEN 'NO ACTION'\n WHEN 'r' THEN 'RESTRICT'\n WHEN 'c' THEN 'CASCADE'\n WHEN 'n' THEN 'SET NULL'\n WHEN 'd' THEN 'SET DEFAULT'\n END AS delete_rule\n FROM fk\n JOIN pg_class root ON root.oid = fk.root_oid\n JOIN pg_class tgt ON tgt.oid = fk.confrelid\n CROSS JOIN LATERAL unnest(fk.conkey) WITH ORDINALITY AS s(attnum, ord)\n JOIN pg_attribute sa ON sa.attrelid = fk.conrelid AND sa.attnum = s.attnum\n CROSS JOIN LATERAL unnest(fk.confkey) WITH ORDINALITY AS f(attnum, ord)\n JOIN pg_attribute ta ON ta.attrelid = fk.confrelid AND ta.attnum = f.attnum\n WHERE fk.pick = 1 AND s.ord = f.ord\n ORDER BY root.relname, fk.conname, s.ord\n`;\n\n/**\n * Unique constraints and unique indexes, as ordered column lists.\n *\n * Indexes rather than constraints alone, because `CREATE UNIQUE INDEX` and\n * `ADD CONSTRAINT ... UNIQUE` produce the same guarantee and schemas use both.\n * Partial indexes (`indpred IS NOT NULL`) are excluded: they promise uniqueness\n * only over the rows matching their predicate, which is not the promise a\n * `validation: { unique: true }` field makes.\n */\nexport const UNIQUE_CONSTRAINTS_QUERY = `\n SELECT t.relname AS table_name,\n ic.relname AS constraint_name,\n -- ::text, because the driver has no parser for an array of \\`name\\`\n -- and hands back the raw \\`{a,b}\\` literal as a string. Every consumer\n -- here indexes and compares it as an array, and a string of that\n -- shape fails those silently rather than loudly.\n array_agg(a.attname::text ORDER BY k.ord) AS column_names\n FROM pg_index i\n JOIN pg_class t ON t.oid = i.indrelid\n JOIN pg_class ic ON ic.oid = i.indexrelid\n JOIN pg_namespace n ON n.oid = t.relnamespace\n CROSS JOIN LATERAL unnest(i.indkey) WITH ORDINALITY AS k(attnum, ord)\n JOIN pg_attribute a ON a.attrelid = i.indrelid AND a.attnum = k.attnum\n WHERE i.indisunique\n AND NOT i.indisprimary\n AND i.indpred IS NULL\n AND n.nspname = $1\n AND NOT t.relispartition\n GROUP BY t.relname, ic.relname\n ORDER BY t.relname, ic.relname\n`;\n\n/**\n * CHECK constraints, as the source text Postgres reproduces them from.\n *\n * `pg_get_constraintdef` output is normalized by the server — the parser in\n * {@link ./introspect-db-constraints.ts} reads that normalized form, not\n * whatever the author typed.\n */\nexport const CHECK_CONSTRAINTS_QUERY = `\n SELECT DISTINCT\n root.relname AS table_name,\n con.conname AS constraint_name,\n pg_get_constraintdef(con.oid) AS definition\n FROM pg_constraint con\n JOIN pg_namespace n ON n.oid = con.connamespace\n JOIN pg_class root ON root.oid = COALESCE(pg_partition_root(con.conrelid), con.conrelid)\n WHERE con.contype = 'c'\n AND n.nspname = $1\n ORDER BY root.relname, con.conname\n`;\n\n/**\n * `COMMENT ON TABLE` / `COMMENT ON COLUMN`, which is documentation the author\n * already wrote and which nothing downstream has ever read.\n */\nexport const COMMENTS_QUERY = `\n SELECT t.relname AS table_name,\n a.attname AS column_name,\n d.description AS comment\n FROM pg_description d\n JOIN pg_class t ON t.oid = d.objoid\n JOIN pg_namespace n ON n.oid = t.relnamespace\n LEFT JOIN pg_attribute a ON a.attrelid = t.oid AND a.attnum = d.objsubid\n WHERE n.nspname = $1\n AND t.relkind IN ('r', 'p')\n AND (d.objsubid = 0 OR a.attname IS NOT NULL)\n ORDER BY t.relname, d.objsubid\n`;\n\n/**\n * Counts a table's rows, stopping once the answer can only be \"more than\n * `limit`\".\n *\n * Introspection asks this to tell a small reference table from a table that\n * merely looks like one (see `classifyTables`). `reltuples` would answer for\n * free but lies on a table that has never been analyzed — which is every table\n * in a database restored from a dump, i.e. exactly the case introspection meets\n * — and reports -1 there rather than an error, so the lie is silent.\n *\n * The subquery's LIMIT caps the work at `limit + 1` rows however large the\n * table is, so this stays cheap on a table with a billion rows.\n */\nexport async function countRowsUpTo(\n client: QueryableClient,\n schema: string,\n table: string,\n limit: number\n): Promise<number> {\n const { rows } = await client.query<{ count: string }>(\n `SELECT count(*)::text AS count FROM (SELECT 1 FROM ${quoteRelation(schema, table)} LIMIT ${limit + 1}) probe`\n );\n return Number(rows[0]?.count ?? 0);\n}\n\n/**\n * Reads everything the generator needs, in one pass.\n *\n * `rowCounts` is deliberately absent here: it costs a query per table and only\n * a handful of tables can possibly need it. The caller fills it in for the\n * candidates `lookupCandidates()` names.\n */\nexport async function readSchemaMetadata(\n client: QueryableClient,\n schema: string\n): Promise<SchemaMetadata> {\n // Sequential, not `Promise.all`. A `pg.Client` is a single connection with\n // one protocol stream: concurrent `query()` calls on one are queued and\n // deprecated (removed in pg@9), so the parallel version buys nothing and\n // warns while doing it.\n const tables = await client.query<TableRow>(TABLES_QUERY, [schema]);\n const columns = await client.query<TableColumn>(COLUMNS_QUERY, [schema]);\n const enumValues = await client.query<EnumValue>(ENUMS_QUERY, [schema]);\n const pks = await client.query<PrimaryKeyRow>(PRIMARY_KEYS_QUERY, [schema]);\n const fks = await client.query<ForeignKeyRow>(FOREIGN_KEYS_QUERY, [schema]);\n const uniques = await client.query<UniqueConstraintRow>(UNIQUE_CONSTRAINTS_QUERY, [schema]);\n const checks = await client.query<CheckConstraintRow>(CHECK_CONSTRAINTS_QUERY, [schema]);\n const comments = await client.query<CommentRow>(COMMENTS_QUERY, [schema]);\n\n // A column list scoped to the schema still names tables this run excludes —\n // views, partitions, the drizzle bookkeeping tables. Narrow every row set to\n // the tables actually being introspected so downstream code never has to\n // ask whether a table it found in `columns` really exists.\n const known = new Set(tables.rows.map((t) => t.table_name));\n const scoped = <T extends { table_name: string }>(rows: T[]) => rows.filter((r) => known.has(r.table_name));\n\n return {\n schema,\n tables: tables.rows,\n columns: scoped(columns.rows),\n enumValues: enumValues.rows,\n pks: scoped(pks.rows),\n // A foreign key pointing *out* of the introspected set (into another\n // schema, or at a view) cannot be turned into a relation: there is no\n // collection on the other end to link to.\n fks: scoped(fks.rows).filter((fk) => known.has(fk.foreign_table_name)),\n uniques: scoped(uniques.rows),\n checks: scoped(checks.rows),\n comments: scoped(comments.rows),\n rowCounts: {}\n };\n}\n","/**\n * Naming helpers shared by the introspection modules. These live apart from\n * `introspect-db-logic.ts` because the inference pass needs them too, and\n * importing them from there would close a cycle back through this module.\n */\n\n/**\n * Convert a snake_case name to a human-readable Title Case label.\n * e.g. \"created_at\" -> \"Created At\", \"customer_id\" -> \"Customer Id\"\n */\nexport function humanize(snakeName: string): string {\n return snakeName\n .replace(/_/g, \" \")\n .replace(/\\b\\w/g, (c) => c.toUpperCase());\n}\n","import { humanize } from \"./introspect-db-naming\";\n\nexport interface InferenceResult {\n propType?: string; // If the inference changes the base type\n extra?: string; // String to append to the property definition\n}\n\nconst ISO_8601_REGEX = /^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}(?:\\.\\d+)?(?:Z|[+-]\\d{2}:\\d{2})$/;\nconst UUID_REGEX = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;\nconst CUID_REGEX = /^c[^\\s-]{7,}$/i; // Basic CUID check\nconst COLOR_HEX_REGEX = /^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$/;\n\nexport function inferPropertyFromData(\n columnName: string,\n pgDataType: string,\n currentPropType: string,\n sampleValues: unknown[],\n isPk: boolean,\n /**\n * False when generating for a project without `@rebasepro/cms-types`, where\n * `BaseProperty` declares no `admin` field and the block below would not\n * compile. The type-level inferences (`propType`, `url`, `storage`) are\n * unaffected — only the form-widget hints are dropped.\n */\n emitAdmin = true\n): InferenceResult {\n const result: InferenceResult = {};\n const extraLines: string[] = [];\n // Collected separately and emitted as one block at the end. Pushed inline,\n // two admin-owned options from different branches produced two `admin: {`\n // blocks in one property — a duplicate key, which is also a compile error.\n const adminOptions: string[] = [];\n\n // Filter out null/undefined for analysis\n const validValues = sampleValues.filter(v => v !== null && v !== undefined && v !== \"\");\n\n if (validValues.length === 0) {\n return result; // Not enough data\n }\n\n const colNameLower = columnName.toLowerCase();\n\n // ── Number Analysis ──────────────────────────────────────────────────\n if (currentPropType === \"number\") {\n // Boolean stored as int\n const allZeroOrOne = validValues.every(v => v === 0 || v === 1 || v === \"0\" || v === \"1\");\n if (allZeroOrOne) {\n result.propType = \"boolean\";\n return result; // Don't do min/max if boolean\n }\n\n // Min / Max constraints\n const numValues = validValues.map(v => Number(v)).filter(v => !isNaN(v));\n if (numValues.length === validValues.length) {\n const min = Math.min(...numValues);\n const max = Math.max(...numValues);\n // Example heuristic: percentages\n if (min >= 0 && max <= 100 && (colNameLower.includes(\"percent\") || colNameLower.includes(\"rate\") || colNameLower.includes(\"score\"))) {\n extraLines.push(\" validation: {\\n min: 0,\\n max: 100\\n }\");\n } else if (min >= 0 && (colNameLower.includes(\"count\") || colNameLower.includes(\"total\") || colNameLower.includes(\"amount\"))) {\n extraLines.push(\" validation: {\\n min: 0\\n }\");\n }\n }\n\n // No currency heuristic. `admin.currency` was never a declared option and\n // nothing reads it, so a column called `price` got a block of config that did\n // nothing — invisible because this generator emits *source text*, which no\n // typechecker ever saw. Reinstate it by adding `currency` to\n // AdminNumberOptions first, and something that renders it.\n }\n\n // ── JSON / JSONB Analysis ────────────────────────────────────────────\n if (currentPropType === \"map\" || pgDataType.includes(\"json\")) {\n // PostGres jsonb can be object or array\n let allArrays = true;\n let allObjects = true;\n\n for (const v of validValues) {\n let parsed = v;\n if (typeof v === \"string\") {\n try { parsed = JSON.parse(v); } catch (e) { allArrays = false; allObjects = false; break; }\n }\n if (Array.isArray(parsed)) {\n allObjects = false;\n } else if (typeof parsed === \"object\" && parsed !== null) {\n allArrays = false;\n } else {\n allArrays = false;\n allObjects = false;\n }\n }\n\n if (allArrays && !allObjects) {\n result.propType = \"array\";\n // Infer inner type\n let allNumbers = true;\n let allStrings = true;\n for (const v of validValues) {\n const parsed = typeof v === \"string\" ? JSON.parse(v) : v;\n for (const item of parsed) {\n if (typeof item !== \"number\") allNumbers = false;\n if (typeof item !== \"string\") allStrings = false;\n }\n }\n const innerType = allNumbers ? \"number\" : allStrings ? \"string\" : \"map\";\n extraLines.push(` of: { name: \"${humanize(columnName)} Item\", type: \"${innerType}\" }`);\n } else {\n result.propType = \"map\";\n\n // Infer inner schema\n if (allObjects && validValues.length > 0) {\n const schema: Record<string, string> = {};\n for (const v of validValues) {\n const parsed = typeof v === \"string\" ? JSON.parse(v) : v;\n for (const [k, val] of Object.entries(parsed)) {\n if (val === null || val === undefined) continue;\n const type = typeof val;\n if (type === \"string\" || type === \"number\" || type === \"boolean\") {\n if (!schema[k]) schema[k] = type;\n else if (schema[k] !== type) schema[k] = \"mixed\";\n } else if (Array.isArray(val)) {\n if (!schema[k]) schema[k] = \"array\";\n else if (schema[k] !== \"array\") schema[k] = \"mixed\";\n } else if (type === \"object\") {\n if (!schema[k]) schema[k] = \"map\";\n else if (schema[k] !== \"map\") schema[k] = \"mixed\";\n }\n }\n }\n\n const keys = Object.keys(schema).filter(k => schema[k] !== \"mixed\");\n if (keys.length > 0) {\n const props = keys.map(k => {\n return `\\n ${k}: { name: \"${humanize(k)}\", type: \"${schema[k]}\" }`;\n }).join(\",\");\n extraLines.push(` properties: {${props}\\n }`);\n } else {\n extraLines.push(\" keyValue: true\");\n }\n } else {\n extraLines.push(\" keyValue: true\");\n }\n }\n }\n\n // ── String Analysis ──────────────────────────────────────────────────\n if (currentPropType === \"string\") {\n // Date/Time Strings\n if (validValues.every(v => typeof v === \"string\" && ISO_8601_REGEX.test(v))) {\n result.propType = \"date\";\n return result;\n }\n\n // Implicit Enums\n const uniqueValues = new Set(validValues);\n // Determine the maximum length among unique values\n let maxEnumLength = 0;\n for (const v of uniqueValues) {\n if (typeof v === \"string\" && v.length > maxEnumLength) {\n maxEnumLength = v.length;\n }\n }\n\n // Ensure no empty string, max length makes sense, and fewer unique values than total values (unless small total)\n if (uniqueValues.size > 0 && uniqueValues.size <= 5 && maxEnumLength <= 50 && validValues.length > uniqueValues.size && !uniqueValues.has(\"\")) {\n const isLikelyId = isPk || colNameLower.endsWith(\"_id\");\n if (!isLikelyId) {\n const enumEntries = Array.from(uniqueValues).map(v => `{ id: ${JSON.stringify(v)}, label: ${JSON.stringify(humanize(v as string))} }`).join(\", \");\n extraLines.push(` enum: [${enumEntries}]`);\n result.extra = extraLines.length > 0 ? \"\\n\" + extraLines.join(\",\\n\") + \",\" : \"\";\n return result; // Skip other string checks if it's an enum\n }\n }\n\n // UUID / CUID Detection\n const allUuid = validValues.every(v => typeof v === \"string\" && UUID_REGEX.test(v));\n const allCuid = validValues.every(v => typeof v === \"string\" && CUID_REGEX.test(v));\n if (allUuid) {\n if (isPk) extraLines.push(\" isId: \\\"uuid\\\"\");\n } else if (allCuid) {\n if (isPk) extraLines.push(\" isId: \\\"cuid\\\"\");\n }\n\n // No colour heuristic. `admin.color` was never a declared option and nothing\n // reads it — the same dead branch `currency` was. This generator emits *source\n // text*, so no typechecker ever saw either. Reinstate by adding the option and\n // something that renders it first.\n\n // Text Lengths, Multiline & Markdown\n let maxLength = 0;\n let hasNewlines = false;\n let hasMarkdown = false;\n for (const v of validValues) {\n if (typeof v === \"string\") {\n if (v.length > maxLength) maxLength = v.length;\n if (v.includes(\"\\n\")) hasNewlines = true;\n if (/^#{1,6}\\s+.+/m.test(v) || /\\*\\*.+\\*\\*/.test(v) || /\\[.+\\]\\(.+\\)/.test(v) || /^\\s*[-*]\\s+.+/m.test(v)) {\n hasMarkdown = true;\n }\n }\n }\n\n // `multiline` and `markdown` are *admin* property options, so they have\n // to sit inside the `admin` block. Emitted at the top of the property —\n // where they were — `PostgresCollectionConfig` does not declare them and\n // the generated file does not compile.\n if (hasMarkdown) {\n adminOptions.push(\"multiline: true\", \"markdown: true\");\n } else if (hasNewlines || maxLength > 100) {\n adminOptions.push(\"multiline: true\");\n }\n\n if (maxLength > 0 && maxLength < 10000) { // arbitrary cap to avoid huge limits\n // Pad the max length slightly for the constraint\n const paddedMax = Math.ceil((maxLength * 1.5) / 10) * 10;\n // Only add length constraint if it seems like a bounded string, not a long text\n if (paddedMax < 255) {\n extraLines.push(` validation: {\\n max: ${paddedMax}\\n }`);\n }\n }\n\n // Storage & Media (Extracted from old logic)\n const isUrl = colNameLower.endsWith(\"_url\") || colNameLower.endsWith(\"_uri\") || colNameLower.endsWith(\"_link\");\n const isMedia = colNameLower.includes(\"image\") || colNameLower.includes(\"avatar\") || colNameLower.includes(\"photo\") || colNameLower.includes(\"logo\") || colNameLower.includes(\"cover\");\n\n const allAbsoluteUrls = validValues.every(v => typeof v === \"string\" && (v.startsWith(\"http://\") || v.startsWith(\"https://\")));\n if (allAbsoluteUrls) {\n const isImage = validValues.some(v => typeof v === \"string\" && v.match(/\\.(jpeg|jpg|gif|png|webp|svg)/i));\n if (isImage || isMedia) {\n extraLines.push(\" url: true\");\n adminOptions.push(\"urlPreview: \\\"image\\\"\");\n } else {\n extraLines.push(\" url: true\");\n }\n } else {\n const hasFileExtension = validValues.some(v => typeof v === \"string\" && v.match(/\\.[a-zA-Z0-9]+$/));\n if (hasFileExtension) {\n const firstVal = validValues[0] as string;\n const lastSlash = firstVal.lastIndexOf(\"/\");\n const inferredStoragePath = lastSlash > 0 ? firstVal.substring(0, lastSlash) : \"files\";\n extraLines.push(` storage: {\\n storagePath: \"${inferredStoragePath}\"\\n }`);\n } else if (isUrl) {\n extraLines.push(\" url: true\");\n if (isMedia) adminOptions.push(\"urlPreview: \\\"image\\\"\");\n }\n }\n }\n\n if (emitAdmin && adminOptions.length > 0) {\n extraLines.push(` admin: {\\n${adminOptions.map((o) => ` ${o}`).join(\",\\n\")}\\n }`);\n }\n\n result.extra = extraLines.length > 0 ? \"\\n\" + extraLines.join(\",\\n\") + \",\" : \"\";\n return result;\n}\n","/**\n * The PostgreSQL type → Rebase property type mapping.\n *\n * Split out of `introspect-db-logic` so that the structural analysis can use it\n * without importing the generator, which imports the analysis. Re-exported from\n * `introspect-db-logic` so existing callers keep their import path.\n */\n\n/**\n * Map a PostgreSQL data type to a Rebase property type.\n */\nexport function mapPgType(dataType: string): string {\n const dt = dataType.toLowerCase();\n\n // Interval MUST be checked before numeric (\"interval\" contains \"int\")\n if (dt === \"interval\") return \"string\";\n\n // Array types MUST be checked before numeric (\"_int4\" contains \"int\")\n if (dt === \"array\" || dt.startsWith(\"_\")) return \"array\";\n\n // Numeric types\n if (\n dt.includes(\"int\") || // integer, smallint, bigint\n dt.includes(\"numeric\") ||\n dt.includes(\"decimal\") ||\n dt.includes(\"serial\") || // serial, bigserial\n dt === \"real\" ||\n dt === \"float4\" ||\n dt === \"float8\" ||\n dt === \"double precision\" ||\n dt === \"money\"\n ) {\n return \"number\";\n }\n\n // Boolean\n if (dt.includes(\"bool\")) return \"boolean\";\n\n // Date / Time\n if (dt.includes(\"time\") || dt.includes(\"date\")) return \"date\";\n\n // JSON\n if (dt === \"json\" || dt === \"jsonb\") return \"map\";\n\n // Binary\n if (dt === \"bytea\") return \"binary\";\n\n // Network types\n if (dt === \"inet\" || dt === \"cidr\" || dt === \"macaddr\" || dt === \"macaddr8\") return \"string\";\n\n // UUID\n if (dt === \"uuid\") return \"string\";\n\n // Text/varchar/char — default to string\n return \"string\";\n}\n","/**\n * What a schema's *structure* says about the app on top of it.\n *\n * Introspection has always been a table mirror: one table in, one collection\n * out, one nav entry each, every column a form field. A schema of thirty tables\n * produces thirty sidebar entries, and a panel whose navigation is a list of\n * table names reads as a database browser however good the fields are — which\n * is the actual complaint about generated admin panels, and is structural, not\n * cosmetic.\n *\n * Most of what separates the eight nouns a user navigates by from the thirty\n * tables underneath them is written down in the schema already: which tables\n * only exist to join two others, which are small referenced code lists, which\n * rows cannot outlive a parent row. This module reads that.\n *\n * ## Structure only\n *\n * Nothing here looks at a column or table *name*. Name heuristics — `status`,\n * `*_url`, `image`, `created_at` — are wrong exactly when a schema is not in\n * English, or is domain-specific, or spells things differently, and they are\n * wrong silently. Every rule below is a fact the database enforces: key\n * composition, foreign-key direction and delete rule, uniqueness, nullability,\n * declared type and length, generated-ness, row count.\n *\n * That constraint has a cost, and it is worth stating: a schema that declares\n * nothing beyond `NOT NULL` gives this module very little to work with, and it\n * returns `entity` for everything rather than guessing. Under-classifying is\n * the intended failure mode. A table wrongly hidden from the navigation is a\n * table the user cannot find; a table wrongly left in it is merely the status\n * quo.\n *\n * Pure module: no I/O. Row counts come in on {@link SchemaMetadata.rowCounts},\n * which the caller fills from {@link ./introspect-db-queries.countRowsUpTo} for\n * the tables {@link lookupCandidates} names.\n */\nimport type {\n ForeignKeyRow,\n SchemaMetadata,\n TableColumn,\n TableMeta,\n UniqueConstraintRow\n} from \"./introspect-db-logic\";\nimport { mapPgType } from \"./introspect-db-types\";\nimport type { CheckFactsByTable } from \"./introspect-db-constraints\";\n\n// ── Thresholds ────────────────────────────────────────────────────────\n\n/**\n * The row count above which a referenced table is a real entity rather than a\n * code list. Deliberately low: `pagila.category` has 16 rows and `language` 6,\n * while `actor` has 200 and `country` 109 — the gap between \"a fixed set\n * somebody typed once\" and \"data the app accumulates\" is wide, and picking a\n * number in the middle of it costs nothing.\n */\nexport const LOOKUP_MAX_ROWS = 50;\n\n/**\n * The most payload columns a code list may carry. A code list is an id, a\n * label, and perhaps a sort key or a flag; past that it is a table with\n * attributes, which is an entity.\n */\nexport const LOOKUP_MAX_PAYLOAD_COLUMNS = 3;\n\n/**\n * The most enum values a board can usefully have as columns. A kanban with\n * thirty columns is a horizontally scrolling table.\n */\nexport const KANBAN_MAX_VALUES = 12;\n\n/** Below this, a \"board\" is one or two columns — a filter, not a board. */\nexport const KANBAN_MIN_VALUES = 2;\n\n/**\n * How many columns a generated list view shows before it stops being readable.\n * Only applied when a table has more properties than this; a six-column table\n * gets no `listProperties` at all rather than a restatement of its own columns.\n */\nexport const LIST_PROPERTIES_CAP = 6;\n\n// ── Roles ─────────────────────────────────────────────────────────────\n\n/**\n * What a table *is*, structurally.\n *\n * - `entity` — a thing the app is about. Gets a collection and a nav entry.\n * - `junction` — exists only to relate two other tables. Gets no collection at\n * all; it becomes a many-to-many relation on both sides.\n * - `lookup` — a small, referenced, self-contained code list. Gets a collection,\n * grouped away from the entities rather than listed beside them.\n * - `owned-child` — rows that belong to exactly one parent row and are reached\n * through it. Gets a collection (it is a real table with real rows, and the\n * API still serves it) but no nav entry: it already renders as a tab on its\n * parent.\n */\nexport type TableRole = \"entity\" | \"junction\" | \"lookup\" | \"owned-child\";\n\n/**\n * Why a table was called someone's child, weakest last.\n *\n * Carried into the generated file as a comment. A reader who disagrees with the\n * classification needs to see what it was based on to know which line to change.\n */\nexport type OwnershipEvidence =\n /** The only foreign key declared `ON DELETE CASCADE`. */\n | \"cascade-delete\"\n /** The only foreign key that is part of the table's primary key. */\n | \"identifying-key\"\n /** The only foreign key that is `NOT NULL`. */\n | \"sole-required-key\"\n /** First column of a composite primary key made entirely of foreign keys. */\n | \"leading-key-column\";\n\nexport interface JunctionShape {\n sourceTable: string;\n sourceColumn: string;\n targetTable: string;\n targetColumn: string;\n}\n\nexport interface TableClassification {\n table: string;\n role: TableRole;\n /** One line, in prose, for the generated file. */\n reason: string;\n /** Set when `role === \"owned-child\"`. */\n owner?: { table: string; column: string; evidence: OwnershipEvidence };\n /** Set when `role === \"junction\"`. */\n junction?: JunctionShape;\n}\n\n// ── Column-level structural predicates ────────────────────────────────\n\n/**\n * A timestamp the database maintains: a temporal column defaulting to the\n * transaction clock.\n *\n * This is the structural stand-in for the `created_at`/`updated_at` name check.\n * It is strictly better than the name: it catches `fecha_creacion` and\n * `last_update` (pagila's spelling, which the name list misses), and it does not\n * fire on a user-editable `created_at date` column that has no default and which\n * the name check would wrongly make read-only.\n */\nexport function isAutoTimestamp(column: TableColumn): boolean {\n if (mapPgType(column.data_type) !== \"date\") return false;\n const columnDefault = (column.column_default ?? \"\").toLowerCase();\n if (!columnDefault) return false;\n return /\\b(now\\(\\)|current_timestamp|current_date|current_time|localtimestamp|localtime|transaction_timestamp\\(\\)|statement_timestamp\\(\\)|clock_timestamp\\(\\))/.test(columnDefault);\n}\n\n/** A key the database fills in: identity, serial, or a uuid-generating default. */\nexport function isGeneratedKey(column: TableColumn): boolean {\n if (column.is_identity === \"YES\") return true;\n const columnDefault = (column.column_default ?? \"\").toLowerCase();\n if (!columnDefault) return false;\n return columnDefault.includes(\"nextval(\") ||\n columnDefault.includes(\"gen_random_uuid\") ||\n columnDefault.includes(\"uuid_generate\");\n}\n\n/** A column Postgres computes; writing to it is an error. */\nexport function isGeneratedColumn(column: TableColumn): boolean {\n return column.is_generated === \"ALWAYS\";\n}\n\n/**\n * Types that exist to be searched or indexed, never to be typed into.\n *\n * A `tsvector` column is a derived search index — maintained by a trigger, a\n * generated expression, or an application job — and its contents are lexeme\n * positions, not text. Pagila's `film.fulltext` is one, and introspection used\n * to emit it as an ordinary required string: a mandatory form field whose\n * correct value no user can produce, on the sixth column of the list view.\n */\nexport function isDerivedIndexColumn(column: TableColumn): boolean {\n return column.udt_name === \"tsvector\" || column.udt_name === \"tsquery\";\n}\n\n/** Anything the user cannot meaningfully edit, whatever the reason. */\nexport function isReadOnlyColumn(column: TableColumn): boolean {\n return isGeneratedColumn(column) || isDerivedIndexColumn(column);\n}\n\n/**\n * A string column with a declared maximum length.\n *\n * `varchar(50)` and `text` are the same type to an application but not to the\n * author: choosing a bound is a statement that the value is short and\n * label-like, which is what makes this usable for picking a display column.\n */\nexport function isBoundedString(column: TableColumn): boolean {\n return mapPgType(column.data_type) === \"string\" &&\n typeof column.character_maximum_length === \"number\" &&\n column.character_maximum_length > 0;\n}\n\n/**\n * A column carrying data rather than structure: not a key, not a foreign key,\n * not a database-maintained timestamp, not computed.\n *\n * The count of these is what tells a pure join table from an association that\n * carries its own attributes — `northwind.order_details` has the key shape of a\n * junction and three payload columns, so it is not one.\n */\nexport function isPayloadColumn(column: TableColumn, pks: string[], fkColumns: Set<string>): boolean {\n if (pks.includes(column.column_name)) return false;\n if (fkColumns.has(column.column_name)) return false;\n if (isAutoTimestamp(column)) return false;\n if (isGeneratedColumn(column)) return false;\n return true;\n}\n\n// ── Foreign-key topology ──────────────────────────────────────────────\n\n/** One foreign key, with its columns grouped back together. */\nexport interface ForeignKeyConstraint {\n name: string;\n table: string;\n columns: string[];\n foreignTable: string;\n foreignColumns: string[];\n deleteRule?: string;\n}\n\n/**\n * Groups per-column foreign key rows back into constraints.\n *\n * Rows arrive one per referencing column. A composite key looks exactly like two\n * separate keys until they are grouped by constraint name, and the difference\n * matters: two single-column keys to two tables can be a junction, one\n * two-column key never is.\n */\nexport function groupForeignKeys(fks: ForeignKeyRow[]): ForeignKeyConstraint[] {\n const byName = new Map<string, ForeignKeyConstraint>();\n for (const fk of fks) {\n // Rows from older callers carry no constraint name. Falling back to a\n // per-column key treats each as its own single-column constraint, which\n // is what the generator assumed before names were available.\n const key = `${fk.table_name}::${fk.constraint_name ?? `${fk.column_name}->${fk.foreign_table_name}`}`;\n const existing = byName.get(key);\n if (existing) {\n existing.columns.push(fk.column_name);\n existing.foreignColumns.push(fk.foreign_column_name);\n continue;\n }\n byName.set(key, {\n name: fk.constraint_name ?? `${fk.table_name}_${fk.column_name}_fkey`,\n table: fk.table_name,\n columns: [fk.column_name],\n foreignTable: fk.foreign_table_name,\n foreignColumns: [fk.foreign_column_name],\n deleteRule: fk.delete_rule\n });\n }\n return Array.from(byName.values());\n}\n\n/** Column sets that are unique in a table: its primary key and every unique index. */\nfunction uniqueColumnSets(table: string, pks: string[], uniques: UniqueConstraintRow[]): string[][] {\n const sets: string[][] = [];\n if (pks.length > 0) sets.push(pks);\n for (const unique of uniques) {\n if (unique.table_name === table) sets.push(unique.column_names);\n }\n return sets;\n}\n\nfunction sameColumnSet(a: string[], b: string[]): boolean {\n if (a.length !== b.length) return false;\n const left = [...a].sort();\n const right = [...b].sort();\n return left.every((value, index) => value === right[index]);\n}\n\n// ── Classification ────────────────────────────────────────────────────\n\n/** Everything classification needs, assembled once per run. */\ninterface AnalysisContext {\n /** The schema the tables were read from. */\n schema: string;\n tables: Map<string, TableMeta>;\n constraintsByTable: Map<string, ForeignKeyConstraint[]>;\n inboundByTable: Map<string, ForeignKeyConstraint[]>;\n uniques: UniqueConstraintRow[];\n rowCounts: Record<string, number>;\n}\n\nfunction buildContext(metadata: SchemaMetadata, tables: Map<string, TableMeta>): AnalysisContext {\n const allConstraints = groupForeignKeys(metadata.fks);\n const constraintsByTable = new Map<string, ForeignKeyConstraint[]>();\n const inboundByTable = new Map<string, ForeignKeyConstraint[]>();\n\n for (const constraint of allConstraints) {\n const outbound = constraintsByTable.get(constraint.table) ?? [];\n outbound.push(constraint);\n constraintsByTable.set(constraint.table, outbound);\n\n const inbound = inboundByTable.get(constraint.foreignTable) ?? [];\n inbound.push(constraint);\n inboundByTable.set(constraint.foreignTable, inbound);\n }\n\n return {\n schema: metadata.schema,\n tables,\n constraintsByTable,\n inboundByTable,\n uniques: metadata.uniques,\n rowCounts: metadata.rowCounts\n };\n}\n\n/**\n * A table that exists only to relate two others.\n *\n * Requires all of:\n * - exactly two single-column foreign keys, to two distinct columns;\n * - that pair is unique — it is the primary key, or a unique index covers it.\n * Without this the table can hold the same pair twice, which is a list of\n * events between two things, not a set membership;\n * - no payload columns. A join row that carries a quantity is an association\n * with attributes, and collapsing it into a many-to-many silently drops those\n * attributes from the UI entirely;\n * - nothing references it. A junction with its own dependents is something rows\n * point *at*, so it needs an identity of its own;\n * - it is in `public`. That is the only schema Rebase reads and creates\n * junction tables in (`resolveJunctionSpecs`), so a `manyToMany` folded from\n * `crm.contacts_tags` would read and write an empty `public.contacts_tags`\n * instead. Kept as a table, its rows stay where they are and are served.\n */\nfunction classifyJunction(table: string, context: AnalysisContext): TableClassification | null {\n if (context.schema !== \"public\") return null;\n const meta = context.tables.get(table);\n if (!meta) return null;\n\n const constraints = context.constraintsByTable.get(table) ?? [];\n if (constraints.length !== 2) return null;\n if (constraints.some((c) => c.columns.length !== 1)) return null;\n\n const [first, second] = constraints;\n if (first.columns[0] === second.columns[0]) return null;\n\n if ((context.inboundByTable.get(table) ?? []).length > 0) return null;\n\n const pair = [first.columns[0], second.columns[0]];\n const uniqueSets = uniqueColumnSets(table, meta.pks, context.uniques);\n if (!uniqueSets.some((set) => sameColumnSet(set, pair))) return null;\n\n const fkColumns = new Set(pair);\n const payload = meta.columns.filter((column) => isPayloadColumn(column, meta.pks, fkColumns));\n if (payload.length > 0) return null;\n\n return {\n table,\n role: \"junction\",\n reason: `only relates ${first.foreignTable} to ${second.foreignTable}: two foreign keys, unique together, no other data`,\n junction: {\n sourceTable: first.foreignTable,\n sourceColumn: first.columns[0],\n targetTable: second.foreignTable,\n targetColumn: second.columns[0]\n }\n };\n}\n\n/**\n * A small, referenced, self-contained code list.\n *\n * Requires: something references it, it references nothing, it carries few and\n * simple payload columns, and it is small — where \"small\" is a real count, not\n * an estimate. `reltuples` is -1 on any table that has never been analyzed,\n * which is every table in a freshly restored dump, so a count that is merely\n * absent must not read as \"small\".\n */\nfunction classifyLookup(table: string, context: AnalysisContext): TableClassification | null {\n if (!isLookupCandidate(table, context)) return null;\n\n const rowCount = context.rowCounts[table];\n if (typeof rowCount !== \"number\") return null;\n // An empty table is not a code list — there is nothing to look up, and a\n // schema-only restore would otherwise classify half the database this way.\n if (rowCount < 1 || rowCount > LOOKUP_MAX_ROWS) return null;\n\n const referencedBy = (context.inboundByTable.get(table) ?? []).length;\n return {\n table,\n role: \"lookup\",\n reason: `a ${rowCount}-row code list: referenced by ${referencedBy} table(s), references none, ${LOOKUP_MAX_PAYLOAD_COLUMNS} or fewer simple columns`\n };\n}\n\n/**\n * The structural half of the lookup test — everything except the row count.\n *\n * Separate because the count costs a query per table, and the caller only wants\n * to pay it for tables that could possibly qualify.\n */\nfunction isLookupCandidate(table: string, context: AnalysisContext): boolean {\n const meta = context.tables.get(table);\n if (!meta) return false;\n\n if ((context.constraintsByTable.get(table) ?? []).length > 0) return false;\n if ((context.inboundByTable.get(table) ?? []).length === 0) return false;\n\n const payload = meta.columns.filter((column) => isPayloadColumn(column, meta.pks, new Set()));\n if (payload.length > LOOKUP_MAX_PAYLOAD_COLUMNS) return false;\n\n // A code list holds labels and flags. Documents, arrays, files and dates are\n // content, and content means the rows are worth browsing in their own right.\n //\n // Strings are allowed whether or not they declare a length. Requiring\n // `varchar(n)` looked like a way to say \"label, not prose\", and it is —\n // except that a schema written any time recently uses `text` for everything,\n // which is the advice Postgres itself gives. Modern pagila is all `text`, so\n // that rule classified nothing at all on it.\n return payload.every((column) => {\n const type = mapPgType(column.data_type);\n return type === \"boolean\" || type === \"number\" || type === \"string\";\n });\n}\n\n/**\n * Names the tables whose classification depends on a row count.\n *\n * The caller counts these — and only these — before calling\n * {@link classifyTables}. On a schema of any size this is a handful of tables,\n * and the count itself is capped (see `countRowsUpTo`), so the whole extra cost\n * is bounded regardless of how much data the database holds.\n */\nexport function lookupCandidates(metadata: SchemaMetadata, tables: Map<string, TableMeta>): string[] {\n const context = buildContext(metadata, tables);\n return Array.from(tables.keys()).filter((table) => isLookupCandidate(table, context));\n}\n\n/**\n * Rows that belong to one parent row and are reached through it.\n *\n * Requires nothing to reference the table — a table others point at has an\n * identity of its own and belongs in the navigation — and an *unambiguous*\n * owner among its foreign keys. The evidence ladder runs strongest first; each\n * rung is only taken when exactly one key satisfies it, so a table with two\n * equally plausible parents stays an entity rather than being filed under a\n * coin-flip.\n */\nfunction classifyOwnedChild(table: string, context: AnalysisContext): TableClassification | null {\n const meta = context.tables.get(table);\n if (!meta) return null;\n\n const constraints = context.constraintsByTable.get(table) ?? [];\n if (constraints.length === 0) return null;\n if ((context.inboundByTable.get(table) ?? []).length > 0) return null;\n\n const owner = pickOwner(meta, constraints);\n if (!owner) return null;\n\n return {\n table,\n role: \"owned-child\",\n reason: `belongs to ${owner.constraint.foreignTable} (${describeEvidence(owner.evidence)}) and nothing else references it`,\n owner: {\n table: owner.constraint.foreignTable,\n column: owner.constraint.columns[0],\n evidence: owner.evidence\n }\n };\n}\n\nfunction describeEvidence(evidence: OwnershipEvidence): string {\n switch (evidence) {\n case \"cascade-delete\": return \"its only ON DELETE CASCADE foreign key\";\n case \"identifying-key\": return \"its only foreign key inside the primary key\";\n case \"sole-required-key\": return \"its only NOT NULL foreign key\";\n case \"leading-key-column\": return \"the leading column of a primary key made only of foreign keys\";\n }\n}\n\nfunction pickOwner(\n meta: TableMeta,\n constraints: ForeignKeyConstraint[]\n): { constraint: ForeignKeyConstraint; evidence: OwnershipEvidence } | null {\n const singleColumn = constraints.filter((c) => c.columns.length === 1);\n if (singleColumn.length === 0) return null;\n\n const nullability = new Map(meta.columns.map((c) => [c.column_name, c.is_nullable === \"NO\"]));\n\n const cascading = singleColumn.filter((c) => c.deleteRule === \"CASCADE\");\n if (cascading.length === 1) return { constraint: cascading[0], evidence: \"cascade-delete\" };\n\n const identifying = singleColumn.filter((c) => meta.pks.includes(c.columns[0]));\n if (identifying.length === 1) return { constraint: identifying[0], evidence: \"identifying-key\" };\n\n const required = singleColumn.filter((c) => nullability.get(c.columns[0]) === true);\n if (required.length === 1) return { constraint: required[0], evidence: \"sole-required-key\" };\n\n // Last resort, and the only rung that leans on an ordering convention rather\n // than a guarantee: in a table whose whole primary key is foreign keys, the\n // first key column is the parent the rows are filed under.\n // `northwind.order_details` is keyed `(order_id, product_id)` — the rows are\n // lines of an order, which is exactly what that ordering says.\n if (identifying.length === meta.pks.length && meta.pks.length > 1) {\n const leading = identifying.find((c) => c.columns[0] === meta.pks[0]);\n if (leading) return { constraint: leading, evidence: \"leading-key-column\" };\n }\n\n return null;\n}\n\n/**\n * Classifies every table in the schema.\n *\n * Order matters: junction is the most specific and most consequential (the\n * table disappears), so it is tested first; then lookup, which needs no\n * ownership reasoning; then ownership. Anything unmatched is an entity, which\n * is also what every rule falls back to when its evidence is ambiguous.\n */\nexport function classifyTables(\n metadata: SchemaMetadata,\n tables: Map<string, TableMeta>\n): Map<string, TableClassification> {\n const context = buildContext(metadata, tables);\n const result = new Map<string, TableClassification>();\n\n for (const table of tables.keys()) {\n const classification =\n classifyJunction(table, context) ??\n classifyLookup(table, context) ??\n classifyOwnedChild(table, context) ?? {\n table,\n role: \"entity\" as const,\n reason: \"no structural evidence that it is a join table, a code list, or owned by another table\"\n };\n result.set(table, classification);\n }\n\n return result;\n}\n\n// ── Presentation derived from structure ───────────────────────────────\n\n/**\n * The columns a property-level derivation needs, resolved once.\n */\nexport interface ColumnFacts {\n column: TableColumn;\n isPk: boolean;\n isFk: boolean;\n /** Covered by a single-column unique constraint or unique index. */\n isUniqueAlone: boolean;\n isAutoTimestamp: boolean;\n isGenerated: boolean;\n /** Allowed values, from a Postgres enum type or a readable CHECK. */\n enumValues?: string[];\n propType: string;\n}\n\nexport function buildColumnFacts(\n meta: TableMeta,\n metadata: SchemaMetadata,\n enumMap: Map<string, string[]>,\n checkFacts: CheckFactsByTable\n): Map<string, ColumnFacts> {\n const fkColumns = new Set(meta.fks.map((fk) => fk.column_name));\n const singleColumnUniques = new Set(\n metadata.uniques\n .filter((u) => u.table_name === meta.name && u.column_names.length === 1)\n .map((u) => u.column_names[0])\n );\n const tableChecks = checkFacts.get(meta.name);\n\n const facts = new Map<string, ColumnFacts>();\n for (const column of meta.columns) {\n const pgEnum = column.data_type === \"USER-DEFINED\" ? enumMap.get(column.udt_name) : undefined;\n const checkEnum = tableChecks?.get(column.column_name)?.enumValues;\n facts.set(column.column_name, {\n column,\n isPk: meta.pks.includes(column.column_name),\n isFk: fkColumns.has(column.column_name),\n isUniqueAlone: singleColumnUniques.has(column.column_name),\n isAutoTimestamp: isAutoTimestamp(column),\n isGenerated: isGeneratedColumn(column),\n enumValues: pgEnum ?? checkEnum,\n propType: pgEnum ? \"string\" : mapPgType(column.data_type)\n });\n }\n return facts;\n}\n\n/**\n * The column that identifies a row to a human.\n *\n * Structural, in three rungs, strongest first:\n *\n * 1. A single-column unique constraint on a required string. This is as close\n * as a schema comes to declaring \"this is what a row is called\": it is the\n * column a person looks a row up by, and the database guarantees it picks\n * out one row.\n * 2. The first required string that declares a length, when the table also has\n * strings that do not. Choosing `varchar(n)` for one column and `text` for\n * another is the author distinguishing a label from prose.\n * 3. The first required string in declaration order. Weak, but it is the same\n * rung the panel's own fallback stands on, and column order carries real\n * information — the identifying column of a table is written near the top of\n * it, in every schema, in every language.\n *\n * Deliberately not: a column called `name`, or `title`. That works on English\n * schemas written by someone who read the same tutorial. This picks\n * `film.title`, `actor.first_name` and `category.name` out of pagila without\n * knowing what any of those words mean.\n */\nexport function deriveTitleProperty(facts: Map<string, ColumnFacts>): string | undefined {\n const candidates = Array.from(facts.values()).filter((f) =>\n !f.isPk && !f.isFk && !f.isGenerated &&\n f.propType === \"string\" &&\n !f.enumValues &&\n f.column.is_nullable === \"NO\"\n );\n if (candidates.length === 0) return undefined;\n\n const unique = candidates.find((f) => f.isUniqueAlone);\n if (unique) return unique.column.column_name;\n\n const bounded = candidates.filter((f) => isBoundedString(f.column));\n if (bounded.length > 0 && bounded.length < candidates.length) {\n return bounded[0].column.column_name;\n }\n\n return candidates[0].column.column_name;\n}\n\n/**\n * The enum column a board should have as its columns.\n *\n * A board needs a small, closed, always-present set of states. `NOT NULL` is\n * required because a null has no column to sit in; the bounds keep out\n * two-state flags (a filter, not a board) and long code lists (a scrolling\n * table). The first qualifying column in declaration order wins, so the output\n * is stable across runs.\n */\nexport function deriveKanbanProperty(facts: Map<string, ColumnFacts>): string | undefined {\n for (const fact of facts.values()) {\n if (fact.isPk || fact.isFk || fact.isGenerated) continue;\n if (!fact.enumValues) continue;\n if (fact.column.is_nullable !== \"NO\") continue;\n if (fact.enumValues.length < KANBAN_MIN_VALUES || fact.enumValues.length > KANBAN_MAX_VALUES) continue;\n return fact.column.column_name;\n }\n return undefined;\n}\n\n/**\n * The column a list should be sorted by, newest first.\n *\n * Only when the table has exactly one database-maintained timestamp. With two —\n * a created and an updated stamp — the two orderings differ and the schema does\n * not say which the user means, so neither is chosen.\n */\nexport function deriveSort(facts: Map<string, ColumnFacts>): [string, \"desc\"] | undefined {\n const stamps = Array.from(facts.values()).filter((f) => f.isAutoTimestamp);\n if (stamps.length !== 1) return undefined;\n return [stamps[0].column.column_name, \"desc\"];\n}\n\n/**\n * The first `LIST_PROPERTIES_CAP` visible properties, or nothing.\n *\n * Returning nothing when the table is already narrow matters: `listProperties`\n * that restates every column is config the reader has to check against the\n * property list to discover it does nothing, and it silently stops new columns\n * from appearing in the list view when someone adds one later.\n *\n * `hidden` names the properties already marked `hideFromCollection` — spending\n * one of six columns on a value the list does not render is worse than not\n * capping at all.\n */\nexport function deriveListProperties(propertiesOrder: string[], hidden: ReadonlySet<string> = new Set()): string[] | undefined {\n const visible = propertiesOrder.filter((key) => !hidden.has(key));\n if (visible.length <= LIST_PROPERTIES_CAP) return undefined;\n return visible.slice(0, LIST_PROPERTIES_CAP);\n}\n","/**\n * Introspection logic — pure functions and the pipeline that transforms\n * raw PostgreSQL metadata into Rebase collection definition files.\n *\n * This module contains NO side-effects: no fs writes, no pg.Client creation,\n * no process.exit. It is imported by introspect-db.ts (the CLI entry-point)\n * and consumed directly by tests.\n */\nimport { firstFreeKey, toWireKey } from \"@rebasepro/utils\";\nimport { inferPropertyFromData } from \"./introspect-db-inference\";\nimport { humanize } from \"./introspect-db-naming\";\nimport { mapPgType } from \"./introspect-db-types\";\nimport type { CheckFactsByTable } from \"./introspect-db-constraints\";\nimport type { TableClassification } from \"./introspect-db-structure\";\nimport {\n buildColumnFacts,\n deriveKanbanProperty,\n deriveListProperties,\n deriveSort,\n deriveTitleProperty,\n isDerivedIndexColumn,\n isReadOnlyColumn\n} from \"./introspect-db-structure\";\n\n// ── Typed interfaces for SQL query results ────────────────────────────\n\nexport interface TableRow {\n table_name: string;\n /** True for the parent of a partitioned table (`relkind = 'p'`). */\n is_partitioned?: boolean;\n}\n\nexport interface TableColumn {\n table_name: string;\n column_name: string;\n data_type: string;\n udt_name: string;\n is_nullable: string;\n column_default: string | null;\n atttypmod: number | null;\n /** 1-based position in the table, as declared. */\n ordinal_position?: number;\n /** `\"ALWAYS\"` for a generated column, `\"NEVER\"` otherwise. */\n is_generated?: string;\n /** The `GENERATED ALWAYS AS (…)` expression, when `is_generated` says so. */\n generation_expression?: string | null;\n /** `\"YES\"` for an identity column. */\n is_identity?: string;\n /** `\"ALWAYS\"` or `\"BY DEFAULT\"` on an identity column. */\n identity_generation?: string | null;\n /** The declared `varchar(n)` / `char(n)` bound, if any. */\n character_maximum_length?: number | null;\n numeric_precision?: number | null;\n numeric_scale?: number | null;\n}\n\nexport interface EnumValue {\n enum_name: string;\n enum_value: string;\n sort_order: number;\n}\n\nexport interface PrimaryKeyRow {\n table_name: string;\n column_name: string;\n}\n\nexport interface ForeignKeyRow {\n table_name: string;\n column_name: string;\n foreign_table_name: string;\n foreign_column_name: string;\n /** Name of the FK constraint — the only way to tell composite keys apart. */\n constraint_name?: string;\n /** 1-based position of this column within its constraint. */\n ordinal?: number;\n /** `\"CASCADE\"`, `\"RESTRICT\"`, `\"SET NULL\"`, `\"SET DEFAULT\"`, `\"NO ACTION\"`. */\n delete_rule?: string;\n}\n\n/** A unique constraint or unique index, as an ordered column list. */\nexport interface UniqueConstraintRow {\n table_name: string;\n constraint_name: string;\n column_names: string[];\n}\n\n/** A CHECK constraint, as `pg_get_constraintdef` renders it. */\nexport interface CheckConstraintRow {\n table_name: string;\n constraint_name: string;\n definition: string;\n}\n\n/** A `COMMENT ON TABLE` (null `column_name`) or `COMMENT ON COLUMN`. */\nexport interface CommentRow {\n table_name: string;\n column_name: string | null;\n comment: string;\n}\n\n/**\n * Everything one introspection run reads from the database.\n *\n * Passed around as one value so a new signal means a new field here rather than\n * a new parameter on every function between the query and the generator — the\n * shape `generateCollectionFile` had grown to seven positional arguments by.\n */\nexport interface SchemaMetadata {\n schema: string;\n tables: TableRow[];\n columns: TableColumn[];\n enumValues: EnumValue[];\n pks: PrimaryKeyRow[];\n fks: ForeignKeyRow[];\n uniques: UniqueConstraintRow[];\n checks: CheckConstraintRow[];\n comments: CommentRow[];\n /**\n * Row counts for the tables that needed one, capped — see `countRowsUpTo`.\n * Absent for every table introspection never had a reason to count.\n */\n rowCounts: Record<string, number>;\n}\n\nexport interface TableMeta {\n name: string;\n columns: TableColumn[];\n pks: string[];\n fks: ForeignKeyRow[];\n}\n\n// ── Irregular plurals that naive rules can't handle ───────────────────\n\nconst IRREGULAR_SINGULARS: Record<string, string> = {\n people: \"person\",\n children: \"child\",\n men: \"man\",\n women: \"woman\",\n mice: \"mouse\",\n geese: \"goose\",\n teeth: \"tooth\",\n feet: \"foot\",\n data: \"datum\",\n media: \"medium\",\n criteria: \"criterion\",\n phenomena: \"phenomenon\"\n};\n\n/**\n * Plurals in \"-ves\" whose singular really ends in f/fe, and which of the two.\n *\n * A blanket \"-ves\" -> \"-f\" rule gets `knives` -> `knif`, and mangles every\n * ordinary \"-ive\" noun that happens to be plural along with it: `archives` ->\n * `archif`, `objectives` -> `objectif`. The set of English words that genuinely\n * swap f/fe for ves is small and closed, so it is listed rather than guessed —\n * anything else ending in \"ves\" drops the trailing 's' like any other plural.\n *\n * Matched on the whole word, not on the suffix: `olives` ends in `lives`.\n */\nconst VES_SINGULAR_ENDINGS: Record<string, \"f\" | \"fe\"> = {\n calves: \"f\", dwarves: \"f\", elves: \"f\", halves: \"f\", hooves: \"f\",\n leaves: \"f\", loaves: \"f\", scarves: \"f\", selves: \"f\", sheaves: \"f\",\n shelves: \"f\", thieves: \"f\", wharves: \"f\", wolves: \"f\",\n knives: \"fe\", lives: \"fe\", wives: \"fe\"\n};\n\n/** Words ending in 's' that are already singular. */\nconst UNCOUNTABLE = new Set([\n \"status\", \"campus\", \"virus\", \"bus\", \"plus\", \"census\",\n \"diagnosis\", \"analysis\", \"basis\", \"crisis\", \"thesis\",\n \"synopsis\", \"parenthesis\", \"hypothesis\", \"emphasis\",\n \"news\", \"series\", \"species\", \"means\", \"athletics\",\n \"economics\", \"electronics\", \"mathematics\", \"physics\",\n \"politics\", \"statistics\"\n]);\n\nexport function singularize(word: string): string {\n const lower = word.toLowerCase();\n\n // Check irregular forms\n if (IRREGULAR_SINGULARS[lower]) {\n // Preserve the original casing of the first character\n const singular = IRREGULAR_SINGULARS[lower];\n return word[0] === word[0].toUpperCase()\n ? singular.charAt(0).toUpperCase() + singular.slice(1)\n : singular;\n }\n\n // Check uncountable\n if (UNCOUNTABLE.has(lower)) return word;\n\n // Latin/Greek -es endings (diagnosis -> diagnosis is uncountable, but \"addresses\" -> \"address\")\n if (lower.endsWith(\"ices\") && lower.length > 5) {\n // e.g. \"indices\" -> \"index\", \"vertices\" -> \"vertex\"\n return word.slice(0, -4) + \"ex\";\n }\n if (lower.endsWith(\"ies\") && lower.length > 3) {\n return word.slice(0, -3) + \"y\";\n }\n if (VES_SINGULAR_ENDINGS[lower]) {\n // e.g. \"wolves\" -> \"wolf\", \"knives\" -> \"knife\"\n return word.slice(0, -3) + VES_SINGULAR_ENDINGS[lower];\n }\n if (lower.endsWith(\"ches\") || lower.endsWith(\"shes\") || lower.endsWith(\"sses\") || lower.endsWith(\"xes\") || lower.endsWith(\"zes\")) {\n return word.slice(0, -2);\n }\n if (lower.endsWith(\"ses\") && !lower.endsWith(\"sses\")) {\n // e.g. \"responses\" -> \"response\", \"databases\" -> \"database\"\n return word.slice(0, -1);\n }\n if (lower.endsWith(\"s\") && !lower.endsWith(\"ss\") && !lower.endsWith(\"us\") && !lower.endsWith(\"is\")) {\n return word.slice(0, -1);\n }\n\n return word;\n}\n\n/**\n * Convert a snake_case table name to a camelCase + \"Collection\" variable name.\n * e.g. \"company_token\" -> \"companyTokenCollection\"\n */\nexport function toCollectionVarName(tableName: string): string {\n const camel = tableName.replace(/_([a-z])/g, (_g, letter: string) => letter.toUpperCase()) + \"Collection\";\n // Only reshapes names that are not identifiers already, so every table that\n // generated a working file keeps the exact variable name it had. A table\n // called `2024 archive` used to emit `const 2024 archiveCollection`, which\n // is three syntax errors rather than a declaration.\n if (JS_IDENTIFIER.test(camel)) return camel;\n const sanitized = camel.replace(/[^A-Za-z0-9_$]/g, \"_\");\n return /^[0-9]/.test(sanitized) ? `_${sanitized}` : sanitized;\n}\n\nexport function getIconForTable(tableName: string): string {\n const table = tableName.toLowerCase();\n if (table.includes(\"user\") || table.includes(\"account\") || table.includes(\"member\") || table.includes(\"customer\") || table.includes(\"client\") || table.includes(\"patient\")) return \"Users\";\n if (table.includes(\"post\") || table.includes(\"article\") || table.includes(\"blog\") || table.includes(\"page\")) return \"FileText\";\n if (table.includes(\"product\") || table.includes(\"item\")) return \"Package\";\n if (table.includes(\"order\") || table.includes(\"cart\") || table.includes(\"purchase\") || table.includes(\"invoice\")) return \"ShoppingCart\";\n if (table.includes(\"setting\") || table.includes(\"config\")) return \"Settings\";\n if (table.includes(\"tag\") || table.includes(\"categor\")) return \"Tag\";\n if (table.includes(\"image\") || table.includes(\"photo\") || table.includes(\"media\") || table.includes(\"asset\")) return \"Image\";\n if (table.includes(\"notification\") || table.includes(\"message\") || table.includes(\"email\")) return \"Mail\";\n if (table.includes(\"log\") || table.includes(\"audit\") || table.includes(\"event\")) return \"Activity\";\n if (table.includes(\"subscription\") || table.includes(\"plan\") || table.includes(\"billing\")) return \"CreditCard\";\n if (table.includes(\"comment\") || table.includes(\"review\") || table.includes(\"feedback\")) return \"MessageCircle\";\n return \"Database\";\n}\n\nexport { mapPgType };\n\n// ── Build the enum map from query results ─────────────────────────────\n\nexport function buildEnumMap(enumValues: EnumValue[]): Map<string, string[]> {\n const enumMap = new Map<string, string[]>();\n for (const ev of enumValues) {\n const existing = enumMap.get(ev.enum_name);\n if (existing) {\n existing.push(ev.enum_value);\n } else {\n enumMap.set(ev.enum_name, [ev.enum_value]);\n }\n }\n return enumMap;\n}\n\n// ── Build the tables map from raw query results ───────────────────────\n\nexport function buildTablesMap(\n tables: TableRow[],\n columns: TableColumn[],\n pks: PrimaryKeyRow[],\n fks: ForeignKeyRow[]\n): Map<string, TableMeta> {\n const tablesMap = new Map<string, TableMeta>();\n for (const t of tables) {\n tablesMap.set(t.table_name, {\n name: t.table_name,\n columns: columns.filter((c) => c.table_name === t.table_name),\n pks: pks.filter((pk) => pk.table_name === t.table_name).map((pk) => pk.column_name),\n fks: fks.filter((fk) => fk.table_name === t.table_name)\n });\n }\n return tablesMap;\n}\n\n// ── Identify join tables ──────────────────────────────────────────────\n\n/**\n * Join tables, identified by column name.\n *\n * Superseded for the CLI by `classifyTables` in `./introspect-db-structure`,\n * which asks the database instead: two single-column keys, unique together, no\n * payload column, nothing referencing the table. This rule folds away\n * `northwind.order_details` — which has the key shape and carries unit price,\n * quantity and discount — because it recognises `id`, `created_at` and\n * `updated_at` by name and calls everything else a foreign key.\n *\n * Still used by `./introspect-runtime`, which builds collections in memory from\n * a narrower set of catalog queries and has no unique-constraint or row-count\n * data to reason with.\n */\nexport function identifyJoinTables(tablesMap: Map<string, TableMeta>): Set<string> {\n const joinTables = new Set<string>();\n for (const [tableName, meta] of tablesMap.entries()) {\n if (meta.fks.length === 2) {\n const isLikelyJoinTable = meta.columns.every((c) =>\n meta.fks.some((fk) => fk.column_name === c.column_name) ||\n c.column_name === \"id\" ||\n c.column_name === \"created_at\" ||\n c.column_name === \"updated_at\"\n );\n\n if (isLikelyJoinTable) {\n joinTables.add(tableName);\n }\n }\n }\n return joinTables;\n}\n\n// ── Property ordering heuristics ──────────────────────────────────────\n\n/**\n * Property metadata used to compute display priority.\n * Keeps computePropertyPriority free of any TableMeta coupling.\n */\nexport interface PropertyOrderingContext {\n /** The resolved Rebase property type (e.g. \"string\", \"number\", \"date\", \"relation\"). */\n propType: string;\n /** Whether this column is a primary key. */\n isPk: boolean;\n /** Whether this column is an enum (USER-DEFINED with matching values). */\n isEnum: boolean;\n /** Whether this is a storage/file-upload field (detected from column name). */\n isStorage: boolean;\n /** The PostgreSQL data_type (e.g. \"text\", \"character varying\", \"jsonb\"). */\n pgDataType: string;\n /** The original column index in PostgreSQL (for stable tiebreaking). */\n originalIndex: number;\n}\n\n// — Tier 0: Identity (0–9) ————————————————————————————————————————————\nconst IDENTITY_EXACT: Record<string, number> = {\n id: 0,\n uuid: 1,\n _id: 2\n};\n\n// — Tier 1: Title / Name — the \"display column\" (10–19) ———————————————\nconst TITLE_EXACT: Record<string, number> = {\n name: 10,\n title: 11,\n label: 12,\n display_name: 13,\n displayname: 13,\n headline: 14,\n subject: 15,\n heading: 16\n};\n\n// — Tier 2: Human identity fields (20–29) —————————————————————————————\nconst HUMAN_IDENTITY_EXACT: Record<string, number> = {\n first_name: 20,\n firstname: 20,\n last_name: 21,\n lastname: 21,\n full_name: 22,\n fullname: 22,\n given_name: 22,\n family_name: 23,\n middle_name: 24,\n username: 25,\n user_name: 25,\n email: 26,\n email_address: 26,\n phone: 27,\n phone_number: 27,\n mobile: 27\n};\n\n// — Tier 3: Core descriptors (30–39) ——————————————————————————————————\nconst DESCRIPTOR_EXACT: Record<string, number> = {\n slug: 30,\n code: 31,\n sku: 32,\n reference: 33,\n ref: 33,\n type: 34,\n kind: 34,\n status: 35,\n state: 35,\n role: 36,\n category: 37,\n group: 38,\n priority: 39,\n order: 39,\n sort_order: 39,\n position: 39\n};\n\n// — Tier 12: System timestamps (120–129) ——————————————————————————————\nconst SYSTEM_TIMESTAMP_EXACT: Record<string, number> = {\n created_at: 120,\n createdat: 120,\n creation_date: 120,\n inserted_at: 121,\n updated_at: 122,\n updatedat: 122,\n modified_at: 122,\n last_modified: 122,\n deleted_at: 123,\n deletedat: 123,\n archived_at: 124\n};\n\n// — Pattern-based rules for partial matches ———————————————————————————\nconst TITLE_PATTERNS = [\"name\", \"title\", \"label\"];\nconst LONG_TEXT_NAMES = new Set([\"description\", \"summary\", \"excerpt\", \"abstract\", \"overview\", \"bio\", \"biography\", \"about\"]);\nconst RICH_CONTENT_NAMES = new Set([\"content\", \"body\", \"html\", \"markup\", \"text\", \"article_body\", \"post_body\"]);\nconst MEDIA_PATTERNS = [\"image\", \"avatar\", \"photo\", \"logo\", \"cover\", \"thumbnail\", \"banner\", \"icon\", \"picture\", \"poster\"];\nconst JSON_MAP_NAMES = new Set([\"metadata\", \"meta\", \"config\", \"configuration\", \"settings\", \"options\", \"preferences\", \"data\", \"payload\", \"attributes\", \"extra\", \"additional_info\"]);\n\n/**\n * Compute a numeric priority score for a property.\n * Lower scores appear first in the generated `propertiesOrder` array.\n *\n * The system uses 14 tiers (0–139), with the original column index\n * added as a fractional tiebreaker (originalIndex / 10000) to\n * guarantee stable ordering within the same tier.\n *\n * Pure function — no side effects.\n */\nexport function computePropertyPriority(\n columnName: string,\n ctx: PropertyOrderingContext\n): number {\n // Normalize camelCase/PascalCase to snake_case, then lowercase\n const col = columnName.replace(/([a-z0-9])([A-Z])/g, \"$1_$2\").toLowerCase();\n const tiebreaker = ctx.originalIndex / 10000;\n\n // ── Tier 0: Primary key identity fields\n if (ctx.isPk) {\n const exactScore = IDENTITY_EXACT[col];\n return (exactScore ?? 5) + tiebreaker;\n }\n\n // ── Tier 12: System timestamps (check early to prevent false matches)\n const systemTs = SYSTEM_TIMESTAMP_EXACT[col];\n if (systemTs !== undefined) {\n return systemTs + tiebreaker;\n }\n\n // ── Tier 1: Title / Name exact matches\n const titleExact = TITLE_EXACT[col];\n if (titleExact !== undefined) {\n return titleExact + tiebreaker;\n }\n\n // ── Tier 2: Human identity exact matches\n const humanExact = HUMAN_IDENTITY_EXACT[col];\n if (humanExact !== undefined) {\n return humanExact + tiebreaker;\n }\n\n // ── Tier 3: Core descriptor exact matches\n const descriptorExact = DESCRIPTOR_EXACT[col];\n if (descriptorExact !== undefined) {\n return descriptorExact + tiebreaker;\n }\n\n // ── Tier 1b: Title-like partial matches (e.g. \"product_name\", \"page_title\")\n // Score 17–19 so they rank after exact matches but still in tier 1.\n for (const pattern of TITLE_PATTERNS) {\n if (col.includes(pattern) && col !== pattern) {\n return 17 + tiebreaker;\n }\n }\n\n // ── Tier 9: Media / file upload fields (check before general strings)\n if (ctx.isStorage) {\n return 90 + tiebreaker;\n }\n for (const pattern of MEDIA_PATTERNS) {\n if (col.includes(pattern)) {\n return 91 + tiebreaker;\n }\n }\n if (col.endsWith(\"_url\") || col.endsWith(\"_uri\") || col.endsWith(\"_link\")) {\n return 92 + tiebreaker;\n }\n\n // ── Tier 7: Long text fields\n if (LONG_TEXT_NAMES.has(col)) {\n return 70 + tiebreaker;\n }\n\n // ── Tier 8: Rich content fields\n if (RICH_CONTENT_NAMES.has(col)) {\n return 80 + tiebreaker;\n }\n\n // ── Tier 10: JSON / Map types\n if (ctx.propType === \"map\") {\n return JSON_MAP_NAMES.has(col) ? 100 + tiebreaker : 105 + tiebreaker;\n }\n\n // ── Tier 11: Array types\n if (ctx.propType === \"array\") {\n return 110 + tiebreaker;\n }\n\n // ── Tier 6: Owning relations\n if (ctx.propType === \"relation\") {\n return 60 + tiebreaker;\n }\n\n // ── Tier 4: Short text, enums, booleans — \"quick glance\" fields\n if (ctx.isEnum) {\n return 40 + tiebreaker;\n }\n if (ctx.propType === \"boolean\") {\n return 45 + tiebreaker;\n }\n if (ctx.propType === \"string\" && ctx.pgDataType !== \"text\") {\n // Short string (varchar, char, uuid that's not a PK)\n return 42 + tiebreaker;\n }\n\n // ── Tier 5: Numbers & user-facing dates\n if (ctx.propType === \"number\") {\n return 50 + tiebreaker;\n }\n if (ctx.propType === \"date\") {\n // A date that isn't a system timestamp (already handled above)\n return 55 + tiebreaker;\n }\n\n // ── Tier 7b: text data_type that didn't match long-text names\n if (ctx.propType === \"string\" && ctx.pgDataType === \"text\") {\n return 75 + tiebreaker;\n }\n\n // ── Tier 13: Fallback / unknown\n return 130 + tiebreaker;\n}\n\n/**\n * Sort a `propertiesOrder` array using the priority heuristic.\n * Returns a new sorted array; does not mutate the input.\n *\n * @param entries - Array of { key, columnName, propType, ... } objects\n * carrying the information needed to compute priority.\n */\nexport interface PropertyOrderEntry {\n /** The property key in the generated collection (may differ from columnName for relations). */\n key: string;\n /** The ordering context for this property. */\n ctx: PropertyOrderingContext;\n}\n\nexport function sortPropertiesOrder(entries: PropertyOrderEntry[]): string[] {\n return [...entries]\n .sort((a, b) => computePropertyPriority(a.key, a.ctx) - computePropertyPriority(b.key, b.ctx))\n .map((e) => e.key);\n}\n\n// ── Generate collection file content ──────────────────────────────────\n\nexport interface GeneratedFile {\n tableName: string;\n fileName: string;\n content: string;\n}\n\n/**\n * The structural analysis a run can hand the generator.\n *\n * Optional in full, and the generator degrades to exactly its previous output\n * without it. That is not politeness towards old callers: three existing test\n * suites and the `rebase init` scaffold path build a `TableMeta` by hand and\n * have no database to read constraints or row counts from, and they must keep\n * producing a valid collection.\n */\n/**\n * Which `defineCollection` — if any — the project being generated into can import.\n *\n * A bare `const x: PostgresCollectionConfig = { … }` annotation widens `properties`\n * to `Record<string, …>`, and every key-shaped field in the admin block —\n * `titleProperty`, `sort`, `propertiesOrder`, `listProperties`, `fixedFilter` — is\n * derived from those keys. Annotated, they accept any string: introspection was\n * emitting a `propertiesOrder` array that nothing checked, so renaming a column and\n * re-introspecting left a stale key that compiled silently. `defineCollection` is\n * the identity function whose `const P` type parameter keeps the keys literal, which\n * is what turns that checking on.\n *\n * There are two of them and they are not interchangeable:\n *\n * - `admin-types` — `@rebasepro/cms-types`. Its index side-effect-imports\n * `augment.ts`, so importing it is also what *declares* the `admin` block. Only a\n * project that depends on the package can resolve it.\n * - `common` — `@rebasepro/common`. Same key inference, no admin surface, no React\n * anywhere in its graph (`scripts/headless-guard` lists it as core). This is the\n * headless flavour.\n * - `annotation` — neither package is declared, so neither import would resolve and\n * the old annotation is the only honest thing to emit. Projects scaffolded before\n * `@rebasepro/common` joined the headless config package land here.\n *\n * The last two emit **no admin block, on the collection or on any property**. That is\n * not a downgrade: `@rebasepro/types` declares no `admin` field at all, so the block\n * introspection used to emit was a type error in every headless project it was\n * written into. See `packages/cms-types/src/augment.ts`.\n */\nexport type CollectionBuilder = \"admin-types\" | \"common\" | \"annotation\";\n\n/**\n * The package specifiers the generated files name, spelled once.\n *\n * Written as constants rather than inline in the import templates below because\n * `scripts/headless-guard/check-types.mjs` scans core sources for `from\n * \"@rebasepro/cms-types\"` and cannot tell a real import from one this module\n * *writes*. It is right to be that blunt — the guard's whole value is that it\n * cannot be reasoned around — so the string simply never appears in that shape\n * here. Inlining them back into the templates re-breaks `check:types-headless`.\n */\nexport const ADMIN_TYPES_PACKAGE = \"@rebasepro/cms-types\";\nexport const COMMON_PACKAGE = \"@rebasepro/common\";\nexport const TYPES_PACKAGE = \"@rebasepro/types\";\n\nexport interface GenerationContext {\n metadata?: SchemaMetadata;\n classifications?: Map<string, TableClassification>;\n checkFacts?: CheckFactsByTable;\n /**\n * Defaults to `admin-types`, which is what the generator has always emitted.\n * The CLI never relies on the default — `introspect-db.ts` detects the flavour\n * from the target project and passes it. See `detectCollectionBuilder`.\n */\n builder?: CollectionBuilder;\n}\n\n/** Adds entries to a property's `validation` block, creating it if absent. */\nfunction withValidation(extra: string, entries: string[]): string {\n if (entries.length === 0) return extra;\n const block = entries.map((e) => ` ${e}`).join(\",\\n\");\n if (extra.includes(\"validation: {\")) {\n return extra.replace(\"validation: {\", `validation: {\\n${block},`);\n }\n return `${extra}\\n validation: {\\n${block}\\n },`;\n}\n\n/**\n * Whether a property's generated text already sets `key:` as an object key.\n *\n * A bare `extra.includes(\"min:\")` looks like it answers this and does not:\n * `admin:` ends in `min:`, so every property with an admin block claimed to\n * have a minimum already and silently lost the one the database declared. The\n * leading-delimiter requirement is the whole point — a key is preceded by a\n * newline, a brace or a comma, never by another identifier character.\n */\nfunction hasGeneratedKey(extra: string, key: string): boolean {\n return new RegExp(`(^|[\\\\s{,])${key}\\\\s*:`).test(extra);\n}\n\n/**\n * Adds entries to a property's `admin` block, creating it if absent.\n *\n * `emitAdmin` is false for the headless flavours, where `BaseProperty` has no\n * `admin` field to put them in — see {@link CollectionBuilder}. The options are\n * dropped rather than relocated: every one of them (`readOnly`, `multiline`,\n * `hideFromCollection`, `urlPreview`) describes a form widget, and there is no\n * form.\n */\nfunction withAdminOptions(extra: string, entries: string[], emitAdmin = true): string {\n if (!emitAdmin) return extra;\n if (entries.length === 0) return extra;\n const block = entries.map((e) => ` ${e}`).join(\",\\n\");\n if (extra.includes(\"admin: {\")) {\n return extra.replace(\"admin: {\", `admin: {\\n${block},`);\n }\n return `${extra}\\n admin: {\\n${block}\\n },`;\n}\n\n/** A TypeScript string literal, escaped. */\nfunction quote(value: string): string {\n return JSON.stringify(value);\n}\n\nconst JS_IDENTIFIER = /^[A-Za-z_$][A-Za-z0-9_$]*$/;\n\n/**\n * An object key for the generated file: verbatim when the name is a JavaScript\n * identifier, quoted otherwise.\n *\n * Postgres constrains an identifier only by quoting, so `order`, `full name`\n * and `2fa_enabled` are all ordinary column names — and all three produced a\n * file that did not parse when written as a bare key.\n *\n * Still needed now that keys are camel-cased rather than copied from the\n * column: `toWireKey` splits on separators and joins, which fixes `full name`\n * but cannot fix a name that is not an identifier for some other reason —\n * `2fa_enabled` becomes `2faEnabled`, still leading with a digit, and `order`\n * was never a separator problem at all.\n */\nfunction propKey(name: string): string {\n return JS_IDENTIFIER.test(name) ? name : quote(name);\n}\n\n/**\n * Text safe to put after `//`.\n *\n * A table comment or a classification reason is free text out of the database.\n * A newline in one ended the comment and let the rest of the value continue as\n * code.\n */\nfunction commentText(value: string): string {\n return value.replace(/\\s*[\\r\\n]+\\s*/g, \" \");\n}\n\n/** A property-key array, one key per line, indented for the admin block. */\nfunction formatKeyList(keys: string[]): string {\n if (keys.length === 0) return \"[]\";\n return `[\\n${keys.map((k) => ` ${quote(k)}`).join(\",\\n\")}\\n ]`;\n}\n\n/**\n * Generate the full TypeScript file content for a single collection.\n * Pure function — no I/O.\n */\nexport function generateCollectionFile(\n tableName: string,\n meta: TableMeta,\n allFks: ForeignKeyRow[],\n joinTables: Set<string>,\n tablesMap: Map<string, TableMeta>,\n enumMap: Map<string, string[]>,\n sampleData?: Record<string, unknown>[],\n context: GenerationContext = {}\n): string {\n const collectionName = humanize(tableName);\n const singular = singularize(collectionName);\n const icon = getIconForTable(tableName);\n\n const classification = context.classifications?.get(tableName);\n const checkFacts: CheckFactsByTable = context.checkFacts ?? new Map();\n const tableChecks = checkFacts.get(tableName);\n const columnComments = new Map<string, string>();\n let tableComment: string | undefined;\n for (const comment of context.metadata?.comments ?? []) {\n if (comment.table_name !== tableName) continue;\n if (comment.column_name === null) tableComment = comment.comment;\n else columnComments.set(comment.column_name, comment.comment);\n }\n const singleColumnUniques = new Set(\n (context.metadata?.uniques ?? [])\n .filter((u) => u.table_name === tableName && u.column_names.length === 1)\n .map((u) => u.column_names[0])\n );\n\n const builder: CollectionBuilder = context.builder ?? \"admin-types\";\n /** Whether the target project has an `admin` field to write into at all. */\n const emitAdmin = builder === \"admin-types\";\n\n const BUILDER_IMPORT: Record<CollectionBuilder, string> = {\n \"admin-types\": `import { defineCollection } from ${quote(ADMIN_TYPES_PACKAGE)};`,\n common: `import { defineCollection } from ${quote(COMMON_PACKAGE)};`,\n annotation: `import { PostgresCollectionConfig } from ${quote(TYPES_PACKAGE)};`\n };\n const imports = new Set<string>([BUILDER_IMPORT[builder]]);\n\n /**\n * Imports the collection a relation points at — unless it is this one.\n *\n * A self-referencing key (`employees.reports_to -> employees`, which both\n * northwind and chinook have) otherwise made the file import its own default\n * export under the name it declares three lines later: `TS2440: Import\n * declaration conflicts with local declaration`. The relation target is a\n * thunk, so referring to the local const directly is fine — it is only\n * dereferenced after the module has finished evaluating.\n */\n const importCollection = (otherTable: string): string => {\n const varName = toCollectionVarName(otherTable);\n if (otherTable !== tableName) imports.add(`import ${varName} from ${quote(`./${otherTable}`)};`);\n return varName;\n };\n\n /**\n * A relation's `target` thunk, with its return type spelled out.\n *\n * The annotation is what makes `defineCollection` survive a relational schema.\n * Without an explicit type on the const, the collection's type is *inferred*,\n * and a relation cycle — `posts` belongs to `authors`, `authors` has many\n * `posts`; or `employees.reports_to -> employees`, which northwind, chinook and\n * musicbrainz all have — makes that inference circular: `TS7022: implicitly has\n * type 'any' because it is referenced directly or indirectly in its own\n * initializer`, plus `TS7023` on the thunk and `TS2303` on the import alias.\n * Naming the return type lets the checker type the thunk without resolving the\n * collection it points at, which breaks the cycle. Nothing else is given up:\n * the inference that matters runs over `properties`, not over `relations`.\n *\n * The annotated flavour has no cycle to break — its const is already typed — so\n * it keeps the plainer thunk it has always emitted.\n */\n const relationTarget = (targetVarName: string): string => {\n if (builder === \"annotation\") return `() => ${targetVarName}`;\n imports.add(`import type { AnyCollectionConfig } from ${quote(TYPES_PACKAGE)};`);\n return `(): AnyCollectionConfig => ${targetVarName}`;\n };\n\n let propsOutput = \"\";\n let relationsOutput = \"\";\n const orderEntries: PropertyOrderEntry[] = [];\n const propertyBlocks = new Map<string, string>();\n /**\n * Column → the property key it was generated under.\n *\n * Needed because the structural helpers below (`deriveTitleProperty`,\n * `deriveKanbanProperty`, `deriveSort`) answer in *columns* — they read\n * `pg_attribute` — while `display.title`, `kanban.columnProperty` and\n * `sort` name **properties**. The two used to be the same string, so\n * nothing carried the translation; now `full_name` is generated as\n * `fullName` and a title pointing at `full_name` points at nothing.\n */\n const keyByColumn = new Map<string, string>();\n /** Properties the list view will not render, so `listProperties` skips them. */\n const hiddenFromCollection = new Set<string>();\n let columnIndex = 0;\n\n // Detect composite primary keys\n const isCompositePk = meta.pks.length > 1;\n\n // Map columns\n for (const col of meta.columns) {\n // Skip foreign keys since we handle them as relations\n // Exception: Do not skip if it's part of the primary key!\n if (meta.fks.some((fk) => fk.column_name === col.column_name) && !meta.pks.includes(col.column_name)) continue;\n\n const currentIndex = columnIndex++;\n\n // The key this column is generated under — its *wire* name, which is\n // not its column name. `columnName` below carries the column, so the\n // two never have to agree and the API stops carrying `user_id` next to\n // `displayName`.\n //\n // Camel-casing makes collisions possible where none existed: `user_id`\n // and `userId` are two columns and one key, which is a duplicate key in\n // an object literal — a TypeScript error that stops the whole generated\n // collection compiling. Resolved the same way the foreign-key loop\n // below resolves its own: first free candidate, then a numbered tail,\n // so no column is ever dropped. The raw column name is the second\n // candidate, so the loser of a collision still gets a name that means\n // something. Deterministic for a given database: the columns arrive in\n // ordinal order, so the same schema always yields the same keys.\n const propertyKey = firstFreeKey([toWireKey(col.column_name), col.column_name], propertyBlocks);\n keyByColumn.set(col.column_name, propertyKey);\n\n // Check if this column uses a PostgreSQL enum type\n const colEnumValues = enumMap.get(col.udt_name);\n const isEnumColumn = col.data_type === \"USER-DEFINED\" && colEnumValues !== undefined;\n const isVectorColumn = col.udt_name === \"vector\";\n\n const propType = isEnumColumn ? \"string\" : (isVectorColumn ? \"vector\" : mapPgType(col.data_type));\n let extra = \"\";\n\n const colNameLower = col.column_name.toLowerCase();\n\n // ── Data Inference Engine ────────────────────────────────────────────\n let finalPropType = propType;\n let inferenceExtra = \"\";\n\n if (!isEnumColumn && sampleData && sampleData.length > 0) {\n const values = sampleData.map(r => r[col.column_name]);\n const inferred = inferPropertyFromData(col.column_name, col.data_type, propType, values, meta.pks.includes(col.column_name), emitAdmin);\n if (inferred.propType) finalPropType = inferred.propType;\n if (inferred.extra) inferenceExtra = inferred.extra;\n }\n\n const columnChecks = tableChecks?.get(col.column_name);\n\n // Enum values — generate real enum from the PG enum\n if (isEnumColumn && colEnumValues) {\n const enumEntries = colEnumValues\n .map((v) => `{ id: ${quote(v)}, label: ${quote(humanize(v))} }`)\n .join(\", \");\n extra += `\\n enum: [${enumEntries}],`;\n } else if (columnChecks?.enumValues && !inferenceExtra.includes(\"enum:\") && propType === \"string\") {\n // `CHECK (col IN (…))` is the other way a schema declares a closed\n // set. It is the same statement as a Postgres enum type, made by an\n // author who did not want a type — and until now the form offered a\n // free-text box for it and let the database reject the write.\n const enumEntries = columnChecks.enumValues\n .map((v) => `{ id: ${quote(v)}, label: ${quote(humanize(v))} }`)\n .join(\", \");\n extra += `\\n enum: [${enumEntries}],`;\n }\n\n // Date auto-value heuristics\n if (finalPropType === \"date\") {\n if (colNameLower === \"created_at\" || colNameLower === \"createdat\") {\n extra += \"\\n autoValue: \\\"on_create\\\",\";\n extra = withAdminOptions(extra, [\"readOnly: true\", \"hideFromCollection: true\"], emitAdmin);\n hiddenFromCollection.add(propertyKey);\n } else if (colNameLower === \"updated_at\" || colNameLower === \"updatedat\") {\n extra += \"\\n autoValue: \\\"on_update\\\",\";\n extra = withAdminOptions(extra, [\"readOnly: true\", \"hideFromCollection: true\"], emitAdmin);\n hiddenFromCollection.add(propertyKey);\n } else if (col.column_default && (col.column_default.includes(\"now()\") || col.column_default.includes(\"CURRENT_TIMESTAMP\"))) {\n extra += \"\\n autoValue: \\\"on_create\\\",\";\n extra = withAdminOptions(extra, [\"readOnly: true\"], emitAdmin);\n }\n }\n\n // Array/Map heuristics (Fallback if not inferred)\n if (finalPropType === \"array\" && !inferenceExtra.includes(\"of: {\")) {\n let innerType = \"string\";\n let colType = \"\";\n if (col.udt_name.startsWith(\"_\")) {\n const baseType = col.udt_name.substring(1);\n innerType = mapPgType(baseType);\n if (innerType === \"string\") colType = \"text[]\";\n else if (innerType === \"number\") colType = col.udt_name === \"_numeric\" ? \"numeric[]\" : \"integer[]\";\n else if (innerType === \"boolean\") colType = \"boolean[]\";\n }\n if (colType) {\n extra += `\\n columnType: ${quote(colType)},`;\n }\n extra += `\\n of: { name: ${quote(`${humanize(col.column_name)} Item`)}, type: ${quote(innerType)} },`;\n } else if (finalPropType === \"map\" && !inferenceExtra.includes(\"keyValue: true\") && !inferenceExtra.includes(\"properties: {\")) {\n extra += \"\\n keyValue: true,\";\n }\n\n // String sub-type heuristics (Fallback if not handled by inference or enum)\n if (finalPropType === \"string\" && !isEnumColumn && !inferenceExtra) {\n const isUrl = colNameLower.endsWith(\"_url\") || colNameLower.endsWith(\"_uri\") || colNameLower.endsWith(\"_link\");\n const isMedia = colNameLower.includes(\"image\") || colNameLower.includes(\"avatar\") || colNameLower.includes(\"photo\") || colNameLower.includes(\"logo\") || colNameLower.includes(\"cover\");\n\n if (isMedia) {\n extra += `\\n storage: {\\n storagePath: ${quote(`${tableName}/${col.column_name}`)}\\n },`;\n } else if (isUrl) {\n extra += \"\\n url: true,\";\n } else if (colNameLower === \"description\" || colNameLower === \"summary\" || colNameLower === \"excerpt\") {\n extra = withAdminOptions(extra, [\"multiline: true\"], emitAdmin);\n } else if (colNameLower === \"content\" || colNameLower === \"body\") {\n // Inside `admin`, because that is where both options live. At the\n // top of the property — where these were — the generated file\n // does not compile: `StringProperty` declares neither. Six of\n // OpenStreetMap's tables have a `body` column, which is how this\n // surfaced.\n extra = withAdminOptions(extra, [\"multiline: true\", \"markdown: true\"], emitAdmin);\n } else if (col.data_type === \"text\") {\n extra = withAdminOptions(extra, [\"multiline: true\"], emitAdmin);\n }\n }\n\n // Append inference results\n if (inferenceExtra) {\n extra += inferenceExtra;\n if (!extra.endsWith(\",\")) extra += \",\";\n }\n\n // ── Rules the database already enforces ──────────────────────────────\n // Everything below is read from the catalog, not guessed from the data\n // or the column's name. Each one is a constraint a write would hit\n // anyway; surfacing it means the form says no before the database does.\n const declaredValidation: string[] = [];\n\n // `varchar(n)` — a bound the author wrote down and nothing has read.\n if (finalPropType === \"string\" &&\n typeof col.character_maximum_length === \"number\" &&\n col.character_maximum_length > 0 &&\n !hasGeneratedKey(extra, \"max\")) {\n declaredValidation.push(`max: ${col.character_maximum_length}`);\n }\n\n if (columnChecks) {\n if (finalPropType === \"number\") {\n if (columnChecks.min !== undefined && !hasGeneratedKey(extra, \"min\")) declaredValidation.push(`min: ${columnChecks.min}`);\n if (columnChecks.max !== undefined && !hasGeneratedKey(extra, \"max\")) declaredValidation.push(`max: ${columnChecks.max}`);\n if (columnChecks.moreThan !== undefined) declaredValidation.push(`moreThan: ${columnChecks.moreThan}`);\n if (columnChecks.lessThan !== undefined) declaredValidation.push(`lessThan: ${columnChecks.lessThan}`);\n }\n if (finalPropType === \"string\") {\n if (columnChecks.lengthMin !== undefined && !hasGeneratedKey(extra, \"min\")) declaredValidation.push(`min: ${columnChecks.lengthMin}`);\n if (columnChecks.lengthMax !== undefined && !declaredValidation.some((v) => v.startsWith(\"max:\")) && !hasGeneratedKey(extra, \"max\")) {\n declaredValidation.push(`max: ${columnChecks.lengthMax}`);\n }\n }\n }\n\n // A single-column unique index is the same promise `validation.unique`\n // makes. Composite uniqueness is not: it constrains the combination, and\n // marking either column unique on its own would reject valid rows.\n if (singleColumnUniques.has(col.column_name) && !meta.pks.includes(col.column_name)) {\n declaredValidation.push(\"unique: true\");\n }\n\n extra = withValidation(extra, declaredValidation);\n\n // A generated column rejects every write, and a tsvector holds lexeme\n // positions rather than text, so an editable field for either is a field\n // that can only ever produce an error.\n if (isReadOnlyColumn(col) && !hasGeneratedKey(extra, \"readOnly\")) {\n const options = [\"readOnly: true\"];\n if (isDerivedIndexColumn(col) && !hasGeneratedKey(extra, \"hideFromCollection\")) {\n options.push(\"hideFromCollection: true\");\n hiddenFromCollection.add(propertyKey);\n }\n extra = withAdminOptions(extra, options, emitAdmin);\n }\n\n // `COMMENT ON COLUMN` — documentation the author already wrote, which\n // introspection has never carried across.\n const columnComment = columnComments.get(col.column_name);\n if (columnComment) {\n extra = `\\n description: ${quote(columnComment)},${extra}`;\n }\n\n // Identify IDs (unless already inferred as UUID/CUID by inferenceEngine)\n if (meta.pks.includes(col.column_name)) {\n if (isCompositePk) {\n extra += `\\n // Part of composite primary key (${commentText(meta.pks.join(\", \"))})`;\n } else if (finalPropType === \"number\" && !inferenceExtra.includes(\"isId:\")) {\n extra += \"\\n isId: \\\"increment\\\",\";\n } else if (col.data_type.toLowerCase() === \"uuid\" && !inferenceExtra.includes(\"isId:\")) {\n extra += \"\\n isId: \\\"uuid\\\",\";\n } else if (!inferenceExtra.includes(\"isId:\")) {\n extra += \"\\n isId: \\\"uuid\\\", // Verify if this is a UUID or CUID\";\n }\n }\n\n if (finalPropType === \"vector\") {\n const dims = col.atttypmod && col.atttypmod > 0 ? col.atttypmod : 1536;\n extra += `\\n dimensions: ${dims},`;\n }\n\n // `required` on a column the user cannot write is a form that cannot be\n // submitted: pagila's `film.fulltext` is NOT NULL and maintained by a\n // trigger, so demanding it of the user blocks every create.\n if (col.is_nullable === \"NO\" && !meta.pks.includes(col.column_name) && !col.column_default && !isReadOnlyColumn(col)) {\n if (extra.includes(\"validation: {\")) {\n extra = extra.replace(\"validation: {\", \"validation: {\\n required: true,\");\n } else {\n extra += \"\\n validation: {\\n required: true\\n },\";\n }\n }\n\n const humanName = humanize(col.column_name);\n\n orderEntries.push({\n key: propertyKey,\n ctx: {\n propType: finalPropType,\n isPk: meta.pks.includes(col.column_name),\n isEnum: isEnumColumn,\n isStorage: extra.includes(\"storage: {\") || inferenceExtra.includes(\"storage: {\"),\n pgDataType: col.data_type,\n originalIndex: currentIndex\n }\n });\n\n propertyBlocks.set(propertyKey, `\n ${propKey(propertyKey)}: {\n name: ${quote(humanName)},\n columnName: ${quote(col.column_name)},\n type: ${quote(finalPropType)},${extra}\n },`);\n }\n\n // Map Owning Relations (from this table's FKs to other tables)\n for (const fk of meta.fks) {\n const targetTableName = fk.foreign_table_name;\n if (!joinTables.has(targetTableName)) {\n // The relation gets its own property key, and it must not be one this\n // file has already used — a duplicate key in an object literal is a\n // TypeScript error, so the whole collection stops compiling.\n //\n // The collision needs three things at once and is invisible without\n // all three: a foreign key column that does *not* end in `_id`, that\n // column also being part of the primary key (which is what keeps it\n // as a property of its own rather than folding it into the relation),\n // and the stripped name matching the target table. MusicBrainz names\n // every foreign key after the table it points at — `area_tag (area,\n // tag)` — so 67 of its 339 collections came out with a property\n // declared twice.\n //\n // Camel-cased for the same reason the columns above are: this is a\n // property key, it sits in the same object literal, and a\n // `blog_author` beside a `publishedAt` is the two-conventions\n // defect reproduced inside a single collection.\n const stripped = toWireKey(fk.column_name.replace(/_id$/, \"\"));\n const relName = firstFreeKey(\n [\n stripped,\n toWireKey(targetTableName),\n `${stripped}Relation`\n ],\n propertyBlocks\n );\n // Push the relation property key, not the FK column name\n orderEntries.push({\n key: relName,\n ctx: {\n propType: \"relation\",\n isPk: false,\n isEnum: false,\n isStorage: false,\n pgDataType: \"\",\n originalIndex: columnIndex++\n }\n });\n\n const targetCollectionCamel = importCollection(targetTableName);\n\n const relHumanName = humanize(relName);\n\n propertyBlocks.set(relName, `\n ${propKey(relName)}: {\n name: ${quote(relHumanName)},\n type: \"relation\",\n // mapped from foreign key: ${commentText(fk.column_name)} -> ${commentText(targetTableName)}(${commentText(fk.foreign_column_name)})\n relation: {\n kind: \"belongsTo\",\n target: ${relationTarget(targetCollectionCamel)},\n localKey: ${quote(fk.column_name)}\n }\n },`);\n }\n }\n\n // Map Inverse Relations (1-to-many where OTHER table points to THIS table)\n // These go into the `relations` array so they render as subcollection tabs.\n const inverseFks = allFks.filter((fk) => fk.foreign_table_name === tableName && !joinTables.has(fk.table_name));\n for (const fk of inverseFks) {\n const sourceTableName = fk.table_name;\n\n const targetCollectionCamel = importCollection(sourceTableName);\n\n relationsOutput += `\n {\n kind: \"hasMany\",\n relationName: ${quote(sourceTableName)},\n target: ${relationTarget(targetCollectionCamel)},\n // the ${commentText(sourceTableName)}.${commentText(fk.column_name)} FK points back here\n foreignKeyOnTarget: ${quote(fk.column_name)}\n },`;\n }\n\n // Map Many-to-Many Relations (Join Tables)\n // These also go into the `relations` array so they render as subcollection tabs.\n const relatedJoinTables = Array.from(joinTables).filter((jt) => {\n const jtMeta = tablesMap.get(jt);\n return jtMeta ? jtMeta.fks.some((fk) => fk.foreign_table_name === tableName) : false;\n });\n\n for (const jt of relatedJoinTables) {\n const jtMeta = tablesMap.get(jt);\n if (!jtMeta) continue;\n\n const joinFks = jtMeta.fks;\n\n // Handle self-referencing M2M: both FKs point to the same table\n const selfRefFks = joinFks.filter((fk) => fk.foreign_table_name === tableName);\n if (selfRefFks.length === 2) {\n // Self-referencing M2M — generate a single owning relation\n const thisFk = selfRefFks[0];\n const otherFk = selfRefFks[1];\n\n const relPropName = `${tableName}_via_${otherFk.column_name.replace(/_id$/, \"\")}`;\n\n relationsOutput += `\n {\n kind: \"manyToMany\",\n relationName: ${quote(relPropName)},\n target: ${relationTarget(toCollectionVarName(tableName))},\n through: {\n table: ${quote(jt)},\n sourceColumn: ${quote(thisFk.column_name)},\n targetColumn: ${quote(otherFk.column_name)}\n }\n },`;\n continue;\n }\n\n const otherFk = joinFks.find((fk) => fk.foreign_table_name !== tableName);\n\n if (otherFk) {\n const targetTableName = otherFk.foreign_table_name;\n\n const targetCollectionCamel = importCollection(targetTableName);\n\n // Both sides of a many-to-many are `manyToMany`. There is no owning\n // and inverse side to pick between any more, so this no longer\n // guesses one from table-name ordering and no longer emits a\n // half-configured relation on the losing side with a comment asking\n // the reader to finish it by hand. Introspection already knows both\n // junction columns; each side just names them from its own end.\n const thisFk = joinFks.find((fk) => fk.foreign_table_name === tableName);\n\n const throughCode = thisFk\n ? `\\n through: {\\n table: ${quote(jt)},\\n sourceColumn: ${quote(thisFk.column_name)},\\n targetColumn: ${quote(otherFk.column_name)}\\n }`\n : \"\";\n\n relationsOutput += `\n {\n kind: \"manyToMany\",\n relationName: ${quote(targetTableName)},\n target: ${relationTarget(targetCollectionCamel)},${throughCode}\n },`;\n }\n }\n\n const relationsBlock = relationsOutput\n ? `\\n relations: [${relationsOutput}\\n ],`\n : \"\";\n\n const sortedPropertiesOrder = sortPropertiesOrder(orderEntries);\n for (const key of sortedPropertiesOrder) {\n propsOutput += propertyBlocks.get(key) || \"\";\n }\n\n // ── The admin block ──────────────────────────────────────────────────\n // `icon` and `propertiesOrder` used to be emitted at the *top level* of the\n // config, where they have not belonged since the admin block was split out:\n // `PostgresCollectionConfig` does not declare them, so every generated file\n // was a type error, and the panel — which reads the block — never saw them.\n const adminEntries: string[] = [`icon: ${quote(icon)}`];\n\n if (classification) {\n const derivedFacts = context.metadata\n ? buildColumnFacts(meta, context.metadata, enumMap, checkFacts)\n : undefined;\n\n if (classification.role === \"owned-child\") {\n // The rows are already reachable: every inbound foreign key renders\n // as a tab on the parent. A second, top-level entry for them is what\n // turns a navigation of eight nouns into a list of thirty tables.\n adminEntries.push(\"hideFromNavigation: true\");\n } else if (classification.role === \"lookup\") {\n adminEntries.push('group: \"Reference\"');\n }\n\n if (derivedFacts) {\n // Each of these comes back as a column and is emitted as a property.\n const asProperty = (column: string): string => keyByColumn.get(column) ?? toWireKey(column);\n\n const titleProperty = deriveTitleProperty(derivedFacts);\n if (titleProperty) adminEntries.push(`display: { title: ${quote(asProperty(titleProperty))} }`);\n\n const kanbanProperty = deriveKanbanProperty(derivedFacts);\n if (kanbanProperty) adminEntries.push(`kanban: {\\n columnProperty: ${quote(asProperty(kanbanProperty))}\\n }`);\n\n const sort = deriveSort(derivedFacts);\n if (sort) adminEntries.push(`sort: [${quote(asProperty(sort[0]))}, \"desc\"]`);\n }\n\n const listProperties = deriveListProperties(sortedPropertiesOrder, hiddenFromCollection);\n if (listProperties) {\n adminEntries.push(`listProperties: ${formatKeyList(listProperties)}`);\n }\n }\n\n adminEntries.push(`propertiesOrder: ${formatKeyList(sortedPropertiesOrder)}`);\n\n // Every entry above names a property key, and with `defineCollection` those\n // keys are now checked against `properties` — a stale `propertiesOrder` entry\n // left behind by a renamed column is a compile error rather than a silent\n // no-op. Which is also why the block cannot be emitted where the field is not\n // declared: see {@link CollectionBuilder}.\n const adminBlock = emitAdmin\n ? `\\n admin: {\\n ${adminEntries.join(\",\\n \")}\\n }`\n : \"\";\n\n const descriptionBlock = tableComment\n ? `\\n description: ${quote(tableComment)},`\n : \"\";\n\n // The schema the table was read from. `public` is what an absent `schema`\n // means, so it is left out; any other is load-bearing — without it the\n // runtime serves, and boot creates, a table of the same name in `public`.\n const pgSchema = context.metadata?.schema;\n const schemaBlock = pgSchema && pgSchema !== \"public\"\n ? `\\n schema: ${quote(pgSchema)},`\n : \"\";\n\n // The classification is stated in the file because it is a *decision*, and a\n // decision the reader may disagree with. Naming the evidence tells them\n // which line to delete when they do.\n const classificationNote = classification && classification.role !== \"entity\"\n ? `\\n// Introspected as a ${commentText(classification.role)}: ${commentText(classification.reason)}.\\n`\n : \"\";\n\n const collectionVarName = toCollectionVarName(tableName);\n // `const x = defineCollection({ … })` is also the shape the ts-morph schema\n // editor in `@rebasepro/server` expects — `COLLECTION_FACTORIES` — so an\n // introspected collection is now editable from the panel the way a scaffolded\n // one is.\n const [open, close] = builder === \"annotation\"\n ? [`const ${collectionVarName}: PostgresCollectionConfig = {`, \"};\"]\n : [`const ${collectionVarName} = defineCollection({`, \"});\"];\n // Package imports first, then siblings. `AnyCollectionConfig` is added the\n // moment the first relation needs it — which is after the sibling collections\n // it points at have already been added — and only when a relation needs it, so\n // a project with `noUnusedLocals` never sees an import it does not use.\n const importLines = Array.from(imports);\n const orderedImports = [\n ...importLines.filter((line) => !line.includes('from \"./')),\n ...importLines.filter((line) => line.includes('from \"./'))\n ];\n const fileContent = `${orderedImports.join(\"\\n\")}\n${classificationNote}\n${open}\n name: ${quote(collectionName)},\n singularName: ${quote(singular)},\n slug: ${quote(tableName)},\n table: ${quote(tableName)},${schemaBlock}${descriptionBlock}\n properties: {${propsOutput}\n },${relationsBlock}${adminBlock}\n${close}\n\nexport default ${collectionVarName};\n`;\n\n return fileContent;\n}\n\n/**\n * Generate the content for an index.ts file that re-exports all collections.\n */\nexport function generateIndexContent(fileNames: string[]): string {\n const sorted = [...fileNames].sort();\n let imports = \"\";\n let arrayElements = \"\";\n for (const f of sorted) {\n const varName = toCollectionVarName(f);\n imports += `import ${varName} from ${quote(`./${f}`)};\\n`;\n arrayElements += ` ${varName},\\n`;\n }\n return `${imports}\\nexport const collections = [\\n${arrayElements}];\\n`;\n}\n\n/**\n * Merge new exports into existing index.ts content.\n * Returns the merged content string.\n */\nexport function mergeIndexContent(existingContent: string, newFileNames: string[]): string {\n const existingImports = new Set(\n [...existingContent.matchAll(/import\\s+([a-zA-Z0-9_]+)\\s+from\\s+\"\\.\\/([^\"]+)\"/g)].map((m) => m[2])\n );\n const sorted = [...newFileNames].sort();\n\n let newImports = \"\";\n let newElements = \"\";\n\n for (const f of sorted) {\n if (!existingImports.has(f)) {\n const varName = toCollectionVarName(f);\n newImports += `import ${varName} from ${quote(`./${f}`)};\\n`;\n newElements += ` ${varName},\\n`;\n }\n }\n\n if (!newImports) return existingContent;\n\n // Simple injection logic:\n // Add new imports below the last import or at the top\n const importRegex = /import\\s+.*?;/g;\n let lastImportMatch;\n let match;\n while ((match = importRegex.exec(existingContent)) !== null) {\n lastImportMatch = match;\n }\n\n let contentWithImports = existingContent;\n if (lastImportMatch) {\n const pos = lastImportMatch.index + lastImportMatch[0].length;\n contentWithImports = existingContent.slice(0, pos) + \"\\n\" + newImports.trimEnd() + existingContent.slice(pos);\n } else {\n contentWithImports = newImports + \"\\n\" + existingContent;\n }\n\n // Inject into the `collections = [...]` array\n const arrayRegex = /export\\s+const\\s+collections\\s*=\\s*\\[([\\s\\S]*?)\\];/;\n return contentWithImports.replace(arrayRegex, (fullMatch, arrayContent) => {\n let mergedArray = arrayContent.trimEnd();\n if (mergedArray && !mergedArray.endsWith(\",\")) mergedArray += \",\";\n if (mergedArray) mergedArray += \"\\n\";\n mergedArray += newElements.trimEnd();\n return `export const collections = [\\n ${mergedArray.trim()}\\n];`;\n });\n}\n\n/**\n * Safely extract the host portion of a database URL for logging.\n */\nexport function safeHostFromUrl(url: string): string {\n return url.includes(\"@\") ? url.split(\"@\")[1] : \"(local connection)\";\n}\n"],"mappings":"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"}