@typokit/db-raw 0.1.4
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/dist/index.d.ts +17 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +361 -0
- package/dist/index.js.map +1 -0
- package/package.json +29 -0
- package/src/index.test.ts +486 -0
- package/src/index.ts +478 -0
|
@@ -0,0 +1,486 @@
|
|
|
1
|
+
import { describe, it, expect } from "@rstest/core";
|
|
2
|
+
import { RawSqlDatabaseAdapter, parseUnionValues } from "./index.js";
|
|
3
|
+
import type { SchemaTypeMap } from "@typokit/types";
|
|
4
|
+
import type { DatabaseState } from "@typokit/core";
|
|
5
|
+
|
|
6
|
+
describe("RawSqlDatabaseAdapter", () => {
|
|
7
|
+
describe("parseUnionValues", () => {
|
|
8
|
+
it("should parse string union types", () => {
|
|
9
|
+
const values = parseUnionValues('"active" | "suspended" | "deleted"');
|
|
10
|
+
expect(values).toEqual(["active", "suspended", "deleted"]);
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
it("should return null for non-union types", () => {
|
|
14
|
+
expect(parseUnionValues("string")).toBeNull();
|
|
15
|
+
expect(parseUnionValues("number")).toBeNull();
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
it("should return null for mixed unions", () => {
|
|
19
|
+
expect(parseUnionValues('"a" | number')).toBeNull();
|
|
20
|
+
});
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
describe("generate() - PostgreSQL", () => {
|
|
24
|
+
const adapter = new RawSqlDatabaseAdapter({ dialect: "postgresql" });
|
|
25
|
+
|
|
26
|
+
it("should generate SQL DDL from User type", () => {
|
|
27
|
+
const types: SchemaTypeMap = {
|
|
28
|
+
User: {
|
|
29
|
+
name: "User",
|
|
30
|
+
jsdoc: { table: "users" },
|
|
31
|
+
properties: {
|
|
32
|
+
id: {
|
|
33
|
+
type: "string",
|
|
34
|
+
optional: false,
|
|
35
|
+
jsdoc: { id: "", generated: "uuid" },
|
|
36
|
+
},
|
|
37
|
+
email: {
|
|
38
|
+
type: "string",
|
|
39
|
+
optional: false,
|
|
40
|
+
jsdoc: { format: "email", unique: "" },
|
|
41
|
+
},
|
|
42
|
+
displayName: {
|
|
43
|
+
type: "string",
|
|
44
|
+
optional: false,
|
|
45
|
+
jsdoc: { maxLength: "100" },
|
|
46
|
+
},
|
|
47
|
+
status: {
|
|
48
|
+
type: '"active" | "suspended" | "deleted"',
|
|
49
|
+
optional: false,
|
|
50
|
+
jsdoc: { default: "active" },
|
|
51
|
+
},
|
|
52
|
+
createdAt: {
|
|
53
|
+
type: "Date",
|
|
54
|
+
optional: false,
|
|
55
|
+
jsdoc: { generated: "now" },
|
|
56
|
+
},
|
|
57
|
+
updatedAt: {
|
|
58
|
+
type: "Date",
|
|
59
|
+
optional: false,
|
|
60
|
+
jsdoc: { onUpdate: "now" },
|
|
61
|
+
},
|
|
62
|
+
},
|
|
63
|
+
},
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
const outputs = adapter.generate(types);
|
|
67
|
+
// SQL file + types file
|
|
68
|
+
expect(outputs.length).toBeGreaterThanOrEqual(2);
|
|
69
|
+
|
|
70
|
+
const sqlOutput = outputs.find((o) => o.filePath.endsWith(".sql"));
|
|
71
|
+
expect(sqlOutput).toBeDefined();
|
|
72
|
+
expect(sqlOutput!.filePath).toBe("sql/users.sql");
|
|
73
|
+
expect(sqlOutput!.overwrite).toBe(true);
|
|
74
|
+
|
|
75
|
+
const content = sqlOutput!.content;
|
|
76
|
+
|
|
77
|
+
// Header
|
|
78
|
+
expect(content).toContain("-- AUTO-GENERATED by @typokit/db-raw");
|
|
79
|
+
|
|
80
|
+
// CREATE TYPE for enum
|
|
81
|
+
expect(content).toContain("CREATE TYPE user_status AS ENUM");
|
|
82
|
+
expect(content).toContain("'active', 'suspended', 'deleted'");
|
|
83
|
+
|
|
84
|
+
// CREATE TABLE
|
|
85
|
+
expect(content).toContain("CREATE TABLE users");
|
|
86
|
+
|
|
87
|
+
// Columns
|
|
88
|
+
expect(content).toContain(
|
|
89
|
+
"id UUID NOT NULL PRIMARY KEY DEFAULT gen_random_uuid()",
|
|
90
|
+
);
|
|
91
|
+
expect(content).toContain("email VARCHAR(255) NOT NULL UNIQUE");
|
|
92
|
+
expect(content).toContain("display_name VARCHAR(100) NOT NULL");
|
|
93
|
+
expect(content).toContain("user_status NOT NULL DEFAULT 'active'");
|
|
94
|
+
expect(content).toContain(
|
|
95
|
+
"created_at TIMESTAMPTZ NOT NULL DEFAULT now()",
|
|
96
|
+
);
|
|
97
|
+
expect(content).toContain(
|
|
98
|
+
"updated_at TIMESTAMPTZ NOT NULL DEFAULT now()",
|
|
99
|
+
);
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
it("should generate TypeScript interfaces", () => {
|
|
103
|
+
const types: SchemaTypeMap = {
|
|
104
|
+
User: {
|
|
105
|
+
name: "User",
|
|
106
|
+
jsdoc: { table: "users" },
|
|
107
|
+
properties: {
|
|
108
|
+
id: {
|
|
109
|
+
type: "string",
|
|
110
|
+
optional: false,
|
|
111
|
+
jsdoc: { id: "", generated: "uuid" },
|
|
112
|
+
},
|
|
113
|
+
email: { type: "string", optional: false },
|
|
114
|
+
displayName: { type: "string", optional: false },
|
|
115
|
+
bio: { type: "string", optional: true },
|
|
116
|
+
},
|
|
117
|
+
},
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
const outputs = adapter.generate(types);
|
|
121
|
+
const tsOutput = outputs.find((o) => o.filePath.endsWith(".ts"));
|
|
122
|
+
expect(tsOutput).toBeDefined();
|
|
123
|
+
expect(tsOutput!.filePath).toBe("sql/types.ts");
|
|
124
|
+
|
|
125
|
+
const content = tsOutput!.content;
|
|
126
|
+
expect(content).toContain("export interface User {");
|
|
127
|
+
expect(content).toContain("id: string;");
|
|
128
|
+
expect(content).toContain("email: string;");
|
|
129
|
+
expect(content).toContain("displayName: string;");
|
|
130
|
+
expect(content).toContain("bio?: string;");
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
it("should handle all supported column types", () => {
|
|
134
|
+
const types: SchemaTypeMap = {
|
|
135
|
+
Product: {
|
|
136
|
+
name: "Product",
|
|
137
|
+
properties: {
|
|
138
|
+
id: {
|
|
139
|
+
type: "string",
|
|
140
|
+
optional: false,
|
|
141
|
+
jsdoc: { id: "", generated: "uuid" },
|
|
142
|
+
},
|
|
143
|
+
name: { type: "string", optional: false },
|
|
144
|
+
price: { type: "number", optional: false },
|
|
145
|
+
stock: { type: "bigint", optional: false },
|
|
146
|
+
active: { type: "boolean", optional: false },
|
|
147
|
+
metadata: { type: "Record<string, unknown>", optional: true },
|
|
148
|
+
description: { type: "string", optional: true },
|
|
149
|
+
},
|
|
150
|
+
},
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
const outputs = adapter.generate(types);
|
|
154
|
+
const sqlOutput = outputs.find((o) => o.filePath.endsWith(".sql"));
|
|
155
|
+
const content = sqlOutput!.content;
|
|
156
|
+
|
|
157
|
+
expect(content).toContain("id UUID");
|
|
158
|
+
expect(content).toContain("name TEXT NOT NULL");
|
|
159
|
+
expect(content).toContain("price INTEGER NOT NULL");
|
|
160
|
+
expect(content).toContain("stock BIGINT NOT NULL");
|
|
161
|
+
expect(content).toContain("active BOOLEAN NOT NULL");
|
|
162
|
+
expect(content).toContain("metadata JSONB");
|
|
163
|
+
expect(content).toContain("description TEXT");
|
|
164
|
+
|
|
165
|
+
// Optional fields do NOT have NOT NULL
|
|
166
|
+
expect(content).not.toMatch(/metadata JSONB NOT NULL/);
|
|
167
|
+
expect(content).not.toMatch(/description TEXT NOT NULL/);
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
it("should use @table JSDoc tag for table name", () => {
|
|
171
|
+
const types: SchemaTypeMap = {
|
|
172
|
+
UserAccount: {
|
|
173
|
+
name: "UserAccount",
|
|
174
|
+
jsdoc: { table: "custom_accounts" },
|
|
175
|
+
properties: {
|
|
176
|
+
id: { type: "string", optional: false, jsdoc: { id: "" } },
|
|
177
|
+
},
|
|
178
|
+
},
|
|
179
|
+
};
|
|
180
|
+
|
|
181
|
+
const outputs = adapter.generate(types);
|
|
182
|
+
const sqlOutput = outputs.find((o) => o.filePath.endsWith(".sql"));
|
|
183
|
+
expect(sqlOutput!.content).toContain("CREATE TABLE custom_accounts");
|
|
184
|
+
expect(sqlOutput!.filePath).toBe("sql/custom_accounts.sql");
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
it("should pluralize table names from type names", () => {
|
|
188
|
+
const types: SchemaTypeMap = {
|
|
189
|
+
Category: {
|
|
190
|
+
name: "Category",
|
|
191
|
+
properties: {
|
|
192
|
+
id: { type: "string", optional: false, jsdoc: { id: "" } },
|
|
193
|
+
},
|
|
194
|
+
},
|
|
195
|
+
};
|
|
196
|
+
|
|
197
|
+
const outputs = adapter.generate(types);
|
|
198
|
+
const sqlOutput = outputs.find((o) => o.filePath.endsWith(".sql"));
|
|
199
|
+
expect(sqlOutput!.content).toContain("CREATE TABLE categories");
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
it("should generate CREATE TYPE for string union types", () => {
|
|
203
|
+
const types: SchemaTypeMap = {
|
|
204
|
+
Order: {
|
|
205
|
+
name: "Order",
|
|
206
|
+
properties: {
|
|
207
|
+
status: {
|
|
208
|
+
type: '"pending" | "shipped" | "delivered"',
|
|
209
|
+
optional: false,
|
|
210
|
+
},
|
|
211
|
+
},
|
|
212
|
+
},
|
|
213
|
+
};
|
|
214
|
+
|
|
215
|
+
const outputs = adapter.generate(types);
|
|
216
|
+
const sqlOutput = outputs.find((o) => o.filePath.endsWith(".sql"));
|
|
217
|
+
const content = sqlOutput!.content;
|
|
218
|
+
expect(content).toContain("CREATE TYPE order_status AS ENUM");
|
|
219
|
+
expect(content).toContain("'pending', 'shipped', 'delivered'");
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
it("should handle @default with numeric values", () => {
|
|
223
|
+
const types: SchemaTypeMap = {
|
|
224
|
+
Config: {
|
|
225
|
+
name: "Config",
|
|
226
|
+
properties: {
|
|
227
|
+
retryCount: {
|
|
228
|
+
type: "number",
|
|
229
|
+
optional: false,
|
|
230
|
+
jsdoc: { default: "3" },
|
|
231
|
+
},
|
|
232
|
+
},
|
|
233
|
+
},
|
|
234
|
+
};
|
|
235
|
+
|
|
236
|
+
const outputs = adapter.generate(types);
|
|
237
|
+
const sqlOutput = outputs.find((o) => o.filePath.endsWith(".sql"));
|
|
238
|
+
expect(sqlOutput!.content).toContain("DEFAULT 3");
|
|
239
|
+
});
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
describe("generate() - SQLite", () => {
|
|
243
|
+
const adapter = new RawSqlDatabaseAdapter({ dialect: "sqlite" });
|
|
244
|
+
|
|
245
|
+
it("should generate SQLite DDL", () => {
|
|
246
|
+
const types: SchemaTypeMap = {
|
|
247
|
+
User: {
|
|
248
|
+
name: "User",
|
|
249
|
+
jsdoc: { table: "users" },
|
|
250
|
+
properties: {
|
|
251
|
+
id: { type: "string", optional: false, jsdoc: { id: "" } },
|
|
252
|
+
name: { type: "string", optional: false },
|
|
253
|
+
age: { type: "number", optional: false },
|
|
254
|
+
active: { type: "boolean", optional: false },
|
|
255
|
+
},
|
|
256
|
+
},
|
|
257
|
+
};
|
|
258
|
+
|
|
259
|
+
const outputs = adapter.generate(types);
|
|
260
|
+
const sqlOutput = outputs.find((o) => o.filePath.endsWith(".sql"));
|
|
261
|
+
const content = sqlOutput!.content;
|
|
262
|
+
|
|
263
|
+
expect(content).toContain("CREATE TABLE users");
|
|
264
|
+
expect(content).toContain("id TEXT NOT NULL PRIMARY KEY");
|
|
265
|
+
expect(content).toContain("name TEXT NOT NULL");
|
|
266
|
+
expect(content).toContain("age INTEGER NOT NULL");
|
|
267
|
+
expect(content).toContain("active INTEGER NOT NULL");
|
|
268
|
+
|
|
269
|
+
// No CREATE TYPE in SQLite
|
|
270
|
+
expect(content).not.toContain("CREATE TYPE");
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
it("should handle enum unions as TEXT in SQLite", () => {
|
|
274
|
+
const types: SchemaTypeMap = {
|
|
275
|
+
Order: {
|
|
276
|
+
name: "Order",
|
|
277
|
+
properties: {
|
|
278
|
+
status: {
|
|
279
|
+
type: '"pending" | "shipped"',
|
|
280
|
+
optional: false,
|
|
281
|
+
},
|
|
282
|
+
},
|
|
283
|
+
},
|
|
284
|
+
};
|
|
285
|
+
|
|
286
|
+
const outputs = adapter.generate(types);
|
|
287
|
+
const sqlOutput = outputs.find((o) => o.filePath.endsWith(".sql"));
|
|
288
|
+
expect(sqlOutput!.content).toContain("status TEXT NOT NULL");
|
|
289
|
+
});
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
describe("diff()", () => {
|
|
293
|
+
const adapter = new RawSqlDatabaseAdapter({ dialect: "postgresql" });
|
|
294
|
+
|
|
295
|
+
it("should detect new tables", () => {
|
|
296
|
+
const types: SchemaTypeMap = {
|
|
297
|
+
User: {
|
|
298
|
+
name: "User",
|
|
299
|
+
jsdoc: { table: "users" },
|
|
300
|
+
properties: {
|
|
301
|
+
id: { type: "string", optional: false },
|
|
302
|
+
},
|
|
303
|
+
},
|
|
304
|
+
};
|
|
305
|
+
const state: DatabaseState = { tables: {} };
|
|
306
|
+
|
|
307
|
+
const draft = adapter.diff(types, state);
|
|
308
|
+
expect(draft.changes).toHaveLength(1);
|
|
309
|
+
expect(draft.changes[0]).toEqual({ type: "add", entity: "users" });
|
|
310
|
+
expect(draft.destructive).toBe(false);
|
|
311
|
+
});
|
|
312
|
+
|
|
313
|
+
it("should detect new columns", () => {
|
|
314
|
+
const types: SchemaTypeMap = {
|
|
315
|
+
User: {
|
|
316
|
+
name: "User",
|
|
317
|
+
jsdoc: { table: "users" },
|
|
318
|
+
properties: {
|
|
319
|
+
id: { type: "string", optional: false },
|
|
320
|
+
email: { type: "string", optional: false },
|
|
321
|
+
},
|
|
322
|
+
},
|
|
323
|
+
};
|
|
324
|
+
const state: DatabaseState = {
|
|
325
|
+
tables: {
|
|
326
|
+
users: {
|
|
327
|
+
columns: {
|
|
328
|
+
id: { type: "text", nullable: false },
|
|
329
|
+
},
|
|
330
|
+
},
|
|
331
|
+
},
|
|
332
|
+
};
|
|
333
|
+
|
|
334
|
+
const draft = adapter.diff(types, state);
|
|
335
|
+
expect(draft.changes).toHaveLength(1);
|
|
336
|
+
expect(draft.changes[0].type).toBe("add");
|
|
337
|
+
expect(draft.changes[0].field).toBe("email");
|
|
338
|
+
});
|
|
339
|
+
|
|
340
|
+
it("should detect removed columns with DESTRUCTIVE comment", () => {
|
|
341
|
+
const types: SchemaTypeMap = {
|
|
342
|
+
User: {
|
|
343
|
+
name: "User",
|
|
344
|
+
jsdoc: { table: "users" },
|
|
345
|
+
properties: {
|
|
346
|
+
id: { type: "string", optional: false },
|
|
347
|
+
},
|
|
348
|
+
},
|
|
349
|
+
};
|
|
350
|
+
const state: DatabaseState = {
|
|
351
|
+
tables: {
|
|
352
|
+
users: {
|
|
353
|
+
columns: {
|
|
354
|
+
id: { type: "text", nullable: false },
|
|
355
|
+
old_col: { type: "text", nullable: true },
|
|
356
|
+
},
|
|
357
|
+
},
|
|
358
|
+
},
|
|
359
|
+
};
|
|
360
|
+
|
|
361
|
+
const draft = adapter.diff(types, state);
|
|
362
|
+
expect(draft.changes).toHaveLength(1);
|
|
363
|
+
expect(draft.changes[0]).toEqual({
|
|
364
|
+
type: "remove",
|
|
365
|
+
entity: "users",
|
|
366
|
+
field: "old_col",
|
|
367
|
+
});
|
|
368
|
+
expect(draft.destructive).toBe(true);
|
|
369
|
+
expect(draft.sql).toContain("-- DESTRUCTIVE: requires review");
|
|
370
|
+
expect(draft.sql).toContain('DROP COLUMN "old_col"');
|
|
371
|
+
});
|
|
372
|
+
|
|
373
|
+
it("should detect removed tables with DESTRUCTIVE comment", () => {
|
|
374
|
+
const types: SchemaTypeMap = {};
|
|
375
|
+
const state: DatabaseState = {
|
|
376
|
+
tables: {
|
|
377
|
+
old_table: { columns: { id: { type: "text", nullable: false } } },
|
|
378
|
+
},
|
|
379
|
+
};
|
|
380
|
+
|
|
381
|
+
const draft = adapter.diff(types, state);
|
|
382
|
+
expect(draft.changes).toHaveLength(1);
|
|
383
|
+
expect(draft.changes[0]).toEqual({ type: "remove", entity: "old_table" });
|
|
384
|
+
expect(draft.destructive).toBe(true);
|
|
385
|
+
expect(draft.sql).toContain("-- DESTRUCTIVE: requires review");
|
|
386
|
+
expect(draft.sql).toContain("DROP TABLE");
|
|
387
|
+
});
|
|
388
|
+
|
|
389
|
+
it("should detect nullable changes", () => {
|
|
390
|
+
const types: SchemaTypeMap = {
|
|
391
|
+
User: {
|
|
392
|
+
name: "User",
|
|
393
|
+
jsdoc: { table: "users" },
|
|
394
|
+
properties: {
|
|
395
|
+
email: { type: "string", optional: true },
|
|
396
|
+
},
|
|
397
|
+
},
|
|
398
|
+
};
|
|
399
|
+
const state: DatabaseState = {
|
|
400
|
+
tables: {
|
|
401
|
+
users: {
|
|
402
|
+
columns: {
|
|
403
|
+
email: { type: "text", nullable: false },
|
|
404
|
+
},
|
|
405
|
+
},
|
|
406
|
+
},
|
|
407
|
+
};
|
|
408
|
+
|
|
409
|
+
const draft = adapter.diff(types, state);
|
|
410
|
+
expect(draft.changes).toHaveLength(1);
|
|
411
|
+
expect(draft.changes[0].type).toBe("modify");
|
|
412
|
+
});
|
|
413
|
+
|
|
414
|
+
it("should generate migration SQL with ALTER TABLE", () => {
|
|
415
|
+
const types: SchemaTypeMap = {
|
|
416
|
+
User: {
|
|
417
|
+
name: "User",
|
|
418
|
+
jsdoc: { table: "users" },
|
|
419
|
+
properties: {
|
|
420
|
+
id: { type: "string", optional: false },
|
|
421
|
+
newField: { type: "string", optional: false },
|
|
422
|
+
},
|
|
423
|
+
},
|
|
424
|
+
};
|
|
425
|
+
const state: DatabaseState = {
|
|
426
|
+
tables: {
|
|
427
|
+
users: {
|
|
428
|
+
columns: {
|
|
429
|
+
id: { type: "text", nullable: false },
|
|
430
|
+
},
|
|
431
|
+
},
|
|
432
|
+
},
|
|
433
|
+
};
|
|
434
|
+
|
|
435
|
+
const draft = adapter.diff(types, state);
|
|
436
|
+
expect(draft.sql).toContain(
|
|
437
|
+
'ALTER TABLE "users" ADD COLUMN "new_field" TEXT;',
|
|
438
|
+
);
|
|
439
|
+
expect(draft.name).toMatch(/^\d{14}_schema_update$/);
|
|
440
|
+
});
|
|
441
|
+
});
|
|
442
|
+
|
|
443
|
+
describe("constructor options", () => {
|
|
444
|
+
it("should use custom output directory", () => {
|
|
445
|
+
const adapter = new RawSqlDatabaseAdapter({ outputDir: "db/schema" });
|
|
446
|
+
const types: SchemaTypeMap = {
|
|
447
|
+
Item: {
|
|
448
|
+
name: "Item",
|
|
449
|
+
properties: { id: { type: "string", optional: false } },
|
|
450
|
+
},
|
|
451
|
+
};
|
|
452
|
+
|
|
453
|
+
const outputs = adapter.generate(types);
|
|
454
|
+
expect(outputs[0].filePath).toContain("db/schema/");
|
|
455
|
+
});
|
|
456
|
+
|
|
457
|
+
it("should default to postgresql dialect", () => {
|
|
458
|
+
const adapter = new RawSqlDatabaseAdapter();
|
|
459
|
+
const types: SchemaTypeMap = {
|
|
460
|
+
Item: {
|
|
461
|
+
name: "Item",
|
|
462
|
+
properties: { id: { type: "string", optional: false } },
|
|
463
|
+
},
|
|
464
|
+
};
|
|
465
|
+
|
|
466
|
+
const outputs = adapter.generate(types);
|
|
467
|
+
const sqlOutput = outputs.find((o) => o.filePath.endsWith(".sql"));
|
|
468
|
+
expect(sqlOutput!.content).toContain("CREATE TABLE");
|
|
469
|
+
// PostgreSQL uses TEXT for bare strings
|
|
470
|
+
expect(sqlOutput!.content).toContain("TEXT");
|
|
471
|
+
});
|
|
472
|
+
|
|
473
|
+
it("should default to sql output directory", () => {
|
|
474
|
+
const adapter = new RawSqlDatabaseAdapter();
|
|
475
|
+
const types: SchemaTypeMap = {
|
|
476
|
+
Item: {
|
|
477
|
+
name: "Item",
|
|
478
|
+
properties: { id: { type: "string", optional: false } },
|
|
479
|
+
},
|
|
480
|
+
};
|
|
481
|
+
|
|
482
|
+
const outputs = adapter.generate(types);
|
|
483
|
+
expect(outputs[0].filePath).toContain("sql/");
|
|
484
|
+
});
|
|
485
|
+
});
|
|
486
|
+
});
|