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

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 (132) hide show
  1. package/dist/audio/audio-processor.d.ts +2 -2
  2. package/dist/audio/audio-processor.d.ts.map +1 -1
  3. package/dist/audio/audio-processor.js +62 -48
  4. package/dist/audio/audio-processor.js.map +1 -1
  5. package/dist/audio/probe.d.ts +1 -1
  6. package/dist/audio/probe.d.ts.map +1 -1
  7. package/dist/audio/probe.js +6 -3
  8. package/dist/audio/probe.js.map +1 -1
  9. package/dist/duration/media-duration.d.ts +2 -2
  10. package/dist/duration/media-duration.d.ts.map +1 -1
  11. package/dist/duration/media-duration.js +23 -21
  12. package/dist/duration/media-duration.js.map +1 -1
  13. package/dist/image/formats.d.ts +2 -2
  14. package/dist/image/formats.js +25 -25
  15. package/dist/image/formats.js.map +1 -1
  16. package/dist/image/image-processor.d.ts +2 -2
  17. package/dist/image/image-processor.d.ts.map +1 -1
  18. package/dist/image/image-processor.js +47 -37
  19. package/dist/image/image-processor.js.map +1 -1
  20. package/dist/image/resize.d.ts +2 -2
  21. package/dist/image/resize.d.ts.map +1 -1
  22. package/dist/image/resize.js +13 -13
  23. package/dist/image/resize.js.map +1 -1
  24. package/dist/index.d.ts +19 -16
  25. package/dist/index.d.ts.map +1 -1
  26. package/dist/index.js +18 -16
  27. package/dist/index.js.map +1 -1
  28. package/dist/inspection/inspect-media.d.ts +13 -13
  29. package/dist/inspection/inspect-media.d.ts.map +1 -1
  30. package/dist/inspection/inspect-media.js +196 -140
  31. package/dist/inspection/inspect-media.js.map +1 -1
  32. package/dist/io/fs.d.ts +1 -1
  33. package/dist/io/fs.js +4 -4
  34. package/dist/io/fs.js.map +1 -1
  35. package/dist/io/local-input.d.ts +1 -1
  36. package/dist/io/local-input.d.ts.map +1 -1
  37. package/dist/io/local-input.js +15 -15
  38. package/dist/io/local-input.js.map +1 -1
  39. package/dist/io/types.d.ts.map +1 -1
  40. package/dist/io/types.js.map +1 -1
  41. package/dist/moderation/merge.d.ts +1 -1
  42. package/dist/moderation/merge.d.ts.map +1 -1
  43. package/dist/moderation/merge.js +1 -1
  44. package/dist/moderation/merge.js.map +1 -1
  45. package/dist/moderation/types.d.ts +3 -3
  46. package/dist/moderation/types.js.map +1 -1
  47. package/dist/process-media.d.ts +2 -3
  48. package/dist/process-media.d.ts.map +1 -1
  49. package/dist/process-media.js +9 -9
  50. package/dist/process-media.js.map +1 -1
  51. package/dist/processing/errors.d.ts.map +1 -1
  52. package/dist/processing/errors.js +1 -1
  53. package/dist/processing/errors.js.map +1 -1
  54. package/dist/processing/result.d.ts +2 -2
  55. package/dist/processing/result.js +1 -1
  56. package/dist/processing/result.js.map +1 -1
  57. package/dist/run-pipeline.d.ts +5 -5
  58. package/dist/run-pipeline.d.ts.map +1 -1
  59. package/dist/run-pipeline.js +70 -54
  60. package/dist/run-pipeline.js.map +1 -1
  61. package/dist/server/download-token.d.ts +14 -0
  62. package/dist/server/download-token.d.ts.map +1 -0
  63. package/dist/server/download-token.js +19 -0
  64. package/dist/server/download-token.js.map +1 -0
  65. package/dist/server/firebase-media-io.d.ts +2 -2
  66. package/dist/server/firebase-media-io.js +1 -1
  67. package/dist/server/firebase-media-io.js.map +1 -1
  68. package/dist/server/gcs-errors.js +2 -2
  69. package/dist/server/gcs-errors.js.map +1 -1
  70. package/dist/server/index.d.ts +7 -5
  71. package/dist/server/index.d.ts.map +1 -1
  72. package/dist/server/index.js +7 -5
  73. package/dist/server/index.js.map +1 -1
  74. package/dist/server/pending-media.d.ts +2 -2
  75. package/dist/server/pending-media.d.ts.map +1 -1
  76. package/dist/server/pending-media.js +12 -6
  77. package/dist/server/pending-media.js.map +1 -1
  78. package/dist/server/r2-retry.d.ts +4 -4
  79. package/dist/server/r2-retry.d.ts.map +1 -1
  80. package/dist/server/r2-retry.js +48 -47
  81. package/dist/server/r2-retry.js.map +1 -1
  82. package/dist/server/staged-object.d.ts +2 -2
  83. package/dist/server/staged-object.d.ts.map +1 -1
  84. package/dist/server/staged-object.js +4 -4
  85. package/dist/server/staged-object.js.map +1 -1
  86. package/dist/server/storage-ops.d.ts +3 -5
  87. package/dist/server/storage-ops.d.ts.map +1 -1
  88. package/dist/server/storage-ops.js +101 -97
  89. package/dist/server/storage-ops.js.map +1 -1
  90. package/dist/types.d.ts +2 -2
  91. package/dist/types.js.map +1 -1
  92. package/dist/utils/log.d.ts.map +1 -1
  93. package/dist/utils/log.js +3 -3
  94. package/dist/utils/log.js.map +1 -1
  95. package/dist/utils/paths.d.ts.map +1 -1
  96. package/dist/utils/paths.js +1 -4
  97. package/dist/utils/paths.js.map +1 -1
  98. package/dist/utils/safe-path.js +8 -8
  99. package/dist/utils/safe-path.js.map +1 -1
  100. package/dist/validation/validate-duration.d.ts.map +1 -1
  101. package/dist/validation/validate-duration.js.map +1 -1
  102. package/dist/validation/validate-mime.d.ts.map +1 -1
  103. package/dist/validation/validate-mime.js.map +1 -1
  104. package/dist/validation/validate-size.d.ts.map +1 -1
  105. package/dist/validation/validate-size.js.map +1 -1
  106. package/dist/video/ffmpeg.d.ts +1 -1
  107. package/dist/video/ffmpeg.d.ts.map +1 -1
  108. package/dist/video/ffmpeg.js +51 -43
  109. package/dist/video/ffmpeg.js.map +1 -1
  110. package/dist/video/probe.d.ts +1 -1
  111. package/dist/video/probe.d.ts.map +1 -1
  112. package/dist/video/probe.js +10 -7
  113. package/dist/video/probe.js.map +1 -1
  114. package/dist/video/video-processor.d.ts +2 -2
  115. package/dist/video/video-processor.d.ts.map +1 -1
  116. package/dist/video/video-processor.js +123 -81
  117. package/dist/video/video-processor.js.map +1 -1
  118. package/dist/workspace/temp.js +4 -4
  119. package/dist/workspace/temp.js.map +1 -1
  120. package/package.json +2 -2
  121. package/dist/audio/index.d.ts +0 -3
  122. package/dist/audio/index.d.ts.map +0 -1
  123. package/dist/audio/index.js +0 -3
  124. package/dist/audio/index.js.map +0 -1
  125. package/dist/image/index.d.ts +0 -4
  126. package/dist/image/index.d.ts.map +0 -1
  127. package/dist/image/index.js +0 -4
  128. package/dist/image/index.js.map +0 -1
  129. package/dist/video/index.d.ts +0 -4
  130. package/dist/video/index.d.ts.map +0 -1
  131. package/dist/video/index.js +0 -4
  132. package/dist/video/index.js.map +0 -1
@@ -1,18 +1,18 @@
1
- import { parseMediaProcessingSpec } from "@ttt-productions/media-schemas";
2
- import { createTempWorkspace } from "./workspace/temp.js";
3
- import { processMedia } from "./process-media.js";
4
- import { inspectMedia } from "./inspection/inspect-media.js";
5
- import { mergeModeration } from "./moderation/merge.js";
6
- import path from "node:path";
7
- import { stat } from "node:fs/promises";
1
+ import { parseMediaProcessingSpec } from '@ttt-productions/media-schemas';
2
+ import { createTempWorkspace } from './workspace/temp.js';
3
+ import { processMedia } from './process-media.js';
4
+ import { inspectMedia } from './inspection/inspect-media.js';
5
+ import { mergeModeration } from './moderation/merge.js';
6
+ import path from 'node:path';
7
+ import { stat } from 'node:fs/promises';
8
8
  function mediaTypeFromSpecKind(kind) {
9
- if (kind === "image")
10
- return "image";
11
- if (kind === "video")
12
- return "video";
13
- if (kind === "audio")
14
- return "audio";
15
- return "other";
9
+ if (kind === 'image')
10
+ return 'image';
11
+ if (kind === 'video')
12
+ return 'video';
13
+ if (kind === 'audio')
14
+ return 'audio';
15
+ return 'other';
16
16
  }
17
17
  function attachProvider(m, provider) {
18
18
  if (!m)
@@ -21,7 +21,7 @@ function attachProvider(m, provider) {
21
21
  }
22
22
  export async function runMediaPipeline(args) {
23
23
  let { spec } = args;
24
- const { io, outputBaseName = "media", moderation, signal, onProgress, resolveAfterInspection } = args;
24
+ const { io, outputBaseName = 'media', moderation, signal, onProgress, resolveAfterInspection, } = args;
25
25
  // Carried onto every return path once produced — the finalizer must hand the
26
26
  // SAME inspection object to the safety gate (one classification authority).
27
27
  let inspection;
@@ -30,7 +30,7 @@ export async function runMediaPipeline(args) {
30
30
  return {
31
31
  ok: false,
32
32
  mediaType: mediaTypeFromSpecKind(spec.kind),
33
- error: { code: "processing_canceled", message: "Processing canceled." },
33
+ error: { code: 'processing_canceled', message: 'Processing canceled.' },
34
34
  };
35
35
  }
36
36
  // Runtime contract validation (fast-fail with actionable errors).
@@ -42,22 +42,26 @@ export async function runMediaPipeline(args) {
42
42
  ok: false,
43
43
  mediaType: mediaTypeFromSpecKind(spec.kind),
44
44
  error: {
45
- code: "invalid_spec",
46
- message: "Invalid MediaProcessingSpec.",
45
+ code: 'invalid_spec',
46
+ message: 'Invalid MediaProcessingSpec.',
47
47
  details: { issues: e?.issues ?? e?.message ?? String(e) },
48
48
  },
49
49
  };
50
50
  }
51
51
  const ws = await createTempWorkspace();
52
52
  try {
53
- const inputPath = path.join(ws.dir, "input");
53
+ const inputPath = path.join(ws.dir, 'input');
54
54
  const outputBasePath = path.join(ws.dir, outputBaseName);
55
55
  if (signal?.aborted) {
56
- return { ok: false, mediaType: mediaTypeFromSpecKind(spec.kind), error: { code: "processing_canceled", message: "Processing canceled." } };
56
+ return {
57
+ ok: false,
58
+ mediaType: mediaTypeFromSpecKind(spec.kind),
59
+ error: { code: 'processing_canceled', message: 'Processing canceled.' },
60
+ };
57
61
  }
58
- onProgress?.({ phase: "read_input", percent: 0 });
62
+ onProgress?.({ phase: 'read_input', percent: 0 });
59
63
  await io.input.readToFile(inputPath);
60
- onProgress?.({ phase: "read_input", percent: 1 });
64
+ onProgress?.({ phase: 'read_input', percent: 1 });
61
65
  // CANONICAL INSPECTION (before moderation and processing): classify the
62
66
  // just-downloaded bytes structurally and let the caller's policy adapter
63
67
  // pick the spec from the result. See RunPipelineArgs.resolveAfterInspection.
@@ -68,7 +72,7 @@ export async function runMediaPipeline(args) {
68
72
  ...(args.inspectionDeps ? { deps: args.inspectionDeps } : {}),
69
73
  });
70
74
  const resolution = await resolveAfterInspection(inspection);
71
- if ("reject" in resolution) {
75
+ if ('reject' in resolution) {
72
76
  return withInspection({
73
77
  ok: false,
74
78
  mediaType: mediaTypeFromSpecKind(spec.kind),
@@ -83,8 +87,8 @@ export async function runMediaPipeline(args) {
83
87
  ok: false,
84
88
  mediaType: mediaTypeFromSpecKind(spec.kind),
85
89
  error: {
86
- code: "invalid_spec",
87
- message: "Invalid MediaProcessingSpec from resolveAfterInspection.",
90
+ code: 'invalid_spec',
91
+ message: 'Invalid MediaProcessingSpec from resolveAfterInspection.',
88
92
  details: { issues: e?.issues ?? e?.message ?? String(e) },
89
93
  },
90
94
  });
@@ -93,14 +97,14 @@ export async function runMediaPipeline(args) {
93
97
  // PRE moderation (original)
94
98
  let mIn;
95
99
  if (moderation?.moderateInput) {
96
- onProgress?.({ phase: "moderation_input", percent: 0 });
100
+ onProgress?.({ phase: 'moderation_input', percent: 0 });
97
101
  mIn = attachProvider(await moderation.moderateInput({
98
102
  spec,
99
103
  kind: spec.kind,
100
104
  localPath: inputPath,
101
105
  mime: io.input.mime,
102
106
  }), moderation.provider);
103
- onProgress?.({ phase: "moderation_input", percent: 1 });
107
+ onProgress?.({ phase: 'moderation_input', percent: 1 });
104
108
  // Fail closed on both "rejected" (policy violation) and "error" (the
105
109
  // moderation check itself did not complete — quota/outage/API failure).
106
110
  // A non-passing check must NEVER fall through to transcode + persist:
@@ -109,20 +113,20 @@ export async function runMediaPipeline(args) {
109
113
  // retryable `processing_failed` code AND carries `moderation` (status
110
114
  // "error") on the result — callers route that to their retryable
111
115
  // moderation lane and land the row failed rather than publishing it.
112
- if (mIn?.status === "rejected" || mIn?.status === "error") {
116
+ if (mIn?.status === 'rejected' || mIn?.status === 'error') {
113
117
  return withInspection({
114
118
  ok: false,
115
119
  mediaType: mediaTypeFromSpecKind(spec.kind),
116
120
  moderation: mIn,
117
- error: mIn.status === "error"
121
+ error: mIn.status === 'error'
118
122
  ? {
119
- code: "processing_failed",
120
- message: "Moderation check did not complete.",
121
- details: { provider: mIn.provider, reason: "moderation_error" },
123
+ code: 'processing_failed',
124
+ message: 'Moderation check did not complete.',
125
+ details: { provider: mIn.provider, reason: 'moderation_error' },
122
126
  }
123
127
  : {
124
- code: "rejected",
125
- message: "Rejected by moderation.",
128
+ code: 'rejected',
129
+ message: 'Rejected by moderation.',
126
130
  details: { provider: mIn.provider, reasons: mIn.reasons, findings: mIn.findings },
127
131
  },
128
132
  });
@@ -151,8 +155,8 @@ export async function runMediaPipeline(args) {
151
155
  ok: false,
152
156
  mediaType: mediaTypeFromSpecKind(spec.kind),
153
157
  error: {
154
- code: "too_large",
155
- message: "Processed output exceeds allowed size.",
158
+ code: 'too_large',
159
+ message: 'Processed output exceeds allowed size.',
156
160
  details: { key: o.key, sizeBytes: size, limitBytes: perFileLimit },
157
161
  },
158
162
  });
@@ -162,8 +166,8 @@ export async function runMediaPipeline(args) {
162
166
  ok: false,
163
167
  mediaType: mediaTypeFromSpecKind(spec.kind),
164
168
  error: {
165
- code: "too_large",
166
- message: "Total processed outputs exceed allowed size.",
169
+ code: 'too_large',
170
+ message: 'Total processed outputs exceed allowed size.',
167
171
  details: { totalBytes: total, limitBytes: totalLimit },
168
172
  },
169
173
  });
@@ -173,7 +177,7 @@ export async function runMediaPipeline(args) {
173
177
  if (!result.ok || !result.outputs?.length) {
174
178
  if (mIn)
175
179
  result.moderation = mIn;
176
- onProgress?.({ phase: "done", percent: result.ok ? 1 : 0 });
180
+ onProgress?.({ phase: 'done', percent: result.ok ? 1 : 0 });
177
181
  return withInspection(result);
178
182
  }
179
183
  // POST moderation (processed outputs) — runs on the LOCAL output paths
@@ -182,7 +186,7 @@ export async function runMediaPipeline(args) {
182
186
  // the consuming app's media-serving design docs.
183
187
  let mOut;
184
188
  if (moderation?.moderateOutput) {
185
- onProgress?.({ phase: "moderation_output", percent: 0 });
189
+ onProgress?.({ phase: 'moderation_output', percent: 0 });
186
190
  mOut = attachProvider(await moderation.moderateOutput({
187
191
  spec,
188
192
  kind: spec.kind,
@@ -190,39 +194,51 @@ export async function runMediaPipeline(args) {
190
194
  .filter((o) => !!o.path)
191
195
  .map((o) => ({ key: o.key, localPath: o.path, mime: o.mime })),
192
196
  }), moderation.provider);
193
- onProgress?.({ phase: "moderation_output", percent: 1 });
197
+ onProgress?.({ phase: 'moderation_output', percent: 1 });
194
198
  // Same fail-closed posture as the input check: block both "rejected"
195
199
  // and "error" before any persistence, so an incomplete check can never
196
200
  // publish unscanned output. Ordering is load-bearing — see the
197
201
  // consuming app's media-serving design docs.
198
- if (mOut?.status === "rejected" || mOut?.status === "error") {
202
+ if (mOut?.status === 'rejected' || mOut?.status === 'error') {
199
203
  return withInspection({
200
204
  ok: false,
201
205
  mediaType: result.mediaType,
202
206
  moderation: mOut,
203
- error: mOut.status === "error"
207
+ error: mOut.status === 'error'
204
208
  ? {
205
- code: "processing_failed",
206
- message: "Moderation check did not complete.",
207
- details: { provider: mOut.provider, reason: "moderation_error" },
209
+ code: 'processing_failed',
210
+ message: 'Moderation check did not complete.',
211
+ details: { provider: mOut.provider, reason: 'moderation_error' },
208
212
  }
209
213
  : {
210
- code: "rejected",
211
- message: "Rejected by moderation.",
212
- details: { provider: mOut.provider, reasons: mOut.reasons, findings: mOut.findings },
214
+ code: 'rejected',
215
+ message: 'Rejected by moderation.',
216
+ details: {
217
+ provider: mOut.provider,
218
+ reasons: mOut.reasons,
219
+ findings: mOut.findings,
220
+ },
213
221
  },
214
222
  });
215
223
  }
216
224
  }
217
225
  // Persist outputs — only after input AND output moderation both passed.
218
- onProgress?.({ phase: "persist_outputs", percent: 0 });
226
+ onProgress?.({ phase: 'persist_outputs', percent: 0 });
219
227
  const totalOut = result.outputs.length;
220
228
  let outIdx = 0;
221
229
  for (const out of result.outputs) {
222
230
  if (signal?.aborted) {
223
- return withInspection({ ok: false, mediaType: result.mediaType, error: { code: "processing_canceled", message: "Processing canceled." } });
231
+ return withInspection({
232
+ ok: false,
233
+ mediaType: result.mediaType,
234
+ error: { code: 'processing_canceled', message: 'Processing canceled.' },
235
+ });
224
236
  }
225
- onProgress?.({ phase: "persist_outputs", percent: totalOut ? outIdx / totalOut : 0, detail: { key: out.key } });
237
+ onProgress?.({
238
+ phase: 'persist_outputs',
239
+ percent: totalOut ? outIdx / totalOut : 0,
240
+ detail: { key: out.key },
241
+ });
226
242
  if (!out.path)
227
243
  continue;
228
244
  const persisted = await io.output.writeFromFile(out.path, out.key);
@@ -232,11 +248,11 @@ export async function runMediaPipeline(args) {
232
248
  out.path = persisted.path;
233
249
  outIdx += 1;
234
250
  }
235
- onProgress?.({ phase: "persist_outputs", percent: 1 });
251
+ onProgress?.({ phase: 'persist_outputs', percent: 1 });
236
252
  const merged = mergeModeration(mIn ?? undefined, mOut ?? undefined);
237
253
  if (merged)
238
254
  result.moderation = merged;
239
- onProgress?.({ phase: "done", percent: 1 });
255
+ onProgress?.({ phase: 'done', percent: 1 });
240
256
  return withInspection(result);
241
257
  }
242
258
  finally {
@@ -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\";\n import { createTempWorkspace } from \"./workspace/temp.js\";\n import { processMedia } from \"./process-media.js\";\n import { inspectMedia, type InspectMediaDeps } from \"./inspection/inspect-media.js\";\n import type { MediaIO } from \"./io/types.js\";\n import type { ModerationAdapter } from \"./moderation/types.js\";\n import { mergeModeration } from \"./moderation/merge.js\";\n import path from \"node:path\";\n import { stat } from \"node:fs/promises\";\n\n export 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 ) => Promise<{ spec: MediaProcessingSpec } | { reject: MediaProcessingError }> | ({ spec: MediaProcessingSpec } | { reject: MediaProcessingError });\n /** Inspection tool overrides (tests). */\n inspectionDeps?: InspectMediaDeps;\n }\n \n function mediaTypeFromSpecKind(kind: MediaProcessingSpec[\"kind\"]): \"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 \n function 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 \n export async function runMediaPipeline(args: RunPipelineArgs): Promise<MediaProcessingResult> {\n let { spec } = args;\n const { io, outputBaseName = \"media\", moderation, signal, onProgress, resolveAfterInspection } = 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 { ok: false, mediaType: mediaTypeFromSpecKind(spec.kind), error: { code: \"processing_canceled\", message: \"Processing canceled.\" } };\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(spec, {\n inputPath,\n outputBasePath,\n inputMime: io.input.mime,\n }, { signal, onProgress });\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 = 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: { provider: mOut.provider, reasons: mOut.reasons, findings: mOut.findings },\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({ ok: false, mediaType: result.mediaType, error: { code: \"processing_canceled\", message: \"Processing canceled.\" } });\n }\n onProgress?.({ phase: \"persist_outputs\", percent: totalOut ? outIdx / totalOut : 0, detail: { key: out.key } });\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 /**\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"]}
@@ -0,0 +1,14 @@
1
+ /** The one method of a Storage object handle this module uses. */
2
+ export interface DownloadTokenWritable {
3
+ setMetadata(update: {
4
+ metadata: Record<string, string | null>;
5
+ }): Promise<unknown>;
6
+ }
7
+ /** True when the object's stored metadata still carries a download token. */
8
+ 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
+ export declare function removeDownloadToken(file: DownloadTokenWritable): Promise<void>;
14
+ //# sourceMappingURL=download-token.d.ts.map
@@ -0,0 +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"}
@@ -0,0 +1,19 @@
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
+ const DOWNLOAD_TOKENS_KEY = 'firebaseStorageDownloadTokens';
7
+ /** True when the object's stored metadata still carries a download token. */
8
+ export function hasDownloadToken(objectMetadata) {
9
+ const custom = objectMetadata?.['metadata'];
10
+ return typeof custom === 'object' && custom !== null && DOWNLOAD_TOKENS_KEY in custom;
11
+ }
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
+ export async function removeDownloadToken(file) {
17
+ await file.setMetadata({ metadata: { [DOWNLOAD_TOKENS_KEY]: null } });
18
+ }
19
+ //# sourceMappingURL=download-token.js.map
@@ -0,0 +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,5 +1,5 @@
1
- import type { MediaIO } from "../io/types.js";
2
- import type { MediaObjectStore } from "./storage-ops.js";
1
+ import type { MediaIO } from '../io/types.js';
2
+ import type { MediaObjectStore } from './storage-ops.js';
3
3
  export interface CreateObjectStoreMediaIOArgs {
4
4
  /** Firebase Storage staging path of the uploaded source file. */
5
5
  inputStoragePath: string;
@@ -3,7 +3,7 @@
3
3
  // environment (see storage-ops.ts). Output keys are extension-less:
4
4
  // `${outputKeyPrefix}/${outputKey}` — meaning lives in the asset registry,
5
5
  // not the key. Writes return the object key; no URLs anywhere.
6
- import { getStorage } from "firebase-admin/storage";
6
+ import { getStorage } from 'firebase-admin/storage';
7
7
  export function createObjectStoreMediaIO(args) {
8
8
  const bucket = getStorage().bucket();
9
9
  return {
@@ -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,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,4 +1,4 @@
1
- import { R2StorageError } from "./r2-retry.js";
1
+ import { R2StorageError } from './r2-retry.js';
2
2
  /**
3
3
  * True when a storage read or delete failed because the object does not exist, from
4
4
  * either backend this package's stores use, decided from structured fields only — never
@@ -9,7 +9,7 @@ import { R2StorageError } from "./r2-retry.js";
9
9
  */
10
10
  export function isObjectNotFoundError(e) {
11
11
  if (e instanceof R2StorageError)
12
- return e.status === 404 && e.s3ErrorCode === "NoSuchKey";
12
+ return e.status === 404 && e.s3ErrorCode === 'NoSuchKey';
13
13
  return e?.code === 404;
14
14
  }
15
15
  //# sourceMappingURL=gcs-errors.js.map
@@ -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;;;;;;;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,6 +1,8 @@
1
- export * from "./storage-ops.js";
2
- export * from "./firebase-media-io.js";
3
- export * from "./pending-media.js";
4
- export * from "./staged-object.js";
5
- export * from "./gcs-errors.js";
1
+ export * from './storage-ops.js';
2
+ export { R2StorageError } from './r2-retry.js';
3
+ export * from './firebase-media-io.js';
4
+ export * from './pending-media.js';
5
+ export * from './staged-object.js';
6
+ export * from './gcs-errors.js';
7
+ export * from './download-token.js';
6
8
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/server/index.ts"],"names":[],"mappings":"AAAA,cAAc,kBAAkB,CAAC;AACjC,cAAc,wBAAwB,CAAC;AACvC,cAAc,oBAAoB,CAAC;AACnC,cAAc,oBAAoB,CAAC;AACnC,cAAc,iBAAiB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/server/index.ts"],"names":[],"mappings":"AAAA,cAAc,kBAAkB,CAAC;AACjC,OAAO,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAC/C,cAAc,wBAAwB,CAAC;AACvC,cAAc,oBAAoB,CAAC;AACnC,cAAc,oBAAoB,CAAC;AACnC,cAAc,iBAAiB,CAAC;AAChC,cAAc,qBAAqB,CAAC"}
@@ -1,6 +1,8 @@
1
- export * from "./storage-ops.js";
2
- export * from "./firebase-media-io.js";
3
- export * from "./pending-media.js";
4
- export * from "./staged-object.js";
5
- export * from "./gcs-errors.js";
1
+ export * from './storage-ops.js';
2
+ export { R2StorageError } from './r2-retry.js';
3
+ export * from './firebase-media-io.js';
4
+ export * from './pending-media.js';
5
+ export * from './staged-object.js';
6
+ export * from './gcs-errors.js';
7
+ export * from './download-token.js';
6
8
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/server/index.ts"],"names":[],"mappings":"AAAA,cAAc,kBAAkB,CAAC;AACjC,cAAc,wBAAwB,CAAC;AACvC,cAAc,oBAAoB,CAAC;AACnC,cAAc,oBAAoB,CAAC;AACnC,cAAc,iBAAiB,CAAC","sourcesContent":["export * from \"./storage-ops.js\";\nexport * from \"./firebase-media-io.js\";\nexport * from \"./pending-media.js\";\nexport * from \"./staged-object.js\";\nexport * from \"./gcs-errors.js\";\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/server/index.ts"],"names":[],"mappings":"AAAA,cAAc,kBAAkB,CAAC;AACjC,OAAO,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAC/C,cAAc,wBAAwB,CAAC;AACvC,cAAc,oBAAoB,CAAC;AACnC,cAAc,oBAAoB,CAAC;AACnC,cAAc,iBAAiB,CAAC;AAChC,cAAc,qBAAqB,CAAC","sourcesContent":["export * from './storage-ops.js';\nexport { R2StorageError } from './r2-retry.js';\nexport * from './firebase-media-io.js';\nexport * from './pending-media.js';\nexport * from './staged-object.js';\nexport * from './gcs-errors.js';\nexport * from './download-token.js';\n"]}
@@ -1,5 +1,5 @@
1
- import type { DocumentReference, DocumentSnapshot, Firestore, Transaction } from "firebase-admin/firestore";
2
- import { type ClaimablePendingMedia, type PendingMediaClaimOutcome, type PendingMediaClaimPolicy, type PendingMediaFinalizeResult, type PendingMediaTerminalStatus } from "@ttt-productions/media-schemas";
1
+ import type { DocumentReference, DocumentSnapshot, Firestore, Transaction } from 'firebase-admin/firestore';
2
+ import { type ClaimablePendingMedia, type PendingMediaClaimOutcome, type PendingMediaClaimPolicy, type PendingMediaFinalizeResult, type PendingMediaTerminalStatus } from '@ttt-productions/media-schemas';
3
3
  /** The app's pending-media row schema — anything with zod's `safeParse` shape. */
4
4
  export interface PendingMediaRowParser<TRow> {
5
5
  safeParse(data: unknown): {
@@ -1 +1 @@
1
- {"version":3,"file":"pending-media.d.ts","sourceRoot":"","sources":["../../src/server/pending-media.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAC;AAC5G,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,CAWzC;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,CAW9C;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,CAE9C;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,CAYf"}
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"}
@@ -4,7 +4,7 @@
4
4
  // decide, and write. Everything app-specific is injected: the row's `DocumentReference`
5
5
  // (so no collection name lives here), the row schema, the claim policy, the extra
6
6
  // writes a terminal transition carries, and the clock.
7
- import { buildPendingMediaTerminalFields, decidePendingMediaClaim, decidePendingMediaFinalize, isPendingMediaNonTerminalStatus, PendingMediaStatusSchema, } from "@ttt-productions/media-schemas";
7
+ import { buildPendingMediaTerminalFields, decidePendingMediaClaim, decidePendingMediaFinalize, isPendingMediaNonTerminalStatus, PendingMediaStatusSchema, } from '@ttt-productions/media-schemas';
8
8
  /**
9
9
  * Claim a row for processing inside `transaction`, driven by the CURRENT row (never a
10
10
  * frozen event snapshot). Reads before any write. A row that no longer parses is
@@ -13,10 +13,10 @@ import { buildPendingMediaTerminalFields, decidePendingMediaClaim, decidePending
13
13
  export async function claimPendingMediaForProcessing(transaction, pendingMediaRef, options) {
14
14
  const snap = await transaction.get(pendingMediaRef);
15
15
  if (!snap.exists)
16
- return { kind: "missing" };
16
+ return { kind: 'missing' };
17
17
  const parsed = options.schema.safeParse(snap.data());
18
18
  if (!parsed.success)
19
- return { kind: "missing" };
19
+ return { kind: 'missing' };
20
20
  const { outcome, write } = decidePendingMediaClaim(parsed.data, options.now ?? Date.now(), options.policy);
21
21
  // Spread into a literal: the Admin SDK's update() wants an UpdateData-compatible object.
22
22
  if (write)
@@ -32,9 +32,12 @@ export async function claimPendingMediaForProcessing(transaction, pendingMediaRe
32
32
  export async function finalizePendingMediaInTransaction(transaction, ref, targetStatus, options = {}) {
33
33
  const snap = await transaction.get(ref);
34
34
  const result = decidePendingMediaFinalize(snap.exists ? (snap.data() ?? {}) : undefined, targetStatus);
35
- if (result.decision !== "applied")
35
+ if (result.decision !== 'applied')
36
36
  return result;
37
- const applyExtra = await options.extraWrites?.(transaction, snap, { targetStatus, unparsable: false });
37
+ const applyExtra = await options.extraWrites?.(transaction, snap, {
38
+ targetStatus,
39
+ unparsable: false,
40
+ });
38
41
  transaction.update(ref, buildPendingMediaTerminalFields(targetStatus, options.extraFields ?? {}, options.now ?? Date.now()));
39
42
  return applyExtra ? { ...result, extra: applyExtra() } : result;
40
43
  }
@@ -57,7 +60,10 @@ export async function finalizeUnparsablePendingMedia(db, ref, options = {}) {
57
60
  if (status !== undefined && !isPendingMediaNonTerminalStatus(status))
58
61
  return;
59
62
  const failed = PendingMediaStatusSchema.enum.failed;
60
- const applyExtra = await options.extraWrites?.(transaction, snap, { targetStatus: failed, unparsable: true });
63
+ const applyExtra = await options.extraWrites?.(transaction, snap, {
64
+ targetStatus: failed,
65
+ unparsable: true,
66
+ });
61
67
  transaction.update(ref, buildPendingMediaTerminalFields(failed, options.extraFields ?? {}, options.now ?? Date.now()));
62
68
  applyExtra?.();
63
69
  });