@lossless.org/client 1.11.0 → 1.13.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_ts/00_commitinfo_data.js +1 -1
- package/dist_ts/nosqldb/classes.db.d.ts +20 -3
- package/dist_ts/nosqldb/classes.db.js +45 -12
- package/dist_ts/nosqldb/classes.doc.d.ts +12 -2
- package/dist_ts/nosqldb/classes.doc.js +1 -1
- package/dist_ts/nosqldb/classes.operationbudget.d.ts +2 -0
- package/dist_ts/nosqldb/classes.operationbudget.js +7 -1
- package/dist_ts/nosqldb/classes.persistence.d.ts +6 -0
- package/dist_ts/nosqldb/classes.persistence.js +8 -1
- package/dist_ts/nosqldb/classes.session.d.ts +40 -1
- package/dist_ts/nosqldb/classes.session.js +188 -32
- package/package.json +4 -4
- package/readme.md +11 -7
- package/ts/00_commitinfo_data.ts +1 -1
- package/ts/nosqldb/classes.db.ts +68 -12
- package/ts/nosqldb/classes.doc.ts +19 -2
- package/ts/nosqldb/classes.operationbudget.ts +6 -0
- package/ts/nosqldb/classes.persistence.ts +9 -0
- package/ts/nosqldb/classes.session.ts +256 -41
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
SmartdataPersistenceError,
|
|
12
12
|
createSmartdataTimeoutError,
|
|
13
13
|
isMongoDeadlineError,
|
|
14
|
+
isMongoTransientTransactionError,
|
|
14
15
|
normalizeDeadlineError,
|
|
15
16
|
normalizeOrdinaryPersistenceError,
|
|
16
17
|
} from './classes.persistence.js';
|
|
@@ -31,12 +32,75 @@ interface ISmartdataSessionBinding {
|
|
|
31
32
|
transactionSignal?: AbortSignal;
|
|
32
33
|
transactionAttempt?: symbol;
|
|
33
34
|
transactionFailure?: { error: unknown };
|
|
35
|
+
/** Set once the current attempt's callback returned and the driver moves on to commit. */
|
|
36
|
+
transactionCommitReached: boolean;
|
|
37
|
+
/** Set when the session ended while a transaction ran, stating how far the transaction had got. */
|
|
38
|
+
transactionEnded?: TSmartdataTransactionEnd;
|
|
34
39
|
operationSettled?: Promise<void>;
|
|
35
40
|
resolveOperationSettled?: () => void;
|
|
36
41
|
invalidated: boolean;
|
|
37
42
|
closing?: Promise<void>;
|
|
38
43
|
}
|
|
39
44
|
|
|
45
|
+
/** How far a transaction had got when its session ended outside the callback's control. */
|
|
46
|
+
type TSmartdataTransactionEnd = 'before_commit' | 'during_commit';
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Whether a transaction that `SmartdataSession.withTransaction()` rejected
|
|
50
|
+
* could have committed:
|
|
51
|
+
*
|
|
52
|
+
* - `not_committed`: nothing of the transaction was committed. The call failed
|
|
53
|
+
* before the driver sent a commit, or the database answered the commit with
|
|
54
|
+
* an error that states the transaction was aborted.
|
|
55
|
+
* - `unknown`: a commit was sent and its outcome was never confirmed; it may
|
|
56
|
+
* have applied.
|
|
57
|
+
*/
|
|
58
|
+
export type TSmartdataTransactionCommitOutcome = 'not_committed' | 'unknown';
|
|
59
|
+
|
|
60
|
+
/** The commit outcome of every value a `withTransaction()` call rejected with. */
|
|
61
|
+
const transactionCommitOutcomes = new WeakMap<object, TSmartdataTransactionCommitOutcome>();
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Records what `errorArg`, the rejection of a `withTransaction()` call, means
|
|
65
|
+
* for the commit, and returns it for the throw. A value several transactions
|
|
66
|
+
* rejected with keeps `unknown` once any of them may have committed. A
|
|
67
|
+
* primitive cannot be recorded.
|
|
68
|
+
*/
|
|
69
|
+
const rejectTransaction = (
|
|
70
|
+
errorArg: unknown,
|
|
71
|
+
outcomeArg: TSmartdataTransactionCommitOutcome,
|
|
72
|
+
): unknown => {
|
|
73
|
+
if (
|
|
74
|
+
((typeof errorArg === 'object' && errorArg !== null) || typeof errorArg === 'function')
|
|
75
|
+
&& transactionCommitOutcomes.get(errorArg) !== 'unknown'
|
|
76
|
+
) {
|
|
77
|
+
transactionCommitOutcomes.set(errorArg, outcomeArg);
|
|
78
|
+
}
|
|
79
|
+
return errorArg;
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
const commitOutcomeOfTransactionEnd = (
|
|
83
|
+
endArg: TSmartdataTransactionEnd,
|
|
84
|
+
): TSmartdataTransactionCommitOutcome =>
|
|
85
|
+
endArg === 'during_commit' ? 'unknown' : 'not_committed';
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* The session of a transaction ended while it ran, for example through the
|
|
89
|
+
* database's `close()`. The driver then returns the callback's result without
|
|
90
|
+
* committing, so the call must reject instead.
|
|
91
|
+
*/
|
|
92
|
+
const createTransactionSessionEndedError = (
|
|
93
|
+
endArg: TSmartdataTransactionEnd,
|
|
94
|
+
causeArg?: unknown,
|
|
95
|
+
): SmartdataPersistenceError =>
|
|
96
|
+
new SmartdataPersistenceError(
|
|
97
|
+
'unsupported_operation',
|
|
98
|
+
endArg === 'during_commit'
|
|
99
|
+
? 'SmartData transaction session ended during commit; the commit outcome is unknown.'
|
|
100
|
+
: 'SmartData transaction session ended before commit; nothing was committed.',
|
|
101
|
+
causeArg === undefined ? undefined : { cause: causeArg },
|
|
102
|
+
);
|
|
103
|
+
|
|
40
104
|
const bindings = new WeakMap<SmartdataSession, ISmartdataSessionBinding>();
|
|
41
105
|
const bindingsByOwner = new WeakMap<SmartdataDb, Set<ISmartdataSessionBinding>>();
|
|
42
106
|
const smartdataSessionInvalidationAggregateBrand = Symbol(
|
|
@@ -130,6 +194,30 @@ const normalizeTransactionOptions = (
|
|
|
130
194
|
};
|
|
131
195
|
};
|
|
132
196
|
|
|
197
|
+
/** Everything `withTransaction()` refuses before a transaction starts. */
|
|
198
|
+
const admitTransaction = (
|
|
199
|
+
sessionArg: SmartdataSession,
|
|
200
|
+
callbackArg: unknown,
|
|
201
|
+
optionsArg: ISmartdataTransactionOptions | undefined,
|
|
202
|
+
): { binding: ISmartdataSessionBinding; timeoutMS?: number; signal?: AbortSignal } => {
|
|
203
|
+
if (typeof callbackArg !== 'function') {
|
|
204
|
+
throw new SmartdataPersistenceError(
|
|
205
|
+
'invalid_argument',
|
|
206
|
+
'SmartData transaction callback must be a function.',
|
|
207
|
+
);
|
|
208
|
+
}
|
|
209
|
+
const { timeoutMS, signal } = normalizeTransactionOptions(optionsArg);
|
|
210
|
+
const binding = resolveBinding(sessionArg);
|
|
211
|
+
if (binding.operationActive || binding.transactionActive) {
|
|
212
|
+
throw new SmartdataPersistenceError(
|
|
213
|
+
'unsupported_operation',
|
|
214
|
+
'Parallel or nested SmartData session operations are not supported.',
|
|
215
|
+
);
|
|
216
|
+
}
|
|
217
|
+
signal?.throwIfAborted();
|
|
218
|
+
return { binding, timeoutMS, signal };
|
|
219
|
+
};
|
|
220
|
+
|
|
133
221
|
const assertTransactionSucceeded = (bindingArg: ISmartdataSessionBinding): void => {
|
|
134
222
|
if (bindingArg.transactionFailure) {
|
|
135
223
|
throw bindingArg.transactionFailure.error;
|
|
@@ -137,6 +225,25 @@ const assertTransactionSucceeded = (bindingArg: ISmartdataSessionBinding): void
|
|
|
137
225
|
};
|
|
138
226
|
|
|
139
227
|
export class SmartdataSession {
|
|
228
|
+
/**
|
|
229
|
+
* Whether the transaction whose `withTransaction()` call rejected with
|
|
230
|
+
* `errorArg` could have committed: `not_committed` when nothing of it was
|
|
231
|
+
* committed, `unknown` when a commit was sent and never confirmed, so it may
|
|
232
|
+
* have applied. Every rejection of `withTransaction()` carries one, whatever
|
|
233
|
+
* it is: the client's own errors, the callback's error, the abort reason or
|
|
234
|
+
* a driver error, which all keep their identity. A value several calls
|
|
235
|
+
* rejected with reads `unknown` once any of them may have committed.
|
|
236
|
+
*
|
|
237
|
+
* Returns `undefined` for any other value, and for a primitive rejection —
|
|
238
|
+
* a callback that throws a string, or a string abort reason — which cannot
|
|
239
|
+
* carry the outcome; treat `undefined` as `unknown`.
|
|
240
|
+
*/
|
|
241
|
+
public static getCommitOutcome(errorArg: unknown): TSmartdataTransactionCommitOutcome | undefined {
|
|
242
|
+
return (typeof errorArg === 'object' && errorArg !== null) || typeof errorArg === 'function'
|
|
243
|
+
? transactionCommitOutcomes.get(errorArg)
|
|
244
|
+
: undefined;
|
|
245
|
+
}
|
|
246
|
+
|
|
140
247
|
/**
|
|
141
248
|
* Runs `callbackArg` in a transaction, retrying it as the driver decides.
|
|
142
249
|
*
|
|
@@ -162,42 +269,50 @@ export class SmartdataSession {
|
|
|
162
269
|
* Any operation on the session that its deadline or a signal interrupts
|
|
163
270
|
* fails the attempt: the call rolls it back and rejects with that
|
|
164
271
|
* operation's error even when the callback caught it.
|
|
272
|
+
*
|
|
273
|
+
* When the session ends while the call runs — the database's `close()`
|
|
274
|
+
* ends every session — the call never reports a commit. It rejects with
|
|
275
|
+
* `unsupported_operation`: before the callback returned nothing was
|
|
276
|
+
* committed, afterwards the commit outcome is unknown. The callback's
|
|
277
|
+
* error, if it threw, is the `cause`.
|
|
278
|
+
*
|
|
279
|
+
* Every rejection states whether the transaction could have committed:
|
|
280
|
+
* `SmartdataSession.getCommitOutcome(error)` returns `not_committed` or
|
|
281
|
+
* `unknown` for the value the call rejected with.
|
|
165
282
|
*/
|
|
166
283
|
public async withTransaction<TResult>(
|
|
167
284
|
callbackArg: (sessionArg: SmartdataSession) => Promise<TResult>,
|
|
168
285
|
optionsArg?: ISmartdataTransactionOptions,
|
|
169
286
|
): Promise<TResult | undefined> {
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
);
|
|
175
|
-
}
|
|
176
|
-
const { timeoutMS, signal } = normalizeTransactionOptions(optionsArg);
|
|
177
|
-
const binding = resolveBinding(this);
|
|
178
|
-
if (binding.operationActive || binding.transactionActive) {
|
|
179
|
-
throw new SmartdataPersistenceError(
|
|
180
|
-
'unsupported_operation',
|
|
181
|
-
'Parallel or nested SmartData session operations are not supported.',
|
|
182
|
-
);
|
|
287
|
+
let admitted: ReturnType<typeof admitTransaction>;
|
|
288
|
+
try {
|
|
289
|
+
admitted = admitTransaction(this, callbackArg, optionsArg);
|
|
290
|
+
} catch (errorArg) {
|
|
291
|
+
throw rejectTransaction(errorArg, 'not_committed');
|
|
183
292
|
}
|
|
184
|
-
signal
|
|
293
|
+
const { binding, timeoutMS, signal } = admitted;
|
|
185
294
|
binding.transactionActive = true;
|
|
186
295
|
binding.transactionTimeoutMS = timeoutMS;
|
|
187
296
|
binding.transactionSignal = signal;
|
|
297
|
+
binding.transactionEnded = undefined;
|
|
188
298
|
let callbackError: unknown;
|
|
189
|
-
|
|
299
|
+
/** The signal or deadline interruption that failed the last attempt. */
|
|
300
|
+
let attemptInterruption: { error: unknown } | undefined;
|
|
301
|
+
/** The session-ended rejection the last attempt failed with, and how far that transaction had got. */
|
|
302
|
+
let sessionEnded: { error: SmartdataPersistenceError; end: TSmartdataTransactionEnd } | undefined;
|
|
190
303
|
const runAttempt = async () => {
|
|
191
304
|
const attempt = Symbol('smartdataTransactionAttempt');
|
|
192
305
|
binding.transactionAttempt = attempt;
|
|
193
306
|
binding.transactionCallbackOpen = true;
|
|
194
307
|
binding.transactionUsesOrdinaryWrite = false;
|
|
195
308
|
binding.transactionFailure = undefined;
|
|
309
|
+
binding.transactionCommitReached = false;
|
|
196
310
|
callbackError = undefined;
|
|
197
|
-
|
|
311
|
+
attemptInterruption = undefined;
|
|
198
312
|
try {
|
|
199
313
|
let result: TResult;
|
|
200
314
|
let callbackReturned = false;
|
|
315
|
+
let callbackThrown: { error: unknown } | undefined;
|
|
201
316
|
try {
|
|
202
317
|
// A retry the driver starts after an abort ends before the callback.
|
|
203
318
|
signal?.throwIfAborted();
|
|
@@ -206,6 +321,9 @@ export class SmartdataSession {
|
|
|
206
321
|
async () => callbackArg(this),
|
|
207
322
|
);
|
|
208
323
|
callbackReturned = true;
|
|
324
|
+
} catch (errorArg) {
|
|
325
|
+
callbackThrown = { error: errorArg };
|
|
326
|
+
throw errorArg;
|
|
209
327
|
} finally {
|
|
210
328
|
const activeOperation = binding.operationActive
|
|
211
329
|
? binding.operationSettled
|
|
@@ -217,7 +335,20 @@ export class SmartdataSession {
|
|
|
217
335
|
if (activeOperation) {
|
|
218
336
|
await activeOperation;
|
|
219
337
|
}
|
|
338
|
+
attemptInterruption = binding.transactionFailure;
|
|
220
339
|
assertTransactionSucceeded(binding);
|
|
340
|
+
if (binding.transactionEnded) {
|
|
341
|
+
// The driver would commit or return the result on a session that is
|
|
342
|
+
// being ended; the attempt fails instead, keeping the callback's error.
|
|
343
|
+
sessionEnded = {
|
|
344
|
+
error: createTransactionSessionEndedError(
|
|
345
|
+
binding.transactionEnded,
|
|
346
|
+
callbackThrown?.error,
|
|
347
|
+
),
|
|
348
|
+
end: binding.transactionEnded,
|
|
349
|
+
};
|
|
350
|
+
throw sessionEnded.error;
|
|
351
|
+
}
|
|
221
352
|
if (callbackReturned && activeOperation) {
|
|
222
353
|
throw new SmartdataPersistenceError(
|
|
223
354
|
'unsupported_operation',
|
|
@@ -227,45 +358,107 @@ export class SmartdataSession {
|
|
|
227
358
|
}
|
|
228
359
|
// An abort that arrived while the callback ran rolls the attempt back.
|
|
229
360
|
signal?.throwIfAborted();
|
|
230
|
-
// The driver commits next; a deadline that expires
|
|
231
|
-
// leaves the commit outcome unknown.
|
|
232
|
-
|
|
361
|
+
// The driver commits next; a deadline that expires or a session that
|
|
362
|
+
// ends from here on leaves the commit outcome unknown.
|
|
363
|
+
binding.transactionCommitReached = true;
|
|
233
364
|
return result;
|
|
234
365
|
} catch (errorArg) {
|
|
235
366
|
callbackError = errorArg;
|
|
367
|
+
if (signal?.aborted && errorArg === signal.reason) {
|
|
368
|
+
attemptInterruption ??= { error: errorArg };
|
|
369
|
+
}
|
|
236
370
|
throw errorArg;
|
|
237
371
|
}
|
|
238
372
|
};
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
373
|
+
/**
|
|
374
|
+
* What the call rejects with when the driver's `withTransaction()` rejected
|
|
375
|
+
* with `errorArg`, and whether the transaction could have committed. Once
|
|
376
|
+
* the last attempt's callback returned, the driver was committing: every
|
|
377
|
+
* error from there on leaves the commit outcome unknown, unless it states
|
|
378
|
+
* that the transaction was aborted. The driver starts another attempt only
|
|
379
|
+
* after such an error, so an earlier attempt never committed.
|
|
380
|
+
*/
|
|
381
|
+
const settleTransactionRejection = (
|
|
382
|
+
errorArg: unknown,
|
|
383
|
+
): { error: unknown; outcome: TSmartdataTransactionCommitOutcome } => {
|
|
384
|
+
// The driver's closing abort replaces the attempt's error when it finds
|
|
385
|
+
// the transaction already aborted, as a session that close() ended leaves
|
|
386
|
+
// it; the interruption that failed the attempt keeps its rejection.
|
|
387
|
+
if (attemptInterruption) {
|
|
388
|
+
return { error: normalizeDeadlineError(attemptInterruption.error), outcome: 'not_committed' };
|
|
389
|
+
}
|
|
390
|
+
if (sessionEnded) {
|
|
391
|
+
return { error: sessionEnded.error, outcome: commitOutcomeOfTransactionEnd(sessionEnded.end) };
|
|
392
|
+
}
|
|
393
|
+
if (
|
|
394
|
+
binding.transactionEnded
|
|
395
|
+
&& errorArg !== signal?.reason
|
|
396
|
+
&& !isMongoDeadlineError(errorArg)
|
|
397
|
+
&& !(errorArg instanceof SmartdataPersistenceError && errorArg.code === 'timeout')
|
|
398
|
+
) {
|
|
399
|
+
return {
|
|
400
|
+
error: createTransactionSessionEndedError(binding.transactionEnded, errorArg),
|
|
401
|
+
outcome: commitOutcomeOfTransactionEnd(binding.transactionEnded),
|
|
402
|
+
};
|
|
403
|
+
}
|
|
244
404
|
if (errorArg === callbackError) {
|
|
245
|
-
|
|
405
|
+
return { error: normalizeDeadlineError(errorArg), outcome: 'not_committed' };
|
|
246
406
|
}
|
|
407
|
+
const outcome: TSmartdataTransactionCommitOutcome =
|
|
408
|
+
binding.transactionCommitReached && !isMongoTransientTransactionError(errorArg)
|
|
409
|
+
? 'unknown'
|
|
410
|
+
: 'not_committed';
|
|
247
411
|
if (isMongoDeadlineError(errorArg)) {
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
412
|
+
return {
|
|
413
|
+
error: createSmartdataTimeoutError(
|
|
414
|
+
outcome === 'unknown'
|
|
415
|
+
? 'SmartData transaction exceeded its client deadline during commit; the commit outcome is unknown.'
|
|
416
|
+
: binding.transactionCommitReached
|
|
417
|
+
? 'SmartData transaction exceeded its client deadline after the database aborted its commit; nothing was committed.'
|
|
418
|
+
: 'SmartData transaction exceeded its client deadline before commit; nothing was committed.',
|
|
419
|
+
errorArg,
|
|
420
|
+
),
|
|
421
|
+
outcome,
|
|
422
|
+
};
|
|
254
423
|
}
|
|
255
424
|
if (binding.transactionUsesOrdinaryWrite) {
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
425
|
+
try {
|
|
426
|
+
normalizeOrdinaryPersistenceError(
|
|
427
|
+
errorArg,
|
|
428
|
+
'SmartData transaction conflicts with a unique index.',
|
|
429
|
+
);
|
|
430
|
+
} catch (normalizedArg) {
|
|
431
|
+
return { error: normalizedArg, outcome };
|
|
432
|
+
}
|
|
260
433
|
}
|
|
261
|
-
|
|
434
|
+
return { error: errorArg, outcome };
|
|
435
|
+
};
|
|
436
|
+
let result: TResult | undefined;
|
|
437
|
+
let transactionEnded: TSmartdataTransactionEnd | undefined;
|
|
438
|
+
try {
|
|
439
|
+
result = timeoutMS === undefined
|
|
440
|
+
? await binding.rawSession.withTransaction(runAttempt)
|
|
441
|
+
: await binding.rawSession.withTransaction(runAttempt, { timeoutMS });
|
|
442
|
+
transactionEnded = binding.transactionEnded;
|
|
443
|
+
} catch (errorArg) {
|
|
444
|
+
const { error, outcome } = settleTransactionRejection(errorArg);
|
|
445
|
+
throw rejectTransaction(error, outcome);
|
|
262
446
|
} finally {
|
|
263
447
|
binding.transactionActive = false;
|
|
264
448
|
binding.transactionTimeoutMS = undefined;
|
|
265
449
|
binding.transactionSignal = undefined;
|
|
266
450
|
binding.transactionUsesOrdinaryWrite = false;
|
|
267
451
|
binding.transactionFailure = undefined;
|
|
452
|
+
binding.transactionCommitReached = false;
|
|
453
|
+
binding.transactionEnded = undefined;
|
|
268
454
|
}
|
|
455
|
+
if (transactionEnded) {
|
|
456
|
+
throw rejectTransaction(
|
|
457
|
+
createTransactionSessionEndedError(transactionEnded),
|
|
458
|
+
commitOutcomeOfTransactionEnd(transactionEnded),
|
|
459
|
+
);
|
|
460
|
+
}
|
|
461
|
+
return result;
|
|
269
462
|
}
|
|
270
463
|
|
|
271
464
|
public async close(): Promise<void> {
|
|
@@ -301,6 +494,7 @@ export const createSmartdataSession = (ownerArg: SmartdataDb): SmartdataSession
|
|
|
301
494
|
transactionActive: false,
|
|
302
495
|
transactionCallbackOpen: false,
|
|
303
496
|
transactionUsesOrdinaryWrite: false,
|
|
497
|
+
transactionCommitReached: false,
|
|
304
498
|
invalidated: false,
|
|
305
499
|
};
|
|
306
500
|
bindings.set(session, binding);
|
|
@@ -559,8 +753,13 @@ export const runWithOrdinarySmartdataSession = async <TResult>(
|
|
|
559
753
|
}
|
|
560
754
|
};
|
|
561
755
|
|
|
756
|
+
/**
|
|
757
|
+
* Ends every session of the owner. `endOptionsArg.timeoutMS` is the driver's
|
|
758
|
+
* deadline for aborting a transaction a session still holds.
|
|
759
|
+
*/
|
|
562
760
|
export const invalidateSmartdataSessions = async (
|
|
563
761
|
ownerArg: SmartdataDb,
|
|
762
|
+
endOptionsArg?: Pick<plugins.mongodb.EndSessionOptions, 'timeoutMS'>,
|
|
564
763
|
): Promise<void> => {
|
|
565
764
|
const ownerBindings = bindingsByOwner.get(ownerArg);
|
|
566
765
|
if (!ownerBindings) {
|
|
@@ -568,11 +767,11 @@ export const invalidateSmartdataSessions = async (
|
|
|
568
767
|
}
|
|
569
768
|
const sessions = [...ownerBindings];
|
|
570
769
|
for (const bindingArg of sessions) {
|
|
571
|
-
bindingArg
|
|
770
|
+
invalidateSessionBinding(bindingArg);
|
|
572
771
|
}
|
|
573
772
|
const results = await Promise.allSettled(
|
|
574
773
|
sessions.map((bindingArg) =>
|
|
575
|
-
closeSessionBinding(bindingArg),
|
|
774
|
+
closeSessionBinding(bindingArg, endOptionsArg),
|
|
576
775
|
),
|
|
577
776
|
);
|
|
578
777
|
const errors = results.flatMap((resultArg) =>
|
|
@@ -589,12 +788,28 @@ export const invalidateSmartdataSessions = async (
|
|
|
589
788
|
}
|
|
590
789
|
};
|
|
591
790
|
|
|
791
|
+
/**
|
|
792
|
+
* Refuses every later operation on the session. A transaction still running on
|
|
793
|
+
* it records how far it had got, so it rejects instead of reporting a commit.
|
|
794
|
+
*/
|
|
795
|
+
const invalidateSessionBinding = (bindingArg: ISmartdataSessionBinding): void => {
|
|
796
|
+
bindingArg.invalidated = true;
|
|
797
|
+
if (bindingArg.transactionActive) {
|
|
798
|
+
bindingArg.transactionEnded ??= bindingArg.transactionCommitReached
|
|
799
|
+
? 'during_commit'
|
|
800
|
+
: 'before_commit';
|
|
801
|
+
}
|
|
802
|
+
};
|
|
803
|
+
|
|
592
804
|
/** Failed cleanup remains owned and invalidated until an explicit retry succeeds. */
|
|
593
|
-
const closeSessionBinding = async (
|
|
805
|
+
const closeSessionBinding = async (
|
|
806
|
+
bindingArg: ISmartdataSessionBinding,
|
|
807
|
+
endOptionsArg?: Pick<plugins.mongodb.EndSessionOptions, 'timeoutMS'>,
|
|
808
|
+
): Promise<void> => {
|
|
594
809
|
if (bindingArg.closing) return bindingArg.closing;
|
|
595
|
-
bindingArg
|
|
810
|
+
invalidateSessionBinding(bindingArg);
|
|
596
811
|
const closing = Promise.resolve().then(async () => {
|
|
597
|
-
await bindingArg.rawSession.endSession();
|
|
812
|
+
await bindingArg.rawSession.endSession(endOptionsArg);
|
|
598
813
|
bindingsByOwner.get(bindingArg.owner)?.delete(bindingArg);
|
|
599
814
|
bindings.delete(bindingArg.session);
|
|
600
815
|
});
|