@ooneex/storage 0.0.1 → 0.0.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,34 +1,30 @@
1
1
  # @ooneex/storage
2
2
 
3
- A comprehensive TypeScript/JavaScript storage library providing unified interfaces for file storage operations. This package supports both local filesystem and Cloudflare R2 storage with a consistent API, making it easy to switch between storage backends or use multiple storage providers in your applications.
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
  ![Bun](https://img.shields.io/badge/Bun-Compatible-orange?style=flat-square&logo=bun)
6
+ ![Deno](https://img.shields.io/badge/Deno-Compatible-blue?style=flat-square&logo=deno)
7
+ ![Node.js](https://img.shields.io/badge/Node.js-Compatible-green?style=flat-square&logo=node.js)
6
8
  ![TypeScript](https://img.shields.io/badge/TypeScript-Ready-blue?style=flat-square&logo=typescript)
7
9
  ![MIT License](https://img.shields.io/badge/License-MIT-yellow?style=flat-square)
8
10
 
9
11
  ## Features
10
12
 
11
- ✅ **Unified Storage Interface** - Single API for multiple storage providers
13
+ ✅ **Multiple Backends** - Support for filesystem, Cloudflare R2, and Bunny CDN
12
14
 
13
- ✅ **Local Filesystem Storage** - Fast local file operations with automatic directory management
15
+ ✅ **Unified Interface** - Consistent API across all storage providers
14
16
 
15
- ✅ **Cloudflare R2 Support** - Full integration with Cloudflare's S3-compatible object storage
17
+ ✅ **Bucket Management** - Organize files into buckets/directories
16
18
 
17
- ✅ **Type-Safe** - Full TypeScript support with proper type definitions
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
- ✅ **File Operations** - Put, get, delete, exists, and list operations
21
+ ✅ **JSON Support** - Store and retrieve JSON data directly
24
22
 
25
- ✅ **Stream Support** - Efficient streaming for large files
23
+ ✅ **File Upload** - Upload files from local paths or binary data
26
24
 
27
- ✅ **Exception Handling** - Comprehensive error handling with custom exceptions
25
+ ✅ **Container Integration** - Works seamlessly with dependency injection
28
26
 
29
- ✅ **Environment Configuration** - Support for environment variables and constructor options
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
- storagePath: './my-storage'
59
+ basePath: './uploads'
67
60
  });
68
61
 
69
- // Or using environment variable FILESYSTEM_STORAGE_PATH
70
- const storage = new FilesystemStorage();
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 string content
85
- await storage.put('hello.txt', 'Hello, World!');
65
+ // Store a file
66
+ await storage.putFile('photo.jpg', '/path/to/local/photo.jpg');
86
67
 
87
- // Store JSON data
88
- await storage.put('config.json', JSON.stringify({ theme: 'dark', version: '1.0' }));
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('hello.txt'); // true
72
+ const exists = await storage.exists('photo.jpg');
73
+ console.log(exists); // true
95
74
 
96
- // Retrieve as string
97
- const content = await storage.getAsArrayBuffer('hello.txt');
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
- // Retrieve as JSON
101
- const config = await storage.getAsJson<{ theme: string; version: string }>('config.json');
78
+ // Get file as ArrayBuffer
79
+ const buffer = await storage.getAsArrayBuffer('photo.jpg');
102
80
 
103
- // Get as stream for large files
104
- const stream = storage.getAsStream('backup.zip');
81
+ // Get file as stream
82
+ const stream = storage.getAsStream('video.mp4');
105
83
 
106
- // List all files
107
- const files = await storage.list(); // ['hello.txt', 'config.json', 'backup.zip']
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('hello.txt');
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 { CloudflareStorageAdapter } from '@ooneex/storage';
122
-
123
- // Using constructor options
124
- const storage = new CloudflareStorageAdapter({
125
- accessKey: 'your-access-key',
126
- secretKey: 'your-secret-key',
127
- endpoint: 'https://your-account.r2.cloudflarestorage.com',
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
- // Or using environment variables:
132
- // STORAGE_CLOUDFLARE_ACCESS_KEY, STORAGE_CLOUDFLARE_SECRET_KEY, STORAGE_CLOUDFLARE_ENDPOINT, STORAGE_CLOUDFLARE_REGION
133
- const storage = new CloudflareStorageAdapter();
107
+ // Upload a file
108
+ await storage.put('uploads/image.png', imageBuffer);
134
109
 
135
- storage.setBucket('my-bucket');
110
+ // Get file content
111
+ const content = await storage.getAsArrayBuffer('uploads/image.png');
136
112
  ```
137
113
 
138
- #### Advanced Usage
114
+ ### Bunny CDN Storage
139
115
 
140
116
  ```typescript
141
- import { CloudflareStorageAdapter } from '@ooneex/storage';
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
- ### Working with Different Data Types
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
- ```typescript
163
- import { FilesystemStorage } from '@ooneex/storage';
125
+ storage.setBucket('assets');
164
126
 
165
- const storage = new FilesystemStorage({ storagePath: './data' })
166
- .setBucket('examples');
127
+ // Upload file
128
+ await storage.putFile('styles.css', './dist/styles.css');
167
129
 
168
- // String content
169
- await storage.put('text.txt', 'Plain text content');
130
+ // List files
131
+ const files = await storage.list();
132
+ ```
170
133
 
171
- // JSON objects
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
- // Binary data (ArrayBuffer)
176
- const binaryData = new ArrayBuffer(256);
177
- await storage.put('binary.dat', binaryData);
136
+ ```typescript
137
+ import { CloudflareStorage } from '@ooneex/storage';
178
138
 
179
- // Blobs
180
- const blob = new Blob(['CSV data,here'], { type: 'text/csv' });
181
- await storage.put('data.csv', blob);
139
+ // Automatically uses environment variables
140
+ const storage = new CloudflareStorage();
182
141
 
183
- // Files from disk
184
- await storage.putFile('document.pdf', './local/document.pdf');
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
- // Retrieve data in different formats
187
- const textBuffer = await storage.getAsArrayBuffer('text.txt');
188
- const text = new TextDecoder().decode(textBuffer);
149
+ ## API Reference
189
150
 
190
- const user = await storage.getAsJson<{ name: string; age: number; active: boolean }>('user.json');
151
+ ### Classes
191
152
 
192
- const csvStream = storage.getAsStream('data.csv');
193
- ```
153
+ #### `FilesystemStorage`
194
154
 
195
- ### Error Handling
155
+ Local filesystem storage implementation.
196
156
 
157
+ **Constructor:**
197
158
  ```typescript
198
- import { FilesystemStorage, StorageException } from '@ooneex/storage';
159
+ new FilesystemStorage(options?: { basePath?: string })
160
+ ```
199
161
 
200
- const storage = new FilesystemStorage({ storagePath: './storage' });
162
+ **Parameters:**
163
+ - `options.basePath` - Base directory for storage (default: current working directory)
201
164
 
202
- try {
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
- ### Using Abstract Interface
167
+ #### `CloudflareStorage`
213
168
 
214
- ```typescript
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
- // Use the same interface regardless of storage type
229
- const storage = createStorage('filesystem');
230
- await storage.setBucket('shared-bucket');
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
- ## API Reference
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
- ### `IStorage` Interface
184
+ #### `BunnyStorage`
237
185
 
238
- The main interface that all storage adapters implement.
186
+ Bunny CDN storage implementation.
239
187
 
188
+ **Constructor:**
240
189
  ```typescript
241
- interface IStorage {
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
- ### `FilesystemStorage` Class
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
- Local filesystem storage implementation.
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
- #### Constructor
211
+ **Returns:** The storage instance for chaining
260
212
 
213
+ **Example:**
261
214
  ```typescript
262
- constructor(options?: {
263
- storagePath?: string;
264
- })
215
+ storage.setBucket('images').setBucket('thumbnails');
265
216
  ```
266
217
 
267
- **Parameters:**
268
- - `options.storagePath` - Base path for storage (optional, can use `FILESYSTEM_STORAGE_PATH` env var)
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 storage = new FilesystemStorage({
273
- storagePath: '/var/app/storage'
274
- });
226
+ const files = await storage.list();
227
+ console.log(files); // ['file1.txt', 'file2.jpg', ...]
275
228
  ```
276
229
 
277
- #### Methods
230
+ ##### `exists(key: string): Promise<boolean>`
278
231
 
279
- ##### `setBucket(name: string): this`
280
- Sets the bucket (directory) name for operations.
232
+ Checks if a file exists.
281
233
 
282
234
  **Parameters:**
283
- - `name` - Bucket/directory name
235
+ - `key` - File key/path
284
236
 
285
- **Returns:** `this` for method chaining
237
+ **Returns:** `true` if file exists
286
238
 
287
239
  **Example:**
288
240
  ```typescript
289
- storage.setBucket('uploads');
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 within the bucket
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 bytesWritten = await storage.put('file.txt', 'Hello World');
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
- Stores a local file at the specified key.
262
+
263
+ Uploads a local file to storage.
308
264
 
309
265
  **Parameters:**
310
- - `key` - Destination key/path within the bucket
311
- - `localPath` - Local file system path
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('backup.zip', './local-backup.zip');
273
+ await storage.putFile('uploads/photo.jpg', '/tmp/photo.jpg');
318
274
  ```
319
275
 
320
- ##### `get*` Methods
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
- // Get as ArrayBuffer
327
- getAsArrayBuffer(key: string): Promise<ArrayBuffer>
278
+ Retrieves and parses a file as JSON.
328
279
 
329
- // Get as ReadableStream
330
- getAsStream(key: string): ReadableStream
331
- ```
280
+ **Parameters:**
281
+ - `key` - File key/path
332
282
 
333
- ##### `exists(key: string): Promise<boolean>`
334
- Checks if a file exists at the specified key.
283
+ **Returns:** Parsed JSON object
335
284
 
336
285
  **Example:**
337
286
  ```typescript
338
- const fileExists = await storage.exists('important.txt');
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
- ##### `delete(key: string): Promise<void>`
342
- Deletes the file at the specified key.
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
- await storage.delete('old-file.txt');
315
+ const stream = storage.getAsStream('large-file.zip');
316
+
317
+ // Pipe to response
318
+ return new Response(stream);
347
319
  ```
348
320
 
349
- ##### `list(): Promise<string[]>`
350
- Lists all files in the current bucket.
321
+ ##### `delete(key: string): Promise<void>`
322
+
323
+ Deletes a file.
351
324
 
352
- **Returns:** Array of file keys
325
+ **Parameters:**
326
+ - `key` - File key/path
353
327
 
354
328
  **Example:**
355
329
  ```typescript
356
- const files = await storage.list();
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:** `this` for method chaining
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
- ### `CloudflareStorageAdapter` Class
344
+ ### Types
371
345
 
372
- Cloudflare R2 storage implementation.
373
-
374
- #### Constructor
346
+ #### `IStorage`
375
347
 
376
348
  ```typescript
377
- constructor(options?: {
378
- accessKey?: string;
379
- secretKey?: string;
380
- endpoint?: string;
381
- region?: "EEUR" | "WEUR" | "APAC" | "NAM";
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
- **Parameters:**
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
- const storage = new CloudflareStorageAdapter({
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
- The `CloudflareStorageAdapter` inherits all methods from `AbstractStorage` and implements the same interface as `FilesystemStorage`.
402
-
403
- ### `StorageException` Class
369
+ ## Advanced Usage
404
370
 
405
- Custom exception class for storage-related errors.
371
+ ### Integration with Ooneex App
406
372
 
407
373
  ```typescript
408
- class StorageException extends Exception {
409
- constructor(message: string, data?: T)
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
- **Example:**
385
+ ### Using in Controllers
386
+
414
387
  ```typescript
415
- try {
416
- await storage.getAsJson('missing.json');
417
- } catch (error) {
418
- if (error instanceof StorageException) {
419
- console.error('Storage operation failed:', error.message);
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
- ### `AbstractStorage` Class
425
-
426
- Base class providing common functionality for storage adapters.
419
+ ### Container Integration with Decorators
427
420
 
428
421
  ```typescript
429
- abstract class AbstractStorage implements IStorage {
430
- protected abstract bucket: string;
431
- public abstract getOptions(): S3Options;
432
-
433
- // All IStorage methods implemented
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
- - `FILESYSTEM_STORAGE_PATH` - Base path for filesystem storage
442
-
443
- ### Cloudflare R2 Storage
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
- The library uses custom `StorageException` for all storage-related errors:
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 FilesystemStorage(); // No path provided
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
- // Handle storage-specific errors
462
- console.error('Storage setup failed:', error.message);
448
+ console.error('Storage Error:', error.message);
449
+ console.error('Status:', error.status);
463
450
  }
464
451
  }
465
452
  ```
466
453
 
467
- Common error scenarios:
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
- // .env file
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
- ### 3. Use Streams for Large Files
459
+ const storage = new FilesystemStorage();
460
+ storage.setBucket('videos');
509
461
 
510
- ```typescript
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
- // Avoid for large files - loads entire file into memory
518
- const buffer = await storage.getAsArrayBuffer('large-video.mp4');
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
- ### 4. Organize Files with Proper Keys
473
+ ### Organizing Files with Buckets
522
474
 
523
475
  ```typescript
524
- // Good - organized structure
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
- ### 5. Use Type Safety with JSON Operations
478
+ const storage = new CloudflareStorage();
536
479
 
537
- ```typescript
538
- interface UserProfile {
539
- id: number;
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
- // Type-safe JSON operations
549
- const profile = await storage.getAsJson<UserProfile>('user/profile.json');
550
- console.log(profile.preferences.theme); // TypeScript knows this exists
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 CloudflareStorageAdapter extends AbstractStorage {
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 { BunFile as BunFile3, S3File as S3File3, S3Options as S3Options3 } from "bun";
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 | BunFile3 | S3File3 | Blob): Promise<number>;
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, CloudflareStorageAdapter, AbstractStorage };
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=C81D99E08CC7CE6B64756E2164756E21
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/CloudflareStorageAdapter.ts", "src/FilesystemStorage.ts"],
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 { S3Options } from \"bun\";\nimport { AbstractStorage } from \"./AbstractStorage\";\nimport { StorageException } from \"./StorageException\";\n\nexport class CloudflareStorageAdapter 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",
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": "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",
11
- "debugId": "C81D99E08CC7CE6B64756E2164756E21",
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.1",
3
+ "description": "File and object storage abstraction layer with support for local filesystem and cloud storage providers",
4
+ "version": "0.0.5",
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:prod": "bun publish --tolerate-republish --access public",
29
- "publish:pack": "bun pm pack --destination ./dist",
30
- "publish:dry": "bun publish --dry-run"
28
+ "npm:publish": "bun publish --tolerate-republish --access public"
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