@lossless.org/client 1.23.0 → 1.23.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/dist_ts/00_commitinfo_data.js +1 -1
- package/dist_ts/nosqldb/classes.atomicdelete.js +3 -3
- package/dist_ts/nosqldb/classes.atomicfindoneandupdate.js +12 -5
- package/dist_ts/nosqldb/classes.atomicupdate.js +4 -4
- package/dist_ts/nosqldb/classes.collection.js +3 -3
- package/dist_ts/nosqldb/classes.db.js +4 -7
- package/dist_ts/nosqldb/classes.exactpersistence.js +3 -3
- package/dist_ts/nosqldb/classes.readtracker.d.ts +6 -0
- package/dist_ts/nosqldb/classes.readtracker.js +10 -1
- package/dist_ts/nosqldb/classes.session.d.ts +5 -2
- package/dist_ts/nosqldb/classes.session.js +29 -17
- package/dist_ts/nosqldb/classes.writerevisions.d.ts +39 -17
- package/dist_ts/nosqldb/classes.writerevisions.js +163 -126
- package/package.json +1 -1
- package/readme.md +1 -1
- package/ts/00_commitinfo_data.ts +1 -1
- package/ts/nosqldb/classes.atomicdelete.ts +2 -2
- package/ts/nosqldb/classes.atomicfindoneandupdate.ts +11 -6
- package/ts/nosqldb/classes.atomicupdate.ts +3 -4
- package/ts/nosqldb/classes.collection.ts +2 -2
- package/ts/nosqldb/classes.db.ts +2 -6
- package/ts/nosqldb/classes.exactpersistence.ts +2 -2
- package/ts/nosqldb/classes.readtracker.ts +10 -0
- package/ts/nosqldb/classes.session.ts +33 -18
- package/ts/nosqldb/classes.writerevisions.ts +214 -156
|
@@ -15,6 +15,8 @@ import {
|
|
|
15
15
|
type TSmartdataOrdinarySession,
|
|
16
16
|
} from './classes.session.js';
|
|
17
17
|
import { validateOrdinaryStoredDocument } from './classes.ordinarypersistence.js';
|
|
18
|
+
import { updateLeavesField } from './classes.readtracker.js';
|
|
19
|
+
import { writeRevisionsPartitionField } from './classes.writerevisions.js';
|
|
18
20
|
|
|
19
21
|
type TRawDocument = plugins.mongodb.WithId<plugins.mongodb.Document>;
|
|
20
22
|
|
|
@@ -129,11 +131,14 @@ export const executeAtomicFindOneAndUpdate = async <T>(
|
|
|
129
131
|
}
|
|
130
132
|
await deadlineArg.join(collectionArg.init());
|
|
131
133
|
try {
|
|
132
|
-
// A before image states the document's previous partition itself
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
134
|
+
// A before image states the document's previous partition itself, and
|
|
135
|
+
// so does an after image of an update that leaves the partition field
|
|
136
|
+
// alone: the field holds afterwards what it held before.
|
|
137
|
+
const partitionField = writeRevisionsPartitionField(collectionArg.smartdataDb);
|
|
138
|
+
const imageStatesPreviousPartition = optionsArg.returnDocument === 'before'
|
|
139
|
+
|| (partitionField !== undefined && updateLeavesField(updateArg, partitionField));
|
|
140
|
+
const previousPartitionsRead = !imageStatesPreviousPartition
|
|
141
|
+
&& await readPreviousWriteRevisions(collectionArg, rawSessionArg, filterArg, deadlineArg);
|
|
137
142
|
const result = await collectionArg.mongoDbCollection.findOneAndUpdate(
|
|
138
143
|
filterArg,
|
|
139
144
|
updateArg,
|
|
@@ -226,7 +231,7 @@ export const executeAtomicFindOneAndReplace = async <T>(
|
|
|
226
231
|
try {
|
|
227
232
|
const previousPartitionsRead = optionsArg.returnDocument === 'after'
|
|
228
233
|
&& await readPreviousWriteRevisions(
|
|
229
|
-
collectionArg
|
|
234
|
+
collectionArg, rawSessionArg, filterArg, deadlineArg,
|
|
230
235
|
);
|
|
231
236
|
const result = await collectionArg.mongoDbCollection.findOneAndReplace(
|
|
232
237
|
filterArg,
|
|
@@ -44,7 +44,7 @@ export const executeAtomicUpdate = async <T>(
|
|
|
44
44
|
await deadlineArg.join(collectionArg.init());
|
|
45
45
|
try {
|
|
46
46
|
const previousPartitionsRead = await readPreviousWriteRevisions(
|
|
47
|
-
collectionArg
|
|
47
|
+
collectionArg, rawSessionArg, filterArg, deadlineArg,
|
|
48
48
|
);
|
|
49
49
|
const result = await collectionArg.mongoDbCollection.updateOne(
|
|
50
50
|
filterArg,
|
|
@@ -140,8 +140,7 @@ export const executeAtomicUpsertMany = async <T>(
|
|
|
140
140
|
};
|
|
141
141
|
try {
|
|
142
142
|
previousPartitionsRead = await readPreviousWriteRevisions(
|
|
143
|
-
collectionArg
|
|
144
|
-
collectionArg.mongoDbCollection,
|
|
143
|
+
collectionArg,
|
|
145
144
|
rawSessionArg,
|
|
146
145
|
{ $or: operationsArg.map((operationArg) => operationArg.filter) },
|
|
147
146
|
deadlineArg,
|
|
@@ -219,7 +218,7 @@ export const executeAtomicUpdateMany = async <T>(
|
|
|
219
218
|
await deadlineArg.join(collectionArg.init());
|
|
220
219
|
try {
|
|
221
220
|
const previousPartitionsRead = await readPreviousWriteRevisions(
|
|
222
|
-
collectionArg
|
|
221
|
+
collectionArg, rawSessionArg, filterArg, deadlineArg,
|
|
223
222
|
);
|
|
224
223
|
const result = await collectionArg.mongoDbCollection.updateMany(
|
|
225
224
|
filterArg,
|
|
@@ -2924,7 +2924,7 @@ export class SmartdataCollection<T> {
|
|
|
2924
2924
|
};
|
|
2925
2925
|
try {
|
|
2926
2926
|
const previousPartitionsRead = await readPreviousWriteRevisions(
|
|
2927
|
-
this
|
|
2927
|
+
this, rawSessionArg, identifiableObject, deadlineArg,
|
|
2928
2928
|
);
|
|
2929
2929
|
const result = await this.mongoDbCollection.updateOne(
|
|
2930
2930
|
identifiableObject,
|
|
@@ -2969,7 +2969,7 @@ export class SmartdataCollection<T> {
|
|
|
2969
2969
|
? await dbDocArg.createSavableObject() as object
|
|
2970
2970
|
: undefined;
|
|
2971
2971
|
const previousPartitionsRead = await readPreviousWriteRevisions(
|
|
2972
|
-
this
|
|
2972
|
+
this, rawSessionArg, identifiableObject, deadlineArg,
|
|
2973
2973
|
);
|
|
2974
2974
|
const result = await this.mongoDbCollection.deleteOne(identifiableObject, {
|
|
2975
2975
|
session: rawSessionArg,
|
package/ts/nosqldb/classes.db.ts
CHANGED
|
@@ -34,7 +34,6 @@ import {
|
|
|
34
34
|
configureSmartdataWriteRevisions,
|
|
35
35
|
ensureSmartdataWriteRevisions,
|
|
36
36
|
isSmartdataWriteRevisionsActive,
|
|
37
|
-
stopSmartdataWriteRevisionCompactions,
|
|
38
37
|
type ISmartdataWriteRevisionsOptions,
|
|
39
38
|
} from './classes.writerevisions.js';
|
|
40
39
|
import {
|
|
@@ -432,11 +431,8 @@ export class SmartdataDb {
|
|
|
432
431
|
try {
|
|
433
432
|
const closeErrors: unknown[] = [];
|
|
434
433
|
try {
|
|
435
|
-
// Followers
|
|
436
|
-
const backgroundStop =
|
|
437
|
-
stopSmartdataWriteFollowers(this),
|
|
438
|
-
stopSmartdataWriteRevisionCompactions(this),
|
|
439
|
-
]);
|
|
434
|
+
// Followers abort at once; their reads end with the client otherwise.
|
|
435
|
+
const backgroundStop = stopSmartdataWriteFollowers(this);
|
|
440
436
|
if (budget) await budget.waitFor(backgroundStop);
|
|
441
437
|
await backgroundStop;
|
|
442
438
|
} catch (errorArg) {
|
|
@@ -1956,7 +1956,7 @@ export class SmartdataExactCollection<
|
|
|
1956
1956
|
// the write-revision partition it held is read first.
|
|
1957
1957
|
const previousPartitionsRead = current._smartdataRevision === undefined
|
|
1958
1958
|
&& await readPreviousWriteRevisions(
|
|
1959
|
-
this.collection
|
|
1959
|
+
this.collection, sessionArg, selector, deadlineArg,
|
|
1960
1960
|
);
|
|
1961
1961
|
const reportTransition = (): void => {
|
|
1962
1962
|
reportSmartdataCollectionWrite(
|
|
@@ -2064,7 +2064,7 @@ export class SmartdataExactCollection<
|
|
|
2064
2064
|
};
|
|
2065
2065
|
const previousPartitionsRead = current._smartdataRevision === undefined
|
|
2066
2066
|
&& await readPreviousWriteRevisions(
|
|
2067
|
-
this.collection
|
|
2067
|
+
this.collection, sessionArg, selector, deadlineArg,
|
|
2068
2068
|
);
|
|
2069
2069
|
let result: plugins.mongodb.DeleteResult;
|
|
2070
2070
|
const deleteOptions = { session: sessionArg, ...deadlineArg.commandOptions() };
|
|
@@ -1162,6 +1162,16 @@ export const captureSmartdataCollectionChange = (
|
|
|
1162
1162
|
return () => applyChanges(dbArg, collectionArg, [{ kind: 'unknown', touched: 'all', startedAt: 0 }]);
|
|
1163
1163
|
};
|
|
1164
1164
|
|
|
1165
|
+
/**
|
|
1166
|
+
* @internal Whether an update by operators leaves a field as each document it
|
|
1167
|
+
* updates held it: no operator names the field or a path through it. An update
|
|
1168
|
+
* the tracker cannot follow may change any field.
|
|
1169
|
+
*/
|
|
1170
|
+
export const updateLeavesField = (updateArg: unknown, fieldArg: string): boolean => {
|
|
1171
|
+
const { touched } = analyzeUpdate(updateArg, false);
|
|
1172
|
+
return touched !== 'all' && ![...touched].some((pathArg) => pathsOverlap(pathArg, fieldArg));
|
|
1173
|
+
};
|
|
1174
|
+
|
|
1165
1175
|
/**
|
|
1166
1176
|
* @internal The write-revision counters one acknowledged write bumps: the
|
|
1167
1177
|
* partition of each document state it names before and after, and the shared
|
|
@@ -37,10 +37,10 @@ import {
|
|
|
37
37
|
type ISmartdataReadSpec,
|
|
38
38
|
} from './classes.readtracker.js';
|
|
39
39
|
import {
|
|
40
|
-
|
|
40
|
+
beginSmartdataWriteRevisionAttempt,
|
|
41
41
|
flushSmartdataWriteRevisions,
|
|
42
42
|
isSmartdataWriteRevisionsActive,
|
|
43
|
-
|
|
43
|
+
type ISmartdataWriteRevisionAttempt,
|
|
44
44
|
readPreviousRevisionIds,
|
|
45
45
|
sharedRevisionId,
|
|
46
46
|
writeRevisionsPartitionField,
|
|
@@ -82,6 +82,8 @@ interface ISmartdataSessionBinding {
|
|
|
82
82
|
transactionWrites: ISmartdataTransactionWrite[];
|
|
83
83
|
/** The write-revision counters the current attempt's writes bump right before its commit. */
|
|
84
84
|
revisionBumps: Set<string>;
|
|
85
|
+
/** The slots of those counters the current attempt holds. */
|
|
86
|
+
revisionAttempt?: ISmartdataWriteRevisionAttempt;
|
|
85
87
|
/** The reports of the current attempt's writes, which its write-log entry carries. */
|
|
86
88
|
logReports: ISmartdataWriteReport[];
|
|
87
89
|
/** Collections the current attempt changed in ways no report describes. */
|
|
@@ -377,12 +379,14 @@ export class SmartdataSession {
|
|
|
377
379
|
let attemptInterruption: { error: unknown } | undefined;
|
|
378
380
|
/** The session-ended rejection the last attempt failed with, and how far that transaction had got. */
|
|
379
381
|
let sessionEnded: { error: SmartdataPersistenceError; end: TSmartdataTransactionEnd } | undefined;
|
|
380
|
-
let
|
|
382
|
+
let revisionAttempt: ISmartdataWriteRevisionAttempt | undefined;
|
|
381
383
|
/** The last attempt's write-log entry until the transaction's outcome is known. */
|
|
382
384
|
let logCommit: ISmartdataWriteLogCommit | undefined;
|
|
383
385
|
const runAttempt = async () => {
|
|
384
|
-
|
|
385
|
-
|
|
386
|
+
// The last attempt's slots end after this one starts, so it bumps others.
|
|
387
|
+
const attemptStartedAt = performance.now();
|
|
388
|
+
revisionAttempt?.finish(false);
|
|
389
|
+
revisionAttempt = beginSmartdataWriteRevisionAttempt(binding.owner, attemptStartedAt);
|
|
386
390
|
// The driver starts another attempt only once the last one was aborted.
|
|
387
391
|
logCommit?.settle();
|
|
388
392
|
logCommit = undefined;
|
|
@@ -394,6 +398,7 @@ export class SmartdataSession {
|
|
|
394
398
|
binding.transactionCommitReached = false;
|
|
395
399
|
binding.transactionWrites = [];
|
|
396
400
|
binding.revisionBumps = new Set();
|
|
401
|
+
binding.revisionAttempt = revisionAttempt;
|
|
397
402
|
binding.logReports = [];
|
|
398
403
|
binding.logUnplaced = new Set();
|
|
399
404
|
callbackError = undefined;
|
|
@@ -452,8 +457,8 @@ export class SmartdataSession {
|
|
|
452
457
|
// retried with it.
|
|
453
458
|
const mongoDb = binding.client.db(binding.owner.smartdataOptions.mongoDbName);
|
|
454
459
|
if (binding.revisionBumps.size > 0) {
|
|
455
|
-
|
|
456
|
-
await flushSmartdataWriteRevisions(mongoDb, binding.rawSession,
|
|
460
|
+
revisionAttempt.commit();
|
|
461
|
+
await flushSmartdataWriteRevisions(mongoDb, binding.rawSession, revisionAttempt.slots, signal);
|
|
457
462
|
}
|
|
458
463
|
logCommit = await flushSmartdataWriteLog(
|
|
459
464
|
binding.owner,
|
|
@@ -557,7 +562,7 @@ export class SmartdataSession {
|
|
|
557
562
|
throw rejectTransaction(error, outcome);
|
|
558
563
|
} finally {
|
|
559
564
|
if (!returnedFromCommit) {
|
|
560
|
-
|
|
565
|
+
revisionAttempt?.finish(false);
|
|
561
566
|
logCommit?.settle();
|
|
562
567
|
}
|
|
563
568
|
binding.transactionActive = false;
|
|
@@ -569,6 +574,7 @@ export class SmartdataSession {
|
|
|
569
574
|
binding.transactionEnded = undefined;
|
|
570
575
|
binding.transactionWrites = [];
|
|
571
576
|
binding.revisionBumps = new Set();
|
|
577
|
+
binding.revisionAttempt = undefined;
|
|
572
578
|
binding.logReports = [];
|
|
573
579
|
binding.logUnplaced = new Set();
|
|
574
580
|
}
|
|
@@ -578,7 +584,7 @@ export class SmartdataSession {
|
|
|
578
584
|
try {
|
|
579
585
|
if (outcome === 'unknown') changeReadsOfTransaction(committedWrites);
|
|
580
586
|
} finally {
|
|
581
|
-
|
|
587
|
+
revisionAttempt?.finish(false);
|
|
582
588
|
}
|
|
583
589
|
throw rejectTransaction(createTransactionSessionEndedError(transactionEnded), outcome);
|
|
584
590
|
}
|
|
@@ -586,9 +592,8 @@ export class SmartdataSession {
|
|
|
586
592
|
changeReadsOfTransaction(committedWrites);
|
|
587
593
|
accountSmartdataOwnWriteRevisions(binding.owner, committedRevisionBumps);
|
|
588
594
|
} finally {
|
|
589
|
-
|
|
595
|
+
revisionAttempt?.finish(true);
|
|
590
596
|
}
|
|
591
|
-
noteSmartdataWriteRevisionEntries(binding.owner, committedRevisionBumps);
|
|
592
597
|
for (const write of committedWrites) {
|
|
593
598
|
write.deliver?.();
|
|
594
599
|
}
|
|
@@ -1051,7 +1056,10 @@ const bumpTransactionRevisions = (
|
|
|
1051
1056
|
): void => {
|
|
1052
1057
|
const attempt = transactionContext.getStore()?.attempt;
|
|
1053
1058
|
if (attempt === undefined || attempt !== bindingArg.transactionAttempt) return;
|
|
1054
|
-
for (const id of idsArg)
|
|
1059
|
+
for (const id of idsArg) {
|
|
1060
|
+
bindingArg.revisionBumps.add(id);
|
|
1061
|
+
bindingArg.revisionAttempt?.bump(id);
|
|
1062
|
+
}
|
|
1055
1063
|
};
|
|
1056
1064
|
|
|
1057
1065
|
/**
|
|
@@ -1059,19 +1067,26 @@ const bumpTransactionRevisions = (
|
|
|
1059
1067
|
* documents a write by selector is about to change, and returns whether it
|
|
1060
1068
|
* did; the write's report then states `previousPartitionsRead`. `false` when
|
|
1061
1069
|
* the database keeps no revisions or the write runs outside an owned
|
|
1062
|
-
* transaction.
|
|
1070
|
+
* transaction. `false` without a read, too, when the collection's model
|
|
1071
|
+
* persists no partition field: its documents count on the shared counter,
|
|
1072
|
+
* which the write's report bumps for each document state it cannot place, and
|
|
1073
|
+
* every read of the collection depends on that counter.
|
|
1063
1074
|
*/
|
|
1064
1075
|
export const readPreviousWriteRevisions = async (
|
|
1065
|
-
|
|
1066
|
-
collectionArg: plugins.mongodb.Collection<any>,
|
|
1076
|
+
collectionArg: SmartdataCollection<any>,
|
|
1067
1077
|
rawSessionArg: plugins.mongodb.ClientSession | undefined,
|
|
1068
1078
|
selectorArg: unknown,
|
|
1069
1079
|
deadlineArg: SmartdataOperationDeadline,
|
|
1070
1080
|
): Promise<boolean> => {
|
|
1071
|
-
|
|
1081
|
+
const owner = collectionArg.smartdataDb;
|
|
1082
|
+
if (!rawSessionArg || !isSmartdataWriteRevisionsActive(owner)) return false;
|
|
1072
1083
|
const binding = bindingsByRawSession.get(rawSessionArg);
|
|
1073
|
-
if (!binding?.transactionActive || binding.owner !==
|
|
1074
|
-
const
|
|
1084
|
+
if (!binding?.transactionActive || binding.owner !== owner) return false;
|
|
1085
|
+
const persistedFields = collectionArg.getBoundModelSchema()?.persistedFields;
|
|
1086
|
+
if (persistedFields && !persistedFields.includes(writeRevisionsPartitionField(owner)!)) return false;
|
|
1087
|
+
const ids = await readPreviousRevisionIds(
|
|
1088
|
+
owner, collectionArg.mongoDbCollection, rawSessionArg, selectorArg, deadlineArg,
|
|
1089
|
+
);
|
|
1075
1090
|
bumpTransactionRevisions(binding, ids);
|
|
1076
1091
|
return true;
|
|
1077
1092
|
};
|