@nuvix/storage 1.0.0 → 2.0.0

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.md CHANGED
@@ -1,361 +1,227 @@
1
1
  # @nuvix/storage
2
2
 
3
- A powerful, TypeScript-first S3-compatible storage library for the Nuvix BaaS platform. Supports multiple storage backends including local filesystem, AWS S3, Wasabi, and MinIO with a unified API.
3
+ A TypeScript-first storage library for the Nuvix BaaS platform, built entirely on [Bun](https://bun.sh)'s native APIs (`Bun.file`, `Bun.S3Client`). Provides a unified API across local filesystem, AWS S3, Wasabi, and MinIO — with zero runtime dependencies.
4
4
 
5
- ## ✨ Features
5
+ ## Requirements
6
6
 
7
- - 🚀 **S3-Compatible API** - Consistent interface across all storage providers
8
- - 🔌 **Multiple Storage Backends** - Local, AWS S3, Wasabi, MinIO, and more
9
- - 📝 **TypeScript First** - Full type safety with comprehensive TypeScript definitions
10
- - ✅ **File Validation** - Built-in validators for file types, sizes, names, and extensions
11
- - 🔄 **Chunked Uploads** - Support for large file uploads with multipart upload
12
- - 🌐 **Modern Builds** - ESM and CommonJS support for maximum compatibility
13
- - 🧪 **Well Tested** - Comprehensive test suite with real implementation testing
7
+ - **[Bun](https://bun.sh) >= 1.2.0** (required at runtime — this library is Bun-only)
14
8
 
15
- ## 📦 Installation
9
+ ## Features
16
10
 
17
- ```bash
18
- # Using npm
19
- npm install @nuvix/storage
20
-
21
- # Using yarn
22
- yarn add @nuvix/storage
11
+ - 🚀 **Zero dependencies** — signing, multipart uploads, and XML parsing are handled natively by `Bun.S3Client`
12
+ - 🔌 **Multiple backends** — Local filesystem, AWS S3, Wasabi, and MinIO behind one interface
13
+ - ✍️ **Options-object constructors** — explicit, typed configuration for every device
14
+ - 🔏 **Presigned URLs** — generate time-limited upload/download URLs offline (no network call)
15
+ - 📊 **File stats** — size + MIME type in a single `stat()` call
16
+ - 🔄 **Chunked uploads** — large files assembled from ordered chunks, with abort support
17
+ - 🛡️ **Typed errors** — every failure throws a `StorageError` with a machine-readable `code`
18
+ - ✅ **Validators** — file extension, name, size, content type (magic bytes), and upload checks
23
19
 
24
- # Using pnpm
25
- pnpm add @nuvix/storage
20
+ ## Installation
26
21
 
27
- # Using bun
22
+ ```bash
28
23
  bun add @nuvix/storage
29
24
  ```
30
25
 
31
- ## 🚀 Quick Start
32
-
33
- ### ESM (Modern)
26
+ ## Quick Start
34
27
 
35
28
  ```typescript
36
- import {
37
- Storage,
38
- Local,
39
- Wasabi,
40
- MinIO,
41
- FileExt,
42
- FileSize,
43
- } from "@nuvix/storage";
44
-
45
- // Set up local storage
46
- const localStorage = new Local("./uploads");
47
- Storage.setDevice(Storage.DEVICE_LOCAL, localStorage);
48
-
49
- // Upload a file
50
- const localDevice = Storage.getDevice(Storage.DEVICE_LOCAL);
51
- await localDevice.write("test.txt", "Hello, World!", "text/plain");
52
- ```
53
-
54
- ### CommonJS (Legacy)
29
+ import { Storage, Local } from "@nuvix/storage";
55
30
 
56
- ```javascript
57
- const { Storage, Local, Wasabi, MinIO } = require("@nuvix/storage");
58
-
59
- // Set up local storage
60
- const localStorage = new Local("./uploads");
31
+ const localStorage = new Local({ root: "./uploads" });
61
32
  Storage.setDevice(Storage.DEVICE_LOCAL, localStorage);
62
- ```
63
-
64
- ## 🔧 Storage Devices
65
-
66
- ### Local File System
67
-
68
- ```typescript
69
- import { Local, Storage } from "@nuvix/storage";
70
-
71
- const localStorage = new Local("./uploads");
72
- Storage.setDevice("local", localStorage);
73
-
74
- // Write file
75
- await localStorage.write("documents/file.txt", "content", "text/plain");
76
33
 
77
- // Read file
78
- const content = await localStorage.read("documents/file.txt");
34
+ const device = Storage.getDevice(Storage.DEVICE_LOCAL);
35
+ await device.write("hello.txt", "Hello, World!", "text/plain");
79
36
 
80
- // Check if file exists
81
- const exists = await localStorage.exists("documents/file.txt");
37
+ console.log(await device.read("hello.txt")); // Buffer("Hello, World!")
82
38
  ```
83
39
 
84
- ### Wasabi Cloud Storage
40
+ ## Storage Devices
85
41
 
86
- ```typescript
87
- import { Wasabi, Storage } from "@nuvix/storage";
88
-
89
- const wasabiStorage = new Wasabi(
90
- "my-app-uploads", // root path
91
- "your-access-key", // access key
92
- "your-secret-key", // secret key
93
- "your-bucket-name", // bucket name
94
- Wasabi.US_CENTRAL_1, // region
95
- Wasabi.ACL_PRIVATE, // ACL (optional)
96
- );
97
-
98
- Storage.setDevice("wasabi", wasabiStorage);
99
-
100
- // Upload with metadata
101
- await wasabiStorage.uploadData(
102
- "Hello, Cloud!",
103
- "cloud-file.txt",
104
- "text/plain",
105
- 1,
106
- 1,
107
- { author: "user123" },
108
- );
109
- ```
110
-
111
- ### AWS S3
112
-
113
- ```typescript
114
- import { S3, Storage } from "@nuvix/storage";
115
-
116
- const s3Storage = new S3(
117
- "my-app-uploads", // root path
118
- "your-access-key", // access key
119
- "your-secret-key", // secret key
120
- "your-bucket-name", // bucket name
121
- S3.US_EAST_1, // region
122
- S3.ACL_PRIVATE, // ACL (optional)
123
- );
124
-
125
- Storage.setDevice("s3", s3Storage);
126
- ```
127
-
128
- ### MinIO
42
+ ### Local Filesystem
129
43
 
130
44
  ```typescript
131
- import { MinIO, Storage } from "@nuvix/storage";
132
-
133
- const minioStorage = new MinIO(
134
- "my-app-uploads", // root path
135
- "minioadmin", // access key (default MinIO credentials)
136
- "minioadmin", // secret key (default MinIO credentials)
137
- "your-bucket-name", // bucket name
138
- "localhost:9000", // MinIO endpoint
139
- MinIO.ACL_PRIVATE, // ACL (optional)
140
- false, // useSSL (optional, default: false)
141
- );
142
-
143
- Storage.setDevice("minio", minioStorage);
144
-
145
- // Upload to MinIO
146
- await minioStorage.write("local-file.txt", "Hello, MinIO!", "text/plain");
45
+ import { Local } from "@nuvix/storage";
147
46
 
148
- // Read from MinIO
149
- const content = await minioStorage.read("local-file.txt");
47
+ // Root defaults to the current working directory
48
+ const device = new Local({ root: "./uploads" });
150
49
 
151
- // Check if file exists
152
- const exists = await minioStorage.exists("local-file.txt");
153
-
154
- // Get file information
155
- const size = await minioStorage.getFileSize("local-file.txt");
156
- const mimeType = await minioStorage.getFileMimeType("local-file.txt");
50
+ await device.write("docs/report.pdf", buffer);
51
+ await device.exists("docs/report.pdf"); // true
52
+ await device.stat("docs/report.pdf"); // { size, mimeType }
157
53
  ```
158
54
 
159
- ## 🛡️ File Validation
160
-
161
- ### File Extension Validation
55
+ ### AWS S3
162
56
 
163
57
  ```typescript
164
- import { FileExt } from "@nuvix/storage";
58
+ import { S3 } from "@nuvix/storage";
165
59
 
166
- const imageValidator = new FileExt(["jpg", "png", "gif", "webp"]);
60
+ const device = new S3({
61
+ accessKeyId: "your-access-key",
62
+ secretAccessKey: "your-secret-key",
63
+ bucket: "your-bucket",
64
+ region: S3.US_EAST_1, // default
65
+ acl: S3.ACL_PRIVATE, // optional
66
+ root: "app-uploads", // optional key prefix
67
+ endpoint: "https://...", // optional custom endpoint (path-style URLs)
68
+ });
167
69
 
168
- console.log(imageValidator.isValid("photo.jpg")); // true
169
- console.log(imageValidator.isValid("document.pdf")); // false
70
+ // Presigned URL — computed locally, no network round-trip
71
+ const url = device.presign("reports/q3.pdf", { expiresIn: 3600 });
170
72
  ```
171
73
 
172
- ### File Size Validation
74
+ ### Wasabi
173
75
 
174
76
  ```typescript
175
- import { FileSize } from "@nuvix/storage";
176
-
177
- const sizeValidator = new FileSize(5 * 1024 * 1024); // 5MB limit
77
+ import { Wasabi } from "@nuvix/storage";
178
78
 
179
- console.log(sizeValidator.isValid(1024 * 1024)); // true (1MB)
180
- console.log(sizeValidator.isValid(10 * 1024 * 1024)); // false (10MB)
79
+ // Endpoint is derived automatically: https://s3.{region}.wasabisys.com
80
+ const device = new Wasabi({
81
+ accessKeyId: "your-access-key",
82
+ secretAccessKey: "your-secret-key",
83
+ bucket: "your-bucket",
84
+ region: Wasabi.EU_CENTRAL_1, // default
85
+ });
181
86
  ```
182
87
 
183
- ### File Name Validation
88
+ ### MinIO
184
89
 
185
90
  ```typescript
186
- import { FileName } from "@nuvix/storage";
187
-
188
- const nameValidator = new FileName();
91
+ import { MinIO } from "@nuvix/storage";
189
92
 
190
- console.log(nameValidator.isValid("validfile123.txt")); // true
191
- console.log(nameValidator.isValid("invalid-file.txt")); // false (contains hyphen)
93
+ const device = new MinIO({
94
+ accessKeyId: "minioadmin",
95
+ secretAccessKey: "minioadmin",
96
+ bucket: "your-bucket",
97
+ endpoint: "localhost:9000", // default; protocol prefix is stripped
98
+ useSSL: false, // default
99
+ });
192
100
  ```
193
101
 
194
- ### File Type (MIME) Validation
102
+ ## File Validation
195
103
 
196
104
  ```typescript
197
- import { FileType } from "@nuvix/storage";
198
-
199
- const typeValidator = new FileType(["jpeg", "png", "gif"]);
200
-
201
- // Validates based on file content, not extension
202
- console.log(await typeValidator.isValid("./image.jpg")); // true if actual JPEG
203
- ```
204
-
205
- ### Upload File Validation
105
+ import { FileExt, FileName, FileSize, FileType, Upload } from "@nuvix/storage";
206
106
 
207
- ```typescript
208
- import { Upload } from "@nuvix/storage";
107
+ new FileExt(["jpg", "png"]).isValid("photo.jpg"); // true
108
+ new FileName().isValid("report_2024.pdf"); // true
109
+ new FileSize(5 * 1024 * 1024).isValid(1024); // true (< 5MB)
209
110
 
210
- const uploadValidator = new Upload();
111
+ // Content-type check via magic bytes — reads only the first few bytes of the file
112
+ await new FileType([FileType.FILE_TYPE_PNG]).isValid("./maybe-image.png");
211
113
 
212
- console.log(await uploadValidator.isValid("./existing-file.txt")); // true
213
- console.log(await uploadValidator.isValid("./non-existent.txt")); // false
114
+ // Verifies the file actually exists on disk
115
+ await new Upload().isValid("./uploaded.txt");
214
116
  ```
215
117
 
216
- ## 🔄 Advanced Features
118
+ ## Error Handling
217
119
 
218
- ### Chunked Uploads
120
+ All failures throw a `StorageError` with a stable `code` you can branch on:
219
121
 
220
122
  ```typescript
221
- // Upload large files in chunks
222
- const chunks = ["chunk1", "chunk2", "chunk3"];
223
- const metadata = {};
224
-
225
- for (let i = 0; i < chunks.length; i++) {
226
- await device.uploadData(
227
- chunks[i],
228
- "large-file.txt",
229
- "text/plain",
230
- i + 1, // current chunk
231
- chunks.length, // total chunks
232
- metadata,
233
- );
123
+ import { StorageError } from "@nuvix/storage";
124
+
125
+ try {
126
+ await device.read("missing.txt");
127
+ } catch (error) {
128
+ if (error instanceof StorageError && error.code === "FILE_NOT_FOUND") {
129
+ // handle gracefully
130
+ }
234
131
  }
235
132
  ```
236
133
 
237
- ### File Transfer Between Devices
238
-
239
- ```typescript
240
- const sourceDevice = Storage.getDevice("local");
241
- const targetDevice = Storage.getDevice("wasabi");
134
+ | Code | Thrown when |
135
+ |------|-------------|
136
+ | `DEVICE_NOT_FOUND` | `Storage.getDevice()` with an unregistered name |
137
+ | `FILE_NOT_FOUND` | Reading/stat a missing file, or aborting an upload with no staged parts |
138
+ | `WRITE_FAILED` / `READ_FAILED` | Filesystem or S3 I/O failure |
139
+ | `UPLOAD_FAILED` | A chunk could not be staged |
140
+ | `TRANSFER_FAILED` | Cross-device transfer failed mid-stream |
141
+ | `DELETE_FAILED` | Deletion failed |
142
+ | `UNSUPPORTED_OPERATION` | e.g. `presign()` on a device without support |
143
+ | `INVALID_CONFIG` | Device constructed with missing credentials |
242
144
 
243
- // Transfer file from local to cloud
244
- await sourceDevice.transfer("local-file.txt", "cloud-file.txt", targetDevice);
245
- ```
145
+ ## Chunked Uploads
246
146
 
247
- ### Human-Readable File Sizes
147
+ Upload large files in ordered chunks; the final chunk triggers assembly:
248
148
 
249
149
  ```typescript
250
- import { Storage } from "@nuvix/storage";
150
+ const metadata = {};
151
+ for (let chunk = 1; chunk <= totalChunks; chunk++) {
152
+ await device.uploadData(chunkBuffer, "big-file.bin", "application/octet-stream", chunk, totalChunks, metadata);
153
+ }
251
154
 
252
- console.log(Storage.human(1024)); // "1.00kB"
253
- console.log(Storage.human(1048576)); // "1.00MB"
254
- console.log(Storage.human(1024, 2, "binary")); // "1.00KiB"
155
+ // Or give up and clean any staged parts:
156
+ await device.abort("big-file.bin");
255
157
  ```
256
158
 
257
- ## 🏗️ Building
159
+ ## Device Registry & Utilities
258
160
 
259
- The library is built using Rollup and provides both ESM and CommonJS outputs:
260
-
261
- ```bash
262
- # Build for production
263
- bun run build
264
-
265
- # Build and watch for changes
266
- bun run build:watch
161
+ ```typescript
162
+ Storage.setDevice("avatars", device); // register
163
+ Storage.getDevice("avatars"); // retrieve (throws DEVICE_NOT_FOUND)
164
+ Storage.exists("avatars"); // boolean
165
+ Storage.listDevices(); // string[]
166
+ Storage.removeDevice("avatars"); // unregister
267
167
 
268
- # Clean build directory
269
- bun run clean
168
+ Storage.human(1536); // "1.54kB"
169
+ Storage.human(1048576, 2, "binary"); // "1.00MiB"
270
170
  ```
271
171
 
272
- ## 🧪 Testing
172
+ ### Common Device Methods
273
173
 
274
- Comprehensive test suite with real implementation testing:
275
-
276
- ```bash
277
- # Run all tests
278
- bun test
174
+ Every device implements the same interface:
279
175
 
280
- # Run tests in watch mode
281
- bun run test:watch
176
+ - `write(path, data, contentType?)` / `read(path, offset?, length?)`
177
+ - `upload(source, path, chunk?, chunks?, metadata?)` — move a file from disk into storage
178
+ - `uploadData(data, path, contentType, chunk?, chunks?, metadata?)` — upload raw data
179
+ - `exists(path)` / `delete(path)` / `deletePath(path)`
180
+ - `stat(path)` → `{ size, mimeType }`
181
+ - `getFileSize(path)` / `getFileMimeType(path)` / `getFileHash(path)` (MD5)
182
+ - `transfer(source, destination, targetDevice)` / `move(source, target)`
183
+ - `createDirectory(path)` / `getDirectorySize(path)` / `getFiles(dir)`
184
+ - `presign(path, options?)` — presigned URL where supported
185
+ - `getTransferChunkSize()` / `setTransferChunkSize(bytes)`
282
186
 
283
- # Run tests with coverage
284
- bun run test:coverage
187
+ ## Migrating from v1
285
188
 
286
- # Run specific test files
287
- bun test storage.test.ts
288
- bun test device/
289
- bun test validator/
290
- ```
189
+ v2.0.0 is a breaking release:
291
190
 
292
- ### Testing with Real Cloud Storage
191
+ - **Bun only** — Node.js is no longer supported. The hand-rolled SigV4/XML client was replaced by `Bun.S3Client`.
192
+ - **Constructors take a single options object** instead of positional arguments:
193
+ ```diff
194
+ - new S3("root", accessKey, secretKey, bucket, S3.US_EAST_1)
195
+ + new S3({ accessKeyId, secretAccessKey, bucket, region: S3.US_EAST_1, root: "root" })
196
+ ```
197
+ - **ESM only** — CommonJS builds are no longer published.
198
+ - **Errors are typed** — methods throw `StorageError` (with `.code`) instead of plain `Error`s or silent booleans.
199
+ - **Fixed:** `S3.SA_EAST_1` now maps to `"sa-east-1"` (previously incorrectly mapped to `"eu-north-1"`).
200
+ - **New:** `stat()`, `presign()`, `Storage.removeDevice()`, `Storage.listDevices()`, `move()`, `abort()`.
293
201
 
294
- Set environment variables to test against real storage services:
202
+ ## Development
295
203
 
296
204
  ```bash
297
- # For Wasabi testing
298
- export WASABI_ACCESS_KEY="your-access-key"
299
- export WASABI_SECRET_KEY="your-secret-key"
300
- export WASABI_BUCKET="your-test-bucket"
301
-
302
- # For AWS S3 testing
303
- export AWS_ACCESS_KEY="your-access-key"
304
- export AWS_SECRET_KEY="your-secret-key"
305
- export AWS_BUCKET="your-test-bucket"
306
-
307
- # For MinIO testing
308
- export MINIO_ACCESS_KEY="minioadmin"
309
- export MINIO_SECRET_KEY="minioadmin"
310
- export MINIO_BUCKET="test-bucket"
311
- export MINIO_ENDPOINT="localhost:9000"
205
+ bun install # install dev dependencies
206
+ bun test # run test suite
207
+ bun run test:watch # watch mode
208
+ bun run lint # oxlint
209
+ bun run typecheck # tsc --noEmit
210
+ bun run build # bundle to dist/ (ESM + declarations)
312
211
  ```
313
212
 
314
- ## 📝 API Reference
315
-
316
- ### Storage Class
317
-
318
- - `Storage.setDevice(name, device)` - Register a storage device
319
- - `Storage.getDevice(name)` - Get a registered storage device
320
- - `Storage.exists(name)` - Check if a device is registered
321
- - `Storage.human(bytes, decimals?, system?)` - Format bytes to human-readable string
322
-
323
- ### Device Interface
213
+ Tests that hit real cloud services stay skipped unless credentials are provided via environment variables (`AWS_*`, `WASABI_*`, `MINIO_*`).
324
214
 
325
- All storage devices implement the same interface:
215
+ ## Contributing
326
216
 
327
- - `upload(source, path, chunk?, chunks?, metadata?)` - Upload file
328
- - `uploadData(data, path, contentType, chunk?, chunks?, metadata?)` - Upload data
329
- - `read(path, offset?, length?)` - Read file content
330
- - `write(path, data, contentType)` - Write file
331
- - `delete(path, recursive?)` - Delete file/directory
332
- - `exists(path)` - Check if file exists
333
- - `getFileSize(path)` - Get file size
334
- - `getFileMimeType(path)` - Get file MIME type
335
- - `getFileHash(path)` - Get file MD5 hash
336
- - `transfer(source, destination, targetDevice)` - Transfer file
337
-
338
- ## 🌍 Browser Support
339
-
340
- This library is designed for Node.js environments. For browser usage, you'll need to provide polyfills for Node.js modules or use a bundler that can handle Node.js dependencies.
341
-
342
- ## 🤝 Contributing
343
-
344
- Contributions are welcome! Please read our contributing guidelines and ensure all tests pass:
345
-
346
- ```bash
347
- bun run lint # Check code style
348
- bun run lint:fix # Fix code style issues
349
- bun test # Run test suite
350
- ```
217
+ Contributions are welcome! Please ensure `bun test`, `bun run lint`, and `bun run typecheck` all pass before opening a PR.
351
218
 
352
- ## 📄 License
219
+ ## License
353
220
 
354
221
  MIT © [Nuvix](https://github.com/nuvix-tech/storage)
355
222
 
356
- ## 🔗 Links
223
+ ## Links
357
224
 
358
225
  - [GitHub Repository](https://github.com/nuvix-tech/storage)
359
226
  - [NPM Package](https://www.npmjs.com/package/@nuvix/storage)
360
- - [Documentation](https://github.com/nuvix-tech/storage#readme)
361
227
  - [Issue Tracker](https://github.com/nuvix-tech/storage/issues)