@hyperframes/producer 0.8.181 → 0.8.184

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.
@@ -68,6 +68,7 @@ export declare function assemble(planDir: string, chunkOutputs: AssembleChunkPat
68
68
  */
69
69
  cfr?: boolean;
70
70
  }): Promise<AssembleResult>;
71
- /** Chunks that carry their audio are stitched under one index, the same bytes as the S3 stitch. */
71
+ /** Chunks carrying audio, or with it as a separate file, stitch under one index like S3's. */
72
72
  export declare function chunksAreStitchedMp4(planDir: string, cfr: boolean): boolean;
73
+ export declare function stitchesSeparateAudio(planDir: string): boolean;
73
74
  export {};
@@ -1,4 +1,4 @@
1
- import { type BeforeCaptureHook, BROWSER_GPU_NOT_SOFTWARE, type CaptureOptions, type EngineConfig, type ExtractedFrames, type FrameLookupTable } from "@hyperframes/engine";
1
+ import { type BeforeCaptureHook, BROWSER_GPU_NOT_SOFTWARE, type CaptureOptions, type EngineConfig, type ExtractedFrames, type FrameLookupTable, type ShownBox } from "@hyperframes/engine";
2
2
  import type { LockedRenderConfig } from "../render/stages/freezePlan.js";
3
3
  import type { PlanV2RemoteSources } from "./planV2.js";
4
4
  import { type DistributedFormat, INVALID_VIDEO_METADATA, type PlanVideosJson } from "./shared.js";
@@ -39,6 +39,10 @@ export declare function buildChunkCaptureOptions(input: {
39
39
  videoMetadataHints?: CaptureOptions["videoMetadataHints"];
40
40
  }): CaptureOptions;
41
41
  /** Extracts the given frames of deferred videos under the names a full extraction gives them. */
42
+ export declare function shownDeferredFrameIndexes(planVideos: PlanVideosJson, frames: Iterable<number>, fps: {
43
+ num: number;
44
+ den: number;
45
+ }): Map<string, Set<number>>;
42
46
  export declare function extractDeferredVideoFramesForChunk(input: {
43
47
  planDir: string;
44
48
  planVideos: PlanVideosJson;
@@ -50,4 +54,5 @@ export declare function extractDeferredVideoFramesForChunk(input: {
50
54
  outputRoot: string;
51
55
  cfg: Pick<EngineConfig, "ffmpegProcessTimeout">;
52
56
  remoteSources?: PlanV2RemoteSources;
57
+ shownBoxes?: ReadonlyMap<string, ShownBox>;
53
58
  }): Promise<ExtractedFrames[]>;
@@ -31,9 +31,9 @@ export interface TrackTable {
31
31
  }
32
32
  /** Each track's sample tables, expanded to one entry per sample (and per chunk). */
33
33
  export declare function readTracks(moov: Buffer): TrackTable[];
34
- export declare function buildStitchedHeader(layouts: readonly Mp4Layout[]): Buffer;
34
+ export declare function buildStitchedHeader(layouts: readonly Mp4Layout[], audio?: Mp4Layout): Buffer;
35
35
  export declare function fileRange(path: string): ReadRange;
36
- /** Local join: the stitched header, then each chunk's mdat payload in order. Like the ffmpeg
37
- * join, it writes in a work folder beside `outputPath` and renames only a finished file there. */
38
- export declare function writeStitchedMp4(chunkPaths: readonly string[], outputPath: string): Promise<void>;
36
+ /** Local join: the header, then `audioPath`'s and each chunk's payload. Like the ffmpeg join, it
37
+ * writes in a work folder beside `outputPath` and renames only a finished file there. */
38
+ export declare function writeStitchedMp4(chunkPaths: readonly string[], outputPath: string, audioPath?: string): Promise<void>;
39
39
  export {};
@@ -211,6 +211,8 @@ export interface BuildLocalExecutionPlanOptions {
211
211
  readonly chunksCarryAudio?: boolean;
212
212
  /** With `chunksCarryAudio`, also write each chunk's audio packets to their own file. Plan v2 only. */
213
213
  readonly sliceChunkAudio?: boolean;
214
+ /** Leave the audio to {@link buildPlanAudio}; chunks render video only. mp4 without `cfr` only. */
215
+ readonly deferAudio?: boolean;
214
216
  }
215
217
  export interface RemoteProjectFile {
216
218
  readonly url: string;
@@ -401,7 +403,16 @@ export declare function resolveDistributedEngineConfig(config: DistributedRender
401
403
  * Build the shared local execution representation used by both transport
402
404
  * protocols. See the module docstring for the directory layout.
403
405
  */
406
+ /** Whether the plan leaves its audio to {@link buildPlanAudio}. */
407
+ export declare function plansSeparateAudio(config: Pick<DistributedRenderConfig, "format" | "cfr">, options: Pick<BuildLocalExecutionPlanOptions, "deferAudio">): boolean;
404
408
  export declare function buildLocalExecutionPlan(projectDir: string, config: DistributedRenderConfig, executionPlanDir: string, options?: Readonly<BuildLocalExecutionPlanOptions>): Promise<LocalExecutionPlan>;
409
+ /**
410
+ * The audio of a plan made with `deferAudio`: the mix and true-peak hold that plan would have
411
+ * made, written to `outputPath` (on `workRoot`'s filesystem) at exactly the video's length.
412
+ */
413
+ export declare function buildPlanAudio(projectDir: string, config: DistributedRenderConfig, workRoot: string, outputPath: string, options?: Pick<BuildLocalExecutionPlanOptions, "remoteFiles">): Promise<{
414
+ hasAudio: boolean;
415
+ }>;
405
416
  /**
406
417
  * Legacy v1 transport wrapper around {@link buildLocalExecutionPlan}.
407
418
  *
@@ -91,6 +91,7 @@ export interface PlanV2WithPublisherOptions {
91
91
  /** See {@link BuildLocalExecutionPlanOptions.remoteFiles}; needs a publisher with `referenceStoredBlob`. */
92
92
  readonly remoteFiles?: BuildLocalExecutionPlanOptions["remoteFiles"];
93
93
  readonly chunksCarryAudio?: boolean;
94
+ readonly deferAudio?: boolean;
94
95
  }
95
96
  export interface PlanV2MaterializationResult {
96
97
  readonly planDir: string;
@@ -22,9 +22,6 @@ export declare function listSiteFiles(projectDir: string): string[];
22
22
  * same-bytes rename still yields a fresh id. We trim to 16 chars because the
23
23
  * full 64 isn't useful in an object key for legibility. Pass `files` when the
24
24
  * caller already listed them, so the hashed set is the shipped set.
25
- *
26
- * Reads are synchronous: project trees are typically tens of MB at most
27
- * (HTML/CSS/JS plus a few composition assets), so the simpler shape wins over
28
- * a streaming pipeline.
25
+ * Files are read in fixed-size chunks, so no file is ever held whole in memory.
29
26
  */
30
27
  export declare function hashProjectDir(projectDir: string, files?: readonly string[]): string;
@@ -73,7 +73,7 @@ export interface ChunkResult {
73
73
  sessionBootMs: number;
74
74
  captureStageMs: number;
75
75
  encodeStageMs: number;
76
- /** Capture workers used for this chunk (`calculateOptimalWorkers` result). */
76
+ /** Capture workers used for this chunk (`resolveChunkCaptureWorkers` result). */
77
77
  workers: number;
78
78
  /**
79
79
  * Effective engine mode used by every worker, after any browser fallback.
@@ -110,11 +110,18 @@ export interface RenderChunkOptions {
110
110
  * whole slice.
111
111
  */
112
112
  frameRange?: ChunkFrameRange;
113
+ /**
114
+ * Capture browsers the runtime can host. Raises the machine-wide sizing up to
115
+ * this many, one browser per 20 frames; never lowers it.
116
+ */
117
+ maxCaptureWorkers?: number;
113
118
  }
119
+ /** Capture browsers for a chunk: the machine-wide sizing, raised by the runtime's budget. */
120
+ export declare function resolveChunkCaptureWorkers(framesInChunk: number, machineSized: number, maxCaptureWorkers: number | undefined): number;
114
121
  /** The frames a chunk renders: its slice, or a validated sub-range of it. */
115
122
  export declare function resolveChunkFrameRange(slice: ChunkSliceJson, chunkIndex: number, frameRange: ChunkFrameRange | undefined): ChunkFrameRange;
116
123
  /** Compatibility-safe adapter seam for built-in or injected chunk renderers. */
117
- export type ChunkRenderer = (planDir: string, chunkIndex: number, outputChunkPath: string) => Promise<ChunkResult>;
124
+ export type ChunkRenderer = (planDir: string, chunkIndex: number, outputChunkPath: string, options?: RenderChunkOptions) => Promise<ChunkResult>;
118
125
  interface DistributedCaptureSessionDependencies {
119
126
  createCaptureSession: typeof createCaptureSession;
120
127
  assertSwiftShader: typeof assertSwiftShader;
@@ -22,6 +22,8 @@ export interface ArtifactTransactionFileSystem {
22
22
  export interface ArtifactDurationProbeResult {
23
23
  durationSeconds: number;
24
24
  frames?: number;
25
+ width?: number;
26
+ height?: number;
25
27
  }
26
28
  /**
27
29
  * Pluggable probe used by `ArtifactTransaction.validate()` when the caller
@@ -60,6 +62,9 @@ export interface ArtifactValidationExpectation {
60
62
  expectedDurationSeconds: number;
61
63
  fps?: number;
62
64
  expectedFrames?: number;
65
+ /** The output size the render was asked for; a route that ignores it must not publish. */
66
+ expectedWidth?: number;
67
+ expectedHeight?: number;
63
68
  toleranceSeconds?: number;
64
69
  /** Path relative to the staging directory to run the duration probe against. */
65
70
  probeTarget?: string;
@@ -84,6 +89,8 @@ export declare function buildArtifactExpectation(input: {
84
89
  durationSeconds: number;
85
90
  fps: number;
86
91
  expectedFrames: number | undefined;
92
+ width?: number;
93
+ height?: number;
87
94
  }): ArtifactValidationExpectation | undefined;
88
95
  /**
89
96
  * Stages a render beside its final destination and promotes only a validated
@@ -54,12 +54,16 @@ export interface PadTrimAudioInput {
54
54
  probeVideoFrameInfo?: (videoPath: string) => Promise<ProbeVideoFrameInfo>;
55
55
  probeAudioInfo?: (audioPath: string, signal?: AbortSignal) => Promise<AudioProbeInfo>;
56
56
  probeAudioTruePeakDbfs?: (audioPath: string, signal?: AbortSignal) => Promise<number>;
57
- runFfmpeg?: (args: string[]) => Promise<{
58
- success: boolean;
59
- error?: string;
60
- failureReason?: "external_interruption";
61
- }>;
57
+ runFfmpeg?: (args: string[]) => Promise<StepResult>;
58
+ encodeAac?: AacEncoder;
62
59
  }
60
+ interface StepResult {
61
+ success: boolean;
62
+ error?: string;
63
+ failureReason?: "external_interruption";
64
+ }
65
+ /** Encodes `filter` applied to `inputPath` as AAC of exactly `durationSeconds`. */
66
+ type AacEncoder = (inputPath: string, outputPath: string, durationSeconds: number, filter: string) => Promise<StepResult>;
63
67
  export type PadTrimOperation = "pad" | "trim" | "copy";
64
68
  export interface PadTrimAudioResult {
65
69
  success: boolean;
@@ -77,19 +81,21 @@ export interface PadTrimAudioResult {
77
81
  /** dB the true-peak limiter lowered the whole mix by; absent when it did not engage. */
78
82
  audioLoweredDb?: number;
79
83
  }
80
- export type PadTrimAudioStepKind = "copy" | "trim" | "normalize";
81
- export interface PadTrimAudioStep {
82
- kind: PadTrimAudioStepKind;
84
+ export type PadTrimAudioStep = {
85
+ kind: "copy";
83
86
  args: string[];
84
- }
87
+ } | {
88
+ kind: "trim" | "normalize";
89
+ filter: string;
90
+ };
85
91
  export interface PadTrimAudioPlan {
86
92
  operation: PadTrimOperation;
87
93
  steps: PadTrimAudioStep[];
88
94
  cleanupPaths: string[];
89
95
  }
90
96
  /**
91
- * Pure helper: decide the pad/trim operation and build the ffmpeg argv
92
- * sequence that materializes it. Exported separately so unit tests can pin
97
+ * Pure helper: decide the pad/trim operation and the steps that
98
+ * materialize it. Exported separately so unit tests can pin
93
99
  * every branch without spawning ffmpeg.
94
100
  *
95
101
  * - `sourceDuration < targetDuration` → pad with `apad` to the exact target
@@ -102,10 +108,6 @@ export interface PadTrimAudioPlan {
102
108
  * run ffmpeg with `-c:a copy` to materialize the output path.
103
109
  */
104
110
  export declare function buildPadTrimAudioPlan(audioPath: string, outputPath: string, sourceDurationSeconds: number, targetDurationSeconds: number): PadTrimAudioPlan;
105
- export declare function buildPadTrimAudioArgs(audioPath: string, outputPath: string, sourceDurationSeconds: number, targetDurationSeconds: number): {
106
- args: string[];
107
- operation: PadTrimOperation;
108
- };
109
111
  /**
110
112
  * Pad or trim the mixed audio so its exact duration matches `frameCount / fps`
111
113
  * for the assembled video.
@@ -117,6 +119,8 @@ export declare function padOrTrimAudioToVideoFrameCount(input: PadTrimAudioInput
117
119
  */
118
120
  export declare function constrainAacTruePeak(input: {
119
121
  audioPath: string;
122
+ /** The PCM `audioPath` was encoded from: a correction re-encodes it instead of decoding the AAC. */
123
+ sourcePath?: string;
120
124
  targetDurationSeconds: number;
121
125
  signal?: AbortSignal;
122
126
  timeoutMs?: number;
@@ -128,3 +132,4 @@ export declare function constrainAacTruePeak(input: {
128
132
  }>;
129
133
  export declare function defaultProbeVideoFrameInfo(videoPath: string, signal?: AbortSignal): Promise<ProbeVideoFrameInfo>;
130
134
  export declare function runFfprobeJson<T>(args: string[], signal?: AbortSignal): Promise<T>;
135
+ export {};
@@ -1,12 +1,8 @@
1
1
  /**
2
2
  * The true peak one `ebur128=peak=true` pass over the whole file reports, measured in parallel
3
- * segments when the length is known and the rate is 48 kHz: the same digit, not an estimate.
3
+ * segments when the file is 48 kHz with a known length: the same digit, not an estimate.
4
4
  */
5
5
  export declare function measureTruePeakDbfs(audioPath: string, options: {
6
6
  timeoutMs: number;
7
7
  signal?: AbortSignal;
8
- segments?: {
9
- durationSeconds: number;
10
- sampleRate: number;
11
- };
12
8
  }): Promise<number>;
@@ -50,6 +50,8 @@ export declare function roundDb(value: number | undefined): number | undefined;
50
50
  export interface DrawElementPerfInput {
51
51
  compileGate?: string;
52
52
  clampReason?: string;
53
+ /** Engine init-time gate the pre-capture probe hit; its session may be closed before capture. */
54
+ probeGateReason?: string;
53
55
  workerInversion?: "inverted" | "reverted";
54
56
  /** Auto-resolved worker count before the inversion pinned it to 1 (set only when the inversion fired). */
55
57
  preInversionWorkers?: number;
@@ -34,6 +34,8 @@ export interface AudioStageInput {
34
34
  audios: CompositionMetadata["audios"];
35
35
  masterFxChain?: string;
36
36
  urlByAbsentLocalPath?: ReadonlyMap<string, string>;
37
+ /** Keep the float mix the AAC was encoded from here, for a later re-encode from it. */
38
+ keepEncoderInputAt?: string;
37
39
  abortSignal: AbortSignal | undefined;
38
40
  assertNotAborted: () => void;
39
41
  /** Where a per-track failure's detail goes. Optional so tests need not pass one. */
@@ -34,10 +34,10 @@
34
34
  * `safeCleanup`, `sampleDirectoryBytes`, etc.) into a shared module so
35
35
  * the stages can import them without reaching back into the orchestrator.
36
36
  */
37
- import { type BeforeCaptureHook, type CaptureOptions, type CapturePerfSummary, type CaptureSession, type EngineConfig } from "@hyperframes/engine";
37
+ import { type BeforeCaptureHook, type CaptureOptions, type CapturePerfSummary, type CaptureSession, type EngineConfig, type LandedFrame } from "@hyperframes/engine";
38
38
  import type { FileServerHandle } from "../../fileServer.js";
39
39
  import type { ProducerLogger } from "../../../logger.js";
40
- import { type CaptureAttemptSummary, type ProgressCallback, type RenderJob } from "../../renderOrchestrator.js";
40
+ import { type CaptureAttemptSummary, type ProgressCallback, type RenderConfig, type RenderJob } from "../../renderOrchestrator.js";
41
41
  import type { SdrDiskCapturePlan } from "../capturePlan.js";
42
42
  export interface CaptureStageInput {
43
43
  fileServer: FileServerHandle;
@@ -87,6 +87,7 @@ export interface CaptureStageInput {
87
87
  startFrame: number;
88
88
  endFrame: number;
89
89
  };
90
+ onFrameLanded?: (frame: LandedFrame) => void;
90
91
  }
91
92
  export interface CaptureStageResult {
92
93
  /** Final worker count after any adaptive retry. */
@@ -121,8 +122,13 @@ export type DiskCaptureHeadroom = {
121
122
  };
122
123
  /** Shared 90% disk gate used by both fallback planning and disk execution. */
123
124
  export declare function inspectDiskCaptureHeadroom(framesDir: string, totalFrames: number, captureOptions: CaptureOptions, freeDiskBytes?: (path: string) => number | null): DiskCaptureHeadroom;
124
- export declare function diskCaptureShortfallError(needBytes: number, freeBytes: number, framesDir: string, basis: "estimate" | "measured"): Error;
125
- export declare function assertDiskCaptureHeadroom(framesDir: string, totalFrames: number, captureOptions: CaptureOptions, freeDiskBytes?: (path: string) => number | null): void;
125
+ /**
126
+ * Why this render stores frames and what would take it off disk. `--workers 1`
127
+ * is offered only when a one-worker run of the same render would stream.
128
+ */
129
+ export declare function diskCaptureRemedy(cfg: EngineConfig, outputFormat: NonNullable<RenderConfig["format"]>, durationSeconds: number, workerCount: number): string;
130
+ export declare function diskCaptureShortfallError(needBytes: number, freeBytes: number, framesDir: string, basis: "estimate" | "measured", remedy: string): Error;
131
+ export declare function assertDiskCaptureHeadroom(framesDir: string, totalFrames: number, captureOptions: CaptureOptions, remedy: string, freeDiskBytes?: (path: string) => number | null): void;
126
132
  /** Frame bytes written so far: the merged frames dir plus every capture attempt's worker dirs. */
127
133
  export declare function measureCaptureFrameBytes(workDir: string, framesDir: string): number;
128
134
  /** Per-frame callback: after the sample frames, throws if the projected remainder exceeds 90% of free space. */
@@ -130,6 +136,7 @@ export declare function createDiskCaptureProjection(input: {
130
136
  framesDir: string;
131
137
  totalFrames: number;
132
138
  measureBytes: () => number;
139
+ remedy: string;
133
140
  freeDiskBytes?: (path: string) => number | null;
134
141
  }): (capturedFrames: number) => void;
135
142
  export declare function runCaptureStage(input: CaptureStageInput): Promise<CaptureStageResult>;
@@ -26,8 +26,12 @@
26
26
  * branch + same args.
27
27
  * - `Encoding failed: <err>` throws on the encoder's
28
28
  * `success: false`.
29
+ *
30
+ * `encodeWhileCapturing` runs the same mp4 encode during multi-worker disk
31
+ * capture, feeding frames in order as they land, and falls back to this
32
+ * stage's encode if it fails.
29
33
  */
30
- import { getEncoderPreset, type EngineConfig } from "@hyperframes/engine";
34
+ import { getEncoderPreset, type EngineConfig, type LandedFrame } from "@hyperframes/engine";
31
35
  import type { ProducerLogger } from "../../../logger.js";
32
36
  import type { ProgressCallback, RenderJob } from "../../renderOrchestrator.js";
33
37
  export interface EncodeStageInput {
@@ -71,7 +75,7 @@ export interface EncodeStageInput {
71
75
  enableChunkedEncode: boolean;
72
76
  chunkedEncodeSize: number;
73
77
  /** Already-resolved engine config from the orchestrator; direct callers fall back below. */
74
- engineConfig?: Pick<EngineConfig, "ffmpegEncodeTimeout" | "vp9CpuUsed">;
78
+ engineConfig?: Pick<EngineConfig, "ffmpegEncodeTimeout" | "vp9CpuUsed"> & Partial<Pick<EngineConfig, "ffmpegStreamingTimeout">>;
75
79
  abortSignal: AbortSignal | undefined;
76
80
  assertNotAborted: () => void;
77
81
  onProgress?: ProgressCallback;
@@ -97,4 +101,24 @@ export interface EncodeStageResult {
97
101
  /** Wall-clock ms for the encode (or png-copy) phase. */
98
102
  encodeMs: number;
99
103
  }
104
+ /**
105
+ * Software libx264 from JPEG only: the streaming encoder then runs the disk encoder's
106
+ * arguments (same bytes). Needs a core the capture workers leave free.
107
+ */
108
+ export declare function canEncodeWhileCapturing(input: EncodeStageInput, workerCount: number, cpuCount: number): boolean;
109
+ export interface EncodeWhileCapturing {
110
+ /** A worker finished writing this frame. */
111
+ onFrameLanded: (frame: LandedFrame) => void;
112
+ /** Capture succeeded and every frame is in `framesDir`: encode the rest. */
113
+ finish: () => Promise<EncodeStageResult>;
114
+ /** Capture failed: stop the encoder and wait for it to exit. */
115
+ abort: () => Promise<void>;
116
+ /** Frames handed to the encoder so far. */
117
+ readonly framesFed: number;
118
+ }
119
+ /**
120
+ * Feeds frames 0, 1, 2… to the encoder as each lands; capture never waits on it.
121
+ * On failure `finish` re-runs `runEncodeStage` on the same frames.
122
+ */
123
+ export declare function encodeWhileCapturing(input: EncodeStageInput, totalFrames: number): EncodeWhileCapturing;
100
124
  export declare function runEncodeStage(input: EncodeStageInput): Promise<EncodeStageResult>;
@@ -70,6 +70,7 @@ export interface LockedRenderConfig {
70
70
  */
71
71
  variables?: Record<string, unknown>;
72
72
  chunksCarryAudio?: true;
73
+ separateAudio?: true;
73
74
  }
74
75
  export interface CompositionMetadataJson {
75
76
  durationSeconds: number;
@@ -1152,8 +1152,8 @@ export declare function closeOrphanedProbeForRetry(probe: CaptureSession, closer
1152
1152
  */
1153
1153
  export declare function resolveParallelCaptureMode(args: {
1154
1154
  platform: NodeJS.Platform;
1155
- /** A chrome-headless-shell binary was resolved for this render. */
1156
- headlessShell: boolean;
1155
+ /** The chrome-headless-shell binary resolved for this render, if any. */
1156
+ headlessShellPath: string | undefined;
1157
1157
  forceScreenshot: boolean;
1158
1158
  deviceScaleFactor?: number;
1159
1159
  }): "screenshot" | "beginframe";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hyperframes/producer",
3
- "version": "0.8.181",
3
+ "version": "0.8.184",
4
4
  "description": "HTML-to-video rendering engine using Chrome's BeginFrame API",
5
5
  "repository": {
6
6
  "type": "git",
@@ -35,11 +35,11 @@
35
35
  "postcss": "^8.4.0",
36
36
  "puppeteer": "^25.2.1",
37
37
  "puppeteer-core": "^25.2.1",
38
- "@hyperframes/core": "0.8.181",
39
- "@hyperframes/lint": "0.8.181",
40
- "@hyperframes/engine": "0.8.181",
41
- "@hyperframes/parsers": "0.8.181",
42
- "@hyperframes/studio-server": "0.8.181"
38
+ "@hyperframes/core": "0.8.184",
39
+ "@hyperframes/engine": "0.8.184",
40
+ "@hyperframes/lint": "0.8.184",
41
+ "@hyperframes/parsers": "0.8.184",
42
+ "@hyperframes/studio-server": "0.8.184"
43
43
  },
44
44
  "devDependencies": {
45
45
  "@types/node": "^25.0.10",
@@ -48,7 +48,7 @@
48
48
  "tsx": "^4.21.0",
49
49
  "typescript": "^5.7.2",
50
50
  "vitest": "^4.1.11",
51
- "@hyperframes/aws-lambda": "0.8.181"
51
+ "@hyperframes/aws-lambda": "0.8.184"
52
52
  },
53
53
  "engines": {
54
54
  "node": ">=22"