@peerbit/document 13.1.3 → 13.1.5

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@peerbit/document",
3
- "version": "13.1.3",
3
+ "version": "13.1.5",
4
4
  "description": "Document store implementation",
5
5
  "type": "module",
6
6
  "sideEffects": false,
@@ -59,19 +59,19 @@
59
59
  "@multiformats/multiaddr": "^13.0.1",
60
60
  "p-defer": "^4.0.0",
61
61
  "uint8arrays": "^5.1.0",
62
- "@peerbit/cache": "3.1.0",
63
62
  "@peerbit/indexer-cache": "0.2.8",
64
- "@peerbit/document-interface": "3.2.46",
65
63
  "@peerbit/indexer-interface": "3.0.5",
64
+ "@peerbit/cache": "3.1.0",
65
+ "@peerbit/document-interface": "3.2.47",
66
+ "@peerbit/indexer-simple": "1.2.9",
66
67
  "@peerbit/crypto": "3.1.2",
67
- "@peerbit/indexer-simple": "1.2.8",
68
- "@peerbit/indexer-sqlite3": "3.0.8",
69
- "@peerbit/log": "6.2.3",
68
+ "@peerbit/log": "6.2.4",
70
69
  "@peerbit/logger": "2.0.1",
71
- "@peerbit/program": "6.0.33",
72
- "@peerbit/rpc": "6.1.1",
70
+ "@peerbit/indexer-sqlite3": "3.0.8",
73
71
  "@peerbit/pubsub": "5.3.0",
74
- "@peerbit/shared-log": "13.2.3",
72
+ "@peerbit/program": "6.0.34",
73
+ "@peerbit/rpc": "6.1.2",
74
+ "@peerbit/shared-log": "13.2.5",
75
75
  "@peerbit/stream-interface": "6.0.11"
76
76
  },
77
77
  "optionalDependencies": {
@@ -84,10 +84,10 @@
84
84
  "pidusage": "^4.0.1",
85
85
  "uuid": "^10.0.0",
86
86
  "@peerbit/log-rust": "1.1.2",
87
- "@peerbit/native-backbone": "0.1.2",
88
- "@peerbit/test-utils": "3.1.3",
89
- "peerbit": "5.3.3",
90
- "@peerbit/time": "3.0.0"
87
+ "@peerbit/native-backbone": "0.1.4",
88
+ "@peerbit/time": "3.0.0",
89
+ "@peerbit/test-utils": "3.1.5",
90
+ "peerbit": "5.3.5"
91
91
  },
92
92
  "repository": {
93
93
  "type": "git",
@@ -102,6 +102,7 @@
102
102
  "clean": "aegir clean",
103
103
  "build": "aegir build --no-bundle",
104
104
  "test": "aegir test --target node",
105
+ "test:document-rust-core": "npm run build && PEERBIT_SHARED_LOG_RUST_CORE=1 aegir test -t node --grep '^(?!.*(uses the validated local append path|uses the independent native prepared batch path|uses native shared-log planning for replicated target-none puts|uses commit-only local puts when coordinate persistence is deferred|can delete without being replicator)).*(native conformance guard |operations basic |operations get |operations index |operations search |iterate sort |count approximate |query distribution |returnIndexed |caching |custom index |prefetch )'",
105
106
  "lint": "aegir lint",
106
107
  "test:cov": "aegir test -t node --cov"
107
108
  }
package/src/program.ts CHANGED
@@ -112,6 +112,16 @@ export class NativeDocumentModeError extends Error {
112
112
 
113
113
  type MaybePromise<T> = Promise<T> | T;
114
114
 
115
+ /**
116
+ * True when an error signals that an entry's payload bytes were never
117
+ * materialized on the JS side (a hollow entry backed by a native block store).
118
+ * `DecryptedThing.getValue` throws `Error("Missing data")` in that case. Used to
119
+ * decide whether the auto-mode append/delete read-back should recover the
120
+ * operation from the storage-bytes / block-store path instead.
121
+ */
122
+ const isMissingPayloadDataError = (error: unknown): boolean =>
123
+ error instanceof Error && error.message === "Missing data";
124
+
115
125
  const isPromiseLike = <T>(value: MaybePromise<T>): value is Promise<T> =>
116
126
  !!value && typeof (value as Promise<T>).then === "function";
117
127
 
@@ -2837,17 +2847,44 @@ export class Documents<
2837
2847
  return;
2838
2848
  }
2839
2849
  ensureInitialized?.();
2840
- return entry.getPayloadValue();
2850
+ try {
2851
+ return await entry.getPayloadValue();
2852
+ } catch (error) {
2853
+ // Auto (non-native document) mode with a native block store: the entry
2854
+ // materialized in the entry index can be a hollow shell whose in-memory
2855
+ // payload bytes were never loaded (the native store keeps the block
2856
+ // bytes at the storage layer, not on the JS entry). `getPayloadValue`
2857
+ // then throws "Missing data". The block itself is present, so recover
2858
+ // the operation via the storage-bytes / block-store read path. This is
2859
+ // a no-op for the pure-JS backend, where `getPayloadValue` succeeds.
2860
+ if (!isMissingPayloadDataError(error)) {
2861
+ throw error;
2862
+ }
2863
+ const operation = await this.getPlainEntryOperationFromStorage(entry);
2864
+ if (operation) {
2865
+ return operation;
2866
+ }
2867
+ throw error;
2868
+ }
2841
2869
  }
2842
2870
 
2843
2871
  private async getPlainEntryOperationFromStorage(
2844
2872
  entry: Entry<Operation>,
2845
2873
  ): Promise<Operation | undefined> {
2846
- let storageBytes: Uint8Array;
2874
+ let storageBytes: Uint8Array | undefined;
2847
2875
  try {
2848
2876
  storageBytes =
2849
2877
  Entry.getPreparedStorageBytes(entry) ?? entry.getStorageBytes();
2850
2878
  } catch {
2879
+ // The entry object is hollow (its payload bytes never materialized on
2880
+ // the JS side), so it cannot re-serialize itself. Fall through to the
2881
+ // block-store fallback below.
2882
+ storageBytes = undefined;
2883
+ }
2884
+ // Fall back to the raw block held by the block store, keyed by the entry
2885
+ // hash, whenever the entry could not (or did not) yield its own bytes.
2886
+ storageBytes ??= await this.getEntryStorageBytesFromBlocks(entry);
2887
+ if (!storageBytes) {
2851
2888
  return;
2852
2889
  }
2853
2890
  try {
@@ -2869,6 +2906,21 @@ export class Documents<
2869
2906
  }
2870
2907
  }
2871
2908
 
2909
+ private async getEntryStorageBytesFromBlocks(
2910
+ entry: Entry<Operation>,
2911
+ ): Promise<Uint8Array | undefined> {
2912
+ const hash = entry.hash;
2913
+ if (!hash) {
2914
+ return;
2915
+ }
2916
+ try {
2917
+ const bytes = await this.log.log.blocks.get(hash);
2918
+ return bytes ?? undefined;
2919
+ } catch {
2920
+ return;
2921
+ }
2922
+ }
2923
+
2872
2924
  private getNativeDocumentFieldExtractionPlan(
2873
2925
  path: string | readonly string[],
2874
2926
  ): SimpleDocumentFieldExtractionPlan | undefined {
package/src/search.ts CHANGED
@@ -403,6 +403,7 @@ export type ReachScope = {
403
403
 
404
404
  export type RemoteQueryOptions<Q, R, D> = RPCRequestAllOptions<Q, R> & {
405
405
  replicate?: boolean;
406
+ from?: string[]; // if specified, only query these peers
406
407
  minAge?: number;
407
408
  throwOnMissing?: boolean;
408
409
  retryMissingResponses?: boolean;
@@ -1178,6 +1179,28 @@ export class DocumentIndex<
1178
1179
  }
1179
1180
  }
1180
1181
 
1182
+ /** Head entry safe to borsh-serialize into ResultIndexedValue.entries over the
1183
+ * document RPC. Under the native block store, this._log.log.get can return a
1184
+ * hollow entry whose payload bytes were never materialized on the JS side;
1185
+ * serializing it throws and aborts the whole RPC response. The complete entry is
1186
+ * in the block store keyed by hash, so recover it there. No-op for pure JS. */
1187
+ private async getSerializableHead(
1188
+ hash: string,
1189
+ ): Promise<Entry<any> | undefined> {
1190
+ const head = await this._log.log.get(hash);
1191
+ if (!head?.hash) return head ?? undefined;
1192
+ try {
1193
+ head.getStorageBytes(); // = serialize(head); throws on a hollow native entry
1194
+ return head;
1195
+ } catch {
1196
+ try {
1197
+ return await Entry.fromMultihash(this._log.log.blocks, head.hash);
1198
+ } catch {
1199
+ return head; // block absent (e.g. pruned) — preserve today's behavior
1200
+ }
1201
+ }
1202
+ }
1203
+
1181
1204
  private async wrapPushResults(
1182
1205
  matches: Array<WithContext<T> | WithContext<I>>,
1183
1206
  resolve: boolean,
@@ -1219,7 +1242,7 @@ export class DocumentIndex<
1219
1242
  continue;
1220
1243
  }
1221
1244
 
1222
- const head = await this._log.log.get(indexed.__context.head);
1245
+ const head = await this.getSerializableHead(indexed.__context.head);
1223
1246
  results.push(
1224
1247
  new types.ResultIndexedValue({
1225
1248
  context: indexed.__context,
@@ -1230,7 +1253,7 @@ export class DocumentIndex<
1230
1253
  );
1231
1254
  } else {
1232
1255
  const indexed = match as WithContext<I>;
1233
- const head = await this._log.log.get(indexed.__context.head);
1256
+ const head = await this.getSerializableHead(indexed.__context.head);
1234
1257
  results.push(
1235
1258
  new types.ResultIndexedValue({
1236
1259
  context: indexed.__context,
@@ -1275,7 +1298,7 @@ export class DocumentIndex<
1275
1298
  continue;
1276
1299
  }
1277
1300
 
1278
- const head = await this._log.log.get(entry.value.__context.head);
1301
+ const head = await this.getSerializableHead(entry.value.__context.head);
1279
1302
  results.push(
1280
1303
  new types.ResultIndexedValue({
1281
1304
  context: entry.value.__context,
@@ -1285,7 +1308,7 @@ export class DocumentIndex<
1285
1308
  }),
1286
1309
  );
1287
1310
  } else {
1288
- const head = await this._log.log.get(entry.value.__context.head);
1311
+ const head = await this.getSerializableHead(entry.value.__context.head);
1289
1312
  results.push(
1290
1313
  new types.ResultIndexedValue({
1291
1314
  context: entry.value.__context,
@@ -3652,7 +3675,7 @@ export class DocumentIndex<
3652
3675
  );
3653
3676
  } else {
3654
3677
  const context = result.value.__context;
3655
- const head = await this._log.log.get(context.head);
3678
+ const head = await this.getSerializableHead(context.head);
3656
3679
  if (replicateIndexFlag) {
3657
3680
  if (!head) {
3658
3681
  continue;
@@ -4907,7 +4930,12 @@ export class DocumentIndex<
4907
4930
  ...(typeof options?.remote === "object"
4908
4931
  ? options.remote
4909
4932
  : {}),
4910
- from: fetchOptions?.from ?? initialRemoteTargets,
4933
+ from:
4934
+ fetchOptions?.from ??
4935
+ initialRemoteTargets ??
4936
+ (typeof options?.remote === "object"
4937
+ ? options.remote.from
4938
+ : undefined),
4911
4939
  }
4912
4940
  : false,
4913
4941
  resolve,