@happyvertical/smrt-core 0.43.10 → 0.44.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/agents/generators.md +70 -0
- package/agents/schema-paths.md +99 -0
- package/dist/generators/custom-action.d.ts.map +1 -1
- package/dist/generators/custom-action.js +1 -1
- package/dist/generators/custom-action.js.map +1 -1
- package/dist/index.js +2 -2
- package/dist/knowledge.d.ts +17 -1
- package/dist/knowledge.d.ts.map +1 -1
- package/dist/knowledge.js +33 -3
- package/dist/knowledge.js.map +1 -1
- package/dist/manifest/static-manifest.js +1 -1
- package/dist/manifest/static-manifest.js.map +1 -1
- package/dist/manifest/store.js +1 -1
- package/dist/manifest.json +1 -1
- package/dist/migrations/differ.d.ts +49 -0
- package/dist/migrations/differ.d.ts.map +1 -1
- package/dist/migrations/differ.js +220 -4
- package/dist/migrations/differ.js.map +1 -1
- package/dist/migrations/orchestrate.d.ts +23 -0
- package/dist/migrations/orchestrate.d.ts.map +1 -1
- package/dist/migrations/orchestrate.js +12 -3
- package/dist/migrations/orchestrate.js.map +1 -1
- package/dist/registry/types.d.ts +8 -3
- package/dist/registry/types.d.ts.map +1 -1
- package/dist/schema/foreign-key-ddl.d.ts +7 -0
- package/dist/schema/foreign-key-ddl.d.ts.map +1 -1
- package/dist/schema/foreign-key-ddl.js +7 -1
- package/dist/schema/foreign-key-ddl.js.map +1 -1
- package/dist/schema/schema-manager.d.ts +15 -0
- package/dist/schema/schema-manager.d.ts.map +1 -1
- package/dist/schema/schema-manager.js +52 -9
- package/dist/schema/schema-manager.js.map +1 -1
- package/dist/schema/types.d.ts +10 -0
- package/dist/schema/types.d.ts.map +1 -1
- package/dist/schema/uuid-convergence.d.ts +142 -0
- package/dist/schema/uuid-convergence.d.ts.map +1 -0
- package/dist/schema/uuid-convergence.js +0 -0
- package/dist/schema/uuid-convergence.js.map +1 -0
- package/dist/smrt-knowledge.json +7 -7
- package/dist/utils/scanner-module.d.ts +44 -0
- package/dist/utils/scanner-module.d.ts.map +1 -1
- package/dist/vite-plugin/index.d.ts +14 -1
- package/dist/vite-plugin/index.d.ts.map +1 -1
- package/dist/vite-plugin/index.js +54 -2
- package/dist/vite-plugin/index.js.map +1 -1
- package/dist/vite-plugin.js +3 -2
- package/package.json +4 -4
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"schema-manager.js","names":[],"sources":["../../src/schema/schema-manager.ts"],"sourcesContent":["/**\n * Schema Manager - Direct DDL Execution for SMRT\n *\n * SMRT handles ALL database maintenance directly.\n * SDK SQL remains a pure query/CRUD layer.\n *\n * This module replaces the use of SDK's syncSchema() with direct DDL execution\n * using engine-specific DDL strategies.\n */\n\nimport { createLogger } from '@happyvertical/logger';\nimport type { DatabaseInterface } from '@happyvertical/sql';\nimport {\n type DatabaseEngine,\n detectEngine,\n type EngineSpecificDDL,\n getDDLStrategy,\n} from './ddl/index.js';\nimport {\n type ForeignKeyUuidCastSide,\n foreignKeyConstraintName,\n renderForeignKeyConstraint,\n renderForeignKeyOrphanDetector,\n renderForeignKeyOrphanRepair,\n} from './foreign-key-ddl.js';\nimport { planForeignKeyCreation } from './foreign-key-planner.js';\nimport type { ForeignKeyDefinition, SchemaDefinition } from './types.js';\n\n/**\n * Normalize a `db.query` result into an array of rows.\n *\n * Adapters return either a bare row array or a `{ rows }` envelope. This\n * mirrors the prior inline `Array.isArray(result) ? result : result?.rows ?? []`\n * narrowing while keeping the row shape typed for callers.\n */\nfunction extractRows<Row>(result: unknown): Row[] {\n if (Array.isArray(result)) {\n return result as Row[];\n }\n const rows = (result as { rows?: unknown } | null | undefined)?.rows;\n return Array.isArray(rows) ? (rows as Row[]) : [];\n}\n\n/**\n * Schema Manager Options\n */\nexport interface SchemaManagerOptions {\n /** Force engine type (overrides auto-detection) */\n engine?: DatabaseEngine;\n /** Whether to skip triggers (useful for DuckDB-backed JSON) */\n skipTriggers?: boolean;\n /** Log DDL statements before execution */\n debug?: boolean;\n}\n\n/**\n * Schema Manager\n *\n * Handles direct DDL execution for database schema management.\n * Replaces SDK's syncSchema() with SMRT-controlled schema creation.\n */\nexport class SchemaManager {\n private db: DatabaseInterface;\n private engine: DatabaseEngine;\n private options: SchemaManagerOptions;\n // Per-instance logger: the `debug` option must actually emit debug traces, so\n // the level follows it (a fixed level: 'info' would filter every debug call).\n private logger: ReturnType<typeof createLogger>;\n\n constructor(db: DatabaseInterface, options: SchemaManagerOptions = {}) {\n this.db = db;\n this.options = options;\n this.logger = createLogger({ level: options.debug ? 'debug' : 'info' });\n\n // Detect or use provided engine\n if (options.engine) {\n this.engine = options.engine;\n } else if ((db as { exportTable?: unknown }).exportTable) {\n // JSON adapter detected (has unique exportTable method)\n // JSON adapter uses DuckDB internally, but native UUID values do not\n // round-trip through the SDK row objects as canonical UUID strings.\n this.engine = 'json';\n } else {\n this.engine = detectEngine(db.url);\n }\n }\n\n /**\n * Ensure a table exists with the correct schema\n *\n * If table doesn't exist, creates it with DDL from the engine-specific strategy.\n * If table exists but is missing columns, adds them via ALTER TABLE ADD COLUMN.\n *\n * @param schema - The schema definition\n */\n async ensureTable(schema: SchemaDefinition): Promise<void> {\n const { tableName } = schema;\n\n // Check if table already exists\n const exists = await this.db.tableExists(tableName);\n if (exists) {\n // Table exists — check for missing columns and add them\n // This handles the case where a table was created with a partial schema\n // (e.g., base SmrtObject fields only) and needs additional columns\n await this.addMissingColumns(tableName, schema);\n\n if (this.options.debug) {\n this.logger.debug(\n `[SchemaManager] Table \"${tableName}\" already exists, checked for missing columns`,\n );\n }\n return;\n }\n\n // Generate DDL using engine-specific strategy\n const strategy = getDDLStrategy(this.engine);\n const ddl: EngineSpecificDDL = {\n createTable: strategy.generateCreateTable(schema),\n indexes: strategy.generateIndexes(schema),\n triggers: strategy.generateTriggers(schema),\n };\n\n // Prefer adapter's syncSchema if available (handles adapter-specific transformations)\n // This is critical for JSON adapter which needs to convert UNIQUE indexes to\n // inline constraints for DuckDB compatibility\n // Adapters that support direct DDL sync expose an optional `syncSchema`\n // method beyond the base `DatabaseInterface`; narrow to it here so both the\n // capability check and the call preserve `this.db` as the receiver.\n const dbWithSync = this.db as DatabaseInterface & {\n syncSchema?: (schema: string) => Promise<void>;\n };\n if (dbWithSync.syncSchema) {\n // Combine DDL into single schema string for adapter's syncSchema\n const fullSchema = [\n ddl.createTable,\n ...ddl.indexes,\n ...(this.options.skipTriggers ? [] : ddl.triggers),\n ].join(';\\n');\n\n if (this.options.debug) {\n this.logger.debug(\n `[SchemaManager] Using adapter syncSchema for \"${tableName}\"`,\n { fullSchema },\n );\n }\n\n try {\n await dbWithSync.syncSchema(fullSchema);\n\n // FIX #735: Verify table was actually created\n // Some adapters (notably PostgreSQL) may silently fail to create tables\n // when syncSchema returns without error but doesn't execute the DDL\n const createdSuccessfully = await this.db.tableExists(tableName);\n if (createdSuccessfully) {\n if (this.options.debug) {\n this.logger.debug(\n `[SchemaManager] Table \"${tableName}\" created via adapter syncSchema`,\n );\n }\n return;\n }\n\n // Table wasn't created - fall through to direct DDL execution\n this.logger.warn(\n `[SchemaManager] syncSchema returned but table \"${tableName}\" doesn't exist, falling back to direct DDL`,\n );\n } catch (syncError) {\n // syncSchema failed - fall through to direct DDL execution\n this.logger.warn(\n `[SchemaManager] syncSchema failed for \"${tableName}\", falling back to direct DDL`,\n { error: syncError },\n );\n }\n }\n\n // Fallback: Execute DDL directly (for adapters without syncSchema)\n if (this.options.debug) {\n this.logger.debug(\n `[SchemaManager] Creating table \"${tableName}\" for ${this.engine} (direct DDL)`,\n { createTable: ddl.createTable },\n );\n }\n\n await this.db.query(ddl.createTable);\n\n // Execute indexes\n for (const indexSQL of ddl.indexes) {\n if (this.options.debug) {\n this.logger.debug(`[SchemaManager] Creating index`, { indexSQL });\n }\n await this.db.query(indexSQL);\n }\n\n // Execute triggers (unless skipped)\n if (!this.options.skipTriggers) {\n for (const triggerSQL of ddl.triggers) {\n if (this.options.debug) {\n this.logger.debug(`[SchemaManager] Creating trigger`, { triggerSQL });\n }\n await this.db.query(triggerSQL);\n }\n }\n\n if (this.options.debug) {\n this.logger.debug(\n `[SchemaManager] Table \"${tableName}\" created successfully`,\n );\n }\n }\n\n /**\n * Get existing column names from a database table\n *\n * Uses engine-appropriate introspection:\n * - SQLite/DuckDB: PRAGMA table_info\n * - PostgreSQL: pg_catalog.pg_attribute for the exact search-path relation\n *\n * @param tableName - Name of the table to inspect\n * @returns Set of existing column names with catalog identifier case preserved\n */\n private async getExistingColumns(tableName: string): Promise<Set<string>> {\n const columns = new Set<string>();\n\n try {\n if (this.engine === 'postgres') {\n const result = await this.db.query(\n `SELECT attribute.attname AS column_name\n FROM pg_attribute AS attribute\n WHERE attribute.attrelid = to_regclass(format('%I', $1::text))\n AND attribute.attnum > 0\n AND NOT attribute.attisdropped`,\n tableName,\n );\n const rows = extractRows<{ column_name: string }>(result);\n for (const row of rows) {\n columns.add(row.column_name);\n }\n } else {\n // SQLite and DuckDB use PRAGMA\n const result = await this.db.query(\n `PRAGMA table_info(${this.quoteIdentifier(tableName)})`,\n );\n const rows = extractRows<{ name: string }>(result);\n for (const row of rows) {\n columns.add(row.name);\n }\n }\n } catch (error) {\n // If introspection fails, return empty set (no columns will be added)\n if (this.options.debug) {\n this.logger.warn(\n `[SchemaManager] Failed to introspect columns for \"${tableName}\"`,\n { error },\n );\n }\n }\n\n return columns;\n }\n\n /**\n * Add missing columns to an existing table via ALTER TABLE ADD COLUMN\n *\n * Compares the schema's expected columns against the table's actual columns\n * and adds any that are missing. This handles the race condition where a table\n * is created with base fields by one code path, then a later code path needs\n * additional columns from a more complete schema.\n *\n * SQLite limitation: ALTER TABLE ADD COLUMN cannot add NOT NULL columns\n * without a DEFAULT value. We relax NOT NULL for such columns.\n *\n * @param tableName - Name of the existing table\n * @param schema - The full schema definition with expected columns\n */\n private async addMissingColumns(\n tableName: string,\n schema: SchemaDefinition,\n ): Promise<void> {\n if (!schema.columns || Object.keys(schema.columns).length === 0) {\n return;\n }\n\n const existingColumns = await this.getExistingColumns(tableName);\n if (existingColumns.size === 0) {\n // Introspection failed or returned no columns — skip to avoid errors\n return;\n }\n\n const strategy = getDDLStrategy(this.engine);\n let addedCount = 0;\n\n for (const [colName, colDef] of Object.entries(schema.columns)) {\n if (existingColumns.has(colName)) {\n continue; // Column already exists\n }\n\n // SQLite limitation: ALTER TABLE ADD COLUMN cannot have NOT NULL\n // without a DEFAULT value. Relax NOT NULL for such columns.\n const safeDef = { ...colDef };\n if (\n safeDef.notNull &&\n safeDef.defaultValue === undefined &&\n !safeDef.primaryKey\n ) {\n safeDef.notNull = false;\n }\n\n // Cannot add PRIMARY KEY columns via ALTER TABLE\n if (safeDef.primaryKey) {\n continue;\n }\n\n const colSQL = strategy.generateColumnDefinition(colName, safeDef);\n const alterSQL = `ALTER TABLE ${this.quoteIdentifier(tableName)} ADD COLUMN ${colSQL}`;\n\n try {\n await this.db.query(alterSQL);\n addedCount++;\n\n if (this.options.debug) {\n this.logger.debug(\n `[SchemaManager] Added column \"${colName}\" to \"${tableName}\"`,\n );\n }\n } catch (error) {\n // Column might already exist (race condition) or other issue\n // Log and continue — don't fail the entire operation\n if (this.options.debug) {\n this.logger.warn(\n `[SchemaManager] Failed to add column \"${colName}\" to \"${tableName}\"`,\n { error },\n );\n }\n }\n }\n\n if (addedCount > 0 && this.options.debug) {\n this.logger.debug(\n `[SchemaManager] Added ${addedCount} missing column(s) to \"${tableName}\"`,\n );\n }\n }\n\n private quoteIdentifier(name: string): string {\n return `\"${name.replace(/\"/g, '\"\"')}\"`;\n }\n\n /**\n * Create multiple tables with dependency ordering\n *\n * @param schemas - Array of schema definitions\n */\n async ensureTables(schemas: SchemaDefinition[]): Promise<void> {\n const plan = planForeignKeyCreation(schemas, this.engine);\n const schemasByTable = new Map(\n schemas.map((schema) => [schema.tableName, schema]),\n );\n\n for (const schema of plan.schemas) {\n await this.ensureTable(schema);\n }\n for (const { table, foreignKey } of plan.deferredConstraints) {\n const source = schemasByTable.get(table);\n const target = schemasByTable.get(foreignKey.referencesTable);\n await this.ensurePostgresForeignKey(table, foreignKey, {\n nullable: source?.columns[foreignKey.column]?.notNull !== true,\n uuidComparison:\n source?.columns[foreignKey.column]?.type === 'UUID' &&\n target?.columns[foreignKey.referencesColumn]?.type === 'UUID',\n });\n }\n }\n\n /**\n * Safely add or validate one PostgreSQL foreign key.\n *\n * Existing rows are probed with the canonical exact-column orphan detector\n * before an absent constraint is added as NOT VALID and then explicitly\n * validated. Existing NOT VALID constraints follow the same preflight.\n */\n async ensurePostgresForeignKey(\n tableName: string,\n foreignKey: ForeignKeyDefinition,\n options: {\n nullable?: boolean;\n uuidComparison?: boolean;\n uuidCastSide?: ForeignKeyUuidCastSide;\n } = {},\n ): Promise<void> {\n const constraintName = foreignKeyConstraintName(tableName, foreignKey);\n let existing: unknown;\n try {\n existing = await this.db.query(\n `SELECT constraint_row.convalidated FROM pg_constraint AS constraint_row JOIN pg_class AS table_row ON table_row.oid = constraint_row.conrelid JOIN pg_namespace AS namespace_row ON namespace_row.oid = table_row.relnamespace WHERE table_row.relname = $1 AND namespace_row.nspname = current_schema() AND constraint_row.conname = $2 AND constraint_row.contype = 'f' LIMIT 1`,\n [tableName, constraintName],\n );\n } catch (error) {\n throw new Error(\n `[SchemaManager] Cannot verify deferred foreign key ${constraintName}; refusing an unsafe duplicate/add attempt: ${error instanceof Error ? error.message : String(error)}`,\n { cause: error },\n );\n }\n const existingRows = extractRows<{ convalidated?: boolean }>(existing);\n if (existingRows[0]?.convalidated === true) return;\n\n const probeOptions = await this.resolvePostgresForeignKeyProbeOptions(\n tableName,\n foreignKey,\n options,\n );\n\n const detector = renderForeignKeyOrphanDetector(tableName, foreignKey, {\n engine: 'postgres',\n limitOne: true,\n uuidComparison: probeOptions.uuidComparison,\n uuidCastSide: probeOptions.uuidCastSide,\n });\n let orphanResult: unknown;\n try {\n orphanResult = await this.db.query(detector);\n } catch (error) {\n throw new Error(\n `[SchemaManager] Cannot probe ${tableName}.${foreignKey.column} for orphan rows; refusing to add ${constraintName}. Run the detector manually: ${detector}`,\n { cause: error },\n );\n }\n if (extractRows(orphanResult).length > 0) {\n throw new Error(\n `[SchemaManager] Cannot add ${constraintName}: existing rows do not match ${foreignKey.referencesTable}.${foreignKey.referencesColumn}. Repair them, then retry. Suggested repair: ${renderForeignKeyOrphanRepair(tableName, foreignKey, { engine: 'postgres', nullable: probeOptions.nullable, uuidComparison: probeOptions.uuidComparison, uuidCastSide: probeOptions.uuidCastSide })}`,\n );\n }\n\n if (existingRows.length === 0) {\n await this.db.query(\n `ALTER TABLE ${this.quoteIdentifier(tableName)} ADD ${renderForeignKeyConstraint(tableName, foreignKey)} NOT VALID`,\n );\n }\n await this.db.query(\n `ALTER TABLE ${this.quoteIdentifier(tableName)} VALIDATE CONSTRAINT ${this.quoteIdentifier(constraintName)}`,\n );\n }\n\n private async resolvePostgresForeignKeyProbeOptions(\n tableName: string,\n foreignKey: ForeignKeyDefinition,\n options: {\n nullable?: boolean;\n uuidComparison?: boolean;\n uuidCastSide?: ForeignKeyUuidCastSide;\n },\n ): Promise<typeof options> {\n if (!options.uuidComparison || options.uuidCastSide) return options;\n\n const childSchema = await this.db.getTableSchema?.(tableName);\n const parentSchema =\n foreignKey.referencesTable === tableName\n ? childSchema\n : await this.db.getTableSchema?.(foreignKey.referencesTable);\n const childType = childSchema?.columns[foreignKey.column]?.type;\n const parentType = parentSchema?.columns[foreignKey.referencesColumn]?.type;\n if (!childType || !parentType) return options;\n\n const childTypeNormalized = this.normalizeForeignKeyProbeType(childType);\n const parentTypeNormalized = this.normalizeForeignKeyProbeType(parentType);\n if (childTypeNormalized === parentTypeNormalized) {\n return { ...options, uuidCastSide: 'none' };\n }\n if (childTypeNormalized === 'UUID') {\n return { ...options, uuidCastSide: 'parent' };\n }\n if (parentTypeNormalized === 'UUID') {\n return { ...options, uuidCastSide: 'child' };\n }\n return { ...options, uuidCastSide: 'both' };\n }\n\n private normalizeForeignKeyProbeType(type: string): string {\n const upper = type.toUpperCase().trim();\n if (/^UUID$/i.test(upper)) return 'UUID';\n if (/^(TEXT|CLOB|STRING|VARCHAR|CHAR)/i.test(upper)) return 'TEXT';\n return upper;\n }\n\n /**\n * Generate DDL without executing (useful for debugging/preview)\n *\n * @param schema - The schema definition\n * @returns Engine-specific DDL\n */\n generateDDL(schema: SchemaDefinition): EngineSpecificDDL {\n const strategy = getDDLStrategy(this.engine);\n return {\n createTable: strategy.generateCreateTable(schema),\n indexes: strategy.generateIndexes(schema),\n triggers: strategy.generateTriggers(schema),\n };\n }\n\n /**\n * Get the detected/configured database engine\n */\n getEngine(): DatabaseEngine {\n return this.engine;\n }\n}\n\n/**\n * Create a SchemaManager instance\n *\n * @param db - Database interface\n * @param options - Optional configuration\n * @returns SchemaManager instance\n */\nexport function createSchemaManager(\n db: DatabaseInterface,\n options?: SchemaManagerOptions,\n): SchemaManager {\n return new SchemaManager(db, options);\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"schema-manager.js","names":[],"sources":["../../src/schema/schema-manager.ts"],"sourcesContent":["/**\n * Schema Manager - Direct DDL Execution for SMRT\n *\n * SMRT handles ALL database maintenance directly.\n * SDK SQL remains a pure query/CRUD layer.\n *\n * This module replaces the use of SDK's syncSchema() with direct DDL execution\n * using engine-specific DDL strategies.\n */\n\nimport { createLogger } from '@happyvertical/logger';\nimport type { DatabaseInterface } from '@happyvertical/sql';\nimport {\n type DatabaseEngine,\n detectEngine,\n type EngineSpecificDDL,\n getDDLStrategy,\n} from './ddl/index.js';\nimport {\n type ForeignKeyUuidCastSide,\n foreignKeyConstraintName,\n renderForeignKeyConstraint,\n renderForeignKeyOrphanDetector,\n renderForeignKeyOrphanRepair,\n} from './foreign-key-ddl.js';\nimport { planForeignKeyCreation } from './foreign-key-planner.js';\nimport type { ForeignKeyDefinition, SchemaDefinition } from './types.js';\nimport {\n describeForeignKeyTypeConflict,\n renderUuidConvergenceRepairHint,\n} from './uuid-convergence.js';\n\n/**\n * Normalize a `db.query` result into an array of rows.\n *\n * Adapters return either a bare row array or a `{ rows }` envelope. This\n * mirrors the prior inline `Array.isArray(result) ? result : result?.rows ?? []`\n * narrowing while keeping the row shape typed for callers.\n */\nfunction extractRows<Row>(result: unknown): Row[] {\n if (Array.isArray(result)) {\n return result as Row[];\n }\n const rows = (result as { rows?: unknown } | null | undefined)?.rows;\n return Array.isArray(rows) ? (rows as Row[]) : [];\n}\n\n/**\n * Schema Manager Options\n */\nexport interface SchemaManagerOptions {\n /** Force engine type (overrides auto-detection) */\n engine?: DatabaseEngine;\n /** Whether to skip triggers (useful for DuckDB-backed JSON) */\n skipTriggers?: boolean;\n /** Log DDL statements before execution */\n debug?: boolean;\n}\n\n/**\n * Schema Manager\n *\n * Handles direct DDL execution for database schema management.\n * Replaces SDK's syncSchema() with SMRT-controlled schema creation.\n */\nexport class SchemaManager {\n private db: DatabaseInterface;\n private engine: DatabaseEngine;\n private options: SchemaManagerOptions;\n // Per-instance logger: the `debug` option must actually emit debug traces, so\n // the level follows it (a fixed level: 'info' would filter every debug call).\n private logger: ReturnType<typeof createLogger>;\n\n constructor(db: DatabaseInterface, options: SchemaManagerOptions = {}) {\n this.db = db;\n this.options = options;\n this.logger = createLogger({ level: options.debug ? 'debug' : 'info' });\n\n // Detect or use provided engine\n if (options.engine) {\n this.engine = options.engine;\n } else if ((db as { exportTable?: unknown }).exportTable) {\n // JSON adapter detected (has unique exportTable method)\n // JSON adapter uses DuckDB internally, but native UUID values do not\n // round-trip through the SDK row objects as canonical UUID strings.\n this.engine = 'json';\n } else {\n this.engine = detectEngine(db.url);\n }\n }\n\n /**\n * Ensure a table exists with the correct schema\n *\n * If table doesn't exist, creates it with DDL from the engine-specific strategy.\n * If table exists but is missing columns, adds them via ALTER TABLE ADD COLUMN.\n *\n * @param schema - The schema definition\n */\n async ensureTable(schema: SchemaDefinition): Promise<void> {\n const { tableName } = schema;\n\n // Check if table already exists\n const exists = await this.db.tableExists(tableName);\n if (exists) {\n // Table exists — check for missing columns and add them\n // This handles the case where a table was created with a partial schema\n // (e.g., base SmrtObject fields only) and needs additional columns\n await this.addMissingColumns(tableName, schema);\n\n if (this.options.debug) {\n this.logger.debug(\n `[SchemaManager] Table \"${tableName}\" already exists, checked for missing columns`,\n );\n }\n return;\n }\n\n // Generate DDL using engine-specific strategy\n const strategy = getDDLStrategy(this.engine);\n const ddl: EngineSpecificDDL = {\n createTable: strategy.generateCreateTable(schema),\n indexes: strategy.generateIndexes(schema),\n triggers: strategy.generateTriggers(schema),\n };\n\n // Prefer adapter's syncSchema if available (handles adapter-specific transformations)\n // This is critical for JSON adapter which needs to convert UNIQUE indexes to\n // inline constraints for DuckDB compatibility\n // Adapters that support direct DDL sync expose an optional `syncSchema`\n // method beyond the base `DatabaseInterface`; narrow to it here so both the\n // capability check and the call preserve `this.db` as the receiver.\n const dbWithSync = this.db as DatabaseInterface & {\n syncSchema?: (schema: string) => Promise<void>;\n };\n if (dbWithSync.syncSchema) {\n // Combine DDL into single schema string for adapter's syncSchema\n const fullSchema = [\n ddl.createTable,\n ...ddl.indexes,\n ...(this.options.skipTriggers ? [] : ddl.triggers),\n ].join(';\\n');\n\n if (this.options.debug) {\n this.logger.debug(\n `[SchemaManager] Using adapter syncSchema for \"${tableName}\"`,\n { fullSchema },\n );\n }\n\n try {\n await dbWithSync.syncSchema(fullSchema);\n\n // FIX #735: Verify table was actually created\n // Some adapters (notably PostgreSQL) may silently fail to create tables\n // when syncSchema returns without error but doesn't execute the DDL\n const createdSuccessfully = await this.db.tableExists(tableName);\n if (createdSuccessfully) {\n if (this.options.debug) {\n this.logger.debug(\n `[SchemaManager] Table \"${tableName}\" created via adapter syncSchema`,\n );\n }\n return;\n }\n\n // Table wasn't created - fall through to direct DDL execution\n this.logger.warn(\n `[SchemaManager] syncSchema returned but table \"${tableName}\" doesn't exist, falling back to direct DDL`,\n );\n } catch (syncError) {\n // syncSchema failed - fall through to direct DDL execution\n this.logger.warn(\n `[SchemaManager] syncSchema failed for \"${tableName}\", falling back to direct DDL`,\n { error: syncError },\n );\n }\n }\n\n // Fallback: Execute DDL directly (for adapters without syncSchema)\n if (this.options.debug) {\n this.logger.debug(\n `[SchemaManager] Creating table \"${tableName}\" for ${this.engine} (direct DDL)`,\n { createTable: ddl.createTable },\n );\n }\n\n await this.db.query(ddl.createTable);\n\n // Execute indexes\n for (const indexSQL of ddl.indexes) {\n if (this.options.debug) {\n this.logger.debug(`[SchemaManager] Creating index`, { indexSQL });\n }\n await this.db.query(indexSQL);\n }\n\n // Execute triggers (unless skipped)\n if (!this.options.skipTriggers) {\n for (const triggerSQL of ddl.triggers) {\n if (this.options.debug) {\n this.logger.debug(`[SchemaManager] Creating trigger`, { triggerSQL });\n }\n await this.db.query(triggerSQL);\n }\n }\n\n if (this.options.debug) {\n this.logger.debug(\n `[SchemaManager] Table \"${tableName}\" created successfully`,\n );\n }\n }\n\n /**\n * Get existing column names from a database table\n *\n * Uses engine-appropriate introspection:\n * - SQLite/DuckDB: PRAGMA table_info\n * - PostgreSQL: pg_catalog.pg_attribute for the exact search-path relation\n *\n * @param tableName - Name of the table to inspect\n * @returns Set of existing column names with catalog identifier case preserved\n */\n private async getExistingColumns(tableName: string): Promise<Set<string>> {\n const columns = new Set<string>();\n\n try {\n if (this.engine === 'postgres') {\n const result = await this.db.query(\n `SELECT attribute.attname AS column_name\n FROM pg_attribute AS attribute\n WHERE attribute.attrelid = to_regclass(format('%I', $1::text))\n AND attribute.attnum > 0\n AND NOT attribute.attisdropped`,\n tableName,\n );\n const rows = extractRows<{ column_name: string }>(result);\n for (const row of rows) {\n columns.add(row.column_name);\n }\n } else {\n // SQLite and DuckDB use PRAGMA\n const result = await this.db.query(\n `PRAGMA table_info(${this.quoteIdentifier(tableName)})`,\n );\n const rows = extractRows<{ name: string }>(result);\n for (const row of rows) {\n columns.add(row.name);\n }\n }\n } catch (error) {\n // If introspection fails, return empty set (no columns will be added)\n if (this.options.debug) {\n this.logger.warn(\n `[SchemaManager] Failed to introspect columns for \"${tableName}\"`,\n { error },\n );\n }\n }\n\n return columns;\n }\n\n /**\n * Add missing columns to an existing table via ALTER TABLE ADD COLUMN\n *\n * Compares the schema's expected columns against the table's actual columns\n * and adds any that are missing. This handles the race condition where a table\n * is created with base fields by one code path, then a later code path needs\n * additional columns from a more complete schema.\n *\n * SQLite limitation: ALTER TABLE ADD COLUMN cannot add NOT NULL columns\n * without a DEFAULT value. We relax NOT NULL for such columns.\n *\n * @param tableName - Name of the existing table\n * @param schema - The full schema definition with expected columns\n */\n private async addMissingColumns(\n tableName: string,\n schema: SchemaDefinition,\n ): Promise<void> {\n if (!schema.columns || Object.keys(schema.columns).length === 0) {\n return;\n }\n\n const existingColumns = await this.getExistingColumns(tableName);\n if (existingColumns.size === 0) {\n // Introspection failed or returned no columns — skip to avoid errors\n return;\n }\n\n const strategy = getDDLStrategy(this.engine);\n let addedCount = 0;\n\n for (const [colName, colDef] of Object.entries(schema.columns)) {\n if (existingColumns.has(colName)) {\n continue; // Column already exists\n }\n\n // SQLite limitation: ALTER TABLE ADD COLUMN cannot have NOT NULL\n // without a DEFAULT value. Relax NOT NULL for such columns.\n const safeDef = { ...colDef };\n if (\n safeDef.notNull &&\n safeDef.defaultValue === undefined &&\n !safeDef.primaryKey\n ) {\n safeDef.notNull = false;\n }\n\n // Cannot add PRIMARY KEY columns via ALTER TABLE\n if (safeDef.primaryKey) {\n continue;\n }\n\n const colSQL = strategy.generateColumnDefinition(colName, safeDef);\n const alterSQL = `ALTER TABLE ${this.quoteIdentifier(tableName)} ADD COLUMN ${colSQL}`;\n\n try {\n await this.db.query(alterSQL);\n addedCount++;\n\n if (this.options.debug) {\n this.logger.debug(\n `[SchemaManager] Added column \"${colName}\" to \"${tableName}\"`,\n );\n }\n } catch (error) {\n // Column might already exist (race condition) or other issue\n // Log and continue — don't fail the entire operation\n if (this.options.debug) {\n this.logger.warn(\n `[SchemaManager] Failed to add column \"${colName}\" to \"${tableName}\"`,\n { error },\n );\n }\n }\n }\n\n if (addedCount > 0 && this.options.debug) {\n this.logger.debug(\n `[SchemaManager] Added ${addedCount} missing column(s) to \"${tableName}\"`,\n );\n }\n }\n\n private quoteIdentifier(name: string): string {\n return `\"${name.replace(/\"/g, '\"\"')}\"`;\n }\n\n /**\n * Create multiple tables with dependency ordering\n *\n * @param schemas - Array of schema definitions\n */\n async ensureTables(schemas: SchemaDefinition[]): Promise<void> {\n const plan = planForeignKeyCreation(schemas, this.engine);\n const schemasByTable = new Map(\n schemas.map((schema) => [schema.tableName, schema]),\n );\n\n for (const schema of plan.schemas) {\n await this.ensureTable(schema);\n }\n for (const { table, foreignKey } of plan.deferredConstraints) {\n const source = schemasByTable.get(table);\n const target = schemasByTable.get(foreignKey.referencesTable);\n await this.ensurePostgresForeignKey(table, foreignKey, {\n nullable: source?.columns[foreignKey.column]?.notNull !== true,\n uuidComparison:\n source?.columns[foreignKey.column]?.type === 'UUID' &&\n target?.columns[foreignKey.referencesColumn]?.type === 'UUID',\n });\n }\n }\n\n /**\n * Safely add or validate one PostgreSQL foreign key.\n *\n * Existing rows are probed with the canonical exact-column orphan detector\n * before an absent constraint is added as NOT VALID and then explicitly\n * validated. Existing NOT VALID constraints follow the same preflight.\n */\n async ensurePostgresForeignKey(\n tableName: string,\n foreignKey: ForeignKeyDefinition,\n options: {\n nullable?: boolean;\n uuidComparison?: boolean;\n uuidCastSide?: ForeignKeyUuidCastSide;\n } = {},\n ): Promise<void> {\n const constraintName = foreignKeyConstraintName(tableName, foreignKey);\n let existing: unknown;\n try {\n existing = await this.db.query(\n `SELECT constraint_row.convalidated FROM pg_constraint AS constraint_row JOIN pg_class AS table_row ON table_row.oid = constraint_row.conrelid JOIN pg_namespace AS namespace_row ON namespace_row.oid = table_row.relnamespace WHERE table_row.relname = $1 AND namespace_row.nspname = current_schema() AND constraint_row.conname = $2 AND constraint_row.contype = 'f' LIMIT 1`,\n [tableName, constraintName],\n );\n } catch (error) {\n throw new Error(\n `[SchemaManager] Cannot verify deferred foreign key ${constraintName}; refusing an unsafe duplicate/add attempt: ${error instanceof Error ? error.message : String(error)}`,\n { cause: error },\n );\n }\n const existingRows = extractRows<{ convalidated?: boolean }>(existing);\n if (existingRows[0]?.convalidated === true) return;\n\n // #2608: PostgreSQL cannot implement a FOREIGN KEY whose child and parent\n // columns have different physical types — FK DDL admits no cast — so the\n // ADD CONSTRAINT below would fail with SQLSTATE 42804 and abort every\n // later statement in the batch. Fail closed *before* the orphan probe:\n // across mismatched types a probe answers a question about casted values,\n // not about the constraint we are refusing to add, and it must be re-run\n // after the columns converge anyway.\n const liveTypes = await this.resolveLiveForeignKeyTypes(\n tableName,\n foreignKey,\n );\n if (liveTypes && liveTypes.childNormalized !== liveTypes.parentNormalized) {\n const conflict = describeForeignKeyTypeConflict({\n childTable: tableName,\n childColumn: foreignKey.column,\n childType: liveTypes.childType,\n parentTable: foreignKey.referencesTable,\n parentColumn: foreignKey.referencesColumn,\n parentType: liveTypes.parentType,\n });\n // The uuid convergence is only the right repair for a relationship the\n // manifest declares UUID on both sides (`options.uuidComparison`) and\n // whose live drift is actually a legacy `text` column. Any other\n // incompatible pair reaches this guard too, and a `USING …::uuid` hint\n // there would be wrong — name the two live types instead.\n const textNodes = options.uuidComparison\n ? [\n liveTypes.childNormalized === 'TEXT'\n ? { table: tableName, column: foreignKey.column }\n : undefined,\n liveTypes.parentNormalized === 'TEXT'\n ? {\n table: foreignKey.referencesTable,\n column: foreignKey.referencesColumn,\n }\n : undefined,\n ].filter((node) => node !== undefined)\n : [];\n const repair =\n textNodes.length > 0\n ? ` Suggested repair: ${renderUuidConvergenceRepairHint(textNodes).join('; ')}`\n : ` Align ${tableName}.${foreignKey.column} (${liveTypes.childType}) and ` +\n `${foreignKey.referencesTable}.${foreignKey.referencesColumn} (${liveTypes.parentType}) ` +\n 'on one physical type deliberately, then retry.';\n throw new Error(\n `[SchemaManager] Cannot add ${constraintName}: incompatible column types. ` +\n `${conflict}. Converge the columns first, then retry.${repair}`,\n );\n }\n\n const probeOptions = this.resolvePostgresForeignKeyProbeOptions(\n options,\n liveTypes,\n );\n\n const detector = renderForeignKeyOrphanDetector(tableName, foreignKey, {\n engine: 'postgres',\n limitOne: true,\n uuidComparison: probeOptions.uuidComparison,\n uuidCastSide: probeOptions.uuidCastSide,\n });\n let orphanResult: unknown;\n try {\n orphanResult = await this.db.query(detector);\n } catch (error) {\n throw new Error(\n `[SchemaManager] Cannot probe ${tableName}.${foreignKey.column} for orphan rows; refusing to add ${constraintName}. Run the detector manually: ${detector}`,\n { cause: error },\n );\n }\n if (extractRows(orphanResult).length > 0) {\n throw new Error(\n `[SchemaManager] Cannot add ${constraintName}: existing rows do not match ${foreignKey.referencesTable}.${foreignKey.referencesColumn}. Repair them, then retry. Suggested repair: ${renderForeignKeyOrphanRepair(tableName, foreignKey, { engine: 'postgres', nullable: probeOptions.nullable, uuidComparison: probeOptions.uuidComparison, uuidCastSide: probeOptions.uuidCastSide })}`,\n );\n }\n\n if (existingRows.length === 0) {\n await this.db.query(\n `ALTER TABLE ${this.quoteIdentifier(tableName)} ADD ${renderForeignKeyConstraint(tableName, foreignKey)} NOT VALID`,\n );\n }\n await this.db.query(\n `ALTER TABLE ${this.quoteIdentifier(tableName)} VALIDATE CONSTRAINT ${this.quoteIdentifier(constraintName)}`,\n );\n }\n\n /**\n * Read the live physical types of one child/parent column pair.\n *\n * Returns `undefined` when either side cannot be introspected (adapter\n * without `getTableSchema`, table not created yet) — callers then keep the\n * pre-#2608 behaviour rather than guessing at a mismatch.\n */\n private async resolveLiveForeignKeyTypes(\n tableName: string,\n foreignKey: ForeignKeyDefinition,\n ): Promise<\n | {\n childType: string;\n parentType: string;\n childNormalized: string;\n parentNormalized: string;\n }\n | undefined\n > {\n const childSchema = await this.db.getTableSchema?.(tableName);\n const parentSchema =\n foreignKey.referencesTable === tableName\n ? childSchema\n : await this.db.getTableSchema?.(foreignKey.referencesTable);\n const childType = childSchema?.columns[foreignKey.column]?.type;\n const parentType = parentSchema?.columns[foreignKey.referencesColumn]?.type;\n if (!childType || !parentType) return undefined;\n return {\n childType,\n parentType,\n childNormalized: this.normalizeForeignKeyProbeType(childType),\n parentNormalized: this.normalizeForeignKeyProbeType(parentType),\n };\n }\n\n /**\n * Classify which side of an orphan probe needs a guarded uuid cast.\n *\n * Only reachable with matching live types while the #2608 guard rejects\n * every mismatch, but the classification stays correct for callers that\n * reuse the helper with an unguarded pair.\n */\n private resolvePostgresForeignKeyProbeOptions(\n options: {\n nullable?: boolean;\n uuidComparison?: boolean;\n uuidCastSide?: ForeignKeyUuidCastSide;\n },\n liveTypes?: Awaited<\n ReturnType<SchemaManager['resolveLiveForeignKeyTypes']>\n >,\n ): typeof options {\n if (!options.uuidComparison || options.uuidCastSide) return options;\n if (!liveTypes) return options;\n\n const { childNormalized, parentNormalized } = liveTypes;\n if (childNormalized === parentNormalized) {\n return { ...options, uuidCastSide: 'none' };\n }\n if (childNormalized === 'UUID') {\n return { ...options, uuidCastSide: 'parent' };\n }\n if (parentNormalized === 'UUID') {\n return { ...options, uuidCastSide: 'child' };\n }\n return { ...options, uuidCastSide: 'both' };\n }\n\n private normalizeForeignKeyProbeType(type: string): string {\n const upper = type.toUpperCase().trim();\n if (/^UUID$/i.test(upper)) return 'UUID';\n if (/^(TEXT|CLOB|STRING|VARCHAR|CHAR)/i.test(upper)) return 'TEXT';\n return upper;\n }\n\n /**\n * Generate DDL without executing (useful for debugging/preview)\n *\n * @param schema - The schema definition\n * @returns Engine-specific DDL\n */\n generateDDL(schema: SchemaDefinition): EngineSpecificDDL {\n const strategy = getDDLStrategy(this.engine);\n return {\n createTable: strategy.generateCreateTable(schema),\n indexes: strategy.generateIndexes(schema),\n triggers: strategy.generateTriggers(schema),\n };\n }\n\n /**\n * Get the detected/configured database engine\n */\n getEngine(): DatabaseEngine {\n return this.engine;\n }\n}\n\n/**\n * Create a SchemaManager instance\n *\n * @param db - Database interface\n * @param options - Optional configuration\n * @returns SchemaManager instance\n */\nexport function createSchemaManager(\n db: DatabaseInterface,\n options?: SchemaManagerOptions,\n): SchemaManager {\n return new SchemaManager(db, options);\n}\n"],"mappings":"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"}
|
package/dist/schema/types.d.ts
CHANGED
|
@@ -313,6 +313,16 @@ export interface SchemaChange {
|
|
|
313
313
|
};
|
|
314
314
|
/** Which property an `alter_column` change repairs. */
|
|
315
315
|
alteration?: ColumnAlteration;
|
|
316
|
+
/**
|
|
317
|
+
* Ordering phase for changes that must run before an unrelated family of
|
|
318
|
+
* statements in the same batch. `pre_foreign_key` marks the pre-R11
|
|
319
|
+
* `text` -> `uuid` column convergence (#2608): PostgreSQL cannot implement
|
|
320
|
+
* a foreign key across mismatched physical types, so these conversions —
|
|
321
|
+
* and the advisories that replace them when convergence is refused —
|
|
322
|
+
* precede every foreign-key statement in the migration stream, including
|
|
323
|
+
* the deferred constraints emitted for newly created tables.
|
|
324
|
+
*/
|
|
325
|
+
phase?: 'pre_foreign_key';
|
|
316
326
|
/**
|
|
317
327
|
* Present on report-only changes (`orphan_column`, `orphan_index`, and
|
|
318
328
|
* `alter_column` relaxations the caller has not opted into). A change
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/schema/types.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,MAAM,MAAM,WAAW,GACnB,MAAM,GACN,SAAS,GACT,MAAM,GACN,MAAM,GACN,SAAS,GACT,MAAM,GACN,WAAW,GAKX,MAAM,CAAC;AAEX,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,WAAW,CAAC;IAClB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB;;;;OAIG;IACH,aAAa,CAAC,EAAE,IAAI,GAAG,YAAY,GAAG,iBAAiB,GAAG,UAAU,CAAC;IACrE,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,UAAU,CAAC,EAAE;QACX,KAAK,EAAE,MAAM,CAAC;QACd,MAAM,EAAE,MAAM,CAAC;QACf,QAAQ,CAAC,EAAE,gBAAgB,CAAC;QAC5B,QAAQ,CAAC,EAAE,gBAAgB,CAAC;QAC5B,2DAA2D;QAC3D,OAAO,CAAC,EAAE,KAAK,CAAC,UAAU,GAAG,QAAQ,GAAG,QAAQ,GAAG,MAAM,CAAC,CAAC;KAC5D,CAAC;IACF,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,MAAM,gBAAgB,GACxB,SAAS,GACT,UAAU,GACV,UAAU,GACV,WAAW,CAAC;AAEhB,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;;;;OAMG;IACH,QAAQ,CAAC,EAAE;QACT,MAAM,EAAE,MAAM,CAAC;QACf,IAAI,EAAE,MAAM,CAAC;KACd,CAAC;CACH;AAED;;;;;;;;;;;;;;GAcG;AACH,MAAM,WAAW,uBAAuB;IACtC;;;;OAIG;IACH,IAAI,EAAE,MAAM,CAAC;IACb;;;;OAIG;IACH,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,8BAA8B;IAC9B,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB;;;;;OAKG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,QAAQ,GAAG,OAAO,GAAG,YAAY,CAAC;IACxC,KAAK,EAAE,QAAQ,GAAG,QAAQ,GAAG,QAAQ,CAAC;IACtC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,oBAAoB;IACnC,MAAM,EAAE,MAAM,CAAC;IACf,eAAe,EAAE,MAAM,CAAC;IACxB,gBAAgB,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,EAAE,gBAAgB,CAAC;IAC5B,QAAQ,CAAC,EAAE,gBAAgB,CAAC;IAC5B,2DAA2D;IAC3D,OAAO,CAAC,EAAE,KAAK,CAAC,UAAU,GAAG,QAAQ,GAAG,QAAQ,GAAG,MAAM,CAAC,CAAC;CAC5D;AAED,MAAM,WAAW,gBAAgB;IAC/B,SAAS,EAAE,MAAM,CAAC;IAClB;;;;;;;;;OASG;IACH,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,gBAAgB,CAAC,CAAC;IAC1C,OAAO,EAAE,eAAe,EAAE,CAAC;IAC3B;;;;;;;;;;;;;;;;OAgBG;IACH,QAAQ,EAAE,iBAAiB,EAAE,CAAC;IAC9B,WAAW,EAAE,oBAAoB,EAAE,CAAC;IACpC,OAAO,EAAE,MAAM,CAAC;IAChB,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,cAAc;IAC7B,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,gBAAgB,CAAC,CAAC;IAC1C,YAAY,EAAE,MAAM,EAAE,CAAC;CACxB;AAED,MAAM,WAAW,eAAe;IAC9B,EAAE,EAAE,MAAM,CAAC;IACX,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,EAAE,MAAM,CAAC;IACpB,EAAE,EAAE,MAAM,EAAE,CAAC;IACb,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,WAAW,EAAE,MAAM,CAAC;CACrB;AAED;;GAEG;AACH,MAAM,MAAM,eAAe,GACvB,SAAS,GACT,SAAS,GACT,WAAW,GACX,QAAQ,GACR,aAAa,CAAC;AAElB;;;GAGG;AACH,MAAM,WAAW,qBAAqB;IACpC,uBAAuB;IACvB,EAAE,EAAE,MAAM,CAAC;IACX,kDAAkD;IAClD,IAAI,EAAE,MAAM,CAAC;IACb,8CAA8C;IAC9C,OAAO,EAAE,MAAM,CAAC;IAChB,wDAAwD;IACxD,QAAQ,EAAE,MAAM,CAAC;IACjB,iEAAiE;IACjE,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,qCAAqC;IACrC,UAAU,EAAE,IAAI,CAAC;IACjB,yCAAyC;IACzC,iBAAiB,EAAE,MAAM,GAAG,IAAI,CAAC;IACjC,sDAAsD;IACtD,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,mCAAmC;IACnC,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,+BAA+B;IAC/B,MAAM,EAAE,eAAe,CAAC;IACxB,0CAA0C;IAC1C,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,qCAAqC;IACrC,QAAQ,EAAE,MAAM,CAAC;IACjB,gDAAgD;IAChD,aAAa,EAAE,OAAO,CAAC;IACvB,yDAAyD;IACzD,cAAc,EAAE,IAAI,GAAG,IAAI,CAAC;IAC5B,+DAA+D;IAC/D,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,mDAAmD;IACnD,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;CACtB;AAED;;;GAGG;AACH,MAAM,WAAW,mBAAmB;IAClC,gEAAgE;IAChE,EAAE,EAAE,MAAM,CAAC;IACX,iCAAiC;IACjC,WAAW,EAAE,MAAM,CAAC;IACpB,kDAAkD;IAClD,OAAO,EAAE,MAAM,CAAC;IAChB,4CAA4C;IAC5C,EAAE,EAAE,MAAM,EAAE,CAAC;IACb,+CAA+C;IAC/C,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,0CAA0C;IAC1C,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;IACxB,0CAA0C;IAC1C,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,uBAAuB;IACvB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,gDAAgD;IAChD,aAAa,CAAC,EAAE,OAAO,CAAC;CACzB;AAED;;GAEG;AACH,MAAM,WAAW,eAAe;IAC9B,uDAAuD;IACvD,OAAO,EAAE,OAAO,CAAC;IACjB,qBAAqB;IACrB,IAAI,EAAE,MAAM,CAAC;IACb,wCAAwC;IACxC,QAAQ,EAAE,MAAM,CAAC;IACjB,yCAAyC;IACzC,iBAAiB,EAAE,MAAM,CAAC;IAC1B,4EAA4E;IAC5E,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,6EAA6E;IAC7E,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,gCAAgC;IAChC,KAAK,CAAC,EAAE,KAAK,GAAG,MAAM,CAAC;IACvB,4CAA4C;IAC5C,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB;AAED;;GAEG;AACH,MAAM,WAAW,WAAW;IAC1B,qBAAqB;IACrB,cAAc,EAAE,MAAM,CAAC;IACvB,kDAAkD;IAClD,iBAAiB,EAAE,MAAM,CAAC;IAC1B,wCAAwC;IACxC,eAAe,EAAE,MAAM,CAAC;IACxB,6BAA6B;IAC7B,UAAU,EACN,eAAe,GACf,aAAa,GACb,eAAe,GACf,eAAe,CAAC;IACpB,4CAA4C;IAC5C,cAAc,EAAE,MAAM,CAAC;CACxB;AAED;;;;;;;;;;;GAWG;AACH,MAAM,MAAM,gBAAgB,GACxB,cAAc,GACd,eAAe,GACf,aAAa,GACb,cAAc,CAAC;AAEnB;;;;;GAKG;AACH,MAAM,WAAW,oBAAoB;IACnC,gFAAgF;IAChF,QAAQ,EAAE,SAAS,GAAG,MAAM,CAAC;IAC7B,qEAAqE;IACrE,OAAO,EAAE,MAAM,CAAC;IAChB,4DAA4D;IAC5D,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;CACzB;AAED;;GAEG;AACH,MAAM,WAAW,YAAY;IAC3B,qBAAqB;IACrB,IAAI,EACA,WAAW,GACX,YAAY,GACZ,YAAY,GACZ,aAAa,GACb,cAAc,GACd,eAAe,GACf,WAAW,GACX,iBAAiB,GACjB,kBAAkB,GAClB,YAAY,GACZ,cAAc,GACd,eAAe,GACf,cAAc,CAAC;IACnB,0BAA0B;IAC1B,KAAK,EAAE,MAAM,CAAC;IACd,2CAA2C;IAC3C,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,yCAAyC;IACzC,MAAM,CAAC,EAAE,gBAAgB,CAAC;IAC1B,uCAAuC;IACvC,KAAK,CAAC,EAAE,eAAe,CAAC;IACxB,qEAAqE;IACrE,UAAU,CAAC,EAAE,oBAAoB,CAAC;IAClC;;;;;;OAMG;IACH,QAAQ,CAAC,EAAE;QACT,QAAQ,EAAE,MAAM,CAAC;QACjB,MAAM,EAAE,MAAM,CAAC;KAChB,CAAC;IACF,uDAAuD;IACvD,UAAU,CAAC,EAAE,gBAAgB,CAAC;IAC9B;;;;OAIG;IACH,QAAQ,CAAC,EAAE,oBAAoB,CAAC;IAChC,8BAA8B;IAC9B,GAAG,CAAC,EAAE,MAAM,CAAC;IACb;;;OAGG;IACH,aAAa,CAAC,EAAE,MAAM,EAAE,CAAC;CAC1B;AAED;;GAEG;AACH,MAAM,WAAW,UAAU;IACzB,2BAA2B;IAC3B,YAAY,EAAE,gBAAgB,EAAE,CAAC;IACjC,oDAAoD;IACpD,cAAc,EAAE,MAAM,EAAE,CAAC;IACzB;;;;;OAKG;IACH,aAAa,CAAC,EAAE,MAAM,EAAE,CAAC;IACzB,iCAAiC;IACjC,OAAO,EAAE,YAAY,EAAE,CAAC;IACxB;;;;OAIG;IACH,WAAW,EAAE,OAAO,CAAC;CACtB"}
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/schema/types.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import { SchemaDefinition } from './types.js';
|
|
2
|
+
/** One physical column participating in a manifest UUID relationship. */
|
|
3
|
+
export interface UuidConvergenceNode {
|
|
4
|
+
table: string;
|
|
5
|
+
column: string;
|
|
6
|
+
}
|
|
7
|
+
/** A planned, executable `text` → `uuid` conversion for one column. */
|
|
8
|
+
export interface UuidConvergenceConversion extends UuidConvergenceNode {
|
|
9
|
+
/** The live PostgreSQL type the column has today (e.g. `text`). */
|
|
10
|
+
liveType: string;
|
|
11
|
+
/** Ordered statements: optional `DROP DEFAULT`, then the type change. */
|
|
12
|
+
statements: string[];
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* A relationship group that must converge but cannot be converged safely.
|
|
16
|
+
* Reported, never executed.
|
|
17
|
+
*/
|
|
18
|
+
export interface UuidConvergenceBlock {
|
|
19
|
+
/** Every column in the affected relationship component, sorted. */
|
|
20
|
+
nodes: UuidConvergenceNode[];
|
|
21
|
+
/** Which columns would have had to change, sorted. */
|
|
22
|
+
targets: UuidConvergenceNode[];
|
|
23
|
+
/** Why the automatic conversion is refused. */
|
|
24
|
+
reason: string;
|
|
25
|
+
/** Manual remediation the operator can inspect and run deliberately. */
|
|
26
|
+
suggestedSql: string[];
|
|
27
|
+
}
|
|
28
|
+
/** Result of {@link planUuidConvergence}. */
|
|
29
|
+
export interface UuidConvergencePlan {
|
|
30
|
+
/** Executable conversions, ordered parent-first then by table/column. */
|
|
31
|
+
conversions: UuidConvergenceConversion[];
|
|
32
|
+
/** Components that need convergence but were refused. */
|
|
33
|
+
blocks: UuidConvergenceBlock[];
|
|
34
|
+
/**
|
|
35
|
+
* Type each column will have once this plan is applied: `UUID` for a
|
|
36
|
+
* converted or already-native column, the normalized live type otherwise.
|
|
37
|
+
* Callers use it to decide whether a foreign key is safe to emit in the
|
|
38
|
+
* same run.
|
|
39
|
+
*/
|
|
40
|
+
effectiveType(table: string, column: string): string | undefined;
|
|
41
|
+
}
|
|
42
|
+
/** Live shape of one column, as the planner needs it. */
|
|
43
|
+
export interface UuidConvergenceLiveColumn {
|
|
44
|
+
/** Normalized bucket — `UUID`, `TEXT`, or any other normalized type. */
|
|
45
|
+
normalizedType: string;
|
|
46
|
+
/** Raw live type as introspected, used verbatim in diagnostics. */
|
|
47
|
+
rawType: string;
|
|
48
|
+
/** True when the live column carries a DEFAULT that must be dropped first. */
|
|
49
|
+
hasDefault: boolean;
|
|
50
|
+
/** False when the table/column does not exist yet (it will be created). */
|
|
51
|
+
exists: boolean;
|
|
52
|
+
}
|
|
53
|
+
/** One live foreign key that already constrains a pair of columns. */
|
|
54
|
+
export interface UuidConvergenceLiveForeignKey {
|
|
55
|
+
table: string;
|
|
56
|
+
column: string;
|
|
57
|
+
referencesTable: string;
|
|
58
|
+
referencesColumn: string;
|
|
59
|
+
}
|
|
60
|
+
/** Outcome of probing a column for values that are not uuid-shaped. */
|
|
61
|
+
export type UuidShapeProbeResult = {
|
|
62
|
+
status: 'clean';
|
|
63
|
+
} | {
|
|
64
|
+
status: 'dirty';
|
|
65
|
+
count: number;
|
|
66
|
+
sample?: string;
|
|
67
|
+
} | {
|
|
68
|
+
status: 'unavailable';
|
|
69
|
+
reason: string;
|
|
70
|
+
};
|
|
71
|
+
/** One manifest-declared relationship considered for convergence. */
|
|
72
|
+
export interface UuidConvergenceRelationship {
|
|
73
|
+
childTable: string;
|
|
74
|
+
childColumn: string;
|
|
75
|
+
parentTable: string;
|
|
76
|
+
parentColumn: string;
|
|
77
|
+
}
|
|
78
|
+
/** Inputs {@link planUuidConvergence} needs from the live database. */
|
|
79
|
+
export interface UuidConvergenceInput {
|
|
80
|
+
manifestSchemas: Record<string, SchemaDefinition>;
|
|
81
|
+
/**
|
|
82
|
+
* Physical relationships declared by the manifest for this engine. The
|
|
83
|
+
* caller filters engine allowlists (`@foreignKey({ constraint: { engines }})`)
|
|
84
|
+
* before handing them over.
|
|
85
|
+
*/
|
|
86
|
+
relationships: readonly UuidConvergenceRelationship[];
|
|
87
|
+
/** Live column shape, or `undefined` when the column does not exist yet. */
|
|
88
|
+
getLiveColumn(table: string, column: string): UuidConvergenceLiveColumn | undefined;
|
|
89
|
+
/** Every live foreign key across the schemas under comparison. */
|
|
90
|
+
liveForeignKeys: readonly UuidConvergenceLiveForeignKey[];
|
|
91
|
+
/** Probe run once per column that would be rewritten. */
|
|
92
|
+
probeUuidShape(table: string, column: string): Promise<UuidShapeProbeResult>;
|
|
93
|
+
}
|
|
94
|
+
/** Stable key for one `table.column` node. */
|
|
95
|
+
export declare function uuidConvergenceNodeKey(table: string, column: string): string;
|
|
96
|
+
/**
|
|
97
|
+
* SQL that counts, and samples, values a `text` column holds that could not
|
|
98
|
+
* be reinterpreted as `uuid`. `0` rows means the conversion is safe.
|
|
99
|
+
*/
|
|
100
|
+
export declare function renderUuidShapeProbe(tableName: string, columnName: string): string;
|
|
101
|
+
/**
|
|
102
|
+
* Render the one-time PostgreSQL conversion of a legacy `text` identifier or
|
|
103
|
+
* reference column to native `uuid`.
|
|
104
|
+
*
|
|
105
|
+
* A live DEFAULT is dropped first: PostgreSQL refuses `ALTER COLUMN … TYPE`
|
|
106
|
+
* when the existing default cannot be cast automatically, and the differ's
|
|
107
|
+
* ordinary default comparison re-establishes the manifest default on the next
|
|
108
|
+
* run once the type is settled.
|
|
109
|
+
*/
|
|
110
|
+
export declare function renderUuidColumnConversion(tableName: string, columnName: string, options?: {
|
|
111
|
+
hasDefault?: boolean;
|
|
112
|
+
}): string[];
|
|
113
|
+
/**
|
|
114
|
+
* The `[SchemaManager]`/`[SchemaComparer]` sentence that describes one
|
|
115
|
+
* incompatible child/parent pair. Shared so the runtime guard and the status
|
|
116
|
+
* planner say exactly the same thing about the same drift.
|
|
117
|
+
*/
|
|
118
|
+
export declare function describeForeignKeyTypeConflict(input: {
|
|
119
|
+
childTable: string;
|
|
120
|
+
childColumn: string;
|
|
121
|
+
childType: string;
|
|
122
|
+
parentTable: string;
|
|
123
|
+
parentColumn: string;
|
|
124
|
+
parentType: string;
|
|
125
|
+
}): string;
|
|
126
|
+
/**
|
|
127
|
+
* Remediation text appended to every incompatible-type diagnostic: the
|
|
128
|
+
* planned convergence normally handles it, and this is the manual equivalent.
|
|
129
|
+
*/
|
|
130
|
+
export declare function renderUuidConvergenceRepairHint(nodes: readonly UuidConvergenceNode[]): string[];
|
|
131
|
+
/**
|
|
132
|
+
* Group manifest UUID relationships into connected components and decide,
|
|
133
|
+
* per component, whether the live database must converge on native `uuid`.
|
|
134
|
+
*
|
|
135
|
+
* Components — not individual pairs — are the unit of decision because a
|
|
136
|
+
* single legacy `text` primary key can be referenced by several children:
|
|
137
|
+
* converting the parent for one child would break every other member that is
|
|
138
|
+
* still `text`. Self-referential tables fall out of the same grouping (both
|
|
139
|
+
* endpoints land in one component).
|
|
140
|
+
*/
|
|
141
|
+
export declare function planUuidConvergence(input: UuidConvergenceInput): Promise<UuidConvergencePlan>;
|
|
142
|
+
//# sourceMappingURL=uuid-convergence.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"uuid-convergence.d.ts","sourceRoot":"","sources":["../../src/schema/uuid-convergence.ts"],"names":[],"mappings":"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"}
|
|
Binary file
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"uuid-convergence.js","names":[],"sources":["../../src/schema/uuid-convergence.ts"],"sourcesContent":["/**\n * UUID convergence for pre-R11 PostgreSQL databases (#2608).\n *\n * R11 made SMRT identifiers and references native `uuid` on PostgreSQL.\n * Databases created before that change still carry `text` id columns, while\n * every column added afterwards materializes as `uuid`. PostgreSQL cannot\n * implement a `FOREIGN KEY` across those two physical types — there is no\n * cast inside FK DDL — so `ALTER TABLE … ADD CONSTRAINT … NOT VALID` fails\n * with SQLSTATE 42804 and takes the rest of the migration batch with it.\n *\n * This module owns the deterministic half of the repair:\n *\n * - {@link renderUuidColumnConversion} renders the one-time\n * `ALTER COLUMN … TYPE uuid USING …::uuid` (plus the `DROP DEFAULT` a live\n * text default would otherwise block), and\n * - {@link planUuidConvergence} decides *which* columns must convert, by\n * grouping every manifest-declared UUID relationship into connected\n * components and converging a component only when the live database already\n * proves the target shape (at least one side is native `uuid`).\n *\n * The planner never coerces data: a component converges only when a caller\n * supplied probe confirms every non-null value is uuid-shaped, no live\n * foreign key depends on a column being rewritten, and no member carries some\n * third physical type. Anything else is returned as a *block* with an\n * operator-facing reason, so `db:status` can report \"blocked\" instead of\n * \"pending\" and `db:migrate` never attempts DDL PostgreSQL will reject.\n *\n * A database whose relationship is `text` on *both* sides is left alone: that\n * is the tolerated pre-R11 shape (see the R11 uuid/text equivalence in\n * `migrations/differ.ts`), and its foreign keys apply fine.\n */\n\nimport { CANONICAL_UUID_PATTERN } from './foreign-key-ddl.js';\nimport { quoteIdentifier } from './sql-identifiers.js';\nimport type { SchemaDefinition } from './types.js';\n\n/** One physical column participating in a manifest UUID relationship. */\nexport interface UuidConvergenceNode {\n table: string;\n column: string;\n}\n\n/** A planned, executable `text` → `uuid` conversion for one column. */\nexport interface UuidConvergenceConversion extends UuidConvergenceNode {\n /** The live PostgreSQL type the column has today (e.g. `text`). */\n liveType: string;\n /** Ordered statements: optional `DROP DEFAULT`, then the type change. */\n statements: string[];\n}\n\n/**\n * A relationship group that must converge but cannot be converged safely.\n * Reported, never executed.\n */\nexport interface UuidConvergenceBlock {\n /** Every column in the affected relationship component, sorted. */\n nodes: UuidConvergenceNode[];\n /** Which columns would have had to change, sorted. */\n targets: UuidConvergenceNode[];\n /** Why the automatic conversion is refused. */\n reason: string;\n /** Manual remediation the operator can inspect and run deliberately. */\n suggestedSql: string[];\n}\n\n/** Result of {@link planUuidConvergence}. */\nexport interface UuidConvergencePlan {\n /** Executable conversions, ordered parent-first then by table/column. */\n conversions: UuidConvergenceConversion[];\n /** Components that need convergence but were refused. */\n blocks: UuidConvergenceBlock[];\n /**\n * Type each column will have once this plan is applied: `UUID` for a\n * converted or already-native column, the normalized live type otherwise.\n * Callers use it to decide whether a foreign key is safe to emit in the\n * same run.\n */\n effectiveType(table: string, column: string): string | undefined;\n}\n\n/** Live shape of one column, as the planner needs it. */\nexport interface UuidConvergenceLiveColumn {\n /** Normalized bucket — `UUID`, `TEXT`, or any other normalized type. */\n normalizedType: string;\n /** Raw live type as introspected, used verbatim in diagnostics. */\n rawType: string;\n /** True when the live column carries a DEFAULT that must be dropped first. */\n hasDefault: boolean;\n /** False when the table/column does not exist yet (it will be created). */\n exists: boolean;\n}\n\n/** One live foreign key that already constrains a pair of columns. */\nexport interface UuidConvergenceLiveForeignKey {\n table: string;\n column: string;\n referencesTable: string;\n referencesColumn: string;\n}\n\n/** Outcome of probing a column for values that are not uuid-shaped. */\nexport type UuidShapeProbeResult =\n | { status: 'clean' }\n | { status: 'dirty'; count: number; sample?: string }\n | { status: 'unavailable'; reason: string };\n\n/** One manifest-declared relationship considered for convergence. */\nexport interface UuidConvergenceRelationship {\n childTable: string;\n childColumn: string;\n parentTable: string;\n parentColumn: string;\n}\n\n/** Inputs {@link planUuidConvergence} needs from the live database. */\nexport interface UuidConvergenceInput {\n manifestSchemas: Record<string, SchemaDefinition>;\n /**\n * Physical relationships declared by the manifest for this engine. The\n * caller filters engine allowlists (`@foreignKey({ constraint: { engines }})`)\n * before handing them over.\n */\n relationships: readonly UuidConvergenceRelationship[];\n /** Live column shape, or `undefined` when the column does not exist yet. */\n getLiveColumn(\n table: string,\n column: string,\n ): UuidConvergenceLiveColumn | undefined;\n /** Every live foreign key across the schemas under comparison. */\n liveForeignKeys: readonly UuidConvergenceLiveForeignKey[];\n /** Probe run once per column that would be rewritten. */\n probeUuidShape(table: string, column: string): Promise<UuidShapeProbeResult>;\n}\n\n/** Stable key for one `table.column` node. */\nexport function uuidConvergenceNodeKey(table: string, column: string): string {\n return `${table}\u0000${column}`;\n}\n\nfunction describeNode(node: UuidConvergenceNode): string {\n return `${node.table}.${node.column}`;\n}\n\n/**\n * SQL that counts, and samples, values a `text` column holds that could not\n * be reinterpreted as `uuid`. `0` rows means the conversion is safe.\n */\nexport function renderUuidShapeProbe(\n tableName: string,\n columnName: string,\n): string {\n const column = quoteIdentifier(columnName);\n return (\n `SELECT count(*) AS invalid_count, min(${column}::text) AS sample_value ` +\n `FROM ${quoteIdentifier(tableName)} ` +\n `WHERE ${column} IS NOT NULL AND ${column}::text !~* '${CANONICAL_UUID_PATTERN}'`\n );\n}\n\n/**\n * Render the one-time PostgreSQL conversion of a legacy `text` identifier or\n * reference column to native `uuid`.\n *\n * A live DEFAULT is dropped first: PostgreSQL refuses `ALTER COLUMN … TYPE`\n * when the existing default cannot be cast automatically, and the differ's\n * ordinary default comparison re-establishes the manifest default on the next\n * run once the type is settled.\n */\nexport function renderUuidColumnConversion(\n tableName: string,\n columnName: string,\n options: { hasDefault?: boolean } = {},\n): string[] {\n const table = quoteIdentifier(tableName);\n const column = quoteIdentifier(columnName);\n const statements: string[] = [];\n if (options.hasDefault) {\n statements.push(`ALTER TABLE ${table} ALTER COLUMN ${column} DROP DEFAULT`);\n }\n statements.push(\n `ALTER TABLE ${table} ALTER COLUMN ${column} TYPE uuid USING ${column}::uuid`,\n );\n return statements;\n}\n\n/**\n * The `[SchemaManager]`/`[SchemaComparer]` sentence that describes one\n * incompatible child/parent pair. Shared so the runtime guard and the status\n * planner say exactly the same thing about the same drift.\n */\nexport function describeForeignKeyTypeConflict(input: {\n childTable: string;\n childColumn: string;\n childType: string;\n parentTable: string;\n parentColumn: string;\n parentType: string;\n}): string {\n return (\n `${input.childTable}.${input.childColumn} is ${input.childType} but ` +\n `${input.parentTable}.${input.parentColumn} is ${input.parentType}; ` +\n 'PostgreSQL cannot cast inside FOREIGN KEY DDL (SQLSTATE 42804)'\n );\n}\n\n/**\n * Remediation text appended to every incompatible-type diagnostic: the\n * planned convergence normally handles it, and this is the manual equivalent.\n */\nexport function renderUuidConvergenceRepairHint(\n nodes: readonly UuidConvergenceNode[],\n): string[] {\n return nodes.flatMap((node) => [\n renderUuidShapeProbe(node.table, node.column),\n ...renderUuidColumnConversion(node.table, node.column),\n ]);\n}\n\ninterface ComponentNode extends UuidConvergenceNode {\n key: string;\n live: UuidConvergenceLiveColumn;\n}\n\n/**\n * Group manifest UUID relationships into connected components and decide,\n * per component, whether the live database must converge on native `uuid`.\n *\n * Components — not individual pairs — are the unit of decision because a\n * single legacy `text` primary key can be referenced by several children:\n * converting the parent for one child would break every other member that is\n * still `text`. Self-referential tables fall out of the same grouping (both\n * endpoints land in one component).\n */\nexport async function planUuidConvergence(\n input: UuidConvergenceInput,\n): Promise<UuidConvergencePlan> {\n const nodes = new Map<string, ComponentNode>();\n const parents = new Map<string, string>();\n\n const find = (key: string): string => {\n let root = key;\n while (parents.get(root) !== root) {\n const next = parents.get(root);\n if (next === undefined) break;\n root = next;\n }\n let cursor = key;\n while (parents.get(cursor) !== root) {\n const next = parents.get(cursor);\n if (next === undefined) break;\n parents.set(cursor, root);\n cursor = next;\n }\n return root;\n };\n const union = (a: string, b: string) => {\n const rootA = find(a);\n const rootB = find(b);\n if (rootA !== rootB) parents.set(rootB, rootA);\n };\n\n const addNode = (\n table: string,\n column: string,\n manifestType: string,\n ): string | undefined => {\n const key = uuidConvergenceNodeKey(table, column);\n const existing = nodes.get(key);\n if (existing) return key;\n const live = input.getLiveColumn(table, column) ?? {\n // A table or column the migration is about to create materializes with\n // the manifest type, so it is authoritative evidence for the component.\n normalizedType: manifestType,\n rawType: manifestType.toLowerCase(),\n hasDefault: false,\n exists: false,\n };\n nodes.set(key, { key, table, column, live });\n parents.set(key, key);\n return key;\n };\n\n for (const relationship of input.relationships) {\n const childColumn =\n input.manifestSchemas[relationship.childTable]?.columns[\n relationship.childColumn\n ];\n const parentColumn =\n input.manifestSchemas[relationship.parentTable]?.columns[\n relationship.parentColumn\n ];\n // Only relationships the manifest declares UUID on both sides can\n // converge on uuid; anything else is an ordinary type question the\n // column differ already owns.\n if (childColumn?.type !== 'UUID' || parentColumn?.type !== 'UUID') continue;\n const childKey = addNode(\n relationship.childTable,\n relationship.childColumn,\n 'UUID',\n );\n const parentKey = addNode(\n relationship.parentTable,\n relationship.parentColumn,\n 'UUID',\n );\n if (childKey && parentKey) union(childKey, parentKey);\n }\n\n const components = new Map<string, ComponentNode[]>();\n for (const node of nodes.values()) {\n const root = find(node.key);\n const bucket = components.get(root);\n if (bucket) bucket.push(node);\n else components.set(root, [node]);\n }\n\n const conversions: UuidConvergenceConversion[] = [];\n const blocks: UuidConvergenceBlock[] = [];\n const converted = new Set<string>();\n\n for (const members of components.values()) {\n members.sort((a, b) => a.key.localeCompare(b.key));\n const hasUuid = members.some((node) => node.live.normalizedType === 'UUID');\n const targets = members.filter(\n (node) => node.live.normalizedType === 'TEXT' && node.live.exists,\n );\n const foreign = members.filter(\n (node) =>\n node.live.normalizedType !== 'UUID' &&\n node.live.normalizedType !== 'TEXT',\n );\n\n // All-`text` component: the tolerated pre-R11 shape. Its foreign keys are\n // type-compatible today, so forcing native uuid here would be churn.\n if (!hasUuid || targets.length === 0) {\n if (hasUuid && foreign.length > 0) {\n blocks.push(\n buildBlock(\n members,\n [],\n `column${foreign.length === 1 ? '' : 's'} ${foreign\n .map(describeNode)\n .join(', ')} hold${foreign.length === 1 ? 's' : ''} an ` +\n `unexpected physical type (${foreign\n .map((node) => node.live.rawType)\n .join(', ')}) that SMRT will not rewrite automatically`,\n ),\n );\n }\n continue;\n }\n\n if (foreign.length > 0) {\n blocks.push(\n buildBlock(\n members,\n targets,\n `column${foreign.length === 1 ? '' : 's'} ${foreign\n .map(describeNode)\n .join(', ')} hold${foreign.length === 1 ? 's' : ''} an ` +\n `unexpected physical type (${foreign\n .map((node) => node.live.rawType)\n .join(', ')}) that SMRT will not rewrite automatically`,\n ),\n );\n continue;\n }\n\n const targetKeys = new Set(targets.map((node) => node.key));\n const dependents = input.liveForeignKeys.filter(\n (live) =>\n targetKeys.has(uuidConvergenceNodeKey(live.table, live.column)) ||\n targetKeys.has(\n uuidConvergenceNodeKey(live.referencesTable, live.referencesColumn),\n ),\n );\n if (dependents.length > 0) {\n blocks.push(\n buildBlock(\n members,\n targets,\n 'live foreign keys still depend on the columns that must change (' +\n dependents\n .map(\n (live) =>\n `${live.table}.${live.column} -> ${live.referencesTable}.${live.referencesColumn}`,\n )\n .join(', ') +\n '). Drop those constraints deliberately, rerun the migration to ' +\n 'converge the columns, and let SMRT re-add the manifest constraints',\n ),\n );\n continue;\n }\n\n let dirty: string | undefined;\n for (const node of targets) {\n const probe = await input.probeUuidShape(node.table, node.column);\n if (probe.status === 'clean') continue;\n dirty =\n probe.status === 'dirty'\n ? `${describeNode(node)} holds ${probe.count} value${\n probe.count === 1 ? '' : 's'\n } that ${probe.count === 1 ? 'is' : 'are'} not uuid-shaped` +\n (probe.sample\n ? ` (for example ${JSON.stringify(probe.sample)})`\n : '')\n : `${describeNode(node)} could not be probed for uuid-shaped values (${probe.reason})`;\n break;\n }\n if (dirty) {\n blocks.push(\n buildBlock(\n members,\n targets,\n `${dirty}. SMRT never coerces identifier values; repair or remove them, then rerun the migration`,\n ),\n );\n continue;\n }\n\n for (const node of targets) {\n converted.add(node.key);\n conversions.push({\n table: node.table,\n column: node.column,\n liveType: node.live.rawType,\n statements: renderUuidColumnConversion(node.table, node.column, {\n hasDefault: node.live.hasDefault,\n }),\n });\n }\n }\n\n // Parents before children: converting a referenced primary key first keeps\n // the statement stream valid even if an operator replays it by hand.\n conversions.sort((left, right) => {\n const leftIsId = left.column === 'id' ? 0 : 1;\n const rightIsId = right.column === 'id' ? 0 : 1;\n if (leftIsId !== rightIsId) return leftIsId - rightIsId;\n return (\n left.table.localeCompare(right.table) ||\n left.column.localeCompare(right.column)\n );\n });\n\n return {\n conversions,\n blocks,\n effectiveType(table: string, column: string): string | undefined {\n const key = uuidConvergenceNodeKey(table, column);\n if (converted.has(key)) return 'UUID';\n return nodes.get(key)?.live.normalizedType;\n },\n };\n}\n\nfunction buildBlock(\n members: readonly ComponentNode[],\n targets: readonly ComponentNode[],\n reason: string,\n): UuidConvergenceBlock {\n const plain = (node: UuidConvergenceNode) => ({\n table: node.table,\n column: node.column,\n });\n return {\n nodes: members.map(plain),\n targets: targets.map(plain),\n reason,\n suggestedSql: renderUuidConvergenceRepairHint(targets.map(plain)),\n };\n}\n"],"mappings":"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"}
|