@hyperframes/gcp-cloud-run 0.6.79

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.
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/server.ts", "../src/chromium.ts", "../src/formatExtension.ts", "../src/gcsTransport.ts", "../src/sdk/deploySite.ts", "../src/sdk/renderToCloudRun.ts", "../src/sdk/validateConfig.ts", "../src/sdk/costAccounting.ts", "../src/sdk/getRenderProgress.ts"],
4
+ "sourcesContent": ["/**\n * Cloud Run request handler for HyperFrames distributed rendering.\n *\n * One container image, three roles. Cloud Workflows POSTs a JSON body with\n * an `Action` field; the handler unwraps any `Payload`/`Input` envelope,\n * primes the runtime (Chrome path), and forwards to the matching OSS\n * primitive from `@hyperframes/producer/distributed`.\n *\n * Everything heavy \u2014 capture, encode, audio mix \u2014 happens inside the OSS\n * primitives. The handler is thin glue: parse body \u2192 GCS download \u2192 call\n * primitive \u2192 GCS upload \u2192 return small JSON result.\n *\n * `dispatch()` is the testable core (inject `storage` + `primitives`); the\n * Hono app at the bottom is the HTTP shell the Dockerfile runs. The shape\n * deliberately tracks `@hyperframes/aws-lambda`'s `handler.ts` so the two\n * adapters stay easy to diff.\n */\n\nimport { existsSync, mkdirSync, mkdtempSync, readFileSync, rmSync, statSync } from \"node:fs\";\nimport { tmpdir } from \"node:os\";\nimport { basename, extname, join } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { serve } from \"@hono/node-server\";\nimport { Storage } from \"@google-cloud/storage\";\nimport { Hono } from \"hono\";\nimport {\n assemble,\n type AssembleResult,\n type ChunkResult,\n type DistributedRenderConfig,\n plan,\n type PlanResult,\n renderChunk,\n} from \"@hyperframes/producer/distributed\";\nimport { resolveChromeExecutablePath } from \"./chromium.js\";\nimport type {\n AssembleEvent,\n AssembleResultBody,\n CloudRunAction,\n CloudRunEvent,\n CloudRunResult,\n PlanEvent,\n PlanResultBody,\n RenderChunkEvent,\n RenderChunkResultBody,\n} from \"./events.js\";\nimport { type DistributedFormat, formatExtension } from \"./formatExtension.js\";\nimport {\n downloadGcsObjectToFile,\n parseGcsUri,\n tarDirectory,\n untarDirectory,\n uploadFileToGcs,\n} from \"./gcsTransport.js\";\n\n/**\n * Lazily-constructed Storage client. Cached at module scope so warm\n * container instances reuse the underlying HTTP keep-alive pool across\n * requests.\n */\nlet cachedStorage: Storage | null = null;\nfunction getStorage(): Storage {\n if (cachedStorage) return cachedStorage;\n cachedStorage = new Storage();\n return cachedStorage;\n}\n\n/**\n * Optional injection points used by the handler's unit tests. Production\n * callers leave these unset; the real OSS primitives are used. Tests inject\n * `storage` and `primitives` directly rather than mutating module state.\n */\nexport interface HandlerDeps {\n storage?: Storage;\n primitives?: {\n plan: typeof plan;\n renderChunk: typeof renderChunk;\n assemble: typeof assemble;\n };\n /** Override the per-request workdir root (defaults to the OS tmpdir). */\n tmpRoot?: string;\n /** Skip Chrome resolution (used by dispatch tests that mock renderChunk). */\n skipChromeResolution?: boolean;\n}\n\n/**\n * Dispatch a single render request. Cloud Workflows (or a direct caller)\n * sometimes wraps the body in `{ Payload: ... }` or `{ Input: ... }`; unwrap\n * until we hit a discriminated event.\n */\n// fallow-ignore-next-line complexity\nexport async function dispatch(event: CloudRunEvent, deps?: HandlerDeps): Promise<CloudRunResult> {\n const unwrapped = unwrapEvent(event);\n validateEventGcsUris(unwrapped);\n logEvent({ event: \"handler_start\", action: unwrapped.Action, input: summarizeEvent(unwrapped) });\n try {\n switch (unwrapped.Action) {\n case \"plan\":\n return await handlePlan(unwrapped, deps);\n case \"renderChunk\":\n return await handleRenderChunk(unwrapped, deps);\n case \"assemble\":\n return await handleAssemble(unwrapped, deps);\n default: {\n // Compile-time exhaustiveness: a new CloudRunAction member trips\n // the `never` assignment before the runtime error is reachable.\n const _exhaustive: never = unwrapped;\n throw new Error(\n `[handler] unknown Action: ${JSON.stringify(\n (_exhaustive as { Action?: string }).Action,\n )}. Expected one of \"plan\", \"renderChunk\", \"assemble\".`,\n );\n }\n }\n } catch (err) {\n logEvent({\n event: \"handler_error\",\n action: unwrapped.Action,\n message: err instanceof Error ? err.message : String(err),\n name: err instanceof Error ? err.name : undefined,\n });\n throw err;\n }\n}\n\n// At most `{Payload: {Input: ...}}` is expected; 4 levels is 2\u00D7 headroom\n// and prevents infinite loops on malformed input.\nconst MAX_ENVELOPE_DEPTH = 4;\n\n// fallow-ignore-next-line complexity\nexport function unwrapEvent(event: CloudRunEvent): PlanEvent | RenderChunkEvent | AssembleEvent {\n let cursor: CloudRunEvent = event;\n for (let i = 0; i < MAX_ENVELOPE_DEPTH; i++) {\n if (cursor && typeof cursor === \"object\") {\n const obj = cursor as Record<string, unknown>;\n if (typeof obj.Action === \"string\" && isCloudRunAction(obj.Action)) {\n return cursor as PlanEvent | RenderChunkEvent | AssembleEvent;\n }\n if (\"Payload\" in obj) {\n cursor = obj.Payload as CloudRunEvent;\n continue;\n }\n if (\"Input\" in obj) {\n cursor = obj.Input as CloudRunEvent;\n continue;\n }\n }\n break;\n }\n throw new Error(\n `[handler] body has no recognised Action; unwrapped ${MAX_ENVELOPE_DEPTH} levels of Payload/Input without finding one.`,\n );\n}\n\nfunction isCloudRunAction(value: string): value is CloudRunAction {\n return value === \"plan\" || value === \"renderChunk\" || value === \"assemble\";\n}\n\n/**\n * Emit a single JSON line to stdout. Cloud Logging ingests each stdout line\n * as a structured `jsonPayload` entry, so Logs Explorer can filter on\n * `jsonPayload.event=\"handler_start\"` and project specific fields when\n * triaging without attaching a debugger.\n */\nfunction logEvent(payload: Record<string, unknown>): void {\n console.log(JSON.stringify(payload));\n}\n\n/**\n * Compact, non-PII summary of an event for logging. The full body can\n * include the entire project config; we only emit the routable fields\n * needed to triage a failure from Cloud Logging.\n */\nfunction summarizeEvent(\n event: PlanEvent | RenderChunkEvent | AssembleEvent,\n): Record<string, unknown> {\n switch (event.Action) {\n case \"plan\":\n return {\n projectGcsUri: event.ProjectGcsUri,\n planOutputGcsPrefix: event.PlanOutputGcsPrefix,\n format: event.Config.format,\n fps: event.Config.fps,\n };\n case \"renderChunk\":\n return {\n planGcsUri: event.PlanGcsUri,\n chunkIndex: event.ChunkIndex,\n format: event.Format,\n };\n case \"assemble\":\n return {\n planGcsUri: event.PlanGcsUri,\n chunkCount: event.ChunkGcsUris.length,\n hasAudio: event.AudioGcsUri !== null,\n outputGcsUri: event.OutputGcsUri,\n format: event.Format,\n };\n }\n}\n\n/**\n * Point the engine at the in-image Chrome binary. The OSS engine resolves\n * Chrome via `PRODUCER_HEADLESS_SHELL_PATH` first; set it once per instance\n * before invoking any browser-touching primitive. ffmpeg is on the image's\n * PATH (apt-installed by the Dockerfile), so nothing to prime there.\n */\nfunction primeChrome(deps?: HandlerDeps): void {\n if (deps?.skipChromeResolution) return;\n if (process.env.PRODUCER_HEADLESS_SHELL_PATH) return;\n process.env.PRODUCER_HEADLESS_SHELL_PATH = resolveChromeExecutablePath();\n}\n\n// \u2500\u2500 Plan \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n// fallow-ignore-next-line complexity\nasync function handlePlan(event: PlanEvent, deps?: HandlerDeps): Promise<PlanResultBody> {\n const started = Date.now();\n const storage = deps?.storage ?? getStorage();\n const primitive = deps?.primitives?.plan ?? plan;\n\n // The producer's probe stage launches Chromium whenever the composition\n // needs a runtime duration probe or has unresolved sub-compositions, so\n // plan has to resolve Chrome the same way renderChunk does.\n primeChrome(deps);\n\n const work = mkdtempSync(join(deps?.tmpRoot ?? tmpdir(), \"hf-cr-plan-\"));\n const projectArchive = join(work, \"project.tar.gz\");\n const projectDir = join(work, \"project\");\n const planDir = join(work, \"plan\");\n\n try {\n await downloadGcsObjectToFile(storage, event.ProjectGcsUri, projectArchive);\n await untarDirectory(projectArchive, projectDir);\n\n const config: DistributedRenderConfig = {\n ...event.Config,\n };\n const result: PlanResult = await primitive(projectDir, config, planDir);\n\n // Upload the planDir as a single tarball. The workflow cannot pass a\n // directory-shaped artifact between steps; we serialize and rely on the\n // consumer (renderChunk / assemble) to untar. `audio.aac` lives inside\n // planDir, so it already rides along in this tarball \u2014 every consumer\n // (including assemble) gets it from the untar. We deliberately do NOT\n // upload a separate audio object: it would duplicate the bytes on every\n // plan upload and be re-downloaded + overwritten by assemble. `AudioGcsUri`\n // stays in the result shape for wire compatibility but is null.\n const planTar = join(work, \"plan.tar.gz\");\n await tarDirectory(planDir, planTar);\n const planTarUri = `${trimTrailingSlash(event.PlanOutputGcsPrefix)}/plan.tar.gz`;\n const audioPath = join(planDir, \"audio.aac\");\n const hasAudio = existsSync(audioPath) && statSync(audioPath).size > 0;\n await uploadFileToGcs(storage, planTar, planTarUri, \"application/gzip\");\n\n return {\n Action: \"plan\",\n PlanGcsUri: planTarUri,\n PlanHash: result.planHash,\n ChunkCount: result.chunkCount,\n TotalFrames: result.totalFrames,\n Fps: result.fps,\n Width: result.width,\n Height: result.height,\n Format: result.format,\n HasAudio: hasAudio,\n AudioGcsUri: null,\n FfmpegVersion: result.ffmpegVersion,\n ProducerVersion: result.producerVersion,\n DurationMs: Date.now() - started,\n };\n } finally {\n cleanupDir(work);\n }\n}\n\n// \u2500\u2500 RenderChunk \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n// fallow-ignore-next-line complexity\nasync function handleRenderChunk(\n event: RenderChunkEvent,\n deps?: HandlerDeps,\n): Promise<RenderChunkResultBody> {\n const started = Date.now();\n const storage = deps?.storage ?? getStorage();\n const primitive = deps?.primitives?.renderChunk ?? renderChunk;\n\n primeChrome(deps);\n\n const work = mkdtempSync(join(deps?.tmpRoot ?? tmpdir(), \"hf-cr-chunk-\"));\n const planTar = join(work, \"plan.tar.gz\");\n const planDir = join(work, \"plan\");\n\n try {\n await downloadGcsObjectToFile(storage, event.PlanGcsUri, planTar);\n await untarDirectory(planTar, planDir);\n\n // Verify the plan's hash matches what the workflow told us to render.\n // The producer's renderChunk re-checks internally (defense-in-depth),\n // but doing it here at the handler boundary lets us fail before paying\n // the Chrome-launch + render cost on a misrouted chunk. Throws a typed\n // PLAN_HASH_MISMATCH the workflow can route as non-retryable.\n verifyPlanHash(planDir, event.PlanHash);\n\n const chunkOutputBase = join(\n work,\n event.Format === \"png-sequence\"\n ? `chunk-${pad(event.ChunkIndex)}`\n : `chunk-${pad(event.ChunkIndex)}${formatExtension(event.Format)}`,\n );\n\n const result: ChunkResult = await primitive(planDir, event.ChunkIndex, chunkOutputBase);\n\n const chunkUri = await uploadChunkOutput(\n storage,\n result,\n event.ChunkOutputGcsPrefix,\n event.ChunkIndex,\n );\n\n return {\n Action: \"renderChunk\",\n ChunkGcsUri: chunkUri,\n ChunkIndex: event.ChunkIndex,\n Sha256: result.sha256,\n FramesEncoded: result.framesEncoded,\n DurationMs: Date.now() - started,\n };\n } finally {\n cleanupDir(work);\n }\n}\n\nasync function uploadChunkOutput(\n storage: Storage,\n result: ChunkResult,\n prefix: string,\n chunkIndex: number,\n): Promise<string> {\n const trimmed = trimTrailingSlash(prefix);\n if (result.outputKind === \"file\") {\n const ext = extname(result.outputPath);\n const uri = `${trimmed}/chunks/${pad(chunkIndex)}${ext}`;\n await uploadFileToGcs(storage, result.outputPath, uri);\n return uri;\n }\n // frame-dir: upload as a tarball so a single GCS object represents the\n // chunk. Assemble's png-sequence path expects a directory per chunk; it\n // untars on its end.\n const tarball = `${result.outputPath}.tar.gz`;\n await tarDirectory(result.outputPath, tarball);\n const uri = `${trimmed}/chunks/${pad(chunkIndex)}.tar.gz`;\n await uploadFileToGcs(storage, tarball, uri, \"application/gzip\");\n return uri;\n}\n\n// \u2500\u2500 Assemble \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n// fallow-ignore-next-line complexity\nasync function handleAssemble(\n event: AssembleEvent,\n deps?: HandlerDeps,\n): Promise<AssembleResultBody> {\n const started = Date.now();\n const storage = deps?.storage ?? getStorage();\n const primitive = deps?.primitives?.assemble ?? assemble;\n\n const work = mkdtempSync(join(deps?.tmpRoot ?? tmpdir(), \"hf-cr-assemble-\"));\n const planTar = join(work, \"plan.tar.gz\");\n const planDir = join(work, \"plan\");\n\n try {\n await downloadGcsObjectToFile(storage, event.PlanGcsUri, planTar);\n await untarDirectory(planTar, planDir);\n\n const chunkPaths = await downloadChunkObjects(storage, event.ChunkGcsUris, work, event.Format);\n\n // Audio rides inside the plan tarball, so it's already on disk after the\n // untar above \u2014 no separate download. Fall back to a supplied AudioGcsUri\n // only for backward compatibility with an older Plan that uploaded it\n // standalone.\n let audioPath: string | null = null;\n const planAudio = join(planDir, \"audio.aac\");\n if (existsSync(planAudio) && statSync(planAudio).size > 0) {\n audioPath = planAudio;\n } else if (event.AudioGcsUri) {\n audioPath = planAudio;\n await downloadGcsObjectToFile(storage, event.AudioGcsUri, audioPath);\n }\n\n const finalOutput =\n event.Format === \"png-sequence\"\n ? join(work, \"output-frames\")\n : join(work, `output${formatExtension(event.Format)}`);\n\n const result: AssembleResult = await primitive(planDir, chunkPaths, audioPath, finalOutput, {\n cfr: event.Cfr === true,\n });\n\n if (event.Format === \"png-sequence\") {\n const tarball = `${finalOutput}.tar.gz`;\n await tarDirectory(finalOutput, tarball);\n await uploadFileToGcs(storage, tarball, event.OutputGcsUri, \"application/gzip\");\n } else {\n await uploadFileToGcs(storage, finalOutput, event.OutputGcsUri);\n }\n\n return {\n Action: \"assemble\",\n OutputGcsUri: event.OutputGcsUri,\n FramesEncoded: result.framesEncoded,\n FileSize: result.fileSize,\n DurationMs: Date.now() - started,\n };\n } finally {\n cleanupDir(work);\n }\n}\n\nasync function downloadChunkObjects(\n storage: Storage,\n uris: string[],\n workDir: string,\n format: DistributedFormat,\n): Promise<string[]> {\n const chunksDir = join(workDir, \"chunks\");\n mkdirSync(chunksDir, { recursive: true });\n // Each chunk is an independent GCS GET (+ untar for png-sequence). Run\n // them in parallel \u2014 assemble's wall-clock is otherwise dominated by\n // `\u03A3 chunk-download-ms` instead of `max(chunk-download-ms)`. Preserve the\n // input order by writing into a pre-sized array rather than pushing as\n // each task settles.\n const local: string[] = new Array<string>(uris.length);\n await Promise.all(\n uris.map(async (uri, i) => {\n if (!uri) {\n throw new Error(`[handler] chunk URI at index ${i} is empty`);\n }\n const { key } = parseGcsUri(uri);\n const localPath = join(chunksDir, basename(key));\n await downloadGcsObjectToFile(storage, uri, localPath);\n if (format === \"png-sequence\") {\n const dirPath = join(chunksDir, `frames-${pad(i)}`);\n await untarDirectory(localPath, dirPath);\n local[i] = dirPath;\n } else {\n local[i] = localPath;\n }\n }),\n );\n return local;\n}\n\n// \u2500\u2500 Helpers \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n/** Collect every GCS URI that the handler will touch for a given event. */\nfunction getEventGcsUris(event: PlanEvent | RenderChunkEvent | AssembleEvent): string[] {\n switch (event.Action) {\n case \"plan\":\n return [event.ProjectGcsUri, event.PlanOutputGcsPrefix];\n case \"renderChunk\":\n return [event.PlanGcsUri, event.ChunkOutputGcsPrefix];\n case \"assemble\":\n return [\n event.PlanGcsUri,\n ...event.ChunkGcsUris,\n event.OutputGcsUri,\n event.AudioGcsUri,\n ].filter((u): u is string => u != null);\n }\n}\n\n/** Emit the \"guard disabled\" warning at most once per instance. */\nlet warnedAllowlistDisabled = false;\n\n/**\n * Verify every GCS URI in the event resolves to the configured render\n * bucket. Throws `GCS_URI_NOT_ALLOWED` (non-retryable) when a URI targets a\n * different bucket, preventing request injection from reading or writing\n * arbitrary GCS data.\n *\n * Opt-out is explicit: set `HYPERFRAMES_RENDER_BUCKET=\"*\"` to disable the\n * guard intentionally. If the env var is simply unset (or empty), the guard\n * is disabled but a warning is logged once so the gap is visible in Cloud\n * Logging \u2014 it shouldn't silently fail open. The Terraform module always\n * wires the bucket name, so the prod path enforces.\n */\n// fallow-ignore-next-line complexity\nfunction validateEventGcsUris(event: PlanEvent | RenderChunkEvent | AssembleEvent): void {\n const allowedBucket = process.env.HYPERFRAMES_RENDER_BUCKET?.trim();\n if (allowedBucket === \"*\") return; // explicit, intentional opt-out\n if (!allowedBucket) {\n if (!warnedAllowlistDisabled) {\n warnedAllowlistDisabled = true;\n logEvent({\n event: \"bucket_allowlist_disabled\",\n level: \"WARNING\",\n message:\n \"HYPERFRAMES_RENDER_BUCKET is unset \u2014 the GCS bucket-allowlist guard is DISABLED. \" +\n 'Set it to the render bucket name to enforce, or to \"*\" to opt out intentionally.',\n });\n }\n return;\n }\n\n for (const uri of getEventGcsUris(event)) {\n const { bucket } = parseGcsUri(uri);\n if (bucket !== allowedBucket) {\n const err = new Error(\n `[handler] GCS_URI_NOT_ALLOWED: URI ${JSON.stringify(uri)} targets bucket \"${bucket}\" but only \"${allowedBucket}\" is permitted`,\n );\n err.name = \"GCS_URI_NOT_ALLOWED\";\n throw err;\n }\n }\n}\n\nfunction pad(n: number): string {\n return n.toString().padStart(4, \"0\");\n}\n\nfunction trimTrailingSlash(prefix: string): string {\n return prefix.endsWith(\"/\") ? prefix.slice(0, -1) : prefix;\n}\n\nfunction cleanupDir(dir: string): void {\n try {\n // Cloud Run re-uses an instance's filesystem across requests; clean up\n // aggressively so we don't leak a chunk-sized footprint between renders\n // (the writable filesystem counts against the instance's memory).\n rmSync(dir, { recursive: true, force: true });\n } catch {\n // Best-effort \u2014 leak is preferable to crashing on the success path.\n }\n}\n\n/**\n * Read the untarred planDir's `plan.json` and assert its `planHash` matches\n * what the workflow event claims. Throws on mismatch with a typed\n * `PLAN_HASH_MISMATCH` error name so the workflow's non-retryable list\n * routes it correctly. Defense-in-depth \u2014 the producer's `renderChunk` does\n * the same check internally \u2014 but performing it here lets us fail before\n * paying the Chrome-launch + per-frame capture cost on a misrouted chunk.\n */\n// fallow-ignore-next-line complexity\nfunction verifyPlanHash(planDir: string, expected: string): void {\n const planJsonPath = join(planDir, \"plan.json\");\n let parsed: { planHash?: unknown };\n try {\n parsed = JSON.parse(readFileSync(planJsonPath, \"utf-8\")) as { planHash?: unknown };\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n const error = new Error(`PLAN_HASH_MISMATCH: failed to read ${planJsonPath}: ${msg}`);\n error.name = \"PLAN_HASH_MISMATCH\";\n throw error;\n }\n const actual = parsed.planHash;\n if (typeof actual !== \"string\" || actual !== expected) {\n const error = new Error(\n `PLAN_HASH_MISMATCH: event PlanHash=${expected} did not match plan.json planHash=${String(actual)}`,\n );\n error.name = \"PLAN_HASH_MISMATCH\";\n throw error;\n }\n}\n\n// \u2500\u2500 HTTP shell \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n/**\n * Error names the workflow treats as non-retryable. A request that fails\n * with one of these is the caller's fault (bad input, misrouted chunk) and\n * retrying it just burns instance-seconds, so we map them to HTTP 400 while\n * any other failure maps to 500 (which the workflow retry policy backs off\n * and re-attempts). Keep this list in sync with the `retry` predicate in\n * `packages/gcp-cloud-run/terraform/workflow.yaml`.\n */\nconst NON_RETRYABLE_ERROR_NAMES = new Set([\n // Handler-boundary guards.\n \"GCS_URI_NOT_ALLOWED\",\n \"PLAN_HASH_MISMATCH\",\n // Producer error class names (`.name`) + their string code aliases \u2014 the\n // class sets `.name` to the class name but wraps a `code`; cover both so a\n // raw-code throw is caught too. Mirrors the AWS state machine's\n // non-retryable list.\n \"FormatNotSupportedInDistributedError\",\n \"PlanTooLargeError\",\n \"RenderChunkValidationError\",\n \"FFMPEG_VERSION_MISMATCH\",\n \"FORMAT_NOT_SUPPORTED_IN_DISTRIBUTED\",\n \"PLAN_TOO_LARGE\",\n \"BROWSER_GPU_NOT_SOFTWARE\",\n \"FONT_FETCH_FAILED\",\n \"ChromeBinaryUnavailableError\",\n]);\n\n/**\n * Build the Hono app. A single `POST /` endpoint dispatches on the body's\n * `Action` field \u2014 the workflow points every step (plan, each renderChunk,\n * assemble) at the same URL and varies only the body. `GET /healthz` backs\n * the Cloud Run startup/liveness probe.\n *\n * `deps` is threaded through so tests can drive the real HTTP surface with\n * an injected Storage double + mocked primitives.\n */\nexport function createApp(deps?: HandlerDeps): Hono {\n const app = new Hono();\n\n app.get(\"/healthz\", (c) => c.json({ status: \"ok\" }));\n\n // fallow-ignore-next-line complexity\n app.post(\"/\", async (c) => {\n let body: CloudRunEvent;\n try {\n body = (await c.req.json()) as CloudRunEvent;\n } catch {\n return c.json({ error: \"BAD_REQUEST\", message: \"request body must be JSON\" }, 400);\n }\n try {\n const result = await dispatch(body, deps);\n return c.json(result, 200);\n } catch (err) {\n const name = err instanceof Error ? err.name : undefined;\n const message = err instanceof Error ? err.message : String(err);\n const status = name && NON_RETRYABLE_ERROR_NAMES.has(name) ? 400 : 500;\n // Surface `error` (the name) as the discriminator the workflow's\n // retry predicate keys off, plus `message` for human triage.\n return c.json({ error: name ?? \"RenderError\", message }, status);\n }\n });\n\n return app;\n}\n\n/** Start the HTTP server. Cloud Run injects `PORT` (default 8080). */\nexport function startServer(): void {\n const port = Number(process.env.PORT ?? 8080);\n const app = createApp();\n serve({ fetch: app.fetch, port }, (info) => {\n logEvent({ event: \"server_listening\", port: info.port });\n });\n}\n\n// Boot when executed directly (the Dockerfile runs `node dist/server.js`),\n// but not when imported by tests or the SDK.\nif (process.argv[1] && fileURLToPath(import.meta.url) === process.argv[1]) {\n startServer();\n}\n", "/**\n * Cloud Run Chrome resolver.\n *\n * `renderChunk()` (the only primitive that needs a browser) launches Chrome\n * via the engine's `BrowserManager`. Because Cloud Run runs a container\n * image rather than a size-capped ZIP, the Chrome story is far simpler than\n * the Lambda adapter's: the `Dockerfile` installs `chrome-headless-shell`\n * (the same BeginFrame-capable build the K8s deploy uses) into the image at\n * a known path and exports `HYPERFRAMES_CHROME_PATH`. There is no runtime\n * decompression-into-/tmp step and no 250 MB packaging ceiling to fight.\n *\n * Resolution order:\n * 1. `PRODUCER_HEADLESS_SHELL_PATH` \u2014 the engine's own override. If a\n * caller (or the Docker image) already set it, honour it untouched.\n * 2. `HYPERFRAMES_CHROME_PATH` \u2014 set by the Dockerfile to the installed\n * `chrome-headless-shell` binary.\n * 3. A small list of conventional install paths, as a last resort for\n * images built outside our Dockerfile.\n *\n * Throws {@link ChromeBinaryUnavailableError} when nothing resolves, so a\n * misconfigured image fails loudly at the first chunk rather than emitting\n * a confusing puppeteer-core \"executablePath must be specified\" assertion.\n */\n\nimport { existsSync } from \"node:fs\";\n\n/**\n * Thrown when the Chrome binary resolver can't produce a usable path. The\n * class name is the workflow's non-retryable error discriminator.\n */\nexport class ChromeBinaryUnavailableError extends Error {\n // Read indirectly via the error envelope / Error.prototype.toString.\n // fallow-ignore-next-line unused-class-member\n override readonly name = \"ChromeBinaryUnavailableError\";\n readonly resolvedPath: string | null;\n constructor(resolvedPath: string | null, hint: string) {\n super(`[chromium] Chrome binary unavailable: ${hint}`);\n this.resolvedPath = resolvedPath;\n }\n}\n\n/**\n * Conventional locations a `chrome-headless-shell` (or full Chrome) binary\n * may live at in a Debian/Ubuntu-based container. Checked only after the\n * two env-var overrides miss.\n */\nconst FALLBACK_CHROME_PATHS = [\n \"/opt/chrome/chrome-headless-shell\",\n \"/usr/bin/chrome-headless-shell\",\n \"/usr/bin/google-chrome\",\n \"/usr/bin/google-chrome-stable\",\n \"/usr/bin/chromium\",\n \"/usr/bin/chromium-browser\",\n];\n\n/**\n * Resolve the absolute path to a Chrome binary suitable for BeginFrame.\n * Pure (no env mutation) so callers decide whether to export the result\n * into `PRODUCER_HEADLESS_SHELL_PATH`.\n */\n// fallow-ignore-next-line complexity\nexport function resolveChromeExecutablePath(): string {\n const fromEngineOverride = process.env.PRODUCER_HEADLESS_SHELL_PATH?.trim();\n if (fromEngineOverride) {\n if (!existsSync(fromEngineOverride)) {\n throw new ChromeBinaryUnavailableError(\n fromEngineOverride,\n `PRODUCER_HEADLESS_SHELL_PATH=${JSON.stringify(fromEngineOverride)} does not exist on disk.`,\n );\n }\n return fromEngineOverride;\n }\n\n const fromImage = process.env.HYPERFRAMES_CHROME_PATH?.trim();\n if (fromImage) {\n if (!existsSync(fromImage)) {\n throw new ChromeBinaryUnavailableError(\n fromImage,\n `HYPERFRAMES_CHROME_PATH=${JSON.stringify(fromImage)} does not exist on disk.`,\n );\n }\n return fromImage;\n }\n\n for (const candidate of FALLBACK_CHROME_PATHS) {\n if (existsSync(candidate)) return candidate;\n }\n\n throw new ChromeBinaryUnavailableError(\n null,\n \"no Chrome binary found. Set HYPERFRAMES_CHROME_PATH (the Dockerfile does this) or \" +\n \"PRODUCER_HEADLESS_SHELL_PATH to the absolute path of a chrome-headless-shell binary. \" +\n `Searched: ${FALLBACK_CHROME_PATHS.join(\", \")}.`,\n );\n}\n", "/**\n * Map a distributed `format` to the file extension the assembled output\n * should carry on disk + in GCS. Shared by `src/server.ts` (chunk +\n * assemble output paths) and `src/sdk/renderToCloudRun.ts` (final\n * output key construction) so the two sides agree on what an mp4\n * looks like vs a png-sequence.\n */\n\nimport type { DistributedFormat } from \"@hyperframes/producer/distributed\";\n\nexport type { DistributedFormat } from \"@hyperframes/producer/distributed\";\n\n// Closed-enum lookup table. TS enforces exhaustiveness via the\n// `Record<DistributedFormat, string>` annotation \u2014 adding a format to\n// `DistributedFormat` without adding the matching key here fails to\n// typecheck, which is the same exhaustiveness guarantee a switch +\n// `_exhaustive: never` arm provides but at lower complexity.\nconst FORMAT_EXTENSIONS: Record<DistributedFormat, string> = {\n mp4: \".mp4\",\n mov: \".mov\",\n webm: \".webm\",\n \"png-sequence\": \"\",\n};\n\nexport function formatExtension(format: DistributedFormat): string {\n return FORMAT_EXTENSIONS[format];\n}\n", "/**\n * Thin GCS transport for the Cloud Run handler.\n *\n * The OSS distributed primitives are pure functions over local file paths;\n * the handler bridges GCS \u2194 the container's writable `/tmp` filesystem on\n * each request. Functions here are intentionally narrow: parse a URI,\n * download an object to a local path, upload a path, tar-pack a planDir,\n * tar-extract a planDir back out.\n *\n * Tar (not zip) for planDir transit:\n * - planDirs contain symlinks (the extract stage materializes them but\n * the compiled/ subtree may include linked assets); tar preserves them,\n * zip does not.\n * - We use the `tar` npm package (pure JS over `node:zlib`) so the\n * archive format doesn't depend on a system `tar`/`unzip` being present\n * in the container image.\n *\n * Apart from the `gs://` scheme and the `@google-cloud/storage` client this\n * is the same shape as `@hyperframes/aws-lambda`'s `s3Transport.ts`.\n */\n\nimport { createWriteStream, existsSync, mkdirSync, rmSync, statSync } from \"node:fs\";\nimport { dirname } from \"node:path\";\nimport { pipeline } from \"node:stream/promises\";\nimport type { Storage } from \"@google-cloud/storage\";\nimport * as tar from \"tar\";\n\n/** Parsed `gs://bucket/key` URI. */\nexport interface GcsLocation {\n bucket: string;\n key: string;\n}\n\n/** Parse `gs://bucket/key/path` \u2192 `{ bucket, key }`. Throws on malformed input. */\n// fallow-ignore-next-line complexity\nexport function parseGcsUri(uri: string): GcsLocation {\n if (!uri.startsWith(\"gs://\")) {\n throw new Error(`[gcsTransport] expected gs:// URI, got: ${JSON.stringify(uri)}`);\n }\n const rest = uri.slice(\"gs://\".length);\n const slash = rest.indexOf(\"/\");\n if (slash === -1) {\n throw new Error(`[gcsTransport] missing key in gs URI: ${JSON.stringify(uri)}`);\n }\n const bucket = rest.slice(0, slash);\n const key = rest.slice(slash + 1);\n if (!bucket || !key) {\n throw new Error(`[gcsTransport] empty bucket or key in gs URI: ${JSON.stringify(uri)}`);\n }\n return { bucket, key };\n}\n\n/** Build `gs://bucket/key` from a location. */\nexport function formatGcsUri(loc: GcsLocation): string {\n return `gs://${loc.bucket}/${loc.key}`;\n}\n\n/** Stream a GCS object to a local file path. */\nexport async function downloadGcsObjectToFile(\n storage: Storage,\n uri: string,\n destPath: string,\n): Promise<void> {\n const { bucket, key } = parseGcsUri(uri);\n mkdirSync(dirname(destPath), { recursive: true });\n const file = storage.bucket(bucket).file(key);\n // `createReadStream` streams the object body; piping into a write stream\n // keeps memory flat for large plan tarballs / chunk files rather than\n // buffering the whole object the way `file.download()` would.\n await pipeline(file.createReadStream(), createWriteStream(destPath));\n}\n\n/**\n * Upload a local file's contents to a GCS URI using a resumable upload.\n * GCS objects have no practical size ceiling for the artifacts this adapter\n * handles (plan tarballs \u2264 2 GB, chunks \u2264 a few hundred MB), so a single\n * upload call works for every case.\n */\nexport async function uploadFileToGcs(\n storage: Storage,\n localPath: string,\n uri: string,\n contentType?: string,\n): Promise<void> {\n if (!existsSync(localPath)) {\n throw new Error(`[gcsTransport] upload source missing: ${localPath}`);\n }\n const { bucket, key } = parseGcsUri(uri);\n await storage.bucket(bucket).upload(localPath, {\n destination: key,\n // `resumable: false` (simple upload) is faster for the small-to-medium\n // objects this adapter moves and avoids the extra round-trip a resumable\n // session start costs; GCS recommends resumable only past ~8 MB but our\n // chunks are reliably above that, so let the client pick by default.\n contentType,\n });\n}\n\n/**\n * Pack a directory into a `.tar.gz` at `destTarball`. Uses the `tar` npm\n * package (pure JS over `node:zlib`) rather than spawning a system tar\n * binary so the archive format is independent of the container's userland.\n */\nexport async function tarDirectory(sourceDir: string, destTarball: string): Promise<void> {\n if (!existsSync(sourceDir) || !statSync(sourceDir).isDirectory()) {\n throw new Error(`[gcsTransport] tar source must be an existing directory: ${sourceDir}`);\n }\n mkdirSync(dirname(destTarball), { recursive: true });\n await tar.create({ gzip: true, file: destTarball, cwd: sourceDir }, [\".\"]);\n}\n\n/**\n * Extract a `.tar.gz` produced by {@link tarDirectory} into `destDir`.\n * The directory is created (or cleared) before extraction so a retried\n * request doesn't observe stale files from a prior run on the same warm\n * container instance.\n */\nexport async function untarDirectory(tarballPath: string, destDir: string): Promise<void> {\n if (!existsSync(tarballPath)) {\n throw new Error(`[gcsTransport] tarball missing: ${tarballPath}`);\n }\n // Wipe target so a warm container instance's prior planDir doesn't bleed\n // into the new request. Cloud Run re-uses the instance filesystem across\n // requests served by the same instance.\n if (existsSync(destDir)) {\n rmSync(destDir, { recursive: true, force: true });\n }\n mkdirSync(destDir, { recursive: true });\n await tar.extract({ file: tarballPath, cwd: destDir });\n}\n", "/**\n * `deploySite` \u2014 upload a project directory to GCS once per content hash\n * and return a reusable handle.\n *\n * `renderToCloudRun` calls this implicitly when no `siteHandle` is passed,\n * but exposing it as a standalone verb lets adopters bundle a project ahead\n * of time and reuse the handle across many renders without re-tarring the\n * project tree on every call.\n *\n * The handle is **content-addressed**: `siteId` is derived from a SHA-256\n * over the project files. Two `deploySite` calls on an unchanged tree\n * produce the same `siteId` and short-circuit the upload after a single\n * existence check.\n */\n\nimport { mkdtempSync, rmSync, statSync } from \"node:fs\";\nimport { tmpdir } from \"node:os\";\nimport { join } from \"node:path\";\nimport { Storage } from \"@google-cloud/storage\";\nimport { hashProjectDir } from \"@hyperframes/producer/distributed\";\nimport { formatGcsUri, tarDirectory, uploadFileToGcs } from \"../gcsTransport.js\";\n\n/** Options for {@link deploySite}. */\nexport interface DeploySiteOptions {\n /** Local project directory containing `index.html` (and any composition assets). */\n projectDir: string;\n /** GCS bucket the Terraform module provisioned. */\n bucketName: string;\n /**\n * Override the content-addressed site id. Useful when the caller has a\n * stable external identifier they want to use (e.g. a git SHA); if unset,\n * the hash of the project tree picks it.\n */\n siteId?: string;\n /** Injection seam for tests. Production callers leave unset. */\n storage?: Storage;\n}\n\n/** Stable handle returned by {@link deploySite}. Pass back to {@link renderToCloudRun}. */\nexport interface SiteHandle {\n /** Content-addressed (or caller-supplied) identifier; stable across re-uploads of the same tree. */\n siteId: string;\n /** Bucket the site landed in. Surfaced separately so callers don't have to re-parse `projectGcsUri`. */\n bucketName: string;\n /** Full `gs://bucket/sites/<siteId>/project.tar.gz` URI; pass through to `renderToCloudRun`. */\n projectGcsUri: string;\n /** Tarball size in bytes; useful for \"did we actually skip the upload?\" assertions. */\n bytes: number;\n /** ISO timestamp of the most recent upload OR the existing object the short-circuit found. */\n uploadedAt: string;\n /** `false` if the object already existed and we skipped the upload. */\n uploaded: boolean;\n}\n\n/**\n * Upload `projectDir` to `gs://bucketName/sites/<siteId>/project.tar.gz`.\n *\n * Short-circuits when an object with the same key already exists in the\n * bucket \u2014 `siteId` derives from the project's content hash, so the same\n * bytes produce the same key, and re-uploading would be redundant.\n */\n// fallow-ignore-next-line complexity\nexport async function deploySite(opts: DeploySiteOptions): Promise<SiteHandle> {\n if (!statSync(opts.projectDir).isDirectory()) {\n throw new Error(`[deploySite] projectDir is not a directory: ${opts.projectDir}`);\n }\n\n const siteId = opts.siteId ?? hashProjectDir(opts.projectDir);\n const key = `sites/${siteId}/project.tar.gz`;\n const projectGcsUri = formatGcsUri({ bucket: opts.bucketName, key });\n const storage = opts.storage ?? new Storage();\n const file = storage.bucket(opts.bucketName).file(key);\n\n // Existence short-circuit. Adopters re-rendering the same project on a\n // tight inner loop (CI smoke, demo flows) save the tar+gzip+upload pass\n // on every iteration.\n const existing = await headObject(file);\n if (existing) {\n return {\n siteId,\n bucketName: opts.bucketName,\n projectGcsUri,\n bytes: existing.bytes,\n uploadedAt: existing.lastModified,\n uploaded: false,\n };\n }\n\n const workdir = mkdtempSync(join(tmpdir(), \"hf-deploy-site-\"));\n try {\n const tarball = join(workdir, \"project.tar.gz\");\n await tarDirectory(opts.projectDir, tarball);\n const size = statSync(tarball).size;\n await uploadFileToGcs(storage, tarball, projectGcsUri, \"application/gzip\");\n return {\n siteId,\n bucketName: opts.bucketName,\n projectGcsUri,\n bytes: size,\n uploadedAt: new Date().toISOString(),\n uploaded: true,\n };\n } finally {\n rmSync(workdir, { recursive: true, force: true });\n }\n}\n\n/**\n * Narrow surface of the `@google-cloud/storage` `File` this module uses \u2014\n * lets the test double implement just `exists()` + `getMetadata()` without\n * pulling the full client type.\n */\ninterface FileLike {\n exists(): Promise<[boolean, ...unknown[]]>;\n getMetadata(): Promise<[{ size?: string | number; updated?: string }, ...unknown[]]>;\n}\n\n// fallow-ignore-next-line complexity\nasync function headObject(file: FileLike): Promise<{ bytes: number; lastModified: string } | null> {\n const [exists] = await file.exists();\n if (!exists) return null;\n const [meta] = await file.getMetadata();\n const sizeRaw = meta.size;\n const bytes =\n typeof sizeRaw === \"string\" ? Number(sizeRaw) : typeof sizeRaw === \"number\" ? sizeRaw : 0;\n return {\n bytes: Number.isFinite(bytes) ? bytes : 0,\n lastModified: meta.updated ?? new Date().toISOString(),\n };\n}\n", "/**\n * `renderToCloudRun` \u2014 start a distributed render against an already-deployed\n * Cloud Run service + Cloud Workflows definition and return a handle the\n * caller can poll with {@link getRenderProgress}.\n *\n * The function does *not* wait for the render to finish. Cloud Workflows\n * executions can run for hours; blocking the caller's process on the\n * execution is the wrong default. The returned `RenderHandle` carries\n * everything the progress / cost / download paths need.\n *\n * Wire order:\n * 1. Validate config (typed throw before any GCP call).\n * 2. `deploySite` if no `siteHandle` was provided.\n * 3. `CreateExecution` against the workflow with the argument shape the\n * `packages/gcp-cloud-run/terraform/workflow.yaml` definition expects.\n * 4. Return handle. The GCS `outputKey` is deterministic from the\n * client-generated `renderId` so the caller can predict the final\n * object URL before the (server-assigned) execution id exists.\n *\n * Unlike Step Functions, Cloud Workflows assigns the execution id\n * server-side, so we cannot use it as the GCS prefix. We mint a `renderId`\n * (uuid) client-side, use it for every GCS path, and pass it into the\n * workflow argument; the server-assigned execution resource name is tracked\n * separately for polling.\n */\n\nimport { randomUUID } from \"node:crypto\";\nimport type { Storage } from \"@google-cloud/storage\";\nimport type { SerializableDistributedRenderConfig } from \"../events.js\";\nimport { formatExtension } from \"../formatExtension.js\";\nimport { formatGcsUri } from \"../gcsTransport.js\";\nimport { deploySite, type SiteHandle } from \"./deploySite.js\";\nimport { validateDistributedRenderConfig, validateWorkflowsInputSize } from \"./validateConfig.js\";\n\n/**\n * Minimal surface of `@google-cloud/workflows`' `ExecutionsClient` that\n * this module needs. The real client satisfies this; tests inject a double.\n */\nexport interface ExecutionsClientLike {\n workflowPath(project: string, location: string, workflow: string): string;\n createExecution(req: {\n parent: string;\n execution: { argument: string };\n }): Promise<[{ name?: string | null; state?: string | null }, ...unknown[]]>;\n}\n\n/** Options for {@link renderToCloudRun}. */\nexport interface RenderToCloudRunOptions {\n /** Local project directory. Required when `siteHandle` is not supplied. */\n projectDir?: string;\n /** Re-use an existing `deploySite` upload (skips tar+GCS upload). */\n siteHandle?: SiteHandle;\n /** Validated `SerializableDistributedRenderConfig` (no logger / abortSignal). */\n config: SerializableDistributedRenderConfig;\n /** GCS bucket from the Terraform output (`render_bucket_name`). */\n bucketName: string;\n /** GCP project id hosting the workflow. */\n projectId: string;\n /** Workflow location, e.g. `us-central1`. */\n location: string;\n /** Workflow id from the Terraform output (`workflow_name`). */\n workflowId: string;\n /**\n * HTTPS URL of the deployed Cloud Run render service (Terraform output\n * `service_url`). The workflow POSTs every step (plan / renderChunk /\n * assemble) to this URL; passed as an execution argument so the workflow\n * definition stays free of hard-coded URLs.\n */\n serviceUrl: string;\n /**\n * Final output GCS key. Defaults to `renders/<renderId>/output.<ext>`\n * where `<ext>` is derived from `config.format`.\n */\n outputKey?: string;\n /**\n * Client-generated render id. Defaults to `hf-render-<uuid>`. Used as the\n * GCS key prefix and echoed into the workflow argument; not the same as\n * the server-assigned execution id.\n */\n renderId?: string;\n /** Test injection seam \u2014 production callers leave unset. */\n executions?: ExecutionsClientLike;\n /** Test injection seam \u2014 propagated to `deploySite` when applicable. */\n storage?: Storage;\n}\n\n/** Stable identifier + every URL/name the caller needs to follow the render. */\nexport interface RenderHandle {\n /** Client-generated render id; the GCS prefix everything lands under. */\n renderId: string;\n /** Server-assigned execution resource name; pass to {@link getRenderProgress}. */\n executionName: string;\n bucketName: string;\n workflowId: string;\n outputGcsUri: string;\n projectGcsUri: string;\n startedAt: string;\n}\n\n// fallow-ignore-next-line complexity\nexport async function renderToCloudRun(opts: RenderToCloudRunOptions): Promise<RenderHandle> {\n validateDistributedRenderConfig(opts.config);\n\n if (!opts.bucketName) throw new Error(\"[renderToCloudRun] bucketName is required\");\n if (!opts.projectId) throw new Error(\"[renderToCloudRun] projectId is required\");\n if (!opts.location) throw new Error(\"[renderToCloudRun] location is required\");\n if (!opts.workflowId) throw new Error(\"[renderToCloudRun] workflowId is required\");\n if (!opts.serviceUrl) throw new Error(\"[renderToCloudRun] serviceUrl is required\");\n if (!opts.siteHandle && !opts.projectDir) {\n throw new Error(\"[renderToCloudRun] either siteHandle or projectDir must be supplied\");\n }\n\n const renderId = opts.renderId ?? `hf-render-${randomUUID()}`;\n // `renderId` is interpolated directly into GCS object keys\n // (`renders/<renderId>/\u2026`). Reject anything that could escape that prefix\n // or build a malformed key \u2014 `..`, slashes, or other path metacharacters \u2014\n // so a caller-supplied id can't collide with or overwrite another render's\n // artifacts elsewhere in the bucket.\n if (!/^[A-Za-z0-9._-]+$/.test(renderId) || renderId.includes(\"..\")) {\n throw new Error(\n `[renderToCloudRun] renderId must match [A-Za-z0-9._-]+ and not contain \"..\": ${JSON.stringify(renderId)}`,\n );\n }\n const ext = formatExtension(opts.config.format);\n const outputKey = opts.outputKey ?? `renders/${renderId}/output${ext}`;\n const planOutputGcsPrefix = formatGcsUri({\n bucket: opts.bucketName,\n key: `renders/${renderId}/`,\n });\n const outputGcsUri = formatGcsUri({ bucket: opts.bucketName, key: outputKey });\n\n const site =\n opts.siteHandle ??\n (await deploySite({\n projectDir: opts.projectDir as string,\n bucketName: opts.bucketName,\n storage: opts.storage,\n }));\n\n const argument = {\n RenderId: renderId,\n ProjectGcsUri: site.projectGcsUri,\n PlanOutputGcsPrefix: planOutputGcsPrefix,\n OutputGcsUri: outputGcsUri,\n ServiceUrl: opts.serviceUrl,\n Config: opts.config,\n };\n\n // Reject oversize input client-side. Cloud Workflows caps the execution\n // argument at 512 KiB; without this check, input bloat (typically from\n // `config.variables` containing inlined media) surfaces as an opaque\n // server-side error after the execution starts, far from the caller's\n // stack frame.\n validateWorkflowsInputSize(argument);\n\n const executions = opts.executions ?? (await defaultExecutionsClient());\n const parent = executions.workflowPath(opts.projectId, opts.location, opts.workflowId);\n const startedAt = new Date().toISOString();\n const [execution] = await executions.createExecution({\n parent,\n execution: { argument: JSON.stringify(argument) },\n });\n\n if (!execution.name) {\n throw new Error(\"[renderToCloudRun] CreateExecution returned no execution name\");\n }\n\n return {\n renderId,\n executionName: execution.name,\n bucketName: opts.bucketName,\n workflowId: opts.workflowId,\n outputGcsUri,\n projectGcsUri: site.projectGcsUri,\n startedAt,\n };\n}\n\n/**\n * Lazily import the real `@google-cloud/workflows` ExecutionsClient. Dynamic\n * so SDK consumers that only call `validateDistributedRenderConfig` (or\n * inject their own client) don't pay the import cost.\n */\nasync function defaultExecutionsClient(): Promise<ExecutionsClientLike> {\n const mod = await import(\"@google-cloud/workflows\");\n const client = new mod.ExecutionsClient();\n return client as unknown as ExecutionsClientLike;\n}\n", "/**\n * Client-side validation for the Cloud Run adapter.\n *\n * The cloud-agnostic config-shape validation (`validateDistributedRenderConfig`,\n * `validateVariablesPayload`, `InvalidConfigError`) lives in\n * `@hyperframes/producer/distributed` and is shared with the other adapters.\n * This module re-exports those and adds the one piece that is specific to\n * Cloud Workflows: the 512 KiB execution-argument size cap.\n */\n\nimport { InvalidConfigError } from \"@hyperframes/producer/distributed\";\n\nexport {\n InvalidConfigError,\n validateDistributedRenderConfig,\n validateVariablesPayload,\n} from \"@hyperframes/producer/distributed\";\n\n/**\n * Hard cap on Cloud Workflows execution arguments \u2014 512 KiB per the Workflows\n * quotas page (maximum size of arguments passed when an execution starts).\n * The cap is on the entire serialized argument, not just the variables,\n * because users hit it at the wire boundary regardless of which field caused\n * the bloat.\n *\n * Specific to Cloud Workflows. Other runtimes (Lambda + Step Functions,\n * Temporal) have different caps; don't reuse this constant for those without\n * confirming the limit.\n */\nexport const MAX_WORKFLOWS_INPUT_BYTES = 512 * 1024;\n\n/** Pointer to the docs section that explains the URL-your-assets convention. */\nconst LARGE_VARIABLES_DOCS_URL =\n \"https://hyperframes.heygen.com/deploy/templates-on-lambda#working-with-large-variables\";\n\n/**\n * Validate that the serialized Cloud Workflows execution argument fits inside\n * the 512 KiB cap. Measured in UTF-8 bytes (the format the API uses on the\n * wire) \u2014 JS strings count UTF-16 code units, which under-reports for any\n * multi-byte character.\n *\n * Throws {@link InvalidConfigError} with a clear message naming the actual\n * byte count, the cap, and a pointer to the \"working with large variables\"\n * docs section, so users hit the limit at the SDK boundary with actionable\n * guidance instead of as an opaque argument-too-large error after the\n * execution starts.\n */\n// fallow-ignore-next-line complexity\nexport function validateWorkflowsInputSize(input: unknown): void {\n let serialized: string | undefined;\n try {\n serialized = JSON.stringify(input);\n } catch (err) {\n throw new InvalidConfigError(\n \"config\",\n `Cloud Workflows execution argument is not JSON-serializable: ${err instanceof Error ? err.message : String(err)}`,\n );\n }\n if (serialized === undefined) {\n throw new InvalidConfigError(\n \"config\",\n \"Cloud Workflows execution argument is not JSON-serializable (JSON.stringify returned undefined). \" +\n \"Check that all fields, including config.variables, are plain JSON values.\",\n );\n }\n const byteLength = Buffer.byteLength(serialized, \"utf8\");\n if (byteLength > MAX_WORKFLOWS_INPUT_BYTES) {\n throw new InvalidConfigError(\n \"config\",\n `Cloud Workflows execution argument is ${byteLength} bytes, which exceeds the ` +\n `${MAX_WORKFLOWS_INPUT_BYTES}-byte (512 KiB) limit. Variables are for typed data ` +\n `(strings, numbers, structured records); media assets (images, audio, video) should ` +\n `be passed as URL references the composition resolves at render time, not inlined as ` +\n `base64. See ${LARGE_VARIABLES_DOCS_URL} for the URL-your-assets convention.`,\n );\n }\n}\n", "/**\n * Per-render cost accounting for {@link getRenderProgress}.\n *\n * Google bills the render service two ways:\n *\n * - **Cloud Run** by **vCPU-seconds** and **GiB-seconds** of request\n * processing time, plus a flat per-request charge. Each handler\n * invocation returns its own `DurationMs` in the result body, so the\n * progress reader can recover billed time per step without a separate\n * Cloud Monitoring query \u2014 multiply by the service's configured vCPU /\n * memory to get the resource-seconds.\n * - **Cloud Workflows** by **steps executed**. The orchestration is a\n * fixed shape (Plan + N\u00D7RenderChunk + Assemble + a handful of control\n * steps), so the step count scales with chunk count.\n *\n * The math is documented inline so the constants stay close to the pricing\n * source they came from. Cost is **best-effort**: GCP pricing varies by\n * region + committed-use discounts; we use on-demand `us-central1` (Tier 1)\n * rates as of 2026-06 and label the result `displayCost` so callers see the\n * dollar value but downstream automation can also read the raw number.\n */\n\n/** Cloud Run request-based billing, us-central1 Tier 1: USD per vCPU-second. */\nconst CLOUD_RUN_USD_PER_VCPU_SECOND = 0.000024;\n/** Cloud Run request-based billing, us-central1 Tier 1: USD per GiB-second. */\nconst CLOUD_RUN_USD_PER_GIB_SECOND = 0.0000025;\n/** Cloud Run: USD per request ($0.40 per million). */\nconst CLOUD_RUN_USD_PER_REQUEST = 0.0000004;\n/** Cloud Workflows: USD per internal step ($0.01 per 1,000, after a free tier). */\nconst WORKFLOWS_USD_PER_STEP = 0.00001;\n\n/** Per-invocation billed slice the cost calc cares about. */\nexport interface BilledCloudRunInvocation {\n /** Wall-clock the handler reported via `DurationMs` in its result body. */\n durationMs: number;\n /** vCPU the Cloud Run service was configured with at invocation time. */\n vcpu: number;\n /** Memory in GiB the Cloud Run service was configured with. */\n memoryGib: number;\n /** `true` if the duration was inferred (step result missing) rather than read from the handler payload. */\n estimated: boolean;\n}\n\n/**\n * Result of {@link computeRenderCost}.\n *\n * NOTE: `displayCost` / `accruedSoFarUsd` cover Cloud Run compute + Cloud\n * Workflows steps only. They EXCLUDE GCS storage + network egress for the\n * plan tarball (which can be ~100 MB), chunk artifacts, and the final output\n * \u2014 see `breakdown.gcsEstimate`. Treat the figure as a compute-cost floor,\n * not the authoritative total bill.\n */\nexport interface RenderCost {\n /** USD accrued to date (Cloud Run + Workflows only; excludes GCS \u2014 see note above). */\n accruedSoFarUsd: number;\n /** Human-readable USD string, e.g. `\"$0.0214\"`. Excludes GCS storage/egress. */\n displayCost: string;\n breakdown: {\n cloudRunUsd: number;\n workflowsUsd: number;\n /** GCS transfer + storage cost varies by tier; we don't try to compute it here. */\n gcsEstimate: \"not-included\";\n /** `true` if any invocation fell back to estimated billing. */\n estimated: boolean;\n };\n}\n\n/**\n * Sum Cloud Run vCPU-seconds + GiB-seconds + per-request charges and Cloud\n * Workflows steps into an aggregate USD figure.\n *\n * `workflowSteps` is the count of Workflows steps executed so far \u2014 Plan\n * (1) + RenderChunk (chunkCount) + Assemble (1) + the control steps\n * (BuildChunkList, AssertChunkCount, \u2026). Pass the count the progress reader\n * derived from the execution; a rough constant overhead is fine since the\n * step charge is a rounding error next to Cloud Run compute.\n */\nexport function computeRenderCost(\n invocations: BilledCloudRunInvocation[],\n workflowSteps: number,\n): RenderCost {\n let cloudRunUsd = 0;\n let anyEstimated = false;\n for (const inv of invocations) {\n const seconds = inv.durationMs / 1000;\n cloudRunUsd += seconds * inv.vcpu * CLOUD_RUN_USD_PER_VCPU_SECOND;\n cloudRunUsd += seconds * inv.memoryGib * CLOUD_RUN_USD_PER_GIB_SECOND;\n cloudRunUsd += CLOUD_RUN_USD_PER_REQUEST;\n if (inv.estimated) anyEstimated = true;\n }\n const workflowsUsd = workflowSteps * WORKFLOWS_USD_PER_STEP;\n const accruedSoFarUsd = roundUsd(cloudRunUsd + workflowsUsd);\n return {\n accruedSoFarUsd,\n displayCost: formatUsd(accruedSoFarUsd),\n breakdown: {\n cloudRunUsd: roundUsd(cloudRunUsd),\n workflowsUsd: roundUsd(workflowsUsd),\n gcsEstimate: \"not-included\",\n estimated: anyEstimated,\n },\n };\n}\n\nfunction roundUsd(usd: number): number {\n // Four decimal places \u2014 enough resolution for per-chunk granularity.\n // Anything finer is noise vs GCP's own rounding.\n return Math.round(usd * 10_000) / 10_000;\n}\n\nfunction formatUsd(usd: number): string {\n return `$${usd.toFixed(4)}`;\n}\n", "/**\n * `getRenderProgress` \u2014 read-only progress + cost snapshot for a single\n * render started by {@link renderToCloudRun}.\n *\n * Pulls one `GetExecution` per call. Cloud Workflows does not surface\n * per-step payloads through the basic Executions API the way Step Functions\n * exposes its history, so this reader takes a different tack than the AWS\n * adapter: the workflow definition **accumulates** each step's result body\n * (Plan + every RenderChunk + Assemble) and returns them as one structured\n * object. On success we parse that object for frame totals, the output\n * file, and per-step `DurationMs` (which the handler stamps into every\n * result), then compute cost against the service's configured vCPU/memory.\n *\n * Progress is therefore coarse while the execution is ACTIVE (we report\n * `running` with `overallProgress = 0`) and exact once it SUCCEEDS\n * (`overallProgress = 1`, real frame + cost numbers). Mid-flight per-chunk\n * progress would require the Workflows step-entries API; that's a tracked\n * follow-up, not part of the first version.\n */\n\nimport {\n type BilledCloudRunInvocation,\n computeRenderCost,\n type RenderCost,\n} from \"./costAccounting.js\";\n\n/** Normalised render status. Maps from Cloud Workflows execution states. */\nexport type RenderStatus = \"running\" | \"succeeded\" | \"failed\" | \"cancelled\" | \"unknown\";\n\n/** One error surfaced by the execution. */\nexport interface RenderError {\n /** Step the failure surfaced in, when recoverable from the error context; else `<execution>`. */\n state: string;\n /** Error class / type. */\n error: string;\n /** Cause string (often a stringified JSON payload from the handler). */\n cause: string;\n}\n\n/** Snapshot of a single render's progress + cost + errors at one point in time. */\nexport interface RenderProgress {\n status: RenderStatus;\n /** `[0, 1]`; coarse while running, exact on success. */\n overallProgress: number;\n framesRendered: number;\n /** `null` until the execution succeeds and the accumulated plan result is read. */\n totalFrames: number | null;\n /** Cloud Run invocations the workflow scheduled (Plan + chunks + Assemble), when known. */\n invocationsObserved: number;\n costs: RenderCost;\n /** Final output object if Assemble succeeded; `null` otherwise. */\n outputFile: { gcsUri: string; bytes: number | null } | null;\n errors: RenderError[];\n /** `true` once the execution has terminated in a non-success state. */\n fatalErrorEncountered: boolean;\n startedAt: string;\n endedAt: string | null;\n}\n\n/** Protobuf Timestamp shape the gapic client returns for start/end times. */\ninterface ProtoTimestamp {\n seconds?: number | string | null;\n nanos?: number | null;\n}\n\n/** Subset of a Cloud Workflows Execution this reader consumes. */\nexport interface ExecutionRecord {\n name?: string | null;\n state?: string | null;\n result?: string | null;\n error?: { payload?: string | null; context?: string | null } | null;\n startTime?: ProtoTimestamp | string | null;\n endTime?: ProtoTimestamp | string | null;\n}\n\n/** Minimal surface of `@google-cloud/workflows`' `ExecutionsClient` for reads. */\nexport interface ExecutionsGetClientLike {\n getExecution(req: { name: string }): Promise<[ExecutionRecord, ...unknown[]]>;\n}\n\n/** Options for {@link getRenderProgress}. */\nexport interface GetRenderProgressOptions {\n /** Server-assigned execution resource name from a {@link renderToCloudRun} call. */\n executionName: string;\n /** vCPU the Cloud Run service is configured with (for cost). Default 4. */\n vcpu?: number;\n /** Memory in GiB the Cloud Run service is configured with (for cost). Default 16. */\n memoryGib?: number;\n /** Test injection seam \u2014 production callers leave unset. */\n executions?: ExecutionsGetClientLike;\n}\n\nconst DEFAULT_VCPU = 4;\nconst DEFAULT_MEMORY_GIB = 16;\n\n/** Result body the handler returns for each action; the workflow accumulates these. */\ninterface AccumulatedResult {\n Plan?: { TotalFrames?: number; DurationMs?: number } | null;\n Chunks?: Array<{ FramesEncoded?: number; DurationMs?: number } | null> | null;\n Assemble?: {\n OutputGcsUri?: string;\n FileSize?: number;\n FramesEncoded?: number;\n DurationMs?: number;\n } | null;\n}\n\n/** Pull a current progress snapshot for one render. */\n// fallow-ignore-next-line complexity\nexport async function getRenderProgress(opts: GetRenderProgressOptions): Promise<RenderProgress> {\n if (!opts.executionName) {\n throw new Error(\"[getRenderProgress] executionName is required\");\n }\n const executions = opts.executions ?? (await defaultExecutionsClient());\n const vcpu = opts.vcpu ?? DEFAULT_VCPU;\n const memoryGib = opts.memoryGib ?? DEFAULT_MEMORY_GIB;\n\n const [execution] = await executions.getExecution({ name: opts.executionName });\n const status = mapState(execution.state);\n const startedAt = toIso(execution.startTime) ?? new Date(0).toISOString();\n const endedAt = toIso(execution.endTime);\n\n const errors: RenderError[] = [];\n if (execution.error) {\n errors.push({\n state: execution.error.context ?? \"<execution>\",\n error: extractErrorName(execution.error.payload) ?? \"ExecutionError\",\n cause: execution.error.payload ?? \"\",\n });\n }\n\n // Default snapshot: running / unknown \u2014 no frame or cost data until the\n // accumulated result is available on success.\n if (status !== \"succeeded\") {\n return {\n status,\n overallProgress: 0,\n framesRendered: 0,\n totalFrames: null,\n invocationsObserved: 0,\n costs: computeRenderCost([], 0),\n outputFile: null,\n errors,\n fatalErrorEncountered: status === \"failed\" || status === \"cancelled\",\n startedAt,\n endedAt,\n };\n }\n\n const acc = parseAccumulated(execution.result);\n const chunks = acc.Chunks?.filter((c): c is NonNullable<typeof c> => c != null) ?? [];\n const framesRendered = chunks.reduce((sum, c) => sum + (c.FramesEncoded ?? 0), 0);\n const totalFrames = typeof acc.Plan?.TotalFrames === \"number\" ? acc.Plan.TotalFrames : null;\n\n const invocations: BilledCloudRunInvocation[] = [];\n const pushInv = (durationMs: number | undefined): void => {\n invocations.push({\n durationMs: typeof durationMs === \"number\" ? durationMs : 0,\n vcpu,\n memoryGib,\n estimated: typeof durationMs !== \"number\",\n });\n };\n if (acc.Plan) pushInv(acc.Plan.DurationMs);\n for (const c of chunks) pushInv(c.DurationMs);\n if (acc.Assemble) pushInv(acc.Assemble.DurationMs);\n\n // Workflow step count: Plan + N chunks + Assemble + a small constant of\n // control steps (BuildChunkList, AssertChunkCount, the map scaffold).\n const workflowSteps = invocations.length + 4;\n const costs = computeRenderCost(invocations, workflowSteps);\n\n const outputGcsUri = acc.Assemble?.OutputGcsUri;\n const outputFile = outputGcsUri\n ? {\n gcsUri: outputGcsUri,\n bytes: typeof acc.Assemble?.FileSize === \"number\" ? acc.Assemble.FileSize : null,\n }\n : null;\n\n return {\n status,\n overallProgress: 1,\n framesRendered,\n totalFrames,\n invocationsObserved: invocations.length,\n costs,\n outputFile,\n errors,\n fatalErrorEncountered: false,\n startedAt,\n endedAt,\n };\n}\n\n// fallow-ignore-next-line complexity\nfunction mapState(state: string | null | undefined): RenderStatus {\n switch (state) {\n case \"ACTIVE\":\n case \"QUEUED\":\n return \"running\";\n case \"SUCCEEDED\":\n return \"succeeded\";\n case \"FAILED\":\n case \"UNAVAILABLE\":\n return \"failed\";\n case \"CANCELLED\":\n return \"cancelled\";\n default:\n return \"unknown\";\n }\n}\n\n// fallow-ignore-next-line complexity\nfunction parseAccumulated(result: string | null | undefined): AccumulatedResult {\n if (!result) return {};\n try {\n const parsed = JSON.parse(result) as unknown;\n if (parsed && typeof parsed === \"object\") return parsed as AccumulatedResult;\n } catch {\n // Non-JSON result \u2014 treat as empty so cost/frames degrade to zero\n // rather than throwing on a snapshot read.\n }\n return {};\n}\n\n/**\n * Best-effort pull of the handler's error name out of a Workflows failure\n * payload. On an http step failure, Workflows wraps the response as\n * `{ code, message, body, ... }` where `body` is the handler's JSON\n * `{ error, message }`. We dig out `error` (the typed name like\n * `PLAN_HASH_MISMATCH`) so triage sees the real cause, not a generic label.\n * Returns undefined for any shape we don't recognise \u2014 never throws.\n */\n// fallow-ignore-next-line complexity\nfunction extractErrorName(payload: string | null | undefined): string | undefined {\n if (!payload) return undefined;\n try {\n const outer = JSON.parse(payload) as { error?: unknown; body?: unknown };\n if (typeof outer.error === \"string\") return outer.error;\n if (typeof outer.body === \"string\") {\n const inner = JSON.parse(outer.body) as { error?: unknown };\n if (typeof inner.error === \"string\") return inner.error;\n } else if (outer.body && typeof outer.body === \"object\") {\n const inner = outer.body as { error?: unknown };\n if (typeof inner.error === \"string\") return inner.error;\n }\n } catch {\n // Non-JSON / unexpected shape \u2014 fall through to the generic label.\n }\n return undefined;\n}\n\n// fallow-ignore-next-line complexity\nfunction toIso(ts: ProtoTimestamp | string | null | undefined): string | null {\n if (ts == null) return null;\n if (typeof ts === \"string\") return ts;\n const seconds = ts.seconds == null ? null : Number(ts.seconds);\n if (seconds == null || !Number.isFinite(seconds)) return null;\n const ms = seconds * 1000 + (ts.nanos ?? 0) / 1e6;\n return new Date(ms).toISOString();\n}\n\nasync function defaultExecutionsClient(): Promise<ExecutionsGetClientLike> {\n const mod = await import(\"@google-cloud/workflows\");\n const client = new mod.ExecutionsClient();\n return client as unknown as ExecutionsGetClientLike;\n}\n"],
5
+ "mappings": "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",
6
+ "names": ["existsSync", "mkdirSync", "rmSync", "statSync", "existsSync", "existsSync", "statSync", "uri", "mkdirSync", "rmSync", "mkdtempSync", "rmSync", "statSync", "tmpdir", "join", "Storage", "statSync", "Storage", "mkdtempSync", "join", "tmpdir", "rmSync", "InvalidConfigError", "defaultExecutionsClient"]
7
+ }
@@ -0,0 +1,67 @@
1
+ /**
2
+ * Per-render cost accounting for {@link getRenderProgress}.
3
+ *
4
+ * Google bills the render service two ways:
5
+ *
6
+ * - **Cloud Run** by **vCPU-seconds** and **GiB-seconds** of request
7
+ * processing time, plus a flat per-request charge. Each handler
8
+ * invocation returns its own `DurationMs` in the result body, so the
9
+ * progress reader can recover billed time per step without a separate
10
+ * Cloud Monitoring query — multiply by the service's configured vCPU /
11
+ * memory to get the resource-seconds.
12
+ * - **Cloud Workflows** by **steps executed**. The orchestration is a
13
+ * fixed shape (Plan + N×RenderChunk + Assemble + a handful of control
14
+ * steps), so the step count scales with chunk count.
15
+ *
16
+ * The math is documented inline so the constants stay close to the pricing
17
+ * source they came from. Cost is **best-effort**: GCP pricing varies by
18
+ * region + committed-use discounts; we use on-demand `us-central1` (Tier 1)
19
+ * rates as of 2026-06 and label the result `displayCost` so callers see the
20
+ * dollar value but downstream automation can also read the raw number.
21
+ */
22
+ /** Per-invocation billed slice the cost calc cares about. */
23
+ export interface BilledCloudRunInvocation {
24
+ /** Wall-clock the handler reported via `DurationMs` in its result body. */
25
+ durationMs: number;
26
+ /** vCPU the Cloud Run service was configured with at invocation time. */
27
+ vcpu: number;
28
+ /** Memory in GiB the Cloud Run service was configured with. */
29
+ memoryGib: number;
30
+ /** `true` if the duration was inferred (step result missing) rather than read from the handler payload. */
31
+ estimated: boolean;
32
+ }
33
+ /**
34
+ * Result of {@link computeRenderCost}.
35
+ *
36
+ * NOTE: `displayCost` / `accruedSoFarUsd` cover Cloud Run compute + Cloud
37
+ * Workflows steps only. They EXCLUDE GCS storage + network egress for the
38
+ * plan tarball (which can be ~100 MB), chunk artifacts, and the final output
39
+ * — see `breakdown.gcsEstimate`. Treat the figure as a compute-cost floor,
40
+ * not the authoritative total bill.
41
+ */
42
+ export interface RenderCost {
43
+ /** USD accrued to date (Cloud Run + Workflows only; excludes GCS — see note above). */
44
+ accruedSoFarUsd: number;
45
+ /** Human-readable USD string, e.g. `"$0.0214"`. Excludes GCS storage/egress. */
46
+ displayCost: string;
47
+ breakdown: {
48
+ cloudRunUsd: number;
49
+ workflowsUsd: number;
50
+ /** GCS transfer + storage cost varies by tier; we don't try to compute it here. */
51
+ gcsEstimate: "not-included";
52
+ /** `true` if any invocation fell back to estimated billing. */
53
+ estimated: boolean;
54
+ };
55
+ }
56
+ /**
57
+ * Sum Cloud Run vCPU-seconds + GiB-seconds + per-request charges and Cloud
58
+ * Workflows steps into an aggregate USD figure.
59
+ *
60
+ * `workflowSteps` is the count of Workflows steps executed so far — Plan
61
+ * (1) + RenderChunk (chunkCount) + Assemble (1) + the control steps
62
+ * (BuildChunkList, AssertChunkCount, …). Pass the count the progress reader
63
+ * derived from the execution; a rough constant overhead is fine since the
64
+ * step charge is a rounding error next to Cloud Run compute.
65
+ */
66
+ export declare function computeRenderCost(invocations: BilledCloudRunInvocation[], workflowSteps: number): RenderCost;
67
+ //# sourceMappingURL=costAccounting.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"costAccounting.d.ts","sourceRoot":"","sources":["../../src/sdk/costAccounting.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAWH,6DAA6D;AAC7D,MAAM,WAAW,wBAAwB;IACvC,2EAA2E;IAC3E,UAAU,EAAE,MAAM,CAAC;IACnB,yEAAyE;IACzE,IAAI,EAAE,MAAM,CAAC;IACb,+DAA+D;IAC/D,SAAS,EAAE,MAAM,CAAC;IAClB,2GAA2G;IAC3G,SAAS,EAAE,OAAO,CAAC;CACpB;AAED;;;;;;;;GAQG;AACH,MAAM,WAAW,UAAU;IACzB,uFAAuF;IACvF,eAAe,EAAE,MAAM,CAAC;IACxB,gFAAgF;IAChF,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE;QACT,WAAW,EAAE,MAAM,CAAC;QACpB,YAAY,EAAE,MAAM,CAAC;QACrB,mFAAmF;QACnF,WAAW,EAAE,cAAc,CAAC;QAC5B,+DAA+D;QAC/D,SAAS,EAAE,OAAO,CAAC;KACpB,CAAC;CACH;AAED;;;;;;;;;GASG;AACH,wBAAgB,iBAAiB,CAC/B,WAAW,EAAE,wBAAwB,EAAE,EACvC,aAAa,EAAE,MAAM,GACpB,UAAU,CAsBZ"}
@@ -0,0 +1,54 @@
1
+ /**
2
+ * `deploySite` — upload a project directory to GCS once per content hash
3
+ * and return a reusable handle.
4
+ *
5
+ * `renderToCloudRun` calls this implicitly when no `siteHandle` is passed,
6
+ * but exposing it as a standalone verb lets adopters bundle a project ahead
7
+ * of time and reuse the handle across many renders without re-tarring the
8
+ * project tree on every call.
9
+ *
10
+ * The handle is **content-addressed**: `siteId` is derived from a SHA-256
11
+ * over the project files. Two `deploySite` calls on an unchanged tree
12
+ * produce the same `siteId` and short-circuit the upload after a single
13
+ * existence check.
14
+ */
15
+ import { Storage } from "@google-cloud/storage";
16
+ /** Options for {@link deploySite}. */
17
+ export interface DeploySiteOptions {
18
+ /** Local project directory containing `index.html` (and any composition assets). */
19
+ projectDir: string;
20
+ /** GCS bucket the Terraform module provisioned. */
21
+ bucketName: string;
22
+ /**
23
+ * Override the content-addressed site id. Useful when the caller has a
24
+ * stable external identifier they want to use (e.g. a git SHA); if unset,
25
+ * the hash of the project tree picks it.
26
+ */
27
+ siteId?: string;
28
+ /** Injection seam for tests. Production callers leave unset. */
29
+ storage?: Storage;
30
+ }
31
+ /** Stable handle returned by {@link deploySite}. Pass back to {@link renderToCloudRun}. */
32
+ export interface SiteHandle {
33
+ /** Content-addressed (or caller-supplied) identifier; stable across re-uploads of the same tree. */
34
+ siteId: string;
35
+ /** Bucket the site landed in. Surfaced separately so callers don't have to re-parse `projectGcsUri`. */
36
+ bucketName: string;
37
+ /** Full `gs://bucket/sites/<siteId>/project.tar.gz` URI; pass through to `renderToCloudRun`. */
38
+ projectGcsUri: string;
39
+ /** Tarball size in bytes; useful for "did we actually skip the upload?" assertions. */
40
+ bytes: number;
41
+ /** ISO timestamp of the most recent upload OR the existing object the short-circuit found. */
42
+ uploadedAt: string;
43
+ /** `false` if the object already existed and we skipped the upload. */
44
+ uploaded: boolean;
45
+ }
46
+ /**
47
+ * Upload `projectDir` to `gs://bucketName/sites/<siteId>/project.tar.gz`.
48
+ *
49
+ * Short-circuits when an object with the same key already exists in the
50
+ * bucket — `siteId` derives from the project's content hash, so the same
51
+ * bytes produce the same key, and re-uploading would be redundant.
52
+ */
53
+ export declare function deploySite(opts: DeploySiteOptions): Promise<SiteHandle>;
54
+ //# sourceMappingURL=deploySite.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"deploySite.d.ts","sourceRoot":"","sources":["../../src/sdk/deploySite.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAKH,OAAO,EAAE,OAAO,EAAE,MAAM,uBAAuB,CAAC;AAIhD,sCAAsC;AACtC,MAAM,WAAW,iBAAiB;IAChC,oFAAoF;IACpF,UAAU,EAAE,MAAM,CAAC;IACnB,mDAAmD;IACnD,UAAU,EAAE,MAAM,CAAC;IACnB;;;;OAIG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,gEAAgE;IAChE,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAED,2FAA2F;AAC3F,MAAM,WAAW,UAAU;IACzB,oGAAoG;IACpG,MAAM,EAAE,MAAM,CAAC;IACf,wGAAwG;IACxG,UAAU,EAAE,MAAM,CAAC;IACnB,gGAAgG;IAChG,aAAa,EAAE,MAAM,CAAC;IACtB,uFAAuF;IACvF,KAAK,EAAE,MAAM,CAAC;IACd,8FAA8F;IAC9F,UAAU,EAAE,MAAM,CAAC;IACnB,uEAAuE;IACvE,QAAQ,EAAE,OAAO,CAAC;CACnB;AAED;;;;;;GAMG;AAEH,wBAAsB,UAAU,CAAC,IAAI,EAAE,iBAAiB,GAAG,OAAO,CAAC,UAAU,CAAC,CA2C7E"}
@@ -0,0 +1,91 @@
1
+ /**
2
+ * `getRenderProgress` — read-only progress + cost snapshot for a single
3
+ * render started by {@link renderToCloudRun}.
4
+ *
5
+ * Pulls one `GetExecution` per call. Cloud Workflows does not surface
6
+ * per-step payloads through the basic Executions API the way Step Functions
7
+ * exposes its history, so this reader takes a different tack than the AWS
8
+ * adapter: the workflow definition **accumulates** each step's result body
9
+ * (Plan + every RenderChunk + Assemble) and returns them as one structured
10
+ * object. On success we parse that object for frame totals, the output
11
+ * file, and per-step `DurationMs` (which the handler stamps into every
12
+ * result), then compute cost against the service's configured vCPU/memory.
13
+ *
14
+ * Progress is therefore coarse while the execution is ACTIVE (we report
15
+ * `running` with `overallProgress = 0`) and exact once it SUCCEEDS
16
+ * (`overallProgress = 1`, real frame + cost numbers). Mid-flight per-chunk
17
+ * progress would require the Workflows step-entries API; that's a tracked
18
+ * follow-up, not part of the first version.
19
+ */
20
+ import { type RenderCost } from "./costAccounting.js";
21
+ /** Normalised render status. Maps from Cloud Workflows execution states. */
22
+ export type RenderStatus = "running" | "succeeded" | "failed" | "cancelled" | "unknown";
23
+ /** One error surfaced by the execution. */
24
+ export interface RenderError {
25
+ /** Step the failure surfaced in, when recoverable from the error context; else `<execution>`. */
26
+ state: string;
27
+ /** Error class / type. */
28
+ error: string;
29
+ /** Cause string (often a stringified JSON payload from the handler). */
30
+ cause: string;
31
+ }
32
+ /** Snapshot of a single render's progress + cost + errors at one point in time. */
33
+ export interface RenderProgress {
34
+ status: RenderStatus;
35
+ /** `[0, 1]`; coarse while running, exact on success. */
36
+ overallProgress: number;
37
+ framesRendered: number;
38
+ /** `null` until the execution succeeds and the accumulated plan result is read. */
39
+ totalFrames: number | null;
40
+ /** Cloud Run invocations the workflow scheduled (Plan + chunks + Assemble), when known. */
41
+ invocationsObserved: number;
42
+ costs: RenderCost;
43
+ /** Final output object if Assemble succeeded; `null` otherwise. */
44
+ outputFile: {
45
+ gcsUri: string;
46
+ bytes: number | null;
47
+ } | null;
48
+ errors: RenderError[];
49
+ /** `true` once the execution has terminated in a non-success state. */
50
+ fatalErrorEncountered: boolean;
51
+ startedAt: string;
52
+ endedAt: string | null;
53
+ }
54
+ /** Protobuf Timestamp shape the gapic client returns for start/end times. */
55
+ interface ProtoTimestamp {
56
+ seconds?: number | string | null;
57
+ nanos?: number | null;
58
+ }
59
+ /** Subset of a Cloud Workflows Execution this reader consumes. */
60
+ export interface ExecutionRecord {
61
+ name?: string | null;
62
+ state?: string | null;
63
+ result?: string | null;
64
+ error?: {
65
+ payload?: string | null;
66
+ context?: string | null;
67
+ } | null;
68
+ startTime?: ProtoTimestamp | string | null;
69
+ endTime?: ProtoTimestamp | string | null;
70
+ }
71
+ /** Minimal surface of `@google-cloud/workflows`' `ExecutionsClient` for reads. */
72
+ export interface ExecutionsGetClientLike {
73
+ getExecution(req: {
74
+ name: string;
75
+ }): Promise<[ExecutionRecord, ...unknown[]]>;
76
+ }
77
+ /** Options for {@link getRenderProgress}. */
78
+ export interface GetRenderProgressOptions {
79
+ /** Server-assigned execution resource name from a {@link renderToCloudRun} call. */
80
+ executionName: string;
81
+ /** vCPU the Cloud Run service is configured with (for cost). Default 4. */
82
+ vcpu?: number;
83
+ /** Memory in GiB the Cloud Run service is configured with (for cost). Default 16. */
84
+ memoryGib?: number;
85
+ /** Test injection seam — production callers leave unset. */
86
+ executions?: ExecutionsGetClientLike;
87
+ }
88
+ /** Pull a current progress snapshot for one render. */
89
+ export declare function getRenderProgress(opts: GetRenderProgressOptions): Promise<RenderProgress>;
90
+ export {};
91
+ //# sourceMappingURL=getRenderProgress.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"getRenderProgress.d.ts","sourceRoot":"","sources":["../../src/sdk/getRenderProgress.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,OAAO,EAGL,KAAK,UAAU,EAChB,MAAM,qBAAqB,CAAC;AAE7B,4EAA4E;AAC5E,MAAM,MAAM,YAAY,GAAG,SAAS,GAAG,WAAW,GAAG,QAAQ,GAAG,WAAW,GAAG,SAAS,CAAC;AAExF,2CAA2C;AAC3C,MAAM,WAAW,WAAW;IAC1B,iGAAiG;IACjG,KAAK,EAAE,MAAM,CAAC;IACd,0BAA0B;IAC1B,KAAK,EAAE,MAAM,CAAC;IACd,wEAAwE;IACxE,KAAK,EAAE,MAAM,CAAC;CACf;AAED,mFAAmF;AACnF,MAAM,WAAW,cAAc;IAC7B,MAAM,EAAE,YAAY,CAAC;IACrB,wDAAwD;IACxD,eAAe,EAAE,MAAM,CAAC;IACxB,cAAc,EAAE,MAAM,CAAC;IACvB,mFAAmF;IACnF,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,2FAA2F;IAC3F,mBAAmB,EAAE,MAAM,CAAC;IAC5B,KAAK,EAAE,UAAU,CAAC;IAClB,mEAAmE;IACnE,UAAU,EAAE;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAA;KAAE,GAAG,IAAI,CAAC;IAC5D,MAAM,EAAE,WAAW,EAAE,CAAC;IACtB,uEAAuE;IACvE,qBAAqB,EAAE,OAAO,CAAC;IAC/B,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;CACxB;AAED,6EAA6E;AAC7E,UAAU,cAAc;IACtB,OAAO,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAAC;IACjC,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CACvB;AAED,kEAAkE;AAClE,MAAM,WAAW,eAAe;IAC9B,IAAI,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,KAAK,CAAC,EAAE;QAAE,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;KAAE,GAAG,IAAI,CAAC;IACpE,SAAS,CAAC,EAAE,cAAc,GAAG,MAAM,GAAG,IAAI,CAAC;IAC3C,OAAO,CAAC,EAAE,cAAc,GAAG,MAAM,GAAG,IAAI,CAAC;CAC1C;AAED,kFAAkF;AAClF,MAAM,WAAW,uBAAuB;IACtC,YAAY,CAAC,GAAG,EAAE;QAAE,IAAI,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,CAAC,eAAe,EAAE,GAAG,OAAO,EAAE,CAAC,CAAC,CAAC;CAC/E;AAED,6CAA6C;AAC7C,MAAM,WAAW,wBAAwB;IACvC,oFAAoF;IACpF,aAAa,EAAE,MAAM,CAAC;IACtB,2EAA2E;IAC3E,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,qFAAqF;IACrF,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,4DAA4D;IAC5D,UAAU,CAAC,EAAE,uBAAuB,CAAC;CACtC;AAiBD,uDAAuD;AAEvD,wBAAsB,iBAAiB,CAAC,IAAI,EAAE,wBAAwB,GAAG,OAAO,CAAC,cAAc,CAAC,CAoF/F"}
@@ -0,0 +1,17 @@
1
+ /**
2
+ * SDK subpath export — `@hyperframes/gcp-cloud-run/sdk`.
3
+ *
4
+ * Pulled into its own subpath so consumers that only drive renders (CLI, CI
5
+ * scripts, adopter tooling) don't pay the cost of importing `./server.js`,
6
+ * which transitively pulls `puppeteer-core` into the module graph. The SDK
7
+ * files here are GCS + Workflows clients only — safe to load in any Node
8
+ * environment.
9
+ */
10
+ export { deploySite, type DeploySiteOptions, type SiteHandle } from "./deploySite.js";
11
+ export { type ExecutionsClientLike, renderToCloudRun, type RenderHandle, type RenderToCloudRunOptions, } from "./renderToCloudRun.js";
12
+ export { type ExecutionRecord, type ExecutionsGetClientLike, getRenderProgress, type GetRenderProgressOptions, type RenderError, type RenderProgress, type RenderStatus, } from "./getRenderProgress.js";
13
+ export { type BilledCloudRunInvocation, computeRenderCost, type RenderCost, } from "./costAccounting.js";
14
+ export { InvalidConfigError, MAX_WORKFLOWS_INPUT_BYTES, validateDistributedRenderConfig, validateVariablesPayload, validateWorkflowsInputSize, } from "./validateConfig.js";
15
+ export type { SerializableDistributedRenderConfig } from "../events.js";
16
+ export type { DistributedFormat } from "../formatExtension.js";
17
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/sdk/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAE,UAAU,EAAE,KAAK,iBAAiB,EAAE,KAAK,UAAU,EAAE,MAAM,iBAAiB,CAAC;AACtF,OAAO,EACL,KAAK,oBAAoB,EACzB,gBAAgB,EAChB,KAAK,YAAY,EACjB,KAAK,uBAAuB,GAC7B,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EACL,KAAK,eAAe,EACpB,KAAK,uBAAuB,EAC5B,iBAAiB,EACjB,KAAK,wBAAwB,EAC7B,KAAK,WAAW,EAChB,KAAK,cAAc,EACnB,KAAK,YAAY,GAClB,MAAM,wBAAwB,CAAC;AAChC,OAAO,EACL,KAAK,wBAAwB,EAC7B,iBAAiB,EACjB,KAAK,UAAU,GAChB,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EACL,kBAAkB,EAClB,yBAAyB,EACzB,+BAA+B,EAC/B,wBAAwB,EACxB,0BAA0B,GAC3B,MAAM,qBAAqB,CAAC;AAC7B,YAAY,EAAE,mCAAmC,EAAE,MAAM,cAAc,CAAC;AACxE,YAAY,EAAE,iBAAiB,EAAE,MAAM,uBAAuB,CAAC"}