@peerbit/document 15.0.13 → 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";
@@ -1088,12 +1093,23 @@ export class Documents<
1088
1093
  private _documentChangeListenerTrackingInitialized = false;
1089
1094
  private _documentBackend!: DocumentBackend<T>;
1090
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
+ }
1091
1106
  private _nativeDocumentIdExtractionPlan?: SimpleDocumentFieldExtractionPlan;
1092
1107
  private _nativeDocumentFieldExtractionPlans?: Map<
1093
1108
  string,
1094
1109
  SimpleDocumentFieldExtractionPlan | undefined
1095
1110
  >;
1096
1111
  private _hasLogTrim = false;
1112
+ private _hasCustomIdResolver = false;
1097
1113
  private idResolver!: (any: any) => indexerTypes.Ideable;
1098
1114
  private domain?: CustomDocumentDomain<InferR<D>>;
1099
1115
  private strictHistory: boolean;
@@ -1131,6 +1147,84 @@ export class Documents<
1131
1147
  );
1132
1148
  }
1133
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
+
1134
1228
  private createNativeDocumentBackendContext(): NativeDocumentBackendContext<
1135
1229
  T,
1136
1230
  I
@@ -2356,6 +2450,7 @@ export class Documents<
2356
2450
  : (obj: any) =>
2357
2451
  indexerTypes.extractFieldValue(obj, idProperty as string[]));
2358
2452
 
2453
+ this._hasCustomIdResolver = options.id != null;
2359
2454
  this.idResolver = idResolver;
2360
2455
  this.strictHistory = options.strictHistory ?? false;
2361
2456
  this._hasLogTrim = options.log?.trim != null;
@@ -2404,7 +2499,9 @@ export class Documents<
2404
2499
  // 7 -> log v9) is retired; the rejection at the top of open() fires for
2405
2500
  // any defined value before this point.
2406
2501
 
2407
- this.domain = options.domain?.(this);
2502
+ this.domain = options.domain
2503
+ ? this.detachDomainEntryCallback(options.domain(this))
2504
+ : undefined;
2408
2505
 
2409
2506
  let keepFunction:
2410
2507
  | ((
@@ -2449,7 +2546,16 @@ export class Documents<
2449
2546
  return false;
2450
2547
  };
2451
2548
  } else {
2452
- 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;
2453
2559
  }
2454
2560
 
2455
2561
  await this.log.open({
@@ -2473,7 +2579,10 @@ export class Documents<
2473
2579
  distributionDebounceTime: options?.distributionDebounceTime,
2474
2580
  strictFullReplicaFallback: false,
2475
2581
  domain: options?.domain
2476
- ? () => options.domain!(this) as unknown as D
2582
+ ? () =>
2583
+ this.detachDomainEntryCallback(
2584
+ options.domain!(this),
2585
+ ) as unknown as D
2477
2586
  : undefined,
2478
2587
  eagerBlocks: options?.eagerBlocks,
2479
2588
  fanout: options?.fanout,
@@ -2565,7 +2674,7 @@ export class Documents<
2565
2674
  }
2566
2675
 
2567
2676
  try {
2568
- let operation: PutOperation | DeleteOperation = l0;
2677
+ const operation: PutOperation | DeleteOperation = l0;
2569
2678
  if (this._optionCanPerform) {
2570
2679
  if (this._optionCanPerformNativePolicy && this.isNativeMode()) {
2571
2680
  return this.nativeCanPerformAllowsAppend(
@@ -2575,22 +2684,22 @@ export class Documents<
2575
2684
  reference?.document,
2576
2685
  );
2577
2686
  }
2578
- let document: T | undefined = reference?.document;
2579
- if (!document) {
2580
- if (isPutOperation(l0)) {
2581
- document =
2582
- this._canAppendDecodedDocuments.get(l0) ??
2583
- this._index.valueEncoding.decoder(l0.data);
2584
- if (!document) {
2585
- return false;
2586
- }
2587
- } else if (isDeleteOperation(l0)) {
2588
- // Nothing to do here by default.
2589
- // Checking if the document exists is not necessary since it
2590
- // might already be deleted.
2591
- } else {
2592
- 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;
2593
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");
2594
2703
  }
2595
2704
  const previousEntries =
2596
2705
  this._optionCanPerformNativePolicy &&
@@ -2598,7 +2707,9 @@ export class Documents<
2598
2707
  canPerformPolicyNeedsPreviousEntries(
2599
2708
  this._optionCanPerformNativePolicy,
2600
2709
  )
2601
- ? await this.resolveCanPerformPreviousEntries(entry)
2710
+ ? (await this.resolveCanPerformPreviousEntries(entry)).map(
2711
+ detachEntryPayloadForCallback,
2712
+ )
2602
2713
  : undefined;
2603
2714
  const deleteValue =
2604
2715
  this._optionCanPerformNativePolicy &&
@@ -2610,20 +2721,22 @@ export class Documents<
2610
2721
  : undefined;
2611
2722
  if (
2612
2723
  !(await this._optionCanPerform(
2613
- isPutOperation(operation)
2614
- ? {
2615
- type: "put",
2616
- value: document!,
2617
- operation,
2618
- entry: entry as unknown as Entry<PutOperation>,
2619
- previousEntries,
2620
- }
2621
- : {
2622
- type: "delete",
2623
- value: deleteValue,
2624
- operation,
2625
- entry: entry as unknown as Entry<DeleteOperation>,
2626
- },
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
+ ),
2627
2740
  ))
2628
2741
  ) {
2629
2742
  return false;
@@ -2712,6 +2825,12 @@ export class Documents<
2712
2825
  return entries;
2713
2826
  }
2714
2827
 
2828
+ private detachDocumentValueForCallback(document: T): T {
2829
+ return this._index.valueEncoding.decoder(
2830
+ this._index.valueEncoding.encoder(document),
2831
+ );
2832
+ }
2833
+
2715
2834
  private async resolveCanPerformDeleteValue(
2716
2835
  operation: DeleteOperation,
2717
2836
  options?: { allowEntryFallback?: boolean },
@@ -2728,12 +2847,15 @@ export class Documents<
2728
2847
  const indexedDocument =
2729
2848
  await this.getLocalIdentityDocumentByHead(existingHead);
2730
2849
  if (indexedDocument) {
2731
- return indexedDocument;
2850
+ return this.detachDocumentValueForCallback(indexedDocument);
2732
2851
  }
2733
2852
  const indexedPolicyDocument =
2734
2853
  await this.getLocalIndexedDocumentForNativeDeletePolicy(key);
2735
2854
  if (indexedPolicyDocument) {
2736
- return indexedPolicyDocument;
2855
+ return deserialize(
2856
+ serialize(indexedPolicyDocument),
2857
+ this._index.indexedType,
2858
+ ) as unknown as T;
2737
2859
  }
2738
2860
  if (options?.allowEntryFallback === false) {
2739
2861
  return;
@@ -2745,7 +2867,9 @@ export class Documents<
2745
2867
  if (!isPutOperation(existingOperation)) {
2746
2868
  return;
2747
2869
  }
2748
- return this._index.valueEncoding.decoder(existingOperation.data);
2870
+ return this._index.valueEncoding.decoder(
2871
+ new Uint8Array(existingOperation.data),
2872
+ );
2749
2873
  }
2750
2874
 
2751
2875
  protected async _canAppend(
@@ -2796,17 +2920,32 @@ export class Documents<
2796
2920
  if (isPutOperation(operation)) {
2797
2921
  // check nexts
2798
2922
  const putOperation = operation as PutOperation;
2923
+ let decodedDocumentForCanPerform: T | undefined;
2924
+ let hasDecodedDocumentForCanPerform = false;
2799
2925
  let keyValue: indexerTypes.Ideable | undefined;
2800
2926
  if (reference?.document) {
2801
- 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
+ );
2802
2934
  } else {
2803
2935
  keyValue = await this.getNativeDocumentIdFromPutOperation(putOperation);
2804
2936
  if (keyValue == null) {
2805
2937
  if (this.isNativeMode()) {
2806
2938
  return false;
2807
2939
  }
2808
- const value = this.index.valueEncoding.decoder(putOperation.data);
2809
- 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
+ }
2810
2949
  keyValue = this.idResolver(value);
2811
2950
  }
2812
2951
  }
@@ -2843,7 +2982,11 @@ export class Documents<
2843
2982
  return false; // can not append to immutable document
2844
2983
  }
2845
2984
 
2846
- return putOperation;
2985
+ return this.acceptPutOperation(
2986
+ putOperation,
2987
+ decodedDocumentForCanPerform,
2988
+ hasDecodedDocumentForCanPerform,
2989
+ );
2847
2990
  } else {
2848
2991
  if (this.strictHistory) {
2849
2992
  // make sure that the next pointer exist and points to the existing documents
@@ -2851,7 +2994,11 @@ export class Documents<
2851
2994
  return false;
2852
2995
  }
2853
2996
  if (entry.meta.next[0] === existingContext.head) {
2854
- return putOperation;
2997
+ return this.acceptPutOperation(
2998
+ putOperation,
2999
+ decodedDocumentForCanPerform,
3000
+ hasDecodedDocumentForCanPerform,
3001
+ );
2855
3002
  }
2856
3003
 
2857
3004
  const prevEntry = await this.log.log.entryIndex.get(
@@ -2866,14 +3013,29 @@ export class Documents<
2866
3013
  }
2867
3014
  const referenceHistoryCorrectly =
2868
3015
  await pointsToHistory(prevEntry);
2869
- return referenceHistoryCorrectly ? putOperation : false;
3016
+ return referenceHistoryCorrectly
3017
+ ? this.acceptPutOperation(
3018
+ putOperation,
3019
+ decodedDocumentForCanPerform,
3020
+ hasDecodedDocumentForCanPerform,
3021
+ )
3022
+ : false;
2870
3023
  } else {
2871
- return putOperation;
3024
+ return this.acceptPutOperation(
3025
+ putOperation,
3026
+ decodedDocumentForCanPerform,
3027
+ hasDecodedDocumentForCanPerform,
3028
+ );
2872
3029
  }
2873
3030
  }
2874
3031
  } else {
2875
3032
  // Keep existing behavior: next pointers may express document dependencies.
2876
3033
  }
3034
+ return this.acceptPutOperation(
3035
+ putOperation,
3036
+ decodedDocumentForCanPerform,
3037
+ hasDecodedDocumentForCanPerform,
3038
+ );
2877
3039
  } else if (isDeleteOperation(operation)) {
2878
3040
  if (entry.meta.next.length !== 1) {
2879
3041
  return false;
@@ -2924,7 +3086,6 @@ export class Documents<
2924
3086
  throw new Error("Unsupported operation");
2925
3087
  }
2926
3088
 
2927
- return operation;
2928
3089
  } catch (error) {
2929
3090
  if (error instanceof AccessError) {
2930
3091
  return false; // we cant index because we can not decrypt
@@ -5459,14 +5620,25 @@ export class Documents<
5459
5620
  }
5460
5621
  }
5461
5622
  let value =
5462
- (isReferencedAppendEntry && reference?.document) ||
5463
- this.index.valueEncoding.decoder(payload.data);
5623
+ isReferencedAppendEntry && reference?.document
5624
+ ? reference.document
5625
+ : this.index.valueEncoding.decoder(
5626
+ new Uint8Array(payload.data),
5627
+ );
5464
5628
 
5465
5629
  // get index key from value
5466
5630
  const key =
5467
5631
  isReferencedAppendEntry && reference?.key
5468
5632
  ? reference.key
5469
- : 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
+ );
5470
5642
 
5471
5643
  // document is already updated with more recent entry
5472
5644
  if (modified.has(key.primitive)) {