@happyvertical/smrt-core 0.51.39 → 0.52.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.
Files changed (79) hide show
  1. package/AGENTS.md +8 -0
  2. package/agents/schema-paths.md +79 -0
  3. package/dist/cascade.d.ts.map +1 -1
  4. package/dist/cascade.js +2 -1
  5. package/dist/cascade.js.map +1 -1
  6. package/dist/collection.d.ts +10 -1
  7. package/dist/collection.d.ts.map +1 -1
  8. package/dist/collection.js +14 -4
  9. package/dist/collection.js.map +1 -1
  10. package/dist/errors.d.ts +18 -0
  11. package/dist/errors.d.ts.map +1 -1
  12. package/dist/errors.js +21 -0
  13. package/dist/errors.js.map +1 -1
  14. package/dist/index.d.ts +1 -0
  15. package/dist/index.d.ts.map +1 -1
  16. package/dist/index.js +2 -1
  17. package/dist/manifest/static-manifest.d.ts.map +1 -1
  18. package/dist/manifest/static-manifest.js +9 -1
  19. package/dist/manifest/static-manifest.js.map +1 -1
  20. package/dist/manifest/store.js +1 -1
  21. package/dist/manifest/store.js.map +1 -1
  22. package/dist/manifest.json +9 -1
  23. package/dist/migrations/differ.d.ts +56 -0
  24. package/dist/migrations/differ.d.ts.map +1 -1
  25. package/dist/migrations/differ.js +152 -27
  26. package/dist/migrations/differ.js.map +1 -1
  27. package/dist/migrations/null-equal-indexes.js +1 -1
  28. package/dist/migrations/orchestrate.d.ts +7 -0
  29. package/dist/migrations/orchestrate.d.ts.map +1 -1
  30. package/dist/migrations/orchestrate.js +4 -2
  31. package/dist/migrations/orchestrate.js.map +1 -1
  32. package/dist/migrations/tracker.d.ts +29 -1
  33. package/dist/migrations/tracker.d.ts.map +1 -1
  34. package/dist/migrations/tracker.js +92 -7
  35. package/dist/migrations/tracker.js.map +1 -1
  36. package/dist/object.d.ts +128 -0
  37. package/dist/object.d.ts.map +1 -1
  38. package/dist/object.js +333 -44
  39. package/dist/object.js.map +1 -1
  40. package/dist/registry/schema-builder.d.ts.map +1 -1
  41. package/dist/registry/schema-builder.js +7 -5
  42. package/dist/registry/schema-builder.js.map +1 -1
  43. package/dist/registry.d.ts +68 -4
  44. package/dist/registry.d.ts.map +1 -1
  45. package/dist/registry.js +119 -8
  46. package/dist/registry.js.map +1 -1
  47. package/dist/scanner/index.d.ts +1 -1
  48. package/dist/scanner/index.d.ts.map +1 -1
  49. package/dist/scanner/manifest-generator.d.ts +3 -2
  50. package/dist/scanner/manifest-generator.d.ts.map +1 -1
  51. package/dist/scanner/manifest-generator.js +12 -8
  52. package/dist/scanner/manifest-generator.js.map +1 -1
  53. package/dist/scanner/types.d.ts +21 -0
  54. package/dist/scanner/types.d.ts.map +1 -1
  55. package/dist/scanner/types.js.map +1 -1
  56. package/dist/schema/conflict-target.d.ts +68 -11
  57. package/dist/schema/conflict-target.d.ts.map +1 -1
  58. package/dist/schema/conflict-target.js +68 -12
  59. package/dist/schema/conflict-target.js.map +1 -1
  60. package/dist/schema/foreign-key-ddl.d.ts +17 -1
  61. package/dist/schema/foreign-key-ddl.d.ts.map +1 -1
  62. package/dist/schema/foreign-key-ddl.js +29 -3
  63. package/dist/schema/foreign-key-ddl.js.map +1 -1
  64. package/dist/schema/foreign-key-policy.d.ts +8 -0
  65. package/dist/schema/foreign-key-policy.d.ts.map +1 -1
  66. package/dist/schema/foreign-key-policy.js +2 -1
  67. package/dist/schema/foreign-key-policy.js.map +1 -1
  68. package/dist/schema/generator.d.ts +7 -0
  69. package/dist/schema/generator.d.ts.map +1 -1
  70. package/dist/schema/generator.js +18 -10
  71. package/dist/schema/generator.js.map +1 -1
  72. package/dist/schema/text-cast-probe.d.ts +50 -1
  73. package/dist/schema/text-cast-probe.d.ts.map +1 -1
  74. package/dist/schema/text-cast-probe.js +46 -11
  75. package/dist/schema/text-cast-probe.js.map +1 -1
  76. package/dist/schema/types.d.ts +6 -0
  77. package/dist/schema/types.d.ts.map +1 -1
  78. package/dist/smrt-knowledge.json +12 -7
  79. package/package.json +10 -10
@@ -1 +1 @@
1
- {"version":3,"file":"foreign-key-ddl.js","names":[],"sources":["../../src/schema/foreign-key-ddl.ts"],"sourcesContent":["import type { DatabaseEngine } from './ddl/types.js';\nimport { requireForeignKeyAction } from './foreign-key-policy.js';\nimport { shortenIdentifier } from './index-utils.js';\nimport { quoteIdentifier, quoteStringLiteral } from './sql-identifiers.js';\nimport type { ForeignKeyDefinition, SchemaDefinition } from './types.js';\n\nexport type ForeignKeyUuidCastSide = 'child' | 'parent' | 'both' | 'none';\n\nexport function foreignKeyConstraintName(\n tableName: string,\n foreignKey: ForeignKeyDefinition,\n): string {\n return shortenIdentifier(\n `${tableName}_${foreignKey.column}_${foreignKey.referencesTable}_${foreignKey.referencesColumn}_fkey`,\n );\n}\n\nexport function renderForeignKeyConstraint(\n tableName: string,\n foreignKey: ForeignKeyDefinition,\n): string {\n const action = (value: unknown, clause: 'DELETE' | 'UPDATE') => {\n if (value === undefined) return undefined;\n return requireForeignKeyAction(\n value,\n `${tableName}.${foreignKey.column} ON ${clause}`,\n );\n };\n const onDelete = action(foreignKey.onDelete, 'DELETE');\n const onUpdate = action(foreignKey.onUpdate, 'UPDATE');\n const name = foreignKeyConstraintName(tableName, foreignKey);\n const parts = [\n `CONSTRAINT ${quoteIdentifier(name)}`,\n `FOREIGN KEY (${quoteIdentifier(foreignKey.column)})`,\n `REFERENCES ${quoteIdentifier(foreignKey.referencesTable)} (${quoteIdentifier(foreignKey.referencesColumn)})`,\n ];\n if (onDelete) parts.push(`ON DELETE ${onDelete}`);\n if (onUpdate) parts.push(`ON UPDATE ${onUpdate}`);\n return parts.join(' ');\n}\n\n/**\n * Render the two-statement `ADD ... NOT VALID` + `VALIDATE CONSTRAINT`\n * sequence PostgreSQL uses to add a foreign key without holding a full-table\n * lock during validation. Shared by the differ's normal FK-add planning and\n * `db:migrate --null-orphans` (#2748), which reconstructs this same pair\n * after nulling out orphan references the differ refused to plan around.\n */\nexport function renderForeignKeyAddStatements(\n tableName: string,\n foreignKey: ForeignKeyDefinition,\n): string[] {\n const constraintName = foreignKeyConstraintName(tableName, foreignKey);\n return [\n `ALTER TABLE ${quoteIdentifier(tableName)} ADD ${renderForeignKeyConstraint(tableName, foreignKey)} NOT VALID`,\n `ALTER TABLE ${quoteIdentifier(tableName)} VALIDATE CONSTRAINT ${quoteIdentifier(constraintName)}`,\n ];\n}\n\n/** Render a PostgreSQL DROP CONSTRAINT statement for a catalog-owned FK. */\nexport function renderForeignKeyConstraintDrop(\n tableName: string,\n constraintName: string,\n): string {\n return `ALTER TABLE ${quoteIdentifier(tableName)} DROP CONSTRAINT ${quoteIdentifier(constraintName)};`;\n}\n\n/** Render a PostgreSQL VALIDATE CONSTRAINT statement. */\nexport function renderForeignKeyConstraintValidate(\n tableName: string,\n constraintName: string,\n): string {\n return `ALTER TABLE ${quoteIdentifier(tableName)} VALIDATE CONSTRAINT ${quoteIdentifier(constraintName)};`;\n}\n\n/** Render a PostgreSQL constraint comment used to identify framework ownership. */\nexport function renderForeignKeyConstraintComment(\n tableName: string,\n constraintName: string,\n comment: string,\n): string {\n return `COMMENT ON CONSTRAINT ${quoteIdentifier(constraintName)} ON ${quoteIdentifier(tableName)} IS ${quoteStringLiteral(comment)};`;\n}\n\nexport function renderForeignKeyOrphanDetector(\n tableName: string,\n foreignKey: ForeignKeyDefinition,\n options: {\n engine?: DatabaseEngine;\n limitOne?: boolean;\n /**\n * Aggregate the same predicate as `COUNT(*)` instead of listing orphan\n * keys. Used by the read-only per-foreign-key orphan count report\n * (#2753) so the report shares the exact FROM/JOIN/WHERE clause the\n * migration gate probes with, rather than a second hand-written copy.\n */\n countOnly?: boolean;\n uuidComparison?: boolean;\n uuidCastSide?: ForeignKeyUuidCastSide;\n } = {},\n): string {\n const parts = foreignKeyOrphanParts(\n tableName,\n foreignKey,\n options.engine,\n options.uuidComparison,\n options.uuidCastSide,\n );\n const fromClause =\n `${parts.childTable} ` +\n `LEFT JOIN ${parts.parentTable} ON ${parts.joinPredicate} ` +\n `WHERE ${parts.childColumn} IS NOT NULL AND ${parts.parentColumn} IS NULL`;\n if (options.countOnly) {\n return `SELECT COUNT(*) AS orphan_count FROM ${fromClause}`;\n }\n return (\n `SELECT ${parts.childColumn} AS orphan_key FROM ${fromClause}` +\n (options.limitOne ? ' LIMIT 1' : '')\n );\n}\n\nexport function renderForeignKeyOrphanRepair(\n tableName: string,\n foreignKey: ForeignKeyDefinition,\n options: {\n engine?: DatabaseEngine;\n uuidComparison?: boolean;\n uuidCastSide?: ForeignKeyUuidCastSide;\n nullable?: boolean;\n } = {},\n): string {\n const parts = foreignKeyOrphanParts(\n tableName,\n foreignKey,\n options.engine,\n options.uuidComparison,\n options.uuidCastSide,\n );\n const orphanPredicate =\n `${parts.childColumn} IS NOT NULL AND NOT EXISTS (` +\n `SELECT 1 FROM ${parts.parentTable} WHERE ${parts.joinPredicate})`;\n if (options.nullable === false) {\n return (\n `-- Manual repair required: ${quoteIdentifier(tableName)}.${quoteIdentifier(foreignKey.column)} is NOT NULL. ` +\n `Reassign each orphaned reference to a valid ${quoteIdentifier(foreignKey.referencesTable)}.${quoteIdentifier(foreignKey.referencesColumn)} value, ` +\n 'or deliberately delete rows only after preserving any required child data.'\n );\n }\n return (\n `UPDATE ${quoteIdentifier(tableName)} AS ${parts.childAlias} ` +\n `SET ${quoteIdentifier(foreignKey.column)} = NULL ` +\n `WHERE ${orphanPredicate}`\n );\n}\n\nconst FOREIGN_KEY_CHILD_ALIAS = 'smrt_fk_child';\nconst FOREIGN_KEY_PARENT_ALIAS = 'smrt_fk_parent';\n/**\n * Canonical UUID text shape used by every PostgreSQL guard that has to decide\n * whether a `text` column can be reinterpreted as `uuid`. Exported so the\n * orphan probe, the FK provisioning guard, and the uuid convergence planner\n * all test the same shape (#2608).\n */\nexport const CANONICAL_UUID_PATTERN =\n '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$';\n\n/**\n * The same canonical UUID shape as {@link CANONICAL_UUID_PATTERN}, expressed\n * as a SQLite `GLOB` pattern instead of a regex — SQLite has no built-in\n * regex operator, so a shape probe there cannot push a regex predicate down\n * to the server. `GLOB` is case-sensitive, so callers must `LOWER()` the\n * value first; combine with a `LENGTH(...) = 36` check (`GLOB` alone allows\n * a shorter/longer value to still match a middle substring) so this can run\n * as a whole-table server-side aggregate instead of fetching every row into\n * JS to test in a loop (#2767 review).\n */\nexport const CANONICAL_UUID_SQLITE_GLOB_PATTERN =\n '[0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f]-' +\n '[0-9a-f][0-9a-f][0-9a-f][0-9a-f]-' +\n '[0-9a-f][0-9a-f][0-9a-f][0-9a-f]-' +\n '[0-9a-f][0-9a-f][0-9a-f][0-9a-f]-' +\n '[0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f]';\n\nfunction foreignKeyOrphanParts(\n tableName: string,\n foreignKey: ForeignKeyDefinition,\n engine: DatabaseEngine = 'postgres',\n uuidComparison = false,\n uuidCastSide?: ForeignKeyUuidCastSide,\n) {\n const childAlias = quoteIdentifier(FOREIGN_KEY_CHILD_ALIAS);\n const parentAlias = quoteIdentifier(FOREIGN_KEY_PARENT_ALIAS);\n const childColumn = `${childAlias}.${quoteIdentifier(foreignKey.column)}`;\n const parentColumn = `${parentAlias}.${quoteIdentifier(foreignKey.referencesColumn)}`;\n const guardedUuidValue = (column: string) =>\n `CASE WHEN ${column}::text ~* '${CANONICAL_UUID_PATTERN}' THEN ${column}::uuid ELSE NULL END`;\n const effectiveUuidCastSide =\n engine === 'postgres' && uuidComparison\n ? (uuidCastSide ?? 'child')\n : undefined;\n const childValue =\n effectiveUuidCastSide === 'child' || effectiveUuidCastSide === 'both'\n ? guardedUuidValue(childColumn)\n : childColumn;\n const parentValue =\n effectiveUuidCastSide === 'parent' || effectiveUuidCastSide === 'both'\n ? guardedUuidValue(parentColumn)\n : parentColumn;\n\n return {\n childAlias,\n childColumn,\n childTable: `${quoteIdentifier(tableName)} AS ${childAlias}`,\n joinPredicate: `${parentValue} = ${childValue}`,\n parentColumn,\n parentTable: `${quoteIdentifier(foreignKey.referencesTable)} AS ${parentAlias}`,\n };\n}\n\nexport function schemaForeignKeys(\n schema: Pick<SchemaDefinition, 'columns'> &\n Partial<Pick<SchemaDefinition, 'foreignKeys'>>,\n): ForeignKeyDefinition[] {\n if (schema.foreignKeys && schema.foreignKeys.length > 0) {\n return schema.foreignKeys;\n }\n return Object.entries(schema.columns).flatMap(([column, definition]) => {\n if (!definition.foreignKey) return [];\n return [\n {\n column,\n referencesTable: definition.foreignKey.table,\n referencesColumn: definition.foreignKey.column,\n onDelete: definition.foreignKey.onDelete,\n onUpdate: definition.foreignKey.onUpdate,\n ...(definition.foreignKey.engines\n ? { engines: definition.foreignKey.engines }\n : {}),\n },\n ];\n });\n}\n\n/** Stable identity for one physical relationship, independent of allocation. */\nexport function foreignKeyRelationshipKey(\n foreignKey: Pick<\n ForeignKeyDefinition,\n 'column' | 'referencesColumn' | 'referencesTable'\n >,\n): string {\n return JSON.stringify([\n foreignKey.column,\n foreignKey.referencesTable,\n foreignKey.referencesColumn,\n ]);\n}\n\n/** Return only physical constraints explicitly enabled for an engine. */\nexport function schemaForeignKeysForEngine(\n schema: Pick<SchemaDefinition, 'columns'> &\n Partial<Pick<SchemaDefinition, 'foreignKeys'>>,\n engine: DatabaseEngine,\n): ForeignKeyDefinition[] {\n return schemaForeignKeys(schema).filter((foreignKey) => {\n if (foreignKey.engines === undefined) return true;\n\n const validEngines = new Set<DatabaseEngine>([\n 'postgres',\n 'sqlite',\n 'duckdb',\n 'json',\n ]);\n if (\n foreignKey.engines.length === 0 ||\n foreignKey.engines.some(\n (declaredEngine) => !validEngines.has(declaredEngine),\n )\n ) {\n throw new Error(\n `[DDL:${engine}] Foreign key ${foreignKey.column} declares an invalid physical constraint engine allowlist. Use one or more of: postgres, sqlite, duckdb, json.`,\n );\n }\n\n return foreignKey.engines.includes(engine);\n });\n}\n\n/** Return declared schema dependencies after excluding engine-disabled FKs. */\nexport function schemaDependenciesForEngine(\n schema: Pick<SchemaDefinition, 'columns'> &\n Partial<Pick<SchemaDefinition, 'dependencies' | 'foreignKeys'>>,\n engine: DatabaseEngine,\n): string[] {\n const foreignKeys = schemaForeignKeys(schema);\n const enabledForeignKeys = schemaForeignKeysForEngine(schema, engine);\n const enabledKeys = new Set(\n enabledForeignKeys.map(foreignKeyRelationshipKey),\n );\n const enabledTargets = new Set(\n enabledForeignKeys.map((foreignKey) => foreignKey.referencesTable),\n );\n const disabledTargets = new Set(\n foreignKeys\n .filter(\n (foreignKey) => !enabledKeys.has(foreignKeyRelationshipKey(foreignKey)),\n )\n .map((foreignKey) => foreignKey.referencesTable),\n );\n\n return Array.from(\n new Set([\n ...(schema.dependencies ?? []).filter(\n (dependency) =>\n !disabledTargets.has(dependency) || enabledTargets.has(dependency),\n ),\n ...enabledTargets,\n ]),\n );\n}\n"],"mappings":";;;;AAQA,SAAgB,yBACd,WACA,YACQ;CACR,OAAO,kBACL,GAAG,UAAU,GAAG,WAAW,OAAO,GAAG,WAAW,gBAAgB,GAAG,WAAW,iBAAiB,MACjG;AACF;AAEA,SAAgB,2BACd,WACA,YACQ;CACR,MAAM,UAAU,OAAgB,WAAgC;EAC9D,IAAI,UAAU,KAAA,GAAW,OAAO,KAAA;EAChC,OAAO,wBACL,OACA,GAAG,UAAU,GAAG,WAAW,OAAO,MAAM,QAC1C;CACF;CACA,MAAM,WAAW,OAAO,WAAW,UAAU,QAAQ;CACrD,MAAM,WAAW,OAAO,WAAW,UAAU,QAAQ;CAErD,MAAM,QAAQ;EACZ,cAAc,gBAFH,yBAAyB,WAAW,UAEjB,CAAI;EAClC,gBAAgB,gBAAgB,WAAW,MAAM,EAAE;EACnD,cAAc,gBAAgB,WAAW,eAAe,EAAE,IAAI,gBAAgB,WAAW,gBAAgB,EAAE;CAC7G;CACA,IAAI,UAAU,MAAM,KAAK,aAAa,UAAU;CAChD,IAAI,UAAU,MAAM,KAAK,aAAa,UAAU;CAChD,OAAO,MAAM,KAAK,GAAG;AACvB;;;;;;;;AASA,SAAgB,8BACd,WACA,YACU;CACV,MAAM,iBAAiB,yBAAyB,WAAW,UAAU;CACrE,OAAO,CACL,eAAe,gBAAgB,SAAS,EAAE,OAAO,2BAA2B,WAAW,UAAU,EAAE,aACnG,eAAe,gBAAgB,SAAS,EAAE,uBAAuB,gBAAgB,cAAc,GACjG;AACF;;AAGA,SAAgB,+BACd,WACA,gBACQ;CACR,OAAO,eAAe,gBAAgB,SAAS,EAAE,mBAAmB,gBAAgB,cAAc,EAAE;AACtG;;AAGA,SAAgB,mCACd,WACA,gBACQ;CACR,OAAO,eAAe,gBAAgB,SAAS,EAAE,uBAAuB,gBAAgB,cAAc,EAAE;AAC1G;;AAGA,SAAgB,kCACd,WACA,gBACA,SACQ;CACR,OAAO,yBAAyB,gBAAgB,cAAc,EAAE,MAAM,gBAAgB,SAAS,EAAE,MAAM,mBAAmB,OAAO,EAAE;AACrI;AAEA,SAAgB,+BACd,WACA,YACA,UAYI,CAAC,GACG;CACR,MAAM,QAAQ,sBACZ,WACA,YACA,QAAQ,QACR,QAAQ,gBACR,QAAQ,YACV;CACA,MAAM,aACJ,GAAG,MAAM,WAAW,aACP,MAAM,YAAY,MAAM,MAAM,cAAc,SAChD,MAAM,YAAY,mBAAmB,MAAM,aAAa;CACnE,IAAI,QAAQ,WACV,OAAO,wCAAwC;CAEjD,OACE,UAAU,MAAM,YAAY,sBAAsB,gBACjD,QAAQ,WAAW,aAAa;AAErC;AAEA,SAAgB,6BACd,WACA,YACA,UAKI,CAAC,GACG;CACR,MAAM,QAAQ,sBACZ,WACA,YACA,QAAQ,QACR,QAAQ,gBACR,QAAQ,YACV;CACA,MAAM,kBACJ,GAAG,MAAM,YAAY,6CACJ,MAAM,YAAY,SAAS,MAAM,cAAc;CAClE,IAAI,QAAQ,aAAa,OACvB,OACE,8BAA8B,gBAAgB,SAAS,EAAE,GAAG,gBAAgB,WAAW,MAAM,EAAE,4DAChD,gBAAgB,WAAW,eAAe,EAAE,GAAG,gBAAgB,WAAW,gBAAgB,EAAE;CAI/I,OACE,UAAU,gBAAgB,SAAS,EAAE,MAAM,MAAM,WAAW,OACrD,gBAAgB,WAAW,MAAM,EAAE,gBACjC;AAEb;AAEA,IAAM,0BAA0B;AAChC,IAAM,2BAA2B;;;;;;;AAOjC,IAAa,yBACX;;;;;;;;;;;AAYF,IAAa,qCACX;AAMF,SAAS,sBACP,WACA,YACA,SAAyB,YACzB,iBAAiB,OACjB,cACA;CACA,MAAM,aAAa,gBAAgB,uBAAuB;CAC1D,MAAM,cAAc,gBAAgB,wBAAwB;CAC5D,MAAM,cAAc,GAAG,WAAW,GAAG,gBAAgB,WAAW,MAAM;CACtE,MAAM,eAAe,GAAG,YAAY,GAAG,gBAAgB,WAAW,gBAAgB;CAClF,MAAM,oBAAoB,WACxB,aAAa,OAAO,aAAa,uBAAuB,SAAS,OAAO;CAC1E,MAAM,wBACJ,WAAW,cAAc,iBACpB,gBAAgB,UACjB,KAAA;CACN,MAAM,aACJ,0BAA0B,WAAW,0BAA0B,SAC3D,iBAAiB,WAAW,IAC5B;CACN,MAAM,cACJ,0BAA0B,YAAY,0BAA0B,SAC5D,iBAAiB,YAAY,IAC7B;CAEN,OAAO;EACL;EACA;EACA,YAAY,GAAG,gBAAgB,SAAS,EAAE,MAAM;EAChD,eAAe,GAAG,YAAY,KAAK;EACnC;EACA,aAAa,GAAG,gBAAgB,WAAW,eAAe,EAAE,MAAM;CACpE;AACF;AAEA,SAAgB,kBACd,QAEwB;CACxB,IAAI,OAAO,eAAe,OAAO,YAAY,SAAS,GACpD,OAAO,OAAO;CAEhB,OAAO,OAAO,QAAQ,OAAO,OAAO,CAAC,CAAC,SAAS,CAAC,QAAQ,gBAAgB;EACtE,IAAI,CAAC,WAAW,YAAY,OAAO,CAAC;EACpC,OAAO,CACL;GACE;GACA,iBAAiB,WAAW,WAAW;GACvC,kBAAkB,WAAW,WAAW;GACxC,UAAU,WAAW,WAAW;GAChC,UAAU,WAAW,WAAW;GAChC,GAAI,WAAW,WAAW,UACtB,EAAE,SAAS,WAAW,WAAW,QAAQ,IACzC,CAAC;EACP,CACF;CACF,CAAC;AACH;;AAGA,SAAgB,0BACd,YAIQ;CACR,OAAO,KAAK,UAAU;EACpB,WAAW;EACX,WAAW;EACX,WAAW;CACb,CAAC;AACH;;AAGA,SAAgB,2BACd,QAEA,QACwB;CACxB,OAAO,kBAAkB,MAAM,CAAC,CAAC,QAAQ,eAAe;EACtD,IAAI,WAAW,YAAY,KAAA,GAAW,OAAO;EAE7C,MAAM,+BAAe,IAAI,IAAoB;GAC3C;GACA;GACA;GACA;EACF,CAAC;EACD,IACE,WAAW,QAAQ,WAAW,KAC9B,WAAW,QAAQ,MAChB,mBAAmB,CAAC,aAAa,IAAI,cAAc,CACtD,GAEA,MAAM,IAAI,MACR,QAAQ,OAAO,gBAAgB,WAAW,OAAO,+GACnD;EAGF,OAAO,WAAW,QAAQ,SAAS,MAAM;CAC3C,CAAC;AACH;;AAGA,SAAgB,4BACd,QAEA,QACU;CACV,MAAM,cAAc,kBAAkB,MAAM;CAC5C,MAAM,qBAAqB,2BAA2B,QAAQ,MAAM;CACpE,MAAM,cAAc,IAAI,IACtB,mBAAmB,IAAI,yBAAyB,CAClD;CACA,MAAM,iBAAiB,IAAI,IACzB,mBAAmB,KAAK,eAAe,WAAW,eAAe,CACnE;CACA,MAAM,kBAAkB,IAAI,IAC1B,YACG,QACE,eAAe,CAAC,YAAY,IAAI,0BAA0B,UAAU,CAAC,CACxE,CAAC,CACA,KAAK,eAAe,WAAW,eAAe,CACnD;CAEA,OAAO,MAAM,qBACX,IAAI,IAAI,CACN,IAAI,OAAO,gBAAgB,CAAC,EAAA,CAAG,QAC5B,eACC,CAAC,gBAAgB,IAAI,UAAU,KAAK,eAAe,IAAI,UAAU,CACrE,GACA,GAAG,cACL,CAAC,CACH;AACF"}
1
+ {"version":3,"file":"foreign-key-ddl.js","names":[],"sources":["../../src/schema/foreign-key-ddl.ts"],"sourcesContent":["import type { DatabaseEngine } from './ddl/types.js';\nimport { requireForeignKeyAction } from './foreign-key-policy.js';\nimport { shortenIdentifier } from './index-utils.js';\nimport { quoteIdentifier, quoteStringLiteral } from './sql-identifiers.js';\nimport type { ForeignKeyDefinition, SchemaDefinition } from './types.js';\n\nexport type ForeignKeyUuidCastSide = 'child' | 'parent' | 'both' | 'none';\n\nexport function foreignKeyConstraintName(\n tableName: string,\n foreignKey: ForeignKeyDefinition,\n): string {\n return shortenIdentifier(\n `${tableName}_${foreignKey.column}_${foreignKey.referencesTable}_${foreignKey.referencesColumn}_fkey`,\n );\n}\n\nexport function renderForeignKeyConstraint(\n tableName: string,\n foreignKey: ForeignKeyDefinition,\n constraintName?: string,\n): string {\n const action = (value: unknown, clause: 'DELETE' | 'UPDATE') => {\n if (value === undefined) return undefined;\n return requireForeignKeyAction(\n value,\n `${tableName}.${foreignKey.column} ON ${clause}`,\n );\n };\n const onDelete = action(foreignKey.onDelete, 'DELETE');\n const onUpdate = action(foreignKey.onUpdate, 'UPDATE');\n const name =\n constraintName ?? foreignKeyConstraintName(tableName, foreignKey);\n const parts = [\n `CONSTRAINT ${quoteIdentifier(name)}`,\n `FOREIGN KEY (${quoteIdentifier(foreignKey.column)})`,\n `REFERENCES ${quoteIdentifier(foreignKey.referencesTable)} (${quoteIdentifier(foreignKey.referencesColumn)})`,\n ];\n if (onDelete) parts.push(`ON DELETE ${onDelete}`);\n if (onUpdate) parts.push(`ON UPDATE ${onUpdate}`);\n return parts.join(' ');\n}\n\n/**\n * Render the two-statement `ADD ... NOT VALID` + `VALIDATE CONSTRAINT`\n * sequence PostgreSQL uses to add a foreign key without holding a full-table\n * lock during validation. Shared by the differ's normal FK-add planning and\n * `db:migrate --null-orphans` (#2748), which reconstructs this same pair\n * after nulling out orphan references the differ refused to plan around.\n */\nexport function renderForeignKeyAddStatements(\n tableName: string,\n foreignKey: ForeignKeyDefinition,\n): string[] {\n const constraintName = foreignKeyConstraintName(tableName, foreignKey);\n return [\n `ALTER TABLE ${quoteIdentifier(tableName)} ADD ${renderForeignKeyConstraint(tableName, foreignKey)} NOT VALID`,\n `ALTER TABLE ${quoteIdentifier(tableName)} VALIDATE CONSTRAINT ${quoteIdentifier(constraintName)}`,\n ];\n}\n\n/**\n * Replace a framework-owned PostgreSQL foreign key whose only drift is its\n * referential action, without re-scanning the child table under the\n * migration's locks (#3023 review):\n *\n * 1. `ADD CONSTRAINT <name>_smrt_new … NOT VALID` — catalog only, no scan;\n * 2. `VALIDATE CONSTRAINT <name>_smrt_new` — the scan, before any\n * ACCESS EXCLUSIVE lock is taken; under `--postgres-safe` the planner runs\n * it (and the swap after it) outside the batch transaction, where\n * VALIDATE holds only SHARE UPDATE EXCLUSIVE and reads and writes go on;\n * 3. `DROP CONSTRAINT <name>` and `RENAME CONSTRAINT <name>_smrt_new TO\n * <name>` — catalog only.\n *\n * The child column is enforced by one of the two constraints at every step.\n */\nexport function renderForeignKeyReplaceStatements(\n tableName: string,\n foreignKey: ForeignKeyDefinition,\n liveConstraintName: string,\n): string[] {\n const constraintName = foreignKeyConstraintName(tableName, foreignKey);\n const staged = shortenIdentifier(`${constraintName}_smrt_new`);\n const table = quoteIdentifier(tableName);\n return [\n `ALTER TABLE ${table} ADD ${renderForeignKeyConstraint(tableName, foreignKey, staged)} NOT VALID`,\n `ALTER TABLE ${table} VALIDATE CONSTRAINT ${quoteIdentifier(staged)}`,\n `ALTER TABLE ${table} DROP CONSTRAINT ${quoteIdentifier(liveConstraintName)}`,\n `ALTER TABLE ${table} RENAME CONSTRAINT ${quoteIdentifier(staged)} TO ${quoteIdentifier(constraintName)}`,\n ];\n}\n\n/** Render a PostgreSQL DROP CONSTRAINT statement for a catalog-owned FK. */\nexport function renderForeignKeyConstraintDrop(\n tableName: string,\n constraintName: string,\n): string {\n return `ALTER TABLE ${quoteIdentifier(tableName)} DROP CONSTRAINT ${quoteIdentifier(constraintName)};`;\n}\n\n/** Render a PostgreSQL VALIDATE CONSTRAINT statement. */\nexport function renderForeignKeyConstraintValidate(\n tableName: string,\n constraintName: string,\n): string {\n return `ALTER TABLE ${quoteIdentifier(tableName)} VALIDATE CONSTRAINT ${quoteIdentifier(constraintName)};`;\n}\n\n/** Render a PostgreSQL constraint comment used to identify framework ownership. */\nexport function renderForeignKeyConstraintComment(\n tableName: string,\n constraintName: string,\n comment: string,\n): string {\n return `COMMENT ON CONSTRAINT ${quoteIdentifier(constraintName)} ON ${quoteIdentifier(tableName)} IS ${quoteStringLiteral(comment)};`;\n}\n\nexport function renderForeignKeyOrphanDetector(\n tableName: string,\n foreignKey: ForeignKeyDefinition,\n options: {\n engine?: DatabaseEngine;\n limitOne?: boolean;\n /**\n * Aggregate the same predicate as `COUNT(*)` instead of listing orphan\n * keys. Used by the read-only per-foreign-key orphan count report\n * (#2753) so the report shares the exact FROM/JOIN/WHERE clause the\n * migration gate probes with, rather than a second hand-written copy.\n */\n countOnly?: boolean;\n uuidComparison?: boolean;\n uuidCastSide?: ForeignKeyUuidCastSide;\n } = {},\n): string {\n const parts = foreignKeyOrphanParts(\n tableName,\n foreignKey,\n options.engine,\n options.uuidComparison,\n options.uuidCastSide,\n );\n const fromClause =\n `${parts.childTable} ` +\n `LEFT JOIN ${parts.parentTable} ON ${parts.joinPredicate} ` +\n `WHERE ${parts.childColumn} IS NOT NULL AND ${parts.parentColumn} IS NULL`;\n if (options.countOnly) {\n return `SELECT COUNT(*) AS orphan_count FROM ${fromClause}`;\n }\n return (\n `SELECT ${parts.childColumn} AS orphan_key FROM ${fromClause}` +\n (options.limitOne ? ' LIMIT 1' : '')\n );\n}\n\nexport function renderForeignKeyOrphanRepair(\n tableName: string,\n foreignKey: ForeignKeyDefinition,\n options: {\n engine?: DatabaseEngine;\n uuidComparison?: boolean;\n uuidCastSide?: ForeignKeyUuidCastSide;\n nullable?: boolean;\n } = {},\n): string {\n const parts = foreignKeyOrphanParts(\n tableName,\n foreignKey,\n options.engine,\n options.uuidComparison,\n options.uuidCastSide,\n );\n const orphanPredicate =\n `${parts.childColumn} IS NOT NULL AND NOT EXISTS (` +\n `SELECT 1 FROM ${parts.parentTable} WHERE ${parts.joinPredicate})`;\n if (options.nullable === false) {\n return (\n `-- Manual repair required: ${quoteIdentifier(tableName)}.${quoteIdentifier(foreignKey.column)} is NOT NULL. ` +\n `Reassign each orphaned reference to a valid ${quoteIdentifier(foreignKey.referencesTable)}.${quoteIdentifier(foreignKey.referencesColumn)} value, ` +\n 'or deliberately delete rows only after preserving any required child data.'\n );\n }\n return (\n `UPDATE ${quoteIdentifier(tableName)} AS ${parts.childAlias} ` +\n `SET ${quoteIdentifier(foreignKey.column)} = NULL ` +\n `WHERE ${orphanPredicate}`\n );\n}\n\nconst FOREIGN_KEY_CHILD_ALIAS = 'smrt_fk_child';\nconst FOREIGN_KEY_PARENT_ALIAS = 'smrt_fk_parent';\n/**\n * Canonical UUID text shape used by every PostgreSQL guard that has to decide\n * whether a `text` column can be reinterpreted as `uuid`. Exported so the\n * orphan probe, the FK provisioning guard, and the uuid convergence planner\n * all test the same shape (#2608).\n */\nexport const CANONICAL_UUID_PATTERN =\n '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$';\n\n/**\n * The same canonical UUID shape as {@link CANONICAL_UUID_PATTERN}, expressed\n * as a SQLite `GLOB` pattern instead of a regex — SQLite has no built-in\n * regex operator, so a shape probe there cannot push a regex predicate down\n * to the server. `GLOB` is case-sensitive, so callers must `LOWER()` the\n * value first; combine with a `LENGTH(...) = 36` check (`GLOB` alone allows\n * a shorter/longer value to still match a middle substring) so this can run\n * as a whole-table server-side aggregate instead of fetching every row into\n * JS to test in a loop (#2767 review).\n */\nexport const CANONICAL_UUID_SQLITE_GLOB_PATTERN =\n '[0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f]-' +\n '[0-9a-f][0-9a-f][0-9a-f][0-9a-f]-' +\n '[0-9a-f][0-9a-f][0-9a-f][0-9a-f]-' +\n '[0-9a-f][0-9a-f][0-9a-f][0-9a-f]-' +\n '[0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f]';\n\nfunction foreignKeyOrphanParts(\n tableName: string,\n foreignKey: ForeignKeyDefinition,\n engine: DatabaseEngine = 'postgres',\n uuidComparison = false,\n uuidCastSide?: ForeignKeyUuidCastSide,\n) {\n const childAlias = quoteIdentifier(FOREIGN_KEY_CHILD_ALIAS);\n const parentAlias = quoteIdentifier(FOREIGN_KEY_PARENT_ALIAS);\n const childColumn = `${childAlias}.${quoteIdentifier(foreignKey.column)}`;\n const parentColumn = `${parentAlias}.${quoteIdentifier(foreignKey.referencesColumn)}`;\n const guardedUuidValue = (column: string) =>\n `CASE WHEN ${column}::text ~* '${CANONICAL_UUID_PATTERN}' THEN ${column}::uuid ELSE NULL END`;\n const effectiveUuidCastSide =\n engine === 'postgres' && uuidComparison\n ? (uuidCastSide ?? 'child')\n : undefined;\n const childValue =\n effectiveUuidCastSide === 'child' || effectiveUuidCastSide === 'both'\n ? guardedUuidValue(childColumn)\n : childColumn;\n const parentValue =\n effectiveUuidCastSide === 'parent' || effectiveUuidCastSide === 'both'\n ? guardedUuidValue(parentColumn)\n : parentColumn;\n\n return {\n childAlias,\n childColumn,\n childTable: `${quoteIdentifier(tableName)} AS ${childAlias}`,\n joinPredicate: `${parentValue} = ${childValue}`,\n parentColumn,\n parentTable: `${quoteIdentifier(foreignKey.referencesTable)} AS ${parentAlias}`,\n };\n}\n\nexport function schemaForeignKeys(\n schema: Pick<SchemaDefinition, 'columns'> &\n Partial<Pick<SchemaDefinition, 'foreignKeys'>>,\n): ForeignKeyDefinition[] {\n if (schema.foreignKeys && schema.foreignKeys.length > 0) {\n return schema.foreignKeys;\n }\n return Object.entries(schema.columns).flatMap(([column, definition]) => {\n if (!definition.foreignKey) return [];\n return [\n {\n column,\n referencesTable: definition.foreignKey.table,\n referencesColumn: definition.foreignKey.column,\n onDelete: definition.foreignKey.onDelete,\n onUpdate: definition.foreignKey.onUpdate,\n ...(definition.foreignKey.engines\n ? { engines: definition.foreignKey.engines }\n : {}),\n },\n ];\n });\n}\n\n/** Stable identity for one physical relationship, independent of allocation. */\nexport function foreignKeyRelationshipKey(\n foreignKey: Pick<\n ForeignKeyDefinition,\n 'column' | 'referencesColumn' | 'referencesTable'\n >,\n): string {\n return JSON.stringify([\n foreignKey.column,\n foreignKey.referencesTable,\n foreignKey.referencesColumn,\n ]);\n}\n\n/** Return only physical constraints explicitly enabled for an engine. */\nexport function schemaForeignKeysForEngine(\n schema: Pick<SchemaDefinition, 'columns'> &\n Partial<Pick<SchemaDefinition, 'foreignKeys'>>,\n engine: DatabaseEngine,\n): ForeignKeyDefinition[] {\n return schemaForeignKeys(schema).filter((foreignKey) => {\n if (foreignKey.engines === undefined) return true;\n\n const validEngines = new Set<DatabaseEngine>([\n 'postgres',\n 'sqlite',\n 'duckdb',\n 'json',\n ]);\n if (\n foreignKey.engines.length === 0 ||\n foreignKey.engines.some(\n (declaredEngine) => !validEngines.has(declaredEngine),\n )\n ) {\n throw new Error(\n `[DDL:${engine}] Foreign key ${foreignKey.column} declares an invalid physical constraint engine allowlist. Use one or more of: postgres, sqlite, duckdb, json.`,\n );\n }\n\n return foreignKey.engines.includes(engine);\n });\n}\n\n/** Return declared schema dependencies after excluding engine-disabled FKs. */\nexport function schemaDependenciesForEngine(\n schema: Pick<SchemaDefinition, 'columns'> &\n Partial<Pick<SchemaDefinition, 'dependencies' | 'foreignKeys'>>,\n engine: DatabaseEngine,\n): string[] {\n const foreignKeys = schemaForeignKeys(schema);\n const enabledForeignKeys = schemaForeignKeysForEngine(schema, engine);\n const enabledKeys = new Set(\n enabledForeignKeys.map(foreignKeyRelationshipKey),\n );\n const enabledTargets = new Set(\n enabledForeignKeys.map((foreignKey) => foreignKey.referencesTable),\n );\n const disabledTargets = new Set(\n foreignKeys\n .filter(\n (foreignKey) => !enabledKeys.has(foreignKeyRelationshipKey(foreignKey)),\n )\n .map((foreignKey) => foreignKey.referencesTable),\n );\n\n return Array.from(\n new Set([\n ...(schema.dependencies ?? []).filter(\n (dependency) =>\n !disabledTargets.has(dependency) || enabledTargets.has(dependency),\n ),\n ...enabledTargets,\n ]),\n );\n}\n"],"mappings":";;;;AAQA,SAAgB,yBACd,WACA,YACQ;CACR,OAAO,kBACL,GAAG,UAAU,GAAG,WAAW,OAAO,GAAG,WAAW,gBAAgB,GAAG,WAAW,iBAAiB,MACjG;AACF;AAEA,SAAgB,2BACd,WACA,YACA,gBACQ;CACR,MAAM,UAAU,OAAgB,WAAgC;EAC9D,IAAI,UAAU,KAAA,GAAW,OAAO,KAAA;EAChC,OAAO,wBACL,OACA,GAAG,UAAU,GAAG,WAAW,OAAO,MAAM,QAC1C;CACF;CACA,MAAM,WAAW,OAAO,WAAW,UAAU,QAAQ;CACrD,MAAM,WAAW,OAAO,WAAW,UAAU,QAAQ;CAGrD,MAAM,QAAQ;EACZ,cAAc,gBAFd,kBAAkB,yBAAyB,WAAW,UAAU,CAE9B;EAClC,gBAAgB,gBAAgB,WAAW,MAAM,EAAE;EACnD,cAAc,gBAAgB,WAAW,eAAe,EAAE,IAAI,gBAAgB,WAAW,gBAAgB,EAAE;CAC7G;CACA,IAAI,UAAU,MAAM,KAAK,aAAa,UAAU;CAChD,IAAI,UAAU,MAAM,KAAK,aAAa,UAAU;CAChD,OAAO,MAAM,KAAK,GAAG;AACvB;;;;;;;;AASA,SAAgB,8BACd,WACA,YACU;CACV,MAAM,iBAAiB,yBAAyB,WAAW,UAAU;CACrE,OAAO,CACL,eAAe,gBAAgB,SAAS,EAAE,OAAO,2BAA2B,WAAW,UAAU,EAAE,aACnG,eAAe,gBAAgB,SAAS,EAAE,uBAAuB,gBAAgB,cAAc,GACjG;AACF;;;;;;;;;;;;;;;;AAiBA,SAAgB,kCACd,WACA,YACA,oBACU;CACV,MAAM,iBAAiB,yBAAyB,WAAW,UAAU;CACrE,MAAM,SAAS,kBAAkB,GAAG,eAAe,UAAU;CAC7D,MAAM,QAAQ,gBAAgB,SAAS;CACvC,OAAO;EACL,eAAe,MAAM,OAAO,2BAA2B,WAAW,YAAY,MAAM,EAAE;EACtF,eAAe,MAAM,uBAAuB,gBAAgB,MAAM;EAClE,eAAe,MAAM,mBAAmB,gBAAgB,kBAAkB;EAC1E,eAAe,MAAM,qBAAqB,gBAAgB,MAAM,EAAE,MAAM,gBAAgB,cAAc;CACxG;AACF;;AAGA,SAAgB,+BACd,WACA,gBACQ;CACR,OAAO,eAAe,gBAAgB,SAAS,EAAE,mBAAmB,gBAAgB,cAAc,EAAE;AACtG;;AAGA,SAAgB,mCACd,WACA,gBACQ;CACR,OAAO,eAAe,gBAAgB,SAAS,EAAE,uBAAuB,gBAAgB,cAAc,EAAE;AAC1G;;AAGA,SAAgB,kCACd,WACA,gBACA,SACQ;CACR,OAAO,yBAAyB,gBAAgB,cAAc,EAAE,MAAM,gBAAgB,SAAS,EAAE,MAAM,mBAAmB,OAAO,EAAE;AACrI;AAEA,SAAgB,+BACd,WACA,YACA,UAYI,CAAC,GACG;CACR,MAAM,QAAQ,sBACZ,WACA,YACA,QAAQ,QACR,QAAQ,gBACR,QAAQ,YACV;CACA,MAAM,aACJ,GAAG,MAAM,WAAW,aACP,MAAM,YAAY,MAAM,MAAM,cAAc,SAChD,MAAM,YAAY,mBAAmB,MAAM,aAAa;CACnE,IAAI,QAAQ,WACV,OAAO,wCAAwC;CAEjD,OACE,UAAU,MAAM,YAAY,sBAAsB,gBACjD,QAAQ,WAAW,aAAa;AAErC;AAEA,SAAgB,6BACd,WACA,YACA,UAKI,CAAC,GACG;CACR,MAAM,QAAQ,sBACZ,WACA,YACA,QAAQ,QACR,QAAQ,gBACR,QAAQ,YACV;CACA,MAAM,kBACJ,GAAG,MAAM,YAAY,6CACJ,MAAM,YAAY,SAAS,MAAM,cAAc;CAClE,IAAI,QAAQ,aAAa,OACvB,OACE,8BAA8B,gBAAgB,SAAS,EAAE,GAAG,gBAAgB,WAAW,MAAM,EAAE,4DAChD,gBAAgB,WAAW,eAAe,EAAE,GAAG,gBAAgB,WAAW,gBAAgB,EAAE;CAI/I,OACE,UAAU,gBAAgB,SAAS,EAAE,MAAM,MAAM,WAAW,OACrD,gBAAgB,WAAW,MAAM,EAAE,gBACjC;AAEb;AAEA,IAAM,0BAA0B;AAChC,IAAM,2BAA2B;;;;;;;AAOjC,IAAa,yBACX;;;;;;;;;;;AAYF,IAAa,qCACX;AAMF,SAAS,sBACP,WACA,YACA,SAAyB,YACzB,iBAAiB,OACjB,cACA;CACA,MAAM,aAAa,gBAAgB,uBAAuB;CAC1D,MAAM,cAAc,gBAAgB,wBAAwB;CAC5D,MAAM,cAAc,GAAG,WAAW,GAAG,gBAAgB,WAAW,MAAM;CACtE,MAAM,eAAe,GAAG,YAAY,GAAG,gBAAgB,WAAW,gBAAgB;CAClF,MAAM,oBAAoB,WACxB,aAAa,OAAO,aAAa,uBAAuB,SAAS,OAAO;CAC1E,MAAM,wBACJ,WAAW,cAAc,iBACpB,gBAAgB,UACjB,KAAA;CACN,MAAM,aACJ,0BAA0B,WAAW,0BAA0B,SAC3D,iBAAiB,WAAW,IAC5B;CACN,MAAM,cACJ,0BAA0B,YAAY,0BAA0B,SAC5D,iBAAiB,YAAY,IAC7B;CAEN,OAAO;EACL;EACA;EACA,YAAY,GAAG,gBAAgB,SAAS,EAAE,MAAM;EAChD,eAAe,GAAG,YAAY,KAAK;EACnC;EACA,aAAa,GAAG,gBAAgB,WAAW,eAAe,EAAE,MAAM;CACpE;AACF;AAEA,SAAgB,kBACd,QAEwB;CACxB,IAAI,OAAO,eAAe,OAAO,YAAY,SAAS,GACpD,OAAO,OAAO;CAEhB,OAAO,OAAO,QAAQ,OAAO,OAAO,CAAC,CAAC,SAAS,CAAC,QAAQ,gBAAgB;EACtE,IAAI,CAAC,WAAW,YAAY,OAAO,CAAC;EACpC,OAAO,CACL;GACE;GACA,iBAAiB,WAAW,WAAW;GACvC,kBAAkB,WAAW,WAAW;GACxC,UAAU,WAAW,WAAW;GAChC,UAAU,WAAW,WAAW;GAChC,GAAI,WAAW,WAAW,UACtB,EAAE,SAAS,WAAW,WAAW,QAAQ,IACzC,CAAC;EACP,CACF;CACF,CAAC;AACH;;AAGA,SAAgB,0BACd,YAIQ;CACR,OAAO,KAAK,UAAU;EACpB,WAAW;EACX,WAAW;EACX,WAAW;CACb,CAAC;AACH;;AAGA,SAAgB,2BACd,QAEA,QACwB;CACxB,OAAO,kBAAkB,MAAM,CAAC,CAAC,QAAQ,eAAe;EACtD,IAAI,WAAW,YAAY,KAAA,GAAW,OAAO;EAE7C,MAAM,+BAAe,IAAI,IAAoB;GAC3C;GACA;GACA;GACA;EACF,CAAC;EACD,IACE,WAAW,QAAQ,WAAW,KAC9B,WAAW,QAAQ,MAChB,mBAAmB,CAAC,aAAa,IAAI,cAAc,CACtD,GAEA,MAAM,IAAI,MACR,QAAQ,OAAO,gBAAgB,WAAW,OAAO,+GACnD;EAGF,OAAO,WAAW,QAAQ,SAAS,MAAM;CAC3C,CAAC;AACH;;AAGA,SAAgB,4BACd,QAEA,QACU;CACV,MAAM,cAAc,kBAAkB,MAAM;CAC5C,MAAM,qBAAqB,2BAA2B,QAAQ,MAAM;CACpE,MAAM,cAAc,IAAI,IACtB,mBAAmB,IAAI,yBAAyB,CAClD;CACA,MAAM,iBAAiB,IAAI,IACzB,mBAAmB,KAAK,eAAe,WAAW,eAAe,CACnE;CACA,MAAM,kBAAkB,IAAI,IAC1B,YACG,QACE,eAAe,CAAC,YAAY,IAAI,0BAA0B,UAAU,CAAC,CACxE,CAAC,CACA,KAAK,eAAe,WAAW,eAAe,CACnD;CAEA,OAAO,MAAM,qBACX,IAAI,IAAI,CACN,IAAI,OAAO,gBAAgB,CAAC,EAAA,CAAG,QAC5B,eACC,CAAC,gBAAgB,IAAI,UAAU,KAAK,eAAe,IAAI,UAAU,CACrE,GACA,GAAG,cACL,CAAC,CACH;AACF"}
@@ -14,6 +14,14 @@ export declare function resolveForeignKeyDeleteAction(options: {
14
14
  declared?: unknown;
15
15
  isConflictColumn: boolean;
16
16
  isTenantIdField: boolean;
17
+ /**
18
+ * The referencing column. The implicit tenant ownership column
19
+ * (`tenant_id`) leads a tenant-owned table's default natural key without
20
+ * identifying the row, so like a `@tenantId()` marker it never turns a
21
+ * reference into an ownership CASCADE (deleting a tenant must not silently
22
+ * cascade through every tenant-owned table).
23
+ */
24
+ columnName?: string;
17
25
  }): {
18
26
  action: ForeignKeyAction;
19
27
  declared: boolean;
@@ -1 +1 @@
1
- {"version":3,"file":"foreign-key-policy.d.ts","sourceRoot":"","sources":["../../src/schema/foreign-key-policy.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AASnD,mEAAmE;AACnE,wBAAgB,yBAAyB,CACvC,KAAK,EAAE,OAAO,GACb,gBAAgB,GAAG,SAAS,CAM9B;AAED,gFAAgF;AAChF,wBAAgB,uBAAuB,CACrC,KAAK,EAAE,OAAO,EACd,OAAO,EAAE,MAAM,GACd,gBAAgB,CAQlB;AAED;;;;;;GAMG;AACH,wBAAgB,6BAA6B,CAAC,OAAO,EAAE;IACrD,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,gBAAgB,EAAE,OAAO,CAAC;IAC1B,eAAe,EAAE,OAAO,CAAC;CAC1B,GAAG;IAAE,MAAM,EAAE,gBAAgB,CAAC;IAAC,QAAQ,EAAE,OAAO,CAAA;CAAE,CAelD"}
1
+ {"version":3,"file":"foreign-key-policy.d.ts","sourceRoot":"","sources":["../../src/schema/foreign-key-policy.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AASnD,mEAAmE;AACnE,wBAAgB,yBAAyB,CACvC,KAAK,EAAE,OAAO,GACb,gBAAgB,GAAG,SAAS,CAM9B;AAED,gFAAgF;AAChF,wBAAgB,uBAAuB,CACrC,KAAK,EAAE,OAAO,EACd,OAAO,EAAE,MAAM,GACd,gBAAgB,CAQlB;AAED;;;;;;GAMG;AACH,wBAAgB,6BAA6B,CAAC,OAAO,EAAE;IACrD,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,gBAAgB,EAAE,OAAO,CAAC;IAC1B,eAAe,EAAE,OAAO,CAAC;IACzB;;;;;;OAMG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB,GAAG;IAAE,MAAM,EAAE,gBAAgB,CAAC;IAAC,QAAQ,EAAE,OAAO,CAAA;CAAE,CAiBlD"}
@@ -1,3 +1,4 @@
1
+ import "./conflict-target.js";
1
2
  //#region src/schema/foreign-key-policy.ts
2
3
  var FOREIGN_KEY_ACTIONS = /* @__PURE__ */ new Set([
3
4
  "CASCADE",
@@ -31,7 +32,7 @@ function resolveForeignKeyDeleteAction(options) {
31
32
  declared: true
32
33
  };
33
34
  return {
34
- action: !options.isTenantIdField && options.isConflictColumn ? "CASCADE" : "NO ACTION",
35
+ action: !options.isTenantIdField && options.columnName !== "tenant_id" && options.isConflictColumn ? "CASCADE" : "NO ACTION",
35
36
  declared: false
36
37
  };
37
38
  }
@@ -1 +1 @@
1
- {"version":3,"file":"foreign-key-policy.js","names":[],"sources":["../../src/schema/foreign-key-policy.ts"],"sourcesContent":["import type { ForeignKeyAction } from './types.js';\n\nconst FOREIGN_KEY_ACTIONS = new Set<ForeignKeyAction>([\n 'CASCADE',\n 'SET NULL',\n 'RESTRICT',\n 'NO ACTION',\n]);\n\n/** Normalize decorator action spelling into the SQL vocabulary. */\nexport function normalizeForeignKeyAction(\n value: unknown,\n): ForeignKeyAction | undefined {\n if (typeof value !== 'string') return undefined;\n const normalized = value.trim().toUpperCase().replace(/_/g, ' ');\n return FOREIGN_KEY_ACTIONS.has(normalized as ForeignKeyAction)\n ? (normalized as ForeignKeyAction)\n : undefined;\n}\n\n/** Validate an externally supplied action rather than interpolating raw SQL. */\nexport function requireForeignKeyAction(\n value: unknown,\n context: string,\n): ForeignKeyAction {\n const normalized = normalizeForeignKeyAction(value);\n if (!normalized) {\n throw new Error(\n `Invalid foreign-key action ${JSON.stringify(value)} for ${context}. Expected CASCADE, SET NULL, RESTRICT, or NO ACTION.`,\n );\n }\n return normalized;\n}\n\n/**\n * Resolve the one delete policy shared by app-side cascade and database DDL.\n *\n * A same-package reference that identifies the referencing row defaults to\n * CASCADE. Tenant ids are scope markers rather than ownership edges, and all\n * other references default to SQL NO ACTION.\n */\nexport function resolveForeignKeyDeleteAction(options: {\n declared?: unknown;\n isConflictColumn: boolean;\n isTenantIdField: boolean;\n}): { action: ForeignKeyAction; declared: boolean } {\n const declaredAction =\n options.declared === undefined\n ? undefined\n : requireForeignKeyAction(options.declared, 'ON DELETE');\n if (declaredAction) {\n return { action: declaredAction, declared: true };\n }\n return {\n action:\n !options.isTenantIdField && options.isConflictColumn\n ? 'CASCADE'\n : 'NO ACTION',\n declared: false,\n };\n}\n"],"mappings":";AAEA,IAAM,sCAAsB,IAAI,IAAsB;CACpD;CACA;CACA;CACA;AACF,CAAC;;AAGD,SAAgB,0BACd,OAC8B;CAC9B,IAAI,OAAO,UAAU,UAAU,OAAO,KAAA;CACtC,MAAM,aAAa,MAAM,KAAK,CAAC,CAAC,YAAY,CAAC,CAAC,QAAQ,MAAM,GAAG;CAC/D,OAAO,oBAAoB,IAAI,UAA8B,IACxD,aACD,KAAA;AACN;;AAGA,SAAgB,wBACd,OACA,SACkB;CAClB,MAAM,aAAa,0BAA0B,KAAK;CAClD,IAAI,CAAC,YACH,MAAM,IAAI,MACR,8BAA8B,KAAK,UAAU,KAAK,EAAE,OAAO,QAAQ,sDACrE;CAEF,OAAO;AACT;;;;;;;;AASA,SAAgB,8BAA8B,SAIM;CAClD,MAAM,iBACJ,QAAQ,aAAa,KAAA,IACjB,KAAA,IACA,wBAAwB,QAAQ,UAAU,WAAW;CAC3D,IAAI,gBACF,OAAO;EAAE,QAAQ;EAAgB,UAAU;CAAK;CAElD,OAAO;EACL,QACE,CAAC,QAAQ,mBAAmB,QAAQ,mBAChC,YACA;EACN,UAAU;CACZ;AACF"}
1
+ {"version":3,"file":"foreign-key-policy.js","names":[],"sources":["../../src/schema/foreign-key-policy.ts"],"sourcesContent":["import { IMPLICIT_TENANT_COLUMN } from './conflict-target.js';\nimport type { ForeignKeyAction } from './types.js';\n\nconst FOREIGN_KEY_ACTIONS = new Set<ForeignKeyAction>([\n 'CASCADE',\n 'SET NULL',\n 'RESTRICT',\n 'NO ACTION',\n]);\n\n/** Normalize decorator action spelling into the SQL vocabulary. */\nexport function normalizeForeignKeyAction(\n value: unknown,\n): ForeignKeyAction | undefined {\n if (typeof value !== 'string') return undefined;\n const normalized = value.trim().toUpperCase().replace(/_/g, ' ');\n return FOREIGN_KEY_ACTIONS.has(normalized as ForeignKeyAction)\n ? (normalized as ForeignKeyAction)\n : undefined;\n}\n\n/** Validate an externally supplied action rather than interpolating raw SQL. */\nexport function requireForeignKeyAction(\n value: unknown,\n context: string,\n): ForeignKeyAction {\n const normalized = normalizeForeignKeyAction(value);\n if (!normalized) {\n throw new Error(\n `Invalid foreign-key action ${JSON.stringify(value)} for ${context}. Expected CASCADE, SET NULL, RESTRICT, or NO ACTION.`,\n );\n }\n return normalized;\n}\n\n/**\n * Resolve the one delete policy shared by app-side cascade and database DDL.\n *\n * A same-package reference that identifies the referencing row defaults to\n * CASCADE. Tenant ids are scope markers rather than ownership edges, and all\n * other references default to SQL NO ACTION.\n */\nexport function resolveForeignKeyDeleteAction(options: {\n declared?: unknown;\n isConflictColumn: boolean;\n isTenantIdField: boolean;\n /**\n * The referencing column. The implicit tenant ownership column\n * (`tenant_id`) leads a tenant-owned table's default natural key without\n * identifying the row, so like a `@tenantId()` marker it never turns a\n * reference into an ownership CASCADE (deleting a tenant must not silently\n * cascade through every tenant-owned table).\n */\n columnName?: string;\n}): { action: ForeignKeyAction; declared: boolean } {\n const declaredAction =\n options.declared === undefined\n ? undefined\n : requireForeignKeyAction(options.declared, 'ON DELETE');\n if (declaredAction) {\n return { action: declaredAction, declared: true };\n }\n return {\n action:\n !options.isTenantIdField &&\n options.columnName !== IMPLICIT_TENANT_COLUMN &&\n options.isConflictColumn\n ? 'CASCADE'\n : 'NO ACTION',\n declared: false,\n };\n}\n"],"mappings":";;AAGA,IAAM,sCAAsB,IAAI,IAAsB;CACpD;CACA;CACA;CACA;AACF,CAAC;;AAGD,SAAgB,0BACd,OAC8B;CAC9B,IAAI,OAAO,UAAU,UAAU,OAAO,KAAA;CACtC,MAAM,aAAa,MAAM,KAAK,CAAC,CAAC,YAAY,CAAC,CAAC,QAAQ,MAAM,GAAG;CAC/D,OAAO,oBAAoB,IAAI,UAA8B,IACxD,aACD,KAAA;AACN;;AAGA,SAAgB,wBACd,OACA,SACkB;CAClB,MAAM,aAAa,0BAA0B,KAAK;CAClD,IAAI,CAAC,YACH,MAAM,IAAI,MACR,8BAA8B,KAAK,UAAU,KAAK,EAAE,OAAO,QAAQ,sDACrE;CAEF,OAAO;AACT;;;;;;;;AASA,SAAgB,8BAA8B,SAYM;CAClD,MAAM,iBACJ,QAAQ,aAAa,KAAA,IACjB,KAAA,IACA,wBAAwB,QAAQ,UAAU,WAAW;CAC3D,IAAI,gBACF,OAAO;EAAE,QAAQ;EAAgB,UAAU;CAAK;CAElD,OAAO;EACL,QACE,CAAC,QAAQ,mBACT,QAAQ,eAAA,eACR,QAAQ,mBACJ,YACA;EACN,UAAU;CACZ;AACF"}
@@ -75,6 +75,13 @@ export declare class SchemaGenerator {
75
75
  private getIdColumnType;
76
76
  private getRelationshipColumnType;
77
77
  private getReferenceKind;
78
+ /**
79
+ * A uuid column never defaults to '' (same rule as the manifest generator
80
+ * and the registry schema builder, 9832f70fa): PostgreSQL stores
81
+ * `(''::text)::uuid` unevaluated and every INSERT that omits the column
82
+ * fails with 22P02. Judged on the final column type, so a plain
83
+ * `@field({ sqlType: 'UUID' }) tenantId = ''` (TenantKey) is covered too.
84
+ */
78
85
  private shouldEmitDefault;
79
86
  private getRegistryTargetIdColumnType;
80
87
  private reconcileRegistryForeignKeyColumnTypes;
@@ -1 +1 @@
1
- {"version":3,"file":"generator.d.ts","sourceRoot":"","sources":["../../src/schema/generator.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"generator.d.ts","sourceRoot":"","sources":["../../src/schema/generator.ts"],"names":[],"mappings":"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"}
@@ -1,9 +1,9 @@
1
+ import { conflictIndexName, nullableConflictIdentity, resolveConflictColumns, resolveOwnershipTenantColumn, resolveTenantColumn, servesSlugLookup } from "./conflict-target.js";
1
2
  import { resolveForeignKeyDeleteAction } from "./foreign-key-policy.js";
2
3
  import { classnameToTablename } from "../utils/naming.js";
3
4
  import { formatDefaultValue, quoteIdentifier, quoteStringLiteral } from "./sql-identifiers.js";
4
5
  import { assertIdentifierFits, enforceIdentifierLimits } from "./index-utils.js";
5
6
  import { getDDLStrategy } from "./ddl/index.js";
6
- import { conflictIndexName, nullableConflictIdentity, resolveConflictColumns, resolveTenantColumn, servesSlugLookup } from "./conflict-target.js";
7
7
  import { normalizeBackfill } from "./backfill.js";
8
8
  import { createHash } from "node:crypto";
9
9
  //#region src/schema/generator.ts
@@ -45,8 +45,15 @@ var SchemaGenerator = class {
45
45
  if (field?.type === "foreignKey") return "foreignKey";
46
46
  if (field?.type === "crossPackageRef") return "crossPackageRef";
47
47
  }
48
- shouldEmitDefault(field, defaultValue) {
49
- return !(this.getReferenceKind(field) === "tenantId" && this.getRelationshipColumnType(field) === "UUID" && defaultValue === "");
48
+ /**
49
+ * A uuid column never defaults to '' (same rule as the manifest generator
50
+ * and the registry schema builder, 9832f70fa): PostgreSQL stores
51
+ * `(''::text)::uuid` unevaluated and every INSERT that omits the column
52
+ * fails with 22P02. Judged on the final column type, so a plain
53
+ * `@field({ sqlType: 'UUID' }) tenantId = ''` (TenantKey) is covered too.
54
+ */
55
+ shouldEmitDefault(columnType, defaultValue) {
56
+ return !(String(columnType).toUpperCase() === "UUID" && defaultValue === "");
50
57
  }
51
58
  getRegistryTargetIdColumnType(relatedName, registry) {
52
59
  if (!relatedName || !registry?.getConfig) return;
@@ -103,7 +110,8 @@ var SchemaGenerator = class {
103
110
  const { action } = resolveForeignKeyDeleteAction({
104
111
  declared: field._meta?.onDelete,
105
112
  isConflictColumn: conflictSet.has(columnName),
106
- isTenantIdField
113
+ isTenantIdField,
114
+ columnName
107
115
  });
108
116
  column.foreignKey = {
109
117
  table: targetTable,
@@ -422,7 +430,7 @@ var SchemaGenerator = class {
422
430
  return {
423
431
  conflictColumns: resolveConflictColumns({
424
432
  strategy,
425
- tenantColumn: tenantScoped ? resolveTenantColumn(typeof tenantScoped === "object" ? tenantScoped.field ?? "tenantId" : "tenantId", (fieldName) => Boolean(columns[this.toSnakeCase(fieldName)]), (fieldName) => this.toSnakeCase(fieldName)) : void 0
433
+ tenantColumn: resolveOwnershipTenantColumn(tenantScoped ? resolveTenantColumn(typeof tenantScoped === "object" ? tenantScoped.field ?? "tenantId" : "tenantId", (fieldName) => Boolean(columns[this.toSnakeCase(fieldName)]), (fieldName) => this.toSnakeCase(fieldName)) : void 0, (column) => Boolean(columns[column]))
426
434
  }),
427
435
  tenantColumn
428
436
  };
@@ -448,7 +456,7 @@ var SchemaGenerator = class {
448
456
  */
449
457
  ensureSlugLookupIndex(indexes, columns, tableName) {
450
458
  if (!columns.slug || !columns.context) return;
451
- const tenantColumn = this.findTenantColumn(columns);
459
+ const tenantColumn = resolveOwnershipTenantColumn(this.findTenantColumn(columns), (column) => Boolean(columns[column]));
452
460
  if (indexes.some((index) => !index.where && !index.jsonPath && servesSlugLookup(index.columns ?? [], tenantColumn))) return;
453
461
  const name = `${tableName}_slug_context_idx`;
454
462
  if (indexes.some((index) => index.name === name)) return;
@@ -623,7 +631,7 @@ var SchemaGenerator = class {
623
631
  unique: field._meta?.unique || false,
624
632
  description: field._meta?.description
625
633
  };
626
- if (field._meta?.default !== void 0 && this.shouldEmitDefault(field, field._meta.default)) columnDef.defaultValue = field._meta.default;
634
+ if (field._meta?.default !== void 0 && this.shouldEmitDefault(columnDef.type, field._meta.default)) columnDef.defaultValue = field._meta.default;
627
635
  const backfill = normalizeBackfill(field._meta?.backfill);
628
636
  if (backfill !== void 0) columnDef.backfill = backfill;
629
637
  if (field.type === "foreignKey" && field._meta?.constraint !== false && field._meta?.__tenancy?.isTenantIdField !== true) {
@@ -814,7 +822,7 @@ var SchemaGenerator = class {
814
822
  unique: false,
815
823
  description: field._meta?.description
816
824
  };
817
- if (field._meta?.default !== void 0 && this.shouldEmitDefault(field, field._meta.default)) columnDef.defaultValue = field._meta.default;
825
+ if (field._meta?.default !== void 0 && this.shouldEmitDefault(columnDef.type, field._meta.default)) columnDef.defaultValue = field._meta.default;
818
826
  if (field.type === "foreignKey" && field._meta?.constraint !== false && field._meta?.__tenancy?.isTenantIdField !== true) {
819
827
  const relatedName = field.related;
820
828
  const onDeleteAction = field._meta?.onDelete;
@@ -971,7 +979,7 @@ var SchemaGenerator = class {
971
979
  referenceKind: this.getReferenceKind(field),
972
980
  notNull: false
973
981
  };
974
- if (field.default !== void 0 && this.shouldEmitDefault(field, field.default)) columnDef.default = field.default;
982
+ if (field.default !== void 0 && this.shouldEmitDefault(columnDef.type, field.default)) columnDef.default = field.default;
975
983
  if ((indexedField.indexed === true || field._meta?.indexed === true) && field.type !== "foreignKey") indexedStiColumns.add(columnName);
976
984
  columns[columnName] = columnDef;
977
985
  }
@@ -1078,7 +1086,7 @@ var SchemaGenerator = class {
1078
1086
  notNull: field._meta?.nullable ? false : field.required || false,
1079
1087
  unique: field._meta?.unique || false
1080
1088
  };
1081
- if (field.default !== void 0 && this.shouldEmitDefault(field, field.default)) columnDef.default = field.default;
1089
+ if (field.default !== void 0 && this.shouldEmitDefault(columnDef.type, field.default)) columnDef.default = field.default;
1082
1090
  if ((field.indexed === true || field._meta?.indexed === true) && field.type !== "foreignKey" && !columnDef.unique) indexedColumns.add(columnName);
1083
1091
  columns[columnName] = columnDef;
1084
1092
  }