@peerbit/document 13.0.43 → 13.1.0

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