@fragno-dev/db 0.2.1 → 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.
- package/.turbo/turbo-build.log +34 -30
- package/CHANGELOG.md +32 -0
- package/dist/adapters/generic-sql/query/where-builder.js +1 -1
- package/dist/db-fragment-definition-builder.d.ts +27 -89
- package/dist/db-fragment-definition-builder.d.ts.map +1 -1
- package/dist/db-fragment-definition-builder.js +16 -56
- package/dist/db-fragment-definition-builder.js.map +1 -1
- package/dist/fragments/internal-fragment.d.ts +94 -8
- package/dist/fragments/internal-fragment.d.ts.map +1 -1
- package/dist/fragments/internal-fragment.js +56 -55
- package/dist/fragments/internal-fragment.js.map +1 -1
- package/dist/hooks/hooks.d.ts +5 -3
- package/dist/hooks/hooks.d.ts.map +1 -1
- package/dist/hooks/hooks.js +38 -37
- package/dist/hooks/hooks.js.map +1 -1
- package/dist/mod.d.ts +3 -3
- package/dist/mod.d.ts.map +1 -1
- package/dist/mod.js +4 -4
- package/dist/mod.js.map +1 -1
- package/dist/query/unit-of-work/execute-unit-of-work.d.ts +351 -100
- package/dist/query/unit-of-work/execute-unit-of-work.d.ts.map +1 -1
- package/dist/query/unit-of-work/execute-unit-of-work.js +431 -263
- package/dist/query/unit-of-work/execute-unit-of-work.js.map +1 -1
- package/dist/query/unit-of-work/unit-of-work.d.ts +17 -8
- package/dist/query/unit-of-work/unit-of-work.d.ts.map +1 -1
- package/dist/query/unit-of-work/unit-of-work.js +24 -8
- package/dist/query/unit-of-work/unit-of-work.js.map +1 -1
- package/dist/query/value-decoding.js +1 -1
- package/dist/schema/create.d.ts +3 -1
- package/dist/schema/create.d.ts.map +1 -1
- package/dist/schema/create.js +2 -1
- package/dist/schema/create.js.map +1 -1
- package/dist/schema/generate-id.d.ts +20 -0
- package/dist/schema/generate-id.d.ts.map +1 -0
- package/dist/schema/generate-id.js +28 -0
- package/dist/schema/generate-id.js.map +1 -0
- package/package.json +1 -1
- package/src/adapters/drizzle/drizzle-adapter-sqlite3.test.ts +41 -25
- package/src/adapters/generic-sql/test/generic-drizzle-adapter-sqlite3.test.ts +39 -25
- package/src/db-fragment-definition-builder.test.ts +58 -42
- package/src/db-fragment-definition-builder.ts +58 -248
- package/src/db-fragment-instantiator.test.ts +64 -88
- package/src/db-fragment-integration.test.ts +292 -142
- package/src/fragments/internal-fragment.test.ts +272 -266
- package/src/fragments/internal-fragment.ts +155 -121
- package/src/hooks/hooks.test.ts +248 -256
- package/src/hooks/hooks.ts +74 -63
- package/src/mod.ts +14 -4
- package/src/query/unit-of-work/execute-unit-of-work.test.ts +1494 -1464
- package/src/query/unit-of-work/execute-unit-of-work.ts +1685 -590
- package/src/query/unit-of-work/tx-builder.test.ts +1041 -0
- package/src/query/unit-of-work/unit-of-work-coordinator.test.ts +20 -20
- package/src/query/unit-of-work/unit-of-work.test.ts +64 -0
- package/src/query/unit-of-work/unit-of-work.ts +26 -13
- package/src/schema/create.ts +2 -0
- package/src/schema/generate-id.test.ts +57 -0
- package/src/schema/generate-id.ts +38 -0
- package/src/shared/config.ts +0 -10
- package/src/shared/connection-pool.ts +0 -24
- package/src/shared/prisma.ts +0 -45
|
@@ -1,331 +1,481 @@
|
|
|
1
1
|
import type { AnySchema } from "../../schema/create";
|
|
2
2
|
import type { TypedUnitOfWork, IUnitOfWork } from "./unit-of-work";
|
|
3
3
|
import type { HooksMap } from "../../hooks/hooks";
|
|
4
|
-
import {
|
|
5
|
-
import type { FragnoId } from "../../schema/create";
|
|
4
|
+
import { ExponentialBackoffRetryPolicy, type RetryPolicy } from "./retry-policy";
|
|
6
5
|
|
|
7
6
|
/**
|
|
8
|
-
*
|
|
9
|
-
* This error triggers automatic retry behavior in executeRestrictedUnitOfWork.
|
|
7
|
+
* Symbol to identify TxResult objects
|
|
10
8
|
*/
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
9
|
+
const TX_RESULT_BRAND = Symbol("TxResult");
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Check if a value is a TxResult
|
|
13
|
+
*/
|
|
14
|
+
export function isTxResult(value: unknown): value is TxResult<unknown> {
|
|
15
|
+
return (
|
|
16
|
+
value !== null &&
|
|
17
|
+
typeof value === "object" &&
|
|
18
|
+
TX_RESULT_BRAND in value &&
|
|
19
|
+
(value as Record<symbol, boolean>)[TX_RESULT_BRAND] === true
|
|
20
|
+
);
|
|
16
21
|
}
|
|
17
22
|
|
|
18
23
|
/**
|
|
19
|
-
*
|
|
20
|
-
*
|
|
24
|
+
* Extract the retrieve success result type from a TxResult.
|
|
25
|
+
* If the TxResult has retrieveSuccess, returns its return type.
|
|
26
|
+
* Otherwise returns the raw retrieve results type.
|
|
27
|
+
* Handles undefined (for optional service patterns like optionalService?.method()).
|
|
21
28
|
*/
|
|
22
|
-
export type
|
|
23
|
-
|
|
24
|
-
T extends
|
|
25
|
-
?
|
|
26
|
-
:
|
|
27
|
-
T extends readonly [unknown, ...unknown[]]
|
|
28
|
-
? { [K in keyof T]: AwaitedPromisesInObject<T[K]> }
|
|
29
|
-
: T extends [unknown, ...unknown[]]
|
|
30
|
-
? { [K in keyof T]: AwaitedPromisesInObject<T[K]> }
|
|
31
|
-
: // Check for regular arrays (unknown length)
|
|
32
|
-
T extends (infer U)[]
|
|
33
|
-
? Awaited<U>[]
|
|
34
|
-
: T extends readonly (infer U)[]
|
|
35
|
-
? readonly Awaited<U>[]
|
|
36
|
-
: // Check for objects
|
|
37
|
-
T extends Record<string, unknown>
|
|
38
|
-
? {
|
|
39
|
-
[K in keyof T]: T[K] extends Promise<infer U> ? Awaited<U> : T[K];
|
|
40
|
-
}
|
|
41
|
-
: // Otherwise return as-is
|
|
42
|
-
T;
|
|
29
|
+
export type ExtractTxRetrieveSuccessResult<T> = T extends undefined
|
|
30
|
+
? undefined
|
|
31
|
+
: T extends TxResult<unknown, infer R>
|
|
32
|
+
? R
|
|
33
|
+
: never;
|
|
43
34
|
|
|
44
35
|
/**
|
|
45
|
-
*
|
|
36
|
+
* Extract the final result type from a TxResult.
|
|
37
|
+
* Handles undefined (for optional service patterns like optionalService?.method()).
|
|
46
38
|
*/
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
39
|
+
export type ExtractTxFinalResult<T> = T extends undefined
|
|
40
|
+
? undefined
|
|
41
|
+
: T extends TxResult<infer R, infer _>
|
|
42
|
+
? R
|
|
43
|
+
: Awaited<T>;
|
|
51
44
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
45
|
+
/**
|
|
46
|
+
* Map over service calls array to extract retrieve success results from each service call.
|
|
47
|
+
* Preserves tuple structure while extracting the retrieve success result type from each element.
|
|
48
|
+
*/
|
|
49
|
+
export type ExtractServiceRetrieveResults<T extends readonly unknown[]> = {
|
|
50
|
+
[K in keyof T]: ExtractTxRetrieveSuccessResult<T[K]>;
|
|
51
|
+
};
|
|
55
52
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
53
|
+
/**
|
|
54
|
+
* Map over service calls array to extract final results from each service call.
|
|
55
|
+
* Preserves tuple structure while extracting the final result type from each element.
|
|
56
|
+
*/
|
|
57
|
+
export type ExtractServiceFinalResults<T extends readonly unknown[]> = {
|
|
58
|
+
[K in keyof T]: ExtractTxFinalResult<T[K]>;
|
|
59
|
+
};
|
|
60
60
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
61
|
+
/**
|
|
62
|
+
* Context passed to mutate callback for service methods
|
|
63
|
+
*/
|
|
64
|
+
export interface ServiceTxMutateContext<
|
|
65
|
+
TSchema extends AnySchema,
|
|
66
|
+
TRetrieveSuccessResult,
|
|
67
|
+
TServiceRetrieveResults extends readonly unknown[],
|
|
68
|
+
THooks extends HooksMap,
|
|
69
|
+
> {
|
|
70
|
+
/** Unit of work for scheduling mutations */
|
|
71
|
+
uow: TypedUnitOfWork<TSchema, [], unknown, THooks>;
|
|
72
|
+
/** Result from retrieveSuccess callback (or raw retrieve results if no retrieveSuccess) */
|
|
73
|
+
retrieveResult: TRetrieveSuccessResult;
|
|
74
|
+
/** Array of retrieve success results from service calls (intermediate results, not final) */
|
|
75
|
+
serviceIntermediateResult: TServiceRetrieveResults;
|
|
76
|
+
}
|
|
68
77
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
)
|
|
78
|
+
/**
|
|
79
|
+
* Context passed to handler-level callbacks
|
|
80
|
+
*/
|
|
81
|
+
export interface HandlerTxContext<THooks extends HooksMap> {
|
|
82
|
+
/** Get a typed Unit of Work for the given schema */
|
|
83
|
+
forSchema: <S extends AnySchema, H extends HooksMap = THooks>(
|
|
84
|
+
schema: S,
|
|
85
|
+
hooks?: H,
|
|
86
|
+
) => TypedUnitOfWork<S, [], unknown, H>;
|
|
87
|
+
/** Unique key for this transaction attempt (for idempotency/deduplication) */
|
|
88
|
+
idempotencyKey: string;
|
|
89
|
+
/** Current attempt number (0-based) */
|
|
90
|
+
currentAttempt: number;
|
|
91
|
+
}
|
|
78
92
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
93
|
+
/**
|
|
94
|
+
* Context passed to handler mutate callback
|
|
95
|
+
*/
|
|
96
|
+
export interface HandlerTxMutateContext<
|
|
97
|
+
TRetrieveSuccessResult,
|
|
98
|
+
TServiceRetrieveResults extends readonly unknown[],
|
|
99
|
+
THooks extends HooksMap,
|
|
100
|
+
> extends HandlerTxContext<THooks> {
|
|
101
|
+
/** Result from retrieveSuccess callback (or raw retrieve results if no retrieveSuccess) */
|
|
102
|
+
retrieveResult: TRetrieveSuccessResult;
|
|
103
|
+
/** Array of retrieve success results from service calls (intermediate results, not final) */
|
|
104
|
+
serviceIntermediateResult: TServiceRetrieveResults;
|
|
105
|
+
}
|
|
82
106
|
|
|
83
|
-
|
|
107
|
+
/**
|
|
108
|
+
* Context passed to success callback when mutate IS provided
|
|
109
|
+
*/
|
|
110
|
+
export interface TxSuccessContextWithMutate<
|
|
111
|
+
TRetrieveSuccessResult,
|
|
112
|
+
TMutateResult,
|
|
113
|
+
TServiceFinalResults extends readonly unknown[],
|
|
114
|
+
TServiceRetrieveResults extends readonly unknown[],
|
|
115
|
+
> {
|
|
116
|
+
/** Result from retrieveSuccess callback (or raw retrieve results if no retrieveSuccess) */
|
|
117
|
+
retrieveResult: TRetrieveSuccessResult;
|
|
118
|
+
/** Result from mutate callback */
|
|
119
|
+
mutateResult: TMutateResult;
|
|
120
|
+
/** Array of final results from service calls */
|
|
121
|
+
serviceResult: TServiceFinalResults;
|
|
122
|
+
/** Array of retrieve success results from service calls (same as what mutate receives) */
|
|
123
|
+
serviceIntermediateResult: TServiceRetrieveResults;
|
|
84
124
|
}
|
|
85
125
|
|
|
86
126
|
/**
|
|
87
|
-
*
|
|
88
|
-
* Promises in mutationResult are unwrapped 1 level deep
|
|
127
|
+
* Context passed to success callback when mutate is NOT provided
|
|
89
128
|
*/
|
|
90
|
-
export
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
129
|
+
export interface TxSuccessContextWithoutMutate<
|
|
130
|
+
TRetrieveSuccessResult,
|
|
131
|
+
TServiceFinalResults extends readonly unknown[],
|
|
132
|
+
TServiceRetrieveResults extends readonly unknown[],
|
|
133
|
+
> {
|
|
134
|
+
/** Result from retrieveSuccess callback (or raw retrieve results if no retrieveSuccess) */
|
|
135
|
+
retrieveResult: TRetrieveSuccessResult;
|
|
136
|
+
/** No mutate callback was provided */
|
|
137
|
+
mutateResult: undefined;
|
|
138
|
+
/** Array of final results from service calls */
|
|
139
|
+
serviceResult: TServiceFinalResults;
|
|
140
|
+
/** Array of retrieve success results from service calls (same as what mutate receives) */
|
|
141
|
+
serviceIntermediateResult: TServiceRetrieveResults;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Context passed to success callback.
|
|
146
|
+
* Union of TxSuccessContextWithMutate and TxSuccessContextWithoutMutate to handle
|
|
147
|
+
* both cases in a single callback signature.
|
|
148
|
+
*/
|
|
149
|
+
export type TxSuccessContext<
|
|
150
|
+
TRetrieveSuccessResult,
|
|
151
|
+
TMutateResult,
|
|
152
|
+
TServiceFinalResults extends readonly unknown[],
|
|
153
|
+
TServiceRetrieveResults extends readonly unknown[] = readonly unknown[],
|
|
154
|
+
> =
|
|
155
|
+
| TxSuccessContextWithMutate<
|
|
156
|
+
TRetrieveSuccessResult,
|
|
157
|
+
TMutateResult,
|
|
158
|
+
TServiceFinalResults,
|
|
159
|
+
TServiceRetrieveResults
|
|
160
|
+
>
|
|
161
|
+
| TxSuccessContextWithoutMutate<
|
|
162
|
+
TRetrieveSuccessResult,
|
|
163
|
+
TServiceFinalResults,
|
|
164
|
+
TServiceRetrieveResults
|
|
165
|
+
>;
|
|
111
166
|
|
|
112
167
|
/**
|
|
113
|
-
* Callbacks for
|
|
168
|
+
* Callbacks for service-level TxResult.
|
|
169
|
+
*
|
|
170
|
+
* Return type priority:
|
|
171
|
+
* 1. If success exists: ReturnType<success>
|
|
172
|
+
* 2. Else if mutate exists: ReturnType<mutate>
|
|
173
|
+
* 3. Else if retrieveSuccess exists: ReturnType<retrieveSuccess>
|
|
174
|
+
* 4. Else if retrieve exists: TRetrieveResults
|
|
175
|
+
* 5. Else: serviceResult array type
|
|
114
176
|
*/
|
|
115
|
-
export interface
|
|
177
|
+
export interface ServiceTxCallbacks<
|
|
116
178
|
TSchema extends AnySchema,
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
179
|
+
TRetrieveResults extends unknown[],
|
|
180
|
+
TRetrieveSuccessResult,
|
|
181
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
182
|
+
TServiceCalls extends readonly (TxResult<any, any> | undefined)[],
|
|
183
|
+
TMutateResult,
|
|
184
|
+
TSuccessResult,
|
|
185
|
+
THooks extends HooksMap,
|
|
120
186
|
> {
|
|
121
187
|
/**
|
|
122
|
-
*
|
|
188
|
+
* Service calls - other TxResults to execute first.
|
|
189
|
+
*/
|
|
190
|
+
serviceCalls?: () => TServiceCalls;
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Retrieval phase callback - schedules retrieval operations.
|
|
123
194
|
*/
|
|
124
195
|
retrieve?: (
|
|
125
|
-
uow: TypedUnitOfWork<TSchema, [],
|
|
126
|
-
) => TypedUnitOfWork<TSchema,
|
|
196
|
+
uow: TypedUnitOfWork<TSchema, [], unknown, THooks>,
|
|
197
|
+
) => TypedUnitOfWork<TSchema, TRetrieveResults, unknown, THooks>;
|
|
127
198
|
|
|
128
199
|
/**
|
|
129
|
-
*
|
|
200
|
+
* Transform retrieve results before passing to mutate.
|
|
201
|
+
*/
|
|
202
|
+
retrieveSuccess?: (
|
|
203
|
+
retrieveResult: TRetrieveResults,
|
|
204
|
+
serviceResult: ExtractServiceRetrieveResults<TServiceCalls>,
|
|
205
|
+
) => TRetrieveSuccessResult;
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* Mutation phase callback - schedules mutations based on retrieve results.
|
|
130
209
|
*/
|
|
131
210
|
mutate?: (
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
211
|
+
ctx: ServiceTxMutateContext<
|
|
212
|
+
TSchema,
|
|
213
|
+
TRetrieveSuccessResult,
|
|
214
|
+
ExtractServiceRetrieveResults<TServiceCalls>,
|
|
215
|
+
THooks
|
|
216
|
+
>,
|
|
217
|
+
) => TMutateResult;
|
|
135
218
|
|
|
136
219
|
/**
|
|
137
|
-
* Success callback -
|
|
138
|
-
* Promises in mutationResult are already unwrapped 1 level deep
|
|
220
|
+
* Success callback - final transformation after mutations complete.
|
|
139
221
|
*/
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
222
|
+
success?: (
|
|
223
|
+
ctx: TxSuccessContext<
|
|
224
|
+
TRetrieveSuccessResult,
|
|
225
|
+
TMutateResult,
|
|
226
|
+
ExtractServiceFinalResults<TServiceCalls>,
|
|
227
|
+
ExtractServiceRetrieveResults<TServiceCalls>
|
|
228
|
+
>,
|
|
229
|
+
) => TSuccessResult;
|
|
146
230
|
}
|
|
147
231
|
|
|
148
232
|
/**
|
|
149
|
-
*
|
|
233
|
+
* Callbacks for handler-level executeTx.
|
|
234
|
+
* Uses context-based callbacks that provide forSchema() method.
|
|
150
235
|
*/
|
|
151
|
-
export interface
|
|
236
|
+
export interface HandlerTxCallbacks<
|
|
237
|
+
TRetrieveResults extends unknown[],
|
|
238
|
+
TRetrieveSuccessResult,
|
|
239
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
240
|
+
TServiceCalls extends readonly (TxResult<any, any> | undefined)[],
|
|
241
|
+
TMutateResult,
|
|
242
|
+
TSuccessResult,
|
|
243
|
+
THooks extends HooksMap,
|
|
244
|
+
> {
|
|
152
245
|
/**
|
|
153
|
-
*
|
|
246
|
+
* Service calls - other TxResults to execute first.
|
|
154
247
|
*/
|
|
155
|
-
|
|
248
|
+
serviceCalls?: () => TServiceCalls;
|
|
156
249
|
|
|
157
250
|
/**
|
|
158
|
-
*
|
|
251
|
+
* Retrieval phase callback - schedules retrieval operations using context.forSchema().
|
|
252
|
+
* Return a TypedUnitOfWork to get typed results, or void for no retrieval.
|
|
159
253
|
*/
|
|
160
|
-
|
|
254
|
+
retrieve?: (
|
|
255
|
+
context: HandlerTxContext<THooks>,
|
|
256
|
+
) => TypedUnitOfWork<AnySchema, TRetrieveResults, unknown, HooksMap> | void;
|
|
161
257
|
|
|
162
258
|
/**
|
|
163
|
-
*
|
|
259
|
+
* Transform retrieve results before passing to mutate.
|
|
164
260
|
*/
|
|
165
|
-
|
|
261
|
+
retrieveSuccess?: (
|
|
262
|
+
retrieveResult: TRetrieveResults,
|
|
263
|
+
serviceResult: ExtractServiceRetrieveResults<TServiceCalls>,
|
|
264
|
+
) => TRetrieveSuccessResult;
|
|
265
|
+
|
|
266
|
+
/**
|
|
267
|
+
* Mutation phase callback - schedules mutations based on retrieve results.
|
|
268
|
+
*/
|
|
269
|
+
mutate?: (
|
|
270
|
+
ctx: HandlerTxMutateContext<
|
|
271
|
+
TRetrieveSuccessResult,
|
|
272
|
+
ExtractServiceRetrieveResults<TServiceCalls>,
|
|
273
|
+
THooks
|
|
274
|
+
>,
|
|
275
|
+
) => TMutateResult;
|
|
276
|
+
|
|
277
|
+
/**
|
|
278
|
+
* Success callback - final transformation after mutations complete.
|
|
279
|
+
*/
|
|
280
|
+
success?: (
|
|
281
|
+
ctx: TxSuccessContext<
|
|
282
|
+
TRetrieveSuccessResult,
|
|
283
|
+
TMutateResult,
|
|
284
|
+
ExtractServiceFinalResults<TServiceCalls>,
|
|
285
|
+
ExtractServiceRetrieveResults<TServiceCalls>
|
|
286
|
+
>,
|
|
287
|
+
) => TSuccessResult;
|
|
166
288
|
}
|
|
167
289
|
|
|
168
290
|
/**
|
|
169
|
-
*
|
|
170
|
-
* This is useful for handler contexts where the factory is already known.
|
|
171
|
-
*
|
|
172
|
-
* @param createUnitOfWork - Factory function that creates a fresh UOW instance
|
|
173
|
-
* @returns A bound executeUnitOfWork function that doesn't require the factory parameter
|
|
174
|
-
*
|
|
175
|
-
* @example
|
|
176
|
-
* ```ts
|
|
177
|
-
* const boundExecute = createExecuteUnitOfWork(() => db.createUnitOfWork());
|
|
178
|
-
* const result = await boundExecute({
|
|
179
|
-
* retrieve: (uow) => uow.find("users", (b) => b.whereIndex("primary")),
|
|
180
|
-
* mutate: (uow, [users]) => {
|
|
181
|
-
* uow.update("users", users[0].id, (b) => b.set({ balance: newBalance }));
|
|
182
|
-
* }
|
|
183
|
-
* });
|
|
184
|
-
* ```
|
|
291
|
+
* Internal structure storing TxResult callbacks and state.
|
|
185
292
|
*/
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
293
|
+
interface TxResultInternal<
|
|
294
|
+
TSchema extends AnySchema,
|
|
295
|
+
TRetrieveResults extends unknown[],
|
|
296
|
+
TRetrieveSuccessResult,
|
|
297
|
+
TServiceCalls extends readonly (TxResult<unknown> | undefined)[],
|
|
298
|
+
TMutateResult,
|
|
299
|
+
TSuccessResult,
|
|
300
|
+
THooks extends HooksMap,
|
|
301
|
+
> {
|
|
302
|
+
schema: TSchema | undefined;
|
|
303
|
+
callbacks: ServiceTxCallbacks<
|
|
304
|
+
TSchema,
|
|
305
|
+
TRetrieveResults,
|
|
306
|
+
TRetrieveSuccessResult,
|
|
307
|
+
TServiceCalls,
|
|
308
|
+
TMutateResult,
|
|
309
|
+
TSuccessResult,
|
|
310
|
+
THooks
|
|
311
|
+
>;
|
|
312
|
+
/** The typed UOW created during retrieve callback */
|
|
313
|
+
typedUow: TypedUnitOfWork<TSchema, TRetrieveResults, unknown, THooks> | undefined;
|
|
314
|
+
/** The restricted UOW for signaling (used when typedUow is undefined) */
|
|
315
|
+
restrictedUow: IUnitOfWork;
|
|
316
|
+
/** Promise that resolves when retrieve phase is complete */
|
|
317
|
+
retrievePhase: Promise<TRetrieveResults>;
|
|
318
|
+
/** Resolve function for retrievePhase */
|
|
319
|
+
resolveRetrievePhase: (results: TRetrieveResults) => void;
|
|
320
|
+
/** Reject function for retrievePhase */
|
|
321
|
+
rejectRetrievePhase: (error: unknown) => void;
|
|
322
|
+
/** Computed retrieve success result (set after retrieveSuccess runs) */
|
|
323
|
+
retrieveSuccessResult: TRetrieveSuccessResult | undefined;
|
|
324
|
+
/** Computed mutate result (set after mutate runs) */
|
|
325
|
+
mutateResult: TMutateResult | undefined;
|
|
326
|
+
/** Computed final result (set after success runs or defaults) */
|
|
327
|
+
finalResult: TSuccessResult | undefined;
|
|
328
|
+
/** Service calls resolved */
|
|
329
|
+
serviceCalls: TServiceCalls | undefined;
|
|
195
330
|
}
|
|
196
331
|
|
|
197
332
|
/**
|
|
198
|
-
*
|
|
199
|
-
*
|
|
200
|
-
* This function orchestrates the two-phase execution (retrieval + mutation) with retry logic.
|
|
201
|
-
* It creates fresh UOW instances for each attempt.
|
|
333
|
+
* TxResult represents a transaction definition (not yet executed).
|
|
334
|
+
* It describes the work to be done: retrieve operations, transformations, and mutations.
|
|
202
335
|
*
|
|
203
|
-
*
|
|
204
|
-
*
|
|
205
|
-
* @returns Promise resolving to the execution result
|
|
336
|
+
* Service methods return TxResult objects, and the handler's executeTx function
|
|
337
|
+
* orchestrates their execution with retry support.
|
|
206
338
|
*
|
|
207
|
-
* @
|
|
208
|
-
*
|
|
209
|
-
*
|
|
210
|
-
* {
|
|
211
|
-
* retrieve: (uow) => uow.find("users", (b) => b.whereIndex("primary")),
|
|
212
|
-
* mutate: (uow, [users]) => {
|
|
213
|
-
* const user = users[0];
|
|
214
|
-
* uow.update("users", user.id, (b) => b.set({ balance: newBalance }));
|
|
215
|
-
* },
|
|
216
|
-
* onSuccess: async ({ results, mutationResult }) => {
|
|
217
|
-
* console.log("Update successful!");
|
|
218
|
-
* }
|
|
219
|
-
* },
|
|
220
|
-
* {
|
|
221
|
-
* createUnitOfWork: () => queryEngine.createUnitOfWork(),
|
|
222
|
-
* retryPolicy: new ExponentialBackoffRetryPolicy({ maxRetries: 3 })
|
|
223
|
-
* }
|
|
224
|
-
* );
|
|
225
|
-
* ```
|
|
339
|
+
* @template TResult - The final result type (determined by return type priority)
|
|
340
|
+
* @template TRetrieveSuccessResult - The retrieve success result type (what serviceCalls receive).
|
|
341
|
+
* Defaults to TResult, meaning serviceCalls receive the same type as the final result.
|
|
226
342
|
*/
|
|
227
|
-
export
|
|
343
|
+
export interface TxResult<TResult, TRetrieveSuccessResult = TResult> {
|
|
344
|
+
/** Brand to identify TxResult objects */
|
|
345
|
+
readonly [TX_RESULT_BRAND]: true;
|
|
346
|
+
|
|
347
|
+
/** Internal structure - do not access directly */
|
|
348
|
+
readonly _internal: TxResultInternal<
|
|
349
|
+
AnySchema,
|
|
350
|
+
unknown[],
|
|
351
|
+
TRetrieveSuccessResult,
|
|
352
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
353
|
+
readonly TxResult<any, any>[],
|
|
354
|
+
unknown,
|
|
355
|
+
TResult,
|
|
356
|
+
HooksMap
|
|
357
|
+
>;
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
/**
|
|
361
|
+
* Create a TxResult for service context.
|
|
362
|
+
* Schedules retrieve operations on the baseUow and returns a TxResult with callbacks stored.
|
|
363
|
+
* @internal Used by ServiceTxBuilder.build()
|
|
364
|
+
*/
|
|
365
|
+
function createServiceTx<
|
|
228
366
|
TSchema extends AnySchema,
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
367
|
+
TRetrieveResults extends unknown[],
|
|
368
|
+
TRetrieveSuccessResult,
|
|
369
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
370
|
+
TServiceCalls extends readonly (TxResult<any, any> | undefined)[],
|
|
371
|
+
TMutateResult,
|
|
372
|
+
TSuccessResult,
|
|
373
|
+
THooks extends HooksMap = {},
|
|
232
374
|
>(
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
375
|
+
schema: TSchema | undefined,
|
|
376
|
+
callbacks: ServiceTxCallbacks<
|
|
377
|
+
TSchema,
|
|
378
|
+
TRetrieveResults,
|
|
379
|
+
TRetrieveSuccessResult,
|
|
380
|
+
TServiceCalls,
|
|
381
|
+
TMutateResult,
|
|
382
|
+
TSuccessResult,
|
|
383
|
+
THooks
|
|
384
|
+
>,
|
|
385
|
+
baseUow: IUnitOfWork,
|
|
386
|
+
): TxResult<unknown, unknown> {
|
|
387
|
+
// Create deferred promise for retrieve phase
|
|
388
|
+
const {
|
|
389
|
+
promise: retrievePhase,
|
|
390
|
+
resolve: resolveRetrievePhase,
|
|
391
|
+
reject: rejectRetrievePhase,
|
|
392
|
+
} = Promise.withResolvers<TRetrieveResults>();
|
|
393
|
+
|
|
394
|
+
// Get a restricted view that signals readiness
|
|
395
|
+
const restrictedUow = baseUow.restrict({ readyFor: "none" });
|
|
396
|
+
|
|
397
|
+
// Call serviceCalls factory if provided - this invokes other services which schedule their operations
|
|
398
|
+
let serviceCalls: TServiceCalls | undefined;
|
|
399
|
+
try {
|
|
400
|
+
if (callbacks.serviceCalls) {
|
|
401
|
+
serviceCalls = callbacks.serviceCalls();
|
|
402
|
+
}
|
|
403
|
+
} catch (error) {
|
|
404
|
+
restrictedUow.signalReadyForRetrieval();
|
|
405
|
+
restrictedUow.signalReadyForMutation();
|
|
406
|
+
retrievePhase.catch(() => {});
|
|
407
|
+
rejectRetrievePhase(error);
|
|
408
|
+
throw error;
|
|
239
409
|
}
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
while (true) {
|
|
246
|
-
// Check if aborted before starting attempt
|
|
247
|
-
if (signal?.aborted) {
|
|
248
|
-
return { success: false, reason: "aborted" };
|
|
410
|
+
let typedUow: TypedUnitOfWork<TSchema, TRetrieveResults, unknown, THooks> | undefined;
|
|
411
|
+
try {
|
|
412
|
+
if (schema && callbacks.retrieve) {
|
|
413
|
+
const emptyUow = restrictedUow.forSchema<TSchema, THooks>(schema);
|
|
414
|
+
typedUow = callbacks.retrieve(emptyUow);
|
|
249
415
|
}
|
|
416
|
+
} catch (error) {
|
|
417
|
+
restrictedUow.signalReadyForRetrieval();
|
|
418
|
+
restrictedUow.signalReadyForMutation();
|
|
419
|
+
retrievePhase.catch(() => {});
|
|
420
|
+
rejectRetrievePhase(error);
|
|
421
|
+
throw error;
|
|
422
|
+
}
|
|
423
|
+
restrictedUow.signalReadyForRetrieval();
|
|
250
424
|
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
// This is safe because when there's no retrieve, TRetrievalResults should be []
|
|
262
|
-
retrievalUow = uow as unknown as TypedUnitOfWork<TSchema, TRetrievalResults, TRawInput>;
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
// Execute retrieval phase
|
|
266
|
-
const results = (await retrievalUow.executeRetrieve()) as TRetrievalResults;
|
|
267
|
-
|
|
268
|
-
// Invoke mutation phase callback if provided
|
|
269
|
-
let mutationResult: TMutationResult;
|
|
270
|
-
if (callbacks.mutate) {
|
|
271
|
-
mutationResult = await callbacks.mutate(retrievalUow, results);
|
|
272
|
-
} else {
|
|
273
|
-
mutationResult = undefined as TMutationResult;
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
// Execute mutation phase
|
|
277
|
-
const { success } = await retrievalUow.executeMutations();
|
|
278
|
-
|
|
279
|
-
if (success) {
|
|
280
|
-
// Success! Get created IDs and nonce, then invoke onSuccess if provided
|
|
281
|
-
const createdIds = retrievalUow.getCreatedIds();
|
|
282
|
-
const nonce = retrievalUow.nonce;
|
|
283
|
-
|
|
284
|
-
// Await promises in mutationResult (1 level deep)
|
|
285
|
-
const awaitedMutationResult = await awaitPromisesInObject(mutationResult);
|
|
286
|
-
|
|
287
|
-
if (callbacks.onSuccess) {
|
|
288
|
-
await callbacks.onSuccess({
|
|
289
|
-
results,
|
|
290
|
-
mutationResult: awaitedMutationResult,
|
|
291
|
-
createdIds,
|
|
292
|
-
nonce,
|
|
293
|
-
});
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
return {
|
|
297
|
-
success: true,
|
|
298
|
-
results,
|
|
299
|
-
mutationResult: awaitedMutationResult,
|
|
300
|
-
createdIds,
|
|
301
|
-
nonce,
|
|
302
|
-
};
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
// Failed - check if we should retry
|
|
306
|
-
// attempt represents the number of attempts completed so far
|
|
307
|
-
if (!retryPolicy.shouldRetry(attempt, undefined, signal)) {
|
|
308
|
-
// No more retries
|
|
309
|
-
return { success: false, reason: "conflict" };
|
|
310
|
-
}
|
|
425
|
+
// Set up the retrieve phase promise to resolve when the handler executes retrieve
|
|
426
|
+
if (typedUow) {
|
|
427
|
+
typedUow.retrievalPhase.then(
|
|
428
|
+
(results) => resolveRetrievePhase(results as TRetrieveResults),
|
|
429
|
+
(error) => rejectRetrievePhase(error),
|
|
430
|
+
);
|
|
431
|
+
} else if (!callbacks.retrieve) {
|
|
432
|
+
// No retrieve callback - resolve immediately with empty array
|
|
433
|
+
resolveRetrievePhase([] as unknown as TRetrieveResults);
|
|
434
|
+
}
|
|
311
435
|
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
436
|
+
const internal: TxResultInternal<
|
|
437
|
+
TSchema,
|
|
438
|
+
TRetrieveResults,
|
|
439
|
+
TRetrieveSuccessResult,
|
|
440
|
+
TServiceCalls,
|
|
441
|
+
TMutateResult,
|
|
442
|
+
TSuccessResult,
|
|
443
|
+
THooks
|
|
444
|
+
> = {
|
|
445
|
+
schema,
|
|
446
|
+
callbacks,
|
|
447
|
+
typedUow,
|
|
448
|
+
restrictedUow,
|
|
449
|
+
retrievePhase,
|
|
450
|
+
resolveRetrievePhase,
|
|
451
|
+
rejectRetrievePhase,
|
|
452
|
+
retrieveSuccessResult: undefined,
|
|
453
|
+
mutateResult: undefined,
|
|
454
|
+
finalResult: undefined,
|
|
455
|
+
serviceCalls,
|
|
456
|
+
};
|
|
316
457
|
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
458
|
+
return {
|
|
459
|
+
[TX_RESULT_BRAND]: true as const,
|
|
460
|
+
// Cast through unknown to avoid type incompatibility issues with generic constraints
|
|
461
|
+
_internal: internal as unknown as TxResultInternal<
|
|
462
|
+
AnySchema,
|
|
463
|
+
unknown[],
|
|
464
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
465
|
+
any,
|
|
466
|
+
readonly TxResult<unknown>[],
|
|
467
|
+
unknown,
|
|
468
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
469
|
+
any,
|
|
470
|
+
HooksMap
|
|
471
|
+
>,
|
|
472
|
+
};
|
|
323
473
|
}
|
|
324
474
|
|
|
325
475
|
/**
|
|
326
|
-
* Options for executing
|
|
476
|
+
* Options for executing transactions
|
|
327
477
|
*/
|
|
328
|
-
export interface
|
|
478
|
+
export interface ExecuteTxOptions {
|
|
329
479
|
/**
|
|
330
480
|
* Factory function that creates or resets a UOW instance for each attempt
|
|
331
481
|
*/
|
|
@@ -351,112 +501,229 @@ export interface ExecuteRestrictedUnitOfWorkOptions {
|
|
|
351
501
|
* Callback invoked after successful mutation phase.
|
|
352
502
|
* Use this for post-mutation processing like hook execution.
|
|
353
503
|
*/
|
|
354
|
-
|
|
504
|
+
onAfterMutate?: (uow: IUnitOfWork) => Promise<void>;
|
|
355
505
|
}
|
|
356
506
|
|
|
357
507
|
/**
|
|
358
|
-
*
|
|
508
|
+
* Recursively collect all TxResults from a service call tree.
|
|
509
|
+
* Returns them in a flat array in dependency order (serviceCalls before their dependents).
|
|
510
|
+
* Skips undefined values (which can occur with optional service patterns like
|
|
511
|
+
* optionalService?.method()).
|
|
359
512
|
*/
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
513
|
+
function collectAllTxResults(
|
|
514
|
+
txResults: readonly (TxResult<unknown> | undefined)[],
|
|
515
|
+
): TxResult<unknown>[] {
|
|
516
|
+
const collected: TxResult<unknown>[] = [];
|
|
517
|
+
const seen = new Set<TxResult<unknown>>();
|
|
518
|
+
|
|
519
|
+
function collect(txResult: TxResult<unknown> | undefined) {
|
|
520
|
+
if (txResult === undefined) {
|
|
521
|
+
return;
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
if (seen.has(txResult)) {
|
|
525
|
+
return;
|
|
526
|
+
}
|
|
527
|
+
seen.add(txResult);
|
|
528
|
+
|
|
529
|
+
// First collect serviceCalls (so they come before this TxResult)
|
|
530
|
+
const serviceCalls = txResult._internal.serviceCalls;
|
|
531
|
+
if (serviceCalls) {
|
|
532
|
+
for (const serviceCall of serviceCalls) {
|
|
533
|
+
collect(serviceCall);
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
collected.push(txResult);
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
for (const txResult of txResults) {
|
|
541
|
+
collect(txResult);
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
return collected;
|
|
368
545
|
}
|
|
369
546
|
|
|
370
547
|
/**
|
|
371
|
-
*
|
|
372
|
-
* This
|
|
548
|
+
* Execute a single TxResult's callbacks after retrieve phase completes.
|
|
549
|
+
* This processes retrieveSuccess, mutate, and success callbacks in order.
|
|
373
550
|
*/
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
551
|
+
async function processTxResultAfterRetrieve<T>(
|
|
552
|
+
txResult: TxResult<T>,
|
|
553
|
+
baseUow: IUnitOfWork,
|
|
554
|
+
): Promise<void> {
|
|
555
|
+
const internal = txResult._internal;
|
|
556
|
+
const callbacks = internal.callbacks;
|
|
557
|
+
|
|
558
|
+
// Wait for retrieve phase to complete
|
|
559
|
+
const retrieveResults = await internal.retrievePhase;
|
|
560
|
+
|
|
561
|
+
// Collect serviceCalls' retrieve success results (or mutate results if no retrieve was provided)
|
|
562
|
+
// When a serviceCall has no retrieve/retrieveSuccess but has mutate, its mutate has already run
|
|
563
|
+
// (due to service call execution order), so we use its mutate result as the "retrieve success result".
|
|
564
|
+
const serviceResults: unknown[] = [];
|
|
565
|
+
if (internal.serviceCalls) {
|
|
566
|
+
for (const serviceCall of internal.serviceCalls) {
|
|
567
|
+
if (serviceCall === undefined) {
|
|
568
|
+
serviceResults.push(undefined);
|
|
569
|
+
continue;
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
const serviceCallInternal = serviceCall._internal;
|
|
573
|
+
// Check if this is a mutate-only service call (empty array sentinel with mutate callback)
|
|
574
|
+
// In that case, prefer mutateResult over the empty array retrieveSuccessResult
|
|
575
|
+
if (
|
|
576
|
+
serviceCallInternal.retrieveSuccessResult !== undefined &&
|
|
577
|
+
!(
|
|
578
|
+
Array.isArray(serviceCallInternal.retrieveSuccessResult) &&
|
|
579
|
+
serviceCallInternal.retrieveSuccessResult.length === 0 &&
|
|
580
|
+
serviceCallInternal.callbacks.mutate
|
|
581
|
+
)
|
|
582
|
+
) {
|
|
583
|
+
serviceResults.push(serviceCallInternal.retrieveSuccessResult);
|
|
584
|
+
} else if (serviceCallInternal.mutateResult !== undefined) {
|
|
585
|
+
serviceResults.push(serviceCallInternal.mutateResult);
|
|
586
|
+
} else {
|
|
587
|
+
serviceResults.push(serviceCallInternal.retrieveSuccessResult);
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
if (callbacks.retrieveSuccess) {
|
|
593
|
+
internal.retrieveSuccessResult = callbacks.retrieveSuccess(
|
|
594
|
+
retrieveResults,
|
|
595
|
+
serviceResults as ExtractServiceRetrieveResults<readonly TxResult<unknown>[]>,
|
|
596
|
+
);
|
|
597
|
+
} else {
|
|
598
|
+
internal.retrieveSuccessResult = retrieveResults as typeof internal.retrieveSuccessResult;
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
if (callbacks.mutate) {
|
|
602
|
+
const mutateCtx = {
|
|
603
|
+
uow: internal.schema
|
|
604
|
+
? baseUow.forSchema(internal.schema)
|
|
605
|
+
: (undefined as unknown as TypedUnitOfWork<AnySchema, [], unknown, HooksMap>),
|
|
606
|
+
// At this point retrieveSuccessResult has been set (either by retrieveSuccess
|
|
607
|
+
// callback or defaulted to retrieveResults)
|
|
608
|
+
retrieveResult: internal.retrieveSuccessResult as NonNullable<
|
|
609
|
+
typeof internal.retrieveSuccessResult
|
|
610
|
+
>,
|
|
611
|
+
serviceIntermediateResult: serviceResults as ExtractServiceRetrieveResults<
|
|
612
|
+
readonly TxResult<unknown>[]
|
|
613
|
+
>,
|
|
614
|
+
};
|
|
615
|
+
internal.mutateResult = callbacks.mutate(mutateCtx);
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
if (internal.typedUow) {
|
|
619
|
+
internal.typedUow.signalReadyForMutation();
|
|
620
|
+
} else {
|
|
621
|
+
// For TxResults without retrieve callback, signal via the restricted UOW
|
|
622
|
+
internal.restrictedUow.signalReadyForMutation();
|
|
623
|
+
}
|
|
385
624
|
}
|
|
386
625
|
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
626
|
+
/**
|
|
627
|
+
* Execute a single TxResult's success callback after mutations complete.
|
|
628
|
+
*/
|
|
629
|
+
async function processTxResultAfterMutate<T>(txResult: TxResult<T>): Promise<T> {
|
|
630
|
+
const internal = txResult._internal;
|
|
631
|
+
const callbacks = internal.callbacks;
|
|
632
|
+
|
|
633
|
+
const serviceIntermediateResults: unknown[] = [];
|
|
634
|
+
const serviceFinalResults: unknown[] = [];
|
|
635
|
+
if (internal.serviceCalls) {
|
|
636
|
+
for (const serviceCall of internal.serviceCalls) {
|
|
637
|
+
if (serviceCall === undefined) {
|
|
638
|
+
serviceIntermediateResults.push(undefined);
|
|
639
|
+
serviceFinalResults.push(undefined);
|
|
640
|
+
continue;
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
// Mirror the logic from processTxResultAfterRetrieve/executeTx:
|
|
644
|
+
// For mutate-only serviceCalls (no retrieve phase, just mutations), use mutateResult instead of retrieveSuccessResult
|
|
645
|
+
const serviceCallInternal = serviceCall._internal;
|
|
646
|
+
// Check if this is a mutate-only service call (empty array sentinel with mutate callback)
|
|
647
|
+
// In that case, prefer mutateResult over the empty array retrieveSuccessResult
|
|
648
|
+
if (
|
|
649
|
+
serviceCallInternal.retrieveSuccessResult !== undefined &&
|
|
650
|
+
!(
|
|
651
|
+
Array.isArray(serviceCallInternal.retrieveSuccessResult) &&
|
|
652
|
+
serviceCallInternal.retrieveSuccessResult.length === 0 &&
|
|
653
|
+
serviceCallInternal.callbacks.mutate
|
|
654
|
+
)
|
|
655
|
+
) {
|
|
656
|
+
serviceIntermediateResults.push(serviceCallInternal.retrieveSuccessResult);
|
|
657
|
+
} else if (serviceCallInternal.mutateResult !== undefined) {
|
|
658
|
+
serviceIntermediateResults.push(serviceCallInternal.mutateResult);
|
|
659
|
+
} else {
|
|
660
|
+
serviceIntermediateResults.push(serviceCallInternal.retrieveSuccessResult);
|
|
661
|
+
}
|
|
662
|
+
serviceFinalResults.push(serviceCallInternal.finalResult);
|
|
663
|
+
}
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
if (callbacks.success) {
|
|
667
|
+
const successCtx = {
|
|
668
|
+
retrieveResult: internal.retrieveSuccessResult as NonNullable<
|
|
669
|
+
typeof internal.retrieveSuccessResult
|
|
670
|
+
>,
|
|
671
|
+
mutateResult: internal.mutateResult,
|
|
672
|
+
serviceResult: serviceFinalResults as ExtractServiceFinalResults<
|
|
673
|
+
readonly TxResult<unknown>[]
|
|
674
|
+
>,
|
|
675
|
+
serviceIntermediateResult: serviceIntermediateResults as ExtractServiceRetrieveResults<
|
|
676
|
+
readonly TxResult<unknown>[]
|
|
677
|
+
>,
|
|
678
|
+
};
|
|
679
|
+
internal.finalResult = callbacks.success(successCtx) as T;
|
|
680
|
+
} else if (callbacks.mutate) {
|
|
681
|
+
internal.finalResult = (await awaitPromisesInObject(internal.mutateResult)) as T;
|
|
682
|
+
} else if (callbacks.retrieveSuccess || callbacks.retrieve) {
|
|
683
|
+
internal.finalResult = internal.retrieveSuccessResult as T;
|
|
684
|
+
} else {
|
|
685
|
+
internal.finalResult = serviceFinalResults as T;
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
return internal.finalResult as T;
|
|
406
689
|
}
|
|
407
690
|
|
|
408
691
|
/**
|
|
409
|
-
* Execute a
|
|
692
|
+
* Execute a transaction with the unified TxResult pattern.
|
|
410
693
|
*
|
|
411
|
-
* This
|
|
412
|
-
* a context object with forSchema, executeRetrieve, and executeMutate methods. The user can
|
|
413
|
-
* create schema-specific UOWs via forSchema, then call executeRetrieve() and executeMutate()
|
|
414
|
-
* to execute the retrieval and mutation phases. The entire callback is re-executed on optimistic
|
|
415
|
-
* concurrency conflicts, ensuring retries work properly.
|
|
694
|
+
* This is the handler-level function that actually executes TxResults with retry support.
|
|
416
695
|
*
|
|
417
|
-
* @param
|
|
696
|
+
* @param callbacks - Transaction callbacks (serviceCalls, retrieve, retrieveSuccess, mutate, success)
|
|
418
697
|
* @param options - Configuration including UOW factory, retry policy, and abort signal
|
|
419
|
-
* @returns Promise resolving to the
|
|
420
|
-
* @throws Error if retries are exhausted or callback throws an error
|
|
698
|
+
* @returns Promise resolving to the result determined by return type priority
|
|
421
699
|
*
|
|
422
700
|
* @example
|
|
423
701
|
* ```ts
|
|
424
|
-
*
|
|
425
|
-
*
|
|
426
|
-
*
|
|
427
|
-
*
|
|
428
|
-
*
|
|
429
|
-
*
|
|
430
|
-
* await executeRetrieve();
|
|
431
|
-
*
|
|
432
|
-
* const profileId = uow.create("profiles", { userId });
|
|
433
|
-
*
|
|
434
|
-
* // Execute mutation phase
|
|
435
|
-
* await executeMutate();
|
|
436
|
-
*
|
|
437
|
-
* return { userId, profileId };
|
|
438
|
-
* },
|
|
439
|
-
* {
|
|
440
|
-
* createUnitOfWork: () => db.createUnitOfWork(),
|
|
441
|
-
* retryPolicy: new ExponentialBackoffRetryPolicy({ maxRetries: 5 })
|
|
442
|
-
* }
|
|
443
|
-
* );
|
|
444
|
-
* ```
|
|
702
|
+
* // Simple retrieve + transform
|
|
703
|
+
* const user = await executeTx({
|
|
704
|
+
* retrieve: (ctx) => ctx.forSchema(usersSchema).find("users", ...),
|
|
705
|
+
* retrieveSuccess: ([users]) => users[0] ?? null,
|
|
706
|
+
* }, { createUnitOfWork });
|
|
707
|
+
* @internal Used by HandlerTxBuilder.execute()
|
|
445
708
|
*/
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
709
|
+
async function executeTx(
|
|
710
|
+
callbacks: HandlerTxCallbacks<
|
|
711
|
+
unknown[],
|
|
712
|
+
unknown,
|
|
713
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
714
|
+
readonly (TxResult<any, any> | undefined)[],
|
|
715
|
+
unknown,
|
|
716
|
+
unknown,
|
|
717
|
+
HooksMap
|
|
718
|
+
>,
|
|
719
|
+
options: ExecuteTxOptions,
|
|
720
|
+
): Promise<unknown> {
|
|
721
|
+
type TRetrieveResults = unknown[];
|
|
722
|
+
type TRetrieveSuccessResult = unknown;
|
|
723
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
724
|
+
type TServiceCalls = readonly (TxResult<any, any> | undefined)[];
|
|
725
|
+
type TMutateResult = unknown;
|
|
726
|
+
type THooks = HooksMap;
|
|
460
727
|
const retryPolicy =
|
|
461
728
|
options.retryPolicy ??
|
|
462
729
|
new ExponentialBackoffRetryPolicy({
|
|
@@ -470,153 +737,147 @@ export async function executeRestrictedUnitOfWork<TResult, THooks extends HooksM
|
|
|
470
737
|
while (true) {
|
|
471
738
|
// Check if aborted before starting attempt
|
|
472
739
|
if (signal?.aborted) {
|
|
473
|
-
throw new Error("
|
|
740
|
+
throw new Error("Transaction execution aborted");
|
|
474
741
|
}
|
|
475
742
|
|
|
476
743
|
try {
|
|
477
744
|
// Create a fresh UOW for this attempt
|
|
478
745
|
const baseUow = options.createUnitOfWork();
|
|
479
746
|
|
|
480
|
-
|
|
747
|
+
// Create handler context
|
|
748
|
+
const context: HandlerTxContext<THooks> = {
|
|
481
749
|
forSchema: <S extends AnySchema, H extends HooksMap = THooks>(schema: S, hooks?: H) => {
|
|
482
750
|
return baseUow.forSchema(schema, hooks);
|
|
483
751
|
},
|
|
484
|
-
|
|
485
|
-
await baseUow.executeRetrieve();
|
|
486
|
-
},
|
|
487
|
-
executeMutate: async () => {
|
|
488
|
-
if (baseUow.state === "executed") {
|
|
489
|
-
return;
|
|
490
|
-
}
|
|
491
|
-
|
|
492
|
-
if (baseUow.state === "building-retrieval") {
|
|
493
|
-
await baseUow.executeRetrieve();
|
|
494
|
-
}
|
|
495
|
-
|
|
496
|
-
// Add hook mutations before executing
|
|
497
|
-
if (options.onBeforeMutate) {
|
|
498
|
-
options.onBeforeMutate(baseUow);
|
|
499
|
-
}
|
|
500
|
-
|
|
501
|
-
const result = await baseUow.executeMutations();
|
|
502
|
-
if (!result.success) {
|
|
503
|
-
throw new ConcurrencyConflictError();
|
|
504
|
-
}
|
|
505
|
-
|
|
506
|
-
if (options.onSuccess) {
|
|
507
|
-
await options.onSuccess(baseUow);
|
|
508
|
-
}
|
|
509
|
-
},
|
|
510
|
-
nonce: baseUow.nonce,
|
|
752
|
+
idempotencyKey: baseUow.idempotencyKey,
|
|
511
753
|
currentAttempt: attempt,
|
|
512
754
|
};
|
|
513
755
|
|
|
514
|
-
//
|
|
515
|
-
|
|
756
|
+
// Call serviceCalls factory if provided - this creates TxResults that schedule operations
|
|
757
|
+
let serviceCalls: TServiceCalls | undefined;
|
|
758
|
+
if (callbacks.serviceCalls) {
|
|
759
|
+
serviceCalls = callbacks.serviceCalls();
|
|
760
|
+
}
|
|
516
761
|
|
|
517
|
-
//
|
|
518
|
-
const
|
|
762
|
+
// Call retrieve callback - it returns a TypedUnitOfWork with scheduled operations or void
|
|
763
|
+
const typedUowFromRetrieve = callbacks.retrieve?.(context);
|
|
519
764
|
|
|
520
|
-
|
|
521
|
-
return awaitedResult;
|
|
522
|
-
} catch (error) {
|
|
523
|
-
if (signal?.aborted) {
|
|
524
|
-
throw new Error("Unit of Work execution aborted");
|
|
525
|
-
}
|
|
765
|
+
const allServiceCallTxResults = serviceCalls ? collectAllTxResults([...serviceCalls]) : [];
|
|
526
766
|
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
767
|
+
await baseUow.executeRetrieve();
|
|
768
|
+
|
|
769
|
+
// Get retrieve results from TypedUnitOfWork's retrievalPhase or default to empty array
|
|
770
|
+
const retrieveResult: TRetrieveResults = typedUowFromRetrieve
|
|
771
|
+
? await typedUowFromRetrieve.retrievalPhase
|
|
772
|
+
: ([] as unknown as TRetrieveResults);
|
|
773
|
+
|
|
774
|
+
for (const txResult of allServiceCallTxResults) {
|
|
775
|
+
await processTxResultAfterRetrieve(txResult, baseUow);
|
|
531
776
|
}
|
|
532
777
|
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
778
|
+
const serviceResults: unknown[] = [];
|
|
779
|
+
if (serviceCalls) {
|
|
780
|
+
for (const serviceCall of serviceCalls) {
|
|
781
|
+
if (serviceCall === undefined) {
|
|
782
|
+
serviceResults.push(undefined);
|
|
783
|
+
continue;
|
|
784
|
+
}
|
|
785
|
+
const serviceCallInternal = serviceCall._internal;
|
|
786
|
+
// Check if this is a mutate-only service call (empty array sentinel with mutate callback)
|
|
787
|
+
// In that case, prefer mutateResult over the empty array retrieveSuccessResult
|
|
788
|
+
if (
|
|
789
|
+
serviceCallInternal.retrieveSuccessResult !== undefined &&
|
|
790
|
+
!(
|
|
791
|
+
Array.isArray(serviceCallInternal.retrieveSuccessResult) &&
|
|
792
|
+
serviceCallInternal.retrieveSuccessResult.length === 0 &&
|
|
793
|
+
serviceCallInternal.callbacks.mutate
|
|
794
|
+
)
|
|
795
|
+
) {
|
|
796
|
+
serviceResults.push(serviceCallInternal.retrieveSuccessResult);
|
|
797
|
+
} else if (serviceCallInternal.mutateResult !== undefined) {
|
|
798
|
+
serviceResults.push(serviceCallInternal.mutateResult);
|
|
799
|
+
} else {
|
|
800
|
+
serviceResults.push(serviceCallInternal.retrieveSuccessResult);
|
|
801
|
+
}
|
|
537
802
|
}
|
|
538
|
-
throw new Error("Unit of Work execution failed: optimistic concurrency conflict", {
|
|
539
|
-
cause: error,
|
|
540
|
-
});
|
|
541
803
|
}
|
|
542
804
|
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
805
|
+
// Call retrieveSuccess if provided
|
|
806
|
+
let retrieveSuccessResult: TRetrieveSuccessResult;
|
|
807
|
+
if (callbacks.retrieveSuccess) {
|
|
808
|
+
retrieveSuccessResult = callbacks.retrieveSuccess(
|
|
809
|
+
retrieveResult,
|
|
810
|
+
serviceResults as ExtractServiceRetrieveResults<TServiceCalls>,
|
|
811
|
+
);
|
|
812
|
+
} else {
|
|
813
|
+
retrieveSuccessResult = retrieveResult as unknown as TRetrieveSuccessResult;
|
|
546
814
|
}
|
|
547
815
|
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
* @example
|
|
562
|
-
* ```ts
|
|
563
|
-
* const [result1, result2] = await executeTxArray(
|
|
564
|
-
* () => [
|
|
565
|
-
* executeServiceTx(schema, callbacks1, uow),
|
|
566
|
-
* executeServiceTx(schema, callbacks2, uow)
|
|
567
|
-
* ],
|
|
568
|
-
* { createUnitOfWork }
|
|
569
|
-
* );
|
|
570
|
-
* ```
|
|
571
|
-
*/
|
|
572
|
-
export async function executeTxArray<T extends readonly unknown[]>(
|
|
573
|
-
servicesFactory: () => readonly [...{ [K in keyof T]: Promise<T[K]> }],
|
|
574
|
-
options: ExecuteRestrictedUnitOfWorkOptions,
|
|
575
|
-
): Promise<{ [K in keyof T]: T[K] }> {
|
|
576
|
-
const retryPolicy =
|
|
577
|
-
options.retryPolicy ??
|
|
578
|
-
new ExponentialBackoffRetryPolicy({
|
|
579
|
-
maxRetries: 5,
|
|
580
|
-
initialDelayMs: 10,
|
|
581
|
-
maxDelayMs: 100,
|
|
582
|
-
});
|
|
583
|
-
const signal = options.signal;
|
|
584
|
-
let attempt = 0;
|
|
585
|
-
|
|
586
|
-
while (true) {
|
|
587
|
-
// Check if aborted before starting attempt
|
|
588
|
-
if (signal?.aborted) {
|
|
589
|
-
throw new Error("Unit of Work execution aborted");
|
|
590
|
-
}
|
|
591
|
-
|
|
592
|
-
try {
|
|
593
|
-
// Create a fresh UOW for this attempt
|
|
594
|
-
const baseUow = options.createUnitOfWork();
|
|
595
|
-
|
|
596
|
-
// Call factory to create fresh service promises for this attempt
|
|
597
|
-
const services = servicesFactory();
|
|
598
|
-
|
|
599
|
-
await baseUow.executeRetrieve();
|
|
816
|
+
let mutateResult: TMutateResult | undefined;
|
|
817
|
+
if (callbacks.mutate) {
|
|
818
|
+
const mutateCtx: HandlerTxMutateContext<
|
|
819
|
+
TRetrieveSuccessResult,
|
|
820
|
+
ExtractServiceRetrieveResults<TServiceCalls>,
|
|
821
|
+
THooks
|
|
822
|
+
> = {
|
|
823
|
+
...context,
|
|
824
|
+
retrieveResult: retrieveSuccessResult,
|
|
825
|
+
serviceIntermediateResult: serviceResults as ExtractServiceRetrieveResults<TServiceCalls>,
|
|
826
|
+
};
|
|
827
|
+
mutateResult = callbacks.mutate(mutateCtx);
|
|
828
|
+
}
|
|
600
829
|
|
|
601
830
|
if (options.onBeforeMutate) {
|
|
602
831
|
options.onBeforeMutate(baseUow);
|
|
603
832
|
}
|
|
604
|
-
|
|
605
833
|
const result = await baseUow.executeMutations();
|
|
606
834
|
if (!result.success) {
|
|
607
835
|
throw new ConcurrencyConflictError();
|
|
608
836
|
}
|
|
609
837
|
|
|
610
|
-
|
|
611
|
-
|
|
838
|
+
// Process each serviceCall TxResult's success callback
|
|
839
|
+
for (const txResult of allServiceCallTxResults) {
|
|
840
|
+
await processTxResultAfterMutate(txResult);
|
|
612
841
|
}
|
|
613
842
|
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
843
|
+
const serviceFinalResults: unknown[] = [];
|
|
844
|
+
if (serviceCalls) {
|
|
845
|
+
for (const serviceCall of serviceCalls) {
|
|
846
|
+
if (serviceCall === undefined) {
|
|
847
|
+
serviceFinalResults.push(undefined);
|
|
848
|
+
continue;
|
|
849
|
+
}
|
|
850
|
+
serviceFinalResults.push(serviceCall._internal.finalResult);
|
|
851
|
+
}
|
|
852
|
+
}
|
|
853
|
+
|
|
854
|
+
let finalResult: unknown;
|
|
855
|
+
if (callbacks.success) {
|
|
856
|
+
// The success context type is determined by the overload - we construct it at runtime
|
|
857
|
+
// and the type safety is guaranteed by the discriminated overloads
|
|
858
|
+
const successCtx = {
|
|
859
|
+
retrieveResult: retrieveSuccessResult,
|
|
860
|
+
mutateResult,
|
|
861
|
+
serviceResult: serviceFinalResults as ExtractServiceFinalResults<TServiceCalls>,
|
|
862
|
+
serviceIntermediateResult: serviceResults as ExtractServiceRetrieveResults<TServiceCalls>,
|
|
863
|
+
} as Parameters<NonNullable<typeof callbacks.success>>[0];
|
|
864
|
+
finalResult = callbacks.success(successCtx);
|
|
865
|
+
} else if (callbacks.mutate) {
|
|
866
|
+
finalResult = await awaitPromisesInObject(mutateResult);
|
|
867
|
+
} else if (callbacks.retrieveSuccess || callbacks.retrieve) {
|
|
868
|
+
finalResult = retrieveSuccessResult;
|
|
869
|
+
} else {
|
|
870
|
+
finalResult = serviceFinalResults;
|
|
871
|
+
}
|
|
872
|
+
|
|
873
|
+
if (options.onAfterMutate) {
|
|
874
|
+
await options.onAfterMutate(baseUow);
|
|
875
|
+
}
|
|
876
|
+
|
|
877
|
+
return await awaitPromisesInObject(finalResult);
|
|
617
878
|
} catch (error) {
|
|
618
879
|
if (signal?.aborted) {
|
|
619
|
-
throw new Error("
|
|
880
|
+
throw new Error("Transaction execution aborted");
|
|
620
881
|
}
|
|
621
882
|
|
|
622
883
|
// Only retry concurrency conflicts, not other errors
|
|
@@ -626,11 +887,9 @@ export async function executeTxArray<T extends readonly unknown[]>(
|
|
|
626
887
|
|
|
627
888
|
if (!retryPolicy.shouldRetry(attempt, error, signal)) {
|
|
628
889
|
if (signal?.aborted) {
|
|
629
|
-
throw new Error("
|
|
890
|
+
throw new Error("Transaction execution aborted");
|
|
630
891
|
}
|
|
631
|
-
throw new
|
|
632
|
-
cause: error,
|
|
633
|
-
});
|
|
892
|
+
throw new ConcurrencyConflictError();
|
|
634
893
|
}
|
|
635
894
|
|
|
636
895
|
const delayMs = retryPolicy.getDelayMs(attempt);
|
|
@@ -644,169 +903,1005 @@ export async function executeTxArray<T extends readonly unknown[]>(
|
|
|
644
903
|
}
|
|
645
904
|
|
|
646
905
|
/**
|
|
647
|
-
*
|
|
648
|
-
*
|
|
649
|
-
*
|
|
650
|
-
* @param callbacks - Object containing retrieve and mutate callbacks
|
|
651
|
-
* @param options - Configuration including UOW factory, retry policy, and abort signal
|
|
652
|
-
* @returns Promise resolving to the mutation result with promises awaited 1 level deep
|
|
906
|
+
* Error thrown when a Unit of Work execution fails due to optimistic concurrency conflict.
|
|
907
|
+
* This error triggers automatic retry behavior in executeTx.
|
|
653
908
|
*/
|
|
654
|
-
export
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
options: ExecuteRestrictedUnitOfWorkOptions,
|
|
661
|
-
): Promise<AwaitedPromisesInObject<TMutationResult>> {
|
|
662
|
-
const retryPolicy =
|
|
663
|
-
options.retryPolicy ??
|
|
664
|
-
new ExponentialBackoffRetryPolicy({
|
|
665
|
-
maxRetries: 5,
|
|
666
|
-
initialDelayMs: 10,
|
|
667
|
-
maxDelayMs: 100,
|
|
668
|
-
});
|
|
669
|
-
const signal = options.signal;
|
|
670
|
-
let attempt = 0;
|
|
909
|
+
export class ConcurrencyConflictError extends Error {
|
|
910
|
+
constructor(message = "Optimistic concurrency conflict detected") {
|
|
911
|
+
super(message);
|
|
912
|
+
this.name = "ConcurrencyConflictError";
|
|
913
|
+
}
|
|
914
|
+
}
|
|
671
915
|
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
916
|
+
/**
|
|
917
|
+
* Type utility that unwraps promises 1 level deep in objects, arrays, or direct promises
|
|
918
|
+
* Handles tuples, arrays, objects, and direct promises
|
|
919
|
+
*/
|
|
920
|
+
export type AwaitedPromisesInObject<T> =
|
|
921
|
+
// First check if it's a Promise
|
|
922
|
+
T extends Promise<infer U>
|
|
923
|
+
? Awaited<U>
|
|
924
|
+
: // Check for arrays with known length (tuples) - preserves tuple structure
|
|
925
|
+
T extends readonly [unknown, ...unknown[]]
|
|
926
|
+
? { [K in keyof T]: AwaitedPromisesInObject<T[K]> }
|
|
927
|
+
: T extends [unknown, ...unknown[]]
|
|
928
|
+
? { [K in keyof T]: AwaitedPromisesInObject<T[K]> }
|
|
929
|
+
: // Check for regular arrays (unknown length)
|
|
930
|
+
T extends (infer U)[]
|
|
931
|
+
? Awaited<U>[]
|
|
932
|
+
: T extends readonly (infer U)[]
|
|
933
|
+
? readonly Awaited<U>[]
|
|
934
|
+
: // Check for objects
|
|
935
|
+
T extends Record<string, unknown>
|
|
936
|
+
? {
|
|
937
|
+
[K in keyof T]: T[K] extends Promise<infer U> ? Awaited<U> : T[K];
|
|
938
|
+
}
|
|
939
|
+
: // Otherwise return as-is
|
|
940
|
+
T;
|
|
677
941
|
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
942
|
+
/**
|
|
943
|
+
* Await promises in an object 1 level deep
|
|
944
|
+
*/
|
|
945
|
+
async function awaitPromisesInObject<T>(obj: T): Promise<AwaitedPromisesInObject<T>> {
|
|
946
|
+
if (obj === null || obj === undefined) {
|
|
947
|
+
return obj as AwaitedPromisesInObject<T>;
|
|
948
|
+
}
|
|
681
949
|
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
},
|
|
686
|
-
};
|
|
950
|
+
if (typeof obj !== "object") {
|
|
951
|
+
return obj as AwaitedPromisesInObject<T>;
|
|
952
|
+
}
|
|
687
953
|
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
retrieveResult = undefined as TRetrieveResult;
|
|
693
|
-
}
|
|
954
|
+
// Check if it's a Promise
|
|
955
|
+
if (obj instanceof Promise) {
|
|
956
|
+
return (await obj) as AwaitedPromisesInObject<T>;
|
|
957
|
+
}
|
|
694
958
|
|
|
695
|
-
|
|
959
|
+
// Check if it's an array
|
|
960
|
+
if (Array.isArray(obj)) {
|
|
961
|
+
const awaited = await Promise.all(
|
|
962
|
+
obj.map((item) => (item instanceof Promise ? item : Promise.resolve(item))),
|
|
963
|
+
);
|
|
964
|
+
return awaited as AwaitedPromisesInObject<T>;
|
|
965
|
+
}
|
|
696
966
|
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
967
|
+
if (obj.constructor !== Object) {
|
|
968
|
+
return obj as AwaitedPromisesInObject<T>;
|
|
969
|
+
}
|
|
970
|
+
const result = {} as T;
|
|
971
|
+
const entries = Object.entries(obj as Record<string, unknown>);
|
|
972
|
+
const awaitedEntries = await Promise.all(
|
|
973
|
+
entries.map(async ([key, value]) => {
|
|
974
|
+
const awaitedValue = value instanceof Promise ? await value : value;
|
|
975
|
+
return [key, awaitedValue] as const;
|
|
976
|
+
}),
|
|
977
|
+
);
|
|
703
978
|
|
|
704
|
-
|
|
979
|
+
for (const [key, value] of awaitedEntries) {
|
|
980
|
+
(result as Record<string, unknown>)[key] = value;
|
|
981
|
+
}
|
|
705
982
|
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
}
|
|
983
|
+
return result as AwaitedPromisesInObject<T>;
|
|
984
|
+
}
|
|
709
985
|
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
}
|
|
986
|
+
// ============================================================================
|
|
987
|
+
// Builder Pattern Types and Classes
|
|
988
|
+
// ============================================================================
|
|
714
989
|
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
990
|
+
/**
|
|
991
|
+
* Context passed to service-level mutate callback in builder pattern.
|
|
992
|
+
*/
|
|
993
|
+
export interface ServiceBuilderMutateContext<
|
|
994
|
+
TSchema extends AnySchema,
|
|
995
|
+
TRetrieveSuccessResult,
|
|
996
|
+
TServiceResult extends readonly unknown[],
|
|
997
|
+
THooks extends HooksMap,
|
|
998
|
+
> {
|
|
999
|
+
/** Unit of work for scheduling mutations */
|
|
1000
|
+
uow: TypedUnitOfWork<TSchema, [], unknown, THooks>;
|
|
1001
|
+
/** Result from transformRetrieve callback (or raw retrieve results if no transformRetrieve) */
|
|
1002
|
+
retrieveResult: TRetrieveSuccessResult;
|
|
1003
|
+
/** Array of retrieve success results from service calls (intermediate results, not final: retrieve results if service has retrieve, mutate result if service only mutates) */
|
|
1004
|
+
serviceIntermediateResult: TServiceResult;
|
|
1005
|
+
}
|
|
718
1006
|
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
1007
|
+
/**
|
|
1008
|
+
* Context passed to handler-level mutate callback in builder pattern.
|
|
1009
|
+
*/
|
|
1010
|
+
export interface HandlerBuilderMutateContext<
|
|
1011
|
+
TRetrieveSuccessResult,
|
|
1012
|
+
TServiceResult extends readonly unknown[],
|
|
1013
|
+
THooks extends HooksMap,
|
|
1014
|
+
> {
|
|
1015
|
+
/** Get a typed Unit of Work for the given schema */
|
|
1016
|
+
forSchema: <S extends AnySchema, H extends HooksMap = THooks>(
|
|
1017
|
+
schema: S,
|
|
1018
|
+
hooks?: H,
|
|
1019
|
+
) => TypedUnitOfWork<S, [], unknown, H>;
|
|
1020
|
+
/** Unique key for this transaction (for idempotency/deduplication) */
|
|
1021
|
+
idempotencyKey: string;
|
|
1022
|
+
/** Current attempt number (0-based) */
|
|
1023
|
+
currentAttempt: number;
|
|
1024
|
+
/** Result from transformRetrieve callback (or raw retrieve results if no transformRetrieve) */
|
|
1025
|
+
retrieveResult: TRetrieveSuccessResult;
|
|
1026
|
+
/** Array of retrieve success results from service calls (intermediate results, not final: retrieve results if service has retrieve, mutate result if service only mutates) */
|
|
1027
|
+
serviceIntermediateResult: TServiceResult;
|
|
1028
|
+
}
|
|
724
1029
|
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
1030
|
+
/**
|
|
1031
|
+
* Context passed to transform callback when mutate IS provided.
|
|
1032
|
+
*/
|
|
1033
|
+
export interface BuilderTransformContextWithMutate<
|
|
1034
|
+
TRetrieveSuccessResult,
|
|
1035
|
+
TMutateResult,
|
|
1036
|
+
TServiceFinalResult extends readonly unknown[],
|
|
1037
|
+
TServiceIntermediateResult extends readonly unknown[],
|
|
1038
|
+
> {
|
|
1039
|
+
/** Result from transformRetrieve callback (or raw retrieve results if no transformRetrieve) */
|
|
1040
|
+
retrieveResult: TRetrieveSuccessResult;
|
|
1041
|
+
/** Result from mutate callback */
|
|
1042
|
+
mutateResult: TMutateResult;
|
|
1043
|
+
/** Array of final results from service calls (after success/transform callbacks) */
|
|
1044
|
+
serviceResult: TServiceFinalResult;
|
|
1045
|
+
/** Array of retrieve success results from service calls (same as what mutate receives: retrieve results if service has retrieve, mutate result if service only mutates) */
|
|
1046
|
+
serviceIntermediateResult: TServiceIntermediateResult;
|
|
1047
|
+
}
|
|
729
1048
|
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
1049
|
+
/**
|
|
1050
|
+
* Context passed to transform callback when mutate is NOT provided.
|
|
1051
|
+
*/
|
|
1052
|
+
export interface BuilderTransformContextWithoutMutate<
|
|
1053
|
+
TRetrieveSuccessResult,
|
|
1054
|
+
TServiceFinalResult extends readonly unknown[],
|
|
1055
|
+
TServiceIntermediateResult extends readonly unknown[],
|
|
1056
|
+
> {
|
|
1057
|
+
/** Result from transformRetrieve callback (or raw retrieve results if no transformRetrieve) */
|
|
1058
|
+
retrieveResult: TRetrieveSuccessResult;
|
|
1059
|
+
/** No mutate callback was provided */
|
|
1060
|
+
mutateResult: undefined;
|
|
1061
|
+
/** Array of final results from service calls (after success/transform callbacks) */
|
|
1062
|
+
serviceResult: TServiceFinalResult;
|
|
1063
|
+
/** Array of retrieve success results from service calls (same as what mutate receives: retrieve results if service has retrieve, mutate result if service only mutates) */
|
|
1064
|
+
serviceIntermediateResult: TServiceIntermediateResult;
|
|
1065
|
+
}
|
|
738
1066
|
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
1067
|
+
/**
|
|
1068
|
+
* Infer the final result type from builder state:
|
|
1069
|
+
* 1. transform → TTransformResult
|
|
1070
|
+
* 2. mutate → AwaitedPromisesInObject<TMutateResult>
|
|
1071
|
+
* 3. transformRetrieve → TRetrieveSuccessResult
|
|
1072
|
+
* 4. retrieve → TRetrieveResults
|
|
1073
|
+
* 5. withServiceCalls → ExtractServiceFinalResults<TServiceCalls>
|
|
1074
|
+
*/
|
|
1075
|
+
export type InferBuilderResultType<
|
|
1076
|
+
TRetrieveResults extends unknown[],
|
|
1077
|
+
TRetrieveSuccessResult,
|
|
1078
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1079
|
+
TServiceCalls extends readonly (TxResult<any, any> | undefined)[],
|
|
1080
|
+
TMutateResult,
|
|
1081
|
+
TTransformResult,
|
|
1082
|
+
HasTransform extends boolean,
|
|
1083
|
+
HasMutate extends boolean,
|
|
1084
|
+
HasTransformRetrieve extends boolean,
|
|
1085
|
+
HasRetrieve extends boolean,
|
|
1086
|
+
> = HasTransform extends true
|
|
1087
|
+
? TTransformResult
|
|
1088
|
+
: HasMutate extends true
|
|
1089
|
+
? AwaitedPromisesInObject<TMutateResult>
|
|
1090
|
+
: HasTransformRetrieve extends true
|
|
1091
|
+
? TRetrieveSuccessResult
|
|
1092
|
+
: HasRetrieve extends true
|
|
1093
|
+
? TRetrieveResults
|
|
1094
|
+
: ExtractServiceFinalResults<TServiceCalls>;
|
|
743
1095
|
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
1096
|
+
/**
|
|
1097
|
+
* Infer the retrieve success result type for the builder:
|
|
1098
|
+
* - If transformRetrieve exists: TRetrieveSuccessResult
|
|
1099
|
+
* - Else if retrieve exists: TRetrieveResults (raw retrieve results)
|
|
1100
|
+
* - Else if mutate exists: AwaitedPromisesInObject<TMutateResult>
|
|
1101
|
+
* (mutate result becomes retrieve result for dependents)
|
|
1102
|
+
* - Else: TRetrieveResults (raw retrieve results, typically [])
|
|
1103
|
+
*/
|
|
1104
|
+
export type InferBuilderRetrieveSuccessResult<
|
|
1105
|
+
TRetrieveResults extends unknown[],
|
|
1106
|
+
TRetrieveSuccessResult,
|
|
1107
|
+
TMutateResult,
|
|
1108
|
+
HasTransformRetrieve extends boolean,
|
|
1109
|
+
HasRetrieve extends boolean,
|
|
1110
|
+
HasMutate extends boolean,
|
|
1111
|
+
> = HasTransformRetrieve extends true
|
|
1112
|
+
? TRetrieveSuccessResult
|
|
1113
|
+
: HasRetrieve extends true
|
|
1114
|
+
? TRetrieveResults
|
|
1115
|
+
: HasMutate extends true
|
|
1116
|
+
? AwaitedPromisesInObject<TMutateResult>
|
|
1117
|
+
: TRetrieveResults;
|
|
1118
|
+
|
|
1119
|
+
/**
|
|
1120
|
+
* Internal state for ServiceTxBuilder
|
|
1121
|
+
*/
|
|
1122
|
+
interface ServiceTxBuilderState<
|
|
1123
|
+
TSchema extends AnySchema,
|
|
1124
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1125
|
+
TServiceCalls extends readonly (TxResult<any, any> | undefined)[],
|
|
1126
|
+
TRetrieveResults extends unknown[],
|
|
1127
|
+
TRetrieveSuccessResult,
|
|
1128
|
+
TMutateResult,
|
|
1129
|
+
TTransformResult,
|
|
1130
|
+
THooks extends HooksMap,
|
|
1131
|
+
> {
|
|
1132
|
+
schema: TSchema;
|
|
1133
|
+
baseUow: IUnitOfWork;
|
|
1134
|
+
hooks?: THooks;
|
|
1135
|
+
withServiceCallsFn?: () => TServiceCalls;
|
|
1136
|
+
retrieveFn?: (
|
|
1137
|
+
uow: TypedUnitOfWork<TSchema, [], unknown, THooks>,
|
|
1138
|
+
) => TypedUnitOfWork<TSchema, TRetrieveResults, unknown, THooks>;
|
|
1139
|
+
transformRetrieveFn?: (
|
|
1140
|
+
retrieveResult: TRetrieveResults,
|
|
1141
|
+
serviceRetrieveResult: ExtractServiceRetrieveResults<TServiceCalls>,
|
|
1142
|
+
) => TRetrieveSuccessResult;
|
|
1143
|
+
mutateFn?: (
|
|
1144
|
+
ctx: ServiceBuilderMutateContext<
|
|
1145
|
+
TSchema,
|
|
1146
|
+
TRetrieveSuccessResult,
|
|
1147
|
+
ExtractServiceRetrieveResults<TServiceCalls>,
|
|
1148
|
+
THooks
|
|
1149
|
+
>,
|
|
1150
|
+
) => TMutateResult;
|
|
1151
|
+
transformFn?: (
|
|
1152
|
+
ctx:
|
|
1153
|
+
| BuilderTransformContextWithMutate<
|
|
1154
|
+
TRetrieveSuccessResult,
|
|
1155
|
+
TMutateResult,
|
|
1156
|
+
ExtractServiceFinalResults<TServiceCalls>,
|
|
1157
|
+
ExtractServiceRetrieveResults<TServiceCalls>
|
|
1158
|
+
>
|
|
1159
|
+
| BuilderTransformContextWithoutMutate<
|
|
1160
|
+
TRetrieveSuccessResult,
|
|
1161
|
+
ExtractServiceFinalResults<TServiceCalls>,
|
|
1162
|
+
ExtractServiceRetrieveResults<TServiceCalls>
|
|
1163
|
+
>,
|
|
1164
|
+
) => TTransformResult;
|
|
747
1165
|
}
|
|
748
1166
|
|
|
749
1167
|
/**
|
|
750
|
-
*
|
|
751
|
-
*
|
|
1168
|
+
* Builder for service-level transactions.
|
|
1169
|
+
* Uses a fluent API to build up transaction callbacks with proper type inference.
|
|
752
1170
|
*
|
|
753
|
-
* @
|
|
754
|
-
*
|
|
755
|
-
*
|
|
756
|
-
*
|
|
1171
|
+
* @example
|
|
1172
|
+
* ```ts
|
|
1173
|
+
* return serviceTx(schema)
|
|
1174
|
+
* .withServiceCalls(() => [otherService.getData()])
|
|
1175
|
+
* .retrieve((uow) => uow.find("users", ...))
|
|
1176
|
+
* .transformRetrieve(([users], serviceResult) => users[0])
|
|
1177
|
+
* .mutate(({ uow, retrieveResult, serviceIntermediateResult }) =>
|
|
1178
|
+
* uow.create("records", { ... })
|
|
1179
|
+
* )
|
|
1180
|
+
* .transform(({ mutateResult, serviceResult, serviceIntermediateResult }) => ({ id: mutateResult }))
|
|
1181
|
+
* .build();
|
|
1182
|
+
* ```
|
|
757
1183
|
*/
|
|
758
|
-
export
|
|
1184
|
+
export class ServiceTxBuilder<
|
|
759
1185
|
TSchema extends AnySchema,
|
|
760
|
-
|
|
761
|
-
|
|
1186
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1187
|
+
TServiceCalls extends readonly (TxResult<any, any> | undefined)[],
|
|
1188
|
+
TRetrieveResults extends unknown[],
|
|
1189
|
+
TRetrieveSuccessResult,
|
|
1190
|
+
TMutateResult,
|
|
1191
|
+
TTransformResult,
|
|
1192
|
+
HasRetrieve extends boolean,
|
|
1193
|
+
HasTransformRetrieve extends boolean,
|
|
1194
|
+
HasMutate extends boolean,
|
|
1195
|
+
HasTransform extends boolean,
|
|
762
1196
|
THooks extends HooksMap,
|
|
763
|
-
>
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
1197
|
+
> {
|
|
1198
|
+
readonly #state: ServiceTxBuilderState<
|
|
1199
|
+
TSchema,
|
|
1200
|
+
TServiceCalls,
|
|
1201
|
+
TRetrieveResults,
|
|
1202
|
+
TRetrieveSuccessResult,
|
|
1203
|
+
TMutateResult,
|
|
1204
|
+
TTransformResult,
|
|
1205
|
+
THooks
|
|
1206
|
+
>;
|
|
1207
|
+
|
|
1208
|
+
constructor(
|
|
1209
|
+
state: ServiceTxBuilderState<
|
|
1210
|
+
TSchema,
|
|
1211
|
+
TServiceCalls,
|
|
1212
|
+
TRetrieveResults,
|
|
1213
|
+
TRetrieveSuccessResult,
|
|
1214
|
+
TMutateResult,
|
|
1215
|
+
TTransformResult,
|
|
1216
|
+
THooks
|
|
1217
|
+
>,
|
|
1218
|
+
) {
|
|
1219
|
+
this.#state = state;
|
|
1220
|
+
}
|
|
769
1221
|
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
1222
|
+
/**
|
|
1223
|
+
* Add dependencies to execute before this transaction.
|
|
1224
|
+
*/
|
|
1225
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1226
|
+
withServiceCalls<TNewDeps extends readonly (TxResult<any, any> | undefined)[]>(
|
|
1227
|
+
fn: () => TNewDeps,
|
|
1228
|
+
): ServiceTxBuilder<
|
|
1229
|
+
TSchema,
|
|
1230
|
+
TNewDeps,
|
|
1231
|
+
TRetrieveResults,
|
|
1232
|
+
TRetrieveSuccessResult,
|
|
1233
|
+
TMutateResult,
|
|
1234
|
+
TTransformResult,
|
|
1235
|
+
HasRetrieve,
|
|
1236
|
+
HasTransformRetrieve,
|
|
1237
|
+
HasMutate,
|
|
1238
|
+
HasTransform,
|
|
1239
|
+
THooks
|
|
1240
|
+
> {
|
|
1241
|
+
return new ServiceTxBuilder({
|
|
1242
|
+
...this.#state,
|
|
1243
|
+
withServiceCallsFn: fn,
|
|
1244
|
+
} as ServiceTxBuilderState<
|
|
1245
|
+
TSchema,
|
|
1246
|
+
TNewDeps,
|
|
1247
|
+
TRetrieveResults,
|
|
1248
|
+
TRetrieveSuccessResult,
|
|
1249
|
+
TMutateResult,
|
|
1250
|
+
TTransformResult,
|
|
1251
|
+
THooks
|
|
1252
|
+
>);
|
|
1253
|
+
}
|
|
1254
|
+
|
|
1255
|
+
/**
|
|
1256
|
+
* Add retrieval operations to the transaction.
|
|
1257
|
+
*/
|
|
1258
|
+
retrieve<TNewRetrieveResults extends unknown[]>(
|
|
1259
|
+
fn: (
|
|
1260
|
+
uow: TypedUnitOfWork<TSchema, [], unknown, THooks>,
|
|
1261
|
+
) => TypedUnitOfWork<TSchema, TNewRetrieveResults, unknown, THooks>,
|
|
1262
|
+
): ServiceTxBuilder<
|
|
1263
|
+
TSchema,
|
|
1264
|
+
TServiceCalls,
|
|
1265
|
+
TNewRetrieveResults,
|
|
1266
|
+
TNewRetrieveResults, // Default TRetrieveSuccessResult to TNewRetrieveResults
|
|
1267
|
+
TMutateResult,
|
|
1268
|
+
TTransformResult,
|
|
1269
|
+
true, // HasRetrieve = true
|
|
1270
|
+
false, // Reset HasTransformRetrieve since retrieve results changed
|
|
1271
|
+
HasMutate,
|
|
1272
|
+
HasTransform,
|
|
1273
|
+
THooks
|
|
1274
|
+
> {
|
|
1275
|
+
return new ServiceTxBuilder({
|
|
1276
|
+
...this.#state,
|
|
1277
|
+
retrieveFn: fn,
|
|
1278
|
+
transformRetrieveFn: undefined, // Clear any existing transformRetrieve since results shape changed
|
|
1279
|
+
} as unknown as ServiceTxBuilderState<
|
|
1280
|
+
TSchema,
|
|
1281
|
+
TServiceCalls,
|
|
1282
|
+
TNewRetrieveResults,
|
|
1283
|
+
TNewRetrieveResults,
|
|
1284
|
+
TMutateResult,
|
|
1285
|
+
TTransformResult,
|
|
1286
|
+
THooks
|
|
1287
|
+
>);
|
|
1288
|
+
}
|
|
1289
|
+
|
|
1290
|
+
/**
|
|
1291
|
+
* Transform retrieve results before passing to mutate.
|
|
1292
|
+
*/
|
|
1293
|
+
transformRetrieve<TNewRetrieveSuccessResult>(
|
|
1294
|
+
fn: (
|
|
1295
|
+
retrieveResult: TRetrieveResults,
|
|
1296
|
+
serviceResult: ExtractServiceRetrieveResults<TServiceCalls>,
|
|
1297
|
+
) => TNewRetrieveSuccessResult,
|
|
1298
|
+
): ServiceTxBuilder<
|
|
1299
|
+
TSchema,
|
|
1300
|
+
TServiceCalls,
|
|
1301
|
+
TRetrieveResults,
|
|
1302
|
+
TNewRetrieveSuccessResult,
|
|
1303
|
+
TMutateResult,
|
|
1304
|
+
TTransformResult,
|
|
1305
|
+
HasRetrieve,
|
|
1306
|
+
true, // HasTransformRetrieve = true
|
|
1307
|
+
HasMutate,
|
|
1308
|
+
HasTransform,
|
|
1309
|
+
THooks
|
|
1310
|
+
> {
|
|
1311
|
+
return new ServiceTxBuilder({
|
|
1312
|
+
...this.#state,
|
|
1313
|
+
transformRetrieveFn: fn,
|
|
1314
|
+
} as unknown as ServiceTxBuilderState<
|
|
1315
|
+
TSchema,
|
|
1316
|
+
TServiceCalls,
|
|
1317
|
+
TRetrieveResults,
|
|
1318
|
+
TNewRetrieveSuccessResult,
|
|
1319
|
+
TMutateResult,
|
|
1320
|
+
TTransformResult,
|
|
1321
|
+
THooks
|
|
1322
|
+
>);
|
|
1323
|
+
}
|
|
1324
|
+
|
|
1325
|
+
/**
|
|
1326
|
+
* Add mutation operations based on retrieve results.
|
|
1327
|
+
*/
|
|
1328
|
+
mutate<TNewMutateResult>(
|
|
1329
|
+
fn: (
|
|
1330
|
+
ctx: ServiceBuilderMutateContext<
|
|
777
1331
|
TSchema,
|
|
778
|
-
|
|
779
|
-
|
|
1332
|
+
HasTransformRetrieve extends true ? TRetrieveSuccessResult : TRetrieveResults,
|
|
1333
|
+
ExtractServiceRetrieveResults<TServiceCalls>,
|
|
780
1334
|
THooks
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
1335
|
+
>,
|
|
1336
|
+
) => TNewMutateResult,
|
|
1337
|
+
): ServiceTxBuilder<
|
|
1338
|
+
TSchema,
|
|
1339
|
+
TServiceCalls,
|
|
1340
|
+
TRetrieveResults,
|
|
1341
|
+
HasTransformRetrieve extends true ? TRetrieveSuccessResult : TRetrieveResults,
|
|
1342
|
+
TNewMutateResult,
|
|
1343
|
+
TTransformResult,
|
|
1344
|
+
HasRetrieve,
|
|
1345
|
+
HasTransformRetrieve,
|
|
1346
|
+
true, // HasMutate = true
|
|
1347
|
+
HasTransform,
|
|
1348
|
+
THooks
|
|
1349
|
+
> {
|
|
1350
|
+
return new ServiceTxBuilder({
|
|
1351
|
+
...this.#state,
|
|
1352
|
+
mutateFn: fn,
|
|
1353
|
+
} as unknown as ServiceTxBuilderState<
|
|
1354
|
+
TSchema,
|
|
1355
|
+
TServiceCalls,
|
|
1356
|
+
TRetrieveResults,
|
|
1357
|
+
HasTransformRetrieve extends true ? TRetrieveSuccessResult : TRetrieveResults,
|
|
1358
|
+
TNewMutateResult,
|
|
1359
|
+
TTransformResult,
|
|
1360
|
+
THooks
|
|
1361
|
+
>);
|
|
787
1362
|
}
|
|
788
1363
|
|
|
789
|
-
|
|
1364
|
+
/**
|
|
1365
|
+
* Add final transformation after mutations complete.
|
|
1366
|
+
*/
|
|
1367
|
+
transform<TNewTransformResult>(
|
|
1368
|
+
fn: (
|
|
1369
|
+
ctx: HasMutate extends true
|
|
1370
|
+
? BuilderTransformContextWithMutate<
|
|
1371
|
+
HasTransformRetrieve extends true ? TRetrieveSuccessResult : TRetrieveResults,
|
|
1372
|
+
TMutateResult,
|
|
1373
|
+
ExtractServiceFinalResults<TServiceCalls>,
|
|
1374
|
+
ExtractServiceRetrieveResults<TServiceCalls>
|
|
1375
|
+
>
|
|
1376
|
+
: BuilderTransformContextWithoutMutate<
|
|
1377
|
+
HasTransformRetrieve extends true ? TRetrieveSuccessResult : TRetrieveResults,
|
|
1378
|
+
ExtractServiceFinalResults<TServiceCalls>,
|
|
1379
|
+
ExtractServiceRetrieveResults<TServiceCalls>
|
|
1380
|
+
>,
|
|
1381
|
+
) => TNewTransformResult,
|
|
1382
|
+
): ServiceTxBuilder<
|
|
1383
|
+
TSchema,
|
|
1384
|
+
TServiceCalls,
|
|
1385
|
+
TRetrieveResults,
|
|
1386
|
+
HasTransformRetrieve extends true ? TRetrieveSuccessResult : TRetrieveResults,
|
|
1387
|
+
TMutateResult,
|
|
1388
|
+
TNewTransformResult,
|
|
1389
|
+
HasRetrieve,
|
|
1390
|
+
HasTransformRetrieve,
|
|
1391
|
+
HasMutate,
|
|
1392
|
+
true, // HasTransform = true
|
|
1393
|
+
THooks
|
|
1394
|
+
> {
|
|
1395
|
+
return new ServiceTxBuilder({
|
|
1396
|
+
...this.#state,
|
|
1397
|
+
transformFn: fn,
|
|
1398
|
+
} as unknown as ServiceTxBuilderState<
|
|
1399
|
+
TSchema,
|
|
1400
|
+
TServiceCalls,
|
|
1401
|
+
TRetrieveResults,
|
|
1402
|
+
HasTransformRetrieve extends true ? TRetrieveSuccessResult : TRetrieveResults,
|
|
1403
|
+
TMutateResult,
|
|
1404
|
+
TNewTransformResult,
|
|
1405
|
+
THooks
|
|
1406
|
+
>);
|
|
1407
|
+
}
|
|
790
1408
|
|
|
791
|
-
|
|
792
|
-
|
|
1409
|
+
/**
|
|
1410
|
+
* Build and return the TxResult.
|
|
1411
|
+
*/
|
|
1412
|
+
build(): TxResult<
|
|
1413
|
+
InferBuilderResultType<
|
|
1414
|
+
TRetrieveResults,
|
|
1415
|
+
TRetrieveSuccessResult,
|
|
1416
|
+
TServiceCalls,
|
|
1417
|
+
TMutateResult,
|
|
1418
|
+
TTransformResult,
|
|
1419
|
+
HasTransform,
|
|
1420
|
+
HasMutate,
|
|
1421
|
+
HasTransformRetrieve,
|
|
1422
|
+
HasRetrieve
|
|
1423
|
+
>,
|
|
1424
|
+
InferBuilderRetrieveSuccessResult<
|
|
1425
|
+
TRetrieveResults,
|
|
1426
|
+
TRetrieveSuccessResult,
|
|
1427
|
+
TMutateResult,
|
|
1428
|
+
HasTransformRetrieve,
|
|
1429
|
+
HasRetrieve,
|
|
1430
|
+
HasMutate
|
|
1431
|
+
>
|
|
1432
|
+
> {
|
|
1433
|
+
const state = this.#state;
|
|
1434
|
+
|
|
1435
|
+
// Convert builder state to legacy callbacks format
|
|
1436
|
+
const callbacks: ServiceTxCallbacks<
|
|
1437
|
+
TSchema,
|
|
1438
|
+
TRetrieveResults,
|
|
1439
|
+
TRetrieveSuccessResult,
|
|
1440
|
+
TServiceCalls,
|
|
1441
|
+
TMutateResult,
|
|
1442
|
+
TTransformResult,
|
|
1443
|
+
THooks
|
|
1444
|
+
> = {
|
|
1445
|
+
serviceCalls: state.withServiceCallsFn,
|
|
1446
|
+
retrieve: state.retrieveFn,
|
|
1447
|
+
retrieveSuccess: state.transformRetrieveFn,
|
|
1448
|
+
mutate: state.mutateFn
|
|
1449
|
+
? (ctx) => {
|
|
1450
|
+
return state.mutateFn!({
|
|
1451
|
+
uow: ctx.uow,
|
|
1452
|
+
retrieveResult: ctx.retrieveResult,
|
|
1453
|
+
serviceIntermediateResult: ctx.serviceIntermediateResult,
|
|
1454
|
+
});
|
|
1455
|
+
}
|
|
1456
|
+
: undefined,
|
|
1457
|
+
success: state.transformFn
|
|
1458
|
+
? (ctx) => {
|
|
1459
|
+
return state.transformFn!({
|
|
1460
|
+
retrieveResult: ctx.retrieveResult,
|
|
1461
|
+
mutateResult: ctx.mutateResult,
|
|
1462
|
+
serviceResult: ctx.serviceResult,
|
|
1463
|
+
serviceIntermediateResult: ctx.serviceIntermediateResult,
|
|
1464
|
+
} as BuilderTransformContextWithMutate<
|
|
1465
|
+
TRetrieveSuccessResult,
|
|
1466
|
+
TMutateResult,
|
|
1467
|
+
ExtractServiceFinalResults<TServiceCalls>,
|
|
1468
|
+
ExtractServiceRetrieveResults<TServiceCalls>
|
|
1469
|
+
>);
|
|
1470
|
+
}
|
|
1471
|
+
: undefined,
|
|
1472
|
+
};
|
|
793
1473
|
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
1474
|
+
// Use the existing createServiceTx implementation
|
|
1475
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1476
|
+
return createServiceTx(state.schema, callbacks as any, state.baseUow) as unknown as TxResult<
|
|
1477
|
+
InferBuilderResultType<
|
|
1478
|
+
TRetrieveResults,
|
|
1479
|
+
TRetrieveSuccessResult,
|
|
1480
|
+
TServiceCalls,
|
|
1481
|
+
TMutateResult,
|
|
1482
|
+
TTransformResult,
|
|
1483
|
+
HasTransform,
|
|
1484
|
+
HasMutate,
|
|
1485
|
+
HasTransformRetrieve,
|
|
1486
|
+
HasRetrieve
|
|
1487
|
+
>,
|
|
1488
|
+
InferBuilderRetrieveSuccessResult<
|
|
1489
|
+
TRetrieveResults,
|
|
1490
|
+
TRetrieveSuccessResult,
|
|
1491
|
+
TMutateResult,
|
|
1492
|
+
HasTransformRetrieve,
|
|
1493
|
+
HasRetrieve,
|
|
1494
|
+
HasMutate
|
|
1495
|
+
>
|
|
1496
|
+
>;
|
|
805
1497
|
}
|
|
1498
|
+
}
|
|
806
1499
|
|
|
807
|
-
|
|
1500
|
+
/**
|
|
1501
|
+
* Create a new ServiceTxBuilder for the given schema.
|
|
1502
|
+
*/
|
|
1503
|
+
export function createServiceTxBuilder<TSchema extends AnySchema, THooks extends HooksMap = {}>(
|
|
1504
|
+
schema: TSchema,
|
|
1505
|
+
baseUow: IUnitOfWork,
|
|
1506
|
+
hooks?: THooks,
|
|
1507
|
+
): ServiceTxBuilder<
|
|
1508
|
+
TSchema,
|
|
1509
|
+
readonly [],
|
|
1510
|
+
[],
|
|
1511
|
+
[],
|
|
1512
|
+
unknown,
|
|
1513
|
+
unknown,
|
|
1514
|
+
false,
|
|
1515
|
+
false,
|
|
1516
|
+
false,
|
|
1517
|
+
false,
|
|
1518
|
+
THooks
|
|
1519
|
+
> {
|
|
1520
|
+
return new ServiceTxBuilder({
|
|
1521
|
+
schema,
|
|
1522
|
+
baseUow,
|
|
1523
|
+
hooks,
|
|
1524
|
+
});
|
|
1525
|
+
}
|
|
808
1526
|
|
|
809
|
-
|
|
1527
|
+
/**
|
|
1528
|
+
* Internal state for HandlerTxBuilder
|
|
1529
|
+
*/
|
|
1530
|
+
interface HandlerTxBuilderState<
|
|
1531
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1532
|
+
TServiceCalls extends readonly (TxResult<any, any> | undefined)[],
|
|
1533
|
+
TRetrieveResults extends unknown[],
|
|
1534
|
+
TRetrieveSuccessResult,
|
|
1535
|
+
TMutateResult,
|
|
1536
|
+
TTransformResult,
|
|
1537
|
+
THooks extends HooksMap,
|
|
1538
|
+
> {
|
|
1539
|
+
options: ExecuteTxOptions;
|
|
1540
|
+
hooks?: THooks;
|
|
1541
|
+
withServiceCallsFn?: () => TServiceCalls;
|
|
1542
|
+
retrieveFn?: (context: {
|
|
1543
|
+
forSchema: <S extends AnySchema, H extends HooksMap = THooks>(
|
|
1544
|
+
schema: S,
|
|
1545
|
+
hooks?: H,
|
|
1546
|
+
) => TypedUnitOfWork<S, [], unknown, H>;
|
|
1547
|
+
idempotencyKey: string;
|
|
1548
|
+
currentAttempt: number;
|
|
1549
|
+
}) => TypedUnitOfWork<AnySchema, TRetrieveResults, unknown, HooksMap> | void;
|
|
1550
|
+
transformRetrieveFn?: (
|
|
1551
|
+
retrieveResult: TRetrieveResults,
|
|
1552
|
+
serviceResult: ExtractServiceRetrieveResults<TServiceCalls>,
|
|
1553
|
+
) => TRetrieveSuccessResult;
|
|
1554
|
+
mutateFn?: (
|
|
1555
|
+
ctx: HandlerBuilderMutateContext<
|
|
1556
|
+
TRetrieveSuccessResult,
|
|
1557
|
+
ExtractServiceRetrieveResults<TServiceCalls>,
|
|
1558
|
+
THooks
|
|
1559
|
+
>,
|
|
1560
|
+
) => TMutateResult;
|
|
1561
|
+
transformFn?: (
|
|
1562
|
+
ctx:
|
|
1563
|
+
| BuilderTransformContextWithMutate<
|
|
1564
|
+
TRetrieveSuccessResult,
|
|
1565
|
+
TMutateResult,
|
|
1566
|
+
ExtractServiceFinalResults<TServiceCalls>,
|
|
1567
|
+
ExtractServiceRetrieveResults<TServiceCalls>
|
|
1568
|
+
>
|
|
1569
|
+
| BuilderTransformContextWithoutMutate<
|
|
1570
|
+
TRetrieveSuccessResult,
|
|
1571
|
+
ExtractServiceFinalResults<TServiceCalls>,
|
|
1572
|
+
ExtractServiceRetrieveResults<TServiceCalls>
|
|
1573
|
+
>,
|
|
1574
|
+
) => TTransformResult;
|
|
1575
|
+
}
|
|
810
1576
|
|
|
811
|
-
|
|
1577
|
+
/**
|
|
1578
|
+
* Builder for handler-level transactions.
|
|
1579
|
+
* Uses a fluent API to build up transaction callbacks with proper type inference.
|
|
1580
|
+
*
|
|
1581
|
+
* @example
|
|
1582
|
+
* ```ts
|
|
1583
|
+
* const result = await handlerTx()
|
|
1584
|
+
* .withServiceCalls(() => [userService.getUser(id)])
|
|
1585
|
+
* .mutate(({ forSchema, idempotencyKey, currentAttempt, serviceIntermediateResult }) => {
|
|
1586
|
+
* return forSchema(ordersSchema).create("orders", { ... });
|
|
1587
|
+
* })
|
|
1588
|
+
* .transform(({ mutateResult, serviceResult }) => ({ ... }))
|
|
1589
|
+
* .execute();
|
|
1590
|
+
* ```
|
|
1591
|
+
*/
|
|
1592
|
+
export class HandlerTxBuilder<
|
|
1593
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1594
|
+
TServiceCalls extends readonly (TxResult<any, any> | undefined)[],
|
|
1595
|
+
TRetrieveResults extends unknown[],
|
|
1596
|
+
TRetrieveSuccessResult,
|
|
1597
|
+
TMutateResult,
|
|
1598
|
+
TTransformResult,
|
|
1599
|
+
HasRetrieve extends boolean,
|
|
1600
|
+
HasTransformRetrieve extends boolean,
|
|
1601
|
+
HasMutate extends boolean,
|
|
1602
|
+
HasTransform extends boolean,
|
|
1603
|
+
THooks extends HooksMap,
|
|
1604
|
+
> {
|
|
1605
|
+
readonly #state: HandlerTxBuilderState<
|
|
1606
|
+
TServiceCalls,
|
|
1607
|
+
TRetrieveResults,
|
|
1608
|
+
TRetrieveSuccessResult,
|
|
1609
|
+
TMutateResult,
|
|
1610
|
+
TTransformResult,
|
|
1611
|
+
THooks
|
|
1612
|
+
>;
|
|
1613
|
+
|
|
1614
|
+
constructor(
|
|
1615
|
+
state: HandlerTxBuilderState<
|
|
1616
|
+
TServiceCalls,
|
|
1617
|
+
TRetrieveResults,
|
|
1618
|
+
TRetrieveSuccessResult,
|
|
1619
|
+
TMutateResult,
|
|
1620
|
+
TTransformResult,
|
|
1621
|
+
THooks
|
|
1622
|
+
>,
|
|
1623
|
+
) {
|
|
1624
|
+
this.#state = state;
|
|
1625
|
+
}
|
|
1626
|
+
|
|
1627
|
+
/**
|
|
1628
|
+
* Add dependencies to execute before this transaction.
|
|
1629
|
+
*/
|
|
1630
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1631
|
+
withServiceCalls<TNewDeps extends readonly (TxResult<any, any> | undefined)[]>(
|
|
1632
|
+
fn: () => TNewDeps,
|
|
1633
|
+
): HandlerTxBuilder<
|
|
1634
|
+
TNewDeps,
|
|
1635
|
+
TRetrieveResults,
|
|
1636
|
+
TRetrieveSuccessResult,
|
|
1637
|
+
TMutateResult,
|
|
1638
|
+
TTransformResult,
|
|
1639
|
+
HasRetrieve,
|
|
1640
|
+
HasTransformRetrieve,
|
|
1641
|
+
HasMutate,
|
|
1642
|
+
HasTransform,
|
|
1643
|
+
THooks
|
|
1644
|
+
> {
|
|
1645
|
+
return new HandlerTxBuilder({
|
|
1646
|
+
...this.#state,
|
|
1647
|
+
withServiceCallsFn: fn,
|
|
1648
|
+
} as HandlerTxBuilderState<
|
|
1649
|
+
TNewDeps,
|
|
1650
|
+
TRetrieveResults,
|
|
1651
|
+
TRetrieveSuccessResult,
|
|
1652
|
+
TMutateResult,
|
|
1653
|
+
TTransformResult,
|
|
1654
|
+
THooks
|
|
1655
|
+
>);
|
|
1656
|
+
}
|
|
1657
|
+
|
|
1658
|
+
/**
|
|
1659
|
+
* Add retrieval operations to the transaction.
|
|
1660
|
+
* Return a TypedUnitOfWork from forSchema().find() to get typed results.
|
|
1661
|
+
*/
|
|
1662
|
+
retrieve<TNewRetrieveResults extends unknown[]>(
|
|
1663
|
+
fn: (context: {
|
|
1664
|
+
forSchema: <S extends AnySchema, H extends HooksMap = THooks>(
|
|
1665
|
+
schema: S,
|
|
1666
|
+
hooks?: H,
|
|
1667
|
+
) => TypedUnitOfWork<S, [], unknown, H>;
|
|
1668
|
+
idempotencyKey: string;
|
|
1669
|
+
currentAttempt: number;
|
|
1670
|
+
}) => TypedUnitOfWork<AnySchema, TNewRetrieveResults, unknown, HooksMap> | void,
|
|
1671
|
+
): HandlerTxBuilder<
|
|
1672
|
+
TServiceCalls,
|
|
1673
|
+
TNewRetrieveResults,
|
|
1674
|
+
TNewRetrieveResults, // Default TRetrieveSuccessResult to TNewRetrieveResults
|
|
1675
|
+
TMutateResult,
|
|
1676
|
+
TTransformResult,
|
|
1677
|
+
true, // HasRetrieve = true
|
|
1678
|
+
false, // Reset HasTransformRetrieve since retrieve results changed
|
|
1679
|
+
HasMutate,
|
|
1680
|
+
HasTransform,
|
|
1681
|
+
THooks
|
|
1682
|
+
> {
|
|
1683
|
+
return new HandlerTxBuilder({
|
|
1684
|
+
...this.#state,
|
|
1685
|
+
retrieveFn: fn,
|
|
1686
|
+
transformRetrieveFn: undefined, // Clear any existing transformRetrieve since results shape changed
|
|
1687
|
+
} as unknown as HandlerTxBuilderState<
|
|
1688
|
+
TServiceCalls,
|
|
1689
|
+
TNewRetrieveResults,
|
|
1690
|
+
TNewRetrieveResults,
|
|
1691
|
+
TMutateResult,
|
|
1692
|
+
TTransformResult,
|
|
1693
|
+
THooks
|
|
1694
|
+
>);
|
|
1695
|
+
}
|
|
1696
|
+
|
|
1697
|
+
/**
|
|
1698
|
+
* Transform retrieve results before passing to mutate.
|
|
1699
|
+
*/
|
|
1700
|
+
transformRetrieve<TNewRetrieveSuccessResult>(
|
|
1701
|
+
fn: (
|
|
1702
|
+
retrieveResult: TRetrieveResults,
|
|
1703
|
+
serviceResult: ExtractServiceRetrieveResults<TServiceCalls>,
|
|
1704
|
+
) => TNewRetrieveSuccessResult,
|
|
1705
|
+
): HandlerTxBuilder<
|
|
1706
|
+
TServiceCalls,
|
|
1707
|
+
TRetrieveResults,
|
|
1708
|
+
TNewRetrieveSuccessResult,
|
|
1709
|
+
TMutateResult,
|
|
1710
|
+
TTransformResult,
|
|
1711
|
+
HasRetrieve,
|
|
1712
|
+
true, // HasTransformRetrieve = true
|
|
1713
|
+
HasMutate,
|
|
1714
|
+
HasTransform,
|
|
1715
|
+
THooks
|
|
1716
|
+
> {
|
|
1717
|
+
return new HandlerTxBuilder({
|
|
1718
|
+
...this.#state,
|
|
1719
|
+
transformRetrieveFn: fn,
|
|
1720
|
+
} as unknown as HandlerTxBuilderState<
|
|
1721
|
+
TServiceCalls,
|
|
1722
|
+
TRetrieveResults,
|
|
1723
|
+
TNewRetrieveSuccessResult,
|
|
1724
|
+
TMutateResult,
|
|
1725
|
+
TTransformResult,
|
|
1726
|
+
THooks
|
|
1727
|
+
>);
|
|
1728
|
+
}
|
|
1729
|
+
|
|
1730
|
+
/**
|
|
1731
|
+
* Add mutation operations based on retrieve results.
|
|
1732
|
+
*/
|
|
1733
|
+
mutate<TNewMutateResult>(
|
|
1734
|
+
fn: (
|
|
1735
|
+
ctx: HandlerBuilderMutateContext<
|
|
1736
|
+
HasTransformRetrieve extends true ? TRetrieveSuccessResult : TRetrieveResults,
|
|
1737
|
+
ExtractServiceRetrieveResults<TServiceCalls>,
|
|
1738
|
+
THooks
|
|
1739
|
+
>,
|
|
1740
|
+
) => TNewMutateResult,
|
|
1741
|
+
): HandlerTxBuilder<
|
|
1742
|
+
TServiceCalls,
|
|
1743
|
+
TRetrieveResults,
|
|
1744
|
+
HasTransformRetrieve extends true ? TRetrieveSuccessResult : TRetrieveResults,
|
|
1745
|
+
TNewMutateResult,
|
|
1746
|
+
TTransformResult,
|
|
1747
|
+
HasRetrieve,
|
|
1748
|
+
HasTransformRetrieve,
|
|
1749
|
+
true, // HasMutate = true
|
|
1750
|
+
HasTransform,
|
|
1751
|
+
THooks
|
|
1752
|
+
> {
|
|
1753
|
+
return new HandlerTxBuilder({
|
|
1754
|
+
...this.#state,
|
|
1755
|
+
mutateFn: fn,
|
|
1756
|
+
} as unknown as HandlerTxBuilderState<
|
|
1757
|
+
TServiceCalls,
|
|
1758
|
+
TRetrieveResults,
|
|
1759
|
+
HasTransformRetrieve extends true ? TRetrieveSuccessResult : TRetrieveResults,
|
|
1760
|
+
TNewMutateResult,
|
|
1761
|
+
TTransformResult,
|
|
1762
|
+
THooks
|
|
1763
|
+
>);
|
|
1764
|
+
}
|
|
1765
|
+
|
|
1766
|
+
/**
|
|
1767
|
+
* Add final transformation after mutations complete.
|
|
1768
|
+
*/
|
|
1769
|
+
transform<TNewTransformResult>(
|
|
1770
|
+
fn: (
|
|
1771
|
+
ctx: HasMutate extends true
|
|
1772
|
+
? BuilderTransformContextWithMutate<
|
|
1773
|
+
HasTransformRetrieve extends true ? TRetrieveSuccessResult : TRetrieveResults,
|
|
1774
|
+
TMutateResult,
|
|
1775
|
+
ExtractServiceFinalResults<TServiceCalls>,
|
|
1776
|
+
ExtractServiceRetrieveResults<TServiceCalls>
|
|
1777
|
+
>
|
|
1778
|
+
: BuilderTransformContextWithoutMutate<
|
|
1779
|
+
HasTransformRetrieve extends true ? TRetrieveSuccessResult : TRetrieveResults,
|
|
1780
|
+
ExtractServiceFinalResults<TServiceCalls>,
|
|
1781
|
+
ExtractServiceRetrieveResults<TServiceCalls>
|
|
1782
|
+
>,
|
|
1783
|
+
) => TNewTransformResult,
|
|
1784
|
+
): HandlerTxBuilder<
|
|
1785
|
+
TServiceCalls,
|
|
1786
|
+
TRetrieveResults,
|
|
1787
|
+
HasTransformRetrieve extends true ? TRetrieveSuccessResult : TRetrieveResults,
|
|
1788
|
+
TMutateResult,
|
|
1789
|
+
TNewTransformResult,
|
|
1790
|
+
HasRetrieve,
|
|
1791
|
+
HasTransformRetrieve,
|
|
1792
|
+
HasMutate,
|
|
1793
|
+
true, // HasTransform = true
|
|
1794
|
+
THooks
|
|
1795
|
+
> {
|
|
1796
|
+
return new HandlerTxBuilder({
|
|
1797
|
+
...this.#state,
|
|
1798
|
+
transformFn: fn,
|
|
1799
|
+
} as unknown as HandlerTxBuilderState<
|
|
1800
|
+
TServiceCalls,
|
|
1801
|
+
TRetrieveResults,
|
|
1802
|
+
HasTransformRetrieve extends true ? TRetrieveSuccessResult : TRetrieveResults,
|
|
1803
|
+
TMutateResult,
|
|
1804
|
+
TNewTransformResult,
|
|
1805
|
+
THooks
|
|
1806
|
+
>);
|
|
1807
|
+
}
|
|
1808
|
+
|
|
1809
|
+
/**
|
|
1810
|
+
* Execute the transaction and return the result.
|
|
1811
|
+
*/
|
|
1812
|
+
execute(): Promise<
|
|
1813
|
+
AwaitedPromisesInObject<
|
|
1814
|
+
InferBuilderResultType<
|
|
1815
|
+
TRetrieveResults,
|
|
1816
|
+
TRetrieveSuccessResult,
|
|
1817
|
+
TServiceCalls,
|
|
1818
|
+
TMutateResult,
|
|
1819
|
+
TTransformResult,
|
|
1820
|
+
HasTransform,
|
|
1821
|
+
HasMutate,
|
|
1822
|
+
HasTransformRetrieve,
|
|
1823
|
+
HasRetrieve
|
|
1824
|
+
>
|
|
1825
|
+
>
|
|
1826
|
+
> {
|
|
1827
|
+
const state = this.#state;
|
|
1828
|
+
|
|
1829
|
+
// Convert builder state to legacy callbacks format
|
|
1830
|
+
const callbacks: HandlerTxCallbacks<
|
|
1831
|
+
TRetrieveResults,
|
|
1832
|
+
TRetrieveSuccessResult,
|
|
1833
|
+
TServiceCalls,
|
|
1834
|
+
TMutateResult,
|
|
1835
|
+
TTransformResult,
|
|
1836
|
+
THooks
|
|
1837
|
+
> = {
|
|
1838
|
+
serviceCalls: state.withServiceCallsFn,
|
|
1839
|
+
retrieve: state.retrieveFn
|
|
1840
|
+
? (context) => {
|
|
1841
|
+
return state.retrieveFn!({
|
|
1842
|
+
forSchema: context.forSchema,
|
|
1843
|
+
idempotencyKey: context.idempotencyKey,
|
|
1844
|
+
currentAttempt: context.currentAttempt,
|
|
1845
|
+
});
|
|
1846
|
+
}
|
|
1847
|
+
: undefined,
|
|
1848
|
+
retrieveSuccess: state.transformRetrieveFn,
|
|
1849
|
+
mutate: state.mutateFn
|
|
1850
|
+
? (ctx) => {
|
|
1851
|
+
return state.mutateFn!({
|
|
1852
|
+
forSchema: ctx.forSchema,
|
|
1853
|
+
idempotencyKey: ctx.idempotencyKey,
|
|
1854
|
+
currentAttempt: ctx.currentAttempt,
|
|
1855
|
+
retrieveResult: ctx.retrieveResult,
|
|
1856
|
+
serviceIntermediateResult: ctx.serviceIntermediateResult,
|
|
1857
|
+
});
|
|
1858
|
+
}
|
|
1859
|
+
: undefined,
|
|
1860
|
+
success: state.transformFn
|
|
1861
|
+
? (ctx) => {
|
|
1862
|
+
return state.transformFn!({
|
|
1863
|
+
retrieveResult: ctx.retrieveResult,
|
|
1864
|
+
mutateResult: ctx.mutateResult,
|
|
1865
|
+
serviceResult: ctx.serviceResult,
|
|
1866
|
+
serviceIntermediateResult: ctx.serviceIntermediateResult,
|
|
1867
|
+
} as BuilderTransformContextWithMutate<
|
|
1868
|
+
TRetrieveSuccessResult,
|
|
1869
|
+
TMutateResult,
|
|
1870
|
+
ExtractServiceFinalResults<TServiceCalls>,
|
|
1871
|
+
ExtractServiceRetrieveResults<TServiceCalls>
|
|
1872
|
+
>);
|
|
1873
|
+
}
|
|
1874
|
+
: undefined,
|
|
1875
|
+
};
|
|
1876
|
+
|
|
1877
|
+
// Use the existing executeTx implementation
|
|
1878
|
+
return executeTx(callbacks as Parameters<typeof executeTx>[0], state.options) as Promise<
|
|
1879
|
+
AwaitedPromisesInObject<
|
|
1880
|
+
InferBuilderResultType<
|
|
1881
|
+
TRetrieveResults,
|
|
1882
|
+
TRetrieveSuccessResult,
|
|
1883
|
+
TServiceCalls,
|
|
1884
|
+
TMutateResult,
|
|
1885
|
+
TTransformResult,
|
|
1886
|
+
HasTransform,
|
|
1887
|
+
HasMutate,
|
|
1888
|
+
HasTransformRetrieve,
|
|
1889
|
+
HasRetrieve
|
|
1890
|
+
>
|
|
1891
|
+
>
|
|
1892
|
+
>;
|
|
1893
|
+
}
|
|
1894
|
+
}
|
|
1895
|
+
|
|
1896
|
+
/**
|
|
1897
|
+
* Create a new HandlerTxBuilder with the given options.
|
|
1898
|
+
*/
|
|
1899
|
+
export function createHandlerTxBuilder<THooks extends HooksMap = {}>(
|
|
1900
|
+
options: ExecuteTxOptions,
|
|
1901
|
+
hooks?: THooks,
|
|
1902
|
+
): HandlerTxBuilder<readonly [], [], [], unknown, unknown, false, false, false, false, THooks> {
|
|
1903
|
+
return new HandlerTxBuilder({
|
|
1904
|
+
options,
|
|
1905
|
+
hooks,
|
|
1906
|
+
});
|
|
812
1907
|
}
|