@dbsp/cli 1.0.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.
- package/LICENSE +21 -0
- package/README.md +64 -0
- package/dist/chunk-AQC34IO5.js +107 -0
- package/dist/chunk-AQC34IO5.js.map +1 -0
- package/dist/chunk-U5DSGBS2.js +123 -0
- package/dist/chunk-U5DSGBS2.js.map +1 -0
- package/dist/chunk-UZEMCTNH.js +243 -0
- package/dist/chunk-UZEMCTNH.js.map +1 -0
- package/dist/chunk-ZSGVJFWG.js +2304 -0
- package/dist/chunk-ZSGVJFWG.js.map +1 -0
- package/dist/generators/schema-codegen.d.ts +39 -0
- package/dist/generators/schema-codegen.js +7 -0
- package/dist/generators/schema-codegen.js.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1138 -0
- package/dist/index.js.map +1 -0
- package/dist/repl/batch.d.ts +343 -0
- package/dist/repl/batch.js +407 -0
- package/dist/repl/batch.js.map +1 -0
- package/dist/repl-4OFERLKZ.js +1454 -0
- package/dist/repl-4OFERLKZ.js.map +1 -0
- package/dist/utils/schema-loader.d.ts +47 -0
- package/dist/utils/schema-loader.js +15 -0
- package/dist/utils/schema-loader.js.map +1 -0
- package/package.json +94 -0
|
@@ -0,0 +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: Was for legacy defineSchema() format\n * - kysely: Was for legacy defineSchema() format\n */\n\nimport { mkdirSync, writeFileSync } from 'node:fs';\nimport { dirname, resolve } from 'node:path';\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// 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 > dialect default > 'snake'\n\t\t\t\t\t\tconst casing = options.casing ?? 'snake';\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 dbCasing =\n\t\t\t\t\t\t\tcasing === 'snake'\n\t\t\t\t\t\t\t\t? ('snake_case' as const)\n\t\t\t\t\t\t\t\t: ('preserve' as const);\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: ${casing}`);\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\n\t\t\t\t\tcase 'manifest':\n\t\t\t\t\tcase 'kysely':\n\t\t\t\t\t\t// EH-10: Throw instead of process.exit(1) inside try ā outer catch handles exit\n\t\t\t\t\t\tthrow new Error(\n\t\t\t\t\t\t\t`Target '${target}' has been removed in ARCH-005. ` +\n\t\t\t\t\t\t\t\t`These generators required the legacy defineSchema() format. ` +\n\t\t\t\t\t\t\t\t`Use 'ddl' target for SQL generation.`,\n\t\t\t\t\t\t);\n\n\t\t\t\t\tdefault:\n\t\t\t\t\t\t// EH-10: Throw instead of process.exit(1) inside try ā outer catch handles exit\n\t\t\t\t\t\tthrow new Error(\n\t\t\t\t\t\t\t`Unknown target: ${target}. Available targets: ddl`,\n\t\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","/**\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\tgenerateSchemaFile,\n\ttype SchemaCodegenOptions,\n} from '../generators/schema-codegen.js';\nimport { createDbConnection, redactDbUrl } from '../utils/db-utils.js';\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\t\t\t\t\tif (model.warnings?.length) {\n\t\t\t\t\t\tfor (const w of model.warnings) {\n\t\t\t\t\t\t\tconsole.log(` ā ļø ${w}`);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tconsole.log('');\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\twarnings: model.warnings,\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 schemaCode = generateSchemaFile(model, codegenOptions);\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, schemaCode, '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\tcompareSchemata,\n\tensureMigrationsTable,\n\tgenerateMigrationFile,\n\tgenerateMigrationSQL,\n\tgetAppliedMigrations,\n\tgetNextSchemaVersion,\n\tintrospect,\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// 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 dbModel = await introspect(pool, {\n\t\t\t\t\t\t...(options.schemaName ? { schema: options.schemaName } : {}),\n\t\t\t\t\t});\n\n\t\t\t\t\tconst diff = compareSchemata(schemaModel, dbModel);\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(\n\t\t\t\t\t\t\t(s) => s.length > 0 && !s.startsWith('-- '),\n\t\t\t\t\t\t);\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 = isDestructiveDown(parsed.downStatements);\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(\n\t\t\t\t\t\t\t\t(s) => s.length > 0 && !s.startsWith('-- '),\n\t\t\t\t\t\t\t);\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: Destructive DOWN check\n\t\t\t\t\t\t\tif (isDestructiveDown(parsed.downStatements) && !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 destructive DOWN operations\\n` +\n\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);\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 {\n\tcompareSchemata,\n\tgenerateDDL,\n\tgenerateMigrationSQL,\n\tintrospect,\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});\n\n\t\t\t\t\t\t// SEC-7: Escape MIGRATIONS_TABLE before interpolating into RegExp\n\t\t\t\t\t\tconst escapedTable = MIGRATIONS_TABLE.replace(\n\t\t\t\t\t\t\t/[.*+?^${}()|[\\]\\\\]/g,\n\t\t\t\t\t\t\t'\\\\$&',\n\t\t\t\t\t\t);\n\t\t\t\t\t\t// CC-11: Token-based check ā match DROP TABLE ... \"tableName\" (no greedy .*\n\t\t\t\t\t\t// across statement boundaries). The pattern anchors on the quoted table name\n\t\t\t\t\t\t// appearing anywhere in the statement, which is safe for single-statement\n\t\t\t\t\t\t// inputs (generateDDL returns one statement per array entry).\n\t\t\t\t\t\tconst migrationsPattern = new RegExp(\n\t\t\t\t\t\t\t`DROP\\\\s+TABLE(?:\\\\s+IF\\\\s+EXISTS)?(?:\\\\s+\"[^\"]*\"\\\\s*\\\\.)?\\\\s*\"${escapedTable}\"`,\n\t\t\t\t\t\t\t'i',\n\t\t\t\t\t\t);\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\t// CC-1: --drop --json must emit JSON to stdout on success\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((s) => {\n\t\t\t\t\t\t\t\t\t// M6: handle CASCADE between last quoted identifier and semicolon\n\t\t\t\t\t\t\t\t\t// e.g. DROP TABLE IF EXISTS \"public\".\"users\" CASCADE;\n\t\t\t\t\t\t\t\t\tconst m = s.match(/\"([^\"]+)\"\\s*(?:CASCADE\\s*)?;?\\s*$/i);\n\t\t\t\t\t\t\t\t\treturn m ? m[1] : s;\n\t\t\t\t\t\t\t\t})\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: introspect ā diff ā generate migration SQL (additive only)\n\t\t\t\t\t\tconst dbModel = await introspect(pool, {\n\t\t\t\t\t\t\t...(options.schemaName ? { schema: options.schemaName } : {}),\n\t\t\t\t\t\t});\n\n\t\t\t\t\t\tconst diff = compareSchemata(schemaModel, dbModel);\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.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 */\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","/**\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 { Command } from 'commander';\nimport { config } from '../config.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// CLI-CASING: Map --casing flag to dbCasing (intuitive: describes DB columns)\n\t\t\tconst dbCasing =\n\t\t\t\toptions.casing === 'snake'\n\t\t\t\t\t? ('snake_case' as const)\n\t\t\t\t\t: options.casing === 'camel'\n\t\t\t\t\t\t? ('camelCase' as const)\n\t\t\t\t\t\t: options.casing === 'none'\n\t\t\t\t\t\t\t? ('preserve' as const)\n\t\t\t\t\t\t\t: undefined;\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","/**\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 { compareSchemata, introspect } 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\t// Introspect database ā ModelIR\n\t\t\t\t\tconst dbModel = await introspect(pool, {\n\t\t\t\t\t\t...(options.schemaName ? { schema: options.schemaName } : {}),\n\t\t\t\t\t});\n\n\t\t\t\t\t// Compare using the comparison engine\n\t\t\t\t\tconst diff = compareSchemata(schemaModel, dbModel);\n\n\t\t\t\t\t// Convert to verify result\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// 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 * @deprecated Use DbTableInfo from introspection instead.\n * Kept for backward compatibility with existing tests.\n */\nexport interface DbTableInfo {\n\tname: string;\n\tcolumns: DbColumnInfo[];\n}\n\n/**\n * @deprecated Use ColumnIR from introspection instead.\n */\nexport interface DbColumnInfo {\n\tname: string;\n\tdataType: string;\n\tisNullable: boolean;\n\tisPrimaryKey?: boolean;\n\thasDefault?: boolean;\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\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","introspect","resolve","mkdirSync","dirname","writeFileSync","Command","mkdirSync","writeFileSync","resolve","Command","compareSchemata","generateMigrationSQL","introspect","Command","Command","introspect","compareSchemata","generateMigrationSQL","readFileSync","Command","Command","readFileSync","compareSchemata","introspect","Command","Command","introspect","compareSchemata","Command"]}
|
|
@@ -0,0 +1,343 @@
|
|
|
1
|
+
import { IntentSummary, ModelIR, AssertionSummary } from '@dbsp/core';
|
|
2
|
+
import { LoadedSchema } from '@dbsp/types';
|
|
3
|
+
import pg from 'pg';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
import type React from 'react';
|
|
7
|
+
* DX-030: REPL Types
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Query mode - natural syntax or SQL
|
|
12
|
+
*/
|
|
13
|
+
type QueryMode = 'natural' | 'sql';
|
|
14
|
+
/**
|
|
15
|
+
* Column aliasing mode for included relations (CLI-010)
|
|
16
|
+
* - 'always': Alias all columns from included tables
|
|
17
|
+
* - 'onCollision': Only alias columns that exist in multiple tables
|
|
18
|
+
*/
|
|
19
|
+
type AliasingMode = 'always' | 'onCollision';
|
|
20
|
+
/**
|
|
21
|
+
* Include strategy for relations (CLI-011)
|
|
22
|
+
* - 'auto': Let the planner choose based on relation type (DEFAULT)
|
|
23
|
+
* - 'join': Use JOIN (single query, database optimizes)
|
|
24
|
+
* - 'subquery': Use subquery queries (N+1 style with batching)
|
|
25
|
+
* - 'cte': Use CTE to materialize base query before joining
|
|
26
|
+
* - 'lateral': Use LATERAL JOIN (PostgreSQL only) - limit N children per parent
|
|
27
|
+
* - 'json_agg': Use JSON aggregation (PostgreSQL, MySQL 8+) - no row duplication
|
|
28
|
+
*/
|
|
29
|
+
type IncludeStrategyMode = 'auto' | 'join' | 'subquery' | 'cte' | 'lateral' | 'json_agg';
|
|
30
|
+
/**
|
|
31
|
+
* SQL dialect for the REPL (CLI-011)
|
|
32
|
+
* Determines SQL syntax and available features.
|
|
33
|
+
*/
|
|
34
|
+
type DialectMode = 'postgresql' | 'mysql' | 'sqlite' | 'mssql' | 'duckdb';
|
|
35
|
+
/**
|
|
36
|
+
* Subquery include query for SEPARATE strategy relations
|
|
37
|
+
*/
|
|
38
|
+
interface SeparateQueryResult {
|
|
39
|
+
relation: string;
|
|
40
|
+
sql: string;
|
|
41
|
+
params: readonly unknown[];
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Query execution result
|
|
45
|
+
*/
|
|
46
|
+
interface QueryResult {
|
|
47
|
+
sql: string;
|
|
48
|
+
params: readonly unknown[];
|
|
49
|
+
/** Additional queries for SEPARATE strategy relations (manyToMany, hasMany) */
|
|
50
|
+
separateQueries?: SeparateQueryResult[];
|
|
51
|
+
plan?: {
|
|
52
|
+
strategy: string;
|
|
53
|
+
rootTable: string;
|
|
54
|
+
tables: string[];
|
|
55
|
+
decisions: Array<{
|
|
56
|
+
type: string;
|
|
57
|
+
context: string;
|
|
58
|
+
choice: string;
|
|
59
|
+
reasoning: string;
|
|
60
|
+
alternatives?: string[];
|
|
61
|
+
foreignKey?: string | string[];
|
|
62
|
+
relationType?: string;
|
|
63
|
+
intentPath?: string;
|
|
64
|
+
relationPath?: string;
|
|
65
|
+
decisionId?: string;
|
|
66
|
+
}>;
|
|
67
|
+
warnings: Array<{
|
|
68
|
+
message: string;
|
|
69
|
+
suggestion?: string;
|
|
70
|
+
code?: string;
|
|
71
|
+
relatedDecision?: string;
|
|
72
|
+
}>;
|
|
73
|
+
cteCount: number;
|
|
74
|
+
planningTimeMs: number;
|
|
75
|
+
ctes?: Array<{
|
|
76
|
+
name: string;
|
|
77
|
+
purpose: string;
|
|
78
|
+
recursive?: boolean;
|
|
79
|
+
referencedBy?: string[];
|
|
80
|
+
}>;
|
|
81
|
+
metadata?: {
|
|
82
|
+
relationsAnalyzed: number;
|
|
83
|
+
isAmbiguous: boolean;
|
|
84
|
+
ambiguousOptions?: string[];
|
|
85
|
+
};
|
|
86
|
+
};
|
|
87
|
+
error?: string;
|
|
88
|
+
/** CLI-NQL: Parsed query AST for .parse mode */
|
|
89
|
+
parsedQuery?: unknown;
|
|
90
|
+
/** Intent summary for batch assertions + introspection */
|
|
91
|
+
intent?: IntentSummary;
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* CLI-020: Database execution result
|
|
95
|
+
*/
|
|
96
|
+
interface ExecutionResult$1 {
|
|
97
|
+
/** Result rows from database */
|
|
98
|
+
rows: Record<string, unknown>[];
|
|
99
|
+
/** Column names in order */
|
|
100
|
+
columns: string[];
|
|
101
|
+
/** Row count */
|
|
102
|
+
rowCount: number;
|
|
103
|
+
/** Execution time in milliseconds */
|
|
104
|
+
executionTimeMs: number;
|
|
105
|
+
/** Error message if execution failed */
|
|
106
|
+
error?: string;
|
|
107
|
+
/** Was result truncated? */
|
|
108
|
+
truncated?: boolean;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Engine Event Types, Configuration, and State Interface
|
|
113
|
+
*
|
|
114
|
+
* Defines the event-driven interface between ReplEngine (pure business logic)
|
|
115
|
+
* and any UI consumer (Ink TUI, batch mode, tests, future GUI).
|
|
116
|
+
*/
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Output layout controls what appears inline in the conversation flow.
|
|
120
|
+
* Detail inspection uses the anchored panel (.sql, .plan, .results, etc.).
|
|
121
|
+
*/
|
|
122
|
+
type OutputLayout = 'compact' | 'results' | 'sql' | 'full';
|
|
123
|
+
/**
|
|
124
|
+
* Plan verbosity controls detail level within plan output.
|
|
125
|
+
* - compact: one-liner summary
|
|
126
|
+
* - normal: decisions + warnings (default, unchanged behavior)
|
|
127
|
+
* - verbose: all fields including alternatives, FK, CTE details, metadata
|
|
128
|
+
*/
|
|
129
|
+
type PlanVerbosity = 'compact' | 'normal' | 'verbose';
|
|
130
|
+
/**
|
|
131
|
+
* Panel view types for the anchored inspection panel below input.
|
|
132
|
+
*/
|
|
133
|
+
type PanelView = 'sql' | 'plan' | 'results' | 'params' | 'dump';
|
|
134
|
+
/**
|
|
135
|
+
* Events emitted by the engine for UI consumption.
|
|
136
|
+
*/
|
|
137
|
+
type EngineEvent = {
|
|
138
|
+
type: 'query-result';
|
|
139
|
+
result: QueryResult;
|
|
140
|
+
} | {
|
|
141
|
+
type: 'execution-result';
|
|
142
|
+
result: ExecutionResult$1;
|
|
143
|
+
query: QueryResult;
|
|
144
|
+
} | {
|
|
145
|
+
type: 'info';
|
|
146
|
+
message: string;
|
|
147
|
+
} | {
|
|
148
|
+
type: 'error';
|
|
149
|
+
message: string;
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* Emitted by init() when the database connection fails.
|
|
153
|
+
* Callers can match on `type === 'init-error'` without string inspection.
|
|
154
|
+
*/
|
|
155
|
+
| {
|
|
156
|
+
type: 'init-error';
|
|
157
|
+
message: string;
|
|
158
|
+
} | {
|
|
159
|
+
type: 'clear';
|
|
160
|
+
} | {
|
|
161
|
+
type: 'exit';
|
|
162
|
+
} | {
|
|
163
|
+
type: 'state-change';
|
|
164
|
+
state: EngineState;
|
|
165
|
+
} | {
|
|
166
|
+
type: 'show-history';
|
|
167
|
+
} | {
|
|
168
|
+
type: 'show-panel';
|
|
169
|
+
view: PanelView;
|
|
170
|
+
} | {
|
|
171
|
+
type: 'close-panel';
|
|
172
|
+
} | {
|
|
173
|
+
type: 'layout-change';
|
|
174
|
+
layout: OutputLayout;
|
|
175
|
+
};
|
|
176
|
+
/**
|
|
177
|
+
* Engine state ā mirrors the business-relevant state from the REPL.
|
|
178
|
+
* UI-only state (showHelp, inputKey, completions) stays in the component.
|
|
179
|
+
*/
|
|
180
|
+
interface EngineState {
|
|
181
|
+
mode: QueryMode;
|
|
182
|
+
execMode: boolean;
|
|
183
|
+
connected: boolean;
|
|
184
|
+
explainMode: boolean;
|
|
185
|
+
parseMode: boolean;
|
|
186
|
+
aliasingMode: AliasingMode;
|
|
187
|
+
includeStrategy: IncludeStrategyMode;
|
|
188
|
+
dialect: DialectMode;
|
|
189
|
+
schemaName?: string;
|
|
190
|
+
dbCasing?: 'snake_case' | 'camelCase' | 'preserve';
|
|
191
|
+
outputMode: 'json' | 'table' | 'csv';
|
|
192
|
+
outputLayout: OutputLayout;
|
|
193
|
+
planVerbosity: PlanVerbosity;
|
|
194
|
+
/** E16c: Whether a transaction is currently active */
|
|
195
|
+
inTransaction: boolean;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* CLI-020: Database Connection Manager
|
|
200
|
+
*
|
|
201
|
+
* Manages PostgreSQL connection for REPL execution mode.
|
|
202
|
+
*/
|
|
203
|
+
|
|
204
|
+
interface DbConnection {
|
|
205
|
+
/** Execute a raw SQL query */
|
|
206
|
+
executeRaw(query: string, params?: readonly unknown[]): Promise<ExecutionResult>;
|
|
207
|
+
/** Test the connection */
|
|
208
|
+
ping(): Promise<boolean>;
|
|
209
|
+
/** Close the connection */
|
|
210
|
+
close(): Promise<void>;
|
|
211
|
+
/** Get the underlying pg Pool */
|
|
212
|
+
getPool(): pg.Pool;
|
|
213
|
+
/** Start a transaction (acquires dedicated client, sends BEGIN) */
|
|
214
|
+
beginTransaction(): Promise<void>;
|
|
215
|
+
/** Commit the active transaction and release the client */
|
|
216
|
+
commitTransaction(): Promise<void>;
|
|
217
|
+
/** Rollback the active transaction and release the client */
|
|
218
|
+
rollbackTransaction(): Promise<void>;
|
|
219
|
+
/** Whether a transaction is currently active */
|
|
220
|
+
readonly inTransaction: boolean;
|
|
221
|
+
}
|
|
222
|
+
interface ExecutionResult {
|
|
223
|
+
rows: Record<string, unknown>[];
|
|
224
|
+
columns: string[];
|
|
225
|
+
rowCount: number;
|
|
226
|
+
executionTimeMs: number;
|
|
227
|
+
error?: string;
|
|
228
|
+
truncated?: boolean;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* @module dot-commands
|
|
233
|
+
* Interactive REPL dot-command processing (`.tables`, `.schema`, `.exec`, etc.).
|
|
234
|
+
*
|
|
235
|
+
* Extracted from batch.ts for SRP (Phase 5.5).
|
|
236
|
+
*/
|
|
237
|
+
|
|
238
|
+
/** Shared state interface used by dot-commands and batch mode. */
|
|
239
|
+
interface BatchState {
|
|
240
|
+
mode: QueryMode;
|
|
241
|
+
execEnabled: boolean;
|
|
242
|
+
schemaName: string | undefined;
|
|
243
|
+
dbConnection: DbConnection | undefined;
|
|
244
|
+
/** CLI-MUT: Show EXPLAIN output with query results */
|
|
245
|
+
explainMode: boolean;
|
|
246
|
+
/** CLI-NQL: Show parse tree (AST) for queries */
|
|
247
|
+
parseMode: boolean;
|
|
248
|
+
/** NQL v2: ModelIR built from schema for NQL compilation */
|
|
249
|
+
model: ModelIR | undefined;
|
|
250
|
+
/** NQL v2.1: Output display format (json|table|csv) */
|
|
251
|
+
outputMode: 'json' | 'table' | 'csv';
|
|
252
|
+
/** DB column casing (intuitive). */
|
|
253
|
+
dbCasing?: 'snake_case' | 'camelCase' | 'preserve';
|
|
254
|
+
/** E16c: Whether a transaction is currently active */
|
|
255
|
+
inTransaction: boolean;
|
|
256
|
+
}
|
|
257
|
+
type DotCommandResult = {
|
|
258
|
+
output: string;
|
|
259
|
+
stateChange?: Partial<BatchState>;
|
|
260
|
+
success?: boolean;
|
|
261
|
+
error?: string;
|
|
262
|
+
};
|
|
263
|
+
/**
|
|
264
|
+
* Process a dot command (async to support .import)
|
|
265
|
+
* ARCH-005: Uses LoadedSchema instead of ResolvedSchema
|
|
266
|
+
* @internal - Exported for testing
|
|
267
|
+
*/
|
|
268
|
+
declare function processDotCommand(input: string, schema: LoadedSchema, state: BatchState): Promise<DotCommandResult>;
|
|
269
|
+
|
|
270
|
+
/**
|
|
271
|
+
* CLI-022: Batch Mode for REPL
|
|
272
|
+
*
|
|
273
|
+
* Executes queries from files or command line without interactive UI.
|
|
274
|
+
* Routes all input through ReplEngine.submit() for a single processing path.
|
|
275
|
+
*/
|
|
276
|
+
|
|
277
|
+
interface BatchModeOptions {
|
|
278
|
+
queries: string[];
|
|
279
|
+
schema: LoadedSchema;
|
|
280
|
+
schemaPath: string;
|
|
281
|
+
format: 'text' | 'json';
|
|
282
|
+
databaseUrl?: string;
|
|
283
|
+
/** DEMO-E2E: Path to assertion file (.assert.dbsp) */
|
|
284
|
+
assertFile?: string;
|
|
285
|
+
/** DB column casing (intuitive: describes what the DB looks like). */
|
|
286
|
+
dbCasing?: 'snake_case' | 'camelCase' | 'preserve';
|
|
287
|
+
}
|
|
288
|
+
interface BatchResult {
|
|
289
|
+
query: string;
|
|
290
|
+
/** Compile-only success of the query (NQL compilation passed).
|
|
291
|
+
*
|
|
292
|
+
* Does NOT reflect DB execution outcome ā see `dbSuccess` for that.
|
|
293
|
+
* Combine with `dbSuccess` via `@dbsp/core`'s `isOverallSuccess()` for
|
|
294
|
+
* end-to-end status; exit-code logic in `runBatchMode` does exactly that. */
|
|
295
|
+
success: boolean;
|
|
296
|
+
/** DB execution success only (compile-only mode leaves this undefined).
|
|
297
|
+
* When present: `true` = DB query executed without error. */
|
|
298
|
+
dbSuccess?: boolean;
|
|
299
|
+
output?: string;
|
|
300
|
+
sql?: string;
|
|
301
|
+
params?: readonly unknown[];
|
|
302
|
+
error?: string;
|
|
303
|
+
type: 'command' | 'query' | 'mutation';
|
|
304
|
+
/** Row count from DB execution (for db.* assertions) */
|
|
305
|
+
rowCount?: number;
|
|
306
|
+
/** Column names from DB result (for db.column.exists) */
|
|
307
|
+
columns?: string[];
|
|
308
|
+
/** Row data from DB result (for db.value.equals) */
|
|
309
|
+
rows?: unknown[];
|
|
310
|
+
/** Intent summary for intent.* assertions */
|
|
311
|
+
intent?: IntentSummary;
|
|
312
|
+
}
|
|
313
|
+
/**
|
|
314
|
+
* Result of executing a batch of queries (without side effects).
|
|
315
|
+
* Used by tests to programmatically run examples.
|
|
316
|
+
*/
|
|
317
|
+
interface BatchExecutionResult {
|
|
318
|
+
results: BatchResult[];
|
|
319
|
+
assertionSummary?: AssertionSummary | undefined;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
/**
|
|
323
|
+
* Map collected engine events to a BatchResult for a single query.
|
|
324
|
+
*
|
|
325
|
+
* Event patterns:
|
|
326
|
+
* NQL success: query-result ā [execution-result]
|
|
327
|
+
* NQL error: query-result (with .error)
|
|
328
|
+
* Raw SQL: query-result ā [execution-result]
|
|
329
|
+
* Dot command: info | error [+ state-change]
|
|
330
|
+
*
|
|
331
|
+
* @internal Exported for unit testing ā not part of the public API.
|
|
332
|
+
*/
|
|
333
|
+
declare function mapEventsToBatchResult(query: string, events: EngineEvent[], outputMode: 'json' | 'table' | 'csv'): BatchResult;
|
|
334
|
+
/**
|
|
335
|
+
* Core batch execution logic ā runs queries and optional assertions,
|
|
336
|
+
* returning structured results without printing or calling process.exit().
|
|
337
|
+
*
|
|
338
|
+
* All input is routed through ReplEngine.submit() for a single processing path.
|
|
339
|
+
*/
|
|
340
|
+
declare function executeBatch(options: BatchModeOptions): Promise<BatchExecutionResult>;
|
|
341
|
+
declare function runBatchMode(options: BatchModeOptions): Promise<void>;
|
|
342
|
+
|
|
343
|
+
export { type BatchExecutionResult, type BatchModeOptions, type BatchResult, type BatchState, executeBatch, mapEventsToBatchResult, processDotCommand, runBatchMode };
|