@gravito/nebula 3.0.1 → 4.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -20,93 +20,354 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
20
20
  // src/index.ts
21
21
  var index_exports = {};
22
22
  __export(index_exports, {
23
- LocalStorageProvider: () => LocalStorageProvider,
23
+ LocalStorageProvider: () => LocalStore,
24
+ LocalStore: () => LocalStore,
25
+ MemoryStore: () => MemoryStore,
26
+ NullStore: () => NullStore,
24
27
  OrbitNebula: () => OrbitNebula,
25
28
  OrbitStorage: () => OrbitStorage,
29
+ StorageManager: () => StorageManager,
30
+ StorageRepository: () => StorageRepository,
26
31
  default: () => orbitStorage
27
32
  });
28
33
  module.exports = __toCommonJS(index_exports);
34
+
35
+ // src/StorageRepository.ts
36
+ var StorageRepository = class {
37
+ constructor(store, hooks) {
38
+ this.store = store;
39
+ this.hooks = hooks;
40
+ }
41
+ async put(key, data) {
42
+ const finalData = this.hooks ? await this.hooks.applyFilter("storage:upload", data, { key }) : data;
43
+ await this.store.put(key, finalData);
44
+ if (this.hooks) {
45
+ await this.hooks.doAction("storage:uploaded", { key });
46
+ }
47
+ }
48
+ async get(key) {
49
+ const data = await this.store.get(key);
50
+ if (this.hooks) {
51
+ if (data) {
52
+ await this.hooks.doAction("storage:hit", { key });
53
+ } else {
54
+ await this.hooks.doAction("storage:miss", { key });
55
+ }
56
+ }
57
+ return data;
58
+ }
59
+ async delete(key) {
60
+ const deleted = await this.store.delete(key);
61
+ if (deleted && this.hooks) {
62
+ await this.hooks.doAction("storage:deleted", { key });
63
+ }
64
+ return deleted;
65
+ }
66
+ async exists(key) {
67
+ return this.store.exists(key);
68
+ }
69
+ async copy(from, to) {
70
+ await this.store.copy(from, to);
71
+ if (this.hooks) {
72
+ await this.hooks.doAction("storage:copied", { from, to });
73
+ }
74
+ }
75
+ async move(from, to) {
76
+ await this.store.move(from, to);
77
+ if (this.hooks) {
78
+ await this.hooks.doAction("storage:moved", { from, to });
79
+ }
80
+ }
81
+ async *list(prefix) {
82
+ if (!this.store.list) {
83
+ throw new Error("[StorageRepository] This storage driver does not support listing files.");
84
+ }
85
+ yield* this.store.list(prefix);
86
+ }
87
+ async getMetadata(key) {
88
+ return this.store.getMetadata(key);
89
+ }
90
+ getUrl(key) {
91
+ return this.store.getUrl(key);
92
+ }
93
+ async getSignedUrl(key, expiresIn) {
94
+ if (!this.store.getSignedUrl) {
95
+ throw new Error("[StorageRepository] This storage driver does not support signed URLs.");
96
+ }
97
+ return this.store.getSignedUrl(key, expiresIn);
98
+ }
99
+ };
100
+
101
+ // src/StorageManager.ts
102
+ var StorageManager = class {
103
+ constructor(storeFactory, options, hooks) {
104
+ this.storeFactory = storeFactory;
105
+ this.options = options;
106
+ this.hooks = hooks;
107
+ }
108
+ stores = /* @__PURE__ */ new Map();
109
+ repositories = /* @__PURE__ */ new Map();
110
+ disk(name) {
111
+ const diskName = name ?? this.options.default;
112
+ if (!this.repositories.has(diskName)) {
113
+ const store = this.resolveStore(diskName);
114
+ this.repositories.set(diskName, new StorageRepository(store, this.hooks));
115
+ }
116
+ return this.repositories.get(diskName);
117
+ }
118
+ resolveStore(name) {
119
+ if (!this.stores.has(name)) {
120
+ this.stores.set(name, this.storeFactory(name));
121
+ }
122
+ return this.stores.get(name);
123
+ }
124
+ put(key, data) {
125
+ return this.disk().put(key, data);
126
+ }
127
+ get(key) {
128
+ return this.disk().get(key);
129
+ }
130
+ delete(key) {
131
+ return this.disk().delete(key);
132
+ }
133
+ exists(key) {
134
+ return this.disk().exists(key);
135
+ }
136
+ copy(from, to) {
137
+ return this.disk().copy(from, to);
138
+ }
139
+ move(from, to) {
140
+ return this.disk().move(from, to);
141
+ }
142
+ list(prefix) {
143
+ return this.disk().list(prefix);
144
+ }
145
+ getMetadata(key) {
146
+ return this.disk().getMetadata(key);
147
+ }
148
+ getUrl(key) {
149
+ return this.disk().getUrl(key);
150
+ }
151
+ getSignedUrl(key, expiresIn) {
152
+ return this.disk().getSignedUrl(key, expiresIn);
153
+ }
154
+ };
155
+
156
+ // src/stores/LocalStore.ts
29
157
  var import_promises = require("fs/promises");
30
158
  var import_node_path = require("path");
31
159
  var import_core = require("@gravito/core");
32
- var LocalStorageProvider = class {
33
- rootDir;
34
- baseUrl;
35
- runtime = (0, import_core.getRuntimeAdapter)();
36
- /**
37
- * Create a new LocalStorageProvider.
38
- *
39
- * @param rootDir - The absolute path to the local storage directory.
40
- * @param baseUrl - The public URL path for accessing stored files (e.g., '/storage').
41
- */
160
+ var LocalStore = class {
42
161
  constructor(rootDir, baseUrl = "/storage") {
43
162
  this.rootDir = rootDir;
44
163
  this.baseUrl = baseUrl;
45
164
  }
46
- /**
47
- * Write data to the local disk.
48
- */
165
+ runtime = (0, import_core.getRuntimeAdapter)();
49
166
  async put(key, data) {
50
- const path = this.resolveKeyPath(key);
51
- const dir = path.substring(0, path.lastIndexOf("/"));
52
- if (dir && dir !== this.rootDir) {
53
- await (0, import_promises.mkdir)(dir, { recursive: true });
54
- }
167
+ const path = this.resolvePath(key);
168
+ await this.ensureDirectory(path);
55
169
  await this.runtime.writeFile(path, data);
56
170
  }
57
- /**
58
- * Read data from the local disk.
59
- */
60
171
  async get(key) {
61
- const path = this.resolveKeyPath(key);
62
- if (!await this.runtime.exists(path)) {
172
+ if (!await this.exists(key)) {
63
173
  return null;
64
174
  }
65
- return await this.runtime.readFileAsBlob(path);
175
+ const path = this.resolvePath(key);
176
+ return this.runtime.readFileAsBlob(path);
66
177
  }
67
- /**
68
- * Delete a file from the local disk.
69
- */
70
178
  async delete(key) {
71
- await this.runtime.deleteFile(this.resolveKeyPath(key));
179
+ if (!await this.exists(key)) {
180
+ return false;
181
+ }
182
+ const path = this.resolvePath(key);
183
+ await this.runtime.deleteFile(path);
184
+ return true;
185
+ }
186
+ async exists(key) {
187
+ const path = this.resolvePath(key);
188
+ return this.runtime.exists(path);
189
+ }
190
+ async copy(from, to) {
191
+ const data = await this.get(from);
192
+ if (!data) {
193
+ throw new Error(`[LocalStore] Source file not found: ${from}`);
194
+ }
195
+ await this.put(to, data);
196
+ }
197
+ async move(from, to) {
198
+ await this.copy(from, to);
199
+ await this.delete(from);
200
+ }
201
+ list(prefix = "") {
202
+ throw new Error(
203
+ "[LocalStore] list() is not yet implemented. Requires RuntimeAdapter.readDir() support in @gravito/core."
204
+ );
205
+ }
206
+ async getMetadata(key) {
207
+ if (!await this.exists(key)) {
208
+ return null;
209
+ }
210
+ const path = this.resolvePath(key);
211
+ const stat = await this.runtime.stat(path);
212
+ return {
213
+ key,
214
+ size: stat.size,
215
+ mimeType: this.guessMimeType(key),
216
+ lastModified: /* @__PURE__ */ new Date()
217
+ };
72
218
  }
73
- /**
74
- * Resolve the public URL for a locally stored file.
75
- */
76
219
  getUrl(key) {
77
220
  const safeKey = this.normalizeKey(key);
78
221
  return `${this.baseUrl}/${safeKey}`;
79
222
  }
80
223
  normalizeKey(key) {
81
224
  if (!key || key.includes("\0")) {
82
- throw new Error("Invalid storage key.");
225
+ throw new Error("[LocalStore] Invalid storage key: empty or contains null byte.");
83
226
  }
84
227
  const normalized = (0, import_node_path.normalize)(key).replace(/^[/\\]+/, "");
85
- if (normalized === "." || normalized === ".." || normalized.startsWith(`..${import_node_path.sep}`) || (0, import_node_path.isAbsolute)(normalized)) {
86
- throw new Error("Invalid storage key.");
228
+ if (normalized === "." || normalized === ".." || normalized.startsWith(`..${import_node_path.sep}`) || normalized.startsWith(`.${import_node_path.sep}`) || (0, import_node_path.isAbsolute)(normalized)) {
229
+ throw new Error("[LocalStore] Invalid storage key: path traversal attempt.");
87
230
  }
88
231
  return normalized.replace(/\\/g, "/");
89
232
  }
90
- resolveKeyPath(key) {
233
+ resolvePath(key) {
91
234
  const normalized = this.normalizeKey(key);
92
235
  const root = (0, import_node_path.resolve)(this.rootDir);
93
236
  const resolved = (0, import_node_path.resolve)(root, normalized);
94
237
  const rootPrefix = root.endsWith(import_node_path.sep) ? root : `${root}${import_node_path.sep}`;
95
238
  if (!resolved.startsWith(rootPrefix) && resolved !== root) {
96
- throw new Error("Invalid storage key.");
239
+ throw new Error("[LocalStore] Invalid storage key: resolved path outside root.");
97
240
  }
98
241
  return resolved;
99
242
  }
243
+ async ensureDirectory(filePath) {
244
+ const dir = filePath.substring(0, filePath.lastIndexOf(import_node_path.sep));
245
+ if (dir && dir !== this.rootDir) {
246
+ await (0, import_promises.mkdir)(dir, { recursive: true });
247
+ }
248
+ }
249
+ guessMimeType(key) {
250
+ const ext = key.split(".").pop()?.toLowerCase();
251
+ const mimeTypes = {
252
+ txt: "text/plain",
253
+ html: "text/html",
254
+ css: "text/css",
255
+ js: "text/javascript",
256
+ json: "application/json",
257
+ png: "image/png",
258
+ jpg: "image/jpeg",
259
+ jpeg: "image/jpeg",
260
+ gif: "image/gif",
261
+ svg: "image/svg+xml",
262
+ pdf: "application/pdf",
263
+ zip: "application/zip"
264
+ };
265
+ return mimeTypes[ext ?? ""] ?? "application/octet-stream";
266
+ }
267
+ };
268
+
269
+ // src/stores/MemoryStore.ts
270
+ var MemoryStore = class {
271
+ files = /* @__PURE__ */ new Map();
272
+ async put(key, data) {
273
+ let blob;
274
+ if (data instanceof Blob) {
275
+ blob = data;
276
+ } else if (typeof data === "string") {
277
+ blob = new Blob([data]);
278
+ } else {
279
+ blob = new Blob([new Uint8Array(data)]);
280
+ }
281
+ this.files.set(key, {
282
+ data: blob,
283
+ metadata: {
284
+ key,
285
+ size: blob.size,
286
+ mimeType: blob.type || "application/octet-stream",
287
+ lastModified: /* @__PURE__ */ new Date()
288
+ }
289
+ });
290
+ }
291
+ async get(key) {
292
+ return this.files.get(key)?.data ?? null;
293
+ }
294
+ async delete(key) {
295
+ return this.files.delete(key);
296
+ }
297
+ async exists(key) {
298
+ return this.files.has(key);
299
+ }
300
+ async copy(from, to) {
301
+ const file = this.files.get(from);
302
+ if (!file) {
303
+ throw new Error(`[MemoryStore] Source file not found: ${from}`);
304
+ }
305
+ this.files.set(to, {
306
+ data: file.data,
307
+ metadata: { ...file.metadata, key: to, lastModified: /* @__PURE__ */ new Date() }
308
+ });
309
+ }
310
+ async move(from, to) {
311
+ await this.copy(from, to);
312
+ await this.delete(from);
313
+ }
314
+ async *list(prefix = "") {
315
+ for (const [key, file] of this.files.entries()) {
316
+ if (key.startsWith(prefix)) {
317
+ yield {
318
+ key,
319
+ isDirectory: false,
320
+ size: file.metadata.size,
321
+ lastModified: file.metadata.lastModified
322
+ };
323
+ }
324
+ }
325
+ }
326
+ async getMetadata(key) {
327
+ return this.files.get(key)?.metadata ?? null;
328
+ }
329
+ getUrl(key) {
330
+ return `/memory/${key}`;
331
+ }
332
+ };
333
+
334
+ // src/stores/NullStore.ts
335
+ var NullStore = class {
336
+ async put(_key, _data) {
337
+ }
338
+ async get(_key) {
339
+ return null;
340
+ }
341
+ async delete(_key) {
342
+ return false;
343
+ }
344
+ async exists(_key) {
345
+ return false;
346
+ }
347
+ async copy(_from, _to) {
348
+ }
349
+ async move(_from, _to) {
350
+ }
351
+ async *list(_prefix) {
352
+ }
353
+ async getMetadata(_key) {
354
+ return null;
355
+ }
356
+ getUrl(key) {
357
+ return `/null/${key}`;
358
+ }
100
359
  };
360
+
361
+ // src/index.ts
101
362
  var OrbitNebula = class {
102
363
  constructor(options) {
103
364
  this.options = options;
104
365
  }
366
+ manager;
105
367
  /**
106
368
  * Install storage service into PlanetCore.
107
369
  *
108
370
  * @param core - The PlanetCore instance.
109
- * @throws {Error} If configuration or provider is missing.
110
371
  */
111
372
  install(core) {
112
373
  const config = this.options || core.config.get("storage");
@@ -116,62 +377,86 @@ var OrbitNebula = class {
116
377
  );
117
378
  }
118
379
  const { exposeAs = "storage" } = config;
119
- const logger = core.logger;
120
- logger.info(`[OrbitNebula] Initializing Storage (Exposed as: ${exposeAs})`);
121
- let provider = config.provider;
122
- if (!provider && config.local) {
123
- logger.info(`[OrbitNebula] Using LocalStorageProvider at ${config.local.root}`);
124
- provider = new LocalStorageProvider(config.local.root, config.local.baseUrl);
125
- }
126
- if (!provider) {
127
- throw new Error(
128
- "[OrbitNebula] No provider configured. Please provide a provider instance or local configuration."
129
- );
380
+ core.logger.info(`[OrbitNebula] Initializing Storage (Exposed as: ${exposeAs})`);
381
+ let defaultDisk = config.default;
382
+ if (!defaultDisk) {
383
+ if (config.local) defaultDisk = "local";
384
+ else if (config.provider) defaultDisk = "custom";
385
+ else defaultDisk = "local";
130
386
  }
131
- const storageService = {
132
- put: async (key, data) => {
133
- const finalData = await core.hooks.applyFilters("storage:upload", data, { key });
134
- await provider?.put(key, finalData);
135
- await core.hooks.doAction("storage:uploaded", { key });
136
- },
137
- get: (key) => provider?.get(key),
138
- delete: (key) => provider?.delete(key),
139
- getUrl: (key) => provider?.getUrl(key)
387
+ const managerOptions = {
388
+ default: defaultDisk
389
+ };
390
+ const storageHooks = {
391
+ applyFilter: (h, v, c) => core.hooks.applyFilters(h, v, c),
392
+ doAction: (h, c) => core.hooks.doAction(h, c)
140
393
  };
141
- core.container.instance(exposeAs, storageService);
394
+ const storeFactory = this.createStoreFactory(config);
395
+ this.manager = new StorageManager(storeFactory, managerOptions, storageHooks);
396
+ this.manager.disk();
397
+ core.container.instance(exposeAs, this.manager);
142
398
  core.adapter.use("*", async (c, next) => {
143
- c.set(exposeAs, storageService);
399
+ c.set(exposeAs, this.manager);
144
400
  await next();
145
401
  return void 0;
146
402
  });
147
- core.hooks.doAction("storage:init", storageService);
403
+ core.hooks.doAction("storage:init", { manager: this.manager });
404
+ }
405
+ /**
406
+ * Get the installed StorageManager instance.
407
+ *
408
+ * @returns The StorageManager instance.
409
+ * @throws {Error} If not installed.
410
+ */
411
+ getStorage() {
412
+ if (!this.manager) {
413
+ throw new Error("[OrbitNebula] StorageManager not initialized. Call install() first.");
414
+ }
415
+ return this.manager;
416
+ }
417
+ createStoreFactory(options) {
418
+ return (name) => {
419
+ const config = options.disks?.[name];
420
+ if (config) {
421
+ if (config.driver === "local") {
422
+ return new LocalStore(config.root, config.baseUrl);
423
+ }
424
+ if (config.driver === "memory") {
425
+ return new MemoryStore();
426
+ }
427
+ if (config.driver === "null") {
428
+ return new NullStore();
429
+ }
430
+ if (config.driver === "custom") {
431
+ return config.store;
432
+ }
433
+ }
434
+ if (name === "local" && options.local) {
435
+ return new LocalStore(options.local.root, options.local.baseUrl);
436
+ }
437
+ if (options.provider) {
438
+ if (name === "custom" || !options.disks && !options.local) {
439
+ return options.provider;
440
+ }
441
+ }
442
+ throw new Error(`[OrbitNebula] Driver not configured for disk: ${name}`);
443
+ };
148
444
  }
149
445
  };
150
446
  function orbitStorage(core, options) {
151
447
  const orbit = new OrbitNebula(options);
152
448
  orbit.install(core);
153
- let provider = options.provider;
154
- if (!provider && options.local) {
155
- provider = new LocalStorageProvider(options.local.root, options.local.baseUrl);
156
- }
157
- if (!provider) {
158
- throw new Error("[OrbitNebula] No provider configured.");
159
- }
160
- return {
161
- put: async (key, data) => {
162
- const finalData = await core.hooks.applyFilters("storage:upload", data, { key });
163
- await provider?.put(key, finalData);
164
- await core.hooks.doAction("storage:uploaded", { key });
165
- },
166
- get: (key) => provider?.get(key),
167
- delete: (key) => provider?.delete(key),
168
- getUrl: (key) => provider?.getUrl(key)
169
- };
449
+ return orbit.getStorage();
170
450
  }
171
451
  var OrbitStorage = OrbitNebula;
172
452
  // Annotate the CommonJS export names for ESM import in node:
173
453
  0 && (module.exports = {
174
454
  LocalStorageProvider,
455
+ LocalStore,
456
+ MemoryStore,
457
+ NullStore,
175
458
  OrbitNebula,
176
- OrbitStorage
459
+ OrbitStorage,
460
+ StorageManager,
461
+ StorageRepository
177
462
  });