@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.
- package/.turbo/turbo-build.log +61 -53
- package/CHANGELOG.md +12 -0
- package/dist/adapters/adapters.d.ts +11 -1
- package/dist/adapters/adapters.d.ts.map +1 -1
- package/dist/adapters/drizzle/drizzle-adapter.d.ts +9 -2
- package/dist/adapters/drizzle/drizzle-adapter.d.ts.map +1 -1
- package/dist/adapters/drizzle/drizzle-adapter.js +21 -39
- package/dist/adapters/drizzle/drizzle-adapter.js.map +1 -1
- package/dist/adapters/drizzle/drizzle-query.d.ts.map +1 -1
- package/dist/adapters/drizzle/drizzle-query.js +3 -2
- package/dist/adapters/drizzle/drizzle-query.js.map +1 -1
- package/dist/adapters/drizzle/drizzle-uow-compiler.js +8 -6
- package/dist/adapters/drizzle/drizzle-uow-compiler.js.map +1 -1
- package/dist/adapters/drizzle/drizzle-uow-decoder.js.map +1 -1
- package/dist/adapters/drizzle/drizzle-uow-executor.js.map +1 -1
- package/dist/adapters/drizzle/generate.js +107 -34
- package/dist/adapters/drizzle/generate.js.map +1 -1
- package/dist/adapters/drizzle/shared.js +14 -1
- package/dist/adapters/drizzle/shared.js.map +1 -1
- package/dist/adapters/kysely/kysely-adapter.d.ts +2 -1
- package/dist/adapters/kysely/kysely-adapter.d.ts.map +1 -1
- package/dist/adapters/kysely/kysely-adapter.js +25 -30
- package/dist/adapters/kysely/kysely-adapter.js.map +1 -1
- package/dist/adapters/kysely/kysely-query-builder.js +48 -44
- package/dist/adapters/kysely/kysely-query-builder.js.map +1 -1
- package/dist/adapters/kysely/kysely-query-compiler.js +2 -2
- package/dist/adapters/kysely/kysely-query-compiler.js.map +1 -1
- package/dist/adapters/kysely/kysely-query.js +3 -2
- package/dist/adapters/kysely/kysely-query.js.map +1 -1
- package/dist/adapters/kysely/kysely-shared.js +18 -0
- package/dist/adapters/kysely/kysely-shared.js.map +1 -0
- package/dist/adapters/kysely/kysely-uow-compiler.js +4 -3
- package/dist/adapters/kysely/kysely-uow-compiler.js.map +1 -1
- package/dist/adapters/kysely/migration/execute.js +15 -12
- package/dist/adapters/kysely/migration/execute.js.map +1 -1
- package/dist/migration-engine/auto-from-schema.js +2 -8
- package/dist/migration-engine/auto-from-schema.js.map +1 -1
- package/dist/migration-engine/create.d.ts +1 -5
- package/dist/migration-engine/create.js +1 -1
- package/dist/migration-engine/create.js.map +1 -1
- package/dist/migration-engine/generation-engine.d.ts +51 -0
- package/dist/migration-engine/generation-engine.d.ts.map +1 -0
- package/dist/migration-engine/generation-engine.js +165 -0
- package/dist/migration-engine/generation-engine.js.map +1 -0
- package/dist/migration-engine/shared.d.ts +5 -2
- package/dist/migration-engine/shared.d.ts.map +1 -1
- package/dist/migration-engine/shared.js.map +1 -1
- package/dist/mod.d.ts +0 -8
- package/dist/mod.d.ts.map +1 -1
- package/dist/mod.js +0 -32
- package/dist/mod.js.map +1 -1
- package/dist/query/condition-builder.js.map +1 -1
- package/dist/query/result-transform.js +2 -1
- package/dist/query/result-transform.js.map +1 -1
- package/dist/schema/create.d.ts +74 -16
- package/dist/schema/create.d.ts.map +1 -1
- package/dist/schema/create.js +76 -11
- package/dist/schema/create.js.map +1 -1
- package/dist/schema/serialize.js.map +1 -1
- package/dist/shared/settings-schema.js +36 -0
- package/dist/shared/settings-schema.js.map +1 -0
- package/dist/util/import-generator.js.map +1 -1
- package/dist/util/parse.js.map +1 -1
- package/package.json +8 -2
- package/src/adapters/adapters.ts +10 -3
- package/src/adapters/drizzle/drizzle-adapter-pglite.test.ts +11 -7
- package/src/adapters/drizzle/drizzle-adapter.test.ts +77 -29
- package/src/adapters/drizzle/drizzle-adapter.ts +31 -78
- package/src/adapters/drizzle/drizzle-query.ts +4 -7
- package/src/adapters/drizzle/drizzle-uow-compiler.test.ts +9 -3
- package/src/adapters/drizzle/drizzle-uow-compiler.ts +12 -6
- package/src/adapters/drizzle/drizzle-uow-decoder.ts +1 -1
- package/src/adapters/drizzle/drizzle-uow-executor.ts +1 -1
- package/src/adapters/drizzle/generate.test.ts +573 -150
- package/src/adapters/drizzle/generate.ts +187 -36
- package/src/adapters/drizzle/migrate-drizzle.test.ts +30 -6
- package/src/adapters/drizzle/shared.ts +31 -1
- package/src/adapters/drizzle/test-utils.ts +3 -1
- package/src/adapters/kysely/kysely-adapter-pglite.test.ts +25 -27
- package/src/adapters/kysely/kysely-adapter.ts +35 -58
- package/src/adapters/kysely/kysely-query-builder.ts +75 -44
- package/src/adapters/kysely/kysely-query-compiler.ts +3 -1
- package/src/adapters/kysely/kysely-query.ts +8 -2
- package/src/adapters/kysely/kysely-shared.ts +23 -0
- package/src/adapters/kysely/kysely-uow-compiler.ts +5 -2
- package/src/adapters/kysely/migration/execute-mysql.test.ts +2 -2
- package/src/adapters/kysely/migration/execute-postgres.test.ts +19 -19
- package/src/adapters/kysely/migration/execute.ts +48 -17
- package/src/adapters/kysely/migration/kysely-migrator.test.ts +19 -37
- package/src/fragment.test.ts +1 -0
- package/src/migration-engine/auto-from-schema.ts +14 -18
- package/src/migration-engine/create.ts +1 -6
- package/src/migration-engine/generation-engine.test.ts +597 -0
- package/src/migration-engine/generation-engine.ts +356 -0
- package/src/migration-engine/shared.ts +1 -4
- package/src/mod.ts +0 -66
- package/src/query/condition-builder.ts +24 -8
- package/src/query/result-transform.ts +7 -1
- package/src/schema/create.test.ts +4 -1
- package/src/schema/create.ts +132 -24
- package/src/schema/serialize.ts +21 -7
- package/src/shared/settings-schema.ts +61 -0
- package/src/util/deep-equal.ts +21 -7
- package/src/util/import-generator.ts +3 -1
- package/src/util/parse.ts +3 -1
- package/tsdown.config.ts +1 -0
- package/.turbo/turbo-test.log +0 -37
- 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
|
+
});
|