@happyvertical/smrt-core 0.40.69 → 0.41.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 (237) hide show
  1. package/AGENTS.md +29 -4
  2. package/README.md +20 -1
  3. package/agents/change-feed.md +1 -1
  4. package/agents/query-bounds.md +45 -0
  5. package/agents/schema-paths.md +786 -0
  6. package/dist/browser.d.ts +1 -0
  7. package/dist/browser.d.ts.map +1 -1
  8. package/dist/browser.js +5 -3
  9. package/dist/cascade.d.ts +120 -0
  10. package/dist/cascade.d.ts.map +1 -0
  11. package/dist/cascade.js +430 -0
  12. package/dist/cascade.js.map +1 -0
  13. package/dist/change-feed.d.ts +34 -2
  14. package/dist/change-feed.d.ts.map +1 -1
  15. package/dist/change-feed.js +52 -11
  16. package/dist/change-feed.js.map +1 -1
  17. package/dist/class.d.ts +36 -3
  18. package/dist/class.d.ts.map +1 -1
  19. package/dist/class.js +87 -9
  20. package/dist/class.js.map +1 -1
  21. package/dist/collection-cache.js +0 -0
  22. package/dist/collection-cache.js.map +1 -1
  23. package/dist/collection.d.ts +130 -2
  24. package/dist/collection.d.ts.map +1 -1
  25. package/dist/collection.js +290 -57
  26. package/dist/collection.js.map +1 -1
  27. package/dist/config.d.ts +10 -0
  28. package/dist/config.d.ts.map +1 -1
  29. package/dist/config.js.map +1 -1
  30. package/dist/database.d.ts +8 -0
  31. package/dist/database.d.ts.map +1 -1
  32. package/dist/database.js +16 -8
  33. package/dist/database.js.map +1 -1
  34. package/dist/db-errors.d.ts +105 -0
  35. package/dist/db-errors.d.ts.map +1 -0
  36. package/dist/db-errors.js +382 -0
  37. package/dist/db-errors.js.map +1 -0
  38. package/dist/decorators/index.d.ts +80 -6
  39. package/dist/decorators/index.d.ts.map +1 -1
  40. package/dist/decorators/index.js +102 -12
  41. package/dist/decorators/index.js.map +1 -1
  42. package/dist/dispatch/bus.d.ts.map +1 -1
  43. package/dist/dispatch/bus.js +4 -3
  44. package/dist/dispatch/bus.js.map +1 -1
  45. package/dist/dispatch/collections/Dispatches.d.ts.map +1 -1
  46. package/dist/dispatch/collections/Dispatches.js +19 -4
  47. package/dist/dispatch/collections/Dispatches.js.map +1 -1
  48. package/dist/dispatch/types.d.ts +5 -0
  49. package/dist/dispatch/types.d.ts.map +1 -1
  50. package/dist/embedded-write-queue.d.ts +46 -0
  51. package/dist/embedded-write-queue.d.ts.map +1 -0
  52. package/dist/embedded-write-queue.js +66 -0
  53. package/dist/embedded-write-queue.js.map +1 -0
  54. package/dist/embeddings/storage.d.ts +7 -0
  55. package/dist/embeddings/storage.d.ts.map +1 -1
  56. package/dist/embeddings/storage.js +29 -12
  57. package/dist/embeddings/storage.js.map +1 -1
  58. package/dist/errors.d.ts +31 -3
  59. package/dist/errors.d.ts.map +1 -1
  60. package/dist/errors.js +34 -2
  61. package/dist/errors.js.map +1 -1
  62. package/dist/generators/changes-route.d.ts.map +1 -1
  63. package/dist/generators/changes-route.js +6 -3
  64. package/dist/generators/changes-route.js.map +1 -1
  65. package/dist/generators/mcp-runtime-template.d.ts +8 -0
  66. package/dist/generators/mcp-runtime-template.d.ts.map +1 -1
  67. package/dist/generators/mcp-runtime-template.js +38 -4
  68. package/dist/generators/mcp-runtime-template.js.map +1 -1
  69. package/dist/generators/mcp.d.ts +16 -0
  70. package/dist/generators/mcp.d.ts.map +1 -1
  71. package/dist/generators/mcp.js +41 -3
  72. package/dist/generators/mcp.js.map +1 -1
  73. package/dist/generators/rest.d.ts +22 -0
  74. package/dist/generators/rest.d.ts.map +1 -1
  75. package/dist/generators/rest.js +34 -3
  76. package/dist/generators/rest.js.map +1 -1
  77. package/dist/hierarchical.js +1 -1
  78. package/dist/index.d.ts +7 -1
  79. package/dist/index.d.ts.map +1 -1
  80. package/dist/index.js +12 -5
  81. package/dist/interceptors.d.ts +21 -0
  82. package/dist/interceptors.d.ts.map +1 -1
  83. package/dist/interceptors.js +27 -1
  84. package/dist/interceptors.js.map +1 -1
  85. package/dist/manifest/generator.d.ts.map +1 -1
  86. package/dist/manifest/generator.js +4 -7
  87. package/dist/manifest/generator.js.map +1 -1
  88. package/dist/manifest/static-manifest.js +10 -10
  89. package/dist/manifest/static-manifest.js.map +1 -1
  90. package/dist/manifest/store.js +1 -1
  91. package/dist/manifest/store.js.map +1 -1
  92. package/dist/manifest.json +19 -19
  93. package/dist/migrations/differ.d.ts +211 -9
  94. package/dist/migrations/differ.d.ts.map +1 -1
  95. package/dist/migrations/differ.js +613 -50
  96. package/dist/migrations/differ.js.map +1 -1
  97. package/dist/migrations/generator.d.ts +31 -4
  98. package/dist/migrations/generator.d.ts.map +1 -1
  99. package/dist/migrations/generator.js +49 -5
  100. package/dist/migrations/generator.js.map +1 -1
  101. package/dist/migrations/index.d.ts +4 -2
  102. package/dist/migrations/index.d.ts.map +1 -1
  103. package/dist/migrations/index.js +6 -3
  104. package/dist/migrations/minor-units.d.ts +162 -0
  105. package/dist/migrations/minor-units.d.ts.map +1 -0
  106. package/dist/migrations/minor-units.js +381 -0
  107. package/dist/migrations/minor-units.js.map +1 -0
  108. package/dist/migrations/orchestrate.js +35 -6
  109. package/dist/migrations/orchestrate.js.map +1 -1
  110. package/dist/migrations/sqlite-rebuild.d.ts +142 -0
  111. package/dist/migrations/sqlite-rebuild.d.ts.map +1 -0
  112. package/dist/migrations/sqlite-rebuild.js +514 -0
  113. package/dist/migrations/sqlite-rebuild.js.map +1 -0
  114. package/dist/migrations/tracker.d.ts +114 -1
  115. package/dist/migrations/tracker.d.ts.map +1 -1
  116. package/dist/migrations/tracker.js +331 -16
  117. package/dist/migrations/tracker.js.map +1 -1
  118. package/dist/migrations/types.d.ts +19 -4
  119. package/dist/migrations/types.d.ts.map +1 -1
  120. package/dist/migrations.js +6 -3
  121. package/dist/object.d.ts +142 -10
  122. package/dist/object.d.ts.map +1 -1
  123. package/dist/object.js +196 -41
  124. package/dist/object.js.map +1 -1
  125. package/dist/postgres-timeouts.d.ts +240 -0
  126. package/dist/postgres-timeouts.d.ts.map +1 -0
  127. package/dist/postgres-timeouts.js +204 -0
  128. package/dist/postgres-timeouts.js.map +1 -0
  129. package/dist/query-bounds.d.ts +101 -0
  130. package/dist/query-bounds.d.ts.map +1 -0
  131. package/dist/query-bounds.js +177 -0
  132. package/dist/query-bounds.js.map +1 -0
  133. package/dist/registry/class-registration.d.ts.map +1 -1
  134. package/dist/registry/class-registration.js +3 -1
  135. package/dist/registry/class-registration.js.map +1 -1
  136. package/dist/registry/manifest-field-merge.d.ts +12 -0
  137. package/dist/registry/manifest-field-merge.d.ts.map +1 -1
  138. package/dist/registry/manifest-field-merge.js +14 -2
  139. package/dist/registry/manifest-field-merge.js.map +1 -1
  140. package/dist/registry/schema-builder.d.ts +22 -1
  141. package/dist/registry/schema-builder.d.ts.map +1 -1
  142. package/dist/registry/schema-builder.js +205 -165
  143. package/dist/registry/schema-builder.js.map +1 -1
  144. package/dist/registry/types.d.ts +35 -3
  145. package/dist/registry/types.d.ts.map +1 -1
  146. package/dist/registry.d.ts +41 -46
  147. package/dist/registry.d.ts.map +1 -1
  148. package/dist/registry.js +61 -83
  149. package/dist/registry.js.map +1 -1
  150. package/dist/scanner/manifest-generator.d.ts +45 -0
  151. package/dist/scanner/manifest-generator.d.ts.map +1 -1
  152. package/dist/scanner/manifest-generator.js +92 -28
  153. package/dist/scanner/manifest-generator.js.map +1 -1
  154. package/dist/scanner/types.d.ts +5 -0
  155. package/dist/scanner/types.d.ts.map +1 -1
  156. package/dist/scanner/types.js.map +1 -1
  157. package/dist/schema/conflict-target.d.ts +104 -0
  158. package/dist/schema/conflict-target.d.ts.map +1 -0
  159. package/dist/schema/conflict-target.js +129 -0
  160. package/dist/schema/conflict-target.js.map +1 -0
  161. package/dist/schema/ddl/base-strategy.d.ts.map +1 -1
  162. package/dist/schema/ddl/base-strategy.js +2 -2
  163. package/dist/schema/ddl/base-strategy.js.map +1 -1
  164. package/dist/schema/ddl/duckdb-strategy.d.ts.map +1 -1
  165. package/dist/schema/ddl/duckdb-strategy.js +2 -1
  166. package/dist/schema/ddl/duckdb-strategy.js.map +1 -1
  167. package/dist/schema/ddl/postgres-strategy.d.ts.map +1 -1
  168. package/dist/schema/ddl/postgres-strategy.js +12 -1
  169. package/dist/schema/ddl/postgres-strategy.js.map +1 -1
  170. package/dist/schema/generator.d.ts +307 -6
  171. package/dist/schema/generator.d.ts.map +1 -1
  172. package/dist/schema/generator.js +510 -87
  173. package/dist/schema/generator.js.map +1 -1
  174. package/dist/schema/index-utils.d.ts +120 -0
  175. package/dist/schema/index-utils.d.ts.map +1 -1
  176. package/dist/schema/index-utils.js +242 -1
  177. package/dist/schema/index-utils.js.map +1 -1
  178. package/dist/schema/index.d.ts +3 -0
  179. package/dist/schema/index.d.ts.map +1 -1
  180. package/dist/schema/index.js +4 -1
  181. package/dist/schema/live-parity.d.ts +90 -0
  182. package/dist/schema/live-parity.d.ts.map +1 -0
  183. package/dist/schema/live-parity.js +602 -0
  184. package/dist/schema/live-parity.js.map +1 -0
  185. package/dist/schema/manifest-schema.d.ts +121 -0
  186. package/dist/schema/manifest-schema.d.ts.map +1 -0
  187. package/dist/schema/manifest-schema.js +267 -0
  188. package/dist/schema/manifest-schema.js.map +1 -0
  189. package/dist/schema/schema-aggregator.d.ts +24 -10
  190. package/dist/schema/schema-aggregator.d.ts.map +1 -1
  191. package/dist/schema/schema-aggregator.js +35 -90
  192. package/dist/schema/schema-aggregator.js.map +1 -1
  193. package/dist/schema/system-table-shapes.d.ts +65 -0
  194. package/dist/schema/system-table-shapes.d.ts.map +1 -0
  195. package/dist/schema/system-table-shapes.js +187 -0
  196. package/dist/schema/system-table-shapes.js.map +1 -0
  197. package/dist/schema/types.d.ts +103 -4
  198. package/dist/schema/types.d.ts.map +1 -1
  199. package/dist/schema/utils.d.ts +2 -1
  200. package/dist/schema/utils.d.ts.map +1 -1
  201. package/dist/schema/utils.js +5 -3
  202. package/dist/schema/utils.js.map +1 -1
  203. package/dist/schema.js +4 -1
  204. package/dist/smrt-knowledge.json +20 -8
  205. package/dist/sync/apply.d.ts.map +1 -1
  206. package/dist/sync/apply.js +9 -16
  207. package/dist/sync/apply.js.map +1 -1
  208. package/dist/system/compatibility.d.ts +42 -0
  209. package/dist/system/compatibility.d.ts.map +1 -1
  210. package/dist/system/compatibility.js +182 -9
  211. package/dist/system/compatibility.js.map +1 -1
  212. package/dist/system/index.d.ts +1 -0
  213. package/dist/system/index.d.ts.map +1 -1
  214. package/dist/system/index.js +3 -2
  215. package/dist/system/retention.d.ts +237 -0
  216. package/dist/system/retention.d.ts.map +1 -0
  217. package/dist/system/retention.js +497 -0
  218. package/dist/system/retention.js.map +1 -0
  219. package/dist/system/schema.d.ts +100 -15
  220. package/dist/system/schema.d.ts.map +1 -1
  221. package/dist/system/schema.js +81 -45
  222. package/dist/system/schema.js.map +1 -1
  223. package/dist/system/types.d.ts +0 -2
  224. package/dist/system/types.d.ts.map +1 -1
  225. package/dist/testing/database.d.ts.map +1 -1
  226. package/dist/testing/database.js +1 -0
  227. package/dist/testing/database.js.map +1 -1
  228. package/dist/vite-plugin/index.d.ts.map +1 -1
  229. package/dist/vite-plugin/index.js +4 -9
  230. package/dist/vite-plugin/index.js.map +1 -1
  231. package/dist/vite-plugin/sveltekit-generator.d.ts.map +1 -1
  232. package/dist/vite-plugin/sveltekit-generator.js +71 -5
  233. package/dist/vite-plugin/sveltekit-generator.js.map +1 -1
  234. package/dist/vite-plugin/web-collections.d.ts.map +1 -1
  235. package/dist/vite-plugin/web-collections.js +6 -4
  236. package/dist/vite-plugin/web-collections.js.map +1 -1
  237. package/package.json +5 -5
@@ -1,8 +1,10 @@
1
1
  import { renderIndexTarget } from "../schema/index-utils.js";
2
2
  import { getDDLStrategy } from "../schema/ddl/index.js";
3
3
  import { materializeManifestDDLForEngine } from "../schema/ddl/materialize-manifest.js";
4
+ import { cachedDdlTableConstraints } from "../schema/manifest-schema.js";
4
5
  import "../schema/utils.js";
5
6
  import { computeChecksum } from "./checksum.js";
7
+ import { createLogger } from "@happyvertical/logger";
6
8
  //#region src/migrations/generator.ts
7
9
  /**
8
10
  * Migration Generator
@@ -10,6 +12,7 @@ import { computeChecksum } from "./checksum.js";
10
12
  * Generates migration files from schema diffs.
11
13
  * Supports both SQL and TypeScript formats.
12
14
  */
15
+ var logger = createLogger({ level: "info" });
13
16
  /**
14
17
  * MigrationGenerator class for creating migration files
15
18
  */
@@ -24,7 +27,7 @@ var MigrationGenerator = class {
24
27
  this.format = options.format || "sql";
25
28
  this.includeDown = options.includeDown ?? true;
26
29
  this.packageName = options.packageName;
27
- this.materializeStructuredSchema = options.materializeStructuredSchema ?? false;
30
+ this.materializeStructuredSchema = options.materializeStructuredSchema ?? true;
28
31
  }
29
32
  /**
30
33
  * Generate a migration from a schema diff
@@ -34,6 +37,8 @@ var MigrationGenerator = class {
34
37
  const downStatements = [];
35
38
  for (const schema of diff.added_tables) {
36
39
  upStatements.push(this.generateCreateTable(schema));
40
+ upStatements.push(...this.generateTableIndexes(schema));
41
+ upStatements.push(...this.generateTableTriggers(schema));
37
42
  if (this.includeDown || options.includeDown) downStatements.push(this.generateDropTable(schema.tableName));
38
43
  }
39
44
  for (const change of diff.changes) {
@@ -90,11 +95,11 @@ var MigrationGenerator = class {
90
95
  lines.push("");
91
96
  lines.push("-- @up");
92
97
  if (upStatements.length === 0) lines.push("-- Add your UP statements here");
93
- else for (const stmt of upStatements) lines.push(`${stmt};`);
98
+ else for (const stmt of upStatements) lines.push(this.terminateStatement(stmt));
94
99
  lines.push("");
95
100
  lines.push("-- @down");
96
101
  if (downStatements.length === 0) lines.push("-- Add your DOWN statements here");
97
- else for (const stmt of downStatements) lines.push(`${stmt};`);
102
+ else for (const stmt of downStatements) lines.push(this.terminateStatement(stmt));
98
103
  lines.push("");
99
104
  return lines.join("\n");
100
105
  }
@@ -134,9 +139,27 @@ ${downStatementsStr}
134
139
  generateCreateTable(schema) {
135
140
  if (Object.keys(schema.columns).length === 0 && schema.ddl?.trim()) return materializeManifestDDLForEngine(schema.ddl, this.engine);
136
141
  if (!this.materializeStructuredSchema && schema.ddl?.trim()) return materializeManifestDDLForEngine(schema.ddl, this.engine);
142
+ if (schema.ddl?.trim()) {
143
+ const constraints = cachedDdlTableConstraints(schema.ddl);
144
+ if (constraints.length > 0) logger.warn(`[MigrationGenerator] cached ddl for table "${schema.tableName}" declares ${constraints.length} table constraint(s) that structured columns cannot carry (${constraints.join("; ")}); the structured schema is authoritative and they are not rendered (#2358).`);
145
+ }
137
146
  return getDDLStrategy(this.engine).generateCreateTable(schema);
138
147
  }
139
148
  /**
149
+ * Generate the CREATE INDEX statements for a new table through the engine
150
+ * strategy (partial `WHERE`, JSON-path targets, inline-UNIQUE engines).
151
+ */
152
+ generateTableIndexes(schema) {
153
+ return getDDLStrategy(this.engine).generateIndexes(schema);
154
+ }
155
+ /**
156
+ * Generate the CREATE TRIGGER statements for a new table (engines without
157
+ * trigger support return none).
158
+ */
159
+ generateTableTriggers(schema) {
160
+ return getDDLStrategy(this.engine).generateTriggers(schema);
161
+ }
162
+ /**
140
163
  * Generate DROP TABLE SQL
141
164
  */
142
165
  generateDropTable(tableName) {
@@ -177,6 +200,16 @@ ${downStatementsStr}
177
200
  up.push(`-- Expected: ${change.mismatch?.expected}, Actual: ${change.mismatch?.actual}`);
178
201
  up.push(`-- Manual migration required`);
179
202
  break;
203
+ case "alter_column":
204
+ if (change.advisory && sqlStatements.length === 0) break;
205
+ for (const sql of sqlStatements) up.push(sql);
206
+ break;
207
+ case "orphan_column":
208
+ case "orphan_index":
209
+ up.push(`-- ${change.advisory?.severity === "warning" ? "WARNING" : "NOTE"}: ${change.type === "orphan_column" ? "Orphan column" : "Orphan unique constraint"} ${change.table}.${change.name} (${change.mismatch?.actual ?? "unknown shape"})`);
210
+ if (change.advisory?.message) up.push(`-- ${change.advisory.message}`);
211
+ for (const suggestion of change.advisory?.suggestedSql ?? []) up.push(`-- suggested: ${suggestion}`);
212
+ break;
180
213
  default: break;
181
214
  }
182
215
  return {
@@ -206,8 +239,9 @@ ${downStatementsStr}
206
239
  generateAddIndex(tableName, index) {
207
240
  const uniqueStr = index.unique ? "UNIQUE " : "";
208
241
  const target = renderIndexTarget(index, this.engine);
209
- if (this.engine === "postgres") return `CREATE ${uniqueStr}INDEX CONCURRENTLY IF NOT EXISTS ${this.quoteIdentifier(index.name)} ON ${this.quoteIdentifier(tableName)} (${target})`;
210
- return `CREATE ${uniqueStr}INDEX IF NOT EXISTS ${this.quoteIdentifier(index.name)} ON ${this.quoteIdentifier(tableName)} (${target})`;
242
+ const whereClause = this.engine !== "duckdb" && index.where ? ` WHERE ${index.where}` : "";
243
+ if (this.engine === "postgres") return `CREATE ${uniqueStr}INDEX CONCURRENTLY IF NOT EXISTS ${this.quoteIdentifier(index.name)} ON ${this.quoteIdentifier(tableName)} (${target})${whereClause}`;
244
+ return `CREATE ${uniqueStr}INDEX IF NOT EXISTS ${this.quoteIdentifier(index.name)} ON ${this.quoteIdentifier(tableName)} (${target})${whereClause}`;
211
245
  }
212
246
  /**
213
247
  * Generate DROP INDEX SQL
@@ -233,6 +267,16 @@ ${downStatementsStr}
233
267
  return String(value);
234
268
  }
235
269
  /**
270
+ * Terminate a statement with exactly one `;` for the SQL file format.
271
+ *
272
+ * Strategy-rendered statements (`generateCreateTable`, `generateIndexes`,
273
+ * `generateTriggers`) already end with `;`, while differ-produced ALTERs do
274
+ * not — appending unconditionally wrote `;;` (#2406 review).
275
+ */
276
+ terminateStatement(stmt) {
277
+ return stmt.trimEnd().endsWith(";") ? stmt : `${stmt};`;
278
+ }
279
+ /**
236
280
  * Escape template literal content
237
281
  */
238
282
  escapeTemplate(str) {
@@ -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 { getDDLStrategy } from '../schema/ddl/index.js';\nimport { materializeManifestDDLForEngine } from '../schema/ddl/materialize-manifest.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\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 * Ignore cached manifest DDL and materialize structured columns for the\n * selected engine. Framework-owned executable paths should enable this;\n * the default preserves the published custom-DDL contract while still\n * materializing bare PostgreSQL TIMESTAMP columns as TIMESTAMPTZ.\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 ?? false;\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\n // Handle new tables\n for (const schema of diff.added_tables) {\n upStatements.push(this.generateCreateTable(schema));\n if (this.includeDown || options.includeDown) {\n downStatements.push(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 // 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(`${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(`${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 // Preserve the documented custom-DDL contract by default, including\n // table-level constraints that structured ColumnDefinition cannot\n // represent. Engine-aware framework callers explicitly opt into the\n // structured materialization path below.\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. Always 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 return getDDLStrategy(this.engine).generateCreateTable(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 '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 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\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})`;\n }\n\n return `CREATE ${uniqueStr}INDEX IF NOT EXISTS ${this.quoteIdentifier(index.name)} ON ${this.quoteIdentifier(tableName)} (${target})`;\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 * 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 { 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\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 for (const schema of diff.added_tables) {\n upStatements.push(this.generateCreateTable(schema));\n upStatements.push(...this.generateTableIndexes(schema));\n upStatements.push(...this.generateTableTriggers(schema));\n if (this.includeDown || options.includeDown) {\n downStatements.push(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 // 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 '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 // 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'}: ${change.type === 'orphan_column' ? 'Orphan column' : 'Orphan unique constraint'} ${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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"}
@@ -11,10 +11,12 @@
11
11
  export { type BackfillRecord, BackfillTableUnavailableError, BackfillTracker, type BackfillTrackerOptions, } from './backfill-tracker.js';
12
12
  export { computeChecksum, normalizeSQL, shortChecksum, verifyChecksum, } from './checksum.js';
13
13
  export type { DiffOptions } from './differ.js';
14
- export { generateSchemaDiff, getSQLFromDiff, hasActionableChanges, SchemaComparer, } from './differ.js';
14
+ export { canonicalizeDefault, generateSchemaDiff, getSQLFromDiff, hasActionableChanges, isAdvisoryOnlyChange, isInfoOnlyChange, isManualOrAdvisoryChange, SchemaComparer, uniqueColumnIndexName, } from './differ.js';
15
15
  export type { GeneratedMigration, GenerateMigrationOptions, } from './generator.js';
16
16
  export { createMigrationDefinition, generateMigrationSequence, generateMigrationTimestamp, MigrationGenerator, } from './generator.js';
17
+ 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';
17
18
  export { getPendingSchemaStatements, type MigrateSmrtSchemasOptions, type MigrateSmrtSchemasResult, migrateSmrtSchemas, type PendingSchemaStatementsResult, } from './orchestrate.js';
18
- export { MigrationTracker, planPostgresStatements } from './tracker.js';
19
+ export { type BuildSqliteRebuildStatementsInput, buildSqliteRebuildStatements, isSqliteRebuildPlaceholder, type PlanSqliteTableRebuildsOptions, planSqliteTableRebuilds, rewriteSqliteCreateTable, SQLITE_REBUILD_TABLE_PREFIX, type SqliteTableRebuildPlan, sqliteRebuildPlaceholderSql, } from './sqlite-rebuild.js';
20
+ export { buildConcurrentIndexPlan, extractCreatedIndexName, MigrationTracker, parsePostgresTimeoutMs, planPostgresStatements, } from './tracker.js';
19
21
  export type { ApplyMigrationsOptions, ColumnDefinitionWithName, ColumnInfo, DatabaseEngine, DatabaseInterface, DataMigration, DataMigrationResult, DataMigrationStatement, DriftReport, ForeignKeyInfo, IndexDefinition, IndexInfo, MigrationDefinition, MigrationMode, MigrationResult, MigrationStatus, MigrationsConfig, MigrationTrackerOptions, ParsedMigrationFile, ParseMigrationOptions, RollbackOptions, SchemaChange, SchemaDiff, SchemaMigrationRecord, StiDiscriminatorMigration, TableSchemaInfo, } from './types.js';
20
22
  //# sourceMappingURL=index.d.ts.map
@@ -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,kBAAkB,EAClB,cAAc,EACd,oBAAoB,EACpB,cAAc,GACf,MAAM,aAAa,CAAC;AACrB,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,0BAA0B,EAC1B,KAAK,yBAAyB,EAC9B,KAAK,wBAAwB,EAC7B,kBAAkB,EAClB,KAAK,6BAA6B,GACnC,MAAM,kBAAkB,CAAC;AAE1B,OAAO,EAAE,gBAAgB,EAAE,sBAAsB,EAAE,MAAM,cAAc,CAAC;AAGxE,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":"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;AACrB,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,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,7 +1,10 @@
1
- import { SchemaComparer, generateSchemaDiff, getSQLFromDiff, hasActionableChanges } from "./differ.js";
1
+ import { parsePostgresTimeoutMs } from "../postgres-timeouts.js";
2
+ import { SQLITE_REBUILD_TABLE_PREFIX, buildSqliteRebuildStatements, isSqliteRebuildPlaceholder, planSqliteTableRebuilds, rewriteSqliteCreateTable, sqliteRebuildPlaceholderSql } from "./sqlite-rebuild.js";
3
+ import { SchemaComparer, canonicalizeDefault, generateSchemaDiff, getSQLFromDiff, hasActionableChanges, isAdvisoryOnlyChange, isInfoOnlyChange, isManualOrAdvisoryChange, uniqueColumnIndexName } from "./differ.js";
2
4
  import { BackfillTableUnavailableError, BackfillTracker } from "./backfill-tracker.js";
3
5
  import { computeChecksum, normalizeSQL, shortChecksum, verifyChecksum } from "./checksum.js";
4
6
  import { MigrationGenerator, createMigrationDefinition, generateMigrationSequence, generateMigrationTimestamp } from "./generator.js";
5
- import { MigrationTracker, planPostgresStatements } from "./tracker.js";
7
+ import { MinorUnitsPreflightError, buildMinorUnitsStatements, preflightMinorUnitsRescale, rescaleMoneyColumnsToMinorUnits } from "./minor-units.js";
8
+ import { MigrationTracker, buildConcurrentIndexPlan, extractCreatedIndexName, planPostgresStatements } from "./tracker.js";
6
9
  import { getPendingSchemaStatements, migrateSmrtSchemas } from "./orchestrate.js";
7
- export { BackfillTableUnavailableError, BackfillTracker, MigrationGenerator, MigrationTracker, SchemaComparer, computeChecksum, createMigrationDefinition, generateMigrationSequence, generateMigrationTimestamp, generateSchemaDiff, getPendingSchemaStatements, getSQLFromDiff, hasActionableChanges, migrateSmrtSchemas, normalizeSQL, planPostgresStatements, shortChecksum, verifyChecksum };
10
+ export { BackfillTableUnavailableError, BackfillTracker, MigrationGenerator, MigrationTracker, MinorUnitsPreflightError, SQLITE_REBUILD_TABLE_PREFIX, SchemaComparer, buildConcurrentIndexPlan, buildMinorUnitsStatements, buildSqliteRebuildStatements, canonicalizeDefault, computeChecksum, createMigrationDefinition, extractCreatedIndexName, generateMigrationSequence, generateMigrationTimestamp, generateSchemaDiff, getPendingSchemaStatements, getSQLFromDiff, hasActionableChanges, isAdvisoryOnlyChange, isInfoOnlyChange, isManualOrAdvisoryChange, isSqliteRebuildPlaceholder, migrateSmrtSchemas, normalizeSQL, parsePostgresTimeoutMs, planPostgresStatements, planSqliteTableRebuilds, preflightMinorUnitsRescale, rescaleMoneyColumnsToMinorUnits, rewriteSqliteCreateTable, shortChecksum, sqliteRebuildPlaceholderSql, uniqueColumnIndexName, verifyChecksum };
@@ -0,0 +1,162 @@
1
+ import { DatabaseInterface } from './types.js';
2
+ /** A table and the money columns on it that must move to minor units. */
3
+ export interface MoneyColumnTarget {
4
+ /** Physical table name, e.g. `payments`. */
5
+ table: string;
6
+ /** Physical column names, e.g. `['amount', 'native_amount']`. */
7
+ columns: string[];
8
+ }
9
+ /** What the preflight found about one column. */
10
+ export type MinorUnitsColumnState =
11
+ /** Floating-point today: needs converting. */
12
+ 'pending'
13
+ /** Already an integer column: nothing to do. */
14
+ | 'converted'
15
+ /** Table or column absent (package not installed, older schema). */
16
+ | 'missing';
17
+ /** One row that cannot be converted without losing information. */
18
+ export interface MinorUnitsProblemRow {
19
+ /** Primary key, when the table has an `id` column. */
20
+ id: string | null;
21
+ /** The stored major-unit value. */
22
+ value: number;
23
+ /** `value * scale`, before rounding. */
24
+ scaled: number;
25
+ }
26
+ /** Preflight findings for a single column. */
27
+ export interface MinorUnitsColumnReport {
28
+ table: string;
29
+ column: string;
30
+ state: MinorUnitsColumnState;
31
+ /** Declared column type as the database reports it, when resolvable. */
32
+ declaredType: string | null;
33
+ /** Non-null values inspected. `0` unless `state` is `pending`. */
34
+ inspectedRows: number;
35
+ /** Values whose scaled form is not a whole number — rounding loses money. */
36
+ nonIntegral: MinorUnitsProblemRow[];
37
+ /** Values whose scaled form falls outside PostgreSQL `int4`. */
38
+ overflow: MinorUnitsProblemRow[];
39
+ }
40
+ /** Result of {@link preflightMinorUnitsRescale}. */
41
+ export interface MinorUnitsPreflightResult {
42
+ engine: string;
43
+ scale: number;
44
+ columns: MinorUnitsColumnReport[];
45
+ /** Columns still holding floating-point major units. */
46
+ pendingColumns: number;
47
+ /** Columns already integer. */
48
+ convertedColumns: number;
49
+ /** Columns whose table/column does not exist. */
50
+ missingColumns: number;
51
+ /** Total rows that would be rounded. */
52
+ nonIntegralRows: number;
53
+ /** Total rows that would overflow `int4`. */
54
+ overflowRows: number;
55
+ /** No row would lose information. */
56
+ ok: boolean;
57
+ /** Human-readable report, suitable for pasting into a migration log or PR. */
58
+ summary: string;
59
+ }
60
+ /** Options shared by the preflight and the rescale. */
61
+ export interface MinorUnitsOptions {
62
+ /** Minor units per major unit. Default `100` (cents). */
63
+ scale?: number;
64
+ /** Engine hint for adapters whose URL does not identify the engine. */
65
+ engineHint?: string;
66
+ /** Problem rows recorded per column before truncating. Default `20`. */
67
+ maxProblemRows?: number;
68
+ }
69
+ /** Options for {@link rescaleMoneyColumnsToMinorUnits}. */
70
+ export interface MinorUnitsRescaleOptions extends MinorUnitsOptions {
71
+ /**
72
+ * Stable marker name recorded in `_smrt_backfills`, e.g.
73
+ * `@happyvertical/smrt-commerce:money-minor-units:v1`.
74
+ *
75
+ * This is what makes the migration idempotent on SQLite, where the rescale
76
+ * is an `UPDATE` no column type can distinguish from an unconverted table.
77
+ */
78
+ backfillName: string;
79
+ /** Package recorded alongside the marker. */
80
+ packageName?: string;
81
+ /**
82
+ * Convert even when the preflight found rows that would be rounded or
83
+ * overflow. Off by default: silently rounding a half-cent away is exactly
84
+ * the failure this migration exists to prevent.
85
+ */
86
+ force?: boolean;
87
+ }
88
+ /** Result of {@link rescaleMoneyColumnsToMinorUnits}. */
89
+ export interface MinorUnitsRescaleResult {
90
+ /** `false` when the backfill marker was already present. */
91
+ ran: boolean;
92
+ engine: string;
93
+ scale: number;
94
+ /** The preflight that gated the run. */
95
+ preflight: MinorUnitsPreflightResult;
96
+ /** Columns whose values were rescaled. */
97
+ rescaledColumns: Array<{
98
+ table: string;
99
+ column: string;
100
+ }>;
101
+ /** Statements executed, in order — useful for logs and tests. */
102
+ statements: string[];
103
+ /**
104
+ * Columns whose values are now minor units but whose *declared* type is
105
+ * still floating-point. Only ever populated on SQLite, which cannot alter a
106
+ * column type in place; clearing it needs the table-rebuild path (#2370).
107
+ */
108
+ declaredTypeChangePending: Array<{
109
+ table: string;
110
+ column: string;
111
+ }>;
112
+ }
113
+ /**
114
+ * A money-losing preflight finding blocked the conversion.
115
+ *
116
+ * Carries the full preflight so a caller can print the summary, fix the data,
117
+ * and retry rather than re-deriving what went wrong.
118
+ */
119
+ export declare class MinorUnitsPreflightError extends Error {
120
+ readonly preflight: MinorUnitsPreflightResult;
121
+ constructor(preflight: MinorUnitsPreflightResult);
122
+ }
123
+ /**
124
+ * The statements that convert one column, for one engine.
125
+ *
126
+ * Exported so the emitted DDL can be asserted without a live PostgreSQL or
127
+ * DuckDB connection; each owning package's `test:postgres` lane then proves the
128
+ * same SQL runs against a real server.
129
+ *
130
+ * @param engine - `postgres`, `duckdb`, or anything SQLite-shaped.
131
+ * @param table - Physical table name.
132
+ * @param column - Physical column name.
133
+ * @param scale - Minor units per major unit.
134
+ * @returns Statements in execution order.
135
+ */
136
+ export declare function buildMinorUnitsStatements(engine: string, table: string, column: string, scale: number): string[];
137
+ /**
138
+ * Report what {@link rescaleMoneyColumnsToMinorUnits} would do, without
139
+ * writing anything.
140
+ *
141
+ * @param db - Root database handle.
142
+ * @param targets - Tables and money columns owned by the calling package.
143
+ * @param options - Scale, engine hint, problem-row cap.
144
+ * @returns Per-column state plus the rows that would lose information.
145
+ */
146
+ export declare function preflightMinorUnitsRescale(db: DatabaseInterface, targets: MoneyColumnTarget[], options?: MinorUnitsOptions): Promise<MinorUnitsPreflightResult>;
147
+ /**
148
+ * Convert floating-point major-unit money columns to integer minor units.
149
+ *
150
+ * Idempotent: the run is wrapped in a {@link BackfillTracker} marker, so a
151
+ * second call is a no-op even on SQLite where the column type cannot record
152
+ * that the conversion already happened.
153
+ *
154
+ * @param db - Root database handle (must support `query`).
155
+ * @param targets - Tables and money columns owned by the calling package.
156
+ * @param options - Marker name, scale, and whether to convert despite
157
+ * information-losing rows.
158
+ * @throws {MinorUnitsPreflightError} when the preflight found rows that would
159
+ * be rounded or overflow and `force` was not set.
160
+ */
161
+ export declare function rescaleMoneyColumnsToMinorUnits(db: DatabaseInterface, targets: MoneyColumnTarget[], options: MinorUnitsRescaleOptions): Promise<MinorUnitsRescaleResult>;
162
+ //# sourceMappingURL=minor-units.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"minor-units.d.ts","sourceRoot":"","sources":["../../src/migrations/minor-units.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoCG;AAKH,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AAqCpD,yEAAyE;AACzE,MAAM,WAAW,iBAAiB;IAChC,4CAA4C;IAC5C,KAAK,EAAE,MAAM,CAAC;IACd,iEAAiE;IACjE,OAAO,EAAE,MAAM,EAAE,CAAC;CACnB;AAED,iDAAiD;AACjD,MAAM,MAAM,qBAAqB;AAC/B,8CAA8C;AAC5C,SAAS;AACX,gDAAgD;GAC9C,WAAW;AACb,oEAAoE;GAClE,SAAS,CAAC;AAEd,mEAAmE;AACnE,MAAM,WAAW,oBAAoB;IACnC,sDAAsD;IACtD,EAAE,EAAE,MAAM,GAAG,IAAI,CAAC;IAClB,mCAAmC;IACnC,KAAK,EAAE,MAAM,CAAC;IACd,wCAAwC;IACxC,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,8CAA8C;AAC9C,MAAM,WAAW,sBAAsB;IACrC,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,qBAAqB,CAAC;IAC7B,wEAAwE;IACxE,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,kEAAkE;IAClE,aAAa,EAAE,MAAM,CAAC;IACtB,6EAA6E;IAC7E,WAAW,EAAE,oBAAoB,EAAE,CAAC;IACpC,gEAAgE;IAChE,QAAQ,EAAE,oBAAoB,EAAE,CAAC;CAClC;AAED,oDAAoD;AACpD,MAAM,WAAW,yBAAyB;IACxC,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,sBAAsB,EAAE,CAAC;IAClC,wDAAwD;IACxD,cAAc,EAAE,MAAM,CAAC;IACvB,+BAA+B;IAC/B,gBAAgB,EAAE,MAAM,CAAC;IACzB,iDAAiD;IACjD,cAAc,EAAE,MAAM,CAAC;IACvB,wCAAwC;IACxC,eAAe,EAAE,MAAM,CAAC;IACxB,6CAA6C;IAC7C,YAAY,EAAE,MAAM,CAAC;IACrB,qCAAqC;IACrC,EAAE,EAAE,OAAO,CAAC;IACZ,8EAA8E;IAC9E,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,uDAAuD;AACvD,MAAM,WAAW,iBAAiB;IAChC,yDAAyD;IACzD,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,uEAAuE;IACvE,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,wEAAwE;IACxE,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAED,2DAA2D;AAC3D,MAAM,WAAW,wBAAyB,SAAQ,iBAAiB;IACjE;;;;;;OAMG;IACH,YAAY,EAAE,MAAM,CAAC;IACrB,6CAA6C;IAC7C,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;;OAIG;IACH,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AAED,yDAAyD;AACzD,MAAM,WAAW,uBAAuB;IACtC,4DAA4D;IAC5D,GAAG,EAAE,OAAO,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,wCAAwC;IACxC,SAAS,EAAE,yBAAyB,CAAC;IACrC,0CAA0C;IAC1C,eAAe,EAAE,KAAK,CAAC;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAC1D,iEAAiE;IACjE,UAAU,EAAE,MAAM,EAAE,CAAC;IACrB;;;;OAIG;IACH,yBAAyB,EAAE,KAAK,CAAC;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CACrE;AAED;;;;;GAKG;AACH,qBAAa,wBAAyB,SAAQ,KAAK;IACrC,QAAQ,CAAC,SAAS,EAAE,yBAAyB;gBAApC,SAAS,EAAE,yBAAyB;CAS1D;AAgMD;;;;;;;;;;;;GAYG;AACH,wBAAgB,yBAAyB,CACvC,MAAM,EAAE,MAAM,EACd,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,MAAM,EACd,KAAK,EAAE,MAAM,GACZ,MAAM,EAAE,CA8BV;AAED;;;;;;;;GAQG;AACH,wBAAsB,0BAA0B,CAC9C,EAAE,EAAE,iBAAiB,EACrB,OAAO,EAAE,iBAAiB,EAAE,EAC5B,OAAO,GAAE,iBAAsB,GAC9B,OAAO,CAAC,yBAAyB,CAAC,CAgFpC;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAsB,+BAA+B,CACnD,EAAE,EAAE,iBAAiB,EACrB,OAAO,EAAE,iBAAiB,EAAE,EAC5B,OAAO,EAAE,wBAAwB,GAChC,OAAO,CAAC,uBAAuB,CAAC,CAgElC"}