@ttt-productions/upload-core 0.7.6 → 0.7.8

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.
@@ -1,6 +1,5 @@
1
1
  import type { UploadSessionPersistenceAdapter } from '../types.js';
2
2
  export declare function createLocalStorageUploadSessionPersistence(opts?: {
3
- /** Prefix for keys. Default: "upload_session:" */
4
3
  prefix?: string;
5
4
  }): UploadSessionPersistenceAdapter;
6
5
  //# sourceMappingURL=localstorage.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"localstorage.d.ts","sourceRoot":"","sources":["../../src/browser/localstorage.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,+BAA+B,EAAsB,MAAM,aAAa,CAAC;AAUvF,wBAAgB,0CAA0C,CAAC,IAAI,CAAC,EAAE;IAChE,kDAAkD;IAClD,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB,GAAG,+BAA+B,CA0ClC"}
1
+ {"version":3,"file":"localstorage.d.ts","sourceRoot":"","sources":["../../src/browser/localstorage.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,+BAA+B,EAAsB,MAAM,aAAa,CAAC;AAUvF,wBAAgB,0CAA0C,CAAC,IAAI,CAAC,EAAE;IAChE,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB,GAAG,+BAA+B,CA0ClC"}
@@ -1 +1 @@
1
- {"version":3,"file":"localstorage.js","sourceRoot":"","sources":["../../src/browser/localstorage.ts"],"names":[],"mappings":"AAEA,SAAS,eAAe;IACtB,IAAI,CAAC;QACH,OAAO,OAAO,MAAM,KAAK,WAAW,IAAI,CAAC,CAAC,MAAM,CAAC,YAAY,CAAC;IAChE,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC;AAED,MAAM,UAAU,0CAA0C,CAAC,IAG1D;IACC,MAAM,MAAM,GAAG,IAAI,EAAE,MAAM,IAAI,iBAAiB,CAAC;IAEjD,MAAM,CAAC,GAAG,CAAC,EAAU,EAAE,EAAE,CAAC,GAAG,MAAM,GAAG,EAAE,EAAE,CAAC;IAE3C,MAAM,SAAS,GAAG,CAAC,GAAkB,EAA6B,EAAE;QAClE,IAAI,CAAC,GAAG;YAAE,OAAO,IAAI,CAAC;QACtB,IAAI,CAAC;YACH,OAAO,IAAI,CAAC,KAAK,CAAC,GAAG,CAAuB,CAAC;QAC/C,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,IAAI,CAAC;QACd,CAAC;IACH,CAAC,CAAC;IAEF,OAAO;QACL,OAAO,EAAE,GAAG,EAAE;YACZ,IAAI,CAAC,eAAe,EAAE;gBAAE,OAAO,EAAE,CAAC;YAClC,MAAM,GAAG,GAAa,EAAE,CAAC;YACzB,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,MAAM,CAAC,YAAY,CAAC,MAAM,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC;gBACvD,MAAM,GAAG,GAAG,MAAM,CAAC,YAAY,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;gBACvC,IAAI,CAAC,GAAG;oBAAE,SAAS;gBACnB,IAAI,CAAC,GAAG,CAAC,UAAU,CAAC,MAAM,CAAC;oBAAE,SAAS;gBACtC,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC;YACrC,CAAC;YACD,OAAO,GAAG,CAAC;QACb,CAAC;QAED,GAAG,EAAE,CAAC,EAAU,EAAE,EAAE;YAClB,IAAI,CAAC,eAAe,EAAE;gBAAE,OAAO,IAAI,CAAC;YACpC,OAAO,SAAS,CAAC,MAAM,CAAC,YAAY,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;QACvD,CAAC;QAED,GAAG,EAAE,CAAC,EAAU,EAAE,KAAyB,EAAE,EAAE;YAC7C,IAAI,CAAC,eAAe,EAAE;gBAAE,OAAO;YAC/B,MAAM,CAAC,YAAY,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC,CAAC;QAC5D,CAAC;QAED,MAAM,EAAE,CAAC,EAAU,EAAE,EAAE;YACrB,IAAI,CAAC,eAAe,EAAE;gBAAE,OAAO;YAC/B,MAAM,CAAC,YAAY,CAAC,UAAU,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;QACxC,CAAC;KACF,CAAC;AACJ,CAAC","sourcesContent":["import type { UploadSessionPersistenceAdapter, UploadSessionState } from '../types.js';\n\nfunction hasLocalStorage(): boolean {\n try {\n return typeof window !== 'undefined' && !!window.localStorage;\n } catch {\n return false;\n }\n}\n\nexport function createLocalStorageUploadSessionPersistence(opts?: {\n /** Prefix for keys. Default: \"upload_session:\" */\n prefix?: string;\n}): UploadSessionPersistenceAdapter {\n const prefix = opts?.prefix ?? 'upload_session:';\n\n const k = (id: string) => `${prefix}${id}`;\n\n const safeParse = (raw: string | null): UploadSessionState | null => {\n if (!raw) return null;\n try {\n return JSON.parse(raw) as UploadSessionState;\n } catch {\n return null;\n }\n };\n\n return {\n listIds: () => {\n if (!hasLocalStorage()) return [];\n const ids: string[] = [];\n for (let i = 0; i < window.localStorage.length; i += 1) {\n const key = window.localStorage.key(i);\n if (!key) continue;\n if (!key.startsWith(prefix)) continue;\n ids.push(key.slice(prefix.length));\n }\n return ids;\n },\n\n get: (id: string) => {\n if (!hasLocalStorage()) return null;\n return safeParse(window.localStorage.getItem(k(id)));\n },\n\n set: (id: string, state: UploadSessionState) => {\n if (!hasLocalStorage()) return;\n window.localStorage.setItem(k(id), JSON.stringify(state));\n },\n\n remove: (id: string) => {\n if (!hasLocalStorage()) return;\n window.localStorage.removeItem(k(id));\n },\n };\n}\n"]}
1
+ {"version":3,"file":"localstorage.js","sourceRoot":"","sources":["../../src/browser/localstorage.ts"],"names":[],"mappings":"AAEA,SAAS,eAAe;IACtB,IAAI,CAAC;QACH,OAAO,OAAO,MAAM,KAAK,WAAW,IAAI,CAAC,CAAC,MAAM,CAAC,YAAY,CAAC;IAChE,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC;AAED,MAAM,UAAU,0CAA0C,CAAC,IAE1D;IACC,MAAM,MAAM,GAAG,IAAI,EAAE,MAAM,IAAI,iBAAiB,CAAC;IAEjD,MAAM,CAAC,GAAG,CAAC,EAAU,EAAE,EAAE,CAAC,GAAG,MAAM,GAAG,EAAE,EAAE,CAAC;IAE3C,MAAM,SAAS,GAAG,CAAC,GAAkB,EAA6B,EAAE;QAClE,IAAI,CAAC,GAAG;YAAE,OAAO,IAAI,CAAC;QACtB,IAAI,CAAC;YACH,OAAO,IAAI,CAAC,KAAK,CAAC,GAAG,CAAuB,CAAC;QAC/C,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,IAAI,CAAC;QACd,CAAC;IACH,CAAC,CAAC;IAEF,OAAO;QACL,OAAO,EAAE,GAAG,EAAE;YACZ,IAAI,CAAC,eAAe,EAAE;gBAAE,OAAO,EAAE,CAAC;YAClC,MAAM,GAAG,GAAa,EAAE,CAAC;YACzB,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,MAAM,CAAC,YAAY,CAAC,MAAM,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC;gBACvD,MAAM,GAAG,GAAG,MAAM,CAAC,YAAY,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;gBACvC,IAAI,CAAC,GAAG;oBAAE,SAAS;gBACnB,IAAI,CAAC,GAAG,CAAC,UAAU,CAAC,MAAM,CAAC;oBAAE,SAAS;gBACtC,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC;YACrC,CAAC;YACD,OAAO,GAAG,CAAC;QACb,CAAC;QAED,GAAG,EAAE,CAAC,EAAU,EAAE,EAAE;YAClB,IAAI,CAAC,eAAe,EAAE;gBAAE,OAAO,IAAI,CAAC;YACpC,OAAO,SAAS,CAAC,MAAM,CAAC,YAAY,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;QACvD,CAAC;QAED,GAAG,EAAE,CAAC,EAAU,EAAE,KAAyB,EAAE,EAAE;YAC7C,IAAI,CAAC,eAAe,EAAE;gBAAE,OAAO;YAC/B,MAAM,CAAC,YAAY,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC,CAAC;QAC5D,CAAC;QAED,MAAM,EAAE,CAAC,EAAU,EAAE,EAAE;YACrB,IAAI,CAAC,eAAe,EAAE;gBAAE,OAAO;YAC/B,MAAM,CAAC,YAAY,CAAC,UAAU,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;QACxC,CAAC;KACF,CAAC;AACJ,CAAC","sourcesContent":["import type { UploadSessionPersistenceAdapter, UploadSessionState } from '../types.js';\n\nfunction hasLocalStorage(): boolean {\n try {\n return typeof window !== 'undefined' && !!window.localStorage;\n } catch {\n return false;\n }\n}\n\nexport function createLocalStorageUploadSessionPersistence(opts?: {\n prefix?: string;\n}): UploadSessionPersistenceAdapter {\n const prefix = opts?.prefix ?? 'upload_session:';\n\n const k = (id: string) => `${prefix}${id}`;\n\n const safeParse = (raw: string | null): UploadSessionState | null => {\n if (!raw) return null;\n try {\n return JSON.parse(raw) as UploadSessionState;\n } catch {\n return null;\n }\n };\n\n return {\n listIds: () => {\n if (!hasLocalStorage()) return [];\n const ids: string[] = [];\n for (let i = 0; i < window.localStorage.length; i += 1) {\n const key = window.localStorage.key(i);\n if (!key) continue;\n if (!key.startsWith(prefix)) continue;\n ids.push(key.slice(prefix.length));\n }\n return ids;\n },\n\n get: (id: string) => {\n if (!hasLocalStorage()) return null;\n return safeParse(window.localStorage.getItem(k(id)));\n },\n\n set: (id: string, state: UploadSessionState) => {\n if (!hasLocalStorage()) return;\n window.localStorage.setItem(k(id), JSON.stringify(state));\n },\n\n remove: (id: string) => {\n if (!hasLocalStorage()) return;\n window.localStorage.removeItem(k(id));\n },\n };\n}\n"]}
@@ -12,7 +12,6 @@ export interface UploadFileResumableArgs {
12
12
  file: Blob | File;
13
13
  metadata?: UploadMetadata;
14
14
  onProgress?: UploadProgressHandler;
15
- /** Optional cancellation signal. */
16
15
  signal?: AbortSignal;
17
16
  }
18
17
  export interface DeleteFileArgs {
@@ -25,16 +24,10 @@ export interface UploadController {
25
24
  pause: () => boolean;
26
25
  resume: () => boolean;
27
26
  cancel: () => boolean;
28
- /** Resolves on success, rejects on error/cancel. */
29
27
  done: Promise<UploadFileResumableResult>;
30
28
  }
31
29
  export interface StartUploadArgs extends Omit<UploadFileResumableArgs, 'onProgress'> {
32
- /** Optional stable id for UI tracking. */
33
30
  id?: string;
34
- /**
35
- * UploadQueue-only: higher numbers run sooner.
36
- * Ignored by startResumableUpload/uploadFileResumable.
37
- */
38
31
  priority?: number;
39
32
  }
40
33
  //# sourceMappingURL=types.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/browser/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,eAAe,EACf,cAAc,EACd,UAAU,EACV,kBAAkB,EACnB,MAAM,kBAAkB,CAAC;AAC1B,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,aAAa,CAAC;AAE7D,MAAM,MAAM,qBAAqB,GAAG,CAAC,CAAC,EAAE;IACtC,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,kBAAkB,CAAC;CAC/B,KAAK,IAAI,CAAC;AAEX,MAAM,WAAW,uBAAuB;IACtC,OAAO,EAAE,eAAe,CAAC;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,IAAI,GAAG,IAAI,CAAC;IAClB,QAAQ,CAAC,EAAE,cAAc,CAAC;IAC1B,UAAU,CAAC,EAAE,qBAAqB,CAAC;IAEnC,oCAAoC;IACpC,MAAM,CAAC,EAAE,WAAW,CAAC;CACtB;AAED,MAAM,WAAW,cAAc;IAC7B,OAAO,EAAE,eAAe,CAAC;IACzB,IAAI,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,gBAAgB;IAC/B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,UAAU,CAAC;IAEjB,KAAK,EAAE,MAAM,OAAO,CAAC;IACrB,MAAM,EAAE,MAAM,OAAO,CAAC;IACtB,MAAM,EAAE,MAAM,OAAO,CAAC;IAEtB,oDAAoD;IACpD,IAAI,EAAE,OAAO,CAAC,yBAAyB,CAAC,CAAC;CAC1C;AAED,MAAM,WAAW,eAAgB,SAAQ,IAAI,CAAC,uBAAuB,EAAE,YAAY,CAAC;IAClF,0CAA0C;IAC1C,EAAE,CAAC,EAAE,MAAM,CAAC;IAEZ;;;OAGG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/browser/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,eAAe,EACf,cAAc,EACd,UAAU,EACV,kBAAkB,EACnB,MAAM,kBAAkB,CAAC;AAC1B,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,aAAa,CAAC;AAE7D,MAAM,MAAM,qBAAqB,GAAG,CAAC,CAAC,EAAE;IACtC,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,kBAAkB,CAAC;CAC/B,KAAK,IAAI,CAAC;AAEX,MAAM,WAAW,uBAAuB;IACtC,OAAO,EAAE,eAAe,CAAC;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,IAAI,GAAG,IAAI,CAAC;IAClB,QAAQ,CAAC,EAAE,cAAc,CAAC;IAC1B,UAAU,CAAC,EAAE,qBAAqB,CAAC;IAEnC,MAAM,CAAC,EAAE,WAAW,CAAC;CACtB;AAED,MAAM,WAAW,cAAc;IAC7B,OAAO,EAAE,eAAe,CAAC;IACzB,IAAI,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,gBAAgB;IAC/B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,UAAU,CAAC;IAEjB,KAAK,EAAE,MAAM,OAAO,CAAC;IACrB,MAAM,EAAE,MAAM,OAAO,CAAC;IACtB,MAAM,EAAE,MAAM,OAAO,CAAC;IAEtB,IAAI,EAAE,OAAO,CAAC,yBAAyB,CAAC,CAAC;CAC1C;AAED,MAAM,WAAW,eAAgB,SAAQ,IAAI,CAAC,uBAAuB,EAAE,YAAY,CAAC;IAClF,EAAE,CAAC,EAAE,MAAM,CAAC;IAEZ,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB"}
@@ -1 +1 @@
1
- {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/browser/types.ts"],"names":[],"mappings":"","sourcesContent":["import type {\n FirebaseStorage,\n UploadMetadata,\n UploadTask,\n UploadTaskSnapshot,\n} from 'firebase/storage';\nimport type { UploadFileResumableResult } from '../types.js';\n\nexport type UploadProgressHandler = (p: {\n transferred: number;\n total: number;\n percent: number; // 0..100\n snapshot?: UploadTaskSnapshot;\n}) => void;\n\nexport interface UploadFileResumableArgs {\n storage: FirebaseStorage;\n path: string;\n file: Blob | File;\n metadata?: UploadMetadata;\n onProgress?: UploadProgressHandler;\n\n /** Optional cancellation signal. */\n signal?: AbortSignal;\n}\n\nexport interface DeleteFileArgs {\n storage: FirebaseStorage;\n path: string;\n}\n\nexport interface UploadController {\n id: string;\n task: UploadTask;\n\n pause: () => boolean;\n resume: () => boolean;\n cancel: () => boolean;\n\n /** Resolves on success, rejects on error/cancel. */\n done: Promise<UploadFileResumableResult>;\n}\n\nexport interface StartUploadArgs extends Omit<UploadFileResumableArgs, 'onProgress'> {\n /** Optional stable id for UI tracking. */\n id?: string;\n\n /**\n * UploadQueue-only: higher numbers run sooner.\n * Ignored by startResumableUpload/uploadFileResumable.\n */\n priority?: number;\n}\n"]}
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/browser/types.ts"],"names":[],"mappings":"","sourcesContent":["import type {\n FirebaseStorage,\n UploadMetadata,\n UploadTask,\n UploadTaskSnapshot,\n} from 'firebase/storage';\nimport type { UploadFileResumableResult } from '../types.js';\n\nexport type UploadProgressHandler = (p: {\n transferred: number;\n total: number;\n percent: number;\n snapshot?: UploadTaskSnapshot;\n}) => void;\n\nexport interface UploadFileResumableArgs {\n storage: FirebaseStorage;\n path: string;\n file: Blob | File;\n metadata?: UploadMetadata;\n onProgress?: UploadProgressHandler;\n\n signal?: AbortSignal;\n}\n\nexport interface DeleteFileArgs {\n storage: FirebaseStorage;\n path: string;\n}\n\nexport interface UploadController {\n id: string;\n task: UploadTask;\n\n pause: () => boolean;\n resume: () => boolean;\n cancel: () => boolean;\n\n done: Promise<UploadFileResumableResult>;\n}\n\nexport interface StartUploadArgs extends Omit<UploadFileResumableArgs, 'onProgress'> {\n id?: string;\n\n priority?: number;\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"upload-queue.js","sourceRoot":"","sources":["../../src/browser/upload-queue.ts"],"names":[],"mappings":"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","sourcesContent":["import type { UploadFileResumableResult, UploadQueueOptions } from '../types.js';\nimport type { UploadController, StartUploadArgs } from './types.js';\nimport { getFileSize } from '../utils/file-size.js';\nimport { upsertUploadSession, removeUploadSession } from '../utils/upload-store.js';\nimport { startResumableUpload } from './upload.js';\n\nfunction safeId() {\n return Math.random().toString(36).slice(2) + Date.now().toString(36);\n}\nfunction noopBool() {\n return false;\n}\n\ntype Job = {\n id: string;\n args: StartUploadArgs & { priority?: number };\n priority: number; // higher = sooner\n seq: number; // FIFO within same priority\n resolveController: (c: UploadController) => void;\n rejectController: (e: unknown) => void;\n};\n\nexport class UploadQueue {\n private concurrency: number;\n private running = 0;\n private pending: Job[] = [];\n private seq = 0;\n\n constructor(opts?: UploadQueueOptions) {\n this.concurrency = Math.max(1, opts?.concurrency ?? 2);\n }\n\n setConcurrency(n: number) {\n this.concurrency = Math.max(1, n);\n this.pump();\n }\n\n getPendingCount() {\n return this.pending.length;\n }\n\n getRunningCount() {\n return this.running;\n }\n\n enqueue(args: StartUploadArgs & { priority?: number }): UploadController {\n const id = args.id ?? `upl_${safeId()}`;\n const priority = args.priority ?? 0;\n\n const startedAt = Date.now();\n upsertUploadSession({\n id,\n status: 'queued',\n path: args.path,\n transferred: 0,\n total: getFileSize(args.file),\n percent: 0,\n startedAt,\n updatedAt: startedAt,\n });\n\n let real: UploadController | null = null;\n\n let resolveDone!: (r: UploadFileResumableResult) => void;\n let rejectDone!: (e: unknown) => void;\n\n const done = new Promise<UploadFileResumableResult>((res, rej) => {\n resolveDone = res;\n rejectDone = rej;\n });\n\n const controller: UploadController = {\n id,\n task: null as any,\n pause: () => (real ? real.pause() : noopBool()),\n resume: () => (real ? real.resume() : noopBool()),\n cancel: () => {\n if (real) return real.cancel();\n this.pending = this.pending.filter((j) => j.id !== id);\n removeUploadSession(id);\n rejectDone(new DOMException('Aborted', 'AbortError'));\n return true;\n },\n done,\n };\n\n const job: Job = {\n id,\n args: { ...args, id },\n priority,\n seq: this.seq++,\n resolveController: (c) => {\n real = c;\n controller.task = c.task;\n controller.pause = c.pause;\n controller.resume = c.resume;\n controller.cancel = c.cancel;\n c.done.then(resolveDone, rejectDone);\n },\n rejectController: (e) => rejectDone(e),\n };\n\n this.pending.push(job);\n this.pump();\n\n return controller;\n }\n\n private pump() {\n while (this.running < this.concurrency && this.pending.length > 0) {\n this.pending.sort((a, b) => b.priority - a.priority || a.seq - b.seq);\n\n const job = this.pending.shift()!;\n this.running += 1;\n\n try {\n const { priority: _p, ...rest } = job.args;\n const c = startResumableUpload({\n ...(rest as any),\n id: job.id,\n });\n\n job.resolveController(c);\n\n c.done.finally(() => {\n this.running -= 1;\n this.pump();\n });\n } catch (e) {\n this.running -= 1;\n\n upsertUploadSession({\n id: job.id,\n status: 'error',\n path: job.args.path,\n error: e,\n updatedAt: Date.now(),\n });\n\n job.rejectController(e);\n this.pump();\n }\n }\n }\n}\n"]}
1
+ {"version":3,"file":"upload-queue.js","sourceRoot":"","sources":["../../src/browser/upload-queue.ts"],"names":[],"mappings":"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","sourcesContent":["import type { UploadFileResumableResult, UploadQueueOptions } from '../types.js';\nimport type { UploadController, StartUploadArgs } from './types.js';\nimport { getFileSize } from '../utils/file-size.js';\nimport { upsertUploadSession, removeUploadSession } from '../utils/upload-store.js';\nimport { startResumableUpload } from './upload.js';\n\nfunction safeId() {\n return Math.random().toString(36).slice(2) + Date.now().toString(36);\n}\nfunction noopBool() {\n return false;\n}\n\ntype Job = {\n id: string;\n args: StartUploadArgs & { priority?: number };\n priority: number;\n seq: number;\n resolveController: (c: UploadController) => void;\n rejectController: (e: unknown) => void;\n};\n\nexport class UploadQueue {\n private concurrency: number;\n private running = 0;\n private pending: Job[] = [];\n private seq = 0;\n\n constructor(opts?: UploadQueueOptions) {\n this.concurrency = Math.max(1, opts?.concurrency ?? 2);\n }\n\n setConcurrency(n: number) {\n this.concurrency = Math.max(1, n);\n this.pump();\n }\n\n getPendingCount() {\n return this.pending.length;\n }\n\n getRunningCount() {\n return this.running;\n }\n\n enqueue(args: StartUploadArgs & { priority?: number }): UploadController {\n const id = args.id ?? `upl_${safeId()}`;\n const priority = args.priority ?? 0;\n\n const startedAt = Date.now();\n upsertUploadSession({\n id,\n status: 'queued',\n path: args.path,\n transferred: 0,\n total: getFileSize(args.file),\n percent: 0,\n startedAt,\n updatedAt: startedAt,\n });\n\n let real: UploadController | null = null;\n\n let resolveDone!: (r: UploadFileResumableResult) => void;\n let rejectDone!: (e: unknown) => void;\n\n const done = new Promise<UploadFileResumableResult>((res, rej) => {\n resolveDone = res;\n rejectDone = rej;\n });\n\n const controller: UploadController = {\n id,\n task: null as any,\n pause: () => (real ? real.pause() : noopBool()),\n resume: () => (real ? real.resume() : noopBool()),\n cancel: () => {\n if (real) return real.cancel();\n this.pending = this.pending.filter((j) => j.id !== id);\n removeUploadSession(id);\n rejectDone(new DOMException('Aborted', 'AbortError'));\n return true;\n },\n done,\n };\n\n const job: Job = {\n id,\n args: { ...args, id },\n priority,\n seq: this.seq++,\n resolveController: (c) => {\n real = c;\n controller.task = c.task;\n controller.pause = c.pause;\n controller.resume = c.resume;\n controller.cancel = c.cancel;\n c.done.then(resolveDone, rejectDone);\n },\n rejectController: (e) => rejectDone(e),\n };\n\n this.pending.push(job);\n this.pump();\n\n return controller;\n }\n\n private pump() {\n while (this.running < this.concurrency && this.pending.length > 0) {\n this.pending.sort((a, b) => b.priority - a.priority || a.seq - b.seq);\n\n const job = this.pending.shift()!;\n this.running += 1;\n\n try {\n const { priority: _p, ...rest } = job.args;\n const c = startResumableUpload({\n ...(rest as any),\n id: job.id,\n });\n\n job.resolveController(c);\n\n c.done.finally(() => {\n this.running -= 1;\n this.pump();\n });\n } catch (e) {\n this.running -= 1;\n\n upsertUploadSession({\n id: job.id,\n status: 'error',\n path: job.args.path,\n error: e,\n updatedAt: Date.now(),\n });\n\n job.rejectController(e);\n this.pump();\n }\n }\n }\n}\n"]}
@@ -12,15 +12,6 @@ export declare function startResumableUpload(args: {
12
12
  }): UploadController;
13
13
  export declare function uploadFileResumable(args: Omit<Parameters<typeof startResumableUpload>[0], 'id'> & {
14
14
  id?: string;
15
- /**
16
- * EXPLICIT opt-in for the NEUTRAL content type: a picker file with unknown
17
- * browser metadata must reach the server AS unknown
18
- * (`application/octet-stream`) so the server-side byte inspector — the only
19
- * classification authority — sees it unrewritten. Default false keeps the
20
- * media-only gate for every other consumer. A concrete media MIME is still validated either
21
- * way; this flag never permits arbitrary declared types, only the one
22
- * neutral value.
23
- */
24
15
  allowNeutralContentType?: boolean;
25
16
  }): Promise<UploadFileResumableResult>;
26
17
  //# sourceMappingURL=upload.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"upload.d.ts","sourceRoot":"","sources":["../../src/browser/upload.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,aAAa,CAAC;AAC7D,OAAO,KAAK,EAAE,uBAAuB,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAM5E,OAAO,EAGL,KAAK,eAAe,EACpB,KAAK,cAAc,EAEpB,MAAM,kBAAkB,CAAC;AAY1B,wBAAgB,oBAAoB,CAAC,IAAI,EAAE;IACzC,EAAE,EAAE,MAAM,CAAC;IACX,OAAO,EAAE,eAAe,CAAC;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,IAAI,CAAC;IACX,QAAQ,CAAC,EAAE,cAAc,CAAC;IAC1B,UAAU,CAAC,EAAE,uBAAuB,CAAC,YAAY,CAAC,CAAC;IACnD,MAAM,CAAC,EAAE,WAAW,CAAC;CACtB,GAAG,gBAAgB,CAiKnB;AAED,wBAAsB,mBAAmB,CACvC,IAAI,EAAE,IAAI,CAAC,UAAU,CAAC,OAAO,oBAAoB,CAAC,CAAC,CAAC,CAAC,EAAE,IAAI,CAAC,GAAG;IAC7D,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ;;;;;;;;OAQG;IACH,uBAAuB,CAAC,EAAE,OAAO,CAAC;CACnC,GACA,OAAO,CAAC,yBAAyB,CAAC,CAwBpC"}
1
+ {"version":3,"file":"upload.d.ts","sourceRoot":"","sources":["../../src/browser/upload.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,aAAa,CAAC;AAC7D,OAAO,KAAK,EAAE,uBAAuB,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAM5E,OAAO,EAGL,KAAK,eAAe,EACpB,KAAK,cAAc,EAEpB,MAAM,kBAAkB,CAAC;AAY1B,wBAAgB,oBAAoB,CAAC,IAAI,EAAE;IACzC,EAAE,EAAE,MAAM,CAAC;IACX,OAAO,EAAE,eAAe,CAAC;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,IAAI,CAAC;IACX,QAAQ,CAAC,EAAE,cAAc,CAAC;IAC1B,UAAU,CAAC,EAAE,uBAAuB,CAAC,YAAY,CAAC,CAAC;IACnD,MAAM,CAAC,EAAE,WAAW,CAAC;CACtB,GAAG,gBAAgB,CA8JnB;AAED,wBAAsB,mBAAmB,CACvC,IAAI,EAAE,IAAI,CAAC,UAAU,CAAC,OAAO,oBAAoB,CAAC,CAAC,CAAC,CAAC,EAAE,IAAI,CAAC,GAAG;IAC7D,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,uBAAuB,CAAC,EAAE,OAAO,CAAC;CACnC,GACA,OAAO,CAAC,yBAAyB,CAAC,CAqBpC"}
@@ -31,11 +31,8 @@ export function startResumableUpload(args) {
31
31
  resolveDone = res;
32
32
  rejectDone = rej;
33
33
  });
34
- // The Storage SDK rejects a cancelled task with its own `storage/canceled` error.
35
- // When this upload asked for the cancel (its signal aborted, or its controller's
36
- // cancel() ran), that rejection becomes the canonical AbortError every consumer
37
- // already treats as a user cancel; a `storage/canceled` nobody here asked for
38
- // stays a real failure.
34
+ // The Storage SDK rejects a cancelled task with its own `storage/canceled` error; only a cancel
35
+ // this upload asked for becomes the AbortError consumers treat as a user cancel.
39
36
  let canceledLocally = false;
40
37
  const cancelTask = () => {
41
38
  canceledLocally = true;
@@ -158,9 +155,6 @@ export function startResumableUpload(args) {
158
155
  };
159
156
  }
160
157
  export async function uploadFileResumable(args) {
161
- // Defense-in-depth: reject missing or invalid contentType before any bytes are sent.
162
- // file-input should already guarantee a valid or explicitly-neutral MIME, but this
163
- // is the last gate before Firebase Storage.
164
158
  const ct = args.metadata?.contentType;
165
159
  if (!ct) {
166
160
  throw new UploadError('missing_content_type', 'Upload rejected: contentType metadata is required. Ensure file.type is set before uploading.');
@@ -1 +1 @@
1
- {"version":3,"file":"upload.js","sourceRoot":"","sources":["../../src/browser/upload.ts"],"names":[],"mappings":"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","sourcesContent":["import type { UploadFileResumableResult } from '../types.js';\nimport type { UploadFileResumableArgs, UploadController } from './types.js';\nimport { getFileSize } from '../utils/file-size.js';\nimport { upsertUploadSession } from '../utils/upload-store.js';\nimport { NEUTRAL_CONTENT_TYPE } from '@ttt-productions/media-schemas';\nimport { UploadError, isValidMediaContentType } from '../storage/upload-error.js';\n\nimport {\n ref,\n uploadBytesResumable,\n type FirebaseStorage,\n type UploadMetadata,\n type UploadTaskSnapshot,\n} from 'firebase/storage';\n\nfunction isAbortError(e: unknown) {\n return e instanceof DOMException && e.name === 'AbortError';\n}\n\nfunction isStorageCanceledError(e: unknown) {\n return (\n typeof e === 'object' && e !== null && (e as { code?: unknown }).code === 'storage/canceled'\n );\n}\n\nexport function startResumableUpload(args: {\n id: string;\n storage: FirebaseStorage;\n path: string;\n file: Blob;\n metadata?: UploadMetadata;\n onProgress?: UploadFileResumableArgs['onProgress'];\n signal?: AbortSignal;\n}): UploadController {\n const { id, storage, path, file, metadata, onProgress, signal } = args;\n\n const r = ref(storage, path);\n const task = uploadBytesResumable(r, file, metadata);\n\n const startedAt = Date.now();\n\n upsertUploadSession({\n id,\n status: 'uploading',\n path,\n transferred: 0,\n total: getFileSize(file),\n percent: 0,\n startedAt,\n updatedAt: startedAt,\n });\n\n let unsub: (() => void) | null = null;\n\n let resolveDone!: (v: UploadFileResumableResult) => void;\n let rejectDone!: (e: unknown) => void;\n const done = new Promise<UploadFileResumableResult>((res, rej) => {\n resolveDone = res;\n rejectDone = rej;\n });\n\n // The Storage SDK rejects a cancelled task with its own `storage/canceled` error.\n // When this upload asked for the cancel (its signal aborted, or its controller's\n // cancel() ran), that rejection becomes the canonical AbortError every consumer\n // already treats as a user cancel; a `storage/canceled` nobody here asked for\n // stays a real failure.\n let canceledLocally = false;\n const cancelTask = () => {\n canceledLocally = true;\n try {\n return task.cancel();\n } catch {\n return false;\n }\n };\n\n const onAbort = () => {\n cancelTask();\n };\n\n if (signal) {\n if (signal.aborted) onAbort();\n signal.addEventListener('abort', onAbort, { once: true });\n }\n\n unsub = task.on(\n 'state_changed',\n (snap: UploadTaskSnapshot) => {\n const transferred = snap.bytesTransferred ?? 0;\n const total = snap.totalBytes ?? getFileSize(file);\n const percent = total > 0 ? (transferred / total) * 100 : 0;\n\n upsertUploadSession({\n id,\n status:\n snap.state === 'paused' ? 'paused' : snap.state === 'running' ? 'uploading' : 'uploading',\n path,\n transferred,\n total,\n percent,\n updatedAt: Date.now(),\n });\n\n onProgress?.({ transferred, total, percent, snapshot: snap });\n },\n async (err) => {\n if (signal) signal.removeEventListener('abort', onAbort);\n if (unsub) unsub();\n\n const cancelError = isAbortError(err)\n ? err\n : canceledLocally && isStorageCanceledError(err)\n ? new DOMException('Aborted', 'AbortError')\n : null;\n if (cancelError) {\n upsertUploadSession({\n id,\n status: 'canceled',\n path,\n updatedAt: Date.now(),\n error: cancelError,\n });\n return rejectDone(cancelError);\n }\n\n upsertUploadSession({\n id,\n status: 'error',\n path,\n updatedAt: Date.now(),\n error: err,\n });\n\n rejectDone(err);\n },\n async () => {\n try {\n const contentType = task.snapshot.metadata?.contentType ?? null;\n const size = task.snapshot.totalBytes ?? getFileSize(file);\n\n const result: UploadFileResumableResult = {\n fullPath: task.snapshot.ref.fullPath,\n contentType,\n size,\n };\n\n upsertUploadSession({\n id,\n status: 'success',\n path,\n transferred: size,\n total: size,\n percent: 100,\n updatedAt: Date.now(),\n result,\n });\n\n resolveDone(result);\n } catch (e) {\n upsertUploadSession({\n id,\n status: 'error',\n path,\n updatedAt: Date.now(),\n error: e,\n });\n rejectDone(e);\n } finally {\n if (signal) signal.removeEventListener('abort', onAbort);\n if (unsub) unsub();\n }\n },\n );\n\n return {\n id,\n task,\n pause: () => {\n try {\n return task.pause();\n } catch {\n return false;\n }\n },\n resume: () => {\n try {\n return task.resume();\n } catch {\n return false;\n }\n },\n cancel: cancelTask,\n done,\n };\n}\n\nexport async function uploadFileResumable(\n args: Omit<Parameters<typeof startResumableUpload>[0], 'id'> & {\n id?: string;\n /**\n * EXPLICIT opt-in for the NEUTRAL content type: a picker file with unknown\n * browser metadata must reach the server AS unknown\n * (`application/octet-stream`) so the server-side byte inspector — the only\n * classification authority — sees it unrewritten. Default false keeps the\n * media-only gate for every other consumer. A concrete media MIME is still validated either\n * way; this flag never permits arbitrary declared types, only the one\n * neutral value.\n */\n allowNeutralContentType?: boolean;\n },\n): Promise<UploadFileResumableResult> {\n // Defense-in-depth: reject missing or invalid contentType before any bytes are sent.\n // file-input should already guarantee a valid or explicitly-neutral MIME, but this\n // is the last gate before Firebase Storage.\n const ct = args.metadata?.contentType;\n if (!ct) {\n throw new UploadError(\n 'missing_content_type',\n 'Upload rejected: contentType metadata is required. Ensure file.type is set before uploading.',\n );\n }\n const neutralAllowed =\n args.allowNeutralContentType === true && ct.toLowerCase().trim() === NEUTRAL_CONTENT_TYPE;\n if (!neutralAllowed && !isValidMediaContentType(ct)) {\n throw new UploadError(\n 'invalid_content_type',\n `Upload rejected: contentType \"${ct}\" is not a supported media MIME (expected image/*, video/*, or audio/*).`,\n );\n }\n\n const { allowNeutralContentType: _allowNeutral, ...rest } = args;\n const id = args.id ?? `upl_${Math.random().toString(36).slice(2)}${Date.now().toString(36)}`;\n const c = startResumableUpload({ ...rest, id });\n return c.done;\n}\n"]}
1
+ {"version":3,"file":"upload.js","sourceRoot":"","sources":["../../src/browser/upload.ts"],"names":[],"mappings":"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","sourcesContent":["import type { UploadFileResumableResult } from '../types.js';\nimport type { UploadFileResumableArgs, UploadController } from './types.js';\nimport { getFileSize } from '../utils/file-size.js';\nimport { upsertUploadSession } from '../utils/upload-store.js';\nimport { NEUTRAL_CONTENT_TYPE } from '@ttt-productions/media-schemas';\nimport { UploadError, isValidMediaContentType } from '../storage/upload-error.js';\n\nimport {\n ref,\n uploadBytesResumable,\n type FirebaseStorage,\n type UploadMetadata,\n type UploadTaskSnapshot,\n} from 'firebase/storage';\n\nfunction isAbortError(e: unknown) {\n return e instanceof DOMException && e.name === 'AbortError';\n}\n\nfunction isStorageCanceledError(e: unknown) {\n return (\n typeof e === 'object' && e !== null && (e as { code?: unknown }).code === 'storage/canceled'\n );\n}\n\nexport function startResumableUpload(args: {\n id: string;\n storage: FirebaseStorage;\n path: string;\n file: Blob;\n metadata?: UploadMetadata;\n onProgress?: UploadFileResumableArgs['onProgress'];\n signal?: AbortSignal;\n}): UploadController {\n const { id, storage, path, file, metadata, onProgress, signal } = args;\n\n const r = ref(storage, path);\n const task = uploadBytesResumable(r, file, metadata);\n\n const startedAt = Date.now();\n\n upsertUploadSession({\n id,\n status: 'uploading',\n path,\n transferred: 0,\n total: getFileSize(file),\n percent: 0,\n startedAt,\n updatedAt: startedAt,\n });\n\n let unsub: (() => void) | null = null;\n\n let resolveDone!: (v: UploadFileResumableResult) => void;\n let rejectDone!: (e: unknown) => void;\n const done = new Promise<UploadFileResumableResult>((res, rej) => {\n resolveDone = res;\n rejectDone = rej;\n });\n\n // The Storage SDK rejects a cancelled task with its own `storage/canceled` error; only a cancel\n // this upload asked for becomes the AbortError consumers treat as a user cancel.\n let canceledLocally = false;\n const cancelTask = () => {\n canceledLocally = true;\n try {\n return task.cancel();\n } catch {\n return false;\n }\n };\n\n const onAbort = () => {\n cancelTask();\n };\n\n if (signal) {\n if (signal.aborted) onAbort();\n signal.addEventListener('abort', onAbort, { once: true });\n }\n\n unsub = task.on(\n 'state_changed',\n (snap: UploadTaskSnapshot) => {\n const transferred = snap.bytesTransferred ?? 0;\n const total = snap.totalBytes ?? getFileSize(file);\n const percent = total > 0 ? (transferred / total) * 100 : 0;\n\n upsertUploadSession({\n id,\n status:\n snap.state === 'paused' ? 'paused' : snap.state === 'running' ? 'uploading' : 'uploading',\n path,\n transferred,\n total,\n percent,\n updatedAt: Date.now(),\n });\n\n onProgress?.({ transferred, total, percent, snapshot: snap });\n },\n async (err) => {\n if (signal) signal.removeEventListener('abort', onAbort);\n if (unsub) unsub();\n\n const cancelError = isAbortError(err)\n ? err\n : canceledLocally && isStorageCanceledError(err)\n ? new DOMException('Aborted', 'AbortError')\n : null;\n if (cancelError) {\n upsertUploadSession({\n id,\n status: 'canceled',\n path,\n updatedAt: Date.now(),\n error: cancelError,\n });\n return rejectDone(cancelError);\n }\n\n upsertUploadSession({\n id,\n status: 'error',\n path,\n updatedAt: Date.now(),\n error: err,\n });\n\n rejectDone(err);\n },\n async () => {\n try {\n const contentType = task.snapshot.metadata?.contentType ?? null;\n const size = task.snapshot.totalBytes ?? getFileSize(file);\n\n const result: UploadFileResumableResult = {\n fullPath: task.snapshot.ref.fullPath,\n contentType,\n size,\n };\n\n upsertUploadSession({\n id,\n status: 'success',\n path,\n transferred: size,\n total: size,\n percent: 100,\n updatedAt: Date.now(),\n result,\n });\n\n resolveDone(result);\n } catch (e) {\n upsertUploadSession({\n id,\n status: 'error',\n path,\n updatedAt: Date.now(),\n error: e,\n });\n rejectDone(e);\n } finally {\n if (signal) signal.removeEventListener('abort', onAbort);\n if (unsub) unsub();\n }\n },\n );\n\n return {\n id,\n task,\n pause: () => {\n try {\n return task.pause();\n } catch {\n return false;\n }\n },\n resume: () => {\n try {\n return task.resume();\n } catch {\n return false;\n }\n },\n cancel: cancelTask,\n done,\n };\n}\n\nexport async function uploadFileResumable(\n args: Omit<Parameters<typeof startResumableUpload>[0], 'id'> & {\n id?: string;\n allowNeutralContentType?: boolean;\n },\n): Promise<UploadFileResumableResult> {\n const ct = args.metadata?.contentType;\n if (!ct) {\n throw new UploadError(\n 'missing_content_type',\n 'Upload rejected: contentType metadata is required. Ensure file.type is set before uploading.',\n );\n }\n const neutralAllowed =\n args.allowNeutralContentType === true && ct.toLowerCase().trim() === NEUTRAL_CONTENT_TYPE;\n if (!neutralAllowed && !isValidMediaContentType(ct)) {\n throw new UploadError(\n 'invalid_content_type',\n `Upload rejected: contentType \"${ct}\" is not a supported media MIME (expected image/*, video/*, or audio/*).`,\n );\n }\n\n const { allowNeutralContentType: _allowNeutral, ...rest } = args;\n const id = args.id ?? `upl_${Math.random().toString(36).slice(2)}${Date.now().toString(36)}`;\n const c = startResumableUpload({ ...rest, id });\n return c.done;\n}\n"]}
@@ -1,17 +1,7 @@
1
1
  export type UploadErrorCode = 'missing_content_type' | 'invalid_content_type';
2
- /**
3
- * Typed error thrown by upload-core when a client-side guard rejects an upload
4
- * before it reaches Firebase Storage. Consumers can catch this and show a
5
- * friendly message instead of parsing a storage 403.
6
- */
7
2
  export declare class UploadError extends Error {
8
3
  readonly code: UploadErrorCode;
9
4
  constructor(code: UploadErrorCode, message?: string);
10
5
  }
11
- /**
12
- * Returns true if the given string looks like a valid media MIME type
13
- * for an image, video, or audio upload: image/*, video/*, or audio/*.
14
- * Empty strings, application/octet-stream, and anything else → false.
15
- */
16
6
  export declare function isValidMediaContentType(contentType: string | undefined | null): boolean;
17
7
  //# sourceMappingURL=upload-error.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"upload-error.d.ts","sourceRoot":"","sources":["../../src/storage/upload-error.ts"],"names":[],"mappings":"AAEA,MAAM,MAAM,eAAe,GAAG,sBAAsB,GAAG,sBAAsB,CAAC;AAE9E;;;;GAIG;AACH,qBAAa,WAAY,SAAQ,KAAK;IACpC,QAAQ,CAAC,IAAI,EAAE,eAAe,CAAC;gBAEnB,IAAI,EAAE,eAAe,EAAE,OAAO,CAAC,EAAE,MAAM;CAOpD;AAED;;;;GAIG;AACH,wBAAgB,uBAAuB,CAAC,WAAW,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI,GAAG,OAAO,CAKvF"}
1
+ {"version":3,"file":"upload-error.d.ts","sourceRoot":"","sources":["../../src/storage/upload-error.ts"],"names":[],"mappings":"AAEA,MAAM,MAAM,eAAe,GAAG,sBAAsB,GAAG,sBAAsB,CAAC;AAE9E,qBAAa,WAAY,SAAQ,KAAK;IACpC,QAAQ,CAAC,IAAI,EAAE,eAAe,CAAC;gBAEnB,IAAI,EAAE,eAAe,EAAE,OAAO,CAAC,EAAE,MAAM;CAOpD;AAED,wBAAgB,uBAAuB,CAAC,WAAW,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI,GAAG,OAAO,CAKvF"}
@@ -1,9 +1,4 @@
1
1
  import { NEUTRAL_CONTENT_TYPE } from '@ttt-productions/media-schemas';
2
- /**
3
- * Typed error thrown by upload-core when a client-side guard rejects an upload
4
- * before it reaches Firebase Storage. Consumers can catch this and show a
5
- * friendly message instead of parsing a storage 403.
6
- */
7
2
  export class UploadError extends Error {
8
3
  code;
9
4
  constructor(code, message) {
@@ -14,11 +9,6 @@ export class UploadError extends Error {
14
9
  Object.setPrototypeOf(this, UploadError.prototype);
15
10
  }
16
11
  }
17
- /**
18
- * Returns true if the given string looks like a valid media MIME type
19
- * for an image, video, or audio upload: image/*, video/*, or audio/*.
20
- * Empty strings, application/octet-stream, and anything else → false.
21
- */
22
12
  export function isValidMediaContentType(contentType) {
23
13
  if (!contentType)
24
14
  return false;
@@ -1 +1 @@
1
- {"version":3,"file":"upload-error.js","sourceRoot":"","sources":["../../src/storage/upload-error.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,oBAAoB,EAAE,MAAM,gCAAgC,CAAC;AAItE;;;;GAIG;AACH,MAAM,OAAO,WAAY,SAAQ,KAAK;IAC3B,IAAI,CAAkB;IAE/B,YAAY,IAAqB,EAAE,OAAgB;QACjD,KAAK,CAAC,OAAO,IAAI,IAAI,CAAC,CAAC;QACvB,IAAI,CAAC,IAAI,GAAG,aAAa,CAAC;QAC1B,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;QACjB,uEAAuE;QACvE,MAAM,CAAC,cAAc,CAAC,IAAI,EAAE,WAAW,CAAC,SAAS,CAAC,CAAC;IACrD,CAAC;CACF;AAED;;;;GAIG;AACH,MAAM,UAAU,uBAAuB,CAAC,WAAsC;IAC5E,IAAI,CAAC,WAAW;QAAE,OAAO,KAAK,CAAC;IAC/B,MAAM,EAAE,GAAG,WAAW,CAAC,WAAW,EAAE,CAAC,IAAI,EAAE,CAAC;IAC5C,IAAI,EAAE,KAAK,oBAAoB;QAAE,OAAO,KAAK,CAAC;IAC9C,OAAO,qCAAqC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;AACxD,CAAC","sourcesContent":["import { NEUTRAL_CONTENT_TYPE } from '@ttt-productions/media-schemas';\n\nexport type UploadErrorCode = 'missing_content_type' | 'invalid_content_type';\n\n/**\n * Typed error thrown by upload-core when a client-side guard rejects an upload\n * before it reaches Firebase Storage. Consumers can catch this and show a\n * friendly message instead of parsing a storage 403.\n */\nexport class UploadError extends Error {\n readonly code: UploadErrorCode;\n\n constructor(code: UploadErrorCode, message?: string) {\n super(message ?? code);\n this.name = 'UploadError';\n this.code = code;\n // Preserve prototype chain for `instanceof` checks after transpilation\n Object.setPrototypeOf(this, UploadError.prototype);\n }\n}\n\n/**\n * Returns true if the given string looks like a valid media MIME type\n * for an image, video, or audio upload: image/*, video/*, or audio/*.\n * Empty strings, application/octet-stream, and anything else → false.\n */\nexport function isValidMediaContentType(contentType: string | undefined | null): boolean {\n if (!contentType) return false;\n const ct = contentType.toLowerCase().trim();\n if (ct === NEUTRAL_CONTENT_TYPE) return false;\n return /^(image|video|audio)\\/[a-z0-9.+-]+$/.test(ct);\n}\n"]}
1
+ {"version":3,"file":"upload-error.js","sourceRoot":"","sources":["../../src/storage/upload-error.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,oBAAoB,EAAE,MAAM,gCAAgC,CAAC;AAItE,MAAM,OAAO,WAAY,SAAQ,KAAK;IAC3B,IAAI,CAAkB;IAE/B,YAAY,IAAqB,EAAE,OAAgB;QACjD,KAAK,CAAC,OAAO,IAAI,IAAI,CAAC,CAAC;QACvB,IAAI,CAAC,IAAI,GAAG,aAAa,CAAC;QAC1B,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;QACjB,uEAAuE;QACvE,MAAM,CAAC,cAAc,CAAC,IAAI,EAAE,WAAW,CAAC,SAAS,CAAC,CAAC;IACrD,CAAC;CACF;AAED,MAAM,UAAU,uBAAuB,CAAC,WAAsC;IAC5E,IAAI,CAAC,WAAW;QAAE,OAAO,KAAK,CAAC;IAC/B,MAAM,EAAE,GAAG,WAAW,CAAC,WAAW,EAAE,CAAC,IAAI,EAAE,CAAC;IAC5C,IAAI,EAAE,KAAK,oBAAoB;QAAE,OAAO,KAAK,CAAC;IAC9C,OAAO,qCAAqC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;AACxD,CAAC","sourcesContent":["import { NEUTRAL_CONTENT_TYPE } from '@ttt-productions/media-schemas';\n\nexport type UploadErrorCode = 'missing_content_type' | 'invalid_content_type';\n\nexport class UploadError extends Error {\n readonly code: UploadErrorCode;\n\n constructor(code: UploadErrorCode, message?: string) {\n super(message ?? code);\n this.name = 'UploadError';\n this.code = code;\n // Preserve prototype chain for `instanceof` checks after transpilation\n Object.setPrototypeOf(this, UploadError.prototype);\n }\n}\n\nexport function isValidMediaContentType(contentType: string | undefined | null): boolean {\n if (!contentType) return false;\n const ct = contentType.toLowerCase().trim();\n if (ct === NEUTRAL_CONTENT_TYPE) return false;\n return /^(image|video|audio)\\/[a-z0-9.+-]+$/.test(ct);\n}\n"]}
package/dist/types.d.ts CHANGED
@@ -1,7 +1,3 @@
1
- /**
2
- * What a finished upload reports: where the bytes landed, their stored content type, and
3
- * their size. No download URL is minted — a staged upload is read only by the backend.
4
- */
5
1
  export interface UploadFileResumableResult {
6
2
  fullPath: string;
7
3
  contentType: string | null;
@@ -9,23 +5,19 @@ export interface UploadFileResumableResult {
9
5
  }
10
6
  export type UploadSessionStatus = 'idle' | 'queued' | 'uploading' | 'paused' | 'success' | 'error' | 'canceled';
11
7
  export interface UploadSessionPersistenceAdapter {
12
- /** Return all session ids currently stored. */
13
8
  listIds: () => Promise<string[]> | string[];
14
9
  get: (id: string) => Promise<UploadSessionState | null> | UploadSessionState | null;
15
10
  set: (id: string, state: UploadSessionState) => Promise<void> | void;
16
11
  remove: (id: string) => Promise<void> | void;
17
12
  }
18
13
  export interface UploadQueueOptions {
19
- /** Maximum concurrent uploads. Default: 3 */
20
14
  concurrency?: number;
21
- /** Optional persistence adapter for session state. */
22
15
  persistence?: UploadSessionPersistenceAdapter;
23
16
  }
24
17
  export interface UploadSessionState {
25
18
  id: string;
26
19
  status: UploadSessionStatus;
27
20
  path: string;
28
- /** Monotonic version counter for state updates. */
29
21
  version: number;
30
22
  transferred: number;
31
23
  total: number;
@@ -36,11 +28,8 @@ export interface UploadSessionState {
36
28
  result?: UploadFileResumableResult;
37
29
  }
38
30
  export interface DisposeUploadSessionArgs {
39
- /** Session id to dispose. */
40
31
  id: string;
41
- /** If true, cancel any active upload task before disposal. Default true. */
42
32
  cancel?: boolean;
43
- /** If true, remove session state from the in-memory store. Default true. */
44
33
  remove?: boolean;
45
34
  }
46
35
  //# sourceMappingURL=types.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,MAAM,WAAW,yBAAyB;IACxC,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,IAAI,EAAE,MAAM,CAAC;CACd;AAED,MAAM,MAAM,mBAAmB,GAC7B,MAAM,GAAG,QAAQ,GAAG,WAAW,GAAG,QAAQ,GAAG,SAAS,GAAG,OAAO,GAAG,UAAU,CAAC;AAEhF,MAAM,WAAW,+BAA+B;IAC9C,+CAA+C;IAC/C,OAAO,EAAE,MAAM,OAAO,CAAC,MAAM,EAAE,CAAC,GAAG,MAAM,EAAE,CAAC;IAC5C,GAAG,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,OAAO,CAAC,kBAAkB,GAAG,IAAI,CAAC,GAAG,kBAAkB,GAAG,IAAI,CAAC;IACpF,GAAG,EAAE,CAAC,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,kBAAkB,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;IACrE,MAAM,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;CAC9C;AAED,MAAM,WAAW,kBAAkB;IACjC,6CAA6C;IAC7C,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,sDAAsD;IACtD,WAAW,CAAC,EAAE,+BAA+B,CAAC;CAC/C;AAED,MAAM,WAAW,kBAAkB;IACjC,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,EAAE,mBAAmB,CAAC;IAC5B,IAAI,EAAE,MAAM,CAAC;IAEb,mDAAmD;IACnD,OAAO,EAAE,MAAM,CAAC;IAEhB,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAEhB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAElB,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,MAAM,CAAC,EAAE,yBAAyB,CAAC;CACpC;AAED,MAAM,WAAW,wBAAwB;IACvC,6BAA6B;IAC7B,EAAE,EAAE,MAAM,CAAC;IACX,4EAA4E;IAC5E,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,4EAA4E;IAC5E,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,yBAAyB;IACxC,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,IAAI,EAAE,MAAM,CAAC;CACd;AAED,MAAM,MAAM,mBAAmB,GAC7B,MAAM,GAAG,QAAQ,GAAG,WAAW,GAAG,QAAQ,GAAG,SAAS,GAAG,OAAO,GAAG,UAAU,CAAC;AAEhF,MAAM,WAAW,+BAA+B;IAC9C,OAAO,EAAE,MAAM,OAAO,CAAC,MAAM,EAAE,CAAC,GAAG,MAAM,EAAE,CAAC;IAC5C,GAAG,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,OAAO,CAAC,kBAAkB,GAAG,IAAI,CAAC,GAAG,kBAAkB,GAAG,IAAI,CAAC;IACpF,GAAG,EAAE,CAAC,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,kBAAkB,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;IACrE,MAAM,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;CAC9C;AAED,MAAM,WAAW,kBAAkB;IACjC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,WAAW,CAAC,EAAE,+BAA+B,CAAC;CAC/C;AAED,MAAM,WAAW,kBAAkB;IACjC,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,EAAE,mBAAmB,CAAC;IAC5B,IAAI,EAAE,MAAM,CAAC;IAEb,OAAO,EAAE,MAAM,CAAC;IAEhB,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAEhB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAElB,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,MAAM,CAAC,EAAE,yBAAyB,CAAC;CACpC;AAED,MAAM,WAAW,wBAAwB;IACvC,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB"}
package/dist/types.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"","sourcesContent":["/**\n * What a finished upload reports: where the bytes landed, their stored content type, and\n * their size. No download URL is minted — a staged upload is read only by the backend.\n */\nexport interface UploadFileResumableResult {\n fullPath: string;\n contentType: string | null;\n size: number;\n}\n\nexport type UploadSessionStatus =\n 'idle' | 'queued' | 'uploading' | 'paused' | 'success' | 'error' | 'canceled';\n\nexport interface UploadSessionPersistenceAdapter {\n /** Return all session ids currently stored. */\n listIds: () => Promise<string[]> | string[];\n get: (id: string) => Promise<UploadSessionState | null> | UploadSessionState | null;\n set: (id: string, state: UploadSessionState) => Promise<void> | void;\n remove: (id: string) => Promise<void> | void;\n}\n\nexport interface UploadQueueOptions {\n /** Maximum concurrent uploads. Default: 3 */\n concurrency?: number;\n /** Optional persistence adapter for session state. */\n persistence?: UploadSessionPersistenceAdapter;\n}\n\nexport interface UploadSessionState {\n id: string;\n status: UploadSessionStatus;\n path: string;\n\n /** Monotonic version counter for state updates. */\n version: number;\n\n transferred: number;\n total: number;\n percent: number; // 0..100\n\n startedAt: number;\n updatedAt: number;\n\n error?: unknown;\n result?: UploadFileResumableResult;\n}\n\nexport interface DisposeUploadSessionArgs {\n /** Session id to dispose. */\n id: string;\n /** If true, cancel any active upload task before disposal. Default true. */\n cancel?: boolean;\n /** If true, remove session state from the in-memory store. Default true. */\n remove?: boolean;\n}\n"]}
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"","sourcesContent":["export interface UploadFileResumableResult {\n fullPath: string;\n contentType: string | null;\n size: number;\n}\n\nexport type UploadSessionStatus =\n 'idle' | 'queued' | 'uploading' | 'paused' | 'success' | 'error' | 'canceled';\n\nexport interface UploadSessionPersistenceAdapter {\n listIds: () => Promise<string[]> | string[];\n get: (id: string) => Promise<UploadSessionState | null> | UploadSessionState | null;\n set: (id: string, state: UploadSessionState) => Promise<void> | void;\n remove: (id: string) => Promise<void> | void;\n}\n\nexport interface UploadQueueOptions {\n concurrency?: number;\n persistence?: UploadSessionPersistenceAdapter;\n}\n\nexport interface UploadSessionState {\n id: string;\n status: UploadSessionStatus;\n path: string;\n\n version: number;\n\n transferred: number;\n total: number;\n percent: number;\n\n startedAt: number;\n updatedAt: number;\n\n error?: unknown;\n result?: UploadFileResumableResult;\n}\n\nexport interface DisposeUploadSessionArgs {\n id: string;\n cancel?: boolean;\n remove?: boolean;\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"path.d.ts","sourceRoot":"","sources":["../../src/utils/path.ts"],"names":[],"mappings":"AAaA,wBAAgB,eAAe,CAAC,IAAI,EAAE;IACpC,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB,UAWA"}
1
+ {"version":3,"file":"path.d.ts","sourceRoot":"","sources":["../../src/utils/path.ts"],"names":[],"mappings":"AAYA,wBAAgB,eAAe,CAAC,IAAI,EAAE;IACpC,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB,UAUA"}
@@ -2,7 +2,6 @@ import { joinPath } from '@ttt-productions/firebase-helpers';
2
2
  import { normalizeFilename } from './filename.js';
3
3
  function sanitizeSegment(seg) {
4
4
  const s = String(seg ?? '').trim();
5
- // Remove slashes and collapse dot-runs to avoid traversal.
6
5
  const noSlashes = s.replace(/[\\/]+/g, '_');
7
6
  const noDots = noSlashes.replace(/\.+/g, '.');
8
7
  const cleaned = noDots.replace(/[^a-zA-Z0-9._-]/g, '_');
@@ -11,7 +10,6 @@ function sanitizeSegment(seg) {
11
10
  }
12
11
  export function buildUploadPath(args) {
13
12
  const { basePath, ownerId, contentId, filename } = args;
14
- // Sanitize segments for security (traversal prevention)
15
13
  const segments = [basePath, ownerId, contentId]
16
14
  .filter((p) => !!p)
17
15
  .map(sanitizeSegment);
@@ -1 +1 @@
1
- {"version":3,"file":"path.js","sourceRoot":"","sources":["../../src/utils/path.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,mCAAmC,CAAC;AAC7D,OAAO,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAC;AAElD,SAAS,eAAe,CAAC,GAAW;IAClC,MAAM,CAAC,GAAG,MAAM,CAAC,GAAG,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;IACnC,2DAA2D;IAC3D,MAAM,SAAS,GAAG,CAAC,CAAC,OAAO,CAAC,SAAS,EAAE,GAAG,CAAC,CAAC;IAC5C,MAAM,MAAM,GAAG,SAAS,CAAC,OAAO,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;IAC9C,MAAM,OAAO,GAAG,MAAM,CAAC,OAAO,CAAC,kBAAkB,EAAE,GAAG,CAAC,CAAC;IACxD,MAAM,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;IAChE,OAAO,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,GAAG,CAAC;AACrC,CAAC;AAED,MAAM,UAAU,eAAe,CAAC,IAK/B;IACC,MAAM,EAAE,QAAQ,EAAE,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAE,GAAG,IAAI,CAAC;IAExD,wDAAwD;IACxD,MAAM,QAAQ,GAAG,CAAC,QAAQ,EAAE,OAAO,EAAE,SAAS,CAAC;SAC5C,MAAM,CAAC,CAAC,CAAC,EAAe,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC;SAC/B,GAAG,CAAC,eAAe,CAAC,CAAC;IAExB,MAAM,QAAQ,GAAG,QAAQ,CAAC,CAAC,CAAC,iBAAiB,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC;IAEjE,OAAO,QAAQ,CAAC,GAAG,QAAQ,EAAE,QAAQ,CAAC,CAAC;AACzC,CAAC","sourcesContent":["import { joinPath } from '@ttt-productions/firebase-helpers';\nimport { normalizeFilename } from './filename.js';\n\nfunction sanitizeSegment(seg: string): string {\n const s = String(seg ?? '').trim();\n // Remove slashes and collapse dot-runs to avoid traversal.\n const noSlashes = s.replace(/[\\\\/]+/g, '_');\n const noDots = noSlashes.replace(/\\.+/g, '.');\n const cleaned = noDots.replace(/[^a-zA-Z0-9._-]/g, '_');\n const trimmed = cleaned.replace(/^\\.+/, '').replace(/\\.+$/, '');\n return trimmed.slice(0, 80) || '_';\n}\n\nexport function buildUploadPath(args: {\n basePath: string;\n ownerId?: string;\n contentId?: string;\n filename?: string;\n}) {\n const { basePath, ownerId, contentId, filename } = args;\n\n // Sanitize segments for security (traversal prevention)\n const segments = [basePath, ownerId, contentId]\n .filter((p): p is string => !!p)\n .map(sanitizeSegment);\n\n const filePart = filename ? normalizeFilename(filename) : 'file';\n\n return joinPath(...segments, filePart);\n}\n"]}
1
+ {"version":3,"file":"path.js","sourceRoot":"","sources":["../../src/utils/path.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,mCAAmC,CAAC;AAC7D,OAAO,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAC;AAElD,SAAS,eAAe,CAAC,GAAW;IAClC,MAAM,CAAC,GAAG,MAAM,CAAC,GAAG,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;IACnC,MAAM,SAAS,GAAG,CAAC,CAAC,OAAO,CAAC,SAAS,EAAE,GAAG,CAAC,CAAC;IAC5C,MAAM,MAAM,GAAG,SAAS,CAAC,OAAO,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;IAC9C,MAAM,OAAO,GAAG,MAAM,CAAC,OAAO,CAAC,kBAAkB,EAAE,GAAG,CAAC,CAAC;IACxD,MAAM,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;IAChE,OAAO,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,GAAG,CAAC;AACrC,CAAC;AAED,MAAM,UAAU,eAAe,CAAC,IAK/B;IACC,MAAM,EAAE,QAAQ,EAAE,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAE,GAAG,IAAI,CAAC;IAExD,MAAM,QAAQ,GAAG,CAAC,QAAQ,EAAE,OAAO,EAAE,SAAS,CAAC;SAC5C,MAAM,CAAC,CAAC,CAAC,EAAe,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC;SAC/B,GAAG,CAAC,eAAe,CAAC,CAAC;IAExB,MAAM,QAAQ,GAAG,QAAQ,CAAC,CAAC,CAAC,iBAAiB,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC;IAEjE,OAAO,QAAQ,CAAC,GAAG,QAAQ,EAAE,QAAQ,CAAC,CAAC;AACzC,CAAC","sourcesContent":["import { joinPath } from '@ttt-productions/firebase-helpers';\nimport { normalizeFilename } from './filename.js';\n\nfunction sanitizeSegment(seg: string): string {\n const s = String(seg ?? '').trim();\n const noSlashes = s.replace(/[\\\\/]+/g, '_');\n const noDots = noSlashes.replace(/\\.+/g, '.');\n const cleaned = noDots.replace(/[^a-zA-Z0-9._-]/g, '_');\n const trimmed = cleaned.replace(/^\\.+/, '').replace(/\\.+$/, '');\n return trimmed.slice(0, 80) || '_';\n}\n\nexport function buildUploadPath(args: {\n basePath: string;\n ownerId?: string;\n contentId?: string;\n filename?: string;\n}) {\n const { basePath, ownerId, contentId, filename } = args;\n\n const segments = [basePath, ownerId, contentId]\n .filter((p): p is string => !!p)\n .map(sanitizeSegment);\n\n const filePart = filename ? normalizeFilename(filename) : 'file';\n\n return joinPath(...segments, filePart);\n}\n"]}
@@ -5,14 +5,13 @@ export declare function setUploadSessionPersistence(adapter: UploadSessionPersis
5
5
  export type PersistenceErrorHandler = (err: unknown, op: 'set' | 'remove' | 'get', id: string) => void;
6
6
  export declare function setUploadSessionPersistenceErrorHandler(handler: PersistenceErrorHandler | null): void;
7
7
  export declare function pruneOldUploadSessions(): number;
8
- /** Load persisted sessions into the in-memory store. */
9
8
  export declare function rehydrateUploadSessions(): Promise<void>;
10
9
  export declare function getUploadSession(id: string): UploadSessionState | undefined;
11
10
  export declare function listUploadSessions(): UploadSessionState[];
12
11
  export declare function upsertUploadSession(partial: Partial<UploadSessionState> & Pick<UploadSessionState, 'id'>): void;
13
12
  export declare function removeUploadSession(id: string): void;
14
13
  export declare function subscribeUploadSession(id: string, fn: Listener): () => void;
15
- /** Subscribe to changes in the *list* (new/removed sessions, rehydrate). */
16
14
  export declare function subscribeUploadSessionsList(fn: ListListener): () => void;
15
+ export declare function __resetUploadStore(): void;
17
16
  export {};
18
17
  //# sourceMappingURL=upload-store.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"upload-store.d.ts","sourceRoot":"","sources":["../../src/utils/upload-store.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,kBAAkB,EAAE,+BAA+B,EAAE,MAAM,aAAa,CAAC;AAEvF,KAAK,QAAQ,GAAG,CAAC,CAAC,EAAE,kBAAkB,KAAK,IAAI,CAAC;AAChD,KAAK,YAAY,GAAG,MAAM,IAAI,CAAC;AAQ/B,wBAAgB,2BAA2B,CAAC,OAAO,EAAE,+BAA+B,GAAG,IAAI,QAE1F;AAED,MAAM,MAAM,uBAAuB,GAAG,CACpC,GAAG,EAAE,OAAO,EACZ,EAAE,EAAE,KAAK,GAAG,QAAQ,GAAG,KAAK,EAC5B,EAAE,EAAE,MAAM,KACP,IAAI,CAAC;AAIV,wBAAgB,uCAAuC,CAAC,OAAO,EAAE,uBAAuB,GAAG,IAAI,QAE9F;AA4BD,wBAAgB,sBAAsB,WA4BrC;AAED,wDAAwD;AACxD,wBAAsB,uBAAuB,kBAgB5C;AAED,wBAAgB,gBAAgB,CAAC,EAAE,EAAE,MAAM,GAAG,kBAAkB,GAAG,SAAS,CAE3E;AAED,wBAAgB,kBAAkB,IAAI,kBAAkB,EAAE,CAEzD;AAED,wBAAgB,mBAAmB,CACjC,OAAO,EAAE,OAAO,CAAC,kBAAkB,CAAC,GAAG,IAAI,CAAC,kBAAkB,EAAE,IAAI,CAAC,QA+CtE;AAED,wBAAgB,mBAAmB,CAAC,EAAE,EAAE,MAAM,QAK7C;AAMD,wBAAgB,sBAAsB,CAAC,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,QAAQ,GAAG,MAAM,IAAI,CAiB3E;AAED,4EAA4E;AAC5E,wBAAgB,2BAA2B,CAAC,EAAE,EAAE,YAAY,GAAG,MAAM,IAAI,CAGxE"}
1
+ {"version":3,"file":"upload-store.d.ts","sourceRoot":"","sources":["../../src/utils/upload-store.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,kBAAkB,EAAE,+BAA+B,EAAE,MAAM,aAAa,CAAC;AAEvF,KAAK,QAAQ,GAAG,CAAC,CAAC,EAAE,kBAAkB,KAAK,IAAI,CAAC;AAChD,KAAK,YAAY,GAAG,MAAM,IAAI,CAAC;AAQ/B,wBAAgB,2BAA2B,CAAC,OAAO,EAAE,+BAA+B,GAAG,IAAI,QAE1F;AAED,MAAM,MAAM,uBAAuB,GAAG,CACpC,GAAG,EAAE,OAAO,EACZ,EAAE,EAAE,KAAK,GAAG,QAAQ,GAAG,KAAK,EAC5B,EAAE,EAAE,MAAM,KACP,IAAI,CAAC;AAIV,wBAAgB,uCAAuC,CAAC,OAAO,EAAE,uBAAuB,GAAG,IAAI,QAE9F;AA4BD,wBAAgB,sBAAsB,WA4BrC;AAED,wBAAsB,uBAAuB,kBAgB5C;AAED,wBAAgB,gBAAgB,CAAC,EAAE,EAAE,MAAM,GAAG,kBAAkB,GAAG,SAAS,CAE3E;AAED,wBAAgB,kBAAkB,IAAI,kBAAkB,EAAE,CAEzD;AAED,wBAAgB,mBAAmB,CACjC,OAAO,EAAE,OAAO,CAAC,kBAAkB,CAAC,GAAG,IAAI,CAAC,kBAAkB,EAAE,IAAI,CAAC,QA4CtE;AAED,wBAAgB,mBAAmB,CAAC,EAAE,EAAE,MAAM,QAK7C;AAMD,wBAAgB,sBAAsB,CAAC,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,QAAQ,GAAG,MAAM,IAAI,CAiB3E;AAED,wBAAgB,2BAA2B,CAAC,EAAE,EAAE,YAAY,GAAG,MAAM,IAAI,CAGxE;AAED,wBAAgB,kBAAkB,IAAI,IAAI,CAMzC"}
@@ -33,8 +33,8 @@ function notifyList() {
33
33
  fn();
34
34
  }
35
35
  const MAX_SESSIONS = 100;
36
- const SUCCESS_TTL_MS = 24 * 60 * 60 * 1000; // 24h
37
- const ERROR_TTL_MS = 7 * 24 * 60 * 60 * 1000; // 7d
36
+ const SUCCESS_TTL_MS = 24 * 60 * 60 * 1000;
37
+ const ERROR_TTL_MS = 7 * 24 * 60 * 60 * 1000;
38
38
  export function pruneOldUploadSessions() {
39
39
  const t = now();
40
40
  const all = listUploadSessions();
@@ -59,7 +59,6 @@ export function pruneOldUploadSessions() {
59
59
  }
60
60
  return pruned;
61
61
  }
62
- /** Load persisted sessions into the in-memory store. */
63
62
  export async function rehydrateUploadSessions() {
64
63
  if (!persistence)
65
64
  return;
@@ -86,7 +85,6 @@ export function listUploadSessions() {
86
85
  }
87
86
  export function upsertUploadSession(partial) {
88
87
  const prev = sessions.get(partial.id);
89
- // Guard against out-of-order async updates.
90
88
  if (prev && partial.updatedAt != null && partial.updatedAt < prev.updatedAt)
91
89
  return;
92
90
  const prevStatus = prev?.status ?? 'idle';
@@ -94,9 +92,7 @@ export function upsertUploadSession(partial) {
94
92
  const isTerminal = (s) => s === 'success' || s === 'error' || s === 'canceled';
95
93
  const prevIsTerminal = isTerminal(prevStatus);
96
94
  const nextIsTerminal = isTerminal(nextStatus);
97
- // Once terminal, stay terminal. If we receive a terminal nextStatus first, accept it.
98
95
  const status = prevIsTerminal ? prevStatus : nextIsTerminal ? nextStatus : nextStatus;
99
- // Progress values should never go backwards.
100
96
  const transferred = Math.max(partial.transferred ?? 0, prev?.transferred ?? 0);
101
97
  const total = Math.max(partial.total ?? 0, prev?.total ?? 0);
102
98
  const percent = Math.max(partial.percent ?? 0, prev?.percent ?? 0);
@@ -154,9 +150,15 @@ export function subscribeUploadSession(id, fn) {
154
150
  listeners.delete(id);
155
151
  };
156
152
  }
157
- /** Subscribe to changes in the *list* (new/removed sessions, rehydrate). */
158
153
  export function subscribeUploadSessionsList(fn) {
159
154
  listListeners.add(fn);
160
155
  return () => listListeners.delete(fn);
161
156
  }
157
+ export function __resetUploadStore() {
158
+ sessions.clear();
159
+ listeners.clear();
160
+ listListeners.clear();
161
+ persistence = null;
162
+ persistenceErrorHandler = null;
163
+ }
162
164
  //# sourceMappingURL=upload-store.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"upload-store.js","sourceRoot":"","sources":["../../src/utils/upload-store.ts"],"names":[],"mappings":"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","sourcesContent":["import { now } from '@ttt-productions/firebase-helpers';\nimport type { UploadSessionState, UploadSessionPersistenceAdapter } from '../types.js';\n\ntype Listener = (s: UploadSessionState) => void;\ntype ListListener = () => void;\n\nconst sessions = new Map<string, UploadSessionState>();\nconst listeners = new Map<string, Set<Listener>>();\nconst listListeners = new Set<ListListener>();\n\nlet persistence: UploadSessionPersistenceAdapter | null = null;\n\nexport function setUploadSessionPersistence(adapter: UploadSessionPersistenceAdapter | null) {\n persistence = adapter;\n}\n\nexport type PersistenceErrorHandler = (\n err: unknown,\n op: 'set' | 'remove' | 'get',\n id: string,\n) => void;\n\nlet persistenceErrorHandler: PersistenceErrorHandler | null = null;\n\nexport function setUploadSessionPersistenceErrorHandler(handler: PersistenceErrorHandler | null) {\n persistenceErrorHandler = handler;\n}\n\nasync function persistUpsert(state: UploadSessionState) {\n try {\n await persistence?.set(state.id, state);\n } catch (err) {\n console.error('[upload-store] Failed to persist session:', state.id, err);\n persistenceErrorHandler?.(err, 'set', state.id);\n }\n}\n\nasync function persistRemove(id: string) {\n try {\n await persistence?.remove(id);\n } catch (err) {\n console.error('[upload-store] Failed to remove persisted session:', id, err);\n persistenceErrorHandler?.(err, 'remove', id);\n }\n}\n\nfunction notifyList() {\n for (const fn of listListeners) fn();\n}\n\nconst MAX_SESSIONS = 100;\nconst SUCCESS_TTL_MS = 24 * 60 * 60 * 1000; // 24h\nconst ERROR_TTL_MS = 7 * 24 * 60 * 60 * 1000; // 7d\n\nexport function pruneOldUploadSessions() {\n const t = now();\n const all = listUploadSessions();\n\n let pruned = 0;\n\n for (const s of all) {\n if (s.status === 'success' && t - s.updatedAt > SUCCESS_TTL_MS) {\n removeUploadSession(s.id);\n pruned++;\n } else if (\n (s.status === 'error' || s.status === 'canceled') &&\n t - s.updatedAt > ERROR_TTL_MS\n ) {\n removeUploadSession(s.id);\n pruned++;\n }\n }\n\n const remaining = listUploadSessions();\n if (remaining.length > MAX_SESSIONS) {\n for (const s of remaining.slice(MAX_SESSIONS)) {\n removeUploadSession(s.id);\n pruned++;\n }\n }\n\n return pruned;\n}\n\n/** Load persisted sessions into the in-memory store. */\nexport async function rehydrateUploadSessions() {\n if (!persistence) return;\n\n const ids = await persistence.listIds();\n for (const id of ids) {\n try {\n const s = await persistence.get(id);\n if (s) sessions.set(id, s);\n } catch (err) {\n console.error('[upload-store] Failed to read persisted session:', id, err);\n persistenceErrorHandler?.(err, 'get', id);\n }\n }\n\n pruneOldUploadSessions();\n notifyList();\n}\n\nexport function getUploadSession(id: string): UploadSessionState | undefined {\n return sessions.get(id);\n}\n\nexport function listUploadSessions(): UploadSessionState[] {\n return Array.from(sessions.values()).sort((a, b) => b.updatedAt - a.updatedAt);\n}\n\nexport function upsertUploadSession(\n partial: Partial<UploadSessionState> & Pick<UploadSessionState, 'id'>,\n) {\n const prev = sessions.get(partial.id);\n\n // Guard against out-of-order async updates.\n if (prev && partial.updatedAt != null && partial.updatedAt < prev.updatedAt) return;\n\n const prevStatus = prev?.status ?? 'idle';\n const nextStatus = partial.status ?? prevStatus;\n\n const isTerminal = (s: string) => s === 'success' || s === 'error' || s === 'canceled';\n const prevIsTerminal = isTerminal(prevStatus);\n const nextIsTerminal = isTerminal(nextStatus);\n\n // Once terminal, stay terminal. If we receive a terminal nextStatus first, accept it.\n const status = prevIsTerminal ? prevStatus : nextIsTerminal ? nextStatus : nextStatus;\n\n // Progress values should never go backwards.\n const transferred = Math.max(partial.transferred ?? 0, prev?.transferred ?? 0);\n const total = Math.max(partial.total ?? 0, prev?.total ?? 0);\n const percent = Math.max(partial.percent ?? 0, prev?.percent ?? 0);\n\n const version = (prev?.version ?? 0) + 1;\n\n const next: UploadSessionState = {\n id: partial.id,\n status,\n path: partial.path ?? prev?.path ?? '',\n version,\n transferred,\n total,\n percent,\n startedAt: partial.startedAt ?? prev?.startedAt ?? now(),\n updatedAt: partial.updatedAt ?? now(),\n error: partial.error ?? prev?.error,\n result: partial.result ?? prev?.result,\n };\n\n const wasMissing = !sessions.has(partial.id);\n\n sessions.set(partial.id, next);\n void persistUpsert(next);\n\n const ls = listeners.get(partial.id);\n if (ls) for (const fn of ls) fn(next);\n\n if (wasMissing) notifyList();\n}\n\nexport function removeUploadSession(id: string) {\n const existed = sessions.delete(id);\n clearUploadSessionListeners(id);\n void persistRemove(id);\n if (existed) notifyList();\n}\n\nfunction clearUploadSessionListeners(id: string) {\n if (listeners.has(id)) listeners.delete(id);\n}\n\nexport function subscribeUploadSession(id: string, fn: Listener): () => void {\n let set = listeners.get(id);\n if (!set) {\n set = new Set();\n listeners.set(id, set);\n }\n set.add(fn);\n\n const current = sessions.get(id);\n if (current) fn(current);\n\n return () => {\n const s = listeners.get(id);\n if (!s) return;\n s.delete(fn);\n if (s.size === 0) listeners.delete(id);\n };\n}\n\n/** Subscribe to changes in the *list* (new/removed sessions, rehydrate). */\nexport function subscribeUploadSessionsList(fn: ListListener): () => void {\n listListeners.add(fn);\n return () => listListeners.delete(fn);\n}\n"]}
1
+ {"version":3,"file":"upload-store.js","sourceRoot":"","sources":["../../src/utils/upload-store.ts"],"names":[],"mappings":"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","sourcesContent":["import { now } from '@ttt-productions/firebase-helpers';\nimport type { UploadSessionState, UploadSessionPersistenceAdapter } from '../types.js';\n\ntype Listener = (s: UploadSessionState) => void;\ntype ListListener = () => void;\n\nconst sessions = new Map<string, UploadSessionState>();\nconst listeners = new Map<string, Set<Listener>>();\nconst listListeners = new Set<ListListener>();\n\nlet persistence: UploadSessionPersistenceAdapter | null = null;\n\nexport function setUploadSessionPersistence(adapter: UploadSessionPersistenceAdapter | null) {\n persistence = adapter;\n}\n\nexport type PersistenceErrorHandler = (\n err: unknown,\n op: 'set' | 'remove' | 'get',\n id: string,\n) => void;\n\nlet persistenceErrorHandler: PersistenceErrorHandler | null = null;\n\nexport function setUploadSessionPersistenceErrorHandler(handler: PersistenceErrorHandler | null) {\n persistenceErrorHandler = handler;\n}\n\nasync function persistUpsert(state: UploadSessionState) {\n try {\n await persistence?.set(state.id, state);\n } catch (err) {\n console.error('[upload-store] Failed to persist session:', state.id, err);\n persistenceErrorHandler?.(err, 'set', state.id);\n }\n}\n\nasync function persistRemove(id: string) {\n try {\n await persistence?.remove(id);\n } catch (err) {\n console.error('[upload-store] Failed to remove persisted session:', id, err);\n persistenceErrorHandler?.(err, 'remove', id);\n }\n}\n\nfunction notifyList() {\n for (const fn of listListeners) fn();\n}\n\nconst MAX_SESSIONS = 100;\nconst SUCCESS_TTL_MS = 24 * 60 * 60 * 1000;\nconst ERROR_TTL_MS = 7 * 24 * 60 * 60 * 1000;\n\nexport function pruneOldUploadSessions() {\n const t = now();\n const all = listUploadSessions();\n\n let pruned = 0;\n\n for (const s of all) {\n if (s.status === 'success' && t - s.updatedAt > SUCCESS_TTL_MS) {\n removeUploadSession(s.id);\n pruned++;\n } else if (\n (s.status === 'error' || s.status === 'canceled') &&\n t - s.updatedAt > ERROR_TTL_MS\n ) {\n removeUploadSession(s.id);\n pruned++;\n }\n }\n\n const remaining = listUploadSessions();\n if (remaining.length > MAX_SESSIONS) {\n for (const s of remaining.slice(MAX_SESSIONS)) {\n removeUploadSession(s.id);\n pruned++;\n }\n }\n\n return pruned;\n}\n\nexport async function rehydrateUploadSessions() {\n if (!persistence) return;\n\n const ids = await persistence.listIds();\n for (const id of ids) {\n try {\n const s = await persistence.get(id);\n if (s) sessions.set(id, s);\n } catch (err) {\n console.error('[upload-store] Failed to read persisted session:', id, err);\n persistenceErrorHandler?.(err, 'get', id);\n }\n }\n\n pruneOldUploadSessions();\n notifyList();\n}\n\nexport function getUploadSession(id: string): UploadSessionState | undefined {\n return sessions.get(id);\n}\n\nexport function listUploadSessions(): UploadSessionState[] {\n return Array.from(sessions.values()).sort((a, b) => b.updatedAt - a.updatedAt);\n}\n\nexport function upsertUploadSession(\n partial: Partial<UploadSessionState> & Pick<UploadSessionState, 'id'>,\n) {\n const prev = sessions.get(partial.id);\n\n if (prev && partial.updatedAt != null && partial.updatedAt < prev.updatedAt) return;\n\n const prevStatus = prev?.status ?? 'idle';\n const nextStatus = partial.status ?? prevStatus;\n\n const isTerminal = (s: string) => s === 'success' || s === 'error' || s === 'canceled';\n const prevIsTerminal = isTerminal(prevStatus);\n const nextIsTerminal = isTerminal(nextStatus);\n\n const status = prevIsTerminal ? prevStatus : nextIsTerminal ? nextStatus : nextStatus;\n\n const transferred = Math.max(partial.transferred ?? 0, prev?.transferred ?? 0);\n const total = Math.max(partial.total ?? 0, prev?.total ?? 0);\n const percent = Math.max(partial.percent ?? 0, prev?.percent ?? 0);\n\n const version = (prev?.version ?? 0) + 1;\n\n const next: UploadSessionState = {\n id: partial.id,\n status,\n path: partial.path ?? prev?.path ?? '',\n version,\n transferred,\n total,\n percent,\n startedAt: partial.startedAt ?? prev?.startedAt ?? now(),\n updatedAt: partial.updatedAt ?? now(),\n error: partial.error ?? prev?.error,\n result: partial.result ?? prev?.result,\n };\n\n const wasMissing = !sessions.has(partial.id);\n\n sessions.set(partial.id, next);\n void persistUpsert(next);\n\n const ls = listeners.get(partial.id);\n if (ls) for (const fn of ls) fn(next);\n\n if (wasMissing) notifyList();\n}\n\nexport function removeUploadSession(id: string) {\n const existed = sessions.delete(id);\n clearUploadSessionListeners(id);\n void persistRemove(id);\n if (existed) notifyList();\n}\n\nfunction clearUploadSessionListeners(id: string) {\n if (listeners.has(id)) listeners.delete(id);\n}\n\nexport function subscribeUploadSession(id: string, fn: Listener): () => void {\n let set = listeners.get(id);\n if (!set) {\n set = new Set();\n listeners.set(id, set);\n }\n set.add(fn);\n\n const current = sessions.get(id);\n if (current) fn(current);\n\n return () => {\n const s = listeners.get(id);\n if (!s) return;\n s.delete(fn);\n if (s.size === 0) listeners.delete(id);\n };\n}\n\nexport function subscribeUploadSessionsList(fn: ListListener): () => void {\n listListeners.add(fn);\n return () => listListeners.delete(fn);\n}\n\nexport function __resetUploadStore(): void {\n sessions.clear();\n listeners.clear();\n listListeners.clear();\n persistence = null;\n persistenceErrorHandler = null;\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ttt-productions/upload-core",
3
- "version": "0.7.6",
3
+ "version": "0.7.8",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+https://github.com/ttt-productions/ttt-packages.git",
@@ -31,8 +31,8 @@
31
31
  "prepublishOnly": "npm run clean && npm run build"
32
32
  },
33
33
  "dependencies": {
34
- "@ttt-productions/firebase-helpers": "^0.12.13",
35
- "@ttt-productions/media-schemas": "^0.14.11"
34
+ "@ttt-productions/firebase-helpers": "^0.12.15",
35
+ "@ttt-productions/media-schemas": "^0.14.12"
36
36
  },
37
37
  "peerDependencies": {
38
38
  "firebase": ">=12.0.0"