@fragno-dev/db 0.2.0 → 0.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (62) hide show
  1. package/.turbo/turbo-build.log +34 -30
  2. package/CHANGELOG.md +49 -0
  3. package/dist/adapters/generic-sql/query/where-builder.js +1 -1
  4. package/dist/db-fragment-definition-builder.d.ts +31 -39
  5. package/dist/db-fragment-definition-builder.d.ts.map +1 -1
  6. package/dist/db-fragment-definition-builder.js +20 -16
  7. package/dist/db-fragment-definition-builder.js.map +1 -1
  8. package/dist/fragments/internal-fragment.d.ts +94 -8
  9. package/dist/fragments/internal-fragment.d.ts.map +1 -1
  10. package/dist/fragments/internal-fragment.js +56 -55
  11. package/dist/fragments/internal-fragment.js.map +1 -1
  12. package/dist/hooks/hooks.d.ts +5 -3
  13. package/dist/hooks/hooks.d.ts.map +1 -1
  14. package/dist/hooks/hooks.js +38 -37
  15. package/dist/hooks/hooks.js.map +1 -1
  16. package/dist/mod.d.ts +3 -3
  17. package/dist/mod.d.ts.map +1 -1
  18. package/dist/mod.js +4 -4
  19. package/dist/mod.js.map +1 -1
  20. package/dist/query/unit-of-work/execute-unit-of-work.d.ts +367 -80
  21. package/dist/query/unit-of-work/execute-unit-of-work.d.ts.map +1 -1
  22. package/dist/query/unit-of-work/execute-unit-of-work.js +448 -148
  23. package/dist/query/unit-of-work/execute-unit-of-work.js.map +1 -1
  24. package/dist/query/unit-of-work/unit-of-work.d.ts +35 -11
  25. package/dist/query/unit-of-work/unit-of-work.d.ts.map +1 -1
  26. package/dist/query/unit-of-work/unit-of-work.js +49 -19
  27. package/dist/query/unit-of-work/unit-of-work.js.map +1 -1
  28. package/dist/query/value-decoding.js +1 -1
  29. package/dist/schema/create.d.ts +2 -3
  30. package/dist/schema/create.d.ts.map +1 -1
  31. package/dist/schema/create.js +2 -5
  32. package/dist/schema/create.js.map +1 -1
  33. package/dist/schema/generate-id.d.ts +20 -0
  34. package/dist/schema/generate-id.d.ts.map +1 -0
  35. package/dist/schema/generate-id.js +28 -0
  36. package/dist/schema/generate-id.js.map +1 -0
  37. package/dist/sql-driver/dialects/durable-object-dialect.d.ts.map +1 -1
  38. package/package.json +3 -3
  39. package/src/adapters/drizzle/drizzle-adapter-pglite.test.ts +1 -0
  40. package/src/adapters/drizzle/drizzle-adapter-sqlite3.test.ts +41 -25
  41. package/src/adapters/generic-sql/test/generic-drizzle-adapter-sqlite3.test.ts +39 -25
  42. package/src/db-fragment-definition-builder.test.ts +58 -42
  43. package/src/db-fragment-definition-builder.ts +78 -88
  44. package/src/db-fragment-instantiator.test.ts +64 -88
  45. package/src/db-fragment-integration.test.ts +292 -142
  46. package/src/fragments/internal-fragment.test.ts +272 -266
  47. package/src/fragments/internal-fragment.ts +155 -122
  48. package/src/hooks/hooks.test.ts +268 -264
  49. package/src/hooks/hooks.ts +74 -63
  50. package/src/mod.ts +14 -4
  51. package/src/query/unit-of-work/execute-unit-of-work.test.ts +1582 -998
  52. package/src/query/unit-of-work/execute-unit-of-work.ts +1746 -343
  53. package/src/query/unit-of-work/tx-builder.test.ts +1041 -0
  54. package/src/query/unit-of-work/unit-of-work-coordinator.test.ts +269 -21
  55. package/src/query/unit-of-work/unit-of-work.test.ts +64 -0
  56. package/src/query/unit-of-work/unit-of-work.ts +65 -30
  57. package/src/schema/create.ts +2 -5
  58. package/src/schema/generate-id.test.ts +57 -0
  59. package/src/schema/generate-id.ts +38 -0
  60. package/src/shared/config.ts +0 -10
  61. package/src/shared/connection-pool.ts +0 -24
  62. package/src/shared/prisma.ts +0 -45
@@ -0,0 +1,1041 @@
1
+ /**
2
+ * Test suite for ServiceTxBuilder and HandlerTxBuilder type inference.
3
+ * These tests verify that the builder pattern correctly infers types based on
4
+ * which callbacks are set.
5
+ *
6
+ * NOTE: These are TYPE tests only. We test that the type inference is correct,
7
+ * not the runtime behavior (which is covered by execute-unit-of-work.test.ts).
8
+ */
9
+ import { describe, expectTypeOf, it } from "vitest";
10
+ import type { AnySchema } from "../../schema/create";
11
+ import type { TypedUnitOfWork } from "./unit-of-work";
12
+ import {
13
+ ServiceTxBuilder,
14
+ HandlerTxBuilder,
15
+ type TxResult,
16
+ type ServiceBuilderMutateContext,
17
+ type HandlerBuilderMutateContext,
18
+ type BuilderTransformContextWithMutate,
19
+ type BuilderTransformContextWithoutMutate,
20
+ type ExtractServiceRetrieveResults,
21
+ type ExtractServiceFinalResults,
22
+ type AwaitedPromisesInObject,
23
+ } from "./execute-unit-of-work";
24
+
25
+ // =============================================================================
26
+ // Helper types for extracting TxResult type parameters
27
+ // =============================================================================
28
+
29
+ /**
30
+ * Extract the final result type (TResult) from a TxResult
31
+ */
32
+ type TxResultFinalType<T> = T extends TxResult<infer R, infer _> ? R : never;
33
+
34
+ /**
35
+ * Extract the retrieve success result type from a TxResult
36
+ */
37
+ type TxResultRetrieveType<T> = T extends TxResult<infer _, infer R> ? R : never;
38
+
39
+ // =============================================================================
40
+ // Test Schema
41
+ // =============================================================================
42
+
43
+ type TestSchema = AnySchema & {
44
+ version: 1;
45
+ entities: {
46
+ users: { id: string; name: string; email: string };
47
+ orders: { id: string; userId: string; total: number };
48
+ };
49
+ };
50
+
51
+ // =============================================================================
52
+ // ServiceTxBuilder Type Tests
53
+ // =============================================================================
54
+
55
+ describe("ServiceTxBuilder type inference", () => {
56
+ // We're testing types only, not runtime behavior
57
+ // Using `as` casts to create builders without runtime dependencies
58
+
59
+ describe("return type priority", () => {
60
+ it("returns empty array when no callbacks are set", () => {
61
+ // Create a builder type directly to test type inference
62
+ type Builder = ServiceTxBuilder<
63
+ TestSchema,
64
+ readonly [],
65
+ [],
66
+ [],
67
+ unknown,
68
+ unknown,
69
+ false,
70
+ false,
71
+ false,
72
+ false,
73
+ {}
74
+ >;
75
+
76
+ // The build() return type should be TxResult<[], []>
77
+ type BuildResult = ReturnType<Builder["build"]>;
78
+ // Extract just the type parameters to avoid complex internal type matching
79
+ // Check length property to verify empty tuple without array method comparison issues
80
+ expectTypeOf<TxResultFinalType<BuildResult>["length"]>().toEqualTypeOf<0>();
81
+ expectTypeOf<TxResultRetrieveType<BuildResult>["length"]>().toEqualTypeOf<0>();
82
+ });
83
+
84
+ it("returns retrieve results when only retrieve is set", () => {
85
+ type Builder = ServiceTxBuilder<
86
+ TestSchema,
87
+ readonly [],
88
+ [{ id: string; name: string }[]],
89
+ [{ id: string; name: string }[]], // same as retrieve since no transformRetrieve
90
+ unknown,
91
+ unknown,
92
+ true, // HasRetrieve
93
+ false,
94
+ false,
95
+ false,
96
+ {}
97
+ >;
98
+
99
+ type BuildResult = ReturnType<Builder["build"]>;
100
+ expectTypeOf<TxResultFinalType<BuildResult>>().toEqualTypeOf<
101
+ [{ id: string; name: string }[]]
102
+ >();
103
+ expectTypeOf<TxResultRetrieveType<BuildResult>>().toEqualTypeOf<
104
+ [{ id: string; name: string }[]]
105
+ >();
106
+ });
107
+
108
+ it("returns transformRetrieve result when transformRetrieve is set", () => {
109
+ type Builder = ServiceTxBuilder<
110
+ TestSchema,
111
+ readonly [],
112
+ [{ id: string; name: string }[]],
113
+ { id: string; name: string } | null, // transformed result
114
+ unknown,
115
+ unknown,
116
+ true, // HasRetrieve
117
+ true, // HasTransformRetrieve
118
+ false,
119
+ false,
120
+ {}
121
+ >;
122
+
123
+ type BuildResult = ReturnType<Builder["build"]>;
124
+ expectTypeOf<TxResultFinalType<BuildResult>>().toEqualTypeOf<{
125
+ id: string;
126
+ name: string;
127
+ } | null>();
128
+ expectTypeOf<TxResultRetrieveType<BuildResult>>().toEqualTypeOf<{
129
+ id: string;
130
+ name: string;
131
+ } | null>();
132
+ });
133
+
134
+ it("returns mutate result when mutate is set (no transformRetrieve)", () => {
135
+ type Builder = ServiceTxBuilder<
136
+ TestSchema,
137
+ readonly [],
138
+ [{ id: string; name: string }[]],
139
+ [{ id: string; name: string }[]], // raw retrieve results since no transformRetrieve
140
+ { created: true; id: string }, // mutate result
141
+ unknown,
142
+ true, // HasRetrieve
143
+ false, // NO transformRetrieve
144
+ true, // HasMutate
145
+ false,
146
+ {}
147
+ >;
148
+
149
+ type BuildResult = ReturnType<Builder["build"]>;
150
+ // Final result type is the mutate return type
151
+ expectTypeOf<TxResultFinalType<BuildResult>>().toEqualTypeOf<{
152
+ created: true;
153
+ id: string;
154
+ }>();
155
+ // Retrieve success result is the raw retrieve results (no transformRetrieve)
156
+ expectTypeOf<TxResultRetrieveType<BuildResult>>().toEqualTypeOf<
157
+ [{ id: string; name: string }[]]
158
+ >();
159
+ });
160
+
161
+ it("returns mutate result when mutate is set (with transformRetrieve)", () => {
162
+ type Builder = ServiceTxBuilder<
163
+ TestSchema,
164
+ readonly [],
165
+ [{ id: string; name: string }[]],
166
+ { id: string; name: string } | null, // transformed
167
+ { orderId: string }, // mutate result
168
+ unknown,
169
+ true, // HasRetrieve
170
+ true, // HasTransformRetrieve
171
+ true, // HasMutate
172
+ false,
173
+ {}
174
+ >;
175
+
176
+ type BuildResult = ReturnType<Builder["build"]>;
177
+ expectTypeOf<TxResultFinalType<BuildResult>>().toEqualTypeOf<{ orderId: string }>();
178
+ expectTypeOf<TxResultRetrieveType<BuildResult>>().toEqualTypeOf<{
179
+ id: string;
180
+ name: string;
181
+ } | null>();
182
+ });
183
+
184
+ it("returns transform result when transform is set", () => {
185
+ type Builder = ServiceTxBuilder<
186
+ TestSchema,
187
+ readonly [],
188
+ [{ id: string; name: string }[]],
189
+ { id: string; name: string } | null,
190
+ { orderId: string },
191
+ { success: true; data: string }, // transform result
192
+ true, // HasRetrieve
193
+ true, // HasTransformRetrieve
194
+ true, // HasMutate
195
+ true, // HasTransform
196
+ {}
197
+ >;
198
+
199
+ type BuildResult = ReturnType<Builder["build"]>;
200
+ expectTypeOf<TxResultFinalType<BuildResult>>().toEqualTypeOf<{
201
+ success: true;
202
+ data: string;
203
+ }>();
204
+ expectTypeOf<TxResultRetrieveType<BuildResult>>().toEqualTypeOf<{
205
+ id: string;
206
+ name: string;
207
+ } | null>();
208
+ });
209
+ });
210
+
211
+ describe("withServiceCalls type inference", () => {
212
+ it("infers service call result types in mutate context", () => {
213
+ type ServiceCall1 = TxResult<{ userId: string }, { userId: string }>;
214
+ type ServiceCall2 = TxResult<number, number>;
215
+ type ServiceCalls = readonly [ServiceCall1, ServiceCall2];
216
+
217
+ type Builder = ServiceTxBuilder<
218
+ TestSchema,
219
+ ServiceCalls,
220
+ [],
221
+ [],
222
+ string, // mutate result
223
+ unknown,
224
+ false,
225
+ false,
226
+ true, // HasMutate
227
+ false,
228
+ {}
229
+ >;
230
+
231
+ // Get the mutate method's parameter type
232
+ type MutateParam = Parameters<Builder["mutate"]>[0];
233
+ type MutateCtx = Parameters<MutateParam>[0];
234
+
235
+ // serviceIntermediateResult should be a tuple with the retrieve success results
236
+ expectTypeOf<MutateCtx["serviceIntermediateResult"]>().toEqualTypeOf<
237
+ readonly [{ userId: string }, number]
238
+ >();
239
+ });
240
+
241
+ it("handles optional service calls (undefined in tuple)", () => {
242
+ type ServiceCall = TxResult<{ userId: string }, { userId: string }>;
243
+ type ServiceCalls = readonly [ServiceCall, undefined];
244
+
245
+ type Extracted = ExtractServiceRetrieveResults<ServiceCalls>;
246
+
247
+ // Second element should be undefined
248
+ expectTypeOf<Extracted>().toEqualTypeOf<readonly [{ userId: string }, undefined]>();
249
+ });
250
+
251
+ it("provides serviceIntermediateResult in transform with final results", () => {
252
+ type ServiceCall = TxResult<{ finalData: string }, { retrieveData: string }>;
253
+ type ServiceCalls = readonly [ServiceCall];
254
+
255
+ type Builder = ServiceTxBuilder<
256
+ TestSchema,
257
+ ServiceCalls,
258
+ [],
259
+ [],
260
+ { created: boolean },
261
+ unknown,
262
+ false,
263
+ false,
264
+ true, // HasMutate
265
+ true, // HasTransform (we'll check its param type)
266
+ {}
267
+ >;
268
+
269
+ // Get the transform method's parameter type
270
+ type TransformParam = Parameters<Builder["transform"]>[0];
271
+ type TransformCtx = Parameters<TransformParam>[0];
272
+
273
+ // serviceResult contains final results
274
+ expectTypeOf<TransformCtx["serviceResult"]>().toEqualTypeOf<
275
+ readonly [{ finalData: string }]
276
+ >();
277
+ // serviceIntermediateResult contains retrieve success results
278
+ expectTypeOf<TransformCtx["serviceIntermediateResult"]>().toEqualTypeOf<
279
+ readonly [{ retrieveData: string }]
280
+ >();
281
+ });
282
+ });
283
+
284
+ describe("context types", () => {
285
+ it("ServiceBuilderMutateContext has correct shape", () => {
286
+ type Ctx = ServiceBuilderMutateContext<TestSchema, string, readonly [], {}>;
287
+
288
+ expectTypeOf<Ctx["uow"]>().toExtend<TypedUnitOfWork<TestSchema, [], unknown, {}>>();
289
+ expectTypeOf<Ctx["retrieveResult"]>().toEqualTypeOf<string>();
290
+ expectTypeOf<Ctx["serviceIntermediateResult"]>().toEqualTypeOf<readonly []>();
291
+ });
292
+
293
+ it("BuilderTransformContextWithMutate has mutateResult defined", () => {
294
+ type Ctx = BuilderTransformContextWithMutate<
295
+ string,
296
+ { id: string },
297
+ readonly [],
298
+ readonly []
299
+ >;
300
+
301
+ expectTypeOf<Ctx["retrieveResult"]>().toEqualTypeOf<string>();
302
+ expectTypeOf<Ctx["mutateResult"]>().toEqualTypeOf<{ id: string }>();
303
+ expectTypeOf<Ctx["serviceIntermediateResult"]>().toEqualTypeOf<readonly []>();
304
+ expectTypeOf<Ctx["serviceIntermediateResult"]>().toEqualTypeOf<readonly []>();
305
+ });
306
+
307
+ it("BuilderTransformContextWithoutMutate has mutateResult as undefined", () => {
308
+ type Ctx = BuilderTransformContextWithoutMutate<string, readonly [], readonly []>;
309
+
310
+ expectTypeOf<Ctx["retrieveResult"]>().toEqualTypeOf<string>();
311
+ expectTypeOf<Ctx["mutateResult"]>().toEqualTypeOf<undefined>();
312
+ expectTypeOf<Ctx["serviceIntermediateResult"]>().toEqualTypeOf<readonly []>();
313
+ expectTypeOf<Ctx["serviceIntermediateResult"]>().toEqualTypeOf<readonly []>();
314
+ });
315
+ });
316
+
317
+ describe("builder state tracking via type parameters", () => {
318
+ it("builder with HasRetrieve=true returns retrieve results when no other callbacks", () => {
319
+ type BuilderWithRetrieve = ServiceTxBuilder<
320
+ TestSchema,
321
+ readonly [],
322
+ [{ id: string }[]],
323
+ [{ id: string }[]], // same as retrieve since no transformRetrieve
324
+ unknown,
325
+ unknown,
326
+ true, // HasRetrieve = true
327
+ false,
328
+ false,
329
+ false,
330
+ {}
331
+ >;
332
+
333
+ type BuildResult = ReturnType<BuilderWithRetrieve["build"]>;
334
+ expectTypeOf<TxResultFinalType<BuildResult>>().toEqualTypeOf<[{ id: string }[]]>();
335
+ expectTypeOf<TxResultRetrieveType<BuildResult>>().toEqualTypeOf<[{ id: string }[]]>();
336
+ });
337
+
338
+ it("builder with HasTransformRetrieve=true returns transformed result", () => {
339
+ type BuilderWithTransformRetrieve = ServiceTxBuilder<
340
+ TestSchema,
341
+ readonly [],
342
+ [{ id: string }[]],
343
+ { id: string } | null, // transformed
344
+ unknown,
345
+ unknown,
346
+ true, // HasRetrieve
347
+ true, // HasTransformRetrieve = true
348
+ false,
349
+ false,
350
+ {}
351
+ >;
352
+
353
+ type BuildResult = ReturnType<BuilderWithTransformRetrieve["build"]>;
354
+ expectTypeOf<TxResultFinalType<BuildResult>>().toEqualTypeOf<{ id: string } | null>();
355
+ expectTypeOf<TxResultRetrieveType<BuildResult>>().toEqualTypeOf<{ id: string } | null>();
356
+ });
357
+
358
+ it("builder with HasMutate=true returns mutate result", () => {
359
+ type BuilderWithMutate = ServiceTxBuilder<
360
+ TestSchema,
361
+ readonly [],
362
+ [{ id: string }[]],
363
+ [{ id: string }[]],
364
+ { created: boolean }, // mutate result
365
+ unknown,
366
+ true,
367
+ false,
368
+ true, // HasMutate = true
369
+ false,
370
+ {}
371
+ >;
372
+
373
+ type BuildResult = ReturnType<BuilderWithMutate["build"]>;
374
+ expectTypeOf<TxResultFinalType<BuildResult>>().toEqualTypeOf<{ created: boolean }>();
375
+ expectTypeOf<TxResultRetrieveType<BuildResult>>().toEqualTypeOf<[{ id: string }[]]>();
376
+ });
377
+
378
+ it("builder with HasTransform=true returns transform result", () => {
379
+ type BuilderWithTransform = ServiceTxBuilder<
380
+ TestSchema,
381
+ readonly [],
382
+ [],
383
+ [],
384
+ { orderId: string },
385
+ { success: boolean }, // transform result
386
+ false,
387
+ false,
388
+ true,
389
+ true, // HasTransform = true
390
+ {}
391
+ >;
392
+
393
+ type BuildResult = ReturnType<BuilderWithTransform["build"]>;
394
+ expectTypeOf<TxResultFinalType<BuildResult>>().toEqualTypeOf<{ success: boolean }>();
395
+ // When HasMutate=true but no retrieve, the mutate result becomes the retrieve success result for dependents
396
+ expectTypeOf<TxResultRetrieveType<BuildResult>>().toEqualTypeOf<{ orderId: string }>();
397
+ });
398
+ });
399
+ });
400
+
401
+ // =============================================================================
402
+ // HandlerTxBuilder Type Tests
403
+ // =============================================================================
404
+
405
+ describe("HandlerTxBuilder type inference", () => {
406
+ describe("return type priority", () => {
407
+ it("returns service final results when only withServiceCalls is set", () => {
408
+ type ServiceCall = TxResult<{ data: string }, { data: string }>;
409
+
410
+ type Builder = HandlerTxBuilder<
411
+ readonly [ServiceCall],
412
+ [],
413
+ [],
414
+ unknown,
415
+ unknown,
416
+ false,
417
+ false,
418
+ false,
419
+ false,
420
+ {}
421
+ >;
422
+
423
+ type ExecuteResult = ReturnType<Builder["execute"]>;
424
+ // execute() returns a Promise of the service final results
425
+ expectTypeOf<ExecuteResult>().toExtend<
426
+ Promise<AwaitedPromisesInObject<readonly [{ data: string }]>>
427
+ >();
428
+ });
429
+
430
+ it("returns mutate result when mutate is set", () => {
431
+ type Builder = HandlerTxBuilder<
432
+ readonly [],
433
+ [],
434
+ [],
435
+ { orderId: string }, // mutate result
436
+ unknown,
437
+ false,
438
+ false,
439
+ true, // HasMutate
440
+ false,
441
+ {}
442
+ >;
443
+
444
+ type ExecuteResult = ReturnType<Builder["execute"]>;
445
+ expectTypeOf<ExecuteResult>().toExtend<Promise<{ orderId: string }>>();
446
+ });
447
+
448
+ it("returns transform result when transform is set", () => {
449
+ type Builder = HandlerTxBuilder<
450
+ readonly [],
451
+ [],
452
+ [],
453
+ { orderId: string },
454
+ { success: true; orderId: string }, // transform result
455
+ false,
456
+ false,
457
+ true, // HasMutate
458
+ true, // HasTransform
459
+ {}
460
+ >;
461
+
462
+ type ExecuteResult = ReturnType<Builder["execute"]>;
463
+ expectTypeOf<ExecuteResult>().toExtend<Promise<{ success: true; orderId: string }>>();
464
+ });
465
+ });
466
+
467
+ describe("context field names", () => {
468
+ it("HandlerBuilderMutateContext has idempotencyKey and currentAttempt", () => {
469
+ type Ctx = HandlerBuilderMutateContext<[], readonly [], {}>;
470
+
471
+ expectTypeOf<Ctx["idempotencyKey"]>().toEqualTypeOf<string>();
472
+ expectTypeOf<Ctx["currentAttempt"]>().toEqualTypeOf<number>();
473
+ expectTypeOf<Ctx["forSchema"]>().toBeFunction();
474
+ // Check length property to verify empty tuple without array method comparison issues
475
+ expectTypeOf<Ctx["retrieveResult"]["length"]>().toEqualTypeOf<0>();
476
+ expectTypeOf<Ctx["serviceIntermediateResult"]["length"]>().toEqualTypeOf<0>();
477
+ });
478
+
479
+ it("retrieve context in HandlerTxBuilder has correct type", () => {
480
+ type Builder = HandlerTxBuilder<
481
+ readonly [],
482
+ [],
483
+ [],
484
+ unknown,
485
+ unknown,
486
+ false,
487
+ false,
488
+ false,
489
+ false,
490
+ {}
491
+ >;
492
+
493
+ // Get the retrieve parameter type
494
+ type RetrieveParam = Parameters<Builder["retrieve"]>[0];
495
+ type RetrieveCtx = Parameters<RetrieveParam>[0];
496
+
497
+ expectTypeOf<RetrieveCtx["idempotencyKey"]>().toEqualTypeOf<string>();
498
+ expectTypeOf<RetrieveCtx["currentAttempt"]>().toEqualTypeOf<number>();
499
+ expectTypeOf<RetrieveCtx["forSchema"]>().toBeFunction();
500
+ });
501
+
502
+ it("transform context has serviceResult and serviceIntermediateResult", () => {
503
+ type ServiceCall = TxResult<{ finalData: string }, { retrieveData: string }>;
504
+
505
+ type Builder = HandlerTxBuilder<
506
+ readonly [ServiceCall],
507
+ [],
508
+ [],
509
+ unknown,
510
+ unknown,
511
+ false,
512
+ false,
513
+ false,
514
+ true, // HasTransform
515
+ {}
516
+ >;
517
+
518
+ type TransformParam = Parameters<Builder["transform"]>[0];
519
+ type TransformCtx = Parameters<TransformParam>[0];
520
+
521
+ expectTypeOf<TransformCtx["serviceResult"]>().toEqualTypeOf<
522
+ readonly [{ finalData: string }]
523
+ >();
524
+ expectTypeOf<TransformCtx["serviceIntermediateResult"]>().toEqualTypeOf<
525
+ readonly [{ retrieveData: string }]
526
+ >();
527
+ expectTypeOf<TransformCtx["mutateResult"]>().toEqualTypeOf<undefined>();
528
+ });
529
+ });
530
+
531
+ describe("withServiceCalls + mutate flow", () => {
532
+ it("serviceIntermediateResult is available in mutate", () => {
533
+ type ServiceCall = TxResult<number, { user: { id: string } }>;
534
+
535
+ type Builder = HandlerTxBuilder<
536
+ readonly [ServiceCall],
537
+ [],
538
+ [],
539
+ unknown,
540
+ unknown,
541
+ false,
542
+ false,
543
+ true, // HasMutate
544
+ false,
545
+ {}
546
+ >;
547
+
548
+ type MutateParam = Parameters<Builder["mutate"]>[0];
549
+ type MutateCtx = Parameters<MutateParam>[0];
550
+
551
+ expectTypeOf<MutateCtx["serviceIntermediateResult"]>().toEqualTypeOf<
552
+ readonly [{ user: { id: string } }]
553
+ >();
554
+ });
555
+ });
556
+
557
+ describe("retrieve + transformRetrieve flow", () => {
558
+ it("transformRetrieve receives raw retrieve results", () => {
559
+ type Builder = HandlerTxBuilder<
560
+ readonly [],
561
+ [{ id: string }, { id: string }], // retrieve results
562
+ [{ id: string }, { id: string }],
563
+ unknown,
564
+ unknown,
565
+ true, // HasRetrieve
566
+ false,
567
+ false,
568
+ false,
569
+ {}
570
+ >;
571
+
572
+ type TransformRetrieveParam = Parameters<Builder["transformRetrieve"]>[0];
573
+
574
+ // First param is the raw retrieve results
575
+ expectTypeOf<Parameters<TransformRetrieveParam>[0]>().toEqualTypeOf<
576
+ [{ id: string }, { id: string }]
577
+ >();
578
+ });
579
+
580
+ it("mutate receives transformed retrieve result when transformRetrieve is set", () => {
581
+ type Builder = HandlerTxBuilder<
582
+ readonly [],
583
+ [{ id: string }[]],
584
+ { id: string } | null, // transformed
585
+ unknown,
586
+ unknown,
587
+ true, // HasRetrieve
588
+ true, // HasTransformRetrieve
589
+ true, // HasMutate
590
+ false,
591
+ {}
592
+ >;
593
+
594
+ type MutateParam = Parameters<Builder["mutate"]>[0];
595
+ type MutateCtx = Parameters<MutateParam>[0];
596
+
597
+ // retrieveResult is the transformed type
598
+ expectTypeOf<MutateCtx["retrieveResult"]>().toEqualTypeOf<{ id: string } | null>();
599
+ });
600
+ });
601
+ });
602
+
603
+ // =============================================================================
604
+ // Helper Type Tests
605
+ // =============================================================================
606
+
607
+ describe("Helper type utilities", () => {
608
+ describe("ExtractServiceRetrieveResults", () => {
609
+ it("extracts retrieve success results from TxResult tuple", () => {
610
+ type ServiceCalls = readonly [TxResult<number, string>, TxResult<boolean, { data: number }>];
611
+
612
+ type Result = ExtractServiceRetrieveResults<ServiceCalls>;
613
+
614
+ // Should extract the second type parameter (retrieve success result)
615
+ expectTypeOf<Result>().toEqualTypeOf<readonly [string, { data: number }]>();
616
+ });
617
+
618
+ it("handles undefined in service calls", () => {
619
+ type ServiceCalls = readonly [TxResult<number, string>, undefined];
620
+
621
+ type Result = ExtractServiceRetrieveResults<ServiceCalls>;
622
+
623
+ expectTypeOf<Result>().toEqualTypeOf<readonly [string, undefined]>();
624
+ });
625
+
626
+ it("handles empty service calls array", () => {
627
+ type ServiceCalls = readonly [];
628
+
629
+ type Result = ExtractServiceRetrieveResults<ServiceCalls>;
630
+
631
+ expectTypeOf<Result>().toEqualTypeOf<readonly []>();
632
+ });
633
+ });
634
+
635
+ describe("ExtractServiceFinalResults", () => {
636
+ it("extracts final results from TxResult tuple", () => {
637
+ type ServiceCalls = readonly [
638
+ TxResult<{ id: number }, string>,
639
+ TxResult<boolean, { data: number }>,
640
+ ];
641
+
642
+ type Result = ExtractServiceFinalResults<ServiceCalls>;
643
+
644
+ // Should extract the first type parameter (final result)
645
+ expectTypeOf<Result>().toEqualTypeOf<readonly [{ id: number }, boolean]>();
646
+ });
647
+
648
+ it("handles undefined in service calls", () => {
649
+ type ServiceCalls = readonly [TxResult<number, string>, undefined];
650
+
651
+ type Result = ExtractServiceFinalResults<ServiceCalls>;
652
+
653
+ expectTypeOf<Result>().toEqualTypeOf<readonly [number, undefined]>();
654
+ });
655
+
656
+ it("handles empty service calls array", () => {
657
+ type ServiceCalls = readonly [];
658
+
659
+ type Result = ExtractServiceFinalResults<ServiceCalls>;
660
+
661
+ expectTypeOf<Result>().toEqualTypeOf<readonly []>();
662
+ });
663
+ });
664
+ });
665
+
666
+ // =============================================================================
667
+ // Builder Method Parameter Type Tests
668
+ // =============================================================================
669
+
670
+ describe("ServiceTxBuilder method parameter types", () => {
671
+ // Test with an initial builder type
672
+ type InitialBuilder = ServiceTxBuilder<
673
+ TestSchema,
674
+ readonly [],
675
+ [],
676
+ [],
677
+ unknown,
678
+ unknown,
679
+ false,
680
+ false,
681
+ false,
682
+ false,
683
+ {}
684
+ >;
685
+
686
+ describe("retrieve() callback parameter", () => {
687
+ it("receives TypedUnitOfWork with correct schema", () => {
688
+ type RetrieveCallback = Parameters<InitialBuilder["retrieve"]>[0];
689
+ type UowParam = Parameters<RetrieveCallback>[0];
690
+
691
+ // The UoW should be typed for TestSchema
692
+ expectTypeOf<UowParam>().toExtend<TypedUnitOfWork<TestSchema, [], unknown, {}>>();
693
+ });
694
+ });
695
+
696
+ describe("transformRetrieve() callback parameters", () => {
697
+ // Builder with retrieve results set
698
+ type BuilderWithRetrieve = ServiceTxBuilder<
699
+ TestSchema,
700
+ readonly [],
701
+ [{ id: string; name: string }[]], // retrieve results
702
+ [{ id: string; name: string }[]], // same since no transformRetrieve yet
703
+ unknown,
704
+ unknown,
705
+ true, // HasRetrieve
706
+ false,
707
+ false,
708
+ false,
709
+ {}
710
+ >;
711
+
712
+ it("first param is raw retrieve results", () => {
713
+ type TransformRetrieveCallback = Parameters<BuilderWithRetrieve["transformRetrieve"]>[0];
714
+ type FirstParam = Parameters<TransformRetrieveCallback>[0];
715
+
716
+ expectTypeOf<FirstParam>().toEqualTypeOf<[{ id: string; name: string }[]]>();
717
+ });
718
+
719
+ it("second param is service retrieve results", () => {
720
+ // Builder with service calls
721
+ type BuilderWithServices = ServiceTxBuilder<
722
+ TestSchema,
723
+ readonly [TxResult<number, { userId: string }>, TxResult<boolean, { count: number }>],
724
+ [{ id: string }[]],
725
+ [{ id: string }[]],
726
+ unknown,
727
+ unknown,
728
+ true,
729
+ false,
730
+ false,
731
+ false,
732
+ {}
733
+ >;
734
+
735
+ type TransformRetrieveCallback = Parameters<BuilderWithServices["transformRetrieve"]>[0];
736
+ type SecondParam = Parameters<TransformRetrieveCallback>[1];
737
+
738
+ expectTypeOf<SecondParam>().toEqualTypeOf<readonly [{ userId: string }, { count: number }]>();
739
+ });
740
+ });
741
+
742
+ describe("mutate() context parameter", () => {
743
+ it("has uow typed for schema", () => {
744
+ type MutateCallback = Parameters<InitialBuilder["mutate"]>[0];
745
+ type MutateCtx = Parameters<MutateCallback>[0];
746
+
747
+ expectTypeOf<MutateCtx["uow"]>().toExtend<TypedUnitOfWork<TestSchema, [], unknown, {}>>();
748
+ });
749
+
750
+ it("has retrieveResult from retrieve results when no transformRetrieve", () => {
751
+ type BuilderWithRetrieve = ServiceTxBuilder<
752
+ TestSchema,
753
+ readonly [],
754
+ [{ id: string; name: string }[]],
755
+ [{ id: string; name: string }[]], // same as TRetrieveResults
756
+ unknown,
757
+ unknown,
758
+ true,
759
+ false, // no transformRetrieve
760
+ false,
761
+ false,
762
+ {}
763
+ >;
764
+
765
+ type MutateCallback = Parameters<BuilderWithRetrieve["mutate"]>[0];
766
+ type MutateCtx = Parameters<MutateCallback>[0];
767
+
768
+ expectTypeOf<MutateCtx["retrieveResult"]>().toEqualTypeOf<[{ id: string; name: string }[]]>();
769
+ });
770
+
771
+ it("has retrieveResult from transformRetrieve when set", () => {
772
+ type BuilderWithTransformRetrieve = ServiceTxBuilder<
773
+ TestSchema,
774
+ readonly [],
775
+ [{ id: string; name: string }[]],
776
+ { user: { id: string } } | null, // transformed
777
+ unknown,
778
+ unknown,
779
+ true,
780
+ true, // has transformRetrieve
781
+ false,
782
+ false,
783
+ {}
784
+ >;
785
+
786
+ type MutateCallback = Parameters<BuilderWithTransformRetrieve["mutate"]>[0];
787
+ type MutateCtx = Parameters<MutateCallback>[0];
788
+
789
+ expectTypeOf<MutateCtx["retrieveResult"]>().toEqualTypeOf<{ user: { id: string } } | null>();
790
+ });
791
+
792
+ it("has serviceResult from service calls", () => {
793
+ type BuilderWithServices = ServiceTxBuilder<
794
+ TestSchema,
795
+ readonly [TxResult<string, { data: number[] }>],
796
+ [],
797
+ [],
798
+ unknown,
799
+ unknown,
800
+ false,
801
+ false,
802
+ false,
803
+ false,
804
+ {}
805
+ >;
806
+
807
+ type MutateCallback = Parameters<BuilderWithServices["mutate"]>[0];
808
+ type MutateCtx = Parameters<MutateCallback>[0];
809
+
810
+ expectTypeOf<MutateCtx["serviceIntermediateResult"]>().toEqualTypeOf<
811
+ readonly [{ data: number[] }]
812
+ >();
813
+ });
814
+ });
815
+
816
+ describe("transform() context parameter", () => {
817
+ it("has mutateResult when HasMutate=true", () => {
818
+ type BuilderWithMutate = ServiceTxBuilder<
819
+ TestSchema,
820
+ readonly [],
821
+ [{ id: string }[]],
822
+ [{ id: string }[]],
823
+ { created: boolean }, // mutate result
824
+ unknown,
825
+ true,
826
+ false,
827
+ true, // HasMutate
828
+ false,
829
+ {}
830
+ >;
831
+
832
+ type TransformCallback = Parameters<BuilderWithMutate["transform"]>[0];
833
+ type TransformCtx = Parameters<TransformCallback>[0];
834
+
835
+ expectTypeOf<TransformCtx["mutateResult"]>().toEqualTypeOf<{ created: boolean }>();
836
+ });
837
+
838
+ it("has mutateResult as undefined when HasMutate=false", () => {
839
+ type BuilderWithoutMutate = ServiceTxBuilder<
840
+ TestSchema,
841
+ readonly [],
842
+ [{ id: string }[]],
843
+ [{ id: string }[]],
844
+ unknown,
845
+ unknown,
846
+ true,
847
+ false,
848
+ false, // HasMutate = false
849
+ false,
850
+ {}
851
+ >;
852
+
853
+ type TransformCallback = Parameters<BuilderWithoutMutate["transform"]>[0];
854
+ type TransformCtx = Parameters<TransformCallback>[0];
855
+
856
+ expectTypeOf<TransformCtx["mutateResult"]>().toEqualTypeOf<undefined>();
857
+ });
858
+
859
+ it("has serviceResult with final results and serviceIntermediateResult with retrieve results", () => {
860
+ type BuilderWithServices = ServiceTxBuilder<
861
+ TestSchema,
862
+ readonly [TxResult<{ finalValue: number }, { retrieveValue: string }>],
863
+ [],
864
+ [],
865
+ { id: string },
866
+ unknown,
867
+ false,
868
+ false,
869
+ true,
870
+ false,
871
+ {}
872
+ >;
873
+
874
+ type TransformCallback = Parameters<BuilderWithServices["transform"]>[0];
875
+ type TransformCtx = Parameters<TransformCallback>[0];
876
+
877
+ // serviceResult has FINAL results
878
+ expectTypeOf<TransformCtx["serviceResult"]>().toEqualTypeOf<
879
+ readonly [{ finalValue: number }]
880
+ >();
881
+ // serviceIntermediateResult has RETRIEVE results
882
+ expectTypeOf<TransformCtx["serviceIntermediateResult"]>().toEqualTypeOf<
883
+ readonly [{ retrieveValue: string }]
884
+ >();
885
+ });
886
+ });
887
+ });
888
+
889
+ describe("HandlerTxBuilder method parameter types", () => {
890
+ type InitialBuilder = HandlerTxBuilder<
891
+ readonly [],
892
+ [],
893
+ [],
894
+ unknown,
895
+ unknown,
896
+ false,
897
+ false,
898
+ false,
899
+ false,
900
+ {}
901
+ >;
902
+
903
+ describe("retrieve() callback parameter", () => {
904
+ it("receives context with idempotencyKey and currentAttempt", () => {
905
+ type RetrieveCallback = Parameters<InitialBuilder["retrieve"]>[0];
906
+ type CtxParam = Parameters<RetrieveCallback>[0];
907
+
908
+ expectTypeOf<CtxParam["idempotencyKey"]>().toEqualTypeOf<string>();
909
+ expectTypeOf<CtxParam["currentAttempt"]>().toEqualTypeOf<number>();
910
+ expectTypeOf<CtxParam["forSchema"]>().toBeFunction();
911
+ });
912
+ });
913
+
914
+ describe("mutate() context parameter", () => {
915
+ it("has idempotencyKey, currentAttempt, and forSchema", () => {
916
+ type BuilderWithRetrieve = HandlerTxBuilder<
917
+ readonly [],
918
+ [{ id: string }[]],
919
+ [{ id: string }[]],
920
+ unknown,
921
+ unknown,
922
+ true,
923
+ false,
924
+ false,
925
+ false,
926
+ {}
927
+ >;
928
+
929
+ type MutateCallback = Parameters<BuilderWithRetrieve["mutate"]>[0];
930
+ type MutateCtx = Parameters<MutateCallback>[0];
931
+
932
+ expectTypeOf<MutateCtx["idempotencyKey"]>().toEqualTypeOf<string>();
933
+ expectTypeOf<MutateCtx["currentAttempt"]>().toEqualTypeOf<number>();
934
+ expectTypeOf<MutateCtx["forSchema"]>().toBeFunction();
935
+ });
936
+
937
+ it("has retrieveResult from retrieve", () => {
938
+ type BuilderWithRetrieve = HandlerTxBuilder<
939
+ readonly [],
940
+ [{ id: string }[]],
941
+ [{ id: string }[]],
942
+ unknown,
943
+ unknown,
944
+ true,
945
+ false,
946
+ false,
947
+ false,
948
+ {}
949
+ >;
950
+
951
+ type MutateCallback = Parameters<BuilderWithRetrieve["mutate"]>[0];
952
+ type MutateCtx = Parameters<MutateCallback>[0];
953
+
954
+ expectTypeOf<MutateCtx["retrieveResult"]>().toEqualTypeOf<[{ id: string }[]]>();
955
+ });
956
+
957
+ it("has serviceResult from service calls", () => {
958
+ type BuilderWithServices = HandlerTxBuilder<
959
+ readonly [TxResult<number, { user: { id: string } }>],
960
+ [],
961
+ [],
962
+ unknown,
963
+ unknown,
964
+ false,
965
+ false,
966
+ false,
967
+ false,
968
+ {}
969
+ >;
970
+
971
+ type MutateCallback = Parameters<BuilderWithServices["mutate"]>[0];
972
+ type MutateCtx = Parameters<MutateCallback>[0];
973
+
974
+ expectTypeOf<MutateCtx["serviceIntermediateResult"]>().toEqualTypeOf<
975
+ readonly [{ user: { id: string } }]
976
+ >();
977
+ });
978
+ });
979
+
980
+ describe("execute() return type", () => {
981
+ it("returns Promise of mutate result when HasMutate=true", () => {
982
+ type BuilderWithMutate = HandlerTxBuilder<
983
+ readonly [],
984
+ [],
985
+ [],
986
+ { orderId: string; success: boolean }, // mutate result
987
+ unknown,
988
+ false,
989
+ false,
990
+ true, // HasMutate
991
+ false,
992
+ {}
993
+ >;
994
+
995
+ type ExecuteResult = ReturnType<BuilderWithMutate["execute"]>;
996
+
997
+ expectTypeOf<ExecuteResult>().toEqualTypeOf<Promise<{ orderId: string; success: boolean }>>();
998
+ });
999
+
1000
+ it("returns Promise of transform result when HasTransform=true", () => {
1001
+ type BuilderWithTransform = HandlerTxBuilder<
1002
+ readonly [],
1003
+ [],
1004
+ [],
1005
+ { orderId: string },
1006
+ { success: true; order: { id: string } }, // transform result
1007
+ false,
1008
+ false,
1009
+ true,
1010
+ true, // HasTransform
1011
+ {}
1012
+ >;
1013
+
1014
+ type ExecuteResult = ReturnType<BuilderWithTransform["execute"]>;
1015
+
1016
+ expectTypeOf<ExecuteResult>().toEqualTypeOf<
1017
+ Promise<{ success: true; order: { id: string } }>
1018
+ >();
1019
+ });
1020
+
1021
+ it("returns Promise of service final results when only withServiceCalls is set", () => {
1022
+ type BuilderWithServices = HandlerTxBuilder<
1023
+ readonly [TxResult<{ data: string }, unknown>, TxResult<number, unknown>],
1024
+ [],
1025
+ [],
1026
+ unknown,
1027
+ unknown,
1028
+ false,
1029
+ false,
1030
+ false,
1031
+ false,
1032
+ {}
1033
+ >;
1034
+
1035
+ type ExecuteResult = ReturnType<BuilderWithServices["execute"]>;
1036
+
1037
+ // Result is the awaited service final results tuple
1038
+ expectTypeOf<ExecuteResult>().toExtend<Promise<readonly [{ data: string }, number]>>();
1039
+ });
1040
+ });
1041
+ });