@dbsp/cli 1.0.1 → 1.0.3

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.
@@ -1,8 +1,10 @@
1
1
  import {
2
+ InvalidIdentifierError,
2
3
  TABLE_OPTIONS,
3
4
  config,
4
- isValidTableOption
5
- } from "./chunk-U5DSGBS2.js";
5
+ isValidTableOption,
6
+ validateIdentifier
7
+ } from "./chunk-AKVQI5ZD.js";
6
8
 
7
9
  // src/repl/db-connection.ts
8
10
  import pg from "pg";
@@ -134,55 +136,6 @@ function getDatabaseName(connectionString) {
134
136
  // src/repl/dot-commands.ts
135
137
  import { existsSync, readFileSync, writeFileSync } from "fs";
136
138
 
137
- // src/utils/identifier-validation.ts
138
- var InvalidIdentifierError = class extends Error {
139
- constructor(value, identifierType, reason) {
140
- super(
141
- `Invalid ${identifierType} identifier ${JSON.stringify(value)}: ${reason}`
142
- );
143
- this.value = value;
144
- this.identifierType = identifierType;
145
- this.name = "InvalidIdentifierError";
146
- }
147
- value;
148
- identifierType;
149
- };
150
- var VALID_IDENTIFIER_RE = /^[a-zA-Z_][a-zA-Z0-9_$]*$/;
151
- function validateIdentifier(value, identifierType) {
152
- if (!value || value.length === 0) {
153
- throw new InvalidIdentifierError(value, identifierType, "cannot be empty");
154
- }
155
- if (/[\u0000-\u001f\u007f]/u.test(value)) {
156
- throw new InvalidIdentifierError(
157
- value,
158
- identifierType,
159
- "contains control characters or NUL byte"
160
- );
161
- }
162
- const byteLen = Buffer.byteLength(value, "utf8");
163
- if (byteLen > 63) {
164
- throw new InvalidIdentifierError(
165
- value,
166
- identifierType,
167
- `exceeds maximum length of 63 bytes (got ${byteLen})`
168
- );
169
- }
170
- if (!VALID_IDENTIFIER_RE.test(value)) {
171
- if (/^[0-9]/.test(value)) {
172
- throw new InvalidIdentifierError(
173
- value,
174
- identifierType,
175
- "cannot start with a digit"
176
- );
177
- }
178
- throw new InvalidIdentifierError(
179
- value,
180
- identifierType,
181
- "contains invalid characters (only letters, digits, underscore, and $ allowed)"
182
- );
183
- }
184
- }
185
-
186
139
  // src/utils/path-containment.ts
187
140
  import { isAbsolute, relative, resolve, sep } from "path";
188
141
  var PathEscapeError = class extends Error {
@@ -2301,4 +2254,4 @@ export {
2301
2254
  processDotCommand,
2302
2255
  ReplEngine
2303
2256
  };
2304
- //# sourceMappingURL=chunk-ZSGVJFWG.js.map
2257
+ //# sourceMappingURL=chunk-3Y6N6G7L.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/repl/db-connection.ts","../src/repl/dot-commands.ts","../src/utils/path-containment.ts","../src/repl/csv.ts","../src/repl/completion.ts","../src/repl/mode-escape.ts","../src/repl/nql-executor.ts","../src/repl/engine/repl-engine.ts"],"sourcesContent":["/**\n * CLI-020: Database Connection Manager\n *\n * Manages PostgreSQL connection for REPL execution mode.\n */\n\nimport pg from 'pg';\n\nconst { Pool } = pg;\n\n/** Maximum rows to return from a query */\nconst MAX_ROWS = 100;\n\nexport interface DbConnection {\n\t/** Execute a raw SQL query */\n\texecuteRaw(\n\t\tquery: string,\n\t\tparams?: readonly unknown[],\n\t): Promise<ExecutionResult>;\n\t/** Test the connection */\n\tping(): Promise<boolean>;\n\t/** Close the connection */\n\tclose(): Promise<void>;\n\t/** Get the underlying pg Pool */\n\tgetPool(): pg.Pool;\n\t/** Start a transaction (acquires dedicated client, sends BEGIN) */\n\tbeginTransaction(): Promise<void>;\n\t/** Commit the active transaction and release the client */\n\tcommitTransaction(): Promise<void>;\n\t/** Rollback the active transaction and release the client */\n\trollbackTransaction(): Promise<void>;\n\t/** Whether a transaction is currently active */\n\treadonly inTransaction: boolean;\n}\n\nexport interface ExecutionResult {\n\trows: Record<string, unknown>[];\n\tcolumns: string[];\n\trowCount: number;\n\texecutionTimeMs: number;\n\terror?: string;\n\ttruncated?: boolean;\n}\n\n/**\n * Create a database connection from a PostgreSQL URL.\n *\n * @param connectionString - PostgreSQL connection URL (e.g., postgres://localhost/mydb)\n * @returns Database connection instance\n * @throws Error if connection fails\n */\nexport async function createDbConnection(\n\tconnectionString: string,\n): Promise<DbConnection> {\n\t// Validate connection string format\n\tif (\n\t\t!connectionString.startsWith('postgres://') &&\n\t\t!connectionString.startsWith('postgresql://')\n\t) {\n\t\tthrow new Error(\n\t\t\t`Invalid connection URL: must start with postgres:// or postgresql://`,\n\t\t);\n\t}\n\n\t// Create pg Pool\n\tconst pool = new Pool({\n\t\tconnectionString,\n\t\tmax: 1, // Single connection for REPL\n\t\tconnectionTimeoutMillis: 10000,\n\t\tidleTimeoutMillis: 30000,\n\t});\n\n\t// Test connection\n\ttry {\n\t\tawait pool.query('SELECT 1');\n\t} catch (error) {\n\t\tawait pool.end();\n\t\tconst message = error instanceof Error ? error.message : String(error);\n\t\tthrow new Error(`Failed to connect to database: ${message}`);\n\t}\n\n\t// Transaction state: dedicated client held for the duration of .begin → .commit/.rollback\n\tlet txClient: pg.PoolClient | null = null;\n\n\t/**\n\t * Execute COMMIT or ROLLBACK on the active transaction client, then release it.\n\t * Extracted to avoid structural clone between commitTransaction and rollbackTransaction (SC-7).\n\t */\n\tasync function runTransactionControl(\n\t\tsql: 'COMMIT' | 'ROLLBACK',\n\t): Promise<void> {\n\t\tif (!txClient) {\n\t\t\tthrow new Error('No active transaction. Use .begin first.');\n\t\t}\n\t\ttry {\n\t\t\tawait txClient.query(sql);\n\t\t} finally {\n\t\t\ttxClient.release();\n\t\t\ttxClient = null;\n\t\t}\n\t}\n\n\t/**\n\t * Execute a query on the active target (transaction client or pool).\n\t */\n\tasync function executeRaw(\n\t\tquery: string,\n\t\tparams: readonly unknown[] = [],\n\t): Promise<ExecutionResult> {\n\t\tconst startTime = performance.now();\n\t\tconst target = txClient ?? pool;\n\n\t\ttry {\n\t\t\tconst poolResult = await target.query(query, [...params]);\n\t\t\tconst endTime = performance.now();\n\t\t\tconst executionTimeMs = Math.round(endTime - startTime);\n\n\t\t\tconst rows = (poolResult.rows ?? []) as Record<string, unknown>[];\n\t\t\tconst columns = poolResult.fields?.map((f) => f.name) ?? [];\n\t\t\tconst rowCount = poolResult.rowCount ?? rows?.length ?? 0;\n\t\t\tconst truncated = rows.length > MAX_ROWS;\n\t\t\tconst limitedRows = truncated ? rows.slice(0, MAX_ROWS) : rows;\n\n\t\t\treturn {\n\t\t\t\trows: limitedRows,\n\t\t\t\tcolumns,\n\t\t\t\trowCount,\n\t\t\t\texecutionTimeMs,\n\t\t\t\ttruncated,\n\t\t\t};\n\t\t} catch (error) {\n\t\t\tconst endTime = performance.now();\n\t\t\tconst executionTimeMs = Math.round(endTime - startTime);\n\t\t\tconst message = error instanceof Error ? error.message : String(error);\n\n\t\t\treturn {\n\t\t\t\trows: [],\n\t\t\t\tcolumns: [],\n\t\t\t\trowCount: 0,\n\t\t\t\texecutionTimeMs,\n\t\t\t\terror: message,\n\t\t\t};\n\t\t}\n\t}\n\n\treturn {\n\t\texecuteRaw,\n\n\t\tasync ping(): Promise<boolean> {\n\t\t\ttry {\n\t\t\t\tawait pool.query('SELECT 1');\n\t\t\t\treturn true;\n\t\t\t} catch {\n\t\t\t\treturn false;\n\t\t\t}\n\t\t},\n\n\t\tasync close(): Promise<void> {\n\t\t\tif (txClient) {\n\t\t\t\ttry {\n\t\t\t\t\tawait txClient.query('ROLLBACK');\n\t\t\t\t} catch {\n\t\t\t\t\t// Best-effort rollback on close\n\t\t\t\t}\n\t\t\t\ttxClient.release();\n\t\t\t\ttxClient = null;\n\t\t\t}\n\t\t\tawait pool.end();\n\t\t},\n\n\t\tgetPool(): pg.Pool {\n\t\t\treturn pool;\n\t\t},\n\n\t\tasync beginTransaction(): Promise<void> {\n\t\t\tif (txClient) {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t'Transaction already active. Use .commit or .rollback first.',\n\t\t\t\t);\n\t\t\t}\n\t\t\ttxClient = await pool.connect();\n\t\t\ttry {\n\t\t\t\tawait txClient.query('BEGIN');\n\t\t\t} catch (err) {\n\t\t\t\t// M4: release client on BEGIN failure to avoid pool deadlock (max: 1)\n\t\t\t\ttxClient.release();\n\t\t\t\ttxClient = null;\n\t\t\t\tthrow err;\n\t\t\t}\n\t\t},\n\n\t\tasync commitTransaction(): Promise<void> {\n\t\t\treturn runTransactionControl('COMMIT');\n\t\t},\n\n\t\tasync rollbackTransaction(): Promise<void> {\n\t\t\treturn runTransactionControl('ROLLBACK');\n\t\t},\n\n\t\tget inTransaction(): boolean {\n\t\t\treturn txClient !== null;\n\t\t},\n\t};\n}\n\n/**\n * Extract database name from connection URL for display.\n */\nexport function getDatabaseName(connectionString: string): string {\n\ttry {\n\t\tconst url = new URL(connectionString);\n\t\t// Remove leading slash from pathname\n\t\treturn url.pathname.slice(1) || url.hostname;\n\t} catch {\n\t\treturn 'database';\n\t}\n}\n","/**\n * @module dot-commands\n * Interactive REPL dot-command processing (`.tables`, `.schema`, `.exec`, etc.).\n *\n * Extracted from batch.ts for SRP (Phase 5.5).\n */\n\nimport { existsSync, readFileSync, writeFileSync } from 'node:fs';\nimport type { ModelIR } from '@dbsp/core';\nimport {\n\tInvalidIdentifierError,\n\tvalidateIdentifier,\n} from '../utils/identifier-validation.js';\nimport {\n\tPathEscapeError,\n\tvalidatePathInCwd,\n} from '../utils/path-containment.js';\nimport type { LoadedSchema } from '../utils/schema-loader.js';\nimport { formatCsv, parseCsvFile } from './csv.js';\nimport type { DbConnection } from './db-connection.js';\nimport type { QueryMode } from './types.js';\n\n/** Shared state interface used by dot-commands and batch mode. */\nexport interface BatchState {\n\tmode: QueryMode;\n\texecEnabled: boolean;\n\tschemaName: string | undefined;\n\tdbConnection: DbConnection | undefined;\n\t/** CLI-MUT: Show EXPLAIN output with query results */\n\texplainMode: boolean;\n\t/** CLI-NQL: Show parse tree (AST) for queries */\n\tparseMode: boolean;\n\t/** NQL v2: ModelIR built from schema for NQL compilation */\n\tmodel: ModelIR | undefined;\n\t/** NQL v2.1: Output display format (json|table|csv) */\n\toutputMode: 'json' | 'table' | 'csv';\n\t/** DB column casing (intuitive). */\n\tdbCasing?: 'snake_case' | 'camelCase' | 'preserve';\n\t/** E16c: Whether a transaction is currently active */\n\tinTransaction: boolean;\n}\n\n// ============================================================================\n// Format Helpers\n// ============================================================================\n\n/**\n * Format table list as text\n * ARCH-005: Uses schema.model (ModelIR) instead of ResolvedSchema\n */\nfunction formatTables(schema: LoadedSchema): string {\n\tconst tables = schema.tableNames;\n\treturn `Tables (${tables.length}):\\n${tables.map((t) => ` - ${t}`).join('\\n')}`;\n}\n\n/**\n * Format table schema as text\n * ARCH-005: Uses schema.model (ModelIR) instead of ResolvedSchema\n */\nfunction formatTableSchema(schema: LoadedSchema, tableName: string): string {\n\tconst table = schema.model.tables.get(tableName);\n\tif (!table) {\n\t\treturn `❌ Table not found: ${tableName}`;\n\t}\n\n\tconst lines = [`Table: ${tableName}`, 'Columns:'];\n\tfor (const col of table.columns) {\n\t\tconst nullable = col.nullable ? '' : ' (NOT NULL)';\n\t\tlines.push(` - ${col.name}: ${col.type}${nullable}`);\n\t}\n\treturn lines.join('\\n');\n}\n\n/**\n * Get relation description\n * ARCH-005: RelationIR has 'type' and 'target' fields\n */\nfunction getRelationDescription(rel: { type: string; target: string }): string {\n\treturn `${rel.type} → ${rel.target}`;\n}\n\n/**\n * Format relations as text\n * ARCH-005: Uses schema.model (ModelIR) instead of ResolvedSchema\n */\nfunction formatRelations(schema: LoadedSchema, tableName?: string): string {\n\tconst relations = Array.from(schema.model.relations.entries());\n\n\tif (tableName) {\n\t\t// Filter relations involving this table\n\t\tconst tableRelations = relations.filter(\n\t\t\t([, rel]) => rel.target === tableName || rel.source === tableName,\n\t\t);\n\t\tif (tableRelations.length === 0) {\n\t\t\treturn `No relations found for table: ${tableName}`;\n\t\t}\n\t\tconst lines = [`Relations for ${tableName}:`];\n\t\tfor (const [name, rel] of tableRelations) {\n\t\t\tlines.push(` - ${name}: ${getRelationDescription(rel)}`);\n\t\t}\n\t\treturn lines.join('\\n');\n\t}\n\n\tconst lines = [`Relations (${relations.length}):`];\n\tfor (const [name, rel] of relations) {\n\t\tlines.push(` - ${name}: ${getRelationDescription(rel)}`);\n\t}\n\treturn lines.join('\\n');\n}\n\n/**\n * [SC-11] Shared helper for boolean-toggle dot-commands (.exec/.explain/.parse).\n *\n * Handles three argument forms:\n * - `\"on\"` → force true (for .exec: requires dbConnection)\n * - `\"off\"` → force false\n * - `\"\"` → toggle current value (for .exec: requires dbConnection)\n *\n * @param arg - Cleaned argument string (already NUL-stripped)\n * @param key - The BatchState boolean key to change\n * @param label - Human-readable name used in output (e.g. \"EXPLAIN mode\")\n * @param state - Current BatchState\n */\nfunction handleBooleanToggle(\n\targ: string,\n\tkey: 'execEnabled' | 'explainMode' | 'parseMode',\n\tlabel: string,\n\tstate: BatchState,\n): { output: string; stateChange?: Partial<BatchState> } {\n\t// execEnabled requires a live database connection\n\tconst requiresDb = key === 'execEnabled';\n\n\tif (arg === 'on') {\n\t\tif (requiresDb && !state.dbConnection) {\n\t\t\treturn { output: '❌ No database connection. Use --db option.' };\n\t\t}\n\t\treturn {\n\t\t\toutput: `✓ ${label}: ON`,\n\t\t\tstateChange: { [key]: true } as Partial<BatchState>,\n\t\t};\n\t}\n\tif (arg === 'off') {\n\t\treturn {\n\t\t\toutput: `✓ ${label}: OFF`,\n\t\t\tstateChange: { [key]: false } as Partial<BatchState>,\n\t\t};\n\t}\n\t// Toggle (no argument)\n\tif (requiresDb && !state.dbConnection) {\n\t\treturn { output: '❌ No database connection. Use --db option.' };\n\t}\n\tconst newValue = !state[key];\n\treturn {\n\t\toutput: `✓ ${label}: ${newValue ? 'ON' : 'OFF'}`,\n\t\tstateChange: { [key]: newValue } as Partial<BatchState>,\n\t};\n}\n\n// ============================================================================\n// Dot Command Handler Types\n// ============================================================================\n\ntype DotCommandResult = {\n\toutput: string;\n\tstateChange?: Partial<BatchState>;\n\tsuccess?: boolean;\n\terror?: string;\n};\n\ntype DotCommandHandler = (\n\targ: string,\n\tschema: LoadedSchema,\n\tstate: BatchState,\n) => Promise<DotCommandResult> | DotCommandResult;\n\n// ============================================================================\n// Transaction Helper\n// ============================================================================\n\nasync function runTransactionAction(\n\tstate: BatchState,\n\taction: 'begin' | 'commit' | 'rollback',\n): Promise<DotCommandResult> {\n\tif (!state.dbConnection) {\n\t\treturn { output: '❌ No database connection. Use --db option.' };\n\t}\n\tconst methodMap = {\n\t\tbegin: {\n\t\t\tmethod: 'beginTransaction' as const,\n\t\t\texpectInTx: false,\n\t\t\terrorMsg: 'Transaction already active. Use .commit or .rollback first.',\n\t\t\tokOutput: '✓ Transaction started (BEGIN)',\n\t\t\tfailPrefix: 'Failed to begin transaction',\n\t\t\tstateChange: { inTransaction: true },\n\t\t},\n\t\tcommit: {\n\t\t\tmethod: 'commitTransaction' as const,\n\t\t\texpectInTx: true,\n\t\t\terrorMsg: 'No active transaction. Use .begin first.',\n\t\t\tokOutput: '✓ Transaction committed (COMMIT)',\n\t\t\tfailPrefix: 'Commit failed',\n\t\t\tstateChange: { inTransaction: false },\n\t\t},\n\t\trollback: {\n\t\t\tmethod: 'rollbackTransaction' as const,\n\t\t\texpectInTx: true,\n\t\t\terrorMsg: 'No active transaction. Use .begin first.',\n\t\t\tokOutput: '✓ Transaction rolled back (ROLLBACK)',\n\t\t\tfailPrefix: 'Rollback failed',\n\t\t\tstateChange: { inTransaction: false },\n\t\t},\n\t};\n\tconst cfg = methodMap[action];\n\tif (state.dbConnection.inTransaction !== cfg.expectInTx) {\n\t\treturn { output: `❌ ${cfg.errorMsg}` };\n\t}\n\ttry {\n\t\tawait state.dbConnection[cfg.method]();\n\t\treturn { output: cfg.okOutput, stateChange: cfg.stateChange };\n\t} catch (err) {\n\t\tconst message = err instanceof Error ? err.message : String(err);\n\t\treturn { output: `❌ ${cfg.failPrefix}: ${message}` };\n\t}\n}\n\n// ============================================================================\n// Individual Command Handlers\n// ============================================================================\n\nfunction handleHelp(): DotCommandResult {\n\treturn {\n\t\toutput: `Available commands:\n .tables - List all tables\n .schema <table> - Show table schema\n .relations [table]- Show relations (optionally for a specific table)\n .use [schema] - Set/clear PostgreSQL schema for multi-tenant\n .exec [on|off] - Toggle or set execution mode (requires --db)\n .explain [on|off] - Toggle EXPLAIN output for queries\n .parse [on|off] - Toggle parse tree (AST) output for queries\n .natural - Switch to natural query mode\n .sql - Switch to SQL mode\n .output [mode] - Set output format (json|table|csv)\n .import <file> - Execute SQL file (DDL, seed data)\n .load <table> <f> - Import CSV file into table\n .dump <table> <f> - Export table to CSV file\n .begin - Start a transaction\n .commit - Commit the active transaction\n .rollback - Rollback the active transaction\n .help - Show this help`,\n\t};\n}\n\nfunction handleTables(_arg: string, schema: LoadedSchema): DotCommandResult {\n\treturn { output: formatTables(schema) };\n}\n\nfunction handleSchema(arg: string, schema: LoadedSchema): DotCommandResult {\n\tif (!arg) {\n\t\tconst tableCount = schema.tableNames.length;\n\t\tconst relationCount = schema.model.relations.size;\n\t\treturn {\n\t\t\toutput: `Schema Summary:\\n - Tables: ${tableCount}\\n - Relations: ${relationCount}\\n Use .schema <table> for details`,\n\t\t};\n\t}\n\treturn { output: formatTableSchema(schema, arg) };\n}\n\nfunction handleRelations(arg: string, schema: LoadedSchema): DotCommandResult {\n\treturn { output: formatRelations(schema, arg || undefined) };\n}\n\nfunction handleUse(arg: string): DotCommandResult {\n\tif (!arg) {\n\t\treturn {\n\t\t\toutput: 'Cleared schema scope. Queries now use default schema.',\n\t\t\tstateChange: { schemaName: undefined },\n\t\t};\n\t}\n\t// [SEC-S1] Validate schema name at set-time to prevent injection\n\t// via SET search_path TO \"${schemaName}\" in .import\n\ttry {\n\t\tvalidateIdentifier(arg, 'schema');\n\t} catch (err) {\n\t\tconst reason =\n\t\t\terr instanceof InvalidIdentifierError ? err.message : String(err);\n\t\treturn { output: `❌ ${reason}` };\n\t}\n\treturn {\n\t\toutput: `Using schema: ${arg}`,\n\t\tstateChange: { schemaName: arg },\n\t};\n}\n\nfunction handleExec(\n\targ: string,\n\t_schema: LoadedSchema,\n\tstate: BatchState,\n): DotCommandResult {\n\treturn handleBooleanToggle(arg, 'execEnabled', 'Execution mode', state);\n}\n\nfunction handleNatural(): DotCommandResult {\n\treturn {\n\t\toutput: 'Switched to natural query mode',\n\t\tstateChange: { mode: 'natural' },\n\t};\n}\n\nfunction handleSql(): DotCommandResult {\n\treturn {\n\t\toutput: 'Switched to SQL mode',\n\t\tstateChange: { mode: 'sql' },\n\t};\n}\n\nfunction handleExplain(\n\targ: string,\n\t_schema: LoadedSchema,\n\tstate: BatchState,\n): DotCommandResult {\n\treturn handleBooleanToggle(arg, 'explainMode', 'EXPLAIN mode', state);\n}\n\nfunction handleParse(\n\targ: string,\n\t_schema: LoadedSchema,\n\tstate: BatchState,\n): DotCommandResult {\n\treturn handleBooleanToggle(arg, 'parseMode', 'Parse mode', state);\n}\n\nfunction handleOutput(\n\targ: string,\n\t_schema: LoadedSchema,\n\tstate: BatchState,\n): DotCommandResult {\n\tconst validModes = ['json', 'table', 'csv'] as const;\n\ttype OutputMode = (typeof validModes)[number];\n\n\tif (!arg) {\n\t\treturn {\n\t\t\toutput: `Current output mode: ${state.outputMode}`,\n\t\t};\n\t}\n\n\tconst requestedMode = arg.toLowerCase();\n\tif (!validModes.includes(requestedMode as OutputMode)) {\n\t\treturn {\n\t\t\toutput: `❌ Invalid output mode: ${arg}. Use: json, table, csv`,\n\t\t};\n\t}\n\n\treturn {\n\t\toutput: `✓ Output mode: ${requestedMode}`,\n\t\tstateChange: { outputMode: requestedMode as OutputMode },\n\t};\n}\n\nconst handleBegin: DotCommandHandler = (_arg, _schema, state) =>\n\trunTransactionAction(state, 'begin');\n\nconst handleCommit: DotCommandHandler = (_arg, _schema, state) =>\n\trunTransactionAction(state, 'commit');\n\nconst handleRollback: DotCommandHandler = (_arg, _schema, state) =>\n\trunTransactionAction(state, 'rollback');\n\nasync function handleImport(\n\targ: string,\n\t_schema: LoadedSchema,\n\tstate: BatchState,\n): Promise<DotCommandResult> {\n\tif (!arg) {\n\t\treturn { output: '❌ Usage: .import <file.sql>' };\n\t}\n\n\tif (!state.dbConnection) {\n\t\treturn { output: '❌ .import requires database connection (--db)' };\n\t}\n\n\t// [SEC-M2] Path containment — rejects ../ escapes; NUL already stripped\n\tlet resolvedPath: string;\n\ttry {\n\t\tresolvedPath = validatePathInCwd(arg);\n\t} catch (err) {\n\t\tconst reason = err instanceof PathEscapeError ? err.message : String(err);\n\t\treturn { output: `❌ ${reason}` };\n\t}\n\n\tif (!existsSync(resolvedPath)) {\n\t\treturn { output: `❌ File not found: ${arg}` };\n\t}\n\n\ttry {\n\t\tlet sqlContent = readFileSync(resolvedPath, 'utf-8');\n\n\t\t// [SEC-S1] schemaName is already validated at .use time —\n\t\t// safe to interpolate here (double-quoted, no injection path)\n\t\tif (state.schemaName) {\n\t\t\tsqlContent = `SET search_path TO \"${state.schemaName}\", public;\\n${sqlContent}`;\n\t\t}\n\n\t\tconst result = await state.dbConnection.executeRaw(sqlContent, []);\n\n\t\tif (result.error) {\n\t\t\treturn {\n\t\t\t\toutput: `❌ Import failed: ${result.error}`,\n\t\t\t\tsuccess: false,\n\t\t\t\terror: result.error,\n\t\t\t};\n\t\t}\n\n\t\tconst rowInfo =\n\t\t\tresult.rowCount !== undefined\n\t\t\t\t? ` (${result.rowCount} rows affected)`\n\t\t\t\t: '';\n\t\tconst schemaInfo = state.schemaName ? ` (schema: ${state.schemaName})` : '';\n\t\treturn {\n\t\t\toutput: `✅ Imported: ${arg}${rowInfo}${schemaInfo}`,\n\t\t\tsuccess: true,\n\t\t};\n\t} catch (err) {\n\t\tconst message = err instanceof Error ? err.message : String(err);\n\t\treturn {\n\t\t\toutput: `❌ Import failed: ${message}`,\n\t\t\tsuccess: false,\n\t\t\terror: message,\n\t\t};\n\t}\n}\n\nasync function handleLoad(\n\targ: string,\n\tschema: LoadedSchema,\n\tstate: BatchState,\n): Promise<DotCommandResult> {\n\tconst loadParts = arg.split(/\\s+/);\n\tconst tableName = loadParts[0];\n\tconst filePath = loadParts.slice(1).join(' ');\n\n\tif (!tableName || !filePath) {\n\t\treturn { output: '❌ Usage: .load <table> <file.csv>' };\n\t}\n\n\tif (!state.dbConnection) {\n\t\treturn { output: '❌ .load requires database connection (--db)' };\n\t}\n\n\t// [SEC-S2] Validate table identifier before any SQL interpolation\n\ttry {\n\t\tvalidateIdentifier(tableName, 'table');\n\t} catch (err) {\n\t\tconst reason =\n\t\t\terr instanceof InvalidIdentifierError ? err.message : String(err);\n\t\treturn { output: `❌ ${reason}` };\n\t}\n\n\t// Check schema for column hints (optional — table may exist only in DB)\n\tconst loadTable = schema.model.tables.get(tableName);\n\tconst schemaColumns = loadTable\n\t\t? loadTable.columns.map((c) => c.name)\n\t\t: undefined;\n\n\t// [SEC-M2] Path containment for file argument\n\tlet loadFilePath: string;\n\ttry {\n\t\tloadFilePath = validatePathInCwd(filePath);\n\t} catch (err) {\n\t\tconst reason = err instanceof PathEscapeError ? err.message : String(err);\n\t\treturn { output: `❌ ${reason}` };\n\t}\n\n\tif (!existsSync(loadFilePath)) {\n\t\treturn { output: `❌ File not found: ${filePath}` };\n\t}\n\n\ttry {\n\t\tconst csvData = await parseCsvFile(loadFilePath, schemaColumns);\n\n\t\tif (csvData.rows.length === 0) {\n\t\t\treturn { output: '⚠️ CSV file is empty — no rows to import' };\n\t\t}\n\n\t\t// Use CSV columns, optionally filtered to schema columns\n\t\tconst csvColumns = [...csvData.format.columns];\n\n\t\t// [SEC-S2] Validate every CSV column name before SQL interpolation\n\t\tfor (const col of csvColumns) {\n\t\t\ttry {\n\t\t\t\tvalidateIdentifier(col, 'column');\n\t\t\t} catch (err) {\n\t\t\t\tconst reason =\n\t\t\t\t\terr instanceof InvalidIdentifierError ? err.message : String(err);\n\t\t\t\treturn {\n\t\t\t\t\toutput: `❌ CSV header contains invalid column: ${reason}`,\n\t\t\t\t};\n\t\t\t}\n\t\t}\n\n\t\tconst validColumns = schemaColumns\n\t\t\t? csvColumns.filter((c) => schemaColumns.includes(c))\n\t\t\t: csvColumns;\n\t\tif (validColumns.length === 0) {\n\t\t\treturn {\n\t\t\t\toutput: `❌ No matching columns found in CSV: ${csvColumns.join(', ')}`,\n\t\t\t};\n\t\t}\n\n\t\t// Batch insert (100 rows per batch to avoid parameter limit)\n\t\tconst BATCH_SIZE = 100;\n\t\tlet totalInserted = 0;\n\n\t\tfor (let i = 0; i < csvData.rows.length; i += BATCH_SIZE) {\n\t\t\tconst batch = csvData.rows.slice(i, i + BATCH_SIZE);\n\t\t\tconst params: unknown[] = [];\n\t\t\tconst valueRows: string[] = [];\n\n\t\t\tfor (const row of batch) {\n\t\t\t\tconst placeholders: string[] = [];\n\t\t\t\tfor (const col of validColumns) {\n\t\t\t\t\tparams.push(row[col] ?? null);\n\t\t\t\t\tplaceholders.push(`$${params.length}`);\n\t\t\t\t}\n\t\t\t\tvalueRows.push(`(${placeholders.join(', ')})`);\n\t\t\t}\n\n\t\t\tconst quotedCols = validColumns.map((c) => `\"${c}\"`).join(', ');\n\t\t\tconst schemaPrefix = state.schemaName ? `\"${state.schemaName}\".` : '';\n\t\t\tconst sql = `INSERT INTO ${schemaPrefix}\"${tableName}\" (${quotedCols}) VALUES ${valueRows.join(', ')}`;\n\n\t\t\tconst result = await state.dbConnection.executeRaw(sql, params);\n\t\t\tif (result.error) {\n\t\t\t\treturn {\n\t\t\t\t\toutput: `❌ Insert failed at row ${totalInserted + 1}: ${result.error}`,\n\t\t\t\t\tsuccess: false,\n\t\t\t\t\terror: result.error,\n\t\t\t\t};\n\t\t\t}\n\t\t\ttotalInserted += batch.length;\n\t\t}\n\n\t\tconst formatInfo = `separator: '${csvData.format.separator === '\\t' ? '\\\\t' : csvData.format.separator}', header: ${csvData.format.hasHeader ? 'yes' : 'no'}`;\n\t\treturn {\n\t\t\toutput: `✅ Loaded ${totalInserted} rows into ${tableName} (${formatInfo})`,\n\t\t\tsuccess: true,\n\t\t};\n\t} catch (err) {\n\t\tconst message = err instanceof Error ? err.message : String(err);\n\t\treturn {\n\t\t\toutput: `❌ Load failed: ${message}`,\n\t\t\tsuccess: false,\n\t\t\terror: message,\n\t\t};\n\t}\n}\n\nasync function handleDump(\n\targ: string,\n\tschema: LoadedSchema,\n\tstate: BatchState,\n): Promise<DotCommandResult> {\n\tconst dumpParts = arg.split(/\\s+/);\n\tconst dumpTableName = dumpParts[0];\n\tconst dumpFilePath = dumpParts.slice(1).join(' ');\n\n\tif (!dumpTableName || !dumpFilePath) {\n\t\treturn { output: '❌ Usage: .dump <table> <file.csv>' };\n\t}\n\n\tif (!state.dbConnection) {\n\t\treturn { output: '❌ .dump requires database connection (--db)' };\n\t}\n\n\t// [SEC-S2] Validate table identifier before any SQL interpolation\n\ttry {\n\t\tvalidateIdentifier(dumpTableName, 'table');\n\t} catch (err) {\n\t\tconst reason =\n\t\t\terr instanceof InvalidIdentifierError ? err.message : String(err);\n\t\treturn { output: `❌ ${reason}` };\n\t}\n\n\t// Verify table exists in schema\n\tconst dumpTable = schema.model.tables.get(dumpTableName);\n\tif (!dumpTable) {\n\t\treturn { output: `❌ Table not found: ${dumpTableName}` };\n\t}\n\n\t// [SEC-M2] Path containment for output file\n\tlet resolvedDumpPath: string;\n\ttry {\n\t\tresolvedDumpPath = validatePathInCwd(dumpFilePath);\n\t} catch (err) {\n\t\tconst reason = err instanceof PathEscapeError ? err.message : String(err);\n\t\treturn { output: `❌ ${reason}` };\n\t}\n\n\ttry {\n\t\tconst schemaPrefix = state.schemaName ? `\"${state.schemaName}\".` : '';\n\t\tconst result = await state.dbConnection.executeRaw(\n\t\t\t`SELECT * FROM ${schemaPrefix}\"${dumpTableName}\"`,\n\t\t);\n\n\t\tif (result.error) {\n\t\t\treturn {\n\t\t\t\toutput: `❌ Query failed: ${result.error}`,\n\t\t\t\tsuccess: false,\n\t\t\t\terror: result.error,\n\t\t\t};\n\t\t}\n\n\t\tconst columns =\n\t\t\tresult.columns.length > 0\n\t\t\t\t? result.columns\n\t\t\t\t: dumpTable.columns.map((c) => c.name);\n\n\t\tconst csv = formatCsv(result.rows, columns);\n\t\twriteFileSync(resolvedDumpPath, `${csv}\\n`, 'utf-8');\n\n\t\treturn {\n\t\t\toutput: `✅ Dumped ${result.rows.length} rows from ${dumpTableName} to ${dumpFilePath}`,\n\t\t\tsuccess: true,\n\t\t};\n\t} catch (err) {\n\t\tconst message = err instanceof Error ? err.message : String(err);\n\t\treturn {\n\t\t\toutput: `❌ Dump failed: ${message}`,\n\t\t\tsuccess: false,\n\t\t\terror: message,\n\t\t};\n\t}\n}\n\nfunction handleExit(): DotCommandResult {\n\treturn { output: 'Exiting...' };\n}\n\n// ============================================================================\n// Dispatch Table\n// ============================================================================\n\nconst DOT_COMMAND_HANDLERS: Map<string, DotCommandHandler> = new Map([\n\t['.help', handleHelp],\n\t['.tables', handleTables],\n\t['.schema', handleSchema],\n\t['.relations', handleRelations],\n\t['.use', handleUse],\n\t['.exec', handleExec],\n\t['.natural', handleNatural],\n\t['.sql', handleSql],\n\t['.explain', handleExplain],\n\t['.parse', handleParse],\n\t['.output', handleOutput],\n\t['.begin', handleBegin],\n\t['.commit', handleCommit],\n\t['.rollback', handleRollback],\n\t['.import', handleImport],\n\t['.load', handleLoad],\n\t['.dump', handleDump],\n\t['.exit', handleExit],\n\t['.quit', handleExit], // alias — same handler instance\n]);\n\n// ============================================================================\n// Dot Command Processor\n// ============================================================================\n\n/**\n * Process a dot command (async to support .import)\n * ARCH-005: Uses LoadedSchema instead of ResolvedSchema\n * @internal - Exported for testing\n */\nexport async function processDotCommand(\n\tinput: string,\n\tschema: LoadedSchema,\n\tstate: BatchState,\n): Promise<DotCommandResult> {\n\tconst parts = input.split(/\\s+/);\n\tconst command = (parts[0] ?? '').toLowerCase();\n\t// [SEC-M2] Strip NUL bytes from arg before any use\n\tconst arg = parts.slice(1).join(' ').trim().replace(/\\0/g, '');\n\n\tconst handler = DOT_COMMAND_HANDLERS.get(command);\n\tif (!handler) {\n\t\treturn { output: `❌ Unknown command: ${command}` };\n\t}\n\treturn handler(arg, schema, state);\n}\n","/**\n * @module path-containment\n * Validate that a user-supplied path stays within the working directory.\n *\n * Security notes:\n * - NUL bytes are stripped before any comparison (they can truncate paths on\n * some OS/libc layers and confuse string-prefix checks).\n * - Uses path.relative() instead of startsWith() to detect traversal — the\n * startsWith approach is unsafe because a basePath of /foo matches /foobar.\n * path.relative() returns a leading \"..\" segment when the resolved path\n * escapes the base directory.\n *\n * Reference: security-basics GOTCHAS.md §\"Path containment bypass via\n * startsWith\" (2026-02-05).\n */\n\nimport { isAbsolute, relative, resolve, sep } from 'node:path';\n\n/** Thrown when a path escapes the permitted base directory. */\nexport class PathEscapeError extends Error {\n\tconstructor(\n\t\tpublic readonly originalArg: string,\n\t\tpublic readonly resolvedPath: string,\n\t\tpublic readonly baseDir: string,\n\t) {\n\t\tsuper(\n\t\t\t`Path escapes working directory: \"${originalArg}\" resolves to \"${resolvedPath}\" which is outside \"${baseDir}\"`,\n\t\t);\n\t\tthis.name = 'PathEscapeError';\n\t}\n}\n\n/**\n * Sanitise a user-provided path argument and verify it stays within `cwd`.\n *\n * Steps:\n * 1. Strip NUL bytes (defensive: truncation attacks).\n * 2. Resolve the path relative to `cwd`.\n * 3. Verify the resolved path is inside `cwd` using `path.relative()`.\n *\n * @param arg - Raw user-supplied path (may be relative or absolute).\n * @param cwd - Base directory to contain the path (default: process.cwd()).\n * @returns The sanitised, resolved absolute path.\n * @throws {PathEscapeError} if the path resolves outside `cwd`.\n */\nexport function validatePathInCwd(\n\targ: string,\n\tcwd: string = process.cwd(),\n): string {\n\t// Step 1: strip NUL bytes\n\tconst sanitised = arg.replace(/\\0/g, '');\n\n\t// Step 2: resolve to absolute path\n\tconst resolved = resolve(cwd, sanitised);\n\tconst base = resolve(cwd);\n\n\t// Step 3: containment check — only for RELATIVE paths.\n\t//\n\t// Rationale: a relative path like '../../etc/passwd' silently escapes the\n\t// working directory without the user realising it. An explicit absolute\n\t// path (e.g. '/home/user/data/seed.sql') is unambiguous user intent and\n\t// is allowed — the user typed it in full.\n\t//\n\t// We only enforce containment on relative paths. Uses path.isAbsolute()\n\t// (cross-platform: handles Windows drive-letter and UNC paths) instead of\n\t// checking for a leading '/'. The containment check itself uses\n\t// path.relative() instead of startsWith() to avoid the prefix-collision\n\t// trap (/foo matches /foobar with startsWith). The '..' check is tightened\n\t// to rel === '..' or rel starting with '../' / '..<sep>' to avoid false\n\t// positives on legitimate paths like '..foo/file'.\n\t//\n\t// Reference: security-basics GOTCHAS.md §\"Path containment bypass via\n\t// startsWith\" (2026-02-05).\n\tconst isRelative = !isAbsolute(sanitised);\n\tif (isRelative) {\n\t\tconst rel = relative(base, resolved);\n\t\t// Use exact match for '..' or check for '..' followed by a separator\n\t\t// to avoid false positives on paths like '..foo/file' (starts with '..'\n\t\t// but is NOT a parent-directory traversal).\n\t\tif (rel === '..' || rel.startsWith('../') || rel.startsWith(`..${sep}`)) {\n\t\t\tthrow new PathEscapeError(arg, resolved, base);\n\t\t}\n\t}\n\n\treturn resolved;\n}\n","/**\n * @module csv\n * CSV parsing and format detection for .load / .dump commands.\n *\n * No external dependencies — uses RFC 4180 parsing with smart format sniffing.\n */\n\nimport { readFile } from 'node:fs/promises';\n\n// ============================================================================\n// Types\n// ============================================================================\n\n/** Detected CSV format parameters. */\nexport interface CsvFormat {\n\t/** Field separator (comma, semicolon, tab, pipe) */\n\treadonly separator: string;\n\t/** Quote character (double-quote, single-quote, or none) */\n\treadonly quote: string;\n\t/** Whether the first row contains column names */\n\treadonly hasHeader: boolean;\n\t/** Column names (from header row or generated col_0, col_1, ...) */\n\treadonly columns: readonly string[];\n}\n\n/** A parsed CSV file ready for insertion. */\nexport interface CsvData {\n\treadonly format: CsvFormat;\n\treadonly rows: readonly Record<string, string>[];\n}\n\n// ============================================================================\n// Error\n// ============================================================================\n\n/**\n * Thrown when the CSV content is structurally invalid (RFC 4180 violation,\n * field-count mismatch, unterminated quote, etc.).\n */\nexport class CsvParseError extends Error {\n\t/** 1-based physical line number where the error was detected. */\n\treadonly line: number;\n\n\tconstructor(line: number, message: string) {\n\t\tsuper(`CSV parse error at line ${line}: ${message}`);\n\t\tthis.name = 'CsvParseError';\n\t\tthis.line = line;\n\t\tObject.setPrototypeOf(this, CsvParseError.prototype);\n\t}\n}\n\n// ============================================================================\n// Format Sniffing\n// ============================================================================\n\nconst CANDIDATE_SEPARATORS = [',', ';', '\\t', '|'] as const;\nconst CANDIDATE_QUOTES = ['\"', \"'\"] as const;\n\n/**\n * Detect CSV format from the first N lines of a file.\n *\n * Strategy:\n * 1. Try each separator candidate — the one that produces the most consistent\n * field count across sample lines wins.\n * 2. Detect quote character by checking which appears most in field boundaries.\n * 3. Detect header row by checking if first row values look like column names\n * (no digits-only values, and optionally matches schema column names).\n *\n * @param lines - Sample lines (typically first 5-10 lines of the file)\n * @param schemaColumns - Optional column names from DB schema for header matching\n */\nexport function sniffCsvFormat(\n\tlines: readonly string[],\n\tschemaColumns?: readonly string[],\n): CsvFormat {\n\tif (lines.length === 0) {\n\t\treturn { separator: ',', quote: '\"', hasHeader: false, columns: [] };\n\t}\n\n\t// 1. Detect quote character\n\tconst quote = detectQuoteChar(lines);\n\n\t// 2. Detect separator — pick the one with most consistent field count\n\tconst separator = detectSeparator(lines, quote);\n\n\t// 3. Parse first row as potential header\n\t// biome-ignore lint/style/noNonNullAssertion: guarded by `if (lines.length === 0) return` at function entry\n\tconst firstRowFields = parseCsvLine(lines[0]!, separator, quote);\n\n\t// 4. Detect header\n\tconst hasHeader = detectHeader(firstRowFields, schemaColumns);\n\n\t// 5. Build column names\n\tconst columns = hasHeader\n\t\t? firstRowFields.map((f) => f.trim())\n\t\t: firstRowFields.map((_, i) => `col_${i}`);\n\n\treturn { separator, quote, hasHeader, columns };\n}\n\n/**\n * Detect the most likely quote character from sample lines.\n */\nfunction detectQuoteChar(lines: readonly string[]): string {\n\tconst joined = lines.join('');\n\tlet bestQuote = '\"';\n\tlet bestCount = 0;\n\n\tfor (const q of CANDIDATE_QUOTES) {\n\t\tconst count = joined.split(q).length - 1;\n\t\tif (count > bestCount) {\n\t\t\tbestCount = count;\n\t\t\tbestQuote = q;\n\t\t}\n\t}\n\n\treturn bestQuote;\n}\n\n/**\n * Detect separator by measuring field-count consistency across lines.\n */\nfunction detectSeparator(lines: readonly string[], quote: string): string {\n\tlet bestSep = ',';\n\tlet bestScore = -1;\n\n\tfor (const sep of CANDIDATE_SEPARATORS) {\n\t\tconst counts = lines.map((line) => parseCsvLine(line, sep, quote).length);\n\t\t// Score: high if consistent count AND count > 1\n\t\t// biome-ignore lint/style/noNonNullAssertion: counts is derived from lines.map() which is non-empty (CANDIDATE_SEPARATORS loop body only reached when lines.length > 0)\n\t\tconst firstCount = counts[0]!;\n\t\tif (firstCount <= 1) continue;\n\n\t\tconst consistent = counts.every((c) => c === firstCount);\n\t\tconst score = consistent ? firstCount * 10 : firstCount;\n\n\t\tif (score > bestScore) {\n\t\t\tbestScore = score;\n\t\t\tbestSep = sep;\n\t\t}\n\t}\n\n\treturn bestSep;\n}\n\n/**\n * Heuristic header detection (used when no '--header' / '--no-header' flag).\n *\n * Returns true only when ALL first-row values match a strict snake_case\n * identifier pattern (lowercase letters, digits, underscores only, must\n * start with a lowercase letter). This conservative heuristic rejects\n * PascalCase / camelCase headers — pass 'schemaColumns' explicitly\n * (or use '--header') for those.\n *\n * Conservative on purpose: incorrectly classifying a data row as header\n * silently drops one record (worse than failing to detect a header,\n * which only causes a recoverable schema mismatch error).\n */\nfunction detectHeader(\n\tfirstRowFields: readonly string[],\n\tschemaColumns?: readonly string[],\n): boolean {\n\tif (firstRowFields.length === 0) return false;\n\n\t// If schema columns provided, check for match\n\tif (schemaColumns && schemaColumns.length > 0) {\n\t\tconst schemaSet = new Set(schemaColumns.map((c) => c.toLowerCase().trim()));\n\t\tconst matchCount = firstRowFields.filter((f) =>\n\t\t\tschemaSet.has(f.toLowerCase().trim()),\n\t\t).length;\n\t\t// If > 50% of fields match schema columns → header\n\t\tif (matchCount > firstRowFields.length / 2) return true;\n\t}\n\n\t// C6: The broad heuristic \"all values are non-numeric, short strings\" has an\n\t// unacceptably high false-positive rate for plain data (e.g. Alice,Paris —\n\t// both non-numeric, both short). Tighten to: only auto-detect as header when\n\t// ALL values match the DB identifier pattern (lowercase/digits/underscores,\n\t// no uppercase, no spaces). This reliably distinguishes column names like\n\t// `name`, `user_id`, `created_at` from proper-noun data like `Alice`, `Paris`.\n\treturn firstRowFields.every((field) => {\n\t\tconst trimmed = field.trim();\n\t\tif (trimmed === '') return false;\n\t\t// Must match DB identifier pattern: only lowercase letters, digits, underscores.\n\t\t// Uppercase first letter → almost certainly data (proper noun), not a column name.\n\t\treturn /^[a-z][a-z0-9_]*$/.test(trimmed);\n\t});\n}\n\n// ============================================================================\n// CSV Line Parsing (RFC 4180)\n// ============================================================================\n\n/**\n * Parse a single CSV line into fields, respecting quoted values.\n */\nexport function parseCsvLine(\n\tline: string,\n\tseparator: string,\n\tquote: string,\n\tphysicalLine?: number,\n): string[] {\n\tconst fields: string[] = [];\n\tlet current = '';\n\tlet inQuotes = false;\n\tlet i = 0;\n\n\twhile (i < line.length) {\n\t\t// biome-ignore lint/style/noNonNullAssertion: loop invariant — i < line.length guaranteed by while condition\n\t\tconst char = line[i]!;\n\n\t\tif (inQuotes) {\n\t\t\tif (char === quote) {\n\t\t\t\t// Check for escaped quote (RFC 4180 doubled-quote escape)\n\t\t\t\tif (i + 1 < line.length && line[i + 1] === quote) {\n\t\t\t\t\tcurrent += quote;\n\t\t\t\t\ti += 2;\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\t// Closing quote — next char must be separator or end-of-string.\n\t\t\t\t// (RFC 4180 §2.6: chars after closing quote before separator = error)\n\t\t\t\t// Only validate in strict mode (physicalLine provided).\n\t\t\t\tif (\n\t\t\t\t\tphysicalLine !== undefined &&\n\t\t\t\t\ti + 1 < line.length &&\n\t\t\t\t\tline[i + 1] !== separator\n\t\t\t\t) {\n\t\t\t\t\tthrow new CsvParseError(\n\t\t\t\t\t\tphysicalLine,\n\t\t\t\t\t\t`unexpected character '${line[i + 1]}' after closing quote`,\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t\tinQuotes = false;\n\t\t\t\ti++;\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tcurrent += char;\n\t\t\ti++;\n\t\t} else {\n\t\t\tif (char === quote && current === '') {\n\t\t\t\t// Start of quoted field (RFC 4180 §2.5: quotes must start the field)\n\t\t\t\tinQuotes = true;\n\t\t\t\ti++;\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tif (char === separator) {\n\t\t\t\tfields.push(current);\n\t\t\t\tcurrent = '';\n\t\t\t\ti++;\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tcurrent += char;\n\t\t\ti++;\n\t\t}\n\t}\n\n\t// EOF-in-quote: only throw in strict mode (physicalLine provided).\n\t// In lenient mode (sniffing), return partial fields.\n\tif (inQuotes && physicalLine !== undefined) {\n\t\tthrow new CsvParseError(\n\t\t\tphysicalLine,\n\t\t\t'unterminated quoted field (EOF in quote)',\n\t\t);\n\t}\n\n\tfields.push(current);\n\treturn fields;\n}\n\n// ============================================================================\n// Stateful RFC 4180 tokeniser internals\n// ============================================================================\n\n/**\n * A logical CSV row: one or more physical lines joined by quoted newlines.\n * @internal\n */\ninterface LogicalRow {\n\t/** Concatenated text of all physical lines (including embedded newlines). */\n\trawText: string;\n\t/** The first physical line's text — used for format sniffing. */\n\trawFirstLine: string;\n\t/** 1-based physical line number where this logical row starts. */\n\tstartPhysicalLine: number;\n}\n\n/**\n * Tokenise a complete CSV file string into logical rows.\n *\n * A logical row spans one or more physical lines: when a field is opened with\n * a quote character, embedded newlines (LF or CRLF) are part of the field\n * until the matching closing quote is found. This is the fix for RFC 4180\n * multiline quoted fields being silently truncated by the previous readline\n * approach.\n *\n * Returns an array of LogicalRow objects. Throws CsvParseError if the file\n * ends while inside a quoted field (unterminated quote).\n */\nfunction tokeniseCsvContent(content: string, quoteChar: string): LogicalRow[] {\n\tconst rows: LogicalRow[] = [];\n\tconst q = quoteChar;\n\tlet inQuote = false;\n\tlet rowStart = 0;\n\tlet physicalLine = 1;\n\tlet rowStartPhysicalLine = 1;\n\t/**\n\t * Exclusive end index of the first physical line within the current logical\n\t * row — used to populate rawFirstLine for format sniffing.\n\t * -1 means we haven't crossed a newline yet in this row.\n\t */\n\tlet firstLineOfRowEnd = -1;\n\n\tfor (let i = 0; i < content.length; i++) {\n\t\t// biome-ignore lint/style/noNonNullAssertion: loop invariant — i < content.length guaranteed by for-loop condition\n\t\tconst ch = content[i]!;\n\n\t\tif (ch === q) {\n\t\t\tif (!inQuote) {\n\t\t\t\tinQuote = true;\n\t\t\t} else {\n\t\t\t\t// Check for doubled quote (RFC 4180 escape)\n\t\t\t\tif (i + 1 < content.length && content[i + 1] === q) {\n\t\t\t\t\ti++; // skip the doubled quote\n\t\t\t\t} else {\n\t\t\t\t\tinQuote = false;\n\t\t\t\t}\n\t\t\t}\n\t\t} else if (ch === '\\n') {\n\t\t\t// Record end of first physical line (regardless of quote state)\n\t\t\tif (firstLineOfRowEnd === -1) {\n\t\t\t\tfirstLineOfRowEnd = i > 0 && content[i - 1] === '\\r' ? i - 1 : i;\n\t\t\t}\n\t\t\tphysicalLine++;\n\n\t\t\tif (!inQuote) {\n\t\t\t\t// End of logical row\n\t\t\t\tconst rawEnd = i > 0 && content[i - 1] === '\\r' ? i - 1 : i;\n\t\t\t\tconst rawText = content.slice(rowStart, rawEnd);\n\t\t\t\tconst rawFirstLine =\n\t\t\t\t\tfirstLineOfRowEnd === rawEnd\n\t\t\t\t\t\t? rawText // single-line row: rawFirstLine == rawText\n\t\t\t\t\t\t: content.slice(rowStart, firstLineOfRowEnd);\n\t\t\t\trows.push({\n\t\t\t\t\trawText,\n\t\t\t\t\trawFirstLine,\n\t\t\t\t\tstartPhysicalLine: rowStartPhysicalLine,\n\t\t\t\t});\n\t\t\t\trowStart = i + 1;\n\t\t\t\trowStartPhysicalLine = physicalLine;\n\t\t\t\tfirstLineOfRowEnd = -1;\n\t\t\t}\n\t\t\t// inQuote: embedded newline — stay in same logical row, continue accumulating\n\t\t} else if (ch === '\\r') {\n\t\t\t// CR not followed by LF = CR-only line ending\n\t\t\tif (i + 1 >= content.length || content[i + 1] !== '\\n') {\n\t\t\t\tif (firstLineOfRowEnd === -1) {\n\t\t\t\t\tfirstLineOfRowEnd = i;\n\t\t\t\t}\n\t\t\t\tphysicalLine++;\n\n\t\t\t\tif (!inQuote) {\n\t\t\t\t\tconst rawText = content.slice(rowStart, i);\n\t\t\t\t\tconst rawFirstLine =\n\t\t\t\t\t\tfirstLineOfRowEnd === i\n\t\t\t\t\t\t\t? rawText\n\t\t\t\t\t\t\t: content.slice(rowStart, firstLineOfRowEnd);\n\t\t\t\t\trows.push({\n\t\t\t\t\t\trawText,\n\t\t\t\t\t\trawFirstLine,\n\t\t\t\t\t\tstartPhysicalLine: rowStartPhysicalLine,\n\t\t\t\t\t});\n\t\t\t\t\trowStart = i + 1;\n\t\t\t\t\trowStartPhysicalLine = physicalLine;\n\t\t\t\t\tfirstLineOfRowEnd = -1;\n\t\t\t\t}\n\t\t\t}\n\t\t\t// CR+LF: CR is skipped here; the \\n at i+1 will handle row emission\n\t\t}\n\t}\n\n\t// Handle final row (file with no trailing newline)\n\tif (rowStart < content.length) {\n\t\tif (inQuote) {\n\t\t\tthrow new CsvParseError(\n\t\t\t\trowStartPhysicalLine,\n\t\t\t\t'unterminated quoted field at end of file',\n\t\t\t);\n\t\t}\n\t\tconst rawText = content.slice(rowStart).replace(/\\r$/, '');\n\t\tif (rawText.length > 0) {\n\t\t\tconst rawFirstLine =\n\t\t\t\tfirstLineOfRowEnd === -1\n\t\t\t\t\t? rawText\n\t\t\t\t\t: content.slice(rowStart, firstLineOfRowEnd);\n\t\t\trows.push({\n\t\t\t\trawText,\n\t\t\t\trawFirstLine,\n\t\t\t\tstartPhysicalLine: rowStartPhysicalLine,\n\t\t\t});\n\t\t}\n\t} else if (inQuote) {\n\t\t// File ended exactly at a newline while in quote\n\t\tthrow new CsvParseError(\n\t\t\trowStartPhysicalLine,\n\t\t\t'unterminated quoted field at end of file',\n\t\t);\n\t}\n\n\treturn rows;\n}\n\n/**\n * Parse a logical row's rawText into field values.\n *\n * The rawText may contain embedded newlines inside quoted fields (RFC 4180 §2.6).\n * Uses a character-level state machine — does NOT use parseCsvLine (which is\n * single-line only and throws on unterminated quotes).\n */\nfunction parseLogicalRow(\n\trow: LogicalRow,\n\tseparator: string,\n\tquote: string,\n): string[] {\n\tconst text = row.rawText;\n\tconst fields: string[] = [];\n\tlet current = '';\n\tlet inQuotes = false;\n\tlet i = 0;\n\n\twhile (i < text.length) {\n\t\t// biome-ignore lint/style/noNonNullAssertion: loop invariant — i < text.length guaranteed by while condition\n\t\tconst ch = text[i]!;\n\n\t\tif (inQuotes) {\n\t\t\tif (ch === quote) {\n\t\t\t\tif (i + 1 < text.length && text[i + 1] === quote) {\n\t\t\t\t\t// Doubled quote — literal quote character\n\t\t\t\t\tcurrent += quote;\n\t\t\t\t\ti += 2;\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\t// Closing quote — validate next char\n\t\t\t\tconst next = text[i + 1];\n\t\t\t\tif (\n\t\t\t\t\tnext !== undefined &&\n\t\t\t\t\tnext !== separator &&\n\t\t\t\t\tnext !== '\\r' &&\n\t\t\t\t\tnext !== '\\n'\n\t\t\t\t) {\n\t\t\t\t\tthrow new CsvParseError(\n\t\t\t\t\t\trow.startPhysicalLine,\n\t\t\t\t\t\t`unexpected character '${next}' after closing quote`,\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t\tinQuotes = false;\n\t\t\t\ti++;\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\t// Embedded newline or any other char inside quotes\n\t\t\tcurrent += ch;\n\t\t\ti++;\n\t\t} else {\n\t\t\tif (ch === quote && current === '') {\n\t\t\t\tinQuotes = true;\n\t\t\t\ti++;\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tif (ch === separator) {\n\t\t\t\tfields.push(current);\n\t\t\t\tcurrent = '';\n\t\t\t\ti++;\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tcurrent += ch;\n\t\t\ti++;\n\t\t}\n\t}\n\n\tfields.push(current);\n\treturn fields;\n}\n\n// ============================================================================\n// File Reading\n// ============================================================================\n\n/**\n * Read and parse a CSV file with automatic format detection.\n *\n * Single-pass stateful RFC 4180 parser: reads the file once, tokenises into\n * logical rows (spanning physical lines for quoted multiline fields), sniffs\n * format from the first 10 rows, then parses all rows into field maps.\n *\n * Throws CsvParseError on structural violations (unterminated quotes, field\n * count mismatch, extra chars after closing quote).\n *\n * @param filePath - Absolute path to CSV file\n * @param schemaColumns - Optional column names from DB schema for header matching\n * @returns Parsed CSV data with detected format and row records\n */\nexport async function parseCsvFile(\n\tfilePath: string,\n\tschemaColumns?: readonly string[],\n): Promise<CsvData> {\n\t// Single read — no double-open (fixes TOCTOU / double-read issue)\n\tconst raw = await readFile(filePath, 'utf-8');\n\n\tif (raw.length === 0) {\n\t\treturn {\n\t\t\tformat: { separator: ',', quote: '\"', hasHeader: false, columns: [] },\n\t\t\trows: [],\n\t\t};\n\t}\n\n\t// -------------------------------------------------------------------------\n\t// Phase 1 — Quick physical-line sniff (ignores quote state intentionally)\n\t//\n\t// We split on \\n to get a sample of physical lines for format detection.\n\t// This is sufficient for detecting separator, quote char, and header — the\n\t// sniff heuristics work on field counts and char frequency which don't\n\t// require quote-aware row spanning.\n\t// -------------------------------------------------------------------------\n\tconst SNIFF_LINE_COUNT = 10;\n\tconst physicalLines = raw.split('\\n');\n\tconst sampleLines = physicalLines\n\t\t.slice(0, SNIFF_LINE_COUNT * 3) // over-sample to find SNIFF_LINE_COUNT non-blank\n\t\t.map((l) => l.replace(/\\r$/, ''))\n\t\t.filter((l) => l.trim() !== '')\n\t\t.slice(0, SNIFF_LINE_COUNT);\n\n\tif (sampleLines.length === 0) {\n\t\treturn {\n\t\t\tformat: { separator: ',', quote: '\"', hasHeader: false, columns: [] },\n\t\t\trows: [],\n\t\t};\n\t}\n\n\tconst format = sniffCsvFormat(sampleLines, schemaColumns);\n\n\t// -------------------------------------------------------------------------\n\t// Phase 2 — Stateful RFC 4180 character-level tokeniser\n\t//\n\t// Now that we know the quote character, tokenise the full content into\n\t// logical rows, correctly handling embedded newlines in quoted fields.\n\t// -------------------------------------------------------------------------\n\tconst logicalRows = tokeniseCsvContent(raw, format.quote);\n\n\tif (logicalRows.length === 0) {\n\t\treturn { format, rows: [] };\n\t}\n\n\t// -------------------------------------------------------------------------\n\t// Phase 3 — Parse logical rows into field arrays\n\t// -------------------------------------------------------------------------\n\tconst rows: Record<string, string>[] = [];\n\tlet rowIndex = 0;\n\n\tfor (const logicalRow of logicalRows) {\n\t\t// Skip blank logical rows\n\t\tif (logicalRow.rawText.trim() === '') {\n\t\t\tcontinue;\n\t\t}\n\n\t\t// Skip header row (rowIndex counts only non-blank rows)\n\t\tif (rowIndex === 0 && format.hasHeader) {\n\t\t\trowIndex++;\n\t\t\tcontinue;\n\t\t}\n\n\t\tconst fields = parseLogicalRow(logicalRow, format.separator, format.quote);\n\n\t\t// Field count validation (M-class fix)\n\t\tif (fields.length !== format.columns.length) {\n\t\t\tthrow new CsvParseError(\n\t\t\t\tlogicalRow.startPhysicalLine,\n\t\t\t\t`expected ${format.columns.length} fields, got ${fields.length}`,\n\t\t\t);\n\t\t}\n\n\t\tconst row: Record<string, string> = {};\n\t\tfor (let i = 0; i < format.columns.length; i++) {\n\t\t\t// biome-ignore lint/style/noNonNullAssertion: loop invariant — i < format.columns.length guaranteed by for-loop condition\n\t\t\trow[format.columns[i]!] = fields[i] ?? '';\n\t\t}\n\t\trows.push(row);\n\t\trowIndex++;\n\t}\n\n\treturn { format, rows };\n}\n\n// ============================================================================\n// CSV Export (for .dump)\n// ============================================================================\n\n/**\n * Format rows as CSV string with header.\n *\n * @param rows - Data rows to export\n * @param columns - Column names for header and field ordering\n * @returns CSV string with header row\n */\nexport function formatCsv(\n\trows: readonly Record<string, unknown>[],\n\tcolumns: readonly string[],\n): string {\n\tif (columns.length === 0) return '';\n\n\tconst header = columns.map((c) => escapeCsvField(c)).join(',');\n\tconst dataLines = rows.map((row) =>\n\t\tcolumns.map((col) => escapeCsvField(formatFieldValue(row[col]))).join(','),\n\t);\n\n\treturn [header, ...dataLines].join('\\n');\n}\n\n/**\n * Escape a CSV field value (RFC 4180).\n */\nfunction escapeCsvField(value: string): string {\n\tif (\n\t\tvalue.includes(',') ||\n\t\tvalue.includes('\\n') ||\n\t\tvalue.includes('\\r') ||\n\t\tvalue.includes('\"')\n\t) {\n\t\treturn `\"${value.replace(/\"/g, '\"\"')}\"`;\n\t}\n\treturn value;\n}\n\n/**\n * Convert a value to its CSV string representation.\n */\nfunction formatFieldValue(value: unknown): string {\n\tif (value === null || value === undefined) return '';\n\tif (typeof value === 'object') return JSON.stringify(value);\n\treturn String(value);\n}\n","/**\n * DX-030 Block 6: Autocompletion\n *\n * Provides context-aware autocompletion suggestions for the REPL.\n */\n\nimport type { LoadedSchema } from '../utils/schema-loader.js';\n\n/**\n * Levenshtein distance between two strings.\n * Used for fuzzy matching suggestions.\n */\nexport function levenshtein(a: string, b: string): number {\n\tconst aLower = a.toLowerCase();\n\tconst bLower = b.toLowerCase();\n\n\tif (aLower === bLower) return 0;\n\tif (aLower.length === 0) return bLower.length;\n\tif (bLower.length === 0) return aLower.length;\n\n\t// Create matrix with proper initialization\n\tconst matrix: number[][] = Array.from({ length: aLower.length + 1 }, (_, i) =>\n\t\tArray.from({ length: bLower.length + 1 }, (_, j) =>\n\t\t\ti === 0 ? j : j === 0 ? i : 0,\n\t\t),\n\t);\n\n\t// Fill matrix\n\tfor (let i = 1; i <= aLower.length; i++) {\n\t\tfor (let j = 1; j <= bLower.length; j++) {\n\t\t\tconst cost = aLower[i - 1] === bLower[j - 1] ? 0 : 1;\n\t\t\tconst deletion = matrix[i - 1]?.[j] ?? 0;\n\t\t\tconst insertion = matrix[i]?.[j - 1] ?? 0;\n\t\t\tconst substitution = matrix[i - 1]?.[j - 1] ?? 0;\n\t\t\t// biome-ignore lint/style/noNonNullAssertion: Matrix row guaranteed to exist by outer loop initialization\n\t\t\tmatrix[i]![j] = Math.min(\n\t\t\t\tdeletion + 1,\n\t\t\t\tinsertion + 1,\n\t\t\t\tsubstitution + cost,\n\t\t\t);\n\t\t}\n\t}\n\n\treturn matrix[aLower.length]?.[bLower.length] ?? 0;\n}\n\n/**\n * Find the closest match from a list of candidates using Levenshtein distance.\n * Returns null if no match is found within the threshold.\n *\n * @param input - The input string to match\n * @param candidates - List of valid candidates\n * @param maxDistance - Maximum Levenshtein distance for a suggestion (default: 3)\n */\nexport function suggestClosestMatch(\n\tinput: string,\n\tcandidates: readonly string[],\n\tmaxDistance = 3,\n): string | null {\n\tif (!input || candidates.length === 0) return null;\n\n\tconst matches = candidates\n\t\t.map((c) => ({ name: c, distance: levenshtein(input, c) }))\n\t\t.filter((m) => m.distance <= maxDistance && m.distance > 0)\n\t\t.sort((a, b) => a.distance - b.distance);\n\n\treturn matches[0]?.name ?? null;\n}\n\n/**\n * Enhance an error message with a suggestion if the error is about an unknown table/column.\n *\n * @param error - The error message\n * @param tableNames - Available table names\n * @param columnNames - Available column names (from current table context)\n */\nexport function enhanceErrorWithSuggestion(\n\terror: string,\n\ttableNames: readonly string[],\n\tcolumnNames: readonly string[] = [],\n): string {\n\t// Check for unknown table error\n\tconst tableMatch = error.match(/Unknown table:\\s*(\\w+)/i);\n\tif (tableMatch?.[1]) {\n\t\tconst unknownTable = tableMatch[1];\n\t\tconst suggestion = suggestClosestMatch(unknownTable, tableNames);\n\t\tif (suggestion) {\n\t\t\treturn `${error}\\n\\nDid you mean '${suggestion}'?`;\n\t\t}\n\t}\n\n\t// Check for unknown column error (if we have column context)\n\tif (columnNames.length > 0) {\n\t\tconst columnMatch = error.match(/Unknown column:\\s*(\\w+)/i);\n\t\tif (columnMatch?.[1]) {\n\t\t\tconst unknownColumn = columnMatch[1];\n\t\t\tconst suggestion = suggestClosestMatch(unknownColumn, columnNames);\n\t\t\tif (suggestion) {\n\t\t\t\treturn `${error}\\n\\nDid you mean '${suggestion}'?`;\n\t\t\t}\n\t\t}\n\t}\n\n\treturn error;\n}\n\n/**\n * Completion suggestion\n */\nexport interface CompletionSuggestion {\n\t/** Text to insert */\n\ttext: string;\n\t/** Display label (may differ from text) */\n\tlabel: string;\n\t/** Type of completion (table, column, keyword, command) */\n\ttype: 'table' | 'column' | 'keyword' | 'command' | 'relation';\n\t/** Optional description */\n\tdescription?: string;\n}\n\n/**\n * Dot commands for completion\n */\nconst DOT_COMMANDS: CompletionSuggestion[] = [\n\t{ text: '.help', label: '.help', type: 'command', description: 'Show help' },\n\t{\n\t\ttext: '.tables',\n\t\tlabel: '.tables',\n\t\ttype: 'command',\n\t\tdescription: 'List tables',\n\t},\n\t{\n\t\ttext: '.schema',\n\t\tlabel: '.schema',\n\t\ttype: 'command',\n\t\tdescription: 'Show schema',\n\t},\n\t{\n\t\ttext: '.relations',\n\t\tlabel: '.relations',\n\t\ttype: 'command',\n\t\tdescription: 'List relations',\n\t},\n\t{\n\t\ttext: '.history',\n\t\tlabel: '.history',\n\t\ttype: 'command',\n\t\tdescription: 'Show history',\n\t},\n\t{\n\t\ttext: '.sql',\n\t\tlabel: '.sql',\n\t\ttype: 'command',\n\t\tdescription: 'Switch to SQL mode',\n\t},\n\t{\n\t\ttext: '.natural',\n\t\tlabel: '.natural',\n\t\ttype: 'command',\n\t\tdescription: 'Switch to natural mode',\n\t},\n\t{\n\t\ttext: '.split',\n\t\tlabel: '.split',\n\t\ttype: 'command',\n\t\tdescription: 'Toggle split view',\n\t},\n\t{\n\t\ttext: '.clear',\n\t\tlabel: '.clear',\n\t\ttype: 'command',\n\t\tdescription: 'Clear screen',\n\t},\n\t{\n\t\ttext: '.exit',\n\t\tlabel: '.exit',\n\t\ttype: 'command',\n\t\tdescription: 'Exit REPL',\n\t},\n\t{\n\t\ttext: '.quit',\n\t\tlabel: '.quit',\n\t\ttype: 'command',\n\t\tdescription: 'Exit REPL (alias)',\n\t},\n\t{\n\t\ttext: '.aliasing',\n\t\tlabel: '.aliasing',\n\t\ttype: 'command',\n\t\tdescription: 'Toggle column aliasing mode',\n\t},\n\t{\n\t\ttext: '.strategy',\n\t\tlabel: '.strategy',\n\t\ttype: 'command',\n\t\tdescription: 'Show/set include strategy',\n\t},\n\t{\n\t\ttext: '.dialect',\n\t\tlabel: '.dialect',\n\t\ttype: 'command',\n\t\tdescription: 'Show/set SQL dialect',\n\t},\n\t{\n\t\ttext: '.use',\n\t\tlabel: '.use',\n\t\ttype: 'command',\n\t\tdescription: 'Set schema for queries',\n\t},\n\t{\n\t\ttext: '.exec',\n\t\tlabel: '.exec',\n\t\ttype: 'command',\n\t\tdescription: 'Toggle execution mode',\n\t},\n\t{\n\t\ttext: '.import',\n\t\tlabel: '.import',\n\t\ttype: 'command',\n\t\tdescription: 'Execute SQL file',\n\t},\n\t{\n\t\ttext: '.parse',\n\t\tlabel: '.parse',\n\t\ttype: 'command',\n\t\tdescription: 'Toggle parse tree (AST) output',\n\t},\n\t{\n\t\ttext: '.explain',\n\t\tlabel: '.explain',\n\t\ttype: 'command',\n\t\tdescription: 'Toggle EXPLAIN prefix for queries',\n\t},\n\t{\n\t\ttext: '.table',\n\t\tlabel: '.table',\n\t\ttype: 'command',\n\t\tdescription: 'Configure table display options',\n\t},\n];\n\n/**\n * Natural query keywords for completion\n */\nconst KEYWORDS: CompletionSuggestion[] = [\n\t// Pipe operator (NQL syntax)\n\t{\n\t\ttext: '|',\n\t\tlabel: '|',\n\t\ttype: 'keyword',\n\t\tdescription: 'Pipe to next clause',\n\t},\n\t{\n\t\ttext: 'where',\n\t\tlabel: 'where',\n\t\ttype: 'keyword',\n\t\tdescription: 'Filter results',\n\t},\n\t{\n\t\ttext: 'with',\n\t\tlabel: 'with',\n\t\ttype: 'keyword',\n\t\tdescription: 'Include related data',\n\t},\n\t{\n\t\ttext: 'select',\n\t\tlabel: 'select',\n\t\ttype: 'keyword',\n\t\tdescription: 'Select columns',\n\t},\n\t{\n\t\ttext: 'group',\n\t\tlabel: 'group',\n\t\ttype: 'keyword',\n\t\tdescription: 'Group by columns',\n\t},\n\t{ text: 'limit', label: 'limit', type: 'keyword', description: 'Limit rows' },\n\t{\n\t\ttext: 'offset',\n\t\tlabel: 'offset',\n\t\ttype: 'keyword',\n\t\tdescription: 'Skip rows',\n\t},\n\t{\n\t\ttext: 'order',\n\t\tlabel: 'order',\n\t\ttype: 'keyword',\n\t\tdescription: 'Sort results',\n\t},\n\t{\n\t\ttext: 'asc',\n\t\tlabel: 'asc',\n\t\ttype: 'keyword',\n\t\tdescription: 'Ascending order',\n\t},\n\t{\n\t\ttext: 'desc',\n\t\tlabel: 'desc',\n\t\ttype: 'keyword',\n\t\tdescription: 'Descending order',\n\t},\n\t{ text: 'and', label: 'and', type: 'keyword', description: 'Logical AND' },\n\t{ text: 'or', label: 'or', type: 'keyword', description: 'Logical OR' },\n\t{\n\t\ttext: 'true',\n\t\tlabel: 'true',\n\t\ttype: 'keyword',\n\t\tdescription: 'Boolean true',\n\t},\n\t{\n\t\ttext: 'false',\n\t\tlabel: 'false',\n\t\ttype: 'keyword',\n\t\tdescription: 'Boolean false',\n\t},\n\t{ text: 'null', label: 'null', type: 'keyword', description: 'Null value' },\n\t// Range operators (PostgreSQL)\n\t{\n\t\ttext: 'overlaps',\n\t\tlabel: 'overlaps',\n\t\ttype: 'keyword',\n\t\tdescription: 'Range overlap operator (&&)',\n\t},\n\t{\n\t\ttext: 'contains',\n\t\tlabel: 'contains',\n\t\ttype: 'keyword',\n\t\tdescription: 'Range contains element (@>)',\n\t},\n\t{\n\t\ttext: 'containedBy',\n\t\tlabel: 'containedBy',\n\t\ttype: 'keyword',\n\t\tdescription: 'Range within another (<@)',\n\t},\n\t// String operators\n\t{\n\t\ttext: 'like',\n\t\tlabel: 'like',\n\t\ttype: 'keyword',\n\t\tdescription: 'Pattern matching (LIKE)',\n\t},\n\t// Set operators\n\t{\n\t\ttext: 'in',\n\t\tlabel: 'in',\n\t\ttype: 'keyword',\n\t\tdescription: 'Value in set (IN)',\n\t},\n];\n\n/**\n * Completion provider for the REPL\n */\nexport class CompletionProvider {\n\tprivate tables: CompletionSuggestion[] = [];\n\tprivate columns: Map<string, CompletionSuggestion[]> = new Map();\n\tprivate relations: CompletionSuggestion[] = [];\n\t// Relations per table - key is table name, value is relations for that table\n\tprivate tableRelations: Map<string, CompletionSuggestion[]> = new Map();\n\n\tconstructor(schema: LoadedSchema) {\n\t\tthis.initializeFromSchema(schema);\n\t}\n\n\t/**\n\t * Initialize completions from schema\n\t * ARCH-005: Uses schema.model (ModelIR) directly\n\t */\n\tprivate initializeFromSchema(schema: LoadedSchema): void {\n\t\t// Build table completions from ModelIR\n\t\tfor (const [tableName, table] of schema.model.tables) {\n\t\t\tthis.tables.push({\n\t\t\t\ttext: tableName,\n\t\t\t\tlabel: tableName,\n\t\t\t\ttype: 'table',\n\t\t\t\tdescription: 'Table',\n\t\t\t});\n\n\t\t\t// Build column completions for this table\n\t\t\tconst tableColumns: CompletionSuggestion[] = [];\n\t\t\tfor (const col of table.columns) {\n\t\t\t\ttableColumns.push({\n\t\t\t\t\ttext: col.name,\n\t\t\t\t\tlabel: col.name,\n\t\t\t\t\ttype: 'column',\n\t\t\t\t\tdescription: col.type,\n\t\t\t\t});\n\t\t\t}\n\t\t\tthis.columns.set(tableName, tableColumns);\n\t\t}\n\n\t\t// Build relation completions from ModelIR\n\t\tfor (const [relName] of schema.model.relations) {\n\t\t\tthis.relations.push({\n\t\t\t\ttext: relName,\n\t\t\t\tlabel: relName,\n\t\t\t\ttype: 'relation',\n\t\t\t\tdescription: 'Relation',\n\t\t\t});\n\n\t\t\t// Also build per-table relations for context-aware completion\n\t\t\t// Relations are keyed as \"table.relationName\" (qualified format)\n\t\t\tif (relName.includes('.')) {\n\t\t\t\tconst [tableName, ...relParts] = relName.split('.');\n\t\t\t\t// tableName is guaranteed to exist because relName.includes('.')\n\t\t\t\tif (!tableName) continue;\n\t\t\t\tconst simpleRelName = relParts.join('.');\n\t\t\t\tif (!this.tableRelations.has(tableName)) {\n\t\t\t\t\tthis.tableRelations.set(tableName, []);\n\t\t\t\t}\n\t\t\t\tthis.tableRelations.get(tableName)?.push({\n\t\t\t\t\ttext: simpleRelName,\n\t\t\t\t\tlabel: simpleRelName,\n\t\t\t\t\ttype: 'relation',\n\t\t\t\t\tdescription: `Relation from ${tableName}`,\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * Get completions for the given input\n\t */\n\tcomplete(input: string): CompletionSuggestion[] {\n\t\tconst trimmed = input.trim();\n\n\t\t// Empty input - suggest tables or dot commands\n\t\tif (!trimmed) {\n\t\t\treturn [...this.tables, ...DOT_COMMANDS.slice(0, 5)];\n\t\t}\n\n\t\t// Dot commands\n\t\tif (trimmed.startsWith('.')) {\n\t\t\treturn this.filterSuggestions(DOT_COMMANDS, trimmed);\n\t\t}\n\n\t\t// Parse context from input (use original to preserve trailing space)\n\t\tconst context = this.parseContext(input);\n\n\t\tswitch (context.expecting) {\n\t\t\tcase 'table':\n\t\t\t\treturn this.filterSuggestions(this.tables, context.partial);\n\n\t\t\tcase 'keyword':\n\t\t\t\treturn this.filterSuggestions(KEYWORDS, context.partial);\n\n\t\t\tcase 'column': {\n\t\t\t\tconst tableCols = this.columns.get(context.table ?? '') ?? [];\n\t\t\t\treturn this.filterSuggestions(\n\t\t\t\t\t[...tableCols, ...KEYWORDS],\n\t\t\t\t\tcontext.partial,\n\t\t\t\t);\n\t\t\t}\n\n\t\t\tcase 'relation': {\n\t\t\t\t// Use table-specific relations if table is known\n\t\t\t\tconst tableRels = context.table\n\t\t\t\t\t? (this.tableRelations.get(context.table) ?? [])\n\t\t\t\t\t: [];\n\t\t\t\t// Fall back to all relations if no table-specific ones found\n\t\t\t\tconst relSuggestions =\n\t\t\t\t\ttableRels.length > 0 ? tableRels : this.relations;\n\t\t\t\treturn this.filterSuggestions(relSuggestions, context.partial);\n\t\t\t}\n\n\t\t\tcase 'value':\n\t\t\t\t// For values, only suggest boolean/null keywords\n\t\t\t\treturn this.filterSuggestions(\n\t\t\t\t\tKEYWORDS.filter((k) => ['true', 'false', 'null'].includes(k.text)),\n\t\t\t\t\tcontext.partial,\n\t\t\t\t);\n\n\t\t\tdefault:\n\t\t\t\treturn this.filterSuggestions(\n\t\t\t\t\t[...this.tables, ...KEYWORDS],\n\t\t\t\t\tcontext.partial,\n\t\t\t\t);\n\t\t}\n\t}\n\n\t/**\n\t * Parse input to determine completion context\n\t */\n\tprivate parseContext(input: string): CompletionContext {\n\t\t// Handle trailing space - user wants completions for NEXT word\n\t\tconst endsWithSpace = input.endsWith(' ');\n\t\tconst words = input\n\t\t\t.toLowerCase()\n\t\t\t.split(/\\s+/)\n\t\t\t.filter((w) => w.length > 0);\n\n\t\t// No words - expect table\n\t\tif (words.length === 0) {\n\t\t\treturn { expecting: 'table', partial: '' };\n\t\t}\n\n\t\t// If ends with space, we're looking for next word (partial is empty)\n\t\t// Otherwise, we're completing the last word\n\t\tconst partial = endsWithSpace ? '' : (words[words.length - 1] ?? '');\n\t\tconst contextWords = endsWithSpace ? words : words.slice(0, -1);\n\t\tconst lastContextWord = contextWords[contextWords.length - 1];\n\n\t\t// After 'with' - expect relation (with table context)\n\t\tif (lastContextWord === 'with') {\n\t\t\tconst table = this.findTableInInput(words);\n\t\t\treturn { expecting: 'relation', partial, table };\n\t\t}\n\n\t\t// After 'where' - expect column\n\t\tif (lastContextWord === 'where') {\n\t\t\tconst table = this.findTableInInput(words);\n\t\t\treturn { expecting: 'column', partial, table };\n\t\t}\n\n\t\t// After 'and' or 'or' - expect column\n\t\tif (lastContextWord === 'and' || lastContextWord === 'or') {\n\t\t\tconst table = this.findTableInInput(words);\n\t\t\treturn { expecting: 'column', partial, table };\n\t\t}\n\n\t\t// After operator (=, !=, >, <, etc.) - expect value\n\t\tif (lastContextWord && /^[=!<>]+$/.test(lastContextWord)) {\n\t\t\treturn { expecting: 'value', partial };\n\t\t}\n\n\t\t// First word position (no context words) - expect table\n\t\tif (contextWords.length === 0) {\n\t\t\treturn { expecting: 'table', partial };\n\t\t}\n\n\t\t// After table name - expect keyword\n\t\tconst firstWordIsTable = this.tables.some(\n\t\t\t(t) => t.text.toLowerCase() === contextWords[0],\n\t\t);\n\t\tif (firstWordIsTable) {\n\t\t\treturn {\n\t\t\t\texpecting: 'keyword',\n\t\t\t\tpartial,\n\t\t\t\ttable: contextWords[0],\n\t\t\t};\n\t\t}\n\n\t\treturn { expecting: 'any', partial };\n\t}\n\n\t/**\n\t * Find table name in the input words\n\t */\n\tprivate findTableInInput(words: string[]): string | undefined {\n\t\tfor (const word of words) {\n\t\t\tif (this.tables.some((t) => t.text.toLowerCase() === word)) {\n\t\t\t\treturn word;\n\t\t\t}\n\t\t}\n\t\treturn undefined;\n\t}\n\n\t/**\n\t * Filter suggestions by prefix\n\t */\n\tprivate filterSuggestions(\n\t\tsuggestions: CompletionSuggestion[],\n\t\tprefix: string,\n\t): CompletionSuggestion[] {\n\t\tif (!prefix) return suggestions;\n\n\t\tconst lower = prefix.toLowerCase();\n\t\treturn suggestions.filter(\n\t\t\t(s) =>\n\t\t\t\ts.text.toLowerCase().startsWith(lower) ||\n\t\t\t\ts.label.toLowerCase().includes(lower),\n\t\t);\n\t}\n\n\t/**\n\t * Get table names for direct access\n\t */\n\tgetTableNames(): string[] {\n\t\treturn this.tables.map((t) => t.text);\n\t}\n\n\t/**\n\t * Get column names for a table\n\t */\n\tgetColumnNames(tableName: string): string[] {\n\t\treturn (this.columns.get(tableName) ?? []).map((c) => c.text);\n\t}\n\n\t/**\n\t * Get relation names\n\t */\n\tgetRelationNames(): string[] {\n\t\treturn this.relations.map((r) => r.text);\n\t}\n\n\t/**\n\t * Apply a completion to the current input.\n\t * Returns the new input text with the partial word replaced by the completion.\n\t *\n\t * @param input - Current input text\n\t * @param completionText - The completion text to insert\n\t * @returns New input text with completion applied\n\t */\n\tapplyCompletion(input: string, completionText: string): string {\n\t\tconst endsWithSpace = input.endsWith(' ');\n\n\t\t// If input ends with space, just append the completion\n\t\tif (endsWithSpace) {\n\t\t\treturn input + completionText;\n\t\t}\n\n\t\t// Otherwise, find and replace the partial word\n\t\tconst words = input.split(/\\s+/);\n\t\tif (words.length === 0) {\n\t\t\treturn completionText;\n\t\t}\n\n\t\t// Replace the last word (partial) with the completion\n\t\twords[words.length - 1] = completionText;\n\t\treturn words.join(' ');\n\t}\n}\n\n/**\n * Context for completion\n */\ninterface CompletionContext {\n\texpecting: 'table' | 'column' | 'keyword' | 'relation' | 'value' | 'any';\n\tpartial: string;\n\ttable?: string | undefined;\n}\n\n/**\n * Format completions for display\n */\nexport function formatCompletions(\n\tsuggestions: CompletionSuggestion[],\n\tmaxItems = 10,\n): string {\n\tif (suggestions.length === 0) return '';\n\n\tconst items = suggestions.slice(0, maxItems);\n\tconst typeColors: Record<string, string> = {\n\t\ttable: '🗃️',\n\t\tcolumn: '📋',\n\t\tkeyword: '🔑',\n\t\tcommand: '⚡',\n\t\trelation: '🔗',\n\t};\n\n\treturn items.map((s) => `${typeColors[s.type] ?? ''} ${s.label}`).join(' ');\n}\n","/**\n * CLI-020: Mode Escape Logic\n *\n * Handles the symmetric mode escape behavior with ! prefix:\n * - Natural mode (default): input is natural query, ! escapes to raw SQL\n * - SQL mode (.sql): input is raw SQL, ! escapes to natural query\n */\n\nimport type { QueryMode } from './types.js';\n\nexport interface ModeEscapeResult {\n\t/** The query content without the escape prefix */\n\tcontent: string;\n\t/** Whether the input should be treated as raw SQL */\n\tisRawSql: boolean;\n\t/** Whether the ! escape prefix was used */\n\tescaped: boolean;\n}\n\n/**\n * Parse input and determine query type based on mode and escape prefix.\n *\n * @param input - The raw user input\n * @param mode - Current query mode ('natural' or 'sql')\n * @returns Parsed result with content and query type\n */\nexport function parseInputMode(\n\tinput: string,\n\tmode: QueryMode,\n): ModeEscapeResult {\n\tconst trimmed = input.trim();\n\tconst escaped = trimmed.startsWith('!');\n\tconst content = escaped ? trimmed.slice(1).trim() : trimmed;\n\n\t// Mode escape logic:\n\t// - Natural mode + no escape = natural query\n\t// - Natural mode + escape (!) = raw SQL\n\t// - SQL mode + no escape = raw SQL\n\t// - SQL mode + escape (!) = natural query\n\tconst isRawSql =\n\t\t(mode === 'natural' && escaped) || (mode === 'sql' && !escaped);\n\n\treturn { content, isRawSql, escaped };\n}\n\n/**\n * Get the warning message for the current mode and escape state.\n */\nexport function getModeWarning(mode: QueryMode, escaped: boolean): string {\n\tif (mode === 'sql' && !escaped) {\n\t\treturn 'SQL mode: direct SQL';\n\t}\n\tif (mode === 'natural' && escaped) {\n\t\treturn 'Escaped to raw SQL with !';\n\t}\n\tif (mode === 'sql' && escaped) {\n\t\treturn 'Escaped to natural query with !';\n\t}\n\treturn 'Natural query mode';\n}\n","/**\n * NQL Executor - Execute NQL queries via @dbsp/nql + ORM\n *\n * Flow:\n * NQL string → parse() → compile() → IntentAST → plan()/compileX() → SQL → execute\n *\n * Part of NQLM (NQL CLI Migration) - Block 2\n */\n\nimport {\n\textractPseudoColumnKeywords,\n\ttype ModelIR,\n\ttype MutationIntent,\n\ttype PlanReport,\n\tplan,\n\ttype QueryIntent,\n} from '@dbsp/core';\nimport { type CompileResult, compile as compileNql } from '@dbsp/nql';\n\n/**\n * Extract IntentSummary from NQL compilation result\n */\nfunction extractIntentSummary(\n\tcompiled: CompileResult,\n\tintentType: IntentSummary['type'],\n): IntentSummary {\n\tif (compiled.query) {\n\t\tconst q = compiled.query;\n\t\treturn {\n\t\t\ttype: 'query',\n\t\t\ttable: q.from,\n\t\t\twith: (q.include ?? []).map((i) => i.relation),\n\t\t\thasWhere: !!q.where,\n\t\t\thasGroupBy: !!(q.groupBy && q.groupBy.length > 0),\n\t\t\thasOrderBy: !!(q.orderBy && q.orderBy.length > 0),\n\t\t\tctes: [], // CTEs are at program level, not in CompileResult\n\t\t};\n\t}\n\n\tif (compiled.mutation) {\n\t\tconst m = compiled.mutation;\n\t\treturn {\n\t\t\ttype: intentType,\n\t\t\ttable: m.table,\n\t\t\twith: [],\n\t\t\thasWhere: 'where' in m && !!m.where,\n\t\t\thasGroupBy: false,\n\t\t\thasOrderBy: false,\n\t\t\tctes: [],\n\t\t};\n\t}\n\n\tif (compiled.setOperation) {\n\t\tconst setOp = compiled.setOperation;\n\t\t// Narrow to QueryIntent (which has `from`/`where`) vs nested SetOperationIntent\n\t\tconst leftLeaf = 'from' in setOp.left ? setOp.left : null;\n\t\treturn {\n\t\t\ttype: 'setOperation',\n\t\t\ttable: leftLeaf?.from ?? '',\n\t\t\twith: [],\n\t\t\thasWhere: leftLeaf != null ? !!leftLeaf.where : false,\n\t\t\thasGroupBy: false,\n\t\t\thasOrderBy: false,\n\t\t\tctes: [],\n\t\t};\n\t}\n\n\t// Fallback for edge cases\n\treturn {\n\t\ttype: intentType,\n\t\ttable: '',\n\t\twith: [],\n\t\thasWhere: false,\n\t\thasGroupBy: false,\n\t\thasOrderBy: false,\n\t\tctes: [],\n\t};\n}\n\n/**\n * Error thrown when NQL parsing fails\n */\nexport class NqlParseError extends Error {\n\tconstructor(\n\t\tpublic readonly errors: Array<{ code: string; message: string }>,\n\t) {\n\t\tconst messages = errors.map((e) => e.message).join('\\n');\n\t\tsuper(`NQL parse error:\\n${messages}`);\n\t\tthis.name = 'NqlParseError';\n\t}\n}\n\n/**\n * Error thrown when NQL compilation fails\n */\nexport class NqlCompileError extends Error {\n\tconstructor(message: string) {\n\t\tsuper(`NQL compile error: ${message}`);\n\t\tthis.name = 'NqlCompileError';\n\t}\n}\n\n/**\n * Result of NQL execution\n */\nexport interface NqlExecutionResult {\n\t/** Compiled SQL query */\n\tsql: string;\n\t/** Query parameters */\n\tparams: readonly unknown[];\n\t/** Query result rows */\n\trows: readonly Record<string, unknown>[];\n\t/** Number of affected rows (for mutations) */\n\taffectedRows?: number;\n\t/** Intent type that was executed */\n\tintentType: 'query' | 'insert' | 'update' | 'delete' | 'upsert';\n}\n\n/**\n * Simplified intent summary for assertions\n */\nexport interface IntentSummary {\n\t/** Intent type */\n\ttype: 'query' | 'insert' | 'update' | 'delete' | 'upsert' | 'setOperation';\n\t/** Main table name */\n\ttable: string;\n\t/** Relations joined via `with` keyword */\n\twith: string[];\n\t/** Has WHERE clause */\n\thasWhere: boolean;\n\t/** Has GROUP BY clause */\n\thasGroupBy: boolean;\n\t/** Has ORDER BY clause */\n\thasOrderBy: boolean;\n\t/** CTE names (let bindings) */\n\tctes: string[];\n}\n\n/**\n * Result of NQL compilation (SQL only, no execution)\n */\nexport interface NqlCompileOnlyResult {\n\t/** Compiled SQL query */\n\tsql: string;\n\t/** Query parameters */\n\tparams: readonly unknown[];\n\t/** Intent type */\n\tintentType:\n\t\t| 'query'\n\t\t| 'insert'\n\t\t| 'update'\n\t\t| 'delete'\n\t\t| 'upsert'\n\t\t| 'setOperation';\n\t/** Intent summary for assertions */\n\tintent: IntentSummary;\n\t/** Full plan report (only for queries — mutations don't have one) */\n\tplanReport?: PlanReport;\n}\n\n/**\n * Options for NQL compilation\n */\nexport interface NqlCompileOptions {\n\t/** Database schema name for schema-scoped queries */\n\tschemaName?: string;\n\t/** DB column casing — describes what casing the database uses (default: 'preserve') */\n\tdbCasing?: 'snake_case' | 'camelCase' | 'preserve';\n}\n\n/**\n * Compile NQL to SQL without executing\n *\n * Useful for REPL preview and testing. Creates its own CompileOnlyAdapter internally.\n *\n * @param nql - NQL query string\n * @param model - ModelIR schema\n * @param options - Optional compilation options (dialect, schemaName)\n * @returns Compiled SQL and parameters\n *\n * @example\n * ```typescript\n * const result = compileNqlToSql(\n * 'users | where active = true | limit 10',\n * model\n * );\n * console.log(result.sql); // SELECT * FROM \"users\" WHERE \"active\" = $1 LIMIT 10\n * console.log(result.params); // [true]\n * ```\n */\nexport async function compileNqlToSql(\n\tnql: string,\n\tmodel: ModelIR,\n\toptions?: NqlCompileOptions,\n): Promise<NqlCompileOnlyResult> {\n\t// Lazy-load adapter to avoid loading pgsql-parser at module init time\n\t// pgsql-parser has issues with ESM dynamic requires, so we defer its import\n\t// until it's actually needed (CLI-PGSQL-LAZY-LOAD)\n\tconst {\n\t\tcreatePgsqlCompileOnlyAdapter,\n\t\tcompileSetOperation,\n\t\tcreateLeafCompileFn,\n\t} = await import('@dbsp/adapter-pgsql');\n\n\t// Create compile-only adapter for SQL generation (no DB connection needed)\n\tconst adapter = createPgsqlCompileOnlyAdapter({\n\t\t...(options?.schemaName !== undefined && {\n\t\t\tschemaName: options.schemaName,\n\t\t}),\n\t\t...(options?.dbCasing !== undefined && {\n\t\t\tdbCasing: options.dbCasing,\n\t\t}),\n\t});\n\n\t// 1. Parse and compile NQL to IntentAST\n\tconst compiled = compileNqlToIntent(nql, model);\n\n\t// 2. Compile IntentAST to SQL using adapter\n\tif (compiled.query) {\n\t\tconst queryIntent = compiled.query;\n\t\t// Read capabilities from adapter (always available on BaseAdapter)\n\t\tconst planReport = plan(queryIntent, model, {\n\t\t\tdialectCapabilities: adapter.dialectCapabilities,\n\t\t});\n\t\tconst compiledQuery = adapter.compile(planReport, { model });\n\n\t\treturn {\n\t\t\tsql: compiledQuery.sql,\n\t\t\tparams: compiledQuery.parameters,\n\t\t\tintentType: 'query',\n\t\t\tintent: extractIntentSummary(compiled, 'query'),\n\t\t\tplanReport,\n\t\t};\n\t}\n\n\tif (compiled.setOperation) {\n\t\tconst compileFn = createLeafCompileFn(adapter, model, plan);\n\t\tconst result = compileSetOperation(compiled.setOperation, compileFn);\n\n\t\treturn {\n\t\t\tsql: result.sql,\n\t\t\tparams: result.parameters,\n\t\t\tintentType: 'setOperation',\n\t\t\tintent: extractIntentSummary(compiled, 'setOperation'),\n\t\t};\n\t}\n\n\tif (compiled.mutation) {\n\t\tconst mutation = compiled.mutation;\n\n\t\tswitch (mutation.type) {\n\t\t\tcase 'insert': {\n\t\t\t\tconst compiledQuery = adapter.compileInsert(mutation);\n\t\t\t\treturn {\n\t\t\t\t\tsql: compiledQuery.sql,\n\t\t\t\t\tparams: compiledQuery.parameters,\n\t\t\t\t\tintentType: 'insert',\n\t\t\t\t\tintent: extractIntentSummary(compiled, 'insert'),\n\t\t\t\t};\n\t\t\t}\n\t\t\tcase 'update': {\n\t\t\t\tconst compiledQuery = adapter.compileUpdate(mutation);\n\t\t\t\treturn {\n\t\t\t\t\tsql: compiledQuery.sql,\n\t\t\t\t\tparams: compiledQuery.parameters,\n\t\t\t\t\tintentType: 'update',\n\t\t\t\t\tintent: extractIntentSummary(compiled, 'update'),\n\t\t\t\t};\n\t\t\t}\n\t\t\tcase 'delete': {\n\t\t\t\tconst compiledQuery = adapter.compileDelete(mutation);\n\t\t\t\treturn {\n\t\t\t\t\tsql: compiledQuery.sql,\n\t\t\t\t\tparams: compiledQuery.parameters,\n\t\t\t\t\tintentType: 'delete',\n\t\t\t\t\tintent: extractIntentSummary(compiled, 'delete'),\n\t\t\t\t};\n\t\t\t}\n\t\t\tcase 'upsert': {\n\t\t\t\tconst compiledQuery = adapter.compileUpsert(mutation);\n\t\t\t\treturn {\n\t\t\t\t\tsql: compiledQuery.sql,\n\t\t\t\t\tparams: compiledQuery.parameters,\n\t\t\t\t\tintentType: 'upsert',\n\t\t\t\t\tintent: extractIntentSummary(compiled, 'upsert'),\n\t\t\t\t};\n\t\t\t}\n\t\t\tdefault:\n\t\t\t\tthrow new NqlCompileError(\n\t\t\t\t\t`Unknown mutation type: ${JSON.stringify(mutation)}`,\n\t\t\t\t);\n\t\t}\n\t}\n\n\tthrow new NqlCompileError(\n\t\t'NQL compiled to neither query, mutation, nor set operation',\n\t);\n}\n\n/**\n * Internal: Parse and compile NQL to IntentAST\n */\nfunction compileNqlToIntent(nql: string, _model: ModelIR): CompileResult {\n\tconst compilerOptions = extractPseudoColumnKeywords(_model);\n\tconst result = compileNql(nql, _model, undefined, compilerOptions);\n\n\tif (!result.success) {\n\t\tthrow new NqlParseError(result.errors);\n\t}\n\n\tif (!result.ast) {\n\t\tthrow new NqlCompileError('Compilation succeeded but no AST produced');\n\t}\n\n\treturn result.ast;\n}\n\n/**\n * Check if a string is valid NQL (does not start with REPL commands)\n *\n * REPL commands: .tables, .schema, !sql, etc.\n */\nexport function isNqlQuery(input: string): boolean {\n\tconst trimmed = input.trim();\n\n\t// REPL commands start with . or !\n\tif (trimmed.startsWith('.') || trimmed.startsWith('!')) {\n\t\treturn false;\n\t}\n\n\t// Empty input\n\tif (!trimmed) {\n\t\treturn false;\n\t}\n\n\treturn true;\n}\n\n/**\n * Get the IntentAST from NQL without compiling to SQL\n *\n * Useful for debugging and introspection.\n */\nexport function getNqlIntent(\n\tnql: string,\n\tmodel: ModelIR,\n): { query: QueryIntent | undefined; mutation: MutationIntent | undefined } {\n\tconst compiled = compileNqlToIntent(nql, model);\n\treturn {\n\t\tquery: compiled.query ? compiled.query : undefined,\n\t\tmutation: compiled.mutation,\n\t};\n}\n","/**\n * ReplEngine — Pure business logic for the REPL.\n *\n * Owns all REPL state and logic, emits events for UI consumption.\n * No React/Ink dependency — usable by interactive REPL, batch mode, tests.\n */\n\nimport type { ModelIR } from '@dbsp/core';\nimport {\n\tconfig as appConfig,\n\tisValidTableOption,\n\tTABLE_OPTIONS,\n} from '../../config.js';\nimport {\n\tCompletionProvider,\n\tenhanceErrorWithSuggestion,\n} from '../completion.js';\nimport {\n\tcreateDbConnection,\n\ttype DbConnection,\n\tgetDatabaseName,\n} from '../db-connection.js';\nimport { type BatchState, processDotCommand } from '../dot-commands.js';\nimport { getModeWarning, parseInputMode } from '../mode-escape.js';\nimport { compileNqlToSql } from '../nql-executor.js';\nimport type { QueryResult } from '../types.js';\nimport type {\n\tEngineConfig,\n\tEngineEvent,\n\tEngineEventHandler,\n\tEngineState,\n\tOutputLayout,\n\tPanelView,\n\tPlanVerbosity,\n} from './engine-types.js';\n\n/**\n * Dialect → available include strategies mapping.\n * Compact version of the STRATEGY_INFO/DIALECT_INFO from the old index.tsx.\n */\nconst DIALECT_STRATEGIES: Record<string, readonly string[]> = {\n\tpostgresql: ['auto', 'join', 'subquery', 'cte', 'lateral', 'json_agg'],\n\tmysql: ['auto', 'join', 'subquery', 'cte', 'json_agg'],\n\tsqlite: ['auto', 'join', 'subquery', 'cte'],\n\tmssql: ['auto', 'join', 'subquery', 'cte'],\n\tduckdb: ['auto', 'join', 'subquery', 'cte', 'json_agg'],\n};\n\n/**\n * Check if the last character of the input is inside a single-quoted string literal.\n * NQL uses SQL-style single quotes; escaped quotes are '' (doubled).\n */\nexport function isInsideStringLiteral(input: string): boolean {\n\tlet inString = false;\n\tfor (let i = 0; i < input.length - 1; i++) {\n\t\tif (input[i] === \"'\") {\n\t\t\tif (inString && i + 1 < input.length - 1 && input[i + 1] === \"'\") {\n\t\t\t\ti++; // skip escaped quote ''\n\t\t\t} else {\n\t\t\t\tinString = !inString;\n\t\t\t}\n\t\t}\n\t}\n\treturn inString;\n}\n\ntype TableConfigKey = keyof typeof TABLE_OPTIONS;\n\ntype TableOptionHandler = {\n\t/** The config field name passed to appConfig.updateTable / TABLE_OPTIONS / isValidTableOption. */\n\tfield: TableConfigKey;\n\t/** Human-readable label printed in success/error messages. */\n\tlabel: string;\n\t/** Parses the raw string argument into the validated value type. */\n\tparse: (raw: string) => string | number;\n};\n\n// Keyed by every command word the user can type — aliases share the same handler instance.\nconst borderHandler: TableOptionHandler = {\n\tfield: 'borderStyle',\n\tlabel: 'borders',\n\t// Border style values (none / outline / rounded / etc.) are all-lowercase —\n\t// normalize input so e.g. `.table borders NONE` matches.\n\tparse: (s) => s.toLowerCase(),\n};\n\nconst headerHandler: TableOptionHandler = {\n\tfield: 'headerFormatter',\n\tlabel: 'headers',\n\t// Header-formatter values include camelCase (capitalCase / snakeCase / camelCase) —\n\t// preserve original case so the user-typed value matches TABLE_OPTIONS exactly.\n\tparse: (s) => s,\n};\n\n// Keyed by every command word the user can type — aliases share the same handler instance.\nconst TABLE_OPTION_HANDLERS: Record<string, TableOptionHandler> = {\n\tborders: borderHandler,\n\tborder: borderHandler,\n\toverflow: {\n\t\tfield: 'overflow',\n\t\tlabel: 'overflow',\n\t\t// Overflow values (truncate / wrap) are all-lowercase — normalize input.\n\t\tparse: (s) => s.toLowerCase(),\n\t},\n\theaders: headerHandler,\n\theader: headerHandler,\n\tpadding: {\n\t\tfield: 'padding',\n\t\tlabel: 'padding',\n\t\tparse: (s) => Number.parseInt(s, 10),\n\t},\n};\n\nexport class ReplEngine {\n\tprivate state: EngineState;\n\tprivate listeners: EngineEventHandler[] = [];\n\tprivate schema: EngineConfig['schema'];\n\tprivate schemaPath: string;\n\tprivate model: ModelIR;\n\tprivate dbConnection: DbConnection | null = null;\n\tprivate completionProvider: CompletionProvider;\n\tprivate databaseUrl: string | undefined;\n\tprivate continuationBuffer = '';\n\tprivate readonly engineDotHandlers: Map<string, (arg: string) => void>;\n\n\tconstructor(config: EngineConfig) {\n\t\tthis.schema = config.schema;\n\t\tthis.schemaPath = config.schemaPath;\n\t\tthis.model = config.schema.model;\n\t\tthis.databaseUrl = config.databaseUrl;\n\t\tthis.completionProvider = new CompletionProvider(config.schema);\n\n\t\tthis.state = {\n\t\t\tmode: 'natural',\n\t\t\texecMode: config.initialExecMode ?? false,\n\t\t\tconnected: false,\n\t\t\texplainMode: false,\n\t\t\tparseMode: config.initialParseMode ?? false,\n\t\t\taliasingMode: 'always',\n\t\t\tincludeStrategy: 'auto',\n\t\t\tdialect: 'postgresql',\n\t\t\t...(config.initialSchemaName !== undefined && {\n\t\t\t\tschemaName: config.initialSchemaName,\n\t\t\t}),\n\t\t\t...(config.dbCasing !== undefined && { dbCasing: config.dbCasing }),\n\t\t\toutputMode: 'json',\n\t\t\toutputLayout: 'full',\n\t\t\tplanVerbosity: 'normal',\n\t\t\tinTransaction: false,\n\t\t};\n\n\t\tconst exitHandler = this.handleExitCommand.bind(this);\n\t\tthis.engineDotHandlers = new Map<string, (arg: string) => void>([\n\t\t\t['.exit', exitHandler],\n\t\t\t['.quit', exitHandler],\n\t\t\t['.clear', this.handleClearCommand.bind(this)],\n\t\t\t['.help', this.handleHelpCommand.bind(this)],\n\t\t\t['.history', this.handleHistoryCommand.bind(this)],\n\t\t\t['.aliasing', this.handleAliasingCommand.bind(this)],\n\t\t\t['.strategy', this.handleStrategyCommand.bind(this)],\n\t\t\t['.dialect', this.handleDialectCommand.bind(this)],\n\t\t\t['.table', this.handleTableCommand.bind(this)],\n\t\t\t['.show', this.handleShowCommand.bind(this)],\n\t\t\t['.close', this.handleCloseCommand.bind(this)],\n\t\t\t['.layout', this.handleLayoutCommand.bind(this)],\n\t\t\t['.plan', this.handlePlanCommand.bind(this)],\n\t\t]);\n\t}\n\n\t/**\n\t * Initialize database connection if configured.\n\t * Must be called after construction for async init.\n\t */\n\tasync init(): Promise<void> {\n\t\tif (!this.databaseUrl) return;\n\n\t\ttry {\n\t\t\tthis.dbConnection = await createDbConnection(this.databaseUrl);\n\t\t\tthis.state.connected = true;\n\t\t\tconst dbName = getDatabaseName(this.databaseUrl);\n\t\t\tthis.emit({\n\t\t\t\ttype: 'info',\n\t\t\t\tmessage: `✓ Connected to database: ${dbName}`,\n\t\t\t});\n\t\t\tthis.emitStateChange();\n\t\t} catch (error) {\n\t\t\tthis.state.connected = false;\n\t\t\tconst message = error instanceof Error ? error.message : String(error);\n\t\t\tthis.emit({\n\t\t\t\ttype: 'init-error',\n\t\t\t\tmessage: `Connection failed: ${message}`,\n\t\t\t});\n\t\t\tthis.emitStateChange();\n\t\t}\n\t}\n\n\t/** Subscribe to engine events. Returns unsubscribe function. */\n\ton(handler: EngineEventHandler): () => void {\n\t\tthis.listeners.push(handler);\n\t\treturn () => {\n\t\t\tconst idx = this.listeners.indexOf(handler);\n\t\t\tif (idx >= 0) this.listeners.splice(idx, 1);\n\t\t};\n\t}\n\n\t/** Get current state (readonly snapshot). */\n\tgetState(): Readonly<EngineState> {\n\t\treturn { ...this.state };\n\t}\n\n\t/** Get the completion provider for the UI. */\n\tgetCompletionProvider(): CompletionProvider {\n\t\treturn this.completionProvider;\n\t}\n\n\t/** Get schema for UI display. */\n\tgetSchema(): EngineConfig['schema'] {\n\t\treturn this.schema;\n\t}\n\n\t/** Get schema path for header display. */\n\tgetSchemaPath(): string {\n\t\treturn this.schemaPath;\n\t}\n\n\t/** Get database name for header display. */\n\tgetDatabaseName(): string | undefined {\n\t\treturn this.databaseUrl ? getDatabaseName(this.databaseUrl) : undefined;\n\t}\n\n\t/**\n\t * Main entry point: process user input.\n\t * Handles dot commands, raw SQL, and NQL queries.\n\t */\n\tasync submit(input: string): Promise<void> {\n\t\tconst trimmed = input.trim();\n\n\t\t// Blank line or comment — flush continuation buffer (separator) and skip\n\t\tif (!trimmed || trimmed.startsWith('#')) {\n\t\t\tthis.continuationBuffer = '';\n\t\t\treturn;\n\t\t}\n\n\t\t// Backslash continuation: accumulate and wait for next line\n\t\tif (trimmed.endsWith('\\\\')) {\n\t\t\tthis.continuationBuffer +=\n\t\t\t\t(this.continuationBuffer ? '\\n' : '') + trimmed.slice(0, -1);\n\t\t\treturn;\n\t\t}\n\n\t\t// Merge continuation buffer with current line\n\t\tconst merged = this.continuationBuffer\n\t\t\t? `${this.continuationBuffer}\\n${trimmed}`\n\t\t\t: trimmed;\n\t\tthis.continuationBuffer = '';\n\n\t\t// --- Dot commands ---\n\t\tif (merged.startsWith('.')) {\n\t\t\tawait this.processDotCommand(merged);\n\t\t\treturn;\n\t\t}\n\n\t\t// --- Query execution ---\n\t\tconst { content, isRawSql, escaped } = parseInputMode(\n\t\t\tmerged,\n\t\t\tthis.state.mode,\n\t\t);\n\n\t\tif (!content) {\n\t\t\tthis.emit({\n\t\t\t\ttype: 'error',\n\t\t\t\tmessage:\n\t\t\t\t\tthis.state.mode === 'sql'\n\t\t\t\t\t\t? 'Empty query. Enter SQL or use ! for natural query'\n\t\t\t\t\t\t: 'Empty query. Enter query or use ! for raw SQL',\n\t\t\t});\n\t\t\treturn;\n\t\t}\n\n\t\tif (isRawSql) {\n\t\t\tawait this.handleRawSql(content, escaped);\n\t\t} else {\n\t\t\tawait this.handleNql(content);\n\t\t}\n\t}\n\n\t/** Cleanup resources. */\n\tasync destroy(): Promise<void> {\n\t\tif (this.dbConnection) {\n\t\t\tawait this.dbConnection.close();\n\t\t\tthis.dbConnection = null;\n\t\t\tthis.state.connected = false;\n\t\t}\n\t}\n\n\t// ========================================================================\n\t// Private: event emission\n\t// ========================================================================\n\n\tprivate emit(event: EngineEvent): void {\n\t\tfor (const listener of this.listeners) {\n\t\t\tlistener(event);\n\t\t}\n\t}\n\n\tprivate emitStateChange(): void {\n\t\tthis.emit({ type: 'state-change', state: { ...this.state } });\n\t}\n\n\t// ========================================================================\n\t// Private: dot command processing\n\t// ========================================================================\n\n\tprivate async processDotCommand(input: string): Promise<void> {\n\t\tconst [rawCmd = '', ...args] = input.trim().split(/\\s+/);\n\t\tconst cmd = rawCmd.toLowerCase();\n\t\tconst arg = args.join(' ').trim();\n\n\t\t// Engine-level commands first\n\t\tconst engineHandler = this.engineDotHandlers.get(cmd);\n\t\tif (engineHandler) {\n\t\t\tengineHandler(arg);\n\t\t\treturn;\n\t\t}\n\n\t\t// Delegate to shared dot-command processor (used by batch mode too)\n\t\tconst batchState: BatchState = {\n\t\t\tmode: this.state.mode,\n\t\t\texecEnabled: this.state.execMode,\n\t\t\tschemaName: this.state.schemaName as string | undefined,\n\t\t\tdbConnection: this.dbConnection ?? undefined,\n\t\t\texplainMode: this.state.explainMode,\n\t\t\tparseMode: this.state.parseMode,\n\t\t\tmodel: this.model,\n\t\t\toutputMode: this.state.outputMode,\n\t\t\tinTransaction: this.state.inTransaction,\n\t\t\t...(this.state.dbCasing !== undefined && {\n\t\t\t\tdbCasing: this.state.dbCasing,\n\t\t\t}),\n\t\t};\n\n\t\tconst result = await processDotCommand(input, this.schema, batchState);\n\t\tthis.applyDotCommandStateChange(result.stateChange);\n\n\t\tif (result.error) {\n\t\t\tthis.emit({ type: 'error', message: result.output });\n\t\t} else {\n\t\t\tthis.emit({ type: 'info', message: result.output });\n\t\t}\n\t}\n\n\t// ========================================================================\n\t// Private: engine-level dot-command handlers\n\t// ========================================================================\n\n\tprivate handleExitCommand(_arg: string): void {\n\t\tthis.emit({ type: 'exit' });\n\t}\n\n\tprivate handleClearCommand(_arg: string): void {\n\t\tthis.emit({ type: 'clear' });\n\t}\n\n\tprivate handleHelpCommand(_arg: string): void {\n\t\tthis.emit({ type: 'info', message: 'SHOW_HELP' });\n\t}\n\n\tprivate handleHistoryCommand(_arg: string): void {\n\t\tthis.emit({ type: 'show-history' });\n\t}\n\n\tprivate handleAliasingCommand(_arg: string): void {\n\t\tconst newMode =\n\t\t\tthis.state.aliasingMode === 'always' ? 'onCollision' : 'always';\n\t\tthis.state.aliasingMode = newMode;\n\t\tthis.emitStateChange();\n\t\tthis.emit({\n\t\t\ttype: 'info',\n\t\t\tmessage: `🏷️ Column aliasing mode: ${newMode}${\n\t\t\t\tnewMode === 'always'\n\t\t\t\t\t? ' (all included columns prefixed)'\n\t\t\t\t\t: ' (only colliding columns prefixed)'\n\t\t\t}`,\n\t\t});\n\t}\n\n\tprivate handleStrategyCommand(arg: string): void {\n\t\tconst strategyArg = arg?.toLowerCase();\n\t\tconst validStrategies = DIALECT_STRATEGIES[this.state.dialect] ?? [];\n\n\t\tif (!strategyArg) {\n\t\t\tconst lines = [\n\t\t\t\t`🔗 Include Strategy: ${this.state.includeStrategy.toUpperCase()}`,\n\t\t\t\t`Dialect: ${this.state.dialect}`,\n\t\t\t\t`Available: ${validStrategies.join(', ')}`,\n\t\t\t\t`Usage: .strategy ${validStrategies.join(' | ')}`,\n\t\t\t];\n\t\t\tthis.emit({ type: 'info', message: lines.join('\\n') });\n\t\t} else if (\n\t\t\tvalidStrategies.includes(strategyArg as (typeof validStrategies)[number])\n\t\t) {\n\t\t\tthis.state.includeStrategy =\n\t\t\t\tstrategyArg as typeof this.state.includeStrategy;\n\t\t\tthis.emitStateChange();\n\t\t\tthis.emit({\n\t\t\t\ttype: 'info',\n\t\t\t\tmessage: `✓ Include strategy: ${strategyArg.toUpperCase()}`,\n\t\t\t});\n\t\t} else {\n\t\t\tthis.emit({\n\t\t\t\ttype: 'error',\n\t\t\t\tmessage: `❌ Unknown or unavailable strategy: ${strategyArg}. Available: ${validStrategies.join(', ')}`,\n\t\t\t});\n\t\t}\n\t}\n\n\tprivate handleDialectCommand(arg: string): void {\n\t\tconst dialectArg = arg?.toLowerCase();\n\t\tconst validDialects = Object.keys(DIALECT_STRATEGIES);\n\n\t\tif (!dialectArg) {\n\t\t\tconst lines = [\n\t\t\t\t`🗄️ SQL Dialect: ${this.state.dialect}`,\n\t\t\t\t`Available: ${validDialects.join(', ')}`,\n\t\t\t\t`Usage: .dialect ${validDialects.join(' | ')}`,\n\t\t\t];\n\t\t\tthis.emit({ type: 'info', message: lines.join('\\n') });\n\t\t} else if (validDialects.includes(dialectArg)) {\n\t\t\tconst strategies =\n\t\t\t\tDIALECT_STRATEGIES[dialectArg as keyof typeof DIALECT_STRATEGIES];\n\t\t\tthis.state.dialect = dialectArg as typeof this.state.dialect;\n\t\t\t// Reset strategy if incompatible with new dialect\n\t\t\tif (\n\t\t\t\tstrategies &&\n\t\t\t\t!strategies.includes(this.state.includeStrategy as never)\n\t\t\t) {\n\t\t\t\tthis.state.includeStrategy = 'join';\n\t\t\t\tthis.emitStateChange();\n\t\t\t\tthis.emit({\n\t\t\t\t\ttype: 'info',\n\t\t\t\t\tmessage: `✓ Dialect: ${dialectArg}\\n⚠ Strategy reset to 'join' (previous not available for ${dialectArg})`,\n\t\t\t\t});\n\t\t\t} else {\n\t\t\t\tthis.emitStateChange();\n\t\t\t\tthis.emit({ type: 'info', message: `✓ Dialect: ${dialectArg}` });\n\t\t\t}\n\t\t} else {\n\t\t\tthis.emit({\n\t\t\t\ttype: 'error',\n\t\t\t\tmessage: `❌ Unknown dialect: ${dialectArg}. Available: ${validDialects.join(', ')}`,\n\t\t\t});\n\t\t}\n\t}\n\n\tprivate handleTableCommand(arg: string): void {\n\t\tthis.handleTableConfig(arg);\n\t}\n\n\tprivate handleShowCommand(arg: string): void {\n\t\t// Panel inspection commands — open anchored panel below input\n\t\t// Usage: .show sql | plan | results | params | dump\n\t\tconst validViews: PanelView[] = [\n\t\t\t'sql',\n\t\t\t'plan',\n\t\t\t'results',\n\t\t\t'params',\n\t\t\t'dump',\n\t\t];\n\t\tconst viewArg = arg?.toLowerCase();\n\n\t\tif (!viewArg) {\n\t\t\tthis.emit({\n\t\t\t\ttype: 'info',\n\t\t\t\tmessage: `📋 Inspection panel views: ${validViews.join(', ')}\\nUsage: .show ${validViews.join(' | ')}`,\n\t\t\t});\n\t\t} else if (validViews.includes(viewArg as PanelView)) {\n\t\t\tthis.emit({ type: 'show-panel', view: viewArg as PanelView });\n\t\t} else {\n\t\t\tthis.emit({\n\t\t\t\ttype: 'error',\n\t\t\t\tmessage: `❌ Unknown panel view: ${viewArg}. Available: ${validViews.join(', ')}`,\n\t\t\t});\n\t\t}\n\t}\n\n\tprivate handleCloseCommand(_arg: string): void {\n\t\tthis.emit({ type: 'close-panel' });\n\t}\n\n\tprivate handleLayoutCommand(arg: string): void {\n\t\tconst validLayouts: OutputLayout[] = ['compact', 'results', 'sql', 'full'];\n\t\tconst layoutArg = arg?.toLowerCase();\n\n\t\tif (!layoutArg) {\n\t\t\tthis.emit({\n\t\t\t\ttype: 'info',\n\t\t\t\tmessage: `📐 Output layout: ${this.state.outputLayout}\\nAvailable: ${validLayouts.join(', ')}\\nUsage: .layout ${validLayouts.join(' | ')}`,\n\t\t\t});\n\t\t} else if (validLayouts.includes(layoutArg as OutputLayout)) {\n\t\t\tthis.state.outputLayout = layoutArg as OutputLayout;\n\t\t\tthis.emitStateChange();\n\t\t\tthis.emit({ type: 'layout-change', layout: this.state.outputLayout });\n\t\t\tthis.emit({ type: 'info', message: `✓ Output layout: ${layoutArg}` });\n\t\t} else {\n\t\t\tthis.emit({\n\t\t\t\ttype: 'error',\n\t\t\t\tmessage: `❌ Unknown layout: ${layoutArg}. Available: ${validLayouts.join(', ')}`,\n\t\t\t});\n\t\t}\n\t}\n\n\tprivate handlePlanCommand(arg: string): void {\n\t\tconst validLevels: PlanVerbosity[] = ['compact', 'normal', 'verbose'];\n\t\tconst level = arg?.toLowerCase();\n\n\t\tif (!level) {\n\t\t\tthis.emit({\n\t\t\t\ttype: 'info',\n\t\t\t\tmessage: `📋 Plan verbosity: ${this.state.planVerbosity}\\nAvailable: ${validLevels.join(', ')}\\nUsage: .plan ${validLevels.join(' | ')}`,\n\t\t\t});\n\t\t} else if (validLevels.includes(level as PlanVerbosity)) {\n\t\t\tthis.state.planVerbosity = level as PlanVerbosity;\n\t\t\tthis.emitStateChange();\n\t\t\tthis.emit({ type: 'info', message: `✓ Plan verbosity: ${level}` });\n\t\t} else {\n\t\t\tthis.emit({\n\t\t\t\ttype: 'error',\n\t\t\t\tmessage: `❌ Invalid plan verbosity: ${level}. Use: ${validLevels.join(', ')}`,\n\t\t\t});\n\t\t}\n\t}\n\n\tprivate applyDotCommandStateChange(\n\t\tstateChange: Partial<BatchState> | undefined,\n\t): void {\n\t\tif (!stateChange) return;\n\n\t\tif (stateChange.mode !== undefined) this.state.mode = stateChange.mode;\n\t\tif (stateChange.execEnabled !== undefined) {\n\t\t\tthis.state.execMode = stateChange.execEnabled;\n\t\t}\n\t\tif ('schemaName' in stateChange) {\n\t\t\tif (stateChange.schemaName !== undefined) {\n\t\t\t\tthis.state.schemaName = stateChange.schemaName;\n\t\t\t} else {\n\t\t\t\tdelete this.state.schemaName;\n\t\t\t}\n\t\t}\n\t\tif (stateChange.explainMode !== undefined) {\n\t\t\tthis.state.explainMode = stateChange.explainMode;\n\t\t}\n\t\tif (stateChange.parseMode !== undefined) {\n\t\t\tthis.state.parseMode = stateChange.parseMode;\n\t\t}\n\t\tif (stateChange.outputMode !== undefined) {\n\t\t\tthis.state.outputMode = stateChange.outputMode;\n\t\t}\n\t\tif (stateChange.inTransaction !== undefined) {\n\t\t\tthis.state.inTransaction = stateChange.inTransaction;\n\t\t}\n\n\t\tthis.emitStateChange();\n\t}\n\n\t// ========================================================================\n\t// Private: .table config\n\t// ========================================================================\n\n\tprivate handleTableConfig(arg: string): void {\n\t\tconst tableConfig = appConfig.getTable();\n\t\tconst parts = arg.split(/\\s+/);\n\t\tconst option = parts[0]?.toLowerCase() ?? '';\n\t\tconst value = parts[1] ?? '';\n\n\t\tif (!option) {\n\t\t\tthis.emit({\n\t\t\t\ttype: 'info',\n\t\t\t\tmessage: `Table Configuration:\\n borders: ${tableConfig.borderStyle}\\n overflow: ${tableConfig.overflow}\\n headers: ${tableConfig.headerFormatter}\\n padding: ${tableConfig.padding}`,\n\t\t\t});\n\t\t\treturn;\n\t\t}\n\n\t\tif (option === 'reset') {\n\t\t\tappConfig.resetTable();\n\t\t\tthis.emit({\n\t\t\t\ttype: 'info',\n\t\t\t\tmessage: '✓ Table configuration reset to defaults',\n\t\t\t});\n\t\t\treturn;\n\t\t}\n\n\t\tconst handler = TABLE_OPTION_HANDLERS[option];\n\t\tif (!handler) {\n\t\t\tthis.emit({\n\t\t\t\ttype: 'error',\n\t\t\t\tmessage: `Unknown option: ${option}. Options: borders, overflow, headers, padding, reset`,\n\t\t\t});\n\t\t\treturn;\n\t\t}\n\n\t\tif (!value) {\n\t\t\tthis.emit({\n\t\t\t\ttype: 'info',\n\t\t\t\tmessage: `Current: ${tableConfig[handler.field]}\\nOptions: ${TABLE_OPTIONS[handler.field].join(', ')}`,\n\t\t\t});\n\t\t\treturn;\n\t\t}\n\n\t\tconst parsed = handler.parse(value);\n\t\tif (isValidTableOption(handler.field, parsed)) {\n\t\t\tappConfig.updateTable({ [handler.field]: parsed } as Parameters<\n\t\t\t\ttypeof appConfig.updateTable\n\t\t\t>[0]);\n\t\t\tthis.emit({ type: 'info', message: `✓ ${handler.label} = ${parsed}` });\n\t\t} else {\n\t\t\tthis.emit({\n\t\t\t\ttype: 'error',\n\t\t\t\tmessage: `Invalid value. Options: ${TABLE_OPTIONS[handler.field].join(', ')}`,\n\t\t\t});\n\t\t}\n\t}\n\n\t// ========================================================================\n\t// Private: raw SQL handling\n\t// ========================================================================\n\n\tprivate async handleRawSql(content: string, escaped: boolean): Promise<void> {\n\t\tconst queryResult: QueryResult = {\n\t\t\tsql: content,\n\t\t\tparams: [],\n\t\t\tplan: {\n\t\t\t\tstrategy: 'RAW_SQL',\n\t\t\t\trootTable: '',\n\t\t\t\ttables: [],\n\t\t\t\tdecisions: [],\n\t\t\t\twarnings: [\n\t\t\t\t\t{ message: getModeWarning(this.state.mode, escaped) },\n\t\t\t\t\t...(!this.state.execMode || !this.state.connected\n\t\t\t\t\t\t? [{ message: '(compile-only, use .exec on to execute)' }]\n\t\t\t\t\t\t: []),\n\t\t\t\t].filter((w) => w.message),\n\t\t\t\tcteCount: 0,\n\t\t\t\tplanningTimeMs: 0,\n\t\t\t},\n\t\t};\n\n\t\tthis.emit({ type: 'query-result', result: queryResult });\n\n\t\t// Execute if in exec mode and connected\n\t\tif (this.state.execMode && this.state.connected && this.dbConnection) {\n\t\t\ttry {\n\t\t\t\tconst execResult = await this.dbConnection.executeRaw(content, []);\n\t\t\t\tthis.emit({\n\t\t\t\t\ttype: 'execution-result',\n\t\t\t\t\tresult: execResult,\n\t\t\t\t\tquery: queryResult,\n\t\t\t\t});\n\t\t\t} catch (err) {\n\t\t\t\tconst message = err instanceof Error ? err.message : String(err);\n\t\t\t\tthis.emit({\n\t\t\t\t\ttype: 'query-result',\n\t\t\t\t\tresult: { sql: content, params: [], error: message },\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\t}\n\n\t// ========================================================================\n\t// Private: NQL handling\n\t// ========================================================================\n\n\tprivate async handleNql(content: string): Promise<void> {\n\t\tif (!this.model) {\n\t\t\tthis.emit({\n\t\t\t\ttype: 'error',\n\t\t\t\tmessage: 'No schema model available for NQL compilation',\n\t\t\t});\n\t\t\treturn;\n\t\t}\n\n\t\ttry {\n\t\t\t// Strip trailing ! (bang suffix = execute mutation) before compilation\n\t\t\t// Must verify the ! is outside string literals (odd number of unescaped quotes = inside string)\n\t\t\tconst trimmed = content.trim();\n\t\t\tconst hasBangSuffix =\n\t\t\t\ttrimmed.endsWith('!') && !isInsideStringLiteral(trimmed);\n\t\t\tconst nqlContent = hasBangSuffix ? trimmed.slice(0, -1).trim() : content;\n\n\t\t\tconst result = await compileNqlToSql(nqlContent, this.model, {\n\t\t\t\t...(this.state.schemaName ? { schemaName: this.state.schemaName } : {}),\n\t\t\t\t...(this.state.dbCasing ? { dbCasing: this.state.dbCasing } : {}),\n\t\t\t});\n\n\t\t\tconst isMutation =\n\t\t\t\tresult.intentType !== 'query' && result.intentType !== 'setOperation';\n\t\t\tconst isDryRun = isMutation && !hasBangSuffix;\n\n\t\t\t// Apply EXPLAIN prefix if explainMode is on (queries only)\n\t\t\tconst finalSql =\n\t\t\t\t!isMutation && this.state.explainMode\n\t\t\t\t\t? `EXPLAIN ${result.sql}`\n\t\t\t\t\t: result.sql;\n\n\t\t\t// Build plan info\n\t\t\tconst planInfo = isMutation\n\t\t\t\t? isDryRun\n\t\t\t\t\t? 'DRY-RUN (add ! to execute)'\n\t\t\t\t\t: 'EXECUTED'\n\t\t\t\t: '';\n\n\t\t\tconst queryResult = this.buildQueryResult(\n\t\t\t\tresult,\n\t\t\t\tfinalSql,\n\t\t\t\tisMutation,\n\t\t\t\tisDryRun,\n\t\t\t\tplanInfo,\n\t\t\t);\n\t\t\tthis.emit({ type: 'query-result', result: queryResult });\n\n\t\t\tif (this.shouldExecuteQuery(isMutation, isDryRun) && this.dbConnection) {\n\t\t\t\tconst execResult = await this.dbConnection.executeRaw(\n\t\t\t\t\tfinalSql,\n\t\t\t\t\tresult.params,\n\t\t\t\t);\n\t\t\t\tthis.emit({\n\t\t\t\t\ttype: 'execution-result',\n\t\t\t\t\tresult: execResult,\n\t\t\t\t\tquery: queryResult,\n\t\t\t\t});\n\t\t\t}\n\t\t} catch (err) {\n\t\t\tconst tableNames = this.schema.tableNames;\n\t\t\tconst rawError = err instanceof Error ? err.message : String(err);\n\t\t\tconst enhancedError = enhanceErrorWithSuggestion(rawError, tableNames);\n\n\t\t\tthis.emit({\n\t\t\t\ttype: 'query-result',\n\t\t\t\tresult: { sql: '', params: [], error: enhancedError },\n\t\t\t});\n\t\t}\n\t}\n\n\t// ========================================================================\n\t// Private: NQL helpers\n\t// ========================================================================\n\n\tprivate buildQueryResult(\n\t\tnqlResult: Awaited<ReturnType<typeof compileNqlToSql>>,\n\t\tfinalSql: string,\n\t\tisMutation: boolean,\n\t\tisDryRun: boolean,\n\t\tplanInfo: string,\n\t): QueryResult {\n\t\tconst pr = nqlResult.planReport;\n\t\treturn {\n\t\t\tsql: finalSql,\n\t\t\tparams: nqlResult.params,\n\t\t\tintent: nqlResult.intent,\n\t\t\tplan: {\n\t\t\t\tstrategy: isMutation\n\t\t\t\t\t? `${nqlResult.intentType.toUpperCase()} - ${planInfo}`\n\t\t\t\t\t: 'NQL v2',\n\t\t\t\trootTable: pr?.rootTable ?? '',\n\t\t\t\ttables: [\n\t\t\t\t\t...new Set(\n\t\t\t\t\t\tpr?.decisions.map((d) => d.context.sourceTable).filter(Boolean) ??\n\t\t\t\t\t\t\t[],\n\t\t\t\t\t),\n\t\t\t\t],\n\t\t\t\tdecisions:\n\t\t\t\t\tpr?.decisions.map((d) => ({\n\t\t\t\t\t\ttype: d.type,\n\t\t\t\t\t\tcontext: [d.context.sourceTable, d.context.target]\n\t\t\t\t\t\t\t.filter(Boolean)\n\t\t\t\t\t\t\t.join(' → '),\n\t\t\t\t\t\tchoice: d.choice,\n\t\t\t\t\t\treasoning: d.reasoning,\n\t\t\t\t\t\t...(d.alternatives.length > 0 && {\n\t\t\t\t\t\t\talternatives: [...d.alternatives],\n\t\t\t\t\t\t}),\n\t\t\t\t\t\t...(d.context.foreignKey !== undefined && {\n\t\t\t\t\t\t\tforeignKey:\n\t\t\t\t\t\t\t\ttypeof d.context.foreignKey === 'string'\n\t\t\t\t\t\t\t\t\t? d.context.foreignKey\n\t\t\t\t\t\t\t\t\t: [...d.context.foreignKey],\n\t\t\t\t\t\t}),\n\t\t\t\t\t\t...(d.context.relationType !== undefined && {\n\t\t\t\t\t\t\trelationType: d.context.relationType,\n\t\t\t\t\t\t}),\n\t\t\t\t\t\t...(d.context.intentPath !== undefined && {\n\t\t\t\t\t\t\tintentPath: d.context.intentPath,\n\t\t\t\t\t\t}),\n\t\t\t\t\t\t...(d.context.relationPath !== undefined && {\n\t\t\t\t\t\t\trelationPath: d.context.relationPath,\n\t\t\t\t\t\t}),\n\t\t\t\t\t\t...(d.id !== undefined && { decisionId: d.id }),\n\t\t\t\t\t})) ?? [],\n\t\t\t\twarnings: [\n\t\t\t\t\t...(isDryRun\n\t\t\t\t\t\t? [{ message: 'This is a dry-run. Add ! suffix to execute.' }]\n\t\t\t\t\t\t: []),\n\t\t\t\t\t...(pr?.warnings.map((w) => ({\n\t\t\t\t\t\tmessage: w.message,\n\t\t\t\t\t\t...(w.suggestion !== undefined && { suggestion: w.suggestion }),\n\t\t\t\t\t\t...(w.code !== undefined && { code: w.code }),\n\t\t\t\t\t\t...(w.relatedDecision !== undefined && {\n\t\t\t\t\t\t\trelatedDecision: w.relatedDecision,\n\t\t\t\t\t\t}),\n\t\t\t\t\t})) ?? []),\n\t\t\t\t],\n\t\t\t\tcteCount: pr?.ctes.length ?? 0,\n\t\t\t\tplanningTimeMs: pr?.metadata.planningTimeMs ?? 0,\n\t\t\t\t...(pr?.ctes && pr.ctes.length > 0\n\t\t\t\t\t? {\n\t\t\t\t\t\t\tctes: pr.ctes.map((c) => ({\n\t\t\t\t\t\t\t\tname: c.name,\n\t\t\t\t\t\t\t\tpurpose: c.purpose,\n\t\t\t\t\t\t\t\t...(c.recursive && { recursive: c.recursive }),\n\t\t\t\t\t\t\t\t...(c.referencedBy.length > 0 && {\n\t\t\t\t\t\t\t\t\treferencedBy: [...c.referencedBy],\n\t\t\t\t\t\t\t\t}),\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...(pr?.metadata\n\t\t\t\t\t? {\n\t\t\t\t\t\t\tmetadata: {\n\t\t\t\t\t\t\t\trelationsAnalyzed: pr.metadata.relationsAnalyzed,\n\t\t\t\t\t\t\t\tisAmbiguous: pr.metadata.isAmbiguous,\n\t\t\t\t\t\t\t\t...(pr.metadata.ambiguousOptions &&\n\t\t\t\t\t\t\t\t\tpr.metadata.ambiguousOptions.length > 0 && {\n\t\t\t\t\t\t\t\t\t\tambiguousOptions: [...pr.metadata.ambiguousOptions],\n\t\t\t\t\t\t\t\t\t}),\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},\n\t\t};\n\t}\n\n\tprivate shouldExecuteQuery(isMutation: boolean, isDryRun: boolean): boolean {\n\t\t// dbConnection narrowing is left to the call site so its truthy check participates\n\t\t// in TypeScript flow analysis without requiring this helper to repeat the assertion.\n\t\treturn isMutation\n\t\t\t? !isDryRun && this.state.execMode && this.state.connected\n\t\t\t: this.state.execMode && this.state.connected;\n\t}\n}\n"],"mappings":"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AM;AAAA,IACN,aAAa;AAAA,EACd;AAAA,EACA;AAAA,IACC,MAAM;AAAA,IACN,OAAO;AAAA,IACP,MAAM;AAAA,IACN,aAAa;AAAA,EACd;AAAA,EACA;AAAA,IACC,MAAM;AAAA,IACN,OAAO;AAAA,IACP,MAAM;AAAA,IACN,aAAa;AAAA,EACd;AAAA,EACA;AAAA,IACC,MAAM;AAAA,IACN,OAAO;AAAA,IACP,MAAM;AAAA,IACN,aAAa;AAAA,EACd;AAAA,EACA;AAAA,IACC,MAAM;AAAA,IACN,OAAO;AAAA,IACP,MAAM;AAAA,IACN,aAAa;AAAA,EACd;AAAA,EACA;AAAA,IACC,MAAM;AAAA,IACN,OAAO;AAAA,IACP,MAAM;AAAA,IACN,aAAa;AAAA,EACd;AAAA,EACA;AAAA,IACC,MAAM;AAAA,IACN,OAAO;AAAA,IACP,MAAM;AAAA,IACN,aAAa;AAAA,EACd;AAAA,EACA;AAAA,IACC,MAAM;AAAA,IACN,OAAO;AAAA,IACP,MAAM;AAAA,IACN,aAAa;AAAA,EACd;AAAA,EACA;AAAA,IACC,MAAM;AAAA,IACN,OAAO;AAAA,IACP,MAAM;AAAA,IACN,aAAa;AAAA,EACd;AAAA,EACA;AAAA,IACC,MAAM;AAAA,IACN,OAAO;AAAA,IACP,MAAM;AAAA,IACN,aAAa;AAAA,EACd;AAAA,EACA;AAAA,IACC,MAAM;AAAA,IACN,OAAO;AAAA,IACP,MAAM;AAAA,IACN,aAAa;AAAA,EACd;AAAA,EACA;AAAA,IACC,MAAM;AAAA,IACN,OAAO;AAAA,IACP,MAAM;AAAA,IACN,aAAa;AAAA,EACd;AAAA,EACA;AAAA,IACC,MAAM;AAAA,IACN,OAAO;AAAA,IACP,MAAM;AAAA,IACN,aAAa;AAAA,EACd;AAAA,EACA;AAAA,IACC,MAAM;AAAA,IACN,OAAO;AAAA,IACP,MAAM;AAAA,IACN,aAAa;AAAA,EACd;AAAA,EACA;AAAA,IACC,MAAM;AAAA,IACN,OAAO;AAAA,IACP,MAAM;AAAA,IACN,aAAa;AAAA,EACd;AAAA,EACA;AAAA,IACC,MAAM;AAAA,IACN,OAAO;AAAA,IACP,MA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","names":["sep","lines","_","config"]}
@@ -115,9 +115,60 @@ function isValidTableOption(option, value) {
115
115
  return TABLE_OPTIONS[option].includes(value);
116
116
  }
117
117
 
118
+ // src/utils/identifier-validation.ts
119
+ var InvalidIdentifierError = class extends Error {
120
+ constructor(value, identifierType, reason) {
121
+ super(
122
+ `Invalid ${identifierType} identifier ${JSON.stringify(value)}: ${reason}`
123
+ );
124
+ this.value = value;
125
+ this.identifierType = identifierType;
126
+ this.name = "InvalidIdentifierError";
127
+ }
128
+ value;
129
+ identifierType;
130
+ };
131
+ var VALID_IDENTIFIER_RE = /^[a-zA-Z_][a-zA-Z0-9_$]*$/;
132
+ function validateIdentifier(value, identifierType) {
133
+ if (!value || value.length === 0) {
134
+ throw new InvalidIdentifierError(value, identifierType, "cannot be empty");
135
+ }
136
+ if (/[\u0000-\u001f\u007f]/u.test(value)) {
137
+ throw new InvalidIdentifierError(
138
+ value,
139
+ identifierType,
140
+ "contains control characters or NUL byte"
141
+ );
142
+ }
143
+ const byteLen = Buffer.byteLength(value, "utf8");
144
+ if (byteLen > 63) {
145
+ throw new InvalidIdentifierError(
146
+ value,
147
+ identifierType,
148
+ `exceeds maximum length of 63 bytes (got ${byteLen})`
149
+ );
150
+ }
151
+ if (!VALID_IDENTIFIER_RE.test(value)) {
152
+ if (/^[0-9]/.test(value)) {
153
+ throw new InvalidIdentifierError(
154
+ value,
155
+ identifierType,
156
+ "cannot start with a digit"
157
+ );
158
+ }
159
+ throw new InvalidIdentifierError(
160
+ value,
161
+ identifierType,
162
+ "contains invalid characters (only letters, digits, underscore, and $ allowed)"
163
+ );
164
+ }
165
+ }
166
+
118
167
  export {
119
168
  config,
120
169
  TABLE_OPTIONS,
121
- isValidTableOption
170
+ isValidTableOption,
171
+ InvalidIdentifierError,
172
+ validateIdentifier
122
173
  };
123
- //# sourceMappingURL=chunk-U5DSGBS2.js.map
174
+ //# sourceMappingURL=chunk-AKVQI5ZD.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/config.ts","../src/utils/identifier-validation.ts"],"sourcesContent":["/**\n * CLI Configuration Module\n *\n * Handles persistent configuration for the REPL, including table display settings.\n * Default config path: ~/.dbsp/config.json\n * Override with -c flag.\n */\n\nimport * as fs from 'node:fs';\nimport * as os from 'node:os';\nimport * as path from 'node:path';\n\n/** Table border style options */\nexport type BorderStyle =\n\t| 'all'\n\t| 'outline'\n\t| 'headers-only'\n\t| 'vertical'\n\t| 'horizontal'\n\t| 'none';\n\n/** Table overflow handling options */\nexport type OverflowStyle =\n\t| 'wrap'\n\t| 'truncate'\n\t| 'truncate-middle'\n\t| 'truncate-start'\n\t| 'truncate-end';\n\n/** Table header formatting options */\nexport type HeaderFormatter =\n\t| 'capitalCase'\n\t| 'none'\n\t| 'snakeCase'\n\t| 'camelCase';\n\n/** Table display configuration */\nexport interface TableConfig {\n\tborderStyle: BorderStyle;\n\toverflow: OverflowStyle;\n\theaderFormatter: HeaderFormatter;\n\tpadding: number;\n}\n\n/** Full CLI configuration */\nexport interface CliConfig {\n\ttable: TableConfig;\n}\n\n/** Default table configuration */\nconst DEFAULT_TABLE_CONFIG: TableConfig = {\n\tborderStyle: 'all',\n\toverflow: 'wrap',\n\theaderFormatter: 'capitalCase',\n\tpadding: 1,\n};\n\n/** Default CLI configuration */\nconst DEFAULT_CONFIG: CliConfig = {\n\ttable: DEFAULT_TABLE_CONFIG,\n};\n\n/** Get default config directory path */\nfunction getDefaultConfigDir(): string {\n\treturn path.join(os.homedir(), '.dbsp');\n}\n\n/** Get default config file path */\nfunction getDefaultConfigPath(): string {\n\treturn path.join(getDefaultConfigDir(), 'config.json');\n}\n\n/** Configuration manager singleton */\nclass ConfigManager {\n\tprivate config: CliConfig = structuredClone(DEFAULT_CONFIG);\n\tprivate configPath: string = getDefaultConfigPath();\n\tprivate loaded = false;\n\n\t/** Set custom config path */\n\tsetConfigPath(configPath: string): void {\n\t\tthis.configPath = configPath;\n\t\tthis.loaded = false;\n\t}\n\n\t/** Get current config path */\n\tgetConfigPath(): string {\n\t\treturn this.configPath;\n\t}\n\n\t/** Load configuration from file */\n\tload(): CliConfig {\n\t\tif (this.loaded) return this.config;\n\n\t\ttry {\n\t\t\tif (fs.existsSync(this.configPath)) {\n\t\t\t\tconst content = fs.readFileSync(this.configPath, 'utf-8');\n\t\t\t\tconst parsed = JSON.parse(content) as Partial<CliConfig>;\n\t\t\t\tthis.config = {\n\t\t\t\t\ttable: {\n\t\t\t\t\t\t...DEFAULT_TABLE_CONFIG,\n\t\t\t\t\t\t...(parsed.table ?? {}),\n\t\t\t\t\t},\n\t\t\t\t};\n\t\t\t}\n\t\t} catch {\n\t\t\t// If config file is invalid, use defaults\n\t\t\tthis.config = structuredClone(DEFAULT_CONFIG);\n\t\t}\n\n\t\tthis.loaded = true;\n\t\treturn this.config;\n\t}\n\n\t/** Save configuration to file */\n\tsave(): void {\n\t\ttry {\n\t\t\tconst dir = path.dirname(this.configPath);\n\t\t\tif (!fs.existsSync(dir)) {\n\t\t\t\tfs.mkdirSync(dir, { recursive: true });\n\t\t\t}\n\t\t\tfs.writeFileSync(\n\t\t\t\tthis.configPath,\n\t\t\t\tJSON.stringify(this.config, null, 2),\n\t\t\t\t'utf-8',\n\t\t\t);\n\t\t} catch (error) {\n\t\t\tconsole.error(`Failed to save config: ${error}`);\n\t\t}\n\t}\n\n\t/** Get current configuration */\n\tget(): CliConfig {\n\t\tif (!this.loaded) this.load();\n\t\treturn this.config;\n\t}\n\n\t/** Get table configuration */\n\tgetTable(): TableConfig {\n\t\treturn this.get().table;\n\t}\n\n\t/** Update table configuration */\n\tupdateTable(updates: Partial<TableConfig>): void {\n\t\tthis.config.table = { ...this.config.table, ...updates };\n\t\tthis.save();\n\t}\n\n\t/** Reset table configuration to defaults */\n\tresetTable(): void {\n\t\tthis.config.table = structuredClone(DEFAULT_TABLE_CONFIG);\n\t\tthis.save();\n\t}\n\n\t/** Reset all configuration to defaults */\n\treset(): void {\n\t\tthis.config = structuredClone(DEFAULT_CONFIG);\n\t\tthis.save();\n\t}\n}\n\n/** Global config manager instance */\nexport const config = new ConfigManager();\n\n/** Valid values for each table option (for validation) */\nexport const TABLE_OPTIONS = {\n\tborderStyle: [\n\t\t'all',\n\t\t'outline',\n\t\t'headers-only',\n\t\t'vertical',\n\t\t'horizontal',\n\t\t'none',\n\t] as const,\n\toverflow: [\n\t\t'wrap',\n\t\t'truncate',\n\t\t'truncate-middle',\n\t\t'truncate-start',\n\t\t'truncate-end',\n\t] as const,\n\theaderFormatter: ['capitalCase', 'none', 'snakeCase', 'camelCase'] as const,\n\tpadding: [0, 1, 2, 3, 4] as const,\n};\n\n/** Check if a value is valid for a given option */\nexport function isValidTableOption<K extends keyof typeof TABLE_OPTIONS>(\n\toption: K,\n\tvalue: unknown,\n): value is (typeof TABLE_OPTIONS)[K][number] {\n\treturn TABLE_OPTIONS[option].includes(value as never);\n}\n","/**\n * @module identifier-validation\n * Validate SQL identifiers (schema, table, column names) in CLI commands.\n *\n * Mirrors the contract from packages/adapter-pgsql/src/validate.ts but kept\n * local to avoid a runtime dependency on the full adapter just for validation.\n *\n * Rules (PostgreSQL-compatible):\n * 1. Must not be empty\n * 2. Must not exceed 63 bytes (PostgreSQL NAMEDATALEN - 1)\n * 3. Must start with a letter (a-z, A-Z) or underscore\n * 4. Must contain only letters, digits, underscore, or dollar sign\n * 5. Must not contain NUL bytes or control characters\n */\n\n/** Thrown when a string is not a valid SQL identifier. */\nexport class InvalidIdentifierError extends Error {\n\tconstructor(\n\t\tpublic readonly value: string,\n\t\tpublic readonly identifierType: string,\n\t\treason: string,\n\t) {\n\t\tsuper(\n\t\t\t`Invalid ${identifierType} identifier ${JSON.stringify(value)}: ${reason}`,\n\t\t);\n\t\tthis.name = 'InvalidIdentifierError';\n\t}\n}\n\nconst VALID_IDENTIFIER_RE = /^[a-zA-Z_][a-zA-Z0-9_$]*$/;\n\n/**\n * Validate that `value` is a safe SQL identifier.\n *\n * @param value - The string to validate\n * @param identifierType - Human-readable type label used in error messages\n * (e.g. 'schema', 'table', 'column')\n * @throws {InvalidIdentifierError} if the value is not safe\n */\nexport function validateIdentifier(\n\tvalue: string,\n\tidentifierType: string,\n): void {\n\tif (!value || value.length === 0) {\n\t\tthrow new InvalidIdentifierError(value, identifierType, 'cannot be empty');\n\t}\n\n\t// Rule 5: reject NUL bytes / control chars before length check\n\t// (NUL byte could be used to confuse downstream consumers)\n\t// biome-ignore lint/suspicious/noControlCharactersInRegex: intentional — detecting control chars is the purpose of this regex\n\tif (/[\\u0000-\\u001f\\u007f]/u.test(value)) {\n\t\tthrow new InvalidIdentifierError(\n\t\t\tvalue,\n\t\t\tidentifierType,\n\t\t\t'contains control characters or NUL byte',\n\t\t);\n\t}\n\n\t// Rule 2: PostgreSQL NAMEDATALEN is 64 bytes; max identifier = 63 bytes.\n\t// Buffer.byteLength gives accurate UTF-8 length (JS strings are UTF-16).\n\tconst byteLen = Buffer.byteLength(value, 'utf8');\n\tif (byteLen > 63) {\n\t\tthrow new InvalidIdentifierError(\n\t\t\tvalue,\n\t\t\tidentifierType,\n\t\t\t`exceeds maximum length of 63 bytes (got ${byteLen})`,\n\t\t);\n\t}\n\n\t// Rules 3 & 4: character set\n\tif (!VALID_IDENTIFIER_RE.test(value)) {\n\t\tif (/^[0-9]/.test(value)) {\n\t\t\tthrow new InvalidIdentifierError(\n\t\t\t\tvalue,\n\t\t\t\tidentifierType,\n\t\t\t\t'cannot start with a digit',\n\t\t\t);\n\t\t}\n\t\tthrow new InvalidIdentifierError(\n\t\t\tvalue,\n\t\t\tidentifierType,\n\t\t\t'contains invalid characters (only letters, digits, underscore, and $ allowed)',\n\t\t);\n\t}\n}\n"],"mappings":"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","names":[]}
package/dist/index.js CHANGED
@@ -9,8 +9,9 @@ import {
9
9
  loadSchemaFromCwd
10
10
  } from "./chunk-AQC34IO5.js";
11
11
  import {
12
- config
13
- } from "./chunk-U5DSGBS2.js";
12
+ config,
13
+ validateIdentifier
14
+ } from "./chunk-AKVQI5ZD.js";
14
15
 
15
16
  // src/index.ts
16
17
  import { Command as Command7, CommanderError } from "commander";
@@ -299,6 +300,12 @@ async function runMigrateAction(fn) {
299
300
  process.exit(1);
300
301
  }
301
302
  }
303
+ function hasExecutableSql(stmt) {
304
+ return stmt.split("\n").some((line) => {
305
+ const t = line.trim();
306
+ return t.length > 0 && !t.startsWith("--");
307
+ });
308
+ }
302
309
  var devCommand = new Command3("dev").description("Generate a migration from schema changes").option(
303
310
  "-s, --schema <path>",
304
311
  "Path to schema file (default: dbsp.schema.ts)"
@@ -399,9 +406,7 @@ Migration file has been tampered with after being applied.`
399
406
  for (const file of pending) {
400
407
  console.log(` Applying: ${file.name}...`);
401
408
  const parsed = parseMigrationFile(file.content);
402
- const statements = parsed.upStatements.filter(
403
- (s) => s.length > 0 && !s.startsWith("-- ")
404
- );
409
+ const statements = parsed.upStatements.filter(hasExecutableSql);
405
410
  const version = await getNextSchemaVersion(
406
411
  client
407
412
  );
@@ -498,9 +503,7 @@ Migration file has been modified since it was applied.`
498
503
  Cannot roll back a migration without a DOWN section.`
499
504
  );
500
505
  }
501
- const downStmts = parsed.downStatements.filter(
502
- (s) => s.length > 0 && !s.startsWith("-- ")
503
- );
506
+ const downStmts = parsed.downStatements.filter(hasExecutableSql);
504
507
  if (downStmts.length === 0 && !options.force) {
505
508
  throw new MigrationError(
506
509
  `Migration ${record.name} has an empty DOWN section
@@ -869,6 +872,9 @@ var replCommand = new Command5("repl").description("Launch interactive REPL for
869
872
  if (options.import && !options.eval && !options.input) {
870
873
  throw new Error("--import requires batch mode (--eval or --input)");
871
874
  }
875
+ if (options.use) {
876
+ validateIdentifier(options.use, "schema");
877
+ }
872
878
  const dbCasing = options.casing === "snake" ? "snake_case" : options.casing === "camel" ? "camelCase" : options.casing === "none" ? "preserve" : void 0;
873
879
  if (options.eval || options.input) {
874
880
  const { runBatchMode } = await import("./repl/batch.js");
@@ -907,7 +913,7 @@ var replCommand = new Command5("repl").description("Launch interactive REPL for
907
913
  });
908
914
  return;
909
915
  }
910
- const { startRepl } = await import("./repl-4OFERLKZ.js");
916
+ const { startRepl } = await import("./repl-TDYVU3GW.js");
911
917
  await startRepl({
912
918
  schema,
913
919
  schemaPath,