@dbsp/cli 1.0.9 → 1.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.
|
@@ -172,6 +172,48 @@ function generateTableCode(table, options) {
|
|
|
172
172
|
${columnLines.join(",\n")},
|
|
173
173
|
}`;
|
|
174
174
|
}
|
|
175
|
+
function generatedName(name, options) {
|
|
176
|
+
return options.dbCasing === "snake_case" ? snakeToCamelCase(name) : name;
|
|
177
|
+
}
|
|
178
|
+
function stringArrayLiteral(values) {
|
|
179
|
+
return `[${values.map(singleQuoteEscape).join(", ")}]`;
|
|
180
|
+
}
|
|
181
|
+
function compositeForeignKeys(table) {
|
|
182
|
+
return table.foreignKeys.filter(
|
|
183
|
+
(fk) => fk.columns.length > 1 || fk.references.columns.length > 1
|
|
184
|
+
);
|
|
185
|
+
}
|
|
186
|
+
function generateCompositeConstraintCode(model, options) {
|
|
187
|
+
const tableBlocks = [];
|
|
188
|
+
for (const table of model.tables.values()) {
|
|
189
|
+
const fks = compositeForeignKeys(table);
|
|
190
|
+
if (fks.length === 0) continue;
|
|
191
|
+
const fkLines = fks.map((fk) => {
|
|
192
|
+
const refOptions = [
|
|
193
|
+
`columns: ${stringArrayLiteral(fk.columns.map((col) => generatedName(col, options)))}`,
|
|
194
|
+
`references: ${stringArrayLiteral(fk.references.columns.map((col) => generatedName(col, options)))}`
|
|
195
|
+
];
|
|
196
|
+
if (fk.onDelete && fk.onDelete !== "NO ACTION") {
|
|
197
|
+
refOptions.push(`onDelete: ${singleQuoteEscape(fk.onDelete)}`);
|
|
198
|
+
}
|
|
199
|
+
if (fk.onUpdate && fk.onUpdate !== "NO ACTION") {
|
|
200
|
+
refOptions.push(`onUpdate: ${singleQuoteEscape(fk.onUpdate)}`);
|
|
201
|
+
}
|
|
202
|
+
return ` ref(${singleQuoteEscape(generatedName(fk.references.table, options))}, { ${refOptions.join(", ")} })`;
|
|
203
|
+
});
|
|
204
|
+
tableBlocks.push(
|
|
205
|
+
` ${quoteKey(generatedName(table.name, options))}: {
|
|
206
|
+
foreignKeys: [
|
|
207
|
+
${fkLines.join(",\n")}
|
|
208
|
+
]
|
|
209
|
+
}`
|
|
210
|
+
);
|
|
211
|
+
}
|
|
212
|
+
if (tableBlocks.length === 0) return void 0;
|
|
213
|
+
return `{
|
|
214
|
+
${tableBlocks.join(",\n\n")}
|
|
215
|
+
}`;
|
|
216
|
+
}
|
|
175
217
|
function generateSchemaFile(model, options = {}) {
|
|
176
218
|
const lines = [];
|
|
177
219
|
lines.push("/**");
|
|
@@ -209,7 +251,12 @@ function generateSchemaFile(model, options = {}) {
|
|
|
209
251
|
const tables = Array.from(model.tables.values());
|
|
210
252
|
const tableLines = tables.map((table) => generateTableCode(table, options));
|
|
211
253
|
lines.push(tableLines.join(",\n\n"));
|
|
212
|
-
|
|
254
|
+
const compositeConstraints = generateCompositeConstraintCode(model, options);
|
|
255
|
+
if (compositeConstraints) {
|
|
256
|
+
lines.push(`}, ${compositeConstraints});`);
|
|
257
|
+
} else {
|
|
258
|
+
lines.push("});");
|
|
259
|
+
}
|
|
213
260
|
lines.push("");
|
|
214
261
|
lines.push("export default dbSchema;");
|
|
215
262
|
lines.push("");
|
|
@@ -243,4 +290,4 @@ export {
|
|
|
243
290
|
redactDbUrl,
|
|
244
291
|
generateSchemaFile
|
|
245
292
|
};
|
|
246
|
-
//# sourceMappingURL=chunk-
|
|
293
|
+
//# sourceMappingURL=chunk-XOYWYHZF.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/utils/db-utils.ts","../src/generators/schema-codegen.ts"],"sourcesContent":["/**\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","/**\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 type { ModelIR, TableIR } from '@dbsp/core';\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/** Warnings from introspection to include as comments */\n\treadonly warnings?: readonly string[];\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\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 * 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 = `'${column.type}'`;\n\t\tif (options.includeDbTypeComments && column.originalDbType) {\n\t\t\tcode += ` /* from: ${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: '${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: ${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: ${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\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: fk.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\n/**\n * Generate a TypeScript schema file from ModelIR.\n *\n * @param model - The ModelIR (or IntrospectedModelIR) to generate from\n * @param options - Code generation options\n * @returns TypeScript source code for a schema file\n */\nexport function generateSchemaFile(\n\tmodel: ModelIR,\n\toptions: SchemaCodegenOptions = {},\n): string {\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: ${redactedUrl}`);\n\t}\n\tif (options.introspectedAt) {\n\t\tlines.push(` * Generated: ${options.introspectedAt.toISOString()}`);\n\t}\n\tlines.push(' *');\n\tif (options.warnings && options.warnings.length > 0) {\n\t\tlines.push(' * ⚠️ Warnings:');\n\t\tfor (const warning of options.warnings) {\n\t\t\tlines.push(` * - ${warning}`);\n\t\t}\n\t}\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 - ARCH-005: Use schema() + ref() instead of defineSchema()\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 - ARCH-005: Use schema() instead of defineSchema()\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\tlines.push('});');\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 lines.join('\\n');\n}\n"],"mappings":"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","names":["code"]}
|
|
1
|
+
{"version":3,"sources":["../src/utils/db-utils.ts","../src/generators/schema-codegen.ts"],"sourcesContent":["/**\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","/**\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 type { ModelIR, TableIR } from '@dbsp/core';\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/** Warnings from introspection to include as comments */\n\treadonly warnings?: readonly string[];\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\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 * 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 = `'${column.type}'`;\n\t\tif (options.includeDbTypeComments && column.originalDbType) {\n\t\t\tcode += ` /* from: ${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: '${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: ${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: ${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\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: fk.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\nfunction compositeForeignKeys(table: TableIR): TableIR['foreignKeys'] {\n\treturn table.foreignKeys.filter(\n\t\t(fk) => fk.columns.length > 1 || fk.references.columns.length > 1,\n\t);\n}\n\nfunction generateCompositeConstraintCode(\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 = compositeForeignKeys(table);\n\t\tif (fks.length === 0) continue;\n\t\tconst fkLines = fks.map((fk) => {\n\t\t\tconst refOptions = [\n\t\t\t\t`columns: ${stringArrayLiteral(fk.columns.map((col) => generatedName(col, options)))}`,\n\t\t\t\t`references: ${stringArrayLiteral(fk.references.columns.map((col) => generatedName(col, options)))}`,\n\t\t\t];\n\t\t\tif (fk.onDelete && fk.onDelete !== 'NO ACTION') {\n\t\t\t\trefOptions.push(`onDelete: ${singleQuoteEscape(fk.onDelete)}`);\n\t\t\t}\n\t\t\tif (fk.onUpdate && fk.onUpdate !== 'NO ACTION') {\n\t\t\t\trefOptions.push(`onUpdate: ${singleQuoteEscape(fk.onUpdate)}`);\n\t\t\t}\n\t\t\treturn `\\t\\t\\tref(${singleQuoteEscape(generatedName(fk.references.table, options))}, { ${refOptions.join(', ')} })`;\n\t\t});\n\t\ttableBlocks.push(\n\t\t\t`\\t${quoteKey(generatedName(table.name, options))}: {\\n\\t\\tforeignKeys: [\\n${fkLines.join(',\\n')}\\n\\t\\t]\\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.\n *\n * @param model - The ModelIR (or IntrospectedModelIR) to generate from\n * @param options - Code generation options\n * @returns TypeScript source code for a schema file\n */\nexport function generateSchemaFile(\n\tmodel: ModelIR,\n\toptions: SchemaCodegenOptions = {},\n): string {\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: ${redactedUrl}`);\n\t}\n\tif (options.introspectedAt) {\n\t\tlines.push(` * Generated: ${options.introspectedAt.toISOString()}`);\n\t}\n\tlines.push(' *');\n\tif (options.warnings && options.warnings.length > 0) {\n\t\tlines.push(' * ⚠️ Warnings:');\n\t\tfor (const warning of options.warnings) {\n\t\t\tlines.push(` * - ${warning}`);\n\t\t}\n\t}\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 - ARCH-005: Use schema() + ref() instead of defineSchema()\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 - ARCH-005: Use schema() instead of defineSchema()\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 compositeConstraints = generateCompositeConstraintCode(model, options);\n\tif (compositeConstraints) {\n\t\tlines.push(`}, ${compositeConstraints});`);\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 lines.join('\\n');\n}\n"],"mappings":"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","names":["code"]}
|
package/dist/index.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dbsp/cli",
|
|
3
|
-
"version": "1.0
|
|
3
|
+
"version": "1.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": "^14.0.3",
|
|
61
61
|
"ink": "^7.0.1",
|
|
62
62
|
"react": "^19.2.5",
|
|
63
|
-
"@dbsp/
|
|
64
|
-
"@dbsp/
|
|
65
|
-
"@dbsp/nql": "1.
|
|
66
|
-
"@dbsp/
|
|
63
|
+
"@dbsp/core": "1.5.0",
|
|
64
|
+
"@dbsp/types": "1.5.0",
|
|
65
|
+
"@dbsp/nql": "1.6.0",
|
|
66
|
+
"@dbsp/adapter-pgsql": "1.6.0"
|
|
67
67
|
},
|
|
68
68
|
"peerDependencies": {
|
|
69
69
|
"pg": "^8.16.0",
|