@remavideo/sdk 0.13.0 → 0.15.0

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.
package/dist/index.d.ts CHANGED
@@ -20,6 +20,11 @@ declare const CreateNodeInputSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
20
20
  rtmpReader: z.ZodObject<{
21
21
  app: z.ZodString;
22
22
  sourceName: z.ZodString;
23
+ retry: z.ZodOptional<z.ZodObject<{
24
+ maxAttempts: z.ZodOptional<z.ZodNumber>;
25
+ baseDelayMs: z.ZodOptional<z.ZodNumber>;
26
+ maxDelayMs: z.ZodOptional<z.ZodNumber>;
27
+ }, z.core.$strip>>;
23
28
  }, z.core.$strip>;
24
29
  }, z.core.$strip>, z.ZodObject<{
25
30
  workflowId: z.ZodString;
@@ -61,6 +66,8 @@ declare const CreateNodeInputSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
61
66
  makePrefixUnique: z.ZodDefault<z.ZodBoolean>;
62
67
  accessKeyId: z.ZodString;
63
68
  secretAccessKey: z.ZodString;
69
+ playlistCacheControl: z.ZodOptional<z.ZodString>;
70
+ segmentsCacheControl: z.ZodOptional<z.ZodString>;
64
71
  }, z.core.$strip>, z.ZodObject<{
65
72
  type: z.ZodLiteral<"http">;
66
73
  baseUrl: z.ZodString;
@@ -118,6 +125,11 @@ declare const CreateNodeInputSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
118
125
  srtReader: z.ZodObject<{
119
126
  app: z.ZodString;
120
127
  sourceName: z.ZodString;
128
+ retry: z.ZodOptional<z.ZodObject<{
129
+ maxAttempts: z.ZodOptional<z.ZodNumber>;
130
+ baseDelayMs: z.ZodOptional<z.ZodNumber>;
131
+ maxDelayMs: z.ZodOptional<z.ZodNumber>;
132
+ }, z.core.$strip>>;
121
133
  }, z.core.$strip>;
122
134
  }, z.core.$strip>, z.ZodObject<{
123
135
  workflowId: z.ZodString;
@@ -479,6 +491,11 @@ declare const appRouter: _trpc_server.TRPCBuiltRouter<{
479
491
  rtmpReader: {
480
492
  app: string;
481
493
  sourceName: string;
494
+ retry?: {
495
+ maxAttempts?: number | undefined;
496
+ baseDelayMs?: number | undefined;
497
+ maxDelayMs?: number | undefined;
498
+ } | undefined;
482
499
  };
483
500
  } | {
484
501
  workflowId: string;
@@ -520,6 +537,8 @@ declare const appRouter: _trpc_server.TRPCBuiltRouter<{
520
537
  endpoint?: string | undefined;
521
538
  keyPrefix?: string | undefined;
522
539
  makePrefixUnique?: boolean | undefined;
540
+ playlistCacheControl?: string | undefined;
541
+ segmentsCacheControl?: string | undefined;
523
542
  } | {
524
543
  type: "http";
525
544
  baseUrl: string;
@@ -573,6 +592,11 @@ declare const appRouter: _trpc_server.TRPCBuiltRouter<{
573
592
  srtReader: {
574
593
  app: string;
575
594
  sourceName: string;
595
+ retry?: {
596
+ maxAttempts?: number | undefined;
597
+ baseDelayMs?: number | undefined;
598
+ maxDelayMs?: number | undefined;
599
+ } | undefined;
576
600
  };
577
601
  } | {
578
602
  workflowId: string;
@@ -1391,6 +1415,19 @@ interface RtmpReaderSettings extends NodeCallbacks {
1391
1415
  app: string;
1392
1416
  /** Source name (stream key). */
1393
1417
  sourceName: string;
1418
+ /**
1419
+ * Retry policy for a failed FFmpeg pull from the internal MediaMTX relay
1420
+ * (e.g. a transient network blip). All fields optional; defaults to 5
1421
+ * attempts with 1s→10s exponential backoff before the node errors out.
1422
+ */
1423
+ retry?: {
1424
+ /** Max retries before giving up and marking the node ERROR. Default: 5. */
1425
+ maxAttempts?: number;
1426
+ /** Initial backoff delay in ms before the first retry. Default: 1000. */
1427
+ baseDelayMs?: number;
1428
+ /** Backoff delay ceiling in ms. Default: 10000. */
1429
+ maxDelayMs?: number;
1430
+ };
1394
1431
  /** Called when an RTMP publisher connects. */
1395
1432
  onPublish?: (info: {
1396
1433
  app: string;
@@ -1462,6 +1499,19 @@ interface SrtReaderSettings extends NodeCallbacks {
1462
1499
  app: string;
1463
1500
  /** Source name (stream key / stream ID suffix). */
1464
1501
  sourceName: string;
1502
+ /**
1503
+ * Retry policy for a failed FFmpeg pull from the internal MediaMTX relay
1504
+ * (e.g. a transient network blip). All fields optional; defaults to 5
1505
+ * attempts with 1s→10s exponential backoff before the node errors out.
1506
+ */
1507
+ retry?: {
1508
+ /** Max retries before giving up and marking the node ERROR. Default: 5. */
1509
+ maxAttempts?: number;
1510
+ /** Initial backoff delay in ms before the first retry. Default: 1000. */
1511
+ baseDelayMs?: number;
1512
+ /** Backoff delay ceiling in ms. Default: 10000. */
1513
+ maxDelayMs?: number;
1514
+ };
1465
1515
  /** Called when an SRT publisher connects and the stream is ready to read. */
1466
1516
  onPublish?: (info: {
1467
1517
  app: string;
@@ -1514,6 +1564,18 @@ type HlsDestination = {
1514
1564
  makePrefixUnique?: boolean;
1515
1565
  accessKeyId: string;
1516
1566
  secretAccessKey: string;
1567
+ /**
1568
+ * `Cache-Control` header for `.m3u8` playlist objects.
1569
+ * Default: `"no-cache, no-store, must-revalidate"` — playlists must
1570
+ * never be served stale.
1571
+ */
1572
+ playlistCacheControl?: string;
1573
+ /**
1574
+ * `Cache-Control` header for segment objects (`.ts`, `.vtt`).
1575
+ * Default: `"public, max-age=86400"` — segments are immutable once
1576
+ * written, so they can be cached aggressively.
1577
+ */
1578
+ segmentsCacheControl?: string;
1517
1579
  } | {
1518
1580
  type: "http";
1519
1581
  /** Base URL that receives PUT/DELETE requests for each segment and playlist. */
package/dist/index.js CHANGED
@@ -524,7 +524,11 @@ async function createRtmpReaderNode(workflow, settings, nodeId) {
524
524
  kind: "RTMP_READER",
525
525
  workflowId: workflow.workflowId,
526
526
  nodeId: id,
527
- rtmpReader: { app: settings.app, sourceName: settings.sourceName }
527
+ rtmpReader: {
528
+ app: settings.app,
529
+ sourceName: settings.sourceName,
530
+ ...settings.retry !== void 0 && { retry: settings.retry }
531
+ }
528
532
  }).catch((err) => {
529
533
  removeHandler();
530
534
  throw err;
@@ -619,7 +623,11 @@ async function createSrtReaderNode(workflow, settings, nodeId) {
619
623
  kind: "SRT_READER",
620
624
  workflowId: workflow.workflowId,
621
625
  nodeId: id,
622
- srtReader: { app: settings.app, sourceName: settings.sourceName }
626
+ srtReader: {
627
+ app: settings.app,
628
+ sourceName: settings.sourceName,
629
+ ...settings.retry !== void 0 && { retry: settings.retry }
630
+ }
623
631
  }).catch((err) => {
624
632
  removeHandler();
625
633
  throw err;
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/input/caption-source.ts","../src/node.ts","../src/input/file.ts","../src/input/node-stream.ts","../src/input/rtmp-announcer.ts","../src/input/rtmp-multiplexer.ts","../src/input/rtmp-reader.ts","../src/input/srt-announcer.ts","../src/input/srt-reader.ts","../src/output/file.ts","../src/output/hls.ts","../src/output/rtmp.ts","../src/output/srt.ts","../src/processor/buffer.ts","../src/processor/camb-ai-translate.ts","../src/processor/cea608-encoder.ts","../src/processor/ffprobe-monitor.ts","../src/processor/gate.ts","../src/processor/gemini-translate.ts","../src/client.ts","../src/workflow.ts","../src/processor/audio-mixer.ts","../src/rema.ts","../src/selectors.ts"],"sourcesContent":["import { readFile } from \"node:fs/promises\";\nimport {\n BaseNode,\n dispatchNodeCallbacks,\n type NodeCallbacks,\n type WorkflowHandle,\n} from \"../node.js\";\n\nexport interface CaptionSourceSettings extends NodeCallbacks {\n /**\n * How many past caption entries to replay to an encoder that subscribes\n * after some captions have already been sent.\n *\n * - `0` (default) — no replay; each encoder only receives captions sent\n * after it connects. Correct for live captioning where old text is stale.\n * - `Infinity` — replay the full history. Use this when pre-scheduling\n * all captions upfront (e.g. from an SRT file) before the pipeline starts,\n * so encoders that activate later still see every entry.\n * - `N` — replay the last N entries only.\n */\n replayCount?: number;\n /**\n * BCP-47 language tag written into the HLS master playlist when this caption\n * source is wired as a subtitle track. Default: \"und\".\n */\n language?: string;\n /** Human-readable track label shown in player subtitle menus. Default: \"Subtitles\". */\n label?: string;\n /** Whether this track is pre-selected when the player first loads. Default: true. */\n defaultTrack?: boolean;\n /**\n * Maximum character length per caption line. When set, captions whose text\n * exceeds this length are automatically split into sequential chunks on the\n * server. Each chunk's display duration is proportional to its character\n * count relative to the original caption's total duration.\n */\n maxLineLength?: number;\n}\n\nexport class CaptionSourceNode extends BaseNode {\n readonly settings: CaptionSourceSettings;\n\n constructor(\n nodeId: string,\n workflow: WorkflowHandle,\n settings: CaptionSourceSettings,\n ) {\n super(nodeId, workflow.client, workflow.workflowId);\n this.settings = settings;\n }\n\n /**\n * Queue caption text for injection into the video stream.\n *\n * @param options.startAt Stream-elapsed milliseconds (relative to the\n * first video frame received by the server) at which\n * to show the caption. The server holds the text and\n * injects it at the matching PTS, so you can schedule\n * captions before that point in the stream is reached.\n * Omit (or 0) to inject immediately at the next frame.\n * @param options.duration How long (ms) the caption stays visible before\n * being automatically erased. Required.\n */\n async send(\n text: string,\n options: { duration: number; startAt?: number },\n ): Promise<void> {\n await this.client.trpc.sendCaption.mutate({\n workflowId: this.workflowId,\n nodeId: this.nodeId,\n text,\n durationMs: options.duration,\n startAtMs: options.startAt ?? 0,\n });\n }\n\n /**\n * Parse raw SRT or WebVTT content and schedule all entries on the server.\n *\n * Use `replayCount: Infinity` when creating this node if the pipeline may\n * connect encoders after the captions are loaded.\n *\n * @returns The number of caption entries loaded.\n */\n async sendFile(content: string): Promise<{ count: number }> {\n return this.client.trpc.loadCaptionFile.mutate({\n workflowId: this.workflowId,\n nodeId: this.nodeId,\n content,\n });\n }\n\n /**\n * Read a local SRT or WebVTT file and schedule all its entries on the server.\n * The file is read client-side; only the content string is sent over the wire.\n *\n * @param filePath Absolute or relative path to the subtitle file.\n * @returns The number of caption entries loaded.\n */\n async sendFileByPath(filePath: string): Promise<{ count: number }> {\n const content = await readFile(filePath, \"utf-8\");\n return this.sendFile(content);\n }\n}\n\nexport async function createCaptionSourceNode(\n workflow: WorkflowHandle,\n settings: CaptionSourceSettings = {},\n nodeId?: string,\n): Promise<CaptionSourceNode> {\n const id = nodeId ?? `caption_source_${Date.now()}`;\n const node = new CaptionSourceNode(id, workflow, settings);\n\n node._bindCallbacks(settings);\n const removeHandler = workflow._onNodeEvent(id, (evt) => {\n dispatchNodeCallbacks(node, evt, settings);\n });\n node._setEventCleanup(removeHandler);\n\n await workflow.client.trpc.createNode\n .mutate({\n kind: \"CAPTION_SOURCE\",\n workflowId: workflow.workflowId,\n nodeId: id,\n captionSource: {\n replayCount: settings.replayCount ?? 0,\n language: settings.language ?? \"und\",\n label: settings.label ?? \"Subtitles\",\n defaultTrack: settings.defaultTrack ?? true,\n ...(settings.maxLineLength !== undefined && {\n maxLineLength: settings.maxLineLength,\n }),\n },\n })\n .catch((err: unknown) => {\n removeHandler();\n throw err;\n });\n\n return node;\n}\n","import type {\n ActivationGate,\n NodeEventMsg,\n NodeState,\n TrackInfo,\n} from \"@remavideo/server\";\nimport { TypedEmitter } from \"tiny-typed-emitter\";\nimport type { RemaClient } from \"./client.js\";\n\n/** Lifecycle callbacks available on every node's settings object. */\nexport interface NodeCallbacks {\n /** Called when the node transitions into the `ERROR` state. */\n onError?: (err: Error) => void;\n /** Called on every lifecycle state transition (`READY`, `ACTIVE`, `ENDED`, `ERROR`, `CLOSED`). */\n onStatusChange?: (state: NodeState, message?: string) => void;\n /** Called when the node closes — locally via `.close()`, or remotely (workflow close, crash). */\n onClose?: () => void;\n}\n\n/** Events emitted by every node, mirroring {@link NodeCallbacks} for `.on()`-style consumers. */\nexport interface NodeEvents {\n error: (err: Error) => void;\n statusChange: (state: NodeState, message?: string) => void;\n close: () => void;\n}\n\nconst NODE_STATES = new Set<NodeState>([\n \"READY\",\n \"ACTIVE\",\n \"ENDED\",\n \"ERROR\",\n \"CLOSED\",\n]);\n\n/**\n * Report an error to a node's `onError` callback and its `NodeEvents`\n * emitter. The SDK does no logging of its own — the caller decides whether\n * and how an unhandled error surfaces, via `onError` or `.on(\"error\", ...)`.\n *\n * `BaseNode` extends Node's `EventEmitter` (via `tiny-typed-emitter`), which\n * throws synchronously from `emit()` when an `\"error\"` event has no\n * listener. Since callers typically only set the `onError` callback (not\n * `.on(\"error\", ...)`), emitting unconditionally would throw back into\n * whatever triggered this call — e.g. the `workflow.events` subscription's\n * `onData` handler, tearing down event delivery for the entire workflow.\n * Only emit when someone has opted in.\n */\nexport function reportNodeError(\n node: BaseNode,\n callbacks: NodeCallbacks,\n err: Error,\n): void {\n callbacks.onError?.(err);\n if (node.listenerCount(\"error\") > 0) node.emit(\"error\", err);\n}\n\n/**\n * Dispatch a raw node event to the generic lifecycle callbacks on a node's\n * settings object, and to the node's `NodeEvents` emitter. Call this from a\n * node factory's event handler alongside any node-type-specific event handling.\n */\nexport function dispatchNodeCallbacks(\n node: BaseNode,\n evt: NodeEventMsg,\n callbacks: NodeCallbacks,\n): void {\n if (NODE_STATES.has(evt.kind as NodeState)) {\n const state = evt.kind as NodeState;\n callbacks.onStatusChange?.(state, evt.message);\n node.emit(\"statusChange\", state, evt.message);\n }\n if (evt.kind === \"ERROR\") {\n reportNodeError(node, callbacks, new Error(evt.message ?? \"node error\"));\n }\n if (evt.kind === \"CLOSED\") {\n callbacks.onClose?.();\n node.emit(\"close\");\n }\n}\n\n/**\n * FFmpeg codec specification for one stream of a subscription.\n *\n * The receiving output node applies it where appropriate (e.g. per-rendition\n * `-c:v:N`/`-c:a:N` for HLS, `-c:v`/`-c:a` for RTMP/File outputs). Omitted =\n * stream copy.\n */\nexport interface CodecSpec {\n /** FFmpeg encoder name: `\"copy\"`, `\"aac\"`, `\"libx264\"`, … */\n name: string;\n /**\n * Extra encoder options keyed by bare FFmpeg option name (no stream\n * specifier), e.g. `{ preset: \"veryfast\", b: \"3M\" }`.\n */\n options?: Record<string, string | number>;\n}\n\n/**\n * Audio rendition metadata for HLS output nodes. Sets the language tag and\n * label that appear in the player's track menu. Ignored by non-HLS nodes.\n */\nexport interface AudioRenditionOptions {\n /** BCP-47 language tag, e.g. `\"en\"`, `\"fr\"`. Default: `\"und\"`. */\n language?: string;\n /** Label shown in player track menus. Default: `\"Audio\"`. */\n label?: string;\n /** Pre-select this track on first play. Default: `false`. */\n defaultTrack?: boolean;\n}\n\n/**\n * Video rendition metadata for HLS output nodes. Ignored by non-HLS nodes.\n */\nexport interface VideoRenditionOptions {\n /**\n * Label used to name the HLS variant playlist/segments (sanitized to a\n * URL-safe variant name).\n */\n label?: string;\n}\n\n/** Fields shared by every subscription variant. */\ninterface SubscriptionSpecBase {\n source: BaseNode;\n /**\n * Delay this stream by N milliseconds before it reaches the subscribing\n * node (applied server-side). Typical use: delay the A/V subscriptions so\n * a slower caption source has lead time.\n */\n delayMs?: number;\n /**\n * Read from a keyed output port of a multi-output source node (Gate).\n * Ports are named by the `name` field of the specs the gate itself was\n * subscribed with. Mutually exclusive with `track`.\n */\n port?: string;\n /**\n * When the *subscribing* node is a multi-output node (Gate), names the\n * pass-through port it creates for this source (default: the source's\n * index as a string). Ignored by other node types.\n */\n name?: string;\n}\n\n/** Subscribe to a video-only stream of a source node. */\nexport interface VideoSubscriptionSpec extends SubscriptionSpecBase {\n sourceSelector: \"VIDEO\";\n /**\n * Per-type video track index within the source container (0 = first video\n * track). Discover available indices with `await source.tracks()`.\n * Default: 0.\n */\n track?: number;\n /** Codec applied to the video stream by the receiving output node. */\n codec?: CodecSpec;\n /** Video rendition metadata (HLS). */\n video?: VideoRenditionOptions;\n}\n\n/** Subscribe to an audio-only stream of a source node. */\nexport interface AudioSubscriptionSpec extends SubscriptionSpecBase {\n sourceSelector: \"AUDIO\";\n /**\n * Per-type audio track index within the source container (0 = first audio\n * track). Discover available indices with `await source.tracks()`.\n * Default: 0.\n */\n track?: number;\n /** Codec applied to the audio stream by the receiving output node. */\n codec?: CodecSpec;\n /** Audio rendition metadata (HLS). */\n audio?: AudioRenditionOptions;\n}\n\n/** Subscribe to the combined A/V stream of a source node. */\nexport interface AllSubscriptionSpec extends SubscriptionSpecBase {\n sourceSelector: \"ALL\";\n /** Codec applied to the video track by the receiving output node. */\n videoCodec?: CodecSpec;\n /** Codec applied to the audio track by the receiving output node. */\n audioCodec?: CodecSpec;\n /** Video rendition metadata (HLS). */\n video?: VideoRenditionOptions;\n /** Audio rendition metadata (HLS). */\n audio?: AudioRenditionOptions;\n}\n\n/** Subscribe to the caption stream of a source node. */\nexport interface CaptionsSubscriptionSpec extends SubscriptionSpecBase {\n sourceSelector: \"CAPTIONS\";\n}\n\n/**\n * One source wired into a subscribing node, discriminated by\n * `sourceSelector` so per-stream options (codec, rendition metadata) are\n * only available where they apply.\n */\nexport type SubscriptionSpec =\n | VideoSubscriptionSpec\n | AudioSubscriptionSpec\n | AllSubscriptionSpec\n | CaptionsSubscriptionSpec;\n\n/**\n * Minimal interface passed to factory functions. Defined here (not in workflow.ts)\n * to avoid a circular import: workflow.ts imports factory files, factory files\n * import this interface.\n */\nexport interface WorkflowHandle {\n readonly workflowId: string;\n readonly client: RemaClient;\n _onNodeEvent(\n nodeId: string,\n handler: (evt: NodeEventMsg) => void,\n ): () => void;\n}\n\n/**\n * Base class for all rema nodes.\n * Provides the .subscribe() method that wires nodes together on the server,\n * and a `NodeEvents` emitter (`.on(\"error\"/\"statusChange\"/\"close\", ...)`) as\n * an alternative to the `onError`/`onStatusChange`/`onClose` settings callbacks.\n */\nexport abstract class BaseNode extends TypedEmitter<NodeEvents> {\n readonly nodeId: string;\n readonly workflowId: string;\n protected readonly client: RemaClient;\n private _eventCleanup: (() => void) | null = null;\n private _callbacks: NodeCallbacks = {};\n\n constructor(nodeId: string, client: RemaClient, workflowId: string) {\n super();\n this.nodeId = nodeId;\n this.workflowId = workflowId;\n this.client = client;\n }\n\n /** Called by create functions to register the workflow event handler cleanup. */\n _setEventCleanup(fn: () => void): void {\n this._eventCleanup = fn;\n }\n\n /**\n * Called by create functions to register the node's lifecycle callbacks\n * (`onError`/`onStatusChange`/`onClose`), so `.close()` can invoke `onClose`\n * directly on a locally-initiated close.\n */\n _bindCallbacks(callbacks: NodeCallbacks): void {\n this._callbacks = callbacks;\n }\n\n /**\n * Subscribe this node to one or more source nodes.\n * This tells the server to route streams from the sources into this node.\n */\n async subscribe(\n specs: SubscriptionSpec[],\n options?: { gate?: ActivationGate },\n ): Promise<void> {\n await this.client.trpc.subscribeNode.mutate({\n workflowId: this.workflowId,\n nodeId: this.nodeId,\n sources: specs.map((s) => BaseNode._toSourceInput(s)),\n gate: options?.gate,\n });\n }\n\n /** Map one `SubscriptionSpec` to the wire shape expected by the server. */\n private static _toSourceInput(spec: SubscriptionSpec) {\n const base = {\n sourceNodeId: spec.source.nodeId,\n ...(spec.delayMs !== undefined ? { delayMs: spec.delayMs } : {}),\n ...(spec.port !== undefined ? { port: spec.port } : {}),\n ...(spec.name !== undefined ? { name: spec.name } : {}),\n };\n const audio = (a: AudioRenditionOptions) => ({\n language: a.language ?? \"und\",\n label: a.label ?? \"Audio\",\n defaultTrack: a.defaultTrack ?? false,\n });\n // `video` is only sent when a label is provided — it is the only field\n // and is required on the wire.\n const video = (v: VideoRenditionOptions) =>\n v.label !== undefined ? { video: { label: v.label } } : {};\n\n switch (spec.sourceSelector) {\n case \"CAPTIONS\":\n return { ...base, selector: \"CAPTIONS\" as const };\n case \"VIDEO\":\n return {\n ...base,\n selector: \"VIDEO\" as const,\n ...(spec.track !== undefined ? { track: spec.track } : {}),\n ...(spec.codec !== undefined ? { codec: spec.codec } : {}),\n ...(spec.video !== undefined ? video(spec.video) : {}),\n };\n case \"AUDIO\":\n return {\n ...base,\n selector: \"AUDIO\" as const,\n ...(spec.track !== undefined ? { track: spec.track } : {}),\n ...(spec.codec !== undefined ? { codec: spec.codec } : {}),\n ...(spec.audio !== undefined ? { audio: audio(spec.audio) } : {}),\n };\n case \"ALL\":\n return {\n ...base,\n selector: \"ALL\" as const,\n ...(spec.videoCodec !== undefined\n ? { videoCodec: spec.videoCodec }\n : {}),\n ...(spec.audioCodec !== undefined\n ? { audioCodec: spec.audioCodec }\n : {}),\n ...(spec.video !== undefined ? video(spec.video) : {}),\n ...(spec.audio !== undefined ? { audio: audio(spec.audio) } : {}),\n };\n }\n }\n\n /**\n * Probe this node's media stream (ffprobe, server-side) and return its\n * track list. Each entry's `track` is the per-type index to pass back as\n * `track` in a VIDEO/AUDIO subscription:\n *\n * ```ts\n * const tracks = await input.tracks();\n * const ita = tracks.find((t) => t.type === \"audio\" && t.language === \"ita\");\n * await output.subscribe([\n * { source: input, sourceSelector: \"VIDEO\" },\n * { source: input, sourceSelector: \"AUDIO\", track: ita!.track },\n * ]);\n * ```\n *\n * Probing consumes real-time-paced stream data, so it can take several\n * seconds; `probeSizeBytes` (default 1 MB) trades discovery depth for\n * latency. The server errors after ~15 s if the source produces no data\n * (e.g. no publisher connected yet, or a disabled file input).\n */\n async tracks(options?: { probeSizeBytes?: number }): Promise<TrackInfo[]> {\n return this.client.trpc.probeTracks.query({\n workflowId: this.workflowId,\n nodeId: this.nodeId,\n ...(options?.probeSizeBytes !== undefined\n ? { probeSizeBytes: options.probeSizeBytes }\n : {}),\n });\n }\n\n /** Tear down this node on the server. */\n async close(): Promise<void> {\n this._eventCleanup?.();\n await this.client.trpc.closeNode.mutate({\n workflowId: this.workflowId,\n nodeId: this.nodeId,\n });\n this._callbacks.onClose?.();\n this.emit(\"close\");\n }\n}\n","import { uid } from \"radash\";\nimport {\n BaseNode,\n dispatchNodeCallbacks,\n type NodeCallbacks,\n type WorkflowHandle,\n} from \"../node.js\";\n\nexport interface FileInputSettings extends NodeCallbacks {\n /** Absolute path to the media file (MP4, TS, MKV, …) */\n fileName: string;\n /** Loop the file indefinitely. Default: false */\n loop?: boolean;\n /** Source name used to identify streams from this node */\n sourceName?: string;\n /**\n * Start in a disabled state — the file is not read until `.enable()` is called\n * (or the node is enabled from the web UI). Default: false.\n */\n disabled?: boolean;\n /** Called when the node transitions to the enabled / ready state */\n onReady?: () => void;\n /**\n * Called once when the first data packet from this input arrives at the server.\n * Use this to convert absolute timestamps to the stream-relative `startAt`\n * expected by `CaptionSourceNode.send()`:\n *\n * ```ts\n * onStreamStart(wallClockMs) {\n * const startAt = myAbsoluteCaptionMs - wallClockMs;\n * await captions.send(text, { startAt });\n * }\n * ```\n *\n * @param wallClockMs `Date.now()` on the server at the moment the first\n * MPEG-TS data packet arrived from this input.\n */\n onStreamStart?: (wallClockMs: number) => void;\n /** Called once the file has finished playing (reaches EOF). Never fires when `loop` is true. */\n onEnded?: () => void;\n}\n\nexport class FileInputNode extends BaseNode {\n readonly settings: FileInputSettings;\n\n constructor(\n nodeId: string,\n workflow: WorkflowHandle,\n settings: FileInputSettings,\n ) {\n super(nodeId, workflow.client, workflow.workflowId);\n this.settings = settings;\n }\n\n /** Enable this node — starts reading the file and streaming to downstream nodes. */\n async enable(): Promise<void> {\n await this.client.trpc.enableNode.mutate({\n workflowId: this.workflowId,\n nodeId: this.nodeId,\n });\n }\n}\n\nexport async function createFileInputNode(\n workflow: WorkflowHandle,\n settings: FileInputSettings,\n nodeId?: string,\n): Promise<FileInputNode> {\n const id = nodeId ?? `file_input_${uid(12)}`;\n const node = new FileInputNode(id, workflow, settings);\n\n node._bindCallbacks(settings);\n const removeHandler = workflow._onNodeEvent(id, (evt) => {\n if (evt.kind === \"READY\") settings.onReady?.();\n if (evt.kind === \"ACTIVE\" && evt.message !== undefined)\n settings.onStreamStart?.(Number(evt.message));\n if (evt.kind === \"ENDED\") settings.onEnded?.();\n dispatchNodeCallbacks(node, evt, settings);\n });\n node._setEventCleanup(removeHandler);\n\n await workflow.client.trpc.createNode\n .mutate({\n kind: \"FILE_INPUT\",\n workflowId: workflow.workflowId,\n nodeId: id,\n fileInput: {\n fileName: settings.fileName,\n loop: settings.loop ?? false,\n sourceName: settings.sourceName,\n disabled: settings.disabled ?? false,\n },\n })\n .catch((err: unknown) => {\n removeHandler();\n throw err;\n });\n\n return node;\n}\n","import { uid } from \"radash\";\nimport {\n BaseNode,\n dispatchNodeCallbacks,\n type NodeCallbacks,\n type WorkflowHandle,\n} from \"../node.js\";\n\nexport interface NodeStreamInputSettings extends NodeCallbacks {\n /**\n * FFmpeg format identifier for the incoming audio data.\n *\n * Raw PCM examples: `\"s16le\"` (16-bit signed LE), `\"f32le\"` (32-bit float LE).\n * Encoded examples: `\"wav\"`, `\"mp3\"`, `\"aac\"`, `\"flac\"`.\n */\n format: string;\n /** Sample rate in Hz. Required when `format` is a raw PCM format. */\n sampleRate?: number;\n /** Number of audio channels. Required when `format` is a raw PCM format. */\n channels?: number;\n /**\n * FFmpeg codec for the MPEG-TS output. Defaults to `\"aac\"`, which is safe\n * for any input format. Use `\"copy\"` only when the input is already\n * MPEG-TS-compatible AAC.\n */\n audioCodec?: string;\n /** Human-readable label shown in the dashboard. */\n label?: string;\n /** Called when the node transitions to the ready state on the server. */\n onReady?: () => void;\n /**\n * Called once when the first audio packet is written to the server.\n * The argument is `Date.now()` on the server at that moment.\n */\n onStreamStart?: (wallClockMs: number) => void;\n /** Called once the underlying FFmpeg process exits cleanly after `end()`. */\n onEnded?: () => void;\n}\n\nexport class NodeStreamInputNode extends BaseNode {\n readonly settings: NodeStreamInputSettings;\n private readonly _apiBase: string;\n\n constructor(\n nodeId: string,\n workflow: WorkflowHandle,\n settings: NodeStreamInputSettings,\n ) {\n super(nodeId, workflow.client, workflow.workflowId);\n this.settings = settings;\n const { host, port } = workflow.client.options;\n this._apiBase = `http://${host}:${port}`;\n }\n\n /**\n * Push an audio stream to the server.\n *\n * Accepts any `AsyncIterable<Buffer | Uint8Array>`, which includes Node.js\n * `Readable` streams (async-iterable since Node 10) and async generators.\n * The call resolves when the source iterable is exhausted or the server\n * closes the connection.\n *\n * @example\n * ```ts\n * import { createReadStream } from \"node:fs\";\n * await audioInput.stream(createReadStream(\"audio.mp3\"));\n * ```\n *\n * @example\n * ```ts\n * // Raw PCM from a microphone via node-record-lpcm16\n * const mic = recorder.start({ sampleRate: 16000, channels: 1 });\n * await audioInput.stream(mic);\n * ```\n */\n async stream(source: AsyncIterable<Buffer | Uint8Array>): Promise<void> {\n const url = `${this._apiBase}/api/node-stream/${this.workflowId}/${this.nodeId}`;\n\n const body = new ReadableStream<Uint8Array>({\n async start(controller) {\n try {\n for await (const chunk of source) {\n controller.enqueue(\n chunk instanceof Uint8Array ? chunk : new Uint8Array(chunk),\n );\n }\n controller.close();\n } catch (err) {\n controller.error(err);\n }\n },\n });\n\n await fetch(url, {\n method: \"POST\",\n body,\n // Required for streaming request bodies in Node.js 18+ native fetch.\n duplex: \"half\",\n } as RequestInit);\n }\n}\n\nexport async function createNodeStreamInputNode(\n workflow: WorkflowHandle,\n settings: NodeStreamInputSettings,\n nodeId?: string,\n): Promise<NodeStreamInputNode> {\n const id = nodeId ?? `node_stream_${uid(12)}`;\n const node = new NodeStreamInputNode(id, workflow, settings);\n\n node._bindCallbacks(settings);\n const removeHandler = workflow._onNodeEvent(id, (evt) => {\n if (evt.kind === \"READY\") settings.onReady?.();\n if (evt.kind === \"ACTIVE\" && evt.message !== undefined)\n settings.onStreamStart?.(Number(evt.message));\n if (evt.kind === \"ENDED\") settings.onEnded?.();\n dispatchNodeCallbacks(node, evt, settings);\n });\n node._setEventCleanup(removeHandler);\n\n await workflow.client.trpc.createNode\n .mutate({\n kind: \"NODE_STREAM_INPUT\",\n workflowId: workflow.workflowId,\n nodeId: id,\n nodeStreamInput: {\n format: settings.format,\n ...(settings.sampleRate !== undefined && {\n sampleRate: settings.sampleRate,\n }),\n ...(settings.channels !== undefined && {\n channels: settings.channels,\n }),\n audioCodec: settings.audioCodec ?? \"aac\",\n ...(settings.label !== undefined && { label: settings.label }),\n },\n })\n .catch((err: unknown) => {\n removeHandler();\n throw err;\n });\n\n return node;\n}\n","import { uid } from \"radash\";\nimport {\n BaseNode,\n dispatchNodeCallbacks,\n type NodeCallbacks,\n type WorkflowHandle,\n} from \"../node.js\";\n\n/** Connection info passed to `onConnect` and `onDisconnect`. */\nexport interface RtmpConnectionInfo {\n app: string;\n sourceName: string;\n /**\n * MediaMTX connection id for this publisher, if it could be resolved at\n * connect time. Pass directly to `operations.disconnectSource`.\n */\n connectionId?: string;\n}\n\n/** Operations available on a live RTMP publisher connection. */\nexport interface RtmpAnnouncerOperations {\n /**\n * Forcibly disconnect the connection identified by `connectionId`. Safe to\n * call even if it already disconnected (resolves as a no-op).\n */\n disconnectSource(connectionId: string): Promise<void>;\n}\n\nexport interface RtmpAnnouncerSettings extends NodeCallbacks {\n /** RTMP app name to watch (default: \"live\"). */\n app?: string;\n /**\n * Called when a publisher connects. Create nodes in any workflow from\n * here, or call `operations.disconnectSource(info.connectionId)` to kick\n * the publisher synchronously (e.g. to reject an invalid `sourceName`).\n */\n onConnect: (\n info: RtmpConnectionInfo,\n operations: RtmpAnnouncerOperations,\n ) => void;\n /** Called when a publisher disconnects. */\n onDisconnect?: (info: RtmpConnectionInfo) => void;\n}\n\n/** SDK handle for an RTMP announcer node. */\nexport class RtmpAnnouncerNode extends BaseNode {\n /**\n * Forcibly disconnect the connection identified by `connectionId`. Can be\n * called at any time after the node is created — e.g. from an async\n * moderation decision made after `onConnect` already returned.\n */\n async disconnectSource(connectionId: string): Promise<void> {\n await this.client.trpc.disconnectSource.mutate({\n workflowId: this.workflowId,\n nodeId: this.nodeId,\n connectionId,\n });\n }\n}\n\nexport async function createRtmpAnnouncerNode(\n workflow: WorkflowHandle,\n settings: RtmpAnnouncerSettings,\n nodeId?: string,\n): Promise<RtmpAnnouncerNode> {\n const id = nodeId ?? `rtmp_announcer_${uid(12)}`;\n const app = settings.app ?? \"live\";\n const node = new RtmpAnnouncerNode(id, workflow.client, workflow.workflowId);\n const operations: RtmpAnnouncerOperations = {\n disconnectSource: (connectionId) => node.disconnectSource(connectionId),\n };\n\n node._bindCallbacks(settings);\n const removeHandler = workflow._onNodeEvent(id, (evt) => {\n if (evt.kind === \"STREAM_CONNECTED\" && evt.message !== undefined) {\n const info = JSON.parse(evt.message) as RtmpConnectionInfo;\n settings.onConnect(info, operations);\n }\n if (evt.kind === \"STREAM_DISCONNECTED\" && evt.message !== undefined) {\n const info = JSON.parse(evt.message) as RtmpConnectionInfo;\n settings.onDisconnect?.(info);\n }\n dispatchNodeCallbacks(node, evt, settings);\n });\n node._setEventCleanup(removeHandler);\n\n await workflow.client.trpc.createNode\n .mutate({\n kind: \"RTMP_ANNOUNCER\",\n workflowId: workflow.workflowId,\n nodeId: id,\n rtmpAnnouncer: { app },\n })\n .catch((err: unknown) => {\n removeHandler();\n throw err;\n });\n\n return node;\n}\n","import { uid } from \"radash\";\nimport type { RemaClient } from \"../client.js\";\nimport {\n BaseNode,\n dispatchNodeCallbacks,\n type NodeCallbacks,\n reportNodeError,\n type WorkflowHandle,\n} from \"../node.js\";\n\n/** Normalize a `catch`-clause value (which TypeScript types as `unknown`) into an `Error`. */\nfunction toError(err: unknown): Error {\n return err instanceof Error ? err : new Error(String(err));\n}\n\n// ── RtmpStreamNode ─────────────────────────────────────────────────────────────\n\n/**\n * Represents a single active RTMP stream created by an `RtmpMultiplexerNode`.\n *\n * The server creates this node automatically when a publisher connects. The SDK\n * wraps the server-side node ID so it can be passed as a source to downstream\n * nodes (encoders, outputs) via `subscribe()`.\n */\nexport class RtmpStreamNode extends BaseNode {\n readonly app: string;\n readonly sourceName: string;\n\n constructor(\n nodeId: string,\n client: RemaClient,\n app: string,\n sourceName: string,\n workflowId: string,\n ) {\n super(nodeId, client, workflowId);\n this.app = app;\n this.sourceName = sourceName;\n }\n}\n\n// ── RtmpMultiplexerNode ────────────────────────────────────────────────────────\n\n/**\n * SDK handle for an RTMP multiplexer node.\n *\n * Calling `close()` deactivates the multiplexer and all of its active stream\n * nodes on the server.\n */\nexport class RtmpMultiplexerNode extends BaseNode {}\n\n// ── Settings ──────────────────────────────────────────────────────────────────\n\nexport interface RtmpMultiplexerSettings extends NodeCallbacks {\n /** RTMP app name to listen on (default: \"live\"). */\n app?: string;\n\n /**\n * Called when a publisher connects. Return `{ accept: false }` to kick the\n * connection immediately; return `{ accept: true }` or omit the return value\n * to accept and trigger `onStream`.\n */\n onConnect?: (info: {\n app: string;\n sourceName: string;\n }) =>\n | Promise<{ accept: boolean } | undefined>\n | { accept: boolean }\n | undefined;\n\n /**\n * Called with the per-publisher `RtmpStreamNode` when a new RTMP stream is\n * accepted. Create your downstream pipeline (encoders, outputs) from this node.\n */\n onStream: (node: RtmpStreamNode) => Promise<void> | void;\n\n /**\n * Called when a publisher disconnects or is rejected. Use this to close any\n * downstream nodes you created in `onStream`.\n */\n onStreamEnd?: (node: RtmpStreamNode) => Promise<void> | void;\n}\n\n// ── Factory ───────────────────────────────────────────────────────────────────\n\nexport async function createRtmpMultiplexerNode(\n workflow: WorkflowHandle,\n settings: RtmpMultiplexerSettings,\n nodeId?: string,\n): Promise<RtmpMultiplexerNode> {\n const id = nodeId ?? `rtmp_mux_${uid(12)}`;\n const app = settings.app ?? \"live\";\n const node = new RtmpMultiplexerNode(\n id,\n workflow.client,\n workflow.workflowId,\n );\n\n // Track active stream nodes by sourceName for onStreamEnd lookup.\n const activeStreams = new Map<string, RtmpStreamNode>();\n\n node._bindCallbacks(settings);\n const removeHandler = workflow._onNodeEvent(id, (evt) => {\n dispatchNodeCallbacks(node, evt, settings);\n if (evt.kind === \"CHILD_CREATED\" && evt.message !== undefined) {\n const { childNodeId, sourceName } = JSON.parse(evt.message) as {\n childNodeId: string;\n sourceName: string;\n };\n\n Promise.resolve(settings.onConnect?.({ app, sourceName }))\n .then(async (result) => {\n if (result?.accept === false) {\n await workflow.client.trpc.rejectPublisher\n .mutate({\n workflowId: workflow.workflowId,\n nodeId: id,\n sourceName,\n })\n .catch((err: unknown) => {\n reportNodeError(node, settings, toError(err));\n });\n return;\n }\n const streamNode = new RtmpStreamNode(\n childNodeId,\n workflow.client,\n app,\n sourceName,\n workflow.workflowId,\n );\n activeStreams.set(sourceName, streamNode);\n await settings.onStream(streamNode);\n })\n .catch((err: unknown) => {\n reportNodeError(node, settings, toError(err));\n });\n return;\n }\n\n if (evt.kind === \"CHILD_CLOSED\" && evt.message !== undefined) {\n const { sourceName } = JSON.parse(evt.message) as { sourceName: string };\n const streamNode = activeStreams.get(sourceName);\n activeStreams.delete(sourceName);\n if (streamNode) {\n Promise.resolve(settings.onStreamEnd?.(streamNode)).catch(\n (err: unknown) => {\n reportNodeError(node, settings, toError(err));\n },\n );\n }\n }\n });\n node._setEventCleanup(removeHandler);\n\n await workflow.client.trpc.createNode\n .mutate({\n kind: \"RTMP_MULTIPLEXER\",\n workflowId: workflow.workflowId,\n nodeId: id,\n rtmpMultiplexer: { app, port: 1935 },\n })\n .catch((err: unknown) => {\n removeHandler();\n throw err;\n });\n\n return node;\n}\n","import { uid } from \"radash\";\nimport {\n BaseNode,\n dispatchNodeCallbacks,\n type NodeCallbacks,\n type WorkflowHandle,\n} from \"../node.js\";\n\nexport interface RtmpReaderSettings extends NodeCallbacks {\n /** RTMP app name. */\n app: string;\n /** Source name (stream key). */\n sourceName: string;\n /** Called when an RTMP publisher connects. */\n onPublish?: (info: { app: string; internalUrl: string }) => void;\n /**\n * Called once when the first data packet from this input arrives at the server.\n * @param wallClockMs `Date.now()` on the server at the moment the first MPEG-TS packet arrived.\n */\n onStreamStart?: (wallClockMs: number) => void;\n /** Called when the publisher disconnects (the underlying FFmpeg pull exits). */\n onStreamEnded?: () => void;\n}\n\nexport class RtmpReaderNode extends BaseNode {\n readonly settings: RtmpReaderSettings;\n\n constructor(\n nodeId: string,\n workflow: WorkflowHandle,\n settings: RtmpReaderSettings,\n ) {\n super(nodeId, workflow.client, workflow.workflowId);\n this.settings = settings;\n }\n}\n\nexport async function createRtmpReaderNode(\n workflow: WorkflowHandle,\n settings: RtmpReaderSettings,\n nodeId?: string,\n): Promise<RtmpReaderNode> {\n const id = nodeId ?? `rtmp_reader_${uid(12)}`;\n const node = new RtmpReaderNode(id, workflow, settings);\n\n node._bindCallbacks(settings);\n const removeHandler = workflow._onNodeEvent(id, (evt) => {\n if (evt.kind === \"READY\") {\n settings.onPublish?.({\n app: settings.app,\n internalUrl: evt.message ?? \"\",\n });\n }\n if (evt.kind === \"ACTIVE\" && evt.message !== undefined) {\n settings.onStreamStart?.(Number(evt.message));\n }\n if (evt.kind === \"ENDED\") settings.onStreamEnded?.();\n dispatchNodeCallbacks(node, evt, settings);\n });\n node._setEventCleanup(removeHandler);\n\n await workflow.client.trpc.createNode\n .mutate({\n kind: \"RTMP_READER\",\n workflowId: workflow.workflowId,\n nodeId: id,\n rtmpReader: { app: settings.app, sourceName: settings.sourceName },\n })\n .catch((err: unknown) => {\n removeHandler();\n throw err;\n });\n\n return node;\n}\n","import { uid } from \"radash\";\nimport {\n BaseNode,\n dispatchNodeCallbacks,\n type NodeCallbacks,\n type WorkflowHandle,\n} from \"../node.js\";\n\n/** Connection info passed to `onConnect` and `onDisconnect`. */\nexport interface SrtConnectionInfo {\n app: string;\n sourceName: string;\n /** Passphrase configured on the announcer, if any. */\n passphrase?: string;\n /** Negotiated SRT latency in milliseconds, if configured. */\n latencyMs?: number;\n /**\n * MediaMTX connection id for this publisher, if it could be resolved at\n * connect time. Pass directly to `operations.disconnectSource`.\n */\n connectionId?: string;\n}\n\n/** Operations available on a live SRT publisher connection. */\nexport interface SrtAnnouncerOperations {\n /**\n * Forcibly disconnect the connection identified by `connectionId`. Safe to\n * call even if it already disconnected (resolves as a no-op).\n */\n disconnectSource(connectionId: string): Promise<void>;\n}\n\nexport interface SrtAnnouncerSettings extends NodeCallbacks {\n /** SRT app name to watch (default: \"live\"). */\n app?: string;\n /** Passphrase to include in connection events (mirrors MediaMTX path config). */\n passphrase?: string;\n /** SRT latency in milliseconds to include in connection events. */\n latencyMs?: number;\n /**\n * Called when a publisher connects. Create nodes in any workflow from\n * here, or call `operations.disconnectSource(info.connectionId)` to kick\n * the publisher synchronously (e.g. to reject an invalid `sourceName`).\n */\n onConnect: (\n info: SrtConnectionInfo,\n operations: SrtAnnouncerOperations,\n ) => void;\n /** Called when a publisher disconnects. */\n onDisconnect?: (info: Pick<SrtConnectionInfo, \"app\" | \"sourceName\">) => void;\n}\n\n/** SDK handle for an SRT announcer node. */\nexport class SrtAnnouncerNode extends BaseNode {\n /**\n * Forcibly disconnect the connection identified by `connectionId`. Can be\n * called at any time after the node is created — e.g. from an async\n * moderation decision made after `onConnect` already returned.\n */\n async disconnectSource(connectionId: string): Promise<void> {\n await this.client.trpc.disconnectSource.mutate({\n workflowId: this.workflowId,\n nodeId: this.nodeId,\n connectionId,\n });\n }\n}\n\nexport async function createSrtAnnouncerNode(\n workflow: WorkflowHandle,\n settings: SrtAnnouncerSettings,\n nodeId?: string,\n): Promise<SrtAnnouncerNode> {\n const id = nodeId ?? `srt_announcer_${uid(12)}`;\n const app = settings.app ?? \"live\";\n const node = new SrtAnnouncerNode(id, workflow.client, workflow.workflowId);\n const operations: SrtAnnouncerOperations = {\n disconnectSource: (connectionId) => node.disconnectSource(connectionId),\n };\n\n node._bindCallbacks(settings);\n const removeHandler = workflow._onNodeEvent(id, (evt) => {\n if (evt.kind === \"STREAM_CONNECTED\" && evt.message !== undefined) {\n const info = JSON.parse(evt.message) as SrtConnectionInfo;\n settings.onConnect(info, operations);\n }\n if (evt.kind === \"STREAM_DISCONNECTED\" && evt.message !== undefined) {\n const info = JSON.parse(evt.message) as Pick<\n SrtConnectionInfo,\n \"app\" | \"sourceName\"\n >;\n settings.onDisconnect?.(info);\n }\n dispatchNodeCallbacks(node, evt, settings);\n });\n node._setEventCleanup(removeHandler);\n\n await workflow.client.trpc.createNode\n .mutate({\n kind: \"SRT_ANNOUNCER\",\n workflowId: workflow.workflowId,\n nodeId: id,\n srtAnnouncer: {\n app,\n ...(settings.passphrase !== undefined && {\n passphrase: settings.passphrase,\n }),\n ...(settings.latencyMs !== undefined && {\n latencyMs: settings.latencyMs,\n }),\n },\n })\n .catch((err: unknown) => {\n removeHandler();\n throw err;\n });\n\n return node;\n}\n","import { uid } from \"radash\";\nimport {\n BaseNode,\n dispatchNodeCallbacks,\n type NodeCallbacks,\n type WorkflowHandle,\n} from \"../node.js\";\n\nexport interface SrtReaderSettings extends NodeCallbacks {\n /** SRT app name. */\n app: string;\n /** Source name (stream key / stream ID suffix). */\n sourceName: string;\n /** Called when an SRT publisher connects and the stream is ready to read. */\n onPublish?: (info: { app: string; internalUrl: string }) => void;\n /**\n * Called once when the first data packet from this input arrives at the server.\n * @param wallClockMs `Date.now()` on the server at the moment the first MPEG-TS packet arrived.\n */\n onStreamStart?: (wallClockMs: number) => void;\n /** Called when the publisher disconnects (the underlying FFmpeg pull exits). */\n onStreamEnded?: () => void;\n}\n\n/** SDK handle for an SRT reader node. */\nexport class SrtReaderNode extends BaseNode {\n readonly settings: SrtReaderSettings;\n\n constructor(\n nodeId: string,\n workflow: WorkflowHandle,\n settings: SrtReaderSettings,\n ) {\n super(nodeId, workflow.client, workflow.workflowId);\n this.settings = settings;\n }\n}\n\nexport async function createSrtReaderNode(\n workflow: WorkflowHandle,\n settings: SrtReaderSettings,\n nodeId?: string,\n): Promise<SrtReaderNode> {\n const id = nodeId ?? `srt_reader_${uid(12)}`;\n const node = new SrtReaderNode(id, workflow, settings);\n\n node._bindCallbacks(settings);\n const removeHandler = workflow._onNodeEvent(id, (evt) => {\n if (evt.kind === \"READY\") {\n settings.onPublish?.({\n app: settings.app,\n internalUrl: evt.message ?? \"\",\n });\n }\n if (evt.kind === \"ACTIVE\" && evt.message !== undefined) {\n settings.onStreamStart?.(Number(evt.message));\n }\n if (evt.kind === \"ENDED\") settings.onStreamEnded?.();\n dispatchNodeCallbacks(node, evt, settings);\n });\n node._setEventCleanup(removeHandler);\n\n await workflow.client.trpc.createNode\n .mutate({\n kind: \"SRT_READER\",\n workflowId: workflow.workflowId,\n nodeId: id,\n srtReader: { app: settings.app, sourceName: settings.sourceName },\n })\n .catch((err: unknown) => {\n removeHandler();\n throw err;\n });\n\n return node;\n}\n","import { uid } from \"radash\";\nimport {\n BaseNode,\n dispatchNodeCallbacks,\n type NodeCallbacks,\n type WorkflowHandle,\n} from \"../node.js\";\n\nexport interface FileOutputSettings extends NodeCallbacks {\n /** Absolute path for the output file */\n fileName: string;\n /** Container format: \"mp4\" | \"ts\" | \"mkv\". Default: inferred from extension */\n format?: string;\n /** Overwrite `fileName` if it already exists. Default: `true`. */\n overwrite?: boolean;\n}\n\nexport class FileOutputNode extends BaseNode {\n readonly settings: FileOutputSettings;\n\n constructor(\n nodeId: string,\n workflow: WorkflowHandle,\n settings: FileOutputSettings,\n ) {\n super(nodeId, workflow.client, workflow.workflowId);\n this.settings = settings;\n }\n}\n\nexport async function createFileOutputNode(\n workflow: WorkflowHandle,\n settings: FileOutputSettings,\n nodeId?: string,\n): Promise<FileOutputNode> {\n const id = nodeId ?? `file_output_${uid(12)}`;\n const node = new FileOutputNode(id, workflow, settings);\n\n node._bindCallbacks(settings);\n const removeHandler = workflow._onNodeEvent(id, (evt) => {\n dispatchNodeCallbacks(node, evt, settings);\n });\n node._setEventCleanup(removeHandler);\n\n await workflow.client.trpc.createNode\n .mutate({\n kind: \"FILE_OUTPUT\",\n workflowId: workflow.workflowId,\n nodeId: id,\n fileOutput: {\n fileName: settings.fileName,\n format: settings.format ?? \"\",\n overwrite: settings.overwrite ?? true,\n },\n })\n .catch((err: unknown) => {\n removeHandler();\n throw err;\n });\n\n return node;\n}\n","import { uid } from \"radash\";\nimport {\n BaseNode,\n dispatchNodeCallbacks,\n type NodeCallbacks,\n type WorkflowHandle,\n} from \"../node.js\";\n\nexport type HlsDestination =\n | { type: \"file\"; path: string }\n | {\n type: \"s3\";\n bucket: string;\n region: string;\n /** S3-compatible endpoint URL (e.g. Tigris, MinIO, Cloudflare R2). */\n endpoint?: string;\n /** Key prefix prepended to all uploaded paths. */\n keyPrefix?: string;\n /**\n * When true (default), a per-session timestamp suffix is appended to\n * `keyPrefix` so that browser-cached segments from a previous session are\n * never served for a new one. Set to false when the caller already\n * generates a unique prefix or when sub-paths are not supported.\n */\n makePrefixUnique?: boolean;\n accessKeyId: string;\n secretAccessKey: string;\n }\n | {\n type: \"http\";\n /** Base URL that receives PUT/DELETE requests for each segment and playlist. */\n baseUrl: string;\n /** Extra headers sent with every PUT/DELETE request. */\n headers?: Record<string, string>;\n };\n\nexport interface HlsOutputSettings extends NodeCallbacks {\n /** One or more destinations to fan HLS output to. */\n destinations: HlsDestination[];\n /** Target segment duration in seconds. Default: 6 */\n segmentTime?: number;\n /**\n * Number of segments kept in the live playlist window.\n * 0 = keep all (VOD). Default: 5.\n */\n listSize?: number;\n /**\n * Node-level fallback audio codec, applied to audio streams whose\n * subscription does not specify its own `codec`. Omitted (or `copy`)\n * preserves the input audio codec — except when more than one audio source\n * is connected, where the default becomes `aac` so all alternate-audio\n * renditions share one codec (required by hls.js).\n *\n * For a caption-readiness buffer (the old `avDelayMs`), put `delayMs` on\n * each media subscription instead and leave caption subscriptions\n * undelayed.\n */\n audioCodec?: \"copy\" | string | null;\n /**\n * Node-level fallback video codec, applied to video streams whose\n * subscription does not specify its own `codec`. Omitted (or `copy`)\n * preserves the input video codec.\n */\n videoCodec?: \"copy\" | string | null;\n}\n\nexport class HlsOutputNode extends BaseNode {\n readonly settings: HlsOutputSettings;\n\n constructor(\n nodeId: string,\n workflow: WorkflowHandle,\n settings: HlsOutputSettings,\n ) {\n super(nodeId, workflow.client, workflow.workflowId);\n this.settings = settings;\n }\n\n /**\n * Return the effective base path or URL for each configured destination in\n * the current session. Must be called after `subscribe()` so that the server\n * has wired the destinations. For S3 destinations this includes the\n * per-session subfolder appended by `makePrefixUnique`.\n */\n async effectivePaths(): Promise<string[]> {\n const result = await this.client.trpc.hlsOutputInfo.query({\n workflowId: this.workflowId,\n nodeId: this.nodeId,\n });\n return result.effectivePaths;\n }\n}\n\nexport async function createHlsOutputNode(\n workflow: WorkflowHandle,\n settings: HlsOutputSettings,\n nodeId?: string,\n): Promise<HlsOutputNode> {\n const id = nodeId ?? `hls_output_${uid(12)}`;\n const node = new HlsOutputNode(id, workflow, settings);\n\n node._bindCallbacks(settings);\n const removeHandler = workflow._onNodeEvent(id, (evt) => {\n dispatchNodeCallbacks(node, evt, settings);\n });\n node._setEventCleanup(removeHandler);\n\n await workflow.client.trpc.createNode\n .mutate({\n kind: \"HLS_OUTPUT\",\n workflowId: workflow.workflowId,\n nodeId: id,\n hlsOutput: {\n destinations: settings.destinations,\n segmentTime: settings.segmentTime ?? 6,\n listSize: settings.listSize ?? 5,\n audioCodec: settings.audioCodec ?? undefined,\n videoCodec: settings.videoCodec ?? undefined,\n },\n })\n .catch((err: unknown) => {\n removeHandler();\n throw err;\n });\n\n return node;\n}\n","import { uid } from \"radash\";\nimport {\n BaseNode,\n dispatchNodeCallbacks,\n type NodeCallbacks,\n type WorkflowHandle,\n} from \"../node.js\";\n\nexport interface RtmpOutputSettings extends NodeCallbacks {\n /** Full RTMP URL to push to, e.g. \"rtmp://live.twitch.tv/app/stream-key\" */\n url: string;\n /**\n * Called when the output FFmpeg process exits with an error after the node\n * has been created — e.g. the destination server refused the connection,\n * dropped mid-stream, or returned an unexpected error code.\n *\n * Errors that occur during node creation still reject the\n * `workflow.output.rtmp()` promise as usual.\n */\n onError?: (err: Error) => void;\n}\n\nexport class RtmpOutputNode extends BaseNode {\n readonly settings: RtmpOutputSettings;\n\n constructor(\n nodeId: string,\n workflow: WorkflowHandle,\n settings: RtmpOutputSettings,\n ) {\n super(nodeId, workflow.client, workflow.workflowId);\n this.settings = settings;\n }\n}\n\nexport async function createRtmpOutputNode(\n workflow: WorkflowHandle,\n settings: RtmpOutputSettings,\n nodeId?: string,\n): Promise<RtmpOutputNode> {\n const id = nodeId ?? `rtmp_output_${uid(12)}`;\n const node = new RtmpOutputNode(id, workflow, settings);\n\n node._bindCallbacks(settings);\n const removeHandler = workflow._onNodeEvent(id, (evt) => {\n dispatchNodeCallbacks(node, evt, settings);\n });\n node._setEventCleanup(removeHandler);\n\n await workflow.client.trpc.createNode\n .mutate({\n kind: \"RTMP_OUTPUT\",\n workflowId: workflow.workflowId,\n nodeId: id,\n rtmpOutput: { url: settings.url },\n })\n .catch((err: unknown) => {\n removeHandler();\n throw err;\n });\n\n return node;\n}\n","import { uid } from \"radash\";\nimport {\n BaseNode,\n dispatchNodeCallbacks,\n type NodeCallbacks,\n type WorkflowHandle,\n} from \"../node.js\";\n\nexport interface SrtOutputSettings extends NodeCallbacks {\n /**\n * Full SRT URL to push to, e.g. `\"srt://host:port\"`.\n * Transport parameters (latency, passphrase, etc.) are passed as query\n * params: `\"srt://host:port?latency=200&passphrase=secret\"`.\n */\n url: string;\n /**\n * Called when the output FFmpeg process exits with an error after the node\n * has been created — e.g. the destination refused the connection or dropped\n * mid-stream.\n *\n * Errors that occur during node creation still reject the\n * `workflow.output.srt()` promise as usual.\n */\n onError?: (err: Error) => void;\n}\n\nexport class SrtOutputNode extends BaseNode {\n readonly settings: SrtOutputSettings;\n\n constructor(\n nodeId: string,\n workflow: WorkflowHandle,\n settings: SrtOutputSettings,\n ) {\n super(nodeId, workflow.client, workflow.workflowId);\n this.settings = settings;\n }\n}\n\nexport async function createSrtOutputNode(\n workflow: WorkflowHandle,\n settings: SrtOutputSettings,\n nodeId?: string,\n): Promise<SrtOutputNode> {\n const id = nodeId ?? `srt_output_${uid(12)}`;\n const node = new SrtOutputNode(id, workflow, settings);\n\n node._bindCallbacks(settings);\n const removeHandler = workflow._onNodeEvent(id, (evt) => {\n dispatchNodeCallbacks(node, evt, settings);\n });\n node._setEventCleanup(removeHandler);\n\n await workflow.client.trpc.createNode\n .mutate({\n kind: \"SRT_OUTPUT\",\n workflowId: workflow.workflowId,\n nodeId: id,\n srtOutput: { url: settings.url },\n })\n .catch((err: unknown) => {\n removeHandler();\n throw err;\n });\n\n return node;\n}\n","import { uid } from \"radash\";\nimport {\n BaseNode,\n dispatchNodeCallbacks,\n type NodeCallbacks,\n type WorkflowHandle,\n} from \"../node.js\";\n\nexport interface BufferSettings extends NodeCallbacks {\n /** Time (ms) to hold back the input stream before forwarding it to subscribers. */\n bufferMs: number;\n}\n\n/**\n * Time-shift node. Subscribe it to exactly one source (any selector); it\n * re-emits that source delayed by `bufferMs`, shared so every downstream\n * subscriber sees the same delayed feed.\n */\nexport class BufferNode extends BaseNode {\n readonly settings: BufferSettings;\n\n constructor(\n nodeId: string,\n workflow: WorkflowHandle,\n settings: BufferSettings,\n ) {\n super(nodeId, workflow.client, workflow.workflowId);\n this.settings = settings;\n }\n}\n\n/** Create a {@link BufferNode} on the server. */\nexport async function createBufferNode(\n workflow: WorkflowHandle,\n settings: BufferSettings,\n nodeId?: string,\n): Promise<BufferNode> {\n const id = nodeId ?? `buffer_${uid(12)}`;\n const node = new BufferNode(id, workflow, settings);\n\n node._bindCallbacks(settings);\n const removeHandler = workflow._onNodeEvent(id, (evt) => {\n dispatchNodeCallbacks(node, evt, settings);\n });\n node._setEventCleanup(removeHandler);\n\n await workflow.client.trpc.createNode\n .mutate({\n kind: \"BUFFER\",\n workflowId: workflow.workflowId,\n nodeId: id,\n buffer: { bufferMs: settings.bufferMs },\n })\n .catch((err: unknown) => {\n removeHandler();\n throw err;\n });\n\n return node;\n}\n","import { uid } from \"radash\";\nimport {\n BaseNode,\n dispatchNodeCallbacks,\n type NodeCallbacks,\n type WorkflowHandle,\n} from \"../node.js\";\n\nexport interface CambAiTranslateSettings extends NodeCallbacks {\n /** Camb.ai API key. */\n apiKey: string;\n /** BCP-47 source language code for the input audio (e.g. `\"en-US\"`). */\n sourceLanguageCode: string;\n /** BCP-47 target language code for the translation output (e.g. `\"de-DE\"`). */\n targetLanguageCode: string;\n /** Realtime translation model. Defaults to Camb's server-side default. */\n model?: \"lilac\" | \"violet\" | \"iris\" | \"orchid\";\n /** Cloned voice ID to synthesize the translation with. Defaults to a built-in voice for the target language. */\n voiceId?: number;\n /** Human-readable label shown in the dashboard. */\n label?: string;\n}\n\n/** Processor node that translates audio via the Camb.ai realtime speech-to-speech API. */\nexport class CambAiTranslateNode extends BaseNode {\n readonly settings: CambAiTranslateSettings;\n\n constructor(\n nodeId: string,\n workflow: WorkflowHandle,\n settings: CambAiTranslateSettings,\n ) {\n super(nodeId, workflow.client, workflow.workflowId);\n this.settings = settings;\n }\n}\n\nexport async function createCambAiTranslateNode(\n workflow: WorkflowHandle,\n settings: CambAiTranslateSettings,\n nodeId?: string,\n): Promise<CambAiTranslateNode> {\n const id = nodeId ?? `camb_translate_${uid(12)}`;\n const node = new CambAiTranslateNode(id, workflow, settings);\n\n node._bindCallbacks(settings);\n const removeHandler = workflow._onNodeEvent(id, (evt) => {\n dispatchNodeCallbacks(node, evt, settings);\n });\n node._setEventCleanup(removeHandler);\n\n await workflow.client.trpc.createNode\n .mutate({\n kind: \"CAMB_AI_TRANSLATE\",\n workflowId: workflow.workflowId,\n nodeId: id,\n cambAiTranslate: {\n apiKey: settings.apiKey,\n sourceLanguageCode: settings.sourceLanguageCode,\n targetLanguageCode: settings.targetLanguageCode,\n model: settings.model,\n voiceId: settings.voiceId,\n label: settings.label,\n },\n })\n .catch((err: unknown) => {\n removeHandler();\n throw err;\n });\n\n return node;\n}\n","import { uid } from \"radash\";\nimport {\n BaseNode,\n dispatchNodeCallbacks,\n type NodeCallbacks,\n type WorkflowHandle,\n} from \"../node.js\";\n\nexport type CaptionMode = \"rollUp\" | \"popOn\" | \"paintOn\";\n\nexport interface Cea608EncoderSettings extends NodeCallbacks {\n /** Caption display mode. Default: \"rollUp\" */\n mode?: CaptionMode;\n /**\n * Maximum number of visible rows (2, 3, or 4). Default: 2.\n *\n * - **rollUp**: sets the scroll-window height (RU2/RU3/RU4 command).\n * - **popOn**: captions sent with `\\n`-separated lines are displayed\n * bottom-anchored; this value caps how many lines are shown at once.\n * E.g. `rows: 2` with `\"line one\\nline two\"` fills rows 14 and 15.\n */\n rows?: 2 | 3 | 4;\n /**\n * When `mode` is `\"popOn\"`, automatically word-wrap caption text to\n * CEA-608's 32-column display width before laying it out as a\n * bottom-anchored multi-line caption card (capped at `rows` lines) —\n * instead of requiring you to insert `\\n` yourself. Has no effect in\n * `\"rollUp\"`/`\"paintOn\"` modes, where each `send()` is naturally a single\n * line.\n *\n * Default: false.\n */\n autoWrap?: boolean;\n}\n\nexport class Cea608EncoderNode extends BaseNode {\n readonly settings: Cea608EncoderSettings;\n\n constructor(\n nodeId: string,\n workflow: WorkflowHandle,\n settings: Cea608EncoderSettings,\n ) {\n super(nodeId, workflow.client, workflow.workflowId);\n this.settings = settings;\n }\n}\n\nexport async function createCea608EncoderNode(\n workflow: WorkflowHandle,\n settings: Cea608EncoderSettings = {},\n nodeId?: string,\n): Promise<Cea608EncoderNode> {\n const id = nodeId ?? `cea608_${uid(12)}`;\n const node = new Cea608EncoderNode(id, workflow, settings);\n\n node._bindCallbacks(settings);\n const removeHandler = workflow._onNodeEvent(id, (evt) => {\n dispatchNodeCallbacks(node, evt, settings);\n });\n node._setEventCleanup(removeHandler);\n\n await workflow.client.trpc.createNode\n .mutate({\n kind: \"CEA608_ENCODER\",\n workflowId: workflow.workflowId,\n nodeId: id,\n cea608Encoder: {\n mode: settings.mode ?? \"rollUp\",\n rows: settings.rows ?? 2,\n autoWrap: settings.autoWrap ?? false,\n },\n })\n .catch((err: unknown) => {\n removeHandler();\n throw err;\n });\n\n return node;\n}\n","import type { MonitorEventMsg } from \"@remavideo/server\";\nimport { uid } from \"radash\";\nimport {\n BaseNode,\n dispatchNodeCallbacks,\n type NodeCallbacks,\n type WorkflowHandle,\n} from \"../node.js\";\n\nexport type { MonitorEventMsg };\n\nexport interface FfprobeMonitorSettings extends NodeCallbacks {\n /**\n * How often (ms) to re-run ffprobe on the attached stream.\n * Each cycle spawns a fresh ffprobe process, probes `probeSizeBytes` of data,\n * and publishes the result to the **stream-info** monitor.\n * Default: 30 000 ms.\n */\n intervalMs?: number;\n /**\n * Bytes of stream data to feed into each ffprobe invocation.\n * Larger values give ffprobe more context (e.g. long-GOP streams) at the\n * cost of a slightly longer first-result latency. Default: 1 048 576 (1 MB).\n */\n probeSizeBytes?: number;\n}\n\nexport class FfprobeMonitorNode extends BaseNode {\n readonly settings: FfprobeMonitorSettings;\n\n constructor(\n nodeId: string,\n workflow: WorkflowHandle,\n settings: FfprobeMonitorSettings,\n ) {\n super(nodeId, workflow.client, workflow.workflowId);\n this.settings = settings;\n }\n\n /**\n * Subscribe to a monitor stream on this node.\n *\n * - `\"stream-info\"` — emits a `status` event (key/value map) every time a\n * probe cycle completes, containing codec names, resolution, frame rate,\n * bit rates, and format.\n * - `\"ffprobe-log\"` — emits `log` events with raw ffprobe stderr output.\n *\n * Returns an object with an `unsubscribe()` method; call it to stop receiving\n * events and release the tRPC subscription.\n */\n observe(\n monitorName: \"stream-info\" | \"ffprobe-log\",\n onData: (event: MonitorEventMsg) => void,\n onError?: (err: unknown) => void,\n ): { unsubscribe(): void } {\n return this.client.trpc.observeNode.subscribe(\n { workflowId: this.workflowId, nodeId: this.nodeId, monitorName },\n { onData, ...(onError !== undefined && { onError }) },\n );\n }\n}\n\nexport async function createFfprobeMonitorNode(\n workflow: WorkflowHandle,\n settings: FfprobeMonitorSettings = {},\n nodeId?: string,\n): Promise<FfprobeMonitorNode> {\n const id = nodeId ?? `ffprobe_${uid(12)}`;\n const node = new FfprobeMonitorNode(id, workflow, settings);\n\n node._bindCallbacks(settings);\n const removeHandler = workflow._onNodeEvent(id, (evt) => {\n dispatchNodeCallbacks(node, evt, settings);\n });\n node._setEventCleanup(removeHandler);\n\n await workflow.client.trpc.createNode\n .mutate({\n kind: \"FFPROBE_MONITOR\",\n workflowId: workflow.workflowId,\n nodeId: id,\n ffprobeMonitor: {\n intervalMs: settings.intervalMs ?? 30_000,\n probeSizeBytes: settings.probeSizeBytes ?? 1_048_576,\n },\n })\n .catch((err: unknown) => {\n removeHandler();\n throw err;\n });\n\n return node;\n}\n","import { uid } from \"radash\";\nimport {\n BaseNode,\n dispatchNodeCallbacks,\n type NodeCallbacks,\n type WorkflowHandle,\n} from \"../node.js\";\n\nexport interface GateSettings extends NodeCallbacks {\n /**\n * Number of distinct sources that must have emitted at least one item\n * before the gate opens. Default: wait for every subscribed source.\n */\n minReady?: number;\n /**\n * Force the gate open after this many ms even if `minReady` hasn't been\n * reached, using whatever sources are ready by then. Omit to wait\n * indefinitely — a source that never produces data would otherwise stall\n * the gate forever.\n */\n timeoutMs?: number;\n}\n\n/**\n * Synchronization barrier for fan-in from multiple, independently-timed\n * sources (e.g. two different transcription/translation services). Not to\n * be confused with the `gate` option on `.subscribe()` (`ActivationGate`),\n * which defers wiring based on upstream node *lifecycle*, not data.\n *\n * `GateNode` subscribes to every source immediately and queues everything\n * each one emits, then starts forwarding — for every source at once — only\n * once enough of them have produced their first item. It is a one-time\n * rendezvous, not a per-item join.\n *\n * Every source becomes its own keyed pass-through port on the gate — streams\n * are never merged. Name a port with `name` on the gate's subscription spec\n * (default: the source's index as a string) and read it downstream with\n * `port`:\n *\n * ```ts\n * await gate.subscribe([\n * { source: a, sourceSelector: \"ALL\", name: \"main\" },\n * { source: b, sourceSelector: \"ALL\", name: \"backup\" },\n * ]);\n * await out1.subscribe([{ source: gate, sourceSelector: \"ALL\", port: \"main\" }]);\n * await out2.subscribe([{ source: gate, sourceSelector: \"ALL\", port: \"backup\" }]);\n * ```\n *\n * With exactly one source of a kind, plain selector subscriptions (no\n * `port`) still resolve to it; with several same-kind sources the server\n * errors and asks for an explicit `port`.\n */\nexport class GateNode extends BaseNode {\n readonly settings: GateSettings;\n\n constructor(\n nodeId: string,\n workflow: WorkflowHandle,\n settings: GateSettings,\n ) {\n super(nodeId, workflow.client, workflow.workflowId);\n this.settings = settings;\n }\n}\n\n/** Create a {@link GateNode} on the server. */\nexport async function createGateNode(\n workflow: WorkflowHandle,\n settings: GateSettings = {},\n nodeId?: string,\n): Promise<GateNode> {\n const id = nodeId ?? `gate_${uid(12)}`;\n const node = new GateNode(id, workflow, settings);\n\n node._bindCallbacks(settings);\n const removeHandler = workflow._onNodeEvent(id, (evt) => {\n dispatchNodeCallbacks(node, evt, settings);\n });\n node._setEventCleanup(removeHandler);\n\n await workflow.client.trpc.createNode\n .mutate({\n kind: \"GATE\",\n workflowId: workflow.workflowId,\n nodeId: id,\n gate: {\n ...(settings.minReady !== undefined && {\n minReady: settings.minReady,\n }),\n ...(settings.timeoutMs !== undefined && {\n timeoutMs: settings.timeoutMs,\n }),\n },\n })\n .catch((err: unknown) => {\n removeHandler();\n throw err;\n });\n\n return node;\n}\n","import { uid } from \"radash\";\nimport {\n BaseNode,\n dispatchNodeCallbacks,\n type NodeCallbacks,\n type WorkflowHandle,\n} from \"../node.js\";\n\nexport interface GeminiTranslateSettings extends NodeCallbacks {\n /** Gemini API key. */\n apiKey: string;\n /**\n * BCP-47 target language code for the translation output\n * (e.g. `\"it\"`, `\"es\"`, `\"fr\"`).\n */\n targetLanguageCode: string;\n /** Human-readable label shown in the dashboard. */\n label?: string;\n}\n\n/** Processor node that translates audio via the Gemini Live Translate API. */\nexport class GeminiTranslateNode extends BaseNode {\n readonly settings: GeminiTranslateSettings;\n\n constructor(\n nodeId: string,\n workflow: WorkflowHandle,\n settings: GeminiTranslateSettings,\n ) {\n super(nodeId, workflow.client, workflow.workflowId);\n this.settings = settings;\n }\n}\n\nexport async function createGeminiTranslateNode(\n workflow: WorkflowHandle,\n settings: GeminiTranslateSettings,\n nodeId?: string,\n): Promise<GeminiTranslateNode> {\n const id = nodeId ?? `gemini_translate_${uid(12)}`;\n const node = new GeminiTranslateNode(id, workflow, settings);\n\n node._bindCallbacks(settings);\n const removeHandler = workflow._onNodeEvent(id, (evt) => {\n dispatchNodeCallbacks(node, evt, settings);\n });\n node._setEventCleanup(removeHandler);\n\n await workflow.client.trpc.createNode\n .mutate({\n kind: \"GEMINI_TRANSLATE\",\n workflowId: workflow.workflowId,\n nodeId: id,\n geminiTranslate: {\n apiKey: settings.apiKey,\n targetLanguageCode: settings.targetLanguageCode,\n label: settings.label,\n },\n })\n .catch((err: unknown) => {\n removeHandler();\n throw err;\n });\n\n return node;\n}\n","import type { AppRouter, HealthStatus } from \"@remavideo/server\";\nimport {\n createTRPCClient,\n httpLink,\n httpSubscriptionLink,\n splitLink,\n} from \"@trpc/client\";\nimport { EventSource } from \"eventsource\";\nimport superjson from \"superjson\";\n\nexport interface RemaClientOptions {\n host?: string;\n port?: number;\n onLogEvent?: (log: { level: string; message: string }) => void;\n onReady?: () => void;\n onFailedToConnect?: () => void;\n}\n\nexport class RemaClient {\n readonly trpc: ReturnType<typeof createTRPCClient<AppRouter>>;\n readonly options: Required<Pick<RemaClientOptions, \"host\" | \"port\">>;\n\n private constructor(\n trpc: ReturnType<typeof createTRPCClient<AppRouter>>,\n options: RemaClientOptions,\n ) {\n this.trpc = trpc;\n this.options = {\n host: options.host ?? \"127.0.0.1\",\n port: options.port ?? 8080,\n };\n }\n\n static async connect(opts: RemaClientOptions = {}): Promise<RemaClient> {\n const host = opts.host ?? \"127.0.0.1\";\n const port = opts.port ?? 8080;\n const url = `http://${host}:${port}/trpc`;\n\n const trpc = createTRPCClient<AppRouter>({\n links: [\n splitLink({\n condition: (op) => op.type === \"subscription\",\n true: httpSubscriptionLink({\n url,\n transformer: superjson,\n EventSource,\n }),\n false: httpLink({ url, transformer: superjson }),\n }),\n ],\n });\n\n try {\n await trpc.ping.query();\n opts.onReady?.();\n } catch (err) {\n opts.onFailedToConnect?.();\n throw new Error(\n `Failed to connect to rema server at ${host}:${port}: ${String(err)}`,\n );\n }\n\n return new RemaClient(trpc, opts);\n }\n\n /** No-op kept for API compatibility — SSE has no persistent connection to close. */\n close(): void {}\n\n /** Fetches the server's health status from the plain HTTP `/api/health` endpoint. */\n async health(): Promise<HealthStatus> {\n const url = `http://${this.options.host}:${this.options.port}/api/health`;\n const res = await fetch(url);\n if (!res.ok) {\n throw new Error(`Health check failed: ${res.status} ${res.statusText}`);\n }\n return (await res.json()) as HealthStatus;\n }\n}\n","import type { NodeEventMsg, WorkflowInfo } from \"@remavideo/server\";\nimport { TypedEmitter } from \"tiny-typed-emitter\";\nimport type { RemaClient } from \"./client.js\";\nimport {\n type CaptionSourceNode,\n type CaptionSourceSettings,\n createCaptionSourceNode,\n} from \"./input/caption-source.js\";\nimport {\n createFileInputNode,\n type FileInputNode,\n type FileInputSettings,\n} from \"./input/file.js\";\nimport {\n createNodeStreamInputNode,\n type NodeStreamInputNode,\n type NodeStreamInputSettings,\n} from \"./input/node-stream.js\";\nimport {\n createRtmpAnnouncerNode,\n type RtmpAnnouncerNode,\n type RtmpAnnouncerSettings,\n} from \"./input/rtmp-announcer.js\";\nimport {\n createRtmpMultiplexerNode,\n type RtmpMultiplexerNode,\n type RtmpMultiplexerSettings,\n} from \"./input/rtmp-multiplexer.js\";\nimport {\n createRtmpReaderNode,\n type RtmpReaderNode,\n type RtmpReaderSettings,\n} from \"./input/rtmp-reader.js\";\nimport {\n createSrtAnnouncerNode,\n type SrtAnnouncerNode,\n type SrtAnnouncerSettings,\n} from \"./input/srt-announcer.js\";\nimport {\n createSrtReaderNode,\n type SrtReaderNode,\n type SrtReaderSettings,\n} from \"./input/srt-reader.js\";\nimport {\n createFileOutputNode,\n type FileOutputNode,\n type FileOutputSettings,\n} from \"./output/file.js\";\nimport {\n createHlsOutputNode,\n type HlsOutputNode,\n type HlsOutputSettings,\n} from \"./output/hls.js\";\nimport {\n createRtmpOutputNode,\n type RtmpOutputNode,\n type RtmpOutputSettings,\n} from \"./output/rtmp.js\";\nimport {\n createSrtOutputNode,\n type SrtOutputNode,\n type SrtOutputSettings,\n} from \"./output/srt.js\";\nimport {\n type AudioMixerNode,\n type AudioMixerSettings,\n createAudioMixerNode,\n} from \"./processor/audio-mixer.js\";\nimport {\n type BufferNode,\n type BufferSettings,\n createBufferNode,\n} from \"./processor/buffer.js\";\nimport {\n type CambAiTranslateNode,\n type CambAiTranslateSettings,\n createCambAiTranslateNode,\n} from \"./processor/camb-ai-translate.js\";\nimport {\n type Cea608EncoderNode,\n type Cea608EncoderSettings,\n createCea608EncoderNode,\n} from \"./processor/cea608-encoder.js\";\nimport {\n createFfprobeMonitorNode,\n type FfprobeMonitorNode,\n type FfprobeMonitorSettings,\n} from \"./processor/ffprobe-monitor.js\";\nimport {\n createGateNode,\n type GateNode,\n type GateSettings,\n} from \"./processor/gate.js\";\nimport {\n createGeminiTranslateNode,\n type GeminiTranslateNode,\n type GeminiTranslateSettings,\n} from \"./processor/gemini-translate.js\";\n\nexport type { WorkflowInfo };\n\n/** Normalize a `catch`-clause/subscription value (typed `unknown`) into an `Error`. */\nfunction toError(err: unknown): Error {\n return err instanceof Error ? err : new Error(String(err));\n}\n\n/** Lifecycle callbacks available on a workflow. */\nexport interface WorkflowCallbacks {\n /**\n * Called if this workflow's worker process crashes. Workflow errors\n * otherwise only appear in server logs — this is the client-side signal.\n */\n onError?: (err: Error) => void;\n /** Called when the workflow closes, whether via `.close()` or a crash. */\n onClose?: () => void;\n}\n\n/** Events emitted by a workflow, mirroring {@link WorkflowCallbacks} for `.on()`-style consumers. */\nexport interface WorkflowEvents {\n error: (err: Error) => void;\n close: () => void;\n}\n\n/**\n * A workflow is an isolated sub-registry on the server.\n * All nodes created through a workflow handle are scoped to that workflow;\n * they cannot subscribe to nodes in other workflows.\n *\n * Obtain a workflow via `rema.workflow.create()`.\n */\nexport class Workflow extends TypedEmitter<WorkflowEvents> {\n readonly workflowId: string;\n /** Non-private: accessible to factory functions via WorkflowHandle. */\n readonly client: RemaClient;\n\n private readonly _callbacks: WorkflowCallbacks;\n private readonly _eventHandlers = new Map<\n string,\n Set<(evt: NodeEventMsg) => void>\n >();\n private _eventSub: { unsubscribe(): void } | null = null;\n private _stateSub: { unsubscribe(): void } | null = null;\n private _seenInList = false;\n private _erroredFired = false;\n private _closedFired = false;\n\n /** @internal — use `rema.workflow.create()` instead */\n constructor(\n workflowId: string,\n client: RemaClient,\n callbacks: WorkflowCallbacks = {},\n ) {\n super();\n this.workflowId = workflowId;\n this.client = client;\n this._callbacks = callbacks;\n this._startEventSubscription();\n this._startStateSubscription();\n }\n\n /**\n * Register a handler for events emitted by a specific node in this workflow.\n * Returns an unsubscribe function — call it when the node is closed.\n * @internal used by node factory functions\n */\n _onNodeEvent(\n nodeId: string,\n handler: (evt: NodeEventMsg) => void,\n ): () => void {\n let handlers = this._eventHandlers.get(nodeId);\n if (handlers === undefined) {\n handlers = new Set();\n this._eventHandlers.set(nodeId, handlers);\n }\n handlers.add(handler);\n return () => {\n this._eventHandlers.get(nodeId)?.delete(handler);\n };\n }\n\n private _startEventSubscription(): void {\n this._eventSub = this.client.trpc.workflow.events.subscribe(\n { workflowId: this.workflowId },\n {\n onData: (evt) => {\n this._eventHandlers.get(evt.nodeId)?.forEach((h) => {\n h(evt);\n });\n },\n onError: (err) => this._reportError(toError(err)),\n },\n );\n }\n\n /**\n * Watches the server's list of all workflows for this workflow's entry, so\n * `onError`/`onClose` can be derived without a dedicated per-workflow\n * subscription: a transition to `status: \"crashed\"` fires `onError`, and\n * the entry disappearing (graceful close) fires `onClose`.\n */\n private _startStateSubscription(): void {\n this._stateSub = this.client.trpc.workflow.state.subscribe(undefined, {\n onData: (workflows) => {\n const entry = workflows.find((w) => w.id === this.workflowId);\n if (entry !== undefined) {\n this._seenInList = true;\n if (entry.status === \"crashed\" && !this._erroredFired) {\n this._erroredFired = true;\n this._reportError(new Error(\"workflow worker crashed\"));\n }\n return;\n }\n if (this._seenInList) this._fireClose();\n },\n onError: (err) => this._reportError(toError(err)),\n });\n }\n\n /**\n * Report an error to this workflow's `onError` callback and its\n * `WorkflowEvents` emitter. Like `BaseNode`, `Workflow` extends Node's\n * `EventEmitter`, which throws synchronously from `emit()` when an\n * `\"error\"` event has no listener — so this only emits when someone has\n * opted in via `.on(\"error\", ...)`; the `onError` callback covers the\n * common case. The SDK does no logging of its own.\n */\n private _reportError(err: Error): void {\n this._callbacks.onError?.(err);\n if (this.listenerCount(\"error\") > 0) this.emit(\"error\", err);\n }\n\n private _fireClose(): void {\n if (this._closedFired) return;\n this._closedFired = true;\n this._eventSub?.unsubscribe();\n this._stateSub?.unsubscribe();\n this._callbacks.onClose?.();\n this.emit(\"close\");\n }\n\n // ─── Input nodes ─────────────────────────────────────────────────────────\n\n readonly input = {\n file: (\n settings: FileInputSettings,\n nodeId?: string,\n ): Promise<FileInputNode> => createFileInputNode(this, settings, nodeId),\n\n rtmpReader: (\n settings: RtmpReaderSettings,\n nodeId?: string,\n ): Promise<RtmpReaderNode> => createRtmpReaderNode(this, settings, nodeId),\n\n /** Announces RTMP publisher connections on an `app` without creating nodes. */\n rtmpAnnouncer: (\n settings: RtmpAnnouncerSettings,\n nodeId?: string,\n ): Promise<RtmpAnnouncerNode> =>\n createRtmpAnnouncerNode(this, settings, nodeId),\n\n /** Announces SRT publisher connections on an `app` without creating nodes. */\n srtAnnouncer: (\n settings: SrtAnnouncerSettings,\n nodeId?: string,\n ): Promise<SrtAnnouncerNode> =>\n createSrtAnnouncerNode(this, settings, nodeId),\n\n /** Reads an SRT stream ingested by MediaMTX, pulling it back via RTSP internally. */\n srtReader: (\n settings: SrtReaderSettings,\n nodeId?: string,\n ): Promise<SrtReaderNode> => createSrtReaderNode(this, settings, nodeId),\n\n /**\n * An RTMP multiplexer: accepts any number of simultaneous publishers under\n * the given `app`. For each connection `onStream` is called with a fresh\n * `RtmpStreamNode` that can be wired into its own encoder/output pipeline.\n */\n rtmpMultiplexer: (\n settings: RtmpMultiplexerSettings,\n nodeId?: string,\n ): Promise<RtmpMultiplexerNode> =>\n createRtmpMultiplexerNode(this, settings, nodeId),\n\n /** Format-agnostic caption schedule node. Send captions to it; wire it to a cea608() encoder. */\n captions: (\n settings: CaptionSourceSettings = {},\n nodeId?: string,\n ): Promise<CaptionSourceNode> =>\n createCaptionSourceNode(this, settings, nodeId),\n\n /**\n * Audio stream input node. Push a Node.js `Readable` (or any\n * `AsyncIterable<Buffer>`) directly into the pipeline by calling\n * `.stream(source)` on the returned node.\n */\n streamInput: (\n settings: NodeStreamInputSettings,\n nodeId?: string,\n ): Promise<NodeStreamInputNode> =>\n createNodeStreamInputNode(this, settings, nodeId),\n };\n\n // ─── Output nodes ─────────────────────────────────────────────────────────\n\n readonly output = {\n rtmp: (\n settings: RtmpOutputSettings,\n nodeId?: string,\n ): Promise<RtmpOutputNode> => createRtmpOutputNode(this, settings, nodeId),\n\n file: (\n settings: FileOutputSettings,\n nodeId?: string,\n ): Promise<FileOutputNode> => createFileOutputNode(this, settings, nodeId),\n\n /** HLS output node. Subscribe it to one or more media/subtitle sources. */\n hls: (\n settings: HlsOutputSettings,\n nodeId?: string,\n ): Promise<HlsOutputNode> => createHlsOutputNode(this, settings, nodeId),\n\n /** SRT output node. Pushes MPEG-TS over SRT to the given URL. */\n srt: (\n settings: SrtOutputSettings,\n nodeId?: string,\n ): Promise<SrtOutputNode> => createSrtOutputNode(this, settings, nodeId),\n };\n\n // ─── Processor nodes ──────────────────────────────────────────────────────\n\n readonly processor = {\n /** CEA-608/708 encoder node. Subscribe it to video + a captions() node. */\n cea608: (\n settings: Cea608EncoderSettings = {},\n nodeId?: string,\n ): Promise<Cea608EncoderNode> =>\n createCea608EncoderNode(this, settings, nodeId),\n\n /**\n * ffprobe monitor node.\n * Subscribe it to any media source; it periodically probes the live stream\n * and exposes codec/resolution/bitrate info via the `stream-info` monitor.\n */\n ffprobeMonitor: (\n settings: FfprobeMonitorSettings = {},\n nodeId?: string,\n ): Promise<FfprobeMonitorNode> =>\n createFfprobeMonitorNode(this, settings, nodeId),\n\n /**\n * Gemini Live Translate node.\n * Subscribe it to an audio/media source; it outputs translated audio-only\n * MPEG-TS using the Gemini Live Translate API.\n */\n geminiTranslate: (\n settings: GeminiTranslateSettings,\n nodeId?: string,\n ): Promise<GeminiTranslateNode> =>\n createGeminiTranslateNode(this, settings, nodeId),\n\n /**\n * Camb.ai realtime speech-to-speech translation node.\n * Subscribe it to an audio/media source; it outputs translated audio-only\n * MPEG-TS using the Camb.ai realtime translation API.\n */\n cambAiTranslate: (\n settings: CambAiTranslateSettings,\n nodeId?: string,\n ): Promise<CambAiTranslateNode> =>\n createCambAiTranslateNode(this, settings, nodeId),\n\n /**\n * Time-shift node. Subscribe it to exactly one source; it re-emits that\n * source delayed by `bufferMs`, shared across every downstream\n * subscriber.\n */\n buffer: (settings: BufferSettings, nodeId?: string): Promise<BufferNode> =>\n createBufferNode(this, settings, nodeId),\n\n /**\n * Synchronization barrier for fan-in from multiple, independently-timed\n * sources. Subscribe it to two or more sources; it withholds all of them\n * until enough (`minReady`, default: all) have produced their first\n * item, then forwards everything live.\n */\n gate: (settings: GateSettings = {}, nodeId?: string): Promise<GateNode> =>\n createGateNode(this, settings, nodeId),\n\n /**\n * Audio mixer node. Subscribe it to exactly `settings.audio.length`\n * AUDIO/ALL sources, in order — the Nth `audio[]` entry configures the\n * Nth subscribed source. Outputs a single mixed audio feed.\n */\n audioMixer: (\n settings: AudioMixerSettings,\n nodeId?: string,\n ): Promise<AudioMixerNode> => createAudioMixerNode(this, settings, nodeId),\n };\n\n // ─── Lifecycle ────────────────────────────────────────────────────────────\n\n /**\n * Close this workflow and deactivate all its nodes on the server.\n * After calling this the workflow handle is no longer usable.\n *\n * The event/state subscriptions are kept open until the close mutation\n * resolves so that the server's per-node `CLOSED` events (dispatched to\n * each node's `onClose`/`onStatusChange` callbacks) are still received,\n * rather than tearing the subscriptions down before those events arrive.\n */\n async close(): Promise<void> {\n await this.client.trpc.workflow.close.mutate({\n workflowId: this.workflowId,\n });\n this._fireClose();\n }\n}\n","import { uid } from \"radash\";\nimport {\n BaseNode,\n dispatchNodeCallbacks,\n type NodeCallbacks,\n type WorkflowHandle,\n} from \"../node.js\";\n\n/** Per-source settings for one audio input to an `AudioMixerNode`. */\nexport interface AudioMixerInputSettings {\n /** Linear gain multiplier (1 = unity, 0.5 = -6dB). Default: 1. */\n volume?: number;\n /** Silences this input entirely, regardless of `volume`. Default: false. */\n muted?: boolean;\n /** Stereo balance: -1 (full left) .. 1 (full right), 0 = center. Default: 0. */\n pan?: number;\n /** High-pass filter cutoff (Hz), applied before mixing. Omit for no high-pass. */\n highpassHz?: number;\n /** Low-pass filter cutoff (Hz), applied before mixing. Omit for no low-pass. */\n lowpassHz?: number;\n}\n\nexport interface AudioMixerSettings extends NodeCallbacks {\n /**\n * One entry per audio source this node will be subscribed to, in the same\n * order — the Nth entry configures the Nth subscribed AUDIO/ALL source.\n * Subscribe with exactly `audio.length` sources.\n */\n audio: AudioMixerInputSettings[];\n}\n\nexport class AudioMixerNode extends BaseNode {\n readonly settings: AudioMixerSettings;\n\n constructor(\n nodeId: string,\n workflow: WorkflowHandle,\n settings: AudioMixerSettings,\n ) {\n super(nodeId, workflow.client, workflow.workflowId);\n this.settings = settings;\n }\n}\n\nexport async function createAudioMixerNode(\n workflow: WorkflowHandle,\n settings: AudioMixerSettings,\n nodeId?: string,\n): Promise<AudioMixerNode> {\n const id = nodeId ?? `audio_mixer_${uid(12)}`;\n const node = new AudioMixerNode(id, workflow, settings);\n\n node._bindCallbacks(settings);\n const removeHandler = workflow._onNodeEvent(id, (evt) => {\n dispatchNodeCallbacks(node, evt, settings);\n });\n node._setEventCleanup(removeHandler);\n\n await workflow.client.trpc.createNode\n .mutate({\n kind: \"AUDIO_MIXER\",\n workflowId: workflow.workflowId,\n nodeId: id,\n audioMixer: {\n audio: settings.audio.map((a) => ({\n volume: a.volume ?? 1,\n muted: a.muted ?? false,\n pan: a.pan ?? 0,\n ...(a.highpassHz !== undefined ? { highpassHz: a.highpassHz } : {}),\n ...(a.lowpassHz !== undefined ? { lowpassHz: a.lowpassHz } : {}),\n })),\n },\n })\n .catch((err: unknown) => {\n removeHandler();\n throw err;\n });\n\n return node;\n}\n","import type { HealthStatus, WorkflowInfo } from \"@remavideo/server\";\nimport { RemaClient, type RemaClientOptions } from \"./client.js\";\nimport { Workflow, type WorkflowCallbacks } from \"./workflow.js\";\n\nexport class Rema {\n private readonly client: RemaClient;\n private readonly _autoCloseWorkflows = new Set<Workflow>();\n\n private constructor(client: RemaClient) {\n this.client = client;\n }\n\n static async connect(opts: RemaClientOptions = {}): Promise<Rema> {\n const client = await RemaClient.connect(opts);\n return new Rema(client);\n }\n\n // ─── Workflow management ──────────────────────────────────────────────────\n\n readonly workflow = {\n /**\n * Create a new isolated workflow on the server.\n *\n * All nodes created through the returned `Workflow` handle are scoped to\n * this workflow; they cannot subscribe to nodes in other workflows.\n *\n * @param opts.name Human-readable label shown in the web UI.\n * @param opts.workflowId Optional stable ID. Server generates one if omitted.\n * @param opts.closeOnDisconnect When true, this workflow is closed automatically\n * when `rema.close()`/`wf.close()` is called, or\n * when the connection to the server is lost (e.g.\n * the process crashes) for more than a few seconds.\n * @param opts.parentId ID of an existing workflow to attach this one to as\n * a child (shown nested in the web UI).\n * @param opts.closeWithParent When true, this workflow is closed automatically\n * when its parent workflow is closed.\n * @param opts.onError Called if this workflow's worker process crashes.\n * @param opts.onClose Called when the workflow closes, whether via\n * `.close()` or a crash.\n */\n create: async (\n opts: {\n name?: string;\n workflowId?: string;\n closeOnDisconnect?: boolean;\n parentId?: string;\n closeWithParent?: boolean;\n } & WorkflowCallbacks = {},\n ): Promise<Workflow> => {\n const info = await this.client.trpc.workflow.create.mutate({\n workflowId: opts.workflowId,\n name: opts.name,\n parentId: opts.parentId,\n closeWithParent: opts.closeWithParent,\n closeOnDisconnect: opts.closeOnDisconnect,\n });\n const wf = new Workflow(info.id, this.client, {\n ...(opts.onError !== undefined && { onError: opts.onError }),\n ...(opts.onClose !== undefined && { onClose: opts.onClose }),\n });\n if (opts.closeOnDisconnect === true) {\n this._autoCloseWorkflows.add(wf);\n }\n return wf;\n },\n\n /** List all active workflows on the server. */\n list: (): Promise<WorkflowInfo[]> => this.client.trpc.workflow.list.query(),\n };\n\n /** Fetches the server's current health status (uptime, workflow counts, MediaMTX reachability). */\n health(): Promise<HealthStatus> {\n return this.client.health();\n }\n\n // ─── Lifecycle ────────────────────────────────────────────────────────────\n\n /**\n * Close all `closeOnDisconnect` workflows, then disconnect from the server.\n */\n async close(): Promise<void> {\n await Promise.allSettled(\n [...this._autoCloseWorkflows].map((wf) => wf.close()),\n );\n this.client.close();\n }\n}\n","/**\n * Stream selectors — used when calling node.subscribe() to pick which\n * streams flow from source to subscriber.\n */\n\nexport type StreamSelector = \"VIDEO\" | \"AUDIO\" | \"CAPTIONS\" | \"ALL\";\n\n// Narrowly typed (`as const`) so a spec literal like\n// `{ sourceSelector: selectAudio, audio: {...} }` discriminates the\n// SubscriptionSpec union correctly.\nexport const selectVideo = \"VIDEO\" as const;\nexport const selectAudio = \"AUDIO\" as const;\nexport const selectCaptions = \"CAPTIONS\" as const;\nexport const selectAll = \"ALL\" as const;\n"],"mappings":"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","names":["uid","uid","uid","uid","uid","uid","uid","uid","uid","uid","uid","uid","uid","uid","uid","uid","uid","uid","uid","uid","uid","uid","uid","uid","uid","uid","uid","uid","uid","uid","uid","uid","TypedEmitter","uid","uid","toError","TypedEmitter"]}
1
+ {"version":3,"sources":["../src/input/caption-source.ts","../src/node.ts","../src/input/file.ts","../src/input/node-stream.ts","../src/input/rtmp-announcer.ts","../src/input/rtmp-multiplexer.ts","../src/input/rtmp-reader.ts","../src/input/srt-announcer.ts","../src/input/srt-reader.ts","../src/output/file.ts","../src/output/hls.ts","../src/output/rtmp.ts","../src/output/srt.ts","../src/processor/buffer.ts","../src/processor/camb-ai-translate.ts","../src/processor/cea608-encoder.ts","../src/processor/ffprobe-monitor.ts","../src/processor/gate.ts","../src/processor/gemini-translate.ts","../src/client.ts","../src/workflow.ts","../src/processor/audio-mixer.ts","../src/rema.ts","../src/selectors.ts"],"sourcesContent":["import { readFile } from \"node:fs/promises\";\nimport {\n BaseNode,\n dispatchNodeCallbacks,\n type NodeCallbacks,\n type WorkflowHandle,\n} from \"../node.js\";\n\nexport interface CaptionSourceSettings extends NodeCallbacks {\n /**\n * How many past caption entries to replay to an encoder that subscribes\n * after some captions have already been sent.\n *\n * - `0` (default) — no replay; each encoder only receives captions sent\n * after it connects. Correct for live captioning where old text is stale.\n * - `Infinity` — replay the full history. Use this when pre-scheduling\n * all captions upfront (e.g. from an SRT file) before the pipeline starts,\n * so encoders that activate later still see every entry.\n * - `N` — replay the last N entries only.\n */\n replayCount?: number;\n /**\n * BCP-47 language tag written into the HLS master playlist when this caption\n * source is wired as a subtitle track. Default: \"und\".\n */\n language?: string;\n /** Human-readable track label shown in player subtitle menus. Default: \"Subtitles\". */\n label?: string;\n /** Whether this track is pre-selected when the player first loads. Default: true. */\n defaultTrack?: boolean;\n /**\n * Maximum character length per caption line. When set, captions whose text\n * exceeds this length are automatically split into sequential chunks on the\n * server. Each chunk's display duration is proportional to its character\n * count relative to the original caption's total duration.\n */\n maxLineLength?: number;\n}\n\nexport class CaptionSourceNode extends BaseNode {\n readonly settings: CaptionSourceSettings;\n\n constructor(\n nodeId: string,\n workflow: WorkflowHandle,\n settings: CaptionSourceSettings,\n ) {\n super(nodeId, workflow.client, workflow.workflowId);\n this.settings = settings;\n }\n\n /**\n * Queue caption text for injection into the video stream.\n *\n * @param options.startAt Stream-elapsed milliseconds (relative to the\n * first video frame received by the server) at which\n * to show the caption. The server holds the text and\n * injects it at the matching PTS, so you can schedule\n * captions before that point in the stream is reached.\n * Omit (or 0) to inject immediately at the next frame.\n * @param options.duration How long (ms) the caption stays visible before\n * being automatically erased. Required.\n */\n async send(\n text: string,\n options: { duration: number; startAt?: number },\n ): Promise<void> {\n await this.client.trpc.sendCaption.mutate({\n workflowId: this.workflowId,\n nodeId: this.nodeId,\n text,\n durationMs: options.duration,\n startAtMs: options.startAt ?? 0,\n });\n }\n\n /**\n * Parse raw SRT or WebVTT content and schedule all entries on the server.\n *\n * Use `replayCount: Infinity` when creating this node if the pipeline may\n * connect encoders after the captions are loaded.\n *\n * @returns The number of caption entries loaded.\n */\n async sendFile(content: string): Promise<{ count: number }> {\n return this.client.trpc.loadCaptionFile.mutate({\n workflowId: this.workflowId,\n nodeId: this.nodeId,\n content,\n });\n }\n\n /**\n * Read a local SRT or WebVTT file and schedule all its entries on the server.\n * The file is read client-side; only the content string is sent over the wire.\n *\n * @param filePath Absolute or relative path to the subtitle file.\n * @returns The number of caption entries loaded.\n */\n async sendFileByPath(filePath: string): Promise<{ count: number }> {\n const content = await readFile(filePath, \"utf-8\");\n return this.sendFile(content);\n }\n}\n\nexport async function createCaptionSourceNode(\n workflow: WorkflowHandle,\n settings: CaptionSourceSettings = {},\n nodeId?: string,\n): Promise<CaptionSourceNode> {\n const id = nodeId ?? `caption_source_${Date.now()}`;\n const node = new CaptionSourceNode(id, workflow, settings);\n\n node._bindCallbacks(settings);\n const removeHandler = workflow._onNodeEvent(id, (evt) => {\n dispatchNodeCallbacks(node, evt, settings);\n });\n node._setEventCleanup(removeHandler);\n\n await workflow.client.trpc.createNode\n .mutate({\n kind: \"CAPTION_SOURCE\",\n workflowId: workflow.workflowId,\n nodeId: id,\n captionSource: {\n replayCount: settings.replayCount ?? 0,\n language: settings.language ?? \"und\",\n label: settings.label ?? \"Subtitles\",\n defaultTrack: settings.defaultTrack ?? true,\n ...(settings.maxLineLength !== undefined && {\n maxLineLength: settings.maxLineLength,\n }),\n },\n })\n .catch((err: unknown) => {\n removeHandler();\n throw err;\n });\n\n return node;\n}\n","import type {\n ActivationGate,\n NodeEventMsg,\n NodeState,\n TrackInfo,\n} from \"@remavideo/server\";\nimport { TypedEmitter } from \"tiny-typed-emitter\";\nimport type { RemaClient } from \"./client.js\";\n\n/** Lifecycle callbacks available on every node's settings object. */\nexport interface NodeCallbacks {\n /** Called when the node transitions into the `ERROR` state. */\n onError?: (err: Error) => void;\n /** Called on every lifecycle state transition (`READY`, `ACTIVE`, `ENDED`, `ERROR`, `CLOSED`). */\n onStatusChange?: (state: NodeState, message?: string) => void;\n /** Called when the node closes — locally via `.close()`, or remotely (workflow close, crash). */\n onClose?: () => void;\n}\n\n/** Events emitted by every node, mirroring {@link NodeCallbacks} for `.on()`-style consumers. */\nexport interface NodeEvents {\n error: (err: Error) => void;\n statusChange: (state: NodeState, message?: string) => void;\n close: () => void;\n}\n\nconst NODE_STATES = new Set<NodeState>([\n \"READY\",\n \"ACTIVE\",\n \"ENDED\",\n \"ERROR\",\n \"CLOSED\",\n]);\n\n/**\n * Report an error to a node's `onError` callback and its `NodeEvents`\n * emitter. The SDK does no logging of its own — the caller decides whether\n * and how an unhandled error surfaces, via `onError` or `.on(\"error\", ...)`.\n *\n * `BaseNode` extends Node's `EventEmitter` (via `tiny-typed-emitter`), which\n * throws synchronously from `emit()` when an `\"error\"` event has no\n * listener. Since callers typically only set the `onError` callback (not\n * `.on(\"error\", ...)`), emitting unconditionally would throw back into\n * whatever triggered this call — e.g. the `workflow.events` subscription's\n * `onData` handler, tearing down event delivery for the entire workflow.\n * Only emit when someone has opted in.\n */\nexport function reportNodeError(\n node: BaseNode,\n callbacks: NodeCallbacks,\n err: Error,\n): void {\n callbacks.onError?.(err);\n if (node.listenerCount(\"error\") > 0) node.emit(\"error\", err);\n}\n\n/**\n * Dispatch a raw node event to the generic lifecycle callbacks on a node's\n * settings object, and to the node's `NodeEvents` emitter. Call this from a\n * node factory's event handler alongside any node-type-specific event handling.\n */\nexport function dispatchNodeCallbacks(\n node: BaseNode,\n evt: NodeEventMsg,\n callbacks: NodeCallbacks,\n): void {\n if (NODE_STATES.has(evt.kind as NodeState)) {\n const state = evt.kind as NodeState;\n callbacks.onStatusChange?.(state, evt.message);\n node.emit(\"statusChange\", state, evt.message);\n }\n if (evt.kind === \"ERROR\") {\n reportNodeError(node, callbacks, new Error(evt.message ?? \"node error\"));\n }\n if (evt.kind === \"CLOSED\") {\n callbacks.onClose?.();\n node.emit(\"close\");\n }\n}\n\n/**\n * FFmpeg codec specification for one stream of a subscription.\n *\n * The receiving output node applies it where appropriate (e.g. per-rendition\n * `-c:v:N`/`-c:a:N` for HLS, `-c:v`/`-c:a` for RTMP/File outputs). Omitted =\n * stream copy.\n */\nexport interface CodecSpec {\n /** FFmpeg encoder name: `\"copy\"`, `\"aac\"`, `\"libx264\"`, … */\n name: string;\n /**\n * Extra encoder options keyed by bare FFmpeg option name (no stream\n * specifier), e.g. `{ preset: \"veryfast\", b: \"3M\" }`.\n */\n options?: Record<string, string | number>;\n}\n\n/**\n * Audio rendition metadata for HLS output nodes. Sets the language tag and\n * label that appear in the player's track menu. Ignored by non-HLS nodes.\n */\nexport interface AudioRenditionOptions {\n /** BCP-47 language tag, e.g. `\"en\"`, `\"fr\"`. Default: `\"und\"`. */\n language?: string;\n /** Label shown in player track menus. Default: `\"Audio\"`. */\n label?: string;\n /** Pre-select this track on first play. Default: `false`. */\n defaultTrack?: boolean;\n}\n\n/**\n * Video rendition metadata for HLS output nodes. Ignored by non-HLS nodes.\n */\nexport interface VideoRenditionOptions {\n /**\n * Label used to name the HLS variant playlist/segments (sanitized to a\n * URL-safe variant name).\n */\n label?: string;\n}\n\n/** Fields shared by every subscription variant. */\ninterface SubscriptionSpecBase {\n source: BaseNode;\n /**\n * Delay this stream by N milliseconds before it reaches the subscribing\n * node (applied server-side). Typical use: delay the A/V subscriptions so\n * a slower caption source has lead time.\n */\n delayMs?: number;\n /**\n * Read from a keyed output port of a multi-output source node (Gate).\n * Ports are named by the `name` field of the specs the gate itself was\n * subscribed with. Mutually exclusive with `track`.\n */\n port?: string;\n /**\n * When the *subscribing* node is a multi-output node (Gate), names the\n * pass-through port it creates for this source (default: the source's\n * index as a string). Ignored by other node types.\n */\n name?: string;\n}\n\n/** Subscribe to a video-only stream of a source node. */\nexport interface VideoSubscriptionSpec extends SubscriptionSpecBase {\n sourceSelector: \"VIDEO\";\n /**\n * Per-type video track index within the source container (0 = first video\n * track). Discover available indices with `await source.tracks()`.\n * Default: 0.\n */\n track?: number;\n /** Codec applied to the video stream by the receiving output node. */\n codec?: CodecSpec;\n /** Video rendition metadata (HLS). */\n video?: VideoRenditionOptions;\n}\n\n/** Subscribe to an audio-only stream of a source node. */\nexport interface AudioSubscriptionSpec extends SubscriptionSpecBase {\n sourceSelector: \"AUDIO\";\n /**\n * Per-type audio track index within the source container (0 = first audio\n * track). Discover available indices with `await source.tracks()`.\n * Default: 0.\n */\n track?: number;\n /** Codec applied to the audio stream by the receiving output node. */\n codec?: CodecSpec;\n /** Audio rendition metadata (HLS). */\n audio?: AudioRenditionOptions;\n}\n\n/** Subscribe to the combined A/V stream of a source node. */\nexport interface AllSubscriptionSpec extends SubscriptionSpecBase {\n sourceSelector: \"ALL\";\n /** Codec applied to the video track by the receiving output node. */\n videoCodec?: CodecSpec;\n /** Codec applied to the audio track by the receiving output node. */\n audioCodec?: CodecSpec;\n /** Video rendition metadata (HLS). */\n video?: VideoRenditionOptions;\n /** Audio rendition metadata (HLS). */\n audio?: AudioRenditionOptions;\n}\n\n/** Subscribe to the caption stream of a source node. */\nexport interface CaptionsSubscriptionSpec extends SubscriptionSpecBase {\n sourceSelector: \"CAPTIONS\";\n}\n\n/**\n * One source wired into a subscribing node, discriminated by\n * `sourceSelector` so per-stream options (codec, rendition metadata) are\n * only available where they apply.\n */\nexport type SubscriptionSpec =\n | VideoSubscriptionSpec\n | AudioSubscriptionSpec\n | AllSubscriptionSpec\n | CaptionsSubscriptionSpec;\n\n/**\n * Minimal interface passed to factory functions. Defined here (not in workflow.ts)\n * to avoid a circular import: workflow.ts imports factory files, factory files\n * import this interface.\n */\nexport interface WorkflowHandle {\n readonly workflowId: string;\n readonly client: RemaClient;\n _onNodeEvent(\n nodeId: string,\n handler: (evt: NodeEventMsg) => void,\n ): () => void;\n}\n\n/**\n * Base class for all rema nodes.\n * Provides the .subscribe() method that wires nodes together on the server,\n * and a `NodeEvents` emitter (`.on(\"error\"/\"statusChange\"/\"close\", ...)`) as\n * an alternative to the `onError`/`onStatusChange`/`onClose` settings callbacks.\n */\nexport abstract class BaseNode extends TypedEmitter<NodeEvents> {\n readonly nodeId: string;\n readonly workflowId: string;\n protected readonly client: RemaClient;\n private _eventCleanup: (() => void) | null = null;\n private _callbacks: NodeCallbacks = {};\n\n constructor(nodeId: string, client: RemaClient, workflowId: string) {\n super();\n this.nodeId = nodeId;\n this.workflowId = workflowId;\n this.client = client;\n }\n\n /** Called by create functions to register the workflow event handler cleanup. */\n _setEventCleanup(fn: () => void): void {\n this._eventCleanup = fn;\n }\n\n /**\n * Called by create functions to register the node's lifecycle callbacks\n * (`onError`/`onStatusChange`/`onClose`), so `.close()` can invoke `onClose`\n * directly on a locally-initiated close.\n */\n _bindCallbacks(callbacks: NodeCallbacks): void {\n this._callbacks = callbacks;\n }\n\n /**\n * Subscribe this node to one or more source nodes.\n * This tells the server to route streams from the sources into this node.\n */\n async subscribe(\n specs: SubscriptionSpec[],\n options?: { gate?: ActivationGate },\n ): Promise<void> {\n await this.client.trpc.subscribeNode.mutate({\n workflowId: this.workflowId,\n nodeId: this.nodeId,\n sources: specs.map((s) => BaseNode._toSourceInput(s)),\n gate: options?.gate,\n });\n }\n\n /** Map one `SubscriptionSpec` to the wire shape expected by the server. */\n private static _toSourceInput(spec: SubscriptionSpec) {\n const base = {\n sourceNodeId: spec.source.nodeId,\n ...(spec.delayMs !== undefined ? { delayMs: spec.delayMs } : {}),\n ...(spec.port !== undefined ? { port: spec.port } : {}),\n ...(spec.name !== undefined ? { name: spec.name } : {}),\n };\n const audio = (a: AudioRenditionOptions) => ({\n language: a.language ?? \"und\",\n label: a.label ?? \"Audio\",\n defaultTrack: a.defaultTrack ?? false,\n });\n // `video` is only sent when a label is provided — it is the only field\n // and is required on the wire.\n const video = (v: VideoRenditionOptions) =>\n v.label !== undefined ? { video: { label: v.label } } : {};\n\n switch (spec.sourceSelector) {\n case \"CAPTIONS\":\n return { ...base, selector: \"CAPTIONS\" as const };\n case \"VIDEO\":\n return {\n ...base,\n selector: \"VIDEO\" as const,\n ...(spec.track !== undefined ? { track: spec.track } : {}),\n ...(spec.codec !== undefined ? { codec: spec.codec } : {}),\n ...(spec.video !== undefined ? video(spec.video) : {}),\n };\n case \"AUDIO\":\n return {\n ...base,\n selector: \"AUDIO\" as const,\n ...(spec.track !== undefined ? { track: spec.track } : {}),\n ...(spec.codec !== undefined ? { codec: spec.codec } : {}),\n ...(spec.audio !== undefined ? { audio: audio(spec.audio) } : {}),\n };\n case \"ALL\":\n return {\n ...base,\n selector: \"ALL\" as const,\n ...(spec.videoCodec !== undefined\n ? { videoCodec: spec.videoCodec }\n : {}),\n ...(spec.audioCodec !== undefined\n ? { audioCodec: spec.audioCodec }\n : {}),\n ...(spec.video !== undefined ? video(spec.video) : {}),\n ...(spec.audio !== undefined ? { audio: audio(spec.audio) } : {}),\n };\n }\n }\n\n /**\n * Probe this node's media stream (ffprobe, server-side) and return its\n * track list. Each entry's `track` is the per-type index to pass back as\n * `track` in a VIDEO/AUDIO subscription:\n *\n * ```ts\n * const tracks = await input.tracks();\n * const ita = tracks.find((t) => t.type === \"audio\" && t.language === \"ita\");\n * await output.subscribe([\n * { source: input, sourceSelector: \"VIDEO\" },\n * { source: input, sourceSelector: \"AUDIO\", track: ita!.track },\n * ]);\n * ```\n *\n * Probing consumes real-time-paced stream data, so it can take several\n * seconds; `probeSizeBytes` (default 1 MB) trades discovery depth for\n * latency. The server errors after ~15 s if the source produces no data\n * (e.g. no publisher connected yet, or a disabled file input).\n */\n async tracks(options?: { probeSizeBytes?: number }): Promise<TrackInfo[]> {\n return this.client.trpc.probeTracks.query({\n workflowId: this.workflowId,\n nodeId: this.nodeId,\n ...(options?.probeSizeBytes !== undefined\n ? { probeSizeBytes: options.probeSizeBytes }\n : {}),\n });\n }\n\n /** Tear down this node on the server. */\n async close(): Promise<void> {\n this._eventCleanup?.();\n await this.client.trpc.closeNode.mutate({\n workflowId: this.workflowId,\n nodeId: this.nodeId,\n });\n this._callbacks.onClose?.();\n this.emit(\"close\");\n }\n}\n","import { uid } from \"radash\";\nimport {\n BaseNode,\n dispatchNodeCallbacks,\n type NodeCallbacks,\n type WorkflowHandle,\n} from \"../node.js\";\n\nexport interface FileInputSettings extends NodeCallbacks {\n /** Absolute path to the media file (MP4, TS, MKV, …) */\n fileName: string;\n /** Loop the file indefinitely. Default: false */\n loop?: boolean;\n /** Source name used to identify streams from this node */\n sourceName?: string;\n /**\n * Start in a disabled state — the file is not read until `.enable()` is called\n * (or the node is enabled from the web UI). Default: false.\n */\n disabled?: boolean;\n /** Called when the node transitions to the enabled / ready state */\n onReady?: () => void;\n /**\n * Called once when the first data packet from this input arrives at the server.\n * Use this to convert absolute timestamps to the stream-relative `startAt`\n * expected by `CaptionSourceNode.send()`:\n *\n * ```ts\n * onStreamStart(wallClockMs) {\n * const startAt = myAbsoluteCaptionMs - wallClockMs;\n * await captions.send(text, { startAt });\n * }\n * ```\n *\n * @param wallClockMs `Date.now()` on the server at the moment the first\n * MPEG-TS data packet arrived from this input.\n */\n onStreamStart?: (wallClockMs: number) => void;\n /** Called once the file has finished playing (reaches EOF). Never fires when `loop` is true. */\n onEnded?: () => void;\n}\n\nexport class FileInputNode extends BaseNode {\n readonly settings: FileInputSettings;\n\n constructor(\n nodeId: string,\n workflow: WorkflowHandle,\n settings: FileInputSettings,\n ) {\n super(nodeId, workflow.client, workflow.workflowId);\n this.settings = settings;\n }\n\n /** Enable this node — starts reading the file and streaming to downstream nodes. */\n async enable(): Promise<void> {\n await this.client.trpc.enableNode.mutate({\n workflowId: this.workflowId,\n nodeId: this.nodeId,\n });\n }\n}\n\nexport async function createFileInputNode(\n workflow: WorkflowHandle,\n settings: FileInputSettings,\n nodeId?: string,\n): Promise<FileInputNode> {\n const id = nodeId ?? `file_input_${uid(12)}`;\n const node = new FileInputNode(id, workflow, settings);\n\n node._bindCallbacks(settings);\n const removeHandler = workflow._onNodeEvent(id, (evt) => {\n if (evt.kind === \"READY\") settings.onReady?.();\n if (evt.kind === \"ACTIVE\" && evt.message !== undefined)\n settings.onStreamStart?.(Number(evt.message));\n if (evt.kind === \"ENDED\") settings.onEnded?.();\n dispatchNodeCallbacks(node, evt, settings);\n });\n node._setEventCleanup(removeHandler);\n\n await workflow.client.trpc.createNode\n .mutate({\n kind: \"FILE_INPUT\",\n workflowId: workflow.workflowId,\n nodeId: id,\n fileInput: {\n fileName: settings.fileName,\n loop: settings.loop ?? false,\n sourceName: settings.sourceName,\n disabled: settings.disabled ?? false,\n },\n })\n .catch((err: unknown) => {\n removeHandler();\n throw err;\n });\n\n return node;\n}\n","import { uid } from \"radash\";\nimport {\n BaseNode,\n dispatchNodeCallbacks,\n type NodeCallbacks,\n type WorkflowHandle,\n} from \"../node.js\";\n\nexport interface NodeStreamInputSettings extends NodeCallbacks {\n /**\n * FFmpeg format identifier for the incoming audio data.\n *\n * Raw PCM examples: `\"s16le\"` (16-bit signed LE), `\"f32le\"` (32-bit float LE).\n * Encoded examples: `\"wav\"`, `\"mp3\"`, `\"aac\"`, `\"flac\"`.\n */\n format: string;\n /** Sample rate in Hz. Required when `format` is a raw PCM format. */\n sampleRate?: number;\n /** Number of audio channels. Required when `format` is a raw PCM format. */\n channels?: number;\n /**\n * FFmpeg codec for the MPEG-TS output. Defaults to `\"aac\"`, which is safe\n * for any input format. Use `\"copy\"` only when the input is already\n * MPEG-TS-compatible AAC.\n */\n audioCodec?: string;\n /** Human-readable label shown in the dashboard. */\n label?: string;\n /** Called when the node transitions to the ready state on the server. */\n onReady?: () => void;\n /**\n * Called once when the first audio packet is written to the server.\n * The argument is `Date.now()` on the server at that moment.\n */\n onStreamStart?: (wallClockMs: number) => void;\n /** Called once the underlying FFmpeg process exits cleanly after `end()`. */\n onEnded?: () => void;\n}\n\nexport class NodeStreamInputNode extends BaseNode {\n readonly settings: NodeStreamInputSettings;\n private readonly _apiBase: string;\n\n constructor(\n nodeId: string,\n workflow: WorkflowHandle,\n settings: NodeStreamInputSettings,\n ) {\n super(nodeId, workflow.client, workflow.workflowId);\n this.settings = settings;\n const { host, port } = workflow.client.options;\n this._apiBase = `http://${host}:${port}`;\n }\n\n /**\n * Push an audio stream to the server.\n *\n * Accepts any `AsyncIterable<Buffer | Uint8Array>`, which includes Node.js\n * `Readable` streams (async-iterable since Node 10) and async generators.\n * The call resolves when the source iterable is exhausted or the server\n * closes the connection.\n *\n * @example\n * ```ts\n * import { createReadStream } from \"node:fs\";\n * await audioInput.stream(createReadStream(\"audio.mp3\"));\n * ```\n *\n * @example\n * ```ts\n * // Raw PCM from a microphone via node-record-lpcm16\n * const mic = recorder.start({ sampleRate: 16000, channels: 1 });\n * await audioInput.stream(mic);\n * ```\n */\n async stream(source: AsyncIterable<Buffer | Uint8Array>): Promise<void> {\n const url = `${this._apiBase}/api/node-stream/${this.workflowId}/${this.nodeId}`;\n\n const body = new ReadableStream<Uint8Array>({\n async start(controller) {\n try {\n for await (const chunk of source) {\n controller.enqueue(\n chunk instanceof Uint8Array ? chunk : new Uint8Array(chunk),\n );\n }\n controller.close();\n } catch (err) {\n controller.error(err);\n }\n },\n });\n\n await fetch(url, {\n method: \"POST\",\n body,\n // Required for streaming request bodies in Node.js 18+ native fetch.\n duplex: \"half\",\n } as RequestInit);\n }\n}\n\nexport async function createNodeStreamInputNode(\n workflow: WorkflowHandle,\n settings: NodeStreamInputSettings,\n nodeId?: string,\n): Promise<NodeStreamInputNode> {\n const id = nodeId ?? `node_stream_${uid(12)}`;\n const node = new NodeStreamInputNode(id, workflow, settings);\n\n node._bindCallbacks(settings);\n const removeHandler = workflow._onNodeEvent(id, (evt) => {\n if (evt.kind === \"READY\") settings.onReady?.();\n if (evt.kind === \"ACTIVE\" && evt.message !== undefined)\n settings.onStreamStart?.(Number(evt.message));\n if (evt.kind === \"ENDED\") settings.onEnded?.();\n dispatchNodeCallbacks(node, evt, settings);\n });\n node._setEventCleanup(removeHandler);\n\n await workflow.client.trpc.createNode\n .mutate({\n kind: \"NODE_STREAM_INPUT\",\n workflowId: workflow.workflowId,\n nodeId: id,\n nodeStreamInput: {\n format: settings.format,\n ...(settings.sampleRate !== undefined && {\n sampleRate: settings.sampleRate,\n }),\n ...(settings.channels !== undefined && {\n channels: settings.channels,\n }),\n audioCodec: settings.audioCodec ?? \"aac\",\n ...(settings.label !== undefined && { label: settings.label }),\n },\n })\n .catch((err: unknown) => {\n removeHandler();\n throw err;\n });\n\n return node;\n}\n","import { uid } from \"radash\";\nimport {\n BaseNode,\n dispatchNodeCallbacks,\n type NodeCallbacks,\n type WorkflowHandle,\n} from \"../node.js\";\n\n/** Connection info passed to `onConnect` and `onDisconnect`. */\nexport interface RtmpConnectionInfo {\n app: string;\n sourceName: string;\n /**\n * MediaMTX connection id for this publisher, if it could be resolved at\n * connect time. Pass directly to `operations.disconnectSource`.\n */\n connectionId?: string;\n}\n\n/** Operations available on a live RTMP publisher connection. */\nexport interface RtmpAnnouncerOperations {\n /**\n * Forcibly disconnect the connection identified by `connectionId`. Safe to\n * call even if it already disconnected (resolves as a no-op).\n */\n disconnectSource(connectionId: string): Promise<void>;\n}\n\nexport interface RtmpAnnouncerSettings extends NodeCallbacks {\n /** RTMP app name to watch (default: \"live\"). */\n app?: string;\n /**\n * Called when a publisher connects. Create nodes in any workflow from\n * here, or call `operations.disconnectSource(info.connectionId)` to kick\n * the publisher synchronously (e.g. to reject an invalid `sourceName`).\n */\n onConnect: (\n info: RtmpConnectionInfo,\n operations: RtmpAnnouncerOperations,\n ) => void;\n /** Called when a publisher disconnects. */\n onDisconnect?: (info: RtmpConnectionInfo) => void;\n}\n\n/** SDK handle for an RTMP announcer node. */\nexport class RtmpAnnouncerNode extends BaseNode {\n /**\n * Forcibly disconnect the connection identified by `connectionId`. Can be\n * called at any time after the node is created — e.g. from an async\n * moderation decision made after `onConnect` already returned.\n */\n async disconnectSource(connectionId: string): Promise<void> {\n await this.client.trpc.disconnectSource.mutate({\n workflowId: this.workflowId,\n nodeId: this.nodeId,\n connectionId,\n });\n }\n}\n\nexport async function createRtmpAnnouncerNode(\n workflow: WorkflowHandle,\n settings: RtmpAnnouncerSettings,\n nodeId?: string,\n): Promise<RtmpAnnouncerNode> {\n const id = nodeId ?? `rtmp_announcer_${uid(12)}`;\n const app = settings.app ?? \"live\";\n const node = new RtmpAnnouncerNode(id, workflow.client, workflow.workflowId);\n const operations: RtmpAnnouncerOperations = {\n disconnectSource: (connectionId) => node.disconnectSource(connectionId),\n };\n\n node._bindCallbacks(settings);\n const removeHandler = workflow._onNodeEvent(id, (evt) => {\n if (evt.kind === \"STREAM_CONNECTED\" && evt.message !== undefined) {\n const info = JSON.parse(evt.message) as RtmpConnectionInfo;\n settings.onConnect(info, operations);\n }\n if (evt.kind === \"STREAM_DISCONNECTED\" && evt.message !== undefined) {\n const info = JSON.parse(evt.message) as RtmpConnectionInfo;\n settings.onDisconnect?.(info);\n }\n dispatchNodeCallbacks(node, evt, settings);\n });\n node._setEventCleanup(removeHandler);\n\n await workflow.client.trpc.createNode\n .mutate({\n kind: \"RTMP_ANNOUNCER\",\n workflowId: workflow.workflowId,\n nodeId: id,\n rtmpAnnouncer: { app },\n })\n .catch((err: unknown) => {\n removeHandler();\n throw err;\n });\n\n return node;\n}\n","import { uid } from \"radash\";\nimport type { RemaClient } from \"../client.js\";\nimport {\n BaseNode,\n dispatchNodeCallbacks,\n type NodeCallbacks,\n reportNodeError,\n type WorkflowHandle,\n} from \"../node.js\";\n\n/** Normalize a `catch`-clause value (which TypeScript types as `unknown`) into an `Error`. */\nfunction toError(err: unknown): Error {\n return err instanceof Error ? err : new Error(String(err));\n}\n\n// ── RtmpStreamNode ─────────────────────────────────────────────────────────────\n\n/**\n * Represents a single active RTMP stream created by an `RtmpMultiplexerNode`.\n *\n * The server creates this node automatically when a publisher connects. The SDK\n * wraps the server-side node ID so it can be passed as a source to downstream\n * nodes (encoders, outputs) via `subscribe()`.\n */\nexport class RtmpStreamNode extends BaseNode {\n readonly app: string;\n readonly sourceName: string;\n\n constructor(\n nodeId: string,\n client: RemaClient,\n app: string,\n sourceName: string,\n workflowId: string,\n ) {\n super(nodeId, client, workflowId);\n this.app = app;\n this.sourceName = sourceName;\n }\n}\n\n// ── RtmpMultiplexerNode ────────────────────────────────────────────────────────\n\n/**\n * SDK handle for an RTMP multiplexer node.\n *\n * Calling `close()` deactivates the multiplexer and all of its active stream\n * nodes on the server.\n */\nexport class RtmpMultiplexerNode extends BaseNode {}\n\n// ── Settings ──────────────────────────────────────────────────────────────────\n\nexport interface RtmpMultiplexerSettings extends NodeCallbacks {\n /** RTMP app name to listen on (default: \"live\"). */\n app?: string;\n\n /**\n * Called when a publisher connects. Return `{ accept: false }` to kick the\n * connection immediately; return `{ accept: true }` or omit the return value\n * to accept and trigger `onStream`.\n */\n onConnect?: (info: {\n app: string;\n sourceName: string;\n }) =>\n | Promise<{ accept: boolean } | undefined>\n | { accept: boolean }\n | undefined;\n\n /**\n * Called with the per-publisher `RtmpStreamNode` when a new RTMP stream is\n * accepted. Create your downstream pipeline (encoders, outputs) from this node.\n */\n onStream: (node: RtmpStreamNode) => Promise<void> | void;\n\n /**\n * Called when a publisher disconnects or is rejected. Use this to close any\n * downstream nodes you created in `onStream`.\n */\n onStreamEnd?: (node: RtmpStreamNode) => Promise<void> | void;\n}\n\n// ── Factory ───────────────────────────────────────────────────────────────────\n\nexport async function createRtmpMultiplexerNode(\n workflow: WorkflowHandle,\n settings: RtmpMultiplexerSettings,\n nodeId?: string,\n): Promise<RtmpMultiplexerNode> {\n const id = nodeId ?? `rtmp_mux_${uid(12)}`;\n const app = settings.app ?? \"live\";\n const node = new RtmpMultiplexerNode(\n id,\n workflow.client,\n workflow.workflowId,\n );\n\n // Track active stream nodes by sourceName for onStreamEnd lookup.\n const activeStreams = new Map<string, RtmpStreamNode>();\n\n node._bindCallbacks(settings);\n const removeHandler = workflow._onNodeEvent(id, (evt) => {\n dispatchNodeCallbacks(node, evt, settings);\n if (evt.kind === \"CHILD_CREATED\" && evt.message !== undefined) {\n const { childNodeId, sourceName } = JSON.parse(evt.message) as {\n childNodeId: string;\n sourceName: string;\n };\n\n Promise.resolve(settings.onConnect?.({ app, sourceName }))\n .then(async (result) => {\n if (result?.accept === false) {\n await workflow.client.trpc.rejectPublisher\n .mutate({\n workflowId: workflow.workflowId,\n nodeId: id,\n sourceName,\n })\n .catch((err: unknown) => {\n reportNodeError(node, settings, toError(err));\n });\n return;\n }\n const streamNode = new RtmpStreamNode(\n childNodeId,\n workflow.client,\n app,\n sourceName,\n workflow.workflowId,\n );\n activeStreams.set(sourceName, streamNode);\n await settings.onStream(streamNode);\n })\n .catch((err: unknown) => {\n reportNodeError(node, settings, toError(err));\n });\n return;\n }\n\n if (evt.kind === \"CHILD_CLOSED\" && evt.message !== undefined) {\n const { sourceName } = JSON.parse(evt.message) as { sourceName: string };\n const streamNode = activeStreams.get(sourceName);\n activeStreams.delete(sourceName);\n if (streamNode) {\n Promise.resolve(settings.onStreamEnd?.(streamNode)).catch(\n (err: unknown) => {\n reportNodeError(node, settings, toError(err));\n },\n );\n }\n }\n });\n node._setEventCleanup(removeHandler);\n\n await workflow.client.trpc.createNode\n .mutate({\n kind: \"RTMP_MULTIPLEXER\",\n workflowId: workflow.workflowId,\n nodeId: id,\n rtmpMultiplexer: { app, port: 1935 },\n })\n .catch((err: unknown) => {\n removeHandler();\n throw err;\n });\n\n return node;\n}\n","import { uid } from \"radash\";\nimport {\n BaseNode,\n dispatchNodeCallbacks,\n type NodeCallbacks,\n type WorkflowHandle,\n} from \"../node.js\";\n\nexport interface RtmpReaderSettings extends NodeCallbacks {\n /** RTMP app name. */\n app: string;\n /** Source name (stream key). */\n sourceName: string;\n /**\n * Retry policy for a failed FFmpeg pull from the internal MediaMTX relay\n * (e.g. a transient network blip). All fields optional; defaults to 5\n * attempts with 1s→10s exponential backoff before the node errors out.\n */\n retry?: {\n /** Max retries before giving up and marking the node ERROR. Default: 5. */\n maxAttempts?: number;\n /** Initial backoff delay in ms before the first retry. Default: 1000. */\n baseDelayMs?: number;\n /** Backoff delay ceiling in ms. Default: 10000. */\n maxDelayMs?: number;\n };\n /** Called when an RTMP publisher connects. */\n onPublish?: (info: { app: string; internalUrl: string }) => void;\n /**\n * Called once when the first data packet from this input arrives at the server.\n * @param wallClockMs `Date.now()` on the server at the moment the first MPEG-TS packet arrived.\n */\n onStreamStart?: (wallClockMs: number) => void;\n /** Called when the publisher disconnects (the underlying FFmpeg pull exits). */\n onStreamEnded?: () => void;\n}\n\nexport class RtmpReaderNode extends BaseNode {\n readonly settings: RtmpReaderSettings;\n\n constructor(\n nodeId: string,\n workflow: WorkflowHandle,\n settings: RtmpReaderSettings,\n ) {\n super(nodeId, workflow.client, workflow.workflowId);\n this.settings = settings;\n }\n}\n\nexport async function createRtmpReaderNode(\n workflow: WorkflowHandle,\n settings: RtmpReaderSettings,\n nodeId?: string,\n): Promise<RtmpReaderNode> {\n const id = nodeId ?? `rtmp_reader_${uid(12)}`;\n const node = new RtmpReaderNode(id, workflow, settings);\n\n node._bindCallbacks(settings);\n const removeHandler = workflow._onNodeEvent(id, (evt) => {\n if (evt.kind === \"READY\") {\n settings.onPublish?.({\n app: settings.app,\n internalUrl: evt.message ?? \"\",\n });\n }\n if (evt.kind === \"ACTIVE\" && evt.message !== undefined) {\n settings.onStreamStart?.(Number(evt.message));\n }\n if (evt.kind === \"ENDED\") settings.onStreamEnded?.();\n dispatchNodeCallbacks(node, evt, settings);\n });\n node._setEventCleanup(removeHandler);\n\n await workflow.client.trpc.createNode\n .mutate({\n kind: \"RTMP_READER\",\n workflowId: workflow.workflowId,\n nodeId: id,\n rtmpReader: {\n app: settings.app,\n sourceName: settings.sourceName,\n ...(settings.retry !== undefined && { retry: settings.retry }),\n },\n })\n .catch((err: unknown) => {\n removeHandler();\n throw err;\n });\n\n return node;\n}\n","import { uid } from \"radash\";\nimport {\n BaseNode,\n dispatchNodeCallbacks,\n type NodeCallbacks,\n type WorkflowHandle,\n} from \"../node.js\";\n\n/** Connection info passed to `onConnect` and `onDisconnect`. */\nexport interface SrtConnectionInfo {\n app: string;\n sourceName: string;\n /** Passphrase configured on the announcer, if any. */\n passphrase?: string;\n /** Negotiated SRT latency in milliseconds, if configured. */\n latencyMs?: number;\n /**\n * MediaMTX connection id for this publisher, if it could be resolved at\n * connect time. Pass directly to `operations.disconnectSource`.\n */\n connectionId?: string;\n}\n\n/** Operations available on a live SRT publisher connection. */\nexport interface SrtAnnouncerOperations {\n /**\n * Forcibly disconnect the connection identified by `connectionId`. Safe to\n * call even if it already disconnected (resolves as a no-op).\n */\n disconnectSource(connectionId: string): Promise<void>;\n}\n\nexport interface SrtAnnouncerSettings extends NodeCallbacks {\n /** SRT app name to watch (default: \"live\"). */\n app?: string;\n /** Passphrase to include in connection events (mirrors MediaMTX path config). */\n passphrase?: string;\n /** SRT latency in milliseconds to include in connection events. */\n latencyMs?: number;\n /**\n * Called when a publisher connects. Create nodes in any workflow from\n * here, or call `operations.disconnectSource(info.connectionId)` to kick\n * the publisher synchronously (e.g. to reject an invalid `sourceName`).\n */\n onConnect: (\n info: SrtConnectionInfo,\n operations: SrtAnnouncerOperations,\n ) => void;\n /** Called when a publisher disconnects. */\n onDisconnect?: (info: Pick<SrtConnectionInfo, \"app\" | \"sourceName\">) => void;\n}\n\n/** SDK handle for an SRT announcer node. */\nexport class SrtAnnouncerNode extends BaseNode {\n /**\n * Forcibly disconnect the connection identified by `connectionId`. Can be\n * called at any time after the node is created — e.g. from an async\n * moderation decision made after `onConnect` already returned.\n */\n async disconnectSource(connectionId: string): Promise<void> {\n await this.client.trpc.disconnectSource.mutate({\n workflowId: this.workflowId,\n nodeId: this.nodeId,\n connectionId,\n });\n }\n}\n\nexport async function createSrtAnnouncerNode(\n workflow: WorkflowHandle,\n settings: SrtAnnouncerSettings,\n nodeId?: string,\n): Promise<SrtAnnouncerNode> {\n const id = nodeId ?? `srt_announcer_${uid(12)}`;\n const app = settings.app ?? \"live\";\n const node = new SrtAnnouncerNode(id, workflow.client, workflow.workflowId);\n const operations: SrtAnnouncerOperations = {\n disconnectSource: (connectionId) => node.disconnectSource(connectionId),\n };\n\n node._bindCallbacks(settings);\n const removeHandler = workflow._onNodeEvent(id, (evt) => {\n if (evt.kind === \"STREAM_CONNECTED\" && evt.message !== undefined) {\n const info = JSON.parse(evt.message) as SrtConnectionInfo;\n settings.onConnect(info, operations);\n }\n if (evt.kind === \"STREAM_DISCONNECTED\" && evt.message !== undefined) {\n const info = JSON.parse(evt.message) as Pick<\n SrtConnectionInfo,\n \"app\" | \"sourceName\"\n >;\n settings.onDisconnect?.(info);\n }\n dispatchNodeCallbacks(node, evt, settings);\n });\n node._setEventCleanup(removeHandler);\n\n await workflow.client.trpc.createNode\n .mutate({\n kind: \"SRT_ANNOUNCER\",\n workflowId: workflow.workflowId,\n nodeId: id,\n srtAnnouncer: {\n app,\n ...(settings.passphrase !== undefined && {\n passphrase: settings.passphrase,\n }),\n ...(settings.latencyMs !== undefined && {\n latencyMs: settings.latencyMs,\n }),\n },\n })\n .catch((err: unknown) => {\n removeHandler();\n throw err;\n });\n\n return node;\n}\n","import { uid } from \"radash\";\nimport {\n BaseNode,\n dispatchNodeCallbacks,\n type NodeCallbacks,\n type WorkflowHandle,\n} from \"../node.js\";\n\nexport interface SrtReaderSettings extends NodeCallbacks {\n /** SRT app name. */\n app: string;\n /** Source name (stream key / stream ID suffix). */\n sourceName: string;\n /**\n * Retry policy for a failed FFmpeg pull from the internal MediaMTX relay\n * (e.g. a transient network blip). All fields optional; defaults to 5\n * attempts with 1s→10s exponential backoff before the node errors out.\n */\n retry?: {\n /** Max retries before giving up and marking the node ERROR. Default: 5. */\n maxAttempts?: number;\n /** Initial backoff delay in ms before the first retry. Default: 1000. */\n baseDelayMs?: number;\n /** Backoff delay ceiling in ms. Default: 10000. */\n maxDelayMs?: number;\n };\n /** Called when an SRT publisher connects and the stream is ready to read. */\n onPublish?: (info: { app: string; internalUrl: string }) => void;\n /**\n * Called once when the first data packet from this input arrives at the server.\n * @param wallClockMs `Date.now()` on the server at the moment the first MPEG-TS packet arrived.\n */\n onStreamStart?: (wallClockMs: number) => void;\n /** Called when the publisher disconnects (the underlying FFmpeg pull exits). */\n onStreamEnded?: () => void;\n}\n\n/** SDK handle for an SRT reader node. */\nexport class SrtReaderNode extends BaseNode {\n readonly settings: SrtReaderSettings;\n\n constructor(\n nodeId: string,\n workflow: WorkflowHandle,\n settings: SrtReaderSettings,\n ) {\n super(nodeId, workflow.client, workflow.workflowId);\n this.settings = settings;\n }\n}\n\nexport async function createSrtReaderNode(\n workflow: WorkflowHandle,\n settings: SrtReaderSettings,\n nodeId?: string,\n): Promise<SrtReaderNode> {\n const id = nodeId ?? `srt_reader_${uid(12)}`;\n const node = new SrtReaderNode(id, workflow, settings);\n\n node._bindCallbacks(settings);\n const removeHandler = workflow._onNodeEvent(id, (evt) => {\n if (evt.kind === \"READY\") {\n settings.onPublish?.({\n app: settings.app,\n internalUrl: evt.message ?? \"\",\n });\n }\n if (evt.kind === \"ACTIVE\" && evt.message !== undefined) {\n settings.onStreamStart?.(Number(evt.message));\n }\n if (evt.kind === \"ENDED\") settings.onStreamEnded?.();\n dispatchNodeCallbacks(node, evt, settings);\n });\n node._setEventCleanup(removeHandler);\n\n await workflow.client.trpc.createNode\n .mutate({\n kind: \"SRT_READER\",\n workflowId: workflow.workflowId,\n nodeId: id,\n srtReader: {\n app: settings.app,\n sourceName: settings.sourceName,\n ...(settings.retry !== undefined && { retry: settings.retry }),\n },\n })\n .catch((err: unknown) => {\n removeHandler();\n throw err;\n });\n\n return node;\n}\n","import { uid } from \"radash\";\nimport {\n BaseNode,\n dispatchNodeCallbacks,\n type NodeCallbacks,\n type WorkflowHandle,\n} from \"../node.js\";\n\nexport interface FileOutputSettings extends NodeCallbacks {\n /** Absolute path for the output file */\n fileName: string;\n /** Container format: \"mp4\" | \"ts\" | \"mkv\". Default: inferred from extension */\n format?: string;\n /** Overwrite `fileName` if it already exists. Default: `true`. */\n overwrite?: boolean;\n}\n\nexport class FileOutputNode extends BaseNode {\n readonly settings: FileOutputSettings;\n\n constructor(\n nodeId: string,\n workflow: WorkflowHandle,\n settings: FileOutputSettings,\n ) {\n super(nodeId, workflow.client, workflow.workflowId);\n this.settings = settings;\n }\n}\n\nexport async function createFileOutputNode(\n workflow: WorkflowHandle,\n settings: FileOutputSettings,\n nodeId?: string,\n): Promise<FileOutputNode> {\n const id = nodeId ?? `file_output_${uid(12)}`;\n const node = new FileOutputNode(id, workflow, settings);\n\n node._bindCallbacks(settings);\n const removeHandler = workflow._onNodeEvent(id, (evt) => {\n dispatchNodeCallbacks(node, evt, settings);\n });\n node._setEventCleanup(removeHandler);\n\n await workflow.client.trpc.createNode\n .mutate({\n kind: \"FILE_OUTPUT\",\n workflowId: workflow.workflowId,\n nodeId: id,\n fileOutput: {\n fileName: settings.fileName,\n format: settings.format ?? \"\",\n overwrite: settings.overwrite ?? true,\n },\n })\n .catch((err: unknown) => {\n removeHandler();\n throw err;\n });\n\n return node;\n}\n","import { uid } from \"radash\";\nimport {\n BaseNode,\n dispatchNodeCallbacks,\n type NodeCallbacks,\n type WorkflowHandle,\n} from \"../node.js\";\n\nexport type HlsDestination =\n | { type: \"file\"; path: string }\n | {\n type: \"s3\";\n bucket: string;\n region: string;\n /** S3-compatible endpoint URL (e.g. Tigris, MinIO, Cloudflare R2). */\n endpoint?: string;\n /** Key prefix prepended to all uploaded paths. */\n keyPrefix?: string;\n /**\n * When true (default), a per-session timestamp suffix is appended to\n * `keyPrefix` so that browser-cached segments from a previous session are\n * never served for a new one. Set to false when the caller already\n * generates a unique prefix or when sub-paths are not supported.\n */\n makePrefixUnique?: boolean;\n accessKeyId: string;\n secretAccessKey: string;\n /**\n * `Cache-Control` header for `.m3u8` playlist objects.\n * Default: `\"no-cache, no-store, must-revalidate\"` — playlists must\n * never be served stale.\n */\n playlistCacheControl?: string;\n /**\n * `Cache-Control` header for segment objects (`.ts`, `.vtt`).\n * Default: `\"public, max-age=86400\"` — segments are immutable once\n * written, so they can be cached aggressively.\n */\n segmentsCacheControl?: string;\n }\n | {\n type: \"http\";\n /** Base URL that receives PUT/DELETE requests for each segment and playlist. */\n baseUrl: string;\n /** Extra headers sent with every PUT/DELETE request. */\n headers?: Record<string, string>;\n };\n\nexport interface HlsOutputSettings extends NodeCallbacks {\n /** One or more destinations to fan HLS output to. */\n destinations: HlsDestination[];\n /** Target segment duration in seconds. Default: 6 */\n segmentTime?: number;\n /**\n * Number of segments kept in the live playlist window.\n * 0 = keep all (VOD). Default: 5.\n */\n listSize?: number;\n /**\n * Node-level fallback audio codec, applied to audio streams whose\n * subscription does not specify its own `codec`. Omitted (or `copy`)\n * preserves the input audio codec — except when more than one audio source\n * is connected, where the default becomes `aac` so all alternate-audio\n * renditions share one codec (required by hls.js).\n *\n * For a caption-readiness buffer (the old `avDelayMs`), put `delayMs` on\n * each media subscription instead and leave caption subscriptions\n * undelayed.\n */\n audioCodec?: \"copy\" | string | null;\n /**\n * Node-level fallback video codec, applied to video streams whose\n * subscription does not specify its own `codec`. Omitted (or `copy`)\n * preserves the input video codec.\n */\n videoCodec?: \"copy\" | string | null;\n}\n\nexport class HlsOutputNode extends BaseNode {\n readonly settings: HlsOutputSettings;\n\n constructor(\n nodeId: string,\n workflow: WorkflowHandle,\n settings: HlsOutputSettings,\n ) {\n super(nodeId, workflow.client, workflow.workflowId);\n this.settings = settings;\n }\n\n /**\n * Return the effective base path or URL for each configured destination in\n * the current session. Must be called after `subscribe()` so that the server\n * has wired the destinations. For S3 destinations this includes the\n * per-session subfolder appended by `makePrefixUnique`.\n */\n async effectivePaths(): Promise<string[]> {\n const result = await this.client.trpc.hlsOutputInfo.query({\n workflowId: this.workflowId,\n nodeId: this.nodeId,\n });\n return result.effectivePaths;\n }\n}\n\nexport async function createHlsOutputNode(\n workflow: WorkflowHandle,\n settings: HlsOutputSettings,\n nodeId?: string,\n): Promise<HlsOutputNode> {\n const id = nodeId ?? `hls_output_${uid(12)}`;\n const node = new HlsOutputNode(id, workflow, settings);\n\n node._bindCallbacks(settings);\n const removeHandler = workflow._onNodeEvent(id, (evt) => {\n dispatchNodeCallbacks(node, evt, settings);\n });\n node._setEventCleanup(removeHandler);\n\n await workflow.client.trpc.createNode\n .mutate({\n kind: \"HLS_OUTPUT\",\n workflowId: workflow.workflowId,\n nodeId: id,\n hlsOutput: {\n destinations: settings.destinations,\n segmentTime: settings.segmentTime ?? 6,\n listSize: settings.listSize ?? 5,\n audioCodec: settings.audioCodec ?? undefined,\n videoCodec: settings.videoCodec ?? undefined,\n },\n })\n .catch((err: unknown) => {\n removeHandler();\n throw err;\n });\n\n return node;\n}\n","import { uid } from \"radash\";\nimport {\n BaseNode,\n dispatchNodeCallbacks,\n type NodeCallbacks,\n type WorkflowHandle,\n} from \"../node.js\";\n\nexport interface RtmpOutputSettings extends NodeCallbacks {\n /** Full RTMP URL to push to, e.g. \"rtmp://live.twitch.tv/app/stream-key\" */\n url: string;\n /**\n * Called when the output FFmpeg process exits with an error after the node\n * has been created — e.g. the destination server refused the connection,\n * dropped mid-stream, or returned an unexpected error code.\n *\n * Errors that occur during node creation still reject the\n * `workflow.output.rtmp()` promise as usual.\n */\n onError?: (err: Error) => void;\n}\n\nexport class RtmpOutputNode extends BaseNode {\n readonly settings: RtmpOutputSettings;\n\n constructor(\n nodeId: string,\n workflow: WorkflowHandle,\n settings: RtmpOutputSettings,\n ) {\n super(nodeId, workflow.client, workflow.workflowId);\n this.settings = settings;\n }\n}\n\nexport async function createRtmpOutputNode(\n workflow: WorkflowHandle,\n settings: RtmpOutputSettings,\n nodeId?: string,\n): Promise<RtmpOutputNode> {\n const id = nodeId ?? `rtmp_output_${uid(12)}`;\n const node = new RtmpOutputNode(id, workflow, settings);\n\n node._bindCallbacks(settings);\n const removeHandler = workflow._onNodeEvent(id, (evt) => {\n dispatchNodeCallbacks(node, evt, settings);\n });\n node._setEventCleanup(removeHandler);\n\n await workflow.client.trpc.createNode\n .mutate({\n kind: \"RTMP_OUTPUT\",\n workflowId: workflow.workflowId,\n nodeId: id,\n rtmpOutput: { url: settings.url },\n })\n .catch((err: unknown) => {\n removeHandler();\n throw err;\n });\n\n return node;\n}\n","import { uid } from \"radash\";\nimport {\n BaseNode,\n dispatchNodeCallbacks,\n type NodeCallbacks,\n type WorkflowHandle,\n} from \"../node.js\";\n\nexport interface SrtOutputSettings extends NodeCallbacks {\n /**\n * Full SRT URL to push to, e.g. `\"srt://host:port\"`.\n * Transport parameters (latency, passphrase, etc.) are passed as query\n * params: `\"srt://host:port?latency=200&passphrase=secret\"`.\n */\n url: string;\n /**\n * Called when the output FFmpeg process exits with an error after the node\n * has been created — e.g. the destination refused the connection or dropped\n * mid-stream.\n *\n * Errors that occur during node creation still reject the\n * `workflow.output.srt()` promise as usual.\n */\n onError?: (err: Error) => void;\n}\n\nexport class SrtOutputNode extends BaseNode {\n readonly settings: SrtOutputSettings;\n\n constructor(\n nodeId: string,\n workflow: WorkflowHandle,\n settings: SrtOutputSettings,\n ) {\n super(nodeId, workflow.client, workflow.workflowId);\n this.settings = settings;\n }\n}\n\nexport async function createSrtOutputNode(\n workflow: WorkflowHandle,\n settings: SrtOutputSettings,\n nodeId?: string,\n): Promise<SrtOutputNode> {\n const id = nodeId ?? `srt_output_${uid(12)}`;\n const node = new SrtOutputNode(id, workflow, settings);\n\n node._bindCallbacks(settings);\n const removeHandler = workflow._onNodeEvent(id, (evt) => {\n dispatchNodeCallbacks(node, evt, settings);\n });\n node._setEventCleanup(removeHandler);\n\n await workflow.client.trpc.createNode\n .mutate({\n kind: \"SRT_OUTPUT\",\n workflowId: workflow.workflowId,\n nodeId: id,\n srtOutput: { url: settings.url },\n })\n .catch((err: unknown) => {\n removeHandler();\n throw err;\n });\n\n return node;\n}\n","import { uid } from \"radash\";\nimport {\n BaseNode,\n dispatchNodeCallbacks,\n type NodeCallbacks,\n type WorkflowHandle,\n} from \"../node.js\";\n\nexport interface BufferSettings extends NodeCallbacks {\n /** Time (ms) to hold back the input stream before forwarding it to subscribers. */\n bufferMs: number;\n}\n\n/**\n * Time-shift node. Subscribe it to exactly one source (any selector); it\n * re-emits that source delayed by `bufferMs`, shared so every downstream\n * subscriber sees the same delayed feed.\n */\nexport class BufferNode extends BaseNode {\n readonly settings: BufferSettings;\n\n constructor(\n nodeId: string,\n workflow: WorkflowHandle,\n settings: BufferSettings,\n ) {\n super(nodeId, workflow.client, workflow.workflowId);\n this.settings = settings;\n }\n}\n\n/** Create a {@link BufferNode} on the server. */\nexport async function createBufferNode(\n workflow: WorkflowHandle,\n settings: BufferSettings,\n nodeId?: string,\n): Promise<BufferNode> {\n const id = nodeId ?? `buffer_${uid(12)}`;\n const node = new BufferNode(id, workflow, settings);\n\n node._bindCallbacks(settings);\n const removeHandler = workflow._onNodeEvent(id, (evt) => {\n dispatchNodeCallbacks(node, evt, settings);\n });\n node._setEventCleanup(removeHandler);\n\n await workflow.client.trpc.createNode\n .mutate({\n kind: \"BUFFER\",\n workflowId: workflow.workflowId,\n nodeId: id,\n buffer: { bufferMs: settings.bufferMs },\n })\n .catch((err: unknown) => {\n removeHandler();\n throw err;\n });\n\n return node;\n}\n","import { uid } from \"radash\";\nimport {\n BaseNode,\n dispatchNodeCallbacks,\n type NodeCallbacks,\n type WorkflowHandle,\n} from \"../node.js\";\n\nexport interface CambAiTranslateSettings extends NodeCallbacks {\n /** Camb.ai API key. */\n apiKey: string;\n /** BCP-47 source language code for the input audio (e.g. `\"en-US\"`). */\n sourceLanguageCode: string;\n /** BCP-47 target language code for the translation output (e.g. `\"de-DE\"`). */\n targetLanguageCode: string;\n /** Realtime translation model. Defaults to Camb's server-side default. */\n model?: \"lilac\" | \"violet\" | \"iris\" | \"orchid\";\n /** Cloned voice ID to synthesize the translation with. Defaults to a built-in voice for the target language. */\n voiceId?: number;\n /** Human-readable label shown in the dashboard. */\n label?: string;\n}\n\n/** Processor node that translates audio via the Camb.ai realtime speech-to-speech API. */\nexport class CambAiTranslateNode extends BaseNode {\n readonly settings: CambAiTranslateSettings;\n\n constructor(\n nodeId: string,\n workflow: WorkflowHandle,\n settings: CambAiTranslateSettings,\n ) {\n super(nodeId, workflow.client, workflow.workflowId);\n this.settings = settings;\n }\n}\n\nexport async function createCambAiTranslateNode(\n workflow: WorkflowHandle,\n settings: CambAiTranslateSettings,\n nodeId?: string,\n): Promise<CambAiTranslateNode> {\n const id = nodeId ?? `camb_translate_${uid(12)}`;\n const node = new CambAiTranslateNode(id, workflow, settings);\n\n node._bindCallbacks(settings);\n const removeHandler = workflow._onNodeEvent(id, (evt) => {\n dispatchNodeCallbacks(node, evt, settings);\n });\n node._setEventCleanup(removeHandler);\n\n await workflow.client.trpc.createNode\n .mutate({\n kind: \"CAMB_AI_TRANSLATE\",\n workflowId: workflow.workflowId,\n nodeId: id,\n cambAiTranslate: {\n apiKey: settings.apiKey,\n sourceLanguageCode: settings.sourceLanguageCode,\n targetLanguageCode: settings.targetLanguageCode,\n model: settings.model,\n voiceId: settings.voiceId,\n label: settings.label,\n },\n })\n .catch((err: unknown) => {\n removeHandler();\n throw err;\n });\n\n return node;\n}\n","import { uid } from \"radash\";\nimport {\n BaseNode,\n dispatchNodeCallbacks,\n type NodeCallbacks,\n type WorkflowHandle,\n} from \"../node.js\";\n\nexport type CaptionMode = \"rollUp\" | \"popOn\" | \"paintOn\";\n\nexport interface Cea608EncoderSettings extends NodeCallbacks {\n /** Caption display mode. Default: \"rollUp\" */\n mode?: CaptionMode;\n /**\n * Maximum number of visible rows (2, 3, or 4). Default: 2.\n *\n * - **rollUp**: sets the scroll-window height (RU2/RU3/RU4 command).\n * - **popOn**: captions sent with `\\n`-separated lines are displayed\n * bottom-anchored; this value caps how many lines are shown at once.\n * E.g. `rows: 2` with `\"line one\\nline two\"` fills rows 14 and 15.\n */\n rows?: 2 | 3 | 4;\n /**\n * When `mode` is `\"popOn\"`, automatically word-wrap caption text to\n * CEA-608's 32-column display width before laying it out as a\n * bottom-anchored multi-line caption card (capped at `rows` lines) —\n * instead of requiring you to insert `\\n` yourself. Has no effect in\n * `\"rollUp\"`/`\"paintOn\"` modes, where each `send()` is naturally a single\n * line.\n *\n * Default: false.\n */\n autoWrap?: boolean;\n}\n\nexport class Cea608EncoderNode extends BaseNode {\n readonly settings: Cea608EncoderSettings;\n\n constructor(\n nodeId: string,\n workflow: WorkflowHandle,\n settings: Cea608EncoderSettings,\n ) {\n super(nodeId, workflow.client, workflow.workflowId);\n this.settings = settings;\n }\n}\n\nexport async function createCea608EncoderNode(\n workflow: WorkflowHandle,\n settings: Cea608EncoderSettings = {},\n nodeId?: string,\n): Promise<Cea608EncoderNode> {\n const id = nodeId ?? `cea608_${uid(12)}`;\n const node = new Cea608EncoderNode(id, workflow, settings);\n\n node._bindCallbacks(settings);\n const removeHandler = workflow._onNodeEvent(id, (evt) => {\n dispatchNodeCallbacks(node, evt, settings);\n });\n node._setEventCleanup(removeHandler);\n\n await workflow.client.trpc.createNode\n .mutate({\n kind: \"CEA608_ENCODER\",\n workflowId: workflow.workflowId,\n nodeId: id,\n cea608Encoder: {\n mode: settings.mode ?? \"rollUp\",\n rows: settings.rows ?? 2,\n autoWrap: settings.autoWrap ?? false,\n },\n })\n .catch((err: unknown) => {\n removeHandler();\n throw err;\n });\n\n return node;\n}\n","import type { MonitorEventMsg } from \"@remavideo/server\";\nimport { uid } from \"radash\";\nimport {\n BaseNode,\n dispatchNodeCallbacks,\n type NodeCallbacks,\n type WorkflowHandle,\n} from \"../node.js\";\n\nexport type { MonitorEventMsg };\n\nexport interface FfprobeMonitorSettings extends NodeCallbacks {\n /**\n * How often (ms) to re-run ffprobe on the attached stream.\n * Each cycle spawns a fresh ffprobe process, probes `probeSizeBytes` of data,\n * and publishes the result to the **stream-info** monitor.\n * Default: 30 000 ms.\n */\n intervalMs?: number;\n /**\n * Bytes of stream data to feed into each ffprobe invocation.\n * Larger values give ffprobe more context (e.g. long-GOP streams) at the\n * cost of a slightly longer first-result latency. Default: 1 048 576 (1 MB).\n */\n probeSizeBytes?: number;\n}\n\nexport class FfprobeMonitorNode extends BaseNode {\n readonly settings: FfprobeMonitorSettings;\n\n constructor(\n nodeId: string,\n workflow: WorkflowHandle,\n settings: FfprobeMonitorSettings,\n ) {\n super(nodeId, workflow.client, workflow.workflowId);\n this.settings = settings;\n }\n\n /**\n * Subscribe to a monitor stream on this node.\n *\n * - `\"stream-info\"` — emits a `status` event (key/value map) every time a\n * probe cycle completes, containing codec names, resolution, frame rate,\n * bit rates, and format.\n * - `\"ffprobe-log\"` — emits `log` events with raw ffprobe stderr output.\n *\n * Returns an object with an `unsubscribe()` method; call it to stop receiving\n * events and release the tRPC subscription.\n */\n observe(\n monitorName: \"stream-info\" | \"ffprobe-log\",\n onData: (event: MonitorEventMsg) => void,\n onError?: (err: unknown) => void,\n ): { unsubscribe(): void } {\n return this.client.trpc.observeNode.subscribe(\n { workflowId: this.workflowId, nodeId: this.nodeId, monitorName },\n { onData, ...(onError !== undefined && { onError }) },\n );\n }\n}\n\nexport async function createFfprobeMonitorNode(\n workflow: WorkflowHandle,\n settings: FfprobeMonitorSettings = {},\n nodeId?: string,\n): Promise<FfprobeMonitorNode> {\n const id = nodeId ?? `ffprobe_${uid(12)}`;\n const node = new FfprobeMonitorNode(id, workflow, settings);\n\n node._bindCallbacks(settings);\n const removeHandler = workflow._onNodeEvent(id, (evt) => {\n dispatchNodeCallbacks(node, evt, settings);\n });\n node._setEventCleanup(removeHandler);\n\n await workflow.client.trpc.createNode\n .mutate({\n kind: \"FFPROBE_MONITOR\",\n workflowId: workflow.workflowId,\n nodeId: id,\n ffprobeMonitor: {\n intervalMs: settings.intervalMs ?? 30_000,\n probeSizeBytes: settings.probeSizeBytes ?? 1_048_576,\n },\n })\n .catch((err: unknown) => {\n removeHandler();\n throw err;\n });\n\n return node;\n}\n","import { uid } from \"radash\";\nimport {\n BaseNode,\n dispatchNodeCallbacks,\n type NodeCallbacks,\n type WorkflowHandle,\n} from \"../node.js\";\n\nexport interface GateSettings extends NodeCallbacks {\n /**\n * Number of distinct sources that must have emitted at least one item\n * before the gate opens. Default: wait for every subscribed source.\n */\n minReady?: number;\n /**\n * Force the gate open after this many ms even if `minReady` hasn't been\n * reached, using whatever sources are ready by then. Omit to wait\n * indefinitely — a source that never produces data would otherwise stall\n * the gate forever.\n */\n timeoutMs?: number;\n}\n\n/**\n * Synchronization barrier for fan-in from multiple, independently-timed\n * sources (e.g. two different transcription/translation services). Not to\n * be confused with the `gate` option on `.subscribe()` (`ActivationGate`),\n * which defers wiring based on upstream node *lifecycle*, not data.\n *\n * `GateNode` subscribes to every source immediately and queues everything\n * each one emits, then starts forwarding — for every source at once — only\n * once enough of them have produced their first item. It is a one-time\n * rendezvous, not a per-item join.\n *\n * Every source becomes its own keyed pass-through port on the gate — streams\n * are never merged. Name a port with `name` on the gate's subscription spec\n * (default: the source's index as a string) and read it downstream with\n * `port`:\n *\n * ```ts\n * await gate.subscribe([\n * { source: a, sourceSelector: \"ALL\", name: \"main\" },\n * { source: b, sourceSelector: \"ALL\", name: \"backup\" },\n * ]);\n * await out1.subscribe([{ source: gate, sourceSelector: \"ALL\", port: \"main\" }]);\n * await out2.subscribe([{ source: gate, sourceSelector: \"ALL\", port: \"backup\" }]);\n * ```\n *\n * With exactly one source of a kind, plain selector subscriptions (no\n * `port`) still resolve to it; with several same-kind sources the server\n * errors and asks for an explicit `port`.\n */\nexport class GateNode extends BaseNode {\n readonly settings: GateSettings;\n\n constructor(\n nodeId: string,\n workflow: WorkflowHandle,\n settings: GateSettings,\n ) {\n super(nodeId, workflow.client, workflow.workflowId);\n this.settings = settings;\n }\n}\n\n/** Create a {@link GateNode} on the server. */\nexport async function createGateNode(\n workflow: WorkflowHandle,\n settings: GateSettings = {},\n nodeId?: string,\n): Promise<GateNode> {\n const id = nodeId ?? `gate_${uid(12)}`;\n const node = new GateNode(id, workflow, settings);\n\n node._bindCallbacks(settings);\n const removeHandler = workflow._onNodeEvent(id, (evt) => {\n dispatchNodeCallbacks(node, evt, settings);\n });\n node._setEventCleanup(removeHandler);\n\n await workflow.client.trpc.createNode\n .mutate({\n kind: \"GATE\",\n workflowId: workflow.workflowId,\n nodeId: id,\n gate: {\n ...(settings.minReady !== undefined && {\n minReady: settings.minReady,\n }),\n ...(settings.timeoutMs !== undefined && {\n timeoutMs: settings.timeoutMs,\n }),\n },\n })\n .catch((err: unknown) => {\n removeHandler();\n throw err;\n });\n\n return node;\n}\n","import { uid } from \"radash\";\nimport {\n BaseNode,\n dispatchNodeCallbacks,\n type NodeCallbacks,\n type WorkflowHandle,\n} from \"../node.js\";\n\nexport interface GeminiTranslateSettings extends NodeCallbacks {\n /** Gemini API key. */\n apiKey: string;\n /**\n * BCP-47 target language code for the translation output\n * (e.g. `\"it\"`, `\"es\"`, `\"fr\"`).\n */\n targetLanguageCode: string;\n /** Human-readable label shown in the dashboard. */\n label?: string;\n}\n\n/** Processor node that translates audio via the Gemini Live Translate API. */\nexport class GeminiTranslateNode extends BaseNode {\n readonly settings: GeminiTranslateSettings;\n\n constructor(\n nodeId: string,\n workflow: WorkflowHandle,\n settings: GeminiTranslateSettings,\n ) {\n super(nodeId, workflow.client, workflow.workflowId);\n this.settings = settings;\n }\n}\n\nexport async function createGeminiTranslateNode(\n workflow: WorkflowHandle,\n settings: GeminiTranslateSettings,\n nodeId?: string,\n): Promise<GeminiTranslateNode> {\n const id = nodeId ?? `gemini_translate_${uid(12)}`;\n const node = new GeminiTranslateNode(id, workflow, settings);\n\n node._bindCallbacks(settings);\n const removeHandler = workflow._onNodeEvent(id, (evt) => {\n dispatchNodeCallbacks(node, evt, settings);\n });\n node._setEventCleanup(removeHandler);\n\n await workflow.client.trpc.createNode\n .mutate({\n kind: \"GEMINI_TRANSLATE\",\n workflowId: workflow.workflowId,\n nodeId: id,\n geminiTranslate: {\n apiKey: settings.apiKey,\n targetLanguageCode: settings.targetLanguageCode,\n label: settings.label,\n },\n })\n .catch((err: unknown) => {\n removeHandler();\n throw err;\n });\n\n return node;\n}\n","import type { AppRouter, HealthStatus } from \"@remavideo/server\";\nimport {\n createTRPCClient,\n httpLink,\n httpSubscriptionLink,\n splitLink,\n} from \"@trpc/client\";\nimport { EventSource } from \"eventsource\";\nimport superjson from \"superjson\";\n\nexport interface RemaClientOptions {\n host?: string;\n port?: number;\n onLogEvent?: (log: { level: string; message: string }) => void;\n onReady?: () => void;\n onFailedToConnect?: () => void;\n}\n\nexport class RemaClient {\n readonly trpc: ReturnType<typeof createTRPCClient<AppRouter>>;\n readonly options: Required<Pick<RemaClientOptions, \"host\" | \"port\">>;\n\n private constructor(\n trpc: ReturnType<typeof createTRPCClient<AppRouter>>,\n options: RemaClientOptions,\n ) {\n this.trpc = trpc;\n this.options = {\n host: options.host ?? \"127.0.0.1\",\n port: options.port ?? 8080,\n };\n }\n\n static async connect(opts: RemaClientOptions = {}): Promise<RemaClient> {\n const host = opts.host ?? \"127.0.0.1\";\n const port = opts.port ?? 8080;\n const url = `http://${host}:${port}/trpc`;\n\n const trpc = createTRPCClient<AppRouter>({\n links: [\n splitLink({\n condition: (op) => op.type === \"subscription\",\n true: httpSubscriptionLink({\n url,\n transformer: superjson,\n EventSource,\n }),\n false: httpLink({ url, transformer: superjson }),\n }),\n ],\n });\n\n try {\n await trpc.ping.query();\n opts.onReady?.();\n } catch (err) {\n opts.onFailedToConnect?.();\n throw new Error(\n `Failed to connect to rema server at ${host}:${port}: ${String(err)}`,\n );\n }\n\n return new RemaClient(trpc, opts);\n }\n\n /** No-op kept for API compatibility — SSE has no persistent connection to close. */\n close(): void {}\n\n /** Fetches the server's health status from the plain HTTP `/api/health` endpoint. */\n async health(): Promise<HealthStatus> {\n const url = `http://${this.options.host}:${this.options.port}/api/health`;\n const res = await fetch(url);\n if (!res.ok) {\n throw new Error(`Health check failed: ${res.status} ${res.statusText}`);\n }\n return (await res.json()) as HealthStatus;\n }\n}\n","import type { NodeEventMsg, WorkflowInfo } from \"@remavideo/server\";\nimport { TypedEmitter } from \"tiny-typed-emitter\";\nimport type { RemaClient } from \"./client.js\";\nimport {\n type CaptionSourceNode,\n type CaptionSourceSettings,\n createCaptionSourceNode,\n} from \"./input/caption-source.js\";\nimport {\n createFileInputNode,\n type FileInputNode,\n type FileInputSettings,\n} from \"./input/file.js\";\nimport {\n createNodeStreamInputNode,\n type NodeStreamInputNode,\n type NodeStreamInputSettings,\n} from \"./input/node-stream.js\";\nimport {\n createRtmpAnnouncerNode,\n type RtmpAnnouncerNode,\n type RtmpAnnouncerSettings,\n} from \"./input/rtmp-announcer.js\";\nimport {\n createRtmpMultiplexerNode,\n type RtmpMultiplexerNode,\n type RtmpMultiplexerSettings,\n} from \"./input/rtmp-multiplexer.js\";\nimport {\n createRtmpReaderNode,\n type RtmpReaderNode,\n type RtmpReaderSettings,\n} from \"./input/rtmp-reader.js\";\nimport {\n createSrtAnnouncerNode,\n type SrtAnnouncerNode,\n type SrtAnnouncerSettings,\n} from \"./input/srt-announcer.js\";\nimport {\n createSrtReaderNode,\n type SrtReaderNode,\n type SrtReaderSettings,\n} from \"./input/srt-reader.js\";\nimport {\n createFileOutputNode,\n type FileOutputNode,\n type FileOutputSettings,\n} from \"./output/file.js\";\nimport {\n createHlsOutputNode,\n type HlsOutputNode,\n type HlsOutputSettings,\n} from \"./output/hls.js\";\nimport {\n createRtmpOutputNode,\n type RtmpOutputNode,\n type RtmpOutputSettings,\n} from \"./output/rtmp.js\";\nimport {\n createSrtOutputNode,\n type SrtOutputNode,\n type SrtOutputSettings,\n} from \"./output/srt.js\";\nimport {\n type AudioMixerNode,\n type AudioMixerSettings,\n createAudioMixerNode,\n} from \"./processor/audio-mixer.js\";\nimport {\n type BufferNode,\n type BufferSettings,\n createBufferNode,\n} from \"./processor/buffer.js\";\nimport {\n type CambAiTranslateNode,\n type CambAiTranslateSettings,\n createCambAiTranslateNode,\n} from \"./processor/camb-ai-translate.js\";\nimport {\n type Cea608EncoderNode,\n type Cea608EncoderSettings,\n createCea608EncoderNode,\n} from \"./processor/cea608-encoder.js\";\nimport {\n createFfprobeMonitorNode,\n type FfprobeMonitorNode,\n type FfprobeMonitorSettings,\n} from \"./processor/ffprobe-monitor.js\";\nimport {\n createGateNode,\n type GateNode,\n type GateSettings,\n} from \"./processor/gate.js\";\nimport {\n createGeminiTranslateNode,\n type GeminiTranslateNode,\n type GeminiTranslateSettings,\n} from \"./processor/gemini-translate.js\";\n\nexport type { WorkflowInfo };\n\n/** Normalize a `catch`-clause/subscription value (typed `unknown`) into an `Error`. */\nfunction toError(err: unknown): Error {\n return err instanceof Error ? err : new Error(String(err));\n}\n\n/** Lifecycle callbacks available on a workflow. */\nexport interface WorkflowCallbacks {\n /**\n * Called if this workflow's worker process crashes. Workflow errors\n * otherwise only appear in server logs — this is the client-side signal.\n */\n onError?: (err: Error) => void;\n /** Called when the workflow closes, whether via `.close()` or a crash. */\n onClose?: () => void;\n}\n\n/** Events emitted by a workflow, mirroring {@link WorkflowCallbacks} for `.on()`-style consumers. */\nexport interface WorkflowEvents {\n error: (err: Error) => void;\n close: () => void;\n}\n\n/**\n * A workflow is an isolated sub-registry on the server.\n * All nodes created through a workflow handle are scoped to that workflow;\n * they cannot subscribe to nodes in other workflows.\n *\n * Obtain a workflow via `rema.workflow.create()`.\n */\nexport class Workflow extends TypedEmitter<WorkflowEvents> {\n readonly workflowId: string;\n /** Non-private: accessible to factory functions via WorkflowHandle. */\n readonly client: RemaClient;\n\n private readonly _callbacks: WorkflowCallbacks;\n private readonly _eventHandlers = new Map<\n string,\n Set<(evt: NodeEventMsg) => void>\n >();\n private _eventSub: { unsubscribe(): void } | null = null;\n private _stateSub: { unsubscribe(): void } | null = null;\n private _seenInList = false;\n private _erroredFired = false;\n private _closedFired = false;\n\n /** @internal — use `rema.workflow.create()` instead */\n constructor(\n workflowId: string,\n client: RemaClient,\n callbacks: WorkflowCallbacks = {},\n ) {\n super();\n this.workflowId = workflowId;\n this.client = client;\n this._callbacks = callbacks;\n this._startEventSubscription();\n this._startStateSubscription();\n }\n\n /**\n * Register a handler for events emitted by a specific node in this workflow.\n * Returns an unsubscribe function — call it when the node is closed.\n * @internal used by node factory functions\n */\n _onNodeEvent(\n nodeId: string,\n handler: (evt: NodeEventMsg) => void,\n ): () => void {\n let handlers = this._eventHandlers.get(nodeId);\n if (handlers === undefined) {\n handlers = new Set();\n this._eventHandlers.set(nodeId, handlers);\n }\n handlers.add(handler);\n return () => {\n this._eventHandlers.get(nodeId)?.delete(handler);\n };\n }\n\n private _startEventSubscription(): void {\n this._eventSub = this.client.trpc.workflow.events.subscribe(\n { workflowId: this.workflowId },\n {\n onData: (evt) => {\n this._eventHandlers.get(evt.nodeId)?.forEach((h) => {\n h(evt);\n });\n },\n onError: (err) => this._reportError(toError(err)),\n },\n );\n }\n\n /**\n * Watches the server's list of all workflows for this workflow's entry, so\n * `onError`/`onClose` can be derived without a dedicated per-workflow\n * subscription: a transition to `status: \"crashed\"` fires `onError`, and\n * the entry disappearing (graceful close) fires `onClose`.\n */\n private _startStateSubscription(): void {\n this._stateSub = this.client.trpc.workflow.state.subscribe(undefined, {\n onData: (workflows) => {\n const entry = workflows.find((w) => w.id === this.workflowId);\n if (entry !== undefined) {\n this._seenInList = true;\n if (entry.status === \"crashed\" && !this._erroredFired) {\n this._erroredFired = true;\n this._reportError(new Error(\"workflow worker crashed\"));\n }\n return;\n }\n if (this._seenInList) this._fireClose();\n },\n onError: (err) => this._reportError(toError(err)),\n });\n }\n\n /**\n * Report an error to this workflow's `onError` callback and its\n * `WorkflowEvents` emitter. Like `BaseNode`, `Workflow` extends Node's\n * `EventEmitter`, which throws synchronously from `emit()` when an\n * `\"error\"` event has no listener — so this only emits when someone has\n * opted in via `.on(\"error\", ...)`; the `onError` callback covers the\n * common case. The SDK does no logging of its own.\n */\n private _reportError(err: Error): void {\n this._callbacks.onError?.(err);\n if (this.listenerCount(\"error\") > 0) this.emit(\"error\", err);\n }\n\n private _fireClose(): void {\n if (this._closedFired) return;\n this._closedFired = true;\n this._eventSub?.unsubscribe();\n this._stateSub?.unsubscribe();\n this._callbacks.onClose?.();\n this.emit(\"close\");\n }\n\n // ─── Input nodes ─────────────────────────────────────────────────────────\n\n readonly input = {\n file: (\n settings: FileInputSettings,\n nodeId?: string,\n ): Promise<FileInputNode> => createFileInputNode(this, settings, nodeId),\n\n rtmpReader: (\n settings: RtmpReaderSettings,\n nodeId?: string,\n ): Promise<RtmpReaderNode> => createRtmpReaderNode(this, settings, nodeId),\n\n /** Announces RTMP publisher connections on an `app` without creating nodes. */\n rtmpAnnouncer: (\n settings: RtmpAnnouncerSettings,\n nodeId?: string,\n ): Promise<RtmpAnnouncerNode> =>\n createRtmpAnnouncerNode(this, settings, nodeId),\n\n /** Announces SRT publisher connections on an `app` without creating nodes. */\n srtAnnouncer: (\n settings: SrtAnnouncerSettings,\n nodeId?: string,\n ): Promise<SrtAnnouncerNode> =>\n createSrtAnnouncerNode(this, settings, nodeId),\n\n /** Reads an SRT stream ingested by MediaMTX, pulling it back via RTSP internally. */\n srtReader: (\n settings: SrtReaderSettings,\n nodeId?: string,\n ): Promise<SrtReaderNode> => createSrtReaderNode(this, settings, nodeId),\n\n /**\n * An RTMP multiplexer: accepts any number of simultaneous publishers under\n * the given `app`. For each connection `onStream` is called with a fresh\n * `RtmpStreamNode` that can be wired into its own encoder/output pipeline.\n */\n rtmpMultiplexer: (\n settings: RtmpMultiplexerSettings,\n nodeId?: string,\n ): Promise<RtmpMultiplexerNode> =>\n createRtmpMultiplexerNode(this, settings, nodeId),\n\n /** Format-agnostic caption schedule node. Send captions to it; wire it to a cea608() encoder. */\n captions: (\n settings: CaptionSourceSettings = {},\n nodeId?: string,\n ): Promise<CaptionSourceNode> =>\n createCaptionSourceNode(this, settings, nodeId),\n\n /**\n * Audio stream input node. Push a Node.js `Readable` (or any\n * `AsyncIterable<Buffer>`) directly into the pipeline by calling\n * `.stream(source)` on the returned node.\n */\n streamInput: (\n settings: NodeStreamInputSettings,\n nodeId?: string,\n ): Promise<NodeStreamInputNode> =>\n createNodeStreamInputNode(this, settings, nodeId),\n };\n\n // ─── Output nodes ─────────────────────────────────────────────────────────\n\n readonly output = {\n rtmp: (\n settings: RtmpOutputSettings,\n nodeId?: string,\n ): Promise<RtmpOutputNode> => createRtmpOutputNode(this, settings, nodeId),\n\n file: (\n settings: FileOutputSettings,\n nodeId?: string,\n ): Promise<FileOutputNode> => createFileOutputNode(this, settings, nodeId),\n\n /** HLS output node. Subscribe it to one or more media/subtitle sources. */\n hls: (\n settings: HlsOutputSettings,\n nodeId?: string,\n ): Promise<HlsOutputNode> => createHlsOutputNode(this, settings, nodeId),\n\n /** SRT output node. Pushes MPEG-TS over SRT to the given URL. */\n srt: (\n settings: SrtOutputSettings,\n nodeId?: string,\n ): Promise<SrtOutputNode> => createSrtOutputNode(this, settings, nodeId),\n };\n\n // ─── Processor nodes ──────────────────────────────────────────────────────\n\n readonly processor = {\n /** CEA-608/708 encoder node. Subscribe it to video + a captions() node. */\n cea608: (\n settings: Cea608EncoderSettings = {},\n nodeId?: string,\n ): Promise<Cea608EncoderNode> =>\n createCea608EncoderNode(this, settings, nodeId),\n\n /**\n * ffprobe monitor node.\n * Subscribe it to any media source; it periodically probes the live stream\n * and exposes codec/resolution/bitrate info via the `stream-info` monitor.\n */\n ffprobeMonitor: (\n settings: FfprobeMonitorSettings = {},\n nodeId?: string,\n ): Promise<FfprobeMonitorNode> =>\n createFfprobeMonitorNode(this, settings, nodeId),\n\n /**\n * Gemini Live Translate node.\n * Subscribe it to an audio/media source; it outputs translated audio-only\n * MPEG-TS using the Gemini Live Translate API.\n */\n geminiTranslate: (\n settings: GeminiTranslateSettings,\n nodeId?: string,\n ): Promise<GeminiTranslateNode> =>\n createGeminiTranslateNode(this, settings, nodeId),\n\n /**\n * Camb.ai realtime speech-to-speech translation node.\n * Subscribe it to an audio/media source; it outputs translated audio-only\n * MPEG-TS using the Camb.ai realtime translation API.\n */\n cambAiTranslate: (\n settings: CambAiTranslateSettings,\n nodeId?: string,\n ): Promise<CambAiTranslateNode> =>\n createCambAiTranslateNode(this, settings, nodeId),\n\n /**\n * Time-shift node. Subscribe it to exactly one source; it re-emits that\n * source delayed by `bufferMs`, shared across every downstream\n * subscriber.\n */\n buffer: (settings: BufferSettings, nodeId?: string): Promise<BufferNode> =>\n createBufferNode(this, settings, nodeId),\n\n /**\n * Synchronization barrier for fan-in from multiple, independently-timed\n * sources. Subscribe it to two or more sources; it withholds all of them\n * until enough (`minReady`, default: all) have produced their first\n * item, then forwards everything live.\n */\n gate: (settings: GateSettings = {}, nodeId?: string): Promise<GateNode> =>\n createGateNode(this, settings, nodeId),\n\n /**\n * Audio mixer node. Subscribe it to exactly `settings.audio.length`\n * AUDIO/ALL sources, in order — the Nth `audio[]` entry configures the\n * Nth subscribed source. Outputs a single mixed audio feed.\n */\n audioMixer: (\n settings: AudioMixerSettings,\n nodeId?: string,\n ): Promise<AudioMixerNode> => createAudioMixerNode(this, settings, nodeId),\n };\n\n // ─── Lifecycle ────────────────────────────────────────────────────────────\n\n /**\n * Close this workflow and deactivate all its nodes on the server.\n * After calling this the workflow handle is no longer usable.\n *\n * The event/state subscriptions are kept open until the close mutation\n * resolves so that the server's per-node `CLOSED` events (dispatched to\n * each node's `onClose`/`onStatusChange` callbacks) are still received,\n * rather than tearing the subscriptions down before those events arrive.\n */\n async close(): Promise<void> {\n await this.client.trpc.workflow.close.mutate({\n workflowId: this.workflowId,\n });\n this._fireClose();\n }\n}\n","import { uid } from \"radash\";\nimport {\n BaseNode,\n dispatchNodeCallbacks,\n type NodeCallbacks,\n type WorkflowHandle,\n} from \"../node.js\";\n\n/** Per-source settings for one audio input to an `AudioMixerNode`. */\nexport interface AudioMixerInputSettings {\n /** Linear gain multiplier (1 = unity, 0.5 = -6dB). Default: 1. */\n volume?: number;\n /** Silences this input entirely, regardless of `volume`. Default: false. */\n muted?: boolean;\n /** Stereo balance: -1 (full left) .. 1 (full right), 0 = center. Default: 0. */\n pan?: number;\n /** High-pass filter cutoff (Hz), applied before mixing. Omit for no high-pass. */\n highpassHz?: number;\n /** Low-pass filter cutoff (Hz), applied before mixing. Omit for no low-pass. */\n lowpassHz?: number;\n}\n\nexport interface AudioMixerSettings extends NodeCallbacks {\n /**\n * One entry per audio source this node will be subscribed to, in the same\n * order — the Nth entry configures the Nth subscribed AUDIO/ALL source.\n * Subscribe with exactly `audio.length` sources.\n */\n audio: AudioMixerInputSettings[];\n}\n\nexport class AudioMixerNode extends BaseNode {\n readonly settings: AudioMixerSettings;\n\n constructor(\n nodeId: string,\n workflow: WorkflowHandle,\n settings: AudioMixerSettings,\n ) {\n super(nodeId, workflow.client, workflow.workflowId);\n this.settings = settings;\n }\n}\n\nexport async function createAudioMixerNode(\n workflow: WorkflowHandle,\n settings: AudioMixerSettings,\n nodeId?: string,\n): Promise<AudioMixerNode> {\n const id = nodeId ?? `audio_mixer_${uid(12)}`;\n const node = new AudioMixerNode(id, workflow, settings);\n\n node._bindCallbacks(settings);\n const removeHandler = workflow._onNodeEvent(id, (evt) => {\n dispatchNodeCallbacks(node, evt, settings);\n });\n node._setEventCleanup(removeHandler);\n\n await workflow.client.trpc.createNode\n .mutate({\n kind: \"AUDIO_MIXER\",\n workflowId: workflow.workflowId,\n nodeId: id,\n audioMixer: {\n audio: settings.audio.map((a) => ({\n volume: a.volume ?? 1,\n muted: a.muted ?? false,\n pan: a.pan ?? 0,\n ...(a.highpassHz !== undefined ? { highpassHz: a.highpassHz } : {}),\n ...(a.lowpassHz !== undefined ? { lowpassHz: a.lowpassHz } : {}),\n })),\n },\n })\n .catch((err: unknown) => {\n removeHandler();\n throw err;\n });\n\n return node;\n}\n","import type { HealthStatus, WorkflowInfo } from \"@remavideo/server\";\nimport { RemaClient, type RemaClientOptions } from \"./client.js\";\nimport { Workflow, type WorkflowCallbacks } from \"./workflow.js\";\n\nexport class Rema {\n private readonly client: RemaClient;\n private readonly _autoCloseWorkflows = new Set<Workflow>();\n\n private constructor(client: RemaClient) {\n this.client = client;\n }\n\n static async connect(opts: RemaClientOptions = {}): Promise<Rema> {\n const client = await RemaClient.connect(opts);\n return new Rema(client);\n }\n\n // ─── Workflow management ──────────────────────────────────────────────────\n\n readonly workflow = {\n /**\n * Create a new isolated workflow on the server.\n *\n * All nodes created through the returned `Workflow` handle are scoped to\n * this workflow; they cannot subscribe to nodes in other workflows.\n *\n * @param opts.name Human-readable label shown in the web UI.\n * @param opts.workflowId Optional stable ID. Server generates one if omitted.\n * @param opts.closeOnDisconnect When true, this workflow is closed automatically\n * when `rema.close()`/`wf.close()` is called, or\n * when the connection to the server is lost (e.g.\n * the process crashes) for more than a few seconds.\n * @param opts.parentId ID of an existing workflow to attach this one to as\n * a child (shown nested in the web UI).\n * @param opts.closeWithParent When true, this workflow is closed automatically\n * when its parent workflow is closed.\n * @param opts.onError Called if this workflow's worker process crashes.\n * @param opts.onClose Called when the workflow closes, whether via\n * `.close()` or a crash.\n */\n create: async (\n opts: {\n name?: string;\n workflowId?: string;\n closeOnDisconnect?: boolean;\n parentId?: string;\n closeWithParent?: boolean;\n } & WorkflowCallbacks = {},\n ): Promise<Workflow> => {\n const info = await this.client.trpc.workflow.create.mutate({\n workflowId: opts.workflowId,\n name: opts.name,\n parentId: opts.parentId,\n closeWithParent: opts.closeWithParent,\n closeOnDisconnect: opts.closeOnDisconnect,\n });\n const wf = new Workflow(info.id, this.client, {\n ...(opts.onError !== undefined && { onError: opts.onError }),\n ...(opts.onClose !== undefined && { onClose: opts.onClose }),\n });\n if (opts.closeOnDisconnect === true) {\n this._autoCloseWorkflows.add(wf);\n }\n return wf;\n },\n\n /** List all active workflows on the server. */\n list: (): Promise<WorkflowInfo[]> => this.client.trpc.workflow.list.query(),\n };\n\n /** Fetches the server's current health status (uptime, workflow counts, MediaMTX reachability). */\n health(): Promise<HealthStatus> {\n return this.client.health();\n }\n\n // ─── Lifecycle ────────────────────────────────────────────────────────────\n\n /**\n * Close all `closeOnDisconnect` workflows, then disconnect from the server.\n */\n async close(): Promise<void> {\n await Promise.allSettled(\n [...this._autoCloseWorkflows].map((wf) => wf.close()),\n );\n this.client.close();\n }\n}\n","/**\n * Stream selectors — used when calling node.subscribe() to pick which\n * streams flow from source to subscriber.\n */\n\nexport type StreamSelector = \"VIDEO\" | \"AUDIO\" | \"CAPTIONS\" | \"ALL\";\n\n// Narrowly typed (`as const`) so a spec literal like\n// `{ sourceSelector: selectAudio, audio: {...} }` discriminates the\n// SubscriptionSpec union correctly.\nexport const selectVideo = \"VIDEO\" as const;\nexport const selectAudio = \"AUDIO\" as const;\nexport const selectCaptions = \"CAPTIONS\" as const;\nexport const selectAll = \"ALL\" as const;\n"],"mappings":";AAAA,SAAS,gBAAgB;;;ACMzB,SAAS,oBAAoB;AAoB7B,IAAM,cAAc,oBAAI,IAAe;AAAA,EACrC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,CAAC;AAeM,SAAS,gBACd,MACA,WACA,KACM;AACN,YAAU,UAAU,GAAG;AACvB,MAAI,KAAK,cAAc,OAAO,IAAI,EAAG,MAAK,KAAK,SAAS,GAAG;AAC7D;AAOO,SAAS,sBACd,MACA,KACA,WACM;AACN,MAAI,YAAY,IAAI,IAAI,IAAiB,GAAG;AAC1C,UAAM,QAAQ,IAAI;AAClB,cAAU,iBAAiB,OAAO,IAAI,OAAO;AAC7C,SAAK,KAAK,gBAAgB,OAAO,IAAI,OAAO;AAAA,EAC9C;AACA,MAAI,IAAI,SAAS,SAAS;AACxB,oBAAgB,MAAM,WAAW,IAAI,MAAM,IAAI,WAAW,YAAY,CAAC;AAAA,EACzE;AACA,MAAI,IAAI,SAAS,UAAU;AACzB,cAAU,UAAU;AACpB,SAAK,KAAK,OAAO;AAAA,EACnB;AACF;AAiJO,IAAe,WAAf,MAAe,kBAAiB,aAAyB;AAAA,EACrD;AAAA,EACA;AAAA,EACU;AAAA,EACX,gBAAqC;AAAA,EACrC,aAA4B,CAAC;AAAA,EAErC,YAAY,QAAgB,QAAoB,YAAoB;AAClE,UAAM;AACN,SAAK,SAAS;AACd,SAAK,aAAa;AAClB,SAAK,SAAS;AAAA,EAChB;AAAA;AAAA,EAGA,iBAAiB,IAAsB;AACrC,SAAK,gBAAgB;AAAA,EACvB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,eAAe,WAAgC;AAC7C,SAAK,aAAa;AAAA,EACpB;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,MAAM,UACJ,OACA,SACe;AACf,UAAM,KAAK,OAAO,KAAK,cAAc,OAAO;AAAA,MAC1C,YAAY,KAAK;AAAA,MACjB,QAAQ,KAAK;AAAA,MACb,SAAS,MAAM,IAAI,CAAC,MAAM,UAAS,eAAe,CAAC,CAAC;AAAA,MACpD,MAAM,SAAS;AAAA,IACjB,CAAC;AAAA,EACH;AAAA;AAAA,EAGA,OAAe,eAAe,MAAwB;AACpD,UAAM,OAAO;AAAA,MACX,cAAc,KAAK,OAAO;AAAA,MAC1B,GAAI,KAAK,YAAY,SAAY,EAAE,SAAS,KAAK,QAAQ,IAAI,CAAC;AAAA,MAC9D,GAAI,KAAK,SAAS,SAAY,EAAE,MAAM,KAAK,KAAK,IAAI,CAAC;AAAA,MACrD,GAAI,KAAK,SAAS,SAAY,EAAE,MAAM,KAAK,KAAK,IAAI,CAAC;AAAA,IACvD;AACA,UAAM,QAAQ,CAAC,OAA8B;AAAA,MAC3C,UAAU,EAAE,YAAY;AAAA,MACxB,OAAO,EAAE,SAAS;AAAA,MAClB,cAAc,EAAE,gBAAgB;AAAA,IAClC;AAGA,UAAM,QAAQ,CAAC,MACb,EAAE,UAAU,SAAY,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC;AAE3D,YAAQ,KAAK,gBAAgB;AAAA,MAC3B,KAAK;AACH,eAAO,EAAE,GAAG,MAAM,UAAU,WAAoB;AAAA,MAClD,KAAK;AACH,eAAO;AAAA,UACL,GAAG;AAAA,UACH,UAAU;AAAA,UACV,GAAI,KAAK,UAAU,SAAY,EAAE,OAAO,KAAK,MAAM,IAAI,CAAC;AAAA,UACxD,GAAI,KAAK,UAAU,SAAY,EAAE,OAAO,KAAK,MAAM,IAAI,CAAC;AAAA,UACxD,GAAI,KAAK,UAAU,SAAY,MAAM,KAAK,KAAK,IAAI,CAAC;AAAA,QACtD;AAAA,MACF,KAAK;AACH,eAAO;AAAA,UACL,GAAG;AAAA,UACH,UAAU;AAAA,UACV,GAAI,KAAK,UAAU,SAAY,EAAE,OAAO,KAAK,MAAM,IAAI,CAAC;AAAA,UACxD,GAAI,KAAK,UAAU,SAAY,EAAE,OAAO,KAAK,MAAM,IAAI,CAAC;AAAA,UACxD,GAAI,KAAK,UAAU,SAAY,EAAE,OAAO,MAAM,KAAK,KAAK,EAAE,IAAI,CAAC;AAAA,QACjE;AAAA,MACF,KAAK;AACH,eAAO;AAAA,UACL,GAAG;AAAA,UACH,UAAU;AAAA,UACV,GAAI,KAAK,eAAe,SACpB,EAAE,YAAY,KAAK,WAAW,IAC9B,CAAC;AAAA,UACL,GAAI,KAAK,eAAe,SACpB,EAAE,YAAY,KAAK,WAAW,IAC9B,CAAC;AAAA,UACL,GAAI,KAAK,UAAU,SAAY,MAAM,KAAK,KAAK,IAAI,CAAC;AAAA,UACpD,GAAI,KAAK,UAAU,SAAY,EAAE,OAAO,MAAM,KAAK,KAAK,EAAE,IAAI,CAAC;AAAA,QACjE;AAAA,IACJ;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAqBA,MAAM,OAAO,SAA6D;AACxE,WAAO,KAAK,OAAO,KAAK,YAAY,MAAM;AAAA,MACxC,YAAY,KAAK;AAAA,MACjB,QAAQ,KAAK;AAAA,MACb,GAAI,SAAS,mBAAmB,SAC5B,EAAE,gBAAgB,QAAQ,eAAe,IACzC,CAAC;AAAA,IACP,CAAC;AAAA,EACH;AAAA;AAAA,EAGA,MAAM,QAAuB;AAC3B,SAAK,gBAAgB;AACrB,UAAM,KAAK,OAAO,KAAK,UAAU,OAAO;AAAA,MACtC,YAAY,KAAK;AAAA,MACjB,QAAQ,KAAK;AAAA,IACf,CAAC;AACD,SAAK,WAAW,UAAU;AAC1B,SAAK,KAAK,OAAO;AAAA,EACnB;AACF;;;ADhUO,IAAM,oBAAN,cAAgC,SAAS;AAAA,EACrC;AAAA,EAET,YACE,QACA,UACA,UACA;AACA,UAAM,QAAQ,SAAS,QAAQ,SAAS,UAAU;AAClD,SAAK,WAAW;AAAA,EAClB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAcA,MAAM,KACJ,MACA,SACe;AACf,UAAM,KAAK,OAAO,KAAK,YAAY,OAAO;AAAA,MACxC,YAAY,KAAK;AAAA,MACjB,QAAQ,KAAK;AAAA,MACb;AAAA,MACA,YAAY,QAAQ;AAAA,MACpB,WAAW,QAAQ,WAAW;AAAA,IAChC,CAAC;AAAA,EACH;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAUA,MAAM,SAAS,SAA6C;AAC1D,WAAO,KAAK,OAAO,KAAK,gBAAgB,OAAO;AAAA,MAC7C,YAAY,KAAK;AAAA,MACjB,QAAQ,KAAK;AAAA,MACb;AAAA,IACF,CAAC;AAAA,EACH;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASA,MAAM,eAAe,UAA8C;AACjE,UAAM,UAAU,MAAM,SAAS,UAAU,OAAO;AAChD,WAAO,KAAK,SAAS,OAAO;AAAA,EAC9B;AACF;AAEA,eAAsB,wBACpB,UACA,WAAkC,CAAC,GACnC,QAC4B;AAC5B,QAAM,KAAK,UAAU,kBAAkB,KAAK,IAAI,CAAC;AACjD,QAAM,OAAO,IAAI,kBAAkB,IAAI,UAAU,QAAQ;AAEzD,OAAK,eAAe,QAAQ;AAC5B,QAAM,gBAAgB,SAAS,aAAa,IAAI,CAAC,QAAQ;AACvD,0BAAsB,MAAM,KAAK,QAAQ;AAAA,EAC3C,CAAC;AACD,OAAK,iBAAiB,aAAa;AAEnC,QAAM,SAAS,OAAO,KAAK,WACxB,OAAO;AAAA,IACN,MAAM;AAAA,IACN,YAAY,SAAS;AAAA,IACrB,QAAQ;AAAA,IACR,eAAe;AAAA,MACb,aAAa,SAAS,eAAe;AAAA,MACrC,UAAU,SAAS,YAAY;AAAA,MAC/B,OAAO,SAAS,SAAS;AAAA,MACzB,cAAc,SAAS,gBAAgB;AAAA,MACvC,GAAI,SAAS,kBAAkB,UAAa;AAAA,QAC1C,eAAe,SAAS;AAAA,MAC1B;AAAA,IACF;AAAA,EACF,CAAC,EACA,MAAM,CAAC,QAAiB;AACvB,kBAAc;AACd,UAAM;AAAA,EACR,CAAC;AAEH,SAAO;AACT;;;AE5IA,SAAS,WAAW;AA0Cb,IAAM,gBAAN,cAA4B,SAAS;AAAA,EACjC;AAAA,EAET,YACE,QACA,UACA,UACA;AACA,UAAM,QAAQ,SAAS,QAAQ,SAAS,UAAU;AAClD,SAAK,WAAW;AAAA,EAClB;AAAA;AAAA,EAGA,MAAM,SAAwB;AAC5B,UAAM,KAAK,OAAO,KAAK,WAAW,OAAO;AAAA,MACvC,YAAY,KAAK;AAAA,MACjB,QAAQ,KAAK;AAAA,IACf,CAAC;AAAA,EACH;AACF;AAEA,eAAsB,oBACpB,UACA,UACA,QACwB;AACxB,QAAM,KAAK,UAAU,cAAc,IAAI,EAAE,CAAC;AAC1C,QAAM,OAAO,IAAI,cAAc,IAAI,UAAU,QAAQ;AAErD,OAAK,eAAe,QAAQ;AAC5B,QAAM,gBAAgB,SAAS,aAAa,IAAI,CAAC,QAAQ;AACvD,QAAI,IAAI,SAAS,QAAS,UAAS,UAAU;AAC7C,QAAI,IAAI,SAAS,YAAY,IAAI,YAAY;AAC3C,eAAS,gBAAgB,OAAO,IAAI,OAAO,CAAC;AAC9C,QAAI,IAAI,SAAS,QAAS,UAAS,UAAU;AAC7C,0BAAsB,MAAM,KAAK,QAAQ;AAAA,EAC3C,CAAC;AACD,OAAK,iBAAiB,aAAa;AAEnC,QAAM,SAAS,OAAO,KAAK,WACxB,OAAO;AAAA,IACN,MAAM;AAAA,IACN,YAAY,SAAS;AAAA,IACrB,QAAQ;AAAA,IACR,WAAW;AAAA,MACT,UAAU,SAAS;AAAA,MACnB,MAAM,SAAS,QAAQ;AAAA,MACvB,YAAY,SAAS;AAAA,MACrB,UAAU,SAAS,YAAY;AAAA,IACjC;AAAA,EACF,CAAC,EACA,MAAM,CAAC,QAAiB;AACvB,kBAAc;AACd,UAAM;AAAA,EACR,CAAC;AAEH,SAAO;AACT;;;ACnGA,SAAS,OAAAA,YAAW;AAuCb,IAAM,sBAAN,cAAkC,SAAS;AAAA,EACvC;AAAA,EACQ;AAAA,EAEjB,YACE,QACA,UACA,UACA;AACA,UAAM,QAAQ,SAAS,QAAQ,SAAS,UAAU;AAClD,SAAK,WAAW;AAChB,UAAM,EAAE,MAAM,KAAK,IAAI,SAAS,OAAO;AACvC,SAAK,WAAW,UAAU,IAAI,IAAI,IAAI;AAAA,EACxC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAuBA,MAAM,OAAO,QAA2D;AACtE,UAAM,MAAM,GAAG,KAAK,QAAQ,oBAAoB,KAAK,UAAU,IAAI,KAAK,MAAM;AAE9E,UAAM,OAAO,IAAI,eAA2B;AAAA,MAC1C,MAAM,MAAM,YAAY;AACtB,YAAI;AACF,2BAAiB,SAAS,QAAQ;AAChC,uBAAW;AAAA,cACT,iBAAiB,aAAa,QAAQ,IAAI,WAAW,KAAK;AAAA,YAC5D;AAAA,UACF;AACA,qBAAW,MAAM;AAAA,QACnB,SAAS,KAAK;AACZ,qBAAW,MAAM,GAAG;AAAA,QACtB;AAAA,MACF;AAAA,IACF,CAAC;AAED,UAAM,MAAM,KAAK;AAAA,MACf,QAAQ;AAAA,MACR;AAAA;AAAA,MAEA,QAAQ;AAAA,IACV,CAAgB;AAAA,EAClB;AACF;AAEA,eAAsB,0BACpB,UACA,UACA,QAC8B;AAC9B,QAAM,KAAK,UAAU,eAAeC,KAAI,EAAE,CAAC;AAC3C,QAAM,OAAO,IAAI,oBAAoB,IAAI,UAAU,QAAQ;AAE3D,OAAK,eAAe,QAAQ;AAC5B,QAAM,gBAAgB,SAAS,aAAa,IAAI,CAAC,QAAQ;AACvD,QAAI,IAAI,SAAS,QAAS,UAAS,UAAU;AAC7C,QAAI,IAAI,SAAS,YAAY,IAAI,YAAY;AAC3C,eAAS,gBAAgB,OAAO,IAAI,OAAO,CAAC;AAC9C,QAAI,IAAI,SAAS,QAAS,UAAS,UAAU;AAC7C,0BAAsB,MAAM,KAAK,QAAQ;AAAA,EAC3C,CAAC;AACD,OAAK,iBAAiB,aAAa;AAEnC,QAAM,SAAS,OAAO,KAAK,WACxB,OAAO;AAAA,IACN,MAAM;AAAA,IACN,YAAY,SAAS;AAAA,IACrB,QAAQ;AAAA,IACR,iBAAiB;AAAA,MACf,QAAQ,SAAS;AAAA,MACjB,GAAI,SAAS,eAAe,UAAa;AAAA,QACvC,YAAY,SAAS;AAAA,MACvB;AAAA,MACA,GAAI,SAAS,aAAa,UAAa;AAAA,QACrC,UAAU,SAAS;AAAA,MACrB;AAAA,MACA,YAAY,SAAS,cAAc;AAAA,MACnC,GAAI,SAAS,UAAU,UAAa,EAAE,OAAO,SAAS,MAAM;AAAA,IAC9D;AAAA,EACF,CAAC,EACA,MAAM,CAAC,QAAiB;AACvB,kBAAc;AACd,UAAM;AAAA,EACR,CAAC;AAEH,SAAO;AACT;;;AC/IA,SAAS,OAAAC,YAAW;AA6Cb,IAAM,oBAAN,cAAgC,SAAS;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAM9C,MAAM,iBAAiB,cAAqC;AAC1D,UAAM,KAAK,OAAO,KAAK,iBAAiB,OAAO;AAAA,MAC7C,YAAY,KAAK;AAAA,MACjB,QAAQ,KAAK;AAAA,MACb;AAAA,IACF,CAAC;AAAA,EACH;AACF;AAEA,eAAsB,wBACpB,UACA,UACA,QAC4B;AAC5B,QAAM,KAAK,UAAU,kBAAkBC,KAAI,EAAE,CAAC;AAC9C,QAAM,MAAM,SAAS,OAAO;AAC5B,QAAM,OAAO,IAAI,kBAAkB,IAAI,SAAS,QAAQ,SAAS,UAAU;AAC3E,QAAM,aAAsC;AAAA,IAC1C,kBAAkB,CAAC,iBAAiB,KAAK,iBAAiB,YAAY;AAAA,EACxE;AAEA,OAAK,eAAe,QAAQ;AAC5B,QAAM,gBAAgB,SAAS,aAAa,IAAI,CAAC,QAAQ;AACvD,QAAI,IAAI,SAAS,sBAAsB,IAAI,YAAY,QAAW;AAChE,YAAM,OAAO,KAAK,MAAM,IAAI,OAAO;AACnC,eAAS,UAAU,MAAM,UAAU;AAAA,IACrC;AACA,QAAI,IAAI,SAAS,yBAAyB,IAAI,YAAY,QAAW;AACnE,YAAM,OAAO,KAAK,MAAM,IAAI,OAAO;AACnC,eAAS,eAAe,IAAI;AAAA,IAC9B;AACA,0BAAsB,MAAM,KAAK,QAAQ;AAAA,EAC3C,CAAC;AACD,OAAK,iBAAiB,aAAa;AAEnC,QAAM,SAAS,OAAO,KAAK,WACxB,OAAO;AAAA,IACN,MAAM;AAAA,IACN,YAAY,SAAS;AAAA,IACrB,QAAQ;AAAA,IACR,eAAe,EAAE,IAAI;AAAA,EACvB,CAAC,EACA,MAAM,CAAC,QAAiB;AACvB,kBAAc;AACd,UAAM;AAAA,EACR,CAAC;AAEH,SAAO;AACT;;;ACnGA,SAAS,OAAAC,YAAW;AAWpB,SAAS,QAAQ,KAAqB;AACpC,SAAO,eAAe,QAAQ,MAAM,IAAI,MAAM,OAAO,GAAG,CAAC;AAC3D;AAWO,IAAM,iBAAN,cAA6B,SAAS;AAAA,EAClC;AAAA,EACA;AAAA,EAET,YACE,QACA,QACA,KACA,YACA,YACA;AACA,UAAM,QAAQ,QAAQ,UAAU;AAChC,SAAK,MAAM;AACX,SAAK,aAAa;AAAA,EACpB;AACF;AAUO,IAAM,sBAAN,cAAkC,SAAS;AAAC;AAoCnD,eAAsB,0BACpB,UACA,UACA,QAC8B;AAC9B,QAAM,KAAK,UAAU,YAAYC,KAAI,EAAE,CAAC;AACxC,QAAM,MAAM,SAAS,OAAO;AAC5B,QAAM,OAAO,IAAI;AAAA,IACf;AAAA,IACA,SAAS;AAAA,IACT,SAAS;AAAA,EACX;AAGA,QAAM,gBAAgB,oBAAI,IAA4B;AAEtD,OAAK,eAAe,QAAQ;AAC5B,QAAM,gBAAgB,SAAS,aAAa,IAAI,CAAC,QAAQ;AACvD,0BAAsB,MAAM,KAAK,QAAQ;AACzC,QAAI,IAAI,SAAS,mBAAmB,IAAI,YAAY,QAAW;AAC7D,YAAM,EAAE,aAAa,WAAW,IAAI,KAAK,MAAM,IAAI,OAAO;AAK1D,cAAQ,QAAQ,SAAS,YAAY,EAAE,KAAK,WAAW,CAAC,CAAC,EACtD,KAAK,OAAO,WAAW;AACtB,YAAI,QAAQ,WAAW,OAAO;AAC5B,gBAAM,SAAS,OAAO,KAAK,gBACxB,OAAO;AAAA,YACN,YAAY,SAAS;AAAA,YACrB,QAAQ;AAAA,YACR;AAAA,UACF,CAAC,EACA,MAAM,CAAC,QAAiB;AACvB,4BAAgB,MAAM,UAAU,QAAQ,GAAG,CAAC;AAAA,UAC9C,CAAC;AACH;AAAA,QACF;AACA,cAAM,aAAa,IAAI;AAAA,UACrB;AAAA,UACA,SAAS;AAAA,UACT;AAAA,UACA;AAAA,UACA,SAAS;AAAA,QACX;AACA,sBAAc,IAAI,YAAY,UAAU;AACxC,cAAM,SAAS,SAAS,UAAU;AAAA,MACpC,CAAC,EACA,MAAM,CAAC,QAAiB;AACvB,wBAAgB,MAAM,UAAU,QAAQ,GAAG,CAAC;AAAA,MAC9C,CAAC;AACH;AAAA,IACF;AAEA,QAAI,IAAI,SAAS,kBAAkB,IAAI,YAAY,QAAW;AAC5D,YAAM,EAAE,WAAW,IAAI,KAAK,MAAM,IAAI,OAAO;AAC7C,YAAM,aAAa,cAAc,IAAI,UAAU;AAC/C,oBAAc,OAAO,UAAU;AAC/B,UAAI,YAAY;AACd,gBAAQ,QAAQ,SAAS,cAAc,UAAU,CAAC,EAAE;AAAA,UAClD,CAAC,QAAiB;AAChB,4BAAgB,MAAM,UAAU,QAAQ,GAAG,CAAC;AAAA,UAC9C;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAAA,EACF,CAAC;AACD,OAAK,iBAAiB,aAAa;AAEnC,QAAM,SAAS,OAAO,KAAK,WACxB,OAAO;AAAA,IACN,MAAM;AAAA,IACN,YAAY,SAAS;AAAA,IACrB,QAAQ;AAAA,IACR,iBAAiB,EAAE,KAAK,MAAM,KAAK;AAAA,EACrC,CAAC,EACA,MAAM,CAAC,QAAiB;AACvB,kBAAc;AACd,UAAM;AAAA,EACR,CAAC;AAEH,SAAO;AACT;;;ACxKA,SAAS,OAAAC,YAAW;AAqCb,IAAM,iBAAN,cAA6B,SAAS;AAAA,EAClC;AAAA,EAET,YACE,QACA,UACA,UACA;AACA,UAAM,QAAQ,SAAS,QAAQ,SAAS,UAAU;AAClD,SAAK,WAAW;AAAA,EAClB;AACF;AAEA,eAAsB,qBACpB,UACA,UACA,QACyB;AACzB,QAAM,KAAK,UAAU,eAAeC,KAAI,EAAE,CAAC;AAC3C,QAAM,OAAO,IAAI,eAAe,IAAI,UAAU,QAAQ;AAEtD,OAAK,eAAe,QAAQ;AAC5B,QAAM,gBAAgB,SAAS,aAAa,IAAI,CAAC,QAAQ;AACvD,QAAI,IAAI,SAAS,SAAS;AACxB,eAAS,YAAY;AAAA,QACnB,KAAK,SAAS;AAAA,QACd,aAAa,IAAI,WAAW;AAAA,MAC9B,CAAC;AAAA,IACH;AACA,QAAI,IAAI,SAAS,YAAY,IAAI,YAAY,QAAW;AACtD,eAAS,gBAAgB,OAAO,IAAI,OAAO,CAAC;AAAA,IAC9C;AACA,QAAI,IAAI,SAAS,QAAS,UAAS,gBAAgB;AACnD,0BAAsB,MAAM,KAAK,QAAQ;AAAA,EAC3C,CAAC;AACD,OAAK,iBAAiB,aAAa;AAEnC,QAAM,SAAS,OAAO,KAAK,WACxB,OAAO;AAAA,IACN,MAAM;AAAA,IACN,YAAY,SAAS;AAAA,IACrB,QAAQ;AAAA,IACR,YAAY;AAAA,MACV,KAAK,SAAS;AAAA,MACd,YAAY,SAAS;AAAA,MACrB,GAAI,SAAS,UAAU,UAAa,EAAE,OAAO,SAAS,MAAM;AAAA,IAC9D;AAAA,EACF,CAAC,EACA,MAAM,CAAC,QAAiB;AACvB,kBAAc;AACd,UAAM;AAAA,EACR,CAAC;AAEH,SAAO;AACT;;;AC3FA,SAAS,OAAAC,YAAW;AAqDb,IAAM,mBAAN,cAA+B,SAAS;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAM7C,MAAM,iBAAiB,cAAqC;AAC1D,UAAM,KAAK,OAAO,KAAK,iBAAiB,OAAO;AAAA,MAC7C,YAAY,KAAK;AAAA,MACjB,QAAQ,KAAK;AAAA,MACb;AAAA,IACF,CAAC;AAAA,EACH;AACF;AAEA,eAAsB,uBACpB,UACA,UACA,QAC2B;AAC3B,QAAM,KAAK,UAAU,iBAAiBC,KAAI,EAAE,CAAC;AAC7C,QAAM,MAAM,SAAS,OAAO;AAC5B,QAAM,OAAO,IAAI,iBAAiB,IAAI,SAAS,QAAQ,SAAS,UAAU;AAC1E,QAAM,aAAqC;AAAA,IACzC,kBAAkB,CAAC,iBAAiB,KAAK,iBAAiB,YAAY;AAAA,EACxE;AAEA,OAAK,eAAe,QAAQ;AAC5B,QAAM,gBAAgB,SAAS,aAAa,IAAI,CAAC,QAAQ;AACvD,QAAI,IAAI,SAAS,sBAAsB,IAAI,YAAY,QAAW;AAChE,YAAM,OAAO,KAAK,MAAM,IAAI,OAAO;AACnC,eAAS,UAAU,MAAM,UAAU;AAAA,IACrC;AACA,QAAI,IAAI,SAAS,yBAAyB,IAAI,YAAY,QAAW;AACnE,YAAM,OAAO,KAAK,MAAM,IAAI,OAAO;AAInC,eAAS,eAAe,IAAI;AAAA,IAC9B;AACA,0BAAsB,MAAM,KAAK,QAAQ;AAAA,EAC3C,CAAC;AACD,OAAK,iBAAiB,aAAa;AAEnC,QAAM,SAAS,OAAO,KAAK,WACxB,OAAO;AAAA,IACN,MAAM;AAAA,IACN,YAAY,SAAS;AAAA,IACrB,QAAQ;AAAA,IACR,cAAc;AAAA,MACZ;AAAA,MACA,GAAI,SAAS,eAAe,UAAa;AAAA,QACvC,YAAY,SAAS;AAAA,MACvB;AAAA,MACA,GAAI,SAAS,cAAc,UAAa;AAAA,QACtC,WAAW,SAAS;AAAA,MACtB;AAAA,IACF;AAAA,EACF,CAAC,EACA,MAAM,CAAC,QAAiB;AACvB,kBAAc;AACd,UAAM;AAAA,EACR,CAAC;AAEH,SAAO;AACT;;;ACtHA,SAAS,OAAAC,YAAW;AAsCb,IAAM,gBAAN,cAA4B,SAAS;AAAA,EACjC;AAAA,EAET,YACE,QACA,UACA,UACA;AACA,UAAM,QAAQ,SAAS,QAAQ,SAAS,UAAU;AAClD,SAAK,WAAW;AAAA,EAClB;AACF;AAEA,eAAsB,oBACpB,UACA,UACA,QACwB;AACxB,QAAM,KAAK,UAAU,cAAcC,KAAI,EAAE,CAAC;AAC1C,QAAM,OAAO,IAAI,cAAc,IAAI,UAAU,QAAQ;AAErD,OAAK,eAAe,QAAQ;AAC5B,QAAM,gBAAgB,SAAS,aAAa,IAAI,CAAC,QAAQ;AACvD,QAAI,IAAI,SAAS,SAAS;AACxB,eAAS,YAAY;AAAA,QACnB,KAAK,SAAS;AAAA,QACd,aAAa,IAAI,WAAW;AAAA,MAC9B,CAAC;AAAA,IACH;AACA,QAAI,IAAI,SAAS,YAAY,IAAI,YAAY,QAAW;AACtD,eAAS,gBAAgB,OAAO,IAAI,OAAO,CAAC;AAAA,IAC9C;AACA,QAAI,IAAI,SAAS,QAAS,UAAS,gBAAgB;AACnD,0BAAsB,MAAM,KAAK,QAAQ;AAAA,EAC3C,CAAC;AACD,OAAK,iBAAiB,aAAa;AAEnC,QAAM,SAAS,OAAO,KAAK,WACxB,OAAO;AAAA,IACN,MAAM;AAAA,IACN,YAAY,SAAS;AAAA,IACrB,QAAQ;AAAA,IACR,WAAW;AAAA,MACT,KAAK,SAAS;AAAA,MACd,YAAY,SAAS;AAAA,MACrB,GAAI,SAAS,UAAU,UAAa,EAAE,OAAO,SAAS,MAAM;AAAA,IAC9D;AAAA,EACF,CAAC,EACA,MAAM,CAAC,QAAiB;AACvB,kBAAc;AACd,UAAM;AAAA,EACR,CAAC;AAEH,SAAO;AACT;;;AC5FA,SAAS,OAAAC,YAAW;AAiBb,IAAM,iBAAN,cAA6B,SAAS;AAAA,EAClC;AAAA,EAET,YACE,QACA,UACA,UACA;AACA,UAAM,QAAQ,SAAS,QAAQ,SAAS,UAAU;AAClD,SAAK,WAAW;AAAA,EAClB;AACF;AAEA,eAAsB,qBACpB,UACA,UACA,QACyB;AACzB,QAAM,KAAK,UAAU,eAAeC,KAAI,EAAE,CAAC;AAC3C,QAAM,OAAO,IAAI,eAAe,IAAI,UAAU,QAAQ;AAEtD,OAAK,eAAe,QAAQ;AAC5B,QAAM,gBAAgB,SAAS,aAAa,IAAI,CAAC,QAAQ;AACvD,0BAAsB,MAAM,KAAK,QAAQ;AAAA,EAC3C,CAAC;AACD,OAAK,iBAAiB,aAAa;AAEnC,QAAM,SAAS,OAAO,KAAK,WACxB,OAAO;AAAA,IACN,MAAM;AAAA,IACN,YAAY,SAAS;AAAA,IACrB,QAAQ;AAAA,IACR,YAAY;AAAA,MACV,UAAU,SAAS;AAAA,MACnB,QAAQ,SAAS,UAAU;AAAA,MAC3B,WAAW,SAAS,aAAa;AAAA,IACnC;AAAA,EACF,CAAC,EACA,MAAM,CAAC,QAAiB;AACvB,kBAAc;AACd,UAAM;AAAA,EACR,CAAC;AAEH,SAAO;AACT;;;AC7DA,SAAS,OAAAC,YAAW;AA8Eb,IAAM,gBAAN,cAA4B,SAAS;AAAA,EACjC;AAAA,EAET,YACE,QACA,UACA,UACA;AACA,UAAM,QAAQ,SAAS,QAAQ,SAAS,UAAU;AAClD,SAAK,WAAW;AAAA,EAClB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,MAAM,iBAAoC;AACxC,UAAM,SAAS,MAAM,KAAK,OAAO,KAAK,cAAc,MAAM;AAAA,MACxD,YAAY,KAAK;AAAA,MACjB,QAAQ,KAAK;AAAA,IACf,CAAC;AACD,WAAO,OAAO;AAAA,EAChB;AACF;AAEA,eAAsB,oBACpB,UACA,UACA,QACwB;AACxB,QAAM,KAAK,UAAU,cAAcC,KAAI,EAAE,CAAC;AAC1C,QAAM,OAAO,IAAI,cAAc,IAAI,UAAU,QAAQ;AAErD,OAAK,eAAe,QAAQ;AAC5B,QAAM,gBAAgB,SAAS,aAAa,IAAI,CAAC,QAAQ;AACvD,0BAAsB,MAAM,KAAK,QAAQ;AAAA,EAC3C,CAAC;AACD,OAAK,iBAAiB,aAAa;AAEnC,QAAM,SAAS,OAAO,KAAK,WACxB,OAAO;AAAA,IACN,MAAM;AAAA,IACN,YAAY,SAAS;AAAA,IACrB,QAAQ;AAAA,IACR,WAAW;AAAA,MACT,cAAc,SAAS;AAAA,MACvB,aAAa,SAAS,eAAe;AAAA,MACrC,UAAU,SAAS,YAAY;AAAA,MAC/B,YAAY,SAAS,cAAc;AAAA,MACnC,YAAY,SAAS,cAAc;AAAA,IACrC;AAAA,EACF,CAAC,EACA,MAAM,CAAC,QAAiB;AACvB,kBAAc;AACd,UAAM;AAAA,EACR,CAAC;AAEH,SAAO;AACT;;;AC1IA,SAAS,OAAAC,aAAW;AAsBb,IAAM,iBAAN,cAA6B,SAAS;AAAA,EAClC;AAAA,EAET,YACE,QACA,UACA,UACA;AACA,UAAM,QAAQ,SAAS,QAAQ,SAAS,UAAU;AAClD,SAAK,WAAW;AAAA,EAClB;AACF;AAEA,eAAsB,qBACpB,UACA,UACA,QACyB;AACzB,QAAM,KAAK,UAAU,eAAeC,MAAI,EAAE,CAAC;AAC3C,QAAM,OAAO,IAAI,eAAe,IAAI,UAAU,QAAQ;AAEtD,OAAK,eAAe,QAAQ;AAC5B,QAAM,gBAAgB,SAAS,aAAa,IAAI,CAAC,QAAQ;AACvD,0BAAsB,MAAM,KAAK,QAAQ;AAAA,EAC3C,CAAC;AACD,OAAK,iBAAiB,aAAa;AAEnC,QAAM,SAAS,OAAO,KAAK,WACxB,OAAO;AAAA,IACN,MAAM;AAAA,IACN,YAAY,SAAS;AAAA,IACrB,QAAQ;AAAA,IACR,YAAY,EAAE,KAAK,SAAS,IAAI;AAAA,EAClC,CAAC,EACA,MAAM,CAAC,QAAiB;AACvB,kBAAc;AACd,UAAM;AAAA,EACR,CAAC;AAEH,SAAO;AACT;;;AC9DA,SAAS,OAAAC,aAAW;AA0Bb,IAAM,gBAAN,cAA4B,SAAS;AAAA,EACjC;AAAA,EAET,YACE,QACA,UACA,UACA;AACA,UAAM,QAAQ,SAAS,QAAQ,SAAS,UAAU;AAClD,SAAK,WAAW;AAAA,EAClB;AACF;AAEA,eAAsB,oBACpB,UACA,UACA,QACwB;AACxB,QAAM,KAAK,UAAU,cAAcC,MAAI,EAAE,CAAC;AAC1C,QAAM,OAAO,IAAI,cAAc,IAAI,UAAU,QAAQ;AAErD,OAAK,eAAe,QAAQ;AAC5B,QAAM,gBAAgB,SAAS,aAAa,IAAI,CAAC,QAAQ;AACvD,0BAAsB,MAAM,KAAK,QAAQ;AAAA,EAC3C,CAAC;AACD,OAAK,iBAAiB,aAAa;AAEnC,QAAM,SAAS,OAAO,KAAK,WACxB,OAAO;AAAA,IACN,MAAM;AAAA,IACN,YAAY,SAAS;AAAA,IACrB,QAAQ;AAAA,IACR,WAAW,EAAE,KAAK,SAAS,IAAI;AAAA,EACjC,CAAC,EACA,MAAM,CAAC,QAAiB;AACvB,kBAAc;AACd,UAAM;AAAA,EACR,CAAC;AAEH,SAAO;AACT;;;AClEA,SAAS,OAAAC,aAAW;AAkBb,IAAM,aAAN,cAAyB,SAAS;AAAA,EAC9B;AAAA,EAET,YACE,QACA,UACA,UACA;AACA,UAAM,QAAQ,SAAS,QAAQ,SAAS,UAAU;AAClD,SAAK,WAAW;AAAA,EAClB;AACF;AAGA,eAAsB,iBACpB,UACA,UACA,QACqB;AACrB,QAAM,KAAK,UAAU,UAAUC,MAAI,EAAE,CAAC;AACtC,QAAM,OAAO,IAAI,WAAW,IAAI,UAAU,QAAQ;AAElD,OAAK,eAAe,QAAQ;AAC5B,QAAM,gBAAgB,SAAS,aAAa,IAAI,CAAC,QAAQ;AACvD,0BAAsB,MAAM,KAAK,QAAQ;AAAA,EAC3C,CAAC;AACD,OAAK,iBAAiB,aAAa;AAEnC,QAAM,SAAS,OAAO,KAAK,WACxB,OAAO;AAAA,IACN,MAAM;AAAA,IACN,YAAY,SAAS;AAAA,IACrB,QAAQ;AAAA,IACR,QAAQ,EAAE,UAAU,SAAS,SAAS;AAAA,EACxC,CAAC,EACA,MAAM,CAAC,QAAiB;AACvB,kBAAc;AACd,UAAM;AAAA,EACR,CAAC;AAEH,SAAO;AACT;;;AC3DA,SAAS,OAAAC,aAAW;AAwBb,IAAM,sBAAN,cAAkC,SAAS;AAAA,EACvC;AAAA,EAET,YACE,QACA,UACA,UACA;AACA,UAAM,QAAQ,SAAS,QAAQ,SAAS,UAAU;AAClD,SAAK,WAAW;AAAA,EAClB;AACF;AAEA,eAAsB,0BACpB,UACA,UACA,QAC8B;AAC9B,QAAM,KAAK,UAAU,kBAAkBC,MAAI,EAAE,CAAC;AAC9C,QAAM,OAAO,IAAI,oBAAoB,IAAI,UAAU,QAAQ;AAE3D,OAAK,eAAe,QAAQ;AAC5B,QAAM,gBAAgB,SAAS,aAAa,IAAI,CAAC,QAAQ;AACvD,0BAAsB,MAAM,KAAK,QAAQ;AAAA,EAC3C,CAAC;AACD,OAAK,iBAAiB,aAAa;AAEnC,QAAM,SAAS,OAAO,KAAK,WACxB,OAAO;AAAA,IACN,MAAM;AAAA,IACN,YAAY,SAAS;AAAA,IACrB,QAAQ;AAAA,IACR,iBAAiB;AAAA,MACf,QAAQ,SAAS;AAAA,MACjB,oBAAoB,SAAS;AAAA,MAC7B,oBAAoB,SAAS;AAAA,MAC7B,OAAO,SAAS;AAAA,MAChB,SAAS,SAAS;AAAA,MAClB,OAAO,SAAS;AAAA,IAClB;AAAA,EACF,CAAC,EACA,MAAM,CAAC,QAAiB;AACvB,kBAAc;AACd,UAAM;AAAA,EACR,CAAC;AAEH,SAAO;AACT;;;ACvEA,SAAS,OAAAC,aAAW;AAmCb,IAAM,oBAAN,cAAgC,SAAS;AAAA,EACrC;AAAA,EAET,YACE,QACA,UACA,UACA;AACA,UAAM,QAAQ,SAAS,QAAQ,SAAS,UAAU;AAClD,SAAK,WAAW;AAAA,EAClB;AACF;AAEA,eAAsB,wBACpB,UACA,WAAkC,CAAC,GACnC,QAC4B;AAC5B,QAAM,KAAK,UAAU,UAAUC,MAAI,EAAE,CAAC;AACtC,QAAM,OAAO,IAAI,kBAAkB,IAAI,UAAU,QAAQ;AAEzD,OAAK,eAAe,QAAQ;AAC5B,QAAM,gBAAgB,SAAS,aAAa,IAAI,CAAC,QAAQ;AACvD,0BAAsB,MAAM,KAAK,QAAQ;AAAA,EAC3C,CAAC;AACD,OAAK,iBAAiB,aAAa;AAEnC,QAAM,SAAS,OAAO,KAAK,WACxB,OAAO;AAAA,IACN,MAAM;AAAA,IACN,YAAY,SAAS;AAAA,IACrB,QAAQ;AAAA,IACR,eAAe;AAAA,MACb,MAAM,SAAS,QAAQ;AAAA,MACvB,MAAM,SAAS,QAAQ;AAAA,MACvB,UAAU,SAAS,YAAY;AAAA,IACjC;AAAA,EACF,CAAC,EACA,MAAM,CAAC,QAAiB;AACvB,kBAAc;AACd,UAAM;AAAA,EACR,CAAC;AAEH,SAAO;AACT;;;AC9EA,SAAS,OAAAC,aAAW;AA0Bb,IAAM,qBAAN,cAAiC,SAAS;AAAA,EACtC;AAAA,EAET,YACE,QACA,UACA,UACA;AACA,UAAM,QAAQ,SAAS,QAAQ,SAAS,UAAU;AAClD,SAAK,WAAW;AAAA,EAClB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAaA,QACE,aACA,QACA,SACyB;AACzB,WAAO,KAAK,OAAO,KAAK,YAAY;AAAA,MAClC,EAAE,YAAY,KAAK,YAAY,QAAQ,KAAK,QAAQ,YAAY;AAAA,MAChE,EAAE,QAAQ,GAAI,YAAY,UAAa,EAAE,QAAQ,EAAG;AAAA,IACtD;AAAA,EACF;AACF;AAEA,eAAsB,yBACpB,UACA,WAAmC,CAAC,GACpC,QAC6B;AAC7B,QAAM,KAAK,UAAU,WAAWC,MAAI,EAAE,CAAC;AACvC,QAAM,OAAO,IAAI,mBAAmB,IAAI,UAAU,QAAQ;AAE1D,OAAK,eAAe,QAAQ;AAC5B,QAAM,gBAAgB,SAAS,aAAa,IAAI,CAAC,QAAQ;AACvD,0BAAsB,MAAM,KAAK,QAAQ;AAAA,EAC3C,CAAC;AACD,OAAK,iBAAiB,aAAa;AAEnC,QAAM,SAAS,OAAO,KAAK,WACxB,OAAO;AAAA,IACN,MAAM;AAAA,IACN,YAAY,SAAS;AAAA,IACrB,QAAQ;AAAA,IACR,gBAAgB;AAAA,MACd,YAAY,SAAS,cAAc;AAAA,MACnC,gBAAgB,SAAS,kBAAkB;AAAA,IAC7C;AAAA,EACF,CAAC,EACA,MAAM,CAAC,QAAiB;AACvB,kBAAc;AACd,UAAM;AAAA,EACR,CAAC;AAEH,SAAO;AACT;;;AC5FA,SAAS,OAAAC,aAAW;AAoDb,IAAM,WAAN,cAAuB,SAAS;AAAA,EAC5B;AAAA,EAET,YACE,QACA,UACA,UACA;AACA,UAAM,QAAQ,SAAS,QAAQ,SAAS,UAAU;AAClD,SAAK,WAAW;AAAA,EAClB;AACF;AAGA,eAAsB,eACpB,UACA,WAAyB,CAAC,GAC1B,QACmB;AACnB,QAAM,KAAK,UAAU,QAAQC,MAAI,EAAE,CAAC;AACpC,QAAM,OAAO,IAAI,SAAS,IAAI,UAAU,QAAQ;AAEhD,OAAK,eAAe,QAAQ;AAC5B,QAAM,gBAAgB,SAAS,aAAa,IAAI,CAAC,QAAQ;AACvD,0BAAsB,MAAM,KAAK,QAAQ;AAAA,EAC3C,CAAC;AACD,OAAK,iBAAiB,aAAa;AAEnC,QAAM,SAAS,OAAO,KAAK,WACxB,OAAO;AAAA,IACN,MAAM;AAAA,IACN,YAAY,SAAS;AAAA,IACrB,QAAQ;AAAA,IACR,MAAM;AAAA,MACJ,GAAI,SAAS,aAAa,UAAa;AAAA,QACrC,UAAU,SAAS;AAAA,MACrB;AAAA,MACA,GAAI,SAAS,cAAc,UAAa;AAAA,QACtC,WAAW,SAAS;AAAA,MACtB;AAAA,IACF;AAAA,EACF,CAAC,EACA,MAAM,CAAC,QAAiB;AACvB,kBAAc;AACd,UAAM;AAAA,EACR,CAAC;AAEH,SAAO;AACT;;;ACpGA,SAAS,OAAAC,aAAW;AAqBb,IAAM,sBAAN,cAAkC,SAAS;AAAA,EACvC;AAAA,EAET,YACE,QACA,UACA,UACA;AACA,UAAM,QAAQ,SAAS,QAAQ,SAAS,UAAU;AAClD,SAAK,WAAW;AAAA,EAClB;AACF;AAEA,eAAsB,0BACpB,UACA,UACA,QAC8B;AAC9B,QAAM,KAAK,UAAU,oBAAoBC,MAAI,EAAE,CAAC;AAChD,QAAM,OAAO,IAAI,oBAAoB,IAAI,UAAU,QAAQ;AAE3D,OAAK,eAAe,QAAQ;AAC5B,QAAM,gBAAgB,SAAS,aAAa,IAAI,CAAC,QAAQ;AACvD,0BAAsB,MAAM,KAAK,QAAQ;AAAA,EAC3C,CAAC;AACD,OAAK,iBAAiB,aAAa;AAEnC,QAAM,SAAS,OAAO,KAAK,WACxB,OAAO;AAAA,IACN,MAAM;AAAA,IACN,YAAY,SAAS;AAAA,IACrB,QAAQ;AAAA,IACR,iBAAiB;AAAA,MACf,QAAQ,SAAS;AAAA,MACjB,oBAAoB,SAAS;AAAA,MAC7B,OAAO,SAAS;AAAA,IAClB;AAAA,EACF,CAAC,EACA,MAAM,CAAC,QAAiB;AACvB,kBAAc;AACd,UAAM;AAAA,EACR,CAAC;AAEH,SAAO;AACT;;;AChEA;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AACP,SAAS,mBAAmB;AAC5B,OAAO,eAAe;AAUf,IAAM,aAAN,MAAM,YAAW;AAAA,EACb;AAAA,EACA;AAAA,EAED,YACN,MACA,SACA;AACA,SAAK,OAAO;AACZ,SAAK,UAAU;AAAA,MACb,MAAM,QAAQ,QAAQ;AAAA,MACtB,MAAM,QAAQ,QAAQ;AAAA,IACxB;AAAA,EACF;AAAA,EAEA,aAAa,QAAQ,OAA0B,CAAC,GAAwB;AACtE,UAAM,OAAO,KAAK,QAAQ;AAC1B,UAAM,OAAO,KAAK,QAAQ;AAC1B,UAAM,MAAM,UAAU,IAAI,IAAI,IAAI;AAElC,UAAM,OAAO,iBAA4B;AAAA,MACvC,OAAO;AAAA,QACL,UAAU;AAAA,UACR,WAAW,CAAC,OAAO,GAAG,SAAS;AAAA,UAC/B,MAAM,qBAAqB;AAAA,YACzB;AAAA,YACA,aAAa;AAAA,YACb;AAAA,UACF,CAAC;AAAA,UACD,OAAO,SAAS,EAAE,KAAK,aAAa,UAAU,CAAC;AAAA,QACjD,CAAC;AAAA,MACH;AAAA,IACF,CAAC;AAED,QAAI;AACF,YAAM,KAAK,KAAK,MAAM;AACtB,WAAK,UAAU;AAAA,IACjB,SAAS,KAAK;AACZ,WAAK,oBAAoB;AACzB,YAAM,IAAI;AAAA,QACR,uCAAuC,IAAI,IAAI,IAAI,KAAK,OAAO,GAAG,CAAC;AAAA,MACrE;AAAA,IACF;AAEA,WAAO,IAAI,YAAW,MAAM,IAAI;AAAA,EAClC;AAAA;AAAA,EAGA,QAAc;AAAA,EAAC;AAAA;AAAA,EAGf,MAAM,SAAgC;AACpC,UAAM,MAAM,UAAU,KAAK,QAAQ,IAAI,IAAI,KAAK,QAAQ,IAAI;AAC5D,UAAM,MAAM,MAAM,MAAM,GAAG;AAC3B,QAAI,CAAC,IAAI,IAAI;AACX,YAAM,IAAI,MAAM,wBAAwB,IAAI,MAAM,IAAI,IAAI,UAAU,EAAE;AAAA,IACxE;AACA,WAAQ,MAAM,IAAI,KAAK;AAAA,EACzB;AACF;;;AC5EA,SAAS,gBAAAC,qBAAoB;;;ACD7B,SAAS,OAAAC,aAAW;AA+Bb,IAAM,iBAAN,cAA6B,SAAS;AAAA,EAClC;AAAA,EAET,YACE,QACA,UACA,UACA;AACA,UAAM,QAAQ,SAAS,QAAQ,SAAS,UAAU;AAClD,SAAK,WAAW;AAAA,EAClB;AACF;AAEA,eAAsB,qBACpB,UACA,UACA,QACyB;AACzB,QAAM,KAAK,UAAU,eAAeC,MAAI,EAAE,CAAC;AAC3C,QAAM,OAAO,IAAI,eAAe,IAAI,UAAU,QAAQ;AAEtD,OAAK,eAAe,QAAQ;AAC5B,QAAM,gBAAgB,SAAS,aAAa,IAAI,CAAC,QAAQ;AACvD,0BAAsB,MAAM,KAAK,QAAQ;AAAA,EAC3C,CAAC;AACD,OAAK,iBAAiB,aAAa;AAEnC,QAAM,SAAS,OAAO,KAAK,WACxB,OAAO;AAAA,IACN,MAAM;AAAA,IACN,YAAY,SAAS;AAAA,IACrB,QAAQ;AAAA,IACR,YAAY;AAAA,MACV,OAAO,SAAS,MAAM,IAAI,CAAC,OAAO;AAAA,QAChC,QAAQ,EAAE,UAAU;AAAA,QACpB,OAAO,EAAE,SAAS;AAAA,QAClB,KAAK,EAAE,OAAO;AAAA,QACd,GAAI,EAAE,eAAe,SAAY,EAAE,YAAY,EAAE,WAAW,IAAI,CAAC;AAAA,QACjE,GAAI,EAAE,cAAc,SAAY,EAAE,WAAW,EAAE,UAAU,IAAI,CAAC;AAAA,MAChE,EAAE;AAAA,IACJ;AAAA,EACF,CAAC,EACA,MAAM,CAAC,QAAiB;AACvB,kBAAc;AACd,UAAM;AAAA,EACR,CAAC;AAEH,SAAO;AACT;;;ADuBA,SAASC,SAAQ,KAAqB;AACpC,SAAO,eAAe,QAAQ,MAAM,IAAI,MAAM,OAAO,GAAG,CAAC;AAC3D;AA0BO,IAAM,WAAN,cAAuBC,cAA6B;AAAA,EAChD;AAAA;AAAA,EAEA;AAAA,EAEQ;AAAA,EACA,iBAAiB,oBAAI,IAGpC;AAAA,EACM,YAA4C;AAAA,EAC5C,YAA4C;AAAA,EAC5C,cAAc;AAAA,EACd,gBAAgB;AAAA,EAChB,eAAe;AAAA;AAAA,EAGvB,YACE,YACA,QACA,YAA+B,CAAC,GAChC;AACA,UAAM;AACN,SAAK,aAAa;AAClB,SAAK,SAAS;AACd,SAAK,aAAa;AAClB,SAAK,wBAAwB;AAC7B,SAAK,wBAAwB;AAAA,EAC/B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,aACE,QACA,SACY;AACZ,QAAI,WAAW,KAAK,eAAe,IAAI,MAAM;AAC7C,QAAI,aAAa,QAAW;AAC1B,iBAAW,oBAAI,IAAI;AACnB,WAAK,eAAe,IAAI,QAAQ,QAAQ;AAAA,IAC1C;AACA,aAAS,IAAI,OAAO;AACpB,WAAO,MAAM;AACX,WAAK,eAAe,IAAI,MAAM,GAAG,OAAO,OAAO;AAAA,IACjD;AAAA,EACF;AAAA,EAEQ,0BAAgC;AACtC,SAAK,YAAY,KAAK,OAAO,KAAK,SAAS,OAAO;AAAA,MAChD,EAAE,YAAY,KAAK,WAAW;AAAA,MAC9B;AAAA,QACE,QAAQ,CAAC,QAAQ;AACf,eAAK,eAAe,IAAI,IAAI,MAAM,GAAG,QAAQ,CAAC,MAAM;AAClD,cAAE,GAAG;AAAA,UACP,CAAC;AAAA,QACH;AAAA,QACA,SAAS,CAAC,QAAQ,KAAK,aAAaD,SAAQ,GAAG,CAAC;AAAA,MAClD;AAAA,IACF;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQQ,0BAAgC;AACtC,SAAK,YAAY,KAAK,OAAO,KAAK,SAAS,MAAM,UAAU,QAAW;AAAA,MACpE,QAAQ,CAAC,cAAc;AACrB,cAAM,QAAQ,UAAU,KAAK,CAAC,MAAM,EAAE,OAAO,KAAK,UAAU;AAC5D,YAAI,UAAU,QAAW;AACvB,eAAK,cAAc;AACnB,cAAI,MAAM,WAAW,aAAa,CAAC,KAAK,eAAe;AACrD,iBAAK,gBAAgB;AACrB,iBAAK,aAAa,IAAI,MAAM,yBAAyB,CAAC;AAAA,UACxD;AACA;AAAA,QACF;AACA,YAAI,KAAK,YAAa,MAAK,WAAW;AAAA,MACxC;AAAA,MACA,SAAS,CAAC,QAAQ,KAAK,aAAaA,SAAQ,GAAG,CAAC;AAAA,IAClD,CAAC;AAAA,EACH;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAUQ,aAAa,KAAkB;AACrC,SAAK,WAAW,UAAU,GAAG;AAC7B,QAAI,KAAK,cAAc,OAAO,IAAI,EAAG,MAAK,KAAK,SAAS,GAAG;AAAA,EAC7D;AAAA,EAEQ,aAAmB;AACzB,QAAI,KAAK,aAAc;AACvB,SAAK,eAAe;AACpB,SAAK,WAAW,YAAY;AAC5B,SAAK,WAAW,YAAY;AAC5B,SAAK,WAAW,UAAU;AAC1B,SAAK,KAAK,OAAO;AAAA,EACnB;AAAA;AAAA,EAIS,QAAQ;AAAA,IACf,MAAM,CACJ,UACA,WAC2B,oBAAoB,MAAM,UAAU,MAAM;AAAA,IAEvE,YAAY,CACV,UACA,WAC4B,qBAAqB,MAAM,UAAU,MAAM;AAAA;AAAA,IAGzE,eAAe,CACb,UACA,WAEA,wBAAwB,MAAM,UAAU,MAAM;AAAA;AAAA,IAGhD,cAAc,CACZ,UACA,WAEA,uBAAuB,MAAM,UAAU,MAAM;AAAA;AAAA,IAG/C,WAAW,CACT,UACA,WAC2B,oBAAoB,MAAM,UAAU,MAAM;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAOvE,iBAAiB,CACf,UACA,WAEA,0BAA0B,MAAM,UAAU,MAAM;AAAA;AAAA,IAGlD,UAAU,CACR,WAAkC,CAAC,GACnC,WAEA,wBAAwB,MAAM,UAAU,MAAM;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAOhD,aAAa,CACX,UACA,WAEA,0BAA0B,MAAM,UAAU,MAAM;AAAA,EACpD;AAAA;AAAA,EAIS,SAAS;AAAA,IAChB,MAAM,CACJ,UACA,WAC4B,qBAAqB,MAAM,UAAU,MAAM;AAAA,IAEzE,MAAM,CACJ,UACA,WAC4B,qBAAqB,MAAM,UAAU,MAAM;AAAA;AAAA,IAGzE,KAAK,CACH,UACA,WAC2B,oBAAoB,MAAM,UAAU,MAAM;AAAA;AAAA,IAGvE,KAAK,CACH,UACA,WAC2B,oBAAoB,MAAM,UAAU,MAAM;AAAA,EACzE;AAAA;AAAA,EAIS,YAAY;AAAA;AAAA,IAEnB,QAAQ,CACN,WAAkC,CAAC,GACnC,WAEA,wBAAwB,MAAM,UAAU,MAAM;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAOhD,gBAAgB,CACd,WAAmC,CAAC,GACpC,WAEA,yBAAyB,MAAM,UAAU,MAAM;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAOjD,iBAAiB,CACf,UACA,WAEA,0BAA0B,MAAM,UAAU,MAAM;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAOlD,iBAAiB,CACf,UACA,WAEA,0BAA0B,MAAM,UAAU,MAAM;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAOlD,QAAQ,CAAC,UAA0B,WACjC,iBAAiB,MAAM,UAAU,MAAM;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAQzC,MAAM,CAAC,WAAyB,CAAC,GAAG,WAClC,eAAe,MAAM,UAAU,MAAM;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAOvC,YAAY,CACV,UACA,WAC4B,qBAAqB,MAAM,UAAU,MAAM;AAAA,EAC3E;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAaA,MAAM,QAAuB;AAC3B,UAAM,KAAK,OAAO,KAAK,SAAS,MAAM,OAAO;AAAA,MAC3C,YAAY,KAAK;AAAA,IACnB,CAAC;AACD,SAAK,WAAW;AAAA,EAClB;AACF;;;AE7ZO,IAAM,OAAN,MAAM,MAAK;AAAA,EACC;AAAA,EACA,sBAAsB,oBAAI,IAAc;AAAA,EAEjD,YAAY,QAAoB;AACtC,SAAK,SAAS;AAAA,EAChB;AAAA,EAEA,aAAa,QAAQ,OAA0B,CAAC,GAAkB;AAChE,UAAM,SAAS,MAAM,WAAW,QAAQ,IAAI;AAC5C,WAAO,IAAI,MAAK,MAAM;AAAA,EACxB;AAAA;AAAA,EAIS,WAAW;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAqBlB,QAAQ,OACN,OAMwB,CAAC,MACH;AACtB,YAAM,OAAO,MAAM,KAAK,OAAO,KAAK,SAAS,OAAO,OAAO;AAAA,QACzD,YAAY,KAAK;AAAA,QACjB,MAAM,KAAK;AAAA,QACX,UAAU,KAAK;AAAA,QACf,iBAAiB,KAAK;AAAA,QACtB,mBAAmB,KAAK;AAAA,MAC1B,CAAC;AACD,YAAM,KAAK,IAAI,SAAS,KAAK,IAAI,KAAK,QAAQ;AAAA,QAC5C,GAAI,KAAK,YAAY,UAAa,EAAE,SAAS,KAAK,QAAQ;AAAA,QAC1D,GAAI,KAAK,YAAY,UAAa,EAAE,SAAS,KAAK,QAAQ;AAAA,MAC5D,CAAC;AACD,UAAI,KAAK,sBAAsB,MAAM;AACnC,aAAK,oBAAoB,IAAI,EAAE;AAAA,MACjC;AACA,aAAO;AAAA,IACT;AAAA;AAAA,IAGA,MAAM,MAA+B,KAAK,OAAO,KAAK,SAAS,KAAK,MAAM;AAAA,EAC5E;AAAA;AAAA,EAGA,SAAgC;AAC9B,WAAO,KAAK,OAAO,OAAO;AAAA,EAC5B;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,MAAM,QAAuB;AAC3B,UAAM,QAAQ;AAAA,MACZ,CAAC,GAAG,KAAK,mBAAmB,EAAE,IAAI,CAAC,OAAO,GAAG,MAAM,CAAC;AAAA,IACtD;AACA,SAAK,OAAO,MAAM;AAAA,EACpB;AACF;;;AC5EO,IAAM,cAAc;AACpB,IAAM,cAAc;AACpB,IAAM,iBAAiB;AACvB,IAAM,YAAY;","names":["uid","uid","uid","uid","uid","uid","uid","uid","uid","uid","uid","uid","uid","uid","uid","uid","uid","uid","uid","uid","uid","uid","uid","uid","uid","uid","uid","uid","uid","uid","uid","uid","TypedEmitter","uid","uid","toError","TypedEmitter"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@remavideo/sdk",
3
- "version": "0.13.0",
3
+ "version": "0.15.0",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",
@@ -32,7 +32,7 @@
32
32
  "tsx": "^4.21.0",
33
33
  "typescript": "^5.8.3",
34
34
  "vitest": "^3.0.0",
35
- "@remavideo/server": "0.13.0"
35
+ "@remavideo/server": "0.15.0"
36
36
  },
37
37
  "scripts": {
38
38
  "build": "tsup",