@rljson/bs 0.0.19 → 0.0.21
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/README.architecture.md +1295 -2
- package/README.public.md +324 -169
- package/dist/README.architecture.md +1295 -2
- package/dist/README.public.md +324 -169
- package/dist/bs-peer-bridge.d.ts +2 -0
- package/dist/bs.js +163 -14
- package/dist/directional-socket-mock.d.ts +10 -0
- package/dist/index.d.ts +1 -0
- package/package.json +17 -17
package/dist/README.public.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
<!--
|
|
2
2
|
@license
|
|
3
|
-
Copyright (c)
|
|
3
|
+
Copyright (c) 2026 Rljson
|
|
4
4
|
|
|
5
5
|
Use of this source code is governed by terms that can be
|
|
6
6
|
found in the LICENSE file in the root of this package.
|
|
@@ -8,19 +8,20 @@ found in the LICENSE file in the root of this package.
|
|
|
8
8
|
|
|
9
9
|
# @rljson/bs
|
|
10
10
|
|
|
11
|
-
Content-addressable blob storage interface and implementations for
|
|
11
|
+
Content-addressable blob storage interface and implementations for TypeScript/JavaScript.
|
|
12
12
|
|
|
13
13
|
## Overview
|
|
14
14
|
|
|
15
|
-
`@rljson/bs` provides a unified interface for blob storage with content-addressable semantics. All blobs are identified by their SHA256 hash, ensuring automatic deduplication and
|
|
15
|
+
`@rljson/bs` provides a unified interface for blob storage with content-addressable semantics. All blobs are identified by their SHA256 hash, ensuring automatic deduplication, data integrity verification, and location independence.
|
|
16
16
|
|
|
17
17
|
### Key Features
|
|
18
18
|
|
|
19
19
|
- **Content-Addressable Storage**: Blobs are identified by SHA256 hash of their content
|
|
20
|
-
- **Automatic Deduplication**: Identical content is stored only once
|
|
20
|
+
- **Automatic Deduplication**: Identical content is stored only once across the entire system
|
|
21
21
|
- **Multiple Implementations**: In-memory, peer-to-peer, server-based, and multi-tier
|
|
22
22
|
- **Type-Safe**: Full TypeScript support with comprehensive type definitions
|
|
23
23
|
- **Stream Support**: Efficient handling of large blobs via ReadableStreams
|
|
24
|
+
- **Network Layer**: Built-in peer-to-peer and client-server implementations
|
|
24
25
|
- **100% Test Coverage**: Fully tested with comprehensive test suite
|
|
25
26
|
|
|
26
27
|
## Installation
|
|
@@ -31,7 +32,7 @@ npm install @rljson/bs
|
|
|
31
32
|
|
|
32
33
|
## Quick Start
|
|
33
34
|
|
|
34
|
-
### In-Memory Storage
|
|
35
|
+
### Basic Usage: In-Memory Storage
|
|
35
36
|
|
|
36
37
|
The simplest implementation for testing or temporary storage:
|
|
37
38
|
|
|
@@ -42,8 +43,9 @@ import { BsMem } from '@rljson/bs';
|
|
|
42
43
|
const bs = new BsMem();
|
|
43
44
|
|
|
44
45
|
// Store a blob - returns SHA256 hash as blobId
|
|
45
|
-
const { blobId } = await bs.setBlob('Hello, World!');
|
|
46
|
-
console.log(blobId); //
|
|
46
|
+
const { blobId, size } = await bs.setBlob('Hello, World!');
|
|
47
|
+
console.log(blobId); // "dffd6021bb2bd5b0af676290809ec3a53191dd81c7f70a4b28688a362182986f"
|
|
48
|
+
console.log(size); // 13
|
|
47
49
|
|
|
48
50
|
// Retrieve the blob
|
|
49
51
|
const { content } = await bs.getBlob(blobId);
|
|
@@ -53,27 +55,55 @@ console.log(content.toString()); // "Hello, World!"
|
|
|
53
55
|
const exists = await bs.blobExists(blobId);
|
|
54
56
|
console.log(exists); // true
|
|
55
57
|
|
|
58
|
+
// Get blob properties without downloading
|
|
59
|
+
const props = await bs.getBlobProperties(blobId);
|
|
60
|
+
console.log(props.createdAt); // Timestamp
|
|
61
|
+
|
|
56
62
|
// List all blobs
|
|
57
63
|
const { blobs } = await bs.listBlobs();
|
|
58
64
|
console.log(blobs.length); // 1
|
|
59
65
|
```
|
|
60
66
|
|
|
67
|
+
### Client-Server Architecture
|
|
68
|
+
|
|
69
|
+
Access remote blob storage over a socket connection:
|
|
70
|
+
|
|
71
|
+
```typescript
|
|
72
|
+
import { BsMem, BsServer, BsPeer, SocketMock } from '@rljson/bs';
|
|
73
|
+
|
|
74
|
+
// Server setup
|
|
75
|
+
const storage = new BsMem();
|
|
76
|
+
const server = new BsServer(storage);
|
|
77
|
+
|
|
78
|
+
// Client setup
|
|
79
|
+
const socket = new SocketMock(); // Use real socket in production
|
|
80
|
+
await server.addSocket(socket);
|
|
81
|
+
const client = new BsPeer(socket);
|
|
82
|
+
await client.init();
|
|
83
|
+
|
|
84
|
+
// Client can now access server storage
|
|
85
|
+
const { blobId } = await client.setBlob('Remote data');
|
|
86
|
+
const { content } = await client.getBlob(blobId);
|
|
87
|
+
console.log(content.toString()); // "Remote data"
|
|
88
|
+
|
|
89
|
+
// Close connection
|
|
90
|
+
await client.close();
|
|
91
|
+
```
|
|
92
|
+
|
|
61
93
|
### Multi-Tier Storage (Cache + Remote)
|
|
62
94
|
|
|
63
95
|
Combine multiple storage backends with automatic caching:
|
|
64
96
|
|
|
65
97
|
```typescript
|
|
66
|
-
import { BsMulti, BsMem, BsPeer
|
|
67
|
-
|
|
68
|
-
// Setup remote storage (simulated)
|
|
69
|
-
const remoteStore = new BsMem();
|
|
70
|
-
const remoteSocket = new PeerSocketMock(remoteStore);
|
|
71
|
-
const remotePeer = new BsPeer(remoteSocket);
|
|
72
|
-
await remotePeer.init();
|
|
98
|
+
import { BsMulti, BsMem, BsPeer } from '@rljson/bs';
|
|
73
99
|
|
|
74
100
|
// Setup local cache
|
|
75
101
|
const localCache = new BsMem();
|
|
76
102
|
|
|
103
|
+
// Setup remote storage (via BsPeer)
|
|
104
|
+
const remotePeer = new BsPeer(remoteSocket);
|
|
105
|
+
await remotePeer.init();
|
|
106
|
+
|
|
77
107
|
// Create multi-tier storage with cache-first strategy
|
|
78
108
|
const bs = new BsMulti([
|
|
79
109
|
{ bs: localCache, priority: 0, read: true, write: true }, // Cache first
|
|
@@ -84,8 +114,8 @@ await bs.init();
|
|
|
84
114
|
// Store blob - writes to cache only (writable stores)
|
|
85
115
|
const { blobId } = await bs.setBlob('Cached content');
|
|
86
116
|
|
|
87
|
-
// Read from cache first, falls back to remote
|
|
88
|
-
// Automatically hot-swaps remote blobs to cache
|
|
117
|
+
// Read from cache first, falls back to remote if not found
|
|
118
|
+
// Automatically hot-swaps remote blobs to cache for future reads
|
|
89
119
|
const { content } = await bs.getBlob(blobId);
|
|
90
120
|
```
|
|
91
121
|
|
|
@@ -93,11 +123,7 @@ const { content } = await bs.getBlob(blobId);
|
|
|
93
123
|
|
|
94
124
|
### Content-Addressable Storage
|
|
95
125
|
|
|
96
|
-
Every blob is identified by the SHA256 hash of its content
|
|
97
|
-
|
|
98
|
-
- **Automatic Deduplication**: Storing the same content twice returns the same `blobId`
|
|
99
|
-
- **Data Integrity**: The `blobId` serves as a cryptographic checksum
|
|
100
|
-
- **Location Independence**: Blobs can be identified and verified anywhere
|
|
126
|
+
Every blob is identified by the SHA256 hash of its content:
|
|
101
127
|
|
|
102
128
|
```typescript
|
|
103
129
|
const bs = new BsMem();
|
|
@@ -105,133 +131,33 @@ const bs = new BsMem();
|
|
|
105
131
|
const result1 = await bs.setBlob('Same content');
|
|
106
132
|
const result2 = await bs.setBlob('Same content');
|
|
107
133
|
|
|
108
|
-
// Both return the same blobId
|
|
134
|
+
// Both return the same blobId (automatic deduplication)
|
|
109
135
|
console.log(result1.blobId === result2.blobId); // true
|
|
136
|
+
|
|
137
|
+
// Different content = different blobId
|
|
138
|
+
const result3 = await bs.setBlob('Different content');
|
|
139
|
+
console.log(result1.blobId !== result3.blobId); // true
|
|
110
140
|
```
|
|
111
141
|
|
|
142
|
+
**Benefits:**
|
|
143
|
+
|
|
144
|
+
- **Automatic Deduplication**: Identical content stored once, regardless of how many times you call `setBlob`
|
|
145
|
+
- **Data Integrity**: The `blobId` serves as a cryptographic checksum
|
|
146
|
+
- **Location Independence**: Blobs can be identified and verified anywhere
|
|
147
|
+
- **Cache Efficiency**: Content can be cached anywhere and verified by its hash
|
|
148
|
+
|
|
112
149
|
### Blob Properties
|
|
113
150
|
|
|
114
151
|
All blobs have associated metadata:
|
|
115
152
|
|
|
116
153
|
```typescript
|
|
117
154
|
interface BlobProperties {
|
|
118
|
-
blobId: string; // SHA256 hash of content
|
|
155
|
+
blobId: string; // SHA256 hash of content (64 hex characters)
|
|
119
156
|
size: number; // Size in bytes
|
|
120
|
-
contentType: string; // MIME type (default: 'application/octet-stream')
|
|
121
157
|
createdAt: Date; // Creation timestamp
|
|
122
|
-
metadata?: Record<string, string>; // Optional custom metadata
|
|
123
158
|
}
|
|
124
159
|
```
|
|
125
160
|
|
|
126
|
-
## Implementations
|
|
127
|
-
|
|
128
|
-
### BsMem - In-Memory Storage
|
|
129
|
-
|
|
130
|
-
Fast, ephemeral storage for testing and temporary data:
|
|
131
|
-
|
|
132
|
-
```typescript
|
|
133
|
-
import { BsMem } from '@rljson/bs';
|
|
134
|
-
|
|
135
|
-
const bs = new BsMem();
|
|
136
|
-
const { blobId } = await bs.setBlob('Temporary data');
|
|
137
|
-
```
|
|
138
|
-
|
|
139
|
-
**Use Cases:**
|
|
140
|
-
|
|
141
|
-
- Unit testing
|
|
142
|
-
- Temporary caching
|
|
143
|
-
- Development and prototyping
|
|
144
|
-
|
|
145
|
-
**Limitations:**
|
|
146
|
-
|
|
147
|
-
- Data lost when process ends
|
|
148
|
-
- Limited by available RAM
|
|
149
|
-
|
|
150
|
-
### BsPeer - Peer-to-Peer Storage
|
|
151
|
-
|
|
152
|
-
Access remote blob storage over a socket connection:
|
|
153
|
-
|
|
154
|
-
```typescript
|
|
155
|
-
import { BsPeer, PeerSocketMock } from '@rljson/bs';
|
|
156
|
-
|
|
157
|
-
// Create a peer connected to a remote storage
|
|
158
|
-
const remoteStorage = new BsMem();
|
|
159
|
-
const socket = new PeerSocketMock(remoteStorage);
|
|
160
|
-
const peer = new BsPeer(socket);
|
|
161
|
-
await peer.init();
|
|
162
|
-
|
|
163
|
-
// Use like any other Bs implementation
|
|
164
|
-
const { blobId } = await peer.setBlob('Remote data');
|
|
165
|
-
const { content } = await peer.getBlob(blobId);
|
|
166
|
-
|
|
167
|
-
// Close connection when done
|
|
168
|
-
await peer.close();
|
|
169
|
-
```
|
|
170
|
-
|
|
171
|
-
**Use Cases:**
|
|
172
|
-
|
|
173
|
-
- Distributed systems
|
|
174
|
-
- Client-server architectures
|
|
175
|
-
- Remote backup
|
|
176
|
-
|
|
177
|
-
### BsServer - Server-Side Handler
|
|
178
|
-
|
|
179
|
-
Handle blob storage requests from remote peers:
|
|
180
|
-
|
|
181
|
-
```typescript
|
|
182
|
-
import { BsServer, BsMem, SocketMock } from '@rljson/bs';
|
|
183
|
-
|
|
184
|
-
// Server-side setup
|
|
185
|
-
const storage = new BsMem();
|
|
186
|
-
const server = new BsServer(storage);
|
|
187
|
-
|
|
188
|
-
// Handle incoming connection
|
|
189
|
-
const clientSocket = new SocketMock();
|
|
190
|
-
const serverSocket = clientSocket.createPeer();
|
|
191
|
-
server.handleConnection(serverSocket);
|
|
192
|
-
|
|
193
|
-
// Client can now access storage through clientSocket
|
|
194
|
-
```
|
|
195
|
-
|
|
196
|
-
**Use Cases:**
|
|
197
|
-
|
|
198
|
-
- Building blob storage services
|
|
199
|
-
- Network protocol implementation
|
|
200
|
-
- API backends
|
|
201
|
-
|
|
202
|
-
### BsMulti - Multi-Tier Storage
|
|
203
|
-
|
|
204
|
-
Combine multiple storage backends with configurable priorities:
|
|
205
|
-
|
|
206
|
-
```typescript
|
|
207
|
-
import { BsMulti, BsMem } from '@rljson/bs';
|
|
208
|
-
|
|
209
|
-
const fastCache = new BsMem();
|
|
210
|
-
const mainStorage = new BsMem();
|
|
211
|
-
const backup = new BsMem();
|
|
212
|
-
|
|
213
|
-
const bs = new BsMulti([
|
|
214
|
-
{ bs: fastCache, priority: 0, read: true, write: true }, // L1 cache
|
|
215
|
-
{ bs: mainStorage, priority: 1, read: true, write: true }, // Main storage
|
|
216
|
-
{ bs: backup, priority: 2, read: true, write: false }, // Read-only backup
|
|
217
|
-
]);
|
|
218
|
-
await bs.init();
|
|
219
|
-
```
|
|
220
|
-
|
|
221
|
-
**Features:**
|
|
222
|
-
|
|
223
|
-
- **Priority-Based Reads**: Reads from lowest priority number first
|
|
224
|
-
- **Hot-Swapping**: Automatically caches blobs from remote to local
|
|
225
|
-
- **Parallel Writes**: Writes to all writable stores simultaneously
|
|
226
|
-
- **Deduplication**: Merges results from all readable stores
|
|
227
|
-
|
|
228
|
-
**Use Cases:**
|
|
229
|
-
|
|
230
|
-
- Local cache + remote storage
|
|
231
|
-
- Local network storage infrastructure
|
|
232
|
-
- Backup and archival systems
|
|
233
|
-
- Distributed blob storage across network nodes
|
|
234
|
-
|
|
235
161
|
## API Reference
|
|
236
162
|
|
|
237
163
|
### Bs Interface
|
|
@@ -247,10 +173,10 @@ Stores a blob and returns its properties including the SHA256 `blobId`.
|
|
|
247
173
|
const { blobId } = await bs.setBlob('Hello');
|
|
248
174
|
|
|
249
175
|
// From Buffer
|
|
250
|
-
const buffer = Buffer.from('World');
|
|
176
|
+
const buffer = Buffer.from('World', 'utf8');
|
|
251
177
|
await bs.setBlob(buffer);
|
|
252
178
|
|
|
253
|
-
// From ReadableStream
|
|
179
|
+
// From ReadableStream (for large files)
|
|
254
180
|
const stream = new ReadableStream({
|
|
255
181
|
start(controller) {
|
|
256
182
|
controller.enqueue(new TextEncoder().encode('Stream data'));
|
|
@@ -297,13 +223,14 @@ Deletes a blob from storage.
|
|
|
297
223
|
|
|
298
224
|
```typescript
|
|
299
225
|
await bs.deleteBlob(blobId);
|
|
300
|
-
```
|
|
301
226
|
|
|
302
|
-
|
|
227
|
+
// Note: In production with content-addressable storage,
|
|
228
|
+
// consider reference counting before deletion
|
|
229
|
+
```
|
|
303
230
|
|
|
304
231
|
#### `blobExists(blobId: string): Promise<boolean>`
|
|
305
232
|
|
|
306
|
-
Checks if a blob exists.
|
|
233
|
+
Checks if a blob exists without downloading it.
|
|
307
234
|
|
|
308
235
|
```typescript
|
|
309
236
|
if (await bs.blobExists(blobId)) {
|
|
@@ -318,7 +245,7 @@ Gets blob metadata without downloading content.
|
|
|
318
245
|
```typescript
|
|
319
246
|
const props = await bs.getBlobProperties(blobId);
|
|
320
247
|
console.log(`Blob size: ${props.size} bytes`);
|
|
321
|
-
console.log(`Created: ${props.createdAt}`);
|
|
248
|
+
console.log(`Created: ${props.createdAt.toISOString()}`);
|
|
322
249
|
```
|
|
323
250
|
|
|
324
251
|
#### `listBlobs(options?: ListBlobsOptions): Promise<ListBlobsResult>`
|
|
@@ -329,7 +256,7 @@ Lists all blobs with optional filtering and pagination.
|
|
|
329
256
|
// List all blobs
|
|
330
257
|
const { blobs } = await bs.listBlobs();
|
|
331
258
|
|
|
332
|
-
// With prefix filter
|
|
259
|
+
// With prefix filter (blobs starting with "abc")
|
|
333
260
|
const result = await bs.listBlobs({ prefix: 'abc' });
|
|
334
261
|
|
|
335
262
|
// Paginated listing
|
|
@@ -350,56 +277,202 @@ do {
|
|
|
350
277
|
Generates a signed URL for temporary access to a blob.
|
|
351
278
|
|
|
352
279
|
```typescript
|
|
353
|
-
// Read-only URL valid for 1 hour
|
|
280
|
+
// Read-only URL valid for 1 hour (3600 seconds)
|
|
354
281
|
const url = await bs.generateSignedUrl(blobId, 3600);
|
|
355
282
|
|
|
356
|
-
// Delete permission URL
|
|
283
|
+
// Delete permission URL valid for 5 minutes
|
|
357
284
|
const deleteUrl = await bs.generateSignedUrl(blobId, 300, 'delete');
|
|
358
285
|
```
|
|
359
286
|
|
|
360
|
-
##
|
|
287
|
+
## Implementations
|
|
361
288
|
|
|
362
|
-
###
|
|
289
|
+
### BsMem - In-Memory Storage
|
|
363
290
|
|
|
364
|
-
|
|
291
|
+
Fast, ephemeral storage for testing and temporary data.
|
|
365
292
|
|
|
366
293
|
```typescript
|
|
367
|
-
import {
|
|
294
|
+
import { BsMem } from '@rljson/bs';
|
|
368
295
|
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
}
|
|
296
|
+
const bs = new BsMem();
|
|
297
|
+
const { blobId } = await bs.setBlob('Temporary data');
|
|
298
|
+
```
|
|
373
299
|
|
|
374
|
-
|
|
375
|
-
// Your implementation
|
|
376
|
-
}
|
|
300
|
+
**Use Cases:**
|
|
377
301
|
|
|
378
|
-
|
|
379
|
-
|
|
302
|
+
- Unit testing
|
|
303
|
+
- Temporary caching
|
|
304
|
+
- Development and prototyping
|
|
305
|
+
- Fast local storage for small datasets
|
|
306
|
+
|
|
307
|
+
**Limitations:**
|
|
308
|
+
|
|
309
|
+
- Data lost when process ends
|
|
310
|
+
- Limited by available RAM
|
|
311
|
+
- Single-process only
|
|
312
|
+
|
|
313
|
+
### BsPeer - Peer-to-Peer Storage Client
|
|
314
|
+
|
|
315
|
+
Access remote blob storage over a socket connection.
|
|
316
|
+
|
|
317
|
+
```typescript
|
|
318
|
+
import { BsPeer } from '@rljson/bs';
|
|
319
|
+
|
|
320
|
+
// Create a peer connected to a remote storage
|
|
321
|
+
const peer = new BsPeer(socket);
|
|
322
|
+
await peer.init();
|
|
323
|
+
|
|
324
|
+
// Use like any other Bs implementation
|
|
325
|
+
const { blobId } = await peer.setBlob('Remote data');
|
|
326
|
+
const { content } = await peer.getBlob(blobId);
|
|
327
|
+
|
|
328
|
+
// Close connection when done
|
|
329
|
+
await peer.close();
|
|
330
|
+
```
|
|
331
|
+
|
|
332
|
+
**Use Cases:**
|
|
333
|
+
|
|
334
|
+
- Client-server architectures
|
|
335
|
+
- Distributed systems
|
|
336
|
+
- Remote backup
|
|
337
|
+
- Accessing centralized storage
|
|
338
|
+
|
|
339
|
+
**Features:**
|
|
340
|
+
|
|
341
|
+
- Async socket-based communication
|
|
342
|
+
- Error-first callback pattern (Node.js style)
|
|
343
|
+
- Connection state management
|
|
344
|
+
- Automatic retry support
|
|
345
|
+
|
|
346
|
+
### BsServer - Server-Side Handler
|
|
347
|
+
|
|
348
|
+
Handle blob storage requests from remote peers.
|
|
349
|
+
|
|
350
|
+
```typescript
|
|
351
|
+
import { BsServer, BsMem, SocketMock } from '@rljson/bs';
|
|
352
|
+
|
|
353
|
+
// Server-side setup
|
|
354
|
+
const storage = new BsMem();
|
|
355
|
+
const server = new BsServer(storage);
|
|
356
|
+
|
|
357
|
+
// Handle incoming connection
|
|
358
|
+
const clientSocket = new SocketMock(); // Use real socket in production
|
|
359
|
+
await server.addSocket(clientSocket);
|
|
360
|
+
|
|
361
|
+
// Client can now access storage through socket
|
|
362
|
+
```
|
|
363
|
+
|
|
364
|
+
**Use Cases:**
|
|
365
|
+
|
|
366
|
+
- Building blob storage services
|
|
367
|
+
- Network protocol implementation
|
|
368
|
+
- API backends
|
|
369
|
+
- Multi-client storage systems
|
|
370
|
+
|
|
371
|
+
**Features:**
|
|
372
|
+
|
|
373
|
+
- Multiple client support
|
|
374
|
+
- Socket lifecycle management
|
|
375
|
+
- Automatic method mapping
|
|
376
|
+
- Error handling
|
|
377
|
+
|
|
378
|
+
### BsPeerBridge - PULL Architecture Bridge (Read-Only)
|
|
379
|
+
|
|
380
|
+
Exposes local blob storage for server to PULL from (read-only access).
|
|
381
|
+
|
|
382
|
+
```typescript
|
|
383
|
+
import { BsPeerBridge, BsMem } from '@rljson/bs';
|
|
384
|
+
|
|
385
|
+
// Client-side: expose local storage for server to read
|
|
386
|
+
const localStorage = new BsMem();
|
|
387
|
+
const bridge = new BsPeerBridge(localStorage, socket);
|
|
388
|
+
bridge.start();
|
|
389
|
+
|
|
390
|
+
// Server can now read from client's local storage
|
|
391
|
+
// but CANNOT write to it (PULL architecture)
|
|
392
|
+
```
|
|
393
|
+
|
|
394
|
+
**Architecture Pattern:**
|
|
395
|
+
|
|
396
|
+
- **PULL-only**: Server can read from client, but cannot write
|
|
397
|
+
- **Read Operations Only**: `getBlob`, `getBlobStream`, `blobExists`, `getBlobProperties`, `listBlobs`
|
|
398
|
+
- **No Write Operations**: Does not expose `setBlob`, `deleteBlob`, or `generateSignedUrl`
|
|
399
|
+
|
|
400
|
+
**Use Cases:**
|
|
401
|
+
|
|
402
|
+
- Client exposes local cache for server to access
|
|
403
|
+
- Distributed storage where server pulls from clients
|
|
404
|
+
- Peer-to-peer networks with read-only sharing
|
|
405
|
+
|
|
406
|
+
### BsMulti - Multi-Tier Storage
|
|
407
|
+
|
|
408
|
+
Combine multiple storage backends with configurable priorities.
|
|
409
|
+
|
|
410
|
+
```typescript
|
|
411
|
+
import { BsMulti, BsMem } from '@rljson/bs';
|
|
412
|
+
|
|
413
|
+
const fastCache = new BsMem();
|
|
414
|
+
const mainStorage = new BsMem();
|
|
415
|
+
const backup = new BsMem();
|
|
416
|
+
|
|
417
|
+
const bs = new BsMulti([
|
|
418
|
+
{ bs: fastCache, priority: 0, read: true, write: true }, // L1 cache
|
|
419
|
+
{ bs: mainStorage, priority: 1, read: true, write: true }, // Main storage
|
|
420
|
+
{ bs: backup, priority: 2, read: true, write: false }, // Read-only backup
|
|
421
|
+
]);
|
|
422
|
+
await bs.init();
|
|
380
423
|
```
|
|
381
424
|
|
|
382
|
-
|
|
425
|
+
**Features:**
|
|
426
|
+
|
|
427
|
+
- **Priority-Based Reads**: Reads from lowest priority number first (0 = highest priority)
|
|
428
|
+
- **Hot-Swapping**: Automatically caches blobs from remote to local on read
|
|
429
|
+
- **Parallel Writes**: Writes to all writable stores simultaneously
|
|
430
|
+
- **Deduplication**: Merges results from all readable stores when listing
|
|
431
|
+
- **Graceful Fallback**: If highest priority fails, falls back to next priority
|
|
383
432
|
|
|
384
|
-
**
|
|
433
|
+
**Use Cases:**
|
|
434
|
+
|
|
435
|
+
- Local cache + remote storage
|
|
436
|
+
- Multi-region storage replication
|
|
437
|
+
- Local network storage infrastructure
|
|
438
|
+
- Backup and archival systems
|
|
439
|
+
- Hierarchical storage management (HSM)
|
|
440
|
+
|
|
441
|
+
## Common Patterns
|
|
442
|
+
|
|
443
|
+
### Local Cache + Remote Storage (PULL Architecture)
|
|
385
444
|
|
|
386
445
|
```typescript
|
|
446
|
+
const localCache = new BsMem();
|
|
447
|
+
const remotePeer = new BsPeer(remoteSocket);
|
|
448
|
+
await remotePeer.init();
|
|
449
|
+
|
|
387
450
|
const bs = new BsMulti([
|
|
388
451
|
{ bs: localCache, priority: 0, read: true, write: true },
|
|
389
|
-
{ bs:
|
|
452
|
+
{ bs: remotePeer, priority: 1, read: true, write: false }, // Read-only
|
|
390
453
|
]);
|
|
454
|
+
|
|
455
|
+
// Writes go to cache only
|
|
456
|
+
await bs.setBlob('data');
|
|
457
|
+
|
|
458
|
+
// Reads check cache first, then remote
|
|
459
|
+
// Remote blobs are automatically cached
|
|
460
|
+
const { content } = await bs.getBlob(blobId);
|
|
391
461
|
```
|
|
392
462
|
|
|
393
|
-
|
|
463
|
+
### Write-Through Cache
|
|
394
464
|
|
|
395
465
|
```typescript
|
|
396
466
|
const bs = new BsMulti([
|
|
397
467
|
{ bs: localCache, priority: 0, read: true, write: true },
|
|
398
468
|
{ bs: remoteStorage, priority: 1, read: true, write: true }, // Also writable
|
|
399
469
|
]);
|
|
470
|
+
|
|
471
|
+
// Writes go to both cache and remote simultaneously
|
|
472
|
+
await bs.setBlob('data');
|
|
400
473
|
```
|
|
401
474
|
|
|
402
|
-
|
|
475
|
+
### Multi-Region Replication
|
|
403
476
|
|
|
404
477
|
```typescript
|
|
405
478
|
const bs = new BsMulti([
|
|
@@ -407,9 +480,32 @@ const bs = new BsMulti([
|
|
|
407
480
|
{ bs: regionEu, priority: 1, read: true, write: true },
|
|
408
481
|
{ bs: regionAsia, priority: 2, read: true, write: true },
|
|
409
482
|
]);
|
|
483
|
+
|
|
484
|
+
// Writes replicate to all regions
|
|
485
|
+
// Reads come from fastest responding region
|
|
486
|
+
```
|
|
487
|
+
|
|
488
|
+
### Client-Server with BsPeerBridge (PULL Pattern)
|
|
489
|
+
|
|
490
|
+
```typescript
|
|
491
|
+
// Client setup
|
|
492
|
+
const clientStorage = new BsMem();
|
|
493
|
+
const bridge = new BsPeerBridge(clientStorage, socketToServer);
|
|
494
|
+
bridge.start(); // Exposes read-only access to server
|
|
495
|
+
|
|
496
|
+
const bsPeer = new BsPeer(socketToServer);
|
|
497
|
+
await bsPeer.init();
|
|
498
|
+
|
|
499
|
+
const clientBs = new BsMulti([
|
|
500
|
+
{ bs: clientStorage, priority: 0, read: true, write: true }, // Local storage
|
|
501
|
+
{ bs: bsPeer, priority: 1, read: true, write: false }, // Server (read-only)
|
|
502
|
+
]);
|
|
503
|
+
|
|
504
|
+
// Server can pull from client via bridge
|
|
505
|
+
// Client can pull from server via bsPeer
|
|
410
506
|
```
|
|
411
507
|
|
|
412
|
-
|
|
508
|
+
## Error Handling
|
|
413
509
|
|
|
414
510
|
All methods throw errors for invalid operations:
|
|
415
511
|
|
|
@@ -436,6 +532,7 @@ The package includes comprehensive test utilities:
|
|
|
436
532
|
|
|
437
533
|
```typescript
|
|
438
534
|
import { BsMem } from '@rljson/bs';
|
|
535
|
+
import { describe, it, expect, beforeEach } from 'vitest';
|
|
439
536
|
|
|
440
537
|
describe('My Tests', () => {
|
|
441
538
|
let bs: BsMem;
|
|
@@ -449,6 +546,12 @@ describe('My Tests', () => {
|
|
|
449
546
|
const { content } = await bs.getBlob(blobId);
|
|
450
547
|
expect(content.toString()).toBe('test data');
|
|
451
548
|
});
|
|
549
|
+
|
|
550
|
+
it('should deduplicate identical content', async () => {
|
|
551
|
+
const result1 = await bs.setBlob('same');
|
|
552
|
+
const result2 = await bs.setBlob('same');
|
|
553
|
+
expect(result1.blobId).toBe(result2.blobId);
|
|
554
|
+
});
|
|
452
555
|
});
|
|
453
556
|
```
|
|
454
557
|
|
|
@@ -458,21 +561,72 @@ describe('My Tests', () => {
|
|
|
458
561
|
|
|
459
562
|
- `BsMem` stores all data in RAM - suitable for small to medium datasets
|
|
460
563
|
- Use streams (`getBlobStream`) for large blobs to avoid loading entire content into memory
|
|
461
|
-
- `BsMulti` with local cache reduces network overhead
|
|
564
|
+
- `BsMulti` with local cache reduces network overhead significantly
|
|
462
565
|
|
|
463
566
|
### Network Efficiency
|
|
464
567
|
|
|
465
568
|
- Use `BsPeer` for remote access with minimal protocol overhead
|
|
466
|
-
- `BsMulti` automatically caches frequently accessed blobs
|
|
467
|
-
- Content-addressable nature prevents redundant transfers
|
|
569
|
+
- `BsMulti` automatically caches frequently accessed blobs locally
|
|
570
|
+
- Content-addressable nature prevents redundant transfers (same content = same hash)
|
|
571
|
+
- Hot-swapping in `BsMulti` reduces repeated network requests
|
|
468
572
|
|
|
469
|
-
### Deduplication
|
|
573
|
+
### Deduplication Benefits
|
|
470
574
|
|
|
471
575
|
- Identical content stored multiple times occupies space only once
|
|
472
576
|
- Particularly effective for:
|
|
473
|
-
- Version control systems
|
|
474
|
-
- Backup solutions
|
|
475
|
-
- Build artifact storage
|
|
577
|
+
- Version control systems (many files unchanged between versions)
|
|
578
|
+
- Backup solutions (incremental backups with deduplication)
|
|
579
|
+
- Build artifact storage (shared dependencies)
|
|
580
|
+
- Document management (attachments, templates)
|
|
581
|
+
|
|
582
|
+
## Migration Guide
|
|
583
|
+
|
|
584
|
+
### From Traditional Blob Storage
|
|
585
|
+
|
|
586
|
+
Traditional blob storage typically uses arbitrary identifiers:
|
|
587
|
+
|
|
588
|
+
```typescript
|
|
589
|
+
// Traditional
|
|
590
|
+
await blobStore.put('my-file-id', content);
|
|
591
|
+
const data = await blobStore.get('my-file-id');
|
|
592
|
+
```
|
|
593
|
+
|
|
594
|
+
With content-addressable storage, the ID is derived from content:
|
|
595
|
+
|
|
596
|
+
```typescript
|
|
597
|
+
// Content-addressable
|
|
598
|
+
const { blobId } = await bs.setBlob(content); // blobId = SHA256(content)
|
|
599
|
+
const { content } = await bs.getBlob(blobId);
|
|
600
|
+
```
|
|
601
|
+
|
|
602
|
+
**Key Differences:**
|
|
603
|
+
|
|
604
|
+
1. **No custom IDs**: You cannot choose blob IDs, they are computed
|
|
605
|
+
2. **Automatic deduplication**: Same content = same ID
|
|
606
|
+
3. **Verify on read**: You can verify content integrity by recomputing the hash
|
|
607
|
+
4. **External metadata**: Store file names, tags, etc. separately (e.g., in @rljson/io)
|
|
608
|
+
|
|
609
|
+
## Frequently Asked Questions
|
|
610
|
+
|
|
611
|
+
### Q: How do I organize blobs into folders or containers?
|
|
612
|
+
|
|
613
|
+
A: The Bs interface provides a flat storage pool. Organizational metadata (folders, tags, file names) should be stored separately, such as in a database or using `@rljson/io` (data table storage). Reference blobs by their `blobId`.
|
|
614
|
+
|
|
615
|
+
### Q: What happens if I delete a blob that's referenced elsewhere?
|
|
616
|
+
|
|
617
|
+
A: The blob is permanently deleted. In production systems with shared blobs, implement reference counting before deletion.
|
|
618
|
+
|
|
619
|
+
### Q: Can I use this in the browser?
|
|
620
|
+
|
|
621
|
+
A: Yes, but you'll need to provide your own Socket implementation for network communication, or use `BsMem` for local-only storage.
|
|
622
|
+
|
|
623
|
+
### Q: How does BsMulti handle write conflicts?
|
|
624
|
+
|
|
625
|
+
A: `BsMulti` writes to all writable stores in parallel. If any write fails, the error is thrown. All writable stores will have the blob since content is identical (content-addressable).
|
|
626
|
+
|
|
627
|
+
### Q: Why is BsPeerBridge read-only?
|
|
628
|
+
|
|
629
|
+
A: BsPeerBridge implements the PULL architecture pattern, where the server can read from client storage but cannot modify it. This prevents the server from pushing unwanted data to clients. Use BsPeer for client-to-server writes.
|
|
476
630
|
|
|
477
631
|
## License
|
|
478
632
|
|
|
@@ -485,3 +639,4 @@ Issues and pull requests welcome at <https://github.com/rljson/bs>
|
|
|
485
639
|
## Related Packages
|
|
486
640
|
|
|
487
641
|
- `@rljson/io` - Data table storage interface and implementations
|
|
642
|
+
- `@rljson/hash` - Cryptographic hashing utilities
|
package/dist/bs-peer-bridge.d.ts
CHANGED
|
@@ -10,6 +10,8 @@ export declare class BsPeerBridge {
|
|
|
10
10
|
private _bs;
|
|
11
11
|
private _socket;
|
|
12
12
|
private _eventHandlers;
|
|
13
|
+
private _handleConnectBound;
|
|
14
|
+
private _handleDisconnectBound;
|
|
13
15
|
constructor(_bs: Bs, _socket: Socket);
|
|
14
16
|
/**
|
|
15
17
|
* Starts the bridge by setting up connection event handlers and
|