@ttt-productions/media-processing-core 0.8.9 → 0.8.10

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.
@@ -22,11 +22,38 @@ export declare function readStagedUploadMetadata(bucket: Bucket, storagePath: st
22
22
  * The staged object's generation from metadata the caller already fetched, as a string
23
23
  * (GCS generations exceed 2^53) — the one normalization `readStagedObjectGeneration`
24
24
  * applies, for a processor that already holds the object's metadata and must not read
25
- * it again. `undefined` when the metadata carries no generation (the emulator may not).
25
+ * it again. `undefined` when the metadata carries no generation (the emulator may not):
26
+ * absent, `null`, or an empty string, which names no generation.
26
27
  */
27
28
  export declare function stagedObjectGenerationFromMetadata(metadata: {
28
29
  generation?: string | number | null;
29
30
  }): string | undefined;
31
+ /** The stored-object metadata field an `UnreadableStorageObjectMetadataError` names. */
32
+ export type StorageObjectMetadataField = "generation" | "size";
33
+ /**
34
+ * A storage object whose metadata has no readable generation or byte size. GCS, the Storage
35
+ * emulator, and a Storage finalize event always carry both, so this is an anomaly to surface,
36
+ * never to paper over with a placeholder. The bucket and key are PROPERTIES, never message
37
+ * text: an escaping error reaches monitoring, and an object key must not.
38
+ */
39
+ export declare class UnreadableStorageObjectMetadataError extends Error {
40
+ readonly bucket: string;
41
+ readonly key: string;
42
+ readonly field: StorageObjectMetadataField;
43
+ constructor(bucket: string, key: string, field: StorageObjectMetadataField);
44
+ }
45
+ /**
46
+ * The object's generation from metadata the caller already holds, for a site that cannot
47
+ * proceed without it (a hold key, an evidence record, a pinned read). Throws
48
+ * `UnreadableStorageObjectMetadataError` (field `generation`) when the metadata names none —
49
+ * the same normalization as `stagedObjectGenerationFromMetadata`, with no fallback value.
50
+ */
51
+ export declare function requireStorageObjectGeneration(metadata: {
52
+ generation?: string | number | null;
53
+ }, object: {
54
+ bucket: string;
55
+ key: string;
56
+ }): string;
30
57
  export interface ReadStagedObjectGenerationOptions {
31
58
  /** Called when the generation cannot be read (the caller logs it); reads then stay unpinned. */
32
59
  onReadFailure?: (error: unknown) => void;
@@ -1 +1 @@
1
- {"version":3,"file":"staged-object.d.ts","sourceRoot":"","sources":["../../src/server/staged-object.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAE/C,sFAAsF;AACtF,MAAM,WAAW,oBAAoB;IACnC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,IAAI,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IACvB,UAAU,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IAC7B,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC;CAC1B;AAED,MAAM,MAAM,wBAAwB,GAChC;IAAE,KAAK,EAAE,IAAI,CAAC;IAAC,QAAQ,EAAE,oBAAoB,CAAA;CAAE,GAC/C;IAAE,KAAK,EAAE,KAAK,CAAA;CAAE,CAAC;AAErB;;;;GAIG;AACH,wBAAsB,wBAAwB,CAAC,MAAM,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,wBAAwB,CAAC,CAQrH;AAED;;;;;GAKG;AACH,wBAAgB,kCAAkC,CAAC,QAAQ,EAAE;IAC3D,UAAU,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAAC;CACrC,GAAG,MAAM,GAAG,SAAS,CAGrB;AAED,MAAM,WAAW,iCAAiC;IAChD,gGAAgG;IAChG,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,IAAI,CAAC;CAC1C;AAED;;;;;;;;;;GAUG;AACH,wBAAsB,0BAA0B,CAC9C,MAAM,EAAE,MAAM,EACd,WAAW,EAAE,MAAM,GAAG,SAAS,EAC/B,OAAO,GAAE,iCAAsC,GAC9C,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAS7B;AAED;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,CAEnF"}
1
+ {"version":3,"file":"staged-object.d.ts","sourceRoot":"","sources":["../../src/server/staged-object.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAE/C,sFAAsF;AACtF,MAAM,WAAW,oBAAoB;IACnC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,IAAI,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IACvB,UAAU,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IAC7B,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC;CAC1B;AAED,MAAM,MAAM,wBAAwB,GAChC;IAAE,KAAK,EAAE,IAAI,CAAC;IAAC,QAAQ,EAAE,oBAAoB,CAAA;CAAE,GAC/C;IAAE,KAAK,EAAE,KAAK,CAAA;CAAE,CAAC;AAErB;;;;GAIG;AACH,wBAAsB,wBAAwB,CAAC,MAAM,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,wBAAwB,CAAC,CAQrH;AAED;;;;;;GAMG;AACH,wBAAgB,kCAAkC,CAAC,QAAQ,EAAE;IAC3D,UAAU,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAAC;CACrC,GAAG,MAAM,GAAG,SAAS,CAKrB;AAED,wFAAwF;AACxF,MAAM,MAAM,0BAA0B,GAAG,YAAY,GAAG,MAAM,CAAC;AAE/D;;;;;GAKG;AACH,qBAAa,oCAAqC,SAAQ,KAAK;aAE3C,MAAM,EAAE,MAAM;aACd,GAAG,EAAE,MAAM;aACX,KAAK,EAAE,0BAA0B;gBAFjC,MAAM,EAAE,MAAM,EACd,GAAG,EAAE,MAAM,EACX,KAAK,EAAE,0BAA0B;CAKpD;AAED;;;;;GAKG;AACH,wBAAgB,8BAA8B,CAC5C,QAAQ,EAAE;IAAE,UAAU,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAAA;CAAE,EACjD,MAAM,EAAE;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,GACtC,MAAM,CAMR;AAED,MAAM,WAAW,iCAAiC;IAChD,gGAAgG;IAChG,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,IAAI,CAAC;CAC1C;AAED;;;;;;;;;;GAUG;AACH,wBAAsB,0BAA0B,CAC9C,MAAM,EAAE,MAAM,EACd,WAAW,EAAE,MAAM,GAAG,SAAS,EAC/B,OAAO,GAAE,iCAAsC,GAC9C,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAS7B;AAED;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,CAEnF"}
@@ -22,11 +22,46 @@ export async function readStagedUploadMetadata(bucket, storagePath) {
22
22
  * The staged object's generation from metadata the caller already fetched, as a string
23
23
  * (GCS generations exceed 2^53) — the one normalization `readStagedObjectGeneration`
24
24
  * applies, for a processor that already holds the object's metadata and must not read
25
- * it again. `undefined` when the metadata carries no generation (the emulator may not).
25
+ * it again. `undefined` when the metadata carries no generation (the emulator may not):
26
+ * absent, `null`, or an empty string, which names no generation.
26
27
  */
27
28
  export function stagedObjectGenerationFromMetadata(metadata) {
28
29
  const { generation } = metadata;
29
- return generation === undefined || generation === null ? undefined : String(generation);
30
+ if (generation === undefined || generation === null)
31
+ return undefined;
32
+ const value = String(generation);
33
+ return value === "" ? undefined : value;
34
+ }
35
+ /**
36
+ * A storage object whose metadata has no readable generation or byte size. GCS, the Storage
37
+ * emulator, and a Storage finalize event always carry both, so this is an anomaly to surface,
38
+ * never to paper over with a placeholder. The bucket and key are PROPERTIES, never message
39
+ * text: an escaping error reaches monitoring, and an object key must not.
40
+ */
41
+ export class UnreadableStorageObjectMetadataError extends Error {
42
+ bucket;
43
+ key;
44
+ field;
45
+ constructor(bucket, key, field) {
46
+ super(`storage object metadata has no readable ${field}`);
47
+ this.bucket = bucket;
48
+ this.key = key;
49
+ this.field = field;
50
+ this.name = "UnreadableStorageObjectMetadataError";
51
+ }
52
+ }
53
+ /**
54
+ * The object's generation from metadata the caller already holds, for a site that cannot
55
+ * proceed without it (a hold key, an evidence record, a pinned read). Throws
56
+ * `UnreadableStorageObjectMetadataError` (field `generation`) when the metadata names none —
57
+ * the same normalization as `stagedObjectGenerationFromMetadata`, with no fallback value.
58
+ */
59
+ export function requireStorageObjectGeneration(metadata, object) {
60
+ const generation = stagedObjectGenerationFromMetadata(metadata);
61
+ if (generation === undefined) {
62
+ throw new UnreadableStorageObjectMetadataError(object.bucket, object.key, "generation");
63
+ }
64
+ return generation;
30
65
  }
31
66
  /**
32
67
  * The staged object's current generation, as a string (GCS generations exceed 2^53).
@@ -1 +1 @@
1
- {"version":3,"file":"staged-object.js","sourceRoot":"","sources":["../../src/server/staged-object.ts"],"names":[],"mappings":"AAAA,+EAA+E;AAC/E,+EAA+E;AAC/E,oEAAoE;AAEpE,OAAO,EAAE,qBAAqB,EAAE,MAAM,iBAAiB,CAAC;AAexD;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,wBAAwB,CAAC,MAAc,EAAE,WAAmB;IAChF,IAAI,CAAC;QACH,MAAM,CAAC,QAAQ,CAAC,GAAG,MAAM,MAAM,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC,WAAW,EAAE,CAAC;QAChE,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAC;IACnC,CAAC;IAAC,OAAO,CAAC,EAAE,CAAC;QACX,IAAI,qBAAqB,CAAC,CAAC,CAAC;YAAE,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC;QACtD,MAAM,CAAC,CAAC;IACV,CAAC;AACH,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,kCAAkC,CAAC,QAElD;IACC,MAAM,EAAE,UAAU,EAAE,GAAG,QAAQ,CAAC;IAChC,OAAO,UAAU,KAAK,SAAS,IAAI,UAAU,KAAK,IAAI,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC;AAC1F,CAAC;AAOD;;;;;;;;;;GAUG;AACH,MAAM,CAAC,KAAK,UAAU,0BAA0B,CAC9C,MAAc,EACd,WAA+B,EAC/B,UAA6C,EAAE;IAE/C,IAAI,CAAC,WAAW;QAAE,OAAO,SAAS,CAAC;IACnC,IAAI,CAAC;QACH,MAAM,CAAC,QAAQ,CAAC,GAAG,MAAM,MAAM,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC,WAAW,EAAE,CAAC;QAChE,OAAO,kCAAkC,CAAC,QAAQ,CAAC,CAAC;IACtD,CAAC;IAAC,OAAO,CAAC,EAAE,CAAC;QACX,OAAO,CAAC,aAAa,EAAE,CAAC,CAAC,CAAC,CAAC;QAC3B,OAAO,SAAS,CAAC;IACnB,CAAC;AACH,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,YAAY,CAAC,MAAc,EAAE,UAA8B;IACzE,OAAO,UAAU,CAAC,CAAC,CAAC,GAAG,MAAM,IAAI,UAAU,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC;AACzD,CAAC","sourcesContent":["// Admin-SDK reads of a STAGED upload object (the client-written staging copy a\n// processor reads from). The accept-time verdict over the metadata is the pure\n// `verifyStagedUploadMetadata` in `@ttt-productions/media-schemas`.\n\nimport { isObjectNotFoundError } from \"./gcs-errors.js\";\nimport type { Bucket } from \"./storage-ops.js\";\n\n/** The stored-object metadata fields these reads use (the Admin SDK returns more). */\nexport interface StagedObjectMetadata {\n contentType?: string;\n size?: string | number;\n generation?: string | number;\n [field: string]: unknown;\n}\n\nexport type StagedUploadMetadataRead =\n | { found: true; metadata: StagedObjectMetadata }\n | { found: false };\n\n/**\n * Read a staged object's stored metadata (`contentType`, `size`, `generation`, …) for\n * `verifyStagedUploadMetadata`. A missing object is `{ found: false }`; any other read\n * failure throws.\n */\nexport async function readStagedUploadMetadata(bucket: Bucket, storagePath: string): Promise<StagedUploadMetadataRead> {\n try {\n const [metadata] = await bucket.file(storagePath).getMetadata();\n return { found: true, metadata };\n } catch (e) {\n if (isObjectNotFoundError(e)) return { found: false };\n throw e;\n }\n}\n\n/**\n * The staged object's generation from metadata the caller already fetched, as a string\n * (GCS generations exceed 2^53) — the one normalization `readStagedObjectGeneration`\n * applies, for a processor that already holds the object's metadata and must not read\n * it again. `undefined` when the metadata carries no generation (the emulator may not).\n */\nexport function stagedObjectGenerationFromMetadata(metadata: {\n generation?: string | number | null;\n}): string | undefined {\n const { generation } = metadata;\n return generation === undefined || generation === null ? undefined : String(generation);\n}\n\nexport interface ReadStagedObjectGenerationOptions {\n /** Called when the generation cannot be read (the caller logs it); reads then stay unpinned. */\n onReadFailure?: (error: unknown) => void;\n}\n\n/**\n * The staged object's current generation, as a string (GCS generations exceed 2^53).\n *\n * This is the one value that pins every later read of the staged bytes — the processing\n * download, a moderation scan, a hash — to the SAME immutable object, so \"processed\n * bytes == scanned bytes == hashed bytes\" holds even if the staging path were\n * overwritten (a pinned read resolves exactly the captured generation). Returns\n * `undefined` when no path is given, the object reports no generation (the emulator may\n * not), or the read fails — best effort: callers fall back to unpinned reads, and a\n * create-only staging rule stays the primary defense.\n */\nexport async function readStagedObjectGeneration(\n bucket: Bucket,\n storagePath: string | undefined,\n options: ReadStagedObjectGenerationOptions = {},\n): Promise<string | undefined> {\n if (!storagePath) return undefined;\n try {\n const [metadata] = await bucket.file(storagePath).getMetadata();\n return stagedObjectGenerationFromMetadata(metadata);\n } catch (e) {\n options.onReadFailure?.(e);\n return undefined;\n }\n}\n\n/**\n * A `gs://` URI pinned to a captured generation (`gs://bucket/path#<generation>`), so a\n * service reading by URI (a moderation scan) reads the same bytes the processor did.\n * Without a generation it is the bare URI.\n */\nexport function pinnedGcsUri(gcsUri: string, generation: string | undefined): string {\n return generation ? `${gcsUri}#${generation}` : gcsUri;\n}\n"]}
1
+ {"version":3,"file":"staged-object.js","sourceRoot":"","sources":["../../src/server/staged-object.ts"],"names":[],"mappings":"AAAA,+EAA+E;AAC/E,+EAA+E;AAC/E,oEAAoE;AAEpE,OAAO,EAAE,qBAAqB,EAAE,MAAM,iBAAiB,CAAC;AAexD;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,wBAAwB,CAAC,MAAc,EAAE,WAAmB;IAChF,IAAI,CAAC;QACH,MAAM,CAAC,QAAQ,CAAC,GAAG,MAAM,MAAM,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC,WAAW,EAAE,CAAC;QAChE,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAC;IACnC,CAAC;IAAC,OAAO,CAAC,EAAE,CAAC;QACX,IAAI,qBAAqB,CAAC,CAAC,CAAC;YAAE,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC;QACtD,MAAM,CAAC,CAAC;IACV,CAAC;AACH,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,kCAAkC,CAAC,QAElD;IACC,MAAM,EAAE,UAAU,EAAE,GAAG,QAAQ,CAAC;IAChC,IAAI,UAAU,KAAK,SAAS,IAAI,UAAU,KAAK,IAAI;QAAE,OAAO,SAAS,CAAC;IACtE,MAAM,KAAK,GAAG,MAAM,CAAC,UAAU,CAAC,CAAC;IACjC,OAAO,KAAK,KAAK,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,KAAK,CAAC;AAC1C,CAAC;AAKD;;;;;GAKG;AACH,MAAM,OAAO,oCAAqC,SAAQ,KAAK;IAE3C;IACA;IACA;IAHlB,YACkB,MAAc,EACd,GAAW,EACX,KAAiC;QAEjD,KAAK,CAAC,2CAA2C,KAAK,EAAE,CAAC,CAAC;QAJ1C,WAAM,GAAN,MAAM,CAAQ;QACd,QAAG,GAAH,GAAG,CAAQ;QACX,UAAK,GAAL,KAAK,CAA4B;QAGjD,IAAI,CAAC,IAAI,GAAG,sCAAsC,CAAC;IACrD,CAAC;CACF;AAED;;;;;GAKG;AACH,MAAM,UAAU,8BAA8B,CAC5C,QAAiD,EACjD,MAAuC;IAEvC,MAAM,UAAU,GAAG,kCAAkC,CAAC,QAAQ,CAAC,CAAC;IAChE,IAAI,UAAU,KAAK,SAAS,EAAE,CAAC;QAC7B,MAAM,IAAI,oCAAoC,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,EAAE,YAAY,CAAC,CAAC;IAC1F,CAAC;IACD,OAAO,UAAU,CAAC;AACpB,CAAC;AAOD;;;;;;;;;;GAUG;AACH,MAAM,CAAC,KAAK,UAAU,0BAA0B,CAC9C,MAAc,EACd,WAA+B,EAC/B,UAA6C,EAAE;IAE/C,IAAI,CAAC,WAAW;QAAE,OAAO,SAAS,CAAC;IACnC,IAAI,CAAC;QACH,MAAM,CAAC,QAAQ,CAAC,GAAG,MAAM,MAAM,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC,WAAW,EAAE,CAAC;QAChE,OAAO,kCAAkC,CAAC,QAAQ,CAAC,CAAC;IACtD,CAAC;IAAC,OAAO,CAAC,EAAE,CAAC;QACX,OAAO,CAAC,aAAa,EAAE,CAAC,CAAC,CAAC,CAAC;QAC3B,OAAO,SAAS,CAAC;IACnB,CAAC;AACH,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,YAAY,CAAC,MAAc,EAAE,UAA8B;IACzE,OAAO,UAAU,CAAC,CAAC,CAAC,GAAG,MAAM,IAAI,UAAU,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC;AACzD,CAAC","sourcesContent":["// Admin-SDK reads of a STAGED upload object (the client-written staging copy a\n// processor reads from). The accept-time verdict over the metadata is the pure\n// `verifyStagedUploadMetadata` in `@ttt-productions/media-schemas`.\n\nimport { isObjectNotFoundError } from \"./gcs-errors.js\";\nimport type { Bucket } from \"./storage-ops.js\";\n\n/** The stored-object metadata fields these reads use (the Admin SDK returns more). */\nexport interface StagedObjectMetadata {\n contentType?: string;\n size?: string | number;\n generation?: string | number;\n [field: string]: unknown;\n}\n\nexport type StagedUploadMetadataRead =\n | { found: true; metadata: StagedObjectMetadata }\n | { found: false };\n\n/**\n * Read a staged object's stored metadata (`contentType`, `size`, `generation`, …) for\n * `verifyStagedUploadMetadata`. A missing object is `{ found: false }`; any other read\n * failure throws.\n */\nexport async function readStagedUploadMetadata(bucket: Bucket, storagePath: string): Promise<StagedUploadMetadataRead> {\n try {\n const [metadata] = await bucket.file(storagePath).getMetadata();\n return { found: true, metadata };\n } catch (e) {\n if (isObjectNotFoundError(e)) return { found: false };\n throw e;\n }\n}\n\n/**\n * The staged object's generation from metadata the caller already fetched, as a string\n * (GCS generations exceed 2^53) — the one normalization `readStagedObjectGeneration`\n * applies, for a processor that already holds the object's metadata and must not read\n * it again. `undefined` when the metadata carries no generation (the emulator may not):\n * absent, `null`, or an empty string, which names no generation.\n */\nexport function stagedObjectGenerationFromMetadata(metadata: {\n generation?: string | number | null;\n}): string | undefined {\n const { generation } = metadata;\n if (generation === undefined || generation === null) return undefined;\n const value = String(generation);\n return value === \"\" ? undefined : value;\n}\n\n/** The stored-object metadata field an `UnreadableStorageObjectMetadataError` names. */\nexport type StorageObjectMetadataField = \"generation\" | \"size\";\n\n/**\n * A storage object whose metadata has no readable generation or byte size. GCS, the Storage\n * emulator, and a Storage finalize event always carry both, so this is an anomaly to surface,\n * never to paper over with a placeholder. The bucket and key are PROPERTIES, never message\n * text: an escaping error reaches monitoring, and an object key must not.\n */\nexport class UnreadableStorageObjectMetadataError extends Error {\n constructor(\n public readonly bucket: string,\n public readonly key: string,\n public readonly field: StorageObjectMetadataField,\n ) {\n super(`storage object metadata has no readable ${field}`);\n this.name = \"UnreadableStorageObjectMetadataError\";\n }\n}\n\n/**\n * The object's generation from metadata the caller already holds, for a site that cannot\n * proceed without it (a hold key, an evidence record, a pinned read). Throws\n * `UnreadableStorageObjectMetadataError` (field `generation`) when the metadata names none —\n * the same normalization as `stagedObjectGenerationFromMetadata`, with no fallback value.\n */\nexport function requireStorageObjectGeneration(\n metadata: { generation?: string | number | null },\n object: { bucket: string; key: string },\n): string {\n const generation = stagedObjectGenerationFromMetadata(metadata);\n if (generation === undefined) {\n throw new UnreadableStorageObjectMetadataError(object.bucket, object.key, \"generation\");\n }\n return generation;\n}\n\nexport interface ReadStagedObjectGenerationOptions {\n /** Called when the generation cannot be read (the caller logs it); reads then stay unpinned. */\n onReadFailure?: (error: unknown) => void;\n}\n\n/**\n * The staged object's current generation, as a string (GCS generations exceed 2^53).\n *\n * This is the one value that pins every later read of the staged bytes — the processing\n * download, a moderation scan, a hash — to the SAME immutable object, so \"processed\n * bytes == scanned bytes == hashed bytes\" holds even if the staging path were\n * overwritten (a pinned read resolves exactly the captured generation). Returns\n * `undefined` when no path is given, the object reports no generation (the emulator may\n * not), or the read fails — best effort: callers fall back to unpinned reads, and a\n * create-only staging rule stays the primary defense.\n */\nexport async function readStagedObjectGeneration(\n bucket: Bucket,\n storagePath: string | undefined,\n options: ReadStagedObjectGenerationOptions = {},\n): Promise<string | undefined> {\n if (!storagePath) return undefined;\n try {\n const [metadata] = await bucket.file(storagePath).getMetadata();\n return stagedObjectGenerationFromMetadata(metadata);\n } catch (e) {\n options.onReadFailure?.(e);\n return undefined;\n }\n}\n\n/**\n * A `gs://` URI pinned to a captured generation (`gs://bucket/path#<generation>`), so a\n * service reading by URI (a moderation scan) reads the same bytes the processor did.\n * Without a generation it is the bare URI.\n */\nexport function pinnedGcsUri(gcsUri: string, generation: string | undefined): string {\n return generation ? `${gcsUri}#${generation}` : gcsUri;\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ttt-productions/media-processing-core",
3
- "version": "0.8.9",
3
+ "version": "0.8.10",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+https://github.com/ttt-productions/ttt-packages.git",
@@ -31,7 +31,7 @@
31
31
  "prepublishOnly": "npm run clean && npm run build"
32
32
  },
33
33
  "dependencies": {
34
- "@ttt-productions/media-schemas": "^0.14.6",
34
+ "@ttt-productions/media-schemas": "^0.14.7",
35
35
  "aws4fetch": "^1.0.20",
36
36
  "sharp": "^0.35.3",
37
37
  "zod": "^4.4.3"