@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
@@ -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.runtime === "auto") {
275
- const idGen = ctx.idGeneratorImport ?? { name: "createId", from: "@fragno-dev/db/id" };
276
- ctx.imports.addImport(idGen.name, idGen.from);
277
- parts.push(`$defaultFn(() => ${idGen.name}())`);
278
- } else if (column.default.runtime === "now") {
279
- parts.push("defaultNow()");
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
- foreignColumns.push(`${relation.table.ormName}.${actualRefCol}`);
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
- const fkName = `${table.ormName}_${relation.table.ormName}_${relation.name}_fk`;
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("${idx.name}").on(${columns})`);
371
+ indexes.push(`uniqueIndex("${indexName}").on(${columns})`);
348
372
  } else {
349
373
  ctx.imports.addImport("index", ctx.importSource);
350
- indexes.push(`index("${idx.name}").on(${columns})`);
374
+ indexes.push(`index("${indexName}").on(${columns})`);
351
375
  }
352
376
  }
353
377
 
354
378
  return indexes;
355
379
  }
356
380
 
357
- function generateTableConstraints(ctx: GeneratorContext, table: AnyTable): string[] {
358
- return [...generateForeignKeys(ctx, table), ...generateIndexes(ctx, table)];
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(ctx: GeneratorContext, table: AnyTable, customTypes: string[]): string {
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
- const args: string[] = [`"${table.ormName}"`, `{\n${columns.join(",\n")}\n}`];
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 ${table.ormName} = ${tableFn}(${args.join(", ")})`;
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(ctx: GeneratorContext, table: AnyTable): string | undefined {
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(`${table.ormName}.${left}`);
456
+ fields.push(`${tableRef}.${left}`);
407
457
  // Relations reference internal IDs
408
458
  const actualRight = right === "id" ? "_internalId" : right;
409
- references.push(`${relation.table.ormName}.${actualRight}`);
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 args: string[] = [relation.table.ormName];
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 ${table.ormName}Relations = relations(${table.ormName}, (${relationParams}) => ({
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 tables: string[] = [];
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
- // Generate tables and collect custom types
467
- for (const table of Object.values(schema.tables)) {
468
- // Custom types might be generated during column processing
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
- const relationCode = generateRelation(ctx, table);
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, ...tables];
480
- return lines.join("\n\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("createdAt", column("timestamp").defaultTo$("now"))
23
- .addColumn("updatedAt", column("timestamp").defaultTo$("now"))
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("createdAt", column("timestamp").defaultTo$("now"))
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("createdAt", column("timestamp").defaultTo$("now"))
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("createdAt", column("timestamp").defaultTo$("now"))
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 "users" (
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
- const drizzleSchemaTs = generateSchema(schema, dialect);
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 "fragno_db_settings" ("key" varchar(255) primary key, "value" text not null);
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 "users" ("name");
127
+ create index "name_idx" on "users_test" ("name");
130
128
 
131
- create index "age_idx" on "users" ("age");
129
+ create index "age_idx" on "users_test" ("age");
132
130
 
133
- create table "emails" ("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);
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 "emails" ("email");
133
+ create unique index "unique_email" on "emails_test" ("email");
136
134
 
137
- create index "user_emails" on "emails" ("user_id");
135
+ create index "user_emails" on "emails_test" ("user_id");
138
136
 
139
- create table "posts" ("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);
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 "posts" ("user_id");
139
+ create index "posts_user_idx" on "posts_test" ("user_id");
142
140
 
143
- create table "tags" ("id" varchar(30) not null unique, "name" text not null, "_internalId" bigserial not null primary key, "_version" integer default 0 not null);
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 "tags" ("name");
143
+ create index "tag_name" on "tags_test" ("name");
146
144
 
147
- create table "post_tags" ("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);
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 "post_tags" ("post_id");
147
+ create index "pt_post" on "post_tags_test" ("post_id");
150
148
 
151
- create index "pt_tag" on "post_tags" ("tag_id");
149
+ create index "pt_tag" on "post_tags_test" ("tag_id");
152
150
 
153
- create table "comments" ("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);
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 "comments" ("post_id");
153
+ create index "comments_post_idx" on "comments_test" ("post_id");
156
154
 
157
- create index "comments_user_idx" on "comments" ("user_id");
155
+ create index "comments_user_idx" on "comments_test" ("user_id");
158
156
 
159
- alter table "emails" add constraint "emails_users_user_fk" foreign key ("user_id") references "users" ("_internalId") on delete restrict on update restrict;
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 "posts" add constraint "posts_users_author_fk" foreign key ("user_id") references "users" ("_internalId") on delete restrict on update restrict;
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 "post_tags" add constraint "post_tags_posts_post_fk" foreign key ("post_id") references "posts" ("_internalId") on delete restrict on update restrict;
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 "post_tags" add constraint "post_tags_tags_tag_fk" foreign key ("tag_id") references "tags" ("_internalId") on delete restrict on update restrict;
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 "comments" add constraint "comments_posts_post_fk" foreign key ("post_id") references "posts" ("_internalId") on delete restrict on update restrict;
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 "comments" add constraint "comments_users_commenter_fk" foreign key ("user_id") references "users" ("_internalId") on delete restrict on update restrict;"
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();