@gravito/nebula 3.0.1 → 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 +144 -15
- package/README.zh-TW.md +158 -19
- package/dist/index.cjs +958 -74
- package/dist/index.d.cts +893 -86
- package/dist/index.d.ts +893 -86
- package/dist/index.js +951 -76
- package/package.json +7 -5
package/dist/index.js
CHANGED
|
@@ -1,87 +1,931 @@
|
|
|
1
|
-
// src/
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
1
|
+
// src/StorageRepository.ts
|
|
2
|
+
var StorageRepository = class {
|
|
3
|
+
constructor(store, hooks) {
|
|
4
|
+
this.store = store;
|
|
5
|
+
this.hooks = hooks;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Stores content and triggers upload hooks.
|
|
9
|
+
*
|
|
10
|
+
* Applies the `storage:upload` filter to the data before storage and
|
|
11
|
+
* fires the `storage:uploaded` action upon success.
|
|
12
|
+
*
|
|
13
|
+
* @param key - The destination path
|
|
14
|
+
* @param data - The content to store
|
|
15
|
+
* @param options - Optional upload options (content-type, metadata, cache-control, etc.)
|
|
16
|
+
* @throws {Error} If the underlying store fails to persist the data
|
|
17
|
+
*/
|
|
18
|
+
async put(key, data, options) {
|
|
19
|
+
const finalData = this.hooks ? await this.hooks.applyFilter("storage:upload", data, { key, options }) : data;
|
|
20
|
+
await this.store.put(key, finalData, options);
|
|
21
|
+
if (this.hooks) {
|
|
22
|
+
await this.hooks.doAction("storage:uploaded", { key, options });
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Retrieves content and triggers hit/miss hooks.
|
|
27
|
+
*
|
|
28
|
+
* Fires `storage:hit` if the file exists, or `storage:miss` otherwise.
|
|
29
|
+
*
|
|
30
|
+
* @param key - The path of the file to retrieve
|
|
31
|
+
* @returns The file content as a Blob, or null if not found
|
|
32
|
+
*/
|
|
33
|
+
async get(key) {
|
|
34
|
+
const data = await this.store.get(key);
|
|
35
|
+
if (this.hooks) {
|
|
36
|
+
if (data) {
|
|
37
|
+
await this.hooks.doAction("storage:hit", { key });
|
|
38
|
+
} else {
|
|
39
|
+
await this.hooks.doAction("storage:miss", { key });
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
return data;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Deletes a file and triggers the deleted hook.
|
|
46
|
+
*
|
|
47
|
+
* Fires `storage:deleted` only if the file was actually removed.
|
|
48
|
+
*
|
|
49
|
+
* @param key - The path of the file to delete
|
|
50
|
+
* @returns True if deleted, false if file didn't exist
|
|
51
|
+
*/
|
|
52
|
+
async delete(key) {
|
|
53
|
+
const deleted = await this.store.delete(key);
|
|
54
|
+
if (deleted && this.hooks) {
|
|
55
|
+
await this.hooks.doAction("storage:deleted", { key });
|
|
56
|
+
}
|
|
57
|
+
return deleted;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Checks for file existence.
|
|
61
|
+
*
|
|
62
|
+
* @param key - The path to check
|
|
63
|
+
* @returns True if exists, false otherwise
|
|
64
|
+
*/
|
|
65
|
+
async exists(key) {
|
|
66
|
+
return this.store.exists(key);
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Copies a file and triggers the copied hook.
|
|
70
|
+
*
|
|
71
|
+
* Fires `storage:copied` upon successful completion.
|
|
72
|
+
*
|
|
73
|
+
* @param from - Source path
|
|
74
|
+
* @param to - Destination path
|
|
75
|
+
* @throws {Error} If source missing or operation fails
|
|
76
|
+
*/
|
|
77
|
+
async copy(from, to) {
|
|
78
|
+
await this.store.copy(from, to);
|
|
79
|
+
if (this.hooks) {
|
|
80
|
+
await this.hooks.doAction("storage:copied", { from, to });
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Moves a file and triggers the moved hook.
|
|
85
|
+
*
|
|
86
|
+
* Fires `storage:moved` upon successful completion.
|
|
87
|
+
*
|
|
88
|
+
* @param from - Current path
|
|
89
|
+
* @param to - New path
|
|
90
|
+
* @throws {Error} If source missing or operation fails
|
|
91
|
+
*/
|
|
92
|
+
async move(from, to) {
|
|
93
|
+
await this.store.move(from, to);
|
|
94
|
+
if (this.hooks) {
|
|
95
|
+
await this.hooks.doAction("storage:moved", { from, to });
|
|
96
|
+
}
|
|
97
|
+
}
|
|
11
98
|
/**
|
|
12
|
-
*
|
|
99
|
+
* Lists files using an async generator.
|
|
13
100
|
*
|
|
14
|
-
* @param
|
|
15
|
-
* @
|
|
101
|
+
* @param prefix - Path prefix to filter by
|
|
102
|
+
* @returns Async iterable of storage items
|
|
103
|
+
* @throws {Error} If the underlying driver does not support listing
|
|
16
104
|
*/
|
|
105
|
+
async *list(prefix) {
|
|
106
|
+
if (!this.store.list) {
|
|
107
|
+
throw new Error("[StorageRepository] This storage driver does not support listing files.");
|
|
108
|
+
}
|
|
109
|
+
yield* this.store.list(prefix);
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Lists files with pagination support.
|
|
113
|
+
*
|
|
114
|
+
* 分頁列舉檔案
|
|
115
|
+
* Recommended for large storage backends to prevent OOM.
|
|
116
|
+
* Provides cursor-based pagination for efficient enumeration.
|
|
117
|
+
*
|
|
118
|
+
* @param prefix - Path prefix to filter by
|
|
119
|
+
* @param options - Pagination and filtering options
|
|
120
|
+
* @returns Paginated list result with cursor for next page
|
|
121
|
+
* @throws {Error} If the underlying driver does not support paginated listing
|
|
122
|
+
*/
|
|
123
|
+
async listPaginated(prefix, options) {
|
|
124
|
+
if (!this.store.listPaginated) {
|
|
125
|
+
throw new Error("[StorageRepository] This storage driver does not support paginated listing.");
|
|
126
|
+
}
|
|
127
|
+
return this.store.listPaginated(prefix, options);
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Retrieves file metadata.
|
|
131
|
+
*
|
|
132
|
+
* @param key - Path of the file
|
|
133
|
+
* @returns Metadata object or null if not found
|
|
134
|
+
*/
|
|
135
|
+
async getMetadata(key) {
|
|
136
|
+
return this.store.getMetadata(key);
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Updates custom metadata for a file.
|
|
140
|
+
*
|
|
141
|
+
* 更新自定義 Metadata
|
|
142
|
+
*
|
|
143
|
+
* @param key - Path of the file
|
|
144
|
+
* @param metadata - Custom metadata to set
|
|
145
|
+
* @throws {Error} If the underlying driver does not support metadata updates
|
|
146
|
+
*/
|
|
147
|
+
async setMetadata(key, metadata) {
|
|
148
|
+
if (!this.store.setMetadata) {
|
|
149
|
+
throw new Error("[StorageRepository] This storage driver does not support metadata updates.");
|
|
150
|
+
}
|
|
151
|
+
await this.store.setMetadata(key, metadata);
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* Generates a public URL.
|
|
155
|
+
*
|
|
156
|
+
* @param key - Path of the file
|
|
157
|
+
* @returns URL string
|
|
158
|
+
*/
|
|
159
|
+
getUrl(key) {
|
|
160
|
+
return this.store.getUrl(key);
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* Generates a temporary signed URL.
|
|
164
|
+
*
|
|
165
|
+
* @param key - Path of the file
|
|
166
|
+
* @param expiresIn - Expiration in seconds
|
|
167
|
+
* @returns Signed URL string
|
|
168
|
+
* @throws {Error} If the underlying driver does not support signed URLs
|
|
169
|
+
*/
|
|
170
|
+
async getSignedUrl(key, expiresIn) {
|
|
171
|
+
if (!this.store.getSignedUrl) {
|
|
172
|
+
throw new Error("[StorageRepository] This storage driver does not support signed URLs.");
|
|
173
|
+
}
|
|
174
|
+
return this.store.getSignedUrl(key, expiresIn);
|
|
175
|
+
}
|
|
176
|
+
/**
|
|
177
|
+
* Stores content from a readable stream and triggers upload hooks.
|
|
178
|
+
*
|
|
179
|
+
* 串流上傳並觸發 hooks
|
|
180
|
+
* Useful for handling large files without loading entire content into memory.
|
|
181
|
+
* Applies the `storage:upload` filter and fires the `storage:uploaded` action.
|
|
182
|
+
*
|
|
183
|
+
* @param key - The destination path
|
|
184
|
+
* @param stream - Readable stream containing the data
|
|
185
|
+
* @throws {Error} If the underlying driver does not support stream writing
|
|
186
|
+
*/
|
|
187
|
+
async putStream(key, stream) {
|
|
188
|
+
if (!this.store.putStream) {
|
|
189
|
+
throw new Error("[StorageRepository] This storage driver does not support stream writing.");
|
|
190
|
+
}
|
|
191
|
+
await this.store.putStream(key, stream);
|
|
192
|
+
if (this.hooks) {
|
|
193
|
+
await this.hooks.doAction("storage:uploaded", { key });
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
/**
|
|
197
|
+
* Retrieves content as a readable stream and triggers hit/miss hooks.
|
|
198
|
+
*
|
|
199
|
+
* 串流讀取並觸發 hooks
|
|
200
|
+
* Useful for handling large files without loading entire content into memory.
|
|
201
|
+
* Fires `storage:hit` if the file exists, or `storage:miss` otherwise.
|
|
202
|
+
*
|
|
203
|
+
* @param key - Path of the file
|
|
204
|
+
* @returns Readable stream of file content, or null if not found
|
|
205
|
+
* @throws {Error} If the underlying driver does not support stream reading
|
|
206
|
+
*/
|
|
207
|
+
async getStream(key) {
|
|
208
|
+
if (!this.store.getStream) {
|
|
209
|
+
throw new Error("[StorageRepository] This storage driver does not support stream reading.");
|
|
210
|
+
}
|
|
211
|
+
const stream = await this.store.getStream(key);
|
|
212
|
+
if (this.hooks) {
|
|
213
|
+
if (stream) {
|
|
214
|
+
await this.hooks.doAction("storage:hit", { key });
|
|
215
|
+
} else {
|
|
216
|
+
await this.hooks.doAction("storage:miss", { key });
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
return stream;
|
|
220
|
+
}
|
|
221
|
+
};
|
|
222
|
+
|
|
223
|
+
// src/StorageManager.ts
|
|
224
|
+
var StorageManager = class {
|
|
225
|
+
constructor(storeFactory, options, hooks) {
|
|
226
|
+
this.storeFactory = storeFactory;
|
|
227
|
+
this.options = options;
|
|
228
|
+
this.hooks = hooks;
|
|
229
|
+
}
|
|
230
|
+
stores = /* @__PURE__ */ new Map();
|
|
231
|
+
repositories = /* @__PURE__ */ new Map();
|
|
232
|
+
/**
|
|
233
|
+
* Accesses a specific storage disk by name.
|
|
234
|
+
*
|
|
235
|
+
* If no name is provided, the default disk configured during initialization is returned.
|
|
236
|
+
* Repositories are lazily initialized and cached for subsequent access.
|
|
237
|
+
*
|
|
238
|
+
* @param name - The unique identifier of the disk to access
|
|
239
|
+
* @returns A repository instance for the requested disk
|
|
240
|
+
* @throws {Error} If the requested disk is not configured or factory fails
|
|
241
|
+
*/
|
|
242
|
+
disk(name) {
|
|
243
|
+
const diskName = name ?? this.options.default;
|
|
244
|
+
if (!this.repositories.has(diskName)) {
|
|
245
|
+
const store = this.resolveStore(diskName);
|
|
246
|
+
this.repositories.set(diskName, new StorageRepository(store, this.hooks));
|
|
247
|
+
}
|
|
248
|
+
return this.repositories.get(diskName);
|
|
249
|
+
}
|
|
250
|
+
resolveStore(name) {
|
|
251
|
+
if (!this.stores.has(name)) {
|
|
252
|
+
this.stores.set(name, this.storeFactory(name));
|
|
253
|
+
}
|
|
254
|
+
return this.stores.get(name);
|
|
255
|
+
}
|
|
256
|
+
/**
|
|
257
|
+
* Stores content at the specified key on the default disk.
|
|
258
|
+
*
|
|
259
|
+
* @param key - The destination path/identifier
|
|
260
|
+
* @param data - The content to store
|
|
261
|
+
* @param options - Optional upload options (content-type, metadata, cache-control, etc.)
|
|
262
|
+
* @returns A promise that resolves when the operation completes
|
|
263
|
+
* @throws {Error} If the storage operation fails on the default disk
|
|
264
|
+
*/
|
|
265
|
+
put(key, data, options) {
|
|
266
|
+
return this.disk().put(key, data, options);
|
|
267
|
+
}
|
|
268
|
+
/**
|
|
269
|
+
* Retrieves content from the specified key on the default disk.
|
|
270
|
+
*
|
|
271
|
+
* @param key - The path/identifier of the file to retrieve
|
|
272
|
+
* @returns The file content as a Blob, or null if not found
|
|
273
|
+
*/
|
|
274
|
+
get(key) {
|
|
275
|
+
return this.disk().get(key);
|
|
276
|
+
}
|
|
277
|
+
/**
|
|
278
|
+
* Removes a file from the default disk.
|
|
279
|
+
*
|
|
280
|
+
* @param key - The path/identifier of the file to delete
|
|
281
|
+
* @returns True if the file was deleted, false if it didn't exist
|
|
282
|
+
*/
|
|
283
|
+
delete(key) {
|
|
284
|
+
return this.disk().delete(key);
|
|
285
|
+
}
|
|
286
|
+
/**
|
|
287
|
+
* Checks if a file exists on the default disk.
|
|
288
|
+
*
|
|
289
|
+
* @param key - The path/identifier to check
|
|
290
|
+
* @returns True if the file exists, false otherwise
|
|
291
|
+
*/
|
|
292
|
+
exists(key) {
|
|
293
|
+
return this.disk().exists(key);
|
|
294
|
+
}
|
|
295
|
+
/**
|
|
296
|
+
* Creates a copy of a file on the default disk.
|
|
297
|
+
*
|
|
298
|
+
* @param from - The source path
|
|
299
|
+
* @param to - The destination path
|
|
300
|
+
* @throws {Error} If the source file does not exist or copy fails
|
|
301
|
+
*/
|
|
302
|
+
copy(from, to) {
|
|
303
|
+
return this.disk().copy(from, to);
|
|
304
|
+
}
|
|
305
|
+
/**
|
|
306
|
+
* Moves or renames a file on the default disk.
|
|
307
|
+
*
|
|
308
|
+
* @param from - The current path
|
|
309
|
+
* @param to - The new path
|
|
310
|
+
* @throws {Error} If the source file does not exist or move fails
|
|
311
|
+
*/
|
|
312
|
+
move(from, to) {
|
|
313
|
+
return this.disk().move(from, to);
|
|
314
|
+
}
|
|
315
|
+
/**
|
|
316
|
+
* Lists files and directories under a given prefix on the default disk.
|
|
317
|
+
*
|
|
318
|
+
* @param prefix - The directory or path prefix to list
|
|
319
|
+
* @returns An async iterable of storage items
|
|
320
|
+
* @throws {Error} If the default disk driver does not support listing
|
|
321
|
+
*/
|
|
322
|
+
list(prefix) {
|
|
323
|
+
return this.disk().list(prefix);
|
|
324
|
+
}
|
|
325
|
+
/**
|
|
326
|
+
* Retrieves metadata for a specific file on the default disk.
|
|
327
|
+
*
|
|
328
|
+
* @param key - The path/identifier of the file
|
|
329
|
+
* @returns Metadata object or null if file not found
|
|
330
|
+
*/
|
|
331
|
+
getMetadata(key) {
|
|
332
|
+
return this.disk().getMetadata(key);
|
|
333
|
+
}
|
|
334
|
+
/**
|
|
335
|
+
* Generates a public URL for a file on the default disk.
|
|
336
|
+
*
|
|
337
|
+
* @param key - The path/identifier of the file
|
|
338
|
+
* @returns The absolute or relative URL string
|
|
339
|
+
*/
|
|
340
|
+
getUrl(key) {
|
|
341
|
+
return this.disk().getUrl(key);
|
|
342
|
+
}
|
|
343
|
+
/**
|
|
344
|
+
* Generates a temporary, signed URL for a file on the default disk.
|
|
345
|
+
*
|
|
346
|
+
* @param key - The path/identifier of the file
|
|
347
|
+
* @param expiresIn - Expiration time in seconds
|
|
348
|
+
* @returns A promise resolving to the signed URL string
|
|
349
|
+
* @throws {Error} If the default disk driver does not support signed URLs
|
|
350
|
+
*/
|
|
351
|
+
getSignedUrl(key, expiresIn) {
|
|
352
|
+
return this.disk().getSignedUrl(key, expiresIn);
|
|
353
|
+
}
|
|
354
|
+
/**
|
|
355
|
+
* Stores content from a readable stream on the default disk.
|
|
356
|
+
*
|
|
357
|
+
* 串流上傳到預設磁碟
|
|
358
|
+
* Useful for handling large files without loading entire content into memory.
|
|
359
|
+
*
|
|
360
|
+
* @param key - The destination path
|
|
361
|
+
* @param stream - Readable stream containing the data
|
|
362
|
+
* @throws {Error} If the default disk driver does not support stream writing
|
|
363
|
+
*/
|
|
364
|
+
putStream(key, stream) {
|
|
365
|
+
return this.disk().putStream(key, stream);
|
|
366
|
+
}
|
|
367
|
+
/**
|
|
368
|
+
* Retrieves content as a readable stream from the default disk.
|
|
369
|
+
*
|
|
370
|
+
* 從預設磁碟串流讀取
|
|
371
|
+
* Useful for handling large files without loading entire content into memory.
|
|
372
|
+
*
|
|
373
|
+
* @param key - The path/identifier of the file
|
|
374
|
+
* @returns Readable stream of file content, or null if not found
|
|
375
|
+
* @throws {Error} If the default disk driver does not support stream reading
|
|
376
|
+
*/
|
|
377
|
+
getStream(key) {
|
|
378
|
+
return this.disk().getStream(key);
|
|
379
|
+
}
|
|
380
|
+
/**
|
|
381
|
+
* Lists files with pagination support on the default disk.
|
|
382
|
+
*
|
|
383
|
+
* 在預設磁碟上分頁列舉檔案
|
|
384
|
+
* Recommended for large storage backends to prevent OOM.
|
|
385
|
+
*
|
|
386
|
+
* @param prefix - Path prefix to filter by
|
|
387
|
+
* @param options - Pagination and filtering options
|
|
388
|
+
* @returns Paginated list result with cursor for next page
|
|
389
|
+
* @throws {Error} If the default disk driver does not support paginated listing
|
|
390
|
+
*/
|
|
391
|
+
listPaginated(prefix, options) {
|
|
392
|
+
return this.disk().listPaginated(prefix, options);
|
|
393
|
+
}
|
|
394
|
+
/**
|
|
395
|
+
* Updates custom metadata for a file on the default disk.
|
|
396
|
+
*
|
|
397
|
+
* 更新預設磁碟上檔案的自定義 Metadata
|
|
398
|
+
*
|
|
399
|
+
* @param key - The path/identifier of the file
|
|
400
|
+
* @param metadata - Custom metadata to set
|
|
401
|
+
* @throws {Error} If the default disk driver does not support metadata updates
|
|
402
|
+
*/
|
|
403
|
+
setMetadata(key, metadata) {
|
|
404
|
+
return this.disk().setMetadata(key, metadata);
|
|
405
|
+
}
|
|
406
|
+
};
|
|
407
|
+
|
|
408
|
+
// src/stores/LocalStore.ts
|
|
409
|
+
import { mkdir } from "fs/promises";
|
|
410
|
+
import { isAbsolute, normalize, resolve, sep } from "path";
|
|
411
|
+
import { getRuntimeAdapter } from "@gravito/core";
|
|
412
|
+
var LocalStore = class {
|
|
17
413
|
constructor(rootDir, baseUrl = "/storage") {
|
|
18
414
|
this.rootDir = rootDir;
|
|
19
415
|
this.baseUrl = baseUrl;
|
|
20
416
|
}
|
|
417
|
+
runtime = getRuntimeAdapter();
|
|
21
418
|
/**
|
|
22
|
-
*
|
|
419
|
+
* Writes data to a file on the local disk.
|
|
420
|
+
*
|
|
421
|
+
* Automatically creates parent directories if they don't exist.
|
|
422
|
+
*
|
|
423
|
+
* @param key - Relative path from the root directory
|
|
424
|
+
* @param data - Content to write
|
|
425
|
+
* @param options - Optional upload options (note: customMetadata not persisted in LocalStore)
|
|
426
|
+
* @throws {Error} If the key is invalid or path is outside root
|
|
23
427
|
*/
|
|
24
|
-
async put(key, data) {
|
|
25
|
-
const path = this.
|
|
26
|
-
|
|
27
|
-
if (dir && dir !== this.rootDir) {
|
|
28
|
-
await mkdir(dir, { recursive: true });
|
|
29
|
-
}
|
|
428
|
+
async put(key, data, options) {
|
|
429
|
+
const path = this.resolvePath(key);
|
|
430
|
+
await this.ensureDirectory(path);
|
|
30
431
|
await this.runtime.writeFile(path, data);
|
|
31
432
|
}
|
|
32
433
|
/**
|
|
33
|
-
*
|
|
434
|
+
* Reads a file from the local disk as a Blob.
|
|
435
|
+
*
|
|
436
|
+
* @param key - Relative path from the root directory
|
|
437
|
+
* @returns File content as Blob, or null if not found
|
|
438
|
+
* @throws {Error} If the key is invalid or path is outside root
|
|
34
439
|
*/
|
|
35
440
|
async get(key) {
|
|
36
|
-
|
|
37
|
-
if (!await this.runtime.exists(path)) {
|
|
441
|
+
if (!await this.exists(key)) {
|
|
38
442
|
return null;
|
|
39
443
|
}
|
|
40
|
-
|
|
444
|
+
const path = this.resolvePath(key);
|
|
445
|
+
return this.runtime.readFileAsBlob(path);
|
|
41
446
|
}
|
|
42
447
|
/**
|
|
43
|
-
*
|
|
448
|
+
* Deletes a file from the local disk.
|
|
449
|
+
*
|
|
450
|
+
* @param key - Relative path from the root directory
|
|
451
|
+
* @returns True if deleted, false if file didn't exist
|
|
452
|
+
* @throws {Error} If the key is invalid or path is outside root
|
|
44
453
|
*/
|
|
45
454
|
async delete(key) {
|
|
46
|
-
await this.
|
|
455
|
+
if (!await this.exists(key)) {
|
|
456
|
+
return false;
|
|
457
|
+
}
|
|
458
|
+
const path = this.resolvePath(key);
|
|
459
|
+
await this.runtime.deleteFile(path);
|
|
460
|
+
return true;
|
|
47
461
|
}
|
|
48
462
|
/**
|
|
49
|
-
*
|
|
463
|
+
* Checks if a file exists on the local disk.
|
|
464
|
+
*
|
|
465
|
+
* @param key - Relative path from the root directory
|
|
466
|
+
* @returns True if exists, false otherwise
|
|
467
|
+
*/
|
|
468
|
+
async exists(key) {
|
|
469
|
+
const path = this.resolvePath(key);
|
|
470
|
+
return this.runtime.exists(path);
|
|
471
|
+
}
|
|
472
|
+
/**
|
|
473
|
+
* Copies a file on the local disk.
|
|
474
|
+
*
|
|
475
|
+
* @param from - Source relative path
|
|
476
|
+
* @param to - Destination relative path
|
|
477
|
+
* @throws {Error} If source missing or operation fails
|
|
478
|
+
*/
|
|
479
|
+
async copy(from, to) {
|
|
480
|
+
const data = await this.get(from);
|
|
481
|
+
if (!data) {
|
|
482
|
+
throw new Error(`[LocalStore] Source file not found: ${from}`);
|
|
483
|
+
}
|
|
484
|
+
await this.put(to, data);
|
|
485
|
+
}
|
|
486
|
+
/**
|
|
487
|
+
* Moves a file on the local disk.
|
|
488
|
+
*
|
|
489
|
+
* @param from - Current relative path
|
|
490
|
+
* @param to - New relative path
|
|
491
|
+
* @throws {Error} If source missing or operation fails
|
|
492
|
+
*/
|
|
493
|
+
async move(from, to) {
|
|
494
|
+
await this.copy(from, to);
|
|
495
|
+
await this.delete(from);
|
|
496
|
+
}
|
|
497
|
+
/**
|
|
498
|
+
* @internal
|
|
499
|
+
* @throws {Error} Always, as not yet implemented
|
|
500
|
+
*/
|
|
501
|
+
list(_prefix = "") {
|
|
502
|
+
throw new Error(
|
|
503
|
+
"[LocalStore] list() is not yet implemented. Requires RuntimeAdapter.readDir() support in @gravito/core."
|
|
504
|
+
);
|
|
505
|
+
}
|
|
506
|
+
/**
|
|
507
|
+
* Retrieves file metadata from the local filesystem.
|
|
508
|
+
*
|
|
509
|
+
* @param key - Relative path from the root directory
|
|
510
|
+
* @returns Metadata object or null if not found
|
|
511
|
+
*/
|
|
512
|
+
async getMetadata(key) {
|
|
513
|
+
if (!await this.exists(key)) {
|
|
514
|
+
return null;
|
|
515
|
+
}
|
|
516
|
+
const path = this.resolvePath(key);
|
|
517
|
+
const stat = await this.runtime.stat(path);
|
|
518
|
+
return {
|
|
519
|
+
key,
|
|
520
|
+
size: stat.size,
|
|
521
|
+
mimeType: this.guessMimeType(key),
|
|
522
|
+
lastModified: /* @__PURE__ */ new Date()
|
|
523
|
+
};
|
|
524
|
+
}
|
|
525
|
+
/**
|
|
526
|
+
* Generates a public URL based on the configured base URL.
|
|
527
|
+
*
|
|
528
|
+
* @param key - Relative path from the root directory
|
|
529
|
+
* @returns URL string
|
|
50
530
|
*/
|
|
51
531
|
getUrl(key) {
|
|
52
532
|
const safeKey = this.normalizeKey(key);
|
|
53
533
|
return `${this.baseUrl}/${safeKey}`;
|
|
54
534
|
}
|
|
535
|
+
/**
|
|
536
|
+
* Writes data from a readable stream to a file on the local disk.
|
|
537
|
+
*
|
|
538
|
+
* 串流寫入檔案
|
|
539
|
+
* Automatically creates parent directories if they don't exist.
|
|
540
|
+
* Useful for handling large files without loading entire content into memory.
|
|
541
|
+
*
|
|
542
|
+
* @param key - Relative path from the root directory
|
|
543
|
+
* @param stream - Readable stream containing the data
|
|
544
|
+
* @throws {Error} If the key is invalid or path is outside root
|
|
545
|
+
*/
|
|
546
|
+
async putStream(key, stream) {
|
|
547
|
+
const path = this.resolvePath(key);
|
|
548
|
+
await this.ensureDirectory(path);
|
|
549
|
+
const file = Bun.file(path);
|
|
550
|
+
const writer = file.writer();
|
|
551
|
+
const reader = stream.getReader();
|
|
552
|
+
try {
|
|
553
|
+
while (true) {
|
|
554
|
+
const { done, value } = await reader.read();
|
|
555
|
+
if (done) break;
|
|
556
|
+
writer.write(value);
|
|
557
|
+
}
|
|
558
|
+
await writer.end();
|
|
559
|
+
} catch (error) {
|
|
560
|
+
reader.releaseLock();
|
|
561
|
+
throw new Error(`[LocalStore] Failed to write stream: ${error}`);
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
/**
|
|
565
|
+
* Reads a file from the local disk as a readable stream.
|
|
566
|
+
*
|
|
567
|
+
* 串流讀取檔案
|
|
568
|
+
* Useful for handling large files without loading entire content into memory.
|
|
569
|
+
*
|
|
570
|
+
* @param key - Relative path from the root directory
|
|
571
|
+
* @returns Readable stream of file content, or null if not found
|
|
572
|
+
* @throws {Error} If the key is invalid or path is outside root
|
|
573
|
+
*/
|
|
574
|
+
async getStream(key) {
|
|
575
|
+
if (!await this.exists(key)) {
|
|
576
|
+
return null;
|
|
577
|
+
}
|
|
578
|
+
const path = this.resolvePath(key);
|
|
579
|
+
const file = Bun.file(path);
|
|
580
|
+
return file.stream();
|
|
581
|
+
}
|
|
55
582
|
normalizeKey(key) {
|
|
56
583
|
if (!key || key.includes("\0")) {
|
|
57
|
-
throw new Error("Invalid storage key.");
|
|
584
|
+
throw new Error("[LocalStore] Invalid storage key: empty or contains null byte.");
|
|
58
585
|
}
|
|
59
586
|
const normalized = normalize(key).replace(/^[/\\]+/, "");
|
|
60
|
-
if (normalized === "." || normalized === ".." || normalized.startsWith(`..${sep}`) || isAbsolute(normalized)) {
|
|
61
|
-
throw new Error("Invalid storage key.");
|
|
587
|
+
if (normalized === "." || normalized === ".." || normalized.startsWith(`..${sep}`) || normalized.startsWith(`.${sep}`) || isAbsolute(normalized)) {
|
|
588
|
+
throw new Error("[LocalStore] Invalid storage key: path traversal attempt.");
|
|
62
589
|
}
|
|
63
590
|
return normalized.replace(/\\/g, "/");
|
|
64
591
|
}
|
|
65
|
-
|
|
592
|
+
resolvePath(key) {
|
|
66
593
|
const normalized = this.normalizeKey(key);
|
|
67
594
|
const root = resolve(this.rootDir);
|
|
68
595
|
const resolved = resolve(root, normalized);
|
|
69
596
|
const rootPrefix = root.endsWith(sep) ? root : `${root}${sep}`;
|
|
70
597
|
if (!resolved.startsWith(rootPrefix) && resolved !== root) {
|
|
71
|
-
throw new Error("Invalid storage key.");
|
|
598
|
+
throw new Error("[LocalStore] Invalid storage key: resolved path outside root.");
|
|
72
599
|
}
|
|
73
600
|
return resolved;
|
|
74
601
|
}
|
|
602
|
+
async ensureDirectory(filePath) {
|
|
603
|
+
const dir = filePath.substring(0, filePath.lastIndexOf(sep));
|
|
604
|
+
if (dir && dir !== this.rootDir) {
|
|
605
|
+
await mkdir(dir, { recursive: true });
|
|
606
|
+
}
|
|
607
|
+
}
|
|
608
|
+
guessMimeType(key) {
|
|
609
|
+
const ext = key.split(".").pop()?.toLowerCase();
|
|
610
|
+
const mimeTypes = {
|
|
611
|
+
txt: "text/plain",
|
|
612
|
+
html: "text/html",
|
|
613
|
+
css: "text/css",
|
|
614
|
+
js: "text/javascript",
|
|
615
|
+
json: "application/json",
|
|
616
|
+
png: "image/png",
|
|
617
|
+
jpg: "image/jpeg",
|
|
618
|
+
jpeg: "image/jpeg",
|
|
619
|
+
gif: "image/gif",
|
|
620
|
+
svg: "image/svg+xml",
|
|
621
|
+
pdf: "application/pdf",
|
|
622
|
+
zip: "application/zip"
|
|
623
|
+
};
|
|
624
|
+
return mimeTypes[ext ?? ""] ?? "application/octet-stream";
|
|
625
|
+
}
|
|
75
626
|
};
|
|
627
|
+
|
|
628
|
+
// src/stores/MemoryStore.ts
|
|
629
|
+
var MemoryStore = class {
|
|
630
|
+
files = /* @__PURE__ */ new Map();
|
|
631
|
+
/**
|
|
632
|
+
* Stores data in the internal Map.
|
|
633
|
+
*
|
|
634
|
+
* Converts input data to a Blob before storage.
|
|
635
|
+
*
|
|
636
|
+
* @param key - Unique identifier for the file
|
|
637
|
+
* @param data - Content to store
|
|
638
|
+
* @param options - Optional upload options (content-type, metadata, etc.)
|
|
639
|
+
*/
|
|
640
|
+
async put(key, data, options) {
|
|
641
|
+
let blob;
|
|
642
|
+
if (data instanceof Blob) {
|
|
643
|
+
blob = data;
|
|
644
|
+
} else if (typeof data === "string") {
|
|
645
|
+
blob = new Blob([data], { type: options?.contentType });
|
|
646
|
+
} else {
|
|
647
|
+
blob = new Blob([new Uint8Array(data)], { type: options?.contentType });
|
|
648
|
+
}
|
|
649
|
+
this.files.set(key, {
|
|
650
|
+
data: blob,
|
|
651
|
+
options,
|
|
652
|
+
metadata: {
|
|
653
|
+
key,
|
|
654
|
+
size: blob.size,
|
|
655
|
+
mimeType: options?.contentType || blob.type || "application/octet-stream",
|
|
656
|
+
lastModified: /* @__PURE__ */ new Date(),
|
|
657
|
+
customMetadata: options?.metadata
|
|
658
|
+
}
|
|
659
|
+
});
|
|
660
|
+
}
|
|
661
|
+
/**
|
|
662
|
+
* Retrieves a Blob from the internal Map.
|
|
663
|
+
*
|
|
664
|
+
* @param key - Unique identifier for the file
|
|
665
|
+
* @returns The Blob content, or null if not found
|
|
666
|
+
*/
|
|
667
|
+
async get(key) {
|
|
668
|
+
return this.files.get(key)?.data ?? null;
|
|
669
|
+
}
|
|
670
|
+
/**
|
|
671
|
+
* Removes a file from memory.
|
|
672
|
+
*
|
|
673
|
+
* @param key - Unique identifier for the file
|
|
674
|
+
* @returns True if deleted, false if not found
|
|
675
|
+
*/
|
|
676
|
+
async delete(key) {
|
|
677
|
+
return this.files.delete(key);
|
|
678
|
+
}
|
|
679
|
+
/**
|
|
680
|
+
* Checks if a key exists in the internal Map.
|
|
681
|
+
*
|
|
682
|
+
* @param key - Unique identifier to check
|
|
683
|
+
* @returns True if exists, false otherwise
|
|
684
|
+
*/
|
|
685
|
+
async exists(key) {
|
|
686
|
+
return this.files.has(key);
|
|
687
|
+
}
|
|
688
|
+
/**
|
|
689
|
+
* Copies a file within memory.
|
|
690
|
+
*
|
|
691
|
+
* @param from - Source key
|
|
692
|
+
* @param to - Destination key
|
|
693
|
+
* @throws {Error} If source file is missing
|
|
694
|
+
*/
|
|
695
|
+
async copy(from, to) {
|
|
696
|
+
const file = this.files.get(from);
|
|
697
|
+
if (!file) {
|
|
698
|
+
throw new Error(`[MemoryStore] Source file not found: ${from}`);
|
|
699
|
+
}
|
|
700
|
+
this.files.set(to, {
|
|
701
|
+
data: file.data,
|
|
702
|
+
metadata: { ...file.metadata, key: to, lastModified: /* @__PURE__ */ new Date() }
|
|
703
|
+
});
|
|
704
|
+
}
|
|
705
|
+
/**
|
|
706
|
+
* Moves a file within memory.
|
|
707
|
+
*
|
|
708
|
+
* @param from - Current key
|
|
709
|
+
* @param to - New key
|
|
710
|
+
* @throws {Error} If source file is missing
|
|
711
|
+
*/
|
|
712
|
+
async move(from, to) {
|
|
713
|
+
await this.copy(from, to);
|
|
714
|
+
await this.delete(from);
|
|
715
|
+
}
|
|
716
|
+
/**
|
|
717
|
+
* Lists all files currently in memory.
|
|
718
|
+
*
|
|
719
|
+
* @param prefix - Optional key prefix to filter by
|
|
720
|
+
* @returns Async iterable of storage items
|
|
721
|
+
*/
|
|
722
|
+
async *list(prefix = "") {
|
|
723
|
+
for (const [key, file] of this.files.entries()) {
|
|
724
|
+
if (key.startsWith(prefix)) {
|
|
725
|
+
yield {
|
|
726
|
+
key,
|
|
727
|
+
isDirectory: false,
|
|
728
|
+
size: file.metadata.size,
|
|
729
|
+
lastModified: file.metadata.lastModified
|
|
730
|
+
};
|
|
731
|
+
}
|
|
732
|
+
}
|
|
733
|
+
}
|
|
734
|
+
/**
|
|
735
|
+
* Retrieves metadata for an in-memory file.
|
|
736
|
+
*
|
|
737
|
+
* @param key - Unique identifier for the file
|
|
738
|
+
* @returns Metadata object or null if not found
|
|
739
|
+
*/
|
|
740
|
+
async getMetadata(key) {
|
|
741
|
+
return this.files.get(key)?.metadata ?? null;
|
|
742
|
+
}
|
|
743
|
+
/**
|
|
744
|
+
* Generates a dummy URL for the in-memory file.
|
|
745
|
+
*
|
|
746
|
+
* @param key - Unique identifier for the file
|
|
747
|
+
* @returns A string starting with /memory/
|
|
748
|
+
*/
|
|
749
|
+
getUrl(key) {
|
|
750
|
+
return `/memory/${key}`;
|
|
751
|
+
}
|
|
752
|
+
/**
|
|
753
|
+
* Stores data from a readable stream in memory.
|
|
754
|
+
*
|
|
755
|
+
* 串流寫入記憶體
|
|
756
|
+
* Reads the entire stream into memory before storing.
|
|
757
|
+
*
|
|
758
|
+
* @param key - Unique identifier for the file
|
|
759
|
+
* @param stream - Readable stream containing the data
|
|
760
|
+
*/
|
|
761
|
+
async putStream(key, stream) {
|
|
762
|
+
const reader = stream.getReader();
|
|
763
|
+
const chunks = [];
|
|
764
|
+
try {
|
|
765
|
+
while (true) {
|
|
766
|
+
const { done, value } = await reader.read();
|
|
767
|
+
if (done) break;
|
|
768
|
+
chunks.push(value);
|
|
769
|
+
}
|
|
770
|
+
const buffer = Buffer.concat(chunks);
|
|
771
|
+
const blob = new Blob([buffer]);
|
|
772
|
+
await this.put(key, blob);
|
|
773
|
+
} catch (error) {
|
|
774
|
+
reader.releaseLock();
|
|
775
|
+
throw new Error(`[MemoryStore] Failed to write stream: ${error}`);
|
|
776
|
+
}
|
|
777
|
+
}
|
|
778
|
+
/**
|
|
779
|
+
* Reads an in-memory file as a readable stream.
|
|
780
|
+
*
|
|
781
|
+
* 串流讀取記憶體檔案
|
|
782
|
+
*
|
|
783
|
+
* @param key - Unique identifier for the file
|
|
784
|
+
* @returns Readable stream of file content, or null if not found
|
|
785
|
+
*/
|
|
786
|
+
async getStream(key) {
|
|
787
|
+
const blob = await this.get(key);
|
|
788
|
+
if (!blob) {
|
|
789
|
+
return null;
|
|
790
|
+
}
|
|
791
|
+
return blob.stream();
|
|
792
|
+
}
|
|
793
|
+
/**
|
|
794
|
+
* Updates custom metadata for an in-memory file.
|
|
795
|
+
*
|
|
796
|
+
* 更新自定義 Metadata
|
|
797
|
+
*
|
|
798
|
+
* @param key - Unique identifier for the file
|
|
799
|
+
* @param metadata - Custom metadata to set
|
|
800
|
+
* @throws {Error} If file does not exist
|
|
801
|
+
*/
|
|
802
|
+
async setMetadata(key, metadata) {
|
|
803
|
+
const file = this.files.get(key);
|
|
804
|
+
if (!file) {
|
|
805
|
+
throw new Error(`[MemoryStore] File not found: ${key}`);
|
|
806
|
+
}
|
|
807
|
+
file.metadata.customMetadata = {
|
|
808
|
+
...file.metadata.customMetadata,
|
|
809
|
+
...metadata
|
|
810
|
+
};
|
|
811
|
+
file.metadata.lastModified = /* @__PURE__ */ new Date();
|
|
812
|
+
}
|
|
813
|
+
/**
|
|
814
|
+
* Lists files with pagination support.
|
|
815
|
+
*
|
|
816
|
+
* 分頁列舉記憶體中的檔案
|
|
817
|
+
* Provides cursor-based pagination for consistent API with other drivers.
|
|
818
|
+
*
|
|
819
|
+
* @param prefix - Key prefix to filter by
|
|
820
|
+
* @param options - Pagination and filtering options
|
|
821
|
+
* @returns Paginated list result
|
|
822
|
+
*/
|
|
823
|
+
async listPaginated(prefix = "", options) {
|
|
824
|
+
const maxResults = options?.maxResults ?? 1e3;
|
|
825
|
+
const cursorKey = options?.cursor;
|
|
826
|
+
const allKeys = Array.from(this.files.keys()).filter((key) => key.startsWith(prefix)).sort();
|
|
827
|
+
let startIndex = 0;
|
|
828
|
+
if (cursorKey) {
|
|
829
|
+
startIndex = allKeys.findIndex((key) => key > cursorKey);
|
|
830
|
+
if (startIndex === -1) {
|
|
831
|
+
return {
|
|
832
|
+
items: [],
|
|
833
|
+
nextCursor: null,
|
|
834
|
+
hasMore: false,
|
|
835
|
+
count: 0
|
|
836
|
+
};
|
|
837
|
+
}
|
|
838
|
+
}
|
|
839
|
+
const endIndex = startIndex + maxResults;
|
|
840
|
+
const pageKeys = allKeys.slice(startIndex, endIndex);
|
|
841
|
+
const hasMore = endIndex < allKeys.length;
|
|
842
|
+
const items = pageKeys.map((key) => {
|
|
843
|
+
const file = this.files.get(key);
|
|
844
|
+
return {
|
|
845
|
+
key,
|
|
846
|
+
isDirectory: false,
|
|
847
|
+
size: file.metadata.size,
|
|
848
|
+
lastModified: file.metadata.lastModified
|
|
849
|
+
};
|
|
850
|
+
});
|
|
851
|
+
const nextCursor = hasMore && items.length > 0 ? items[items.length - 1].key : null;
|
|
852
|
+
return {
|
|
853
|
+
items,
|
|
854
|
+
nextCursor,
|
|
855
|
+
hasMore,
|
|
856
|
+
count: items.length
|
|
857
|
+
};
|
|
858
|
+
}
|
|
859
|
+
};
|
|
860
|
+
|
|
861
|
+
// src/stores/NullStore.ts
|
|
862
|
+
var NullStore = class {
|
|
863
|
+
/**
|
|
864
|
+
* No-op: does not store any data.
|
|
865
|
+
*/
|
|
866
|
+
async put(_key, _data) {
|
|
867
|
+
}
|
|
868
|
+
/**
|
|
869
|
+
* Always returns null.
|
|
870
|
+
*/
|
|
871
|
+
async get(_key) {
|
|
872
|
+
return null;
|
|
873
|
+
}
|
|
874
|
+
/**
|
|
875
|
+
* Always returns false.
|
|
876
|
+
*/
|
|
877
|
+
async delete(_key) {
|
|
878
|
+
return false;
|
|
879
|
+
}
|
|
880
|
+
/**
|
|
881
|
+
* Always returns false.
|
|
882
|
+
*/
|
|
883
|
+
async exists(_key) {
|
|
884
|
+
return false;
|
|
885
|
+
}
|
|
886
|
+
/**
|
|
887
|
+
* No-op: does not copy anything.
|
|
888
|
+
*/
|
|
889
|
+
async copy(_from, _to) {
|
|
890
|
+
}
|
|
891
|
+
/**
|
|
892
|
+
* No-op: does not move anything.
|
|
893
|
+
*/
|
|
894
|
+
async move(_from, _to) {
|
|
895
|
+
}
|
|
896
|
+
/**
|
|
897
|
+
* Always yields nothing.
|
|
898
|
+
*/
|
|
899
|
+
async *list(_prefix) {
|
|
900
|
+
}
|
|
901
|
+
/**
|
|
902
|
+
* Always returns null.
|
|
903
|
+
*/
|
|
904
|
+
async getMetadata(_key) {
|
|
905
|
+
return null;
|
|
906
|
+
}
|
|
907
|
+
/**
|
|
908
|
+
* Generates a dummy URL starting with /null/.
|
|
909
|
+
*/
|
|
910
|
+
getUrl(key) {
|
|
911
|
+
return `/null/${key}`;
|
|
912
|
+
}
|
|
913
|
+
};
|
|
914
|
+
|
|
915
|
+
// src/index.ts
|
|
76
916
|
var OrbitNebula = class {
|
|
77
917
|
constructor(options) {
|
|
78
918
|
this.options = options;
|
|
79
919
|
}
|
|
920
|
+
manager;
|
|
80
921
|
/**
|
|
81
|
-
*
|
|
922
|
+
* Bootstraps the storage service and registers it with PlanetCore.
|
|
82
923
|
*
|
|
83
|
-
*
|
|
84
|
-
*
|
|
924
|
+
* This method initializes the StorageManager, configures the default disk,
|
|
925
|
+
* and registers the storage service in the IoC container and middleware.
|
|
926
|
+
*
|
|
927
|
+
* @param core - The PlanetCore instance to install into
|
|
928
|
+
* @throws {Error} If configuration is missing or default disk cannot be initialized
|
|
85
929
|
*/
|
|
86
930
|
install(core) {
|
|
87
931
|
const config = this.options || core.config.get("storage");
|
|
@@ -91,62 +935,93 @@ var OrbitNebula = class {
|
|
|
91
935
|
);
|
|
92
936
|
}
|
|
93
937
|
const { exposeAs = "storage" } = config;
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
);
|
|
938
|
+
core.logger.info(`[OrbitNebula] Initializing Storage (Exposed as: ${exposeAs})`);
|
|
939
|
+
let defaultDisk = config.default;
|
|
940
|
+
if (!defaultDisk) {
|
|
941
|
+
if (config.local) {
|
|
942
|
+
defaultDisk = "local";
|
|
943
|
+
} else if (config.provider) {
|
|
944
|
+
defaultDisk = "custom";
|
|
945
|
+
} else {
|
|
946
|
+
defaultDisk = "local";
|
|
947
|
+
}
|
|
105
948
|
}
|
|
106
|
-
const
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
get: (key) => provider?.get(key),
|
|
113
|
-
delete: (key) => provider?.delete(key),
|
|
114
|
-
getUrl: (key) => provider?.getUrl(key)
|
|
949
|
+
const managerOptions = {
|
|
950
|
+
default: defaultDisk
|
|
951
|
+
};
|
|
952
|
+
const storageHooks = {
|
|
953
|
+
applyFilter: (h, v, c) => core.hooks.applyFilters(h, v, c),
|
|
954
|
+
doAction: (h, c) => core.hooks.doAction(h, c)
|
|
115
955
|
};
|
|
116
|
-
|
|
956
|
+
const storeFactory = this.createStoreFactory(config);
|
|
957
|
+
this.manager = new StorageManager(storeFactory, managerOptions, storageHooks);
|
|
958
|
+
this.manager.disk();
|
|
959
|
+
core.container.instance(exposeAs, this.manager);
|
|
117
960
|
core.adapter.use("*", async (c, next) => {
|
|
118
|
-
c.set(exposeAs,
|
|
961
|
+
c.set(exposeAs, this.manager);
|
|
119
962
|
await next();
|
|
120
963
|
return void 0;
|
|
121
964
|
});
|
|
122
|
-
core.hooks.doAction("storage:init",
|
|
965
|
+
core.hooks.doAction("storage:init", { manager: this.manager });
|
|
966
|
+
}
|
|
967
|
+
/**
|
|
968
|
+
* Retrieves the initialized StorageManager instance.
|
|
969
|
+
*
|
|
970
|
+
* Use this to access storage operations directly from the orbit instance
|
|
971
|
+
* after it has been installed.
|
|
972
|
+
*
|
|
973
|
+
* @returns The active StorageManager instance
|
|
974
|
+
* @throws {Error} If called before the orbit is installed
|
|
975
|
+
*/
|
|
976
|
+
getStorage() {
|
|
977
|
+
if (!this.manager) {
|
|
978
|
+
throw new Error("[OrbitNebula] StorageManager not initialized. Call install() first.");
|
|
979
|
+
}
|
|
980
|
+
return this.manager;
|
|
981
|
+
}
|
|
982
|
+
createStoreFactory(options) {
|
|
983
|
+
return (name) => {
|
|
984
|
+
const config = options.disks?.[name];
|
|
985
|
+
if (config) {
|
|
986
|
+
if (config.driver === "local") {
|
|
987
|
+
return new LocalStore(config.root, config.baseUrl);
|
|
988
|
+
}
|
|
989
|
+
if (config.driver === "memory") {
|
|
990
|
+
return new MemoryStore();
|
|
991
|
+
}
|
|
992
|
+
if (config.driver === "null") {
|
|
993
|
+
return new NullStore();
|
|
994
|
+
}
|
|
995
|
+
if (config.driver === "custom") {
|
|
996
|
+
return config.store;
|
|
997
|
+
}
|
|
998
|
+
}
|
|
999
|
+
if (name === "local" && options.local) {
|
|
1000
|
+
return new LocalStore(options.local.root, options.local.baseUrl);
|
|
1001
|
+
}
|
|
1002
|
+
if (options.provider) {
|
|
1003
|
+
if (name === "custom" || !options.disks && !options.local) {
|
|
1004
|
+
return options.provider;
|
|
1005
|
+
}
|
|
1006
|
+
}
|
|
1007
|
+
throw new Error(`[OrbitNebula] Driver not configured for disk: ${name}`);
|
|
1008
|
+
};
|
|
123
1009
|
}
|
|
124
1010
|
};
|
|
125
1011
|
function orbitStorage(core, options) {
|
|
126
1012
|
const orbit = new OrbitNebula(options);
|
|
127
1013
|
orbit.install(core);
|
|
128
|
-
|
|
129
|
-
if (!provider && options.local) {
|
|
130
|
-
provider = new LocalStorageProvider(options.local.root, options.local.baseUrl);
|
|
131
|
-
}
|
|
132
|
-
if (!provider) {
|
|
133
|
-
throw new Error("[OrbitNebula] No provider configured.");
|
|
134
|
-
}
|
|
135
|
-
return {
|
|
136
|
-
put: async (key, data) => {
|
|
137
|
-
const finalData = await core.hooks.applyFilters("storage:upload", data, { key });
|
|
138
|
-
await provider?.put(key, finalData);
|
|
139
|
-
await core.hooks.doAction("storage:uploaded", { key });
|
|
140
|
-
},
|
|
141
|
-
get: (key) => provider?.get(key),
|
|
142
|
-
delete: (key) => provider?.delete(key),
|
|
143
|
-
getUrl: (key) => provider?.getUrl(key)
|
|
144
|
-
};
|
|
1014
|
+
return orbit.getStorage();
|
|
145
1015
|
}
|
|
146
1016
|
var OrbitStorage = OrbitNebula;
|
|
147
1017
|
export {
|
|
148
|
-
LocalStorageProvider,
|
|
1018
|
+
LocalStore as LocalStorageProvider,
|
|
1019
|
+
LocalStore,
|
|
1020
|
+
MemoryStore,
|
|
1021
|
+
NullStore,
|
|
149
1022
|
OrbitNebula,
|
|
150
1023
|
OrbitStorage,
|
|
1024
|
+
StorageManager,
|
|
1025
|
+
StorageRepository,
|
|
151
1026
|
orbitStorage as default
|
|
152
1027
|
};
|