@ttt-productions/media-processing-core 0.8.17 → 0.8.18

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 (90) hide show
  1. package/dist/audio/audio-processor.d.ts.map +1 -1
  2. package/dist/audio/audio-processor.js +1 -2
  3. package/dist/audio/audio-processor.js.map +1 -1
  4. package/dist/audio/probe.d.ts.map +1 -1
  5. package/dist/audio/probe.js +0 -2
  6. package/dist/audio/probe.js.map +1 -1
  7. package/dist/duration/media-duration.d.ts +0 -11
  8. package/dist/duration/media-duration.d.ts.map +1 -1
  9. package/dist/duration/media-duration.js +1 -28
  10. package/dist/duration/media-duration.js.map +1 -1
  11. package/dist/image/formats.d.ts +0 -11
  12. package/dist/image/formats.d.ts.map +1 -1
  13. package/dist/image/formats.js +0 -10
  14. package/dist/image/formats.js.map +1 -1
  15. package/dist/image/image-processor.d.ts.map +1 -1
  16. package/dist/image/image-processor.js +1 -20
  17. package/dist/image/image-processor.js.map +1 -1
  18. package/dist/image/resize.d.ts +0 -14
  19. package/dist/image/resize.d.ts.map +1 -1
  20. package/dist/image/resize.js +0 -15
  21. package/dist/image/resize.js.map +1 -1
  22. package/dist/inspection/inspect-media.d.ts +0 -7
  23. package/dist/inspection/inspect-media.d.ts.map +1 -1
  24. package/dist/inspection/inspect-media.js +4 -64
  25. package/dist/inspection/inspect-media.js.map +1 -1
  26. package/dist/io/fs.d.ts.map +1 -1
  27. package/dist/io/fs.js +0 -1
  28. package/dist/io/fs.js.map +1 -1
  29. package/dist/io/local-input.d.ts +0 -20
  30. package/dist/io/local-input.d.ts.map +1 -1
  31. package/dist/io/local-input.js +0 -20
  32. package/dist/io/local-input.js.map +1 -1
  33. package/dist/io/types.d.ts +0 -3
  34. package/dist/io/types.d.ts.map +1 -1
  35. package/dist/io/types.js.map +1 -1
  36. package/dist/moderation/merge.d.ts.map +1 -1
  37. package/dist/moderation/merge.js +0 -1
  38. package/dist/moderation/merge.js.map +1 -1
  39. package/dist/moderation/types.d.ts +0 -2
  40. package/dist/moderation/types.d.ts.map +1 -1
  41. package/dist/moderation/types.js.map +1 -1
  42. package/dist/run-pipeline.d.ts +0 -17
  43. package/dist/run-pipeline.d.ts.map +1 -1
  44. package/dist/run-pipeline.js +2 -27
  45. package/dist/run-pipeline.js.map +1 -1
  46. package/dist/server/download-token.d.ts +0 -6
  47. package/dist/server/download-token.d.ts.map +1 -1
  48. package/dist/server/download-token.js +0 -10
  49. package/dist/server/download-token.js.map +1 -1
  50. package/dist/server/firebase-media-io.d.ts +1 -11
  51. package/dist/server/firebase-media-io.d.ts.map +1 -1
  52. package/dist/server/firebase-media-io.js +0 -5
  53. package/dist/server/firebase-media-io.js.map +1 -1
  54. package/dist/server/gcs-errors.d.ts +0 -8
  55. package/dist/server/gcs-errors.d.ts.map +1 -1
  56. package/dist/server/gcs-errors.js +0 -8
  57. package/dist/server/gcs-errors.js.map +1 -1
  58. package/dist/server/pending-media.d.ts +1 -32
  59. package/dist/server/pending-media.d.ts.map +1 -1
  60. package/dist/server/pending-media.js +1 -24
  61. package/dist/server/pending-media.js.map +1 -1
  62. package/dist/server/r2-retry.d.ts +0 -48
  63. package/dist/server/r2-retry.d.ts.map +1 -1
  64. package/dist/server/r2-retry.js +4 -90
  65. package/dist/server/r2-retry.js.map +1 -1
  66. package/dist/server/staged-object.d.ts +0 -44
  67. package/dist/server/staged-object.d.ts.map +1 -1
  68. package/dist/server/staged-object.js +0 -44
  69. package/dist/server/staged-object.js.map +1 -1
  70. package/dist/server/storage-ops.d.ts +2 -62
  71. package/dist/server/storage-ops.d.ts.map +1 -1
  72. package/dist/server/storage-ops.js +9 -88
  73. package/dist/server/storage-ops.js.map +1 -1
  74. package/dist/types.d.ts +0 -2
  75. package/dist/types.d.ts.map +1 -1
  76. package/dist/types.js.map +1 -1
  77. package/dist/utils/safe-path.d.ts.map +1 -1
  78. package/dist/utils/safe-path.js +1 -2
  79. package/dist/utils/safe-path.js.map +1 -1
  80. package/dist/video/ffmpeg.d.ts +0 -22
  81. package/dist/video/ffmpeg.d.ts.map +1 -1
  82. package/dist/video/ffmpeg.js +6 -21
  83. package/dist/video/ffmpeg.js.map +1 -1
  84. package/dist/video/probe.d.ts.map +1 -1
  85. package/dist/video/probe.js +0 -1
  86. package/dist/video/probe.js.map +1 -1
  87. package/dist/video/video-processor.d.ts.map +1 -1
  88. package/dist/video/video-processor.js +2 -15
  89. package/dist/video/video-processor.js.map +1 -1
  90. package/package.json +2 -2
@@ -1 +1 @@
1
- {"version":3,"file":"merge.js","sourceRoot":"","sources":["../../src/moderation/merge.ts"],"names":[],"mappings":"AAEA,MAAM,UAAU,eAAe,CAC7B,KAAoC,EACpC,MAAqC;IAErC,MAAM,CAAC,GAAG,KAAK,IAAI,SAAS,CAAC;IAC7B,MAAM,CAAC,GAAG,MAAM,IAAI,SAAS,CAAC;IAE9B,IAAI,CAAC,CAAC,IAAI,CAAC,CAAC;QAAE,OAAO,SAAS,CAAC;IAC/B,IAAI,CAAC,CAAC;QAAE,OAAO,CAAC,IAAI,SAAS,CAAC;IAC9B,IAAI,CAAC,CAAC;QAAE,OAAO,CAAC,IAAI,SAAS,CAAC;IAE9B,yDAAyD;IACzD,MAAM,IAAI,GAAG,CAAC,CAAS,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,UAAU,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IAEjG,MAAM,MAAM,GAAG,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC;IAEtE,MAAM,OAAO,GAAG,KAAK,CAAC,IAAI,CAAC,IAAI,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,OAAO,IAAI,EAAE,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC,OAAO,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC;IAClF,MAAM,QAAQ,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,QAAQ,IAAI,EAAE,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC,QAAQ,IAAI,EAAE,CAAC,CAAC,CAAC;IAEhE,OAAO;QACL,MAAM;QACN,QAAQ,EAAE,CAAC,CAAC,QAAQ,IAAI,CAAC,CAAC,QAAQ;QAClC,OAAO,EAAE,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,SAAS;QAC7C,QAAQ,EAAE,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,SAAS;QAChD,UAAU,EAAE,CAAC,CAAC,UAAU,IAAI,CAAC,CAAC,UAAU;KACzC,CAAC;AACJ,CAAC","sourcesContent":["import type { MediaModerationResult } from '@ttt-productions/media-schemas';\n\nexport function mergeModeration(\n input?: MediaModerationResult | null,\n output?: MediaModerationResult | null,\n): MediaModerationResult | undefined {\n const a = input ?? undefined;\n const b = output ?? undefined;\n\n if (!a && !b) return undefined;\n if (!a) return b ?? undefined;\n if (!b) return a ?? undefined;\n\n // worst status wins: rejected > flagged > error > passed\n const rank = (s: string) => (s === 'rejected' ? 4 : s === 'flagged' ? 3 : s === 'error' ? 2 : 1);\n\n const status = rank(a.status) >= rank(b.status) ? a.status : b.status;\n\n const reasons = Array.from(new Set([...(a.reasons ?? []), ...(b.reasons ?? [])]));\n const findings = [...(a.findings ?? []), ...(b.findings ?? [])];\n\n return {\n status,\n provider: b.provider ?? a.provider,\n reasons: reasons.length ? reasons : undefined,\n findings: findings.length ? findings : undefined,\n reviewedAt: b.reviewedAt ?? a.reviewedAt,\n };\n}\n"]}
1
+ {"version":3,"file":"merge.js","sourceRoot":"","sources":["../../src/moderation/merge.ts"],"names":[],"mappings":"AAEA,MAAM,UAAU,eAAe,CAC7B,KAAoC,EACpC,MAAqC;IAErC,MAAM,CAAC,GAAG,KAAK,IAAI,SAAS,CAAC;IAC7B,MAAM,CAAC,GAAG,MAAM,IAAI,SAAS,CAAC;IAE9B,IAAI,CAAC,CAAC,IAAI,CAAC,CAAC;QAAE,OAAO,SAAS,CAAC;IAC/B,IAAI,CAAC,CAAC;QAAE,OAAO,CAAC,IAAI,SAAS,CAAC;IAC9B,IAAI,CAAC,CAAC;QAAE,OAAO,CAAC,IAAI,SAAS,CAAC;IAE9B,MAAM,IAAI,GAAG,CAAC,CAAS,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,UAAU,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IAEjG,MAAM,MAAM,GAAG,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC;IAEtE,MAAM,OAAO,GAAG,KAAK,CAAC,IAAI,CAAC,IAAI,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,OAAO,IAAI,EAAE,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC,OAAO,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC;IAClF,MAAM,QAAQ,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,QAAQ,IAAI,EAAE,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC,QAAQ,IAAI,EAAE,CAAC,CAAC,CAAC;IAEhE,OAAO;QACL,MAAM;QACN,QAAQ,EAAE,CAAC,CAAC,QAAQ,IAAI,CAAC,CAAC,QAAQ;QAClC,OAAO,EAAE,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,SAAS;QAC7C,QAAQ,EAAE,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,SAAS;QAChD,UAAU,EAAE,CAAC,CAAC,UAAU,IAAI,CAAC,CAAC,UAAU;KACzC,CAAC;AACJ,CAAC","sourcesContent":["import type { MediaModerationResult } from '@ttt-productions/media-schemas';\n\nexport function mergeModeration(\n input?: MediaModerationResult | null,\n output?: MediaModerationResult | null,\n): MediaModerationResult | undefined {\n const a = input ?? undefined;\n const b = output ?? undefined;\n\n if (!a && !b) return undefined;\n if (!a) return b ?? undefined;\n if (!b) return a ?? undefined;\n\n const rank = (s: string) => (s === 'rejected' ? 4 : s === 'flagged' ? 3 : s === 'error' ? 2 : 1);\n\n const status = rank(a.status) >= rank(b.status) ? a.status : b.status;\n\n const reasons = Array.from(new Set([...(a.reasons ?? []), ...(b.reasons ?? [])]));\n const findings = [...(a.findings ?? []), ...(b.findings ?? [])];\n\n return {\n status,\n provider: b.provider ?? a.provider,\n reasons: reasons.length ? reasons : undefined,\n findings: findings.length ? findings : undefined,\n reviewedAt: b.reviewedAt ?? a.reviewedAt,\n };\n}\n"]}
@@ -16,9 +16,7 @@ export interface ModerationOutput {
16
16
  }
17
17
  export interface ModerationAdapter {
18
18
  provider: string;
19
- /** Run on the ORIGINAL uploaded file before processing. */
20
19
  moderateInput?: (input: ModerationInput) => Promise<MediaModerationResult | null | undefined>;
21
- /** Run on PROCESSED outputs after processing. */
22
20
  moderateOutput?: (output: ModerationOutput) => Promise<MediaModerationResult | null | undefined>;
23
21
  }
24
22
  //# sourceMappingURL=types.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/moderation/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,qBAAqB,EAAE,mBAAmB,EAAE,MAAM,gCAAgC,CAAC;AAEjG,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,mBAAmB,CAAC;IAC1B,IAAI,EAAE,OAAO,GAAG,OAAO,GAAG,OAAO,GAAG,SAAS,CAAC;IAC9C,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,mBAAmB,CAAC;IAC1B,IAAI,EAAE,OAAO,GAAG,OAAO,GAAG,OAAO,GAAG,SAAS,CAAC;IAC9C,OAAO,EAAE,KAAK,CAAC;QACb,GAAG,EAAE,MAAM,CAAC;QACZ,SAAS,EAAE,MAAM,CAAC;QAClB,IAAI,CAAC,EAAE,MAAM,CAAC;KACf,CAAC,CAAC;CACJ;AAED,MAAM,WAAW,iBAAiB;IAChC,QAAQ,EAAE,MAAM,CAAC;IAEjB,2DAA2D;IAC3D,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,eAAe,KAAK,OAAO,CAAC,qBAAqB,GAAG,IAAI,GAAG,SAAS,CAAC,CAAC;IAE9F,iDAAiD;IACjD,cAAc,CAAC,EAAE,CAAC,MAAM,EAAE,gBAAgB,KAAK,OAAO,CAAC,qBAAqB,GAAG,IAAI,GAAG,SAAS,CAAC,CAAC;CAClG"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/moderation/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,qBAAqB,EAAE,mBAAmB,EAAE,MAAM,gCAAgC,CAAC;AAEjG,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,mBAAmB,CAAC;IAC1B,IAAI,EAAE,OAAO,GAAG,OAAO,GAAG,OAAO,GAAG,SAAS,CAAC;IAC9C,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,mBAAmB,CAAC;IAC1B,IAAI,EAAE,OAAO,GAAG,OAAO,GAAG,OAAO,GAAG,SAAS,CAAC;IAC9C,OAAO,EAAE,KAAK,CAAC;QACb,GAAG,EAAE,MAAM,CAAC;QACZ,SAAS,EAAE,MAAM,CAAC;QAClB,IAAI,CAAC,EAAE,MAAM,CAAC;KACf,CAAC,CAAC;CACJ;AAED,MAAM,WAAW,iBAAiB;IAChC,QAAQ,EAAE,MAAM,CAAC;IAEjB,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,eAAe,KAAK,OAAO,CAAC,qBAAqB,GAAG,IAAI,GAAG,SAAS,CAAC,CAAC;IAE9F,cAAc,CAAC,EAAE,CAAC,MAAM,EAAE,gBAAgB,KAAK,OAAO,CAAC,qBAAqB,GAAG,IAAI,GAAG,SAAS,CAAC,CAAC;CAClG"}
@@ -1 +1 @@
1
- {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/moderation/types.ts"],"names":[],"mappings":"","sourcesContent":["import type { MediaModerationResult, MediaProcessingSpec } from '@ttt-productions/media-schemas';\n\nexport interface ModerationInput {\n spec: MediaProcessingSpec;\n kind: 'image' | 'video' | 'audio' | 'generic';\n localPath: string;\n mime?: string;\n}\n\nexport interface ModerationOutput {\n spec: MediaProcessingSpec;\n kind: 'image' | 'video' | 'audio' | 'generic';\n outputs: Array<{\n key: string;\n localPath: string;\n mime?: string;\n }>;\n}\n\nexport interface ModerationAdapter {\n provider: string;\n\n /** Run on the ORIGINAL uploaded file before processing. */\n moderateInput?: (input: ModerationInput) => Promise<MediaModerationResult | null | undefined>;\n\n /** Run on PROCESSED outputs after processing. */\n moderateOutput?: (output: ModerationOutput) => Promise<MediaModerationResult | null | undefined>;\n}\n"]}
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/moderation/types.ts"],"names":[],"mappings":"","sourcesContent":["import type { MediaModerationResult, MediaProcessingSpec } from '@ttt-productions/media-schemas';\n\nexport interface ModerationInput {\n spec: MediaProcessingSpec;\n kind: 'image' | 'video' | 'audio' | 'generic';\n localPath: string;\n mime?: string;\n}\n\nexport interface ModerationOutput {\n spec: MediaProcessingSpec;\n kind: 'image' | 'video' | 'audio' | 'generic';\n outputs: Array<{\n key: string;\n localPath: string;\n mime?: string;\n }>;\n}\n\nexport interface ModerationAdapter {\n provider: string;\n\n moderateInput?: (input: ModerationInput) => Promise<MediaModerationResult | null | undefined>;\n\n moderateOutput?: (output: ModerationOutput) => Promise<MediaModerationResult | null | undefined>;\n}\n"]}
@@ -9,22 +9,6 @@ export interface RunPipelineArgs {
9
9
  moderation?: ModerationAdapter;
10
10
  signal?: AbortSignal;
11
11
  onProgress?: (p: import('./types.js').MediaPipelineProgress) => void;
12
- /**
13
- * CANONICAL INSPECTION SEAM (canonical-upload-content-classification).
14
- * When present, the pipeline runs `inspectMedia` on the just-downloaded temp
15
- * input — BEFORE moderation and processing — and calls this policy hook with
16
- * the bounded result. The hook either:
17
- * - returns `{ spec }` — the caller's policy adapter selected the
18
- * processing spec FROM the inspection (the byte-derived kind, never the
19
- * client MIME); the pipeline continues with that spec; or
20
- * - returns `{ reject }` — a typed terminal (`kind_mismatch`,
21
- * `unsupported_format`, `unsupported_codec`, …); the pipeline stops
22
- * before any transcode/moderation work.
23
- * The inspection result rides the returned `MediaProcessingResult.inspection`
24
- * either way, so the finalizer hands the SAME object to the safety gate —
25
- * one authority, no re-detection. Callers that omit the hook get the
26
- * pre-existing behavior unchanged.
27
- */
28
12
  resolveAfterInspection?: (inspection: MediaInspectionResult) => Promise<{
29
13
  spec: MediaProcessingSpec;
30
14
  } | {
@@ -34,7 +18,6 @@ export interface RunPipelineArgs {
34
18
  } | {
35
19
  reject: MediaProcessingError;
36
20
  });
37
- /** Inspection tool overrides (tests). */
38
21
  inspectionDeps?: InspectMediaDeps;
39
22
  }
40
23
  export declare function runMediaPipeline(args: RunPipelineArgs): Promise<MediaProcessingResult>;
@@ -1 +1 @@
1
- {"version":3,"file":"run-pipeline.d.ts","sourceRoot":"","sources":["../src/run-pipeline.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,qBAAqB,EACrB,mBAAmB,EAEnB,qBAAqB,EACrB,oBAAoB,EACrB,MAAM,gCAAgC,CAAC;AAIxC,OAAO,EAAgB,KAAK,gBAAgB,EAAE,MAAM,+BAA+B,CAAC;AACpF,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,eAAe,CAAC;AAC7C,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,uBAAuB,CAAC;AAK/D,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,mBAAmB,CAAC;IAC1B,EAAE,EAAE,OAAO,CAAC;IACZ,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,UAAU,CAAC,EAAE,iBAAiB,CAAC;IAC/B,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB,UAAU,CAAC,EAAE,CAAC,CAAC,EAAE,OAAO,YAAY,EAAE,qBAAqB,KAAK,IAAI,CAAC;IACrE;;;;;;;;;;;;;;;OAeG;IACH,sBAAsB,CAAC,EAAE,CACvB,UAAU,EAAE,qBAAqB,KAE/B,OAAO,CAAC;QAAE,IAAI,EAAE,mBAAmB,CAAA;KAAE,GAAG;QAAE,MAAM,EAAE,oBAAoB,CAAA;KAAE,CAAC,GACzE,CAAC;QAAE,IAAI,EAAE,mBAAmB,CAAA;KAAE,GAAG;QAAE,MAAM,EAAE,oBAAoB,CAAA;KAAE,CAAC,CAAC;IACvE,yCAAyC;IACzC,cAAc,CAAC,EAAE,gBAAgB,CAAC;CACnC;AAmBD,wBAAsB,gBAAgB,CAAC,IAAI,EAAE,eAAe,GAAG,OAAO,CAAC,qBAAqB,CAAC,CA4Q5F"}
1
+ {"version":3,"file":"run-pipeline.d.ts","sourceRoot":"","sources":["../src/run-pipeline.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,qBAAqB,EACrB,mBAAmB,EAEnB,qBAAqB,EACrB,oBAAoB,EACrB,MAAM,gCAAgC,CAAC;AAIxC,OAAO,EAAgB,KAAK,gBAAgB,EAAE,MAAM,+BAA+B,CAAC;AACpF,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,eAAe,CAAC;AAC7C,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,uBAAuB,CAAC;AAK/D,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,mBAAmB,CAAC;IAC1B,EAAE,EAAE,OAAO,CAAC;IACZ,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,UAAU,CAAC,EAAE,iBAAiB,CAAC;IAC/B,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB,UAAU,CAAC,EAAE,CAAC,CAAC,EAAE,OAAO,YAAY,EAAE,qBAAqB,KAAK,IAAI,CAAC;IACrE,sBAAsB,CAAC,EAAE,CACvB,UAAU,EAAE,qBAAqB,KAE/B,OAAO,CAAC;QAAE,IAAI,EAAE,mBAAmB,CAAA;KAAE,GAAG;QAAE,MAAM,EAAE,oBAAoB,CAAA;KAAE,CAAC,GACzE,CAAC;QAAE,IAAI,EAAE,mBAAmB,CAAA;KAAE,GAAG;QAAE,MAAM,EAAE,oBAAoB,CAAA;KAAE,CAAC,CAAC;IACvE,cAAc,CAAC,EAAE,gBAAgB,CAAC;CACnC;AAmBD,wBAAsB,gBAAgB,CAAC,IAAI,EAAE,eAAe,GAAG,OAAO,CAAC,qBAAqB,CAAC,CAmP5F"}
@@ -22,8 +22,6 @@ function attachProvider(m, provider) {
22
22
  export async function runMediaPipeline(args) {
23
23
  let { spec } = args;
24
24
  const { io, outputBaseName = 'media', moderation, signal, onProgress, resolveAfterInspection, } = args;
25
- // Carried onto every return path once produced — the finalizer must hand the
26
- // SAME inspection object to the safety gate (one classification authority).
27
25
  let inspection;
28
26
  const withInspection = (r) => inspection ? { ...r, inspection } : r;
29
27
  if (signal?.aborted) {
@@ -33,7 +31,6 @@ export async function runMediaPipeline(args) {
33
31
  error: { code: 'processing_canceled', message: 'Processing canceled.' },
34
32
  };
35
33
  }
36
- // Runtime contract validation (fast-fail with actionable errors).
37
34
  try {
38
35
  spec = parseMediaProcessingSpec(spec);
39
36
  }
@@ -62,9 +59,6 @@ export async function runMediaPipeline(args) {
62
59
  onProgress?.({ phase: 'read_input', percent: 0 });
63
60
  await io.input.readToFile(inputPath);
64
61
  onProgress?.({ phase: 'read_input', percent: 1 });
65
- // CANONICAL INSPECTION (before moderation and processing): classify the
66
- // just-downloaded bytes structurally and let the caller's policy adapter
67
- // pick the spec from the result. See RunPipelineArgs.resolveAfterInspection.
68
62
  if (resolveAfterInspection) {
69
63
  inspection = await inspectMedia({
70
64
  localPath: inputPath,
@@ -94,7 +88,6 @@ export async function runMediaPipeline(args) {
94
88
  });
95
89
  }
96
90
  }
97
- // PRE moderation (original)
98
91
  let mIn;
99
92
  if (moderation?.moderateInput) {
100
93
  onProgress?.({ phase: 'moderation_input', percent: 0 });
@@ -105,14 +98,6 @@ export async function runMediaPipeline(args) {
105
98
  mime: io.input.mime,
106
99
  }), moderation.provider);
107
100
  onProgress?.({ phase: 'moderation_input', percent: 1 });
108
- // Fail closed on both "rejected" (policy violation) and "error" (the
109
- // moderation check itself did not complete — quota/outage/API failure).
110
- // A non-passing check must NEVER fall through to transcode + persist:
111
- // published bytes must always have been scanned. An "error" is a system
112
- // failure, not a content rejection, so it returns the distinct,
113
- // retryable `processing_failed` code AND carries `moderation` (status
114
- // "error") on the result — callers route that to their retryable
115
- // moderation lane and land the row failed rather than publishing it.
116
101
  if (mIn?.status === 'rejected' || mIn?.status === 'error') {
117
102
  return withInspection({
118
103
  ok: false,
@@ -137,10 +122,9 @@ export async function runMediaPipeline(args) {
137
122
  outputBasePath,
138
123
  inputMime: io.input.mime,
139
124
  }, { signal, onProgress });
140
- // Output size enforcement (processed outputs)
141
125
  if (result.ok && result.outputs?.length) {
142
- const perFileLimit = spec.maxOutputBytes ?? spec.maxBytes ?? 200 * 1024 * 1024; // 200MB default
143
- const totalLimit = spec.maxTotalOutputBytes ?? spec.maxOutputBytes ?? spec.maxBytes ?? 400 * 1024 * 1024; // 400MB default
126
+ const perFileLimit = spec.maxOutputBytes ?? spec.maxBytes ?? 200 * 1024 * 1024;
127
+ const totalLimit = spec.maxTotalOutputBytes ?? spec.maxOutputBytes ?? spec.maxBytes ?? 400 * 1024 * 1024;
144
128
  let total = 0;
145
129
  for (const o of result.outputs) {
146
130
  if (!o.path)
@@ -180,10 +164,6 @@ export async function runMediaPipeline(args) {
180
164
  onProgress?.({ phase: 'done', percent: result.ok ? 1 : 0 });
181
165
  return withInspection(result);
182
166
  }
183
- // POST moderation (processed outputs) — runs on the LOCAL output paths
184
- // BEFORE any final persistence, so rejected output never reaches the
185
- // final store (R2 / emulator). Ordering is load-bearing — see
186
- // the consuming app's media-serving design docs.
187
167
  let mOut;
188
168
  if (moderation?.moderateOutput) {
189
169
  onProgress?.({ phase: 'moderation_output', percent: 0 });
@@ -195,10 +175,6 @@ export async function runMediaPipeline(args) {
195
175
  .map((o) => ({ key: o.key, localPath: o.path, mime: o.mime })),
196
176
  }), moderation.provider);
197
177
  onProgress?.({ phase: 'moderation_output', percent: 1 });
198
- // Same fail-closed posture as the input check: block both "rejected"
199
- // and "error" before any persistence, so an incomplete check can never
200
- // publish unscanned output. Ordering is load-bearing — see the
201
- // consuming app's media-serving design docs.
202
178
  if (mOut?.status === 'rejected' || mOut?.status === 'error') {
203
179
  return withInspection({
204
180
  ok: false,
@@ -222,7 +198,6 @@ export async function runMediaPipeline(args) {
222
198
  });
223
199
  }
224
200
  }
225
- // Persist outputs — only after input AND output moderation both passed.
226
201
  onProgress?.({ phase: 'persist_outputs', percent: 0 });
227
202
  const totalOut = result.outputs.length;
228
203
  let outIdx = 0;
@@ -1 +1 @@
1
- {"version":3,"file":"run-pipeline.js","sourceRoot":"","sources":["../src/run-pipeline.ts"],"names":[],"mappings":"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","sourcesContent":["import type {\n MediaProcessingResult,\n MediaProcessingSpec,\n MediaModerationResult,\n MediaInspectionResult,\n MediaProcessingError,\n} from '@ttt-productions/media-schemas';\nimport { parseMediaProcessingSpec } from '@ttt-productions/media-schemas';\nimport { createTempWorkspace } from './workspace/temp.js';\nimport { processMedia } from './process-media.js';\nimport { inspectMedia, type InspectMediaDeps } from './inspection/inspect-media.js';\nimport type { MediaIO } from './io/types.js';\nimport type { ModerationAdapter } from './moderation/types.js';\nimport { mergeModeration } from './moderation/merge.js';\nimport path from 'node:path';\nimport { stat } from 'node:fs/promises';\n\nexport interface RunPipelineArgs {\n spec: MediaProcessingSpec;\n io: MediaIO;\n outputBaseName?: string;\n moderation?: ModerationAdapter;\n signal?: AbortSignal;\n onProgress?: (p: import('./types.js').MediaPipelineProgress) => void;\n /**\n * CANONICAL INSPECTION SEAM (canonical-upload-content-classification).\n * When present, the pipeline runs `inspectMedia` on the just-downloaded temp\n * input — BEFORE moderation and processing — and calls this policy hook with\n * the bounded result. The hook either:\n * - returns `{ spec }` — the caller's policy adapter selected the\n * processing spec FROM the inspection (the byte-derived kind, never the\n * client MIME); the pipeline continues with that spec; or\n * - returns `{ reject }` — a typed terminal (`kind_mismatch`,\n * `unsupported_format`, `unsupported_codec`, …); the pipeline stops\n * before any transcode/moderation work.\n * The inspection result rides the returned `MediaProcessingResult.inspection`\n * either way, so the finalizer hands the SAME object to the safety gate —\n * one authority, no re-detection. Callers that omit the hook get the\n * pre-existing behavior unchanged.\n */\n resolveAfterInspection?: (\n inspection: MediaInspectionResult,\n ) =>\n | Promise<{ spec: MediaProcessingSpec } | { reject: MediaProcessingError }>\n | ({ spec: MediaProcessingSpec } | { reject: MediaProcessingError });\n /** Inspection tool overrides (tests). */\n inspectionDeps?: InspectMediaDeps;\n}\n\nfunction mediaTypeFromSpecKind(\n kind: MediaProcessingSpec['kind'],\n): 'image' | 'video' | 'audio' | 'other' {\n if (kind === 'image') return 'image';\n if (kind === 'video') return 'video';\n if (kind === 'audio') return 'audio';\n return 'other';\n}\n\nfunction attachProvider(\n m: MediaModerationResult | null | undefined,\n provider?: string,\n): MediaModerationResult | null | undefined {\n if (!m) return m;\n return { ...m, provider: m.provider ?? provider, reviewedAt: m.reviewedAt ?? Date.now() };\n}\n\nexport async function runMediaPipeline(args: RunPipelineArgs): Promise<MediaProcessingResult> {\n let { spec } = args;\n const {\n io,\n outputBaseName = 'media',\n moderation,\n signal,\n onProgress,\n resolveAfterInspection,\n } = args;\n // Carried onto every return path once produced — the finalizer must hand the\n // SAME inspection object to the safety gate (one classification authority).\n let inspection: MediaInspectionResult | undefined;\n const withInspection = (r: MediaProcessingResult): MediaProcessingResult =>\n inspection ? { ...r, inspection } : r;\n\n if (signal?.aborted) {\n return {\n ok: false,\n mediaType: mediaTypeFromSpecKind(spec.kind),\n error: { code: 'processing_canceled', message: 'Processing canceled.' },\n };\n }\n\n // Runtime contract validation (fast-fail with actionable errors).\n try {\n spec = parseMediaProcessingSpec(spec);\n } catch (e: any) {\n return {\n ok: false,\n mediaType: mediaTypeFromSpecKind(spec.kind),\n error: {\n code: 'invalid_spec',\n message: 'Invalid MediaProcessingSpec.',\n details: { issues: e?.issues ?? e?.message ?? String(e) },\n },\n };\n }\n\n const ws = await createTempWorkspace();\n\n try {\n const inputPath = path.join(ws.dir, 'input');\n const outputBasePath = path.join(ws.dir, outputBaseName);\n\n if (signal?.aborted) {\n return {\n ok: false,\n mediaType: mediaTypeFromSpecKind(spec.kind),\n error: { code: 'processing_canceled', message: 'Processing canceled.' },\n };\n }\n onProgress?.({ phase: 'read_input', percent: 0 });\n await io.input.readToFile(inputPath);\n onProgress?.({ phase: 'read_input', percent: 1 });\n\n // CANONICAL INSPECTION (before moderation and processing): classify the\n // just-downloaded bytes structurally and let the caller's policy adapter\n // pick the spec from the result. See RunPipelineArgs.resolveAfterInspection.\n if (resolveAfterInspection) {\n inspection = await inspectMedia({\n localPath: inputPath,\n ...(signal ? { signal } : {}),\n ...(args.inspectionDeps ? { deps: args.inspectionDeps } : {}),\n });\n const resolution = await resolveAfterInspection(inspection);\n if ('reject' in resolution) {\n return withInspection({\n ok: false,\n mediaType: mediaTypeFromSpecKind(spec.kind),\n error: resolution.reject,\n });\n }\n try {\n spec = parseMediaProcessingSpec(resolution.spec);\n } catch (e: any) {\n return withInspection({\n ok: false,\n mediaType: mediaTypeFromSpecKind(spec.kind),\n error: {\n code: 'invalid_spec',\n message: 'Invalid MediaProcessingSpec from resolveAfterInspection.',\n details: { issues: e?.issues ?? e?.message ?? String(e) },\n },\n });\n }\n }\n\n // PRE moderation (original)\n let mIn: MediaModerationResult | null | undefined;\n if (moderation?.moderateInput) {\n onProgress?.({ phase: 'moderation_input', percent: 0 });\n mIn = attachProvider(\n await moderation.moderateInput({\n spec,\n kind: spec.kind,\n localPath: inputPath,\n mime: io.input.mime,\n }),\n moderation.provider,\n );\n onProgress?.({ phase: 'moderation_input', percent: 1 });\n\n // Fail closed on both \"rejected\" (policy violation) and \"error\" (the\n // moderation check itself did not complete — quota/outage/API failure).\n // A non-passing check must NEVER fall through to transcode + persist:\n // published bytes must always have been scanned. An \"error\" is a system\n // failure, not a content rejection, so it returns the distinct,\n // retryable `processing_failed` code AND carries `moderation` (status\n // \"error\") on the result — callers route that to their retryable\n // moderation lane and land the row failed rather than publishing it.\n if (mIn?.status === 'rejected' || mIn?.status === 'error') {\n return withInspection({\n ok: false,\n mediaType: mediaTypeFromSpecKind(spec.kind),\n moderation: mIn,\n error:\n mIn.status === 'error'\n ? {\n code: 'processing_failed',\n message: 'Moderation check did not complete.',\n details: { provider: mIn.provider, reason: 'moderation_error' },\n }\n : {\n code: 'rejected',\n message: 'Rejected by moderation.',\n details: { provider: mIn.provider, reasons: mIn.reasons, findings: mIn.findings },\n },\n });\n }\n }\n\n const result = await processMedia(\n spec,\n {\n inputPath,\n outputBasePath,\n inputMime: io.input.mime,\n },\n { signal, onProgress },\n );\n\n // Output size enforcement (processed outputs)\n if (result.ok && result.outputs?.length) {\n const perFileLimit = spec.maxOutputBytes ?? spec.maxBytes ?? 200 * 1024 * 1024; // 200MB default\n const totalLimit =\n spec.maxTotalOutputBytes ?? spec.maxOutputBytes ?? spec.maxBytes ?? 400 * 1024 * 1024; // 400MB default\n\n let total = 0;\n for (const o of result.outputs) {\n if (!o.path) continue;\n const s = await stat(o.path);\n const size = Number(s.size ?? 0);\n total += size;\n if (!o.sizeBytes) o.sizeBytes = size;\n if (size > perFileLimit) {\n return withInspection({\n ok: false,\n mediaType: mediaTypeFromSpecKind(spec.kind),\n error: {\n code: 'too_large',\n message: 'Processed output exceeds allowed size.',\n details: { key: o.key, sizeBytes: size, limitBytes: perFileLimit },\n },\n });\n }\n if (total > totalLimit) {\n return withInspection({\n ok: false,\n mediaType: mediaTypeFromSpecKind(spec.kind),\n error: {\n code: 'too_large',\n message: 'Total processed outputs exceed allowed size.',\n details: { totalBytes: total, limitBytes: totalLimit },\n },\n });\n }\n }\n }\n\n if (!result.ok || !result.outputs?.length) {\n if (mIn) result.moderation = mIn;\n onProgress?.({ phase: 'done', percent: result.ok ? 1 : 0 });\n return withInspection(result);\n }\n\n // POST moderation (processed outputs) — runs on the LOCAL output paths\n // BEFORE any final persistence, so rejected output never reaches the\n // final store (R2 / emulator). Ordering is load-bearing — see\n // the consuming app's media-serving design docs.\n let mOut: MediaModerationResult | null | undefined;\n if (moderation?.moderateOutput) {\n onProgress?.({ phase: 'moderation_output', percent: 0 });\n mOut = attachProvider(\n await moderation.moderateOutput({\n spec,\n kind: spec.kind,\n outputs: (result.outputs ?? [])\n .filter((o) => !!o.path)\n .map((o) => ({ key: o.key, localPath: o.path!, mime: o.mime })),\n }),\n moderation.provider,\n );\n onProgress?.({ phase: 'moderation_output', percent: 1 });\n\n // Same fail-closed posture as the input check: block both \"rejected\"\n // and \"error\" before any persistence, so an incomplete check can never\n // publish unscanned output. Ordering is load-bearing — see the\n // consuming app's media-serving design docs.\n if (mOut?.status === 'rejected' || mOut?.status === 'error') {\n return withInspection({\n ok: false,\n mediaType: result.mediaType,\n moderation: mOut,\n error:\n mOut.status === 'error'\n ? {\n code: 'processing_failed',\n message: 'Moderation check did not complete.',\n details: { provider: mOut.provider, reason: 'moderation_error' },\n }\n : {\n code: 'rejected',\n message: 'Rejected by moderation.',\n details: {\n provider: mOut.provider,\n reasons: mOut.reasons,\n findings: mOut.findings,\n },\n },\n });\n }\n }\n\n // Persist outputs — only after input AND output moderation both passed.\n onProgress?.({ phase: 'persist_outputs', percent: 0 });\n const totalOut = result.outputs.length;\n let outIdx = 0;\n for (const out of result.outputs) {\n if (signal?.aborted) {\n return withInspection({\n ok: false,\n mediaType: result.mediaType,\n error: { code: 'processing_canceled', message: 'Processing canceled.' },\n });\n }\n onProgress?.({\n phase: 'persist_outputs',\n percent: totalOut ? outIdx / totalOut : 0,\n detail: { key: out.key },\n });\n if (!out.path) continue;\n const persisted = await io.output.writeFromFile(out.path, out.key);\n if (persisted.url) out.url = persisted.url;\n if (persisted.path) out.path = persisted.path;\n outIdx += 1;\n }\n onProgress?.({ phase: 'persist_outputs', percent: 1 });\n\n const merged = mergeModeration(mIn ?? undefined, mOut ?? undefined);\n if (merged) result.moderation = merged;\n\n onProgress?.({ phase: 'done', percent: 1 });\n\n return withInspection(result);\n } finally {\n await ws.cleanup();\n }\n}\n"]}
1
+ {"version":3,"file":"run-pipeline.js","sourceRoot":"","sources":["../src/run-pipeline.ts"],"names":[],"mappings":"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","sourcesContent":["import type {\n MediaProcessingResult,\n MediaProcessingSpec,\n MediaModerationResult,\n MediaInspectionResult,\n MediaProcessingError,\n} from '@ttt-productions/media-schemas';\nimport { parseMediaProcessingSpec } from '@ttt-productions/media-schemas';\nimport { createTempWorkspace } from './workspace/temp.js';\nimport { processMedia } from './process-media.js';\nimport { inspectMedia, type InspectMediaDeps } from './inspection/inspect-media.js';\nimport type { MediaIO } from './io/types.js';\nimport type { ModerationAdapter } from './moderation/types.js';\nimport { mergeModeration } from './moderation/merge.js';\nimport path from 'node:path';\nimport { stat } from 'node:fs/promises';\n\nexport interface RunPipelineArgs {\n spec: MediaProcessingSpec;\n io: MediaIO;\n outputBaseName?: string;\n moderation?: ModerationAdapter;\n signal?: AbortSignal;\n onProgress?: (p: import('./types.js').MediaPipelineProgress) => void;\n resolveAfterInspection?: (\n inspection: MediaInspectionResult,\n ) =>\n | Promise<{ spec: MediaProcessingSpec } | { reject: MediaProcessingError }>\n | ({ spec: MediaProcessingSpec } | { reject: MediaProcessingError });\n inspectionDeps?: InspectMediaDeps;\n}\n\nfunction mediaTypeFromSpecKind(\n kind: MediaProcessingSpec['kind'],\n): 'image' | 'video' | 'audio' | 'other' {\n if (kind === 'image') return 'image';\n if (kind === 'video') return 'video';\n if (kind === 'audio') return 'audio';\n return 'other';\n}\n\nfunction attachProvider(\n m: MediaModerationResult | null | undefined,\n provider?: string,\n): MediaModerationResult | null | undefined {\n if (!m) return m;\n return { ...m, provider: m.provider ?? provider, reviewedAt: m.reviewedAt ?? Date.now() };\n}\n\nexport async function runMediaPipeline(args: RunPipelineArgs): Promise<MediaProcessingResult> {\n let { spec } = args;\n const {\n io,\n outputBaseName = 'media',\n moderation,\n signal,\n onProgress,\n resolveAfterInspection,\n } = args;\n let inspection: MediaInspectionResult | undefined;\n const withInspection = (r: MediaProcessingResult): MediaProcessingResult =>\n inspection ? { ...r, inspection } : r;\n\n if (signal?.aborted) {\n return {\n ok: false,\n mediaType: mediaTypeFromSpecKind(spec.kind),\n error: { code: 'processing_canceled', message: 'Processing canceled.' },\n };\n }\n\n try {\n spec = parseMediaProcessingSpec(spec);\n } catch (e: any) {\n return {\n ok: false,\n mediaType: mediaTypeFromSpecKind(spec.kind),\n error: {\n code: 'invalid_spec',\n message: 'Invalid MediaProcessingSpec.',\n details: { issues: e?.issues ?? e?.message ?? String(e) },\n },\n };\n }\n\n const ws = await createTempWorkspace();\n\n try {\n const inputPath = path.join(ws.dir, 'input');\n const outputBasePath = path.join(ws.dir, outputBaseName);\n\n if (signal?.aborted) {\n return {\n ok: false,\n mediaType: mediaTypeFromSpecKind(spec.kind),\n error: { code: 'processing_canceled', message: 'Processing canceled.' },\n };\n }\n onProgress?.({ phase: 'read_input', percent: 0 });\n await io.input.readToFile(inputPath);\n onProgress?.({ phase: 'read_input', percent: 1 });\n\n if (resolveAfterInspection) {\n inspection = await inspectMedia({\n localPath: inputPath,\n ...(signal ? { signal } : {}),\n ...(args.inspectionDeps ? { deps: args.inspectionDeps } : {}),\n });\n const resolution = await resolveAfterInspection(inspection);\n if ('reject' in resolution) {\n return withInspection({\n ok: false,\n mediaType: mediaTypeFromSpecKind(spec.kind),\n error: resolution.reject,\n });\n }\n try {\n spec = parseMediaProcessingSpec(resolution.spec);\n } catch (e: any) {\n return withInspection({\n ok: false,\n mediaType: mediaTypeFromSpecKind(spec.kind),\n error: {\n code: 'invalid_spec',\n message: 'Invalid MediaProcessingSpec from resolveAfterInspection.',\n details: { issues: e?.issues ?? e?.message ?? String(e) },\n },\n });\n }\n }\n\n let mIn: MediaModerationResult | null | undefined;\n if (moderation?.moderateInput) {\n onProgress?.({ phase: 'moderation_input', percent: 0 });\n mIn = attachProvider(\n await moderation.moderateInput({\n spec,\n kind: spec.kind,\n localPath: inputPath,\n mime: io.input.mime,\n }),\n moderation.provider,\n );\n onProgress?.({ phase: 'moderation_input', percent: 1 });\n\n if (mIn?.status === 'rejected' || mIn?.status === 'error') {\n return withInspection({\n ok: false,\n mediaType: mediaTypeFromSpecKind(spec.kind),\n moderation: mIn,\n error:\n mIn.status === 'error'\n ? {\n code: 'processing_failed',\n message: 'Moderation check did not complete.',\n details: { provider: mIn.provider, reason: 'moderation_error' },\n }\n : {\n code: 'rejected',\n message: 'Rejected by moderation.',\n details: { provider: mIn.provider, reasons: mIn.reasons, findings: mIn.findings },\n },\n });\n }\n }\n\n const result = await processMedia(\n spec,\n {\n inputPath,\n outputBasePath,\n inputMime: io.input.mime,\n },\n { signal, onProgress },\n );\n\n if (result.ok && result.outputs?.length) {\n const perFileLimit = spec.maxOutputBytes ?? spec.maxBytes ?? 200 * 1024 * 1024;\n const totalLimit =\n spec.maxTotalOutputBytes ?? spec.maxOutputBytes ?? spec.maxBytes ?? 400 * 1024 * 1024;\n\n let total = 0;\n for (const o of result.outputs) {\n if (!o.path) continue;\n const s = await stat(o.path);\n const size = Number(s.size ?? 0);\n total += size;\n if (!o.sizeBytes) o.sizeBytes = size;\n if (size > perFileLimit) {\n return withInspection({\n ok: false,\n mediaType: mediaTypeFromSpecKind(spec.kind),\n error: {\n code: 'too_large',\n message: 'Processed output exceeds allowed size.',\n details: { key: o.key, sizeBytes: size, limitBytes: perFileLimit },\n },\n });\n }\n if (total > totalLimit) {\n return withInspection({\n ok: false,\n mediaType: mediaTypeFromSpecKind(spec.kind),\n error: {\n code: 'too_large',\n message: 'Total processed outputs exceed allowed size.',\n details: { totalBytes: total, limitBytes: totalLimit },\n },\n });\n }\n }\n }\n\n if (!result.ok || !result.outputs?.length) {\n if (mIn) result.moderation = mIn;\n onProgress?.({ phase: 'done', percent: result.ok ? 1 : 0 });\n return withInspection(result);\n }\n\n let mOut: MediaModerationResult | null | undefined;\n if (moderation?.moderateOutput) {\n onProgress?.({ phase: 'moderation_output', percent: 0 });\n mOut = attachProvider(\n await moderation.moderateOutput({\n spec,\n kind: spec.kind,\n outputs: (result.outputs ?? [])\n .filter((o) => !!o.path)\n .map((o) => ({ key: o.key, localPath: o.path!, mime: o.mime })),\n }),\n moderation.provider,\n );\n onProgress?.({ phase: 'moderation_output', percent: 1 });\n\n if (mOut?.status === 'rejected' || mOut?.status === 'error') {\n return withInspection({\n ok: false,\n mediaType: result.mediaType,\n moderation: mOut,\n error:\n mOut.status === 'error'\n ? {\n code: 'processing_failed',\n message: 'Moderation check did not complete.',\n details: { provider: mOut.provider, reason: 'moderation_error' },\n }\n : {\n code: 'rejected',\n message: 'Rejected by moderation.',\n details: {\n provider: mOut.provider,\n reasons: mOut.reasons,\n findings: mOut.findings,\n },\n },\n });\n }\n }\n\n onProgress?.({ phase: 'persist_outputs', percent: 0 });\n const totalOut = result.outputs.length;\n let outIdx = 0;\n for (const out of result.outputs) {\n if (signal?.aborted) {\n return withInspection({\n ok: false,\n mediaType: result.mediaType,\n error: { code: 'processing_canceled', message: 'Processing canceled.' },\n });\n }\n onProgress?.({\n phase: 'persist_outputs',\n percent: totalOut ? outIdx / totalOut : 0,\n detail: { key: out.key },\n });\n if (!out.path) continue;\n const persisted = await io.output.writeFromFile(out.path, out.key);\n if (persisted.url) out.url = persisted.url;\n if (persisted.path) out.path = persisted.path;\n outIdx += 1;\n }\n onProgress?.({ phase: 'persist_outputs', percent: 1 });\n\n const merged = mergeModeration(mIn ?? undefined, mOut ?? undefined);\n if (merged) result.moderation = merged;\n\n onProgress?.({ phase: 'done', percent: 1 });\n\n return withInspection(result);\n } finally {\n await ws.cleanup();\n }\n}\n"]}
@@ -1,14 +1,8 @@
1
- /** The one method of a Storage object handle this module uses. */
2
1
  export interface DownloadTokenWritable {
3
2
  setMetadata(update: {
4
3
  metadata: Record<string, string | null>;
5
4
  }): Promise<unknown>;
6
5
  }
7
- /** True when the object's stored metadata still carries a download token. */
8
6
  export declare function hasDownloadToken(objectMetadata: Readonly<Record<string, unknown>> | undefined): boolean;
9
- /**
10
- * Remove the object's download token and nothing else: a null custom-metadata value deletes that
11
- * one key, leaving every other entry. A no-op write when the object has none.
12
- */
13
7
  export declare function removeDownloadToken(file: DownloadTokenWritable): Promise<void>;
14
8
  //# sourceMappingURL=download-token.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"download-token.d.ts","sourceRoot":"","sources":["../../src/server/download-token.ts"],"names":[],"mappings":"AAKA,kEAAkE;AAClE,MAAM,WAAW,qBAAqB;IACpC,WAAW,CAAC,MAAM,EAAE;QAAE,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC,CAAA;KAAE,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;CACpF;AAKD,6EAA6E;AAC7E,wBAAgB,gBAAgB,CAC9B,cAAc,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,GAAG,SAAS,GAC5D,OAAO,CAGT;AAED;;;GAGG;AACH,wBAAsB,mBAAmB,CAAC,IAAI,EAAE,qBAAqB,GAAG,OAAO,CAAC,IAAI,CAAC,CAEpF"}
1
+ {"version":3,"file":"download-token.d.ts","sourceRoot":"","sources":["../../src/server/download-token.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,qBAAqB;IACpC,WAAW,CAAC,MAAM,EAAE;QAAE,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC,CAAA;KAAE,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;CACpF;AAID,wBAAgB,gBAAgB,CAC9B,cAAc,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,GAAG,SAAS,GAC5D,OAAO,CAGT;AAED,wBAAsB,mBAAmB,CAAC,IAAI,EAAE,qBAAqB,GAAG,OAAO,CAAC,IAAI,CAAC,CAEpF"}
@@ -1,18 +1,8 @@
1
- // The one remover of a Storage object's Firebase download token. The Firebase Storage client gives
2
- // every object it uploads a `firebaseStorageDownloadTokens` metadata entry: a never-expiring link
3
- // that reads the object signed out, around the Storage rules. An app that serves no object through
4
- // one removes it as soon as the server takes the object over.
5
- /** The custom-metadata key the Firebase Storage client writes on upload. */
6
1
  const DOWNLOAD_TOKENS_KEY = 'firebaseStorageDownloadTokens';
7
- /** True when the object's stored metadata still carries a download token. */
8
2
  export function hasDownloadToken(objectMetadata) {
9
3
  const custom = objectMetadata?.['metadata'];
10
4
  return typeof custom === 'object' && custom !== null && DOWNLOAD_TOKENS_KEY in custom;
11
5
  }
12
- /**
13
- * Remove the object's download token and nothing else: a null custom-metadata value deletes that
14
- * one key, leaving every other entry. A no-op write when the object has none.
15
- */
16
6
  export async function removeDownloadToken(file) {
17
7
  await file.setMetadata({ metadata: { [DOWNLOAD_TOKENS_KEY]: null } });
18
8
  }
@@ -1 +1 @@
1
- {"version":3,"file":"download-token.js","sourceRoot":"","sources":["../../src/server/download-token.ts"],"names":[],"mappings":"AAAA,mGAAmG;AACnG,kGAAkG;AAClG,mGAAmG;AACnG,8DAA8D;AAO9D,4EAA4E;AAC5E,MAAM,mBAAmB,GAAG,+BAA+B,CAAC;AAE5D,6EAA6E;AAC7E,MAAM,UAAU,gBAAgB,CAC9B,cAA6D;IAE7D,MAAM,MAAM,GAAG,cAAc,EAAE,CAAC,UAAU,CAAC,CAAC;IAC5C,OAAO,OAAO,MAAM,KAAK,QAAQ,IAAI,MAAM,KAAK,IAAI,IAAI,mBAAmB,IAAI,MAAM,CAAC;AACxF,CAAC;AAED;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,mBAAmB,CAAC,IAA2B;IACnE,MAAM,IAAI,CAAC,WAAW,CAAC,EAAE,QAAQ,EAAE,EAAE,CAAC,mBAAmB,CAAC,EAAE,IAAI,EAAE,EAAE,CAAC,CAAC;AACxE,CAAC","sourcesContent":["// The one remover of a Storage object's Firebase download token. The Firebase Storage client gives\n// every object it uploads a `firebaseStorageDownloadTokens` metadata entry: a never-expiring link\n// that reads the object signed out, around the Storage rules. An app that serves no object through\n// one removes it as soon as the server takes the object over.\n\n/** The one method of a Storage object handle this module uses. */\nexport interface DownloadTokenWritable {\n setMetadata(update: { metadata: Record<string, string | null> }): Promise<unknown>;\n}\n\n/** The custom-metadata key the Firebase Storage client writes on upload. */\nconst DOWNLOAD_TOKENS_KEY = 'firebaseStorageDownloadTokens';\n\n/** True when the object's stored metadata still carries a download token. */\nexport function hasDownloadToken(\n objectMetadata: Readonly<Record<string, unknown>> | undefined,\n): boolean {\n const custom = objectMetadata?.['metadata'];\n return typeof custom === 'object' && custom !== null && DOWNLOAD_TOKENS_KEY in custom;\n}\n\n/**\n * Remove the object's download token and nothing else: a null custom-metadata value deletes that\n * one key, leaving every other entry. A no-op write when the object has none.\n */\nexport async function removeDownloadToken(file: DownloadTokenWritable): Promise<void> {\n await file.setMetadata({ metadata: { [DOWNLOAD_TOKENS_KEY]: null } });\n}\n"]}
1
+ {"version":3,"file":"download-token.js","sourceRoot":"","sources":["../../src/server/download-token.ts"],"names":[],"mappings":"AAIA,MAAM,mBAAmB,GAAG,+BAA+B,CAAC;AAE5D,MAAM,UAAU,gBAAgB,CAC9B,cAA6D;IAE7D,MAAM,MAAM,GAAG,cAAc,EAAE,CAAC,UAAU,CAAC,CAAC;IAC5C,OAAO,OAAO,MAAM,KAAK,QAAQ,IAAI,MAAM,KAAK,IAAI,IAAI,mBAAmB,IAAI,MAAM,CAAC;AACxF,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,mBAAmB,CAAC,IAA2B;IACnE,MAAM,IAAI,CAAC,WAAW,CAAC,EAAE,QAAQ,EAAE,EAAE,CAAC,mBAAmB,CAAC,EAAE,IAAI,EAAE,EAAE,CAAC,CAAC;AACxE,CAAC","sourcesContent":["export interface DownloadTokenWritable {\n setMetadata(update: { metadata: Record<string, string | null> }): Promise<unknown>;\n}\n\nconst DOWNLOAD_TOKENS_KEY = 'firebaseStorageDownloadTokens';\n\nexport function hasDownloadToken(\n objectMetadata: Readonly<Record<string, unknown>> | undefined,\n): boolean {\n const custom = objectMetadata?.['metadata'];\n return typeof custom === 'object' && custom !== null && DOWNLOAD_TOKENS_KEY in custom;\n}\n\nexport async function removeDownloadToken(file: DownloadTokenWritable): Promise<void> {\n await file.setMetadata({ metadata: { [DOWNLOAD_TOKENS_KEY]: null } });\n}\n"]}
@@ -1,21 +1,11 @@
1
1
  import type { MediaIO } from '../io/types.js';
2
2
  import type { MediaObjectStore } from './storage-ops.js';
3
3
  export interface CreateObjectStoreMediaIOArgs {
4
- /** Firebase Storage staging path of the uploaded source file. */
5
4
  inputStoragePath: string;
6
- /** Store receiving the processed outputs. */
7
5
  outputStore: MediaObjectStore;
8
- /** Key prefix for outputs, e.g. `mediaAssets/{mediaAssetId}`. */
9
6
  outputKeyPrefix: string;
10
- /** Optional mime hint for the input. */
11
7
  inputMime?: string;
12
- /**
13
- * Optional object generation to pin the input download to. When set, the
14
- * download resolves the exact immutable generation of the staged object
15
- * (per GCS generation-precondition semantics), so a concurrent overwrite of
16
- * the source path cannot swap the bytes that get read. Accepts the numeric
17
- * generation or its string form (GCS generations exceed 2^53).
18
- */
8
+ /** GCS generations exceed 2^53, so the string form is accepted too. */
19
9
  inputGeneration?: string | number;
20
10
  }
21
11
  export declare function createObjectStoreMediaIO(args: CreateObjectStoreMediaIOArgs): MediaIO;
@@ -1 +1 @@
1
- {"version":3,"file":"firebase-media-io.d.ts","sourceRoot":"","sources":["../../src/server/firebase-media-io.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,gBAAgB,CAAC;AAC9C,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AAEzD,MAAM,WAAW,4BAA4B;IAC3C,iEAAiE;IACjE,gBAAgB,EAAE,MAAM,CAAC;IACzB,6CAA6C;IAC7C,WAAW,EAAE,gBAAgB,CAAC;IAC9B,iEAAiE;IACjE,eAAe,EAAE,MAAM,CAAC;IACxB,wCAAwC;IACxC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;;;;;OAMG;IACH,eAAe,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;CACnC;AAED,wBAAgB,wBAAwB,CAAC,IAAI,EAAE,4BAA4B,GAAG,OAAO,CAsBpF"}
1
+ {"version":3,"file":"firebase-media-io.d.ts","sourceRoot":"","sources":["../../src/server/firebase-media-io.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,gBAAgB,CAAC;AAC9C,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AAEzD,MAAM,WAAW,4BAA4B;IAC3C,gBAAgB,EAAE,MAAM,CAAC;IACzB,WAAW,EAAE,gBAAgB,CAAC;IAC9B,eAAe,EAAE,MAAM,CAAC;IACxB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,uEAAuE;IACvE,eAAe,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;CACnC;AAED,wBAAgB,wBAAwB,CAAC,IAAI,EAAE,4BAA4B,GAAG,OAAO,CAsBpF"}
@@ -1,8 +1,3 @@
1
- // MediaIO adapter: reads the staged upload from Firebase Storage, writes
2
- // processed outputs through whichever MediaObjectStore the app selected for the
3
- // environment (see storage-ops.ts). Output keys are extension-less:
4
- // `${outputKeyPrefix}/${outputKey}` — meaning lives in the asset registry,
5
- // not the key. Writes return the object key; no URLs anywhere.
6
1
  import { getStorage } from 'firebase-admin/storage';
7
2
  export function createObjectStoreMediaIO(args) {
8
3
  const bucket = getStorage().bucket();
@@ -1 +1 @@
1
- {"version":3,"file":"firebase-media-io.js","sourceRoot":"","sources":["../../src/server/firebase-media-io.ts"],"names":[],"mappings":"AAAA,yEAAyE;AACzE,gFAAgF;AAChF,oEAAoE;AACpE,2EAA2E;AAC3E,+DAA+D;AAE/D,OAAO,EAAE,UAAU,EAAE,MAAM,wBAAwB,CAAC;AAuBpD,MAAM,UAAU,wBAAwB,CAAC,IAAkC;IACzE,MAAM,MAAM,GAAG,UAAU,EAAE,CAAC,MAAM,EAAE,CAAC;IAErC,OAAO;QACL,KAAK,EAAE;YACL,IAAI,EAAE,IAAI,CAAC,SAAS;YACpB,KAAK,CAAC,UAAU,CAAC,SAAiB;gBAChC,MAAM,IAAI,GACR,IAAI,CAAC,eAAe,IAAI,IAAI;oBAC1B,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,gBAAgB,EAAE,EAAE,UAAU,EAAE,IAAI,CAAC,eAAe,EAAE,CAAC;oBAC1E,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,gBAAgB,CAAC,CAAC;gBACzC,MAAM,IAAI,CAAC,QAAQ,CAAC,EAAE,WAAW,EAAE,SAAS,EAAE,CAAC,CAAC;YAClD,CAAC;SACF;QACD,MAAM,EAAE;YACN,KAAK,CAAC,aAAa,CAAC,SAAiB,EAAE,SAAiB;gBACtD,MAAM,GAAG,GAAG,GAAG,IAAI,CAAC,eAAe,IAAI,SAAS,EAAE,CAAC;gBACnD,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,WAAW,CAAC,OAAO,CAAC,EAAE,SAAS,EAAE,GAAG,EAAE,CAAC,CAAC;gBAClE,OAAO,EAAE,IAAI,EAAE,MAAM,CAAC,GAAG,EAAE,CAAC;YAC9B,CAAC;SACF;KACF,CAAC;AACJ,CAAC","sourcesContent":["// MediaIO adapter: reads the staged upload from Firebase Storage, writes\n// processed outputs through whichever MediaObjectStore the app selected for the\n// environment (see storage-ops.ts). Output keys are extension-less:\n// `${outputKeyPrefix}/${outputKey}` — meaning lives in the asset registry,\n// not the key. Writes return the object key; no URLs anywhere.\n\nimport { getStorage } from 'firebase-admin/storage';\nimport type { MediaIO } from '../io/types.js';\nimport type { MediaObjectStore } from './storage-ops.js';\n\nexport interface CreateObjectStoreMediaIOArgs {\n /** Firebase Storage staging path of the uploaded source file. */\n inputStoragePath: string;\n /** Store receiving the processed outputs. */\n outputStore: MediaObjectStore;\n /** Key prefix for outputs, e.g. `mediaAssets/{mediaAssetId}`. */\n outputKeyPrefix: string;\n /** Optional mime hint for the input. */\n inputMime?: string;\n /**\n * Optional object generation to pin the input download to. When set, the\n * download resolves the exact immutable generation of the staged object\n * (per GCS generation-precondition semantics), so a concurrent overwrite of\n * the source path cannot swap the bytes that get read. Accepts the numeric\n * generation or its string form (GCS generations exceed 2^53).\n */\n inputGeneration?: string | number;\n}\n\nexport function createObjectStoreMediaIO(args: CreateObjectStoreMediaIOArgs): MediaIO {\n const bucket = getStorage().bucket();\n\n return {\n input: {\n mime: args.inputMime,\n async readToFile(localPath: string) {\n const file =\n args.inputGeneration != null\n ? bucket.file(args.inputStoragePath, { generation: args.inputGeneration })\n : bucket.file(args.inputStoragePath);\n await file.download({ destination: localPath });\n },\n },\n output: {\n async writeFromFile(localPath: string, outputKey: string) {\n const key = `${args.outputKeyPrefix}/${outputKey}`;\n const result = await args.outputStore.putFile({ localPath, key });\n return { path: result.key };\n },\n },\n };\n}\n"]}
1
+ {"version":3,"file":"firebase-media-io.js","sourceRoot":"","sources":["../../src/server/firebase-media-io.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,wBAAwB,CAAC;AAapD,MAAM,UAAU,wBAAwB,CAAC,IAAkC;IACzE,MAAM,MAAM,GAAG,UAAU,EAAE,CAAC,MAAM,EAAE,CAAC;IAErC,OAAO;QACL,KAAK,EAAE;YACL,IAAI,EAAE,IAAI,CAAC,SAAS;YACpB,KAAK,CAAC,UAAU,CAAC,SAAiB;gBAChC,MAAM,IAAI,GACR,IAAI,CAAC,eAAe,IAAI,IAAI;oBAC1B,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,gBAAgB,EAAE,EAAE,UAAU,EAAE,IAAI,CAAC,eAAe,EAAE,CAAC;oBAC1E,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,gBAAgB,CAAC,CAAC;gBACzC,MAAM,IAAI,CAAC,QAAQ,CAAC,EAAE,WAAW,EAAE,SAAS,EAAE,CAAC,CAAC;YAClD,CAAC;SACF;QACD,MAAM,EAAE;YACN,KAAK,CAAC,aAAa,CAAC,SAAiB,EAAE,SAAiB;gBACtD,MAAM,GAAG,GAAG,GAAG,IAAI,CAAC,eAAe,IAAI,SAAS,EAAE,CAAC;gBACnD,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,WAAW,CAAC,OAAO,CAAC,EAAE,SAAS,EAAE,GAAG,EAAE,CAAC,CAAC;gBAClE,OAAO,EAAE,IAAI,EAAE,MAAM,CAAC,GAAG,EAAE,CAAC;YAC9B,CAAC;SACF;KACF,CAAC;AACJ,CAAC","sourcesContent":["import { getStorage } from 'firebase-admin/storage';\nimport type { MediaIO } from '../io/types.js';\nimport type { MediaObjectStore } from './storage-ops.js';\n\nexport interface CreateObjectStoreMediaIOArgs {\n inputStoragePath: string;\n outputStore: MediaObjectStore;\n outputKeyPrefix: string;\n inputMime?: string;\n /** GCS generations exceed 2^53, so the string form is accepted too. */\n inputGeneration?: string | number;\n}\n\nexport function createObjectStoreMediaIO(args: CreateObjectStoreMediaIOArgs): MediaIO {\n const bucket = getStorage().bucket();\n\n return {\n input: {\n mime: args.inputMime,\n async readToFile(localPath: string) {\n const file =\n args.inputGeneration != null\n ? bucket.file(args.inputStoragePath, { generation: args.inputGeneration })\n : bucket.file(args.inputStoragePath);\n await file.download({ destination: localPath });\n },\n },\n output: {\n async writeFromFile(localPath: string, outputKey: string) {\n const key = `${args.outputKeyPrefix}/${outputKey}`;\n const result = await args.outputStore.putFile({ localPath, key });\n return { path: result.key };\n },\n },\n };\n}\n"]}
@@ -1,10 +1,2 @@
1
- /**
2
- * True when a storage read or delete failed because the object does not exist, from
3
- * either backend this package's stores use, decided from structured fields only — never
4
- * from message text, which a library may rewrite and which carries no contract. The R2
5
- * store's `R2StorageError` answers with status `404` and S3 error code `NoSuchKey`, which
6
- * tells a missing key from a missing bucket (`NoSuchBucket`); the Cloud Storage Admin SDK
7
- * answers with the numeric HTTP code `404`.
8
- */
9
1
  export declare function isObjectNotFoundError(e: unknown): boolean;
10
2
  //# sourceMappingURL=gcs-errors.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"gcs-errors.d.ts","sourceRoot":"","sources":["../../src/server/gcs-errors.ts"],"names":[],"mappings":"AAEA;;;;;;;GAOG;AACH,wBAAgB,qBAAqB,CAAC,CAAC,EAAE,OAAO,GAAG,OAAO,CAGzD"}
1
+ {"version":3,"file":"gcs-errors.d.ts","sourceRoot":"","sources":["../../src/server/gcs-errors.ts"],"names":[],"mappings":"AAEA,wBAAgB,qBAAqB,CAAC,CAAC,EAAE,OAAO,GAAG,OAAO,CAGzD"}
@@ -1,12 +1,4 @@
1
1
  import { R2StorageError } from './r2-retry.js';
2
- /**
3
- * True when a storage read or delete failed because the object does not exist, from
4
- * either backend this package's stores use, decided from structured fields only — never
5
- * from message text, which a library may rewrite and which carries no contract. The R2
6
- * store's `R2StorageError` answers with status `404` and S3 error code `NoSuchKey`, which
7
- * tells a missing key from a missing bucket (`NoSuchBucket`); the Cloud Storage Admin SDK
8
- * answers with the numeric HTTP code `404`.
9
- */
10
2
  export function isObjectNotFoundError(e) {
11
3
  if (e instanceof R2StorageError)
12
4
  return e.status === 404 && e.s3ErrorCode === 'NoSuchKey';
@@ -1 +1 @@
1
- {"version":3,"file":"gcs-errors.js","sourceRoot":"","sources":["../../src/server/gcs-errors.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAE/C;;;;;;;GAOG;AACH,MAAM,UAAU,qBAAqB,CAAC,CAAU;IAC9C,IAAI,CAAC,YAAY,cAAc;QAAE,OAAO,CAAC,CAAC,MAAM,KAAK,GAAG,IAAI,CAAC,CAAC,WAAW,KAAK,WAAW,CAAC;IAC1F,OAAQ,CAA2C,EAAE,IAAI,KAAK,GAAG,CAAC;AACpE,CAAC","sourcesContent":["import { R2StorageError } from './r2-retry.js';\n\n/**\n * True when a storage read or delete failed because the object does not exist, from\n * either backend this package's stores use, decided from structured fields only — never\n * from message text, which a library may rewrite and which carries no contract. The R2\n * store's `R2StorageError` answers with status `404` and S3 error code `NoSuchKey`, which\n * tells a missing key from a missing bucket (`NoSuchBucket`); the Cloud Storage Admin SDK\n * answers with the numeric HTTP code `404`.\n */\nexport function isObjectNotFoundError(e: unknown): boolean {\n if (e instanceof R2StorageError) return e.status === 404 && e.s3ErrorCode === 'NoSuchKey';\n return (e as { code?: unknown } | null | undefined)?.code === 404;\n}\n"]}
1
+ {"version":3,"file":"gcs-errors.js","sourceRoot":"","sources":["../../src/server/gcs-errors.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAE/C,MAAM,UAAU,qBAAqB,CAAC,CAAU;IAC9C,IAAI,CAAC,YAAY,cAAc;QAAE,OAAO,CAAC,CAAC,MAAM,KAAK,GAAG,IAAI,CAAC,CAAC,WAAW,KAAK,WAAW,CAAC;IAC1F,OAAQ,CAA2C,EAAE,IAAI,KAAK,GAAG,CAAC;AACpE,CAAC","sourcesContent":["import { R2StorageError } from './r2-retry.js';\n\nexport function isObjectNotFoundError(e: unknown): boolean {\n if (e instanceof R2StorageError) return e.status === 404 && e.s3ErrorCode === 'NoSuchKey';\n return (e as { code?: unknown } | null | undefined)?.code === 404;\n}\n"]}
@@ -1,6 +1,5 @@
1
1
  import type { DocumentReference, DocumentSnapshot, Firestore, Transaction } from 'firebase-admin/firestore';
2
2
  import { type ClaimablePendingMedia, type PendingMediaClaimOutcome, type PendingMediaClaimPolicy, type PendingMediaFinalizeResult, type PendingMediaTerminalStatus } from '@ttt-productions/media-schemas';
3
- /** The app's pending-media row schema — anything with zod's `safeParse` shape. */
4
3
  export interface PendingMediaRowParser<TRow> {
5
4
  safeParse(data: unknown): {
6
5
  success: true;
@@ -12,54 +11,24 @@ export interface PendingMediaRowParser<TRow> {
12
11
  export interface ClaimPendingMediaOptions<TRow> {
13
12
  schema: PendingMediaRowParser<TRow>;
14
13
  policy: PendingMediaClaimPolicy;
15
- /** Epoch ms; defaults to `Date.now()`. */
16
14
  now?: number;
17
15
  }
18
- /**
19
- * Claim a row for processing inside `transaction`, driven by the CURRENT row (never a
20
- * frozen event snapshot). Reads before any write. A row that no longer parses is
21
- * `missing` and never dispatched: a corrupt row must not drive the processor.
22
- */
23
16
  export declare function claimPendingMediaForProcessing<TRow extends ClaimablePendingMedia>(transaction: Transaction, pendingMediaRef: DocumentReference, options: ClaimPendingMediaOptions<TRow>): Promise<PendingMediaClaimOutcome<TRow>>;
24
17
  export interface PendingMediaFinalizeContext {
25
18
  targetStatus: PendingMediaTerminalStatus;
26
- /** True when the row failed schema validation and is being failed as unparsable. */
27
19
  unparsable: boolean;
28
20
  }
29
- /**
30
- * Caller-owned writes that must commit atomically with a terminal transition, and only
31
- * when it applies (releasing a quota reservation, for example — exactly once, because
32
- * the compare-and-set gates it). It runs in the transaction's READ phase and may
33
- * `transaction.get`; it returns the WRITE-phase step, or undefined when there is nothing
34
- * to write. The step runs after the terminal write, and its return value is surfaced as
35
- * the outcome's `extra`.
36
- */
37
21
  export type PendingMediaExtraWrites<TExtra> = (transaction: Transaction, snapshot: DocumentSnapshot, context: PendingMediaFinalizeContext) => Promise<(() => TExtra) | undefined>;
38
22
  export interface FinalizePendingMediaOptions<TExtra> {
39
- /** Merged into the terminal fields (`result`, `errorCategory`, `errorMessage`, …). */
40
23
  extraFields?: Record<string, unknown>;
41
24
  extraWrites?: PendingMediaExtraWrites<TExtra>;
42
- /** Epoch ms; defaults to `Date.now()`. */
43
25
  now?: number;
44
26
  }
45
27
  export interface PendingMediaFinalizeOutcome<TExtra> extends PendingMediaFinalizeResult {
46
- /** What the extra writes returned — present only on `applied`, when they wrote. */
47
28
  extra?: TExtra;
48
29
  }
49
- /**
50
- * Compare-and-set finalize inside the caller's transaction: re-read the row, decide,
51
- * and write the terminal fields (plus any extra writes) only on `applied`. An
52
- * already-terminal row is never overwritten. Reads-before-writes holds as long as the
53
- * caller has issued no write on `transaction` yet.
54
- */
30
+ /** The caller must not have written on `transaction` yet: a Firestore transaction reads before its first write. */
55
31
  export declare function finalizePendingMediaInTransaction<TExtra = never>(transaction: Transaction, ref: DocumentReference, targetStatus: PendingMediaTerminalStatus, options?: FinalizePendingMediaOptions<TExtra>): Promise<PendingMediaFinalizeOutcome<TExtra>>;
56
- /** Standalone compare-and-set finalize, in its own transaction. */
57
32
  export declare function finalizePendingMedia<TExtra = never>(db: Firestore, ref: DocumentReference, targetStatus: PendingMediaTerminalStatus, options?: FinalizePendingMediaOptions<TExtra>): Promise<PendingMediaFinalizeOutcome<TExtra>>;
58
- /**
59
- * Fail a row that no longer passes schema validation, so the transition table cannot
60
- * be applied (it may carry no readable `status`). One transaction: the row is failed —
61
- * with any extra writes — only while it has no status or a `pending` / `processing`
62
- * one, so an already-terminal row is never touched and extra writes run exactly once.
63
- */
64
33
  export declare function finalizeUnparsablePendingMedia<TExtra = never>(db: Firestore, ref: DocumentReference, options?: FinalizePendingMediaOptions<TExtra>): Promise<void>;
65
34
  //# sourceMappingURL=pending-media.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"pending-media.d.ts","sourceRoot":"","sources":["../../src/server/pending-media.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EACV,iBAAiB,EACjB,gBAAgB,EAChB,SAAS,EACT,WAAW,EACZ,MAAM,0BAA0B,CAAC;AAClC,OAAO,EAML,KAAK,qBAAqB,EAC1B,KAAK,wBAAwB,EAC7B,KAAK,uBAAuB,EAC5B,KAAK,0BAA0B,EAC/B,KAAK,0BAA0B,EAChC,MAAM,gCAAgC,CAAC;AAMxC,kFAAkF;AAClF,MAAM,WAAW,qBAAqB,CAAC,IAAI;IACzC,SAAS,CAAC,IAAI,EAAE,OAAO,GAAG;QAAE,OAAO,EAAE,IAAI,CAAC;QAAC,IAAI,EAAE,IAAI,CAAA;KAAE,GAAG;QAAE,OAAO,EAAE,KAAK,CAAA;KAAE,CAAC;CAC9E;AAED,MAAM,WAAW,wBAAwB,CAAC,IAAI;IAC5C,MAAM,EAAE,qBAAqB,CAAC,IAAI,CAAC,CAAC;IACpC,MAAM,EAAE,uBAAuB,CAAC;IAChC,0CAA0C;IAC1C,GAAG,CAAC,EAAE,MAAM,CAAC;CACd;AAED;;;;GAIG;AACH,wBAAsB,8BAA8B,CAAC,IAAI,SAAS,qBAAqB,EACrF,WAAW,EAAE,WAAW,EACxB,eAAe,EAAE,iBAAiB,EAClC,OAAO,EAAE,wBAAwB,CAAC,IAAI,CAAC,GACtC,OAAO,CAAC,wBAAwB,CAAC,IAAI,CAAC,CAAC,CAezC;AAMD,MAAM,WAAW,2BAA2B;IAC1C,YAAY,EAAE,0BAA0B,CAAC;IACzC,oFAAoF;IACpF,UAAU,EAAE,OAAO,CAAC;CACrB;AAED;;;;;;;GAOG;AACH,MAAM,MAAM,uBAAuB,CAAC,MAAM,IAAI,CAC5C,WAAW,EAAE,WAAW,EACxB,QAAQ,EAAE,gBAAgB,EAC1B,OAAO,EAAE,2BAA2B,KACjC,OAAO,CAAC,CAAC,MAAM,MAAM,CAAC,GAAG,SAAS,CAAC,CAAC;AAEzC,MAAM,WAAW,2BAA2B,CAAC,MAAM;IACjD,sFAAsF;IACtF,WAAW,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACtC,WAAW,CAAC,EAAE,uBAAuB,CAAC,MAAM,CAAC,CAAC;IAC9C,0CAA0C;IAC1C,GAAG,CAAC,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,2BAA2B,CAAC,MAAM,CAAE,SAAQ,0BAA0B;IACrF,mFAAmF;IACnF,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED;;;;;GAKG;AACH,wBAAsB,iCAAiC,CAAC,MAAM,GAAG,KAAK,EACpE,WAAW,EAAE,WAAW,EACxB,GAAG,EAAE,iBAAiB,EACtB,YAAY,EAAE,0BAA0B,EACxC,OAAO,GAAE,2BAA2B,CAAC,MAAM,CAAM,GAChD,OAAO,CAAC,2BAA2B,CAAC,MAAM,CAAC,CAAC,CAqB9C;AAED,mEAAmE;AACnE,wBAAgB,oBAAoB,CAAC,MAAM,GAAG,KAAK,EACjD,EAAE,EAAE,SAAS,EACb,GAAG,EAAE,iBAAiB,EACtB,YAAY,EAAE,0BAA0B,EACxC,OAAO,GAAE,2BAA2B,CAAC,MAAM,CAAM,GAChD,OAAO,CAAC,2BAA2B,CAAC,MAAM,CAAC,CAAC,CAI9C;AAED;;;;;GAKG;AACH,wBAAsB,8BAA8B,CAAC,MAAM,GAAG,KAAK,EACjE,EAAE,EAAE,SAAS,EACb,GAAG,EAAE,iBAAiB,EACtB,OAAO,GAAE,2BAA2B,CAAC,MAAM,CAAM,GAChD,OAAO,CAAC,IAAI,CAAC,CAkBf"}
1
+ {"version":3,"file":"pending-media.d.ts","sourceRoot":"","sources":["../../src/server/pending-media.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,iBAAiB,EACjB,gBAAgB,EAChB,SAAS,EACT,WAAW,EACZ,MAAM,0BAA0B,CAAC;AAClC,OAAO,EAML,KAAK,qBAAqB,EAC1B,KAAK,wBAAwB,EAC7B,KAAK,uBAAuB,EAC5B,KAAK,0BAA0B,EAC/B,KAAK,0BAA0B,EAChC,MAAM,gCAAgC,CAAC;AAExC,MAAM,WAAW,qBAAqB,CAAC,IAAI;IACzC,SAAS,CAAC,IAAI,EAAE,OAAO,GAAG;QAAE,OAAO,EAAE,IAAI,CAAC;QAAC,IAAI,EAAE,IAAI,CAAA;KAAE,GAAG;QAAE,OAAO,EAAE,KAAK,CAAA;KAAE,CAAC;CAC9E;AAED,MAAM,WAAW,wBAAwB,CAAC,IAAI;IAC5C,MAAM,EAAE,qBAAqB,CAAC,IAAI,CAAC,CAAC;IACpC,MAAM,EAAE,uBAAuB,CAAC;IAChC,GAAG,CAAC,EAAE,MAAM,CAAC;CACd;AAED,wBAAsB,8BAA8B,CAAC,IAAI,SAAS,qBAAqB,EACrF,WAAW,EAAE,WAAW,EACxB,eAAe,EAAE,iBAAiB,EAClC,OAAO,EAAE,wBAAwB,CAAC,IAAI,CAAC,GACtC,OAAO,CAAC,wBAAwB,CAAC,IAAI,CAAC,CAAC,CAezC;AAED,MAAM,WAAW,2BAA2B;IAC1C,YAAY,EAAE,0BAA0B,CAAC;IACzC,UAAU,EAAE,OAAO,CAAC;CACrB;AAED,MAAM,MAAM,uBAAuB,CAAC,MAAM,IAAI,CAC5C,WAAW,EAAE,WAAW,EACxB,QAAQ,EAAE,gBAAgB,EAC1B,OAAO,EAAE,2BAA2B,KACjC,OAAO,CAAC,CAAC,MAAM,MAAM,CAAC,GAAG,SAAS,CAAC,CAAC;AAEzC,MAAM,WAAW,2BAA2B,CAAC,MAAM;IACjD,WAAW,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACtC,WAAW,CAAC,EAAE,uBAAuB,CAAC,MAAM,CAAC,CAAC;IAC9C,GAAG,CAAC,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,2BAA2B,CAAC,MAAM,CAAE,SAAQ,0BAA0B;IACrF,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,mHAAmH;AACnH,wBAAsB,iCAAiC,CAAC,MAAM,GAAG,KAAK,EACpE,WAAW,EAAE,WAAW,EACxB,GAAG,EAAE,iBAAiB,EACtB,YAAY,EAAE,0BAA0B,EACxC,OAAO,GAAE,2BAA2B,CAAC,MAAM,CAAM,GAChD,OAAO,CAAC,2BAA2B,CAAC,MAAM,CAAC,CAAC,CAqB9C;AAED,wBAAgB,oBAAoB,CAAC,MAAM,GAAG,KAAK,EACjD,EAAE,EAAE,SAAS,EACb,GAAG,EAAE,iBAAiB,EACtB,YAAY,EAAE,0BAA0B,EACxC,OAAO,GAAE,2BAA2B,CAAC,MAAM,CAAM,GAChD,OAAO,CAAC,2BAA2B,CAAC,MAAM,CAAC,CAAC,CAI9C;AAED,wBAAsB,8BAA8B,CAAC,MAAM,GAAG,KAAK,EACjE,EAAE,EAAE,SAAS,EACb,GAAG,EAAE,iBAAiB,EACtB,OAAO,GAAE,2BAA2B,CAAC,MAAM,CAAM,GAChD,OAAO,CAAC,IAAI,CAAC,CAkBf"}
@@ -1,15 +1,4 @@
1
- // Admin-SDK transaction wrappers for the pending-media lifecycle. The decisions are the
2
- // pure ones in `@ttt-productions/media-schemas` (`decidePendingMediaClaim`,
3
- // `decidePendingMediaFinalize`); these read the CURRENT row inside a transaction,
4
- // decide, and write. Everything app-specific is injected: the row's `DocumentReference`
5
- // (so no collection name lives here), the row schema, the claim policy, the extra
6
- // writes a terminal transition carries, and the clock.
7
1
  import { buildPendingMediaTerminalFields, decidePendingMediaClaim, decidePendingMediaFinalize, isPendingMediaNonTerminalStatus, PendingMediaStatusSchema, } from '@ttt-productions/media-schemas';
8
- /**
9
- * Claim a row for processing inside `transaction`, driven by the CURRENT row (never a
10
- * frozen event snapshot). Reads before any write. A row that no longer parses is
11
- * `missing` and never dispatched: a corrupt row must not drive the processor.
12
- */
13
2
  export async function claimPendingMediaForProcessing(transaction, pendingMediaRef, options) {
14
3
  const snap = await transaction.get(pendingMediaRef);
15
4
  if (!snap.exists)
@@ -23,12 +12,7 @@ export async function claimPendingMediaForProcessing(transaction, pendingMediaRe
23
12
  transaction.update(pendingMediaRef, { ...write });
24
13
  return outcome;
25
14
  }
26
- /**
27
- * Compare-and-set finalize inside the caller's transaction: re-read the row, decide,
28
- * and write the terminal fields (plus any extra writes) only on `applied`. An
29
- * already-terminal row is never overwritten. Reads-before-writes holds as long as the
30
- * caller has issued no write on `transaction` yet.
31
- */
15
+ /** The caller must not have written on `transaction` yet: a Firestore transaction reads before its first write. */
32
16
  export async function finalizePendingMediaInTransaction(transaction, ref, targetStatus, options = {}) {
33
17
  const snap = await transaction.get(ref);
34
18
  const result = decidePendingMediaFinalize(snap.exists ? (snap.data() ?? {}) : undefined, targetStatus);
@@ -41,16 +25,9 @@ export async function finalizePendingMediaInTransaction(transaction, ref, target
41
25
  transaction.update(ref, buildPendingMediaTerminalFields(targetStatus, options.extraFields ?? {}, options.now ?? Date.now()));
42
26
  return applyExtra ? { ...result, extra: applyExtra() } : result;
43
27
  }
44
- /** Standalone compare-and-set finalize, in its own transaction. */
45
28
  export function finalizePendingMedia(db, ref, targetStatus, options = {}) {
46
29
  return db.runTransaction((transaction) => finalizePendingMediaInTransaction(transaction, ref, targetStatus, options));
47
30
  }
48
- /**
49
- * Fail a row that no longer passes schema validation, so the transition table cannot
50
- * be applied (it may carry no readable `status`). One transaction: the row is failed —
51
- * with any extra writes — only while it has no status or a `pending` / `processing`
52
- * one, so an already-terminal row is never touched and extra writes run exactly once.
53
- */
54
31
  export async function finalizeUnparsablePendingMedia(db, ref, options = {}) {
55
32
  await db.runTransaction(async (transaction) => {
56
33
  const snap = await transaction.get(ref);
@@ -1 +1 @@
1
- {"version":3,"file":"pending-media.js","sourceRoot":"","sources":["../../src/server/pending-media.ts"],"names":[],"mappings":"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","sourcesContent":["// Admin-SDK transaction wrappers for the pending-media lifecycle. The decisions are the\n// pure ones in `@ttt-productions/media-schemas` (`decidePendingMediaClaim`,\n// `decidePendingMediaFinalize`); these read the CURRENT row inside a transaction,\n// decide, and write. Everything app-specific is injected: the row's `DocumentReference`\n// (so no collection name lives here), the row schema, the claim policy, the extra\n// writes a terminal transition carries, and the clock.\n\nimport type {\n DocumentReference,\n DocumentSnapshot,\n Firestore,\n Transaction,\n} from 'firebase-admin/firestore';\nimport {\n buildPendingMediaTerminalFields,\n decidePendingMediaClaim,\n decidePendingMediaFinalize,\n isPendingMediaNonTerminalStatus,\n PendingMediaStatusSchema,\n type ClaimablePendingMedia,\n type PendingMediaClaimOutcome,\n type PendingMediaClaimPolicy,\n type PendingMediaFinalizeResult,\n type PendingMediaTerminalStatus,\n} from '@ttt-productions/media-schemas';\n\n// ---------------------------------------------------------------------------\n// Claim\n// ---------------------------------------------------------------------------\n\n/** The app's pending-media row schema — anything with zod's `safeParse` shape. */\nexport interface PendingMediaRowParser<TRow> {\n safeParse(data: unknown): { success: true; data: TRow } | { success: false };\n}\n\nexport interface ClaimPendingMediaOptions<TRow> {\n schema: PendingMediaRowParser<TRow>;\n policy: PendingMediaClaimPolicy;\n /** Epoch ms; defaults to `Date.now()`. */\n now?: number;\n}\n\n/**\n * Claim a row for processing inside `transaction`, driven by the CURRENT row (never a\n * frozen event snapshot). Reads before any write. A row that no longer parses is\n * `missing` and never dispatched: a corrupt row must not drive the processor.\n */\nexport async function claimPendingMediaForProcessing<TRow extends ClaimablePendingMedia>(\n transaction: Transaction,\n pendingMediaRef: DocumentReference,\n options: ClaimPendingMediaOptions<TRow>,\n): Promise<PendingMediaClaimOutcome<TRow>> {\n const snap = await transaction.get(pendingMediaRef);\n if (!snap.exists) return { kind: 'missing' };\n\n const parsed = options.schema.safeParse(snap.data());\n if (!parsed.success) return { kind: 'missing' };\n\n const { outcome, write } = decidePendingMediaClaim(\n parsed.data,\n options.now ?? Date.now(),\n options.policy,\n );\n // Spread into a literal: the Admin SDK's update() wants an UpdateData-compatible object.\n if (write) transaction.update(pendingMediaRef, { ...write });\n return outcome;\n}\n\n// ---------------------------------------------------------------------------\n// Finalize\n// ---------------------------------------------------------------------------\n\nexport interface PendingMediaFinalizeContext {\n targetStatus: PendingMediaTerminalStatus;\n /** True when the row failed schema validation and is being failed as unparsable. */\n unparsable: boolean;\n}\n\n/**\n * Caller-owned writes that must commit atomically with a terminal transition, and only\n * when it applies (releasing a quota reservation, for example — exactly once, because\n * the compare-and-set gates it). It runs in the transaction's READ phase and may\n * `transaction.get`; it returns the WRITE-phase step, or undefined when there is nothing\n * to write. The step runs after the terminal write, and its return value is surfaced as\n * the outcome's `extra`.\n */\nexport type PendingMediaExtraWrites<TExtra> = (\n transaction: Transaction,\n snapshot: DocumentSnapshot,\n context: PendingMediaFinalizeContext,\n) => Promise<(() => TExtra) | undefined>;\n\nexport interface FinalizePendingMediaOptions<TExtra> {\n /** Merged into the terminal fields (`result`, `errorCategory`, `errorMessage`, …). */\n extraFields?: Record<string, unknown>;\n extraWrites?: PendingMediaExtraWrites<TExtra>;\n /** Epoch ms; defaults to `Date.now()`. */\n now?: number;\n}\n\nexport interface PendingMediaFinalizeOutcome<TExtra> extends PendingMediaFinalizeResult {\n /** What the extra writes returned — present only on `applied`, when they wrote. */\n extra?: TExtra;\n}\n\n/**\n * Compare-and-set finalize inside the caller's transaction: re-read the row, decide,\n * and write the terminal fields (plus any extra writes) only on `applied`. An\n * already-terminal row is never overwritten. Reads-before-writes holds as long as the\n * caller has issued no write on `transaction` yet.\n */\nexport async function finalizePendingMediaInTransaction<TExtra = never>(\n transaction: Transaction,\n ref: DocumentReference,\n targetStatus: PendingMediaTerminalStatus,\n options: FinalizePendingMediaOptions<TExtra> = {},\n): Promise<PendingMediaFinalizeOutcome<TExtra>> {\n const snap = await transaction.get(ref);\n const result = decidePendingMediaFinalize(\n snap.exists ? (snap.data() ?? {}) : undefined,\n targetStatus,\n );\n if (result.decision !== 'applied') return result;\n\n const applyExtra = await options.extraWrites?.(transaction, snap, {\n targetStatus,\n unparsable: false,\n });\n transaction.update(\n ref,\n buildPendingMediaTerminalFields(\n targetStatus,\n options.extraFields ?? {},\n options.now ?? Date.now(),\n ),\n );\n return applyExtra ? { ...result, extra: applyExtra() } : result;\n}\n\n/** Standalone compare-and-set finalize, in its own transaction. */\nexport function finalizePendingMedia<TExtra = never>(\n db: Firestore,\n ref: DocumentReference,\n targetStatus: PendingMediaTerminalStatus,\n options: FinalizePendingMediaOptions<TExtra> = {},\n): Promise<PendingMediaFinalizeOutcome<TExtra>> {\n return db.runTransaction((transaction) =>\n finalizePendingMediaInTransaction(transaction, ref, targetStatus, options),\n );\n}\n\n/**\n * Fail a row that no longer passes schema validation, so the transition table cannot\n * be applied (it may carry no readable `status`). One transaction: the row is failed —\n * with any extra writes — only while it has no status or a `pending` / `processing`\n * one, so an already-terminal row is never touched and extra writes run exactly once.\n */\nexport async function finalizeUnparsablePendingMedia<TExtra = never>(\n db: Firestore,\n ref: DocumentReference,\n options: FinalizePendingMediaOptions<TExtra> = {},\n): Promise<void> {\n await db.runTransaction(async (transaction) => {\n const snap = await transaction.get(ref);\n if (!snap.exists) return;\n const status = (snap.data() as { status?: unknown } | undefined)?.status;\n if (status !== undefined && !isPendingMediaNonTerminalStatus(status)) return;\n\n const failed = PendingMediaStatusSchema.enum.failed;\n const applyExtra = await options.extraWrites?.(transaction, snap, {\n targetStatus: failed,\n unparsable: true,\n });\n transaction.update(\n ref,\n buildPendingMediaTerminalFields(failed, options.extraFields ?? {}, options.now ?? Date.now()),\n );\n applyExtra?.();\n });\n}\n"]}
1
+ {"version":3,"file":"pending-media.js","sourceRoot":"","sources":["../../src/server/pending-media.ts"],"names":[],"mappings":"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","sourcesContent":["import type {\n DocumentReference,\n DocumentSnapshot,\n Firestore,\n Transaction,\n} from 'firebase-admin/firestore';\nimport {\n buildPendingMediaTerminalFields,\n decidePendingMediaClaim,\n decidePendingMediaFinalize,\n isPendingMediaNonTerminalStatus,\n PendingMediaStatusSchema,\n type ClaimablePendingMedia,\n type PendingMediaClaimOutcome,\n type PendingMediaClaimPolicy,\n type PendingMediaFinalizeResult,\n type PendingMediaTerminalStatus,\n} from '@ttt-productions/media-schemas';\n\nexport interface PendingMediaRowParser<TRow> {\n safeParse(data: unknown): { success: true; data: TRow } | { success: false };\n}\n\nexport interface ClaimPendingMediaOptions<TRow> {\n schema: PendingMediaRowParser<TRow>;\n policy: PendingMediaClaimPolicy;\n now?: number;\n}\n\nexport async function claimPendingMediaForProcessing<TRow extends ClaimablePendingMedia>(\n transaction: Transaction,\n pendingMediaRef: DocumentReference,\n options: ClaimPendingMediaOptions<TRow>,\n): Promise<PendingMediaClaimOutcome<TRow>> {\n const snap = await transaction.get(pendingMediaRef);\n if (!snap.exists) return { kind: 'missing' };\n\n const parsed = options.schema.safeParse(snap.data());\n if (!parsed.success) return { kind: 'missing' };\n\n const { outcome, write } = decidePendingMediaClaim(\n parsed.data,\n options.now ?? Date.now(),\n options.policy,\n );\n // Spread into a literal: the Admin SDK's update() wants an UpdateData-compatible object.\n if (write) transaction.update(pendingMediaRef, { ...write });\n return outcome;\n}\n\nexport interface PendingMediaFinalizeContext {\n targetStatus: PendingMediaTerminalStatus;\n unparsable: boolean;\n}\n\nexport type PendingMediaExtraWrites<TExtra> = (\n transaction: Transaction,\n snapshot: DocumentSnapshot,\n context: PendingMediaFinalizeContext,\n) => Promise<(() => TExtra) | undefined>;\n\nexport interface FinalizePendingMediaOptions<TExtra> {\n extraFields?: Record<string, unknown>;\n extraWrites?: PendingMediaExtraWrites<TExtra>;\n now?: number;\n}\n\nexport interface PendingMediaFinalizeOutcome<TExtra> extends PendingMediaFinalizeResult {\n extra?: TExtra;\n}\n\n/** The caller must not have written on `transaction` yet: a Firestore transaction reads before its first write. */\nexport async function finalizePendingMediaInTransaction<TExtra = never>(\n transaction: Transaction,\n ref: DocumentReference,\n targetStatus: PendingMediaTerminalStatus,\n options: FinalizePendingMediaOptions<TExtra> = {},\n): Promise<PendingMediaFinalizeOutcome<TExtra>> {\n const snap = await transaction.get(ref);\n const result = decidePendingMediaFinalize(\n snap.exists ? (snap.data() ?? {}) : undefined,\n targetStatus,\n );\n if (result.decision !== 'applied') return result;\n\n const applyExtra = await options.extraWrites?.(transaction, snap, {\n targetStatus,\n unparsable: false,\n });\n transaction.update(\n ref,\n buildPendingMediaTerminalFields(\n targetStatus,\n options.extraFields ?? {},\n options.now ?? Date.now(),\n ),\n );\n return applyExtra ? { ...result, extra: applyExtra() } : result;\n}\n\nexport function finalizePendingMedia<TExtra = never>(\n db: Firestore,\n ref: DocumentReference,\n targetStatus: PendingMediaTerminalStatus,\n options: FinalizePendingMediaOptions<TExtra> = {},\n): Promise<PendingMediaFinalizeOutcome<TExtra>> {\n return db.runTransaction((transaction) =>\n finalizePendingMediaInTransaction(transaction, ref, targetStatus, options),\n );\n}\n\nexport async function finalizeUnparsablePendingMedia<TExtra = never>(\n db: Firestore,\n ref: DocumentReference,\n options: FinalizePendingMediaOptions<TExtra> = {},\n): Promise<void> {\n await db.runTransaction(async (transaction) => {\n const snap = await transaction.get(ref);\n if (!snap.exists) return;\n const status = (snap.data() as { status?: unknown } | undefined)?.status;\n if (status !== undefined && !isPendingMediaNonTerminalStatus(status)) return;\n\n const failed = PendingMediaStatusSchema.enum.failed;\n const applyExtra = await options.extraWrites?.(transaction, snap, {\n targetStatus: failed,\n unparsable: true,\n });\n transaction.update(\n ref,\n buildPendingMediaTerminalFields(failed, options.extraFields ?? {}, options.now ?? Date.now()),\n );\n applyExtra?.();\n });\n}\n"]}