firebase-storage-kit 1.0.0 → 1.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -23,10 +23,9 @@ bun add firebase-storage-kit
23
23
  ```ts
24
24
  import { getStorage } from "firebase/storage";
25
25
  import { StorageManager } from "firebase-storage-kit";
26
- import { FirebaseStorageProvider } from "firebase-storage-kit/firebase";
27
26
 
28
27
  const storage = getStorage(app);
29
- const manager = new StorageManager(new FirebaseStorageProvider(storage));
28
+ const manager = new StorageManager(storage);
30
29
 
31
30
  const handle = manager.uploadFile(file, { path: `uploads/${file.name}` });
32
31
 
package/dist/index.d.ts CHANGED
@@ -1,5 +1,4 @@
1
- import { S as StorageProvider, F as FileMetadata, U as UploadOptions } from './provider-B89iZO4q.js';
2
- export { P as ProviderUploadCallbacks, a as ProviderUploadTask } from './provider-B89iZO4q.js';
1
+ import { FirebaseStorage } from 'firebase/storage';
3
2
 
4
3
  /** The current state of the storage manager. */
5
4
  interface StorageState {
@@ -10,6 +9,7 @@ type UploadStatus = "queued" | "uploading" | "paused" | "success" | "error" | "c
10
9
  interface UploadItem {
11
10
  id: string;
12
11
  file: File;
12
+ path: string;
13
13
  progress: number;
14
14
  bytesTransferred: number;
15
15
  totalBytes: number;
@@ -108,6 +108,8 @@ declare class BatchHandle extends Emitter<BatchHandleEvents> {
108
108
  private onChange;
109
109
  private running;
110
110
  private nextIdx;
111
+ /** Indices that were started via {@link fillSlots} and still hold a concurrency slot. */
112
+ private activeSlots;
111
113
  private settledCount;
112
114
  private succeededCount;
113
115
  private failedCount;
@@ -131,6 +133,38 @@ declare class BatchHandle extends Emitter<BatchHandleEvents> {
131
133
  private handleChildSettled;
132
134
  }
133
135
 
136
+ interface ProviderUploadTask {
137
+ cancel(): void;
138
+ pause?(): void;
139
+ resume?(): void;
140
+ }
141
+ interface ProviderUploadCallbacks {
142
+ onProgress: (bytesTransferred: number, totalBytes: number) => void;
143
+ onError: (error: Error) => void;
144
+ onSuccess: (downloadURL: string) => void;
145
+ }
146
+ interface UploadOptions {
147
+ /** Object path in the bucket (Firebase), e.g. `uploads/photo.jpg`. */
148
+ path: string;
149
+ }
150
+
151
+ interface FileMetadata {
152
+ path: string;
153
+ size: number;
154
+ contentType?: string;
155
+ createdAt: Date;
156
+ updatedAt: Date;
157
+ customMetadata?: Record<string, string>;
158
+ }
159
+
160
+ interface StorageProvider {
161
+ upload(file: File, options: UploadOptions, callbacks: ProviderUploadCallbacks): ProviderUploadTask;
162
+ exists(path: string): Promise<boolean>;
163
+ getMetadata(path: string): Promise<FileMetadata>;
164
+ getDownloadURL(path: string): Promise<string>;
165
+ delete(path: string): Promise<void>;
166
+ }
167
+
134
168
  /**
135
169
  * Manages file uploads and storage queries.
136
170
  *
@@ -138,7 +172,7 @@ declare class BatchHandle extends Emitter<BatchHandleEvents> {
138
172
  * that {@link StorageManager.getState} returns whenever anything changes (call the returned function to stop listening).
139
173
  *
140
174
  */
141
- declare class StorageManager {
175
+ declare class StorageManager$1 {
142
176
  private provider;
143
177
  private uploadHandles;
144
178
  private batches;
@@ -178,4 +212,9 @@ declare class StorageManager {
178
212
  private notifyChange;
179
213
  }
180
214
 
181
- export { BatchHandle, type BatchHandleEvents, type BatchHandleInit, type BatchOptions, FileMetadata, StorageManager, type StorageState, type UploadBatch, UploadHandle, type UploadHandleEvents, type UploadItem, UploadOptions, type UploadStatus };
215
+ /** Firebase Storage uploads and file helpers. Pass a `FirebaseStorage` instance from `getStorage(app)`. */
216
+ declare class StorageManager extends StorageManager$1 {
217
+ constructor(storage: FirebaseStorage);
218
+ }
219
+
220
+ export { BatchHandle, type BatchHandleEvents, type BatchHandleInit, type BatchOptions, type FileMetadata, type ProviderUploadCallbacks, type ProviderUploadTask, StorageManager, type StorageState, type UploadBatch, UploadHandle, type UploadHandleEvents, type UploadItem, type UploadOptions, type UploadStatus };
package/dist/index.js CHANGED
@@ -1,18 +1,20 @@
1
+ import { ref, uploadBytesResumable, getDownloadURL, getMetadata, deleteObject } from 'firebase/storage';
2
+
1
3
  // src/core/emitter.ts
2
4
  var Emitter = class {
3
- listeners = /* @__PURE__ */ new Map();
5
+ listeners = {};
4
6
  /**
5
7
  * @param event The event to listen for.
6
8
  * @param listener The function to call when the event is emitted.
7
9
  * @returns A function to unsubscribe from the event.
8
10
  */
9
11
  on(event, listener) {
10
- if (!this.listeners.has(event)) {
11
- this.listeners.set(event, /* @__PURE__ */ new Set());
12
+ if (!this.listeners[event]) {
13
+ this.listeners[event] = /* @__PURE__ */ new Set();
12
14
  }
13
- this.listeners.get(event)?.add(listener);
15
+ this.listeners[event].add(listener);
14
16
  return () => {
15
- this.listeners.get(event)?.delete(listener);
17
+ this.listeners[event]?.delete(listener);
16
18
  };
17
19
  }
18
20
  /**
@@ -20,7 +22,7 @@ var Emitter = class {
20
22
  * @param payload The payload to emit.
21
23
  */
22
24
  emit(event, payload) {
23
- const listeners = this.listeners.get(event);
25
+ const listeners = this.listeners[event];
24
26
  if (!listeners) {
25
27
  return;
26
28
  }
@@ -40,6 +42,8 @@ var BatchHandle = class extends Emitter {
40
42
  onChange;
41
43
  running = 0;
42
44
  nextIdx = 0;
45
+ /** Indices that were started via {@link fillSlots} and still hold a concurrency slot. */
46
+ activeSlots = /* @__PURE__ */ new Set();
43
47
  settledCount = 0;
44
48
  succeededCount = 0;
45
49
  failedCount = 0;
@@ -134,8 +138,10 @@ var BatchHandle = class extends Emitter {
134
138
  }
135
139
  fillSlots() {
136
140
  while (!this.canceled && !this.paused && !this.failedFast && this.running < this.concurrency && this.nextIdx < this.uploads.length) {
137
- const handle = this.uploads[this.nextIdx++];
138
- if (!handle) continue;
141
+ const idx = this.nextIdx++;
142
+ const handle = this.uploads[idx];
143
+ if (!handle || handle.upload.status !== "queued") continue;
144
+ this.activeSlots.add(idx);
139
145
  this.running++;
140
146
  this.startNext(handle);
141
147
  }
@@ -148,7 +154,9 @@ var BatchHandle = class extends Emitter {
148
154
  }
149
155
  handleChildSettled(idx, upload, kind) {
150
156
  this.updateAggregate(idx, upload);
151
- this.running = Math.max(0, this.running - 1);
157
+ if (this.activeSlots.delete(idx)) {
158
+ this.running = Math.max(0, this.running - 1);
159
+ }
152
160
  this.settledCount++;
153
161
  if (kind === "success") {
154
162
  this.succeededCount++;
@@ -310,7 +318,7 @@ var StorageManager = class {
310
318
  * @returns An {@link UploadHandle} to control the upload: `pause`, `resume`, `cancel`, and listen for progress with `.on`.
311
319
  */
312
320
  uploadFile(file, options) {
313
- const handle = this.createHandle(file);
321
+ const handle = this.createHandle(file, { path: options.path });
314
322
  this.uploadHandles.push(handle);
315
323
  this.notifyChange();
316
324
  this.startUpload(handle, options);
@@ -328,12 +336,9 @@ var StorageManager = class {
328
336
  */
329
337
  uploadFiles(files, optionsFor, batchOptions = {}) {
330
338
  const id = crypto.randomUUID();
331
- const handles = files.map((file) => this.createHandle(file, id));
332
- const optionsByUploadId = /* @__PURE__ */ new Map();
333
- handles.forEach((h, i) => {
334
- const file = files[i];
335
- if (!file) return;
336
- optionsByUploadId.set(h.upload.id, optionsFor(file, i));
339
+ const handles = files.map((file, i) => {
340
+ const options = optionsFor(file, i);
341
+ return this.createHandle(file, { batchId: id, path: options.path });
337
342
  });
338
343
  this.uploadHandles.push(...handles);
339
344
  const batch = new BatchHandle({
@@ -341,9 +346,7 @@ var StorageManager = class {
341
346
  uploads: handles,
342
347
  options: batchOptions,
343
348
  startNext: (handle) => {
344
- const opts = optionsByUploadId.get(handle.upload.id);
345
- if (!opts) return;
346
- this.startUpload(handle, opts);
349
+ this.startUpload(handle, { path: handle.upload.path });
347
350
  },
348
351
  onChange: () => this.notifyChange()
349
352
  });
@@ -352,15 +355,16 @@ var StorageManager = class {
352
355
  batch._start();
353
356
  return batch;
354
357
  }
355
- createHandle(file, batchId) {
358
+ createHandle(file, options) {
356
359
  const upload = {
357
360
  id: crypto.randomUUID(),
358
361
  file,
362
+ path: options.path,
359
363
  progress: 0,
360
364
  bytesTransferred: 0,
361
365
  totalBytes: file.size,
362
366
  status: "queued",
363
- ...batchId !== void 0 ? { batchId } : {}
367
+ ...options.batchId !== void 0 ? { batchId: options.batchId } : {}
364
368
  };
365
369
  return new UploadHandle(upload, () => this.notifyChange());
366
370
  }
@@ -382,7 +386,83 @@ var StorageManager = class {
382
386
  }
383
387
  }
384
388
  };
389
+ var FirebaseStorageProvider = class {
390
+ constructor(storage) {
391
+ this.storage = storage;
392
+ }
393
+ storage;
394
+ upload(file, options, callbacks) {
395
+ const storageRef = ref(this.storage, options.path);
396
+ const task = uploadBytesResumable(storageRef, file);
397
+ task.on(
398
+ "state_changed",
399
+ (snapshot) => {
400
+ callbacks.onProgress(
401
+ snapshot.bytesTransferred,
402
+ snapshot.totalBytes
403
+ );
404
+ },
405
+ (error) => {
406
+ callbacks.onError(error);
407
+ },
408
+ async () => {
409
+ try {
410
+ const downloadURL = await getDownloadURL(storageRef);
411
+ callbacks.onSuccess(downloadURL);
412
+ } catch (error) {
413
+ callbacks.onError(error);
414
+ }
415
+ }
416
+ );
417
+ return {
418
+ cancel: () => {
419
+ task.cancel();
420
+ },
421
+ pause: () => {
422
+ task.pause();
423
+ },
424
+ resume: () => {
425
+ task.resume();
426
+ }
427
+ };
428
+ }
429
+ async exists(path) {
430
+ try {
431
+ await getMetadata(ref(this.storage, path));
432
+ return true;
433
+ } catch (err) {
434
+ if (err.code === "storage/object-not-found") {
435
+ return false;
436
+ }
437
+ throw err;
438
+ }
439
+ }
440
+ async getMetadata(path) {
441
+ const meta = await getMetadata(ref(this.storage, path));
442
+ return {
443
+ path,
444
+ size: meta.size,
445
+ contentType: meta.contentType,
446
+ createdAt: new Date(meta.timeCreated),
447
+ updatedAt: new Date(meta.updated),
448
+ customMetadata: meta.customMetadata
449
+ };
450
+ }
451
+ getDownloadURL(path) {
452
+ return getDownloadURL(ref(this.storage, path));
453
+ }
454
+ async delete(path) {
455
+ await deleteObject(ref(this.storage, path));
456
+ }
457
+ };
458
+
459
+ // src/firebase-storage-manager.ts
460
+ var StorageManager2 = class extends StorageManager {
461
+ constructor(storage) {
462
+ super(new FirebaseStorageProvider(storage));
463
+ }
464
+ };
385
465
 
386
- export { BatchHandle, StorageManager, UploadHandle };
466
+ export { BatchHandle, StorageManager2 as StorageManager, UploadHandle };
387
467
  //# sourceMappingURL=index.js.map
388
468
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/core/emitter.ts","../src/core/batch-handle.ts","../src/core/upload-handle.ts","../src/core/storage-manager.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["type Listener<TPayload> = (payload: TPayload) => void;\n\nexport class Emitter<TEvents extends Record<string, unknown>> {\n private listeners = new Map<keyof TEvents, Set<Listener<any>>>();\n\n /**\n * @param event The event to listen for.\n * @param listener The function to call when the event is emitted.\n * @returns A function to unsubscribe from the event.\n */\n on<K extends keyof TEvents>(event: K, listener: Listener<TEvents[K]>) {\n if (!this.listeners.has(event)) {\n this.listeners.set(event, new Set());\n }\n\n this.listeners.get(event)?.add(listener);\n\n return () => {\n this.listeners.get(event)?.delete(listener);\n };\n }\n\n /**\n * @param event The event to emit.\n * @param payload The payload to emit.\n */\n protected emit<K extends keyof TEvents>(event: K, payload: TEvents[K]) {\n const listeners = this.listeners.get(event);\n\n if (!listeners) {\n return;\n }\n\n for (const listener of listeners) {\n listener(payload);\n }\n }\n}\n","import type { UploadBatch, UploadItem } from \"../types/upload\";\nimport { Emitter } from \"./emitter\";\nimport type { UploadHandle } from \"./upload-handle\";\n\n/**\n * Events on a batch from {@link StorageManager.uploadFiles}. `progress` / `change` carry a {@link UploadBatch} snapshot.\n * `uploadSuccess` and `uploadError` fire once per child. When all children are done: `success` (normal end) or `error` (only if `continueOnError` was `false` and one failed).\n */\nexport type BatchHandleEvents = {\n progress: UploadBatch;\n uploadSuccess: UploadItem;\n uploadError: UploadItem;\n success: UploadBatch;\n error: UploadBatch;\n change: UploadBatch;\n};\n\n/** Settings for {@link StorageManager.uploadFiles} (third argument). */\nexport interface BatchOptions {\n /** How many files upload at the same time. Default is 3. */\n concurrency?: number;\n /**\n * `true` (default): if one file fails, the others keep going. When every file has finished, the batch fires `success` — look at each upload for errors.\n *\n * `false`: the first failure stops the other files and the batch fires `error`.\n */\n continueOnError?: boolean;\n}\n\nexport interface BatchHandleInit {\n id: string;\n uploads: UploadHandle[];\n options: BatchOptions;\n startNext: (handle: UploadHandle) => void;\n onChange: () => void;\n}\n\n/**\n * A group of files from {@link StorageManager.uploadFiles}. Use `.on(...)` like a single {@link UploadHandle}, plus `snapshot()` for totals.\n */\nexport class BatchHandle extends Emitter<BatchHandleEvents> {\n public readonly id: string;\n public readonly uploads: UploadHandle[];\n\n private concurrency: number;\n private continueOnError: boolean;\n private startNext: (handle: UploadHandle) => void;\n private onChange: () => void;\n\n private running = 0;\n private nextIdx = 0;\n private settledCount = 0;\n private succeededCount = 0;\n private failedCount = 0;\n private failedFast = false;\n private canceled = false;\n private paused = false;\n private terminalEmitted = false;\n\n // Stable view of UploadItems; the items themselves mutate in place, but the\n // array reference never changes, so we allocate it once instead of rebuilding\n // it on every snapshot.\n private readonly uploadsView: UploadItem[];\n\n // Running aggregates updated incrementally on each child event so snapshot()\n // is O(1) instead of O(n) per progress tick.\n private aggregateBytesTransferred = 0;\n private aggregateTotalBytes = 0;\n private readonly lastBytes: number[];\n private readonly lastTotal: number[];\n\n constructor(init: BatchHandleInit) {\n super();\n this.id = init.id;\n this.uploads = init.uploads;\n this.concurrency = Math.max(1, init.options.concurrency ?? 3);\n this.continueOnError = init.options.continueOnError ?? true;\n this.startNext = init.startNext;\n this.onChange = init.onChange;\n\n this.uploadsView = init.uploads.map((h) => h.upload);\n this.lastBytes = new Array(init.uploads.length).fill(0);\n this.lastTotal = new Array(init.uploads.length).fill(0);\n for (let i = 0; i < this.uploadsView.length; i++) {\n const u = this.uploadsView[i]!;\n this.lastBytes[i] = u.bytesTransferred;\n this.lastTotal[i] = u.totalBytes;\n this.aggregateBytesTransferred += u.bytesTransferred;\n this.aggregateTotalBytes += u.totalBytes;\n }\n\n this.uploads.forEach((child, idx) => {\n child.on(\"progress\", (u) => this.handleChildProgress(idx, u));\n child.on(\"success\", (u) => this.handleChildSettled(idx, u, \"success\"));\n child.on(\"error\", (u) => this.handleChildSettled(idx, u, \"error\"));\n child.on(\"canceled\", (u) => this.handleChildSettled(idx, u, \"canceled\"));\n });\n }\n\n /** @internal */\n _start(): void {\n this.fillSlots();\n }\n\n cancel(): void {\n if (this.canceled || this.terminalEmitted) return;\n this.canceled = true;\n for (const h of this.uploads) {\n const s = h.upload.status;\n if (s === \"queued\" || s === \"uploading\" || s === \"paused\") {\n h.cancel();\n }\n }\n this.onChange();\n }\n\n pause(): void {\n if (this.paused || this.canceled || this.terminalEmitted) return;\n this.paused = true;\n for (const h of this.uploads) {\n if (h.upload.status === \"uploading\") h.pause();\n }\n this.onChange();\n }\n\n resume(): void {\n if (!this.paused || this.canceled || this.terminalEmitted) return;\n this.paused = false;\n for (const h of this.uploads) {\n if (h.upload.status === \"paused\") h.resume();\n }\n this.fillSlots();\n this.onChange();\n }\n\n snapshot(): UploadBatch {\n const totalProgress =\n this.aggregateTotalBytes > 0\n ? (this.aggregateBytesTransferred / this.aggregateTotalBytes) * 100\n : 0;\n return {\n id: this.id,\n uploads: this.uploadsView,\n totalProgress,\n completedCount: this.succeededCount,\n failedCount: this.failedCount,\n };\n }\n\n private updateAggregate(idx: number, u: UploadItem): void {\n const prevBytes = this.lastBytes[idx] ?? 0;\n const prevTotal = this.lastTotal[idx] ?? 0;\n this.aggregateBytesTransferred += u.bytesTransferred - prevBytes;\n this.aggregateTotalBytes += u.totalBytes - prevTotal;\n this.lastBytes[idx] = u.bytesTransferred;\n this.lastTotal[idx] = u.totalBytes;\n }\n\n private fillSlots(): void {\n while (\n !this.canceled &&\n !this.paused &&\n !this.failedFast &&\n this.running < this.concurrency &&\n this.nextIdx < this.uploads.length\n ) {\n const handle = this.uploads[this.nextIdx++];\n if (!handle) continue;\n this.running++;\n this.startNext(handle);\n }\n }\n\n private handleChildProgress(idx: number, upload: UploadItem): void {\n this.updateAggregate(idx, upload);\n const snap = this.snapshot();\n this.emit(\"progress\", snap);\n this.emit(\"change\", snap);\n }\n\n private handleChildSettled(\n idx: number,\n upload: UploadItem,\n kind: \"success\" | \"error\" | \"canceled\",\n ): void {\n this.updateAggregate(idx, upload);\n this.running = Math.max(0, this.running - 1);\n this.settledCount++;\n\n if (kind === \"success\") {\n this.succeededCount++;\n this.emit(\"uploadSuccess\", upload);\n } else if (kind === \"error\") {\n this.failedCount++;\n this.emit(\"uploadError\", upload);\n }\n\n const snap = this.snapshot();\n this.emit(\"change\", snap);\n\n if (\n kind === \"error\" &&\n !this.continueOnError &&\n !this.failedFast &&\n !this.canceled\n ) {\n this.failedFast = true;\n for (const h of this.uploads) {\n if (h.upload.id === upload.id) continue;\n const s = h.upload.status;\n if (s === \"queued\" || s === \"uploading\" || s === \"paused\") {\n h.cancel();\n }\n }\n this.terminalEmitted = true;\n this.emit(\"error\", snap);\n this.onChange();\n return;\n }\n\n if (this.failedFast || this.canceled) {\n // we call this because a task can canceled as seen above\n // by h.cancel(). Which then triggers the handelchildSettled call above.\n this.onChange();\n return;\n }\n\n this.fillSlots();\n\n if (this.settledCount >= this.uploads.length && !this.terminalEmitted) {\n this.terminalEmitted = true;\n this.emit(\"success\", snap);\n }\n\n this.onChange();\n }\n}\n","import { Emitter } from \"./emitter\";\n\nimport type { ProviderUploadTask } from \"../types/provider\";\nimport type { UploadItem, UploadStatus } from \"../types/upload\";\n\nexport type UploadHandleEvents = {\n progress: UploadItem;\n success: UploadItem;\n error: UploadItem;\n canceled: UploadItem;\n statusChange: UploadItem;\n};\n\n/** Controls file upload and tracks its progress.\n *\n * @param upload The upload item to control.\n * @param onChange A function to call when the upload status changes.\n * @returns An {@link UploadHandle} to control the upload: `pause`, `resume`, `cancel`, and listen for progress with `.on`.\n */\nexport class UploadHandle extends Emitter<UploadHandleEvents> {\n public readonly upload: UploadItem;\n\n private task: ProviderUploadTask | null = null;\n private onChange: () => void;\n private terminated = false;\n\n constructor(upload: UploadItem, onChange: () => void) {\n super();\n this.upload = upload;\n this.onChange = onChange;\n }\n\n /** @internal */\n _attachTask(task: ProviderUploadTask): void {\n this.task = task;\n }\n\n /** @internal */\n _setStatus(status: UploadStatus): void {\n if (this.upload.status === status) return;\n this.upload.status = status;\n this.emit(\"statusChange\", this.upload);\n }\n\n /** @internal */\n _reportProgress(bytesTransferred: number, totalBytes: number): void {\n if (this.terminated) return;\n this.upload.bytesTransferred = bytesTransferred;\n this.upload.totalBytes = totalBytes;\n this.upload.progress =\n totalBytes > 0 ? (bytesTransferred / totalBytes) * 100 : 0;\n if (this.upload.status !== \"uploading\") {\n this._setStatus(\"uploading\");\n }\n this.emit(\"progress\", this.upload);\n this.onChange();\n }\n\n /** @internal */\n _reportSuccess(downloadURL: string): void {\n if (this.terminated) return;\n this.terminated = true;\n this.upload.downloadURL = downloadURL;\n this.upload.progress = 100;\n this._setStatus(\"success\");\n this.emit(\"success\", this.upload);\n this.onChange();\n }\n\n /** @internal */\n _reportError(error: Error): void {\n if (this.terminated) return;\n this.terminated = true;\n this.upload.error = error;\n this._setStatus(\"error\");\n this.emit(\"error\", this.upload);\n this.onChange();\n }\n\n cancel(): void {\n if (this.terminated) return;\n this.terminated = true;\n this.task?.cancel();\n this._setStatus(\"canceled\");\n this.emit(\"canceled\", this.upload);\n this.onChange();\n }\n\n pause(): void {\n if (this.terminated) return;\n if (this.upload.status !== \"uploading\") return;\n this.task?.pause?.();\n this._setStatus(\"paused\");\n this.onChange();\n }\n\n resume(): void {\n if (this.terminated) return;\n if (this.upload.status !== \"paused\") return;\n this.task?.resume?.();\n this._setStatus(\"uploading\");\n this.onChange();\n }\n}\n","import { BatchHandle, type BatchOptions } from \"./batch-handle\";\nimport { UploadHandle } from \"./upload-handle\";\n\nimport type { StorageProvider } from \"../providers/provider\";\nimport type { FileMetadata } from \"../types/metadata\";\nimport type { UploadOptions } from \"../types/provider\";\nimport type { StorageState, UploadItem } from \"../types/upload\";\n\n/**\n * Manages file uploads and storage queries.\n *\n * You can react in two ways: call `.on(...)` on each {@link UploadHandle} / {@link BatchHandle}, **or** call `subscribe` to get the same lists\n * that {@link StorageManager.getState} returns whenever anything changes (call the returned function to stop listening).\n *\n */\nexport class StorageManager {\n private provider: StorageProvider;\n private uploadHandles: UploadHandle[] = [];\n private batches: BatchHandle[] = [];\n private changeListeners = new Set<(state: StorageState) => void>();\n private cachedState: StorageState | null = null;\n\n constructor(provider: StorageProvider) {\n this.provider = provider;\n }\n\n /** Current `uploads` and `batches` state. */\n getState = (): StorageState => {\n if (this.cachedState === null) {\n this.cachedState = {\n uploads: this.uploadHandles.map((h) => h.upload),\n batches: this.batches.map((b) => b.snapshot()),\n };\n }\n return this.cachedState;\n };\n\n /** Runs `listener` after each change; returns a function — call it to unsubscribe. */\n subscribe = (listener: (state: StorageState) => void): (() => void) => {\n this.changeListeners.add(listener);\n return () => {\n this.changeListeners.delete(listener);\n };\n };\n\n /** Returns `true` if the object exists. Returns `false` only for not-found; other errors are thrown. */\n exists(path: string): Promise<boolean> {\n return this.provider.exists(path);\n }\n\n /** Returns metadata for the object at `path`. Throws if the object does not exist. */\n getMetadata(path: string): Promise<FileMetadata> {\n return this.provider.getMetadata(path);\n }\n\n /** Returns a download URL for the object at `path`. */\n getDownloadURL(path: string): Promise<string> {\n return this.provider.getDownloadURL(path);\n }\n\n /** Deletes the object at `path`. */\n delete(path: string): Promise<void> {\n return this.provider.delete(path);\n }\n\n /** Starts the file upload.`options.path` is the object path in storage (see {@link UploadOptions}).\n *\n * @returns An {@link UploadHandle} to control the upload: `pause`, `resume`, `cancel`, and listen for progress with `.on`.\n */\n uploadFile(file: File, options: UploadOptions): UploadHandle {\n const handle = this.createHandle(file);\n this.uploadHandles.push(handle);\n this.notifyChange();\n this.startUpload(handle, options);\n return handle;\n }\n\n /**\n * Upload several files as one batch. The optional third argument sets how many run at once\n * and what happens when one file fails — see {@link BatchOptions}.\n *\n * @remarks\n * `optionsFor` picks storage options per file (same as `uploadFile`); index matches `files` order.\n * If `continueOnError` is `false`, the first failure cancels the other files and the batch fires `error`.\n * If it stays `true` (default), other files keep going; when every file has finished, the batch fires `success`.\n *\n */\n uploadFiles(\n files: File[],\n optionsFor: (file: File, index: number) => UploadOptions,\n batchOptions: BatchOptions = {},\n ): BatchHandle {\n const id = crypto.randomUUID();\n const handles = files.map((file) => this.createHandle(file, id));\n const optionsByUploadId = new Map<string, UploadOptions>();\n handles.forEach((h, i) => {\n const file = files[i];\n if (!file) return;\n optionsByUploadId.set(h.upload.id, optionsFor(file, i));\n });\n\n this.uploadHandles.push(...handles);\n\n const batch = new BatchHandle({\n id,\n uploads: handles,\n options: batchOptions,\n startNext: (handle) => {\n const opts = optionsByUploadId.get(handle.upload.id);\n if (!opts) return;\n this.startUpload(handle, opts);\n },\n onChange: () => this.notifyChange(),\n });\n\n this.batches.push(batch);\n this.notifyChange();\n batch._start();\n return batch;\n }\n\n private createHandle(file: File, batchId?: string): UploadHandle {\n const upload: UploadItem = {\n id: crypto.randomUUID(),\n file,\n progress: 0,\n bytesTransferred: 0,\n totalBytes: file.size,\n status: \"queued\",\n ...(batchId !== undefined ? { batchId } : {}),\n };\n return new UploadHandle(upload, () => this.notifyChange());\n }\n\n private startUpload(handle: UploadHandle, options: UploadOptions): void {\n handle._setStatus(\"uploading\");\n const task = this.provider.upload(handle.upload.file, options, {\n onProgress: (bytesTransferred, totalBytes) =>\n handle._reportProgress(bytesTransferred, totalBytes),\n onError: (error) => handle._reportError(error),\n onSuccess: (downloadURL) => handle._reportSuccess(downloadURL),\n });\n handle._attachTask(task);\n this.notifyChange();\n }\n\n private notifyChange(): void {\n this.cachedState = null;\n const state = this.getState();\n for (const listener of this.changeListeners) {\n listener(state);\n }\n }\n}\n"]}
1
+ {"version":3,"sources":["../src/core/emitter.ts","../src/core/batch-handle.ts","../src/core/upload-handle.ts","../src/core/storage-manager.ts","../src/providers/firebase-provider.ts","../src/firebase-storage-manager.ts"],"names":["StorageManager"],"mappings":"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","file":"index.js","sourcesContent":["type Listener<TPayload> = (payload: TPayload) => void;\n\nexport class Emitter<TEvents extends Record<string, unknown>> {\n private listeners: { [K in keyof TEvents]?: Set<Listener<TEvents[K]>> } = {};\n\n /**\n * @param event The event to listen for.\n * @param listener The function to call when the event is emitted.\n * @returns A function to unsubscribe from the event.\n */\n on<K extends keyof TEvents>(event: K, listener: Listener<TEvents[K]>) {\n if (!this.listeners[event]) {\n this.listeners[event] = new Set();\n }\n\n this.listeners[event]!.add(listener);\n\n return () => {\n this.listeners[event]?.delete(listener);\n };\n }\n\n /**\n * @param event The event to emit.\n * @param payload The payload to emit.\n */\n protected emit<K extends keyof TEvents>(event: K, payload: TEvents[K]) {\n const listeners = this.listeners[event];\n\n if (!listeners) {\n return;\n }\n\n for (const listener of listeners) {\n listener(payload);\n }\n }\n}\n","import type { UploadBatch, UploadItem } from \"../types/upload\";\nimport { Emitter } from \"./emitter\";\nimport type { UploadHandle } from \"./upload-handle\";\n\n/**\n * Events on a batch from {@link StorageManager.uploadFiles}. `progress` / `change` carry a {@link UploadBatch} snapshot.\n * `uploadSuccess` and `uploadError` fire once per child. When all children are done: `success` (normal end) or `error` (only if `continueOnError` was `false` and one failed).\n */\nexport type BatchHandleEvents = {\n progress: UploadBatch;\n uploadSuccess: UploadItem;\n uploadError: UploadItem;\n success: UploadBatch;\n error: UploadBatch;\n change: UploadBatch;\n};\n\n/** Settings for {@link StorageManager.uploadFiles} (third argument). */\nexport interface BatchOptions {\n /** How many files upload at the same time. Default is 3. */\n concurrency?: number;\n /**\n * `true` (default): if one file fails, the others keep going. When every file has finished, the batch fires `success` — look at each upload for errors.\n *\n * `false`: the first failure stops the other files and the batch fires `error`.\n */\n continueOnError?: boolean;\n}\n\nexport interface BatchHandleInit {\n id: string;\n uploads: UploadHandle[];\n options: BatchOptions;\n startNext: (handle: UploadHandle) => void;\n onChange: () => void;\n}\n\n/**\n * A group of files from {@link StorageManager.uploadFiles}. Use `.on(...)` like a single {@link UploadHandle}, plus `snapshot()` for totals.\n */\nexport class BatchHandle extends Emitter<BatchHandleEvents> {\n public readonly id: string;\n public readonly uploads: UploadHandle[];\n\n private concurrency: number;\n private continueOnError: boolean;\n private startNext: (handle: UploadHandle) => void;\n private onChange: () => void;\n\n private running = 0;\n private nextIdx = 0;\n /** Indices that were started via {@link fillSlots} and still hold a concurrency slot. */\n private activeSlots = new Set<number>();\n private settledCount = 0;\n private succeededCount = 0;\n private failedCount = 0;\n private failedFast = false;\n private canceled = false;\n private paused = false;\n private terminalEmitted = false;\n\n // Stable view of UploadItems; the items themselves mutate in place, but the\n // array reference never changes, so we allocate it once instead of rebuilding\n // it on every snapshot.\n private readonly uploadsView: UploadItem[];\n\n // Running aggregates updated incrementally on each child event so snapshot()\n // is O(1) instead of O(n) per progress tick.\n private aggregateBytesTransferred = 0;\n private aggregateTotalBytes = 0;\n private readonly lastBytes: number[];\n private readonly lastTotal: number[];\n\n constructor(init: BatchHandleInit) {\n super();\n this.id = init.id;\n this.uploads = init.uploads;\n this.concurrency = Math.max(1, init.options.concurrency ?? 3);\n this.continueOnError = init.options.continueOnError ?? true;\n this.startNext = init.startNext;\n this.onChange = init.onChange;\n\n this.uploadsView = init.uploads.map((h) => h.upload);\n this.lastBytes = new Array(init.uploads.length).fill(0);\n this.lastTotal = new Array(init.uploads.length).fill(0);\n for (let i = 0; i < this.uploadsView.length; i++) {\n const u = this.uploadsView[i]!;\n this.lastBytes[i] = u.bytesTransferred;\n this.lastTotal[i] = u.totalBytes;\n this.aggregateBytesTransferred += u.bytesTransferred;\n this.aggregateTotalBytes += u.totalBytes;\n }\n\n this.uploads.forEach((child, idx) => {\n child.on(\"progress\", (u) => this.handleChildProgress(idx, u));\n child.on(\"success\", (u) => this.handleChildSettled(idx, u, \"success\"));\n child.on(\"error\", (u) => this.handleChildSettled(idx, u, \"error\"));\n child.on(\"canceled\", (u) => this.handleChildSettled(idx, u, \"canceled\"));\n });\n }\n\n /** @internal */\n _start(): void {\n this.fillSlots();\n }\n\n cancel(): void {\n if (this.canceled || this.terminalEmitted) return;\n this.canceled = true;\n for (const h of this.uploads) {\n const s = h.upload.status;\n if (s === \"queued\" || s === \"uploading\" || s === \"paused\") {\n h.cancel();\n }\n }\n this.onChange();\n }\n\n pause(): void {\n if (this.paused || this.canceled || this.terminalEmitted) return;\n this.paused = true;\n for (const h of this.uploads) {\n if (h.upload.status === \"uploading\") h.pause();\n }\n this.onChange();\n }\n\n resume(): void {\n if (!this.paused || this.canceled || this.terminalEmitted) return;\n this.paused = false;\n for (const h of this.uploads) {\n if (h.upload.status === \"paused\") h.resume();\n }\n this.fillSlots();\n this.onChange();\n }\n\n snapshot(): UploadBatch {\n const totalProgress =\n this.aggregateTotalBytes > 0\n ? (this.aggregateBytesTransferred / this.aggregateTotalBytes) * 100\n : 0;\n return {\n id: this.id,\n uploads: this.uploadsView,\n totalProgress,\n completedCount: this.succeededCount,\n failedCount: this.failedCount,\n };\n }\n\n private updateAggregate(idx: number, u: UploadItem): void {\n const prevBytes = this.lastBytes[idx] ?? 0;\n const prevTotal = this.lastTotal[idx] ?? 0;\n this.aggregateBytesTransferred += u.bytesTransferred - prevBytes;\n this.aggregateTotalBytes += u.totalBytes - prevTotal;\n this.lastBytes[idx] = u.bytesTransferred;\n this.lastTotal[idx] = u.totalBytes;\n }\n\n private fillSlots(): void {\n while (\n !this.canceled &&\n !this.paused &&\n !this.failedFast &&\n this.running < this.concurrency &&\n this.nextIdx < this.uploads.length\n ) {\n const idx = this.nextIdx++;\n const handle = this.uploads[idx];\n if (!handle || handle.upload.status !== \"queued\") continue;\n this.activeSlots.add(idx);\n this.running++;\n this.startNext(handle);\n }\n }\n\n private handleChildProgress(idx: number, upload: UploadItem): void {\n this.updateAggregate(idx, upload);\n const snap = this.snapshot();\n this.emit(\"progress\", snap);\n this.emit(\"change\", snap);\n }\n\n private handleChildSettled(\n idx: number,\n upload: UploadItem,\n kind: \"success\" | \"error\" | \"canceled\",\n ): void {\n this.updateAggregate(idx, upload);\n if (this.activeSlots.delete(idx)) {\n this.running = Math.max(0, this.running - 1);\n }\n this.settledCount++;\n\n if (kind === \"success\") {\n this.succeededCount++;\n this.emit(\"uploadSuccess\", upload);\n } else if (kind === \"error\") {\n this.failedCount++;\n this.emit(\"uploadError\", upload);\n }\n\n const snap = this.snapshot();\n this.emit(\"change\", snap);\n\n if (\n kind === \"error\" &&\n !this.continueOnError &&\n !this.failedFast &&\n !this.canceled\n ) {\n this.failedFast = true;\n for (const h of this.uploads) {\n if (h.upload.id === upload.id) continue;\n const s = h.upload.status;\n if (s === \"queued\" || s === \"uploading\" || s === \"paused\") {\n h.cancel();\n }\n }\n this.terminalEmitted = true;\n this.emit(\"error\", snap);\n this.onChange();\n return;\n }\n\n if (this.failedFast || this.canceled) {\n // we call this because a task can canceled as seen above\n // by h.cancel(). Which then triggers the handelchildSettled call above.\n this.onChange();\n return;\n }\n\n this.fillSlots();\n\n if (this.settledCount >= this.uploads.length && !this.terminalEmitted) {\n this.terminalEmitted = true;\n this.emit(\"success\", snap);\n }\n\n this.onChange();\n }\n}\n","import { Emitter } from \"./emitter\";\n\nimport type { ProviderUploadTask } from \"../types/provider\";\nimport type { UploadItem, UploadStatus } from \"../types/upload\";\n\nexport type UploadHandleEvents = {\n progress: UploadItem;\n success: UploadItem;\n error: UploadItem;\n canceled: UploadItem;\n statusChange: UploadItem;\n};\n\n/** Controls file upload and tracks its progress.\n *\n * @param upload The upload item to control.\n * @param onChange A function to call when the upload status changes.\n * @returns An {@link UploadHandle} to control the upload: `pause`, `resume`, `cancel`, and listen for progress with `.on`.\n */\nexport class UploadHandle extends Emitter<UploadHandleEvents> {\n public readonly upload: UploadItem;\n\n private task: ProviderUploadTask | null = null;\n private onChange: () => void;\n private terminated = false;\n\n constructor(upload: UploadItem, onChange: () => void) {\n super();\n this.upload = upload;\n this.onChange = onChange;\n }\n\n /** @internal */\n _attachTask(task: ProviderUploadTask): void {\n this.task = task;\n }\n\n /** @internal */\n _setStatus(status: UploadStatus): void {\n if (this.upload.status === status) return;\n this.upload.status = status;\n this.emit(\"statusChange\", this.upload);\n }\n\n /** @internal */\n _reportProgress(bytesTransferred: number, totalBytes: number): void {\n if (this.terminated) return;\n this.upload.bytesTransferred = bytesTransferred;\n this.upload.totalBytes = totalBytes;\n this.upload.progress =\n totalBytes > 0 ? (bytesTransferred / totalBytes) * 100 : 0;\n if (this.upload.status !== \"uploading\") {\n this._setStatus(\"uploading\");\n }\n this.emit(\"progress\", this.upload);\n this.onChange();\n }\n\n /** @internal */\n _reportSuccess(downloadURL: string): void {\n if (this.terminated) return;\n this.terminated = true;\n this.upload.downloadURL = downloadURL;\n this.upload.progress = 100;\n this._setStatus(\"success\");\n this.emit(\"success\", this.upload);\n this.onChange();\n }\n\n /** @internal */\n _reportError(error: Error): void {\n if (this.terminated) return;\n this.terminated = true;\n this.upload.error = error;\n this._setStatus(\"error\");\n this.emit(\"error\", this.upload);\n this.onChange();\n }\n\n cancel(): void {\n if (this.terminated) return;\n this.terminated = true;\n this.task?.cancel();\n this._setStatus(\"canceled\");\n this.emit(\"canceled\", this.upload);\n this.onChange();\n }\n\n pause(): void {\n if (this.terminated) return;\n if (this.upload.status !== \"uploading\") return;\n this.task?.pause?.();\n this._setStatus(\"paused\");\n this.onChange();\n }\n\n resume(): void {\n if (this.terminated) return;\n if (this.upload.status !== \"paused\") return;\n this.task?.resume?.();\n this._setStatus(\"uploading\");\n this.onChange();\n }\n}\n","import { BatchHandle, type BatchOptions } from \"./batch-handle\";\nimport { UploadHandle } from \"./upload-handle\";\n\nimport type { StorageProvider } from \"../providers/provider\";\nimport type { FileMetadata } from \"../types/metadata\";\nimport type { UploadOptions } from \"../types/provider\";\nimport type { StorageState, UploadItem } from \"../types/upload\";\n\n/**\n * Manages file uploads and storage queries.\n *\n * You can react in two ways: call `.on(...)` on each {@link UploadHandle} / {@link BatchHandle}, **or** call `subscribe` to get the same lists\n * that {@link StorageManager.getState} returns whenever anything changes (call the returned function to stop listening).\n *\n */\nexport class StorageManager {\n private provider: StorageProvider;\n private uploadHandles: UploadHandle[] = [];\n private batches: BatchHandle[] = [];\n private changeListeners = new Set<(state: StorageState) => void>();\n private cachedState: StorageState | null = null;\n\n constructor(provider: StorageProvider) {\n this.provider = provider;\n }\n\n /** Current `uploads` and `batches` state. */\n getState = (): StorageState => {\n if (this.cachedState === null) {\n this.cachedState = {\n uploads: this.uploadHandles.map((h) => h.upload),\n batches: this.batches.map((b) => b.snapshot()),\n };\n }\n return this.cachedState;\n };\n\n /** Runs `listener` after each change; returns a function — call it to unsubscribe. */\n subscribe = (listener: (state: StorageState) => void): (() => void) => {\n this.changeListeners.add(listener);\n return () => {\n this.changeListeners.delete(listener);\n };\n };\n\n /** Returns `true` if the object exists. Returns `false` only for not-found; other errors are thrown. */\n exists(path: string): Promise<boolean> {\n return this.provider.exists(path);\n }\n\n /** Returns metadata for the object at `path`. Throws if the object does not exist. */\n getMetadata(path: string): Promise<FileMetadata> {\n return this.provider.getMetadata(path);\n }\n\n /** Returns a download URL for the object at `path`. */\n getDownloadURL(path: string): Promise<string> {\n return this.provider.getDownloadURL(path);\n }\n\n /** Deletes the object at `path`. */\n delete(path: string): Promise<void> {\n return this.provider.delete(path);\n }\n\n /** Starts the file upload.`options.path` is the object path in storage (see {@link UploadOptions}).\n *\n * @returns An {@link UploadHandle} to control the upload: `pause`, `resume`, `cancel`, and listen for progress with `.on`.\n */\n uploadFile(file: File, options: UploadOptions): UploadHandle {\n const handle = this.createHandle(file, { path: options.path });\n this.uploadHandles.push(handle);\n this.notifyChange();\n this.startUpload(handle, options);\n return handle;\n }\n\n /**\n * Upload several files as one batch. The optional third argument sets how many run at once\n * and what happens when one file fails — see {@link BatchOptions}.\n *\n * @remarks\n * `optionsFor` picks storage options per file (same as `uploadFile`); index matches `files` order.\n * If `continueOnError` is `false`, the first failure cancels the other files and the batch fires `error`.\n * If it stays `true` (default), other files keep going; when every file has finished, the batch fires `success`.\n *\n */\n uploadFiles(\n files: File[],\n optionsFor: (file: File, index: number) => UploadOptions,\n batchOptions: BatchOptions = {},\n ): BatchHandle {\n const id = crypto.randomUUID();\n const handles = files.map((file, i) => {\n const options = optionsFor(file, i);\n return this.createHandle(file, { batchId: id, path: options.path });\n });\n\n this.uploadHandles.push(...handles);\n\n const batch = new BatchHandle({\n id,\n uploads: handles,\n options: batchOptions,\n startNext: (handle) => {\n this.startUpload(handle, { path: handle.upload.path });\n },\n onChange: () => this.notifyChange(),\n });\n\n this.batches.push(batch);\n this.notifyChange();\n batch._start();\n return batch;\n }\n\n private createHandle(\n file: File,\n options: { path: string; batchId?: string },\n ): UploadHandle {\n const upload: UploadItem = {\n id: crypto.randomUUID(),\n file,\n path: options.path,\n progress: 0,\n bytesTransferred: 0,\n totalBytes: file.size,\n status: \"queued\",\n ...(options.batchId !== undefined ? { batchId: options.batchId } : {}),\n };\n return new UploadHandle(upload, () => this.notifyChange());\n }\n\n private startUpload(handle: UploadHandle, options: UploadOptions): void {\n handle._setStatus(\"uploading\");\n const task = this.provider.upload(handle.upload.file, options, {\n onProgress: (bytesTransferred, totalBytes) =>\n handle._reportProgress(bytesTransferred, totalBytes),\n onError: (error) => handle._reportError(error),\n onSuccess: (downloadURL) => handle._reportSuccess(downloadURL),\n });\n handle._attachTask(task);\n this.notifyChange();\n }\n\n private notifyChange(): void {\n this.cachedState = null;\n const state = this.getState();\n for (const listener of this.changeListeners) {\n listener(state);\n }\n }\n}\n","import {\n deleteObject,\n getDownloadURL,\n getMetadata,\n ref,\n uploadBytesResumable,\n type FirebaseStorage,\n type StorageError,\n} from \"firebase/storage\";\n\nimport type { FileMetadata } from \"../types/metadata\";\nimport type {\n ProviderUploadCallbacks,\n ProviderUploadTask,\n UploadOptions,\n} from \"../types/provider\";\nimport type { StorageProvider } from \"./provider\";\n\nexport class FirebaseStorageProvider implements StorageProvider {\n constructor(private storage: FirebaseStorage) {}\n\n upload(\n file: File,\n\n options: UploadOptions,\n\n callbacks: ProviderUploadCallbacks,\n ): ProviderUploadTask {\n const storageRef = ref(this.storage, options.path);\n\n const task = uploadBytesResumable(storageRef, file);\n\n task.on(\n \"state_changed\",\n\n (snapshot) => {\n callbacks.onProgress(\n snapshot.bytesTransferred,\n\n snapshot.totalBytes,\n );\n },\n\n (error) => {\n callbacks.onError(error);\n },\n\n async () => {\n try {\n const downloadURL = await getDownloadURL(storageRef);\n\n callbacks.onSuccess(downloadURL);\n } catch (error) {\n callbacks.onError(error as Error);\n }\n },\n );\n\n return {\n cancel: () => {\n task.cancel();\n },\n\n pause: () => {\n task.pause();\n },\n\n resume: () => {\n task.resume();\n },\n };\n }\n\n async exists(path: string): Promise<boolean> {\n try {\n await getMetadata(ref(this.storage, path));\n return true;\n } catch (err) {\n if ((err as StorageError).code === \"storage/object-not-found\") {\n return false;\n }\n throw err;\n }\n }\n\n async getMetadata(path: string): Promise<FileMetadata> {\n const meta = await getMetadata(ref(this.storage, path));\n return {\n path,\n size: meta.size,\n contentType: meta.contentType,\n createdAt: new Date(meta.timeCreated),\n updatedAt: new Date(meta.updated),\n customMetadata: meta.customMetadata,\n };\n }\n\n getDownloadURL(path: string): Promise<string> {\n return getDownloadURL(ref(this.storage, path));\n }\n\n async delete(path: string): Promise<void> {\n await deleteObject(ref(this.storage, path));\n }\n}\n","import type { FirebaseStorage } from \"firebase/storage\";\n\nimport { StorageManager as BaseStorageManager } from \"./core/storage-manager\";\nimport { FirebaseStorageProvider } from \"./providers/firebase-provider\";\n\n/** Firebase Storage uploads and file helpers. Pass a `FirebaseStorage` instance from `getStorage(app)`. */\nexport class StorageManager extends BaseStorageManager {\n constructor(storage: FirebaseStorage) {\n super(new FirebaseStorageProvider(storage));\n }\n}\n"]}
package/package.json CHANGED
@@ -1,10 +1,10 @@
1
1
  {
2
2
  "name": "firebase-storage-kit",
3
- "version": "1.0.0",
3
+ "version": "1.1.1",
4
4
  "description": "Storage manager for Firebase Storage with uploads, progress tracking, batch uploads, and file query helpers.",
5
5
  "keywords": [
6
6
  "firebase",
7
- "firebase-storage",
7
+ "firebase storage",
8
8
  "storage",
9
9
  "upload",
10
10
  "resumable",
@@ -30,10 +30,6 @@
30
30
  ".": {
31
31
  "types": "./dist/index.d.ts",
32
32
  "import": "./dist/index.js"
33
- },
34
- "./firebase": {
35
- "types": "./dist/firebase.d.ts",
36
- "import": "./dist/firebase.js"
37
33
  }
38
34
  },
39
35
  "files": [
@@ -44,6 +40,8 @@
44
40
  "dev": "tsup --watch",
45
41
  "clean": "rm -rf dist",
46
42
  "typecheck": "tsc --noEmit",
43
+ "test": "bun test",
44
+ "test:watch": "bun test --watch",
47
45
  "prepublishOnly": "npm run build"
48
46
  },
49
47
  "peerDependencies": {
@@ -1,14 +0,0 @@
1
- import { FirebaseStorage } from 'firebase/storage';
2
- import { S as StorageProvider, U as UploadOptions, P as ProviderUploadCallbacks, a as ProviderUploadTask, F as FileMetadata } from './provider-B89iZO4q.js';
3
-
4
- declare class FirebaseStorageProvider implements StorageProvider {
5
- private storage;
6
- constructor(storage: FirebaseStorage);
7
- upload(file: File, options: UploadOptions, callbacks: ProviderUploadCallbacks): ProviderUploadTask;
8
- exists(path: string): Promise<boolean>;
9
- getMetadata(path: string): Promise<FileMetadata>;
10
- getDownloadURL(path: string): Promise<string>;
11
- delete(path: string): Promise<void>;
12
- }
13
-
14
- export { FirebaseStorageProvider };
package/dist/firebase.js DELETED
@@ -1,76 +0,0 @@
1
- import { ref, uploadBytesResumable, getDownloadURL, getMetadata, deleteObject } from 'firebase/storage';
2
-
3
- // src/providers/firebase-provider.ts
4
- var FirebaseStorageProvider = class {
5
- constructor(storage) {
6
- this.storage = storage;
7
- }
8
- storage;
9
- upload(file, options, callbacks) {
10
- const storageRef = ref(this.storage, options.path);
11
- const task = uploadBytesResumable(storageRef, file);
12
- task.on(
13
- "state_changed",
14
- (snapshot) => {
15
- callbacks.onProgress(
16
- snapshot.bytesTransferred,
17
- snapshot.totalBytes
18
- );
19
- },
20
- (error) => {
21
- callbacks.onError(error);
22
- },
23
- async () => {
24
- try {
25
- const downloadURL = await getDownloadURL(storageRef);
26
- callbacks.onSuccess(downloadURL);
27
- } catch (error) {
28
- callbacks.onError(error);
29
- }
30
- }
31
- );
32
- return {
33
- cancel: () => {
34
- task.cancel();
35
- },
36
- pause: () => {
37
- task.pause();
38
- },
39
- resume: () => {
40
- task.resume();
41
- }
42
- };
43
- }
44
- async exists(path) {
45
- try {
46
- await getMetadata(ref(this.storage, path));
47
- return true;
48
- } catch (err) {
49
- if (err.code === "storage/object-not-found") {
50
- return false;
51
- }
52
- throw err;
53
- }
54
- }
55
- async getMetadata(path) {
56
- const meta = await getMetadata(ref(this.storage, path));
57
- return {
58
- path,
59
- size: meta.size,
60
- contentType: meta.contentType,
61
- createdAt: new Date(meta.timeCreated),
62
- updatedAt: new Date(meta.updated),
63
- customMetadata: meta.customMetadata
64
- };
65
- }
66
- getDownloadURL(path) {
67
- return getDownloadURL(ref(this.storage, path));
68
- }
69
- async delete(path) {
70
- await deleteObject(ref(this.storage, path));
71
- }
72
- };
73
-
74
- export { FirebaseStorageProvider };
75
- //# sourceMappingURL=firebase.js.map
76
- //# sourceMappingURL=firebase.js.map
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/providers/firebase-provider.ts"],"names":[],"mappings":";;;AAkBO,IAAM,0BAAN,MAAyD;AAAA,EAC9D,YAAoB,OAAA,EAA0B;AAA1B,IAAA,IAAA,CAAA,OAAA,GAAA,OAAA;AAAA,EAA2B;AAAA,EAA3B,OAAA;AAAA,EAEpB,MAAA,CACE,IAAA,EAEA,OAAA,EAEA,SAAA,EACoB;AACpB,IAAA,MAAM,UAAA,GAAa,GAAA,CAAI,IAAA,CAAK,OAAA,EAAS,QAAQ,IAAI,CAAA;AAEjD,IAAA,MAAM,IAAA,GAAO,oBAAA,CAAqB,UAAA,EAAY,IAAI,CAAA;AAElD,IAAA,IAAA,CAAK,EAAA;AAAA,MACH,eAAA;AAAA,MAEA,CAAC,QAAA,KAAa;AACZ,QAAA,SAAA,CAAU,UAAA;AAAA,UACR,QAAA,CAAS,gBAAA;AAAA,UAET,QAAA,CAAS;AAAA,SACX;AAAA,MACF,CAAA;AAAA,MAEA,CAAC,KAAA,KAAU;AACT,QAAA,SAAA,CAAU,QAAQ,KAAK,CAAA;AAAA,MACzB,CAAA;AAAA,MAEA,YAAY;AACV,QAAA,IAAI;AACF,UAAA,MAAM,WAAA,GAAc,MAAM,cAAA,CAAe,UAAU,CAAA;AAEnD,UAAA,SAAA,CAAU,UAAU,WAAW,CAAA;AAAA,QACjC,SAAS,KAAA,EAAO;AACd,UAAA,SAAA,CAAU,QAAQ,KAAc,CAAA;AAAA,QAClC;AAAA,MACF;AAAA,KACF;AAEA,IAAA,OAAO;AAAA,MACL,QAAQ,MAAM;AACZ,QAAA,IAAA,CAAK,MAAA,EAAO;AAAA,MACd,CAAA;AAAA,MAEA,OAAO,MAAM;AACX,QAAA,IAAA,CAAK,KAAA,EAAM;AAAA,MACb,CAAA;AAAA,MAEA,QAAQ,MAAM;AACZ,QAAA,IAAA,CAAK,MAAA,EAAO;AAAA,MACd;AAAA,KACF;AAAA,EACF;AAAA,EAEA,MAAM,OAAO,IAAA,EAAgC;AAC3C,IAAA,IAAI;AACF,MAAA,MAAM,WAAA,CAAY,GAAA,CAAI,IAAA,CAAK,OAAA,EAAS,IAAI,CAAC,CAAA;AACzC,MAAA,OAAO,IAAA;AAAA,IACT,SAAS,GAAA,EAAK;AACZ,MAAA,IAAK,GAAA,CAAqB,SAAS,0BAAA,EAA4B;AAC7D,QAAA,OAAO,KAAA;AAAA,MACT;AACA,MAAA,MAAM,GAAA;AAAA,IACR;AAAA,EACF;AAAA,EAEA,MAAM,YAAY,IAAA,EAAqC;AACrD,IAAA,MAAM,OAAO,MAAM,WAAA,CAAY,IAAI,IAAA,CAAK,OAAA,EAAS,IAAI,CAAC,CAAA;AACtD,IAAA,OAAO;AAAA,MACL,IAAA;AAAA,MACA,MAAM,IAAA,CAAK,IAAA;AAAA,MACX,aAAa,IAAA,CAAK,WAAA;AAAA,MAClB,SAAA,EAAW,IAAI,IAAA,CAAK,IAAA,CAAK,WAAW,CAAA;AAAA,MACpC,SAAA,EAAW,IAAI,IAAA,CAAK,IAAA,CAAK,OAAO,CAAA;AAAA,MAChC,gBAAgB,IAAA,CAAK;AAAA,KACvB;AAAA,EACF;AAAA,EAEA,eAAe,IAAA,EAA+B;AAC5C,IAAA,OAAO,cAAA,CAAe,GAAA,CAAI,IAAA,CAAK,OAAA,EAAS,IAAI,CAAC,CAAA;AAAA,EAC/C;AAAA,EAEA,MAAM,OAAO,IAAA,EAA6B;AACxC,IAAA,MAAM,YAAA,CAAa,GAAA,CAAI,IAAA,CAAK,OAAA,EAAS,IAAI,CAAC,CAAA;AAAA,EAC5C;AACF","file":"firebase.js","sourcesContent":["import {\n deleteObject,\n getDownloadURL,\n getMetadata,\n ref,\n uploadBytesResumable,\n type FirebaseStorage,\n type StorageError,\n} from \"firebase/storage\";\n\nimport type {\n ProviderUploadCallbacks,\n ProviderUploadTask,\n UploadOptions,\n} from \"../types/provider\";\nimport type { FileMetadata } from \"../types/metadata\";\nimport type { StorageProvider } from \"./provider\";\n\nexport class FirebaseStorageProvider implements StorageProvider {\n constructor(private storage: FirebaseStorage) {}\n\n upload(\n file: File,\n\n options: UploadOptions,\n\n callbacks: ProviderUploadCallbacks,\n ): ProviderUploadTask {\n const storageRef = ref(this.storage, options.path);\n\n const task = uploadBytesResumable(storageRef, file);\n\n task.on(\n \"state_changed\",\n\n (snapshot) => {\n callbacks.onProgress(\n snapshot.bytesTransferred,\n\n snapshot.totalBytes,\n );\n },\n\n (error) => {\n callbacks.onError(error);\n },\n\n async () => {\n try {\n const downloadURL = await getDownloadURL(storageRef);\n\n callbacks.onSuccess(downloadURL);\n } catch (error) {\n callbacks.onError(error as Error);\n }\n },\n );\n\n return {\n cancel: () => {\n task.cancel();\n },\n\n pause: () => {\n task.pause();\n },\n\n resume: () => {\n task.resume();\n },\n };\n }\n\n async exists(path: string): Promise<boolean> {\n try {\n await getMetadata(ref(this.storage, path));\n return true;\n } catch (err) {\n if ((err as StorageError).code === \"storage/object-not-found\") {\n return false;\n }\n throw err;\n }\n }\n\n async getMetadata(path: string): Promise<FileMetadata> {\n const meta = await getMetadata(ref(this.storage, path));\n return {\n path,\n size: meta.size,\n contentType: meta.contentType,\n createdAt: new Date(meta.timeCreated),\n updatedAt: new Date(meta.updated),\n customMetadata: meta.customMetadata,\n };\n }\n\n getDownloadURL(path: string): Promise<string> {\n return getDownloadURL(ref(this.storage, path));\n }\n\n async delete(path: string): Promise<void> {\n await deleteObject(ref(this.storage, path));\n }\n}\n"]}
@@ -1,33 +0,0 @@
1
- interface ProviderUploadTask {
2
- cancel(): void;
3
- pause?(): void;
4
- resume?(): void;
5
- }
6
- interface ProviderUploadCallbacks {
7
- onProgress: (bytesTransferred: number, totalBytes: number) => void;
8
- onError: (error: Error) => void;
9
- onSuccess: (downloadURL: string) => void;
10
- }
11
- interface UploadOptions {
12
- /** Object path in the bucket (Firebase), e.g. `uploads/photo.jpg`. */
13
- path: string;
14
- }
15
-
16
- interface FileMetadata {
17
- path: string;
18
- size: number;
19
- contentType?: string;
20
- createdAt: Date;
21
- updatedAt: Date;
22
- customMetadata?: Record<string, string>;
23
- }
24
-
25
- interface StorageProvider {
26
- upload(file: File, options: UploadOptions, callbacks: ProviderUploadCallbacks): ProviderUploadTask;
27
- exists(path: string): Promise<boolean>;
28
- getMetadata(path: string): Promise<FileMetadata>;
29
- getDownloadURL(path: string): Promise<string>;
30
- delete(path: string): Promise<void>;
31
- }
32
-
33
- export type { FileMetadata as F, ProviderUploadCallbacks as P, StorageProvider as S, UploadOptions as U, ProviderUploadTask as a };