@gravito/nebula 4.0.0 → 4.1.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 +96 -275
- package/README.zh-TW.md +158 -19
- package/dist/index.cjs +620 -21
- package/dist/index.d.cts +734 -38
- package/dist/index.d.ts +734 -38
- package/dist/index.js +620 -21
- package/package.json +7 -5
package/README.md
CHANGED
|
@@ -1,8 +1,23 @@
|
|
|
1
1
|
# @gravito/nebula
|
|
2
2
|
|
|
3
|
-
> The Standard Storage Orbit for Galaxy Architecture.
|
|
3
|
+
> The Standard Storage Orbit for Galaxy Architecture. Lightweight, multi-disk, and pluggable.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
[](https://www.npmjs.com/package/@gravito/nebula)
|
|
6
|
+
[](https://opensource.org/licenses/MIT)
|
|
7
|
+
[](https://www.typescriptlang.org/)
|
|
8
|
+
[](https://bun.sh/)
|
|
9
|
+
|
|
10
|
+
**@gravito/nebula** provides a unified file storage abstraction layer for Gravito applications. Built on the **Orbit** pattern, it acts as a bridge between the core micro-kernel and various storage backends (Local, S3, Memory, etc.), supporting multi-disk configurations and high extensibility via hooks.
|
|
11
|
+
|
|
12
|
+
## ✨ Features
|
|
13
|
+
|
|
14
|
+
- 🪐 **Orbit Integration** - Seamlessly plugs into the PlanetCore micro-kernel.
|
|
15
|
+
- 💽 **Multi-Disk Support** - Manage multiple storage backends (disks) within a single application.
|
|
16
|
+
- 🔌 **Pluggable Drivers** - Built-in support for `local`, `memory`, and `null` drivers, with easy `custom` driver implementation.
|
|
17
|
+
- 🪝 **Powerful Hooks** - Intercept and modify storage operations (upload, delete, etc.) using Gravito's async Hook system.
|
|
18
|
+
- 🏢 **Enterprise Ready** - Automatic service registration in the IoC container and context-aware middleware.
|
|
19
|
+
- 🧪 **Test Friendly** - Includes a `MemoryStore` for fast, zero-side-effect unit testing.
|
|
20
|
+
- 🚀 **Modern** - Built for **Bun** with native TypeScript support.
|
|
6
21
|
|
|
7
22
|
## 📦 Installation
|
|
8
23
|
|
|
@@ -12,14 +27,15 @@ bun add @gravito/nebula
|
|
|
12
27
|
|
|
13
28
|
## 🚀 Quick Start
|
|
14
29
|
|
|
15
|
-
###
|
|
30
|
+
### 1. Initialize with PlanetCore
|
|
16
31
|
|
|
17
32
|
```typescript
|
|
18
|
-
import { PlanetCore } from '@gravito/core'
|
|
19
|
-
import orbitStorage from '@gravito/nebula'
|
|
33
|
+
import { PlanetCore } from '@gravito/core';
|
|
34
|
+
import orbitStorage from '@gravito/nebula';
|
|
20
35
|
|
|
21
|
-
const core = new PlanetCore()
|
|
36
|
+
const core = new PlanetCore();
|
|
22
37
|
|
|
38
|
+
// Quick install with options
|
|
23
39
|
const storage = orbitStorage(core, {
|
|
24
40
|
default: 'local',
|
|
25
41
|
disks: {
|
|
@@ -29,326 +45,131 @@ const storage = orbitStorage(core, {
|
|
|
29
45
|
baseUrl: '/uploads'
|
|
30
46
|
}
|
|
31
47
|
}
|
|
32
|
-
})
|
|
48
|
+
});
|
|
49
|
+
```
|
|
33
50
|
|
|
34
|
-
|
|
51
|
+
### 2. Use in Routes (Middleware)
|
|
52
|
+
|
|
53
|
+
Nebula automatically injects the `storage` manager into the request context:
|
|
54
|
+
|
|
55
|
+
```typescript
|
|
35
56
|
core.app.post('/upload', async (c) => {
|
|
36
|
-
const body = await c.req.parseBody()
|
|
37
|
-
const file = body['file']
|
|
57
|
+
const body = await c.req.parseBody();
|
|
58
|
+
const file = body['file'];
|
|
38
59
|
|
|
39
60
|
if (file instanceof File) {
|
|
40
|
-
const storage = c.get('storage')
|
|
41
|
-
await storage.put(file.name
|
|
42
|
-
|
|
61
|
+
const storage = c.get('storage'); // Resolved from context
|
|
62
|
+
await storage.put(`avatars/${file.name}`, file);
|
|
63
|
+
|
|
64
|
+
return c.json({
|
|
65
|
+
success: true,
|
|
66
|
+
url: storage.getUrl(`avatars/${file.name}`)
|
|
67
|
+
});
|
|
43
68
|
}
|
|
44
69
|
|
|
45
|
-
return c.text('No file uploaded', 400)
|
|
46
|
-
})
|
|
70
|
+
return c.text('No file uploaded', 400);
|
|
71
|
+
});
|
|
47
72
|
```
|
|
48
73
|
|
|
49
|
-
---
|
|
50
|
-
|
|
51
74
|
## 🔧 Multi-Disk Configuration
|
|
52
75
|
|
|
76
|
+
You can define multiple disks and switch between them at runtime:
|
|
77
|
+
|
|
53
78
|
```typescript
|
|
54
79
|
const storage = orbitStorage(core, {
|
|
55
80
|
default: 'local',
|
|
56
81
|
disks: {
|
|
57
|
-
|
|
58
|
-
|
|
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)
|
|
82
|
+
local: { driver: 'local', root: './uploads', baseUrl: '/uploads' },
|
|
83
|
+
temp: { driver: 'memory' },
|
|
75
84
|
s3: {
|
|
76
85
|
driver: 'custom',
|
|
77
|
-
store: new S3Store({
|
|
78
|
-
bucket: 'my-bucket',
|
|
79
|
-
region: 'us-east-1'
|
|
80
|
-
})
|
|
86
|
+
store: new S3Store({ bucket: 'my-bucket' })
|
|
81
87
|
}
|
|
82
88
|
}
|
|
83
|
-
})
|
|
89
|
+
});
|
|
84
90
|
|
|
85
|
-
//
|
|
86
|
-
await storage.put('
|
|
91
|
+
// Uses 'local' (default)
|
|
92
|
+
await storage.put('hello.txt', 'world');
|
|
87
93
|
|
|
88
|
-
//
|
|
89
|
-
await storage.disk('s3').put('
|
|
90
|
-
await storage.disk('temp').put('cache.json', jsonData)
|
|
94
|
+
// Uses 's3' disk explicitly
|
|
95
|
+
await storage.disk('s3').put('backup.zip', data);
|
|
91
96
|
```
|
|
92
97
|
|
|
93
|
-
---
|
|
94
|
-
|
|
95
98
|
## 📖 API Reference
|
|
96
99
|
|
|
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).
|
|
100
|
+
### `StorageManager`
|
|
169
101
|
|
|
170
|
-
|
|
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
|
-
```
|
|
102
|
+
The central hub accessed via `c.get('storage')` or returned by `orbitStorage()`.
|
|
185
103
|
|
|
186
|
-
|
|
104
|
+
- **`disk(name?: string)`**: Access a specific disk repository.
|
|
105
|
+
- **`put(key, data)`**: Store content (Default disk).
|
|
106
|
+
- **`get(key)`**: Retrieve content as a `Blob` (Default disk).
|
|
107
|
+
- **`delete(key)`**: Remove a file (Default disk).
|
|
108
|
+
- **`exists(key)`**: Check file existence (Default disk).
|
|
109
|
+
- **`copy(from, to)`**: Copy a file (Default disk).
|
|
110
|
+
- **`move(from, to)`**: Move/Rename a file (Default disk).
|
|
111
|
+
- **`getUrl(key)`**: Get public URL (Default disk).
|
|
112
|
+
- **`getSignedUrl(key, expires)`**: Get temporary signed URL (Default disk).
|
|
113
|
+
- **`getMetadata(key)`**: Get file metadata (size, mimeType, etc.).
|
|
114
|
+
- **`list(prefix?)`**: List files as an async iterable.
|
|
187
115
|
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
```typescript
|
|
191
|
-
// Generate a URL that expires in 1 hour
|
|
192
|
-
const signedUrl = await storage.disk('s3').getSignedUrl('private.pdf', 3600)
|
|
193
|
-
```
|
|
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
|
-
```
|
|
116
|
+
### `StorageRepository`
|
|
204
117
|
|
|
205
|
-
|
|
118
|
+
Returned by `storage.disk('name')`. Implements the same storage methods as above but scoped to that specific disk.
|
|
206
119
|
|
|
207
120
|
## 🪝 Hooks
|
|
208
121
|
|
|
209
|
-
Nebula
|
|
122
|
+
Nebula triggers various hooks during its lifecycle:
|
|
210
123
|
|
|
211
|
-
| Hook | Type |
|
|
212
|
-
|
|
213
|
-
| `storage:init` | Action | `{ manager
|
|
214
|
-
| `storage:upload` | Filter | `data
|
|
215
|
-
| `storage:uploaded
|
|
216
|
-
| `storage:hit` | Action | `{ key
|
|
217
|
-
| `storage:miss` | Action | `{ key
|
|
218
|
-
| `storage:deleted` | Action | `{ key
|
|
219
|
-
| `storage:copied` 🆕 | Action | `{ from: string, to: string }` | File copied |
|
|
220
|
-
| `storage:moved` 🆕 | Action | `{ from: string, to: string }` | File moved |
|
|
124
|
+
| Hook | Type | Context | Description |
|
|
125
|
+
|------|------|---------|-------------|
|
|
126
|
+
| `storage:init` | Action | `{ manager }` | Fired on initialization |
|
|
127
|
+
| `storage:upload` | Filter | `data, { key }` | Modify data before it's saved |
|
|
128
|
+
| `storage:uploaded`| Action | `{ key }` | Fired after successful save |
|
|
129
|
+
| `storage:hit` | Action | `{ key }` | Fired when file is found/retrieved |
|
|
130
|
+
| `storage:miss` | Action | `{ key }` | Fired when file is not found |
|
|
131
|
+
| `storage:deleted` | Action | `{ key }` | Fired after file deletion |
|
|
221
132
|
|
|
222
|
-
### Example:
|
|
133
|
+
### Example: Image Resizing
|
|
223
134
|
|
|
224
135
|
```typescript
|
|
225
136
|
core.hooks.addFilter('storage:upload', async (data, context) => {
|
|
226
|
-
if (context.key.
|
|
227
|
-
|
|
228
|
-
return await resizeImage(data, { width: 1920 })
|
|
137
|
+
if (context.key.match(/\.(jpg|png)$/)) {
|
|
138
|
+
return await myImageProcessor.resize(data, 800);
|
|
229
139
|
}
|
|
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
|
-
})
|
|
140
|
+
return data;
|
|
141
|
+
});
|
|
240
142
|
```
|
|
241
143
|
|
|
242
|
-
|
|
144
|
+
## 🔌 Custom Drivers
|
|
243
145
|
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
Implement the `StorageStore` interface to create custom drivers.
|
|
146
|
+
Implement the `StorageStore` interface to create your own backend:
|
|
247
147
|
|
|
248
148
|
```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
|
-
}
|
|
149
|
+
import type { StorageStore, StorageMetadata } from '@gravito/nebula';
|
|
267
150
|
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
async
|
|
273
|
-
|
|
274
|
-
|
|
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
|
-
}
|
|
151
|
+
class MyCustomStore implements StorageStore {
|
|
152
|
+
async put(key: string, data: Blob | Buffer | string): Promise<void> { /* ... */ }
|
|
153
|
+
async get(key: string): Promise<Blob | null> { /* ... */ }
|
|
154
|
+
async delete(key: string): Promise<boolean> { /* ... */ }
|
|
155
|
+
async exists(key: string): Promise<boolean> { /* ... */ }
|
|
156
|
+
getUrl(key: string): string { /* ... */ }
|
|
157
|
+
// ... implement other methods
|
|
288
158
|
}
|
|
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
159
|
```
|
|
300
160
|
|
|
301
|
-
---
|
|
302
|
-
|
|
303
161
|
## 🔄 Migration from v3.x
|
|
304
162
|
|
|
305
|
-
|
|
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
|
|
163
|
+
Nebula v4.0 introduces the **Manager** pattern for multi-disk support.
|
|
342
164
|
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
| `LocalStorageProvider` | `LocalStore` |
|
|
347
|
-
| `OrbitStorageOptions` | `OrbitNebulaOptions` |
|
|
165
|
+
- **Breaking**: The configuration structure has moved under a `disks` property.
|
|
166
|
+
- **Compatibility**: The old flat configuration format is still supported but will trigger a deprecation warning.
|
|
167
|
+
- **Types**: `StorageProvider` is now `StorageStore`, and `LocalStorageProvider` is now `LocalStore`.
|
|
348
168
|
|
|
349
|
-
|
|
169
|
+
## 🤝 Contributing
|
|
350
170
|
|
|
351
|
-
|
|
171
|
+
Contributions, issues and feature requests are welcome!
|
|
172
|
+
Feel free to check the [issues page](https://github.com/gravito-framework/gravito/issues).
|
|
352
173
|
|
|
353
174
|
## 📝 License
|
|
354
175
|
|
package/README.zh-TW.md
CHANGED
|
@@ -1,37 +1,176 @@
|
|
|
1
1
|
# @gravito/nebula
|
|
2
2
|
|
|
3
|
-
>
|
|
3
|
+
> Galaxy 架構的標準儲存 Orbit。輕量、多磁碟支援且可插拔。
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
[](https://www.npmjs.com/package/@gravito/nebula)
|
|
6
|
+
[](https://opensource.org/licenses/MIT)
|
|
7
|
+
[](https://www.typescriptlang.org/)
|
|
8
|
+
[](https://bun.sh/)
|
|
9
|
+
|
|
10
|
+
**@gravito/nebula** 為 Gravito 應用程式提供統一的檔案儲存抽象層。基於 **Orbit** 模式,它充當核心微核心與各種儲存後端(本地、S3、記憶體等)之間的橋樑,支援多磁碟配置並透過 Hook 系統提供高度可擴展性。
|
|
11
|
+
|
|
12
|
+
## ✨ 特性
|
|
13
|
+
|
|
14
|
+
- 🪐 **Orbit 整合** - 無縫插入 PlanetCore 微核心。
|
|
15
|
+
- 💽 **多磁碟支援** - 在單個應用程式中管理多個儲存後端(磁碟)。
|
|
16
|
+
- 🔌 **可插拔驅動** - 內建支援 `local`、`memory` 與 `null` 驅動,並支援輕鬆實作 `custom` 驅動。
|
|
17
|
+
- 🪝 **強大的 Hooks** - 使用 Gravito 的異步 Hook 系統攔截並修改儲存操作(上傳、刪除等)。
|
|
18
|
+
- 🏢 **企業級設計** - 在 IoC 容器中自動註冊服務,並提供 Context 感知的中間件。
|
|
19
|
+
- 🧪 **測試友善** - 包含 `MemoryStore`,用於快速、無副作用的單元測試。
|
|
20
|
+
- 🚀 **現代化** - 專為 **Bun** 構建,原生支援 TypeScript。
|
|
21
|
+
|
|
22
|
+
## 📦 安裝
|
|
6
23
|
|
|
7
24
|
```bash
|
|
8
25
|
bun add @gravito/nebula
|
|
9
26
|
```
|
|
10
27
|
|
|
11
|
-
##
|
|
28
|
+
## 🚀 快速上手
|
|
29
|
+
|
|
30
|
+
### 1. 初始化與 PlanetCore 整合
|
|
12
31
|
|
|
13
32
|
```typescript
|
|
14
|
-
import { PlanetCore } from '@gravito/core'
|
|
15
|
-
import orbitStorage from '@gravito/nebula'
|
|
33
|
+
import { PlanetCore } from '@gravito/core';
|
|
34
|
+
import orbitStorage from '@gravito/nebula';
|
|
16
35
|
|
|
17
|
-
const core = new PlanetCore()
|
|
36
|
+
const core = new PlanetCore();
|
|
18
37
|
|
|
38
|
+
// 使用選項快速安裝
|
|
19
39
|
const storage = orbitStorage(core, {
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
40
|
+
default: 'local',
|
|
41
|
+
disks: {
|
|
42
|
+
local: {
|
|
43
|
+
driver: 'local',
|
|
44
|
+
root: './uploads',
|
|
45
|
+
baseUrl: '/uploads'
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
```
|
|
26
50
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
51
|
+
### 2. 在路由中使用(中間件)
|
|
52
|
+
|
|
53
|
+
Nebula 會自動將 `storage` 管理器注入請求上下文(Context):
|
|
30
54
|
|
|
55
|
+
```typescript
|
|
56
|
+
core.app.post('/upload', async (c) => {
|
|
57
|
+
const body = await c.req.parseBody();
|
|
58
|
+
const file = body['file'];
|
|
59
|
+
|
|
31
60
|
if (file instanceof File) {
|
|
32
|
-
|
|
33
|
-
|
|
61
|
+
const storage = c.get('storage'); // 從上下文中解析
|
|
62
|
+
await storage.put(`avatars/${file.name}`, file);
|
|
63
|
+
|
|
64
|
+
return c.json({
|
|
65
|
+
success: true,
|
|
66
|
+
url: storage.getUrl(`avatars/${file.name}`)
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return c.text('No file uploaded', 400);
|
|
71
|
+
});
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
## 🔧 多磁碟配置
|
|
75
|
+
|
|
76
|
+
您可以定義多個磁碟並在運行時切換:
|
|
77
|
+
|
|
78
|
+
```typescript
|
|
79
|
+
const storage = orbitStorage(core, {
|
|
80
|
+
default: 'local',
|
|
81
|
+
disks: {
|
|
82
|
+
local: { driver: 'local', root: './uploads', baseUrl: '/uploads' },
|
|
83
|
+
temp: { driver: 'memory' },
|
|
84
|
+
s3: {
|
|
85
|
+
driver: 'custom',
|
|
86
|
+
store: new S3Store({ bucket: 'my-bucket' })
|
|
87
|
+
}
|
|
34
88
|
}
|
|
35
|
-
|
|
36
|
-
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
// 使用 'local' (預設)
|
|
92
|
+
await storage.put('hello.txt', 'world');
|
|
93
|
+
|
|
94
|
+
// 明確使用 's3' 磁碟
|
|
95
|
+
await storage.disk('s3').put('backup.zip', data);
|
|
37
96
|
```
|
|
97
|
+
|
|
98
|
+
## 📖 API 參考
|
|
99
|
+
|
|
100
|
+
### `StorageManager`
|
|
101
|
+
|
|
102
|
+
透過 `c.get('storage')` 存取或由 `orbitStorage()` 回傳的中央管理器。
|
|
103
|
+
|
|
104
|
+
- **`disk(name?: string)`**: 存取特定的磁碟倉庫。
|
|
105
|
+
- **`put(key, data)`**: 儲存內容(預設磁碟)。
|
|
106
|
+
- **`get(key)`**: 取得內容為 `Blob`(預設磁碟)。
|
|
107
|
+
- **`delete(key)`**: 刪除檔案(預設磁碟)。
|
|
108
|
+
- **`exists(key)`**: 檢查檔案是否存在(預設磁碟)。
|
|
109
|
+
- **`copy(from, to)`**: 複製檔案(預設磁碟)。
|
|
110
|
+
- **`move(from, to)`**: 移動/重新命名檔案(預設磁碟)。
|
|
111
|
+
- **`getUrl(key)`**: 取得公開 URL(預設磁碟)。
|
|
112
|
+
- **`getSignedUrl(key, expires)`**: 取得臨時簽名 URL(預設磁碟)。
|
|
113
|
+
- **`getMetadata(key)`**: 取得檔案元數據(大小、MIME 類型等)。
|
|
114
|
+
- **`list(prefix?)`**: 列出檔案(異步叠代器)。
|
|
115
|
+
|
|
116
|
+
### `StorageRepository`
|
|
117
|
+
|
|
118
|
+
由 `storage.disk('name')` 回傳。實作與上述相同的儲存方法,但範圍限定於該特定磁碟。
|
|
119
|
+
|
|
120
|
+
## 🪝 Hooks
|
|
121
|
+
|
|
122
|
+
Nebula 在其生命週期中觸發各種 Hook:
|
|
123
|
+
|
|
124
|
+
| Hook | 類型 | 上下文 | 描述 |
|
|
125
|
+
|------|------|---------|-------------|
|
|
126
|
+
| `storage:init` | Action | `{ manager }` | 初始化時觸發 |
|
|
127
|
+
| `storage:upload` | Filter | `data, { key }` | 在儲存前修改資料 |
|
|
128
|
+
| `storage:uploaded`| Action | `{ key }` | 儲存成功後觸發 |
|
|
129
|
+
| `storage:hit` | Action | `{ key }` | 找到/取得檔案時觸發 |
|
|
130
|
+
| `storage:miss` | Action | `{ key }` | 找不到檔案時觸發 |
|
|
131
|
+
| `storage:deleted` | Action | `{ key }` | 檔案刪除後觸發 |
|
|
132
|
+
|
|
133
|
+
### 範例:圖片縮放
|
|
134
|
+
|
|
135
|
+
```typescript
|
|
136
|
+
core.hooks.addFilter('storage:upload', async (data, context) => {
|
|
137
|
+
if (context.key.match(/\.(jpg|png)$/)) {
|
|
138
|
+
return await myImageProcessor.resize(data, 800);
|
|
139
|
+
}
|
|
140
|
+
return data;
|
|
141
|
+
});
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
## 🔌 自定義驅動
|
|
145
|
+
|
|
146
|
+
實作 `StorageStore` 介面來建立您自己的後端:
|
|
147
|
+
|
|
148
|
+
```typescript
|
|
149
|
+
import type { StorageStore, StorageMetadata } from '@gravito/nebula';
|
|
150
|
+
|
|
151
|
+
class MyCustomStore implements StorageStore {
|
|
152
|
+
async put(key: string, data: Blob | Buffer | string): Promise<void> { /* ... */ }
|
|
153
|
+
async get(key: string): Promise<Blob | null> { /* ... */ }
|
|
154
|
+
async delete(key: string): Promise<boolean> { /* ... */ }
|
|
155
|
+
async exists(key: string): Promise<boolean> { /* ... */ }
|
|
156
|
+
getUrl(key: string): string { /* ... */ }
|
|
157
|
+
// ... 實作其他方法
|
|
158
|
+
}
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
## 🔄 從 v3.x 遷移
|
|
162
|
+
|
|
163
|
+
Nebula v4.0 引入了 **Manager** 模式以支援多磁碟。
|
|
164
|
+
|
|
165
|
+
- **破壞性變更**:配置結構已移至 `disks` 屬性下。
|
|
166
|
+
- **相容性**:舊的扁平配置格式仍然支援,但會觸發棄用警告。
|
|
167
|
+
- **類型變更**:`StorageProvider` 現已更名為 `StorageStore`,`LocalStorageProvider` 更名為 `LocalStore`。
|
|
168
|
+
|
|
169
|
+
## 🤝 貢獻
|
|
170
|
+
|
|
171
|
+
歡迎提交貢獻、問題與功能請求!
|
|
172
|
+
請隨時查看 [Issues 頁面](https://github.com/gravito-framework/gravito/issues)。
|
|
173
|
+
|
|
174
|
+
## 📝 授權
|
|
175
|
+
|
|
176
|
+
MIT © [Carl Lee](https://github.com/gravito-framework/gravito)
|