@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
@@ -0,0 +1,947 @@
1
+ import { describe, it, expect, assert, expectTypeOf } from "vitest";
2
+ import { column, schema, idColumn } from "../schema/create";
3
+ import {
4
+ UnitOfWork,
5
+ type UOWCompiler,
6
+ type UOWDecoder,
7
+ createUnitOfWork,
8
+ type InferIdColumnName,
9
+ type IndexColumns,
10
+ } from "./unit-of-work";
11
+ import { createIndexedBuilder } from "./condition-builder";
12
+ import type { AnySchema } from "../schema/create";
13
+ import type { AbstractQuery } from "./query";
14
+
15
+ // Mock compiler and executor for testing
16
+ function createMockCompiler<TSchema extends AnySchema = AnySchema>(): UOWCompiler<
17
+ TSchema,
18
+ unknown
19
+ > {
20
+ return {
21
+ compileRetrievalOperation: () => null,
22
+ compileMutationOperation: () => null,
23
+ };
24
+ }
25
+
26
+ function createMockExecutor() {
27
+ return {
28
+ executeRetrievalPhase: async () => [],
29
+ executeMutationPhase: async () => ({ success: true, createdInternalIds: [] }),
30
+ };
31
+ }
32
+
33
+ function createMockDecoder<TSchema extends AnySchema = AnySchema>(): UOWDecoder<TSchema> {
34
+ return (rawResults, operations) => {
35
+ if (rawResults.length !== operations.length) {
36
+ throw new Error("rawResults and operations must have the same length");
37
+ }
38
+ return rawResults;
39
+ };
40
+ }
41
+
42
+ describe("FindBuilder", () => {
43
+ it("should support primary index", () => {
44
+ const testSchema = schema((s) =>
45
+ s.addTable("users", (t) =>
46
+ t
47
+ .addColumn("id", idColumn())
48
+ .addColumn("email", "string")
49
+ .addColumn("name", "string")
50
+ .addColumn("age", "integer")
51
+ .createIndex("idx_email", ["email"], { unique: true })
52
+ .createIndex("idx_name_age", ["name", "age"]),
53
+ ),
54
+ );
55
+
56
+ const uow = new UnitOfWork(
57
+ testSchema,
58
+ createMockCompiler(),
59
+ createMockExecutor(),
60
+ createMockDecoder(),
61
+ );
62
+ uow.find("users", (b) => b.whereIndex("primary"));
63
+
64
+ const ops = uow.getRetrievalOperations();
65
+ expect(ops).toHaveLength(1);
66
+ expect(ops[0].indexName).toBe("_primary");
67
+ });
68
+
69
+ it("should support custom indexes", () => {
70
+ const testSchema = schema((s) =>
71
+ s.addTable("users", (t) =>
72
+ t
73
+ .addColumn("id", idColumn())
74
+ .addColumn("email", "string")
75
+ .addColumn("name", "string")
76
+ .createIndex("idx_email", ["email"], { unique: true }),
77
+ ),
78
+ );
79
+
80
+ const uow = new UnitOfWork(
81
+ testSchema,
82
+ createMockCompiler(),
83
+ createMockExecutor(),
84
+ createMockDecoder(),
85
+ );
86
+ uow.find("users", (b) =>
87
+ b.whereIndex("idx_email", (eb) => eb("email", "=", "test@example.com")),
88
+ );
89
+
90
+ const ops = uow.getRetrievalOperations();
91
+ expect(ops).toHaveLength(1);
92
+ expect(ops[0].indexName).toBe("idx_email");
93
+ });
94
+
95
+ it("should support cursor-based pagination", () => {
96
+ const testSchema = schema((s) =>
97
+ s.addTable("users", (t) => t.addColumn("id", idColumn()).addColumn("name", "string")),
98
+ );
99
+
100
+ const uow = new UnitOfWork(
101
+ testSchema,
102
+ createMockCompiler(),
103
+ createMockExecutor(),
104
+ createMockDecoder(),
105
+ );
106
+
107
+ const cursor = "eyJpbmRleFZhbHVlcyI6eyJpZCI6InVzZXIxMjMifSwiZGlyZWN0aW9uIjoiZm9yd2FyZCJ9";
108
+ uow.find("users", (b) => b.whereIndex("primary").after(cursor).pageSize(10));
109
+
110
+ const ops = uow.getRetrievalOperations();
111
+ expect(ops).toHaveLength(1);
112
+ const op = ops[0];
113
+ assert(op.type === "find");
114
+ expect(op.options.after).toBe(cursor);
115
+ expect(op.options.pageSize).toBe(10);
116
+ });
117
+
118
+ it("should support backward cursor pagination", () => {
119
+ const testSchema = schema((s) =>
120
+ s.addTable("users", (t) => t.addColumn("id", idColumn()).addColumn("name", "string")),
121
+ );
122
+
123
+ const uow = new UnitOfWork(
124
+ testSchema,
125
+ createMockCompiler(),
126
+ createMockExecutor(),
127
+ createMockDecoder(),
128
+ );
129
+
130
+ const cursor = "eyJpbmRleFZhbHVlcyI6eyJpZCI6InVzZXI0NTYifSwiZGlyZWN0aW9uIjoiYmFja3dhcmQifQ==";
131
+ uow.find("users", (b) => b.whereIndex("primary").before(cursor).pageSize(5));
132
+
133
+ const ops = uow.getRetrievalOperations();
134
+ expect(ops).toHaveLength(1);
135
+ const op = ops[0];
136
+ assert(op.type === "find");
137
+ expect(op.options.before).toBe(cursor);
138
+ expect(op.options.pageSize).toBe(5);
139
+ });
140
+
141
+ it("should throw if index doesn't exist", () => {
142
+ const testSchema = schema((s) =>
143
+ s.addTable("users", (t) => t.addColumn("id", idColumn()).addColumn("name", "string")),
144
+ );
145
+
146
+ const uow = new UnitOfWork(
147
+ testSchema,
148
+ createMockCompiler(),
149
+ createMockExecutor(),
150
+ createMockDecoder(),
151
+ );
152
+ expect(() => {
153
+ uow.find("users", (b) => b.whereIndex("nonexistent" as "primary"));
154
+ }).toThrow('Index "nonexistent" not found on table "users"');
155
+ });
156
+
157
+ it("should throw if finalized without index", () => {
158
+ const testSchema = schema((s) =>
159
+ s.addTable("users", (t) => t.addColumn("id", idColumn()).addColumn("name", "string")),
160
+ );
161
+
162
+ const uow = new UnitOfWork(
163
+ testSchema,
164
+ createMockCompiler(),
165
+ createMockExecutor(),
166
+ createMockDecoder(),
167
+ );
168
+ expect(() => {
169
+ uow.find("users", (b) => b);
170
+ }).toThrow(
171
+ 'Must specify an index using .whereIndex() before finalizing find operation on table "users"',
172
+ );
173
+ });
174
+
175
+ it("should support count operations", () => {
176
+ const testSchema = schema((s) =>
177
+ s.addTable("users", (t) =>
178
+ t.addColumn("id", idColumn()).addColumn("name", "string").addColumn("age", "integer"),
179
+ ),
180
+ );
181
+
182
+ const uow = new UnitOfWork(
183
+ testSchema,
184
+ createMockCompiler(),
185
+ createMockExecutor(),
186
+ createMockDecoder(),
187
+ );
188
+ uow.find("users", (b) => b.whereIndex("primary").selectCount());
189
+
190
+ const ops = uow.getRetrievalOperations();
191
+ expect(ops).toHaveLength(1);
192
+ expect(ops[0]?.type).toBe("count");
193
+ });
194
+
195
+ it("should throw when using both select and selectCount", () => {
196
+ const testSchema = schema((s) =>
197
+ s.addTable("users", (t) => t.addColumn("id", idColumn()).addColumn("name", "string")),
198
+ );
199
+
200
+ const uow = new UnitOfWork(
201
+ testSchema,
202
+ createMockCompiler(),
203
+ createMockExecutor(),
204
+ createMockDecoder(),
205
+ );
206
+
207
+ // select() then selectCount()
208
+ expect(() => {
209
+ uow.find("users", (b) => b.whereIndex("primary").select(["name"]).selectCount());
210
+ }).toThrow(/cannot call selectCount/i);
211
+
212
+ // selectCount() then select()
213
+ const uow2 = new UnitOfWork(
214
+ testSchema,
215
+ createMockCompiler(),
216
+ createMockExecutor(),
217
+ createMockDecoder(),
218
+ );
219
+ expect(() => {
220
+ uow2.find("users", (b) => b.whereIndex("primary").selectCount().select(["name"]));
221
+ }).toThrow(/cannot call select/i);
222
+ });
223
+
224
+ it("should support orderByIndex", () => {
225
+ const testSchema = schema((s) =>
226
+ s.addTable("users", (t) =>
227
+ t
228
+ .addColumn("id", idColumn())
229
+ .addColumn("name", "string")
230
+ .addColumn("createdAt", "integer")
231
+ .createIndex("idx_created", ["createdAt"]),
232
+ ),
233
+ );
234
+
235
+ const uow = new UnitOfWork(
236
+ testSchema,
237
+ createMockCompiler(),
238
+ createMockExecutor(),
239
+ createMockDecoder(),
240
+ );
241
+ uow.find("users", (b) => b.whereIndex("primary").orderByIndex("idx_created", "desc"));
242
+
243
+ const ops = uow.getRetrievalOperations();
244
+ expect(ops).toHaveLength(1);
245
+ const op = ops[0];
246
+ if (op?.type === "find") {
247
+ expect(op.options.orderByIndex).toEqual({
248
+ indexName: "idx_created",
249
+ direction: "desc",
250
+ });
251
+ } else {
252
+ throw new Error("Expected find operation");
253
+ }
254
+ });
255
+
256
+ it("should support join operations", () => {
257
+ const testSchema = schema((s) =>
258
+ s
259
+ .addTable("users", (t) => t.addColumn("id", idColumn()).addColumn("name", "string"))
260
+ .addTable("posts", (t) =>
261
+ t
262
+ .addColumn("id", idColumn())
263
+ .addColumn("userId", column("string"))
264
+ .addColumn("title", "string")
265
+ .createIndex("idx_user", ["userId"]),
266
+ )
267
+ .addReference("user", {
268
+ type: "one",
269
+ from: { table: "posts", column: "userId" },
270
+ to: { table: "users", column: "id" },
271
+ }),
272
+ );
273
+
274
+ const uow = new UnitOfWork(
275
+ testSchema,
276
+ createMockCompiler(),
277
+ createMockExecutor(),
278
+ createMockDecoder(),
279
+ );
280
+
281
+ uow.find("posts", (b) =>
282
+ b.whereIndex("primary").join((jb) => jb["user"]((builder) => builder.select(["name"]))),
283
+ );
284
+
285
+ const ops = uow.getRetrievalOperations();
286
+ expect(ops).toHaveLength(1);
287
+ const op = ops[0];
288
+ assert(op.type === "find");
289
+ expect(op.options.joins).toBeDefined();
290
+ expect(op.options.joins).toHaveLength(1);
291
+ });
292
+
293
+ it("should support join operations without builder function", () => {
294
+ const testSchema = schema((s) =>
295
+ s
296
+ .addTable("users", (t) => t.addColumn("id", idColumn()).addColumn("name", "string"))
297
+ .addTable("posts", (t) =>
298
+ t
299
+ .addColumn("id", idColumn())
300
+ .addColumn("userId", column("string"))
301
+ .addColumn("title", "string")
302
+ .createIndex("idx_user", ["userId"]),
303
+ )
304
+ .addReference("user", {
305
+ type: "one",
306
+ from: { table: "posts", column: "userId" },
307
+ to: { table: "users", column: "id" },
308
+ }),
309
+ );
310
+
311
+ const uow = new UnitOfWork(
312
+ testSchema,
313
+ createMockCompiler(),
314
+ createMockExecutor(),
315
+ createMockDecoder(),
316
+ );
317
+
318
+ // Join without builder function should use default options
319
+ uow.find("posts", (b) => b.whereIndex("primary").join((jb) => jb.user()));
320
+
321
+ const ops = uow.getRetrievalOperations();
322
+ expect(ops).toHaveLength(1);
323
+ const op = ops[0];
324
+ assert(op.type === "find");
325
+ expect(op.options.joins).toBeDefined();
326
+ expect(op.options.joins).toHaveLength(1);
327
+ const joinOptions = op.options.joins![0]!.options;
328
+ assert(joinOptions !== false);
329
+ expect(joinOptions.select).toBe(true); // Should default to selecting all columns
330
+ });
331
+
332
+ it("should support join with whereIndex", () => {
333
+ const testSchema = schema((s) =>
334
+ s
335
+ .addTable("users", (t) =>
336
+ t
337
+ .addColumn("id", idColumn())
338
+ .addColumn("name", "string")
339
+ .createIndex("idx_name", ["name"]),
340
+ )
341
+ .addTable("posts", (t) =>
342
+ t
343
+ .addColumn("id", idColumn())
344
+ .addColumn("userId", column("string"))
345
+ .addColumn("title", "string")
346
+ .createIndex("idx_user", ["userId"]),
347
+ )
348
+ .addReference("user", {
349
+ type: "one",
350
+ from: { table: "posts", column: "userId" },
351
+ to: { table: "users", column: "id" },
352
+ }),
353
+ );
354
+
355
+ const uow = new UnitOfWork(
356
+ testSchema,
357
+ createMockCompiler(),
358
+ createMockExecutor(),
359
+ createMockDecoder(),
360
+ );
361
+
362
+ uow.find("posts", (b) =>
363
+ b
364
+ .whereIndex("primary")
365
+ .join((jb) =>
366
+ jb["user"]((builder) =>
367
+ builder.whereIndex("idx_name", (eb) => eb("name", "=", "Alice")).select(["name"]),
368
+ ),
369
+ ),
370
+ );
371
+
372
+ const ops = uow.getRetrievalOperations();
373
+ expect(ops).toHaveLength(1);
374
+ const op = ops[0];
375
+ assert(op.type === "find");
376
+ expect(op.options.joins).toBeDefined();
377
+ expect(op.options.joins).toHaveLength(1);
378
+ const joinOptions = op.options.joins![0]!.options;
379
+ assert(joinOptions !== false);
380
+ expect(joinOptions.where).toBeDefined();
381
+ });
382
+
383
+ it("should support join with orderByIndex", () => {
384
+ const testSchema = schema((s) =>
385
+ s
386
+ .addTable("users", (t) =>
387
+ t
388
+ .addColumn("id", idColumn())
389
+ .addColumn("name", "string")
390
+ .addColumn("createdAt", "integer")
391
+ .createIndex("idx_created", ["createdAt"]),
392
+ )
393
+ .addTable("posts", (t) =>
394
+ t
395
+ .addColumn("id", idColumn())
396
+ .addColumn("userId", column("string"))
397
+ .addColumn("title", "string")
398
+ .createIndex("idx_user", ["userId"]),
399
+ )
400
+ .addReference("user", {
401
+ type: "one",
402
+ from: { table: "posts", column: "userId" },
403
+ to: { table: "users", column: "id" },
404
+ }),
405
+ );
406
+
407
+ const uow = new UnitOfWork(
408
+ testSchema,
409
+ createMockCompiler(),
410
+ createMockExecutor(),
411
+ createMockDecoder(),
412
+ );
413
+
414
+ uow.find("posts", (b) =>
415
+ b
416
+ .whereIndex("primary")
417
+ .join((jb) => jb["user"]((builder) => builder.orderByIndex("idx_created", "desc"))),
418
+ );
419
+
420
+ const ops = uow.getRetrievalOperations();
421
+ expect(ops).toHaveLength(1);
422
+ const op = ops[0];
423
+ assert(op.type === "find");
424
+ expect(op.options.joins).toBeDefined();
425
+ const joinOptions = op.options.joins![0]!.options;
426
+ assert(joinOptions !== false);
427
+ expect(joinOptions.orderBy).toBeDefined();
428
+ expect(joinOptions.orderBy).toHaveLength(1);
429
+ });
430
+
431
+ it("should support join with pageSize", () => {
432
+ const testSchema = schema((s) =>
433
+ s
434
+ .addTable("users", (t) => t.addColumn("id", idColumn()).addColumn("name", "string"))
435
+ .addTable("posts", (t) =>
436
+ t
437
+ .addColumn("id", idColumn())
438
+ .addColumn("userId", column("string"))
439
+ .addColumn("title", "string")
440
+ .createIndex("idx_user", ["userId"]),
441
+ )
442
+ .addReference("user", {
443
+ type: "one",
444
+ from: { table: "posts", column: "userId" },
445
+ to: { table: "users", column: "id" },
446
+ }),
447
+ );
448
+
449
+ const uow = new UnitOfWork(
450
+ testSchema,
451
+ createMockCompiler(),
452
+ createMockExecutor(),
453
+ createMockDecoder(),
454
+ );
455
+
456
+ uow.find("posts", (b) =>
457
+ b.whereIndex("primary").join((jb) => jb["user"]((builder) => builder.pageSize(5))),
458
+ );
459
+
460
+ const ops = uow.getRetrievalOperations();
461
+ expect(ops).toHaveLength(1);
462
+ const op = ops[0];
463
+ assert(op.type === "find");
464
+ const joinOptions = op.options.joins![0]!.options;
465
+ assert(joinOptions !== false);
466
+ expect(joinOptions.limit).toBe(5);
467
+ });
468
+
469
+ it("should support nested joins", () => {
470
+ const testSchema = schema((s) =>
471
+ s
472
+ .addTable("users", (t) => t.addColumn("id", idColumn()).addColumn("name", "string"))
473
+ .addTable("posts", (t) =>
474
+ t
475
+ .addColumn("id", idColumn())
476
+ .addColumn("userId", column("string"))
477
+ .addColumn("authorId", column("string"))
478
+ .addColumn("title", "string")
479
+ .createIndex("idx_user", ["userId"])
480
+ .createIndex("idx_author", ["authorId"]),
481
+ )
482
+ .addTable("comments", (t) =>
483
+ t
484
+ .addColumn("id", idColumn())
485
+ .addColumn("postId", column("string"))
486
+ .addColumn("text", "string")
487
+ .createIndex("idx_post", ["postId"]),
488
+ )
489
+ .addReference("user", {
490
+ type: "one",
491
+ from: { table: "posts", column: "userId" },
492
+ to: { table: "users", column: "id" },
493
+ })
494
+ .addReference("post", {
495
+ type: "one",
496
+ from: { table: "comments", column: "postId" },
497
+ to: { table: "posts", column: "id" },
498
+ }),
499
+ );
500
+
501
+ const uow = new UnitOfWork(
502
+ testSchema,
503
+ createMockCompiler(),
504
+ createMockExecutor(),
505
+ createMockDecoder(),
506
+ );
507
+
508
+ uow.find("comments", (b) =>
509
+ b
510
+ .whereIndex("primary")
511
+ .join((jb) =>
512
+ jb["post"]((postBuilder) =>
513
+ postBuilder
514
+ .select(["title"])
515
+ .join((jb2) => jb2["user"]((userBuilder) => userBuilder.select(["name"]))),
516
+ ),
517
+ ),
518
+ );
519
+
520
+ const ops = uow.getRetrievalOperations();
521
+ expect(ops).toHaveLength(1);
522
+ const op = ops[0];
523
+ assert(op.type === "find");
524
+ expect(op.options.joins).toBeDefined();
525
+ expect(op.options.joins).toHaveLength(1);
526
+
527
+ const postJoin = op.options.joins![0]!;
528
+ assert(postJoin.options !== false);
529
+ expect(postJoin.options.join).toBeDefined();
530
+ expect(postJoin.options.join).toHaveLength(1);
531
+
532
+ const userJoin = postJoin.options.join![0]!;
533
+ assert(userJoin.options !== false);
534
+ expect(userJoin.relation.name).toBe("user");
535
+ });
536
+ });
537
+
538
+ describe("IndexedConditionBuilder", () => {
539
+ const testSchema = schema((s) =>
540
+ s.addTable("users", (t) =>
541
+ t
542
+ .addColumn("id", idColumn())
543
+ .addColumn("email", column("string"))
544
+ .addColumn("name", column("string"))
545
+ .addColumn("age", column("integer").nullable())
546
+ .addColumn("bio", column("string").nullable()) // Not indexed
547
+ .createIndex("_primary", ["id"], { unique: true })
548
+ .createIndex("idx_email", ["email"], { unique: true })
549
+ .createIndex("idx_name_age", ["name", "age"]),
550
+ ),
551
+ );
552
+
553
+ const usersTable = testSchema.tables.users;
554
+
555
+ it("should enforce indexed columns at runtime", () => {
556
+ // Collect all indexed column names from all indexes
557
+ const indexedColumns = new Set<string>();
558
+ for (const index of Object.values(usersTable.indexes)) {
559
+ for (const col of index.columns) {
560
+ indexedColumns.add(col.ormName);
561
+ }
562
+ }
563
+
564
+ const builder = createIndexedBuilder(usersTable.columns, indexedColumns);
565
+
566
+ // Should work with indexed columns
567
+ expect(() => builder("id", "=", "123")).not.toThrow();
568
+ expect(() => builder("email", "=", "test@example.com")).not.toThrow();
569
+ expect(() => builder("name", "=", "Alice")).not.toThrow();
570
+ expect(() => builder("age", ">", 18)).not.toThrow();
571
+
572
+ // Should throw when using non-indexed column
573
+ expect(() => builder("bio" as "email", "=", "Some bio")).toThrow('Column "bio" is not indexed');
574
+ });
575
+
576
+ it("should work with complex conditions", () => {
577
+ const indexedColumns = new Set(["id", "email", "name", "age"]);
578
+ const builder = createIndexedBuilder(usersTable.columns, indexedColumns);
579
+
580
+ // Complex AND condition with indexed columns
581
+ const condition = builder.and(
582
+ builder("name", "=", "Alice"),
583
+ builder("age", ">", 18),
584
+ builder("email", "contains", "example"),
585
+ );
586
+
587
+ expect(condition).toEqual({
588
+ type: "and",
589
+ items: [
590
+ {
591
+ type: "compare",
592
+ a: usersTable.columns.name,
593
+ operator: "=",
594
+ b: "Alice",
595
+ },
596
+ {
597
+ type: "compare",
598
+ a: usersTable.columns.age,
599
+ operator: ">",
600
+ b: 18,
601
+ },
602
+ {
603
+ type: "compare",
604
+ a: usersTable.columns.email,
605
+ operator: "contains",
606
+ b: "example",
607
+ },
608
+ ],
609
+ });
610
+ });
611
+
612
+ it("should provide helpful error message listing available columns", () => {
613
+ const indexedColumns = new Set(["id", "email"]);
614
+ const builder = createIndexedBuilder(usersTable.columns, indexedColumns);
615
+
616
+ expect(() => builder("name" as "email", "=", "Alice")).toThrow(
617
+ "Only indexed columns can be used in Unit of Work queries. Available indexed columns: id, email",
618
+ );
619
+ });
620
+
621
+ it("should work with all builder helper methods", () => {
622
+ const indexedColumns = new Set(["id", "email", "age"]);
623
+ const builder = createIndexedBuilder(usersTable.columns, indexedColumns);
624
+
625
+ // isNull
626
+ expect(() => builder.isNull("age")).not.toThrow();
627
+ expect(() => builder.isNull("bio" as "age")).toThrow('Column "bio" is not indexed');
628
+
629
+ // isNotNull
630
+ expect(() => builder.isNotNull("email")).not.toThrow();
631
+ expect(() => builder.isNotNull("bio" as "email")).toThrow('Column "bio" is not indexed');
632
+
633
+ // not
634
+ const notCondition = builder.not(builder("id", "=", "123"));
635
+ expect(notCondition).toEqual({
636
+ type: "not",
637
+ item: {
638
+ type: "compare",
639
+ a: usersTable.columns.id,
640
+ operator: "=",
641
+ b: "123",
642
+ },
643
+ });
644
+
645
+ // or
646
+ const orCondition = builder.or(builder("email", "contains", "gmail"), builder("age", ">", 30));
647
+ expect(orCondition).toEqual({
648
+ type: "or",
649
+ items: [
650
+ {
651
+ type: "compare",
652
+ a: usersTable.columns.email,
653
+ operator: "contains",
654
+ b: "gmail",
655
+ },
656
+ {
657
+ type: "compare",
658
+ a: usersTable.columns.age,
659
+ operator: ">",
660
+ b: 30,
661
+ },
662
+ ],
663
+ });
664
+ });
665
+
666
+ it("should enforce index restrictions in nested conditions", () => {
667
+ const indexedColumns = new Set(["id", "email"]);
668
+ const builder = createIndexedBuilder(usersTable.columns, indexedColumns);
669
+
670
+ // This should throw because "name" is not indexed, even though it's nested
671
+ expect(() => {
672
+ builder.and(
673
+ builder("email", "=", "test@example.com"),
674
+ builder("name" as "email", "=", "Alice"),
675
+ );
676
+ }).toThrow('Column "name" is not indexed');
677
+
678
+ // This should throw because "bio" is not indexed, even in OR
679
+ expect(() => {
680
+ builder.or(builder("id", "=", "123"), builder("bio" as "id", "=", "Some bio"));
681
+ }).toThrow('Column "bio" is not indexed');
682
+ });
683
+
684
+ describe("type safety", () => {
685
+ it("should restrict to only indexed columns at type level", () => {
686
+ // This schema has "bio" column that is NOT indexed
687
+ const typeTestSchema = schema((s) =>
688
+ s.addTable("users", (t) =>
689
+ t
690
+ .addColumn("id", idColumn())
691
+ .addColumn("email", column("string"))
692
+ .addColumn("name", column("string"))
693
+ .addColumn("age", column("integer").nullable())
694
+ .addColumn("bio", column("string").nullable()) // Not indexed!
695
+ .createIndex("idx_email", ["email"], { unique: true })
696
+ .createIndex("idx_name_age", ["name", "age"]),
697
+ ),
698
+ );
699
+
700
+ type _IdColumnName = InferIdColumnName<typeof typeTestSchema.tables.users>;
701
+ expectTypeOf<_IdColumnName>().toEqualTypeOf<"id">();
702
+ type _IndexColumnNames = IndexColumns<
703
+ typeof typeTestSchema.tables.users.indexes.idx_name_age
704
+ >;
705
+ expectTypeOf<_IndexColumnNames>().toEqualTypeOf<"name" | "age">();
706
+
707
+ const uow = createUnitOfWork(
708
+ typeTestSchema,
709
+ createMockCompiler<typeof typeTestSchema>(),
710
+ createMockExecutor(),
711
+ createMockDecoder<typeof typeTestSchema>(),
712
+ );
713
+ expectTypeOf(uow.schema).toEqualTypeOf(typeTestSchema);
714
+ expectTypeOf<keyof typeof typeTestSchema.tables>().toEqualTypeOf<"users">();
715
+ type _Query = AbstractQuery<typeof typeTestSchema>;
716
+ expectTypeOf<Parameters<_Query["create"]>[0]>().toEqualTypeOf<"users">();
717
+
718
+ expectTypeOf(uow.find).parameter(0).toEqualTypeOf<"users">();
719
+
720
+ uow.find("users", (b) =>
721
+ b.whereIndex("primary", (eb) => {
722
+ type _EbFirstParameter = Parameters<typeof eb>[0];
723
+ expectTypeOf<_EbFirstParameter>().toEqualTypeOf<"id">();
724
+ return eb("id", "=", "123");
725
+ }),
726
+ );
727
+
728
+ uow.find("users", (b) =>
729
+ b.whereIndex("idx_email", (eb) => {
730
+ expectTypeOf(eb).parameter(0).toEqualTypeOf<"email">();
731
+ return eb("email", "=", "123");
732
+ }),
733
+ );
734
+
735
+ uow.find("users", (b) =>
736
+ b.whereIndex("idx_name_age", (eb) => {
737
+ expectTypeOf(eb).parameter(0).toEqualTypeOf<"name" | "age">();
738
+ return eb("name", "=", "123");
739
+ }),
740
+ );
741
+ });
742
+ });
743
+ });
744
+
745
+ describe("UpdateBuilder with string ID", () => {
746
+ const testSchema = schema((s) =>
747
+ s.addTable("users", (t) => t.addColumn("id", idColumn()).addColumn("name", "string")),
748
+ );
749
+
750
+ it("should allow update with string ID", async () => {
751
+ const uow = new UnitOfWork(
752
+ testSchema,
753
+ createMockCompiler(),
754
+ createMockExecutor(),
755
+ createMockDecoder(),
756
+ );
757
+
758
+ // Should work with string ID
759
+ uow.update("users", "user-123", (b) => b.set({ name: "New Name" }));
760
+
761
+ const ops = uow.getMutationOperations();
762
+ expect(ops).toHaveLength(1);
763
+ expect(ops).toMatchObject([
764
+ {
765
+ type: "update",
766
+ id: "user-123",
767
+ checkVersion: false,
768
+ },
769
+ ]);
770
+ });
771
+
772
+ it("should throw when using check() with string ID", async () => {
773
+ const uow = new UnitOfWork(
774
+ testSchema,
775
+ createMockCompiler(),
776
+ createMockExecutor(),
777
+ createMockDecoder(),
778
+ );
779
+
780
+ // Should throw because check() is not allowed with string ID
781
+ expect(() => {
782
+ uow.update("users", "user-123", (b) => b.set({ name: "New Name" }).check());
783
+ }).toThrow(
784
+ 'Cannot use check() with a string ID on table "users". Version checking requires a FragnoId with version information.',
785
+ );
786
+ });
787
+ });
788
+
789
+ describe("DeleteBuilder with string ID", () => {
790
+ const testSchema = schema((s) =>
791
+ s.addTable("users", (t) => t.addColumn("id", idColumn()).addColumn("name", "string")),
792
+ );
793
+
794
+ it("should allow delete with string ID", async () => {
795
+ const uow = new UnitOfWork(
796
+ testSchema,
797
+ createMockCompiler(),
798
+ createMockExecutor(),
799
+ createMockDecoder(),
800
+ );
801
+
802
+ // Should work with string ID
803
+ uow.delete("users", "user-123");
804
+
805
+ const ops = uow.getMutationOperations();
806
+ expect(ops).toMatchObject([
807
+ {
808
+ type: "delete",
809
+ id: "user-123",
810
+ checkVersion: false,
811
+ },
812
+ ]);
813
+ });
814
+
815
+ it("should throw when using check() with string ID", async () => {
816
+ const uow = new UnitOfWork(
817
+ testSchema,
818
+ createMockCompiler(),
819
+ createMockExecutor(),
820
+ createMockDecoder(),
821
+ );
822
+
823
+ // Should throw because check() is not allowed with string ID
824
+ expect(() => {
825
+ uow.delete("users", "user-123", (b) => b.check());
826
+ }).toThrow(
827
+ 'Cannot use check() with a string ID on table "users". Version checking requires a FragnoId with version information.',
828
+ );
829
+ });
830
+ });
831
+
832
+ describe("getCreatedIds", () => {
833
+ const testSchema = schema((s) =>
834
+ s.addTable("users", (t) =>
835
+ t.addColumn("id", idColumn()).addColumn("email", "string").addColumn("name", "string"),
836
+ ),
837
+ );
838
+
839
+ it("should return created IDs after executeMutations with internal IDs", async () => {
840
+ const executor = {
841
+ executeRetrievalPhase: async () => [],
842
+ executeMutationPhase: async () => ({
843
+ success: true,
844
+ createdInternalIds: [1n, 2n],
845
+ }),
846
+ };
847
+
848
+ const uow = new UnitOfWork(testSchema, createMockCompiler(), executor, createMockDecoder());
849
+
850
+ uow.create("users", { email: "user1@example.com", name: "User 1" });
851
+ uow.create("users", { email: "user2@example.com", name: "User 2" });
852
+
853
+ await uow.executeMutations();
854
+ const createdIds = uow.getCreatedIds();
855
+
856
+ expect(createdIds).toHaveLength(2);
857
+ expect(createdIds[0].externalId).toBeDefined();
858
+ expect(createdIds[0].internalId).toBe(1n);
859
+ expect(createdIds[0].version).toBe(0);
860
+ expect(createdIds[1].externalId).toBeDefined();
861
+ expect(createdIds[1].internalId).toBe(2n);
862
+ expect(createdIds[1].version).toBe(0);
863
+ });
864
+
865
+ it("should return created IDs without internal IDs when not supported", async () => {
866
+ const executor = {
867
+ executeRetrievalPhase: async () => [],
868
+ executeMutationPhase: async () => ({
869
+ success: true,
870
+ createdInternalIds: [null, null],
871
+ }),
872
+ };
873
+
874
+ const uow = new UnitOfWork(testSchema, createMockCompiler(), executor, createMockDecoder());
875
+
876
+ uow.create("users", { email: "user1@example.com", name: "User 1" });
877
+ uow.create("users", { email: "user2@example.com", name: "User 2" });
878
+
879
+ await uow.executeMutations();
880
+ const createdIds = uow.getCreatedIds();
881
+
882
+ expect(createdIds).toHaveLength(2);
883
+ expect(createdIds[0].externalId).toBeDefined();
884
+ expect(createdIds[0].internalId).toBeUndefined();
885
+ expect(createdIds[1].externalId).toBeDefined();
886
+ expect(createdIds[1].internalId).toBeUndefined();
887
+ });
888
+
889
+ it("should preserve user-provided external IDs", async () => {
890
+ const executor = {
891
+ executeRetrievalPhase: async () => [],
892
+ executeMutationPhase: async () => ({
893
+ success: true,
894
+ createdInternalIds: [1n],
895
+ }),
896
+ };
897
+
898
+ const uow = new UnitOfWork(testSchema, createMockCompiler(), executor, createMockDecoder());
899
+
900
+ uow.create("users", { id: "my-custom-id", email: "user@example.com", name: "User" });
901
+
902
+ await uow.executeMutations();
903
+ const createdIds = uow.getCreatedIds();
904
+
905
+ expect(createdIds).toHaveLength(1);
906
+ expect(createdIds[0].externalId).toBe("my-custom-id");
907
+ expect(createdIds[0].internalId).toBe(1n);
908
+ });
909
+
910
+ it("should only return IDs for create operations, not updates or deletes", async () => {
911
+ const executor = {
912
+ executeRetrievalPhase: async () => [],
913
+ executeMutationPhase: async () => ({
914
+ success: true,
915
+ createdInternalIds: [1n],
916
+ }),
917
+ };
918
+
919
+ const uow = new UnitOfWork(testSchema, createMockCompiler(), executor, createMockDecoder());
920
+
921
+ uow.create("users", { email: "user@example.com", name: "User" });
922
+ uow.update("users", "existing-id", (b) => b.set({ name: "Updated" }));
923
+ uow.delete("users", "other-id");
924
+
925
+ await uow.executeMutations();
926
+ const createdIds = uow.getCreatedIds();
927
+
928
+ // Only one create operation, so only one ID returned
929
+ expect(createdIds).toHaveLength(1);
930
+ expect(createdIds[0].internalId).toBe(1n);
931
+ });
932
+
933
+ it("should throw when called before executeMutations", () => {
934
+ const uow = new UnitOfWork(
935
+ testSchema,
936
+ createMockCompiler(),
937
+ createMockExecutor(),
938
+ createMockDecoder(),
939
+ );
940
+
941
+ uow.create("users", { email: "user@example.com", name: "User" });
942
+
943
+ expect(() => uow.getCreatedIds()).toThrow(
944
+ "getCreatedIds() can only be called after executeMutations()",
945
+ );
946
+ });
947
+ });