@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
|
@@ -8,6 +8,8 @@ import {
|
|
|
8
8
|
} from "../../schema/create";
|
|
9
9
|
import type { SQLProvider } from "../../shared/providers";
|
|
10
10
|
import { schemaToDBType, type DBTypeLiteral } from "../../schema/serialize";
|
|
11
|
+
import { createTableNameMapper } from "./shared";
|
|
12
|
+
import { settingsSchema, SETTINGS_TABLE_NAME } from "../../shared/settings-schema";
|
|
11
13
|
|
|
12
14
|
// ============================================================================
|
|
13
15
|
// PROVIDER CONFIGURATION
|
|
@@ -263,6 +265,7 @@ function generateColumnDefinition(
|
|
|
263
265
|
// Default values
|
|
264
266
|
if (column.default) {
|
|
265
267
|
if ("value" in column.default) {
|
|
268
|
+
// Static defaults: defaultTo(value)
|
|
266
269
|
let value: string;
|
|
267
270
|
if (typeof column.default.value === "bigint") {
|
|
268
271
|
ctx.imports.addImport("sql", "drizzle-orm");
|
|
@@ -271,12 +274,22 @@ function generateColumnDefinition(
|
|
|
271
274
|
value = JSON.stringify(column.default.value);
|
|
272
275
|
}
|
|
273
276
|
parts.push(`default(${value})`);
|
|
274
|
-
} else if (column.default
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
277
|
+
} else if ("dbSpecial" in column.default) {
|
|
278
|
+
// Database-level special functions: defaultTo(b => b.now())
|
|
279
|
+
if (column.default.dbSpecial === "now") {
|
|
280
|
+
parts.push("defaultNow()");
|
|
281
|
+
}
|
|
282
|
+
} else if ("runtime" in column.default) {
|
|
283
|
+
// Runtime defaults: defaultTo$()
|
|
284
|
+
if (column.default.runtime === "cuid") {
|
|
285
|
+
const idGen = ctx.idGeneratorImport ?? { name: "createId", from: "@fragno-dev/db/id" };
|
|
286
|
+
ctx.imports.addImport(idGen.name, idGen.from);
|
|
287
|
+
parts.push(`$defaultFn(() => ${idGen.name}())`);
|
|
288
|
+
} else if (column.default.runtime === "now") {
|
|
289
|
+
// Runtime-generated timestamp (not database-level)
|
|
290
|
+
parts.push("$defaultFn(() => new Date())");
|
|
291
|
+
}
|
|
292
|
+
// Note: Custom functions in defaultTo$(() => ...) are not supported in schema generation
|
|
280
293
|
}
|
|
281
294
|
}
|
|
282
295
|
|
|
@@ -297,7 +310,8 @@ function generateAllColumns(
|
|
|
297
310
|
// CONSTRAINT GENERATION
|
|
298
311
|
// ============================================================================
|
|
299
312
|
|
|
300
|
-
function generateForeignKeys(ctx: GeneratorContext, table: AnyTable): string[] {
|
|
313
|
+
function generateForeignKeys(ctx: GeneratorContext, table: AnyTable, namespace?: string): string[] {
|
|
314
|
+
const mapper = namespace ? createTableNameMapper(namespace) : undefined;
|
|
301
315
|
const keys: string[] = [];
|
|
302
316
|
|
|
303
317
|
for (const relation of Object.values(table.relations)) {
|
|
@@ -319,12 +333,19 @@ function generateForeignKeys(ctx: GeneratorContext, table: AnyTable): string[] {
|
|
|
319
333
|
if (isSelfReference) {
|
|
320
334
|
foreignColumns.push(`table.${actualRefCol}`);
|
|
321
335
|
} else {
|
|
322
|
-
|
|
336
|
+
// Suffix the foreign table reference with namespace if provided
|
|
337
|
+
const foreignTableRef =
|
|
338
|
+
mapper && namespace ? mapper.toPhysical(relation.table.ormName) : relation.table.ormName;
|
|
339
|
+
foreignColumns.push(`${foreignTableRef}.${actualRefCol}`);
|
|
323
340
|
}
|
|
324
341
|
}
|
|
325
342
|
|
|
326
343
|
ctx.imports.addImport("foreignKey", ctx.importSource);
|
|
327
|
-
|
|
344
|
+
// Include namespace in FK name to avoid collisions
|
|
345
|
+
const fkName =
|
|
346
|
+
namespace && mapper
|
|
347
|
+
? "fk_" + mapper.toPhysical(`${table.ormName}_${relation.table.ormName}_${relation.name}`)
|
|
348
|
+
: `${table.ormName}_${relation.table.ormName}_${relation.name}_fk`;
|
|
328
349
|
|
|
329
350
|
keys.push(`foreignKey({
|
|
330
351
|
columns: [${columns.join(", ")}],
|
|
@@ -336,53 +357,74 @@ function generateForeignKeys(ctx: GeneratorContext, table: AnyTable): string[] {
|
|
|
336
357
|
return keys;
|
|
337
358
|
}
|
|
338
359
|
|
|
339
|
-
function generateIndexes(ctx: GeneratorContext, table: AnyTable): string[] {
|
|
360
|
+
function generateIndexes(ctx: GeneratorContext, table: AnyTable, namespace?: string): string[] {
|
|
340
361
|
const indexes: string[] = [];
|
|
341
362
|
|
|
342
363
|
for (const idx of Object.values(table.indexes)) {
|
|
343
364
|
const columns = idx.columns.map((col) => `table.${col.ormName}`).join(", ");
|
|
344
365
|
|
|
366
|
+
// Include namespace in index name to avoid collisions
|
|
367
|
+
const indexName = namespace ? `${idx.name}_${namespace}` : idx.name;
|
|
368
|
+
|
|
345
369
|
if (idx.unique) {
|
|
346
370
|
ctx.imports.addImport("uniqueIndex", ctx.importSource);
|
|
347
|
-
indexes.push(`uniqueIndex("${
|
|
371
|
+
indexes.push(`uniqueIndex("${indexName}").on(${columns})`);
|
|
348
372
|
} else {
|
|
349
373
|
ctx.imports.addImport("index", ctx.importSource);
|
|
350
|
-
indexes.push(`index("${
|
|
374
|
+
indexes.push(`index("${indexName}").on(${columns})`);
|
|
351
375
|
}
|
|
352
376
|
}
|
|
353
377
|
|
|
354
378
|
return indexes;
|
|
355
379
|
}
|
|
356
380
|
|
|
357
|
-
function generateTableConstraints(
|
|
358
|
-
|
|
381
|
+
function generateTableConstraints(
|
|
382
|
+
ctx: GeneratorContext,
|
|
383
|
+
table: AnyTable,
|
|
384
|
+
namespace?: string,
|
|
385
|
+
): string[] {
|
|
386
|
+
return [...generateForeignKeys(ctx, table, namespace), ...generateIndexes(ctx, table, namespace)];
|
|
359
387
|
}
|
|
360
388
|
|
|
361
389
|
// ============================================================================
|
|
362
390
|
// TABLE GENERATION
|
|
363
391
|
// ============================================================================
|
|
364
392
|
|
|
365
|
-
function generateTable(
|
|
393
|
+
function generateTable(
|
|
394
|
+
ctx: GeneratorContext,
|
|
395
|
+
table: AnyTable,
|
|
396
|
+
customTypes: string[],
|
|
397
|
+
namespace?: string,
|
|
398
|
+
): string {
|
|
366
399
|
const tableFn = PROVIDER_TABLE_FUNCTIONS[ctx.provider];
|
|
367
400
|
ctx.imports.addImport(tableFn, ctx.importSource);
|
|
368
401
|
|
|
369
402
|
const columns = generateAllColumns(ctx, table, customTypes);
|
|
370
|
-
const constraints = generateTableConstraints(ctx, table);
|
|
403
|
+
const constraints = generateTableConstraints(ctx, table, namespace);
|
|
371
404
|
|
|
372
|
-
|
|
405
|
+
// Suffix table name with namespace if provided
|
|
406
|
+
const physicalTableName = namespace ? `${table.ormName}_${namespace}` : table.ormName;
|
|
407
|
+
// Sanitize namespace for use in export name (valid JS identifier)
|
|
408
|
+
const exportName = namespace ? `${table.ormName}_${sanitizeNamespace(namespace)}` : table.ormName;
|
|
409
|
+
|
|
410
|
+
const args: string[] = [`"${physicalTableName}"`, `{\n${columns.join(",\n")}\n}`];
|
|
373
411
|
|
|
374
412
|
if (constraints.length > 0) {
|
|
375
413
|
args.push(`(table) => [\n${ident(constraints.join(",\n"))}\n]`);
|
|
376
414
|
}
|
|
377
415
|
|
|
378
|
-
return `export const ${
|
|
416
|
+
return `export const ${exportName} = ${tableFn}(${args.join(", ")})`;
|
|
379
417
|
}
|
|
380
418
|
|
|
381
419
|
// ============================================================================
|
|
382
420
|
// RELATION GENERATION
|
|
383
421
|
// ============================================================================
|
|
384
422
|
|
|
385
|
-
function generateRelation(
|
|
423
|
+
function generateRelation(
|
|
424
|
+
ctx: GeneratorContext,
|
|
425
|
+
table: AnyTable,
|
|
426
|
+
namespace?: string,
|
|
427
|
+
): string | undefined {
|
|
386
428
|
const relations: string[] = [];
|
|
387
429
|
let hasOne = false;
|
|
388
430
|
let hasMany = false;
|
|
@@ -402,17 +444,29 @@ function generateRelation(ctx: GeneratorContext, table: AnyTable): string | unde
|
|
|
402
444
|
const fields: string[] = [];
|
|
403
445
|
const references: string[] = [];
|
|
404
446
|
|
|
447
|
+
// Use sanitized namespace for identifier references
|
|
448
|
+
const tableRef = namespace
|
|
449
|
+
? `${table.ormName}_${sanitizeNamespace(namespace)}`
|
|
450
|
+
: table.ormName;
|
|
451
|
+
const relatedTableRef = namespace
|
|
452
|
+
? `${relation.table.ormName}_${sanitizeNamespace(namespace)}`
|
|
453
|
+
: relation.table.ormName;
|
|
454
|
+
|
|
405
455
|
for (const [left, right] of relation.on) {
|
|
406
|
-
fields.push(`${
|
|
456
|
+
fields.push(`${tableRef}.${left}`);
|
|
407
457
|
// Relations reference internal IDs
|
|
408
458
|
const actualRight = right === "id" ? "_internalId" : right;
|
|
409
|
-
references.push(`${
|
|
459
|
+
references.push(`${relatedTableRef}.${actualRight}`);
|
|
410
460
|
}
|
|
411
461
|
|
|
412
462
|
options.push(`fields: [${fields.join(", ")}]`, `references: [${references.join(", ")}]`);
|
|
413
463
|
}
|
|
414
464
|
|
|
415
|
-
const
|
|
465
|
+
const relatedTableRef = namespace
|
|
466
|
+
? `${relation.table.ormName}_${sanitizeNamespace(namespace)}`
|
|
467
|
+
: relation.table.ormName;
|
|
468
|
+
|
|
469
|
+
const args: string[] = [relatedTableRef];
|
|
416
470
|
if (options.length > 0) {
|
|
417
471
|
args.push(`{\n${ident(options.join(",\n"))}\n}`);
|
|
418
472
|
}
|
|
@@ -434,12 +488,59 @@ function generateRelation(ctx: GeneratorContext, table: AnyTable): string | unde
|
|
|
434
488
|
}
|
|
435
489
|
const relationParams = params.length > 0 ? `{ ${params.join(", ")} }` : "{}";
|
|
436
490
|
|
|
491
|
+
const tableRef = namespace ? `${table.ormName}_${sanitizeNamespace(namespace)}` : table.ormName;
|
|
492
|
+
const relationsName = namespace
|
|
493
|
+
? `${table.ormName}_${sanitizeNamespace(namespace)}Relations`
|
|
494
|
+
: `${table.ormName}Relations`;
|
|
495
|
+
|
|
437
496
|
ctx.imports.addImport("relations", "drizzle-orm");
|
|
438
|
-
return `export const ${
|
|
497
|
+
return `export const ${relationsName} = relations(${tableRef}, (${relationParams}) => ({
|
|
439
498
|
${relations.join(",\n")}
|
|
440
499
|
}));`;
|
|
441
500
|
}
|
|
442
501
|
|
|
502
|
+
// ============================================================================
|
|
503
|
+
// UTILITIES
|
|
504
|
+
// ============================================================================
|
|
505
|
+
|
|
506
|
+
/**
|
|
507
|
+
* Sanitize a namespace to be a valid JavaScript identifier
|
|
508
|
+
* Replaces hyphens and other invalid characters with underscores
|
|
509
|
+
*/
|
|
510
|
+
function sanitizeNamespace(namespace: string): string {
|
|
511
|
+
return namespace.replace(/[^a-zA-Z0-9_]/g, "_");
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
/**
|
|
515
|
+
* Generate a schema export object for a fragment
|
|
516
|
+
* This groups all tables by their logical names for easier access
|
|
517
|
+
*/
|
|
518
|
+
function generateFragmentSchemaExport(schema: AnySchema, namespace: string): string {
|
|
519
|
+
const entries: string[] = [];
|
|
520
|
+
|
|
521
|
+
for (const table of Object.values(schema.tables)) {
|
|
522
|
+
const physicalExportName = namespace
|
|
523
|
+
? `${table.ormName}_${sanitizeNamespace(namespace)}`
|
|
524
|
+
: table.ormName;
|
|
525
|
+
|
|
526
|
+
if (namespace) {
|
|
527
|
+
const physicalTableName = namespace ? `${table.ormName}_${namespace}` : table.ormName;
|
|
528
|
+
// Use physical table name as key for Drizzle schema lookups
|
|
529
|
+
entries.push(` "${physicalTableName}": ${physicalExportName}`);
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
// Also provide logical name for convenience
|
|
533
|
+
entries.push(` ${table.ormName}: ${physicalExportName}`);
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
// Add schema version as a number
|
|
537
|
+
entries.push(` schemaVersion: ${schema.version}`);
|
|
538
|
+
|
|
539
|
+
const exportName = namespace ? `${sanitizeNamespace(namespace)}_schema` : "_schema";
|
|
540
|
+
|
|
541
|
+
return `export const ${exportName} = {\n${entries.join(",\n")}\n}`;
|
|
542
|
+
}
|
|
543
|
+
|
|
443
544
|
// ============================================================================
|
|
444
545
|
// MAIN GENERATION
|
|
445
546
|
// ============================================================================
|
|
@@ -454,28 +555,78 @@ export interface GenerateSchemaOptions {
|
|
|
454
555
|
};
|
|
455
556
|
}
|
|
456
557
|
|
|
558
|
+
/**
|
|
559
|
+
* Generate a settings table for storing fragment versions
|
|
560
|
+
*/
|
|
561
|
+
function generateSettingsTable(ctx: GeneratorContext): string {
|
|
562
|
+
// Use centralized settings schema
|
|
563
|
+
|
|
564
|
+
// Extract the table from the schema
|
|
565
|
+
const settingsTable =
|
|
566
|
+
settingsSchema.tables[SETTINGS_TABLE_NAME as keyof typeof settingsSchema.tables];
|
|
567
|
+
|
|
568
|
+
// Generate the table using the existing generateTable function
|
|
569
|
+
const customTypes: string[] = [];
|
|
570
|
+
return generateTable(ctx, settingsTable, customTypes);
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
/**
|
|
574
|
+
* Generate a schema file from one or more fragments with a shared settings table
|
|
575
|
+
*/
|
|
457
576
|
export function generateSchema(
|
|
458
|
-
schema: AnySchema,
|
|
577
|
+
fragments: { namespace: string; schema: AnySchema }[],
|
|
459
578
|
provider: SupportedProvider,
|
|
460
579
|
options?: GenerateSchemaOptions,
|
|
461
580
|
): string {
|
|
462
581
|
const ctx = createContext(provider, options?.idGeneratorImport);
|
|
463
582
|
const customTypes: string[] = [];
|
|
464
|
-
const
|
|
583
|
+
const sections: string[] = [];
|
|
584
|
+
|
|
585
|
+
// Generate settings table first
|
|
586
|
+
sections.push("");
|
|
587
|
+
sections.push("// ============================================================================");
|
|
588
|
+
sections.push("// Settings Table (shared across all fragments)");
|
|
589
|
+
sections.push("// ============================================================================");
|
|
590
|
+
sections.push("");
|
|
591
|
+
sections.push(generateSettingsTable(ctx));
|
|
592
|
+
sections.push("");
|
|
593
|
+
sections.push(`export const fragnoDbSettingSchemaVersion = ${settingsSchema.version};`);
|
|
594
|
+
|
|
595
|
+
// Generate each fragment's tables
|
|
596
|
+
for (const { namespace, schema } of fragments) {
|
|
597
|
+
const fragmentTables: string[] = [];
|
|
598
|
+
|
|
599
|
+
// Add section header
|
|
600
|
+
fragmentTables.push("");
|
|
601
|
+
fragmentTables.push(
|
|
602
|
+
"// ============================================================================",
|
|
603
|
+
);
|
|
604
|
+
fragmentTables.push(`// Fragment: ${namespace}`);
|
|
605
|
+
fragmentTables.push(
|
|
606
|
+
"// ============================================================================",
|
|
607
|
+
);
|
|
608
|
+
|
|
609
|
+
// Generate tables for this fragment
|
|
610
|
+
for (const table of Object.values(schema.tables)) {
|
|
611
|
+
const tableCode = generateTable(ctx, table, customTypes, namespace);
|
|
612
|
+
fragmentTables.push("");
|
|
613
|
+
fragmentTables.push(tableCode);
|
|
614
|
+
|
|
615
|
+
const relationCode = generateRelation(ctx, table, namespace);
|
|
616
|
+
if (relationCode) {
|
|
617
|
+
fragmentTables.push("");
|
|
618
|
+
fragmentTables.push(relationCode);
|
|
619
|
+
}
|
|
620
|
+
}
|
|
465
621
|
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
const tableCode = generateTable(ctx, table, customTypes);
|
|
470
|
-
tables.push(tableCode);
|
|
622
|
+
// Generate schema export object
|
|
623
|
+
fragmentTables.push("");
|
|
624
|
+
fragmentTables.push(generateFragmentSchemaExport(schema, namespace));
|
|
471
625
|
|
|
472
|
-
|
|
473
|
-
if (relationCode) {
|
|
474
|
-
tables.push(relationCode);
|
|
475
|
-
}
|
|
626
|
+
sections.push(...fragmentTables);
|
|
476
627
|
}
|
|
477
628
|
|
|
478
629
|
// Assemble final output
|
|
479
|
-
const lines: string[] = [ctx.imports.format(), ...customTypes, ...
|
|
480
|
-
return lines.join("\n
|
|
630
|
+
const lines: string[] = [ctx.imports.format(), ...customTypes, ...sections];
|
|
631
|
+
return lines.join("\n");
|
|
481
632
|
}
|
|
@@ -19,8 +19,14 @@ describe("generateSchema and migrate", () => {
|
|
|
19
19
|
.addColumn("age", column("integer").nullable())
|
|
20
20
|
.addColumn("isActive", column("bool").defaultTo(true))
|
|
21
21
|
.addColumn("bio", column("string").nullable())
|
|
22
|
-
.addColumn(
|
|
23
|
-
|
|
22
|
+
.addColumn(
|
|
23
|
+
"createdAt",
|
|
24
|
+
column("timestamp").defaultTo((b) => b.now()),
|
|
25
|
+
)
|
|
26
|
+
.addColumn(
|
|
27
|
+
"updatedAt",
|
|
28
|
+
column("timestamp").defaultTo((b) => b.now()),
|
|
29
|
+
)
|
|
24
30
|
.createIndex("idx_users_email", ["email"], { unique: true })
|
|
25
31
|
.createIndex("idx_users_name", ["name"])
|
|
26
32
|
.createIndex("idx_users_active", ["isActive"]);
|
|
@@ -40,7 +46,10 @@ describe("generateSchema and migrate", () => {
|
|
|
40
46
|
.addColumn("metadata", column("json").nullable())
|
|
41
47
|
.addColumn("rating", column("decimal").nullable())
|
|
42
48
|
.addColumn("thumbnail", column("binary").nullable())
|
|
43
|
-
.addColumn(
|
|
49
|
+
.addColumn(
|
|
50
|
+
"createdAt",
|
|
51
|
+
column("timestamp").defaultTo((b) => b.now()),
|
|
52
|
+
)
|
|
44
53
|
.createIndex("idx_posts_user", ["userId"])
|
|
45
54
|
.createIndex("idx_posts_title", ["title"])
|
|
46
55
|
.createIndex("idx_posts_slug", ["slug"], { unique: true })
|
|
@@ -53,7 +62,10 @@ describe("generateSchema and migrate", () => {
|
|
|
53
62
|
.addColumn("postId", referenceColumn())
|
|
54
63
|
.addColumn("userId", referenceColumn())
|
|
55
64
|
.addColumn("parentId", referenceColumn().nullable())
|
|
56
|
-
.addColumn(
|
|
65
|
+
.addColumn(
|
|
66
|
+
"createdAt",
|
|
67
|
+
column("timestamp").defaultTo((b) => b.now()),
|
|
68
|
+
)
|
|
57
69
|
.addColumn("editedAt", column("timestamp").nullable())
|
|
58
70
|
.addColumn("isDeleted", column("bool").defaultTo(false))
|
|
59
71
|
.createIndex("idx_comments_post", ["postId"])
|
|
@@ -77,7 +89,10 @@ describe("generateSchema and migrate", () => {
|
|
|
77
89
|
.addColumn("postId", referenceColumn())
|
|
78
90
|
.addColumn("tagId", referenceColumn())
|
|
79
91
|
.addColumn("order", column("integer").defaultTo(0))
|
|
80
|
-
.addColumn(
|
|
92
|
+
.addColumn(
|
|
93
|
+
"createdAt",
|
|
94
|
+
column("timestamp").defaultTo((b) => b.now()),
|
|
95
|
+
)
|
|
81
96
|
.createIndex("idx_postTags_post_tag", ["postId", "tagId"], { unique: true })
|
|
82
97
|
.createIndex("idx_postTags_tag", ["tagId"]);
|
|
83
98
|
})
|
|
@@ -135,7 +150,15 @@ describe("generateSchema and migrate", () => {
|
|
|
135
150
|
);
|
|
136
151
|
|
|
137
152
|
expect(migrationStatements.join("\n")).toMatchInlineSnapshot(`
|
|
138
|
-
"CREATE TABLE "
|
|
153
|
+
"CREATE TABLE "fragno_db_settings" (
|
|
154
|
+
"id" varchar(30) NOT NULL,
|
|
155
|
+
"key" text NOT NULL,
|
|
156
|
+
"value" text NOT NULL,
|
|
157
|
+
"_internalId" bigserial PRIMARY KEY NOT NULL,
|
|
158
|
+
"_version" integer DEFAULT 0 NOT NULL
|
|
159
|
+
);
|
|
160
|
+
|
|
161
|
+
CREATE TABLE "users" (
|
|
139
162
|
"id" varchar(30) NOT NULL,
|
|
140
163
|
"name" text NOT NULL,
|
|
141
164
|
"email" text NOT NULL,
|
|
@@ -207,6 +230,7 @@ describe("generateSchema and migrate", () => {
|
|
|
207
230
|
ALTER TABLE "comments" ADD CONSTRAINT "comments_comments_parent_fk" FOREIGN KEY ("parentId") REFERENCES "public"."comments"("_internalId") ON DELETE no action ON UPDATE no action;
|
|
208
231
|
ALTER TABLE "postTags" ADD CONSTRAINT "postTags_posts_post_fk" FOREIGN KEY ("postId") REFERENCES "public"."posts"("_internalId") ON DELETE no action ON UPDATE no action;
|
|
209
232
|
ALTER TABLE "postTags" ADD CONSTRAINT "postTags_tags_tag_fk" FOREIGN KEY ("tagId") REFERENCES "public"."tags"("_internalId") ON DELETE no action ON UPDATE no action;
|
|
233
|
+
CREATE UNIQUE INDEX "unique_key" ON "fragno_db_settings" USING btree ("key");
|
|
210
234
|
CREATE UNIQUE INDEX "idx_users_email" ON "users" USING btree ("email");
|
|
211
235
|
CREATE INDEX "idx_users_name" ON "users" USING btree ("name");
|
|
212
236
|
CREATE INDEX "idx_users_active" ON "users" USING btree ("isActive");
|
|
@@ -13,10 +13,40 @@ export type DBType = MySQL.MySqlDatabase<
|
|
|
13
13
|
export function parseDrizzle(drizzle: unknown) {
|
|
14
14
|
const db = drizzle as DBType;
|
|
15
15
|
const drizzleTables = db._.fullSchema as Record<string, TableType>;
|
|
16
|
-
if (!drizzleTables || Object.keys(drizzleTables).length === 0)
|
|
16
|
+
if (!drizzleTables || Object.keys(drizzleTables).length === 0) {
|
|
17
17
|
throw new Error(
|
|
18
18
|
"Drizzle adapter requires query mode, make sure to configure it following their guide: https://orm.drizzle.team/docs/rqb.",
|
|
19
19
|
);
|
|
20
|
+
}
|
|
20
21
|
|
|
21
22
|
return [db, drizzleTables] as const;
|
|
22
23
|
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Maps logical table names (used by fragment authors) to physical table names (with namespace suffix)
|
|
27
|
+
*/
|
|
28
|
+
export interface TableNameMapper {
|
|
29
|
+
toPhysical(logicalName: string): string;
|
|
30
|
+
toLogical(physicalName: string): string;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Creates a table name mapper for a given namespace.
|
|
35
|
+
* Physical names have format: {logicalName}_{namespace}
|
|
36
|
+
*/
|
|
37
|
+
export function createTableNameMapper(namespace: string): TableNameMapper {
|
|
38
|
+
return {
|
|
39
|
+
toPhysical: (logicalName: string) => `${logicalName}_${namespace}`,
|
|
40
|
+
toLogical: (physicalName: string) => {
|
|
41
|
+
if (physicalName.endsWith(`_${namespace}`)) {
|
|
42
|
+
return physicalName.slice(0, -(namespace.length + 1));
|
|
43
|
+
}
|
|
44
|
+
return physicalName;
|
|
45
|
+
},
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export interface DrizzleResult {
|
|
50
|
+
rows: Record<string, unknown>[];
|
|
51
|
+
affectedRows: number;
|
|
52
|
+
}
|
|
@@ -18,6 +18,7 @@ export async function writeAndLoadSchema(
|
|
|
18
18
|
testFileName: string,
|
|
19
19
|
schema: Schema,
|
|
20
20
|
dialect: SupportedProvider,
|
|
21
|
+
namespace?: string,
|
|
21
22
|
) {
|
|
22
23
|
// Create test-specific directory inside _generated
|
|
23
24
|
const baseDir = join(import.meta.dirname, "_generated");
|
|
@@ -37,7 +38,8 @@ export async function writeAndLoadSchema(
|
|
|
37
38
|
);
|
|
38
39
|
|
|
39
40
|
// Generate and write the Drizzle schema to file
|
|
40
|
-
|
|
41
|
+
// Use empty namespace for tests to avoid table name prefixing
|
|
42
|
+
const drizzleSchemaTs = generateSchema([{ namespace: namespace ?? "", schema }], dialect);
|
|
41
43
|
await writeFile(schemaFilePath, drizzleSchemaTs, "utf-8");
|
|
42
44
|
|
|
43
45
|
// Dynamically import the generated schema (with cache busting)
|
|
@@ -116,57 +116,55 @@ describe("KyselyAdapter PGLite", () => {
|
|
|
116
116
|
expect(schemaVersion).toBeUndefined();
|
|
117
117
|
|
|
118
118
|
const migrator = adapter.createMigrationEngine(testSchema, "test");
|
|
119
|
-
const preparedMigration = await migrator.prepareMigration(
|
|
119
|
+
const preparedMigration = await migrator.prepareMigration({
|
|
120
|
+
updateSettings: false,
|
|
121
|
+
});
|
|
120
122
|
assert(preparedMigration.getSQL);
|
|
121
123
|
|
|
122
124
|
expect(preparedMigration.getSQL()).toMatchInlineSnapshot(`
|
|
123
|
-
"create table "
|
|
124
|
-
|
|
125
|
-
insert into "fragno_db_settings" ("key", "value") values ('test.schema_version', '12');
|
|
126
|
-
|
|
127
|
-
create table "users" ("id" varchar(30) not null unique, "name" text not null, "age" integer, "_internalId" bigserial not null primary key, "_version" integer default 0 not null);
|
|
125
|
+
"create table "users_test" ("id" varchar(30) not null unique, "name" text not null, "age" integer, "_internalId" bigserial not null primary key, "_version" integer default 0 not null);
|
|
128
126
|
|
|
129
|
-
create index "name_idx" on "
|
|
127
|
+
create index "name_idx" on "users_test" ("name");
|
|
130
128
|
|
|
131
|
-
create index "age_idx" on "
|
|
129
|
+
create index "age_idx" on "users_test" ("age");
|
|
132
130
|
|
|
133
|
-
create table "
|
|
131
|
+
create table "emails_test" ("id" varchar(30) not null unique, "user_id" bigint not null, "email" text not null, "is_primary" boolean default false not null, "_internalId" bigserial not null primary key, "_version" integer default 0 not null);
|
|
134
132
|
|
|
135
|
-
create unique index "unique_email" on "
|
|
133
|
+
create unique index "unique_email" on "emails_test" ("email");
|
|
136
134
|
|
|
137
|
-
create index "user_emails" on "
|
|
135
|
+
create index "user_emails" on "emails_test" ("user_id");
|
|
138
136
|
|
|
139
|
-
create table "
|
|
137
|
+
create table "posts_test" ("id" varchar(30) not null unique, "user_id" bigint not null, "title" text not null, "content" text not null, "_internalId" bigserial not null primary key, "_version" integer default 0 not null);
|
|
140
138
|
|
|
141
|
-
create index "posts_user_idx" on "
|
|
139
|
+
create index "posts_user_idx" on "posts_test" ("user_id");
|
|
142
140
|
|
|
143
|
-
create table "
|
|
141
|
+
create table "tags_test" ("id" varchar(30) not null unique, "name" text not null, "_internalId" bigserial not null primary key, "_version" integer default 0 not null);
|
|
144
142
|
|
|
145
|
-
create index "tag_name" on "
|
|
143
|
+
create index "tag_name" on "tags_test" ("name");
|
|
146
144
|
|
|
147
|
-
create table "
|
|
145
|
+
create table "post_tags_test" ("id" varchar(30) not null unique, "post_id" bigint not null, "tag_id" bigint not null, "_internalId" bigserial not null primary key, "_version" integer default 0 not null);
|
|
148
146
|
|
|
149
|
-
create index "pt_post" on "
|
|
147
|
+
create index "pt_post" on "post_tags_test" ("post_id");
|
|
150
148
|
|
|
151
|
-
create index "pt_tag" on "
|
|
149
|
+
create index "pt_tag" on "post_tags_test" ("tag_id");
|
|
152
150
|
|
|
153
|
-
create table "
|
|
151
|
+
create table "comments_test" ("id" varchar(30) not null unique, "post_id" bigint not null, "user_id" bigint not null, "text" text not null, "_internalId" bigserial not null primary key, "_version" integer default 0 not null);
|
|
154
152
|
|
|
155
|
-
create index "comments_post_idx" on "
|
|
153
|
+
create index "comments_post_idx" on "comments_test" ("post_id");
|
|
156
154
|
|
|
157
|
-
create index "comments_user_idx" on "
|
|
155
|
+
create index "comments_user_idx" on "comments_test" ("user_id");
|
|
158
156
|
|
|
159
|
-
alter table "
|
|
157
|
+
alter table "emails_test" add constraint "emails_users_user_fk" foreign key ("user_id") references "users_test" ("_internalId") on delete restrict on update restrict;
|
|
160
158
|
|
|
161
|
-
alter table "
|
|
159
|
+
alter table "posts_test" add constraint "posts_users_author_fk" foreign key ("user_id") references "users_test" ("_internalId") on delete restrict on update restrict;
|
|
162
160
|
|
|
163
|
-
alter table "
|
|
161
|
+
alter table "post_tags_test" add constraint "post_tags_posts_post_fk" foreign key ("post_id") references "posts_test" ("_internalId") on delete restrict on update restrict;
|
|
164
162
|
|
|
165
|
-
alter table "
|
|
163
|
+
alter table "post_tags_test" add constraint "post_tags_tags_tag_fk" foreign key ("tag_id") references "tags_test" ("_internalId") on delete restrict on update restrict;
|
|
166
164
|
|
|
167
|
-
alter table "
|
|
165
|
+
alter table "comments_test" add constraint "comments_posts_post_fk" foreign key ("post_id") references "posts_test" ("_internalId") on delete restrict on update restrict;
|
|
168
166
|
|
|
169
|
-
alter table "
|
|
167
|
+
alter table "comments_test" add constraint "comments_users_commenter_fk" foreign key ("user_id") references "users_test" ("_internalId") on delete restrict on update restrict;"
|
|
170
168
|
`);
|
|
171
169
|
|
|
172
170
|
await preparedMigration.execute();
|