@peerbit/native-backbone 0.2.8 → 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.d.ts +42 -0
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +178 -0
- package/dist/src/index.js.map +1 -1
- package/package.json +1 -1
- package/src/index.ts +307 -0
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;
|
|
@@ -2961,6 +2985,15 @@ export class NativeBackboneMemoryCoordinatePersistenceStore {
|
|
|
2961
2985
|
const file = this.files.get(validateCoordinatePersistenceName(name));
|
|
2962
2986
|
return file ? copyBytes(file) : undefined;
|
|
2963
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
|
+
}
|
|
2964
2997
|
async write(name, bytes) {
|
|
2965
2998
|
this.files.set(validateCoordinatePersistenceName(name), copyBytes(bytes));
|
|
2966
2999
|
}
|
|
@@ -2981,6 +3014,7 @@ export class NativeBackboneNodeCoordinatePersistenceStore {
|
|
|
2981
3014
|
filePaths = new Map();
|
|
2982
3015
|
directoryEnsured = false;
|
|
2983
3016
|
appendFailure;
|
|
3017
|
+
readLimited;
|
|
2984
3018
|
durableBarrier;
|
|
2985
3019
|
constructor(directory, fs) {
|
|
2986
3020
|
this.directory = directory;
|
|
@@ -2991,6 +3025,9 @@ export class NativeBackboneNodeCoordinatePersistenceStore {
|
|
|
2991
3025
|
if (!fs || typeof fs.open === "function") {
|
|
2992
3026
|
this.durableBarrier = (name) => this.syncDurably(name);
|
|
2993
3027
|
}
|
|
3028
|
+
if (!fs || typeof fs.openBoundedRead === "function") {
|
|
3029
|
+
this.readLimited = (name, maxBytes) => this.readWithinLimit(name, maxBytes);
|
|
3030
|
+
}
|
|
2994
3031
|
}
|
|
2995
3032
|
async nodeFs() {
|
|
2996
3033
|
return this.fs ?? (await importNodeFs());
|
|
@@ -3043,6 +3080,79 @@ export class NativeBackboneNodeCoordinatePersistenceStore {
|
|
|
3043
3080
|
throw error;
|
|
3044
3081
|
}
|
|
3045
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
|
+
}
|
|
3046
3156
|
async write(name, bytes) {
|
|
3047
3157
|
const fs = await this.ensureDirectory();
|
|
3048
3158
|
const path = await this.filePath(name);
|
|
@@ -3214,6 +3324,31 @@ export class NativeBackboneOPFSCoordinatePersistenceStore {
|
|
|
3214
3324
|
throw error;
|
|
3215
3325
|
}
|
|
3216
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
|
+
}
|
|
3217
3352
|
async write(name, bytes) {
|
|
3218
3353
|
const handle = await this.directory.getFileHandle(validateCoordinatePersistenceName(name), { create: true });
|
|
3219
3354
|
const writable = await handle.createWritable();
|
|
@@ -3320,6 +3455,7 @@ export class NativeBackboneBufferedCoordinatePersistenceStore {
|
|
|
3320
3455
|
options;
|
|
3321
3456
|
buffers = new Map();
|
|
3322
3457
|
bufferedBytes = 0;
|
|
3458
|
+
readLimited;
|
|
3323
3459
|
supportsRemoval;
|
|
3324
3460
|
durableBarrier;
|
|
3325
3461
|
constructor(inner, options = {}) {
|
|
@@ -3327,6 +3463,10 @@ export class NativeBackboneBufferedCoordinatePersistenceStore {
|
|
|
3327
3463
|
this.options = options;
|
|
3328
3464
|
this.supportsRemoval =
|
|
3329
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
|
+
}
|
|
3330
3470
|
if (typeof inner.durableBarrier === "function") {
|
|
3331
3471
|
this.durableBarrier = async (name) => {
|
|
3332
3472
|
await this.flush(name);
|
|
@@ -3347,6 +3487,44 @@ export class NativeBackboneBufferedCoordinatePersistenceStore {
|
|
|
3347
3487
|
await this.flush(name);
|
|
3348
3488
|
return this.inner.read(name);
|
|
3349
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
|
+
}
|
|
3350
3528
|
async write(name, bytes) {
|
|
3351
3529
|
await this.flush(name);
|
|
3352
3530
|
await this.inner.write(name, bytes);
|