firebase-storage-kit 1.1.0 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +15 -0
- package/dist/index.d.ts +59 -2
- package/dist/index.js +232 -30
- package/dist/index.js.map +1 -1
- package/package.json +4 -2
package/README.md
CHANGED
|
@@ -60,6 +60,21 @@ const unsubscribe = manager.subscribe((state) => {
|
|
|
60
60
|
});
|
|
61
61
|
```
|
|
62
62
|
|
|
63
|
+
### Upload retries
|
|
64
|
+
|
|
65
|
+
Uploads retry transient failures automatically (3 retries by default, exponential backoff with jitter). Pass `retry: false` to disable, or customize:
|
|
66
|
+
|
|
67
|
+
```ts
|
|
68
|
+
const handle = manager.uploadFile(file, {
|
|
69
|
+
path: `uploads/${file.name}`,
|
|
70
|
+
retry: { maxRetries: 5, initialDelayMs: 700 },
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
handle.on("retry", ({ attempt, maxAttempts, delayMs, error }) => {
|
|
74
|
+
console.log(`Retry ${attempt}/${maxAttempts} in ${delayMs}ms`, error.message);
|
|
75
|
+
});
|
|
76
|
+
```
|
|
77
|
+
|
|
63
78
|
### Querying files
|
|
64
79
|
|
|
65
80
|
```ts
|
package/dist/index.d.ts
CHANGED
|
@@ -5,16 +5,18 @@ interface StorageState {
|
|
|
5
5
|
uploads: UploadItem[];
|
|
6
6
|
batches: UploadBatch[];
|
|
7
7
|
}
|
|
8
|
-
type UploadStatus = "queued" | "uploading" | "paused" | "success" | "error" | "canceled";
|
|
8
|
+
type UploadStatus = "queued" | "uploading" | "retrying" | "paused" | "success" | "error" | "canceled";
|
|
9
9
|
interface UploadItem {
|
|
10
10
|
id: string;
|
|
11
11
|
file: File;
|
|
12
|
+
path: string;
|
|
12
13
|
progress: number;
|
|
13
14
|
bytesTransferred: number;
|
|
14
15
|
totalBytes: number;
|
|
15
16
|
status: UploadStatus;
|
|
16
17
|
downloadURL?: string;
|
|
17
18
|
error?: Error;
|
|
19
|
+
retryAttempt?: number;
|
|
18
20
|
batchId?: string;
|
|
19
21
|
}
|
|
20
22
|
interface UploadBatch {
|
|
@@ -41,12 +43,31 @@ declare class Emitter<TEvents extends Record<string, unknown>> {
|
|
|
41
43
|
protected emit<K extends keyof TEvents>(event: K, payload: TEvents[K]): void;
|
|
42
44
|
}
|
|
43
45
|
|
|
46
|
+
interface UploadRetryEvent {
|
|
47
|
+
upload: UploadItem;
|
|
48
|
+
error: Error;
|
|
49
|
+
attempt: number;
|
|
50
|
+
maxAttempts: number;
|
|
51
|
+
delayMs: number;
|
|
52
|
+
}
|
|
53
|
+
interface UploadRetryBackoffDetails {
|
|
54
|
+
attempt: number;
|
|
55
|
+
maxAttempts: number;
|
|
56
|
+
delayMs: number;
|
|
57
|
+
error: Error;
|
|
58
|
+
}
|
|
59
|
+
interface UploadControlHooks {
|
|
60
|
+
abort: () => void;
|
|
61
|
+
pauseDuringRetry?: () => void;
|
|
62
|
+
resumeDuringRetry?: () => void;
|
|
63
|
+
}
|
|
44
64
|
type UploadHandleEvents = {
|
|
45
65
|
progress: UploadItem;
|
|
46
66
|
success: UploadItem;
|
|
47
67
|
error: UploadItem;
|
|
48
68
|
canceled: UploadItem;
|
|
49
69
|
statusChange: UploadItem;
|
|
70
|
+
retry: UploadRetryEvent;
|
|
50
71
|
};
|
|
51
72
|
/** Controls file upload and tracks its progress.
|
|
52
73
|
*
|
|
@@ -59,6 +80,8 @@ declare class UploadHandle extends Emitter<UploadHandleEvents> {
|
|
|
59
80
|
private task;
|
|
60
81
|
private onChange;
|
|
61
82
|
private terminated;
|
|
83
|
+
private controlHooks;
|
|
84
|
+
private pausedDuringRetry;
|
|
62
85
|
constructor(upload: UploadItem, onChange: () => void);
|
|
63
86
|
cancel(): void;
|
|
64
87
|
pause(): void;
|
|
@@ -107,6 +130,8 @@ declare class BatchHandle extends Emitter<BatchHandleEvents> {
|
|
|
107
130
|
private onChange;
|
|
108
131
|
private running;
|
|
109
132
|
private nextIdx;
|
|
133
|
+
/** Indices that were started via {@link fillSlots} and still hold a concurrency slot. */
|
|
134
|
+
private activeSlots;
|
|
110
135
|
private settledCount;
|
|
111
136
|
private succeededCount;
|
|
112
137
|
private failedCount;
|
|
@@ -140,10 +165,41 @@ interface ProviderUploadCallbacks {
|
|
|
140
165
|
onError: (error: Error) => void;
|
|
141
166
|
onSuccess: (downloadURL: string) => void;
|
|
142
167
|
}
|
|
168
|
+
interface RetryOptions {
|
|
169
|
+
/** Extra attempts after the first failure. Default: 3 (4 total tries). */
|
|
170
|
+
maxRetries?: number;
|
|
171
|
+
/** Initial backoff in ms. Default: 1000. */
|
|
172
|
+
initialDelayMs?: number;
|
|
173
|
+
/** Backoff cap in ms. Default: 30000. */
|
|
174
|
+
maxDelayMs?: number;
|
|
175
|
+
/** Apply jitter to backoff delays. Default: true. */
|
|
176
|
+
jitter?: boolean;
|
|
177
|
+
/** Override retry classification. */
|
|
178
|
+
isRetryable?: (error: Error) => boolean;
|
|
179
|
+
}
|
|
143
180
|
interface UploadOptions {
|
|
144
181
|
/** Object path in the bucket (Firebase), e.g. `uploads/photo.jpg`. */
|
|
145
182
|
path: string;
|
|
183
|
+
/** Retries are enabled by default. Pass `false` to disable, or an object to customize. */
|
|
184
|
+
retry?: RetryOptions | false;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
interface ResolvedRetryOptions {
|
|
188
|
+
maxRetries: number;
|
|
189
|
+
initialDelayMs: number;
|
|
190
|
+
maxDelayMs: number;
|
|
191
|
+
jitter: boolean;
|
|
192
|
+
isRetryable?: (error: Error) => boolean;
|
|
146
193
|
}
|
|
194
|
+
declare const DEFAULT_RETRY_OPTIONS: ResolvedRetryOptions;
|
|
195
|
+
/** Resolves upload retry settings. Returns `null` when retries are disabled. */
|
|
196
|
+
declare function resolveRetryOptions(retry?: RetryOptions | false): ResolvedRetryOptions | null;
|
|
197
|
+
/** Reads a Firebase-style `code` from an error without importing Firebase types. */
|
|
198
|
+
declare function getStorageErrorCode(error: Error): string | undefined;
|
|
199
|
+
/** Whether an upload error should trigger another attempt. */
|
|
200
|
+
declare function isRetryableStorageError(error: Error, options: ResolvedRetryOptions | null): boolean;
|
|
201
|
+
/** Computes backoff delay after a failed attempt (1-based). */
|
|
202
|
+
declare function computeRetryDelay(failedAttempt: number, options: ResolvedRetryOptions): number;
|
|
147
203
|
|
|
148
204
|
interface FileMetadata {
|
|
149
205
|
path: string;
|
|
@@ -175,6 +231,7 @@ declare class StorageManager$1 {
|
|
|
175
231
|
private batches;
|
|
176
232
|
private changeListeners;
|
|
177
233
|
private cachedState;
|
|
234
|
+
private uploadOptionsByHandleId;
|
|
178
235
|
constructor(provider: StorageProvider);
|
|
179
236
|
/** Current `uploads` and `batches` state. */
|
|
180
237
|
getState: () => StorageState;
|
|
@@ -214,4 +271,4 @@ declare class StorageManager extends StorageManager$1 {
|
|
|
214
271
|
constructor(storage: FirebaseStorage);
|
|
215
272
|
}
|
|
216
273
|
|
|
217
|
-
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 };
|
|
274
|
+
export { BatchHandle, type BatchHandleEvents, type BatchHandleInit, type BatchOptions, DEFAULT_RETRY_OPTIONS, type FileMetadata, type ProviderUploadCallbacks, type ProviderUploadTask, type RetryOptions, StorageManager, type StorageState, type UploadBatch, type UploadControlHooks, UploadHandle, type UploadHandleEvents, type UploadItem, type UploadOptions, type UploadRetryBackoffDetails, type UploadRetryEvent, type UploadStatus, computeRetryDelay, getStorageErrorCode, isRetryableStorageError, resolveRetryOptions };
|
package/dist/index.js
CHANGED
|
@@ -42,6 +42,8 @@ var BatchHandle = class extends Emitter {
|
|
|
42
42
|
onChange;
|
|
43
43
|
running = 0;
|
|
44
44
|
nextIdx = 0;
|
|
45
|
+
/** Indices that were started via {@link fillSlots} and still hold a concurrency slot. */
|
|
46
|
+
activeSlots = /* @__PURE__ */ new Set();
|
|
45
47
|
settledCount = 0;
|
|
46
48
|
succeededCount = 0;
|
|
47
49
|
failedCount = 0;
|
|
@@ -93,7 +95,7 @@ var BatchHandle = class extends Emitter {
|
|
|
93
95
|
this.canceled = true;
|
|
94
96
|
for (const h of this.uploads) {
|
|
95
97
|
const s = h.upload.status;
|
|
96
|
-
if (s === "queued" || s === "uploading" || s === "paused") {
|
|
98
|
+
if (s === "queued" || s === "uploading" || s === "retrying" || s === "paused") {
|
|
97
99
|
h.cancel();
|
|
98
100
|
}
|
|
99
101
|
}
|
|
@@ -103,7 +105,9 @@ var BatchHandle = class extends Emitter {
|
|
|
103
105
|
if (this.paused || this.canceled || this.terminalEmitted) return;
|
|
104
106
|
this.paused = true;
|
|
105
107
|
for (const h of this.uploads) {
|
|
106
|
-
if (h.upload.status === "uploading"
|
|
108
|
+
if (h.upload.status === "uploading" || h.upload.status === "retrying") {
|
|
109
|
+
h.pause();
|
|
110
|
+
}
|
|
107
111
|
}
|
|
108
112
|
this.onChange();
|
|
109
113
|
}
|
|
@@ -136,8 +140,10 @@ var BatchHandle = class extends Emitter {
|
|
|
136
140
|
}
|
|
137
141
|
fillSlots() {
|
|
138
142
|
while (!this.canceled && !this.paused && !this.failedFast && this.running < this.concurrency && this.nextIdx < this.uploads.length) {
|
|
139
|
-
const
|
|
140
|
-
|
|
143
|
+
const idx = this.nextIdx++;
|
|
144
|
+
const handle = this.uploads[idx];
|
|
145
|
+
if (!handle || handle.upload.status !== "queued") continue;
|
|
146
|
+
this.activeSlots.add(idx);
|
|
141
147
|
this.running++;
|
|
142
148
|
this.startNext(handle);
|
|
143
149
|
}
|
|
@@ -150,7 +156,9 @@ var BatchHandle = class extends Emitter {
|
|
|
150
156
|
}
|
|
151
157
|
handleChildSettled(idx, upload, kind) {
|
|
152
158
|
this.updateAggregate(idx, upload);
|
|
153
|
-
this.
|
|
159
|
+
if (this.activeSlots.delete(idx)) {
|
|
160
|
+
this.running = Math.max(0, this.running - 1);
|
|
161
|
+
}
|
|
154
162
|
this.settledCount++;
|
|
155
163
|
if (kind === "success") {
|
|
156
164
|
this.succeededCount++;
|
|
@@ -166,7 +174,7 @@ var BatchHandle = class extends Emitter {
|
|
|
166
174
|
for (const h of this.uploads) {
|
|
167
175
|
if (h.upload.id === upload.id) continue;
|
|
168
176
|
const s = h.upload.status;
|
|
169
|
-
if (s === "queued" || s === "uploading" || s === "paused") {
|
|
177
|
+
if (s === "queued" || s === "uploading" || s === "retrying" || s === "paused") {
|
|
170
178
|
h.cancel();
|
|
171
179
|
}
|
|
172
180
|
}
|
|
@@ -188,12 +196,79 @@ var BatchHandle = class extends Emitter {
|
|
|
188
196
|
}
|
|
189
197
|
};
|
|
190
198
|
|
|
199
|
+
// src/core/retry.ts
|
|
200
|
+
var DEFAULT_RETRY_OPTIONS = {
|
|
201
|
+
maxRetries: 3,
|
|
202
|
+
initialDelayMs: 1e3,
|
|
203
|
+
maxDelayMs: 3e4,
|
|
204
|
+
jitter: true
|
|
205
|
+
};
|
|
206
|
+
var NON_RETRYABLE_CODES = /* @__PURE__ */ new Set([
|
|
207
|
+
"storage/canceled",
|
|
208
|
+
"storage/unauthorized",
|
|
209
|
+
"storage/unauthenticated",
|
|
210
|
+
"storage/quota-exceeded",
|
|
211
|
+
"storage/invalid-argument",
|
|
212
|
+
"storage/invalid-argument-count",
|
|
213
|
+
"storage/invalid-url",
|
|
214
|
+
"storage/invalid-default-bucket",
|
|
215
|
+
"storage/no-default-bucket",
|
|
216
|
+
"storage/bucket-not-found",
|
|
217
|
+
"storage/project-not-found",
|
|
218
|
+
"storage/invalid-root-operation",
|
|
219
|
+
"storage/invalid-event-name",
|
|
220
|
+
"storage/invalid-format",
|
|
221
|
+
"storage/no-download-url",
|
|
222
|
+
"storage/unauthorized-app",
|
|
223
|
+
"storage/unsupported-environment"
|
|
224
|
+
]);
|
|
225
|
+
var RETRYABLE_CODES = /* @__PURE__ */ new Set([
|
|
226
|
+
"storage/retry-limit-exceeded",
|
|
227
|
+
"storage/unknown",
|
|
228
|
+
"storage/invalid-checksum",
|
|
229
|
+
"storage/cannot-slice-blob",
|
|
230
|
+
"storage/server-file-wrong-size"
|
|
231
|
+
]);
|
|
232
|
+
function resolveRetryOptions(retry) {
|
|
233
|
+
if (retry === false) return null;
|
|
234
|
+
return {
|
|
235
|
+
...DEFAULT_RETRY_OPTIONS,
|
|
236
|
+
...retry
|
|
237
|
+
};
|
|
238
|
+
}
|
|
239
|
+
function getStorageErrorCode(error) {
|
|
240
|
+
return error.code;
|
|
241
|
+
}
|
|
242
|
+
function isRetryableStorageError(error, options) {
|
|
243
|
+
if (!options) return false;
|
|
244
|
+
if (options.isRetryable) {
|
|
245
|
+
return options.isRetryable(error);
|
|
246
|
+
}
|
|
247
|
+
const code = getStorageErrorCode(error);
|
|
248
|
+
if (code) {
|
|
249
|
+
if (NON_RETRYABLE_CODES.has(code)) return false;
|
|
250
|
+
if (RETRYABLE_CODES.has(code)) return true;
|
|
251
|
+
return false;
|
|
252
|
+
}
|
|
253
|
+
return true;
|
|
254
|
+
}
|
|
255
|
+
function computeRetryDelay(failedAttempt, options) {
|
|
256
|
+
const base = Math.min(
|
|
257
|
+
options.maxDelayMs,
|
|
258
|
+
options.initialDelayMs * 2 ** (failedAttempt - 1)
|
|
259
|
+
);
|
|
260
|
+
if (!options.jitter) return base;
|
|
261
|
+
return Math.round(base * (0.5 + Math.random() * 0.5));
|
|
262
|
+
}
|
|
263
|
+
|
|
191
264
|
// src/core/upload-handle.ts
|
|
192
265
|
var UploadHandle = class extends Emitter {
|
|
193
266
|
upload;
|
|
194
267
|
task = null;
|
|
195
268
|
onChange;
|
|
196
269
|
terminated = false;
|
|
270
|
+
controlHooks = null;
|
|
271
|
+
pausedDuringRetry = false;
|
|
197
272
|
constructor(upload, onChange) {
|
|
198
273
|
super();
|
|
199
274
|
this.upload = upload;
|
|
@@ -204,6 +279,14 @@ var UploadHandle = class extends Emitter {
|
|
|
204
279
|
this.task = task;
|
|
205
280
|
}
|
|
206
281
|
/** @internal */
|
|
282
|
+
_registerControlHooks(hooks) {
|
|
283
|
+
this.controlHooks = hooks;
|
|
284
|
+
}
|
|
285
|
+
/** @internal */
|
|
286
|
+
_clearControlHooks() {
|
|
287
|
+
this.controlHooks = null;
|
|
288
|
+
}
|
|
289
|
+
/** @internal */
|
|
207
290
|
_setStatus(status) {
|
|
208
291
|
if (this.upload.status === status) return;
|
|
209
292
|
this.upload.status = status;
|
|
@@ -222,11 +305,39 @@ var UploadHandle = class extends Emitter {
|
|
|
222
305
|
this.onChange();
|
|
223
306
|
}
|
|
224
307
|
/** @internal */
|
|
308
|
+
_prepareRetryAttempt(attempt) {
|
|
309
|
+
if (this.terminated) return;
|
|
310
|
+
this.upload.retryAttempt = attempt;
|
|
311
|
+
this.upload.bytesTransferred = 0;
|
|
312
|
+
this.upload.progress = 0;
|
|
313
|
+
this.upload.error = void 0;
|
|
314
|
+
this.pausedDuringRetry = false;
|
|
315
|
+
this._setStatus("uploading");
|
|
316
|
+
this.onChange();
|
|
317
|
+
}
|
|
318
|
+
/** @internal */
|
|
319
|
+
_enterRetryBackoff(details) {
|
|
320
|
+
if (this.terminated) return;
|
|
321
|
+
this.upload.retryAttempt = details.attempt;
|
|
322
|
+
this.upload.error = details.error;
|
|
323
|
+
this.task = null;
|
|
324
|
+
this._setStatus("retrying");
|
|
325
|
+
this.emit("retry", {
|
|
326
|
+
upload: this.upload,
|
|
327
|
+
error: details.error,
|
|
328
|
+
attempt: details.attempt,
|
|
329
|
+
maxAttempts: details.maxAttempts,
|
|
330
|
+
delayMs: details.delayMs
|
|
331
|
+
});
|
|
332
|
+
this.onChange();
|
|
333
|
+
}
|
|
334
|
+
/** @internal */
|
|
225
335
|
_reportSuccess(downloadURL) {
|
|
226
336
|
if (this.terminated) return;
|
|
227
337
|
this.terminated = true;
|
|
228
338
|
this.upload.downloadURL = downloadURL;
|
|
229
339
|
this.upload.progress = 100;
|
|
340
|
+
this._clearControlHooks();
|
|
230
341
|
this._setStatus("success");
|
|
231
342
|
this.emit("success", this.upload);
|
|
232
343
|
this.onChange();
|
|
@@ -236,6 +347,7 @@ var UploadHandle = class extends Emitter {
|
|
|
236
347
|
if (this.terminated) return;
|
|
237
348
|
this.terminated = true;
|
|
238
349
|
this.upload.error = error;
|
|
350
|
+
this._clearControlHooks();
|
|
239
351
|
this._setStatus("error");
|
|
240
352
|
this.emit("error", this.upload);
|
|
241
353
|
this.onChange();
|
|
@@ -243,21 +355,37 @@ var UploadHandle = class extends Emitter {
|
|
|
243
355
|
cancel() {
|
|
244
356
|
if (this.terminated) return;
|
|
245
357
|
this.terminated = true;
|
|
358
|
+
this.controlHooks?.abort();
|
|
246
359
|
this.task?.cancel();
|
|
360
|
+
this._clearControlHooks();
|
|
247
361
|
this._setStatus("canceled");
|
|
248
362
|
this.emit("canceled", this.upload);
|
|
249
363
|
this.onChange();
|
|
250
364
|
}
|
|
251
365
|
pause() {
|
|
252
366
|
if (this.terminated) return;
|
|
253
|
-
if (this.upload.status
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
367
|
+
if (this.upload.status === "uploading") {
|
|
368
|
+
this.task?.pause?.();
|
|
369
|
+
this._setStatus("paused");
|
|
370
|
+
this.onChange();
|
|
371
|
+
return;
|
|
372
|
+
}
|
|
373
|
+
if (this.upload.status === "retrying") {
|
|
374
|
+
this.pausedDuringRetry = true;
|
|
375
|
+
this.controlHooks?.pauseDuringRetry?.();
|
|
376
|
+
this._setStatus("paused");
|
|
377
|
+
this.onChange();
|
|
378
|
+
}
|
|
257
379
|
}
|
|
258
380
|
resume() {
|
|
259
381
|
if (this.terminated) return;
|
|
260
382
|
if (this.upload.status !== "paused") return;
|
|
383
|
+
if (this.pausedDuringRetry) {
|
|
384
|
+
this.pausedDuringRetry = false;
|
|
385
|
+
this.controlHooks?.resumeDuringRetry?.();
|
|
386
|
+
this.onChange();
|
|
387
|
+
return;
|
|
388
|
+
}
|
|
261
389
|
this.task?.resume?.();
|
|
262
390
|
this._setStatus("uploading");
|
|
263
391
|
this.onChange();
|
|
@@ -271,6 +399,7 @@ var StorageManager = class {
|
|
|
271
399
|
batches = [];
|
|
272
400
|
changeListeners = /* @__PURE__ */ new Set();
|
|
273
401
|
cachedState = null;
|
|
402
|
+
uploadOptionsByHandleId = /* @__PURE__ */ new Map();
|
|
274
403
|
constructor(provider) {
|
|
275
404
|
this.provider = provider;
|
|
276
405
|
}
|
|
@@ -312,7 +441,8 @@ var StorageManager = class {
|
|
|
312
441
|
* @returns An {@link UploadHandle} to control the upload: `pause`, `resume`, `cancel`, and listen for progress with `.on`.
|
|
313
442
|
*/
|
|
314
443
|
uploadFile(file, options) {
|
|
315
|
-
const handle = this.createHandle(file);
|
|
444
|
+
const handle = this.createHandle(file, { path: options.path });
|
|
445
|
+
this.uploadOptionsByHandleId.set(handle.upload.id, options);
|
|
316
446
|
this.uploadHandles.push(handle);
|
|
317
447
|
this.notifyChange();
|
|
318
448
|
this.startUpload(handle, options);
|
|
@@ -330,12 +460,14 @@ var StorageManager = class {
|
|
|
330
460
|
*/
|
|
331
461
|
uploadFiles(files, optionsFor, batchOptions = {}) {
|
|
332
462
|
const id = crypto.randomUUID();
|
|
333
|
-
const handles = files.map((file) =>
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
463
|
+
const handles = files.map((file, i) => {
|
|
464
|
+
const options = optionsFor(file, i);
|
|
465
|
+
const handle = this.createHandle(file, {
|
|
466
|
+
batchId: id,
|
|
467
|
+
path: options.path
|
|
468
|
+
});
|
|
469
|
+
this.uploadOptionsByHandleId.set(handle.upload.id, options);
|
|
470
|
+
return handle;
|
|
339
471
|
});
|
|
340
472
|
this.uploadHandles.push(...handles);
|
|
341
473
|
const batch = new BatchHandle({
|
|
@@ -343,9 +475,10 @@ var StorageManager = class {
|
|
|
343
475
|
uploads: handles,
|
|
344
476
|
options: batchOptions,
|
|
345
477
|
startNext: (handle) => {
|
|
346
|
-
const
|
|
347
|
-
|
|
348
|
-
|
|
478
|
+
const options = this.uploadOptionsByHandleId.get(handle.upload.id) ?? {
|
|
479
|
+
path: handle.upload.path
|
|
480
|
+
};
|
|
481
|
+
this.startUpload(handle, options);
|
|
349
482
|
},
|
|
350
483
|
onChange: () => this.notifyChange()
|
|
351
484
|
});
|
|
@@ -354,27 +487,96 @@ var StorageManager = class {
|
|
|
354
487
|
batch._start();
|
|
355
488
|
return batch;
|
|
356
489
|
}
|
|
357
|
-
createHandle(file,
|
|
490
|
+
createHandle(file, options) {
|
|
358
491
|
const upload = {
|
|
359
492
|
id: crypto.randomUUID(),
|
|
360
493
|
file,
|
|
494
|
+
path: options.path,
|
|
361
495
|
progress: 0,
|
|
362
496
|
bytesTransferred: 0,
|
|
363
497
|
totalBytes: file.size,
|
|
364
498
|
status: "queued",
|
|
365
|
-
...batchId !== void 0 ? { batchId } : {}
|
|
499
|
+
...options.batchId !== void 0 ? { batchId: options.batchId } : {}
|
|
366
500
|
};
|
|
367
501
|
return new UploadHandle(upload, () => this.notifyChange());
|
|
368
502
|
}
|
|
369
503
|
startUpload(handle, options) {
|
|
370
|
-
|
|
371
|
-
const
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
504
|
+
const retryOptions = resolveRetryOptions(options.retry);
|
|
505
|
+
const maxAttempts = retryOptions ? retryOptions.maxRetries + 1 : 1;
|
|
506
|
+
let attempt = 0;
|
|
507
|
+
let retryTimeout = null;
|
|
508
|
+
let currentTask = null;
|
|
509
|
+
let aborted = false;
|
|
510
|
+
let pausedDuringRetry = false;
|
|
511
|
+
let pendingRetryError = null;
|
|
512
|
+
const clearRetryTimeout = () => {
|
|
513
|
+
if (retryTimeout !== null) {
|
|
514
|
+
clearTimeout(retryTimeout);
|
|
515
|
+
retryTimeout = null;
|
|
516
|
+
}
|
|
517
|
+
};
|
|
518
|
+
const abort = () => {
|
|
519
|
+
aborted = true;
|
|
520
|
+
clearRetryTimeout();
|
|
521
|
+
currentTask?.cancel();
|
|
522
|
+
currentTask = null;
|
|
523
|
+
pendingRetryError = null;
|
|
524
|
+
};
|
|
525
|
+
const scheduleRetry = (error, delayMs) => {
|
|
526
|
+
if (aborted) return;
|
|
527
|
+
pendingRetryError = error;
|
|
528
|
+
handle._enterRetryBackoff({
|
|
529
|
+
attempt: attempt + 1,
|
|
530
|
+
maxAttempts,
|
|
531
|
+
delayMs,
|
|
532
|
+
error
|
|
533
|
+
});
|
|
534
|
+
retryTimeout = setTimeout(() => {
|
|
535
|
+
retryTimeout = null;
|
|
536
|
+
if (aborted || pausedDuringRetry) return;
|
|
537
|
+
pendingRetryError = null;
|
|
538
|
+
runAttempt();
|
|
539
|
+
}, delayMs);
|
|
540
|
+
};
|
|
541
|
+
const runAttempt = () => {
|
|
542
|
+
if (aborted) return;
|
|
543
|
+
attempt += 1;
|
|
544
|
+
handle._prepareRetryAttempt(attempt);
|
|
545
|
+
currentTask = this.provider.upload(handle.upload.file, options, {
|
|
546
|
+
onProgress: (bytesTransferred, totalBytes) => handle._reportProgress(bytesTransferred, totalBytes),
|
|
547
|
+
onError: (error) => {
|
|
548
|
+
currentTask = null;
|
|
549
|
+
if (aborted) return;
|
|
550
|
+
if (attempt < maxAttempts && isRetryableStorageError(error, retryOptions)) {
|
|
551
|
+
const delayMs = retryOptions ? computeRetryDelay(attempt, retryOptions) : 0;
|
|
552
|
+
scheduleRetry(error, delayMs);
|
|
553
|
+
return;
|
|
554
|
+
}
|
|
555
|
+
handle._reportError(error);
|
|
556
|
+
},
|
|
557
|
+
onSuccess: (downloadURL) => {
|
|
558
|
+
currentTask = null;
|
|
559
|
+
if (aborted) return;
|
|
560
|
+
handle._reportSuccess(downloadURL);
|
|
561
|
+
}
|
|
562
|
+
});
|
|
563
|
+
handle._attachTask(currentTask);
|
|
564
|
+
};
|
|
565
|
+
handle._registerControlHooks({
|
|
566
|
+
abort,
|
|
567
|
+
pauseDuringRetry: () => {
|
|
568
|
+
pausedDuringRetry = true;
|
|
569
|
+
clearRetryTimeout();
|
|
570
|
+
},
|
|
571
|
+
resumeDuringRetry: () => {
|
|
572
|
+
if (aborted) return;
|
|
573
|
+
pausedDuringRetry = false;
|
|
574
|
+
if (pendingRetryError) {
|
|
575
|
+
scheduleRetry(pendingRetryError, 0);
|
|
576
|
+
}
|
|
577
|
+
}
|
|
375
578
|
});
|
|
376
|
-
|
|
377
|
-
this.notifyChange();
|
|
579
|
+
runAttempt();
|
|
378
580
|
}
|
|
379
581
|
notifyChange() {
|
|
380
582
|
this.cachedState = null;
|
|
@@ -461,6 +663,6 @@ var StorageManager2 = class extends StorageManager {
|
|
|
461
663
|
}
|
|
462
664
|
};
|
|
463
665
|
|
|
464
|
-
export { BatchHandle, StorageManager2 as StorageManager, UploadHandle };
|
|
666
|
+
export { BatchHandle, DEFAULT_RETRY_OPTIONS, StorageManager2 as StorageManager, UploadHandle, computeRetryDelay, getStorageErrorCode, isRetryableStorageError, resolveRetryOptions };
|
|
465
667
|
//# sourceMappingURL=index.js.map
|
|
466
668
|
//# 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","../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 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","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","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"]}
|
|
1
|
+
{"version":3,"sources":["../src/core/emitter.ts","../src/core/batch-handle.ts","../src/core/retry.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 (\n s === \"queued\" ||\n s === \"uploading\" ||\n s === \"retrying\" ||\n s === \"paused\"\n ) {\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.upload.status === \"retrying\") {\n h.pause();\n }\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 (\n s === \"queued\" ||\n s === \"uploading\" ||\n s === \"retrying\" ||\n s === \"paused\"\n ) {\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 type { RetryOptions } from \"../types/provider\";\n\nexport interface ResolvedRetryOptions {\n maxRetries: number;\n initialDelayMs: number;\n maxDelayMs: number;\n jitter: boolean;\n isRetryable?: (error: Error) => boolean;\n}\n\nexport const DEFAULT_RETRY_OPTIONS: ResolvedRetryOptions = {\n maxRetries: 3,\n initialDelayMs: 1000,\n maxDelayMs: 30000,\n jitter: true,\n};\n\nconst NON_RETRYABLE_CODES = new Set([\n \"storage/canceled\",\n \"storage/unauthorized\",\n \"storage/unauthenticated\",\n \"storage/quota-exceeded\",\n \"storage/invalid-argument\",\n \"storage/invalid-argument-count\",\n \"storage/invalid-url\",\n \"storage/invalid-default-bucket\",\n \"storage/no-default-bucket\",\n \"storage/bucket-not-found\",\n \"storage/project-not-found\",\n \"storage/invalid-root-operation\",\n \"storage/invalid-event-name\",\n \"storage/invalid-format\",\n \"storage/no-download-url\",\n \"storage/unauthorized-app\",\n \"storage/unsupported-environment\",\n]);\n\nconst RETRYABLE_CODES = new Set([\n \"storage/retry-limit-exceeded\",\n \"storage/unknown\",\n \"storage/invalid-checksum\",\n \"storage/cannot-slice-blob\",\n \"storage/server-file-wrong-size\",\n]);\n\n/** Resolves upload retry settings. Returns `null` when retries are disabled. */\nexport function resolveRetryOptions(\n retry?: RetryOptions | false,\n): ResolvedRetryOptions | null {\n if (retry === false) return null;\n return {\n ...DEFAULT_RETRY_OPTIONS,\n ...retry,\n };\n}\n\n/** Reads a Firebase-style `code` from an error without importing Firebase types. */\nexport function getStorageErrorCode(error: Error): string | undefined {\n return (error as { code?: string }).code;\n}\n\n/** Whether an upload error should trigger another attempt. */\nexport function isRetryableStorageError(\n error: Error,\n options: ResolvedRetryOptions | null,\n): boolean {\n if (!options) return false;\n\n if (options.isRetryable) {\n return options.isRetryable(error);\n }\n\n const code = getStorageErrorCode(error);\n if (code) {\n if (NON_RETRYABLE_CODES.has(code)) return false;\n if (RETRYABLE_CODES.has(code)) return true;\n return false;\n }\n\n return true;\n}\n\n/** Computes backoff delay after a failed attempt (1-based). */\nexport function computeRetryDelay(\n failedAttempt: number,\n options: ResolvedRetryOptions,\n): number {\n const base = Math.min(\n options.maxDelayMs,\n options.initialDelayMs * 2 ** (failedAttempt - 1),\n );\n if (!options.jitter) return base;\n return Math.round(base * (0.5 + Math.random() * 0.5));\n}\n","import { Emitter } from \"./emitter\";\n\nimport type { ProviderUploadTask } from \"../types/provider\";\nimport type { UploadItem, UploadStatus } from \"../types/upload\";\n\nexport interface UploadRetryEvent {\n upload: UploadItem;\n error: Error;\n attempt: number;\n maxAttempts: number;\n delayMs: number;\n}\n\nexport interface UploadRetryBackoffDetails {\n attempt: number;\n maxAttempts: number;\n delayMs: number;\n error: Error;\n}\n\nexport interface UploadControlHooks {\n abort: () => void;\n pauseDuringRetry?: () => void;\n resumeDuringRetry?: () => void;\n}\n\nexport type UploadHandleEvents = {\n progress: UploadItem;\n success: UploadItem;\n error: UploadItem;\n canceled: UploadItem;\n statusChange: UploadItem;\n retry: UploadRetryEvent;\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 private controlHooks: UploadControlHooks | null = null;\n private pausedDuringRetry = 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 _registerControlHooks(hooks: UploadControlHooks): void {\n this.controlHooks = hooks;\n }\n\n /** @internal */\n _clearControlHooks(): void {\n this.controlHooks = null;\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 _prepareRetryAttempt(attempt: number): void {\n if (this.terminated) return;\n this.upload.retryAttempt = attempt;\n this.upload.bytesTransferred = 0;\n this.upload.progress = 0;\n this.upload.error = undefined;\n this.pausedDuringRetry = false;\n this._setStatus(\"uploading\");\n this.onChange();\n }\n\n /** @internal */\n _enterRetryBackoff(details: UploadRetryBackoffDetails): void {\n if (this.terminated) return;\n this.upload.retryAttempt = details.attempt;\n this.upload.error = details.error;\n this.task = null;\n this._setStatus(\"retrying\");\n this.emit(\"retry\", {\n upload: this.upload,\n error: details.error,\n attempt: details.attempt,\n maxAttempts: details.maxAttempts,\n delayMs: details.delayMs,\n });\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._clearControlHooks();\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._clearControlHooks();\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.controlHooks?.abort();\n this.task?.cancel();\n this._clearControlHooks();\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\") {\n this.task?.pause?.();\n this._setStatus(\"paused\");\n this.onChange();\n return;\n }\n if (this.upload.status === \"retrying\") {\n this.pausedDuringRetry = true;\n this.controlHooks?.pauseDuringRetry?.();\n this._setStatus(\"paused\");\n this.onChange();\n }\n }\n\n resume(): void {\n if (this.terminated) return;\n if (this.upload.status !== \"paused\") return;\n\n if (this.pausedDuringRetry) {\n this.pausedDuringRetry = false;\n this.controlHooks?.resumeDuringRetry?.();\n this.onChange();\n return;\n }\n\n this.task?.resume?.();\n this._setStatus(\"uploading\");\n this.onChange();\n }\n}\n","import { BatchHandle, type BatchOptions } from \"./batch-handle\";\nimport {\n computeRetryDelay,\n isRetryableStorageError,\n resolveRetryOptions,\n} from \"./retry\";\nimport { UploadHandle } from \"./upload-handle\";\n\nimport type { StorageProvider } from \"../providers/provider\";\nimport type { FileMetadata } from \"../types/metadata\";\nimport type { ProviderUploadTask, 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 private uploadOptionsByHandleId = new Map<string, UploadOptions>();\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.uploadOptionsByHandleId.set(handle.upload.id, options);\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 const handle = this.createHandle(file, {\n batchId: id,\n path: options.path,\n });\n this.uploadOptionsByHandleId.set(handle.upload.id, options);\n return handle;\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 options = this.uploadOptionsByHandleId.get(handle.upload.id) ?? {\n path: handle.upload.path,\n };\n this.startUpload(handle, options);\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 const retryOptions = resolveRetryOptions(options.retry);\n const maxAttempts = retryOptions ? retryOptions.maxRetries + 1 : 1;\n\n let attempt = 0;\n let retryTimeout: ReturnType<typeof setTimeout> | null = null;\n let currentTask: ProviderUploadTask | null = null;\n let aborted = false;\n let pausedDuringRetry = false;\n let pendingRetryError: Error | null = null;\n\n const clearRetryTimeout = (): void => {\n if (retryTimeout !== null) {\n clearTimeout(retryTimeout);\n retryTimeout = null;\n }\n };\n\n const abort = (): void => {\n aborted = true;\n clearRetryTimeout();\n currentTask?.cancel();\n currentTask = null;\n pendingRetryError = null;\n };\n\n const scheduleRetry = (error: Error, delayMs: number): void => {\n if (aborted) return;\n\n pendingRetryError = error;\n handle._enterRetryBackoff({\n attempt: attempt + 1,\n maxAttempts,\n delayMs,\n error,\n });\n\n retryTimeout = setTimeout(() => {\n retryTimeout = null;\n if (aborted || pausedDuringRetry) return;\n pendingRetryError = null;\n runAttempt();\n }, delayMs);\n };\n\n const runAttempt = (): void => {\n if (aborted) return;\n\n attempt += 1;\n handle._prepareRetryAttempt(attempt);\n\n currentTask = this.provider.upload(handle.upload.file, options, {\n onProgress: (bytesTransferred, totalBytes) =>\n handle._reportProgress(bytesTransferred, totalBytes),\n onError: (error) => {\n currentTask = null;\n if (aborted) return;\n\n if (\n attempt < maxAttempts &&\n isRetryableStorageError(error, retryOptions)\n ) {\n const delayMs = retryOptions\n ? computeRetryDelay(attempt, retryOptions)\n : 0;\n scheduleRetry(error, delayMs);\n return;\n }\n\n handle._reportError(error);\n },\n onSuccess: (downloadURL) => {\n currentTask = null;\n if (aborted) return;\n handle._reportSuccess(downloadURL);\n },\n });\n handle._attachTask(currentTask);\n };\n\n handle._registerControlHooks({\n abort,\n pauseDuringRetry: () => {\n pausedDuringRetry = true;\n clearRetryTimeout();\n },\n resumeDuringRetry: () => {\n if (aborted) return;\n pausedDuringRetry = false;\n if (pendingRetryError) {\n scheduleRetry(pendingRetryError, 0);\n }\n },\n });\n\n runAttempt();\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.
|
|
3
|
+
"version": "1.2.0",
|
|
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
|
|
7
|
+
"firebase storage",
|
|
8
8
|
"storage",
|
|
9
9
|
"upload",
|
|
10
10
|
"resumable",
|
|
@@ -40,6 +40,8 @@
|
|
|
40
40
|
"dev": "tsup --watch",
|
|
41
41
|
"clean": "rm -rf dist",
|
|
42
42
|
"typecheck": "tsc --noEmit",
|
|
43
|
+
"test": "bun test",
|
|
44
|
+
"test:watch": "bun test --watch",
|
|
43
45
|
"prepublishOnly": "npm run build"
|
|
44
46
|
},
|
|
45
47
|
"peerDependencies": {
|