@peerbit/document 13.1.4 → 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.4",
3
+ "version": "13.1.5",
4
4
  "description": "Document store implementation",
5
5
  "type": "module",
6
6
  "sideEffects": false,
@@ -59,20 +59,20 @@
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
- "@peerbit/crypto": "3.1.2",
64
- "@peerbit/document-interface": "3.2.46",
65
62
  "@peerbit/indexer-cache": "0.2.8",
66
63
  "@peerbit/indexer-interface": "3.0.5",
67
- "@peerbit/indexer-simple": "1.2.8",
68
- "@peerbit/log": "6.2.3",
64
+ "@peerbit/cache": "3.1.0",
65
+ "@peerbit/document-interface": "3.2.47",
66
+ "@peerbit/indexer-simple": "1.2.9",
67
+ "@peerbit/crypto": "3.1.2",
68
+ "@peerbit/log": "6.2.4",
69
+ "@peerbit/logger": "2.0.1",
69
70
  "@peerbit/indexer-sqlite3": "3.0.8",
70
- "@peerbit/program": "6.0.33",
71
71
  "@peerbit/pubsub": "5.3.0",
72
- "@peerbit/shared-log": "13.2.4",
73
- "@peerbit/rpc": "6.1.1",
74
- "@peerbit/stream-interface": "6.0.11",
75
- "@peerbit/logger": "2.0.1"
72
+ "@peerbit/program": "6.0.34",
73
+ "@peerbit/rpc": "6.1.2",
74
+ "@peerbit/shared-log": "13.2.5",
75
+ "@peerbit/stream-interface": "6.0.11"
76
76
  },
77
77
  "optionalDependencies": {
78
78
  "@peerbit/document-rust": "0.1.1"
@@ -83,11 +83,11 @@
83
83
  "@types/pidusage": "^2.0.5",
84
84
  "pidusage": "^4.0.1",
85
85
  "uuid": "^10.0.0",
86
- "@peerbit/native-backbone": "0.1.3",
87
- "@peerbit/test-utils": "3.1.4",
88
86
  "@peerbit/log-rust": "1.1.2",
89
- "peerbit": "5.3.4",
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
@@ -1179,6 +1179,28 @@ export class DocumentIndex<
1179
1179
  }
1180
1180
  }
1181
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
+
1182
1204
  private async wrapPushResults(
1183
1205
  matches: Array<WithContext<T> | WithContext<I>>,
1184
1206
  resolve: boolean,
@@ -1220,7 +1242,7 @@ export class DocumentIndex<
1220
1242
  continue;
1221
1243
  }
1222
1244
 
1223
- const head = await this._log.log.get(indexed.__context.head);
1245
+ const head = await this.getSerializableHead(indexed.__context.head);
1224
1246
  results.push(
1225
1247
  new types.ResultIndexedValue({
1226
1248
  context: indexed.__context,
@@ -1231,7 +1253,7 @@ export class DocumentIndex<
1231
1253
  );
1232
1254
  } else {
1233
1255
  const indexed = match as WithContext<I>;
1234
- const head = await this._log.log.get(indexed.__context.head);
1256
+ const head = await this.getSerializableHead(indexed.__context.head);
1235
1257
  results.push(
1236
1258
  new types.ResultIndexedValue({
1237
1259
  context: indexed.__context,
@@ -1276,7 +1298,7 @@ export class DocumentIndex<
1276
1298
  continue;
1277
1299
  }
1278
1300
 
1279
- const head = await this._log.log.get(entry.value.__context.head);
1301
+ const head = await this.getSerializableHead(entry.value.__context.head);
1280
1302
  results.push(
1281
1303
  new types.ResultIndexedValue({
1282
1304
  context: entry.value.__context,
@@ -1286,7 +1308,7 @@ export class DocumentIndex<
1286
1308
  }),
1287
1309
  );
1288
1310
  } else {
1289
- const head = await this._log.log.get(entry.value.__context.head);
1311
+ const head = await this.getSerializableHead(entry.value.__context.head);
1290
1312
  results.push(
1291
1313
  new types.ResultIndexedValue({
1292
1314
  context: entry.value.__context,
@@ -3653,7 +3675,7 @@ export class DocumentIndex<
3653
3675
  );
3654
3676
  } else {
3655
3677
  const context = result.value.__context;
3656
- const head = await this._log.log.get(context.head);
3678
+ const head = await this.getSerializableHead(context.head);
3657
3679
  if (replicateIndexFlag) {
3658
3680
  if (!head) {
3659
3681
  continue;