@fragno-dev/db 0.1.13 → 0.1.15

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 (178) hide show
  1. package/.turbo/turbo-build.log +179 -132
  2. package/CHANGELOG.md +30 -0
  3. package/dist/adapters/adapters.d.ts +27 -1
  4. package/dist/adapters/adapters.d.ts.map +1 -1
  5. package/dist/adapters/adapters.js.map +1 -1
  6. package/dist/adapters/drizzle/drizzle-adapter.d.ts +5 -1
  7. package/dist/adapters/drizzle/drizzle-adapter.d.ts.map +1 -1
  8. package/dist/adapters/drizzle/drizzle-adapter.js +15 -3
  9. package/dist/adapters/drizzle/drizzle-adapter.js.map +1 -1
  10. package/dist/adapters/drizzle/drizzle-query.js +7 -5
  11. package/dist/adapters/drizzle/drizzle-query.js.map +1 -1
  12. package/dist/adapters/drizzle/drizzle-uow-compiler.d.ts +0 -1
  13. package/dist/adapters/drizzle/drizzle-uow-compiler.d.ts.map +1 -1
  14. package/dist/adapters/drizzle/drizzle-uow-compiler.js +76 -44
  15. package/dist/adapters/drizzle/drizzle-uow-compiler.js.map +1 -1
  16. package/dist/adapters/drizzle/drizzle-uow-decoder.js +23 -16
  17. package/dist/adapters/drizzle/drizzle-uow-decoder.js.map +1 -1
  18. package/dist/adapters/drizzle/drizzle-uow-executor.js +18 -7
  19. package/dist/adapters/drizzle/drizzle-uow-executor.js.map +1 -1
  20. package/dist/adapters/drizzle/generate.d.ts +4 -1
  21. package/dist/adapters/drizzle/generate.d.ts.map +1 -1
  22. package/dist/adapters/drizzle/generate.js +11 -18
  23. package/dist/adapters/drizzle/generate.js.map +1 -1
  24. package/dist/adapters/drizzle/shared.d.ts +14 -1
  25. package/dist/adapters/drizzle/shared.d.ts.map +1 -0
  26. package/dist/adapters/kysely/kysely-adapter.d.ts +5 -1
  27. package/dist/adapters/kysely/kysely-adapter.d.ts.map +1 -1
  28. package/dist/adapters/kysely/kysely-adapter.js +14 -3
  29. package/dist/adapters/kysely/kysely-adapter.js.map +1 -1
  30. package/dist/adapters/kysely/kysely-query-builder.js +1 -1
  31. package/dist/adapters/kysely/kysely-query-compiler.js +3 -2
  32. package/dist/adapters/kysely/kysely-query-compiler.js.map +1 -1
  33. package/dist/adapters/kysely/kysely-query.d.ts +1 -0
  34. package/dist/adapters/kysely/kysely-query.d.ts.map +1 -1
  35. package/dist/adapters/kysely/kysely-query.js +28 -19
  36. package/dist/adapters/kysely/kysely-query.js.map +1 -1
  37. package/dist/adapters/kysely/kysely-shared.d.ts +14 -0
  38. package/dist/adapters/kysely/kysely-shared.d.ts.map +1 -0
  39. package/dist/adapters/kysely/kysely-shared.js +16 -1
  40. package/dist/adapters/kysely/kysely-shared.js.map +1 -1
  41. package/dist/adapters/kysely/kysely-uow-compiler.js +68 -16
  42. package/dist/adapters/kysely/kysely-uow-compiler.js.map +1 -1
  43. package/dist/adapters/kysely/kysely-uow-executor.js +8 -4
  44. package/dist/adapters/kysely/kysely-uow-executor.js.map +1 -1
  45. package/dist/adapters/kysely/migration/execute-base.js +1 -1
  46. package/dist/adapters/kysely/migration/execute-base.js.map +1 -1
  47. package/dist/db-fragment-definition-builder.d.ts +152 -0
  48. package/dist/db-fragment-definition-builder.d.ts.map +1 -0
  49. package/dist/db-fragment-definition-builder.js +137 -0
  50. package/dist/db-fragment-definition-builder.js.map +1 -0
  51. package/dist/fragments/internal-fragment.d.ts +19 -0
  52. package/dist/fragments/internal-fragment.d.ts.map +1 -0
  53. package/dist/fragments/internal-fragment.js +39 -0
  54. package/dist/fragments/internal-fragment.js.map +1 -0
  55. package/dist/migration-engine/generation-engine.d.ts.map +1 -1
  56. package/dist/migration-engine/generation-engine.js +35 -15
  57. package/dist/migration-engine/generation-engine.js.map +1 -1
  58. package/dist/mod.d.ts +8 -18
  59. package/dist/mod.d.ts.map +1 -1
  60. package/dist/mod.js +7 -34
  61. package/dist/mod.js.map +1 -1
  62. package/dist/node_modules/.pnpm/rou3@0.7.8/node_modules/rou3/dist/index.js +165 -0
  63. package/dist/node_modules/.pnpm/rou3@0.7.8/node_modules/rou3/dist/index.js.map +1 -0
  64. package/dist/packages/fragno/dist/api/bind-services.js +20 -0
  65. package/dist/packages/fragno/dist/api/bind-services.js.map +1 -0
  66. package/dist/packages/fragno/dist/api/error.js +48 -0
  67. package/dist/packages/fragno/dist/api/error.js.map +1 -0
  68. package/dist/packages/fragno/dist/api/fragment-definition-builder.js +320 -0
  69. package/dist/packages/fragno/dist/api/fragment-definition-builder.js.map +1 -0
  70. package/dist/packages/fragno/dist/api/fragment-instantiator.js +487 -0
  71. package/dist/packages/fragno/dist/api/fragment-instantiator.js.map +1 -0
  72. package/dist/packages/fragno/dist/api/fragno-response.js +73 -0
  73. package/dist/packages/fragno/dist/api/fragno-response.js.map +1 -0
  74. package/dist/packages/fragno/dist/api/internal/response-stream.js +81 -0
  75. package/dist/packages/fragno/dist/api/internal/response-stream.js.map +1 -0
  76. package/dist/packages/fragno/dist/api/internal/route.js +10 -0
  77. package/dist/packages/fragno/dist/api/internal/route.js.map +1 -0
  78. package/dist/packages/fragno/dist/api/mutable-request-state.js +97 -0
  79. package/dist/packages/fragno/dist/api/mutable-request-state.js.map +1 -0
  80. package/dist/packages/fragno/dist/api/request-context-storage.js +43 -0
  81. package/dist/packages/fragno/dist/api/request-context-storage.js.map +1 -0
  82. package/dist/packages/fragno/dist/api/request-input-context.js +118 -0
  83. package/dist/packages/fragno/dist/api/request-input-context.js.map +1 -0
  84. package/dist/packages/fragno/dist/api/request-middleware.js +83 -0
  85. package/dist/packages/fragno/dist/api/request-middleware.js.map +1 -0
  86. package/dist/packages/fragno/dist/api/request-output-context.js +119 -0
  87. package/dist/packages/fragno/dist/api/request-output-context.js.map +1 -0
  88. package/dist/packages/fragno/dist/api/route.js +17 -0
  89. package/dist/packages/fragno/dist/api/route.js.map +1 -0
  90. package/dist/packages/fragno/dist/internal/symbols.js +10 -0
  91. package/dist/packages/fragno/dist/internal/symbols.js.map +1 -0
  92. package/dist/query/cursor.d.ts +10 -2
  93. package/dist/query/cursor.d.ts.map +1 -1
  94. package/dist/query/cursor.js +11 -4
  95. package/dist/query/cursor.js.map +1 -1
  96. package/dist/query/execute-unit-of-work.d.ts +123 -0
  97. package/dist/query/execute-unit-of-work.d.ts.map +1 -0
  98. package/dist/query/execute-unit-of-work.js +184 -0
  99. package/dist/query/execute-unit-of-work.js.map +1 -0
  100. package/dist/query/query.d.ts +3 -3
  101. package/dist/query/query.d.ts.map +1 -1
  102. package/dist/query/result-transform.js +4 -2
  103. package/dist/query/result-transform.js.map +1 -1
  104. package/dist/query/retry-policy.d.ts +88 -0
  105. package/dist/query/retry-policy.d.ts.map +1 -0
  106. package/dist/query/retry-policy.js +61 -0
  107. package/dist/query/retry-policy.js.map +1 -0
  108. package/dist/query/unit-of-work.d.ts +171 -32
  109. package/dist/query/unit-of-work.d.ts.map +1 -1
  110. package/dist/query/unit-of-work.js +530 -133
  111. package/dist/query/unit-of-work.js.map +1 -1
  112. package/dist/schema/serialize.js +12 -7
  113. package/dist/schema/serialize.js.map +1 -1
  114. package/dist/with-database.d.ts +28 -0
  115. package/dist/with-database.d.ts.map +1 -0
  116. package/dist/with-database.js +34 -0
  117. package/dist/with-database.js.map +1 -0
  118. package/package.json +10 -3
  119. package/src/adapters/adapters.ts +30 -0
  120. package/src/adapters/drizzle/drizzle-adapter-pglite.test.ts +86 -17
  121. package/src/adapters/drizzle/drizzle-adapter-sqlite.test.ts +291 -7
  122. package/src/adapters/drizzle/drizzle-adapter.test.ts +3 -51
  123. package/src/adapters/drizzle/drizzle-adapter.ts +35 -7
  124. package/src/adapters/drizzle/drizzle-query.ts +25 -15
  125. package/src/adapters/drizzle/drizzle-uow-compiler-mysql.test.ts +1442 -0
  126. package/src/adapters/drizzle/drizzle-uow-compiler-sqlite.test.ts +1414 -0
  127. package/src/adapters/drizzle/drizzle-uow-compiler.test.ts +78 -61
  128. package/src/adapters/drizzle/drizzle-uow-compiler.ts +123 -42
  129. package/src/adapters/drizzle/drizzle-uow-decoder.ts +34 -27
  130. package/src/adapters/drizzle/drizzle-uow-executor.ts +41 -8
  131. package/src/adapters/drizzle/generate.test.ts +102 -269
  132. package/src/adapters/drizzle/generate.ts +12 -30
  133. package/src/adapters/drizzle/test-utils.ts +36 -5
  134. package/src/adapters/kysely/kysely-adapter-pglite.test.ts +66 -22
  135. package/src/adapters/kysely/kysely-adapter-sqlite.test.ts +156 -0
  136. package/src/adapters/kysely/kysely-adapter.ts +25 -2
  137. package/src/adapters/kysely/kysely-query-compiler.ts +3 -8
  138. package/src/adapters/kysely/kysely-query.ts +57 -37
  139. package/src/adapters/kysely/kysely-shared.ts +34 -0
  140. package/src/adapters/kysely/kysely-uow-compiler.test.ts +62 -74
  141. package/src/adapters/kysely/kysely-uow-compiler.ts +92 -24
  142. package/src/adapters/kysely/kysely-uow-executor.ts +26 -7
  143. package/src/adapters/kysely/kysely-uow-joins.test.ts +33 -50
  144. package/src/adapters/kysely/migration/execute-base.ts +1 -1
  145. package/src/db-fragment-definition-builder.test.ts +887 -0
  146. package/src/db-fragment-definition-builder.ts +506 -0
  147. package/src/db-fragment-instantiator.test.ts +467 -0
  148. package/src/db-fragment-integration.test.ts +408 -0
  149. package/src/fragments/internal-fragment.test.ts +160 -0
  150. package/src/fragments/internal-fragment.ts +85 -0
  151. package/src/migration-engine/generation-engine.test.ts +58 -15
  152. package/src/migration-engine/generation-engine.ts +78 -25
  153. package/src/mod.ts +35 -43
  154. package/src/query/cursor.test.ts +119 -0
  155. package/src/query/cursor.ts +17 -4
  156. package/src/query/execute-unit-of-work.test.ts +1310 -0
  157. package/src/query/execute-unit-of-work.ts +463 -0
  158. package/src/query/query.ts +4 -4
  159. package/src/query/result-transform.test.ts +129 -0
  160. package/src/query/result-transform.ts +4 -1
  161. package/src/query/retry-policy.test.ts +217 -0
  162. package/src/query/retry-policy.ts +141 -0
  163. package/src/query/unit-of-work-coordinator.test.ts +833 -0
  164. package/src/query/unit-of-work-types.test.ts +15 -2
  165. package/src/query/unit-of-work.test.ts +878 -200
  166. package/src/query/unit-of-work.ts +963 -321
  167. package/src/schema/serialize.ts +22 -11
  168. package/src/with-database.ts +140 -0
  169. package/tsdown.config.ts +1 -0
  170. package/dist/fragment.d.ts +0 -54
  171. package/dist/fragment.d.ts.map +0 -1
  172. package/dist/fragment.js +0 -92
  173. package/dist/fragment.js.map +0 -1
  174. package/dist/shared/settings-schema.js +0 -36
  175. package/dist/shared/settings-schema.js.map +0 -1
  176. package/src/fragment.test.ts +0 -341
  177. package/src/fragment.ts +0 -198
  178. package/src/shared/settings-schema.ts +0 -61
@@ -1,7 +1,6 @@
1
1
  import { describe, it, expect, assert, expectTypeOf } from "vitest";
2
- import { column, schema, idColumn } from "../schema/create";
2
+ import { column, schema, idColumn, FragnoId } from "../schema/create";
3
3
  import {
4
- UnitOfWork,
5
4
  type UOWCompiler,
6
5
  type UOWDecoder,
7
6
  createUnitOfWork,
@@ -9,14 +8,10 @@ import {
9
8
  type IndexColumns,
10
9
  } from "./unit-of-work";
11
10
  import { createIndexedBuilder } from "./condition-builder";
12
- import type { AnySchema } from "../schema/create";
13
11
  import type { AbstractQuery } from "./query";
14
12
 
15
13
  // Mock compiler and executor for testing
16
- function createMockCompiler<TSchema extends AnySchema = AnySchema>(): UOWCompiler<
17
- TSchema,
18
- unknown
19
- > {
14
+ function createMockCompiler(): UOWCompiler<unknown> {
20
15
  return {
21
16
  compileRetrievalOperation: () => null,
22
17
  compileMutationOperation: () => null,
@@ -30,7 +25,7 @@ function createMockExecutor() {
30
25
  };
31
26
  }
32
27
 
33
- function createMockDecoder<TSchema extends AnySchema = AnySchema>(): UOWDecoder<TSchema> {
28
+ function createMockDecoder(): UOWDecoder {
34
29
  return (rawResults, operations) => {
35
30
  if (rawResults.length !== operations.length) {
36
31
  throw new Error("rawResults and operations must have the same length");
@@ -53,13 +48,8 @@ describe("FindBuilder", () => {
53
48
  ),
54
49
  );
55
50
 
56
- const uow = new UnitOfWork(
57
- testSchema,
58
- createMockCompiler(),
59
- createMockExecutor(),
60
- createMockDecoder(),
61
- );
62
- uow.find("users", (b) => b.whereIndex("primary"));
51
+ const uow = createUnitOfWork(createMockCompiler(), createMockExecutor(), createMockDecoder());
52
+ uow.forSchema(testSchema).find("users", (b) => b.whereIndex("primary"));
63
53
 
64
54
  const ops = uow.getRetrievalOperations();
65
55
  expect(ops).toHaveLength(1);
@@ -77,15 +67,12 @@ describe("FindBuilder", () => {
77
67
  ),
78
68
  );
79
69
 
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
- );
70
+ const uow = createUnitOfWork(createMockCompiler(), createMockExecutor(), createMockDecoder());
71
+ uow
72
+ .forSchema(testSchema)
73
+ .find("users", (b) =>
74
+ b.whereIndex("idx_email", (eb) => eb("email", "=", "test@example.com")),
75
+ );
89
76
 
90
77
  const ops = uow.getRetrievalOperations();
91
78
  expect(ops).toHaveLength(1);
@@ -97,15 +84,12 @@ describe("FindBuilder", () => {
97
84
  s.addTable("users", (t) => t.addColumn("id", idColumn()).addColumn("name", "string")),
98
85
  );
99
86
 
100
- const uow = new UnitOfWork(
101
- testSchema,
102
- createMockCompiler(),
103
- createMockExecutor(),
104
- createMockDecoder(),
105
- );
87
+ const uow = createUnitOfWork(createMockCompiler(), createMockExecutor(), createMockDecoder());
106
88
 
107
89
  const cursor = "eyJpbmRleFZhbHVlcyI6eyJpZCI6InVzZXIxMjMifSwiZGlyZWN0aW9uIjoiZm9yd2FyZCJ9";
108
- uow.find("users", (b) => b.whereIndex("primary").after(cursor).pageSize(10));
90
+ uow
91
+ .forSchema(testSchema)
92
+ .find("users", (b) => b.whereIndex("primary").after(cursor).pageSize(10));
109
93
 
110
94
  const ops = uow.getRetrievalOperations();
111
95
  expect(ops).toHaveLength(1);
@@ -120,15 +104,12 @@ describe("FindBuilder", () => {
120
104
  s.addTable("users", (t) => t.addColumn("id", idColumn()).addColumn("name", "string")),
121
105
  );
122
106
 
123
- const uow = new UnitOfWork(
124
- testSchema,
125
- createMockCompiler(),
126
- createMockExecutor(),
127
- createMockDecoder(),
128
- );
107
+ const uow = createUnitOfWork(createMockCompiler(), createMockExecutor(), createMockDecoder());
129
108
 
130
109
  const cursor = "eyJpbmRleFZhbHVlcyI6eyJpZCI6InVzZXI0NTYifSwiZGlyZWN0aW9uIjoiYmFja3dhcmQifQ==";
131
- uow.find("users", (b) => b.whereIndex("primary").before(cursor).pageSize(5));
110
+ uow
111
+ .forSchema(testSchema)
112
+ .find("users", (b) => b.whereIndex("primary").before(cursor).pageSize(5));
132
113
 
133
114
  const ops = uow.getRetrievalOperations();
134
115
  expect(ops).toHaveLength(1);
@@ -143,14 +124,9 @@ describe("FindBuilder", () => {
143
124
  s.addTable("users", (t) => t.addColumn("id", idColumn()).addColumn("name", "string")),
144
125
  );
145
126
 
146
- const uow = new UnitOfWork(
147
- testSchema,
148
- createMockCompiler(),
149
- createMockExecutor(),
150
- createMockDecoder(),
151
- );
127
+ const uow = createUnitOfWork(createMockCompiler(), createMockExecutor(), createMockDecoder());
152
128
  expect(() => {
153
- uow.find("users", (b) => b.whereIndex("nonexistent" as "primary"));
129
+ uow.forSchema(testSchema).find("users", (b) => b.whereIndex("nonexistent" as "primary"));
154
130
  }).toThrow('Index "nonexistent" not found on table "users"');
155
131
  });
156
132
 
@@ -159,14 +135,9 @@ describe("FindBuilder", () => {
159
135
  s.addTable("users", (t) => t.addColumn("id", idColumn()).addColumn("name", "string")),
160
136
  );
161
137
 
162
- const uow = new UnitOfWork(
163
- testSchema,
164
- createMockCompiler(),
165
- createMockExecutor(),
166
- createMockDecoder(),
167
- );
138
+ const uow = createUnitOfWork(createMockCompiler(), createMockExecutor(), createMockDecoder());
168
139
  expect(() => {
169
- uow.find("users", (b) => b);
140
+ uow.forSchema(testSchema).find("users", (b) => b);
170
141
  }).toThrow(
171
142
  'Must specify an index using .whereIndex() before finalizing find operation on table "users"',
172
143
  );
@@ -179,13 +150,8 @@ describe("FindBuilder", () => {
179
150
  ),
180
151
  );
181
152
 
182
- const uow = new UnitOfWork(
183
- testSchema,
184
- createMockCompiler(),
185
- createMockExecutor(),
186
- createMockDecoder(),
187
- );
188
- uow.find("users", (b) => b.whereIndex("primary").selectCount());
153
+ const uow = createUnitOfWork(createMockCompiler(), createMockExecutor(), createMockDecoder());
154
+ uow.forSchema(testSchema).find("users", (b) => b.whereIndex("primary").selectCount());
189
155
 
190
156
  const ops = uow.getRetrievalOperations();
191
157
  expect(ops).toHaveLength(1);
@@ -197,27 +163,21 @@ describe("FindBuilder", () => {
197
163
  s.addTable("users", (t) => t.addColumn("id", idColumn()).addColumn("name", "string")),
198
164
  );
199
165
 
200
- const uow = new UnitOfWork(
201
- testSchema,
202
- createMockCompiler(),
203
- createMockExecutor(),
204
- createMockDecoder(),
205
- );
166
+ const uow = createUnitOfWork(createMockCompiler(), createMockExecutor(), createMockDecoder());
206
167
 
207
168
  // select() then selectCount()
208
169
  expect(() => {
209
- uow.find("users", (b) => b.whereIndex("primary").select(["name"]).selectCount());
170
+ uow
171
+ .forSchema(testSchema)
172
+ .find("users", (b) => b.whereIndex("primary").select(["name"]).selectCount());
210
173
  }).toThrow(/cannot call selectCount/i);
211
174
 
212
175
  // selectCount() then select()
213
- const uow2 = new UnitOfWork(
214
- testSchema,
215
- createMockCompiler(),
216
- createMockExecutor(),
217
- createMockDecoder(),
218
- );
176
+ const uow2 = createUnitOfWork(createMockCompiler(), createMockExecutor(), createMockDecoder());
219
177
  expect(() => {
220
- uow2.find("users", (b) => b.whereIndex("primary").selectCount().select(["name"]));
178
+ uow2
179
+ .forSchema(testSchema)
180
+ .find("users", (b) => b.whereIndex("primary").selectCount().select(["name"]));
221
181
  }).toThrow(/cannot call select/i);
222
182
  });
223
183
 
@@ -232,13 +192,10 @@ describe("FindBuilder", () => {
232
192
  ),
233
193
  );
234
194
 
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"));
195
+ const uow = createUnitOfWork(createMockCompiler(), createMockExecutor(), createMockDecoder());
196
+ uow
197
+ .forSchema(testSchema)
198
+ .find("users", (b) => b.whereIndex("primary").orderByIndex("idx_created", "desc"));
242
199
 
243
200
  const ops = uow.getRetrievalOperations();
244
201
  expect(ops).toHaveLength(1);
@@ -271,16 +228,13 @@ describe("FindBuilder", () => {
271
228
  }),
272
229
  );
273
230
 
274
- const uow = new UnitOfWork(
275
- testSchema,
276
- createMockCompiler(),
277
- createMockExecutor(),
278
- createMockDecoder(),
279
- );
231
+ const uow = createUnitOfWork(createMockCompiler(), createMockExecutor(), createMockDecoder());
280
232
 
281
- uow.find("posts", (b) =>
282
- b.whereIndex("primary").join((jb) => jb["user"]((builder) => builder.select(["name"]))),
283
- );
233
+ uow
234
+ .forSchema(testSchema)
235
+ .find("posts", (b) =>
236
+ b.whereIndex("primary").join((jb) => jb["user"]((builder) => builder.select(["name"]))),
237
+ );
284
238
 
285
239
  const ops = uow.getRetrievalOperations();
286
240
  expect(ops).toHaveLength(1);
@@ -308,15 +262,10 @@ describe("FindBuilder", () => {
308
262
  }),
309
263
  );
310
264
 
311
- const uow = new UnitOfWork(
312
- testSchema,
313
- createMockCompiler(),
314
- createMockExecutor(),
315
- createMockDecoder(),
316
- );
265
+ const uow = createUnitOfWork(createMockCompiler(), createMockExecutor(), createMockDecoder());
317
266
 
318
267
  // Join without builder function should use default options
319
- uow.find("posts", (b) => b.whereIndex("primary").join((jb) => jb.user()));
268
+ uow.forSchema(testSchema).find("posts", (b) => b.whereIndex("primary").join((jb) => jb.user()));
320
269
 
321
270
  const ops = uow.getRetrievalOperations();
322
271
  expect(ops).toHaveLength(1);
@@ -352,22 +301,19 @@ describe("FindBuilder", () => {
352
301
  }),
353
302
  );
354
303
 
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"]),
304
+ const uow = createUnitOfWork(createMockCompiler(), createMockExecutor(), createMockDecoder());
305
+
306
+ uow
307
+ .forSchema(testSchema)
308
+ .find("posts", (b) =>
309
+ b
310
+ .whereIndex("primary")
311
+ .join((jb) =>
312
+ jb["user"]((builder) =>
313
+ builder.whereIndex("idx_name", (eb) => eb("name", "=", "Alice")).select(["name"]),
314
+ ),
368
315
  ),
369
- ),
370
- );
316
+ );
371
317
 
372
318
  const ops = uow.getRetrievalOperations();
373
319
  expect(ops).toHaveLength(1);
@@ -404,18 +350,15 @@ describe("FindBuilder", () => {
404
350
  }),
405
351
  );
406
352
 
407
- const uow = new UnitOfWork(
408
- testSchema,
409
- createMockCompiler(),
410
- createMockExecutor(),
411
- createMockDecoder(),
412
- );
353
+ const uow = createUnitOfWork(createMockCompiler(), createMockExecutor(), createMockDecoder());
413
354
 
414
- uow.find("posts", (b) =>
415
- b
416
- .whereIndex("primary")
417
- .join((jb) => jb["user"]((builder) => builder.orderByIndex("idx_created", "desc"))),
418
- );
355
+ uow
356
+ .forSchema(testSchema)
357
+ .find("posts", (b) =>
358
+ b
359
+ .whereIndex("primary")
360
+ .join((jb) => jb["user"]((builder) => builder.orderByIndex("idx_created", "desc"))),
361
+ );
419
362
 
420
363
  const ops = uow.getRetrievalOperations();
421
364
  expect(ops).toHaveLength(1);
@@ -446,16 +389,13 @@ describe("FindBuilder", () => {
446
389
  }),
447
390
  );
448
391
 
449
- const uow = new UnitOfWork(
450
- testSchema,
451
- createMockCompiler(),
452
- createMockExecutor(),
453
- createMockDecoder(),
454
- );
392
+ const uow = createUnitOfWork(createMockCompiler(), createMockExecutor(), createMockDecoder());
455
393
 
456
- uow.find("posts", (b) =>
457
- b.whereIndex("primary").join((jb) => jb["user"]((builder) => builder.pageSize(5))),
458
- );
394
+ uow
395
+ .forSchema(testSchema)
396
+ .find("posts", (b) =>
397
+ b.whereIndex("primary").join((jb) => jb["user"]((builder) => builder.pageSize(5))),
398
+ );
459
399
 
460
400
  const ops = uow.getRetrievalOperations();
461
401
  expect(ops).toHaveLength(1);
@@ -498,24 +438,21 @@ describe("FindBuilder", () => {
498
438
  }),
499
439
  );
500
440
 
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"]))),
441
+ const uow = createUnitOfWork(createMockCompiler(), createMockExecutor(), createMockDecoder());
442
+
443
+ uow
444
+ .forSchema(testSchema)
445
+ .find("comments", (b) =>
446
+ b
447
+ .whereIndex("primary")
448
+ .join((jb) =>
449
+ jb["post"]((postBuilder) =>
450
+ postBuilder
451
+ .select(["title"])
452
+ .join((jb2) => jb2["user"]((userBuilder) => userBuilder.select(["name"]))),
453
+ ),
516
454
  ),
517
- ),
518
- );
455
+ );
519
456
 
520
457
  const ops = uow.getRetrievalOperations();
521
458
  expect(ops).toHaveLength(1);
@@ -704,18 +641,17 @@ describe("IndexedConditionBuilder", () => {
704
641
  >;
705
642
  expectTypeOf<_IndexColumnNames>().toEqualTypeOf<"name" | "age">();
706
643
 
707
- const uow = createUnitOfWork(
708
- typeTestSchema,
709
- createMockCompiler<typeof typeTestSchema>(),
644
+ const baseUow = createUnitOfWork(
645
+ createMockCompiler(),
710
646
  createMockExecutor(),
711
- createMockDecoder<typeof typeTestSchema>(),
647
+ createMockDecoder(),
712
648
  );
713
- expectTypeOf(uow.schema).toEqualTypeOf(typeTestSchema);
649
+ const uow = baseUow.forSchema(typeTestSchema);
714
650
  expectTypeOf<keyof typeof typeTestSchema.tables>().toEqualTypeOf<"users">();
715
651
  type _Query = AbstractQuery<typeof typeTestSchema>;
716
652
  expectTypeOf<Parameters<_Query["create"]>[0]>().toEqualTypeOf<"users">();
717
653
 
718
- expectTypeOf(uow.find).parameter(0).toEqualTypeOf<"users">();
654
+ expectTypeOf<Parameters<typeof uow.find>[0]>().toEqualTypeOf<"users">();
719
655
 
720
656
  uow.find("users", (b) =>
721
657
  b.whereIndex("primary", (eb) => {
@@ -748,15 +684,10 @@ describe("UpdateBuilder with string ID", () => {
748
684
  );
749
685
 
750
686
  it("should allow update with string ID", async () => {
751
- const uow = new UnitOfWork(
752
- testSchema,
753
- createMockCompiler(),
754
- createMockExecutor(),
755
- createMockDecoder(),
756
- );
687
+ const uow = createUnitOfWork(createMockCompiler(), createMockExecutor(), createMockDecoder());
757
688
 
758
689
  // Should work with string ID
759
- uow.update("users", "user-123", (b) => b.set({ name: "New Name" }));
690
+ uow.forSchema(testSchema).update("users", "user-123", (b) => b.set({ name: "New Name" }));
760
691
 
761
692
  const ops = uow.getMutationOperations();
762
693
  expect(ops).toHaveLength(1);
@@ -770,16 +701,13 @@ describe("UpdateBuilder with string ID", () => {
770
701
  });
771
702
 
772
703
  it("should throw when using check() with string ID", async () => {
773
- const uow = new UnitOfWork(
774
- testSchema,
775
- createMockCompiler(),
776
- createMockExecutor(),
777
- createMockDecoder(),
778
- );
704
+ const uow = createUnitOfWork(createMockCompiler(), createMockExecutor(), createMockDecoder());
779
705
 
780
706
  // Should throw because check() is not allowed with string ID
781
707
  expect(() => {
782
- uow.update("users", "user-123", (b) => b.set({ name: "New Name" }).check());
708
+ uow
709
+ .forSchema(testSchema)
710
+ .update("users", "user-123", (b) => b.set({ name: "New Name" }).check());
783
711
  }).toThrow(
784
712
  'Cannot use check() with a string ID on table "users". Version checking requires a FragnoId with version information.',
785
713
  );
@@ -792,15 +720,10 @@ describe("DeleteBuilder with string ID", () => {
792
720
  );
793
721
 
794
722
  it("should allow delete with string ID", async () => {
795
- const uow = new UnitOfWork(
796
- testSchema,
797
- createMockCompiler(),
798
- createMockExecutor(),
799
- createMockDecoder(),
800
- );
723
+ const uow = createUnitOfWork(createMockCompiler(), createMockExecutor(), createMockDecoder());
801
724
 
802
725
  // Should work with string ID
803
- uow.delete("users", "user-123");
726
+ uow.forSchema(testSchema).delete("users", "user-123");
804
727
 
805
728
  const ops = uow.getMutationOperations();
806
729
  expect(ops).toMatchObject([
@@ -813,16 +736,11 @@ describe("DeleteBuilder with string ID", () => {
813
736
  });
814
737
 
815
738
  it("should throw when using check() with string ID", async () => {
816
- const uow = new UnitOfWork(
817
- testSchema,
818
- createMockCompiler(),
819
- createMockExecutor(),
820
- createMockDecoder(),
821
- );
739
+ const uow = createUnitOfWork(createMockCompiler(), createMockExecutor(), createMockDecoder());
822
740
 
823
741
  // Should throw because check() is not allowed with string ID
824
742
  expect(() => {
825
- uow.delete("users", "user-123", (b) => b.check());
743
+ uow.forSchema(testSchema).delete("users", "user-123", (b) => b.check());
826
744
  }).toThrow(
827
745
  'Cannot use check() with a string ID on table "users". Version checking requires a FragnoId with version information.',
828
746
  );
@@ -845,10 +763,10 @@ describe("getCreatedIds", () => {
845
763
  }),
846
764
  };
847
765
 
848
- const uow = new UnitOfWork(testSchema, createMockCompiler(), executor, createMockDecoder());
766
+ const uow = createUnitOfWork(createMockCompiler(), executor, createMockDecoder());
849
767
 
850
- uow.create("users", { email: "user1@example.com", name: "User 1" });
851
- uow.create("users", { email: "user2@example.com", name: "User 2" });
768
+ uow.forSchema(testSchema).create("users", { email: "user1@example.com", name: "User 1" });
769
+ uow.forSchema(testSchema).create("users", { email: "user2@example.com", name: "User 2" });
852
770
 
853
771
  await uow.executeMutations();
854
772
  const createdIds = uow.getCreatedIds();
@@ -871,10 +789,10 @@ describe("getCreatedIds", () => {
871
789
  }),
872
790
  };
873
791
 
874
- const uow = new UnitOfWork(testSchema, createMockCompiler(), executor, createMockDecoder());
792
+ const uow = createUnitOfWork(createMockCompiler(), executor, createMockDecoder());
875
793
 
876
- uow.create("users", { email: "user1@example.com", name: "User 1" });
877
- uow.create("users", { email: "user2@example.com", name: "User 2" });
794
+ uow.forSchema(testSchema).create("users", { email: "user1@example.com", name: "User 1" });
795
+ uow.forSchema(testSchema).create("users", { email: "user2@example.com", name: "User 2" });
878
796
 
879
797
  await uow.executeMutations();
880
798
  const createdIds = uow.getCreatedIds();
@@ -895,9 +813,11 @@ describe("getCreatedIds", () => {
895
813
  }),
896
814
  };
897
815
 
898
- const uow = new UnitOfWork(testSchema, createMockCompiler(), executor, createMockDecoder());
816
+ const uow = createUnitOfWork(createMockCompiler(), executor, createMockDecoder());
899
817
 
900
- uow.create("users", { id: "my-custom-id", email: "user@example.com", name: "User" });
818
+ uow
819
+ .forSchema(testSchema)
820
+ .create("users", { id: "my-custom-id", email: "user@example.com", name: "User" });
901
821
 
902
822
  await uow.executeMutations();
903
823
  const createdIds = uow.getCreatedIds();
@@ -916,11 +836,11 @@ describe("getCreatedIds", () => {
916
836
  }),
917
837
  };
918
838
 
919
- const uow = new UnitOfWork(testSchema, createMockCompiler(), executor, createMockDecoder());
839
+ const uow = createUnitOfWork(createMockCompiler(), executor, createMockDecoder());
920
840
 
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");
841
+ uow.forSchema(testSchema).create("users", { email: "user@example.com", name: "User" });
842
+ uow.forSchema(testSchema).update("users", "existing-id", (b) => b.set({ name: "Updated" }));
843
+ uow.forSchema(testSchema).delete("users", "other-id");
924
844
 
925
845
  await uow.executeMutations();
926
846
  const createdIds = uow.getCreatedIds();
@@ -931,17 +851,775 @@ describe("getCreatedIds", () => {
931
851
  });
932
852
 
933
853
  it("should throw when called before executeMutations", () => {
934
- const uow = new UnitOfWork(
935
- testSchema,
854
+ const uow = createUnitOfWork(createMockCompiler(), createMockExecutor(), createMockDecoder());
855
+
856
+ uow.forSchema(testSchema).create("users", { email: "user@example.com", name: "User" });
857
+
858
+ expect(() => uow.getCreatedIds()).toThrow(
859
+ "getCreatedIds() can only be called after executeMutations()",
860
+ );
861
+ });
862
+ });
863
+
864
+ describe("Phase promises with multiple views", () => {
865
+ it("should return only operations added to the current view when using retrievalPhase promise", async () => {
866
+ // Create two separate schemas
867
+ const schema1 = schema((s) =>
868
+ s.addTable("users", (t) =>
869
+ t.addColumn("id", idColumn()).addColumn("name", "string").addColumn("email", "string"),
870
+ ),
871
+ );
872
+
873
+ const schema2 = schema((s) =>
874
+ s.addTable("posts", (t) =>
875
+ t.addColumn("id", idColumn()).addColumn("title", "string").addColumn("content", "string"),
876
+ ),
877
+ );
878
+
879
+ // Create a schema namespace map
880
+ const schemaNamespaceMap = new WeakMap<typeof schema1 | typeof schema2, string>();
881
+ schemaNamespaceMap.set(schema1, "namespace1");
882
+ schemaNamespaceMap.set(schema2, "namespace2");
883
+
884
+ // Mock executor that returns distinct results
885
+ const executor = {
886
+ executeRetrievalPhase: async () => {
887
+ return [
888
+ [{ id: "user1", name: "Alice", email: "alice@example.com" }],
889
+ [{ id: "user2", name: "Bob", email: "bob@example.com" }],
890
+ [{ id: "post1", title: "Post 1", content: "Content 1" }],
891
+ ];
892
+ },
893
+ executeMutationPhase: async () => ({
894
+ success: true,
895
+ createdInternalIds: [],
896
+ }),
897
+ };
898
+
899
+ // Create parent UOW
900
+ const parentUow = createUnitOfWork(
936
901
  createMockCompiler(),
937
- createMockExecutor(),
902
+ executor,
938
903
  createMockDecoder(),
904
+ schemaNamespaceMap,
905
+ "test-uow",
939
906
  );
940
907
 
941
- uow.create("users", { email: "user@example.com", name: "User" });
908
+ // Add a find operation via a schema1 view (but don't keep a reference to this view)
909
+ parentUow.forSchema(schema1).find("users", (b) => b.whereIndex("primary"));
942
910
 
943
- expect(() => uow.getCreatedIds()).toThrow(
944
- "getCreatedIds() can only be called after executeMutations()",
911
+ // Create a view for schema1 and add another find operation
912
+ const view1 = parentUow.forSchema(schema1).find("users", (b) => b.whereIndex("primary"));
913
+
914
+ // Create a view for schema2 and add a find operation
915
+ const view2 = parentUow.forSchema(schema2).find("posts", (b) => b.whereIndex("primary"));
916
+
917
+ // Execute retrieval phase on parent
918
+ const parentResults = await parentUow.executeRetrieve();
919
+
920
+ // Parent should have all 3 results
921
+ expect(parentResults).toHaveLength(3);
922
+
923
+ // View1's retrievalPhase promise should only contain results from operations added through view1
924
+ // (which is index 1 in the parent's operations)
925
+ const view1Results = await view1.retrievalPhase;
926
+ expect(view1Results).toHaveLength(1);
927
+ expect(view1Results[0]).toEqual([{ id: "user2", name: "Bob", email: "bob@example.com" }]);
928
+
929
+ // View2's retrievalPhase promise should only contain results from operations added through view2
930
+ // (which is index 2 in the parent's operations)
931
+ const view2Results = await view2.retrievalPhase;
932
+ expect(view2Results).toHaveLength(1);
933
+ expect(view2Results[0]).toEqual([{ id: "post1", title: "Post 1", content: "Content 1" }]);
934
+ });
935
+
936
+ it("should isolate operations when getUnitOfWork is called multiple times with same schema", async () => {
937
+ const testSchema = schema((s) =>
938
+ s.addTable("users", (t) =>
939
+ t.addColumn("id", idColumn()).addColumn("name", "string").addColumn("email", "string"),
940
+ ),
941
+ );
942
+
943
+ const executor = {
944
+ executeRetrievalPhase: async () => {
945
+ return [
946
+ [{ id: "user1", name: "Alice", email: "alice@example.com" }],
947
+ [{ id: "user2", name: "Bob", email: "bob@example.com" }],
948
+ ];
949
+ },
950
+ executeMutationPhase: async () => ({
951
+ success: true,
952
+ createdInternalIds: [],
953
+ }),
954
+ };
955
+
956
+ const parentUow = createUnitOfWork(
957
+ createMockCompiler(),
958
+ executor,
959
+ createMockDecoder(),
960
+ undefined,
961
+ "test-uow",
962
+ );
963
+
964
+ // Simulate what happens in db-fragment-definition-builder when getUnitOfWork(schema) is called twice
965
+ const view1 = parentUow.forSchema(testSchema).find("users", (b) => b.whereIndex("primary"));
966
+
967
+ const view2 = parentUow.forSchema(testSchema).find("users", (b) => b.whereIndex("primary"));
968
+
969
+ // Execute retrieval
970
+ await parentUow.executeRetrieve();
971
+
972
+ // Each view should only see its own operation's results
973
+ const view1Results = await view1.retrievalPhase;
974
+ expect(view1Results).toHaveLength(1);
975
+ expect(view1Results[0]).toEqual([{ id: "user1", name: "Alice", email: "alice@example.com" }]);
976
+
977
+ const view2Results = await view2.retrievalPhase;
978
+ expect(view2Results).toHaveLength(1);
979
+ expect(view2Results[0]).toEqual([{ id: "user2", name: "Bob", email: "bob@example.com" }]);
980
+ });
981
+
982
+ it("should show that getCreatedIds returns ALL created IDs regardless of which view created them", async () => {
983
+ const schema1 = schema((s) =>
984
+ s.addTable("users", (t) =>
985
+ t.addColumn("id", idColumn()).addColumn("name", "string").addColumn("email", "string"),
986
+ ),
987
+ );
988
+
989
+ const schema2 = schema((s) =>
990
+ s.addTable("posts", (t) =>
991
+ t.addColumn("id", idColumn()).addColumn("title", "string").addColumn("content", "string"),
992
+ ),
945
993
  );
994
+
995
+ const schemaNamespaceMap = new WeakMap<typeof schema1 | typeof schema2, string>();
996
+ schemaNamespaceMap.set(schema1, "namespace1");
997
+ schemaNamespaceMap.set(schema2, "namespace2");
998
+
999
+ const executor = {
1000
+ executeRetrievalPhase: async () => [],
1001
+ executeMutationPhase: async () => ({
1002
+ success: true,
1003
+ createdInternalIds: [1n, 2n],
1004
+ }),
1005
+ };
1006
+
1007
+ const parentUow = createUnitOfWork(
1008
+ createMockCompiler(),
1009
+ executor,
1010
+ createMockDecoder(),
1011
+ schemaNamespaceMap,
1012
+ "test-uow",
1013
+ );
1014
+
1015
+ // View1 creates one user
1016
+ const view1 = parentUow.forSchema(schema1);
1017
+ view1.create("users", { name: "Alice", email: "alice@example.com" });
1018
+
1019
+ // View2 creates one post
1020
+ const view2 = parentUow.forSchema(schema2);
1021
+ view2.create("posts", { title: "Post 1", content: "Content 1" });
1022
+
1023
+ // Execute mutations
1024
+ await parentUow.executeMutations();
1025
+
1026
+ // Both views see ALL created IDs (not filtered by view)
1027
+ const view1Ids = view1.getCreatedIds();
1028
+ const view2Ids = view2.getCreatedIds();
1029
+
1030
+ expect(view1Ids).toHaveLength(2); // Sees both IDs, not just the one it created
1031
+ expect(view2Ids).toHaveLength(2); // Sees both IDs, not just the one it created
1032
+
1033
+ // They're the same array
1034
+ expect(view1Ids).toEqual(view2Ids);
1035
+ });
1036
+
1037
+ it("should generate unique IDs when multiple views create items", async () => {
1038
+ const schema1 = schema((s) =>
1039
+ s.addTable("users", (t) =>
1040
+ t.addColumn("id", idColumn()).addColumn("name", "string").addColumn("email", "string"),
1041
+ ),
1042
+ );
1043
+
1044
+ const schema2 = schema((s) =>
1045
+ s.addTable("posts", (t) =>
1046
+ t.addColumn("id", idColumn()).addColumn("title", "string").addColumn("content", "string"),
1047
+ ),
1048
+ );
1049
+
1050
+ const schemaNamespaceMap = new WeakMap<typeof schema1 | typeof schema2, string>();
1051
+ schemaNamespaceMap.set(schema1, "namespace1");
1052
+ schemaNamespaceMap.set(schema2, "namespace2");
1053
+
1054
+ const executor = {
1055
+ executeRetrievalPhase: async () => [],
1056
+ executeMutationPhase: async () => ({
1057
+ success: true,
1058
+ createdInternalIds: [1n, 2n],
1059
+ }),
1060
+ };
1061
+
1062
+ const parentUow = createUnitOfWork(
1063
+ createMockCompiler(),
1064
+ executor,
1065
+ createMockDecoder(),
1066
+ schemaNamespaceMap,
1067
+ "test-uow",
1068
+ );
1069
+
1070
+ // View1 creates a user
1071
+ const view1 = parentUow.forSchema(schema1);
1072
+ const userId = view1.create("users", { name: "Alice", email: "alice@example.com" });
1073
+
1074
+ // View2 creates a post
1075
+ const view2 = parentUow.forSchema(schema2);
1076
+ const postId = view2.create("posts", { title: "Post 1", content: "Content 1" });
1077
+
1078
+ // IDs should be unique before execution
1079
+ expect(userId.externalId).not.toBe(postId.externalId);
1080
+ expect(userId.externalId).toBeTruthy();
1081
+ expect(postId.externalId).toBeTruthy();
1082
+ expect(userId.internalId).toBeUndefined();
1083
+ expect(postId.internalId).toBeUndefined();
1084
+
1085
+ // Execute mutations
1086
+ await parentUow.executeMutations();
1087
+
1088
+ // Get the created IDs after execution
1089
+ const createdIds = parentUow.getCreatedIds();
1090
+ expect(createdIds).toHaveLength(2);
1091
+
1092
+ // Both should have external IDs set (from create operation)
1093
+ expect(createdIds[0].externalId).toBe(userId.externalId);
1094
+ expect(createdIds[1].externalId).toBe(postId.externalId);
1095
+
1096
+ // Both should now have internal IDs (from database)
1097
+ expect(createdIds[0].internalId).toBe(1n);
1098
+ expect(createdIds[1].internalId).toBe(2n);
1099
+
1100
+ // IDs should still be unique
1101
+ expect(createdIds[0].externalId).not.toBe(createdIds[1].externalId);
1102
+ });
1103
+ });
1104
+
1105
+ describe("Error Handling", () => {
1106
+ const testSchema = schema((s) =>
1107
+ s.addTable("users", (t) =>
1108
+ t.addColumn("id", idColumn()).addColumn("name", "string").addColumn("email", "string"),
1109
+ ),
1110
+ );
1111
+
1112
+ it("should throw error from executeRetrieve() when retrieval fails", async () => {
1113
+ const executor = {
1114
+ executeRetrievalPhase: async () => {
1115
+ throw new Error("Database connection failed");
1116
+ },
1117
+ executeMutationPhase: async () => ({ success: true, createdInternalIds: [] }),
1118
+ };
1119
+
1120
+ const uow = createUnitOfWork(createMockCompiler(), executor, createMockDecoder());
1121
+ uow.forSchema(testSchema).find("users", (b) => b.whereIndex("primary"));
1122
+
1123
+ await expect(uow.executeRetrieve()).rejects.toThrow("Database connection failed");
1124
+ });
1125
+
1126
+ it("should throw error from executeMutations() when mutation fails", async () => {
1127
+ const executor = {
1128
+ executeRetrievalPhase: async () => [],
1129
+ executeMutationPhase: async () => {
1130
+ throw new Error("Write conflict");
1131
+ },
1132
+ };
1133
+
1134
+ const uow = createUnitOfWork(createMockCompiler(), executor, createMockDecoder());
1135
+ uow.forSchema(testSchema).create("users", { name: "Alice", email: "alice@example.com" });
1136
+
1137
+ await expect(uow.executeMutations()).rejects.toThrow("Write conflict");
1138
+ });
1139
+
1140
+ it("should reject retrievalPhase promise when executeRetrieve() fails", async () => {
1141
+ const executor = {
1142
+ executeRetrievalPhase: async () => {
1143
+ throw new Error("Query timeout");
1144
+ },
1145
+ executeMutationPhase: async () => ({ success: true, createdInternalIds: [] }),
1146
+ };
1147
+
1148
+ const uow = createUnitOfWork(createMockCompiler(), executor, createMockDecoder());
1149
+ uow.forSchema(testSchema).find("users", (b) => b.whereIndex("primary"));
1150
+
1151
+ // Start executing (this will fail)
1152
+ const executePromise = uow.executeRetrieve().catch((e) => {
1153
+ return (e as Error).message;
1154
+ });
1155
+
1156
+ uow.retrievalPhase
1157
+ .then(() => {
1158
+ throw new Error("Should not be called");
1159
+ })
1160
+ .catch(() => {
1161
+ throw new Error("Should not be called");
1162
+ });
1163
+
1164
+ // uow.retrievalPhase should not be settled yet
1165
+
1166
+ // Wait for execute to complete
1167
+ expect(await executePromise).toBe("Query timeout");
1168
+ });
1169
+
1170
+ it("should reject mutationPhase promise when executeMutations() fails", async () => {
1171
+ const executor = {
1172
+ executeRetrievalPhase: async () => [],
1173
+ executeMutationPhase: async () => {
1174
+ throw new Error("Constraint violation");
1175
+ },
1176
+ };
1177
+
1178
+ const uow = createUnitOfWork(createMockCompiler(), executor, createMockDecoder());
1179
+ uow.forSchema(testSchema).create("users", { name: "Alice", email: "alice@example.com" });
1180
+
1181
+ // Start executing (this will fail)
1182
+ const executePromise = uow.executeMutations().catch((e) => {
1183
+ return (e as Error).message;
1184
+ });
1185
+
1186
+ uow.mutationPhase
1187
+ .then(() => {
1188
+ throw new Error("Should not be called");
1189
+ })
1190
+ .catch(() => {
1191
+ throw new Error("Should not be called");
1192
+ });
1193
+ // Wait for execute to complete
1194
+ expect(await executePromise).toBe("Constraint violation");
1195
+ });
1196
+
1197
+ it("should not cause unhandled promise rejection when executeRetrieve() fails and coordination promise is not awaited", async () => {
1198
+ const executor = {
1199
+ executeRetrievalPhase: async () => {
1200
+ throw new Error("Table does not exist");
1201
+ },
1202
+ executeMutationPhase: async () => ({ success: true, createdInternalIds: [] }),
1203
+ };
1204
+
1205
+ const uow = createUnitOfWork(createMockCompiler(), executor, createMockDecoder());
1206
+ uow.forSchema(testSchema).find("users", (b) => b.whereIndex("primary"));
1207
+
1208
+ // Access the retrievalPhase promise but don't await it
1209
+ // This simulates the internal coordination promise that might not be awaited
1210
+ const _retrievalPhase = uow.retrievalPhase;
1211
+
1212
+ const errorResolver = Promise.withResolvers<void>();
1213
+
1214
+ // Execute and catch the error from executeRetrieve()
1215
+ try {
1216
+ await uow.executeRetrieve();
1217
+ } catch (error) {
1218
+ // Error is caught, this is expected
1219
+ expect(error).toBeInstanceOf(Error);
1220
+ expect((error as Error).message).toBe("Table does not exist");
1221
+ errorResolver.resolve();
1222
+ }
1223
+
1224
+ await errorResolver.promise;
1225
+ });
1226
+
1227
+ it("should not cause unhandled promise rejection when executeMutations() fails and coordination promise is not awaited", async () => {
1228
+ const executor = {
1229
+ executeRetrievalPhase: async () => [],
1230
+ executeMutationPhase: async () => {
1231
+ throw new Error("Deadlock detected");
1232
+ },
1233
+ };
1234
+
1235
+ const uow = createUnitOfWork(createMockCompiler(), executor, createMockDecoder());
1236
+ uow.forSchema(testSchema).create("users", { name: "Alice", email: "alice@example.com" });
1237
+
1238
+ // Access the mutationPhase promise but don't await it
1239
+ // This simulates the internal coordination promise that might not be awaited
1240
+ const _mutationPhase = uow.mutationPhase;
1241
+
1242
+ const errorResolver = Promise.withResolvers<void>();
1243
+
1244
+ // Execute and catch the error from executeMutations()
1245
+ try {
1246
+ await uow.executeMutations();
1247
+ } catch (error) {
1248
+ // Error is caught, this is expected
1249
+ expect(error).toBeInstanceOf(Error);
1250
+ expect((error as Error).message).toBe("Deadlock detected");
1251
+ errorResolver.resolve();
1252
+ }
1253
+
1254
+ await errorResolver.promise;
1255
+ });
1256
+
1257
+ it("should handle error in executeRetrieve() when coordination promise is never accessed", async () => {
1258
+ const executor = {
1259
+ executeRetrievalPhase: async () => {
1260
+ throw new Error("Connection lost");
1261
+ },
1262
+ executeMutationPhase: async () => ({ success: true, createdInternalIds: [] }),
1263
+ };
1264
+
1265
+ const uow = createUnitOfWork(createMockCompiler(), executor, createMockDecoder());
1266
+ uow.forSchema(testSchema).find("users", (b) => b.whereIndex("primary"));
1267
+
1268
+ // Don't access retrievalPhase at all - this is the most common case
1269
+ // The internal coordination promise should not cause unhandled rejection
1270
+ const errorResolver = Promise.withResolvers<void>();
1271
+
1272
+ // Execute and catch the error
1273
+ try {
1274
+ await uow.executeRetrieve();
1275
+ } catch (error) {
1276
+ expect(error).toBeInstanceOf(Error);
1277
+ expect((error as Error).message).toBe("Connection lost");
1278
+ errorResolver.resolve();
1279
+ }
1280
+
1281
+ await errorResolver.promise;
1282
+ });
1283
+
1284
+ it("should handle error in executeMutations() when coordination promise is never accessed", async () => {
1285
+ const executor = {
1286
+ executeRetrievalPhase: async () => [],
1287
+ executeMutationPhase: async () => {
1288
+ throw new Error("Transaction aborted");
1289
+ },
1290
+ };
1291
+
1292
+ const uow = createUnitOfWork(createMockCompiler(), executor, createMockDecoder());
1293
+ uow.forSchema(testSchema).create("users", { name: "Alice", email: "alice@example.com" });
1294
+
1295
+ // Don't access mutationPhase at all - this is the most common case
1296
+ // The internal coordination promise should not cause unhandled rejection
1297
+ const errorResolver = Promise.withResolvers<void>();
1298
+ // Execute and catch the error
1299
+ try {
1300
+ await uow.executeMutations();
1301
+ } catch (error) {
1302
+ expect(error).toBeInstanceOf(Error);
1303
+ expect((error as Error).message).toBe("Transaction aborted");
1304
+ errorResolver.resolve();
1305
+ }
1306
+
1307
+ await errorResolver.promise;
1308
+ });
1309
+
1310
+ it("should handle reset() after retrieval error", async () => {
1311
+ const executor = {
1312
+ executeRetrievalPhase: async () => {
1313
+ throw new Error("First attempt failed");
1314
+ },
1315
+ executeMutationPhase: async () => ({ success: true, createdInternalIds: [] }),
1316
+ };
1317
+
1318
+ const uow = createUnitOfWork(createMockCompiler(), executor, createMockDecoder());
1319
+ uow.forSchema(testSchema).find("users", (b) => b.whereIndex("primary"));
1320
+
1321
+ // First attempt fails
1322
+ const errorResolver = Promise.withResolvers<void>();
1323
+ try {
1324
+ await uow.executeRetrieve();
1325
+ } catch (error) {
1326
+ expect((error as Error).message).toBe("First attempt failed");
1327
+ errorResolver.resolve();
1328
+ }
1329
+
1330
+ // Reset the UOW
1331
+ uow.reset();
1332
+
1333
+ // The UOW should be in a clean state
1334
+ expect(uow.state).toBe("building-retrieval");
1335
+ expect(uow.getRetrievalOperations()).toHaveLength(0);
1336
+
1337
+ await errorResolver.promise;
1338
+ });
1339
+
1340
+ it("should handle reset() after mutation error", async () => {
1341
+ const executor = {
1342
+ executeRetrievalPhase: async () => [],
1343
+ executeMutationPhase: async () => {
1344
+ throw new Error("First mutation failed");
1345
+ },
1346
+ };
1347
+
1348
+ const uow = createUnitOfWork(createMockCompiler(), executor, createMockDecoder());
1349
+ uow.forSchema(testSchema).create("users", { name: "Alice", email: "alice@example.com" });
1350
+
1351
+ // First attempt fails
1352
+ const errorResolver = Promise.withResolvers<void>();
1353
+ try {
1354
+ await uow.executeMutations();
1355
+ } catch (error) {
1356
+ expect((error as Error).message).toBe("First mutation failed");
1357
+ errorResolver.resolve();
1358
+ }
1359
+
1360
+ // Reset the UOW
1361
+ uow.reset();
1362
+
1363
+ // The UOW should be in a clean state
1364
+ expect(uow.state).toBe("building-retrieval");
1365
+ expect(uow.getMutationOperations()).toHaveLength(0);
1366
+
1367
+ await errorResolver.promise;
1368
+ });
1369
+
1370
+ it("should support standalone check() operation", () => {
1371
+ const uow = createUnitOfWork(createMockCompiler(), createMockExecutor(), createMockDecoder());
1372
+ const typedUow = uow.forSchema(testSchema);
1373
+
1374
+ // Create a FragnoId for testing
1375
+ const userId = FragnoId.fromExternal("user-123", 5);
1376
+
1377
+ typedUow.check("users", userId);
1378
+
1379
+ const mutationOps = uow.getMutationOperations();
1380
+ expect(mutationOps).toHaveLength(1);
1381
+
1382
+ const checkOp = mutationOps[0];
1383
+ assert(checkOp);
1384
+ assert(checkOp.type === "check");
1385
+ expect(checkOp.table).toBe("users");
1386
+ expect(checkOp.id).toBe(userId);
1387
+ });
1388
+ });
1389
+
1390
+ describe("findFirst convenience method", () => {
1391
+ const testSchema = schema((s) =>
1392
+ s
1393
+ .addTable("users", (t) =>
1394
+ t
1395
+ .addColumn("id", idColumn())
1396
+ .addColumn("name", "string")
1397
+ .addColumn("email", "string")
1398
+ .createIndex("idx_email", ["email"])
1399
+ .createIndex("idx_name", ["name"]),
1400
+ )
1401
+ .addTable("posts", (t) =>
1402
+ t
1403
+ .addColumn("id", idColumn())
1404
+ .addColumn("userId", "string")
1405
+ .addColumn("title", "string")
1406
+ .createIndex("idx_user", ["userId"]),
1407
+ ),
1408
+ );
1409
+
1410
+ it("should return a single result instead of an array", async () => {
1411
+ const executor = {
1412
+ executeRetrievalPhase: async () => {
1413
+ return [[{ id: "mock-id", name: "Mock User", email: "mock@example.com" }]];
1414
+ },
1415
+ executeMutationPhase: async () => ({ success: true, createdInternalIds: [] }),
1416
+ };
1417
+
1418
+ const uow = createUnitOfWork(createMockCompiler(), executor, createMockDecoder());
1419
+
1420
+ // Use findFirst instead of find
1421
+ const typedUow = uow.forSchema(testSchema).findFirst("users", (b) => b.whereIndex("primary"));
1422
+
1423
+ // Execute retrieval
1424
+ await uow.executeRetrieve();
1425
+ const results = await typedUow.retrievalPhase;
1426
+
1427
+ // Result should be a single object, not an array
1428
+ const [user] = results;
1429
+ expect(user).toEqual({ id: "mock-id", name: "Mock User", email: "mock@example.com" });
1430
+ expect(Array.isArray(user)).toBe(false);
1431
+ });
1432
+
1433
+ it("should return null when no results are found", async () => {
1434
+ // Create executor that returns empty results
1435
+ const emptyExecutor = {
1436
+ executeRetrievalPhase: async () => {
1437
+ return [[]]; // Empty array for no results
1438
+ },
1439
+ executeMutationPhase: async () => {
1440
+ return { success: true, createdInternalIds: [] };
1441
+ },
1442
+ };
1443
+
1444
+ const uow = createUnitOfWork(createMockCompiler(), emptyExecutor, createMockDecoder());
1445
+
1446
+ const typedUow = uow.forSchema(testSchema).findFirst("users", (b) => b.whereIndex("primary"));
1447
+
1448
+ await uow.executeRetrieve();
1449
+ const results = await typedUow.retrievalPhase;
1450
+ const [user] = results;
1451
+
1452
+ // Should be null when no results
1453
+ expect(user).toBeNull();
1454
+ });
1455
+
1456
+ it("should automatically set pageSize to 1", () => {
1457
+ const uow = createUnitOfWork(createMockCompiler(), createMockExecutor(), createMockDecoder());
1458
+
1459
+ uow.forSchema(testSchema).findFirst("users", (b) => b.whereIndex("primary"));
1460
+
1461
+ // Check that pageSize was set to 1 in the operation
1462
+ const ops = uow.getRetrievalOperations();
1463
+ expect(ops).toHaveLength(1);
1464
+ expect(ops[0]?.type).toBe("find");
1465
+ if (ops[0]?.type === "find") {
1466
+ expect(ops[0].options.pageSize).toBe(1);
1467
+ }
1468
+ });
1469
+
1470
+ it("should work with custom select clause", async () => {
1471
+ const executor = {
1472
+ executeRetrievalPhase: async () => {
1473
+ return [[{ id: "mock-id", name: "Mock User" }]];
1474
+ },
1475
+ executeMutationPhase: async () => ({ success: true, createdInternalIds: [] }),
1476
+ };
1477
+
1478
+ const uow = createUnitOfWork(createMockCompiler(), executor, createMockDecoder());
1479
+
1480
+ const typedUow = uow
1481
+ .forSchema(testSchema)
1482
+ .findFirst("users", (b) => b.whereIndex("primary").select(["id", "name"] as const));
1483
+
1484
+ await uow.executeRetrieve();
1485
+ const results = await typedUow.retrievalPhase;
1486
+ const [user] = results;
1487
+
1488
+ expect(user).toBeDefined();
1489
+ expect(user).not.toBeNull();
1490
+ expect(user).toEqual({ id: "mock-id", name: "Mock User" });
1491
+ });
1492
+
1493
+ it("should work with where conditions", async () => {
1494
+ const executor = {
1495
+ executeRetrievalPhase: async () => {
1496
+ return [[{ id: "mock-id", name: "Mock User", email: "test@example.com" }]];
1497
+ },
1498
+ executeMutationPhase: async () => ({ success: true, createdInternalIds: [] }),
1499
+ };
1500
+
1501
+ const uow = createUnitOfWork(createMockCompiler(), executor, createMockDecoder());
1502
+
1503
+ const typedUow = uow
1504
+ .forSchema(testSchema)
1505
+ .findFirst("users", (b) =>
1506
+ b.whereIndex("idx_email", (eb) => eb("email", "=", "test@example.com")),
1507
+ );
1508
+
1509
+ await uow.executeRetrieve();
1510
+ const results = await typedUow.retrievalPhase;
1511
+ const [user] = results;
1512
+
1513
+ expect(user).toEqual({ id: "mock-id", name: "Mock User", email: "test@example.com" });
1514
+ });
1515
+
1516
+ it("should handle multiple findFirst operations in the same UOW", async () => {
1517
+ const executor = {
1518
+ executeRetrievalPhase: async () => {
1519
+ return [
1520
+ [{ id: "user-1", name: "User 1", email: "user1@example.com" }],
1521
+ [{ id: "post-1", userId: "user-1", title: "Post 1" }],
1522
+ ];
1523
+ },
1524
+ executeMutationPhase: async () => ({ success: true, createdInternalIds: [] }),
1525
+ };
1526
+
1527
+ const uow = createUnitOfWork(createMockCompiler(), executor, createMockDecoder());
1528
+
1529
+ const typedUow = uow
1530
+ .forSchema(testSchema)
1531
+ .findFirst("users", (b) => b.whereIndex("primary"))
1532
+ .findFirst("posts", (b) => b.whereIndex("primary"));
1533
+
1534
+ await uow.executeRetrieve();
1535
+ const results = await typedUow.retrievalPhase;
1536
+ const [user, post] = results;
1537
+
1538
+ // Both should be single objects, not arrays
1539
+ expect(user).toEqual({ id: "user-1", name: "User 1", email: "user1@example.com" });
1540
+ expect(post).toEqual({ id: "post-1", userId: "user-1", title: "Post 1" });
1541
+ expect(Array.isArray(user)).toBe(false);
1542
+ expect(Array.isArray(post)).toBe(false);
1543
+ });
1544
+
1545
+ it("should handle mix of find and findFirst operations", async () => {
1546
+ const executor = {
1547
+ executeRetrievalPhase: async () => {
1548
+ return [
1549
+ [{ id: "user-1", name: "User 1", email: "user1@example.com" }],
1550
+ [
1551
+ { id: "post-1", userId: "user-1", title: "Post 1" },
1552
+ { id: "post-2", userId: "user-1", title: "Post 2" },
1553
+ ],
1554
+ ];
1555
+ },
1556
+ executeMutationPhase: async () => ({ success: true, createdInternalIds: [] }),
1557
+ };
1558
+
1559
+ const uow = createUnitOfWork(createMockCompiler(), executor, createMockDecoder());
1560
+
1561
+ const typedUow = uow
1562
+ .forSchema(testSchema)
1563
+ .findFirst("users", (b) => b.whereIndex("primary")) // Single result
1564
+ .find("posts", (b) => b.whereIndex("primary")); // Array of results
1565
+
1566
+ await uow.executeRetrieve();
1567
+ const results = await typedUow.retrievalPhase;
1568
+ const [user, posts] = results;
1569
+
1570
+ // User should be a single object
1571
+ expect(user).toEqual({ id: "user-1", name: "User 1", email: "user1@example.com" });
1572
+ expect(Array.isArray(user)).toBe(false);
1573
+
1574
+ // Posts should be an array
1575
+ expect(Array.isArray(posts)).toBe(true);
1576
+ expect(posts).toHaveLength(2);
1577
+ });
1578
+
1579
+ it("should work with orderByIndex", async () => {
1580
+ const executor = {
1581
+ executeRetrievalPhase: async () => {
1582
+ return [[{ id: "user-1", name: "Alice", email: "alice@example.com" }]];
1583
+ },
1584
+ executeMutationPhase: async () => ({ success: true, createdInternalIds: [] }),
1585
+ };
1586
+
1587
+ const uow = createUnitOfWork(createMockCompiler(), executor, createMockDecoder());
1588
+
1589
+ const typedUow = uow
1590
+ .forSchema(testSchema)
1591
+ .findFirst("users", (b) => b.whereIndex("idx_name").orderByIndex("idx_name", "asc"));
1592
+
1593
+ await uow.executeRetrieve();
1594
+ const results = await typedUow.retrievalPhase;
1595
+ const [user] = results;
1596
+
1597
+ expect(user).toEqual({ id: "user-1", name: "Alice", email: "alice@example.com" });
1598
+ expect(Array.isArray(user)).toBe(false);
1599
+ });
1600
+
1601
+ it("should work without explicit builder function", async () => {
1602
+ const executor = {
1603
+ executeRetrievalPhase: async () => {
1604
+ return [[{ id: "user-1", name: "User 1", email: "user1@example.com" }]];
1605
+ },
1606
+ executeMutationPhase: async () => ({ success: true, createdInternalIds: [] }),
1607
+ };
1608
+
1609
+ const uow = createUnitOfWork(createMockCompiler(), executor, createMockDecoder());
1610
+
1611
+ // findFirst without builder function should use primary index by default
1612
+ const typedUow = uow.forSchema(testSchema).findFirst("users");
1613
+
1614
+ await uow.executeRetrieve();
1615
+ const results = await typedUow.retrievalPhase;
1616
+ const [user] = results;
1617
+
1618
+ expect(user).toEqual({ id: "user-1", name: "User 1", email: "user1@example.com" });
1619
+ expect(Array.isArray(user)).toBe(false);
1620
+
1621
+ // Verify the operation used the primary index
1622
+ const ops = uow.getRetrievalOperations();
1623
+ expect(ops[0]?.indexName).toBe("_primary");
946
1624
  });
947
1625
  });