@andrew_l/mongo-transaction 0.3.21 → 0.4.0
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/dist/index.d.mts +86 -88
- package/dist/index.d.mts.map +1 -0
- package/dist/index.mjs +332 -431
- package/dist/index.mjs.map +1 -1
- package/package.json +10 -9
- package/dist/index.cjs +0 -473
- package/dist/index.cjs.map +0 -1
- package/dist/index.d.cts +0 -391
- package/dist/index.d.ts +0 -391
package/dist/index.cjs
DELETED
|
@@ -1,473 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
const toolkit = require('@andrew_l/toolkit');
|
|
4
|
-
const context = require('@andrew_l/context');
|
|
5
|
-
const mongodb = require('mongodb');
|
|
6
|
-
|
|
7
|
-
const [injectMongoSession, provideMongoSession] = context.createContext("withMongoTransaction");
|
|
8
|
-
function useMongoSession() {
|
|
9
|
-
return context.hasInjectionContext() ? injectMongoSession(null) : null;
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
function onMongoSessionCommitted(...args) {
|
|
13
|
-
let session;
|
|
14
|
-
let fn;
|
|
15
|
-
if (args.length === 2) {
|
|
16
|
-
[session, fn] = args;
|
|
17
|
-
} else {
|
|
18
|
-
session = injectMongoSession();
|
|
19
|
-
fn = args[0];
|
|
20
|
-
}
|
|
21
|
-
const q = toolkit.defer();
|
|
22
|
-
const onEnded = () => {
|
|
23
|
-
if (!session.transaction.isCommitted) {
|
|
24
|
-
return q.resolve(void 0);
|
|
25
|
-
}
|
|
26
|
-
try {
|
|
27
|
-
const result = fn();
|
|
28
|
-
if (toolkit.isPromise(result)) {
|
|
29
|
-
result.then((r) => q.resolve(r)).catch(q.reject);
|
|
30
|
-
} else {
|
|
31
|
-
q.resolve(result);
|
|
32
|
-
}
|
|
33
|
-
} catch (err) {
|
|
34
|
-
q.reject(err);
|
|
35
|
-
}
|
|
36
|
-
};
|
|
37
|
-
session.once("ended", onEnded);
|
|
38
|
-
const cancel = () => {
|
|
39
|
-
session.off("ended", onEnded);
|
|
40
|
-
};
|
|
41
|
-
return {
|
|
42
|
-
promise: q.promise,
|
|
43
|
-
cancel
|
|
44
|
-
};
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
const [injectTransactionScope, provideTransactionScope] = context.createContext([
|
|
48
|
-
"withTransaction",
|
|
49
|
-
"withTransactionControlled",
|
|
50
|
-
"withMongoTransaction"
|
|
51
|
-
]);
|
|
52
|
-
|
|
53
|
-
class TransactionScope {
|
|
54
|
-
constructor(fn) {
|
|
55
|
-
this.fn = fn;
|
|
56
|
-
const scope = this;
|
|
57
|
-
this.run = function(...args) {
|
|
58
|
-
const self = this === scope ? void 0 : this;
|
|
59
|
-
return scope._run(self, ...args);
|
|
60
|
-
};
|
|
61
|
-
}
|
|
62
|
-
/**
|
|
63
|
-
* @internal
|
|
64
|
-
*/
|
|
65
|
-
log = toolkit.logger("TransactionScope");
|
|
66
|
-
/**
|
|
67
|
-
* @internal
|
|
68
|
-
* Indicates currently ran scope
|
|
69
|
-
*/
|
|
70
|
-
_active = false;
|
|
71
|
-
/**
|
|
72
|
-
* Last run error
|
|
73
|
-
*/
|
|
74
|
-
error;
|
|
75
|
-
/**
|
|
76
|
-
* Last run result
|
|
77
|
-
*/
|
|
78
|
-
result;
|
|
79
|
-
run;
|
|
80
|
-
/**
|
|
81
|
-
* @internal
|
|
82
|
-
*/
|
|
83
|
-
hooks = {
|
|
84
|
-
committed: { byCursor: [], cursor: 0 },
|
|
85
|
-
effects: { byCursor: [], cursor: 0 },
|
|
86
|
-
rollbacks: { byCursor: [], cursor: 0 }
|
|
87
|
-
};
|
|
88
|
-
get active() {
|
|
89
|
-
return this._active;
|
|
90
|
-
}
|
|
91
|
-
/**
|
|
92
|
-
* @internal
|
|
93
|
-
*/
|
|
94
|
-
async _run(self, ...args) {
|
|
95
|
-
toolkit.assert.ok(!this._active, "Cannot run while transaction active.");
|
|
96
|
-
this.reset();
|
|
97
|
-
this._active = true;
|
|
98
|
-
const [cbError, cbResult] = await context.withContext(() => {
|
|
99
|
-
provideTransactionScope(this);
|
|
100
|
-
return toolkit.catchError(this.fn.bind(self, ...args));
|
|
101
|
-
})();
|
|
102
|
-
if (cbError) {
|
|
103
|
-
this.error = cbError;
|
|
104
|
-
} else {
|
|
105
|
-
const applyError = await effectsApply(this, "post");
|
|
106
|
-
if (applyError) {
|
|
107
|
-
this.error = applyError;
|
|
108
|
-
}
|
|
109
|
-
this.result = cbResult;
|
|
110
|
-
}
|
|
111
|
-
this._active = false;
|
|
112
|
-
}
|
|
113
|
-
async commit() {
|
|
114
|
-
toolkit.assert.ok(!this._active, "Cannot commit while transaction active.");
|
|
115
|
-
toolkit.assert.ok(!this.error, this.error);
|
|
116
|
-
await toolkit.asyncForEach(
|
|
117
|
-
this.hooks.committed.byCursor,
|
|
118
|
-
(h) => toolkit.catchError(h.callback),
|
|
119
|
-
{ concurrency: 4 }
|
|
120
|
-
);
|
|
121
|
-
this.reset();
|
|
122
|
-
this.clean();
|
|
123
|
-
}
|
|
124
|
-
async rollback() {
|
|
125
|
-
toolkit.assert.ok(!this._active, "Cannot rollback while transaction active.");
|
|
126
|
-
const error = await effectsCleanup(this);
|
|
127
|
-
if (error) {
|
|
128
|
-
return Promise.reject(error);
|
|
129
|
-
}
|
|
130
|
-
await toolkit.asyncForEach(
|
|
131
|
-
this.hooks.rollbacks.byCursor,
|
|
132
|
-
(h) => toolkit.catchError(h.callback),
|
|
133
|
-
{ concurrency: 4 }
|
|
134
|
-
);
|
|
135
|
-
this.reset();
|
|
136
|
-
this.clean();
|
|
137
|
-
}
|
|
138
|
-
reset() {
|
|
139
|
-
toolkit.assert.ok(!this._active, "Cannot reset while transaction active.");
|
|
140
|
-
this.hooks.effects.cursor = 0;
|
|
141
|
-
this.hooks.committed.cursor = 0;
|
|
142
|
-
this.hooks.rollbacks.cursor = 0;
|
|
143
|
-
this.result = void 0;
|
|
144
|
-
this.error = void 0;
|
|
145
|
-
}
|
|
146
|
-
clean() {
|
|
147
|
-
toolkit.assert.ok(!this._active, "Cannot clean while transaction active.");
|
|
148
|
-
this.hooks.effects = { byCursor: [], cursor: 0 };
|
|
149
|
-
this.hooks.committed = { byCursor: [], cursor: 0 };
|
|
150
|
-
this.hooks.rollbacks = { byCursor: [], cursor: 0 };
|
|
151
|
-
}
|
|
152
|
-
}
|
|
153
|
-
function createTransactionScope(fn) {
|
|
154
|
-
return new TransactionScope(fn);
|
|
155
|
-
}
|
|
156
|
-
async function effectsApply(scope, reason = "no reason") {
|
|
157
|
-
let error;
|
|
158
|
-
const onComplete = (err) => void (error = err || error);
|
|
159
|
-
await toolkit.asyncForEach(
|
|
160
|
-
scope.hooks.effects.byCursor,
|
|
161
|
-
(effect) => applyEffect(scope, effect, reason).then(onComplete),
|
|
162
|
-
{
|
|
163
|
-
concurrency: 4
|
|
164
|
-
}
|
|
165
|
-
);
|
|
166
|
-
return error;
|
|
167
|
-
}
|
|
168
|
-
async function effectsCleanup(scope, reason = "no reason") {
|
|
169
|
-
let error;
|
|
170
|
-
const onComplete = (err) => void (error = err || error);
|
|
171
|
-
await toolkit.asyncForEach(
|
|
172
|
-
scope.hooks.effects.byCursor,
|
|
173
|
-
(effect) => cleanupEffect(scope, effect, reason).then(onComplete),
|
|
174
|
-
{
|
|
175
|
-
concurrency: 4
|
|
176
|
-
}
|
|
177
|
-
);
|
|
178
|
-
return error;
|
|
179
|
-
}
|
|
180
|
-
async function applyEffect(scope, effect, reason = "no reason") {
|
|
181
|
-
if (effect.cleanup) return;
|
|
182
|
-
scope.log.debug(
|
|
183
|
-
"Effect name = %s, flush = %s, apply by %s",
|
|
184
|
-
effect.name,
|
|
185
|
-
effect.flush,
|
|
186
|
-
reason
|
|
187
|
-
);
|
|
188
|
-
const [err, effectResult] = await toolkit.catchError(effect.setup);
|
|
189
|
-
if (err) {
|
|
190
|
-
!toolkit.env.isTest && scope.log.error(
|
|
191
|
-
"Effect name = %s, flush = %s apply error",
|
|
192
|
-
effect.name,
|
|
193
|
-
effect.flush,
|
|
194
|
-
err
|
|
195
|
-
);
|
|
196
|
-
return err;
|
|
197
|
-
}
|
|
198
|
-
effect.cleanup = toolkit.isFunction(effectResult) ? effectResult : toolkit.noop;
|
|
199
|
-
return;
|
|
200
|
-
}
|
|
201
|
-
async function cleanupEffect(scope, effect, reason = "no reason") {
|
|
202
|
-
if (!effect.cleanup) return;
|
|
203
|
-
scope.log.debug(
|
|
204
|
-
"Effect name = %s, flush = %s, cleanup by %s",
|
|
205
|
-
effect.name,
|
|
206
|
-
effect.flush,
|
|
207
|
-
reason
|
|
208
|
-
);
|
|
209
|
-
const [err] = await toolkit.catchError(effect.cleanup);
|
|
210
|
-
if (err) {
|
|
211
|
-
!toolkit.env.isTest && scope.log.error(
|
|
212
|
-
"Effect name = %s, flush = %s, cleanup error",
|
|
213
|
-
effect.name,
|
|
214
|
-
effect.flush,
|
|
215
|
-
err
|
|
216
|
-
);
|
|
217
|
-
return err;
|
|
218
|
-
}
|
|
219
|
-
effect.cleanup = void 0;
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
async function useTransactionEffect(setup, options = {}) {
|
|
223
|
-
const scope = injectTransactionScope();
|
|
224
|
-
const { cursor, byCursor } = scope.hooks.effects;
|
|
225
|
-
const effectConfig = byCursor[cursor] ?? {};
|
|
226
|
-
const flush = options?.flush || "pre";
|
|
227
|
-
const name = options?.name || `Effect #${cursor + 1}`;
|
|
228
|
-
const dependencies = options.dependencies ?? [];
|
|
229
|
-
const prevDependencies = effectConfig.dependencies;
|
|
230
|
-
byCursor[cursor] = {
|
|
231
|
-
...effectConfig,
|
|
232
|
-
dependencies,
|
|
233
|
-
flush,
|
|
234
|
-
name,
|
|
235
|
-
setup
|
|
236
|
-
};
|
|
237
|
-
if (dependencies && prevDependencies) {
|
|
238
|
-
if (!toolkit.isEqual(dependencies, prevDependencies)) {
|
|
239
|
-
scope.log.debug(
|
|
240
|
-
"Effect name = %s, flush = %s, caused by dependencies",
|
|
241
|
-
name,
|
|
242
|
-
flush,
|
|
243
|
-
{
|
|
244
|
-
prevDependencies,
|
|
245
|
-
newDependencies: dependencies
|
|
246
|
-
}
|
|
247
|
-
);
|
|
248
|
-
await scheduleEffect(scope, cursor);
|
|
249
|
-
}
|
|
250
|
-
} else {
|
|
251
|
-
scope.log.debug(
|
|
252
|
-
"Effect name = %s, flush = %s, caused by missing dependencies",
|
|
253
|
-
name,
|
|
254
|
-
flush
|
|
255
|
-
);
|
|
256
|
-
await scheduleEffect(scope, cursor);
|
|
257
|
-
}
|
|
258
|
-
scope.hooks.effects.cursor++;
|
|
259
|
-
}
|
|
260
|
-
async function scheduleEffect(scope, cursor) {
|
|
261
|
-
const { byCursor } = scope.hooks.effects;
|
|
262
|
-
const effect = byCursor[cursor];
|
|
263
|
-
const cleanupErr = await cleanupEffect(scope, effect, "schedule pre");
|
|
264
|
-
if (cleanupErr) {
|
|
265
|
-
return Promise.reject(cleanupErr);
|
|
266
|
-
}
|
|
267
|
-
if (effect.flush === "pre") {
|
|
268
|
-
const err = await applyEffect(scope, effect, "schedule pre");
|
|
269
|
-
if (err) {
|
|
270
|
-
return Promise.reject(err);
|
|
271
|
-
}
|
|
272
|
-
}
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
function onCommitted(callback, dependencies) {
|
|
276
|
-
const scope = injectTransactionScope();
|
|
277
|
-
const { cursor, byCursor } = scope.hooks.committed;
|
|
278
|
-
const config = byCursor[cursor];
|
|
279
|
-
if (dependencies && config?.dependencies) {
|
|
280
|
-
if (!toolkit.isEqual(dependencies, config.dependencies)) {
|
|
281
|
-
scope.log.debug("OnCommitted caused by dependencies", {
|
|
282
|
-
prevDependencies: config.dependencies,
|
|
283
|
-
newDependencies: dependencies,
|
|
284
|
-
cursor
|
|
285
|
-
});
|
|
286
|
-
byCursor[cursor] = { callback, dependencies };
|
|
287
|
-
}
|
|
288
|
-
} else {
|
|
289
|
-
scope.log.debug("OnCommitted caused by missing dependencies", { cursor });
|
|
290
|
-
byCursor[cursor] = { callback, dependencies };
|
|
291
|
-
}
|
|
292
|
-
scope.hooks.committed.cursor++;
|
|
293
|
-
return () => {
|
|
294
|
-
byCursor[cursor].callback = toolkit.noop;
|
|
295
|
-
};
|
|
296
|
-
}
|
|
297
|
-
|
|
298
|
-
function onRollback(callback, dependencies) {
|
|
299
|
-
const scope = injectTransactionScope();
|
|
300
|
-
const { cursor, byCursor } = scope.hooks.rollbacks;
|
|
301
|
-
const config = byCursor[cursor];
|
|
302
|
-
if (dependencies && config?.dependencies) {
|
|
303
|
-
if (!toolkit.isEqual(dependencies, config.dependencies)) {
|
|
304
|
-
scope.log.debug("OnCommitted caused by dependencies", {
|
|
305
|
-
prevDependencies: config.dependencies,
|
|
306
|
-
newDependencies: dependencies,
|
|
307
|
-
cursor
|
|
308
|
-
});
|
|
309
|
-
byCursor[cursor] = { callback, dependencies };
|
|
310
|
-
}
|
|
311
|
-
} else {
|
|
312
|
-
scope.log.debug("OnCommitted caused by missing dependencies", { cursor });
|
|
313
|
-
byCursor[cursor] = { callback, dependencies };
|
|
314
|
-
}
|
|
315
|
-
scope.hooks.committed.cursor++;
|
|
316
|
-
return () => {
|
|
317
|
-
byCursor[cursor].callback = toolkit.noop;
|
|
318
|
-
};
|
|
319
|
-
}
|
|
320
|
-
|
|
321
|
-
function isTransactionAborted(transaction) {
|
|
322
|
-
return transaction?.state === "TRANSACTION_ABORTED";
|
|
323
|
-
}
|
|
324
|
-
function isTransactionCommittedEmpty(transaction) {
|
|
325
|
-
return transaction?.state === "TRANSACTION_COMMITTED_EMPTY";
|
|
326
|
-
}
|
|
327
|
-
function isMongoClientLike(value) {
|
|
328
|
-
return toolkit.has(value, ["startSession"]) && toolkit.isFunction(value.startSession);
|
|
329
|
-
}
|
|
330
|
-
|
|
331
|
-
const DEF_SESSION_OPTIONS = Object.freeze({
|
|
332
|
-
defaultTransactionOptions: {
|
|
333
|
-
readPreference: "primary",
|
|
334
|
-
readConcern: { level: "local" },
|
|
335
|
-
writeConcern: { w: "majority" }
|
|
336
|
-
}
|
|
337
|
-
});
|
|
338
|
-
function withMongoTransaction(connectionOrOptions, maybeFn, maybeOptions) {
|
|
339
|
-
const {
|
|
340
|
-
connection: connectionValue,
|
|
341
|
-
fn,
|
|
342
|
-
sessionOptions = {},
|
|
343
|
-
timeoutMS
|
|
344
|
-
} = prepareOptions(connectionOrOptions, maybeFn, maybeOptions);
|
|
345
|
-
return async function(...args) {
|
|
346
|
-
const connection = toolkit.isFunction(connectionValue) ? await connectionValue() : connectionValue;
|
|
347
|
-
const session = await connection.startSession(
|
|
348
|
-
sessionOptions
|
|
349
|
-
);
|
|
350
|
-
const scope = createTransactionScope(function(...args2) {
|
|
351
|
-
provideMongoSession(session);
|
|
352
|
-
return fn.call(this, session, ...args2);
|
|
353
|
-
});
|
|
354
|
-
const timeoutAt = timeoutMS ? Date.now() + timeoutMS : 0;
|
|
355
|
-
const timeoutError = new mongodb.MongoTransactionError(
|
|
356
|
-
"Transaction client-side timeout"
|
|
357
|
-
);
|
|
358
|
-
let [transactionError, transactionResult] = await toolkit.catchError(
|
|
359
|
-
() => session.withTransaction(async () => {
|
|
360
|
-
if (timeoutAt && timeoutAt < Date.now()) {
|
|
361
|
-
return Promise.reject(timeoutError);
|
|
362
|
-
}
|
|
363
|
-
await scope.run.apply(this, args);
|
|
364
|
-
if (scope.error) {
|
|
365
|
-
return Promise.reject(scope.error);
|
|
366
|
-
}
|
|
367
|
-
})
|
|
368
|
-
);
|
|
369
|
-
const { result } = scope;
|
|
370
|
-
await session.endSession().catch(toolkit.noop);
|
|
371
|
-
if (transactionResult === void 0 && isTransactionCommittedEmpty(session.transaction)) ; else if (isTransactionAborted(session.transaction) && transactionResult === void 0 && transactionError === void 0) {
|
|
372
|
-
transactionError = new mongodb.MongoTransactionError(
|
|
373
|
-
"Transaction is explicitly aborted"
|
|
374
|
-
);
|
|
375
|
-
}
|
|
376
|
-
if (transactionError) {
|
|
377
|
-
await scope.rollback();
|
|
378
|
-
return Promise.reject(transactionError);
|
|
379
|
-
}
|
|
380
|
-
await scope.commit();
|
|
381
|
-
return result;
|
|
382
|
-
};
|
|
383
|
-
}
|
|
384
|
-
function prepareOptions(connectionOrOptions, maybeFn, maybeOptions) {
|
|
385
|
-
let options;
|
|
386
|
-
if (toolkit.isFunction(connectionOrOptions) || isMongoClientLike(connectionOrOptions)) {
|
|
387
|
-
options = {
|
|
388
|
-
...maybeOptions || {},
|
|
389
|
-
connection: connectionOrOptions,
|
|
390
|
-
fn: maybeFn
|
|
391
|
-
};
|
|
392
|
-
} else {
|
|
393
|
-
options = connectionOrOptions;
|
|
394
|
-
}
|
|
395
|
-
return toolkit.deepDefaults(options, {
|
|
396
|
-
sessionOptions: DEF_SESSION_OPTIONS
|
|
397
|
-
});
|
|
398
|
-
}
|
|
399
|
-
|
|
400
|
-
function withTransaction(fn, {
|
|
401
|
-
beforeRetryCallback,
|
|
402
|
-
shouldRetryBasedOnError = () => true,
|
|
403
|
-
maxAttempts,
|
|
404
|
-
maxRetriesNumber = 5,
|
|
405
|
-
delayFactor = 0,
|
|
406
|
-
delayMaxMs = 1e3,
|
|
407
|
-
delayMinMs = 100
|
|
408
|
-
} = {}) {
|
|
409
|
-
return async function(...args) {
|
|
410
|
-
const scope = createTransactionScope(fn);
|
|
411
|
-
await toolkit.retryOnError(
|
|
412
|
-
{
|
|
413
|
-
beforeRetryCallback,
|
|
414
|
-
shouldRetryBasedOnError,
|
|
415
|
-
maxAttempts,
|
|
416
|
-
maxRetriesNumber,
|
|
417
|
-
delayFactor,
|
|
418
|
-
delayMaxMs,
|
|
419
|
-
delayMinMs
|
|
420
|
-
},
|
|
421
|
-
async () => {
|
|
422
|
-
await scope.run.apply(this, args);
|
|
423
|
-
if (scope.error) {
|
|
424
|
-
return Promise.reject(scope.error);
|
|
425
|
-
}
|
|
426
|
-
}
|
|
427
|
-
)().catch(toolkit.noop);
|
|
428
|
-
const { error, result } = scope;
|
|
429
|
-
if (error) {
|
|
430
|
-
await scope.rollback();
|
|
431
|
-
return Promise.reject(error);
|
|
432
|
-
} else {
|
|
433
|
-
await scope.commit();
|
|
434
|
-
}
|
|
435
|
-
return result;
|
|
436
|
-
};
|
|
437
|
-
}
|
|
438
|
-
|
|
439
|
-
function withTransactionControlled(fn) {
|
|
440
|
-
const scope = createTransactionScope(fn);
|
|
441
|
-
const controlled = {
|
|
442
|
-
run(...args) {
|
|
443
|
-
const self = this === controlled ? void 0 : this;
|
|
444
|
-
return scope.run.apply(self, args);
|
|
445
|
-
},
|
|
446
|
-
commit() {
|
|
447
|
-
return scope.commit();
|
|
448
|
-
},
|
|
449
|
-
rollback() {
|
|
450
|
-
return scope.rollback();
|
|
451
|
-
},
|
|
452
|
-
get active() {
|
|
453
|
-
return scope.active;
|
|
454
|
-
},
|
|
455
|
-
get result() {
|
|
456
|
-
return scope.result;
|
|
457
|
-
},
|
|
458
|
-
get error() {
|
|
459
|
-
return scope.error;
|
|
460
|
-
}
|
|
461
|
-
};
|
|
462
|
-
return controlled;
|
|
463
|
-
}
|
|
464
|
-
|
|
465
|
-
exports.onCommitted = onCommitted;
|
|
466
|
-
exports.onMongoSessionCommitted = onMongoSessionCommitted;
|
|
467
|
-
exports.onRollback = onRollback;
|
|
468
|
-
exports.useMongoSession = useMongoSession;
|
|
469
|
-
exports.useTransactionEffect = useTransactionEffect;
|
|
470
|
-
exports.withMongoTransaction = withMongoTransaction;
|
|
471
|
-
exports.withTransaction = withTransaction;
|
|
472
|
-
exports.withTransactionControlled = withTransactionControlled;
|
|
473
|
-
//# sourceMappingURL=index.cjs.map
|
package/dist/index.cjs.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"index.cjs","sources":["../src/hooks/useMongoSession.ts","../src/hooks/onMongoSessionCommitted.ts","../src/hooks/scope.ts","../src/scope.ts","../src/hooks/useTransactionEffect.ts","../src/hooks/onCommitted.ts","../src/hooks/onRollback.ts","../src/utils.ts","../src/withMongoTransaction.ts","../src/withTransaction.ts","../src/withTransactionControlled.ts"],"sourcesContent":["import { createContext, hasInjectionContext } from '@andrew_l/context';\nimport type { ClientSession } from 'mongodb';\n\nexport const [injectMongoSession, provideMongoSession] =\n createContext<ClientSession>('withMongoTransaction');\n\n/**\n * Returns the current transaction session if executed within `withMongoTransaction()` otherwise returns `null`\n *\n * @example\n * async function createAlert() {\n * const session = useMongoSession();\n *\n * await db.alerts.insertOne(\n * { title: 'Order Created' },\n * { session: session ?? undefined }\n * );\n * }\n *\n * @group Hooks\n */\nexport function useMongoSession(): ClientSession | null {\n return hasInjectionContext() ? injectMongoSession(null) : null;\n}\n","import {\n type AnyFunction,\n type Awaitable,\n defer,\n isPromise,\n} from '@andrew_l/toolkit';\nimport type { ClientSession } from 'mongodb';\nimport { injectMongoSession } from './useMongoSession';\n\nexport type OnMongoSessionCommittedResult<T> = {\n /**\n * Executes the provided function upon transaction commit.\n *\n * Returns `T` if the transaction is committed and the function completes successfully.\n *\n * Returns `undefined` if the transaction is explicitly aborted or ends without committing.\n *\n * Rejects if the function throws an error.\n */\n promise: Promise<T | undefined>;\n\n cancel: () => void;\n};\n\n/**\n * Executes the provided function upon transaction commit.\n *\n * Returns `T` if the transaction is committed and the function completes successfully.\n *\n * Returns `false` if the transaction ends without committing.\n *\n * Rejects if the function throws an error.\n *\n * @example\n * const { promise } = onTransactionCommitted(async () => {\n * console.info('Transaction committed successfully!');\n * return Math.random(); // Random value generated after commit\n * });\n *\n * promise.then(result => {\n * if (result !== false) {\n * console.info('Handler result:', result); // e.g., Handler result: 0.07576196837476501\n * }\n * });\n *\n * @group Hooks\n */\nexport function onMongoSessionCommitted<T>(\n fn: () => Awaitable<T>,\n): OnMongoSessionCommittedResult<T>;\n\nexport function onMongoSessionCommitted<T>(\n session: ClientSession,\n fn: () => Awaitable<T>,\n): OnMongoSessionCommittedResult<T>;\n\nexport function onMongoSessionCommitted(\n ...args: any[]\n): OnMongoSessionCommittedResult<unknown> {\n let session: ClientSession;\n let fn: AnyFunction;\n\n if (args.length === 2) {\n [session, fn] = args;\n } else {\n session = injectMongoSession();\n fn = args[0];\n }\n\n const q = defer<undefined | unknown>();\n\n const onEnded = () => {\n if (!session.transaction.isCommitted) {\n return q.resolve(undefined);\n }\n\n try {\n const result = fn();\n\n if (isPromise(result)) {\n result.then(r => q.resolve(r)).catch(q.reject);\n } else {\n q.resolve(result);\n }\n } catch (err) {\n q.reject(err);\n }\n };\n\n session.once('ended', onEnded);\n\n const cancel = () => {\n session.off('ended', onEnded);\n };\n\n return {\n promise: q.promise,\n cancel,\n };\n}\n","import { createContext } from '@andrew_l/context';\nimport type { TransactionScope } from '../scope';\n\nexport const [injectTransactionScope, provideTransactionScope] =\n createContext<TransactionScope>([\n 'withTransaction',\n 'withTransactionControlled',\n 'withMongoTransaction',\n ]);\n","import { withContext } from '@andrew_l/context';\nimport {\n type Awaitable,\n assert,\n asyncForEach,\n catchError,\n env,\n isFunction,\n logger,\n noop,\n} from '@andrew_l/toolkit';\nimport { provideTransactionScope } from './hooks/scope';\n\nexport interface TransactionEffect {\n /**\n * Specifies when the transaction effect should run:\n *\n * `pre` - execute immediately\n *\n * `post` - execute before transaction commit\n *\n * @default: \"pre\"\n */\n flush: 'pre' | 'post';\n\n /**\n * Setup effect function. You can return a cleanup callback to be used as a rollback.\n */\n setup: EffectCallback;\n\n /**\n * Cleanup function.\n */\n cleanup?: EffectCleanup;\n\n /**\n * Useful for debugging execution logs.\n */\n name?: string;\n\n dependencies?: readonly any[];\n}\n\nexport type EffectCallback = () => Awaitable<EffectCleanup | void>;\n\nexport type EffectCleanup = () => Awaitable<void>;\n\nexport interface TransactionOnCommitted {\n callback: OnCommittedCallback;\n dependencies?: readonly any[];\n}\n\nexport type OnCommittedCallback = () => Awaitable<void>;\n\nexport interface TransactionOnRollback {\n callback: OnRollbackCallback;\n dependencies?: readonly any[];\n}\n\nexport type OnRollbackCallback = () => Awaitable<void>;\n\ninterface TransactionScopeHooks {\n effects: {\n cursor: number;\n byCursor: TransactionEffect[];\n };\n committed: {\n cursor: number;\n byCursor: TransactionOnCommitted[];\n };\n rollbacks: {\n cursor: number;\n byCursor: TransactionOnRollback[];\n };\n}\n\nexport class TransactionScope<T = any, Args extends any[] = any[]> {\n /**\n * @internal\n */\n log = logger('TransactionScope');\n\n /**\n * @internal\n * Indicates currently ran scope\n */\n _active: boolean = false;\n\n /**\n * Last run error\n */\n error: Error | undefined;\n\n /**\n * Last run result\n */\n result: T | undefined;\n\n run: (this: any, ...args: Args) => Promise<void>;\n\n /**\n * @internal\n */\n hooks: TransactionScopeHooks = {\n committed: { byCursor: [], cursor: 0 },\n effects: { byCursor: [], cursor: 0 },\n rollbacks: { byCursor: [], cursor: 0 },\n };\n\n constructor(private fn: (...args: Args) => T) {\n const scope = this;\n\n this.run = function (...args) {\n const self = this === scope ? undefined : this;\n return scope._run(self, ...args);\n };\n }\n\n get active(): boolean {\n return this._active;\n }\n\n /**\n * @internal\n */\n async _run(self?: any, ...args: Args): Promise<void> {\n assert.ok(!this._active, 'Cannot run while transaction active.');\n\n this.reset();\n this._active = true;\n\n const [cbError, cbResult] = await withContext(() => {\n provideTransactionScope(this);\n return catchError(this.fn.bind(self, ...(args as any[])));\n })();\n\n if (cbError) {\n this.error = cbError;\n } else {\n const applyError = await effectsApply(this, 'post');\n\n if (applyError) {\n this.error = applyError;\n }\n\n this.result = cbResult;\n }\n\n this._active = false;\n }\n\n async commit(): Promise<void> {\n assert.ok(!this._active, 'Cannot commit while transaction active.');\n assert.ok(!this.error, this.error);\n\n await asyncForEach(\n this.hooks.committed.byCursor,\n h => catchError(h.callback) as any,\n { concurrency: 4 },\n );\n\n this.reset();\n this.clean();\n }\n\n async rollback(): Promise<void> {\n assert.ok(!this._active, 'Cannot rollback while transaction active.');\n\n const error = await effectsCleanup(this);\n\n if (error) {\n return Promise.reject(error);\n }\n\n await asyncForEach(\n this.hooks.rollbacks.byCursor,\n h => catchError(h.callback) as any,\n { concurrency: 4 },\n );\n\n this.reset();\n this.clean();\n }\n\n reset() {\n assert.ok(!this._active, 'Cannot reset while transaction active.');\n\n this.hooks.effects.cursor = 0;\n this.hooks.committed.cursor = 0;\n this.hooks.rollbacks.cursor = 0;\n this.result = undefined;\n this.error = undefined;\n }\n\n clean() {\n assert.ok(!this._active, 'Cannot clean while transaction active.');\n this.hooks.effects = { byCursor: [], cursor: 0 };\n this.hooks.committed = { byCursor: [], cursor: 0 };\n this.hooks.rollbacks = { byCursor: [], cursor: 0 };\n }\n}\n\nexport function createTransactionScope<T = any, Args extends any[] = any[]>(\n fn: (...args: Args) => T,\n): TransactionScope<Awaited<T>, Args> {\n return new TransactionScope<any>(fn);\n}\n\nexport async function effectsApply(\n scope: TransactionScope,\n reason: string = 'no reason',\n): Promise<Error | undefined> {\n let error: Error | undefined;\n\n const onComplete = (err: Error | undefined) => void (error = err || error);\n\n await asyncForEach(\n scope.hooks.effects.byCursor,\n effect => applyEffect(scope, effect, reason).then(onComplete),\n {\n concurrency: 4,\n },\n );\n\n return error;\n}\n\nexport async function effectsCleanup(\n scope: TransactionScope,\n reason: string = 'no reason',\n): Promise<Error | undefined> {\n let error: Error | undefined;\n\n const onComplete = (err: Error | undefined) => void (error = err || error);\n\n await asyncForEach(\n scope.hooks.effects.byCursor,\n effect => cleanupEffect(scope, effect, reason).then(onComplete),\n {\n concurrency: 4,\n },\n );\n\n return error;\n}\n\nexport async function applyEffect(\n scope: TransactionScope,\n effect: TransactionEffect,\n reason: string = 'no reason',\n): Promise<Error | undefined> {\n if (effect.cleanup) return;\n\n scope.log.debug(\n 'Effect name = %s, flush = %s, apply by %s',\n effect.name,\n effect.flush,\n reason,\n );\n\n const [err, effectResult] = await catchError(effect.setup);\n\n if (err) {\n !env.isTest &&\n scope.log.error(\n 'Effect name = %s, flush = %s apply error',\n effect.name,\n effect.flush,\n err,\n );\n return err;\n }\n\n effect.cleanup = isFunction(effectResult) ? effectResult : noop;\n\n return;\n}\n\nexport async function cleanupEffect(\n scope: TransactionScope,\n effect: TransactionEffect,\n reason: string = 'no reason',\n): Promise<Error | undefined> {\n if (!effect.cleanup) return;\n\n scope.log.debug(\n 'Effect name = %s, flush = %s, cleanup by %s',\n effect.name,\n effect.flush,\n reason,\n );\n\n const [err] = await catchError(effect.cleanup);\n\n if (err) {\n !env.isTest &&\n scope.log.error(\n 'Effect name = %s, flush = %s, cleanup error',\n effect.name,\n effect.flush,\n err,\n );\n return err;\n }\n\n effect.cleanup = undefined;\n}\n","import { isEqual } from '@andrew_l/toolkit';\nimport {\n type TransactionEffect,\n type TransactionScope,\n applyEffect,\n cleanupEffect,\n} from '../scope';\nimport { injectTransactionScope } from './scope';\n\nexport type UseTransactionEffectOptions = Partial<\n Pick<TransactionEffect, 'name' | 'flush' | 'dependencies'>\n>;\n\n/**\n * Executes a transactional effect with cleanup on error or rollback.\n *\n * Ensures the `callback` function is executed only once per transaction, even during retries.\n * On errors or dependency changes, the cleanup logic is invoked before re-execution to maintain consistency.\n *\n * @param setup A function defining the transactional effect. It is guaranteed to run once per transaction\n * and may be re-executed after cleanup if dependencies change.\n *\n * @example\n * const confirmOrder = withMongoTransaction({\n * connection: () => mongoose.connection.getClient(),\n * async fn(session) {\n * // Register an alert as a transactional effect\n * await useTransactionEffect(async () => {\n * const alertId = await alertService.create({\n * title: `Order Confirmed: ${orderId}`,\n * });\n *\n * // Define cleanup logic to remove the alert on rollback\n * return () => alertService.removeById(alertId);\n * });\n *\n * // Simulate order processing (e.g., database updates)\n * await db\n * .collection('orders')\n * .updateOne({ orderId }, { $set: { status: 'confirmed' } }, { session });\n *\n * // Simulate an error to test rollback\n * throw new Error('Simulated transaction failure');\n * },\n * });\n *\n * @group Hooks\n */\nexport async function useTransactionEffect(\n setup: TransactionEffect['setup'],\n options: UseTransactionEffectOptions = {},\n): Promise<void> {\n const scope = injectTransactionScope();\n\n const { cursor, byCursor } = scope.hooks.effects;\n\n const effectConfig: Partial<TransactionEffect> = byCursor[cursor] ?? {};\n\n const flush = options?.flush || 'pre';\n const name = options?.name || `Effect #${cursor + 1}`;\n const dependencies = options.dependencies ?? [];\n const prevDependencies = effectConfig.dependencies;\n\n byCursor[cursor] = {\n ...effectConfig,\n dependencies,\n flush,\n name,\n setup,\n };\n\n if (dependencies && prevDependencies) {\n if (!isEqual(dependencies, prevDependencies)) {\n scope.log.debug(\n 'Effect name = %s, flush = %s, caused by dependencies',\n name,\n flush,\n {\n prevDependencies,\n newDependencies: dependencies,\n },\n );\n\n await scheduleEffect(scope, cursor);\n }\n } else {\n scope.log.debug(\n 'Effect name = %s, flush = %s, caused by missing dependencies',\n name,\n flush,\n );\n\n await scheduleEffect(scope, cursor);\n }\n\n scope.hooks.effects.cursor++;\n}\n\nasync function scheduleEffect(scope: TransactionScope, cursor: number) {\n const { byCursor } = scope.hooks.effects;\n const effect = byCursor[cursor]!;\n\n const cleanupErr = await cleanupEffect(scope, effect, 'schedule pre');\n\n if (cleanupErr) {\n return Promise.reject(cleanupErr);\n }\n\n if (effect.flush === 'pre') {\n const err = await applyEffect(scope, effect, 'schedule pre');\n\n if (err) {\n return Promise.reject(err);\n }\n }\n}\n","import { type Fn, isEqual, noop } from '@andrew_l/toolkit';\nimport type { OnCommittedCallback } from '../scope';\nimport { injectTransactionScope } from './scope';\n\n/**\n * Registers a callback to be executed upon transaction commitment, with support\n * for dependency-based updates.\n *\n * This function is used within a transaction scope to perform specific actions\n * when a transaction is committed. If dependencies are provided, the callback\n * is re-registered only if the dependencies have changed. Otherwise, the\n * callback is registered unconditionally.\n *\n * @param {OnCommittedCallback} callback - The function to be executed upon\n * transaction commitment.\n * @param {readonly any[]} [dependencies=[]] - An optional array of dependencies\n * to determine if the callback should be re-registered. If the dependencies\n * differ from the previously registered ones, the callback is updated.\n * @returns {Fn} A cleanup function to cancel event listener.\n *\n * @example\n * // Basic usage without dependencies\n * onCommitted(() => {\n * console.log('Transaction committed!');\n * });\n *\n * @example\n * // Using dependencies\n * count++;\n * onCommitted(() => {\n * console.log(`Commit #${count}`);\n * }, [count]);\n *\n * @example\n * // Cancel by request\n * const cancel = onCommitted(() => {\n * console.log('This will run only once!');\n * });\n *\n * if (orderReceived) {\n * cancel(); // Prevents onCommitted from running\n * }\n *\n * @group Hooks\n */\nexport function onCommitted(\n callback: OnCommittedCallback,\n dependencies?: readonly any[],\n): Fn {\n const scope = injectTransactionScope();\n const { cursor, byCursor } = scope.hooks.committed;\n\n const config = byCursor[cursor];\n\n if (dependencies && config?.dependencies) {\n if (!isEqual(dependencies, config.dependencies)) {\n scope.log.debug('OnCommitted caused by dependencies', {\n prevDependencies: config.dependencies,\n newDependencies: dependencies,\n cursor,\n });\n\n byCursor[cursor] = { callback, dependencies };\n }\n } else {\n scope.log.debug('OnCommitted caused by missing dependencies', { cursor });\n byCursor[cursor] = { callback, dependencies };\n }\n\n scope.hooks.committed.cursor++;\n\n return () => {\n byCursor[cursor].callback = noop;\n };\n}\n","import { type Fn, isEqual, noop } from '@andrew_l/toolkit';\nimport type { OnRollbackCallback } from '../scope';\nimport { injectTransactionScope } from './scope';\n\n/**\n * Registers a callback to be executed upon transaction rollback, with support\n * for dependency-based updates.\n *\n * This function is used within a transaction scope to perform specific actions\n * when a transaction is rolled back. If dependencies are provided, the callback\n * is re-registered only if the dependencies have changed. Otherwise, the\n * callback is registered unconditionally.\n *\n * @param {OnRollbackCallback} callback - The function to be executed upon\n * transaction rollback.\n * @param {readonly any[]} [dependencies=[]] - An optional array of dependencies\n * to determine if the callback should be re-registered. If the dependencies\n * differ from the previously registered ones, the callback is updated.\n * @returns {Fn} A cleanup function to cancel event listener.\n *\n * @example\n * // Basic usage without dependencies\n * onRollback(() => {\n * console.log('Transaction rolled back!');\n * });\n *\n * @example\n * // Using dependencies\n * count++;\n * onRollback(() => {\n * console.log(`Rollback detected, flag is ${flag}`);\n * }, [count]);\n *\n * @example\n * // Cancel by request\n * const cancel = onRollback(() => {\n * console.log('This will run only once on rollback!');\n * });\n *\n * if (orderReceived) {\n * cancel(); // Prevents onRollback from running\n * }\n *\n * @group Hooks\n */\nexport function onRollback(\n callback: OnRollbackCallback,\n dependencies?: readonly any[],\n): Fn {\n const scope = injectTransactionScope();\n const { cursor, byCursor } = scope.hooks.rollbacks;\n\n const config = byCursor[cursor];\n\n if (dependencies && config?.dependencies) {\n if (!isEqual(dependencies, config.dependencies)) {\n scope.log.debug('OnCommitted caused by dependencies', {\n prevDependencies: config.dependencies,\n newDependencies: dependencies,\n cursor,\n });\n\n byCursor[cursor] = { callback, dependencies };\n }\n } else {\n scope.log.debug('OnCommitted caused by missing dependencies', { cursor });\n byCursor[cursor] = { callback, dependencies };\n }\n\n scope.hooks.committed.cursor++;\n\n return () => {\n byCursor[cursor].callback = noop;\n };\n}\n","import { has, isFunction } from '@andrew_l/toolkit';\nimport type { Transaction } from 'mongodb';\nimport type {\n ClientSessionLike,\n MongoClientLike,\n} from './withMongoTransaction';\n\nexport function isTransactionAborted(transaction: Transaction): boolean {\n return (transaction as any)?.state === 'TRANSACTION_ABORTED';\n}\n\nexport function isTransactionCommittedEmpty(transaction: Transaction): boolean {\n return (transaction as any)?.state === 'TRANSACTION_COMMITTED_EMPTY';\n}\n\nexport function isMongoClientLike(value: unknown): value is MongoClientLike {\n return has(value, ['startSession']) && isFunction(value.startSession);\n}\n\nexport function isClientSessionLike(\n value: unknown,\n): value is ClientSessionLike {\n return (\n has(value, ['withTransaction', 'endSession']) &&\n isFunction(value.withTransaction) &&\n isFunction(value.endSession)\n );\n}\n","import {\n type ClientSession,\n type ClientSessionOptions,\n MongoTransactionError,\n} from 'mongodb';\n\nimport {\n type AnyFunction,\n type Awaitable,\n catchError,\n deepDefaults,\n isFunction,\n noop,\n} from '@andrew_l/toolkit';\nimport { provideMongoSession } from './hooks/useMongoSession';\nimport { createTransactionScope } from './scope';\nimport {\n isMongoClientLike,\n isTransactionAborted,\n isTransactionCommittedEmpty,\n} from './utils';\n\nconst DEF_SESSION_OPTIONS = Object.freeze({\n defaultTransactionOptions: {\n readPreference: 'primary',\n readConcern: { level: 'local' },\n writeConcern: { w: 'majority' },\n },\n} as ClientSessionOptions);\n\nexport interface MongoClientLike {\n startSession(options: Record<string, any>): ClientSessionLike;\n}\n\nexport interface ClientSessionLike {\n withTransaction(fn: AnyFunction): Promise<any>;\n endSession(): Promise<void>;\n}\n\ntype ConnectionValue = MongoClientLike | (() => Awaitable<MongoClientLike>);\n\ntype Callback<T, K = any, Args extends Array<any> = any[]> = (\n this: K,\n session: ClientSession,\n ...args: Args\n) => Awaitable<T>;\n\nexport interface WithMongoTransactionOptions<\n T,\n K = any,\n Args extends Array<any> = any[],\n> {\n /**\n * Mongodb connection getter\n */\n connection: ConnectionValue;\n\n /**\n * Transaction session options\n *\n * @default: {\n * defaultTransactionOptions: {\n * readPreference: 'primary',\n * readConcern: { level: 'local' },\n * writeConcern: { w: 'majority' },\n * }\n * }\n */\n sessionOptions?: ClientSessionOptions;\n\n /**\n * Configures a timeoutMS expiry for the entire withTransactionCallback.\n *\n * @remarks\n * - The remaining timeout will not be applied to callback operations that do not use the ClientSession.\n * - Overriding timeoutMS for operations executed using the explicit session inside the provided callback will result in a client-side error.\n */\n timeoutMS?: number;\n\n /**\n * Transaction function that will be executed\n *\n * ⚠️ Possible several times!\n */\n fn: Callback<T, K, Args>;\n}\n\ntype WithMongoTransactionWrapped<\n T,\n K = any,\n Args extends Array<any> = any[],\n> = (this: K, ...args: Args) => Promise<T>;\n\n/**\n * Runs a provided callback within a transaction, retrying either the commitTransaction operation or entire transaction as needed (and when the error permits) to better ensure that the transaction can complete successfully.\n *\n * Passes the session as the function's first argument or via `useMongoSession()` hook\n *\n * @example\n * const executeTransaction = withMongoTransaction({\n * connection: () => mongoose.connection.getClient(),\n * async fn() {\n * const session = useMongoSession();\n * const orders = mongoose.connection.collection('orders');\n *\n * const { modifiedCount } = await orders.updateMany(\n * { status: 'pending' },\n * { $set: { status: 'confirmed' } },\n * { session },\n * );\n * },\n * });\n *\n * @group Main\n */\nexport function withMongoTransaction<\n T,\n K = any,\n Args extends Array<any> = any[],\n>(\n options: WithMongoTransactionOptions<T, K, Args>,\n): WithMongoTransactionWrapped<T, K, Args>;\n\n/**\n * Runs a provided callback within a transaction, retrying either the commitTransaction operation or entire transaction as needed (and when the error permits) to better ensure that the transaction can complete successfully.\n *\n * Passes the session as the function's first argument or via `useMongoSession()` hook\n *\n * @example\n * const executeTransaction = withMongoTransaction(mongoose.connection.getClient(), async () => {\n * const session = useMongoSession();\n * const orders = mongoose.connection.collection('orders');\n *\n * const { modifiedCount } = await orders.updateMany(\n * { status: 'pending' },\n * { $set: { status: 'confirmed' } },\n * { session },\n * );\n * });\n */\nexport function withMongoTransaction<\n T,\n K = any,\n Args extends Array<any> = any[],\n>(\n connection: ConnectionValue,\n fn: Callback<T, K, Args>,\n options?: Omit<WithMongoTransactionOptions<any>, 'fn' | 'connection'>,\n): WithMongoTransactionWrapped<T, K, Args>;\n\nexport function withMongoTransaction(\n connectionOrOptions: ConnectionValue | WithMongoTransactionOptions<any>,\n maybeFn?: Callback<any>,\n maybeOptions?: Partial<WithMongoTransactionOptions<any>>,\n): WithMongoTransactionWrapped<any> {\n const {\n connection: connectionValue,\n fn,\n sessionOptions = {},\n timeoutMS,\n } = prepareOptions(connectionOrOptions, maybeFn, maybeOptions);\n\n return async function (this: any, ...args: any[]) {\n const connection = isFunction(connectionValue)\n ? await connectionValue()\n : connectionValue;\n\n const session = (await connection.startSession(\n sessionOptions,\n )) as ClientSession;\n\n const scope = createTransactionScope(function (this: any, ...args: any[]) {\n provideMongoSession(session);\n return fn.call(this, session, ...args);\n });\n\n const timeoutAt = timeoutMS ? Date.now() + timeoutMS : 0;\n const timeoutError = new MongoTransactionError(\n 'Transaction client-side timeout',\n );\n\n let [transactionError, transactionResult] = await catchError(() =>\n session.withTransaction(async () => {\n if (timeoutAt && timeoutAt < Date.now()) {\n return Promise.reject(timeoutError);\n }\n\n await scope.run.apply(this, args);\n\n if (scope.error) {\n return Promise.reject(scope.error);\n }\n }),\n );\n\n const { result } = scope;\n\n await session.endSession().catch(noop);\n\n if (\n transactionResult === undefined &&\n isTransactionCommittedEmpty(session.transaction)\n ) {\n // do nothing here\n } else if (\n isTransactionAborted(session.transaction) &&\n transactionResult === undefined &&\n transactionError === undefined\n ) {\n transactionError = new MongoTransactionError(\n 'Transaction is explicitly aborted',\n );\n }\n\n if (transactionError) {\n await scope.rollback();\n return Promise.reject(transactionError);\n }\n\n await scope.commit();\n\n return result!;\n };\n}\n\nfunction prepareOptions(\n connectionOrOptions: ConnectionValue | WithMongoTransactionOptions<any>,\n maybeFn?: Callback<any>,\n maybeOptions?: Partial<WithMongoTransactionOptions<any>>,\n): WithMongoTransactionOptions<any> {\n let options: WithMongoTransactionOptions<any>;\n\n if (\n isFunction(connectionOrOptions) ||\n isMongoClientLike(connectionOrOptions)\n ) {\n options = {\n ...(maybeOptions || {}),\n connection: connectionOrOptions,\n fn: maybeFn!,\n };\n } else {\n options = connectionOrOptions;\n }\n\n return deepDefaults(options, {\n sessionOptions: DEF_SESSION_OPTIONS,\n });\n}\n","import { type RetryOnErrorConfig, noop, retryOnError } from '@andrew_l/toolkit';\nimport { createTransactionScope } from './scope';\n\nexport interface WithTransactionOptions extends Partial<RetryOnErrorConfig> {}\n\n/**\n * Wraps a function with transaction context, enabling retry logic and transactional effects.\n *\n * The wrapped function may be executed multiple times (up to `maxRetriesNumber`) to ensure\n * all side effects complete successfully. If the retries are exhausted without success,\n * registered cleanup functions will be executed to undo any applied effects.\n *\n * This utility is useful for managing transactional side effects, such as\n * updates to external systems, and ensures proper cleanup in case of failure.\n *\n * Additionally, this enables hooks like `useTransactionEffect()`, which allows\n * defining effects with automatic rollback mechanisms.\n *\n * @param fn - The target function to wrap with transaction handling.\n * @param [options] - Configuration options for the transaction handling.\n * @param [options.beforeRetryCallback] - An optional callback to execute before each retry attempt.\n * @param [options.shouldRetryBasedOnError] - A predicate to determine if a retry should occur based on the thrown error. Defaults to always retry.\n * @param [options.maxRetriesNumber=5] - The maximum number of retries before failing the transaction. Defaults to 5.\n * @param [options.delayFactor=0] - A multiplier for the delay between retries. Default is 0 (no exponential backoff).\n * @param [options.delayMaxMs=1000] - The maximum delay between retries, in milliseconds. Defaults to 1000 ms.\n * @param [options.delayMinMs=100] - The minimum delay between retries, in milliseconds. Defaults to 100 ms.\n *\n * @example\n * const confirmOrder = withTransaction(async (orderId) => {\n * // Register Alert\n * await useTransactionEffect(async () => {\n * const alertId = await alertService.create({\n * title: 'New Order: ' + orderId,\n * });\n *\n * return () => alertService.removeById(alertId); // Cleanup in case of failure\n * });\n *\n * // Update Statistics\n * await useTransactionEffect(async () => {\n * await statService.increment('orders_amount', 1);\n *\n * return () => statService.decrement('orders_amount', 1); // Cleanup in case of failure\n * });\n *\n * // Simulate failure to trigger rollback\n * throw new Error('Cancel transaction.');\n * });\n *\n * @group Main\n */\nexport function withTransaction<T, K = any, Args extends Array<any> = any[]>(\n fn: (this: K, ...args: Args) => T,\n {\n beforeRetryCallback,\n shouldRetryBasedOnError = () => true,\n maxAttempts,\n maxRetriesNumber = 5,\n delayFactor = 0,\n delayMaxMs = 1000,\n delayMinMs = 100,\n }: WithTransactionOptions = {},\n): (this: K, ...args: Args) => Promise<Awaited<T>> {\n return async function (this: K, ...args: Args): Promise<Awaited<T>> {\n const scope = createTransactionScope(fn);\n\n await retryOnError(\n {\n beforeRetryCallback,\n shouldRetryBasedOnError,\n maxAttempts,\n maxRetriesNumber,\n delayFactor,\n delayMaxMs,\n delayMinMs,\n },\n async () => {\n await scope.run.apply(this, args);\n\n // explicitly reject to trigger retry\n if (scope.error) {\n return Promise.reject(scope.error);\n }\n },\n )().catch(noop);\n\n const { error, result } = scope;\n\n if (error) {\n await scope.rollback();\n return Promise.reject(error) as any;\n } else {\n await scope.commit();\n }\n\n return result as Awaited<T>;\n };\n}\n","import { createTransactionScope } from './scope';\n\nexport interface TransactionControlled<\n T,\n K = any,\n Args extends Array<any> = any[],\n> {\n run: (this: K, ...args: Args) => Promise<void>;\n\n commit: () => Promise<void>;\n\n rollback: () => Promise<void>;\n\n result: Readonly<T | undefined>;\n\n error: Readonly<Error | undefined>;\n\n active: boolean;\n}\n\n/**\n * Wraps a function and returns a `TransactionControlled` interface, allowing manual control\n * over transaction commit and rollback operations.\n *\n * This provides finer-grained control over the transaction lifecycle, enabling users to\n * explicitly commit or rollback a transaction based on custom logic. It's especially useful\n * in scenarios where transactional state or conditions need to be externally determined.\n *\n * @example\n * const t = withTransactionControlled(async (userId) => {\n * await useTransactionEffect(async () => {\n * await db.users.updateById(userId, { premium: true });\n *\n * return () => db.users.updateById(userId, { premium: false })\n * });\n *\n * const user = await db.users.findById(userId);\n *\n * return user;\n * });\n *\n *\n * await t.run();\n *\n * // Remove premium when no subscriptions\n * if (t.result.activeSubscriptions > 0) {\n * await t.commit();\n * } else {\n * await t.rollback();\n * }\n *\n * @group Main\n */\nexport function withTransactionControlled<\n T,\n K = any,\n Args extends Array<any> = any[],\n>(\n fn: (this: K, ...args: Args) => T,\n): TransactionControlled<Awaited<T>, K, Args> {\n const scope = createTransactionScope(fn);\n\n const controlled = {\n run(...args: Args) {\n const self = this === controlled ? undefined : this;\n return scope.run.apply(self, args);\n },\n commit() {\n return scope.commit();\n },\n rollback() {\n return scope.rollback();\n },\n get active() {\n return scope.active;\n },\n get result() {\n return scope.result;\n },\n get error() {\n return scope.error;\n },\n };\n\n return controlled;\n}\n"],"names":["createContext","hasInjectionContext","defer","isPromise","logger","assert","withContext","catchError","asyncForEach","env","isFunction","noop","isEqual","has","args","MongoTransactionError","deepDefaults","retryOnError"],"mappings":"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"}
|