@ttt-productions/upload-core 0.7.5 → 0.7.7

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.
Files changed (52) hide show
  1. package/dist/browser/delete.d.ts +3 -0
  2. package/dist/browser/delete.d.ts.map +1 -0
  3. package/dist/{storage → browser}/delete.js +1 -1
  4. package/dist/{storage → browser}/delete.js.map +1 -1
  5. package/dist/browser/index.d.ts +5 -5
  6. package/dist/browser/index.d.ts.map +1 -1
  7. package/dist/browser/index.js +5 -5
  8. package/dist/browser/index.js.map +1 -1
  9. package/dist/{persistence → browser}/localstorage.d.ts +1 -1
  10. package/dist/browser/localstorage.d.ts.map +1 -0
  11. package/dist/{persistence → browser}/localstorage.js +2 -2
  12. package/dist/browser/localstorage.js.map +1 -0
  13. package/dist/browser/types.d.ts +3 -3
  14. package/dist/browser/types.d.ts.map +1 -1
  15. package/dist/browser/types.js.map +1 -1
  16. package/dist/{queue → browser}/upload-queue.d.ts +2 -2
  17. package/dist/{queue → browser}/upload-queue.d.ts.map +1 -1
  18. package/dist/{queue → browser}/upload-queue.js +8 -8
  19. package/dist/browser/upload-queue.js.map +1 -0
  20. package/dist/browser/upload.d.ts +26 -0
  21. package/dist/browser/upload.d.ts.map +1 -0
  22. package/dist/{storage → browser}/upload.js +18 -22
  23. package/dist/browser/upload.js.map +1 -0
  24. package/dist/index.d.ts +6 -6
  25. package/dist/index.js +6 -6
  26. package/dist/index.js.map +1 -1
  27. package/dist/storage/upload-error.d.ts.map +1 -1
  28. package/dist/storage/upload-error.js +1 -1
  29. package/dist/storage/upload-error.js.map +1 -1
  30. package/dist/types.d.ts +1 -1
  31. package/dist/types.d.ts.map +1 -1
  32. package/dist/types.js.map +1 -1
  33. package/dist/utils/file-size.js +1 -1
  34. package/dist/utils/file-size.js.map +1 -1
  35. package/dist/utils/filename.d.ts.map +1 -1
  36. package/dist/utils/filename.js +9 -9
  37. package/dist/utils/filename.js.map +1 -1
  38. package/dist/utils/path.js +9 -9
  39. package/dist/utils/path.js.map +1 -1
  40. package/dist/utils/upload-store.d.ts +5 -3
  41. package/dist/utils/upload-store.d.ts.map +1 -1
  42. package/dist/utils/upload-store.js +21 -12
  43. package/dist/utils/upload-store.js.map +1 -1
  44. package/package.json +3 -3
  45. package/dist/persistence/localstorage.d.ts.map +0 -1
  46. package/dist/persistence/localstorage.js.map +0 -1
  47. package/dist/queue/upload-queue.js.map +0 -1
  48. package/dist/storage/delete.d.ts +0 -3
  49. package/dist/storage/delete.d.ts.map +0 -1
  50. package/dist/storage/upload.d.ts +0 -27
  51. package/dist/storage/upload.d.ts.map +0 -1
  52. package/dist/storage/upload.js.map +0 -1
@@ -0,0 +1,3 @@
1
+ import type { DeleteFileArgs } from './types.js';
2
+ export declare function deleteFile(args: DeleteFileArgs): Promise<void>;
3
+ //# sourceMappingURL=delete.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"delete.d.ts","sourceRoot":"","sources":["../../src/browser/delete.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAGjD,wBAAsB,UAAU,CAAC,IAAI,EAAE,cAAc,GAAG,OAAO,CAAC,IAAI,CAAC,CAGpE"}
@@ -1,4 +1,4 @@
1
- import { deleteObject, ref } from "firebase/storage";
1
+ import { deleteObject, ref } from 'firebase/storage';
2
2
  export async function deleteFile(args) {
3
3
  const { storage, path } = args;
4
4
  await deleteObject(ref(storage, path));
@@ -1 +1 @@
1
- {"version":3,"file":"delete.js","sourceRoot":"","sources":["../../src/storage/delete.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,YAAY,EAAE,GAAG,EAAE,MAAM,kBAAkB,CAAC;AAErD,MAAM,CAAC,KAAK,UAAU,UAAU,CAAC,IAAoB;IACnD,MAAM,EAAE,OAAO,EAAE,IAAI,EAAE,GAAG,IAAI,CAAC;IAC/B,MAAM,YAAY,CAAC,GAAG,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC,CAAC;AACzC,CAAC","sourcesContent":["import type { DeleteFileArgs } from \"../browser/types.js\";\nimport { deleteObject, ref } from \"firebase/storage\";\n\nexport async function deleteFile(args: DeleteFileArgs): Promise<void> {\n const { storage, path } = args;\n await deleteObject(ref(storage, path));\n}\n"]}
1
+ {"version":3,"file":"delete.js","sourceRoot":"","sources":["../../src/browser/delete.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,YAAY,EAAE,GAAG,EAAE,MAAM,kBAAkB,CAAC;AAErD,MAAM,CAAC,KAAK,UAAU,UAAU,CAAC,IAAoB;IACnD,MAAM,EAAE,OAAO,EAAE,IAAI,EAAE,GAAG,IAAI,CAAC;IAC/B,MAAM,YAAY,CAAC,GAAG,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC,CAAC;AACzC,CAAC","sourcesContent":["import type { DeleteFileArgs } from './types.js';\nimport { deleteObject, ref } from 'firebase/storage';\n\nexport async function deleteFile(args: DeleteFileArgs): Promise<void> {\n const { storage, path } = args;\n await deleteObject(ref(storage, path));\n}\n"]}
@@ -1,6 +1,6 @@
1
- export * from "./types.js";
2
- export * from "../storage/upload.js";
3
- export * from "../storage/delete.js";
4
- export * from "../queue/upload-queue.js";
5
- export * from "../persistence/localstorage.js";
1
+ export * from './types.js';
2
+ export * from './upload.js';
3
+ export * from './delete.js';
4
+ export * from './upload-queue.js';
5
+ export * from './localstorage.js';
6
6
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/browser/index.ts"],"names":[],"mappings":"AAAA,cAAc,YAAY,CAAC;AAC3B,cAAc,sBAAsB,CAAC;AACrC,cAAc,sBAAsB,CAAC;AACrC,cAAc,0BAA0B,CAAC;AACzC,cAAc,gCAAgC,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/browser/index.ts"],"names":[],"mappings":"AAAA,cAAc,YAAY,CAAC;AAC3B,cAAc,aAAa,CAAC;AAC5B,cAAc,aAAa,CAAC;AAC5B,cAAc,mBAAmB,CAAC;AAClC,cAAc,mBAAmB,CAAC"}
@@ -1,6 +1,6 @@
1
- export * from "./types.js";
2
- export * from "../storage/upload.js";
3
- export * from "../storage/delete.js";
4
- export * from "../queue/upload-queue.js";
5
- export * from "../persistence/localstorage.js";
1
+ export * from './types.js';
2
+ export * from './upload.js';
3
+ export * from './delete.js';
4
+ export * from './upload-queue.js';
5
+ export * from './localstorage.js';
6
6
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/browser/index.ts"],"names":[],"mappings":"AAAA,cAAc,YAAY,CAAC;AAC3B,cAAc,sBAAsB,CAAC;AACrC,cAAc,sBAAsB,CAAC;AACrC,cAAc,0BAA0B,CAAC;AACzC,cAAc,gCAAgC,CAAC","sourcesContent":["export * from \"./types.js\";\nexport * from \"../storage/upload.js\";\nexport * from \"../storage/delete.js\";\nexport * from \"../queue/upload-queue.js\";\nexport * from \"../persistence/localstorage.js\";\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/browser/index.ts"],"names":[],"mappings":"AAAA,cAAc,YAAY,CAAC;AAC3B,cAAc,aAAa,CAAC;AAC5B,cAAc,aAAa,CAAC;AAC5B,cAAc,mBAAmB,CAAC;AAClC,cAAc,mBAAmB,CAAC","sourcesContent":["export * from './types.js';\nexport * from './upload.js';\nexport * from './delete.js';\nexport * from './upload-queue.js';\nexport * from './localstorage.js';\n"]}
@@ -1,4 +1,4 @@
1
- import type { UploadSessionPersistenceAdapter } from "../types.js";
1
+ import type { UploadSessionPersistenceAdapter } from '../types.js';
2
2
  export declare function createLocalStorageUploadSessionPersistence(opts?: {
3
3
  /** Prefix for keys. Default: "upload_session:" */
4
4
  prefix?: string;
@@ -0,0 +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,13 +1,13 @@
1
1
  function hasLocalStorage() {
2
2
  try {
3
- return typeof window !== "undefined" && !!window.localStorage;
3
+ return typeof window !== 'undefined' && !!window.localStorage;
4
4
  }
5
5
  catch {
6
6
  return false;
7
7
  }
8
8
  }
9
9
  export function createLocalStorageUploadSessionPersistence(opts) {
10
- const prefix = opts?.prefix ?? "upload_session:";
10
+ const prefix = opts?.prefix ?? 'upload_session:';
11
11
  const k = (id) => `${prefix}${id}`;
12
12
  const safeParse = (raw) => {
13
13
  if (!raw)
@@ -0,0 +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,5 +1,5 @@
1
- import type { FirebaseStorage, UploadMetadata, UploadTask, UploadTaskSnapshot } from "firebase/storage";
2
- import type { UploadFileResumableResult } from "../types.js";
1
+ import type { FirebaseStorage, UploadMetadata, UploadTask, UploadTaskSnapshot } from 'firebase/storage';
2
+ import type { UploadFileResumableResult } from '../types.js';
3
3
  export type UploadProgressHandler = (p: {
4
4
  transferred: number;
5
5
  total: number;
@@ -28,7 +28,7 @@ export interface UploadController {
28
28
  /** Resolves on success, rejects on error/cancel. */
29
29
  done: Promise<UploadFileResumableResult>;
30
30
  }
31
- export interface StartUploadArgs extends Omit<UploadFileResumableArgs, "onProgress"> {
31
+ export interface StartUploadArgs extends Omit<UploadFileResumableArgs, 'onProgress'> {
32
32
  /** Optional stable id for UI tracking. */
33
33
  id?: string;
34
34
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/browser/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,cAAc,EAAE,UAAU,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AACxG,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,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 +1 @@
1
- {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/browser/types.ts"],"names":[],"mappings":"","sourcesContent":["import type { FirebaseStorage, UploadMetadata, UploadTask, UploadTaskSnapshot } 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; // 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,5 +1,5 @@
1
- import type { UploadQueueOptions } from "../types.js";
2
- import type { UploadController, StartUploadArgs } from "../browser/types.js";
1
+ import type { UploadQueueOptions } from '../types.js';
2
+ import type { UploadController, StartUploadArgs } from './types.js';
3
3
  export declare class UploadQueue {
4
4
  private concurrency;
5
5
  private running;
@@ -1 +1 @@
1
- {"version":3,"file":"upload-queue.d.ts","sourceRoot":"","sources":["../../src/queue/upload-queue.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAA6B,kBAAkB,EAAE,MAAM,aAAa,CAAC;AACjF,OAAO,KAAK,EAAE,gBAAgB,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AAqB7E,qBAAa,WAAW;IACtB,OAAO,CAAC,WAAW,CAAS;IAC5B,OAAO,CAAC,OAAO,CAAK;IACpB,OAAO,CAAC,OAAO,CAAa;IAC5B,OAAO,CAAC,GAAG,CAAK;gBAEJ,IAAI,CAAC,EAAE,kBAAkB;IAIrC,cAAc,CAAC,CAAC,EAAE,MAAM;IAKxB,eAAe;IAIf,eAAe;IAIf,OAAO,CAAC,IAAI,EAAE,eAAe,GAAG;QAAE,QAAQ,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,gBAAgB;IA+DxE,OAAO,CAAC,IAAI;CAoCb"}
1
+ {"version":3,"file":"upload-queue.d.ts","sourceRoot":"","sources":["../../src/browser/upload-queue.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAA6B,kBAAkB,EAAE,MAAM,aAAa,CAAC;AACjF,OAAO,KAAK,EAAE,gBAAgB,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAqBpE,qBAAa,WAAW;IACtB,OAAO,CAAC,WAAW,CAAS;IAC5B,OAAO,CAAC,OAAO,CAAK;IACpB,OAAO,CAAC,OAAO,CAAa;IAC5B,OAAO,CAAC,GAAG,CAAK;gBAEJ,IAAI,CAAC,EAAE,kBAAkB;IAIrC,cAAc,CAAC,CAAC,EAAE,MAAM;IAKxB,eAAe;IAIf,eAAe;IAIf,OAAO,CAAC,IAAI,EAAE,eAAe,GAAG;QAAE,QAAQ,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,gBAAgB;IA+DxE,OAAO,CAAC,IAAI;CAoCb"}
@@ -1,6 +1,6 @@
1
- import { getFileSize } from "../utils/file-size.js";
2
- import { upsertUploadSession, removeUploadSession } from "../utils/upload-store.js";
3
- import { startResumableUpload } from "../storage/upload.js";
1
+ import { getFileSize } from '../utils/file-size.js';
2
+ import { upsertUploadSession, removeUploadSession } from '../utils/upload-store.js';
3
+ import { startResumableUpload } from './upload.js';
4
4
  function safeId() {
5
5
  return Math.random().toString(36).slice(2) + Date.now().toString(36);
6
6
  }
@@ -31,7 +31,7 @@ export class UploadQueue {
31
31
  const startedAt = Date.now();
32
32
  upsertUploadSession({
33
33
  id,
34
- status: "queued",
34
+ status: 'queued',
35
35
  path: args.path,
36
36
  transferred: 0,
37
37
  total: getFileSize(args.file),
@@ -56,7 +56,7 @@ export class UploadQueue {
56
56
  return real.cancel();
57
57
  this.pending = this.pending.filter((j) => j.id !== id);
58
58
  removeUploadSession(id);
59
- rejectDone(new DOMException("Aborted", "AbortError"));
59
+ rejectDone(new DOMException('Aborted', 'AbortError'));
60
60
  return true;
61
61
  },
62
62
  done,
@@ -82,14 +82,14 @@ export class UploadQueue {
82
82
  }
83
83
  pump() {
84
84
  while (this.running < this.concurrency && this.pending.length > 0) {
85
- this.pending.sort((a, b) => (b.priority - a.priority) || (a.seq - b.seq));
85
+ this.pending.sort((a, b) => b.priority - a.priority || a.seq - b.seq);
86
86
  const job = this.pending.shift();
87
87
  this.running += 1;
88
88
  try {
89
89
  const { priority: _p, ...rest } = job.args;
90
90
  const c = startResumableUpload({
91
91
  ...rest,
92
- id: job.id, // guarantee
92
+ id: job.id,
93
93
  });
94
94
  job.resolveController(c);
95
95
  c.done.finally(() => {
@@ -101,7 +101,7 @@ export class UploadQueue {
101
101
  this.running -= 1;
102
102
  upsertUploadSession({
103
103
  id: job.id,
104
- status: "error",
104
+ status: 'error',
105
105
  path: job.args.path,
106
106
  error: e,
107
107
  updatedAt: Date.now(),
@@ -0,0 +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"]}
@@ -0,0 +1,26 @@
1
+ import type { UploadFileResumableResult } from '../types.js';
2
+ import type { UploadFileResumableArgs, UploadController } from './types.js';
3
+ import { type FirebaseStorage, type UploadMetadata } from 'firebase/storage';
4
+ export declare function startResumableUpload(args: {
5
+ id: string;
6
+ storage: FirebaseStorage;
7
+ path: string;
8
+ file: Blob;
9
+ metadata?: UploadMetadata;
10
+ onProgress?: UploadFileResumableArgs['onProgress'];
11
+ signal?: AbortSignal;
12
+ }): UploadController;
13
+ export declare function uploadFileResumable(args: Omit<Parameters<typeof startResumableUpload>[0], 'id'> & {
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
+ allowNeutralContentType?: boolean;
25
+ }): Promise<UploadFileResumableResult>;
26
+ //# sourceMappingURL=upload.d.ts.map
@@ -0,0 +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,13 +1,13 @@
1
- import { getFileSize } from "../utils/file-size.js";
2
- import { upsertUploadSession } from "../utils/upload-store.js";
3
- import { NEUTRAL_CONTENT_TYPE } from "@ttt-productions/media-schemas";
4
- import { UploadError, isValidMediaContentType } from "./upload-error.js";
5
- import { ref, uploadBytesResumable, } from "firebase/storage";
1
+ import { getFileSize } from '../utils/file-size.js';
2
+ import { upsertUploadSession } from '../utils/upload-store.js';
3
+ import { NEUTRAL_CONTENT_TYPE } from '@ttt-productions/media-schemas';
4
+ import { UploadError, isValidMediaContentType } from '../storage/upload-error.js';
5
+ import { ref, uploadBytesResumable, } from 'firebase/storage';
6
6
  function isAbortError(e) {
7
- return e instanceof DOMException && e.name === "AbortError";
7
+ return e instanceof DOMException && e.name === 'AbortError';
8
8
  }
9
9
  function isStorageCanceledError(e) {
10
- return typeof e === "object" && e !== null && e.code === "storage/canceled";
10
+ return (typeof e === 'object' && e !== null && e.code === 'storage/canceled');
11
11
  }
12
12
  export function startResumableUpload(args) {
13
13
  const { id, storage, path, file, metadata, onProgress, signal } = args;
@@ -16,7 +16,7 @@ export function startResumableUpload(args) {
16
16
  const startedAt = Date.now();
17
17
  upsertUploadSession({
18
18
  id,
19
- status: "uploading",
19
+ status: 'uploading',
20
20
  path,
21
21
  transferred: 0,
22
22
  total: getFileSize(file),
@@ -52,19 +52,15 @@ export function startResumableUpload(args) {
52
52
  if (signal) {
53
53
  if (signal.aborted)
54
54
  onAbort();
55
- signal.addEventListener("abort", onAbort, { once: true });
55
+ signal.addEventListener('abort', onAbort, { once: true });
56
56
  }
57
- unsub = task.on("state_changed", (snap) => {
57
+ unsub = task.on('state_changed', (snap) => {
58
58
  const transferred = snap.bytesTransferred ?? 0;
59
59
  const total = snap.totalBytes ?? getFileSize(file);
60
60
  const percent = total > 0 ? (transferred / total) * 100 : 0;
61
61
  upsertUploadSession({
62
62
  id,
63
- status: snap.state === "paused"
64
- ? "paused"
65
- : snap.state === "running"
66
- ? "uploading"
67
- : "uploading",
63
+ status: snap.state === 'paused' ? 'paused' : snap.state === 'running' ? 'uploading' : 'uploading',
68
64
  path,
69
65
  transferred,
70
66
  total,
@@ -74,18 +70,18 @@ export function startResumableUpload(args) {
74
70
  onProgress?.({ transferred, total, percent, snapshot: snap });
75
71
  }, async (err) => {
76
72
  if (signal)
77
- signal.removeEventListener("abort", onAbort);
73
+ signal.removeEventListener('abort', onAbort);
78
74
  if (unsub)
79
75
  unsub();
80
76
  const cancelError = isAbortError(err)
81
77
  ? err
82
78
  : canceledLocally && isStorageCanceledError(err)
83
- ? new DOMException("Aborted", "AbortError")
79
+ ? new DOMException('Aborted', 'AbortError')
84
80
  : null;
85
81
  if (cancelError) {
86
82
  upsertUploadSession({
87
83
  id,
88
- status: "canceled",
84
+ status: 'canceled',
89
85
  path,
90
86
  updatedAt: Date.now(),
91
87
  error: cancelError,
@@ -94,7 +90,7 @@ export function startResumableUpload(args) {
94
90
  }
95
91
  upsertUploadSession({
96
92
  id,
97
- status: "error",
93
+ status: 'error',
98
94
  path,
99
95
  updatedAt: Date.now(),
100
96
  error: err,
@@ -111,7 +107,7 @@ export function startResumableUpload(args) {
111
107
  };
112
108
  upsertUploadSession({
113
109
  id,
114
- status: "success",
110
+ status: 'success',
115
111
  path,
116
112
  transferred: size,
117
113
  total: size,
@@ -124,7 +120,7 @@ export function startResumableUpload(args) {
124
120
  catch (e) {
125
121
  upsertUploadSession({
126
122
  id,
127
- status: "error",
123
+ status: 'error',
128
124
  path,
129
125
  updatedAt: Date.now(),
130
126
  error: e,
@@ -133,7 +129,7 @@ export function startResumableUpload(args) {
133
129
  }
134
130
  finally {
135
131
  if (signal)
136
- signal.removeEventListener("abort", onAbort);
132
+ signal.removeEventListener('abort', onAbort);
137
133
  if (unsub)
138
134
  unsub();
139
135
  }
@@ -0,0 +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"]}
package/dist/index.d.ts CHANGED
@@ -1,7 +1,7 @@
1
- export * from "./types.js";
2
- export * from "./storage/upload-error.js";
3
- export * from "./utils/filename.js";
4
- export * from "./utils/path.js";
5
- export * from "./utils/upload-store.js";
6
- export * from "./utils/file-size.js";
1
+ export * from './types.js';
2
+ export * from './storage/upload-error.js';
3
+ export * from './utils/filename.js';
4
+ export * from './utils/path.js';
5
+ export * from './utils/upload-store.js';
6
+ export * from './utils/file-size.js';
7
7
  //# sourceMappingURL=index.d.ts.map
package/dist/index.js CHANGED
@@ -1,7 +1,7 @@
1
- export * from "./types.js";
2
- export * from "./storage/upload-error.js";
3
- export * from "./utils/filename.js";
4
- export * from "./utils/path.js";
5
- export * from "./utils/upload-store.js";
6
- export * from "./utils/file-size.js";
1
+ export * from './types.js';
2
+ export * from './storage/upload-error.js';
3
+ export * from './utils/filename.js';
4
+ export * from './utils/path.js';
5
+ export * from './utils/upload-store.js';
6
+ export * from './utils/file-size.js';
7
7
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,YAAY,CAAC;AAE3B,cAAc,2BAA2B,CAAC;AAE1C,cAAc,qBAAqB,CAAC;AACpC,cAAc,iBAAiB,CAAC;AAChC,cAAc,yBAAyB,CAAC;AACxC,cAAc,sBAAsB,CAAC","sourcesContent":["export * from \"./types.js\";\n\nexport * from \"./storage/upload-error.js\";\n\nexport * from \"./utils/filename.js\";\nexport * from \"./utils/path.js\";\nexport * from \"./utils/upload-store.js\";\nexport * from \"./utils/file-size.js\";\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,YAAY,CAAC;AAE3B,cAAc,2BAA2B,CAAC;AAE1C,cAAc,qBAAqB,CAAC;AACpC,cAAc,iBAAiB,CAAC;AAChC,cAAc,yBAAyB,CAAC;AACxC,cAAc,sBAAsB,CAAC","sourcesContent":["export * from './types.js';\n\nexport * from './storage/upload-error.js';\n\nexport * from './utils/filename.js';\nexport * from './utils/path.js';\nexport * from './utils/upload-store.js';\nexport * from './utils/file-size.js';\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"upload-error.d.ts","sourceRoot":"","sources":["../../src/storage/upload-error.ts"],"names":[],"mappings":"AAEA,MAAM,MAAM,eAAe,GACvB,sBAAsB,GACtB,sBAAsB,CAAC;AAE3B;;;;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;;;;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,4 +1,4 @@
1
- import { NEUTRAL_CONTENT_TYPE } from "@ttt-productions/media-schemas";
1
+ import { NEUTRAL_CONTENT_TYPE } from '@ttt-productions/media-schemas';
2
2
  /**
3
3
  * Typed error thrown by upload-core when a client-side guard rejects an upload
4
4
  * before it reaches Firebase Storage. Consumers can catch this and show a
@@ -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;AAMtE;;;;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 =\n | 'missing_content_type'\n | '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;;;;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"]}
package/dist/types.d.ts CHANGED
@@ -7,7 +7,7 @@ export interface UploadFileResumableResult {
7
7
  contentType: string | null;
8
8
  size: number;
9
9
  }
10
- export type UploadSessionStatus = "idle" | "queued" | "uploading" | "paused" | "success" | "error" | "canceled";
10
+ export type UploadSessionStatus = 'idle' | 'queued' | 'uploading' | 'paused' | 'success' | 'error' | 'canceled';
11
11
  export interface UploadSessionPersistenceAdapter {
12
12
  /** Return all session ids currently stored. */
13
13
  listIds: () => Promise<string[]> | string[];
@@ -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,GAC3B,MAAM,GACN,QAAQ,GACR,WAAW,GACX,QAAQ,GACR,SAAS,GACT,OAAO,GACP,UAAU,CAAC;AAEf,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;;;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"}
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\"\n | \"queued\"\n | \"uploading\"\n | \"paused\"\n | \"success\"\n | \"error\"\n | \"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":["/**\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,6 +1,6 @@
1
1
  export function getFileSize(file) {
2
2
  const anyFile = file;
3
3
  const size = anyFile?.size;
4
- return typeof size === "number" && size >= 0 ? size : 0;
4
+ return typeof size === 'number' && size >= 0 ? size : 0;
5
5
  }
6
6
  //# sourceMappingURL=file-size.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"file-size.js","sourceRoot":"","sources":["../../src/utils/file-size.ts"],"names":[],"mappings":"AAAA,MAAM,UAAU,WAAW,CAAC,IAAiB;IAC3C,MAAM,OAAO,GAAG,IAAW,CAAC;IAC5B,MAAM,IAAI,GAAG,OAAO,EAAE,IAAI,CAAC;IAC3B,OAAO,OAAO,IAAI,KAAK,QAAQ,IAAI,IAAI,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC;AAC1D,CAAC","sourcesContent":["export function getFileSize(file: Blob | File): number {\n const anyFile = file as any;\n const size = anyFile?.size;\n return typeof size === \"number\" && size >= 0 ? size : 0;\n}\n"]}
1
+ {"version":3,"file":"file-size.js","sourceRoot":"","sources":["../../src/utils/file-size.ts"],"names":[],"mappings":"AAAA,MAAM,UAAU,WAAW,CAAC,IAAiB;IAC3C,MAAM,OAAO,GAAG,IAAW,CAAC;IAC5B,MAAM,IAAI,GAAG,OAAO,EAAE,IAAI,CAAC;IAC3B,OAAO,OAAO,IAAI,KAAK,QAAQ,IAAI,IAAI,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC;AAC1D,CAAC","sourcesContent":["export function getFileSize(file: Blob | File): number {\n const anyFile = file as any;\n const size = anyFile?.size;\n return typeof size === 'number' && size >= 0 ? size : 0;\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"filename.d.ts","sourceRoot":"","sources":["../../src/utils/filename.ts"],"names":[],"mappings":"AAAA,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAgBpD"}
1
+ {"version":3,"file":"filename.d.ts","sourceRoot":"","sources":["../../src/utils/filename.ts"],"names":[],"mappings":"AAAA,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAgBtD"}
@@ -1,16 +1,16 @@
1
1
  export function normalizeFilename(name) {
2
- const trimmed = (name || "").trim();
2
+ const trimmed = (name || '').trim();
3
3
  if (!trimmed)
4
- return "file";
5
- const lastDot = trimmed.lastIndexOf(".");
4
+ return 'file';
5
+ const lastDot = trimmed.lastIndexOf('.');
6
6
  const base = lastDot > 0 ? trimmed.slice(0, lastDot) : trimmed;
7
- const ext = lastDot > 0 ? trimmed.slice(lastDot).toLowerCase() : "";
7
+ const ext = lastDot > 0 ? trimmed.slice(lastDot).toLowerCase() : '';
8
8
  const safeBase = base
9
9
  .toLowerCase()
10
- .replace(/[^a-z0-9-_ ]/g, "")
11
- .replace(/\s+/g, "-")
12
- .replace(/-+/g, "-")
13
- .replace(/^-|-$/g, "");
14
- return `${safeBase || "file"}${ext}`;
10
+ .replace(/[^a-z0-9-_ ]/g, '')
11
+ .replace(/\s+/g, '-')
12
+ .replace(/-+/g, '-')
13
+ .replace(/^-|-$/g, '');
14
+ return `${safeBase || 'file'}${ext}`;
15
15
  }
16
16
  //# sourceMappingURL=filename.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"filename.js","sourceRoot":"","sources":["../../src/utils/filename.ts"],"names":[],"mappings":"AAAA,MAAM,UAAU,iBAAiB,CAAC,IAAY;IAC1C,MAAM,OAAO,GAAG,CAAC,IAAI,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;IACpC,IAAI,CAAC,OAAO;QAAE,OAAO,MAAM,CAAC;IAE5B,MAAM,OAAO,GAAG,OAAO,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC;IACzC,MAAM,IAAI,GAAG,OAAO,GAAG,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC;IAC/D,MAAM,GAAG,GAAG,OAAO,GAAG,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;IAEpE,MAAM,QAAQ,GAAG,IAAI;SAClB,WAAW,EAAE;SACb,OAAO,CAAC,eAAe,EAAE,EAAE,CAAC;SAC5B,OAAO,CAAC,MAAM,EAAE,GAAG,CAAC;SACpB,OAAO,CAAC,KAAK,EAAE,GAAG,CAAC;SACnB,OAAO,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC;IAEzB,OAAO,GAAG,QAAQ,IAAI,MAAM,GAAG,GAAG,EAAE,CAAC;AACvC,CAAC","sourcesContent":["export function normalizeFilename(name: string): string {\n const trimmed = (name || \"\").trim();\n if (!trimmed) return \"file\";\n \n const lastDot = trimmed.lastIndexOf(\".\");\n const base = lastDot > 0 ? trimmed.slice(0, lastDot) : trimmed;\n const ext = lastDot > 0 ? trimmed.slice(lastDot).toLowerCase() : \"\";\n \n const safeBase = base\n .toLowerCase()\n .replace(/[^a-z0-9-_ ]/g, \"\")\n .replace(/\\s+/g, \"-\")\n .replace(/-+/g, \"-\")\n .replace(/^-|-$/g, \"\");\n \n return `${safeBase || \"file\"}${ext}`;\n }\n "]}
1
+ {"version":3,"file":"filename.js","sourceRoot":"","sources":["../../src/utils/filename.ts"],"names":[],"mappings":"AAAA,MAAM,UAAU,iBAAiB,CAAC,IAAY;IAC5C,MAAM,OAAO,GAAG,CAAC,IAAI,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;IACpC,IAAI,CAAC,OAAO;QAAE,OAAO,MAAM,CAAC;IAE5B,MAAM,OAAO,GAAG,OAAO,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC;IACzC,MAAM,IAAI,GAAG,OAAO,GAAG,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC;IAC/D,MAAM,GAAG,GAAG,OAAO,GAAG,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;IAEpE,MAAM,QAAQ,GAAG,IAAI;SAClB,WAAW,EAAE;SACb,OAAO,CAAC,eAAe,EAAE,EAAE,CAAC;SAC5B,OAAO,CAAC,MAAM,EAAE,GAAG,CAAC;SACpB,OAAO,CAAC,KAAK,EAAE,GAAG,CAAC;SACnB,OAAO,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC;IAEzB,OAAO,GAAG,QAAQ,IAAI,MAAM,GAAG,GAAG,EAAE,CAAC;AACvC,CAAC","sourcesContent":["export function normalizeFilename(name: string): string {\n const trimmed = (name || '').trim();\n if (!trimmed) return 'file';\n\n const lastDot = trimmed.lastIndexOf('.');\n const base = lastDot > 0 ? trimmed.slice(0, lastDot) : trimmed;\n const ext = lastDot > 0 ? trimmed.slice(lastDot).toLowerCase() : '';\n\n const safeBase = base\n .toLowerCase()\n .replace(/[^a-z0-9-_ ]/g, '')\n .replace(/\\s+/g, '-')\n .replace(/-+/g, '-')\n .replace(/^-|-$/g, '');\n\n return `${safeBase || 'file'}${ext}`;\n}\n"]}
@@ -1,13 +1,13 @@
1
- import { joinPath } from "@ttt-productions/firebase-helpers";
2
- import { normalizeFilename } from "./filename.js";
1
+ import { joinPath } from '@ttt-productions/firebase-helpers';
2
+ import { normalizeFilename } from './filename.js';
3
3
  function sanitizeSegment(seg) {
4
- const s = String(seg ?? "").trim();
4
+ const s = String(seg ?? '').trim();
5
5
  // Remove slashes and collapse dot-runs to avoid traversal.
6
- const noSlashes = s.replace(/[\\/]+/g, "_");
7
- const noDots = noSlashes.replace(/\.+/g, ".");
8
- const cleaned = noDots.replace(/[^a-zA-Z0-9._-]/g, "_");
9
- const trimmed = cleaned.replace(/^\.+/, "").replace(/\.+$/, "");
10
- return trimmed.slice(0, 80) || "_";
6
+ const noSlashes = s.replace(/[\\/]+/g, '_');
7
+ const noDots = noSlashes.replace(/\.+/g, '.');
8
+ const cleaned = noDots.replace(/[^a-zA-Z0-9._-]/g, '_');
9
+ const trimmed = cleaned.replace(/^\.+/, '').replace(/\.+$/, '');
10
+ return trimmed.slice(0, 80) || '_';
11
11
  }
12
12
  export function buildUploadPath(args) {
13
13
  const { basePath, ownerId, contentId, filename } = args;
@@ -15,7 +15,7 @@ export function buildUploadPath(args) {
15
15
  const segments = [basePath, ownerId, contentId]
16
16
  .filter((p) => !!p)
17
17
  .map(sanitizeSegment);
18
- const filePart = filename ? normalizeFilename(filename) : "file";
18
+ const filePart = filename ? normalizeFilename(filename) : 'file';
19
19
  return joinPath(...segments, filePart);
20
20
  }
21
21
  //# sourceMappingURL=path.js.map
@@ -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}"]}
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,18 +1,20 @@
1
- import type { UploadSessionState, UploadSessionPersistenceAdapter } from "../types.js";
1
+ import type { UploadSessionState, UploadSessionPersistenceAdapter } from '../types.js';
2
2
  type Listener = (s: UploadSessionState) => void;
3
3
  type ListListener = () => void;
4
4
  export declare function setUploadSessionPersistence(adapter: UploadSessionPersistenceAdapter | null): void;
5
- export type PersistenceErrorHandler = (err: unknown, op: "set" | "remove" | "get", id: string) => void;
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
8
  /** Load persisted sessions into the in-memory store. */
9
9
  export declare function rehydrateUploadSessions(): Promise<void>;
10
10
  export declare function getUploadSession(id: string): UploadSessionState | undefined;
11
11
  export declare function listUploadSessions(): UploadSessionState[];
12
- export declare function upsertUploadSession(partial: Partial<UploadSessionState> & Pick<UploadSessionState, "id">): void;
12
+ export declare function upsertUploadSession(partial: Partial<UploadSessionState> & Pick<UploadSessionState, 'id'>): void;
13
13
  export declare function removeUploadSession(id: string): void;
14
14
  export declare function subscribeUploadSession(id: string, fn: Listener): () => void;
15
15
  /** Subscribe to changes in the *list* (new/removed sessions, rehydrate). */
16
16
  export declare function subscribeUploadSessionsList(fn: ListListener): () => void;
17
+ /** Test-only: empties every session, listener, and persistence binding. Never call in production code. */
18
+ export declare function __resetUploadStore(): void;
17
19
  export {};
18
20
  //# 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,CAAC,GAAG,EAAE,OAAO,EAAE,EAAE,EAAE,KAAK,GAAG,QAAQ,GAAG,KAAK,EAAE,EAAE,EAAE,MAAM,KAAK,IAAI,CAAC;AAIvG,wBAAgB,uCAAuC,CAAC,OAAO,EAAE,uBAAuB,GAAG,IAAI,QAE9F;AA4BD,wBAAgB,sBAAsB,WAyBrC;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,CAAC,OAAO,EAAE,OAAO,CAAC,kBAAkB,CAAC,GAAG,IAAI,CAAC,kBAAkB,EAAE,IAAI,CAAC,QA8CxG;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,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;AAED,0GAA0G;AAC1G,wBAAgB,kBAAkB,IAAI,IAAI,CAMzC"}
@@ -1,4 +1,4 @@
1
- import { now } from "@ttt-productions/firebase-helpers";
1
+ import { now } from '@ttt-productions/firebase-helpers';
2
2
  const sessions = new Map();
3
3
  const listeners = new Map();
4
4
  const listListeners = new Set();
@@ -15,8 +15,8 @@ async function persistUpsert(state) {
15
15
  await persistence?.set(state.id, state);
16
16
  }
17
17
  catch (err) {
18
- console.error("[upload-store] Failed to persist session:", state.id, err);
19
- persistenceErrorHandler?.(err, "set", state.id);
18
+ console.error('[upload-store] Failed to persist session:', state.id, err);
19
+ persistenceErrorHandler?.(err, 'set', state.id);
20
20
  }
21
21
  }
22
22
  async function persistRemove(id) {
@@ -24,8 +24,8 @@ async function persistRemove(id) {
24
24
  await persistence?.remove(id);
25
25
  }
26
26
  catch (err) {
27
- console.error("[upload-store] Failed to remove persisted session:", id, err);
28
- persistenceErrorHandler?.(err, "remove", id);
27
+ console.error('[upload-store] Failed to remove persisted session:', id, err);
28
+ persistenceErrorHandler?.(err, 'remove', id);
29
29
  }
30
30
  }
31
31
  function notifyList() {
@@ -40,11 +40,12 @@ export function pruneOldUploadSessions() {
40
40
  const all = listUploadSessions();
41
41
  let pruned = 0;
42
42
  for (const s of all) {
43
- if (s.status === "success" && t - s.updatedAt > SUCCESS_TTL_MS) {
43
+ if (s.status === 'success' && t - s.updatedAt > SUCCESS_TTL_MS) {
44
44
  removeUploadSession(s.id);
45
45
  pruned++;
46
46
  }
47
- else if ((s.status === "error" || s.status === "canceled") && t - s.updatedAt > ERROR_TTL_MS) {
47
+ else if ((s.status === 'error' || s.status === 'canceled') &&
48
+ t - s.updatedAt > ERROR_TTL_MS) {
48
49
  removeUploadSession(s.id);
49
50
  pruned++;
50
51
  }
@@ -70,8 +71,8 @@ export async function rehydrateUploadSessions() {
70
71
  sessions.set(id, s);
71
72
  }
72
73
  catch (err) {
73
- console.error("[upload-store] Failed to read persisted session:", id, err);
74
- persistenceErrorHandler?.(err, "get", id);
74
+ console.error('[upload-store] Failed to read persisted session:', id, err);
75
+ persistenceErrorHandler?.(err, 'get', id);
75
76
  }
76
77
  }
77
78
  pruneOldUploadSessions();
@@ -88,9 +89,9 @@ export function upsertUploadSession(partial) {
88
89
  // Guard against out-of-order async updates.
89
90
  if (prev && partial.updatedAt != null && partial.updatedAt < prev.updatedAt)
90
91
  return;
91
- const prevStatus = prev?.status ?? "idle";
92
+ const prevStatus = prev?.status ?? 'idle';
92
93
  const nextStatus = partial.status ?? prevStatus;
93
- const isTerminal = (s) => s === "success" || s === "error" || s === "canceled";
94
+ const isTerminal = (s) => s === 'success' || s === 'error' || s === 'canceled';
94
95
  const prevIsTerminal = isTerminal(prevStatus);
95
96
  const nextIsTerminal = isTerminal(nextStatus);
96
97
  // Once terminal, stay terminal. If we receive a terminal nextStatus first, accept it.
@@ -103,7 +104,7 @@ export function upsertUploadSession(partial) {
103
104
  const next = {
104
105
  id: partial.id,
105
106
  status,
106
- path: partial.path ?? prev?.path ?? "",
107
+ path: partial.path ?? prev?.path ?? '',
107
108
  version,
108
109
  transferred,
109
110
  total,
@@ -158,4 +159,12 @@ export function subscribeUploadSessionsList(fn) {
158
159
  listListeners.add(fn);
159
160
  return () => listListeners.delete(fn);
160
161
  }
162
+ /** Test-only: empties every session, listener, and persistence binding. Never call in production code. */
163
+ export function __resetUploadStore() {
164
+ sessions.clear();
165
+ listeners.clear();
166
+ listListeners.clear();
167
+ persistence = null;
168
+ persistenceErrorHandler = null;
169
+ }
161
170
  //# 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 = (err: unknown, op: \"set\" | \"remove\" | \"get\", id: string) => 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 ((s.status === \"error\" || s.status === \"canceled\") && t - s.updatedAt > ERROR_TTL_MS) {\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(partial: Partial<UploadSessionState> & Pick<UploadSessionState, \"id\">) {\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; // 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\n/** Test-only: empties every session, listener, and persistence binding. Never call in production code. */\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.5",
3
+ "version": "0.7.7",
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.9",
35
- "@ttt-productions/media-schemas": "^0.14.8"
34
+ "@ttt-productions/firebase-helpers": "^0.12.14",
35
+ "@ttt-productions/media-schemas": "^0.14.11"
36
36
  },
37
37
  "peerDependencies": {
38
38
  "firebase": ">=12.0.0"
@@ -1 +0,0 @@
1
- {"version":3,"file":"localstorage.d.ts","sourceRoot":"","sources":["../../src/persistence/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 +0,0 @@
1
- {"version":3,"file":"localstorage.js","sourceRoot":"","sources":["../../src/persistence/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 +0,0 @@
1
- {"version":3,"file":"upload-queue.js","sourceRoot":"","sources":["../../src/queue/upload-queue.ts"],"names":[],"mappings":"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","sourcesContent":["import type { UploadFileResumableResult, UploadQueueOptions } from \"../types.js\";\nimport type { UploadController, StartUploadArgs } from \"../browser/types.js\";\nimport { getFileSize } from \"../utils/file-size.js\";\nimport { upsertUploadSession, removeUploadSession } from \"../utils/upload-store.js\";\nimport { startResumableUpload } from \"../storage/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, // guarantee\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,3 +0,0 @@
1
- import type { DeleteFileArgs } from "../browser/types.js";
2
- export declare function deleteFile(args: DeleteFileArgs): Promise<void>;
3
- //# sourceMappingURL=delete.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"delete.d.ts","sourceRoot":"","sources":["../../src/storage/delete.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAG1D,wBAAsB,UAAU,CAAC,IAAI,EAAE,cAAc,GAAG,OAAO,CAAC,IAAI,CAAC,CAGpE"}
@@ -1,27 +0,0 @@
1
- import type { UploadFileResumableResult } from "../types.js";
2
- import type { UploadFileResumableArgs, UploadController } from "../browser/types.js";
3
- import { type FirebaseStorage, type UploadMetadata } from "firebase/storage";
4
- export declare function startResumableUpload(args: {
5
- id: string;
6
- storage: FirebaseStorage;
7
- path: string;
8
- file: Blob;
9
- metadata?: UploadMetadata;
10
- onProgress?: UploadFileResumableArgs["onProgress"];
11
- signal?: AbortSignal;
12
- }): UploadController;
13
- export declare function uploadFileResumable(args: Omit<Parameters<typeof startResumableUpload>[0], "id"> & {
14
- id?: string;
15
- /**
16
- * EXPLICIT opt-in for the NEUTRAL content type (canonical-upload-content-
17
- * classification, 2026-07-31): a picker file with unknown browser metadata
18
- * must reach the server AS unknown (`application/octet-stream`) so the
19
- * server-side byte inspector — the only classification authority — sees it
20
- * unrewritten. Default false preserves the historical media-only gate for
21
- * every other consumer. A concrete media MIME is still validated either
22
- * way; this flag never permits arbitrary declared types, only the one
23
- * neutral value.
24
- */
25
- allowNeutralContentType?: boolean;
26
- }): Promise<UploadFileResumableResult>;
27
- //# sourceMappingURL=upload.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"upload.d.ts","sourceRoot":"","sources":["../../src/storage/upload.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,aAAa,CAAC;AAC7D,OAAO,KAAK,EAAE,uBAAuB,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AAMrF,OAAO,EAGL,KAAK,eAAe,EACpB,KAAK,cAAc,EAEpB,MAAM,kBAAkB,CAAC;AAU1B,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,CAqKnB;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;;;;;;;;;OASG;IACH,uBAAuB,CAAC,EAAE,OAAO,CAAC;CACnC,GACA,OAAO,CAAC,yBAAyB,CAAC,CAwBpC"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"upload.js","sourceRoot":"","sources":["../../src/storage/upload.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAC;AACpD,OAAO,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAC;AAC/D,OAAO,EAAE,oBAAoB,EAAE,MAAM,gCAAgC,CAAC;AACtE,OAAO,EAAE,WAAW,EAAE,uBAAuB,EAAE,MAAM,mBAAmB,CAAC;AAEzE,OAAO,EACL,GAAG,EACH,oBAAoB,GAIrB,MAAM,kBAAkB,CAAC;AAE1B,SAAS,YAAY,CAAC,CAAU;IAC9B,OAAO,CAAC,YAAY,YAAY,IAAI,CAAC,CAAC,IAAI,KAAK,YAAY,CAAC;AAC9D,CAAC;AAED,SAAS,sBAAsB,CAAC,CAAU;IACxC,OAAO,OAAO,CAAC,KAAK,QAAQ,IAAI,CAAC,KAAK,IAAI,IAAK,CAAwB,CAAC,IAAI,KAAK,kBAAkB,CAAC;AACtG,CAAC;AAED,MAAM,UAAU,oBAAoB,CAAC,IAQpC;IACC,MAAM,EAAE,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,QAAQ,EAAE,UAAU,EAAE,MAAM,EAAE,GAAG,IAAI,CAAC;IAEvE,MAAM,CAAC,GAAG,GAAG,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC;IAC7B,MAAM,IAAI,GAAG,oBAAoB,CAAC,CAAC,EAAE,IAAI,EAAE,QAAQ,CAAC,CAAC;IAErD,MAAM,SAAS,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;IAE7B,mBAAmB,CAAC;QAClB,EAAE;QACF,MAAM,EAAE,WAAW;QACnB,IAAI;QACJ,WAAW,EAAE,CAAC;QACd,KAAK,EAAE,WAAW,CAAC,IAAI,CAAC;QACxB,OAAO,EAAE,CAAC;QACV,SAAS;QACT,SAAS,EAAE,SAAS;KACrB,CAAC,CAAC;IAEH,IAAI,KAAK,GAAwB,IAAI,CAAC;IAEtC,IAAI,WAAoD,CAAC;IACzD,IAAI,UAAiC,CAAC;IACtC,MAAM,IAAI,GAAG,IAAI,OAAO,CAA4B,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE;QAC/D,WAAW,GAAG,GAAG,CAAC;QAClB,UAAU,GAAG,GAAG,CAAC;IACnB,CAAC,CAAC,CAAC;IAEH,kFAAkF;IAClF,iFAAiF;IACjF,gFAAgF;IAChF,8EAA8E;IAC9E,wBAAwB;IACxB,IAAI,eAAe,GAAG,KAAK,CAAC;IAC5B,MAAM,UAAU,GAAG,GAAG,EAAE;QACtB,eAAe,GAAG,IAAI,CAAC;QACvB,IAAI,CAAC;YACH,OAAO,IAAI,CAAC,MAAM,EAAE,CAAC;QACvB,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,KAAK,CAAC;QACf,CAAC;IACH,CAAC,CAAC;IAEF,MAAM,OAAO,GAAG,GAAG,EAAE;QACnB,UAAU,EAAE,CAAC;IACf,CAAC,CAAC;IAEF,IAAI,MAAM,EAAE,CAAC;QACX,IAAI,MAAM,CAAC,OAAO;YAAE,OAAO,EAAE,CAAC;QAC9B,MAAM,CAAC,gBAAgB,CAAC,OAAO,EAAE,OAAO,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC;IAC5D,CAAC;IAED,KAAK,GAAG,IAAI,CAAC,EAAE,CACb,eAAe,EACf,CAAC,IAAwB,EAAE,EAAE;QAC3B,MAAM,WAAW,GAAG,IAAI,CAAC,gBAAgB,IAAI,CAAC,CAAC;QAC/C,MAAM,KAAK,GAAG,IAAI,CAAC,UAAU,IAAI,WAAW,CAAC,IAAI,CAAC,CAAC;QACnD,MAAM,OAAO,GAAG,KAAK,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,WAAW,GAAG,KAAK,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;QAE5D,mBAAmB,CAAC;YAClB,EAAE;YACF,MAAM,EACJ,IAAI,CAAC,KAAK,KAAK,QAAQ;gBACrB,CAAC,CAAC,QAAQ;gBACV,CAAC,CAAC,IAAI,CAAC,KAAK,KAAK,SAAS;oBAC1B,CAAC,CAAC,WAAW;oBACb,CAAC,CAAC,WAAW;YACjB,IAAI;YACJ,WAAW;YACX,KAAK;YACL,OAAO;YACP,SAAS,EAAE,IAAI,CAAC,GAAG,EAAE;SACtB,CAAC,CAAC;QAEH,UAAU,EAAE,CAAC,EAAE,WAAW,EAAE,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC;IAChE,CAAC,EACD,KAAK,EAAE,GAAG,EAAE,EAAE;QACZ,IAAI,MAAM;YAAE,MAAM,CAAC,mBAAmB,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;QACzD,IAAI,KAAK;YAAE,KAAK,EAAE,CAAC;QAEnB,MAAM,WAAW,GAAG,YAAY,CAAC,GAAG,CAAC;YACnC,CAAC,CAAC,GAAG;YACL,CAAC,CAAC,eAAe,IAAI,sBAAsB,CAAC,GAAG,CAAC;gBAChD,CAAC,CAAC,IAAI,YAAY,CAAC,SAAS,EAAE,YAAY,CAAC;gBAC3C,CAAC,CAAC,IAAI,CAAC;QACT,IAAI,WAAW,EAAE,CAAC;YAChB,mBAAmB,CAAC;gBAClB,EAAE;gBACF,MAAM,EAAE,UAAU;gBAClB,IAAI;gBACJ,SAAS,EAAE,IAAI,CAAC,GAAG,EAAE;gBACrB,KAAK,EAAE,WAAW;aACnB,CAAC,CAAC;YACH,OAAO,UAAU,CAAC,WAAW,CAAC,CAAC;QACjC,CAAC;QAED,mBAAmB,CAAC;YAClB,EAAE;YACF,MAAM,EAAE,OAAO;YACf,IAAI;YACJ,SAAS,EAAE,IAAI,CAAC,GAAG,EAAE;YACrB,KAAK,EAAE,GAAG;SACX,CAAC,CAAC;QAEH,UAAU,CAAC,GAAG,CAAC,CAAC;IAClB,CAAC,EACD,KAAK,IAAI,EAAE;QACT,IAAI,CAAC;YACH,MAAM,WAAW,GAAG,IAAI,CAAC,QAAQ,CAAC,QAAQ,EAAE,WAAW,IAAI,IAAI,CAAC;YAChE,MAAM,IAAI,GAAG,IAAI,CAAC,QAAQ,CAAC,UAAU,IAAI,WAAW,CAAC,IAAI,CAAC,CAAC;YAE3D,MAAM,MAAM,GAA8B;gBACxC,QAAQ,EAAE,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,QAAQ;gBACpC,WAAW;gBACX,IAAI;aACL,CAAC;YAEF,mBAAmB,CAAC;gBAClB,EAAE;gBACF,MAAM,EAAE,SAAS;gBACjB,IAAI;gBACJ,WAAW,EAAE,IAAI;gBACjB,KAAK,EAAE,IAAI;gBACX,OAAO,EAAE,GAAG;gBACZ,SAAS,EAAE,IAAI,CAAC,GAAG,EAAE;gBACrB,MAAM;aACP,CAAC,CAAC;YAEH,WAAW,CAAC,MAAM,CAAC,CAAC;QACtB,CAAC;QAAC,OAAO,CAAC,EAAE,CAAC;YACX,mBAAmB,CAAC;gBAClB,EAAE;gBACF,MAAM,EAAE,OAAO;gBACf,IAAI;gBACJ,SAAS,EAAE,IAAI,CAAC,GAAG,EAAE;gBACrB,KAAK,EAAE,CAAC;aACT,CAAC,CAAC;YACH,UAAU,CAAC,CAAC,CAAC,CAAC;QAChB,CAAC;gBAAS,CAAC;YACT,IAAI,MAAM;gBAAE,MAAM,CAAC,mBAAmB,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;YACzD,IAAI,KAAK;gBAAE,KAAK,EAAE,CAAC;QACrB,CAAC;IACH,CAAC,CACF,CAAC;IAEF,OAAO;QACL,EAAE;QACF,IAAI;QACJ,KAAK,EAAE,GAAG,EAAE;YACV,IAAI,CAAC;gBACH,OAAO,IAAI,CAAC,KAAK,EAAE,CAAC;YACtB,CAAC;YAAC,MAAM,CAAC;gBACP,OAAO,KAAK,CAAC;YACf,CAAC;QACH,CAAC;QACD,MAAM,EAAE,GAAG,EAAE;YACX,IAAI,CAAC;gBACH,OAAO,IAAI,CAAC,MAAM,EAAE,CAAC;YACvB,CAAC;YAAC,MAAM,CAAC;gBACP,OAAO,KAAK,CAAC;YACf,CAAC;QACH,CAAC;QACD,MAAM,EAAE,UAAU;QAClB,IAAI;KACL,CAAC;AACJ,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,mBAAmB,CACvC,IAaC;IAED,qFAAqF;IACrF,mFAAmF;IACnF,4CAA4C;IAC5C,MAAM,EAAE,GAAG,IAAI,CAAC,QAAQ,EAAE,WAAW,CAAC;IACtC,IAAI,CAAC,EAAE,EAAE,CAAC;QACR,MAAM,IAAI,WAAW,CACnB,sBAAsB,EACtB,8FAA8F,CAC/F,CAAC;IACJ,CAAC;IACD,MAAM,cAAc,GAClB,IAAI,CAAC,uBAAuB,KAAK,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC,IAAI,EAAE,KAAK,oBAAoB,CAAC;IAC5F,IAAI,CAAC,cAAc,IAAI,CAAC,uBAAuB,CAAC,EAAE,CAAC,EAAE,CAAC;QACpD,MAAM,IAAI,WAAW,CACnB,sBAAsB,EACtB,iCAAiC,EAAE,0EAA0E,CAC9G,CAAC;IACJ,CAAC;IAED,MAAM,EAAE,uBAAuB,EAAE,aAAa,EAAE,GAAG,IAAI,EAAE,GAAG,IAAI,CAAC;IACjE,MAAM,EAAE,GAAG,IAAI,CAAC,EAAE,IAAI,OAAO,IAAI,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC,QAAQ,CAAC,EAAE,CAAC,EAAE,CAAC;IAC7F,MAAM,CAAC,GAAG,oBAAoB,CAAC,EAAE,GAAG,IAAI,EAAE,EAAE,EAAE,CAAC,CAAC;IAChD,OAAO,CAAC,CAAC,IAAI,CAAC;AAChB,CAAC","sourcesContent":["import type { UploadFileResumableResult } from \"../types.js\";\nimport type { UploadFileResumableArgs, UploadController } from \"../browser/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 \"./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 typeof e === \"object\" && e !== null && (e as { code?: unknown }).code === \"storage/canceled\";\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\"\n ? \"paused\"\n : snap.state === \"running\"\n ? \"uploading\"\n : \"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 (canonical-upload-content-\n * classification, 2026-07-31): a picker file with unknown browser metadata\n * must reach the server AS unknown (`application/octet-stream`) so the\n * server-side byte inspector — the only classification authority — sees it\n * unrewritten. Default false preserves the historical media-only gate for\n * 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"]}