@rebasepro/server-postgres 0.21.0 → 0.21.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +15 -13
- package/dist/{BranchService-W3DMfcZZ.js → BranchService-CucnFcSE.js} +2 -2
- package/dist/{BranchService-W3DMfcZZ.js.map → BranchService-CucnFcSE.js.map} +1 -1
- package/dist/PostgresBackendDriver.d.ts +26 -10
- package/dist/backup/backup-cron.d.ts +28 -1
- package/dist/{backup-cli-Bp-ou6o2.js → backup-cli-CkjsJEcu.js} +3 -2
- package/dist/backup-cli-CkjsJEcu.js.map +1 -0
- package/dist/{cli-errors-C3g_kHBw.js → cli-errors-Dka89exj.js} +19 -2
- package/dist/cli-errors-Dka89exj.js.map +1 -0
- package/dist/cli.js +15 -8
- package/dist/cli.js.map +1 -1
- package/dist/{doctor-ByFWL_ET.js → doctor-Gzzd8wTq.js} +4 -4
- package/dist/{doctor-ByFWL_ET.js.map → doctor-Gzzd8wTq.js.map} +1 -1
- package/dist/{ensure-collection-policies-CQGHln-y.js → ensure-collection-policies-B4IDFtQ-.js} +2 -2
- package/dist/{ensure-collection-policies-CQGHln-y.js.map → ensure-collection-policies-B4IDFtQ-.js.map} +1 -1
- package/dist/{ensure-collection-tables-Bkvehxdm.js → ensure-collection-tables-Cr2ye5JC.js} +2 -2
- package/dist/{ensure-collection-tables-Bkvehxdm.js.map → ensure-collection-tables-Cr2ye5JC.js.map} +1 -1
- package/dist/{generate-drizzle-schema-vSK-VdYT.js → generate-drizzle-schema-D2MDdJt-.js} +2 -2
- package/dist/{generate-drizzle-schema-vSK-VdYT.js.map → generate-drizzle-schema-D2MDdJt-.js.map} +1 -1
- package/dist/{generate-drizzle-schema-logic-Dfyf_MRu.js → generate-drizzle-schema-logic-DFa9qy9u.js} +4 -2
- package/dist/{generate-drizzle-schema-logic-Dfyf_MRu.js.map → generate-drizzle-schema-logic-DFa9qy9u.js.map} +1 -1
- package/dist/{generated-schema-staleness-DRQe2BpC.js → generated-schema-staleness-Di9c4ZxN.js} +9 -5
- package/dist/{generated-schema-staleness-DRQe2BpC.js.map → generated-schema-staleness-Di9c4ZxN.js.map} +1 -1
- package/dist/index.es.js +264 -180
- package/dist/index.es.js.map +1 -1
- package/dist/schema/doctor-cli.js +2 -2
- package/dist/schema/generate-drizzle-schema.js +1 -1
- package/dist/schema/generated-schema-staleness.d.ts +22 -0
- package/dist/services/realtimeService.d.ts +75 -12
- package/package.json +7 -7
- package/dist/backup-cli-Bp-ou6o2.js.map +0 -1
- package/dist/cli-errors-C3g_kHBw.js.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"generate-drizzle-schema-logic-Dfyf_MRu.js","names":[],"sources":["../src/schema/plan/render-drizzle.ts","../src/schema/generate-drizzle-schema-logic.ts"],"sourcesContent":["/**\n * `schema.generated.ts`, rendered from a {@link SchemaPlan}.\n *\n * This file is what a developer running drizzle-kit themselves diffs against,\n * and what the runtime's query builder is typed by. It has to *compile*, which\n * nothing in this repo checked until `generated-schema-compiles.test.ts`: six\n * ordinary inputs used to produce a file TypeScript rejects, and the class the\n * bigint incident already taught is that a file which will not build gets\n * hand-patched and then sits stale until a security fix misses production.\n *\n * Two structural rules keep that from coming back:\n *\n * - **The import list is derived from what was emitted.** Every builder is\n * recorded as it is used ({@link BuilderUses}), and the header is written\n * last, from that set. It used to be a fixed roster plus three\n * property-scanning heuristics, so `columnType: \"uuid\"` on a plain string\n * emitted `uuid(…)` and imported nothing.\n * - **Nothing here reads a `Property`.** Types, defaults, keys and constraints\n * all arrive decided. The Drizzle generator disagreeing with the DDL one is\n * what left `geopoint` with a database column and no Drizzle key.\n */\nimport type { ColumnPlan, ForeignKeyPlan, PgType, PolicyPlan, RelationPlan, SchemaPlan, TablePlan } from \"./types\";\nimport { renderPredicate } from \"../collection-index\";\n\n/** What the generated file may leave out. */\nexport interface DrizzleRenderOptions {\n /**\n * `false` strips the `pgPolicy(...)` calls. RLS is still enabled on every\n * table regardless — a bare table must default-deny, not fail open.\n */\n policies?: boolean;\n}\n\nconst JS_IDENTIFIER = /^[A-Za-z_$][A-Za-z0-9_$]*$/;\n\n/**\n * A string literal for the generated file.\n *\n * Column names, table names and enum values are all written in as literals and\n * none of them is constrained to be quote-free: a Postgres identifier only has\n * to be quoted, and `O'Brien` is an ordinary enum value. Interpolating them raw\n * ended the literal early.\n */\nconst quote = (value: string): string => JSON.stringify(value);\n\n/** An object key: verbatim when it is an identifier, quoted otherwise. */\nconst propKey = (name: string): string => (JS_IDENTIFIER.test(name) ? name : quote(name));\n\n/**\n * A property access on a generated table variable.\n *\n * `users.full name` is not an expression; `users[\"full name\"]` is, and Drizzle\n * treats the two identically.\n */\nconst member = (object: string, key: string): string =>\n (JS_IDENTIFIER.test(key) ? `${object}.${key}` : `${object}[${quote(key)}]`);\n\n/**\n * The drizzle-orm builders this file emitted, collected as they are written.\n * The only way to use a builder is to record it here.\n */\ntype BuilderUses = Set<string>;\n\n/** Record a builder and hand back its name, so a call site cannot use one silently. */\nconst needs = (uses: BuilderUses, builder: string): string => {\n uses.add(builder);\n return builder;\n};\n\n/**\n * Wraps a compiled SQL clause in a Drizzle `sql\\`...\\`` template literal.\n *\n * The clause is SQL being written into a TypeScript file, so it has to survive\n * being read back as a template literal. Three characters do not:\n *\n * - `` ` `` closes the template early, and the rest of the clause becomes code.\n * - `${` opens an interpolation — the file stops compiling, or worse, compiles\n * against whatever identifier happens to be in scope.\n * - `\\` is an escape, and Drizzle's `sql` tag reads the *cooked* strings, not\n * `.raw`. So a policy written as `email ~ '^admin\\.user@corp\\.com$'` reached\n * the database as `^admin.user@corp.com$`, where every `\\.` now matches any\n * character. A `USING` clause is a security boundary and that one silently\n * widened it.\n */\nconst wrapSql = (clause: string): string =>\n `sql\\`${clause.replace(/\\\\/g, \"\\\\\\\\\").replace(/`/g, \"\\\\`\").replace(/\\$\\{/g, \"\\\\${\")}\\``;\n\n/** The pg-core builder call for a tagged type, minus the column name. */\nconst builderFor = (type: PgType, column: string, uses: BuilderUses): string => {\n const name = quote(column);\n switch (type.kind) {\n case \"text\": return `${needs(uses, \"text\")}(${name})`;\n case \"varchar\": return `${needs(uses, \"varchar\")}(${name}, { length: ${type.length} })`;\n case \"char\": return `${needs(uses, \"char\")}(${name}, { length: ${type.length} })`;\n case \"uuid\": return `${needs(uses, \"uuid\")}(${name})`;\n // The enum variable is declared at the top of the same file; it is not\n // a pg-core import.\n case \"enum\": return `${type.varName}(${name})`;\n case \"smallint\": return `${needs(uses, \"smallint\")}(${name})`;\n case \"integer\": return `${needs(uses, \"integer\")}(${name})`;\n // `bigint` and `bigserial` are the only pg-core builders that *require*\n // a config argument: without `mode`, drizzle cannot know whether to hand\n // back a `number` or a `bigint`, and the emitted call does not\n // typecheck. This is why `schema.generated.ts` drifted — regenerating it\n // produced a file that would not compile, so the bigint lines were\n // hand-patched, and every regeneration after that looked like a large,\n // alarming diff nobody wanted to ship.\n //\n // `number` rather than `bigint`: these are counters and byte totals that\n // every caller already treats as numbers.\n case \"bigint\": return `${needs(uses, \"bigint\")}(${name}, { mode: \"number\" })`;\n case \"bigserial\": return `${needs(uses, \"bigserial\")}(${name}, { mode: \"number\" })`;\n case \"smallserial\": return `${needs(uses, \"smallserial\")}(${name})`;\n case \"serial\": return `${needs(uses, \"serial\")}(${name})`;\n case \"real\": return `${needs(uses, \"real\")}(${name})`;\n case \"doublePrecision\": return `${needs(uses, \"doublePrecision\")}(${name})`;\n case \"numeric\":\n if (type.precision === undefined) return `${needs(uses, \"numeric\")}(${name})`;\n return type.scale === undefined\n ? `${needs(uses, \"numeric\")}(${name}, { precision: ${type.precision} })`\n : `${needs(uses, \"numeric\")}(${name}, { precision: ${type.precision}, scale: ${type.scale} })`;\n case \"boolean\": return `${needs(uses, \"boolean\")}(${name})`;\n case \"timestamptz\": return `${needs(uses, \"timestamp\")}(${name}, { withTimezone: true, mode: 'string' })`;\n case \"date\": return `${needs(uses, \"date\")}(${name}, { mode: 'string' })`;\n case \"time\": return `${needs(uses, \"time\")}(${name})`;\n case \"json\": return `${needs(uses, \"json\")}(${name})`;\n case \"jsonb\": return `${needs(uses, \"jsonb\")}(${name})`;\n case \"vector\": return `${needs(uses, \"vector\")}(${name}, { dimensions: ${type.dimensions} })`;\n case \"bytea\": return `${needs(uses, \"customType\")}({ dataType() { return 'bytea'; } })(${name})`;\n case \"tsvector\": return `${needs(uses, \"customType\")}({ dataType() { return 'tsvector'; } })(${name})`;\n // pg-core has no array *type*; `.array()` is a modifier on the element.\n case \"array\": return `${builderFor(type.of, column, uses)}.array()`;\n }\n};\n\n/**\n * The `.references(...)` clause a foreign key column carries.\n *\n * The `(): AnyPgColumn =>` annotation is not optional decoration and it is\n * emitted on every reference rather than only the self-referential ones. A\n * `belongsTo` pointing at its own table — a comment thread, a category tree —\n * produces `.references(() => posts.id)` inside the initializer of `posts`, and\n * TypeScript cannot infer a type for a `const` that appears in its own\n * initializer (TS7022). Drizzle documents the annotation as the fix; applying\n * it everywhere means the generated line does not depend on whether the author\n * happened to point the relation at another table.\n */\nconst referencesClause = (\n fk: ForeignKeyPlan,\n targetVar: string,\n targetKey: string,\n uses: BuilderUses\n): string => {\n needs(uses, \"type AnyPgColumn\");\n const parts = [\n fk.onUpdate ? `onUpdate: \"${fk.onUpdate.toLowerCase()}\"` : \"\",\n `onDelete: \"${fk.onDelete.toLowerCase()}\"`\n ].filter(Boolean);\n return `.references((): AnyPgColumn => ${member(targetVar, targetKey)}, { ${parts.join(\", \")} })`;\n};\n\n/** The declaration a column compiles to, without its object key. */\nconst renderColumn = (column: ColumnPlan, table: TablePlan, plan: SchemaPlan, uses: BuilderUses): string => {\n let out = builderFor(column.type, column.column, uses);\n\n if (column.generated) {\n return `${out}.generatedAlwaysAs(${wrapSql(column.generated.expression)})`;\n }\n\n // A junction endpoint spells `.notNull()` before its reference; every other\n // column spells it last. Both are the same constraint — this keeps the\n // generated file byte-stable across the move to one planner.\n const junctionKey = column.source.kind === \"junction-key\";\n if (junctionKey) out += \".notNull()\";\n\n if (column.default?.kind === \"identity\") out += \".generatedByDefaultAsIdentity()\";\n if (column.primaryKey) out += \".primaryKey()\";\n if (column.default && column.default.kind !== \"identity\") {\n const expression = column.default.kind === \"sql\" ? column.default.expression : column.default.sql;\n // `.defaultRandom()` is drizzle's own spelling of `gen_random_uuid()`,\n // and it exists **only on the uuid builder** — a `text` id whose\n // strategy happens to be spelled ``isId: \"sql`gen_random_uuid()`\"`` has\n // to take the generic form, which is what the file has always emitted\n // for it.\n out += column.type.kind === \"uuid\" && expression === \"gen_random_uuid()\"\n ? \".defaultRandom()\"\n : `.default(${wrapSql(expression)})`;\n }\n // A plain property that carries a relation's foreign key column gets the\n // constraint the relation would have emitted. `postId: { columnName:\n // \"post_id\" }` beside a `belongsTo` on `post_id` used to emit an integer\n // with no `.references()` at all, so that project got no foreign key on the\n // Drizzle side while `db push` created one.\n const foreignKey = column.foreignKey\n ?? table.columns.find(c => c.columnOwnedByProperty && c.column === column.column)?.foreignKey;\n if (foreignKey) {\n const target = plan.tables.find(t =>\n t.schema === foreignKey.targetSchema && t.table === foreignKey.targetTable);\n if (target) {\n const targetColumn = target.columns.find(c => c.column === foreignKey.targetColumn);\n out += referencesClause(foreignKey, target.varName, targetColumn?.key ?? foreignKey.targetColumn, uses);\n }\n }\n if (column.unique) out += \".unique()\";\n if (!column.nullable && !column.primaryKey && !junctionKey) out += \".notNull()\";\n return out;\n};\n\n/** The declared `indexes:` block, as Drizzle table extras. */\nconst indexExtras = (table: TablePlan, uses: BuilderUses): string[] =>\n table.indexes.map(spec => {\n const builder = spec.unique ? needs(uses, \"uniqueIndex\") : needs(uses, \"index\");\n // The spec holds COLUMN names; the generated table is keyed by field\n // name (`authorId` for `author_id`).\n const column = (name: string): string =>\n member(\"table\", table.columns.find(c => c.column === name)?.key ?? name);\n const keys = spec.keys.map(key => {\n if (spec.method !== \"btree\") return column(key.column);\n const direction = key.direction === \"desc\" ? \".desc()\" : \".asc()\";\n const nulls = key.nulls === \"first\" ? \".nullsFirst()\" : \".nullsLast()\";\n return `${column(key.column)}${direction}${nulls}`;\n });\n const on = spec.method === \"btree\"\n ? `.on(${keys.join(\", \")})`\n : `.using(${quote(spec.method)}, ${keys.join(\", \")})`;\n const where = spec.predicate ? `.where(sql\\`${renderPredicate(spec.predicate)}\\`)` : \"\";\n // drizzle-orm has no INCLUDE in its index builder (0.45), so a covering\n // index is emitted here as its key columns alone. The database still\n // gets the INCLUDE — `schema.sql` and boot-ensure both emit it.\n const covering = spec.include.length > 0\n ? ` // INCLUDE (${spec.include.join(\", \")}) — drizzle cannot express it; schema.sql does`\n : \"\";\n return ` ${builder}(${quote(spec.indexName)})${on}${where},${covering}`;\n });\n\nconst policyExtra = (policy: PolicyPlan, uses: BuilderUses): string => {\n needs(uses, \"pgPolicy\");\n const parts = [\n `as: \"${policy.mode}\"`,\n `for: \"${policy.operation}\"`,\n `to: [${policy.roles.map(r => `\"${r}\"`).join(\", \")}]`\n ];\n if (policy.using) parts.push(`using: ${wrapSql(policy.using)}`);\n if (policy.withCheck) parts.push(`withCheck: ${wrapSql(policy.withCheck)}`);\n return ` pgPolicy(${quote(policy.name)}, { ${parts.join(\", \")} }),`;\n};\n\nexport function renderDrizzleSchema(plan: SchemaPlan, options: DrizzleRenderOptions = {}): string {\n const withPolicies = options.policies !== false;\n const uses: BuilderUses = new Set();\n // The body is assembled first and the header composed around it, because\n // the header cannot be written until the body has said what it needs.\n let body = \"\";\n\n let schemaDeclarations = \"\";\n if (plan.declaredSchemas.length > 0) {\n needs(uses, \"pgSchema\");\n plan.declaredSchemas.forEach(schema => {\n schemaDeclarations += `export const ${schema}Schema = pgSchema(\"${schema}\");\\n`;\n });\n schemaDeclarations += \"\\n\";\n }\n\n const enumVars: string[] = [];\n for (const enumPlan of plan.enums) {\n // A collection with `schema: \"app\"` gets `CREATE TYPE \"app\".\"…\"` from\n // the DDL renderer, so the type has to be declared in the same schema\n // here — `pgEnum` is `public` and nothing else. Unqualified, the runtime\n // and drizzle-kit looked for a type that does not exist in `public`.\n const labels = enumPlan.labels.map(quote).join(\", \");\n const declaration = enumPlan.declaredSchema\n ? `${enumPlan.declaredSchema}Schema.enum(${quote(enumPlan.name)}, [${labels}])`\n : `${needs(uses, \"pgEnum\")}(${quote(enumPlan.name)}, [${labels}])`;\n body += `export const ${enumPlan.varName} = ${declaration};\\n`;\n if (!enumVars.includes(enumPlan.varName)) enumVars.push(enumPlan.varName);\n }\n body += \"\\n\";\n\n const tableVars: string[] = [];\n for (const table of plan.tables) {\n const creator = table.declaredSchema\n ? `${table.declaredSchema}Schema.table`\n : needs(uses, \"pgTable\");\n body += `export const ${table.varName} = ${creator}(\"${table.table}\", {\\n`;\n\n // The implicit `id` goes last here and first in the SQL file: the DDL\n // generator unshifts it so the key leads the `CREATE TABLE`, and this\n // one appends it after the generated search columns. Both describe the\n // same table; only the reading order differs.\n const ordered = [\n ...table.columns.filter(c => c.source.kind !== \"implicit-id\"),\n ...table.columns.filter(c => c.source.kind === \"implicit-id\")\n ].filter(c =>\n !c.columnOwnedByProperty\n // The auth columns a users collection does not itself declare —\n // `is_anonymous`, `tokens_valid_after`. `db push` emits them because\n // Atlas diffs the database against exactly what it is shown and\n // would otherwise plan a DROP; drizzle-kit creates no auth table, so\n // this file describes the collection as written and leaves auth's\n // own contract to the SQL side that owns it.\n && c.source.kind !== \"auth\");\n\n const lines = ordered.map(column => ` ${propKey(column.key)}: ${renderColumn(column, table, plan, uses)}`);\n // A junction's column list carries a trailing comma; a collection's does\n // not. Kept as the two files have always spelled them.\n body += table.kind === \"junction\"\n ? `${lines.join(\",\\n\")},\\n`\n : `${lines.join(\",\\n\")}\\n`;\n\n const extras: string[] = [];\n if (table.kind === \"junction\") {\n extras.push(` ${needs(uses, \"primaryKey\")}({ columns: [${table.primaryKey.map(c => member(\"table\", c)).join(\", \")}] }),`);\n } else {\n extras.push(...indexExtras(table, uses));\n }\n if (withPolicies) {\n extras.push(...table.policies.map(policy => policyExtra(policy, uses)));\n }\n\n if (extras.length > 0) {\n body += \"}, (table) => ([\\n\";\n body += `${extras.join(\"\\n\")}\\n`;\n body += \"])).enableRLS();\\n\\n\";\n } else {\n // No explicit policies and no declared indexes — RLS enabled with a\n // deny-all default (Postgres denies everything when RLS is on and no\n // permissive policies exist).\n body += \"}).enableRLS();\\n\\n\";\n }\n if (!tableVars.includes(table.varName)) tableVars.push(table.varName);\n }\n\n const relationVars: string[] = [];\n for (const table of plan.tables) {\n const entries = plan.relations.filter(r => r.tableVar === table.varName);\n if (entries.length === 0) continue;\n const rendered = entries.map(relation => renderRelation(table, relation));\n const varName = `${table.varName}Relations`;\n body += `export const ${varName} = drizzleRelations(${table.varName}, ({ one, many }) => ({\\n${rendered.join(\",\\n\")}\\n}));\\n\\n`;\n if (!relationVars.includes(varName)) relationVars.push(varName);\n }\n\n // ── The header, written last because the body decides what is in it ──────\n const imports = Array.from(uses).sort((a, b) =>\n a.replace(/^type /, \"\").localeCompare(b.replace(/^type /, \"\")));\n\n let out = \"// This file is auto-generated by the Rebase Drizzle generator. Do not edit manually.\\n\\n\";\n out += `import { ${imports.join(\", \")} } from 'drizzle-orm/pg-core';\\n`;\n out += \"import { relations as drizzleRelations, sql } from 'drizzle-orm';\\n\\n\";\n out += schemaDeclarations;\n out += body;\n out += `export const tables = { ${tableVars.join(\", \")} };\\n`;\n out += `export const enums = { ${enumVars.join(\", \")} };\\n`;\n out += `export const relations = { ${relationVars.join(\", \")} };\\n\\n`;\n return out;\n}\n\nconst renderRelation = (table: TablePlan, relation: RelationPlan): string => {\n if (relation.kind === \"many\") {\n return ` ${quote(relation.key)}: many(${relation.targetVar}, { relationName: ${quote(relation.relationName!)} })`;\n }\n // A `hasOne` inverse has no `fields`/`references` to give: the foreign key\n // lives on the target. `one(target, { relationName })` is not a\n // `RelationConfig` (TS2345) *and* not something the runtime survives —\n // `createOne` reads `config.fields.reduce(...)` unconditionally — so a bare\n // `one(target)` is the documented FK-less form.\n if (!relation.fields) return ` ${quote(relation.key)}: one(${relation.targetVar})`;\n return ` ${quote(relation.key)}: one(${relation.targetVar}, {\\n` +\n ` fields: [${relation.fields.map(f => member(table.varName, f)).join(\", \")}],\\n` +\n ` references: [${relation.references!.map(r => member(relation.targetVar, r)).join(\", \")}],\\n` +\n ` relationName: ${quote(relation.relationName!)}\\n` +\n \" })\";\n};\n","/**\n * `schema.generated.ts`, the Drizzle half of `rebase db generate`.\n *\n * There is no interpretation left in this file. `schema/plan/plan-schema.ts`\n * reads the collections into a {@link SchemaPlan} and\n * `schema/plan/render-drizzle.ts` writes the TypeScript; this is the entry\n * point they are reached through.\n *\n * It used to hold `getDrizzleColumn` — one of three `switch (prop.type)`\n * statements, each with its own reading of relations, enums, search and\n * `validation.unique`, and each disagreeing with the other two about something.\n * See `schema/plan/types.ts`.\n */\nimport type { CollectionConfig } from \"@rebasepro/types\";\nimport { relationalCollections, sortCollectionsBySlug } from \"@rebasepro/common\";\nimport { planSchema } from \"./plan/plan-schema\";\nimport { renderDrizzleSchema, type DrizzleRenderOptions } from \"./plan/render-drizzle\";\n\n/**\n * The generated Drizzle schema for a set of collections.\n *\n * Synchronous, and takes an options object. It was `async` with nothing to\n * await and a positional `stripPolicies` boolean — `generateSchema(c, true)`,\n * which reads as neither \"with policies\" nor \"without\".\n *\n * Sorted by slug here rather than in the writer script: the output is\n * order-dependent and `rebase doctor` regenerates it in memory to compare\n * against the file on disk, so with the sort in the writer only, a project\n * whose file order differed from its slug order was reported stale forever.\n */\nexport const generateSchema = (\n allCollections: CollectionConfig[],\n options: DrizzleRenderOptions = {}\n): string => {\n // A Firestore or MongoDB collection has no table to generate, and\n // generating one for it is not merely wasted output: `db push` would create\n // it, and `rebase doctor` would then report the store the collection\n // actually reads from as drift.\n const collections = sortCollectionsBySlug(relationalCollections(allCollections));\n return renderDrizzleSchema(planSchema(collections), options);\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"generate-drizzle-schema-logic-DFa9qy9u.js","names":[],"sources":["../src/schema/plan/render-drizzle.ts","../src/schema/generate-drizzle-schema-logic.ts"],"sourcesContent":["/**\n * `schema.generated.ts`, rendered from a {@link SchemaPlan}.\n *\n * This file is what a developer running drizzle-kit themselves diffs against,\n * and what the runtime's query builder is typed by. It has to *compile*, which\n * nothing in this repo checked until `generated-schema-compiles.test.ts`: six\n * ordinary inputs used to produce a file TypeScript rejects, and the class the\n * bigint incident already taught is that a file which will not build gets\n * hand-patched and then sits stale until a security fix misses production.\n *\n * Two structural rules keep that from coming back:\n *\n * - **The import list is derived from what was emitted.** Every builder is\n * recorded as it is used ({@link BuilderUses}), and the header is written\n * last, from that set. It used to be a fixed roster plus three\n * property-scanning heuristics, so `columnType: \"uuid\"` on a plain string\n * emitted `uuid(…)` and imported nothing.\n * - **Nothing here reads a `Property`.** Types, defaults, keys and constraints\n * all arrive decided. The Drizzle generator disagreeing with the DDL one is\n * what left `geopoint` with a database column and no Drizzle key.\n */\nimport type { ColumnPlan, ForeignKeyPlan, PgType, PolicyPlan, RelationPlan, SchemaPlan, TablePlan } from \"./types\";\nimport { renderPredicate } from \"../collection-index\";\n\n/** What the generated file may leave out. */\nexport interface DrizzleRenderOptions {\n /**\n * `false` strips the `pgPolicy(...)` calls. RLS is still enabled on every\n * table regardless — a bare table must default-deny, not fail open.\n */\n policies?: boolean;\n}\n\nconst JS_IDENTIFIER = /^[A-Za-z_$][A-Za-z0-9_$]*$/;\n\n/**\n * A string literal for the generated file.\n *\n * Column names, table names and enum values are all written in as literals and\n * none of them is constrained to be quote-free: a Postgres identifier only has\n * to be quoted, and `O'Brien` is an ordinary enum value. Interpolating them raw\n * ended the literal early.\n */\nconst quote = (value: string): string => JSON.stringify(value);\n\n/** An object key: verbatim when it is an identifier, quoted otherwise. */\nconst propKey = (name: string): string => (JS_IDENTIFIER.test(name) ? name : quote(name));\n\n/**\n * A property access on a generated table variable.\n *\n * `users.full name` is not an expression; `users[\"full name\"]` is, and Drizzle\n * treats the two identically.\n */\nconst member = (object: string, key: string): string =>\n (JS_IDENTIFIER.test(key) ? `${object}.${key}` : `${object}[${quote(key)}]`);\n\n/**\n * The drizzle-orm builders this file emitted, collected as they are written.\n * The only way to use a builder is to record it here.\n */\ntype BuilderUses = Set<string>;\n\n/** Record a builder and hand back its name, so a call site cannot use one silently. */\nconst needs = (uses: BuilderUses, builder: string): string => {\n uses.add(builder);\n return builder;\n};\n\n/**\n * Wraps a compiled SQL clause in a Drizzle `sql\\`...\\`` template literal.\n *\n * The clause is SQL being written into a TypeScript file, so it has to survive\n * being read back as a template literal. Three characters do not:\n *\n * - `` ` `` closes the template early, and the rest of the clause becomes code.\n * - `${` opens an interpolation — the file stops compiling, or worse, compiles\n * against whatever identifier happens to be in scope.\n * - `\\` is an escape, and Drizzle's `sql` tag reads the *cooked* strings, not\n * `.raw`. So a policy written as `email ~ '^admin\\.user@corp\\.com$'` reached\n * the database as `^admin.user@corp.com$`, where every `\\.` now matches any\n * character. A `USING` clause is a security boundary and that one silently\n * widened it.\n */\nconst wrapSql = (clause: string): string =>\n `sql\\`${clause.replace(/\\\\/g, \"\\\\\\\\\").replace(/`/g, \"\\\\`\").replace(/\\$\\{/g, \"\\\\${\")}\\``;\n\n/** The pg-core builder call for a tagged type, minus the column name. */\nconst builderFor = (type: PgType, column: string, uses: BuilderUses): string => {\n const name = quote(column);\n switch (type.kind) {\n case \"text\": return `${needs(uses, \"text\")}(${name})`;\n case \"varchar\": return `${needs(uses, \"varchar\")}(${name}, { length: ${type.length} })`;\n case \"char\": return `${needs(uses, \"char\")}(${name}, { length: ${type.length} })`;\n case \"uuid\": return `${needs(uses, \"uuid\")}(${name})`;\n // The enum variable is declared at the top of the same file; it is not\n // a pg-core import.\n case \"enum\": return `${type.varName}(${name})`;\n case \"smallint\": return `${needs(uses, \"smallint\")}(${name})`;\n case \"integer\": return `${needs(uses, \"integer\")}(${name})`;\n // `bigint` and `bigserial` are the only pg-core builders that *require*\n // a config argument: without `mode`, drizzle cannot know whether to hand\n // back a `number` or a `bigint`, and the emitted call does not\n // typecheck. This is why `schema.generated.ts` drifted — regenerating it\n // produced a file that would not compile, so the bigint lines were\n // hand-patched, and every regeneration after that looked like a large,\n // alarming diff nobody wanted to ship.\n //\n // `number` rather than `bigint`: these are counters and byte totals that\n // every caller already treats as numbers.\n case \"bigint\": return `${needs(uses, \"bigint\")}(${name}, { mode: \"number\" })`;\n case \"bigserial\": return `${needs(uses, \"bigserial\")}(${name}, { mode: \"number\" })`;\n case \"smallserial\": return `${needs(uses, \"smallserial\")}(${name})`;\n case \"serial\": return `${needs(uses, \"serial\")}(${name})`;\n case \"real\": return `${needs(uses, \"real\")}(${name})`;\n case \"doublePrecision\": return `${needs(uses, \"doublePrecision\")}(${name})`;\n case \"numeric\":\n if (type.precision === undefined) return `${needs(uses, \"numeric\")}(${name})`;\n return type.scale === undefined\n ? `${needs(uses, \"numeric\")}(${name}, { precision: ${type.precision} })`\n : `${needs(uses, \"numeric\")}(${name}, { precision: ${type.precision}, scale: ${type.scale} })`;\n case \"boolean\": return `${needs(uses, \"boolean\")}(${name})`;\n case \"timestamptz\": return `${needs(uses, \"timestamp\")}(${name}, { withTimezone: true, mode: 'string' })`;\n case \"date\": return `${needs(uses, \"date\")}(${name}, { mode: 'string' })`;\n case \"time\": return `${needs(uses, \"time\")}(${name})`;\n case \"json\": return `${needs(uses, \"json\")}(${name})`;\n case \"jsonb\": return `${needs(uses, \"jsonb\")}(${name})`;\n case \"vector\": return `${needs(uses, \"vector\")}(${name}, { dimensions: ${type.dimensions} })`;\n case \"bytea\": return `${needs(uses, \"customType\")}({ dataType() { return 'bytea'; } })(${name})`;\n case \"tsvector\": return `${needs(uses, \"customType\")}({ dataType() { return 'tsvector'; } })(${name})`;\n // pg-core has no array *type*; `.array()` is a modifier on the element.\n case \"array\": return `${builderFor(type.of, column, uses)}.array()`;\n }\n};\n\n/**\n * The `.references(...)` clause a foreign key column carries.\n *\n * The `(): AnyPgColumn =>` annotation is not optional decoration and it is\n * emitted on every reference rather than only the self-referential ones. A\n * `belongsTo` pointing at its own table — a comment thread, a category tree —\n * produces `.references(() => posts.id)` inside the initializer of `posts`, and\n * TypeScript cannot infer a type for a `const` that appears in its own\n * initializer (TS7022). Drizzle documents the annotation as the fix; applying\n * it everywhere means the generated line does not depend on whether the author\n * happened to point the relation at another table.\n */\nconst referencesClause = (\n fk: ForeignKeyPlan,\n targetVar: string,\n targetKey: string,\n uses: BuilderUses\n): string => {\n needs(uses, \"type AnyPgColumn\");\n const parts = [\n fk.onUpdate ? `onUpdate: \"${fk.onUpdate.toLowerCase()}\"` : \"\",\n `onDelete: \"${fk.onDelete.toLowerCase()}\"`\n ].filter(Boolean);\n return `.references((): AnyPgColumn => ${member(targetVar, targetKey)}, { ${parts.join(\", \")} })`;\n};\n\n/** The declaration a column compiles to, without its object key. */\nconst renderColumn = (column: ColumnPlan, table: TablePlan, plan: SchemaPlan, uses: BuilderUses): string => {\n let out = builderFor(column.type, column.column, uses);\n\n if (column.generated) {\n return `${out}.generatedAlwaysAs(${wrapSql(column.generated.expression)})`;\n }\n\n // A junction endpoint spells `.notNull()` before its reference; every other\n // column spells it last. Both are the same constraint — this keeps the\n // generated file byte-stable across the move to one planner.\n const junctionKey = column.source.kind === \"junction-key\";\n if (junctionKey) out += \".notNull()\";\n\n if (column.default?.kind === \"identity\") out += \".generatedByDefaultAsIdentity()\";\n if (column.primaryKey) out += \".primaryKey()\";\n if (column.default && column.default.kind !== \"identity\") {\n const expression = column.default.kind === \"sql\" ? column.default.expression : column.default.sql;\n // `.defaultRandom()` is drizzle's own spelling of `gen_random_uuid()`,\n // and it exists **only on the uuid builder** — a `text` id whose\n // strategy happens to be spelled ``isId: \"sql`gen_random_uuid()`\"`` has\n // to take the generic form, which is what the file has always emitted\n // for it.\n out += column.type.kind === \"uuid\" && expression === \"gen_random_uuid()\"\n ? \".defaultRandom()\"\n : `.default(${wrapSql(expression)})`;\n }\n // A plain property that carries a relation's foreign key column gets the\n // constraint the relation would have emitted. `postId: { columnName:\n // \"post_id\" }` beside a `belongsTo` on `post_id` used to emit an integer\n // with no `.references()` at all, so that project got no foreign key on the\n // Drizzle side while `db push` created one.\n const foreignKey = column.foreignKey\n ?? table.columns.find(c => c.columnOwnedByProperty && c.column === column.column)?.foreignKey;\n if (foreignKey) {\n const target = plan.tables.find(t =>\n t.schema === foreignKey.targetSchema && t.table === foreignKey.targetTable);\n if (target) {\n const targetColumn = target.columns.find(c => c.column === foreignKey.targetColumn);\n out += referencesClause(foreignKey, target.varName, targetColumn?.key ?? foreignKey.targetColumn, uses);\n }\n }\n if (column.unique) out += \".unique()\";\n if (!column.nullable && !column.primaryKey && !junctionKey) out += \".notNull()\";\n return out;\n};\n\n/** The declared `indexes:` block, as Drizzle table extras. */\nconst indexExtras = (table: TablePlan, uses: BuilderUses): string[] =>\n table.indexes.map(spec => {\n const builder = spec.unique ? needs(uses, \"uniqueIndex\") : needs(uses, \"index\");\n // The spec holds COLUMN names; the generated table is keyed by field\n // name (`authorId` for `author_id`).\n const column = (name: string): string =>\n member(\"table\", table.columns.find(c => c.column === name)?.key ?? name);\n const keys = spec.keys.map(key => {\n if (spec.method !== \"btree\") return column(key.column);\n const direction = key.direction === \"desc\" ? \".desc()\" : \".asc()\";\n const nulls = key.nulls === \"first\" ? \".nullsFirst()\" : \".nullsLast()\";\n return `${column(key.column)}${direction}${nulls}`;\n });\n const on = spec.method === \"btree\"\n ? `.on(${keys.join(\", \")})`\n : `.using(${quote(spec.method)}, ${keys.join(\", \")})`;\n const where = spec.predicate ? `.where(sql\\`${renderPredicate(spec.predicate)}\\`)` : \"\";\n // drizzle-orm has no INCLUDE in its index builder (0.45), so a covering\n // index is emitted here as its key columns alone. The database still\n // gets the INCLUDE — `schema.sql` and boot-ensure both emit it.\n const covering = spec.include.length > 0\n ? ` // INCLUDE (${spec.include.join(\", \")}) — drizzle cannot express it; schema.sql does`\n : \"\";\n return ` ${builder}(${quote(spec.indexName)})${on}${where},${covering}`;\n });\n\nconst policyExtra = (policy: PolicyPlan, uses: BuilderUses): string => {\n needs(uses, \"pgPolicy\");\n const parts = [\n `as: \"${policy.mode}\"`,\n `for: \"${policy.operation}\"`,\n `to: [${policy.roles.map(r => `\"${r}\"`).join(\", \")}]`\n ];\n if (policy.using) parts.push(`using: ${wrapSql(policy.using)}`);\n if (policy.withCheck) parts.push(`withCheck: ${wrapSql(policy.withCheck)}`);\n return ` pgPolicy(${quote(policy.name)}, { ${parts.join(\", \")} }),`;\n};\n\nexport function renderDrizzleSchema(plan: SchemaPlan, options: DrizzleRenderOptions = {}): string {\n const withPolicies = options.policies !== false;\n const uses: BuilderUses = new Set();\n // The body is assembled first and the header composed around it, because\n // the header cannot be written until the body has said what it needs.\n let body = \"\";\n\n let schemaDeclarations = \"\";\n if (plan.declaredSchemas.length > 0) {\n needs(uses, \"pgSchema\");\n plan.declaredSchemas.forEach(schema => {\n schemaDeclarations += `export const ${schema}Schema = pgSchema(\"${schema}\");\\n`;\n });\n schemaDeclarations += \"\\n\";\n }\n\n const enumVars: string[] = [];\n for (const enumPlan of plan.enums) {\n // A collection with `schema: \"app\"` gets `CREATE TYPE \"app\".\"…\"` from\n // the DDL renderer, so the type has to be declared in the same schema\n // here — `pgEnum` is `public` and nothing else. Unqualified, the runtime\n // and drizzle-kit looked for a type that does not exist in `public`.\n const labels = enumPlan.labels.map(quote).join(\", \");\n const declaration = enumPlan.declaredSchema\n ? `${enumPlan.declaredSchema}Schema.enum(${quote(enumPlan.name)}, [${labels}])`\n : `${needs(uses, \"pgEnum\")}(${quote(enumPlan.name)}, [${labels}])`;\n body += `export const ${enumPlan.varName} = ${declaration};\\n`;\n if (!enumVars.includes(enumPlan.varName)) enumVars.push(enumPlan.varName);\n }\n body += \"\\n\";\n\n const tableVars: string[] = [];\n for (const table of plan.tables) {\n const creator = table.declaredSchema\n ? `${table.declaredSchema}Schema.table`\n : needs(uses, \"pgTable\");\n body += `export const ${table.varName} = ${creator}(\"${table.table}\", {\\n`;\n\n // The implicit `id` goes last here and first in the SQL file: the DDL\n // generator unshifts it so the key leads the `CREATE TABLE`, and this\n // one appends it after the generated search columns. Both describe the\n // same table; only the reading order differs.\n const ordered = [\n ...table.columns.filter(c => c.source.kind !== \"implicit-id\"),\n ...table.columns.filter(c => c.source.kind === \"implicit-id\")\n ].filter(c =>\n !c.columnOwnedByProperty\n // The auth columns a users collection does not itself declare —\n // `is_anonymous`, `tokens_valid_after`. `db push` emits them because\n // Atlas diffs the database against exactly what it is shown and\n // would otherwise plan a DROP; drizzle-kit creates no auth table, so\n // this file describes the collection as written and leaves auth's\n // own contract to the SQL side that owns it.\n && c.source.kind !== \"auth\");\n\n const lines = ordered.map(column => ` ${propKey(column.key)}: ${renderColumn(column, table, plan, uses)}`);\n // A junction's column list carries a trailing comma; a collection's does\n // not. Kept as the two files have always spelled them.\n body += table.kind === \"junction\"\n ? `${lines.join(\",\\n\")},\\n`\n : `${lines.join(\",\\n\")}\\n`;\n\n const extras: string[] = [];\n if (table.kind === \"junction\") {\n extras.push(` ${needs(uses, \"primaryKey\")}({ columns: [${table.primaryKey.map(c => member(\"table\", c)).join(\", \")}] }),`);\n } else {\n extras.push(...indexExtras(table, uses));\n }\n if (withPolicies) {\n extras.push(...table.policies.map(policy => policyExtra(policy, uses)));\n }\n\n if (extras.length > 0) {\n body += \"}, (table) => ([\\n\";\n body += `${extras.join(\"\\n\")}\\n`;\n body += \"])).enableRLS();\\n\\n\";\n } else {\n // No explicit policies and no declared indexes — RLS enabled with a\n // deny-all default (Postgres denies everything when RLS is on and no\n // permissive policies exist).\n body += \"}).enableRLS();\\n\\n\";\n }\n if (!tableVars.includes(table.varName)) tableVars.push(table.varName);\n }\n\n const relationVars: string[] = [];\n for (const table of plan.tables) {\n const entries = plan.relations.filter(r => r.tableVar === table.varName);\n if (entries.length === 0) continue;\n const rendered = entries.map(relation => renderRelation(table, relation));\n const varName = `${table.varName}Relations`;\n body += `export const ${varName} = drizzleRelations(${table.varName}, ({ one, many }) => ({\\n${rendered.join(\",\\n\")}\\n}));\\n\\n`;\n if (!relationVars.includes(varName)) relationVars.push(varName);\n }\n\n // ── The header, written last because the body decides what is in it ──────\n const imports = Array.from(uses).sort((a, b) =>\n a.replace(/^type /, \"\").localeCompare(b.replace(/^type /, \"\")));\n\n let out = \"// This file is auto-generated by the Rebase Drizzle generator. Do not edit manually.\\n\\n\";\n out += `import { ${imports.join(\", \")} } from 'drizzle-orm/pg-core';\\n`;\n out += \"import { relations as drizzleRelations, sql } from 'drizzle-orm';\\n\\n\";\n out += schemaDeclarations;\n out += body;\n out += `export const tables = { ${tableVars.join(\", \")} };\\n`;\n out += `export const enums = { ${enumVars.join(\", \")} };\\n`;\n out += `export const relations = { ${relationVars.join(\", \")} };\\n\\n`;\n return out;\n}\n\nconst renderRelation = (table: TablePlan, relation: RelationPlan): string => {\n if (relation.kind === \"many\") {\n return ` ${quote(relation.key)}: many(${relation.targetVar}, { relationName: ${quote(relation.relationName!)} })`;\n }\n // A `hasOne` inverse has no `fields`/`references` to give: the foreign key\n // lives on the target. `one(target, { relationName })` is not a\n // `RelationConfig` (TS2345) *and* not something the runtime survives —\n // `createOne` reads `config.fields.reduce(...)` unconditionally — so a bare\n // `one(target)` is the documented FK-less form.\n if (!relation.fields) return ` ${quote(relation.key)}: one(${relation.targetVar})`;\n return ` ${quote(relation.key)}: one(${relation.targetVar}, {\\n` +\n ` fields: [${relation.fields.map(f => member(table.varName, f)).join(\", \")}],\\n` +\n ` references: [${relation.references!.map(r => member(relation.targetVar, r)).join(\", \")}],\\n` +\n ` relationName: ${quote(relation.relationName!)}\\n` +\n \" })\";\n};\n","/**\n * `schema.generated.ts`, the Drizzle half of `rebase db generate`.\n *\n * There is no interpretation left in this file. `schema/plan/plan-schema.ts`\n * reads the collections into a {@link SchemaPlan} and\n * `schema/plan/render-drizzle.ts` writes the TypeScript; this is the entry\n * point they are reached through.\n *\n * It used to hold `getDrizzleColumn` — one of three `switch (prop.type)`\n * statements, each with its own reading of relations, enums, search and\n * `validation.unique`, and each disagreeing with the other two about something.\n * See `schema/plan/types.ts`.\n */\nimport type { CollectionConfig } from \"@rebasepro/types\";\nimport { relationalCollections, sortCollectionsBySlug } from \"@rebasepro/common\";\nimport { planSchema } from \"./plan/plan-schema\";\nimport { renderDrizzleSchema, type DrizzleRenderOptions } from \"./plan/render-drizzle\";\n\n/**\n * The generated Drizzle schema for a set of collections.\n *\n * Synchronous, and takes an options object. It was `async` with nothing to\n * await and a positional `stripPolicies` boolean — `generateSchema(c, true)`,\n * which reads as neither \"with policies\" nor \"without\".\n *\n * Sorted by slug here rather than in the writer script: the output is\n * order-dependent and `rebase doctor` regenerates it in memory to compare\n * against the file on disk, so with the sort in the writer only, a project\n * whose file order differed from its slug order was reported stale forever.\n */\nexport const generateSchema = (\n allCollections: CollectionConfig[],\n options: DrizzleRenderOptions = {}\n): string => {\n // A Firestore or MongoDB collection has no table to generate, and\n // generating one for it is not merely wasted output: `db push` would create\n // it, and `rebase doctor` would then report the store the collection\n // actually reads from as drift.\n const collections = sortCollectionsBySlug(relationalCollections(allCollections));\n return renderDrizzleSchema(planSchema(collections), options);\n};\n"],"mappings":"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"}
|
package/dist/{generated-schema-staleness-DRQe2BpC.js → generated-schema-staleness-Di9c4ZxN.js}
RENAMED
|
@@ -133,15 +133,18 @@ function staleVerdict(input) {
|
|
|
133
133
|
exitCode: 0,
|
|
134
134
|
regenerate: false
|
|
135
135
|
};
|
|
136
|
-
|
|
136
|
+
const problems = [];
|
|
137
|
+
if (input.stale.length > 0) problems.push(` ⚠️ ${input.outputPath} names ${input.stale.length} foreign key(s) the way an earlier release did:`, describeLegacyForeignKeyNames(input.stale));
|
|
138
|
+
const differences = input.differences ?? [];
|
|
139
|
+
if (differences.length > 0) problems.push(` ⚠️ ${input.outputPath} is not what the current collections generate.`, ` Differs in: ${describeDeclarationDifferences(differences)}.`);
|
|
140
|
+
if (problems.length === 0) return {
|
|
137
141
|
lines: quiet([` ✓ Nothing stale — 1 generated file matches (${input.outputPath}).`]),
|
|
138
142
|
exitCode: 0,
|
|
139
143
|
regenerate: false
|
|
140
144
|
};
|
|
141
145
|
const found = [
|
|
142
146
|
"",
|
|
143
|
-
|
|
144
|
-
describeLegacyForeignKeyNames(input.stale),
|
|
147
|
+
...problems,
|
|
145
148
|
""
|
|
146
149
|
];
|
|
147
150
|
if (input.fix) return {
|
|
@@ -152,7 +155,8 @@ function staleVerdict(input) {
|
|
|
152
155
|
return {
|
|
153
156
|
lines: [
|
|
154
157
|
...found,
|
|
155
|
-
" The database column has already been renamed at boot.
|
|
158
|
+
...input.stale.length > 0 ? [" The database column has already been renamed at boot."] : [],
|
|
159
|
+
" The server reads the database, so it will still start — but `db push`, Atlas, `eject` and any code importing this file are all reading a description that no longer matches.",
|
|
156
160
|
" Run `rebase schema generate` to regenerate it."
|
|
157
161
|
],
|
|
158
162
|
exitCode: 1,
|
|
@@ -232,4 +236,4 @@ function describeDeclarationDifferences(differences) {
|
|
|
232
236
|
//#endregion
|
|
233
237
|
export { describeDeclarationDifferences as n, generated_schema_staleness_exports as r, compareGeneratedDeclarations as t };
|
|
234
238
|
|
|
235
|
-
//# sourceMappingURL=generated-schema-staleness-
|
|
239
|
+
//# sourceMappingURL=generated-schema-staleness-Di9c4ZxN.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"generated-schema-staleness-DRQe2BpC.js","names":[],"sources":["../src/schema/generated-schema-staleness.ts"],"sourcesContent":["import { CollectionConfig } from \"@rebasepro/types\";\nimport { getTableName, resolveCollectionRelations, relationalCollections } from \"@rebasepro/common\";\nimport { generateForeignKeyName, legacyForeignKeyName } from \"@rebasepro/utils\";\n\n/**\n * Notice a generated Drizzle schema that a *library upgrade* invalidated.\n *\n * `rebase dev` already watches `config/collections` and warns when a collection\n * file changes. That covers drift the developer caused. It cannot cover this one,\n * because nothing the developer owns changed: 0.13 derives `category_id` where\n * 0.12 derived `categorie_id`, from the same unedited collection. The watcher\n * never fires, and `backend/src/schema.generated.ts` quietly stops describing the\n * schema the runtime expects.\n *\n * The consequence is not cosmetic. Boot-ensure renames the column in the\n * database, then relation validation reads the stale module and refuses to\n * start — on that boot and every boot after it, because the rename is already\n * applied and will not be attempted again.\n *\n * Deliberately narrow: this answers \"does the generated schema name a foreign key\n * the way the previous rule did\", not \"is this file what we would generate now\".\n * The wide question would report every whitespace change in the generator as a\n * fatal staleness, and a check that cries wolf gets switched off.\n */\n\n/** One column the generated schema names under the pre-0.13 rule. */\nexport interface LegacyForeignKeyName {\n /** Table whose column declaration is stale. */\n table: string;\n /** The name the generated schema declares. */\n legacy: string;\n /** The name this release derives, and which the database now carries. */\n current: string;\n /** `<collection>.<relation>` that derives it, for the message. */\n relation: string;\n}\n\n/**\n * The slice of the generated source declaring one table.\n *\n * Scoping matters: two junctions in one file can carry columns of the same name,\n * and a whole-file match would attribute a stale column to whichever table the\n * reader looks at first. Returns \"\" when the table is not in the file at all,\n * which is not staleness — it is a table the generator has not been asked about.\n */\nfunction tableBlock(source: string, table: string): string {\n // Both forms the generator emits. A collection with `schema: \"rebase\"` — the\n // auth users collection, in every scaffold — is written as\n // `rebaseSchema.table(\"users\", …)`, so matching only `pgTable(` made this\n // blind to every table outside `public`: the block came back empty, and an\n // empty block reads as \"a table the generator has not been asked about\".\n // The rename check therefore never inspected them.\n const literal = table.replace(/[.*+?^${}()|[\\]\\\\]/g, \"\\\\$&\");\n const start = source.search(new RegExp(`(?:pgTable|\\\\.table)\\\\(\\\\s*[\"']${literal}[\"']`));\n if (start === -1) return \"\";\n // Generated files put every table in its own `export const`, so the next one\n // is the end of this block. No brace counting, nothing to get wrong.\n const next = source.indexOf(\"\\nexport \", start);\n return next === -1 ? source.slice(start) : source.slice(start, next);\n}\n\n/**\n * Whether a block *declares* the column, rather than merely mentioning it.\n *\n * A comment explaining the rename, or a policy expression naming the old column,\n * must not read as a declaration — otherwise regenerating the file would not\n * clear the finding and the check would be permanently red.\n */\nfunction declaresColumn(block: string, column: string): boolean {\n // `categorieId: integer(\"categorie_id\")` — the Drizzle column shape. Only\n // the string argument is the column; the key beside it is the *wire* name\n // and no longer agrees with it (`authorId: integer(\"author_id\")`). Matching\n // both, as this did, made every camelCased key look like a column the\n // generated file did not declare.\n const literal = column.replace(/[.*+?^${}()|[\\]\\\\]/g, \"\\\\$&\");\n return new RegExp(`(^|[\\\\s,{])[\\\\w$\"']+\\\\s*:\\\\s*\\\\w+\\\\(\\\\s*[\"']${literal}[\"']`, \"m\").test(block);\n}\n\n/**\n * @param generatedSource contents of `backend/src/schema.generated.ts`\n * @param collections the project's collections, as this release reads them\n */\nexport function findLegacyForeignKeyNames(\n generatedSource: string,\n collections: CollectionConfig[]\n): LegacyForeignKeyName[] {\n const found: LegacyForeignKeyName[] = [];\n const seen = new Set<string>();\n\n /**\n * Report `wanted` as stale when the generated schema declares what the\n * previous rule would have derived from `source` instead.\n *\n * The `wanted !== current` guard is what honours an explicitly named column.\n * An author who pinned `categorie_id` has said so on the relation, so the\n * generated file agreeing with them is correct, not stale — and the rename\n * note documents exactly that opt-out.\n */\n const consider = (\n table: string,\n wanted: string,\n sourceName: string | undefined,\n relation: string\n ): void => {\n if (!table || !wanted || !sourceName) return;\n\n const current = generateForeignKeyName(sourceName);\n const legacy = legacyForeignKeyName(sourceName);\n if (legacy === current) return; // this name never moved\n if (wanted !== current) return; // pinned by the author, or unrelated\n\n const block = tableBlock(generatedSource, table);\n if (!block) return;\n if (!declaresColumn(block, legacy)) return;\n if (declaresColumn(block, current)) return; // already regenerated\n\n const key = `${table}.${legacy}`;\n if (seen.has(key)) return;\n seen.add(key);\n found.push({ table, legacy, current, relation });\n };\n\n // Same rule as the generator whose output this reads: a collection that\n // gets no table cannot have named a column in it. Inert in practice today —\n // the finding also requires the generated file to declare the table — but\n // the guard is what keeps that true when a non-SQL collection's slug happens\n // to match a SQL one's table.\n for (const collection of relationalCollections(collections)) {\n const sourceTable = getTableName(collection);\n\n for (const [name, relation] of Object.entries(resolveCollectionRelations(collection))) {\n const at = `${collection.slug}.${name}`;\n\n let target: CollectionConfig | undefined;\n try {\n target = relation.target?.();\n } catch {\n // A throwing target thunk is its own defect, reported at boot by\n // `validate-relations`. Nothing to say about its column names.\n continue;\n }\n\n switch (relation.kind) {\n case \"belongsTo\":\n consider(sourceTable, relation.localKey, relation.relationName, at);\n break;\n\n case \"hasOne\":\n case \"hasMany\":\n if (target) {\n consider(getTableName(target), relation.foreignKeyOnTarget, collection.slug, at);\n }\n break;\n\n case \"manyToMany\":\n consider(relation.through.table, relation.through.sourceColumn, collection.slug, at);\n if (target) {\n consider(relation.through.table, relation.through.targetColumn, target.slug, at);\n }\n break;\n\n default:\n // `via` joins are written by hand — there is no derived name\n // for the rule change to have moved.\n break;\n }\n }\n }\n\n return found;\n}\n\n/** One-line summary for a log or a CLI notice. */\nexport function describeLegacyForeignKeyNames(found: LegacyForeignKeyName[]): string {\n return found\n .map(f => ` • ${f.table}.${f.legacy} → ${f.current} (${f.relation})`)\n .join(\"\\n\");\n}\n\n// ── The other way a generated schema goes stale ──────────────────────────────\n//\n// The rename above is the subtle one: nothing the developer owns changed. The\n// ordinary one is the opposite — a collection or a relation was added and the\n// generator was not re-run, so the checked-in module simply does not describe\n// part of the schema. It fails later and elsewhere: relation validation, or a\n// query against a table the module has never heard of.\n//\n// Still not \"is this byte-for-byte what the generator would emit now\". This asks\n// only about *names*, which is what the rest of the system reads out of the\n// file, so reformatting the generator cannot make it red.\n\n/** Something the collections derive that the generated schema does not declare. */\nexport interface MissingGeneratedName {\n /** The table it belongs to, or is. */\n table: string;\n /** The column, when a column is what is missing. */\n column?: string;\n /** `<collection>` or `<collection>.<relation>`, for the message. */\n source: string;\n}\n\n/**\n * Tables and derived foreign-key columns the collections name and the generated\n * schema does not.\n *\n * @param generatedSource contents of `backend/src/schema.generated.ts`\n * @param collections the project's collections, as this release reads them\n */\nexport function findMissingGeneratedNames(\n generatedSource: string,\n collections: CollectionConfig[]\n): MissingGeneratedName[] {\n const missing: MissingGeneratedName[] = [];\n const seen = new Set<string>();\n\n const report = (table: string, source: string, column?: string): void => {\n const key = `${table}.${column ?? \"\"}`;\n if (seen.has(key)) return;\n seen.add(key);\n missing.push({ table, source, ...(column ? { column } : {}) });\n };\n\n const requireColumn = (table: string, column: string | undefined, source: string): void => {\n if (!table || !column) return;\n const block = tableBlock(generatedSource, table);\n // A table that is absent is reported once, as a table. Listing each of\n // its columns as separately missing would bury the one fact that\n // explains all of them.\n if (!block) return report(table, source);\n if (!declaresColumn(block, column)) report(table, source, column);\n };\n\n for (const collection of relationalCollections(collections)) {\n const sourceTable = getTableName(collection);\n if (!tableBlock(generatedSource, sourceTable)) {\n report(sourceTable, collection.slug);\n continue;\n }\n\n for (const [name, relation] of Object.entries(resolveCollectionRelations(collection))) {\n const at = `${collection.slug}.${name}`;\n\n let target: CollectionConfig | undefined;\n try {\n target = relation.target?.();\n } catch {\n // Its own defect, reported at boot by `validate-relations`.\n continue;\n }\n\n switch (relation.kind) {\n case \"belongsTo\":\n requireColumn(sourceTable, relation.localKey, at);\n break;\n\n case \"hasOne\":\n case \"hasMany\":\n if (target) requireColumn(getTableName(target), relation.foreignKeyOnTarget, at);\n break;\n\n case \"manyToMany\":\n requireColumn(relation.through.table, relation.through.sourceColumn, at);\n requireColumn(relation.through.table, relation.through.targetColumn, at);\n break;\n\n default:\n // `via` joins are written by hand; there is no derived name\n // to be missing.\n break;\n }\n }\n }\n\n return missing;\n}\n\n/** One-line summary for a log or a CLI notice. */\nexport function describeMissingGeneratedNames(missing: MissingGeneratedName[]): string {\n return missing\n .map(m => (m.column\n ? ` • ${m.table}.${m.column} is not declared (${m.source})`\n : ` • table ${m.table} is not declared (${m.source})`))\n .join(\"\\n\");\n}\n\n/** What `rebase schema stale` found, and what that costs. */\nexport interface StaleVerdict {\n /** The lines to print, in order. Empty only under `--fix` with nothing to fix. */\n lines: string[];\n /** Non-zero only when the reader must act before the server will boot. */\n exitCode: 0 | 1;\n /** True when `--fix` should regenerate. */\n regenerate: boolean;\n}\n\n/**\n * The whole of `schema stale`'s decision, separated from doing it.\n *\n * Separated because of what the command used to do on the clean path: **exit 0\n * having written zero bytes to stdout and zero to stderr.** For a command the\n * top-level help describes as \"Report generated schema files the collections\n * have moved past\", that is indistinguishable from a no-op, a crash, and a\n * subcommand the driver does not implement. Three of its four paths were\n * silent, and none of them could be tested where they stood: `cli.ts` uses\n * `import.meta`, which the driver's jest suite cannot load at all.\n *\n * `--fix` is the exception, and stays silent when there is nothing to fix.\n * `rebase dev` runs `schema stale --fix` before every boot with inherited\n * stdio, and \"nothing was wrong\" is not news a hundred lines into a start-up\n * transcript. The flag is the automated caller's; its silence is the point.\n */\nexport function staleVerdict(input: {\n /** `--output`, as the reader typed it. */\n outputPath: string;\n /** Whether the generated schema exists at all. */\n generatedExists: boolean;\n /** The legacy foreign-key names found in it, if it was read. */\n stale: LegacyForeignKeyName[];\n /** Why the comparison could not be made, when it could not. */\n unreadable?: string;\n /** Whether `--fix` was given. */\n fix: boolean;\n}): StaleVerdict {\n const quiet = (lines: string[]): string[] => (input.fix ? [] : lines);\n\n if (!input.generatedExists) {\n // Not staleness: a fresh project has not run the generator, and saying\n // \"stale\" about a file that does not exist sends the reader looking for\n // something to fix.\n return {\n lines: quiet([` No generated schema yet at ${input.outputPath} — nothing to compare.`,\n \" `rebase schema generate` creates it.\"]),\n exitCode: 0,\n regenerate: false\n };\n }\n\n if (input.unreadable !== undefined) {\n // Best-effort by design: a collections directory that will not load is a\n // real error, but boot reports it far better than this does. Saying so\n // is still better than exiting 0 in silence about a check that did not\n // run — this file's whole subject.\n return {\n lines: quiet([` ⏭ Not checked: ${input.unreadable}`]),\n exitCode: 0,\n regenerate: false\n };\n }\n\n if (input.stale.length === 0) {\n return {\n lines: quiet([` ✓ Nothing stale — 1 generated file matches (${input.outputPath}).`]),\n exitCode: 0,\n regenerate: false\n };\n }\n\n const found = [\n \"\",\n ` ⚠️ ${input.outputPath} names ${input.stale.length} foreign key(s) the way an earlier release did:`,\n describeLegacyForeignKeyNames(input.stale),\n \"\"\n ];\n\n if (input.fix) {\n return { lines: [...found, \" Regenerating the Drizzle schema so it matches...\"], exitCode: 0, regenerate: true };\n }\n\n return {\n lines: [\n ...found,\n // Not \"the server will refuse to start\" any more. The runtime builds\n // its tables from `information_schema`, so a stale file changes\n // nothing it serves — it warns once at boot and carries on. What the\n // file still decides is everything around the server: Atlas plans\n // migrations from it, `db push` diffs it, `eject` writes it out, and\n // user code imports it.\n \" The database column has already been renamed at boot. The server reads the database, \"\n + \"so it will still start — but `db push`, Atlas, `eject` and any code importing this \"\n + \"file are all reading a description that no longer matches.\",\n \" Run `rebase schema generate` to regenerate it.\"\n ],\n exitCode: 1,\n regenerate: false\n };\n}\n\n\n// ── Is this file what the current collections generate? ──────────────────────\n\n/** One exported declaration that differs between two generated schemas. */\nexport interface DeclarationDifference {\n /** The exported symbol: a table, an enum, a relations block. */\n name: string;\n kind: \"missing\" | \"unexpected\" | \"changed\";\n}\n\n/**\n * `export const NAME = …` → the declaration, with formatting and the\n * generator's own comments removed.\n *\n * Every generated declaration starts at column 0 and runs to the next one, so\n * splitting on them needs no brace counting and cannot get out of step with a\n * policy clause that happens to contain a brace.\n */\nfunction declarations(source: string): Map<string, string> {\n const out = new Map<string, string>();\n const starts = [...source.matchAll(/^export const (\\w+) =/gm)];\n starts.forEach((match, index) => {\n const from = match.index! + match[0].length;\n const to = index + 1 < starts.length ? starts[index + 1].index! : source.length;\n out.set(\n match[1],\n source.slice(from, to)\n // The `// INCLUDE (...)` note on a covering index, and any other\n // remark the renderer leaves: they describe the declaration,\n // they are not part of it.\n .replace(/\\/\\/[^\\n]*/g, \"\")\n .replace(/\\s+/g, \" \")\n .trim()\n );\n });\n return out;\n}\n\n/**\n * What differs between the schema the current collections generate and the one\n * on disk — by declaration, not by byte.\n *\n * `rebase doctor` used to whitespace-normalise both files and compare the\n * strings, which answers \"are these the same text\" when the question is \"does\n * this file still describe the same schema\". The difference is not academic:\n * the answer was a single yes/no with nothing to act on, so a developer told\n * their schema was stale had to regenerate and read the diff to find out what\n * had changed.\n *\n * Comparing declarations answers the question the reader actually has. The\n * generated file is a flat list of `export const`s — one per table, enum and\n * relations block, each the rendering of a piece of the schema plan — and two\n * files declaring the same symbols with the same bodies describe the same\n * database whatever their formatting.\n *\n * Not a plan-to-plan comparison, and deliberately not: reading a plan back out\n * of `schema.generated.ts` means interpreting drizzle-orm builder calls, which\n * is a second interpreter — the exact thing this area was just rid of. The\n * rendered declarations are the closest honest proxy, and the renderer is\n * deterministic, so the only things normalised away are what it is free to vary\n * independently of the plan: whitespace and its own comments.\n */\nexport function compareGeneratedDeclarations(expected: string, actual: string): DeclarationDifference[] {\n const want = declarations(expected);\n const have = declarations(actual);\n const differences: DeclarationDifference[] = [];\n for (const [name, body] of want) {\n if (!have.has(name)) differences.push({ name, kind: \"missing\" });\n else if (have.get(name) !== body) differences.push({ name, kind: \"changed\" });\n }\n for (const name of have.keys()) {\n if (!want.has(name)) differences.push({ name, kind: \"unexpected\" });\n }\n return differences;\n}\n\n/** The differences, as the line a developer reads in `rebase doctor`. */\nexport function describeDeclarationDifferences(differences: DeclarationDifference[]): string {\n const shown = differences.slice(0, 5).map(difference => {\n if (difference.kind === \"missing\") return `${difference.name} (not in the file)`;\n if (difference.kind === \"unexpected\") return `${difference.name} (in the file, no longer generated)`;\n return `${difference.name} (differs)`;\n });\n const rest = differences.length - shown.length;\n return shown.join(\", \") + (rest > 0 ? `, and ${rest} more` : \"\");\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"generated-schema-staleness-Di9c4ZxN.js","names":[],"sources":["../src/schema/generated-schema-staleness.ts"],"sourcesContent":["import { CollectionConfig } from \"@rebasepro/types\";\nimport { getTableName, resolveCollectionRelations, relationalCollections } from \"@rebasepro/common\";\nimport { generateForeignKeyName, legacyForeignKeyName } from \"@rebasepro/utils\";\n\n/**\n * Notice a generated Drizzle schema that a *library upgrade* invalidated.\n *\n * `rebase dev` already watches `config/collections` and warns when a collection\n * file changes. That covers drift the developer caused. It cannot cover this one,\n * because nothing the developer owns changed: 0.13 derives `category_id` where\n * 0.12 derived `categorie_id`, from the same unedited collection. The watcher\n * never fires, and `backend/src/schema.generated.ts` quietly stops describing the\n * schema the runtime expects.\n *\n * The consequence is not cosmetic. Boot-ensure renames the column in the\n * database, then relation validation reads the stale module and refuses to\n * start — on that boot and every boot after it, because the rename is already\n * applied and will not be attempted again.\n *\n * Deliberately narrow: this answers \"does the generated schema name a foreign key\n * the way the previous rule did\", not \"is this file what we would generate now\".\n * The wide question would report every whitespace change in the generator as a\n * fatal staleness, and a check that cries wolf gets switched off.\n */\n\n/** One column the generated schema names under the pre-0.13 rule. */\nexport interface LegacyForeignKeyName {\n /** Table whose column declaration is stale. */\n table: string;\n /** The name the generated schema declares. */\n legacy: string;\n /** The name this release derives, and which the database now carries. */\n current: string;\n /** `<collection>.<relation>` that derives it, for the message. */\n relation: string;\n}\n\n/**\n * The slice of the generated source declaring one table.\n *\n * Scoping matters: two junctions in one file can carry columns of the same name,\n * and a whole-file match would attribute a stale column to whichever table the\n * reader looks at first. Returns \"\" when the table is not in the file at all,\n * which is not staleness — it is a table the generator has not been asked about.\n */\nfunction tableBlock(source: string, table: string): string {\n // Both forms the generator emits. A collection with `schema: \"rebase\"` — the\n // auth users collection, in every scaffold — is written as\n // `rebaseSchema.table(\"users\", …)`, so matching only `pgTable(` made this\n // blind to every table outside `public`: the block came back empty, and an\n // empty block reads as \"a table the generator has not been asked about\".\n // The rename check therefore never inspected them.\n const literal = table.replace(/[.*+?^${}()|[\\]\\\\]/g, \"\\\\$&\");\n const start = source.search(new RegExp(`(?:pgTable|\\\\.table)\\\\(\\\\s*[\"']${literal}[\"']`));\n if (start === -1) return \"\";\n // Generated files put every table in its own `export const`, so the next one\n // is the end of this block. No brace counting, nothing to get wrong.\n const next = source.indexOf(\"\\nexport \", start);\n return next === -1 ? source.slice(start) : source.slice(start, next);\n}\n\n/**\n * Whether a block *declares* the column, rather than merely mentioning it.\n *\n * A comment explaining the rename, or a policy expression naming the old column,\n * must not read as a declaration — otherwise regenerating the file would not\n * clear the finding and the check would be permanently red.\n */\nfunction declaresColumn(block: string, column: string): boolean {\n // `categorieId: integer(\"categorie_id\")` — the Drizzle column shape. Only\n // the string argument is the column; the key beside it is the *wire* name\n // and no longer agrees with it (`authorId: integer(\"author_id\")`). Matching\n // both, as this did, made every camelCased key look like a column the\n // generated file did not declare.\n const literal = column.replace(/[.*+?^${}()|[\\]\\\\]/g, \"\\\\$&\");\n return new RegExp(`(^|[\\\\s,{])[\\\\w$\"']+\\\\s*:\\\\s*\\\\w+\\\\(\\\\s*[\"']${literal}[\"']`, \"m\").test(block);\n}\n\n/**\n * @param generatedSource contents of `backend/src/schema.generated.ts`\n * @param collections the project's collections, as this release reads them\n */\nexport function findLegacyForeignKeyNames(\n generatedSource: string,\n collections: CollectionConfig[]\n): LegacyForeignKeyName[] {\n const found: LegacyForeignKeyName[] = [];\n const seen = new Set<string>();\n\n /**\n * Report `wanted` as stale when the generated schema declares what the\n * previous rule would have derived from `source` instead.\n *\n * The `wanted !== current` guard is what honours an explicitly named column.\n * An author who pinned `categorie_id` has said so on the relation, so the\n * generated file agreeing with them is correct, not stale — and the rename\n * note documents exactly that opt-out.\n */\n const consider = (\n table: string,\n wanted: string,\n sourceName: string | undefined,\n relation: string\n ): void => {\n if (!table || !wanted || !sourceName) return;\n\n const current = generateForeignKeyName(sourceName);\n const legacy = legacyForeignKeyName(sourceName);\n if (legacy === current) return; // this name never moved\n if (wanted !== current) return; // pinned by the author, or unrelated\n\n const block = tableBlock(generatedSource, table);\n if (!block) return;\n if (!declaresColumn(block, legacy)) return;\n if (declaresColumn(block, current)) return; // already regenerated\n\n const key = `${table}.${legacy}`;\n if (seen.has(key)) return;\n seen.add(key);\n found.push({ table, legacy, current, relation });\n };\n\n // Same rule as the generator whose output this reads: a collection that\n // gets no table cannot have named a column in it. Inert in practice today —\n // the finding also requires the generated file to declare the table — but\n // the guard is what keeps that true when a non-SQL collection's slug happens\n // to match a SQL one's table.\n for (const collection of relationalCollections(collections)) {\n const sourceTable = getTableName(collection);\n\n for (const [name, relation] of Object.entries(resolveCollectionRelations(collection))) {\n const at = `${collection.slug}.${name}`;\n\n let target: CollectionConfig | undefined;\n try {\n target = relation.target?.();\n } catch {\n // A throwing target thunk is its own defect, reported at boot by\n // `validate-relations`. Nothing to say about its column names.\n continue;\n }\n\n switch (relation.kind) {\n case \"belongsTo\":\n consider(sourceTable, relation.localKey, relation.relationName, at);\n break;\n\n case \"hasOne\":\n case \"hasMany\":\n if (target) {\n consider(getTableName(target), relation.foreignKeyOnTarget, collection.slug, at);\n }\n break;\n\n case \"manyToMany\":\n consider(relation.through.table, relation.through.sourceColumn, collection.slug, at);\n if (target) {\n consider(relation.through.table, relation.through.targetColumn, target.slug, at);\n }\n break;\n\n default:\n // `via` joins are written by hand — there is no derived name\n // for the rule change to have moved.\n break;\n }\n }\n }\n\n return found;\n}\n\n/** One-line summary for a log or a CLI notice. */\nexport function describeLegacyForeignKeyNames(found: LegacyForeignKeyName[]): string {\n return found\n .map(f => ` • ${f.table}.${f.legacy} → ${f.current} (${f.relation})`)\n .join(\"\\n\");\n}\n\n// ── The other way a generated schema goes stale ──────────────────────────────\n//\n// The rename above is the subtle one: nothing the developer owns changed. The\n// ordinary one is the opposite — a collection or a relation was added and the\n// generator was not re-run, so the checked-in module simply does not describe\n// part of the schema. It fails later and elsewhere: relation validation, or a\n// query against a table the module has never heard of.\n//\n// Still not \"is this byte-for-byte what the generator would emit now\". This asks\n// only about *names*, which is what the rest of the system reads out of the\n// file, so reformatting the generator cannot make it red.\n\n/** Something the collections derive that the generated schema does not declare. */\nexport interface MissingGeneratedName {\n /** The table it belongs to, or is. */\n table: string;\n /** The column, when a column is what is missing. */\n column?: string;\n /** `<collection>` or `<collection>.<relation>`, for the message. */\n source: string;\n}\n\n/**\n * Tables and derived foreign-key columns the collections name and the generated\n * schema does not.\n *\n * @param generatedSource contents of `backend/src/schema.generated.ts`\n * @param collections the project's collections, as this release reads them\n */\nexport function findMissingGeneratedNames(\n generatedSource: string,\n collections: CollectionConfig[]\n): MissingGeneratedName[] {\n const missing: MissingGeneratedName[] = [];\n const seen = new Set<string>();\n\n const report = (table: string, source: string, column?: string): void => {\n const key = `${table}.${column ?? \"\"}`;\n if (seen.has(key)) return;\n seen.add(key);\n missing.push({ table, source, ...(column ? { column } : {}) });\n };\n\n const requireColumn = (table: string, column: string | undefined, source: string): void => {\n if (!table || !column) return;\n const block = tableBlock(generatedSource, table);\n // A table that is absent is reported once, as a table. Listing each of\n // its columns as separately missing would bury the one fact that\n // explains all of them.\n if (!block) return report(table, source);\n if (!declaresColumn(block, column)) report(table, source, column);\n };\n\n for (const collection of relationalCollections(collections)) {\n const sourceTable = getTableName(collection);\n if (!tableBlock(generatedSource, sourceTable)) {\n report(sourceTable, collection.slug);\n continue;\n }\n\n for (const [name, relation] of Object.entries(resolveCollectionRelations(collection))) {\n const at = `${collection.slug}.${name}`;\n\n let target: CollectionConfig | undefined;\n try {\n target = relation.target?.();\n } catch {\n // Its own defect, reported at boot by `validate-relations`.\n continue;\n }\n\n switch (relation.kind) {\n case \"belongsTo\":\n requireColumn(sourceTable, relation.localKey, at);\n break;\n\n case \"hasOne\":\n case \"hasMany\":\n if (target) requireColumn(getTableName(target), relation.foreignKeyOnTarget, at);\n break;\n\n case \"manyToMany\":\n requireColumn(relation.through.table, relation.through.sourceColumn, at);\n requireColumn(relation.through.table, relation.through.targetColumn, at);\n break;\n\n default:\n // `via` joins are written by hand; there is no derived name\n // to be missing.\n break;\n }\n }\n }\n\n return missing;\n}\n\n/** One-line summary for a log or a CLI notice. */\nexport function describeMissingGeneratedNames(missing: MissingGeneratedName[]): string {\n return missing\n .map(m => (m.column\n ? ` • ${m.table}.${m.column} is not declared (${m.source})`\n : ` • table ${m.table} is not declared (${m.source})`))\n .join(\"\\n\");\n}\n\n/** What `rebase schema stale` found, and what that costs. */\nexport interface StaleVerdict {\n /** The lines to print, in order. Empty only under `--fix` with nothing to fix. */\n lines: string[];\n /** Non-zero only when the reader must act before the server will boot. */\n exitCode: 0 | 1;\n /** True when `--fix` should regenerate. */\n regenerate: boolean;\n}\n\n/**\n * The whole of `schema stale`'s decision, separated from doing it.\n *\n * Separated because of what the command used to do on the clean path: **exit 0\n * having written zero bytes to stdout and zero to stderr.** For a command the\n * top-level help describes as \"Report generated schema files the collections\n * have moved past\", that is indistinguishable from a no-op, a crash, and a\n * subcommand the driver does not implement. Three of its four paths were\n * silent, and none of them could be tested where they stood: `cli.ts` uses\n * `import.meta`, which the driver's jest suite cannot load at all.\n *\n * `--fix` is the exception, and stays silent when there is nothing to fix.\n * `rebase dev` runs `schema stale --fix` before every boot with inherited\n * stdio, and \"nothing was wrong\" is not news a hundred lines into a start-up\n * transcript. The flag is the automated caller's; its silence is the point.\n */\nexport function staleVerdict(input: {\n /** `--output`, as the reader typed it. */\n outputPath: string;\n /** Whether the generated schema exists at all. */\n generatedExists: boolean;\n /** The legacy foreign-key names found in it, if it was read. */\n stale: LegacyForeignKeyName[];\n /**\n * How the file differs from what the current collections generate.\n *\n * The check this command's own summary promises — \"generated schema files\n * that no longer match the collections\" — and for a long time the one it did\n * not make. Staleness was computed from {@link findLegacyForeignKeyNames}\n * alone, which finds one specific historical defect: a junction column named\n * the way a previous release derived it. Every other way a file can fall\n * behind its collections — a table added, a column retyped, an enum label\n * dropped from the offer — left the command printing \"Nothing stale\" about a\n * file that regenerating visibly changes.\n *\n * `projects_database_mode` is the one that found this. The shared database\n * tier was retired, the label came off the collection, and `schema stale`\n * called the file a match for weeks while every `pnpm run build` silently\n * rewrote it.\n *\n * Optional so a caller that has not loaded the collections — the only way to\n * know what they would generate — reports what it does know rather than\n * claiming a clean bill it never checked.\n */\n differences?: DeclarationDifference[];\n /** Why the comparison could not be made, when it could not. */\n unreadable?: string;\n /** Whether `--fix` was given. */\n fix: boolean;\n}): StaleVerdict {\n const quiet = (lines: string[]): string[] => (input.fix ? [] : lines);\n\n if (!input.generatedExists) {\n // Not staleness: a fresh project has not run the generator, and saying\n // \"stale\" about a file that does not exist sends the reader looking for\n // something to fix.\n return {\n lines: quiet([` No generated schema yet at ${input.outputPath} — nothing to compare.`,\n \" `rebase schema generate` creates it.\"]),\n exitCode: 0,\n regenerate: false\n };\n }\n\n if (input.unreadable !== undefined) {\n // Best-effort by design: a collections directory that will not load is a\n // real error, but boot reports it far better than this does. Saying so\n // is still better than exiting 0 in silence about a check that did not\n // run — this file's whole subject.\n return {\n lines: quiet([` ⏭ Not checked: ${input.unreadable}`]),\n exitCode: 0,\n regenerate: false\n };\n }\n\n // Both checks, and both reported. They answer different questions and a file\n // can fail either: `stale` names a specific historical defect with a\n // specific remedy, `differences` says the file is simply not what the\n // collections now generate. Reporting only the first is what let this\n // command pass a file that had visibly moved.\n const problems: string[] = [];\n if (input.stale.length > 0) {\n problems.push(\n ` ⚠️ ${input.outputPath} names ${input.stale.length} foreign key(s) the way an earlier release did:`,\n describeLegacyForeignKeyNames(input.stale)\n );\n }\n const differences = input.differences ?? [];\n if (differences.length > 0) {\n problems.push(\n ` ⚠️ ${input.outputPath} is not what the current collections generate.`,\n ` Differs in: ${describeDeclarationDifferences(differences)}.`\n );\n }\n\n if (problems.length === 0) {\n return {\n lines: quiet([` ✓ Nothing stale — 1 generated file matches (${input.outputPath}).`]),\n exitCode: 0,\n regenerate: false\n };\n }\n\n const found = [\"\", ...problems, \"\"];\n\n if (input.fix) {\n return { lines: [...found, \" Regenerating the Drizzle schema so it matches...\"], exitCode: 0, regenerate: true };\n }\n\n return {\n lines: [\n ...found,\n // Not \"the server will refuse to start\" any more. The runtime builds\n // its tables from `information_schema`, so a stale file changes\n // nothing it serves — it warns once at boot and carries on. What the\n // file still decides is everything around the server: Atlas plans\n // migrations from it, `db push` diffs it, `eject` writes it out, and\n // user code imports it.\n //\n // The first sentence is the renamed-column one and belongs only to\n // the legacy foreign-key finding. Printed unconditionally it told a\n // reader whose enum label had simply moved that a column had been\n // renamed at boot, which sends them looking for a rename that never\n // happened. The consequence below is the same either way.\n ...(input.stale.length > 0\n ? [\" The database column has already been renamed at boot.\"]\n : []),\n \" The server reads the database, so it will still start — but `db push`, Atlas, `eject` \"\n + \"and any code importing this file are all reading a description that no longer matches.\",\n \" Run `rebase schema generate` to regenerate it.\"\n ],\n exitCode: 1,\n regenerate: false\n };\n}\n\n\n// ── Is this file what the current collections generate? ──────────────────────\n\n/** One exported declaration that differs between two generated schemas. */\nexport interface DeclarationDifference {\n /** The exported symbol: a table, an enum, a relations block. */\n name: string;\n kind: \"missing\" | \"unexpected\" | \"changed\";\n}\n\n/**\n * `export const NAME = …` → the declaration, with formatting and the\n * generator's own comments removed.\n *\n * Every generated declaration starts at column 0 and runs to the next one, so\n * splitting on them needs no brace counting and cannot get out of step with a\n * policy clause that happens to contain a brace.\n */\nfunction declarations(source: string): Map<string, string> {\n const out = new Map<string, string>();\n const starts = [...source.matchAll(/^export const (\\w+) =/gm)];\n starts.forEach((match, index) => {\n const from = match.index! + match[0].length;\n const to = index + 1 < starts.length ? starts[index + 1].index! : source.length;\n out.set(\n match[1],\n source.slice(from, to)\n // The `// INCLUDE (...)` note on a covering index, and any other\n // remark the renderer leaves: they describe the declaration,\n // they are not part of it.\n .replace(/\\/\\/[^\\n]*/g, \"\")\n .replace(/\\s+/g, \" \")\n .trim()\n );\n });\n return out;\n}\n\n/**\n * What differs between the schema the current collections generate and the one\n * on disk — by declaration, not by byte.\n *\n * `rebase doctor` used to whitespace-normalise both files and compare the\n * strings, which answers \"are these the same text\" when the question is \"does\n * this file still describe the same schema\". The difference is not academic:\n * the answer was a single yes/no with nothing to act on, so a developer told\n * their schema was stale had to regenerate and read the diff to find out what\n * had changed.\n *\n * Comparing declarations answers the question the reader actually has. The\n * generated file is a flat list of `export const`s — one per table, enum and\n * relations block, each the rendering of a piece of the schema plan — and two\n * files declaring the same symbols with the same bodies describe the same\n * database whatever their formatting.\n *\n * Not a plan-to-plan comparison, and deliberately not: reading a plan back out\n * of `schema.generated.ts` means interpreting drizzle-orm builder calls, which\n * is a second interpreter — the exact thing this area was just rid of. The\n * rendered declarations are the closest honest proxy, and the renderer is\n * deterministic, so the only things normalised away are what it is free to vary\n * independently of the plan: whitespace and its own comments.\n */\nexport function compareGeneratedDeclarations(expected: string, actual: string): DeclarationDifference[] {\n const want = declarations(expected);\n const have = declarations(actual);\n const differences: DeclarationDifference[] = [];\n for (const [name, body] of want) {\n if (!have.has(name)) differences.push({ name, kind: \"missing\" });\n else if (have.get(name) !== body) differences.push({ name, kind: \"changed\" });\n }\n for (const name of have.keys()) {\n if (!want.has(name)) differences.push({ name, kind: \"unexpected\" });\n }\n return differences;\n}\n\n/** The differences, as the line a developer reads in `rebase doctor`. */\nexport function describeDeclarationDifferences(differences: DeclarationDifference[]): string {\n const shown = differences.slice(0, 5).map(difference => {\n if (difference.kind === \"missing\") return `${difference.name} (not in the file)`;\n if (difference.kind === \"unexpected\") return `${difference.name} (in the file, no longer generated)`;\n return `${difference.name} (differs)`;\n });\n const rest = differences.length - shown.length;\n return shown.join(\", \") + (rest > 0 ? `, and ${rest} more` : \"\");\n}\n"],"mappings":"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"}
|