@nuvix/storage 1.0.1 → 2.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.
Files changed (37) hide show
  1. package/README.md +154 -268
  2. package/dist/index.d.ts +14 -469
  3. package/dist/index.d.ts.map +1 -0
  4. package/dist/index.js +2 -7360
  5. package/dist/src/device/local.d.ts +49 -0
  6. package/dist/src/device/local.d.ts.map +1 -0
  7. package/dist/src/device/minio.d.ts +22 -0
  8. package/dist/src/device/minio.d.ts.map +1 -0
  9. package/dist/src/device/s3.d.ts +101 -0
  10. package/dist/src/device/s3.d.ts.map +1 -0
  11. package/dist/src/device/wasabi.d.ts +26 -0
  12. package/dist/src/device/wasabi.d.ts.map +1 -0
  13. package/dist/src/device.d.ts +118 -0
  14. package/dist/src/device.d.ts.map +1 -0
  15. package/dist/src/errors.d.ts +15 -0
  16. package/dist/src/errors.d.ts.map +1 -0
  17. package/dist/src/storage.d.ts +44 -0
  18. package/dist/src/storage.d.ts.map +1 -0
  19. package/dist/src/validator/file-ext.d.ts +15 -0
  20. package/dist/src/validator/file-ext.d.ts.map +1 -0
  21. package/dist/src/validator/file-name.d.ts +15 -0
  22. package/dist/src/validator/file-name.d.ts.map +1 -0
  23. package/dist/src/validator/file-size.d.ts +17 -0
  24. package/dist/src/validator/file-size.d.ts.map +1 -0
  25. package/dist/src/validator/file-type.d.ts +23 -0
  26. package/dist/src/validator/file-type.d.ts.map +1 -0
  27. package/dist/src/validator/file.d.ts +14 -0
  28. package/dist/src/validator/file.d.ts.map +1 -0
  29. package/dist/src/validator/upload.d.ts +15 -0
  30. package/dist/src/validator/upload.d.ts.map +1 -0
  31. package/dist/src/validator/validator.d.ts +8 -0
  32. package/dist/src/validator/validator.d.ts.map +1 -0
  33. package/package.json +23 -28
  34. package/dist/index.cjs +0 -7375
  35. package/dist/index.cjs.map +0 -1
  36. package/dist/index.d.cts +0 -469
  37. package/dist/index.js.map +0 -1
package/README.md CHANGED
@@ -1,361 +1,247 @@
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
- ```
29
+ import { Storage, Local } from "@nuvix/storage";
53
30
 
54
- ### CommonJS (Legacy)
55
-
56
- ```javascript
57
- const { Storage, Local, Wasabi, MinIO } = require("@nuvix/storage");
58
-
59
- // Set up local storage
60
31
  const localStorage = new Local("./uploads");
61
32
  Storage.setDevice(Storage.DEVICE_LOCAL, localStorage);
62
- ```
63
-
64
- ## 🔧 Storage Devices
65
33
 
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);
34
+ const device = Storage.getDevice(Storage.DEVICE_LOCAL);
35
+ await device.write("hello.txt", "Hello, World!", "text/plain");
73
36
 
74
- // Write file
75
- await localStorage.write("documents/file.txt", "content", "text/plain");
76
-
77
- // Read file
78
- const content = await localStorage.read("documents/file.txt");
79
-
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
85
-
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
- ```
40
+ ## Storage Devices
110
41
 
111
- ### AWS S3
42
+ ### Local Filesystem
112
43
 
113
44
  ```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
129
-
130
- ```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");
147
-
148
- // Read from MinIO
149
- const content = await minioStorage.read("local-file.txt");
45
+ import { Local } from "@nuvix/storage";
150
46
 
151
- // Check if file exists
152
- const exists = await minioStorage.exists("local-file.txt");
47
+ // Root defaults to the current working directory
48
+ const device = new Local("./uploads");
153
49
 
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";
77
+ import { Wasabi } from "@nuvix/storage";
176
78
 
177
- const sizeValidator = new FileSize(5 * 1024 * 1024); // 5MB limit
178
-
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";
91
+ import { MinIO } from "@nuvix/storage";
187
92
 
188
- const nameValidator = new FileName();
189
-
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"]);
105
+ import { FileExt, FileName, FileSize, FileType, Upload } from "@nuvix/storage";
200
106
 
201
- // Validates based on file content, not extension
202
- console.log(await typeValidator.isValid("./image.jpg")); // true if actual JPEG
203
- ```
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)
204
110
 
205
- ### Upload File Validation
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");
206
113
 
207
- ```typescript
208
- import { Upload } from "@nuvix/storage";
209
-
210
- const uploadValidator = new Upload();
211
-
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
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 |
238
144
 
239
- ```typescript
240
- const sourceDevice = Storage.getDevice("local");
241
- const targetDevice = Storage.getDevice("wasabi");
242
-
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
258
-
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
159
+ ## Device Registry & Utilities
264
160
 
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:
174
+ Every device implements the same interface:
275
175
 
276
- ```bash
277
- # Run all tests
278
- bun test
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)`
279
186
 
280
- # Run tests in watch mode
281
- bun run test:watch
187
+ ## Migrating from v1
282
188
 
283
- # Run tests with coverage
284
- bun run test:coverage
189
+ v2.0.0 is a breaking release:
285
190
 
286
- # Run specific test files
287
- bun test storage.test.ts
288
- bun test device/
289
- bun test validator/
290
- ```
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()`.
291
201
 
292
- ### Testing with Real Cloud Storage
202
+ See [MIGRATION.md](./MIGRATION.md) for the full step-by-step migration guide.
293
203
 
294
- Set environment variables to test against real storage services:
204
+ ## Development
295
205
 
296
206
  ```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"
207
+ bun install # install dev dependencies
208
+ bun test # run offline test suite
209
+ bun run test:watch # watch mode
210
+ bun run lint # oxlint
211
+ bun run typecheck # tsc --noEmit
212
+ bun run build # bundle to dist/ (ESM + declarations)
312
213
  ```
313
214
 
314
- ## 📝 API Reference
215
+ The default suite is fully offline — cloud devices are exercised through offline-presign URL checks and config validation, so no credentials are ever needed.
315
216
 
316
- ### Storage Class
217
+ ### Integration Tests
317
218
 
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
219
+ An opt-in integration suite (`__tests__/integration/`) runs against a real MinIO server and is skipped unless `MINIO_TEST_URL` is set:
322
220
 
323
- ### Device Interface
324
-
325
- All storage devices implement the same interface:
326
-
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
221
+ ```bash
222
+ # 1. Start MinIO and create the test bucket
223
+ docker run -d --name storage-test-minio -p 9000:9000 -p 9001:9001 \
224
+ -e MINIO_ROOT_USER=minioadmin -e MINIO_ROOT_PASSWORD=minioadmin-test \
225
+ minio/minio server /data --console-address ":9001"
226
+ docker exec storage-test-minio mc alias set local http://localhost:9000 minioadmin minioadmin-test
227
+ docker exec storage-test-minio mc mb --ignore-existing local/nuvix-storage-test
339
228
 
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.
229
+ # 2. Run the integration tests
230
+ MINIO_TEST_URL=http://localhost:9000 bun run test:integration
231
+ ```
341
232
 
342
- ## 🤝 Contributing
233
+ These cover real S3 protocol behavior the offline suite can't: chunked upload assembly, abort cleanup, presigned GET/PUT round-trips, and cross-device transfers.
343
234
 
344
- Contributions are welcome! Please read our contributing guidelines and ensure all tests pass:
235
+ ## Contributing
345
236
 
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
- ```
237
+ Contributions are welcome! Please ensure `bun test`, `bun run lint`, and `bun run typecheck` all pass before opening a PR.
351
238
 
352
- ## 📄 License
239
+ ## License
353
240
 
354
241
  MIT © [Nuvix](https://github.com/nuvix-tech/storage)
355
242
 
356
- ## 🔗 Links
243
+ ## Links
357
244
 
358
245
  - [GitHub Repository](https://github.com/nuvix-tech/storage)
359
246
  - [NPM Package](https://www.npmjs.com/package/@nuvix/storage)
360
- - [Documentation](https://github.com/nuvix-tech/storage#readme)
361
247
  - [Issue Tracker](https://github.com/nuvix-tech/storage/issues)