@peerbit/document 13.0.44 → 13.1.1

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