@happyvertical/smrt-core 0.49.3 → 0.49.5

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 (156) hide show
  1. package/README.md +20 -0
  2. package/agents/change-feed.md +19 -0
  3. package/agents/collection-reads.md +21 -0
  4. package/agents/generators.md +5 -0
  5. package/agents/memory.md +23 -3
  6. package/agents/null-equal-indexes.md +88 -0
  7. package/agents/object-runtime.md +85 -0
  8. package/agents/schema-paths.md +7 -7
  9. package/dist/browser.js +2 -2
  10. package/dist/cascade.d.ts.map +1 -1
  11. package/dist/cascade.js +13 -10
  12. package/dist/cascade.js.map +1 -1
  13. package/dist/change-feed.d.ts +22 -0
  14. package/dist/change-feed.d.ts.map +1 -1
  15. package/dist/change-feed.js +147 -6
  16. package/dist/change-feed.js.map +1 -1
  17. package/dist/class.js +1 -1
  18. package/dist/collection-cache.d.ts +10 -1
  19. package/dist/collection-cache.d.ts.map +1 -1
  20. package/dist/collection-cache.js +65 -10
  21. package/dist/collection-cache.js.map +1 -1
  22. package/dist/collection.d.ts +69 -2
  23. package/dist/collection.d.ts.map +1 -1
  24. package/dist/collection.js +142 -9
  25. package/dist/collection.js.map +1 -1
  26. package/dist/consumer-plugin/artifact-publication.d.ts +27 -0
  27. package/dist/consumer-plugin/artifact-publication.d.ts.map +1 -0
  28. package/dist/consumer-plugin/artifact-publication.js +80 -0
  29. package/dist/consumer-plugin/artifact-publication.js.map +1 -0
  30. package/dist/consumer-plugin/index.d.ts.map +1 -1
  31. package/dist/consumer-plugin/index.js +44 -6
  32. package/dist/consumer-plugin/index.js.map +1 -1
  33. package/dist/db-errors.js +1 -1
  34. package/dist/db-errors.js.map +1 -1
  35. package/dist/embedded-write-queue.d.ts +1 -1
  36. package/dist/embedded-write-queue.d.ts.map +1 -1
  37. package/dist/embedded-write-queue.js +16 -6
  38. package/dist/embedded-write-queue.js.map +1 -1
  39. package/dist/embeddings/storage.d.ts +16 -0
  40. package/dist/embeddings/storage.d.ts.map +1 -1
  41. package/dist/embeddings/storage.js +40 -0
  42. package/dist/embeddings/storage.js.map +1 -1
  43. package/dist/index.d.ts +1 -1
  44. package/dist/index.d.ts.map +1 -1
  45. package/dist/index.js +3 -3
  46. package/dist/interceptors.d.ts +22 -0
  47. package/dist/interceptors.d.ts.map +1 -1
  48. package/dist/interceptors.js +12 -0
  49. package/dist/interceptors.js.map +1 -1
  50. package/dist/junction.d.ts +12 -3
  51. package/dist/junction.d.ts.map +1 -1
  52. package/dist/junction.js +34 -4
  53. package/dist/junction.js.map +1 -1
  54. package/dist/knowledge-config.d.ts +6 -0
  55. package/dist/knowledge-config.d.ts.map +1 -0
  56. package/dist/knowledge-config.js +48 -0
  57. package/dist/knowledge-config.js.map +1 -0
  58. package/dist/manifest/static-manifest.d.ts.map +1 -1
  59. package/dist/manifest/static-manifest.js +91 -1
  60. package/dist/manifest/static-manifest.js.map +1 -1
  61. package/dist/manifest/store.js +1 -1
  62. package/dist/manifest/store.js.map +1 -1
  63. package/dist/manifest.json +104 -1
  64. package/dist/migrations/differ.d.ts.map +1 -1
  65. package/dist/migrations/differ.js +2 -0
  66. package/dist/migrations/differ.js.map +1 -1
  67. package/dist/migrations/generator.d.ts.map +1 -1
  68. package/dist/migrations/generator.js +2 -0
  69. package/dist/migrations/generator.js.map +1 -1
  70. package/dist/migrations/index.d.ts +1 -0
  71. package/dist/migrations/index.d.ts.map +1 -1
  72. package/dist/migrations/index.js +2 -1
  73. package/dist/migrations/null-equal-indexes.d.ts +37 -0
  74. package/dist/migrations/null-equal-indexes.d.ts.map +1 -0
  75. package/dist/migrations/null-equal-indexes.js +133 -0
  76. package/dist/migrations/null-equal-indexes.js.map +1 -0
  77. package/dist/migrations/tracker.d.ts.map +1 -1
  78. package/dist/migrations/tracker.js +6 -0
  79. package/dist/migrations/tracker.js.map +1 -1
  80. package/dist/migrations.js +2 -1
  81. package/dist/object.d.ts +33 -0
  82. package/dist/object.d.ts.map +1 -1
  83. package/dist/object.js +316 -46
  84. package/dist/object.js.map +1 -1
  85. package/dist/plain-json.d.ts +6 -0
  86. package/dist/plain-json.d.ts.map +1 -0
  87. package/dist/plain-json.js +41 -0
  88. package/dist/plain-json.js.map +1 -0
  89. package/dist/postgres-permissions.d.ts.map +1 -1
  90. package/dist/postgres-permissions.js +6 -2
  91. package/dist/postgres-permissions.js.map +1 -1
  92. package/dist/prebuild/index.d.ts.map +1 -1
  93. package/dist/prebuild/index.js +3 -3
  94. package/dist/prebuild/index.js.map +1 -1
  95. package/dist/registry/class-registration.d.ts.map +1 -1
  96. package/dist/registry/class-registration.js +4 -1
  97. package/dist/registry/class-registration.js.map +1 -1
  98. package/dist/registry/schema-builder.d.ts.map +1 -1
  99. package/dist/registry/schema-builder.js +4 -3
  100. package/dist/registry/schema-builder.js.map +1 -1
  101. package/dist/registry/validator.d.ts +1 -0
  102. package/dist/registry/validator.d.ts.map +1 -1
  103. package/dist/registry/validator.js +8 -1
  104. package/dist/registry/validator.js.map +1 -1
  105. package/dist/registry.js +1 -1
  106. package/dist/scanner/manifest-generator.d.ts.map +1 -1
  107. package/dist/scanner/manifest-generator.js +21 -0
  108. package/dist/scanner/manifest-generator.js.map +1 -1
  109. package/dist/scanner/types.d.ts +2 -0
  110. package/dist/scanner/types.d.ts.map +1 -1
  111. package/dist/scanner/types.js.map +1 -1
  112. package/dist/schema/conflict-target.d.ts +8 -2
  113. package/dist/schema/conflict-target.d.ts.map +1 -1
  114. package/dist/schema/conflict-target.js +11 -3
  115. package/dist/schema/conflict-target.js.map +1 -1
  116. package/dist/schema/ddl/null-equal-index.d.ts +5 -0
  117. package/dist/schema/ddl/null-equal-index.d.ts.map +1 -0
  118. package/dist/schema/ddl/null-equal-index.js +17 -0
  119. package/dist/schema/ddl/null-equal-index.js.map +1 -0
  120. package/dist/schema/ddl/postgres-strategy.d.ts +2 -1
  121. package/dist/schema/ddl/postgres-strategy.d.ts.map +1 -1
  122. package/dist/schema/ddl/postgres-strategy.js +7 -0
  123. package/dist/schema/ddl/postgres-strategy.js.map +1 -1
  124. package/dist/schema/generator.d.ts.map +1 -1
  125. package/dist/schema/generator.js +8 -3
  126. package/dist/schema/generator.js.map +1 -1
  127. package/dist/schema/manifest-schema.d.ts +2 -1
  128. package/dist/schema/manifest-schema.d.ts.map +1 -1
  129. package/dist/schema/manifest-schema.js +12 -4
  130. package/dist/schema/manifest-schema.js.map +1 -1
  131. package/dist/schema/schema-manager.d.ts.map +1 -1
  132. package/dist/schema/schema-manager.js +1 -1
  133. package/dist/schema/schema-manager.js.map +1 -1
  134. package/dist/schema/types.d.ts +2 -0
  135. package/dist/schema/types.d.ts.map +1 -1
  136. package/dist/scripts/browser-plain-json-regression.mjs +85 -0
  137. package/dist/smrt-knowledge.json +73 -15
  138. package/dist/system/schema.d.ts +14 -3
  139. package/dist/system/schema.d.ts.map +1 -1
  140. package/dist/system/schema.js +120 -2
  141. package/dist/system/schema.js.map +1 -1
  142. package/dist/vite-plugin/api-client-entries.d.ts.map +1 -1
  143. package/dist/vite-plugin/api-client-entries.js +9 -7
  144. package/dist/vite-plugin/api-client-entries.js.map +1 -1
  145. package/dist/vite-plugin/index.d.ts +24 -0
  146. package/dist/vite-plugin/index.d.ts.map +1 -1
  147. package/dist/vite-plugin/index.js +48 -115
  148. package/dist/vite-plugin/index.js.map +1 -1
  149. package/dist/vite-plugin/sveltekit-generator.d.ts.map +1 -1
  150. package/dist/vite-plugin/sveltekit-generator.js +2 -2
  151. package/dist/vite-plugin/sveltekit-generator.js.map +1 -1
  152. package/dist/vite-plugin/web-collections.d.ts +18 -0
  153. package/dist/vite-plugin/web-collections.d.ts.map +1 -1
  154. package/dist/vite-plugin/web-collections.js +55 -2
  155. package/dist/vite-plugin/web-collections.js.map +1 -1
  156. package/package.json +5 -4
@@ -1 +1 @@
1
- {"version":3,"file":"generator.d.ts","sourceRoot":"","sources":["../../src/migrations/generator.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"generator.d.ts","sourceRoot":"","sources":["../../src/migrations/generator.ts"],"names":[],"mappings":"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"}
@@ -1,4 +1,5 @@
1
1
  import { renderIndexTarget } from "../schema/index-utils.js";
2
+ import { renderNullEqualConflictIndex } from "../schema/ddl/null-equal-index.js";
2
3
  import { getDDLStrategy } from "../schema/ddl/index.js";
3
4
  import { planForeignKeyCreation, renderDeferredForeignKeyDrop } from "../schema/foreign-key-planner.js";
4
5
  import { materializeManifestDDLForEngine } from "../schema/ddl/materialize-manifest.js";
@@ -262,6 +263,7 @@ ${downStatementsStr}
262
263
  * Generate ADD INDEX SQL
263
264
  */
264
265
  generateAddIndex(tableName, index) {
266
+ if (this.engine === "postgres" && index.nullsNotDistinct) return renderNullEqualConflictIndex(tableName, index);
265
267
  const uniqueStr = index.unique ? "UNIQUE " : "";
266
268
  const target = renderIndexTarget(index, this.engine);
267
269
  const whereClause = this.engine !== "duckdb" && index.where ? ` WHERE ${index.where}` : "";
@@ -1 +1 @@
1
- {"version":3,"file":"generator.js","names":[],"sources":["../../src/migrations/generator.ts"],"sourcesContent":["/**\n * Migration Generator\n *\n * Generates migration files from schema diffs.\n * Supports both SQL and TypeScript formats.\n */\n\nimport { createLogger } from '@happyvertical/logger';\nimport { getDDLStrategy } from '../schema/ddl/index.js';\nimport { materializeManifestDDLForEngine } from '../schema/ddl/materialize-manifest.js';\nimport {\n planForeignKeyCreation,\n renderDeferredForeignKeyDrop,\n} from '../schema/foreign-key-planner.js';\nimport { cachedDdlTableConstraints } from '../schema/manifest-schema.js';\nimport type {\n MigrationDefinition,\n SchemaChange,\n SchemaDefinition,\n SchemaDiff,\n} from '../schema/types.js';\nimport { renderIndexTarget } from '../schema/utils.js';\nimport { computeChecksum } from './checksum.js';\nimport type { DatabaseEngine } from './types.js';\n\nconst logger = createLogger({ level: 'info' });\n\n/**\n * Options for migration generation\n */\nexport interface GenerateMigrationOptions {\n /** Migration name (without sequence number) */\n name: string;\n /** Output format */\n format?: 'sql' | 'typescript';\n /** Database engine for engine-specific SQL */\n engine?: DatabaseEngine;\n /** Include generated DOWN statements */\n includeDown?: boolean;\n /** Package name for metadata */\n packageName?: string;\n /** Description for the migration */\n description?: string;\n}\n\n/**\n * Result of migration generation\n */\nexport interface GeneratedMigration {\n /** Full migration ID (e.g., '0001_add_users') */\n id: string;\n /** Filename (e.g., '0001_add_users.sql') */\n filename: string;\n /** File content */\n content: string;\n /** UP SQL statements */\n up: string[];\n /** DOWN SQL statements (if includeDown is true) */\n down: string[];\n /** Checksum of the migration */\n checksum: string;\n}\n\n/**\n * MigrationGenerator class for creating migration files\n */\nexport class MigrationGenerator {\n private engine: DatabaseEngine;\n private format: 'sql' | 'typescript';\n private includeDown: boolean;\n private packageName?: string;\n private materializeStructuredSchema: boolean;\n\n constructor(\n options: {\n engine?: DatabaseEngine;\n format?: 'sql' | 'typescript';\n includeDown?: boolean;\n packageName?: string;\n /**\n * Render new tables from their structured columns through the engine's\n * DDL strategy (default, `true`) — the same renderer `db:migrate` uses,\n * so column types, indexes and triggers match what the differ expects\n * on the next run.\n *\n * @deprecated Passing `false` re-enables the retired cached-`ddl` string\n * path for the CREATE TABLE only (#2358): it emits the manifest's\n * engine-neutral CREATE TABLE with only bare PostgreSQL `TIMESTAMP`\n * rewritten, so `REAL`/`JSON` drift on PostgreSQL. The table's indexes\n * and triggers are still rendered through the engine strategy either\n * way — the flag never suppresses them. Schemas that expose no\n * structured columns always fall back to their cached `ddl` regardless\n * of this flag.\n */\n materializeStructuredSchema?: boolean;\n } = {},\n ) {\n this.engine = options.engine || 'sqlite';\n this.format = options.format || 'sql';\n this.includeDown = options.includeDown ?? true;\n this.packageName = options.packageName;\n this.materializeStructuredSchema =\n options.materializeStructuredSchema ?? true;\n }\n\n /**\n * Generate a migration from a schema diff\n */\n generateFromDiff(\n diff: SchemaDiff,\n options: GenerateMigrationOptions,\n ): GeneratedMigration {\n const upStatements: string[] = [];\n const downStatements: string[] = [];\n const addedTableDownStatements: string[] = [];\n\n // Handle new tables: CREATE TABLE plus the table's indexes and triggers,\n // exactly as the migration orchestrator emits them (#2358). The differ\n // only produces `add_index` changes for tables that already exist, so\n // there is no double emission against `diff.changes`.\n const tablePlan = planForeignKeyCreation(diff.added_tables, this.engine);\n for (const schema of tablePlan.schemas) {\n upStatements.push(this.generateCreateTable(schema));\n upStatements.push(...this.generateTableIndexes(schema));\n upStatements.push(...this.generateTableTriggers(schema));\n }\n upStatements.push(...tablePlan.deferredStatements);\n if (this.includeDown || options.includeDown) {\n if (this.engine === 'sqlite' && tablePlan.cyclicTables.length > 0) {\n // SQLite cannot drop either side of a populated mutual FK cycle with\n // immediate checks. Defer checks until the transaction commits, after\n // every member has been removed.\n addedTableDownStatements.push('PRAGMA defer_foreign_keys = ON');\n }\n // PostgreSQL mutual cycles are made acyclic first; every table can then\n // be dropped in the exact reverse of the parent-first creation order.\n addedTableDownStatements.push(\n ...[...tablePlan.deferredConstraints]\n .reverse()\n .map(({ table, foreignKey }) =>\n renderDeferredForeignKeyDrop(table, foreignKey),\n ),\n );\n addedTableDownStatements.push(\n ...[...tablePlan.schemas]\n .reverse()\n .map((schema) => this.generateDropTable(schema.tableName)),\n );\n }\n\n // Handle column and index changes\n for (const change of diff.changes) {\n const { up, down } = this.generateChangeSQL(change);\n upStatements.push(...up);\n if ((this.includeDown || options.includeDown) && down.length > 0) {\n downStatements.push(...down);\n }\n }\n\n // Changes are applied after table creation, so undo them before removing\n // the new tables they may reference. The table block is itself already\n // ordered as deferred-constraint drops followed by child-first drops.\n downStatements.push(...addedTableDownStatements);\n\n // Handle dropped tables (already in SQL form if present)\n for (const tableName of diff.dropped_tables) {\n upStatements.push(this.generateDropTable(tableName));\n // No automatic DOWN for drops - too dangerous\n }\n\n return this.generateMigration(upStatements, downStatements, options);\n }\n\n /**\n * Generate a migration from explicit SQL statements\n */\n generateFromSQL(\n upStatements: string[],\n downStatements: string[],\n options: GenerateMigrationOptions,\n ): GeneratedMigration {\n return this.generateMigration(upStatements, downStatements, options);\n }\n\n /**\n * Generate an empty migration template\n */\n generateEmpty(options: GenerateMigrationOptions): GeneratedMigration {\n return this.generateMigration([], [], options);\n }\n\n /**\n * Core migration generation logic\n */\n private generateMigration(\n upStatements: string[],\n downStatements: string[],\n options: GenerateMigrationOptions,\n ): GeneratedMigration {\n const id = options.name;\n const description = options.description || `Migration: ${options.name}`;\n const format = options.format || this.format;\n const filename = `${id}.${format === 'sql' ? 'sql' : 'ts'}`;\n const checksum = computeChecksum(upStatements);\n\n let content: string;\n if (format === 'sql') {\n content = this.generateSQLFile(\n id,\n description,\n upStatements,\n downStatements,\n );\n } else {\n content = this.generateTypeScriptFile(\n id,\n description,\n upStatements,\n downStatements,\n );\n }\n\n return {\n id,\n filename,\n content,\n up: upStatements,\n down: downStatements,\n checksum,\n };\n }\n\n /**\n * Generate SQL migration file content\n */\n private generateSQLFile(\n id: string,\n description: string,\n upStatements: string[],\n downStatements: string[],\n ): string {\n const lines: string[] = [\n `-- @id: ${id}`,\n `-- @description: ${description}`,\n `-- @generated: ${new Date().toISOString()}`,\n ];\n\n if (this.packageName) {\n lines.push(`-- @package: ${this.packageName}`);\n }\n\n lines.push('');\n lines.push('-- @up');\n\n if (upStatements.length === 0) {\n lines.push('-- Add your UP statements here');\n } else {\n for (const stmt of upStatements) {\n lines.push(this.terminateStatement(stmt));\n }\n }\n\n lines.push('');\n lines.push('-- @down');\n\n if (downStatements.length === 0) {\n lines.push('-- Add your DOWN statements here');\n } else {\n for (const stmt of downStatements) {\n lines.push(this.terminateStatement(stmt));\n }\n }\n\n lines.push('');\n return lines.join('\\n');\n }\n\n /**\n * Generate TypeScript migration file content\n */\n private generateTypeScriptFile(\n id: string,\n description: string,\n upStatements: string[],\n downStatements: string[],\n ): string {\n const upStatementsStr =\n upStatements.length > 0\n ? upStatements\n .map((s) => ` await db.query(\\`${this.escapeTemplate(s)}\\`);`)\n .join('\\n')\n : ' // Add your UP statements here';\n\n const downStatementsStr =\n downStatements.length > 0\n ? downStatements\n .map((s) => ` await db.query(\\`${this.escapeTemplate(s)}\\`);`)\n .join('\\n')\n : ' // Add your DOWN statements here';\n\n return `/**\n * Migration: ${id}\n * ${description}\n *\n * Generated: ${new Date().toISOString()}\n */\n\nimport type { Migration } from '@happyvertical/smrt-core';\n\nexport default {\n id: '${id}',\n description: '${description.replace(/'/g, \"\\\\'\")}',\n version: '1.0.0',\n\n up: async (db) => {\n${upStatementsStr}\n },\n\n down: async (db) => {\n${downStatementsStr}\n },\n} satisfies Migration;\n`;\n }\n\n /**\n * Generate CREATE TABLE SQL from schema definition\n */\n private generateCreateTable(schema: SchemaDefinition): string {\n if (Object.keys(schema.columns).length === 0 && schema.ddl?.trim()) {\n // Structured materialization cannot preserve constraints or vendor SQL\n // when an older/custom manifest exposes only executable DDL. Retain the\n // only valid representation even when materialization was requested.\n return materializeManifestDDLForEngine(schema.ddl, this.engine);\n }\n\n if (!this.materializeStructuredSchema && schema.ddl?.trim()) {\n // Deprecated opt-out (#2358): the cached string is engine-neutral and\n // index-less; only bare PostgreSQL TIMESTAMP is rewritten.\n return materializeManifestDDLForEngine(schema.ddl, this.engine);\n }\n\n // The manifest's pre-generated `ddl` is deliberately engine-neutral and\n // may contain abstract TIMESTAMP/JSON/UUID types. Materialize the\n // structured columns through the selected engine strategy; executing the\n // cached string on PostgreSQL would otherwise bypass TIMESTAMPTZ and lose\n // JavaScript Date offsets (#2069). This matches the migration orchestrator\n // and also prevents JSON/REAL/UUID drift after a create.\n if (schema.ddl?.trim()) {\n // Table-level constraints that live only in a hand-authored cached\n // string cannot be represented structurally and are dropped — as\n // `db:migrate` already drops them. Say so instead of losing them\n // silently (#2358).\n const constraints = cachedDdlTableConstraints(schema.ddl);\n if (constraints.length > 0) {\n logger.warn(\n `[MigrationGenerator] cached ddl for table \"${schema.tableName}\" declares ` +\n `${constraints.length} table constraint(s) that structured columns cannot ` +\n `carry (${constraints.join('; ')}); the structured schema is authoritative ` +\n `and they are not rendered (#2358).`,\n );\n }\n }\n return getDDLStrategy(this.engine).generateCreateTable(schema);\n }\n\n /**\n * Generate the CREATE INDEX statements for a new table through the engine\n * strategy (partial `WHERE`, JSON-path targets, inline-UNIQUE engines).\n */\n private generateTableIndexes(schema: SchemaDefinition): string[] {\n return getDDLStrategy(this.engine).generateIndexes(schema);\n }\n\n /**\n * Generate the CREATE TRIGGER statements for a new table (engines without\n * trigger support return none).\n */\n private generateTableTriggers(schema: SchemaDefinition): string[] {\n return getDDLStrategy(this.engine).generateTriggers(schema);\n }\n\n /**\n * Generate DROP TABLE SQL\n */\n private generateDropTable(tableName: string): string {\n return `DROP TABLE IF EXISTS ${this.quoteIdentifier(tableName)}`;\n }\n\n /**\n * Generate SQL for a schema change\n */\n private generateChangeSQL(change: SchemaChange): {\n up: string[];\n down: string[];\n } {\n const up: string[] = [];\n const down: string[] = [];\n const sqlStatements =\n change.sqlStatements ?? (change.sql ? [change.sql] : []);\n\n switch (change.type) {\n case 'add_column':\n if (sqlStatements.length > 0) {\n up.push(...sqlStatements);\n } else if (change.column && change.name) {\n up.push(\n this.generateAddColumn(change.table, change.name, change.column),\n );\n }\n if (change.name) {\n down.push(this.generateDropColumn(change.table, change.name));\n }\n break;\n\n case 'drop_column':\n if (sqlStatements.length > 0) {\n up.push(...sqlStatements);\n } else if (change.name) {\n up.push(this.generateDropColumn(change.table, change.name));\n }\n // No automatic DOWN for drops\n break;\n\n case 'add_index':\n if (sqlStatements.length > 0) {\n up.push(...sqlStatements);\n } else if (change.index) {\n up.push(this.generateAddIndex(change.table, change.index));\n }\n if (change.name) {\n down.push(this.generateDropIndex(change.name));\n }\n break;\n\n case 'add_foreign_key':\n if (sqlStatements.length > 0) {\n up.push(...sqlStatements);\n } else if (change.advisory) {\n up.push(\n `-- ${change.advisory.severity === 'warning' ? 'WARNING' : 'NOTE'}: Foreign-key constraint ${change.table}.${change.name ?? 'unknown'}`,\n );\n if (change.advisory.message) {\n up.push(`-- ${change.advisory.message}`);\n }\n for (const suggestion of change.advisory.suggestedSql ?? []) {\n up.push(`-- suggested: ${suggestion}`);\n }\n }\n // Constraint rollback is intentionally manual: names on older live\n // databases may not match the deterministic current generator name.\n break;\n\n case 'drop_foreign_key':\n if (sqlStatements.length > 0) {\n up.push(...sqlStatements);\n } else if (change.advisory) {\n up.push(\n `-- ${change.advisory.severity === 'warning' ? 'WARNING' : 'NOTE'}: Foreign-key constraint ${change.table}.${change.name ?? 'unknown'}`,\n );\n up.push(`-- ${change.advisory.message}`);\n }\n // Re-adding a removed constraint requires a fresh orphan check and is\n // therefore intentionally not generated as an automatic rollback.\n break;\n\n case 'drop_index':\n if (change.name) {\n up.push(this.generateDropIndex(change.name));\n }\n // No automatic DOWN for index drops\n break;\n\n case 'type_upgrade':\n // Type upgrades are safe conversions that SMRT can handle automatically\n // The SQL is generated by the differ based on engine-specific requirements\n if (sqlStatements.length > 0) {\n for (const sql of sqlStatements) {\n // Skip comment-only SQL (indicates a no-op migration)\n if (!sql.startsWith('--')) {\n up.push(sql);\n }\n }\n }\n // No automatic DOWN for type changes - could lose data\n break;\n\n case 'type_mismatch':\n // Type mismatches require manual intervention\n up.push(`-- WARNING: Type mismatch for ${change.table}.${change.name}`);\n up.push(\n `-- Expected: ${change.mismatch?.expected}, Actual: ${change.mismatch?.actual}`,\n );\n up.push(`-- Manual migration required`);\n break;\n\n case 'alter_column':\n // Nullability/default repairs (#2369). Executable statements come\n // from the differ (backfill + ALTER COLUMN); comment-only SQL marks\n // an in-place limitation (SQLite) and is surfaced as a note. Report-\n // only advisories (relaxations not opted into) are not emitted.\n if (change.advisory && sqlStatements.length === 0) {\n break;\n }\n for (const sql of sqlStatements) {\n up.push(sql);\n }\n // No automatic DOWN: the previous nullability/default is not\n // captured with enough fidelity to restore it safely.\n break;\n\n case 'orphan_column':\n case 'orphan_index':\n case 'rename_data_pending':\n // Report-only: surface the advisory as comments so a generated\n // migration file documents the drift without acting on it.\n up.push(\n `-- ${change.advisory?.severity === 'warning' ? 'WARNING' : 'NOTE'}: ${\n change.type === 'orphan_column'\n ? 'Orphan column'\n : change.type === 'orphan_index'\n ? 'Orphan unique constraint'\n : 'Rename data pending'\n } ${change.table}.${change.name} (${change.mismatch?.actual ?? 'unknown shape'})`,\n );\n if (change.advisory?.message) {\n up.push(`-- ${change.advisory.message}`);\n }\n for (const suggestion of change.advisory?.suggestedSql ?? []) {\n up.push(`-- suggested: ${suggestion}`);\n }\n break;\n\n default:\n // Unknown change type\n break;\n }\n\n return { up, down };\n }\n\n /**\n * Generate ADD COLUMN SQL\n */\n private generateAddColumn(\n tableName: string,\n colName: string,\n colDef: import('../schema/types.js').ColumnDefinition,\n ): string {\n const parts: string[] = [this.quoteIdentifier(colName), colDef.type];\n\n if (colDef.notNull) {\n parts.push('NOT NULL');\n }\n if (colDef.unique) {\n parts.push('UNIQUE');\n }\n if (colDef.defaultValue !== undefined) {\n parts.push(`DEFAULT ${this.formatDefaultValue(colDef.defaultValue)}`);\n }\n\n return `ALTER TABLE ${this.quoteIdentifier(tableName)} ADD COLUMN ${parts.join(' ')}`;\n }\n\n /**\n * Generate DROP COLUMN SQL\n */\n private generateDropColumn(tableName: string, colName: string): string {\n return `ALTER TABLE ${this.quoteIdentifier(tableName)} DROP COLUMN ${this.quoteIdentifier(colName)}`;\n }\n\n /**\n * Generate ADD INDEX SQL\n */\n private generateAddIndex(\n tableName: string,\n index: import('../schema/types.js').IndexDefinition,\n ): string {\n const uniqueStr = index.unique ? 'UNIQUE ' : '';\n const target = renderIndexTarget(index, this.engine);\n // Partial-index predicate. DuckDB (and the JSON adapter backed by it)\n // rejects partial indexes, so the declaration degrades to a full index\n // there — matching the differ and the DuckDB DDL strategy.\n const whereClause =\n this.engine !== 'duckdb' && index.where ? ` WHERE ${index.where}` : '';\n\n if (this.engine === 'postgres') {\n // PostgreSQL: use CONCURRENTLY for production safety\n return `CREATE ${uniqueStr}INDEX CONCURRENTLY IF NOT EXISTS ${this.quoteIdentifier(index.name)} ON ${this.quoteIdentifier(tableName)} (${target})${whereClause}`;\n }\n\n return `CREATE ${uniqueStr}INDEX IF NOT EXISTS ${this.quoteIdentifier(index.name)} ON ${this.quoteIdentifier(tableName)} (${target})${whereClause}`;\n }\n\n /**\n * Generate DROP INDEX SQL\n */\n private generateDropIndex(indexName: string): string {\n if (this.engine === 'postgres') {\n return `DROP INDEX CONCURRENTLY IF EXISTS ${this.quoteIdentifier(indexName)}`;\n }\n return `DROP INDEX IF EXISTS ${this.quoteIdentifier(indexName)}`;\n }\n\n /**\n * Quote a SQL identifier\n */\n private quoteIdentifier(name: string): string {\n return `\"${name.replace(/\"/g, '\"\"')}\"`;\n }\n\n /**\n * Format a default value for SQL\n */\n private formatDefaultValue(value: unknown): string {\n if (value === null) return 'NULL';\n if (typeof value === 'string') return `'${value.replace(/'/g, \"''\")}'`;\n if (typeof value === 'boolean') return value ? '1' : '0';\n if (typeof value === 'number') return String(value);\n return String(value);\n }\n\n /**\n * Terminate a statement with exactly one `;` for the SQL file format.\n *\n * Strategy-rendered statements (`generateCreateTable`, `generateIndexes`,\n * `generateTriggers`) already end with `;`, while differ-produced ALTERs do\n * not — appending unconditionally wrote `;;` (#2406 review).\n */\n private terminateStatement(stmt: string): string {\n return stmt.trimEnd().endsWith(';') ? stmt : `${stmt};`;\n }\n\n /**\n * Escape template literal content\n */\n private escapeTemplate(str: string): string {\n return str.replace(/`/g, '\\\\`').replace(/\\$/g, '\\\\$');\n }\n}\n\n/**\n * Generate a migration definition from UP and DOWN statements\n */\nexport function createMigrationDefinition(\n id: string,\n upStatements: string[],\n downStatements: string[] = [],\n options: {\n description?: string;\n version?: string;\n packageName?: string;\n sourceFile?: string;\n } = {},\n): MigrationDefinition {\n return {\n id,\n description: options.description || `Migration: ${id}`,\n version: options.version || '1.0.0',\n up: upStatements,\n down: downStatements,\n package_name: options.packageName,\n source_file: options.sourceFile,\n };\n}\n\n/**\n * Generate a sequence number for a new migration\n */\nexport function generateMigrationSequence(existingIds: string[]): string {\n const sequences = existingIds\n .map((id) => {\n const match = id.match(/^(\\d+)_/);\n return match ? parseInt(match[1], 10) : 0;\n })\n .filter((n) => !Number.isNaN(n));\n\n const maxSequence = sequences.length > 0 ? Math.max(...sequences) : 0;\n return String(maxSequence + 1).padStart(4, '0');\n}\n\n/**\n * Generate a timestamp-based migration ID\n */\nexport function generateMigrationTimestamp(): string {\n const now = new Date();\n return [\n now.getFullYear(),\n String(now.getMonth() + 1).padStart(2, '0'),\n String(now.getDate()).padStart(2, '0'),\n String(now.getHours()).padStart(2, '0'),\n String(now.getMinutes()).padStart(2, '0'),\n String(now.getSeconds()).padStart(2, '0'),\n ].join('');\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"generator.js","names":[],"sources":["../../src/migrations/generator.ts"],"sourcesContent":["/**\n * Migration Generator\n *\n * Generates migration files from schema diffs.\n * Supports both SQL and TypeScript formats.\n */\n\nimport { createLogger } from '@happyvertical/logger';\nimport { getDDLStrategy } from '../schema/ddl/index.js';\nimport { materializeManifestDDLForEngine } from '../schema/ddl/materialize-manifest.js';\nimport { renderNullEqualConflictIndex } from '../schema/ddl/null-equal-index.js';\nimport {\n planForeignKeyCreation,\n renderDeferredForeignKeyDrop,\n} from '../schema/foreign-key-planner.js';\nimport { cachedDdlTableConstraints } from '../schema/manifest-schema.js';\nimport type {\n MigrationDefinition,\n SchemaChange,\n SchemaDefinition,\n SchemaDiff,\n} from '../schema/types.js';\nimport { renderIndexTarget } from '../schema/utils.js';\nimport { computeChecksum } from './checksum.js';\nimport type { DatabaseEngine } from './types.js';\n\nconst logger = createLogger({ level: 'info' });\n\n/**\n * Options for migration generation\n */\nexport interface GenerateMigrationOptions {\n /** Migration name (without sequence number) */\n name: string;\n /** Output format */\n format?: 'sql' | 'typescript';\n /** Database engine for engine-specific SQL */\n engine?: DatabaseEngine;\n /** Include generated DOWN statements */\n includeDown?: boolean;\n /** Package name for metadata */\n packageName?: string;\n /** Description for the migration */\n description?: string;\n}\n\n/**\n * Result of migration generation\n */\nexport interface GeneratedMigration {\n /** Full migration ID (e.g., '0001_add_users') */\n id: string;\n /** Filename (e.g., '0001_add_users.sql') */\n filename: string;\n /** File content */\n content: string;\n /** UP SQL statements */\n up: string[];\n /** DOWN SQL statements (if includeDown is true) */\n down: string[];\n /** Checksum of the migration */\n checksum: string;\n}\n\n/**\n * MigrationGenerator class for creating migration files\n */\nexport class MigrationGenerator {\n private engine: DatabaseEngine;\n private format: 'sql' | 'typescript';\n private includeDown: boolean;\n private packageName?: string;\n private materializeStructuredSchema: boolean;\n\n constructor(\n options: {\n engine?: DatabaseEngine;\n format?: 'sql' | 'typescript';\n includeDown?: boolean;\n packageName?: string;\n /**\n * Render new tables from their structured columns through the engine's\n * DDL strategy (default, `true`) — the same renderer `db:migrate` uses,\n * so column types, indexes and triggers match what the differ expects\n * on the next run.\n *\n * @deprecated Passing `false` re-enables the retired cached-`ddl` string\n * path for the CREATE TABLE only (#2358): it emits the manifest's\n * engine-neutral CREATE TABLE with only bare PostgreSQL `TIMESTAMP`\n * rewritten, so `REAL`/`JSON` drift on PostgreSQL. The table's indexes\n * and triggers are still rendered through the engine strategy either\n * way — the flag never suppresses them. Schemas that expose no\n * structured columns always fall back to their cached `ddl` regardless\n * of this flag.\n */\n materializeStructuredSchema?: boolean;\n } = {},\n ) {\n this.engine = options.engine || 'sqlite';\n this.format = options.format || 'sql';\n this.includeDown = options.includeDown ?? true;\n this.packageName = options.packageName;\n this.materializeStructuredSchema =\n options.materializeStructuredSchema ?? true;\n }\n\n /**\n * Generate a migration from a schema diff\n */\n generateFromDiff(\n diff: SchemaDiff,\n options: GenerateMigrationOptions,\n ): GeneratedMigration {\n const upStatements: string[] = [];\n const downStatements: string[] = [];\n const addedTableDownStatements: string[] = [];\n\n // Handle new tables: CREATE TABLE plus the table's indexes and triggers,\n // exactly as the migration orchestrator emits them (#2358). The differ\n // only produces `add_index` changes for tables that already exist, so\n // there is no double emission against `diff.changes`.\n const tablePlan = planForeignKeyCreation(diff.added_tables, this.engine);\n for (const schema of tablePlan.schemas) {\n upStatements.push(this.generateCreateTable(schema));\n upStatements.push(...this.generateTableIndexes(schema));\n upStatements.push(...this.generateTableTriggers(schema));\n }\n upStatements.push(...tablePlan.deferredStatements);\n if (this.includeDown || options.includeDown) {\n if (this.engine === 'sqlite' && tablePlan.cyclicTables.length > 0) {\n // SQLite cannot drop either side of a populated mutual FK cycle with\n // immediate checks. Defer checks until the transaction commits, after\n // every member has been removed.\n addedTableDownStatements.push('PRAGMA defer_foreign_keys = ON');\n }\n // PostgreSQL mutual cycles are made acyclic first; every table can then\n // be dropped in the exact reverse of the parent-first creation order.\n addedTableDownStatements.push(\n ...[...tablePlan.deferredConstraints]\n .reverse()\n .map(({ table, foreignKey }) =>\n renderDeferredForeignKeyDrop(table, foreignKey),\n ),\n );\n addedTableDownStatements.push(\n ...[...tablePlan.schemas]\n .reverse()\n .map((schema) => this.generateDropTable(schema.tableName)),\n );\n }\n\n // Handle column and index changes\n for (const change of diff.changes) {\n const { up, down } = this.generateChangeSQL(change);\n upStatements.push(...up);\n if ((this.includeDown || options.includeDown) && down.length > 0) {\n downStatements.push(...down);\n }\n }\n\n // Changes are applied after table creation, so undo them before removing\n // the new tables they may reference. The table block is itself already\n // ordered as deferred-constraint drops followed by child-first drops.\n downStatements.push(...addedTableDownStatements);\n\n // Handle dropped tables (already in SQL form if present)\n for (const tableName of diff.dropped_tables) {\n upStatements.push(this.generateDropTable(tableName));\n // No automatic DOWN for drops - too dangerous\n }\n\n return this.generateMigration(upStatements, downStatements, options);\n }\n\n /**\n * Generate a migration from explicit SQL statements\n */\n generateFromSQL(\n upStatements: string[],\n downStatements: string[],\n options: GenerateMigrationOptions,\n ): GeneratedMigration {\n return this.generateMigration(upStatements, downStatements, options);\n }\n\n /**\n * Generate an empty migration template\n */\n generateEmpty(options: GenerateMigrationOptions): GeneratedMigration {\n return this.generateMigration([], [], options);\n }\n\n /**\n * Core migration generation logic\n */\n private generateMigration(\n upStatements: string[],\n downStatements: string[],\n options: GenerateMigrationOptions,\n ): GeneratedMigration {\n const id = options.name;\n const description = options.description || `Migration: ${options.name}`;\n const format = options.format || this.format;\n const filename = `${id}.${format === 'sql' ? 'sql' : 'ts'}`;\n const checksum = computeChecksum(upStatements);\n\n let content: string;\n if (format === 'sql') {\n content = this.generateSQLFile(\n id,\n description,\n upStatements,\n downStatements,\n );\n } else {\n content = this.generateTypeScriptFile(\n id,\n description,\n upStatements,\n downStatements,\n );\n }\n\n return {\n id,\n filename,\n content,\n up: upStatements,\n down: downStatements,\n checksum,\n };\n }\n\n /**\n * Generate SQL migration file content\n */\n private generateSQLFile(\n id: string,\n description: string,\n upStatements: string[],\n downStatements: string[],\n ): string {\n const lines: string[] = [\n `-- @id: ${id}`,\n `-- @description: ${description}`,\n `-- @generated: ${new Date().toISOString()}`,\n ];\n\n if (this.packageName) {\n lines.push(`-- @package: ${this.packageName}`);\n }\n\n lines.push('');\n lines.push('-- @up');\n\n if (upStatements.length === 0) {\n lines.push('-- Add your UP statements here');\n } else {\n for (const stmt of upStatements) {\n lines.push(this.terminateStatement(stmt));\n }\n }\n\n lines.push('');\n lines.push('-- @down');\n\n if (downStatements.length === 0) {\n lines.push('-- Add your DOWN statements here');\n } else {\n for (const stmt of downStatements) {\n lines.push(this.terminateStatement(stmt));\n }\n }\n\n lines.push('');\n return lines.join('\\n');\n }\n\n /**\n * Generate TypeScript migration file content\n */\n private generateTypeScriptFile(\n id: string,\n description: string,\n upStatements: string[],\n downStatements: string[],\n ): string {\n const upStatementsStr =\n upStatements.length > 0\n ? upStatements\n .map((s) => ` await db.query(\\`${this.escapeTemplate(s)}\\`);`)\n .join('\\n')\n : ' // Add your UP statements here';\n\n const downStatementsStr =\n downStatements.length > 0\n ? downStatements\n .map((s) => ` await db.query(\\`${this.escapeTemplate(s)}\\`);`)\n .join('\\n')\n : ' // Add your DOWN statements here';\n\n return `/**\n * Migration: ${id}\n * ${description}\n *\n * Generated: ${new Date().toISOString()}\n */\n\nimport type { Migration } from '@happyvertical/smrt-core';\n\nexport default {\n id: '${id}',\n description: '${description.replace(/'/g, \"\\\\'\")}',\n version: '1.0.0',\n\n up: async (db) => {\n${upStatementsStr}\n },\n\n down: async (db) => {\n${downStatementsStr}\n },\n} satisfies Migration;\n`;\n }\n\n /**\n * Generate CREATE TABLE SQL from schema definition\n */\n private generateCreateTable(schema: SchemaDefinition): string {\n if (Object.keys(schema.columns).length === 0 && schema.ddl?.trim()) {\n // Structured materialization cannot preserve constraints or vendor SQL\n // when an older/custom manifest exposes only executable DDL. Retain the\n // only valid representation even when materialization was requested.\n return materializeManifestDDLForEngine(schema.ddl, this.engine);\n }\n\n if (!this.materializeStructuredSchema && schema.ddl?.trim()) {\n // Deprecated opt-out (#2358): the cached string is engine-neutral and\n // index-less; only bare PostgreSQL TIMESTAMP is rewritten.\n return materializeManifestDDLForEngine(schema.ddl, this.engine);\n }\n\n // The manifest's pre-generated `ddl` is deliberately engine-neutral and\n // may contain abstract TIMESTAMP/JSON/UUID types. Materialize the\n // structured columns through the selected engine strategy; executing the\n // cached string on PostgreSQL would otherwise bypass TIMESTAMPTZ and lose\n // JavaScript Date offsets (#2069). This matches the migration orchestrator\n // and also prevents JSON/REAL/UUID drift after a create.\n if (schema.ddl?.trim()) {\n // Table-level constraints that live only in a hand-authored cached\n // string cannot be represented structurally and are dropped — as\n // `db:migrate` already drops them. Say so instead of losing them\n // silently (#2358).\n const constraints = cachedDdlTableConstraints(schema.ddl);\n if (constraints.length > 0) {\n logger.warn(\n `[MigrationGenerator] cached ddl for table \"${schema.tableName}\" declares ` +\n `${constraints.length} table constraint(s) that structured columns cannot ` +\n `carry (${constraints.join('; ')}); the structured schema is authoritative ` +\n `and they are not rendered (#2358).`,\n );\n }\n }\n return getDDLStrategy(this.engine).generateCreateTable(schema);\n }\n\n /**\n * Generate the CREATE INDEX statements for a new table through the engine\n * strategy (partial `WHERE`, JSON-path targets, inline-UNIQUE engines).\n */\n private generateTableIndexes(schema: SchemaDefinition): string[] {\n return getDDLStrategy(this.engine).generateIndexes(schema);\n }\n\n /**\n * Generate the CREATE TRIGGER statements for a new table (engines without\n * trigger support return none).\n */\n private generateTableTriggers(schema: SchemaDefinition): string[] {\n return getDDLStrategy(this.engine).generateTriggers(schema);\n }\n\n /**\n * Generate DROP TABLE SQL\n */\n private generateDropTable(tableName: string): string {\n return `DROP TABLE IF EXISTS ${this.quoteIdentifier(tableName)}`;\n }\n\n /**\n * Generate SQL for a schema change\n */\n private generateChangeSQL(change: SchemaChange): {\n up: string[];\n down: string[];\n } {\n const up: string[] = [];\n const down: string[] = [];\n const sqlStatements =\n change.sqlStatements ?? (change.sql ? [change.sql] : []);\n\n switch (change.type) {\n case 'add_column':\n if (sqlStatements.length > 0) {\n up.push(...sqlStatements);\n } else if (change.column && change.name) {\n up.push(\n this.generateAddColumn(change.table, change.name, change.column),\n );\n }\n if (change.name) {\n down.push(this.generateDropColumn(change.table, change.name));\n }\n break;\n\n case 'drop_column':\n if (sqlStatements.length > 0) {\n up.push(...sqlStatements);\n } else if (change.name) {\n up.push(this.generateDropColumn(change.table, change.name));\n }\n // No automatic DOWN for drops\n break;\n\n case 'add_index':\n if (sqlStatements.length > 0) {\n up.push(...sqlStatements);\n } else if (change.index) {\n up.push(this.generateAddIndex(change.table, change.index));\n }\n if (change.name) {\n down.push(this.generateDropIndex(change.name));\n }\n break;\n\n case 'add_foreign_key':\n if (sqlStatements.length > 0) {\n up.push(...sqlStatements);\n } else if (change.advisory) {\n up.push(\n `-- ${change.advisory.severity === 'warning' ? 'WARNING' : 'NOTE'}: Foreign-key constraint ${change.table}.${change.name ?? 'unknown'}`,\n );\n if (change.advisory.message) {\n up.push(`-- ${change.advisory.message}`);\n }\n for (const suggestion of change.advisory.suggestedSql ?? []) {\n up.push(`-- suggested: ${suggestion}`);\n }\n }\n // Constraint rollback is intentionally manual: names on older live\n // databases may not match the deterministic current generator name.\n break;\n\n case 'drop_foreign_key':\n if (sqlStatements.length > 0) {\n up.push(...sqlStatements);\n } else if (change.advisory) {\n up.push(\n `-- ${change.advisory.severity === 'warning' ? 'WARNING' : 'NOTE'}: Foreign-key constraint ${change.table}.${change.name ?? 'unknown'}`,\n );\n up.push(`-- ${change.advisory.message}`);\n }\n // Re-adding a removed constraint requires a fresh orphan check and is\n // therefore intentionally not generated as an automatic rollback.\n break;\n\n case 'drop_index':\n if (change.name) {\n up.push(this.generateDropIndex(change.name));\n }\n // No automatic DOWN for index drops\n break;\n\n case 'type_upgrade':\n // Type upgrades are safe conversions that SMRT can handle automatically\n // The SQL is generated by the differ based on engine-specific requirements\n if (sqlStatements.length > 0) {\n for (const sql of sqlStatements) {\n // Skip comment-only SQL (indicates a no-op migration)\n if (!sql.startsWith('--')) {\n up.push(sql);\n }\n }\n }\n // No automatic DOWN for type changes - could lose data\n break;\n\n case 'type_mismatch':\n // Type mismatches require manual intervention\n up.push(`-- WARNING: Type mismatch for ${change.table}.${change.name}`);\n up.push(\n `-- Expected: ${change.mismatch?.expected}, Actual: ${change.mismatch?.actual}`,\n );\n up.push(`-- Manual migration required`);\n break;\n\n case 'alter_column':\n // Nullability/default repairs (#2369). Executable statements come\n // from the differ (backfill + ALTER COLUMN); comment-only SQL marks\n // an in-place limitation (SQLite) and is surfaced as a note. Report-\n // only advisories (relaxations not opted into) are not emitted.\n if (change.advisory && sqlStatements.length === 0) {\n break;\n }\n for (const sql of sqlStatements) {\n up.push(sql);\n }\n // No automatic DOWN: the previous nullability/default is not\n // captured with enough fidelity to restore it safely.\n break;\n\n case 'orphan_column':\n case 'orphan_index':\n case 'rename_data_pending':\n // Report-only: surface the advisory as comments so a generated\n // migration file documents the drift without acting on it.\n up.push(\n `-- ${change.advisory?.severity === 'warning' ? 'WARNING' : 'NOTE'}: ${\n change.type === 'orphan_column'\n ? 'Orphan column'\n : change.type === 'orphan_index'\n ? 'Orphan unique constraint'\n : 'Rename data pending'\n } ${change.table}.${change.name} (${change.mismatch?.actual ?? 'unknown shape'})`,\n );\n if (change.advisory?.message) {\n up.push(`-- ${change.advisory.message}`);\n }\n for (const suggestion of change.advisory?.suggestedSql ?? []) {\n up.push(`-- suggested: ${suggestion}`);\n }\n break;\n\n default:\n // Unknown change type\n break;\n }\n\n return { up, down };\n }\n\n /**\n * Generate ADD COLUMN SQL\n */\n private generateAddColumn(\n tableName: string,\n colName: string,\n colDef: import('../schema/types.js').ColumnDefinition,\n ): string {\n const parts: string[] = [this.quoteIdentifier(colName), colDef.type];\n\n if (colDef.notNull) {\n parts.push('NOT NULL');\n }\n if (colDef.unique) {\n parts.push('UNIQUE');\n }\n if (colDef.defaultValue !== undefined) {\n parts.push(`DEFAULT ${this.formatDefaultValue(colDef.defaultValue)}`);\n }\n\n return `ALTER TABLE ${this.quoteIdentifier(tableName)} ADD COLUMN ${parts.join(' ')}`;\n }\n\n /**\n * Generate DROP COLUMN SQL\n */\n private generateDropColumn(tableName: string, colName: string): string {\n return `ALTER TABLE ${this.quoteIdentifier(tableName)} DROP COLUMN ${this.quoteIdentifier(colName)}`;\n }\n\n /**\n * Generate ADD INDEX SQL\n */\n private generateAddIndex(\n tableName: string,\n index: import('../schema/types.js').IndexDefinition,\n ): string {\n if (this.engine === 'postgres' && index.nullsNotDistinct) {\n return renderNullEqualConflictIndex(tableName, index);\n }\n const uniqueStr = index.unique ? 'UNIQUE ' : '';\n const target = renderIndexTarget(index, this.engine);\n // Partial-index predicate. DuckDB (and the JSON adapter backed by it)\n // rejects partial indexes, so the declaration degrades to a full index\n // there — matching the differ and the DuckDB DDL strategy.\n const whereClause =\n this.engine !== 'duckdb' && index.where ? ` WHERE ${index.where}` : '';\n\n if (this.engine === 'postgres') {\n // PostgreSQL: use CONCURRENTLY for production safety\n return `CREATE ${uniqueStr}INDEX CONCURRENTLY IF NOT EXISTS ${this.quoteIdentifier(index.name)} ON ${this.quoteIdentifier(tableName)} (${target})${whereClause}`;\n }\n\n return `CREATE ${uniqueStr}INDEX IF NOT EXISTS ${this.quoteIdentifier(index.name)} ON ${this.quoteIdentifier(tableName)} (${target})${whereClause}`;\n }\n\n /**\n * Generate DROP INDEX SQL\n */\n private generateDropIndex(indexName: string): string {\n if (this.engine === 'postgres') {\n return `DROP INDEX CONCURRENTLY IF EXISTS ${this.quoteIdentifier(indexName)}`;\n }\n return `DROP INDEX IF EXISTS ${this.quoteIdentifier(indexName)}`;\n }\n\n /**\n * Quote a SQL identifier\n */\n private quoteIdentifier(name: string): string {\n return `\"${name.replace(/\"/g, '\"\"')}\"`;\n }\n\n /**\n * Format a default value for SQL\n */\n private formatDefaultValue(value: unknown): string {\n if (value === null) return 'NULL';\n if (typeof value === 'string') return `'${value.replace(/'/g, \"''\")}'`;\n if (typeof value === 'boolean') return value ? '1' : '0';\n if (typeof value === 'number') return String(value);\n return String(value);\n }\n\n /**\n * Terminate a statement with exactly one `;` for the SQL file format.\n *\n * Strategy-rendered statements (`generateCreateTable`, `generateIndexes`,\n * `generateTriggers`) already end with `;`, while differ-produced ALTERs do\n * not — appending unconditionally wrote `;;` (#2406 review).\n */\n private terminateStatement(stmt: string): string {\n return stmt.trimEnd().endsWith(';') ? stmt : `${stmt};`;\n }\n\n /**\n * Escape template literal content\n */\n private escapeTemplate(str: string): string {\n return str.replace(/`/g, '\\\\`').replace(/\\$/g, '\\\\$');\n }\n}\n\n/**\n * Generate a migration definition from UP and DOWN statements\n */\nexport function createMigrationDefinition(\n id: string,\n upStatements: string[],\n downStatements: string[] = [],\n options: {\n description?: string;\n version?: string;\n packageName?: string;\n sourceFile?: string;\n } = {},\n): MigrationDefinition {\n return {\n id,\n description: options.description || `Migration: ${id}`,\n version: options.version || '1.0.0',\n up: upStatements,\n down: downStatements,\n package_name: options.packageName,\n source_file: options.sourceFile,\n };\n}\n\n/**\n * Generate a sequence number for a new migration\n */\nexport function generateMigrationSequence(existingIds: string[]): string {\n const sequences = existingIds\n .map((id) => {\n const match = id.match(/^(\\d+)_/);\n return match ? parseInt(match[1], 10) : 0;\n })\n .filter((n) => !Number.isNaN(n));\n\n const maxSequence = sequences.length > 0 ? Math.max(...sequences) : 0;\n return String(maxSequence + 1).padStart(4, '0');\n}\n\n/**\n * Generate a timestamp-based migration ID\n */\nexport function generateMigrationTimestamp(): string {\n const now = new Date();\n return [\n now.getFullYear(),\n String(now.getMonth() + 1).padStart(2, '0'),\n String(now.getDate()).padStart(2, '0'),\n String(now.getHours()).padStart(2, '0'),\n String(now.getMinutes()).padStart(2, '0'),\n String(now.getSeconds()).padStart(2, '0'),\n ].join('');\n}\n"],"mappings":"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"}
@@ -17,6 +17,7 @@ export type { GeneratedMigration, GenerateMigrationOptions, } from './generator.
17
17
  export { createMigrationDefinition, generateMigrationSequence, generateMigrationTimestamp, MigrationGenerator, } from './generator.js';
18
18
  export { buildIntegerWidthStatements, buildIntegerWidthTableStatements, collectIntegerWidthTargets, type IntegerWidthColumnReport, type IntegerWidthColumnState, type IntegerWidthOptions, type IntegerWidthPreflightResult, type IntegerWidthTableReport, type IntegerWidthTarget, type IntegerWidthWidenOptions, type IntegerWidthWidenResult, preflightIntegerWidthWidening, widenIntegerColumnsToBigInt, } from './integer-width.js';
19
19
  export { buildMinorUnitsStatements, type MinorUnitsColumnReport, type MinorUnitsColumnState, type MinorUnitsOptions, MinorUnitsPreflightError, type MinorUnitsPreflightResult, type MinorUnitsProblemRow, type MinorUnitsRescaleOptions, type MinorUnitsRescaleResult, type MoneyColumnTarget, preflightMinorUnitsRescale, rescaleMoneyColumnsToMinorUnits, } from './minor-units.js';
20
+ export { collectNullEqualIndexTargets, migrateNullEqualIndexes, type NullEqualIndexOptions, type NullEqualIndexPreflight, type NullEqualIndexReport, type NullEqualIndexTarget, nullEqualDuplicateDetector, nullEqualIndexStatements, preflightNullEqualIndexes, } from './null-equal-indexes.js';
20
21
  export { getPendingSchemaStatements, type MigrateSmrtSchemasOptions, type MigrateSmrtSchemasResult, migrateSmrtSchemas, type PendingSchemaStatementsResult, } from './orchestrate.js';
21
22
  export { type BuildSqliteRebuildStatementsInput, buildSqliteRebuildStatements, isSqliteRebuildPlaceholder, type PlanSqliteTableRebuildsOptions, planSqliteTableRebuilds, rewriteSqliteCreateTable, SQLITE_REBUILD_TABLE_PREFIX, type SqliteTableRebuildPlan, sqliteRebuildPlaceholderSql, } from './sqlite-rebuild.js';
22
23
  export { buildConcurrentIndexPlan, extractCreatedIndexName, MigrationTracker, parsePostgresTimeoutMs, planPostgresStatements, } from './tracker.js';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/migrations/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAGH,OAAO,EACL,KAAK,cAAc,EACnB,6BAA6B,EAC7B,eAAe,EACf,KAAK,sBAAsB,GAC5B,MAAM,uBAAuB,CAAC;AAE/B,OAAO,EACL,eAAe,EACf,YAAY,EACZ,aAAa,EACb,cAAc,GACf,MAAM,eAAe,CAAC;AACvB,YAAY,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAE/C,OAAO,EACL,mBAAmB,EACnB,kBAAkB,EAClB,cAAc,EACd,oBAAoB,EACpB,oBAAoB,EACpB,gBAAgB,EAChB,wBAAwB,EACxB,cAAc,EACd,qBAAqB,GACtB,MAAM,aAAa,CAAC;AAErB,OAAO,EACL,KAAK,8BAA8B,EACnC,KAAK,6BAA6B,EAClC,uBAAuB,EACvB,0BAA0B,EAC1B,KAAK,sBAAsB,EAC3B,KAAK,yBAAyB,EAC9B,KAAK,wBAAwB,EAC7B,KAAK,uBAAuB,EAC5B,KAAK,iCAAiC,EACtC,0BAA0B,GAC3B,MAAM,4BAA4B,CAAC;AACpC,YAAY,EACV,kBAAkB,EAClB,wBAAwB,GACzB,MAAM,gBAAgB,CAAC;AAExB,OAAO,EACL,yBAAyB,EACzB,yBAAyB,EACzB,0BAA0B,EAC1B,kBAAkB,GACnB,MAAM,gBAAgB,CAAC;AAExB,OAAO,EACL,2BAA2B,EAC3B,gCAAgC,EAChC,0BAA0B,EAC1B,KAAK,wBAAwB,EAC7B,KAAK,uBAAuB,EAC5B,KAAK,mBAAmB,EACxB,KAAK,2BAA2B,EAChC,KAAK,uBAAuB,EAC5B,KAAK,kBAAkB,EACvB,KAAK,wBAAwB,EAC7B,KAAK,uBAAuB,EAC5B,6BAA6B,EAC7B,2BAA2B,GAC5B,MAAM,oBAAoB,CAAC;AAE5B,OAAO,EACL,yBAAyB,EACzB,KAAK,sBAAsB,EAC3B,KAAK,qBAAqB,EAC1B,KAAK,iBAAiB,EACtB,wBAAwB,EACxB,KAAK,yBAAyB,EAC9B,KAAK,oBAAoB,EACzB,KAAK,wBAAwB,EAC7B,KAAK,uBAAuB,EAC5B,KAAK,iBAAiB,EACtB,0BAA0B,EAC1B,+BAA+B,GAChC,MAAM,kBAAkB,CAAC;AAE1B,OAAO,EACL,0BAA0B,EAC1B,KAAK,yBAAyB,EAC9B,KAAK,wBAAwB,EAC7B,kBAAkB,EAClB,KAAK,6BAA6B,GACnC,MAAM,kBAAkB,CAAC;AAE1B,OAAO,EACL,KAAK,iCAAiC,EACtC,4BAA4B,EAC5B,0BAA0B,EAC1B,KAAK,8BAA8B,EACnC,uBAAuB,EACvB,wBAAwB,EACxB,2BAA2B,EAC3B,KAAK,sBAAsB,EAC3B,2BAA2B,GAC5B,MAAM,qBAAqB,CAAC;AAE7B,OAAO,EACL,wBAAwB,EACxB,uBAAuB,EACvB,gBAAgB,EAChB,sBAAsB,EACtB,sBAAsB,GACvB,MAAM,cAAc,CAAC;AAGtB,YAAY,EACV,sBAAsB,EACtB,wBAAwB,EACxB,UAAU,EACV,cAAc,EACd,iBAAiB,EAEjB,aAAa,EACb,mBAAmB,EACnB,sBAAsB,EACtB,WAAW,EACX,cAAc,EACd,eAAe,EACf,SAAS,EACT,mBAAmB,EACnB,aAAa,EACb,eAAe,EAEf,eAAe,EACf,gBAAgB,EAEhB,uBAAuB,EACvB,mBAAmB,EACnB,qBAAqB,EACrB,eAAe,EACf,YAAY,EACZ,UAAU,EACV,qBAAqB,EACrB,yBAAyB,EACzB,eAAe,GAChB,MAAM,YAAY,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/migrations/index.ts"],"names":[],"mappings":"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"}
@@ -7,6 +7,7 @@ import { computeChecksum, normalizeSQL, shortChecksum, verifyChecksum } from "./
7
7
  import { FRAMEWORK_BASE_TABLE_NAMES, dropFrameworkBaseTables, planFrameworkBaseTableDrop } from "./framework-base-tables.js";
8
8
  import { MigrationGenerator, createMigrationDefinition, generateMigrationSequence, generateMigrationTimestamp } from "./generator.js";
9
9
  import { MinorUnitsPreflightError, buildMinorUnitsStatements, preflightMinorUnitsRescale, rescaleMoneyColumnsToMinorUnits } from "./minor-units.js";
10
+ import { collectNullEqualIndexTargets, migrateNullEqualIndexes, nullEqualDuplicateDetector, nullEqualIndexStatements, preflightNullEqualIndexes } from "./null-equal-indexes.js";
10
11
  import { MigrationTracker, buildConcurrentIndexPlan, extractCreatedIndexName, planPostgresStatements } from "./tracker.js";
11
12
  import { getPendingSchemaStatements, migrateSmrtSchemas } from "./orchestrate.js";
12
- export { BackfillTableUnavailableError, BackfillTracker, FRAMEWORK_BASE_TABLE_NAMES, MigrationGenerator, MigrationTracker, MinorUnitsPreflightError, SQLITE_REBUILD_TABLE_PREFIX, SchemaComparer, buildConcurrentIndexPlan, buildIntegerWidthStatements, buildIntegerWidthTableStatements, buildMinorUnitsStatements, buildSqliteRebuildStatements, canonicalizeDefault, collectIntegerWidthTargets, computeChecksum, createMigrationDefinition, dropFrameworkBaseTables, extractCreatedIndexName, generateMigrationSequence, generateMigrationTimestamp, generateSchemaDiff, getPendingSchemaStatements, getSQLFromDiff, hasActionableChanges, isAdvisoryOnlyChange, isInfoOnlyChange, isManualOrAdvisoryChange, isSqliteRebuildPlaceholder, migrateSmrtSchemas, normalizeSQL, parsePostgresTimeoutMs, planFrameworkBaseTableDrop, planPostgresStatements, planSqliteTableRebuilds, preflightIntegerWidthWidening, preflightMinorUnitsRescale, rescaleMoneyColumnsToMinorUnits, rewriteSqliteCreateTable, shortChecksum, sqliteRebuildPlaceholderSql, uniqueColumnIndexName, verifyChecksum, widenIntegerColumnsToBigInt };
13
+ export { BackfillTableUnavailableError, BackfillTracker, FRAMEWORK_BASE_TABLE_NAMES, MigrationGenerator, MigrationTracker, MinorUnitsPreflightError, SQLITE_REBUILD_TABLE_PREFIX, SchemaComparer, buildConcurrentIndexPlan, buildIntegerWidthStatements, buildIntegerWidthTableStatements, buildMinorUnitsStatements, buildSqliteRebuildStatements, canonicalizeDefault, collectIntegerWidthTargets, collectNullEqualIndexTargets, computeChecksum, createMigrationDefinition, dropFrameworkBaseTables, extractCreatedIndexName, generateMigrationSequence, generateMigrationTimestamp, generateSchemaDiff, getPendingSchemaStatements, getSQLFromDiff, hasActionableChanges, isAdvisoryOnlyChange, isInfoOnlyChange, isManualOrAdvisoryChange, isSqliteRebuildPlaceholder, migrateNullEqualIndexes, migrateSmrtSchemas, normalizeSQL, nullEqualDuplicateDetector, nullEqualIndexStatements, parsePostgresTimeoutMs, planFrameworkBaseTableDrop, planPostgresStatements, planSqliteTableRebuilds, preflightIntegerWidthWidening, preflightMinorUnitsRescale, preflightNullEqualIndexes, rescaleMoneyColumnsToMinorUnits, rewriteSqliteCreateTable, shortChecksum, sqliteRebuildPlaceholderSql, uniqueColumnIndexName, verifyChecksum, widenIntegerColumnsToBigInt };
@@ -0,0 +1,37 @@
1
+ import { DatabaseInterface } from '@happyvertical/sql';
2
+ import { SchemaDefinition } from '../schema/types.js';
3
+ export interface NullEqualIndexTarget {
4
+ table: string;
5
+ index: string;
6
+ columns: string[];
7
+ }
8
+ export interface NullEqualIndexOptions {
9
+ engineHint?: string;
10
+ lockTimeout?: string | number;
11
+ statementTimeout?: string | number;
12
+ }
13
+ export interface NullEqualIndexReport extends NullEqualIndexTarget {
14
+ state: 'pending' | 'current' | 'blocked';
15
+ reason?: string;
16
+ duplicateGroups: number;
17
+ detectorSql: string;
18
+ }
19
+ export interface NullEqualIndexPreflight {
20
+ supported: boolean;
21
+ engine: string;
22
+ serverVersion: number | null;
23
+ indexes: NullEqualIndexReport[];
24
+ summary: string;
25
+ }
26
+ /** Metadata is the authority; optional business UNIQUE indexes are never inferred. */
27
+ export declare function collectNullEqualIndexTargets(schemas: Record<string, SchemaDefinition>): NullEqualIndexTarget[];
28
+ export declare function nullEqualDuplicateDetector(target: NullEqualIndexTarget): string;
29
+ export declare function nullEqualIndexStatements(target: NullEqualIndexTarget): string[];
30
+ /** Read-only; even PostgreSQL 14 never parses the PostgreSQL 15 catalog field. */
31
+ export declare function preflightNullEqualIndexes(db: DatabaseInterface, targets: NullEqualIndexTarget[], options?: NullEqualIndexOptions): Promise<NullEqualIndexPreflight>;
32
+ /** Locks all targets before rechecking; any failure restores every original index. */
33
+ export declare function migrateNullEqualIndexes(db: DatabaseInterface, targets: NullEqualIndexTarget[], options?: NullEqualIndexOptions): Promise<{
34
+ preflight: NullEqualIndexPreflight;
35
+ statements: string[];
36
+ }>;
37
+ //# sourceMappingURL=null-equal-indexes.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"null-equal-indexes.d.ts","sourceRoot":"","sources":["../../src/migrations/null-equal-indexes.ts"],"names":[],"mappings":"AAAA,yFAAyF;AACzF,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AAK5D,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAG3D,MAAM,WAAW,oBAAoB;IACnC,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,EAAE,CAAC;CACnB;AACD,MAAM,WAAW,qBAAqB;IACpC,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,WAAW,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IAC9B,gBAAgB,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;CACpC;AACD,MAAM,WAAW,oBAAqB,SAAQ,oBAAoB;IAChE,KAAK,EAAE,SAAS,GAAG,SAAS,GAAG,SAAS,CAAC;IACzC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,eAAe,EAAE,MAAM,CAAC;IACxB,WAAW,EAAE,MAAM,CAAC;CACrB;AACD,MAAM,WAAW,uBAAuB;IACtC,SAAS,EAAE,OAAO,CAAC;IACnB,MAAM,EAAE,MAAM,CAAC;IACf,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,OAAO,EAAE,oBAAoB,EAAE,CAAC;IAChC,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,sFAAsF;AACtF,wBAAgB,4BAA4B,CAC1C,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,gBAAgB,CAAC,GACxC,oBAAoB,EAAE,CAsCxB;AAKD,wBAAgB,0BAA0B,CACxC,MAAM,EAAE,oBAAoB,GAC3B,MAAM,CAGR;AAED,wBAAgB,wBAAwB,CACtC,MAAM,EAAE,oBAAoB,GAC3B,MAAM,EAAE,CAKV;AAED,kFAAkF;AAClF,wBAAsB,yBAAyB,CAC7C,EAAE,EAAE,iBAAiB,EACrB,OAAO,EAAE,oBAAoB,EAAE,EAC/B,OAAO,GAAE,qBAA0B,GAClC,OAAO,CAAC,uBAAuB,CAAC,CA4GlC;AAiBD,sFAAsF;AACtF,wBAAsB,uBAAuB,CAC3C,EAAE,EAAE,iBAAiB,EACrB,OAAO,EAAE,oBAAoB,EAAE,EAC/B,OAAO,GAAE,qBAA0B,GAClC,OAAO,CAAC;IAAE,SAAS,EAAE,uBAAuB,CAAC;IAAC,UAAU,EAAE,MAAM,EAAE,CAAA;CAAE,CAAC,CAwCvE"}
@@ -0,0 +1,133 @@
1
+ import { toSafeInteger } from "../utils/safe-integer.js";
2
+ import { quoteIdentifier } from "../schema/sql-identifiers.js";
3
+ import { detectEngine } from "../schema/ddl/index.js";
4
+ import { parsePostgresTimeoutMs } from "../postgres-timeouts.js";
5
+ import { nullableConflictIdentity } from "../schema/conflict-target.js";
6
+ //#region src/migrations/null-equal-indexes.ts
7
+ /** Metadata is the authority; optional business UNIQUE indexes are never inferred. */
8
+ function collectNullEqualIndexTargets(schemas) {
9
+ const targets = /* @__PURE__ */ new Map();
10
+ for (const schema of Object.values(schemas)) for (const index of schema.indexes ?? []) {
11
+ if (!index.nullsNotDistinct) continue;
12
+ if (!index.unique || index.where || index.jsonPath || !index.columns.length || !index.columns.every((column) => schema.columns[column]) || !nullableConflictIdentity(index.columns, schema.columns)) throw new Error(`Invalid framework NULL-equal identity ${schema.tableName}.${index.name}; regenerate manifests.`);
13
+ const target = {
14
+ table: schema.tableName,
15
+ index: index.name,
16
+ columns: [...index.columns]
17
+ };
18
+ const key = `${target.table}\0${target.index}`;
19
+ const previous = targets.get(key);
20
+ if (previous && JSON.stringify(previous.columns) !== JSON.stringify(target.columns)) throw new Error(`Conflicting framework identity definitions for ${target.table}.${target.index}.`);
21
+ targets.set(key, target);
22
+ }
23
+ return [...targets.values()].sort((a, b) => a.table.localeCompare(b.table) || a.index.localeCompare(b.index));
24
+ }
25
+ var qualified = (name) => `${quoteIdentifier("public")}.${quoteIdentifier(name)}`;
26
+ function nullEqualDuplicateDetector(target) {
27
+ const columns = target.columns.map(quoteIdentifier).join(", ");
28
+ return `SELECT ${columns}, COUNT(*) AS duplicate_count FROM ${qualified(target.table)} GROUP BY ${columns} HAVING COUNT(*) > 1`;
29
+ }
30
+ function nullEqualIndexStatements(target) {
31
+ return [`DROP INDEX ${qualified(target.index)}`, `CREATE UNIQUE INDEX ${quoteIdentifier(target.index)} ON ${qualified(target.table)} (${target.columns.map(quoteIdentifier).join(", ")}) NULLS NOT DISTINCT`];
32
+ }
33
+ /** Read-only; even PostgreSQL 14 never parses the PostgreSQL 15 catalog field. */
34
+ async function preflightNullEqualIndexes(db, targets, options = {}) {
35
+ const engine = detectEngine(db.url ?? "", options.engineHint);
36
+ let serverVersion = null;
37
+ if (engine === "postgres") serverVersion = toSafeInteger((await db.query("SHOW server_version_num")).rows[0]?.server_version_num, "PostgreSQL server version");
38
+ if (engine !== "postgres" || serverVersion === null || serverVersion < 15e4) return {
39
+ supported: false,
40
+ engine,
41
+ serverVersion,
42
+ indexes: [],
43
+ summary: "NULL-equal conflict-index migration requires PostgreSQL 15+. Existing adapter fallback remains unchanged; no migration is applied."
44
+ };
45
+ const indexes = [];
46
+ for (const target of targets) {
47
+ const detectorSql = nullEqualDuplicateDetector(target);
48
+ const report = {
49
+ ...target,
50
+ state: "blocked",
51
+ duplicateGroups: 0,
52
+ detectorSql
53
+ };
54
+ const row = (await db.query(`
55
+ SELECT i.indisunique, i.indisvalid, i.indisready, i.indnullsnotdistinct,
56
+ i.indpred IS NULL AS full_index, i.indexprs IS NULL AS plain_columns,
57
+ i.indnkeyatts = i.indnatts AS no_includes, am.amname,
58
+ ARRAY(SELECT a.attname FROM unnest(i.indkey) WITH ORDINALITY AS k(attnum, ord)
59
+ JOIN pg_attribute a ON a.attrelid = t.oid AND a.attnum = k.attnum ORDER BY k.ord)::text[] AS columns,
60
+ EXISTS(SELECT 1 FROM pg_constraint c WHERE c.conindid = x.oid) AS constraint_owned,
61
+ EXISTS(SELECT 1 FROM pg_depend d WHERE d.refclassid = 'pg_class'::regclass AND d.refobjid = x.oid) AS depended_on,
62
+ EXISTS(SELECT 1 FROM pg_depend d WHERE d.classid = 'pg_class'::regclass AND d.objid = x.oid AND d.deptype IN ('e', 'i')) AS managed_dependency,
63
+ NOT EXISTS(SELECT 1 FROM unnest(i.indoption) AS opt WHERE opt <> 0) AS default_order,
64
+ NOT EXISTS(SELECT 1 FROM unnest(i.indclass) AS op JOIN pg_opclass c ON c.oid = op WHERE NOT c.opcdefault) AS default_ops,
65
+ NOT EXISTS(SELECT 1 FROM unnest(i.indkey, i.indcollation) AS k(attnum, coll)
66
+ JOIN pg_attribute a ON a.attrelid = t.oid AND a.attnum = k.attnum WHERE k.coll <> a.attcollation) AS default_collation,
67
+ x.reloptions IS NULL AND x.reltablespace = 0 AS default_storage,
68
+ t.relkind
69
+ FROM pg_class t JOIN pg_namespace n ON n.oid = t.relnamespace
70
+ JOIN pg_index i ON i.indrelid = t.oid JOIN pg_class x ON x.oid = i.indexrelid
71
+ JOIN pg_am am ON am.oid = x.relam
72
+ WHERE n.nspname = 'public' AND t.relname = $1 AND x.relname = $2`, [target.table, target.index])).rows[0];
73
+ if (!row) report.reason = "Expected framework index is missing. Ordinary db:migrate may treat a differently named equivalent index as already satisfied. Inspect pg_indexes and verify framework ownership, exact ordered keys, uniqueness and dependencies. In a separate maintenance transaction, rename a verified owned equivalent with ALTER INDEX ... RENAME TO the expected name, or create the expected framework index from the generated schema; then repeat preflight. Never rename an arbitrary business index.";
74
+ else if (!row.indisunique || !row.indisvalid || !row.indisready || !row.full_index || !row.plain_columns || !row.no_includes || !row.default_order || !row.default_ops || !row.default_collation || !row.default_storage || row.amname !== "btree" || row.relkind !== "r" || JSON.stringify(row.columns) !== JSON.stringify(target.columns)) report.reason = "Live index shape differs from the framework identity (or table is partitioned). Resolve schema drift before this migration.";
75
+ else if (row.constraint_owned || row.depended_on || row.managed_dependency) report.reason = "Index has constraint ownership or unmanaged dependencies. Arrange a separate dependency-aware migration; this command never drops constraints or uses CASCADE.";
76
+ else {
77
+ report.duplicateGroups = toSafeInteger((await db.query(`SELECT COUNT(*) AS groups FROM (${detectorSql}) AS duplicate_groups`)).rows[0]?.groups ?? 0, "NULL-equal duplicate groups");
78
+ if (report.duplicateGroups) report.reason = `${report.duplicateGroups} duplicate NULL-equal identity group(s). Inspect detectorSql and resolve identities explicitly; no rows are changed.`;
79
+ else report.state = row.indnullsnotdistinct ? "current" : "pending";
80
+ }
81
+ indexes.push(report);
82
+ }
83
+ return {
84
+ supported: true,
85
+ engine,
86
+ serverVersion,
87
+ indexes,
88
+ summary: `NULL-equal conflict indexes: ${indexes.filter((r) => r.state === "pending").length} pending, ${indexes.filter((r) => r.state === "current").length} current, ${indexes.filter((r) => r.state === "blocked").length} blocked.`
89
+ };
90
+ }
91
+ function assertReady(preflight) {
92
+ const blocked = preflight.indexes.filter((index) => index.state === "blocked");
93
+ if (blocked.length) throw new Error(blocked.map((index) => `${index.table}.${index.index}: ${index.reason}\nDetector: ${index.detectorSql}`).join("\n"));
94
+ }
95
+ /** Locks all targets before rechecking; any failure restores every original index. */
96
+ async function migrateNullEqualIndexes(db, targets, options = {}) {
97
+ const preflight = await preflightNullEqualIndexes(db, targets, options);
98
+ if (!preflight.supported) return {
99
+ preflight,
100
+ statements: []
101
+ };
102
+ assertReady(preflight);
103
+ if (!preflight.indexes.some((index) => index.state === "pending")) return {
104
+ preflight,
105
+ statements: []
106
+ };
107
+ if (!db.transaction) throw new Error("NULL-equal conflict-index migration requires an owning transaction.");
108
+ const lockTimeout = parsePostgresTimeoutMs(options.lockTimeout, 3e4);
109
+ const statementTimeout = parsePostgresTimeoutMs(options.statementTimeout, 6e4);
110
+ const statements = [];
111
+ await db.transaction(async (tx) => {
112
+ await tx.query(`SET LOCAL lock_timeout = '${lockTimeout}ms'`);
113
+ await tx.query(`SET LOCAL statement_timeout = '${statementTimeout}ms'`);
114
+ for (const table of [...new Set(targets.map((target) => target.table))].sort()) await tx.query(`LOCK TABLE ${qualified(table)} IN ACCESS EXCLUSIVE MODE`);
115
+ const locked = await preflightNullEqualIndexes(tx, targets, {
116
+ ...options,
117
+ engineHint: "postgres"
118
+ });
119
+ assertReady(locked);
120
+ for (const target of locked.indexes.filter((index) => index.state === "pending")) for (const statement of nullEqualIndexStatements(target)) {
121
+ await tx.query(statement);
122
+ statements.push(statement);
123
+ }
124
+ });
125
+ return {
126
+ preflight,
127
+ statements
128
+ };
129
+ }
130
+ //#endregion
131
+ export { collectNullEqualIndexTargets, migrateNullEqualIndexes, nullEqualDuplicateDetector, nullEqualIndexStatements, preflightNullEqualIndexes };
132
+
133
+ //# sourceMappingURL=null-equal-indexes.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"null-equal-indexes.js","names":[],"sources":["../../src/migrations/null-equal-indexes.ts"],"sourcesContent":["/** Explicit, atomic maintenance upgrade of framework-owned nullable conflict indexes. */\nimport type { DatabaseInterface } from '@happyvertical/sql';\nimport { parsePostgresTimeoutMs } from '../postgres-timeouts.js';\nimport { nullableConflictIdentity } from '../schema/conflict-target.js';\nimport { detectEngine } from '../schema/ddl/index.js';\nimport { quoteIdentifier } from '../schema/sql-identifiers.js';\nimport type { SchemaDefinition } from '../schema/types.js';\nimport { toSafeInteger } from '../utils/safe-integer.js';\n\nexport interface NullEqualIndexTarget {\n table: string;\n index: string;\n columns: string[];\n}\nexport interface NullEqualIndexOptions {\n engineHint?: string;\n lockTimeout?: string | number;\n statementTimeout?: string | number;\n}\nexport interface NullEqualIndexReport extends NullEqualIndexTarget {\n state: 'pending' | 'current' | 'blocked';\n reason?: string;\n duplicateGroups: number;\n detectorSql: string;\n}\nexport interface NullEqualIndexPreflight {\n supported: boolean;\n engine: string;\n serverVersion: number | null;\n indexes: NullEqualIndexReport[];\n summary: string;\n}\n\n/** Metadata is the authority; optional business UNIQUE indexes are never inferred. */\nexport function collectNullEqualIndexTargets(\n schemas: Record<string, SchemaDefinition>,\n): NullEqualIndexTarget[] {\n const targets = new Map<string, NullEqualIndexTarget>();\n for (const schema of Object.values(schemas)) {\n for (const index of schema.indexes ?? []) {\n if (!index.nullsNotDistinct) continue;\n if (\n !index.unique ||\n index.where ||\n index.jsonPath ||\n !index.columns.length ||\n !index.columns.every((column) => schema.columns[column]) ||\n !nullableConflictIdentity(index.columns, schema.columns)\n ) {\n throw new Error(\n `Invalid framework NULL-equal identity ${schema.tableName}.${index.name}; regenerate manifests.`,\n );\n }\n const target = {\n table: schema.tableName,\n index: index.name,\n columns: [...index.columns],\n };\n const key = `${target.table}\\0${target.index}`;\n const previous = targets.get(key);\n if (\n previous &&\n JSON.stringify(previous.columns) !== JSON.stringify(target.columns)\n ) {\n throw new Error(\n `Conflicting framework identity definitions for ${target.table}.${target.index}.`,\n );\n }\n targets.set(key, target);\n }\n }\n return [...targets.values()].sort(\n (a, b) => a.table.localeCompare(b.table) || a.index.localeCompare(b.index),\n );\n}\n\nconst qualified = (name: string) =>\n `${quoteIdentifier('public')}.${quoteIdentifier(name)}`;\n\nexport function nullEqualDuplicateDetector(\n target: NullEqualIndexTarget,\n): string {\n const columns = target.columns.map(quoteIdentifier).join(', ');\n return `SELECT ${columns}, COUNT(*) AS duplicate_count FROM ${qualified(target.table)} GROUP BY ${columns} HAVING COUNT(*) > 1`;\n}\n\nexport function nullEqualIndexStatements(\n target: NullEqualIndexTarget,\n): string[] {\n return [\n `DROP INDEX ${qualified(target.index)}`,\n `CREATE UNIQUE INDEX ${quoteIdentifier(target.index)} ON ${qualified(target.table)} (${target.columns.map(quoteIdentifier).join(', ')}) NULLS NOT DISTINCT`,\n ];\n}\n\n/** Read-only; even PostgreSQL 14 never parses the PostgreSQL 15 catalog field. */\nexport async function preflightNullEqualIndexes(\n db: DatabaseInterface,\n targets: NullEqualIndexTarget[],\n options: NullEqualIndexOptions = {},\n): Promise<NullEqualIndexPreflight> {\n const engine = detectEngine(db.url ?? '', options.engineHint);\n let serverVersion: number | null = null;\n if (engine === 'postgres') {\n const result = await db.query('SHOW server_version_num');\n serverVersion = toSafeInteger(\n result.rows[0]?.server_version_num,\n 'PostgreSQL server version',\n );\n }\n if (\n engine !== 'postgres' ||\n serverVersion === null ||\n serverVersion < 150000\n ) {\n return {\n supported: false,\n engine,\n serverVersion,\n indexes: [],\n summary:\n 'NULL-equal conflict-index migration requires PostgreSQL 15+. Existing adapter fallback remains unchanged; no migration is applied.',\n };\n }\n const indexes: NullEqualIndexReport[] = [];\n for (const target of targets) {\n const detectorSql = nullEqualDuplicateDetector(target);\n const report: NullEqualIndexReport = {\n ...target,\n state: 'blocked',\n duplicateGroups: 0,\n detectorSql,\n };\n // Exact named standalone index, ordered keys, default btree semantics only.\n // Refuse constraint ownership and all reverse dependencies; never CASCADE.\n const result = await db.query(\n `\n SELECT i.indisunique, i.indisvalid, i.indisready, i.indnullsnotdistinct,\n i.indpred IS NULL AS full_index, i.indexprs IS NULL AS plain_columns,\n i.indnkeyatts = i.indnatts AS no_includes, am.amname,\n ARRAY(SELECT a.attname FROM unnest(i.indkey) WITH ORDINALITY AS k(attnum, ord)\n JOIN pg_attribute a ON a.attrelid = t.oid AND a.attnum = k.attnum ORDER BY k.ord)::text[] AS columns,\n EXISTS(SELECT 1 FROM pg_constraint c WHERE c.conindid = x.oid) AS constraint_owned,\n EXISTS(SELECT 1 FROM pg_depend d WHERE d.refclassid = 'pg_class'::regclass AND d.refobjid = x.oid) AS depended_on,\n EXISTS(SELECT 1 FROM pg_depend d WHERE d.classid = 'pg_class'::regclass AND d.objid = x.oid AND d.deptype IN ('e', 'i')) AS managed_dependency,\n NOT EXISTS(SELECT 1 FROM unnest(i.indoption) AS opt WHERE opt <> 0) AS default_order,\n NOT EXISTS(SELECT 1 FROM unnest(i.indclass) AS op JOIN pg_opclass c ON c.oid = op WHERE NOT c.opcdefault) AS default_ops,\n NOT EXISTS(SELECT 1 FROM unnest(i.indkey, i.indcollation) AS k(attnum, coll)\n JOIN pg_attribute a ON a.attrelid = t.oid AND a.attnum = k.attnum WHERE k.coll <> a.attcollation) AS default_collation,\n x.reloptions IS NULL AND x.reltablespace = 0 AS default_storage,\n t.relkind\n FROM pg_class t JOIN pg_namespace n ON n.oid = t.relnamespace\n JOIN pg_index i ON i.indrelid = t.oid JOIN pg_class x ON x.oid = i.indexrelid\n JOIN pg_am am ON am.oid = x.relam\n WHERE n.nspname = 'public' AND t.relname = $1 AND x.relname = $2`,\n [target.table, target.index],\n );\n const row = result.rows[0];\n if (!row) {\n report.reason =\n 'Expected framework index is missing. Ordinary db:migrate may treat a differently named equivalent index as already satisfied. Inspect pg_indexes and verify framework ownership, exact ordered keys, uniqueness and dependencies. In a separate maintenance transaction, rename a verified owned equivalent with ALTER INDEX ... RENAME TO the expected name, or create the expected framework index from the generated schema; then repeat preflight. Never rename an arbitrary business index.';\n } else if (\n !row.indisunique ||\n !row.indisvalid ||\n !row.indisready ||\n !row.full_index ||\n !row.plain_columns ||\n !row.no_includes ||\n !row.default_order ||\n !row.default_ops ||\n !row.default_collation ||\n !row.default_storage ||\n row.amname !== 'btree' ||\n row.relkind !== 'r' ||\n JSON.stringify(row.columns) !== JSON.stringify(target.columns)\n ) {\n report.reason =\n 'Live index shape differs from the framework identity (or table is partitioned). Resolve schema drift before this migration.';\n } else if (\n row.constraint_owned ||\n row.depended_on ||\n row.managed_dependency\n ) {\n report.reason =\n 'Index has constraint ownership or unmanaged dependencies. Arrange a separate dependency-aware migration; this command never drops constraints or uses CASCADE.';\n } else {\n const duplicates = await db.query(\n `SELECT COUNT(*) AS groups FROM (${detectorSql}) AS duplicate_groups`,\n );\n report.duplicateGroups = toSafeInteger(\n duplicates.rows[0]?.groups ?? 0,\n 'NULL-equal duplicate groups',\n );\n if (report.duplicateGroups) {\n report.reason = `${report.duplicateGroups} duplicate NULL-equal identity group(s). Inspect detectorSql and resolve identities explicitly; no rows are changed.`;\n } else {\n report.state = row.indnullsnotdistinct ? 'current' : 'pending';\n }\n }\n indexes.push(report);\n }\n return {\n supported: true,\n engine,\n serverVersion,\n indexes,\n summary: `NULL-equal conflict indexes: ${indexes.filter((r) => r.state === 'pending').length} pending, ${indexes.filter((r) => r.state === 'current').length} current, ${indexes.filter((r) => r.state === 'blocked').length} blocked.`,\n };\n}\n\nfunction assertReady(preflight: NullEqualIndexPreflight): void {\n const blocked = preflight.indexes.filter(\n (index) => index.state === 'blocked',\n );\n if (blocked.length)\n throw new Error(\n blocked\n .map(\n (index) =>\n `${index.table}.${index.index}: ${index.reason}\\nDetector: ${index.detectorSql}`,\n )\n .join('\\n'),\n );\n}\n\n/** Locks all targets before rechecking; any failure restores every original index. */\nexport async function migrateNullEqualIndexes(\n db: DatabaseInterface,\n targets: NullEqualIndexTarget[],\n options: NullEqualIndexOptions = {},\n): Promise<{ preflight: NullEqualIndexPreflight; statements: string[] }> {\n const preflight = await preflightNullEqualIndexes(db, targets, options);\n if (!preflight.supported) return { preflight, statements: [] };\n assertReady(preflight);\n if (!preflight.indexes.some((index) => index.state === 'pending'))\n return { preflight, statements: [] };\n if (!db.transaction)\n throw new Error(\n 'NULL-equal conflict-index migration requires an owning transaction.',\n );\n const lockTimeout = parsePostgresTimeoutMs(options.lockTimeout, 30_000);\n const statementTimeout = parsePostgresTimeoutMs(\n options.statementTimeout,\n 60_000,\n );\n const statements: string[] = [];\n await db.transaction(async (tx) => {\n await tx.query(`SET LOCAL lock_timeout = '${lockTimeout}ms'`);\n await tx.query(`SET LOCAL statement_timeout = '${statementTimeout}ms'`);\n for (const table of [\n ...new Set(targets.map((target) => target.table)),\n ].sort()) {\n await tx.query(`LOCK TABLE ${qualified(table)} IN ACCESS EXCLUSIVE MODE`);\n }\n const locked = await preflightNullEqualIndexes(\n tx as DatabaseInterface,\n targets,\n { ...options, engineHint: 'postgres' },\n );\n assertReady(locked);\n for (const target of locked.indexes.filter(\n (index) => index.state === 'pending',\n )) {\n for (const statement of nullEqualIndexStatements(target)) {\n await tx.query(statement);\n statements.push(statement);\n }\n }\n });\n return { preflight, statements };\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"tracker.d.ts","sourceRoot":"","sources":["../../src/migrations/tracker.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"tracker.d.ts","sourceRoot":"","sources":["../../src/migrations/tracker.ts"],"names":[],"mappings":"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"}
@@ -1,4 +1,5 @@
1
1
  import { toSafeBooleanInteger, toSafeInteger } from "../utils/safe-integer.js";
2
+ import "../schema/ddl/null-equal-index.js";
2
3
  import { detectEngine } from "../schema/ddl/index.js";
3
4
  import { CREATE_SMRT_SCHEMA_MIGRATIONS_TABLE, getSystemTableDDLForEngine } from "../system/schema.js";
4
5
  import { parsePostgresTimeoutMs } from "../postgres-timeouts.js";
@@ -828,6 +829,11 @@ function planPostgresStatements(statements, useConcurrentIndexes) {
828
829
  const concurrent = [];
829
830
  const regular = [];
830
831
  for (const sql of statements) {
832
+ if (sql.trimStart().startsWith("-- smrt:null-equal-conflict-index")) {
833
+ if (useConcurrentIndexes) throw new Error("NULL-equal conflict index DDL requires an atomic maintenance-window migration; retry without --postgres-safe. Existing indexes require db:migrate-null-equal-indexes.");
834
+ regular.push(sql);
835
+ continue;
836
+ }
831
837
  if (concurrentRegex.test(sql)) {
832
838
  concurrent.push(sql);
833
839
  continue;