@peerbit/document 13.0.43 → 13.1.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/benchmark/document-put.js +1224 -63
- package/dist/benchmark/document-put.js.map +1 -1
- package/dist/src/index.d.ts +2 -0
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +2 -0
- package/dist/src/index.js.map +1 -1
- package/dist/src/native-rust.d.ts +50 -0
- package/dist/src/native-rust.d.ts.map +1 -0
- package/dist/src/native-rust.js +38 -0
- package/dist/src/native-rust.js.map +1 -0
- package/dist/src/policy.d.ts +51 -0
- package/dist/src/policy.d.ts.map +1 -0
- package/dist/src/policy.js +347 -0
- package/dist/src/policy.js.map +1 -0
- package/dist/src/program.d.ts +139 -22
- package/dist/src/program.d.ts.map +1 -1
- package/dist/src/program.js +3073 -342
- package/dist/src/program.js.map +1 -1
- package/dist/src/search.d.ts +78 -6
- package/dist/src/search.d.ts.map +1 -1
- package/dist/src/search.js +1105 -53
- package/dist/src/search.js.map +1 -1
- package/dist/src/transform.d.ts +42 -0
- package/dist/src/transform.d.ts.map +1 -0
- package/dist/src/transform.js +136 -0
- package/dist/src/transform.js.map +1 -0
- package/package.json +21 -16
- package/src/index.ts +2 -0
- package/src/native-rust.ts +152 -0
- package/src/policy.ts +592 -0
- package/src/program.ts +5019 -467
- package/src/search.ts +1777 -188
- package/src/transform.ts +277 -0
package/dist/src/program.js
CHANGED
|
@@ -33,17 +33,20 @@ var __runInitializers = (this && this.__runInitializers) || function (thisArg, i
|
|
|
33
33
|
return useValue ? value : void 0;
|
|
34
34
|
};
|
|
35
35
|
import { BorshError, field, serialize, variant, } from "@dao-xyz/borsh";
|
|
36
|
-
import { AccessError, SignatureWithKey } from "@peerbit/crypto";
|
|
37
|
-
import { NotFoundError, } from "@peerbit/document-interface";
|
|
36
|
+
import { AccessError, SignatureWithKey, } from "@peerbit/crypto";
|
|
37
|
+
import { Context, NotFoundError, } from "@peerbit/document-interface";
|
|
38
|
+
import { extractDocumentFieldSimple, initializeDocumentRust, planDocumentContext, planDocumentContextBatch, tryPlanDocumentContext, tryPlanDocumentContextBatch, } from "./native-rust.js";
|
|
38
39
|
import * as indexerTypes from "@peerbit/indexer-interface";
|
|
39
|
-
import { Entry, EntryType, } from "@peerbit/log";
|
|
40
|
+
import { Entry, EntryType, LamportClock, ShallowEntry, ShallowMeta, Timestamp, entryV0PlainPayloadDataFromStorage, } from "@peerbit/log";
|
|
40
41
|
import { logger as loggerFn } from "@peerbit/logger";
|
|
41
42
|
import { Program } from "@peerbit/program";
|
|
42
43
|
import { SharedLog, } from "@peerbit/shared-log";
|
|
43
44
|
import { MAX_BATCH_SIZE } from "./constants.js";
|
|
44
45
|
import { BORSH_ENCODING_OPERATION, DeleteOperation, PutOperation, PutWithKeyOperation, coerceDeleteOperation, isDeleteOperation, isPutOperation, } from "./operation.js";
|
|
46
|
+
import { createCanPerformPolicyEvaluator, createCanPerformDeletePolicyEvaluator, getCanPerformPolicyDescriptor, canPerformPolicyDeleteFieldPaths, canPerformPolicyNeedsDeleteValue, canPerformPolicyNeedsPreviousEntries, canPerformPolicyPutNeedsEntryPublicKeys, canPerformPolicySignedByFieldPaths, } from "./policy.js";
|
|
45
47
|
import { isResultIndexedValue } from "./result-shape.js";
|
|
46
|
-
import { DocumentIndex, INDEX_CONTEXT_SHAPE, coerceWithContext, coerceWithIndexed, } from "./search.js";
|
|
48
|
+
import { DocumentIndex, INDEX_CONTEXT_SHAPE, coerceWithContext, coerceWithIndexed, coerceWithLazyIndexed, encodeContextSuffix as encodeDocumentContextSuffix, } from "./search.js";
|
|
49
|
+
import { getDocumentTransformDescriptor, } from "./transform.js";
|
|
47
50
|
const logger = loggerFn("peerbit:program:document");
|
|
48
51
|
const warn = logger.newScope("warn");
|
|
49
52
|
export class OperationError extends Error {
|
|
@@ -51,6 +54,271 @@ export class OperationError extends Error {
|
|
|
51
54
|
super(message);
|
|
52
55
|
}
|
|
53
56
|
}
|
|
57
|
+
export class NativeDocumentModeError extends Error {
|
|
58
|
+
constructor(message) {
|
|
59
|
+
super(message);
|
|
60
|
+
this.name = "NativeDocumentModeError";
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
const isPromiseLike = (value) => !!value && typeof value.then === "function";
|
|
64
|
+
const mapMaybePromise = (value, fn) => (isPromiseLike(value) ? value.then(fn) : fn(value));
|
|
65
|
+
const bytesEqual = (left, right) => {
|
|
66
|
+
if (left.byteLength !== right.byteLength) {
|
|
67
|
+
return false;
|
|
68
|
+
}
|
|
69
|
+
for (let i = 0; i < left.byteLength; i++) {
|
|
70
|
+
if (left[i] !== right[i]) {
|
|
71
|
+
return false;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
return true;
|
|
75
|
+
};
|
|
76
|
+
const PUT_OPERATION_PREFIX_LENGTH = 6;
|
|
77
|
+
const encodePutOperationPayload = (data) => {
|
|
78
|
+
const encoded = new Uint8Array(PUT_OPERATION_PREFIX_LENGTH + data.byteLength);
|
|
79
|
+
encoded[0] = 0;
|
|
80
|
+
encoded[1] = 3;
|
|
81
|
+
const view = new DataView(encoded.buffer, encoded.byteOffset, encoded.byteLength);
|
|
82
|
+
view.setUint32(2, data.byteLength, true);
|
|
83
|
+
encoded.set(data, PUT_OPERATION_PREFIX_LENGTH);
|
|
84
|
+
return encoded;
|
|
85
|
+
};
|
|
86
|
+
const toContextBigInt = (value) => typeof value === "bigint" ? value : BigInt(value);
|
|
87
|
+
const NATIVE_LOCAL_PUT_OPTIONS = Object.freeze({
|
|
88
|
+
replicate: false,
|
|
89
|
+
target: "none",
|
|
90
|
+
});
|
|
91
|
+
const NATIVE_LOCAL_UNIQUE_PUT_OPTIONS = Object.freeze({
|
|
92
|
+
unique: true,
|
|
93
|
+
replicate: false,
|
|
94
|
+
target: "none",
|
|
95
|
+
});
|
|
96
|
+
const cachedNativeLocalPutOptions = (options) => {
|
|
97
|
+
if (!options) {
|
|
98
|
+
return NATIVE_LOCAL_PUT_OPTIONS;
|
|
99
|
+
}
|
|
100
|
+
let empty = true;
|
|
101
|
+
for (const key in options) {
|
|
102
|
+
empty = false;
|
|
103
|
+
if (key !== "unique") {
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
if (empty) {
|
|
108
|
+
return NATIVE_LOCAL_PUT_OPTIONS;
|
|
109
|
+
}
|
|
110
|
+
return options.unique === true ? NATIVE_LOCAL_UNIQUE_PUT_OPTIONS : undefined;
|
|
111
|
+
};
|
|
112
|
+
class CompatDocumentBackend {
|
|
113
|
+
putImpl;
|
|
114
|
+
putManyImpl;
|
|
115
|
+
deleteImpl;
|
|
116
|
+
constructor(putImpl, putManyImpl, deleteImpl) {
|
|
117
|
+
this.putImpl = putImpl;
|
|
118
|
+
this.putManyImpl = putManyImpl;
|
|
119
|
+
this.deleteImpl = deleteImpl;
|
|
120
|
+
}
|
|
121
|
+
put(doc, options) {
|
|
122
|
+
return this.putImpl(doc, options);
|
|
123
|
+
}
|
|
124
|
+
putMany(docs, options) {
|
|
125
|
+
return this.putManyImpl(docs, options);
|
|
126
|
+
}
|
|
127
|
+
del(id, options) {
|
|
128
|
+
return this.deleteImpl(id, options);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
class NativeDocumentBackend {
|
|
132
|
+
context;
|
|
133
|
+
constructor(context) {
|
|
134
|
+
this.context = context;
|
|
135
|
+
}
|
|
136
|
+
put(doc, options) {
|
|
137
|
+
this.context.assertPlainPutSupported(doc, options);
|
|
138
|
+
const putOptions = this.context.normalizePutOptions(options);
|
|
139
|
+
const prepared = this.context.preparePlainPut(doc);
|
|
140
|
+
const commit = (existing, useNativeExistingDocumentContext = !putOptions?.unique &&
|
|
141
|
+
existing === undefined, requiredPreviousSignerPublicKey) => {
|
|
142
|
+
const nextEntry = existing
|
|
143
|
+
? this.context.getNextFromIndexedContext(existing)
|
|
144
|
+
: undefined;
|
|
145
|
+
if (existing && !nextEntry) {
|
|
146
|
+
throw this.context.nativeModeError("requires indexed document context for native put");
|
|
147
|
+
}
|
|
148
|
+
const next = nextEntry ? [nextEntry] : [];
|
|
149
|
+
return mapMaybePromise(this.context.commitNativeDocumentAppend({
|
|
150
|
+
document: prepared.document,
|
|
151
|
+
key: prepared.key,
|
|
152
|
+
documentBytes: prepared.encodedDocument,
|
|
153
|
+
operationPayloadBytes: prepared.operationPayloadBytes,
|
|
154
|
+
next: next,
|
|
155
|
+
skipMissingNextJoin: true,
|
|
156
|
+
resolveTrimmedEntries: this.context.shouldResolveTrimmedEntries(),
|
|
157
|
+
options: putOptions,
|
|
158
|
+
unique: putOptions?.unique,
|
|
159
|
+
useNativeExistingDocumentContext,
|
|
160
|
+
requiredPreviousSignerPublicKey,
|
|
161
|
+
existing,
|
|
162
|
+
}), (documentAppendCommit) => mapMaybePromise(this.context.handlePreparedPlainPutCommit(documentAppendCommit), () => {
|
|
163
|
+
this.context.keepEntry(documentAppendCommit.append.hash);
|
|
164
|
+
return {
|
|
165
|
+
get entry() {
|
|
166
|
+
return documentAppendCommit.entry;
|
|
167
|
+
},
|
|
168
|
+
removed: documentAppendCommit.removed,
|
|
169
|
+
};
|
|
170
|
+
}));
|
|
171
|
+
};
|
|
172
|
+
const assertPolicyAndCommit = (existingContext, previousSignerPublicKey) => {
|
|
173
|
+
const existingHead = this.context.getIndexedContextHead(existingContext);
|
|
174
|
+
const nativePreviousSignerPublicKey = previousSignerPublicKey ??
|
|
175
|
+
(existingHead
|
|
176
|
+
? this.context.getNativeEntrySignerPublicKeys([existingHead])?.[0]
|
|
177
|
+
: undefined);
|
|
178
|
+
return mapMaybePromise(this.context.assertPlainPutPolicySupported(prepared.document, existingContext, nativePreviousSignerPublicKey), () => commit(existingContext));
|
|
179
|
+
};
|
|
180
|
+
if (!putOptions?.unique &&
|
|
181
|
+
this.context.plainPutPolicyNeedsExistingContext()) {
|
|
182
|
+
const requiredPreviousSignerPublicKey = this.context.getNativeAppendRequiredPreviousSignerPublicKey();
|
|
183
|
+
if (requiredPreviousSignerPublicKey) {
|
|
184
|
+
return commit(undefined, true, requiredPreviousSignerPublicKey);
|
|
185
|
+
}
|
|
186
|
+
const nativeContexts = this.context.getNativeIndexedContextsAndPreviousSignerPublicKeys([
|
|
187
|
+
prepared.key,
|
|
188
|
+
]);
|
|
189
|
+
if (nativeContexts) {
|
|
190
|
+
return assertPolicyAndCommit(nativeContexts.contexts[0] ?? null, nativeContexts.publicKeys[0]);
|
|
191
|
+
}
|
|
192
|
+
const nativePreviousSigner = this.context.getNativePreviousEntrySignerPublicKey(prepared.key);
|
|
193
|
+
if (nativePreviousSigner) {
|
|
194
|
+
if (!nativePreviousSigner.exists) {
|
|
195
|
+
return assertPolicyAndCommit(null);
|
|
196
|
+
}
|
|
197
|
+
if (nativePreviousSigner.publicKey) {
|
|
198
|
+
return mapMaybePromise(this.context.assertPlainPutPolicySupported(prepared.document, undefined, nativePreviousSigner.publicKey), () => commit());
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
throw this.context.nativeModeError("requires native document context/signature facts");
|
|
202
|
+
}
|
|
203
|
+
return assertPolicyAndCommit();
|
|
204
|
+
}
|
|
205
|
+
async putMany(docs, options) {
|
|
206
|
+
if (docs.length === 0) {
|
|
207
|
+
return { entries: [], removed: [] };
|
|
208
|
+
}
|
|
209
|
+
const putOptions = this.context.normalizePutOptions(options);
|
|
210
|
+
for (const doc of docs) {
|
|
211
|
+
this.context.assertPlainPutSupported(doc, putOptions);
|
|
212
|
+
}
|
|
213
|
+
const prepared = docs.map((doc) => this.context.preparePlainPut(doc));
|
|
214
|
+
if (this.context.hasDuplicatePreparedPutKeys(prepared)) {
|
|
215
|
+
const results = [];
|
|
216
|
+
for (const doc of docs) {
|
|
217
|
+
results.push(await this.put(doc, putOptions));
|
|
218
|
+
}
|
|
219
|
+
let entries;
|
|
220
|
+
return {
|
|
221
|
+
get entries() {
|
|
222
|
+
return (entries ??= results.map((result) => result.entry));
|
|
223
|
+
},
|
|
224
|
+
removed: results.flatMap((result) => result.removed),
|
|
225
|
+
};
|
|
226
|
+
}
|
|
227
|
+
this.context.assertPlainPutManySupported(docs, putOptions);
|
|
228
|
+
let existingContexts;
|
|
229
|
+
let previousSignerPublicKeys;
|
|
230
|
+
const policyNeedsExistingContext = this.context.plainPutPolicyNeedsExistingContext();
|
|
231
|
+
const requiredPreviousSignerPublicKey = this.context.getNativeAppendRequiredPreviousSignerPublicKey();
|
|
232
|
+
const useNativeExistingDocumentContext = putOptions?.unique !== true &&
|
|
233
|
+
(!policyNeedsExistingContext || !!requiredPreviousSignerPublicKey);
|
|
234
|
+
if (putOptions?.unique !== true && !useNativeExistingDocumentContext) {
|
|
235
|
+
const keys = prepared.map((item) => item.key);
|
|
236
|
+
const nativeContexts = this.context.getNativeIndexedContextsAndPreviousSignerPublicKeys(keys);
|
|
237
|
+
if (nativeContexts) {
|
|
238
|
+
existingContexts = nativeContexts.contexts;
|
|
239
|
+
if (policyNeedsExistingContext) {
|
|
240
|
+
previousSignerPublicKeys = nativeContexts.publicKeys;
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
else {
|
|
244
|
+
throw this.context.nativeModeError("requires native document context/signature batch facts");
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
if (existingContexts &&
|
|
248
|
+
!previousSignerPublicKeys &&
|
|
249
|
+
policyNeedsExistingContext) {
|
|
250
|
+
const previousHeads = existingContexts.map((existing) => this.context.getIndexedContextHead(existing ?? null));
|
|
251
|
+
if (previousHeads.some((head) => head != null)) {
|
|
252
|
+
previousSignerPublicKeys = new Array(prepared.length);
|
|
253
|
+
const lookupIndexes = [];
|
|
254
|
+
const lookupHashes = [];
|
|
255
|
+
for (let i = 0; i < previousHeads.length; i++) {
|
|
256
|
+
const head = previousHeads[i];
|
|
257
|
+
if (head) {
|
|
258
|
+
lookupIndexes.push(i);
|
|
259
|
+
lookupHashes.push(head);
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
const lookup = this.context.getNativeEntrySignerPublicKeys(lookupHashes);
|
|
263
|
+
if (lookup) {
|
|
264
|
+
for (let i = 0; i < lookup.length; i++) {
|
|
265
|
+
previousSignerPublicKeys[lookupIndexes[i]] = lookup[i];
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
if (!requiredPreviousSignerPublicKey) {
|
|
271
|
+
await Promise.all(prepared.map((item, index) => this.context.assertPlainPutPolicySupported(item.document, existingContexts ? (existingContexts[index] ?? null) : undefined, previousSignerPublicKeys?.[index])));
|
|
272
|
+
}
|
|
273
|
+
return mapMaybePromise(this.context.commitNativeDocumentAppendMany({
|
|
274
|
+
puts: prepared.map((item, index) => ({
|
|
275
|
+
document: item.document,
|
|
276
|
+
key: item.key,
|
|
277
|
+
documentBytes: item.encodedDocument,
|
|
278
|
+
operationPayloadBytes: item.operationPayloadBytes,
|
|
279
|
+
unique: putOptions?.unique,
|
|
280
|
+
requiredPreviousSignerPublicKey,
|
|
281
|
+
existing: existingContexts
|
|
282
|
+
? (existingContexts[index] ?? null)
|
|
283
|
+
: useNativeExistingDocumentContext
|
|
284
|
+
? undefined
|
|
285
|
+
: null,
|
|
286
|
+
})),
|
|
287
|
+
resolveTrimmedEntries: this.context.shouldResolveTrimmedEntries(),
|
|
288
|
+
options: putOptions,
|
|
289
|
+
useNativeExistingDocumentContext,
|
|
290
|
+
}), (documentAppendCommit) => {
|
|
291
|
+
if (!documentAppendCommit) {
|
|
292
|
+
throw this.context.nativeModeError("requires native batched payload append support");
|
|
293
|
+
}
|
|
294
|
+
return mapMaybePromise(this.context.handlePreparedPlainPutManyCommit(documentAppendCommit), () => {
|
|
295
|
+
for (const commit of documentAppendCommit.commits) {
|
|
296
|
+
this.context.keepEntry(commit.append.hash);
|
|
297
|
+
}
|
|
298
|
+
return {
|
|
299
|
+
get entries() {
|
|
300
|
+
return documentAppendCommit.entries;
|
|
301
|
+
},
|
|
302
|
+
removed: documentAppendCommit.removed,
|
|
303
|
+
};
|
|
304
|
+
});
|
|
305
|
+
});
|
|
306
|
+
}
|
|
307
|
+
del(id, options) {
|
|
308
|
+
const deleteOptions = this.context.normalizePutOptions(options);
|
|
309
|
+
return this.context.deleteDocument(id, deleteOptions);
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
const nativeDocumentContextFactsAsContext = (facts) => facts;
|
|
313
|
+
const asTrustedDocumentSharedLog = (log) => log;
|
|
314
|
+
const documentIndexStoreKey = (id) => {
|
|
315
|
+
const key = indexerTypes.toIdeable(id);
|
|
316
|
+
if (key instanceof Uint8Array || ArrayBuffer.isView(key)) {
|
|
317
|
+
return `bytes:${id.primitive.toString()}`;
|
|
318
|
+
}
|
|
319
|
+
return `${typeof key}:${key.toString()}`;
|
|
320
|
+
};
|
|
321
|
+
const asTrustedDocumentIndex = (index) => index;
|
|
54
322
|
let Documents = (() => {
|
|
55
323
|
let _classDecorators = [variant("documents")];
|
|
56
324
|
let _classDescriptor;
|
|
@@ -86,6 +354,21 @@ let Documents = (() => {
|
|
|
86
354
|
_index = (__runInitializers(this, _immutable_extraInitializers), __runInitializers(this, __index_initializers, void 0));
|
|
87
355
|
_clazz = __runInitializers(this, __index_extraInitializers);
|
|
88
356
|
_optionCanPerform;
|
|
357
|
+
_optionCanPerformNativePolicy;
|
|
358
|
+
_optionCanPerformNativeFastPath;
|
|
359
|
+
_nativeBackboneDocumentIndexEnabled = false;
|
|
360
|
+
_mode = "auto";
|
|
361
|
+
_nativeModeReplicatedOpen = false;
|
|
362
|
+
_valueClassIsProgram = false;
|
|
363
|
+
_documentChangeListeners = [];
|
|
364
|
+
_documentChangeListenerCount = 0;
|
|
365
|
+
_documentInternalChangeListenerCount = 0;
|
|
366
|
+
_documentChangeListenerTrackingInitialized = false;
|
|
367
|
+
_documentBackend;
|
|
368
|
+
_canAppendDecodedDocuments = new WeakMap();
|
|
369
|
+
_nativeDocumentIdExtractionPlan;
|
|
370
|
+
_nativeDocumentFieldExtractionPlans;
|
|
371
|
+
_hasLogTrim = false;
|
|
89
372
|
idResolver;
|
|
90
373
|
domain;
|
|
91
374
|
strictHistory;
|
|
@@ -96,123 +379,848 @@ let Documents = (() => {
|
|
|
96
379
|
this.log = new SharedLog(properties);
|
|
97
380
|
this.immutable = properties?.immutable ?? false;
|
|
98
381
|
this._index = properties?.index || new DocumentIndex();
|
|
382
|
+
this.trackDocumentChangeListeners();
|
|
383
|
+
this._documentBackend = this.createDocumentBackend();
|
|
99
384
|
}
|
|
100
385
|
get index() {
|
|
101
386
|
return this._index;
|
|
102
387
|
}
|
|
103
|
-
|
|
104
|
-
return this.
|
|
105
|
-
shape: INDEX_CONTEXT_SHAPE,
|
|
106
|
-
});
|
|
388
|
+
isNativeMode() {
|
|
389
|
+
return this._mode === "native";
|
|
107
390
|
}
|
|
108
|
-
|
|
109
|
-
return
|
|
110
|
-
?
|
|
111
|
-
:
|
|
391
|
+
createDocumentBackend() {
|
|
392
|
+
return this.isNativeMode()
|
|
393
|
+
? new NativeDocumentBackend(this.createNativeDocumentBackendContext())
|
|
394
|
+
: new CompatDocumentBackend(this.putCompatDocumentBackend.bind(this), this.putManyCompatDocumentBackend.bind(this), this.delCompatDocumentBackend.bind(this));
|
|
112
395
|
}
|
|
113
|
-
|
|
114
|
-
return
|
|
396
|
+
createNativeDocumentBackendContext() {
|
|
397
|
+
return {
|
|
398
|
+
assertPlainPutSupported: (doc, options) => {
|
|
399
|
+
this.assertNativeModePlainPutSupported(doc, options);
|
|
400
|
+
},
|
|
401
|
+
assertPlainPutPolicySupported: (doc, existing, previousSignerPublicKey) => this.assertNativeModePlainPutPolicySupported(doc, existing, previousSignerPublicKey),
|
|
402
|
+
assertPlainPutManySupported: (docs, options) => {
|
|
403
|
+
this.assertNativeModePlainPutManySupported(docs, options);
|
|
404
|
+
},
|
|
405
|
+
normalizePutOptions: (options) => this.normalizeNativeModePutOptions(options),
|
|
406
|
+
preparePlainPut: (doc) => this.preparePlainPut(doc),
|
|
407
|
+
hasDuplicatePreparedPutKeys: (prepared) => this.hasDuplicatePreparedPutKeys(prepared),
|
|
408
|
+
getIndexedContextHead: (existing) => this.getExistingContext(existing)?.head,
|
|
409
|
+
getNextFromIndexedContext: (existing) => {
|
|
410
|
+
const existingHead = this.getExistingContext(existing)?.head;
|
|
411
|
+
return existingHead
|
|
412
|
+
? this.nextFromIndexedContext(existingHead, existing)
|
|
413
|
+
: undefined;
|
|
414
|
+
},
|
|
415
|
+
getNativeEntrySignerPublicKeys: (hashes) => this.getNativeEntrySignerPublicKeys(hashes),
|
|
416
|
+
getNativePreviousEntrySignerPublicKey: (key) => this.getNativePreviousEntrySignerPublicKey(key),
|
|
417
|
+
getNativeIndexedContextsAndPreviousSignerPublicKeys: (keys) => this.getNativeIndexedContextsAndPreviousSignerPublicKeys(keys),
|
|
418
|
+
getNativeAppendRequiredPreviousSignerPublicKey: () => this.nativePlainPutPolicyRequiredPreviousSignerPublicKey(),
|
|
419
|
+
plainPutPolicyNeedsExistingContext: () => this.nativePlainPutPolicyNeedsPreviousEntries(),
|
|
420
|
+
shouldResolveTrimmedEntries: () => {
|
|
421
|
+
return !this._index.canGetIndexedKeyByHead();
|
|
422
|
+
},
|
|
423
|
+
commitNativeDocumentAppend: (input) => this.commitNativeDocumentAppend(input),
|
|
424
|
+
commitNativeDocumentAppendMany: (input) => this.commitNativeDocumentAppendMany(input),
|
|
425
|
+
handlePreparedPlainPutCommit: (commit) => this.handlePreparedPlainPutCommit(commit),
|
|
426
|
+
handlePreparedPlainPutManyCommit: (commit) => this.handlePreparedPlainPutManyCommit(commit),
|
|
427
|
+
deleteDocument: (id, options) => this.delNativeDocumentBackend(id, options),
|
|
428
|
+
keepEntry: (hash) => {
|
|
429
|
+
this.keepCache?.add(hash);
|
|
430
|
+
},
|
|
431
|
+
nativeModeError: (message) => this.nativeModeError(message),
|
|
432
|
+
};
|
|
115
433
|
}
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
434
|
+
nativeModeError(message) {
|
|
435
|
+
return new NativeDocumentModeError(`Documents native mode ${message}`);
|
|
436
|
+
}
|
|
437
|
+
assertNativeModeOpenOptions(options) {
|
|
438
|
+
if (!this.isNativeMode()) {
|
|
439
|
+
return;
|
|
440
|
+
}
|
|
441
|
+
const unsupported = [];
|
|
442
|
+
const nativeBackbone = options.nativeBackbone;
|
|
443
|
+
const indexTransform = options.index;
|
|
444
|
+
const nativeIndexTransformDescriptor = typeof indexTransform?.transform === "function"
|
|
445
|
+
? getDocumentTransformDescriptor(indexTransform.transform)
|
|
446
|
+
: undefined;
|
|
447
|
+
if (!nativeBackbone ||
|
|
448
|
+
typeof nativeBackbone !== "object" ||
|
|
449
|
+
nativeBackbone.documentIndex !== true) {
|
|
450
|
+
unsupported.push("missing nativeBackbone.documentIndex");
|
|
451
|
+
}
|
|
452
|
+
else if (nativeBackbone.optional !== false) {
|
|
453
|
+
unsupported.push("optional nativeBackbone");
|
|
454
|
+
}
|
|
455
|
+
else if (!nativeBackbone.coordinatePersistence) {
|
|
456
|
+
unsupported.push("missing nativeBackbone.coordinatePersistence");
|
|
457
|
+
}
|
|
458
|
+
else if (nativeBackbone.heads === false) {
|
|
459
|
+
unsupported.push("disabled native heads");
|
|
460
|
+
}
|
|
461
|
+
if (options.domain) {
|
|
462
|
+
unsupported.push("custom domain");
|
|
463
|
+
}
|
|
464
|
+
if (options.compatibility != null) {
|
|
465
|
+
unsupported.push("legacy compatibility");
|
|
466
|
+
}
|
|
467
|
+
if (options.strictHistory) {
|
|
468
|
+
unsupported.push("strict history");
|
|
469
|
+
}
|
|
470
|
+
if (this.immutable) {
|
|
471
|
+
unsupported.push("immutable documents");
|
|
472
|
+
}
|
|
473
|
+
if (options.appendDurability) {
|
|
474
|
+
unsupported.push("custom append durability");
|
|
475
|
+
}
|
|
476
|
+
if (Program.isPrototypeOf(options.type)) {
|
|
477
|
+
unsupported.push("program-valued document type");
|
|
478
|
+
}
|
|
479
|
+
if (options.canPerform &&
|
|
480
|
+
!getCanPerformPolicyDescriptor(options.canPerform)) {
|
|
481
|
+
unsupported.push("arbitrary canPerform");
|
|
482
|
+
}
|
|
483
|
+
if (options.canOpen) {
|
|
484
|
+
unsupported.push("custom canOpen");
|
|
485
|
+
}
|
|
486
|
+
if (options.id) {
|
|
487
|
+
unsupported.push("custom id");
|
|
488
|
+
}
|
|
489
|
+
if (options.index?.canRead) {
|
|
490
|
+
unsupported.push("custom canRead");
|
|
491
|
+
}
|
|
492
|
+
if (options.index?.canSearch) {
|
|
493
|
+
unsupported.push("custom canSearch");
|
|
494
|
+
}
|
|
495
|
+
if (options.index?.prefetch) {
|
|
496
|
+
unsupported.push("index prefetch");
|
|
497
|
+
}
|
|
498
|
+
if (options.index?.cache?.query) {
|
|
499
|
+
unsupported.push("index query cache");
|
|
500
|
+
}
|
|
501
|
+
if (options.canReplicate) {
|
|
502
|
+
unsupported.push("custom canReplicate");
|
|
503
|
+
}
|
|
504
|
+
if (options.keep) {
|
|
505
|
+
unsupported.push("custom keep");
|
|
506
|
+
}
|
|
507
|
+
if (options.fanout) {
|
|
508
|
+
unsupported.push("fanout");
|
|
509
|
+
}
|
|
510
|
+
if (options.syncronizer) {
|
|
511
|
+
unsupported.push("custom syncronizer");
|
|
512
|
+
}
|
|
513
|
+
if (options.sync?.priority) {
|
|
514
|
+
unsupported.push("custom sync priority");
|
|
515
|
+
}
|
|
516
|
+
if (options.sync?.profile) {
|
|
517
|
+
unsupported.push("custom sync profile");
|
|
518
|
+
}
|
|
519
|
+
if (options.log?.trim &&
|
|
520
|
+
(options.log.trim.type !== "length" || options.log.trim.filter?.canTrim)) {
|
|
521
|
+
unsupported.push("unsupported log trim");
|
|
522
|
+
}
|
|
523
|
+
if (indexTransform?.transform && !nativeIndexTransformDescriptor) {
|
|
524
|
+
unsupported.push("arbitrary index transform");
|
|
525
|
+
}
|
|
526
|
+
if (indexTransform?.type &&
|
|
527
|
+
!indexTransform.transform &&
|
|
528
|
+
indexTransform.type !== options.type) {
|
|
529
|
+
unsupported.push("constructor index transform");
|
|
530
|
+
}
|
|
531
|
+
if (unsupported.length > 0) {
|
|
532
|
+
throw this.nativeModeError(`does not support ${unsupported.join(", ")}`);
|
|
122
533
|
}
|
|
123
|
-
return value;
|
|
124
534
|
}
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
535
|
+
assertNativeModeReady() {
|
|
536
|
+
if (!this.isNativeMode()) {
|
|
537
|
+
return;
|
|
538
|
+
}
|
|
539
|
+
if (!this._nativeBackboneDocumentIndexEnabled) {
|
|
540
|
+
throw this.nativeModeError("requires an attached native backbone document index");
|
|
541
|
+
}
|
|
542
|
+
if (!asTrustedDocumentIndex(this._index).canPrepareNativeBackboneDocumentIndexCommitWithAppendFacts()) {
|
|
543
|
+
throw this.nativeModeError("requires a native-compatible document index transform");
|
|
544
|
+
}
|
|
545
|
+
if (!this._nativeDocumentIdExtractionPlan) {
|
|
546
|
+
throw this.nativeModeError("requires a native-compatible document id field");
|
|
547
|
+
}
|
|
548
|
+
if (this._optionCanPerformNativePolicy) {
|
|
549
|
+
const signedByFieldPaths = canPerformPolicySignedByFieldPaths(this._optionCanPerformNativePolicy);
|
|
550
|
+
for (const path of signedByFieldPaths) {
|
|
551
|
+
if (!this.getNativeDocumentFieldExtractionPlan(path)) {
|
|
552
|
+
const label = Array.isArray(path) ? path.join(".") : path;
|
|
553
|
+
throw this.nativeModeError(`requires native-compatible signedByField policy path: ${label}`);
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
const deleteFieldPaths = canPerformPolicyDeleteFieldPaths(this._optionCanPerformNativePolicy);
|
|
557
|
+
if (deleteFieldPaths.length > 0 &&
|
|
558
|
+
!this._index.canReadNativeIndexedFieldValues(deleteFieldPaths)) {
|
|
559
|
+
const labels = deleteFieldPaths.map((path) => Array.isArray(path) ? path.join(".") : path);
|
|
560
|
+
throw this.nativeModeError(`requires native index to read delete policy field${labels.length > 1 ? "s" : ""}: ${labels.join(", ")}`);
|
|
133
561
|
}
|
|
134
562
|
}
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
563
|
+
}
|
|
564
|
+
canPerformAllowsPlainPutFastPath(doc) {
|
|
565
|
+
return (!this._optionCanPerform || !!this._optionCanPerformNativeFastPath?.(doc));
|
|
566
|
+
}
|
|
567
|
+
nativePlainPutPolicyNeedsPreviousEntries() {
|
|
568
|
+
return (!!this._optionCanPerformNativePolicy &&
|
|
569
|
+
canPerformPolicyNeedsPreviousEntries(this._optionCanPerformNativePolicy));
|
|
570
|
+
}
|
|
571
|
+
nativePlainPutPolicyRequiredPreviousSignerPublicKey() {
|
|
572
|
+
const descriptor = this._optionCanPerformNativePolicy;
|
|
573
|
+
const policyDescriptor = descriptor?.kind === "put" ? descriptor.policy : descriptor;
|
|
574
|
+
if (policyDescriptor?.kind !== "sameSignersAsPrevious") {
|
|
575
|
+
return;
|
|
576
|
+
}
|
|
577
|
+
return this.log.log.identity.publicKey
|
|
578
|
+
.publicKey;
|
|
579
|
+
}
|
|
580
|
+
unsupportedNativePutOptions(options) {
|
|
581
|
+
const unsupported = [];
|
|
582
|
+
if (options?.canAppend) {
|
|
583
|
+
unsupported.push("per-call canAppend");
|
|
584
|
+
}
|
|
585
|
+
if (options?.onChange) {
|
|
586
|
+
unsupported.push("per-call onChange");
|
|
587
|
+
}
|
|
588
|
+
if (options?.signers) {
|
|
589
|
+
unsupported.push("custom signers");
|
|
590
|
+
}
|
|
591
|
+
if (options?.identity) {
|
|
592
|
+
unsupported.push("custom identity");
|
|
593
|
+
}
|
|
594
|
+
if (options?.encryption) {
|
|
595
|
+
unsupported.push("encryption");
|
|
596
|
+
}
|
|
597
|
+
if (options?.trim) {
|
|
598
|
+
unsupported.push("per-call trim");
|
|
599
|
+
}
|
|
600
|
+
if (options?.durability) {
|
|
601
|
+
unsupported.push("per-call durability");
|
|
602
|
+
}
|
|
603
|
+
if (options?.deferIndexWrite !== undefined) {
|
|
604
|
+
unsupported.push("per-call index write deferral");
|
|
605
|
+
}
|
|
606
|
+
if (options?.meta?.type) {
|
|
607
|
+
unsupported.push("custom entry type");
|
|
608
|
+
}
|
|
609
|
+
if (options?.meta && "data" in options.meta) {
|
|
610
|
+
unsupported.push("custom metadata");
|
|
611
|
+
}
|
|
612
|
+
if (options?.meta?.next) {
|
|
613
|
+
unsupported.push("custom next");
|
|
614
|
+
}
|
|
615
|
+
if (options?.meta?.timestamp) {
|
|
616
|
+
unsupported.push("custom timestamp");
|
|
617
|
+
}
|
|
618
|
+
if (options?.meta?.gidSeed) {
|
|
619
|
+
unsupported.push("custom gid seed");
|
|
620
|
+
}
|
|
621
|
+
if (options?.replicate === true) {
|
|
622
|
+
unsupported.push("replicated put");
|
|
623
|
+
}
|
|
624
|
+
if (options?.target && options.target !== "none") {
|
|
625
|
+
unsupported.push("non-local target");
|
|
626
|
+
}
|
|
627
|
+
if (options?.delivery !== undefined && options.delivery !== false) {
|
|
628
|
+
unsupported.push("delivery");
|
|
629
|
+
}
|
|
630
|
+
if (options?.checkRemote) {
|
|
631
|
+
unsupported.push("remote existing-head check");
|
|
632
|
+
}
|
|
633
|
+
if (options?.replicas !== undefined) {
|
|
634
|
+
unsupported.push("per-call replicas");
|
|
635
|
+
}
|
|
636
|
+
return unsupported;
|
|
637
|
+
}
|
|
638
|
+
assertNativeModePlainPutSupported(doc, options) {
|
|
639
|
+
if (!this.isNativeMode()) {
|
|
640
|
+
return false;
|
|
641
|
+
}
|
|
642
|
+
const unsupported = this.unsupportedNativePutOptions(options);
|
|
643
|
+
if (this.immutable) {
|
|
644
|
+
unsupported.push("immutable documents");
|
|
645
|
+
}
|
|
646
|
+
if (this.strictHistory) {
|
|
647
|
+
unsupported.push("strict history");
|
|
648
|
+
}
|
|
649
|
+
if (this.compatibility === 6) {
|
|
650
|
+
unsupported.push("legacy compatibility");
|
|
651
|
+
}
|
|
652
|
+
if (Program.isPrototypeOf(this._clazz)) {
|
|
653
|
+
unsupported.push("program-valued document type");
|
|
654
|
+
}
|
|
655
|
+
if (unsupported.length > 0) {
|
|
656
|
+
throw this.nativeModeError(`does not support ${unsupported.join(", ")}`);
|
|
657
|
+
}
|
|
658
|
+
return true;
|
|
659
|
+
}
|
|
660
|
+
assertNativeModePlainPutPolicySupported(doc, existing, previousSignerPublicKey) {
|
|
661
|
+
return mapMaybePromise(this.canPerformAllowsNativePlainPut(doc, existing, previousSignerPublicKey), (allowed) => {
|
|
662
|
+
if (!allowed) {
|
|
663
|
+
throw this.nativeModeError("canPerform policy rejected this document");
|
|
664
|
+
}
|
|
169
665
|
});
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
logCompatiblity = 8;
|
|
666
|
+
}
|
|
667
|
+
canPerformAllowsNativePlainPut(doc, existing, previousSignerPublicKey) {
|
|
668
|
+
if (!this._optionCanPerform) {
|
|
669
|
+
return true;
|
|
175
670
|
}
|
|
176
|
-
|
|
177
|
-
|
|
671
|
+
if (!this._optionCanPerformNativePolicy) {
|
|
672
|
+
return false;
|
|
178
673
|
}
|
|
179
|
-
this.
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
674
|
+
if (!this.nativePlainPutPolicyNeedsPreviousEntries()) {
|
|
675
|
+
return this.canPerformAllowsPlainPutFastPath(doc);
|
|
676
|
+
}
|
|
677
|
+
const previousEntries = [];
|
|
678
|
+
const existingHead = this.getExistingContext(existing)?.head;
|
|
679
|
+
if (existingHead) {
|
|
680
|
+
if (previousSignerPublicKey) {
|
|
681
|
+
return this.nativePutPolicyAllows(this._optionCanPerformNativePolicy, doc, previousEntries, [previousSignerPublicKey]);
|
|
682
|
+
}
|
|
683
|
+
if (this.isNativeMode()) {
|
|
684
|
+
throw this.nativeModeError("requires native previous signer facts");
|
|
685
|
+
}
|
|
686
|
+
return mapMaybePromise(this._resolveEntry(existingHead, {
|
|
687
|
+
remote: true,
|
|
688
|
+
}), (previousEntry) => {
|
|
689
|
+
if (previousEntry) {
|
|
690
|
+
previousEntries.push(previousEntry);
|
|
691
|
+
}
|
|
692
|
+
return this.nativePutPolicyAllows(this._optionCanPerformNativePolicy, doc, previousEntries);
|
|
693
|
+
});
|
|
694
|
+
}
|
|
695
|
+
return this.nativePutPolicyAllows(this._optionCanPerformNativePolicy, doc, previousEntries);
|
|
696
|
+
}
|
|
697
|
+
async nativePutPolicyAllows(descriptor, doc, previousEntries, previousSignerPublicKeys = []) {
|
|
698
|
+
return this.nativePutOperationPolicyAllows(descriptor, undefined, doc, previousEntries, previousSignerPublicKeys);
|
|
699
|
+
}
|
|
700
|
+
nativeFieldValueMatchesLocalPublicKey(value) {
|
|
701
|
+
const localPublicKey = this.log.log.identity.publicKey;
|
|
702
|
+
return this.nativeFieldValueMatchesPublicKey(value, localPublicKey);
|
|
703
|
+
}
|
|
704
|
+
nativeFieldValueMatchesPublicKey(value, publicKey) {
|
|
705
|
+
const localRawPublicKey = publicKey.publicKey;
|
|
706
|
+
return (value instanceof Uint8Array &&
|
|
707
|
+
(bytesEqual(value, publicKey.bytes) ||
|
|
708
|
+
(localRawPublicKey ? bytesEqual(value, localRawPublicKey) : false)));
|
|
709
|
+
}
|
|
710
|
+
nativeFieldValueMatchesPublicKeys(value, publicKeys) {
|
|
711
|
+
for (const publicKey of publicKeys) {
|
|
712
|
+
if (this.nativeFieldValueMatchesPublicKey(value, publicKey)) {
|
|
713
|
+
return true;
|
|
714
|
+
}
|
|
715
|
+
}
|
|
716
|
+
return false;
|
|
717
|
+
}
|
|
718
|
+
nativeDeletePolicyNeedsEntryPublicKeys(descriptor) {
|
|
719
|
+
switch (descriptor.kind) {
|
|
720
|
+
case "signedByPublicKey":
|
|
721
|
+
case "deleteSignedByExistingField":
|
|
722
|
+
return true;
|
|
723
|
+
case "delete":
|
|
724
|
+
return this.nativeDeletePolicyNeedsEntryPublicKeys(descriptor.policy);
|
|
725
|
+
case "and":
|
|
726
|
+
case "or":
|
|
727
|
+
return descriptor.policies.some((policy) => this.nativeDeletePolicyNeedsEntryPublicKeys(policy));
|
|
728
|
+
default:
|
|
729
|
+
return false;
|
|
730
|
+
}
|
|
731
|
+
}
|
|
732
|
+
async nativePutOperationPolicyAllows(descriptor, operation, doc, previousEntries, previousSignerPublicKeys = [], entryPublicKeys = []) {
|
|
733
|
+
switch (descriptor.kind) {
|
|
734
|
+
case "allowAll":
|
|
735
|
+
return createCanPerformPolicyEvaluator(descriptor, this.log.log.identity.publicKey)(doc);
|
|
736
|
+
case "signedByPublicKey":
|
|
737
|
+
return entryPublicKeys.length > 0
|
|
738
|
+
? this.nativeFieldValueMatchesPublicKeys(descriptor.publicKey, entryPublicKeys)
|
|
739
|
+
: createCanPerformPolicyEvaluator(descriptor, this.log.log.identity.publicKey)(doc);
|
|
740
|
+
case "signedByField": {
|
|
741
|
+
if (doc) {
|
|
742
|
+
return createCanPerformPolicyEvaluator(descriptor, this.log.log.identity.publicKey)(doc);
|
|
743
|
+
}
|
|
744
|
+
if (!operation) {
|
|
745
|
+
return false;
|
|
746
|
+
}
|
|
747
|
+
const value = await this.getNativeDocumentFieldFromPutOperation(operation, descriptor.path);
|
|
748
|
+
return this.nativeFieldValueMatchesPublicKeys(value, entryPublicKeys);
|
|
749
|
+
}
|
|
750
|
+
case "put":
|
|
751
|
+
return this.nativePutOperationPolicyAllows(descriptor.policy, operation, doc, previousEntries, previousSignerPublicKeys, entryPublicKeys);
|
|
752
|
+
case "delete":
|
|
753
|
+
case "deleteSignedByExistingField":
|
|
754
|
+
return false;
|
|
755
|
+
case "sameSignersAsPrevious": {
|
|
756
|
+
if (previousSignerPublicKeys.length > 0) {
|
|
757
|
+
const currentPublicKeys = entryPublicKeys.length > 0
|
|
758
|
+
? entryPublicKeys
|
|
759
|
+
: [this.log.log.identity.publicKey];
|
|
760
|
+
if (currentPublicKeys.length !== previousSignerPublicKeys.length) {
|
|
761
|
+
return false;
|
|
762
|
+
}
|
|
763
|
+
for (const previousSignerPublicKey of previousSignerPublicKeys) {
|
|
764
|
+
if (!this.nativeFieldValueMatchesPublicKeys(previousSignerPublicKey, currentPublicKeys)) {
|
|
765
|
+
return false;
|
|
766
|
+
}
|
|
767
|
+
}
|
|
185
768
|
return true;
|
|
186
769
|
}
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
signatures = e.signatures;
|
|
770
|
+
if (previousEntries.length === 0) {
|
|
771
|
+
return true;
|
|
190
772
|
}
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
773
|
+
const localPublicKey = this.log.log.identity.publicKey;
|
|
774
|
+
for (const previousEntry of previousEntries) {
|
|
775
|
+
const publicKeys = await previousEntry.getPublicKeys();
|
|
776
|
+
if (publicKeys.length !== 1 ||
|
|
777
|
+
!publicKeys[0].equals(localPublicKey)) {
|
|
778
|
+
return false;
|
|
779
|
+
}
|
|
194
780
|
}
|
|
195
|
-
|
|
196
|
-
|
|
781
|
+
return true;
|
|
782
|
+
}
|
|
783
|
+
case "and":
|
|
784
|
+
for (const policy of descriptor.policies) {
|
|
785
|
+
if (!(await this.nativePutOperationPolicyAllows(policy, operation, doc, previousEntries, previousSignerPublicKeys, entryPublicKeys))) {
|
|
786
|
+
return false;
|
|
787
|
+
}
|
|
197
788
|
}
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
789
|
+
return true;
|
|
790
|
+
case "or":
|
|
791
|
+
for (const policy of descriptor.policies) {
|
|
792
|
+
if (await this.nativePutOperationPolicyAllows(policy, operation, doc, previousEntries, previousSignerPublicKeys, entryPublicKeys)) {
|
|
201
793
|
return true;
|
|
202
794
|
}
|
|
203
795
|
}
|
|
204
|
-
return false;
|
|
205
|
-
};
|
|
206
|
-
}
|
|
207
|
-
else {
|
|
208
|
-
keepFunction = options?.keep;
|
|
796
|
+
return false;
|
|
209
797
|
}
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
798
|
+
}
|
|
799
|
+
async nativeDeleteOperationPolicyAllows(descriptor, operation, entryPublicKeys) {
|
|
800
|
+
switch (descriptor.kind) {
|
|
801
|
+
case "allowAll":
|
|
802
|
+
return true;
|
|
803
|
+
case "signedByPublicKey":
|
|
804
|
+
return entryPublicKeys
|
|
805
|
+
? this.nativeFieldValueMatchesPublicKeys(descriptor.publicKey, entryPublicKeys)
|
|
806
|
+
: this.nativeFieldValueMatchesLocalPublicKey(descriptor.publicKey);
|
|
807
|
+
case "delete":
|
|
808
|
+
return this.nativeDeleteOperationPolicyAllows(descriptor.policy, operation, entryPublicKeys);
|
|
809
|
+
case "deleteSignedByExistingField": {
|
|
810
|
+
const value = this.getNativeDeletePolicyFieldValue(operation, descriptor.path);
|
|
811
|
+
return entryPublicKeys
|
|
812
|
+
? this.nativeFieldValueMatchesPublicKeys(value, entryPublicKeys)
|
|
813
|
+
: this.nativeFieldValueMatchesLocalPublicKey(value);
|
|
814
|
+
}
|
|
815
|
+
case "and":
|
|
816
|
+
for (const policy of descriptor.policies) {
|
|
817
|
+
if (!(await this.nativeDeleteOperationPolicyAllows(policy, operation, entryPublicKeys))) {
|
|
818
|
+
return false;
|
|
819
|
+
}
|
|
820
|
+
}
|
|
821
|
+
return true;
|
|
822
|
+
case "or":
|
|
823
|
+
for (const policy of descriptor.policies) {
|
|
824
|
+
if (await this.nativeDeleteOperationPolicyAllows(policy, operation, entryPublicKeys)) {
|
|
825
|
+
return true;
|
|
826
|
+
}
|
|
827
|
+
}
|
|
828
|
+
return false;
|
|
829
|
+
case "put":
|
|
830
|
+
case "signedByField":
|
|
831
|
+
case "sameSignersAsPrevious":
|
|
832
|
+
return false;
|
|
833
|
+
}
|
|
834
|
+
}
|
|
835
|
+
assertNativeModePlainPutManySupported(docs, options) {
|
|
836
|
+
if (!this.isNativeMode()) {
|
|
837
|
+
return;
|
|
838
|
+
}
|
|
839
|
+
const unsupported = this.unsupportedNativePutOptions(options);
|
|
840
|
+
if (this.immutable) {
|
|
841
|
+
unsupported.push("immutable documents");
|
|
842
|
+
}
|
|
843
|
+
if (this.strictHistory) {
|
|
844
|
+
unsupported.push("strict history");
|
|
845
|
+
}
|
|
846
|
+
if (this.compatibility === 6) {
|
|
847
|
+
unsupported.push("legacy compatibility");
|
|
848
|
+
}
|
|
849
|
+
if (Program.isPrototypeOf(this._clazz)) {
|
|
850
|
+
unsupported.push("program-valued document type");
|
|
851
|
+
}
|
|
852
|
+
if (!asTrustedDocumentIndex(this._index).canUseNativeBackboneContextualBatch()) {
|
|
853
|
+
unsupported.push("native batch document index");
|
|
854
|
+
}
|
|
855
|
+
if (unsupported.length > 0) {
|
|
856
|
+
throw this.nativeModeError(`does not support ${unsupported.join(", ")}`);
|
|
857
|
+
}
|
|
858
|
+
}
|
|
859
|
+
assertNativeModeDeleteSupported(options) {
|
|
860
|
+
if (!this.isNativeMode()) {
|
|
861
|
+
return;
|
|
862
|
+
}
|
|
863
|
+
const unsupported = this.unsupportedNativePutOptions(options);
|
|
864
|
+
if (options?.unique !== undefined) {
|
|
865
|
+
unsupported.push("unique delete");
|
|
866
|
+
}
|
|
867
|
+
if (this.immutable) {
|
|
868
|
+
unsupported.push("immutable documents");
|
|
869
|
+
}
|
|
870
|
+
if (this.strictHistory) {
|
|
871
|
+
unsupported.push("strict history");
|
|
872
|
+
}
|
|
873
|
+
if (this.compatibility === 6) {
|
|
874
|
+
unsupported.push("legacy compatibility");
|
|
875
|
+
}
|
|
876
|
+
if (Program.isPrototypeOf(this._clazz)) {
|
|
877
|
+
unsupported.push("program-valued document type");
|
|
878
|
+
}
|
|
879
|
+
if (unsupported.length > 0) {
|
|
880
|
+
throw this.nativeModeError(`does not support ${unsupported.join(", ")}`);
|
|
881
|
+
}
|
|
882
|
+
}
|
|
883
|
+
async canPerformAllowsNativeDelete(properties) {
|
|
884
|
+
if (!this._optionCanPerform) {
|
|
885
|
+
return true;
|
|
886
|
+
}
|
|
887
|
+
if (!this._optionCanPerformNativePolicy) {
|
|
888
|
+
return false;
|
|
889
|
+
}
|
|
890
|
+
if (this.isNativeMode()) {
|
|
891
|
+
return this.nativeDeleteOperationPolicyAllows(this._optionCanPerformNativePolicy, properties.operation);
|
|
892
|
+
}
|
|
893
|
+
let deleteValue;
|
|
894
|
+
if (canPerformPolicyNeedsDeleteValue(this._optionCanPerformNativePolicy)) {
|
|
895
|
+
deleteValue = await properties.getExistingDocument?.();
|
|
896
|
+
if (deleteValue === undefined) {
|
|
897
|
+
const existingEntry = await properties.getExistingEntry();
|
|
898
|
+
const existingOperation = await existingEntry.getPayloadValue();
|
|
899
|
+
if (isPutOperation(existingOperation)) {
|
|
900
|
+
deleteValue = this._index.valueEncoding.decoder(existingOperation.data);
|
|
901
|
+
}
|
|
902
|
+
}
|
|
903
|
+
}
|
|
904
|
+
return createCanPerformDeletePolicyEvaluator(this._optionCanPerformNativePolicy, this.log.log.identity.publicKey)(deleteValue);
|
|
905
|
+
}
|
|
906
|
+
normalizeNativeModePutOptions(options) {
|
|
907
|
+
if (!this.isNativeMode()) {
|
|
908
|
+
return options;
|
|
909
|
+
}
|
|
910
|
+
if (options?.replicate === false && options.target === "none") {
|
|
911
|
+
return options;
|
|
912
|
+
}
|
|
913
|
+
if (this._nativeModeReplicatedOpen) {
|
|
914
|
+
return options?.target === "none"
|
|
915
|
+
? options
|
|
916
|
+
: {
|
|
917
|
+
...options,
|
|
918
|
+
target: "none",
|
|
919
|
+
};
|
|
920
|
+
}
|
|
921
|
+
const cached = cachedNativeLocalPutOptions(options);
|
|
922
|
+
if (cached) {
|
|
923
|
+
return cached;
|
|
924
|
+
}
|
|
925
|
+
return {
|
|
926
|
+
...options,
|
|
927
|
+
replicate: false,
|
|
928
|
+
target: "none",
|
|
929
|
+
};
|
|
930
|
+
}
|
|
931
|
+
trackDocumentChangeListeners() {
|
|
932
|
+
if (this._documentChangeListenerTrackingInitialized) {
|
|
933
|
+
return;
|
|
934
|
+
}
|
|
935
|
+
this._documentChangeListenerTrackingInitialized = true;
|
|
936
|
+
this._documentChangeListeners ??= [];
|
|
937
|
+
this._documentChangeListenerCount ??= 0;
|
|
938
|
+
this._documentInternalChangeListenerCount ??= 0;
|
|
939
|
+
const events = this.events;
|
|
940
|
+
const addEventListener = events.addEventListener.bind(events);
|
|
941
|
+
const removeEventListener = events.removeEventListener.bind(events);
|
|
942
|
+
const captureFromOptions = (options) => typeof options === "boolean"
|
|
943
|
+
? options
|
|
944
|
+
: !!options?.capture;
|
|
945
|
+
events.addEventListener = ((type, ...args) => {
|
|
946
|
+
if (type === "change") {
|
|
947
|
+
const listener = args[0];
|
|
948
|
+
const capture = captureFromOptions(args[1]);
|
|
949
|
+
if (listener &&
|
|
950
|
+
!this._documentChangeListeners.some((entry) => entry.listener === listener && entry.capture === capture)) {
|
|
951
|
+
this._documentChangeListeners.push({ listener, capture });
|
|
952
|
+
this._documentChangeListenerCount =
|
|
953
|
+
this._documentChangeListeners.length;
|
|
954
|
+
}
|
|
955
|
+
}
|
|
956
|
+
return addEventListener(type, ...args);
|
|
957
|
+
});
|
|
958
|
+
events.removeEventListener = ((type, ...args) => {
|
|
959
|
+
if (type === "change") {
|
|
960
|
+
const listener = args[0];
|
|
961
|
+
const capture = captureFromOptions(args[1]);
|
|
962
|
+
const index = this._documentChangeListeners.findIndex((entry) => entry.listener === listener && entry.capture === capture);
|
|
963
|
+
if (index >= 0) {
|
|
964
|
+
this._documentChangeListeners.splice(index, 1);
|
|
965
|
+
this._documentChangeListenerCount =
|
|
966
|
+
this._documentChangeListeners.length;
|
|
967
|
+
}
|
|
968
|
+
}
|
|
969
|
+
return removeEventListener(type, ...args);
|
|
970
|
+
});
|
|
971
|
+
}
|
|
972
|
+
getLocalIndexedContext(key) {
|
|
973
|
+
const getContextById = this._index.index.getContextById;
|
|
974
|
+
const context = getContextById?.call(this._index.index, key);
|
|
975
|
+
if (context) {
|
|
976
|
+
return Promise.resolve({
|
|
977
|
+
id: key,
|
|
978
|
+
value: { __context: context },
|
|
979
|
+
});
|
|
980
|
+
}
|
|
981
|
+
return this._index.index.get(key, {
|
|
982
|
+
shape: INDEX_CONTEXT_SHAPE,
|
|
983
|
+
});
|
|
984
|
+
}
|
|
985
|
+
getNativeEntrySignerPublicKeys(hashes) {
|
|
986
|
+
if (hashes.length === 0) {
|
|
987
|
+
return [];
|
|
988
|
+
}
|
|
989
|
+
const nativeGraph = this.log.log.entryIndex.properties.nativeGraph
|
|
990
|
+
?.graph;
|
|
991
|
+
return nativeGraph?.entrySignaturePublicKeysBatch?.(hashes);
|
|
992
|
+
}
|
|
993
|
+
getSharedLogNativeBackbone() {
|
|
994
|
+
return this.log._nativeBackbone;
|
|
995
|
+
}
|
|
996
|
+
getNativePreviousEntrySignerPublicKey(key) {
|
|
997
|
+
const nativeBackbone = this.getSharedLogNativeBackbone();
|
|
998
|
+
return nativeBackbone?.documentPreviousSignaturePublicKey?.(documentIndexStoreKey(key));
|
|
999
|
+
}
|
|
1000
|
+
async getNativePreviousEntrySignerPublicKeyForPutOperation(operation) {
|
|
1001
|
+
const keyValue = await this.getNativeDocumentIdFromPutOperation(operation);
|
|
1002
|
+
return keyValue == null
|
|
1003
|
+
? undefined
|
|
1004
|
+
: this.getNativePreviousEntrySignerPublicKey(indexerTypes.toId(keyValue));
|
|
1005
|
+
}
|
|
1006
|
+
getNativeIndexedContext(key) {
|
|
1007
|
+
const nativeBackbone = this.getSharedLogNativeBackbone();
|
|
1008
|
+
const row = nativeBackbone?.documentContext?.(documentIndexStoreKey(key));
|
|
1009
|
+
const context = row
|
|
1010
|
+
? {
|
|
1011
|
+
created: BigInt(row[0]),
|
|
1012
|
+
modified: BigInt(row[1]),
|
|
1013
|
+
head: row[2],
|
|
1014
|
+
gid: row[3],
|
|
1015
|
+
size: row[4],
|
|
1016
|
+
}
|
|
1017
|
+
: undefined;
|
|
1018
|
+
return context
|
|
1019
|
+
? {
|
|
1020
|
+
id: key,
|
|
1021
|
+
value: {
|
|
1022
|
+
__context: nativeDocumentContextFactsAsContext(context),
|
|
1023
|
+
},
|
|
1024
|
+
}
|
|
1025
|
+
: undefined;
|
|
1026
|
+
}
|
|
1027
|
+
getNativeModeIndexedContext(key) {
|
|
1028
|
+
if (!this.hasNativeDocumentContextLookup()) {
|
|
1029
|
+
throw this.nativeModeError("requires native document context lookup");
|
|
1030
|
+
}
|
|
1031
|
+
return this.getNativeIndexedContext(key);
|
|
1032
|
+
}
|
|
1033
|
+
hasNativeDocumentContextLookup() {
|
|
1034
|
+
const nativeBackbone = this.getSharedLogNativeBackbone();
|
|
1035
|
+
return typeof nativeBackbone?.documentContext === "function";
|
|
1036
|
+
}
|
|
1037
|
+
getNativeIndexedContextsAndPreviousSignerPublicKeys(keys) {
|
|
1038
|
+
if (keys.length === 0) {
|
|
1039
|
+
return { contexts: [], publicKeys: [] };
|
|
1040
|
+
}
|
|
1041
|
+
const nativeBackbone = this.getSharedLogNativeBackbone();
|
|
1042
|
+
const rows = nativeBackbone?.documentContextsAndPreviousSignaturePublicKeys?.(keys.map(documentIndexStoreKey));
|
|
1043
|
+
if (!rows) {
|
|
1044
|
+
return;
|
|
1045
|
+
}
|
|
1046
|
+
return {
|
|
1047
|
+
contexts: rows.map((row, index) => row.context
|
|
1048
|
+
? {
|
|
1049
|
+
id: keys[index],
|
|
1050
|
+
value: {
|
|
1051
|
+
__context: nativeDocumentContextFactsAsContext(row.context),
|
|
1052
|
+
},
|
|
1053
|
+
}
|
|
1054
|
+
: undefined),
|
|
1055
|
+
publicKeys: rows.map((row) => row.publicKey),
|
|
1056
|
+
};
|
|
1057
|
+
}
|
|
1058
|
+
getExistingContext(existing) {
|
|
1059
|
+
return isResultIndexedValue(existing)
|
|
1060
|
+
? existing.context
|
|
1061
|
+
: existing?.value.__context;
|
|
1062
|
+
}
|
|
1063
|
+
documentFromIdentityIndexedValue(indexed) {
|
|
1064
|
+
const value = indexed?.value;
|
|
1065
|
+
if (!value) {
|
|
1066
|
+
return;
|
|
1067
|
+
}
|
|
1068
|
+
if (typeof value !== "object") {
|
|
1069
|
+
return value;
|
|
1070
|
+
}
|
|
1071
|
+
const document = Object.assign(Object.create(this._clazz.prototype), value);
|
|
1072
|
+
delete document.__context;
|
|
1073
|
+
return document;
|
|
1074
|
+
}
|
|
1075
|
+
async getLocalIdentityDocumentByHead(head) {
|
|
1076
|
+
return this.documentFromIdentityIndexedValue(await this._index.getIdentityIndexedByHead(head));
|
|
1077
|
+
}
|
|
1078
|
+
async getLocalIndexedDocumentForNativeDeletePolicy(key) {
|
|
1079
|
+
if (!this._optionCanPerformNativePolicy) {
|
|
1080
|
+
return;
|
|
1081
|
+
}
|
|
1082
|
+
const fieldPaths = canPerformPolicyDeleteFieldPaths(this._optionCanPerformNativePolicy);
|
|
1083
|
+
if (fieldPaths.length === 0 ||
|
|
1084
|
+
!this._index.canReadOriginalFieldPathsFromIndexedValue(fieldPaths)) {
|
|
1085
|
+
return;
|
|
1086
|
+
}
|
|
1087
|
+
return (await this._index.get(key, {
|
|
1088
|
+
local: true,
|
|
1089
|
+
remote: false,
|
|
1090
|
+
resolve: false,
|
|
1091
|
+
}));
|
|
1092
|
+
}
|
|
1093
|
+
getNativeDeletePolicyFieldValue(operation, path) {
|
|
1094
|
+
const key = operation.key instanceof indexerTypes.IdKey
|
|
1095
|
+
? operation.key
|
|
1096
|
+
: indexerTypes.toId(operation.key);
|
|
1097
|
+
return this._index.getNativeIndexedFieldValue(key, path);
|
|
1098
|
+
}
|
|
1099
|
+
get changes() {
|
|
1100
|
+
return this.events;
|
|
1101
|
+
}
|
|
1102
|
+
async maybeSubprogramOpen(value) {
|
|
1103
|
+
if (await this.canOpen(value)) {
|
|
1104
|
+
return (await this.node.open(value, {
|
|
1105
|
+
parent: this,
|
|
1106
|
+
existing: "reuse",
|
|
1107
|
+
}));
|
|
1108
|
+
}
|
|
1109
|
+
return value;
|
|
1110
|
+
}
|
|
1111
|
+
keepCache = undefined;
|
|
1112
|
+
async open(options) {
|
|
1113
|
+
this.trackDocumentChangeListeners();
|
|
1114
|
+
// Deserialized instances skip constructor/field initializers (borsh creates
|
|
1115
|
+
// objects via Object.create), so re-establish constructor-only state here.
|
|
1116
|
+
this._canAppendDecodedDocuments ??= new WeakMap();
|
|
1117
|
+
this._clazz = options.type;
|
|
1118
|
+
this._valueClassIsProgram = Program.isPrototypeOf(this._clazz);
|
|
1119
|
+
this.canOpen = options.canOpen;
|
|
1120
|
+
this._mode = options.mode ?? "auto";
|
|
1121
|
+
this._nativeModeReplicatedOpen =
|
|
1122
|
+
this.isNativeMode() &&
|
|
1123
|
+
options.replicate !== undefined &&
|
|
1124
|
+
options.replicate !== false;
|
|
1125
|
+
this.assertNativeModeOpenOptions(options);
|
|
1126
|
+
if (Program.isPrototypeOf(this._clazz)) {
|
|
1127
|
+
if (!this.canOpen) {
|
|
1128
|
+
throw new Error("Document store needs to be opened with canOpen option when the document type is a Program");
|
|
1129
|
+
}
|
|
1130
|
+
}
|
|
1131
|
+
this._optionCanPerform = options.canPerform;
|
|
1132
|
+
this._optionCanPerformNativePolicy = getCanPerformPolicyDescriptor(options.canPerform);
|
|
1133
|
+
const idProperty = options.index?.idProperty ||
|
|
1134
|
+
indexerTypes.getIdProperty(this._clazz) ||
|
|
1135
|
+
"id";
|
|
1136
|
+
const idResolver = options.id ||
|
|
1137
|
+
(typeof idProperty === "string"
|
|
1138
|
+
? (obj) => obj[idProperty]
|
|
1139
|
+
: (obj) => indexerTypes.extractFieldValue(obj, idProperty));
|
|
1140
|
+
this.idResolver = idResolver;
|
|
1141
|
+
this.compatibility = options.compatibility;
|
|
1142
|
+
this.strictHistory = options.strictHistory ?? false;
|
|
1143
|
+
this._hasLogTrim = options.log?.trim != null;
|
|
1144
|
+
const changeListenersBeforeIndexOpen = this._documentChangeListenerCount;
|
|
1145
|
+
await this._index.open({
|
|
1146
|
+
documentEvents: this.events,
|
|
1147
|
+
log: this.log,
|
|
1148
|
+
canRead: options?.index?.canRead,
|
|
1149
|
+
canSearch: options.index?.canSearch,
|
|
1150
|
+
documentType: this._clazz,
|
|
1151
|
+
transform: options.index,
|
|
1152
|
+
indexBy: idProperty,
|
|
1153
|
+
compatibility: options.compatibility,
|
|
1154
|
+
cache: options?.index?.cache,
|
|
1155
|
+
replicate: async (query, results) => {
|
|
1156
|
+
// here we arrive for all the results we want to persist.
|
|
1157
|
+
let mergeSegments = this.domain?.canProjectToOneSegment(query);
|
|
1158
|
+
await this.log.join(results.results
|
|
1159
|
+
.flat()
|
|
1160
|
+
.map((x) => isResultIndexedValue(x) && x.entries.length > 0
|
|
1161
|
+
? x.entries[0]
|
|
1162
|
+
: x.context.head), { replicate: { assumeSynced: true, mergeSegments } });
|
|
1163
|
+
},
|
|
1164
|
+
dbType: this.constructor,
|
|
1165
|
+
maybeOpen: this.maybeSubprogramOpen.bind(this),
|
|
1166
|
+
prefetch: options.index?.prefetch,
|
|
1167
|
+
includeIndexed: options.index?.includeIndexed,
|
|
1168
|
+
});
|
|
1169
|
+
this._documentInternalChangeListenerCount = Math.max(0, this._documentChangeListenerCount - changeListenersBeforeIndexOpen);
|
|
1170
|
+
this._nativeDocumentFieldExtractionPlans ??= new Map();
|
|
1171
|
+
this._nativeDocumentFieldExtractionPlans.clear();
|
|
1172
|
+
this._nativeDocumentIdExtractionPlan =
|
|
1173
|
+
asTrustedDocumentIndex(this._index).getNativeDocumentFieldExtractionPlan(idProperty);
|
|
1174
|
+
// document v6 and below need log compatibility of v8 or below
|
|
1175
|
+
// document v7 needs log compatibility of v9
|
|
1176
|
+
let logCompatiblity = undefined;
|
|
1177
|
+
if (options.compatibility === 6) {
|
|
1178
|
+
logCompatiblity = 8;
|
|
1179
|
+
}
|
|
1180
|
+
else if (options.compatibility === 7) {
|
|
1181
|
+
logCompatiblity = 9;
|
|
1182
|
+
}
|
|
1183
|
+
this.domain = options.domain?.(this);
|
|
1184
|
+
let keepFunction;
|
|
1185
|
+
if (options?.keep === "self") {
|
|
1186
|
+
this.keepCache = new Set();
|
|
1187
|
+
keepFunction = async (e) => {
|
|
1188
|
+
if (this.keepCache?.has(e.hash)) {
|
|
1189
|
+
return true;
|
|
1190
|
+
}
|
|
1191
|
+
let signatures = undefined;
|
|
1192
|
+
if (e instanceof Entry) {
|
|
1193
|
+
signatures = e.signatures;
|
|
1194
|
+
}
|
|
1195
|
+
else {
|
|
1196
|
+
const entry = await this.log.log.get(e.hash);
|
|
1197
|
+
signatures = entry?.signatures;
|
|
1198
|
+
}
|
|
1199
|
+
if (!signatures) {
|
|
1200
|
+
return false;
|
|
1201
|
+
}
|
|
1202
|
+
for (const signature of signatures) {
|
|
1203
|
+
if (signature.publicKey.equals(this.node.identity.publicKey)) {
|
|
1204
|
+
this.keepCache?.add(e.hash);
|
|
1205
|
+
return true;
|
|
1206
|
+
}
|
|
1207
|
+
}
|
|
1208
|
+
return false;
|
|
1209
|
+
};
|
|
1210
|
+
}
|
|
1211
|
+
else {
|
|
1212
|
+
keepFunction = options?.keep;
|
|
1213
|
+
}
|
|
1214
|
+
await this.log.open({
|
|
1215
|
+
encoding: BORSH_ENCODING_OPERATION,
|
|
1216
|
+
canReplicate: options?.canReplicate,
|
|
1217
|
+
canAppend: this.canAppend.bind(this),
|
|
214
1218
|
onChange: this.handleChanges.bind(this),
|
|
215
1219
|
trim: options?.log?.trim,
|
|
1220
|
+
appendDurability: options?.appendDurability,
|
|
1221
|
+
nativeBackbone: options?.nativeBackbone,
|
|
1222
|
+
nativeGraph: options?.nativeGraph,
|
|
1223
|
+
nativeRangePlanner: options?.nativeRangePlanner,
|
|
216
1224
|
replicate: options?.replicate,
|
|
217
1225
|
replicas: options?.replicas,
|
|
218
1226
|
respondToIHaveTimeout: options?.respondToIHaveTimeout,
|
|
@@ -223,14 +1231,30 @@ let Documents = (() => {
|
|
|
223
1231
|
waitForPruneDelay: options?.waitForPruneDelay,
|
|
224
1232
|
distributionDebounceTime: options?.distributionDebounceTime,
|
|
225
1233
|
strictFullReplicaFallback: false,
|
|
226
|
-
domain:
|
|
227
|
-
? (
|
|
228
|
-
: undefined
|
|
1234
|
+
domain: options?.domain
|
|
1235
|
+
? () => options.domain(this)
|
|
1236
|
+
: undefined,
|
|
229
1237
|
compatibility: logCompatiblity,
|
|
230
1238
|
eagerBlocks: options?.eagerBlocks,
|
|
231
1239
|
fanout: options?.fanout,
|
|
232
1240
|
keep: keepFunction,
|
|
233
1241
|
});
|
|
1242
|
+
this._nativeBackboneDocumentIndexEnabled = false;
|
|
1243
|
+
if (this._mode !== "compat" &&
|
|
1244
|
+
options?.nativeBackbone &&
|
|
1245
|
+
typeof options.nativeBackbone !== "boolean" &&
|
|
1246
|
+
options.nativeBackbone.documentIndex === true) {
|
|
1247
|
+
this._nativeBackboneDocumentIndexEnabled =
|
|
1248
|
+
asTrustedDocumentIndex(this._index).attachNativeBackboneDocumentIndex(this.getSharedLogNativeBackbone(), { preserveExisting: this._mode === "native" }) === true;
|
|
1249
|
+
if (this._nativeBackboneDocumentIndexEnabled) {
|
|
1250
|
+
await initializeDocumentRust();
|
|
1251
|
+
}
|
|
1252
|
+
}
|
|
1253
|
+
this._optionCanPerformNativeFastPath = this._optionCanPerformNativePolicy
|
|
1254
|
+
? createCanPerformPolicyEvaluator(this._optionCanPerformNativePolicy, this.log.log.identity.publicKey)
|
|
1255
|
+
: undefined;
|
|
1256
|
+
this.assertNativeModeReady();
|
|
1257
|
+
this._documentBackend = this.createDocumentBackend();
|
|
234
1258
|
}
|
|
235
1259
|
async recover() {
|
|
236
1260
|
return this.log.recover();
|
|
@@ -248,229 +1272,1795 @@ let Documents = (() => {
|
|
|
248
1272
|
}
|
|
249
1273
|
try {
|
|
250
1274
|
let operation = l0;
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
document = this._index.valueEncoding.decoder(l0.data);
|
|
255
|
-
if (!document) {
|
|
256
|
-
return false;
|
|
257
|
-
}
|
|
258
|
-
}
|
|
259
|
-
else if (isDeleteOperation(l0)) {
|
|
260
|
-
// Nothing to do here by default
|
|
261
|
-
// checking if the document exists is not necessary
|
|
262
|
-
// since it might already be deleted
|
|
1275
|
+
if (this._optionCanPerform) {
|
|
1276
|
+
if (this._optionCanPerformNativePolicy && this.isNativeMode()) {
|
|
1277
|
+
return this.nativeCanPerformAllowsAppend(this._optionCanPerformNativePolicy, operation, entry, reference?.document);
|
|
263
1278
|
}
|
|
264
|
-
|
|
265
|
-
|
|
1279
|
+
let document = reference?.document;
|
|
1280
|
+
if (!document) {
|
|
1281
|
+
if (isPutOperation(l0)) {
|
|
1282
|
+
document =
|
|
1283
|
+
this._canAppendDecodedDocuments.get(l0) ??
|
|
1284
|
+
this._index.valueEncoding.decoder(l0.data);
|
|
1285
|
+
if (!document) {
|
|
1286
|
+
return false;
|
|
1287
|
+
}
|
|
1288
|
+
}
|
|
1289
|
+
else if (isDeleteOperation(l0)) {
|
|
1290
|
+
// Nothing to do here by default.
|
|
1291
|
+
// Checking if the document exists is not necessary since it
|
|
1292
|
+
// might already be deleted.
|
|
1293
|
+
}
|
|
1294
|
+
else {
|
|
1295
|
+
throw new Error("Unsupported operation");
|
|
1296
|
+
}
|
|
266
1297
|
}
|
|
267
|
-
|
|
268
|
-
|
|
1298
|
+
const previousEntries = this._optionCanPerformNativePolicy &&
|
|
1299
|
+
isPutOperation(operation) &&
|
|
1300
|
+
canPerformPolicyNeedsPreviousEntries(this._optionCanPerformNativePolicy)
|
|
1301
|
+
? await this.resolveCanPerformPreviousEntries(entry)
|
|
1302
|
+
: undefined;
|
|
1303
|
+
const deleteValue = this._optionCanPerformNativePolicy &&
|
|
1304
|
+
isDeleteOperation(operation) &&
|
|
1305
|
+
canPerformPolicyNeedsDeleteValue(this._optionCanPerformNativePolicy)
|
|
1306
|
+
? await this.resolveCanPerformDeleteValue(operation)
|
|
1307
|
+
: undefined;
|
|
269
1308
|
if (!(await this._optionCanPerform(isPutOperation(operation)
|
|
270
1309
|
? {
|
|
271
1310
|
type: "put",
|
|
272
1311
|
value: document,
|
|
273
1312
|
operation,
|
|
274
1313
|
entry: entry,
|
|
1314
|
+
previousEntries,
|
|
275
1315
|
}
|
|
276
1316
|
: {
|
|
277
1317
|
type: "delete",
|
|
1318
|
+
value: deleteValue,
|
|
278
1319
|
operation,
|
|
279
1320
|
entry: entry,
|
|
280
1321
|
}))) {
|
|
281
1322
|
return false;
|
|
282
1323
|
}
|
|
283
1324
|
}
|
|
284
|
-
}
|
|
285
|
-
catch (error) {
|
|
286
|
-
if (error instanceof BorshError) {
|
|
287
|
-
warn("Received payload that could not be decoded, skipping");
|
|
288
|
-
return false;
|
|
1325
|
+
}
|
|
1326
|
+
catch (error) {
|
|
1327
|
+
if (error instanceof BorshError) {
|
|
1328
|
+
warn("Received payload that could not be decoded, skipping");
|
|
1329
|
+
return false;
|
|
1330
|
+
}
|
|
1331
|
+
throw error;
|
|
1332
|
+
}
|
|
1333
|
+
return true;
|
|
1334
|
+
}
|
|
1335
|
+
async nativeCanPerformAllowsAppend(descriptor, operation, entry, document) {
|
|
1336
|
+
if (isPutOperation(operation)) {
|
|
1337
|
+
let previousSignerPublicKeys = [];
|
|
1338
|
+
let previousEntries = [];
|
|
1339
|
+
if (canPerformPolicyNeedsPreviousEntries(descriptor)) {
|
|
1340
|
+
const lookup = this.getNativeEntrySignerPublicKeys(entry.meta.next);
|
|
1341
|
+
if (lookup && lookup.every((key) => key != null)) {
|
|
1342
|
+
previousSignerPublicKeys = lookup;
|
|
1343
|
+
}
|
|
1344
|
+
else if (this.isNativeMode()) {
|
|
1345
|
+
if (entry.meta.next.length > 1) {
|
|
1346
|
+
return false;
|
|
1347
|
+
}
|
|
1348
|
+
const previousSigner = await this.getNativePreviousEntrySignerPublicKeyForPutOperation(operation);
|
|
1349
|
+
if (previousSigner?.publicKey) {
|
|
1350
|
+
previousSignerPublicKeys = [previousSigner.publicKey];
|
|
1351
|
+
}
|
|
1352
|
+
else if (previousSigner?.exists || entry.meta.next.length > 0) {
|
|
1353
|
+
return false;
|
|
1354
|
+
}
|
|
1355
|
+
}
|
|
1356
|
+
else {
|
|
1357
|
+
previousEntries = await this.resolveCanPerformPreviousEntries(entry);
|
|
1358
|
+
}
|
|
1359
|
+
}
|
|
1360
|
+
const entryPublicKeys = !document && canPerformPolicyPutNeedsEntryPublicKeys(descriptor)
|
|
1361
|
+
? entry.publicKeys.length > 0
|
|
1362
|
+
? entry.publicKeys
|
|
1363
|
+
: await entry.getPublicKeys()
|
|
1364
|
+
: [];
|
|
1365
|
+
return this.nativePutOperationPolicyAllows(descriptor, operation, document, previousEntries, previousSignerPublicKeys, entryPublicKeys);
|
|
1366
|
+
}
|
|
1367
|
+
const entryPublicKeys = this.nativeDeletePolicyNeedsEntryPublicKeys(descriptor)
|
|
1368
|
+
? entry.publicKeys.length > 0
|
|
1369
|
+
? entry.publicKeys
|
|
1370
|
+
: await entry.getPublicKeys()
|
|
1371
|
+
: undefined;
|
|
1372
|
+
return this.nativeDeleteOperationPolicyAllows(descriptor, operation, entryPublicKeys);
|
|
1373
|
+
}
|
|
1374
|
+
async resolveCanPerformPreviousEntries(entry) {
|
|
1375
|
+
const entries = [];
|
|
1376
|
+
for (const hash of entry.meta.next) {
|
|
1377
|
+
const previous = await this._resolveEntry(hash);
|
|
1378
|
+
if (previous) {
|
|
1379
|
+
entries.push(previous);
|
|
1380
|
+
}
|
|
1381
|
+
}
|
|
1382
|
+
return entries;
|
|
1383
|
+
}
|
|
1384
|
+
async resolveCanPerformDeleteValue(operation, options) {
|
|
1385
|
+
const key = operation.key instanceof indexerTypes.IdKey
|
|
1386
|
+
? operation.key
|
|
1387
|
+
: indexerTypes.toId(operation.key);
|
|
1388
|
+
const existing = await this.getLocalIndexedContext(key);
|
|
1389
|
+
const existingHead = this.getExistingContext(existing)?.head;
|
|
1390
|
+
if (!existingHead) {
|
|
1391
|
+
return;
|
|
1392
|
+
}
|
|
1393
|
+
const indexedDocument = await this.getLocalIdentityDocumentByHead(existingHead);
|
|
1394
|
+
if (indexedDocument) {
|
|
1395
|
+
return indexedDocument;
|
|
1396
|
+
}
|
|
1397
|
+
const indexedPolicyDocument = await this.getLocalIndexedDocumentForNativeDeletePolicy(key);
|
|
1398
|
+
if (indexedPolicyDocument) {
|
|
1399
|
+
return indexedPolicyDocument;
|
|
1400
|
+
}
|
|
1401
|
+
if (options?.allowEntryFallback === false) {
|
|
1402
|
+
return;
|
|
1403
|
+
}
|
|
1404
|
+
const existingEntry = await this._resolveEntry(existingHead, {
|
|
1405
|
+
remote: true,
|
|
1406
|
+
});
|
|
1407
|
+
const existingOperation = await existingEntry.getPayloadValue();
|
|
1408
|
+
if (!isPutOperation(existingOperation)) {
|
|
1409
|
+
return;
|
|
1410
|
+
}
|
|
1411
|
+
return this._index.valueEncoding.decoder(existingOperation.data);
|
|
1412
|
+
}
|
|
1413
|
+
async _canAppend(entry, reference) {
|
|
1414
|
+
const resolve = async (history) => {
|
|
1415
|
+
return typeof history === "string"
|
|
1416
|
+
? this.log.log.get(history) ||
|
|
1417
|
+
(await Entry.fromMultihash(this.log.log.blocks, history))
|
|
1418
|
+
: history;
|
|
1419
|
+
};
|
|
1420
|
+
const pointsToHistory = async (history) => {
|
|
1421
|
+
// make sure nexts only points to this document at some point in history
|
|
1422
|
+
let current = await resolve(history);
|
|
1423
|
+
const next = entry.meta.next[0];
|
|
1424
|
+
while (current?.hash &&
|
|
1425
|
+
next !== current?.hash &&
|
|
1426
|
+
current.meta.next.length > 0) {
|
|
1427
|
+
current = await this.log.log.get(current.meta.next[0]);
|
|
1428
|
+
}
|
|
1429
|
+
if (current?.hash === next) {
|
|
1430
|
+
return true; // Ok, we are pointing this new edit to some exising point in time of the old document
|
|
1431
|
+
}
|
|
1432
|
+
return false;
|
|
1433
|
+
};
|
|
1434
|
+
let initialized = false;
|
|
1435
|
+
const ensureInitialized = () => {
|
|
1436
|
+
if (!initialized) {
|
|
1437
|
+
entry.init({
|
|
1438
|
+
encoding: this.log.log.encoding,
|
|
1439
|
+
keychain: this.node.services.keychain,
|
|
1440
|
+
});
|
|
1441
|
+
initialized = true;
|
|
1442
|
+
}
|
|
1443
|
+
};
|
|
1444
|
+
try {
|
|
1445
|
+
const operation = reference?.operation ||
|
|
1446
|
+
(await this.getAppendOperation(entry, ensureInitialized));
|
|
1447
|
+
if (!operation) {
|
|
1448
|
+
return false;
|
|
1449
|
+
}
|
|
1450
|
+
if (isPutOperation(operation)) {
|
|
1451
|
+
// check nexts
|
|
1452
|
+
const putOperation = operation;
|
|
1453
|
+
let keyValue;
|
|
1454
|
+
if (reference?.document) {
|
|
1455
|
+
keyValue = this.idResolver(reference.document);
|
|
1456
|
+
}
|
|
1457
|
+
else {
|
|
1458
|
+
keyValue = await this.getNativeDocumentIdFromPutOperation(putOperation);
|
|
1459
|
+
if (keyValue == null) {
|
|
1460
|
+
if (this.isNativeMode()) {
|
|
1461
|
+
return false;
|
|
1462
|
+
}
|
|
1463
|
+
const value = this.index.valueEncoding.decoder(putOperation.data);
|
|
1464
|
+
this._canAppendDecodedDocuments.set(putOperation, value);
|
|
1465
|
+
keyValue = this.idResolver(value);
|
|
1466
|
+
}
|
|
1467
|
+
}
|
|
1468
|
+
const key = indexerTypes.toId(keyValue);
|
|
1469
|
+
const existingDocument = this.isNativeMode()
|
|
1470
|
+
? this.hasNativeDocumentContextLookup()
|
|
1471
|
+
? this.getNativeIndexedContext(key)
|
|
1472
|
+
: undefined
|
|
1473
|
+
: this.immutable
|
|
1474
|
+
? (await this.index.getDetailed(key, {
|
|
1475
|
+
resolve: false,
|
|
1476
|
+
local: true,
|
|
1477
|
+
remote: { strategy: "fallback" },
|
|
1478
|
+
}))?.[0]?.results[0]
|
|
1479
|
+
: await this.getLocalIndexedContext(key);
|
|
1480
|
+
if (this.isNativeMode() && !this.hasNativeDocumentContextLookup()) {
|
|
1481
|
+
return false;
|
|
1482
|
+
}
|
|
1483
|
+
const existingContext = this.getExistingContext(existingDocument);
|
|
1484
|
+
if (existingContext && existingContext.head !== entry.hash) {
|
|
1485
|
+
// This can happen if we reset the operation log without resetting the index, for example during recover.
|
|
1486
|
+
if (this.immutable) {
|
|
1487
|
+
// key already exist but pick the oldest entry
|
|
1488
|
+
// this is because we can not overwrite same id if immutable
|
|
1489
|
+
if (existingContext.created < entry.meta.clock.timestamp.wallTime) {
|
|
1490
|
+
return false;
|
|
1491
|
+
}
|
|
1492
|
+
if (entry.meta.next.length > 0) {
|
|
1493
|
+
return false; // can not append to immutable document
|
|
1494
|
+
}
|
|
1495
|
+
return putOperation;
|
|
1496
|
+
}
|
|
1497
|
+
else {
|
|
1498
|
+
if (this.strictHistory) {
|
|
1499
|
+
// make sure that the next pointer exist and points to the existing documents
|
|
1500
|
+
if (entry.meta.next.length !== 1) {
|
|
1501
|
+
return false;
|
|
1502
|
+
}
|
|
1503
|
+
if (entry.meta.next[0] === existingContext.head) {
|
|
1504
|
+
return putOperation;
|
|
1505
|
+
}
|
|
1506
|
+
const prevEntry = await this.log.log.entryIndex.get(existingContext.head);
|
|
1507
|
+
if (!prevEntry) {
|
|
1508
|
+
logger.error("Failed to find previous entry for document edit: " +
|
|
1509
|
+
entry.hash);
|
|
1510
|
+
return false;
|
|
1511
|
+
}
|
|
1512
|
+
const referenceHistoryCorrectly = await pointsToHistory(prevEntry);
|
|
1513
|
+
return referenceHistoryCorrectly ? putOperation : false;
|
|
1514
|
+
}
|
|
1515
|
+
else {
|
|
1516
|
+
return putOperation;
|
|
1517
|
+
}
|
|
1518
|
+
}
|
|
1519
|
+
}
|
|
1520
|
+
else {
|
|
1521
|
+
// Keep existing behavior: next pointers may express document dependencies.
|
|
1522
|
+
}
|
|
1523
|
+
}
|
|
1524
|
+
else if (isDeleteOperation(operation)) {
|
|
1525
|
+
if (entry.meta.next.length !== 1) {
|
|
1526
|
+
return false;
|
|
1527
|
+
}
|
|
1528
|
+
const deleteKey = operation.key instanceof indexerTypes.IdKey
|
|
1529
|
+
? operation.key
|
|
1530
|
+
: indexerTypes.toId(operation.key);
|
|
1531
|
+
const existingDocument = this.isNativeMode()
|
|
1532
|
+
? this.hasNativeDocumentContextLookup()
|
|
1533
|
+
? this.getNativeIndexedContext(deleteKey)
|
|
1534
|
+
: undefined
|
|
1535
|
+
: this.immutable
|
|
1536
|
+
? (await this.index.getDetailed(operation.key, {
|
|
1537
|
+
resolve: false,
|
|
1538
|
+
local: true,
|
|
1539
|
+
remote: true,
|
|
1540
|
+
}))?.[0]?.results[0]
|
|
1541
|
+
: await this.getLocalIndexedContext(deleteKey);
|
|
1542
|
+
if (this.isNativeMode() && !this.hasNativeDocumentContextLookup()) {
|
|
1543
|
+
return false;
|
|
1544
|
+
}
|
|
1545
|
+
const existingHead = this.getExistingContext(existingDocument)?.head;
|
|
1546
|
+
if (!existingHead) {
|
|
1547
|
+
// already deleted
|
|
1548
|
+
return coerceDeleteOperation(operation); // assume ok
|
|
1549
|
+
}
|
|
1550
|
+
if (entry.meta.next[0] === existingHead) {
|
|
1551
|
+
return coerceDeleteOperation(operation);
|
|
1552
|
+
}
|
|
1553
|
+
if (this.isNativeMode()) {
|
|
1554
|
+
return false;
|
|
1555
|
+
}
|
|
1556
|
+
let doc = await this.log.log.get(existingHead);
|
|
1557
|
+
if (!doc) {
|
|
1558
|
+
logger.error("Failed to find Document from head");
|
|
1559
|
+
return false;
|
|
1560
|
+
}
|
|
1561
|
+
if (await pointsToHistory(doc)) {
|
|
1562
|
+
// references the existing document
|
|
1563
|
+
return coerceDeleteOperation(operation);
|
|
1564
|
+
}
|
|
1565
|
+
return false;
|
|
1566
|
+
}
|
|
1567
|
+
else {
|
|
1568
|
+
throw new Error("Unsupported operation");
|
|
1569
|
+
}
|
|
1570
|
+
return operation;
|
|
1571
|
+
}
|
|
1572
|
+
catch (error) {
|
|
1573
|
+
if (error instanceof AccessError) {
|
|
1574
|
+
return false; // we cant index because we can not decrypt
|
|
1575
|
+
}
|
|
1576
|
+
else if (error instanceof BorshError) {
|
|
1577
|
+
warn("Received payload that could not be decoded, skipping");
|
|
1578
|
+
return false;
|
|
1579
|
+
}
|
|
1580
|
+
throw error;
|
|
1581
|
+
}
|
|
1582
|
+
}
|
|
1583
|
+
async getAppendOperation(entry, ensureInitialized) {
|
|
1584
|
+
if (this.isNativeMode()) {
|
|
1585
|
+
const operation = await this.getPlainEntryOperationFromStorage(entry);
|
|
1586
|
+
if (operation) {
|
|
1587
|
+
return operation;
|
|
1588
|
+
}
|
|
1589
|
+
return;
|
|
1590
|
+
}
|
|
1591
|
+
ensureInitialized?.();
|
|
1592
|
+
return entry.getPayloadValue();
|
|
1593
|
+
}
|
|
1594
|
+
async getPlainEntryOperationFromStorage(entry) {
|
|
1595
|
+
let storageBytes;
|
|
1596
|
+
try {
|
|
1597
|
+
storageBytes =
|
|
1598
|
+
Entry.getPreparedStorageBytes(entry) ?? entry.getStorageBytes();
|
|
1599
|
+
}
|
|
1600
|
+
catch {
|
|
1601
|
+
return;
|
|
1602
|
+
}
|
|
1603
|
+
try {
|
|
1604
|
+
const payloadData = await entryV0PlainPayloadDataFromStorage(storageBytes);
|
|
1605
|
+
return payloadData
|
|
1606
|
+
? BORSH_ENCODING_OPERATION.decoder(payloadData)
|
|
1607
|
+
: undefined;
|
|
1608
|
+
}
|
|
1609
|
+
catch {
|
|
1610
|
+
try {
|
|
1611
|
+
const payloadData = entry
|
|
1612
|
+
.payload?.data;
|
|
1613
|
+
return payloadData
|
|
1614
|
+
? BORSH_ENCODING_OPERATION.decoder(payloadData)
|
|
1615
|
+
: undefined;
|
|
1616
|
+
}
|
|
1617
|
+
catch {
|
|
1618
|
+
return;
|
|
1619
|
+
}
|
|
1620
|
+
}
|
|
1621
|
+
}
|
|
1622
|
+
getNativeDocumentFieldExtractionPlan(path) {
|
|
1623
|
+
const key = JSON.stringify(typeof path === "string" ? [path] : path);
|
|
1624
|
+
const plans = (this._nativeDocumentFieldExtractionPlans ??= new Map());
|
|
1625
|
+
if (plans.has(key)) {
|
|
1626
|
+
return plans.get(key);
|
|
1627
|
+
}
|
|
1628
|
+
const plan = asTrustedDocumentIndex(this._index).getNativeDocumentFieldExtractionPlan(path);
|
|
1629
|
+
plans.set(key, plan);
|
|
1630
|
+
return plan;
|
|
1631
|
+
}
|
|
1632
|
+
async getNativeDocumentFieldFromPutOperation(operation, path) {
|
|
1633
|
+
if (!this.isNativeMode()) {
|
|
1634
|
+
return;
|
|
1635
|
+
}
|
|
1636
|
+
const plan = this.getNativeDocumentFieldExtractionPlan(path);
|
|
1637
|
+
if (!plan) {
|
|
1638
|
+
return;
|
|
1639
|
+
}
|
|
1640
|
+
try {
|
|
1641
|
+
return await extractDocumentFieldSimple(operation.data, plan);
|
|
1642
|
+
}
|
|
1643
|
+
catch {
|
|
1644
|
+
return;
|
|
1645
|
+
}
|
|
1646
|
+
}
|
|
1647
|
+
async getNativeDocumentIdFromPutOperation(operation) {
|
|
1648
|
+
if (!this.isNativeMode() || !this._nativeDocumentIdExtractionPlan) {
|
|
1649
|
+
return;
|
|
1650
|
+
}
|
|
1651
|
+
try {
|
|
1652
|
+
const id = await extractDocumentFieldSimple(operation.data, this._nativeDocumentIdExtractionPlan);
|
|
1653
|
+
return id;
|
|
1654
|
+
}
|
|
1655
|
+
catch {
|
|
1656
|
+
return;
|
|
1657
|
+
}
|
|
1658
|
+
}
|
|
1659
|
+
preparePut(doc) {
|
|
1660
|
+
const keyValue = this.idResolver(doc);
|
|
1661
|
+
indexerTypes.checkId(keyValue);
|
|
1662
|
+
let encodedDocument = serialize(doc);
|
|
1663
|
+
if (encodedDocument.length > MAX_BATCH_SIZE) {
|
|
1664
|
+
throw new Error(`Document is too large (${encodedDocument.length * 1e-6}) mb). Needs to be less than ${MAX_BATCH_SIZE * 1e-6} mb`);
|
|
1665
|
+
}
|
|
1666
|
+
const key = indexerTypes.toId(keyValue);
|
|
1667
|
+
let operation;
|
|
1668
|
+
let encodedOperation;
|
|
1669
|
+
if (this.compatibility === 6) {
|
|
1670
|
+
if (typeof keyValue === "string") {
|
|
1671
|
+
operation = new PutWithKeyOperation({
|
|
1672
|
+
key: keyValue,
|
|
1673
|
+
data: encodedDocument,
|
|
1674
|
+
});
|
|
1675
|
+
}
|
|
1676
|
+
else {
|
|
1677
|
+
throw new Error("Key must be a string in compatibility mode v6");
|
|
1678
|
+
}
|
|
1679
|
+
}
|
|
1680
|
+
else {
|
|
1681
|
+
encodedOperation = encodePutOperationPayload(encodedDocument);
|
|
1682
|
+
encodedDocument = encodedOperation.subarray(PUT_OPERATION_PREFIX_LENGTH);
|
|
1683
|
+
operation = new PutOperation({
|
|
1684
|
+
data: encodedDocument,
|
|
1685
|
+
});
|
|
1686
|
+
}
|
|
1687
|
+
return {
|
|
1688
|
+
document: doc,
|
|
1689
|
+
encodedDocument,
|
|
1690
|
+
encodedOperation,
|
|
1691
|
+
keyValue,
|
|
1692
|
+
key,
|
|
1693
|
+
operation,
|
|
1694
|
+
};
|
|
1695
|
+
}
|
|
1696
|
+
preparePlainPut(doc) {
|
|
1697
|
+
if (this.compatibility === 6) {
|
|
1698
|
+
throw new Error("Plain put preparation is not supported in v6 mode");
|
|
1699
|
+
}
|
|
1700
|
+
const keyValue = this.idResolver(doc);
|
|
1701
|
+
indexerTypes.checkId(keyValue);
|
|
1702
|
+
const documentBytes = serialize(doc);
|
|
1703
|
+
if (documentBytes.length > MAX_BATCH_SIZE) {
|
|
1704
|
+
throw new Error(`Document is too large (${documentBytes.length * 1e-6}) mb). Needs to be less than ${MAX_BATCH_SIZE * 1e-6} mb`);
|
|
1705
|
+
}
|
|
1706
|
+
const operationPayloadBytes = encodePutOperationPayload(documentBytes);
|
|
1707
|
+
return {
|
|
1708
|
+
document: doc,
|
|
1709
|
+
encodedDocument: operationPayloadBytes.subarray(PUT_OPERATION_PREFIX_LENGTH),
|
|
1710
|
+
operationPayloadBytes,
|
|
1711
|
+
keyValue,
|
|
1712
|
+
key: indexerTypes.toId(keyValue),
|
|
1713
|
+
};
|
|
1714
|
+
}
|
|
1715
|
+
async put(doc, options) {
|
|
1716
|
+
return this._documentBackend.put(doc, options);
|
|
1717
|
+
}
|
|
1718
|
+
async putCompatDocumentBackend(doc, options) {
|
|
1719
|
+
const putOptions = this.normalizeNativeModePutOptions(options);
|
|
1720
|
+
const prepared = this.canUsePlainPutFastPath(doc, putOptions)
|
|
1721
|
+
? this.preparePlainPut(doc)
|
|
1722
|
+
: this.preparePut(doc);
|
|
1723
|
+
let existingLocalContext;
|
|
1724
|
+
let existingHead;
|
|
1725
|
+
if (!putOptions?.unique) {
|
|
1726
|
+
if (putOptions?.checkRemote) {
|
|
1727
|
+
existingHead = (await this._index.getDetailed(prepared.key, {
|
|
1728
|
+
resolve: false,
|
|
1729
|
+
local: true,
|
|
1730
|
+
remote: { replicate: putOptions?.replicate },
|
|
1731
|
+
}))?.[0]?.results[0]?.context.head;
|
|
1732
|
+
}
|
|
1733
|
+
else {
|
|
1734
|
+
existingLocalContext =
|
|
1735
|
+
(await this.getLocalIndexedContext(prepared.key)) || null;
|
|
1736
|
+
existingHead = existingLocalContext?.value.__context.head;
|
|
1737
|
+
}
|
|
1738
|
+
}
|
|
1739
|
+
const plainPutPlan = await this.createPlainPutCommitPlan(prepared, existingHead, existingLocalContext, putOptions);
|
|
1740
|
+
if (plainPutPlan) {
|
|
1741
|
+
return this.commitPlainPutPlan(plainPutPlan, putOptions);
|
|
1742
|
+
}
|
|
1743
|
+
const operation = "operation" in prepared
|
|
1744
|
+
? prepared.operation
|
|
1745
|
+
: new PutOperation({ data: prepared.encodedDocument });
|
|
1746
|
+
const appended = await this.log.append(operation, {
|
|
1747
|
+
...putOptions,
|
|
1748
|
+
meta: {
|
|
1749
|
+
next: existingHead ? [await this._resolveEntry(existingHead)] : [],
|
|
1750
|
+
...putOptions?.meta,
|
|
1751
|
+
},
|
|
1752
|
+
canAppend: (entry) => {
|
|
1753
|
+
return this.canAppend(entry, {
|
|
1754
|
+
document: prepared.document,
|
|
1755
|
+
operation,
|
|
1756
|
+
});
|
|
1757
|
+
},
|
|
1758
|
+
onChange: (change) => {
|
|
1759
|
+
return this.handleChanges(change, {
|
|
1760
|
+
document: prepared.document,
|
|
1761
|
+
operation,
|
|
1762
|
+
key: prepared.key,
|
|
1763
|
+
unique: putOptions?.unique,
|
|
1764
|
+
existing: existingLocalContext,
|
|
1765
|
+
});
|
|
1766
|
+
},
|
|
1767
|
+
replicate: putOptions?.replicate,
|
|
1768
|
+
});
|
|
1769
|
+
this.keepCache?.add(appended.entry.hash);
|
|
1770
|
+
return appended;
|
|
1771
|
+
}
|
|
1772
|
+
async putMany(docs, options) {
|
|
1773
|
+
return this._documentBackend.putMany(docs, options);
|
|
1774
|
+
}
|
|
1775
|
+
async putManyCompatDocumentBackend(docs, options) {
|
|
1776
|
+
if (docs.length === 0) {
|
|
1777
|
+
return { entries: [], removed: [] };
|
|
1778
|
+
}
|
|
1779
|
+
if (!this.canUsePlainPutManyFastPath(docs, options)) {
|
|
1780
|
+
return this.putManySequential(docs, options);
|
|
1781
|
+
}
|
|
1782
|
+
const prepared = docs.map((doc) => this.preparePlainPut(doc));
|
|
1783
|
+
if (this.hasDuplicatePreparedPutKeys(prepared)) {
|
|
1784
|
+
return this.putManySequential(docs, options);
|
|
1785
|
+
}
|
|
1786
|
+
const documentAppendCommit = await this.commitNativeDocumentAppendMany({
|
|
1787
|
+
puts: prepared.map((item) => ({
|
|
1788
|
+
document: item.document,
|
|
1789
|
+
key: item.key,
|
|
1790
|
+
documentBytes: item.encodedDocument,
|
|
1791
|
+
operationPayloadBytes: item.operationPayloadBytes,
|
|
1792
|
+
unique: options?.unique,
|
|
1793
|
+
existing: null,
|
|
1794
|
+
})),
|
|
1795
|
+
resolveTrimmedEntries: !this._index.canGetIdentityIndexedByHead(),
|
|
1796
|
+
options,
|
|
1797
|
+
});
|
|
1798
|
+
if (!documentAppendCommit) {
|
|
1799
|
+
return this.putManySequential(docs, options);
|
|
1800
|
+
}
|
|
1801
|
+
await this.handlePreparedPlainPutManyCommit(documentAppendCommit);
|
|
1802
|
+
for (const commit of documentAppendCommit.commits) {
|
|
1803
|
+
this.keepCache?.add(commit.append.hash);
|
|
1804
|
+
}
|
|
1805
|
+
return {
|
|
1806
|
+
get entries() {
|
|
1807
|
+
return documentAppendCommit.entries;
|
|
1808
|
+
},
|
|
1809
|
+
removed: documentAppendCommit.removed,
|
|
1810
|
+
};
|
|
1811
|
+
}
|
|
1812
|
+
async putManySequential(docs, options) {
|
|
1813
|
+
const entries = [];
|
|
1814
|
+
const removed = [];
|
|
1815
|
+
for (const doc of docs) {
|
|
1816
|
+
const appended = await this.put(doc, options);
|
|
1817
|
+
entries.push(appended.entry);
|
|
1818
|
+
removed.push(...appended.removed);
|
|
1819
|
+
}
|
|
1820
|
+
return { entries, removed };
|
|
1821
|
+
}
|
|
1822
|
+
hasDuplicatePreparedPutKeys(prepared) {
|
|
1823
|
+
const keys = new Set();
|
|
1824
|
+
for (const item of prepared) {
|
|
1825
|
+
if (keys.has(item.key.primitive)) {
|
|
1826
|
+
return true;
|
|
1827
|
+
}
|
|
1828
|
+
keys.add(item.key.primitive);
|
|
1829
|
+
}
|
|
1830
|
+
return false;
|
|
1831
|
+
}
|
|
1832
|
+
canUsePlainPutFastPath(doc, options) {
|
|
1833
|
+
return (this._mode !== "compat" &&
|
|
1834
|
+
this.canPerformAllowsPlainPutFastPath(doc) &&
|
|
1835
|
+
!this.immutable &&
|
|
1836
|
+
!this.strictHistory &&
|
|
1837
|
+
this.compatibility !== 6 &&
|
|
1838
|
+
!Program.isPrototypeOf(this._clazz) &&
|
|
1839
|
+
!options?.canAppend &&
|
|
1840
|
+
!options?.onChange &&
|
|
1841
|
+
!options?.signers &&
|
|
1842
|
+
!options?.identity &&
|
|
1843
|
+
!options?.encryption &&
|
|
1844
|
+
!options?.trim &&
|
|
1845
|
+
!options?.durability &&
|
|
1846
|
+
options?.deferIndexWrite === undefined &&
|
|
1847
|
+
!options?.meta?.type &&
|
|
1848
|
+
!(options?.meta && "data" in options.meta) &&
|
|
1849
|
+
!options?.meta?.next &&
|
|
1850
|
+
!options?.meta?.timestamp &&
|
|
1851
|
+
!options?.meta?.gidSeed &&
|
|
1852
|
+
options?.replicate !== true &&
|
|
1853
|
+
(!options?.target || options.target === "none") &&
|
|
1854
|
+
(options?.delivery === undefined || options.delivery === false) &&
|
|
1855
|
+
!options?.checkRemote &&
|
|
1856
|
+
options?.replicas === undefined);
|
|
1857
|
+
}
|
|
1858
|
+
canUsePlainPutManyFastPath(docs, options) {
|
|
1859
|
+
return (options?.unique === true &&
|
|
1860
|
+
options?.replicate !== true &&
|
|
1861
|
+
options?.target === "none" &&
|
|
1862
|
+
(options?.delivery === undefined || options.delivery === false) &&
|
|
1863
|
+
docs.every((doc) => this.canUsePlainPutFastPath(doc, options)));
|
|
1864
|
+
}
|
|
1865
|
+
async createPlainPutCommitPlan(prepared, existingHead, existingLocalContext, options, assumePlainPutFastPath = false) {
|
|
1866
|
+
if (("operation" in prepared &&
|
|
1867
|
+
!(prepared.operation instanceof PutOperation)) ||
|
|
1868
|
+
(!assumePlainPutFastPath &&
|
|
1869
|
+
!this.canUsePlainPutFastPath(prepared.document, options))) {
|
|
1870
|
+
return;
|
|
1871
|
+
}
|
|
1872
|
+
const indexedContextNext = existingHead
|
|
1873
|
+
? this.nextFromIndexedContext(existingHead, existingLocalContext)
|
|
1874
|
+
: undefined;
|
|
1875
|
+
const next = existingHead
|
|
1876
|
+
? indexedContextNext
|
|
1877
|
+
? [indexedContextNext]
|
|
1878
|
+
: [await this._resolveEntry(existingHead)]
|
|
1879
|
+
: [];
|
|
1880
|
+
const canCleanupTrimmedHeads = this._index.canGetIndexedKeyByHead();
|
|
1881
|
+
return {
|
|
1882
|
+
document: prepared.document,
|
|
1883
|
+
encodedDocument: prepared.encodedDocument,
|
|
1884
|
+
payloadData: "operationPayloadBytes" in prepared
|
|
1885
|
+
? prepared.operationPayloadBytes
|
|
1886
|
+
: (prepared.encodedOperation ??
|
|
1887
|
+
encodePutOperationPayload(prepared.operation.data)),
|
|
1888
|
+
key: prepared.key,
|
|
1889
|
+
operation: "operation" in prepared ? prepared.operation : undefined,
|
|
1890
|
+
next,
|
|
1891
|
+
skipMissingNextJoin: !options?.checkRemote,
|
|
1892
|
+
resolveTrimmedEntries: !canCleanupTrimmedHeads,
|
|
1893
|
+
useGenericChangeHandler: !options?.unique && existingLocalContext === undefined,
|
|
1894
|
+
unique: options?.unique,
|
|
1895
|
+
existing: existingLocalContext,
|
|
1896
|
+
};
|
|
1897
|
+
}
|
|
1898
|
+
commitPlainPutPlan(plan, options) {
|
|
1899
|
+
return mapMaybePromise(this.commitNativeDocumentAppend({
|
|
1900
|
+
document: plan.document,
|
|
1901
|
+
key: plan.key,
|
|
1902
|
+
operation: plan.operation,
|
|
1903
|
+
documentBytes: plan.encodedDocument,
|
|
1904
|
+
operationPayloadBytes: plan.payloadData,
|
|
1905
|
+
next: plan.next,
|
|
1906
|
+
skipMissingNextJoin: plan.skipMissingNextJoin,
|
|
1907
|
+
resolveTrimmedEntries: plan.resolveTrimmedEntries,
|
|
1908
|
+
options,
|
|
1909
|
+
unique: plan.unique,
|
|
1910
|
+
existing: plan.existing,
|
|
1911
|
+
}), (documentAppendCommit) => {
|
|
1912
|
+
const handled = plan.useGenericChangeHandler
|
|
1913
|
+
? this.handleChanges({
|
|
1914
|
+
added: [{ head: true, entry: documentAppendCommit.entry }],
|
|
1915
|
+
removed: documentAppendCommit.removed,
|
|
1916
|
+
}, {
|
|
1917
|
+
document: plan.document,
|
|
1918
|
+
operation: documentAppendCommit.operation ??
|
|
1919
|
+
plan.operation ??
|
|
1920
|
+
new PutOperation({ data: plan.encodedDocument }),
|
|
1921
|
+
key: plan.key,
|
|
1922
|
+
unique: plan.unique,
|
|
1923
|
+
existing: plan.existing,
|
|
1924
|
+
})
|
|
1925
|
+
: this.handlePreparedPlainPutCommit(documentAppendCommit);
|
|
1926
|
+
return mapMaybePromise(handled, () => {
|
|
1927
|
+
this.keepCache?.add(documentAppendCommit.append.hash);
|
|
1928
|
+
return {
|
|
1929
|
+
get entry() {
|
|
1930
|
+
return documentAppendCommit.entry;
|
|
1931
|
+
},
|
|
1932
|
+
removed: documentAppendCommit.removed,
|
|
1933
|
+
};
|
|
1934
|
+
});
|
|
1935
|
+
});
|
|
1936
|
+
}
|
|
1937
|
+
commitNativeDocumentAppend(input) {
|
|
1938
|
+
const trustedLog = asTrustedDocumentSharedLog(this.log);
|
|
1939
|
+
const appendOptions = {
|
|
1940
|
+
...input.options,
|
|
1941
|
+
meta: {
|
|
1942
|
+
next: input.next,
|
|
1943
|
+
...input.options?.meta,
|
|
1944
|
+
},
|
|
1945
|
+
replicate: input.options?.replicate,
|
|
1946
|
+
};
|
|
1947
|
+
const prepareNativeDocumentIndexWithAppendFacts = this.createNativeBackboneDocumentIndexAppendFactsPreparer(input);
|
|
1948
|
+
const preferAppendFactsDocumentIndex = this.isNativeMode() && !!prepareNativeDocumentIndexWithAppendFacts;
|
|
1949
|
+
return mapMaybePromise(preferAppendFactsDocumentIndex
|
|
1950
|
+
? undefined
|
|
1951
|
+
: this.prepareNativeBackboneDocumentIndexCommit(input), (nativeDocumentIndexCommit) => {
|
|
1952
|
+
let committedNativeDocumentIndex = nativeDocumentIndexCommit;
|
|
1953
|
+
const prepareNativeDocumentIndexWithAppendFactsForCommit = nativeDocumentIndexCommit
|
|
1954
|
+
? undefined
|
|
1955
|
+
: prepareNativeDocumentIndexWithAppendFacts;
|
|
1956
|
+
if (this.isNativeMode() &&
|
|
1957
|
+
!nativeDocumentIndexCommit &&
|
|
1958
|
+
!prepareNativeDocumentIndexWithAppendFactsForCommit) {
|
|
1959
|
+
throw this.nativeModeError("requires native document-index commit");
|
|
1960
|
+
}
|
|
1961
|
+
const appendProperties = {
|
|
1962
|
+
skipMissingNextJoin: input.skipMissingNextJoin,
|
|
1963
|
+
resolveTrimmedEntries: input.resolveTrimmedEntries,
|
|
1964
|
+
payloadData: input.operationPayloadBytes,
|
|
1965
|
+
useNativeExistingDocumentContext: input.useNativeExistingDocumentContext,
|
|
1966
|
+
...(nativeDocumentIndexCommit
|
|
1967
|
+
? {
|
|
1968
|
+
nativeBackboneDocumentIndex: this.toNativeBackboneDocumentIndexCommitInput(input, nativeDocumentIndexCommit),
|
|
1969
|
+
}
|
|
1970
|
+
: {}),
|
|
1971
|
+
...(prepareNativeDocumentIndexWithAppendFactsForCommit
|
|
1972
|
+
? {
|
|
1973
|
+
prepareNativeBackboneDocumentIndex: (facts) => {
|
|
1974
|
+
committedNativeDocumentIndex =
|
|
1975
|
+
prepareNativeDocumentIndexWithAppendFactsForCommit(facts);
|
|
1976
|
+
return committedNativeDocumentIndex
|
|
1977
|
+
? this.toNativeBackboneDocumentIndexCommitInput(input, committedNativeDocumentIndex)
|
|
1978
|
+
: undefined;
|
|
1979
|
+
},
|
|
1980
|
+
}
|
|
1981
|
+
: {}),
|
|
1982
|
+
};
|
|
1983
|
+
if (input.operation) {
|
|
1984
|
+
if (this.isNativeMode()) {
|
|
1985
|
+
throw this.nativeModeError("requires payload-backed put operations");
|
|
1986
|
+
}
|
|
1987
|
+
return mapMaybePromise(trustedLog.appendLocallyPrepared(input.operation, appendOptions, appendProperties), (appended) => this.createNativeCheckedDocumentAppendCommitFacts(input, appended, committedNativeDocumentIndex));
|
|
1988
|
+
}
|
|
1989
|
+
const commitOnlyAppend = this.isNativeMode()
|
|
1990
|
+
? trustedLog.appendStrictNativeDocumentPayloadCommitOnly(input.operationPayloadBytes, appendOptions, appendProperties)
|
|
1991
|
+
: trustedLog.appendLocallyPreparedPayloadCommitOnly(input.operationPayloadBytes, appendOptions, appendProperties);
|
|
1992
|
+
return mapMaybePromise(commitOnlyAppend, (commitOnly) => {
|
|
1993
|
+
if (commitOnly) {
|
|
1994
|
+
return this.createNativeCheckedDocumentAppendCommitFacts(input, commitOnly, committedNativeDocumentIndex);
|
|
1995
|
+
}
|
|
1996
|
+
if (this.isNativeMode()) {
|
|
1997
|
+
throw this.nativeModeError("requires native payload commit-only append");
|
|
1998
|
+
}
|
|
1999
|
+
return this.commitNativeDocumentAppendPayloadFallback(input, appendOptions, appendProperties, committedNativeDocumentIndex);
|
|
2000
|
+
});
|
|
2001
|
+
});
|
|
2002
|
+
}
|
|
2003
|
+
createNativeCheckedDocumentAppendCommitFacts(input, appended, nativeBackboneDocumentIndex) {
|
|
2004
|
+
return mapMaybePromise(this.createDocumentAppendCommitFacts(input, appended, nativeBackboneDocumentIndex), (commit) => {
|
|
2005
|
+
this.assertNativeModeDocumentAppendCommit(commit);
|
|
2006
|
+
return commit;
|
|
2007
|
+
});
|
|
2008
|
+
}
|
|
2009
|
+
assertNativeModeDocumentAppendCommit(commit) {
|
|
2010
|
+
if (!this.isNativeMode()) {
|
|
2011
|
+
return;
|
|
2012
|
+
}
|
|
2013
|
+
if (!commit.nativeBackboneDocumentIndexCommitted) {
|
|
2014
|
+
throw this.nativeModeError("requires native document-index commit");
|
|
2015
|
+
}
|
|
2016
|
+
}
|
|
2017
|
+
toNativeBackboneDocumentIndexCommitInput(input, commit, useLatestContext = false) {
|
|
2018
|
+
const canUsePlainPutPayload = commit.usePlainPutPayload === true ||
|
|
2019
|
+
(!!input.operationPayloadBytes && !!commit.projection);
|
|
2020
|
+
return {
|
|
2021
|
+
key: documentIndexStoreKey(input.key),
|
|
2022
|
+
valuePrefixBytes: commit.valuePrefixBytes,
|
|
2023
|
+
usePlainPutPayload: canUsePlainPutPayload,
|
|
2024
|
+
projection: commit.projection,
|
|
2025
|
+
existingCreated: input.unique || input.existing === null
|
|
2026
|
+
? undefined
|
|
2027
|
+
: input.existing?.value.__context.created,
|
|
2028
|
+
deleteTrimmedHeads: !this.hasDocumentChangeConsumers() &&
|
|
2029
|
+
this._index.canGetIndexedKeyByHead(),
|
|
2030
|
+
useLatestContext,
|
|
2031
|
+
requiredPreviousSignerPublicKey: input.requiredPreviousSignerPublicKey,
|
|
2032
|
+
};
|
|
2033
|
+
}
|
|
2034
|
+
prepareNativeBackboneDocumentIndexCommit(input) {
|
|
2035
|
+
if (!this._nativeBackboneDocumentIndexEnabled) {
|
|
2036
|
+
return;
|
|
2037
|
+
}
|
|
2038
|
+
return asTrustedDocumentIndex(this._index).prepareNativeBackboneDocumentIndexCommit(input.document, input.documentBytes, { entryPublicKeys: [this.log.log.identity.publicKey] });
|
|
2039
|
+
}
|
|
2040
|
+
createNativeBackboneDocumentIndexAppendFactsPreparer(input) {
|
|
2041
|
+
if (!this._nativeBackboneDocumentIndexEnabled ||
|
|
2042
|
+
!asTrustedDocumentIndex(this._index).canPrepareNativeBackboneDocumentIndexCommitWithAppendFacts()) {
|
|
2043
|
+
return;
|
|
2044
|
+
}
|
|
2045
|
+
const existing = input.unique || input.existing === null ? null : input.existing;
|
|
2046
|
+
return (facts) => {
|
|
2047
|
+
const appendFacts = {
|
|
2048
|
+
wallTime: BigInt(facts.wallTime),
|
|
2049
|
+
gid: facts.gid,
|
|
2050
|
+
payloadSize: facts.payloadSize,
|
|
2051
|
+
};
|
|
2052
|
+
const context = nativeDocumentContextFactsAsContext({
|
|
2053
|
+
created: existing?.value.__context.created || appendFacts.wallTime,
|
|
2054
|
+
modified: appendFacts.wallTime,
|
|
2055
|
+
head: "",
|
|
2056
|
+
gid: appendFacts.gid,
|
|
2057
|
+
size: appendFacts.payloadSize,
|
|
2058
|
+
});
|
|
2059
|
+
return asTrustedDocumentIndex(this._index).prepareNativeBackboneDocumentIndexCommitWithAppendFacts(input.document, input.documentBytes, context, { entryPublicKeys: [this.log.log.identity.publicKey] });
|
|
2060
|
+
};
|
|
2061
|
+
}
|
|
2062
|
+
async commitNativeDocumentAppendPayloadFallback(input, appendOptions, appendProperties, nativeBackboneDocumentIndex) {
|
|
2063
|
+
if (this.isNativeMode()) {
|
|
2064
|
+
throw this.nativeModeError("requires native payload append support");
|
|
2065
|
+
}
|
|
2066
|
+
const trustedLog = asTrustedDocumentSharedLog(this.log);
|
|
2067
|
+
let appended;
|
|
2068
|
+
try {
|
|
2069
|
+
appended = await trustedLog.appendLocallyPreparedPayload(input.operationPayloadBytes, appendOptions, appendProperties);
|
|
2070
|
+
}
|
|
2071
|
+
catch (error) {
|
|
2072
|
+
if (!(error instanceof Error) ||
|
|
2073
|
+
error.message !==
|
|
2074
|
+
"appendLocallyPrepared payload-only path requires native append support") {
|
|
2075
|
+
throw error;
|
|
2076
|
+
}
|
|
2077
|
+
appended = await trustedLog.appendLocallyPrepared(new PutOperation({ data: input.documentBytes }), appendOptions, appendProperties);
|
|
2078
|
+
}
|
|
2079
|
+
return this.createDocumentAppendCommitFacts(input, appended, nativeBackboneDocumentIndex);
|
|
2080
|
+
}
|
|
2081
|
+
async commitNativeDocumentAppendMany(input) {
|
|
2082
|
+
const trustedLog = asTrustedDocumentSharedLog(this.log);
|
|
2083
|
+
const nativeBackboneDocumentIndexes = await this.prepareNativeBackboneDocumentIndexCommitBatch(input.puts);
|
|
2084
|
+
const nativeBackboneDocumentIndexInputs = nativeBackboneDocumentIndexes?.map((commit, index) => this.toNativeBackboneDocumentIndexCommitInput(input.puts[index], commit, input.useNativeExistingDocumentContext === true));
|
|
2085
|
+
const nexts = input.puts.map((put) => {
|
|
2086
|
+
if (input.useNativeExistingDocumentContext === true) {
|
|
2087
|
+
return [];
|
|
2088
|
+
}
|
|
2089
|
+
const existing = put.unique || put.existing === null ? null : put.existing;
|
|
2090
|
+
if (!existing) {
|
|
2091
|
+
return [];
|
|
2092
|
+
}
|
|
2093
|
+
const context = existing.value.__context;
|
|
2094
|
+
const next = this.nextFromIndexedContext(context.head, existing);
|
|
2095
|
+
if (!next) {
|
|
2096
|
+
throw this.nativeModeError("requires indexed document context for non-unique putMany");
|
|
2097
|
+
}
|
|
2098
|
+
return [next];
|
|
2099
|
+
});
|
|
2100
|
+
const appended = await trustedLog.appendLocallyPreparedPayloadsManyIndependent(input.puts.map((put) => put.operationPayloadBytes), {
|
|
2101
|
+
...input.options,
|
|
2102
|
+
replicate: input.options?.replicate,
|
|
2103
|
+
}, {
|
|
2104
|
+
resolveTrimmedEntries: input.resolveTrimmedEntries,
|
|
2105
|
+
nexts,
|
|
2106
|
+
nativeBackboneDocumentIndexes: nativeBackboneDocumentIndexInputs,
|
|
2107
|
+
retainMaterializationBytes: this._hasLogTrim,
|
|
2108
|
+
});
|
|
2109
|
+
if (!appended) {
|
|
2110
|
+
if (this.isNativeMode()) {
|
|
2111
|
+
throw this.nativeModeError("requires native batched payload append support");
|
|
2112
|
+
}
|
|
2113
|
+
return undefined;
|
|
2114
|
+
}
|
|
2115
|
+
const appendInputs = input.puts.map((put, index) => ({
|
|
2116
|
+
input: nativeBackboneDocumentIndexes?.[index]
|
|
2117
|
+
? {
|
|
2118
|
+
...put,
|
|
2119
|
+
nativeBackboneDocumentIndex: nativeBackboneDocumentIndexes[index],
|
|
2120
|
+
}
|
|
2121
|
+
: put,
|
|
2122
|
+
appended: (() => {
|
|
2123
|
+
const materializeEntry = appended.materializeEntries?.[index];
|
|
2124
|
+
let entry;
|
|
2125
|
+
return {
|
|
2126
|
+
get entry() {
|
|
2127
|
+
return (entry ??= materializeEntry
|
|
2128
|
+
? materializeEntry()
|
|
2129
|
+
: appended.entries[index]);
|
|
2130
|
+
},
|
|
2131
|
+
removed: [],
|
|
2132
|
+
appendCommit: appended.appendCommits[index],
|
|
2133
|
+
};
|
|
2134
|
+
})(),
|
|
2135
|
+
}));
|
|
2136
|
+
const commits = await this.createDocumentAppendCommitFactsBatch(appendInputs);
|
|
2137
|
+
let entries;
|
|
2138
|
+
return {
|
|
2139
|
+
get entries() {
|
|
2140
|
+
return (entries ??= commits.map((commit) => commit.entry));
|
|
2141
|
+
},
|
|
2142
|
+
removed: appended.removed,
|
|
2143
|
+
commits,
|
|
2144
|
+
};
|
|
2145
|
+
}
|
|
2146
|
+
prepareNativeBackboneDocumentIndexCommitBatch(inputs) {
|
|
2147
|
+
if (!this._nativeBackboneDocumentIndexEnabled || inputs.length === 0) {
|
|
2148
|
+
return;
|
|
2149
|
+
}
|
|
2150
|
+
const commits = [];
|
|
2151
|
+
const finishAsync = (firstAsyncIndex, firstAsyncCommit) => Promise.all([
|
|
2152
|
+
firstAsyncCommit,
|
|
2153
|
+
...inputs
|
|
2154
|
+
.slice(firstAsyncIndex + 1)
|
|
2155
|
+
.map((input) => this.prepareNativeBackboneDocumentIndexCommit(input)),
|
|
2156
|
+
]).then((resolvedCommits) => {
|
|
2157
|
+
for (const commit of resolvedCommits) {
|
|
2158
|
+
if (!commit) {
|
|
2159
|
+
return;
|
|
2160
|
+
}
|
|
2161
|
+
commits.push(commit);
|
|
2162
|
+
}
|
|
2163
|
+
return commits;
|
|
2164
|
+
});
|
|
2165
|
+
for (let i = 0; i < inputs.length; i++) {
|
|
2166
|
+
const commit = this.prepareNativeBackboneDocumentIndexCommit(inputs[i]);
|
|
2167
|
+
if (isPromiseLike(commit)) {
|
|
2168
|
+
return finishAsync(i, commit);
|
|
2169
|
+
}
|
|
2170
|
+
if (!commit) {
|
|
2171
|
+
return;
|
|
2172
|
+
}
|
|
2173
|
+
commits.push(commit);
|
|
2174
|
+
}
|
|
2175
|
+
return commits;
|
|
2176
|
+
}
|
|
2177
|
+
createDocumentAppendCommitFacts(input, appended, nativeBackboneDocumentIndex) {
|
|
2178
|
+
const append = appended.appendCommit;
|
|
2179
|
+
const nativePreviousContext = append.documentPreviousContext == null
|
|
2180
|
+
? undefined
|
|
2181
|
+
: nativeDocumentContextFactsAsContext(append.documentPreviousContext);
|
|
2182
|
+
const nativePreviousIndexedContext = nativePreviousContext
|
|
2183
|
+
? {
|
|
2184
|
+
id: input.key,
|
|
2185
|
+
value: {
|
|
2186
|
+
__context: nativePreviousContext,
|
|
2187
|
+
},
|
|
2188
|
+
}
|
|
2189
|
+
: undefined;
|
|
2190
|
+
const inputWithExisting = input.existing === undefined && nativePreviousIndexedContext
|
|
2191
|
+
? {
|
|
2192
|
+
...input,
|
|
2193
|
+
existing: nativePreviousIndexedContext,
|
|
2194
|
+
}
|
|
2195
|
+
: input;
|
|
2196
|
+
const existing = inputWithExisting.unique || inputWithExisting.existing === null
|
|
2197
|
+
? null
|
|
2198
|
+
: inputWithExisting.existing;
|
|
2199
|
+
const contextInput = {
|
|
2200
|
+
existingCreated: existing?.value.__context.created,
|
|
2201
|
+
modified: append.wallTime,
|
|
2202
|
+
head: append.hash,
|
|
2203
|
+
gid: append.gid,
|
|
2204
|
+
size: append.payloadSize,
|
|
2205
|
+
};
|
|
2206
|
+
if (append.nativeBackboneDocumentIndexCommitted) {
|
|
2207
|
+
return this.createDocumentAppendCommitFactsWithLazyContext(inputWithExisting, appended, contextInput, nativeBackboneDocumentIndex);
|
|
2208
|
+
}
|
|
2209
|
+
const contextPlan = tryPlanDocumentContext(contextInput);
|
|
2210
|
+
if (contextPlan) {
|
|
2211
|
+
return this.createDocumentAppendCommitFactsWithContext(inputWithExisting, appended, contextPlan, nativeBackboneDocumentIndex);
|
|
2212
|
+
}
|
|
2213
|
+
return planDocumentContext(contextInput).then((plannedContext) => plannedContext
|
|
2214
|
+
? this.createDocumentAppendCommitFactsWithContext(inputWithExisting, appended, plannedContext, nativeBackboneDocumentIndex)
|
|
2215
|
+
: this.createDocumentAppendCommitFactsWithLazyContext(inputWithExisting, appended, contextInput, nativeBackboneDocumentIndex));
|
|
2216
|
+
}
|
|
2217
|
+
createDocumentAppendCommitFactsWithLazyContext(input, appended, contextInput, nativeBackboneDocumentIndex = input.nativeBackboneDocumentIndex) {
|
|
2218
|
+
let contextValues;
|
|
2219
|
+
let context;
|
|
2220
|
+
let contextBytes;
|
|
2221
|
+
const getContextValues = () => {
|
|
2222
|
+
if (contextValues) {
|
|
2223
|
+
return contextValues;
|
|
2224
|
+
}
|
|
2225
|
+
const modified = toContextBigInt(contextInput.modified);
|
|
2226
|
+
const existingCreated = contextInput.existingCreated == null
|
|
2227
|
+
? undefined
|
|
2228
|
+
: toContextBigInt(contextInput.existingCreated);
|
|
2229
|
+
return (contextValues = {
|
|
2230
|
+
created: existingCreated == null || existingCreated === 0n
|
|
2231
|
+
? modified
|
|
2232
|
+
: existingCreated,
|
|
2233
|
+
modified,
|
|
2234
|
+
head: contextInput.head,
|
|
2235
|
+
gid: contextInput.gid,
|
|
2236
|
+
size: contextInput.size,
|
|
2237
|
+
});
|
|
2238
|
+
};
|
|
2239
|
+
const getContext = () => (context ??= new Context(getContextValues()));
|
|
2240
|
+
const getContextBytes = () => (contextBytes ??= encodeDocumentContextSuffix(getContext()));
|
|
2241
|
+
return this.createNativeDocumentAppendTransaction(input, appended, {
|
|
2242
|
+
getContext,
|
|
2243
|
+
getContextBytes,
|
|
2244
|
+
}, nativeBackboneDocumentIndex);
|
|
2245
|
+
}
|
|
2246
|
+
createNativeDocumentAppendTransaction(input, appended, contextAccessors, nativeBackboneDocumentIndex = input.nativeBackboneDocumentIndex) {
|
|
2247
|
+
const append = appended.appendCommit;
|
|
2248
|
+
let contextualEncodedValueParts;
|
|
2249
|
+
let exposedNativeBackboneDocumentIndex;
|
|
2250
|
+
let nativeBackboneDocumentIndexContextSet = false;
|
|
2251
|
+
const ensureNativeBackboneDocumentIndexContext = () => {
|
|
2252
|
+
if (nativeBackboneDocumentIndexContextSet ||
|
|
2253
|
+
!nativeBackboneDocumentIndex?.setContext) {
|
|
2254
|
+
return;
|
|
2255
|
+
}
|
|
2256
|
+
nativeBackboneDocumentIndex.setContext(contextAccessors.getContext());
|
|
2257
|
+
nativeBackboneDocumentIndexContextSet = true;
|
|
2258
|
+
};
|
|
2259
|
+
const getNativeBackboneDocumentIndex = () => {
|
|
2260
|
+
if (!nativeBackboneDocumentIndex) {
|
|
2261
|
+
return;
|
|
2262
|
+
}
|
|
2263
|
+
return (exposedNativeBackboneDocumentIndex ??= {
|
|
2264
|
+
valuePrefixBytes: nativeBackboneDocumentIndex.valuePrefixBytes,
|
|
2265
|
+
projection: nativeBackboneDocumentIndex.projection,
|
|
2266
|
+
indexable: nativeBackboneDocumentIndex.indexable,
|
|
2267
|
+
getIndexable: nativeBackboneDocumentIndex.getIndexable
|
|
2268
|
+
? () => {
|
|
2269
|
+
ensureNativeBackboneDocumentIndexContext();
|
|
2270
|
+
return nativeBackboneDocumentIndex.getIndexable();
|
|
2271
|
+
}
|
|
2272
|
+
: undefined,
|
|
2273
|
+
setContext: nativeBackboneDocumentIndex.setContext
|
|
2274
|
+
? (context) => {
|
|
2275
|
+
nativeBackboneDocumentIndex.setContext(context);
|
|
2276
|
+
nativeBackboneDocumentIndexContextSet = true;
|
|
2277
|
+
}
|
|
2278
|
+
: undefined,
|
|
2279
|
+
});
|
|
2280
|
+
};
|
|
2281
|
+
return {
|
|
2282
|
+
document: input.document,
|
|
2283
|
+
key: input.key,
|
|
2284
|
+
operation: input.operation,
|
|
2285
|
+
encodedDocument: input.documentBytes,
|
|
2286
|
+
operationPayloadBytes: input.operationPayloadBytes,
|
|
2287
|
+
get entry() {
|
|
2288
|
+
return appended.entry;
|
|
2289
|
+
},
|
|
2290
|
+
removed: appended.removed,
|
|
2291
|
+
removedHashes: appended.removedHashes,
|
|
2292
|
+
append,
|
|
2293
|
+
coordinateFields: append.coordinateFields,
|
|
2294
|
+
get context() {
|
|
2295
|
+
return contextAccessors.getContext();
|
|
2296
|
+
},
|
|
2297
|
+
get contextBytes() {
|
|
2298
|
+
return contextAccessors.getContextBytes();
|
|
2299
|
+
},
|
|
2300
|
+
get contextualEncodedValueParts() {
|
|
2301
|
+
return (contextualEncodedValueParts ??= {
|
|
2302
|
+
prefix: input.documentBytes,
|
|
2303
|
+
suffix: contextAccessors.getContextBytes(),
|
|
2304
|
+
});
|
|
2305
|
+
},
|
|
2306
|
+
nativeBackboneDocumentIndexCommitted: appended.appendCommit.nativeBackboneDocumentIndexCommitted,
|
|
2307
|
+
nativeBackboneDocumentIndexTrimmedHeadsProcessed: appended.appendCommit.nativeBackboneDocumentIndexTrimmedHeadsProcessed,
|
|
2308
|
+
get nativeBackboneDocumentIndex() {
|
|
2309
|
+
return getNativeBackboneDocumentIndex();
|
|
2310
|
+
},
|
|
2311
|
+
unique: input.unique,
|
|
2312
|
+
existing: input.existing,
|
|
2313
|
+
};
|
|
2314
|
+
}
|
|
2315
|
+
async createDocumentAppendCommitFactsBatch(rows) {
|
|
2316
|
+
const contextInputs = rows.map(({ input, appended }) => {
|
|
2317
|
+
const append = appended.appendCommit;
|
|
2318
|
+
const nativePreviousContext = append.documentPreviousContext == null
|
|
2319
|
+
? undefined
|
|
2320
|
+
: nativeDocumentContextFactsAsContext(append.documentPreviousContext);
|
|
2321
|
+
const nativePreviousIndexedContext = nativePreviousContext
|
|
2322
|
+
? {
|
|
2323
|
+
id: input.key,
|
|
2324
|
+
value: {
|
|
2325
|
+
__context: nativePreviousContext,
|
|
2326
|
+
},
|
|
2327
|
+
}
|
|
2328
|
+
: undefined;
|
|
2329
|
+
const inputWithExisting = input.existing === undefined && nativePreviousIndexedContext
|
|
2330
|
+
? {
|
|
2331
|
+
...input,
|
|
2332
|
+
existing: nativePreviousIndexedContext,
|
|
2333
|
+
}
|
|
2334
|
+
: input;
|
|
2335
|
+
const existing = inputWithExisting.unique || inputWithExisting.existing === null
|
|
2336
|
+
? null
|
|
2337
|
+
: inputWithExisting.existing;
|
|
2338
|
+
return {
|
|
2339
|
+
existingCreated: existing?.value.__context.created,
|
|
2340
|
+
modified: append.wallTime,
|
|
2341
|
+
head: append.hash,
|
|
2342
|
+
gid: append.gid,
|
|
2343
|
+
size: append.payloadSize,
|
|
2344
|
+
};
|
|
2345
|
+
});
|
|
2346
|
+
const contextPlans = tryPlanDocumentContextBatch(contextInputs) ??
|
|
2347
|
+
(await planDocumentContextBatch(contextInputs));
|
|
2348
|
+
return rows.map((row, index) => {
|
|
2349
|
+
const append = row.appended.appendCommit;
|
|
2350
|
+
const nativePreviousContext = append.documentPreviousContext == null
|
|
2351
|
+
? undefined
|
|
2352
|
+
: nativeDocumentContextFactsAsContext(append.documentPreviousContext);
|
|
2353
|
+
const nativePreviousIndexedContext = nativePreviousContext
|
|
2354
|
+
? {
|
|
2355
|
+
id: row.input.key,
|
|
2356
|
+
value: {
|
|
2357
|
+
__context: nativePreviousContext,
|
|
2358
|
+
},
|
|
2359
|
+
}
|
|
2360
|
+
: undefined;
|
|
2361
|
+
const input = row.input.existing === undefined && nativePreviousIndexedContext
|
|
2362
|
+
? {
|
|
2363
|
+
...row.input,
|
|
2364
|
+
existing: nativePreviousIndexedContext,
|
|
2365
|
+
}
|
|
2366
|
+
: row.input;
|
|
2367
|
+
const contextPlan = contextPlans?.[index];
|
|
2368
|
+
if (!contextPlan) {
|
|
2369
|
+
return this.createDocumentAppendCommitFactsWithLazyContext(input, row.appended, contextInputs[index], input.nativeBackboneDocumentIndex);
|
|
2370
|
+
}
|
|
2371
|
+
if (input.nativeBackboneDocumentIndex) {
|
|
2372
|
+
let context;
|
|
2373
|
+
return this.createNativeDocumentAppendTransaction(input, row.appended, {
|
|
2374
|
+
getContext: () => (context ??= new Context(contextPlan)),
|
|
2375
|
+
getContextBytes: () => contextPlan.contextBytes,
|
|
2376
|
+
}, input.nativeBackboneDocumentIndex);
|
|
2377
|
+
}
|
|
2378
|
+
return this.createDocumentAppendCommitFactsWithContext(input, row.appended, contextPlan);
|
|
2379
|
+
});
|
|
2380
|
+
}
|
|
2381
|
+
createDocumentAppendCommitFactsWithContext(input, appended, contextPlan, preparedNativeBackboneDocumentIndex = input.nativeBackboneDocumentIndex) {
|
|
2382
|
+
const append = appended.appendCommit;
|
|
2383
|
+
const context = new Context(contextPlan);
|
|
2384
|
+
const nativeBackboneDocumentIndex = preparedNativeBackboneDocumentIndex ??
|
|
2385
|
+
(append.nativeBackboneDocumentIndexCommitted
|
|
2386
|
+
? undefined
|
|
2387
|
+
: this._nativeBackboneDocumentIndexEnabled
|
|
2388
|
+
? asTrustedDocumentIndex(this._index).prepareNativeBackboneDocumentIndexCommitWithAppendFacts(input.document, input.documentBytes, context, { entryPublicKeys: [this.log.log.identity.publicKey] })
|
|
2389
|
+
: undefined);
|
|
2390
|
+
return this.createNativeDocumentAppendTransaction(input, appended, {
|
|
2391
|
+
getContext: () => context,
|
|
2392
|
+
getContextBytes: () => contextPlan.contextBytes,
|
|
2393
|
+
}, nativeBackboneDocumentIndex);
|
|
2394
|
+
}
|
|
2395
|
+
hasDocumentChangeConsumers() {
|
|
2396
|
+
if (this._valueClassIsProgram === true) {
|
|
2397
|
+
// Program-valued documents must always materialize removed values so
|
|
2398
|
+
// that open subprograms are dropped on delete, even without listeners.
|
|
2399
|
+
return true;
|
|
2400
|
+
}
|
|
2401
|
+
const changeListenerCount = this._documentChangeListenerCount ?? 0;
|
|
2402
|
+
const internalChangeListenerCount = this._documentInternalChangeListenerCount ?? 0;
|
|
2403
|
+
return (changeListenerCount > internalChangeListenerCount ||
|
|
2404
|
+
this._index.hasPending === true);
|
|
2405
|
+
}
|
|
2406
|
+
dispatchDocumentChangeIfObserved(documentsChanged) {
|
|
2407
|
+
if (!this.hasDocumentChangeConsumers()) {
|
|
2408
|
+
return;
|
|
2409
|
+
}
|
|
2410
|
+
this.events.dispatchEvent(new CustomEvent("change", { detail: documentsChanged }));
|
|
2411
|
+
}
|
|
2412
|
+
nextFromIndexedContext(existingHead, existing) {
|
|
2413
|
+
const context = existing?.value.__context;
|
|
2414
|
+
if (!context || context.head !== existingHead) {
|
|
2415
|
+
return;
|
|
2416
|
+
}
|
|
2417
|
+
return new ShallowEntry({
|
|
2418
|
+
hash: context.head,
|
|
2419
|
+
head: false,
|
|
2420
|
+
payloadSize: context.size,
|
|
2421
|
+
meta: new ShallowMeta({
|
|
2422
|
+
gid: context.gid,
|
|
2423
|
+
clock: new LamportClock({
|
|
2424
|
+
id: this.log.log.identity.publicKey.bytes,
|
|
2425
|
+
timestamp: new Timestamp({
|
|
2426
|
+
wallTime: context.modified,
|
|
2427
|
+
logical: 0,
|
|
2428
|
+
}),
|
|
2429
|
+
}),
|
|
2430
|
+
next: [],
|
|
2431
|
+
type: EntryType.APPEND,
|
|
2432
|
+
}),
|
|
2433
|
+
});
|
|
2434
|
+
}
|
|
2435
|
+
handlePreparedPlainPutCommit(commit) {
|
|
2436
|
+
const shouldPrepareChange = this.hasDocumentChangeConsumers();
|
|
2437
|
+
const removedAlreadyHandled = commit.nativeBackboneDocumentIndexTrimmedHeadsProcessed === true;
|
|
2438
|
+
const removedHashes = commit.removedHashes ?? [];
|
|
2439
|
+
const hasRemovedFacts = commit.removed.length > 0 || removedHashes.length > 0;
|
|
2440
|
+
const existing = commit.unique || commit.existing === null ? null : commit.existing;
|
|
2441
|
+
const persistNativeBackboneDocumentIndexCommit = () => {
|
|
2442
|
+
if (!commit.nativeBackboneDocumentIndexCommitted) {
|
|
2443
|
+
return;
|
|
2444
|
+
}
|
|
2445
|
+
if (this._mode === "native") {
|
|
2446
|
+
return true;
|
|
2447
|
+
}
|
|
2448
|
+
return asTrustedDocumentIndex(this._index)._persistPreparedNativeBackboneDocumentIndexStoredWithContext(commit.key, commit.context, commit.nativeBackboneDocumentIndex, commit.contextualEncodedValueParts, {
|
|
2449
|
+
replace: existing != null,
|
|
2450
|
+
});
|
|
2451
|
+
};
|
|
2452
|
+
if (!shouldPrepareChange &&
|
|
2453
|
+
(!hasRemovedFacts || removedAlreadyHandled) &&
|
|
2454
|
+
commit.nativeBackboneDocumentIndexCommitted) {
|
|
2455
|
+
if (!this.strictHistory && existing) {
|
|
2456
|
+
const shouldIgnoreChange = this.immutable
|
|
2457
|
+
? existing.value.__context.modified < commit.append.wallTime
|
|
2458
|
+
: existing.value.__context.modified > commit.append.wallTime;
|
|
2459
|
+
if (shouldIgnoreChange) {
|
|
2460
|
+
return;
|
|
2461
|
+
}
|
|
2462
|
+
}
|
|
2463
|
+
const finishCommitted = () => {
|
|
2464
|
+
this._index._cacheResolvedIdentityValue(commit.key.primitive, commit.document);
|
|
2465
|
+
};
|
|
2466
|
+
const persisted = persistNativeBackboneDocumentIndexCommit();
|
|
2467
|
+
return persisted === undefined || persisted === false
|
|
2468
|
+
? finishCommitted()
|
|
2469
|
+
: mapMaybePromise(persisted, finishCommitted);
|
|
2470
|
+
}
|
|
2471
|
+
const documentsChanged = shouldPrepareChange
|
|
2472
|
+
? {
|
|
2473
|
+
added: [],
|
|
2474
|
+
removed: [],
|
|
2475
|
+
}
|
|
2476
|
+
: undefined;
|
|
2477
|
+
const modified = new Set();
|
|
2478
|
+
if (!this.strictHistory && existing) {
|
|
2479
|
+
const shouldIgnoreChange = this.immutable
|
|
2480
|
+
? existing.value.__context.modified < commit.append.wallTime
|
|
2481
|
+
: existing.value.__context.modified > commit.append.wallTime;
|
|
2482
|
+
if (shouldIgnoreChange) {
|
|
2483
|
+
modified.add(commit.key.primitive);
|
|
2484
|
+
}
|
|
2485
|
+
}
|
|
2486
|
+
const finishRemoved = () => {
|
|
2487
|
+
if (!shouldPrepareChange) {
|
|
2488
|
+
if (!hasRemovedFacts || removedAlreadyHandled) {
|
|
2489
|
+
return undefined;
|
|
2490
|
+
}
|
|
2491
|
+
if (commit.removed.length === 0 && removedHashes.length > 0) {
|
|
2492
|
+
const handled = this.tryHandlePreparedPlainPutCommitRemovedHashesFromHeads(removedHashes, modified);
|
|
2493
|
+
if (handled !== undefined) {
|
|
2494
|
+
return mapMaybePromise(handled, (handledHeads) => {
|
|
2495
|
+
if (handledHeads.size === removedHashes.length) {
|
|
2496
|
+
return undefined;
|
|
2497
|
+
}
|
|
2498
|
+
const remaining = removedHashes.filter((hash) => !handledHeads.has(hash));
|
|
2499
|
+
return remaining.length === 0
|
|
2500
|
+
? undefined
|
|
2501
|
+
: this.handlePreparedPlainPutCommitRemovedHashes(remaining, modified);
|
|
2502
|
+
});
|
|
2503
|
+
}
|
|
2504
|
+
return this.handlePreparedPlainPutCommitRemovedHashes(removedHashes, modified);
|
|
2505
|
+
}
|
|
2506
|
+
const handled = this.tryHandlePreparedPlainPutCommitRemovedFromHeads(commit.removed, modified);
|
|
2507
|
+
if (handled !== undefined) {
|
|
2508
|
+
return mapMaybePromise(handled, (handledHeads) => {
|
|
2509
|
+
if (handledHeads.size === commit.removed.length) {
|
|
2510
|
+
return undefined;
|
|
2511
|
+
}
|
|
2512
|
+
const remaining = commit.removed.filter((entry) => !handledHeads.has(entry.hash));
|
|
2513
|
+
return remaining.length === 0
|
|
2514
|
+
? undefined
|
|
2515
|
+
: this.handlePreparedPlainPutCommitRemoved(remaining, modified);
|
|
2516
|
+
});
|
|
2517
|
+
}
|
|
2518
|
+
return this.handlePreparedPlainPutCommitRemoved(commit.removed, modified);
|
|
2519
|
+
}
|
|
2520
|
+
if (commit.removed.length === 0) {
|
|
2521
|
+
if (removedHashes.length > 0) {
|
|
2522
|
+
return this.handlePreparedPlainPutCommitRemovedHashes(removedHashes, modified, documentsChanged);
|
|
2523
|
+
}
|
|
2524
|
+
this.dispatchDocumentChangeIfObserved(documentsChanged);
|
|
2525
|
+
return;
|
|
2526
|
+
}
|
|
2527
|
+
return this.handlePreparedPlainPutCommitRemoved(commit.removed, modified, documentsChanged);
|
|
2528
|
+
};
|
|
2529
|
+
const finishIndexed = (indexedDocument) => {
|
|
2530
|
+
if (indexedDocument) {
|
|
2531
|
+
if (documentsChanged) {
|
|
2532
|
+
documentsChanged.added.push(indexedDocument);
|
|
2533
|
+
}
|
|
2534
|
+
modified.add(commit.key.primitive);
|
|
2535
|
+
return finishRemoved();
|
|
2536
|
+
}
|
|
2537
|
+
return mapMaybePromise(this._index.putWithContext(commit.document, commit.key, commit.context, {
|
|
2538
|
+
replace: existing != null,
|
|
2539
|
+
encodedValueParts: commit.contextualEncodedValueParts,
|
|
2540
|
+
transformFacts: { entryPublicKeys: commit.entry.publicKeys },
|
|
2541
|
+
}), ({ indexable }) => {
|
|
2542
|
+
if (documentsChanged) {
|
|
2543
|
+
documentsChanged.added.push(coerceWithIndexed(coerceWithContext(commit.document, commit.context), indexable));
|
|
2544
|
+
}
|
|
2545
|
+
modified.add(commit.key.primitive);
|
|
2546
|
+
return finishRemoved();
|
|
2547
|
+
});
|
|
2548
|
+
};
|
|
2549
|
+
if (!modified.has(commit.key.primitive)) {
|
|
2550
|
+
if (commit.nativeBackboneDocumentIndexCommitted) {
|
|
2551
|
+
const finishCommitted = () => {
|
|
2552
|
+
this._index._cacheResolvedIdentityValue(commit.key.primitive, commit.document);
|
|
2553
|
+
if (!shouldPrepareChange) {
|
|
2554
|
+
modified.add(commit.key.primitive);
|
|
2555
|
+
return finishRemoved();
|
|
2556
|
+
}
|
|
2557
|
+
const withContext = coerceWithContext(commit.document, commit.context);
|
|
2558
|
+
if (commit.nativeBackboneDocumentIndex?.indexable) {
|
|
2559
|
+
return finishIndexed(coerceWithIndexed(withContext, commit.nativeBackboneDocumentIndex.indexable));
|
|
2560
|
+
}
|
|
2561
|
+
if (commit.nativeBackboneDocumentIndex?.getIndexable) {
|
|
2562
|
+
return finishIndexed(coerceWithLazyIndexed(withContext, commit.nativeBackboneDocumentIndex.getIndexable));
|
|
2563
|
+
}
|
|
2564
|
+
return finishIndexed(coerceWithIndexed(withContext, commit.document));
|
|
2565
|
+
};
|
|
2566
|
+
const persisted = persistNativeBackboneDocumentIndexCommit();
|
|
2567
|
+
return persisted === undefined || persisted === false
|
|
2568
|
+
? finishCommitted()
|
|
2569
|
+
: mapMaybePromise(persisted, finishCommitted);
|
|
2570
|
+
}
|
|
2571
|
+
if (commit.nativeBackboneDocumentIndex) {
|
|
2572
|
+
const nativePreparedIndexPut = asTrustedDocumentIndex(this._index)._putPreparedNativeBackboneDocumentIndexWithContext(commit.document, commit.key, commit.context, commit.nativeBackboneDocumentIndex, {
|
|
2573
|
+
replace: existing != null,
|
|
2574
|
+
});
|
|
2575
|
+
if (nativePreparedIndexPut !== undefined) {
|
|
2576
|
+
return mapMaybePromise(nativePreparedIndexPut, finishIndexed);
|
|
2577
|
+
}
|
|
2578
|
+
}
|
|
2579
|
+
const storedIdentityPut = this._index._putStoredIdentityWithContext(commit.document, commit.key, commit.context, commit.contextualEncodedValueParts, {
|
|
2580
|
+
replace: existing != null,
|
|
2581
|
+
});
|
|
2582
|
+
if (storedIdentityPut !== undefined) {
|
|
2583
|
+
return mapMaybePromise(storedIdentityPut, finishIndexed);
|
|
2584
|
+
}
|
|
2585
|
+
return mapMaybePromise(this._index._putIdentityWithContext(commit.document, commit.key, commit.context, {
|
|
2586
|
+
replace: existing != null,
|
|
2587
|
+
encodedValueParts: commit.contextualEncodedValueParts,
|
|
2588
|
+
transformFacts: { entryPublicKeys: commit.entry.publicKeys },
|
|
2589
|
+
}), finishIndexed);
|
|
2590
|
+
}
|
|
2591
|
+
return finishRemoved();
|
|
2592
|
+
}
|
|
2593
|
+
tryHandlePreparedPlainPutCommitRemovedFromHeads(removedEntries, modified) {
|
|
2594
|
+
const handled = new Set();
|
|
2595
|
+
const deleteKeys = [];
|
|
2596
|
+
for (const removed of removedEntries) {
|
|
2597
|
+
if (removed instanceof Entry) {
|
|
2598
|
+
continue;
|
|
2599
|
+
}
|
|
2600
|
+
const resolved = this._index.tryGetIdentityIndexedKeyByHead(removed.hash);
|
|
2601
|
+
if (!resolved.supported) {
|
|
2602
|
+
return;
|
|
2603
|
+
}
|
|
2604
|
+
if (!resolved.key) {
|
|
2605
|
+
continue;
|
|
2606
|
+
}
|
|
2607
|
+
handled.add(removed.hash);
|
|
2608
|
+
if (modified.has(resolved.key.primitive)) {
|
|
2609
|
+
continue;
|
|
2610
|
+
}
|
|
2611
|
+
deleteKeys.push(resolved.key);
|
|
2612
|
+
modified.add(resolved.key.primitive);
|
|
2613
|
+
}
|
|
2614
|
+
if (deleteKeys.length === 0) {
|
|
2615
|
+
return handled;
|
|
2616
|
+
}
|
|
2617
|
+
return mapMaybePromise(this._index.delManyMaybe(deleteKeys), () => handled);
|
|
2618
|
+
}
|
|
2619
|
+
tryHandlePreparedPlainPutCommitRemovedHashesFromHeads(removedHashes, modified) {
|
|
2620
|
+
const handled = new Set();
|
|
2621
|
+
const deleteKeys = [];
|
|
2622
|
+
for (const hash of removedHashes) {
|
|
2623
|
+
const resolved = this._index.tryGetIdentityIndexedKeyByHead(hash);
|
|
2624
|
+
if (!resolved.supported) {
|
|
2625
|
+
return;
|
|
2626
|
+
}
|
|
2627
|
+
if (!resolved.key) {
|
|
2628
|
+
continue;
|
|
2629
|
+
}
|
|
2630
|
+
handled.add(hash);
|
|
2631
|
+
if (modified.has(resolved.key.primitive)) {
|
|
2632
|
+
continue;
|
|
2633
|
+
}
|
|
2634
|
+
deleteKeys.push(resolved.key);
|
|
2635
|
+
modified.add(resolved.key.primitive);
|
|
2636
|
+
}
|
|
2637
|
+
if (deleteKeys.length === 0) {
|
|
2638
|
+
return handled;
|
|
2639
|
+
}
|
|
2640
|
+
return mapMaybePromise(this._index.delManyMaybe(deleteKeys), () => handled);
|
|
2641
|
+
}
|
|
2642
|
+
async handlePreparedPlainPutCommitRemoved(removedEntries, modified, documentsChanged) {
|
|
2643
|
+
const handledRemovedHeads = await this.collectRemovedDocumentChangesFromIndexedHeads(removedEntries, modified, documentsChanged);
|
|
2644
|
+
for (const removed of removedEntries) {
|
|
2645
|
+
if (handledRemovedHeads.has(removed.hash)) {
|
|
2646
|
+
continue;
|
|
2647
|
+
}
|
|
2648
|
+
if (!(removed instanceof Entry) &&
|
|
2649
|
+
(await this.collectRemovedDocumentChangeFromIndexedHead(removed.hash, modified, documentsChanged))) {
|
|
2650
|
+
continue;
|
|
2651
|
+
}
|
|
2652
|
+
const entry = removed instanceof Entry
|
|
2653
|
+
? removed
|
|
2654
|
+
: await this.log.log.entryIndex.get(removed.hash, {
|
|
2655
|
+
type: "full",
|
|
2656
|
+
ignoreMissing: true,
|
|
2657
|
+
});
|
|
2658
|
+
if (!entry) {
|
|
2659
|
+
continue;
|
|
2660
|
+
}
|
|
2661
|
+
try {
|
|
2662
|
+
const payload = await this.getAppendOperation(entry);
|
|
2663
|
+
if (!payload) {
|
|
2664
|
+
continue;
|
|
2665
|
+
}
|
|
2666
|
+
await this.collectRemovedDocumentChange(payload, modified, documentsChanged);
|
|
2667
|
+
}
|
|
2668
|
+
catch (error) {
|
|
2669
|
+
if (error instanceof AccessError) {
|
|
2670
|
+
continue;
|
|
2671
|
+
}
|
|
2672
|
+
throw error;
|
|
2673
|
+
}
|
|
2674
|
+
}
|
|
2675
|
+
if (documentsChanged) {
|
|
2676
|
+
this.dispatchDocumentChangeIfObserved(documentsChanged);
|
|
2677
|
+
}
|
|
2678
|
+
}
|
|
2679
|
+
async handlePreparedPlainPutCommitRemovedHashes(removedHashes, modified, documentsChanged) {
|
|
2680
|
+
const handledRemovedHeads = await this.collectRemovedDocumentChangesFromIndexedHeadHashes(removedHashes, modified, documentsChanged);
|
|
2681
|
+
for (const hash of removedHashes) {
|
|
2682
|
+
if (handledRemovedHeads.has(hash)) {
|
|
2683
|
+
continue;
|
|
2684
|
+
}
|
|
2685
|
+
const entry = await this.log.log.entryIndex.get(hash, {
|
|
2686
|
+
type: "full",
|
|
2687
|
+
ignoreMissing: true,
|
|
2688
|
+
});
|
|
2689
|
+
if (!entry) {
|
|
2690
|
+
continue;
|
|
2691
|
+
}
|
|
2692
|
+
try {
|
|
2693
|
+
const payload = await this.getAppendOperation(entry);
|
|
2694
|
+
if (!payload) {
|
|
2695
|
+
continue;
|
|
2696
|
+
}
|
|
2697
|
+
await this.collectRemovedDocumentChange(payload, modified, documentsChanged);
|
|
2698
|
+
}
|
|
2699
|
+
catch (error) {
|
|
2700
|
+
if (error instanceof AccessError) {
|
|
2701
|
+
continue;
|
|
2702
|
+
}
|
|
2703
|
+
throw error;
|
|
2704
|
+
}
|
|
2705
|
+
}
|
|
2706
|
+
if (documentsChanged) {
|
|
2707
|
+
this.dispatchDocumentChangeIfObserved(documentsChanged);
|
|
2708
|
+
}
|
|
2709
|
+
}
|
|
2710
|
+
async handlePreparedPlainPutManyCommit(commit) {
|
|
2711
|
+
if (!this.hasDocumentChangeConsumers() &&
|
|
2712
|
+
commit.removed.length === 0) {
|
|
2713
|
+
const stored = await asTrustedDocumentIndex(this._index)._putManyPreparedNativeBackboneDocumentIndexStored(commit.commits.map((put) => {
|
|
2714
|
+
const existing = put.unique || put.existing === null ? null : put.existing;
|
|
2715
|
+
return {
|
|
2716
|
+
value: put.document,
|
|
2717
|
+
id: put.key,
|
|
2718
|
+
context: put.context,
|
|
2719
|
+
encodedValueParts: put.contextualEncodedValueParts,
|
|
2720
|
+
nativeDocumentIndex: put.nativeBackboneDocumentIndex,
|
|
2721
|
+
options: {
|
|
2722
|
+
replace: existing != null,
|
|
2723
|
+
},
|
|
2724
|
+
};
|
|
2725
|
+
}));
|
|
2726
|
+
if (stored === true) {
|
|
2727
|
+
return;
|
|
2728
|
+
}
|
|
2729
|
+
}
|
|
2730
|
+
const documentsChanged = {
|
|
2731
|
+
added: [],
|
|
2732
|
+
removed: [],
|
|
2733
|
+
};
|
|
2734
|
+
const modified = new Set();
|
|
2735
|
+
const putsToIndex = [];
|
|
2736
|
+
for (const put of commit.commits) {
|
|
2737
|
+
if (modified.has(put.key.primitive)) {
|
|
2738
|
+
continue;
|
|
2739
|
+
}
|
|
2740
|
+
const existing = put.unique || put.existing === null ? null : put.existing;
|
|
2741
|
+
putsToIndex.push({
|
|
2742
|
+
document: put.document,
|
|
2743
|
+
encodedDocument: put.encodedDocument,
|
|
2744
|
+
key: put.key,
|
|
2745
|
+
context: put.context,
|
|
2746
|
+
contextualEncodedValueParts: put.contextualEncodedValueParts,
|
|
2747
|
+
nativeBackboneDocumentIndex: put.nativeBackboneDocumentIndex,
|
|
2748
|
+
replace: existing != null,
|
|
2749
|
+
});
|
|
2750
|
+
modified.add(put.key.primitive);
|
|
2751
|
+
}
|
|
2752
|
+
let indexedDocuments = await this._index._putManyIdentityWithContext(putsToIndex.map((put) => ({
|
|
2753
|
+
value: put.document,
|
|
2754
|
+
id: put.key,
|
|
2755
|
+
context: put.context,
|
|
2756
|
+
options: {
|
|
2757
|
+
replace: put.replace,
|
|
2758
|
+
encodedValueParts: put.contextualEncodedValueParts,
|
|
2759
|
+
},
|
|
2760
|
+
})));
|
|
2761
|
+
indexedDocuments ??=
|
|
2762
|
+
await asTrustedDocumentIndex(this._index)._putManyPreparedNativeBackboneDocumentIndexWithContext(putsToIndex.map((put) => ({
|
|
2763
|
+
value: put.document,
|
|
2764
|
+
id: put.key,
|
|
2765
|
+
context: put.context,
|
|
2766
|
+
nativeDocumentIndex: put.nativeBackboneDocumentIndex,
|
|
2767
|
+
options: {
|
|
2768
|
+
replace: put.replace,
|
|
2769
|
+
},
|
|
2770
|
+
})));
|
|
2771
|
+
if (indexedDocuments) {
|
|
2772
|
+
documentsChanged.added.push(...indexedDocuments);
|
|
2773
|
+
}
|
|
2774
|
+
else {
|
|
2775
|
+
if (this.isNativeMode()) {
|
|
2776
|
+
throw this.nativeModeError("requires native batch document-index commit");
|
|
2777
|
+
}
|
|
2778
|
+
const indexed = await this._index.putManyWithContext(putsToIndex.map((put) => ({
|
|
2779
|
+
value: put.document,
|
|
2780
|
+
id: put.key,
|
|
2781
|
+
context: put.context,
|
|
2782
|
+
options: {
|
|
2783
|
+
replace: put.replace,
|
|
2784
|
+
encodedValueParts: put.contextualEncodedValueParts,
|
|
2785
|
+
},
|
|
2786
|
+
})));
|
|
2787
|
+
for (let i = 0; i < putsToIndex.length; i++) {
|
|
2788
|
+
const put = putsToIndex[i];
|
|
2789
|
+
const { indexable } = indexed[i];
|
|
2790
|
+
documentsChanged.added.push(coerceWithIndexed(coerceWithContext(put.document, put.context), indexable));
|
|
2791
|
+
}
|
|
2792
|
+
}
|
|
2793
|
+
const handledRemovedHeads = await this.collectRemovedDocumentChangesFromIndexedHeads(commit.removed, modified, documentsChanged);
|
|
2794
|
+
for (const removed of commit.removed) {
|
|
2795
|
+
if (handledRemovedHeads.has(removed.hash)) {
|
|
2796
|
+
continue;
|
|
2797
|
+
}
|
|
2798
|
+
if (!(removed instanceof Entry) &&
|
|
2799
|
+
(await this.collectRemovedDocumentChangeFromIndexedHead(removed.hash, modified, documentsChanged))) {
|
|
2800
|
+
continue;
|
|
2801
|
+
}
|
|
2802
|
+
const entry = removed instanceof Entry
|
|
2803
|
+
? removed
|
|
2804
|
+
: await this.log.log.entryIndex.get(removed.hash, {
|
|
2805
|
+
type: "full",
|
|
2806
|
+
ignoreMissing: true,
|
|
2807
|
+
});
|
|
2808
|
+
if (!entry) {
|
|
2809
|
+
continue;
|
|
2810
|
+
}
|
|
2811
|
+
try {
|
|
2812
|
+
const payload = await this.getAppendOperation(entry);
|
|
2813
|
+
if (!payload) {
|
|
2814
|
+
continue;
|
|
2815
|
+
}
|
|
2816
|
+
await this.collectRemovedDocumentChange(payload, modified, documentsChanged);
|
|
2817
|
+
}
|
|
2818
|
+
catch (error) {
|
|
2819
|
+
if (error instanceof AccessError) {
|
|
2820
|
+
continue;
|
|
2821
|
+
}
|
|
2822
|
+
throw error;
|
|
289
2823
|
}
|
|
290
|
-
throw error;
|
|
291
2824
|
}
|
|
292
|
-
|
|
2825
|
+
this.events.dispatchEvent(new CustomEvent("change", { detail: documentsChanged }));
|
|
293
2826
|
}
|
|
294
|
-
async
|
|
295
|
-
const
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
: history;
|
|
300
|
-
};
|
|
301
|
-
const pointsToHistory = async (history) => {
|
|
302
|
-
// make sure nexts only points to this document at some point in history
|
|
303
|
-
let current = await resolve(history);
|
|
304
|
-
const next = entry.meta.next[0];
|
|
305
|
-
while (current?.hash &&
|
|
306
|
-
next !== current?.hash &&
|
|
307
|
-
current.meta.next.length > 0) {
|
|
308
|
-
current = await this.log.log.get(current.meta.next[0]);
|
|
309
|
-
}
|
|
310
|
-
if (current?.hash === next) {
|
|
311
|
-
return true; // Ok, we are pointing this new edit to some exising point in time of the old document
|
|
2827
|
+
async collectRemovedDocumentChangeFromIndexedHead(head, modified, documentsChanged) {
|
|
2828
|
+
const key = await this._index.getIdentityIndexedKeyByHead(head);
|
|
2829
|
+
if (key) {
|
|
2830
|
+
if (await this.collectRemovedDocumentChangeFromIndexedKey(key, modified, documentsChanged)) {
|
|
2831
|
+
return true;
|
|
312
2832
|
}
|
|
2833
|
+
}
|
|
2834
|
+
if (!documentsChanged) {
|
|
313
2835
|
return false;
|
|
314
|
-
}
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
const key = indexerTypes.toId(keyValue);
|
|
331
|
-
const existingDocument = this.immutable
|
|
332
|
-
? (await this.index.getDetailed(key, {
|
|
333
|
-
resolve: false,
|
|
2836
|
+
}
|
|
2837
|
+
const indexed = await this._index.getIdentityIndexedByHead(head);
|
|
2838
|
+
if (!indexed) {
|
|
2839
|
+
return false;
|
|
2840
|
+
}
|
|
2841
|
+
return this.collectRemovedDocumentChangeFromIndexedKey(indexed.id, modified, documentsChanged, () => coerceWithIndexed(indexed.value, indexed.value));
|
|
2842
|
+
}
|
|
2843
|
+
async collectRemovedDocumentChangeFromIndexedKey(key, modified, documentsChanged, valueProvider) {
|
|
2844
|
+
if (modified.has(key.primitive)) {
|
|
2845
|
+
return true;
|
|
2846
|
+
}
|
|
2847
|
+
let value;
|
|
2848
|
+
if (documentsChanged) {
|
|
2849
|
+
value =
|
|
2850
|
+
valueProvider?.() ??
|
|
2851
|
+
(await this._index.get(key, {
|
|
334
2852
|
local: true,
|
|
335
|
-
remote:
|
|
336
|
-
}))
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
return referenceHistoryCorrectly ? putOperation : false;
|
|
366
|
-
}
|
|
367
|
-
else {
|
|
368
|
-
return putOperation;
|
|
369
|
-
}
|
|
370
|
-
}
|
|
2853
|
+
remote: false,
|
|
2854
|
+
}));
|
|
2855
|
+
if (!value) {
|
|
2856
|
+
return false;
|
|
2857
|
+
}
|
|
2858
|
+
documentsChanged.removed.push(value);
|
|
2859
|
+
}
|
|
2860
|
+
if (value instanceof Program &&
|
|
2861
|
+
value.closed !== true &&
|
|
2862
|
+
value.parents.includes(this)) {
|
|
2863
|
+
await value.drop(this);
|
|
2864
|
+
}
|
|
2865
|
+
await this._index.delMany([key]);
|
|
2866
|
+
modified.add(key.primitive);
|
|
2867
|
+
return true;
|
|
2868
|
+
}
|
|
2869
|
+
async collectRemovedDocumentChangesFromIndexedHeads(removed, modified, documentsChanged) {
|
|
2870
|
+
return this.collectRemovedDocumentChangesFromIndexedHeadHashes(removed.map((entry) => entry.hash), modified, documentsChanged);
|
|
2871
|
+
}
|
|
2872
|
+
async collectRemovedDocumentChangesFromIndexedHeadHashes(removedHashes, modified, documentsChanged) {
|
|
2873
|
+
if (removedHashes.length === 0) {
|
|
2874
|
+
return new Set();
|
|
2875
|
+
}
|
|
2876
|
+
if (!documentsChanged) {
|
|
2877
|
+
const handled = new Set();
|
|
2878
|
+
const deleteKeys = [];
|
|
2879
|
+
for (const hash of removedHashes) {
|
|
2880
|
+
const key = await this._index.getIdentityIndexedKeyByHead(hash);
|
|
2881
|
+
if (!key) {
|
|
2882
|
+
continue;
|
|
371
2883
|
}
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
// for now the default behaviour will allow us to build document dependencies
|
|
2884
|
+
handled.add(hash);
|
|
2885
|
+
if (modified.has(key.primitive)) {
|
|
2886
|
+
continue;
|
|
376
2887
|
}
|
|
2888
|
+
deleteKeys.push(key);
|
|
2889
|
+
modified.add(key.primitive);
|
|
377
2890
|
}
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
? operation.key
|
|
390
|
-
: indexerTypes.toId(operation.key));
|
|
391
|
-
const existingHead = this.getExistingContext(existingDocument)?.head;
|
|
392
|
-
if (!existingHead) {
|
|
393
|
-
// already deleted
|
|
394
|
-
return coerceDeleteOperation(operation); // assume ok
|
|
2891
|
+
await this._index.delMany(deleteKeys);
|
|
2892
|
+
return handled;
|
|
2893
|
+
}
|
|
2894
|
+
const keyByHead = this._index.getIndexedKeysByHeads(removedHashes);
|
|
2895
|
+
if (keyByHead) {
|
|
2896
|
+
const handled = new Set();
|
|
2897
|
+
const deleteKeys = [];
|
|
2898
|
+
for (let i = 0; i < removedHashes.length; i++) {
|
|
2899
|
+
const key = keyByHead[i];
|
|
2900
|
+
if (!key) {
|
|
2901
|
+
continue;
|
|
395
2902
|
}
|
|
396
|
-
|
|
397
|
-
if (
|
|
398
|
-
|
|
399
|
-
return false;
|
|
2903
|
+
handled.add(removedHashes[i]);
|
|
2904
|
+
if (modified.has(key.primitive)) {
|
|
2905
|
+
continue;
|
|
400
2906
|
}
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
2907
|
+
const value = await this._index.get(key, {
|
|
2908
|
+
local: true,
|
|
2909
|
+
remote: false,
|
|
2910
|
+
});
|
|
2911
|
+
if (!value) {
|
|
2912
|
+
handled.delete(removedHashes[i]);
|
|
2913
|
+
continue;
|
|
404
2914
|
}
|
|
405
|
-
|
|
2915
|
+
documentsChanged.removed.push(value);
|
|
2916
|
+
deleteKeys.push(key);
|
|
2917
|
+
modified.add(key.primitive);
|
|
406
2918
|
}
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
}
|
|
410
|
-
return operation;
|
|
2919
|
+
await this._index.delMany(deleteKeys);
|
|
2920
|
+
return handled;
|
|
411
2921
|
}
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
2922
|
+
const indexedByHead = await this._index.getIdentityIndexedByHeads(removedHashes);
|
|
2923
|
+
if (!indexedByHead) {
|
|
2924
|
+
return new Set();
|
|
2925
|
+
}
|
|
2926
|
+
const handled = new Set();
|
|
2927
|
+
const deleteKeys = [];
|
|
2928
|
+
for (let i = 0; i < removedHashes.length; i++) {
|
|
2929
|
+
const indexed = indexedByHead[i];
|
|
2930
|
+
if (!indexed) {
|
|
2931
|
+
continue;
|
|
415
2932
|
}
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
2933
|
+
const key = indexed.id;
|
|
2934
|
+
handled.add(removedHashes[i]);
|
|
2935
|
+
if (modified.has(key.primitive)) {
|
|
2936
|
+
continue;
|
|
419
2937
|
}
|
|
420
|
-
|
|
2938
|
+
if (documentsChanged) {
|
|
2939
|
+
const value = coerceWithIndexed(indexed.value, indexed.value);
|
|
2940
|
+
documentsChanged.removed.push(value);
|
|
2941
|
+
}
|
|
2942
|
+
deleteKeys.push(key);
|
|
2943
|
+
modified.add(key.primitive);
|
|
421
2944
|
}
|
|
2945
|
+
await this._index.delMany(deleteKeys);
|
|
2946
|
+
return handled;
|
|
422
2947
|
}
|
|
423
|
-
async
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
2948
|
+
async collectRemovedDocumentChange(payload, modified, documentsChanged) {
|
|
2949
|
+
let value;
|
|
2950
|
+
let key;
|
|
2951
|
+
if (isPutOperation(payload)) {
|
|
2952
|
+
const keyValue = this.isNativeMode()
|
|
2953
|
+
? await this.getNativeDocumentIdFromPutOperation(payload)
|
|
2954
|
+
: undefined;
|
|
2955
|
+
if (this.isNativeMode() && keyValue == null) {
|
|
2956
|
+
throw this.nativeModeError("requires native document id extraction for removed put");
|
|
2957
|
+
}
|
|
2958
|
+
key = indexerTypes.toId(keyValue ??
|
|
2959
|
+
this.idResolver(this.index.valueEncoding.decoder(payload.data)));
|
|
2960
|
+
if (modified.has(key.primitive)) {
|
|
2961
|
+
return;
|
|
2962
|
+
}
|
|
2963
|
+
if (documentsChanged) {
|
|
2964
|
+
const document = await this._index.get(key, {
|
|
436
2965
|
local: true,
|
|
437
|
-
remote:
|
|
438
|
-
}))?.[0]?.results[0]?.context.head
|
|
439
|
-
: (await this.getLocalIndexedContext(indexerTypes.toId(keyValue)))
|
|
440
|
-
?.value.__context.head;
|
|
441
|
-
let operation;
|
|
442
|
-
if (this.compatibility === 6) {
|
|
443
|
-
if (typeof keyValue === "string") {
|
|
444
|
-
operation = new PutWithKeyOperation({
|
|
445
|
-
key: keyValue,
|
|
446
|
-
data: ser,
|
|
2966
|
+
remote: false,
|
|
447
2967
|
});
|
|
2968
|
+
if (!document) {
|
|
2969
|
+
return;
|
|
2970
|
+
}
|
|
2971
|
+
value = document;
|
|
448
2972
|
}
|
|
449
|
-
|
|
450
|
-
|
|
2973
|
+
}
|
|
2974
|
+
else if (isDeleteOperation(payload)) {
|
|
2975
|
+
key = coerceDeleteOperation(payload).key;
|
|
2976
|
+
if (modified.has(key.primitive)) {
|
|
2977
|
+
return;
|
|
2978
|
+
}
|
|
2979
|
+
if (documentsChanged) {
|
|
2980
|
+
const document = await this._index.get(key, {
|
|
2981
|
+
local: true,
|
|
2982
|
+
remote: false,
|
|
2983
|
+
});
|
|
2984
|
+
if (!document) {
|
|
2985
|
+
return;
|
|
2986
|
+
}
|
|
2987
|
+
value = document;
|
|
451
2988
|
}
|
|
452
2989
|
}
|
|
453
2990
|
else {
|
|
454
|
-
|
|
455
|
-
data: ser,
|
|
456
|
-
});
|
|
2991
|
+
throw new Error("Unexpected");
|
|
457
2992
|
}
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
2993
|
+
if (documentsChanged && value) {
|
|
2994
|
+
documentsChanged.removed.push(value);
|
|
2995
|
+
}
|
|
2996
|
+
if (value instanceof Program &&
|
|
2997
|
+
value.closed !== true &&
|
|
2998
|
+
value.parents.includes(this)) {
|
|
2999
|
+
await value.drop(this);
|
|
3000
|
+
}
|
|
3001
|
+
await this._index.delMany([key]);
|
|
3002
|
+
modified.add(key.primitive);
|
|
3003
|
+
}
|
|
3004
|
+
async collectRemovedPutChangeFromNativeId(payload, modified) {
|
|
3005
|
+
if (!this.isNativeMode() || !isPutOperation(payload)) {
|
|
3006
|
+
return false;
|
|
3007
|
+
}
|
|
3008
|
+
const keyValue = await this.getNativeDocumentIdFromPutOperation(payload);
|
|
3009
|
+
if (keyValue == null) {
|
|
3010
|
+
throw this.nativeModeError("requires native document id extraction for removed put");
|
|
3011
|
+
}
|
|
3012
|
+
const key = indexerTypes.toId(keyValue);
|
|
3013
|
+
if (modified.has(key.primitive)) {
|
|
3014
|
+
return true;
|
|
3015
|
+
}
|
|
3016
|
+
await this._index.delMany([key]);
|
|
3017
|
+
modified.add(key.primitive);
|
|
3018
|
+
return true;
|
|
3019
|
+
}
|
|
3020
|
+
putStrictNativeReceivedDocumentIndexWithContext(value, key, entry, payload, existing) {
|
|
3021
|
+
if (!this.isNativeMode() || !this._nativeBackboneDocumentIndexEnabled) {
|
|
3022
|
+
return;
|
|
3023
|
+
}
|
|
3024
|
+
if (value instanceof Program) {
|
|
3025
|
+
return;
|
|
3026
|
+
}
|
|
3027
|
+
const existingContext = this.getExistingContext(existing);
|
|
3028
|
+
const modified = entry.meta.clock.timestamp.wallTime;
|
|
3029
|
+
const context = new Context({
|
|
3030
|
+
created: existingContext?.created || modified,
|
|
3031
|
+
modified,
|
|
3032
|
+
head: entry.hash,
|
|
3033
|
+
gid: entry.meta.gid,
|
|
3034
|
+
size: encodePutOperationPayload(payload.data).byteLength,
|
|
3035
|
+
});
|
|
3036
|
+
const nativeDocumentIndex = asTrustedDocumentIndex(this._index).prepareNativeBackboneDocumentIndexCommitWithAppendFacts(value, payload.data, context, { entryPublicKeys: entry.publicKeys });
|
|
3037
|
+
if (!nativeDocumentIndex) {
|
|
3038
|
+
return;
|
|
3039
|
+
}
|
|
3040
|
+
return asTrustedDocumentIndex(this._index)._putPreparedNativeBackboneDocumentIndexWithContext(value, key, context, nativeDocumentIndex, {
|
|
3041
|
+
replace: existing != null,
|
|
3042
|
+
});
|
|
3043
|
+
}
|
|
3044
|
+
putStrictNativeReceivedDocumentIndexStoredWithContext(key, entry, payload, existing) {
|
|
3045
|
+
if (!this.isNativeMode() || !this._nativeBackboneDocumentIndexEnabled) {
|
|
3046
|
+
return;
|
|
3047
|
+
}
|
|
3048
|
+
const existingContext = this.getExistingContext(existing);
|
|
3049
|
+
const modified = entry.meta.clock.timestamp.wallTime;
|
|
3050
|
+
const context = new Context({
|
|
3051
|
+
created: existingContext?.created || modified,
|
|
3052
|
+
modified,
|
|
3053
|
+
head: entry.hash,
|
|
3054
|
+
gid: entry.meta.gid,
|
|
3055
|
+
size: encodePutOperationPayload(payload.data).byteLength,
|
|
3056
|
+
});
|
|
3057
|
+
const nativeDocumentIndex = asTrustedDocumentIndex(this._index).prepareNativeBackboneDocumentIndexStoredCommitWithAppendFacts(payload.data, context, { entryPublicKeys: entry.publicKeys });
|
|
3058
|
+
if (!nativeDocumentIndex) {
|
|
3059
|
+
return;
|
|
3060
|
+
}
|
|
3061
|
+
return asTrustedDocumentIndex(this._index)._putPreparedNativeBackboneDocumentIndexStoredWithContext(key, context, nativeDocumentIndex, {
|
|
3062
|
+
replace: existing != null,
|
|
471
3063
|
});
|
|
472
|
-
this.keepCache?.add(appended.entry.hash);
|
|
473
|
-
return appended;
|
|
474
3064
|
}
|
|
475
3065
|
async get(id, options) {
|
|
476
3066
|
const resolved = await this.index.get(id, {
|
|
@@ -480,6 +3070,9 @@ let Documents = (() => {
|
|
|
480
3070
|
return resolved ? resolved : undefined;
|
|
481
3071
|
}
|
|
482
3072
|
async del(id, options) {
|
|
3073
|
+
return this._documentBackend.del(id, options);
|
|
3074
|
+
}
|
|
3075
|
+
async delCompatDocumentBackend(id, options) {
|
|
483
3076
|
const key = id instanceof indexerTypes.IdKey ? id : indexerTypes.toId(id);
|
|
484
3077
|
const existing = (await this._index.getDetailed(key, {
|
|
485
3078
|
resolve: false,
|
|
@@ -504,6 +3097,100 @@ let Documents = (() => {
|
|
|
504
3097
|
},
|
|
505
3098
|
});
|
|
506
3099
|
}
|
|
3100
|
+
async delNativeDocumentBackend(id, options) {
|
|
3101
|
+
const deleteOptions = this.normalizeNativeModePutOptions(options);
|
|
3102
|
+
this.assertNativeModeDeleteSupported(deleteOptions);
|
|
3103
|
+
const key = id instanceof indexerTypes.IdKey ? id : indexerTypes.toId(id);
|
|
3104
|
+
if (!this.hasNativeDocumentContextLookup()) {
|
|
3105
|
+
throw this.nativeModeError("requires native document context lookup");
|
|
3106
|
+
}
|
|
3107
|
+
const existing = this.getNativeIndexedContext(key);
|
|
3108
|
+
const existingContext = this.getExistingContext(existing);
|
|
3109
|
+
if (!existingContext?.head) {
|
|
3110
|
+
throw new NotFoundError(`No entry with key '${key.primitive}' in the database`);
|
|
3111
|
+
}
|
|
3112
|
+
let previousEntry;
|
|
3113
|
+
let existingDocumentChecked = false;
|
|
3114
|
+
let existingDocument;
|
|
3115
|
+
const getPreviousEntry = async () => {
|
|
3116
|
+
if (previousEntry) {
|
|
3117
|
+
return previousEntry;
|
|
3118
|
+
}
|
|
3119
|
+
previousEntry = await this._resolveEntry(existingContext.head, {
|
|
3120
|
+
remote: true,
|
|
3121
|
+
});
|
|
3122
|
+
if (!previousEntry) {
|
|
3123
|
+
throw new NotFoundError(`No entry with key '${key.primitive}' in the database`);
|
|
3124
|
+
}
|
|
3125
|
+
return previousEntry;
|
|
3126
|
+
};
|
|
3127
|
+
const getExistingDocument = async () => {
|
|
3128
|
+
if (!existingDocumentChecked) {
|
|
3129
|
+
existingDocumentChecked = true;
|
|
3130
|
+
existingDocument = await this.getLocalIdentityDocumentByHead(existingContext.head);
|
|
3131
|
+
existingDocument ??=
|
|
3132
|
+
await this.getLocalIndexedDocumentForNativeDeletePolicy(key);
|
|
3133
|
+
}
|
|
3134
|
+
return existingDocument;
|
|
3135
|
+
};
|
|
3136
|
+
const operation = new DeleteOperation({ key });
|
|
3137
|
+
if (!(await this.canPerformAllowsNativeDelete({
|
|
3138
|
+
operation,
|
|
3139
|
+
getExistingEntry: getPreviousEntry,
|
|
3140
|
+
getExistingDocument,
|
|
3141
|
+
}))) {
|
|
3142
|
+
throw this.nativeModeError("canPerform policy rejected this delete");
|
|
3143
|
+
}
|
|
3144
|
+
const operationPayloadBytes = BORSH_ENCODING_OPERATION.encoder(operation);
|
|
3145
|
+
const documentsChanged = this.hasDocumentChangeConsumers()
|
|
3146
|
+
? {
|
|
3147
|
+
added: [],
|
|
3148
|
+
removed: [],
|
|
3149
|
+
}
|
|
3150
|
+
: undefined;
|
|
3151
|
+
const removedDocument = documentsChanged
|
|
3152
|
+
? await this._index.get(key, {
|
|
3153
|
+
local: true,
|
|
3154
|
+
remote: false,
|
|
3155
|
+
})
|
|
3156
|
+
: undefined;
|
|
3157
|
+
this.keepCache?.delete(existingContext.head);
|
|
3158
|
+
const previousForAppend = this.nextFromIndexedContext(existingContext.head, existing) ??
|
|
3159
|
+
(await getPreviousEntry());
|
|
3160
|
+
const trustedLog = asTrustedDocumentSharedLog(this.log);
|
|
3161
|
+
const appended = await trustedLog.appendStrictNativeDocumentPayloadCommitOnly(operationPayloadBytes, {
|
|
3162
|
+
...deleteOptions,
|
|
3163
|
+
meta: {
|
|
3164
|
+
next: [previousForAppend],
|
|
3165
|
+
type: EntryType.CUT,
|
|
3166
|
+
...deleteOptions?.meta,
|
|
3167
|
+
},
|
|
3168
|
+
}, {
|
|
3169
|
+
skipMissingNextJoin: true,
|
|
3170
|
+
resolveTrimmedEntries: false,
|
|
3171
|
+
nativeBackboneDocumentDeleteKey: documentIndexStoreKey(key),
|
|
3172
|
+
});
|
|
3173
|
+
if (!appended) {
|
|
3174
|
+
throw this.nativeModeError("requires native delete append support");
|
|
3175
|
+
}
|
|
3176
|
+
const result = {
|
|
3177
|
+
get entry() {
|
|
3178
|
+
return appended.entry;
|
|
3179
|
+
},
|
|
3180
|
+
removed: appended.removed,
|
|
3181
|
+
};
|
|
3182
|
+
if (appended.appendCommit.nativeBackboneDocumentDeleteCommitted) {
|
|
3183
|
+
this._index.clearResolvedCacheForKeys([key]);
|
|
3184
|
+
}
|
|
3185
|
+
else {
|
|
3186
|
+
await this._index.delManyMaybe([key]);
|
|
3187
|
+
}
|
|
3188
|
+
if (documentsChanged && removedDocument) {
|
|
3189
|
+
documentsChanged.removed.push(removedDocument);
|
|
3190
|
+
this.dispatchDocumentChangeIfObserved(documentsChanged);
|
|
3191
|
+
}
|
|
3192
|
+
return result;
|
|
3193
|
+
}
|
|
507
3194
|
async handleChanges(change, reference) {
|
|
508
3195
|
logger.trace("handleChanges called", change);
|
|
509
3196
|
const isAppendOperation = change?.added.length === 1 ? !!change.added[0] : false;
|
|
@@ -511,45 +3198,90 @@ let Documents = (() => {
|
|
|
511
3198
|
for (const r of change.removed) {
|
|
512
3199
|
removedSet.set(r.hash, r);
|
|
513
3200
|
}
|
|
3201
|
+
const shouldPrepareDocumentChanges = this.hasDocumentChangeConsumers();
|
|
3202
|
+
const canRemoveByIndexedHead = !shouldPrepareDocumentChanges && this._index.canGetIndexedKeyByHead();
|
|
3203
|
+
const removedEntries = canRemoveByIndexedHead
|
|
3204
|
+
? []
|
|
3205
|
+
: ((await Promise.all(change.removed.map((x) => x instanceof Entry ? x : this.log.log.entryIndex.get(x.hash)))) ?? []);
|
|
514
3206
|
const sortedEntries = [
|
|
515
3207
|
...change.added.map((x) => x.entry),
|
|
516
|
-
...
|
|
3208
|
+
...removedEntries,
|
|
517
3209
|
]; // TODO assert sorting
|
|
518
3210
|
/* const sortedEntries = [...change.added, ...(removed || [])]
|
|
519
3211
|
.sort(this.log.log.sortFn)
|
|
520
3212
|
.reverse(); // sort so we get newest to oldest */
|
|
521
3213
|
// There might be a case where change.added and change.removed contains the same document id. Usaully because you use the "trim" option
|
|
522
3214
|
// in combinatpion with inserting the same document. To mitigate this, we loop through the changes and modify the behaviour for this
|
|
523
|
-
let documentsChanged =
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
3215
|
+
let documentsChanged = shouldPrepareDocumentChanges
|
|
3216
|
+
? {
|
|
3217
|
+
added: [],
|
|
3218
|
+
removed: [],
|
|
3219
|
+
}
|
|
3220
|
+
: undefined;
|
|
527
3221
|
let modified = new Set();
|
|
528
3222
|
for (const item of sortedEntries) {
|
|
529
3223
|
if (!item) {
|
|
530
3224
|
continue;
|
|
531
3225
|
}
|
|
532
3226
|
try {
|
|
533
|
-
const
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
3227
|
+
const isReferencedAppendEntry = isAppendOperation &&
|
|
3228
|
+
reference?.operation &&
|
|
3229
|
+
change.added[0]?.entry.hash === item.hash;
|
|
3230
|
+
const payload = isReferencedAppendEntry
|
|
3231
|
+
? reference.operation
|
|
3232
|
+
: await this.getAppendOperation(item);
|
|
3233
|
+
if (!payload) {
|
|
3234
|
+
continue;
|
|
3235
|
+
}
|
|
537
3236
|
if (isPutOperation(payload) && !removedSet.has(item.hash)) {
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
3237
|
+
if (!documentsChanged && this.isNativeMode()) {
|
|
3238
|
+
const keyValue = await this.getNativeDocumentIdFromPutOperation(payload);
|
|
3239
|
+
if (keyValue != null) {
|
|
3240
|
+
const key = indexerTypes.toId(keyValue);
|
|
3241
|
+
if (modified.has(key.primitive)) {
|
|
3242
|
+
continue;
|
|
3243
|
+
}
|
|
3244
|
+
const existing = reference?.unique || reference?.existing === null
|
|
3245
|
+
? null
|
|
3246
|
+
: isReferencedAppendEntry &&
|
|
3247
|
+
reference?.existing !== undefined
|
|
3248
|
+
? reference.existing
|
|
3249
|
+
: this.getNativeModeIndexedContext(key) || null;
|
|
3250
|
+
if (!this.strictHistory && existing) {
|
|
3251
|
+
const shouldIgnoreChange = this.immutable
|
|
3252
|
+
? existing.value.__context.modified <
|
|
3253
|
+
item.meta.clock.timestamp.wallTime
|
|
3254
|
+
: existing.value.__context.modified >
|
|
3255
|
+
item.meta.clock.timestamp.wallTime;
|
|
3256
|
+
if (shouldIgnoreChange) {
|
|
3257
|
+
continue;
|
|
3258
|
+
}
|
|
3259
|
+
}
|
|
3260
|
+
const stored = await this.putStrictNativeReceivedDocumentIndexStoredWithContext(key, item, payload, existing);
|
|
3261
|
+
if (stored) {
|
|
3262
|
+
modified.add(key.primitive);
|
|
3263
|
+
continue;
|
|
3264
|
+
}
|
|
3265
|
+
}
|
|
3266
|
+
}
|
|
3267
|
+
let value = (isReferencedAppendEntry && reference?.document) ||
|
|
541
3268
|
this.index.valueEncoding.decoder(payload.data);
|
|
542
3269
|
// get index key from value
|
|
543
|
-
const
|
|
544
|
-
|
|
3270
|
+
const key = isReferencedAppendEntry && reference?.key
|
|
3271
|
+
? reference.key
|
|
3272
|
+
: indexerTypes.toId(this.idResolver(value));
|
|
545
3273
|
// document is already updated with more recent entry
|
|
546
3274
|
if (modified.has(key.primitive)) {
|
|
547
3275
|
continue;
|
|
548
3276
|
}
|
|
549
3277
|
// if no casual ordering is used, use timestamps to order docs
|
|
550
|
-
let existing = reference?.unique
|
|
3278
|
+
let existing = reference?.unique || reference?.existing === null
|
|
551
3279
|
? null
|
|
552
|
-
:
|
|
3280
|
+
: isReferencedAppendEntry && reference?.existing !== undefined
|
|
3281
|
+
? reference.existing
|
|
3282
|
+
: this.isNativeMode()
|
|
3283
|
+
? this.getNativeModeIndexedContext(key) || null
|
|
3284
|
+
: (await this.getLocalIndexedContext(key)) || null;
|
|
553
3285
|
if (!this.strictHistory && existing) {
|
|
554
3286
|
// if immutable use oldest, else use newest
|
|
555
3287
|
let shouldIgnoreChange = this.immutable
|
|
@@ -566,59 +3298,26 @@ let Documents = (() => {
|
|
|
566
3298
|
// if replicator, then open
|
|
567
3299
|
value = await this.maybeSubprogramOpen(value);
|
|
568
3300
|
}
|
|
3301
|
+
const nativeStoredIndexed = payload instanceof PutOperation
|
|
3302
|
+
? await this.putStrictNativeReceivedDocumentIndexWithContext(value, key, item, payload, existing)
|
|
3303
|
+
: undefined;
|
|
3304
|
+
if (nativeStoredIndexed) {
|
|
3305
|
+
documentsChanged?.added.push(nativeStoredIndexed);
|
|
3306
|
+
modified.add(key.primitive);
|
|
3307
|
+
continue;
|
|
3308
|
+
}
|
|
569
3309
|
const { context, indexable } = await this._index.put(value, key, item, existing);
|
|
570
|
-
documentsChanged
|
|
3310
|
+
documentsChanged?.added.push(coerceWithIndexed(coerceWithContext(value, context), indexable));
|
|
571
3311
|
modified.add(key.primitive);
|
|
572
3312
|
}
|
|
573
3313
|
else if ((isDeleteOperation(payload) && !removedSet.has(item.hash)) ||
|
|
574
3314
|
isPutOperation(payload) ||
|
|
575
3315
|
removedSet.has(item.hash)) {
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
const valueWithoutContext = this.index.valueEncoding.decoder(payload.data);
|
|
580
|
-
key = indexerTypes.toId(this.idResolver(valueWithoutContext));
|
|
581
|
-
// document is already updated with more recent entry
|
|
582
|
-
if (modified.has(key.primitive)) {
|
|
583
|
-
continue;
|
|
584
|
-
}
|
|
585
|
-
// we try to fetch it anyway, because we need the context for the events
|
|
586
|
-
const document = await this._index.get(key, {
|
|
587
|
-
local: true,
|
|
588
|
-
remote: false,
|
|
589
|
-
});
|
|
590
|
-
if (!document) {
|
|
591
|
-
continue;
|
|
592
|
-
}
|
|
593
|
-
value = document;
|
|
594
|
-
}
|
|
595
|
-
else if (isDeleteOperation(payload)) {
|
|
596
|
-
key = coerceDeleteOperation(payload).key;
|
|
597
|
-
// document is already updated with more recent entry
|
|
598
|
-
if (modified.has(key.primitive)) {
|
|
599
|
-
continue;
|
|
600
|
-
}
|
|
601
|
-
const document = await this._index.get(key, {
|
|
602
|
-
local: true,
|
|
603
|
-
remote: false,
|
|
604
|
-
});
|
|
605
|
-
if (!document) {
|
|
606
|
-
continue;
|
|
607
|
-
}
|
|
608
|
-
value = document;
|
|
609
|
-
}
|
|
610
|
-
else {
|
|
611
|
-
throw new Error("Unexpected");
|
|
612
|
-
}
|
|
613
|
-
documentsChanged.removed.push(value);
|
|
614
|
-
if (value instanceof Program &&
|
|
615
|
-
value.closed !== true &&
|
|
616
|
-
value.parents.includes(this)) {
|
|
617
|
-
await value.drop(this);
|
|
3316
|
+
if (!documentsChanged &&
|
|
3317
|
+
(await this.collectRemovedPutChangeFromNativeId(payload, modified))) {
|
|
3318
|
+
continue;
|
|
618
3319
|
}
|
|
619
|
-
|
|
620
|
-
await this._index.del(key);
|
|
621
|
-
modified.add(key.primitive);
|
|
3320
|
+
await this.collectRemovedDocumentChange(payload, modified, documentsChanged);
|
|
622
3321
|
}
|
|
623
3322
|
else {
|
|
624
3323
|
// Unknown operation
|
|
@@ -632,7 +3331,39 @@ let Documents = (() => {
|
|
|
632
3331
|
throw error;
|
|
633
3332
|
}
|
|
634
3333
|
}
|
|
635
|
-
|
|
3334
|
+
if (canRemoveByIndexedHead && change.removed.length > 0) {
|
|
3335
|
+
const handled = await this.collectRemovedDocumentChangesFromIndexedHeads(change.removed, modified);
|
|
3336
|
+
const remainingRemoved = change.removed.filter((entry) => !handled.has(entry.hash));
|
|
3337
|
+
for (const removed of remainingRemoved) {
|
|
3338
|
+
const entry = removed instanceof Entry
|
|
3339
|
+
? removed
|
|
3340
|
+
: await this.log.log.entryIndex.get(removed.hash, {
|
|
3341
|
+
type: "full",
|
|
3342
|
+
ignoreMissing: true,
|
|
3343
|
+
});
|
|
3344
|
+
if (!entry) {
|
|
3345
|
+
continue;
|
|
3346
|
+
}
|
|
3347
|
+
try {
|
|
3348
|
+
const payload = await this.getAppendOperation(entry);
|
|
3349
|
+
if (!payload) {
|
|
3350
|
+
continue;
|
|
3351
|
+
}
|
|
3352
|
+
if (!(await this.collectRemovedPutChangeFromNativeId(payload, modified))) {
|
|
3353
|
+
await this.collectRemovedDocumentChange(payload, modified);
|
|
3354
|
+
}
|
|
3355
|
+
}
|
|
3356
|
+
catch (error) {
|
|
3357
|
+
if (error instanceof AccessError) {
|
|
3358
|
+
continue;
|
|
3359
|
+
}
|
|
3360
|
+
throw error;
|
|
3361
|
+
}
|
|
3362
|
+
}
|
|
3363
|
+
}
|
|
3364
|
+
if (documentsChanged) {
|
|
3365
|
+
this.dispatchDocumentChangeIfObserved(documentsChanged);
|
|
3366
|
+
}
|
|
636
3367
|
}
|
|
637
3368
|
async count(options) {
|
|
638
3369
|
// Local/exact count
|