@fragno-dev/db 0.1.1 → 0.1.2

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 (108) hide show
  1. package/.turbo/turbo-build.log +61 -53
  2. package/CHANGELOG.md +12 -0
  3. package/dist/adapters/adapters.d.ts +11 -1
  4. package/dist/adapters/adapters.d.ts.map +1 -1
  5. package/dist/adapters/drizzle/drizzle-adapter.d.ts +9 -2
  6. package/dist/adapters/drizzle/drizzle-adapter.d.ts.map +1 -1
  7. package/dist/adapters/drizzle/drizzle-adapter.js +21 -39
  8. package/dist/adapters/drizzle/drizzle-adapter.js.map +1 -1
  9. package/dist/adapters/drizzle/drizzle-query.d.ts.map +1 -1
  10. package/dist/adapters/drizzle/drizzle-query.js +3 -2
  11. package/dist/adapters/drizzle/drizzle-query.js.map +1 -1
  12. package/dist/adapters/drizzle/drizzle-uow-compiler.js +8 -6
  13. package/dist/adapters/drizzle/drizzle-uow-compiler.js.map +1 -1
  14. package/dist/adapters/drizzle/drizzle-uow-decoder.js.map +1 -1
  15. package/dist/adapters/drizzle/drizzle-uow-executor.js.map +1 -1
  16. package/dist/adapters/drizzle/generate.js +107 -34
  17. package/dist/adapters/drizzle/generate.js.map +1 -1
  18. package/dist/adapters/drizzle/shared.js +14 -1
  19. package/dist/adapters/drizzle/shared.js.map +1 -1
  20. package/dist/adapters/kysely/kysely-adapter.d.ts +2 -1
  21. package/dist/adapters/kysely/kysely-adapter.d.ts.map +1 -1
  22. package/dist/adapters/kysely/kysely-adapter.js +25 -30
  23. package/dist/adapters/kysely/kysely-adapter.js.map +1 -1
  24. package/dist/adapters/kysely/kysely-query-builder.js +48 -44
  25. package/dist/adapters/kysely/kysely-query-builder.js.map +1 -1
  26. package/dist/adapters/kysely/kysely-query-compiler.js +2 -2
  27. package/dist/adapters/kysely/kysely-query-compiler.js.map +1 -1
  28. package/dist/adapters/kysely/kysely-query.js +3 -2
  29. package/dist/adapters/kysely/kysely-query.js.map +1 -1
  30. package/dist/adapters/kysely/kysely-shared.js +18 -0
  31. package/dist/adapters/kysely/kysely-shared.js.map +1 -0
  32. package/dist/adapters/kysely/kysely-uow-compiler.js +4 -3
  33. package/dist/adapters/kysely/kysely-uow-compiler.js.map +1 -1
  34. package/dist/adapters/kysely/migration/execute.js +15 -12
  35. package/dist/adapters/kysely/migration/execute.js.map +1 -1
  36. package/dist/migration-engine/auto-from-schema.js +2 -8
  37. package/dist/migration-engine/auto-from-schema.js.map +1 -1
  38. package/dist/migration-engine/create.d.ts +1 -5
  39. package/dist/migration-engine/create.js +1 -1
  40. package/dist/migration-engine/create.js.map +1 -1
  41. package/dist/migration-engine/generation-engine.d.ts +51 -0
  42. package/dist/migration-engine/generation-engine.d.ts.map +1 -0
  43. package/dist/migration-engine/generation-engine.js +165 -0
  44. package/dist/migration-engine/generation-engine.js.map +1 -0
  45. package/dist/migration-engine/shared.d.ts +5 -2
  46. package/dist/migration-engine/shared.d.ts.map +1 -1
  47. package/dist/migration-engine/shared.js.map +1 -1
  48. package/dist/mod.d.ts +0 -8
  49. package/dist/mod.d.ts.map +1 -1
  50. package/dist/mod.js +0 -32
  51. package/dist/mod.js.map +1 -1
  52. package/dist/query/condition-builder.js.map +1 -1
  53. package/dist/query/result-transform.js +2 -1
  54. package/dist/query/result-transform.js.map +1 -1
  55. package/dist/schema/create.d.ts +74 -16
  56. package/dist/schema/create.d.ts.map +1 -1
  57. package/dist/schema/create.js +76 -11
  58. package/dist/schema/create.js.map +1 -1
  59. package/dist/schema/serialize.js.map +1 -1
  60. package/dist/shared/settings-schema.js +36 -0
  61. package/dist/shared/settings-schema.js.map +1 -0
  62. package/dist/util/import-generator.js.map +1 -1
  63. package/dist/util/parse.js.map +1 -1
  64. package/package.json +8 -2
  65. package/src/adapters/adapters.ts +10 -3
  66. package/src/adapters/drizzle/drizzle-adapter-pglite.test.ts +11 -7
  67. package/src/adapters/drizzle/drizzle-adapter.test.ts +77 -29
  68. package/src/adapters/drizzle/drizzle-adapter.ts +31 -78
  69. package/src/adapters/drizzle/drizzle-query.ts +4 -7
  70. package/src/adapters/drizzle/drizzle-uow-compiler.test.ts +9 -3
  71. package/src/adapters/drizzle/drizzle-uow-compiler.ts +12 -6
  72. package/src/adapters/drizzle/drizzle-uow-decoder.ts +1 -1
  73. package/src/adapters/drizzle/drizzle-uow-executor.ts +1 -1
  74. package/src/adapters/drizzle/generate.test.ts +573 -150
  75. package/src/adapters/drizzle/generate.ts +187 -36
  76. package/src/adapters/drizzle/migrate-drizzle.test.ts +30 -6
  77. package/src/adapters/drizzle/shared.ts +31 -1
  78. package/src/adapters/drizzle/test-utils.ts +3 -1
  79. package/src/adapters/kysely/kysely-adapter-pglite.test.ts +25 -27
  80. package/src/adapters/kysely/kysely-adapter.ts +35 -58
  81. package/src/adapters/kysely/kysely-query-builder.ts +75 -44
  82. package/src/adapters/kysely/kysely-query-compiler.ts +3 -1
  83. package/src/adapters/kysely/kysely-query.ts +8 -2
  84. package/src/adapters/kysely/kysely-shared.ts +23 -0
  85. package/src/adapters/kysely/kysely-uow-compiler.ts +5 -2
  86. package/src/adapters/kysely/migration/execute-mysql.test.ts +2 -2
  87. package/src/adapters/kysely/migration/execute-postgres.test.ts +19 -19
  88. package/src/adapters/kysely/migration/execute.ts +48 -17
  89. package/src/adapters/kysely/migration/kysely-migrator.test.ts +19 -37
  90. package/src/fragment.test.ts +1 -0
  91. package/src/migration-engine/auto-from-schema.ts +14 -18
  92. package/src/migration-engine/create.ts +1 -6
  93. package/src/migration-engine/generation-engine.test.ts +597 -0
  94. package/src/migration-engine/generation-engine.ts +356 -0
  95. package/src/migration-engine/shared.ts +1 -4
  96. package/src/mod.ts +0 -66
  97. package/src/query/condition-builder.ts +24 -8
  98. package/src/query/result-transform.ts +7 -1
  99. package/src/schema/create.test.ts +4 -1
  100. package/src/schema/create.ts +132 -24
  101. package/src/schema/serialize.ts +21 -7
  102. package/src/shared/settings-schema.ts +61 -0
  103. package/src/util/deep-equal.ts +21 -7
  104. package/src/util/import-generator.ts +3 -1
  105. package/src/util/parse.ts +3 -1
  106. package/tsdown.config.ts +1 -0
  107. package/.turbo/turbo-test.log +0 -37
  108. package/.turbo/turbo-types$colon$check.log +0 -1
@@ -0,0 +1,597 @@
1
+ import { describe, it, expect, beforeAll, beforeEach, vi, afterEach } from "vitest";
2
+ import { Kysely, PostgresDialect } from "kysely";
3
+ import {
4
+ postProcessMigrationFilenames,
5
+ type GenerationInternalResult,
6
+ generateMigrationsOrSchema,
7
+ } from "./generation-engine";
8
+ import { KyselyAdapter } from "../adapters/kysely/kysely-adapter";
9
+ import { column, idColumn, schema, type AnySchema } from "../schema/create";
10
+ import { FragnoDatabase } from "../mod";
11
+
12
+ describe("generateMigrationsOrSchema - kysely", () => {
13
+ const mockDate = new Date("2025-10-24T12:00:00Z");
14
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
15
+ let db: Kysely<any>;
16
+ let adapter: KyselyAdapter;
17
+
18
+ beforeAll(() => {
19
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
20
+ db = new Kysely({ dialect: new PostgresDialect({} as any) });
21
+ adapter = new KyselyAdapter({ db, provider: "postgresql" });
22
+
23
+ // Mock the adapter methods
24
+ vi.spyOn(adapter, "isConnectionHealthy").mockResolvedValue(true);
25
+ vi.spyOn(adapter, "getSchemaVersion").mockResolvedValue(undefined);
26
+ });
27
+
28
+ beforeEach(() => {
29
+ vi.useFakeTimers();
30
+ vi.setSystemTime(mockDate);
31
+ });
32
+
33
+ afterEach(() => {
34
+ vi.useRealTimers();
35
+ });
36
+
37
+ it("should generate migration for single database from version 0", async () => {
38
+ const testSchema: AnySchema = schema((s) => {
39
+ return s.addTable("users", (t) => {
40
+ return t.addColumn("id", idColumn()).addColumn("name", column("string"));
41
+ });
42
+ });
43
+
44
+ const fragnoDb = new FragnoDatabase({
45
+ namespace: "test-db",
46
+ schema: testSchema,
47
+ adapter,
48
+ });
49
+
50
+ const results = await generateMigrationsOrSchema([fragnoDb]);
51
+
52
+ expect(results).toHaveLength(2); // Settings + test-db
53
+ expect(results[0].namespace).toBe("fragno-db-settings");
54
+ expect(results[0].path).toBe("20251024_001_f000_t001_fragno-db-settings.sql");
55
+ expect(results[0].schema).toContain("create table");
56
+ expect(results[0].schema).toContain("fragno_db_settings");
57
+
58
+ expect(results[1].namespace).toBe("test-db");
59
+ expect(results[1].path).toBe("20251024_002_f000_t001_test-db.sql");
60
+ expect(results[1].schema).toContain("create table");
61
+ expect(results[1].schema).toContain("users_test-db");
62
+ });
63
+
64
+ it("should generate migrations for multiple databases in alphabetical order", async () => {
65
+ const schema1: AnySchema = schema((s) => {
66
+ return s.addTable("users", (t) => {
67
+ return t.addColumn("id", idColumn()).addColumn("name", column("string"));
68
+ });
69
+ });
70
+
71
+ const schema2: AnySchema = schema((s) => {
72
+ return s.addTable("posts", (t) => {
73
+ return t.addColumn("id", idColumn()).addColumn("title", column("string"));
74
+ });
75
+ });
76
+
77
+ const schema3: AnySchema = schema((s) => {
78
+ return s.addTable("comments", (t) => {
79
+ return t.addColumn("id", idColumn()).addColumn("text", column("string"));
80
+ });
81
+ });
82
+
83
+ const fragnoDb1 = new FragnoDatabase({
84
+ namespace: "zebra-db",
85
+ schema: schema1,
86
+ adapter,
87
+ });
88
+
89
+ const fragnoDb2 = new FragnoDatabase({
90
+ namespace: "apple-db",
91
+ schema: schema2,
92
+ adapter,
93
+ });
94
+
95
+ const fragnoDb3 = new FragnoDatabase({
96
+ namespace: "mango-db",
97
+ schema: schema3,
98
+ adapter,
99
+ });
100
+
101
+ const results = await generateMigrationsOrSchema([fragnoDb1, fragnoDb2, fragnoDb3]);
102
+
103
+ expect(results).toHaveLength(4); // Settings + 3 databases
104
+ expect(results[0].namespace).toBe("fragno-db-settings");
105
+ expect(results[0].path).toBe("20251024_001_f000_t001_fragno-db-settings.sql");
106
+
107
+ expect(results[1].namespace).toBe("apple-db");
108
+ expect(results[1].path).toBe("20251024_002_f000_t001_apple-db.sql");
109
+ expect(results[1].schema).toContain("posts_apple-db");
110
+
111
+ expect(results[2].namespace).toBe("mango-db");
112
+ expect(results[2].path).toBe("20251024_003_f000_t001_mango-db.sql");
113
+ expect(results[2].schema).toContain("comments_mango-db");
114
+
115
+ expect(results[3].namespace).toBe("zebra-db");
116
+ expect(results[3].path).toBe("20251024_004_f000_t001_zebra-db.sql");
117
+ expect(results[3].schema).toContain("users_zebra-db");
118
+ });
119
+
120
+ it("should handle existing settings version and generate both settings and fragment migrations", async () => {
121
+ const testSchema: AnySchema = schema((s) => {
122
+ return s
123
+ .addTable("users", (t) => {
124
+ return t.addColumn("id", idColumn()).addColumn("name", column("string"));
125
+ })
126
+ .alterTable("users", (t) => {
127
+ return t.addColumn("age", column("integer").nullable());
128
+ });
129
+ });
130
+
131
+ // Override getSchemaVersion for this test - settings already at version 1
132
+ vi.spyOn(adapter, "getSchemaVersion").mockResolvedValueOnce("1");
133
+
134
+ const fragnoDb = new FragnoDatabase({
135
+ namespace: "test-db",
136
+ schema: testSchema,
137
+ adapter,
138
+ });
139
+
140
+ const results = await generateMigrationsOrSchema([fragnoDb]);
141
+
142
+ // Settings table already at version 1, so no settings migration needed
143
+ // But fragment migration is still generated
144
+ expect(results).toHaveLength(2);
145
+ expect(results[0].namespace).toBe("fragno-db-settings");
146
+ expect(results[1].namespace).toBe("test-db");
147
+ expect(results[1].path).toBe("20251024_002_f000_t002_test-db.sql");
148
+ expect(results[1].schema).toContain("create table");
149
+ expect(results[1].schema).toContain("alter table");
150
+ });
151
+
152
+ it("should generate settings migration even with no fragment changes", async () => {
153
+ const testSchema: AnySchema = schema((s) => {
154
+ return s.addTable("users", (t) => {
155
+ return t.addColumn("id", idColumn()).addColumn("name", column("string"));
156
+ });
157
+ });
158
+
159
+ const fragnoDb = new FragnoDatabase({
160
+ namespace: "test-db",
161
+ schema: testSchema,
162
+ adapter,
163
+ });
164
+
165
+ // Generate with toVersion = 0, fromVersion = 0 (no fragment changes)
166
+ const results = await generateMigrationsOrSchema([fragnoDb], {
167
+ toVersion: 0,
168
+ fromVersion: 0,
169
+ });
170
+
171
+ // Settings migration is generated, but no fragment migration (since toVersion=0)
172
+ expect(results).toHaveLength(1);
173
+ expect(results[0].namespace).toBe("fragno-db-settings");
174
+ });
175
+
176
+ it("should throw error when no databases provided", async () => {
177
+ await expect(generateMigrationsOrSchema([])).rejects.toThrow(
178
+ "No databases provided for schema generation",
179
+ );
180
+ });
181
+
182
+ it("should throw error when database connection is unhealthy", async () => {
183
+ const testSchema: AnySchema = schema((s) => {
184
+ return s.addTable("users", (t) => {
185
+ return t.addColumn("id", idColumn()).addColumn("name", column("string"));
186
+ });
187
+ });
188
+
189
+ // Mock unhealthy connection for this test
190
+ vi.spyOn(adapter, "isConnectionHealthy").mockResolvedValueOnce(false);
191
+
192
+ const fragnoDb = new FragnoDatabase({
193
+ namespace: "test-db",
194
+ schema: testSchema,
195
+ adapter,
196
+ });
197
+
198
+ await expect(generateMigrationsOrSchema([fragnoDb])).rejects.toThrow(
199
+ "Database connection is not healthy",
200
+ );
201
+ });
202
+
203
+ it("should generate SQL with correct version tracking in settings", async () => {
204
+ const testSchema: AnySchema = schema((s) => {
205
+ return s.addTable("users", (t) => {
206
+ return t.addColumn("id", idColumn()).addColumn("name", column("string"));
207
+ });
208
+ });
209
+
210
+ const fragnoDb = new FragnoDatabase({
211
+ namespace: "test-db",
212
+ schema: testSchema,
213
+ adapter,
214
+ });
215
+
216
+ const results = await generateMigrationsOrSchema([fragnoDb]);
217
+
218
+ // Check settings migration includes version tracking
219
+ expect(results[0].schema).toContain("fragno_db_settings");
220
+ expect(results[0].schema).toContain("key");
221
+ expect(results[0].schema).toContain("value");
222
+
223
+ // Check fragment migration includes version tracking
224
+ expect(results[1].schema).toContain("test-db.schema_version");
225
+ expect(results[1].schema).toContain("'1'");
226
+ });
227
+
228
+ it("should generate INSERT for version 0->N and UPDATE for version N->M", async () => {
229
+ const testSchema: AnySchema = schema((s) => {
230
+ return s
231
+ .addTable("users", (t) => {
232
+ return t.addColumn("id", idColumn()).addColumn("name", column("string"));
233
+ })
234
+ .alterTable("users", (t) => {
235
+ return t.addColumn("email", column("string").nullable());
236
+ });
237
+ });
238
+
239
+ const fragnoDb = new FragnoDatabase({
240
+ namespace: "test-db",
241
+ schema: testSchema,
242
+ adapter,
243
+ });
244
+
245
+ // First migration: 0 -> 1 (should use INSERT)
246
+ vi.spyOn(adapter, "getSchemaVersion").mockResolvedValueOnce(undefined);
247
+ const resultsV1 = await generateMigrationsOrSchema([fragnoDb], {
248
+ fromVersion: 0,
249
+ toVersion: 1,
250
+ });
251
+
252
+ expect(resultsV1).toHaveLength(2); // Settings + test-db
253
+ // Check that fragment migration contains INSERT for version tracking
254
+ expect(resultsV1[1].schema).toContain("insert into");
255
+ expect(resultsV1[1].schema).toContain("test-db.schema_version");
256
+ expect(resultsV1[1].schema).toContain("'1'");
257
+ expect(resultsV1[1].schema).toMatchInlineSnapshot(`
258
+ "create table "users_test-db" ("id" varchar(30) not null unique, "name" text not null, "_internalId" bigserial not null primary key, "_version" integer default 0 not null);
259
+
260
+ insert into "fragno_db_settings" ("id", "key", "value") values ('6_U2SCfiaNG9VyYmQ_JwzQ', 'test-db.schema_version', '1');"
261
+ `);
262
+
263
+ // Second migration: 1 -> 2 (should use UPDATE)
264
+ vi.spyOn(adapter, "getSchemaVersion").mockResolvedValueOnce("1");
265
+ const resultsV2 = await generateMigrationsOrSchema([fragnoDb], {
266
+ fromVersion: 1,
267
+ toVersion: 2,
268
+ });
269
+
270
+ expect(resultsV2).toHaveLength(2); // Settings + test-db
271
+ // Check that fragment migration contains UPDATE for version tracking
272
+ expect(resultsV2[1].schema).toContain("update");
273
+ expect(resultsV2[1].schema).toContain("fragno_db_settings");
274
+ expect(resultsV2[1].schema).toContain("test-db.schema_version");
275
+ expect(resultsV2[1].schema).toContain("'2'");
276
+ expect(resultsV2[1].schema).toMatchInlineSnapshot(`
277
+ "alter table "users_test-db" add column "email" text;
278
+
279
+ update "fragno_db_settings" set "value" = '2' where "key" = 'test-db.schema_version';"
280
+ `);
281
+ });
282
+
283
+ it("should include MySQL-specific foreign key checks in generated SQL", async () => {
284
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
285
+ const mysqlDb = new Kysely({ dialect: new PostgresDialect({} as any) });
286
+ const mysqlAdapter = new KyselyAdapter({ db: mysqlDb, provider: "mysql" });
287
+
288
+ vi.spyOn(mysqlAdapter, "isConnectionHealthy").mockResolvedValue(true);
289
+ vi.spyOn(mysqlAdapter, "getSchemaVersion").mockResolvedValue(undefined);
290
+
291
+ const testSchema: AnySchema = schema((s) => {
292
+ return s.addTable("users", (t) => {
293
+ return t.addColumn("id", idColumn()).addColumn("name", column("string"));
294
+ });
295
+ });
296
+
297
+ const fragnoDb = new FragnoDatabase({
298
+ namespace: "test-db",
299
+ schema: testSchema,
300
+ adapter: mysqlAdapter,
301
+ });
302
+
303
+ const results = await generateMigrationsOrSchema([fragnoDb]);
304
+
305
+ expect(results).toHaveLength(2);
306
+ // Check that MySQL foreign key checks are included
307
+ expect(results[1].schema).toContain("SET FOREIGN_KEY_CHECKS = 0");
308
+ expect(results[1].schema).toContain("SET FOREIGN_KEY_CHECKS = 1");
309
+ // Verify they're in the correct order
310
+ const schemaContent = results[1].schema;
311
+ const fkCheckOffIndex = schemaContent.indexOf("SET FOREIGN_KEY_CHECKS = 0");
312
+ const fkCheckOnIndex = schemaContent.indexOf("SET FOREIGN_KEY_CHECKS = 1");
313
+ const createTableIndex = schemaContent.indexOf("create table");
314
+ expect(fkCheckOffIndex).toBeLessThan(createTableIndex);
315
+ expect(createTableIndex).toBeLessThan(fkCheckOnIndex);
316
+ });
317
+
318
+ it("should include SQLite-specific pragma in generated SQL", async () => {
319
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
320
+ const sqliteDb = new Kysely({ dialect: new PostgresDialect({} as any) });
321
+ const sqliteAdapter = new KyselyAdapter({ db: sqliteDb, provider: "sqlite" });
322
+
323
+ vi.spyOn(sqliteAdapter, "isConnectionHealthy").mockResolvedValue(true);
324
+ vi.spyOn(sqliteAdapter, "getSchemaVersion").mockResolvedValue(undefined);
325
+
326
+ const testSchema: AnySchema = schema((s) => {
327
+ return s.addTable("users", (t) => {
328
+ return t.addColumn("id", idColumn()).addColumn("name", column("string"));
329
+ });
330
+ });
331
+
332
+ const fragnoDb = new FragnoDatabase({
333
+ namespace: "test-db",
334
+ schema: testSchema,
335
+ adapter: sqliteAdapter,
336
+ });
337
+
338
+ const results = await generateMigrationsOrSchema([fragnoDb]);
339
+
340
+ expect(results).toHaveLength(2);
341
+ // Check that SQLite pragma is included
342
+ expect(results[1].schema).toContain("PRAGMA defer_foreign_keys = ON");
343
+ // Verify it's at the beginning
344
+ const schemaContent = results[1].schema;
345
+ const pragmaIndex = schemaContent.indexOf("PRAGMA defer_foreign_keys = ON");
346
+ const createTableIndex = schemaContent.indexOf("create table");
347
+ expect(pragmaIndex).toBeLessThan(createTableIndex);
348
+ });
349
+ });
350
+
351
+ describe("postProcessMigrationFilenames", () => {
352
+ const mockDate = new Date("2025-10-24T12:00:00Z");
353
+
354
+ beforeEach(() => {
355
+ vi.useFakeTimers();
356
+ vi.setSystemTime(mockDate);
357
+ });
358
+
359
+ afterEach(() => {
360
+ vi.useRealTimers();
361
+ });
362
+
363
+ it("should return empty array when given empty input", () => {
364
+ const result = postProcessMigrationFilenames([]);
365
+ expect(result).toEqual([]);
366
+ });
367
+
368
+ it("should order settings namespace first", () => {
369
+ const files: GenerationInternalResult[] = [
370
+ {
371
+ schema: "schema1",
372
+ path: "placeholder.sql",
373
+ namespace: "fragno-db-comment-db",
374
+ fromVersion: 0,
375
+ toVersion: 3,
376
+ },
377
+ {
378
+ schema: "schema2",
379
+ path: "placeholder.sql",
380
+ namespace: "fragno-db-settings",
381
+ fromVersion: 0,
382
+ toVersion: 1,
383
+ },
384
+ {
385
+ schema: "schema3",
386
+ path: "placeholder.sql",
387
+ namespace: "fragno-db-rating-db",
388
+ fromVersion: 0,
389
+ toVersion: 2,
390
+ },
391
+ ];
392
+
393
+ const result = postProcessMigrationFilenames(files);
394
+
395
+ expect(result).toHaveLength(3);
396
+ expect(result[0].namespace).toBe("fragno-db-settings");
397
+ expect(result[0].path).toBe("20251024_001_f000_t001_fragno-db-settings.sql");
398
+ });
399
+
400
+ it("should sort non-settings namespaces alphabetically", () => {
401
+ const files: GenerationInternalResult[] = [
402
+ {
403
+ schema: "schema1",
404
+ path: "placeholder.sql",
405
+ namespace: "zebra-db",
406
+ fromVersion: 0,
407
+ toVersion: 1,
408
+ },
409
+ {
410
+ schema: "schema2",
411
+ path: "placeholder.sql",
412
+ namespace: "apple-db",
413
+ fromVersion: 0,
414
+ toVersion: 1,
415
+ },
416
+ {
417
+ schema: "schema3",
418
+ path: "placeholder.sql",
419
+ namespace: "mango-db",
420
+ fromVersion: 0,
421
+ toVersion: 1,
422
+ },
423
+ ];
424
+
425
+ const result = postProcessMigrationFilenames(files);
426
+
427
+ expect(result).toHaveLength(3);
428
+ expect(result[0].namespace).toBe("apple-db");
429
+ expect(result[1].namespace).toBe("mango-db");
430
+ expect(result[2].namespace).toBe("zebra-db");
431
+ });
432
+
433
+ it("should format filename with correct ordering and version numbers", () => {
434
+ const files: GenerationInternalResult[] = [
435
+ {
436
+ schema: "CREATE TABLE users;",
437
+ path: "placeholder.sql",
438
+ namespace: "fragno-db-settings",
439
+ fromVersion: 0,
440
+ toVersion: 1,
441
+ },
442
+ {
443
+ schema: "CREATE TABLE comments;",
444
+ path: "placeholder.sql",
445
+ namespace: "comment-db",
446
+ fromVersion: 5,
447
+ toVersion: 10,
448
+ },
449
+ ];
450
+
451
+ const result = postProcessMigrationFilenames(files);
452
+
453
+ expect(result[0].path).toBe("20251024_001_f000_t001_fragno-db-settings.sql");
454
+ expect(result[1].path).toBe("20251024_002_f005_t010_comment-db.sql");
455
+ });
456
+
457
+ it("should pad numbers to 3 digits", () => {
458
+ const files: GenerationInternalResult[] = [
459
+ {
460
+ schema: "schema",
461
+ path: "placeholder.sql",
462
+ namespace: "test-db",
463
+ fromVersion: 99,
464
+ toVersion: 999,
465
+ },
466
+ ];
467
+
468
+ const result = postProcessMigrationFilenames(files);
469
+
470
+ expect(result[0].path).toBe("20251024_001_f099_t999_test-db.sql");
471
+ });
472
+
473
+ it("should sanitize namespace with invalid characters", () => {
474
+ const files: GenerationInternalResult[] = [
475
+ {
476
+ schema: "schema",
477
+ path: "placeholder.sql",
478
+ namespace: "test@db#special!chars",
479
+ fromVersion: 0,
480
+ toVersion: 1,
481
+ },
482
+ ];
483
+
484
+ const result = postProcessMigrationFilenames(files);
485
+
486
+ expect(result[0].path).toBe("20251024_001_f000_t001_test_db_special_chars.sql");
487
+ });
488
+
489
+ it("should preserve schema content", () => {
490
+ const files: GenerationInternalResult[] = [
491
+ {
492
+ schema: "CREATE TABLE users (id INT);",
493
+ path: "placeholder.sql",
494
+ namespace: "user-db",
495
+ fromVersion: 0,
496
+ toVersion: 1,
497
+ },
498
+ ];
499
+
500
+ const result = postProcessMigrationFilenames(files);
501
+
502
+ expect(result[0].schema).toBe("CREATE TABLE users (id INT);");
503
+ });
504
+
505
+ it("should handle multiple files with settings first and others sorted", () => {
506
+ const files: GenerationInternalResult[] = [
507
+ {
508
+ schema: "schema1",
509
+ path: "placeholder.sql",
510
+ namespace: "zoo-db",
511
+ fromVersion: 1,
512
+ toVersion: 2,
513
+ },
514
+ {
515
+ schema: "schema2",
516
+ path: "placeholder.sql",
517
+ namespace: "fragno-db-settings",
518
+ fromVersion: 0,
519
+ toVersion: 5,
520
+ },
521
+ {
522
+ schema: "schema3",
523
+ path: "placeholder.sql",
524
+ namespace: "apple-db",
525
+ fromVersion: 3,
526
+ toVersion: 4,
527
+ },
528
+ {
529
+ schema: "schema4",
530
+ path: "placeholder.sql",
531
+ namespace: "mango-db",
532
+ fromVersion: 2,
533
+ toVersion: 3,
534
+ },
535
+ ];
536
+
537
+ const result = postProcessMigrationFilenames(files);
538
+
539
+ expect(result).toHaveLength(4);
540
+ expect(result[0].namespace).toBe("fragno-db-settings");
541
+ expect(result[0].path).toBe("20251024_001_f000_t005_fragno-db-settings.sql");
542
+ expect(result[1].namespace).toBe("apple-db");
543
+ expect(result[1].path).toBe("20251024_002_f003_t004_apple-db.sql");
544
+ expect(result[2].namespace).toBe("mango-db");
545
+ expect(result[2].path).toBe("20251024_003_f002_t003_mango-db.sql");
546
+ expect(result[3].namespace).toBe("zoo-db");
547
+ expect(result[3].path).toBe("20251024_004_f001_t002_zoo-db.sql");
548
+ });
549
+
550
+ it("should handle ordering numbers beyond 100", () => {
551
+ const files: GenerationInternalResult[] = Array.from({ length: 150 }, (_, i) => ({
552
+ schema: `schema${i}`,
553
+ path: "placeholder.sql",
554
+ namespace: `db-${String(i).padStart(3, "0")}`,
555
+ fromVersion: 0,
556
+ toVersion: 1,
557
+ }));
558
+
559
+ const result = postProcessMigrationFilenames(files);
560
+
561
+ expect(result).toHaveLength(150);
562
+ expect(result[99].path).toContain("_100_");
563
+ expect(result[149].path).toContain("_150_");
564
+ });
565
+
566
+ it("should preserve hyphens in namespace", () => {
567
+ const files: GenerationInternalResult[] = [
568
+ {
569
+ schema: "schema",
570
+ path: "placeholder.sql",
571
+ namespace: "my-awesome-db-fragment",
572
+ fromVersion: 0,
573
+ toVersion: 1,
574
+ },
575
+ ];
576
+
577
+ const result = postProcessMigrationFilenames(files);
578
+
579
+ expect(result[0].path).toBe("20251024_001_f000_t001_my-awesome-db-fragment.sql");
580
+ });
581
+
582
+ it("should use current date in YYYYMMDD format", () => {
583
+ const files: GenerationInternalResult[] = [
584
+ {
585
+ schema: "schema",
586
+ path: "placeholder.sql",
587
+ namespace: "test-db",
588
+ fromVersion: 0,
589
+ toVersion: 1,
590
+ },
591
+ ];
592
+
593
+ const result = postProcessMigrationFilenames(files);
594
+
595
+ expect(result[0].path).toMatch(/^20251024_/);
596
+ });
597
+ });