@gravito/nebula 3.0.1 → 4.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  > The Standard Storage Orbit for Galaxy Architecture.
4
4
 
5
- Provides an abstraction layer for file storage, with a built-in Local Disk provider.
5
+ Provides a unified file storage abstraction layer with multi-disk support and pluggable backends.
6
6
 
7
7
  ## 📦 Installation
8
8
 
@@ -10,38 +10,346 @@ Provides an abstraction layer for file storage, with a built-in Local Disk provi
10
10
  bun add @gravito/nebula
11
11
  ```
12
12
 
13
- ## 🚀 Usage
13
+ ## 🚀 Quick Start
14
+
15
+ ### Basic Usage
14
16
 
15
17
  ```typescript
16
- import { PlanetCore } from '@gravito/core';
17
- import orbitStorage from '@gravito/nebula';
18
+ import { PlanetCore } from '@gravito/core'
19
+ import orbitStorage from '@gravito/nebula'
18
20
 
19
- const core = new PlanetCore();
21
+ const core = new PlanetCore()
20
22
 
21
- // Initialize Storage Orbit (Local)
22
23
  const storage = orbitStorage(core, {
23
- local: {
24
- root: './uploads',
25
- baseUrl: '/uploads'
26
- },
27
- exposeAs: 'storage' // Access via c.get('storage')
28
- });
24
+ default: 'local',
25
+ disks: {
26
+ local: {
27
+ driver: 'local',
28
+ root: './uploads',
29
+ baseUrl: '/uploads'
30
+ }
31
+ }
32
+ })
29
33
 
30
34
  // Use in routes
31
35
  core.app.post('/upload', async (c) => {
32
- const body = await c.req.parseBody();
33
- const file = body['file'];
36
+ const body = await c.req.parseBody()
37
+ const file = body['file']
34
38
 
35
39
  if (file instanceof File) {
36
- await storage.put(file.name, file);
37
- return c.json({ url: storage.getUrl(file.name) });
40
+ const storage = c.get('storage')
41
+ await storage.put(file.name, file)
42
+ return c.json({ url: storage.getUrl(file.name) })
38
43
  }
39
- return c.text('No file uploaded', 400);
40
- });
44
+
45
+ return c.text('No file uploaded', 400)
46
+ })
47
+ ```
48
+
49
+ ---
50
+
51
+ ## 🔧 Multi-Disk Configuration
52
+
53
+ ```typescript
54
+ const storage = orbitStorage(core, {
55
+ default: 'local',
56
+ disks: {
57
+ // Local disk
58
+ local: {
59
+ driver: 'local',
60
+ root: './uploads',
61
+ baseUrl: '/uploads'
62
+ },
63
+
64
+ // Memory disk (for testing)
65
+ temp: {
66
+ driver: 'memory'
67
+ },
68
+
69
+ // Null disk (no-op)
70
+ null: {
71
+ driver: 'null'
72
+ },
73
+
74
+ // Custom disk (e.g., S3)
75
+ s3: {
76
+ driver: 'custom',
77
+ store: new S3Store({
78
+ bucket: 'my-bucket',
79
+ region: 'us-east-1'
80
+ })
81
+ }
82
+ }
83
+ })
84
+
85
+ // Use default disk
86
+ await storage.put('file.txt', 'content')
87
+
88
+ // Use specific disk
89
+ await storage.disk('s3').put('important.pdf', pdfData)
90
+ await storage.disk('temp').put('cache.json', jsonData)
91
+ ```
92
+
93
+ ---
94
+
95
+ ## 📖 API Reference
96
+
97
+ ### StorageManager
98
+
99
+ The main storage manager returned by `orbitStorage()` or `c.get('storage')`.
100
+
101
+ #### Methods
102
+
103
+ ##### `disk(name?: string): StorageRepository`
104
+
105
+ Get a specific disk repository. If `name` is not provided, returns the default disk.
106
+
107
+ ```typescript
108
+ const local = storage.disk('local')
109
+ const s3 = storage.disk('s3')
110
+ ```
111
+
112
+ ##### `put(key: string, data: Blob | Buffer | string): Promise<void>`
113
+
114
+ Store a file (using default disk).
115
+
116
+ ```typescript
117
+ await storage.put('file.txt', 'Hello World')
118
+ await storage.put('image.png', imageBlob)
119
+ ```
120
+
121
+ ##### `get(key: string): Promise<Blob | null>`
122
+
123
+ Retrieve a file (using default disk).
124
+
125
+ ```typescript
126
+ const data = await storage.get('file.txt')
127
+ if (data) {
128
+ console.log(await data.text())
129
+ }
130
+ ```
131
+
132
+ ##### `delete(key: string): Promise<boolean>`
133
+
134
+ Delete a file (using default disk). Returns `true` if deleted, `false` if file didn't exist.
135
+
136
+ ```typescript
137
+ const deleted = await storage.delete('old-file.txt')
138
+ ```
139
+
140
+ ##### `exists(key: string): Promise<boolean>` 🆕
141
+
142
+ Check if a file exists (using default disk).
143
+
144
+ ```typescript
145
+ if (await storage.exists('config.json')) {
146
+ // File exists
147
+ }
148
+ ```
149
+
150
+ ##### `copy(from: string, to: string): Promise<void>` 🆕
151
+
152
+ Copy a file (using default disk).
153
+
154
+ ```typescript
155
+ await storage.copy('original.txt', 'backup.txt')
156
+ ```
157
+
158
+ ##### `move(from: string, to: string): Promise<void>` 🆕
159
+
160
+ Move/rename a file (using default disk).
161
+
162
+ ```typescript
163
+ await storage.move('temp.txt', 'final.txt')
164
+ ```
165
+
166
+ ##### `getMetadata(key: string): Promise<StorageMetadata | null>` 🆕
167
+
168
+ Get file metadata (using default disk).
169
+
170
+ ```typescript
171
+ const meta = await storage.getMetadata('file.pdf')
172
+ if (meta) {
173
+ console.log(meta.size, meta.mimeType, meta.lastModified)
174
+ }
175
+ ```
176
+
177
+ ##### `getUrl(key: string): string`
178
+
179
+ Get the public URL for a file (using default disk).
180
+
181
+ ```typescript
182
+ const url = storage.getUrl('avatar.jpg')
183
+ // "/uploads/avatar.jpg"
184
+ ```
185
+
186
+ ##### `getSignedUrl(key: string, expiresIn: number): Promise<string>` 🆕
187
+
188
+ Get a signed URL with expiration (if supported by the driver).
189
+
190
+ ```typescript
191
+ // Generate a URL that expires in 1 hour
192
+ const signedUrl = await storage.disk('s3').getSignedUrl('private.pdf', 3600)
41
193
  ```
42
194
 
195
+ ##### `list(prefix?: string): AsyncIterable<StorageItem>` 🆕
196
+
197
+ List files in a directory (if supported by the driver).
198
+
199
+ ```typescript
200
+ for await (const item of storage.list('uploads/')) {
201
+ console.log(item.key, item.size, item.lastModified)
202
+ }
203
+ ```
204
+
205
+ ---
206
+
43
207
  ## 🪝 Hooks
44
208
 
45
- - `storage:init` - Fired when initialized.
46
- - `storage:upload` - (Filter) Modify data before upload.
47
- - `storage:uploaded` - (Action) Triggered after successful upload.
209
+ Nebula integrates with Gravito's hook system for extensibility.
210
+
211
+ | Hook | Type | Parameters | Description |
212
+ |------|------|------------|-------------|
213
+ | `storage:init` | Action | `{ manager: StorageManager }` | Fired when storage is initialized |
214
+ | `storage:upload` | Filter | `data: Blob/Buffer/string, { key: string }` | Modify data before upload |
215
+ | `storage:uploaded` | Action | `{ key: string }` | Triggered after successful upload |
216
+ | `storage:hit` | Action | `{ key: string }` | File retrieved successfully |
217
+ | `storage:miss` | Action | `{ key: string }` | File not found |
218
+ | `storage:deleted` | Action | `{ key: string }` | File deleted |
219
+ | `storage:copied` 🆕 | Action | `{ from: string, to: string }` | File copied |
220
+ | `storage:moved` 🆕 | Action | `{ from: string, to: string }` | File moved |
221
+
222
+ ### Example: Auto-resize Images on Upload
223
+
224
+ ```typescript
225
+ core.hooks.addFilter('storage:upload', async (data, context) => {
226
+ if (context.key.endsWith('.jpg') || context.key.endsWith('.png')) {
227
+ // Resize image using sharp, etc.
228
+ return await resizeImage(data, { width: 1920 })
229
+ }
230
+ return data
231
+ })
232
+ ```
233
+
234
+ ### Example: Log All Uploads
235
+
236
+ ```typescript
237
+ core.hooks.addAction('storage:uploaded', async (context) => {
238
+ core.logger.info(`File uploaded: ${context.key}`)
239
+ })
240
+ ```
241
+
242
+ ---
243
+
244
+ ## 🔌 Custom Storage Drivers
245
+
246
+ Implement the `StorageStore` interface to create custom drivers.
247
+
248
+ ```typescript
249
+ import type { StorageStore, StorageMetadata } from '@gravito/nebula'
250
+
251
+ class S3Store implements StorageStore {
252
+ async put(key: string, data: Blob | Buffer | string): Promise<void> {
253
+ // Upload to S3
254
+ }
255
+
256
+ async get(key: string): Promise<Blob | null> {
257
+ // Download from S3
258
+ }
259
+
260
+ async delete(key: string): Promise<boolean> {
261
+ // Delete from S3
262
+ }
263
+
264
+ async exists(key: string): Promise<boolean> {
265
+ // Check if exists in S3
266
+ }
267
+
268
+ async copy(from: string, to: string): Promise<void> {
269
+ // S3 copy operation
270
+ }
271
+
272
+ async move(from: string, to: string): Promise<void> {
273
+ await this.copy(from, to)
274
+ await this.delete(from)
275
+ }
276
+
277
+ async getMetadata(key: string): Promise<StorageMetadata | null> {
278
+ // Get S3 object metadata
279
+ }
280
+
281
+ getUrl(key: string): string {
282
+ return `https://my-bucket.s3.amazonaws.com/${key}`
283
+ }
284
+
285
+ async getSignedUrl(key: string, expiresIn: number): Promise<string> {
286
+ // Generate pre-signed URL
287
+ }
288
+ }
289
+
290
+ // Use it
291
+ const storage = orbitStorage(core, {
292
+ disks: {
293
+ s3: {
294
+ driver: 'custom',
295
+ store: new S3Store({ bucket: 'my-bucket' })
296
+ }
297
+ }
298
+ })
299
+ ```
300
+
301
+ ---
302
+
303
+ ## 🔄 Migration from v3.x
304
+
305
+ ### Configuration Changes
306
+
307
+ ```typescript
308
+ // v3.x (Old)
309
+ orbitStorage(core, {
310
+ local: { root: './uploads', baseUrl: '/uploads' },
311
+ exposeAs: 'storage'
312
+ })
313
+
314
+ // v4.0 (New)
315
+ orbitStorage(core, {
316
+ default: 'local',
317
+ disks: {
318
+ local: { driver: 'local', root: './uploads', baseUrl: '/uploads' }
319
+ },
320
+ exposeAs: 'storage'
321
+ })
322
+ ```
323
+
324
+ **Note**: The old format is still supported for backward compatibility but is deprecated.
325
+
326
+ ### Return Value Changes
327
+
328
+ ```typescript
329
+ // v3.x - Returns wrapped provider
330
+ const storage = orbitStorage(core, { ... })
331
+ await storage.put('file.txt', data)
332
+
333
+ // v4.0 - Returns StorageManager
334
+ const storage = orbitStorage(core, { ... })
335
+ await storage.put('file.txt', data) // Same API!
336
+ await storage.disk('s3').put('file.txt', data) // New!
337
+ ```
338
+
339
+ The API is backward compatible, but v4.0 adds multi-disk support via `disk()`.
340
+
341
+ ### Type Changes
342
+
343
+ | v3.x | v4.0 |
344
+ |------|------|
345
+ | `StorageProvider` | `StorageStore` |
346
+ | `LocalStorageProvider` | `LocalStore` |
347
+ | `OrbitStorageOptions` | `OrbitNebulaOptions` |
348
+
349
+ Old type names are still exported with `@deprecated` warnings.
350
+
351
+ ---
352
+
353
+ ## 📝 License
354
+
355
+ MIT © [Carl Lee](https://github.com/gravito-framework/gravito)