@dbsp/cli 2.0.1 → 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -223,9 +223,9 @@ function indexRepresentationWarning(tableName, idx) {
223
223
  if (hasExpressions(idx)) {
224
224
  return `Expression index ${warningName(idx.name)} on table ${warningName(tableName)} cannot be represented in the schema and is not managed by dbsp. dbsp will neither drop nor recreate it; maintain it by hand.`;
225
225
  }
226
- const ddlWarning = ddlIndexRepresentationWarning(tableName, idx);
227
226
  const schemaWarning = schemaIndexRepresentationWarning(tableName, idx);
228
- return ddlWarning ?? schemaWarning;
227
+ const ddlWarning = ddlIndexRepresentationWarning(tableName, idx);
228
+ return schemaWarning ?? ddlWarning;
229
229
  }
230
230
  function ddlIndexRepresentationWarning(tableName, idx) {
231
231
  if (canGenerateCreateIndex(tableName, idx, void 0, identityNaming)) {
@@ -418,4 +418,4 @@ export {
418
418
  redactDbUrl,
419
419
  generateSchemaFileWithDiagnostics
420
420
  };
421
- //# sourceMappingURL=chunk-VUNVYYOZ.js.map
421
+ //# sourceMappingURL=chunk-G6NS4BAV.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/generators/schema-codegen.ts","../src/utils/db-utils.ts"],"sourcesContent":["/**\n * CLI-DDL: Schema Codegen Module\n *\n * Generates TypeScript schema files from IntrospectedModelIR.\n * Used by `dbsp introspect` to create dbsp.schema.ts from a database.\n */\n\nimport {\n\tcanGenerateCreateIndex,\n\tgenerateCreateIndex,\n\tidentityNaming,\n} from '@dbsp/adapter-pgsql';\nimport {\n\ttype ModelIR,\n\ttype TableIR,\n\tvalidateSchemaIndexOptions,\n} from '@dbsp/core';\nimport type { IndexIR } from '@dbsp/types';\nimport { redactDbUrl } from '../utils/db-utils.js';\n\n/**\n * Convert a snake_case string to camelCase.\n * @example 'author_id' → 'authorId'\n */\nfunction snakeToCamelCase(name: string): string {\n\treturn name.replace(/_([a-z])/g, (_, letter: string) => letter.toUpperCase());\n}\n\n/**\n * Options for schema code generation.\n */\nexport interface SchemaCodegenOptions {\n\t/** Source database URL (for comment header) */\n\treadonly sourceUrl?: string;\n\t/** Include original DB types as comments */\n\treadonly includeDbTypeComments?: boolean;\n\t/** Receive diagnostics as they are collected, including model warnings. */\n\treadonly onWarning?: (message: string) => void;\n\t/** Timestamp of introspection */\n\treadonly introspectedAt?: Date;\n\t/**\n\t * Database column casing convention.\n\t * When set to 'snake_case', column names are converted to camelCase\n\t * and a `dbCasing: 'snake_case'` comment is added to the generated code.\n\t * @default 'preserve'\n\t */\n\treadonly dbCasing?: 'snake_case' | 'camelCase' | 'preserve';\n}\n\nexport type SchemaCodegenDiagnosticsOptions = SchemaCodegenOptions;\n\nexport interface SchemaCodegenResult {\n\treadonly code: string;\n\treadonly warnings: readonly string[];\n}\n\n/**\n * Serialize a column default value to valid TypeScript source code.\n *\n * CODEX-11/CODEX-14: String(value) produced \"[object Object]\" for SQL-expression\n * defaults like { sql: 'now()' }, and single-quote wrapping without escape broke\n * strings containing quotes, backslashes, or newlines.\n *\n * Rules:\n * - null/undefined → 'null'\n * - number | boolean → unquoted literal (String(value))\n * - string → singleQuoteEscape (escapes \\, ', \\n, \\r, \\t — produces single-quoted TS literal)\n * - { sql: '...' } shape → `{ sql: ${JSON.stringify(sqlExpr)} }`\n * - anything else → throws (unrecognized shape; prevents silent [object Object])\n */\n\n/**\n * Wrap a string value in single quotes for TypeScript source output,\n * escaping any single quotes and backslashes contained in the value.\n *\n * Examples:\n * 'hello' → \"'hello'\"\n * \"O'Brien\" → \"'O\\\\'Brien'\"\n * 'C:\\\\Users' → \"'C:\\\\\\\\Users'\"\n */\nfunction singleQuoteEscape(s: string): string {\n\treturn `'${s\n\t\t.replace(/\\\\/g, '\\\\\\\\')\n\t\t.replace(/'/g, \"\\\\'\")\n\t\t.replace(/\\n/g, '\\\\n')\n\t\t.replace(/\\r/g, '\\\\r')\n\t\t.replace(/\\t/g, '\\\\t')}'`;\n}\n\n/**\n * Neutralize text that must remain inside generated block comments.\n *\n * Catalog-derived values are otherwise capable of closing `/* ... *\\/` comments\n * and turning the rest of the generated file into executable TypeScript.\n */\nfunction blockCommentText(s: string): string {\n\treturn s\n\t\t.replace(/\\*\\//g, '* /')\n\t\t.replace(/\\n/g, '\\\\n')\n\t\t.replace(/\\r/g, '\\\\r')\n\t\t.replace(/\\t/g, '\\\\t');\n}\n\n/**\n * Return the key suitable for use as a TypeScript object key.\n * Valid JS identifiers can be used bare; anything else is single-quote-wrapped.\n * A valid JS identifier starts with a letter, underscore, or $, followed by\n * letters, digits, underscores, or $.\n */\nfunction quoteKey(name: string): string {\n\tif (/^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(name)) {\n\t\treturn name;\n\t}\n\t// F3: Delegate to singleQuoteEscape() which also handles \\n, \\r, \\t.\n\t// The previous inline chain only escaped \\\\ and ' — identifiers containing\n\t// newlines, CR, or tabs would have produced invalid TypeScript output.\n\treturn singleQuoteEscape(name);\n}\n\nfunction emitDefault(d: unknown): string {\n\tif (d === null || d === undefined) return 'null';\n\tif (typeof d === 'number' || typeof d === 'boolean') return String(d);\n\tif (typeof d === 'string') return singleQuoteEscape(d);\n\tif (\n\t\ttypeof d === 'object' &&\n\t\t'sql' in d &&\n\t\ttypeof (d as { sql: unknown }).sql === 'string'\n\t) {\n\t\treturn `{ sql: ${singleQuoteEscape((d as { sql: string }).sql)} }`;\n\t}\n\tthrow new Error(\n\t\t`[schema-codegen] Unrecognized default shape: ${JSON.stringify(d)}`,\n\t);\n}\n\n/**\n * Generate column definition object code.\n */\nfunction generateColumnCode(\n\tcolumn: TableIR['columns'][number],\n\tisPrimaryKey: boolean,\n\tfkInfo:\n\t\t| {\n\t\t\t\ttable: string;\n\t\t\t\tcolumn?: string;\n\t\t\t\tnullable?: boolean;\n\t\t\t\tunique?: boolean;\n\t\t\t\tonDelete?: string;\n\t\t\t\tonUpdate?: string;\n\t\t\t\tisSelfRef?: boolean;\n\t\t }\n\t\t| undefined,\n\toptions: SchemaCodegenOptions,\n): string {\n\t// CODEX-13: FK + PK overlap — a column can be both FK and PK (e.g. shared-PK 1:1).\n\t// We must preserve isPrimaryKey even when fkInfo is present.\n\tif (fkInfo) {\n\t\treturn generateRefCode(column, fkInfo, isPrimaryKey, options);\n\t}\n\n\t// Check if we can use short form (just 'type' string)\n\tconst canUseShortForm =\n\t\t!isPrimaryKey &&\n\t\t!column.nullable &&\n\t\tcolumn.default === undefined &&\n\t\t!column.unique;\n\n\tif (canUseShortForm) {\n\t\t// Short form: 'type'\n\t\tlet code = singleQuoteEscape(column.type);\n\t\tif (options.includeDbTypeComments && column.originalDbType) {\n\t\t\tcode += ` /* from: ${blockCommentText(column.originalDbType)} */`;\n\t\t}\n\t\treturn code;\n\t}\n\n\t// Long form: { type, primaryKey?, nullable?, default?, unique? }\n\tconst props: string[] = [];\n\tprops.push(`type: ${singleQuoteEscape(column.type)}`);\n\n\tif (isPrimaryKey) {\n\t\tprops.push('primaryKey: true');\n\t}\n\n\tif (column.nullable) {\n\t\tprops.push('nullable: true');\n\t}\n\n\tif (column.default !== undefined) {\n\t\tprops.push(`default: ${emitDefault(column.default)}`);\n\t}\n\n\tif (column.unique) {\n\t\tprops.push('unique: true');\n\t}\n\n\tlet code = `{ ${props.join(', ')} }`;\n\n\tif (options.includeDbTypeComments && column.originalDbType) {\n\t\tcode += ` /* from: ${blockCommentText(column.originalDbType)} */`;\n\t}\n\n\treturn code;\n}\n\n/**\n * Generate ref() call for a foreign key column.\n * ARCH-005: Use ref() instead of references: { table, column }\n */\nfunction generateRefCode(\n\tcolumn: TableIR['columns'][number],\n\tfkInfo: {\n\t\ttable: string;\n\t\tcolumn?: string;\n\t\tnullable?: boolean;\n\t\tunique?: boolean;\n\t\tonDelete?: string;\n\t\tonUpdate?: string;\n\t\tisSelfRef?: boolean;\n\t},\n\tisPrimaryKey: boolean,\n\toptions: SchemaCodegenOptions,\n): string {\n\tconst refOptions: string[] = [];\n\n\t// C2: emit references option for non-PK FK target columns.\n\t// Now that buildRefColumn plumbs options.references into ModelIR,\n\t// emitting ref('table', { references: ['col'] }) round-trips correctly.\n\t// L1-followup-4: apply snake→camel transform to the target column name when\n\t// dbCasing='snake_case' so the generated references array round-trips correctly.\n\tif (fkInfo.column) {\n\t\tconst refColName =\n\t\t\toptions.dbCasing === 'snake_case'\n\t\t\t\t? snakeToCamelCase(fkInfo.column)\n\t\t\t\t: fkInfo.column;\n\t\trefOptions.push(`references: [${singleQuoteEscape(refColName)}]`);\n\t}\n\n\t// CODEX-13: FK column that is also PK — emit isPrimaryKey inside ref() options\n\tif (isPrimaryKey) {\n\t\trefOptions.push('isPrimaryKey: true');\n\t}\n\n\t// Nullable FK\n\tif (fkInfo.nullable || column.nullable) {\n\t\trefOptions.push('nullable: true');\n\t}\n\n\t// Unique FK → hasOne (1:1 relation)\n\tif (fkInfo.unique || column.unique) {\n\t\trefOptions.push('unique: true');\n\t}\n\n\t// onDelete action\n\tif (fkInfo.onDelete && fkInfo.onDelete !== 'NO ACTION') {\n\t\trefOptions.push(`onDelete: ${singleQuoteEscape(fkInfo.onDelete)}`);\n\t}\n\n\t// CODEX-12: onUpdate action\n\tif (fkInfo.onUpdate && fkInfo.onUpdate !== 'NO ACTION') {\n\t\trefOptions.push(`onUpdate: ${singleQuoteEscape(fkInfo.onUpdate)}`);\n\t}\n\n\t// Self-referential FK needs roles\n\tif (fkInfo.isSelfRef) {\n\t\t// Infer role names from column name\n\t\t// e.g., 'parentId' → parent: 'parent', children: 'children'\n\t\tconst baseName = column.name.replace(/_?[iI]d$/, '');\n\t\tconst childrenName = baseName === 'parent' ? 'children' : `${baseName}s`;\n\t\trefOptions.push(\n\t\t\t`roles: { parent: ${singleQuoteEscape(baseName)}, children: ${singleQuoteEscape(childrenName)} }`,\n\t\t);\n\t}\n\n\t// Build the ref() call — table name converted if dbCasing applies.\n\t// Route through singleQuoteEscape so a table name containing a single quote,\n\t// backslash, or newline produces valid TypeScript instead of a syntax error.\n\tconst refTable =\n\t\toptions.dbCasing === 'snake_case'\n\t\t\t? snakeToCamelCase(fkInfo.table)\n\t\t\t: fkInfo.table;\n\tlet code: string;\n\tif (refOptions.length === 0) {\n\t\tcode = `ref(${singleQuoteEscape(refTable)})`;\n\t} else {\n\t\tcode = `ref(${singleQuoteEscape(refTable)}, { ${refOptions.join(', ')} })`;\n\t}\n\n\t// Add comment for original DB type if requested\n\tif (options.includeDbTypeComments && column.originalDbType) {\n\t\tcode += ` /* from: ${blockCommentText(column.originalDbType)} */`;\n\t}\n\n\treturn code;\n}\n\n/**\n * Generate table definition code.\n */\nfunction generateTableCode(\n\ttable: TableIR,\n\toptions: SchemaCodegenOptions,\n): string {\n\t// ARCH-005: Build FK info map with extended properties\n\tconst fkMap = new Map<\n\t\tstring,\n\t\t{\n\t\t\ttable: string;\n\t\t\tcolumn?: string;\n\t\t\tnullable?: boolean;\n\t\t\tunique?: boolean;\n\t\t\tonDelete?: string;\n\t\t\tonUpdate?: string;\n\t\t\tisSelfRef?: boolean;\n\t\t}\n\t>();\n\n\tfor (const fk of table.foreignKeys) {\n\t\tconst localCol = fk.columns[0];\n\t\tconst refCol = fk.references.columns[0];\n\t\tif (\n\t\t\tfk.columns.length === 1 &&\n\t\t\tfk.references.columns.length === 1 &&\n\t\t\tfk.references.schema === undefined &&\n\t\t\tlocalCol &&\n\t\t\trefCol\n\t\t) {\n\t\t\t// Find the column to check nullable/unique\n\t\t\t// FK column names may be snake_case (from raw SQL) while column.name\n\t\t\t// is camelCase (when CamelCasePlugin is active)\n\t\t\tconst colDef = table.columns.find(\n\t\t\t\t(c) => c.name === localCol || c.name === snakeToCamelCase(localCol),\n\t\t\t);\n\n\t\t\tconst entry: {\n\t\t\t\ttable: string;\n\t\t\t\tcolumn?: string;\n\t\t\t\tnullable?: boolean;\n\t\t\t\tunique?: boolean;\n\t\t\t\tonDelete?: string;\n\t\t\t\tonUpdate?: string;\n\t\t\t\tisSelfRef?: boolean;\n\t\t\t} = {\n\t\t\t\ttable: fk.references.table,\n\t\t\t\tisSelfRef:\n\t\t\t\t\tfk.references.schema === undefined &&\n\t\t\t\t\tfk.references.table === table.name,\n\t\t\t};\n\n\t\t\t// Only include column if not 'id' (the default)\n\t\t\tif (refCol !== 'id') {\n\t\t\t\tentry.column = refCol;\n\t\t\t}\n\n\t\t\t// Include nullable if true\n\t\t\tif (colDef?.nullable) {\n\t\t\t\tentry.nullable = true;\n\t\t\t}\n\n\t\t\t// Include unique if true\n\t\t\tif (colDef?.unique) {\n\t\t\t\tentry.unique = true;\n\t\t\t}\n\n\t\t\t// Include onDelete if not the default\n\t\t\tif (fk.onDelete && fk.onDelete !== 'NO ACTION') {\n\t\t\t\tentry.onDelete = fk.onDelete;\n\t\t\t}\n\n\t\t\t// CODEX-12: Include onUpdate if not the default\n\t\t\tif (fk.onUpdate && fk.onUpdate !== 'NO ACTION') {\n\t\t\t\tentry.onUpdate = fk.onUpdate;\n\t\t\t}\n\n\t\t\t// Store under both snake_case and camelCase keys so fkMap.get(col.name) works\n\t\t\t// regardless of naming convention\n\t\t\tfkMap.set(localCol, entry);\n\t\t\tconst camelCol = snakeToCamelCase(localCol);\n\t\t\tif (camelCol !== localCol) {\n\t\t\t\tfkMap.set(camelCol, entry);\n\t\t\t}\n\t\t}\n\t}\n\n\tconst shouldCamelCase = options.dbCasing === 'snake_case';\n\n\tconst columnLines = table.columns.map((col) => {\n\t\tconst isPrimaryKey = table.primaryKey\n\t\t\t? typeof table.primaryKey === 'string'\n\t\t\t\t? col.name === table.primaryKey\n\t\t\t\t: table.primaryKey.includes(col.name)\n\t\t\t: false;\n\t\tconst fkInfo = fkMap.get(col.name);\n\t\tconst code = generateColumnCode(col, isPrimaryKey, fkInfo, options);\n\t\tconst rawColName = shouldCamelCase ? snakeToCamelCase(col.name) : col.name;\n\t\t// C7: quote the key if it's not a valid JS identifier (e.g. contains hyphens)\n\t\tconst colKey = quoteKey(rawColName);\n\t\treturn `\\t\\t${colKey}: ${code}`;\n\t});\n\n\tconst rawTableName = shouldCamelCase\n\t\t? snakeToCamelCase(table.name)\n\t\t: table.name;\n\t// C7: quote the table key if it's not a valid JS identifier\n\tconst tableKey = quoteKey(rawTableName);\n\treturn `\\t${tableKey}: {\\n${columnLines.join(',\\n')},\\n\\t}`;\n}\n\nfunction generatedName(name: string, options: SchemaCodegenOptions): string {\n\treturn options.dbCasing === 'snake_case' ? snakeToCamelCase(name) : name;\n}\n\nfunction stringArrayLiteral(values: readonly string[]): string {\n\treturn `[${values.map(singleQuoteEscape).join(', ')}]`;\n}\n\n/**\n * Emit a strict boolean literal. Interpolating the value itself would run a\n * foreign object's toString into the generated source, which is later imported.\n */\nfunction booleanLiteral(value: unknown): 'true' | 'false' {\n\treturn value === true ? 'true' : 'false';\n}\n\nfunction stringRecordLiteral(\n\tvalues: Readonly<Record<string, string>>,\n\tkeyName: (name: string) => string = (name) => name,\n): string {\n\tconst entries = Object.entries(values).map(\n\t\t([key, value]) =>\n\t\t\t`[${singleQuoteEscape(keyName(key))}]: ${singleQuoteEscape(value)}`,\n\t);\n\treturn `{ ${entries.join(', ')} }`;\n}\n\nfunction tableLevelForeignKeys(table: TableIR): TableIR['foreignKeys'] {\n\treturn table.foreignKeys.filter(\n\t\t(fk) =>\n\t\t\tfk.references.schema !== undefined ||\n\t\t\tfk.columns.length > 1 ||\n\t\t\tfk.references.columns.length > 1,\n\t);\n}\n\nfunction hasExpressions(idx: IndexIR): boolean {\n\treturn (idx.expressions?.length ?? 0) > 0;\n}\n\nfunction tableLevelIndexes(table: TableIR): readonly IndexIR[] {\n\treturn table.indexes.filter(\n\t\t(idx) => indexRepresentationWarning(table.name, idx) === undefined,\n\t);\n}\n\nfunction warningName(name: string | undefined): string {\n\treturn name === undefined ? '<unnamed>' : JSON.stringify(name);\n}\n\nfunction errorMessage(error: unknown): string {\n\treturn error instanceof Error ? error.message : String(error);\n}\n\nfunction indexRepresentationWarning(\n\ttableName: string,\n\tidx: IndexIR,\n): string | undefined {\n\tif (hasExpressions(idx)) {\n\t\treturn `Expression index ${warningName(idx.name)} on table ${warningName(tableName)} cannot be represented in the schema and is not managed by dbsp. dbsp will neither drop nor recreate it; maintain it by hand.`;\n\t}\n\tconst ddlWarning = ddlIndexRepresentationWarning(tableName, idx);\n\tconst schemaWarning = schemaIndexRepresentationWarning(tableName, idx);\n\treturn ddlWarning ?? schemaWarning;\n}\n\nfunction ddlIndexRepresentationWarning(\n\ttableName: string,\n\tidx: IndexIR,\n): string | undefined {\n\tif (canGenerateCreateIndex(tableName, idx, undefined, identityNaming)) {\n\t\treturn undefined;\n\t}\n\ttry {\n\t\tgenerateCreateIndex(tableName, idx, undefined, identityNaming);\n\t} catch (error) {\n\t\treturn `Index ${warningName(idx.name)} on table ${warningName(tableName)} cannot be represented in the schema and is not managed by dbsp because the DDL emitter rejected it: ${errorMessage(error)}. dbsp will neither drop nor recreate it; maintain it by hand.`;\n\t}\n\treturn undefined;\n}\n\nfunction schemaIndexRepresentationWarning(\n\ttableName: string,\n\tidx: IndexIR,\n): string | undefined {\n\ttry {\n\t\tvalidateSchemaIndexOptions(tableName, idx);\n\t} catch (error) {\n\t\treturn `Index ${warningName(idx.name)} on table ${warningName(tableName)} cannot be represented in the schema and is not managed by dbsp because schema() rejected it: ${errorMessage(error)}. dbsp will neither drop nor recreate it; maintain it by hand.`;\n\t}\n\treturn undefined;\n}\n\nfunction schemaCodegenWarnings(model: ModelIR): string[] {\n\tconst warnings: string[] = [];\n\tfor (const table of model.tables.values()) {\n\t\tfor (const idx of table.indexes) {\n\t\t\tconst warning = indexRepresentationWarning(table.name, idx);\n\t\t\tif (warning !== undefined) {\n\t\t\t\twarnings.push(warning);\n\t\t\t}\n\t\t}\n\t}\n\treturn warnings;\n}\n\nfunction modelWarnings(model: ModelIR): readonly string[] {\n\tconst warnings = (model as ModelIR & { readonly warnings?: unknown })\n\t\t.warnings;\n\treturn Array.isArray(warnings) && warnings.every((w) => typeof w === 'string')\n\t\t? warnings\n\t\t: [];\n}\n\nfunction generateIndexCode(\n\tidx: IndexIR,\n\toptions: SchemaCodegenOptions,\n): string {\n\tconst indexOptions = [\n\t\t`columns: ${stringArrayLiteral(idx.columns.map((col) => generatedName(col, options)))}`,\n\t];\n\tif (idx.unique !== undefined) {\n\t\tindexOptions.push(`unique: ${booleanLiteral(idx.unique)}`);\n\t}\n\tif (idx.name !== undefined) {\n\t\tindexOptions.push(`name: ${singleQuoteEscape(idx.name)}`);\n\t}\n\tif (idx.method !== undefined) {\n\t\tindexOptions.push(`method: ${singleQuoteEscape(idx.method)}`);\n\t}\n\tif (idx.where !== undefined) {\n\t\tindexOptions.push(`where: ${singleQuoteEscape(idx.where)}`);\n\t}\n\tif (idx.opclass !== undefined) {\n\t\tindexOptions.push(\n\t\t\t`opclass: ${stringRecordLiteral(idx.opclass, (key) => generatedName(key, options))}`,\n\t\t);\n\t}\n\tif (idx.with !== undefined) {\n\t\tindexOptions.push(`with: ${stringRecordLiteral(idx.with)}`);\n\t}\n\tif (idx.include !== undefined) {\n\t\tindexOptions.push(\n\t\t\t`include: ${stringArrayLiteral(idx.include.map((col) => generatedName(col, options)))}`,\n\t\t);\n\t}\n\tif (idx.nullsNotDistinct !== undefined) {\n\t\tindexOptions.push(\n\t\t\t`nullsNotDistinct: ${booleanLiteral(idx.nullsNotDistinct)}`,\n\t\t);\n\t}\n\treturn `\\t\\t\\t{ ${indexOptions.join(', ')} }`;\n}\n\nfunction generateTableConstraintCode(\n\tmodel: ModelIR,\n\toptions: SchemaCodegenOptions,\n): string | undefined {\n\tconst tableBlocks: string[] = [];\n\tfor (const table of model.tables.values()) {\n\t\tconst fks = tableLevelForeignKeys(table);\n\t\tconst indexes = tableLevelIndexes(table);\n\t\tif (fks.length === 0 && indexes.length === 0) continue;\n\t\tconst tableOptions: string[] = [];\n\t\tif (indexes.length > 0) {\n\t\t\tconst indexLines = indexes.map((idx) => generateIndexCode(idx, options));\n\t\t\ttableOptions.push(`\\t\\tindexes: [\\n${indexLines.join(',\\n')}\\n\\t\\t]`);\n\t\t}\n\t\tif (fks.length > 0) {\n\t\t\tconst fkLines = fks.map((fk) => {\n\t\t\t\tconst refOptions = [\n\t\t\t\t\t...(fk.references.schema !== undefined\n\t\t\t\t\t\t? [`schema: ${singleQuoteEscape(fk.references.schema)}`]\n\t\t\t\t\t\t: []),\n\t\t\t\t\t`columns: ${stringArrayLiteral(fk.columns.map((col) => generatedName(col, options)))}`,\n\t\t\t\t\t`references: ${stringArrayLiteral(fk.references.columns.map((col) => generatedName(col, options)))}`,\n\t\t\t\t];\n\t\t\t\tif (fk.onDelete && fk.onDelete !== 'NO ACTION') {\n\t\t\t\t\trefOptions.push(`onDelete: ${singleQuoteEscape(fk.onDelete)}`);\n\t\t\t\t}\n\t\t\t\tif (fk.onUpdate && fk.onUpdate !== 'NO ACTION') {\n\t\t\t\t\trefOptions.push(`onUpdate: ${singleQuoteEscape(fk.onUpdate)}`);\n\t\t\t\t}\n\t\t\t\treturn `\\t\\t\\tref(${singleQuoteEscape(generatedName(fk.references.table, options))}, { ${refOptions.join(', ')} })`;\n\t\t\t});\n\t\t\ttableOptions.push(`\\t\\tforeignKeys: [\\n${fkLines.join(',\\n')}\\n\\t\\t]`);\n\t\t}\n\t\ttableBlocks.push(\n\t\t\t`\\t${quoteKey(generatedName(table.name, options))}: {\\n${tableOptions.join(',\\n')}\\n\\t}`,\n\t\t);\n\t}\n\tif (tableBlocks.length === 0) return undefined;\n\treturn `{\\n${tableBlocks.join(',\\n\\n')}\\n}`;\n}\n\n/**\n * Generate a TypeScript schema file from ModelIR and return diagnostics.\n *\n * @param model - The ModelIR (or IntrospectedModelIR) to generate from\n * @param options - Code generation options\n * @returns Generated source code and warnings\n */\nexport function generateSchemaFileWithDiagnostics(\n\tmodel: ModelIR,\n\toptions: SchemaCodegenDiagnosticsOptions = {},\n): SchemaCodegenResult {\n\tconst warnings = [...modelWarnings(model), ...schemaCodegenWarnings(model)];\n\tfor (const warning of warnings) {\n\t\toptions.onWarning?.(warning);\n\t}\n\n\tconst lines: string[] = [];\n\n\t// Header comment\n\tlines.push('/**');\n\tlines.push(' * Auto-generated by: dbsp introspect');\n\tif (options.sourceUrl) {\n\t\tconst redactedUrl = redactDbUrl(options.sourceUrl);\n\t\tlines.push(` * Source: ${blockCommentText(redactedUrl)}`);\n\t}\n\tif (options.introspectedAt) {\n\t\tlines.push(` * Generated: ${options.introspectedAt.toISOString()}`);\n\t}\n\tlines.push(' *');\n\tlines.push(' * Review before using in production.');\n\tlines.push(' */');\n\tlines.push('');\n\n\t// ARCH-005: Check if any table has FKs to determine imports\n\tconst hasForeignKeys = Array.from(model.tables.values()).some(\n\t\t(table) => table.foreignKeys.length > 0,\n\t);\n\n\t// Imports for the live schema() + ref() authoring API.\n\tconst coreImports = ['schema'];\n\tif (hasForeignKeys) {\n\t\tcoreImports.push('ref');\n\t}\n\tlines.push(`import { ${coreImports.join(', ')} } from '@dbsp/core';`);\n\tif (options.dbCasing && options.dbCasing !== 'preserve') {\n\t\tlines.push(\"import { createPgsqlAdapter } from '@dbsp/adapter-pgsql';\");\n\t}\n\tlines.push('');\n\n\t// Schema definition.\n\tlines.push('export const dbSchema = schema({');\n\n\t// Generate each table\n\tconst tables = Array.from(model.tables.values());\n\tconst tableLines = tables.map((table) => generateTableCode(table, options));\n\tlines.push(tableLines.join(',\\n\\n'));\n\n\tconst tableConstraints = generateTableConstraintCode(model, options);\n\tif (tableConstraints) {\n\t\tlines.push(`}, ${tableConstraints});`);\n\t} else {\n\t\tlines.push('});');\n\t}\n\tlines.push('');\n\n\t// Add a default export so schema-loader can load generated files directly.\n\t// schema-loader accepts module.schema || module.default; the named dbSchema\n\t// export is the canonical API, but the default export allows `loadSchema()`\n\t// to round-trip without the caller knowing the internal export name.\n\tlines.push('export default dbSchema;');\n\tlines.push('');\n\n\t// Usage hint with dbCasing\n\tif (options.dbCasing && options.dbCasing !== 'preserve') {\n\t\tlines.push('/**');\n\t\tlines.push(\n\t\t\t` * Usage: columns above are camelCase; the database uses ${options.dbCasing}.`,\n\t\t);\n\t\tlines.push(\n\t\t\t' * Pass dbCasing to the adapter so it maps camelCase ↔ snake_case automatically.',\n\t\t);\n\t\tlines.push(' *');\n\t\tlines.push(' * @example');\n\t\tlines.push(' * ```typescript');\n\t\tlines.push(' * const orm = createOrm({');\n\t\tlines.push(' * model: dbSchema.model,');\n\t\tlines.push(\n\t\t\t` * adapter: createPgsqlAdapter(pool, { dbCasing: '${options.dbCasing}' }),`,\n\t\t);\n\t\tlines.push(' * });');\n\t\tlines.push(' * ```');\n\t\tlines.push(' */');\n\t\tlines.push(`export const dbCasing = '${options.dbCasing}' as const;`);\n\t\tlines.push('');\n\t}\n\n\treturn {\n\t\tcode: lines.join('\\n'),\n\t\twarnings,\n\t};\n}\n","/**\n * Shared database utilities for CLI commands.\n *\n * E09: Extracted from verify.ts and introspect.ts to DRY up code.\n * E09b: Shared URL redaction for secure logging.\n */\n\n/**\n * Create a database connection pool.\n * pg is an optional peer dependency.\n *\n * @param connectionUrl - PostgreSQL connection URL\n * @returns Pool instance\n * @throws Error if pg is not installed\n */\nexport async function createDbConnection(connectionUrl: string) {\n\tlet pg: typeof import('pg').default;\n\ttry {\n\t\tconst mod = await import('pg');\n\t\tpg = mod.default;\n\t} catch {\n\t\tthrow new Error(\n\t\t\t'pg is required for this command. Install it with: pnpm add pg',\n\t\t);\n\t}\n\n\tconst pool = new pg.Pool({\n\t\tconnectionString: connectionUrl,\n\t});\n\treturn { pool };\n}\n\n/**\n * Redact password from a database connection URL for safe logging.\n *\n * @param url - Database connection URL (e.g., postgres://user:pass@host/db)\n * @returns URL with password replaced by ***\n *\n * @example\n * redactDbUrl('postgres://user:secret@localhost/mydb')\n * // => 'postgres://user:***@localhost/mydb'\n */\nexport function redactDbUrl(url: string): string {\n\ttry {\n\t\tconst u = new URL(url);\n\t\tif (u.password) {\n\t\t\tu.password = '***';\n\t\t}\n\t\treturn u.toString();\n\t} catch {\n\t\t// Fall back to regex for non-standard or malformed URLs\n\t\treturn url.replace(/:[^:@]+@/, ':***@');\n\t}\n}\n"],"mappings":";AAOA;AAAA,EACC;AAAA,EACA;AAAA,EACA;AAAA,OACM;AACP;AAAA,EAGC;AAAA,OACM;;;ACDP,eAAsB,mBAAmB,eAAuB;AAC/D,MAAI;AACJ,MAAI;AACH,UAAM,MAAM,MAAM,OAAO,IAAI;AAC7B,SAAK,IAAI;AAAA,EACV,QAAQ;AACP,UAAM,IAAI;AAAA,MACT;AAAA,IACD;AAAA,EACD;AAEA,QAAM,OAAO,IAAI,GAAG,KAAK;AAAA,IACxB,kBAAkB;AAAA,EACnB,CAAC;AACD,SAAO,EAAE,KAAK;AACf;AAYO,SAAS,YAAY,KAAqB;AAChD,MAAI;AACH,UAAM,IAAI,IAAI,IAAI,GAAG;AACrB,QAAI,EAAE,UAAU;AACf,QAAE,WAAW;AAAA,IACd;AACA,WAAO,EAAE,SAAS;AAAA,EACnB,QAAQ;AAEP,WAAO,IAAI,QAAQ,YAAY,OAAO;AAAA,EACvC;AACD;;;AD7BA,SAAS,iBAAiB,MAAsB;AAC/C,SAAO,KAAK,QAAQ,aAAa,CAAC,GAAG,WAAmB,OAAO,YAAY,CAAC;AAC7E;AAsDA,SAAS,kBAAkB,GAAmB;AAC7C,SAAO,IAAI,EACT,QAAQ,OAAO,MAAM,EACrB,QAAQ,MAAM,KAAK,EACnB,QAAQ,OAAO,KAAK,EACpB,QAAQ,OAAO,KAAK,EACpB,QAAQ,OAAO,KAAK,CAAC;AACxB;AAQA,SAAS,iBAAiB,GAAmB;AAC5C,SAAO,EACL,QAAQ,SAAS,KAAK,EACtB,QAAQ,OAAO,KAAK,EACpB,QAAQ,OAAO,KAAK,EACpB,QAAQ,OAAO,KAAK;AACvB;AAQA,SAAS,SAAS,MAAsB;AACvC,MAAI,6BAA6B,KAAK,IAAI,GAAG;AAC5C,WAAO;AAAA,EACR;AAIA,SAAO,kBAAkB,IAAI;AAC9B;AAEA,SAAS,YAAY,GAAoB;AACxC,MAAI,MAAM,QAAQ,MAAM,OAAW,QAAO;AAC1C,MAAI,OAAO,MAAM,YAAY,OAAO,MAAM,UAAW,QAAO,OAAO,CAAC;AACpE,MAAI,OAAO,MAAM,SAAU,QAAO,kBAAkB,CAAC;AACrD,MACC,OAAO,MAAM,YACb,SAAS,KACT,OAAQ,EAAuB,QAAQ,UACtC;AACD,WAAO,UAAU,kBAAmB,EAAsB,GAAG,CAAC;AAAA,EAC/D;AACA,QAAM,IAAI;AAAA,IACT,gDAAgD,KAAK,UAAU,CAAC,CAAC;AAAA,EAClE;AACD;AAKA,SAAS,mBACR,QACA,cACA,QAWA,SACS;AAGT,MAAI,QAAQ;AACX,WAAO,gBAAgB,QAAQ,QAAQ,cAAc,OAAO;AAAA,EAC7D;AAGA,QAAM,kBACL,CAAC,gBACD,CAAC,OAAO,YACR,OAAO,YAAY,UACnB,CAAC,OAAO;AAET,MAAI,iBAAiB;AAEpB,QAAIA,QAAO,kBAAkB,OAAO,IAAI;AACxC,QAAI,QAAQ,yBAAyB,OAAO,gBAAgB;AAC3D,MAAAA,SAAQ,aAAa,iBAAiB,OAAO,cAAc,CAAC;AAAA,IAC7D;AACA,WAAOA;AAAA,EACR;AAGA,QAAM,QAAkB,CAAC;AACzB,QAAM,KAAK,SAAS,kBAAkB,OAAO,IAAI,CAAC,EAAE;AAEpD,MAAI,cAAc;AACjB,UAAM,KAAK,kBAAkB;AAAA,EAC9B;AAEA,MAAI,OAAO,UAAU;AACpB,UAAM,KAAK,gBAAgB;AAAA,EAC5B;AAEA,MAAI,OAAO,YAAY,QAAW;AACjC,UAAM,KAAK,YAAY,YAAY,OAAO,OAAO,CAAC,EAAE;AAAA,EACrD;AAEA,MAAI,OAAO,QAAQ;AAClB,UAAM,KAAK,cAAc;AAAA,EAC1B;AAEA,MAAI,OAAO,KAAK,MAAM,KAAK,IAAI,CAAC;AAEhC,MAAI,QAAQ,yBAAyB,OAAO,gBAAgB;AAC3D,YAAQ,aAAa,iBAAiB,OAAO,cAAc,CAAC;AAAA,EAC7D;AAEA,SAAO;AACR;AAMA,SAAS,gBACR,QACA,QASA,cACA,SACS;AACT,QAAM,aAAuB,CAAC;AAO9B,MAAI,OAAO,QAAQ;AAClB,UAAM,aACL,QAAQ,aAAa,eAClB,iBAAiB,OAAO,MAAM,IAC9B,OAAO;AACX,eAAW,KAAK,gBAAgB,kBAAkB,UAAU,CAAC,GAAG;AAAA,EACjE;AAGA,MAAI,cAAc;AACjB,eAAW,KAAK,oBAAoB;AAAA,EACrC;AAGA,MAAI,OAAO,YAAY,OAAO,UAAU;AACvC,eAAW,KAAK,gBAAgB;AAAA,EACjC;AAGA,MAAI,OAAO,UAAU,OAAO,QAAQ;AACnC,eAAW,KAAK,cAAc;AAAA,EAC/B;AAGA,MAAI,OAAO,YAAY,OAAO,aAAa,aAAa;AACvD,eAAW,KAAK,aAAa,kBAAkB,OAAO,QAAQ,CAAC,EAAE;AAAA,EAClE;AAGA,MAAI,OAAO,YAAY,OAAO,aAAa,aAAa;AACvD,eAAW,KAAK,aAAa,kBAAkB,OAAO,QAAQ,CAAC,EAAE;AAAA,EAClE;AAGA,MAAI,OAAO,WAAW;AAGrB,UAAM,WAAW,OAAO,KAAK,QAAQ,YAAY,EAAE;AACnD,UAAM,eAAe,aAAa,WAAW,aAAa,GAAG,QAAQ;AACrE,eAAW;AAAA,MACV,oBAAoB,kBAAkB,QAAQ,CAAC,eAAe,kBAAkB,YAAY,CAAC;AAAA,IAC9F;AAAA,EACD;AAKA,QAAM,WACL,QAAQ,aAAa,eAClB,iBAAiB,OAAO,KAAK,IAC7B,OAAO;AACX,MAAI;AACJ,MAAI,WAAW,WAAW,GAAG;AAC5B,WAAO,OAAO,kBAAkB,QAAQ,CAAC;AAAA,EAC1C,OAAO;AACN,WAAO,OAAO,kBAAkB,QAAQ,CAAC,OAAO,WAAW,KAAK,IAAI,CAAC;AAAA,EACtE;AAGA,MAAI,QAAQ,yBAAyB,OAAO,gBAAgB;AAC3D,YAAQ,aAAa,iBAAiB,OAAO,cAAc,CAAC;AAAA,EAC7D;AAEA,SAAO;AACR;AAKA,SAAS,kBACR,OACA,SACS;AAET,QAAM,QAAQ,oBAAI,IAWhB;AAEF,aAAW,MAAM,MAAM,aAAa;AACnC,UAAM,WAAW,GAAG,QAAQ,CAAC;AAC7B,UAAM,SAAS,GAAG,WAAW,QAAQ,CAAC;AACtC,QACC,GAAG,QAAQ,WAAW,KACtB,GAAG,WAAW,QAAQ,WAAW,KACjC,GAAG,WAAW,WAAW,UACzB,YACA,QACC;AAID,YAAM,SAAS,MAAM,QAAQ;AAAA,QAC5B,CAAC,MAAM,EAAE,SAAS,YAAY,EAAE,SAAS,iBAAiB,QAAQ;AAAA,MACnE;AAEA,YAAM,QAQF;AAAA,QACH,OAAO,GAAG,WAAW;AAAA,QACrB,WACC,GAAG,WAAW,WAAW,UACzB,GAAG,WAAW,UAAU,MAAM;AAAA,MAChC;AAGA,UAAI,WAAW,MAAM;AACpB,cAAM,SAAS;AAAA,MAChB;AAGA,UAAI,QAAQ,UAAU;AACrB,cAAM,WAAW;AAAA,MAClB;AAGA,UAAI,QAAQ,QAAQ;AACnB,cAAM,SAAS;AAAA,MAChB;AAGA,UAAI,GAAG,YAAY,GAAG,aAAa,aAAa;AAC/C,cAAM,WAAW,GAAG;AAAA,MACrB;AAGA,UAAI,GAAG,YAAY,GAAG,aAAa,aAAa;AAC/C,cAAM,WAAW,GAAG;AAAA,MACrB;AAIA,YAAM,IAAI,UAAU,KAAK;AACzB,YAAM,WAAW,iBAAiB,QAAQ;AAC1C,UAAI,aAAa,UAAU;AAC1B,cAAM,IAAI,UAAU,KAAK;AAAA,MAC1B;AAAA,IACD;AAAA,EACD;AAEA,QAAM,kBAAkB,QAAQ,aAAa;AAE7C,QAAM,cAAc,MAAM,QAAQ,IAAI,CAAC,QAAQ;AAC9C,UAAM,eAAe,MAAM,aACxB,OAAO,MAAM,eAAe,WAC3B,IAAI,SAAS,MAAM,aACnB,MAAM,WAAW,SAAS,IAAI,IAAI,IACnC;AACH,UAAM,SAAS,MAAM,IAAI,IAAI,IAAI;AACjC,UAAM,OAAO,mBAAmB,KAAK,cAAc,QAAQ,OAAO;AAClE,UAAM,aAAa,kBAAkB,iBAAiB,IAAI,IAAI,IAAI,IAAI;AAEtE,UAAM,SAAS,SAAS,UAAU;AAClC,WAAO,KAAO,MAAM,KAAK,IAAI;AAAA,EAC9B,CAAC;AAED,QAAM,eAAe,kBAClB,iBAAiB,MAAM,IAAI,IAC3B,MAAM;AAET,QAAM,WAAW,SAAS,YAAY;AACtC,SAAO,IAAK,QAAQ;AAAA,EAAQ,YAAY,KAAK,KAAK,CAAC;AAAA;AACpD;AAEA,SAAS,cAAc,MAAc,SAAuC;AAC3E,SAAO,QAAQ,aAAa,eAAe,iBAAiB,IAAI,IAAI;AACrE;AAEA,SAAS,mBAAmB,QAAmC;AAC9D,SAAO,IAAI,OAAO,IAAI,iBAAiB,EAAE,KAAK,IAAI,CAAC;AACpD;AAMA,SAAS,eAAe,OAAkC;AACzD,SAAO,UAAU,OAAO,SAAS;AAClC;AAEA,SAAS,oBACR,QACA,UAAoC,CAAC,SAAS,MACrC;AACT,QAAM,UAAU,OAAO,QAAQ,MAAM,EAAE;AAAA,IACtC,CAAC,CAAC,KAAK,KAAK,MACX,IAAI,kBAAkB,QAAQ,GAAG,CAAC,CAAC,MAAM,kBAAkB,KAAK,CAAC;AAAA,EACnE;AACA,SAAO,KAAK,QAAQ,KAAK,IAAI,CAAC;AAC/B;AAEA,SAAS,sBAAsB,OAAwC;AACtE,SAAO,MAAM,YAAY;AAAA,IACxB,CAAC,OACA,GAAG,WAAW,WAAW,UACzB,GAAG,QAAQ,SAAS,KACpB,GAAG,WAAW,QAAQ,SAAS;AAAA,EACjC;AACD;AAEA,SAAS,eAAe,KAAuB;AAC9C,UAAQ,IAAI,aAAa,UAAU,KAAK;AACzC;AAEA,SAAS,kBAAkB,OAAoC;AAC9D,SAAO,MAAM,QAAQ;AAAA,IACpB,CAAC,QAAQ,2BAA2B,MAAM,MAAM,GAAG,MAAM;AAAA,EAC1D;AACD;AAEA,SAAS,YAAY,MAAkC;AACtD,SAAO,SAAS,SAAY,cAAc,KAAK,UAAU,IAAI;AAC9D;AAEA,SAAS,aAAa,OAAwB;AAC7C,SAAO,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;AAC7D;AAEA,SAAS,2BACR,WACA,KACqB;AACrB,MAAI,eAAe,GAAG,GAAG;AACxB,WAAO,oBAAoB,YAAY,IAAI,IAAI,CAAC,aAAa,YAAY,SAAS,CAAC;AAAA,EACpF;AACA,QAAM,aAAa,8BAA8B,WAAW,GAAG;AAC/D,QAAM,gBAAgB,iCAAiC,WAAW,GAAG;AACrE,SAAO,cAAc;AACtB;AAEA,SAAS,8BACR,WACA,KACqB;AACrB,MAAI,uBAAuB,WAAW,KAAK,QAAW,cAAc,GAAG;AACtE,WAAO;AAAA,EACR;AACA,MAAI;AACH,wBAAoB,WAAW,KAAK,QAAW,cAAc;AAAA,EAC9D,SAAS,OAAO;AACf,WAAO,SAAS,YAAY,IAAI,IAAI,CAAC,aAAa,YAAY,SAAS,CAAC,wGAAwG,aAAa,KAAK,CAAC;AAAA,EACpM;AACA,SAAO;AACR;AAEA,SAAS,iCACR,WACA,KACqB;AACrB,MAAI;AACH,+BAA2B,WAAW,GAAG;AAAA,EAC1C,SAAS,OAAO;AACf,WAAO,SAAS,YAAY,IAAI,IAAI,CAAC,aAAa,YAAY,SAAS,CAAC,iGAAiG,aAAa,KAAK,CAAC;AAAA,EAC7L;AACA,SAAO;AACR;AAEA,SAAS,sBAAsB,OAA0B;AACxD,QAAM,WAAqB,CAAC;AAC5B,aAAW,SAAS,MAAM,OAAO,OAAO,GAAG;AAC1C,eAAW,OAAO,MAAM,SAAS;AAChC,YAAM,UAAU,2BAA2B,MAAM,MAAM,GAAG;AAC1D,UAAI,YAAY,QAAW;AAC1B,iBAAS,KAAK,OAAO;AAAA,MACtB;AAAA,IACD;AAAA,EACD;AACA,SAAO;AACR;AAEA,SAAS,cAAc,OAAmC;AACzD,QAAM,WAAY,MAChB;AACF,SAAO,MAAM,QAAQ,QAAQ,KAAK,SAAS,MAAM,CAAC,MAAM,OAAO,MAAM,QAAQ,IAC1E,WACA,CAAC;AACL;AAEA,SAAS,kBACR,KACA,SACS;AACT,QAAM,eAAe;AAAA,IACpB,YAAY,mBAAmB,IAAI,QAAQ,IAAI,CAAC,QAAQ,cAAc,KAAK,OAAO,CAAC,CAAC,CAAC;AAAA,EACtF;AACA,MAAI,IAAI,WAAW,QAAW;AAC7B,iBAAa,KAAK,WAAW,eAAe,IAAI,MAAM,CAAC,EAAE;AAAA,EAC1D;AACA,MAAI,IAAI,SAAS,QAAW;AAC3B,iBAAa,KAAK,SAAS,kBAAkB,IAAI,IAAI,CAAC,EAAE;AAAA,EACzD;AACA,MAAI,IAAI,WAAW,QAAW;AAC7B,iBAAa,KAAK,WAAW,kBAAkB,IAAI,MAAM,CAAC,EAAE;AAAA,EAC7D;AACA,MAAI,IAAI,UAAU,QAAW;AAC5B,iBAAa,KAAK,UAAU,kBAAkB,IAAI,KAAK,CAAC,EAAE;AAAA,EAC3D;AACA,MAAI,IAAI,YAAY,QAAW;AAC9B,iBAAa;AAAA,MACZ,YAAY,oBAAoB,IAAI,SAAS,CAAC,QAAQ,cAAc,KAAK,OAAO,CAAC,CAAC;AAAA,IACnF;AAAA,EACD;AACA,MAAI,IAAI,SAAS,QAAW;AAC3B,iBAAa,KAAK,SAAS,oBAAoB,IAAI,IAAI,CAAC,EAAE;AAAA,EAC3D;AACA,MAAI,IAAI,YAAY,QAAW;AAC9B,iBAAa;AAAA,MACZ,YAAY,mBAAmB,IAAI,QAAQ,IAAI,CAAC,QAAQ,cAAc,KAAK,OAAO,CAAC,CAAC,CAAC;AAAA,IACtF;AAAA,EACD;AACA,MAAI,IAAI,qBAAqB,QAAW;AACvC,iBAAa;AAAA,MACZ,qBAAqB,eAAe,IAAI,gBAAgB,CAAC;AAAA,IAC1D;AAAA,EACD;AACA,SAAO,QAAW,aAAa,KAAK,IAAI,CAAC;AAC1C;AAEA,SAAS,4BACR,OACA,SACqB;AACrB,QAAM,cAAwB,CAAC;AAC/B,aAAW,SAAS,MAAM,OAAO,OAAO,GAAG;AAC1C,UAAM,MAAM,sBAAsB,KAAK;AACvC,UAAM,UAAU,kBAAkB,KAAK;AACvC,QAAI,IAAI,WAAW,KAAK,QAAQ,WAAW,EAAG;AAC9C,UAAM,eAAyB,CAAC;AAChC,QAAI,QAAQ,SAAS,GAAG;AACvB,YAAM,aAAa,QAAQ,IAAI,CAAC,QAAQ,kBAAkB,KAAK,OAAO,CAAC;AACvE,mBAAa,KAAK;AAAA,EAAmB,WAAW,KAAK,KAAK,CAAC;AAAA,IAAS;AAAA,IACrE;AACA,QAAI,IAAI,SAAS,GAAG;AACnB,YAAM,UAAU,IAAI,IAAI,CAAC,OAAO;AAC/B,cAAM,aAAa;AAAA,UAClB,GAAI,GAAG,WAAW,WAAW,SAC1B,CAAC,WAAW,kBAAkB,GAAG,WAAW,MAAM,CAAC,EAAE,IACrD,CAAC;AAAA,UACJ,YAAY,mBAAmB,GAAG,QAAQ,IAAI,CAAC,QAAQ,cAAc,KAAK,OAAO,CAAC,CAAC,CAAC;AAAA,UACpF,eAAe,mBAAmB,GAAG,WAAW,QAAQ,IAAI,CAAC,QAAQ,cAAc,KAAK,OAAO,CAAC,CAAC,CAAC;AAAA,QACnG;AACA,YAAI,GAAG,YAAY,GAAG,aAAa,aAAa;AAC/C,qBAAW,KAAK,aAAa,kBAAkB,GAAG,QAAQ,CAAC,EAAE;AAAA,QAC9D;AACA,YAAI,GAAG,YAAY,GAAG,aAAa,aAAa;AAC/C,qBAAW,KAAK,aAAa,kBAAkB,GAAG,QAAQ,CAAC,EAAE;AAAA,QAC9D;AACA,eAAO,UAAa,kBAAkB,cAAc,GAAG,WAAW,OAAO,OAAO,CAAC,CAAC,OAAO,WAAW,KAAK,IAAI,CAAC;AAAA,MAC/G,CAAC;AACD,mBAAa,KAAK;AAAA,EAAuB,QAAQ,KAAK,KAAK,CAAC;AAAA,IAAS;AAAA,IACtE;AACA,gBAAY;AAAA,MACX,IAAK,SAAS,cAAc,MAAM,MAAM,OAAO,CAAC,CAAC;AAAA,EAAQ,aAAa,KAAK,KAAK,CAAC;AAAA;AAAA,IAClF;AAAA,EACD;AACA,MAAI,YAAY,WAAW,EAAG,QAAO;AACrC,SAAO;AAAA,EAAM,YAAY,KAAK,OAAO,CAAC;AAAA;AACvC;AASO,SAAS,kCACf,OACA,UAA2C,CAAC,GACtB;AACtB,QAAM,WAAW,CAAC,GAAG,cAAc,KAAK,GAAG,GAAG,sBAAsB,KAAK,CAAC;AAC1E,aAAW,WAAW,UAAU;AAC/B,YAAQ,YAAY,OAAO;AAAA,EAC5B;AAEA,QAAM,QAAkB,CAAC;AAGzB,QAAM,KAAK,KAAK;AAChB,QAAM,KAAK,uCAAuC;AAClD,MAAI,QAAQ,WAAW;AACtB,UAAM,cAAc,YAAY,QAAQ,SAAS;AACjD,UAAM,KAAK,cAAc,iBAAiB,WAAW,CAAC,EAAE;AAAA,EACzD;AACA,MAAI,QAAQ,gBAAgB;AAC3B,UAAM,KAAK,iBAAiB,QAAQ,eAAe,YAAY,CAAC,EAAE;AAAA,EACnE;AACA,QAAM,KAAK,IAAI;AACf,QAAM,KAAK,uCAAuC;AAClD,QAAM,KAAK,KAAK;AAChB,QAAM,KAAK,EAAE;AAGb,QAAM,iBAAiB,MAAM,KAAK,MAAM,OAAO,OAAO,CAAC,EAAE;AAAA,IACxD,CAAC,UAAU,MAAM,YAAY,SAAS;AAAA,EACvC;AAGA,QAAM,cAAc,CAAC,QAAQ;AAC7B,MAAI,gBAAgB;AACnB,gBAAY,KAAK,KAAK;AAAA,EACvB;AACA,QAAM,KAAK,YAAY,YAAY,KAAK,IAAI,CAAC,uBAAuB;AACpE,MAAI,QAAQ,YAAY,QAAQ,aAAa,YAAY;AACxD,UAAM,KAAK,2DAA2D;AAAA,EACvE;AACA,QAAM,KAAK,EAAE;AAGb,QAAM,KAAK,kCAAkC;AAG7C,QAAM,SAAS,MAAM,KAAK,MAAM,OAAO,OAAO,CAAC;AAC/C,QAAM,aAAa,OAAO,IAAI,CAAC,UAAU,kBAAkB,OAAO,OAAO,CAAC;AAC1E,QAAM,KAAK,WAAW,KAAK,OAAO,CAAC;AAEnC,QAAM,mBAAmB,4BAA4B,OAAO,OAAO;AACnE,MAAI,kBAAkB;AACrB,UAAM,KAAK,MAAM,gBAAgB,IAAI;AAAA,EACtC,OAAO;AACN,UAAM,KAAK,KAAK;AAAA,EACjB;AACA,QAAM,KAAK,EAAE;AAMb,QAAM,KAAK,0BAA0B;AACrC,QAAM,KAAK,EAAE;AAGb,MAAI,QAAQ,YAAY,QAAQ,aAAa,YAAY;AACxD,UAAM,KAAK,KAAK;AAChB,UAAM;AAAA,MACL,4DAA4D,QAAQ,QAAQ;AAAA,IAC7E;AACA,UAAM;AAAA,MACL;AAAA,IACD;AACA,UAAM,KAAK,IAAI;AACf,UAAM,KAAK,aAAa;AACxB,UAAM,KAAK,kBAAkB;AAC7B,UAAM,KAAK,4BAA4B;AACvC,UAAM,KAAK,6BAA6B;AACxC,UAAM;AAAA,MACL,uDAAuD,QAAQ,QAAQ;AAAA,IACxE;AACA,UAAM,KAAK,QAAQ;AACnB,UAAM,KAAK,QAAQ;AACnB,UAAM,KAAK,KAAK;AAChB,UAAM,KAAK,4BAA4B,QAAQ,QAAQ,aAAa;AACpE,UAAM,KAAK,EAAE;AAAA,EACd;AAEA,SAAO;AAAA,IACN,MAAM,MAAM,KAAK,IAAI;AAAA,IACrB;AAAA,EACD;AACD;","names":["code"]}
1
+ {"version":3,"sources":["../src/generators/schema-codegen.ts","../src/utils/db-utils.ts"],"sourcesContent":["/**\n * CLI-DDL: Schema Codegen Module\n *\n * Generates TypeScript schema files from IntrospectedModelIR.\n * Used by `dbsp introspect` to create dbsp.schema.ts from a database.\n */\n\nimport {\n\tcanGenerateCreateIndex,\n\tgenerateCreateIndex,\n\tidentityNaming,\n} from '@dbsp/adapter-pgsql';\nimport {\n\ttype ModelIR,\n\ttype TableIR,\n\tvalidateSchemaIndexOptions,\n} from '@dbsp/core';\nimport type { IndexIR } from '@dbsp/types';\nimport { redactDbUrl } from '../utils/db-utils.js';\n\n/**\n * Convert a snake_case string to camelCase.\n * @example 'author_id' → 'authorId'\n */\nfunction snakeToCamelCase(name: string): string {\n\treturn name.replace(/_([a-z])/g, (_, letter: string) => letter.toUpperCase());\n}\n\n/**\n * Options for schema code generation.\n */\nexport interface SchemaCodegenOptions {\n\t/** Source database URL (for comment header) */\n\treadonly sourceUrl?: string;\n\t/** Include original DB types as comments */\n\treadonly includeDbTypeComments?: boolean;\n\t/** Receive diagnostics as they are collected, including model warnings. */\n\treadonly onWarning?: (message: string) => void;\n\t/** Timestamp of introspection */\n\treadonly introspectedAt?: Date;\n\t/**\n\t * Database column casing convention.\n\t * When set to 'snake_case', column names are converted to camelCase\n\t * and a `dbCasing: 'snake_case'` comment is added to the generated code.\n\t * @default 'preserve'\n\t */\n\treadonly dbCasing?: 'snake_case' | 'camelCase' | 'preserve';\n}\n\nexport type SchemaCodegenDiagnosticsOptions = SchemaCodegenOptions;\n\nexport interface SchemaCodegenResult {\n\treadonly code: string;\n\treadonly warnings: readonly string[];\n}\n\n/**\n * Serialize a column default value to valid TypeScript source code.\n *\n * CODEX-11/CODEX-14: String(value) produced \"[object Object]\" for SQL-expression\n * defaults like { sql: 'now()' }, and single-quote wrapping without escape broke\n * strings containing quotes, backslashes, or newlines.\n *\n * Rules:\n * - null/undefined → 'null'\n * - number | boolean → unquoted literal (String(value))\n * - string → singleQuoteEscape (escapes \\, ', \\n, \\r, \\t — produces single-quoted TS literal)\n * - { sql: '...' } shape → `{ sql: ${JSON.stringify(sqlExpr)} }`\n * - anything else → throws (unrecognized shape; prevents silent [object Object])\n */\n\n/**\n * Wrap a string value in single quotes for TypeScript source output,\n * escaping any single quotes and backslashes contained in the value.\n *\n * Examples:\n * 'hello' → \"'hello'\"\n * \"O'Brien\" → \"'O\\\\'Brien'\"\n * 'C:\\\\Users' → \"'C:\\\\\\\\Users'\"\n */\nfunction singleQuoteEscape(s: string): string {\n\treturn `'${s\n\t\t.replace(/\\\\/g, '\\\\\\\\')\n\t\t.replace(/'/g, \"\\\\'\")\n\t\t.replace(/\\n/g, '\\\\n')\n\t\t.replace(/\\r/g, '\\\\r')\n\t\t.replace(/\\t/g, '\\\\t')}'`;\n}\n\n/**\n * Neutralize text that must remain inside generated block comments.\n *\n * Catalog-derived values are otherwise capable of closing `/* ... *\\/` comments\n * and turning the rest of the generated file into executable TypeScript.\n */\nfunction blockCommentText(s: string): string {\n\treturn s\n\t\t.replace(/\\*\\//g, '* /')\n\t\t.replace(/\\n/g, '\\\\n')\n\t\t.replace(/\\r/g, '\\\\r')\n\t\t.replace(/\\t/g, '\\\\t');\n}\n\n/**\n * Return the key suitable for use as a TypeScript object key.\n * Valid JS identifiers can be used bare; anything else is single-quote-wrapped.\n * A valid JS identifier starts with a letter, underscore, or $, followed by\n * letters, digits, underscores, or $.\n */\nfunction quoteKey(name: string): string {\n\tif (/^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(name)) {\n\t\treturn name;\n\t}\n\t// F3: Delegate to singleQuoteEscape() which also handles \\n, \\r, \\t.\n\t// The previous inline chain only escaped \\\\ and ' — identifiers containing\n\t// newlines, CR, or tabs would have produced invalid TypeScript output.\n\treturn singleQuoteEscape(name);\n}\n\nfunction emitDefault(d: unknown): string {\n\tif (d === null || d === undefined) return 'null';\n\tif (typeof d === 'number' || typeof d === 'boolean') return String(d);\n\tif (typeof d === 'string') return singleQuoteEscape(d);\n\tif (\n\t\ttypeof d === 'object' &&\n\t\t'sql' in d &&\n\t\ttypeof (d as { sql: unknown }).sql === 'string'\n\t) {\n\t\treturn `{ sql: ${singleQuoteEscape((d as { sql: string }).sql)} }`;\n\t}\n\tthrow new Error(\n\t\t`[schema-codegen] Unrecognized default shape: ${JSON.stringify(d)}`,\n\t);\n}\n\n/**\n * Generate column definition object code.\n */\nfunction generateColumnCode(\n\tcolumn: TableIR['columns'][number],\n\tisPrimaryKey: boolean,\n\tfkInfo:\n\t\t| {\n\t\t\t\ttable: string;\n\t\t\t\tcolumn?: string;\n\t\t\t\tnullable?: boolean;\n\t\t\t\tunique?: boolean;\n\t\t\t\tonDelete?: string;\n\t\t\t\tonUpdate?: string;\n\t\t\t\tisSelfRef?: boolean;\n\t\t }\n\t\t| undefined,\n\toptions: SchemaCodegenOptions,\n): string {\n\t// CODEX-13: FK + PK overlap — a column can be both FK and PK (e.g. shared-PK 1:1).\n\t// We must preserve isPrimaryKey even when fkInfo is present.\n\tif (fkInfo) {\n\t\treturn generateRefCode(column, fkInfo, isPrimaryKey, options);\n\t}\n\n\t// Check if we can use short form (just 'type' string)\n\tconst canUseShortForm =\n\t\t!isPrimaryKey &&\n\t\t!column.nullable &&\n\t\tcolumn.default === undefined &&\n\t\t!column.unique;\n\n\tif (canUseShortForm) {\n\t\t// Short form: 'type'\n\t\tlet code = singleQuoteEscape(column.type);\n\t\tif (options.includeDbTypeComments && column.originalDbType) {\n\t\t\tcode += ` /* from: ${blockCommentText(column.originalDbType)} */`;\n\t\t}\n\t\treturn code;\n\t}\n\n\t// Long form: { type, primaryKey?, nullable?, default?, unique? }\n\tconst props: string[] = [];\n\tprops.push(`type: ${singleQuoteEscape(column.type)}`);\n\n\tif (isPrimaryKey) {\n\t\tprops.push('primaryKey: true');\n\t}\n\n\tif (column.nullable) {\n\t\tprops.push('nullable: true');\n\t}\n\n\tif (column.default !== undefined) {\n\t\tprops.push(`default: ${emitDefault(column.default)}`);\n\t}\n\n\tif (column.unique) {\n\t\tprops.push('unique: true');\n\t}\n\n\tlet code = `{ ${props.join(', ')} }`;\n\n\tif (options.includeDbTypeComments && column.originalDbType) {\n\t\tcode += ` /* from: ${blockCommentText(column.originalDbType)} */`;\n\t}\n\n\treturn code;\n}\n\n/**\n * Generate ref() call for a foreign key column.\n * ARCH-005: Use ref() instead of references: { table, column }\n */\nfunction generateRefCode(\n\tcolumn: TableIR['columns'][number],\n\tfkInfo: {\n\t\ttable: string;\n\t\tcolumn?: string;\n\t\tnullable?: boolean;\n\t\tunique?: boolean;\n\t\tonDelete?: string;\n\t\tonUpdate?: string;\n\t\tisSelfRef?: boolean;\n\t},\n\tisPrimaryKey: boolean,\n\toptions: SchemaCodegenOptions,\n): string {\n\tconst refOptions: string[] = [];\n\n\t// C2: emit references option for non-PK FK target columns.\n\t// Now that buildRefColumn plumbs options.references into ModelIR,\n\t// emitting ref('table', { references: ['col'] }) round-trips correctly.\n\t// L1-followup-4: apply snake→camel transform to the target column name when\n\t// dbCasing='snake_case' so the generated references array round-trips correctly.\n\tif (fkInfo.column) {\n\t\tconst refColName =\n\t\t\toptions.dbCasing === 'snake_case'\n\t\t\t\t? snakeToCamelCase(fkInfo.column)\n\t\t\t\t: fkInfo.column;\n\t\trefOptions.push(`references: [${singleQuoteEscape(refColName)}]`);\n\t}\n\n\t// CODEX-13: FK column that is also PK — emit isPrimaryKey inside ref() options\n\tif (isPrimaryKey) {\n\t\trefOptions.push('isPrimaryKey: true');\n\t}\n\n\t// Nullable FK\n\tif (fkInfo.nullable || column.nullable) {\n\t\trefOptions.push('nullable: true');\n\t}\n\n\t// Unique FK → hasOne (1:1 relation)\n\tif (fkInfo.unique || column.unique) {\n\t\trefOptions.push('unique: true');\n\t}\n\n\t// onDelete action\n\tif (fkInfo.onDelete && fkInfo.onDelete !== 'NO ACTION') {\n\t\trefOptions.push(`onDelete: ${singleQuoteEscape(fkInfo.onDelete)}`);\n\t}\n\n\t// CODEX-12: onUpdate action\n\tif (fkInfo.onUpdate && fkInfo.onUpdate !== 'NO ACTION') {\n\t\trefOptions.push(`onUpdate: ${singleQuoteEscape(fkInfo.onUpdate)}`);\n\t}\n\n\t// Self-referential FK needs roles\n\tif (fkInfo.isSelfRef) {\n\t\t// Infer role names from column name\n\t\t// e.g., 'parentId' → parent: 'parent', children: 'children'\n\t\tconst baseName = column.name.replace(/_?[iI]d$/, '');\n\t\tconst childrenName = baseName === 'parent' ? 'children' : `${baseName}s`;\n\t\trefOptions.push(\n\t\t\t`roles: { parent: ${singleQuoteEscape(baseName)}, children: ${singleQuoteEscape(childrenName)} }`,\n\t\t);\n\t}\n\n\t// Build the ref() call — table name converted if dbCasing applies.\n\t// Route through singleQuoteEscape so a table name containing a single quote,\n\t// backslash, or newline produces valid TypeScript instead of a syntax error.\n\tconst refTable =\n\t\toptions.dbCasing === 'snake_case'\n\t\t\t? snakeToCamelCase(fkInfo.table)\n\t\t\t: fkInfo.table;\n\tlet code: string;\n\tif (refOptions.length === 0) {\n\t\tcode = `ref(${singleQuoteEscape(refTable)})`;\n\t} else {\n\t\tcode = `ref(${singleQuoteEscape(refTable)}, { ${refOptions.join(', ')} })`;\n\t}\n\n\t// Add comment for original DB type if requested\n\tif (options.includeDbTypeComments && column.originalDbType) {\n\t\tcode += ` /* from: ${blockCommentText(column.originalDbType)} */`;\n\t}\n\n\treturn code;\n}\n\n/**\n * Generate table definition code.\n */\nfunction generateTableCode(\n\ttable: TableIR,\n\toptions: SchemaCodegenOptions,\n): string {\n\t// ARCH-005: Build FK info map with extended properties\n\tconst fkMap = new Map<\n\t\tstring,\n\t\t{\n\t\t\ttable: string;\n\t\t\tcolumn?: string;\n\t\t\tnullable?: boolean;\n\t\t\tunique?: boolean;\n\t\t\tonDelete?: string;\n\t\t\tonUpdate?: string;\n\t\t\tisSelfRef?: boolean;\n\t\t}\n\t>();\n\n\tfor (const fk of table.foreignKeys) {\n\t\tconst localCol = fk.columns[0];\n\t\tconst refCol = fk.references.columns[0];\n\t\tif (\n\t\t\tfk.columns.length === 1 &&\n\t\t\tfk.references.columns.length === 1 &&\n\t\t\tfk.references.schema === undefined &&\n\t\t\tlocalCol &&\n\t\t\trefCol\n\t\t) {\n\t\t\t// Find the column to check nullable/unique\n\t\t\t// FK column names may be snake_case (from raw SQL) while column.name\n\t\t\t// is camelCase (when CamelCasePlugin is active)\n\t\t\tconst colDef = table.columns.find(\n\t\t\t\t(c) => c.name === localCol || c.name === snakeToCamelCase(localCol),\n\t\t\t);\n\n\t\t\tconst entry: {\n\t\t\t\ttable: string;\n\t\t\t\tcolumn?: string;\n\t\t\t\tnullable?: boolean;\n\t\t\t\tunique?: boolean;\n\t\t\t\tonDelete?: string;\n\t\t\t\tonUpdate?: string;\n\t\t\t\tisSelfRef?: boolean;\n\t\t\t} = {\n\t\t\t\ttable: fk.references.table,\n\t\t\t\tisSelfRef:\n\t\t\t\t\tfk.references.schema === undefined &&\n\t\t\t\t\tfk.references.table === table.name,\n\t\t\t};\n\n\t\t\t// Only include column if not 'id' (the default)\n\t\t\tif (refCol !== 'id') {\n\t\t\t\tentry.column = refCol;\n\t\t\t}\n\n\t\t\t// Include nullable if true\n\t\t\tif (colDef?.nullable) {\n\t\t\t\tentry.nullable = true;\n\t\t\t}\n\n\t\t\t// Include unique if true\n\t\t\tif (colDef?.unique) {\n\t\t\t\tentry.unique = true;\n\t\t\t}\n\n\t\t\t// Include onDelete if not the default\n\t\t\tif (fk.onDelete && fk.onDelete !== 'NO ACTION') {\n\t\t\t\tentry.onDelete = fk.onDelete;\n\t\t\t}\n\n\t\t\t// CODEX-12: Include onUpdate if not the default\n\t\t\tif (fk.onUpdate && fk.onUpdate !== 'NO ACTION') {\n\t\t\t\tentry.onUpdate = fk.onUpdate;\n\t\t\t}\n\n\t\t\t// Store under both snake_case and camelCase keys so fkMap.get(col.name) works\n\t\t\t// regardless of naming convention\n\t\t\tfkMap.set(localCol, entry);\n\t\t\tconst camelCol = snakeToCamelCase(localCol);\n\t\t\tif (camelCol !== localCol) {\n\t\t\t\tfkMap.set(camelCol, entry);\n\t\t\t}\n\t\t}\n\t}\n\n\tconst shouldCamelCase = options.dbCasing === 'snake_case';\n\n\tconst columnLines = table.columns.map((col) => {\n\t\tconst isPrimaryKey = table.primaryKey\n\t\t\t? typeof table.primaryKey === 'string'\n\t\t\t\t? col.name === table.primaryKey\n\t\t\t\t: table.primaryKey.includes(col.name)\n\t\t\t: false;\n\t\tconst fkInfo = fkMap.get(col.name);\n\t\tconst code = generateColumnCode(col, isPrimaryKey, fkInfo, options);\n\t\tconst rawColName = shouldCamelCase ? snakeToCamelCase(col.name) : col.name;\n\t\t// C7: quote the key if it's not a valid JS identifier (e.g. contains hyphens)\n\t\tconst colKey = quoteKey(rawColName);\n\t\treturn `\\t\\t${colKey}: ${code}`;\n\t});\n\n\tconst rawTableName = shouldCamelCase\n\t\t? snakeToCamelCase(table.name)\n\t\t: table.name;\n\t// C7: quote the table key if it's not a valid JS identifier\n\tconst tableKey = quoteKey(rawTableName);\n\treturn `\\t${tableKey}: {\\n${columnLines.join(',\\n')},\\n\\t}`;\n}\n\nfunction generatedName(name: string, options: SchemaCodegenOptions): string {\n\treturn options.dbCasing === 'snake_case' ? snakeToCamelCase(name) : name;\n}\n\nfunction stringArrayLiteral(values: readonly string[]): string {\n\treturn `[${values.map(singleQuoteEscape).join(', ')}]`;\n}\n\n/**\n * Emit a strict boolean literal. Interpolating the value itself would run a\n * foreign object's toString into the generated source, which is later imported.\n */\nfunction booleanLiteral(value: unknown): 'true' | 'false' {\n\treturn value === true ? 'true' : 'false';\n}\n\nfunction stringRecordLiteral(\n\tvalues: Readonly<Record<string, string>>,\n\tkeyName: (name: string) => string = (name) => name,\n): string {\n\tconst entries = Object.entries(values).map(\n\t\t([key, value]) =>\n\t\t\t`[${singleQuoteEscape(keyName(key))}]: ${singleQuoteEscape(value)}`,\n\t);\n\treturn `{ ${entries.join(', ')} }`;\n}\n\nfunction tableLevelForeignKeys(table: TableIR): TableIR['foreignKeys'] {\n\treturn table.foreignKeys.filter(\n\t\t(fk) =>\n\t\t\tfk.references.schema !== undefined ||\n\t\t\tfk.columns.length > 1 ||\n\t\t\tfk.references.columns.length > 1,\n\t);\n}\n\nfunction hasExpressions(idx: IndexIR): boolean {\n\treturn (idx.expressions?.length ?? 0) > 0;\n}\n\nfunction tableLevelIndexes(table: TableIR): readonly IndexIR[] {\n\treturn table.indexes.filter(\n\t\t(idx) => indexRepresentationWarning(table.name, idx) === undefined,\n\t);\n}\n\nfunction warningName(name: string | undefined): string {\n\treturn name === undefined ? '<unnamed>' : JSON.stringify(name);\n}\n\nfunction errorMessage(error: unknown): string {\n\treturn error instanceof Error ? error.message : String(error);\n}\n\nfunction indexRepresentationWarning(\n\ttableName: string,\n\tidx: IndexIR,\n): string | undefined {\n\tif (hasExpressions(idx)) {\n\t\treturn `Expression index ${warningName(idx.name)} on table ${warningName(tableName)} cannot be represented in the schema and is not managed by dbsp. dbsp will neither drop nor recreate it; maintain it by hand.`;\n\t}\n\tconst schemaWarning = schemaIndexRepresentationWarning(tableName, idx);\n\tconst ddlWarning = ddlIndexRepresentationWarning(tableName, idx);\n\treturn schemaWarning ?? ddlWarning;\n}\n\nfunction ddlIndexRepresentationWarning(\n\ttableName: string,\n\tidx: IndexIR,\n): string | undefined {\n\tif (canGenerateCreateIndex(tableName, idx, undefined, identityNaming)) {\n\t\treturn undefined;\n\t}\n\ttry {\n\t\tgenerateCreateIndex(tableName, idx, undefined, identityNaming);\n\t} catch (error) {\n\t\treturn `Index ${warningName(idx.name)} on table ${warningName(tableName)} cannot be represented in the schema and is not managed by dbsp because the DDL emitter rejected it: ${errorMessage(error)}. dbsp will neither drop nor recreate it; maintain it by hand.`;\n\t}\n\treturn undefined;\n}\n\nfunction schemaIndexRepresentationWarning(\n\ttableName: string,\n\tidx: IndexIR,\n): string | undefined {\n\ttry {\n\t\tvalidateSchemaIndexOptions(tableName, idx);\n\t} catch (error) {\n\t\treturn `Index ${warningName(idx.name)} on table ${warningName(tableName)} cannot be represented in the schema and is not managed by dbsp because schema() rejected it: ${errorMessage(error)}. dbsp will neither drop nor recreate it; maintain it by hand.`;\n\t}\n\treturn undefined;\n}\n\nfunction schemaCodegenWarnings(model: ModelIR): string[] {\n\tconst warnings: string[] = [];\n\tfor (const table of model.tables.values()) {\n\t\tfor (const idx of table.indexes) {\n\t\t\tconst warning = indexRepresentationWarning(table.name, idx);\n\t\t\tif (warning !== undefined) {\n\t\t\t\twarnings.push(warning);\n\t\t\t}\n\t\t}\n\t}\n\treturn warnings;\n}\n\nfunction modelWarnings(model: ModelIR): readonly string[] {\n\tconst warnings = (model as ModelIR & { readonly warnings?: unknown })\n\t\t.warnings;\n\treturn Array.isArray(warnings) && warnings.every((w) => typeof w === 'string')\n\t\t? warnings\n\t\t: [];\n}\n\nfunction generateIndexCode(\n\tidx: IndexIR,\n\toptions: SchemaCodegenOptions,\n): string {\n\tconst indexOptions = [\n\t\t`columns: ${stringArrayLiteral(idx.columns.map((col) => generatedName(col, options)))}`,\n\t];\n\tif (idx.unique !== undefined) {\n\t\tindexOptions.push(`unique: ${booleanLiteral(idx.unique)}`);\n\t}\n\tif (idx.name !== undefined) {\n\t\tindexOptions.push(`name: ${singleQuoteEscape(idx.name)}`);\n\t}\n\tif (idx.method !== undefined) {\n\t\tindexOptions.push(`method: ${singleQuoteEscape(idx.method)}`);\n\t}\n\tif (idx.where !== undefined) {\n\t\tindexOptions.push(`where: ${singleQuoteEscape(idx.where)}`);\n\t}\n\tif (idx.opclass !== undefined) {\n\t\tindexOptions.push(\n\t\t\t`opclass: ${stringRecordLiteral(idx.opclass, (key) => generatedName(key, options))}`,\n\t\t);\n\t}\n\tif (idx.with !== undefined) {\n\t\tindexOptions.push(`with: ${stringRecordLiteral(idx.with)}`);\n\t}\n\tif (idx.include !== undefined) {\n\t\tindexOptions.push(\n\t\t\t`include: ${stringArrayLiteral(idx.include.map((col) => generatedName(col, options)))}`,\n\t\t);\n\t}\n\tif (idx.nullsNotDistinct !== undefined) {\n\t\tindexOptions.push(\n\t\t\t`nullsNotDistinct: ${booleanLiteral(idx.nullsNotDistinct)}`,\n\t\t);\n\t}\n\treturn `\\t\\t\\t{ ${indexOptions.join(', ')} }`;\n}\n\nfunction generateTableConstraintCode(\n\tmodel: ModelIR,\n\toptions: SchemaCodegenOptions,\n): string | undefined {\n\tconst tableBlocks: string[] = [];\n\tfor (const table of model.tables.values()) {\n\t\tconst fks = tableLevelForeignKeys(table);\n\t\tconst indexes = tableLevelIndexes(table);\n\t\tif (fks.length === 0 && indexes.length === 0) continue;\n\t\tconst tableOptions: string[] = [];\n\t\tif (indexes.length > 0) {\n\t\t\tconst indexLines = indexes.map((idx) => generateIndexCode(idx, options));\n\t\t\ttableOptions.push(`\\t\\tindexes: [\\n${indexLines.join(',\\n')}\\n\\t\\t]`);\n\t\t}\n\t\tif (fks.length > 0) {\n\t\t\tconst fkLines = fks.map((fk) => {\n\t\t\t\tconst refOptions = [\n\t\t\t\t\t...(fk.references.schema !== undefined\n\t\t\t\t\t\t? [`schema: ${singleQuoteEscape(fk.references.schema)}`]\n\t\t\t\t\t\t: []),\n\t\t\t\t\t`columns: ${stringArrayLiteral(fk.columns.map((col) => generatedName(col, options)))}`,\n\t\t\t\t\t`references: ${stringArrayLiteral(fk.references.columns.map((col) => generatedName(col, options)))}`,\n\t\t\t\t];\n\t\t\t\tif (fk.onDelete && fk.onDelete !== 'NO ACTION') {\n\t\t\t\t\trefOptions.push(`onDelete: ${singleQuoteEscape(fk.onDelete)}`);\n\t\t\t\t}\n\t\t\t\tif (fk.onUpdate && fk.onUpdate !== 'NO ACTION') {\n\t\t\t\t\trefOptions.push(`onUpdate: ${singleQuoteEscape(fk.onUpdate)}`);\n\t\t\t\t}\n\t\t\t\treturn `\\t\\t\\tref(${singleQuoteEscape(generatedName(fk.references.table, options))}, { ${refOptions.join(', ')} })`;\n\t\t\t});\n\t\t\ttableOptions.push(`\\t\\tforeignKeys: [\\n${fkLines.join(',\\n')}\\n\\t\\t]`);\n\t\t}\n\t\ttableBlocks.push(\n\t\t\t`\\t${quoteKey(generatedName(table.name, options))}: {\\n${tableOptions.join(',\\n')}\\n\\t}`,\n\t\t);\n\t}\n\tif (tableBlocks.length === 0) return undefined;\n\treturn `{\\n${tableBlocks.join(',\\n\\n')}\\n}`;\n}\n\n/**\n * Generate a TypeScript schema file from ModelIR and return diagnostics.\n *\n * @param model - The ModelIR (or IntrospectedModelIR) to generate from\n * @param options - Code generation options\n * @returns Generated source code and warnings\n */\nexport function generateSchemaFileWithDiagnostics(\n\tmodel: ModelIR,\n\toptions: SchemaCodegenDiagnosticsOptions = {},\n): SchemaCodegenResult {\n\tconst warnings = [...modelWarnings(model), ...schemaCodegenWarnings(model)];\n\tfor (const warning of warnings) {\n\t\toptions.onWarning?.(warning);\n\t}\n\n\tconst lines: string[] = [];\n\n\t// Header comment\n\tlines.push('/**');\n\tlines.push(' * Auto-generated by: dbsp introspect');\n\tif (options.sourceUrl) {\n\t\tconst redactedUrl = redactDbUrl(options.sourceUrl);\n\t\tlines.push(` * Source: ${blockCommentText(redactedUrl)}`);\n\t}\n\tif (options.introspectedAt) {\n\t\tlines.push(` * Generated: ${options.introspectedAt.toISOString()}`);\n\t}\n\tlines.push(' *');\n\tlines.push(' * Review before using in production.');\n\tlines.push(' */');\n\tlines.push('');\n\n\t// ARCH-005: Check if any table has FKs to determine imports\n\tconst hasForeignKeys = Array.from(model.tables.values()).some(\n\t\t(table) => table.foreignKeys.length > 0,\n\t);\n\n\t// Imports for the live schema() + ref() authoring API.\n\tconst coreImports = ['schema'];\n\tif (hasForeignKeys) {\n\t\tcoreImports.push('ref');\n\t}\n\tlines.push(`import { ${coreImports.join(', ')} } from '@dbsp/core';`);\n\tif (options.dbCasing && options.dbCasing !== 'preserve') {\n\t\tlines.push(\"import { createPgsqlAdapter } from '@dbsp/adapter-pgsql';\");\n\t}\n\tlines.push('');\n\n\t// Schema definition.\n\tlines.push('export const dbSchema = schema({');\n\n\t// Generate each table\n\tconst tables = Array.from(model.tables.values());\n\tconst tableLines = tables.map((table) => generateTableCode(table, options));\n\tlines.push(tableLines.join(',\\n\\n'));\n\n\tconst tableConstraints = generateTableConstraintCode(model, options);\n\tif (tableConstraints) {\n\t\tlines.push(`}, ${tableConstraints});`);\n\t} else {\n\t\tlines.push('});');\n\t}\n\tlines.push('');\n\n\t// Add a default export so schema-loader can load generated files directly.\n\t// schema-loader accepts module.schema || module.default; the named dbSchema\n\t// export is the canonical API, but the default export allows `loadSchema()`\n\t// to round-trip without the caller knowing the internal export name.\n\tlines.push('export default dbSchema;');\n\tlines.push('');\n\n\t// Usage hint with dbCasing\n\tif (options.dbCasing && options.dbCasing !== 'preserve') {\n\t\tlines.push('/**');\n\t\tlines.push(\n\t\t\t` * Usage: columns above are camelCase; the database uses ${options.dbCasing}.`,\n\t\t);\n\t\tlines.push(\n\t\t\t' * Pass dbCasing to the adapter so it maps camelCase ↔ snake_case automatically.',\n\t\t);\n\t\tlines.push(' *');\n\t\tlines.push(' * @example');\n\t\tlines.push(' * ```typescript');\n\t\tlines.push(' * const orm = createOrm({');\n\t\tlines.push(' * model: dbSchema.model,');\n\t\tlines.push(\n\t\t\t` * adapter: createPgsqlAdapter(pool, { dbCasing: '${options.dbCasing}' }),`,\n\t\t);\n\t\tlines.push(' * });');\n\t\tlines.push(' * ```');\n\t\tlines.push(' */');\n\t\tlines.push(`export const dbCasing = '${options.dbCasing}' as const;`);\n\t\tlines.push('');\n\t}\n\n\treturn {\n\t\tcode: lines.join('\\n'),\n\t\twarnings,\n\t};\n}\n","/**\n * Shared database utilities for CLI commands.\n *\n * E09: Extracted from verify.ts and introspect.ts to DRY up code.\n * E09b: Shared URL redaction for secure logging.\n */\n\n/**\n * Create a database connection pool.\n * pg is an optional peer dependency.\n *\n * @param connectionUrl - PostgreSQL connection URL\n * @returns Pool instance\n * @throws Error if pg is not installed\n */\nexport async function createDbConnection(connectionUrl: string) {\n\tlet pg: typeof import('pg').default;\n\ttry {\n\t\tconst mod = await import('pg');\n\t\tpg = mod.default;\n\t} catch {\n\t\tthrow new Error(\n\t\t\t'pg is required for this command. Install it with: pnpm add pg',\n\t\t);\n\t}\n\n\tconst pool = new pg.Pool({\n\t\tconnectionString: connectionUrl,\n\t});\n\treturn { pool };\n}\n\n/**\n * Redact password from a database connection URL for safe logging.\n *\n * @param url - Database connection URL (e.g., postgres://user:pass@host/db)\n * @returns URL with password replaced by ***\n *\n * @example\n * redactDbUrl('postgres://user:secret@localhost/mydb')\n * // => 'postgres://user:***@localhost/mydb'\n */\nexport function redactDbUrl(url: string): string {\n\ttry {\n\t\tconst u = new URL(url);\n\t\tif (u.password) {\n\t\t\tu.password = '***';\n\t\t}\n\t\treturn u.toString();\n\t} catch {\n\t\t// Fall back to regex for non-standard or malformed URLs\n\t\treturn url.replace(/:[^:@]+@/, ':***@');\n\t}\n}\n"],"mappings":"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","names":["code"]}
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  generateSchemaFileWithDiagnostics
3
- } from "../chunk-VUNVYYOZ.js";
3
+ } from "../chunk-G6NS4BAV.js";
4
4
  export {
5
5
  generateSchemaFileWithDiagnostics
6
6
  };
package/dist/index.js CHANGED
@@ -3,7 +3,7 @@ import {
3
3
  createDbConnection,
4
4
  generateSchemaFileWithDiagnostics,
5
5
  redactDbUrl
6
- } from "./chunk-VUNVYYOZ.js";
6
+ } from "./chunk-G6NS4BAV.js";
7
7
  import {
8
8
  loadSchema,
9
9
  loadSchemaFromCwd
@@ -52,14 +52,14 @@ var generateCommand = new Command("generate").description("Generate code from sc
52
52
  const log = useStdout ? console.error : console.log;
53
53
  log(`\u{1F4C4} Loaded schema from: ${schemaPath}`);
54
54
  const dialect = options.dialect ?? "postgresql";
55
- if (dialect !== "postgresql") {
55
+ if (!isDialectSupported(dialect)) {
56
56
  console.error(
57
57
  `\u26A0\uFE0F Warning: Only 'postgresql' dialect is currently supported. Using postgresql.`
58
58
  );
59
59
  }
60
60
  switch (target) {
61
61
  case "ddl": {
62
- const dbCasing = mapGenerateCasingOption(options.casing) ?? schema.dbCasing ?? "snake_case";
62
+ const dbCasing = mapCasingToDbCasing(options.casing) ?? schema.dbCasing ?? "snake_case";
63
63
  const casingLabel = formatGenerateCasingLabel(dbCasing);
64
64
  const { createPgsqlCompileOnlyAdapter } = await import("@dbsp/adapter-pgsql");
65
65
  const adapter = createPgsqlCompileOnlyAdapter({
@@ -102,7 +102,10 @@ var generateCommand = new Command("generate").description("Generate code from sc
102
102
  }
103
103
  }
104
104
  );
105
- function mapGenerateCasingOption(casing) {
105
+ function isDialectSupported(dialect) {
106
+ return dialect === "postgresql";
107
+ }
108
+ function mapCasingToDbCasing(casing) {
106
109
  switch (casing) {
107
110
  case "snake":
108
111
  return "snake_case";
@@ -343,6 +346,218 @@ function hasExecutableSql(stmt) {
343
346
  return t.length > 0 && !t.startsWith("--");
344
347
  });
345
348
  }
349
+ var defaultMigrateDeps = {
350
+ withMigratePool,
351
+ ensureMigrationsTable,
352
+ withMigrationLock,
353
+ getAppliedMigrations,
354
+ scanMigrationFiles,
355
+ parseMigrationFile,
356
+ getNextSchemaVersion,
357
+ recordMigration,
358
+ removeMigrationRecord,
359
+ isDestructiveDown
360
+ };
361
+ function resolveMigrateDeps(deps) {
362
+ return { ...defaultMigrateDeps, ...deps };
363
+ }
364
+ async function runApply(options, deps) {
365
+ const d = resolveMigrateDeps(deps);
366
+ console.log("\u{1F504} Applying migrations");
367
+ console.log(` Database: ${redactDbUrl(options.db)}`);
368
+ console.log(` Directory: ${options.dir}`);
369
+ console.log("");
370
+ await d.withMigratePool(options.db, async (pool) => {
371
+ await d.ensureMigrationsTable(pool);
372
+ await d.withMigrationLock(pool, async (client) => {
373
+ const applied = await d.getAppliedMigrations(client);
374
+ const appliedMap = new Map(applied.map((m) => [m.name, m.checksum]));
375
+ const files = d.scanMigrationFiles(options.dir);
376
+ if (files.length === 0) {
377
+ console.log(`No migration files found in ${options.dir}`);
378
+ return;
379
+ }
380
+ for (const file of files) {
381
+ const existingChecksum = appliedMap.get(file.name);
382
+ if (existingChecksum !== void 0 && existingChecksum !== file.checksum) {
383
+ throw new MigrationError(
384
+ `Checksum mismatch for ${file.name}
385
+ Expected: ${existingChecksum}
386
+ Got: ${file.checksum}
387
+
388
+ Migration file has been tampered with after being applied.`
389
+ );
390
+ }
391
+ }
392
+ const pending = files.filter((f) => !appliedMap.has(f.name));
393
+ if (pending.length === 0) {
394
+ console.log("\u2705 All migrations already applied.");
395
+ return;
396
+ }
397
+ if (options.dryRun) {
398
+ console.log(`${pending.length} pending migration(s):`);
399
+ for (const file of pending) {
400
+ console.log(` - ${file.name}`);
401
+ }
402
+ return;
403
+ }
404
+ let appliedCount = 0;
405
+ for (const file of pending) {
406
+ console.log(` Applying: ${file.name}...`);
407
+ const parsed = d.parseMigrationFile(file.content);
408
+ const statements = parsed.upStatements.filter(hasExecutableSql);
409
+ const version = await d.getNextSchemaVersion(client);
410
+ const destructive = parsed.destructive === true;
411
+ try {
412
+ await client.query("BEGIN");
413
+ for (const stmt of statements) {
414
+ await client.query(stmt);
415
+ }
416
+ await d.recordMigration(
417
+ client,
418
+ file.name,
419
+ file.checksum,
420
+ version,
421
+ destructive
422
+ );
423
+ await client.query("COMMIT");
424
+ } catch (applyError) {
425
+ let rollbackError;
426
+ try {
427
+ await client.query("ROLLBACK");
428
+ } catch (e) {
429
+ rollbackError = e;
430
+ }
431
+ const primary = sanitizePgError(applyError);
432
+ if (rollbackError !== void 0) {
433
+ console.error(
434
+ ` Note: rollback also failed: ${sanitizePgError(rollbackError).message}`
435
+ );
436
+ }
437
+ throw primary;
438
+ }
439
+ appliedCount++;
440
+ console.log(` \u2705 Applied: ${file.name}`);
441
+ }
442
+ console.log(`
443
+ \u2705 ${appliedCount} migration(s) applied successfully.`);
444
+ });
445
+ });
446
+ }
447
+ async function runRollback(options, deps) {
448
+ const d = resolveMigrateDeps(deps);
449
+ console.log(`\u23EA Rolling back ${options.count} migration(s)`);
450
+ console.log(` Database: ${redactDbUrl(options.db)}`);
451
+ console.log(` Directory: ${options.dir}`);
452
+ console.log("");
453
+ await d.withMigratePool(options.db, async (pool) => {
454
+ await d.ensureMigrationsTable(pool);
455
+ await d.withMigrationLock(pool, async (client) => {
456
+ const applied = await d.getAppliedMigrations(client);
457
+ const sortedDesc = [...applied].sort(
458
+ (a, b) => b.name.localeCompare(a.name)
459
+ );
460
+ if (options.count > sortedDesc.length) {
461
+ throw new MigrationError(
462
+ `Cannot roll back ${options.count} migration(s) \u2014 only ${sortedDesc.length} applied`
463
+ );
464
+ }
465
+ const toRollback = sortedDesc.slice(0, options.count);
466
+ const files = d.scanMigrationFiles(options.dir);
467
+ const fileMap = new Map(files.map((f) => [f.name, f]));
468
+ let rolledBack = 0;
469
+ for (const record of toRollback) {
470
+ const file = fileMap.get(record.name);
471
+ if (!file) {
472
+ throw new MigrationError(
473
+ `Migration file not found on disk: ${record.name}`
474
+ );
475
+ }
476
+ if (file.checksum !== record.checksum) {
477
+ throw new MigrationError(
478
+ `Checksum mismatch for ${record.name}
479
+ Expected: ${record.checksum}
480
+ Got: ${file.checksum}
481
+
482
+ Migration file has been modified since it was applied.`
483
+ );
484
+ }
485
+ const parsed = d.parseMigrationFile(file.content);
486
+ if (!parsed.hasDown) {
487
+ throw new MigrationError(
488
+ `Migration ${record.name} has no DOWN section
489
+ Cannot roll back a migration without a DOWN section.`
490
+ );
491
+ }
492
+ const downStmts = parsed.downStatements.filter(hasExecutableSql);
493
+ if (downStmts.length === 0 && !options.force) {
494
+ throw new MigrationError(
495
+ `Migration ${record.name} has an empty DOWN section
496
+ Use --force to roll back anyway.`
497
+ );
498
+ }
499
+ const safeHeaderContradictedByDown = parsed.destructive === false && d.isDestructiveDown(downStmts);
500
+ if ((parsed.destructive !== false || safeHeaderContradictedByDown) && !options.force) {
501
+ if (parsed.destructive === true) {
502
+ throw new MigrationError(
503
+ `Migration ${record.name} has destructive DOWN operations
504
+ Use --force to proceed with destructive rollback.`
505
+ );
506
+ }
507
+ if (safeHeaderContradictedByDown) {
508
+ throw new MigrationError(
509
+ `Migration ${record.name} is marked non-destructive but its DOWN section contains an obvious destructive statement
510
+ Re-generate the migration with accurate dbsp metadata or pass --force.`
511
+ );
512
+ }
513
+ throw new MigrationError(
514
+ `Migration ${record.name} is unmarked or legacy
515
+ Re-generate the migration with dbsp metadata or pass --force.`
516
+ );
517
+ }
518
+ if (downStmts.length > 0 || options.force) {
519
+ console.log(` Rolling back: ${record.name}...`);
520
+ }
521
+ try {
522
+ await client.query("BEGIN");
523
+ for (const stmt of downStmts) {
524
+ await client.query(stmt);
525
+ }
526
+ await d.removeMigrationRecord(client, record.name);
527
+ await client.query("COMMIT");
528
+ } catch (rollbackError) {
529
+ let rollbackCleanupError;
530
+ try {
531
+ await client.query("ROLLBACK");
532
+ } catch (e) {
533
+ rollbackCleanupError = e;
534
+ }
535
+ const primary = sanitizePgError(rollbackError);
536
+ if (rollbackCleanupError !== void 0) {
537
+ console.error(
538
+ ` Note: rollback also failed: ${sanitizePgError(rollbackCleanupError).message}`
539
+ );
540
+ }
541
+ throw primary;
542
+ }
543
+ rolledBack++;
544
+ console.log(` \u2705 Rolled back: ${record.name}`);
545
+ }
546
+ console.log(`
547
+ \u2705 ${rolledBack} migration(s) rolled back successfully.`);
548
+ });
549
+ });
550
+ }
551
+ function runRollbackCommand(countArg, options) {
552
+ const count = Number.parseInt(countArg, 10);
553
+ if (Number.isNaN(count) || count < 1) {
554
+ console.error("\u274C Count must be a positive integer");
555
+ process.exit(1);
556
+ }
557
+ return runMigrateAction(async () => {
558
+ await runRollback({ count, ...options });
559
+ });
560
+ }
346
561
  var devCommand = new Command3("dev").description("Generate a migration from schema changes").option(
347
562
  "-s, --schema <path>",
348
563
  "Path to schema file (default: dbsp.schema.ts)"
@@ -403,211 +618,10 @@ var devCommand = new Command3("dev").description("Generate a migration from sche
403
618
  );
404
619
  var applyCommand = new Command3("apply").description("Apply pending migrations").requiredOption("-d, --db <url>", "Database connection URL (required)").option("--dir <path>", "Migrations directory", DEFAULT_MIGRATIONS_DIR).option("--dry-run", "Show pending migrations without applying").action(
405
620
  (options) => runMigrateAction(async () => {
406
- console.log("\u{1F504} Applying migrations");
407
- console.log(` Database: ${redactDbUrl(options.db)}`);
408
- console.log(` Directory: ${options.dir}`);
409
- console.log("");
410
- await withMigratePool(options.db, async (pool) => {
411
- await ensureMigrationsTable(pool);
412
- await withMigrationLock(pool, async (client) => {
413
- const applied = await getAppliedMigrations(client);
414
- const appliedMap = new Map(applied.map((m) => [m.name, m.checksum]));
415
- const files = scanMigrationFiles(options.dir);
416
- if (files.length === 0) {
417
- console.log(`No migration files found in ${options.dir}`);
418
- return;
419
- }
420
- for (const file of files) {
421
- const existingChecksum = appliedMap.get(file.name);
422
- if (existingChecksum !== void 0 && existingChecksum !== file.checksum) {
423
- throw new MigrationError(
424
- `Checksum mismatch for ${file.name}
425
- Expected: ${existingChecksum}
426
- Got: ${file.checksum}
427
-
428
- Migration file has been tampered with after being applied.`
429
- );
430
- }
431
- }
432
- const pending = files.filter((f) => !appliedMap.has(f.name));
433
- if (pending.length === 0) {
434
- console.log("\u2705 All migrations already applied.");
435
- return;
436
- }
437
- if (options.dryRun) {
438
- console.log(`${pending.length} pending migration(s):`);
439
- for (const file of pending) {
440
- console.log(` - ${file.name}`);
441
- }
442
- return;
443
- }
444
- let appliedCount = 0;
445
- for (const file of pending) {
446
- console.log(` Applying: ${file.name}...`);
447
- const parsed = parseMigrationFile(file.content);
448
- const statements = parsed.upStatements.filter(hasExecutableSql);
449
- const version = await getNextSchemaVersion(
450
- client
451
- );
452
- const destructive = parsed.destructive === true;
453
- try {
454
- await client.query("BEGIN");
455
- for (const stmt of statements) {
456
- await client.query(stmt);
457
- }
458
- await recordMigration(
459
- client,
460
- file.name,
461
- file.checksum,
462
- version,
463
- destructive
464
- );
465
- await client.query("COMMIT");
466
- } catch (applyError) {
467
- let rollbackError;
468
- try {
469
- await client.query("ROLLBACK");
470
- } catch (e) {
471
- rollbackError = e;
472
- }
473
- const primary = sanitizePgError(applyError);
474
- if (rollbackError !== void 0) {
475
- console.error(
476
- ` Note: rollback also failed: ${sanitizePgError(rollbackError).message}`
477
- );
478
- }
479
- throw primary;
480
- }
481
- appliedCount++;
482
- console.log(` \u2705 Applied: ${file.name}`);
483
- }
484
- console.log(
485
- `
486
- \u2705 ${appliedCount} migration(s) applied successfully.`
487
- );
488
- });
489
- });
621
+ await runApply(options);
490
622
  })
491
623
  );
492
- var rollbackCommand = new Command3("rollback").description("Roll back applied migrations").argument("<count>", "Number of migrations to roll back").requiredOption("-d, --db <url>", "Database connection URL (required)").option("--dir <path>", "Migrations directory", DEFAULT_MIGRATIONS_DIR).option("--force", "Force rollback of destructive or empty DOWN migrations").action(
493
- (countArg, options) => {
494
- const count = Number.parseInt(countArg, 10);
495
- if (Number.isNaN(count) || count < 1) {
496
- console.error("\u274C Count must be a positive integer");
497
- process.exit(1);
498
- }
499
- return runMigrateAction(async () => {
500
- console.log(`\u23EA Rolling back ${count} migration(s)`);
501
- console.log(` Database: ${redactDbUrl(options.db)}`);
502
- console.log(` Directory: ${options.dir}`);
503
- console.log("");
504
- await withMigratePool(options.db, async (pool) => {
505
- await ensureMigrationsTable(pool);
506
- await withMigrationLock(pool, async (client) => {
507
- const applied = await getAppliedMigrations(
508
- client
509
- );
510
- const sortedDesc = [...applied].sort(
511
- (a, b) => b.name.localeCompare(a.name)
512
- );
513
- if (count > sortedDesc.length) {
514
- throw new MigrationError(
515
- `Cannot roll back ${count} migration(s) \u2014 only ${sortedDesc.length} applied`
516
- );
517
- }
518
- const toRollback = sortedDesc.slice(0, count);
519
- const files = scanMigrationFiles(options.dir);
520
- const fileMap = new Map(files.map((f) => [f.name, f]));
521
- let rolledBack = 0;
522
- for (const record of toRollback) {
523
- const file = fileMap.get(record.name);
524
- if (!file) {
525
- throw new MigrationError(
526
- `Migration file not found on disk: ${record.name}`
527
- );
528
- }
529
- if (file.checksum !== record.checksum) {
530
- throw new MigrationError(
531
- `Checksum mismatch for ${record.name}
532
- Expected: ${record.checksum}
533
- Got: ${file.checksum}
534
-
535
- Migration file has been modified since it was applied.`
536
- );
537
- }
538
- const parsed = parseMigrationFile(file.content);
539
- if (!parsed.hasDown) {
540
- throw new MigrationError(
541
- `Migration ${record.name} has no DOWN section
542
- Cannot roll back a migration without a DOWN section.`
543
- );
544
- }
545
- const downStmts = parsed.downStatements.filter(hasExecutableSql);
546
- if (downStmts.length === 0 && !options.force) {
547
- throw new MigrationError(
548
- `Migration ${record.name} has an empty DOWN section
549
- Use --force to roll back anyway.`
550
- );
551
- }
552
- const safeHeaderContradictedByDown = parsed.destructive === false && isDestructiveDown(downStmts);
553
- if ((parsed.destructive !== false || safeHeaderContradictedByDown) && !options.force) {
554
- if (parsed.destructive === true) {
555
- throw new MigrationError(
556
- `Migration ${record.name} has destructive DOWN operations
557
- Use --force to proceed with destructive rollback.`
558
- );
559
- }
560
- if (safeHeaderContradictedByDown) {
561
- throw new MigrationError(
562
- `Migration ${record.name} is marked non-destructive but its DOWN section contains an obvious destructive statement
563
- Re-generate the migration with accurate dbsp metadata or pass --force.`
564
- );
565
- }
566
- throw new MigrationError(
567
- `Migration ${record.name} is unmarked or legacy
568
- Re-generate the migration with dbsp metadata or pass --force.`
569
- );
570
- }
571
- if (downStmts.length > 0 || options.force) {
572
- console.log(` Rolling back: ${record.name}...`);
573
- }
574
- try {
575
- await client.query("BEGIN");
576
- for (const stmt of downStmts) {
577
- await client.query(stmt);
578
- }
579
- await removeMigrationRecord(
580
- client,
581
- record.name
582
- );
583
- await client.query("COMMIT");
584
- } catch (rollbackError) {
585
- let rollbackCleanupError;
586
- try {
587
- await client.query("ROLLBACK");
588
- } catch (e) {
589
- rollbackCleanupError = e;
590
- }
591
- const primary = sanitizePgError(rollbackError);
592
- if (rollbackCleanupError !== void 0) {
593
- console.error(
594
- ` Note: rollback also failed: ${sanitizePgError(rollbackCleanupError).message}`
595
- );
596
- }
597
- throw primary;
598
- }
599
- rolledBack++;
600
- console.log(` \u2705 Rolled back: ${record.name}`);
601
- }
602
- console.log(
603
- `
604
- \u2705 ${rolledBack} migration(s) rolled back successfully.`
605
- );
606
- });
607
- });
608
- });
609
- }
610
- );
624
+ var rollbackCommand = new Command3("rollback").description("Roll back applied migrations").argument("<count>", "Number of migrations to roll back").requiredOption("-d, --db <url>", "Database connection URL (required)").option("--dir <path>", "Migrations directory", DEFAULT_MIGRATIONS_DIR).option("--force", "Force rollback of destructive or empty DOWN migrations").action(runRollbackCommand);
611
625
  var statusCommand = new Command3("status").description("Show migration status").requiredOption("-d, --db <url>", "Database connection URL (required)").option("--dir <path>", "Migrations directory", DEFAULT_MIGRATIONS_DIR).option("--json", "Output as JSON").action(
612
626
  (options) => runMigrateAction(async () => {
613
627
  await withMigratePool(options.db, async (pool) => {
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/commands/generate.ts","../src/commands/introspect.ts","../src/commands/migrate.ts","../src/migration-file.ts","../src/commands/push.ts","../src/ddl-executor.ts","../src/commands/repl.ts","../src/commands/verify.ts","../src/verifier.ts"],"sourcesContent":["#!/usr/bin/env node\n/**\n * ARCH-002 Block 3: CLI Scaffold\n *\n * dbsp CLI - Schema-first code generation for db-semantic-planner.\n */\n\nimport { Command, CommanderError } from 'commander';\nimport { generateCommand } from './commands/generate.js';\nimport { introspectCommand } from './commands/introspect.js';\nimport { migrateCommand } from './commands/migrate.js';\nimport { pushCommand } from './commands/push.js';\nimport { replCommand } from './commands/repl.js';\nimport { verifyCommand } from './commands/verify.js';\n\nconst program = new Command();\n\nprogram\n\t.name('dbsp')\n\t.description('Schema-first code generation for db-semantic-planner')\n\t.version('0.0.1');\n\n// Register commands\nprogram.addCommand(generateCommand);\nprogram.addCommand(introspectCommand);\nprogram.addCommand(migrateCommand);\nprogram.addCommand(pushCommand);\nprogram.addCommand(replCommand);\nprogram.addCommand(verifyCommand);\n\n// CC-15: Intercept Commander parse errors so --json commands receive a JSON\n// error object on stdout instead of a plain-text usage message.\nprogram.exitOverride();\n\ntry {\n\tprogram.parse();\n} catch (err) {\n\t// Commander throws CommanderError for --help, --version, and parse errors.\n\t// Exit 0 for informational outputs (help/version); only exit 1 for real errors.\n\tif (err instanceof CommanderError && err.exitCode === 0) {\n\t\tprocess.exit(0);\n\t}\n\tconst message = err instanceof Error ? err.message : 'Command parse error';\n\tif (process.argv.includes('--json')) {\n\t\tconsole.log(JSON.stringify({ status: 'error', error: message }, null, 2));\n\t} else {\n\t\tconsole.error(`❌ ${message}`);\n\t}\n\tprocess.exit(1);\n}\n","/**\n * ARCH-002 Block 3+4+5: Generate Command\n * CLI-DDL: Added DDL generation target\n * ARCH-005: Migrated to schema() API, removed legacy generators\n *\n * dbsp generate <target> - Generate code from schema.\n *\n * Targets:\n * - ddl: Generate SQL DDL (CREATE TABLE statements)\n *\n * Deprecated targets (removed in ARCH-005):\n * - manifest: Removed legacy manifest generator\n * - kysely: Removed legacy Kysely generator\n */\n\nimport { mkdirSync, writeFileSync } from 'node:fs';\nimport { dirname, resolve } from 'node:path';\nimport type { DbCasing } from '@dbsp/types';\nimport { Command } from 'commander';\nimport { loadSchema, loadSchemaFromCwd } from '../utils/schema-loader.js';\n\nexport const generateCommand = new Command('generate')\n\t.description('Generate code from schema')\n\t.argument('<target>', 'Target to generate: ddl')\n\t.option('-s, --schema <path>', 'Path to schema file (default: auto-detect)')\n\t.option('-o, --out <dir>', 'Output directory (default: ./generated/<target>)')\n\t.option('--output <dir>', 'Output directory (alias for --out)')\n\t.option('--drop', 'Include DROP TABLE IF EXISTS statements (ddl only)')\n\t.option('--schema-name <name>', 'Database schema name (ddl only)')\n\t.option(\n\t\t'--dialect <name>',\n\t\t'Database dialect: postgresql | mysql | sqlite | mssql (default: postgresql)',\n\t)\n\t.option(\n\t\t'--casing <type>',\n\t\t'Column naming: snake | camel | none (default: based on dialect)',\n\t)\n\t.action(\n\t\tasync (\n\t\t\ttarget: string,\n\t\t\toptions: {\n\t\t\t\tschema?: string;\n\t\t\t\tout?: string;\n\t\t\t\toutput?: string;\n\t\t\t\tdrop?: boolean;\n\t\t\t\tschemaName?: string;\n\t\t\t\tdialect?: string;\n\t\t\t\tcasing?: 'snake' | 'camel' | 'none';\n\t\t\t},\n\t\t) => {\n\t\t\ttry {\n\t\t\t\t// Reject the target BEFORE loading the schema: loading it executes the\n\t\t\t\t// user's schema module, and there is no reason to run their code for a\n\t\t\t\t// target we are going to refuse anyway.\n\t\t\t\tif (target === 'manifest' || target === 'kysely') {\n\t\t\t\t\tthrow new Error(\n\t\t\t\t\t\t`Target '${target}' has been removed. ` +\n\t\t\t\t\t\t\t`Use 'ddl' for SQL generation, or the 'introspect' command to generate a schema file.`,\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t\tif (target !== 'ddl') {\n\t\t\t\t\tthrow new Error(`Unknown target: ${target}. Available targets: ddl`);\n\t\t\t\t}\n\n\t\t\t\t// Load schema (ARCH-005: only schema() format supported)\n\t\t\t\tlet schema: Awaited<ReturnType<typeof loadSchema>>;\n\t\t\t\tlet schemaPath: string;\n\n\t\t\t\tif (options.schema) {\n\t\t\t\t\tschema = await loadSchema(options.schema);\n\t\t\t\t\tschemaPath = options.schema;\n\t\t\t\t} else {\n\t\t\t\t\tconst result = await loadSchemaFromCwd();\n\t\t\t\t\tschema = result.schema;\n\t\t\t\t\tschemaPath = result.path;\n\t\t\t\t}\n\n\t\t\t\t// For DDL without --output, we output to stdout so use stderr for info\n\t\t\t\tconst outputPath = options.out ?? options.output;\n\t\t\t\tconst useStdout = target === 'ddl' && !outputPath;\n\t\t\t\tconst log = useStdout ? console.error : console.log;\n\n\t\t\t\tlog(`📄 Loaded schema from: ${schemaPath}`);\n\n\t\t\t\t// Validate dialect option\n\t\t\t\tconst dialect = options.dialect ?? 'postgresql';\n\t\t\t\tif (dialect !== 'postgresql') {\n\t\t\t\t\tconsole.error(\n\t\t\t\t\t\t`⚠️ Warning: Only 'postgresql' dialect is currently supported. Using postgresql.`,\n\t\t\t\t\t);\n\t\t\t\t}\n\n\t\t\t\t// Generate based on target\n\t\t\t\tswitch (target) {\n\t\t\t\t\tcase 'ddl': {\n\t\t\t\t\t\t// Determine casing: explicit option > schema export > dialect default.\n\t\t\t\t\t\tconst dbCasing =\n\t\t\t\t\t\t\tmapGenerateCasingOption(options.casing) ??\n\t\t\t\t\t\t\tschema.dbCasing ??\n\t\t\t\t\t\t\t('snake_case' as const);\n\t\t\t\t\t\tconst casingLabel = formatGenerateCasingLabel(dbCasing);\n\n\t\t\t\t\t\t// Import adapter from adapter-pgsql (compile-only, no DB connection needed)\n\t\t\t\t\t\tconst { createPgsqlCompileOnlyAdapter } = await import(\n\t\t\t\t\t\t\t'@dbsp/adapter-pgsql'\n\t\t\t\t\t\t);\n\n\t\t\t\t\t\tconst adapter = createPgsqlCompileOnlyAdapter({\n\t\t\t\t\t\t\tdbCasing,\n\t\t\t\t\t\t\t...(options.schemaName ? { schemaName: options.schemaName } : {}),\n\t\t\t\t\t\t});\n\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\t// ARCH-005: Use schema.model directly (already ModelIR)\n\t\t\t\t\t\t\tconst ddlStatements = adapter.generateDDL(schema.model, {\n\t\t\t\t\t\t\t\t...(options.drop !== undefined && {\n\t\t\t\t\t\t\t\t\tincludeDropStatements: options.drop,\n\t\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t\t});\n\n\t\t\t\t\t\t\tconst ddlContent = ddlStatements.join('\\n\\n');\n\t\t\t\t\t\t\tconst outputPath = options.out ?? options.output;\n\n\t\t\t\t\t\t\tif (outputPath) {\n\t\t\t\t\t\t\t\t// Write to file if --output is specified\n\t\t\t\t\t\t\t\tconst outPath = outputPath.endsWith('.sql')\n\t\t\t\t\t\t\t\t\t? resolve(process.cwd(), outputPath)\n\t\t\t\t\t\t\t\t\t: resolve(process.cwd(), outputPath, 'schema.sql');\n\n\t\t\t\t\t\t\t\tmkdirSync(dirname(outPath), { recursive: true });\n\t\t\t\t\t\t\t\twriteFileSync(outPath, ddlContent, 'utf-8');\n\n\t\t\t\t\t\t\t\tconsole.log(`✅ Generated DDL: ${outPath}`);\n\t\t\t\t\t\t\t\tconsole.log(` Tables: ${schema.tableNames.length}`);\n\t\t\t\t\t\t\t\tconsole.log(` Statements: ${ddlStatements.length}`);\n\t\t\t\t\t\t\t\tconsole.log(` Casing: ${casingLabel}`);\n\t\t\t\t\t\t\t\tif (options.drop) {\n\t\t\t\t\t\t\t\t\tconsole.log(` Includes DROP statements`);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tif (options.schemaName) {\n\t\t\t\t\t\t\t\t\tconsole.log(` Schema: ${options.schemaName}`);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t// Output DDL to stdout (for piping)\n\t\t\t\t\t\t\t\t// Info messages (schema loaded) went to stderr\n\t\t\t\t\t\t\t\tconsole.log(ddlContent);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t} catch (error) {\n\t\t\t\tconst message = error instanceof Error ? error.message : String(error);\n\t\t\t\tconsole.error(`❌ ${message}`);\n\t\t\t\tprocess.exit(1);\n\t\t\t}\n\t\t},\n\t);\n\nfunction mapGenerateCasingOption(\n\tcasing: 'snake' | 'camel' | 'none' | undefined,\n): DbCasing | undefined {\n\tswitch (casing) {\n\t\tcase 'snake':\n\t\t\treturn 'snake_case';\n\t\tcase 'camel':\n\t\t\treturn 'camelCase';\n\t\tcase 'none':\n\t\t\treturn 'preserve';\n\t\tdefault:\n\t\t\treturn undefined;\n\t}\n}\n\nfunction formatGenerateCasingLabel(\n\tdbCasing: DbCasing,\n): 'snake' | 'camel' | 'none' {\n\tswitch (dbCasing) {\n\t\tcase 'snake_case':\n\t\t\treturn 'snake';\n\t\tcase 'camelCase':\n\t\t\treturn 'camel';\n\t\tcase 'preserve':\n\t\t\treturn 'none';\n\t}\n}\n","/**\n * CLI-DDL: Introspect Command\n *\n * dbsp introspect - Generate schema.ts from database introspection.\n */\n\nimport { mkdirSync, writeFileSync } from 'node:fs';\nimport { dirname, resolve } from 'node:path';\nimport { Command } from 'commander';\nimport {\n\tgenerateSchemaFileWithDiagnostics,\n\ttype SchemaCodegenOptions,\n} from '../generators/schema-codegen.js';\nimport { createDbConnection, redactDbUrl } from '../utils/db-utils.js';\n\nfunction uniqueWarnings(warnings: readonly string[]): string[] {\n\tconst seen = new Set<string>();\n\tconst unique: string[] = [];\n\tfor (const warning of warnings) {\n\t\tif (seen.has(warning)) continue;\n\t\tseen.add(warning);\n\t\tunique.push(warning);\n\t}\n\treturn unique;\n}\n\nfunction reportWarnings(warnings: readonly string[]): void {\n\tfor (const warning of warnings) {\n\t\tconsole.log(` ⚠️ ${warning}`);\n\t}\n}\n\nexport const introspectCommand = new Command('introspect')\n\t.description('Generate schema.ts from database introspection')\n\t.requiredOption('-d, --db <url>', 'Database connection URL (required)')\n\t.option('-o, --out <file>', 'Output schema file', './dbsp.schema.ts')\n\t.option('--schema-name <name>', 'Database schema name', 'public')\n\t.option(\n\t\t'--exclude <patterns>',\n\t\t'Tables to exclude (comma-separated glob patterns)',\n\t\t'_migrations,_prisma*,pg_*',\n\t)\n\t.option('--include <patterns>', 'Tables to include (comma-separated)')\n\t.option('--no-db-type-comments', 'Omit original DB type comments')\n\t.option(\n\t\t'--db-casing <casing>',\n\t\t'Database column casing (snake_case → camelCase in generated code)',\n\t\t'snake_case',\n\t)\n\t.action(\n\t\tasync (options: {\n\t\t\tdb: string;\n\t\t\tout: string;\n\t\t\tschemaName: string;\n\t\t\texclude: string;\n\t\t\tinclude?: string;\n\t\t\tdbTypeComments: boolean;\n\t\t\tdbCasing: 'snake_case' | 'camelCase' | 'preserve';\n\t\t}) => {\n\t\t\tconst redactedUrl = redactDbUrl(options.db);\n\n\t\t\tconsole.log(`🔍 Introspecting database: ${redactedUrl}`);\n\t\t\tconsole.log(` Schema: ${options.schemaName}`);\n\t\t\tif (options.exclude) {\n\t\t\t\tconsole.log(` Excluding: ${options.exclude}`);\n\t\t\t}\n\t\t\tconsole.log('');\n\n\t\t\ttry {\n\t\t\t\t// Connect to database\n\t\t\t\tconst { pool } = await createDbConnection(options.db);\n\n\t\t\t\ttry {\n\t\t\t\t\t// Import introspect from adapter-pgsql\n\t\t\t\t\tconst { introspect } = await import('@dbsp/adapter-pgsql');\n\n\t\t\t\t\t// Build introspection options from CLI flags\n\t\t\t\t\tconst excludePatterns = options.exclude\n\t\t\t\t\t\t? options.exclude.split(',').map((s) => s.trim())\n\t\t\t\t\t\t: undefined;\n\t\t\t\t\tconst includePatterns = options.include\n\t\t\t\t\t\t? options.include.split(',').map((s) => s.trim())\n\t\t\t\t\t\t: undefined;\n\n\t\t\t\t\t// Introspect the database directly (returns IntrospectedModelIR)\n\t\t\t\t\tconst model = await introspect(pool, {\n\t\t\t\t\t\tschema: options.schemaName,\n\t\t\t\t\t\t...(excludePatterns ? { exclude: excludePatterns } : {}),\n\t\t\t\t\t\t...(includePatterns ? { include: includePatterns } : {}),\n\t\t\t\t\t});\n\n\t\t\t\t\t// Report what we found\n\t\t\t\t\tconst tableCount = model.tables.size;\n\t\t\t\t\tconst relationCount = model.relations.size;\n\t\t\t\t\tconst hierarchyCount = model.hierarchies?.length ?? 0;\n\n\t\t\t\t\tconsole.log(\n\t\t\t\t\t\t`📊 Found ${tableCount} tables, ${relationCount} relations, ${hierarchyCount} hierarchies`,\n\t\t\t\t\t);\n\n\t\t\t\t\t// Generate schema file — pass metadata from introspection\n\t\t\t\t\tconst codegenOptions: SchemaCodegenOptions = {\n\t\t\t\t\t\tsourceUrl: options.db,\n\t\t\t\t\t\tincludeDbTypeComments: options.dbTypeComments,\n\t\t\t\t\t\tintrospectedAt: model.introspectedAt,\n\t\t\t\t\t\tdbCasing: options.dbCasing,\n\t\t\t\t\t};\n\n\t\t\t\t\tconst generated = generateSchemaFileWithDiagnostics(\n\t\t\t\t\t\tmodel,\n\t\t\t\t\t\tcodegenOptions,\n\t\t\t\t\t);\n\t\t\t\t\tconst warnings = uniqueWarnings(generated.warnings);\n\t\t\t\t\tif (warnings.length > 0) {\n\t\t\t\t\t\treportWarnings(warnings);\n\t\t\t\t\t}\n\t\t\t\t\tconsole.log('');\n\n\t\t\t\t\t// Write output file\n\t\t\t\t\tconst outPath = resolve(process.cwd(), options.out);\n\t\t\t\t\tmkdirSync(dirname(outPath), { recursive: true });\n\t\t\t\t\twriteFileSync(outPath, generated.code, 'utf-8');\n\n\t\t\t\t\tconsole.log(`✅ Generated schema: ${outPath}`);\n\t\t\t\t\tconsole.log(` Tables: ${tableCount}`);\n\t\t\t\t\tconsole.log(` Relations: ${relationCount}`);\n\t\t\t\t} finally {\n\t\t\t\t\t// Close database connection\n\t\t\t\t\tawait pool.end();\n\t\t\t\t}\n\t\t\t} catch (error) {\n\t\t\t\tconst message = error instanceof Error ? error.message : String(error);\n\t\t\t\tconsole.error(`❌ ${message}`);\n\t\t\t\tprocess.exit(1);\n\t\t\t}\n\t\t},\n\t);\n","/**\n * Migrate Command — Migration Infrastructure\n *\n * dbsp migrate dev - Generate migration from schema diff\n * dbsp migrate apply - Apply pending migrations\n * dbsp migrate rollback - Roll back applied migrations\n * dbsp migrate status - Show migration status\n */\n\nimport {\n\tcomparePgsqlDatabaseSchema,\n\tcreatePgsqlAdapter,\n\tensureMigrationsTable,\n\tgenerateMigrationFile,\n\tgenerateMigrationSQL,\n\tgetAppliedMigrations,\n\tgetNextSchemaVersion,\n\tisDestructiveDown,\n\tparseMigrationFile,\n\trecordMigration,\n\tremoveMigrationRecord,\n\twithMigrationLock,\n} from '@dbsp/adapter-pgsql';\nimport { Command } from 'commander';\nimport type { Pool } from 'pg';\nimport {\n\tDEFAULT_MIGRATIONS_DIR,\n\tgenerateMigrationFilename,\n\tscanMigrationFiles,\n\twriteMigrationFile,\n} from '../migration-file.js';\nimport { createDbConnection, redactDbUrl } from '../utils/db-utils.js';\nimport { loadSchema } from '../utils/schema-loader.js';\n\n// ============================================================================\n// Helpers\n// ============================================================================\n\n/**\n * Typed error for migration failures.\n * Distinguishes user-facing errors (validation, logic) from unexpected errors.\n */\nexport class MigrationError extends Error {\n\tconstructor(message: string) {\n\t\tsuper(message);\n\t\tthis.name = 'MigrationError';\n\t}\n}\n\n/**\n * Sanitize a pg error for user-facing output.\n *\n * PostgreSQL errors may carry schema/table/row details in their message text.\n * For pg errors (identifiable by SQLSTATE `.code`), we emit a sanitized message\n * keyed only by the SQLSTATE code. The raw message is available via DEBUG=dbsp.\n *\n * @param err - Any caught value\n * @returns An Error instance safe to display to the user\n */\nexport function sanitizePgError(err: unknown): Error {\n\tif (err instanceof Error) {\n\t\t// pg errors carry a SQLSTATE `.code` property\n\t\tconst code = (err as Error & { code?: string }).code;\n\t\tif (typeof code === 'string' && /^[0-9A-Z]{5}$/.test(code)) {\n\t\t\tif (process.env.DEBUG?.includes('dbsp')) {\n\t\t\t\tconsole.error(`[DEBUG] pg error detail: ${err.message}`);\n\t\t\t}\n\t\t\treturn new MigrationError(`Migration failed: database error ${code}`);\n\t\t}\n\t\treturn err;\n\t}\n\treturn new Error(String(err));\n}\n\n/**\n * DRY lifecycle wrapper: create pool → run fn → end pool on success or error.\n * Replaces the repeated try { ... } finally { pool.end() } pattern across commands.\n *\n * process.exit is called OUTSIDE the pool lifecycle so the lock-holding client\n * (from withMigrationLock) is always released before the process terminates.\n */\nexport async function withMigratePool<T>(\n\tdbUrl: string,\n\tfn: (pool: Pool) => Promise<T>,\n): Promise<T> {\n\tconst { pool } = await createDbConnection(dbUrl);\n\ttry {\n\t\treturn await fn(pool);\n\t} finally {\n\t\tlet endError: unknown;\n\t\ttry {\n\t\t\tawait pool.end();\n\t\t} catch (e) {\n\t\t\tendError = e;\n\t\t}\n\t\tif (endError !== undefined) {\n\t\t\t// Cleanup failure is non-fatal — log it but don't mask the original error\n\t\t\tconsole.error(\n\t\t\t\t`Warning: pool.end() failed: ${endError instanceof Error ? endError.message : String(endError)}`,\n\t\t\t);\n\t\t}\n\t}\n}\n\n/**\n * Top-level error handler for migrate commands.\n * Ensures process.exit is only called AFTER pool cleanup (via withMigratePool).\n */\nasync function runMigrateAction(fn: () => Promise<void>): Promise<void> {\n\ttry {\n\t\tawait fn();\n\t} catch (error) {\n\t\tif (error instanceof Error) {\n\t\t\tconsole.error(`❌ Error: ${error.message}`);\n\t\t} else {\n\t\t\tconsole.error('❌ Unknown error occurred');\n\t\t}\n\t\tprocess.exit(1);\n\t}\n}\n\n/**\n * Returns true if a migration statement contains at least one executable SQL\n * line (non-empty, not a comment). Statements that consist entirely of blank\n * lines and `--` comment lines are skipped; statements that begin with a\n * comment header but also contain real SQL are kept (PostgreSQL ignores the\n * comment, so the header is harmless and preserves intent).\n */\nexport function hasExecutableSql(stmt: string): boolean {\n\treturn stmt.split('\\n').some((line) => {\n\t\tconst t = line.trim();\n\t\treturn t.length > 0 && !t.startsWith('--');\n\t});\n}\n\n// ============================================================================\n// Subcommands\n// ============================================================================\n\nconst devCommand = new Command('dev')\n\t.description('Generate a migration from schema changes')\n\t.option(\n\t\t'-s, --schema <path>',\n\t\t'Path to schema file (default: dbsp.schema.ts)',\n\t)\n\t.requiredOption('-d, --db <url>', 'Database connection URL (required)')\n\t.option('--schema-name <name>', 'Database schema name (default: public)')\n\t.option('--dir <path>', 'Migrations directory', DEFAULT_MIGRATIONS_DIR)\n\t.option('-n, --name <description>', 'Migration description', 'migration')\n\t.option('--allow-destructive', 'Include destructive changes (drops)')\n\t.action(\n\t\t(options: {\n\t\t\tschema?: string;\n\t\t\tdb: string;\n\t\t\tschemaName?: string;\n\t\t\tdir: string;\n\t\t\tname: string;\n\t\t\tallowDestructive?: boolean;\n\t\t}) =>\n\t\t\trunMigrateAction(async () => {\n\t\t\t\tconst schemaPath = options.schema ?? 'dbsp.schema.ts';\n\n\t\t\t\tconsole.log(`📝 Generating migration: ${schemaPath}`);\n\t\t\t\tconsole.log(` Database: ${redactDbUrl(options.db)}`);\n\t\t\t\tconsole.log('');\n\n\t\t\t\tconst loaded = await loadSchema(schemaPath);\n\t\t\t\tconst schemaModel = loaded.model;\n\n\t\t\t\tawait withMigratePool(options.db, async (pool) => {\n\t\t\t\t\tconst adapter = createPgsqlAdapter(pool);\n\t\t\t\t\tconst diff = await comparePgsqlDatabaseSchema(adapter, schemaModel, {\n\t\t\t\t\t\t...(options.schemaName ? { schema: options.schemaName } : {}),\n\t\t\t\t\t\t...(loaded.dbCasing ? { dbCasing: loaded.dbCasing } : {}),\n\t\t\t\t\t\tonWarning: (message) => console.warn(`⚠️ ${message}`),\n\t\t\t\t\t});\n\n\t\t\t\t\tif (diff.changes.length === 0) {\n\t\t\t\t\t\tconsole.log('✅ No changes detected — database matches schema.');\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\n\t\t\t\t\t// Check for destructive changes\n\t\t\t\t\tif (diff.hasDestructive && !options.allowDestructive) {\n\t\t\t\t\t\tconst destructive = diff.changes.filter((c) => c.destructive);\n\t\t\t\t\t\tthrow new MigrationError(\n\t\t\t\t\t\t\t`${destructive.length} destructive change(s) detected:\\n` +\n\t\t\t\t\t\t\t\tdestructive.map((c) => ` - ${c.details}`).join('\\n') +\n\t\t\t\t\t\t\t\t'\\n\\nUse --allow-destructive to include these changes.',\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\n\t\t\t\t\tconst sqlOptions = {\n\t\t\t\t\t\tincludeDestructive: options.allowDestructive ?? false,\n\t\t\t\t\t\t...(options.schemaName ? { schemaName: options.schemaName } : {}),\n\t\t\t\t\t};\n\n\t\t\t\t\tconst statements = generateMigrationSQL(diff, sqlOptions);\n\n\t\t\t\t\tif (statements.length === 0) {\n\t\t\t\t\t\tconsole.log(\n\t\t\t\t\t\t\t'✅ No migration needed — all changes are non-actionable.',\n\t\t\t\t\t\t);\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\n\t\t\t\t\t// Generate migration file with UP + DOWN sections\n\t\t\t\t\tconst existingFiles = scanMigrationFiles(options.dir).map(\n\t\t\t\t\t\t(f) => f.name,\n\t\t\t\t\t);\n\t\t\t\t\tconst filename = generateMigrationFilename(\n\t\t\t\t\t\texistingFiles,\n\t\t\t\t\t\toptions.name,\n\t\t\t\t\t);\n\t\t\t\t\tconst content = generateMigrationFile(diff, {\n\t\t\t\t\t\t...sqlOptions,\n\t\t\t\t\t\tname: filename,\n\t\t\t\t\t});\n\n\t\t\t\t\tconst file = writeMigrationFile(options.dir, filename, content);\n\n\t\t\t\t\tconsole.log(`✅ Migration created: ${file.path}`);\n\t\t\t\t\tconsole.log(` Statements: ${statements.length}`);\n\t\t\t\t\tconsole.log(` Checksum: ${file.checksum.slice(0, 12)}...`);\n\t\t\t\t});\n\t\t\t}),\n\t);\n\nconst applyCommand = new Command('apply')\n\t.description('Apply pending migrations')\n\t.requiredOption('-d, --db <url>', 'Database connection URL (required)')\n\t.option('--dir <path>', 'Migrations directory', DEFAULT_MIGRATIONS_DIR)\n\t.option('--dry-run', 'Show pending migrations without applying')\n\t.action((options: { db: string; dir: string; dryRun?: boolean }) =>\n\t\trunMigrateAction(async () => {\n\t\t\tconsole.log('🔄 Applying migrations');\n\t\t\tconsole.log(` Database: ${redactDbUrl(options.db)}`);\n\t\t\tconsole.log(` Directory: ${options.dir}`);\n\t\t\tconsole.log('');\n\n\t\t\tawait withMigratePool(options.db, async (pool) => {\n\t\t\t\t// Ensure tracking table exists before acquiring lock\n\t\t\t\tawait ensureMigrationsTable(pool);\n\n\t\t\t\tawait withMigrationLock(pool, async (client) => {\n\t\t\t\t\t// Get applied migrations on the lock-holding client\n\t\t\t\t\tconst applied = await getAppliedMigrations(client as unknown as Pool);\n\t\t\t\t\tconst appliedMap = new Map(applied.map((m) => [m.name, m.checksum]));\n\n\t\t\t\t\t// Scan migration files\n\t\t\t\t\tconst files = scanMigrationFiles(options.dir);\n\n\t\t\t\t\tif (files.length === 0) {\n\t\t\t\t\t\tconsole.log(`No migration files found in ${options.dir}`);\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\n\t\t\t\t\t// Validate checksums for already-applied migrations\n\t\t\t\t\tfor (const file of files) {\n\t\t\t\t\t\tconst existingChecksum = appliedMap.get(file.name);\n\t\t\t\t\t\tif (\n\t\t\t\t\t\t\texistingChecksum !== undefined &&\n\t\t\t\t\t\t\texistingChecksum !== file.checksum\n\t\t\t\t\t\t) {\n\t\t\t\t\t\t\tthrow new MigrationError(\n\t\t\t\t\t\t\t\t`Checksum mismatch for ${file.name}\\n` +\n\t\t\t\t\t\t\t\t\t` Expected: ${existingChecksum}\\n` +\n\t\t\t\t\t\t\t\t\t` Got: ${file.checksum}\\n` +\n\t\t\t\t\t\t\t\t\t'\\nMigration file has been tampered with after being applied.',\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\t// Find pending migrations\n\t\t\t\t\tconst pending = files.filter((f) => !appliedMap.has(f.name));\n\n\t\t\t\t\tif (pending.length === 0) {\n\t\t\t\t\t\tconsole.log('✅ All migrations already applied.');\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\n\t\t\t\t\tif (options.dryRun) {\n\t\t\t\t\t\tconsole.log(`${pending.length} pending migration(s):`);\n\t\t\t\t\t\tfor (const file of pending) {\n\t\t\t\t\t\t\tconsole.log(` - ${file.name}`);\n\t\t\t\t\t\t}\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\n\t\t\t\t\t// Apply each pending migration atomically (DDL + record in one transaction)\n\t\t\t\t\tlet appliedCount = 0;\n\t\t\t\t\tfor (const file of pending) {\n\t\t\t\t\t\tconsole.log(` Applying: ${file.name}...`);\n\n\t\t\t\t\t\t// Parse UP section from file\n\t\t\t\t\t\tconst parsed = parseMigrationFile(file.content);\n\t\t\t\t\t\tconst statements = parsed.upStatements.filter(hasExecutableSql);\n\n\t\t\t\t\t\t// Determine version and destructive flag before the transaction\n\t\t\t\t\t\t// (read from the lock-held client so we see a consistent view)\n\t\t\t\t\t\tconst version = await getNextSchemaVersion(\n\t\t\t\t\t\t\tclient as unknown as Pool,\n\t\t\t\t\t\t);\n\t\t\t\t\t\tconst destructive = parsed.destructive === true;\n\n\t\t\t\t\t\t// Atomic: DDL + record in ONE transaction on the lock-holding client\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\tawait client.query('BEGIN');\n\n\t\t\t\t\t\t\tfor (const stmt of statements) {\n\t\t\t\t\t\t\t\tawait client.query(stmt);\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\tawait recordMigration(\n\t\t\t\t\t\t\t\tclient as unknown as Pool,\n\t\t\t\t\t\t\t\tfile.name,\n\t\t\t\t\t\t\t\tfile.checksum,\n\t\t\t\t\t\t\t\tversion,\n\t\t\t\t\t\t\t\tdestructive,\n\t\t\t\t\t\t\t);\n\n\t\t\t\t\t\t\tawait client.query('COMMIT');\n\t\t\t\t\t\t} catch (applyError) {\n\t\t\t\t\t\t\tlet rollbackError: unknown;\n\t\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\t\tawait client.query('ROLLBACK');\n\t\t\t\t\t\t\t} catch (e) {\n\t\t\t\t\t\t\t\trollbackError = e;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tconst primary = sanitizePgError(applyError);\n\t\t\t\t\t\t\tif (rollbackError !== undefined) {\n\t\t\t\t\t\t\t\tconsole.error(\n\t\t\t\t\t\t\t\t\t` Note: rollback also failed: ${sanitizePgError(rollbackError).message}`,\n\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tthrow primary;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tappliedCount++;\n\t\t\t\t\t\tconsole.log(` ✅ Applied: ${file.name}`);\n\t\t\t\t\t}\n\n\t\t\t\t\tconsole.log(\n\t\t\t\t\t\t`\\n✅ ${appliedCount} migration(s) applied successfully.`,\n\t\t\t\t\t);\n\t\t\t\t});\n\t\t\t});\n\t\t}),\n\t);\n\nconst rollbackCommand = new Command('rollback')\n\t.description('Roll back applied migrations')\n\t.argument('<count>', 'Number of migrations to roll back')\n\t.requiredOption('-d, --db <url>', 'Database connection URL (required)')\n\t.option('--dir <path>', 'Migrations directory', DEFAULT_MIGRATIONS_DIR)\n\t.option('--force', 'Force rollback of destructive or empty DOWN migrations')\n\t.action(\n\t\t(\n\t\t\tcountArg: string,\n\t\t\toptions: { db: string; dir: string; force?: boolean },\n\t\t) => {\n\t\t\tconst count = Number.parseInt(countArg, 10);\n\t\t\tif (Number.isNaN(count) || count < 1) {\n\t\t\t\tconsole.error('❌ Count must be a positive integer');\n\t\t\t\tprocess.exit(1);\n\t\t\t}\n\n\t\t\treturn runMigrateAction(async () => {\n\t\t\t\tconsole.log(`⏪ Rolling back ${count} migration(s)`);\n\t\t\t\tconsole.log(` Database: ${redactDbUrl(options.db)}`);\n\t\t\t\tconsole.log(` Directory: ${options.dir}`);\n\t\t\t\tconsole.log('');\n\n\t\t\t\tawait withMigratePool(options.db, async (pool) => {\n\t\t\t\t\tawait ensureMigrationsTable(pool);\n\n\t\t\t\t\tawait withMigrationLock(pool, async (client) => {\n\t\t\t\t\t\t// Get applied migrations on the lock-holding client\n\t\t\t\t\t\tconst applied = await getAppliedMigrations(\n\t\t\t\t\t\t\tclient as unknown as Pool,\n\t\t\t\t\t\t);\n\t\t\t\t\t\tconst sortedDesc = [...applied].sort((a, b) =>\n\t\t\t\t\t\t\tb.name.localeCompare(a.name),\n\t\t\t\t\t\t);\n\n\t\t\t\t\t\t// SC-18: Validate count\n\t\t\t\t\t\tif (count > sortedDesc.length) {\n\t\t\t\t\t\t\tthrow new MigrationError(\n\t\t\t\t\t\t\t\t`Cannot roll back ${count} migration(s) — only ${sortedDesc.length} applied`,\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tconst toRollback = sortedDesc.slice(0, count);\n\n\t\t\t\t\t\t// Load migration files from disk\n\t\t\t\t\t\tconst files = scanMigrationFiles(options.dir);\n\t\t\t\t\t\tconst fileMap = new Map(files.map((f) => [f.name, f]));\n\n\t\t\t\t\t\tlet rolledBack = 0;\n\t\t\t\t\t\tfor (const record of toRollback) {\n\t\t\t\t\t\t\tconst file = fileMap.get(record.name);\n\t\t\t\t\t\t\tif (!file) {\n\t\t\t\t\t\t\t\tthrow new MigrationError(\n\t\t\t\t\t\t\t\t\t`Migration file not found on disk: ${record.name}`,\n\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t// SC-17: Verify checksum\n\t\t\t\t\t\t\tif (file.checksum !== record.checksum) {\n\t\t\t\t\t\t\t\tthrow new MigrationError(\n\t\t\t\t\t\t\t\t\t`Checksum mismatch for ${record.name}\\n` +\n\t\t\t\t\t\t\t\t\t\t` Expected: ${record.checksum}\\n` +\n\t\t\t\t\t\t\t\t\t\t` Got: ${file.checksum}\\n` +\n\t\t\t\t\t\t\t\t\t\t'\\nMigration file has been modified since it was applied.',\n\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t// Parse DOWN section\n\t\t\t\t\t\t\tconst parsed = parseMigrationFile(file.content);\n\n\t\t\t\t\t\t\t// SC-10/ERR-01: No DOWN section\n\t\t\t\t\t\t\tif (!parsed.hasDown) {\n\t\t\t\t\t\t\t\tthrow new MigrationError(\n\t\t\t\t\t\t\t\t\t`Migration ${record.name} has no DOWN section\\n` +\n\t\t\t\t\t\t\t\t\t\t' Cannot roll back a migration without a DOWN section.',\n\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t// SC-11/ERR-04: Empty DOWN section\n\t\t\t\t\t\t\tconst downStmts = parsed.downStatements.filter(hasExecutableSql);\n\t\t\t\t\t\t\tif (downStmts.length === 0 && !options.force) {\n\t\t\t\t\t\t\t\tthrow new MigrationError(\n\t\t\t\t\t\t\t\t\t`Migration ${record.name} has an empty DOWN section\\n` +\n\t\t\t\t\t\t\t\t\t\t' Use --force to roll back anyway.',\n\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t// SC-19: Metadata-driven destructive rollback guard\n\t\t\t\t\t\t\tconst safeHeaderContradictedByDown =\n\t\t\t\t\t\t\t\tparsed.destructive === false && isDestructiveDown(downStmts);\n\t\t\t\t\t\t\tif (\n\t\t\t\t\t\t\t\t(parsed.destructive !== false ||\n\t\t\t\t\t\t\t\t\tsafeHeaderContradictedByDown) &&\n\t\t\t\t\t\t\t\t!options.force\n\t\t\t\t\t\t\t) {\n\t\t\t\t\t\t\t\tif (parsed.destructive === true) {\n\t\t\t\t\t\t\t\t\tthrow new MigrationError(\n\t\t\t\t\t\t\t\t\t\t`Migration ${record.name} has destructive DOWN operations\\n` +\n\t\t\t\t\t\t\t\t\t\t\t' Use --force to proceed with destructive rollback.',\n\t\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tif (safeHeaderContradictedByDown) {\n\t\t\t\t\t\t\t\t\tthrow new MigrationError(\n\t\t\t\t\t\t\t\t\t\t`Migration ${record.name} is marked non-destructive but its DOWN section contains an obvious destructive statement\\n` +\n\t\t\t\t\t\t\t\t\t\t\t' Re-generate the migration with accurate dbsp metadata or pass --force.',\n\t\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tthrow new MigrationError(\n\t\t\t\t\t\t\t\t\t`Migration ${record.name} is unmarked or legacy\\n` +\n\t\t\t\t\t\t\t\t\t\t' Re-generate the migration with dbsp metadata or pass --force.',\n\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t// Atomic: DOWN SQL + remove record in ONE transaction on the lock-holding client\n\t\t\t\t\t\t\tif (downStmts.length > 0 || options.force) {\n\t\t\t\t\t\t\t\tconsole.log(` Rolling back: ${record.name}...`);\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\t\tawait client.query('BEGIN');\n\n\t\t\t\t\t\t\t\tfor (const stmt of downStmts) {\n\t\t\t\t\t\t\t\t\tawait client.query(stmt);\n\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t\tawait removeMigrationRecord(\n\t\t\t\t\t\t\t\t\tclient as unknown as Pool,\n\t\t\t\t\t\t\t\t\trecord.name,\n\t\t\t\t\t\t\t\t);\n\n\t\t\t\t\t\t\t\tawait client.query('COMMIT');\n\t\t\t\t\t\t\t} catch (rollbackError) {\n\t\t\t\t\t\t\t\tlet rollbackCleanupError: unknown;\n\t\t\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\t\t\tawait client.query('ROLLBACK');\n\t\t\t\t\t\t\t\t} catch (e) {\n\t\t\t\t\t\t\t\t\trollbackCleanupError = e;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tconst primary = sanitizePgError(rollbackError);\n\t\t\t\t\t\t\t\tif (rollbackCleanupError !== undefined) {\n\t\t\t\t\t\t\t\t\tconsole.error(\n\t\t\t\t\t\t\t\t\t\t` Note: rollback also failed: ${sanitizePgError(rollbackCleanupError).message}`,\n\t\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tthrow primary;\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\trolledBack++;\n\t\t\t\t\t\t\tconsole.log(` ✅ Rolled back: ${record.name}`);\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tconsole.log(\n\t\t\t\t\t\t\t`\\n✅ ${rolledBack} migration(s) rolled back successfully.`,\n\t\t\t\t\t\t);\n\t\t\t\t\t});\n\t\t\t\t});\n\t\t\t});\n\t\t},\n\t);\n\nconst statusCommand = new Command('status')\n\t.description('Show migration status')\n\t.requiredOption('-d, --db <url>', 'Database connection URL (required)')\n\t.option('--dir <path>', 'Migrations directory', DEFAULT_MIGRATIONS_DIR)\n\t.option('--json', 'Output as JSON')\n\t.action((options: { db: string; dir: string; json?: boolean }) =>\n\t\trunMigrateAction(async () => {\n\t\t\tawait withMigratePool(options.db, async (pool) => {\n\t\t\t\tawait ensureMigrationsTable(pool);\n\n\t\t\t\tconst applied = await getAppliedMigrations(pool);\n\t\t\t\tconst appliedMap = new Map(applied.map((m) => [m.name, m]));\n\n\t\t\t\tconst files = scanMigrationFiles(options.dir);\n\n\t\t\t\tconst statuses: Array<{\n\t\t\t\t\tname: string;\n\t\t\t\t\tstatus: 'pending' | 'applied' | 'checksum_mismatch' | 'missing_file';\n\t\t\t\t\tappliedAt?: Date;\n\t\t\t\t}> = files.map((file) => {\n\t\t\t\t\tconst record = appliedMap.get(file.name);\n\t\t\t\t\tif (record === undefined) {\n\t\t\t\t\t\treturn {\n\t\t\t\t\t\t\tname: file.name,\n\t\t\t\t\t\t\tstatus: 'pending' as const,\n\t\t\t\t\t\t};\n\t\t\t\t\t}\n\t\t\t\t\tif (record.checksum !== file.checksum) {\n\t\t\t\t\t\treturn {\n\t\t\t\t\t\t\tname: file.name,\n\t\t\t\t\t\t\tstatus: 'checksum_mismatch' as const,\n\t\t\t\t\t\t\tappliedAt: record.appliedAt,\n\t\t\t\t\t\t};\n\t\t\t\t\t}\n\t\t\t\t\treturn {\n\t\t\t\t\t\tname: file.name,\n\t\t\t\t\t\tstatus: 'applied' as const,\n\t\t\t\t\t\tappliedAt: record.appliedAt,\n\t\t\t\t\t};\n\t\t\t\t});\n\n\t\t\t\t// Also show applied migrations that don't have files anymore\n\t\t\t\tfor (const record of applied) {\n\t\t\t\t\tif (!files.some((f) => f.name === record.name)) {\n\t\t\t\t\t\tstatuses.push({\n\t\t\t\t\t\t\tname: record.name,\n\t\t\t\t\t\t\tstatus: 'missing_file' as const,\n\t\t\t\t\t\t\tappliedAt: record.appliedAt,\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tif (options.json) {\n\t\t\t\t\tconsole.log(JSON.stringify(statuses, null, 2));\n\t\t\t\t} else {\n\t\t\t\t\tconsole.log('Migration Status');\n\t\t\t\t\tconsole.log(` Database: ${redactDbUrl(options.db)}`);\n\t\t\t\t\tconsole.log(` Directory: ${options.dir}`);\n\t\t\t\t\tconsole.log('');\n\n\t\t\t\t\tif (statuses.length === 0) {\n\t\t\t\t\t\tconsole.log('No migrations found.');\n\t\t\t\t\t} else {\n\t\t\t\t\t\tfor (const s of statuses) {\n\t\t\t\t\t\t\tconst icon =\n\t\t\t\t\t\t\t\ts.status === 'applied'\n\t\t\t\t\t\t\t\t\t? '✅'\n\t\t\t\t\t\t\t\t\t: s.status === 'pending'\n\t\t\t\t\t\t\t\t\t\t? '⏳'\n\t\t\t\t\t\t\t\t\t\t: s.status === 'checksum_mismatch'\n\t\t\t\t\t\t\t\t\t\t\t? '⚠️'\n\t\t\t\t\t\t\t\t\t\t\t: '❓';\n\t\t\t\t\t\t\tconst detail =\n\t\t\t\t\t\t\t\t'appliedAt' in s && s.appliedAt\n\t\t\t\t\t\t\t\t\t? ` (applied: ${s.appliedAt.toISOString()})`\n\t\t\t\t\t\t\t\t\t: '';\n\t\t\t\t\t\t\tconsole.log(` ${icon} ${s.name} — ${s.status}${detail}`);\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tconst pendingCount = statuses.filter(\n\t\t\t\t\t\t\t(s) => s.status === 'pending',\n\t\t\t\t\t\t).length;\n\t\t\t\t\t\tconst appliedCount = statuses.filter(\n\t\t\t\t\t\t\t(s) => s.status === 'applied',\n\t\t\t\t\t\t).length;\n\n\t\t\t\t\t\tconsole.log('');\n\t\t\t\t\t\tconsole.log(\n\t\t\t\t\t\t\t`Total: ${statuses.length} | Applied: ${appliedCount} | Pending: ${pendingCount}`,\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t});\n\t\t}),\n\t);\n\n// ============================================================================\n// Main Command\n// ============================================================================\n\nexport const migrateCommand = new Command('migrate')\n\t.description('Database migration management')\n\t.addCommand(devCommand)\n\t.addCommand(applyCommand)\n\t.addCommand(rollbackCommand)\n\t.addCommand(statusCommand);\n","/**\n * Migration File — File I/O, naming conventions, checksums.\n *\n * Handles reading/writing migration SQL files and computing\n * SHA-256 checksums for tamper detection.\n */\n\nimport { createHash } from 'node:crypto';\nimport {\n\texistsSync,\n\tmkdirSync,\n\treaddirSync,\n\treadFileSync,\n\twriteFileSync,\n} from 'node:fs';\nimport { join, resolve } from 'node:path';\n\n// ============================================================================\n// Types\n// ============================================================================\n\nexport interface MigrationFile {\n\t/** Filename (e.g., \"0001_create_users.sql\") */\n\treadonly name: string;\n\t/** Full path to the file */\n\treadonly path: string;\n\t/** SQL content */\n\treadonly content: string;\n\t/** SHA-256 checksum of content */\n\treadonly checksum: string;\n}\n\n// ============================================================================\n// Constants\n// ============================================================================\n\n/** Default directory for migration files */\nexport const DEFAULT_MIGRATIONS_DIR = 'migrations';\n\n/** Migration file naming pattern: NNNN_description.sql */\nconst MIGRATION_FILENAME_PATTERN = /^\\d{4}_[\\w-]+\\.sql$/;\n\n// ============================================================================\n// Checksum\n// ============================================================================\n\n/**\n * Compute SHA-256 checksum of content.\n */\nexport function computeChecksum(content: string): string {\n\treturn createHash('sha256').update(content, 'utf-8').digest('hex');\n}\n\n// ============================================================================\n// File Naming\n// ============================================================================\n\n/**\n * Generate the next migration filename.\n *\n * @param existingFiles - List of existing migration filenames\n * @param description - Human-readable description (e.g., \"create_users\")\n * @returns Filename (e.g., \"0001_create_users.sql\")\n */\nexport function generateMigrationFilename(\n\texistingFiles: readonly string[],\n\tdescription: string,\n): string {\n\tconst maxNum = existingFiles.reduce((max, f) => {\n\t\tconst match = f.match(/^(\\d{4})/);\n\t\treturn match?.[1] ? Math.max(max, Number.parseInt(match[1], 10)) : max;\n\t}, 0);\n\n\tconst nextNum = String(maxNum + 1).padStart(4, '0');\n\tconst sanitized = description\n\t\t.toLowerCase()\n\t\t.replace(/[^a-z0-9_-]/g, '_')\n\t\t.replace(/_+/g, '_')\n\t\t.replace(/^_|_$/g, '');\n\n\treturn `${nextNum}_${sanitized || 'migration'}.sql`;\n}\n\n// ============================================================================\n// File I/O\n// ============================================================================\n\n/**\n * Ensure the migrations directory exists.\n */\nexport function ensureMigrationsDir(dir: string): string {\n\tconst fullPath = resolve(dir);\n\tif (!existsSync(fullPath)) {\n\t\tmkdirSync(fullPath, { recursive: true });\n\t}\n\treturn fullPath;\n}\n\n/**\n * Scan migrations directory and return all valid migration files,\n * sorted by name (lexicographic = chronological with zero-padded numbers).\n */\nexport function scanMigrationFiles(dir: string): readonly MigrationFile[] {\n\tconst fullPath = resolve(dir);\n\tif (!existsSync(fullPath)) {\n\t\treturn [];\n\t}\n\n\tconst entries = readdirSync(fullPath)\n\t\t.filter((f) => MIGRATION_FILENAME_PATTERN.test(f))\n\t\t.sort();\n\n\treturn entries.map((name) => {\n\t\tconst filePath = join(fullPath, name);\n\t\tconst content = readFileSync(filePath, 'utf-8');\n\t\treturn {\n\t\t\tname,\n\t\t\tpath: filePath,\n\t\t\tcontent,\n\t\t\tchecksum: computeChecksum(content),\n\t\t};\n\t});\n}\n\n/**\n * Write a migration file to the migrations directory.\n *\n * @returns The created MigrationFile\n */\nexport function writeMigrationFile(\n\tdir: string,\n\tfilename: string,\n\tcontent: string,\n): MigrationFile {\n\tconst fullDir = ensureMigrationsDir(dir);\n\tconst filePath = join(fullDir, filename);\n\twriteFileSync(filePath, content, 'utf-8');\n\n\treturn {\n\t\tname: filename,\n\t\tpath: filePath,\n\t\tcontent,\n\t\tchecksum: computeChecksum(content),\n\t};\n}\n","/**\n * Push Command — Schema Provisioning\n *\n * dbsp push - Push schema to database.\n * Additive by default: only creates missing objects.\n * With --drop: recreates from scratch (preserves _dbsp_migrations).\n */\n\nimport type { SchemaChange, SchemaDiff } from '@dbsp/adapter-pgsql';\nimport {\n\tcomparePgsqlDatabaseSchema,\n\tcreatePgsqlAdapter,\n\tgenerateDDL,\n\tgenerateMigrationSQL,\n\tgetNamingPluginForDbCasing,\n} from '@dbsp/adapter-pgsql';\nimport { Command } from 'commander';\nimport { executeDdl } from '../ddl-executor.js';\nimport { createDbConnection, redactDbUrl } from '../utils/db-utils.js';\nimport { loadSchema } from '../utils/schema-loader.js';\n\n/** Table name reserved for migration history — never dropped by push. */\nconst MIGRATIONS_TABLE = '_dbsp_migrations';\n\nexport const pushCommand = new Command('push')\n\t.description('Push schema to database (additive by default)')\n\t.option(\n\t\t'-s, --schema <path>',\n\t\t'Path to schema file (default: dbsp.schema.ts)',\n\t)\n\t.requiredOption('-d, --db <url>', 'Database connection URL (required)')\n\t.option('--schema-name <name>', 'Database schema name (default: public)')\n\t.option('--drop', 'Drop and recreate all objects (preserves migrations)')\n\t.option('--dry-run', 'Print SQL without executing')\n\t.option('--json', 'Output as JSON')\n\t.action(\n\t\tasync (options: {\n\t\t\tschema?: string;\n\t\t\tdb: string;\n\t\t\tschemaName?: string;\n\t\t\tdrop?: boolean;\n\t\t\tdryRun?: boolean;\n\t\t\tjson?: boolean;\n\t\t}) => {\n\t\t\tconst schemaPath = options.schema ?? 'dbsp.schema.ts';\n\t\t\tconst redactedUrl = redactDbUrl(options.db);\n\n\t\t\tif (!options.json) {\n\t\t\t\tconsole.log(\n\t\t\t\t\t`🚀 Pushing schema: ${schemaPath}${options.drop ? ' (with --drop)' : ''}`,\n\t\t\t\t);\n\t\t\t\tconsole.log(` Database: ${redactedUrl}`);\n\t\t\t\tif (options.schemaName) {\n\t\t\t\t\tconsole.log(` Schema: ${options.schemaName}`);\n\t\t\t\t}\n\t\t\t\tif (options.dryRun) {\n\t\t\t\t\tconsole.log(` Mode: DRY RUN (no changes will be applied)`);\n\t\t\t\t}\n\t\t\t\tconsole.log('');\n\t\t\t}\n\n\t\t\ttry {\n\t\t\t\tconst loaded = await loadSchema(schemaPath);\n\t\t\t\tconst schemaModel = loaded.model;\n\n\t\t\t\tconst { pool } = await createDbConnection(options.db);\n\n\t\t\t\ttry {\n\t\t\t\t\tif (options.drop) {\n\t\t\t\t\t\t// --drop mode: generate full DDL with drops, excluding _dbsp_migrations\n\t\t\t\t\t\tconst statements = generateDDL(schemaModel, {\n\t\t\t\t\t\t\tincludeDropStatements: true,\n\t\t\t\t\t\t\t...(options.schemaName ? { schemaName: options.schemaName } : {}),\n\t\t\t\t\t\t\t...(loaded.dbCasing !== undefined\n\t\t\t\t\t\t\t\t? { naming: getNamingPluginForDbCasing(loaded.dbCasing) }\n\t\t\t\t\t\t\t\t: {}),\n\t\t\t\t\t\t});\n\n\t\t\t\t\t\tconst migrationsPattern =\n\t\t\t\t\t\t\tbuildMigrationsTableDropPattern(MIGRATIONS_TABLE);\n\t\t\t\t\t\tconst filtered = statements.filter(\n\t\t\t\t\t\t\t(stmt) => !migrationsPattern.test(stmt),\n\t\t\t\t\t\t);\n\n\t\t\t\t\t\toutputResult(filtered, options);\n\n\t\t\t\t\t\tconst result = await executeDdl(pool, filtered, {\n\t\t\t\t\t\t\t...(options.dryRun !== undefined\n\t\t\t\t\t\t\t\t? { dryRun: options.dryRun }\n\t\t\t\t\t\t\t\t: {}),\n\t\t\t\t\t\t});\n\n\t\t\t\t\t\tif (options.json) {\n\t\t\t\t\t\t\tconst droppedTables = statements\n\t\t\t\t\t\t\t\t.filter((s) => /DROP\\s+TABLE/i.test(s))\n\t\t\t\t\t\t\t\t.filter((s) => !migrationsPattern.test(s))\n\t\t\t\t\t\t\t\t.map(extractDroppedTableName)\n\t\t\t\t\t\t\t\t.filter((t): t is string => t !== undefined);\n\t\t\t\t\t\t\tconsole.log(\n\t\t\t\t\t\t\t\tJSON.stringify(\n\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\tstatus: options.dryRun ? 'dry-run' : 'dropped',\n\t\t\t\t\t\t\t\t\t\ttables: droppedTables,\n\t\t\t\t\t\t\t\t\t\ttablesDropped: droppedTables.length,\n\t\t\t\t\t\t\t\t\t\tstatementsExecuted: result.statementsExecuted,\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\tnull,\n\t\t\t\t\t\t\t\t\t2,\n\t\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t} else if (!options.dryRun) {\n\t\t\t\t\t\t\tconsole.log(\n\t\t\t\t\t\t\t\t`\\n✅ Push complete: ${result.statementsExecuted} statements executed`,\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t}\n\t\t\t\t\t} else {\n\t\t\t\t\t\t// Additive mode: live diff -> generate migration SQL (additive only)\n\t\t\t\t\t\tconst adapter = createPgsqlAdapter(pool);\n\t\t\t\t\t\tconst compareOptions = {\n\t\t\t\t\t\t\t...(options.schemaName ? { schema: options.schemaName } : {}),\n\t\t\t\t\t\t\t...(loaded.dbCasing ? { dbCasing: loaded.dbCasing } : {}),\n\t\t\t\t\t\t\tonWarning: (message: string) => console.warn(`⚠️ ${message}`),\n\t\t\t\t\t\t};\n\t\t\t\t\t\tconst diff = await comparePgsqlDatabaseSchema(\n\t\t\t\t\t\t\tadapter,\n\t\t\t\t\t\t\tschemaModel,\n\t\t\t\t\t\t\tcompareOptions,\n\t\t\t\t\t\t);\n\n\t\t\t\t\t\t// Generate SQL for additive changes only (no destructive)\n\t\t\t\t\t\tconst statements = generateMigrationSQL(diff, {\n\t\t\t\t\t\t\tincludeDestructive: false,\n\t\t\t\t\t\t\t...(options.schemaName ? { schemaName: options.schemaName } : {}),\n\t\t\t\t\t\t});\n\n\t\t\t\t\t\t// Collect warnings for skipped non-additive changes\n\t\t\t\t\t\tconst skippedChanges = diff.changes.filter((c) => c.destructive);\n\n\t\t\t\t\t\tif (!options.json && skippedChanges.length > 0) {\n\t\t\t\t\t\t\tconsole.log(\n\t\t\t\t\t\t\t\t`⚠️ ${skippedChanges.length} non-additive change(s) skipped:`,\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\tfor (const change of skippedChanges) {\n\t\t\t\t\t\t\t\tconsole.log(` - ${change.details}`);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tconsole.log('');\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tif (statements.length === 0) {\n\t\t\t\t\t\t\tif (options.json) {\n\t\t\t\t\t\t\t\tconsole.log(\n\t\t\t\t\t\t\t\t\tJSON.stringify(\n\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\tstatus: 'up-to-date',\n\t\t\t\t\t\t\t\t\t\t\tstatementsExecuted: 0,\n\t\t\t\t\t\t\t\t\t\t\tskippedChanges: skippedChanges.length,\n\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\tnull,\n\t\t\t\t\t\t\t\t\t\t2,\n\t\t\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tconsole.log('✅ Database is up to date — nothing to push.');\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t// EH-14: return instead of process.exit(0) so pool.end() in finally runs\n\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\toutputResult(statements, options);\n\n\t\t\t\t\t\tconst result = await executeDdl(pool, statements, {\n\t\t\t\t\t\t\t...(options.dryRun !== undefined\n\t\t\t\t\t\t\t\t? { dryRun: options.dryRun }\n\t\t\t\t\t\t\t\t: {}),\n\t\t\t\t\t\t});\n\n\t\t\t\t\t\tif (!options.dryRun) {\n\t\t\t\t\t\t\tconst postApplyDiff = await comparePgsqlDatabaseSchema(\n\t\t\t\t\t\t\t\tadapter,\n\t\t\t\t\t\t\t\tschemaModel,\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t...compareOptions,\n\t\t\t\t\t\t\t\t\tpreviouslyAppliedDiff: diff,\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\tassertNoUnexpectedResidualDrift(postApplyDiff, skippedChanges);\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tif (options.json) {\n\t\t\t\t\t\t\tconsole.log(\n\t\t\t\t\t\t\t\tJSON.stringify(\n\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\tstatus: options.dryRun ? 'dry-run' : 'applied',\n\t\t\t\t\t\t\t\t\t\tstatementsExecuted: result.statementsExecuted,\n\t\t\t\t\t\t\t\t\t\tskippedChanges: skippedChanges.length,\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\tnull,\n\t\t\t\t\t\t\t\t\t2,\n\t\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t} else if (!options.dryRun) {\n\t\t\t\t\t\t\tconsole.log(\n\t\t\t\t\t\t\t\t`\\n✅ Push complete: ${result.statementsExecuted} statement(s) executed`,\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\t// EH-14: return so finally runs pool.end() before the outer success path\n\t\t\t\t} finally {\n\t\t\t\t\tawait pool.end();\n\t\t\t\t}\n\t\t\t} catch (error) {\n\t\t\t\tconst message =\n\t\t\t\t\terror instanceof Error ? error.message : 'Unknown error occurred';\n\t\t\t\t// CC-2+EH-7: If --json, error goes to stdout as JSON; otherwise stderr\n\t\t\t\tif (options.json) {\n\t\t\t\t\tconsole.log(\n\t\t\t\t\t\tJSON.stringify({ status: 'error', error: message }, null, 2),\n\t\t\t\t\t);\n\t\t\t\t} else {\n\t\t\t\t\tconsole.error(`❌ Error: ${message}`);\n\t\t\t\t}\n\t\t\t\tprocess.exit(1);\n\t\t\t}\n\t\t},\n\t);\n\n/**\n * Output SQL statements (dry-run or --json mode).\n */\n/**\n * Match a `DROP TABLE` statement that targets the migrations table, so `--drop`\n * can never destroy dbsp's own migration history.\n *\n * The table name is escaped before it reaches the pattern: an unescaped `.` or\n * `$` in it would otherwise match characters it should not, and a table whose\n * name merely resembles the migrations table would be spared — or worse, one\n * that should be spared would be dropped.\n *\n * The pattern matches the quoted name anywhere in the statement rather than\n * anchoring to the end. That is safe because `generateDDL` returns one statement\n * per array entry, so a match can never jump across a statement boundary.\n */\nexport function buildMigrationsTableDropPattern(tableName: string): RegExp {\n\tconst escaped = tableName.replace(/[.*+?^${}()|[\\]\\\\]/gu, '\\\\$&');\n\treturn new RegExp(\n\t\t`DROP\\\\s+TABLE(?:\\\\s+IF\\\\s+EXISTS)?(?:\\\\s+\"[^\"]*\"\\\\s*\\\\.)?\\\\s*\"${escaped}\"`,\n\t\t'i',\n\t);\n}\n\n/**\n * Read the table name out of a `DROP TABLE` statement for the JSON report.\n *\n * The name is the last quoted identifier, which skips the schema in\n * `\"public\".\"users\"`, and it may be followed by `CASCADE` before the semicolon.\n * Returns the statement itself when nothing matches, so the report never silently\n * loses a table.\n */\nexport function extractDroppedTableName(statement: string): string {\n\tconst match = statement.match(/\"([^\"]+)\"\\s*(?:CASCADE\\s*)?;?\\s*$/iu);\n\treturn match?.[1] ?? statement;\n}\n\nfunction outputResult(\n\tstatements: readonly string[],\n\toptions: { dryRun?: boolean; json?: boolean },\n): void {\n\tif (options.dryRun && !options.json) {\n\t\tconsole.log(`-- Dry run: ${statements.length} statement(s)\\n`);\n\t\tfor (const stmt of statements) {\n\t\t\tconsole.log(`${stmt};\\n`);\n\t\t}\n\t}\n}\n\nfunction assertNoUnexpectedResidualDrift(\n\tpostApplyDiff: SchemaDiff,\n\tskippedChanges: readonly SchemaChange[],\n): void {\n\tconst skippedKeys = new Set(skippedChanges.map(changeIdentity));\n\tconst unexpected = postApplyDiff.changes.filter(\n\t\t(change) =>\n\t\t\t!(change.destructive && skippedKeys.has(changeIdentity(change))),\n\t);\n\tif (unexpected.length === 0) return;\n\n\tthrow new Error(\n\t\t`Push did not converge after applying DDL; ${unexpected.length} outstanding change(s) remain:\\n` +\n\t\t\tunexpected.map(formatOutstandingChange).join('\\n'),\n\t);\n}\n\nfunction changeIdentity(change: SchemaChange): string {\n\treturn JSON.stringify([\n\t\tchange.kind,\n\t\tchange.table,\n\t\tchange.column ?? null,\n\t\tchange.details,\n\t\tchange.destructive,\n\t]);\n}\n\nfunction formatOutstandingChange(change: SchemaChange): string {\n\tconst target = change.column\n\t\t? `${change.table}.${change.column}`\n\t\t: change.table || 'schema';\n\treturn ` - ${change.details} (${change.kind}: ${target})`;\n}\n","/**\n * DDL Executor — Transaction-wrapped DDL execution.\n *\n * Shared by `dbsp push` and `dbsp migrate apply`.\n * Executes an array of SQL statements inside a single transaction.\n */\n\nimport type { Pool, PoolClient } from 'pg';\n\n// ============================================================================\n// Types\n// ============================================================================\n\nexport interface DdlExecutionResult {\n\t/** Number of statements executed */\n\tstatementsExecuted: number;\n\t/** Whether execution was a dry-run (no actual changes) */\n\tdryRun: boolean;\n}\n\n// ============================================================================\n// Executor\n// ============================================================================\n\n/**\n * Execute DDL statements in a transaction.\n *\n * @param pool - pg Pool instance\n * @param statements - SQL statements to execute\n * @param options - Execution options\n * @returns Execution result\n */\nexport async function executeDdl(\n\tpool: Pool,\n\tstatements: readonly string[],\n\toptions?: { dryRun?: boolean },\n): Promise<DdlExecutionResult> {\n\tif (statements.length === 0) {\n\t\treturn { statementsExecuted: 0, dryRun: options?.dryRun ?? false };\n\t}\n\n\tif (options?.dryRun) {\n\t\treturn { statementsExecuted: statements.length, dryRun: true };\n\t}\n\n\tlet client: PoolClient | undefined;\n\ttry {\n\t\tclient = await pool.connect();\n\t\tawait client.query('BEGIN');\n\n\t\tfor (const stmt of statements) {\n\t\t\tawait client.query(stmt);\n\t\t}\n\n\t\tawait client.query('COMMIT');\n\t\treturn { statementsExecuted: statements.length, dryRun: false };\n\t} catch (error) {\n\t\tif (client) {\n\t\t\tawait client.query('ROLLBACK');\n\t\t}\n\t\tthrow error;\n\t} finally {\n\t\tif (client) {\n\t\t\tclient.release();\n\t\t}\n\t}\n}\n","/**\n * DX-030 Block 1: REPL Command\n *\n * dbsp repl [--schema <path>] - Launch interactive REPL.\n * CLI-022: Batch mode support with --eval and --input options.\n */\n\nimport { readFileSync } from 'node:fs';\nimport type { DbCasing } from '@dbsp/types';\nimport { Command } from 'commander';\nimport { config } from '../config.js';\nimport { validateIdentifier } from '../utils/identifier-validation.js';\nimport { loadSchema, loadSchemaFromCwd } from '../utils/schema-loader.js';\n\nexport interface ReplOptions {\n\tschema?: string;\n\t/** CLI-020: Database connection URL for execution mode */\n\tdb?: string;\n\t/** CLI-022: Single query to evaluate (batch mode) */\n\teval?: string;\n\t/** CLI-022: File containing queries to execute (batch mode, one per line) */\n\tinput?: string;\n\t/** CLI-022: Output format for batch mode */\n\tformat?: 'text' | 'json';\n\t/** DEMO-E2E: Assertion file for validating query output */\n\tassert?: string;\n\t/** CLI-IMPORT: SQL files to import before queries (injected as .import commands) */\n\timport?: string[];\n\t/** CLI-USE: PostgreSQL schema to use (injected as .use command) */\n\tuse?: string;\n\t/** CLI-MUT: Start REPL with parse mode enabled */\n\tparse?: boolean;\n\t/** CLI-MUT: Start REPL with exec mode enabled */\n\texec?: boolean;\n\t/** CLI-CONFIG: Custom config file path (default: ~/.dbsp/config.json) */\n\tconfig?: string;\n\t/** CLI-CASING: Column naming convention (describes DB column casing) */\n\tcasing?: 'snake' | 'camel' | 'none';\n}\n\nexport const replCommand = new Command('repl')\n\t.description('Launch interactive REPL for exploring schema and queries')\n\t.option('-s, --schema <path>', 'Path to schema file (default: auto-detect)')\n\t.option(\n\t\t'-d, --db <url>',\n\t\t'PostgreSQL connection URL for execution mode (e.g., postgres://localhost/mydb)',\n\t)\n\t.option('-e, --eval <query>', 'Execute a single query and exit (batch mode)')\n\t.option(\n\t\t'-i, --input <file>',\n\t\t'Execute queries from file, one per line (batch mode)',\n\t)\n\t.option(\n\t\t'-f, --format <format>',\n\t\t'Output format for batch mode: text (default) or json',\n\t\t'text',\n\t)\n\t.option(\n\t\t'-a, --assert <file>',\n\t\t'Assertion file to validate query output (requires --input)',\n\t)\n\t.option(\n\t\t'--import <files...>',\n\t\t'SQL files to import before queries (equivalent to .import commands)',\n\t)\n\t.option(\n\t\t'--use <schema>',\n\t\t'PostgreSQL schema to use (equivalent to .use command)',\n\t)\n\t.option('--parse', 'Start REPL with parse mode enabled (.parse toggle)')\n\t.option('--exec', 'Start REPL with exec mode enabled (.exec toggle)')\n\t.option(\n\t\t'--casing <type>',\n\t\t'Column naming convention: snake (DB uses snake_case), camel (DB uses camelCase), none (preserve as-is)',\n\t)\n\t.option(\n\t\t'-c, --config <path>',\n\t\t'Custom config file path (default: ~/.dbsp/config.json)',\n\t)\n\t.action(async (options: ReplOptions) => {\n\t\t// Set custom config path if provided\n\t\tif (options.config) {\n\t\t\tconfig.setConfigPath(options.config);\n\t\t}\n\t\t// Load config\n\t\tconfig.load();\n\t\ttry {\n\t\t\t// Load schema\n\t\t\tlet schemaPath: string;\n\t\t\tlet schema: Awaited<ReturnType<typeof loadSchema>>;\n\n\t\t\tif (options.schema) {\n\t\t\t\tschema = await loadSchema(options.schema);\n\t\t\t\tschemaPath = options.schema;\n\t\t\t} else {\n\t\t\t\tconst result = await loadSchemaFromCwd();\n\t\t\t\tschema = result.schema;\n\t\t\t\tschemaPath = result.path;\n\t\t\t}\n\n\t\t\t// DEMO-E2E: Validate --assert requires --input\n\t\t\tif (options.assert && !options.input) {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t'--assert requires --input (assertion files validate query output from input files)',\n\t\t\t\t);\n\t\t\t}\n\n\t\t\t// CLI-IMPORT: Validate that --import requires batch mode (SQL file execution)\n\t\t\t// Note: --use, --parse, --exec work in interactive mode (REPL state setup)\n\t\t\tif (options.import && !options.eval && !options.input) {\n\t\t\t\tthrow new Error('--import requires batch mode (--eval or --input)');\n\t\t\t}\n\n\t\t\t// SEC: Validate --use schema name at the entry point so both the batch path\n\t\t\t// (which injects `.use <schema>`) and the interactive path (which passes\n\t\t\t// initialSchemaName) are protected against SQL injection.\n\t\t\tif (options.use) {\n\t\t\t\tvalidateIdentifier(options.use, 'schema');\n\t\t\t}\n\n\t\t\t// CLI-CASING: explicit flag wins; otherwise use the schema declaration.\n\t\t\tconst dbCasing = mapReplCasingOption(options.casing) ?? schema.dbCasing;\n\n\t\t\t// CLI-022: Batch mode - execute queries without interactive UI\n\t\t\tif (options.eval || options.input) {\n\t\t\t\tconst { runBatchMode } = await import('../repl/batch.js');\n\t\t\t\tconst queries: string[] = [];\n\n\t\t\t\t// CLI-USE: Inject .use command first (schema scoping)\n\t\t\t\tif (options.use) {\n\t\t\t\t\tqueries.push(`.use ${options.use}`);\n\t\t\t\t}\n\n\t\t\t\t// CLI-IMPORT: Inject .import commands for SQL files\n\t\t\t\tif (options.import) {\n\t\t\t\t\tfor (const file of options.import) {\n\t\t\t\t\t\tqueries.push(`.import ${file}`);\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tif (options.eval) {\n\t\t\t\t\tqueries.push(options.eval);\n\t\t\t\t}\n\n\t\t\t\tif (options.input) {\n\t\t\t\t\t// EH-2: Map ENOENT to a friendly error instead of raw stack trace\n\t\t\t\t\tlet content: string;\n\t\t\t\t\ttry {\n\t\t\t\t\t\tcontent = readFileSync(options.input, 'utf-8');\n\t\t\t\t\t} catch (err) {\n\t\t\t\t\t\tconst isNotFound =\n\t\t\t\t\t\t\terr instanceof Error &&\n\t\t\t\t\t\t\t'code' in err &&\n\t\t\t\t\t\t\t(err as NodeJS.ErrnoException).code === 'ENOENT';\n\t\t\t\t\t\tthrow new Error(\n\t\t\t\t\t\t\tisNotFound\n\t\t\t\t\t\t\t\t? `Input file not found: ${options.input}`\n\t\t\t\t\t\t\t\t: `Failed to read input file: ${err instanceof Error ? err.message : String(err)}`,\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\t\t\t\t\tqueries.push(...content.split('\\n'));\n\t\t\t\t}\n\n\t\t\t\tawait runBatchMode({\n\t\t\t\t\tqueries,\n\t\t\t\t\tschema,\n\t\t\t\t\tschemaPath,\n\t\t\t\t\tformat: options.format ?? 'text',\n\t\t\t\t\t...(options.db && { databaseUrl: options.db }),\n\t\t\t\t\t...(options.assert && { assertFile: options.assert }),\n\t\t\t\t\t...(dbCasing && { dbCasing }),\n\t\t\t\t});\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\t// Dynamic import to avoid loading React/Ink for other commands\n\t\t\tconst { startRepl } = await import('../repl/index.js');\n\t\t\t// CLI-020: Pass database URL if provided\n\t\t\t// CLI-MUT: Pass initial REPL state options\n\t\t\tawait startRepl({\n\t\t\t\tschema,\n\t\t\t\tschemaPath,\n\t\t\t\t...(options.db && { databaseUrl: options.db }),\n\t\t\t\t...(options.use && { initialSchemaName: options.use }),\n\t\t\t\t...(options.parse && { initialParseMode: true }),\n\t\t\t\t...(options.exec && { initialExecMode: true }),\n\t\t\t\t...(dbCasing && { dbCasing }),\n\t\t\t});\n\t\t} catch (error) {\n\t\t\tconst message = error instanceof Error ? error.message : String(error);\n\t\t\tconsole.error(`❌ ${message}`);\n\t\t\tprocess.exit(1);\n\t\t}\n\t});\n\nfunction mapReplCasingOption(\n\tcasing: ReplOptions['casing'],\n): DbCasing | undefined {\n\tswitch (casing) {\n\t\tcase 'snake':\n\t\t\treturn 'snake_case';\n\t\tcase 'camel':\n\t\t\treturn 'camelCase';\n\t\tcase 'none':\n\t\t\treturn 'preserve';\n\t\tdefault:\n\t\t\treturn undefined;\n\t}\n}\n","/**\n * Verify Command — Schema Drift Detection\n *\n * dbsp verify - Compare schema vs real database using the comparison engine.\n * Detects drift in tables, columns, types, nullable, defaults, FKs, indexes.\n */\n\nimport {\n\tcomparePgsqlDatabaseSchema,\n\tcreatePgsqlAdapter,\n} from '@dbsp/adapter-pgsql';\nimport { Command } from 'commander';\nimport { createDbConnection, redactDbUrl } from '../utils/db-utils.js';\nimport { loadSchema } from '../utils/schema-loader.js';\nimport { formatVerifyResult, verifyFromDiff } from '../verifier.js';\n\nexport const verifyCommand = new Command('verify')\n\t.description('Compare schema vs real database (drift detection)')\n\t.option(\n\t\t'-s, --schema <path>',\n\t\t'Path to schema file (default: dbsp.schema.ts)',\n\t)\n\t.requiredOption('-d, --db <url>', 'Database connection URL (required)')\n\t.option('--schema-name <name>', 'Database schema name (default: public)')\n\t.option('--json', 'Output as JSON')\n\t.action(\n\t\tasync (options: {\n\t\t\tschema?: string;\n\t\t\tdb: string;\n\t\t\tschemaName?: string;\n\t\t\tjson?: boolean;\n\t\t}) => {\n\t\t\tconst schemaPath = options.schema ?? 'dbsp.schema.ts';\n\n\t\t\tconst redactedUrl = redactDbUrl(options.db);\n\n\t\t\tif (!options.json) {\n\t\t\t\tconsole.log(`🔍 Verifying schema: ${schemaPath}`);\n\t\t\t\tconsole.log(` Database: ${redactedUrl}`);\n\t\t\t\tif (options.schemaName) {\n\t\t\t\t\tconsole.log(` Schema: ${options.schemaName}`);\n\t\t\t\t}\n\t\t\t\tconsole.log('');\n\t\t\t}\n\n\t\t\ttry {\n\t\t\t\t// Load schema from file → ModelIR\n\t\t\t\tconst loaded = await loadSchema(schemaPath);\n\t\t\t\tconst schemaModel = loaded.model;\n\n\t\t\t\t// Connect to database\n\t\t\t\tconst { pool } = await createDbConnection(options.db);\n\n\t\t\t\ttry {\n\t\t\t\t\tconst adapter = createPgsqlAdapter(pool);\n\t\t\t\t\t// Live diff: introspect database and canonicalise PostgreSQL CHECK\n\t\t\t\t\t// expressions before comparing.\n\t\t\t\t\tconst diff = await comparePgsqlDatabaseSchema(adapter, schemaModel, {\n\t\t\t\t\t\t...(options.schemaName ? { schema: options.schemaName } : {}),\n\t\t\t\t\t\t...(loaded.dbCasing !== undefined\n\t\t\t\t\t\t\t? { dbCasing: loaded.dbCasing }\n\t\t\t\t\t\t\t: {}),\n\t\t\t\t\t\tonWarning: (message) => console.warn(`⚠️ ${message}`),\n\t\t\t\t\t});\n\n\t\t\t\t\t// Convert to verify result\n\t\t\t\t\t// Keep the legacy reporting fields sourced from introspection so\n\t\t\t\t\t// --json consumers continue to see database table names as before.\n\t\t\t\t\tconst dbModel = await adapter.introspect({\n\t\t\t\t\t\t...(options.schemaName ? { schema: options.schemaName } : {}),\n\t\t\t\t\t});\n\t\t\t\t\tconst schemaTables = Array.from(schemaModel.tables.keys());\n\t\t\t\t\tconst dbTables = Array.from(dbModel.tables.keys());\n\t\t\t\t\tconst result = verifyFromDiff(diff, schemaTables, dbTables);\n\n\t\t\t\t\t// Output\n\t\t\t\t\tif (options.json) {\n\t\t\t\t\t\t// Exclude the full diff meta from JSON output (too verbose)\n\t\t\t\t\t\tconst { diff: _diff, ...jsonResult } = result;\n\t\t\t\t\t\tconsole.log(\n\t\t\t\t\t\t\tJSON.stringify(\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t...jsonResult,\n\t\t\t\t\t\t\t\t\tsummary: diff.summary,\n\t\t\t\t\t\t\t\t\thasDestructive: diff.hasDestructive,\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\tnull,\n\t\t\t\t\t\t\t\t2,\n\t\t\t\t\t\t\t),\n\t\t\t\t\t\t);\n\t\t\t\t\t} else {\n\t\t\t\t\t\tconsole.log(formatVerifyResult(result));\n\t\t\t\t\t}\n\n\t\t\t\t\t// EH-14: set exit code; let finally run pool.end() before process exits\n\t\t\t\t\tprocess.exitCode = result.valid ? 0 : 1;\n\t\t\t\t\treturn;\n\t\t\t\t} finally {\n\t\t\t\t\t// Close database connection\n\t\t\t\t\tawait pool.end();\n\t\t\t\t}\n\t\t\t} catch (error) {\n\t\t\t\tconst message =\n\t\t\t\t\terror instanceof Error ? error.message : 'Unknown error occurred';\n\t\t\t\t// CC-2+EH-7: If --json, error goes to stdout as JSON; otherwise stderr\n\t\t\t\tif (options.json) {\n\t\t\t\t\tconsole.log(\n\t\t\t\t\t\tJSON.stringify({ status: 'error', error: message }, null, 2),\n\t\t\t\t\t);\n\t\t\t\t} else {\n\t\t\t\t\tconsole.error(`❌ Error: ${message}`);\n\t\t\t\t}\n\t\t\t\tprocess.exit(1);\n\t\t\t}\n\t\t},\n\t);\n","/**\n * Schema Verifier — Drift Detection via Comparison Engine\n *\n * Compares schema definition against real database using the\n * adapter's compareSchemata engine for full drift detection.\n *\n * Detects: tables, columns, types, nullable, defaults, FKs, indexes, PKs.\n */\n\nimport type { ChangeKind, SchemaChange, SchemaDiff } from '@dbsp/adapter-pgsql';\n\n// ============================================================================\n// Types\n// ============================================================================\n\nexport type DriftSeverity = 'error' | 'warning' | 'info';\n\nexport type DriftType =\n\t// Tables\n\t| 'missing_table_in_db'\n\t| 'missing_table_in_schema'\n\t// Columns\n\t| 'missing_column_in_db'\n\t| 'missing_column_in_schema'\n\t| 'type_mismatch'\n\t| 'nullable_mismatch'\n\t| 'default_mismatch'\n\t| 'unique_mismatch'\n\t// Constraints\n\t| 'primary_key_mismatch'\n\t| 'missing_fk_in_db'\n\t| 'missing_fk_in_schema'\n\t| 'fk_on_delete_mismatch'\n\t// Indexes\n\t| 'missing_index_in_db'\n\t| 'missing_index_in_schema'\n\t// CHECK constraints\n\t| 'missing_check_in_db'\n\t| 'missing_check_in_schema'\n\t// ENUM types\n\t| 'missing_enum_in_db'\n\t| 'missing_enum_in_schema'\n\t| 'enum_value_mismatch'\n\t// Column enhancements\n\t| 'collation_mismatch'\n\t| 'identity_mismatch'\n\t// Comments\n\t| 'comment_mismatch'\n\t// Extensions & Sequences\n\t| 'missing_extension'\n\t| 'missing_sequence'\n\t| 'sequence_mismatch'\n\t// Constraints\n\t| 'constraint_validation_mismatch'\n\t// Row-Level Security\n\t| 'rls_enabled_mismatch'\n\t| 'missing_policy_in_db'\n\t| 'missing_policy_in_schema';\n\nexport interface DriftIssue {\n\t/** Issue severity */\n\tseverity: DriftSeverity;\n\t/** Human-readable message */\n\tmessage: string;\n\t/** Table affected (if any) */\n\ttable?: string;\n\t/** Column affected (if any) */\n\tcolumn?: string;\n\t/** Issue type for programmatic handling */\n\ttype: DriftType;\n}\n\nexport interface VerifyResult {\n\t/** Whether the schema matches the database */\n\tvalid: boolean;\n\t/** List of drift issues found */\n\tissues: DriftIssue[];\n\t/** Tables in schema */\n\tschemaTables: string[];\n\t/** Tables in database */\n\tdbTables: string[];\n\t/** Structured diff (for programmatic consumers) */\n\tdiff: SchemaDiff;\n}\n\n// ============================================================================\n// Change → Drift Mapping\n// ============================================================================\n\nconst CHANGE_TO_DRIFT: Record<\n\tChangeKind,\n\t{ type: DriftType; severity: DriftSeverity }\n> = {\n\tcreate_table: { type: 'missing_table_in_db', severity: 'error' },\n\tdrop_table: { type: 'missing_table_in_schema', severity: 'warning' },\n\tadd_column: { type: 'missing_column_in_db', severity: 'error' },\n\tdrop_column: { type: 'missing_column_in_schema', severity: 'info' },\n\talter_column_type: { type: 'type_mismatch', severity: 'error' },\n\talter_column_nullable: { type: 'nullable_mismatch', severity: 'warning' },\n\talter_column_default: { type: 'default_mismatch', severity: 'warning' },\n\talter_column_unique: { type: 'unique_mismatch', severity: 'warning' },\n\tadd_primary_key: { type: 'primary_key_mismatch', severity: 'error' },\n\tdrop_primary_key: { type: 'primary_key_mismatch', severity: 'error' },\n\tadd_foreign_key: { type: 'missing_fk_in_db', severity: 'error' },\n\tdrop_foreign_key: { type: 'missing_fk_in_schema', severity: 'warning' },\n\talter_foreign_key: { type: 'fk_on_delete_mismatch', severity: 'warning' },\n\tcreate_index: { type: 'missing_index_in_db', severity: 'warning' },\n\tdrop_index: { type: 'missing_index_in_schema', severity: 'info' },\n\t// CHECK constraints\n\tadd_check_constraint: { type: 'missing_check_in_db', severity: 'warning' },\n\tdrop_check_constraint: { type: 'missing_check_in_schema', severity: 'info' },\n\t// ENUM types\n\tcreate_enum: { type: 'missing_enum_in_db', severity: 'error' },\n\talter_enum_add_value: { type: 'enum_value_mismatch', severity: 'warning' },\n\tdrop_enum: { type: 'missing_enum_in_schema', severity: 'warning' },\n\t// Column enhancements\n\talter_column_collation: { type: 'collation_mismatch', severity: 'warning' },\n\talter_column_identity: { type: 'identity_mismatch', severity: 'warning' },\n\t// Comments\n\tadd_comment: { type: 'comment_mismatch', severity: 'info' },\n\tdrop_comment: { type: 'comment_mismatch', severity: 'info' },\n\t// Extensions & Sequences\n\tcreate_extension: { type: 'missing_extension', severity: 'error' },\n\tdrop_extension: { type: 'missing_extension', severity: 'info' },\n\tcreate_sequence: { type: 'missing_sequence', severity: 'warning' },\n\talter_sequence: { type: 'sequence_mismatch', severity: 'warning' },\n\tdrop_sequence: { type: 'missing_sequence', severity: 'info' },\n\t// Constraints\n\tvalidate_constraint: {\n\t\ttype: 'constraint_validation_mismatch',\n\t\tseverity: 'warning',\n\t},\n\t// Row-Level Security\n\tenable_rls: { type: 'rls_enabled_mismatch', severity: 'warning' },\n\tdisable_rls: { type: 'rls_enabled_mismatch', severity: 'warning' },\n\tcreate_policy: { type: 'missing_policy_in_db', severity: 'warning' },\n\tdrop_policy: { type: 'missing_policy_in_schema', severity: 'warning' },\n};\n\nfunction changeToDriftIssue(change: SchemaChange): DriftIssue {\n\tconst mapping = CHANGE_TO_DRIFT[change.kind] ?? {\n\t\ttype: 'type_mismatch' as DriftType,\n\t\tseverity: 'warning' as DriftSeverity,\n\t};\n\treturn {\n\t\tseverity: mapping.severity,\n\t\ttype: mapping.type,\n\t\ttable: change.table,\n\t\t...(change.column !== undefined ? { column: change.column } : {}),\n\t\tmessage: change.details,\n\t};\n}\n\n// ============================================================================\n// Verification (from SchemaDiff)\n// ============================================================================\n\n/**\n * Convert a SchemaDiff into a VerifyResult.\n *\n * @param diff - Structured diff from compareSchemata()\n * @param schemaTables - Table names in schema (for backward compat)\n * @param dbTables - Table names in database (for backward compat)\n */\nexport function verifyFromDiff(\n\tdiff: SchemaDiff,\n\tschemaTables: string[],\n\tdbTables: string[],\n): VerifyResult {\n\tconst issues = diff.changes.map(changeToDriftIssue);\n\n\t// Sort by severity (error > warning > info)\n\tconst severityOrder: Record<DriftSeverity, number> = {\n\t\terror: 0,\n\t\twarning: 1,\n\t\tinfo: 2,\n\t};\n\tissues.sort(\n\t\t(a: DriftIssue, b: DriftIssue) =>\n\t\t\tseverityOrder[a.severity] - severityOrder[b.severity],\n\t);\n\n\tconst hasErrors = issues.some((i: DriftIssue) => i.severity === 'error');\n\n\treturn {\n\t\tvalid: !hasErrors,\n\t\tissues,\n\t\tschemaTables,\n\t\tdbTables,\n\t\tdiff,\n\t};\n}\n\n// ============================================================================\n// Format\n// ============================================================================\n\n/**\n * Format verification result for CLI output.\n */\nexport function formatVerifyResult(result: VerifyResult): string {\n\tconst lines: string[] = [];\n\n\tif (result.valid) {\n\t\tlines.push('✅ Schema matches database');\n\t} else {\n\t\tlines.push('❌ Schema drift detected');\n\t}\n\n\tlines.push('');\n\tlines.push(`Tables in schema: ${result.schemaTables.length}`);\n\tlines.push(`Tables in database: ${result.dbTables.length}`);\n\tlines.push('');\n\n\tif (result.issues.length === 0) {\n\t\tlines.push('No issues found.');\n\t} else {\n\t\tconst errors = result.issues.filter((i) => i.severity === 'error');\n\t\tconst warnings = result.issues.filter((i) => i.severity === 'warning');\n\t\tconst infos = result.issues.filter((i) => i.severity === 'info');\n\n\t\tif (errors.length > 0) {\n\t\t\tlines.push(`❌ ${errors.length} error(s):`);\n\t\t\tfor (const issue of errors) {\n\t\t\t\tlines.push(` ${issue.message}`);\n\t\t\t}\n\t\t\tlines.push('');\n\t\t}\n\n\t\tif (warnings.length > 0) {\n\t\t\tlines.push(`⚠️ ${warnings.length} warning(s):`);\n\t\t\tfor (const issue of warnings) {\n\t\t\t\tlines.push(` ${issue.message}`);\n\t\t\t}\n\t\t\tlines.push('');\n\t\t}\n\n\t\tif (infos.length > 0) {\n\t\t\tlines.push(`ℹ️ ${infos.length} info:`);\n\t\t\tfor (const issue of infos) {\n\t\t\t\tlines.push(` ${issue.message}`);\n\t\t\t}\n\t\t\tlines.push('');\n\t\t}\n\t}\n\n\treturn lines.join('\\n');\n}\n"],"mappings":";;;;;;;;;;;;;;;;AAOA,SAAS,WAAAA,UAAS,sBAAsB;;;ACQxC,SAAS,WAAW,qBAAqB;AACzC,SAAS,SAAS,eAAe;AAEjC,SAAS,eAAe;AAGjB,IAAM,kBAAkB,IAAI,QAAQ,UAAU,EACnD,YAAY,2BAA2B,EACvC,SAAS,YAAY,yBAAyB,EAC9C,OAAO,uBAAuB,4CAA4C,EAC1E,OAAO,mBAAmB,kDAAkD,EAC5E,OAAO,kBAAkB,oCAAoC,EAC7D,OAAO,UAAU,oDAAoD,EACrE,OAAO,wBAAwB,iCAAiC,EAChE;AAAA,EACA;AAAA,EACA;AACD,EACC;AAAA,EACA;AAAA,EACA;AACD,EACC;AAAA,EACA,OACC,QACA,YASI;AACJ,QAAI;AAIH,UAAI,WAAW,cAAc,WAAW,UAAU;AACjD,cAAM,IAAI;AAAA,UACT,WAAW,MAAM;AAAA,QAElB;AAAA,MACD;AACA,UAAI,WAAW,OAAO;AACrB,cAAM,IAAI,MAAM,mBAAmB,MAAM,0BAA0B;AAAA,MACpE;AAGA,UAAI;AACJ,UAAI;AAEJ,UAAI,QAAQ,QAAQ;AACnB,iBAAS,MAAM,WAAW,QAAQ,MAAM;AACxC,qBAAa,QAAQ;AAAA,MACtB,OAAO;AACN,cAAM,SAAS,MAAM,kBAAkB;AACvC,iBAAS,OAAO;AAChB,qBAAa,OAAO;AAAA,MACrB;AAGA,YAAM,aAAa,QAAQ,OAAO,QAAQ;AAC1C,YAAM,YAAY,WAAW,SAAS,CAAC;AACvC,YAAM,MAAM,YAAY,QAAQ,QAAQ,QAAQ;AAEhD,UAAI,iCAA0B,UAAU,EAAE;AAG1C,YAAM,UAAU,QAAQ,WAAW;AACnC,UAAI,YAAY,cAAc;AAC7B,gBAAQ;AAAA,UACP;AAAA,QACD;AAAA,MACD;AAGA,cAAQ,QAAQ;AAAA,QACf,KAAK,OAAO;AAEX,gBAAM,WACL,wBAAwB,QAAQ,MAAM,KACtC,OAAO,YACN;AACF,gBAAM,cAAc,0BAA0B,QAAQ;AAGtD,gBAAM,EAAE,8BAA8B,IAAI,MAAM,OAC/C,qBACD;AAEA,gBAAM,UAAU,8BAA8B;AAAA,YAC7C;AAAA,YACA,GAAI,QAAQ,aAAa,EAAE,YAAY,QAAQ,WAAW,IAAI,CAAC;AAAA,UAChE,CAAC;AAED;AAEC,kBAAM,gBAAgB,QAAQ,YAAY,OAAO,OAAO;AAAA,cACvD,GAAI,QAAQ,SAAS,UAAa;AAAA,gBACjC,uBAAuB,QAAQ;AAAA,cAChC;AAAA,YACD,CAAC;AAED,kBAAM,aAAa,cAAc,KAAK,MAAM;AAC5C,kBAAMC,cAAa,QAAQ,OAAO,QAAQ;AAE1C,gBAAIA,aAAY;AAEf,oBAAM,UAAUA,YAAW,SAAS,MAAM,IACvC,QAAQ,QAAQ,IAAI,GAAGA,WAAU,IACjC,QAAQ,QAAQ,IAAI,GAAGA,aAAY,YAAY;AAElD,wBAAU,QAAQ,OAAO,GAAG,EAAE,WAAW,KAAK,CAAC;AAC/C,4BAAc,SAAS,YAAY,OAAO;AAE1C,sBAAQ,IAAI,yBAAoB,OAAO,EAAE;AACzC,sBAAQ,IAAI,cAAc,OAAO,WAAW,MAAM,EAAE;AACpD,sBAAQ,IAAI,kBAAkB,cAAc,MAAM,EAAE;AACpD,sBAAQ,IAAI,cAAc,WAAW,EAAE;AACvC,kBAAI,QAAQ,MAAM;AACjB,wBAAQ,IAAI,6BAA6B;AAAA,cAC1C;AACA,kBAAI,QAAQ,YAAY;AACvB,wBAAQ,IAAI,cAAc,QAAQ,UAAU,EAAE;AAAA,cAC/C;AAAA,YACD,OAAO;AAGN,sBAAQ,IAAI,UAAU;AAAA,YACvB;AAAA,UACD;AACA;AAAA,QACD;AAAA,MACD;AAAA,IACD,SAAS,OAAO;AACf,YAAM,UAAU,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;AACrE,cAAQ,MAAM,UAAK,OAAO,EAAE;AAC5B,cAAQ,KAAK,CAAC;AAAA,IACf;AAAA,EACD;AACD;AAED,SAAS,wBACR,QACuB;AACvB,UAAQ,QAAQ;AAAA,IACf,KAAK;AACJ,aAAO;AAAA,IACR,KAAK;AACJ,aAAO;AAAA,IACR,KAAK;AACJ,aAAO;AAAA,IACR;AACC,aAAO;AAAA,EACT;AACD;AAEA,SAAS,0BACR,UAC6B;AAC7B,UAAQ,UAAU;AAAA,IACjB,KAAK;AACJ,aAAO;AAAA,IACR,KAAK;AACJ,aAAO;AAAA,IACR,KAAK;AACJ,aAAO;AAAA,EACT;AACD;;;ACnLA,SAAS,aAAAC,YAAW,iBAAAC,sBAAqB;AACzC,SAAS,WAAAC,UAAS,WAAAC,gBAAe;AACjC,SAAS,WAAAC,gBAAe;AAOxB,SAAS,eAAe,UAAuC;AAC9D,QAAM,OAAO,oBAAI,IAAY;AAC7B,QAAM,SAAmB,CAAC;AAC1B,aAAW,WAAW,UAAU;AAC/B,QAAI,KAAK,IAAI,OAAO,EAAG;AACvB,SAAK,IAAI,OAAO;AAChB,WAAO,KAAK,OAAO;AAAA,EACpB;AACA,SAAO;AACR;AAEA,SAAS,eAAe,UAAmC;AAC1D,aAAW,WAAW,UAAU;AAC/B,YAAQ,IAAI,oBAAU,OAAO,EAAE;AAAA,EAChC;AACD;AAEO,IAAM,oBAAoB,IAAIC,SAAQ,YAAY,EACvD,YAAY,gDAAgD,EAC5D,eAAe,kBAAkB,oCAAoC,EACrE,OAAO,oBAAoB,sBAAsB,kBAAkB,EACnE,OAAO,wBAAwB,wBAAwB,QAAQ,EAC/D;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACD,EACC,OAAO,wBAAwB,qCAAqC,EACpE,OAAO,yBAAyB,gCAAgC,EAChE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACD,EACC;AAAA,EACA,OAAO,YAQD;AACL,UAAM,cAAc,YAAY,QAAQ,EAAE;AAE1C,YAAQ,IAAI,qCAA8B,WAAW,EAAE;AACvD,YAAQ,IAAI,cAAc,QAAQ,UAAU,EAAE;AAC9C,QAAI,QAAQ,SAAS;AACpB,cAAQ,IAAI,iBAAiB,QAAQ,OAAO,EAAE;AAAA,IAC/C;AACA,YAAQ,IAAI,EAAE;AAEd,QAAI;AAEH,YAAM,EAAE,KAAK,IAAI,MAAM,mBAAmB,QAAQ,EAAE;AAEpD,UAAI;AAEH,cAAM,EAAE,WAAW,IAAI,MAAM,OAAO,qBAAqB;AAGzD,cAAM,kBAAkB,QAAQ,UAC7B,QAAQ,QAAQ,MAAM,GAAG,EAAE,IAAI,CAAC,MAAM,EAAE,KAAK,CAAC,IAC9C;AACH,cAAM,kBAAkB,QAAQ,UAC7B,QAAQ,QAAQ,MAAM,GAAG,EAAE,IAAI,CAAC,MAAM,EAAE,KAAK,CAAC,IAC9C;AAGH,cAAM,QAAQ,MAAM,WAAW,MAAM;AAAA,UACpC,QAAQ,QAAQ;AAAA,UAChB,GAAI,kBAAkB,EAAE,SAAS,gBAAgB,IAAI,CAAC;AAAA,UACtD,GAAI,kBAAkB,EAAE,SAAS,gBAAgB,IAAI,CAAC;AAAA,QACvD,CAAC;AAGD,cAAM,aAAa,MAAM,OAAO;AAChC,cAAM,gBAAgB,MAAM,UAAU;AACtC,cAAM,iBAAiB,MAAM,aAAa,UAAU;AAEpD,gBAAQ;AAAA,UACP,mBAAY,UAAU,YAAY,aAAa,eAAe,cAAc;AAAA,QAC7E;AAGA,cAAM,iBAAuC;AAAA,UAC5C,WAAW,QAAQ;AAAA,UACnB,uBAAuB,QAAQ;AAAA,UAC/B,gBAAgB,MAAM;AAAA,UACtB,UAAU,QAAQ;AAAA,QACnB;AAEA,cAAM,YAAY;AAAA,UACjB;AAAA,UACA;AAAA,QACD;AACA,cAAM,WAAW,eAAe,UAAU,QAAQ;AAClD,YAAI,SAAS,SAAS,GAAG;AACxB,yBAAe,QAAQ;AAAA,QACxB;AACA,gBAAQ,IAAI,EAAE;AAGd,cAAM,UAAUC,SAAQ,QAAQ,IAAI,GAAG,QAAQ,GAAG;AAClD,QAAAC,WAAUC,SAAQ,OAAO,GAAG,EAAE,WAAW,KAAK,CAAC;AAC/C,QAAAC,eAAc,SAAS,UAAU,MAAM,OAAO;AAE9C,gBAAQ,IAAI,4BAAuB,OAAO,EAAE;AAC5C,gBAAQ,IAAI,cAAc,UAAU,EAAE;AACtC,gBAAQ,IAAI,iBAAiB,aAAa,EAAE;AAAA,MAC7C,UAAE;AAED,cAAM,KAAK,IAAI;AAAA,MAChB;AAAA,IACD,SAAS,OAAO;AACf,YAAM,UAAU,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;AACrE,cAAQ,MAAM,UAAK,OAAO,EAAE;AAC5B,cAAQ,KAAK,CAAC;AAAA,IACf;AAAA,EACD;AACD;;;AC/HD;AAAA,EACC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACM;AACP,SAAS,WAAAC,gBAAe;;;AChBxB,SAAS,kBAAkB;AAC3B;AAAA,EACC;AAAA,EACA,aAAAC;AAAA,EACA;AAAA,EACA;AAAA,EACA,iBAAAC;AAAA,OACM;AACP,SAAS,MAAM,WAAAC,gBAAe;AAsBvB,IAAM,yBAAyB;AAGtC,IAAM,6BAA6B;AAS5B,SAAS,gBAAgB,SAAyB;AACxD,SAAO,WAAW,QAAQ,EAAE,OAAO,SAAS,OAAO,EAAE,OAAO,KAAK;AAClE;AAaO,SAAS,0BACf,eACA,aACS;AACT,QAAM,SAAS,cAAc,OAAO,CAAC,KAAK,MAAM;AAC/C,UAAM,QAAQ,EAAE,MAAM,UAAU;AAChC,WAAO,QAAQ,CAAC,IAAI,KAAK,IAAI,KAAK,OAAO,SAAS,MAAM,CAAC,GAAG,EAAE,CAAC,IAAI;AAAA,EACpE,GAAG,CAAC;AAEJ,QAAM,UAAU,OAAO,SAAS,CAAC,EAAE,SAAS,GAAG,GAAG;AAClD,QAAM,YAAY,YAChB,YAAY,EACZ,QAAQ,gBAAgB,GAAG,EAC3B,QAAQ,OAAO,GAAG,EAClB,QAAQ,UAAU,EAAE;AAEtB,SAAO,GAAG,OAAO,IAAI,aAAa,WAAW;AAC9C;AASO,SAAS,oBAAoB,KAAqB;AACxD,QAAM,WAAWA,SAAQ,GAAG;AAC5B,MAAI,CAAC,WAAW,QAAQ,GAAG;AAC1B,IAAAF,WAAU,UAAU,EAAE,WAAW,KAAK,CAAC;AAAA,EACxC;AACA,SAAO;AACR;AAMO,SAAS,mBAAmB,KAAuC;AACzE,QAAM,WAAWE,SAAQ,GAAG;AAC5B,MAAI,CAAC,WAAW,QAAQ,GAAG;AAC1B,WAAO,CAAC;AAAA,EACT;AAEA,QAAM,UAAU,YAAY,QAAQ,EAClC,OAAO,CAAC,MAAM,2BAA2B,KAAK,CAAC,CAAC,EAChD,KAAK;AAEP,SAAO,QAAQ,IAAI,CAAC,SAAS;AAC5B,UAAM,WAAW,KAAK,UAAU,IAAI;AACpC,UAAM,UAAU,aAAa,UAAU,OAAO;AAC9C,WAAO;AAAA,MACN;AAAA,MACA,MAAM;AAAA,MACN;AAAA,MACA,UAAU,gBAAgB,OAAO;AAAA,IAClC;AAAA,EACD,CAAC;AACF;AAOO,SAAS,mBACf,KACA,UACA,SACgB;AAChB,QAAM,UAAU,oBAAoB,GAAG;AACvC,QAAM,WAAW,KAAK,SAAS,QAAQ;AACvC,EAAAD,eAAc,UAAU,SAAS,OAAO;AAExC,SAAO;AAAA,IACN,MAAM;AAAA,IACN,MAAM;AAAA,IACN;AAAA,IACA,UAAU,gBAAgB,OAAO;AAAA,EAClC;AACD;;;ADtGO,IAAM,iBAAN,cAA6B,MAAM;AAAA,EACzC,YAAY,SAAiB;AAC5B,UAAM,OAAO;AACb,SAAK,OAAO;AAAA,EACb;AACD;AAYO,SAAS,gBAAgB,KAAqB;AACpD,MAAI,eAAe,OAAO;AAEzB,UAAM,OAAQ,IAAkC;AAChD,QAAI,OAAO,SAAS,YAAY,gBAAgB,KAAK,IAAI,GAAG;AAC3D,UAAI,QAAQ,IAAI,OAAO,SAAS,MAAM,GAAG;AACxC,gBAAQ,MAAM,4BAA4B,IAAI,OAAO,EAAE;AAAA,MACxD;AACA,aAAO,IAAI,eAAe,oCAAoC,IAAI,EAAE;AAAA,IACrE;AACA,WAAO;AAAA,EACR;AACA,SAAO,IAAI,MAAM,OAAO,GAAG,CAAC;AAC7B;AASA,eAAsB,gBACrB,OACA,IACa;AACb,QAAM,EAAE,KAAK,IAAI,MAAM,mBAAmB,KAAK;AAC/C,MAAI;AACH,WAAO,MAAM,GAAG,IAAI;AAAA,EACrB,UAAE;AACD,QAAI;AACJ,QAAI;AACH,YAAM,KAAK,IAAI;AAAA,IAChB,SAAS,GAAG;AACX,iBAAW;AAAA,IACZ;AACA,QAAI,aAAa,QAAW;AAE3B,cAAQ;AAAA,QACP,+BAA+B,oBAAoB,QAAQ,SAAS,UAAU,OAAO,QAAQ,CAAC;AAAA,MAC/F;AAAA,IACD;AAAA,EACD;AACD;AAMA,eAAe,iBAAiB,IAAwC;AACvE,MAAI;AACH,UAAM,GAAG;AAAA,EACV,SAAS,OAAO;AACf,QAAI,iBAAiB,OAAO;AAC3B,cAAQ,MAAM,iBAAY,MAAM,OAAO,EAAE;AAAA,IAC1C,OAAO;AACN,cAAQ,MAAM,+BAA0B;AAAA,IACzC;AACA,YAAQ,KAAK,CAAC;AAAA,EACf;AACD;AASO,SAAS,iBAAiB,MAAuB;AACvD,SAAO,KAAK,MAAM,IAAI,EAAE,KAAK,CAAC,SAAS;AACtC,UAAM,IAAI,KAAK,KAAK;AACpB,WAAO,EAAE,SAAS,KAAK,CAAC,EAAE,WAAW,IAAI;AAAA,EAC1C,CAAC;AACF;AAMA,IAAM,aAAa,IAAIE,SAAQ,KAAK,EAClC,YAAY,0CAA0C,EACtD;AAAA,EACA;AAAA,EACA;AACD,EACC,eAAe,kBAAkB,oCAAoC,EACrE,OAAO,wBAAwB,wCAAwC,EACvE,OAAO,gBAAgB,wBAAwB,sBAAsB,EACrE,OAAO,4BAA4B,yBAAyB,WAAW,EACvE,OAAO,uBAAuB,qCAAqC,EACnE;AAAA,EACA,CAAC,YAQA,iBAAiB,YAAY;AAC5B,UAAM,aAAa,QAAQ,UAAU;AAErC,YAAQ,IAAI,mCAA4B,UAAU,EAAE;AACpD,YAAQ,IAAI,gBAAgB,YAAY,QAAQ,EAAE,CAAC,EAAE;AACrD,YAAQ,IAAI,EAAE;AAEd,UAAM,SAAS,MAAM,WAAW,UAAU;AAC1C,UAAM,cAAc,OAAO;AAE3B,UAAM,gBAAgB,QAAQ,IAAI,OAAO,SAAS;AACjD,YAAM,UAAU,mBAAmB,IAAI;AACvC,YAAM,OAAO,MAAM,2BAA2B,SAAS,aAAa;AAAA,QACnE,GAAI,QAAQ,aAAa,EAAE,QAAQ,QAAQ,WAAW,IAAI,CAAC;AAAA,QAC3D,GAAI,OAAO,WAAW,EAAE,UAAU,OAAO,SAAS,IAAI,CAAC;AAAA,QACvD,WAAW,CAAC,YAAY,QAAQ,KAAK,iBAAO,OAAO,EAAE;AAAA,MACtD,CAAC;AAED,UAAI,KAAK,QAAQ,WAAW,GAAG;AAC9B,gBAAQ,IAAI,4DAAkD;AAC9D;AAAA,MACD;AAGA,UAAI,KAAK,kBAAkB,CAAC,QAAQ,kBAAkB;AACrD,cAAM,cAAc,KAAK,QAAQ,OAAO,CAAC,MAAM,EAAE,WAAW;AAC5D,cAAM,IAAI;AAAA,UACT,GAAG,YAAY,MAAM;AAAA,IACpB,YAAY,IAAI,CAAC,MAAM,QAAQ,EAAE,OAAO,EAAE,EAAE,KAAK,IAAI,IACrD;AAAA,QACF;AAAA,MACD;AAEA,YAAM,aAAa;AAAA,QAClB,oBAAoB,QAAQ,oBAAoB;AAAA,QAChD,GAAI,QAAQ,aAAa,EAAE,YAAY,QAAQ,WAAW,IAAI,CAAC;AAAA,MAChE;AAEA,YAAM,aAAa,qBAAqB,MAAM,UAAU;AAExD,UAAI,WAAW,WAAW,GAAG;AAC5B,gBAAQ;AAAA,UACP;AAAA,QACD;AACA;AAAA,MACD;AAGA,YAAM,gBAAgB,mBAAmB,QAAQ,GAAG,EAAE;AAAA,QACrD,CAAC,MAAM,EAAE;AAAA,MACV;AACA,YAAM,WAAW;AAAA,QAChB;AAAA,QACA,QAAQ;AAAA,MACT;AACA,YAAM,UAAU,sBAAsB,MAAM;AAAA,QAC3C,GAAG;AAAA,QACH,MAAM;AAAA,MACP,CAAC;AAED,YAAM,OAAO,mBAAmB,QAAQ,KAAK,UAAU,OAAO;AAE9D,cAAQ,IAAI,6BAAwB,KAAK,IAAI,EAAE;AAC/C,cAAQ,IAAI,kBAAkB,WAAW,MAAM,EAAE;AACjD,cAAQ,IAAI,gBAAgB,KAAK,SAAS,MAAM,GAAG,EAAE,CAAC,KAAK;AAAA,IAC5D,CAAC;AAAA,EACF,CAAC;AACH;AAED,IAAM,eAAe,IAAIA,SAAQ,OAAO,EACtC,YAAY,0BAA0B,EACtC,eAAe,kBAAkB,oCAAoC,EACrE,OAAO,gBAAgB,wBAAwB,sBAAsB,EACrE,OAAO,aAAa,0CAA0C,EAC9D;AAAA,EAAO,CAAC,YACR,iBAAiB,YAAY;AAC5B,YAAQ,IAAI,+BAAwB;AACpC,YAAQ,IAAI,gBAAgB,YAAY,QAAQ,EAAE,CAAC,EAAE;AACrD,YAAQ,IAAI,iBAAiB,QAAQ,GAAG,EAAE;AAC1C,YAAQ,IAAI,EAAE;AAEd,UAAM,gBAAgB,QAAQ,IAAI,OAAO,SAAS;AAEjD,YAAM,sBAAsB,IAAI;AAEhC,YAAM,kBAAkB,MAAM,OAAO,WAAW;AAE/C,cAAM,UAAU,MAAM,qBAAqB,MAAyB;AACpE,cAAM,aAAa,IAAI,IAAI,QAAQ,IAAI,CAAC,MAAM,CAAC,EAAE,MAAM,EAAE,QAAQ,CAAC,CAAC;AAGnE,cAAM,QAAQ,mBAAmB,QAAQ,GAAG;AAE5C,YAAI,MAAM,WAAW,GAAG;AACvB,kBAAQ,IAAI,+BAA+B,QAAQ,GAAG,EAAE;AACxD;AAAA,QACD;AAGA,mBAAW,QAAQ,OAAO;AACzB,gBAAM,mBAAmB,WAAW,IAAI,KAAK,IAAI;AACjD,cACC,qBAAqB,UACrB,qBAAqB,KAAK,UACzB;AACD,kBAAM,IAAI;AAAA,cACT,yBAAyB,KAAK,IAAI;AAAA,eACjB,gBAAgB;AAAA,eAChB,KAAK,QAAQ;AAAA;AAAA;AAAA,YAE/B;AAAA,UACD;AAAA,QACD;AAGA,cAAM,UAAU,MAAM,OAAO,CAAC,MAAM,CAAC,WAAW,IAAI,EAAE,IAAI,CAAC;AAE3D,YAAI,QAAQ,WAAW,GAAG;AACzB,kBAAQ,IAAI,wCAAmC;AAC/C;AAAA,QACD;AAEA,YAAI,QAAQ,QAAQ;AACnB,kBAAQ,IAAI,GAAG,QAAQ,MAAM,wBAAwB;AACrD,qBAAW,QAAQ,SAAS;AAC3B,oBAAQ,IAAI,QAAQ,KAAK,IAAI,EAAE;AAAA,UAChC;AACA;AAAA,QACD;AAGA,YAAI,eAAe;AACnB,mBAAW,QAAQ,SAAS;AAC3B,kBAAQ,IAAI,eAAe,KAAK,IAAI,KAAK;AAGzC,gBAAM,SAAS,mBAAmB,KAAK,OAAO;AAC9C,gBAAM,aAAa,OAAO,aAAa,OAAO,gBAAgB;AAI9D,gBAAM,UAAU,MAAM;AAAA,YACrB;AAAA,UACD;AACA,gBAAM,cAAc,OAAO,gBAAgB;AAG3C,cAAI;AACH,kBAAM,OAAO,MAAM,OAAO;AAE1B,uBAAW,QAAQ,YAAY;AAC9B,oBAAM,OAAO,MAAM,IAAI;AAAA,YACxB;AAEA,kBAAM;AAAA,cACL;AAAA,cACA,KAAK;AAAA,cACL,KAAK;AAAA,cACL;AAAA,cACA;AAAA,YACD;AAEA,kBAAM,OAAO,MAAM,QAAQ;AAAA,UAC5B,SAAS,YAAY;AACpB,gBAAI;AACJ,gBAAI;AACH,oBAAM,OAAO,MAAM,UAAU;AAAA,YAC9B,SAAS,GAAG;AACX,8BAAgB;AAAA,YACjB;AACA,kBAAM,UAAU,gBAAgB,UAAU;AAC1C,gBAAI,kBAAkB,QAAW;AAChC,sBAAQ;AAAA,gBACP,kCAAkC,gBAAgB,aAAa,EAAE,OAAO;AAAA,cACzE;AAAA,YACD;AACA,kBAAM;AAAA,UACP;AAEA;AACA,kBAAQ,IAAI,qBAAgB,KAAK,IAAI,EAAE;AAAA,QACxC;AAEA,gBAAQ;AAAA,UACP;AAAA,SAAO,YAAY;AAAA,QACpB;AAAA,MACD,CAAC;AAAA,IACF,CAAC;AAAA,EACF,CAAC;AACF;AAED,IAAM,kBAAkB,IAAIA,SAAQ,UAAU,EAC5C,YAAY,8BAA8B,EAC1C,SAAS,WAAW,mCAAmC,EACvD,eAAe,kBAAkB,oCAAoC,EACrE,OAAO,gBAAgB,wBAAwB,sBAAsB,EACrE,OAAO,WAAW,wDAAwD,EAC1E;AAAA,EACA,CACC,UACA,YACI;AACJ,UAAM,QAAQ,OAAO,SAAS,UAAU,EAAE;AAC1C,QAAI,OAAO,MAAM,KAAK,KAAK,QAAQ,GAAG;AACrC,cAAQ,MAAM,yCAAoC;AAClD,cAAQ,KAAK,CAAC;AAAA,IACf;AAEA,WAAO,iBAAiB,YAAY;AACnC,cAAQ,IAAI,uBAAkB,KAAK,eAAe;AAClD,cAAQ,IAAI,gBAAgB,YAAY,QAAQ,EAAE,CAAC,EAAE;AACrD,cAAQ,IAAI,iBAAiB,QAAQ,GAAG,EAAE;AAC1C,cAAQ,IAAI,EAAE;AAEd,YAAM,gBAAgB,QAAQ,IAAI,OAAO,SAAS;AACjD,cAAM,sBAAsB,IAAI;AAEhC,cAAM,kBAAkB,MAAM,OAAO,WAAW;AAE/C,gBAAM,UAAU,MAAM;AAAA,YACrB;AAAA,UACD;AACA,gBAAM,aAAa,CAAC,GAAG,OAAO,EAAE;AAAA,YAAK,CAAC,GAAG,MACxC,EAAE,KAAK,cAAc,EAAE,IAAI;AAAA,UAC5B;AAGA,cAAI,QAAQ,WAAW,QAAQ;AAC9B,kBAAM,IAAI;AAAA,cACT,oBAAoB,KAAK,6BAAwB,WAAW,MAAM;AAAA,YACnE;AAAA,UACD;AAEA,gBAAM,aAAa,WAAW,MAAM,GAAG,KAAK;AAG5C,gBAAM,QAAQ,mBAAmB,QAAQ,GAAG;AAC5C,gBAAM,UAAU,IAAI,IAAI,MAAM,IAAI,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC,CAAC,CAAC;AAErD,cAAI,aAAa;AACjB,qBAAW,UAAU,YAAY;AAChC,kBAAM,OAAO,QAAQ,IAAI,OAAO,IAAI;AACpC,gBAAI,CAAC,MAAM;AACV,oBAAM,IAAI;AAAA,gBACT,qCAAqC,OAAO,IAAI;AAAA,cACjD;AAAA,YACD;AAGA,gBAAI,KAAK,aAAa,OAAO,UAAU;AACtC,oBAAM,IAAI;AAAA,gBACT,yBAAyB,OAAO,IAAI;AAAA,eACnB,OAAO,QAAQ;AAAA,eACf,KAAK,QAAQ;AAAA;AAAA;AAAA,cAE/B;AAAA,YACD;AAGA,kBAAM,SAAS,mBAAmB,KAAK,OAAO;AAG9C,gBAAI,CAAC,OAAO,SAAS;AACpB,oBAAM,IAAI;AAAA,gBACT,aAAa,OAAO,IAAI;AAAA;AAAA,cAEzB;AAAA,YACD;AAGA,kBAAM,YAAY,OAAO,eAAe,OAAO,gBAAgB;AAC/D,gBAAI,UAAU,WAAW,KAAK,CAAC,QAAQ,OAAO;AAC7C,oBAAM,IAAI;AAAA,gBACT,aAAa,OAAO,IAAI;AAAA;AAAA,cAEzB;AAAA,YACD;AAGA,kBAAM,+BACL,OAAO,gBAAgB,SAAS,kBAAkB,SAAS;AAC5D,iBACE,OAAO,gBAAgB,SACvB,iCACD,CAAC,QAAQ,OACR;AACD,kBAAI,OAAO,gBAAgB,MAAM;AAChC,sBAAM,IAAI;AAAA,kBACT,aAAa,OAAO,IAAI;AAAA;AAAA,gBAEzB;AAAA,cACD;AACA,kBAAI,8BAA8B;AACjC,sBAAM,IAAI;AAAA,kBACT,aAAa,OAAO,IAAI;AAAA;AAAA,gBAEzB;AAAA,cACD;AACA,oBAAM,IAAI;AAAA,gBACT,aAAa,OAAO,IAAI;AAAA;AAAA,cAEzB;AAAA,YACD;AAGA,gBAAI,UAAU,SAAS,KAAK,QAAQ,OAAO;AAC1C,sBAAQ,IAAI,mBAAmB,OAAO,IAAI,KAAK;AAAA,YAChD;AAEA,gBAAI;AACH,oBAAM,OAAO,MAAM,OAAO;AAE1B,yBAAW,QAAQ,WAAW;AAC7B,sBAAM,OAAO,MAAM,IAAI;AAAA,cACxB;AAEA,oBAAM;AAAA,gBACL;AAAA,gBACA,OAAO;AAAA,cACR;AAEA,oBAAM,OAAO,MAAM,QAAQ;AAAA,YAC5B,SAAS,eAAe;AACvB,kBAAI;AACJ,kBAAI;AACH,sBAAM,OAAO,MAAM,UAAU;AAAA,cAC9B,SAAS,GAAG;AACX,uCAAuB;AAAA,cACxB;AACA,oBAAM,UAAU,gBAAgB,aAAa;AAC7C,kBAAI,yBAAyB,QAAW;AACvC,wBAAQ;AAAA,kBACP,kCAAkC,gBAAgB,oBAAoB,EAAE,OAAO;AAAA,gBAChF;AAAA,cACD;AACA,oBAAM;AAAA,YACP;AAEA;AACA,oBAAQ,IAAI,yBAAoB,OAAO,IAAI,EAAE;AAAA,UAC9C;AAEA,kBAAQ;AAAA,YACP;AAAA,SAAO,UAAU;AAAA,UAClB;AAAA,QACD,CAAC;AAAA,MACF,CAAC;AAAA,IACF,CAAC;AAAA,EACF;AACD;AAED,IAAM,gBAAgB,IAAIA,SAAQ,QAAQ,EACxC,YAAY,uBAAuB,EACnC,eAAe,kBAAkB,oCAAoC,EACrE,OAAO,gBAAgB,wBAAwB,sBAAsB,EACrE,OAAO,UAAU,gBAAgB,EACjC;AAAA,EAAO,CAAC,YACR,iBAAiB,YAAY;AAC5B,UAAM,gBAAgB,QAAQ,IAAI,OAAO,SAAS;AACjD,YAAM,sBAAsB,IAAI;AAEhC,YAAM,UAAU,MAAM,qBAAqB,IAAI;AAC/C,YAAM,aAAa,IAAI,IAAI,QAAQ,IAAI,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC,CAAC,CAAC;AAE1D,YAAM,QAAQ,mBAAmB,QAAQ,GAAG;AAE5C,YAAM,WAID,MAAM,IAAI,CAAC,SAAS;AACxB,cAAM,SAAS,WAAW,IAAI,KAAK,IAAI;AACvC,YAAI,WAAW,QAAW;AACzB,iBAAO;AAAA,YACN,MAAM,KAAK;AAAA,YACX,QAAQ;AAAA,UACT;AAAA,QACD;AACA,YAAI,OAAO,aAAa,KAAK,UAAU;AACtC,iBAAO;AAAA,YACN,MAAM,KAAK;AAAA,YACX,QAAQ;AAAA,YACR,WAAW,OAAO;AAAA,UACnB;AAAA,QACD;AACA,eAAO;AAAA,UACN,MAAM,KAAK;AAAA,UACX,QAAQ;AAAA,UACR,WAAW,OAAO;AAAA,QACnB;AAAA,MACD,CAAC;AAGD,iBAAW,UAAU,SAAS;AAC7B,YAAI,CAAC,MAAM,KAAK,CAAC,MAAM,EAAE,SAAS,OAAO,IAAI,GAAG;AAC/C,mBAAS,KAAK;AAAA,YACb,MAAM,OAAO;AAAA,YACb,QAAQ;AAAA,YACR,WAAW,OAAO;AAAA,UACnB,CAAC;AAAA,QACF;AAAA,MACD;AAEA,UAAI,QAAQ,MAAM;AACjB,gBAAQ,IAAI,KAAK,UAAU,UAAU,MAAM,CAAC,CAAC;AAAA,MAC9C,OAAO;AACN,gBAAQ,IAAI,kBAAkB;AAC9B,gBAAQ,IAAI,gBAAgB,YAAY,QAAQ,EAAE,CAAC,EAAE;AACrD,gBAAQ,IAAI,iBAAiB,QAAQ,GAAG,EAAE;AAC1C,gBAAQ,IAAI,EAAE;AAEd,YAAI,SAAS,WAAW,GAAG;AAC1B,kBAAQ,IAAI,sBAAsB;AAAA,QACnC,OAAO;AACN,qBAAW,KAAK,UAAU;AACzB,kBAAM,OACL,EAAE,WAAW,YACV,WACA,EAAE,WAAW,YACZ,WACA,EAAE,WAAW,sBACZ,iBACA;AACN,kBAAM,SACL,eAAe,KAAK,EAAE,YACnB,cAAc,EAAE,UAAU,YAAY,CAAC,MACvC;AACJ,oBAAQ,IAAI,KAAK,IAAI,IAAI,EAAE,IAAI,WAAM,EAAE,MAAM,GAAG,MAAM,EAAE;AAAA,UACzD;AAEA,gBAAM,eAAe,SAAS;AAAA,YAC7B,CAAC,MAAM,EAAE,WAAW;AAAA,UACrB,EAAE;AACF,gBAAM,eAAe,SAAS;AAAA,YAC7B,CAAC,MAAM,EAAE,WAAW;AAAA,UACrB,EAAE;AAEF,kBAAQ,IAAI,EAAE;AACd,kBAAQ;AAAA,YACP,UAAU,SAAS,MAAM,eAAe,YAAY,eAAe,YAAY;AAAA,UAChF;AAAA,QACD;AAAA,MACD;AAAA,IACD,CAAC;AAAA,EACF,CAAC;AACF;AAMM,IAAM,iBAAiB,IAAIA,SAAQ,SAAS,EACjD,YAAY,+BAA+B,EAC3C,WAAW,UAAU,EACrB,WAAW,YAAY,EACvB,WAAW,eAAe,EAC1B,WAAW,aAAa;;;AE9lB1B;AAAA,EACC,8BAAAC;AAAA,EACA,sBAAAC;AAAA,EACA;AAAA,EACA,wBAAAC;AAAA,EACA;AAAA,OACM;AACP,SAAS,WAAAC,gBAAe;;;ACgBxB,eAAsB,WACrB,MACA,YACA,SAC8B;AAC9B,MAAI,WAAW,WAAW,GAAG;AAC5B,WAAO,EAAE,oBAAoB,GAAG,QAAQ,SAAS,UAAU,MAAM;AAAA,EAClE;AAEA,MAAI,SAAS,QAAQ;AACpB,WAAO,EAAE,oBAAoB,WAAW,QAAQ,QAAQ,KAAK;AAAA,EAC9D;AAEA,MAAI;AACJ,MAAI;AACH,aAAS,MAAM,KAAK,QAAQ;AAC5B,UAAM,OAAO,MAAM,OAAO;AAE1B,eAAW,QAAQ,YAAY;AAC9B,YAAM,OAAO,MAAM,IAAI;AAAA,IACxB;AAEA,UAAM,OAAO,MAAM,QAAQ;AAC3B,WAAO,EAAE,oBAAoB,WAAW,QAAQ,QAAQ,MAAM;AAAA,EAC/D,SAAS,OAAO;AACf,QAAI,QAAQ;AACX,YAAM,OAAO,MAAM,UAAU;AAAA,IAC9B;AACA,UAAM;AAAA,EACP,UAAE;AACD,QAAI,QAAQ;AACX,aAAO,QAAQ;AAAA,IAChB;AAAA,EACD;AACD;;;AD5CA,IAAM,mBAAmB;AAElB,IAAM,cAAc,IAAIC,SAAQ,MAAM,EAC3C,YAAY,+CAA+C,EAC3D;AAAA,EACA;AAAA,EACA;AACD,EACC,eAAe,kBAAkB,oCAAoC,EACrE,OAAO,wBAAwB,wCAAwC,EACvE,OAAO,UAAU,sDAAsD,EACvE,OAAO,aAAa,6BAA6B,EACjD,OAAO,UAAU,gBAAgB,EACjC;AAAA,EACA,OAAO,YAOD;AACL,UAAM,aAAa,QAAQ,UAAU;AACrC,UAAM,cAAc,YAAY,QAAQ,EAAE;AAE1C,QAAI,CAAC,QAAQ,MAAM;AAClB,cAAQ;AAAA,QACP,6BAAsB,UAAU,GAAG,QAAQ,OAAO,mBAAmB,EAAE;AAAA,MACxE;AACA,cAAQ,IAAI,gBAAgB,WAAW,EAAE;AACzC,UAAI,QAAQ,YAAY;AACvB,gBAAQ,IAAI,cAAc,QAAQ,UAAU,EAAE;AAAA,MAC/C;AACA,UAAI,QAAQ,QAAQ;AACnB,gBAAQ,IAAI,+CAA+C;AAAA,MAC5D;AACA,cAAQ,IAAI,EAAE;AAAA,IACf;AAEA,QAAI;AACH,YAAM,SAAS,MAAM,WAAW,UAAU;AAC1C,YAAM,cAAc,OAAO;AAE3B,YAAM,EAAE,KAAK,IAAI,MAAM,mBAAmB,QAAQ,EAAE;AAEpD,UAAI;AACH,YAAI,QAAQ,MAAM;AAEjB,gBAAM,aAAa,YAAY,aAAa;AAAA,YAC3C,uBAAuB;AAAA,YACvB,GAAI,QAAQ,aAAa,EAAE,YAAY,QAAQ,WAAW,IAAI,CAAC;AAAA,YAC/D,GAAI,OAAO,aAAa,SACrB,EAAE,QAAQ,2BAA2B,OAAO,QAAQ,EAAE,IACtD,CAAC;AAAA,UACL,CAAC;AAED,gBAAM,oBACL,gCAAgC,gBAAgB;AACjD,gBAAM,WAAW,WAAW;AAAA,YAC3B,CAAC,SAAS,CAAC,kBAAkB,KAAK,IAAI;AAAA,UACvC;AAEA,uBAAa,UAAU,OAAO;AAE9B,gBAAM,SAAS,MAAM,WAAW,MAAM,UAAU;AAAA,YAC/C,GAAI,QAAQ,WAAW,SACpB,EAAE,QAAQ,QAAQ,OAAO,IACzB,CAAC;AAAA,UACL,CAAC;AAED,cAAI,QAAQ,MAAM;AACjB,kBAAM,gBAAgB,WACpB,OAAO,CAAC,MAAM,gBAAgB,KAAK,CAAC,CAAC,EACrC,OAAO,CAAC,MAAM,CAAC,kBAAkB,KAAK,CAAC,CAAC,EACxC,IAAI,uBAAuB,EAC3B,OAAO,CAAC,MAAmB,MAAM,MAAS;AAC5C,oBAAQ;AAAA,cACP,KAAK;AAAA,gBACJ;AAAA,kBACC,QAAQ,QAAQ,SAAS,YAAY;AAAA,kBACrC,QAAQ;AAAA,kBACR,eAAe,cAAc;AAAA,kBAC7B,oBAAoB,OAAO;AAAA,gBAC5B;AAAA,gBACA;AAAA,gBACA;AAAA,cACD;AAAA,YACD;AAAA,UACD,WAAW,CAAC,QAAQ,QAAQ;AAC3B,oBAAQ;AAAA,cACP;AAAA,wBAAsB,OAAO,kBAAkB;AAAA,YAChD;AAAA,UACD;AAAA,QACD,OAAO;AAEN,gBAAM,UAAUC,oBAAmB,IAAI;AACvC,gBAAM,iBAAiB;AAAA,YACtB,GAAI,QAAQ,aAAa,EAAE,QAAQ,QAAQ,WAAW,IAAI,CAAC;AAAA,YAC3D,GAAI,OAAO,WAAW,EAAE,UAAU,OAAO,SAAS,IAAI,CAAC;AAAA,YACvD,WAAW,CAAC,YAAoB,QAAQ,KAAK,iBAAO,OAAO,EAAE;AAAA,UAC9D;AACA,gBAAM,OAAO,MAAMC;AAAA,YAClB;AAAA,YACA;AAAA,YACA;AAAA,UACD;AAGA,gBAAM,aAAaC,sBAAqB,MAAM;AAAA,YAC7C,oBAAoB;AAAA,YACpB,GAAI,QAAQ,aAAa,EAAE,YAAY,QAAQ,WAAW,IAAI,CAAC;AAAA,UAChE,CAAC;AAGD,gBAAM,iBAAiB,KAAK,QAAQ,OAAO,CAAC,MAAM,EAAE,WAAW;AAE/D,cAAI,CAAC,QAAQ,QAAQ,eAAe,SAAS,GAAG;AAC/C,oBAAQ;AAAA,cACP,iBAAO,eAAe,MAAM;AAAA,YAC7B;AACA,uBAAW,UAAU,gBAAgB;AACpC,sBAAQ,IAAI,QAAQ,OAAO,OAAO,EAAE;AAAA,YACrC;AACA,oBAAQ,IAAI,EAAE;AAAA,UACf;AAEA,cAAI,WAAW,WAAW,GAAG;AAC5B,gBAAI,QAAQ,MAAM;AACjB,sBAAQ;AAAA,gBACP,KAAK;AAAA,kBACJ;AAAA,oBACC,QAAQ;AAAA,oBACR,oBAAoB;AAAA,oBACpB,gBAAgB,eAAe;AAAA,kBAChC;AAAA,kBACA;AAAA,kBACA;AAAA,gBACD;AAAA,cACD;AAAA,YACD,OAAO;AACN,sBAAQ,IAAI,uDAA6C;AAAA,YAC1D;AAEA;AAAA,UACD;AAEA,uBAAa,YAAY,OAAO;AAEhC,gBAAM,SAAS,MAAM,WAAW,MAAM,YAAY;AAAA,YACjD,GAAI,QAAQ,WAAW,SACpB,EAAE,QAAQ,QAAQ,OAAO,IACzB,CAAC;AAAA,UACL,CAAC;AAED,cAAI,CAAC,QAAQ,QAAQ;AACpB,kBAAM,gBAAgB,MAAMD;AAAA,cAC3B;AAAA,cACA;AAAA,cACA;AAAA,gBACC,GAAG;AAAA,gBACH,uBAAuB;AAAA,cACxB;AAAA,YACD;AACA,4CAAgC,eAAe,cAAc;AAAA,UAC9D;AAEA,cAAI,QAAQ,MAAM;AACjB,oBAAQ;AAAA,cACP,KAAK;AAAA,gBACJ;AAAA,kBACC,QAAQ,QAAQ,SAAS,YAAY;AAAA,kBACrC,oBAAoB,OAAO;AAAA,kBAC3B,gBAAgB,eAAe;AAAA,gBAChC;AAAA,gBACA;AAAA,gBACA;AAAA,cACD;AAAA,YACD;AAAA,UACD,WAAW,CAAC,QAAQ,QAAQ;AAC3B,oBAAQ;AAAA,cACP;AAAA,wBAAsB,OAAO,kBAAkB;AAAA,YAChD;AAAA,UACD;AAAA,QACD;AAAA,MAED,UAAE;AACD,cAAM,KAAK,IAAI;AAAA,MAChB;AAAA,IACD,SAAS,OAAO;AACf,YAAM,UACL,iBAAiB,QAAQ,MAAM,UAAU;AAE1C,UAAI,QAAQ,MAAM;AACjB,gBAAQ;AAAA,UACP,KAAK,UAAU,EAAE,QAAQ,SAAS,OAAO,QAAQ,GAAG,MAAM,CAAC;AAAA,QAC5D;AAAA,MACD,OAAO;AACN,gBAAQ,MAAM,iBAAY,OAAO,EAAE;AAAA,MACpC;AACA,cAAQ,KAAK,CAAC;AAAA,IACf;AAAA,EACD;AACD;AAkBM,SAAS,gCAAgC,WAA2B;AAC1E,QAAM,UAAU,UAAU,QAAQ,wBAAwB,MAAM;AAChE,SAAO,IAAI;AAAA,IACV,iEAAiE,OAAO;AAAA,IACxE;AAAA,EACD;AACD;AAUO,SAAS,wBAAwB,WAA2B;AAClE,QAAM,QAAQ,UAAU,MAAM,qCAAqC;AACnE,SAAO,QAAQ,CAAC,KAAK;AACtB;AAEA,SAAS,aACR,YACA,SACO;AACP,MAAI,QAAQ,UAAU,CAAC,QAAQ,MAAM;AACpC,YAAQ,IAAI,eAAe,WAAW,MAAM;AAAA,CAAiB;AAC7D,eAAW,QAAQ,YAAY;AAC9B,cAAQ,IAAI,GAAG,IAAI;AAAA,CAAK;AAAA,IACzB;AAAA,EACD;AACD;AAEA,SAAS,gCACR,eACA,gBACO;AACP,QAAM,cAAc,IAAI,IAAI,eAAe,IAAI,cAAc,CAAC;AAC9D,QAAM,aAAa,cAAc,QAAQ;AAAA,IACxC,CAAC,WACA,EAAE,OAAO,eAAe,YAAY,IAAI,eAAe,MAAM,CAAC;AAAA,EAChE;AACA,MAAI,WAAW,WAAW,EAAG;AAE7B,QAAM,IAAI;AAAA,IACT,6CAA6C,WAAW,MAAM;AAAA,IAC7D,WAAW,IAAI,uBAAuB,EAAE,KAAK,IAAI;AAAA,EACnD;AACD;AAEA,SAAS,eAAe,QAA8B;AACrD,SAAO,KAAK,UAAU;AAAA,IACrB,OAAO;AAAA,IACP,OAAO;AAAA,IACP,OAAO,UAAU;AAAA,IACjB,OAAO;AAAA,IACP,OAAO;AAAA,EACR,CAAC;AACF;AAEA,SAAS,wBAAwB,QAA8B;AAC9D,QAAM,SAAS,OAAO,SACnB,GAAG,OAAO,KAAK,IAAI,OAAO,MAAM,KAChC,OAAO,SAAS;AACnB,SAAO,QAAQ,OAAO,OAAO,KAAK,OAAO,IAAI,KAAK,MAAM;AACzD;;;AE5SA,SAAS,gBAAAE,qBAAoB;AAE7B,SAAS,WAAAC,gBAAe;AA+BjB,IAAM,cAAc,IAAIC,SAAQ,MAAM,EAC3C,YAAY,0DAA0D,EACtE,OAAO,uBAAuB,4CAA4C,EAC1E;AAAA,EACA;AAAA,EACA;AACD,EACC,OAAO,sBAAsB,8CAA8C,EAC3E;AAAA,EACA;AAAA,EACA;AACD,EACC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACD,EACC;AAAA,EACA;AAAA,EACA;AACD,EACC;AAAA,EACA;AAAA,EACA;AACD,EACC;AAAA,EACA;AAAA,EACA;AACD,EACC,OAAO,WAAW,oDAAoD,EACtE,OAAO,UAAU,kDAAkD,EACnE;AAAA,EACA;AAAA,EACA;AACD,EACC;AAAA,EACA;AAAA,EACA;AACD,EACC,OAAO,OAAO,YAAyB;AAEvC,MAAI,QAAQ,QAAQ;AACnB,WAAO,cAAc,QAAQ,MAAM;AAAA,EACpC;AAEA,SAAO,KAAK;AACZ,MAAI;AAEH,QAAI;AACJ,QAAI;AAEJ,QAAI,QAAQ,QAAQ;AACnB,eAAS,MAAM,WAAW,QAAQ,MAAM;AACxC,mBAAa,QAAQ;AAAA,IACtB,OAAO;AACN,YAAM,SAAS,MAAM,kBAAkB;AACvC,eAAS,OAAO;AAChB,mBAAa,OAAO;AAAA,IACrB;AAGA,QAAI,QAAQ,UAAU,CAAC,QAAQ,OAAO;AACrC,YAAM,IAAI;AAAA,QACT;AAAA,MACD;AAAA,IACD;AAIA,QAAI,QAAQ,UAAU,CAAC,QAAQ,QAAQ,CAAC,QAAQ,OAAO;AACtD,YAAM,IAAI,MAAM,kDAAkD;AAAA,IACnE;AAKA,QAAI,QAAQ,KAAK;AAChB,yBAAmB,QAAQ,KAAK,QAAQ;AAAA,IACzC;AAGA,UAAM,WAAW,oBAAoB,QAAQ,MAAM,KAAK,OAAO;AAG/D,QAAI,QAAQ,QAAQ,QAAQ,OAAO;AAClC,YAAM,EAAE,aAAa,IAAI,MAAM,OAAO,iBAAkB;AACxD,YAAM,UAAoB,CAAC;AAG3B,UAAI,QAAQ,KAAK;AAChB,gBAAQ,KAAK,QAAQ,QAAQ,GAAG,EAAE;AAAA,MACnC;AAGA,UAAI,QAAQ,QAAQ;AACnB,mBAAW,QAAQ,QAAQ,QAAQ;AAClC,kBAAQ,KAAK,WAAW,IAAI,EAAE;AAAA,QAC/B;AAAA,MACD;AAEA,UAAI,QAAQ,MAAM;AACjB,gBAAQ,KAAK,QAAQ,IAAI;AAAA,MAC1B;AAEA,UAAI,QAAQ,OAAO;AAElB,YAAI;AACJ,YAAI;AACH,oBAAUC,cAAa,QAAQ,OAAO,OAAO;AAAA,QAC9C,SAAS,KAAK;AACb,gBAAM,aACL,eAAe,SACf,UAAU,OACT,IAA8B,SAAS;AACzC,gBAAM,IAAI;AAAA,YACT,aACG,yBAAyB,QAAQ,KAAK,KACtC,8BAA8B,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,CAAC;AAAA,UAClF;AAAA,QACD;AACA,gBAAQ,KAAK,GAAG,QAAQ,MAAM,IAAI,CAAC;AAAA,MACpC;AAEA,YAAM,aAAa;AAAA,QAClB;AAAA,QACA;AAAA,QACA;AAAA,QACA,QAAQ,QAAQ,UAAU;AAAA,QAC1B,GAAI,QAAQ,MAAM,EAAE,aAAa,QAAQ,GAAG;AAAA,QAC5C,GAAI,QAAQ,UAAU,EAAE,YAAY,QAAQ,OAAO;AAAA,QACnD,GAAI,YAAY,EAAE,SAAS;AAAA,MAC5B,CAAC;AACD;AAAA,IACD;AAGA,UAAM,EAAE,UAAU,IAAI,MAAM,OAAO,oBAAkB;AAGrD,UAAM,UAAU;AAAA,MACf;AAAA,MACA;AAAA,MACA,GAAI,QAAQ,MAAM,EAAE,aAAa,QAAQ,GAAG;AAAA,MAC5C,GAAI,QAAQ,OAAO,EAAE,mBAAmB,QAAQ,IAAI;AAAA,MACpD,GAAI,QAAQ,SAAS,EAAE,kBAAkB,KAAK;AAAA,MAC9C,GAAI,QAAQ,QAAQ,EAAE,iBAAiB,KAAK;AAAA,MAC5C,GAAI,YAAY,EAAE,SAAS;AAAA,IAC5B,CAAC;AAAA,EACF,SAAS,OAAO;AACf,UAAM,UAAU,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;AACrE,YAAQ,MAAM,UAAK,OAAO,EAAE;AAC5B,YAAQ,KAAK,CAAC;AAAA,EACf;AACD,CAAC;AAEF,SAAS,oBACR,QACuB;AACvB,UAAQ,QAAQ;AAAA,IACf,KAAK;AACJ,aAAO;AAAA,IACR,KAAK;AACJ,aAAO;AAAA,IACR,KAAK;AACJ,aAAO;AAAA,IACR;AACC,aAAO;AAAA,EACT;AACD;;;ACzMA;AAAA,EACC,8BAAAC;AAAA,EACA,sBAAAC;AAAA,OACM;AACP,SAAS,WAAAC,gBAAe;;;AC8ExB,IAAM,kBAGF;AAAA,EACH,cAAc,EAAE,MAAM,uBAAuB,UAAU,QAAQ;AAAA,EAC/D,YAAY,EAAE,MAAM,2BAA2B,UAAU,UAAU;AAAA,EACnE,YAAY,EAAE,MAAM,wBAAwB,UAAU,QAAQ;AAAA,EAC9D,aAAa,EAAE,MAAM,4BAA4B,UAAU,OAAO;AAAA,EAClE,mBAAmB,EAAE,MAAM,iBAAiB,UAAU,QAAQ;AAAA,EAC9D,uBAAuB,EAAE,MAAM,qBAAqB,UAAU,UAAU;AAAA,EACxE,sBAAsB,EAAE,MAAM,oBAAoB,UAAU,UAAU;AAAA,EACtE,qBAAqB,EAAE,MAAM,mBAAmB,UAAU,UAAU;AAAA,EACpE,iBAAiB,EAAE,MAAM,wBAAwB,UAAU,QAAQ;AAAA,EACnE,kBAAkB,EAAE,MAAM,wBAAwB,UAAU,QAAQ;AAAA,EACpE,iBAAiB,EAAE,MAAM,oBAAoB,UAAU,QAAQ;AAAA,EAC/D,kBAAkB,EAAE,MAAM,wBAAwB,UAAU,UAAU;AAAA,EACtE,mBAAmB,EAAE,MAAM,yBAAyB,UAAU,UAAU;AAAA,EACxE,cAAc,EAAE,MAAM,uBAAuB,UAAU,UAAU;AAAA,EACjE,YAAY,EAAE,MAAM,2BAA2B,UAAU,OAAO;AAAA;AAAA,EAEhE,sBAAsB,EAAE,MAAM,uBAAuB,UAAU,UAAU;AAAA,EACzE,uBAAuB,EAAE,MAAM,2BAA2B,UAAU,OAAO;AAAA;AAAA,EAE3E,aAAa,EAAE,MAAM,sBAAsB,UAAU,QAAQ;AAAA,EAC7D,sBAAsB,EAAE,MAAM,uBAAuB,UAAU,UAAU;AAAA,EACzE,WAAW,EAAE,MAAM,0BAA0B,UAAU,UAAU;AAAA;AAAA,EAEjE,wBAAwB,EAAE,MAAM,sBAAsB,UAAU,UAAU;AAAA,EAC1E,uBAAuB,EAAE,MAAM,qBAAqB,UAAU,UAAU;AAAA;AAAA,EAExE,aAAa,EAAE,MAAM,oBAAoB,UAAU,OAAO;AAAA,EAC1D,cAAc,EAAE,MAAM,oBAAoB,UAAU,OAAO;AAAA;AAAA,EAE3D,kBAAkB,EAAE,MAAM,qBAAqB,UAAU,QAAQ;AAAA,EACjE,gBAAgB,EAAE,MAAM,qBAAqB,UAAU,OAAO;AAAA,EAC9D,iBAAiB,EAAE,MAAM,oBAAoB,UAAU,UAAU;AAAA,EACjE,gBAAgB,EAAE,MAAM,qBAAqB,UAAU,UAAU;AAAA,EACjE,eAAe,EAAE,MAAM,oBAAoB,UAAU,OAAO;AAAA;AAAA,EAE5D,qBAAqB;AAAA,IACpB,MAAM;AAAA,IACN,UAAU;AAAA,EACX;AAAA;AAAA,EAEA,YAAY,EAAE,MAAM,wBAAwB,UAAU,UAAU;AAAA,EAChE,aAAa,EAAE,MAAM,wBAAwB,UAAU,UAAU;AAAA,EACjE,eAAe,EAAE,MAAM,wBAAwB,UAAU,UAAU;AAAA,EACnE,aAAa,EAAE,MAAM,4BAA4B,UAAU,UAAU;AACtE;AAEA,SAAS,mBAAmB,QAAkC;AAC7D,QAAM,UAAU,gBAAgB,OAAO,IAAI,KAAK;AAAA,IAC/C,MAAM;AAAA,IACN,UAAU;AAAA,EACX;AACA,SAAO;AAAA,IACN,UAAU,QAAQ;AAAA,IAClB,MAAM,QAAQ;AAAA,IACd,OAAO,OAAO;AAAA,IACd,GAAI,OAAO,WAAW,SAAY,EAAE,QAAQ,OAAO,OAAO,IAAI,CAAC;AAAA,IAC/D,SAAS,OAAO;AAAA,EACjB;AACD;AAaO,SAAS,eACf,MACA,cACA,UACe;AACf,QAAM,SAAS,KAAK,QAAQ,IAAI,kBAAkB;AAGlD,QAAM,gBAA+C;AAAA,IACpD,OAAO;AAAA,IACP,SAAS;AAAA,IACT,MAAM;AAAA,EACP;AACA,SAAO;AAAA,IACN,CAAC,GAAe,MACf,cAAc,EAAE,QAAQ,IAAI,cAAc,EAAE,QAAQ;AAAA,EACtD;AAEA,QAAM,YAAY,OAAO,KAAK,CAAC,MAAkB,EAAE,aAAa,OAAO;AAEvE,SAAO;AAAA,IACN,OAAO,CAAC;AAAA,IACR;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACD;AACD;AASO,SAAS,mBAAmB,QAA8B;AAChE,QAAM,QAAkB,CAAC;AAEzB,MAAI,OAAO,OAAO;AACjB,UAAM,KAAK,gCAA2B;AAAA,EACvC,OAAO;AACN,UAAM,KAAK,8BAAyB;AAAA,EACrC;AAEA,QAAM,KAAK,EAAE;AACb,QAAM,KAAK,qBAAqB,OAAO,aAAa,MAAM,EAAE;AAC5D,QAAM,KAAK,uBAAuB,OAAO,SAAS,MAAM,EAAE;AAC1D,QAAM,KAAK,EAAE;AAEb,MAAI,OAAO,OAAO,WAAW,GAAG;AAC/B,UAAM,KAAK,kBAAkB;AAAA,EAC9B,OAAO;AACN,UAAM,SAAS,OAAO,OAAO,OAAO,CAAC,MAAM,EAAE,aAAa,OAAO;AACjE,UAAM,WAAW,OAAO,OAAO,OAAO,CAAC,MAAM,EAAE,aAAa,SAAS;AACrE,UAAM,QAAQ,OAAO,OAAO,OAAO,CAAC,MAAM,EAAE,aAAa,MAAM;AAE/D,QAAI,OAAO,SAAS,GAAG;AACtB,YAAM,KAAK,UAAK,OAAO,MAAM,YAAY;AACzC,iBAAW,SAAS,QAAQ;AAC3B,cAAM,KAAK,MAAM,MAAM,OAAO,EAAE;AAAA,MACjC;AACA,YAAM,KAAK,EAAE;AAAA,IACd;AAEA,QAAI,SAAS,SAAS,GAAG;AACxB,YAAM,KAAK,iBAAO,SAAS,MAAM,cAAc;AAC/C,iBAAW,SAAS,UAAU;AAC7B,cAAM,KAAK,MAAM,MAAM,OAAO,EAAE;AAAA,MACjC;AACA,YAAM,KAAK,EAAE;AAAA,IACd;AAEA,QAAI,MAAM,SAAS,GAAG;AACrB,YAAM,KAAK,iBAAO,MAAM,MAAM,QAAQ;AACtC,iBAAW,SAAS,OAAO;AAC1B,cAAM,KAAK,MAAM,MAAM,OAAO,EAAE;AAAA,MACjC;AACA,YAAM,KAAK,EAAE;AAAA,IACd;AAAA,EACD;AAEA,SAAO,MAAM,KAAK,IAAI;AACvB;;;ADvOO,IAAM,gBAAgB,IAAIC,SAAQ,QAAQ,EAC/C,YAAY,mDAAmD,EAC/D;AAAA,EACA;AAAA,EACA;AACD,EACC,eAAe,kBAAkB,oCAAoC,EACrE,OAAO,wBAAwB,wCAAwC,EACvE,OAAO,UAAU,gBAAgB,EACjC;AAAA,EACA,OAAO,YAKD;AACL,UAAM,aAAa,QAAQ,UAAU;AAErC,UAAM,cAAc,YAAY,QAAQ,EAAE;AAE1C,QAAI,CAAC,QAAQ,MAAM;AAClB,cAAQ,IAAI,+BAAwB,UAAU,EAAE;AAChD,cAAQ,IAAI,gBAAgB,WAAW,EAAE;AACzC,UAAI,QAAQ,YAAY;AACvB,gBAAQ,IAAI,cAAc,QAAQ,UAAU,EAAE;AAAA,MAC/C;AACA,cAAQ,IAAI,EAAE;AAAA,IACf;AAEA,QAAI;AAEH,YAAM,SAAS,MAAM,WAAW,UAAU;AAC1C,YAAM,cAAc,OAAO;AAG3B,YAAM,EAAE,KAAK,IAAI,MAAM,mBAAmB,QAAQ,EAAE;AAEpD,UAAI;AACH,cAAM,UAAUC,oBAAmB,IAAI;AAGvC,cAAM,OAAO,MAAMC,4BAA2B,SAAS,aAAa;AAAA,UACnE,GAAI,QAAQ,aAAa,EAAE,QAAQ,QAAQ,WAAW,IAAI,CAAC;AAAA,UAC3D,GAAI,OAAO,aAAa,SACrB,EAAE,UAAU,OAAO,SAAS,IAC5B,CAAC;AAAA,UACJ,WAAW,CAAC,YAAY,QAAQ,KAAK,iBAAO,OAAO,EAAE;AAAA,QACtD,CAAC;AAKD,cAAM,UAAU,MAAM,QAAQ,WAAW;AAAA,UACxC,GAAI,QAAQ,aAAa,EAAE,QAAQ,QAAQ,WAAW,IAAI,CAAC;AAAA,QAC5D,CAAC;AACD,cAAM,eAAe,MAAM,KAAK,YAAY,OAAO,KAAK,CAAC;AACzD,cAAM,WAAW,MAAM,KAAK,QAAQ,OAAO,KAAK,CAAC;AACjD,cAAM,SAAS,eAAe,MAAM,cAAc,QAAQ;AAG1D,YAAI,QAAQ,MAAM;AAEjB,gBAAM,EAAE,MAAM,OAAO,GAAG,WAAW,IAAI;AACvC,kBAAQ;AAAA,YACP,KAAK;AAAA,cACJ;AAAA,gBACC,GAAG;AAAA,gBACH,SAAS,KAAK;AAAA,gBACd,gBAAgB,KAAK;AAAA,cACtB;AAAA,cACA;AAAA,cACA;AAAA,YACD;AAAA,UACD;AAAA,QACD,OAAO;AACN,kBAAQ,IAAI,mBAAmB,MAAM,CAAC;AAAA,QACvC;AAGA,gBAAQ,WAAW,OAAO,QAAQ,IAAI;AACtC;AAAA,MACD,UAAE;AAED,cAAM,KAAK,IAAI;AAAA,MAChB;AAAA,IACD,SAAS,OAAO;AACf,YAAM,UACL,iBAAiB,QAAQ,MAAM,UAAU;AAE1C,UAAI,QAAQ,MAAM;AACjB,gBAAQ;AAAA,UACP,KAAK,UAAU,EAAE,QAAQ,SAAS,OAAO,QAAQ,GAAG,MAAM,CAAC;AAAA,QAC5D;AAAA,MACD,OAAO;AACN,gBAAQ,MAAM,iBAAY,OAAO,EAAE;AAAA,MACpC;AACA,cAAQ,KAAK,CAAC;AAAA,IACf;AAAA,EACD;AACD;;;ARpGD,IAAM,UAAU,IAAIC,SAAQ;AAE5B,QACE,KAAK,MAAM,EACX,YAAY,sDAAsD,EAClE,QAAQ,OAAO;AAGjB,QAAQ,WAAW,eAAe;AAClC,QAAQ,WAAW,iBAAiB;AACpC,QAAQ,WAAW,cAAc;AACjC,QAAQ,WAAW,WAAW;AAC9B,QAAQ,WAAW,WAAW;AAC9B,QAAQ,WAAW,aAAa;AAIhC,QAAQ,aAAa;AAErB,IAAI;AACH,UAAQ,MAAM;AACf,SAAS,KAAK;AAGb,MAAI,eAAe,kBAAkB,IAAI,aAAa,GAAG;AACxD,YAAQ,KAAK,CAAC;AAAA,EACf;AACA,QAAM,UAAU,eAAe,QAAQ,IAAI,UAAU;AACrD,MAAI,QAAQ,KAAK,SAAS,QAAQ,GAAG;AACpC,YAAQ,IAAI,KAAK,UAAU,EAAE,QAAQ,SAAS,OAAO,QAAQ,GAAG,MAAM,CAAC,CAAC;AAAA,EACzE,OAAO;AACN,YAAQ,MAAM,UAAK,OAAO,EAAE;AAAA,EAC7B;AACA,UAAQ,KAAK,CAAC;AACf;","names":["Command","outputPath","mkdirSync","writeFileSync","dirname","resolve","Command","Command","resolve","mkdirSync","dirname","writeFileSync","Command","mkdirSync","writeFileSync","resolve","Command","comparePgsqlDatabaseSchema","createPgsqlAdapter","generateMigrationSQL","Command","Command","createPgsqlAdapter","comparePgsqlDatabaseSchema","generateMigrationSQL","readFileSync","Command","Command","readFileSync","comparePgsqlDatabaseSchema","createPgsqlAdapter","Command","Command","createPgsqlAdapter","comparePgsqlDatabaseSchema","Command"]}
1
+ {"version":3,"sources":["../src/index.ts","../src/commands/generate.ts","../src/commands/introspect.ts","../src/commands/migrate.ts","../src/migration-file.ts","../src/commands/push.ts","../src/ddl-executor.ts","../src/commands/repl.ts","../src/commands/verify.ts","../src/verifier.ts"],"sourcesContent":["#!/usr/bin/env node\n/**\n * ARCH-002 Block 3: CLI Scaffold\n *\n * dbsp CLI - Schema-first code generation for db-semantic-planner.\n */\n\nimport { Command, CommanderError } from 'commander';\nimport { generateCommand } from './commands/generate.js';\nimport { introspectCommand } from './commands/introspect.js';\nimport { migrateCommand } from './commands/migrate.js';\nimport { pushCommand } from './commands/push.js';\nimport { replCommand } from './commands/repl.js';\nimport { verifyCommand } from './commands/verify.js';\n\nconst program = new Command();\n\nprogram\n\t.name('dbsp')\n\t.description('Schema-first code generation for db-semantic-planner')\n\t.version('0.0.1');\n\n// Register commands\nprogram.addCommand(generateCommand);\nprogram.addCommand(introspectCommand);\nprogram.addCommand(migrateCommand);\nprogram.addCommand(pushCommand);\nprogram.addCommand(replCommand);\nprogram.addCommand(verifyCommand);\n\n// CC-15: Intercept Commander parse errors so --json commands receive a JSON\n// error object on stdout instead of a plain-text usage message.\nprogram.exitOverride();\n\ntry {\n\tprogram.parse();\n} catch (err) {\n\t// Commander throws CommanderError for --help, --version, and parse errors.\n\t// Exit 0 for informational outputs (help/version); only exit 1 for real errors.\n\tif (err instanceof CommanderError && err.exitCode === 0) {\n\t\tprocess.exit(0);\n\t}\n\tconst message = err instanceof Error ? err.message : 'Command parse error';\n\tif (process.argv.includes('--json')) {\n\t\tconsole.log(JSON.stringify({ status: 'error', error: message }, null, 2));\n\t} else {\n\t\tconsole.error(`❌ ${message}`);\n\t}\n\tprocess.exit(1);\n}\n","/**\n * ARCH-002 Block 3+4+5: Generate Command\n * CLI-DDL: Added DDL generation target\n * ARCH-005: Migrated to schema() API, removed legacy generators\n *\n * dbsp generate <target> - Generate code from schema.\n *\n * Targets:\n * - ddl: Generate SQL DDL (CREATE TABLE statements)\n *\n * Deprecated targets (removed in ARCH-005):\n * - manifest: Removed legacy manifest generator\n * - kysely: Removed legacy Kysely generator\n */\n\nimport { mkdirSync, writeFileSync } from 'node:fs';\nimport { dirname, resolve } from 'node:path';\nimport type { DbCasing } from '@dbsp/types';\nimport { Command } from 'commander';\nimport { loadSchema, loadSchemaFromCwd } from '../utils/schema-loader.js';\n\nexport type GenerateCasingOption = 'snake' | 'camel' | 'none';\n\nexport const generateCommand = new Command('generate')\n\t.description('Generate code from schema')\n\t.argument('<target>', 'Target to generate: ddl')\n\t.option('-s, --schema <path>', 'Path to schema file (default: auto-detect)')\n\t.option('-o, --out <dir>', 'Output directory (default: ./generated/<target>)')\n\t.option('--output <dir>', 'Output directory (alias for --out)')\n\t.option('--drop', 'Include DROP TABLE IF EXISTS statements (ddl only)')\n\t.option('--schema-name <name>', 'Database schema name (ddl only)')\n\t.option(\n\t\t'--dialect <name>',\n\t\t'Database dialect: postgresql | mysql | sqlite | mssql (default: postgresql)',\n\t)\n\t.option(\n\t\t'--casing <type>',\n\t\t'Column naming: snake | camel | none (default: based on dialect)',\n\t)\n\t.action(\n\t\tasync (\n\t\t\ttarget: string,\n\t\t\toptions: {\n\t\t\t\tschema?: string;\n\t\t\t\tout?: string;\n\t\t\t\toutput?: string;\n\t\t\t\tdrop?: boolean;\n\t\t\t\tschemaName?: string;\n\t\t\t\tdialect?: string;\n\t\t\t\tcasing?: GenerateCasingOption;\n\t\t\t},\n\t\t) => {\n\t\t\ttry {\n\t\t\t\t// Reject the target BEFORE loading the schema: loading it executes the\n\t\t\t\t// user's schema module, and there is no reason to run their code for a\n\t\t\t\t// target we are going to refuse anyway.\n\t\t\t\tif (target === 'manifest' || target === 'kysely') {\n\t\t\t\t\tthrow new Error(\n\t\t\t\t\t\t`Target '${target}' has been removed. ` +\n\t\t\t\t\t\t\t`Use 'ddl' for SQL generation, or the 'introspect' command to generate a schema file.`,\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t\tif (target !== 'ddl') {\n\t\t\t\t\tthrow new Error(`Unknown target: ${target}. Available targets: ddl`);\n\t\t\t\t}\n\n\t\t\t\t// Load schema (ARCH-005: only schema() format supported)\n\t\t\t\tlet schema: Awaited<ReturnType<typeof loadSchema>>;\n\t\t\t\tlet schemaPath: string;\n\n\t\t\t\tif (options.schema) {\n\t\t\t\t\tschema = await loadSchema(options.schema);\n\t\t\t\t\tschemaPath = options.schema;\n\t\t\t\t} else {\n\t\t\t\t\tconst result = await loadSchemaFromCwd();\n\t\t\t\t\tschema = result.schema;\n\t\t\t\t\tschemaPath = result.path;\n\t\t\t\t}\n\n\t\t\t\t// For DDL without --output, we output to stdout so use stderr for info\n\t\t\t\tconst outputPath = options.out ?? options.output;\n\t\t\t\tconst useStdout = target === 'ddl' && !outputPath;\n\t\t\t\tconst log = useStdout ? console.error : console.log;\n\n\t\t\t\tlog(`📄 Loaded schema from: ${schemaPath}`);\n\n\t\t\t\t// Validate dialect option\n\t\t\t\tconst dialect = options.dialect ?? 'postgresql';\n\t\t\t\tif (!isDialectSupported(dialect)) {\n\t\t\t\t\tconsole.error(\n\t\t\t\t\t\t`⚠️ Warning: Only 'postgresql' dialect is currently supported. Using postgresql.`,\n\t\t\t\t\t);\n\t\t\t\t}\n\n\t\t\t\t// Generate based on target\n\t\t\t\tswitch (target) {\n\t\t\t\t\tcase 'ddl': {\n\t\t\t\t\t\t// Determine casing: explicit option > schema export > dialect default.\n\t\t\t\t\t\tconst dbCasing =\n\t\t\t\t\t\t\tmapCasingToDbCasing(options.casing) ??\n\t\t\t\t\t\t\tschema.dbCasing ??\n\t\t\t\t\t\t\t('snake_case' as const);\n\t\t\t\t\t\tconst casingLabel = formatGenerateCasingLabel(dbCasing);\n\n\t\t\t\t\t\t// Import adapter from adapter-pgsql (compile-only, no DB connection needed)\n\t\t\t\t\t\tconst { createPgsqlCompileOnlyAdapter } = await import(\n\t\t\t\t\t\t\t'@dbsp/adapter-pgsql'\n\t\t\t\t\t\t);\n\n\t\t\t\t\t\tconst adapter = createPgsqlCompileOnlyAdapter({\n\t\t\t\t\t\t\tdbCasing,\n\t\t\t\t\t\t\t...(options.schemaName ? { schemaName: options.schemaName } : {}),\n\t\t\t\t\t\t});\n\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\t// ARCH-005: Use schema.model directly (already ModelIR)\n\t\t\t\t\t\t\tconst ddlStatements = adapter.generateDDL(schema.model, {\n\t\t\t\t\t\t\t\t...(options.drop !== undefined && {\n\t\t\t\t\t\t\t\t\tincludeDropStatements: options.drop,\n\t\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t\t});\n\n\t\t\t\t\t\t\tconst ddlContent = ddlStatements.join('\\n\\n');\n\t\t\t\t\t\t\tconst outputPath = options.out ?? options.output;\n\n\t\t\t\t\t\t\tif (outputPath) {\n\t\t\t\t\t\t\t\t// Write to file if --output is specified\n\t\t\t\t\t\t\t\tconst outPath = outputPath.endsWith('.sql')\n\t\t\t\t\t\t\t\t\t? resolve(process.cwd(), outputPath)\n\t\t\t\t\t\t\t\t\t: resolve(process.cwd(), outputPath, 'schema.sql');\n\n\t\t\t\t\t\t\t\tmkdirSync(dirname(outPath), { recursive: true });\n\t\t\t\t\t\t\t\twriteFileSync(outPath, ddlContent, 'utf-8');\n\n\t\t\t\t\t\t\t\tconsole.log(`✅ Generated DDL: ${outPath}`);\n\t\t\t\t\t\t\t\tconsole.log(` Tables: ${schema.tableNames.length}`);\n\t\t\t\t\t\t\t\tconsole.log(` Statements: ${ddlStatements.length}`);\n\t\t\t\t\t\t\t\tconsole.log(` Casing: ${casingLabel}`);\n\t\t\t\t\t\t\t\tif (options.drop) {\n\t\t\t\t\t\t\t\t\tconsole.log(` Includes DROP statements`);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tif (options.schemaName) {\n\t\t\t\t\t\t\t\t\tconsole.log(` Schema: ${options.schemaName}`);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t// Output DDL to stdout (for piping)\n\t\t\t\t\t\t\t\t// Info messages (schema loaded) went to stderr\n\t\t\t\t\t\t\t\tconsole.log(ddlContent);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t} catch (error) {\n\t\t\t\tconst message = error instanceof Error ? error.message : String(error);\n\t\t\t\tconsole.error(`❌ ${message}`);\n\t\t\t\tprocess.exit(1);\n\t\t\t}\n\t\t},\n\t);\n\nexport function isDialectSupported(dialect: string): dialect is 'postgresql' {\n\treturn dialect === 'postgresql';\n}\n\nexport function mapCasingToDbCasing(\n\tcasing: GenerateCasingOption | undefined,\n): DbCasing | undefined {\n\tswitch (casing) {\n\t\tcase 'snake':\n\t\t\treturn 'snake_case';\n\t\tcase 'camel':\n\t\t\treturn 'camelCase';\n\t\tcase 'none':\n\t\t\treturn 'preserve';\n\t\tdefault:\n\t\t\treturn undefined;\n\t}\n}\n\nfunction formatGenerateCasingLabel(\n\tdbCasing: DbCasing,\n): 'snake' | 'camel' | 'none' {\n\tswitch (dbCasing) {\n\t\tcase 'snake_case':\n\t\t\treturn 'snake';\n\t\tcase 'camelCase':\n\t\t\treturn 'camel';\n\t\tcase 'preserve':\n\t\t\treturn 'none';\n\t}\n}\n","/**\n * CLI-DDL: Introspect Command\n *\n * dbsp introspect - Generate schema.ts from database introspection.\n */\n\nimport { mkdirSync, writeFileSync } from 'node:fs';\nimport { dirname, resolve } from 'node:path';\nimport { Command } from 'commander';\nimport {\n\tgenerateSchemaFileWithDiagnostics,\n\ttype SchemaCodegenOptions,\n} from '../generators/schema-codegen.js';\nimport { createDbConnection, redactDbUrl } from '../utils/db-utils.js';\n\nfunction uniqueWarnings(warnings: readonly string[]): string[] {\n\tconst seen = new Set<string>();\n\tconst unique: string[] = [];\n\tfor (const warning of warnings) {\n\t\tif (seen.has(warning)) continue;\n\t\tseen.add(warning);\n\t\tunique.push(warning);\n\t}\n\treturn unique;\n}\n\nfunction reportWarnings(warnings: readonly string[]): void {\n\tfor (const warning of warnings) {\n\t\tconsole.log(` ⚠️ ${warning}`);\n\t}\n}\n\nexport const introspectCommand = new Command('introspect')\n\t.description('Generate schema.ts from database introspection')\n\t.requiredOption('-d, --db <url>', 'Database connection URL (required)')\n\t.option('-o, --out <file>', 'Output schema file', './dbsp.schema.ts')\n\t.option('--schema-name <name>', 'Database schema name', 'public')\n\t.option(\n\t\t'--exclude <patterns>',\n\t\t'Tables to exclude (comma-separated glob patterns)',\n\t\t'_migrations,_prisma*,pg_*',\n\t)\n\t.option('--include <patterns>', 'Tables to include (comma-separated)')\n\t.option('--no-db-type-comments', 'Omit original DB type comments')\n\t.option(\n\t\t'--db-casing <casing>',\n\t\t'Database column casing (snake_case → camelCase in generated code)',\n\t\t'snake_case',\n\t)\n\t.action(\n\t\tasync (options: {\n\t\t\tdb: string;\n\t\t\tout: string;\n\t\t\tschemaName: string;\n\t\t\texclude: string;\n\t\t\tinclude?: string;\n\t\t\tdbTypeComments: boolean;\n\t\t\tdbCasing: 'snake_case' | 'camelCase' | 'preserve';\n\t\t}) => {\n\t\t\tconst redactedUrl = redactDbUrl(options.db);\n\n\t\t\tconsole.log(`🔍 Introspecting database: ${redactedUrl}`);\n\t\t\tconsole.log(` Schema: ${options.schemaName}`);\n\t\t\tif (options.exclude) {\n\t\t\t\tconsole.log(` Excluding: ${options.exclude}`);\n\t\t\t}\n\t\t\tconsole.log('');\n\n\t\t\ttry {\n\t\t\t\t// Connect to database\n\t\t\t\tconst { pool } = await createDbConnection(options.db);\n\n\t\t\t\ttry {\n\t\t\t\t\t// Import introspect from adapter-pgsql\n\t\t\t\t\tconst { introspect } = await import('@dbsp/adapter-pgsql');\n\n\t\t\t\t\t// Build introspection options from CLI flags\n\t\t\t\t\tconst excludePatterns = options.exclude\n\t\t\t\t\t\t? options.exclude.split(',').map((s) => s.trim())\n\t\t\t\t\t\t: undefined;\n\t\t\t\t\tconst includePatterns = options.include\n\t\t\t\t\t\t? options.include.split(',').map((s) => s.trim())\n\t\t\t\t\t\t: undefined;\n\n\t\t\t\t\t// Introspect the database directly (returns IntrospectedModelIR)\n\t\t\t\t\tconst model = await introspect(pool, {\n\t\t\t\t\t\tschema: options.schemaName,\n\t\t\t\t\t\t...(excludePatterns ? { exclude: excludePatterns } : {}),\n\t\t\t\t\t\t...(includePatterns ? { include: includePatterns } : {}),\n\t\t\t\t\t});\n\n\t\t\t\t\t// Report what we found\n\t\t\t\t\tconst tableCount = model.tables.size;\n\t\t\t\t\tconst relationCount = model.relations.size;\n\t\t\t\t\tconst hierarchyCount = model.hierarchies?.length ?? 0;\n\n\t\t\t\t\tconsole.log(\n\t\t\t\t\t\t`📊 Found ${tableCount} tables, ${relationCount} relations, ${hierarchyCount} hierarchies`,\n\t\t\t\t\t);\n\n\t\t\t\t\t// Generate schema file — pass metadata from introspection\n\t\t\t\t\tconst codegenOptions: SchemaCodegenOptions = {\n\t\t\t\t\t\tsourceUrl: options.db,\n\t\t\t\t\t\tincludeDbTypeComments: options.dbTypeComments,\n\t\t\t\t\t\tintrospectedAt: model.introspectedAt,\n\t\t\t\t\t\tdbCasing: options.dbCasing,\n\t\t\t\t\t};\n\n\t\t\t\t\tconst generated = generateSchemaFileWithDiagnostics(\n\t\t\t\t\t\tmodel,\n\t\t\t\t\t\tcodegenOptions,\n\t\t\t\t\t);\n\t\t\t\t\tconst warnings = uniqueWarnings(generated.warnings);\n\t\t\t\t\tif (warnings.length > 0) {\n\t\t\t\t\t\treportWarnings(warnings);\n\t\t\t\t\t}\n\t\t\t\t\tconsole.log('');\n\n\t\t\t\t\t// Write output file\n\t\t\t\t\tconst outPath = resolve(process.cwd(), options.out);\n\t\t\t\t\tmkdirSync(dirname(outPath), { recursive: true });\n\t\t\t\t\twriteFileSync(outPath, generated.code, 'utf-8');\n\n\t\t\t\t\tconsole.log(`✅ Generated schema: ${outPath}`);\n\t\t\t\t\tconsole.log(` Tables: ${tableCount}`);\n\t\t\t\t\tconsole.log(` Relations: ${relationCount}`);\n\t\t\t\t} finally {\n\t\t\t\t\t// Close database connection\n\t\t\t\t\tawait pool.end();\n\t\t\t\t}\n\t\t\t} catch (error) {\n\t\t\t\tconst message = error instanceof Error ? error.message : String(error);\n\t\t\t\tconsole.error(`❌ ${message}`);\n\t\t\t\tprocess.exit(1);\n\t\t\t}\n\t\t},\n\t);\n","/**\n * Migrate Command — Migration Infrastructure\n *\n * dbsp migrate dev - Generate migration from schema diff\n * dbsp migrate apply - Apply pending migrations\n * dbsp migrate rollback - Roll back applied migrations\n * dbsp migrate status - Show migration status\n */\n\nimport {\n\tcomparePgsqlDatabaseSchema,\n\tcreatePgsqlAdapter,\n\tensureMigrationsTable,\n\tgenerateMigrationFile,\n\tgenerateMigrationSQL,\n\tgetAppliedMigrations,\n\tgetNextSchemaVersion,\n\tisDestructiveDown,\n\ttype MigrationRecord,\n\tparseMigrationFile,\n\trecordMigration,\n\tremoveMigrationRecord,\n\twithMigrationLock,\n} from '@dbsp/adapter-pgsql';\nimport { Command } from 'commander';\nimport type { Pool, PoolClient } from 'pg';\nimport {\n\tDEFAULT_MIGRATIONS_DIR,\n\tgenerateMigrationFilename,\n\ttype MigrationFile,\n\tscanMigrationFiles,\n\twriteMigrationFile,\n} from '../migration-file.js';\nimport { createDbConnection, redactDbUrl } from '../utils/db-utils.js';\nimport { loadSchema } from '../utils/schema-loader.js';\n\n// ============================================================================\n// Helpers\n// ============================================================================\n\n/**\n * Typed error for migration failures.\n * Distinguishes user-facing errors (validation, logic) from unexpected errors.\n */\nexport class MigrationError extends Error {\n\tconstructor(message: string) {\n\t\tsuper(message);\n\t\tthis.name = 'MigrationError';\n\t}\n}\n\n/**\n * Sanitize a pg error for user-facing output.\n *\n * PostgreSQL errors may carry schema/table/row details in their message text.\n * For pg errors (identifiable by SQLSTATE `.code`), we emit a sanitized message\n * keyed only by the SQLSTATE code. The raw message is available via DEBUG=dbsp.\n *\n * @param err - Any caught value\n * @returns An Error instance safe to display to the user\n */\nexport function sanitizePgError(err: unknown): Error {\n\tif (err instanceof Error) {\n\t\t// pg errors carry a SQLSTATE `.code` property\n\t\tconst code = (err as Error & { code?: string }).code;\n\t\tif (typeof code === 'string' && /^[0-9A-Z]{5}$/.test(code)) {\n\t\t\tif (process.env.DEBUG?.includes('dbsp')) {\n\t\t\t\tconsole.error(`[DEBUG] pg error detail: ${err.message}`);\n\t\t\t}\n\t\t\treturn new MigrationError(`Migration failed: database error ${code}`);\n\t\t}\n\t\treturn err;\n\t}\n\treturn new Error(String(err));\n}\n\n/**\n * DRY lifecycle wrapper: create pool → run fn → end pool on success or error.\n * Replaces the repeated try { ... } finally { pool.end() } pattern across commands.\n *\n * process.exit is called OUTSIDE the pool lifecycle so the lock-holding client\n * (from withMigrationLock) is always released before the process terminates.\n */\nexport async function withMigratePool<T>(\n\tdbUrl: string,\n\tfn: (pool: Pool) => Promise<T>,\n): Promise<T> {\n\tconst { pool } = await createDbConnection(dbUrl);\n\ttry {\n\t\treturn await fn(pool);\n\t} finally {\n\t\tlet endError: unknown;\n\t\ttry {\n\t\t\tawait pool.end();\n\t\t} catch (e) {\n\t\t\tendError = e;\n\t\t}\n\t\tif (endError !== undefined) {\n\t\t\t// Cleanup failure is non-fatal — log it but don't mask the original error\n\t\t\tconsole.error(\n\t\t\t\t`Warning: pool.end() failed: ${endError instanceof Error ? endError.message : String(endError)}`,\n\t\t\t);\n\t\t}\n\t}\n}\n\n/**\n * Top-level error handler for migrate commands.\n * Ensures process.exit is only called AFTER pool cleanup (via withMigratePool).\n */\nasync function runMigrateAction(fn: () => Promise<void>): Promise<void> {\n\ttry {\n\t\tawait fn();\n\t} catch (error) {\n\t\tif (error instanceof Error) {\n\t\t\tconsole.error(`❌ Error: ${error.message}`);\n\t\t} else {\n\t\t\tconsole.error('❌ Unknown error occurred');\n\t\t}\n\t\tprocess.exit(1);\n\t}\n}\n\n/**\n * Returns true if a migration statement contains at least one executable SQL\n * line (non-empty, not a comment). Statements that consist entirely of blank\n * lines and `--` comment lines are skipped; statements that begin with a\n * comment header but also contain real SQL are kept (PostgreSQL ignores the\n * comment, so the header is harmless and preserves intent).\n */\nexport function hasExecutableSql(stmt: string): boolean {\n\treturn stmt.split('\\n').some((line) => {\n\t\tconst t = line.trim();\n\t\treturn t.length > 0 && !t.startsWith('--');\n\t});\n}\n\nexport interface ApplyOptions {\n\tdb: string;\n\tdir: string;\n\tdryRun?: boolean;\n}\n\nexport interface RollbackOptions {\n\tcount: number;\n\tdb: string;\n\tdir: string;\n\tforce?: boolean;\n}\n\nexport interface MigrateDeps {\n\twithMigratePool?: <T>(\n\t\tdbUrl: string,\n\t\tfn: (pool: Pool) => Promise<T>,\n\t) => Promise<T>;\n\tensureMigrationsTable?: (pool: Pool) => Promise<void>;\n\twithMigrationLock?: <T>(\n\t\tpool: Pool,\n\t\tfn: (client: PoolClient) => Promise<T>,\n\t) => Promise<T>;\n\tgetAppliedMigrations?: (pool: Pool) => Promise<readonly MigrationRecord[]>;\n\tscanMigrationFiles?: (dir: string) => readonly MigrationFile[];\n\tparseMigrationFile?: typeof parseMigrationFile;\n\tgetNextSchemaVersion?: (pool: Pool) => Promise<number>;\n\trecordMigration?: (\n\t\tpool: Pool,\n\t\tname: string,\n\t\tchecksum: string,\n\t\tschemaVersion: number,\n\t\tdestructive: boolean,\n\t) => Promise<void>;\n\tremoveMigrationRecord?: (pool: Pool, name: string) => Promise<void>;\n\tisDestructiveDown?: (downStatements: readonly string[]) => boolean;\n}\n\nconst defaultMigrateDeps: Required<MigrateDeps> = {\n\twithMigratePool,\n\tensureMigrationsTable,\n\twithMigrationLock,\n\tgetAppliedMigrations,\n\tscanMigrationFiles,\n\tparseMigrationFile,\n\tgetNextSchemaVersion,\n\trecordMigration,\n\tremoveMigrationRecord,\n\tisDestructiveDown,\n};\n\nfunction resolveMigrateDeps(deps?: MigrateDeps): Required<MigrateDeps> {\n\treturn { ...defaultMigrateDeps, ...deps };\n}\n\nexport async function runApply(\n\toptions: ApplyOptions,\n\tdeps?: MigrateDeps,\n): Promise<void> {\n\tconst d = resolveMigrateDeps(deps);\n\n\tconsole.log('🔄 Applying migrations');\n\tconsole.log(` Database: ${redactDbUrl(options.db)}`);\n\tconsole.log(` Directory: ${options.dir}`);\n\tconsole.log('');\n\n\tawait d.withMigratePool(options.db, async (pool) => {\n\t\t// Ensure tracking table exists before acquiring lock\n\t\tawait d.ensureMigrationsTable(pool);\n\n\t\tawait d.withMigrationLock(pool, async (client) => {\n\t\t\t// Get applied migrations on the lock-holding client\n\t\t\tconst applied = await d.getAppliedMigrations(client as unknown as Pool);\n\t\t\tconst appliedMap = new Map(applied.map((m) => [m.name, m.checksum]));\n\n\t\t\t// Scan migration files\n\t\t\tconst files = d.scanMigrationFiles(options.dir);\n\n\t\t\tif (files.length === 0) {\n\t\t\t\tconsole.log(`No migration files found in ${options.dir}`);\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\t// Validate checksums for already-applied migrations\n\t\t\tfor (const file of files) {\n\t\t\t\tconst existingChecksum = appliedMap.get(file.name);\n\t\t\t\tif (\n\t\t\t\t\texistingChecksum !== undefined &&\n\t\t\t\t\texistingChecksum !== file.checksum\n\t\t\t\t) {\n\t\t\t\t\tthrow new MigrationError(\n\t\t\t\t\t\t`Checksum mismatch for ${file.name}\\n` +\n\t\t\t\t\t\t\t` Expected: ${existingChecksum}\\n` +\n\t\t\t\t\t\t\t` Got: ${file.checksum}\\n` +\n\t\t\t\t\t\t\t'\\nMigration file has been tampered with after being applied.',\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Find pending migrations\n\t\t\tconst pending = files.filter((f) => !appliedMap.has(f.name));\n\n\t\t\tif (pending.length === 0) {\n\t\t\t\tconsole.log('✅ All migrations already applied.');\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tif (options.dryRun) {\n\t\t\t\tconsole.log(`${pending.length} pending migration(s):`);\n\t\t\t\tfor (const file of pending) {\n\t\t\t\t\tconsole.log(` - ${file.name}`);\n\t\t\t\t}\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\t// Apply each pending migration atomically (DDL + record in one transaction)\n\t\t\tlet appliedCount = 0;\n\t\t\tfor (const file of pending) {\n\t\t\t\tconsole.log(` Applying: ${file.name}...`);\n\n\t\t\t\t// Parse UP section from file\n\t\t\t\tconst parsed = d.parseMigrationFile(file.content);\n\t\t\t\tconst statements = parsed.upStatements.filter(hasExecutableSql);\n\n\t\t\t\t// Determine version and destructive flag before the transaction\n\t\t\t\t// (read from the lock-held client so we see a consistent view)\n\t\t\t\tconst version = await d.getNextSchemaVersion(client as unknown as Pool);\n\t\t\t\tconst destructive = parsed.destructive === true;\n\n\t\t\t\t// Atomic: DDL + record in ONE transaction on the lock-holding client\n\t\t\t\ttry {\n\t\t\t\t\tawait client.query('BEGIN');\n\n\t\t\t\t\tfor (const stmt of statements) {\n\t\t\t\t\t\tawait client.query(stmt);\n\t\t\t\t\t}\n\n\t\t\t\t\tawait d.recordMigration(\n\t\t\t\t\t\tclient as unknown as Pool,\n\t\t\t\t\t\tfile.name,\n\t\t\t\t\t\tfile.checksum,\n\t\t\t\t\t\tversion,\n\t\t\t\t\t\tdestructive,\n\t\t\t\t\t);\n\n\t\t\t\t\tawait client.query('COMMIT');\n\t\t\t\t} catch (applyError) {\n\t\t\t\t\tlet rollbackError: unknown;\n\t\t\t\t\ttry {\n\t\t\t\t\t\tawait client.query('ROLLBACK');\n\t\t\t\t\t} catch (e) {\n\t\t\t\t\t\trollbackError = e;\n\t\t\t\t\t}\n\t\t\t\t\tconst primary = sanitizePgError(applyError);\n\t\t\t\t\tif (rollbackError !== undefined) {\n\t\t\t\t\t\tconsole.error(\n\t\t\t\t\t\t\t` Note: rollback also failed: ${sanitizePgError(rollbackError).message}`,\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\t\t\t\t\tthrow primary;\n\t\t\t\t}\n\n\t\t\t\tappliedCount++;\n\t\t\t\tconsole.log(` ✅ Applied: ${file.name}`);\n\t\t\t}\n\n\t\t\tconsole.log(`\\n✅ ${appliedCount} migration(s) applied successfully.`);\n\t\t});\n\t});\n}\n\nexport async function runRollback(\n\toptions: RollbackOptions,\n\tdeps?: MigrateDeps,\n): Promise<void> {\n\tconst d = resolveMigrateDeps(deps);\n\n\tconsole.log(`⏪ Rolling back ${options.count} migration(s)`);\n\tconsole.log(` Database: ${redactDbUrl(options.db)}`);\n\tconsole.log(` Directory: ${options.dir}`);\n\tconsole.log('');\n\n\tawait d.withMigratePool(options.db, async (pool) => {\n\t\tawait d.ensureMigrationsTable(pool);\n\n\t\tawait d.withMigrationLock(pool, async (client) => {\n\t\t\t// Get applied migrations on the lock-holding client\n\t\t\tconst applied = await d.getAppliedMigrations(client as unknown as Pool);\n\t\t\tconst sortedDesc = [...applied].sort((a, b) =>\n\t\t\t\tb.name.localeCompare(a.name),\n\t\t\t);\n\n\t\t\t// SC-18: Validate count\n\t\t\tif (options.count > sortedDesc.length) {\n\t\t\t\tthrow new MigrationError(\n\t\t\t\t\t`Cannot roll back ${options.count} migration(s) — only ${sortedDesc.length} applied`,\n\t\t\t\t);\n\t\t\t}\n\n\t\t\tconst toRollback = sortedDesc.slice(0, options.count);\n\n\t\t\t// Load migration files from disk\n\t\t\tconst files = d.scanMigrationFiles(options.dir);\n\t\t\tconst fileMap = new Map(files.map((f) => [f.name, f]));\n\n\t\t\tlet rolledBack = 0;\n\t\t\tfor (const record of toRollback) {\n\t\t\t\tconst file = fileMap.get(record.name);\n\t\t\t\tif (!file) {\n\t\t\t\t\tthrow new MigrationError(\n\t\t\t\t\t\t`Migration file not found on disk: ${record.name}`,\n\t\t\t\t\t);\n\t\t\t\t}\n\n\t\t\t\t// SC-17: Verify checksum\n\t\t\t\tif (file.checksum !== record.checksum) {\n\t\t\t\t\tthrow new MigrationError(\n\t\t\t\t\t\t`Checksum mismatch for ${record.name}\\n` +\n\t\t\t\t\t\t\t` Expected: ${record.checksum}\\n` +\n\t\t\t\t\t\t\t` Got: ${file.checksum}\\n` +\n\t\t\t\t\t\t\t'\\nMigration file has been modified since it was applied.',\n\t\t\t\t\t);\n\t\t\t\t}\n\n\t\t\t\t// Parse DOWN section\n\t\t\t\tconst parsed = d.parseMigrationFile(file.content);\n\n\t\t\t\t// SC-10/ERR-01: No DOWN section\n\t\t\t\tif (!parsed.hasDown) {\n\t\t\t\t\tthrow new MigrationError(\n\t\t\t\t\t\t`Migration ${record.name} has no DOWN section\\n` +\n\t\t\t\t\t\t\t' Cannot roll back a migration without a DOWN section.',\n\t\t\t\t\t);\n\t\t\t\t}\n\n\t\t\t\t// SC-11/ERR-04: Empty DOWN section\n\t\t\t\tconst downStmts = parsed.downStatements.filter(hasExecutableSql);\n\t\t\t\tif (downStmts.length === 0 && !options.force) {\n\t\t\t\t\tthrow new MigrationError(\n\t\t\t\t\t\t`Migration ${record.name} has an empty DOWN section\\n` +\n\t\t\t\t\t\t\t' Use --force to roll back anyway.',\n\t\t\t\t\t);\n\t\t\t\t}\n\n\t\t\t\t// SC-19: Metadata-driven destructive rollback guard\n\t\t\t\tconst safeHeaderContradictedByDown =\n\t\t\t\t\tparsed.destructive === false && d.isDestructiveDown(downStmts);\n\t\t\t\tif (\n\t\t\t\t\t(parsed.destructive !== false || safeHeaderContradictedByDown) &&\n\t\t\t\t\t!options.force\n\t\t\t\t) {\n\t\t\t\t\tif (parsed.destructive === true) {\n\t\t\t\t\t\tthrow new MigrationError(\n\t\t\t\t\t\t\t`Migration ${record.name} has destructive DOWN operations\\n` +\n\t\t\t\t\t\t\t\t' Use --force to proceed with destructive rollback.',\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\t\t\t\t\tif (safeHeaderContradictedByDown) {\n\t\t\t\t\t\tthrow new MigrationError(\n\t\t\t\t\t\t\t`Migration ${record.name} is marked non-destructive but its DOWN section contains an obvious destructive statement\\n` +\n\t\t\t\t\t\t\t\t' Re-generate the migration with accurate dbsp metadata or pass --force.',\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\t\t\t\t\tthrow new MigrationError(\n\t\t\t\t\t\t`Migration ${record.name} is unmarked or legacy\\n` +\n\t\t\t\t\t\t\t' Re-generate the migration with dbsp metadata or pass --force.',\n\t\t\t\t\t);\n\t\t\t\t}\n\n\t\t\t\t// Atomic: DOWN SQL + remove record in ONE transaction on the lock-holding client\n\t\t\t\tif (downStmts.length > 0 || options.force) {\n\t\t\t\t\tconsole.log(` Rolling back: ${record.name}...`);\n\t\t\t\t}\n\n\t\t\t\ttry {\n\t\t\t\t\tawait client.query('BEGIN');\n\n\t\t\t\t\tfor (const stmt of downStmts) {\n\t\t\t\t\t\tawait client.query(stmt);\n\t\t\t\t\t}\n\n\t\t\t\t\tawait d.removeMigrationRecord(client as unknown as Pool, record.name);\n\n\t\t\t\t\tawait client.query('COMMIT');\n\t\t\t\t} catch (rollbackError) {\n\t\t\t\t\tlet rollbackCleanupError: unknown;\n\t\t\t\t\ttry {\n\t\t\t\t\t\tawait client.query('ROLLBACK');\n\t\t\t\t\t} catch (e) {\n\t\t\t\t\t\trollbackCleanupError = e;\n\t\t\t\t\t}\n\t\t\t\t\tconst primary = sanitizePgError(rollbackError);\n\t\t\t\t\tif (rollbackCleanupError !== undefined) {\n\t\t\t\t\t\tconsole.error(\n\t\t\t\t\t\t\t` Note: rollback also failed: ${sanitizePgError(rollbackCleanupError).message}`,\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\t\t\t\t\tthrow primary;\n\t\t\t\t}\n\n\t\t\t\trolledBack++;\n\t\t\t\tconsole.log(` ✅ Rolled back: ${record.name}`);\n\t\t\t}\n\n\t\t\tconsole.log(`\\n✅ ${rolledBack} migration(s) rolled back successfully.`);\n\t\t});\n\t});\n}\n\nfunction runRollbackCommand(\n\tcountArg: string,\n\toptions: { db: string; dir: string; force?: boolean },\n): Promise<void> {\n\tconst count = Number.parseInt(countArg, 10);\n\tif (Number.isNaN(count) || count < 1) {\n\t\tconsole.error('❌ Count must be a positive integer');\n\t\tprocess.exit(1);\n\t}\n\n\treturn runMigrateAction(async () => {\n\t\tawait runRollback({ count, ...options });\n\t});\n}\n\n// ============================================================================\n// Subcommands\n// ============================================================================\n\nconst devCommand = new Command('dev')\n\t.description('Generate a migration from schema changes')\n\t.option(\n\t\t'-s, --schema <path>',\n\t\t'Path to schema file (default: dbsp.schema.ts)',\n\t)\n\t.requiredOption('-d, --db <url>', 'Database connection URL (required)')\n\t.option('--schema-name <name>', 'Database schema name (default: public)')\n\t.option('--dir <path>', 'Migrations directory', DEFAULT_MIGRATIONS_DIR)\n\t.option('-n, --name <description>', 'Migration description', 'migration')\n\t.option('--allow-destructive', 'Include destructive changes (drops)')\n\t.action(\n\t\t(options: {\n\t\t\tschema?: string;\n\t\t\tdb: string;\n\t\t\tschemaName?: string;\n\t\t\tdir: string;\n\t\t\tname: string;\n\t\t\tallowDestructive?: boolean;\n\t\t}) =>\n\t\t\trunMigrateAction(async () => {\n\t\t\t\tconst schemaPath = options.schema ?? 'dbsp.schema.ts';\n\n\t\t\t\tconsole.log(`📝 Generating migration: ${schemaPath}`);\n\t\t\t\tconsole.log(` Database: ${redactDbUrl(options.db)}`);\n\t\t\t\tconsole.log('');\n\n\t\t\t\tconst loaded = await loadSchema(schemaPath);\n\t\t\t\tconst schemaModel = loaded.model;\n\n\t\t\t\tawait withMigratePool(options.db, async (pool) => {\n\t\t\t\t\tconst adapter = createPgsqlAdapter(pool);\n\t\t\t\t\tconst diff = await comparePgsqlDatabaseSchema(adapter, schemaModel, {\n\t\t\t\t\t\t...(options.schemaName ? { schema: options.schemaName } : {}),\n\t\t\t\t\t\t...(loaded.dbCasing ? { dbCasing: loaded.dbCasing } : {}),\n\t\t\t\t\t\tonWarning: (message) => console.warn(`⚠️ ${message}`),\n\t\t\t\t\t});\n\n\t\t\t\t\tif (diff.changes.length === 0) {\n\t\t\t\t\t\tconsole.log('✅ No changes detected — database matches schema.');\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\n\t\t\t\t\t// Check for destructive changes\n\t\t\t\t\tif (diff.hasDestructive && !options.allowDestructive) {\n\t\t\t\t\t\tconst destructive = diff.changes.filter((c) => c.destructive);\n\t\t\t\t\t\tthrow new MigrationError(\n\t\t\t\t\t\t\t`${destructive.length} destructive change(s) detected:\\n` +\n\t\t\t\t\t\t\t\tdestructive.map((c) => ` - ${c.details}`).join('\\n') +\n\t\t\t\t\t\t\t\t'\\n\\nUse --allow-destructive to include these changes.',\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\n\t\t\t\t\tconst sqlOptions = {\n\t\t\t\t\t\tincludeDestructive: options.allowDestructive ?? false,\n\t\t\t\t\t\t...(options.schemaName ? { schemaName: options.schemaName } : {}),\n\t\t\t\t\t};\n\n\t\t\t\t\tconst statements = generateMigrationSQL(diff, sqlOptions);\n\n\t\t\t\t\tif (statements.length === 0) {\n\t\t\t\t\t\tconsole.log(\n\t\t\t\t\t\t\t'✅ No migration needed — all changes are non-actionable.',\n\t\t\t\t\t\t);\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\n\t\t\t\t\t// Generate migration file with UP + DOWN sections\n\t\t\t\t\tconst existingFiles = scanMigrationFiles(options.dir).map(\n\t\t\t\t\t\t(f) => f.name,\n\t\t\t\t\t);\n\t\t\t\t\tconst filename = generateMigrationFilename(\n\t\t\t\t\t\texistingFiles,\n\t\t\t\t\t\toptions.name,\n\t\t\t\t\t);\n\t\t\t\t\tconst content = generateMigrationFile(diff, {\n\t\t\t\t\t\t...sqlOptions,\n\t\t\t\t\t\tname: filename,\n\t\t\t\t\t});\n\n\t\t\t\t\tconst file = writeMigrationFile(options.dir, filename, content);\n\n\t\t\t\t\tconsole.log(`✅ Migration created: ${file.path}`);\n\t\t\t\t\tconsole.log(` Statements: ${statements.length}`);\n\t\t\t\t\tconsole.log(` Checksum: ${file.checksum.slice(0, 12)}...`);\n\t\t\t\t});\n\t\t\t}),\n\t);\n\nconst applyCommand = new Command('apply')\n\t.description('Apply pending migrations')\n\t.requiredOption('-d, --db <url>', 'Database connection URL (required)')\n\t.option('--dir <path>', 'Migrations directory', DEFAULT_MIGRATIONS_DIR)\n\t.option('--dry-run', 'Show pending migrations without applying')\n\t.action((options: ApplyOptions) =>\n\t\trunMigrateAction(async () => {\n\t\t\tawait runApply(options);\n\t\t}),\n\t);\n\nconst rollbackCommand = new Command('rollback')\n\t.description('Roll back applied migrations')\n\t.argument('<count>', 'Number of migrations to roll back')\n\t.requiredOption('-d, --db <url>', 'Database connection URL (required)')\n\t.option('--dir <path>', 'Migrations directory', DEFAULT_MIGRATIONS_DIR)\n\t.option('--force', 'Force rollback of destructive or empty DOWN migrations')\n\t.action(runRollbackCommand);\n\nconst statusCommand = new Command('status')\n\t.description('Show migration status')\n\t.requiredOption('-d, --db <url>', 'Database connection URL (required)')\n\t.option('--dir <path>', 'Migrations directory', DEFAULT_MIGRATIONS_DIR)\n\t.option('--json', 'Output as JSON')\n\t.action((options: { db: string; dir: string; json?: boolean }) =>\n\t\trunMigrateAction(async () => {\n\t\t\tawait withMigratePool(options.db, async (pool) => {\n\t\t\t\tawait ensureMigrationsTable(pool);\n\n\t\t\t\tconst applied = await getAppliedMigrations(pool);\n\t\t\t\tconst appliedMap = new Map(applied.map((m) => [m.name, m]));\n\n\t\t\t\tconst files = scanMigrationFiles(options.dir);\n\n\t\t\t\tconst statuses: Array<{\n\t\t\t\t\tname: string;\n\t\t\t\t\tstatus: 'pending' | 'applied' | 'checksum_mismatch' | 'missing_file';\n\t\t\t\t\tappliedAt?: Date;\n\t\t\t\t}> = files.map((file) => {\n\t\t\t\t\tconst record = appliedMap.get(file.name);\n\t\t\t\t\tif (record === undefined) {\n\t\t\t\t\t\treturn {\n\t\t\t\t\t\t\tname: file.name,\n\t\t\t\t\t\t\tstatus: 'pending' as const,\n\t\t\t\t\t\t};\n\t\t\t\t\t}\n\t\t\t\t\tif (record.checksum !== file.checksum) {\n\t\t\t\t\t\treturn {\n\t\t\t\t\t\t\tname: file.name,\n\t\t\t\t\t\t\tstatus: 'checksum_mismatch' as const,\n\t\t\t\t\t\t\tappliedAt: record.appliedAt,\n\t\t\t\t\t\t};\n\t\t\t\t\t}\n\t\t\t\t\treturn {\n\t\t\t\t\t\tname: file.name,\n\t\t\t\t\t\tstatus: 'applied' as const,\n\t\t\t\t\t\tappliedAt: record.appliedAt,\n\t\t\t\t\t};\n\t\t\t\t});\n\n\t\t\t\t// Also show applied migrations that don't have files anymore\n\t\t\t\tfor (const record of applied) {\n\t\t\t\t\tif (!files.some((f) => f.name === record.name)) {\n\t\t\t\t\t\tstatuses.push({\n\t\t\t\t\t\t\tname: record.name,\n\t\t\t\t\t\t\tstatus: 'missing_file' as const,\n\t\t\t\t\t\t\tappliedAt: record.appliedAt,\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tif (options.json) {\n\t\t\t\t\tconsole.log(JSON.stringify(statuses, null, 2));\n\t\t\t\t} else {\n\t\t\t\t\tconsole.log('Migration Status');\n\t\t\t\t\tconsole.log(` Database: ${redactDbUrl(options.db)}`);\n\t\t\t\t\tconsole.log(` Directory: ${options.dir}`);\n\t\t\t\t\tconsole.log('');\n\n\t\t\t\t\tif (statuses.length === 0) {\n\t\t\t\t\t\tconsole.log('No migrations found.');\n\t\t\t\t\t} else {\n\t\t\t\t\t\tfor (const s of statuses) {\n\t\t\t\t\t\t\tconst icon =\n\t\t\t\t\t\t\t\ts.status === 'applied'\n\t\t\t\t\t\t\t\t\t? '✅'\n\t\t\t\t\t\t\t\t\t: s.status === 'pending'\n\t\t\t\t\t\t\t\t\t\t? '⏳'\n\t\t\t\t\t\t\t\t\t\t: s.status === 'checksum_mismatch'\n\t\t\t\t\t\t\t\t\t\t\t? '⚠️'\n\t\t\t\t\t\t\t\t\t\t\t: '❓';\n\t\t\t\t\t\t\tconst detail =\n\t\t\t\t\t\t\t\t'appliedAt' in s && s.appliedAt\n\t\t\t\t\t\t\t\t\t? ` (applied: ${s.appliedAt.toISOString()})`\n\t\t\t\t\t\t\t\t\t: '';\n\t\t\t\t\t\t\tconsole.log(` ${icon} ${s.name} — ${s.status}${detail}`);\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tconst pendingCount = statuses.filter(\n\t\t\t\t\t\t\t(s) => s.status === 'pending',\n\t\t\t\t\t\t).length;\n\t\t\t\t\t\tconst appliedCount = statuses.filter(\n\t\t\t\t\t\t\t(s) => s.status === 'applied',\n\t\t\t\t\t\t).length;\n\n\t\t\t\t\t\tconsole.log('');\n\t\t\t\t\t\tconsole.log(\n\t\t\t\t\t\t\t`Total: ${statuses.length} | Applied: ${appliedCount} | Pending: ${pendingCount}`,\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t});\n\t\t}),\n\t);\n\n// ============================================================================\n// Main Command\n// ============================================================================\n\nexport const migrateCommand = new Command('migrate')\n\t.description('Database migration management')\n\t.addCommand(devCommand)\n\t.addCommand(applyCommand)\n\t.addCommand(rollbackCommand)\n\t.addCommand(statusCommand);\n","/**\n * Migration File — File I/O, naming conventions, checksums.\n *\n * Handles reading/writing migration SQL files and computing\n * SHA-256 checksums for tamper detection.\n */\n\nimport { createHash } from 'node:crypto';\nimport {\n\texistsSync,\n\tmkdirSync,\n\treaddirSync,\n\treadFileSync,\n\twriteFileSync,\n} from 'node:fs';\nimport { join, resolve } from 'node:path';\n\n// ============================================================================\n// Types\n// ============================================================================\n\nexport interface MigrationFile {\n\t/** Filename (e.g., \"0001_create_users.sql\") */\n\treadonly name: string;\n\t/** Full path to the file */\n\treadonly path: string;\n\t/** SQL content */\n\treadonly content: string;\n\t/** SHA-256 checksum of content */\n\treadonly checksum: string;\n}\n\n// ============================================================================\n// Constants\n// ============================================================================\n\n/** Default directory for migration files */\nexport const DEFAULT_MIGRATIONS_DIR = 'migrations';\n\n/** Migration file naming pattern: NNNN_description.sql */\nconst MIGRATION_FILENAME_PATTERN = /^\\d{4}_[\\w-]+\\.sql$/;\n\n// ============================================================================\n// Checksum\n// ============================================================================\n\n/**\n * Compute SHA-256 checksum of content.\n */\nexport function computeChecksum(content: string): string {\n\treturn createHash('sha256').update(content, 'utf-8').digest('hex');\n}\n\n// ============================================================================\n// File Naming\n// ============================================================================\n\n/**\n * Generate the next migration filename.\n *\n * @param existingFiles - List of existing migration filenames\n * @param description - Human-readable description (e.g., \"create_users\")\n * @returns Filename (e.g., \"0001_create_users.sql\")\n */\nexport function generateMigrationFilename(\n\texistingFiles: readonly string[],\n\tdescription: string,\n): string {\n\tconst maxNum = existingFiles.reduce((max, f) => {\n\t\tconst match = f.match(/^(\\d{4})/);\n\t\treturn match?.[1] ? Math.max(max, Number.parseInt(match[1], 10)) : max;\n\t}, 0);\n\n\tconst nextNum = String(maxNum + 1).padStart(4, '0');\n\tconst sanitized = description\n\t\t.toLowerCase()\n\t\t.replace(/[^a-z0-9_-]/g, '_')\n\t\t.replace(/_+/g, '_')\n\t\t.replace(/^_|_$/g, '');\n\n\treturn `${nextNum}_${sanitized || 'migration'}.sql`;\n}\n\n// ============================================================================\n// File I/O\n// ============================================================================\n\n/**\n * Ensure the migrations directory exists.\n */\nexport function ensureMigrationsDir(dir: string): string {\n\tconst fullPath = resolve(dir);\n\tif (!existsSync(fullPath)) {\n\t\tmkdirSync(fullPath, { recursive: true });\n\t}\n\treturn fullPath;\n}\n\n/**\n * Scan migrations directory and return all valid migration files,\n * sorted by name (lexicographic = chronological with zero-padded numbers).\n */\nexport function scanMigrationFiles(dir: string): readonly MigrationFile[] {\n\tconst fullPath = resolve(dir);\n\tif (!existsSync(fullPath)) {\n\t\treturn [];\n\t}\n\n\tconst entries = readdirSync(fullPath)\n\t\t.filter((f) => MIGRATION_FILENAME_PATTERN.test(f))\n\t\t.sort();\n\n\treturn entries.map((name) => {\n\t\tconst filePath = join(fullPath, name);\n\t\tconst content = readFileSync(filePath, 'utf-8');\n\t\treturn {\n\t\t\tname,\n\t\t\tpath: filePath,\n\t\t\tcontent,\n\t\t\tchecksum: computeChecksum(content),\n\t\t};\n\t});\n}\n\n/**\n * Write a migration file to the migrations directory.\n *\n * @returns The created MigrationFile\n */\nexport function writeMigrationFile(\n\tdir: string,\n\tfilename: string,\n\tcontent: string,\n): MigrationFile {\n\tconst fullDir = ensureMigrationsDir(dir);\n\tconst filePath = join(fullDir, filename);\n\twriteFileSync(filePath, content, 'utf-8');\n\n\treturn {\n\t\tname: filename,\n\t\tpath: filePath,\n\t\tcontent,\n\t\tchecksum: computeChecksum(content),\n\t};\n}\n","/**\n * Push Command — Schema Provisioning\n *\n * dbsp push - Push schema to database.\n * Additive by default: only creates missing objects.\n * With --drop: recreates from scratch (preserves _dbsp_migrations).\n */\n\nimport type { SchemaChange, SchemaDiff } from '@dbsp/adapter-pgsql';\nimport {\n\tcomparePgsqlDatabaseSchema,\n\tcreatePgsqlAdapter,\n\tgenerateDDL,\n\tgenerateMigrationSQL,\n\tgetNamingPluginForDbCasing,\n} from '@dbsp/adapter-pgsql';\nimport { Command } from 'commander';\nimport { executeDdl } from '../ddl-executor.js';\nimport { createDbConnection, redactDbUrl } from '../utils/db-utils.js';\nimport { loadSchema } from '../utils/schema-loader.js';\n\n/** Table name reserved for migration history — never dropped by push. */\nconst MIGRATIONS_TABLE = '_dbsp_migrations';\n\nexport const pushCommand = new Command('push')\n\t.description('Push schema to database (additive by default)')\n\t.option(\n\t\t'-s, --schema <path>',\n\t\t'Path to schema file (default: dbsp.schema.ts)',\n\t)\n\t.requiredOption('-d, --db <url>', 'Database connection URL (required)')\n\t.option('--schema-name <name>', 'Database schema name (default: public)')\n\t.option('--drop', 'Drop and recreate all objects (preserves migrations)')\n\t.option('--dry-run', 'Print SQL without executing')\n\t.option('--json', 'Output as JSON')\n\t.action(\n\t\tasync (options: {\n\t\t\tschema?: string;\n\t\t\tdb: string;\n\t\t\tschemaName?: string;\n\t\t\tdrop?: boolean;\n\t\t\tdryRun?: boolean;\n\t\t\tjson?: boolean;\n\t\t}) => {\n\t\t\tconst schemaPath = options.schema ?? 'dbsp.schema.ts';\n\t\t\tconst redactedUrl = redactDbUrl(options.db);\n\n\t\t\tif (!options.json) {\n\t\t\t\tconsole.log(\n\t\t\t\t\t`🚀 Pushing schema: ${schemaPath}${options.drop ? ' (with --drop)' : ''}`,\n\t\t\t\t);\n\t\t\t\tconsole.log(` Database: ${redactedUrl}`);\n\t\t\t\tif (options.schemaName) {\n\t\t\t\t\tconsole.log(` Schema: ${options.schemaName}`);\n\t\t\t\t}\n\t\t\t\tif (options.dryRun) {\n\t\t\t\t\tconsole.log(` Mode: DRY RUN (no changes will be applied)`);\n\t\t\t\t}\n\t\t\t\tconsole.log('');\n\t\t\t}\n\n\t\t\ttry {\n\t\t\t\tconst loaded = await loadSchema(schemaPath);\n\t\t\t\tconst schemaModel = loaded.model;\n\n\t\t\t\tconst { pool } = await createDbConnection(options.db);\n\n\t\t\t\ttry {\n\t\t\t\t\tif (options.drop) {\n\t\t\t\t\t\t// --drop mode: generate full DDL with drops, excluding _dbsp_migrations\n\t\t\t\t\t\tconst statements = generateDDL(schemaModel, {\n\t\t\t\t\t\t\tincludeDropStatements: true,\n\t\t\t\t\t\t\t...(options.schemaName ? { schemaName: options.schemaName } : {}),\n\t\t\t\t\t\t\t...(loaded.dbCasing !== undefined\n\t\t\t\t\t\t\t\t? { naming: getNamingPluginForDbCasing(loaded.dbCasing) }\n\t\t\t\t\t\t\t\t: {}),\n\t\t\t\t\t\t});\n\n\t\t\t\t\t\tconst migrationsPattern =\n\t\t\t\t\t\t\tbuildMigrationsTableDropPattern(MIGRATIONS_TABLE);\n\t\t\t\t\t\tconst filtered = statements.filter(\n\t\t\t\t\t\t\t(stmt) => !migrationsPattern.test(stmt),\n\t\t\t\t\t\t);\n\n\t\t\t\t\t\toutputResult(filtered, options);\n\n\t\t\t\t\t\tconst result = await executeDdl(pool, filtered, {\n\t\t\t\t\t\t\t...(options.dryRun !== undefined\n\t\t\t\t\t\t\t\t? { dryRun: options.dryRun }\n\t\t\t\t\t\t\t\t: {}),\n\t\t\t\t\t\t});\n\n\t\t\t\t\t\tif (options.json) {\n\t\t\t\t\t\t\tconst droppedTables = statements\n\t\t\t\t\t\t\t\t.filter((s) => /DROP\\s+TABLE/i.test(s))\n\t\t\t\t\t\t\t\t.filter((s) => !migrationsPattern.test(s))\n\t\t\t\t\t\t\t\t.map(extractDroppedTableName)\n\t\t\t\t\t\t\t\t.filter((t): t is string => t !== undefined);\n\t\t\t\t\t\t\tconsole.log(\n\t\t\t\t\t\t\t\tJSON.stringify(\n\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\tstatus: options.dryRun ? 'dry-run' : 'dropped',\n\t\t\t\t\t\t\t\t\t\ttables: droppedTables,\n\t\t\t\t\t\t\t\t\t\ttablesDropped: droppedTables.length,\n\t\t\t\t\t\t\t\t\t\tstatementsExecuted: result.statementsExecuted,\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\tnull,\n\t\t\t\t\t\t\t\t\t2,\n\t\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t} else if (!options.dryRun) {\n\t\t\t\t\t\t\tconsole.log(\n\t\t\t\t\t\t\t\t`\\n✅ Push complete: ${result.statementsExecuted} statements executed`,\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t}\n\t\t\t\t\t} else {\n\t\t\t\t\t\t// Additive mode: live diff -> generate migration SQL (additive only)\n\t\t\t\t\t\tconst adapter = createPgsqlAdapter(pool);\n\t\t\t\t\t\tconst compareOptions = {\n\t\t\t\t\t\t\t...(options.schemaName ? { schema: options.schemaName } : {}),\n\t\t\t\t\t\t\t...(loaded.dbCasing ? { dbCasing: loaded.dbCasing } : {}),\n\t\t\t\t\t\t\tonWarning: (message: string) => console.warn(`⚠️ ${message}`),\n\t\t\t\t\t\t};\n\t\t\t\t\t\tconst diff = await comparePgsqlDatabaseSchema(\n\t\t\t\t\t\t\tadapter,\n\t\t\t\t\t\t\tschemaModel,\n\t\t\t\t\t\t\tcompareOptions,\n\t\t\t\t\t\t);\n\n\t\t\t\t\t\t// Generate SQL for additive changes only (no destructive)\n\t\t\t\t\t\tconst statements = generateMigrationSQL(diff, {\n\t\t\t\t\t\t\tincludeDestructive: false,\n\t\t\t\t\t\t\t...(options.schemaName ? { schemaName: options.schemaName } : {}),\n\t\t\t\t\t\t});\n\n\t\t\t\t\t\t// Collect warnings for skipped non-additive changes\n\t\t\t\t\t\tconst skippedChanges = diff.changes.filter((c) => c.destructive);\n\n\t\t\t\t\t\tif (!options.json && skippedChanges.length > 0) {\n\t\t\t\t\t\t\tconsole.log(\n\t\t\t\t\t\t\t\t`⚠️ ${skippedChanges.length} non-additive change(s) skipped:`,\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\tfor (const change of skippedChanges) {\n\t\t\t\t\t\t\t\tconsole.log(` - ${change.details}`);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tconsole.log('');\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tif (statements.length === 0) {\n\t\t\t\t\t\t\tif (options.json) {\n\t\t\t\t\t\t\t\tconsole.log(\n\t\t\t\t\t\t\t\t\tJSON.stringify(\n\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\tstatus: 'up-to-date',\n\t\t\t\t\t\t\t\t\t\t\tstatementsExecuted: 0,\n\t\t\t\t\t\t\t\t\t\t\tskippedChanges: skippedChanges.length,\n\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\tnull,\n\t\t\t\t\t\t\t\t\t\t2,\n\t\t\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tconsole.log('✅ Database is up to date — nothing to push.');\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t// EH-14: return instead of process.exit(0) so pool.end() in finally runs\n\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\toutputResult(statements, options);\n\n\t\t\t\t\t\tconst result = await executeDdl(pool, statements, {\n\t\t\t\t\t\t\t...(options.dryRun !== undefined\n\t\t\t\t\t\t\t\t? { dryRun: options.dryRun }\n\t\t\t\t\t\t\t\t: {}),\n\t\t\t\t\t\t});\n\n\t\t\t\t\t\tif (!options.dryRun) {\n\t\t\t\t\t\t\tconst postApplyDiff = await comparePgsqlDatabaseSchema(\n\t\t\t\t\t\t\t\tadapter,\n\t\t\t\t\t\t\t\tschemaModel,\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t...compareOptions,\n\t\t\t\t\t\t\t\t\tpreviouslyAppliedDiff: diff,\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\tassertNoUnexpectedResidualDrift(postApplyDiff, skippedChanges);\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tif (options.json) {\n\t\t\t\t\t\t\tconsole.log(\n\t\t\t\t\t\t\t\tJSON.stringify(\n\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\tstatus: options.dryRun ? 'dry-run' : 'applied',\n\t\t\t\t\t\t\t\t\t\tstatementsExecuted: result.statementsExecuted,\n\t\t\t\t\t\t\t\t\t\tskippedChanges: skippedChanges.length,\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\tnull,\n\t\t\t\t\t\t\t\t\t2,\n\t\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t} else if (!options.dryRun) {\n\t\t\t\t\t\t\tconsole.log(\n\t\t\t\t\t\t\t\t`\\n✅ Push complete: ${result.statementsExecuted} statement(s) executed`,\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\t// EH-14: return so finally runs pool.end() before the outer success path\n\t\t\t\t} finally {\n\t\t\t\t\tawait pool.end();\n\t\t\t\t}\n\t\t\t} catch (error) {\n\t\t\t\tconst message =\n\t\t\t\t\terror instanceof Error ? error.message : 'Unknown error occurred';\n\t\t\t\t// CC-2+EH-7: If --json, error goes to stdout as JSON; otherwise stderr\n\t\t\t\tif (options.json) {\n\t\t\t\t\tconsole.log(\n\t\t\t\t\t\tJSON.stringify({ status: 'error', error: message }, null, 2),\n\t\t\t\t\t);\n\t\t\t\t} else {\n\t\t\t\t\tconsole.error(`❌ Error: ${message}`);\n\t\t\t\t}\n\t\t\t\tprocess.exit(1);\n\t\t\t}\n\t\t},\n\t);\n\n/**\n * Output SQL statements (dry-run or --json mode).\n */\n/**\n * Match a `DROP TABLE` statement that targets the migrations table, so `--drop`\n * can never destroy dbsp's own migration history.\n *\n * The table name is escaped before it reaches the pattern: an unescaped `.` or\n * `$` in it would otherwise match characters it should not, and a table whose\n * name merely resembles the migrations table would be spared — or worse, one\n * that should be spared would be dropped.\n *\n * The pattern matches the quoted name anywhere in the statement rather than\n * anchoring to the end. That is safe because `generateDDL` returns one statement\n * per array entry, so a match can never jump across a statement boundary.\n */\nexport function buildMigrationsTableDropPattern(tableName: string): RegExp {\n\tconst escaped = tableName.replace(/[.*+?^${}()|[\\]\\\\]/gu, '\\\\$&');\n\treturn new RegExp(\n\t\t`DROP\\\\s+TABLE(?:\\\\s+IF\\\\s+EXISTS)?(?:\\\\s+\"[^\"]*\"\\\\s*\\\\.)?\\\\s*\"${escaped}\"`,\n\t\t'i',\n\t);\n}\n\n/**\n * Read the table name out of a `DROP TABLE` statement for the JSON report.\n *\n * The name is the last quoted identifier, which skips the schema in\n * `\"public\".\"users\"`, and it may be followed by `CASCADE` before the semicolon.\n * Returns the statement itself when nothing matches, so the report never silently\n * loses a table.\n */\nexport function extractDroppedTableName(statement: string): string {\n\tconst match = statement.match(/\"([^\"]+)\"\\s*(?:CASCADE\\s*)?;?\\s*$/iu);\n\treturn match?.[1] ?? statement;\n}\n\nfunction outputResult(\n\tstatements: readonly string[],\n\toptions: { dryRun?: boolean; json?: boolean },\n): void {\n\tif (options.dryRun && !options.json) {\n\t\tconsole.log(`-- Dry run: ${statements.length} statement(s)\\n`);\n\t\tfor (const stmt of statements) {\n\t\t\tconsole.log(`${stmt};\\n`);\n\t\t}\n\t}\n}\n\nfunction assertNoUnexpectedResidualDrift(\n\tpostApplyDiff: SchemaDiff,\n\tskippedChanges: readonly SchemaChange[],\n): void {\n\tconst skippedKeys = new Set(skippedChanges.map(changeIdentity));\n\tconst unexpected = postApplyDiff.changes.filter(\n\t\t(change) =>\n\t\t\t!(change.destructive && skippedKeys.has(changeIdentity(change))),\n\t);\n\tif (unexpected.length === 0) return;\n\n\tthrow new Error(\n\t\t`Push did not converge after applying DDL; ${unexpected.length} outstanding change(s) remain:\\n` +\n\t\t\tunexpected.map(formatOutstandingChange).join('\\n'),\n\t);\n}\n\nfunction changeIdentity(change: SchemaChange): string {\n\treturn JSON.stringify([\n\t\tchange.kind,\n\t\tchange.table,\n\t\tchange.column ?? null,\n\t\tchange.details,\n\t\tchange.destructive,\n\t]);\n}\n\nfunction formatOutstandingChange(change: SchemaChange): string {\n\tconst target = change.column\n\t\t? `${change.table}.${change.column}`\n\t\t: change.table || 'schema';\n\treturn ` - ${change.details} (${change.kind}: ${target})`;\n}\n","/**\n * DDL Executor — Transaction-wrapped DDL execution.\n *\n * Shared by `dbsp push` and `dbsp migrate apply`.\n * Executes an array of SQL statements inside a single transaction.\n */\n\nimport type { Pool, PoolClient } from 'pg';\n\n// ============================================================================\n// Types\n// ============================================================================\n\nexport interface DdlExecutionResult {\n\t/** Number of statements executed */\n\tstatementsExecuted: number;\n\t/** Whether execution was a dry-run (no actual changes) */\n\tdryRun: boolean;\n}\n\n// ============================================================================\n// Executor\n// ============================================================================\n\n/**\n * Execute DDL statements in a transaction.\n *\n * @param pool - pg Pool instance\n * @param statements - SQL statements to execute\n * @param options - Execution options\n * @returns Execution result\n */\nexport async function executeDdl(\n\tpool: Pool,\n\tstatements: readonly string[],\n\toptions?: { dryRun?: boolean },\n): Promise<DdlExecutionResult> {\n\tif (statements.length === 0) {\n\t\treturn { statementsExecuted: 0, dryRun: options?.dryRun ?? false };\n\t}\n\n\tif (options?.dryRun) {\n\t\treturn { statementsExecuted: statements.length, dryRun: true };\n\t}\n\n\tlet client: PoolClient | undefined;\n\ttry {\n\t\tclient = await pool.connect();\n\t\tawait client.query('BEGIN');\n\n\t\tfor (const stmt of statements) {\n\t\t\tawait client.query(stmt);\n\t\t}\n\n\t\tawait client.query('COMMIT');\n\t\treturn { statementsExecuted: statements.length, dryRun: false };\n\t} catch (error) {\n\t\tif (client) {\n\t\t\tawait client.query('ROLLBACK');\n\t\t}\n\t\tthrow error;\n\t} finally {\n\t\tif (client) {\n\t\t\tclient.release();\n\t\t}\n\t}\n}\n","/**\n * DX-030 Block 1: REPL Command\n *\n * dbsp repl [--schema <path>] - Launch interactive REPL.\n * CLI-022: Batch mode support with --eval and --input options.\n */\n\nimport { readFileSync } from 'node:fs';\nimport type { DbCasing } from '@dbsp/types';\nimport { Command } from 'commander';\nimport { config } from '../config.js';\nimport { validateIdentifier } from '../utils/identifier-validation.js';\nimport { loadSchema, loadSchemaFromCwd } from '../utils/schema-loader.js';\n\nexport interface ReplOptions {\n\tschema?: string;\n\t/** CLI-020: Database connection URL for execution mode */\n\tdb?: string;\n\t/** CLI-022: Single query to evaluate (batch mode) */\n\teval?: string;\n\t/** CLI-022: File containing queries to execute (batch mode, one per line) */\n\tinput?: string;\n\t/** CLI-022: Output format for batch mode */\n\tformat?: 'text' | 'json';\n\t/** DEMO-E2E: Assertion file for validating query output */\n\tassert?: string;\n\t/** CLI-IMPORT: SQL files to import before queries (injected as .import commands) */\n\timport?: string[];\n\t/** CLI-USE: PostgreSQL schema to use (injected as .use command) */\n\tuse?: string;\n\t/** CLI-MUT: Start REPL with parse mode enabled */\n\tparse?: boolean;\n\t/** CLI-MUT: Start REPL with exec mode enabled */\n\texec?: boolean;\n\t/** CLI-CONFIG: Custom config file path (default: ~/.dbsp/config.json) */\n\tconfig?: string;\n\t/** CLI-CASING: Column naming convention (describes DB column casing) */\n\tcasing?: 'snake' | 'camel' | 'none';\n}\n\nexport const replCommand = new Command('repl')\n\t.description('Launch interactive REPL for exploring schema and queries')\n\t.option('-s, --schema <path>', 'Path to schema file (default: auto-detect)')\n\t.option(\n\t\t'-d, --db <url>',\n\t\t'PostgreSQL connection URL for execution mode (e.g., postgres://localhost/mydb)',\n\t)\n\t.option('-e, --eval <query>', 'Execute a single query and exit (batch mode)')\n\t.option(\n\t\t'-i, --input <file>',\n\t\t'Execute queries from file, one per line (batch mode)',\n\t)\n\t.option(\n\t\t'-f, --format <format>',\n\t\t'Output format for batch mode: text (default) or json',\n\t\t'text',\n\t)\n\t.option(\n\t\t'-a, --assert <file>',\n\t\t'Assertion file to validate query output (requires --input)',\n\t)\n\t.option(\n\t\t'--import <files...>',\n\t\t'SQL files to import before queries (equivalent to .import commands)',\n\t)\n\t.option(\n\t\t'--use <schema>',\n\t\t'PostgreSQL schema to use (equivalent to .use command)',\n\t)\n\t.option('--parse', 'Start REPL with parse mode enabled (.parse toggle)')\n\t.option('--exec', 'Start REPL with exec mode enabled (.exec toggle)')\n\t.option(\n\t\t'--casing <type>',\n\t\t'Column naming convention: snake (DB uses snake_case), camel (DB uses camelCase), none (preserve as-is)',\n\t)\n\t.option(\n\t\t'-c, --config <path>',\n\t\t'Custom config file path (default: ~/.dbsp/config.json)',\n\t)\n\t.action(async (options: ReplOptions) => {\n\t\t// Set custom config path if provided\n\t\tif (options.config) {\n\t\t\tconfig.setConfigPath(options.config);\n\t\t}\n\t\t// Load config\n\t\tconfig.load();\n\t\ttry {\n\t\t\t// Load schema\n\t\t\tlet schemaPath: string;\n\t\t\tlet schema: Awaited<ReturnType<typeof loadSchema>>;\n\n\t\t\tif (options.schema) {\n\t\t\t\tschema = await loadSchema(options.schema);\n\t\t\t\tschemaPath = options.schema;\n\t\t\t} else {\n\t\t\t\tconst result = await loadSchemaFromCwd();\n\t\t\t\tschema = result.schema;\n\t\t\t\tschemaPath = result.path;\n\t\t\t}\n\n\t\t\t// DEMO-E2E: Validate --assert requires --input\n\t\t\tif (options.assert && !options.input) {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t'--assert requires --input (assertion files validate query output from input files)',\n\t\t\t\t);\n\t\t\t}\n\n\t\t\t// CLI-IMPORT: Validate that --import requires batch mode (SQL file execution)\n\t\t\t// Note: --use, --parse, --exec work in interactive mode (REPL state setup)\n\t\t\tif (options.import && !options.eval && !options.input) {\n\t\t\t\tthrow new Error('--import requires batch mode (--eval or --input)');\n\t\t\t}\n\n\t\t\t// SEC: Validate --use schema name at the entry point so both the batch path\n\t\t\t// (which injects `.use <schema>`) and the interactive path (which passes\n\t\t\t// initialSchemaName) are protected against SQL injection.\n\t\t\tif (options.use) {\n\t\t\t\tvalidateIdentifier(options.use, 'schema');\n\t\t\t}\n\n\t\t\t// CLI-CASING: explicit flag wins; otherwise use the schema declaration.\n\t\t\tconst dbCasing = mapReplCasingOption(options.casing) ?? schema.dbCasing;\n\n\t\t\t// CLI-022: Batch mode - execute queries without interactive UI\n\t\t\tif (options.eval || options.input) {\n\t\t\t\tconst { runBatchMode } = await import('../repl/batch.js');\n\t\t\t\tconst queries: string[] = [];\n\n\t\t\t\t// CLI-USE: Inject .use command first (schema scoping)\n\t\t\t\tif (options.use) {\n\t\t\t\t\tqueries.push(`.use ${options.use}`);\n\t\t\t\t}\n\n\t\t\t\t// CLI-IMPORT: Inject .import commands for SQL files\n\t\t\t\tif (options.import) {\n\t\t\t\t\tfor (const file of options.import) {\n\t\t\t\t\t\tqueries.push(`.import ${file}`);\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tif (options.eval) {\n\t\t\t\t\tqueries.push(options.eval);\n\t\t\t\t}\n\n\t\t\t\tif (options.input) {\n\t\t\t\t\t// EH-2: Map ENOENT to a friendly error instead of raw stack trace\n\t\t\t\t\tlet content: string;\n\t\t\t\t\ttry {\n\t\t\t\t\t\tcontent = readFileSync(options.input, 'utf-8');\n\t\t\t\t\t} catch (err) {\n\t\t\t\t\t\tconst isNotFound =\n\t\t\t\t\t\t\terr instanceof Error &&\n\t\t\t\t\t\t\t'code' in err &&\n\t\t\t\t\t\t\t(err as NodeJS.ErrnoException).code === 'ENOENT';\n\t\t\t\t\t\tthrow new Error(\n\t\t\t\t\t\t\tisNotFound\n\t\t\t\t\t\t\t\t? `Input file not found: ${options.input}`\n\t\t\t\t\t\t\t\t: `Failed to read input file: ${err instanceof Error ? err.message : String(err)}`,\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\t\t\t\t\tqueries.push(...content.split('\\n'));\n\t\t\t\t}\n\n\t\t\t\tawait runBatchMode({\n\t\t\t\t\tqueries,\n\t\t\t\t\tschema,\n\t\t\t\t\tschemaPath,\n\t\t\t\t\tformat: options.format ?? 'text',\n\t\t\t\t\t...(options.db && { databaseUrl: options.db }),\n\t\t\t\t\t...(options.assert && { assertFile: options.assert }),\n\t\t\t\t\t...(dbCasing && { dbCasing }),\n\t\t\t\t});\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\t// Dynamic import to avoid loading React/Ink for other commands\n\t\t\tconst { startRepl } = await import('../repl/index.js');\n\t\t\t// CLI-020: Pass database URL if provided\n\t\t\t// CLI-MUT: Pass initial REPL state options\n\t\t\tawait startRepl({\n\t\t\t\tschema,\n\t\t\t\tschemaPath,\n\t\t\t\t...(options.db && { databaseUrl: options.db }),\n\t\t\t\t...(options.use && { initialSchemaName: options.use }),\n\t\t\t\t...(options.parse && { initialParseMode: true }),\n\t\t\t\t...(options.exec && { initialExecMode: true }),\n\t\t\t\t...(dbCasing && { dbCasing }),\n\t\t\t});\n\t\t} catch (error) {\n\t\t\tconst message = error instanceof Error ? error.message : String(error);\n\t\t\tconsole.error(`❌ ${message}`);\n\t\t\tprocess.exit(1);\n\t\t}\n\t});\n\nfunction mapReplCasingOption(\n\tcasing: ReplOptions['casing'],\n): DbCasing | undefined {\n\tswitch (casing) {\n\t\tcase 'snake':\n\t\t\treturn 'snake_case';\n\t\tcase 'camel':\n\t\t\treturn 'camelCase';\n\t\tcase 'none':\n\t\t\treturn 'preserve';\n\t\tdefault:\n\t\t\treturn undefined;\n\t}\n}\n","/**\n * Verify Command — Schema Drift Detection\n *\n * dbsp verify - Compare schema vs real database using the comparison engine.\n * Detects drift in tables, columns, types, nullable, defaults, FKs, indexes.\n */\n\nimport {\n\tcomparePgsqlDatabaseSchema,\n\tcreatePgsqlAdapter,\n} from '@dbsp/adapter-pgsql';\nimport { Command } from 'commander';\nimport { createDbConnection, redactDbUrl } from '../utils/db-utils.js';\nimport { loadSchema } from '../utils/schema-loader.js';\nimport { formatVerifyResult, verifyFromDiff } from '../verifier.js';\n\nexport const verifyCommand = new Command('verify')\n\t.description('Compare schema vs real database (drift detection)')\n\t.option(\n\t\t'-s, --schema <path>',\n\t\t'Path to schema file (default: dbsp.schema.ts)',\n\t)\n\t.requiredOption('-d, --db <url>', 'Database connection URL (required)')\n\t.option('--schema-name <name>', 'Database schema name (default: public)')\n\t.option('--json', 'Output as JSON')\n\t.action(\n\t\tasync (options: {\n\t\t\tschema?: string;\n\t\t\tdb: string;\n\t\t\tschemaName?: string;\n\t\t\tjson?: boolean;\n\t\t}) => {\n\t\t\tconst schemaPath = options.schema ?? 'dbsp.schema.ts';\n\n\t\t\tconst redactedUrl = redactDbUrl(options.db);\n\n\t\t\tif (!options.json) {\n\t\t\t\tconsole.log(`🔍 Verifying schema: ${schemaPath}`);\n\t\t\t\tconsole.log(` Database: ${redactedUrl}`);\n\t\t\t\tif (options.schemaName) {\n\t\t\t\t\tconsole.log(` Schema: ${options.schemaName}`);\n\t\t\t\t}\n\t\t\t\tconsole.log('');\n\t\t\t}\n\n\t\t\ttry {\n\t\t\t\t// Load schema from file → ModelIR\n\t\t\t\tconst loaded = await loadSchema(schemaPath);\n\t\t\t\tconst schemaModel = loaded.model;\n\n\t\t\t\t// Connect to database\n\t\t\t\tconst { pool } = await createDbConnection(options.db);\n\n\t\t\t\ttry {\n\t\t\t\t\tconst adapter = createPgsqlAdapter(pool);\n\t\t\t\t\t// Live diff: introspect database and canonicalise PostgreSQL CHECK\n\t\t\t\t\t// expressions before comparing.\n\t\t\t\t\tconst diff = await comparePgsqlDatabaseSchema(adapter, schemaModel, {\n\t\t\t\t\t\t...(options.schemaName ? { schema: options.schemaName } : {}),\n\t\t\t\t\t\t...(loaded.dbCasing !== undefined\n\t\t\t\t\t\t\t? { dbCasing: loaded.dbCasing }\n\t\t\t\t\t\t\t: {}),\n\t\t\t\t\t\tonWarning: (message) => console.warn(`⚠️ ${message}`),\n\t\t\t\t\t});\n\n\t\t\t\t\t// Convert to verify result\n\t\t\t\t\t// Keep the legacy reporting fields sourced from introspection so\n\t\t\t\t\t// --json consumers continue to see database table names as before.\n\t\t\t\t\tconst dbModel = await adapter.introspect({\n\t\t\t\t\t\t...(options.schemaName ? { schema: options.schemaName } : {}),\n\t\t\t\t\t});\n\t\t\t\t\tconst schemaTables = Array.from(schemaModel.tables.keys());\n\t\t\t\t\tconst dbTables = Array.from(dbModel.tables.keys());\n\t\t\t\t\tconst result = verifyFromDiff(diff, schemaTables, dbTables);\n\n\t\t\t\t\t// Output\n\t\t\t\t\tif (options.json) {\n\t\t\t\t\t\t// Exclude the full diff meta from JSON output (too verbose)\n\t\t\t\t\t\tconst { diff: _diff, ...jsonResult } = result;\n\t\t\t\t\t\tconsole.log(\n\t\t\t\t\t\t\tJSON.stringify(\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t...jsonResult,\n\t\t\t\t\t\t\t\t\tsummary: diff.summary,\n\t\t\t\t\t\t\t\t\thasDestructive: diff.hasDestructive,\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\tnull,\n\t\t\t\t\t\t\t\t2,\n\t\t\t\t\t\t\t),\n\t\t\t\t\t\t);\n\t\t\t\t\t} else {\n\t\t\t\t\t\tconsole.log(formatVerifyResult(result));\n\t\t\t\t\t}\n\n\t\t\t\t\t// EH-14: set exit code; let finally run pool.end() before process exits\n\t\t\t\t\tprocess.exitCode = result.valid ? 0 : 1;\n\t\t\t\t\treturn;\n\t\t\t\t} finally {\n\t\t\t\t\t// Close database connection\n\t\t\t\t\tawait pool.end();\n\t\t\t\t}\n\t\t\t} catch (error) {\n\t\t\t\tconst message =\n\t\t\t\t\terror instanceof Error ? error.message : 'Unknown error occurred';\n\t\t\t\t// CC-2+EH-7: If --json, error goes to stdout as JSON; otherwise stderr\n\t\t\t\tif (options.json) {\n\t\t\t\t\tconsole.log(\n\t\t\t\t\t\tJSON.stringify({ status: 'error', error: message }, null, 2),\n\t\t\t\t\t);\n\t\t\t\t} else {\n\t\t\t\t\tconsole.error(`❌ Error: ${message}`);\n\t\t\t\t}\n\t\t\t\tprocess.exit(1);\n\t\t\t}\n\t\t},\n\t);\n","/**\n * Schema Verifier — Drift Detection via Comparison Engine\n *\n * Compares schema definition against real database using the\n * adapter's compareSchemata engine for full drift detection.\n *\n * Detects: tables, columns, types, nullable, defaults, FKs, indexes, PKs.\n */\n\nimport type { ChangeKind, SchemaChange, SchemaDiff } from '@dbsp/adapter-pgsql';\n\n// ============================================================================\n// Types\n// ============================================================================\n\nexport type DriftSeverity = 'error' | 'warning' | 'info';\n\nexport type DriftType =\n\t// Tables\n\t| 'missing_table_in_db'\n\t| 'missing_table_in_schema'\n\t// Columns\n\t| 'missing_column_in_db'\n\t| 'missing_column_in_schema'\n\t| 'type_mismatch'\n\t| 'nullable_mismatch'\n\t| 'default_mismatch'\n\t| 'unique_mismatch'\n\t// Constraints\n\t| 'primary_key_mismatch'\n\t| 'missing_fk_in_db'\n\t| 'missing_fk_in_schema'\n\t| 'fk_on_delete_mismatch'\n\t// Indexes\n\t| 'missing_index_in_db'\n\t| 'missing_index_in_schema'\n\t// CHECK constraints\n\t| 'missing_check_in_db'\n\t| 'missing_check_in_schema'\n\t// ENUM types\n\t| 'missing_enum_in_db'\n\t| 'missing_enum_in_schema'\n\t| 'enum_value_mismatch'\n\t// Column enhancements\n\t| 'collation_mismatch'\n\t| 'identity_mismatch'\n\t// Comments\n\t| 'comment_mismatch'\n\t// Extensions & Sequences\n\t| 'missing_extension'\n\t| 'missing_sequence'\n\t| 'sequence_mismatch'\n\t// Constraints\n\t| 'constraint_validation_mismatch'\n\t// Row-Level Security\n\t| 'rls_enabled_mismatch'\n\t| 'missing_policy_in_db'\n\t| 'missing_policy_in_schema';\n\nexport interface DriftIssue {\n\t/** Issue severity */\n\tseverity: DriftSeverity;\n\t/** Human-readable message */\n\tmessage: string;\n\t/** Table affected (if any) */\n\ttable?: string;\n\t/** Column affected (if any) */\n\tcolumn?: string;\n\t/** Issue type for programmatic handling */\n\ttype: DriftType;\n}\n\nexport interface VerifyResult {\n\t/** Whether the schema matches the database */\n\tvalid: boolean;\n\t/** List of drift issues found */\n\tissues: DriftIssue[];\n\t/** Tables in schema */\n\tschemaTables: string[];\n\t/** Tables in database */\n\tdbTables: string[];\n\t/** Structured diff (for programmatic consumers) */\n\tdiff: SchemaDiff;\n}\n\n// ============================================================================\n// Change → Drift Mapping\n// ============================================================================\n\nconst CHANGE_TO_DRIFT: Record<\n\tChangeKind,\n\t{ type: DriftType; severity: DriftSeverity }\n> = {\n\tcreate_table: { type: 'missing_table_in_db', severity: 'error' },\n\tdrop_table: { type: 'missing_table_in_schema', severity: 'warning' },\n\tadd_column: { type: 'missing_column_in_db', severity: 'error' },\n\tdrop_column: { type: 'missing_column_in_schema', severity: 'info' },\n\talter_column_type: { type: 'type_mismatch', severity: 'error' },\n\talter_column_nullable: { type: 'nullable_mismatch', severity: 'warning' },\n\talter_column_default: { type: 'default_mismatch', severity: 'warning' },\n\talter_column_unique: { type: 'unique_mismatch', severity: 'warning' },\n\tadd_primary_key: { type: 'primary_key_mismatch', severity: 'error' },\n\tdrop_primary_key: { type: 'primary_key_mismatch', severity: 'error' },\n\tadd_foreign_key: { type: 'missing_fk_in_db', severity: 'error' },\n\tdrop_foreign_key: { type: 'missing_fk_in_schema', severity: 'warning' },\n\talter_foreign_key: { type: 'fk_on_delete_mismatch', severity: 'warning' },\n\tcreate_index: { type: 'missing_index_in_db', severity: 'warning' },\n\tdrop_index: { type: 'missing_index_in_schema', severity: 'info' },\n\t// CHECK constraints\n\tadd_check_constraint: { type: 'missing_check_in_db', severity: 'warning' },\n\tdrop_check_constraint: { type: 'missing_check_in_schema', severity: 'info' },\n\t// ENUM types\n\tcreate_enum: { type: 'missing_enum_in_db', severity: 'error' },\n\talter_enum_add_value: { type: 'enum_value_mismatch', severity: 'warning' },\n\tdrop_enum: { type: 'missing_enum_in_schema', severity: 'warning' },\n\t// Column enhancements\n\talter_column_collation: { type: 'collation_mismatch', severity: 'warning' },\n\talter_column_identity: { type: 'identity_mismatch', severity: 'warning' },\n\t// Comments\n\tadd_comment: { type: 'comment_mismatch', severity: 'info' },\n\tdrop_comment: { type: 'comment_mismatch', severity: 'info' },\n\t// Extensions & Sequences\n\tcreate_extension: { type: 'missing_extension', severity: 'error' },\n\tdrop_extension: { type: 'missing_extension', severity: 'info' },\n\tcreate_sequence: { type: 'missing_sequence', severity: 'warning' },\n\talter_sequence: { type: 'sequence_mismatch', severity: 'warning' },\n\tdrop_sequence: { type: 'missing_sequence', severity: 'info' },\n\t// Constraints\n\tvalidate_constraint: {\n\t\ttype: 'constraint_validation_mismatch',\n\t\tseverity: 'warning',\n\t},\n\t// Row-Level Security\n\tenable_rls: { type: 'rls_enabled_mismatch', severity: 'warning' },\n\tdisable_rls: { type: 'rls_enabled_mismatch', severity: 'warning' },\n\tcreate_policy: { type: 'missing_policy_in_db', severity: 'warning' },\n\tdrop_policy: { type: 'missing_policy_in_schema', severity: 'warning' },\n};\n\nfunction changeToDriftIssue(change: SchemaChange): DriftIssue {\n\tconst mapping = CHANGE_TO_DRIFT[change.kind] ?? {\n\t\ttype: 'type_mismatch' as DriftType,\n\t\tseverity: 'warning' as DriftSeverity,\n\t};\n\treturn {\n\t\tseverity: mapping.severity,\n\t\ttype: mapping.type,\n\t\ttable: change.table,\n\t\t...(change.column !== undefined ? { column: change.column } : {}),\n\t\tmessage: change.details,\n\t};\n}\n\n// ============================================================================\n// Verification (from SchemaDiff)\n// ============================================================================\n\n/**\n * Convert a SchemaDiff into a VerifyResult.\n *\n * @param diff - Structured diff from compareSchemata()\n * @param schemaTables - Table names in schema (for backward compat)\n * @param dbTables - Table names in database (for backward compat)\n */\nexport function verifyFromDiff(\n\tdiff: SchemaDiff,\n\tschemaTables: string[],\n\tdbTables: string[],\n): VerifyResult {\n\tconst issues = diff.changes.map(changeToDriftIssue);\n\n\t// Sort by severity (error > warning > info)\n\tconst severityOrder: Record<DriftSeverity, number> = {\n\t\terror: 0,\n\t\twarning: 1,\n\t\tinfo: 2,\n\t};\n\tissues.sort(\n\t\t(a: DriftIssue, b: DriftIssue) =>\n\t\t\tseverityOrder[a.severity] - severityOrder[b.severity],\n\t);\n\n\tconst hasErrors = issues.some((i: DriftIssue) => i.severity === 'error');\n\n\treturn {\n\t\tvalid: !hasErrors,\n\t\tissues,\n\t\tschemaTables,\n\t\tdbTables,\n\t\tdiff,\n\t};\n}\n\n// ============================================================================\n// Format\n// ============================================================================\n\n/**\n * Format verification result for CLI output.\n */\nexport function formatVerifyResult(result: VerifyResult): string {\n\tconst lines: string[] = [];\n\n\tif (result.valid) {\n\t\tlines.push('✅ Schema matches database');\n\t} else {\n\t\tlines.push('❌ Schema drift detected');\n\t}\n\n\tlines.push('');\n\tlines.push(`Tables in schema: ${result.schemaTables.length}`);\n\tlines.push(`Tables in database: ${result.dbTables.length}`);\n\tlines.push('');\n\n\tif (result.issues.length === 0) {\n\t\tlines.push('No issues found.');\n\t} else {\n\t\tconst errors = result.issues.filter((i) => i.severity === 'error');\n\t\tconst warnings = result.issues.filter((i) => i.severity === 'warning');\n\t\tconst infos = result.issues.filter((i) => i.severity === 'info');\n\n\t\tif (errors.length > 0) {\n\t\t\tlines.push(`❌ ${errors.length} error(s):`);\n\t\t\tfor (const issue of errors) {\n\t\t\t\tlines.push(` ${issue.message}`);\n\t\t\t}\n\t\t\tlines.push('');\n\t\t}\n\n\t\tif (warnings.length > 0) {\n\t\t\tlines.push(`⚠️ ${warnings.length} warning(s):`);\n\t\t\tfor (const issue of warnings) {\n\t\t\t\tlines.push(` ${issue.message}`);\n\t\t\t}\n\t\t\tlines.push('');\n\t\t}\n\n\t\tif (infos.length > 0) {\n\t\t\tlines.push(`ℹ️ ${infos.length} info:`);\n\t\t\tfor (const issue of infos) {\n\t\t\t\tlines.push(` ${issue.message}`);\n\t\t\t}\n\t\t\tlines.push('');\n\t\t}\n\t}\n\n\treturn lines.join('\\n');\n}\n"],"mappings":"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","names":["Command","outputPath","mkdirSync","writeFileSync","dirname","resolve","Command","Command","resolve","mkdirSync","dirname","writeFileSync","Command","mkdirSync","writeFileSync","resolve","Command","comparePgsqlDatabaseSchema","createPgsqlAdapter","generateMigrationSQL","Command","Command","createPgsqlAdapter","comparePgsqlDatabaseSchema","generateMigrationSQL","readFileSync","Command","Command","readFileSync","comparePgsqlDatabaseSchema","createPgsqlAdapter","Command","Command","createPgsqlAdapter","comparePgsqlDatabaseSchema","Command"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dbsp/cli",
3
- "version": "2.0.1",
3
+ "version": "2.1.0",
4
4
  "description": "CLI tools for db-semantic-planner — REPL, schema generation, DDL provisioning",
5
5
  "author": "Olivier Orabona <oorabona@users.noreply.github.com>",
6
6
  "license": "MIT",
@@ -60,10 +60,10 @@
60
60
  "commander": "^15.0.0",
61
61
  "ink": "^7.1.0",
62
62
  "react": "^19.2.7",
63
- "@dbsp/core": "3.0.0",
64
- "@dbsp/adapter-pgsql": "3.0.0",
65
- "@dbsp/nql": "1.9.5",
66
- "@dbsp/types": "3.0.0"
63
+ "@dbsp/adapter-pgsql": "3.1.0",
64
+ "@dbsp/nql": "1.10.0",
65
+ "@dbsp/core": "3.1.0",
66
+ "@dbsp/types": "3.1.0"
67
67
  },
68
68
  "peerDependencies": {
69
69
  "pg": "^8.16.0",