@camstack/types 1.2.266 → 1.2.268

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (57) hide show
  1. package/dist/addon.js +4 -4
  2. package/dist/addon.mjs +3 -3
  3. package/dist/capabilities/battery.cap.d.ts +17 -2
  4. package/dist/capabilities/capability-definition.d.ts +22 -0
  5. package/dist/capabilities/composer.cap.d.ts +99 -0
  6. package/dist/capabilities/consumables.cap.d.ts +79 -0
  7. package/dist/capabilities/core-blocks.cap.d.ts +1149 -91
  8. package/dist/capabilities/device-extension.cap.d.ts +12 -13
  9. package/dist/capabilities/device-state.cap.d.ts +186 -2
  10. package/dist/capabilities/index.d.ts +3 -3
  11. package/dist/capabilities/pipeline-analytics.cap.d.ts +14 -0
  12. package/dist/composition/composition-graph.d.ts +42 -9
  13. package/dist/composition/composition-items.d.ts +25 -0
  14. package/dist/composition/composition-report.d.ts +93 -2
  15. package/dist/composition/composition.d.ts +154 -3
  16. package/dist/composition/customization-name.d.ts +6 -0
  17. package/dist/composition/evaluate-field.d.ts +12 -0
  18. package/dist/composition/examples.d.ts +33 -0
  19. package/dist/composition/field-kinds.d.ts +42 -0
  20. package/dist/composition/index.d.ts +1 -0
  21. package/dist/composition/plan-result.d.ts +16 -0
  22. package/dist/composition/state-fields.d.ts +6 -0
  23. package/dist/composition/validate-composition.d.ts +44 -9
  24. package/dist/composition-CPFIlFfw.mjs +1759 -0
  25. package/dist/composition-QlOB7QTG.js +2184 -0
  26. package/dist/device/device-context.d.ts +9 -0
  27. package/dist/device/device-runtime-state.d.ts +15 -1
  28. package/dist/device/index.d.ts +1 -1
  29. package/dist/device-extension/slot-descriptor.d.ts +8 -11
  30. package/dist/device-extension/slot-provider.d.ts +0 -2
  31. package/dist/enums/event-category.d.ts +25 -0
  32. package/dist/enums.js +1 -1
  33. package/dist/enums.mjs +1 -1
  34. package/dist/err-msg-DX6i_MY4.mjs +339 -0
  35. package/dist/err-msg-Dx2Kor0g.js +368 -0
  36. package/dist/{event-category-BVDXG4tB.mjs → event-category-C5xZWqz6.mjs} +31 -1
  37. package/dist/{event-category-BVfsrBYA.js → event-category-MbnB-6AN.js} +48 -0
  38. package/dist/generated/addon-api.d.ts +42 -0
  39. package/dist/generated/device-proxy.d.ts +1 -1
  40. package/dist/generated/method-access-map.d.ts +1 -1
  41. package/dist/generated/method-device-selectors.d.ts +2 -2
  42. package/dist/generated/system-proxy.d.ts +2 -2
  43. package/dist/index.d.ts +4 -3
  44. package/dist/index.js +1548 -1660
  45. package/dist/index.mjs +1243 -1388
  46. package/dist/interfaces/kernel-abstractions.d.ts +16 -0
  47. package/dist/interfaces/status-overlay.d.ts +42 -0
  48. package/dist/node.d.ts +1 -0
  49. package/dist/node.js +29 -8
  50. package/dist/node.mjs +23 -3
  51. package/dist/{sleep-8K9gue-H.js → sleep-BSzia-xT.js} +6 -352
  52. package/dist/{sleep-DC-wdyeS.mjs → sleep-jkRQgtSc.mjs} +7 -329
  53. package/package.json +1 -1
  54. package/dist/canonical-hash-CPK2Dy60.mjs +0 -715
  55. package/dist/canonical-hash-CSE4ioRi.js +0 -792
  56. package/dist/err-msg-COpsHMw2.js +0 -18
  57. package/dist/err-msg-IQTHeDzc.mjs +0 -13
@@ -0,0 +1,2184 @@
1
+ const require_err_msg = require("./err-msg-Dx2Kor0g.js");
2
+ let zod = require("zod");
3
+ let node_crypto = require("node:crypto");
4
+ //#region src/ffmpeg/invocation.ts
5
+ var AUDIO_ENCODER_BY_CODEC = {
6
+ opus: "libopus",
7
+ aac: "aac",
8
+ pcmu: "pcm_mulaw",
9
+ pcma: "pcm_alaw"
10
+ };
11
+ /**
12
+ * Camera-microphone audio, per codec. Lives HERE rather than in
13
+ * `encode-defaults.ts` only to avoid an import cycle (`encode-defaults` depends
14
+ * on these types); it is re-exported from there, which is where to read it.
15
+ *
16
+ * Every source in this repo is a mono camera mic. The former broker preset
17
+ * encoded Opus at `channels: 2`, spending bitrate duplicating one channel —
18
+ * that is the value this consolidation changed.
19
+ */
20
+ var AUDIO_PRESETS = {
21
+ aac: {
22
+ kind: "encode",
23
+ codec: "aac",
24
+ bitrateKbps: 128,
25
+ sampleRateHz: 48e3,
26
+ channels: 1
27
+ },
28
+ opus: {
29
+ kind: "encode",
30
+ codec: "opus",
31
+ bitrateKbps: 64,
32
+ sampleRateHz: 48e3,
33
+ channels: 1
34
+ },
35
+ pcmu: {
36
+ kind: "encode",
37
+ codec: "pcmu",
38
+ sampleRateHz: 8e3,
39
+ channels: 1
40
+ }
41
+ };
42
+ /** `-hide_banner -loglevel <level>` — every ffmpeg site opens with this. */
43
+ function logBannerArgs(level) {
44
+ return [
45
+ "-hide_banner",
46
+ "-loglevel",
47
+ level
48
+ ];
49
+ }
50
+ /** `true` when the resolved value means "decode in software" (⇒ no `-hwaccel`). */
51
+ function isSoftwareDecode(decodeHwAccel) {
52
+ return !decodeHwAccel || decodeHwAccel === "none" || decodeHwAccel === "copy";
53
+ }
54
+ /**
55
+ * Every INPUT option, in order, terminated by `-i <url>`. Nothing may be
56
+ * appended to this list by a caller — that is the whole point of the function.
57
+ */
58
+ function buildInputArgs(input, decodeHwAccel) {
59
+ const args = [];
60
+ if (!isSoftwareDecode(decodeHwAccel)) args.push("-hwaccel", String(decodeHwAccel));
61
+ if (input.extraArgs?.length) args.push(...input.extraArgs);
62
+ if (input.decodeThreadCount !== void 0 && input.decodeThreadCount > 0) args.push("-threads", String(input.decodeThreadCount));
63
+ if (input.analyzeDurationUs !== void 0) args.push("-analyzeduration", String(input.analyzeDurationUs));
64
+ if (input.probeSizeBytes !== void 0) args.push("-probesize", String(input.probeSizeBytes));
65
+ if (input.lowDelay === true) args.push("-flags", "low_delay");
66
+ if (input.useWallclockTimestamps === true) args.push("-use_wallclock_as_timestamps", "1");
67
+ if (input.fflags?.length) for (const flag of input.fflags) args.push("-fflags", flag);
68
+ if (input.rtspTransport) args.push("-rtsp_transport", input.rtspTransport);
69
+ if (input.rawVideo !== void 0) {
70
+ const raw = input.rawVideo;
71
+ args.push("-f", "rawvideo", "-pix_fmt", raw.pixelFormat, "-video_size", `${String(raw.width)}x${String(raw.height)}`, "-framerate", String(raw.framerate));
72
+ }
73
+ args.push("-i", input.url);
74
+ return args;
75
+ }
76
+ /**
77
+ * Derive the output `-map` selectors. The three cases, in the order they are
78
+ * decided:
79
+ *
80
+ * 1. **A filter graph** — the picture is the graph's declared video pad, and
81
+ * the audio is its audio pad when it has one, otherwise input 0's mic. A
82
+ * graph's output is not addressable any other way.
83
+ * 2. **N inputs, no graph** — map input 0 EXPLICITLY. ffmpeg's own selection
84
+ * picks the "best" stream across ALL inputs, so the egress would silently
85
+ * become whichever camera happens to have the largest frame.
86
+ * 3. **One input, no graph** — no `-map`. Anything else would change the argv
87
+ * of every call site that exists today for no gain.
88
+ */
89
+ function resolveStreamMaps(selection) {
90
+ const graph = selection.filterGraph;
91
+ if (graph) return {
92
+ video: `[${graph.videoOutLabel}]`,
93
+ audio: graph.audioOutLabel ? `[${graph.audioOutLabel}]` : "0:a:0?"
94
+ };
95
+ if (selection.extraInputs?.length) return {
96
+ video: "0:v:0",
97
+ audio: "0:a:0?"
98
+ };
99
+ return {
100
+ video: null,
101
+ audio: null
102
+ };
103
+ }
104
+ /** The `-vf` filter args, or `[]` when a consumer `-vf` already claims the slot. */
105
+ function buildVideoFilterArgs(scale, outputArgs, filterGraph, hwUpload) {
106
+ if (filterGraph) {
107
+ if (scale) throw new Error("FfmpegInvocation: video.scale cannot be combined with a filter graph — scale inside the graph instead (the -vf would feed an output the graph already feeds)");
108
+ if (outputArgs.some((a) => a === "-vf")) throw new Error("FfmpegInvocation: outputArgs carries a -vf while a filter graph feeds the same output — fold the filter into the graph");
109
+ return [];
110
+ }
111
+ if (outputArgs.some((a) => a === "-vf")) return [];
112
+ if (!scale) return hwUpload.length > 0 ? ["-vf", hwUpload] : [];
113
+ const suffix = hwUpload.length > 0 ? `,${hwUpload}` : "";
114
+ if (scale.mode === "exact") return ["-vf", `scale=${scale.width}:${scale.height}${suffix}`];
115
+ return ["-vf", `scale='min(${scale.width},iw)':'min(${scale.height},ih)':force_original_aspect_ratio=decrease:force_divisible_by=2${suffix}`];
116
+ }
117
+ /**
118
+ * The upload filter a hardware encode needs, or `''`.
119
+ *
120
+ * Nothing is uploaded when the DECODE already left the frames on the GPU
121
+ * (`-hwaccel vaapi`): they are surfaces already, and uploading a surface is an
122
+ * error rather than a no-op.
123
+ */
124
+ function resolveHwUpload(device, decodeHwAccel) {
125
+ if (device === void 0) return "";
126
+ if (!isSoftwareDecode(decodeHwAccel)) return "";
127
+ return "format=nv12,hwupload";
128
+ }
129
+ /** Rate-control args for an encode plan. */
130
+ function buildRateControlArgs(video) {
131
+ const kbps = video.bitrateKbps;
132
+ if (kbps === void 0) return [];
133
+ const rc = video.rateControl ?? {
134
+ kind: "cap",
135
+ vbvSeconds: 2
136
+ };
137
+ const bufsize = Math.max(1, Math.round(kbps * rc.vbvSeconds));
138
+ return [
139
+ ...rc.kind === "cbr" ? ["-b:v", `${kbps}k`] : [],
140
+ "-maxrate",
141
+ `${kbps}k`,
142
+ "-bufsize",
143
+ `${bufsize}k`
144
+ ];
145
+ }
146
+ /** The whole video block (`-vf` … `-c:v` … knobs), after `-i`. */
147
+ function buildVideoArgs(video, outputArgs, filterGraph, hwEncode) {
148
+ if (video.kind === "raw") {
149
+ if (filterGraph && video.filter !== void 0) throw new Error("FfmpegInvocation: video.filter cannot be combined with a filter graph — fold the filter into the graph");
150
+ return [
151
+ ...video.filter !== void 0 ? ["-vf", video.filter] : [],
152
+ "-c:v",
153
+ "rawvideo",
154
+ "-pix_fmt",
155
+ video.pixelFormat,
156
+ ...video.fps !== void 0 ? ["-r", String(video.fps)] : []
157
+ ];
158
+ }
159
+ if (video.kind === "copy") {
160
+ if (filterGraph) throw new Error("FfmpegInvocation: a filter graph produces a NEW picture, so video.kind must be `encode`");
161
+ return [
162
+ "-c:v",
163
+ "copy",
164
+ ...video.bitstreamFilter ? ["-bsf:v", video.bitstreamFilter] : []
165
+ ];
166
+ }
167
+ const hwUpload = hwEncode === void 0 ? "" : resolveHwUpload(hwEncode.device, hwEncode.decodeHwAccel);
168
+ if (hwEncode !== void 0 && filterGraph) throw new Error("FfmpegInvocation: a hardware encode fed by a filter graph must upload INSIDE the graph — hwEncodeDevice cannot be combined with filterGraph");
169
+ const args = [
170
+ ...buildVideoFilterArgs(video.scale, outputArgs, filterGraph, hwUpload),
171
+ "-c:v",
172
+ video.encoder
173
+ ];
174
+ if (video.preset !== void 0) args.push("-preset", video.preset);
175
+ if (video.tune !== void 0) args.push("-tune", video.tune);
176
+ if (video.singleSlicePerFrame === true && video.encoder === "libx264") args.push("-x264-params", "sliced-threads=0");
177
+ if (video.profile !== void 0) args.push("-profile:v", video.profile);
178
+ if (video.level !== void 0) args.push("-level", video.level);
179
+ if (video.pixelFormat !== void 0) args.push("-pix_fmt", video.pixelFormat);
180
+ if (video.fps !== void 0) args.push("-r", String(video.fps));
181
+ if (video.gopFrames !== void 0) args.push("-g", String(video.gopFrames));
182
+ if (video.forceKeyFramesSeconds !== void 0) args.push("-force_key_frames", `expr:gte(t,n_forced*${video.forceKeyFramesSeconds})`);
183
+ if (video.bf !== void 0) args.push("-bf", String(video.bf));
184
+ args.push(...buildRateControlArgs(video));
185
+ if (video.bitstreamFilter !== void 0) args.push("-bsf:v", video.bitstreamFilter);
186
+ return args;
187
+ }
188
+ /** The whole audio block, after `-i`. */
189
+ function buildAudioArgs(audio) {
190
+ if (audio.kind === "none") return ["-an"];
191
+ if (audio.kind === "copy") return ["-c:a", "copy"];
192
+ const args = [];
193
+ if (audio.filter !== void 0) args.push("-af", audio.filter);
194
+ args.push("-c:a", AUDIO_ENCODER_BY_CODEC[audio.codec]);
195
+ if (audio.application !== void 0) args.push("-application", audio.application);
196
+ if (audio.frameDurationMs !== void 0) args.push("-frame_duration", String(audio.frameDurationMs));
197
+ if (audio.globalHeader === true) args.push("-flags", "+global_header");
198
+ if (audio.sampleRateHz !== void 0) args.push("-ar", String(audio.sampleRateHz));
199
+ if (audio.bitrateKbps !== void 0) args.push("-b:a", `${audio.bitrateKbps}k`);
200
+ if (audio.vbvBufferKbits !== void 0) args.push("-bufsize", `${audio.vbvBufferKbits}k`);
201
+ if (audio.channels !== void 0) args.push("-ac", String(audio.channels));
202
+ return args;
203
+ }
204
+ /** RTP output-leg args (`-payload_type`, `-ssrc`, `-sdp_file`, `-f rtp <url>`). */
205
+ function buildRtpOutputArgs(out) {
206
+ const args = [];
207
+ if (out.payloadType !== void 0) args.push("-payload_type", String(out.payloadType));
208
+ if (out.ssrc !== void 0) args.push("-ssrc", String(out.ssrc));
209
+ if (out.sdpFile !== void 0) args.push("-sdp_file", out.sdpFile);
210
+ args.push("-f", "rtp", out.url);
211
+ return args;
212
+ }
213
+ /** `true` when the sink is a raw elementary bytestream that cannot mux audio. */
214
+ function isElementaryVideoSink(sink) {
215
+ return sink.kind === "stdout" && (sink.container === "h264" || sink.container === "hevc");
216
+ }
217
+ /**
218
+ * The fragmented-MP4 muxer flags, in the order the recorder has proven them
219
+ * (`recorder/addon/ffmpeg-args.ts` passes the same `movflags` string through
220
+ * `-segment_format_options`, across every vendor in the fleet):
221
+ *
222
+ * - `frag_keyframe` — cut a fragment at each key frame, so every fragment
223
+ * opens on a sync sample. HKSV's whole requirement.
224
+ * - `empty_moov` — write `ftyp`+`moov` up front with no samples in it, which
225
+ * is what makes the head a standalone INITIALISATION segment.
226
+ * - `default_base_moof` — fragment offsets are self-relative, so a fragment is
227
+ * demuxable without the bytes that preceded it. D31's byte-range read path
228
+ * depends on exactly this property of the recorder's segments.
229
+ */
230
+ var FMP4_MOVFLAGS = "+frag_keyframe+empty_moov+default_base_moof";
231
+ /**
232
+ * The terminal sink args for every non-`rtp-outputs` sink. Exhaustive over the
233
+ * union so a new member cannot fall through to `['-f', container, 'pipe:1']`,
234
+ * which is what a plain `container` read would have done for `mp4` — a valid
235
+ * argv that writes a NON-fragmented, unseekable-to-a-pipe MP4 and produces one
236
+ * unusable byte stream.
237
+ */
238
+ function buildStdoutOrRtspSinkArgs(sink) {
239
+ if (sink.kind === "rtsp-listen") return [
240
+ "-f",
241
+ "rtsp",
242
+ "-rtsp_transport",
243
+ "tcp",
244
+ "-rtsp_flags",
245
+ "listen",
246
+ sink.url
247
+ ];
248
+ if (sink.kind === "rtp-outputs") return [];
249
+ return sink.container === "mp4" ? buildFmp4SinkArgs(sink) : [
250
+ "-f",
251
+ sink.container,
252
+ "pipe:1"
253
+ ];
254
+ }
255
+ /**
256
+ * How far BELOW the negotiated fragment length `-min_frag_duration` is set.
257
+ *
258
+ * `-min_frag_duration` refuses to cut before that much media has accumulated,
259
+ * and then waits for the next key frame. Set to exactly `fragmentMs`, the
260
+ * commonest camera configuration in existence — a key-frame grid EQUAL to the
261
+ * requested fragment length — lands the deadline on the same instant as the key
262
+ * frame, loses the race, and skips to the following one: **every fragment comes
263
+ * out at twice the requested length.**
264
+ *
265
+ * Measured on the live fleet 2026-08-07, camera 615, `-c:v copy` (D84):
266
+ *
267
+ * | slot | GOP | `-min_frag_duration` | median gap |
268
+ * | --- | --- | --- | --- |
269
+ * | 1280×720 | 40 f @ 10 fps = 4.0 s | 4000 ms | **7944 ms** |
270
+ * | 1280×720 | 40 f @ 10 fps = 4.0 s | 3600 ms | 3973 ms |
271
+ * | 3840×2160 | 100 f @ 25 fps = 4.0 s | 4000 ms | 8042 ms |
272
+ * | 3840×2160 | 100 f @ 25 fps = 4.0 s | 3600 ms | 3998 ms |
273
+ *
274
+ * A doubled fragment is not a cosmetic overshoot: HKSV requires every fragment
275
+ * to be no longer than the length the controller SELECTED, so the shipped-but-
276
+ * inert phase-1 sink would have violated the contract on its first real clip.
277
+ *
278
+ * 10 % is chosen against the two failures either side of it. Too small and
279
+ * ordinary jitter (measured spread 3953-4096 ms) re-loses the race; too large
280
+ * and a source with a key frame slightly EARLY than the grid gets cut there,
281
+ * yielding a short fragment for no reason.
282
+ */
283
+ var FMP4_MIN_FRAG_MARGIN = .9;
284
+ /** `-movflags … -min_frag_duration <us> -f mp4 pipe:1`. */
285
+ function buildFmp4SinkArgs(sink) {
286
+ return [
287
+ "-movflags",
288
+ FMP4_MOVFLAGS,
289
+ "-min_frag_duration",
290
+ String(Math.max(0, Math.round(sink.fragmentMs * FMP4_MIN_FRAG_MARGIN * 1e3))),
291
+ "-f",
292
+ "mp4",
293
+ "pipe:1"
294
+ ];
295
+ }
296
+ /**
297
+ * A second output mapping source audio to RTP-over-UDP. `0:a:0?` makes the
298
+ * audio optional so a source with no audio skips it instead of failing the
299
+ * whole invocation.
300
+ */
301
+ function buildAudioSidecarArgs(sidecar) {
302
+ return [
303
+ "-map",
304
+ "0:a:0?",
305
+ ...buildAudioArgs(sidecar.codec === "pcma" ? {
306
+ kind: "encode",
307
+ codec: "pcma",
308
+ sampleRateHz: 8e3,
309
+ channels: 1
310
+ } : AUDIO_PRESETS[sidecar.codec]),
311
+ ...buildRtpOutputArgs({
312
+ url: sidecar.rtpUrl,
313
+ sdpFile: sidecar.sdpFile
314
+ })
315
+ ];
316
+ }
317
+ /**
318
+ * Assemble the full ffmpeg argument list. Layout:
319
+ *
320
+ * -hide_banner -loglevel <level>
321
+ * [-hwaccel <backend|auto>] ─┐ INPUT options — strictly before -i.
322
+ * [<input.extraArgs>] │
323
+ * [-fflags <flag>…] │
324
+ * [-rtsp_transport tcp] │
325
+ * -i <url> ─┘ …repeated per extraInput
326
+ * [-filter_complex <graph>] ─ global, after the LAST -i
327
+ * [-map <video>] <video block> <threads> ─┐ OUTPUT options.
328
+ * [-map <audio>] <audio block> │
329
+ * <consumer outputArgs verbatim> │
330
+ * <sink> ─┘ terminal
331
+ *
332
+ * The `-map`s are DERIVED (see {@link resolveStreamMaps}), never constants:
333
+ * one input with no graph emits none at all, which is why the argv of every
334
+ * call site that predates the N-input extension is unchanged to the byte.
335
+ *
336
+ * {@link FfmpegInvocation.audioSidecar} is the one map that stays literal
337
+ * (`0:a:0?`): the sidecar is defined as the SOURCE's microphone on its own RTP
338
+ * output, not as whatever the main output happens to carry.
339
+ */
340
+ /** The hardware-encode context for this invocation, or `undefined`. */
341
+ function hwEncodeFor(inv) {
342
+ return inv.hwEncodeDevice === void 0 ? void 0 : {
343
+ device: inv.hwEncodeDevice,
344
+ decodeHwAccel: inv.decodeHwAccel
345
+ };
346
+ }
347
+ function buildFfmpegArgs(inv) {
348
+ const graph = inv.filterGraph ?? null;
349
+ const head = [
350
+ ...logBannerArgs(inv.logLevel),
351
+ ...inv.hwEncodeDevice !== void 0 ? ["-vaapi_device", inv.hwEncodeDevice.renderNode] : [],
352
+ ...buildInputArgs(inv.input, inv.decodeHwAccel),
353
+ ...(inv.extraInputs ?? []).flatMap((extra) => buildInputArgs(extra, inv.decodeHwAccel)),
354
+ ...graph ? ["-filter_complex", graph.graph] : []
355
+ ];
356
+ const threadArgs = [...inv.threadCount > 0 ? ["-threads", String(inv.threadCount)] : [], ...inv.filterThreadCount !== void 0 && inv.filterThreadCount > 0 ? ["-filter_threads", String(inv.filterThreadCount)] : []];
357
+ const maps = resolveStreamMaps(inv);
358
+ const videoMapArgs = maps.video ? ["-map", maps.video] : [];
359
+ const audioMapArgs = maps.audio ? ["-map", maps.audio] : [];
360
+ if (inv.sink.kind === "rtp-outputs") {
361
+ const videoLeg = inv.sink.video ? [
362
+ "-an",
363
+ "-map",
364
+ maps.video ?? "0:v:0",
365
+ ...buildVideoArgs(inv.video, inv.outputArgs, graph, hwEncodeFor(inv)),
366
+ ...threadArgs,
367
+ ...inv.outputArgs,
368
+ ...buildRtpOutputArgs(inv.sink.video)
369
+ ] : [];
370
+ const audioLeg = inv.sink.audio ? [
371
+ "-vn",
372
+ "-map",
373
+ maps.audio ?? "0:a:0?",
374
+ ...buildAudioArgs(inv.audio),
375
+ ...buildRtpOutputArgs(inv.sink.audio)
376
+ ] : [];
377
+ return [
378
+ ...head,
379
+ ...videoLeg,
380
+ ...audioLeg
381
+ ];
382
+ }
383
+ const mutedAudio = isElementaryVideoSink(inv.sink) || inv.audio.kind === "none";
384
+ const audioArgs = isElementaryVideoSink(inv.sink) ? ["-an"] : buildAudioArgs(inv.audio);
385
+ return [
386
+ ...head,
387
+ ...videoMapArgs,
388
+ ...buildVideoArgs(inv.video, inv.outputArgs, graph, hwEncodeFor(inv)),
389
+ ...threadArgs,
390
+ ...mutedAudio ? [] : audioMapArgs,
391
+ ...audioArgs,
392
+ ...inv.outputArgs,
393
+ ...buildStdoutOrRtspSinkArgs(inv.sink),
394
+ ...inv.audioSidecar ? buildAudioSidecarArgs(inv.audioSidecar) : []
395
+ ];
396
+ }
397
+ /**
398
+ * Hardware ENCODER ids per decode-hwaccel backend — a static, deterministic
399
+ * map (the same shape the reference NVR uses: platform → encoder, no probe).
400
+ */
401
+ var ENCODER_IDS_BY_BACKEND = {
402
+ videotoolbox: {
403
+ h264: "h264_videotoolbox",
404
+ h265: "hevc_videotoolbox"
405
+ },
406
+ vaapi: {
407
+ h264: "h264_vaapi",
408
+ h265: "hevc_vaapi"
409
+ },
410
+ qsv: {
411
+ h264: "h264_qsv",
412
+ h265: "hevc_qsv"
413
+ },
414
+ cuda: {
415
+ h264: "h264_nvenc",
416
+ h265: "hevc_nvenc"
417
+ },
418
+ nvdec: {
419
+ h264: "h264_nvenc",
420
+ h265: "hevc_nvenc"
421
+ },
422
+ amf: {
423
+ h264: "h264_amf",
424
+ h265: "hevc_amf"
425
+ }
426
+ };
427
+ /**
428
+ * The hardware encoder for a target codec on `backend`, or the software one.
429
+ * `'auto'` is NOT a backend identity (it is an instruction to ffmpeg), so it
430
+ * maps to software encoding.
431
+ */
432
+ function pickVideoEncoder(target, backend, useHardware) {
433
+ const software = target === "h264" ? "libx264" : "libx265";
434
+ if (!useHardware || backend === null || isSoftwareDecode(backend) || backend === "auto") return software;
435
+ const ids = ENCODER_IDS_BY_BACKEND[backend.toLowerCase()];
436
+ if (!ids) return software;
437
+ return target === "h264" ? ids.h264 : ids.h265;
438
+ }
439
+ /** Map an `EncodeProfile.audio` to an audio plan. */
440
+ function audioPlanFromEncodeProfile(audio) {
441
+ if (audio === "passthrough") return { kind: "none" };
442
+ if (audio.codec === "copy") return { kind: "copy" };
443
+ return {
444
+ kind: "encode",
445
+ codec: audio.codec,
446
+ ...audio.bitrateKbps !== void 0 ? { bitrateKbps: audio.bitrateKbps } : {},
447
+ ...audio.sampleRateHz !== void 0 ? { sampleRateHz: audio.sampleRateHz } : {},
448
+ ...audio.channels !== void 0 ? { channels: audio.channels } : {}
449
+ };
450
+ }
451
+ /**
452
+ * Adapt an `EncodeProfile` (the operator/consumer-facing shape) into an
453
+ * {@link FfmpegInvocation}. This is the ONLY bridge between the two models —
454
+ * a second one is how the repo grew two argv builders that disagreed about
455
+ * hardware.
456
+ *
457
+ * Smart video copy: when the source already speaks the requested codec the
458
+ * encode block is elided entirely and ffmpeg runs as a re-muxer on the video
459
+ * plane. Width / height / fps / bitrate in the profile are a downstream BUDGET,
460
+ * not a forced rescale.
461
+ */
462
+ function invocationFromEncodeProfile(input) {
463
+ const v = input.profile.video;
464
+ const shouldCopy = v.codec === "copy" || input.forceReencode !== true && v.codec === input.sourceCodec;
465
+ const scale = v.width !== void 0 && v.height !== void 0 ? {
466
+ mode: "fit",
467
+ width: v.width,
468
+ height: v.height
469
+ } : null;
470
+ const target = v.codec === "h265" ? "h265" : "h264";
471
+ const video = shouldCopy ? {
472
+ kind: "copy",
473
+ ...input.bitstreamFilter !== void 0 ? { bitstreamFilter: input.bitstreamFilter } : {}
474
+ } : {
475
+ kind: "encode",
476
+ encoder: pickVideoEncoder(target, input.decodeHwAccel, input.hardwareEncoders === true),
477
+ scale,
478
+ ...v.preset !== void 0 ? { preset: v.preset } : {},
479
+ ...v.tune !== void 0 ? { tune: v.tune } : {},
480
+ ...v.singleSlicePerFrame !== void 0 ? { singleSlicePerFrame: v.singleSlicePerFrame } : {},
481
+ ...v.profile !== void 0 ? { profile: v.profile } : {},
482
+ ...v.level !== void 0 ? { level: v.level } : {},
483
+ ...input.pixelFormat !== void 0 ? { pixelFormat: input.pixelFormat } : {},
484
+ ...v.fps !== void 0 ? { fps: v.fps } : {},
485
+ ...v.gopFrames !== void 0 ? { gopFrames: v.gopFrames } : {},
486
+ ...input.forceKeyFramesSeconds !== void 0 ? { forceKeyFramesSeconds: input.forceKeyFramesSeconds } : {},
487
+ ...v.bf !== void 0 ? { bf: v.bf } : {},
488
+ ...v.bitrateKbps !== void 0 ? { bitrateKbps: v.bitrateKbps } : {},
489
+ ...input.rateControl !== void 0 ? { rateControl: input.rateControl } : {},
490
+ ...input.bitstreamFilter !== void 0 ? { bitstreamFilter: input.bitstreamFilter } : {}
491
+ };
492
+ return {
493
+ logLevel: input.logLevel ?? "error",
494
+ decodeHwAccel: input.decodeHwAccel,
495
+ input: {
496
+ url: input.sourceUrl,
497
+ rtspTransport: "tcp",
498
+ fflags: ["+discardcorrupt"],
499
+ ...input.profile.inputArgs?.length ? { extraArgs: input.profile.inputArgs } : {}
500
+ },
501
+ video,
502
+ audio: audioPlanFromEncodeProfile(input.profile.audio),
503
+ threadCount: input.threadCount ?? 0,
504
+ outputArgs: input.profile.outputArgs ?? [],
505
+ sink: input.sink,
506
+ ...input.audioSidecar !== void 0 ? { audioSidecar: input.audioSidecar } : {}
507
+ };
508
+ }
509
+ //#endregion
510
+ //#region src/ffmpeg/fmp4-box-splitter.ts
511
+ var DEFAULT_MAX_UNIT_BYTES = 16 * 1024 * 1024;
512
+ /** Header size for a normal box, and for one carrying a 64-bit `largesize`. */
513
+ var BOX_HEADER_BYTES = 8;
514
+ var LARGE_BOX_HEADER_BYTES = 16;
515
+ var Fmp4BoxSplitter = class {
516
+ maxUnitBytes;
517
+ /** Bytes of the CURRENT unit plus any partial box after it. */
518
+ buffer = new Uint8Array(0);
519
+ /** Where the current unit starts inside {@link buffer}. */
520
+ unitStart = 0;
521
+ /** Where the box scanner has reached inside {@link buffer}. */
522
+ cursor = 0;
523
+ state = "init";
524
+ nextSequence = 0;
525
+ faultReason = null;
526
+ interstitial = /* @__PURE__ */ new Set();
527
+ constructor(options = {}) {
528
+ this.maxUnitBytes = options.maxUnitBytes ?? DEFAULT_MAX_UNIT_BYTES;
529
+ }
530
+ /**
531
+ * Non-null once the stream cannot be split. The splitter emits nothing
532
+ * further, so a caller polls this to kill the child rather than watching a
533
+ * silent stall — a fragmenter that quietly stops producing looks exactly like
534
+ * a camera with no motion.
535
+ */
536
+ get fault() {
537
+ return this.faultReason;
538
+ }
539
+ /** Bytes currently held. The memory bound, observable rather than asserted. */
540
+ get pendingBytes() {
541
+ return this.buffer.length - this.unitStart;
542
+ }
543
+ /**
544
+ * Top-level box types seen BETWEEN fragments and discarded — `mfra`, `free`,
545
+ * a stray `sidx`. Reported rather than dropped in silence: they are legal and
546
+ * useless to a fragment consumer, but a type nobody expected showing up here
547
+ * is the first symptom of a muxer that is not writing what we think it is.
548
+ */
549
+ get discardedInterstitialTypes() {
550
+ return [...this.interstitial];
551
+ }
552
+ /**
553
+ * Feed bytes; get back whatever units completed. Returns `[]` once faulted.
554
+ */
555
+ push(chunk) {
556
+ if (this.faultReason !== null || chunk.length === 0) return [];
557
+ this.append(chunk);
558
+ if (this.pendingBytes > this.maxUnitBytes) return this.fail(`a single fMP4 unit exceeded ${this.maxUnitBytes} bytes — this stream is not fragmented`);
559
+ return this.drainBoxes();
560
+ }
561
+ append(chunk) {
562
+ if (this.buffer.length === 0) {
563
+ this.buffer = chunk.slice();
564
+ return;
565
+ }
566
+ const next = new Uint8Array(this.buffer.length + chunk.length);
567
+ next.set(this.buffer, 0);
568
+ next.set(chunk, this.buffer.length);
569
+ this.buffer = next;
570
+ }
571
+ /** Consume every COMPLETE top-level box now in the buffer. */
572
+ drainBoxes() {
573
+ const units = [];
574
+ for (;;) {
575
+ const header = this.readHeader();
576
+ if (this.faultReason !== null) return units;
577
+ if (header === null) break;
578
+ if (this.cursor + header.totalBytes > this.buffer.length) break;
579
+ const boxStart = this.cursor;
580
+ const boxEnd = boxStart + header.totalBytes;
581
+ this.cursor = boxEnd;
582
+ const unit = this.consumeBox(header.type, boxStart, boxEnd);
583
+ if (this.faultReason !== null) return units;
584
+ if (unit !== null) units.push(unit);
585
+ }
586
+ this.compact();
587
+ return units;
588
+ }
589
+ /**
590
+ * Apply one box to the state machine. Returns a unit when this box CLOSED
591
+ * one, `null` otherwise.
592
+ */
593
+ consumeBox(type, boxStart, boxEnd) {
594
+ if (this.state === "init") {
595
+ if (type !== "moof") return null;
596
+ if (boxStart === this.unitStart) {
597
+ this.fail("a moof arrived before any initialisation box — there is no ftyp/moov to send");
598
+ return null;
599
+ }
600
+ const init = this.emit("init", this.unitStart, boxStart);
601
+ this.unitStart = boxStart;
602
+ this.state = "fragment";
603
+ return init;
604
+ }
605
+ if (this.state === "idle") {
606
+ if (type !== "moof") {
607
+ this.interstitial.add(type);
608
+ this.unitStart = boxEnd;
609
+ return null;
610
+ }
611
+ this.unitStart = boxStart;
612
+ this.state = "fragment";
613
+ return null;
614
+ }
615
+ if (type !== "mdat") return null;
616
+ const fragment = this.emit("fragment", this.unitStart, boxEnd);
617
+ this.unitStart = boxEnd;
618
+ this.state = "idle";
619
+ return fragment;
620
+ }
621
+ /**
622
+ * Parse the header at {@link cursor}, or `null` when too few bytes have
623
+ * arrived to know. Faults on a size the splitter cannot honour.
624
+ */
625
+ readHeader() {
626
+ const available = this.buffer.length - this.cursor;
627
+ if (available < BOX_HEADER_BYTES) return null;
628
+ const view = new DataView(this.buffer.buffer, this.buffer.byteOffset, this.buffer.byteLength);
629
+ const size = view.getUint32(this.cursor);
630
+ const type = String.fromCharCode(this.buffer[this.cursor + 4] ?? 0, this.buffer[this.cursor + 5] ?? 0, this.buffer[this.cursor + 6] ?? 0, this.buffer[this.cursor + 7] ?? 0);
631
+ if (size === 0) {
632
+ this.fail(`box "${type}" declares size 0 (to EOF) — an unbounded box cannot be fragmented`);
633
+ return null;
634
+ }
635
+ if (size === 1) {
636
+ if (available < LARGE_BOX_HEADER_BYTES) return null;
637
+ const large = view.getBigUint64(this.cursor + BOX_HEADER_BYTES);
638
+ if (large > BigInt(this.maxUnitBytes)) {
639
+ this.fail(`box "${type}" declares ${large} bytes, over the ${this.maxUnitBytes} byte bound`);
640
+ return null;
641
+ }
642
+ return {
643
+ type,
644
+ totalBytes: Number(large)
645
+ };
646
+ }
647
+ if (size < BOX_HEADER_BYTES) {
648
+ this.fail(`box "${type}" declares an impossible size of ${size} bytes`);
649
+ return null;
650
+ }
651
+ return {
652
+ type,
653
+ totalBytes: size
654
+ };
655
+ }
656
+ emit(kind, start, end) {
657
+ const sequence = this.nextSequence;
658
+ this.nextSequence += 1;
659
+ return {
660
+ kind,
661
+ data: this.buffer.slice(start, end),
662
+ sequence
663
+ };
664
+ }
665
+ /**
666
+ * Drop everything already emitted or discarded. Without this the buffer is
667
+ * the whole stream and the process dies in hours, not minutes.
668
+ */
669
+ compact() {
670
+ if (this.unitStart === 0) return;
671
+ this.buffer = this.buffer.slice(this.unitStart);
672
+ this.cursor -= this.unitStart;
673
+ this.unitStart = 0;
674
+ }
675
+ fail(reason) {
676
+ this.faultReason = reason;
677
+ this.buffer = new Uint8Array(0);
678
+ this.unitStart = 0;
679
+ this.cursor = 0;
680
+ return [];
681
+ }
682
+ };
683
+ //#endregion
684
+ //#region src/utils/canonical-hash.ts
685
+ /**
686
+ * Deterministic SHA-256 hash of an arbitrary serialisable value. The
687
+ * canonical form sorts object keys alphabetically at every depth so two
688
+ * structurally-equal inputs with different key insertion orders produce
689
+ * the same hash. Returns a 64-char lowercase hex digest.
690
+ *
691
+ * Used by export adapters (Alexa, HAP) to short-circuit re-discovery /
692
+ * accessory-rebuild work when the upstream shape is byte-identical to
693
+ * the last applied state — preventing user-visible "re-discovery"
694
+ * notifications on every addon-runner respawn. Each respawn re-fires
695
+ * `DeviceBindingsChanged` for every cap registration, which without
696
+ * this guard would propagate redundant pushes.
697
+ *
698
+ * Note: this is a SYMPTOMATIC fix layered on top of the binding-change
699
+ * subscription. The proper fix is a single "device ready" lifecycle
700
+ * barrier so exports react only when the full cap set has landed —
701
+ * tracked separately for post-HA-integration work.
702
+ */
703
+ function canonicalHash(value) {
704
+ const canonical = JSON.stringify(value, replaceWithSortedKeys);
705
+ return (0, node_crypto.createHash)("sha256").update(canonical ?? "").digest("hex");
706
+ }
707
+ function replaceWithSortedKeys(_key, value) {
708
+ if (value && typeof value === "object" && !Array.isArray(value)) {
709
+ const obj = value;
710
+ const out = {};
711
+ for (const k of Object.keys(obj).toSorted()) out[k] = obj[k];
712
+ return out;
713
+ }
714
+ return value;
715
+ }
716
+ //#endregion
717
+ //#region src/expression/errors.ts
718
+ /**
719
+ * Error types for the safe expression engine. Two distinct classes so callers
720
+ * can tell a compile-time (grammar) failure from a runtime (evaluation)
721
+ * failure — both are non-fatal to the host: read paths degrade to "skip link".
722
+ */
723
+ /** Thrown by the tokenizer / parser. Carries a 0-based source `position` when
724
+ * the failure is anchored to a character (author-facing inline feedback). */
725
+ var ExpressionParseError = class extends Error {
726
+ position;
727
+ constructor(message, position) {
728
+ super(message);
729
+ this.name = "ExpressionParseError";
730
+ this.position = position;
731
+ }
732
+ };
733
+ /** Thrown by the evaluator (unknown identifier, type mismatch, non-finite
734
+ * result, unknown builtin, step-budget exceeded). */
735
+ var ExpressionEvalError = class extends Error {
736
+ constructor(message) {
737
+ super(message);
738
+ this.name = "ExpressionEvalError";
739
+ }
740
+ };
741
+ //#endregion
742
+ //#region src/expression/builtins.ts
743
+ function asFiniteNumber(value, name, index) {
744
+ if (typeof value !== "number" || !Number.isFinite(value)) throw new ExpressionEvalError(`${name}: argument ${index + 1} must be a finite number`);
745
+ return value;
746
+ }
747
+ function asString(value, name, index) {
748
+ if (typeof value !== "string") throw new ExpressionEvalError(`${name}: argument ${index + 1} must be a string`);
749
+ return value;
750
+ }
751
+ function finiteResult(value, name) {
752
+ if (!Number.isFinite(value)) throw new ExpressionEvalError(`${name}: produced a non-finite result`);
753
+ return value;
754
+ }
755
+ function allFiniteNumbers(args, name) {
756
+ return args.map((a, idx) => asFiniteNumber(a, name, idx));
757
+ }
758
+ function asBoolean(value, name, index) {
759
+ if (typeof value !== "boolean") throw new ExpressionEvalError(`${name}: argument ${index + 1} must be a boolean`);
760
+ return value;
761
+ }
762
+ /** A plain decimal (optional sign, fraction, exponent) — never hex, never `Infinity`, never a unit suffix. */
763
+ var DECIMAL = /^[+-]?(\d+(\.\d*)?|\.\d+)([eE][+-]?\d+)?$/;
764
+ /**
765
+ * `number(x)`: a finite number, or a string that IS one, as a number; every
766
+ * other value — `''`, `'unavailable'`, a boolean, null — is `null`
767
+ * (unavailable, never 0: D393). The one bridge from a string-valued source (an
768
+ * HA sensor with a unit and no device_class is an `enum-sensor`) to a number.
769
+ */
770
+ function toNumberOrNull(value) {
771
+ if (typeof value === "number") return Number.isFinite(value) ? value : null;
772
+ if (typeof value !== "string") return null;
773
+ const trimmed = value.trim();
774
+ if (!DECIMAL.test(trimmed)) return null;
775
+ const parsed = Number(trimmed);
776
+ return Number.isFinite(parsed) ? parsed : null;
777
+ }
778
+ var INF = Number.POSITIVE_INFINITY;
779
+ var table = {
780
+ min: {
781
+ minArgs: 1,
782
+ maxArgs: INF,
783
+ apply: (args) => finiteResult(Math.min(...allFiniteNumbers(args, "min")), "min")
784
+ },
785
+ max: {
786
+ minArgs: 1,
787
+ maxArgs: INF,
788
+ apply: (args) => finiteResult(Math.max(...allFiniteNumbers(args, "max")), "max")
789
+ },
790
+ abs: {
791
+ minArgs: 1,
792
+ maxArgs: 1,
793
+ apply: (args) => finiteResult(Math.abs(asFiniteNumber(args[0], "abs", 0)), "abs")
794
+ },
795
+ floor: {
796
+ minArgs: 1,
797
+ maxArgs: 1,
798
+ apply: (args) => finiteResult(Math.floor(asFiniteNumber(args[0], "floor", 0)), "floor")
799
+ },
800
+ ceil: {
801
+ minArgs: 1,
802
+ maxArgs: 1,
803
+ apply: (args) => finiteResult(Math.ceil(asFiniteNumber(args[0], "ceil", 0)), "ceil")
804
+ },
805
+ sqrt: {
806
+ minArgs: 1,
807
+ maxArgs: 1,
808
+ apply: (args) => finiteResult(Math.sqrt(asFiniteNumber(args[0], "sqrt", 0)), "sqrt")
809
+ },
810
+ round: {
811
+ minArgs: 1,
812
+ maxArgs: 2,
813
+ apply: (args) => {
814
+ const x = asFiniteNumber(args[0], "round", 0);
815
+ const digits = args.length > 1 ? Math.trunc(asFiniteNumber(args[1], "round", 1)) : 0;
816
+ if (digits < 0 || digits > 100) throw new ExpressionEvalError("round: digits must be between 0 and 100");
817
+ const factor = 10 ** digits;
818
+ return finiteResult(Math.round(x * factor) / factor, "round");
819
+ }
820
+ },
821
+ pow: {
822
+ minArgs: 2,
823
+ maxArgs: 2,
824
+ apply: (args) => finiteResult(asFiniteNumber(args[0], "pow", 0) ** asFiniteNumber(args[1], "pow", 1), "pow")
825
+ },
826
+ clamp: {
827
+ minArgs: 3,
828
+ maxArgs: 3,
829
+ apply: (args) => {
830
+ const x = asFiniteNumber(args[0], "clamp", 0);
831
+ const lo = asFiniteNumber(args[1], "clamp", 1);
832
+ const hi = asFiniteNumber(args[2], "clamp", 2);
833
+ if (lo > hi) throw new ExpressionEvalError("clamp: lower bound is greater than upper bound");
834
+ return finiteResult(Math.min(hi, Math.max(lo, x)), "clamp");
835
+ }
836
+ },
837
+ avg: {
838
+ minArgs: 1,
839
+ maxArgs: INF,
840
+ apply: (args) => {
841
+ const nums = allFiniteNumbers(args, "avg");
842
+ return finiteResult(nums.reduce((acc, v) => acc + v, 0) / nums.length, "avg");
843
+ }
844
+ },
845
+ sum: {
846
+ minArgs: 1,
847
+ maxArgs: INF,
848
+ apply: (args) => finiteResult(allFiniteNumbers(args, "sum").reduce((acc, v) => acc + v, 0), "sum")
849
+ },
850
+ coalesce: {
851
+ minArgs: 1,
852
+ maxArgs: INF,
853
+ apply: (args) => {
854
+ for (const a of args) if (a !== null) return a;
855
+ return null;
856
+ }
857
+ },
858
+ age: {
859
+ minArgs: 2,
860
+ maxArgs: 2,
861
+ apply: (args) => finiteResult(asFiniteNumber(args[0], "age", 0) - asFiniteNumber(args[1], "age", 1), "age")
862
+ },
863
+ convert: {
864
+ minArgs: 3,
865
+ maxArgs: 3,
866
+ apply: (args, hooks) => {
867
+ const x = asFiniteNumber(args[0], "convert", 0);
868
+ const from = asString(args[1], "convert", 1).trim();
869
+ const to = asString(args[2], "convert", 2).trim();
870
+ if (hooks.convert) {
871
+ const out = hooks.convert(x, from, to);
872
+ if (out === null) throw new ExpressionEvalError(`convert: cannot convert '${from}' to '${to}'`);
873
+ return finiteResult(out, "convert");
874
+ }
875
+ if (from === to) return x;
876
+ throw new ExpressionEvalError("convert: unit conversion table not installed");
877
+ }
878
+ },
879
+ any: {
880
+ minArgs: 1,
881
+ maxArgs: INF,
882
+ apply: (args) => args.map((a, i) => asBoolean(a, "any", i)).some((b) => b)
883
+ },
884
+ all: {
885
+ minArgs: 1,
886
+ maxArgs: INF,
887
+ apply: (args) => args.map((a, i) => asBoolean(a, "all", i)).every((b) => b)
888
+ },
889
+ within: {
890
+ minArgs: 2,
891
+ maxArgs: 2,
892
+ apply: (args, hooks) => {
893
+ const windowMs = asFiniteNumber(args[1], "within", 1);
894
+ if (windowMs < 0) throw new ExpressionEvalError("within: the window must not be negative");
895
+ const at = args[0];
896
+ if (at === null) return false;
897
+ const ts = asFiniteNumber(at, "within", 0);
898
+ const now = hooks.now;
899
+ if (now === void 0 || !Number.isFinite(now)) throw new ExpressionEvalError("within: no clock was supplied to this evaluation");
900
+ const inside = now - ts <= windowMs;
901
+ if (inside) hooks.noteDeadline?.(ts + windowMs + 1);
902
+ return inside;
903
+ }
904
+ },
905
+ number: {
906
+ minArgs: 1,
907
+ maxArgs: 1,
908
+ apply: (args) => toNumberOrNull(args[0])
909
+ },
910
+ latest: {
911
+ minArgs: 1,
912
+ maxArgs: INF,
913
+ apply: (args) => {
914
+ const present = args.flatMap((a, i) => a === null ? [] : [asFiniteNumber(a, "latest", i)]);
915
+ return present.length === 0 ? null : finiteResult(Math.max(...present), "latest");
916
+ }
917
+ }
918
+ };
919
+ /** Frozen, null-prototype builtin table. */
920
+ var EXPRESSION_BUILTINS = Object.freeze(Object.assign(Object.create(null), table));
921
+ /** The set of valid builtin names — used by the parser to reject unknown
922
+ * callees at parse time (immediate author feedback). */
923
+ var EXPRESSION_BUILTIN_NAMES = new Set(Object.keys(table));
924
+ //#endregion
925
+ //#region src/expression/limits.ts
926
+ /**
927
+ * Resource-bound constants for the safe expression engine.
928
+ *
929
+ * Every bound is defense-in-depth: the grammar is non-Turing-complete (no
930
+ * loops, recursion, lambdas or member access — see `ast.ts`), so evaluation is
931
+ * O(nodeCount) by construction. These caps merely put a hard ceiling on the
932
+ * work a single author-supplied expression can request, so a hostile or
933
+ * accidental pathological string can never spend unbounded CPU/memory.
934
+ */
935
+ /** Max source length (chars) — checked BEFORE tokenizing so a huge string is
936
+ * rejected without allocation. */
937
+ var MAX_EXPRESSION_SOURCE_LENGTH = 2048;
938
+ /** Max AST nodes — checked during parse; a deeply nested grouping that exceeds
939
+ * this is rejected as "expression too complex". */
940
+ var MAX_EXPRESSION_AST_NODES = 256;
941
+ /** Defense-in-depth walker step budget — one increment per node visit during
942
+ * evaluation. The grammar guarantees O(nodeCount) walks, so this can only trip
943
+ * on a crafted maximum-size AST. */
944
+ var MAX_EXPRESSION_EVAL_STEPS = 4096;
945
+ /** Max named bindings on one {@link ExpressionSource}. */
946
+ var MAX_EXPRESSION_BINDINGS = 32;
947
+ /** Max positional arguments to any builtin call. */
948
+ var MAX_EXPRESSION_CALL_ARGS = 16;
949
+ /** LRU compile-cache capacity (parsed ASTs keyed by raw source string). */
950
+ var EXPRESSION_COMPILE_CACHE_CAPACITY = 256;
951
+ /** A legal binding / identifier name. */
952
+ var EXPRESSION_IDENTIFIER_RE = /^[A-Za-z_][A-Za-z0-9_]*$/;
953
+ /** Binding names an author may NOT use: `now` is auto-injected; the literal
954
+ * keywords lex as values, not identifiers, so binding to them is meaningless. */
955
+ var RESERVED_BINDING_NAMES = new Set([
956
+ "now",
957
+ "true",
958
+ "false",
959
+ "null"
960
+ ]);
961
+ //#endregion
962
+ //#region src/expression/tokenizer.ts
963
+ /**
964
+ * Tokenizer for the safe expression mini-language. Hand-rolled, single-pass,
965
+ * zero-dependency. The grammar is deliberately boring: decimal numbers,
966
+ * single/double-quoted strings with a tiny escape set, identifiers, the three
967
+ * value keywords (`true`/`false`/`null`) and a fixed punctuator set. Anything
968
+ * outside that — a bare `.`, `=`, `[`, `]`, `{`, `}`, `;`, backtick, `&`, `|` —
969
+ * is a parse error with a source position, so member access / assignment /
970
+ * template literals are lexically impossible.
971
+ */
972
+ var KEYWORDS = new Set([
973
+ "true",
974
+ "false",
975
+ "null"
976
+ ]);
977
+ function isDigit(ch) {
978
+ return ch >= "0" && ch <= "9";
979
+ }
980
+ function isIdentStart(ch) {
981
+ return ch >= "A" && ch <= "Z" || ch >= "a" && ch <= "z" || ch === "_";
982
+ }
983
+ function isIdentPart(ch) {
984
+ return isIdentStart(ch) || isDigit(ch);
985
+ }
986
+ function isWhitespace(ch) {
987
+ return ch === " " || ch === " " || ch === "\n" || ch === "\r" || ch === "\f" || ch === "\v";
988
+ }
989
+ /** Tokenize `source` into a flat token list ending with a single `eof` token.
990
+ * Throws `ExpressionParseError` on any illegal character or unterminated
991
+ * string. */
992
+ function tokenize(source) {
993
+ if (source.length > 2048) throw new ExpressionParseError(`expression too long (${source.length} > ${MAX_EXPRESSION_SOURCE_LENGTH} chars)`, 0);
994
+ const tokens = [];
995
+ let i = 0;
996
+ const n = source.length;
997
+ while (i < n) {
998
+ const ch = source[i];
999
+ if (isWhitespace(ch)) {
1000
+ i += 1;
1001
+ continue;
1002
+ }
1003
+ if (isDigit(ch)) {
1004
+ const start = i;
1005
+ while (i < n && isDigit(source[i])) i += 1;
1006
+ if (i < n && source[i] === ".") {
1007
+ if (i + 1 >= n || !isDigit(source[i + 1])) throw new ExpressionParseError("malformed number: decimal point needs a digit", i);
1008
+ i += 1;
1009
+ while (i < n && isDigit(source[i])) i += 1;
1010
+ }
1011
+ const text = source.slice(start, i);
1012
+ const value = Number(text);
1013
+ if (!Number.isFinite(value)) throw new ExpressionParseError(`malformed number: '${text}'`, start);
1014
+ tokens.push({
1015
+ type: "number",
1016
+ value,
1017
+ pos: start
1018
+ });
1019
+ continue;
1020
+ }
1021
+ if (ch === "'" || ch === "\"") {
1022
+ const quote = ch;
1023
+ const start = i;
1024
+ i += 1;
1025
+ let out = "";
1026
+ let closed = false;
1027
+ while (i < n) {
1028
+ const c = source[i];
1029
+ if (c === "\\") {
1030
+ const next = i + 1 < n ? source[i + 1] : "";
1031
+ if (next === "\\" || next === "'" || next === "\"") {
1032
+ out += next;
1033
+ i += 2;
1034
+ continue;
1035
+ }
1036
+ throw new ExpressionParseError(`invalid string escape: '\\${next}'`, i);
1037
+ }
1038
+ if (c === quote) {
1039
+ closed = true;
1040
+ i += 1;
1041
+ break;
1042
+ }
1043
+ out += c;
1044
+ i += 1;
1045
+ }
1046
+ if (!closed) throw new ExpressionParseError("unterminated string literal", start);
1047
+ tokens.push({
1048
+ type: "string",
1049
+ value: out,
1050
+ pos: start
1051
+ });
1052
+ continue;
1053
+ }
1054
+ if (isIdentStart(ch)) {
1055
+ const start = i;
1056
+ while (i < n && isIdentPart(source[i])) i += 1;
1057
+ const text = source.slice(start, i);
1058
+ if (KEYWORDS.has(text)) tokens.push({
1059
+ type: "keyword",
1060
+ keyword: keywordOf(text),
1061
+ pos: start
1062
+ });
1063
+ else tokens.push({
1064
+ type: "identifier",
1065
+ name: text,
1066
+ pos: start
1067
+ });
1068
+ continue;
1069
+ }
1070
+ const two = i + 1 < n ? source.slice(i, i + 2) : "";
1071
+ if (two === "<=" || two === ">=" || two === "==" || two === "!=" || two === "&&" || two === "||") {
1072
+ tokens.push({
1073
+ type: "punct",
1074
+ punct: two,
1075
+ pos: i
1076
+ });
1077
+ i += 2;
1078
+ continue;
1079
+ }
1080
+ if (isSinglePunct(ch)) {
1081
+ tokens.push({
1082
+ type: "punct",
1083
+ punct: ch,
1084
+ pos: i
1085
+ });
1086
+ i += 1;
1087
+ continue;
1088
+ }
1089
+ throw new ExpressionParseError(`unexpected character '${ch}'`, i);
1090
+ }
1091
+ tokens.push({
1092
+ type: "eof",
1093
+ pos: n
1094
+ });
1095
+ return tokens;
1096
+ }
1097
+ function keywordOf(text) {
1098
+ if (text === "true") return "true";
1099
+ if (text === "false") return "false";
1100
+ return "null";
1101
+ }
1102
+ function isSinglePunct(ch) {
1103
+ return ch === "(" || ch === ")" || ch === "," || ch === "?" || ch === ":" || ch === "+" || ch === "-" || ch === "*" || ch === "/" || ch === "%" || ch === "!" || ch === "<" || ch === ">";
1104
+ }
1105
+ //#endregion
1106
+ //#region src/expression/parser.ts
1107
+ /**
1108
+ * Pratt (precedence-climbing) parser for the safe expression mini-language.
1109
+ *
1110
+ * Precedence (low → high): ternary `?:` (right-assoc) → `||` → `&&` → equality
1111
+ * → relational → additive → multiplicative → unary `! -` → call / primary.
1112
+ * Calls are ONLY `IDENT '(' args? ')'` at primary position — the callee is a
1113
+ * string validated against the builtin table at parse time, so an unknown
1114
+ * function is rejected immediately (author feedback) and a persisted expression
1115
+ * that references a since-removed builtin degrades at read.
1116
+ *
1117
+ * A node counter caps total AST size (`MAX_EXPRESSION_AST_NODES`) and call
1118
+ * arity is capped (`MAX_EXPRESSION_CALL_ARGS`) — both raise `ExpressionParseError`.
1119
+ */
1120
+ /** Binary/logical operator precedence (higher binds tighter). */
1121
+ var BINARY_PRECEDENCE = {
1122
+ "||": 1,
1123
+ "&&": 2,
1124
+ "==": 3,
1125
+ "!=": 3,
1126
+ "<": 4,
1127
+ "<=": 4,
1128
+ ">": 4,
1129
+ ">=": 4,
1130
+ "+": 5,
1131
+ "-": 5,
1132
+ "*": 6,
1133
+ "/": 6,
1134
+ "%": 6
1135
+ };
1136
+ function isLogicalOp(op) {
1137
+ return op === "&&" || op === "||";
1138
+ }
1139
+ function isBinaryOp(op) {
1140
+ return op === "+" || op === "-" || op === "*" || op === "/" || op === "%" || op === "==" || op === "!=" || op === "<" || op === "<=" || op === ">" || op === ">=";
1141
+ }
1142
+ var Parser = class {
1143
+ tokens;
1144
+ pos = 0;
1145
+ nodeCount = 0;
1146
+ identifiers = /* @__PURE__ */ new Set();
1147
+ callees = /* @__PURE__ */ new Set();
1148
+ constructor(tokens) {
1149
+ this.tokens = tokens;
1150
+ }
1151
+ parse() {
1152
+ const ast = this.parseTernary();
1153
+ const tok = this.peek();
1154
+ if (tok.type !== "eof") throw new ExpressionParseError("unexpected trailing input", tok.pos);
1155
+ return {
1156
+ ast,
1157
+ identifiers: this.identifiers,
1158
+ callees: this.callees,
1159
+ nodeCount: this.nodeCount
1160
+ };
1161
+ }
1162
+ peek() {
1163
+ return this.tokens[this.pos];
1164
+ }
1165
+ next() {
1166
+ return this.tokens[this.pos++];
1167
+ }
1168
+ /** Consume a punctuator token, erroring if the next token isn't it. */
1169
+ expectPunct(punct) {
1170
+ const tok = this.peek();
1171
+ if (tok.type !== "punct" || tok.punct !== punct) throw new ExpressionParseError(`expected '${punct}'`, tok.pos);
1172
+ this.pos += 1;
1173
+ }
1174
+ matchPunct(punct) {
1175
+ const tok = this.peek();
1176
+ if (tok.type === "punct" && tok.punct === punct) {
1177
+ this.pos += 1;
1178
+ return true;
1179
+ }
1180
+ return false;
1181
+ }
1182
+ countNode() {
1183
+ this.nodeCount += 1;
1184
+ if (this.nodeCount > 256) throw new ExpressionParseError("expression too complex", this.peek().pos);
1185
+ }
1186
+ parseTernary() {
1187
+ const test = this.parseBinary(1);
1188
+ if (this.matchPunct("?")) {
1189
+ const consequent = this.parseTernary();
1190
+ this.expectPunct(":");
1191
+ const alternate = this.parseTernary();
1192
+ this.countNode();
1193
+ return {
1194
+ kind: "conditional",
1195
+ test,
1196
+ consequent,
1197
+ alternate
1198
+ };
1199
+ }
1200
+ return test;
1201
+ }
1202
+ parseBinary(minPrec) {
1203
+ let left = this.parseUnary();
1204
+ for (;;) {
1205
+ const tok = this.peek();
1206
+ if (tok.type !== "punct") break;
1207
+ const prec = BINARY_PRECEDENCE[tok.punct];
1208
+ if (prec === void 0 || prec < minPrec) break;
1209
+ const op = tok.punct;
1210
+ this.pos += 1;
1211
+ const right = this.parseBinary(prec + 1);
1212
+ this.countNode();
1213
+ if (isLogicalOp(op)) left = {
1214
+ kind: "logical",
1215
+ op,
1216
+ left,
1217
+ right
1218
+ };
1219
+ else if (isBinaryOp(op)) left = {
1220
+ kind: "binary",
1221
+ op,
1222
+ left,
1223
+ right
1224
+ };
1225
+ else throw new ExpressionParseError(`unexpected operator '${op}'`, tok.pos);
1226
+ }
1227
+ return left;
1228
+ }
1229
+ parseUnary() {
1230
+ const tok = this.peek();
1231
+ if (tok.type === "punct" && (tok.punct === "!" || tok.punct === "-")) {
1232
+ const op = tok.punct;
1233
+ this.pos += 1;
1234
+ const operand = this.parseUnary();
1235
+ this.countNode();
1236
+ return {
1237
+ kind: "unary",
1238
+ op,
1239
+ operand
1240
+ };
1241
+ }
1242
+ return this.parsePrimary();
1243
+ }
1244
+ parsePrimary() {
1245
+ const tok = this.next();
1246
+ switch (tok.type) {
1247
+ case "number":
1248
+ this.countNode();
1249
+ return {
1250
+ kind: "literal",
1251
+ value: tok.value
1252
+ };
1253
+ case "string":
1254
+ this.countNode();
1255
+ return {
1256
+ kind: "literal",
1257
+ value: tok.value
1258
+ };
1259
+ case "keyword":
1260
+ this.countNode();
1261
+ return {
1262
+ kind: "literal",
1263
+ value: tok.keyword === "null" ? null : tok.keyword === "true"
1264
+ };
1265
+ case "identifier": {
1266
+ const nextTok = this.peek();
1267
+ if (nextTok.type === "punct" && nextTok.punct === "(") return this.parseCall(tok.name, tok.pos);
1268
+ this.identifiers.add(tok.name);
1269
+ this.countNode();
1270
+ return {
1271
+ kind: "identifier",
1272
+ name: tok.name
1273
+ };
1274
+ }
1275
+ case "punct":
1276
+ if (tok.punct === "(") {
1277
+ const inner = this.parseTernary();
1278
+ this.expectPunct(")");
1279
+ return inner;
1280
+ }
1281
+ throw new ExpressionParseError(`unexpected token '${tok.punct}'`, tok.pos);
1282
+ case "eof": throw new ExpressionParseError("unexpected end of expression", tok.pos);
1283
+ }
1284
+ }
1285
+ parseCall(callee, pos) {
1286
+ if (!EXPRESSION_BUILTIN_NAMES.has(callee)) throw new ExpressionParseError(`unknown function '${callee}'`, pos);
1287
+ this.expectPunct("(");
1288
+ const args = [];
1289
+ if (!this.matchPunct(")")) for (;;) {
1290
+ args.push(this.parseTernary());
1291
+ if (args.length > 16) throw new ExpressionParseError(`too many arguments to '${callee}'`, pos);
1292
+ if (this.matchPunct(",")) continue;
1293
+ this.expectPunct(")");
1294
+ break;
1295
+ }
1296
+ this.callees.add(callee);
1297
+ this.countNode();
1298
+ return {
1299
+ kind: "call",
1300
+ callee,
1301
+ args
1302
+ };
1303
+ }
1304
+ };
1305
+ /** Tokenize + parse `source` into a validated `ParsedExpression`. Throws
1306
+ * `ExpressionParseError` on any lexical or grammatical failure. */
1307
+ function parseExpression(source) {
1308
+ return new Parser(tokenize(source)).parse();
1309
+ }
1310
+ //#endregion
1311
+ //#region src/expression/compile.ts
1312
+ /**
1313
+ * LRU compile cache for parsed expressions (spec §2.4 "parse once … LRU keyed
1314
+ * by expr"). The cache stores BOTH successes and failures (negative caching),
1315
+ * so a corrupt persisted string costs exactly one tokenize+parse total — not
1316
+ * one per read on a hot resolve path.
1317
+ *
1318
+ * The cache is a module-level singleton: entries are pure, content-addressed
1319
+ * ASTs keyed by the raw source string, so sharing one instance across all
1320
+ * callers is safe and maximises hit rate.
1321
+ */
1322
+ var cache = /* @__PURE__ */ new Map();
1323
+ function getCached(source) {
1324
+ const hit = cache.get(source);
1325
+ if (hit !== void 0) {
1326
+ cache.delete(source);
1327
+ cache.set(source, hit);
1328
+ return hit;
1329
+ }
1330
+ let result;
1331
+ try {
1332
+ result = {
1333
+ ok: true,
1334
+ parsed: parseExpression(source)
1335
+ };
1336
+ } catch (err) {
1337
+ result = {
1338
+ ok: false,
1339
+ error: err instanceof ExpressionParseError ? err.message : String(err)
1340
+ };
1341
+ }
1342
+ cache.set(source, result);
1343
+ if (cache.size > 256) {
1344
+ const oldest = cache.keys().next().value;
1345
+ if (oldest !== void 0) cache.delete(oldest);
1346
+ }
1347
+ return result;
1348
+ }
1349
+ /** Compile `source` to a `ParsedExpression`, throwing `ExpressionParseError`
1350
+ * on failure. LRU/negative-cached. */
1351
+ function compileExpression(source) {
1352
+ const result = getCached(source);
1353
+ if (result.ok) return result.parsed;
1354
+ throw new ExpressionParseError(result.error);
1355
+ }
1356
+ /** Compile `source`, returning a discriminated result instead of throwing.
1357
+ * Used by read paths that must degrade rather than raise. LRU/negative-cached. */
1358
+ function compileExpressionSafe(source) {
1359
+ return getCached(source);
1360
+ }
1361
+ //#endregion
1362
+ //#region src/expression/evaluator.ts
1363
+ /**
1364
+ * Tree-walking evaluator for the safe expression mini-language.
1365
+ *
1366
+ * SECURITY (spec §4 rule 2/5):
1367
+ * - The scope is an `Object.create(null)` copy of ONLY the caller's own
1368
+ * enumerable binding entries, so `name in scope` is a pure own-key check and
1369
+ * `constructor` / `__proto__` / `toString` are plain unknown identifiers.
1370
+ * - Performs ZERO I/O and never touches `globalThis` / `Date` / `Math`
1371
+ * directly — the only external calls are into the frozen builtin table.
1372
+ * - The grammar has no loops/recursion/lambdas, so a walk is O(nodeCount) by
1373
+ * construction; the step counter is defense-in-depth for a crafted max-size
1374
+ * AST. Nothing blocks: there are no timers, awaits or unbounded loops.
1375
+ */
1376
+ var EMPTY_HOOKS = Object.freeze({});
1377
+ /** Build a null-prototype scope from own-enumerable binding entries. Inherited
1378
+ * keys of the input (e.g. from a `{__proto__: {...}}` payload) are NOT copied,
1379
+ * so nothing smuggles in via the prototype chain. */
1380
+ function createExpressionScope(bindings) {
1381
+ const scope = Object.create(null);
1382
+ for (const key of Object.keys(bindings)) if (Object.prototype.hasOwnProperty.call(bindings, key)) scope[key] = bindings[key];
1383
+ return scope;
1384
+ }
1385
+ function isFiniteNumber(value) {
1386
+ return typeof value === "number" && Number.isFinite(value);
1387
+ }
1388
+ /** JS truthiness of a primitive value. */
1389
+ function truthy(value) {
1390
+ return Boolean(value);
1391
+ }
1392
+ function requireFinite(value, context) {
1393
+ if (!Number.isFinite(value)) throw new ExpressionEvalError(`${context} produced a non-finite result`);
1394
+ return value;
1395
+ }
1396
+ function step(ctx) {
1397
+ ctx.steps += 1;
1398
+ if (ctx.steps > ctx.maxSteps) throw new ExpressionEvalError("expression evaluation step budget exceeded");
1399
+ }
1400
+ function evalNode(node, ctx) {
1401
+ step(ctx);
1402
+ switch (node.kind) {
1403
+ case "literal": return node.value;
1404
+ case "identifier":
1405
+ if (!(node.name in ctx.scope)) throw new ExpressionEvalError(`unknown identifier: ${node.name}`);
1406
+ return ctx.scope[node.name];
1407
+ case "unary": return evalUnary(node.op, evalNode(node.operand, ctx));
1408
+ case "binary": return evalBinary(node.op, evalNode(node.left, ctx), evalNode(node.right, ctx));
1409
+ case "logical": {
1410
+ const left = evalNode(node.left, ctx);
1411
+ if (node.op === "&&") return truthy(left) ? evalNode(node.right, ctx) : left;
1412
+ return truthy(left) ? left : evalNode(node.right, ctx);
1413
+ }
1414
+ case "conditional": return truthy(evalNode(node.test, ctx)) ? evalNode(node.consequent, ctx) : evalNode(node.alternate, ctx);
1415
+ case "call": return evalCall(node.callee, node.args.map((a) => evalNode(a, ctx)), ctx.hooks);
1416
+ }
1417
+ }
1418
+ function evalUnary(op, operand) {
1419
+ if (op === "!") return !truthy(operand);
1420
+ if (!isFiniteNumber(operand)) throw new ExpressionEvalError("unary \"-\" requires a finite number");
1421
+ return requireFinite(-operand, "unary \"-\"");
1422
+ }
1423
+ function evalBinary(op, left, right) {
1424
+ switch (op) {
1425
+ case "==": return left === right;
1426
+ case "!=": return left !== right;
1427
+ case "+":
1428
+ if (typeof left === "string" && typeof right === "string") return left + right;
1429
+ if (isFiniteNumber(left) && isFiniteNumber(right)) return requireFinite(left + right, "\"+\"");
1430
+ throw new ExpressionEvalError("\"+\" requires two numbers or two strings");
1431
+ case "-":
1432
+ case "*":
1433
+ case "/":
1434
+ case "%":
1435
+ if (!isFiniteNumber(left) || !isFiniteNumber(right)) throw new ExpressionEvalError(`"${op}" requires two finite numbers`);
1436
+ return requireFinite(op === "-" ? left - right : op === "*" ? left * right : op === "/" ? left / right : left % right, `"${op}"`);
1437
+ case "<":
1438
+ case "<=":
1439
+ case ">":
1440
+ case ">=":
1441
+ if (isFiniteNumber(left) && isFiniteNumber(right)) return op === "<" ? left < right : op === "<=" ? left <= right : op === ">" ? left > right : left >= right;
1442
+ if (typeof left === "string" && typeof right === "string") return op === "<" ? left < right : op === "<=" ? left <= right : op === ">" ? left > right : left >= right;
1443
+ throw new ExpressionEvalError(`"${op}" requires two numbers or two strings`);
1444
+ }
1445
+ }
1446
+ function evalCall(callee, args, hooks) {
1447
+ if (!Object.prototype.hasOwnProperty.call(EXPRESSION_BUILTINS, callee)) throw new ExpressionEvalError(`unknown function: ${callee}`);
1448
+ const builtin = EXPRESSION_BUILTINS[callee];
1449
+ if (args.length < builtin.minArgs || args.length > builtin.maxArgs) throw new ExpressionEvalError(`${callee}: wrong number of arguments (${args.length})`);
1450
+ return builtin.apply(args, hooks);
1451
+ }
1452
+ /** Evaluate an AST node against a scope. Throws `ExpressionEvalError` on any
1453
+ * runtime failure (unknown identifier, type mismatch, non-finite result,
1454
+ * step-budget exhaustion). */
1455
+ function evaluateAst(node, scope, opts) {
1456
+ return evalNode(node, {
1457
+ scope,
1458
+ hooks: opts?.hooks ?? EMPTY_HOOKS,
1459
+ maxSteps: opts?.maxSteps ?? 4096,
1460
+ steps: 0
1461
+ });
1462
+ }
1463
+ //#endregion
1464
+ //#region src/expression/expression-source.ts
1465
+ /** The `now` epoch-ms binding is auto-injected into every evaluation and is a
1466
+ * reserved binding name (authors may not rebind it). */
1467
+ var EXPRESSION_INJECTED_NOW = "now";
1468
+ /**
1469
+ * Coerce an untrusted `getByPath` / mirror read to an `ExpressionValue`.
1470
+ * Non-primitive values (objects, arrays, `undefined`, functions, bigint,
1471
+ * symbol) and non-finite numbers become `undefined` so the caller can apply
1472
+ * its binding-miss policy (→ `null`). `null` itself is a valid value.
1473
+ */
1474
+ function toExpressionValue(raw) {
1475
+ if (raw === null) return null;
1476
+ if (typeof raw === "string") return raw;
1477
+ if (typeof raw === "boolean") return raw;
1478
+ if (typeof raw === "number") return Number.isFinite(raw) ? raw : void 0;
1479
+ }
1480
+ /**
1481
+ * Author-time validation. Returns `null` when the source is valid, else a
1482
+ * human-readable error message. Checks: the expression compiles; binding count
1483
+ * is within `MAX_EXPRESSION_BINDINGS`; every binding name is a legal identifier,
1484
+ * is not reserved (`now`/keywords) and does not shadow a builtin; and every
1485
+ * FREE identifier of the AST is covered by a binding or the injected `now`.
1486
+ */
1487
+ function validateExpressionSource(src) {
1488
+ const names = Object.keys(src.bindings);
1489
+ if (names.length > 32) return `too many bindings (${names.length} > 32)`;
1490
+ for (const name of names) {
1491
+ if (!EXPRESSION_IDENTIFIER_RE.test(name)) return `invalid binding name '${name}'`;
1492
+ if (RESERVED_BINDING_NAMES.has(name)) return `binding name '${name}' is reserved`;
1493
+ if (EXPRESSION_BUILTIN_NAMES.has(name)) return `binding name '${name}' shadows a builtin function`;
1494
+ }
1495
+ const compiled = compileExpressionSafe(src.expr);
1496
+ if (!compiled.ok) return compiled.error;
1497
+ const bound = new Set(names);
1498
+ for (const id of compiled.parsed.identifiers) {
1499
+ if (id === "now") continue;
1500
+ if (!bound.has(id)) return `expression references unbound identifier '${id}'`;
1501
+ }
1502
+ return null;
1503
+ }
1504
+ /**
1505
+ * Shared read-path evaluation. Builds a null-proto scope from `bindingValues`
1506
+ * plus the injected `now` (supplied by the caller for determinism and
1507
+ * testability), compiles via the LRU, and evaluates. Any failure (parse or
1508
+ * eval) returns `{ ok: false }` — the caller treats that as "skip this
1509
+ * derivation", never as a throw that takes the pass down.
1510
+ */
1511
+ function evaluateExpressionSource(expr, bindingValues, now, opts) {
1512
+ const compiled = compileExpressionSafe(expr);
1513
+ if (!compiled.ok) return {
1514
+ ok: false,
1515
+ error: compiled.error
1516
+ };
1517
+ const scope = createExpressionScope({
1518
+ ...bindingValues,
1519
+ ["now"]: now
1520
+ });
1521
+ const hooks = {
1522
+ ...opts?.hooks,
1523
+ now
1524
+ };
1525
+ try {
1526
+ return {
1527
+ ok: true,
1528
+ value: evaluateAst(compiled.parsed.ast, scope, {
1529
+ ...opts,
1530
+ hooks
1531
+ })
1532
+ };
1533
+ } catch (err) {
1534
+ return {
1535
+ ok: false,
1536
+ error: err instanceof ExpressionEvalError ? err.message : String(err)
1537
+ };
1538
+ }
1539
+ }
1540
+ //#endregion
1541
+ //#region src/composition/composition.ts
1542
+ /**
1543
+ * A COMPOSITION: a device assembled from other devices' state (D659).
1544
+ *
1545
+ * The data half of the composed-devices spec §1. A composition is stored inside
1546
+ * a core block (`CoreBlock.source.kind === 'composition'`) and run by the shared
1547
+ * `composer` runner. It names a TARGET and, for each capability of the target, a
1548
+ * FIELD MAP: every state field has exactly one source.
1549
+ *
1550
+ * Sources are keyed by `(addonId, stableId)`, NEVER by numeric device id. A
1551
+ * resync reissues numeric ids, and a composition that stored them would be
1552
+ * orphaned by the first re-adoption (the linked-devices lesson). A `stableId` is
1553
+ * unique only within its owning addon, so the owner travels with it.
1554
+ *
1555
+ * Later slices EXTEND these unions and do not reshape them. The variants a later
1556
+ * slice implements are already in the wire shape, and `validateComposition`
1557
+ * refuses them BY NAME until then:
1558
+ * feature `passthrough` → slice 3 · `commands` (`forward`) → slice 3 ·
1559
+ * source `code` → slice 4. Target `existing` (customize a device) is slice 2 (D663).
1560
+ */
1561
+ /** The owner of every composed device: the `composer` builtin. */
1562
+ var COMPOSER_ADDON_ID = "composer";
1563
+ /**
1564
+ * The stored name of a block that customizes an EXISTING device starts with this
1565
+ * (`customizationBlockName`, `@camstack/types/node`). Never parsed to tell a
1566
+ * customization apart: `composition.target.kind === 'existing'` says that (D663).
1567
+ */
1568
+ var CUSTOMIZATION_BLOCK_NAME_PREFIX = "customize:";
1569
+ /** A composed device's stableId is this prefix plus the block id. */
1570
+ var COMPOSED_DEVICE_STABLE_ID_PREFIX = "composed-";
1571
+ /** A device with more capabilities than this is two devices. */
1572
+ var MAX_COMPOSITION_FEATURES = 16;
1573
+ var MAX_COMPOSITION_FIELDS_PER_FEATURE = 32;
1574
+ /** Longest snippet a `code` source may carry (slice 4). */
1575
+ var MAX_COMPOSITION_CODE_LENGTH = 2e4;
1576
+ var CompositionSourceRefSchema = zod.z.object({
1577
+ /** The owning addon; `stableId` is unique only within it. */
1578
+ addonId: zod.z.string().min(1),
1579
+ stableId: zod.z.string().min(1)
1580
+ });
1581
+ /** One field of one capability of one source device. */
1582
+ var CompositionFieldReadSchema = zod.z.object({
1583
+ source: CompositionSourceRefSchema,
1584
+ cap: zod.z.string().min(1),
1585
+ /** Dotted path into the source cap's runtime-state slice. */
1586
+ fieldPath: zod.z.string().min(1)
1587
+ });
1588
+ /** `from`: copy one source field verbatim. It is also an expression's `from` binding. */
1589
+ var CompositionFromSourceSchema = CompositionFieldReadSchema.extend({ kind: zod.z.literal("from") });
1590
+ /** A constant binding. No device is read. */
1591
+ var CompositionLiteralBindingSchema = zod.z.object({
1592
+ kind: zod.z.literal("literal"),
1593
+ value: zod.z.union([
1594
+ zod.z.string(),
1595
+ zod.z.number(),
1596
+ zod.z.boolean(),
1597
+ zod.z.null()
1598
+ ])
1599
+ });
1600
+ var CompositionBindingSchema = zod.z.discriminatedUnion("kind", [CompositionFromSourceSchema, CompositionLiteralBindingSchema]);
1601
+ /**
1602
+ * `expression`: a formula over named bindings, in the salvaged expression
1603
+ * engine. Validated at parse by the SAME function every other consumer runs,
1604
+ * so the editor and the store cannot disagree.
1605
+ */
1606
+ var CompositionExpressionSourceSchema = zod.z.object({
1607
+ kind: zod.z.literal("expression"),
1608
+ expr: zod.z.string().min(1).max(MAX_EXPRESSION_SOURCE_LENGTH),
1609
+ bindings: zod.z.record(zod.z.string().regex(EXPRESSION_IDENTIFIER_RE), CompositionBindingSchema)
1610
+ }).superRefine((src, ctx) => {
1611
+ const err = validateExpressionSource(src);
1612
+ if (err !== null) ctx.addIssue({
1613
+ code: "custom",
1614
+ message: err,
1615
+ path: ["expr"]
1616
+ });
1617
+ });
1618
+ /** `code`: RESERVED for slice 4 (own runner, one-way eject). */
1619
+ var CompositionCodeSourceSchema = zod.z.object({
1620
+ kind: zod.z.literal("code"),
1621
+ code: zod.z.string().min(1).max(MAX_COMPOSITION_CODE_LENGTH)
1622
+ });
1623
+ var CompositionFieldSourceSchema = zod.z.discriminatedUnion("kind", [
1624
+ CompositionFromSourceSchema,
1625
+ CompositionExpressionSourceSchema,
1626
+ CompositionCodeSourceSchema
1627
+ ]);
1628
+ /** `forward`: RESERVED for slice 3. Argument mapping arrives there as an additive optional `args`. */
1629
+ var CompositionForwardCommandSchema = zod.z.object({
1630
+ kind: zod.z.literal("forward"),
1631
+ source: CompositionSourceRefSchema,
1632
+ cap: zod.z.string().min(1),
1633
+ method: zod.z.string().min(1)
1634
+ });
1635
+ var CompositionCommandTargetSchema = zod.z.discriminatedUnion("kind", [CompositionForwardCommandSchema, CompositionCodeSourceSchema]);
1636
+ /** An item key inside an item-array cap (`consumables.items[<key>]`). */
1637
+ var COMPOSITION_ITEM_KEY_RE = /^[a-z0-9][a-z0-9-]{0,47}$/;
1638
+ var MAX_COMPOSITION_ITEMS = 16;
1639
+ /** The item-array path of every item-array cap that exists (`consumables.items`). */
1640
+ var COMPOSITION_ITEM_ARRAY_PATH = "items";
1641
+ /**
1642
+ * One item of an item-array cap (D663), addressed through the cap's
1643
+ * `status.itemArray` descriptor. `fields` are paths INSIDE the item, nested
1644
+ * allowed (`remaining.value`); the key and label come from the entry itself.
1645
+ */
1646
+ var CompositionItemEntrySchema = zod.z.object({
1647
+ label: zod.z.string().min(1).max(80),
1648
+ fields: zod.z.record(zod.z.string().min(1), CompositionFieldSourceSchema)
1649
+ });
1650
+ var CompositionFieldsFeatureSchema = zod.z.object({
1651
+ kind: zod.z.literal("fields"),
1652
+ cap: zod.z.string().min(1),
1653
+ fields: zod.z.record(zod.z.string().min(1), CompositionFieldSourceSchema).refine((fields) => Object.keys(fields).length <= 32, { message: `at most 32 fields per capability` }),
1654
+ /**
1655
+ * Items of the cap's item array, keyed by item key (D663). Naming `items`
1656
+ * owns the WHOLE array field: the composed value holds exactly these items.
1657
+ */
1658
+ items: zod.z.record(zod.z.string().regex(COMPOSITION_ITEM_KEY_RE), CompositionItemEntrySchema).refine((items) => Object.keys(items).length <= 16, { message: `at most 16 items` }).optional(),
1659
+ /** RESERVED for slice 3; refused by the validator when non-empty. */
1660
+ commands: zod.z.record(zod.z.string().min(1), CompositionCommandTargetSchema).optional()
1661
+ });
1662
+ /** RESERVED for slice 3: a media / data-plane cap, whole, from one source. */
1663
+ var CompositionPassthroughFeatureSchema = zod.z.object({
1664
+ kind: zod.z.literal("passthrough"),
1665
+ cap: zod.z.string().min(1),
1666
+ source: CompositionSourceRefSchema
1667
+ });
1668
+ var CompositionFeatureSchema = zod.z.discriminatedUnion("kind", [CompositionFieldsFeatureSchema, CompositionPassthroughFeatureSchema]);
1669
+ /**
1670
+ * A NEW device. It has no `name` of its own: the block's name IS the device's
1671
+ * name (2026-08-08). A second copy here would be a second writer of one string (D62).
1672
+ */
1673
+ var CompositionNewTargetSchema = zod.z.object({
1674
+ kind: zod.z.literal("new"),
1675
+ type: zod.z.enum(require_err_msg.DeviceType),
1676
+ role: zod.z.enum(require_err_msg.DeviceRole).optional()
1677
+ });
1678
+ /** Customize an EXISTING device: each feature adds a capability or replaces fields of a native one (D663). */
1679
+ var CompositionExistingTargetSchema = zod.z.object({
1680
+ kind: zod.z.literal("existing"),
1681
+ device: CompositionSourceRefSchema
1682
+ });
1683
+ var CompositionTargetSchema = zod.z.discriminatedUnion("kind", [CompositionNewTargetSchema, CompositionExistingTargetSchema]);
1684
+ var CompositionSchema = zod.z.object({
1685
+ target: CompositionTargetSchema,
1686
+ features: zod.z.array(CompositionFeatureSchema).min(1).max(16)
1687
+ }).superRefine((composition, ctx) => {
1688
+ const seen = /* @__PURE__ */ new Set();
1689
+ composition.features.forEach((feature, index) => {
1690
+ if (seen.has(feature.cap)) ctx.addIssue({
1691
+ code: "custom",
1692
+ message: `capability \`${feature.cap}\` is composed twice — one feature per capability`,
1693
+ path: [
1694
+ "features",
1695
+ index,
1696
+ "cap"
1697
+ ]
1698
+ });
1699
+ seen.add(feature.cap);
1700
+ });
1701
+ });
1702
+ function compositionSourceKey(ref) {
1703
+ return `${ref.addonId}/${ref.stableId}`;
1704
+ }
1705
+ function compositionSliceKey(ref, cap) {
1706
+ return `${compositionSourceKey(ref)}#${cap}`;
1707
+ }
1708
+ function compositionReadKey(read) {
1709
+ return `${compositionSliceKey(read.source, read.cap)}.${read.fieldPath}`;
1710
+ }
1711
+ function composedDeviceStableId(blockId) {
1712
+ return `${COMPOSED_DEVICE_STABLE_ID_PREFIX}${blockId}`;
1713
+ }
1714
+ function composedDeviceRef(blockId) {
1715
+ return {
1716
+ addonId: COMPOSER_ADDON_ID,
1717
+ stableId: composedDeviceStableId(blockId)
1718
+ };
1719
+ }
1720
+ /** Every device field a source reads: `from` is one read, an expression is its `from` bindings. */
1721
+ function fieldReads(source) {
1722
+ switch (source.kind) {
1723
+ case "from": return [{
1724
+ source: source.source,
1725
+ cap: source.cap,
1726
+ fieldPath: source.fieldPath
1727
+ }];
1728
+ case "expression": return Object.values(source.bindings).flatMap((binding) => binding.kind === "from" ? [{
1729
+ source: binding.source,
1730
+ cap: binding.cap,
1731
+ fieldPath: binding.fieldPath
1732
+ }] : []);
1733
+ case "code": return [];
1734
+ }
1735
+ }
1736
+ /** The ONE spelling of an item leaf as a field path: `items[desiccant].remaining.value`. */
1737
+ function compositionItemFieldPath(arrayPath, key, path) {
1738
+ return `${arrayPath}[${key}].${path}`;
1739
+ }
1740
+ /**
1741
+ * Every written field of a feature, item leaves under their bracketed path.
1742
+ * `arrayPath` is `'items'` for every item-array cap that exists
1743
+ * (`consumables.cap.ts`); the graph has no cap lookup, so the default stands
1744
+ * and `planItems` asserts it agrees with the cap's descriptor.
1745
+ */
1746
+ function featureFieldEntries(feature, arrayPath = COMPOSITION_ITEM_ARRAY_PATH) {
1747
+ return [...Object.entries(feature.fields).map(([fieldPath, source]) => ({
1748
+ fieldPath,
1749
+ source
1750
+ })), ...Object.entries(feature.items ?? {}).flatMap(([key, item]) => Object.entries(item.fields).map(([path, source]) => ({
1751
+ fieldPath: compositionItemFieldPath(arrayPath, key, path),
1752
+ source
1753
+ })))];
1754
+ }
1755
+ function featureSources(feature) {
1756
+ return featureFieldEntries(feature).map((e) => e.source);
1757
+ }
1758
+ //#endregion
1759
+ Object.defineProperty(exports, "AUDIO_PRESETS", {
1760
+ enumerable: true,
1761
+ get: function() {
1762
+ return AUDIO_PRESETS;
1763
+ }
1764
+ });
1765
+ Object.defineProperty(exports, "COMPOSED_DEVICE_STABLE_ID_PREFIX", {
1766
+ enumerable: true,
1767
+ get: function() {
1768
+ return COMPOSED_DEVICE_STABLE_ID_PREFIX;
1769
+ }
1770
+ });
1771
+ Object.defineProperty(exports, "COMPOSER_ADDON_ID", {
1772
+ enumerable: true,
1773
+ get: function() {
1774
+ return COMPOSER_ADDON_ID;
1775
+ }
1776
+ });
1777
+ Object.defineProperty(exports, "COMPOSITION_ITEM_ARRAY_PATH", {
1778
+ enumerable: true,
1779
+ get: function() {
1780
+ return COMPOSITION_ITEM_ARRAY_PATH;
1781
+ }
1782
+ });
1783
+ Object.defineProperty(exports, "COMPOSITION_ITEM_KEY_RE", {
1784
+ enumerable: true,
1785
+ get: function() {
1786
+ return COMPOSITION_ITEM_KEY_RE;
1787
+ }
1788
+ });
1789
+ Object.defineProperty(exports, "CUSTOMIZATION_BLOCK_NAME_PREFIX", {
1790
+ enumerable: true,
1791
+ get: function() {
1792
+ return CUSTOMIZATION_BLOCK_NAME_PREFIX;
1793
+ }
1794
+ });
1795
+ Object.defineProperty(exports, "CompositionBindingSchema", {
1796
+ enumerable: true,
1797
+ get: function() {
1798
+ return CompositionBindingSchema;
1799
+ }
1800
+ });
1801
+ Object.defineProperty(exports, "CompositionCodeSourceSchema", {
1802
+ enumerable: true,
1803
+ get: function() {
1804
+ return CompositionCodeSourceSchema;
1805
+ }
1806
+ });
1807
+ Object.defineProperty(exports, "CompositionCommandTargetSchema", {
1808
+ enumerable: true,
1809
+ get: function() {
1810
+ return CompositionCommandTargetSchema;
1811
+ }
1812
+ });
1813
+ Object.defineProperty(exports, "CompositionExistingTargetSchema", {
1814
+ enumerable: true,
1815
+ get: function() {
1816
+ return CompositionExistingTargetSchema;
1817
+ }
1818
+ });
1819
+ Object.defineProperty(exports, "CompositionExpressionSourceSchema", {
1820
+ enumerable: true,
1821
+ get: function() {
1822
+ return CompositionExpressionSourceSchema;
1823
+ }
1824
+ });
1825
+ Object.defineProperty(exports, "CompositionFeatureSchema", {
1826
+ enumerable: true,
1827
+ get: function() {
1828
+ return CompositionFeatureSchema;
1829
+ }
1830
+ });
1831
+ Object.defineProperty(exports, "CompositionFieldReadSchema", {
1832
+ enumerable: true,
1833
+ get: function() {
1834
+ return CompositionFieldReadSchema;
1835
+ }
1836
+ });
1837
+ Object.defineProperty(exports, "CompositionFieldSourceSchema", {
1838
+ enumerable: true,
1839
+ get: function() {
1840
+ return CompositionFieldSourceSchema;
1841
+ }
1842
+ });
1843
+ Object.defineProperty(exports, "CompositionFieldsFeatureSchema", {
1844
+ enumerable: true,
1845
+ get: function() {
1846
+ return CompositionFieldsFeatureSchema;
1847
+ }
1848
+ });
1849
+ Object.defineProperty(exports, "CompositionForwardCommandSchema", {
1850
+ enumerable: true,
1851
+ get: function() {
1852
+ return CompositionForwardCommandSchema;
1853
+ }
1854
+ });
1855
+ Object.defineProperty(exports, "CompositionFromSourceSchema", {
1856
+ enumerable: true,
1857
+ get: function() {
1858
+ return CompositionFromSourceSchema;
1859
+ }
1860
+ });
1861
+ Object.defineProperty(exports, "CompositionItemEntrySchema", {
1862
+ enumerable: true,
1863
+ get: function() {
1864
+ return CompositionItemEntrySchema;
1865
+ }
1866
+ });
1867
+ Object.defineProperty(exports, "CompositionLiteralBindingSchema", {
1868
+ enumerable: true,
1869
+ get: function() {
1870
+ return CompositionLiteralBindingSchema;
1871
+ }
1872
+ });
1873
+ Object.defineProperty(exports, "CompositionNewTargetSchema", {
1874
+ enumerable: true,
1875
+ get: function() {
1876
+ return CompositionNewTargetSchema;
1877
+ }
1878
+ });
1879
+ Object.defineProperty(exports, "CompositionPassthroughFeatureSchema", {
1880
+ enumerable: true,
1881
+ get: function() {
1882
+ return CompositionPassthroughFeatureSchema;
1883
+ }
1884
+ });
1885
+ Object.defineProperty(exports, "CompositionSchema", {
1886
+ enumerable: true,
1887
+ get: function() {
1888
+ return CompositionSchema;
1889
+ }
1890
+ });
1891
+ Object.defineProperty(exports, "CompositionSourceRefSchema", {
1892
+ enumerable: true,
1893
+ get: function() {
1894
+ return CompositionSourceRefSchema;
1895
+ }
1896
+ });
1897
+ Object.defineProperty(exports, "CompositionTargetSchema", {
1898
+ enumerable: true,
1899
+ get: function() {
1900
+ return CompositionTargetSchema;
1901
+ }
1902
+ });
1903
+ Object.defineProperty(exports, "EXPRESSION_BUILTINS", {
1904
+ enumerable: true,
1905
+ get: function() {
1906
+ return EXPRESSION_BUILTINS;
1907
+ }
1908
+ });
1909
+ Object.defineProperty(exports, "EXPRESSION_BUILTIN_NAMES", {
1910
+ enumerable: true,
1911
+ get: function() {
1912
+ return EXPRESSION_BUILTIN_NAMES;
1913
+ }
1914
+ });
1915
+ Object.defineProperty(exports, "EXPRESSION_COMPILE_CACHE_CAPACITY", {
1916
+ enumerable: true,
1917
+ get: function() {
1918
+ return EXPRESSION_COMPILE_CACHE_CAPACITY;
1919
+ }
1920
+ });
1921
+ Object.defineProperty(exports, "EXPRESSION_IDENTIFIER_RE", {
1922
+ enumerable: true,
1923
+ get: function() {
1924
+ return EXPRESSION_IDENTIFIER_RE;
1925
+ }
1926
+ });
1927
+ Object.defineProperty(exports, "EXPRESSION_INJECTED_NOW", {
1928
+ enumerable: true,
1929
+ get: function() {
1930
+ return EXPRESSION_INJECTED_NOW;
1931
+ }
1932
+ });
1933
+ Object.defineProperty(exports, "ExpressionEvalError", {
1934
+ enumerable: true,
1935
+ get: function() {
1936
+ return ExpressionEvalError;
1937
+ }
1938
+ });
1939
+ Object.defineProperty(exports, "ExpressionParseError", {
1940
+ enumerable: true,
1941
+ get: function() {
1942
+ return ExpressionParseError;
1943
+ }
1944
+ });
1945
+ Object.defineProperty(exports, "Fmp4BoxSplitter", {
1946
+ enumerable: true,
1947
+ get: function() {
1948
+ return Fmp4BoxSplitter;
1949
+ }
1950
+ });
1951
+ Object.defineProperty(exports, "MAX_COMPOSITION_CODE_LENGTH", {
1952
+ enumerable: true,
1953
+ get: function() {
1954
+ return MAX_COMPOSITION_CODE_LENGTH;
1955
+ }
1956
+ });
1957
+ Object.defineProperty(exports, "MAX_COMPOSITION_FEATURES", {
1958
+ enumerable: true,
1959
+ get: function() {
1960
+ return MAX_COMPOSITION_FEATURES;
1961
+ }
1962
+ });
1963
+ Object.defineProperty(exports, "MAX_COMPOSITION_FIELDS_PER_FEATURE", {
1964
+ enumerable: true,
1965
+ get: function() {
1966
+ return MAX_COMPOSITION_FIELDS_PER_FEATURE;
1967
+ }
1968
+ });
1969
+ Object.defineProperty(exports, "MAX_COMPOSITION_ITEMS", {
1970
+ enumerable: true,
1971
+ get: function() {
1972
+ return MAX_COMPOSITION_ITEMS;
1973
+ }
1974
+ });
1975
+ Object.defineProperty(exports, "MAX_EXPRESSION_AST_NODES", {
1976
+ enumerable: true,
1977
+ get: function() {
1978
+ return MAX_EXPRESSION_AST_NODES;
1979
+ }
1980
+ });
1981
+ Object.defineProperty(exports, "MAX_EXPRESSION_BINDINGS", {
1982
+ enumerable: true,
1983
+ get: function() {
1984
+ return MAX_EXPRESSION_BINDINGS;
1985
+ }
1986
+ });
1987
+ Object.defineProperty(exports, "MAX_EXPRESSION_CALL_ARGS", {
1988
+ enumerable: true,
1989
+ get: function() {
1990
+ return MAX_EXPRESSION_CALL_ARGS;
1991
+ }
1992
+ });
1993
+ Object.defineProperty(exports, "MAX_EXPRESSION_EVAL_STEPS", {
1994
+ enumerable: true,
1995
+ get: function() {
1996
+ return MAX_EXPRESSION_EVAL_STEPS;
1997
+ }
1998
+ });
1999
+ Object.defineProperty(exports, "MAX_EXPRESSION_SOURCE_LENGTH", {
2000
+ enumerable: true,
2001
+ get: function() {
2002
+ return MAX_EXPRESSION_SOURCE_LENGTH;
2003
+ }
2004
+ });
2005
+ Object.defineProperty(exports, "RESERVED_BINDING_NAMES", {
2006
+ enumerable: true,
2007
+ get: function() {
2008
+ return RESERVED_BINDING_NAMES;
2009
+ }
2010
+ });
2011
+ Object.defineProperty(exports, "audioPlanFromEncodeProfile", {
2012
+ enumerable: true,
2013
+ get: function() {
2014
+ return audioPlanFromEncodeProfile;
2015
+ }
2016
+ });
2017
+ Object.defineProperty(exports, "buildAudioArgs", {
2018
+ enumerable: true,
2019
+ get: function() {
2020
+ return buildAudioArgs;
2021
+ }
2022
+ });
2023
+ Object.defineProperty(exports, "buildFfmpegArgs", {
2024
+ enumerable: true,
2025
+ get: function() {
2026
+ return buildFfmpegArgs;
2027
+ }
2028
+ });
2029
+ Object.defineProperty(exports, "buildInputArgs", {
2030
+ enumerable: true,
2031
+ get: function() {
2032
+ return buildInputArgs;
2033
+ }
2034
+ });
2035
+ Object.defineProperty(exports, "buildVideoArgs", {
2036
+ enumerable: true,
2037
+ get: function() {
2038
+ return buildVideoArgs;
2039
+ }
2040
+ });
2041
+ Object.defineProperty(exports, "canonicalHash", {
2042
+ enumerable: true,
2043
+ get: function() {
2044
+ return canonicalHash;
2045
+ }
2046
+ });
2047
+ Object.defineProperty(exports, "compileExpression", {
2048
+ enumerable: true,
2049
+ get: function() {
2050
+ return compileExpression;
2051
+ }
2052
+ });
2053
+ Object.defineProperty(exports, "compileExpressionSafe", {
2054
+ enumerable: true,
2055
+ get: function() {
2056
+ return compileExpressionSafe;
2057
+ }
2058
+ });
2059
+ Object.defineProperty(exports, "composedDeviceRef", {
2060
+ enumerable: true,
2061
+ get: function() {
2062
+ return composedDeviceRef;
2063
+ }
2064
+ });
2065
+ Object.defineProperty(exports, "composedDeviceStableId", {
2066
+ enumerable: true,
2067
+ get: function() {
2068
+ return composedDeviceStableId;
2069
+ }
2070
+ });
2071
+ Object.defineProperty(exports, "compositionItemFieldPath", {
2072
+ enumerable: true,
2073
+ get: function() {
2074
+ return compositionItemFieldPath;
2075
+ }
2076
+ });
2077
+ Object.defineProperty(exports, "compositionReadKey", {
2078
+ enumerable: true,
2079
+ get: function() {
2080
+ return compositionReadKey;
2081
+ }
2082
+ });
2083
+ Object.defineProperty(exports, "compositionSliceKey", {
2084
+ enumerable: true,
2085
+ get: function() {
2086
+ return compositionSliceKey;
2087
+ }
2088
+ });
2089
+ Object.defineProperty(exports, "compositionSourceKey", {
2090
+ enumerable: true,
2091
+ get: function() {
2092
+ return compositionSourceKey;
2093
+ }
2094
+ });
2095
+ Object.defineProperty(exports, "createExpressionScope", {
2096
+ enumerable: true,
2097
+ get: function() {
2098
+ return createExpressionScope;
2099
+ }
2100
+ });
2101
+ Object.defineProperty(exports, "evaluateAst", {
2102
+ enumerable: true,
2103
+ get: function() {
2104
+ return evaluateAst;
2105
+ }
2106
+ });
2107
+ Object.defineProperty(exports, "evaluateExpressionSource", {
2108
+ enumerable: true,
2109
+ get: function() {
2110
+ return evaluateExpressionSource;
2111
+ }
2112
+ });
2113
+ Object.defineProperty(exports, "featureFieldEntries", {
2114
+ enumerable: true,
2115
+ get: function() {
2116
+ return featureFieldEntries;
2117
+ }
2118
+ });
2119
+ Object.defineProperty(exports, "featureSources", {
2120
+ enumerable: true,
2121
+ get: function() {
2122
+ return featureSources;
2123
+ }
2124
+ });
2125
+ Object.defineProperty(exports, "fieldReads", {
2126
+ enumerable: true,
2127
+ get: function() {
2128
+ return fieldReads;
2129
+ }
2130
+ });
2131
+ Object.defineProperty(exports, "invocationFromEncodeProfile", {
2132
+ enumerable: true,
2133
+ get: function() {
2134
+ return invocationFromEncodeProfile;
2135
+ }
2136
+ });
2137
+ Object.defineProperty(exports, "isSoftwareDecode", {
2138
+ enumerable: true,
2139
+ get: function() {
2140
+ return isSoftwareDecode;
2141
+ }
2142
+ });
2143
+ Object.defineProperty(exports, "logBannerArgs", {
2144
+ enumerable: true,
2145
+ get: function() {
2146
+ return logBannerArgs;
2147
+ }
2148
+ });
2149
+ Object.defineProperty(exports, "parseExpression", {
2150
+ enumerable: true,
2151
+ get: function() {
2152
+ return parseExpression;
2153
+ }
2154
+ });
2155
+ Object.defineProperty(exports, "pickVideoEncoder", {
2156
+ enumerable: true,
2157
+ get: function() {
2158
+ return pickVideoEncoder;
2159
+ }
2160
+ });
2161
+ Object.defineProperty(exports, "resolveStreamMaps", {
2162
+ enumerable: true,
2163
+ get: function() {
2164
+ return resolveStreamMaps;
2165
+ }
2166
+ });
2167
+ Object.defineProperty(exports, "toExpressionValue", {
2168
+ enumerable: true,
2169
+ get: function() {
2170
+ return toExpressionValue;
2171
+ }
2172
+ });
2173
+ Object.defineProperty(exports, "tokenize", {
2174
+ enumerable: true,
2175
+ get: function() {
2176
+ return tokenize;
2177
+ }
2178
+ });
2179
+ Object.defineProperty(exports, "validateExpressionSource", {
2180
+ enumerable: true,
2181
+ get: function() {
2182
+ return validateExpressionSource;
2183
+ }
2184
+ });