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