@rebasepro/server-postgres 0.19.1 → 0.19.2-canary.g09316f6
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/dist/{BranchService-ByLfM_Tt.js → BranchService-W3DMfcZZ.js} +2 -2
- package/dist/{BranchService-ByLfM_Tt.js.map → BranchService-W3DMfcZZ.js.map} +1 -1
- package/dist/PostgresBackendDriver.d.ts +81 -7
- package/dist/PostgresBootstrapper.d.ts +10 -0
- package/dist/{backup-cli-oM9WQVlV.js → backup-cli-DSpQyqcG.js} +2 -2
- package/dist/{backup-cli-oM9WQVlV.js.map → backup-cli-DSpQyqcG.js.map} +1 -1
- package/dist/{backup-service-Br3tqwsy.js → backup-service-DA7a6SUV.js} +2 -2
- package/dist/{backup-service-Br3tqwsy.js.map → backup-service-DA7a6SUV.js.map} +1 -1
- package/dist/{cli-errors-DxN4yzdH.js → cli-errors-C3g_kHBw.js} +137 -18
- package/dist/cli-errors-C3g_kHBw.js.map +1 -0
- package/dist/cli-errors.d.ts +19 -0
- package/dist/cli-helpers.d.ts +47 -0
- package/dist/cli.js +233 -19
- package/dist/cli.js.map +1 -1
- package/dist/{collection-index-BHgT_TKd.js → collection-index-DdnsxiJ_.js} +14 -4
- package/dist/{collection-index-BHgT_TKd.js.map → collection-index-DdnsxiJ_.js.map} +1 -1
- package/dist/collections/validate-relations.d.ts +7 -0
- package/dist/column-plan-helpers-CpILzHJS.js +145 -0
- package/dist/column-plan-helpers-CpILzHJS.js.map +1 -0
- package/dist/{doctor-D7HWmGzs.js → doctor-D5SrGJ5P.js} +175 -61
- package/dist/doctor-D5SrGJ5P.js.map +1 -0
- package/dist/{ensure-collection-policies-2c2NV8zX.js → ensure-collection-policies-CHO0moXQ.js} +5 -5
- package/dist/{ensure-collection-policies-2c2NV8zX.js.map → ensure-collection-policies-CHO0moXQ.js.map} +1 -1
- package/dist/{ensure-collection-tables-D4wJzM4y.js → ensure-collection-tables-Bkvehxdm.js} +318 -312
- package/dist/ensure-collection-tables-Bkvehxdm.js.map +1 -0
- package/dist/{ensure-tables-CIojK-Rx.js → ensure-tables-CYMtuuOd.js} +2 -2
- package/dist/{ensure-tables-CIojK-Rx.js.map → ensure-tables-CYMtuuOd.js.map} +1 -1
- package/dist/generate-drizzle-schema-logic-Dfyf_MRu.js +228 -0
- package/dist/generate-drizzle-schema-logic-Dfyf_MRu.js.map +1 -0
- package/dist/{generate-drizzle-schema-CwEgVyjO.js → generate-drizzle-schema-vSK-VdYT.js} +3 -3
- package/dist/generate-drizzle-schema-vSK-VdYT.js.map +1 -0
- package/dist/generate-postgres-ddl-logic-CR2xcS7e.js +475 -0
- package/dist/generate-postgres-ddl-logic-CR2xcS7e.js.map +1 -0
- package/dist/{generated-schema-staleness-DQdfJl2A.js → generated-schema-staleness-DRQe2BpC.js} +81 -3
- package/dist/{generated-schema-staleness-DQdfJl2A.js.map → generated-schema-staleness-DRQe2BpC.js.map} +1 -1
- package/dist/history/HistoryService.d.ts +23 -4
- package/dist/index.es.js +1991 -580
- package/dist/index.es.js.map +1 -1
- package/dist/introspect-db-logic-C6LQdTxj.js.map +1 -1
- package/dist/plan-schema-Hgl62S-w.js +1531 -0
- package/dist/plan-schema-Hgl62S-w.js.map +1 -0
- package/dist/{policy-drift-kNJ6X2Uj.js → policy-drift-B0GDRh7_.js} +2 -2
- package/dist/{policy-drift-kNJ6X2Uj.js.map → policy-drift-B0GDRh7_.js.map} +1 -1
- package/dist/{rls-bootstrap-sql-DLDnUjtx.js → rls-bootstrap-sql-BlzsOUtz.js} +111 -8
- package/dist/{rls-bootstrap-sql-DLDnUjtx.js.map → rls-bootstrap-sql-BlzsOUtz.js.map} +1 -1
- package/dist/{rls-enforcement-DeYEjYLt.js → rls-enforcement-BV12vBwQ.js} +2 -1
- package/dist/rls-enforcement-BV12vBwQ.js.map +1 -0
- package/dist/schema/catalogue-schema.d.ts +97 -0
- package/dist/schema/collection-index.d.ts +9 -0
- package/dist/schema/column-plan-helpers.d.ts +125 -0
- package/dist/schema/config-relations.d.ts +32 -0
- package/dist/schema/doctor-cli.js +4 -4
- package/dist/schema/doctor.d.ts +9 -0
- package/dist/schema/dynamic-tables.d.ts +27 -6
- package/dist/schema/ensure-collection-tables.d.ts +31 -268
- package/dist/schema/generate-drizzle-schema-logic.d.ts +26 -8
- package/dist/schema/generate-drizzle-schema.js +1 -1
- package/dist/schema/generate-postgres-ddl-logic.d.ts +84 -168
- package/dist/schema/generate-postgres-ddl.js +8 -2
- package/dist/schema/generate-postgres-ddl.js.map +1 -1
- package/dist/schema/generated-column-conflicts.d.ts +84 -0
- package/dist/schema/generated-schema-diff.d.ts +56 -0
- package/dist/schema/generated-schema-staleness.d.ts +33 -0
- package/dist/schema/introspect-db-logic.d.ts +2 -0
- package/dist/schema/plan/diff-plan.d.ts +220 -0
- package/dist/schema/plan/plan-schema.d.ts +84 -0
- package/dist/schema/plan/render-ddl.d.ts +62 -0
- package/dist/schema/plan/render-drizzle.d.ts +31 -0
- package/dist/schema/plan/types.d.ts +410 -0
- package/dist/schema/plan/updated-at-trigger.d.ts +22 -0
- package/dist/schema/relation-names.d.ts +28 -0
- package/dist/schema/search-column.d.ts +9 -0
- package/dist/security/rls-enforcement.d.ts +17 -0
- package/dist/services/FetchService.d.ts +176 -36
- package/dist/services/PersistService.d.ts +17 -4
- package/dist/services/RelationService.d.ts +11 -3
- package/dist/services/collection-helpers.d.ts +16 -2
- package/dist/services/dataService.d.ts +29 -2
- package/dist/services/field-op-sql.d.ts +16 -0
- package/dist/services/field-viewer.d.ts +19 -0
- package/dist/services/realtimeService.d.ts +35 -1
- package/dist/services/row-pipeline.d.ts +29 -5
- package/dist/services/soft-delete.d.ts +73 -0
- package/dist/utils/pg-error-utils.d.ts +41 -1
- package/dist/{vector-index-DkWxm2de.js → vector-index-CMJKb3C9.js} +3 -14
- package/dist/{vector-index-DkWxm2de.js.map → vector-index-CMJKb3C9.js.map} +1 -1
- package/package.json +18 -10
- package/dist/cli-errors-DxN4yzdH.js.map +0 -1
- package/dist/doctor-D7HWmGzs.js.map +0 -1
- package/dist/ensure-collection-tables-D4wJzM4y.js.map +0 -1
- package/dist/generate-drizzle-schema-CwEgVyjO.js.map +0 -1
- package/dist/generate-drizzle-schema-logic-so2Z_lyC.js +0 -528
- package/dist/generate-drizzle-schema-logic-so2Z_lyC.js.map +0 -1
- package/dist/generate-postgres-ddl-logic-CtnpIBVv.js +0 -1312
- package/dist/generate-postgres-ddl-logic-CtnpIBVv.js.map +0 -1
- package/dist/rls-enforcement-DeYEjYLt.js.map +0 -1
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"doctor-D7HWmGzs.js","names":[],"sources":["../../codegen/dist/index.es.js","../src/schema/doctor.ts"],"sourcesContent":["import { fieldKeyForColumn, findRelation, isRelationRequired, resolveCollectionRelations, sortCollectionsBySlug } from \"@rebasepro/common\";\n//#region src/utils.ts\n/**\n* Utility functions for the SDK generator\n*/\n/**\n* Convert a slug/snake_case string to PascalCase\n* e.g. \"private_notes\" → \"PrivateNotes\"\n*\n* Capitals already inside a word are meaningful and are kept: lowercasing the\n* tail of every chunk turned \"TestEntities\" into \"Testentities\", which is what\n* ended up in the generated type names. SHOUTING_CASE is the one shape where\n* the tail is not meaningful, so it is folded down.\n*/\nfunction toPascalCase(str) {\n\treturn str.split(/[_\\-\\s]+/).filter(Boolean).map((word) => {\n\t\tconst rest = /^[A-Z0-9]+$/.test(word) ? word.slice(1).toLowerCase() : word.slice(1);\n\t\treturn word.charAt(0).toUpperCase() + rest;\n\t}).join(\"\");\n}\n/**\n* Convert a slug/snake_case string to camelCase\n* e.g. \"private_notes\" → \"privateNotes\"\n*/\nfunction toCamelCase(str) {\n\tif (!/[_\\-\\s]/.test(str)) return str.charAt(0).toLowerCase() + str.slice(1);\n\tconst pascal = toPascalCase(str);\n\treturn pascal.charAt(0).toLowerCase() + pascal.slice(1);\n}\n/**\n* Convert a slug to a safe JS identifier\n* e.g. \"private-notes\" → \"privateNotes\"\n*/\nfunction toSafeIdentifier(str) {\n\treturn toCamelCase(str.replace(/[^a-zA-Z0-9_]/g, \"_\"));\n}\n/**\n* Indent a block of text by a given number of spaces\n*/\nfunction indent(text, spaces) {\n\tconst pad = \" \".repeat(spaces);\n\treturn text.split(\"\\n\").map((line) => line.trim() ? pad + line : line).join(\"\\n\");\n}\n//#endregion\n//#region src/generate-types.ts\n/**\n* A schema that cannot be expressed as a valid TypeScript file.\n*\n* Thrown rather than emitted. The generator used to concatenate whatever it was\n* given, so a slug that collided with another one, or that was not an\n* identifier, produced a file that either failed to compile or — worse —\n* compiled while quietly routing one collection to another's slug.\n*/\nvar CodegenError = class extends Error {\n\tconstructor(message) {\n\t\tsuper(message);\n\t\tthis.name = \"CodegenError\";\n\t}\n};\nvar IDENTIFIER = /^[A-Za-z_$][A-Za-z0-9_$]*$/;\n/**\n* A property name for the emitted TypeScript: verbatim when it is a valid\n* identifier, quoted otherwise.\n*\n* Every key that reaches the output goes through here. Column names are not\n* required to be identifiers — `\"order\"`, `\"user id\"`, a quoted Postgres\n* identifier — and the previous behaviour of camel-casing them into shape\n* renamed the column in the type while the wire kept the original, so the\n* generated `Row` described fields that did not exist.\n*/\nfunction emitKey(key) {\n\treturn IDENTIFIER.test(key) ? key : JSON.stringify(key);\n}\n/**\n* A string literal, escaped.\n*\n* `\"${value}\"` was the previous form. A value containing a quote closed the\n* literal early, which at best broke the file and at worst let a slug from a\n* remote contract inject top-level statements into a file the developer\n* compiles and bundles.\n*/\nfunction emitString(value) {\n\treturn JSON.stringify(value);\n}\n/** The `id`s of an enum declared as an array, an array of `{ id }`, or an object map. */\nfunction enumIds(raw) {\n\tif (Array.isArray(raw)) return raw.map((entry) => entry && typeof entry === \"object\" ? entry.id : entry);\n\tif (raw && typeof raw === \"object\") return Object.keys(raw);\n\treturn [];\n}\nfunction propertyToTypeScriptType(prop) {\n\tswitch (prop.type) {\n\t\tcase \"string\": {\n\t\t\tconst sp = prop;\n\t\t\tif (sp.enum) {\n\t\t\t\tconst ids = enumIds(sp.enum);\n\t\t\t\tif (ids.length === 0) return \"string\";\n\t\t\t\treturn ids.map((v) => emitString(String(v))).join(\" | \");\n\t\t\t}\n\t\t\treturn \"string\";\n\t\t}\n\t\tcase \"number\": {\n\t\t\tconst np = prop;\n\t\t\tif (np.enum) {\n\t\t\t\tconst ids = enumIds(np.enum);\n\t\t\t\tconst numbers = ids.map(Number);\n\t\t\t\tif (ids.length === 0 || numbers.some((n) => !Number.isFinite(n))) return \"number\";\n\t\t\t\treturn numbers.map((n) => String(n)).join(\" | \");\n\t\t\t}\n\t\t\treturn \"number\";\n\t\t}\n\t\tcase \"boolean\": return \"boolean\";\n\t\tcase \"date\": return \"string\";\n\t\tcase \"geopoint\": return \"{ latitude: number; longitude: number; }\";\n\t\tcase \"reference\": return \"string | number\";\n\t\tcase \"relation\": return \"string | number\";\n\t\tcase \"map\": {\n\t\t\tconst mapProp = prop;\n\t\t\tif (mapProp.properties) return `{ ${Object.entries(mapProp.properties).map(([k, v]) => {\n\t\t\t\tconst child = v;\n\t\t\t\tconst optional = !child.validation?.required;\n\t\t\t\tconst type = propertyToTypeScriptType(child);\n\t\t\t\treturn `${emitKey(k)}${optional ? \"?\" : \"\"}: ${optional ? `${type} | null` : type};`;\n\t\t\t}).join(\" \")} }`;\n\t\t\treturn \"Record<string, unknown>\";\n\t\t}\n\t\tcase \"array\": {\n\t\t\tconst arrProp = prop;\n\t\t\tif (arrProp.of) return `Array<${propertyToTypeScriptType(arrProp.of)}>`;\n\t\t\treturn \"Array<unknown>\";\n\t\t}\n\t\tcase \"vector\": return \"number[]\";\n\t\tcase \"binary\": return \"string\";\n\t\tdefault: return \"unknown\";\n\t}\n}\n/**\n* Unwrap a relation target that arrived as a module namespace rather than the\n* collection itself — `target: () => import(\"./authors\")` is a common slip.\n*/\nfunction resolveTargetCollection(relation) {\n\ttry {\n\t\tlet target = relation.target();\n\t\tif (target && (target.default || target.__esModule)) target = target.default ?? target;\n\t\treturn target;\n\t} catch {\n\t\treturn;\n\t}\n}\n/** The TypeScript type of a foreign key: whatever the target's primary key is. */\nfunction foreignKeyType(relation) {\n\tconst target = resolveTargetCollection(relation);\n\tif (!target?.properties) return \"string | number\";\n\tconst idProp = Object.entries(target.properties).find(([_, p]) => p.isId);\n\tif (!idProp) return \"string | number\";\n\treturn idProp[1].type === \"number\" ? \"number\" : \"string\";\n}\n/** Whether a property is the collection's primary key. */\nfunction isPrimaryKey(prop) {\n\treturn Boolean(prop.isId);\n}\n/**\n* Whether the server assigns this primary key, so a write does not have to.\n* `true` and `\"manual\"` both mean the caller supplies it.\n*/\nfunction isAutoAssignedId(prop) {\n\tconst isId = prop.isId;\n\treturn Boolean(isId) && isId !== \"manual\" && isId !== true;\n}\n/**\n* The type an *included* relation arrives as: the target's own row, inlined.\n*\n* This is what the read pipeline actually serves — `toRestRow` puts the\n* target's flat columns where the relation was, and the SDK and the HTTP API\n* both go through it. It is deliberately *not* a `{ __type: \"relation\" }`\n* envelope: that shape is the admin's view-model and never reaches a\n* developer's `find()`.\n*\n* Falls back to an open record when the target is not part of this generation\n* run, since there is no `Row` to point at.\n*/\nfunction includedRelationType(relation, accessors) {\n\tconst slug = resolveTargetCollection(relation)?.slug ?? relation.targetSlug;\n\tconst accessor = slug ? accessors.get(slug) : void 0;\n\tconst rowType = accessor ? `Database[${emitString(accessor)}][\"Row\"]` : \"Record<string, unknown>\";\n\treturn relation.cardinality === \"many\" ? `Array<${rowType}>` : rowType;\n}\n/**\n* Map every slug to the property name it is reachable under on `client.data`.\n*\n* The accessor is a safe identifier because `client.data.myNotes` is the point\n* of generating this at all, and `collectionsDictionary` maps it back to the\n* slug the wire uses. Two slugs that safe down to the same identifier cannot\n* both have it: the interface would not compile, and the dictionary — an object\n* literal — would silently keep only the last, routing one collection's reads\n* to the other's table. There is no defensible way to pick, so this refuses.\n*/\nfunction buildAccessors(collections) {\n\tconst accessors = /* @__PURE__ */ new Map();\n\tconst bySafeName = /* @__PURE__ */ new Map();\n\tfor (const collection of collections) {\n\t\tconst slug = collection.slug;\n\t\tif (typeof slug !== \"string\" || slug.length === 0) throw new CodegenError(\"A collection has no slug, so it has no name to generate a type for. Every collection needs a unique `slug`.\");\n\t\tconst safe = toSafeIdentifier(slug);\n\t\tif (safe.length === 0) throw new CodegenError(`The slug ${emitString(slug)} has no characters that can form a property name, so it cannot be reached as \\`client.data.<name>\\`. Use a slug containing letters, digits, underscores or dashes.`);\n\t\tconst existing = bySafeName.get(safe);\n\t\tif (existing !== void 0) throw new CodegenError(`The collections ${emitString(existing)} and ${emitString(slug)} both generate the accessor \"${safe}\", so only one of them could be reached from the generated client and the other's reads would silently go to the wrong table. Rename one of the slugs.`);\n\t\tbySafeName.set(safe, slug);\n\t\taccessors.set(slug, safe);\n\t}\n\treturn accessors;\n}\n/** One emitted `key: type;` line, already indented. */\nfunction line(key, type, optional) {\n\treturn ` ${emitKey(key)}${optional ? \"?\" : \"\"}: ${type};`;\n}\n/**\n* The keys `excludeFromApi` takes off the API surface — in *both* directions.\n*\n* `excludeFromApi` means one thing: the API surface does not mention this\n* property. `Row` already honoured that; `Insert` and `Update` deliberately did\n* not, on the reading that the column is stripped from responses rather than\n* from writes. That left the generated types as the one place a password hash\n* was still named, and it invited a client to send one. The server still\n* *accepts* such a field on a write — this describes the surface, it does not\n* add an enforcement point — but nothing generated advertises it.\n*\n* Keyed by the property name *and* by its column name, the same pair the\n* server's `stripExcluded` deletes, so a foreign key or a relation addressed\n* under the column name cannot put the property back.\n*/\nfunction excludedApiKeys(properties) {\n\tconst excluded = /* @__PURE__ */ new Set();\n\tfor (const [key, rawProp] of Object.entries(properties)) {\n\t\tconst prop = rawProp;\n\t\tif (!prop?.excludeFromApi) continue;\n\t\texcluded.add(key);\n\t\tif (prop.columnName) excluded.add(prop.columnName);\n\t}\n\treturn excluded;\n}\nfunction generateTypedefs(input) {\n\tconst collections = sortCollectionsBySlug(input);\n\tconst accessors = buildAccessors(collections);\n\tconst lines = [\n\t\t\"/**\",\n\t\t\" * This file was auto-generated by Rebase.\",\n\t\t\" * Do not make direct changes to the file.\",\n\t\t\" */\",\n\t\t\"\",\n\t\t\"export interface Database {\"\n\t];\n\tfor (const collection of collections) {\n\t\tconst properties = collection.properties ?? {};\n\t\tlet resolvedRelations = {};\n\t\ttry {\n\t\t\tresolvedRelations = resolveCollectionRelations(collection);\n\t\t} catch (e) {\n\t\t\tconsole.warn(`[rebase] Could not resolve the relations of \"${collection.slug}\", so its generated type has no relation fields and none of their foreign-key columns. This is usually a circular import in the collection files — make sure the target is \\`() => otherCollection\\` and not evaluated at module load.\\n ${e instanceof Error ? e.message : String(e)}`);\n\t\t}\n\t\tconst subcollections = collection.subcollections;\n\t\tif (Array.isArray(subcollections) && subcollections.length > 0) console.warn(`[rebase] \"${collection.slug}\" declares ${subcollections.length} subcollection(s), which are not part of the generated Database: they are reached over a nested path (\\`data/${collection.slug}/<id>/<relation>\\`), not as a top-level accessor. Register a subcollection as a collection of its own if you want a typed accessor for it.`);\n\t\tlines.push(` ${emitKey(accessors.get(collection.slug))}: {`);\n\t\tlines.push(\" Row: {\");\n\t\tconst emittedKeys = /* @__PURE__ */ new Set();\n\t\tconst excluded = excludedApiKeys(properties);\n\t\tfor (const key of excluded) emittedKeys.add(key);\n\t\tfor (const [key, rawProp] of Object.entries(properties)) {\n\t\t\tconst prop = rawProp;\n\t\t\tif (prop.type === \"relation\") continue;\n\t\t\tif (excluded.has(key)) continue;\n\t\t\tconst tsType = propertyToTypeScriptType(prop);\n\t\t\tconst isRequired = Boolean(prop.validation?.required) || isPrimaryKey(prop);\n\t\t\tlines.push(line(key, isRequired ? tsType : `${tsType} | null`, !isRequired));\n\t\t\temittedKeys.add(key);\n\t\t}\n\t\tfor (const [relKey, relation] of Object.entries(resolvedRelations)) if (relation.kind === \"belongsTo\" && relation.localKey) {\n\t\t\tconst fkKey = fieldKeyForColumn(collection, relation.localKey);\n\t\t\tif (emittedKeys.has(fkKey)) continue;\n\t\t\tconst fkType = foreignKeyType(relation);\n\t\t\tconst shadowedByInclude = relKey === fkKey;\n\t\t\tconst tsType = shadowedByInclude ? `${fkType} | ${includedRelationType(relation, accessors)}` : fkType;\n\t\t\tconst isRequired = isRelationRequired(collection, relation) && !shadowedByInclude;\n\t\t\tlines.push(line(fkKey, isRequired ? tsType : `${tsType} | null`, !isRequired));\n\t\t\temittedKeys.add(fkKey);\n\t\t}\n\t\tfor (const [key, relation] of Object.entries(resolvedRelations)) {\n\t\t\tif (emittedKeys.has(key)) continue;\n\t\t\tlines.push(line(key, includedRelationType(relation, accessors), true));\n\t\t\temittedKeys.add(key);\n\t\t}\n\t\tfor (const [key, rawProp] of Object.entries(properties)) {\n\t\t\tif (rawProp.type !== \"relation\") continue;\n\t\t\tif (emittedKeys.has(key)) continue;\n\t\t\tlines.push(line(key, \"Record<string, unknown>\", true));\n\t\t\temittedKeys.add(key);\n\t\t}\n\t\tlines.push(\" };\");\n\t\tlines.push(\" Insert: {\");\n\t\temittedKeys.clear();\n\t\tfor (const key of excluded) emittedKeys.add(key);\n\t\tfor (const [key, rawProp] of Object.entries(properties)) {\n\t\t\tconst prop = rawProp;\n\t\t\tif (prop.type === \"relation\") continue;\n\t\t\tif (excluded.has(key)) continue;\n\t\t\tconst tsType = propertyToTypeScriptType(prop);\n\t\t\tconst isOptional = !prop.validation?.required || isAutoAssignedId(prop);\n\t\t\tlines.push(line(key, tsType, isOptional));\n\t\t\temittedKeys.add(key);\n\t\t}\n\t\temitWritableRelations(lines, collection, properties, resolvedRelations, emittedKeys, false);\n\t\tlines.push(\" };\");\n\t\tlines.push(\" Update: {\");\n\t\temittedKeys.clear();\n\t\tfor (const key of excluded) emittedKeys.add(key);\n\t\tfor (const [key, rawProp] of Object.entries(properties)) {\n\t\t\tconst prop = rawProp;\n\t\t\tif (prop.type === \"relation\") continue;\n\t\t\tif (isPrimaryKey(prop)) continue;\n\t\t\tif (excluded.has(key)) continue;\n\t\t\tlines.push(line(key, propertyToTypeScriptType(prop), true));\n\t\t\temittedKeys.add(key);\n\t\t}\n\t\temitWritableRelations(lines, collection, properties, resolvedRelations, emittedKeys, true);\n\t\tlines.push(\" };\");\n\t\tlines.push(\" };\");\n\t}\n\tlines.push(\"}\");\n\tlines.push(\"\");\n\tlines.push(\"export type CollectionName = keyof Database;\");\n\tlines.push(\"\");\n\tlines.push(\"export const collectionsDictionary = {\");\n\tfor (const collection of collections) lines.push(` ${emitKey(accessors.get(collection.slug))}: ${emitString(collection.slug)},`);\n\tlines.push(\"} as const;\");\n\tlines.push(\"\");\n\tlines.push(\"export type CollectionsDictionary = typeof collectionsDictionary;\");\n\tlines.push(\"\");\n\treturn lines.join(\"\\n\");\n}\n/**\n* The two ways a write can name a `belongsTo` target, both of which the server\n* accepts: the foreign key under its own wire name (`{ authorId: 5 }`, which\n* passes through to the `author_id` column untouched) and the relation\n* *property* (`{ author: 5 }`, which the write transformer maps onto that\n* column).\n*\n* Only the first was generated, so the documented and idiomatic write shape was\n* a type error.\n*\n* The second form is emitted under the **property key**, not the resolved\n* relation name, because that is what the transformer keys off: it looks the\n* payload key up in `properties` and only treats it as a relation if what it\n* finds there is one. A relation whose `relationName` differs from its property\n* key is reachable as the property and not as the name, so emitting the name\n* would have offered a key that writes to a column that does not exist.\n*/\nfunction emitWritableRelations(lines, collection, properties, resolvedRelations, emittedKeys, allOptional) {\n\tconst emit = (key, relation) => {\n\t\tif (emittedKeys.has(key)) return;\n\t\tconst optional = allOptional || !isRelationRequired(collection, relation);\n\t\tlines.push(line(key, foreignKeyType(relation), optional));\n\t\temittedKeys.add(key);\n\t};\n\tfor (const relation of Object.values(resolvedRelations)) if (relation.kind === \"belongsTo\" && relation.localKey) emit(fieldKeyForColumn(collection, relation.localKey), relation);\n\tfor (const [key, rawProp] of Object.entries(properties)) {\n\t\tif (rawProp.type !== \"relation\") continue;\n\t\tconst relation = findRelation(resolvedRelations, key);\n\t\tif (relation?.kind === \"belongsTo\" && relation.localKey) emit(key, relation);\n\t}\n}\n//#endregion\n//#region src/index.ts\nfunction generateSDK(collections, options = {}) {\n\tconst files = [];\n\tfiles.push({\n\t\tpath: \"database.types.ts\",\n\t\tcontent: generateTypedefs(collections)\n\t});\n\tif (options.includeReadme !== false) files.push({\n\t\tpath: \"README.md\",\n\t\tcontent: `# Rebase SDK\n\n> Auto-generated by \\`rebase generate-sdk\\`. Do not edit manually.\n\n## Usage\n\n1. Install the client package:\n \\`\\`\\`bash\n npm install @rebasepro/client\n \\`\\`\\`\n\n2. Initialize with your generated types:\n \\`\\`\\`typescript\n import { createRebaseClient } from '@rebasepro/client';\n import { collectionsDictionary, type Database } from './database.types';\n\n const rebase = createRebaseClient<Database>({\n baseUrl: 'http://localhost:3001',\n // Maps each accessor back to the slug the wire uses. Without it a\n // hyphenated slug is not resolvable from the property name alone.\n collections: collectionsDictionary,\n });\n\n // Property access is the typed surface: rows, filters and sorts are all\n // checked against the generated Database.\n const { data: users } = await rebase.data.users.find();\n console.log(users[0].email); // flat access — no .values wrapper\n \\`\\`\\`\n\n## Field names are the ones the API serves\n\nThe generated \\`Row\\` uses each field's **wire** name — the key it arrives under in\nJSON — and nothing here renames anything.\n\n- **A declared property is its key in the collection.** A property keyed\n \\`createdAt\\` is \\`row.createdAt\\`, whatever \\`columnName\\` says. A column name is\n the name of a different thing: where the value lives, not what the API calls it.\n- **A foreign key derived from a relation is camelCase**, because that is what\n the wire carries. A \\`belongsTo\\` named \\`author\\` gives you \\`row.authorId\\`, not\n the column spelling.\n- **A collection accessor is camelCase too** (\\`my-notes\\` → \\`rebase.data.myNotes\\`),\n which is what \\`collectionsDictionary\\` maps back to the slug.\n\n\\`where\\` and \\`orderBy\\` are keyed off the same type, so what compiles is what the\nbackend answers to.\n\n## \\`Row\\` vs \\`Insert\\` vs \\`Update\\`\n\n| Type | What it describes |\n|---|---|\n| \\`Row\\` | What a read serves. Nullable columns are \\`T \\\\| null\\`; relations appear only when \\`include\\` names them. |\n| \\`Insert\\` | What \\`create()\\` accepts. Server-assigned ids are optional; a \\`belongsTo\\` target may be named either way (\\`{ author: 5 }\\` or \\`{ authorId: 5 }\\`). |\n| \\`Update\\` | What \\`update()\\` accepts. Everything optional, and the primary key is not settable. |\n\nA property marked \\`excludeFromApi\\` is absent from all three: the API surface\ndoes not mention it, in either direction. The server holds the same line — a\nread never serves the column and a write naming it is refused — so this is a\nguarantee rather than a description, and nothing generated names a password\nhash.\n\nIf you need an untyped escape hatch, \\`rebase.data.collection(slug)\\` still works —\nbut it is generic over \\`Record<string, unknown>\\` and gives up everything above.\n`\n\t});\n\treturn files;\n}\n//#endregion\nexport { CodegenError, generateSDK, generateTypedefs, indent, toCamelCase, toPascalCase, toSafeIdentifier };\n\n//# sourceMappingURL=index.es.js.map","/**\n * Rebase Schema Doctor — Three-way schema drift detection.\n *\n * Compares:\n * 1. Collection definitions → Generated Drizzle schema (staleness check)\n * 2. Collection definitions → Live PostgreSQL database (structural drift)\n *\n * Run via: rebase doctor\n */\nimport { promises as fsPromises } from \"fs\";\nimport * as fs from \"fs\";\nimport path from \"path\";\nimport { pathToFileURL } from \"url\";\nimport chalk from \"chalk\";\nimport { computeSchemaVersion, CollectionConfig, isPostgresCollectionConfig, Property, NumberProperty, StringProperty, DateProperty, ArrayProperty, MapProperty, RelationProperty, type ResolvedManyToMany, type ResolvedBelongsTo, isManyToMany } from \"@rebasepro/types\";\nimport { generateSchema } from \"./generate-drizzle-schema-logic\";\nimport { generateTypedefs } from \"@rebasepro/codegen\";\nimport { getTableName, resolveCollectionRelations, findRelation, relationalCollections } from \"@rebasepro/common\";\nimport { toSnakeCase } from \"@rebasepro/utils\";\nimport { loadCollectionsFromDirectory } from \"@rebasepro/server\";\n// The report is CLI output, not application logging — see cli-output.ts.\nimport { out, outError } from \"../cli-output\";\nimport { deepestErrorMessage, diagnoseDbError, parseHostInfo } from \"../cli-errors\";\n\n/**\n * Resolve the SQL column name for a property.\n * Uses the explicit `columnName` when set (e.g. from introspection),\n * falling back to `toSnakeCase(propName)` for manually-authored collections.\n */\nconst resolveColumnName = (propName: string, prop?: Property | null): string => {\n if (prop && \"columnName\" in prop && typeof prop.columnName === \"string\") {\n return prop.columnName;\n }\n return toSnakeCase(propName);\n};\n\n// ── Types ────────────────────────────────────────────────────────────────\n\nexport type IssueSeverity = \"error\" | \"warning\" | \"info\";\n\nexport interface DoctorIssue {\n severity: IssueSeverity;\n category: \"missing_table\" | \"missing_column\" | \"type_mismatch\" | \"missing_constraint\" | \"schema_stale\" | \"missing_enum\" | \"enum_value_mismatch\" | \"missing_foreign_key\" | \"sdk_stale\" | \"sdk_not_generated\" | \"sdk_ungeneratable\";\n table?: string;\n column?: string;\n expected?: string;\n actual?: string;\n message: string;\n fix: string;\n}\n\nexport interface DoctorPhase {\n passed: boolean;\n issues: DoctorIssue[];\n /**\n * Why this phase never ran, when it did not.\n *\n * A check that did not happen is a third state, not a passing one. While a\n * skipped phase initialised to `{ passed: true, issues: [] }` it rendered as\n * `✅ Collections → Database: In sync` and counted towards\n * `✓ All schemas are in sync!` — so a project whose connection string was\n * spelled `POSTGRES_URL`, or a CI job that never exported one, got two green\n * ticks and exit 0 against a database with no tables in it.\n */\n skipped?: string;\n /**\n * Why this phase had nothing to compare against, when it had nothing.\n *\n * Distinct from `skipped`, which means \"the check could not run, and that\n * is probably worth fixing\". This one means \"the artifact is optional and\n * you have not asked for it\": no drift is possible, so the run is still a\n * clean bill of health.\n *\n * It exists because the alternative was a contradiction. The typed-SDK\n * phase returned `{ passed: true }` when `generated/sdk/database.types.ts`\n * did not exist, so a fresh project's report read\n * `✅ Collections → SDK Types: In sync` directly above\n * `ℹ Typed SDK not generated (optional).` — one line calling a file\n * synchronised and the next saying it is absent. \"In sync\" is a claim about\n * a comparison, and no comparison happened.\n */\n notApplicable?: string;\n /**\n * The phase was skipped by a fault rather than by a choice.\n *\n * `skipped` covers both: \"no DATABASE_URL, so nothing to compare against\"\n * is a local situation a developer may be perfectly happy with, and \"the\n * DATABASE_URL you set refuses connections\" is not. The report renders them\n * the same way — a check that did not run is a check that did not run — but\n * the exit code must not, or `rebase doctor` in CI goes green against a\n * database it never reached.\n */\n blocked?: boolean;\n}\n\nexport interface DoctorReport {\n collectionsToSchema: DoctorPhase;\n collectionsToSdk: DoctorPhase;\n schemaToDatabase: DoctorPhase;\n summary: { passed: number; skipped: number; notApplicable: number; warnings: number; errors: number };\n}\n\n// ── Column type mapping (mirrors generate-drizzle-schema-logic.ts) ───────\n\nexport function getExpectedColumnType(prop: Property): string | null {\n switch (prop.type) {\n case \"string\": {\n const sp = prop as StringProperty;\n if (sp.enum) return \"USER-DEFINED\"; // pgEnum → USER-DEFINED in information_schema\n if (\"isId\" in sp && sp.isId === \"uuid\") return \"uuid\";\n if (sp.columnType === \"uuid\") return \"uuid\";\n if (sp.columnType === \"char\") return \"character\";\n if (sp.columnType === \"varchar\") return \"character varying\";\n // `text` is the default — see generate-postgres-ddl-logic.\n return \"text\";\n }\n case \"number\": {\n const np = prop as NumberProperty;\n if (np.columnType) {\n // The generator passes any columnType straight through to drizzle,\n // so mirror that rather than enumerating a subset (which reported\n // drift for anything unlisted, e.g. smallint). Serial types are\n // integers with a sequence default; information_schema reports the\n // underlying width.\n const serialWidths: Record<string, string> = {\n serial: \"integer\",\n bigserial: \"bigint\",\n smallserial: \"smallint\"\n };\n return serialWidths[np.columnType] ?? np.columnType;\n }\n if (np.validation?.integer || (\"isId\" in np && np.isId)) return \"integer\";\n return \"numeric\";\n }\n case \"boolean\":\n return \"boolean\";\n case \"date\": {\n const dp = prop as DateProperty;\n if (dp.columnType === \"date\") return \"date\";\n if (dp.columnType === \"time\") return \"time without time zone\";\n return \"timestamp with time zone\";\n }\n case \"array\": {\n const ap = prop as ArrayProperty;\n let colType = ap.columnType;\n if (!colType && ap.of && !Array.isArray(ap.of)) {\n const ofProp = ap.of as Property;\n if (ofProp.type === \"string\") {\n colType = \"text[]\";\n } else if (ofProp.type === \"number\") {\n colType = ofProp.validation?.integer ? \"integer[]\" : \"numeric[]\";\n } else if (ofProp.type === \"boolean\") {\n colType = \"boolean[]\";\n }\n }\n\n if (colType === \"json\") return \"json\";\n if (colType === \"jsonb\") return \"jsonb\";\n if (colType && colType.endsWith(\"[]\")) return \"ARRAY\";\n return \"jsonb\";\n }\n case \"map\": {\n const mp = prop as MapProperty;\n if (mp.columnType === \"json\") return \"json\";\n return \"jsonb\";\n }\n case \"relation\":\n return null; // FK columns are derived from the relation, not from the property\n case \"reference\":\n // A reference FK follows the key it points at, and a string key is\n // `text` — see generate-postgres-ddl-logic.\n return \"text\";\n case \"vector\":\n return \"USER-DEFINED\";\n case \"binary\":\n return \"bytea\";\n default:\n return null;\n }\n}\n\n// ── Collection loading ───────────────────────────────────────────────────\n\n/**\n * Re-exported so callers keep importing it from here, but the implementation is\n * now shared with the runtime and the generators — a doctor that disagreed with\n * the policy generator about which files are collections would compare the\n * wrong thing.\n */\nexport async function loadCollections(collectionsPath: string): Promise<CollectionConfig[]> {\n return loadCollectionsFromDirectory(collectionsPath);\n}\n\n// ── Phase 1: Collections ↔ Generated Schema ─────────────────────────────\n\nexport async function checkCollectionsVsSchema(\n collections: CollectionConfig[],\n schemaFilePath: string\n): Promise<DoctorPhase> {\n const issues: DoctorIssue[] = [];\n\n // Check if schema file exists\n if (!fs.existsSync(schemaFilePath)) {\n issues.push({\n severity: \"error\",\n category: \"schema_stale\",\n message: \"Generated schema file does not exist.\",\n fix: \"Run `rebase schema generate`\"\n });\n return { passed: false,\nissues };\n }\n\n // Re-generate schema in-memory and compare with file on disk. Only the\n // collections the generator itself will emit — a Firestore collection has\n // no table in the generated file and must not be reported as missing one.\n const postgresCollections = relationalCollections(collections);\n if (postgresCollections.length === 0) {\n return { passed: true,\nissues };\n }\n\n try {\n const expectedSchema = await generateSchema(postgresCollections);\n const actualSchema = await fsPromises.readFile(schemaFilePath, \"utf-8\");\n\n // Normalize whitespace for comparison\n const normalize = (s: string) =>\n s\n .replace(/\\/\\/.*$/gm, \"\") // strip single-line comments\n .replace(/\\/\\*[\\s\\S]*?\\*\\//g, \"\") // strip multi-line comments\n .replace(/\\s+/g, \" \")\n .trim();\n\n if (normalize(expectedSchema) !== normalize(actualSchema)) {\n issues.push({\n severity: \"warning\",\n category: \"schema_stale\",\n message: \"Generated schema is out of date — collection definitions have changed since last generation.\",\n fix: \"Run `rebase schema generate`\"\n });\n }\n } catch (err: unknown) {\n const message = err instanceof Error ? err.message : String(err);\n issues.push({\n severity: \"warning\",\n category: \"schema_stale\",\n message: `Could not regenerate schema for comparison: ${message}`,\n fix: \"Run `rebase schema generate` to verify\"\n });\n }\n\n return { passed: issues.length === 0,\nissues };\n}\n\nexport async function checkCollectionsVsSdk(\n collections: CollectionConfig[],\n sdkFilePath: string\n): Promise<DoctorPhase> {\n const issues: DoctorIssue[] = [];\n\n // The typed SDK is opt-in — nothing in a scaffolded project imports it until\n // you choose to. A project that never generated one isn't drifting, so report\n // it as information rather than a warning; otherwise `doctor` can never come\n // back clean on a fresh project and users learn to ignore its output.\n if (!fs.existsSync(sdkFilePath)) {\n issues.push({\n severity: \"info\",\n category: \"sdk_not_generated\",\n message: \"Typed SDK not generated (optional).\",\n fix: \"Run `rebase generate-sdk` if you want typed collection access\"\n });\n return { passed: true,\nissues,\nnotApplicable: \"not generated (optional)\" };\n }\n\n try {\n const expectedSdk = generateTypedefs(collections);\n const actualSdk = await fsPromises.readFile(sdkFilePath, \"utf-8\");\n\n // Normalize whitespace for comparison\n const normalize = (s: string) =>\n s\n .replace(/\\/\\/.*$/gm, \"\") // strip single-line comments\n .replace(/\\/\\*[\\s\\S]*?\\*\\//g, \"\") // strip multi-line comments\n .replace(/\\s+/g, \" \")\n .trim();\n\n if (normalize(expectedSdk) !== normalize(actualSdk)) {\n issues.push({\n severity: \"warning\",\n category: \"sdk_stale\",\n message: \"Generated SDK types are out of date — collection definitions have changed since last SDK generation.\",\n fix: \"Run `rebase generate-sdk`\"\n });\n }\n } catch (err: unknown) {\n const message = err instanceof Error ? err.message : String(err);\n\n // A `CodegenError` is not \"the comparison did not run\" — it is the\n // schema saying it cannot produce a valid typed client at all, most\n // often two slugs collapsing onto one accessor. Reporting that as a\n // warning about staleness described the wrong problem and pointed at a\n // command that would fail the same way.\n if (err instanceof Error && err.name === \"CodegenError\") {\n issues.push({\n severity: \"error\",\n category: \"sdk_ungeneratable\",\n message: `The typed SDK cannot be generated from these collections: ${message}`,\n fix: \"Fix the collection definitions named above, then run `rebase generate-sdk`\"\n });\n } else {\n issues.push({\n severity: \"warning\",\n category: \"sdk_stale\",\n message: `Could not regenerate SDK types for comparison: ${message}`,\n fix: \"Run `rebase generate-sdk` to verify\"\n });\n }\n }\n\n return { passed: issues.length === 0,\nissues };\n}\n\n// ── Phase 2: Collections ↔ Database ──────────────────────────────────────\n\ninterface DbColumn {\n table_schema: string;\n table_name: string;\n column_name: string;\n data_type: string;\n is_nullable: string;\n udt_name: string;\n}\n\n\ninterface DbEnumValue {\n enum_name: string;\n enum_value: string;\n}\n\n/**\n * Extensions the collections need, that the database does not have installed.\n *\n * Only `vector` today, and only because a `{ type: \"vector\" }` property plans a\n * column of a type that will not exist. Rebase installs it only where a project\n * has said it may (`database({ extensions: [\"vector\"] })`), so the absence is\n * often correct configuration and a wrong deployment — which is exactly why the\n * report has to name it rather than the first failing INSERT.\n *\n * Exported for its test.\n */\nexport function missingExtensionIssues(\n collections: CollectionConfig[],\n installed: ReadonlySet<string>\n): DoctorIssue[] {\n const usesVector = collections.some(collection =>\n Object.values(collection.properties ?? {})\n .some(property => (property as { type?: string } | null)?.type === \"vector\"));\n\n if (!usesVector || installed.has(\"vector\")) return [];\n\n return [{\n severity: \"error\",\n category: \"missing_table\",\n message: 'A collection declares a `{ type: \"vector\" }` property and the pgvector extension is not installed on this database.',\n fix: 'Declare it — `database({ extensions: [\"vector\"] })` in config/resources.ts — and use an image that ships the library '\n + \"(the scaffold's pgvector/pgvector:pg18; a stock postgres:18 does not). Or install it once by hand: CREATE EXTENSION vector;\"\n }];\n}\n\n/**\n * The stamp the runtime wrote, against the collections on disk.\n *\n * A stamp that disagrees means this database was provisioned from a different\n * set of collections than the ones in this checkout — a colleague's branch, an\n * older deploy, a tenant somebody else migrated. Everything else in this report\n * is then describing a schema somebody else created, which is worth knowing\n * before acting on any of it.\n *\n * A warning, not an error, and never a direction: the stamp is a hash, so it\n * can say the two disagree and never which is ahead. `null` — never stamped —\n * is not a finding: every database provisioned before the stamp existed reads\n * that way, and so does every fresh one.\n */\nexport async function schemaStampIssues(\n pool: { query<T>(text: string): Promise<{ rows: T[] }> },\n collections: CollectionConfig[]\n): Promise<DoctorIssue[]> {\n let stamped: string | null = null;\n try {\n // `to_regclass` so a missing schema or table is a null rather than a\n // thrown 42P01 — the commonest state on a fresh database is neither.\n const present = await pool.query<{ present: boolean }>(\n `SELECT to_regclass('\"rebase\".\"schema_meta\"') IS NOT NULL AS present`\n );\n if (!present.rows[0]?.present) return [];\n const row = await pool.query<{ value: string }>(\n `SELECT value FROM \"rebase\".\"schema_meta\" WHERE key = 'collections_schema_version' LIMIT 1`\n );\n stamped = row.rows[0]?.value ?? null;\n } catch {\n // The stamp is a diagnostic, not a gate. A database that will not\n // answer this question has bigger problems, and they are reported by\n // the checks around this one.\n return [];\n }\n\n if (!stamped) return [];\n const expected = computeSchemaVersion(collections);\n if (stamped === expected) return [];\n\n return [{\n severity: \"warning\",\n category: \"schema_stale\",\n message: `This database was provisioned from a different set of collections (stamp ${stamped.slice(0, 12)}…, this checkout computes ${expected.slice(0, 12)}…).`,\n fix: \"A hash says the two disagree, never which is ahead. Check you are pointed at the database you think you are; then `rebase db push` to bring it to these collections.\"\n }];\n}\n\nexport async function checkCollectionsVsDatabase(\n collections: CollectionConfig[],\n databaseUrl: string\n): Promise<DoctorPhase> {\n const issues: DoctorIssue[] = [];\n\n // Dynamic import to avoid loading pg when not needed\n const pgModule = await import(\"pg\");\n const { Pool } = pgModule.default ?? pgModule;\n const pool = new Pool({ connectionString: databaseUrl });\n\n // Determine all schemas defined by the collections, plus public and rebase\n const schemas = Array.from(new Set([\n \"public\",\n \"rebase\",\n ...relationalCollections(collections)\n .filter(isPostgresCollectionConfig)\n .map(c => c.schema)\n .filter((s): s is string => !!s)\n ]));\n\n try {\n // Fetch all tables in the defined schemas\n const tablesResult = await pool.query<{ table_schema: string; table_name: string }>(\n `SELECT table_schema, table_name \n FROM information_schema.tables \n WHERE table_schema = ANY($1) AND table_type = 'BASE TABLE'`,\n [schemas]\n );\n const existingTables = new Set(tablesResult.rows.map((r) =>\n r.table_schema === \"public\" ? r.table_name : `${r.table_schema}.${r.table_name}`\n ));\n\n // An extension the collections need and the database does not have.\n //\n // The symptom without this is `type \"vector\" does not exist` on the\n // first write to a table that looks perfectly well-formed in the\n // report: the column is planned, the extension is not, and the two\n // facts live in different places. Extensions are opt-in by design —\n // installing one needs an image that ships the library and a role\n // allowed to install it — so this reports, it does not install.\n const extensionsResult = await pool.query<{ extname: string }>(\"SELECT extname FROM pg_extension\");\n const installedExtensions = new Set(extensionsResult.rows.map(r => r.extname));\n issues.push(...missingExtensionIssues(collections, installedExtensions));\n\n // A database provisioned from a different set of collections than the\n // ones on disk.\n //\n // The runtime stamps what it applied; nothing compared it outside boot,\n // and a stamp that disagrees is exactly the state where the rest of\n // this report is describing a schema somebody else's deploy created.\n issues.push(...await schemaStampIssues(pool, collections));\n\n // Fetch all columns in the defined schemas\n const columnsResult = await pool.query<DbColumn>(\n `SELECT table_schema, table_name, column_name, data_type, is_nullable, udt_name\n FROM information_schema.columns\n WHERE table_schema = ANY($1)\n ORDER BY table_schema, table_name, ordinal_position`,\n [schemas]\n );\n const columnsByTable = new Map<string, DbColumn[]>();\n for (const row of columnsResult.rows) {\n const tableSchema = row.table_schema;\n const tableName = row.table_name;\n const key = tableSchema === \"public\" ? tableName : `${tableSchema}.${tableName}`;\n if (!columnsByTable.has(key)) {\n columnsByTable.set(key, []);\n }\n columnsByTable.get(key)!.push(row);\n }\n\n // Fetch enums\n const enumsResult = await pool.query<DbEnumValue>(\n `SELECT t.typname as enum_name, e.enumlabel as enum_value\n FROM pg_type t\n JOIN pg_enum e ON t.oid = e.enumtypid\n ORDER BY t.typname, e.enumsortorder`\n );\n const enumsByName = new Map<string, string[]>();\n for (const row of enumsResult.rows) {\n if (!enumsByName.has(row.enum_name)) {\n enumsByName.set(row.enum_name, []);\n }\n enumsByName.get(row.enum_name)!.push(row.enum_value);\n }\n\n // Fetch foreign key constraints in the defined schemas\n const fksResult = await pool.query<{\n constraint_name: string;\n table_schema: string;\n table_name: string;\n column_name: string;\n foreign_table_schema: string;\n foreign_table_name: string;\n foreign_column_name: string;\n }>(\n `SELECT\n tc.constraint_name,\n tc.table_schema,\n tc.table_name,\n kcu.column_name,\n ccu.table_schema AS foreign_table_schema,\n ccu.table_name AS foreign_table_name,\n ccu.column_name AS foreign_column_name\n FROM information_schema.table_constraints AS tc\n JOIN information_schema.key_column_usage AS kcu\n ON tc.constraint_name = kcu.constraint_name AND tc.table_schema = kcu.table_schema\n JOIN information_schema.constraint_column_usage AS ccu\n ON ccu.constraint_name = tc.constraint_name AND ccu.table_schema = tc.table_schema\n WHERE tc.constraint_type = 'FOREIGN KEY' AND tc.table_schema = ANY($1)`,\n [schemas]\n );\n const fksByTable = new Map<string, typeof fksResult.rows>();\n for (const row of fksResult.rows) {\n const tableSchema = row.table_schema;\n const tableName = row.table_name;\n const key = tableSchema === \"public\" ? tableName : `${tableSchema}.${tableName}`;\n if (!fksByTable.has(key)) {\n fksByTable.set(key, []);\n }\n fksByTable.get(key)!.push(row);\n }\n\n // ── Compare each collection against the database ─────────────────\n\n const postgresCollections = relationalCollections(collections);\n\n for (const collection of postgresCollections) {\n const tableName = getTableName(collection);\n const schemaName = (collection as { schema?: string }).schema || \"public\";\n const fullTableName = schemaName === \"public\" ? tableName : `${schemaName}.${tableName}`;\n\n // Check table existence\n if (!existingTables.has(fullTableName)) {\n issues.push({\n severity: \"error\",\n category: \"missing_table\",\n table: fullTableName,\n message: `Table \"${fullTableName}\" does not exist in the database.`,\n fix: \"Run `rebase db push` or `rebase db generate && rebase db migrate`\"\n });\n continue; // Skip column checks for missing tables\n }\n\n const dbColumns = columnsByTable.get(fullTableName) ?? [];\n const dbColumnMap = new Map(dbColumns.map((c) => [c.column_name, c]));\n\n // System columns that Rebase always creates\n const systemColumns = new Set([\"id\", \"created_on\", \"updated_on\"]);\n\n // Check properties → columns\n for (const [propName, prop] of Object.entries(collection.properties ?? {})) {\n if (prop.type === \"relation\") {\n // ResolvedRelation columns are derived from localKey\n const resolvedRelations = resolveCollectionRelations(collection);\n const relation = findRelation(resolvedRelations, (prop as RelationProperty).relation?.relationName ?? propName);\n if (relation?.kind === \"belongsTo\") {\n const fkColName = relation.localKey;\n if (!dbColumnMap.has(fkColName)) {\n issues.push({\n severity: \"error\",\n category: \"missing_column\",\n table: fullTableName,\n column: fkColName,\n message: `Foreign key column \"${fkColName}\" for relation \"${propName}\" is missing from table \"${fullTableName}\".`,\n fix: \"Run `rebase db push` or `rebase db generate && rebase db migrate`\"\n });\n }\n\n // Check FK constraint exists\n const tableFks = fksByTable.get(fullTableName) ?? [];\n let targetTableName = \"unknown\";\n let targetSchemaName = \"public\";\n try {\n const targetColl = relation.target();\n targetTableName = getTableName(targetColl);\n targetSchemaName = (targetColl as { schema?: string }).schema || \"public\";\n } catch { /* ignore */ }\n\n const hasFk = tableFks.some((fk) =>\n fk.column_name === fkColName &&\n fk.foreign_table_name === targetTableName &&\n fk.foreign_table_schema === targetSchemaName\n );\n\n if (dbColumnMap.has(fkColName) && !hasFk) {\n issues.push({\n severity: \"warning\",\n category: \"missing_foreign_key\",\n table: fullTableName,\n column: fkColName,\n message: `Column \"${fkColName}\" exists but has no FOREIGN KEY constraint referencing \"${targetSchemaName === \"public\" ? targetTableName : `${targetSchemaName}.${targetTableName}`}\".`,\n fix: \"Run `rebase db push` or add the constraint manually\"\n });\n }\n }\n continue;\n }\n\n const colName = resolveColumnName(propName, prop);\n\n // Skip system columns — they're handled automatically\n if (systemColumns.has(colName)) continue;\n\n const dbCol = dbColumnMap.get(colName);\n if (!dbCol) {\n issues.push({\n severity: \"error\",\n category: \"missing_column\",\n table: fullTableName,\n column: colName,\n message: `Column \"${colName}\" is defined in collection \"${collection.slug}\" but missing from table \"${fullTableName}\".`,\n fix: \"Run `rebase db push` or `rebase db generate && rebase db migrate`\"\n });\n continue;\n }\n\n // Type check\n const expectedType = getExpectedColumnType(prop);\n if (expectedType) {\n const actualType = dbCol.data_type;\n let isMismatch = actualType !== expectedType;\n if (prop.type === \"vector\" && dbCol.udt_name !== \"vector\") {\n isMismatch = true;\n }\n if (prop.type === \"array\") {\n const ap = prop as ArrayProperty;\n let expectedColType = ap.columnType;\n if (!expectedColType && ap.of && !Array.isArray(ap.of)) {\n const ofProp = ap.of as Property;\n if (ofProp.type === \"string\") expectedColType = \"text[]\";\n else if (ofProp.type === \"number\") expectedColType = ofProp.validation?.integer ? \"integer[]\" : \"numeric[]\";\n else if (ofProp.type === \"boolean\") expectedColType = \"boolean[]\";\n }\n if (expectedColType && expectedColType.endsWith(\"[]\")) {\n if (actualType !== \"ARRAY\") {\n isMismatch = true;\n } else {\n const expectedUdt = expectedColType === \"text[]\" ? \"_text\" :\n expectedColType === \"integer[]\" ? \"_int4\" :\n expectedColType === \"boolean[]\" ? \"_bool\" :\n expectedColType === \"numeric[]\" ? \"_numeric\" : \"\";\n if (expectedUdt && dbCol.udt_name !== expectedUdt) {\n isMismatch = true;\n }\n }\n }\n }\n if (isMismatch) {\n issues.push({\n severity: \"warning\",\n category: \"type_mismatch\",\n table: fullTableName,\n column: colName,\n expected: prop.type === \"vector\" ? \"vector\" : expectedType,\n actual: dbCol.udt_name === \"vector\" ? \"vector\" : actualType,\n message: `Column \"${colName}\" in table \"${fullTableName}\": expected type \"${prop.type === \"vector\" ? \"vector\" : expectedType}\" but found \"${dbCol.udt_name === \"vector\" ? \"vector\" : actualType}\".`,\n fix: \"Review collection property type or run a migration\"\n });\n }\n }\n\n // Enum value check\n if (prop.type === \"string\" && (prop as StringProperty).enum) {\n const enumValues = (prop as StringProperty).enum;\n if (enumValues) {\n const enumName = `${tableName}_${colName}`;\n const dbEnumValues = enumsByName.get(enumName);\n if (!dbEnumValues) {\n issues.push({\n severity: \"warning\",\n category: \"missing_enum\",\n table: fullTableName,\n column: colName,\n expected: enumName,\n message: `Enum type \"${enumName}\" is defined in collection but not found in the database.`,\n fix: \"Run `rebase db push` or `rebase db generate && rebase db migrate`\"\n });\n } else {\n // Compare enum values\n const expectedValues = Array.isArray(enumValues)\n ? enumValues.map((v) => (typeof v === \"string\" ? v : String(v.id)))\n : Object.keys(enumValues);\n\n const missing = expectedValues.filter((v) => !dbEnumValues.includes(v));\n const extra = dbEnumValues.filter((v) => !expectedValues.includes(v));\n\n if (missing.length > 0 || extra.length > 0) {\n const parts: string[] = [];\n if (missing.length > 0) parts.push(`missing: ${missing.join(\", \")}`);\n if (extra.length > 0) parts.push(`extra in DB: ${extra.join(\", \")}`);\n issues.push({\n severity: \"warning\",\n category: \"enum_value_mismatch\",\n table: fullTableName,\n column: colName,\n expected: expectedValues.join(\", \"),\n actual: dbEnumValues.join(\", \"),\n message: `Enum values for \"${colName}\" in table \"${fullTableName}\" are out of sync (${parts.join(\"; \")}).`,\n fix: \"Run `rebase db push` to update the enum\"\n });\n }\n }\n }\n }\n }\n\n // Also check junction tables for many-to-many relations\n const resolvedRelations = resolveCollectionRelations(collection);\n for (const relation of Object.values(resolvedRelations)) {\n if (isManyToMany(relation)) {\n const junctionTable = relation.through.table;\n const junctionSchema = (collection as { schema?: string }).schema || \"public\";\n const fullJunctionTable = junctionSchema === \"public\" ? junctionTable : `${junctionSchema}.${junctionTable}`;\n if (!existingTables.has(fullJunctionTable)) {\n issues.push({\n severity: \"error\",\n category: \"missing_table\",\n table: fullJunctionTable,\n message: `Junction table \"${fullJunctionTable}\" for many-to-many relation \"${relation.relationName}\" is missing.`,\n fix: \"Run `rebase db push` or `rebase db generate && rebase db migrate`\"\n });\n }\n }\n }\n }\n } finally {\n await pool.end();\n }\n\n return { passed: issues.length === 0,\nissues };\n}\n\n// ── Report Rendering ─────────────────────────────────────────────────────\n\nexport function renderReport(report: DoctorReport): void {\n out();\n out(chalk.bold(\" 🩺 Rebase Schema Doctor\"));\n out(chalk.gray(\" ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\"));\n out();\n\n // Phase 1\n renderPhase(\"Collections → Generated Schema\", report.collectionsToSchema);\n\n // Phase 2\n renderPhase(\"Collections → Database\", report.schemaToDatabase);\n\n // Phase 3\n renderPhase(\"Collections → SDK Types\", report.collectionsToSdk);\n\n // Summary\n out(chalk.gray(\" ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\"));\n const { passed, skipped, notApplicable, warnings, errors } = report.summary;\n\n const parts: string[] = [];\n parts.push(chalk.green(`${passed} passed`));\n if (skipped > 0) parts.push(chalk.yellow(`${skipped} skipped`));\n if (notApplicable > 0) parts.push(chalk.gray(`${notApplicable} not applicable`));\n if (warnings > 0) parts.push(chalk.yellow(`${warnings} warnings`));\n if (errors > 0) parts.push(chalk.red(`${errors} errors`));\n\n out(` Summary: ${parts.join(\", \")}`);\n out();\n\n if (errors > 0) {\n out(chalk.red.bold(\" ✗ Schema drift detected. Run the suggested fixes above.\"));\n } else if (warnings > 0) {\n out(chalk.yellow.bold(\" ⚠ Minor issues detected. Consider running the suggested fixes.\"));\n } else if (skipped > 0) {\n // Never \"all schemas are in sync\" off the back of a check that did not\n // run: a clean bill of health has to come from measurement.\n out(chalk.yellow.bold(` ⚠ Nothing wrong in the checks that ran, but ${skipped} did not run — this is not a clean bill of health.`));\n } else {\n out(chalk.green.bold(\" ✓ All schemas are in sync!\"));\n }\n out();\n}\n\nfunction renderPhase(label: string, phase: DoctorPhase): void {\n // A phase that never ran gets its own marker and its reason. It is neither\n // \"In sync\" nor a drift report; conflating it with the former is what let\n // doctor certify a database it had not opened.\n if (phase.skipped) {\n out(` ${chalk.yellow(\"⏭\")} ${label}: ${chalk.yellow(`skipped (${phase.skipped})`)}`);\n out();\n return;\n }\n\n // Nothing to compare against. Its own marker, and only the remedy under it\n // — the phase header already carries the reason, so re-printing the note\n // would say the same thing twice. See DoctorPhase.notApplicable.\n if (phase.notApplicable) {\n out(` ${chalk.gray(\"➖\")} ${label}: ${chalk.gray(phase.notApplicable)}`);\n for (const issue of phase.issues.filter((i) => i.severity === \"info\")) {\n out(` ${chalk.gray(issue.fix)}`);\n }\n out();\n return;\n }\n\n const issues = phase.issues;\n const errorCount = issues.filter((i) => i.severity === \"error\").length;\n const warnCount = issues.filter((i) => i.severity === \"warning\").length;\n const infoIssues = issues.filter((i) => i.severity === \"info\");\n\n // Informational notes don't make a phase unhealthy, so key the header off\n // real problems rather than `phase.passed` alone.\n if (errorCount === 0 && warnCount === 0) {\n out(` ${chalk.green(\"✅\")} ${label}: ${chalk.green(\"In sync\")}`);\n } else {\n const parts: string[] = [];\n if (errorCount > 0) parts.push(`${errorCount} error${errorCount > 1 ? \"s\" : \"\"}`);\n if (warnCount > 0) parts.push(`${warnCount} warning${warnCount > 1 ? \"s\" : \"\"}`);\n out(` ${chalk.yellow(\"⚠️\")} ${label}: ${chalk.yellow(parts.join(\", \"))}`);\n }\n out();\n\n // Notes render as a quiet one-liner, not a full drift box.\n for (const issue of infoIssues) {\n const fixPart = issue.fix ? chalk.gray(` — ${issue.fix}`) : \"\";\n out(` ${chalk.gray(\"ℹ\")} ${chalk.gray(issue.message)}${fixPart}`);\n out();\n }\n\n for (const issue of issues.filter((i) => i.severity !== \"info\")) {\n const severityIcon = issue.severity === \"error\" ? chalk.red(\"✗\") : chalk.yellow(\"⚠\");\n const categoryLabel = formatCategory(issue.category);\n out(` ${chalk.gray(\"┌─\")} ${severityIcon} ${chalk.bold(categoryLabel)} ${chalk.gray(\"─\".repeat(Math.max(0, 42 - categoryLabel.length)))}`);\n\n if (issue.table) {\n const colPart = issue.column ? ` │ Column: ${chalk.cyan(issue.column)}` : \"\";\n out(` ${chalk.gray(\"│\")} Table: ${chalk.cyan(issue.table)}${colPart}`);\n }\n\n if (issue.expected && issue.actual) {\n out(` ${chalk.gray(\"│\")} Expected: ${chalk.green(issue.expected)} │ Actual: ${chalk.red(issue.actual)}`);\n }\n\n out(` ${chalk.gray(\"│\")} ${issue.message}`);\n out(` ${chalk.gray(\"│\")} Fix: ${chalk.blue(issue.fix)}`);\n out(` ${chalk.gray(\"└\" + \"─\".repeat(48))}`);\n out();\n }\n}\n\nfunction formatCategory(cat: DoctorIssue[\"category\"]): string {\n const labels: Record<DoctorIssue[\"category\"], string> = {\n missing_table: \"Missing Table\",\n missing_column: \"Missing Column\",\n type_mismatch: \"Type Mismatch\",\n missing_constraint: \"Missing Constraint\",\n schema_stale: \"Stale Schema\",\n missing_enum: \"Missing Enum\",\n enum_value_mismatch: \"Enum Value Mismatch\",\n missing_foreign_key: \"Missing Foreign Key\",\n sdk_stale: \"Stale SDK Types\",\n sdk_not_generated: \"SDK Types Not Generated\",\n sdk_ungeneratable: \"SDK Cannot Be Generated\"\n };\n return labels[cat];\n}\n\n// ── Main entry point ─────────────────────────────────────────────────────\n\nexport async function runDoctor(options: {\n collectionsPath: string;\n schemaPath: string;\n sdkPath: string;\n databaseUrl?: string;\n}): Promise<DoctorReport> {\n out();\n out(chalk.bold(\" 🩺 Loading collections...\"));\n const collections = await loadCollections(options.collectionsPath);\n if (collections.length === 0) {\n outError(chalk.red(\" ✗ No collections found.\"));\n process.exit(1);\n }\n out(chalk.gray(` Found ${collections.length} collection(s)`));\n out();\n\n // Phase 1: Collections ↔ Generated Schema\n out(chalk.gray(\" Checking Collections → Generated Schema...\"));\n const collectionsToSchema = await checkCollectionsVsSchema(collections, options.schemaPath);\n\n // Phase 2: Collections ↔ Database (only if we have a DATABASE_URL)\n let schemaToDatabase: DoctorPhase;\n if (options.databaseUrl) {\n out(chalk.gray(\" Checking Collections → Database...\"));\n try {\n schemaToDatabase = await checkCollectionsVsDatabase(collections, options.databaseUrl);\n } catch (err: unknown) {\n // A database that cannot be reached used to end the whole run: the\n // error escaped `runDoctor` and the CLI's last-resort catch printed\n // a stack. So the two phases that need no database — the generated\n // schema and the SDK types, both of which are read off disk — never\n // ran, and the one command whose job is to say what is wrong said\n // only \"Doctor failed\" over a drizzle stack trace.\n const banner = diagnoseDbError(err, options.databaseUrl);\n if (banner) outError(banner);\n const reason = deepestErrorMessage(err)\n ?? (err instanceof Error ? err.message : String(err));\n schemaToDatabase = {\n passed: false,\n issues: [],\n skipped: `could not reach ${parseHostInfo(options.databaseUrl)} — ${reason}`,\n blocked: true\n };\n }\n } else {\n // Not `{ passed: true }`: see DoctorPhase.skipped.\n schemaToDatabase = { passed: false,\nissues: [],\nskipped: \"DATABASE_URL not set\" };\n out(chalk.yellow(\" ⚠ DATABASE_URL not set — skipping database comparison.\"));\n out(chalk.gray(\" Set DATABASE_URL in your .env to enable full drift detection.\"));\n }\n\n // Phase 3: Collections ↔ SDK Types\n out(chalk.gray(\" Checking Collections → SDK Types...\"));\n const collectionsToSdk = await checkCollectionsVsSdk(collections, options.sdkPath);\n\n const phases = [collectionsToSchema, schemaToDatabase, collectionsToSdk];\n const allIssues = phases.flatMap((p) => p.issues);\n const summary = {\n // A skipped phase is not a passing one, however few issues it collected.\n // Nor is one with nothing to compare against: \"passed\" would put a\n // never-generated SDK in the same column as a verified one.\n passed: phases.filter((p) => p.passed && !p.skipped && !p.notApplicable).length,\n skipped: phases.filter((p) => p.skipped).length,\n notApplicable: phases.filter((p) => p.notApplicable && !p.skipped).length,\n warnings: allIssues.filter((i) => i.severity === \"warning\").length,\n errors: allIssues.filter((i) => i.severity === \"error\").length\n };\n\n const report: DoctorReport = { collectionsToSchema,\ncollectionsToSdk,\nschemaToDatabase,\nsummary };\n renderReport(report);\n\n return report;\n}\n"],"mappings":"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"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"ensure-collection-tables-D4wJzM4y.js","names":[],"sources":["../src/schema/column-type-drift.ts","../src/schema/ensure-collection-tables.ts"],"sourcesContent":["/**\n * A column whose type in the database is not the type the collection declares.\n *\n * The boot ensure is additive: it creates tables, columns, enum values and\n * indexes, and it never changes an existing column's type. That is the right\n * default — an automatic `ALTER COLUMN … TYPE` against customer data, decided\n * by a process nobody is watching, is not a thing to do quietly.\n *\n * What was wrong is that it happened *silently*. A property's `columnType`\n * changed after the first deploy, the deploy reported success, the column stayed\n * as it was, and the divergence surfaced later as writes failing:\n *\n * Invalid data format in \"listing_observations\":\n * malformed array literal: \"[0,0,0.0755,…]\"\n *\n * ...after a 10,690-row import, from a deployment that had said \"success\". The\n * two ends disagreed about the schema, so no value satisfied both: the declared\n * shape was rejected by Postgres and the Postgres shape was rejected by the\n * API's own validator, first. There is no encoding out of that, only a report.\n *\n * ## Why the comparison is deliberately blunt\n *\n * A drift report that cries wolf is worse than none, because the boot that says\n * it every time is the boot nobody reads. So types are compared by *family*, and\n * a family is only split from another when the difference changes what the\n * column can hold:\n *\n * - every numeric type is one family. `int4`, `numeric` and `float8` all arrive\n * as JavaScript numbers and accept each other's values; a database adopted\n * from an existing schema legitimately has `int4` where `type: \"number\"`\n * generates `NUMERIC`, and that is not news.\n * - every character type is one family, for the same reason: `text`,\n * `varchar(255)` and `char(3)` differ in what they *reject*, not in shape.\n * - `jsonb` and an array are different families, and that is the case this\n * exists for.\n *\n * Anything this module does not recognise — an enum type, a domain, a PostGIS\n * geometry, an extension type it has never met — returns `null` and is compared\n * with nothing. Silence on an unknown type is the deliberate choice: the cost of\n * a missed report is a question somebody asks once, and the cost of a false one\n * is a warning everybody learns to skip.\n */\n\n/** A column whose live type and declared type are in different families. */\nexport interface ColumnTypeDrift {\n /** `schema.table`. */\n table: string;\n /** The column name as it exists in the database. */\n column: string;\n /** What the collection says the column should be, e.g. `JSONB`. */\n declared: string;\n /** What the database actually has, as `udt_name` reports it, e.g. `_numeric`. */\n actual: string;\n}\n\n/**\n * Types that mean the same thing to everything above the driver.\n *\n * Keyed by every spelling either side can produce: `information_schema` reports\n * `udt_name` (`int4`, `_numeric`, `bpchar`), while the generators emit SQL\n * (`INTEGER`, `NUMERIC[]`, `CHAR(3)`). Both are normalised through here.\n */\nconst FAMILIES: Record<string, string> = {\n // Numbers. One family on purpose — see the note above.\n int2: \"number\", smallint: \"number\",\n int4: \"number\", int: \"number\", integer: \"number\",\n int8: \"number\", bigint: \"number\",\n numeric: \"number\", decimal: \"number\",\n float4: \"number\", real: \"number\",\n float8: \"number\", \"double precision\": \"number\",\n money: \"number\",\n\n // Characters. Also one family: the differences are limits, not shapes.\n text: \"text\", varchar: \"text\", \"character varying\": \"text\",\n bpchar: \"text\", char: \"text\", character: \"text\", citext: \"text\",\n\n bool: \"boolean\", boolean: \"boolean\",\n\n json: \"json\",\n jsonb: \"jsonb\",\n\n uuid: \"uuid\",\n bytea: \"bytea\",\n\n date: \"date\",\n time: \"time\", \"time without time zone\": \"time\",\n timetz: \"time\", \"time with time zone\": \"time\",\n timestamp: \"timestamp\", \"timestamp without time zone\": \"timestamp\",\n timestamptz: \"timestamptz\", \"timestamp with time zone\": \"timestamptz\",\n interval: \"interval\",\n\n tsvector: \"tsvector\",\n vector: \"vector\",\n halfvec: \"halfvec\",\n sparsevec: \"sparsevec\"\n};\n\n/**\n * One SQL or `udt_name` spelling reduced to its family, or `null` when this\n * module has no opinion about it.\n *\n * `null` is not a failure. It is the answer for an enum type, a domain, a\n * PostGIS column, a quoted qualified name — everything whose identity is a\n * *name* rather than a shape, where two spellings being different says nothing\n * about whether the values are compatible.\n */\nexport function typeFamily(raw: string | undefined | null): string | null {\n if (!raw) return null;\n let type = raw.trim().toLowerCase();\n if (!type) return null;\n\n // `\"public\".\"posts_status\"` — an enum, identified by name. Not comparable.\n if (type.startsWith(\"\\\"\")) return null;\n\n // `INTEGER GENERATED BY DEFAULT AS IDENTITY` is an `int4` with a default\n // attached; the identity clause is not part of the type.\n const generated = type.indexOf(\" generated \");\n if (generated > 0) type = type.slice(0, generated).trim();\n\n // Arrays, in both spellings: `NUMERIC[]` from the generators, `_numeric`\n // from `udt_name`. Compared element-wise, so `text[]` and `numeric[]` are\n // different and `int4[]` and `numeric[]` are not.\n if (type.endsWith(\"[]\")) {\n const element = typeFamily(type.slice(0, -2));\n return element && `array:${element}`;\n }\n if (type.startsWith(\"_\")) {\n const element = typeFamily(type.slice(1));\n return element && `array:${element}`;\n }\n\n // `VARCHAR(255)`, `VECTOR(384)`, `NUMERIC(10, 2)` — the width is a limit on\n // a family, not a family. A vector whose *dimensions* changed is a separate\n // concern with its own handling in the ensure.\n const paren = type.indexOf(\"(\");\n if (paren > 0) type = type.slice(0, paren).trim();\n\n return FAMILIES[type] ?? null;\n}\n\n/**\n * Do these two spellings describe columns that hold the same kind of value?\n *\n * `true` whenever the answer is not confidently \"no\" — an unrecognised type on\n * either side agrees with everything, because a report needs evidence and an\n * unknown type is the absence of it.\n */\nexport function typesAgree(declared: string, actual: string): boolean {\n const a = typeFamily(declared);\n const b = typeFamily(actual);\n if (a === null || b === null) return true;\n return a === b;\n}\n\n/**\n * The sentence a boot log says about one drifted column.\n *\n * Written to be actionable in the place it is read, which is a runtime log with\n * no way to run a migration from it: it names both types, says which surface\n * will break first, and gives the statement that resolves it.\n */\nexport function columnTypeDriftMessage(drift: ColumnTypeDrift): string {\n const [schema, table] = drift.table.split(\".\");\n return (\n `Column \"${drift.table}\".\"${drift.column}\" is ${drift.actual} in the database, but this ` +\n `collection declares ${drift.declared}. Boot does not change an existing column's type, so ` +\n \"the database keeps what it has and writes that match the declaration will be rejected by \" +\n \"Postgres. Change it deliberately, when you can watch it:\\n\" +\n ` ALTER TABLE \"${schema}\".\"${table}\" ALTER COLUMN \"${drift.column}\" ` +\n `TYPE ${drift.declared} USING \"${drift.column}\"::${drift.declared};`\n );\n}\n","/**\n * Bringing a database up to date with a bundle's collections, additively.\n *\n * ## Why this exists\n *\n * A managed runtime boots someone else's compiled project against a database it\n * has never seen. Auth tables are ensured at boot already, but collection tables\n * were not created by anything: the platform ran the app and every `/api/data/*`\n * request answered 500 on a missing relation. `rebase db push` cannot help — it\n * is an Atlas-driven CLI command, and the runtime image ships no CLI.\n *\n * ## Why additive-only, forever\n *\n * This runs unattended, against a database with customers' data in it, with no\n * human reading a diff. So it may only ever do things that cannot lose data:\n * create a missing table, add a missing column, create a missing enum type.\n *\n * It will **never** drop a table or a column, narrow a type, or alter a\n * constraint. A removed field leaves its column behind; a renamed field looks\n * like an addition and the old column stays. That is the correct trade for an\n * automated path — the alternative is an unattended process that can silently\n * destroy a column, which is precisely the failure `db push` was hardened\n * against. Destructive changes stay a deliberate, human-reviewed migration.\n *\n * Because of that, this is safe to run on every boot, and re-running it is a\n * no-op.\n */\nimport {\n declaredDatabaseExtensions,\n isPostgresCollectionConfig,\n type CollectionConfig,\n type Property\n} from \"@rebasepro/types\";\nimport { getTableName, relationalCollections } from \"@rebasepro/common\";\nimport { logger, isConcurrentDdlRace, isDuplicateObjectRace } from \"@rebasepro/server\";\nimport {\n assertSearchIsPostgresOnly,\n buildSearchColumnSpec,\n searchExtensionStatements,\n searchHelperFunctions,\n searchIndexStatements,\n searchColumnStamps,\n SEARCH_STAMP_PREFIX,\n SEARCH_TEXT_FN,\n SEARCH_UNACCENT_FN,\n type SearchColumnSpec\n} from \"./search-column\";\nimport {\n getSqlColumnType,\n resolveColumnName,\n isIdProperty,\n planRelationalColumns,\n planJunctionTables,\n quoteSqlLiteral\n} from \"./generate-postgres-ddl-logic\";\nimport {\n buildVectorColumnSpecs,\n buildVectorIndexPlan,\n vectorExtensionDeclared,\n vectorExtensionHint,\n vectorExtensionStatement,\n vectorIndexStatement,\n type SkippedVectorIndex\n} from \"./vector-index\";\nimport { buildCollectionIndexPlan, collectionIndexStatement } from \"./collection-index\";\nimport { extractCauseMessage, extractPgError } from \"../utils/pg-error-utils\";\nimport { columnTypeDriftMessage, typesAgree, type ColumnTypeDrift } from \"./column-type-drift\";\nimport {\n AUTH_USERS_COLUMNS,\n authUsersColumnDefinition,\n authUsersColumnSql,\n isAuthCollection\n} from \"./auth-users-columns\";\n\n/**\n * The subset of a database handle this needs: run a statement, get rows back.\n *\n * Deliberately parameterless. Everything here is DDL or catalogue reads keyed by\n * schema name, and schema names are identifiers — they cannot be bound as\n * parameters anyway. They are validated against {@link SAFE_IDENTIFIER} before\n * they reach a statement, so a config that somehow carried a quote is refused\n * rather than concatenated.\n */\nexport interface Queryable {\n query<T = unknown>(sql: string): Promise<{ rows: T[] }>;\n}\n\n/** Postgres identifiers this module is willing to interpolate. */\nconst SAFE_IDENTIFIER = /^[A-Za-z_][A-Za-z0-9_$]*$/;\n\nfunction assertSafeIdentifier(value: string, what: string): string {\n if (!SAFE_IDENTIFIER.test(value)) {\n throw new Error(`Refusing to build SQL with an unsafe ${what}: ${JSON.stringify(value)}`);\n }\n return value;\n}\n\n/** What the database currently has, as the planner needs it. */\nexport interface ExistingSchema {\n /** `schema.table` → set of column names. */\n tables: Map<string, Set<string>>;\n /** `schema.typename` of every enum type that already exists. */\n enums: Set<string>;\n /**\n * `schema.table.constraint` of every constraint that already exists.\n *\n * Optional so a caller that only cares about tables can still build one by\n * hand; absent is read as \"none known\", which at worst re-attempts a\n * constraint that then fails harmlessly as a duplicate.\n */\n constraints?: Set<string>;\n /**\n * `schema.table.column` → that column's comment, for the columns that have\n * one. This is where a generated search column's fingerprint lives, so it\n * is the only evidence that a `search` block has changed since the column\n * was built. Absent is read as \"no column is stamped\", which plans a stamp\n * and reports nothing as drifted.\n */\n columnComments?: Map<string, string>;\n /**\n * `schema.typename` → the values that type currently holds, in order.\n *\n * Without this, an enum type that already exists is skipped whole and a\n * value added to it never reaches the database — the type is there, so\n * nothing plans anything, and the first row using the new value is rejected\n * by a constraint nobody changed. Absent is read as \"the values are\n * unknown\", which keeps the old skip-by-name behaviour rather than guessing.\n */\n enumValues?: Map<string, string[]>;\n /** `schema.table.column` for every column the database marks NOT NULL. */\n notNullColumns?: Set<string>;\n /**\n * `schema.table.column` → the column's `udt_name` as the database reports\n * it (`int4`, `jsonb`, `_numeric` for an array of numeric).\n *\n * The only evidence that a column's declared type and its real type have\n * parted company. Absent is read as \"unknown\", which reports no drift —\n * see `column-type-drift.ts` for why silence is the right unknown here.\n */\n columnTypes?: Map<string, string>;\n /**\n * Tables known to hold at least one row.\n *\n * The only thing that decides whether a NOT NULL can be added without\n * reading the data: on an empty table the constraint cannot fail, on a\n * populated one it is checked against every existing row. Absent is read as\n * \"assume populated\", which is the conservative direction — it withholds a\n * constraint rather than attempting one that aborts the boot.\n */\n populatedTables?: Set<string>;\n /**\n * `schema.table.column` for every column the database gives a DEFAULT.\n *\n * Paired with {@link notNullColumns} to decide whether a column no property\n * declares can still accept a write: NOT NULL with a default can, NOT NULL\n * without one cannot. Absent is read as \"unknown\", which reports nothing.\n */\n columnDefaults?: Set<string>;\n}\n\n/**\n * How far the planner may go in making the database's constraints match the\n * configuration.\n *\n * - `additive` — the boot default. Columns, tables, indexes and enum values are\n * created; no existing column's constraints are touched. Unattended boots run\n * against customer data with nobody reading a diff, and a database adopted by\n * introspection legitimately carries NOT NULL on columns the generated\n * collection leaves optional (`introspect-db-logic` withholds `required` from\n * a column with a default or a trigger behind it). Converging there would\n * strip real constraints on first boot.\n * - `converge` — the live schema editor. Every statement is planned, shown to\n * the person making the change, and applied only once they confirm it. That\n * is the context in which changing an existing column's constraints is a\n * reviewed act rather than a surprise.\n */\nexport type ConstraintPolicy = \"additive\" | \"converge\";\n\nexport interface EnsureOptions {\n /** Defaults to `additive`. See {@link ConstraintPolicy}. */\n constraints?: ConstraintPolicy;\n /**\n * Server extensions the project's databases gave Rebase leave to install —\n * `declaredDatabaseExtensions()`. Absent means none, which is a refusal and\n * is the right default for a planner given no configuration at all.\n *\n * Explicit rather than read from the resource registry in here, because\n * this function is pure and several callers plan against fixtures: reaching\n * for a process-wide registry would make the plan depend on whatever some\n * other module happened to import. `ensureCollectionTables` is the boundary\n * that reads the world.\n */\n databaseExtensions?: readonly string[];\n}\n\nexport interface EnsureAction {\n kind: \"create-enum\" | \"create-table\" | \"add-column\" | \"add-constraint\" | \"rename-column\"\n | \"create-extension\" | \"create-function\" | \"create-index\" | \"comment-column\"\n | \"add-enum-value\" | \"set-not-null\" | \"drop-not-null\";\n /** Qualified target, for logging: `public.posts` or `public.posts.title`. */\n target: string;\n sql: string;\n}\n\nexport interface EnsurePlan {\n actions: EnsureAction[];\n /** Every statement, in dependency order. Empty when the schema is current. */\n statements: string[];\n /**\n * Relation columns this plan is about to create where the table already\n * carries the same column under its pre-singularization name.\n *\n * The reason this is reported rather than silently handled: the ensure is\n * additive, so it would add `category_id` beside a populated\n * `categorie_id` and the relation would then read the new, empty one. No\n * statement fails, no table is missing, and the only symptom is relations\n * resolving to nothing — which is indistinguishable from having no data.\n */\n legacyForeignKeys: LegacyForeignKey[];\n /**\n * Generated search columns whose `search` block has changed since they were\n * built. Reported, never planned into `actions` — see\n * {@link SearchColumnDrift} for why applying it is not this path's call.\n */\n searchDrift: SearchColumnDrift[];\n /**\n * Generated search columns that exist but carry no fingerprint — created\n * before this check existed, or by `search.sql` on an older CLI. The plan\n * stamps them so the *next* change is detectable; whether they match the\n * current block cannot be known, which is what the caller reports.\n */\n searchAdopted: { table: string; column: string }[];\n /**\n * Vector columns this plan is deliberately leaving unindexed, because\n * pgvector cannot build an ANN index that wide.\n *\n * Reported rather than thrown: the column is valid, storable and\n * searchable, and refusing the boot over it would make a working\n * configuration unbootable. Reported rather than dropped: an unindexed\n * vector column and an indexed one differ only in latency, so nothing\n * about the running system says which one you got.\n */\n vectorIndexSkipped: SkippedVectorIndex[];\n /**\n * Constraints the configuration asks for that this plan is not applying,\n * and why.\n *\n * This is the half of the feature that matters most. Every one of these was\n * previously withheld in silence: a required property arrived nullable, and\n * the only evidence was a database that disagreed with its own\n * configuration. Reporting them is what lets boot warn, the live editor\n * refuse, and the doctor explain — three surfaces that until now had nothing\n * to read.\n */\n withheldConstraints: WithheldConstraint[];\n /**\n * Columns whose type in the database is in a different family from the one\n * the collection declares.\n *\n * Reported and never acted on — see `column-type-drift.ts`. This is the one\n * divergence that presents as a *working* deploy: the statement was never\n * planned, so nothing failed, and the first evidence is a write rejected\n * hours later by whichever end validates first.\n */\n columnTypeDrift: ColumnTypeDrift[];\n /**\n * Columns that are NOT NULL, have no default, and that no property declares\n * any more — so every insert through the API is rejected for a field the\n * author cannot name, because they already deleted it.\n *\n * This is what a **rename** looks like to an additive provisioner. Rename\n * `title` to `headline` and boot does exactly what it promises: it adds\n * `headline` (nullable, correctly, and says why) and leaves `title` alone,\n * because dropping a column is destructive and this runs unattended. The\n * table is then unwritable:\n *\n * POST /api/data/posts -> 400 PG_23502\n * Missing required field: \"title\" in \"posts\" cannot be empty.\n *\n * naming a field that is no longer in the collection at all. On the default\n * first-run path there is no way out inside the product either: the managed\n * database is PGlite, and `db push` — the documented repair — refuses there\n * because Atlas needs a second database to diff against.\n *\n * Reported, never acted on. Dropping the column is the right fix roughly\n * always and destructive exactly once, which is not a decision to take\n * unattended.\n */\n orphanedRequiredColumns: OrphanedRequiredColumn[];\n}\n\n/**\n * A NOT NULL column with no default that no property declares.\n * @see EnsurePlan.orphanedRequiredColumns\n */\nexport interface OrphanedRequiredColumn {\n /** `schema.table`. */\n table: string;\n /** The column left behind. */\n column: string;\n /** The collection that no longer declares it. */\n slug: string;\n}\n\n/** A constraint the configuration asks for that the planner is not applying. */\nexport interface WithheldConstraint {\n /** `schema.table.column`. */\n target: string;\n kind: \"not-null\";\n /**\n * Why, in a sentence that names the obstacle rather than the rule. The\n * reader is looking at a column that is nullable when they asked for\n * required, and needs to know what to do about it.\n */\n reason: string;\n /** What would make it applicable. */\n remedy: string;\n}\n\n/**\n * A generated search column built from a `search` block that has since changed.\n *\n * Reported instead of applied because the two ways to apply it are both worse\n * than stopping. `ALTER COLUMN … SET EXPRESSION` exists only on PG17+ and\n * rewrites the table either way; `DROP COLUMN` + `ADD COLUMN` rewrites it under\n * an ACCESS EXCLUSIVE lock and rebuilds the GIN index. This module runs\n * unattended against live customer data with nobody reading a diff — the same\n * reason it withholds `SET NOT NULL` from an adopted table — so a multi-minute\n * outage is not a decision it may take on its own.\n *\n * Not applying it silently is not an option either: that is the bug this\n * detection exists for. A collection that added a field, flipped `unaccent` or\n * raised a weight kept indexing the *old* set forever, and the only symptom was\n * searches returning nothing for content plainly in the row.\n */\nexport interface SearchColumnDrift {\n /** `schema.table`. */\n table: string;\n column: string;\n /** The fingerprint recorded on the column. */\n found: string;\n /** The fingerprint the current `search` block computes. */\n expected: string;\n /** The statements that would rebuild the column, for the operator to run. */\n rebuild: string[];\n}\n\n/** A relation column whose old and new spellings both plausibly apply. */\nexport interface LegacyForeignKey {\n /** `schema.table`. */\n table: string;\n /** The name the current rule derives, and what this plan would create. */\n expected: string;\n /** The name the old rule derived, which the table already has. */\n legacy: string;\n}\n\nexport interface EnsureOutcome extends EnsurePlan {\n /**\n * Actions that could not be applied and are non-fatal by nature.\n *\n * Two kinds qualify. A foreign key can only fail on data that already\n * violates it, and the column it would police exists either way, so the\n * collection still serves; refusing to boot over one would turn a\n * pre-existing data problem into an outage. A column comment is the search\n * fingerprint, which needs table ownership — losing it costs drift\n * detection on the next boot, not the deployment. Both are reported loudly.\n */\n failures: { kind: EnsureAction[\"kind\"]; target: string; error: string }[];\n}\n\n/**\n * The schema a collection lives in — validated, because it is about to be\n * interpolated into DDL.\n *\n * Validation used to run only on the names read back OUT of the catalogue,\n * which is the direction that cannot hurt anyone: those came from Postgres. The\n * names going IN — the schema and table a collection declares — were quoted and\n * concatenated on trust, and quoting is not escaping. A table named\n * `x\" (id int); DROP TABLE users; --` closes the quote and the statement, and\n * the whole thing runs on the owner connection, which is the one connection in\n * the system that is exempt from every RLS policy.\n *\n * That is only a config file on a self-hosted project, where the person writing\n * it could run the SQL directly anyway. It is not only a config file where a\n * collection can be defined over the wire — the live-schema and source editors\n * both do that — and there the caller is an admin on the app, not an operator\n * of the database.\n */\nfunction schemaOf(collection: CollectionConfig): string {\n const schema = isPostgresCollectionConfig(collection) && collection.schema ? collection.schema : \"public\";\n return assertSafeIdentifier(schema, \"schema name\");\n}\n\n/** A collection's table name, validated for the same reason as {@link schemaOf}. */\nfunction tableOf(collection: CollectionConfig): string {\n return assertSafeIdentifier(getTableName(collection), \"table name\");\n}\n\nfunction qualified(collection: CollectionConfig): string {\n return `${schemaOf(collection)}.${tableOf(collection)}`;\n}\n\n/**\n * Enum types a collection's properties require, as `schema.typename`.\n *\n * Named exactly as the DDL generator names them (`<table>_<column>`), because\n * a column added here has to reference the same type the generator would have\n * created — a second, differently-named type for the same field would be a\n * silent schema fork.\n */\nfunction requiredEnums(collection: CollectionConfig): { name: string; values: string[] }[] {\n const table = tableOf(collection);\n const schema = schemaOf(collection);\n const out: { name: string; values: string[] }[] = [];\n for (const [propName, prop] of Object.entries(collection.properties ?? {})) {\n const p = prop as Property;\n if (!(\"enum\" in p) || !p.enum) continue;\n if (p.type !== \"string\" && p.type !== \"number\") continue;\n const values = (p.enum as unknown[])\n .map(entry =>\n entry && typeof entry === \"object\" && \"id\" in (entry as Record<string, unknown>)\n ? String((entry as Record<string, unknown>).id)\n : String(entry)\n )\n .filter(v => v.length > 0);\n if (values.length === 0) continue;\n out.push({ name: `${schema}.${table}_${resolveColumnName(propName, p)}`, values });\n }\n return out;\n}\n\n/**\n * Decide what to add. Pure — the caller supplies what exists and runs the result.\n *\n * Ordering matters and is deliberate: enum types before the tables and columns\n * that reference them, tables before the columns added to other tables (a new\n * table may be the target of a relation), and nothing is emitted twice.\n */\nexport function planCollectionSchemaEnsure(\n allCollections: CollectionConfig[],\n existing: ExistingSchema,\n options: EnsureOptions = {}\n): EnsurePlan {\n const constraintPolicy: ConstraintPolicy = options.constraints ?? \"additive\";\n const withheldConstraints: WithheldConstraint[] = [];\n const columnTypeDrift: ColumnTypeDrift[] = [];\n // Boot receives every collection the bundle declares, including the ones\n // served by another engine entirely. Creating a Postgres table for a\n // Firestore collection is not a harmless extra: the app keeps reading\n // documents from Firestore while an empty table with the same name accretes\n // policies and shows up in every drift report.\n // Before the filter, deliberately: a `search` block on a collection this\n // engine does not store would otherwise be dropped here without a word.\n assertSearchIsPostgresOnly(allCollections);\n\n const collections = relationalCollections(allCollections);\n const actions: EnsureAction[] = [];\n const plannedEnums = new Set<string>();\n\n // 1. Enum types. `CREATE TYPE` has no IF NOT EXISTS, so an existing type is\n // skipped by name rather than guarded in SQL.\n for (const collection of collections) {\n for (const { name, values } of requiredEnums(collection)) {\n if (existing.enums.has(name) || plannedEnums.has(name)) {\n // The type is there, but that says nothing about its *values*.\n // Skipping the whole type by name is what made an added enum\n // value vanish: nothing was planned, the boot reported success,\n // and the first row using the value was rejected by a type that\n // had never heard of it. `ADD VALUE` is the one alteration\n // Postgres offers here, it is purely additive, and it is\n // idempotent with `IF NOT EXISTS`.\n //\n // `enumValues` absent means the caller built the schema by hand\n // and does not know the values; skip by name as before rather\n // than plan against a guess.\n const current = existing.enumValues?.get(name);\n if (!current || plannedEnums.has(name)) continue;\n const [schema, typeName] = name.split(\".\");\n for (const value of values) {\n if (current.includes(value)) continue;\n actions.push({\n kind: \"add-enum-value\",\n target: `${name}.${value}`,\n // Not inside a transaction with any use of the value:\n // Postgres refuses to read a value added by the\n // transaction still adding it. The applier runs these\n // one statement at a time, which is what makes it legal.\n sql: `ALTER TYPE \"${schema}\".\"${typeName}\" ADD VALUE IF NOT EXISTS ${quoteSqlLiteral(value)};`\n });\n }\n continue;\n }\n plannedEnums.add(name);\n const [schema, typeName] = name.split(\".\");\n actions.push({\n kind: \"create-enum\",\n target: name,\n sql: `CREATE TYPE \"${schema}\".\"${typeName}\" AS ENUM (${values.map(quoteSqlLiteral).join(\", \")});`\n });\n }\n }\n\n // 1b. Search support, for collections that declared a `search` block.\n //\n // Before the tables, because a generated column's expression is\n // resolved when the column is created: a table whose search column\n // calls `rebase_search_text` cannot be added before that function\n // exists. Both forms are idempotent, so a boot against a database that\n // already has them plans nothing.\n const searchSpecs = collections\n .map(c => buildSearchColumnSpec(c))\n .filter((spec): spec is SearchColumnSpec => spec !== undefined);\n\n const plannedExtensions = new Set<string>();\n const planExtension = (statement: string): void => {\n if (plannedExtensions.has(statement)) return;\n plannedExtensions.add(statement);\n actions.push({ kind: \"create-extension\", target: statement.replace(/^CREATE EXTENSION IF NOT EXISTS |;$/g, \"\"), sql: statement });\n };\n for (const spec of searchSpecs) {\n for (const statement of searchExtensionStatements(spec)) planExtension(statement);\n }\n\n // pgvector, for collections that declared a `vector` property — and only\n // when a database gave leave to install it. Unlike the search extensions\n // above, which are contrib modules every Postgres carries, pgvector is a\n // separate build behind an image, a grant and a provider allow-list, so\n // whether Rebase may install it is not Rebase's to decide. See\n // `DatabaseOptions.extensions`.\n //\n // Boot has to make the same call `rebase db push` makes, or the two produce\n // different databases from one commit — which is the rule\n // `contracts/derived-names.txt` enforces.\n if (vectorExtensionDeclared(options.databaseExtensions)\n && collections.some(c => buildVectorColumnSpecs(c, resolveColumnName).length > 0)) {\n planExtension(vectorExtensionStatement());\n }\n const plannedFunctions = new Set<string>();\n for (const spec of searchSpecs) {\n for (const statement of searchHelperFunctions(spec)) {\n if (plannedFunctions.has(statement)) continue;\n plannedFunctions.add(statement);\n actions.push({ kind: \"create-function\", target: statement.includes(\"unaccent\") ? SEARCH_UNACCENT_FN : SEARCH_TEXT_FN, sql: statement });\n }\n }\n\n // 2. Missing tables. Only the identity column is created here; every other\n // column is added by step 3, so a new table and an existing table that\n // gained a field travel the exact same code path. One way to build a\n // column means one way for it to be wrong.\n const created = new Set<string>();\n for (const collection of collections) {\n const key = qualified(collection);\n if (existing.tables.has(key) || created.has(key)) continue;\n created.add(key);\n const schema = schemaOf(collection);\n const table = tableOf(collection);\n const idEntry = Object.entries(collection.properties ?? {}).find(([n, p]) =>\n isIdProperty(n, p as Property, collection)\n );\n const idName = idEntry ? resolveColumnName(idEntry[0], idEntry[1] as Property) : \"id\";\n const idProp = idEntry?.[1] as Property | undefined;\n // Derived through the generator's own type mapping, not re-decided here.\n // This branch used to emit BIGSERIAL for a numeric id while `db push`\n // emitted INTEGER GENERATED BY DEFAULT AS IDENTITY, so the same project\n // got an int8 key when the runtime brought the schema up and an int4 key\n // when a human pushed it. Two consequences, both real: node-postgres\n // hands back int8 as a *string*, so a collection declaring\n // `type: \"number\"` served `\"1\"` instead of `1` on the managed path only;\n // and every foreign key and junction column pointing at it stayed\n // INTEGER on both paths, which is a truncation waiting for the sequence\n // to pass 2^31. The agreement test now pins this.\n const idType = idProp\n ? getSqlColumnType(idEntry![0], idProp, collection, collections)\n : \"TEXT\";\n let idDef = `\"${assertSafeIdentifier(idName, \"column name\")}\" ${idType} PRIMARY KEY`;\n if (idProp?.type === \"string\" && (idProp as { isId?: unknown }).isId === \"uuid\") {\n idDef += \" DEFAULT gen_random_uuid()\";\n }\n actions.push({\n kind: \"create-table\",\n target: key,\n sql: `CREATE TABLE IF NOT EXISTS \"${schema}\".\"${table}\" (${idDef});`\n });\n }\n\n // 2b. Junction tables behind many-to-many relations. No collection declares\n // them, so the walk above never sees them — and until they existed, an\n // m2m write had nowhere to land and the junction's derived RLS had\n // nothing to attach to.\n const junctions = planJunctionTables(collections);\n for (const junction of junctions) {\n const key = `${junction.schema}.${junction.table}`;\n if (existing.tables.has(key) || created.has(key)) continue;\n created.add(key);\n actions.push({ kind: \"create-table\", target: key, sql: junction.createTable });\n }\n\n // 3. Missing columns, on both brand-new and pre-existing tables.\n const legacyForeignKeys: LegacyForeignKey[] = [];\n\n /**\n * Move a relation column that is only missing because it was renamed.\n *\n * Returns true when it handled the column, so the caller skips the ordinary\n * ADD. Adding here would be the wrong move and a quiet one: the data is in\n * the old column, `ADD COLUMN` creates the new one empty beside it, every\n * statement succeeds, and the relation reads the empty one. A rename is\n * metadata-only in Postgres, keeps the values, and carries the column's\n * indexes and constraints with it.\n *\n * Only ever reached when the new name is absent and the old name is\n * present, so there is nothing to overwrite and nothing to choose between.\n */\n const renameLegacyColumn = (\n key: string,\n schema: string,\n table: string,\n column: string,\n legacyName: string | undefined\n ): boolean => {\n const present = existing.tables.get(key);\n if (!legacyName || !present) return false;\n if (present.has(column) || !present.has(legacyName)) return false;\n\n legacyForeignKeys.push({ table: key, expected: column, legacy: legacyName });\n actions.push({\n kind: \"rename-column\",\n target: `${key}.${column}`,\n sql: `ALTER TABLE \"${schema}\".\"${table}\" RENAME COLUMN \"${legacyName}\" TO \"${column}\";`\n });\n return true;\n };\n\n const addColumn = (\n key: string,\n schema: string,\n table: string,\n column: string,\n definition: string\n ): void => {\n const present = existing.tables.get(key);\n if (present?.has(column)) return;\n // Same reason as {@link schemaOf}: a column name reaching here came\n // from a property declaration, which on the editor paths came over the\n // wire. `definition` is built by the generator from a closed set of\n // type mappings and is not caller text.\n assertSafeIdentifier(column, \"column name\");\n actions.push({\n kind: \"add-column\",\n target: `${key}.${column}`,\n sql: `ALTER TABLE \"${schema}\".\"${table}\" ADD COLUMN IF NOT EXISTS \"${column}\" ${definition};`\n });\n };\n\n for (const collection of collections) {\n const key = qualified(collection);\n const schema = schemaOf(collection);\n const table = tableOf(collection);\n // A table this run is creating has no rows yet, so the constraints\n // `db push` writes are free to apply. On a table that already exists\n // they are not: `SET NOT NULL` is checked against live rows and a UNIQUE\n // would fail on existing duplicates, and this module runs unattended\n // against customer data with nobody reading a diff. So the constraints\n // are emitted for the fresh case — which is the whole managed-runtime\n // path, and the one that diverged from `db push` — and withheld for the\n // adopted one. `rebase db push` remains how an existing table gets them.\n const fresh = created.has(key);\n const auth = isAuthCollection(collection);\n for (const [propName, prop] of Object.entries(collection.properties ?? {})) {\n const p = prop as Property;\n if (isIdProperty(propName, p, collection)) continue;\n // Relation and reference columns are planned from the shared\n // relational planner below, which derives the column name, type and\n // foreign key the same way `db push` does. Deriving them here as\n // plain columns is what once produced a column with no constraint.\n if (p.type === \"reference\" || p.type === \"relation\") continue;\n\n const column = resolveColumnName(propName, p);\n // On an auth collection, the columns auth itself reads and writes\n // have exactly one definition, wherever the table is created from —\n // see `auth-users-columns`. Anything else on that collection is an\n // ordinary user-declared field and is generated like any other.\n const authDefinition = auth ? authUsersColumnDefinition(column) : undefined;\n if (authDefinition) {\n addColumn(key, schema, table, column, authDefinition);\n continue;\n }\n\n // Assembled in the generator's order — type, UNIQUE, DEFAULT,\n // NOT NULL — so the two produce byte-identical column definitions\n // and the agreement test can compare them directly instead of\n // checking that a column merely exists, which is how BIGSERIAL-vs-\n // INTEGER and every missing constraint went unnoticed.\n const declaredType = getSqlColumnType(propName, p, collection, collections);\n let definition = declaredType;\n if (fresh && p.validation?.unique) definition += \" UNIQUE\";\n // Not gated on `fresh`: a default binds future writes only, so it is\n // safe on a live table, and a column added without it would take the\n // value the application forgot to send rather than `now()`.\n const autoValue = (p as { autoValue?: string }).autoValue;\n const hasDefault = p.type === \"date\" && (autoValue === \"on_create\" || autoValue === \"on_update\");\n if (hasDefault) definition += \" DEFAULT now()\";\n\n const required = p.validation?.required === true;\n const columnKey = `${key}.${column}`;\n const columnExists = existing.tables.get(key)?.has(column) === true;\n\n // The column is there and holds a different kind of value than the\n // collection says it does. Reported, never altered: an unattended\n // `ALTER COLUMN … TYPE` over customer data is not a thing to do\n // quietly, and the *quiet* is what this fixes. A deploy that changed\n // a `columnType` used to report success while the column stayed as\n // it was, and the divergence surfaced later as every write failing.\n const actualType = existing.columnTypes?.get(columnKey);\n if (columnExists && actualType && !typesAgree(declaredType, actualType)) {\n columnTypeDrift.push({\n table: key,\n column,\n declared: declaredType,\n actual: actualType\n });\n }\n\n // A NOT NULL is safe exactly when it cannot fail against rows that\n // are already there, and there are three ways to know that:\n //\n // - the table is being created by this plan (no rows yet);\n // - the table exists and is empty;\n // - the column arrives with a DEFAULT, which Postgres backfills\n // into every existing row as part of ADD COLUMN.\n //\n // Anything else is checked against live data and can abort the boot,\n // which is why it used to be withheld — correctly. What was wrong was\n // withholding it in *silence*: the config said required, the column\n // came out nullable, and nothing anywhere said so.\n // `populatedTables` absent means the caller does not know, and not\n // knowing has to read as \"assume rows\" — the other direction emits a\n // NOT NULL that is checked against live data and aborts the boot.\n // Written as an explicit `!== undefined` because the optional-chain\n // form (`!existing.populatedTables?.has(key)`) quietly says *empty*\n // when the fact is missing, which is the wrong way to be wrong.\n const tableIsEmpty = existing.populatedTables !== undefined\n && existing.tables.has(key)\n && !existing.populatedTables.has(key);\n const notNullIsSafe = fresh || tableIsEmpty || hasDefault;\n\n if (required && !columnExists) {\n if (notNullIsSafe) {\n definition += \" NOT NULL\";\n } else {\n withheldConstraints.push({\n target: columnKey,\n kind: \"not-null\",\n reason:\n `\"${column}\" is required, but \"${key}\" already holds rows and the column ` +\n \"has no default to backfill them with, so NOT NULL would be checked \" +\n \"against data that does not have a value yet.\",\n remedy:\n \"Backfill the column, then add the constraint — or give the property a \" +\n \"default so every existing row gets one.\"\n });\n }\n }\n addColumn(key, schema, table, column, definition);\n\n // The column is already there and only its constraint differs. Two\n // directions, and they are not equally safe — see `ConstraintPolicy`\n // for why neither runs at an unattended boot.\n if (columnExists && constraintPolicy === \"converge\") {\n const isNotNull = existing.notNullColumns?.has(columnKey) === true;\n if (required && !isNotNull) {\n if (tableIsEmpty) {\n actions.push({\n kind: \"set-not-null\",\n target: columnKey,\n sql: `ALTER TABLE \"${schema}\".\"${table}\" ALTER COLUMN \"${column}\" SET NOT NULL;`\n });\n } else {\n withheldConstraints.push({\n target: columnKey,\n kind: \"not-null\",\n reason:\n `\"${column}\" became required, but \"${key}\" holds rows and any of them ` +\n \"with no value would make SET NOT NULL fail.\",\n remedy:\n \"Backfill the column first — `UPDATE … SET \\\"\" + column +\n \"\\\" = … WHERE \\\"\" + column + \"\\\" IS NULL` — then apply this again.\"\n });\n }\n }\n if (!required && isNotNull) {\n // Loosening never fails and never loses data. It is here\n // rather than at boot because a database adopted by\n // introspection carries NOT NULL on columns the generated\n // collection deliberately leaves optional, and converging\n // those unasked would drop constraints nobody edited.\n actions.push({\n kind: \"drop-not-null\",\n target: columnKey,\n sql: `ALTER TABLE \"${schema}\".\"${table}\" ALTER COLUMN \"${column}\" DROP NOT NULL;`\n });\n }\n }\n }\n\n // The auth columns the collection never mentions. The scaffold's users\n // collection describes 12 of the 14 auth reads and writes, so planning\n // only from properties left `is_anonymous` and `tokens_valid_after` to\n // `ensureAuthTablesExist` — which does create them, but only because\n // that function happens to run later in the same boot. Planning them\n // here makes this path self-contained and identical to `db push`, so\n // neither depends on the other having run.\n if (auth) {\n const declared = new Set(\n Object.entries(collection.properties ?? {})\n .map(([name, prop]) => resolveColumnName(name, prop as Property))\n );\n for (const spec of AUTH_USERS_COLUMNS) {\n if (declared.has(spec.column)) continue;\n addColumn(key, schema, table, spec.column, authUsersColumnSql(spec));\n }\n }\n }\n\n // 3aa. The generated search columns.\n //\n // Adding a STORED generated column rewrites the table, which on a large\n // one is not free — but it is the same additive shape as every other\n // column here, and the alternative (leaving it out until someone runs a\n // migration) is a declared `search` block that silently does nothing.\n //\n // Changing one is not additive, and `ADD COLUMN IF NOT EXISTS` is a\n // no-op against a column that is already there — which is why a `search`\n // block that gained a field, flipped `unaccent` or moved a weight used\n // to be inert forever, on every path, with nothing logged. Each column\n // therefore carries a fingerprint of the expression it was built from\n // (in its comment), and a mismatch is reported rather than applied.\n const searchDrift: SearchColumnDrift[] = [];\n const searchAdopted: { table: string; column: string }[] = [];\n for (const spec of searchSpecs) {\n const key = `${spec.schema}.${spec.table}`;\n const definitions: Record<string, string> = {\n [spec.column]: `tsvector GENERATED ALWAYS AS (${spec.expression}) STORED`\n };\n if (spec.fuzzy) {\n definitions[spec.fuzzy.column] = `text GENERATED ALWAYS AS (${spec.fuzzy.expression}) STORED`;\n }\n\n for (const stamp of searchColumnStamps(spec)) {\n const definition = definitions[stamp.column];\n const exists = existing.tables.get(key)?.has(stamp.column) === true;\n const recorded = existing.columnComments?.get(`${key}.${stamp.column}`);\n\n if (exists && recorded?.startsWith(SEARCH_STAMP_PREFIX) && recorded !== stamp.fingerprint) {\n searchDrift.push({\n table: key,\n column: stamp.column,\n found: recorded,\n expected: stamp.fingerprint,\n rebuild: [\n `ALTER TABLE \"${spec.schema}\".\"${spec.table}\" DROP COLUMN \"${stamp.column}\";`,\n `ALTER TABLE \"${spec.schema}\".\"${spec.table}\" ADD COLUMN \"${stamp.column}\" ${definition};`,\n stamp.sql\n ]\n });\n // The old stamp is the only evidence of what the column holds;\n // overwriting it here would erase the drift instead of fixing it.\n continue;\n }\n\n addColumn(key, spec.schema, spec.table, stamp.column, definition);\n if (exists && recorded === undefined) {\n searchAdopted.push({ table: key, column: stamp.column });\n }\n if (recorded !== stamp.fingerprint) {\n actions.push({\n kind: \"comment-column\",\n target: `${key}.${stamp.column}`,\n sql: stamp.sql\n });\n }\n }\n }\n\n // 3b. A junction that already existed, but is short a column. One created\n // above already carries both — unlike a collection table, whose CREATE\n // declares only the identity column — so re-listing them would log two\n // no-op statements and inflate the count of changes applied.\n for (const junction of junctions) {\n const key = `${junction.schema}.${junction.table}`;\n if (created.has(key)) continue;\n for (const column of junction.columns) {\n if (renameLegacyColumn(key, junction.schema, junction.table, column.name, column.legacyName)) continue;\n addColumn(key, junction.schema, junction.table, column.name, column.type);\n }\n }\n\n // 3c. The columns relation and reference properties own.\n //\n // Recorded per table as well as planned: these columns are derived here\n // rather than declared in `properties`, so the orphan check below would\n // otherwise read every foreign key as a column nobody declared.\n const plannedRelationColumns = new Map<string, Set<string>>();\n for (const relational of planRelationalColumns(collections)) {\n const relKey = `${relational.schema}.${relational.table}`;\n if (!plannedRelationColumns.has(relKey)) plannedRelationColumns.set(relKey, new Set());\n plannedRelationColumns.get(relKey)!.add(relational.column);\n if (relational.legacyColumn) plannedRelationColumns.get(relKey)!.add(relational.legacyColumn);\n if (renameLegacyColumn(relKey, relational.schema, relational.table, relational.column, relational.legacyColumn)) continue;\n addColumn(\n relKey,\n relational.schema,\n relational.table,\n relational.column,\n relational.type\n );\n }\n\n // 4. Foreign keys, last: the tables and columns on both ends have to exist\n // first, and a constraint is the one thing here that can fail on data\n // rather than on schema, so nothing else depends on it.\n const knownConstraints = existing.constraints ?? new Set<string>();\n const plannedConstraints = new Set<string>();\n const foreignKeys = [\n ...planRelationalColumns(collections).map(r => r.foreignKey),\n ...junctions.flatMap(j => j.foreignKeys)\n ];\n for (const fk of foreignKeys) {\n if (!fk) continue;\n const name = `${fk.schema}.${fk.table}.${fk.constraintName}`;\n if (knownConstraints.has(name) || plannedConstraints.has(name)) continue;\n plannedConstraints.add(name);\n actions.push({\n kind: \"add-constraint\",\n target: `${fk.schema}.${fk.table}.${fk.constraintName}`,\n sql: fk.sql\n });\n }\n\n // 5. Search indexes, after everything — the column has to exist, and this is\n // the one step that runs against a populated table for real work.\n //\n // CONCURRENTLY: a plain CREATE INDEX takes a lock that blocks writes for\n // the duration of the build, which on a live table is an outage. Each\n // statement here is issued on its own, outside any transaction, which is\n // the condition CONCURRENTLY requires.\n //\n // ...and only on a live table. `concurrentIndexing` withholds it for a\n // table this same plan is creating: there are no rows to build from and\n // no writer to block, so the lock CONCURRENTLY avoids is a lock nobody\n // would have taken. Worth withholding rather than merely harmless,\n // because CONCURRENTLY is the one index form Postgres refuses inside a\n // transaction block — so on a first boot it buys nothing and adds the\n // single failure mode this whole statement family has.\n const concurrentIndexing = (schema: string, table: string): boolean =>\n !created.has(`${schema}.${table}`);\n for (const spec of searchSpecs) {\n for (const statement of searchIndexStatements(spec)) {\n actions.push({\n kind: \"create-index\",\n target: `${spec.schema}.${spec.table}`,\n sql: concurrentIndexing(spec.schema, spec.table)\n ? statement.replace(\"CREATE INDEX IF NOT EXISTS\", \"CREATE INDEX CONCURRENTLY IF NOT EXISTS\")\n : statement\n });\n }\n }\n\n // ANN indexes for vector columns, on the same terms: the column has to\n // exist, the build is real work against real rows, and CONCURRENTLY is\n // what keeps that from locking writes for its duration.\n //\n // A column too wide for pgvector to index is reported, not planned —\n // silence there would read as \"indexed\" to anyone watching the boot.\n const vectorIndexSkipped: SkippedVectorIndex[] = [];\n for (const collection of collections) {\n const plan = buildVectorIndexPlan(collection, resolveColumnName);\n for (const spec of plan.specs) {\n actions.push({\n kind: \"create-index\",\n target: `${spec.schema}.${spec.table}`,\n sql: concurrentIndexing(spec.schema, spec.table)\n ? vectorIndexStatement(spec).replace(\"CREATE INDEX IF NOT EXISTS\", \"CREATE INDEX CONCURRENTLY IF NOT EXISTS\")\n : vectorIndexStatement(spec)\n });\n }\n vectorIndexSkipped.push(...plan.skipped);\n }\n\n // Declared indexes, on exactly the same terms as the ANN ones above.\n //\n // Boot has to emit these, not just `db push`: the managed runtime\n // provisions at boot and never runs a push, so a push-only index would\n // simply not exist there — and nothing would say so.\n // `contracts/derived-names.txt` states the rule (\"Both, or it is a\n // bug\") and the gate enforces it, which is what caught this.\n //\n // `concurrently` is a parameter here rather than a string replacement\n // on the rendered SQL. The `.replace(\"CREATE INDEX IF NOT EXISTS\", …)`\n // just above silently does nothing for a UNIQUE index, whose text is\n // `CREATE UNIQUE INDEX …` and never matches the pattern.\n for (const spec of buildCollectionIndexPlan(collections, resolveColumnName)) {\n actions.push({\n kind: \"create-index\",\n target: `${spec.schema}.${spec.table}`,\n sql: collectionIndexStatement(spec, {\n concurrently: concurrentIndexing(spec.schema, spec.table),\n ifNotExists: true\n })\n });\n }\n\n // A column the database still requires that no property declares any more.\n //\n // Computed last, over the same live snapshot the rest of the plan read, and\n // only for tables this run did not create — a table created here has exactly\n // the columns the collection asked for, so there is nothing to orphan.\n //\n // Every element of the test matters. NOT NULL, because a nullable leftover\n // accepts a write and is merely untidy. No DEFAULT, because a leftover with\n // one is filled in for you. Not declared, because that is what makes it\n // unreachable — the author has no field to send. The id column is excluded\n // for the same reason it is skipped everywhere else here: it is generated.\n const orphanedRequiredColumns: OrphanedRequiredColumn[] = [];\n if (existing.notNullColumns && existing.columnDefaults) {\n for (const collection of collections) {\n const key = qualified(collection);\n const live = existing.tables.get(key);\n if (!live || created.has(key)) continue;\n\n const declared = new Set<string>();\n for (const [propName, prop] of Object.entries(collection.properties ?? {})) {\n declared.add(resolveColumnName(propName, prop as Property));\n }\n // Relation and reference columns are planned by the relational\n // planner, not from `properties`, so a foreign key would otherwise\n // read as undeclared. Anything that planner named for this table is\n // declared by definition.\n for (const column of plannedRelationColumns.get(key) ?? []) declared.add(column);\n\n for (const column of live) {\n const columnKey = `${key}.${column}`;\n if (declared.has(column)) continue;\n if (!existing.notNullColumns.has(columnKey)) continue;\n if (existing.columnDefaults.has(columnKey)) continue;\n orphanedRequiredColumns.push({ table: key, column, slug: collection.slug });\n }\n }\n }\n\n return {\n actions,\n statements: actions.map(a => a.sql),\n legacyForeignKeys,\n searchDrift,\n searchAdopted,\n vectorIndexSkipped,\n withheldConstraints,\n columnTypeDrift,\n orphanedRequiredColumns\n };\n}\n\n/** Read what the database has, for the schemas the collections live in. */\nexport async function readExistingSchema(\n client: Queryable,\n schemas: string[]\n): Promise<ExistingSchema> {\n const tables = new Map<string, Set<string>>();\n const enums = new Set<string>();\n if (schemas.length === 0) return { tables, enums };\n\n const inList = schemas\n .map(schema => `'${assertSafeIdentifier(schema, \"schema name\")}'`)\n .join(\", \");\n\n const notNullColumns = new Set<string>();\n // `udt_name` rather than `data_type`: `data_type` collapses every array to\n // the literal string `ARRAY` and every extension type to `USER-DEFINED`,\n // which cannot be compared with anything. `udt_name` names the actual type\n // — `int4`, `jsonb`, `_numeric`, `vector` — which is what the drift check\n // needs.\n const columnTypes = new Map<string, string>();\n // A NOT NULL column that has a DEFAULT is not a problem for a write that\n // omits it, so orphan detection needs the default as well as the nullability\n // — otherwise every `created_at DEFAULT now()` on a table whose collection\n // does not declare it would be reported as a blocker.\n const columnDefaults = new Set<string>();\n const { rows: columns } = await client.query<{\n table_schema: string;\n table_name: string;\n column_name: string;\n is_nullable: string;\n udt_name: string | null;\n column_default: string | null;\n }>(\n `SELECT table_schema, table_name, column_name, is_nullable, udt_name, column_default\n FROM information_schema.columns\n WHERE table_schema IN (${inList})`\n );\n for (const row of columns) {\n const key = `${row.table_schema}.${row.table_name}`;\n if (!tables.has(key)) tables.set(key, new Set());\n tables.get(key)!.add(row.column_name);\n if (row.is_nullable === \"NO\") notNullColumns.add(`${key}.${row.column_name}`);\n if (row.udt_name) columnTypes.set(`${key}.${row.column_name}`, row.udt_name);\n if (row.column_default !== null) columnDefaults.add(`${key}.${row.column_name}`);\n }\n\n // Which tables hold rows. This is the only fact that decides whether a\n // NOT NULL can be added without reading the data, so it is worth a query.\n //\n // `reltuples` would be cheaper and is wrong for this: it is a planner\n // estimate, it is -1 on a table that has never been analyzed, and a table\n // that was full an hour ago still reads as full after a DELETE. A wrong\n // \"empty\" here means a boot that aborts on a constraint violation, so the\n // estimate is not good enough. `EXISTS … LIMIT 1` stops at the first row,\n // which makes the true cost one page read per table.\n //\n // Restricted to ordinary and partitioned tables: `information_schema.columns`\n // also lists views and materialized views, and probing those runs whatever\n // query defines them.\n const populatedTables = new Set<string>();\n const { rows: realTables } = await client.query<{ schema: string; name: string }>(\n `SELECT n.nspname AS schema, c.relname AS name\n FROM pg_class c\n JOIN pg_namespace n ON c.relnamespace = n.oid\n WHERE c.relkind IN ('r', 'p') AND n.nspname IN (${inList})`\n );\n if (realTables.length > 0) {\n const probes = realTables.map(row => {\n const schema = assertSafeIdentifier(row.schema, \"schema name\");\n const table = assertSafeIdentifier(row.name, \"table name\");\n return `SELECT ${quoteSqlLiteral(`${schema}.${table}`)} AS key, ` +\n `EXISTS(SELECT 1 FROM \"${schema}\".\"${table}\" LIMIT 1) AS populated`;\n });\n const { rows: populationRows } = await client.query<{ key: string; populated: boolean }>(\n probes.join(\" UNION ALL \")\n );\n for (const row of populationRows) {\n if (row.populated) populatedTables.add(row.key);\n }\n }\n\n const enumValues = new Map<string, string[]>();\n const { rows: enumValueRows } = await client.query<{\n schema: string;\n name: string;\n value: string;\n }>(\n // Ordered by `enumsortorder`, not by label: an enum's order is part of\n // its meaning (it is what `<` compares), and reading it back sorted\n // alphabetically would make a correct type look drifted.\n `SELECT n.nspname AS schema, t.typname AS name, e.enumlabel AS value\n FROM pg_enum e\n JOIN pg_type t ON e.enumtypid = t.oid\n JOIN pg_namespace n ON t.typnamespace = n.oid\n WHERE n.nspname IN (${inList})\n ORDER BY t.typname, e.enumsortorder`\n );\n for (const row of enumValueRows) {\n const key = `${row.schema}.${row.name}`;\n if (!enumValues.has(key)) enumValues.set(key, []);\n enumValues.get(key)!.push(row.value);\n }\n\n const { rows: enumRows } = await client.query<{ schema: string; name: string }>(\n `SELECT n.nspname AS schema, t.typname AS name\n FROM pg_type t\n JOIN pg_namespace n ON t.typnamespace = n.oid\n WHERE t.typtype = 'e' AND n.nspname IN (${inList})`\n );\n for (const row of enumRows) enums.add(`${row.schema}.${row.name}`);\n\n // `ADD CONSTRAINT` has no IF NOT EXISTS, so an existing foreign key is\n // skipped by name rather than guarded in SQL.\n const constraints = new Set<string>();\n const { rows: constraintRows } = await client.query<{\n schema: string;\n table: string;\n name: string;\n }>(\n `SELECT n.nspname AS schema, c.relname AS table, con.conname AS name\n FROM pg_constraint con\n JOIN pg_class c ON con.conrelid = c.oid\n JOIN pg_namespace n ON c.relnamespace = n.oid\n WHERE n.nspname IN (${inList})`\n );\n for (const row of constraintRows) constraints.add(`${row.schema}.${row.table}.${row.name}`);\n\n // Column comments, which is where a generated search column records the\n // expression it was built from. `objsubid > 0` is what makes a row a\n // *column* comment rather than the table's own.\n const columnComments = new Map<string, string>();\n const { rows: commentRows } = await client.query<{\n schema: string;\n table: string;\n column: string;\n comment: string | null;\n }>(\n `SELECT n.nspname AS schema, c.relname AS table, a.attname AS column, d.description AS comment\n FROM pg_description d\n JOIN pg_class c ON d.objoid = c.oid\n JOIN pg_namespace n ON c.relnamespace = n.oid\n JOIN pg_attribute a ON a.attrelid = c.oid AND a.attnum = d.objsubid\n WHERE d.objsubid > 0 AND n.nspname IN (${inList})`\n );\n for (const row of commentRows) {\n if (row.comment == null) continue;\n columnComments.set(`${row.schema}.${row.table}.${row.column}`, row.comment);\n }\n\n return {\n tables, enums, constraints, columnComments, enumValues, notNullColumns, populatedTables, columnTypes,\n columnDefaults\n };\n}\n\n/**\n * What to tell an operator whose `search` block no longer matches its column.\n *\n * Every line here is doing work: naming the collection is not enough, because\n * the symptom (a search that finds nothing) points at the data, not the schema;\n * and the remediation has to be exact, because it is a table rewrite the\n * operator is being asked to schedule rather than discover.\n */\nfunction searchDriftMessage(drift: SearchColumnDrift[]): string {\n const blocks = drift.map(d =>\n ` \"${d.table}\".\"${d.column}\" was generated from a different \\`search\\` block ` +\n `(recorded ${d.found}, current ${d.expected}).\\n` +\n d.rebuild.map(s => ` ${s}`).join(\"\\n\")\n );\n return (\n \"The `search` block changed after its generated column was created, and Postgres cannot alter a \" +\n \"generated expression in place.\\n\" +\n \"Rebase will not rebuild it for you: dropping and re-adding a STORED generated column rewrites the whole \" +\n \"table under an ACCESS EXCLUSIVE lock and rebuilds its GIN index, which is an outage this unattended path \" +\n \"may not schedule on your behalf.\\n\" +\n \"Until it is rebuilt the column keeps indexing the previous fields, weights and language — searches for \" +\n \"anything added since return nothing, which reads from outside as \\\"no such row\\\".\\n\" +\n \"Run these (or revert the block to what the column was built from), then boot again:\\n\" +\n blocks.join(\"\\n\") +\n \"\\n The GIN index is dropped with the column and recreated concurrently on the next boot.\"\n );\n}\n\n\n/**\n * Read what the database looks like, for the schemas a set of collections\n * lives in.\n *\n * The same read `ensureCollectionTables` does at boot, exposed on its own for\n * the callers that want to *plan* against a real database without changing it —\n * the live schema editor, which has to tell somebody what a change would do\n * before they agree to it.\n */\nexport async function readSchemaFactsFor(\n client: Queryable,\n collections: CollectionConfig[]\n): Promise<ExistingSchema> {\n const relational = relationalCollections(collections);\n const schemas = Array.from(new Set([\n ...relational.map(schemaOf),\n ...planJunctionTables(relational).map(junction => junction.schema)\n ]));\n return readExistingSchema(client, schemas);\n}\n\n/**\n * Bring the database up to date. Returns what it did.\n *\n * Each statement runs on its own rather than in one transaction: they are all\n * independently safe and idempotent, and a single failure (an enum label that\n * cannot be added, say) should not roll back the tables that were created fine.\n * The error is surfaced with the statement that caused it.\n */\nexport async function ensureCollectionTables(\n client: Queryable,\n collections: CollectionConfig[],\n log?: (message: string) => void,\n options: EnsureOptions = {}\n): Promise<EnsureOutcome> {\n // Junctions live alongside the collections that declare them, so their\n // schema has to be read too — otherwise an existing junction reads as\n // missing and its constraints as unplanned.\n const schemas = Array.from(new Set([\n ...collections.map(schemaOf),\n ...planJunctionTables(collections).map(j => j.schema)\n ]));\n for (const schema of schemas) {\n assertSafeIdentifier(schema, \"schema name\");\n if (schema !== \"public\") {\n await client.query(`CREATE SCHEMA IF NOT EXISTS \"${schema}\";`);\n }\n }\n\n const existing = await readExistingSchema(client, schemas);\n // This is the boundary, so this is where the world is read: the planner\n // itself takes the permission as an argument and never reaches for the\n // registry. By the time boot gets here `loadBundleResourceGraph` has\n // evaluated the project's `resources.ts`, so the declarations are there —\n // and a caller that already knows them can still say so.\n const plan = planCollectionSchemaEnsure(collections, existing, {\n ...options,\n databaseExtensions: options.databaseExtensions ?? declaredDatabaseExtensions()\n });\n const failures: EnsureOutcome[\"failures\"] = [];\n\n // Reported, not warned: this is a rename the ensure is about to perform, and\n // the operator should be able to see in the log why a column changed name.\n // The interesting case is the one that no longer happens — before this,\n // ensure added the new column empty beside the populated old one, every\n // statement succeeded, and the relation read the empty one.\n for (const legacy of plan.legacyForeignKeys) {\n const message =\n `Renaming \"${legacy.table}\".\"${legacy.legacy}\" to \"${legacy.expected}\". The old name is ` +\n \"the one Rebase derived for this relation before it singularized properly; the column \" +\n \"keeps its data, indexes and constraints. To keep the old name instead, set \" +\n `\\`localKey: \"${legacy.legacy}\"\\` on the relation and this will stop.`;\n logger.info(`[schema] ${message}`);\n log?.(message);\n }\n\n // Before anything is applied: a `search` block that changed after its column\n // was generated cannot be honoured by an additive plan, and serving the old\n // index while the config describes a new one is the silent failure this\n // check exists to end. Refusing is the loud half — boot is fatal on purpose\n // (see `ensureCollectionSchema` in the server's boot) and the message\n // carries the exact statements that resolve it.\n if (plan.searchDrift.length > 0) {\n throw new Error(searchDriftMessage(plan.searchDrift));\n }\n\n // Said once per column, at the moment the stamp is applied: from here on a\n // change is detected, but whether *this* column matches the block it is\n // being stamped with is not knowable — it predates the stamp.\n for (const adopted of plan.searchAdopted) {\n const message =\n `Adopting the existing generated column \"${adopted.table}\".\"${adopted.column}\" and recording what the ` +\n \"current `search` block would generate. Any later change to that block will be detected and refused; a \" +\n \"change made *before* this version was deployed cannot be, so if search has been missing content, \" +\n `rebuild the column once: ALTER TABLE \"${adopted.table.split(\".\").join('\".\"')}\" DROP COLUMN \"${adopted.column}\"; and boot again.`;\n logger.info(`[schema] ${message}`);\n log?.(message);\n }\n\n // Said once per column, every boot: an unindexed vector column and an\n // indexed one behave identically apart from latency, so the only way anyone\n // learns which one they have is if the boot says so.\n for (const skip of plan.vectorIndexSkipped) {\n const message = `No ANN index on \"${skip.table}\".\"${skip.column}\": ${skip.reason}`;\n logger.warn(`[schema] ${message}`);\n log?.(message);\n }\n\n // Said once per column, every boot, because the alternative is what this\n // whole feature exists to end: a column the configuration calls required,\n // sitting there nullable, with every surface reporting success. The boot\n // does not fail over it — the column is usable and the data is intact — but\n // it stops being invisible.\n for (const withheld of plan.withheldConstraints) {\n const message =\n `No NOT NULL on \"${withheld.target}\": ${withheld.reason} ${withheld.remedy}`;\n logger.warn(`[schema] ${message}`);\n log?.(message);\n }\n\n // Said once per drifted column, every boot, and this is the one report here\n // that describes a system which currently *looks* healthy. Everything else\n // in this function explains a statement that did not run; this explains a\n // statement that was never planned, on a deploy that reported success, and\n // whose first symptom is a write rejected long afterwards.\n for (const drift of plan.columnTypeDrift) {\n const message = columnTypeDriftMessage(drift);\n logger.warn(`[schema] ${message}`);\n log?.(message);\n }\n\n // Said once per column, every boot, and it matters more than the rest here\n // because the table is not merely imperfect — it is unwritable, and the\n // error the API returns names a field the author has already deleted.\n // This is what a renamed property looks like to an additive provisioner.\n for (const orphan of plan.orphanedRequiredColumns) {\n const [schema, table] = orphan.table.split(\".\");\n const message =\n `\"${orphan.table}\".\"${orphan.column}\" is NOT NULL with no default, and the \"${orphan.slug}\" ` +\n \"collection no longer declares it. Every insert will be rejected for a field you cannot set \" +\n `(PG_23502, naming \"${orphan.column}\"). This is what a renamed property looks like: the new ` +\n \"column was added, and the old one cannot be dropped unattended. Fix it with ONE of:\\n\" +\n ` ALTER TABLE \"${schema}\".\"${table}\" DROP COLUMN \"${orphan.column}\"; -- the rename is complete\\n` +\n ` ALTER TABLE \"${schema}\".\"${table}\" ALTER COLUMN \"${orphan.column}\" DROP NOT NULL; -- keep the data, stop requiring it\\n` +\n \" ...or declare the property again, if the column is still wanted.\";\n logger.warn(`[schema] ${message}`);\n log?.(message);\n }\n\n if (plan.actions.length === 0) {\n log?.(\"Schema is up to date; nothing to create.\");\n return { ...plan, failures };\n }\n\n for (const action of plan.actions) {\n try {\n if (await applyAction(client, action)) {\n log?.(`${action.kind}: ${action.target}`);\n } else {\n log?.(`${action.kind}: ${action.target} (already created by a peer)`);\n }\n } catch (err) {\n const message = describeDriverError(err);\n // A foreign key is the only action that can fail on the customer's\n // data rather than on the schema. The column it polices is already\n // there, so the collection serves either way — record it and carry\n // on rather than crash-looping the deployment.\n //\n // A comment is metadata about a column that was just created\n // successfully, and it can only fail on ownership (COMMENT requires\n // owning the table, which an adopted table may not grant). Losing\n // the stamp costs drift detection on the next boot; it must not cost\n // the deployment.\n //\n // An index that is not UNIQUE is an optimization, and losing an\n // optimization must not cost the deployment. This was the difference\n // between a slow collection and a tenant that would not boot: on a\n // managed runtime the throw killed the process, the pod never went\n // ready, and the deploy reported a 300-second readiness timeout — for\n // an ANN index whose absence changes latency and nothing else.\n //\n // UNIQUE is excluded because it is not an optimization. It is the\n // collection declaring that two rows cannot share a value, and\n // serving without it is serving a promise the database is not\n // keeping.\n const survivable =\n action.kind === \"add-constraint\" ||\n action.kind === \"comment-column\" ||\n (action.kind === \"create-index\" && !/CREATE\\s+UNIQUE\\s+INDEX/i.test(action.sql));\n if (survivable) {\n failures.push({ kind: action.kind, target: action.target, error: message });\n continue;\n }\n throw new Error(\n `Failed to ${action.kind} ${action.target}: ${message}${vectorExtensionHint(message)}\\n ${action.sql}`\n );\n }\n }\n return { ...plan, failures };\n}\n\n/**\n * What actually went wrong, rather than what the ORM said about it.\n *\n * Drizzle wraps every driver error, and its `message` is literally\n * ``Failed query: ${sql}\\nparams: ${params}`` — the statement we already know,\n * and not one word of Postgres's answer. Reporting `err.message` therefore\n * produced a failure that named the statement twice and the *reason* zero\n * times, so four unrelated boot failures — a missing extension, a wrong\n * privilege, a bad index method, a constraint the rows violate — all read\n * identically to whoever had to fix them.\n *\n * Two consequences, both observed in the field before this existed:\n *\n * 1. On a managed deployment the reason never reaches anybody. Boot dies, the\n * pod never goes ready, and the deploy reports a readiness timeout — so the\n * one line that says why is the one line that was thrown away here.\n * 2. {@link vectorExtensionHint} matches on the error text. Against the\n * wrapper it matched nothing, so the hint written specifically to explain a\n * missing pgvector never fired on the path that raises it.\n *\n * `detail` and `hint` come along because they are the fields Postgres uses to\n * say which object it meant and what to do about it — `CREATE EXTENSION` names\n * the missing library there, and a rejected constraint names the row. This\n * writes into the tenant's own boot log, about the tenant's own database.\n */\nexport function describeDriverError(err: unknown): string {\n const pg = extractPgError(err);\n if (pg) {\n const parts = [pg.code ? `${pg.message} [${pg.code}]` : pg.message];\n if (pg.detail) parts.push(` detail: ${pg.detail}`);\n if (pg.hint) parts.push(` hint: ${pg.hint}`);\n return parts.join(\"\\n\");\n }\n // No SQLSTATE anywhere in the chain: not a database answer at all — a\n // socket that closed, a bug in this file. The deepest cause still beats the\n // wrapper, and the wrapper still beats nothing.\n const cause = extractCauseMessage(err);\n if (cause) return cause;\n return err instanceof Error ? err.message : String(err);\n}\n\n/** ` CONCURRENTLY ` as it appears in a rendered `CREATE [UNIQUE] INDEX`. */\nconst CONCURRENTLY_RE = /\\sCONCURRENTLY\\s/i;\n\n/**\n * `25001 active_sql_transaction` — \"cannot run inside a transaction block\".\n *\n * Only ever raised here by `CREATE INDEX CONCURRENTLY`, and only when the\n * connection we were handed is inside a transaction. Deliberately narrow: it\n * decides to retry a *different* statement, so it must not fire on anything it\n * has not been reasoned about.\n */\nfunction isTransactionBlockRefusal(err: unknown): boolean {\n return extractPgError(err)?.code === \"25001\";\n}\n\n/** Attempts per action, including the first. Matches the server's bootstraps. */\nconst DDL_ATTEMPTS = 4;\n\n/**\n * Run one planned statement, surviving a simultaneous boot.\n *\n * Every statement in a plan is written to be idempotent, and that is not the\n * same as being safe to run concurrently: `CREATE … IF NOT EXISTS` reads the\n * catalog and then writes to it as two steps, so peers starting together both\n * see \"absent\" and the loser gets a duplicate key on a *catalog* index. Measured\n * against Postgres 18: five instances, 8 of 10 calls lost. `CREATE TYPE` is\n * worse, because Postgres has no `IF NOT EXISTS` for it at all.\n *\n * What made that fatal here rather than merely noisy is the loop this sits in.\n * A losing statement threw, and the throw abandoned **every remaining action in\n * the plan** — so a replica that lost one race came up missing tables it never\n * attempted, and the boot log blamed the one statement that failed.\n *\n * @returns `true` if this process applied the statement, `false` if a peer had\n * already created the object. The distinction is only for the log; both mean\n * the object is now there.\n * @throws the original error for anything that is not a race — a syntax error, a\n * permission failure, a unique constraint the customer's own rows violate.\n */\nasync function applyAction(\n client: Queryable,\n action: EnsureAction\n): Promise<boolean> {\n let sqlText = action.sql;\n for (let attempt = 1; ; attempt++) {\n try {\n await client.query(sqlText);\n return true;\n } catch (err) {\n // `CREATE INDEX CONCURRENTLY` is the one statement here Postgres\n // refuses inside a transaction block (25001), and whether we are in\n // one is not ours to know: the client is whatever handle the caller\n // bootstrapped with, and an application that builds its own adapter\n // can hand us a connection already inside a transaction. The plain\n // form is always correct and never refused — it takes a write lock\n // for the build, which is the cost of getting the index at all.\n if (isTransactionBlockRefusal(err) && CONCURRENTLY_RE.test(sqlText)) {\n sqlText = sqlText.replace(CONCURRENTLY_RE, \" \");\n logger.warn(\n `[schema] ${action.kind} ${action.target}: this connection is inside a transaction, ` +\n \"so the index is being built with a write lock rather than concurrently. Writes to \" +\n \"this table block until it finishes.\"\n );\n continue;\n }\n // Already there. Not \"retry\" — the end state this statement wanted\n // is the end state the database is in, so carry on to the next\n // action rather than spending three more attempts proving it.\n if (isDuplicateObjectRace(err)) {\n logger.debug(\n `[schema] ${action.kind} ${action.target}: already created by another instance`\n );\n return false;\n }\n // Retryable but not yet satisfied — a deadlock between two boots\n // taking catalog locks in step. The statement did nothing; run it\n // again after a jittered pause so peers that collided once do not\n // collide again in lockstep.\n if (isConcurrentDdlRace(err) && attempt < DDL_ATTEMPTS) {\n logger.debug(\n `[schema] ${action.kind} ${action.target}: lost a race with another instance ` +\n `(attempt ${attempt}/${DDL_ATTEMPTS}) — retrying`\n );\n await new Promise(resolve => setTimeout(resolve, 40 * attempt * (1 + Math.random())));\n continue;\n }\n throw err;\n }\n }\n}\n"],"mappings":"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"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"generate-drizzle-schema-CwEgVyjO.js","names":[],"sources":["../src/schema/generate-next-step.ts","../src/schema/generate-drizzle-schema.ts"],"sourcesContent":["/**\n * The line `rebase schema generate` ends on.\n *\n * Its own module because `generate-drizzle-schema.ts` uses `import.meta` to\n * decide whether it was executed directly, which the driver's jest suite cannot\n * load at all — so the one sentence in that file with a decision in it had no\n * test and could not have one where it stood.\n */\n\nexport const formatTerminalText = (text: string, options: {\n bold?: boolean;\n backgroundColor?: \"blue\" | \"green\" | \"red\" | \"yellow\" | \"cyan\" | \"magenta\";\n textColor?: \"white\" | \"black\" | \"red\" | \"green\" | \"yellow\" | \"blue\" | \"magenta\" | \"cyan\";\n} = {}): string => {\n let codes = \"\";\n if (options.bold) codes += \"\\x1b[1m\";\n if (options.backgroundColor) {\n const bgColors = {\n blue: \"\\x1b[44m\",\n green: \"\\x1b[42m\",\n red: \"\\x1b[41m\",\n yellow: \"\\x1b[43m\",\n cyan: \"\\x1b[46m\",\n magenta: \"\\x1b[45m\"\n } as const;\n codes += bgColors[options.backgroundColor];\n }\n if (options.textColor) {\n const textColors = {\n white: \"\\x1b[37m\",\n black: \"\\x1b[30m\",\n red: \"\\x1b[31m\",\n green: \"\\x1b[32m\",\n yellow: \"\\x1b[33m\",\n blue: \"\\x1b[34m\",\n magenta: \"\\x1b[35m\",\n cyan: \"\\x1b[36m\"\n } as const;\n codes += textColors[options.textColor];\n }\n return `${codes}${text}\\x1b[0m`;\n};\n\n/**\n * What to do with the file that was just generated.\n *\n * Not the same answer on the managed development database: `rebase db generate`\n * plans with Atlas, Atlas needs a second empty database to diff against, and\n * PGlite serves exactly one — so the CLI refuses that command outright there.\n * This line recommended it unconditionally, which meant every stock scaffold\n * was told, by the tool itself, to run something the same tool would refuse.\n *\n * The kind arrives as `REBASE_DEV_DATABASE_KIND`, set by the CLI from the\n * ordered resolution in `dev-db/resolve.ts`. It is a pure decision — nothing is\n * started to make it — and the driver cannot make it for itself: on the managed\n * path `DATABASE_URL` is a perfectly ordinary connection string to a Postgres\n * on loopback. Absent (the driver run directly, outside the CLI) keeps the\n * original wording.\n */\nexport const nextStepAfterGenerate = (): string => {\n if (process.env.REBASE_DEV_DATABASE_KIND === \"managed\") {\n return `Start or restart ${formatTerminalText(\"rebase dev\", {\n bold: true,\n backgroundColor: \"blue\",\n textColor: \"black\"\n })} — boot applies your collections to the managed development database.`;\n }\n\n return `You can now run ${formatTerminalText(\"rebase db generate\", {\n bold: true,\n backgroundColor: \"blue\",\n textColor: \"black\"\n })} to generate the SQL migration files.`;\n};\n","import { promises as fsPromises } from \"fs\";\nimport * as fs from \"fs\";\nimport path from \"path\";\nimport { pathToFileURL } from \"url\";\nimport { generateSchema } from \"./generate-drizzle-schema-logic\";\nimport { CollectionConfig } from \"@rebasepro/types\";\nimport { loadCollectionsFromDirectory } from \"@rebasepro/server\";\nimport { out, outError } from \"../cli-output\";\nimport { nextStepAfterGenerate } from \"./generate-next-step\";\n\n\n// --- Execution and Watch Logic ---\n\nconst runGeneration = async (collectionsFilePath?: string, outputPath?: string) => {\n try {\n if (!collectionsFilePath) {\n throw new Error(\"No collections file path provided.\");\n }\n\n const resolvedPath = path.resolve(collectionsFilePath);\n\n // Shared with the runtime and the doctor: what gets generated here must\n // be exactly what the server serves, including directory-level defaults.\n let collections: CollectionConfig[] = await loadCollectionsFromDirectory(resolvedPath);\n\n\n // If collections directory is empty but exists, or failed to find any, we still want to inject defaults\n if (!collections || !Array.isArray(collections)) {\n collections = [];\n }\n\n\n // Sort collections by slug alphabetically to ensure deterministic schema generation\n collections.sort((a, b) => a.slug.localeCompare(b.slug));\n\n const schemaContent = await generateSchema(collections);\n\n if (outputPath) {\n const outputDir = path.dirname(outputPath);\n await fsPromises.mkdir(outputDir, { recursive: true });\n await fsPromises.writeFile(outputPath, schemaContent);\n out(`✅ Drizzle schema generated successfully at ${outputPath}`);\n } else {\n out(\"✅ Drizzle schema generated successfully.\");\n out(String(schemaContent));\n }\n\n out(nextStepAfterGenerate());\n\n } catch (error) {\n outError(`Error generating schema: ${error instanceof Error ? (error.stack ?? error.message) : String(error)}`);\n // The same contract its sibling `generate-postgres-ddl.ts` keeps, and\n // for the same reason: `rebase db push` runs both as step 1 and reads\n // nothing but their exit codes. Exiting 0 after printing an error made\n // the push apply whatever `src/schema.generated.ts` was already there.\n process.exitCode = 1;\n throw error;\n }\n};\n\nexport const main = async () => {\n const collectionsFilePathArg = process.argv.find(arg => arg.startsWith(\"--collections=\"));\n const collectionsFilePath = collectionsFilePathArg ? collectionsFilePathArg.split(\"=\")[1] : process.argv[2];\n\n const outputPathArg = process.argv.find(arg => arg.startsWith(\"--output=\"));\n const outputPath = outputPathArg ? outputPathArg.split(\"=\")[1] : undefined;\n\n const watch = process.argv.includes(\"--watch\");\n\n if (!collectionsFilePath) {\n out(\"Usage: ts-node generate-drizzle-schema.ts <path-to-collections-file> [--output <path-to-output-file>] [--watch]\");\n return;\n }\n\n const resolvedPath = path.resolve(process.cwd(), collectionsFilePath);\n const resolvedOutputPath = outputPath ? path.resolve(process.cwd(), outputPath) : undefined;\n\n if (watch) {\n out(`Watching for changes in ${resolvedPath}...`);\n // Imported here rather than at module scope, and this is not a style\n // choice: chokidar is needed only by `--watch`, which is a\n // schema-authoring path that never runs inside the runtime image. A\n // top-level import puts it on the boot path of the published driver\n // bundle, and the image installs a hand-listed set of runtime\n // dependencies that does not include it — so the whole driver failed to\n // load with \"Cannot find package 'chokidar'\", and every self-hosted\n // container answered 500 with a stack trace about a file watcher.\n //\n // Same reasoning the image already applies to @ariga/atlas: an\n // authoring-only dependency does not belong on a boot path.\n const { default: chokidar } = await import(\"chokidar\");\n const watcher = chokidar.watch(resolvedPath, {\n persistent: true,\n ignoreInitial: false\n });\n\n watcher.on(\"all\", (event, filePath) => {\n out(`[${event}] ${filePath}. Regenerating schema...`);\n // A watch session outlives a bad edit; the next save is the retry.\n void runGeneration(resolvedPath, resolvedOutputPath).catch(() => undefined);\n });\n } else {\n await runGeneration(resolvedPath, resolvedOutputPath);\n }\n};\n\n// This check ensures the script only runs when executed directly\nif (import.meta.url.endsWith(process.argv[1])) {\n // The cause is already on stderr with the exit code set; this keeps Node\n // from printing the same stack again as an unhandled rejection.\n main().catch(() => {\n process.exitCode = 1;\n });\n}\n"],"mappings":"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"}
|