@lossless.org/client 1.20.2 → 1.21.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.atomicdelete.js +3 -3
- package/dist_ts/nosqldb/classes.atomicfindoneandupdate.js +6 -3
- package/dist_ts/nosqldb/classes.atomicupdate.js +4 -4
- package/dist_ts/nosqldb/classes.bootstraprecord.js +10 -5
- package/dist_ts/nosqldb/classes.collection.js +53 -16
- package/dist_ts/nosqldb/classes.cursor.d.ts +4 -1
- package/dist_ts/nosqldb/classes.cursor.js +14 -3
- package/dist_ts/nosqldb/classes.db.d.ts +22 -0
- package/dist_ts/nosqldb/classes.db.js +25 -1
- package/dist_ts/nosqldb/classes.doc.js +35 -5
- package/dist_ts/nosqldb/classes.exactpersistence.d.ts +6 -0
- package/dist_ts/nosqldb/classes.exactpersistence.js +70 -13
- package/dist_ts/nosqldb/classes.readtracker.d.ts +87 -0
- package/dist_ts/nosqldb/classes.readtracker.js +876 -0
- package/dist_ts/nosqldb/classes.session.d.ts +30 -7
- package/dist_ts/nosqldb/classes.session.js +116 -26
- package/dist_ts/nosqldb/classes.storedinspection.js +12 -4
- package/dist_ts/nosqldb/classes.testdatabase.js +16 -9
- package/dist_ts/nosqldb/classes.writeobserver.d.ts +11 -0
- package/dist_ts/nosqldb/classes.writeobserver.js +1 -1
- package/package.json +1 -1
- package/readme.md +1 -0
- package/ts/00_commitinfo_data.ts +1 -1
- package/ts/nosqldb/classes.atomicdelete.ts +16 -2
- package/ts/nosqldb/classes.atomicfindoneandupdate.ts +5 -0
- package/ts/nosqldb/classes.atomicupdate.ts +10 -1
- package/ts/nosqldb/classes.bootstraprecord.ts +7 -4
- package/ts/nosqldb/classes.collection.ts +56 -12
- package/ts/nosqldb/classes.cursor.ts +14 -1
- package/ts/nosqldb/classes.db.ts +32 -0
- package/ts/nosqldb/classes.doc.ts +32 -2
- package/ts/nosqldb/classes.exactpersistence.ts +93 -19
- package/ts/nosqldb/classes.readtracker.ts +1028 -0
- package/ts/nosqldb/classes.session.ts +163 -30
- package/ts/nosqldb/classes.storedinspection.ts +15 -3
- package/ts/nosqldb/classes.testdatabase.ts +14 -8
- package/ts/nosqldb/classes.writeobserver.ts +11 -0
|
@@ -22,6 +22,16 @@ import {
|
|
|
22
22
|
type ISmartdataWrite,
|
|
23
23
|
type TSmartdataWriteKind,
|
|
24
24
|
} from './classes.writeobserver.js';
|
|
25
|
+
import {
|
|
26
|
+
captureSmartdataCollectionChange,
|
|
27
|
+
captureSmartdataReadChange,
|
|
28
|
+
hasSmartdataReadScope,
|
|
29
|
+
hasSmartdataReadSets,
|
|
30
|
+
observeSmartdataRead,
|
|
31
|
+
runSmartdataWrite,
|
|
32
|
+
type ISmartdataReadRecorder,
|
|
33
|
+
type ISmartdataReadSpec,
|
|
34
|
+
} from './classes.readtracker.js';
|
|
25
35
|
|
|
26
36
|
interface ISmartdataSessionBinding {
|
|
27
37
|
session: SmartdataSession;
|
|
@@ -43,14 +53,28 @@ interface ISmartdataSessionBinding {
|
|
|
43
53
|
transactionCommitReached: boolean;
|
|
44
54
|
/** Set when the session ended while a transaction ran, stating how far the transaction had got. */
|
|
45
55
|
transactionEnded?: TSmartdataTransactionEnd;
|
|
46
|
-
/**
|
|
47
|
-
|
|
56
|
+
/**
|
|
57
|
+
* The writes of the current attempt: their reports, delivered once the
|
|
58
|
+
* transaction committed, and their changes to open read sets, also applied
|
|
59
|
+
* when the commit outcome is unknown.
|
|
60
|
+
*/
|
|
61
|
+
transactionWrites: ISmartdataTransactionWrite[];
|
|
48
62
|
operationSettled?: Promise<void>;
|
|
49
63
|
resolveOperationSettled?: () => void;
|
|
50
64
|
invalidated: boolean;
|
|
51
65
|
closing?: Promise<void>;
|
|
52
66
|
}
|
|
53
67
|
|
|
68
|
+
interface ISmartdataTransactionWrite {
|
|
69
|
+
deliver?: () => void;
|
|
70
|
+
changeReads?: () => void;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/** A transaction whose commit outcome is unknown may have applied its writes. */
|
|
74
|
+
const changeReadsOfTransaction = (writesArg: readonly ISmartdataTransactionWrite[]): void => {
|
|
75
|
+
for (const write of writesArg) write.changeReads?.();
|
|
76
|
+
};
|
|
77
|
+
|
|
54
78
|
/** How far a transaction had got when its session ended outside the callback's control. */
|
|
55
79
|
type TSmartdataTransactionEnd = 'before_commit' | 'during_commit';
|
|
56
80
|
|
|
@@ -445,7 +469,7 @@ export class SmartdataSession {
|
|
|
445
469
|
};
|
|
446
470
|
let result: TResult | undefined;
|
|
447
471
|
let transactionEnded: TSmartdataTransactionEnd | undefined;
|
|
448
|
-
let committedWrites:
|
|
472
|
+
let committedWrites: ISmartdataTransactionWrite[] = [];
|
|
449
473
|
try {
|
|
450
474
|
result = timeoutMS === undefined
|
|
451
475
|
? await binding.rawSession.withTransaction(runAttempt)
|
|
@@ -454,6 +478,7 @@ export class SmartdataSession {
|
|
|
454
478
|
committedWrites = binding.transactionWrites;
|
|
455
479
|
} catch (errorArg) {
|
|
456
480
|
const { error, outcome } = settleTransactionRejection(errorArg);
|
|
481
|
+
if (outcome === 'unknown') changeReadsOfTransaction(binding.transactionWrites);
|
|
457
482
|
throw rejectTransaction(error, outcome);
|
|
458
483
|
} finally {
|
|
459
484
|
binding.transactionActive = false;
|
|
@@ -466,13 +491,13 @@ export class SmartdataSession {
|
|
|
466
491
|
binding.transactionWrites = [];
|
|
467
492
|
}
|
|
468
493
|
if (transactionEnded) {
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
);
|
|
494
|
+
const outcome = commitOutcomeOfTransactionEnd(transactionEnded);
|
|
495
|
+
if (outcome === 'unknown') changeReadsOfTransaction(committedWrites);
|
|
496
|
+
throw rejectTransaction(createTransactionSessionEndedError(transactionEnded), outcome);
|
|
473
497
|
}
|
|
474
|
-
|
|
475
|
-
|
|
498
|
+
changeReadsOfTransaction(committedWrites);
|
|
499
|
+
for (const write of committedWrites) {
|
|
500
|
+
write.deliver?.();
|
|
476
501
|
}
|
|
477
502
|
return result;
|
|
478
503
|
}
|
|
@@ -676,12 +701,14 @@ export const leaseOrdinarySmartdataSession = (
|
|
|
676
701
|
if (
|
|
677
702
|
optionsArg.ordinaryWrite
|
|
678
703
|
&& sessionArg?.inTransaction()
|
|
679
|
-
&& hasSmartdataWriteObservers(ownerArg)
|
|
704
|
+
&& (hasSmartdataWriteObservers(ownerArg) || hasSmartdataReadSets(ownerArg))
|
|
680
705
|
) {
|
|
681
706
|
// Only an owned transaction tells SmartData whether it committed.
|
|
682
707
|
throw new SmartdataPersistenceError(
|
|
683
708
|
'unsupported_operation',
|
|
684
|
-
|
|
709
|
+
hasSmartdataWriteObservers(ownerArg)
|
|
710
|
+
? 'Writes of an observed database run in transactions of db.createSession(), not raw driver sessions.'
|
|
711
|
+
: 'Writes of a database with open read sets run in transactions of db.createSession(), not raw driver sessions.',
|
|
685
712
|
);
|
|
686
713
|
}
|
|
687
714
|
return { rawSession: sessionArg, release: () => {} };
|
|
@@ -755,7 +782,9 @@ export const settleSmartdataOperationError = (
|
|
|
755
782
|
/**
|
|
756
783
|
* Runs one model operation on the ordinary session boundary: the owned
|
|
757
784
|
* session lease, the operation's client deadline and signal, and the mapping
|
|
758
|
-
* of an expired driver deadline to the `timeout` persistence error.
|
|
785
|
+
* of an expired driver deadline to the `timeout` persistence error. A write
|
|
786
|
+
* that fails once started may still have applied, so it changes every open
|
|
787
|
+
* read set that read its collection.
|
|
759
788
|
*/
|
|
760
789
|
export const runWithOrdinarySmartdataSession = async <TResult>(
|
|
761
790
|
sessionArg: TSmartdataOrdinarySession | undefined,
|
|
@@ -769,10 +798,19 @@ export const runWithOrdinarySmartdataSession = async <TResult>(
|
|
|
769
798
|
optionsArg.signal?.throwIfAborted();
|
|
770
799
|
const lease = leaseOrdinarySmartdataSession(sessionArg, ownerArg, optionsArg);
|
|
771
800
|
let deadline: SmartdataOperationDeadline | undefined;
|
|
801
|
+
let writeStarted = false;
|
|
772
802
|
try {
|
|
773
803
|
deadline = resolveSmartdataOperationDeadline(sessionArg, optionsArg);
|
|
774
|
-
|
|
804
|
+
if (!optionsArg.ordinaryWrite) {
|
|
805
|
+
return await operationArg(lease.rawSession, deadline);
|
|
806
|
+
}
|
|
807
|
+
const writeDeadline = deadline;
|
|
808
|
+
writeStarted = true;
|
|
809
|
+
return await runSmartdataWrite(ownerArg, () => operationArg(lease.rawSession, writeDeadline));
|
|
775
810
|
} catch (errorArg) {
|
|
811
|
+
if (writeStarted && optionsArg.collectionName) {
|
|
812
|
+
reportSmartdataWriteFailure(ownerArg, sessionArg, optionsArg.collectionName);
|
|
813
|
+
}
|
|
776
814
|
throw deadline
|
|
777
815
|
? settleSmartdataOperationError(sessionArg, deadline, errorArg)
|
|
778
816
|
: normalizeDeadlineError(errorArg);
|
|
@@ -782,50 +820,145 @@ export const runWithOrdinarySmartdataSession = async <TResult>(
|
|
|
782
820
|
};
|
|
783
821
|
|
|
784
822
|
/**
|
|
785
|
-
*
|
|
786
|
-
*
|
|
787
|
-
|
|
788
|
-
|
|
823
|
+
* Buffers one write of an owned transaction's current attempt. A write that
|
|
824
|
+
* settles after its attempt ended rolls back with that attempt.
|
|
825
|
+
*/
|
|
826
|
+
const bufferTransactionWrite = (
|
|
827
|
+
bindingArg: ISmartdataSessionBinding,
|
|
828
|
+
writeArg: ISmartdataTransactionWrite,
|
|
829
|
+
): void => {
|
|
830
|
+
const attempt = transactionContext.getStore()?.attempt;
|
|
831
|
+
if (attempt !== undefined && attempt === bindingArg.transactionAttempt) {
|
|
832
|
+
bindingArg.transactionWrites.push(writeArg);
|
|
833
|
+
}
|
|
834
|
+
};
|
|
835
|
+
|
|
836
|
+
/**
|
|
837
|
+
* Reports one acknowledged write to the owner's write observers and open read
|
|
838
|
+
* sets: at once, or, inside an owned transaction, after it committed. A
|
|
839
|
+
* transaction that rolls back reports nothing, one whose commit outcome is
|
|
840
|
+
* unknown changes the read sets only, and a retried attempt reports only its
|
|
841
|
+
* own writes. Read sets change before observers hear of the write.
|
|
789
842
|
*/
|
|
790
843
|
export const reportSmartdataWrite = (
|
|
791
844
|
ownerArg: SmartdataDb,
|
|
792
845
|
sessionArg: TSmartdataOrdinarySession | undefined,
|
|
793
846
|
writeArg: ISmartdataWrite,
|
|
794
847
|
): void => {
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
848
|
+
if (!isSmartdataSession(sessionArg) && sessionArg?.inTransaction()) {
|
|
849
|
+
// A raw driver transaction passed its lease while nobody observed or
|
|
850
|
+
// tracked, and whether it commits is never known here.
|
|
851
|
+
captureSmartdataCollectionChange(ownerArg, writeArg.collection, false)?.();
|
|
852
|
+
return;
|
|
853
|
+
}
|
|
854
|
+
const binding = isSmartdataSession(sessionArg) ? bindings.get(sessionArg) : undefined;
|
|
855
|
+
const buffered = binding?.transactionActive === true;
|
|
856
|
+
const changeReads = captureSmartdataReadChange(ownerArg, writeArg, buffered);
|
|
798
857
|
const deliver = captureSmartdataWrite(ownerArg, writeArg);
|
|
799
|
-
if (!deliver) return;
|
|
858
|
+
if (!changeReads && !deliver) return;
|
|
859
|
+
if (binding && buffered) {
|
|
860
|
+
bufferTransactionWrite(binding, { deliver, changeReads });
|
|
861
|
+
return;
|
|
862
|
+
}
|
|
863
|
+
changeReads?.();
|
|
864
|
+
deliver?.();
|
|
865
|
+
};
|
|
866
|
+
|
|
867
|
+
/**
|
|
868
|
+
* Reports a write to the collection that failed after it was sent: it may
|
|
869
|
+
* still have applied, so every open read set that read the collection
|
|
870
|
+
* changes, inside an owned transaction once it commits.
|
|
871
|
+
*/
|
|
872
|
+
export const reportSmartdataWriteFailure = (
|
|
873
|
+
ownerArg: SmartdataDb,
|
|
874
|
+
sessionArg: TSmartdataOrdinarySession | undefined,
|
|
875
|
+
collectionNameArg: string,
|
|
876
|
+
): void => {
|
|
800
877
|
const binding = isSmartdataSession(sessionArg) ? bindings.get(sessionArg) : undefined;
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
}
|
|
878
|
+
const buffered = binding?.transactionActive === true;
|
|
879
|
+
const changeReads = captureSmartdataCollectionChange(ownerArg, collectionNameArg, buffered);
|
|
880
|
+
if (!changeReads) return;
|
|
881
|
+
if (binding && buffered) {
|
|
882
|
+
bufferTransactionWrite(binding, { changeReads });
|
|
807
883
|
return;
|
|
808
884
|
}
|
|
809
|
-
|
|
885
|
+
changeReads();
|
|
810
886
|
};
|
|
811
887
|
|
|
812
|
-
/**
|
|
888
|
+
/**
|
|
889
|
+
* Reports one acknowledged write of a model collection; see
|
|
890
|
+
* `reportSmartdataWrite`. `readImagesArg` states which document states the
|
|
891
|
+
* documents are and the update applied; without it, read sets count the
|
|
892
|
+
* write as changing every read of the collection.
|
|
893
|
+
*/
|
|
813
894
|
export const reportSmartdataCollectionWrite = (
|
|
814
895
|
collectionArg: SmartdataCollection<any>,
|
|
815
896
|
sessionArg: TSmartdataOrdinarySession | undefined,
|
|
816
897
|
kindArg: TSmartdataWriteKind,
|
|
817
898
|
documentsArg: readonly object[] | null,
|
|
818
899
|
selectorArg?: unknown,
|
|
900
|
+
readImagesArg?: Pick<ISmartdataWrite, 'images' | 'update'>,
|
|
819
901
|
): void => {
|
|
902
|
+
const schema = collectionArg.getBoundModelSchema();
|
|
820
903
|
reportSmartdataWrite(collectionArg.smartdataDb, sessionArg, {
|
|
821
904
|
collection: collectionArg.collectionName,
|
|
822
905
|
kind: kindArg,
|
|
823
906
|
documents: documentsArg,
|
|
824
907
|
selector: selectorArg,
|
|
825
|
-
identityDocumentIdField:
|
|
908
|
+
identityDocumentIdField: schema?.identityAsDocumentId,
|
|
909
|
+
identityFields: identityFieldsOfSchema(schema),
|
|
910
|
+
...readImagesArg,
|
|
826
911
|
});
|
|
827
912
|
};
|
|
828
913
|
|
|
914
|
+
/** The identity fields a read or write names a document by besides `_id`. */
|
|
915
|
+
const identityFieldsOfSchema = (
|
|
916
|
+
schemaArg: ReturnType<SmartdataCollection<any>['getBoundModelSchema']>,
|
|
917
|
+
): readonly string[] | undefined =>
|
|
918
|
+
schemaArg && !schemaArg.identityAsDocumentId && schemaArg.identityFields.length > 0
|
|
919
|
+
? schemaArg.identityFields
|
|
920
|
+
: undefined;
|
|
921
|
+
|
|
922
|
+
/**
|
|
923
|
+
* Registers one read of a model collection with the open read sets of the
|
|
924
|
+
* current async context, right before it is issued; `undefined` when none is
|
|
925
|
+
* open. See `observeSmartdataRead`.
|
|
926
|
+
*/
|
|
927
|
+
export const trackSmartdataRead = (
|
|
928
|
+
collectionArg: SmartdataCollection<any>,
|
|
929
|
+
sessionArg: TSmartdataOrdinarySession | undefined,
|
|
930
|
+
specArg: Omit<ISmartdataReadSpec, 'collection' | 'identityFields'>,
|
|
931
|
+
): ISmartdataReadRecorder | undefined => trackSmartdataModelRead(
|
|
932
|
+
collectionArg.smartdataDb,
|
|
933
|
+
collectionArg.collectionName,
|
|
934
|
+
collectionArg.getBoundModelSchema(),
|
|
935
|
+
sessionArg,
|
|
936
|
+
specArg,
|
|
937
|
+
);
|
|
938
|
+
|
|
939
|
+
/** `trackSmartdataRead` for a read that names its model by schema rather than collection. */
|
|
940
|
+
export const trackSmartdataModelRead = (
|
|
941
|
+
dbArg: SmartdataDb,
|
|
942
|
+
collectionNameArg: string,
|
|
943
|
+
schemaArg: ReturnType<SmartdataCollection<any>['getBoundModelSchema']>,
|
|
944
|
+
sessionArg: TSmartdataOrdinarySession | undefined,
|
|
945
|
+
specArg: Omit<ISmartdataReadSpec, 'collection' | 'identityFields'>,
|
|
946
|
+
): ISmartdataReadRecorder | undefined => {
|
|
947
|
+
if (!hasSmartdataReadScope()) return undefined;
|
|
948
|
+
let inTransaction: boolean;
|
|
949
|
+
try {
|
|
950
|
+
inTransaction = isOrdinarySessionInTransaction(sessionArg);
|
|
951
|
+
} catch {
|
|
952
|
+
// An ended session's read fails; a set must not count it as followed.
|
|
953
|
+
inTransaction = true;
|
|
954
|
+
}
|
|
955
|
+
return observeSmartdataRead(dbArg, {
|
|
956
|
+
...specArg,
|
|
957
|
+
collection: collectionNameArg,
|
|
958
|
+
identityFields: identityFieldsOfSchema(schemaArg),
|
|
959
|
+
}, inTransaction);
|
|
960
|
+
};
|
|
961
|
+
|
|
829
962
|
/**
|
|
830
963
|
* Ends every session of the owner. `endOptionsArg.timeoutMS` is the driver's
|
|
831
964
|
* deadline for aborting a transaction a session still holds.
|
|
@@ -3,7 +3,9 @@ import type { SmartdataDb } from './classes.db.js';
|
|
|
3
3
|
import { convertFilterForMongoDb } from './classes.doc.js';
|
|
4
4
|
import { SmartdataPersistenceError } from './classes.persistence.js';
|
|
5
5
|
import { runCursorOperation } from './classes.cursoroperation.js';
|
|
6
|
-
import {
|
|
6
|
+
import {
|
|
7
|
+
acquireSmartdataSession, isSmartdataSession, trackSmartdataModelRead, type SmartdataSession,
|
|
8
|
+
} from './classes.session.js';
|
|
7
9
|
import {
|
|
8
10
|
collectionTopologyDbInspectionSymbol, collectionModelSchemaResolverSymbol,
|
|
9
11
|
} from './classes.collectiontopology.js';
|
|
@@ -79,6 +81,9 @@ const readStoredDocuments = async (
|
|
|
79
81
|
let page: { documents: plugins.mongodb.Document[]; hasMore: boolean };
|
|
80
82
|
try {
|
|
81
83
|
page = await dbArg[collectionTopologyDbInspectionSymbol](async (mongoDbArg) => {
|
|
84
|
+
const tracked = trackSmartdataModelRead(dbArg, schema.collectionName, schema, opts.session, {
|
|
85
|
+
kind: 'documents', filter, sort: { _id: 1 }, matches: skip > 0, limited: true,
|
|
86
|
+
});
|
|
82
87
|
const cursor = mongoDbArg.collection(schema.collectionName).find(filter, {
|
|
83
88
|
sort: { _id: 1 }, skip, limit: limit + 1, batchSize: 1,
|
|
84
89
|
timeoutMS, timeoutMode: 'cursorLifetime', signal: opts.signal, session: lease?.rawSession,
|
|
@@ -88,8 +93,15 @@ const readStoredDocuments = async (
|
|
|
88
93
|
let bytes = 0;
|
|
89
94
|
while (true) {
|
|
90
95
|
const next = await cursorArg.next();
|
|
91
|
-
if (next === null)
|
|
92
|
-
|
|
96
|
+
if (next === null) {
|
|
97
|
+
tracked?.settle();
|
|
98
|
+
return { documents, hasMore: false };
|
|
99
|
+
}
|
|
100
|
+
tracked?.record(next);
|
|
101
|
+
if (documents.length === limit) {
|
|
102
|
+
tracked?.settle();
|
|
103
|
+
return { documents, hasMore: true };
|
|
104
|
+
}
|
|
93
105
|
bytes += plugins.mongodb.BSON.calculateObjectSize(next);
|
|
94
106
|
if (bytes > maxBytes) throw new SmartdataPersistenceError('unsupported_operation',
|
|
95
107
|
'Stored inspection exceeds its byte budget.');
|
|
@@ -3,6 +3,7 @@ import { snapshotBoundedBsonDocument } from './classes.bsonsnapshot.js';
|
|
|
3
3
|
import { SmartdataDb } from './classes.db.js';
|
|
4
4
|
import { SmartDataDbDoc } from './classes.doc.js';
|
|
5
5
|
import { SmartdataPersistenceError } from './classes.persistence.js';
|
|
6
|
+
import { captureSmartdataCollectionChange } from './classes.readtracker.js';
|
|
6
7
|
import { assertDataOptions, SmartdataOperationBudget } from './classes.operationbudget.js';
|
|
7
8
|
|
|
8
9
|
/** A loopback TCP engine endpoint. */
|
|
@@ -255,14 +256,19 @@ export class SmartdataTestDatabase {
|
|
|
255
256
|
await modelArg.prepareCollection({ mode: 'ensureCompatible', timeoutMs: budget.remainingMs(), signal: budget.signal });
|
|
256
257
|
this.assertOwnedBinding();
|
|
257
258
|
const raw = this.db.mongoDb.collection(expected.collectionName);
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
259
|
+
try {
|
|
260
|
+
if (replacementArg) {
|
|
261
|
+
const result = await raw.replaceOne({ _id: document._id }, document,
|
|
262
|
+
{ upsert: false, ...budget.operationOptions() });
|
|
263
|
+
if (!result.acknowledged || result.matchedCount !== 1) throw new SmartdataPersistenceError(
|
|
264
|
+
'unsupported_operation', 'Exact fault replacement requires one existing document.');
|
|
265
|
+
} else {
|
|
266
|
+
const result = await raw.insertOne(document, budget.operationOptions());
|
|
267
|
+
if (!result.acknowledged) throw new SmartdataPersistenceError('unsupported_operation', 'Fault insertion was not acknowledged.');
|
|
268
|
+
}
|
|
269
|
+
} finally {
|
|
270
|
+
// A fault writes behind the models, so every open read set that read the collection changes.
|
|
271
|
+
captureSmartdataCollectionChange(this.db, expected.collectionName, false)?.();
|
|
266
272
|
}
|
|
267
273
|
budget.check();
|
|
268
274
|
})();
|
|
@@ -51,6 +51,17 @@ export interface ISmartdataWrite {
|
|
|
51
51
|
selector?: unknown;
|
|
52
52
|
/** The model's `identityAsDocumentId` field, whose value a selector carries as `_id`. */
|
|
53
53
|
identityDocumentIdField?: string;
|
|
54
|
+
/**
|
|
55
|
+
* Which stored states `documents` are, for read sets: the whole document
|
|
56
|
+
* after the write (for a replacement, the last one), before it, both
|
|
57
|
+
* (before, then after), or neither. A write without it changes every read
|
|
58
|
+
* of its collection.
|
|
59
|
+
*/
|
|
60
|
+
images?: 'after' | 'before' | 'beforeAndAfter' | 'none';
|
|
61
|
+
/** The update document a write by selector applied. */
|
|
62
|
+
update?: unknown;
|
|
63
|
+
/** The model's identity fields, by which a selector names a document without its `_id`. */
|
|
64
|
+
identityFields?: readonly string[];
|
|
54
65
|
}
|
|
55
66
|
|
|
56
67
|
interface IWriteObserver {
|