@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,6 +1,6 @@
1
1
  import { FragnoId } from "../schema/create.js";
2
- import { Cursor } from "./cursor.js";
3
2
  import { buildCondition } from "./condition-builder.js";
3
+ import { Cursor } from "./cursor.js";
4
4
 
5
5
  //#region src/query/unit-of-work.ts
6
6
  /**
@@ -338,29 +338,188 @@ function buildJoinIndexed(table, fn) {
338
338
  fn(builder);
339
339
  return compiled;
340
340
  }
341
- function createUnitOfWork(schema, compiler, executor, decoder, name) {
342
- return new UnitOfWork(schema, compiler, executor, decoder, name);
341
+ function createUnitOfWork(compiler, executor, decoder, schemaNamespaceMap, name) {
342
+ return new UnitOfWork(compiler, executor, decoder, name, void 0, schemaNamespaceMap);
343
343
  }
344
344
  /**
345
+ * Encapsulates a promise with its resolver/rejecter functions.
346
+ * Simplifies management of deferred promises with built-in error handling.
347
+ */
348
+ var DeferredPromise = class {
349
+ #resolve;
350
+ #reject;
351
+ #promise;
352
+ constructor() {
353
+ const { promise, resolve, reject } = Promise.withResolvers();
354
+ this.#promise = promise;
355
+ this.#resolve = resolve;
356
+ this.#reject = reject;
357
+ this.#promise.catch(() => {});
358
+ }
359
+ get promise() {
360
+ return this.#promise;
361
+ }
362
+ resolve(value) {
363
+ this.#resolve?.(value);
364
+ }
365
+ reject(error) {
366
+ this.#reject?.(error);
367
+ }
368
+ /**
369
+ * Reset to a new promise
370
+ */
371
+ reset() {
372
+ const { promise, resolve, reject } = Promise.withResolvers();
373
+ this.#promise = promise;
374
+ this.#resolve = resolve;
375
+ this.#reject = reject;
376
+ this.#promise.catch(() => {});
377
+ }
378
+ };
379
+ /**
380
+ * Tracks readiness signals from a group of children.
381
+ * Maintains a promise that resolves when all registered children have signaled.
382
+ */
383
+ var ReadinessTracker = class {
384
+ #expectedCount = 0;
385
+ #signalCount = 0;
386
+ #resolve;
387
+ #promise = Promise.resolve();
388
+ get promise() {
389
+ return this.#promise;
390
+ }
391
+ /**
392
+ * Register that we're expecting a signal from a child
393
+ */
394
+ registerChild() {
395
+ if (this.#expectedCount === 0) {
396
+ const { promise, resolve } = Promise.withResolvers();
397
+ this.#promise = promise;
398
+ this.#resolve = resolve;
399
+ }
400
+ this.#expectedCount++;
401
+ }
402
+ /**
403
+ * Signal that one child is ready
404
+ */
405
+ signal() {
406
+ this.#signalCount++;
407
+ if (this.#signalCount >= this.#expectedCount && this.#resolve) this.#resolve();
408
+ }
409
+ /**
410
+ * Reset to initial state
411
+ */
412
+ reset() {
413
+ this.#expectedCount = 0;
414
+ this.#signalCount = 0;
415
+ this.#resolve = void 0;
416
+ this.#promise = Promise.resolve();
417
+ }
418
+ };
419
+ /**
420
+ * Manages parent-child relationships and readiness coordination for Unit of Work instances.
421
+ * This allows parent UOWs to wait for all child UOWs to signal readiness before executing phases.
422
+ */
423
+ var UOWChildCoordinator = class {
424
+ #parent = null;
425
+ #parentCoordinator = null;
426
+ #children = /* @__PURE__ */ new Set();
427
+ #isRestricted = false;
428
+ #retrievalTracker = new ReadinessTracker();
429
+ #mutationTracker = new ReadinessTracker();
430
+ get isRestricted() {
431
+ return this.#isRestricted;
432
+ }
433
+ get parent() {
434
+ return this.#parent;
435
+ }
436
+ get children() {
437
+ return this.#children;
438
+ }
439
+ get retrievalReadinessPromise() {
440
+ return this.#retrievalTracker.promise;
441
+ }
442
+ get mutationReadinessPromise() {
443
+ return this.#mutationTracker.promise;
444
+ }
445
+ /**
446
+ * Mark this UOW as a restricted child of the given parent
447
+ */
448
+ setAsRestricted(parent, parentCoordinator) {
449
+ this.#parent = parent;
450
+ this.#parentCoordinator = parentCoordinator;
451
+ this.#isRestricted = true;
452
+ }
453
+ /**
454
+ * Register a child UOW
455
+ */
456
+ addChild(child) {
457
+ this.#children.add(child);
458
+ this.#retrievalTracker.registerChild();
459
+ this.#mutationTracker.registerChild();
460
+ }
461
+ /**
462
+ * Signal that this child is ready for retrieval phase execution.
463
+ * Only valid for restricted (child) UOWs.
464
+ */
465
+ signalReadyForRetrieval() {
466
+ if (!this.#parentCoordinator) throw new Error("signalReadyForRetrieval() can only be called on restricted child UOWs");
467
+ this.#parentCoordinator.notifyChildReadyForRetrieval();
468
+ }
469
+ /**
470
+ * Signal that this child is ready for mutation phase execution.
471
+ * Only valid for restricted (child) UOWs.
472
+ */
473
+ signalReadyForMutation() {
474
+ if (!this.#parentCoordinator) throw new Error("signalReadyForMutation() can only be called on restricted child UOWs");
475
+ this.#parentCoordinator.notifyChildReadyForMutation();
476
+ }
477
+ /**
478
+ * Notify this coordinator that a child is ready for retrieval (internal use).
479
+ * Called by child UOWs when they signal readiness.
480
+ */
481
+ notifyChildReadyForRetrieval() {
482
+ this.#retrievalTracker.signal();
483
+ }
484
+ /**
485
+ * Notify this coordinator that a child is ready for mutation (internal use).
486
+ * Called by child UOWs when they signal readiness.
487
+ */
488
+ notifyChildReadyForMutation() {
489
+ this.#mutationTracker.signal();
490
+ }
491
+ /**
492
+ * Reset coordination state for retry support
493
+ */
494
+ reset() {
495
+ this.#children.clear();
496
+ this.#retrievalTracker.reset();
497
+ this.#mutationTracker.reset();
498
+ }
499
+ };
500
+ /**
345
501
  * Unit of Work implementation with optimistic concurrency control
346
502
  *
347
503
  * UOW has two phases:
348
504
  * 1. Retrieval phase: Read operations to fetch entities with their versions
349
505
  * 2. Mutation phase: Write operations that check versions before committing
350
506
  *
507
+ * This is the untyped base storage. Use TypedUnitOfWork for type-safe operations.
508
+ *
351
509
  * @example
352
510
  * ```ts
353
511
  * const uow = queryEngine.createUnitOfWork("update-user-balance");
512
+ * const typedUow = uow.forSchema(mySchema);
354
513
  *
355
514
  * // Retrieval phase
356
- * uow.find("users", (b) => b.where("primary", (eb) => eb("id", "=", userId)));
515
+ * typedUow.find("users", (b) => b.whereIndex("primary", (eb) => eb("id", "=", userId)));
357
516
  *
358
517
  * // Execute retrieval and transition to mutation phase
359
518
  * const [users] = await uow.executeRetrieve();
360
519
  *
361
520
  * // Mutation phase with version check
362
521
  * const user = users[0];
363
- * uow.update("users", user.id, (b) => b.set({ balance: newBalance }).check());
522
+ * typedUow.update("users", user.id, (b) => b.set({ balance: newBalance }).check());
364
523
  *
365
524
  * // Execute mutations
366
525
  * const { success } = await uow.executeMutations();
@@ -369,28 +528,97 @@ function createUnitOfWork(schema, compiler, executor, decoder, name) {
369
528
  * }
370
529
  * ```
371
530
  */
372
- var UnitOfWork = class {
373
- #schema;
531
+ var UnitOfWork = class UnitOfWork {
374
532
  #name;
375
533
  #config;
534
+ #nonce;
376
535
  #state = "building-retrieval";
377
536
  #retrievalOps = [];
378
537
  #mutationOps = [];
379
538
  #compiler;
380
539
  #executor;
381
540
  #decoder;
541
+ #schemaNamespaceMap;
382
542
  #retrievalResults;
383
543
  #createdInternalIds = [];
384
- constructor(schema, compiler, executor, decoder, name, config) {
385
- this.#schema = schema;
544
+ #retrievalPhaseDeferred = new DeferredPromise();
545
+ #mutationPhaseDeferred = new DeferredPromise();
546
+ #retrievalError = null;
547
+ #mutationError = null;
548
+ #coordinator = new UOWChildCoordinator();
549
+ constructor(compiler, executor, decoder, name, config, schemaNamespaceMap) {
386
550
  this.#compiler = compiler;
387
551
  this.#executor = executor;
388
552
  this.#decoder = decoder;
553
+ this.#schemaNamespaceMap = schemaNamespaceMap;
389
554
  this.#name = name;
390
555
  this.#config = config;
556
+ this.#nonce = config?.nonce ?? crypto.randomUUID();
391
557
  }
392
- get schema() {
393
- return this.#schema;
558
+ /**
559
+ * Get a schema-specific typed view of this UOW for type-safe operations.
560
+ * Returns a wrapper that provides typed operations for the given schema.
561
+ * The namespace is automatically resolved from the schema-namespace map.
562
+ */
563
+ forSchema(schema) {
564
+ const resolvedNamespace = this.#schemaNamespaceMap?.get(schema);
565
+ return new TypedUnitOfWork(schema, resolvedNamespace, this);
566
+ }
567
+ /**
568
+ * Create a restricted child UOW that cannot execute phases.
569
+ * The child shares the same operation storage but must signal readiness
570
+ * before the parent can execute each phase.
571
+ */
572
+ restrict() {
573
+ const child = new UnitOfWork(this.#compiler, this.#executor, this.#decoder, this.#name, {
574
+ ...this.#config,
575
+ nonce: this.#nonce
576
+ }, this.#schemaNamespaceMap);
577
+ child.#coordinator.setAsRestricted(this, this.#coordinator);
578
+ child.#state = this.#state;
579
+ child.#retrievalOps = this.#retrievalOps;
580
+ child.#mutationOps = this.#mutationOps;
581
+ child.#retrievalResults = this.#retrievalResults;
582
+ child.#createdInternalIds = this.#createdInternalIds;
583
+ child.#retrievalPhaseDeferred = this.#retrievalPhaseDeferred;
584
+ child.#mutationPhaseDeferred = this.#mutationPhaseDeferred;
585
+ child.#retrievalError = this.#retrievalError;
586
+ child.#mutationError = this.#mutationError;
587
+ this.#coordinator.addChild(child);
588
+ child.signalReadyForRetrieval();
589
+ child.signalReadyForMutation();
590
+ return child;
591
+ }
592
+ /**
593
+ * Signal that this child is ready for retrieval phase execution.
594
+ * Only valid for restricted (child) UOWs.
595
+ */
596
+ signalReadyForRetrieval() {
597
+ this.#coordinator.signalReadyForRetrieval();
598
+ }
599
+ /**
600
+ * Signal that this child is ready for mutation phase execution.
601
+ * Only valid for restricted (child) UOWs.
602
+ */
603
+ signalReadyForMutation() {
604
+ this.#coordinator.signalReadyForMutation();
605
+ }
606
+ /**
607
+ * Reset the UOW to initial state for retry support.
608
+ * Clears operations, resets state, and resets phase promises.
609
+ */
610
+ reset() {
611
+ if (this.#coordinator.isRestricted) throw new Error("reset() cannot be called on restricted child UOWs");
612
+ this.#retrievalOps = [];
613
+ this.#mutationOps = [];
614
+ this.#retrievalResults = void 0;
615
+ this.#createdInternalIds = [];
616
+ this.#state = "building-retrieval";
617
+ this.#retrievalError = null;
618
+ this.#mutationError = null;
619
+ this.#retrievalPhaseDeferred.reset();
620
+ this.#mutationPhaseDeferred.reset();
621
+ this.#coordinator.reset();
394
622
  }
395
623
  get state() {
396
624
  return this.#state;
@@ -398,75 +626,300 @@ var UnitOfWork = class {
398
626
  get name() {
399
627
  return this.#name;
400
628
  }
629
+ get nonce() {
630
+ return this.#nonce;
631
+ }
632
+ /**
633
+ * Promise that resolves when the retrieval phase is executed
634
+ * Service methods can await this to coordinate multi-phase logic
635
+ */
636
+ get retrievalPhase() {
637
+ return this.#retrievalPhaseDeferred.promise;
638
+ }
639
+ /**
640
+ * Promise that resolves when the mutation phase is executed
641
+ * Service methods can await this to coordinate multi-phase logic
642
+ */
643
+ get mutationPhase() {
644
+ return this.#mutationPhaseDeferred.promise;
645
+ }
401
646
  /**
402
647
  * Execute the retrieval phase and transition to mutation phase
403
648
  * Returns all results from find operations
404
649
  */
405
650
  async executeRetrieve() {
406
- if (this.#retrievalOps.length === 0) return [];
651
+ if (this.#coordinator.isRestricted) throw new Error("executeRetrieve() cannot be called on restricted child UOWs");
407
652
  if (this.#state !== "building-retrieval") throw new Error(`Cannot execute retrieval from state ${this.#state}. Must be in building-retrieval state.`);
408
- const retrievalBatch = [];
409
- for (const op of this.#retrievalOps) {
410
- const compiled = this.#compiler.compileRetrievalOperation(op);
411
- if (compiled !== null) {
412
- this.#config?.onQuery?.(compiled);
413
- retrievalBatch.push(compiled);
653
+ try {
654
+ await this.#coordinator.retrievalReadinessPromise;
655
+ if (this.#retrievalOps.length === 0) {
656
+ this.#state = "building-mutation";
657
+ const emptyResults = [];
658
+ this.#retrievalPhaseDeferred.resolve(emptyResults);
659
+ return emptyResults;
660
+ }
661
+ const retrievalBatch = [];
662
+ for (const op of this.#retrievalOps) {
663
+ const compiled = this.#compiler.compileRetrievalOperation(op);
664
+ if (compiled !== null) {
665
+ this.#config?.onQuery?.(compiled);
666
+ retrievalBatch.push(compiled);
667
+ }
414
668
  }
669
+ if (this.#config?.dryRun) {
670
+ this.#state = "executed";
671
+ const emptyResults = [];
672
+ this.#retrievalPhaseDeferred.resolve(emptyResults);
673
+ return emptyResults;
674
+ }
675
+ const rawResults = await this.#executor.executeRetrievalPhase(retrievalBatch);
676
+ this.#retrievalResults = this.#decoder(rawResults, this.#retrievalOps);
677
+ this.#state = "building-mutation";
678
+ this.#retrievalPhaseDeferred.resolve(this.#retrievalResults);
679
+ return this.#retrievalResults;
680
+ } catch (error) {
681
+ this.#retrievalError = error instanceof Error ? error : new Error(String(error));
682
+ throw error;
415
683
  }
416
- if (this.#config?.dryRun) {
684
+ }
685
+ /**
686
+ * Execute the mutation phase
687
+ * Returns success flag indicating if mutations completed without conflicts
688
+ */
689
+ async executeMutations() {
690
+ if (this.#coordinator.isRestricted) throw new Error("executeMutations() cannot be called on restricted child UOWs");
691
+ if (this.#state === "executed") throw new Error(`Cannot execute mutations from state ${this.#state}.`);
692
+ try {
693
+ await this.#coordinator.mutationReadinessPromise;
694
+ const mutationBatch = [];
695
+ for (const op of this.#mutationOps) {
696
+ const compiled = this.#compiler.compileMutationOperation(op);
697
+ if (compiled !== null) {
698
+ this.#config?.onQuery?.(compiled);
699
+ mutationBatch.push(compiled);
700
+ }
701
+ }
702
+ if (this.#config?.dryRun) {
703
+ this.#state = "executed";
704
+ this.#mutationPhaseDeferred.resolve();
705
+ return { success: true };
706
+ }
707
+ const result = await this.#executor.executeMutationPhase(mutationBatch);
417
708
  this.#state = "executed";
418
- return [];
709
+ if (result.success) this.#createdInternalIds = result.createdInternalIds;
710
+ this.#mutationPhaseDeferred.resolve();
711
+ return { success: result.success };
712
+ } catch (error) {
713
+ this.#mutationError = error instanceof Error ? error : new Error(String(error));
714
+ throw error;
715
+ }
716
+ }
717
+ /**
718
+ * Get the retrieval operations (for inspection/debugging)
719
+ */
720
+ getRetrievalOperations() {
721
+ return this.#retrievalOps;
722
+ }
723
+ /**
724
+ * Get the mutation operations (for inspection/debugging)
725
+ */
726
+ getMutationOperations() {
727
+ return this.#mutationOps;
728
+ }
729
+ /**
730
+ * @internal
731
+ * Add a retrieval operation (used by TypedUnitOfWork)
732
+ */
733
+ addRetrievalOperation(op) {
734
+ if (this.#state !== "building-retrieval") throw new Error(`Cannot add retrieval operation in state ${this.#state}. Must be in building-retrieval state.`);
735
+ this.#retrievalOps.push(op);
736
+ return this.#retrievalOps.length - 1;
737
+ }
738
+ /**
739
+ * @internal
740
+ * Add a mutation operation (used by TypedUnitOfWork)
741
+ */
742
+ addMutationOperation(op) {
743
+ if (this.#state === "executed") throw new Error(`Cannot add mutation operation in executed state.`);
744
+ this.#mutationOps.push(op);
745
+ }
746
+ /**
747
+ * Get the IDs of created entities after executeMutations() has been called.
748
+ * Returns FragnoId objects with external IDs (always available) and internal IDs
749
+ * (available when database supports RETURNING).
750
+ *
751
+ * @throws Error if called before executeMutations()
752
+ * @returns Array of FragnoIds in the same order as create() calls
753
+ */
754
+ getCreatedIds() {
755
+ if (this.#state !== "executed") throw new Error(`getCreatedIds() can only be called after executeMutations(). Current state: ${this.#state}`);
756
+ const createdIds = [];
757
+ let createIndex = 0;
758
+ for (const op of this.#mutationOps) if (op.type === "create") {
759
+ const internalId = this.#createdInternalIds[createIndex] ?? void 0;
760
+ createdIds.push(new FragnoId({
761
+ externalId: op.generatedExternalId,
762
+ internalId,
763
+ version: 0
764
+ }));
765
+ createIndex++;
419
766
  }
420
- this.#retrievalResults = this.#decoder(await this.#executor.executeRetrievalPhase(retrievalBatch), this.#retrievalOps);
421
- this.#state = "building-mutation";
422
- return this.#retrievalResults;
767
+ return createdIds;
423
768
  }
424
769
  /**
425
- * Add a find operation using a builder callback (retrieval phase only)
770
+ * @internal
771
+ * Compile the unit of work to executable queries for testing
426
772
  */
773
+ compile(compiler) {
774
+ const retrievalBatch = [];
775
+ for (const op of this.#retrievalOps) {
776
+ const compiled = compiler.compileRetrievalOperation(op);
777
+ if (compiled !== null) retrievalBatch.push(compiled);
778
+ }
779
+ const mutationBatch = [];
780
+ for (const op of this.#mutationOps) {
781
+ const compiled = compiler.compileMutationOperation(op);
782
+ if (compiled !== null) mutationBatch.push(compiled);
783
+ }
784
+ return {
785
+ name: this.#name,
786
+ retrievalBatch,
787
+ mutationBatch
788
+ };
789
+ }
790
+ };
791
+ /**
792
+ * A typed facade around a UnitOfWork that provides type-safe operations for a specific schema.
793
+ * All operations are stored in the underlying UOW, but this facade ensures type safety and
794
+ * filters retrieval results to only include operations added through this facade.
795
+ */
796
+ var TypedUnitOfWork = class {
797
+ #schema;
798
+ #namespace;
799
+ #uow;
800
+ #operationIndices = [];
801
+ #cachedRetrievalPhase;
802
+ constructor(schema, namespace, uow) {
803
+ this.#schema = schema;
804
+ this.#namespace = namespace;
805
+ this.#uow = uow;
806
+ }
807
+ get $results() {
808
+ throw new Error("type only");
809
+ }
810
+ get schema() {
811
+ return this.#schema;
812
+ }
813
+ get name() {
814
+ return this.#uow.name;
815
+ }
816
+ get nonce() {
817
+ return this.#uow.nonce;
818
+ }
819
+ get state() {
820
+ return this.#uow.state;
821
+ }
822
+ get retrievalPhase() {
823
+ if (!this.#cachedRetrievalPhase) this.#cachedRetrievalPhase = this.#uow.retrievalPhase.then((allResults) => {
824
+ const allOperations = this.#uow.getRetrievalOperations();
825
+ return this.#operationIndices.map((opIndex) => {
826
+ const result = allResults[opIndex];
827
+ const operation = allOperations[opIndex];
828
+ if (operation?.type === "find" && operation.withSingleResult) return Array.isArray(result) ? result[0] ?? null : result;
829
+ return result;
830
+ });
831
+ });
832
+ return this.#cachedRetrievalPhase;
833
+ }
834
+ get mutationPhase() {
835
+ return this.#uow.mutationPhase;
836
+ }
837
+ getRetrievalOperations() {
838
+ return this.#uow.getRetrievalOperations();
839
+ }
840
+ getMutationOperations() {
841
+ return this.#uow.getMutationOperations();
842
+ }
843
+ getCreatedIds() {
844
+ return this.#uow.getCreatedIds();
845
+ }
846
+ async executeRetrieve() {
847
+ return this.#uow.executeRetrieve();
848
+ }
849
+ async executeMutations() {
850
+ return this.#uow.executeMutations();
851
+ }
852
+ restrict() {
853
+ return this.#uow.restrict();
854
+ }
855
+ reset() {
856
+ return this.#uow.reset();
857
+ }
858
+ forSchema(schema) {
859
+ return this.#uow.forSchema(schema);
860
+ }
861
+ compile(compiler) {
862
+ return this.#uow.compile(compiler);
863
+ }
427
864
  find(tableName, builderFn) {
428
- if (this.#state !== "building-retrieval") throw new Error(`find() can only be called during retrieval phase. Current state: ${this.#state}`);
429
865
  const table = this.#schema.tables[tableName];
430
866
  if (!table) throw new Error(`Table ${tableName} not found in schema`);
431
867
  const builder = new FindBuilder(tableName, table);
432
868
  if (builderFn) builderFn(builder);
433
869
  else builder.whereIndex("primary");
434
870
  const { indexName, options, type } = builder.build();
435
- this.#retrievalOps.push({
871
+ const operationIndex = this.#uow.addRetrievalOperation({
436
872
  type,
873
+ schema: this.#schema,
874
+ namespace: this.#namespace,
437
875
  table,
438
876
  indexName,
439
877
  options
440
878
  });
879
+ this.#operationIndices.push(operationIndex);
880
+ return this;
881
+ }
882
+ findFirst(tableName, builderFn) {
883
+ const table = this.#schema.tables[tableName];
884
+ if (!table) throw new Error(`Table ${tableName} not found in schema`);
885
+ const builder = new FindBuilder(tableName, table);
886
+ if (builderFn) builderFn(builder);
887
+ else builder.whereIndex("primary");
888
+ builder.pageSize(1);
889
+ const { indexName, options, type } = builder.build();
890
+ const operationIndex = this.#uow.addRetrievalOperation({
891
+ type,
892
+ schema: this.#schema,
893
+ namespace: this.#namespace,
894
+ table,
895
+ indexName,
896
+ options,
897
+ withSingleResult: true
898
+ });
899
+ this.#operationIndices.push(operationIndex);
441
900
  return this;
442
901
  }
443
- /**
444
- * Add a find operation with cursor metadata (retrieval phase only)
445
- */
446
902
  findWithCursor(tableName, builderFn) {
447
- if (this.#state !== "building-retrieval") throw new Error(`findWithCursor() can only be called during retrieval phase. Current state: ${this.#state}`);
448
903
  const table = this.#schema.tables[tableName];
449
904
  if (!table) throw new Error(`Table ${tableName} not found in schema`);
450
905
  const builder = new FindBuilder(tableName, table);
451
906
  builderFn(builder);
452
907
  const { indexName, options, type } = builder.build();
453
- this.#retrievalOps.push({
908
+ const operationIndex = this.#uow.addRetrievalOperation({
454
909
  type,
910
+ schema: this.#schema,
911
+ namespace: this.#namespace,
455
912
  table,
456
913
  indexName,
457
914
  options,
458
915
  withCursor: true
459
916
  });
917
+ this.#operationIndices.push(operationIndex);
460
918
  return this;
461
919
  }
462
- /**
463
- * Add a create operation (mutation phase only)
464
- * Returns a FragnoId with the external ID that can be used immediately in subsequent operations
465
- */
466
- create(table, values) {
467
- if (this.#state === "executed") throw new Error(`create() can only be called during mutation phase.`);
468
- const tableSchema = this.#schema.tables[table];
469
- if (!tableSchema) throw new Error(`Table ${table} not found in schema`);
920
+ create(tableName, values) {
921
+ const tableSchema = this.#schema.tables[tableName];
922
+ if (!tableSchema) throw new Error(`Table ${tableName} not found in schema`);
470
923
  const idColumn = tableSchema.getIdColumn();
471
924
  let externalId;
472
925
  let updatedValues = values;
@@ -482,126 +935,70 @@ var UnitOfWork = class {
482
935
  [idColumn.ormName]: externalId
483
936
  };
484
937
  }
485
- this.#mutationOps.push({
938
+ this.#uow.addMutationOperation({
486
939
  type: "create",
487
- table,
940
+ schema: this.#schema,
941
+ namespace: this.#namespace,
942
+ table: tableName,
488
943
  values: updatedValues,
489
944
  generatedExternalId: externalId
490
945
  });
491
946
  return FragnoId.fromExternal(externalId, 0);
492
947
  }
493
- /**
494
- * Add an update operation using a builder callback (mutation phase only)
495
- */
496
- update(table, id, builderFn) {
497
- if (this.#state === "executed") throw new Error(`update() can only be called during mutation phase.`);
498
- const builder = new UpdateBuilder(table, id);
948
+ update(tableName, id, builderFn) {
949
+ const builder = new UpdateBuilder(tableName, id);
499
950
  builderFn(builder);
500
951
  const { id: opId, checkVersion, set } = builder.build();
501
- this.#mutationOps.push({
952
+ this.#uow.addMutationOperation({
502
953
  type: "update",
503
- table,
954
+ schema: this.#schema,
955
+ namespace: this.#namespace,
956
+ table: tableName,
504
957
  id: opId,
505
958
  checkVersion,
506
959
  set
507
960
  });
508
961
  }
509
- /**
510
- * Add a delete operation using a builder callback (mutation phase only)
511
- */
512
- delete(table, id, builderFn) {
513
- if (this.#state === "executed") throw new Error(`delete() can only be called during mutation phase.`);
514
- const builder = new DeleteBuilder(table, id);
962
+ delete(tableName, id, builderFn) {
963
+ const builder = new DeleteBuilder(tableName, id);
515
964
  builderFn?.(builder);
516
965
  const { id: opId, checkVersion } = builder.build();
517
- this.#mutationOps.push({
966
+ this.#uow.addMutationOperation({
518
967
  type: "delete",
519
- table,
968
+ schema: this.#schema,
969
+ namespace: this.#namespace,
970
+ table: tableName,
520
971
  id: opId,
521
972
  checkVersion
522
973
  });
523
974
  }
524
975
  /**
525
- * Execute the mutation phase
526
- * Returns success flag indicating if mutations completed without conflicts
527
- */
528
- async executeMutations() {
529
- if (this.#state === "executed") throw new Error(`Cannot execute mutations from state ${this.#state}.`);
530
- const mutationBatch = [];
531
- for (const op of this.#mutationOps) {
532
- const compiled = this.#compiler.compileMutationOperation(op);
533
- if (compiled !== null) {
534
- this.#config?.onQuery?.(compiled);
535
- mutationBatch.push(compiled);
536
- }
537
- }
538
- if (this.#config?.dryRun) {
539
- this.#state = "executed";
540
- return { success: true };
541
- }
542
- const result = await this.#executor.executeMutationPhase(mutationBatch);
543
- this.#state = "executed";
544
- if (result.success) this.#createdInternalIds = result.createdInternalIds;
545
- return { success: result.success };
546
- }
547
- /**
548
- * Get the retrieval operations (for inspection/debugging)
549
- */
550
- getRetrievalOperations() {
551
- return this.#retrievalOps;
552
- }
553
- /**
554
- * Get the mutation operations (for inspection/debugging)
555
- */
556
- getMutationOperations() {
557
- return this.#mutationOps;
558
- }
559
- /**
560
- * Get the IDs of created entities after executeMutations() has been called.
561
- * Returns FragnoId objects with external IDs (always available) and internal IDs
562
- * (available when database supports RETURNING).
976
+ * Check that a record's version hasn't changed since retrieval.
977
+ * This is useful for ensuring related records remain unchanged during a transaction.
563
978
  *
564
- * @throws Error if called before executeMutations()
565
- * @returns Array of FragnoIds in the same order as create() calls
566
- */
567
- getCreatedIds() {
568
- if (this.#state !== "executed") throw new Error(`getCreatedIds() can only be called after executeMutations(). Current state: ${this.#state}`);
569
- const createdIds = [];
570
- let createIndex = 0;
571
- for (const op of this.#mutationOps) if (op.type === "create") {
572
- const internalId = this.#createdInternalIds[createIndex] ?? void 0;
573
- createdIds.push(new FragnoId({
574
- externalId: op.generatedExternalId,
575
- internalId,
576
- version: 0
577
- }));
578
- createIndex++;
579
- }
580
- return createdIds;
581
- }
582
- /**
583
- * @internal
584
- * Compile the unit of work to executable queries for testing
979
+ * @param tableName - The table name
980
+ * @param id - The FragnoId with version information (string IDs are not allowed)
981
+ * @throws Error if the ID is a string without version information
982
+ *
983
+ * @example
984
+ * ```ts
985
+ * // Ensure both accounts haven't changed before creating a transfer
986
+ * uow.check("accounts", fromAccount.id);
987
+ * uow.check("accounts", toAccount.id);
988
+ * uow.create("transactions", { fromAccountId, toAccountId, amount });
989
+ * ```
585
990
  */
586
- compile(compiler) {
587
- const retrievalBatch = [];
588
- for (const op of this.#retrievalOps) {
589
- const compiled = compiler.compileRetrievalOperation(op);
590
- if (compiled !== null) retrievalBatch.push(compiled);
591
- }
592
- const mutationBatch = [];
593
- for (const op of this.#mutationOps) {
594
- const compiled = compiler.compileMutationOperation(op);
595
- if (compiled !== null) mutationBatch.push(compiled);
596
- }
597
- return {
598
- name: this.#name,
599
- retrievalBatch,
600
- mutationBatch
601
- };
991
+ check(tableName, id) {
992
+ this.#uow.addMutationOperation({
993
+ type: "check",
994
+ schema: this.#schema,
995
+ namespace: this.#namespace,
996
+ table: tableName,
997
+ id
998
+ });
602
999
  }
603
1000
  };
604
1001
 
605
1002
  //#endregion
606
- export { DeleteBuilder, FindBuilder, JoinFindBuilder, UnitOfWork, UpdateBuilder, buildJoinIndexed, createUnitOfWork };
1003
+ export { DeleteBuilder, FindBuilder, JoinFindBuilder, TypedUnitOfWork, UnitOfWork, UpdateBuilder, buildJoinIndexed, createUnitOfWork };
607
1004
  //# sourceMappingURL=unit-of-work.js.map