@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.
- package/README.md +154 -268
- package/dist/index.d.ts +14 -469
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +2 -7360
- package/dist/src/device/local.d.ts +49 -0
- package/dist/src/device/local.d.ts.map +1 -0
- package/dist/src/device/minio.d.ts +22 -0
- package/dist/src/device/minio.d.ts.map +1 -0
- package/dist/src/device/s3.d.ts +101 -0
- package/dist/src/device/s3.d.ts.map +1 -0
- package/dist/src/device/wasabi.d.ts +26 -0
- package/dist/src/device/wasabi.d.ts.map +1 -0
- package/dist/src/device.d.ts +118 -0
- package/dist/src/device.d.ts.map +1 -0
- package/dist/src/errors.d.ts +15 -0
- package/dist/src/errors.d.ts.map +1 -0
- package/dist/src/storage.d.ts +44 -0
- package/dist/src/storage.d.ts.map +1 -0
- package/dist/src/validator/file-ext.d.ts +15 -0
- package/dist/src/validator/file-ext.d.ts.map +1 -0
- package/dist/src/validator/file-name.d.ts +15 -0
- package/dist/src/validator/file-name.d.ts.map +1 -0
- package/dist/src/validator/file-size.d.ts +17 -0
- package/dist/src/validator/file-size.d.ts.map +1 -0
- package/dist/src/validator/file-type.d.ts +23 -0
- package/dist/src/validator/file-type.d.ts.map +1 -0
- package/dist/src/validator/file.d.ts +14 -0
- package/dist/src/validator/file.d.ts.map +1 -0
- package/dist/src/validator/upload.d.ts +15 -0
- package/dist/src/validator/upload.d.ts.map +1 -0
- package/dist/src/validator/validator.d.ts +8 -0
- package/dist/src/validator/validator.d.ts.map +1 -0
- package/package.json +23 -28
- package/dist/index.cjs +0 -7375
- package/dist/index.cjs.map +0 -1
- package/dist/index.d.cts +0 -469
- package/dist/index.js.map +0 -1
package/README.md
CHANGED
|
@@ -1,361 +1,247 @@
|
|
|
1
1
|
# @nuvix/storage
|
|
2
2
|
|
|
3
|
-
A
|
|
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
|
-
##
|
|
5
|
+
## Requirements
|
|
6
6
|
|
|
7
|
-
-
|
|
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
|
-
##
|
|
9
|
+
## Features
|
|
16
10
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
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
|
-
|
|
25
|
-
pnpm add @nuvix/storage
|
|
20
|
+
## Installation
|
|
26
21
|
|
|
27
|
-
|
|
22
|
+
```bash
|
|
28
23
|
bun add @nuvix/storage
|
|
29
24
|
```
|
|
30
25
|
|
|
31
|
-
##
|
|
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
|
-
|
|
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
|
-
//
|
|
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
|
-
|
|
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
|
-
###
|
|
42
|
+
### Local Filesystem
|
|
112
43
|
|
|
113
44
|
```typescript
|
|
114
|
-
import {
|
|
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
|
-
//
|
|
152
|
-
const
|
|
47
|
+
// Root defaults to the current working directory
|
|
48
|
+
const device = new Local("./uploads");
|
|
153
49
|
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
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
|
-
|
|
160
|
-
|
|
161
|
-
### File Extension Validation
|
|
55
|
+
### AWS S3
|
|
162
56
|
|
|
163
57
|
```typescript
|
|
164
|
-
import {
|
|
58
|
+
import { S3 } from "@nuvix/storage";
|
|
165
59
|
|
|
166
|
-
const
|
|
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
|
-
|
|
169
|
-
|
|
70
|
+
// Presigned URL — computed locally, no network round-trip
|
|
71
|
+
const url = device.presign("reports/q3.pdf", { expiresIn: 3600 });
|
|
170
72
|
```
|
|
171
73
|
|
|
172
|
-
###
|
|
74
|
+
### Wasabi
|
|
173
75
|
|
|
174
76
|
```typescript
|
|
175
|
-
import {
|
|
77
|
+
import { Wasabi } from "@nuvix/storage";
|
|
176
78
|
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
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
|
-
###
|
|
88
|
+
### MinIO
|
|
184
89
|
|
|
185
90
|
```typescript
|
|
186
|
-
import {
|
|
91
|
+
import { MinIO } from "@nuvix/storage";
|
|
187
92
|
|
|
188
|
-
const
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
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
|
-
|
|
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
|
-
|
|
202
|
-
|
|
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
|
-
|
|
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
|
-
|
|
208
|
-
|
|
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
|
-
##
|
|
118
|
+
## Error Handling
|
|
217
119
|
|
|
218
|
-
|
|
120
|
+
All failures throw a `StorageError` with a stable `code` you can branch on:
|
|
219
121
|
|
|
220
122
|
```typescript
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
147
|
+
Upload large files in ordered chunks; the final chunk triggers assembly:
|
|
248
148
|
|
|
249
149
|
```typescript
|
|
250
|
-
|
|
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
|
-
|
|
253
|
-
|
|
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
|
-
##
|
|
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
|
-
|
|
266
|
-
|
|
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
|
-
|
|
269
|
-
|
|
168
|
+
Storage.human(1536); // "1.54kB"
|
|
169
|
+
Storage.human(1048576, 2, "binary"); // "1.00MiB"
|
|
270
170
|
```
|
|
271
171
|
|
|
272
|
-
|
|
172
|
+
### Common Device Methods
|
|
273
173
|
|
|
274
|
-
|
|
174
|
+
Every device implements the same interface:
|
|
275
175
|
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
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
|
-
|
|
281
|
-
bun run test:watch
|
|
187
|
+
## Migrating from v1
|
|
282
188
|
|
|
283
|
-
|
|
284
|
-
bun run test:coverage
|
|
189
|
+
v2.0.0 is a breaking release:
|
|
285
190
|
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
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
|
-
|
|
202
|
+
See [MIGRATION.md](./MIGRATION.md) for the full step-by-step migration guide.
|
|
293
203
|
|
|
294
|
-
|
|
204
|
+
## Development
|
|
295
205
|
|
|
296
206
|
```bash
|
|
297
|
-
#
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
#
|
|
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
|
-
|
|
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
|
-
###
|
|
217
|
+
### Integration Tests
|
|
317
218
|
|
|
318
|
-
- `
|
|
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
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
-
|
|
329
|
-
-
|
|
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
|
-
|
|
229
|
+
# 2. Run the integration tests
|
|
230
|
+
MINIO_TEST_URL=http://localhost:9000 bun run test:integration
|
|
231
|
+
```
|
|
341
232
|
|
|
342
|
-
|
|
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
|
-
|
|
235
|
+
## Contributing
|
|
345
236
|
|
|
346
|
-
|
|
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
|
-
##
|
|
239
|
+
## License
|
|
353
240
|
|
|
354
241
|
MIT © [Nuvix](https://github.com/nuvix-tech/storage)
|
|
355
242
|
|
|
356
|
-
##
|
|
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)
|