@peerbit/document 13.0.44 → 13.1.1
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 +3074 -342
- package/dist/src/program.js.map +1 -1
- package/dist/src/search.d.ts +80 -6
- package/dist/src/search.d.ts.map +1 -1
- package/dist/src/search.js +1155 -59
- 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 +5020 -467
- package/src/search.ts +1766 -109
- 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,849 @@ 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
|
+
immutable: this.immutable,
|
|
1169
|
+
});
|
|
1170
|
+
this._documentInternalChangeListenerCount = Math.max(0, this._documentChangeListenerCount - changeListenersBeforeIndexOpen);
|
|
1171
|
+
this._nativeDocumentFieldExtractionPlans ??= new Map();
|
|
1172
|
+
this._nativeDocumentFieldExtractionPlans.clear();
|
|
1173
|
+
this._nativeDocumentIdExtractionPlan =
|
|
1174
|
+
asTrustedDocumentIndex(this._index).getNativeDocumentFieldExtractionPlan(idProperty);
|
|
1175
|
+
// document v6 and below need log compatibility of v8 or below
|
|
1176
|
+
// document v7 needs log compatibility of v9
|
|
1177
|
+
let logCompatiblity = undefined;
|
|
1178
|
+
if (options.compatibility === 6) {
|
|
1179
|
+
logCompatiblity = 8;
|
|
1180
|
+
}
|
|
1181
|
+
else if (options.compatibility === 7) {
|
|
1182
|
+
logCompatiblity = 9;
|
|
1183
|
+
}
|
|
1184
|
+
this.domain = options.domain?.(this);
|
|
1185
|
+
let keepFunction;
|
|
1186
|
+
if (options?.keep === "self") {
|
|
1187
|
+
this.keepCache = new Set();
|
|
1188
|
+
keepFunction = async (e) => {
|
|
1189
|
+
if (this.keepCache?.has(e.hash)) {
|
|
1190
|
+
return true;
|
|
1191
|
+
}
|
|
1192
|
+
let signatures = undefined;
|
|
1193
|
+
if (e instanceof Entry) {
|
|
1194
|
+
signatures = e.signatures;
|
|
1195
|
+
}
|
|
1196
|
+
else {
|
|
1197
|
+
const entry = await this.log.log.get(e.hash);
|
|
1198
|
+
signatures = entry?.signatures;
|
|
1199
|
+
}
|
|
1200
|
+
if (!signatures) {
|
|
1201
|
+
return false;
|
|
1202
|
+
}
|
|
1203
|
+
for (const signature of signatures) {
|
|
1204
|
+
if (signature.publicKey.equals(this.node.identity.publicKey)) {
|
|
1205
|
+
this.keepCache?.add(e.hash);
|
|
1206
|
+
return true;
|
|
1207
|
+
}
|
|
1208
|
+
}
|
|
1209
|
+
return false;
|
|
1210
|
+
};
|
|
1211
|
+
}
|
|
1212
|
+
else {
|
|
1213
|
+
keepFunction = options?.keep;
|
|
1214
|
+
}
|
|
1215
|
+
await this.log.open({
|
|
1216
|
+
encoding: BORSH_ENCODING_OPERATION,
|
|
1217
|
+
canReplicate: options?.canReplicate,
|
|
1218
|
+
canAppend: this.canAppend.bind(this),
|
|
214
1219
|
onChange: this.handleChanges.bind(this),
|
|
215
1220
|
trim: options?.log?.trim,
|
|
1221
|
+
appendDurability: options?.appendDurability,
|
|
1222
|
+
nativeBackbone: options?.nativeBackbone,
|
|
1223
|
+
nativeGraph: options?.nativeGraph,
|
|
1224
|
+
nativeRangePlanner: options?.nativeRangePlanner,
|
|
216
1225
|
replicate: options?.replicate,
|
|
217
1226
|
replicas: options?.replicas,
|
|
218
1227
|
respondToIHaveTimeout: options?.respondToIHaveTimeout,
|
|
@@ -223,14 +1232,30 @@ let Documents = (() => {
|
|
|
223
1232
|
waitForPruneDelay: options?.waitForPruneDelay,
|
|
224
1233
|
distributionDebounceTime: options?.distributionDebounceTime,
|
|
225
1234
|
strictFullReplicaFallback: false,
|
|
226
|
-
domain:
|
|
227
|
-
? (
|
|
228
|
-
: undefined
|
|
1235
|
+
domain: options?.domain
|
|
1236
|
+
? () => options.domain(this)
|
|
1237
|
+
: undefined,
|
|
229
1238
|
compatibility: logCompatiblity,
|
|
230
1239
|
eagerBlocks: options?.eagerBlocks,
|
|
231
1240
|
fanout: options?.fanout,
|
|
232
1241
|
keep: keepFunction,
|
|
233
1242
|
});
|
|
1243
|
+
this._nativeBackboneDocumentIndexEnabled = false;
|
|
1244
|
+
if (this._mode !== "compat" &&
|
|
1245
|
+
options?.nativeBackbone &&
|
|
1246
|
+
typeof options.nativeBackbone !== "boolean" &&
|
|
1247
|
+
options.nativeBackbone.documentIndex === true) {
|
|
1248
|
+
this._nativeBackboneDocumentIndexEnabled =
|
|
1249
|
+
asTrustedDocumentIndex(this._index).attachNativeBackboneDocumentIndex(this.getSharedLogNativeBackbone(), { preserveExisting: this._mode === "native" }) === true;
|
|
1250
|
+
if (this._nativeBackboneDocumentIndexEnabled) {
|
|
1251
|
+
await initializeDocumentRust();
|
|
1252
|
+
}
|
|
1253
|
+
}
|
|
1254
|
+
this._optionCanPerformNativeFastPath = this._optionCanPerformNativePolicy
|
|
1255
|
+
? createCanPerformPolicyEvaluator(this._optionCanPerformNativePolicy, this.log.log.identity.publicKey)
|
|
1256
|
+
: undefined;
|
|
1257
|
+
this.assertNativeModeReady();
|
|
1258
|
+
this._documentBackend = this.createDocumentBackend();
|
|
234
1259
|
}
|
|
235
1260
|
async recover() {
|
|
236
1261
|
return this.log.recover();
|
|
@@ -248,229 +1273,1795 @@ let Documents = (() => {
|
|
|
248
1273
|
}
|
|
249
1274
|
try {
|
|
250
1275
|
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
|
|
1276
|
+
if (this._optionCanPerform) {
|
|
1277
|
+
if (this._optionCanPerformNativePolicy && this.isNativeMode()) {
|
|
1278
|
+
return this.nativeCanPerformAllowsAppend(this._optionCanPerformNativePolicy, operation, entry, reference?.document);
|
|
263
1279
|
}
|
|
264
|
-
|
|
265
|
-
|
|
1280
|
+
let document = reference?.document;
|
|
1281
|
+
if (!document) {
|
|
1282
|
+
if (isPutOperation(l0)) {
|
|
1283
|
+
document =
|
|
1284
|
+
this._canAppendDecodedDocuments.get(l0) ??
|
|
1285
|
+
this._index.valueEncoding.decoder(l0.data);
|
|
1286
|
+
if (!document) {
|
|
1287
|
+
return false;
|
|
1288
|
+
}
|
|
1289
|
+
}
|
|
1290
|
+
else if (isDeleteOperation(l0)) {
|
|
1291
|
+
// Nothing to do here by default.
|
|
1292
|
+
// Checking if the document exists is not necessary since it
|
|
1293
|
+
// might already be deleted.
|
|
1294
|
+
}
|
|
1295
|
+
else {
|
|
1296
|
+
throw new Error("Unsupported operation");
|
|
1297
|
+
}
|
|
266
1298
|
}
|
|
267
|
-
|
|
268
|
-
|
|
1299
|
+
const previousEntries = this._optionCanPerformNativePolicy &&
|
|
1300
|
+
isPutOperation(operation) &&
|
|
1301
|
+
canPerformPolicyNeedsPreviousEntries(this._optionCanPerformNativePolicy)
|
|
1302
|
+
? await this.resolveCanPerformPreviousEntries(entry)
|
|
1303
|
+
: undefined;
|
|
1304
|
+
const deleteValue = this._optionCanPerformNativePolicy &&
|
|
1305
|
+
isDeleteOperation(operation) &&
|
|
1306
|
+
canPerformPolicyNeedsDeleteValue(this._optionCanPerformNativePolicy)
|
|
1307
|
+
? await this.resolveCanPerformDeleteValue(operation)
|
|
1308
|
+
: undefined;
|
|
269
1309
|
if (!(await this._optionCanPerform(isPutOperation(operation)
|
|
270
1310
|
? {
|
|
271
1311
|
type: "put",
|
|
272
1312
|
value: document,
|
|
273
1313
|
operation,
|
|
274
1314
|
entry: entry,
|
|
1315
|
+
previousEntries,
|
|
275
1316
|
}
|
|
276
1317
|
: {
|
|
277
1318
|
type: "delete",
|
|
1319
|
+
value: deleteValue,
|
|
278
1320
|
operation,
|
|
279
1321
|
entry: entry,
|
|
280
1322
|
}))) {
|
|
281
1323
|
return false;
|
|
282
1324
|
}
|
|
283
1325
|
}
|
|
284
|
-
}
|
|
285
|
-
catch (error) {
|
|
286
|
-
if (error instanceof BorshError) {
|
|
287
|
-
warn("Received payload that could not be decoded, skipping");
|
|
288
|
-
return false;
|
|
1326
|
+
}
|
|
1327
|
+
catch (error) {
|
|
1328
|
+
if (error instanceof BorshError) {
|
|
1329
|
+
warn("Received payload that could not be decoded, skipping");
|
|
1330
|
+
return false;
|
|
1331
|
+
}
|
|
1332
|
+
throw error;
|
|
1333
|
+
}
|
|
1334
|
+
return true;
|
|
1335
|
+
}
|
|
1336
|
+
async nativeCanPerformAllowsAppend(descriptor, operation, entry, document) {
|
|
1337
|
+
if (isPutOperation(operation)) {
|
|
1338
|
+
let previousSignerPublicKeys = [];
|
|
1339
|
+
let previousEntries = [];
|
|
1340
|
+
if (canPerformPolicyNeedsPreviousEntries(descriptor)) {
|
|
1341
|
+
const lookup = this.getNativeEntrySignerPublicKeys(entry.meta.next);
|
|
1342
|
+
if (lookup && lookup.every((key) => key != null)) {
|
|
1343
|
+
previousSignerPublicKeys = lookup;
|
|
1344
|
+
}
|
|
1345
|
+
else if (this.isNativeMode()) {
|
|
1346
|
+
if (entry.meta.next.length > 1) {
|
|
1347
|
+
return false;
|
|
1348
|
+
}
|
|
1349
|
+
const previousSigner = await this.getNativePreviousEntrySignerPublicKeyForPutOperation(operation);
|
|
1350
|
+
if (previousSigner?.publicKey) {
|
|
1351
|
+
previousSignerPublicKeys = [previousSigner.publicKey];
|
|
1352
|
+
}
|
|
1353
|
+
else if (previousSigner?.exists || entry.meta.next.length > 0) {
|
|
1354
|
+
return false;
|
|
1355
|
+
}
|
|
1356
|
+
}
|
|
1357
|
+
else {
|
|
1358
|
+
previousEntries = await this.resolveCanPerformPreviousEntries(entry);
|
|
1359
|
+
}
|
|
1360
|
+
}
|
|
1361
|
+
const entryPublicKeys = !document && canPerformPolicyPutNeedsEntryPublicKeys(descriptor)
|
|
1362
|
+
? entry.publicKeys.length > 0
|
|
1363
|
+
? entry.publicKeys
|
|
1364
|
+
: await entry.getPublicKeys()
|
|
1365
|
+
: [];
|
|
1366
|
+
return this.nativePutOperationPolicyAllows(descriptor, operation, document, previousEntries, previousSignerPublicKeys, entryPublicKeys);
|
|
1367
|
+
}
|
|
1368
|
+
const entryPublicKeys = this.nativeDeletePolicyNeedsEntryPublicKeys(descriptor)
|
|
1369
|
+
? entry.publicKeys.length > 0
|
|
1370
|
+
? entry.publicKeys
|
|
1371
|
+
: await entry.getPublicKeys()
|
|
1372
|
+
: undefined;
|
|
1373
|
+
return this.nativeDeleteOperationPolicyAllows(descriptor, operation, entryPublicKeys);
|
|
1374
|
+
}
|
|
1375
|
+
async resolveCanPerformPreviousEntries(entry) {
|
|
1376
|
+
const entries = [];
|
|
1377
|
+
for (const hash of entry.meta.next) {
|
|
1378
|
+
const previous = await this._resolveEntry(hash);
|
|
1379
|
+
if (previous) {
|
|
1380
|
+
entries.push(previous);
|
|
1381
|
+
}
|
|
1382
|
+
}
|
|
1383
|
+
return entries;
|
|
1384
|
+
}
|
|
1385
|
+
async resolveCanPerformDeleteValue(operation, options) {
|
|
1386
|
+
const key = operation.key instanceof indexerTypes.IdKey
|
|
1387
|
+
? operation.key
|
|
1388
|
+
: indexerTypes.toId(operation.key);
|
|
1389
|
+
const existing = await this.getLocalIndexedContext(key);
|
|
1390
|
+
const existingHead = this.getExistingContext(existing)?.head;
|
|
1391
|
+
if (!existingHead) {
|
|
1392
|
+
return;
|
|
1393
|
+
}
|
|
1394
|
+
const indexedDocument = await this.getLocalIdentityDocumentByHead(existingHead);
|
|
1395
|
+
if (indexedDocument) {
|
|
1396
|
+
return indexedDocument;
|
|
1397
|
+
}
|
|
1398
|
+
const indexedPolicyDocument = await this.getLocalIndexedDocumentForNativeDeletePolicy(key);
|
|
1399
|
+
if (indexedPolicyDocument) {
|
|
1400
|
+
return indexedPolicyDocument;
|
|
1401
|
+
}
|
|
1402
|
+
if (options?.allowEntryFallback === false) {
|
|
1403
|
+
return;
|
|
1404
|
+
}
|
|
1405
|
+
const existingEntry = await this._resolveEntry(existingHead, {
|
|
1406
|
+
remote: true,
|
|
1407
|
+
});
|
|
1408
|
+
const existingOperation = await existingEntry.getPayloadValue();
|
|
1409
|
+
if (!isPutOperation(existingOperation)) {
|
|
1410
|
+
return;
|
|
1411
|
+
}
|
|
1412
|
+
return this._index.valueEncoding.decoder(existingOperation.data);
|
|
1413
|
+
}
|
|
1414
|
+
async _canAppend(entry, reference) {
|
|
1415
|
+
const resolve = async (history) => {
|
|
1416
|
+
return typeof history === "string"
|
|
1417
|
+
? this.log.log.get(history) ||
|
|
1418
|
+
(await Entry.fromMultihash(this.log.log.blocks, history))
|
|
1419
|
+
: history;
|
|
1420
|
+
};
|
|
1421
|
+
const pointsToHistory = async (history) => {
|
|
1422
|
+
// make sure nexts only points to this document at some point in history
|
|
1423
|
+
let current = await resolve(history);
|
|
1424
|
+
const next = entry.meta.next[0];
|
|
1425
|
+
while (current?.hash &&
|
|
1426
|
+
next !== current?.hash &&
|
|
1427
|
+
current.meta.next.length > 0) {
|
|
1428
|
+
current = await this.log.log.get(current.meta.next[0]);
|
|
1429
|
+
}
|
|
1430
|
+
if (current?.hash === next) {
|
|
1431
|
+
return true; // Ok, we are pointing this new edit to some exising point in time of the old document
|
|
1432
|
+
}
|
|
1433
|
+
return false;
|
|
1434
|
+
};
|
|
1435
|
+
let initialized = false;
|
|
1436
|
+
const ensureInitialized = () => {
|
|
1437
|
+
if (!initialized) {
|
|
1438
|
+
entry.init({
|
|
1439
|
+
encoding: this.log.log.encoding,
|
|
1440
|
+
keychain: this.node.services.keychain,
|
|
1441
|
+
});
|
|
1442
|
+
initialized = true;
|
|
1443
|
+
}
|
|
1444
|
+
};
|
|
1445
|
+
try {
|
|
1446
|
+
const operation = reference?.operation ||
|
|
1447
|
+
(await this.getAppendOperation(entry, ensureInitialized));
|
|
1448
|
+
if (!operation) {
|
|
1449
|
+
return false;
|
|
1450
|
+
}
|
|
1451
|
+
if (isPutOperation(operation)) {
|
|
1452
|
+
// check nexts
|
|
1453
|
+
const putOperation = operation;
|
|
1454
|
+
let keyValue;
|
|
1455
|
+
if (reference?.document) {
|
|
1456
|
+
keyValue = this.idResolver(reference.document);
|
|
1457
|
+
}
|
|
1458
|
+
else {
|
|
1459
|
+
keyValue = await this.getNativeDocumentIdFromPutOperation(putOperation);
|
|
1460
|
+
if (keyValue == null) {
|
|
1461
|
+
if (this.isNativeMode()) {
|
|
1462
|
+
return false;
|
|
1463
|
+
}
|
|
1464
|
+
const value = this.index.valueEncoding.decoder(putOperation.data);
|
|
1465
|
+
this._canAppendDecodedDocuments.set(putOperation, value);
|
|
1466
|
+
keyValue = this.idResolver(value);
|
|
1467
|
+
}
|
|
1468
|
+
}
|
|
1469
|
+
const key = indexerTypes.toId(keyValue);
|
|
1470
|
+
const existingDocument = this.isNativeMode()
|
|
1471
|
+
? this.hasNativeDocumentContextLookup()
|
|
1472
|
+
? this.getNativeIndexedContext(key)
|
|
1473
|
+
: undefined
|
|
1474
|
+
: this.immutable
|
|
1475
|
+
? (await this.index.getDetailed(key, {
|
|
1476
|
+
resolve: false,
|
|
1477
|
+
local: true,
|
|
1478
|
+
remote: { strategy: "fallback" },
|
|
1479
|
+
}))?.[0]?.results[0]
|
|
1480
|
+
: await this.getLocalIndexedContext(key);
|
|
1481
|
+
if (this.isNativeMode() && !this.hasNativeDocumentContextLookup()) {
|
|
1482
|
+
return false;
|
|
1483
|
+
}
|
|
1484
|
+
const existingContext = this.getExistingContext(existingDocument);
|
|
1485
|
+
if (existingContext && existingContext.head !== entry.hash) {
|
|
1486
|
+
// This can happen if we reset the operation log without resetting the index, for example during recover.
|
|
1487
|
+
if (this.immutable) {
|
|
1488
|
+
// key already exist but pick the oldest entry
|
|
1489
|
+
// this is because we can not overwrite same id if immutable
|
|
1490
|
+
if (existingContext.created < entry.meta.clock.timestamp.wallTime) {
|
|
1491
|
+
return false;
|
|
1492
|
+
}
|
|
1493
|
+
if (entry.meta.next.length > 0) {
|
|
1494
|
+
return false; // can not append to immutable document
|
|
1495
|
+
}
|
|
1496
|
+
return putOperation;
|
|
1497
|
+
}
|
|
1498
|
+
else {
|
|
1499
|
+
if (this.strictHistory) {
|
|
1500
|
+
// make sure that the next pointer exist and points to the existing documents
|
|
1501
|
+
if (entry.meta.next.length !== 1) {
|
|
1502
|
+
return false;
|
|
1503
|
+
}
|
|
1504
|
+
if (entry.meta.next[0] === existingContext.head) {
|
|
1505
|
+
return putOperation;
|
|
1506
|
+
}
|
|
1507
|
+
const prevEntry = await this.log.log.entryIndex.get(existingContext.head);
|
|
1508
|
+
if (!prevEntry) {
|
|
1509
|
+
logger.error("Failed to find previous entry for document edit: " +
|
|
1510
|
+
entry.hash);
|
|
1511
|
+
return false;
|
|
1512
|
+
}
|
|
1513
|
+
const referenceHistoryCorrectly = await pointsToHistory(prevEntry);
|
|
1514
|
+
return referenceHistoryCorrectly ? putOperation : false;
|
|
1515
|
+
}
|
|
1516
|
+
else {
|
|
1517
|
+
return putOperation;
|
|
1518
|
+
}
|
|
1519
|
+
}
|
|
1520
|
+
}
|
|
1521
|
+
else {
|
|
1522
|
+
// Keep existing behavior: next pointers may express document dependencies.
|
|
1523
|
+
}
|
|
1524
|
+
}
|
|
1525
|
+
else if (isDeleteOperation(operation)) {
|
|
1526
|
+
if (entry.meta.next.length !== 1) {
|
|
1527
|
+
return false;
|
|
1528
|
+
}
|
|
1529
|
+
const deleteKey = operation.key instanceof indexerTypes.IdKey
|
|
1530
|
+
? operation.key
|
|
1531
|
+
: indexerTypes.toId(operation.key);
|
|
1532
|
+
const existingDocument = this.isNativeMode()
|
|
1533
|
+
? this.hasNativeDocumentContextLookup()
|
|
1534
|
+
? this.getNativeIndexedContext(deleteKey)
|
|
1535
|
+
: undefined
|
|
1536
|
+
: this.immutable
|
|
1537
|
+
? (await this.index.getDetailed(operation.key, {
|
|
1538
|
+
resolve: false,
|
|
1539
|
+
local: true,
|
|
1540
|
+
remote: true,
|
|
1541
|
+
}))?.[0]?.results[0]
|
|
1542
|
+
: await this.getLocalIndexedContext(deleteKey);
|
|
1543
|
+
if (this.isNativeMode() && !this.hasNativeDocumentContextLookup()) {
|
|
1544
|
+
return false;
|
|
1545
|
+
}
|
|
1546
|
+
const existingHead = this.getExistingContext(existingDocument)?.head;
|
|
1547
|
+
if (!existingHead) {
|
|
1548
|
+
// already deleted
|
|
1549
|
+
return coerceDeleteOperation(operation); // assume ok
|
|
1550
|
+
}
|
|
1551
|
+
if (entry.meta.next[0] === existingHead) {
|
|
1552
|
+
return coerceDeleteOperation(operation);
|
|
1553
|
+
}
|
|
1554
|
+
if (this.isNativeMode()) {
|
|
1555
|
+
return false;
|
|
1556
|
+
}
|
|
1557
|
+
let doc = await this.log.log.get(existingHead);
|
|
1558
|
+
if (!doc) {
|
|
1559
|
+
logger.error("Failed to find Document from head");
|
|
1560
|
+
return false;
|
|
1561
|
+
}
|
|
1562
|
+
if (await pointsToHistory(doc)) {
|
|
1563
|
+
// references the existing document
|
|
1564
|
+
return coerceDeleteOperation(operation);
|
|
1565
|
+
}
|
|
1566
|
+
return false;
|
|
1567
|
+
}
|
|
1568
|
+
else {
|
|
1569
|
+
throw new Error("Unsupported operation");
|
|
1570
|
+
}
|
|
1571
|
+
return operation;
|
|
1572
|
+
}
|
|
1573
|
+
catch (error) {
|
|
1574
|
+
if (error instanceof AccessError) {
|
|
1575
|
+
return false; // we cant index because we can not decrypt
|
|
1576
|
+
}
|
|
1577
|
+
else if (error instanceof BorshError) {
|
|
1578
|
+
warn("Received payload that could not be decoded, skipping");
|
|
1579
|
+
return false;
|
|
1580
|
+
}
|
|
1581
|
+
throw error;
|
|
1582
|
+
}
|
|
1583
|
+
}
|
|
1584
|
+
async getAppendOperation(entry, ensureInitialized) {
|
|
1585
|
+
if (this.isNativeMode()) {
|
|
1586
|
+
const operation = await this.getPlainEntryOperationFromStorage(entry);
|
|
1587
|
+
if (operation) {
|
|
1588
|
+
return operation;
|
|
1589
|
+
}
|
|
1590
|
+
return;
|
|
1591
|
+
}
|
|
1592
|
+
ensureInitialized?.();
|
|
1593
|
+
return entry.getPayloadValue();
|
|
1594
|
+
}
|
|
1595
|
+
async getPlainEntryOperationFromStorage(entry) {
|
|
1596
|
+
let storageBytes;
|
|
1597
|
+
try {
|
|
1598
|
+
storageBytes =
|
|
1599
|
+
Entry.getPreparedStorageBytes(entry) ?? entry.getStorageBytes();
|
|
1600
|
+
}
|
|
1601
|
+
catch {
|
|
1602
|
+
return;
|
|
1603
|
+
}
|
|
1604
|
+
try {
|
|
1605
|
+
const payloadData = await entryV0PlainPayloadDataFromStorage(storageBytes);
|
|
1606
|
+
return payloadData
|
|
1607
|
+
? BORSH_ENCODING_OPERATION.decoder(payloadData)
|
|
1608
|
+
: undefined;
|
|
1609
|
+
}
|
|
1610
|
+
catch {
|
|
1611
|
+
try {
|
|
1612
|
+
const payloadData = entry
|
|
1613
|
+
.payload?.data;
|
|
1614
|
+
return payloadData
|
|
1615
|
+
? BORSH_ENCODING_OPERATION.decoder(payloadData)
|
|
1616
|
+
: undefined;
|
|
1617
|
+
}
|
|
1618
|
+
catch {
|
|
1619
|
+
return;
|
|
1620
|
+
}
|
|
1621
|
+
}
|
|
1622
|
+
}
|
|
1623
|
+
getNativeDocumentFieldExtractionPlan(path) {
|
|
1624
|
+
const key = JSON.stringify(typeof path === "string" ? [path] : path);
|
|
1625
|
+
const plans = (this._nativeDocumentFieldExtractionPlans ??= new Map());
|
|
1626
|
+
if (plans.has(key)) {
|
|
1627
|
+
return plans.get(key);
|
|
1628
|
+
}
|
|
1629
|
+
const plan = asTrustedDocumentIndex(this._index).getNativeDocumentFieldExtractionPlan(path);
|
|
1630
|
+
plans.set(key, plan);
|
|
1631
|
+
return plan;
|
|
1632
|
+
}
|
|
1633
|
+
async getNativeDocumentFieldFromPutOperation(operation, path) {
|
|
1634
|
+
if (!this.isNativeMode()) {
|
|
1635
|
+
return;
|
|
1636
|
+
}
|
|
1637
|
+
const plan = this.getNativeDocumentFieldExtractionPlan(path);
|
|
1638
|
+
if (!plan) {
|
|
1639
|
+
return;
|
|
1640
|
+
}
|
|
1641
|
+
try {
|
|
1642
|
+
return await extractDocumentFieldSimple(operation.data, plan);
|
|
1643
|
+
}
|
|
1644
|
+
catch {
|
|
1645
|
+
return;
|
|
1646
|
+
}
|
|
1647
|
+
}
|
|
1648
|
+
async getNativeDocumentIdFromPutOperation(operation) {
|
|
1649
|
+
if (!this.isNativeMode() || !this._nativeDocumentIdExtractionPlan) {
|
|
1650
|
+
return;
|
|
1651
|
+
}
|
|
1652
|
+
try {
|
|
1653
|
+
const id = await extractDocumentFieldSimple(operation.data, this._nativeDocumentIdExtractionPlan);
|
|
1654
|
+
return id;
|
|
1655
|
+
}
|
|
1656
|
+
catch {
|
|
1657
|
+
return;
|
|
1658
|
+
}
|
|
1659
|
+
}
|
|
1660
|
+
preparePut(doc) {
|
|
1661
|
+
const keyValue = this.idResolver(doc);
|
|
1662
|
+
indexerTypes.checkId(keyValue);
|
|
1663
|
+
let encodedDocument = serialize(doc);
|
|
1664
|
+
if (encodedDocument.length > MAX_BATCH_SIZE) {
|
|
1665
|
+
throw new Error(`Document is too large (${encodedDocument.length * 1e-6}) mb). Needs to be less than ${MAX_BATCH_SIZE * 1e-6} mb`);
|
|
1666
|
+
}
|
|
1667
|
+
const key = indexerTypes.toId(keyValue);
|
|
1668
|
+
let operation;
|
|
1669
|
+
let encodedOperation;
|
|
1670
|
+
if (this.compatibility === 6) {
|
|
1671
|
+
if (typeof keyValue === "string") {
|
|
1672
|
+
operation = new PutWithKeyOperation({
|
|
1673
|
+
key: keyValue,
|
|
1674
|
+
data: encodedDocument,
|
|
1675
|
+
});
|
|
1676
|
+
}
|
|
1677
|
+
else {
|
|
1678
|
+
throw new Error("Key must be a string in compatibility mode v6");
|
|
1679
|
+
}
|
|
1680
|
+
}
|
|
1681
|
+
else {
|
|
1682
|
+
encodedOperation = encodePutOperationPayload(encodedDocument);
|
|
1683
|
+
encodedDocument = encodedOperation.subarray(PUT_OPERATION_PREFIX_LENGTH);
|
|
1684
|
+
operation = new PutOperation({
|
|
1685
|
+
data: encodedDocument,
|
|
1686
|
+
});
|
|
1687
|
+
}
|
|
1688
|
+
return {
|
|
1689
|
+
document: doc,
|
|
1690
|
+
encodedDocument,
|
|
1691
|
+
encodedOperation,
|
|
1692
|
+
keyValue,
|
|
1693
|
+
key,
|
|
1694
|
+
operation,
|
|
1695
|
+
};
|
|
1696
|
+
}
|
|
1697
|
+
preparePlainPut(doc) {
|
|
1698
|
+
if (this.compatibility === 6) {
|
|
1699
|
+
throw new Error("Plain put preparation is not supported in v6 mode");
|
|
1700
|
+
}
|
|
1701
|
+
const keyValue = this.idResolver(doc);
|
|
1702
|
+
indexerTypes.checkId(keyValue);
|
|
1703
|
+
const documentBytes = serialize(doc);
|
|
1704
|
+
if (documentBytes.length > MAX_BATCH_SIZE) {
|
|
1705
|
+
throw new Error(`Document is too large (${documentBytes.length * 1e-6}) mb). Needs to be less than ${MAX_BATCH_SIZE * 1e-6} mb`);
|
|
1706
|
+
}
|
|
1707
|
+
const operationPayloadBytes = encodePutOperationPayload(documentBytes);
|
|
1708
|
+
return {
|
|
1709
|
+
document: doc,
|
|
1710
|
+
encodedDocument: operationPayloadBytes.subarray(PUT_OPERATION_PREFIX_LENGTH),
|
|
1711
|
+
operationPayloadBytes,
|
|
1712
|
+
keyValue,
|
|
1713
|
+
key: indexerTypes.toId(keyValue),
|
|
1714
|
+
};
|
|
1715
|
+
}
|
|
1716
|
+
async put(doc, options) {
|
|
1717
|
+
return this._documentBackend.put(doc, options);
|
|
1718
|
+
}
|
|
1719
|
+
async putCompatDocumentBackend(doc, options) {
|
|
1720
|
+
const putOptions = this.normalizeNativeModePutOptions(options);
|
|
1721
|
+
const prepared = this.canUsePlainPutFastPath(doc, putOptions)
|
|
1722
|
+
? this.preparePlainPut(doc)
|
|
1723
|
+
: this.preparePut(doc);
|
|
1724
|
+
let existingLocalContext;
|
|
1725
|
+
let existingHead;
|
|
1726
|
+
if (!putOptions?.unique) {
|
|
1727
|
+
if (putOptions?.checkRemote) {
|
|
1728
|
+
existingHead = (await this._index.getDetailed(prepared.key, {
|
|
1729
|
+
resolve: false,
|
|
1730
|
+
local: true,
|
|
1731
|
+
remote: { replicate: putOptions?.replicate },
|
|
1732
|
+
}))?.[0]?.results[0]?.context.head;
|
|
1733
|
+
}
|
|
1734
|
+
else {
|
|
1735
|
+
existingLocalContext =
|
|
1736
|
+
(await this.getLocalIndexedContext(prepared.key)) || null;
|
|
1737
|
+
existingHead = existingLocalContext?.value.__context.head;
|
|
1738
|
+
}
|
|
1739
|
+
}
|
|
1740
|
+
const plainPutPlan = await this.createPlainPutCommitPlan(prepared, existingHead, existingLocalContext, putOptions);
|
|
1741
|
+
if (plainPutPlan) {
|
|
1742
|
+
return this.commitPlainPutPlan(plainPutPlan, putOptions);
|
|
1743
|
+
}
|
|
1744
|
+
const operation = "operation" in prepared
|
|
1745
|
+
? prepared.operation
|
|
1746
|
+
: new PutOperation({ data: prepared.encodedDocument });
|
|
1747
|
+
const appended = await this.log.append(operation, {
|
|
1748
|
+
...putOptions,
|
|
1749
|
+
meta: {
|
|
1750
|
+
next: existingHead ? [await this._resolveEntry(existingHead)] : [],
|
|
1751
|
+
...putOptions?.meta,
|
|
1752
|
+
},
|
|
1753
|
+
canAppend: (entry) => {
|
|
1754
|
+
return this.canAppend(entry, {
|
|
1755
|
+
document: prepared.document,
|
|
1756
|
+
operation,
|
|
1757
|
+
});
|
|
1758
|
+
},
|
|
1759
|
+
onChange: (change) => {
|
|
1760
|
+
return this.handleChanges(change, {
|
|
1761
|
+
document: prepared.document,
|
|
1762
|
+
operation,
|
|
1763
|
+
key: prepared.key,
|
|
1764
|
+
unique: putOptions?.unique,
|
|
1765
|
+
existing: existingLocalContext,
|
|
1766
|
+
});
|
|
1767
|
+
},
|
|
1768
|
+
replicate: putOptions?.replicate,
|
|
1769
|
+
});
|
|
1770
|
+
this.keepCache?.add(appended.entry.hash);
|
|
1771
|
+
return appended;
|
|
1772
|
+
}
|
|
1773
|
+
async putMany(docs, options) {
|
|
1774
|
+
return this._documentBackend.putMany(docs, options);
|
|
1775
|
+
}
|
|
1776
|
+
async putManyCompatDocumentBackend(docs, options) {
|
|
1777
|
+
if (docs.length === 0) {
|
|
1778
|
+
return { entries: [], removed: [] };
|
|
1779
|
+
}
|
|
1780
|
+
if (!this.canUsePlainPutManyFastPath(docs, options)) {
|
|
1781
|
+
return this.putManySequential(docs, options);
|
|
1782
|
+
}
|
|
1783
|
+
const prepared = docs.map((doc) => this.preparePlainPut(doc));
|
|
1784
|
+
if (this.hasDuplicatePreparedPutKeys(prepared)) {
|
|
1785
|
+
return this.putManySequential(docs, options);
|
|
1786
|
+
}
|
|
1787
|
+
const documentAppendCommit = await this.commitNativeDocumentAppendMany({
|
|
1788
|
+
puts: prepared.map((item) => ({
|
|
1789
|
+
document: item.document,
|
|
1790
|
+
key: item.key,
|
|
1791
|
+
documentBytes: item.encodedDocument,
|
|
1792
|
+
operationPayloadBytes: item.operationPayloadBytes,
|
|
1793
|
+
unique: options?.unique,
|
|
1794
|
+
existing: null,
|
|
1795
|
+
})),
|
|
1796
|
+
resolveTrimmedEntries: !this._index.canGetIdentityIndexedByHead(),
|
|
1797
|
+
options,
|
|
1798
|
+
});
|
|
1799
|
+
if (!documentAppendCommit) {
|
|
1800
|
+
return this.putManySequential(docs, options);
|
|
1801
|
+
}
|
|
1802
|
+
await this.handlePreparedPlainPutManyCommit(documentAppendCommit);
|
|
1803
|
+
for (const commit of documentAppendCommit.commits) {
|
|
1804
|
+
this.keepCache?.add(commit.append.hash);
|
|
1805
|
+
}
|
|
1806
|
+
return {
|
|
1807
|
+
get entries() {
|
|
1808
|
+
return documentAppendCommit.entries;
|
|
1809
|
+
},
|
|
1810
|
+
removed: documentAppendCommit.removed,
|
|
1811
|
+
};
|
|
1812
|
+
}
|
|
1813
|
+
async putManySequential(docs, options) {
|
|
1814
|
+
const entries = [];
|
|
1815
|
+
const removed = [];
|
|
1816
|
+
for (const doc of docs) {
|
|
1817
|
+
const appended = await this.put(doc, options);
|
|
1818
|
+
entries.push(appended.entry);
|
|
1819
|
+
removed.push(...appended.removed);
|
|
1820
|
+
}
|
|
1821
|
+
return { entries, removed };
|
|
1822
|
+
}
|
|
1823
|
+
hasDuplicatePreparedPutKeys(prepared) {
|
|
1824
|
+
const keys = new Set();
|
|
1825
|
+
for (const item of prepared) {
|
|
1826
|
+
if (keys.has(item.key.primitive)) {
|
|
1827
|
+
return true;
|
|
1828
|
+
}
|
|
1829
|
+
keys.add(item.key.primitive);
|
|
1830
|
+
}
|
|
1831
|
+
return false;
|
|
1832
|
+
}
|
|
1833
|
+
canUsePlainPutFastPath(doc, options) {
|
|
1834
|
+
return (this._mode !== "compat" &&
|
|
1835
|
+
this.canPerformAllowsPlainPutFastPath(doc) &&
|
|
1836
|
+
!this.immutable &&
|
|
1837
|
+
!this.strictHistory &&
|
|
1838
|
+
this.compatibility !== 6 &&
|
|
1839
|
+
!Program.isPrototypeOf(this._clazz) &&
|
|
1840
|
+
!options?.canAppend &&
|
|
1841
|
+
!options?.onChange &&
|
|
1842
|
+
!options?.signers &&
|
|
1843
|
+
!options?.identity &&
|
|
1844
|
+
!options?.encryption &&
|
|
1845
|
+
!options?.trim &&
|
|
1846
|
+
!options?.durability &&
|
|
1847
|
+
options?.deferIndexWrite === undefined &&
|
|
1848
|
+
!options?.meta?.type &&
|
|
1849
|
+
!(options?.meta && "data" in options.meta) &&
|
|
1850
|
+
!options?.meta?.next &&
|
|
1851
|
+
!options?.meta?.timestamp &&
|
|
1852
|
+
!options?.meta?.gidSeed &&
|
|
1853
|
+
options?.replicate !== true &&
|
|
1854
|
+
(!options?.target || options.target === "none") &&
|
|
1855
|
+
(options?.delivery === undefined || options.delivery === false) &&
|
|
1856
|
+
!options?.checkRemote &&
|
|
1857
|
+
options?.replicas === undefined);
|
|
1858
|
+
}
|
|
1859
|
+
canUsePlainPutManyFastPath(docs, options) {
|
|
1860
|
+
return (options?.unique === true &&
|
|
1861
|
+
options?.replicate !== true &&
|
|
1862
|
+
options?.target === "none" &&
|
|
1863
|
+
(options?.delivery === undefined || options.delivery === false) &&
|
|
1864
|
+
docs.every((doc) => this.canUsePlainPutFastPath(doc, options)));
|
|
1865
|
+
}
|
|
1866
|
+
async createPlainPutCommitPlan(prepared, existingHead, existingLocalContext, options, assumePlainPutFastPath = false) {
|
|
1867
|
+
if (("operation" in prepared &&
|
|
1868
|
+
!(prepared.operation instanceof PutOperation)) ||
|
|
1869
|
+
(!assumePlainPutFastPath &&
|
|
1870
|
+
!this.canUsePlainPutFastPath(prepared.document, options))) {
|
|
1871
|
+
return;
|
|
1872
|
+
}
|
|
1873
|
+
const indexedContextNext = existingHead
|
|
1874
|
+
? this.nextFromIndexedContext(existingHead, existingLocalContext)
|
|
1875
|
+
: undefined;
|
|
1876
|
+
const next = existingHead
|
|
1877
|
+
? indexedContextNext
|
|
1878
|
+
? [indexedContextNext]
|
|
1879
|
+
: [await this._resolveEntry(existingHead)]
|
|
1880
|
+
: [];
|
|
1881
|
+
const canCleanupTrimmedHeads = this._index.canGetIndexedKeyByHead();
|
|
1882
|
+
return {
|
|
1883
|
+
document: prepared.document,
|
|
1884
|
+
encodedDocument: prepared.encodedDocument,
|
|
1885
|
+
payloadData: "operationPayloadBytes" in prepared
|
|
1886
|
+
? prepared.operationPayloadBytes
|
|
1887
|
+
: (prepared.encodedOperation ??
|
|
1888
|
+
encodePutOperationPayload(prepared.operation.data)),
|
|
1889
|
+
key: prepared.key,
|
|
1890
|
+
operation: "operation" in prepared ? prepared.operation : undefined,
|
|
1891
|
+
next,
|
|
1892
|
+
skipMissingNextJoin: !options?.checkRemote,
|
|
1893
|
+
resolveTrimmedEntries: !canCleanupTrimmedHeads,
|
|
1894
|
+
useGenericChangeHandler: !options?.unique && existingLocalContext === undefined,
|
|
1895
|
+
unique: options?.unique,
|
|
1896
|
+
existing: existingLocalContext,
|
|
1897
|
+
};
|
|
1898
|
+
}
|
|
1899
|
+
commitPlainPutPlan(plan, options) {
|
|
1900
|
+
return mapMaybePromise(this.commitNativeDocumentAppend({
|
|
1901
|
+
document: plan.document,
|
|
1902
|
+
key: plan.key,
|
|
1903
|
+
operation: plan.operation,
|
|
1904
|
+
documentBytes: plan.encodedDocument,
|
|
1905
|
+
operationPayloadBytes: plan.payloadData,
|
|
1906
|
+
next: plan.next,
|
|
1907
|
+
skipMissingNextJoin: plan.skipMissingNextJoin,
|
|
1908
|
+
resolveTrimmedEntries: plan.resolveTrimmedEntries,
|
|
1909
|
+
options,
|
|
1910
|
+
unique: plan.unique,
|
|
1911
|
+
existing: plan.existing,
|
|
1912
|
+
}), (documentAppendCommit) => {
|
|
1913
|
+
const handled = plan.useGenericChangeHandler
|
|
1914
|
+
? this.handleChanges({
|
|
1915
|
+
added: [{ head: true, entry: documentAppendCommit.entry }],
|
|
1916
|
+
removed: documentAppendCommit.removed,
|
|
1917
|
+
}, {
|
|
1918
|
+
document: plan.document,
|
|
1919
|
+
operation: documentAppendCommit.operation ??
|
|
1920
|
+
plan.operation ??
|
|
1921
|
+
new PutOperation({ data: plan.encodedDocument }),
|
|
1922
|
+
key: plan.key,
|
|
1923
|
+
unique: plan.unique,
|
|
1924
|
+
existing: plan.existing,
|
|
1925
|
+
})
|
|
1926
|
+
: this.handlePreparedPlainPutCommit(documentAppendCommit);
|
|
1927
|
+
return mapMaybePromise(handled, () => {
|
|
1928
|
+
this.keepCache?.add(documentAppendCommit.append.hash);
|
|
1929
|
+
return {
|
|
1930
|
+
get entry() {
|
|
1931
|
+
return documentAppendCommit.entry;
|
|
1932
|
+
},
|
|
1933
|
+
removed: documentAppendCommit.removed,
|
|
1934
|
+
};
|
|
1935
|
+
});
|
|
1936
|
+
});
|
|
1937
|
+
}
|
|
1938
|
+
commitNativeDocumentAppend(input) {
|
|
1939
|
+
const trustedLog = asTrustedDocumentSharedLog(this.log);
|
|
1940
|
+
const appendOptions = {
|
|
1941
|
+
...input.options,
|
|
1942
|
+
meta: {
|
|
1943
|
+
next: input.next,
|
|
1944
|
+
...input.options?.meta,
|
|
1945
|
+
},
|
|
1946
|
+
replicate: input.options?.replicate,
|
|
1947
|
+
};
|
|
1948
|
+
const prepareNativeDocumentIndexWithAppendFacts = this.createNativeBackboneDocumentIndexAppendFactsPreparer(input);
|
|
1949
|
+
const preferAppendFactsDocumentIndex = this.isNativeMode() && !!prepareNativeDocumentIndexWithAppendFacts;
|
|
1950
|
+
return mapMaybePromise(preferAppendFactsDocumentIndex
|
|
1951
|
+
? undefined
|
|
1952
|
+
: this.prepareNativeBackboneDocumentIndexCommit(input), (nativeDocumentIndexCommit) => {
|
|
1953
|
+
let committedNativeDocumentIndex = nativeDocumentIndexCommit;
|
|
1954
|
+
const prepareNativeDocumentIndexWithAppendFactsForCommit = nativeDocumentIndexCommit
|
|
1955
|
+
? undefined
|
|
1956
|
+
: prepareNativeDocumentIndexWithAppendFacts;
|
|
1957
|
+
if (this.isNativeMode() &&
|
|
1958
|
+
!nativeDocumentIndexCommit &&
|
|
1959
|
+
!prepareNativeDocumentIndexWithAppendFactsForCommit) {
|
|
1960
|
+
throw this.nativeModeError("requires native document-index commit");
|
|
1961
|
+
}
|
|
1962
|
+
const appendProperties = {
|
|
1963
|
+
skipMissingNextJoin: input.skipMissingNextJoin,
|
|
1964
|
+
resolveTrimmedEntries: input.resolveTrimmedEntries,
|
|
1965
|
+
payloadData: input.operationPayloadBytes,
|
|
1966
|
+
useNativeExistingDocumentContext: input.useNativeExistingDocumentContext,
|
|
1967
|
+
...(nativeDocumentIndexCommit
|
|
1968
|
+
? {
|
|
1969
|
+
nativeBackboneDocumentIndex: this.toNativeBackboneDocumentIndexCommitInput(input, nativeDocumentIndexCommit),
|
|
1970
|
+
}
|
|
1971
|
+
: {}),
|
|
1972
|
+
...(prepareNativeDocumentIndexWithAppendFactsForCommit
|
|
1973
|
+
? {
|
|
1974
|
+
prepareNativeBackboneDocumentIndex: (facts) => {
|
|
1975
|
+
committedNativeDocumentIndex =
|
|
1976
|
+
prepareNativeDocumentIndexWithAppendFactsForCommit(facts);
|
|
1977
|
+
return committedNativeDocumentIndex
|
|
1978
|
+
? this.toNativeBackboneDocumentIndexCommitInput(input, committedNativeDocumentIndex)
|
|
1979
|
+
: undefined;
|
|
1980
|
+
},
|
|
1981
|
+
}
|
|
1982
|
+
: {}),
|
|
1983
|
+
};
|
|
1984
|
+
if (input.operation) {
|
|
1985
|
+
if (this.isNativeMode()) {
|
|
1986
|
+
throw this.nativeModeError("requires payload-backed put operations");
|
|
1987
|
+
}
|
|
1988
|
+
return mapMaybePromise(trustedLog.appendLocallyPrepared(input.operation, appendOptions, appendProperties), (appended) => this.createNativeCheckedDocumentAppendCommitFacts(input, appended, committedNativeDocumentIndex));
|
|
1989
|
+
}
|
|
1990
|
+
const commitOnlyAppend = this.isNativeMode()
|
|
1991
|
+
? trustedLog.appendStrictNativeDocumentPayloadCommitOnly(input.operationPayloadBytes, appendOptions, appendProperties)
|
|
1992
|
+
: trustedLog.appendLocallyPreparedPayloadCommitOnly(input.operationPayloadBytes, appendOptions, appendProperties);
|
|
1993
|
+
return mapMaybePromise(commitOnlyAppend, (commitOnly) => {
|
|
1994
|
+
if (commitOnly) {
|
|
1995
|
+
return this.createNativeCheckedDocumentAppendCommitFacts(input, commitOnly, committedNativeDocumentIndex);
|
|
1996
|
+
}
|
|
1997
|
+
if (this.isNativeMode()) {
|
|
1998
|
+
throw this.nativeModeError("requires native payload commit-only append");
|
|
1999
|
+
}
|
|
2000
|
+
return this.commitNativeDocumentAppendPayloadFallback(input, appendOptions, appendProperties, committedNativeDocumentIndex);
|
|
2001
|
+
});
|
|
2002
|
+
});
|
|
2003
|
+
}
|
|
2004
|
+
createNativeCheckedDocumentAppendCommitFacts(input, appended, nativeBackboneDocumentIndex) {
|
|
2005
|
+
return mapMaybePromise(this.createDocumentAppendCommitFacts(input, appended, nativeBackboneDocumentIndex), (commit) => {
|
|
2006
|
+
this.assertNativeModeDocumentAppendCommit(commit);
|
|
2007
|
+
return commit;
|
|
2008
|
+
});
|
|
2009
|
+
}
|
|
2010
|
+
assertNativeModeDocumentAppendCommit(commit) {
|
|
2011
|
+
if (!this.isNativeMode()) {
|
|
2012
|
+
return;
|
|
2013
|
+
}
|
|
2014
|
+
if (!commit.nativeBackboneDocumentIndexCommitted) {
|
|
2015
|
+
throw this.nativeModeError("requires native document-index commit");
|
|
2016
|
+
}
|
|
2017
|
+
}
|
|
2018
|
+
toNativeBackboneDocumentIndexCommitInput(input, commit, useLatestContext = false) {
|
|
2019
|
+
const canUsePlainPutPayload = commit.usePlainPutPayload === true ||
|
|
2020
|
+
(!!input.operationPayloadBytes && !!commit.projection);
|
|
2021
|
+
return {
|
|
2022
|
+
key: documentIndexStoreKey(input.key),
|
|
2023
|
+
valuePrefixBytes: commit.valuePrefixBytes,
|
|
2024
|
+
usePlainPutPayload: canUsePlainPutPayload,
|
|
2025
|
+
projection: commit.projection,
|
|
2026
|
+
existingCreated: input.unique || input.existing === null
|
|
2027
|
+
? undefined
|
|
2028
|
+
: input.existing?.value.__context.created,
|
|
2029
|
+
deleteTrimmedHeads: !this.hasDocumentChangeConsumers() &&
|
|
2030
|
+
this._index.canGetIndexedKeyByHead(),
|
|
2031
|
+
useLatestContext,
|
|
2032
|
+
requiredPreviousSignerPublicKey: input.requiredPreviousSignerPublicKey,
|
|
2033
|
+
};
|
|
2034
|
+
}
|
|
2035
|
+
prepareNativeBackboneDocumentIndexCommit(input) {
|
|
2036
|
+
if (!this._nativeBackboneDocumentIndexEnabled) {
|
|
2037
|
+
return;
|
|
2038
|
+
}
|
|
2039
|
+
return asTrustedDocumentIndex(this._index).prepareNativeBackboneDocumentIndexCommit(input.document, input.documentBytes, { entryPublicKeys: [this.log.log.identity.publicKey] });
|
|
2040
|
+
}
|
|
2041
|
+
createNativeBackboneDocumentIndexAppendFactsPreparer(input) {
|
|
2042
|
+
if (!this._nativeBackboneDocumentIndexEnabled ||
|
|
2043
|
+
!asTrustedDocumentIndex(this._index).canPrepareNativeBackboneDocumentIndexCommitWithAppendFacts()) {
|
|
2044
|
+
return;
|
|
2045
|
+
}
|
|
2046
|
+
const existing = input.unique || input.existing === null ? null : input.existing;
|
|
2047
|
+
return (facts) => {
|
|
2048
|
+
const appendFacts = {
|
|
2049
|
+
wallTime: BigInt(facts.wallTime),
|
|
2050
|
+
gid: facts.gid,
|
|
2051
|
+
payloadSize: facts.payloadSize,
|
|
2052
|
+
};
|
|
2053
|
+
const context = nativeDocumentContextFactsAsContext({
|
|
2054
|
+
created: existing?.value.__context.created || appendFacts.wallTime,
|
|
2055
|
+
modified: appendFacts.wallTime,
|
|
2056
|
+
head: "",
|
|
2057
|
+
gid: appendFacts.gid,
|
|
2058
|
+
size: appendFacts.payloadSize,
|
|
2059
|
+
});
|
|
2060
|
+
return asTrustedDocumentIndex(this._index).prepareNativeBackboneDocumentIndexCommitWithAppendFacts(input.document, input.documentBytes, context, { entryPublicKeys: [this.log.log.identity.publicKey] });
|
|
2061
|
+
};
|
|
2062
|
+
}
|
|
2063
|
+
async commitNativeDocumentAppendPayloadFallback(input, appendOptions, appendProperties, nativeBackboneDocumentIndex) {
|
|
2064
|
+
if (this.isNativeMode()) {
|
|
2065
|
+
throw this.nativeModeError("requires native payload append support");
|
|
2066
|
+
}
|
|
2067
|
+
const trustedLog = asTrustedDocumentSharedLog(this.log);
|
|
2068
|
+
let appended;
|
|
2069
|
+
try {
|
|
2070
|
+
appended = await trustedLog.appendLocallyPreparedPayload(input.operationPayloadBytes, appendOptions, appendProperties);
|
|
2071
|
+
}
|
|
2072
|
+
catch (error) {
|
|
2073
|
+
if (!(error instanceof Error) ||
|
|
2074
|
+
error.message !==
|
|
2075
|
+
"appendLocallyPrepared payload-only path requires native append support") {
|
|
2076
|
+
throw error;
|
|
2077
|
+
}
|
|
2078
|
+
appended = await trustedLog.appendLocallyPrepared(new PutOperation({ data: input.documentBytes }), appendOptions, appendProperties);
|
|
2079
|
+
}
|
|
2080
|
+
return this.createDocumentAppendCommitFacts(input, appended, nativeBackboneDocumentIndex);
|
|
2081
|
+
}
|
|
2082
|
+
async commitNativeDocumentAppendMany(input) {
|
|
2083
|
+
const trustedLog = asTrustedDocumentSharedLog(this.log);
|
|
2084
|
+
const nativeBackboneDocumentIndexes = await this.prepareNativeBackboneDocumentIndexCommitBatch(input.puts);
|
|
2085
|
+
const nativeBackboneDocumentIndexInputs = nativeBackboneDocumentIndexes?.map((commit, index) => this.toNativeBackboneDocumentIndexCommitInput(input.puts[index], commit, input.useNativeExistingDocumentContext === true));
|
|
2086
|
+
const nexts = input.puts.map((put) => {
|
|
2087
|
+
if (input.useNativeExistingDocumentContext === true) {
|
|
2088
|
+
return [];
|
|
2089
|
+
}
|
|
2090
|
+
const existing = put.unique || put.existing === null ? null : put.existing;
|
|
2091
|
+
if (!existing) {
|
|
2092
|
+
return [];
|
|
2093
|
+
}
|
|
2094
|
+
const context = existing.value.__context;
|
|
2095
|
+
const next = this.nextFromIndexedContext(context.head, existing);
|
|
2096
|
+
if (!next) {
|
|
2097
|
+
throw this.nativeModeError("requires indexed document context for non-unique putMany");
|
|
2098
|
+
}
|
|
2099
|
+
return [next];
|
|
2100
|
+
});
|
|
2101
|
+
const appended = await trustedLog.appendLocallyPreparedPayloadsManyIndependent(input.puts.map((put) => put.operationPayloadBytes), {
|
|
2102
|
+
...input.options,
|
|
2103
|
+
replicate: input.options?.replicate,
|
|
2104
|
+
}, {
|
|
2105
|
+
resolveTrimmedEntries: input.resolveTrimmedEntries,
|
|
2106
|
+
nexts,
|
|
2107
|
+
nativeBackboneDocumentIndexes: nativeBackboneDocumentIndexInputs,
|
|
2108
|
+
retainMaterializationBytes: this._hasLogTrim,
|
|
2109
|
+
});
|
|
2110
|
+
if (!appended) {
|
|
2111
|
+
if (this.isNativeMode()) {
|
|
2112
|
+
throw this.nativeModeError("requires native batched payload append support");
|
|
2113
|
+
}
|
|
2114
|
+
return undefined;
|
|
2115
|
+
}
|
|
2116
|
+
const appendInputs = input.puts.map((put, index) => ({
|
|
2117
|
+
input: nativeBackboneDocumentIndexes?.[index]
|
|
2118
|
+
? {
|
|
2119
|
+
...put,
|
|
2120
|
+
nativeBackboneDocumentIndex: nativeBackboneDocumentIndexes[index],
|
|
2121
|
+
}
|
|
2122
|
+
: put,
|
|
2123
|
+
appended: (() => {
|
|
2124
|
+
const materializeEntry = appended.materializeEntries?.[index];
|
|
2125
|
+
let entry;
|
|
2126
|
+
return {
|
|
2127
|
+
get entry() {
|
|
2128
|
+
return (entry ??= materializeEntry
|
|
2129
|
+
? materializeEntry()
|
|
2130
|
+
: appended.entries[index]);
|
|
2131
|
+
},
|
|
2132
|
+
removed: [],
|
|
2133
|
+
appendCommit: appended.appendCommits[index],
|
|
2134
|
+
};
|
|
2135
|
+
})(),
|
|
2136
|
+
}));
|
|
2137
|
+
const commits = await this.createDocumentAppendCommitFactsBatch(appendInputs);
|
|
2138
|
+
let entries;
|
|
2139
|
+
return {
|
|
2140
|
+
get entries() {
|
|
2141
|
+
return (entries ??= commits.map((commit) => commit.entry));
|
|
2142
|
+
},
|
|
2143
|
+
removed: appended.removed,
|
|
2144
|
+
commits,
|
|
2145
|
+
};
|
|
2146
|
+
}
|
|
2147
|
+
prepareNativeBackboneDocumentIndexCommitBatch(inputs) {
|
|
2148
|
+
if (!this._nativeBackboneDocumentIndexEnabled || inputs.length === 0) {
|
|
2149
|
+
return;
|
|
2150
|
+
}
|
|
2151
|
+
const commits = [];
|
|
2152
|
+
const finishAsync = (firstAsyncIndex, firstAsyncCommit) => Promise.all([
|
|
2153
|
+
firstAsyncCommit,
|
|
2154
|
+
...inputs
|
|
2155
|
+
.slice(firstAsyncIndex + 1)
|
|
2156
|
+
.map((input) => this.prepareNativeBackboneDocumentIndexCommit(input)),
|
|
2157
|
+
]).then((resolvedCommits) => {
|
|
2158
|
+
for (const commit of resolvedCommits) {
|
|
2159
|
+
if (!commit) {
|
|
2160
|
+
return;
|
|
2161
|
+
}
|
|
2162
|
+
commits.push(commit);
|
|
2163
|
+
}
|
|
2164
|
+
return commits;
|
|
2165
|
+
});
|
|
2166
|
+
for (let i = 0; i < inputs.length; i++) {
|
|
2167
|
+
const commit = this.prepareNativeBackboneDocumentIndexCommit(inputs[i]);
|
|
2168
|
+
if (isPromiseLike(commit)) {
|
|
2169
|
+
return finishAsync(i, commit);
|
|
2170
|
+
}
|
|
2171
|
+
if (!commit) {
|
|
2172
|
+
return;
|
|
2173
|
+
}
|
|
2174
|
+
commits.push(commit);
|
|
2175
|
+
}
|
|
2176
|
+
return commits;
|
|
2177
|
+
}
|
|
2178
|
+
createDocumentAppendCommitFacts(input, appended, nativeBackboneDocumentIndex) {
|
|
2179
|
+
const append = appended.appendCommit;
|
|
2180
|
+
const nativePreviousContext = append.documentPreviousContext == null
|
|
2181
|
+
? undefined
|
|
2182
|
+
: nativeDocumentContextFactsAsContext(append.documentPreviousContext);
|
|
2183
|
+
const nativePreviousIndexedContext = nativePreviousContext
|
|
2184
|
+
? {
|
|
2185
|
+
id: input.key,
|
|
2186
|
+
value: {
|
|
2187
|
+
__context: nativePreviousContext,
|
|
2188
|
+
},
|
|
2189
|
+
}
|
|
2190
|
+
: undefined;
|
|
2191
|
+
const inputWithExisting = input.existing === undefined && nativePreviousIndexedContext
|
|
2192
|
+
? {
|
|
2193
|
+
...input,
|
|
2194
|
+
existing: nativePreviousIndexedContext,
|
|
2195
|
+
}
|
|
2196
|
+
: input;
|
|
2197
|
+
const existing = inputWithExisting.unique || inputWithExisting.existing === null
|
|
2198
|
+
? null
|
|
2199
|
+
: inputWithExisting.existing;
|
|
2200
|
+
const contextInput = {
|
|
2201
|
+
existingCreated: existing?.value.__context.created,
|
|
2202
|
+
modified: append.wallTime,
|
|
2203
|
+
head: append.hash,
|
|
2204
|
+
gid: append.gid,
|
|
2205
|
+
size: append.payloadSize,
|
|
2206
|
+
};
|
|
2207
|
+
if (append.nativeBackboneDocumentIndexCommitted) {
|
|
2208
|
+
return this.createDocumentAppendCommitFactsWithLazyContext(inputWithExisting, appended, contextInput, nativeBackboneDocumentIndex);
|
|
2209
|
+
}
|
|
2210
|
+
const contextPlan = tryPlanDocumentContext(contextInput);
|
|
2211
|
+
if (contextPlan) {
|
|
2212
|
+
return this.createDocumentAppendCommitFactsWithContext(inputWithExisting, appended, contextPlan, nativeBackboneDocumentIndex);
|
|
2213
|
+
}
|
|
2214
|
+
return planDocumentContext(contextInput).then((plannedContext) => plannedContext
|
|
2215
|
+
? this.createDocumentAppendCommitFactsWithContext(inputWithExisting, appended, plannedContext, nativeBackboneDocumentIndex)
|
|
2216
|
+
: this.createDocumentAppendCommitFactsWithLazyContext(inputWithExisting, appended, contextInput, nativeBackboneDocumentIndex));
|
|
2217
|
+
}
|
|
2218
|
+
createDocumentAppendCommitFactsWithLazyContext(input, appended, contextInput, nativeBackboneDocumentIndex = input.nativeBackboneDocumentIndex) {
|
|
2219
|
+
let contextValues;
|
|
2220
|
+
let context;
|
|
2221
|
+
let contextBytes;
|
|
2222
|
+
const getContextValues = () => {
|
|
2223
|
+
if (contextValues) {
|
|
2224
|
+
return contextValues;
|
|
2225
|
+
}
|
|
2226
|
+
const modified = toContextBigInt(contextInput.modified);
|
|
2227
|
+
const existingCreated = contextInput.existingCreated == null
|
|
2228
|
+
? undefined
|
|
2229
|
+
: toContextBigInt(contextInput.existingCreated);
|
|
2230
|
+
return (contextValues = {
|
|
2231
|
+
created: existingCreated == null || existingCreated === 0n
|
|
2232
|
+
? modified
|
|
2233
|
+
: existingCreated,
|
|
2234
|
+
modified,
|
|
2235
|
+
head: contextInput.head,
|
|
2236
|
+
gid: contextInput.gid,
|
|
2237
|
+
size: contextInput.size,
|
|
2238
|
+
});
|
|
2239
|
+
};
|
|
2240
|
+
const getContext = () => (context ??= new Context(getContextValues()));
|
|
2241
|
+
const getContextBytes = () => (contextBytes ??= encodeDocumentContextSuffix(getContext()));
|
|
2242
|
+
return this.createNativeDocumentAppendTransaction(input, appended, {
|
|
2243
|
+
getContext,
|
|
2244
|
+
getContextBytes,
|
|
2245
|
+
}, nativeBackboneDocumentIndex);
|
|
2246
|
+
}
|
|
2247
|
+
createNativeDocumentAppendTransaction(input, appended, contextAccessors, nativeBackboneDocumentIndex = input.nativeBackboneDocumentIndex) {
|
|
2248
|
+
const append = appended.appendCommit;
|
|
2249
|
+
let contextualEncodedValueParts;
|
|
2250
|
+
let exposedNativeBackboneDocumentIndex;
|
|
2251
|
+
let nativeBackboneDocumentIndexContextSet = false;
|
|
2252
|
+
const ensureNativeBackboneDocumentIndexContext = () => {
|
|
2253
|
+
if (nativeBackboneDocumentIndexContextSet ||
|
|
2254
|
+
!nativeBackboneDocumentIndex?.setContext) {
|
|
2255
|
+
return;
|
|
2256
|
+
}
|
|
2257
|
+
nativeBackboneDocumentIndex.setContext(contextAccessors.getContext());
|
|
2258
|
+
nativeBackboneDocumentIndexContextSet = true;
|
|
2259
|
+
};
|
|
2260
|
+
const getNativeBackboneDocumentIndex = () => {
|
|
2261
|
+
if (!nativeBackboneDocumentIndex) {
|
|
2262
|
+
return;
|
|
2263
|
+
}
|
|
2264
|
+
return (exposedNativeBackboneDocumentIndex ??= {
|
|
2265
|
+
valuePrefixBytes: nativeBackboneDocumentIndex.valuePrefixBytes,
|
|
2266
|
+
projection: nativeBackboneDocumentIndex.projection,
|
|
2267
|
+
indexable: nativeBackboneDocumentIndex.indexable,
|
|
2268
|
+
getIndexable: nativeBackboneDocumentIndex.getIndexable
|
|
2269
|
+
? () => {
|
|
2270
|
+
ensureNativeBackboneDocumentIndexContext();
|
|
2271
|
+
return nativeBackboneDocumentIndex.getIndexable();
|
|
2272
|
+
}
|
|
2273
|
+
: undefined,
|
|
2274
|
+
setContext: nativeBackboneDocumentIndex.setContext
|
|
2275
|
+
? (context) => {
|
|
2276
|
+
nativeBackboneDocumentIndex.setContext(context);
|
|
2277
|
+
nativeBackboneDocumentIndexContextSet = true;
|
|
2278
|
+
}
|
|
2279
|
+
: undefined,
|
|
2280
|
+
});
|
|
2281
|
+
};
|
|
2282
|
+
return {
|
|
2283
|
+
document: input.document,
|
|
2284
|
+
key: input.key,
|
|
2285
|
+
operation: input.operation,
|
|
2286
|
+
encodedDocument: input.documentBytes,
|
|
2287
|
+
operationPayloadBytes: input.operationPayloadBytes,
|
|
2288
|
+
get entry() {
|
|
2289
|
+
return appended.entry;
|
|
2290
|
+
},
|
|
2291
|
+
removed: appended.removed,
|
|
2292
|
+
removedHashes: appended.removedHashes,
|
|
2293
|
+
append,
|
|
2294
|
+
coordinateFields: append.coordinateFields,
|
|
2295
|
+
get context() {
|
|
2296
|
+
return contextAccessors.getContext();
|
|
2297
|
+
},
|
|
2298
|
+
get contextBytes() {
|
|
2299
|
+
return contextAccessors.getContextBytes();
|
|
2300
|
+
},
|
|
2301
|
+
get contextualEncodedValueParts() {
|
|
2302
|
+
return (contextualEncodedValueParts ??= {
|
|
2303
|
+
prefix: input.documentBytes,
|
|
2304
|
+
suffix: contextAccessors.getContextBytes(),
|
|
2305
|
+
});
|
|
2306
|
+
},
|
|
2307
|
+
nativeBackboneDocumentIndexCommitted: appended.appendCommit.nativeBackboneDocumentIndexCommitted,
|
|
2308
|
+
nativeBackboneDocumentIndexTrimmedHeadsProcessed: appended.appendCommit.nativeBackboneDocumentIndexTrimmedHeadsProcessed,
|
|
2309
|
+
get nativeBackboneDocumentIndex() {
|
|
2310
|
+
return getNativeBackboneDocumentIndex();
|
|
2311
|
+
},
|
|
2312
|
+
unique: input.unique,
|
|
2313
|
+
existing: input.existing,
|
|
2314
|
+
};
|
|
2315
|
+
}
|
|
2316
|
+
async createDocumentAppendCommitFactsBatch(rows) {
|
|
2317
|
+
const contextInputs = rows.map(({ input, appended }) => {
|
|
2318
|
+
const append = appended.appendCommit;
|
|
2319
|
+
const nativePreviousContext = append.documentPreviousContext == null
|
|
2320
|
+
? undefined
|
|
2321
|
+
: nativeDocumentContextFactsAsContext(append.documentPreviousContext);
|
|
2322
|
+
const nativePreviousIndexedContext = nativePreviousContext
|
|
2323
|
+
? {
|
|
2324
|
+
id: input.key,
|
|
2325
|
+
value: {
|
|
2326
|
+
__context: nativePreviousContext,
|
|
2327
|
+
},
|
|
2328
|
+
}
|
|
2329
|
+
: undefined;
|
|
2330
|
+
const inputWithExisting = input.existing === undefined && nativePreviousIndexedContext
|
|
2331
|
+
? {
|
|
2332
|
+
...input,
|
|
2333
|
+
existing: nativePreviousIndexedContext,
|
|
2334
|
+
}
|
|
2335
|
+
: input;
|
|
2336
|
+
const existing = inputWithExisting.unique || inputWithExisting.existing === null
|
|
2337
|
+
? null
|
|
2338
|
+
: inputWithExisting.existing;
|
|
2339
|
+
return {
|
|
2340
|
+
existingCreated: existing?.value.__context.created,
|
|
2341
|
+
modified: append.wallTime,
|
|
2342
|
+
head: append.hash,
|
|
2343
|
+
gid: append.gid,
|
|
2344
|
+
size: append.payloadSize,
|
|
2345
|
+
};
|
|
2346
|
+
});
|
|
2347
|
+
const contextPlans = tryPlanDocumentContextBatch(contextInputs) ??
|
|
2348
|
+
(await planDocumentContextBatch(contextInputs));
|
|
2349
|
+
return rows.map((row, index) => {
|
|
2350
|
+
const append = row.appended.appendCommit;
|
|
2351
|
+
const nativePreviousContext = append.documentPreviousContext == null
|
|
2352
|
+
? undefined
|
|
2353
|
+
: nativeDocumentContextFactsAsContext(append.documentPreviousContext);
|
|
2354
|
+
const nativePreviousIndexedContext = nativePreviousContext
|
|
2355
|
+
? {
|
|
2356
|
+
id: row.input.key,
|
|
2357
|
+
value: {
|
|
2358
|
+
__context: nativePreviousContext,
|
|
2359
|
+
},
|
|
2360
|
+
}
|
|
2361
|
+
: undefined;
|
|
2362
|
+
const input = row.input.existing === undefined && nativePreviousIndexedContext
|
|
2363
|
+
? {
|
|
2364
|
+
...row.input,
|
|
2365
|
+
existing: nativePreviousIndexedContext,
|
|
2366
|
+
}
|
|
2367
|
+
: row.input;
|
|
2368
|
+
const contextPlan = contextPlans?.[index];
|
|
2369
|
+
if (!contextPlan) {
|
|
2370
|
+
return this.createDocumentAppendCommitFactsWithLazyContext(input, row.appended, contextInputs[index], input.nativeBackboneDocumentIndex);
|
|
2371
|
+
}
|
|
2372
|
+
if (input.nativeBackboneDocumentIndex) {
|
|
2373
|
+
let context;
|
|
2374
|
+
return this.createNativeDocumentAppendTransaction(input, row.appended, {
|
|
2375
|
+
getContext: () => (context ??= new Context(contextPlan)),
|
|
2376
|
+
getContextBytes: () => contextPlan.contextBytes,
|
|
2377
|
+
}, input.nativeBackboneDocumentIndex);
|
|
2378
|
+
}
|
|
2379
|
+
return this.createDocumentAppendCommitFactsWithContext(input, row.appended, contextPlan);
|
|
2380
|
+
});
|
|
2381
|
+
}
|
|
2382
|
+
createDocumentAppendCommitFactsWithContext(input, appended, contextPlan, preparedNativeBackboneDocumentIndex = input.nativeBackboneDocumentIndex) {
|
|
2383
|
+
const append = appended.appendCommit;
|
|
2384
|
+
const context = new Context(contextPlan);
|
|
2385
|
+
const nativeBackboneDocumentIndex = preparedNativeBackboneDocumentIndex ??
|
|
2386
|
+
(append.nativeBackboneDocumentIndexCommitted
|
|
2387
|
+
? undefined
|
|
2388
|
+
: this._nativeBackboneDocumentIndexEnabled
|
|
2389
|
+
? asTrustedDocumentIndex(this._index).prepareNativeBackboneDocumentIndexCommitWithAppendFacts(input.document, input.documentBytes, context, { entryPublicKeys: [this.log.log.identity.publicKey] })
|
|
2390
|
+
: undefined);
|
|
2391
|
+
return this.createNativeDocumentAppendTransaction(input, appended, {
|
|
2392
|
+
getContext: () => context,
|
|
2393
|
+
getContextBytes: () => contextPlan.contextBytes,
|
|
2394
|
+
}, nativeBackboneDocumentIndex);
|
|
2395
|
+
}
|
|
2396
|
+
hasDocumentChangeConsumers() {
|
|
2397
|
+
if (this._valueClassIsProgram === true) {
|
|
2398
|
+
// Program-valued documents must always materialize removed values so
|
|
2399
|
+
// that open subprograms are dropped on delete, even without listeners.
|
|
2400
|
+
return true;
|
|
2401
|
+
}
|
|
2402
|
+
const changeListenerCount = this._documentChangeListenerCount ?? 0;
|
|
2403
|
+
const internalChangeListenerCount = this._documentInternalChangeListenerCount ?? 0;
|
|
2404
|
+
return (changeListenerCount > internalChangeListenerCount ||
|
|
2405
|
+
this._index.hasPending === true);
|
|
2406
|
+
}
|
|
2407
|
+
dispatchDocumentChangeIfObserved(documentsChanged) {
|
|
2408
|
+
if (!this.hasDocumentChangeConsumers()) {
|
|
2409
|
+
return;
|
|
2410
|
+
}
|
|
2411
|
+
this.events.dispatchEvent(new CustomEvent("change", { detail: documentsChanged }));
|
|
2412
|
+
}
|
|
2413
|
+
nextFromIndexedContext(existingHead, existing) {
|
|
2414
|
+
const context = existing?.value.__context;
|
|
2415
|
+
if (!context || context.head !== existingHead) {
|
|
2416
|
+
return;
|
|
2417
|
+
}
|
|
2418
|
+
return new ShallowEntry({
|
|
2419
|
+
hash: context.head,
|
|
2420
|
+
head: false,
|
|
2421
|
+
payloadSize: context.size,
|
|
2422
|
+
meta: new ShallowMeta({
|
|
2423
|
+
gid: context.gid,
|
|
2424
|
+
clock: new LamportClock({
|
|
2425
|
+
id: this.log.log.identity.publicKey.bytes,
|
|
2426
|
+
timestamp: new Timestamp({
|
|
2427
|
+
wallTime: context.modified,
|
|
2428
|
+
logical: 0,
|
|
2429
|
+
}),
|
|
2430
|
+
}),
|
|
2431
|
+
next: [],
|
|
2432
|
+
type: EntryType.APPEND,
|
|
2433
|
+
}),
|
|
2434
|
+
});
|
|
2435
|
+
}
|
|
2436
|
+
handlePreparedPlainPutCommit(commit) {
|
|
2437
|
+
const shouldPrepareChange = this.hasDocumentChangeConsumers();
|
|
2438
|
+
const removedAlreadyHandled = commit.nativeBackboneDocumentIndexTrimmedHeadsProcessed === true;
|
|
2439
|
+
const removedHashes = commit.removedHashes ?? [];
|
|
2440
|
+
const hasRemovedFacts = commit.removed.length > 0 || removedHashes.length > 0;
|
|
2441
|
+
const existing = commit.unique || commit.existing === null ? null : commit.existing;
|
|
2442
|
+
const persistNativeBackboneDocumentIndexCommit = () => {
|
|
2443
|
+
if (!commit.nativeBackboneDocumentIndexCommitted) {
|
|
2444
|
+
return;
|
|
2445
|
+
}
|
|
2446
|
+
if (this._mode === "native") {
|
|
2447
|
+
return true;
|
|
2448
|
+
}
|
|
2449
|
+
return asTrustedDocumentIndex(this._index)._persistPreparedNativeBackboneDocumentIndexStoredWithContext(commit.key, commit.context, commit.nativeBackboneDocumentIndex, commit.contextualEncodedValueParts, {
|
|
2450
|
+
replace: existing != null,
|
|
2451
|
+
});
|
|
2452
|
+
};
|
|
2453
|
+
if (!shouldPrepareChange &&
|
|
2454
|
+
(!hasRemovedFacts || removedAlreadyHandled) &&
|
|
2455
|
+
commit.nativeBackboneDocumentIndexCommitted) {
|
|
2456
|
+
if (!this.strictHistory && existing) {
|
|
2457
|
+
const shouldIgnoreChange = this.immutable
|
|
2458
|
+
? existing.value.__context.modified < commit.append.wallTime
|
|
2459
|
+
: existing.value.__context.modified > commit.append.wallTime;
|
|
2460
|
+
if (shouldIgnoreChange) {
|
|
2461
|
+
return;
|
|
2462
|
+
}
|
|
2463
|
+
}
|
|
2464
|
+
const finishCommitted = () => {
|
|
2465
|
+
this._index._cacheResolvedIdentityValue(commit.key.primitive, commit.document);
|
|
2466
|
+
};
|
|
2467
|
+
const persisted = persistNativeBackboneDocumentIndexCommit();
|
|
2468
|
+
return persisted === undefined || persisted === false
|
|
2469
|
+
? finishCommitted()
|
|
2470
|
+
: mapMaybePromise(persisted, finishCommitted);
|
|
2471
|
+
}
|
|
2472
|
+
const documentsChanged = shouldPrepareChange
|
|
2473
|
+
? {
|
|
2474
|
+
added: [],
|
|
2475
|
+
removed: [],
|
|
2476
|
+
}
|
|
2477
|
+
: undefined;
|
|
2478
|
+
const modified = new Set();
|
|
2479
|
+
if (!this.strictHistory && existing) {
|
|
2480
|
+
const shouldIgnoreChange = this.immutable
|
|
2481
|
+
? existing.value.__context.modified < commit.append.wallTime
|
|
2482
|
+
: existing.value.__context.modified > commit.append.wallTime;
|
|
2483
|
+
if (shouldIgnoreChange) {
|
|
2484
|
+
modified.add(commit.key.primitive);
|
|
2485
|
+
}
|
|
2486
|
+
}
|
|
2487
|
+
const finishRemoved = () => {
|
|
2488
|
+
if (!shouldPrepareChange) {
|
|
2489
|
+
if (!hasRemovedFacts || removedAlreadyHandled) {
|
|
2490
|
+
return undefined;
|
|
2491
|
+
}
|
|
2492
|
+
if (commit.removed.length === 0 && removedHashes.length > 0) {
|
|
2493
|
+
const handled = this.tryHandlePreparedPlainPutCommitRemovedHashesFromHeads(removedHashes, modified);
|
|
2494
|
+
if (handled !== undefined) {
|
|
2495
|
+
return mapMaybePromise(handled, (handledHeads) => {
|
|
2496
|
+
if (handledHeads.size === removedHashes.length) {
|
|
2497
|
+
return undefined;
|
|
2498
|
+
}
|
|
2499
|
+
const remaining = removedHashes.filter((hash) => !handledHeads.has(hash));
|
|
2500
|
+
return remaining.length === 0
|
|
2501
|
+
? undefined
|
|
2502
|
+
: this.handlePreparedPlainPutCommitRemovedHashes(remaining, modified);
|
|
2503
|
+
});
|
|
2504
|
+
}
|
|
2505
|
+
return this.handlePreparedPlainPutCommitRemovedHashes(removedHashes, modified);
|
|
2506
|
+
}
|
|
2507
|
+
const handled = this.tryHandlePreparedPlainPutCommitRemovedFromHeads(commit.removed, modified);
|
|
2508
|
+
if (handled !== undefined) {
|
|
2509
|
+
return mapMaybePromise(handled, (handledHeads) => {
|
|
2510
|
+
if (handledHeads.size === commit.removed.length) {
|
|
2511
|
+
return undefined;
|
|
2512
|
+
}
|
|
2513
|
+
const remaining = commit.removed.filter((entry) => !handledHeads.has(entry.hash));
|
|
2514
|
+
return remaining.length === 0
|
|
2515
|
+
? undefined
|
|
2516
|
+
: this.handlePreparedPlainPutCommitRemoved(remaining, modified);
|
|
2517
|
+
});
|
|
2518
|
+
}
|
|
2519
|
+
return this.handlePreparedPlainPutCommitRemoved(commit.removed, modified);
|
|
2520
|
+
}
|
|
2521
|
+
if (commit.removed.length === 0) {
|
|
2522
|
+
if (removedHashes.length > 0) {
|
|
2523
|
+
return this.handlePreparedPlainPutCommitRemovedHashes(removedHashes, modified, documentsChanged);
|
|
2524
|
+
}
|
|
2525
|
+
this.dispatchDocumentChangeIfObserved(documentsChanged);
|
|
2526
|
+
return;
|
|
2527
|
+
}
|
|
2528
|
+
return this.handlePreparedPlainPutCommitRemoved(commit.removed, modified, documentsChanged);
|
|
2529
|
+
};
|
|
2530
|
+
const finishIndexed = (indexedDocument) => {
|
|
2531
|
+
if (indexedDocument) {
|
|
2532
|
+
if (documentsChanged) {
|
|
2533
|
+
documentsChanged.added.push(indexedDocument);
|
|
2534
|
+
}
|
|
2535
|
+
modified.add(commit.key.primitive);
|
|
2536
|
+
return finishRemoved();
|
|
2537
|
+
}
|
|
2538
|
+
return mapMaybePromise(this._index.putWithContext(commit.document, commit.key, commit.context, {
|
|
2539
|
+
replace: existing != null,
|
|
2540
|
+
encodedValueParts: commit.contextualEncodedValueParts,
|
|
2541
|
+
transformFacts: { entryPublicKeys: commit.entry.publicKeys },
|
|
2542
|
+
}), ({ indexable }) => {
|
|
2543
|
+
if (documentsChanged) {
|
|
2544
|
+
documentsChanged.added.push(coerceWithIndexed(coerceWithContext(commit.document, commit.context), indexable));
|
|
2545
|
+
}
|
|
2546
|
+
modified.add(commit.key.primitive);
|
|
2547
|
+
return finishRemoved();
|
|
2548
|
+
});
|
|
2549
|
+
};
|
|
2550
|
+
if (!modified.has(commit.key.primitive)) {
|
|
2551
|
+
if (commit.nativeBackboneDocumentIndexCommitted) {
|
|
2552
|
+
const finishCommitted = () => {
|
|
2553
|
+
this._index._cacheResolvedIdentityValue(commit.key.primitive, commit.document);
|
|
2554
|
+
if (!shouldPrepareChange) {
|
|
2555
|
+
modified.add(commit.key.primitive);
|
|
2556
|
+
return finishRemoved();
|
|
2557
|
+
}
|
|
2558
|
+
const withContext = coerceWithContext(commit.document, commit.context);
|
|
2559
|
+
if (commit.nativeBackboneDocumentIndex?.indexable) {
|
|
2560
|
+
return finishIndexed(coerceWithIndexed(withContext, commit.nativeBackboneDocumentIndex.indexable));
|
|
2561
|
+
}
|
|
2562
|
+
if (commit.nativeBackboneDocumentIndex?.getIndexable) {
|
|
2563
|
+
return finishIndexed(coerceWithLazyIndexed(withContext, commit.nativeBackboneDocumentIndex.getIndexable));
|
|
2564
|
+
}
|
|
2565
|
+
return finishIndexed(coerceWithIndexed(withContext, commit.document));
|
|
2566
|
+
};
|
|
2567
|
+
const persisted = persistNativeBackboneDocumentIndexCommit();
|
|
2568
|
+
return persisted === undefined || persisted === false
|
|
2569
|
+
? finishCommitted()
|
|
2570
|
+
: mapMaybePromise(persisted, finishCommitted);
|
|
2571
|
+
}
|
|
2572
|
+
if (commit.nativeBackboneDocumentIndex) {
|
|
2573
|
+
const nativePreparedIndexPut = asTrustedDocumentIndex(this._index)._putPreparedNativeBackboneDocumentIndexWithContext(commit.document, commit.key, commit.context, commit.nativeBackboneDocumentIndex, {
|
|
2574
|
+
replace: existing != null,
|
|
2575
|
+
});
|
|
2576
|
+
if (nativePreparedIndexPut !== undefined) {
|
|
2577
|
+
return mapMaybePromise(nativePreparedIndexPut, finishIndexed);
|
|
2578
|
+
}
|
|
2579
|
+
}
|
|
2580
|
+
const storedIdentityPut = this._index._putStoredIdentityWithContext(commit.document, commit.key, commit.context, commit.contextualEncodedValueParts, {
|
|
2581
|
+
replace: existing != null,
|
|
2582
|
+
});
|
|
2583
|
+
if (storedIdentityPut !== undefined) {
|
|
2584
|
+
return mapMaybePromise(storedIdentityPut, finishIndexed);
|
|
2585
|
+
}
|
|
2586
|
+
return mapMaybePromise(this._index._putIdentityWithContext(commit.document, commit.key, commit.context, {
|
|
2587
|
+
replace: existing != null,
|
|
2588
|
+
encodedValueParts: commit.contextualEncodedValueParts,
|
|
2589
|
+
transformFacts: { entryPublicKeys: commit.entry.publicKeys },
|
|
2590
|
+
}), finishIndexed);
|
|
2591
|
+
}
|
|
2592
|
+
return finishRemoved();
|
|
2593
|
+
}
|
|
2594
|
+
tryHandlePreparedPlainPutCommitRemovedFromHeads(removedEntries, modified) {
|
|
2595
|
+
const handled = new Set();
|
|
2596
|
+
const deleteKeys = [];
|
|
2597
|
+
for (const removed of removedEntries) {
|
|
2598
|
+
if (removed instanceof Entry) {
|
|
2599
|
+
continue;
|
|
2600
|
+
}
|
|
2601
|
+
const resolved = this._index.tryGetIdentityIndexedKeyByHead(removed.hash);
|
|
2602
|
+
if (!resolved.supported) {
|
|
2603
|
+
return;
|
|
2604
|
+
}
|
|
2605
|
+
if (!resolved.key) {
|
|
2606
|
+
continue;
|
|
2607
|
+
}
|
|
2608
|
+
handled.add(removed.hash);
|
|
2609
|
+
if (modified.has(resolved.key.primitive)) {
|
|
2610
|
+
continue;
|
|
2611
|
+
}
|
|
2612
|
+
deleteKeys.push(resolved.key);
|
|
2613
|
+
modified.add(resolved.key.primitive);
|
|
2614
|
+
}
|
|
2615
|
+
if (deleteKeys.length === 0) {
|
|
2616
|
+
return handled;
|
|
2617
|
+
}
|
|
2618
|
+
return mapMaybePromise(this._index.delManyMaybe(deleteKeys), () => handled);
|
|
2619
|
+
}
|
|
2620
|
+
tryHandlePreparedPlainPutCommitRemovedHashesFromHeads(removedHashes, modified) {
|
|
2621
|
+
const handled = new Set();
|
|
2622
|
+
const deleteKeys = [];
|
|
2623
|
+
for (const hash of removedHashes) {
|
|
2624
|
+
const resolved = this._index.tryGetIdentityIndexedKeyByHead(hash);
|
|
2625
|
+
if (!resolved.supported) {
|
|
2626
|
+
return;
|
|
2627
|
+
}
|
|
2628
|
+
if (!resolved.key) {
|
|
2629
|
+
continue;
|
|
2630
|
+
}
|
|
2631
|
+
handled.add(hash);
|
|
2632
|
+
if (modified.has(resolved.key.primitive)) {
|
|
2633
|
+
continue;
|
|
2634
|
+
}
|
|
2635
|
+
deleteKeys.push(resolved.key);
|
|
2636
|
+
modified.add(resolved.key.primitive);
|
|
2637
|
+
}
|
|
2638
|
+
if (deleteKeys.length === 0) {
|
|
2639
|
+
return handled;
|
|
2640
|
+
}
|
|
2641
|
+
return mapMaybePromise(this._index.delManyMaybe(deleteKeys), () => handled);
|
|
2642
|
+
}
|
|
2643
|
+
async handlePreparedPlainPutCommitRemoved(removedEntries, modified, documentsChanged) {
|
|
2644
|
+
const handledRemovedHeads = await this.collectRemovedDocumentChangesFromIndexedHeads(removedEntries, modified, documentsChanged);
|
|
2645
|
+
for (const removed of removedEntries) {
|
|
2646
|
+
if (handledRemovedHeads.has(removed.hash)) {
|
|
2647
|
+
continue;
|
|
2648
|
+
}
|
|
2649
|
+
if (!(removed instanceof Entry) &&
|
|
2650
|
+
(await this.collectRemovedDocumentChangeFromIndexedHead(removed.hash, modified, documentsChanged))) {
|
|
2651
|
+
continue;
|
|
2652
|
+
}
|
|
2653
|
+
const entry = removed instanceof Entry
|
|
2654
|
+
? removed
|
|
2655
|
+
: await this.log.log.entryIndex.get(removed.hash, {
|
|
2656
|
+
type: "full",
|
|
2657
|
+
ignoreMissing: true,
|
|
2658
|
+
});
|
|
2659
|
+
if (!entry) {
|
|
2660
|
+
continue;
|
|
2661
|
+
}
|
|
2662
|
+
try {
|
|
2663
|
+
const payload = await this.getAppendOperation(entry);
|
|
2664
|
+
if (!payload) {
|
|
2665
|
+
continue;
|
|
2666
|
+
}
|
|
2667
|
+
await this.collectRemovedDocumentChange(payload, modified, documentsChanged);
|
|
2668
|
+
}
|
|
2669
|
+
catch (error) {
|
|
2670
|
+
if (error instanceof AccessError) {
|
|
2671
|
+
continue;
|
|
2672
|
+
}
|
|
2673
|
+
throw error;
|
|
2674
|
+
}
|
|
2675
|
+
}
|
|
2676
|
+
if (documentsChanged) {
|
|
2677
|
+
this.dispatchDocumentChangeIfObserved(documentsChanged);
|
|
2678
|
+
}
|
|
2679
|
+
}
|
|
2680
|
+
async handlePreparedPlainPutCommitRemovedHashes(removedHashes, modified, documentsChanged) {
|
|
2681
|
+
const handledRemovedHeads = await this.collectRemovedDocumentChangesFromIndexedHeadHashes(removedHashes, modified, documentsChanged);
|
|
2682
|
+
for (const hash of removedHashes) {
|
|
2683
|
+
if (handledRemovedHeads.has(hash)) {
|
|
2684
|
+
continue;
|
|
2685
|
+
}
|
|
2686
|
+
const entry = await this.log.log.entryIndex.get(hash, {
|
|
2687
|
+
type: "full",
|
|
2688
|
+
ignoreMissing: true,
|
|
2689
|
+
});
|
|
2690
|
+
if (!entry) {
|
|
2691
|
+
continue;
|
|
2692
|
+
}
|
|
2693
|
+
try {
|
|
2694
|
+
const payload = await this.getAppendOperation(entry);
|
|
2695
|
+
if (!payload) {
|
|
2696
|
+
continue;
|
|
2697
|
+
}
|
|
2698
|
+
await this.collectRemovedDocumentChange(payload, modified, documentsChanged);
|
|
2699
|
+
}
|
|
2700
|
+
catch (error) {
|
|
2701
|
+
if (error instanceof AccessError) {
|
|
2702
|
+
continue;
|
|
2703
|
+
}
|
|
2704
|
+
throw error;
|
|
2705
|
+
}
|
|
2706
|
+
}
|
|
2707
|
+
if (documentsChanged) {
|
|
2708
|
+
this.dispatchDocumentChangeIfObserved(documentsChanged);
|
|
2709
|
+
}
|
|
2710
|
+
}
|
|
2711
|
+
async handlePreparedPlainPutManyCommit(commit) {
|
|
2712
|
+
if (!this.hasDocumentChangeConsumers() &&
|
|
2713
|
+
commit.removed.length === 0) {
|
|
2714
|
+
const stored = await asTrustedDocumentIndex(this._index)._putManyPreparedNativeBackboneDocumentIndexStored(commit.commits.map((put) => {
|
|
2715
|
+
const existing = put.unique || put.existing === null ? null : put.existing;
|
|
2716
|
+
return {
|
|
2717
|
+
value: put.document,
|
|
2718
|
+
id: put.key,
|
|
2719
|
+
context: put.context,
|
|
2720
|
+
encodedValueParts: put.contextualEncodedValueParts,
|
|
2721
|
+
nativeDocumentIndex: put.nativeBackboneDocumentIndex,
|
|
2722
|
+
options: {
|
|
2723
|
+
replace: existing != null,
|
|
2724
|
+
},
|
|
2725
|
+
};
|
|
2726
|
+
}));
|
|
2727
|
+
if (stored === true) {
|
|
2728
|
+
return;
|
|
2729
|
+
}
|
|
2730
|
+
}
|
|
2731
|
+
const documentsChanged = {
|
|
2732
|
+
added: [],
|
|
2733
|
+
removed: [],
|
|
2734
|
+
};
|
|
2735
|
+
const modified = new Set();
|
|
2736
|
+
const putsToIndex = [];
|
|
2737
|
+
for (const put of commit.commits) {
|
|
2738
|
+
if (modified.has(put.key.primitive)) {
|
|
2739
|
+
continue;
|
|
2740
|
+
}
|
|
2741
|
+
const existing = put.unique || put.existing === null ? null : put.existing;
|
|
2742
|
+
putsToIndex.push({
|
|
2743
|
+
document: put.document,
|
|
2744
|
+
encodedDocument: put.encodedDocument,
|
|
2745
|
+
key: put.key,
|
|
2746
|
+
context: put.context,
|
|
2747
|
+
contextualEncodedValueParts: put.contextualEncodedValueParts,
|
|
2748
|
+
nativeBackboneDocumentIndex: put.nativeBackboneDocumentIndex,
|
|
2749
|
+
replace: existing != null,
|
|
2750
|
+
});
|
|
2751
|
+
modified.add(put.key.primitive);
|
|
2752
|
+
}
|
|
2753
|
+
let indexedDocuments = await this._index._putManyIdentityWithContext(putsToIndex.map((put) => ({
|
|
2754
|
+
value: put.document,
|
|
2755
|
+
id: put.key,
|
|
2756
|
+
context: put.context,
|
|
2757
|
+
options: {
|
|
2758
|
+
replace: put.replace,
|
|
2759
|
+
encodedValueParts: put.contextualEncodedValueParts,
|
|
2760
|
+
},
|
|
2761
|
+
})));
|
|
2762
|
+
indexedDocuments ??=
|
|
2763
|
+
await asTrustedDocumentIndex(this._index)._putManyPreparedNativeBackboneDocumentIndexWithContext(putsToIndex.map((put) => ({
|
|
2764
|
+
value: put.document,
|
|
2765
|
+
id: put.key,
|
|
2766
|
+
context: put.context,
|
|
2767
|
+
nativeDocumentIndex: put.nativeBackboneDocumentIndex,
|
|
2768
|
+
options: {
|
|
2769
|
+
replace: put.replace,
|
|
2770
|
+
},
|
|
2771
|
+
})));
|
|
2772
|
+
if (indexedDocuments) {
|
|
2773
|
+
documentsChanged.added.push(...indexedDocuments);
|
|
2774
|
+
}
|
|
2775
|
+
else {
|
|
2776
|
+
if (this.isNativeMode()) {
|
|
2777
|
+
throw this.nativeModeError("requires native batch document-index commit");
|
|
2778
|
+
}
|
|
2779
|
+
const indexed = await this._index.putManyWithContext(putsToIndex.map((put) => ({
|
|
2780
|
+
value: put.document,
|
|
2781
|
+
id: put.key,
|
|
2782
|
+
context: put.context,
|
|
2783
|
+
options: {
|
|
2784
|
+
replace: put.replace,
|
|
2785
|
+
encodedValueParts: put.contextualEncodedValueParts,
|
|
2786
|
+
},
|
|
2787
|
+
})));
|
|
2788
|
+
for (let i = 0; i < putsToIndex.length; i++) {
|
|
2789
|
+
const put = putsToIndex[i];
|
|
2790
|
+
const { indexable } = indexed[i];
|
|
2791
|
+
documentsChanged.added.push(coerceWithIndexed(coerceWithContext(put.document, put.context), indexable));
|
|
2792
|
+
}
|
|
2793
|
+
}
|
|
2794
|
+
const handledRemovedHeads = await this.collectRemovedDocumentChangesFromIndexedHeads(commit.removed, modified, documentsChanged);
|
|
2795
|
+
for (const removed of commit.removed) {
|
|
2796
|
+
if (handledRemovedHeads.has(removed.hash)) {
|
|
2797
|
+
continue;
|
|
2798
|
+
}
|
|
2799
|
+
if (!(removed instanceof Entry) &&
|
|
2800
|
+
(await this.collectRemovedDocumentChangeFromIndexedHead(removed.hash, modified, documentsChanged))) {
|
|
2801
|
+
continue;
|
|
2802
|
+
}
|
|
2803
|
+
const entry = removed instanceof Entry
|
|
2804
|
+
? removed
|
|
2805
|
+
: await this.log.log.entryIndex.get(removed.hash, {
|
|
2806
|
+
type: "full",
|
|
2807
|
+
ignoreMissing: true,
|
|
2808
|
+
});
|
|
2809
|
+
if (!entry) {
|
|
2810
|
+
continue;
|
|
2811
|
+
}
|
|
2812
|
+
try {
|
|
2813
|
+
const payload = await this.getAppendOperation(entry);
|
|
2814
|
+
if (!payload) {
|
|
2815
|
+
continue;
|
|
2816
|
+
}
|
|
2817
|
+
await this.collectRemovedDocumentChange(payload, modified, documentsChanged);
|
|
2818
|
+
}
|
|
2819
|
+
catch (error) {
|
|
2820
|
+
if (error instanceof AccessError) {
|
|
2821
|
+
continue;
|
|
2822
|
+
}
|
|
2823
|
+
throw error;
|
|
289
2824
|
}
|
|
290
|
-
throw error;
|
|
291
2825
|
}
|
|
292
|
-
|
|
2826
|
+
this.events.dispatchEvent(new CustomEvent("change", { detail: documentsChanged }));
|
|
293
2827
|
}
|
|
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
|
|
2828
|
+
async collectRemovedDocumentChangeFromIndexedHead(head, modified, documentsChanged) {
|
|
2829
|
+
const key = await this._index.getIdentityIndexedKeyByHead(head);
|
|
2830
|
+
if (key) {
|
|
2831
|
+
if (await this.collectRemovedDocumentChangeFromIndexedKey(key, modified, documentsChanged)) {
|
|
2832
|
+
return true;
|
|
312
2833
|
}
|
|
2834
|
+
}
|
|
2835
|
+
if (!documentsChanged) {
|
|
313
2836
|
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,
|
|
2837
|
+
}
|
|
2838
|
+
const indexed = await this._index.getIdentityIndexedByHead(head);
|
|
2839
|
+
if (!indexed) {
|
|
2840
|
+
return false;
|
|
2841
|
+
}
|
|
2842
|
+
return this.collectRemovedDocumentChangeFromIndexedKey(indexed.id, modified, documentsChanged, () => coerceWithIndexed(indexed.value, indexed.value));
|
|
2843
|
+
}
|
|
2844
|
+
async collectRemovedDocumentChangeFromIndexedKey(key, modified, documentsChanged, valueProvider) {
|
|
2845
|
+
if (modified.has(key.primitive)) {
|
|
2846
|
+
return true;
|
|
2847
|
+
}
|
|
2848
|
+
let value;
|
|
2849
|
+
if (documentsChanged) {
|
|
2850
|
+
value =
|
|
2851
|
+
valueProvider?.() ??
|
|
2852
|
+
(await this._index.get(key, {
|
|
334
2853
|
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
|
-
}
|
|
2854
|
+
remote: false,
|
|
2855
|
+
}));
|
|
2856
|
+
if (!value) {
|
|
2857
|
+
return false;
|
|
2858
|
+
}
|
|
2859
|
+
documentsChanged.removed.push(value);
|
|
2860
|
+
}
|
|
2861
|
+
if (value instanceof Program &&
|
|
2862
|
+
value.closed !== true &&
|
|
2863
|
+
value.parents.includes(this)) {
|
|
2864
|
+
await value.drop(this);
|
|
2865
|
+
}
|
|
2866
|
+
await this._index.delMany([key]);
|
|
2867
|
+
modified.add(key.primitive);
|
|
2868
|
+
return true;
|
|
2869
|
+
}
|
|
2870
|
+
async collectRemovedDocumentChangesFromIndexedHeads(removed, modified, documentsChanged) {
|
|
2871
|
+
return this.collectRemovedDocumentChangesFromIndexedHeadHashes(removed.map((entry) => entry.hash), modified, documentsChanged);
|
|
2872
|
+
}
|
|
2873
|
+
async collectRemovedDocumentChangesFromIndexedHeadHashes(removedHashes, modified, documentsChanged) {
|
|
2874
|
+
if (removedHashes.length === 0) {
|
|
2875
|
+
return new Set();
|
|
2876
|
+
}
|
|
2877
|
+
if (!documentsChanged) {
|
|
2878
|
+
const handled = new Set();
|
|
2879
|
+
const deleteKeys = [];
|
|
2880
|
+
for (const hash of removedHashes) {
|
|
2881
|
+
const key = await this._index.getIdentityIndexedKeyByHead(hash);
|
|
2882
|
+
if (!key) {
|
|
2883
|
+
continue;
|
|
371
2884
|
}
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
// for now the default behaviour will allow us to build document dependencies
|
|
2885
|
+
handled.add(hash);
|
|
2886
|
+
if (modified.has(key.primitive)) {
|
|
2887
|
+
continue;
|
|
376
2888
|
}
|
|
2889
|
+
deleteKeys.push(key);
|
|
2890
|
+
modified.add(key.primitive);
|
|
377
2891
|
}
|
|
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
|
|
2892
|
+
await this._index.delMany(deleteKeys);
|
|
2893
|
+
return handled;
|
|
2894
|
+
}
|
|
2895
|
+
const keyByHead = this._index.getIndexedKeysByHeads(removedHashes);
|
|
2896
|
+
if (keyByHead) {
|
|
2897
|
+
const handled = new Set();
|
|
2898
|
+
const deleteKeys = [];
|
|
2899
|
+
for (let i = 0; i < removedHashes.length; i++) {
|
|
2900
|
+
const key = keyByHead[i];
|
|
2901
|
+
if (!key) {
|
|
2902
|
+
continue;
|
|
395
2903
|
}
|
|
396
|
-
|
|
397
|
-
if (
|
|
398
|
-
|
|
399
|
-
return false;
|
|
2904
|
+
handled.add(removedHashes[i]);
|
|
2905
|
+
if (modified.has(key.primitive)) {
|
|
2906
|
+
continue;
|
|
400
2907
|
}
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
2908
|
+
const value = await this._index.get(key, {
|
|
2909
|
+
local: true,
|
|
2910
|
+
remote: false,
|
|
2911
|
+
});
|
|
2912
|
+
if (!value) {
|
|
2913
|
+
handled.delete(removedHashes[i]);
|
|
2914
|
+
continue;
|
|
404
2915
|
}
|
|
405
|
-
|
|
2916
|
+
documentsChanged.removed.push(value);
|
|
2917
|
+
deleteKeys.push(key);
|
|
2918
|
+
modified.add(key.primitive);
|
|
406
2919
|
}
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
}
|
|
410
|
-
return operation;
|
|
2920
|
+
await this._index.delMany(deleteKeys);
|
|
2921
|
+
return handled;
|
|
411
2922
|
}
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
2923
|
+
const indexedByHead = await this._index.getIdentityIndexedByHeads(removedHashes);
|
|
2924
|
+
if (!indexedByHead) {
|
|
2925
|
+
return new Set();
|
|
2926
|
+
}
|
|
2927
|
+
const handled = new Set();
|
|
2928
|
+
const deleteKeys = [];
|
|
2929
|
+
for (let i = 0; i < removedHashes.length; i++) {
|
|
2930
|
+
const indexed = indexedByHead[i];
|
|
2931
|
+
if (!indexed) {
|
|
2932
|
+
continue;
|
|
415
2933
|
}
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
2934
|
+
const key = indexed.id;
|
|
2935
|
+
handled.add(removedHashes[i]);
|
|
2936
|
+
if (modified.has(key.primitive)) {
|
|
2937
|
+
continue;
|
|
419
2938
|
}
|
|
420
|
-
|
|
2939
|
+
if (documentsChanged) {
|
|
2940
|
+
const value = coerceWithIndexed(indexed.value, indexed.value);
|
|
2941
|
+
documentsChanged.removed.push(value);
|
|
2942
|
+
}
|
|
2943
|
+
deleteKeys.push(key);
|
|
2944
|
+
modified.add(key.primitive);
|
|
421
2945
|
}
|
|
2946
|
+
await this._index.delMany(deleteKeys);
|
|
2947
|
+
return handled;
|
|
422
2948
|
}
|
|
423
|
-
async
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
2949
|
+
async collectRemovedDocumentChange(payload, modified, documentsChanged) {
|
|
2950
|
+
let value;
|
|
2951
|
+
let key;
|
|
2952
|
+
if (isPutOperation(payload)) {
|
|
2953
|
+
const keyValue = this.isNativeMode()
|
|
2954
|
+
? await this.getNativeDocumentIdFromPutOperation(payload)
|
|
2955
|
+
: undefined;
|
|
2956
|
+
if (this.isNativeMode() && keyValue == null) {
|
|
2957
|
+
throw this.nativeModeError("requires native document id extraction for removed put");
|
|
2958
|
+
}
|
|
2959
|
+
key = indexerTypes.toId(keyValue ??
|
|
2960
|
+
this.idResolver(this.index.valueEncoding.decoder(payload.data)));
|
|
2961
|
+
if (modified.has(key.primitive)) {
|
|
2962
|
+
return;
|
|
2963
|
+
}
|
|
2964
|
+
if (documentsChanged) {
|
|
2965
|
+
const document = await this._index.get(key, {
|
|
436
2966
|
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,
|
|
2967
|
+
remote: false,
|
|
447
2968
|
});
|
|
2969
|
+
if (!document) {
|
|
2970
|
+
return;
|
|
2971
|
+
}
|
|
2972
|
+
value = document;
|
|
448
2973
|
}
|
|
449
|
-
|
|
450
|
-
|
|
2974
|
+
}
|
|
2975
|
+
else if (isDeleteOperation(payload)) {
|
|
2976
|
+
key = coerceDeleteOperation(payload).key;
|
|
2977
|
+
if (modified.has(key.primitive)) {
|
|
2978
|
+
return;
|
|
2979
|
+
}
|
|
2980
|
+
if (documentsChanged) {
|
|
2981
|
+
const document = await this._index.get(key, {
|
|
2982
|
+
local: true,
|
|
2983
|
+
remote: false,
|
|
2984
|
+
});
|
|
2985
|
+
if (!document) {
|
|
2986
|
+
return;
|
|
2987
|
+
}
|
|
2988
|
+
value = document;
|
|
451
2989
|
}
|
|
452
2990
|
}
|
|
453
2991
|
else {
|
|
454
|
-
|
|
455
|
-
data: ser,
|
|
456
|
-
});
|
|
2992
|
+
throw new Error("Unexpected");
|
|
457
2993
|
}
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
2994
|
+
if (documentsChanged && value) {
|
|
2995
|
+
documentsChanged.removed.push(value);
|
|
2996
|
+
}
|
|
2997
|
+
if (value instanceof Program &&
|
|
2998
|
+
value.closed !== true &&
|
|
2999
|
+
value.parents.includes(this)) {
|
|
3000
|
+
await value.drop(this);
|
|
3001
|
+
}
|
|
3002
|
+
await this._index.delMany([key]);
|
|
3003
|
+
modified.add(key.primitive);
|
|
3004
|
+
}
|
|
3005
|
+
async collectRemovedPutChangeFromNativeId(payload, modified) {
|
|
3006
|
+
if (!this.isNativeMode() || !isPutOperation(payload)) {
|
|
3007
|
+
return false;
|
|
3008
|
+
}
|
|
3009
|
+
const keyValue = await this.getNativeDocumentIdFromPutOperation(payload);
|
|
3010
|
+
if (keyValue == null) {
|
|
3011
|
+
throw this.nativeModeError("requires native document id extraction for removed put");
|
|
3012
|
+
}
|
|
3013
|
+
const key = indexerTypes.toId(keyValue);
|
|
3014
|
+
if (modified.has(key.primitive)) {
|
|
3015
|
+
return true;
|
|
3016
|
+
}
|
|
3017
|
+
await this._index.delMany([key]);
|
|
3018
|
+
modified.add(key.primitive);
|
|
3019
|
+
return true;
|
|
3020
|
+
}
|
|
3021
|
+
putStrictNativeReceivedDocumentIndexWithContext(value, key, entry, payload, existing) {
|
|
3022
|
+
if (!this.isNativeMode() || !this._nativeBackboneDocumentIndexEnabled) {
|
|
3023
|
+
return;
|
|
3024
|
+
}
|
|
3025
|
+
if (value instanceof Program) {
|
|
3026
|
+
return;
|
|
3027
|
+
}
|
|
3028
|
+
const existingContext = this.getExistingContext(existing);
|
|
3029
|
+
const modified = entry.meta.clock.timestamp.wallTime;
|
|
3030
|
+
const context = new Context({
|
|
3031
|
+
created: existingContext?.created || modified,
|
|
3032
|
+
modified,
|
|
3033
|
+
head: entry.hash,
|
|
3034
|
+
gid: entry.meta.gid,
|
|
3035
|
+
size: encodePutOperationPayload(payload.data).byteLength,
|
|
3036
|
+
});
|
|
3037
|
+
const nativeDocumentIndex = asTrustedDocumentIndex(this._index).prepareNativeBackboneDocumentIndexCommitWithAppendFacts(value, payload.data, context, { entryPublicKeys: entry.publicKeys });
|
|
3038
|
+
if (!nativeDocumentIndex) {
|
|
3039
|
+
return;
|
|
3040
|
+
}
|
|
3041
|
+
return asTrustedDocumentIndex(this._index)._putPreparedNativeBackboneDocumentIndexWithContext(value, key, context, nativeDocumentIndex, {
|
|
3042
|
+
replace: existing != null,
|
|
3043
|
+
});
|
|
3044
|
+
}
|
|
3045
|
+
putStrictNativeReceivedDocumentIndexStoredWithContext(key, entry, payload, existing) {
|
|
3046
|
+
if (!this.isNativeMode() || !this._nativeBackboneDocumentIndexEnabled) {
|
|
3047
|
+
return;
|
|
3048
|
+
}
|
|
3049
|
+
const existingContext = this.getExistingContext(existing);
|
|
3050
|
+
const modified = entry.meta.clock.timestamp.wallTime;
|
|
3051
|
+
const context = new Context({
|
|
3052
|
+
created: existingContext?.created || modified,
|
|
3053
|
+
modified,
|
|
3054
|
+
head: entry.hash,
|
|
3055
|
+
gid: entry.meta.gid,
|
|
3056
|
+
size: encodePutOperationPayload(payload.data).byteLength,
|
|
3057
|
+
});
|
|
3058
|
+
const nativeDocumentIndex = asTrustedDocumentIndex(this._index).prepareNativeBackboneDocumentIndexStoredCommitWithAppendFacts(payload.data, context, { entryPublicKeys: entry.publicKeys });
|
|
3059
|
+
if (!nativeDocumentIndex) {
|
|
3060
|
+
return;
|
|
3061
|
+
}
|
|
3062
|
+
return asTrustedDocumentIndex(this._index)._putPreparedNativeBackboneDocumentIndexStoredWithContext(key, context, nativeDocumentIndex, {
|
|
3063
|
+
replace: existing != null,
|
|
471
3064
|
});
|
|
472
|
-
this.keepCache?.add(appended.entry.hash);
|
|
473
|
-
return appended;
|
|
474
3065
|
}
|
|
475
3066
|
async get(id, options) {
|
|
476
3067
|
const resolved = await this.index.get(id, {
|
|
@@ -480,6 +3071,9 @@ let Documents = (() => {
|
|
|
480
3071
|
return resolved ? resolved : undefined;
|
|
481
3072
|
}
|
|
482
3073
|
async del(id, options) {
|
|
3074
|
+
return this._documentBackend.del(id, options);
|
|
3075
|
+
}
|
|
3076
|
+
async delCompatDocumentBackend(id, options) {
|
|
483
3077
|
const key = id instanceof indexerTypes.IdKey ? id : indexerTypes.toId(id);
|
|
484
3078
|
const existing = (await this._index.getDetailed(key, {
|
|
485
3079
|
resolve: false,
|
|
@@ -504,6 +3098,100 @@ let Documents = (() => {
|
|
|
504
3098
|
},
|
|
505
3099
|
});
|
|
506
3100
|
}
|
|
3101
|
+
async delNativeDocumentBackend(id, options) {
|
|
3102
|
+
const deleteOptions = this.normalizeNativeModePutOptions(options);
|
|
3103
|
+
this.assertNativeModeDeleteSupported(deleteOptions);
|
|
3104
|
+
const key = id instanceof indexerTypes.IdKey ? id : indexerTypes.toId(id);
|
|
3105
|
+
if (!this.hasNativeDocumentContextLookup()) {
|
|
3106
|
+
throw this.nativeModeError("requires native document context lookup");
|
|
3107
|
+
}
|
|
3108
|
+
const existing = this.getNativeIndexedContext(key);
|
|
3109
|
+
const existingContext = this.getExistingContext(existing);
|
|
3110
|
+
if (!existingContext?.head) {
|
|
3111
|
+
throw new NotFoundError(`No entry with key '${key.primitive}' in the database`);
|
|
3112
|
+
}
|
|
3113
|
+
let previousEntry;
|
|
3114
|
+
let existingDocumentChecked = false;
|
|
3115
|
+
let existingDocument;
|
|
3116
|
+
const getPreviousEntry = async () => {
|
|
3117
|
+
if (previousEntry) {
|
|
3118
|
+
return previousEntry;
|
|
3119
|
+
}
|
|
3120
|
+
previousEntry = await this._resolveEntry(existingContext.head, {
|
|
3121
|
+
remote: true,
|
|
3122
|
+
});
|
|
3123
|
+
if (!previousEntry) {
|
|
3124
|
+
throw new NotFoundError(`No entry with key '${key.primitive}' in the database`);
|
|
3125
|
+
}
|
|
3126
|
+
return previousEntry;
|
|
3127
|
+
};
|
|
3128
|
+
const getExistingDocument = async () => {
|
|
3129
|
+
if (!existingDocumentChecked) {
|
|
3130
|
+
existingDocumentChecked = true;
|
|
3131
|
+
existingDocument = await this.getLocalIdentityDocumentByHead(existingContext.head);
|
|
3132
|
+
existingDocument ??=
|
|
3133
|
+
await this.getLocalIndexedDocumentForNativeDeletePolicy(key);
|
|
3134
|
+
}
|
|
3135
|
+
return existingDocument;
|
|
3136
|
+
};
|
|
3137
|
+
const operation = new DeleteOperation({ key });
|
|
3138
|
+
if (!(await this.canPerformAllowsNativeDelete({
|
|
3139
|
+
operation,
|
|
3140
|
+
getExistingEntry: getPreviousEntry,
|
|
3141
|
+
getExistingDocument,
|
|
3142
|
+
}))) {
|
|
3143
|
+
throw this.nativeModeError("canPerform policy rejected this delete");
|
|
3144
|
+
}
|
|
3145
|
+
const operationPayloadBytes = BORSH_ENCODING_OPERATION.encoder(operation);
|
|
3146
|
+
const documentsChanged = this.hasDocumentChangeConsumers()
|
|
3147
|
+
? {
|
|
3148
|
+
added: [],
|
|
3149
|
+
removed: [],
|
|
3150
|
+
}
|
|
3151
|
+
: undefined;
|
|
3152
|
+
const removedDocument = documentsChanged
|
|
3153
|
+
? await this._index.get(key, {
|
|
3154
|
+
local: true,
|
|
3155
|
+
remote: false,
|
|
3156
|
+
})
|
|
3157
|
+
: undefined;
|
|
3158
|
+
this.keepCache?.delete(existingContext.head);
|
|
3159
|
+
const previousForAppend = this.nextFromIndexedContext(existingContext.head, existing) ??
|
|
3160
|
+
(await getPreviousEntry());
|
|
3161
|
+
const trustedLog = asTrustedDocumentSharedLog(this.log);
|
|
3162
|
+
const appended = await trustedLog.appendStrictNativeDocumentPayloadCommitOnly(operationPayloadBytes, {
|
|
3163
|
+
...deleteOptions,
|
|
3164
|
+
meta: {
|
|
3165
|
+
next: [previousForAppend],
|
|
3166
|
+
type: EntryType.CUT,
|
|
3167
|
+
...deleteOptions?.meta,
|
|
3168
|
+
},
|
|
3169
|
+
}, {
|
|
3170
|
+
skipMissingNextJoin: true,
|
|
3171
|
+
resolveTrimmedEntries: false,
|
|
3172
|
+
nativeBackboneDocumentDeleteKey: documentIndexStoreKey(key),
|
|
3173
|
+
});
|
|
3174
|
+
if (!appended) {
|
|
3175
|
+
throw this.nativeModeError("requires native delete append support");
|
|
3176
|
+
}
|
|
3177
|
+
const result = {
|
|
3178
|
+
get entry() {
|
|
3179
|
+
return appended.entry;
|
|
3180
|
+
},
|
|
3181
|
+
removed: appended.removed,
|
|
3182
|
+
};
|
|
3183
|
+
if (appended.appendCommit.nativeBackboneDocumentDeleteCommitted) {
|
|
3184
|
+
this._index.clearResolvedCacheForKeys([key]);
|
|
3185
|
+
}
|
|
3186
|
+
else {
|
|
3187
|
+
await this._index.delManyMaybe([key]);
|
|
3188
|
+
}
|
|
3189
|
+
if (documentsChanged && removedDocument) {
|
|
3190
|
+
documentsChanged.removed.push(removedDocument);
|
|
3191
|
+
this.dispatchDocumentChangeIfObserved(documentsChanged);
|
|
3192
|
+
}
|
|
3193
|
+
return result;
|
|
3194
|
+
}
|
|
507
3195
|
async handleChanges(change, reference) {
|
|
508
3196
|
logger.trace("handleChanges called", change);
|
|
509
3197
|
const isAppendOperation = change?.added.length === 1 ? !!change.added[0] : false;
|
|
@@ -511,45 +3199,90 @@ let Documents = (() => {
|
|
|
511
3199
|
for (const r of change.removed) {
|
|
512
3200
|
removedSet.set(r.hash, r);
|
|
513
3201
|
}
|
|
3202
|
+
const shouldPrepareDocumentChanges = this.hasDocumentChangeConsumers();
|
|
3203
|
+
const canRemoveByIndexedHead = !shouldPrepareDocumentChanges && this._index.canGetIndexedKeyByHead();
|
|
3204
|
+
const removedEntries = canRemoveByIndexedHead
|
|
3205
|
+
? []
|
|
3206
|
+
: ((await Promise.all(change.removed.map((x) => x instanceof Entry ? x : this.log.log.entryIndex.get(x.hash)))) ?? []);
|
|
514
3207
|
const sortedEntries = [
|
|
515
3208
|
...change.added.map((x) => x.entry),
|
|
516
|
-
...
|
|
3209
|
+
...removedEntries,
|
|
517
3210
|
]; // TODO assert sorting
|
|
518
3211
|
/* const sortedEntries = [...change.added, ...(removed || [])]
|
|
519
3212
|
.sort(this.log.log.sortFn)
|
|
520
3213
|
.reverse(); // sort so we get newest to oldest */
|
|
521
3214
|
// There might be a case where change.added and change.removed contains the same document id. Usaully because you use the "trim" option
|
|
522
3215
|
// 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
|
-
|
|
3216
|
+
let documentsChanged = shouldPrepareDocumentChanges
|
|
3217
|
+
? {
|
|
3218
|
+
added: [],
|
|
3219
|
+
removed: [],
|
|
3220
|
+
}
|
|
3221
|
+
: undefined;
|
|
527
3222
|
let modified = new Set();
|
|
528
3223
|
for (const item of sortedEntries) {
|
|
529
3224
|
if (!item) {
|
|
530
3225
|
continue;
|
|
531
3226
|
}
|
|
532
3227
|
try {
|
|
533
|
-
const
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
3228
|
+
const isReferencedAppendEntry = isAppendOperation &&
|
|
3229
|
+
reference?.operation &&
|
|
3230
|
+
change.added[0]?.entry.hash === item.hash;
|
|
3231
|
+
const payload = isReferencedAppendEntry
|
|
3232
|
+
? reference.operation
|
|
3233
|
+
: await this.getAppendOperation(item);
|
|
3234
|
+
if (!payload) {
|
|
3235
|
+
continue;
|
|
3236
|
+
}
|
|
537
3237
|
if (isPutOperation(payload) && !removedSet.has(item.hash)) {
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
3238
|
+
if (!documentsChanged && this.isNativeMode()) {
|
|
3239
|
+
const keyValue = await this.getNativeDocumentIdFromPutOperation(payload);
|
|
3240
|
+
if (keyValue != null) {
|
|
3241
|
+
const key = indexerTypes.toId(keyValue);
|
|
3242
|
+
if (modified.has(key.primitive)) {
|
|
3243
|
+
continue;
|
|
3244
|
+
}
|
|
3245
|
+
const existing = reference?.unique || reference?.existing === null
|
|
3246
|
+
? null
|
|
3247
|
+
: isReferencedAppendEntry &&
|
|
3248
|
+
reference?.existing !== undefined
|
|
3249
|
+
? reference.existing
|
|
3250
|
+
: this.getNativeModeIndexedContext(key) || null;
|
|
3251
|
+
if (!this.strictHistory && existing) {
|
|
3252
|
+
const shouldIgnoreChange = this.immutable
|
|
3253
|
+
? existing.value.__context.modified <
|
|
3254
|
+
item.meta.clock.timestamp.wallTime
|
|
3255
|
+
: existing.value.__context.modified >
|
|
3256
|
+
item.meta.clock.timestamp.wallTime;
|
|
3257
|
+
if (shouldIgnoreChange) {
|
|
3258
|
+
continue;
|
|
3259
|
+
}
|
|
3260
|
+
}
|
|
3261
|
+
const stored = await this.putStrictNativeReceivedDocumentIndexStoredWithContext(key, item, payload, existing);
|
|
3262
|
+
if (stored) {
|
|
3263
|
+
modified.add(key.primitive);
|
|
3264
|
+
continue;
|
|
3265
|
+
}
|
|
3266
|
+
}
|
|
3267
|
+
}
|
|
3268
|
+
let value = (isReferencedAppendEntry && reference?.document) ||
|
|
541
3269
|
this.index.valueEncoding.decoder(payload.data);
|
|
542
3270
|
// get index key from value
|
|
543
|
-
const
|
|
544
|
-
|
|
3271
|
+
const key = isReferencedAppendEntry && reference?.key
|
|
3272
|
+
? reference.key
|
|
3273
|
+
: indexerTypes.toId(this.idResolver(value));
|
|
545
3274
|
// document is already updated with more recent entry
|
|
546
3275
|
if (modified.has(key.primitive)) {
|
|
547
3276
|
continue;
|
|
548
3277
|
}
|
|
549
3278
|
// if no casual ordering is used, use timestamps to order docs
|
|
550
|
-
let existing = reference?.unique
|
|
3279
|
+
let existing = reference?.unique || reference?.existing === null
|
|
551
3280
|
? null
|
|
552
|
-
:
|
|
3281
|
+
: isReferencedAppendEntry && reference?.existing !== undefined
|
|
3282
|
+
? reference.existing
|
|
3283
|
+
: this.isNativeMode()
|
|
3284
|
+
? this.getNativeModeIndexedContext(key) || null
|
|
3285
|
+
: (await this.getLocalIndexedContext(key)) || null;
|
|
553
3286
|
if (!this.strictHistory && existing) {
|
|
554
3287
|
// if immutable use oldest, else use newest
|
|
555
3288
|
let shouldIgnoreChange = this.immutable
|
|
@@ -566,59 +3299,26 @@ let Documents = (() => {
|
|
|
566
3299
|
// if replicator, then open
|
|
567
3300
|
value = await this.maybeSubprogramOpen(value);
|
|
568
3301
|
}
|
|
3302
|
+
const nativeStoredIndexed = payload instanceof PutOperation
|
|
3303
|
+
? await this.putStrictNativeReceivedDocumentIndexWithContext(value, key, item, payload, existing)
|
|
3304
|
+
: undefined;
|
|
3305
|
+
if (nativeStoredIndexed) {
|
|
3306
|
+
documentsChanged?.added.push(nativeStoredIndexed);
|
|
3307
|
+
modified.add(key.primitive);
|
|
3308
|
+
continue;
|
|
3309
|
+
}
|
|
569
3310
|
const { context, indexable } = await this._index.put(value, key, item, existing);
|
|
570
|
-
documentsChanged
|
|
3311
|
+
documentsChanged?.added.push(coerceWithIndexed(coerceWithContext(value, context), indexable));
|
|
571
3312
|
modified.add(key.primitive);
|
|
572
3313
|
}
|
|
573
3314
|
else if ((isDeleteOperation(payload) && !removedSet.has(item.hash)) ||
|
|
574
3315
|
isPutOperation(payload) ||
|
|
575
3316
|
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);
|
|
3317
|
+
if (!documentsChanged &&
|
|
3318
|
+
(await this.collectRemovedPutChangeFromNativeId(payload, modified))) {
|
|
3319
|
+
continue;
|
|
618
3320
|
}
|
|
619
|
-
|
|
620
|
-
await this._index.del(key);
|
|
621
|
-
modified.add(key.primitive);
|
|
3321
|
+
await this.collectRemovedDocumentChange(payload, modified, documentsChanged);
|
|
622
3322
|
}
|
|
623
3323
|
else {
|
|
624
3324
|
// Unknown operation
|
|
@@ -632,7 +3332,39 @@ let Documents = (() => {
|
|
|
632
3332
|
throw error;
|
|
633
3333
|
}
|
|
634
3334
|
}
|
|
635
|
-
|
|
3335
|
+
if (canRemoveByIndexedHead && change.removed.length > 0) {
|
|
3336
|
+
const handled = await this.collectRemovedDocumentChangesFromIndexedHeads(change.removed, modified);
|
|
3337
|
+
const remainingRemoved = change.removed.filter((entry) => !handled.has(entry.hash));
|
|
3338
|
+
for (const removed of remainingRemoved) {
|
|
3339
|
+
const entry = removed instanceof Entry
|
|
3340
|
+
? removed
|
|
3341
|
+
: await this.log.log.entryIndex.get(removed.hash, {
|
|
3342
|
+
type: "full",
|
|
3343
|
+
ignoreMissing: true,
|
|
3344
|
+
});
|
|
3345
|
+
if (!entry) {
|
|
3346
|
+
continue;
|
|
3347
|
+
}
|
|
3348
|
+
try {
|
|
3349
|
+
const payload = await this.getAppendOperation(entry);
|
|
3350
|
+
if (!payload) {
|
|
3351
|
+
continue;
|
|
3352
|
+
}
|
|
3353
|
+
if (!(await this.collectRemovedPutChangeFromNativeId(payload, modified))) {
|
|
3354
|
+
await this.collectRemovedDocumentChange(payload, modified);
|
|
3355
|
+
}
|
|
3356
|
+
}
|
|
3357
|
+
catch (error) {
|
|
3358
|
+
if (error instanceof AccessError) {
|
|
3359
|
+
continue;
|
|
3360
|
+
}
|
|
3361
|
+
throw error;
|
|
3362
|
+
}
|
|
3363
|
+
}
|
|
3364
|
+
}
|
|
3365
|
+
if (documentsChanged) {
|
|
3366
|
+
this.dispatchDocumentChangeIfObserved(documentsChanged);
|
|
3367
|
+
}
|
|
636
3368
|
}
|
|
637
3369
|
async count(options) {
|
|
638
3370
|
// Local/exact count
|