@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
@@ -1,155 +1,166 @@
1
- import { ExponentialBackoffRetryPolicy, NoRetryPolicy } from "./retry-policy.js";
1
+ import { ExponentialBackoffRetryPolicy } from "./retry-policy.js";
2
2
 
3
3
  //#region src/query/unit-of-work/execute-unit-of-work.ts
4
4
  /**
5
- * Error thrown when a Unit of Work execution fails due to optimistic concurrency conflict.
6
- * This error triggers automatic retry behavior in executeRestrictedUnitOfWork.
5
+ * Symbol to identify TxResult objects
7
6
  */
8
- var ConcurrencyConflictError = class extends Error {
9
- constructor(message = "Optimistic concurrency conflict detected") {
10
- super(message);
11
- this.name = "ConcurrencyConflictError";
7
+ const TX_RESULT_BRAND = Symbol("TxResult");
8
+ /**
9
+ * Create a TxResult for service context.
10
+ * Schedules retrieve operations on the baseUow and returns a TxResult with callbacks stored.
11
+ * @internal Used by ServiceTxBuilder.build()
12
+ */
13
+ function createServiceTx(schema, callbacks, baseUow) {
14
+ const { promise: retrievePhase, resolve: resolveRetrievePhase, reject: rejectRetrievePhase } = Promise.withResolvers();
15
+ const restrictedUow = baseUow.restrict({ readyFor: "none" });
16
+ let serviceCalls;
17
+ try {
18
+ if (callbacks.serviceCalls) serviceCalls = callbacks.serviceCalls();
19
+ } catch (error) {
20
+ restrictedUow.signalReadyForRetrieval();
21
+ restrictedUow.signalReadyForMutation();
22
+ retrievePhase.catch(() => {});
23
+ rejectRetrievePhase(error);
24
+ throw error;
12
25
  }
13
- };
26
+ let typedUow;
27
+ try {
28
+ if (schema && callbacks.retrieve) {
29
+ const emptyUow = restrictedUow.forSchema(schema);
30
+ typedUow = callbacks.retrieve(emptyUow);
31
+ }
32
+ } catch (error) {
33
+ restrictedUow.signalReadyForRetrieval();
34
+ restrictedUow.signalReadyForMutation();
35
+ retrievePhase.catch(() => {});
36
+ rejectRetrievePhase(error);
37
+ throw error;
38
+ }
39
+ restrictedUow.signalReadyForRetrieval();
40
+ if (typedUow) typedUow.retrievalPhase.then((results) => resolveRetrievePhase(results), (error) => rejectRetrievePhase(error));
41
+ else if (!callbacks.retrieve) resolveRetrievePhase([]);
42
+ const internal = {
43
+ schema,
44
+ callbacks,
45
+ typedUow,
46
+ restrictedUow,
47
+ retrievePhase,
48
+ resolveRetrievePhase,
49
+ rejectRetrievePhase,
50
+ retrieveSuccessResult: void 0,
51
+ mutateResult: void 0,
52
+ finalResult: void 0,
53
+ serviceCalls
54
+ };
55
+ return {
56
+ [TX_RESULT_BRAND]: true,
57
+ _internal: internal
58
+ };
59
+ }
14
60
  /**
15
- * Await promises in an object 1 level deep
61
+ * Recursively collect all TxResults from a service call tree.
62
+ * Returns them in a flat array in dependency order (serviceCalls before their dependents).
63
+ * Skips undefined values (which can occur with optional service patterns like
64
+ * optionalService?.method()).
16
65
  */
17
- async function awaitPromisesInObject(obj) {
18
- if (obj === null || obj === void 0) return obj;
19
- if (typeof obj !== "object") return obj;
20
- if (obj instanceof Promise) return await obj;
21
- if (Array.isArray(obj)) return await Promise.all(obj.map((item) => item instanceof Promise ? item : Promise.resolve(item)));
22
- const result = {};
23
- const entries = Object.entries(obj);
24
- const awaitedEntries = await Promise.all(entries.map(async ([key, value]) => {
25
- return [key, value instanceof Promise ? await value : value];
26
- }));
27
- for (const [key, value] of awaitedEntries) result[key] = value;
28
- return result;
66
+ function collectAllTxResults(txResults) {
67
+ const collected = [];
68
+ const seen = /* @__PURE__ */ new Set();
69
+ function collect(txResult) {
70
+ if (txResult === void 0) return;
71
+ if (seen.has(txResult)) return;
72
+ seen.add(txResult);
73
+ const serviceCalls = txResult._internal.serviceCalls;
74
+ if (serviceCalls) for (const serviceCall of serviceCalls) collect(serviceCall);
75
+ collected.push(txResult);
76
+ }
77
+ for (const txResult of txResults) collect(txResult);
78
+ return collected;
29
79
  }
30
80
  /**
31
- * Execute a Unit of Work with automatic retry support for optimistic concurrency conflicts.
32
- *
33
- * This function orchestrates the two-phase execution (retrieval + mutation) with retry logic.
34
- * It creates fresh UOW instances for each attempt.
35
- *
36
- * @param callbacks - Object containing retrieve, mutate, and onSuccess callbacks
37
- * @param options - Configuration including UOW factory, retry policy, and abort signal
38
- * @returns Promise resolving to the execution result
39
- *
40
- * @example
41
- * ```ts
42
- * const result = await executeUnitOfWork(
43
- * {
44
- * retrieve: (uow) => uow.find("users", (b) => b.whereIndex("primary")),
45
- * mutate: (uow, [users]) => {
46
- * const user = users[0];
47
- * uow.update("users", user.id, (b) => b.set({ balance: newBalance }));
48
- * },
49
- * onSuccess: async ({ results, mutationResult }) => {
50
- * console.log("Update successful!");
51
- * }
52
- * },
53
- * {
54
- * createUnitOfWork: () => queryEngine.createUnitOfWork(),
55
- * retryPolicy: new ExponentialBackoffRetryPolicy({ maxRetries: 3 })
56
- * }
57
- * );
58
- * ```
81
+ * Execute a single TxResult's callbacks after retrieve phase completes.
82
+ * This processes retrieveSuccess, mutate, and success callbacks in order.
59
83
  */
60
- async function executeUnitOfWork(callbacks, options) {
61
- if (!callbacks.retrieve && !callbacks.mutate) throw new Error("At least one of 'retrieve' or 'mutate' callbacks must be provided");
62
- const retryPolicy = options.retryPolicy ?? new NoRetryPolicy();
63
- const signal = options.signal;
64
- let attempt = 0;
65
- while (true) {
66
- if (signal?.aborted) return {
67
- success: false,
68
- reason: "aborted"
84
+ async function processTxResultAfterRetrieve(txResult, baseUow) {
85
+ const internal = txResult._internal;
86
+ const callbacks = internal.callbacks;
87
+ const retrieveResults = await internal.retrievePhase;
88
+ const serviceResults = [];
89
+ if (internal.serviceCalls) for (const serviceCall of internal.serviceCalls) {
90
+ if (serviceCall === void 0) {
91
+ serviceResults.push(void 0);
92
+ continue;
93
+ }
94
+ const serviceCallInternal = serviceCall._internal;
95
+ if (serviceCallInternal.retrieveSuccessResult !== void 0 && !(Array.isArray(serviceCallInternal.retrieveSuccessResult) && serviceCallInternal.retrieveSuccessResult.length === 0 && serviceCallInternal.callbacks.mutate)) serviceResults.push(serviceCallInternal.retrieveSuccessResult);
96
+ else if (serviceCallInternal.mutateResult !== void 0) serviceResults.push(serviceCallInternal.mutateResult);
97
+ else serviceResults.push(serviceCallInternal.retrieveSuccessResult);
98
+ }
99
+ if (callbacks.retrieveSuccess) internal.retrieveSuccessResult = callbacks.retrieveSuccess(retrieveResults, serviceResults);
100
+ else internal.retrieveSuccessResult = retrieveResults;
101
+ if (callbacks.mutate) {
102
+ const mutateCtx = {
103
+ uow: internal.schema ? baseUow.forSchema(internal.schema) : void 0,
104
+ retrieveResult: internal.retrieveSuccessResult,
105
+ serviceIntermediateResult: serviceResults
69
106
  };
70
- try {
71
- const uow = options.createUnitOfWork();
72
- let retrievalUow;
73
- if (callbacks.retrieve) retrievalUow = callbacks.retrieve(uow);
74
- else retrievalUow = uow;
75
- const results = await retrievalUow.executeRetrieve();
76
- let mutationResult;
77
- if (callbacks.mutate) mutationResult = await callbacks.mutate(retrievalUow, results);
78
- else mutationResult = void 0;
79
- const { success } = await retrievalUow.executeMutations();
80
- if (success) {
81
- const createdIds = retrievalUow.getCreatedIds();
82
- const nonce = retrievalUow.nonce;
83
- const awaitedMutationResult = await awaitPromisesInObject(mutationResult);
84
- if (callbacks.onSuccess) await callbacks.onSuccess({
85
- results,
86
- mutationResult: awaitedMutationResult,
87
- createdIds,
88
- nonce
89
- });
90
- return {
91
- success: true,
92
- results,
93
- mutationResult: awaitedMutationResult,
94
- createdIds,
95
- nonce
96
- };
97
- }
98
- if (!retryPolicy.shouldRetry(attempt, void 0, signal)) return {
99
- success: false,
100
- reason: "conflict"
101
- };
102
- const delayMs = retryPolicy.getDelayMs(attempt);
103
- if (delayMs > 0) await new Promise((resolve) => setTimeout(resolve, delayMs));
104
- attempt++;
105
- } catch (error) {
106
- return {
107
- success: false,
108
- reason: "error",
109
- error
110
- };
107
+ internal.mutateResult = callbacks.mutate(mutateCtx);
108
+ }
109
+ if (internal.typedUow) internal.typedUow.signalReadyForMutation();
110
+ else internal.restrictedUow.signalReadyForMutation();
111
+ }
112
+ /**
113
+ * Execute a single TxResult's success callback after mutations complete.
114
+ */
115
+ async function processTxResultAfterMutate(txResult) {
116
+ const internal = txResult._internal;
117
+ const callbacks = internal.callbacks;
118
+ const serviceIntermediateResults = [];
119
+ const serviceFinalResults = [];
120
+ if (internal.serviceCalls) for (const serviceCall of internal.serviceCalls) {
121
+ if (serviceCall === void 0) {
122
+ serviceIntermediateResults.push(void 0);
123
+ serviceFinalResults.push(void 0);
124
+ continue;
111
125
  }
126
+ const serviceCallInternal = serviceCall._internal;
127
+ if (serviceCallInternal.retrieveSuccessResult !== void 0 && !(Array.isArray(serviceCallInternal.retrieveSuccessResult) && serviceCallInternal.retrieveSuccessResult.length === 0 && serviceCallInternal.callbacks.mutate)) serviceIntermediateResults.push(serviceCallInternal.retrieveSuccessResult);
128
+ else if (serviceCallInternal.mutateResult !== void 0) serviceIntermediateResults.push(serviceCallInternal.mutateResult);
129
+ else serviceIntermediateResults.push(serviceCallInternal.retrieveSuccessResult);
130
+ serviceFinalResults.push(serviceCallInternal.finalResult);
112
131
  }
132
+ if (callbacks.success) {
133
+ const successCtx = {
134
+ retrieveResult: internal.retrieveSuccessResult,
135
+ mutateResult: internal.mutateResult,
136
+ serviceResult: serviceFinalResults,
137
+ serviceIntermediateResult: serviceIntermediateResults
138
+ };
139
+ internal.finalResult = callbacks.success(successCtx);
140
+ } else if (callbacks.mutate) internal.finalResult = await awaitPromisesInObject(internal.mutateResult);
141
+ else if (callbacks.retrieveSuccess || callbacks.retrieve) internal.finalResult = internal.retrieveSuccessResult;
142
+ else internal.finalResult = serviceFinalResults;
143
+ return internal.finalResult;
113
144
  }
114
145
  /**
115
- * Execute a Unit of Work with explicit phase control and automatic retry support.
146
+ * Execute a transaction with the unified TxResult pattern.
116
147
  *
117
- * This function provides an alternative API where users write a single callback that receives
118
- * a context object with forSchema, executeRetrieve, and executeMutate methods. The user can
119
- * create schema-specific UOWs via forSchema, then call executeRetrieve() and executeMutate()
120
- * to execute the retrieval and mutation phases. The entire callback is re-executed on optimistic
121
- * concurrency conflicts, ensuring retries work properly.
148
+ * This is the handler-level function that actually executes TxResults with retry support.
122
149
  *
123
- * @param callback - Async function that receives a context with forSchema, executeRetrieve, executeMutate, nonce, and currentAttempt
150
+ * @param callbacks - Transaction callbacks (serviceCalls, retrieve, retrieveSuccess, mutate, success)
124
151
  * @param options - Configuration including UOW factory, retry policy, and abort signal
125
- * @returns Promise resolving to the callback's return value
126
- * @throws Error if retries are exhausted or callback throws an error
152
+ * @returns Promise resolving to the result determined by return type priority
127
153
  *
128
154
  * @example
129
155
  * ```ts
130
- * const { userId, profileId } = await executeRestrictedUnitOfWork(
131
- * async ({ forSchema, executeRetrieve, executeMutate, nonce, currentAttempt }) => {
132
- * const uow = forSchema(schema);
133
- * const userId = uow.create("users", { name: "John" });
134
- *
135
- * // Execute retrieval phase
136
- * await executeRetrieve();
137
- *
138
- * const profileId = uow.create("profiles", { userId });
139
- *
140
- * // Execute mutation phase
141
- * await executeMutate();
142
- *
143
- * return { userId, profileId };
144
- * },
145
- * {
146
- * createUnitOfWork: () => db.createUnitOfWork(),
147
- * retryPolicy: new ExponentialBackoffRetryPolicy({ maxRetries: 5 })
148
- * }
149
- * );
150
- * ```
156
+ * // Simple retrieve + transform
157
+ * const user = await executeTx({
158
+ * retrieve: (ctx) => ctx.forSchema(usersSchema).find("users", ...),
159
+ * retrieveSuccess: ([users]) => users[0] ?? null,
160
+ * }, { createUnitOfWork });
161
+ * @internal Used by HandlerTxBuilder.execute()
151
162
  */
152
- async function executeRestrictedUnitOfWork(callback, options) {
163
+ async function executeTx(callbacks, options) {
153
164
  const retryPolicy = options.retryPolicy ?? new ExponentialBackoffRetryPolicy({
154
165
  maxRetries: 5,
155
166
  initialDelayMs: 10,
@@ -158,32 +169,77 @@ async function executeRestrictedUnitOfWork(callback, options) {
158
169
  const signal = options.signal;
159
170
  let attempt = 0;
160
171
  while (true) {
161
- if (signal?.aborted) throw new Error("Unit of Work execution aborted");
172
+ if (signal?.aborted) throw new Error("Transaction execution aborted");
162
173
  try {
163
174
  const baseUow = options.createUnitOfWork();
164
- return await awaitPromisesInObject(await callback({
175
+ const context = {
165
176
  forSchema: (schema, hooks) => {
166
177
  return baseUow.forSchema(schema, hooks);
167
178
  },
168
- executeRetrieve: async () => {
169
- await baseUow.executeRetrieve();
170
- },
171
- executeMutate: async () => {
172
- if (baseUow.state === "executed") return;
173
- if (baseUow.state === "building-retrieval") await baseUow.executeRetrieve();
174
- if (options.onBeforeMutate) options.onBeforeMutate(baseUow);
175
- if (!(await baseUow.executeMutations()).success) throw new ConcurrencyConflictError();
176
- if (options.onSuccess) await options.onSuccess(baseUow);
177
- },
178
- nonce: baseUow.nonce,
179
+ idempotencyKey: baseUow.idempotencyKey,
179
180
  currentAttempt: attempt
180
- }));
181
+ };
182
+ let serviceCalls;
183
+ if (callbacks.serviceCalls) serviceCalls = callbacks.serviceCalls();
184
+ const typedUowFromRetrieve = callbacks.retrieve?.(context);
185
+ const allServiceCallTxResults = serviceCalls ? collectAllTxResults([...serviceCalls]) : [];
186
+ await baseUow.executeRetrieve();
187
+ const retrieveResult = typedUowFromRetrieve ? await typedUowFromRetrieve.retrievalPhase : [];
188
+ for (const txResult of allServiceCallTxResults) await processTxResultAfterRetrieve(txResult, baseUow);
189
+ const serviceResults = [];
190
+ if (serviceCalls) for (const serviceCall of serviceCalls) {
191
+ if (serviceCall === void 0) {
192
+ serviceResults.push(void 0);
193
+ continue;
194
+ }
195
+ const serviceCallInternal = serviceCall._internal;
196
+ if (serviceCallInternal.retrieveSuccessResult !== void 0 && !(Array.isArray(serviceCallInternal.retrieveSuccessResult) && serviceCallInternal.retrieveSuccessResult.length === 0 && serviceCallInternal.callbacks.mutate)) serviceResults.push(serviceCallInternal.retrieveSuccessResult);
197
+ else if (serviceCallInternal.mutateResult !== void 0) serviceResults.push(serviceCallInternal.mutateResult);
198
+ else serviceResults.push(serviceCallInternal.retrieveSuccessResult);
199
+ }
200
+ let retrieveSuccessResult;
201
+ if (callbacks.retrieveSuccess) retrieveSuccessResult = callbacks.retrieveSuccess(retrieveResult, serviceResults);
202
+ else retrieveSuccessResult = retrieveResult;
203
+ let mutateResult;
204
+ if (callbacks.mutate) {
205
+ const mutateCtx = {
206
+ ...context,
207
+ retrieveResult: retrieveSuccessResult,
208
+ serviceIntermediateResult: serviceResults
209
+ };
210
+ mutateResult = callbacks.mutate(mutateCtx);
211
+ }
212
+ if (options.onBeforeMutate) options.onBeforeMutate(baseUow);
213
+ if (!(await baseUow.executeMutations()).success) throw new ConcurrencyConflictError();
214
+ for (const txResult of allServiceCallTxResults) await processTxResultAfterMutate(txResult);
215
+ const serviceFinalResults = [];
216
+ if (serviceCalls) for (const serviceCall of serviceCalls) {
217
+ if (serviceCall === void 0) {
218
+ serviceFinalResults.push(void 0);
219
+ continue;
220
+ }
221
+ serviceFinalResults.push(serviceCall._internal.finalResult);
222
+ }
223
+ let finalResult;
224
+ if (callbacks.success) {
225
+ const successCtx = {
226
+ retrieveResult: retrieveSuccessResult,
227
+ mutateResult,
228
+ serviceResult: serviceFinalResults,
229
+ serviceIntermediateResult: serviceResults
230
+ };
231
+ finalResult = callbacks.success(successCtx);
232
+ } else if (callbacks.mutate) finalResult = await awaitPromisesInObject(mutateResult);
233
+ else if (callbacks.retrieveSuccess || callbacks.retrieve) finalResult = retrieveSuccessResult;
234
+ else finalResult = serviceFinalResults;
235
+ if (options.onAfterMutate) await options.onAfterMutate(baseUow);
236
+ return await awaitPromisesInObject(finalResult);
181
237
  } catch (error) {
182
- if (signal?.aborted) throw new Error("Unit of Work execution aborted");
238
+ if (signal?.aborted) throw new Error("Transaction execution aborted");
183
239
  if (!(error instanceof ConcurrencyConflictError)) throw error;
184
240
  if (!retryPolicy.shouldRetry(attempt, error, signal)) {
185
- if (signal?.aborted) throw new Error("Unit of Work execution aborted");
186
- throw new Error("Unit of Work execution failed: optimistic concurrency conflict", { cause: error });
241
+ if (signal?.aborted) throw new Error("Transaction execution aborted");
242
+ throw new ConcurrencyConflictError();
187
243
  }
188
244
  const delayMs = retryPolicy.getDelayMs(attempt);
189
245
  if (delayMs > 0) await new Promise((resolve) => setTimeout(resolve, delayMs));
@@ -191,7 +247,251 @@ async function executeRestrictedUnitOfWork(callback, options) {
191
247
  }
192
248
  }
193
249
  }
250
+ /**
251
+ * Error thrown when a Unit of Work execution fails due to optimistic concurrency conflict.
252
+ * This error triggers automatic retry behavior in executeTx.
253
+ */
254
+ var ConcurrencyConflictError = class extends Error {
255
+ constructor(message = "Optimistic concurrency conflict detected") {
256
+ super(message);
257
+ this.name = "ConcurrencyConflictError";
258
+ }
259
+ };
260
+ /**
261
+ * Await promises in an object 1 level deep
262
+ */
263
+ async function awaitPromisesInObject(obj) {
264
+ if (obj === null || obj === void 0) return obj;
265
+ if (typeof obj !== "object") return obj;
266
+ if (obj instanceof Promise) return await obj;
267
+ if (Array.isArray(obj)) return await Promise.all(obj.map((item) => item instanceof Promise ? item : Promise.resolve(item)));
268
+ if (obj.constructor !== Object) return obj;
269
+ const result = {};
270
+ const entries = Object.entries(obj);
271
+ const awaitedEntries = await Promise.all(entries.map(async ([key, value]) => {
272
+ return [key, value instanceof Promise ? await value : value];
273
+ }));
274
+ for (const [key, value] of awaitedEntries) result[key] = value;
275
+ return result;
276
+ }
277
+ /**
278
+ * Builder for service-level transactions.
279
+ * Uses a fluent API to build up transaction callbacks with proper type inference.
280
+ *
281
+ * @example
282
+ * ```ts
283
+ * return serviceTx(schema)
284
+ * .withServiceCalls(() => [otherService.getData()])
285
+ * .retrieve((uow) => uow.find("users", ...))
286
+ * .transformRetrieve(([users], serviceResult) => users[0])
287
+ * .mutate(({ uow, retrieveResult, serviceIntermediateResult }) =>
288
+ * uow.create("records", { ... })
289
+ * )
290
+ * .transform(({ mutateResult, serviceResult, serviceIntermediateResult }) => ({ id: mutateResult }))
291
+ * .build();
292
+ * ```
293
+ */
294
+ var ServiceTxBuilder = class ServiceTxBuilder {
295
+ #state;
296
+ constructor(state) {
297
+ this.#state = state;
298
+ }
299
+ /**
300
+ * Add dependencies to execute before this transaction.
301
+ */
302
+ withServiceCalls(fn) {
303
+ return new ServiceTxBuilder({
304
+ ...this.#state,
305
+ withServiceCallsFn: fn
306
+ });
307
+ }
308
+ /**
309
+ * Add retrieval operations to the transaction.
310
+ */
311
+ retrieve(fn) {
312
+ return new ServiceTxBuilder({
313
+ ...this.#state,
314
+ retrieveFn: fn,
315
+ transformRetrieveFn: void 0
316
+ });
317
+ }
318
+ /**
319
+ * Transform retrieve results before passing to mutate.
320
+ */
321
+ transformRetrieve(fn) {
322
+ return new ServiceTxBuilder({
323
+ ...this.#state,
324
+ transformRetrieveFn: fn
325
+ });
326
+ }
327
+ /**
328
+ * Add mutation operations based on retrieve results.
329
+ */
330
+ mutate(fn) {
331
+ return new ServiceTxBuilder({
332
+ ...this.#state,
333
+ mutateFn: fn
334
+ });
335
+ }
336
+ /**
337
+ * Add final transformation after mutations complete.
338
+ */
339
+ transform(fn) {
340
+ return new ServiceTxBuilder({
341
+ ...this.#state,
342
+ transformFn: fn
343
+ });
344
+ }
345
+ /**
346
+ * Build and return the TxResult.
347
+ */
348
+ build() {
349
+ const state = this.#state;
350
+ const callbacks = {
351
+ serviceCalls: state.withServiceCallsFn,
352
+ retrieve: state.retrieveFn,
353
+ retrieveSuccess: state.transformRetrieveFn,
354
+ mutate: state.mutateFn ? (ctx) => {
355
+ return state.mutateFn({
356
+ uow: ctx.uow,
357
+ retrieveResult: ctx.retrieveResult,
358
+ serviceIntermediateResult: ctx.serviceIntermediateResult
359
+ });
360
+ } : void 0,
361
+ success: state.transformFn ? (ctx) => {
362
+ return state.transformFn({
363
+ retrieveResult: ctx.retrieveResult,
364
+ mutateResult: ctx.mutateResult,
365
+ serviceResult: ctx.serviceResult,
366
+ serviceIntermediateResult: ctx.serviceIntermediateResult
367
+ });
368
+ } : void 0
369
+ };
370
+ return createServiceTx(state.schema, callbacks, state.baseUow);
371
+ }
372
+ };
373
+ /**
374
+ * Create a new ServiceTxBuilder for the given schema.
375
+ */
376
+ function createServiceTxBuilder(schema, baseUow, hooks) {
377
+ return new ServiceTxBuilder({
378
+ schema,
379
+ baseUow,
380
+ hooks
381
+ });
382
+ }
383
+ /**
384
+ * Builder for handler-level transactions.
385
+ * Uses a fluent API to build up transaction callbacks with proper type inference.
386
+ *
387
+ * @example
388
+ * ```ts
389
+ * const result = await handlerTx()
390
+ * .withServiceCalls(() => [userService.getUser(id)])
391
+ * .mutate(({ forSchema, idempotencyKey, currentAttempt, serviceIntermediateResult }) => {
392
+ * return forSchema(ordersSchema).create("orders", { ... });
393
+ * })
394
+ * .transform(({ mutateResult, serviceResult }) => ({ ... }))
395
+ * .execute();
396
+ * ```
397
+ */
398
+ var HandlerTxBuilder = class HandlerTxBuilder {
399
+ #state;
400
+ constructor(state) {
401
+ this.#state = state;
402
+ }
403
+ /**
404
+ * Add dependencies to execute before this transaction.
405
+ */
406
+ withServiceCalls(fn) {
407
+ return new HandlerTxBuilder({
408
+ ...this.#state,
409
+ withServiceCallsFn: fn
410
+ });
411
+ }
412
+ /**
413
+ * Add retrieval operations to the transaction.
414
+ * Return a TypedUnitOfWork from forSchema().find() to get typed results.
415
+ */
416
+ retrieve(fn) {
417
+ return new HandlerTxBuilder({
418
+ ...this.#state,
419
+ retrieveFn: fn,
420
+ transformRetrieveFn: void 0
421
+ });
422
+ }
423
+ /**
424
+ * Transform retrieve results before passing to mutate.
425
+ */
426
+ transformRetrieve(fn) {
427
+ return new HandlerTxBuilder({
428
+ ...this.#state,
429
+ transformRetrieveFn: fn
430
+ });
431
+ }
432
+ /**
433
+ * Add mutation operations based on retrieve results.
434
+ */
435
+ mutate(fn) {
436
+ return new HandlerTxBuilder({
437
+ ...this.#state,
438
+ mutateFn: fn
439
+ });
440
+ }
441
+ /**
442
+ * Add final transformation after mutations complete.
443
+ */
444
+ transform(fn) {
445
+ return new HandlerTxBuilder({
446
+ ...this.#state,
447
+ transformFn: fn
448
+ });
449
+ }
450
+ /**
451
+ * Execute the transaction and return the result.
452
+ */
453
+ execute() {
454
+ const state = this.#state;
455
+ return executeTx({
456
+ serviceCalls: state.withServiceCallsFn,
457
+ retrieve: state.retrieveFn ? (context) => {
458
+ return state.retrieveFn({
459
+ forSchema: context.forSchema,
460
+ idempotencyKey: context.idempotencyKey,
461
+ currentAttempt: context.currentAttempt
462
+ });
463
+ } : void 0,
464
+ retrieveSuccess: state.transformRetrieveFn,
465
+ mutate: state.mutateFn ? (ctx) => {
466
+ return state.mutateFn({
467
+ forSchema: ctx.forSchema,
468
+ idempotencyKey: ctx.idempotencyKey,
469
+ currentAttempt: ctx.currentAttempt,
470
+ retrieveResult: ctx.retrieveResult,
471
+ serviceIntermediateResult: ctx.serviceIntermediateResult
472
+ });
473
+ } : void 0,
474
+ success: state.transformFn ? (ctx) => {
475
+ return state.transformFn({
476
+ retrieveResult: ctx.retrieveResult,
477
+ mutateResult: ctx.mutateResult,
478
+ serviceResult: ctx.serviceResult,
479
+ serviceIntermediateResult: ctx.serviceIntermediateResult
480
+ });
481
+ } : void 0
482
+ }, state.options);
483
+ }
484
+ };
485
+ /**
486
+ * Create a new HandlerTxBuilder with the given options.
487
+ */
488
+ function createHandlerTxBuilder(options, hooks) {
489
+ return new HandlerTxBuilder({
490
+ options,
491
+ hooks
492
+ });
493
+ }
194
494
 
195
495
  //#endregion
196
- export { executeRestrictedUnitOfWork, executeUnitOfWork };
496
+ export { ConcurrencyConflictError, HandlerTxBuilder, ServiceTxBuilder, createHandlerTxBuilder, createServiceTxBuilder };
197
497
  //# sourceMappingURL=execute-unit-of-work.js.map