@fragno-dev/db 0.0.1 → 0.1.0

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 (200) hide show
  1. package/.turbo/turbo-build.log +137 -13
  2. package/.turbo/turbo-test.log +36 -0
  3. package/CHANGELOG.md +7 -0
  4. package/dist/adapters/adapters.d.ts +18 -0
  5. package/dist/adapters/adapters.d.ts.map +1 -0
  6. package/dist/adapters/drizzle/drizzle-adapter.d.ts +21 -0
  7. package/dist/adapters/drizzle/drizzle-adapter.d.ts.map +1 -0
  8. package/dist/adapters/drizzle/drizzle-adapter.js +62 -0
  9. package/dist/adapters/drizzle/drizzle-adapter.js.map +1 -0
  10. package/dist/adapters/drizzle/drizzle-query.d.ts +17 -0
  11. package/dist/adapters/drizzle/drizzle-query.d.ts.map +1 -0
  12. package/dist/adapters/drizzle/drizzle-query.js +139 -0
  13. package/dist/adapters/drizzle/drizzle-query.js.map +1 -0
  14. package/dist/adapters/drizzle/drizzle-uow-compiler.d.ts +9 -0
  15. package/dist/adapters/drizzle/drizzle-uow-compiler.d.ts.map +1 -0
  16. package/dist/adapters/drizzle/drizzle-uow-compiler.js +300 -0
  17. package/dist/adapters/drizzle/drizzle-uow-compiler.js.map +1 -0
  18. package/dist/adapters/drizzle/drizzle-uow-decoder.js +82 -0
  19. package/dist/adapters/drizzle/drizzle-uow-decoder.js.map +1 -0
  20. package/dist/adapters/drizzle/drizzle-uow-executor.js +125 -0
  21. package/dist/adapters/drizzle/drizzle-uow-executor.js.map +1 -0
  22. package/dist/adapters/drizzle/generate.js +273 -0
  23. package/dist/adapters/drizzle/generate.js.map +1 -0
  24. package/dist/adapters/drizzle/join-column-utils.js +28 -0
  25. package/dist/adapters/drizzle/join-column-utils.js.map +1 -0
  26. package/dist/adapters/drizzle/shared.js +11 -0
  27. package/dist/adapters/drizzle/shared.js.map +1 -0
  28. package/dist/adapters/kysely/kysely-adapter.d.ts +23 -0
  29. package/dist/adapters/kysely/kysely-adapter.d.ts.map +1 -0
  30. package/dist/adapters/kysely/kysely-adapter.js +119 -0
  31. package/dist/adapters/kysely/kysely-adapter.js.map +1 -0
  32. package/dist/adapters/kysely/kysely-query-builder.js +306 -0
  33. package/dist/adapters/kysely/kysely-query-builder.js.map +1 -0
  34. package/dist/adapters/kysely/kysely-query-compiler.js +67 -0
  35. package/dist/adapters/kysely/kysely-query-compiler.js.map +1 -0
  36. package/dist/adapters/kysely/kysely-query.js +158 -0
  37. package/dist/adapters/kysely/kysely-query.js.map +1 -0
  38. package/dist/adapters/kysely/kysely-uow-compiler.js +139 -0
  39. package/dist/adapters/kysely/kysely-uow-compiler.js.map +1 -0
  40. package/dist/adapters/kysely/kysely-uow-executor.js +89 -0
  41. package/dist/adapters/kysely/kysely-uow-executor.js.map +1 -0
  42. package/dist/adapters/kysely/migration/execute.js +176 -0
  43. package/dist/adapters/kysely/migration/execute.js.map +1 -0
  44. package/dist/fragment.d.ts +54 -0
  45. package/dist/fragment.d.ts.map +1 -0
  46. package/dist/fragment.js +92 -0
  47. package/dist/fragment.js.map +1 -0
  48. package/dist/id.d.ts +2 -0
  49. package/dist/migration-engine/auto-from-schema.js +116 -0
  50. package/dist/migration-engine/auto-from-schema.js.map +1 -0
  51. package/dist/migration-engine/create.d.ts +41 -0
  52. package/dist/migration-engine/create.d.ts.map +1 -0
  53. package/dist/migration-engine/create.js +58 -0
  54. package/dist/migration-engine/create.js.map +1 -0
  55. package/dist/migration-engine/shared.d.ts +90 -0
  56. package/dist/migration-engine/shared.d.ts.map +1 -0
  57. package/dist/migration-engine/shared.js +8 -0
  58. package/dist/migration-engine/shared.js.map +1 -0
  59. package/dist/mod.d.ts +55 -2
  60. package/dist/mod.d.ts.map +1 -1
  61. package/dist/mod.js +111 -2
  62. package/dist/mod.js.map +1 -1
  63. package/dist/node_modules/.bun/drizzle-orm@0.44.6_4fae081eecb963e2/node_modules/drizzle-orm/column-builder.js +108 -0
  64. package/dist/node_modules/.bun/drizzle-orm@0.44.6_4fae081eecb963e2/node_modules/drizzle-orm/column-builder.js.map +1 -0
  65. package/dist/node_modules/.bun/drizzle-orm@0.44.6_4fae081eecb963e2/node_modules/drizzle-orm/column.js +55 -0
  66. package/dist/node_modules/.bun/drizzle-orm@0.44.6_4fae081eecb963e2/node_modules/drizzle-orm/column.js.map +1 -0
  67. package/dist/node_modules/.bun/drizzle-orm@0.44.6_4fae081eecb963e2/node_modules/drizzle-orm/entity.js +18 -0
  68. package/dist/node_modules/.bun/drizzle-orm@0.44.6_4fae081eecb963e2/node_modules/drizzle-orm/entity.js.map +1 -0
  69. package/dist/node_modules/.bun/drizzle-orm@0.44.6_4fae081eecb963e2/node_modules/drizzle-orm/pg-core/columns/common.js +183 -0
  70. package/dist/node_modules/.bun/drizzle-orm@0.44.6_4fae081eecb963e2/node_modules/drizzle-orm/pg-core/columns/common.js.map +1 -0
  71. package/dist/node_modules/.bun/drizzle-orm@0.44.6_4fae081eecb963e2/node_modules/drizzle-orm/pg-core/columns/enum.js +58 -0
  72. package/dist/node_modules/.bun/drizzle-orm@0.44.6_4fae081eecb963e2/node_modules/drizzle-orm/pg-core/columns/enum.js.map +1 -0
  73. package/dist/node_modules/.bun/drizzle-orm@0.44.6_4fae081eecb963e2/node_modules/drizzle-orm/pg-core/foreign-keys.js +68 -0
  74. package/dist/node_modules/.bun/drizzle-orm@0.44.6_4fae081eecb963e2/node_modules/drizzle-orm/pg-core/foreign-keys.js.map +1 -0
  75. package/dist/node_modules/.bun/drizzle-orm@0.44.6_4fae081eecb963e2/node_modules/drizzle-orm/pg-core/unique-constraint.js +56 -0
  76. package/dist/node_modules/.bun/drizzle-orm@0.44.6_4fae081eecb963e2/node_modules/drizzle-orm/pg-core/unique-constraint.js.map +1 -0
  77. package/dist/node_modules/.bun/drizzle-orm@0.44.6_4fae081eecb963e2/node_modules/drizzle-orm/pg-core/utils/array.js +65 -0
  78. package/dist/node_modules/.bun/drizzle-orm@0.44.6_4fae081eecb963e2/node_modules/drizzle-orm/pg-core/utils/array.js.map +1 -0
  79. package/dist/node_modules/.bun/drizzle-orm@0.44.6_4fae081eecb963e2/node_modules/drizzle-orm/sql/expressions/conditions.js +81 -0
  80. package/dist/node_modules/.bun/drizzle-orm@0.44.6_4fae081eecb963e2/node_modules/drizzle-orm/sql/expressions/conditions.js.map +1 -0
  81. package/dist/node_modules/.bun/drizzle-orm@0.44.6_4fae081eecb963e2/node_modules/drizzle-orm/sql/expressions/select.js +13 -0
  82. package/dist/node_modules/.bun/drizzle-orm@0.44.6_4fae081eecb963e2/node_modules/drizzle-orm/sql/expressions/select.js.map +1 -0
  83. package/dist/node_modules/.bun/drizzle-orm@0.44.6_4fae081eecb963e2/node_modules/drizzle-orm/sql/functions/aggregate.js +10 -0
  84. package/dist/node_modules/.bun/drizzle-orm@0.44.6_4fae081eecb963e2/node_modules/drizzle-orm/sql/functions/aggregate.js.map +1 -0
  85. package/dist/node_modules/.bun/drizzle-orm@0.44.6_4fae081eecb963e2/node_modules/drizzle-orm/sql/sql.js +372 -0
  86. package/dist/node_modules/.bun/drizzle-orm@0.44.6_4fae081eecb963e2/node_modules/drizzle-orm/sql/sql.js.map +1 -0
  87. package/dist/node_modules/.bun/drizzle-orm@0.44.6_4fae081eecb963e2/node_modules/drizzle-orm/subquery.js +23 -0
  88. package/dist/node_modules/.bun/drizzle-orm@0.44.6_4fae081eecb963e2/node_modules/drizzle-orm/subquery.js.map +1 -0
  89. package/dist/node_modules/.bun/drizzle-orm@0.44.6_4fae081eecb963e2/node_modules/drizzle-orm/table.js +62 -0
  90. package/dist/node_modules/.bun/drizzle-orm@0.44.6_4fae081eecb963e2/node_modules/drizzle-orm/table.js.map +1 -0
  91. package/dist/node_modules/.bun/drizzle-orm@0.44.6_4fae081eecb963e2/node_modules/drizzle-orm/table.utils.js +6 -0
  92. package/dist/node_modules/.bun/drizzle-orm@0.44.6_4fae081eecb963e2/node_modules/drizzle-orm/table.utils.js.map +1 -0
  93. package/dist/node_modules/.bun/drizzle-orm@0.44.6_4fae081eecb963e2/node_modules/drizzle-orm/tracing-utils.js +8 -0
  94. package/dist/node_modules/.bun/drizzle-orm@0.44.6_4fae081eecb963e2/node_modules/drizzle-orm/tracing-utils.js.map +1 -0
  95. package/dist/node_modules/.bun/drizzle-orm@0.44.6_4fae081eecb963e2/node_modules/drizzle-orm/tracing.js +8 -0
  96. package/dist/node_modules/.bun/drizzle-orm@0.44.6_4fae081eecb963e2/node_modules/drizzle-orm/tracing.js.map +1 -0
  97. package/dist/node_modules/.bun/drizzle-orm@0.44.6_4fae081eecb963e2/node_modules/drizzle-orm/view-common.js +6 -0
  98. package/dist/node_modules/.bun/drizzle-orm@0.44.6_4fae081eecb963e2/node_modules/drizzle-orm/view-common.js.map +1 -0
  99. package/dist/query/condition-builder.d.ts +41 -0
  100. package/dist/query/condition-builder.d.ts.map +1 -0
  101. package/dist/query/condition-builder.js +93 -0
  102. package/dist/query/condition-builder.js.map +1 -0
  103. package/dist/query/cursor.d.ts +88 -0
  104. package/dist/query/cursor.d.ts.map +1 -0
  105. package/dist/query/cursor.js +103 -0
  106. package/dist/query/cursor.js.map +1 -0
  107. package/dist/query/orm/orm.d.ts +18 -0
  108. package/dist/query/orm/orm.d.ts.map +1 -0
  109. package/dist/query/orm/orm.js +48 -0
  110. package/dist/query/orm/orm.js.map +1 -0
  111. package/dist/query/query.d.ts +79 -0
  112. package/dist/query/query.d.ts.map +1 -0
  113. package/dist/query/query.js +1 -0
  114. package/dist/query/result-transform.js +155 -0
  115. package/dist/query/result-transform.js.map +1 -0
  116. package/dist/query/unit-of-work.d.ts +435 -0
  117. package/dist/query/unit-of-work.d.ts.map +1 -0
  118. package/dist/query/unit-of-work.js +549 -0
  119. package/dist/query/unit-of-work.js.map +1 -0
  120. package/dist/schema/create.d.ts +273 -116
  121. package/dist/schema/create.d.ts.map +1 -1
  122. package/dist/schema/create.js +410 -222
  123. package/dist/schema/create.js.map +1 -1
  124. package/dist/schema/serialize.js +101 -0
  125. package/dist/schema/serialize.js.map +1 -0
  126. package/dist/schema-generator/schema-generator.d.ts +15 -0
  127. package/dist/schema-generator/schema-generator.d.ts.map +1 -0
  128. package/dist/shared/providers.d.ts +6 -0
  129. package/dist/shared/providers.d.ts.map +1 -0
  130. package/dist/util/import-generator.js +26 -0
  131. package/dist/util/import-generator.js.map +1 -0
  132. package/dist/util/parse.js +15 -0
  133. package/dist/util/parse.js.map +1 -0
  134. package/dist/util/types.d.ts +8 -0
  135. package/dist/util/types.d.ts.map +1 -0
  136. package/package.json +63 -2
  137. package/src/adapters/adapters.ts +22 -0
  138. package/src/adapters/drizzle/drizzle-adapter-pglite.test.ts +433 -0
  139. package/src/adapters/drizzle/drizzle-adapter.test.ts +122 -0
  140. package/src/adapters/drizzle/drizzle-adapter.ts +118 -0
  141. package/src/adapters/drizzle/drizzle-query.ts +234 -0
  142. package/src/adapters/drizzle/drizzle-uow-compiler.test.ts +1084 -0
  143. package/src/adapters/drizzle/drizzle-uow-compiler.ts +546 -0
  144. package/src/adapters/drizzle/drizzle-uow-decoder.ts +165 -0
  145. package/src/adapters/drizzle/drizzle-uow-executor.ts +213 -0
  146. package/src/adapters/drizzle/generate.test.ts +643 -0
  147. package/src/adapters/drizzle/generate.ts +481 -0
  148. package/src/adapters/drizzle/join-column-utils.test.ts +79 -0
  149. package/src/adapters/drizzle/join-column-utils.ts +39 -0
  150. package/src/adapters/drizzle/migrate-drizzle.test.ts +226 -0
  151. package/src/adapters/drizzle/shared.ts +22 -0
  152. package/src/adapters/drizzle/test-utils.ts +56 -0
  153. package/src/adapters/kysely/kysely-adapter-pglite.test.ts +789 -0
  154. package/src/adapters/kysely/kysely-adapter.ts +196 -0
  155. package/src/adapters/kysely/kysely-query-builder.test.ts +1344 -0
  156. package/src/adapters/kysely/kysely-query-builder.ts +611 -0
  157. package/src/adapters/kysely/kysely-query-compiler.ts +124 -0
  158. package/src/adapters/kysely/kysely-query.ts +254 -0
  159. package/src/adapters/kysely/kysely-uow-compiler.test.ts +916 -0
  160. package/src/adapters/kysely/kysely-uow-compiler.ts +271 -0
  161. package/src/adapters/kysely/kysely-uow-executor.ts +149 -0
  162. package/src/adapters/kysely/kysely-uow-joins.test.ts +811 -0
  163. package/src/adapters/kysely/migration/execute-mysql.test.ts +1173 -0
  164. package/src/adapters/kysely/migration/execute-postgres.test.ts +2657 -0
  165. package/src/adapters/kysely/migration/execute.ts +382 -0
  166. package/src/adapters/kysely/migration/kysely-migrator.test.ts +197 -0
  167. package/src/fragment.test.ts +287 -0
  168. package/src/fragment.ts +198 -0
  169. package/src/migration-engine/auto-from-schema.test.ts +118 -58
  170. package/src/migration-engine/auto-from-schema.ts +103 -32
  171. package/src/migration-engine/create.test.ts +34 -46
  172. package/src/migration-engine/create.ts +41 -26
  173. package/src/migration-engine/shared.ts +26 -6
  174. package/src/mod.ts +197 -1
  175. package/src/query/condition-builder.test.ts +379 -0
  176. package/src/query/condition-builder.ts +294 -0
  177. package/src/query/cursor.test.ts +296 -0
  178. package/src/query/cursor.ts +147 -0
  179. package/src/query/orm/orm.ts +92 -0
  180. package/src/query/query-type.test.ts +429 -0
  181. package/src/query/query.ts +200 -0
  182. package/src/query/result-transform.test.ts +795 -0
  183. package/src/query/result-transform.ts +247 -0
  184. package/src/query/unit-of-work-types.test.ts +192 -0
  185. package/src/query/unit-of-work.test.ts +947 -0
  186. package/src/query/unit-of-work.ts +1199 -0
  187. package/src/schema/create.test.ts +653 -110
  188. package/src/schema/create.ts +708 -337
  189. package/src/schema/serialize.test.ts +559 -0
  190. package/src/schema/serialize.ts +359 -0
  191. package/src/schema-generator/schema-generator.ts +12 -0
  192. package/src/shared/config.ts +0 -8
  193. package/src/util/import-generator.ts +28 -0
  194. package/src/util/parse.ts +16 -0
  195. package/src/util/types.ts +4 -0
  196. package/tsconfig.json +1 -1
  197. package/tsdown.config.ts +11 -1
  198. package/vitest.config.ts +3 -0
  199. /package/dist/{cuid.js → id.js} +0 -0
  200. /package/src/{cuid.ts → id.ts} +0 -0
@@ -1,29 +1,43 @@
1
- import { describe, expect, it } from "vitest";
2
- import { column, compileForeignKey, idColumn, referenceColumn, schema, table } from "./create";
1
+ import { describe, expect, expectTypeOf, it } from "vitest";
2
+ import {
3
+ column,
4
+ FragnoId,
5
+ FragnoReference,
6
+ idColumn,
7
+ referenceColumn,
8
+ schema,
9
+ SchemaBuilder,
10
+ } from "./create";
11
+ import type { RawColumnValues, TableToColumnValues, TableToInsertValues } from "../query/query";
3
12
 
4
13
  describe("create", () => {
5
14
  it("should create a table with columns using callback pattern", () => {
6
- const userTable = table("users", (t) => {
7
- return t
8
- .addColumn("id", idColumn())
9
- .addColumn("name", column("string"))
10
- .addColumn("email", column("string"))
11
- .createIndex("unique_email", ["email"], { unique: true })
12
- .addColumn("age", column("integer").nullable());
15
+ const userSchema = schema((s) => {
16
+ return s.addTable("users", (t) => {
17
+ return t
18
+ .addColumn("id", idColumn())
19
+ .addColumn("name", column("string"))
20
+ .addColumn("email", column("string"))
21
+ .createIndex("unique_email", ["email"], { unique: true })
22
+ .addColumn("age", column("integer").nullable());
23
+ });
13
24
  });
14
25
 
26
+ const userTable = userSchema.tables.users;
15
27
  expect(userTable.columns.id).toBeDefined();
16
28
  expect(userTable.columns.name).toBeDefined();
17
29
  expect(userTable.columns.email).toBeDefined();
18
30
  expect(userTable.columns.age).toBeDefined();
19
31
  expect(userTable.columns.age.isNullable).toBe(true);
20
- expect(userTable.indexes).toEqual([
21
- {
22
- name: "unique_email",
23
- columns: [userTable.columns.email],
24
- unique: true,
25
- },
26
- ]);
32
+
33
+ // Verify the index was stored as a sub-operation
34
+ const addTableOps = userSchema.operations.filter((op) => op.type === "add-table");
35
+ expect(addTableOps).toHaveLength(1);
36
+ const indexOps = addTableOps[0].operations.filter((op) => op.type === "add-index");
37
+ expect(indexOps).toHaveLength(1);
38
+ expect(indexOps[0].name).toBe("unique_email");
39
+ expect(indexOps[0].columns).toEqual(["email"]);
40
+ expect(indexOps[0].unique).toBe(true);
27
41
  });
28
42
 
29
43
  it("should create a schema with multiple tables using callback pattern", () => {
@@ -48,13 +62,16 @@ describe("create", () => {
48
62
  });
49
63
 
50
64
  it("should generate default values for columns", () => {
51
- const testTable = table("test", (t) => {
52
- return t
53
- .addColumn("id", idColumn())
54
- .addColumn("createdAt", column("timestamp").defaultTo$("now"))
55
- .addColumn("status", column("string").defaultTo("active"));
65
+ const testSchema = schema((s) => {
66
+ return s.addTable("test", (t) => {
67
+ return t
68
+ .addColumn("id", idColumn())
69
+ .addColumn("createdAt", column("timestamp").defaultTo$("now"))
70
+ .addColumn("status", column("string").defaultTo("active"));
71
+ });
56
72
  });
57
73
 
74
+ const testTable = testSchema.tables.test;
58
75
  const idValue = testTable.columns.id.generateDefaultValue();
59
76
  expect(typeof idValue).toBe("string");
60
77
  expect(idValue?.length).toBeGreaterThan(0);
@@ -66,7 +83,7 @@ describe("create", () => {
66
83
  expect(statusValue).toBe("active");
67
84
  });
68
85
 
69
- it("should increment version on each builder operation", () => {
86
+ it("should increment schema version on each schema-level operation", () => {
70
87
  const userSchema = schema((s) => {
71
88
  return s
72
89
  .addTable("users", (t) => {
@@ -84,41 +101,53 @@ describe("create", () => {
84
101
  });
85
102
 
86
103
  it("should support unique constraints on tables via unique method", () => {
87
- const userTable = table("users", (t) => {
88
- return t
89
- .addColumn("id", idColumn())
90
- .addColumn("email", column("string"))
91
- .addColumn("username", column("string"))
92
- .createIndex("unique_email_username", ["email", "username"], { unique: true });
93
- });
94
-
95
- const uniqueIndexes = userTable.indexes.filter((idx) => idx.unique);
96
- expect(uniqueIndexes).toHaveLength(1);
97
- expect(uniqueIndexes[0].name).toBe("unique_email_username");
98
- expect(uniqueIndexes[0].columns).toHaveLength(2);
104
+ const userSchema = schema((s) => {
105
+ return s.addTable("users", (t) => {
106
+ return t
107
+ .addColumn("id", idColumn())
108
+ .addColumn("email", column("string"))
109
+ .addColumn("username", column("string"))
110
+ .createIndex("unique_email_username", ["email", "username"], { unique: true });
111
+ });
112
+ });
113
+
114
+ // Verify the unique index was stored as a sub-operation
115
+ const addTableOps = userSchema.operations.filter((op) => op.type === "add-table");
116
+ expect(addTableOps).toHaveLength(1);
117
+ const indexOps = addTableOps[0].operations.filter((op) => op.type === "add-index");
118
+ expect(indexOps).toHaveLength(1);
119
+ expect(indexOps[0].name).toBe("unique_email_username");
120
+ expect(indexOps[0].columns).toEqual(["email", "username"]);
121
+ expect(indexOps[0].unique).toBe(true);
99
122
  });
100
123
 
101
124
  it("should support creating indexes on tables", () => {
102
- const userTable = table("users", (t) => {
103
- return t
104
- .addColumn("id", idColumn())
105
- .addColumn("email", column("string"))
106
- .addColumn("username", column("string"))
107
- .createIndex("idx_email", ["email"])
108
- .createIndex("idx_username_unique", ["username"], { unique: true });
109
- });
110
-
111
- expect(userTable.indexes).toHaveLength(2);
112
- expect(userTable.indexes[0]).toEqual({
113
- name: "idx_email",
114
- columns: [userTable.columns.email],
115
- unique: false,
116
- });
117
- expect(userTable.indexes[1]).toEqual({
118
- name: "idx_username_unique",
119
- columns: [userTable.columns.username],
120
- unique: true,
125
+ const userSchema = schema((s) => {
126
+ return s.addTable("users", (t) => {
127
+ return t
128
+ .addColumn("id", idColumn())
129
+ .addColumn("email", column("string"))
130
+ .addColumn("username", column("string"))
131
+ .createIndex("idx_email", ["email"])
132
+ .createIndex("idx_username_unique", ["username"], { unique: true });
133
+ });
121
134
  });
135
+
136
+ // Verify both indexes were stored as sub-operations
137
+ const addTableOps = userSchema.operations.filter((op) => op.type === "add-table");
138
+ expect(addTableOps).toHaveLength(1);
139
+ const indexOps = addTableOps[0].operations.filter((op) => op.type === "add-index");
140
+ expect(indexOps).toHaveLength(2);
141
+
142
+ const emailIndex = indexOps.find((op) => op.name === "idx_email");
143
+ expect(emailIndex).toBeDefined();
144
+ expect(emailIndex!.columns).toEqual(["email"]);
145
+ expect(emailIndex!.unique).toBe(false);
146
+
147
+ const usernameIndex = indexOps.find((op) => op.name === "idx_username_unique");
148
+ expect(usernameIndex).toBeDefined();
149
+ expect(usernameIndex!.columns).toEqual(["username"]);
150
+ expect(usernameIndex!.unique).toBe(true);
122
151
  });
123
152
 
124
153
  it("should demonstrate manual many-to-many relation setup", () => {
@@ -137,15 +166,15 @@ describe("create", () => {
137
166
  .addColumn("userId", referenceColumn())
138
167
  .addColumn("tagId", referenceColumn());
139
168
  })
140
- .addReference("user_tags", "user", {
141
- columns: ["userId"],
142
- targetTable: "users",
143
- targetColumns: ["id"],
169
+ .addReference("user", {
170
+ type: "one",
171
+ from: { table: "user_tags", column: "userId" },
172
+ to: { table: "users", column: "id" },
144
173
  })
145
- .addReference("user_tags", "tag", {
146
- columns: ["tagId"],
147
- targetTable: "tags",
148
- targetColumns: ["id"],
174
+ .addReference("tag", {
175
+ type: "one",
176
+ from: { table: "user_tags", column: "tagId" },
177
+ to: { table: "tags", column: "id" },
149
178
  });
150
179
  });
151
180
 
@@ -155,10 +184,23 @@ describe("create", () => {
155
184
  expect(junctionTable.relations["user"]).toBeDefined();
156
185
  expect(junctionTable.relations["tag"]).toBeDefined();
157
186
 
158
- // Verify both foreign keys were created
159
- expect(junctionTable.foreignKeys).toHaveLength(2);
160
- expect(junctionTable.foreignKeys[0].referencedTable).toBe(userSchema.tables.users);
161
- expect(junctionTable.foreignKeys[1].referencedTable).toBe(userSchema.tables.tags);
187
+ // Verify both foreign keys were created as operations
188
+ const addReferenceOps = userSchema.operations.filter((op) => op.type === "add-reference");
189
+ expect(addReferenceOps).toHaveLength(2);
190
+
191
+ const userRef = addReferenceOps.find((op) => op.referenceName === "user");
192
+ expect(userRef).toBeDefined();
193
+ expect(userRef!.tableName).toBe("user_tags");
194
+ expect(userRef!.config.type).toBe("one");
195
+ expect(userRef!.config.from).toEqual({ table: "user_tags", column: "userId" });
196
+ expect(userRef!.config.to).toEqual({ table: "users", column: "_internalId" });
197
+
198
+ const tagRef = addReferenceOps.find((op) => op.referenceName === "tag");
199
+ expect(tagRef).toBeDefined();
200
+ expect(tagRef!.tableName).toBe("user_tags");
201
+ expect(tagRef!.config.type).toBe("one");
202
+ expect(tagRef!.config.from).toEqual({ table: "user_tags", column: "tagId" });
203
+ expect(tagRef!.config.to).toEqual({ table: "tags", column: "_internalId" });
162
204
  });
163
205
 
164
206
  it("should create a foreign key reference using addReference", () => {
@@ -173,17 +215,17 @@ describe("create", () => {
173
215
  .addColumn("title", column("string"))
174
216
  .addColumn("authorId", referenceColumn());
175
217
  })
176
- .addReference("posts", "author", {
177
- columns: ["authorId"],
178
- targetTable: "users",
179
- targetColumns: ["id"],
218
+ .addReference("author", {
219
+ type: "one",
220
+ from: { table: "posts", column: "authorId" },
221
+ to: { table: "users", column: "id" },
180
222
  });
181
223
  });
182
224
 
183
225
  const postsTable = userSchema.tables.posts;
184
226
 
185
227
  // Verify the authorId column is marked as a reference
186
- expect(postsTable.columns.authorId.isReference).toBe(true);
228
+ expect(postsTable.columns.authorId.role).toBe("reference");
187
229
 
188
230
  // Verify the relation exists
189
231
  const authorRelation = postsTable.relations["author"];
@@ -192,23 +234,14 @@ describe("create", () => {
192
234
  expect(authorRelation.table).toBe(userSchema.tables.users);
193
235
  expect(authorRelation.on).toEqual([["authorId", "id"]]);
194
236
 
195
- // Verify the foreign key was created
196
- expect(postsTable.foreignKeys).toHaveLength(1);
197
- const fk = postsTable.foreignKeys[0];
198
- expect(fk.table).toBe(postsTable);
199
- expect(fk.referencedTable).toBe(userSchema.tables.users);
200
- expect(fk.columns).toEqual([postsTable.columns.authorId]);
201
- expect(fk.referencedColumns).toEqual([userSchema.tables.users.columns.id]);
202
-
203
- // Verify the compiled foreign key format
204
- const compiledFk = compileForeignKey(fk);
205
- expect(compiledFk).toEqual({
206
- name: "posts_users_author_fk",
207
- table: "posts",
208
- referencedTable: "users",
209
- columns: ["authorId"],
210
- referencedColumns: ["id"],
211
- });
237
+ // Verify the foreign key was created as an operation
238
+ const addReferenceOps = userSchema.operations.filter((op) => op.type === "add-reference");
239
+ expect(addReferenceOps).toHaveLength(1);
240
+ expect(addReferenceOps[0].tableName).toBe("posts");
241
+ expect(addReferenceOps[0].referenceName).toBe("author");
242
+ expect(addReferenceOps[0].config.type).toBe("one");
243
+ expect(addReferenceOps[0].config.from).toEqual({ table: "posts", column: "authorId" });
244
+ expect(addReferenceOps[0].config.to).toEqual({ table: "users", column: "_internalId" });
212
245
  });
213
246
 
214
247
  it("should support multiple references by calling addReference multiple times", () => {
@@ -227,15 +260,15 @@ describe("create", () => {
227
260
  .addColumn("authorId", referenceColumn())
228
261
  .addColumn("categoryId", referenceColumn());
229
262
  })
230
- .addReference("posts", "author", {
231
- columns: ["authorId"],
232
- targetTable: "users",
233
- targetColumns: ["id"],
263
+ .addReference("author", {
264
+ type: "one",
265
+ from: { table: "posts", column: "authorId" },
266
+ to: { table: "users", column: "id" },
234
267
  })
235
- .addReference("posts", "category", {
236
- columns: ["categoryId"],
237
- targetTable: "categories",
238
- targetColumns: ["id"],
268
+ .addReference("category", {
269
+ type: "one",
270
+ from: { table: "posts", column: "categoryId" },
271
+ to: { table: "categories", column: "id" },
239
272
  });
240
273
  });
241
274
 
@@ -245,10 +278,23 @@ describe("create", () => {
245
278
  expect(postsTable.relations["author"]).toBeDefined();
246
279
  expect(postsTable.relations["category"]).toBeDefined();
247
280
 
248
- // Verify both foreign keys were created
249
- expect(postsTable.foreignKeys).toHaveLength(2);
250
- expect(postsTable.foreignKeys[0].referencedTable).toBe(userSchema.tables.users);
251
- expect(postsTable.foreignKeys[1].referencedTable).toBe(userSchema.tables.categories);
281
+ // Verify both foreign keys were created as operations
282
+ const addReferenceOps = userSchema.operations.filter((op) => op.type === "add-reference");
283
+ expect(addReferenceOps).toHaveLength(2);
284
+
285
+ const authorRef = addReferenceOps.find((op) => op.referenceName === "author");
286
+ expect(authorRef).toBeDefined();
287
+ expect(authorRef!.tableName).toBe("posts");
288
+ expect(authorRef!.config.type).toBe("one");
289
+ expect(authorRef!.config.from).toEqual({ table: "posts", column: "authorId" });
290
+ expect(authorRef!.config.to).toEqual({ table: "users", column: "_internalId" });
291
+
292
+ const categoryRef = addReferenceOps.find((op) => op.referenceName === "category");
293
+ expect(categoryRef).toBeDefined();
294
+ expect(categoryRef!.tableName).toBe("posts");
295
+ expect(categoryRef!.config.type).toBe("one");
296
+ expect(categoryRef!.config.from).toEqual({ table: "posts", column: "categoryId" });
297
+ expect(categoryRef!.config.to).toEqual({ table: "categories", column: "_internalId" });
252
298
  });
253
299
 
254
300
  it("should support self-referencing foreign keys", () => {
@@ -260,10 +306,10 @@ describe("create", () => {
260
306
  .addColumn("name", column("string"))
261
307
  .addColumn("invitedBy", referenceColumn().nullable());
262
308
  })
263
- .addReference("users", "inviter", {
264
- columns: ["invitedBy"],
265
- targetTable: "users",
266
- targetColumns: ["id"],
309
+ .addReference("inviter", {
310
+ type: "one",
311
+ from: { table: "users", column: "invitedBy" },
312
+ to: { table: "users", column: "id" },
267
313
  });
268
314
  });
269
315
 
@@ -276,12 +322,509 @@ describe("create", () => {
276
322
  expect(inviterRelation.table).toBe(usersTable);
277
323
  expect(inviterRelation.on).toEqual([["invitedBy", "id"]]);
278
324
 
279
- // Verify the foreign key was created
280
- expect(usersTable.foreignKeys).toHaveLength(1);
281
- const fk = usersTable.foreignKeys[0];
282
- expect(fk.table).toBe(usersTable);
283
- expect(fk.referencedTable).toBe(usersTable);
284
- expect(fk.columns).toEqual([usersTable.columns.invitedBy]);
285
- expect(fk.referencedColumns).toEqual([usersTable.columns.id]);
325
+ // Verify the foreign key was created as an operation
326
+ const addReferenceOps = userSchema.operations.filter((op) => op.type === "add-reference");
327
+ expect(addReferenceOps).toHaveLength(1);
328
+ expect(addReferenceOps[0].tableName).toBe("users");
329
+ expect(addReferenceOps[0].referenceName).toBe("inviter");
330
+ expect(addReferenceOps[0].config.type).toBe("one");
331
+ expect(addReferenceOps[0].config.from).toEqual({ table: "users", column: "invitedBy" });
332
+ expect(addReferenceOps[0].config.to).toEqual({ table: "users", column: "_internalId" });
333
+ });
334
+
335
+ it("should allow altering an existing table to add columns", () => {
336
+ const userSchema = schema((s) => {
337
+ return s
338
+ .addTable("users", (t) => {
339
+ return t.addColumn("id", idColumn()).addColumn("name", column("string"));
340
+ })
341
+ .alterTable("users", (t) => {
342
+ return t
343
+ .addColumn("email", column("string"))
344
+ .addColumn("age", column("integer").nullable());
345
+ });
346
+ });
347
+
348
+ const usersTable = userSchema.tables.users;
349
+
350
+ // Verify the original columns exist
351
+ expect(usersTable.columns.id).toBeDefined();
352
+ expect(usersTable.columns.name).toBeDefined();
353
+
354
+ // Verify the new columns were added
355
+ expect(usersTable.columns.email).toBeDefined();
356
+ expect(usersTable.columns.age).toBeDefined();
357
+ expect(usersTable.columns.age.isNullable).toBe(true);
358
+
359
+ // Verify the operations were recorded
360
+ const alterTableOps = userSchema.operations.filter((op) => op.type === "alter-table");
361
+ expect(alterTableOps).toHaveLength(1);
362
+ expect(alterTableOps[0].operations).toHaveLength(2);
363
+ expect(alterTableOps[0].operations[0].type).toBe("add-column");
364
+ expect(alterTableOps[0].operations[1].type).toBe("add-column");
365
+ if (alterTableOps[0].operations[0].type === "add-column") {
366
+ expect(alterTableOps[0].operations[0].columnName).toBe("email");
367
+ }
368
+ if (alterTableOps[0].operations[1].type === "add-column") {
369
+ expect(alterTableOps[0].operations[1].columnName).toBe("age");
370
+ }
371
+
372
+ // Version should be: 1 (addTable) + 1 (alter-table with 2 columns)
373
+ expect(userSchema.version).toBe(2);
374
+ });
375
+
376
+ it("should allow altering an existing table to add indexes", () => {
377
+ const userSchema = schema((s) => {
378
+ return s
379
+ .addTable("users", (t) => {
380
+ return t
381
+ .addColumn("id", idColumn())
382
+ .addColumn("name", column("string"))
383
+ .addColumn("email", column("string"));
384
+ })
385
+ .alterTable("users", (t) => {
386
+ return t
387
+ .createIndex("idx_email", ["email"])
388
+ .createIndex("idx_name_unique", ["name"], { unique: true });
389
+ });
390
+ });
391
+
392
+ const alterTableOps = userSchema.operations.filter((op) => op.type === "alter-table");
393
+ expect(alterTableOps).toHaveLength(1);
394
+ const indexOps = alterTableOps[0].operations.filter((op) => op.type === "add-index");
395
+ expect(indexOps).toHaveLength(2);
396
+
397
+ // Version should be: 1 (addTable) + 1 (alter-table with indexes as sub-operations)
398
+ expect(userSchema.version).toBe(2);
399
+ });
400
+
401
+ it("should allow multiple alterTable calls on the same table", () => {
402
+ const userSchema = schema((s) => {
403
+ return s
404
+ .addTable("users", (t) => {
405
+ return t.addColumn("id", idColumn()).addColumn("name", column("string"));
406
+ })
407
+ .alterTable("users", (t) => {
408
+ return t.addColumn("email", column("string"));
409
+ })
410
+ .alterTable("users", (t) => {
411
+ return t.addColumn("age", column("integer").nullable());
412
+ });
413
+ });
414
+
415
+ const usersTable = userSchema.tables.users;
416
+ const columns = usersTable.columns;
417
+
418
+ expectTypeOf(columns.id.$in).toExtend<string | FragnoId | null>();
419
+ expectTypeOf(columns.id.$out).toEqualTypeOf<FragnoId>();
420
+
421
+ expectTypeOf(columns.name.$in).toBeString();
422
+ expectTypeOf(columns.name.$out).toBeString();
423
+
424
+ expectTypeOf(columns.email.$in).toBeString();
425
+ expectTypeOf(columns.email.$out).toBeString();
426
+
427
+ expectTypeOf(columns.age.$in).toExtend<number | null>();
428
+ expectTypeOf(columns.age.$out).toExtend<number | null>();
429
+
430
+ // Verify all columns exist
431
+ expect(usersTable.columns.id).toBeDefined();
432
+ expect(usersTable.columns.name).toBeDefined();
433
+ expect(usersTable.columns.email).toBeDefined();
434
+ expect(usersTable.columns.age).toBeDefined();
435
+
436
+ // Version should be: 1 (addTable) + 1 (first alter) + 1 (second alter)
437
+ expect(userSchema.version).toBe(3);
438
+ });
439
+
440
+ it("should preserve indexes when altering a table", () => {
441
+ const userSchema = schema((s) => {
442
+ return s
443
+ .addTable("users", (t) => {
444
+ return t
445
+ .addColumn("id", idColumn())
446
+ .addColumn("name", column("string"))
447
+ .addColumn("email", column("string"))
448
+ .createIndex("idx_email", ["email"])
449
+ .createIndex("idx_name_unique", ["name"], { unique: true });
450
+ })
451
+ .alterTable("users", (t) => {
452
+ return t.addColumn("age", column("integer").nullable());
453
+ });
454
+ });
455
+
456
+ const usersTable = userSchema.tables.users;
457
+
458
+ // Verify the new column was added
459
+ expect(usersTable.columns.age).toBeDefined();
460
+
461
+ // Verify the original indexes are still present in the table
462
+ expect(usersTable.indexes["idx_email"]).toBeDefined();
463
+ expect(usersTable.indexes["idx_email"].columnNames).toEqual(["email"]);
464
+ expect(usersTable.indexes["idx_email"].unique).toBe(false);
465
+
466
+ expect(usersTable.indexes["idx_name_unique"]).toBeDefined();
467
+ expect(usersTable.indexes["idx_name_unique"].columnNames).toEqual(["name"]);
468
+ expect(usersTable.indexes["idx_name_unique"].unique).toBe(true);
469
+ });
470
+
471
+ it("should not duplicate existing indexes when altering a table", () => {
472
+ const userSchema = schema((s) => {
473
+ return s
474
+ .addTable("users", (t) => {
475
+ return t
476
+ .addColumn("id", idColumn())
477
+ .addColumn("name", column("string"))
478
+ .addColumn("email", column("string"))
479
+ .createIndex("idx_email", ["email"])
480
+ .createIndex("idx_name_unique", ["name"], { unique: true });
481
+ })
482
+ .alterTable("users", (t) => {
483
+ return t.addColumn("age", column("integer").nullable());
484
+ });
485
+ });
486
+
487
+ // Verify the add-table operation contains both indexes
488
+ const addTableOps = userSchema.operations.filter((op) => op.type === "add-table");
489
+ expect(addTableOps).toHaveLength(1);
490
+ const addTableIndexOps = addTableOps[0].operations.filter((op) => op.type === "add-index");
491
+ expect(addTableIndexOps).toHaveLength(2);
492
+
493
+ // Verify the alter-table operation does NOT contain the existing indexes
494
+ const alterTableOps = userSchema.operations.filter((op) => op.type === "alter-table");
495
+ expect(alterTableOps).toHaveLength(1);
496
+ const alterTableIndexOps = alterTableOps[0].operations.filter((op) => op.type === "add-index");
497
+ expect(alterTableIndexOps).toHaveLength(0); // Should be 0, not 2
498
+
499
+ // The alter-table should only have the new column
500
+ const alterTableColumnOps = alterTableOps[0].operations.filter(
501
+ (op) => op.type === "add-column",
502
+ );
503
+ expect(alterTableColumnOps).toHaveLength(1);
504
+ if (alterTableColumnOps[0].type === "add-column") {
505
+ expect(alterTableColumnOps[0].columnName).toBe("age");
506
+ }
507
+ });
508
+
509
+ it("should only add new indexes when altering a table with additional indexes", () => {
510
+ const userSchema = schema((s) => {
511
+ return s
512
+ .addTable("users", (t) => {
513
+ return t
514
+ .addColumn("id", idColumn())
515
+ .addColumn("name", column("string"))
516
+ .addColumn("email", column("string"))
517
+ .createIndex("idx_email", ["email"]);
518
+ })
519
+ .alterTable("users", (t) => {
520
+ return t
521
+ .addColumn("age", column("integer").nullable())
522
+ .createIndex("idx_name", ["name"]) // New index
523
+ .createIndex("idx_age", ["age"]); // New index on new column
524
+ });
525
+ });
526
+
527
+ // Verify the add-table operation contains only the original index
528
+ const addTableOps = userSchema.operations.filter((op) => op.type === "add-table");
529
+ expect(addTableOps).toHaveLength(1);
530
+ const addTableIndexOps = addTableOps[0].operations.filter((op) => op.type === "add-index");
531
+ expect(addTableIndexOps).toHaveLength(1);
532
+ expect(addTableIndexOps[0].name).toBe("idx_email");
533
+
534
+ // Verify the alter-table operation contains only the NEW indexes
535
+ const alterTableOps = userSchema.operations.filter((op) => op.type === "alter-table");
536
+ expect(alterTableOps).toHaveLength(1);
537
+ const alterTableIndexOps = alterTableOps[0].operations.filter((op) => op.type === "add-index");
538
+ expect(alterTableIndexOps).toHaveLength(2); // Only the two new indexes
539
+
540
+ const indexNames = alterTableIndexOps.map((op) => op.name);
541
+ expect(indexNames).toContain("idx_name");
542
+ expect(indexNames).toContain("idx_age");
543
+ expect(indexNames).not.toContain("idx_email"); // Should not duplicate the original index
544
+
545
+ // Verify all three indexes are present in the final table structure
546
+ const usersTable = userSchema.tables.users;
547
+ expect(Object.keys(usersTable.indexes)).toHaveLength(3);
548
+ expect(usersTable.indexes["idx_email"]).toBeDefined();
549
+ expect(usersTable.indexes["idx_name"]).toBeDefined();
550
+ expect(usersTable.indexes["idx_age"]).toBeDefined();
551
+ });
552
+
553
+ it("Simple user table types", () => {
554
+ const _userSchema = schema((s) => {
555
+ return s.addTable("users", (t) => {
556
+ return t.addColumn("id", idColumn()).addColumn("name", column("string"));
557
+ });
558
+ });
559
+
560
+ type _UserInsert = TableToInsertValues<typeof _userSchema.tables.users>;
561
+ expectTypeOf<_UserInsert>().toExtend<{
562
+ [x: string]: unknown;
563
+ id?: string | FragnoId | null;
564
+ name: string;
565
+ }>();
566
+
567
+ type _UserResult = TableToColumnValues<typeof _userSchema.tables.users>;
568
+ expectTypeOf<_UserResult>().toExtend<{
569
+ id: FragnoId;
570
+ name: string;
571
+ }>();
572
+
573
+ type _RawUser = RawColumnValues<typeof _userSchema.tables.users>;
574
+ expectTypeOf<_RawUser>().toEqualTypeOf<{
575
+ id: FragnoId;
576
+ name: string;
577
+ }>();
578
+ });
579
+
580
+ it("Simple user table types after alter table statements", () => {
581
+ const _userSchema = schema((s) => {
582
+ return s
583
+ .addTable("users", (t) => {
584
+ return t.addColumn("id", idColumn()).addColumn("name", column("string"));
585
+ })
586
+ .addTable("emails", (t) => {
587
+ return t.addColumn("id", idColumn()).addColumn("email", column("string"));
588
+ })
589
+ .alterTable("emails", (t) => {
590
+ return t.addColumn("is_primary", column("bool").defaultTo(false));
591
+ });
592
+ });
593
+
594
+ type _UserInsert = TableToInsertValues<typeof _userSchema.tables.users>;
595
+ expectTypeOf<_UserInsert>().toEqualTypeOf<{
596
+ id?: string | FragnoId | null | undefined;
597
+ name: string;
598
+ }>();
599
+
600
+ type _UserResult = TableToColumnValues<typeof _userSchema.tables.users>;
601
+ expectTypeOf<_UserResult>().toEqualTypeOf<{
602
+ id: FragnoId;
603
+ name: string;
604
+ }>();
605
+ });
606
+ });
607
+
608
+ describe("idColumn", () => {
609
+ it("should create a table with an id column", () => {
610
+ const idCol = idColumn();
611
+ type _In = typeof idCol.$in;
612
+ type _Out = typeof idCol.$out;
613
+ expectTypeOf<_In>().toEqualTypeOf<string | FragnoId | null>();
614
+ expectTypeOf<_Out>().toEqualTypeOf<FragnoId>();
615
+
616
+ expect(idCol.generateDefaultValue()).toBeDefined();
617
+ });
618
+ });
619
+
620
+ describe("referenceColumn", () => {
621
+ it("should create a table with a reference column", () => {
622
+ const _referenceCol = referenceColumn();
623
+ type _In = typeof _referenceCol.$in;
624
+ type _Out = typeof _referenceCol.$out;
625
+ expectTypeOf<_In>().toEqualTypeOf<string | bigint | FragnoId | FragnoReference>();
626
+ expectTypeOf<_Out>().toEqualTypeOf<FragnoReference>();
627
+ });
628
+ });
629
+
630
+ describe("SchemaBuilder with existing schema", () => {
631
+ it("should initialize with an existing schema", () => {
632
+ const existingSchema = schema((s) => {
633
+ return s.addTable("users", (t) => {
634
+ return t.addColumn("id", idColumn()).addColumn("name", column("string"));
635
+ });
636
+ });
637
+
638
+ const extendedSchema = new SchemaBuilder(existingSchema)
639
+ .addTable("posts", (t) => {
640
+ return t.addColumn("id", idColumn()).addColumn("title", column("string"));
641
+ })
642
+ .build();
643
+
644
+ expect(extendedSchema.tables.users).toBeDefined();
645
+ expect(extendedSchema.tables.posts).toBeDefined();
646
+ expect(extendedSchema.version).toBe(2); // 1 from original + 1 from new table
647
+ expect(extendedSchema.operations).toHaveLength(2);
648
+ });
649
+
650
+ it("should preserve operations from existing schema", () => {
651
+ const existingSchema = schema((s) => {
652
+ return s
653
+ .addTable("users", (t) => {
654
+ return t.addColumn("id", idColumn()).addColumn("name", column("string"));
655
+ })
656
+ .addTable("posts", (t) => {
657
+ return t.addColumn("id", idColumn()).addColumn("title", column("string"));
658
+ });
659
+ });
660
+
661
+ const extendedSchema = new SchemaBuilder(existingSchema)
662
+ .addTable("comments", (t) => {
663
+ return t.addColumn("id", idColumn()).addColumn("text", column("string"));
664
+ })
665
+ .build();
666
+
667
+ expect(extendedSchema.operations).toHaveLength(3);
668
+ expect(extendedSchema.operations[0].type).toBe("add-table");
669
+ expect(extendedSchema.operations[0].tableName).toBe("users");
670
+ expect(extendedSchema.operations[1].type).toBe("add-table");
671
+ expect(extendedSchema.operations[1].tableName).toBe("posts");
672
+ expect(extendedSchema.operations[2].type).toBe("add-table");
673
+ expect(extendedSchema.operations[2].tableName).toBe("comments");
674
+ });
675
+
676
+ it("should merge multiple schemas using mergeWithExistingSchema", () => {
677
+ const schema1 = schema((s) => {
678
+ return s.addTable("users", (t) => {
679
+ return t.addColumn("id", idColumn()).addColumn("name", column("string"));
680
+ });
681
+ });
682
+
683
+ const schema2 = schema((s) => {
684
+ return s.addTable("posts", (t) => {
685
+ return t.addColumn("id", idColumn()).addColumn("title", column("string"));
686
+ });
687
+ });
688
+
689
+ const mergedSchema = new SchemaBuilder()
690
+ .mergeWithExistingSchema(schema1)
691
+ .mergeWithExistingSchema(schema2)
692
+ .build();
693
+
694
+ expect(mergedSchema.tables.users).toBeDefined();
695
+ expect(mergedSchema.tables.posts).toBeDefined();
696
+ expect(mergedSchema.version).toBe(2); // 1 from schema1 + 1 from schema2
697
+ expect(mergedSchema.operations).toHaveLength(2);
698
+ });
699
+
700
+ it("should extend merged schema with new tables", () => {
701
+ const schema1 = schema((s) => {
702
+ return s.addTable("users", (t) => {
703
+ return t.addColumn("id", idColumn()).addColumn("name", column("string"));
704
+ });
705
+ });
706
+
707
+ const schema2 = schema((s) => {
708
+ return s.addTable("posts", (t) => {
709
+ return t.addColumn("id", idColumn()).addColumn("title", column("string"));
710
+ });
711
+ });
712
+
713
+ const extended = new SchemaBuilder()
714
+ .mergeWithExistingSchema(schema1)
715
+ .mergeWithExistingSchema(schema2)
716
+ .addTable("comments", (t) => {
717
+ return t.addColumn("id", idColumn()).addColumn("text", column("string"));
718
+ })
719
+ .build();
720
+
721
+ expect(extended.tables.users).toBeDefined();
722
+ expect(extended.tables.posts).toBeDefined();
723
+ expect(extended.tables.comments).toBeDefined();
724
+ expect(extended.version).toBe(3); // 2 from merged + 1 from new table
725
+ expect(extended.operations).toHaveLength(3);
726
+ });
727
+
728
+ it("should use mergeWithExistingSchema method to merge schemas", () => {
729
+ const schema1 = schema((s) => {
730
+ return s.addTable("users", (t) => {
731
+ return t.addColumn("id", idColumn()).addColumn("name", column("string"));
732
+ });
733
+ });
734
+
735
+ const schema2 = schema((s) => {
736
+ return s.addTable("posts", (t) => {
737
+ return t.addColumn("id", idColumn()).addColumn("title", column("string"));
738
+ });
739
+ });
740
+
741
+ const combined = new SchemaBuilder()
742
+ .mergeWithExistingSchema(schema1)
743
+ .mergeWithExistingSchema(schema2)
744
+ .addTable("comments", (t) => {
745
+ return t.addColumn("id", idColumn()).addColumn("text", column("string"));
746
+ })
747
+ .build();
748
+
749
+ expect(combined.tables.users).toBeDefined();
750
+ expect(combined.tables.posts).toBeDefined();
751
+ expect(combined.tables.comments).toBeDefined();
752
+ expect(combined.version).toBe(3); // 1 + 1 + 1
753
+ expect(combined.operations).toHaveLength(3);
754
+ });
755
+
756
+ it("should merge operations from multiple schemas in order", () => {
757
+ const schema1 = schema((s) => {
758
+ return s.addTable("users", (t) => {
759
+ return t.addColumn("id", idColumn()).addColumn("name", column("string"));
760
+ });
761
+ });
762
+
763
+ const schema2 = schema((s) => {
764
+ return s
765
+ .addTable("posts", (t) => {
766
+ return t.addColumn("id", idColumn()).addColumn("title", column("string"));
767
+ })
768
+ .addTable("categories", (t) => {
769
+ return t.addColumn("id", idColumn()).addColumn("name", column("string"));
770
+ });
771
+ });
772
+
773
+ const mergedSchema = new SchemaBuilder()
774
+ .mergeWithExistingSchema(schema1)
775
+ .mergeWithExistingSchema(schema2)
776
+ .build();
777
+
778
+ expect(mergedSchema.operations).toHaveLength(3);
779
+ expect(mergedSchema.operations[0].tableName).toBe("users");
780
+ expect(mergedSchema.operations[1].tableName).toBe("posts");
781
+ expect(mergedSchema.operations[2].tableName).toBe("categories");
782
+ expect(mergedSchema.version).toBe(3); // 1 from schema1 + 2 from schema2
783
+ });
784
+
785
+ it("should merge three or more schemas", () => {
786
+ const schema1 = schema((s) => {
787
+ return s.addTable("users", (t) => {
788
+ return t.addColumn("id", idColumn()).addColumn("name", column("string"));
789
+ });
790
+ });
791
+
792
+ const schema2 = schema((s) => {
793
+ return s.addTable("posts", (t) => {
794
+ return t.addColumn("id", idColumn()).addColumn("title", column("string"));
795
+ });
796
+ });
797
+
798
+ const schema3 = schema((s) => {
799
+ return s.addTable("comments", (t) => {
800
+ return t.addColumn("id", idColumn()).addColumn("text", column("string"));
801
+ });
802
+ });
803
+
804
+ const mergedSchema = new SchemaBuilder()
805
+ .mergeWithExistingSchema(schema1)
806
+ .mergeWithExistingSchema(schema2)
807
+ .mergeWithExistingSchema(schema3)
808
+ .build();
809
+
810
+ expect(mergedSchema.tables.users).toBeDefined();
811
+ expect(mergedSchema.tables.posts).toBeDefined();
812
+ expect(mergedSchema.tables.comments).toBeDefined();
813
+ expect(mergedSchema.version).toBe(3);
814
+ expect(mergedSchema.operations).toHaveLength(3);
815
+ });
816
+
817
+ it("should handle single schema merge", () => {
818
+ const schema1 = schema((s) => {
819
+ return s.addTable("users", (t) => {
820
+ return t.addColumn("id", idColumn()).addColumn("name", column("string"));
821
+ });
822
+ });
823
+
824
+ const mergedSchema = new SchemaBuilder().mergeWithExistingSchema(schema1).build();
825
+
826
+ expect(mergedSchema.tables.users).toBeDefined();
827
+ expect(mergedSchema.version).toBe(1);
828
+ expect(mergedSchema.operations).toHaveLength(1);
286
829
  });
287
830
  });