@fragno-dev/db 0.1.14 → 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 (183) hide show
  1. package/.turbo/turbo-build.log +179 -139
  2. package/CHANGELOG.md +24 -0
  3. package/dist/adapters/adapters.d.ts +15 -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 +3 -1
  7. package/dist/adapters/drizzle/drizzle-adapter.d.ts.map +1 -1
  8. package/dist/adapters/drizzle/drizzle-adapter.js +9 -2
  9. package/dist/adapters/drizzle/drizzle-adapter.js.map +1 -1
  10. package/dist/adapters/drizzle/drizzle-query.js +2 -2
  11. package/dist/adapters/drizzle/drizzle-query.js.map +1 -1
  12. package/dist/adapters/drizzle/drizzle-uow-compiler.js +27 -8
  13. package/dist/adapters/drizzle/drizzle-uow-compiler.js.map +1 -1
  14. package/dist/adapters/drizzle/drizzle-uow-decoder.js +22 -15
  15. package/dist/adapters/drizzle/drizzle-uow-decoder.js.map +1 -1
  16. package/dist/adapters/drizzle/drizzle-uow-executor.js +18 -7
  17. package/dist/adapters/drizzle/drizzle-uow-executor.js.map +1 -1
  18. package/dist/adapters/drizzle/generate.d.ts +4 -1
  19. package/dist/adapters/drizzle/generate.d.ts.map +1 -1
  20. package/dist/adapters/drizzle/generate.js +11 -18
  21. package/dist/adapters/drizzle/generate.js.map +1 -1
  22. package/dist/adapters/kysely/kysely-adapter.d.ts +3 -1
  23. package/dist/adapters/kysely/kysely-adapter.d.ts.map +1 -1
  24. package/dist/adapters/kysely/kysely-adapter.js +7 -1
  25. package/dist/adapters/kysely/kysely-adapter.js.map +1 -1
  26. package/dist/adapters/kysely/kysely-query-builder.js +1 -1
  27. package/dist/adapters/kysely/kysely-query-compiler.js +3 -2
  28. package/dist/adapters/kysely/kysely-query-compiler.js.map +1 -1
  29. package/dist/adapters/kysely/kysely-query.d.ts +1 -0
  30. package/dist/adapters/kysely/kysely-query.d.ts.map +1 -1
  31. package/dist/adapters/kysely/kysely-query.js +25 -18
  32. package/dist/adapters/kysely/kysely-query.js.map +1 -1
  33. package/dist/adapters/kysely/kysely-shared.d.ts +3 -0
  34. package/dist/adapters/kysely/kysely-shared.d.ts.map +1 -1
  35. package/dist/adapters/kysely/kysely-shared.js +16 -1
  36. package/dist/adapters/kysely/kysely-shared.js.map +1 -1
  37. package/dist/adapters/kysely/kysely-uow-compiler.js +34 -11
  38. package/dist/adapters/kysely/kysely-uow-compiler.js.map +1 -1
  39. package/dist/adapters/kysely/kysely-uow-executor.js +8 -4
  40. package/dist/adapters/kysely/kysely-uow-executor.js.map +1 -1
  41. package/dist/adapters/kysely/migration/execute-base.js +1 -1
  42. package/dist/adapters/kysely/migration/execute-base.js.map +1 -1
  43. package/dist/db-fragment-definition-builder.d.ts +152 -0
  44. package/dist/db-fragment-definition-builder.d.ts.map +1 -0
  45. package/dist/db-fragment-definition-builder.js +137 -0
  46. package/dist/db-fragment-definition-builder.js.map +1 -0
  47. package/dist/fragments/internal-fragment.d.ts +19 -0
  48. package/dist/fragments/internal-fragment.d.ts.map +1 -0
  49. package/dist/fragments/internal-fragment.js +39 -0
  50. package/dist/fragments/internal-fragment.js.map +1 -0
  51. package/dist/migration-engine/generation-engine.d.ts.map +1 -1
  52. package/dist/migration-engine/generation-engine.js +35 -15
  53. package/dist/migration-engine/generation-engine.js.map +1 -1
  54. package/dist/mod.d.ts +8 -20
  55. package/dist/mod.d.ts.map +1 -1
  56. package/dist/mod.js +7 -35
  57. package/dist/mod.js.map +1 -1
  58. package/dist/node_modules/.pnpm/rou3@0.7.8/node_modules/rou3/dist/index.js +165 -0
  59. package/dist/node_modules/.pnpm/rou3@0.7.8/node_modules/rou3/dist/index.js.map +1 -0
  60. package/dist/packages/fragno/dist/api/bind-services.js +20 -0
  61. package/dist/packages/fragno/dist/api/bind-services.js.map +1 -0
  62. package/dist/packages/fragno/dist/api/error.js +48 -0
  63. package/dist/packages/fragno/dist/api/error.js.map +1 -0
  64. package/dist/packages/fragno/dist/api/fragment-definition-builder.js +320 -0
  65. package/dist/packages/fragno/dist/api/fragment-definition-builder.js.map +1 -0
  66. package/dist/packages/fragno/dist/api/fragment-instantiator.js +487 -0
  67. package/dist/packages/fragno/dist/api/fragment-instantiator.js.map +1 -0
  68. package/dist/packages/fragno/dist/api/fragno-response.js +73 -0
  69. package/dist/packages/fragno/dist/api/fragno-response.js.map +1 -0
  70. package/dist/packages/fragno/dist/api/internal/response-stream.js +81 -0
  71. package/dist/packages/fragno/dist/api/internal/response-stream.js.map +1 -0
  72. package/dist/packages/fragno/dist/api/internal/route.js +10 -0
  73. package/dist/packages/fragno/dist/api/internal/route.js.map +1 -0
  74. package/dist/packages/fragno/dist/api/mutable-request-state.js +97 -0
  75. package/dist/packages/fragno/dist/api/mutable-request-state.js.map +1 -0
  76. package/dist/packages/fragno/dist/api/request-context-storage.js +43 -0
  77. package/dist/packages/fragno/dist/api/request-context-storage.js.map +1 -0
  78. package/dist/packages/fragno/dist/api/request-input-context.js +118 -0
  79. package/dist/packages/fragno/dist/api/request-input-context.js.map +1 -0
  80. package/dist/packages/fragno/dist/api/request-middleware.js +83 -0
  81. package/dist/packages/fragno/dist/api/request-middleware.js.map +1 -0
  82. package/dist/packages/fragno/dist/api/request-output-context.js +119 -0
  83. package/dist/packages/fragno/dist/api/request-output-context.js.map +1 -0
  84. package/dist/packages/fragno/dist/api/route.js +17 -0
  85. package/dist/packages/fragno/dist/api/route.js.map +1 -0
  86. package/dist/packages/fragno/dist/internal/symbols.js +10 -0
  87. package/dist/packages/fragno/dist/internal/symbols.js.map +1 -0
  88. package/dist/query/cursor.d.ts +10 -2
  89. package/dist/query/cursor.d.ts.map +1 -1
  90. package/dist/query/cursor.js +11 -4
  91. package/dist/query/cursor.js.map +1 -1
  92. package/dist/query/execute-unit-of-work.d.ts +123 -0
  93. package/dist/query/execute-unit-of-work.d.ts.map +1 -0
  94. package/dist/query/execute-unit-of-work.js +184 -0
  95. package/dist/query/execute-unit-of-work.js.map +1 -0
  96. package/dist/query/query.d.ts +2 -2
  97. package/dist/query/query.d.ts.map +1 -1
  98. package/dist/query/result-transform.js +4 -2
  99. package/dist/query/result-transform.js.map +1 -1
  100. package/dist/query/retry-policy.d.ts +88 -0
  101. package/dist/query/retry-policy.d.ts.map +1 -0
  102. package/dist/query/retry-policy.js +61 -0
  103. package/dist/query/retry-policy.js.map +1 -0
  104. package/dist/query/unit-of-work.d.ts +104 -50
  105. package/dist/query/unit-of-work.d.ts.map +1 -1
  106. package/dist/query/unit-of-work.js +384 -194
  107. package/dist/query/unit-of-work.js.map +1 -1
  108. package/dist/schema/serialize.js +12 -7
  109. package/dist/schema/serialize.js.map +1 -1
  110. package/dist/with-database.d.ts +28 -0
  111. package/dist/with-database.d.ts.map +1 -0
  112. package/dist/with-database.js +34 -0
  113. package/dist/with-database.js.map +1 -0
  114. package/package.json +9 -2
  115. package/src/adapters/adapters.ts +16 -0
  116. package/src/adapters/drizzle/drizzle-adapter-pglite.test.ts +80 -16
  117. package/src/adapters/drizzle/drizzle-adapter-sqlite.test.ts +158 -2
  118. package/src/adapters/drizzle/drizzle-adapter.test.ts +3 -51
  119. package/src/adapters/drizzle/drizzle-adapter.ts +20 -7
  120. package/src/adapters/drizzle/drizzle-query.ts +1 -2
  121. package/src/adapters/drizzle/drizzle-uow-compiler-mysql.test.ts +1442 -0
  122. package/src/adapters/drizzle/drizzle-uow-compiler-sqlite.test.ts +1414 -0
  123. package/src/adapters/drizzle/drizzle-uow-compiler.test.ts +21 -4
  124. package/src/adapters/drizzle/drizzle-uow-compiler.ts +44 -3
  125. package/src/adapters/drizzle/drizzle-uow-decoder.ts +32 -22
  126. package/src/adapters/drizzle/drizzle-uow-executor.ts +41 -8
  127. package/src/adapters/drizzle/generate.test.ts +102 -269
  128. package/src/adapters/drizzle/generate.ts +12 -30
  129. package/src/adapters/drizzle/test-utils.ts +36 -5
  130. package/src/adapters/kysely/kysely-adapter-pglite.test.ts +64 -20
  131. package/src/adapters/kysely/kysely-adapter-sqlite.test.ts +156 -0
  132. package/src/adapters/kysely/kysely-adapter.ts +9 -1
  133. package/src/adapters/kysely/kysely-query-compiler.ts +3 -8
  134. package/src/adapters/kysely/kysely-query.ts +34 -25
  135. package/src/adapters/kysely/kysely-shared.ts +34 -0
  136. package/src/adapters/kysely/kysely-uow-compiler.test.ts +61 -73
  137. package/src/adapters/kysely/kysely-uow-compiler.ts +44 -12
  138. package/src/adapters/kysely/kysely-uow-executor.ts +26 -7
  139. package/src/adapters/kysely/kysely-uow-joins.test.ts +31 -48
  140. package/src/adapters/kysely/migration/execute-base.ts +1 -1
  141. package/src/db-fragment-definition-builder.test.ts +887 -0
  142. package/src/db-fragment-definition-builder.ts +506 -0
  143. package/src/db-fragment-instantiator.test.ts +467 -0
  144. package/src/db-fragment-integration.test.ts +408 -0
  145. package/src/fragments/internal-fragment.test.ts +160 -0
  146. package/src/fragments/internal-fragment.ts +85 -0
  147. package/src/migration-engine/generation-engine.test.ts +58 -15
  148. package/src/migration-engine/generation-engine.ts +78 -25
  149. package/src/mod.ts +25 -52
  150. package/src/query/cursor.test.ts +119 -0
  151. package/src/query/cursor.ts +17 -4
  152. package/src/query/execute-unit-of-work.test.ts +1310 -0
  153. package/src/query/execute-unit-of-work.ts +463 -0
  154. package/src/query/query.ts +2 -2
  155. package/src/query/result-transform.test.ts +129 -0
  156. package/src/query/result-transform.ts +4 -1
  157. package/src/query/retry-policy.test.ts +217 -0
  158. package/src/query/retry-policy.ts +141 -0
  159. package/src/query/unit-of-work-coordinator.test.ts +833 -0
  160. package/src/query/unit-of-work-types.test.ts +2 -2
  161. package/src/query/unit-of-work.test.ts +873 -191
  162. package/src/query/unit-of-work.ts +602 -409
  163. package/src/schema/serialize.ts +22 -11
  164. package/src/with-database.ts +140 -0
  165. package/tsdown.config.ts +1 -0
  166. package/dist/bind-services.d.ts +0 -7
  167. package/dist/bind-services.d.ts.map +0 -1
  168. package/dist/bind-services.js +0 -14
  169. package/dist/bind-services.js.map +0 -1
  170. package/dist/fragment.d.ts +0 -173
  171. package/dist/fragment.d.ts.map +0 -1
  172. package/dist/fragment.js +0 -191
  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/bind-services.test.ts +0 -214
  177. package/src/bind-services.ts +0 -37
  178. package/src/db-fragment.test.ts +0 -800
  179. package/src/fragment.ts +0 -727
  180. package/src/query/unit-of-work-multi-schema.test.ts +0 -64
  181. package/src/shared/settings-schema.ts +0 -61
  182. package/src/uow-context-integration.test.ts +0 -102
  183. package/src/uow-context.test.ts +0 -182
@@ -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,10 +528,10 @@ 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 = [];
@@ -382,39 +541,84 @@ var UnitOfWork = class {
382
541
  #schemaNamespaceMap;
383
542
  #retrievalResults;
384
543
  #createdInternalIds = [];
385
- #retrievalPhaseResolve;
386
- #mutationPhaseResolve;
387
- #retrievalPhasePromise;
388
- #mutationPhasePromise;
389
- constructor(schema, compiler, executor, decoder, name, config, schemaNamespaceMap) {
390
- 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) {
391
550
  this.#compiler = compiler;
392
551
  this.#executor = executor;
393
552
  this.#decoder = decoder;
553
+ this.#schemaNamespaceMap = schemaNamespaceMap;
394
554
  this.#name = name;
395
555
  this.#config = config;
396
- this.#schemaNamespaceMap = schemaNamespaceMap;
397
- this.#retrievalPhasePromise = new Promise((resolve) => {
398
- this.#retrievalPhaseResolve = resolve;
399
- });
400
- this.#mutationPhasePromise = new Promise((resolve) => {
401
- this.#mutationPhaseResolve = resolve;
402
- });
403
- }
404
- get schema() {
405
- return this.#schema;
406
- }
407
- get $results() {
408
- throw new Error("type only");
556
+ this.#nonce = config?.nonce ?? crypto.randomUUID();
409
557
  }
410
558
  /**
411
- * Get a schema-specific view of this UOW for type-safe operations
412
- * Returns a wrapper that uses a different schema for operations.
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.
413
561
  * The namespace is automatically resolved from the schema-namespace map.
414
562
  */
415
563
  forSchema(schema) {
416
564
  const resolvedNamespace = this.#schemaNamespaceMap?.get(schema);
417
- return new UnitOfWorkSchemaView(schema, resolvedNamespace, this);
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();
418
622
  }
419
623
  get state() {
420
624
  return this.#state;
@@ -422,175 +626,93 @@ var UnitOfWork = class {
422
626
  get name() {
423
627
  return this.#name;
424
628
  }
629
+ get nonce() {
630
+ return this.#nonce;
631
+ }
425
632
  /**
426
633
  * Promise that resolves when the retrieval phase is executed
427
634
  * Service methods can await this to coordinate multi-phase logic
428
635
  */
429
636
  get retrievalPhase() {
430
- return this.#retrievalPhasePromise;
637
+ return this.#retrievalPhaseDeferred.promise;
431
638
  }
432
639
  /**
433
640
  * Promise that resolves when the mutation phase is executed
434
641
  * Service methods can await this to coordinate multi-phase logic
435
642
  */
436
643
  get mutationPhase() {
437
- return this.#mutationPhasePromise;
644
+ return this.#mutationPhaseDeferred.promise;
438
645
  }
439
646
  /**
440
647
  * Execute the retrieval phase and transition to mutation phase
441
648
  * Returns all results from find operations
442
649
  */
443
650
  async executeRetrieve() {
651
+ if (this.#coordinator.isRestricted) throw new Error("executeRetrieve() cannot be called on restricted child UOWs");
444
652
  if (this.#state !== "building-retrieval") throw new Error(`Cannot execute retrieval from state ${this.#state}. Must be in building-retrieval state.`);
445
- if (this.#retrievalOps.length === 0) {
446
- this.#state = "building-mutation";
447
- const emptyResults = [];
448
- this.#retrievalPhaseResolve?.(emptyResults);
449
- return emptyResults;
450
- }
451
- const retrievalBatch = [];
452
- for (const op of this.#retrievalOps) {
453
- const compiled = this.#compiler.compileRetrievalOperation(op);
454
- if (compiled !== null) {
455
- this.#config?.onQuery?.(compiled);
456
- 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;
457
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
+ }
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;
458
683
  }
459
- if (this.#config?.dryRun) {
460
- this.#state = "executed";
461
- return [];
462
- }
463
- const rawResults = await this.#executor.executeRetrievalPhase(retrievalBatch);
464
- this.#retrievalResults = this.#decoder(rawResults, this.#retrievalOps);
465
- this.#state = "building-mutation";
466
- this.#retrievalPhaseResolve?.(this.#retrievalResults);
467
- return this.#retrievalResults;
468
- }
469
- find(tableName, builderFn) {
470
- if (this.#state !== "building-retrieval") throw new Error(`find() can only be called during retrieval phase. Current state: ${this.#state}`);
471
- const table = this.#schema.tables[tableName];
472
- if (!table) throw new Error(`Table ${tableName} not found in schema`);
473
- const builder = new FindBuilder(tableName, table);
474
- if (builderFn) builderFn(builder);
475
- else builder.whereIndex("primary");
476
- const { indexName, options, type } = builder.build();
477
- this.#retrievalOps.push({
478
- type,
479
- schema: this.#schema,
480
- table,
481
- indexName,
482
- options
483
- });
484
- return this;
485
- }
486
- /**
487
- * Add a find operation with cursor metadata (retrieval phase only)
488
- */
489
- findWithCursor(tableName, builderFn) {
490
- if (this.#state !== "building-retrieval") throw new Error(`findWithCursor() can only be called during retrieval phase. Current state: ${this.#state}`);
491
- const table = this.#schema.tables[tableName];
492
- if (!table) throw new Error(`Table ${tableName} not found in schema`);
493
- const builder = new FindBuilder(tableName, table);
494
- builderFn(builder);
495
- const { indexName, options, type } = builder.build();
496
- this.#retrievalOps.push({
497
- type,
498
- schema: this.#schema,
499
- table,
500
- indexName,
501
- options,
502
- withCursor: true
503
- });
504
- return this;
505
- }
506
- /**
507
- * Add a create operation (mutation phase only)
508
- * Returns a FragnoId with the external ID that can be used immediately in subsequent operations
509
- */
510
- create(table, values) {
511
- if (this.#state === "executed") throw new Error(`create() can only be called during mutation phase.`);
512
- const tableSchema = this.#schema.tables[table];
513
- if (!tableSchema) throw new Error(`Table ${table} not found in schema`);
514
- const idColumn = tableSchema.getIdColumn();
515
- let externalId;
516
- let updatedValues = values;
517
- const providedIdValue = values[idColumn.ormName];
518
- if (providedIdValue !== void 0) if (typeof providedIdValue === "object" && providedIdValue !== null && "externalId" in providedIdValue) externalId = providedIdValue.externalId;
519
- else externalId = providedIdValue;
520
- else {
521
- const generated = idColumn.generateDefaultValue();
522
- if (generated === void 0) throw new Error(`No ID value provided and ID column ${idColumn.ormName} has no default generator`);
523
- externalId = generated;
524
- updatedValues = {
525
- ...values,
526
- [idColumn.ormName]: externalId
527
- };
528
- }
529
- this.#mutationOps.push({
530
- type: "create",
531
- schema: this.#schema,
532
- table,
533
- values: updatedValues,
534
- generatedExternalId: externalId
535
- });
536
- return FragnoId.fromExternal(externalId, 0);
537
- }
538
- /**
539
- * Add an update operation using a builder callback (mutation phase only)
540
- */
541
- update(table, id, builderFn) {
542
- if (this.#state === "executed") throw new Error(`update() can only be called during mutation phase.`);
543
- const builder = new UpdateBuilder(table, id);
544
- builderFn(builder);
545
- const { id: opId, checkVersion, set } = builder.build();
546
- this.#mutationOps.push({
547
- type: "update",
548
- schema: this.#schema,
549
- table,
550
- id: opId,
551
- checkVersion,
552
- set
553
- });
554
- }
555
- /**
556
- * Add a delete operation using a builder callback (mutation phase only)
557
- */
558
- delete(table, id, builderFn) {
559
- if (this.#state === "executed") throw new Error(`delete() can only be called during mutation phase.`);
560
- const builder = new DeleteBuilder(table, id);
561
- builderFn?.(builder);
562
- const { id: opId, checkVersion } = builder.build();
563
- this.#mutationOps.push({
564
- type: "delete",
565
- schema: this.#schema,
566
- table,
567
- id: opId,
568
- checkVersion
569
- });
570
684
  }
571
685
  /**
572
686
  * Execute the mutation phase
573
687
  * Returns success flag indicating if mutations completed without conflicts
574
688
  */
575
689
  async executeMutations() {
690
+ if (this.#coordinator.isRestricted) throw new Error("executeMutations() cannot be called on restricted child UOWs");
576
691
  if (this.#state === "executed") throw new Error(`Cannot execute mutations from state ${this.#state}.`);
577
- const mutationBatch = [];
578
- for (const op of this.#mutationOps) {
579
- const compiled = this.#compiler.compileMutationOperation(op);
580
- if (compiled !== null) {
581
- this.#config?.onQuery?.(compiled);
582
- mutationBatch.push(compiled);
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
+ }
583
701
  }
584
- }
585
- if (this.#config?.dryRun) {
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);
586
708
  this.#state = "executed";
587
- return { success: true };
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;
588
715
  }
589
- const result = await this.#executor.executeMutationPhase(mutationBatch);
590
- this.#state = "executed";
591
- if (result.success) this.#createdInternalIds = result.createdInternalIds;
592
- this.#mutationPhaseResolve?.();
593
- return { success: result.success };
594
716
  }
595
717
  /**
596
718
  * Get the retrieval operations (for inspection/debugging)
@@ -606,17 +728,19 @@ var UnitOfWork = class {
606
728
  }
607
729
  /**
608
730
  * @internal
609
- * Add a retrieval operation (used by SchemaView)
731
+ * Add a retrieval operation (used by TypedUnitOfWork)
610
732
  */
611
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.`);
612
735
  this.#retrievalOps.push(op);
613
736
  return this.#retrievalOps.length - 1;
614
737
  }
615
738
  /**
616
739
  * @internal
617
- * Add a mutation operation (used by SchemaView)
740
+ * Add a mutation operation (used by TypedUnitOfWork)
618
741
  */
619
742
  addMutationOperation(op) {
743
+ if (this.#state === "executed") throw new Error(`Cannot add mutation operation in executed state.`);
620
744
  this.#mutationOps.push(op);
621
745
  }
622
746
  /**
@@ -665,18 +789,20 @@ var UnitOfWork = class {
665
789
  }
666
790
  };
667
791
  /**
668
- * A lightweight wrapper around a parent UOW that provides type-safe operations for a different schema.
669
- * All operations are stored in the parent UOW, but this wrapper ensures the correct schema is used.
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.
670
795
  */
671
- var UnitOfWorkSchemaView = class {
796
+ var TypedUnitOfWork = class {
672
797
  #schema;
673
798
  #namespace;
674
- #parent;
799
+ #uow;
675
800
  #operationIndices = [];
676
- constructor(schema, namespace, parent) {
801
+ #cachedRetrievalPhase;
802
+ constructor(schema, namespace, uow) {
677
803
  this.#schema = schema;
678
804
  this.#namespace = namespace;
679
- this.#parent = parent;
805
+ this.#uow = uow;
680
806
  }
681
807
  get $results() {
682
808
  throw new Error("type only");
@@ -685,33 +811,55 @@ var UnitOfWorkSchemaView = class {
685
811
  return this.#schema;
686
812
  }
687
813
  get name() {
688
- return this.#parent.name;
814
+ return this.#uow.name;
815
+ }
816
+ get nonce() {
817
+ return this.#uow.nonce;
689
818
  }
690
819
  get state() {
691
- return this.#parent.state;
820
+ return this.#uow.state;
692
821
  }
693
822
  get retrievalPhase() {
694
- return this.#parent.retrievalPhase.then((allResults) => {
695
- return this.#operationIndices.map((index) => allResults[index]);
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
+ });
696
831
  });
832
+ return this.#cachedRetrievalPhase;
697
833
  }
698
834
  get mutationPhase() {
699
- return this.#parent.mutationPhase;
835
+ return this.#uow.mutationPhase;
700
836
  }
701
837
  getRetrievalOperations() {
702
- return this.#parent.getRetrievalOperations();
838
+ return this.#uow.getRetrievalOperations();
703
839
  }
704
840
  getMutationOperations() {
705
- return this.#parent.getMutationOperations();
841
+ return this.#uow.getMutationOperations();
706
842
  }
707
843
  getCreatedIds() {
708
- return this.#parent.getCreatedIds();
844
+ return this.#uow.getCreatedIds();
709
845
  }
710
846
  async executeRetrieve() {
711
- return this.#parent.executeRetrieve();
847
+ return this.#uow.executeRetrieve();
712
848
  }
713
849
  async executeMutations() {
714
- return this.#parent.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);
715
863
  }
716
864
  find(tableName, builderFn) {
717
865
  const table = this.#schema.tables[tableName];
@@ -720,7 +868,7 @@ var UnitOfWorkSchemaView = class {
720
868
  if (builderFn) builderFn(builder);
721
869
  else builder.whereIndex("primary");
722
870
  const { indexName, options, type } = builder.build();
723
- const operationIndex = this.#parent.addRetrievalOperation({
871
+ const operationIndex = this.#uow.addRetrievalOperation({
724
872
  type,
725
873
  schema: this.#schema,
726
874
  namespace: this.#namespace,
@@ -731,13 +879,33 @@ var UnitOfWorkSchemaView = class {
731
879
  this.#operationIndices.push(operationIndex);
732
880
  return this;
733
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);
900
+ return this;
901
+ }
734
902
  findWithCursor(tableName, builderFn) {
735
903
  const table = this.#schema.tables[tableName];
736
904
  if (!table) throw new Error(`Table ${tableName} not found in schema`);
737
905
  const builder = new FindBuilder(tableName, table);
738
906
  builderFn(builder);
739
907
  const { indexName, options, type } = builder.build();
740
- const operationIndex = this.#parent.addRetrievalOperation({
908
+ const operationIndex = this.#uow.addRetrievalOperation({
741
909
  type,
742
910
  schema: this.#schema,
743
911
  namespace: this.#namespace,
@@ -767,7 +935,7 @@ var UnitOfWorkSchemaView = class {
767
935
  [idColumn.ormName]: externalId
768
936
  };
769
937
  }
770
- this.#parent.addMutationOperation({
938
+ this.#uow.addMutationOperation({
771
939
  type: "create",
772
940
  schema: this.#schema,
773
941
  namespace: this.#namespace,
@@ -781,7 +949,7 @@ var UnitOfWorkSchemaView = class {
781
949
  const builder = new UpdateBuilder(tableName, id);
782
950
  builderFn(builder);
783
951
  const { id: opId, checkVersion, set } = builder.build();
784
- this.#parent.addMutationOperation({
952
+ this.#uow.addMutationOperation({
785
953
  type: "update",
786
954
  schema: this.#schema,
787
955
  namespace: this.#namespace,
@@ -795,7 +963,7 @@ var UnitOfWorkSchemaView = class {
795
963
  const builder = new DeleteBuilder(tableName, id);
796
964
  builderFn?.(builder);
797
965
  const { id: opId, checkVersion } = builder.build();
798
- this.#parent.addMutationOperation({
966
+ this.#uow.addMutationOperation({
799
967
  type: "delete",
800
968
  schema: this.#schema,
801
969
  namespace: this.#namespace,
@@ -804,11 +972,33 @@ var UnitOfWorkSchemaView = class {
804
972
  checkVersion
805
973
  });
806
974
  }
807
- forSchema(schema) {
808
- return this.#parent.forSchema(schema);
975
+ /**
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.
978
+ *
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
+ * ```
990
+ */
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
+ });
809
999
  }
810
1000
  };
811
1001
 
812
1002
  //#endregion
813
- export { DeleteBuilder, FindBuilder, JoinFindBuilder, UnitOfWork, UnitOfWorkSchemaView, UpdateBuilder, buildJoinIndexed, createUnitOfWork };
1003
+ export { DeleteBuilder, FindBuilder, JoinFindBuilder, TypedUnitOfWork, UnitOfWork, UpdateBuilder, buildJoinIndexed, createUnitOfWork };
814
1004
  //# sourceMappingURL=unit-of-work.js.map