@peerbit/native-backbone 0.2.7 → 0.2.9

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/dist/src/index.js CHANGED
@@ -168,6 +168,19 @@ const nativeBackboneAppendProfileKeys = [
168
168
  "nativeBackboneRawReceiveGraphPutMs",
169
169
  "nativeBackboneRawReceiveCoordinateCommitMs",
170
170
  ];
171
+ export class NativeBackboneCoordinatePersistenceReadLimitError extends Error {
172
+ file;
173
+ maxBytes;
174
+ observedBytes;
175
+ code = "ERR_NATIVE_BACKBONE_COORDINATE_READ_LIMIT";
176
+ constructor(file, maxBytes, observedBytes) {
177
+ super(`Native backbone coordinate persistence file ${file} exceeds the ${maxBytes} byte read limit (${observedBytes.toString()} bytes)`);
178
+ this.file = file;
179
+ this.maxBytes = maxBytes;
180
+ this.observedBytes = observedBytes;
181
+ this.name = "NativeBackboneCoordinatePersistenceReadLimitError";
182
+ }
183
+ }
171
184
  const nativeBackboneCoordinatePersistenceFiles = {
172
185
  snapshot: "coordinates.bin",
173
186
  journal: "coordinates.wal",
@@ -280,6 +293,17 @@ const validateCoordinatePersistenceName = (name) => {
280
293
  }
281
294
  return name;
282
295
  };
296
+ const validateCoordinatePersistenceReadMaxBytes = (maxBytes) => {
297
+ if (!Number.isSafeInteger(maxBytes) || maxBytes < 0) {
298
+ throw new RangeError("Native backbone coordinate persistence read limit must be a non-negative safe integer");
299
+ }
300
+ return maxBytes;
301
+ };
302
+ const assertCoordinatePersistenceReadWithinLimit = (name, maxBytes, observedBytes) => {
303
+ if (BigInt(observedBytes) > BigInt(maxBytes)) {
304
+ throw new NativeBackboneCoordinatePersistenceReadLimitError(name, maxBytes, BigInt(observedBytes));
305
+ }
306
+ };
283
307
  const nativeBackboneCoordinateDropTombstoneBodyBytes = (body) => new TextEncoder().encode(JSON.stringify(body));
284
308
  const nativeBackboneCoordinateDropChecksum = (bytes) => {
285
309
  let checksum = 0xffffffff;
@@ -1945,6 +1969,19 @@ export class NativePeerbitBackbone {
1945
1969
  }
1946
1970
  return this.native.entry_hash_numbers_in_range_u64(integerString(range.start1), integerString(range.end1), integerString(range.start2), integerString(range.end2));
1947
1971
  }
1972
+ getEntryHashNumbersInRangeLimited(range) {
1973
+ if (typeof this.native.entry_hash_numbers_in_range_limited !== "function") {
1974
+ return undefined;
1975
+ }
1976
+ return rowsToNumbers("u64", this.native.entry_hash_numbers_in_range_limited(integerString(range.start1), integerString(range.end1), integerString(range.start2), integerString(range.end2), range.limit));
1977
+ }
1978
+ getEntryHashNumbersInRangeU64Limited(range) {
1979
+ if (typeof BigUint64Array === "undefined" ||
1980
+ typeof this.native.entry_hash_numbers_in_range_u64_limited !== "function") {
1981
+ return undefined;
1982
+ }
1983
+ return this.native.entry_hash_numbers_in_range_u64_limited(integerString(range.start1), integerString(range.end1), integerString(range.start2), integerString(range.end2), range.limit);
1984
+ }
1948
1985
  countEntryCoordinatesInRanges(ranges, options) {
1949
1986
  const start1 = [];
1950
1987
  const end1 = [];
@@ -2948,6 +2985,15 @@ export class NativeBackboneMemoryCoordinatePersistenceStore {
2948
2985
  const file = this.files.get(validateCoordinatePersistenceName(name));
2949
2986
  return file ? copyBytes(file) : undefined;
2950
2987
  }
2988
+ async readLimited(name, maxBytes) {
2989
+ const validName = validateCoordinatePersistenceName(name);
2990
+ const limit = validateCoordinatePersistenceReadMaxBytes(maxBytes);
2991
+ const file = this.files.get(validName);
2992
+ if (file) {
2993
+ assertCoordinatePersistenceReadWithinLimit(validName, limit, file.byteLength);
2994
+ }
2995
+ return file ? copyBytes(file) : undefined;
2996
+ }
2951
2997
  async write(name, bytes) {
2952
2998
  this.files.set(validateCoordinatePersistenceName(name), copyBytes(bytes));
2953
2999
  }
@@ -2968,6 +3014,7 @@ export class NativeBackboneNodeCoordinatePersistenceStore {
2968
3014
  filePaths = new Map();
2969
3015
  directoryEnsured = false;
2970
3016
  appendFailure;
3017
+ readLimited;
2971
3018
  durableBarrier;
2972
3019
  constructor(directory, fs) {
2973
3020
  this.directory = directory;
@@ -2978,6 +3025,9 @@ export class NativeBackboneNodeCoordinatePersistenceStore {
2978
3025
  if (!fs || typeof fs.open === "function") {
2979
3026
  this.durableBarrier = (name) => this.syncDurably(name);
2980
3027
  }
3028
+ if (!fs || typeof fs.openBoundedRead === "function") {
3029
+ this.readLimited = (name, maxBytes) => this.readWithinLimit(name, maxBytes);
3030
+ }
2981
3031
  }
2982
3032
  async nodeFs() {
2983
3033
  return this.fs ?? (await importNodeFs());
@@ -3030,6 +3080,79 @@ export class NativeBackboneNodeCoordinatePersistenceStore {
3030
3080
  throw error;
3031
3081
  }
3032
3082
  }
3083
+ async readWithinLimit(name, maxBytes) {
3084
+ const validName = validateCoordinatePersistenceName(name);
3085
+ const limit = validateCoordinatePersistenceReadMaxBytes(maxBytes);
3086
+ const fs = await this.nodeFs();
3087
+ const path = await this.filePath(validName);
3088
+ let handle;
3089
+ try {
3090
+ if (this.fs) {
3091
+ handle = await this.fs.openBoundedRead(path);
3092
+ }
3093
+ else {
3094
+ if (!fs.open) {
3095
+ throw new Error("Default Node coordinate persistence does not expose FileHandle.open");
3096
+ }
3097
+ const opened = await fs.open(path, "r");
3098
+ if (!opened.stat || !opened.read) {
3099
+ await opened.close();
3100
+ throw new Error("Default Node coordinate persistence bounded reads require FileHandle.stat and FileHandle.read");
3101
+ }
3102
+ handle = opened;
3103
+ }
3104
+ }
3105
+ catch (error) {
3106
+ if (isNotFoundError(error)) {
3107
+ return undefined;
3108
+ }
3109
+ throw error;
3110
+ }
3111
+ try {
3112
+ const initial = await handle.stat({ bigint: true });
3113
+ if (typeof initial.size !== "bigint" || initial.size < 0n) {
3114
+ throw new Error("Node coordinate persistence returned an invalid bigint file size");
3115
+ }
3116
+ assertCoordinatePersistenceReadWithinLimit(validName, limit, initial.size);
3117
+ if (initial.size > BigInt(Number.MAX_SAFE_INTEGER)) {
3118
+ throw new RangeError("Node coordinate persistence file is too large to materialize safely");
3119
+ }
3120
+ const byteLength = Number(initial.size);
3121
+ const bytes = new Uint8Array(byteLength);
3122
+ let offset = 0;
3123
+ while (offset < byteLength) {
3124
+ const { bytesRead } = await handle.read(bytes, offset, byteLength - offset, offset);
3125
+ if (!Number.isSafeInteger(bytesRead) ||
3126
+ bytesRead <= 0 ||
3127
+ bytesRead > byteLength - offset) {
3128
+ throw new Error("Node coordinate persistence file changed during a bounded read");
3129
+ }
3130
+ offset += bytesRead;
3131
+ }
3132
+ const growthProbe = new Uint8Array(1);
3133
+ const { bytesRead: growthBytes } = await handle.read(growthProbe, 0, 1, byteLength);
3134
+ if (!Number.isSafeInteger(growthBytes) ||
3135
+ growthBytes < 0 ||
3136
+ growthBytes > 1) {
3137
+ throw new Error("Node coordinate persistence returned invalid bounded read progress");
3138
+ }
3139
+ const final = await handle.stat({ bigint: true });
3140
+ if (typeof final.size !== "bigint" || final.size < 0n) {
3141
+ throw new Error("Node coordinate persistence returned an invalid bigint file size");
3142
+ }
3143
+ if (growthBytes !== 0 || final.size > initial.size) {
3144
+ assertCoordinatePersistenceReadWithinLimit(validName, limit, final.size > initial.size ? final.size : initial.size + 1n);
3145
+ throw new Error("Node coordinate persistence file changed during a bounded read");
3146
+ }
3147
+ if (final.size !== initial.size) {
3148
+ throw new Error("Node coordinate persistence file changed during a bounded read");
3149
+ }
3150
+ return bytes;
3151
+ }
3152
+ finally {
3153
+ await handle.close();
3154
+ }
3155
+ }
3033
3156
  async write(name, bytes) {
3034
3157
  const fs = await this.ensureDirectory();
3035
3158
  const path = await this.filePath(name);
@@ -3201,6 +3324,31 @@ export class NativeBackboneOPFSCoordinatePersistenceStore {
3201
3324
  throw error;
3202
3325
  }
3203
3326
  }
3327
+ async readLimited(name, maxBytes) {
3328
+ const validName = validateCoordinatePersistenceName(name);
3329
+ const limit = validateCoordinatePersistenceReadMaxBytes(maxBytes);
3330
+ try {
3331
+ const handle = await this.directory.getFileHandle(validName, {
3332
+ create: false,
3333
+ });
3334
+ const file = await handle.getFile();
3335
+ if (!Number.isSafeInteger(file.size) || file.size < 0) {
3336
+ throw new Error("OPFS coordinate persistence returned an invalid file size");
3337
+ }
3338
+ assertCoordinatePersistenceReadWithinLimit(validName, limit, file.size);
3339
+ const buffer = await file.arrayBuffer();
3340
+ if (buffer.byteLength !== file.size) {
3341
+ throw new Error("OPFS coordinate persistence file changed during a bounded read");
3342
+ }
3343
+ return new Uint8Array(buffer);
3344
+ }
3345
+ catch (error) {
3346
+ if (isNotFoundError(error)) {
3347
+ return undefined;
3348
+ }
3349
+ throw error;
3350
+ }
3351
+ }
3204
3352
  async write(name, bytes) {
3205
3353
  const handle = await this.directory.getFileHandle(validateCoordinatePersistenceName(name), { create: true });
3206
3354
  const writable = await handle.createWritable();
@@ -3307,6 +3455,7 @@ export class NativeBackboneBufferedCoordinatePersistenceStore {
3307
3455
  options;
3308
3456
  buffers = new Map();
3309
3457
  bufferedBytes = 0;
3458
+ readLimited;
3310
3459
  supportsRemoval;
3311
3460
  durableBarrier;
3312
3461
  constructor(inner, options = {}) {
@@ -3314,6 +3463,10 @@ export class NativeBackboneBufferedCoordinatePersistenceStore {
3314
3463
  this.options = options;
3315
3464
  this.supportsRemoval =
3316
3465
  inner.supportsRemoval ?? typeof inner.remove === "function";
3466
+ if (typeof inner.readLimited === "function") {
3467
+ const innerReadLimited = inner.readLimited.bind(inner);
3468
+ this.readLimited = (name, maxBytes) => this.readWithinLimit(name, maxBytes, innerReadLimited);
3469
+ }
3317
3470
  if (typeof inner.durableBarrier === "function") {
3318
3471
  this.durableBarrier = async (name) => {
3319
3472
  await this.flush(name);
@@ -3334,6 +3487,44 @@ export class NativeBackboneBufferedCoordinatePersistenceStore {
3334
3487
  await this.flush(name);
3335
3488
  return this.inner.read(name);
3336
3489
  }
3490
+ async readWithinLimit(name, maxBytes, innerReadLimited) {
3491
+ const validName = validateCoordinatePersistenceName(name);
3492
+ const limit = validateCoordinatePersistenceReadMaxBytes(maxBytes);
3493
+ const pending = this.buffers.get(validName);
3494
+ if (!pending || pending.length === 0) {
3495
+ return innerReadLimited(validName, limit);
3496
+ }
3497
+ const pendingLength = pending.length;
3498
+ let pendingBytes = 0n;
3499
+ for (const chunk of pending) {
3500
+ pendingBytes += BigInt(chunk.byteLength);
3501
+ }
3502
+ assertCoordinatePersistenceReadWithinLimit(validName, limit, pendingBytes);
3503
+ const remaining = limit - Number(pendingBytes);
3504
+ let existingBytes;
3505
+ try {
3506
+ existingBytes = (await innerReadLimited(validName, remaining))
3507
+ ?.byteLength;
3508
+ }
3509
+ catch (error) {
3510
+ if (error instanceof NativeBackboneCoordinatePersistenceReadLimitError) {
3511
+ throw new NativeBackboneCoordinatePersistenceReadLimitError(validName, limit, pendingBytes + error.observedBytes);
3512
+ }
3513
+ throw error;
3514
+ }
3515
+ let confirmedPendingBytes = 0n;
3516
+ for (const chunk of pending) {
3517
+ confirmedPendingBytes += BigInt(chunk.byteLength);
3518
+ }
3519
+ if (this.buffers.get(validName) !== pending ||
3520
+ pending.length !== pendingLength ||
3521
+ confirmedPendingBytes !== pendingBytes) {
3522
+ throw new Error("Native backbone coordinate persistence pending bytes changed during a bounded read");
3523
+ }
3524
+ assertCoordinatePersistenceReadWithinLimit(validName, limit, pendingBytes + BigInt(existingBytes ?? 0));
3525
+ await this.flush(validName);
3526
+ return innerReadLimited(validName, limit);
3527
+ }
3337
3528
  async write(name, bytes) {
3338
3529
  await this.flush(name);
3339
3530
  await this.inner.write(name, bytes);