@peerbit/document 15.0.12 → 15.0.14

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/program.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  import {
2
2
  type AbstractType,
3
3
  BorshError,
4
+ deserialize,
4
5
  field,
5
6
  serialize,
6
7
  variant,
@@ -44,6 +45,10 @@ import {
44
45
  SharedLog,
45
46
  type SharedLogOptions,
46
47
  } from "@peerbit/shared-log";
48
+ import {
49
+ detachCanPerformCallbackProperties,
50
+ detachEntryPayloadForCallback,
51
+ } from "./callback-detachment.js";
47
52
  import { MAX_BATCH_SIZE } from "./constants.js";
48
53
  import type { CustomDocumentDomain } from "./domain.js";
49
54
  import type { DocumentEvents, DocumentsChange } from "./events.js";
@@ -74,6 +79,7 @@ import {
74
79
  type CanRead,
75
80
  type CanSearch,
76
81
  DocumentIndex,
82
+ type DocumentIndexWriteSession,
77
83
  type GetOptions,
78
84
  INDEX_CONTEXT_SHAPE,
79
85
  type IndexedContextOnly,
@@ -1087,12 +1093,23 @@ export class Documents<
1087
1093
  private _documentChangeListenerTrackingInitialized = false;
1088
1094
  private _documentBackend!: DocumentBackend<T>;
1089
1095
  private _canAppendDecodedDocuments = new WeakMap<PutOperation, T>();
1096
+ private acceptPutOperation(
1097
+ operation: PutOperation,
1098
+ document: T | undefined,
1099
+ hasDocument: boolean,
1100
+ ): PutOperation {
1101
+ if (hasDocument) {
1102
+ this._canAppendDecodedDocuments.set(operation, document!);
1103
+ }
1104
+ return operation;
1105
+ }
1090
1106
  private _nativeDocumentIdExtractionPlan?: SimpleDocumentFieldExtractionPlan;
1091
1107
  private _nativeDocumentFieldExtractionPlans?: Map<
1092
1108
  string,
1093
1109
  SimpleDocumentFieldExtractionPlan | undefined
1094
1110
  >;
1095
1111
  private _hasLogTrim = false;
1112
+ private _hasCustomIdResolver = false;
1096
1113
  private idResolver!: (any: any) => indexerTypes.Ideable;
1097
1114
  private domain?: CustomDocumentDomain<InferR<D>>;
1098
1115
  private strictHistory: boolean;
@@ -1130,6 +1147,84 @@ export class Documents<
1130
1147
  );
1131
1148
  }
1132
1149
 
1150
+ private detachDomainEntryCallback(
1151
+ domain: CustomDocumentDomain<InferR<D>>,
1152
+ ): CustomDocumentDomain<InferR<D>> {
1153
+ const boundMethods = new Map<
1154
+ PropertyKey,
1155
+ { source: Function; bound: Function }
1156
+ >();
1157
+ const detachedFromEntry = (
1158
+ entry: Parameters<CustomDocumentDomain<InferR<D>>["fromEntry"]>[0],
1159
+ ) => {
1160
+ const fromEntry = Reflect.get(
1161
+ domain,
1162
+ "fromEntry",
1163
+ domain,
1164
+ ) as CustomDocumentDomain<InferR<D>>["fromEntry"];
1165
+ return Reflect.apply(fromEntry, domain, [
1166
+ entry instanceof Entry
1167
+ ? detachEntryPayloadForCallback(entry)
1168
+ : entry,
1169
+ ]);
1170
+ };
1171
+ const facade = Object.create(
1172
+ Object.getPrototypeOf(domain),
1173
+ ) as CustomDocumentDomain<InferR<D>>;
1174
+ let callbackDomain: CustomDocumentDomain<InferR<D>>;
1175
+ callbackDomain = new Proxy(facade, {
1176
+ get(_target, property) {
1177
+ if (property === "fromEntry") {
1178
+ return detachedFromEntry;
1179
+ }
1180
+ if (property === "valueOf") {
1181
+ return () => callbackDomain;
1182
+ }
1183
+ const value = Reflect.get(domain, property, domain);
1184
+ if (property === "constructor" || typeof value !== "function") {
1185
+ return value;
1186
+ }
1187
+ const existing = boundMethods.get(property);
1188
+ if (existing && existing.source === value) {
1189
+ return existing.bound;
1190
+ }
1191
+ const bound = value.bind(domain);
1192
+ boundMethods.set(property, { source: value, bound });
1193
+ return bound;
1194
+ },
1195
+ set(_target, property, value) {
1196
+ return Reflect.set(domain, property, value, domain);
1197
+ },
1198
+ has(_target, property) {
1199
+ return Reflect.has(domain, property);
1200
+ },
1201
+ ownKeys() {
1202
+ return Reflect.ownKeys(domain);
1203
+ },
1204
+ getOwnPropertyDescriptor(_target, property) {
1205
+ const descriptor = Reflect.getOwnPropertyDescriptor(domain, property);
1206
+ if (!descriptor) {
1207
+ return undefined;
1208
+ }
1209
+ if (property === "fromEntry") {
1210
+ return "value" in descriptor
1211
+ ? {
1212
+ ...descriptor,
1213
+ value: detachedFromEntry,
1214
+ configurable: true,
1215
+ }
1216
+ : {
1217
+ ...descriptor,
1218
+ get: () => detachedFromEntry,
1219
+ configurable: true,
1220
+ };
1221
+ }
1222
+ return { ...descriptor, configurable: true };
1223
+ },
1224
+ });
1225
+ return callbackDomain;
1226
+ }
1227
+
1133
1228
  private createNativeDocumentBackendContext(): NativeDocumentBackendContext<
1134
1229
  T,
1135
1230
  I
@@ -2025,6 +2120,7 @@ export class Documents<
2025
2120
  */
2026
2121
  private getLocalIndexedContextForChange(
2027
2122
  key: indexerTypes.IdKey,
2123
+ orderedSession?: DocumentIndexWriteSession<I>,
2028
2124
  ): Promise<indexerTypes.IndexedResult<IndexedContextOnly<I>> | undefined> {
2029
2125
  // Not every backing index resolves asynchronously (the native one answers
2030
2126
  // synchronously, and may throw synchronously), so guard both shapes without
@@ -2033,9 +2129,9 @@ export class Documents<
2033
2129
  | indexerTypes.IndexedResult<IndexedContextOnly<I>>
2034
2130
  | undefined;
2035
2131
  try {
2036
- const result = this.getLocalIndexedContext(
2037
- key,
2038
- ) as MaybePromise<LocalIndexedContext>;
2132
+ const result = (orderedSession
2133
+ ? orderedSession.get(key, { shape: INDEX_CONTEXT_SHAPE })
2134
+ : this.getLocalIndexedContext(key)) as MaybePromise<LocalIndexedContext>;
2039
2135
  return (
2040
2136
  isPromiseLike(result)
2041
2137
  ? result.catch((error) => this.recoverClosedIndexContextRead(error))
@@ -2354,6 +2450,7 @@ export class Documents<
2354
2450
  : (obj: any) =>
2355
2451
  indexerTypes.extractFieldValue(obj, idProperty as string[]));
2356
2452
 
2453
+ this._hasCustomIdResolver = options.id != null;
2357
2454
  this.idResolver = idResolver;
2358
2455
  this.strictHistory = options.strictHistory ?? false;
2359
2456
  this._hasLogTrim = options.log?.trim != null;
@@ -2402,7 +2499,9 @@ export class Documents<
2402
2499
  // 7 -> log v9) is retired; the rejection at the top of open() fires for
2403
2500
  // any defined value before this point.
2404
2501
 
2405
- this.domain = options.domain?.(this);
2502
+ this.domain = options.domain
2503
+ ? this.detachDomainEntryCallback(options.domain(this))
2504
+ : undefined;
2406
2505
 
2407
2506
  let keepFunction:
2408
2507
  | ((
@@ -2447,7 +2546,16 @@ export class Documents<
2447
2546
  return false;
2448
2547
  };
2449
2548
  } else {
2450
- keepFunction = options?.keep;
2549
+ const keep = options?.keep;
2550
+ keepFunction = keep
2551
+ ? function (this: unknown, entry) {
2552
+ return Reflect.apply(keep, this, [
2553
+ entry instanceof Entry
2554
+ ? detachEntryPayloadForCallback(entry)
2555
+ : entry,
2556
+ ]);
2557
+ }
2558
+ : undefined;
2451
2559
  }
2452
2560
 
2453
2561
  await this.log.open({
@@ -2471,7 +2579,10 @@ export class Documents<
2471
2579
  distributionDebounceTime: options?.distributionDebounceTime,
2472
2580
  strictFullReplicaFallback: false,
2473
2581
  domain: options?.domain
2474
- ? () => options.domain!(this) as unknown as D
2582
+ ? () =>
2583
+ this.detachDomainEntryCallback(
2584
+ options.domain!(this),
2585
+ ) as unknown as D
2475
2586
  : undefined,
2476
2587
  eagerBlocks: options?.eagerBlocks,
2477
2588
  fanout: options?.fanout,
@@ -2563,7 +2674,7 @@ export class Documents<
2563
2674
  }
2564
2675
 
2565
2676
  try {
2566
- let operation: PutOperation | DeleteOperation = l0;
2677
+ const operation: PutOperation | DeleteOperation = l0;
2567
2678
  if (this._optionCanPerform) {
2568
2679
  if (this._optionCanPerformNativePolicy && this.isNativeMode()) {
2569
2680
  return this.nativeCanPerformAllowsAppend(
@@ -2573,22 +2684,22 @@ export class Documents<
2573
2684
  reference?.document,
2574
2685
  );
2575
2686
  }
2576
- let document: T | undefined = reference?.document;
2577
- if (!document) {
2578
- if (isPutOperation(l0)) {
2579
- document =
2580
- this._canAppendDecodedDocuments.get(l0) ??
2581
- this._index.valueEncoding.decoder(l0.data);
2582
- if (!document) {
2583
- return false;
2584
- }
2585
- } else if (isDeleteOperation(l0)) {
2586
- // Nothing to do here by default.
2587
- // Checking if the document exists is not necessary since it
2588
- // might already be deleted.
2589
- } else {
2590
- throw new Error("Unsupported operation");
2687
+ let document: T | undefined;
2688
+ if (isPutOperation(l0)) {
2689
+ const cachedDocument = this._canAppendDecodedDocuments.get(l0);
2690
+ this._canAppendDecodedDocuments.delete(l0);
2691
+ document =
2692
+ cachedDocument ??
2693
+ this._index.valueEncoding.decoder(new Uint8Array(l0.data));
2694
+ if (!document) {
2695
+ return false;
2591
2696
  }
2697
+ } else if (isDeleteOperation(l0)) {
2698
+ // Nothing to do here by default.
2699
+ // Checking if the document exists is not necessary since it
2700
+ // might already be deleted.
2701
+ } else {
2702
+ throw new Error("Unsupported operation");
2592
2703
  }
2593
2704
  const previousEntries =
2594
2705
  this._optionCanPerformNativePolicy &&
@@ -2596,7 +2707,9 @@ export class Documents<
2596
2707
  canPerformPolicyNeedsPreviousEntries(
2597
2708
  this._optionCanPerformNativePolicy,
2598
2709
  )
2599
- ? await this.resolveCanPerformPreviousEntries(entry)
2710
+ ? (await this.resolveCanPerformPreviousEntries(entry)).map(
2711
+ detachEntryPayloadForCallback,
2712
+ )
2600
2713
  : undefined;
2601
2714
  const deleteValue =
2602
2715
  this._optionCanPerformNativePolicy &&
@@ -2608,20 +2721,22 @@ export class Documents<
2608
2721
  : undefined;
2609
2722
  if (
2610
2723
  !(await this._optionCanPerform(
2611
- isPutOperation(operation)
2612
- ? {
2613
- type: "put",
2614
- value: document!,
2615
- operation,
2616
- entry: entry as unknown as Entry<PutOperation>,
2617
- previousEntries,
2618
- }
2619
- : {
2620
- type: "delete",
2621
- value: deleteValue,
2622
- operation,
2623
- entry: entry as unknown as Entry<DeleteOperation>,
2624
- },
2724
+ detachCanPerformCallbackProperties(
2725
+ isPutOperation(operation)
2726
+ ? {
2727
+ type: "put",
2728
+ value: document!,
2729
+ operation,
2730
+ entry: entry as unknown as Entry<PutOperation>,
2731
+ previousEntries,
2732
+ }
2733
+ : {
2734
+ type: "delete",
2735
+ value: deleteValue,
2736
+ operation,
2737
+ entry: entry as unknown as Entry<DeleteOperation>,
2738
+ },
2739
+ ),
2625
2740
  ))
2626
2741
  ) {
2627
2742
  return false;
@@ -2710,6 +2825,12 @@ export class Documents<
2710
2825
  return entries;
2711
2826
  }
2712
2827
 
2828
+ private detachDocumentValueForCallback(document: T): T {
2829
+ return this._index.valueEncoding.decoder(
2830
+ this._index.valueEncoding.encoder(document),
2831
+ );
2832
+ }
2833
+
2713
2834
  private async resolveCanPerformDeleteValue(
2714
2835
  operation: DeleteOperation,
2715
2836
  options?: { allowEntryFallback?: boolean },
@@ -2726,12 +2847,15 @@ export class Documents<
2726
2847
  const indexedDocument =
2727
2848
  await this.getLocalIdentityDocumentByHead(existingHead);
2728
2849
  if (indexedDocument) {
2729
- return indexedDocument;
2850
+ return this.detachDocumentValueForCallback(indexedDocument);
2730
2851
  }
2731
2852
  const indexedPolicyDocument =
2732
2853
  await this.getLocalIndexedDocumentForNativeDeletePolicy(key);
2733
2854
  if (indexedPolicyDocument) {
2734
- return indexedPolicyDocument;
2855
+ return deserialize(
2856
+ serialize(indexedPolicyDocument),
2857
+ this._index.indexedType,
2858
+ ) as unknown as T;
2735
2859
  }
2736
2860
  if (options?.allowEntryFallback === false) {
2737
2861
  return;
@@ -2743,7 +2867,9 @@ export class Documents<
2743
2867
  if (!isPutOperation(existingOperation)) {
2744
2868
  return;
2745
2869
  }
2746
- return this._index.valueEncoding.decoder(existingOperation.data);
2870
+ return this._index.valueEncoding.decoder(
2871
+ new Uint8Array(existingOperation.data),
2872
+ );
2747
2873
  }
2748
2874
 
2749
2875
  protected async _canAppend(
@@ -2794,17 +2920,32 @@ export class Documents<
2794
2920
  if (isPutOperation(operation)) {
2795
2921
  // check nexts
2796
2922
  const putOperation = operation as PutOperation;
2923
+ let decodedDocumentForCanPerform: T | undefined;
2924
+ let hasDecodedDocumentForCanPerform = false;
2797
2925
  let keyValue: indexerTypes.Ideable | undefined;
2798
2926
  if (reference?.document) {
2799
- keyValue = this.idResolver(reference.document);
2927
+ keyValue = this.idResolver(
2928
+ this._hasCustomIdResolver
2929
+ ? this.index.valueEncoding.decoder(
2930
+ new Uint8Array(putOperation.data),
2931
+ )
2932
+ : reference.document,
2933
+ );
2800
2934
  } else {
2801
2935
  keyValue = await this.getNativeDocumentIdFromPutOperation(putOperation);
2802
2936
  if (keyValue == null) {
2803
2937
  if (this.isNativeMode()) {
2804
2938
  return false;
2805
2939
  }
2806
- const value = this.index.valueEncoding.decoder(putOperation.data);
2807
- this._canAppendDecodedDocuments.set(putOperation, value);
2940
+ const value = this.index.valueEncoding.decoder(
2941
+ this._hasCustomIdResolver || this._optionCanPerform
2942
+ ? new Uint8Array(putOperation.data)
2943
+ : putOperation.data,
2944
+ );
2945
+ if (this._optionCanPerform && !this._hasCustomIdResolver) {
2946
+ decodedDocumentForCanPerform = value;
2947
+ hasDecodedDocumentForCanPerform = true;
2948
+ }
2808
2949
  keyValue = this.idResolver(value);
2809
2950
  }
2810
2951
  }
@@ -2841,7 +2982,11 @@ export class Documents<
2841
2982
  return false; // can not append to immutable document
2842
2983
  }
2843
2984
 
2844
- return putOperation;
2985
+ return this.acceptPutOperation(
2986
+ putOperation,
2987
+ decodedDocumentForCanPerform,
2988
+ hasDecodedDocumentForCanPerform,
2989
+ );
2845
2990
  } else {
2846
2991
  if (this.strictHistory) {
2847
2992
  // make sure that the next pointer exist and points to the existing documents
@@ -2849,7 +2994,11 @@ export class Documents<
2849
2994
  return false;
2850
2995
  }
2851
2996
  if (entry.meta.next[0] === existingContext.head) {
2852
- return putOperation;
2997
+ return this.acceptPutOperation(
2998
+ putOperation,
2999
+ decodedDocumentForCanPerform,
3000
+ hasDecodedDocumentForCanPerform,
3001
+ );
2853
3002
  }
2854
3003
 
2855
3004
  const prevEntry = await this.log.log.entryIndex.get(
@@ -2864,14 +3013,29 @@ export class Documents<
2864
3013
  }
2865
3014
  const referenceHistoryCorrectly =
2866
3015
  await pointsToHistory(prevEntry);
2867
- return referenceHistoryCorrectly ? putOperation : false;
3016
+ return referenceHistoryCorrectly
3017
+ ? this.acceptPutOperation(
3018
+ putOperation,
3019
+ decodedDocumentForCanPerform,
3020
+ hasDecodedDocumentForCanPerform,
3021
+ )
3022
+ : false;
2868
3023
  } else {
2869
- return putOperation;
3024
+ return this.acceptPutOperation(
3025
+ putOperation,
3026
+ decodedDocumentForCanPerform,
3027
+ hasDecodedDocumentForCanPerform,
3028
+ );
2870
3029
  }
2871
3030
  }
2872
3031
  } else {
2873
3032
  // Keep existing behavior: next pointers may express document dependencies.
2874
3033
  }
3034
+ return this.acceptPutOperation(
3035
+ putOperation,
3036
+ decodedDocumentForCanPerform,
3037
+ hasDecodedDocumentForCanPerform,
3038
+ );
2875
3039
  } else if (isDeleteOperation(operation)) {
2876
3040
  if (entry.meta.next.length !== 1) {
2877
3041
  return false;
@@ -2922,7 +3086,6 @@ export class Documents<
2922
3086
  throw new Error("Unsupported operation");
2923
3087
  }
2924
3088
 
2925
- return operation;
2926
3089
  } catch (error) {
2927
3090
  if (error instanceof AccessError) {
2928
3091
  return false; // we cant index because we can not decrypt
@@ -5342,6 +5505,24 @@ export class Documents<
5342
5505
  reference?: PutChangeReference<T, I>,
5343
5506
  ): Promise<void> {
5344
5507
  logger.trace("handleChanges called", change);
5508
+ if (
5509
+ reference == null &&
5510
+ change.added.length >= 2 &&
5511
+ !this.isNativeMode() &&
5512
+ this._index.canUseOrderedWriteSession()
5513
+ ) {
5514
+ return this._index.withOrderedWriteSession((orderedSession) =>
5515
+ this.handleChangesInOrder(change, reference, orderedSession),
5516
+ );
5517
+ }
5518
+ return this.handleChangesInOrder(change, reference);
5519
+ }
5520
+
5521
+ private async handleChangesInOrder(
5522
+ change: Change<Operation>,
5523
+ reference?: PutChangeReference<T, I>,
5524
+ orderedSession?: DocumentIndexWriteSession<I>,
5525
+ ): Promise<void> {
5345
5526
  const isAppendOperation =
5346
5527
  change?.added.length === 1 ? !!change.added[0] : false;
5347
5528
  const removedSet = new Map<string, ShallowOrFullEntry<Operation>>();
@@ -5382,6 +5563,7 @@ export class Documents<
5382
5563
  let modified: Set<string | number | bigint> = new Set();
5383
5564
  for (const item of sortedEntries) {
5384
5565
  if (!item) {
5566
+ await orderedSession?.flush();
5385
5567
  continue;
5386
5568
  }
5387
5569
 
@@ -5394,6 +5576,7 @@ export class Documents<
5394
5576
  ? reference.operation
5395
5577
  : await this.getAppendOperation(item);
5396
5578
  if (!payload) {
5579
+ await orderedSession?.flush();
5397
5580
  continue;
5398
5581
  }
5399
5582
 
@@ -5437,17 +5620,31 @@ export class Documents<
5437
5620
  }
5438
5621
  }
5439
5622
  let value =
5440
- (isReferencedAppendEntry && reference?.document) ||
5441
- this.index.valueEncoding.decoder(payload.data);
5623
+ isReferencedAppendEntry && reference?.document
5624
+ ? reference.document
5625
+ : this.index.valueEncoding.decoder(
5626
+ new Uint8Array(payload.data),
5627
+ );
5442
5628
 
5443
5629
  // get index key from value
5444
5630
  const key =
5445
5631
  isReferencedAppendEntry && reference?.key
5446
5632
  ? reference.key
5447
- : indexerTypes.toId(this.idResolver(value));
5633
+ : indexerTypes.toId(
5634
+ this.idResolver(
5635
+ this._hasCustomIdResolver
5636
+ ? this.index.valueEncoding.decoder(
5637
+ new Uint8Array(payload.data),
5638
+ )
5639
+ : value,
5640
+ ),
5641
+ );
5448
5642
 
5449
5643
  // document is already updated with more recent entry
5450
5644
  if (modified.has(key.primitive)) {
5645
+ // Duplicate ids deliberately retain the scalar boundary until
5646
+ // their callback and event semantics are proven independently.
5647
+ await orderedSession?.flush();
5451
5648
  continue;
5452
5649
  }
5453
5650
 
@@ -5459,7 +5656,10 @@ export class Documents<
5459
5656
  ? reference.existing
5460
5657
  : this.isNativeMode()
5461
5658
  ? this.getNativeModeIndexedContext(key) || null
5462
- : (await this.getLocalIndexedContextForChange(key)) || null;
5659
+ : (await this.getLocalIndexedContextForChange(
5660
+ key,
5661
+ orderedSession,
5662
+ )) || null;
5463
5663
  if (!this.strictHistory && existing) {
5464
5664
  // if immutable use oldest, else use newest
5465
5665
  let shouldIgnoreChange = this.immutable
@@ -5468,10 +5668,19 @@ export class Documents<
5468
5668
  : existing.value.__context.modified >
5469
5669
  item.meta.clock.timestamp.wallTime;
5470
5670
  if (shouldIgnoreChange) {
5671
+ await orderedSession?.flush();
5471
5672
  continue;
5472
5673
  }
5473
5674
  }
5474
5675
 
5676
+ const useOrderedWrite =
5677
+ orderedSession != null &&
5678
+ existing == null &&
5679
+ !(value instanceof Program);
5680
+ if (!useOrderedWrite) {
5681
+ await orderedSession?.flush();
5682
+ }
5683
+
5475
5684
  // Program specific
5476
5685
  if (value instanceof Program) {
5477
5686
  // if replicator, then open
@@ -5497,6 +5706,7 @@ export class Documents<
5497
5706
  key,
5498
5707
  item,
5499
5708
  existing,
5709
+ useOrderedWrite ? orderedSession : undefined,
5500
5710
  );
5501
5711
  documentsChanged?.added.push(
5502
5712
  coerceWithIndexed(coerceWithContext(value, context), indexable),
@@ -5508,6 +5718,7 @@ export class Documents<
5508
5718
  isPutOperation(payload) ||
5509
5719
  removedSet.has(item.hash)
5510
5720
  ) {
5721
+ await orderedSession?.flush();
5511
5722
  if (
5512
5723
  !documentsChanged &&
5513
5724
  (await this.collectRemovedPutChangeFromNativeId(payload, modified))
@@ -5525,12 +5736,14 @@ export class Documents<
5525
5736
  }
5526
5737
  } catch (error) {
5527
5738
  if (error instanceof AccessError) {
5739
+ await orderedSession?.flush();
5528
5740
  continue;
5529
5741
  }
5530
5742
  throw error;
5531
5743
  }
5532
5744
  }
5533
5745
 
5746
+ await orderedSession?.flush();
5534
5747
  if (canRemoveByIndexedHead && change.removed.length > 0) {
5535
5748
  const handled = await this.collectRemovedDocumentChangesFromIndexedHeads(
5536
5749
  change.removed,
package/src/search.ts CHANGED
@@ -739,6 +739,9 @@ export type WithContext<I> = {
739
739
  __context: types.Context;
740
740
  } & I;
741
741
 
742
+ export type DocumentIndexWriteSession<I extends Record<string, any>> =
743
+ indexerTypes.OrderedIndexWriteSession<WithContext<I>>;
744
+
742
745
  export const INDEX_CONTEXT_SHAPE = {
743
746
  __context: {
744
747
  created: true,
@@ -1135,6 +1138,23 @@ export class DocumentIndex<
1135
1138
  return this._valueEncoding;
1136
1139
  }
1137
1140
 
1141
+ public canUseOrderedWriteSession(): boolean {
1142
+ return (
1143
+ !this.isProgramValued &&
1144
+ typeof this.index.withOrderedWriteSession === "function"
1145
+ );
1146
+ }
1147
+
1148
+ public async withOrderedWriteSession<R>(
1149
+ operation: (session: DocumentIndexWriteSession<I>) => MaybePromise<R>,
1150
+ ): Promise<R> {
1151
+ const withSession = this.index.withOrderedWriteSession;
1152
+ if (!withSession) {
1153
+ throw new Error("Backing index does not support ordered write sessions");
1154
+ }
1155
+ return withSession.call(this.index, operation) as MaybePromise<R>;
1156
+ }
1157
+
1138
1158
  private ensurePrefetchAccumulator() {
1139
1159
  if (!this._prefetch) {
1140
1160
  this._prefetch = {
@@ -3090,10 +3110,11 @@ export class DocumentIndex<
3090
3110
  | indexerTypes.IndexedResult<IndexedContextOnly<I>>
3091
3111
  | null
3092
3112
  | undefined,
3113
+ orderedSession?: DocumentIndexWriteSession<I>,
3093
3114
  ): Promise<{ context: types.Context; indexable: I }> {
3094
3115
  const existingDefined =
3095
3116
  existing === undefined
3096
- ? await this.index.get(id, {
3117
+ ? await (orderedSession ?? this.index).get(id, {
3097
3118
  shape: INDEX_CONTEXT_SHAPE,
3098
3119
  })
3099
3120
  : existing;
@@ -3106,10 +3127,16 @@ export class DocumentIndex<
3106
3127
  gid: entry.meta.gid,
3107
3128
  size: entry.payload.byteLength,
3108
3129
  });
3109
- return this.putWithContext(value, id, context, {
3110
- replace: existingDefined != null,
3111
- transformFacts: { entryPublicKeys: entry.publicKeys },
3112
- });
3130
+ return this.putWithContext(
3131
+ value,
3132
+ id,
3133
+ context,
3134
+ {
3135
+ replace: existingDefined != null,
3136
+ transformFacts: { entryPublicKeys: entry.publicKeys },
3137
+ },
3138
+ orderedSession,
3139
+ );
3113
3140
  }
3114
3141
 
3115
3142
  public async putWithContext(
@@ -3117,6 +3144,7 @@ export class DocumentIndex<
3117
3144
  id: indexerTypes.IdKey,
3118
3145
  context: types.Context,
3119
3146
  options?: ContextualPutOptions,
3147
+ orderedSession?: DocumentIndexWriteSession<I>,
3120
3148
  ): Promise<{ context: types.Context; indexable: I }> {
3121
3149
  const idString = id.primitive;
3122
3150
  this.cacheResolvedValue(idString, value);
@@ -3129,10 +3157,17 @@ export class DocumentIndex<
3129
3157
  coerceWithContext(value, context);
3130
3158
 
3131
3159
  try {
3132
- const contextualPut = this.transformerIsIdentity
3133
- ? (this.index as ContextualIndexPut<I>).putWithContext
3134
- : undefined;
3135
- if (contextualPut) {
3160
+ const contextualPut =
3161
+ !orderedSession && this.transformerIsIdentity
3162
+ ? (this.index as ContextualIndexPut<I>).putWithContext
3163
+ : undefined;
3164
+ if (orderedSession) {
3165
+ await orderedSession.put(
3166
+ new this.wrappedIndexedType(valueToIndex, context),
3167
+ id,
3168
+ stripEncodedValue(options),
3169
+ );
3170
+ } else if (contextualPut) {
3136
3171
  const encodedValueParts = this.encodeContextualIndexedValueParts(
3137
3172
  options?.encodedValue,
3138
3173
  context,