firebase-storage-kit 0.0.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 ADDED
@@ -0,0 +1,63 @@
1
+ # firebase-storage-kit
2
+
3
+ If you've built a file picker on top of Firebase Storage, you've probably wired the same things again: progress updates, error handling, cancellation, and some limit on parallel uploads when someone selects a whole folder. This library is a thin layer for that, a single upload manager with progress updates and batched uploads with configurable concurrency.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ npm install firebase-storage-kit
9
+ ```
10
+
11
+ ## Quick start
12
+
13
+ ```ts
14
+ import { getStorage } from "firebase/storage";
15
+ import { UploadManager } from "firebase-storage-kit";
16
+ import { FirebaseStorageProvider } from "firebase-storage-kit/firebase";
17
+
18
+ const storage = getStorage(app);
19
+ const manager = new UploadManager(new FirebaseStorageProvider(storage));
20
+
21
+ const handle = manager.uploadFile(file, { path: `uploads/${file.name}` });
22
+
23
+ handle.on("progress", (upload) => {
24
+ console.log(upload.progress);
25
+ });
26
+
27
+ handle.on("success", (upload) => {
28
+ console.log(upload.downloadURL);
29
+ });
30
+ ```
31
+
32
+ ### Batch uploads
33
+
34
+ ```ts
35
+ const batch = manager.uploadFiles(
36
+ files,
37
+ (file) => ({ path: `uploads/${file.name}` }),
38
+ { concurrency: 3, continueOnError: true },
39
+ );
40
+
41
+ batch.on("success", (snapshot) => {
42
+ console.log(snapshot.completedCount, snapshot.failedCount);
43
+ });
44
+ ```
45
+
46
+ ### Subscriptions (manager state)
47
+
48
+ ```ts
49
+ const unsubscribe = manager.subscribe((state) => {
50
+ console.log(state.uploads, state.batches);
51
+ });
52
+ ```
53
+
54
+ ## Entry points
55
+
56
+ | Import | Use |
57
+ | ------------------------------- | ----------------------------------------------------- |
58
+ | `firebase-storage-kit` | `UploadManager`, `UploadHandle`, `BatchHandle`, types |
59
+ | `firebase-storage-kit/firebase` | `FirebaseStorageProvider` |
60
+
61
+ ## License
62
+
63
+ MIT
@@ -0,0 +1,10 @@
1
+ import { FirebaseStorage } from 'firebase/storage';
2
+ import { S as StorageProvider, U as UploadOptions, P as ProviderUploadCallbacks, a as ProviderUploadTask } from './provider-xU7lonvB.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
+ }
9
+
10
+ export { FirebaseStorageProvider };
@@ -0,0 +1,48 @@
1
+ import { ref, uploadBytesResumable, getDownloadURL } 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
+ };
45
+
46
+ export { FirebaseStorageProvider };
47
+ //# sourceMappingURL=firebase.js.map
48
+ //# sourceMappingURL=firebase.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/providers/firebase-provider.ts"],"names":[],"mappings":";;;AAcO,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;AACF","file":"firebase.js","sourcesContent":["import {\n getDownloadURL,\n ref,\n uploadBytesResumable,\n type FirebaseStorage,\n} from \"firebase/storage\";\n\nimport type { StorageProvider } from \"./provider\";\nimport type {\n ProviderUploadCallbacks,\n ProviderUploadTask,\n UploadOptions,\n} from \"../types/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"]}
@@ -0,0 +1,129 @@
1
+ import { S as StorageProvider, U as UploadOptions } from './provider-xU7lonvB.js';
2
+ export { P as ProviderUploadCallbacks, a as ProviderUploadTask } from './provider-xU7lonvB.js';
3
+
4
+ interface UploadState {
5
+ uploads: UploadItem[];
6
+ batches: UploadBatch[];
7
+ }
8
+ type UploadStatus = "queued" | "uploading" | "paused" | "success" | "error" | "canceled";
9
+ interface UploadItem {
10
+ id: string;
11
+ file: File;
12
+ progress: number;
13
+ bytesTransferred: number;
14
+ totalBytes: number;
15
+ status: UploadStatus;
16
+ downloadURL?: string;
17
+ error?: Error;
18
+ batchId?: string;
19
+ }
20
+ interface UploadBatch {
21
+ id: string;
22
+ uploads: UploadItem[];
23
+ totalProgress: number;
24
+ completedCount: number;
25
+ failedCount: number;
26
+ }
27
+
28
+ type Listener<TPayload> = (payload: TPayload) => void;
29
+ declare class Emitter<TEvents extends Record<string, unknown>> {
30
+ private listeners;
31
+ on<K extends keyof TEvents>(event: K, listener: Listener<TEvents[K]>): () => void;
32
+ protected emit<K extends keyof TEvents>(event: K, payload: TEvents[K]): void;
33
+ }
34
+
35
+ type UploadHandleEvents = {
36
+ progress: UploadItem;
37
+ success: UploadItem;
38
+ error: UploadItem;
39
+ canceled: UploadItem;
40
+ statusChange: UploadItem;
41
+ };
42
+ declare class UploadHandle extends Emitter<UploadHandleEvents> {
43
+ readonly upload: UploadItem;
44
+ private task;
45
+ private onChange;
46
+ private terminated;
47
+ constructor(upload: UploadItem, onChange: () => void);
48
+ cancel(): void;
49
+ pause(): void;
50
+ resume(): void;
51
+ }
52
+
53
+ type BatchHandleEvents = {
54
+ progress: UploadBatch;
55
+ uploadSuccess: UploadItem;
56
+ uploadError: UploadItem;
57
+ success: UploadBatch;
58
+ error: UploadBatch;
59
+ change: UploadBatch;
60
+ };
61
+ interface BatchOptions {
62
+ /** Maximum number of uploads in-flight at the same time. Defaults to 3. */
63
+ concurrency?: number;
64
+ /**
65
+ * When true (default), a failing upload does not affect siblings; the batch
66
+ * still emits `success` once all children have settled and callers inspect
67
+ * individual `UploadItem` errors.
68
+ *
69
+ * When false, the first failure cancels all queued and in-flight siblings
70
+ * and the batch emits `error`.
71
+ */
72
+ continueOnError?: boolean;
73
+ }
74
+ interface BatchHandleInit {
75
+ id: string;
76
+ uploads: UploadHandle[];
77
+ options: BatchOptions;
78
+ startNext: (handle: UploadHandle) => void;
79
+ onChange: () => void;
80
+ }
81
+ declare class BatchHandle extends Emitter<BatchHandleEvents> {
82
+ readonly id: string;
83
+ readonly uploads: UploadHandle[];
84
+ private concurrency;
85
+ private continueOnError;
86
+ private startNext;
87
+ private onChange;
88
+ private running;
89
+ private nextIdx;
90
+ private settledCount;
91
+ private succeededCount;
92
+ private failedCount;
93
+ private failedFast;
94
+ private canceled;
95
+ private paused;
96
+ private terminalEmitted;
97
+ private readonly uploadsView;
98
+ private aggregateBytesTransferred;
99
+ private aggregateTotalBytes;
100
+ private readonly lastBytes;
101
+ private readonly lastTotal;
102
+ constructor(init: BatchHandleInit);
103
+ cancel(): void;
104
+ pause(): void;
105
+ resume(): void;
106
+ snapshot(): UploadBatch;
107
+ private updateAggregate;
108
+ private fillSlots;
109
+ private handleChildProgress;
110
+ private handleChildSettled;
111
+ }
112
+
113
+ declare class UploadManager {
114
+ private provider;
115
+ private uploadHandles;
116
+ private batches;
117
+ private changeListeners;
118
+ private cachedState;
119
+ constructor(provider: StorageProvider);
120
+ getState: () => UploadState;
121
+ subscribe: (listener: (state: UploadState) => void) => (() => void);
122
+ uploadFile(file: File, options: UploadOptions): UploadHandle;
123
+ uploadFiles(files: File[], optionsFor: (file: File, index: number) => UploadOptions, batchOptions?: BatchOptions): BatchHandle;
124
+ private createHandle;
125
+ private startUpload;
126
+ private notifyChange;
127
+ }
128
+
129
+ export { BatchHandle, type BatchHandleEvents, type BatchHandleInit, type BatchOptions, type UploadBatch, UploadHandle, type UploadHandleEvents, type UploadItem, UploadManager, UploadOptions, type UploadState, type UploadStatus };
package/dist/index.js ADDED
@@ -0,0 +1,347 @@
1
+ // src/core/emitter.ts
2
+ var Emitter = class {
3
+ listeners = /* @__PURE__ */ new Map();
4
+ on(event, listener) {
5
+ if (!this.listeners.has(event)) {
6
+ this.listeners.set(event, /* @__PURE__ */ new Set());
7
+ }
8
+ this.listeners.get(event)?.add(listener);
9
+ return () => {
10
+ this.listeners.get(event)?.delete(listener);
11
+ };
12
+ }
13
+ emit(event, payload) {
14
+ const listeners = this.listeners.get(event);
15
+ if (!listeners) {
16
+ return;
17
+ }
18
+ for (const listener of listeners) {
19
+ listener(payload);
20
+ }
21
+ }
22
+ };
23
+
24
+ // src/core/batch-handle.ts
25
+ var BatchHandle = class extends Emitter {
26
+ id;
27
+ uploads;
28
+ concurrency;
29
+ continueOnError;
30
+ startNext;
31
+ onChange;
32
+ running = 0;
33
+ nextIdx = 0;
34
+ settledCount = 0;
35
+ succeededCount = 0;
36
+ failedCount = 0;
37
+ failedFast = false;
38
+ canceled = false;
39
+ paused = false;
40
+ terminalEmitted = false;
41
+ // Stable view of UploadItems; the items themselves mutate in place, but the
42
+ // array reference never changes, so we allocate it once instead of rebuilding
43
+ // it on every snapshot.
44
+ uploadsView;
45
+ // Running aggregates updated incrementally on each child event so snapshot()
46
+ // is O(1) instead of O(n) per progress tick.
47
+ aggregateBytesTransferred = 0;
48
+ aggregateTotalBytes = 0;
49
+ lastBytes;
50
+ lastTotal;
51
+ constructor(init) {
52
+ super();
53
+ this.id = init.id;
54
+ this.uploads = init.uploads;
55
+ this.concurrency = Math.max(1, init.options.concurrency ?? 3);
56
+ this.continueOnError = init.options.continueOnError ?? true;
57
+ this.startNext = init.startNext;
58
+ this.onChange = init.onChange;
59
+ this.uploadsView = init.uploads.map((h) => h.upload);
60
+ this.lastBytes = new Array(init.uploads.length).fill(0);
61
+ this.lastTotal = new Array(init.uploads.length).fill(0);
62
+ for (let i = 0; i < this.uploadsView.length; i++) {
63
+ const u = this.uploadsView[i];
64
+ this.lastBytes[i] = u.bytesTransferred;
65
+ this.lastTotal[i] = u.totalBytes;
66
+ this.aggregateBytesTransferred += u.bytesTransferred;
67
+ this.aggregateTotalBytes += u.totalBytes;
68
+ }
69
+ this.uploads.forEach((child, idx) => {
70
+ child.on("progress", (u) => this.handleChildProgress(idx, u));
71
+ child.on("success", (u) => this.handleChildSettled(idx, u, "success"));
72
+ child.on("error", (u) => this.handleChildSettled(idx, u, "error"));
73
+ child.on("canceled", (u) => this.handleChildSettled(idx, u, "canceled"));
74
+ });
75
+ }
76
+ /** @internal */
77
+ _start() {
78
+ this.fillSlots();
79
+ }
80
+ cancel() {
81
+ if (this.canceled || this.terminalEmitted) return;
82
+ this.canceled = true;
83
+ for (const h of this.uploads) {
84
+ const s = h.upload.status;
85
+ if (s === "queued" || s === "uploading" || s === "paused") {
86
+ h.cancel();
87
+ }
88
+ }
89
+ this.onChange();
90
+ }
91
+ pause() {
92
+ if (this.paused || this.canceled || this.terminalEmitted) return;
93
+ this.paused = true;
94
+ for (const h of this.uploads) {
95
+ if (h.upload.status === "uploading") h.pause();
96
+ }
97
+ this.onChange();
98
+ }
99
+ resume() {
100
+ if (!this.paused || this.canceled || this.terminalEmitted) return;
101
+ this.paused = false;
102
+ for (const h of this.uploads) {
103
+ if (h.upload.status === "paused") h.resume();
104
+ }
105
+ this.fillSlots();
106
+ this.onChange();
107
+ }
108
+ snapshot() {
109
+ const totalProgress = this.aggregateTotalBytes > 0 ? this.aggregateBytesTransferred / this.aggregateTotalBytes * 100 : 0;
110
+ return {
111
+ id: this.id,
112
+ uploads: this.uploadsView,
113
+ totalProgress,
114
+ completedCount: this.succeededCount,
115
+ failedCount: this.failedCount
116
+ };
117
+ }
118
+ updateAggregate(idx, u) {
119
+ const prevBytes = this.lastBytes[idx] ?? 0;
120
+ const prevTotal = this.lastTotal[idx] ?? 0;
121
+ this.aggregateBytesTransferred += u.bytesTransferred - prevBytes;
122
+ this.aggregateTotalBytes += u.totalBytes - prevTotal;
123
+ this.lastBytes[idx] = u.bytesTransferred;
124
+ this.lastTotal[idx] = u.totalBytes;
125
+ }
126
+ fillSlots() {
127
+ while (!this.canceled && !this.paused && !this.failedFast && this.running < this.concurrency && this.nextIdx < this.uploads.length) {
128
+ const handle = this.uploads[this.nextIdx++];
129
+ if (!handle) continue;
130
+ this.running++;
131
+ this.startNext(handle);
132
+ }
133
+ }
134
+ handleChildProgress(idx, upload) {
135
+ this.updateAggregate(idx, upload);
136
+ const snap = this.snapshot();
137
+ this.emit("progress", snap);
138
+ this.emit("change", snap);
139
+ }
140
+ handleChildSettled(idx, upload, kind) {
141
+ this.updateAggregate(idx, upload);
142
+ this.running = Math.max(0, this.running - 1);
143
+ this.settledCount++;
144
+ if (kind === "success") {
145
+ this.succeededCount++;
146
+ this.emit("uploadSuccess", upload);
147
+ } else if (kind === "error") {
148
+ this.failedCount++;
149
+ this.emit("uploadError", upload);
150
+ }
151
+ const snap = this.snapshot();
152
+ this.emit("change", snap);
153
+ if (kind === "error" && !this.continueOnError && !this.failedFast && !this.canceled) {
154
+ this.failedFast = true;
155
+ for (const h of this.uploads) {
156
+ if (h.upload.id === upload.id) continue;
157
+ const s = h.upload.status;
158
+ if (s === "queued" || s === "uploading" || s === "paused") {
159
+ h.cancel();
160
+ }
161
+ }
162
+ this.terminalEmitted = true;
163
+ this.emit("error", snap);
164
+ this.onChange();
165
+ return;
166
+ }
167
+ if (this.failedFast || this.canceled) {
168
+ this.onChange();
169
+ return;
170
+ }
171
+ this.fillSlots();
172
+ if (this.settledCount >= this.uploads.length && !this.terminalEmitted) {
173
+ this.terminalEmitted = true;
174
+ this.emit("success", snap);
175
+ }
176
+ this.onChange();
177
+ }
178
+ };
179
+
180
+ // src/core/upload-handle.ts
181
+ var UploadHandle = class extends Emitter {
182
+ upload;
183
+ task = null;
184
+ onChange;
185
+ terminated = false;
186
+ constructor(upload, onChange) {
187
+ super();
188
+ this.upload = upload;
189
+ this.onChange = onChange;
190
+ }
191
+ /** @internal */
192
+ _attachTask(task) {
193
+ this.task = task;
194
+ }
195
+ /** @internal */
196
+ _setStatus(status) {
197
+ if (this.upload.status === status) return;
198
+ this.upload.status = status;
199
+ this.emit("statusChange", this.upload);
200
+ }
201
+ /** @internal */
202
+ _reportProgress(bytesTransferred, totalBytes) {
203
+ if (this.terminated) return;
204
+ this.upload.bytesTransferred = bytesTransferred;
205
+ this.upload.totalBytes = totalBytes;
206
+ this.upload.progress = totalBytes > 0 ? bytesTransferred / totalBytes * 100 : 0;
207
+ if (this.upload.status !== "uploading") {
208
+ this._setStatus("uploading");
209
+ }
210
+ this.emit("progress", this.upload);
211
+ this.onChange();
212
+ }
213
+ /** @internal */
214
+ _reportSuccess(downloadURL) {
215
+ if (this.terminated) return;
216
+ this.terminated = true;
217
+ this.upload.downloadURL = downloadURL;
218
+ this.upload.progress = 100;
219
+ this._setStatus("success");
220
+ this.emit("success", this.upload);
221
+ this.onChange();
222
+ }
223
+ /** @internal */
224
+ _reportError(error) {
225
+ if (this.terminated) return;
226
+ this.terminated = true;
227
+ this.upload.error = error;
228
+ this._setStatus("error");
229
+ this.emit("error", this.upload);
230
+ this.onChange();
231
+ }
232
+ cancel() {
233
+ if (this.terminated) return;
234
+ this.terminated = true;
235
+ this.task?.cancel();
236
+ this._setStatus("canceled");
237
+ this.emit("canceled", this.upload);
238
+ this.onChange();
239
+ }
240
+ pause() {
241
+ if (this.terminated) return;
242
+ if (this.upload.status !== "uploading") return;
243
+ this.task?.pause?.();
244
+ this._setStatus("paused");
245
+ this.onChange();
246
+ }
247
+ resume() {
248
+ if (this.terminated) return;
249
+ if (this.upload.status !== "paused") return;
250
+ this.task?.resume?.();
251
+ this._setStatus("uploading");
252
+ this.onChange();
253
+ }
254
+ };
255
+
256
+ // src/core/upload-manager.ts
257
+ var UploadManager = class {
258
+ provider;
259
+ uploadHandles = [];
260
+ batches = [];
261
+ changeListeners = /* @__PURE__ */ new Set();
262
+ cachedState = null;
263
+ constructor(provider) {
264
+ this.provider = provider;
265
+ }
266
+ getState = () => {
267
+ if (this.cachedState === null) {
268
+ this.cachedState = {
269
+ uploads: this.uploadHandles.map((h) => h.upload),
270
+ batches: this.batches.map((b) => b.snapshot())
271
+ };
272
+ }
273
+ return this.cachedState;
274
+ };
275
+ subscribe = (listener) => {
276
+ this.changeListeners.add(listener);
277
+ return () => {
278
+ this.changeListeners.delete(listener);
279
+ };
280
+ };
281
+ uploadFile(file, options) {
282
+ const handle = this.createHandle(file);
283
+ this.uploadHandles.push(handle);
284
+ this.notifyChange();
285
+ this.startUpload(handle, options);
286
+ return handle;
287
+ }
288
+ uploadFiles(files, optionsFor, batchOptions = {}) {
289
+ const id = crypto.randomUUID();
290
+ const handles = files.map((file) => this.createHandle(file, id));
291
+ const optionsByUploadId = /* @__PURE__ */ new Map();
292
+ handles.forEach((h, i) => {
293
+ const file = files[i];
294
+ if (!file) return;
295
+ optionsByUploadId.set(h.upload.id, optionsFor(file, i));
296
+ });
297
+ this.uploadHandles.push(...handles);
298
+ const batch = new BatchHandle({
299
+ id,
300
+ uploads: handles,
301
+ options: batchOptions,
302
+ startNext: (handle) => {
303
+ const opts = optionsByUploadId.get(handle.upload.id);
304
+ if (!opts) return;
305
+ this.startUpload(handle, opts);
306
+ },
307
+ onChange: () => this.notifyChange()
308
+ });
309
+ this.batches.push(batch);
310
+ this.notifyChange();
311
+ batch._start();
312
+ return batch;
313
+ }
314
+ createHandle(file, batchId) {
315
+ const upload = {
316
+ id: crypto.randomUUID(),
317
+ file,
318
+ progress: 0,
319
+ bytesTransferred: 0,
320
+ totalBytes: file.size,
321
+ status: "queued",
322
+ ...batchId !== void 0 ? { batchId } : {}
323
+ };
324
+ return new UploadHandle(upload, () => this.notifyChange());
325
+ }
326
+ startUpload(handle, options) {
327
+ handle._setStatus("uploading");
328
+ const task = this.provider.upload(handle.upload.file, options, {
329
+ onProgress: (bytesTransferred, totalBytes) => handle._reportProgress(bytesTransferred, totalBytes),
330
+ onError: (error) => handle._reportError(error),
331
+ onSuccess: (downloadURL) => handle._reportSuccess(downloadURL)
332
+ });
333
+ handle._attachTask(task);
334
+ this.notifyChange();
335
+ }
336
+ notifyChange() {
337
+ this.cachedState = null;
338
+ const state = this.getState();
339
+ for (const listener of this.changeListeners) {
340
+ listener(state);
341
+ }
342
+ }
343
+ };
344
+
345
+ export { BatchHandle, UploadHandle, UploadManager };
346
+ //# sourceMappingURL=index.js.map
347
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/core/emitter.ts","../src/core/batch-handle.ts","../src/core/upload-handle.ts","../src/core/upload-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 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 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\nexport type BatchHandleEvents = {\n progress: UploadBatch;\n uploadSuccess: UploadItem;\n uploadError: UploadItem;\n success: UploadBatch;\n error: UploadBatch;\n change: UploadBatch;\n};\n\nexport interface BatchOptions {\n /** Maximum number of uploads in-flight at the same time. Defaults to 3. */\n concurrency?: number;\n /**\n * When true (default), a failing upload does not affect siblings; the batch\n * still emits `success` once all children have settled and callers inspect\n * individual `UploadItem` errors.\n *\n * When false, the first failure cancels all queued and in-flight siblings\n * and the batch emits `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\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\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 { UploadOptions } from \"../types/provider\";\nimport type { UploadItem, UploadState } from \"../types/upload\";\n\nexport class UploadManager {\n private provider: StorageProvider;\n private uploadHandles: UploadHandle[] = [];\n private batches: BatchHandle[] = [];\n private changeListeners = new Set<(state: UploadState) => void>();\n private cachedState: UploadState | null = null;\n\n constructor(provider: StorageProvider) {\n this.provider = provider;\n }\n\n getState = (): UploadState => {\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 subscribe = (listener: (state: UploadState) => void): (() => void) => {\n this.changeListeners.add(listener);\n return () => {\n this.changeListeners.delete(listener);\n };\n };\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 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"]}
@@ -0,0 +1,19 @@
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
+ path: string;
13
+ }
14
+
15
+ interface StorageProvider {
16
+ upload(file: File, options: UploadOptions, callbacks: ProviderUploadCallbacks): ProviderUploadTask;
17
+ }
18
+
19
+ export type { ProviderUploadCallbacks as P, StorageProvider as S, UploadOptions as U, ProviderUploadTask as a };
package/package.json ADDED
@@ -0,0 +1,59 @@
1
+ {
2
+ "name": "firebase-storage-kit",
3
+ "version": "0.0.1",
4
+ "description": "Browser upload manager for Firebase Storage with batches, concurrency, and pause/resume.",
5
+ "keywords": [
6
+ "firebase",
7
+ "firebase-storage",
8
+ "upload",
9
+ "resumable",
10
+ "browser",
11
+ "typescript"
12
+ ],
13
+ "license": "MIT",
14
+ "author": "marknesh (https://github.com/marknesh)",
15
+ "repository": {
16
+ "type": "git",
17
+ "url": "git+https://github.com/marknesh/firebase-storage-kit.git",
18
+ "directory": "packages/firebase-storage-kit"
19
+ },
20
+ "bugs": {
21
+ "url": "https://github.com/marknesh/firebase-storage-kit/issues"
22
+ },
23
+ "homepage": "https://github.com/marknesh/firebase-storage-kit/tree/main/packages/firebase-storage-kit#readme",
24
+ "type": "module",
25
+ "sideEffects": false,
26
+ "main": "./dist/index.js",
27
+ "types": "./dist/index.d.ts",
28
+ "exports": {
29
+ ".": {
30
+ "types": "./dist/index.d.ts",
31
+ "import": "./dist/index.js"
32
+ },
33
+ "./firebase": {
34
+ "types": "./dist/firebase.d.ts",
35
+ "import": "./dist/firebase.js"
36
+ }
37
+ },
38
+ "files": [
39
+ "dist"
40
+ ],
41
+ "scripts": {
42
+ "build": "tsup",
43
+ "dev": "tsup --watch",
44
+ "clean": "rm -rf dist",
45
+ "typecheck": "tsc --noEmit",
46
+ "prepublishOnly": "npm run build"
47
+ },
48
+ "peerDependencies": {
49
+ "firebase": "^12.13.0"
50
+ },
51
+ "peerDependenciesMeta": {
52
+ "firebase": {
53
+ "optional": true
54
+ }
55
+ },
56
+ "devDependencies": {
57
+ "firebase": "^12.13.0"
58
+ }
59
+ }