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