@peerbit/document 15.0.13 → 15.0.15

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/domain.ts CHANGED
@@ -9,6 +9,7 @@ import {
9
9
  type ReplicationDomain,
10
10
  type SharedLog,
11
11
  } from "@peerbit/shared-log";
12
+ import { detachEntryForCallback } from "./callback-detachment.js";
12
13
  import { type Operation, isPutOperation } from "./operation.js";
13
14
  import type { DocumentIndex } from "./search.js";
14
15
 
@@ -93,8 +94,12 @@ export const createDocumentDomain =
93
94
  ): ((db: DB) => CustomDocumentDomain<InferR<DB>>) =>
94
95
  (db: DB) => {
95
96
  let maxValue = args.resolution === "u32" ? MAX_U32 : MAX_U64;
97
+ const detachEntry = (
98
+ entry: ShallowEntry | Entry<Operation> | EntryReplicated<any>,
99
+ ) => detachEntryForCallback(entry);
96
100
  let fromEntry = (args as FromEntry<InferR<DB>>).fromEntry
97
- ? (args as FromEntry<InferR<DB>>).fromEntry!
101
+ ? (entry: ShallowEntry | Entry<Operation> | EntryReplicated<any>) =>
102
+ (args as FromEntry<InferR<DB>>).fromEntry!(detachEntry(entry))
98
103
  : async (
99
104
  entry: ShallowEntry | Entry<Operation> | EntryReplicated<any>,
100
105
  ) => {
@@ -106,11 +111,13 @@ export const createDocumentDomain =
106
111
  if (!item) {
107
112
  logger.error("Item not found");
108
113
  } else if (isPutOperation(item)) {
109
- document = db.index.valueEncoding.decoder(item.data);
114
+ document = db.index.valueEncoding.decoder(
115
+ new Uint8Array(item.data),
116
+ );
110
117
  }
111
118
  return (args as FromValue<any, any>).fromValue!(
112
119
  document,
113
- entry,
120
+ detachEntry(entry),
114
121
  ) as NumberFromType<InferR<DB>>;
115
122
  };
116
123
  return {
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,11 @@ import {
44
45
  SharedLog,
45
46
  type SharedLogOptions,
46
47
  } from "@peerbit/shared-log";
48
+ import {
49
+ detachCanPerformCallbackProperties,
50
+ detachEntryForCallback,
51
+ detachEntryPayloadForCallback,
52
+ } from "./callback-detachment.js";
47
53
  import { MAX_BATCH_SIZE } from "./constants.js";
48
54
  import type { CustomDocumentDomain } from "./domain.js";
49
55
  import type { DocumentEvents, DocumentsChange } from "./events.js";
@@ -1088,12 +1094,23 @@ export class Documents<
1088
1094
  private _documentChangeListenerTrackingInitialized = false;
1089
1095
  private _documentBackend!: DocumentBackend<T>;
1090
1096
  private _canAppendDecodedDocuments = new WeakMap<PutOperation, T>();
1097
+ private acceptPutOperation(
1098
+ operation: PutOperation,
1099
+ document: T | undefined,
1100
+ hasDocument: boolean,
1101
+ ): PutOperation {
1102
+ if (hasDocument) {
1103
+ this._canAppendDecodedDocuments.set(operation, document!);
1104
+ }
1105
+ return operation;
1106
+ }
1091
1107
  private _nativeDocumentIdExtractionPlan?: SimpleDocumentFieldExtractionPlan;
1092
1108
  private _nativeDocumentFieldExtractionPlans?: Map<
1093
1109
  string,
1094
1110
  SimpleDocumentFieldExtractionPlan | undefined
1095
1111
  >;
1096
1112
  private _hasLogTrim = false;
1113
+ private _hasCustomIdResolver = false;
1097
1114
  private idResolver!: (any: any) => indexerTypes.Ideable;
1098
1115
  private domain?: CustomDocumentDomain<InferR<D>>;
1099
1116
  private strictHistory: boolean;
@@ -1131,6 +1148,99 @@ export class Documents<
1131
1148
  );
1132
1149
  }
1133
1150
 
1151
+ private detachDomainEntryCallback(
1152
+ domain: CustomDocumentDomain<InferR<D>>,
1153
+ ): CustomDocumentDomain<InferR<D>> {
1154
+ const boundMethods = new Map<
1155
+ PropertyKey,
1156
+ { source: Function; bound: Function }
1157
+ >();
1158
+ const detachedFromEntry = (
1159
+ entry: Parameters<CustomDocumentDomain<InferR<D>>["fromEntry"]>[0],
1160
+ ) => {
1161
+ const fromEntry = Reflect.get(
1162
+ domain,
1163
+ "fromEntry",
1164
+ domain,
1165
+ ) as CustomDocumentDomain<InferR<D>>["fromEntry"];
1166
+ return Reflect.apply(fromEntry, domain, [detachEntryForCallback(entry)]);
1167
+ };
1168
+ const facade = Object.create(
1169
+ Object.getPrototypeOf(domain),
1170
+ ) as CustomDocumentDomain<InferR<D>>;
1171
+ let callbackDomain: CustomDocumentDomain<InferR<D>>;
1172
+ callbackDomain = new Proxy(facade, {
1173
+ get(_target, property) {
1174
+ if (property === "fromEntry") {
1175
+ return detachedFromEntry;
1176
+ }
1177
+ if (property === "valueOf") {
1178
+ return () => callbackDomain;
1179
+ }
1180
+ const value = Reflect.get(domain, property, domain);
1181
+ if (property === "constructor" || typeof value !== "function") {
1182
+ return value;
1183
+ }
1184
+ const existing = boundMethods.get(property);
1185
+ if (existing && existing.source === value) {
1186
+ return existing.bound;
1187
+ }
1188
+ const bound = value.bind(domain);
1189
+ boundMethods.set(property, { source: value, bound });
1190
+ return bound;
1191
+ },
1192
+ set(_target, property, value) {
1193
+ return Reflect.set(domain, property, value, domain);
1194
+ },
1195
+ has(_target, property) {
1196
+ return Reflect.has(domain, property);
1197
+ },
1198
+ ownKeys() {
1199
+ return Reflect.ownKeys(domain);
1200
+ },
1201
+ getOwnPropertyDescriptor(_target, property) {
1202
+ const descriptor = Reflect.getOwnPropertyDescriptor(domain, property);
1203
+ if (!descriptor) {
1204
+ return undefined;
1205
+ }
1206
+ if (property === "fromEntry") {
1207
+ return "value" in descriptor
1208
+ ? {
1209
+ ...descriptor,
1210
+ value: detachedFromEntry,
1211
+ configurable: true,
1212
+ }
1213
+ : {
1214
+ ...descriptor,
1215
+ get: () => detachedFromEntry,
1216
+ configurable: true,
1217
+ };
1218
+ }
1219
+ return { ...descriptor, configurable: true };
1220
+ },
1221
+ });
1222
+ return callbackDomain;
1223
+ }
1224
+
1225
+ private detachTrimEntryCallback(
1226
+ option: TrimOptions | undefined,
1227
+ ): TrimOptions | undefined {
1228
+ const filter = option?.filter;
1229
+ if (!option || !filter?.canTrim) {
1230
+ return option;
1231
+ }
1232
+ const canTrim = filter.canTrim;
1233
+ return {
1234
+ ...option,
1235
+ filter: {
1236
+ ...filter,
1237
+ canTrim: function (this: unknown, entry: ShallowEntry) {
1238
+ return Reflect.apply(canTrim, this, [detachEntryForCallback(entry)]);
1239
+ },
1240
+ },
1241
+ } as TrimOptions;
1242
+ }
1243
+
1134
1244
  private createNativeDocumentBackendContext(): NativeDocumentBackendContext<
1135
1245
  T,
1136
1246
  I
@@ -2356,6 +2466,7 @@ export class Documents<
2356
2466
  : (obj: any) =>
2357
2467
  indexerTypes.extractFieldValue(obj, idProperty as string[]));
2358
2468
 
2469
+ this._hasCustomIdResolver = options.id != null;
2359
2470
  this.idResolver = idResolver;
2360
2471
  this.strictHistory = options.strictHistory ?? false;
2361
2472
  this._hasLogTrim = options.log?.trim != null;
@@ -2404,7 +2515,9 @@ export class Documents<
2404
2515
  // 7 -> log v9) is retired; the rejection at the top of open() fires for
2405
2516
  // any defined value before this point.
2406
2517
 
2407
- this.domain = options.domain?.(this);
2518
+ this.domain = options.domain
2519
+ ? this.detachDomainEntryCallback(options.domain(this))
2520
+ : undefined;
2408
2521
 
2409
2522
  let keepFunction:
2410
2523
  | ((
@@ -2449,7 +2562,12 @@ export class Documents<
2449
2562
  return false;
2450
2563
  };
2451
2564
  } else {
2452
- keepFunction = options?.keep;
2565
+ const keep = options?.keep;
2566
+ keepFunction = keep
2567
+ ? function (this: unknown, entry) {
2568
+ return Reflect.apply(keep, this, [detachEntryForCallback(entry)]);
2569
+ }
2570
+ : undefined;
2453
2571
  }
2454
2572
 
2455
2573
  await this.log.open({
@@ -2457,7 +2575,7 @@ export class Documents<
2457
2575
  canReplicate: options?.canReplicate,
2458
2576
  canAppend: this.canAppend.bind(this),
2459
2577
  onChange: this.handleChanges.bind(this),
2460
- trim: options?.log?.trim,
2578
+ trim: this.detachTrimEntryCallback(options?.log?.trim),
2461
2579
  appendDurability: options?.appendDurability,
2462
2580
  nativeBackbone: options?.nativeBackbone,
2463
2581
  nativeGraph: options?.nativeGraph,
@@ -2473,7 +2591,10 @@ export class Documents<
2473
2591
  distributionDebounceTime: options?.distributionDebounceTime,
2474
2592
  strictFullReplicaFallback: false,
2475
2593
  domain: options?.domain
2476
- ? () => options.domain!(this) as unknown as D
2594
+ ? () =>
2595
+ this.detachDomainEntryCallback(
2596
+ options.domain!(this),
2597
+ ) as unknown as D
2477
2598
  : undefined,
2478
2599
  eagerBlocks: options?.eagerBlocks,
2479
2600
  fanout: options?.fanout,
@@ -2565,7 +2686,7 @@ export class Documents<
2565
2686
  }
2566
2687
 
2567
2688
  try {
2568
- let operation: PutOperation | DeleteOperation = l0;
2689
+ const operation: PutOperation | DeleteOperation = l0;
2569
2690
  if (this._optionCanPerform) {
2570
2691
  if (this._optionCanPerformNativePolicy && this.isNativeMode()) {
2571
2692
  return this.nativeCanPerformAllowsAppend(
@@ -2575,22 +2696,22 @@ export class Documents<
2575
2696
  reference?.document,
2576
2697
  );
2577
2698
  }
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");
2699
+ let document: T | undefined;
2700
+ if (isPutOperation(l0)) {
2701
+ const cachedDocument = this._canAppendDecodedDocuments.get(l0);
2702
+ this._canAppendDecodedDocuments.delete(l0);
2703
+ document =
2704
+ cachedDocument ??
2705
+ this._index.valueEncoding.decoder(new Uint8Array(l0.data));
2706
+ if (!document) {
2707
+ return false;
2593
2708
  }
2709
+ } else if (isDeleteOperation(l0)) {
2710
+ // Nothing to do here by default.
2711
+ // Checking if the document exists is not necessary since it
2712
+ // might already be deleted.
2713
+ } else {
2714
+ throw new Error("Unsupported operation");
2594
2715
  }
2595
2716
  const previousEntries =
2596
2717
  this._optionCanPerformNativePolicy &&
@@ -2598,7 +2719,9 @@ export class Documents<
2598
2719
  canPerformPolicyNeedsPreviousEntries(
2599
2720
  this._optionCanPerformNativePolicy,
2600
2721
  )
2601
- ? await this.resolveCanPerformPreviousEntries(entry)
2722
+ ? (await this.resolveCanPerformPreviousEntries(entry)).map(
2723
+ detachEntryPayloadForCallback,
2724
+ )
2602
2725
  : undefined;
2603
2726
  const deleteValue =
2604
2727
  this._optionCanPerformNativePolicy &&
@@ -2610,20 +2733,22 @@ export class Documents<
2610
2733
  : undefined;
2611
2734
  if (
2612
2735
  !(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
- },
2736
+ detachCanPerformCallbackProperties(
2737
+ isPutOperation(operation)
2738
+ ? {
2739
+ type: "put",
2740
+ value: document!,
2741
+ operation,
2742
+ entry: entry as unknown as Entry<PutOperation>,
2743
+ previousEntries,
2744
+ }
2745
+ : {
2746
+ type: "delete",
2747
+ value: deleteValue,
2748
+ operation,
2749
+ entry: entry as unknown as Entry<DeleteOperation>,
2750
+ },
2751
+ ),
2627
2752
  ))
2628
2753
  ) {
2629
2754
  return false;
@@ -2712,6 +2837,12 @@ export class Documents<
2712
2837
  return entries;
2713
2838
  }
2714
2839
 
2840
+ private detachDocumentValueForCallback(document: T): T {
2841
+ return this._index.valueEncoding.decoder(
2842
+ this._index.valueEncoding.encoder(document),
2843
+ );
2844
+ }
2845
+
2715
2846
  private async resolveCanPerformDeleteValue(
2716
2847
  operation: DeleteOperation,
2717
2848
  options?: { allowEntryFallback?: boolean },
@@ -2728,12 +2859,15 @@ export class Documents<
2728
2859
  const indexedDocument =
2729
2860
  await this.getLocalIdentityDocumentByHead(existingHead);
2730
2861
  if (indexedDocument) {
2731
- return indexedDocument;
2862
+ return this.detachDocumentValueForCallback(indexedDocument);
2732
2863
  }
2733
2864
  const indexedPolicyDocument =
2734
2865
  await this.getLocalIndexedDocumentForNativeDeletePolicy(key);
2735
2866
  if (indexedPolicyDocument) {
2736
- return indexedPolicyDocument;
2867
+ return deserialize(
2868
+ serialize(indexedPolicyDocument),
2869
+ this._index.indexedType,
2870
+ ) as unknown as T;
2737
2871
  }
2738
2872
  if (options?.allowEntryFallback === false) {
2739
2873
  return;
@@ -2745,7 +2879,9 @@ export class Documents<
2745
2879
  if (!isPutOperation(existingOperation)) {
2746
2880
  return;
2747
2881
  }
2748
- return this._index.valueEncoding.decoder(existingOperation.data);
2882
+ return this._index.valueEncoding.decoder(
2883
+ new Uint8Array(existingOperation.data),
2884
+ );
2749
2885
  }
2750
2886
 
2751
2887
  protected async _canAppend(
@@ -2796,17 +2932,32 @@ export class Documents<
2796
2932
  if (isPutOperation(operation)) {
2797
2933
  // check nexts
2798
2934
  const putOperation = operation as PutOperation;
2935
+ let decodedDocumentForCanPerform: T | undefined;
2936
+ let hasDecodedDocumentForCanPerform = false;
2799
2937
  let keyValue: indexerTypes.Ideable | undefined;
2800
2938
  if (reference?.document) {
2801
- keyValue = this.idResolver(reference.document);
2939
+ keyValue = this.idResolver(
2940
+ this._hasCustomIdResolver
2941
+ ? this.index.valueEncoding.decoder(
2942
+ new Uint8Array(putOperation.data),
2943
+ )
2944
+ : reference.document,
2945
+ );
2802
2946
  } else {
2803
2947
  keyValue = await this.getNativeDocumentIdFromPutOperation(putOperation);
2804
2948
  if (keyValue == null) {
2805
2949
  if (this.isNativeMode()) {
2806
2950
  return false;
2807
2951
  }
2808
- const value = this.index.valueEncoding.decoder(putOperation.data);
2809
- this._canAppendDecodedDocuments.set(putOperation, value);
2952
+ const value = this.index.valueEncoding.decoder(
2953
+ this._hasCustomIdResolver || this._optionCanPerform
2954
+ ? new Uint8Array(putOperation.data)
2955
+ : putOperation.data,
2956
+ );
2957
+ if (this._optionCanPerform && !this._hasCustomIdResolver) {
2958
+ decodedDocumentForCanPerform = value;
2959
+ hasDecodedDocumentForCanPerform = true;
2960
+ }
2810
2961
  keyValue = this.idResolver(value);
2811
2962
  }
2812
2963
  }
@@ -2843,7 +2994,11 @@ export class Documents<
2843
2994
  return false; // can not append to immutable document
2844
2995
  }
2845
2996
 
2846
- return putOperation;
2997
+ return this.acceptPutOperation(
2998
+ putOperation,
2999
+ decodedDocumentForCanPerform,
3000
+ hasDecodedDocumentForCanPerform,
3001
+ );
2847
3002
  } else {
2848
3003
  if (this.strictHistory) {
2849
3004
  // make sure that the next pointer exist and points to the existing documents
@@ -2851,7 +3006,11 @@ export class Documents<
2851
3006
  return false;
2852
3007
  }
2853
3008
  if (entry.meta.next[0] === existingContext.head) {
2854
- return putOperation;
3009
+ return this.acceptPutOperation(
3010
+ putOperation,
3011
+ decodedDocumentForCanPerform,
3012
+ hasDecodedDocumentForCanPerform,
3013
+ );
2855
3014
  }
2856
3015
 
2857
3016
  const prevEntry = await this.log.log.entryIndex.get(
@@ -2866,14 +3025,29 @@ export class Documents<
2866
3025
  }
2867
3026
  const referenceHistoryCorrectly =
2868
3027
  await pointsToHistory(prevEntry);
2869
- return referenceHistoryCorrectly ? putOperation : false;
3028
+ return referenceHistoryCorrectly
3029
+ ? this.acceptPutOperation(
3030
+ putOperation,
3031
+ decodedDocumentForCanPerform,
3032
+ hasDecodedDocumentForCanPerform,
3033
+ )
3034
+ : false;
2870
3035
  } else {
2871
- return putOperation;
3036
+ return this.acceptPutOperation(
3037
+ putOperation,
3038
+ decodedDocumentForCanPerform,
3039
+ hasDecodedDocumentForCanPerform,
3040
+ );
2872
3041
  }
2873
3042
  }
2874
3043
  } else {
2875
3044
  // Keep existing behavior: next pointers may express document dependencies.
2876
3045
  }
3046
+ return this.acceptPutOperation(
3047
+ putOperation,
3048
+ decodedDocumentForCanPerform,
3049
+ hasDecodedDocumentForCanPerform,
3050
+ );
2877
3051
  } else if (isDeleteOperation(operation)) {
2878
3052
  if (entry.meta.next.length !== 1) {
2879
3053
  return false;
@@ -2924,7 +3098,6 @@ export class Documents<
2924
3098
  throw new Error("Unsupported operation");
2925
3099
  }
2926
3100
 
2927
- return operation;
2928
3101
  } catch (error) {
2929
3102
  if (error instanceof AccessError) {
2930
3103
  return false; // we cant index because we can not decrypt
@@ -5459,14 +5632,25 @@ export class Documents<
5459
5632
  }
5460
5633
  }
5461
5634
  let value =
5462
- (isReferencedAppendEntry && reference?.document) ||
5463
- this.index.valueEncoding.decoder(payload.data);
5635
+ isReferencedAppendEntry && reference?.document
5636
+ ? reference.document
5637
+ : this.index.valueEncoding.decoder(
5638
+ new Uint8Array(payload.data),
5639
+ );
5464
5640
 
5465
5641
  // get index key from value
5466
5642
  const key =
5467
5643
  isReferencedAppendEntry && reference?.key
5468
5644
  ? reference.key
5469
- : indexerTypes.toId(this.idResolver(value));
5645
+ : indexerTypes.toId(
5646
+ this.idResolver(
5647
+ this._hasCustomIdResolver
5648
+ ? this.index.valueEncoding.decoder(
5649
+ new Uint8Array(payload.data),
5650
+ )
5651
+ : value,
5652
+ ),
5653
+ );
5470
5654
 
5471
5655
  // document is already updated with more recent entry
5472
5656
  if (modified.has(key.primitive)) {
package/src/search.ts CHANGED
@@ -44,6 +44,7 @@ import { AbortError, TimeoutError, waitFor } from "@peerbit/time";
44
44
  import pDefer, { type DeferredPromise } from "p-defer";
45
45
  import { concat, equals, fromString } from "uint8arrays";
46
46
  import { copySerialization } from "./borsh.js";
47
+ import { detachPublicKeysForCallback } from "./callback-detachment.js";
47
48
  import { MAX_BATCH_SIZE } from "./constants.js";
48
49
  import type { DocumentEvents, DocumentsChange } from "./events.js";
49
50
  import type { QueryPredictor } from "./most-common-query-predictor.js";
@@ -74,6 +75,7 @@ import {
74
75
  canPrepareDocumentTransformWithAppendFacts,
75
76
  documentTransformPreservesFieldPath,
76
77
  getDocumentTransformDescriptor,
78
+ isBuiltInDocumentTransformer,
77
79
  } from "./transform.js";
78
80
 
79
81
  const WARNING_WHEN_ITERATING_FOR_MORE_THAN = 1e5;
@@ -1050,6 +1052,7 @@ export class DocumentIndex<
1050
1052
  // transform options
1051
1053
  transformer: Transformer<T, I>;
1052
1054
  private transformerIsIdentity = false;
1055
+ private detachTransformerFacts = false;
1053
1056
  private nativeTransformDescriptor?: DocumentTransformDescriptor;
1054
1057
  private nativeTransformProjectionPlan?: SimpleDocumentProjectionPlan;
1055
1058
  private nativeBackboneDocumentProjection?: NativeBackboneDocumentProjection;
@@ -1631,6 +1634,9 @@ export class DocumentIndex<
1631
1634
  this.nativeTransformDescriptor = hasTransformFunction
1632
1635
  ? getDocumentTransformDescriptor(transformOptions.transform)
1633
1636
  : undefined;
1637
+ this.detachTransformerFacts =
1638
+ hasTransformFunction &&
1639
+ !isBuiltInDocumentTransformer(transformOptions.transform);
1634
1640
  this.nativeTransformProjectionPlan = createSimpleProjectionPlan(
1635
1641
  getSchema(this.documentType),
1636
1642
  indexedSchema,
@@ -1827,6 +1833,36 @@ export class DocumentIndex<
1827
1833
  );
1828
1834
  }
1829
1835
 
1836
+ private transformWithDetachedFacts(
1837
+ value: T,
1838
+ context: types.Context,
1839
+ facts?: DocumentTransformFacts,
1840
+ ): MaybePromise<I> {
1841
+ let callbackFacts = facts;
1842
+ if (facts?.entryPublicKeys && this.detachTransformerFacts) {
1843
+ const sourceKeys = facts.entryPublicKeys;
1844
+ let callbackKeys: readonly PublicSignKey[] | undefined;
1845
+ let resolved = false;
1846
+ callbackFacts = { ...facts };
1847
+ Object.defineProperty(callbackFacts, "entryPublicKeys", {
1848
+ configurable: true,
1849
+ enumerable: true,
1850
+ get: () => {
1851
+ if (!resolved) {
1852
+ callbackKeys = detachPublicKeysForCallback(sourceKeys);
1853
+ resolved = true;
1854
+ }
1855
+ return callbackKeys;
1856
+ },
1857
+ set: (value: readonly PublicSignKey[] | undefined) => {
1858
+ callbackKeys = value;
1859
+ resolved = true;
1860
+ },
1861
+ });
1862
+ }
1863
+ return this.transformer(value, context, callbackFacts);
1864
+ }
1865
+
1830
1866
  private prepareNativeBackboneDocumentIndexCommit(
1831
1867
  value: T,
1832
1868
  encodedDocument: Uint8Array,
@@ -1851,7 +1887,7 @@ export class DocumentIndex<
1851
1887
  },
1852
1888
  getIndexable: () => {
1853
1889
  if (!hasCached) {
1854
- const transformed = this.transformer(
1890
+ const transformed = this.transformWithDetachedFacts(
1855
1891
  value,
1856
1892
  projectionContext as types.Context,
1857
1893
  transformFacts,
@@ -1878,7 +1914,7 @@ export class DocumentIndex<
1878
1914
  ) {
1879
1915
  return;
1880
1916
  }
1881
- const transformed = this.transformer(
1917
+ const transformed = this.transformWithDetachedFacts(
1882
1918
  value,
1883
1919
  undefined as unknown as types.Context,
1884
1920
  transformFacts,
@@ -1924,7 +1960,7 @@ export class DocumentIndex<
1924
1960
  },
1925
1961
  getIndexable: () => {
1926
1962
  if (!hasCached) {
1927
- const transformed = this.transformer(
1963
+ const transformed = this.transformWithDetachedFacts(
1928
1964
  value,
1929
1965
  projectionContext as types.Context,
1930
1966
  transformFacts,
@@ -1961,7 +1997,11 @@ export class DocumentIndex<
1961
1997
  ) {
1962
1998
  return;
1963
1999
  }
1964
- const transformed = this.transformer(value, context, transformFacts);
2000
+ const transformed = this.transformWithDetachedFacts(
2001
+ value,
2002
+ context,
2003
+ transformFacts,
2004
+ );
1965
2005
  if (isPromiseLike(transformed)) {
1966
2006
  return;
1967
2007
  }
@@ -3150,7 +3190,11 @@ export class DocumentIndex<
3150
3190
  this.cacheResolvedValue(idString, value);
3151
3191
  const valueToIndex = this.transformerIsIdentity
3152
3192
  ? (value as any as I)
3153
- : await this.transformer(value, context, options?.transformFacts);
3193
+ : await this.transformWithDetachedFacts(
3194
+ value,
3195
+ context,
3196
+ options?.transformFacts,
3197
+ );
3154
3198
 
3155
3199
  coerceWithIndexed(value, valueToIndex);
3156
3200
 
package/src/transform.ts CHANGED
@@ -43,6 +43,7 @@ export type DocumentTransformDescriptor =
43
43
  const NATIVE_DOCUMENT_TRANSFORM = Symbol.for(
44
44
  "@peerbit/document/native-document-transform",
45
45
  );
46
+ const builtInDocumentTransformers = new WeakSet<Function>();
46
47
 
47
48
  type DescribedDocumentTransformer<T, I> = DocumentTransformer<T, I> & {
48
49
  readonly [NATIVE_DOCUMENT_TRANSFORM]?: DocumentTransformDescriptor;
@@ -142,9 +143,15 @@ const attachDocumentTransformDescriptor = <T, I>(
142
143
  writable: false,
143
144
  configurable: false,
144
145
  });
146
+ builtInDocumentTransformers.add(fn);
145
147
  return fn;
146
148
  };
147
149
 
150
+ /** Distinguish library-created transforms from forgeable public descriptors. */
151
+ export const isBuiltInDocumentTransformer = <T, I>(
152
+ transformer: DocumentTransformer<T, I> | undefined,
153
+ ): boolean => !!transformer && builtInDocumentTransformers.has(transformer);
154
+
148
155
  export const getDocumentTransformDescriptor = <T, I>(
149
156
  transformer: DocumentTransformer<T, I> | undefined,
150
157
  ): DocumentTransformDescriptor | undefined =>