@cubis/vfsclient 0.0.1
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/LICENSE +21 -0
- package/README.md +315 -0
- package/dist/index.cjs +1577 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +649 -0
- package/dist/index.d.ts +649 -0
- package/dist/index.js +1560 -0
- package/dist/index.js.map +1 -0
- package/package.json +60 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Cubis
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,315 @@
|
|
|
1
|
+
# @cubis/vfsclient
|
|
2
|
+
|
|
3
|
+
The official, universal TypeScript client SDK for **vFS Server** (Virtual File System). Works seamlessly across **Node.js**, **Bun**, and the **Browser**.
|
|
4
|
+
|
|
5
|
+
[](https://www.npmjs.com/package/@cubis/vfsclient)
|
|
6
|
+
[](LICENSE)
|
|
7
|
+
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## Features
|
|
11
|
+
|
|
12
|
+
- 🌐 **Isomorphic & Universal**: Runs in Node.js (18+), Bun, Deno, and modern Browsers.
|
|
13
|
+
- 🔐 **Dual Auth Support**: Authenticate using `x-api-key` or `x-api-hash`.
|
|
14
|
+
- ⚡ **Smart Preflight Deduplication**: Checks quota and SHA-256 before transferring bytes. Skips re-uploading if content already exists in the bucket.
|
|
15
|
+
- 📦 **Resumable Chunked Session Uploads**: Supports VFS edge durable storage chunking (`edge-chunks-v1`) with 1 MiB chunk slicing, per-chunk checksums, and auto-resume.
|
|
16
|
+
- 🚀 **Background Multi-File Queue**: Concurrent batch uploads with progress tracking, pause, resume, and cancellation.
|
|
17
|
+
- 💾 **Multi-Tier Caching**:
|
|
18
|
+
- **Browser**: Native `CacheStorage` (`window.caches`) with memory fallback.
|
|
19
|
+
- **Server-Side**: Local persistent disk cache (`.vfs-cache`) with TTL and atomic writes.
|
|
20
|
+
- **In-Memory**: Universal LRU cache with TTL expiration.
|
|
21
|
+
- 📄 **Metadata & Manifest Lookups**: Retrieve full file metadata JSON (`ObjectManifestResponse`) without downloading file bytes.
|
|
22
|
+
- 📊 **Real-time Bucket Analytics**: File counts, byte usage, quota headroom, read/write counters, and daily growth metrics.
|
|
23
|
+
- 🛡️ **RFC 7807 Problem Details**: Rich, structured error handling with trace IDs and status checking.
|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
## Installation
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
# Bun
|
|
31
|
+
bun add @cubis/vfsclient
|
|
32
|
+
|
|
33
|
+
# npm
|
|
34
|
+
npm install @cubis/vfsclient
|
|
35
|
+
|
|
36
|
+
# pnpm
|
|
37
|
+
pnpm add @cubis/vfsclient
|
|
38
|
+
|
|
39
|
+
# yarn
|
|
40
|
+
yarn add @cubis/vfsclient
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
---
|
|
44
|
+
|
|
45
|
+
## Quickstart
|
|
46
|
+
|
|
47
|
+
```typescript
|
|
48
|
+
import { VFSClient } from '@cubis/vfsclient';
|
|
49
|
+
|
|
50
|
+
// Initialize client
|
|
51
|
+
const vfs = new VFSClient({
|
|
52
|
+
endpoint: 'https://vfs.example.com',
|
|
53
|
+
apiKey: 'vfs_your_api_key', // Or apiHash: 'your_hash_here'
|
|
54
|
+
defaultBucket: 'default',
|
|
55
|
+
cache: true, // Enables automatic browser/server caching
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
// 1. Upload a file
|
|
59
|
+
const file = await vfs.upload({
|
|
60
|
+
file: new Blob(['Hello World!'], { type: 'text/plain' }),
|
|
61
|
+
name: 'hello.txt',
|
|
62
|
+
onProgress: ({ percent, phase }) => {
|
|
63
|
+
console.log(`Upload ${phase}: ${percent}%`);
|
|
64
|
+
},
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
console.log('Uploaded File ID:', file.file_id);
|
|
68
|
+
console.log('Public URL:', file.url);
|
|
69
|
+
|
|
70
|
+
// 2. Get file metadata (JSON manifest)
|
|
71
|
+
const meta = await vfs.getFileMetadata('default', file.file_id);
|
|
72
|
+
console.log(`File size: ${meta.size} bytes, Hash: ${meta.file_hash}`);
|
|
73
|
+
|
|
74
|
+
// 3. Download the file (cached automatically)
|
|
75
|
+
const downloadedBlob = await vfs.getFile('default', file.file_id);
|
|
76
|
+
|
|
77
|
+
// 4. Delete the file (invalidates cache)
|
|
78
|
+
await vfs.deleteFile('default', file.file_id);
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
---
|
|
82
|
+
|
|
83
|
+
## Authentication
|
|
84
|
+
|
|
85
|
+
Configure authentication in `VFSClientOptions`:
|
|
86
|
+
|
|
87
|
+
```typescript
|
|
88
|
+
// Plain API Key (x-api-key)
|
|
89
|
+
const clientWithKey = new VFSClient({
|
|
90
|
+
endpoint: 'https://vfs.example.com',
|
|
91
|
+
apiKey: 'vfs_live_xxxxxxxxxxxx',
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
// HMAC / Hash Key (x-api-hash)
|
|
95
|
+
const clientWithHash = new VFSClient({
|
|
96
|
+
endpoint: 'https://vfs.example.com',
|
|
97
|
+
apiHash: 'd3b07384d113edec49eaa6238ad5ff00',
|
|
98
|
+
});
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
---
|
|
102
|
+
|
|
103
|
+
## File Uploads
|
|
104
|
+
|
|
105
|
+
### 1. Single File Upload with Smart Preflight
|
|
106
|
+
|
|
107
|
+
Accepts `File`, `Blob`, `Buffer`, `Uint8Array`, `ArrayBuffer`, `ReadableStream`, or file path string in Node.js:
|
|
108
|
+
|
|
109
|
+
```typescript
|
|
110
|
+
const result = await vfs.upload({
|
|
111
|
+
// In Node: '/path/to/document.pdf' or Buffer.from(...)
|
|
112
|
+
// In Browser: input.files[0] or new Blob(...)
|
|
113
|
+
file: documentFile,
|
|
114
|
+
name: 'document.pdf',
|
|
115
|
+
bucketId: 'invoices',
|
|
116
|
+
metadata: { customerId: 'cust_123', month: 'September' },
|
|
117
|
+
onProgress: (progress) => {
|
|
118
|
+
console.log(`${progress.phase}: ${progress.percent}%`);
|
|
119
|
+
},
|
|
120
|
+
});
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
> **Deduplication:** When `preflight: true` (default), VFS Client computes the SHA-256 hash. If the bucket already holds an identical file, the server signals a duplicate hit. The SDK returns the existing attachment record immediately without transferring the bytes again.
|
|
124
|
+
|
|
125
|
+
### 2. Resumable Chunked Session Upload
|
|
126
|
+
|
|
127
|
+
For large files or edge storage, enable session chunking:
|
|
128
|
+
|
|
129
|
+
```typescript
|
|
130
|
+
const result = await vfs.upload({
|
|
131
|
+
file: largeVideoFile,
|
|
132
|
+
name: 'movie.mp4',
|
|
133
|
+
resumable: true, // Uses edge-chunks-v1 protocol
|
|
134
|
+
chunkSize: 1048576, // 1 MiB chunks
|
|
135
|
+
resumeId: 'optional_previous_session_id', // Resume interrupted transfer
|
|
136
|
+
onProgress: (p) => {
|
|
137
|
+
console.log(`Uploading chunk: ${p.percent}%`);
|
|
138
|
+
},
|
|
139
|
+
});
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
### 3. Multiple Files & Background Queue
|
|
143
|
+
|
|
144
|
+
Upload batches with concurrency limits and progress tracking:
|
|
145
|
+
|
|
146
|
+
```typescript
|
|
147
|
+
// Simple multi-file upload
|
|
148
|
+
const results = await vfs.uploadMultiple({
|
|
149
|
+
files: [
|
|
150
|
+
{ file: fileA, name: 'fileA.png' },
|
|
151
|
+
{ file: fileB, name: 'fileB.png' },
|
|
152
|
+
{ file: fileC, name: 'fileC.png' },
|
|
153
|
+
],
|
|
154
|
+
concurrency: 3, // Upload up to 3 files in parallel
|
|
155
|
+
onProgress: (progress) => {
|
|
156
|
+
console.log(`Overall: ${progress.completedFiles}/${progress.totalFiles} (${progress.overallPercent}%)`);
|
|
157
|
+
},
|
|
158
|
+
onFileSuccess: (file, response) => {
|
|
159
|
+
console.log(`Uploaded ${file.name} -> ${response.file_id}`);
|
|
160
|
+
},
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
// Or use the interactive background queue
|
|
164
|
+
const queue = vfs.createBackgroundQueue({ concurrency: 2 });
|
|
165
|
+
|
|
166
|
+
queue.onProgress((progress, item) => {
|
|
167
|
+
console.log(`Queue ${progress.overallPercent}% complete. Current: ${progress.currentFile}`);
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
queue.add({ file: file1, name: 'f1.jpg' });
|
|
171
|
+
queue.add({ file: file2, name: 'f2.jpg' });
|
|
172
|
+
|
|
173
|
+
// Control queue
|
|
174
|
+
queue.pause();
|
|
175
|
+
queue.resume();
|
|
176
|
+
queue.cancel();
|
|
177
|
+
|
|
178
|
+
// Wait for all to complete
|
|
179
|
+
const uploaded = await queue.wait();
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
---
|
|
183
|
+
|
|
184
|
+
## File Retrieval & URL Generation
|
|
185
|
+
|
|
186
|
+
### Download with Cache
|
|
187
|
+
|
|
188
|
+
```typescript
|
|
189
|
+
// Returns Blob (default)
|
|
190
|
+
const blob = await vfs.getFile('default', 'file-id');
|
|
191
|
+
|
|
192
|
+
// Desired format: 'arrayBuffer' | 'stream' | 'text' | 'json'
|
|
193
|
+
const text = await vfs.getFile('default', 'file-id', { responseType: 'text' });
|
|
194
|
+
const json = await vfs.getFile('default', 'config.json', { responseType: 'json' });
|
|
195
|
+
const stream = await vfs.getFile('default', 'video.mp4', { responseType: 'stream' });
|
|
196
|
+
|
|
197
|
+
// Download by SHA-256 Hash (/h/:hash - immutable cache)
|
|
198
|
+
const hashedBlob = await vfs.getFileByHash('e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855');
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
### Get File Metadata (JSON Manifest)
|
|
202
|
+
|
|
203
|
+
```typescript
|
|
204
|
+
const meta = await vfs.getFileMetadata('default', 'file-id');
|
|
205
|
+
console.log({
|
|
206
|
+
id: meta.id,
|
|
207
|
+
fileId: meta.file_id,
|
|
208
|
+
name: meta.name,
|
|
209
|
+
size: meta.size,
|
|
210
|
+
hash: meta.file_hash,
|
|
211
|
+
type: meta.type,
|
|
212
|
+
metadata: meta.metadata,
|
|
213
|
+
url: meta.url,
|
|
214
|
+
});
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
### URL Generation & Image Proxy
|
|
218
|
+
|
|
219
|
+
```typescript
|
|
220
|
+
// Direct public URL
|
|
221
|
+
const url = vfs.getFileUrl('photos', 'pic.jpg', {
|
|
222
|
+
download: true, // Forces Content-Disposition: attachment
|
|
223
|
+
imgproxy: {
|
|
224
|
+
width: 600,
|
|
225
|
+
height: 400,
|
|
226
|
+
format: 'webp',
|
|
227
|
+
quality: 80,
|
|
228
|
+
},
|
|
229
|
+
});
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
---
|
|
233
|
+
|
|
234
|
+
## Caching
|
|
235
|
+
|
|
236
|
+
Enable caching by passing `cache: true` in `VFSClientOptions`:
|
|
237
|
+
|
|
238
|
+
- In **Browsers**, caching is stored in `window.caches` (CacheStorage API).
|
|
239
|
+
- In **Node.js / Bun**, caching is written to the local filesystem (`./.vfs-cache`) with TTL and sidecar metadata.
|
|
240
|
+
- Automatically handles cache invalidation when `deleteFile(bucketId, fileId)` is called.
|
|
241
|
+
|
|
242
|
+
Custom cache adapter:
|
|
243
|
+
|
|
244
|
+
```typescript
|
|
245
|
+
import { VFSClient, VFSCacheAdapter } from '@cubis/vfsclient';
|
|
246
|
+
|
|
247
|
+
class RedisCacheAdapter implements VFSCacheAdapter {
|
|
248
|
+
async get(key: string) { /* ... */ }
|
|
249
|
+
async set(key: string, entry: VFSCacheEntry, ttl?: number) { /* ... */ }
|
|
250
|
+
async delete(key: string) { /* ... */ }
|
|
251
|
+
async clear() { /* ... */ }
|
|
252
|
+
async has(key: string) { /* ... */ }
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
const vfs = new VFSClient({
|
|
256
|
+
endpoint: 'https://vfs.example.com',
|
|
257
|
+
cache: new RedisCacheAdapter(),
|
|
258
|
+
});
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
---
|
|
262
|
+
|
|
263
|
+
## Bucket Statistics & Management
|
|
264
|
+
|
|
265
|
+
```typescript
|
|
266
|
+
// 1. Single Bucket Statistics
|
|
267
|
+
const stats = await vfs.getBucketStats('my-bucket');
|
|
268
|
+
console.log(`Files: ${stats.total_file}, Used: ${stats.total_size} bytes`);
|
|
269
|
+
console.log(`Quota: ${stats.max_size_bytes} bytes, Max Files: ${stats.max_files}`);
|
|
270
|
+
|
|
271
|
+
// 2. System-wide Summary
|
|
272
|
+
const summary = await vfs.getBucketSummary();
|
|
273
|
+
console.log(`Total Buckets: ${summary.total_buckets}, Total Files: ${summary.total_files}`);
|
|
274
|
+
|
|
275
|
+
// 3. Bucket Growth Metrics by Date
|
|
276
|
+
const metrics = await vfs.getBucketMetrics('my-bucket');
|
|
277
|
+
metrics.forEach(m => console.log(`${m.date}: ${m.total_files} files, ${m.total_size} bytes`));
|
|
278
|
+
|
|
279
|
+
// 4. List Files in Bucket
|
|
280
|
+
const files = await vfs.listFiles({ bucketId: 'my-bucket' });
|
|
281
|
+
|
|
282
|
+
// 5. Delete Operations
|
|
283
|
+
await vfs.deleteFile('my-bucket', 'file-id'); // Delete specific file
|
|
284
|
+
await vfs.deleteFileById('mongo-object-id'); // Delete by catalog ID
|
|
285
|
+
await vfs.deleteAllInBucket('temp-bucket'); // Purge all files in bucket
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
---
|
|
289
|
+
|
|
290
|
+
## Error Handling
|
|
291
|
+
|
|
292
|
+
Errors are instances of `VFSError` with RFC 7807 Problem Details:
|
|
293
|
+
|
|
294
|
+
```typescript
|
|
295
|
+
import { VFSError } from '@cubis/vfsclient';
|
|
296
|
+
|
|
297
|
+
try {
|
|
298
|
+
await vfs.upload({ file, name: 'large.zip', bucketId: 'limited-bucket' });
|
|
299
|
+
} catch (err) {
|
|
300
|
+
if (err instanceof VFSError) {
|
|
301
|
+
console.error(`Status: ${err.status}`);
|
|
302
|
+
console.error(`Message: ${err.message}`);
|
|
303
|
+
console.error(`Trace ID: ${err.problem?.extensions?.trace_id}`);
|
|
304
|
+
if (err.is(400)) {
|
|
305
|
+
console.error('Quota or metadata validation error');
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
---
|
|
312
|
+
|
|
313
|
+
## License
|
|
314
|
+
|
|
315
|
+
MIT © [Cubis](https://cubis.tech)
|