@rljson/bs 0.0.18 → 0.0.19
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.public.md +475 -3
- package/dist/README.public.md +475 -3
- package/dist/bs-multi.d.ts +96 -0
- package/dist/bs-peer-bridge.d.ts +70 -0
- package/dist/bs-peer.d.ts +77 -0
- package/dist/bs-server.d.ts +32 -0
- package/dist/bs.js +958 -1
- package/dist/conformance-tests/bs-conformance.setup.ts +1 -1
- package/dist/index.d.ts +7 -0
- package/dist/peer-socket-mock.d.ts +48 -0
- package/dist/socket-mock.d.ts +26 -0
- package/dist/socket.d.ts +15 -0
- package/package.json +16 -23
package/dist/README.public.md
CHANGED
|
@@ -8,8 +8,480 @@ found in the LICENSE file in the root of this package.
|
|
|
8
8
|
|
|
9
9
|
# @rljson/bs
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
Content-addressable blob storage interface and implementations for rljson.
|
|
12
12
|
|
|
13
|
-
##
|
|
13
|
+
## Overview
|
|
14
14
|
|
|
15
|
-
|
|
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 data integrity.
|
|
16
|
+
|
|
17
|
+
### Key Features
|
|
18
|
+
|
|
19
|
+
- **Content-Addressable Storage**: Blobs are identified by SHA256 hash of their content
|
|
20
|
+
- **Automatic Deduplication**: Identical content is stored only once
|
|
21
|
+
- **Multiple Implementations**: In-memory, peer-to-peer, server-based, and multi-tier
|
|
22
|
+
- **Type-Safe**: Full TypeScript support with comprehensive type definitions
|
|
23
|
+
- **Stream Support**: Efficient handling of large blobs via ReadableStreams
|
|
24
|
+
- **100% Test Coverage**: Fully tested with comprehensive test suite
|
|
25
|
+
|
|
26
|
+
## Installation
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
npm install @rljson/bs
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## Quick Start
|
|
33
|
+
|
|
34
|
+
### In-Memory Storage
|
|
35
|
+
|
|
36
|
+
The simplest implementation for testing or temporary storage:
|
|
37
|
+
|
|
38
|
+
```typescript
|
|
39
|
+
import { BsMem } from '@rljson/bs';
|
|
40
|
+
|
|
41
|
+
// Create an in-memory blob storage
|
|
42
|
+
const bs = new BsMem();
|
|
43
|
+
|
|
44
|
+
// Store a blob - returns SHA256 hash as blobId
|
|
45
|
+
const { blobId } = await bs.setBlob('Hello, World!');
|
|
46
|
+
console.log(blobId); // e.g., "dffd6021bb2bd5b0af676290809ec3a53191dd81c7f70a4b28688a362182986f"
|
|
47
|
+
|
|
48
|
+
// Retrieve the blob
|
|
49
|
+
const { content } = await bs.getBlob(blobId);
|
|
50
|
+
console.log(content.toString()); // "Hello, World!"
|
|
51
|
+
|
|
52
|
+
// Check if blob exists
|
|
53
|
+
const exists = await bs.blobExists(blobId);
|
|
54
|
+
console.log(exists); // true
|
|
55
|
+
|
|
56
|
+
// List all blobs
|
|
57
|
+
const { blobs } = await bs.listBlobs();
|
|
58
|
+
console.log(blobs.length); // 1
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
### Multi-Tier Storage (Cache + Remote)
|
|
62
|
+
|
|
63
|
+
Combine multiple storage backends with automatic caching:
|
|
64
|
+
|
|
65
|
+
```typescript
|
|
66
|
+
import { BsMulti, BsMem, BsPeer, PeerSocketMock } from '@rljson/bs';
|
|
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();
|
|
73
|
+
|
|
74
|
+
// Setup local cache
|
|
75
|
+
const localCache = new BsMem();
|
|
76
|
+
|
|
77
|
+
// Create multi-tier storage with cache-first strategy
|
|
78
|
+
const bs = new BsMulti([
|
|
79
|
+
{ bs: localCache, priority: 0, read: true, write: true }, // Cache first
|
|
80
|
+
{ bs: remotePeer, priority: 1, read: true, write: false }, // Remote fallback
|
|
81
|
+
]);
|
|
82
|
+
await bs.init();
|
|
83
|
+
|
|
84
|
+
// Store blob - writes to cache only (writable stores)
|
|
85
|
+
const { blobId } = await bs.setBlob('Cached content');
|
|
86
|
+
|
|
87
|
+
// Read from cache first, falls back to remote
|
|
88
|
+
// Automatically hot-swaps remote blobs to cache
|
|
89
|
+
const { content } = await bs.getBlob(blobId);
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
## Core Concepts
|
|
93
|
+
|
|
94
|
+
### Content-Addressable Storage
|
|
95
|
+
|
|
96
|
+
Every blob is identified by the SHA256 hash of its content. This means:
|
|
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
|
|
101
|
+
|
|
102
|
+
```typescript
|
|
103
|
+
const bs = new BsMem();
|
|
104
|
+
|
|
105
|
+
const result1 = await bs.setBlob('Same content');
|
|
106
|
+
const result2 = await bs.setBlob('Same content');
|
|
107
|
+
|
|
108
|
+
// Both return the same blobId
|
|
109
|
+
console.log(result1.blobId === result2.blobId); // true
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
### Blob Properties
|
|
113
|
+
|
|
114
|
+
All blobs have associated metadata:
|
|
115
|
+
|
|
116
|
+
```typescript
|
|
117
|
+
interface BlobProperties {
|
|
118
|
+
blobId: string; // SHA256 hash of content
|
|
119
|
+
size: number; // Size in bytes
|
|
120
|
+
contentType: string; // MIME type (default: 'application/octet-stream')
|
|
121
|
+
createdAt: Date; // Creation timestamp
|
|
122
|
+
metadata?: Record<string, string>; // Optional custom metadata
|
|
123
|
+
}
|
|
124
|
+
```
|
|
125
|
+
|
|
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
|
+
## API Reference
|
|
236
|
+
|
|
237
|
+
### Bs Interface
|
|
238
|
+
|
|
239
|
+
All implementations conform to the `Bs` interface:
|
|
240
|
+
|
|
241
|
+
#### `setBlob(content: Buffer | string | ReadableStream): Promise<BlobProperties>`
|
|
242
|
+
|
|
243
|
+
Stores a blob and returns its properties including the SHA256 `blobId`.
|
|
244
|
+
|
|
245
|
+
```typescript
|
|
246
|
+
// From string
|
|
247
|
+
const { blobId } = await bs.setBlob('Hello');
|
|
248
|
+
|
|
249
|
+
// From Buffer
|
|
250
|
+
const buffer = Buffer.from('World');
|
|
251
|
+
await bs.setBlob(buffer);
|
|
252
|
+
|
|
253
|
+
// From ReadableStream
|
|
254
|
+
const stream = new ReadableStream({
|
|
255
|
+
start(controller) {
|
|
256
|
+
controller.enqueue(new TextEncoder().encode('Stream data'));
|
|
257
|
+
controller.close();
|
|
258
|
+
}
|
|
259
|
+
});
|
|
260
|
+
await bs.setBlob(stream);
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
#### `getBlob(blobId: string, options?: DownloadBlobOptions): Promise<{ content: Buffer; properties: BlobProperties }>`
|
|
264
|
+
|
|
265
|
+
Retrieves a blob by its ID.
|
|
266
|
+
|
|
267
|
+
```typescript
|
|
268
|
+
const { content, properties } = await bs.getBlob(blobId);
|
|
269
|
+
console.log(content.toString());
|
|
270
|
+
console.log(properties.size);
|
|
271
|
+
|
|
272
|
+
// With range request (partial content)
|
|
273
|
+
const { content: partial } = await bs.getBlob(blobId, {
|
|
274
|
+
range: { start: 0, end: 99 } // First 100 bytes
|
|
275
|
+
});
|
|
276
|
+
```
|
|
277
|
+
|
|
278
|
+
#### `getBlobStream(blobId: string): Promise<ReadableStream<Uint8Array>>`
|
|
279
|
+
|
|
280
|
+
Retrieves a blob as a stream for efficient handling of large files.
|
|
281
|
+
|
|
282
|
+
```typescript
|
|
283
|
+
const stream = await bs.getBlobStream(blobId);
|
|
284
|
+
const reader = stream.getReader();
|
|
285
|
+
|
|
286
|
+
while (true) {
|
|
287
|
+
const { done, value } = await reader.read();
|
|
288
|
+
if (done) break;
|
|
289
|
+
// Process chunk
|
|
290
|
+
console.log('Chunk size:', value.length);
|
|
291
|
+
}
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
#### `deleteBlob(blobId: string): Promise<void>`
|
|
295
|
+
|
|
296
|
+
Deletes a blob from storage.
|
|
297
|
+
|
|
298
|
+
```typescript
|
|
299
|
+
await bs.deleteBlob(blobId);
|
|
300
|
+
```
|
|
301
|
+
|
|
302
|
+
**Note:** In production systems with content-addressable storage, consider implementing reference counting before deletion.
|
|
303
|
+
|
|
304
|
+
#### `blobExists(blobId: string): Promise<boolean>`
|
|
305
|
+
|
|
306
|
+
Checks if a blob exists.
|
|
307
|
+
|
|
308
|
+
```typescript
|
|
309
|
+
if (await bs.blobExists(blobId)) {
|
|
310
|
+
console.log('Blob found');
|
|
311
|
+
}
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
#### `getBlobProperties(blobId: string): Promise<BlobProperties>`
|
|
315
|
+
|
|
316
|
+
Gets blob metadata without downloading content.
|
|
317
|
+
|
|
318
|
+
```typescript
|
|
319
|
+
const props = await bs.getBlobProperties(blobId);
|
|
320
|
+
console.log(`Blob size: ${props.size} bytes`);
|
|
321
|
+
console.log(`Created: ${props.createdAt}`);
|
|
322
|
+
```
|
|
323
|
+
|
|
324
|
+
#### `listBlobs(options?: ListBlobsOptions): Promise<ListBlobsResult>`
|
|
325
|
+
|
|
326
|
+
Lists all blobs with optional filtering and pagination.
|
|
327
|
+
|
|
328
|
+
```typescript
|
|
329
|
+
// List all blobs
|
|
330
|
+
const { blobs } = await bs.listBlobs();
|
|
331
|
+
|
|
332
|
+
// With prefix filter
|
|
333
|
+
const result = await bs.listBlobs({ prefix: 'abc' });
|
|
334
|
+
|
|
335
|
+
// Paginated listing
|
|
336
|
+
let continuationToken: string | undefined;
|
|
337
|
+
do {
|
|
338
|
+
const result = await bs.listBlobs({
|
|
339
|
+
maxResults: 100,
|
|
340
|
+
continuationToken
|
|
341
|
+
});
|
|
342
|
+
|
|
343
|
+
console.log(`Got ${result.blobs.length} blobs`);
|
|
344
|
+
continuationToken = result.continuationToken;
|
|
345
|
+
} while (continuationToken);
|
|
346
|
+
```
|
|
347
|
+
|
|
348
|
+
#### `generateSignedUrl(blobId: string, expiresIn: number, permissions?: 'read' | 'delete'): Promise<string>`
|
|
349
|
+
|
|
350
|
+
Generates a signed URL for temporary access to a blob.
|
|
351
|
+
|
|
352
|
+
```typescript
|
|
353
|
+
// Read-only URL valid for 1 hour
|
|
354
|
+
const url = await bs.generateSignedUrl(blobId, 3600);
|
|
355
|
+
|
|
356
|
+
// Delete permission URL
|
|
357
|
+
const deleteUrl = await bs.generateSignedUrl(blobId, 300, 'delete');
|
|
358
|
+
```
|
|
359
|
+
|
|
360
|
+
## Advanced Usage
|
|
361
|
+
|
|
362
|
+
### Custom Storage Implementation
|
|
363
|
+
|
|
364
|
+
Implement the `Bs` interface to create custom storage backends:
|
|
365
|
+
|
|
366
|
+
```typescript
|
|
367
|
+
import { Bs, BlobProperties } from '@rljson/bs';
|
|
368
|
+
|
|
369
|
+
class MyCustomStorage implements Bs {
|
|
370
|
+
async setBlob(content: Buffer | string | ReadableStream): Promise<BlobProperties> {
|
|
371
|
+
// Your implementation
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
async getBlob(blobId: string) {
|
|
375
|
+
// Your implementation
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
// Implement other methods...
|
|
379
|
+
}
|
|
380
|
+
```
|
|
381
|
+
|
|
382
|
+
### Multi-Tier Patterns
|
|
383
|
+
|
|
384
|
+
**Local Cache + Remote Storage:**
|
|
385
|
+
|
|
386
|
+
```typescript
|
|
387
|
+
const bs = new BsMulti([
|
|
388
|
+
{ bs: localCache, priority: 0, read: true, write: true },
|
|
389
|
+
{ bs: remoteStorage, priority: 1, read: true, write: false },
|
|
390
|
+
]);
|
|
391
|
+
```
|
|
392
|
+
|
|
393
|
+
**Write-Through Cache:**
|
|
394
|
+
|
|
395
|
+
```typescript
|
|
396
|
+
const bs = new BsMulti([
|
|
397
|
+
{ bs: localCache, priority: 0, read: true, write: true },
|
|
398
|
+
{ bs: remoteStorage, priority: 1, read: true, write: true }, // Also writable
|
|
399
|
+
]);
|
|
400
|
+
```
|
|
401
|
+
|
|
402
|
+
**Multi-Region Replication:**
|
|
403
|
+
|
|
404
|
+
```typescript
|
|
405
|
+
const bs = new BsMulti([
|
|
406
|
+
{ bs: regionUs, priority: 0, read: true, write: true },
|
|
407
|
+
{ bs: regionEu, priority: 1, read: true, write: true },
|
|
408
|
+
{ bs: regionAsia, priority: 2, read: true, write: true },
|
|
409
|
+
]);
|
|
410
|
+
```
|
|
411
|
+
|
|
412
|
+
### Error Handling
|
|
413
|
+
|
|
414
|
+
All methods throw errors for invalid operations:
|
|
415
|
+
|
|
416
|
+
```typescript
|
|
417
|
+
try {
|
|
418
|
+
await bs.getBlob('nonexistent-id');
|
|
419
|
+
} catch (error) {
|
|
420
|
+
console.error('Blob not found:', error.message);
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
// BsMulti gracefully handles partial failures
|
|
424
|
+
const multi = new BsMulti([
|
|
425
|
+
{ bs: failingStore, priority: 0, read: true, write: false },
|
|
426
|
+
{ bs: workingStore, priority: 1, read: true, write: false },
|
|
427
|
+
]);
|
|
428
|
+
|
|
429
|
+
// Falls back to workingStore if failingStore errors
|
|
430
|
+
const { content } = await multi.getBlob(blobId);
|
|
431
|
+
```
|
|
432
|
+
|
|
433
|
+
## Testing
|
|
434
|
+
|
|
435
|
+
The package includes comprehensive test utilities:
|
|
436
|
+
|
|
437
|
+
```typescript
|
|
438
|
+
import { BsMem } from '@rljson/bs';
|
|
439
|
+
|
|
440
|
+
describe('My Tests', () => {
|
|
441
|
+
let bs: BsMem;
|
|
442
|
+
|
|
443
|
+
beforeEach(() => {
|
|
444
|
+
bs = new BsMem();
|
|
445
|
+
});
|
|
446
|
+
|
|
447
|
+
it('should store and retrieve blobs', async () => {
|
|
448
|
+
const { blobId } = await bs.setBlob('test data');
|
|
449
|
+
const { content } = await bs.getBlob(blobId);
|
|
450
|
+
expect(content.toString()).toBe('test data');
|
|
451
|
+
});
|
|
452
|
+
});
|
|
453
|
+
```
|
|
454
|
+
|
|
455
|
+
## Performance Considerations
|
|
456
|
+
|
|
457
|
+
### Memory Usage
|
|
458
|
+
|
|
459
|
+
- `BsMem` stores all data in RAM - suitable for small to medium datasets
|
|
460
|
+
- Use streams (`getBlobStream`) for large blobs to avoid loading entire content into memory
|
|
461
|
+
- `BsMulti` with local cache reduces network overhead
|
|
462
|
+
|
|
463
|
+
### Network Efficiency
|
|
464
|
+
|
|
465
|
+
- Use `BsPeer` for remote access with minimal protocol overhead
|
|
466
|
+
- `BsMulti` automatically caches frequently accessed blobs
|
|
467
|
+
- Content-addressable nature prevents redundant transfers
|
|
468
|
+
|
|
469
|
+
### Deduplication
|
|
470
|
+
|
|
471
|
+
- Identical content stored multiple times occupies space only once
|
|
472
|
+
- Particularly effective for:
|
|
473
|
+
- Version control systems
|
|
474
|
+
- Backup solutions
|
|
475
|
+
- Build artifact storage
|
|
476
|
+
|
|
477
|
+
## License
|
|
478
|
+
|
|
479
|
+
MIT
|
|
480
|
+
|
|
481
|
+
## Contributing
|
|
482
|
+
|
|
483
|
+
Issues and pull requests welcome at <https://github.com/rljson/bs>
|
|
484
|
+
|
|
485
|
+
## Related Packages
|
|
486
|
+
|
|
487
|
+
- `@rljson/io` - Data table storage interface and implementations
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { BlobProperties, Bs, DownloadBlobOptions, ListBlobsOptions, ListBlobsResult } from './bs.js';
|
|
2
|
+
/**
|
|
3
|
+
* Type representing a Bs instance along with its capabilities and priority.
|
|
4
|
+
*/
|
|
5
|
+
export type BsMultiBs = {
|
|
6
|
+
bs: Bs;
|
|
7
|
+
id?: string;
|
|
8
|
+
priority: number;
|
|
9
|
+
read: boolean;
|
|
10
|
+
write: boolean;
|
|
11
|
+
};
|
|
12
|
+
/**
|
|
13
|
+
* Multi-tier Bs implementation that combines multiple underlying Bs instances
|
|
14
|
+
* with different capabilities (read, write) and priorities.
|
|
15
|
+
*
|
|
16
|
+
* Pattern: Local cache + remote server fallback
|
|
17
|
+
* - Lower priority number = checked first
|
|
18
|
+
* - Reads from highest priority readable, with hot-swapping to cache
|
|
19
|
+
* - Writes to all writable instances in parallel
|
|
20
|
+
*/
|
|
21
|
+
export declare class BsMulti implements Bs {
|
|
22
|
+
private _stores;
|
|
23
|
+
constructor(_stores: Array<BsMultiBs>);
|
|
24
|
+
/**
|
|
25
|
+
* Initializes the BsMulti by assigning IDs to all underlying Bs instances.
|
|
26
|
+
* All underlying Bs instances must already be initialized.
|
|
27
|
+
*/
|
|
28
|
+
init(): Promise<void>;
|
|
29
|
+
/**
|
|
30
|
+
* Stores a blob in all writable Bs instances in parallel.
|
|
31
|
+
* @param content - The blob content to store
|
|
32
|
+
* @returns Promise resolving to blob properties from the first successful write
|
|
33
|
+
*/
|
|
34
|
+
setBlob(content: Buffer | string | ReadableStream<Uint8Array>): Promise<BlobProperties>;
|
|
35
|
+
/**
|
|
36
|
+
* Retrieves a blob from the highest priority readable Bs instance.
|
|
37
|
+
* Hot-swaps the blob to all writable instances for caching.
|
|
38
|
+
* @param blobId - The blob identifier
|
|
39
|
+
* @param options - Download options
|
|
40
|
+
* @returns Promise resolving to blob content and properties
|
|
41
|
+
*/
|
|
42
|
+
getBlob(blobId: string, options?: DownloadBlobOptions): Promise<{
|
|
43
|
+
content: Buffer;
|
|
44
|
+
properties: BlobProperties;
|
|
45
|
+
}>;
|
|
46
|
+
/**
|
|
47
|
+
* Retrieves a blob as a ReadableStream from the highest priority readable Bs instance.
|
|
48
|
+
* @param blobId - The blob identifier
|
|
49
|
+
* @returns Promise resolving to a ReadableStream
|
|
50
|
+
*/
|
|
51
|
+
getBlobStream(blobId: string): Promise<ReadableStream<Uint8Array>>;
|
|
52
|
+
/**
|
|
53
|
+
* Deletes a blob from all writable Bs instances in parallel.
|
|
54
|
+
* @param blobId - The blob identifier
|
|
55
|
+
*/
|
|
56
|
+
deleteBlob(blobId: string): Promise<void>;
|
|
57
|
+
/**
|
|
58
|
+
* Checks if a blob exists in any readable Bs instance.
|
|
59
|
+
* @param blobId - The blob identifier
|
|
60
|
+
* @returns Promise resolving to true if blob exists in any readable
|
|
61
|
+
*/
|
|
62
|
+
blobExists(blobId: string): Promise<boolean>;
|
|
63
|
+
/**
|
|
64
|
+
* Gets blob properties from the highest priority readable Bs instance.
|
|
65
|
+
* @param blobId - The blob identifier
|
|
66
|
+
* @returns Promise resolving to blob properties
|
|
67
|
+
*/
|
|
68
|
+
getBlobProperties(blobId: string): Promise<BlobProperties>;
|
|
69
|
+
/**
|
|
70
|
+
* Lists blobs by merging results from all readable Bs instances.
|
|
71
|
+
* Deduplicates by blobId (content-addressable).
|
|
72
|
+
* @param options - Listing options
|
|
73
|
+
* @returns Promise resolving to list of blobs
|
|
74
|
+
*/
|
|
75
|
+
listBlobs(options?: ListBlobsOptions): Promise<ListBlobsResult>;
|
|
76
|
+
/**
|
|
77
|
+
* Generates a signed URL from the highest priority readable Bs instance.
|
|
78
|
+
* @param blobId - The blob identifier
|
|
79
|
+
* @param expiresIn - Expiration time in seconds
|
|
80
|
+
* @param permissions - Access permissions
|
|
81
|
+
* @returns Promise resolving to signed URL
|
|
82
|
+
*/
|
|
83
|
+
generateSignedUrl(blobId: string, expiresIn: number, permissions?: 'read' | 'delete'): Promise<string>;
|
|
84
|
+
/**
|
|
85
|
+
* Gets the list of underlying readable Bs instances, sorted by priority.
|
|
86
|
+
*/
|
|
87
|
+
get readables(): Array<BsMultiBs>;
|
|
88
|
+
/**
|
|
89
|
+
* Gets the list of underlying writable Bs instances, sorted by priority.
|
|
90
|
+
*/
|
|
91
|
+
get writables(): Array<BsMultiBs>;
|
|
92
|
+
/**
|
|
93
|
+
* Example: Local cache (BsMem) + Remote server (BsPeer)
|
|
94
|
+
*/
|
|
95
|
+
static example: () => Promise<BsMulti>;
|
|
96
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { Bs } from './bs.js';
|
|
2
|
+
import { Socket } from './socket.js';
|
|
3
|
+
/**
|
|
4
|
+
* Bridges Socket events to Bs method calls.
|
|
5
|
+
*
|
|
6
|
+
* This class listens to socket events and translates them into corresponding
|
|
7
|
+
* Bs method calls, automatically registering all Bs interface methods.
|
|
8
|
+
*/
|
|
9
|
+
export declare class BsPeerBridge {
|
|
10
|
+
private _bs;
|
|
11
|
+
private _socket;
|
|
12
|
+
private _eventHandlers;
|
|
13
|
+
constructor(_bs: Bs, _socket: Socket);
|
|
14
|
+
/**
|
|
15
|
+
* Starts the bridge by setting up connection event handlers and
|
|
16
|
+
* automatically registering all Bs methods.
|
|
17
|
+
*/
|
|
18
|
+
start(): void;
|
|
19
|
+
/**
|
|
20
|
+
* Stops the bridge by removing all event handlers.
|
|
21
|
+
*/
|
|
22
|
+
stop(): void;
|
|
23
|
+
/**
|
|
24
|
+
* Automatically registers all Bs interface methods as socket event handlers.
|
|
25
|
+
*/
|
|
26
|
+
private _registerBsMethods;
|
|
27
|
+
/**
|
|
28
|
+
* Registers a socket event to be translated to a Bs method call.
|
|
29
|
+
* @param eventName - The socket event name (should match a Bs method name)
|
|
30
|
+
* @param bsMethodName - (Optional) The Bs method name if different from eventName
|
|
31
|
+
*/
|
|
32
|
+
registerEvent(eventName: string, bsMethodName?: string): void;
|
|
33
|
+
/**
|
|
34
|
+
* Registers multiple socket events at once.
|
|
35
|
+
* @param eventNames - Array of event names to register
|
|
36
|
+
*/
|
|
37
|
+
registerEvents(eventNames: string[]): void;
|
|
38
|
+
/**
|
|
39
|
+
* Unregisters a socket event handler.
|
|
40
|
+
* @param eventName - The event name to unregister
|
|
41
|
+
*/
|
|
42
|
+
unregisterEvent(eventName: string | symbol): void;
|
|
43
|
+
/**
|
|
44
|
+
* Emits a result back through the socket.
|
|
45
|
+
* @param eventName - The event name to emit
|
|
46
|
+
* @param data - The data to send
|
|
47
|
+
*/
|
|
48
|
+
emitToSocket(eventName: string | symbol, ...data: any[]): void;
|
|
49
|
+
/**
|
|
50
|
+
* Calls a Bs method directly and emits the result through the socket.
|
|
51
|
+
* @param bsMethodName - The Bs method to call
|
|
52
|
+
* @param socketEventName - The socket event to emit with the result
|
|
53
|
+
* @param args - Arguments to pass to the Bs method
|
|
54
|
+
*/
|
|
55
|
+
callBsAndEmit(bsMethodName: string, socketEventName: string | symbol, ...args: any[]): Promise<void>;
|
|
56
|
+
private _handleConnect;
|
|
57
|
+
private _handleDisconnect;
|
|
58
|
+
/**
|
|
59
|
+
* Gets the current socket instance.
|
|
60
|
+
*/
|
|
61
|
+
get socket(): Socket;
|
|
62
|
+
/**
|
|
63
|
+
* Gets the current Bs instance.
|
|
64
|
+
*/
|
|
65
|
+
get bs(): Bs;
|
|
66
|
+
/**
|
|
67
|
+
* Returns whether the socket is currently connected.
|
|
68
|
+
*/
|
|
69
|
+
get isConnected(): boolean;
|
|
70
|
+
}
|