@ooneex/storage 0.0.1 → 0.0.4
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 +272 -348
- package/dist/index.d.ts +33 -4
- package/dist/index.js +2 -2
- package/dist/index.js.map +6 -4
- package/package.json +15 -6
- package/dist/ooneex-storage-0.0.1.tgz +0 -0
package/README.md
CHANGED
|
@@ -1,34 +1,30 @@
|
|
|
1
1
|
# @ooneex/storage
|
|
2
2
|
|
|
3
|
-
A
|
|
3
|
+
A file and object storage abstraction layer for TypeScript applications with support for local filesystem, Cloudflare R2, and Bunny CDN. This package provides a unified interface for storing, retrieving, and managing files across different storage backends.
|
|
4
4
|
|
|
5
5
|

|
|
6
|
+

|
|
7
|
+

|
|
6
8
|

|
|
7
9
|

|
|
8
10
|
|
|
9
11
|
## Features
|
|
10
12
|
|
|
11
|
-
✅ **
|
|
13
|
+
✅ **Multiple Backends** - Support for filesystem, Cloudflare R2, and Bunny CDN
|
|
12
14
|
|
|
13
|
-
✅ **
|
|
15
|
+
✅ **Unified Interface** - Consistent API across all storage providers
|
|
14
16
|
|
|
15
|
-
✅ **
|
|
17
|
+
✅ **Bucket Management** - Organize files into buckets/directories
|
|
16
18
|
|
|
17
|
-
✅ **
|
|
18
|
-
|
|
19
|
-
✅ **Bucket Management** - Create, clear, and manage storage buckets/directories
|
|
20
|
-
|
|
21
|
-
✅ **Multiple Data Formats** - Support for strings, JSON, ArrayBuffers, Blobs, and streams
|
|
19
|
+
✅ **Streaming Support** - Read files as streams for memory-efficient processing
|
|
22
20
|
|
|
23
|
-
✅ **
|
|
21
|
+
✅ **JSON Support** - Store and retrieve JSON data directly
|
|
24
22
|
|
|
25
|
-
✅ **
|
|
23
|
+
✅ **File Upload** - Upload files from local paths or binary data
|
|
26
24
|
|
|
27
|
-
✅ **
|
|
25
|
+
✅ **Container Integration** - Works seamlessly with dependency injection
|
|
28
26
|
|
|
29
|
-
✅ **
|
|
30
|
-
|
|
31
|
-
✅ **Zero External Dependencies** - Uses only Bun's built-in APIs
|
|
27
|
+
✅ **Type-Safe** - Full TypeScript support with proper type definitions
|
|
32
28
|
|
|
33
29
|
## Installation
|
|
34
30
|
|
|
@@ -56,58 +52,41 @@ npm install @ooneex/storage
|
|
|
56
52
|
|
|
57
53
|
### Filesystem Storage
|
|
58
54
|
|
|
59
|
-
#### Basic Setup
|
|
60
|
-
|
|
61
55
|
```typescript
|
|
62
56
|
import { FilesystemStorage } from '@ooneex/storage';
|
|
63
57
|
|
|
64
|
-
// Using constructor options
|
|
65
58
|
const storage = new FilesystemStorage({
|
|
66
|
-
|
|
59
|
+
basePath: './uploads'
|
|
67
60
|
});
|
|
68
61
|
|
|
69
|
-
//
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
// Set bucket/directory
|
|
73
|
-
storage.setBucket('my-bucket');
|
|
74
|
-
```
|
|
75
|
-
|
|
76
|
-
#### File Operations
|
|
77
|
-
|
|
78
|
-
```typescript
|
|
79
|
-
import { FilesystemStorage } from '@ooneex/storage';
|
|
80
|
-
|
|
81
|
-
const storage = new FilesystemStorage({ storagePath: './storage' })
|
|
82
|
-
.setBucket('documents');
|
|
62
|
+
// Set the bucket (directory)
|
|
63
|
+
storage.setBucket('images');
|
|
83
64
|
|
|
84
|
-
// Store
|
|
85
|
-
await storage.
|
|
65
|
+
// Store a file
|
|
66
|
+
await storage.putFile('photo.jpg', '/path/to/local/photo.jpg');
|
|
86
67
|
|
|
87
|
-
// Store
|
|
88
|
-
await storage.put('
|
|
89
|
-
|
|
90
|
-
// Store from local file
|
|
91
|
-
await storage.putFile('backup.zip', '/path/to/local/file.zip');
|
|
68
|
+
// Store content directly
|
|
69
|
+
await storage.put('document.txt', 'Hello, World!');
|
|
92
70
|
|
|
93
71
|
// Check if file exists
|
|
94
|
-
const exists = await storage.exists('
|
|
72
|
+
const exists = await storage.exists('photo.jpg');
|
|
73
|
+
console.log(exists); // true
|
|
95
74
|
|
|
96
|
-
//
|
|
97
|
-
const
|
|
98
|
-
const text = new TextDecoder().decode(content);
|
|
75
|
+
// Get file as JSON
|
|
76
|
+
const data = await storage.getAsJson<{ name: string }>('config.json');
|
|
99
77
|
|
|
100
|
-
//
|
|
101
|
-
const
|
|
78
|
+
// Get file as ArrayBuffer
|
|
79
|
+
const buffer = await storage.getAsArrayBuffer('photo.jpg');
|
|
102
80
|
|
|
103
|
-
// Get as stream
|
|
104
|
-
const stream = storage.getAsStream('
|
|
81
|
+
// Get file as stream
|
|
82
|
+
const stream = storage.getAsStream('video.mp4');
|
|
105
83
|
|
|
106
|
-
// List all files
|
|
107
|
-
const files = await storage.list();
|
|
84
|
+
// List all files in bucket
|
|
85
|
+
const files = await storage.list();
|
|
86
|
+
console.log(files); // ['photo.jpg', 'document.txt', ...]
|
|
108
87
|
|
|
109
|
-
// Delete file
|
|
110
|
-
await storage.delete('
|
|
88
|
+
// Delete a file
|
|
89
|
+
await storage.delete('document.txt');
|
|
111
90
|
|
|
112
91
|
// Clear entire bucket
|
|
113
92
|
await storage.clearBucket();
|
|
@@ -115,444 +94,403 @@ await storage.clearBucket();
|
|
|
115
94
|
|
|
116
95
|
### Cloudflare R2 Storage
|
|
117
96
|
|
|
118
|
-
#### Basic Setup
|
|
119
|
-
|
|
120
97
|
```typescript
|
|
121
|
-
import {
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
region: 'EEUR' // EEUR, WEUR, APAC, NAM
|
|
98
|
+
import { CloudflareStorage } from '@ooneex/storage';
|
|
99
|
+
|
|
100
|
+
const storage = new CloudflareStorage({
|
|
101
|
+
accountId: 'your-account-id',
|
|
102
|
+
accessKeyId: 'your-access-key-id',
|
|
103
|
+
secretAccessKey: 'your-secret-access-key',
|
|
104
|
+
bucket: 'my-bucket'
|
|
129
105
|
});
|
|
130
106
|
|
|
131
|
-
//
|
|
132
|
-
|
|
133
|
-
const storage = new CloudflareStorageAdapter();
|
|
107
|
+
// Upload a file
|
|
108
|
+
await storage.put('uploads/image.png', imageBuffer);
|
|
134
109
|
|
|
135
|
-
|
|
110
|
+
// Get file content
|
|
111
|
+
const content = await storage.getAsArrayBuffer('uploads/image.png');
|
|
136
112
|
```
|
|
137
113
|
|
|
138
|
-
|
|
114
|
+
### Bunny CDN Storage
|
|
139
115
|
|
|
140
116
|
```typescript
|
|
141
|
-
import {
|
|
142
|
-
|
|
143
|
-
const storage = new CloudflareStorageAdapter({
|
|
144
|
-
accessKey: process.env.STORAGE_CLOUDFLARE_ACCESS_KEY!,
|
|
145
|
-
secretKey: process.env.STORAGE_CLOUDFLARE_SECRET_KEY!,
|
|
146
|
-
endpoint: process.env.STORAGE_CLOUDFLARE_ENDPOINT!,
|
|
147
|
-
region: 'EEUR'
|
|
148
|
-
}).setBucket('media-files');
|
|
149
|
-
|
|
150
|
-
// Store different types of content
|
|
151
|
-
await storage.put('image.png', new Blob([imageData], { type: 'image/png' }));
|
|
152
|
-
await storage.put('data.json', JSON.stringify({ users: [], posts: [] }));
|
|
153
|
-
await storage.put('binary-data', new ArrayBuffer(1024));
|
|
154
|
-
|
|
155
|
-
// Stream large files
|
|
156
|
-
const largeFileStream = storage.getAsStream('large-video.mp4');
|
|
157
|
-
const response = new Response(largeFileStream);
|
|
158
|
-
```
|
|
117
|
+
import { BunnyStorage } from '@ooneex/storage';
|
|
159
118
|
|
|
160
|
-
|
|
119
|
+
const storage = new BunnyStorage({
|
|
120
|
+
apiKey: 'your-bunny-api-key',
|
|
121
|
+
storageZone: 'your-storage-zone',
|
|
122
|
+
region: 'ny' // Optional: ny, la, sg, etc.
|
|
123
|
+
});
|
|
161
124
|
|
|
162
|
-
|
|
163
|
-
import { FilesystemStorage } from '@ooneex/storage';
|
|
125
|
+
storage.setBucket('assets');
|
|
164
126
|
|
|
165
|
-
|
|
166
|
-
|
|
127
|
+
// Upload file
|
|
128
|
+
await storage.putFile('styles.css', './dist/styles.css');
|
|
167
129
|
|
|
168
|
-
//
|
|
169
|
-
await storage.
|
|
130
|
+
// List files
|
|
131
|
+
const files = await storage.list();
|
|
132
|
+
```
|
|
170
133
|
|
|
171
|
-
|
|
172
|
-
const userData = { name: 'John', age: 30, active: true };
|
|
173
|
-
await storage.put('user.json', JSON.stringify(userData));
|
|
134
|
+
### Using Environment Variables
|
|
174
135
|
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
await storage.put('binary.dat', binaryData);
|
|
136
|
+
```typescript
|
|
137
|
+
import { CloudflareStorage } from '@ooneex/storage';
|
|
178
138
|
|
|
179
|
-
//
|
|
180
|
-
const
|
|
181
|
-
await storage.put('data.csv', blob);
|
|
139
|
+
// Automatically uses environment variables
|
|
140
|
+
const storage = new CloudflareStorage();
|
|
182
141
|
|
|
183
|
-
//
|
|
184
|
-
|
|
142
|
+
// Environment variables:
|
|
143
|
+
// STORAGE_CLOUDFLARE_ACCOUNT_ID
|
|
144
|
+
// STORAGE_CLOUDFLARE_ACCESS_KEY_ID
|
|
145
|
+
// STORAGE_CLOUDFLARE_SECRET_ACCESS_KEY
|
|
146
|
+
// STORAGE_CLOUDFLARE_BUCKET
|
|
147
|
+
```
|
|
185
148
|
|
|
186
|
-
|
|
187
|
-
const textBuffer = await storage.getAsArrayBuffer('text.txt');
|
|
188
|
-
const text = new TextDecoder().decode(textBuffer);
|
|
149
|
+
## API Reference
|
|
189
150
|
|
|
190
|
-
|
|
151
|
+
### Classes
|
|
191
152
|
|
|
192
|
-
|
|
193
|
-
```
|
|
153
|
+
#### `FilesystemStorage`
|
|
194
154
|
|
|
195
|
-
|
|
155
|
+
Local filesystem storage implementation.
|
|
196
156
|
|
|
157
|
+
**Constructor:**
|
|
197
158
|
```typescript
|
|
198
|
-
|
|
159
|
+
new FilesystemStorage(options?: { basePath?: string })
|
|
160
|
+
```
|
|
199
161
|
|
|
200
|
-
|
|
162
|
+
**Parameters:**
|
|
163
|
+
- `options.basePath` - Base directory for storage (default: current working directory)
|
|
201
164
|
|
|
202
|
-
|
|
203
|
-
await storage.getAsJson('nonexistent.json');
|
|
204
|
-
} catch (error) {
|
|
205
|
-
if (error instanceof StorageException) {
|
|
206
|
-
console.error('Storage error:', error.message);
|
|
207
|
-
console.error('Status:', error.getStatus()); // HTTP status code
|
|
208
|
-
}
|
|
209
|
-
}
|
|
210
|
-
```
|
|
165
|
+
---
|
|
211
166
|
|
|
212
|
-
|
|
167
|
+
#### `CloudflareStorage`
|
|
213
168
|
|
|
214
|
-
|
|
215
|
-
import { IStorage, FilesystemStorage, CloudflareStorageAdapter } from '@ooneex/storage';
|
|
216
|
-
|
|
217
|
-
function createStorage(type: 'filesystem' | 'cloudflare'): IStorage {
|
|
218
|
-
switch (type) {
|
|
219
|
-
case 'filesystem':
|
|
220
|
-
return new FilesystemStorage({ storagePath: './storage' });
|
|
221
|
-
case 'cloudflare':
|
|
222
|
-
return new CloudflareStorageAdapter();
|
|
223
|
-
default:
|
|
224
|
-
throw new Error('Unknown storage type');
|
|
225
|
-
}
|
|
226
|
-
}
|
|
169
|
+
Cloudflare R2 object storage implementation.
|
|
227
170
|
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
await storage.put('shared-file.txt', 'This works with any storage provider');
|
|
171
|
+
**Constructor:**
|
|
172
|
+
```typescript
|
|
173
|
+
new CloudflareStorage(options?: CloudflareStorageOptions)
|
|
232
174
|
```
|
|
233
175
|
|
|
234
|
-
|
|
176
|
+
**Parameters:**
|
|
177
|
+
- `options.accountId` - Cloudflare account ID
|
|
178
|
+
- `options.accessKeyId` - R2 access key ID
|
|
179
|
+
- `options.secretAccessKey` - R2 secret access key
|
|
180
|
+
- `options.bucket` - Default bucket name
|
|
181
|
+
|
|
182
|
+
---
|
|
235
183
|
|
|
236
|
-
|
|
184
|
+
#### `BunnyStorage`
|
|
237
185
|
|
|
238
|
-
|
|
186
|
+
Bunny CDN storage implementation.
|
|
239
187
|
|
|
188
|
+
**Constructor:**
|
|
240
189
|
```typescript
|
|
241
|
-
|
|
242
|
-
setBucket(name: string): this;
|
|
243
|
-
list(): Promise<string[]>;
|
|
244
|
-
clearBucket(): Promise<this>;
|
|
245
|
-
exists(key: string): Promise<boolean>;
|
|
246
|
-
delete(key: string): Promise<void>;
|
|
247
|
-
putFile(key: string, localPath: string): Promise<number>;
|
|
248
|
-
put(key: string, content: string | ArrayBufferView | ArrayBuffer | SharedArrayBuffer | Request | Response | BunFile | S3File | Blob): Promise<number>;
|
|
249
|
-
getAsJson<T = unknown>(key: string): Promise<T>;
|
|
250
|
-
getAsArrayBuffer(key: string): Promise<ArrayBuffer>;
|
|
251
|
-
getAsStream(key: string): ReadableStream;
|
|
252
|
-
}
|
|
190
|
+
new BunnyStorage(options?: BunnyStorageOptions)
|
|
253
191
|
```
|
|
254
192
|
|
|
255
|
-
|
|
193
|
+
**Parameters:**
|
|
194
|
+
- `options.apiKey` - Bunny API key
|
|
195
|
+
- `options.storageZone` - Storage zone name
|
|
196
|
+
- `options.region` - Storage region (optional)
|
|
197
|
+
|
|
198
|
+
---
|
|
256
199
|
|
|
257
|
-
|
|
200
|
+
### Interface Methods
|
|
201
|
+
|
|
202
|
+
All storage classes implement the `IStorage` interface:
|
|
203
|
+
|
|
204
|
+
##### `setBucket(name: string): IStorage`
|
|
205
|
+
|
|
206
|
+
Sets the current bucket/directory for operations.
|
|
207
|
+
|
|
208
|
+
**Parameters:**
|
|
209
|
+
- `name` - Bucket name
|
|
258
210
|
|
|
259
|
-
|
|
211
|
+
**Returns:** The storage instance for chaining
|
|
260
212
|
|
|
213
|
+
**Example:**
|
|
261
214
|
```typescript
|
|
262
|
-
|
|
263
|
-
storagePath?: string;
|
|
264
|
-
})
|
|
215
|
+
storage.setBucket('images').setBucket('thumbnails');
|
|
265
216
|
```
|
|
266
217
|
|
|
267
|
-
|
|
268
|
-
|
|
218
|
+
##### `list(): Promise<string[]>`
|
|
219
|
+
|
|
220
|
+
Lists all files in the current bucket.
|
|
221
|
+
|
|
222
|
+
**Returns:** Array of file keys/paths
|
|
269
223
|
|
|
270
224
|
**Example:**
|
|
271
225
|
```typescript
|
|
272
|
-
const
|
|
273
|
-
|
|
274
|
-
});
|
|
226
|
+
const files = await storage.list();
|
|
227
|
+
console.log(files); // ['file1.txt', 'file2.jpg', ...]
|
|
275
228
|
```
|
|
276
229
|
|
|
277
|
-
|
|
230
|
+
##### `exists(key: string): Promise<boolean>`
|
|
278
231
|
|
|
279
|
-
|
|
280
|
-
Sets the bucket (directory) name for operations.
|
|
232
|
+
Checks if a file exists.
|
|
281
233
|
|
|
282
234
|
**Parameters:**
|
|
283
|
-
- `
|
|
235
|
+
- `key` - File key/path
|
|
284
236
|
|
|
285
|
-
**Returns:** `
|
|
237
|
+
**Returns:** `true` if file exists
|
|
286
238
|
|
|
287
239
|
**Example:**
|
|
288
240
|
```typescript
|
|
289
|
-
storage.
|
|
241
|
+
if (await storage.exists('config.json')) {
|
|
242
|
+
const config = await storage.getAsJson('config.json');
|
|
243
|
+
}
|
|
290
244
|
```
|
|
291
245
|
|
|
292
246
|
##### `put(key: string, content: ContentType): Promise<number>`
|
|
247
|
+
|
|
293
248
|
Stores content at the specified key.
|
|
294
249
|
|
|
295
250
|
**Parameters:**
|
|
296
|
-
- `key` - File key/path
|
|
251
|
+
- `key` - File key/path
|
|
297
252
|
- `content` - Content to store (string, ArrayBuffer, Blob, etc.)
|
|
298
253
|
|
|
299
254
|
**Returns:** Number of bytes written
|
|
300
255
|
|
|
301
256
|
**Example:**
|
|
302
257
|
```typescript
|
|
303
|
-
const
|
|
258
|
+
const bytes = await storage.put('data.json', JSON.stringify({ foo: 'bar' }));
|
|
304
259
|
```
|
|
305
260
|
|
|
306
261
|
##### `putFile(key: string, localPath: string): Promise<number>`
|
|
307
|
-
|
|
262
|
+
|
|
263
|
+
Uploads a local file to storage.
|
|
308
264
|
|
|
309
265
|
**Parameters:**
|
|
310
|
-
- `key` - Destination key/path
|
|
311
|
-
- `localPath` -
|
|
266
|
+
- `key` - Destination key/path
|
|
267
|
+
- `localPath` - Path to local file
|
|
312
268
|
|
|
313
269
|
**Returns:** Number of bytes written
|
|
314
270
|
|
|
315
271
|
**Example:**
|
|
316
272
|
```typescript
|
|
317
|
-
await storage.putFile('
|
|
273
|
+
await storage.putFile('uploads/photo.jpg', '/tmp/photo.jpg');
|
|
318
274
|
```
|
|
319
275
|
|
|
320
|
-
##### `
|
|
321
|
-
|
|
322
|
-
```typescript
|
|
323
|
-
// Get as JSON with type safety
|
|
324
|
-
getAsJson<T = unknown>(key: string): Promise<T>
|
|
276
|
+
##### `getAsJson<T>(key: string): Promise<T>`
|
|
325
277
|
|
|
326
|
-
|
|
327
|
-
getAsArrayBuffer(key: string): Promise<ArrayBuffer>
|
|
278
|
+
Retrieves and parses a file as JSON.
|
|
328
279
|
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
```
|
|
280
|
+
**Parameters:**
|
|
281
|
+
- `key` - File key/path
|
|
332
282
|
|
|
333
|
-
|
|
334
|
-
Checks if a file exists at the specified key.
|
|
283
|
+
**Returns:** Parsed JSON object
|
|
335
284
|
|
|
336
285
|
**Example:**
|
|
337
286
|
```typescript
|
|
338
|
-
|
|
287
|
+
interface Config {
|
|
288
|
+
apiUrl: string;
|
|
289
|
+
debug: boolean;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
const config = await storage.getAsJson<Config>('config.json');
|
|
339
293
|
```
|
|
340
294
|
|
|
341
|
-
##### `
|
|
342
|
-
|
|
295
|
+
##### `getAsArrayBuffer(key: string): Promise<ArrayBuffer>`
|
|
296
|
+
|
|
297
|
+
Retrieves a file as an ArrayBuffer.
|
|
298
|
+
|
|
299
|
+
**Parameters:**
|
|
300
|
+
- `key` - File key/path
|
|
301
|
+
|
|
302
|
+
**Returns:** File content as ArrayBuffer
|
|
303
|
+
|
|
304
|
+
##### `getAsStream(key: string): ReadableStream`
|
|
305
|
+
|
|
306
|
+
Gets a file as a readable stream.
|
|
307
|
+
|
|
308
|
+
**Parameters:**
|
|
309
|
+
- `key` - File key/path
|
|
310
|
+
|
|
311
|
+
**Returns:** ReadableStream for the file
|
|
343
312
|
|
|
344
313
|
**Example:**
|
|
345
314
|
```typescript
|
|
346
|
-
|
|
315
|
+
const stream = storage.getAsStream('large-file.zip');
|
|
316
|
+
|
|
317
|
+
// Pipe to response
|
|
318
|
+
return new Response(stream);
|
|
347
319
|
```
|
|
348
320
|
|
|
349
|
-
##### `
|
|
350
|
-
|
|
321
|
+
##### `delete(key: string): Promise<void>`
|
|
322
|
+
|
|
323
|
+
Deletes a file.
|
|
351
324
|
|
|
352
|
-
**
|
|
325
|
+
**Parameters:**
|
|
326
|
+
- `key` - File key/path
|
|
353
327
|
|
|
354
328
|
**Example:**
|
|
355
329
|
```typescript
|
|
356
|
-
|
|
357
|
-
console.log('Files:', files); // ['file1.txt', 'folder/file2.json']
|
|
330
|
+
await storage.delete('old-file.txt');
|
|
358
331
|
```
|
|
359
332
|
|
|
360
333
|
##### `clearBucket(): Promise<this>`
|
|
334
|
+
|
|
361
335
|
Removes all files from the current bucket.
|
|
362
336
|
|
|
363
|
-
**Returns:**
|
|
337
|
+
**Returns:** The storage instance for chaining
|
|
364
338
|
|
|
365
339
|
**Example:**
|
|
366
340
|
```typescript
|
|
367
341
|
await storage.clearBucket();
|
|
368
342
|
```
|
|
369
343
|
|
|
370
|
-
###
|
|
344
|
+
### Types
|
|
371
345
|
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
#### Constructor
|
|
346
|
+
#### `IStorage`
|
|
375
347
|
|
|
376
348
|
```typescript
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
349
|
+
interface IStorage {
|
|
350
|
+
setBucket(name: string): IStorage;
|
|
351
|
+
list(): Promise<string[]>;
|
|
352
|
+
clearBucket(): Promise<this>;
|
|
353
|
+
exists(key: string): Promise<boolean>;
|
|
354
|
+
delete(key: string): Promise<void>;
|
|
355
|
+
putFile(key: string, localPath: string): Promise<number>;
|
|
356
|
+
put(key: string, content: ContentType): Promise<number>;
|
|
357
|
+
getAsJson<T = unknown>(key: string): Promise<T>;
|
|
358
|
+
getAsArrayBuffer(key: string): Promise<ArrayBuffer>;
|
|
359
|
+
getAsStream(key: string): ReadableStream;
|
|
360
|
+
}
|
|
383
361
|
```
|
|
384
362
|
|
|
385
|
-
|
|
386
|
-
- `options.accessKey` - Cloudflare R2 access key (or use `STORAGE_CLOUDFLARE_ACCESS_KEY` env var)
|
|
387
|
-
- `options.secretKey` - Cloudflare R2 secret key (or use `STORAGE_CLOUDFLARE_SECRET_KEY` env var)
|
|
388
|
-
- `options.endpoint` - Cloudflare R2 endpoint URL (or use `STORAGE_CLOUDFLARE_ENDPOINT` env var)
|
|
389
|
-
- `options.region` - Cloudflare R2 region (or use `STORAGE_CLOUDFLARE_REGION` env var)
|
|
363
|
+
#### `StorageClassType`
|
|
390
364
|
|
|
391
|
-
**Example:**
|
|
392
365
|
```typescript
|
|
393
|
-
|
|
394
|
-
accessKey: 'your-access-key',
|
|
395
|
-
secretKey: 'your-secret-key',
|
|
396
|
-
endpoint: 'https://account-id.r2.cloudflarestorage.com',
|
|
397
|
-
region: 'EEUR'
|
|
398
|
-
});
|
|
366
|
+
type StorageClassType = new (...args: any[]) => IStorage;
|
|
399
367
|
```
|
|
400
368
|
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
### `StorageException` Class
|
|
369
|
+
## Advanced Usage
|
|
404
370
|
|
|
405
|
-
|
|
371
|
+
### Integration with Ooneex App
|
|
406
372
|
|
|
407
373
|
```typescript
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
374
|
+
import { App } from '@ooneex/app';
|
|
375
|
+
import { CloudflareStorage } from '@ooneex/storage';
|
|
376
|
+
|
|
377
|
+
const app = new App({
|
|
378
|
+
storage: CloudflareStorage,
|
|
379
|
+
// ... other config
|
|
380
|
+
});
|
|
381
|
+
|
|
382
|
+
await app.run();
|
|
411
383
|
```
|
|
412
384
|
|
|
413
|
-
|
|
385
|
+
### Using in Controllers
|
|
386
|
+
|
|
414
387
|
```typescript
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
388
|
+
import { Route } from '@ooneex/routing';
|
|
389
|
+
import type { IController, ContextType } from '@ooneex/controller';
|
|
390
|
+
|
|
391
|
+
@Route.http({
|
|
392
|
+
name: 'api.files.upload',
|
|
393
|
+
path: '/api/files',
|
|
394
|
+
method: 'POST',
|
|
395
|
+
description: 'Upload a file'
|
|
396
|
+
})
|
|
397
|
+
class FileUploadController implements IController {
|
|
398
|
+
public async index(context: ContextType): Promise<IResponse> {
|
|
399
|
+
const { storage, files } = context;
|
|
400
|
+
const file = files['document'];
|
|
401
|
+
|
|
402
|
+
if (!file) {
|
|
403
|
+
return context.response.exception('No file provided', { status: 400 });
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
storage?.setBucket('documents');
|
|
407
|
+
const key = `${Date.now()}-${file.name}`;
|
|
408
|
+
await storage?.putFile(key, file.path);
|
|
409
|
+
|
|
410
|
+
return context.response.json({
|
|
411
|
+
key,
|
|
412
|
+
size: file.size,
|
|
413
|
+
type: file.type
|
|
414
|
+
});
|
|
420
415
|
}
|
|
421
416
|
}
|
|
422
417
|
```
|
|
423
418
|
|
|
424
|
-
###
|
|
425
|
-
|
|
426
|
-
Base class providing common functionality for storage adapters.
|
|
419
|
+
### Container Integration with Decorators
|
|
427
420
|
|
|
428
421
|
```typescript
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
422
|
+
import { container, EContainerScope } from '@ooneex/container';
|
|
423
|
+
import { CloudflareStorage, decorator } from '@ooneex/storage';
|
|
424
|
+
|
|
425
|
+
// Register with decorator
|
|
426
|
+
@decorator.storage()
|
|
427
|
+
class MyStorageService extends CloudflareStorage {
|
|
428
|
+
constructor() {
|
|
429
|
+
super();
|
|
430
|
+
this.setBucket('my-app');
|
|
431
|
+
}
|
|
434
432
|
}
|
|
435
|
-
```
|
|
436
|
-
|
|
437
|
-
## Environment Variables
|
|
438
|
-
|
|
439
|
-
### Filesystem Storage
|
|
440
433
|
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
- `STORAGE_CLOUDFLARE_ACCESS_KEY` - R2 access key
|
|
446
|
-
- `STORAGE_CLOUDFLARE_SECRET_KEY` - R2 secret key
|
|
447
|
-
- `STORAGE_CLOUDFLARE_ENDPOINT` - R2 endpoint URL
|
|
448
|
-
- `STORAGE_CLOUDFLARE_REGION` - R2 region (EEUR, WEUR, APAC, NAM)
|
|
449
|
-
|
|
450
|
-
## Error Handling
|
|
434
|
+
// Resolve from container
|
|
435
|
+
const storage = container.get(MyStorageService);
|
|
436
|
+
```
|
|
451
437
|
|
|
452
|
-
|
|
438
|
+
### Error Handling
|
|
453
439
|
|
|
454
440
|
```typescript
|
|
455
|
-
import { StorageException } from '@ooneex/storage';
|
|
441
|
+
import { CloudflareStorage, StorageException } from '@ooneex/storage';
|
|
456
442
|
|
|
457
443
|
try {
|
|
458
|
-
const storage = new
|
|
444
|
+
const storage = new CloudflareStorage();
|
|
445
|
+
const content = await storage.getAsJson('missing-file.json');
|
|
459
446
|
} catch (error) {
|
|
460
447
|
if (error instanceof StorageException) {
|
|
461
|
-
|
|
462
|
-
console.error('
|
|
448
|
+
console.error('Storage Error:', error.message);
|
|
449
|
+
console.error('Status:', error.status);
|
|
463
450
|
}
|
|
464
451
|
}
|
|
465
452
|
```
|
|
466
453
|
|
|
467
|
-
|
|
468
|
-
- Missing configuration (paths, credentials)
|
|
469
|
-
- File not found operations
|
|
470
|
-
- Permission errors
|
|
471
|
-
- Network errors (for cloud storage)
|
|
472
|
-
- Invalid JSON parsing
|
|
473
|
-
|
|
474
|
-
## Best Practices
|
|
475
|
-
|
|
476
|
-
### 1. Use Environment Variables for Configuration
|
|
454
|
+
### Streaming Large Files
|
|
477
455
|
|
|
478
456
|
```typescript
|
|
479
|
-
|
|
480
|
-
FILESYSTEM_STORAGE_PATH=./storage
|
|
481
|
-
STORAGE_CLOUDFLARE_ACCESS_KEY=your-access-key
|
|
482
|
-
STORAGE_CLOUDFLARE_SECRET_KEY=your-secret-key
|
|
483
|
-
STORAGE_CLOUDFLARE_ENDPOINT=https://your-account.r2.cloudflarestorage.com
|
|
484
|
-
STORAGE_CLOUDFLARE_REGION=EEUR
|
|
485
|
-
|
|
486
|
-
// Application code
|
|
487
|
-
const storage = new FilesystemStorage(); // Uses env vars
|
|
488
|
-
```
|
|
489
|
-
|
|
490
|
-
### 2. Implement Proper Error Handling
|
|
491
|
-
|
|
492
|
-
```typescript
|
|
493
|
-
async function safeStorageOperation() {
|
|
494
|
-
try {
|
|
495
|
-
const result = await storage.getAsJson('config.json');
|
|
496
|
-
return result;
|
|
497
|
-
} catch (error) {
|
|
498
|
-
if (error instanceof StorageException) {
|
|
499
|
-
// Log error and provide fallback
|
|
500
|
-
console.warn('Failed to load config:', error.message);
|
|
501
|
-
return getDefaultConfig();
|
|
502
|
-
}
|
|
503
|
-
throw error; // Re-throw unexpected errors
|
|
504
|
-
}
|
|
505
|
-
}
|
|
506
|
-
```
|
|
457
|
+
import { FilesystemStorage } from '@ooneex/storage';
|
|
507
458
|
|
|
508
|
-
|
|
459
|
+
const storage = new FilesystemStorage();
|
|
460
|
+
storage.setBucket('videos');
|
|
509
461
|
|
|
510
|
-
|
|
511
|
-
// Good for large files
|
|
462
|
+
// Stream directly to HTTP response
|
|
512
463
|
const stream = storage.getAsStream('large-video.mp4');
|
|
513
|
-
const response = new Response(stream, {
|
|
514
|
-
headers: { 'Content-Type': 'video/mp4' }
|
|
515
|
-
});
|
|
516
464
|
|
|
517
|
-
|
|
518
|
-
|
|
465
|
+
return new Response(stream, {
|
|
466
|
+
headers: {
|
|
467
|
+
'Content-Type': 'video/mp4',
|
|
468
|
+
'Content-Disposition': 'attachment; filename="video.mp4"'
|
|
469
|
+
}
|
|
470
|
+
});
|
|
519
471
|
```
|
|
520
472
|
|
|
521
|
-
###
|
|
473
|
+
### Organizing Files with Buckets
|
|
522
474
|
|
|
523
475
|
```typescript
|
|
524
|
-
|
|
525
|
-
await storage.put('users/profile/123.json', userData);
|
|
526
|
-
await storage.put('uploads/images/2024/01/photo.jpg', imageData);
|
|
527
|
-
await storage.put('logs/2024-01-15.log', logData);
|
|
528
|
-
|
|
529
|
-
// Less organized
|
|
530
|
-
await storage.put('user123.json', userData);
|
|
531
|
-
await storage.put('photo.jpg', imageData);
|
|
532
|
-
await storage.put('log.txt', logData);
|
|
533
|
-
```
|
|
476
|
+
import { CloudflareStorage } from '@ooneex/storage';
|
|
534
477
|
|
|
535
|
-
|
|
478
|
+
const storage = new CloudflareStorage();
|
|
536
479
|
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
name: string;
|
|
541
|
-
email: string;
|
|
542
|
-
preferences: {
|
|
543
|
-
theme: 'light' | 'dark';
|
|
544
|
-
notifications: boolean;
|
|
545
|
-
};
|
|
546
|
-
}
|
|
480
|
+
// Organize by content type
|
|
481
|
+
storage.setBucket('images/thumbnails');
|
|
482
|
+
await storage.put('photo-1.jpg', thumbnailData);
|
|
547
483
|
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
484
|
+
storage.setBucket('images/originals');
|
|
485
|
+
await storage.put('photo-1.jpg', originalData);
|
|
486
|
+
|
|
487
|
+
storage.setBucket('documents/invoices');
|
|
488
|
+
await storage.put('invoice-2024-001.pdf', pdfData);
|
|
551
489
|
```
|
|
552
490
|
|
|
553
491
|
## License
|
|
554
492
|
|
|
555
|
-
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
|
|
493
|
+
This project is licensed under the MIT License - see the [LICENSE](./LICENSE) file for details.
|
|
556
494
|
|
|
557
495
|
## Contributing
|
|
558
496
|
|
|
@@ -571,20 +509,6 @@ Contributions are welcome! Please feel free to submit a Pull Request. For major
|
|
|
571
509
|
- Follow the existing code style
|
|
572
510
|
- Update documentation for API changes
|
|
573
511
|
- Ensure all tests pass before submitting PR
|
|
574
|
-
- Add TypeScript type definitions for new APIs
|
|
575
|
-
|
|
576
|
-
### Running Tests
|
|
577
|
-
|
|
578
|
-
```bash
|
|
579
|
-
# Run all tests
|
|
580
|
-
bun run test
|
|
581
|
-
|
|
582
|
-
# Run tests in watch mode
|
|
583
|
-
bun run test:watch
|
|
584
|
-
|
|
585
|
-
# Run specific test file
|
|
586
|
-
bun test tests/FilesystemStorage.spec.ts
|
|
587
|
-
```
|
|
588
512
|
|
|
589
513
|
---
|
|
590
514
|
|
package/dist/index.d.ts
CHANGED
|
@@ -30,8 +30,33 @@ declare abstract class AbstractStorage implements IStorage {
|
|
|
30
30
|
protected getClient(): Bun.S3Client;
|
|
31
31
|
protected getS3File(path: string): S3File2;
|
|
32
32
|
}
|
|
33
|
+
import { BunFile as BunFile3, S3File as S3File3 } from "bun";
|
|
34
|
+
type BunnyRegion = "de" | "uk" | "ny" | "la" | "sg" | "se" | "br" | "jh" | "syd";
|
|
35
|
+
declare class BunnyStorage implements IStorage {
|
|
36
|
+
private bucket;
|
|
37
|
+
private readonly accessKey;
|
|
38
|
+
private readonly storageZone;
|
|
39
|
+
private readonly region;
|
|
40
|
+
constructor(options?: {
|
|
41
|
+
accessKey?: string;
|
|
42
|
+
storageZone?: string;
|
|
43
|
+
region?: BunnyRegion;
|
|
44
|
+
});
|
|
45
|
+
setBucket(name: string): this;
|
|
46
|
+
list(): Promise<string[]>;
|
|
47
|
+
clearBucket(): Promise<this>;
|
|
48
|
+
exists(key: string): Promise<boolean>;
|
|
49
|
+
delete(key: string): Promise<void>;
|
|
50
|
+
putFile(key: string, localPath: string): Promise<number>;
|
|
51
|
+
put(key: string, content: string | ArrayBufferView | ArrayBuffer | SharedArrayBuffer | Request | Response | BunFile3 | S3File3 | Blob): Promise<number>;
|
|
52
|
+
getAsJson<T = unknown>(key: string): Promise<T>;
|
|
53
|
+
getAsArrayBuffer(key: string): Promise<ArrayBuffer>;
|
|
54
|
+
getAsStream(key: string): ReadableStream;
|
|
55
|
+
private getBaseUrl;
|
|
56
|
+
private buildFileUrl;
|
|
57
|
+
}
|
|
33
58
|
import { S3Options as S3Options2 } from "bun";
|
|
34
|
-
declare class
|
|
59
|
+
declare class CloudflareStorage extends AbstractStorage {
|
|
35
60
|
protected bucket: string;
|
|
36
61
|
private readonly accessKey;
|
|
37
62
|
private readonly secretKey;
|
|
@@ -45,7 +70,11 @@ declare class CloudflareStorageAdapter extends AbstractStorage {
|
|
|
45
70
|
});
|
|
46
71
|
getOptions(): S3Options2;
|
|
47
72
|
}
|
|
48
|
-
import {
|
|
73
|
+
import { EContainerScope } from "@ooneex/container";
|
|
74
|
+
declare const decorator: {
|
|
75
|
+
storage: (scope?: EContainerScope) => (target: StorageClassType) => void;
|
|
76
|
+
};
|
|
77
|
+
import { BunFile as BunFile4, S3File as S3File4, S3Options as S3Options3 } from "bun";
|
|
49
78
|
declare class FilesystemStorage extends AbstractStorage {
|
|
50
79
|
protected bucket: string;
|
|
51
80
|
private readonly storagePath;
|
|
@@ -63,7 +92,7 @@ declare class FilesystemStorage extends AbstractStorage {
|
|
|
63
92
|
exists(key: string): Promise<boolean>;
|
|
64
93
|
delete(key: string): Promise<void>;
|
|
65
94
|
putFile(key: string, localPath: string): Promise<number>;
|
|
66
|
-
put(key: string, content: string | ArrayBuffer | SharedArrayBuffer | Request | Response |
|
|
95
|
+
put(key: string, content: string | ArrayBuffer | SharedArrayBuffer | Request | Response | BunFile4 | S3File4 | Blob): Promise<number>;
|
|
67
96
|
getAsJson<T>(key: string): Promise<T>;
|
|
68
97
|
getAsArrayBuffer(key: string): Promise<ArrayBuffer>;
|
|
69
98
|
getAsStream(key: string): ReadableStream;
|
|
@@ -72,4 +101,4 @@ import { Exception } from "@ooneex/exception";
|
|
|
72
101
|
declare class StorageException extends Exception {
|
|
73
102
|
constructor(message: string, data?: Record<string, unknown>);
|
|
74
103
|
}
|
|
75
|
-
export { StorageException, StorageClassType, IStorage, FilesystemStorage,
|
|
104
|
+
export { decorator, StorageException, StorageClassType, IStorage, FilesystemStorage, CloudflareStorage, BunnyStorage, AbstractStorage };
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
// @bun
|
|
2
|
-
class c{client=null;setBucket(t){return this.bucket=t,this.client=new Bun.S3Client(this.getOptions()),this}async list(){return(await this.getClient().list()).contents?.map((e)=>e.key)||[]}async clearBucket(){let t=this.getClient(),e=await this.list();for(let r of e)await t.delete(r);return this}async exists(t){return await this.getClient().exists(t)}async delete(t){await this.getClient().delete(t)}async putFile(t,e){let r=Bun.file(e);return await this.put(t,r)}async put(t,e){return await this.getS3File(t).write(e)}async getAsJson(t){return await this.getS3File(t).json()}async getAsArrayBuffer(t){return await this.getS3File(t).arrayBuffer()}getAsStream(t){return this.getS3File(t).stream()}getClient(){if(!this.client)this.client=new Bun.S3Client(this.getOptions());return this.client}getS3File(t){return this.getClient().file(t)}}import{Exception as d}from"@ooneex/exception";import{HttpStatus as m}from"@ooneex/http-status";class n extends d{constructor(t,e={}){super(t,{status:m.Code.InternalServerError,data:e});this.name="StorageException"}}class S extends c{bucket;accessKey;secretKey;endpoint;region;constructor(t){super();let e=t?.accessKey||Bun.env.STORAGE_CLOUDFLARE_ACCESS_KEY,r=t?.secretKey||Bun.env.STORAGE_CLOUDFLARE_SECRET_KEY,i=t?.endpoint||Bun.env.STORAGE_CLOUDFLARE_ENDPOINT;if(!e)throw new n("Cloudflare access key is required. Please provide an access key either through the constructor options or set the STORAGE_CLOUDFLARE_ACCESS_KEY environment variable.");if(!r)throw new n("Cloudflare secret key is required. Please provide a secret key either through the constructor options or set the STORAGE_CLOUDFLARE_SECRET_KEY environment variable.");if(!i)throw new n("Cloudflare endpoint is required. Please provide an endpoint either through the constructor options or set the STORAGE_CLOUDFLARE_ENDPOINT environment variable.");this.accessKey=e,this.secretKey=r,this.endpoint=i,this.region=t?.region||Bun.env.STORAGE_CLOUDFLARE_REGION||"EEUR"}getOptions(){return{accessKeyId:this.accessKey,secretAccessKey:this.secretKey,endpoint:this.endpoint,bucket:this.bucket,region:this.region}}}import{existsSync as o,mkdirSync as g}from"fs";import{mkdir as p,readdir as f,rmdir as y,stat as w}from"fs/promises";import{dirname as h,join as l}from"path";class b extends c{bucket;storagePath;constructor(t){super();let e=t?.storagePath||Bun.env.FILESYSTEM_STORAGE_PATH;if(!e)throw new n("Base path is required. Please provide a base path either through the constructor options or set the FILESYSTEM_STORAGE_PATH environment variable.");this.storagePath=e;try{if(!o(e))g(e,{recursive:!0})}catch(r){throw new n(`Failed to create base storage directory at ${e}: ${r instanceof Error?r.message:String(r)}`)}}getOptions(){return{accessKeyId:"filesystem",secretAccessKey:"filesystem",endpoint:this.storagePath,bucket:this.bucket,region:"local"}}getBucketPath(){if(!this.bucket)throw new n("Bucket name is required. Please call setBucket() first.");return l(this.storagePath,this.bucket)}getFilePath(t){return l(this.getBucketPath(),t)}setBucket(t){this.bucket=t;let e=this.getBucketPath();try{if(!o(e))g(e,{recursive:!0})}catch(r){throw new n(`Failed to create bucket directory at ${e}: ${r instanceof Error?r.message:String(r)}`)}return this}async list(){let t=this.getBucketPath();if(!o(t))return[];try{return await this.listFilesRecursive(t,t)}catch(e){throw new n(`Failed to list files in bucket: ${e instanceof Error?e.message:String(e)}`)}}async listFilesRecursive(t,e){let r=[],i=await f(t);for(let s of i){let a=l(t,s);if((await w(a)).isDirectory()){let u=await this.listFilesRecursive(a,e);r.push(...u)}else{let u=a.substring(e.length+1);r.push(u)}}return r}async clearBucket(){let t=this.getBucketPath();if(!o(t))return this;try{await this.removeDirectoryRecursive(t),await p(t,{recursive:!0})}catch(e){throw new n(`Failed to clear bucket: ${e instanceof Error?e.message:String(e)}`)}return this}async removeDirectoryRecursive(t){let e=await f(t);for(let r of e){let i=l(t,r);if((await w(i)).isDirectory())await this.removeDirectoryRecursive(i),await y(i);else await Bun.file(i).delete()}}async exists(t){let e=this.getFilePath(t);return await Bun.file(e).exists()}async delete(t){let e=this.getFilePath(t),r=Bun.file(e);if(!await r.exists())return;try{await r.delete();let i=h(e),s=this.getBucketPath();while(i!==s&&i!==this.storagePath)try{if((await f(i)).length===0)await y(i),i=h(i);else break}catch{break}}catch(i){throw new n(`Failed to delete file ${t}: ${i instanceof Error?i.message:String(i)}`)}}async putFile(t,e){let r=Bun.file(e);return await this.put(t,r)}async put(t,e){let r=this.getFilePath(t),i=h(r);try{if(!o(i))await p(i,{recursive:!0})}catch(s){throw new n(`Failed to create directory ${i}: ${s instanceof Error?s.message:String(s)}`)}try{let s;if(typeof e==="string")s=await Bun.write(r,e);else if(e instanceof ArrayBuffer)s=await Bun.write(r,e);else if(e instanceof SharedArrayBuffer){let a=new ArrayBuffer(e.byteLength);new Uint8Array(a).set(new Uint8Array(e)),s=await Bun.write(r,a)}else if(e instanceof Request){let a=await e.arrayBuffer();s=await Bun.write(r,a)}else if(e instanceof Response){let a=await e.arrayBuffer();s=await Bun.write(r,a)}else if(e instanceof Blob)s=await Bun.write(r,e);else{let a=await e.arrayBuffer();s=await Bun.write(r,a)}return s}catch(s){throw new n(`Failed to write file ${t}: ${s instanceof Error?s.message:String(s)}`)}}async getAsJson(t){let e=this.getFilePath(t),r=Bun.file(e);if(!await r.exists())throw new n(`File ${t} does not exist`);try{return await r.json()}catch(i){throw new n(`Failed to read file ${t} as JSON: ${i instanceof Error?i.message:String(i)}`)}}async getAsArrayBuffer(t){let e=this.getFilePath(t),r=Bun.file(e);if(!await r.exists())throw new n(`File ${t} does not exist`);try{return await r.arrayBuffer()}catch(i){throw new n(`Failed to read file ${t} as ArrayBuffer: ${i instanceof Error?i.message:String(i)}`)}}getAsStream(t){let e=this.getFilePath(t);if(!o(e))throw new n(`File ${t} does not exist`);let r=Bun.file(e);try{return r.stream()}catch(i){throw new n(`Failed to read file ${t} as stream: ${i instanceof Error?i.message:String(i)}`)}}}export{n as StorageException,b as FilesystemStorage,S as CloudflareStorageAdapter,c as AbstractStorage};
|
|
2
|
+
class u{client=null;setBucket(t){return this.bucket=t,this.client=new Bun.S3Client(this.getOptions()),this}async list(){return(await this.getClient().list()).contents?.map((e)=>e.key)||[]}async clearBucket(){let t=this.getClient(),e=await this.list();for(let r of e)await t.delete(r);return this}async exists(t){return await this.getClient().exists(t)}async delete(t){await this.getClient().delete(t)}async putFile(t,e){let r=Bun.file(e);return await this.put(t,r)}async put(t,e){return await this.getS3File(t).write(e)}async getAsJson(t){return await this.getS3File(t).json()}async getAsArrayBuffer(t){return await this.getS3File(t).arrayBuffer()}getAsStream(t){return this.getS3File(t).stream()}getClient(){if(!this.client)this.client=new Bun.S3Client(this.getOptions());return this.client}getS3File(t){return this.getClient().file(t)}}import{Exception as A}from"@ooneex/exception";import{HttpStatus as E}from"@ooneex/http-status";class n extends A{constructor(t,e={}){super(t,{status:E.Code.InternalServerError,data:e});this.name="StorageException"}}class y{bucket="";accessKey;storageZone;region;constructor(t){let e=t?.accessKey??Bun.env.STORAGE_BUNNY_ACCESS_KEY,r=t?.storageZone??Bun.env.STORAGE_BUNNY_STORAGE_ZONE,s=t?.region??Bun.env.STORAGE_BUNNY_REGION;if(!e)throw new n("Bunny access key is required. Please provide an access key either through the constructor options or set the STORAGE_BUNNY_ACCESS_KEY environment variable.");if(!r)throw new n("Bunny storage zone is required. Please provide a storage zone either through the constructor options or set the STORAGE_BUNNY_STORAGE_ZONE environment variable.");this.accessKey=e,this.storageZone=r,this.region=s??"de"}setBucket(t){return this.bucket=t,this}async list(){let t=this.bucket?`${this.bucket}/`:"",e=`${this.getBaseUrl()}/${this.storageZone}/${t}`,r=await fetch(e,{method:"GET",headers:{AccessKey:this.accessKey,accept:"application/json"}});if(!r.ok)throw new n(`Failed to list files: ${r.status} ${r.statusText}`,{status:r.status,path:t});return(await r.json()).filter((i)=>!i.IsDirectory).map((i)=>i.ObjectName)}async clearBucket(){let t=await this.list();for(let e of t)await this.delete(e);return this}async exists(t){let e=this.buildFileUrl(t);return(await fetch(e,{method:"GET",headers:{AccessKey:this.accessKey}})).ok}async delete(t){let e=this.buildFileUrl(t),r=await fetch(e,{method:"DELETE",headers:{AccessKey:this.accessKey}});if(!r.ok&&r.status!==404)throw new n(`Failed to delete file: ${r.status} ${r.statusText}`,{status:r.status,key:t})}async putFile(t,e){let r=Bun.file(e);return await this.put(t,r)}async put(t,e){let r=this.buildFileUrl(t),s,i;if(typeof e==="string")s=e,i=new TextEncoder().encode(e).length;else if(e instanceof ArrayBuffer)s=new Blob([e]),i=e.byteLength;else if(e instanceof SharedArrayBuffer){let o=new Uint8Array(e),c=new Uint8Array(o.length);c.set(o),s=new Blob([c]),i=e.byteLength}else if(ArrayBuffer.isView(e)){let o=e.buffer.slice(e.byteOffset,e.byteOffset+e.byteLength);s=new Blob([o]),i=e.byteLength}else if(e instanceof Blob)s=e,i=e.size;else if(e instanceof Request||e instanceof Response){let o=await e.arrayBuffer();s=new Blob([o]),i=o.byteLength}else if(typeof e==="object"&&e!==null&&"arrayBuffer"in e){let c=await e.arrayBuffer();s=new Blob([c]),i=c.byteLength}else throw new n("Unsupported content type for upload",{key:t});let a=await fetch(r,{method:"PUT",headers:{AccessKey:this.accessKey,"Content-Type":"application/octet-stream"},body:s});if(!a.ok)throw new n(`Failed to upload file: ${a.status} ${a.statusText}`,{status:a.status,key:t});return i}async getAsJson(t){let e=this.buildFileUrl(t),r=await fetch(e,{method:"GET",headers:{AccessKey:this.accessKey}});if(!r.ok)throw new n(`Failed to get file as JSON: ${r.status} ${r.statusText}`,{status:r.status,key:t});return await r.json()}async getAsArrayBuffer(t){let e=this.buildFileUrl(t),r=await fetch(e,{method:"GET",headers:{AccessKey:this.accessKey}});if(!r.ok)throw new n(`Failed to get file as ArrayBuffer: ${r.status} ${r.statusText}`,{status:r.status,key:t});return await r.arrayBuffer()}getAsStream(t){let e=this.buildFileUrl(t);return new ReadableStream({start:async(s)=>{let i=await fetch(e,{method:"GET",headers:{AccessKey:this.accessKey}});if(!i.ok){s.error(new n(`Failed to get file as stream: ${i.status} ${i.statusText}`,{status:i.status,key:t}));return}if(!i.body){s.error(new n("Response body is null",{key:t}));return}let a=i.body.getReader(),o=async()=>{let{done:c,value:S}=await a.read();if(c){s.close();return}s.enqueue(S),await o()};await o()}})}getBaseUrl(){return`https://${{de:"storage.bunnycdn.com",uk:"uk.storage.bunnycdn.com",ny:"ny.storage.bunnycdn.com",la:"la.storage.bunnycdn.com",sg:"sg.storage.bunnycdn.com",se:"se.storage.bunnycdn.com",br:"br.storage.bunnycdn.com",jh:"jh.storage.bunnycdn.com",syd:"syd.storage.bunnycdn.com"}[this.region]}`}buildFileUrl(t){let e=this.bucket?`${this.bucket}/${t}`:t;return`${this.getBaseUrl()}/${this.storageZone}/${e}`}}class p extends u{bucket;accessKey;secretKey;endpoint;region;constructor(t){super();let e=t?.accessKey||Bun.env.STORAGE_CLOUDFLARE_ACCESS_KEY,r=t?.secretKey||Bun.env.STORAGE_CLOUDFLARE_SECRET_KEY,s=t?.endpoint||Bun.env.STORAGE_CLOUDFLARE_ENDPOINT;if(!e)throw new n("Cloudflare access key is required. Please provide an access key either through the constructor options or set the STORAGE_CLOUDFLARE_ACCESS_KEY environment variable.");if(!r)throw new n("Cloudflare secret key is required. Please provide a secret key either through the constructor options or set the STORAGE_CLOUDFLARE_SECRET_KEY environment variable.");if(!s)throw new n("Cloudflare endpoint is required. Please provide an endpoint either through the constructor options or set the STORAGE_CLOUDFLARE_ENDPOINT environment variable.");this.accessKey=e,this.secretKey=r,this.endpoint=s,this.region=t?.region||Bun.env.STORAGE_CLOUDFLARE_REGION||"EEUR"}getOptions(){return{accessKeyId:this.accessKey,secretAccessKey:this.secretKey,endpoint:this.endpoint,bucket:this.bucket,region:this.region}}}import{container as F,EContainerScope as P}from"@ooneex/container";var v={storage:(t=P.Singleton)=>{return(e)=>{F.add(e,t)}}};import{existsSync as l,mkdirSync as d}from"fs";import{mkdir as m,readdir as h,rmdir as b,stat as w}from"fs/promises";import{dirname as g,join as f}from"path";class B extends u{bucket;storagePath;constructor(t){super();let e=t?.storagePath||Bun.env.FILESYSTEM_STORAGE_PATH;if(!e)throw new n("Base path is required. Please provide a base path either through the constructor options or set the FILESYSTEM_STORAGE_PATH environment variable.");this.storagePath=e;try{if(!l(e))d(e,{recursive:!0})}catch(r){throw new n(`Failed to create base storage directory at ${e}: ${r instanceof Error?r.message:String(r)}`)}}getOptions(){return{accessKeyId:"filesystem",secretAccessKey:"filesystem",endpoint:this.storagePath,bucket:this.bucket,region:"local"}}getBucketPath(){if(!this.bucket)throw new n("Bucket name is required. Please call setBucket() first.");return f(this.storagePath,this.bucket)}getFilePath(t){return f(this.getBucketPath(),t)}setBucket(t){this.bucket=t;let e=this.getBucketPath();try{if(!l(e))d(e,{recursive:!0})}catch(r){throw new n(`Failed to create bucket directory at ${e}: ${r instanceof Error?r.message:String(r)}`)}return this}async list(){let t=this.getBucketPath();if(!l(t))return[];try{return await this.listFilesRecursive(t,t)}catch(e){throw new n(`Failed to list files in bucket: ${e instanceof Error?e.message:String(e)}`)}}async listFilesRecursive(t,e){let r=[],s=await h(t);for(let i of s){let a=f(t,i);if((await w(a)).isDirectory()){let c=await this.listFilesRecursive(a,e);r.push(...c)}else{let c=a.substring(e.length+1);r.push(c)}}return r}async clearBucket(){let t=this.getBucketPath();if(!l(t))return this;try{await this.removeDirectoryRecursive(t),await m(t,{recursive:!0})}catch(e){throw new n(`Failed to clear bucket: ${e instanceof Error?e.message:String(e)}`)}return this}async removeDirectoryRecursive(t){let e=await h(t);for(let r of e){let s=f(t,r);if((await w(s)).isDirectory())await this.removeDirectoryRecursive(s),await b(s);else await Bun.file(s).delete()}}async exists(t){let e=this.getFilePath(t);return await Bun.file(e).exists()}async delete(t){let e=this.getFilePath(t),r=Bun.file(e);if(!await r.exists())return;try{await r.delete();let s=g(e),i=this.getBucketPath();while(s!==i&&s!==this.storagePath)try{if((await h(s)).length===0)await b(s),s=g(s);else break}catch{break}}catch(s){throw new n(`Failed to delete file ${t}: ${s instanceof Error?s.message:String(s)}`)}}async putFile(t,e){let r=Bun.file(e);return await this.put(t,r)}async put(t,e){let r=this.getFilePath(t),s=g(r);try{if(!l(s))await m(s,{recursive:!0})}catch(i){throw new n(`Failed to create directory ${s}: ${i instanceof Error?i.message:String(i)}`)}try{let i;if(typeof e==="string")i=await Bun.write(r,e);else if(e instanceof ArrayBuffer)i=await Bun.write(r,e);else if(e instanceof SharedArrayBuffer){let a=new ArrayBuffer(e.byteLength);new Uint8Array(a).set(new Uint8Array(e)),i=await Bun.write(r,a)}else if(e instanceof Request){let a=await e.arrayBuffer();i=await Bun.write(r,a)}else if(e instanceof Response){let a=await e.arrayBuffer();i=await Bun.write(r,a)}else if(e instanceof Blob)i=await Bun.write(r,e);else{let a=await e.arrayBuffer();i=await Bun.write(r,a)}return i}catch(i){throw new n(`Failed to write file ${t}: ${i instanceof Error?i.message:String(i)}`)}}async getAsJson(t){let e=this.getFilePath(t),r=Bun.file(e);if(!await r.exists())throw new n(`File ${t} does not exist`);try{return await r.json()}catch(s){throw new n(`Failed to read file ${t} as JSON: ${s instanceof Error?s.message:String(s)}`)}}async getAsArrayBuffer(t){let e=this.getFilePath(t),r=Bun.file(e);if(!await r.exists())throw new n(`File ${t} does not exist`);try{return await r.arrayBuffer()}catch(s){throw new n(`Failed to read file ${t} as ArrayBuffer: ${s instanceof Error?s.message:String(s)}`)}}getAsStream(t){let e=this.getFilePath(t);if(!l(e))throw new n(`File ${t} does not exist`);let r=Bun.file(e);try{return r.stream()}catch(s){throw new n(`Failed to read file ${t} as stream: ${s instanceof Error?s.message:String(s)}`)}}}export{v as decorator,n as StorageException,B as FilesystemStorage,p as CloudflareStorage,y as BunnyStorage,u as AbstractStorage};
|
|
3
3
|
|
|
4
|
-
//# debugId=
|
|
4
|
+
//# debugId=5DA9047CB989080064756E2164756E21
|
package/dist/index.js.map
CHANGED
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
|
-
"sources": ["src/AbstractStorage.ts", "src/StorageException.ts", "src/
|
|
3
|
+
"sources": ["src/AbstractStorage.ts", "src/StorageException.ts", "src/BunnyStorage.ts", "src/CloudflareStorage.ts", "src/decorators.ts", "src/FilesystemStorage.ts"],
|
|
4
4
|
"sourcesContent": [
|
|
5
5
|
"import type { BunFile, S3File, S3Options } from \"bun\";\nimport type { IStorage } from \"./types\";\n\nexport abstract class AbstractStorage implements IStorage {\n protected client: Bun.S3Client | null = null;\n public abstract getOptions(): S3Options;\n protected abstract bucket: string;\n\n public setBucket(name: string): this {\n this.bucket = name;\n this.client = new Bun.S3Client(this.getOptions());\n\n return this;\n }\n\n public async list(): Promise<string[]> {\n const client = this.getClient();\n\n return (await client.list()).contents?.map((content) => content.key) || [];\n }\n\n public async clearBucket(): Promise<this> {\n const client = this.getClient();\n const keys = await this.list();\n\n for (const key of keys) {\n await client.delete(key);\n }\n\n return this;\n }\n\n public async exists(key: string): Promise<boolean> {\n const client = this.getClient();\n\n return await client.exists(key);\n }\n\n public async delete(key: string): Promise<void> {\n const client = this.getClient();\n\n await client.delete(key);\n }\n\n public async putFile(key: string, localPath: string): Promise<number> {\n const file = Bun.file(localPath);\n\n return await this.put(key, file);\n }\n\n public async put(\n key: string,\n content: string | ArrayBuffer | SharedArrayBuffer | Request | Response | BunFile | S3File | Blob,\n ): Promise<number> {\n const s3file: S3File = this.getS3File(key);\n\n return await s3file.write(content);\n }\n\n public async getAsJson<T>(key: string): Promise<T> {\n const s3file: S3File = this.getS3File(key);\n\n return await s3file.json();\n }\n\n public async getAsArrayBuffer(key: string): Promise<ArrayBuffer> {\n const s3file: S3File = this.getS3File(key);\n\n return await s3file.arrayBuffer();\n }\n\n public getAsStream(key: string): ReadableStream {\n const s3file: S3File = this.getS3File(key);\n\n return s3file.stream();\n }\n\n protected getClient(): Bun.S3Client {\n if (!this.client) {\n this.client = new Bun.S3Client(this.getOptions());\n }\n\n return this.client;\n }\n\n protected getS3File(path: string): S3File {\n const client = this.getClient();\n\n return client.file(path);\n }\n}\n",
|
|
6
6
|
"import { Exception } from \"@ooneex/exception\";\nimport { HttpStatus } from \"@ooneex/http-status\";\n\nexport class StorageException extends Exception {\n constructor(message: string, data: Record<string, unknown> = {}) {\n super(message, {\n status: HttpStatus.Code.InternalServerError,\n data,\n });\n this.name = \"StorageException\";\n }\n}\n",
|
|
7
|
-
"import type {
|
|
7
|
+
"import type { BunFile, S3File } from \"bun\";\nimport { StorageException } from \"./StorageException\";\nimport type { IStorage } from \"./types\";\n\ntype BunnyRegion = \"de\" | \"uk\" | \"ny\" | \"la\" | \"sg\" | \"se\" | \"br\" | \"jh\" | \"syd\";\n\ninterface BunnyFileInfo {\n Guid: string;\n StorageZoneName: string;\n Path: string;\n ObjectName: string;\n Length: number;\n LastChanged: string;\n ServerId: number;\n ArrayNumber: number;\n IsDirectory: boolean;\n UserId: string;\n ContentType: string;\n DateCreated: string;\n StorageZoneId: number;\n Checksum: string | null;\n ReplicatedZones: string | null;\n}\n\nexport class BunnyStorage implements IStorage {\n private bucket = \"\";\n private readonly accessKey: string;\n private readonly storageZone: string;\n private readonly region: BunnyRegion;\n\n constructor(options?: {\n accessKey?: string;\n storageZone?: string;\n region?: BunnyRegion;\n }) {\n const accessKey = options?.accessKey ?? Bun.env.STORAGE_BUNNY_ACCESS_KEY;\n const storageZone = options?.storageZone ?? Bun.env.STORAGE_BUNNY_STORAGE_ZONE;\n const region = options?.region ?? (Bun.env.STORAGE_BUNNY_REGION as BunnyRegion | undefined);\n\n if (!accessKey) {\n throw new StorageException(\n \"Bunny access key is required. Please provide an access key either through the constructor options or set the STORAGE_BUNNY_ACCESS_KEY environment variable.\",\n );\n }\n if (!storageZone) {\n throw new StorageException(\n \"Bunny storage zone is required. Please provide a storage zone either through the constructor options or set the STORAGE_BUNNY_STORAGE_ZONE environment variable.\",\n );\n }\n\n this.accessKey = accessKey;\n this.storageZone = storageZone;\n this.region = region ?? \"de\";\n }\n\n public setBucket(name: string): this {\n this.bucket = name;\n\n return this;\n }\n\n public async list(): Promise<string[]> {\n const path = this.bucket ? `${this.bucket}/` : \"\";\n const url = `${this.getBaseUrl()}/${this.storageZone}/${path}`;\n\n const response = await fetch(url, {\n method: \"GET\",\n headers: {\n AccessKey: this.accessKey,\n accept: \"application/json\",\n },\n });\n\n if (!response.ok) {\n throw new StorageException(`Failed to list files: ${response.status} ${response.statusText}`, {\n status: response.status,\n path,\n });\n }\n\n const files: BunnyFileInfo[] = await response.json();\n\n return files.filter((file) => !file.IsDirectory).map((file) => file.ObjectName);\n }\n\n public async clearBucket(): Promise<this> {\n const keys = await this.list();\n\n for (const key of keys) {\n await this.delete(key);\n }\n\n return this;\n }\n\n public async exists(key: string): Promise<boolean> {\n const url = this.buildFileUrl(key);\n\n const response = await fetch(url, {\n method: \"GET\",\n headers: {\n AccessKey: this.accessKey,\n },\n });\n\n return response.ok;\n }\n\n public async delete(key: string): Promise<void> {\n const url = this.buildFileUrl(key);\n\n const response = await fetch(url, {\n method: \"DELETE\",\n headers: {\n AccessKey: this.accessKey,\n },\n });\n\n if (!response.ok && response.status !== 404) {\n throw new StorageException(`Failed to delete file: ${response.status} ${response.statusText}`, {\n status: response.status,\n key,\n });\n }\n }\n\n public async putFile(key: string, localPath: string): Promise<number> {\n const file = Bun.file(localPath);\n\n return await this.put(key, file);\n }\n\n public async put(\n key: string,\n content: string | ArrayBufferView | ArrayBuffer | SharedArrayBuffer | Request | Response | BunFile | S3File | Blob,\n ): Promise<number> {\n const url = this.buildFileUrl(key);\n\n let body: BodyInit;\n let contentLength: number;\n\n if (typeof content === \"string\") {\n body = content;\n contentLength = new TextEncoder().encode(content).length;\n } else if (content instanceof ArrayBuffer) {\n body = new Blob([content]);\n contentLength = content.byteLength;\n } else if (content instanceof SharedArrayBuffer) {\n const uint8Array = new Uint8Array(content);\n const copiedArray = new Uint8Array(uint8Array.length);\n copiedArray.set(uint8Array);\n body = new Blob([copiedArray]);\n contentLength = content.byteLength;\n } else if (ArrayBuffer.isView(content)) {\n const arrayBuffer = content.buffer.slice(\n content.byteOffset,\n content.byteOffset + content.byteLength,\n ) as ArrayBuffer;\n body = new Blob([arrayBuffer]);\n contentLength = content.byteLength;\n } else if (content instanceof Blob) {\n body = content;\n contentLength = content.size;\n } else if (content instanceof Request || content instanceof Response) {\n const arrayBuffer = await content.arrayBuffer();\n body = new Blob([arrayBuffer]);\n contentLength = arrayBuffer.byteLength;\n } else if (typeof content === \"object\" && content !== null && \"arrayBuffer\" in content) {\n const fileContent = content as BunFile | S3File;\n const arrayBuffer = await fileContent.arrayBuffer();\n body = new Blob([arrayBuffer]);\n contentLength = arrayBuffer.byteLength;\n } else {\n throw new StorageException(\"Unsupported content type for upload\", { key });\n }\n\n const response = await fetch(url, {\n method: \"PUT\",\n headers: {\n AccessKey: this.accessKey,\n \"Content-Type\": \"application/octet-stream\",\n },\n body,\n });\n\n if (!response.ok) {\n throw new StorageException(`Failed to upload file: ${response.status} ${response.statusText}`, {\n status: response.status,\n key,\n });\n }\n\n return contentLength;\n }\n\n public async getAsJson<T = unknown>(key: string): Promise<T> {\n const url = this.buildFileUrl(key);\n\n const response = await fetch(url, {\n method: \"GET\",\n headers: {\n AccessKey: this.accessKey,\n },\n });\n\n if (!response.ok) {\n throw new StorageException(`Failed to get file as JSON: ${response.status} ${response.statusText}`, {\n status: response.status,\n key,\n });\n }\n\n return await response.json();\n }\n\n public async getAsArrayBuffer(key: string): Promise<ArrayBuffer> {\n const url = this.buildFileUrl(key);\n\n const response = await fetch(url, {\n method: \"GET\",\n headers: {\n AccessKey: this.accessKey,\n },\n });\n\n if (!response.ok) {\n throw new StorageException(`Failed to get file as ArrayBuffer: ${response.status} ${response.statusText}`, {\n status: response.status,\n key,\n });\n }\n\n return await response.arrayBuffer();\n }\n\n public getAsStream(key: string): ReadableStream {\n const url = this.buildFileUrl(key);\n\n const stream = new ReadableStream({\n start: async (controller) => {\n const response = await fetch(url, {\n method: \"GET\",\n headers: {\n AccessKey: this.accessKey,\n },\n });\n\n if (!response.ok) {\n controller.error(\n new StorageException(`Failed to get file as stream: ${response.status} ${response.statusText}`, {\n status: response.status,\n key,\n }),\n );\n return;\n }\n\n if (!response.body) {\n controller.error(new StorageException(\"Response body is null\", { key }));\n return;\n }\n\n const reader = response.body.getReader();\n\n const pump = async (): Promise<void> => {\n const { done, value } = await reader.read();\n\n if (done) {\n controller.close();\n return;\n }\n\n controller.enqueue(value);\n await pump();\n };\n\n await pump();\n },\n });\n\n return stream;\n }\n\n private getBaseUrl(): string {\n const regionEndpoints: Record<BunnyRegion, string> = {\n de: \"storage.bunnycdn.com\",\n uk: \"uk.storage.bunnycdn.com\",\n ny: \"ny.storage.bunnycdn.com\",\n la: \"la.storage.bunnycdn.com\",\n sg: \"sg.storage.bunnycdn.com\",\n se: \"se.storage.bunnycdn.com\",\n br: \"br.storage.bunnycdn.com\",\n jh: \"jh.storage.bunnycdn.com\",\n syd: \"syd.storage.bunnycdn.com\",\n };\n\n return `https://${regionEndpoints[this.region]}`;\n }\n\n private buildFileUrl(key: string): string {\n const path = this.bucket ? `${this.bucket}/${key}` : key;\n\n return `${this.getBaseUrl()}/${this.storageZone}/${path}`;\n }\n}\n",
|
|
8
|
+
"import type { S3Options } from \"bun\";\nimport { AbstractStorage } from \"./AbstractStorage\";\nimport { StorageException } from \"./StorageException\";\n\nexport class CloudflareStorage extends AbstractStorage {\n protected bucket: string;\n private readonly accessKey: string;\n private readonly secretKey: string;\n private readonly endpoint: string;\n private readonly region: string;\n\n constructor(options?: {\n accessKey?: string;\n secretKey?: string;\n endpoint?: string;\n region?: \"EEUR\" | \"WEUR\" | \"APAC\" | \"NAM\";\n }) {\n super();\n\n const accessKey = options?.accessKey || Bun.env.STORAGE_CLOUDFLARE_ACCESS_KEY;\n const secretKey = options?.secretKey || Bun.env.STORAGE_CLOUDFLARE_SECRET_KEY;\n const endpoint = options?.endpoint || Bun.env.STORAGE_CLOUDFLARE_ENDPOINT;\n\n if (!accessKey) {\n throw new StorageException(\n \"Cloudflare access key is required. Please provide an access key either through the constructor options or set the STORAGE_CLOUDFLARE_ACCESS_KEY environment variable.\",\n );\n }\n if (!secretKey) {\n throw new StorageException(\n \"Cloudflare secret key is required. Please provide a secret key either through the constructor options or set the STORAGE_CLOUDFLARE_SECRET_KEY environment variable.\",\n );\n }\n if (!endpoint) {\n throw new StorageException(\n \"Cloudflare endpoint is required. Please provide an endpoint either through the constructor options or set the STORAGE_CLOUDFLARE_ENDPOINT environment variable.\",\n );\n }\n\n this.accessKey = accessKey;\n this.secretKey = secretKey;\n this.endpoint = endpoint;\n this.region = options?.region || Bun.env.STORAGE_CLOUDFLARE_REGION || \"EEUR\";\n }\n\n public getOptions(): S3Options {\n return {\n accessKeyId: this.accessKey,\n secretAccessKey: this.secretKey,\n endpoint: this.endpoint,\n bucket: this.bucket,\n region: this.region,\n };\n }\n}\n",
|
|
9
|
+
"import { container, EContainerScope } from \"@ooneex/container\";\nimport type { StorageClassType } from \"./types\";\n\nexport const decorator = {\n storage: (scope: EContainerScope = EContainerScope.Singleton) => {\n return (target: StorageClassType): void => {\n container.add(target, scope);\n };\n },\n};\n",
|
|
8
10
|
"import { existsSync, mkdirSync } from \"node:fs\";\nimport { mkdir, readdir, rmdir, stat } from \"node:fs/promises\";\nimport { dirname, join } from \"node:path\";\nimport type { BunFile, S3File, S3Options } from \"bun\";\nimport { AbstractStorage } from \"./AbstractStorage\";\nimport { StorageException } from \"./StorageException\";\n\nexport class FilesystemStorage extends AbstractStorage {\n protected bucket: string;\n private readonly storagePath: string;\n\n constructor(options?: {\n storagePath?: string;\n }) {\n super();\n\n const basePath = options?.storagePath || Bun.env.FILESYSTEM_STORAGE_PATH;\n\n if (!basePath) {\n throw new StorageException(\n \"Base path is required. Please provide a base path either through the constructor options or set the FILESYSTEM_STORAGE_PATH environment variable.\",\n );\n }\n\n this.storagePath = basePath;\n\n try {\n if (!existsSync(basePath)) {\n mkdirSync(basePath, { recursive: true });\n }\n } catch (error) {\n throw new StorageException(\n `Failed to create base storage directory at ${basePath}: ${error instanceof Error ? error.message : String(error)}`,\n );\n }\n }\n\n public getOptions(): S3Options {\n return {\n accessKeyId: \"filesystem\",\n secretAccessKey: \"filesystem\",\n endpoint: this.storagePath,\n bucket: this.bucket,\n region: \"local\",\n };\n }\n\n private getBucketPath(): string {\n if (!this.bucket) {\n throw new StorageException(\"Bucket name is required. Please call setBucket() first.\");\n }\n return join(this.storagePath, this.bucket);\n }\n\n private getFilePath(key: string): string {\n return join(this.getBucketPath(), key);\n }\n\n public override setBucket(name: string): this {\n this.bucket = name;\n\n const bucketPath = this.getBucketPath();\n try {\n if (!existsSync(bucketPath)) {\n mkdirSync(bucketPath, { recursive: true });\n }\n } catch (error) {\n throw new StorageException(\n `Failed to create bucket directory at ${bucketPath}: ${error instanceof Error ? error.message : String(error)}`,\n );\n }\n\n return this;\n }\n\n public override async list(): Promise<string[]> {\n const bucketPath = this.getBucketPath();\n\n if (!existsSync(bucketPath)) {\n return [];\n }\n\n try {\n const files = await this.listFilesRecursive(bucketPath, bucketPath);\n return files;\n } catch (error) {\n throw new StorageException(\n `Failed to list files in bucket: ${error instanceof Error ? error.message : String(error)}`,\n );\n }\n }\n\n private async listFilesRecursive(dir: string, baseDir: string): Promise<string[]> {\n const files: string[] = [];\n const entries = await readdir(dir);\n\n for (const entry of entries) {\n const fullPath = join(dir, entry);\n const stats = await stat(fullPath);\n\n if (stats.isDirectory()) {\n const subFiles = await this.listFilesRecursive(fullPath, baseDir);\n files.push(...subFiles);\n } else {\n const relativePath = fullPath.substring(baseDir.length + 1);\n files.push(relativePath);\n }\n }\n\n return files;\n }\n\n public override async clearBucket(): Promise<this> {\n const bucketPath = this.getBucketPath();\n\n if (!existsSync(bucketPath)) {\n return this;\n }\n\n try {\n await this.removeDirectoryRecursive(bucketPath);\n await mkdir(bucketPath, { recursive: true });\n } catch (error) {\n throw new StorageException(`Failed to clear bucket: ${error instanceof Error ? error.message : String(error)}`);\n }\n\n return this;\n }\n\n private async removeDirectoryRecursive(dir: string): Promise<void> {\n const entries = await readdir(dir);\n\n for (const entry of entries) {\n const fullPath = join(dir, entry);\n const stats = await stat(fullPath);\n\n if (stats.isDirectory()) {\n await this.removeDirectoryRecursive(fullPath);\n await rmdir(fullPath);\n } else {\n const file = Bun.file(fullPath);\n await file.delete();\n }\n }\n }\n\n public override async exists(key: string): Promise<boolean> {\n const filePath = this.getFilePath(key);\n const file = Bun.file(filePath);\n return await file.exists();\n }\n\n public override async delete(key: string): Promise<void> {\n const filePath = this.getFilePath(key);\n const file = Bun.file(filePath);\n\n if (!(await file.exists())) {\n return;\n }\n\n try {\n await file.delete();\n\n let parentDir = dirname(filePath);\n const bucketPath = this.getBucketPath();\n\n while (parentDir !== bucketPath && parentDir !== this.storagePath) {\n try {\n const entries = await readdir(parentDir);\n if (entries.length === 0) {\n await rmdir(parentDir);\n parentDir = dirname(parentDir);\n } else {\n break;\n }\n } catch {\n break;\n }\n }\n } catch (error) {\n throw new StorageException(\n `Failed to delete file ${key}: ${error instanceof Error ? error.message : String(error)}`,\n );\n }\n }\n\n public override async putFile(key: string, localPath: string): Promise<number> {\n const file = Bun.file(localPath);\n return await this.put(key, file);\n }\n\n public override async put(\n key: string,\n content: string | ArrayBuffer | SharedArrayBuffer | Request | Response | BunFile | S3File | Blob,\n ): Promise<number> {\n const filePath = this.getFilePath(key);\n const dir = dirname(filePath);\n\n try {\n if (!existsSync(dir)) {\n await mkdir(dir, { recursive: true });\n }\n } catch (error) {\n throw new StorageException(\n `Failed to create directory ${dir}: ${error instanceof Error ? error.message : String(error)}`,\n );\n }\n\n try {\n let bytesWritten: number;\n\n if (typeof content === \"string\") {\n bytesWritten = await Bun.write(filePath, content);\n } else if (content instanceof ArrayBuffer) {\n bytesWritten = await Bun.write(filePath, content);\n } else if (content instanceof SharedArrayBuffer) {\n const arrayBuffer = new ArrayBuffer(content.byteLength);\n new Uint8Array(arrayBuffer).set(new Uint8Array(content));\n bytesWritten = await Bun.write(filePath, arrayBuffer);\n } else if (content instanceof Request) {\n const arrayBuffer = await content.arrayBuffer();\n bytesWritten = await Bun.write(filePath, arrayBuffer);\n } else if (content instanceof Response) {\n const arrayBuffer = await content.arrayBuffer();\n bytesWritten = await Bun.write(filePath, arrayBuffer);\n } else if (content instanceof Blob) {\n bytesWritten = await Bun.write(filePath, content);\n } else {\n const arrayBuffer = await (content as BunFile | S3File).arrayBuffer();\n bytesWritten = await Bun.write(filePath, arrayBuffer);\n }\n\n return bytesWritten;\n } catch (error) {\n throw new StorageException(\n `Failed to write file ${key}: ${error instanceof Error ? error.message : String(error)}`,\n );\n }\n }\n\n public override async getAsJson<T>(key: string): Promise<T> {\n const filePath = this.getFilePath(key);\n const file = Bun.file(filePath);\n\n if (!(await file.exists())) {\n throw new StorageException(`File ${key} does not exist`);\n }\n\n try {\n return await file.json();\n } catch (error) {\n throw new StorageException(\n `Failed to read file ${key} as JSON: ${error instanceof Error ? error.message : String(error)}`,\n );\n }\n }\n\n public override async getAsArrayBuffer(key: string): Promise<ArrayBuffer> {\n const filePath = this.getFilePath(key);\n const file = Bun.file(filePath);\n\n if (!(await file.exists())) {\n throw new StorageException(`File ${key} does not exist`);\n }\n\n try {\n return await file.arrayBuffer();\n } catch (error) {\n throw new StorageException(\n `Failed to read file ${key} as ArrayBuffer: ${error instanceof Error ? error.message : String(error)}`,\n );\n }\n }\n\n public override getAsStream(key: string): ReadableStream {\n const filePath = this.getFilePath(key);\n\n if (!existsSync(filePath)) {\n throw new StorageException(`File ${key} does not exist`);\n }\n\n const file = Bun.file(filePath);\n\n try {\n return file.stream();\n } catch (error) {\n throw new StorageException(\n `Failed to read file ${key} as stream: ${error instanceof Error ? error.message : String(error)}`,\n );\n }\n }\n}\n"
|
|
9
11
|
],
|
|
10
|
-
"mappings": ";AAGO,MAAe,CAAoC,CAC9C,OAA8B,KAIjC,SAAS,CAAC,EAAoB,CAInC,OAHA,KAAK,OAAS,EACd,KAAK,OAAS,IAAI,IAAI,SAAS,KAAK,WAAW,CAAC,EAEzC,UAGI,KAAI,EAAsB,CAGrC,OAAQ,MAFO,KAAK,UAAU,EAET,KAAK,GAAG,UAAU,IAAI,CAAC,IAAY,EAAQ,GAAG,GAAK,CAAC,OAG9D,YAAW,EAAkB,CACxC,IAAM,EAAS,KAAK,UAAU,EACxB,EAAO,MAAM,KAAK,KAAK,EAE7B,QAAW,KAAO,EAChB,MAAM,EAAO,OAAO,CAAG,EAGzB,OAAO,UAGI,OAAM,CAAC,EAA+B,CAGjD,OAAO,MAFQ,KAAK,UAAU,EAEV,OAAO,CAAG,OAGnB,OAAM,CAAC,EAA4B,CAG9C,MAFe,KAAK,UAAU,EAEjB,OAAO,CAAG,OAGZ,QAAO,CAAC,EAAa,EAAoC,CACpE,IAAM,EAAO,IAAI,KAAK,CAAS,EAE/B,OAAO,MAAM,KAAK,IAAI,EAAK,CAAI,OAGpB,IAAG,CACd,EACA,EACiB,CAGjB,OAAO,MAFgB,KAAK,UAAU,CAAG,EAErB,MAAM,CAAO,OAGtB,UAAY,CAAC,EAAyB,CAGjD,OAAO,MAFgB,KAAK,UAAU,CAAG,EAErB,KAAK,OAGd,iBAAgB,CAAC,EAAmC,CAG/D,OAAO,MAFgB,KAAK,UAAU,CAAG,EAErB,YAAY,EAG3B,WAAW,CAAC,EAA6B,CAG9C,OAFuB,KAAK,UAAU,CAAG,EAE3B,OAAO,EAGb,SAAS,EAAiB,CAClC,GAAI,CAAC,KAAK,OACR,KAAK,OAAS,IAAI,IAAI,SAAS,KAAK,WAAW,CAAC,EAGlD,OAAO,KAAK,OAGJ,SAAS,CAAC,EAAsB,CAGxC,OAFe,KAAK,UAAU,EAEhB,KAAK,CAAI,EAE3B,CC1FA,oBAAS,0BACT,qBAAS,4BAEF,MAAM,UAAyB,CAAU,CAC9C,WAAW,CAAC,EAAiB,EAAgC,CAAC,EAAG,CAC/D,MAAM,EAAS,CACb,OAAQ,EAAW,KAAK,oBACxB,MACF,CAAC,EACD,KAAK,KAAO,mBAEhB,
|
|
11
|
-
"debugId": "
|
|
12
|
+
"mappings": "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",
|
|
13
|
+
"debugId": "5DA9047CB989080064756E2164756E21",
|
|
12
14
|
"names": []
|
|
13
15
|
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ooneex/storage",
|
|
3
|
-
"description": "",
|
|
4
|
-
"version": "0.0.
|
|
3
|
+
"description": "File and object storage abstraction layer with support for local filesystem and cloud storage providers",
|
|
4
|
+
"version": "0.0.4",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"files": [
|
|
7
7
|
"dist",
|
|
@@ -25,12 +25,21 @@
|
|
|
25
25
|
"test": "bun test tests",
|
|
26
26
|
"build": "bunup",
|
|
27
27
|
"lint": "tsgo --noEmit && bunx biome lint",
|
|
28
|
-
"publish
|
|
29
|
-
"publish:pack": "bun pm pack --destination ./dist",
|
|
30
|
-
"publish:dry": "bun publish --dry-run"
|
|
28
|
+
"publish": "bun publish --access public || true"
|
|
31
29
|
},
|
|
32
30
|
"dependencies": {
|
|
31
|
+
"@ooneex/container": "0.0.2",
|
|
33
32
|
"@ooneex/exception": "0.0.1",
|
|
34
33
|
"@ooneex/http-status": "0.0.1"
|
|
35
|
-
}
|
|
34
|
+
},
|
|
35
|
+
"keywords": [
|
|
36
|
+
"blob",
|
|
37
|
+
"bun",
|
|
38
|
+
"cloud",
|
|
39
|
+
"file-storage",
|
|
40
|
+
"ooneex",
|
|
41
|
+
"s3",
|
|
42
|
+
"storage",
|
|
43
|
+
"typescript"
|
|
44
|
+
]
|
|
36
45
|
}
|
|
Binary file
|