inngest
Version:
Official SDK for Inngest.com. Inngest is the reliability layer for modern applications. Inngest combines durable execution, events, and queues into a zero-infra platform with built-in observability.
1 lines • 22.6 kB
Source Map (JSON)
{"version":3,"file":"TokenSubscription.cjs","names":["StreamFanout","#apiBaseUrl","#signingKey","#signingKeyFallback","#validate","#getSubscriptionToken","#channelId","#topics","url: URL","#debug","createDeferredPromise","#running","#ws","parsedJson: unknown","Realtime","#fanout","streamId: unknown","#chunkStreams","endStreamId: unknown","#encoder"],"sources":["../../../../src/components/realtime/subscribe/TokenSubscription.ts"],"sourcesContent":["import type { StandardSchemaV1 } from \"@standard-schema/spec\";\nimport debug from \"debug\";\nimport { createDeferredPromise } from \"../../../helpers/promises.ts\";\nimport { Realtime } from \"../types.ts\";\nimport { StreamFanout } from \"./StreamFanout.ts\";\n\nconst extractSchema = (topicEntry: unknown): StandardSchemaV1 | undefined => {\n if (!topicEntry || typeof topicEntry !== \"object\") {\n return undefined;\n }\n\n if (\"schema\" in topicEntry && topicEntry.schema) {\n return topicEntry.schema as StandardSchemaV1;\n }\n\n return undefined;\n};\n\nexport interface TokenSubscriptionOptions {\n token: Realtime.Subscribe.Token;\n apiBaseUrl?: string;\n signingKey?: string;\n signingKeyFallback?: string;\n validate?: boolean;\n\n //\n // When provided, used for lazy token retrieval instead of env-based lookup\n getSubscriptionToken?: (channel: string, topics: string[]) => Promise<string>;\n}\n\nexport class TokenSubscription {\n #apiBaseUrl?: string;\n #channelId: string;\n #debug = debug(\"inngest:realtime\");\n #encoder = new TextEncoder();\n #fanout = new StreamFanout<Realtime.Message>();\n #running = false;\n #topics: Map<string, unknown>;\n #ws: WebSocket | null = null;\n #signingKey: string | undefined;\n #signingKeyFallback: string | undefined;\n #validate: boolean;\n #getSubscriptionToken?: (\n channel: string,\n topics: string[],\n ) => Promise<string>;\n\n #chunkStreams = new Map<\n string,\n { stream: ReadableStream; controller: ReadableStreamDefaultController }\n >();\n\n public token: Realtime.Subscribe.Token;\n\n constructor(options: TokenSubscriptionOptions) {\n this.token = options.token;\n this.#apiBaseUrl = options.apiBaseUrl;\n this.#signingKey = options.signingKey;\n this.#signingKeyFallback = options.signingKeyFallback;\n this.#validate = options.validate ?? true;\n this.#getSubscriptionToken = options.getSubscriptionToken;\n\n const channel = this.token.channel;\n\n if (typeof channel === \"string\") {\n this.#channelId = channel;\n\n //\n // String channel — no topic definitions available, store empty entries.\n // Schema validation will be skipped for these topics.\n this.#topics = new Map(\n this.token.topics.map((name) => [name, undefined]),\n );\n } else {\n this.#channelId = channel.name;\n\n //\n // Channel object — store the topic config for optional schema validation\n // on received messages.\n this.#topics = new Map(\n this.token.topics.map((name) => [\n name,\n (\n channel.topics as Record<string, Realtime.TopicConfig | undefined>\n )?.[name],\n ]),\n );\n }\n }\n\n private getWsUrl(token: string): URL {\n const path = \"/v1/realtime/connect\";\n\n let url: URL;\n\n if (this.#apiBaseUrl) {\n url = new URL(path, this.#apiBaseUrl);\n } else {\n url = new URL(path, \"https://api.inngest.com/\");\n }\n\n url.protocol = url.protocol === \"http:\" ? \"ws:\" : \"wss:\";\n url.searchParams.set(\"token\", token);\n\n return url;\n }\n\n private isExpectedChannel(channel: string): boolean {\n if (channel === this.#channelId) {\n return true;\n }\n\n this.#debug(\n `Received message for unexpected channel \"${channel}\" (expected \"${this.#channelId}\")`,\n );\n return false;\n }\n\n public async connect() {\n this.#debug(\n `Establishing connection to channel \"${\n this.#channelId\n }\" with topics ${JSON.stringify([...this.#topics.keys()])}...`,\n );\n\n if (typeof WebSocket === \"undefined\") {\n throw new Error(\"WebSockets not supported in current environment\");\n }\n\n let key = this.token.key;\n if (!key) {\n this.#debug(\n \"No subscription token key passed; attempting to retrieve one automatically...\",\n );\n\n key = await this.lazilyGetSubscriptionToken();\n\n if (!key) {\n throw new Error(\n \"No subscription token key passed and failed to retrieve one automatically\",\n );\n }\n }\n\n const ret = createDeferredPromise<void>();\n let isConnectSettled = false;\n let hasOpened = false;\n\n const resolveConnect = () => {\n if (isConnectSettled) {\n return;\n }\n isConnectSettled = true;\n ret.resolve();\n };\n\n const rejectConnect = (err: unknown) => {\n if (isConnectSettled) {\n return;\n }\n isConnectSettled = true;\n ret.reject(err);\n };\n\n try {\n this.#running = true;\n this.#ws = new WebSocket(this.getWsUrl(key));\n\n this.#ws.onopen = () => {\n this.#debug(\"WebSocket connection established\");\n hasOpened = true;\n resolveConnect();\n };\n\n this.#ws.onmessage = async (event) => {\n let parsedJson: unknown;\n try {\n parsedJson = JSON.parse(event.data as string);\n } catch (err) {\n this.#debug(\"Received non-JSON message:\", err);\n return;\n }\n\n const parseRes =\n await Realtime.messageSchema.safeParseAsync(parsedJson);\n\n if (!parseRes.success) {\n this.#debug(\"Received invalid message:\", parseRes.error);\n return;\n }\n\n const msg = parseRes.data;\n\n if (!this.#running) {\n this.#debug(\n `Received message on channel \"${msg.channel}\" for topic \"${msg.topic}\" but stream is closed`,\n );\n return;\n }\n\n switch (msg.kind) {\n case \"data\": {\n if (!msg.channel) {\n this.#debug(\n `Received message on channel \"${msg.channel}\" with no channel`,\n );\n return;\n }\n\n if (!this.isExpectedChannel(msg.channel)) {\n return;\n }\n\n if (!msg.topic) {\n this.#debug(\n `Received message on channel \"${msg.channel}\" with no topic`,\n );\n return;\n }\n\n if (!this.#topics.has(msg.topic)) {\n this.#debug(\n `Received message on channel \"${msg.channel}\" for unknown topic \"${msg.topic}\"`,\n );\n return;\n }\n\n const dataTopic = this.#topics.get(msg.topic);\n const schema = extractSchema(dataTopic);\n if (this.#validate && schema) {\n const validateRes = await schema[\"~standard\"].validate(msg.data);\n if (validateRes.issues) {\n console.error(\n `Received message on channel \"${msg.channel}\" for topic \"${msg.topic}\" that failed schema validation:`,\n validateRes.issues,\n );\n return;\n }\n\n msg.data = validateRes.value;\n }\n\n this.#debug(\n `Received message on channel \"${msg.channel}\" for topic \"${msg.topic}\":`,\n msg.data,\n );\n return this.#fanout.write({\n channel: msg.channel,\n topic: msg.topic,\n data: msg.data,\n fnId: msg.fn_id,\n createdAt: msg.created_at || new Date(),\n runId: msg.run_id,\n kind: \"data\",\n envId: msg.env_id,\n });\n }\n\n case \"run\": {\n if (msg.channel && !this.isExpectedChannel(msg.channel)) {\n return;\n }\n\n this.#debug(`Received run lifecycle message on \"${msg.channel}\"`);\n return this.#fanout.write({\n channel: msg.channel,\n topic: msg.topic,\n data: msg.data,\n fnId: msg.fn_id,\n createdAt: msg.created_at || new Date(),\n runId: msg.run_id,\n kind: \"run\",\n envId: msg.env_id,\n });\n }\n\n case \"datastream-start\": {\n if (!msg.channel || !msg.topic) {\n this.#debug(\n `Received message on channel \"${msg.channel}\" with no channel or topic`,\n );\n return;\n }\n\n if (!this.isExpectedChannel(msg.channel)) {\n return;\n }\n\n const streamId: unknown = msg.data;\n if (typeof streamId !== \"string\" || !streamId) {\n this.#debug(\n `Received message on channel \"${msg.channel}\" with no stream ID`,\n );\n return;\n }\n\n if (this.#chunkStreams.has(streamId)) {\n this.#debug(\n `Received message on channel \"${msg.channel}\" to create stream ID \"${streamId}\" that already exists`,\n );\n return;\n }\n\n const stream = new ReadableStream({\n start: (controller) => {\n this.#chunkStreams.set(streamId, { stream, controller });\n },\n cancel: () => {\n this.#chunkStreams.delete(streamId);\n },\n });\n\n this.#debug(\n `Created stream ID \"${streamId}\" on channel \"${msg.channel}\"`,\n );\n return this.#fanout.write({\n channel: msg.channel,\n topic: msg.topic,\n kind: \"datastream-start\",\n data: streamId,\n streamId,\n fnId: msg.fn_id,\n runId: msg.run_id,\n stream,\n });\n }\n\n case \"datastream-end\": {\n if (!msg.channel || !msg.topic) {\n this.#debug(\n `Received message on channel \"${msg.channel}\" with no channel or topic`,\n );\n return;\n }\n\n if (!this.isExpectedChannel(msg.channel)) {\n return;\n }\n\n const endStreamId: unknown = msg.data;\n if (typeof endStreamId !== \"string\" || !endStreamId) {\n this.#debug(\n `Received message on channel \"${msg.channel}\" with no stream ID`,\n );\n return;\n }\n\n const endStream = this.#chunkStreams.get(endStreamId);\n if (!endStream) {\n this.#debug(\n `Received message on channel \"${msg.channel}\" to close stream ID \"${endStreamId}\" that doesn't exist`,\n );\n return;\n }\n\n endStream.controller.close();\n this.#chunkStreams.delete(endStreamId);\n\n this.#debug(\n `Closed stream ID \"${endStreamId}\" on channel \"${msg.channel}\"`,\n );\n return this.#fanout.write({\n channel: msg.channel,\n topic: msg.topic,\n kind: \"datastream-end\",\n data: endStreamId,\n streamId: endStreamId,\n fnId: msg.fn_id,\n runId: msg.run_id,\n stream: endStream.stream,\n });\n }\n\n case \"chunk\": {\n if (!msg.channel || !msg.topic) {\n this.#debug(\n `Received message on channel \"${msg.channel}\" with no channel or topic`,\n );\n return;\n }\n\n if (!this.isExpectedChannel(msg.channel)) {\n return;\n }\n\n if (!msg.stream_id) {\n this.#debug(\n `Received message on channel \"${msg.channel}\" with no stream ID`,\n );\n return;\n }\n\n const chunkStream = this.#chunkStreams.get(msg.stream_id);\n if (!chunkStream) {\n this.#debug(\n `Received message on channel \"${msg.channel}\" for unknown stream ID \"${msg.stream_id}\"`,\n );\n return;\n }\n\n this.#debug(\n `Received chunk on channel \"${msg.channel}\" for stream ID \"${msg.stream_id}\":`,\n msg.data,\n );\n\n chunkStream.controller.enqueue(msg.data);\n\n return this.#fanout.write({\n channel: msg.channel,\n topic: msg.topic,\n kind: \"chunk\",\n data: msg.data,\n streamId: msg.stream_id,\n fnId: msg.fn_id,\n runId: msg.run_id,\n stream: chunkStream.stream,\n });\n }\n\n default: {\n this.#debug(\n `Received message on channel \"${msg.channel}\" with unhandled kind \"${msg.kind}\"`,\n );\n return;\n }\n }\n };\n\n this.#ws.onerror = (event) => {\n console.error(\"WebSocket error observed:\", event);\n rejectConnect(event);\n };\n\n this.#ws.onclose = (event) => {\n this.#debug(\"WebSocket closed:\", event.reason);\n if (!hasOpened) {\n rejectConnect(\n new Error(\n `WebSocket closed before opening${\n event.reason ? `: ${event.reason}` : \"\"\n }`,\n ),\n );\n }\n this.close();\n };\n } catch (err) {\n this.#running = false;\n ret.reject(err);\n }\n\n return ret.promise;\n }\n\n private async lazilyGetSubscriptionToken(): Promise<string> {\n const channelId = this.#channelId;\n\n if (!channelId) {\n throw new Error(\"Channel ID is required to create a subscription token\");\n }\n\n if (this.#getSubscriptionToken) {\n return this.#getSubscriptionToken(\n channelId,\n this.token.topics as string[],\n );\n }\n\n //\n // Fallback: try fetching directly using env-based signing keys.\n // This path is used when no Inngest client is available.\n throw new Error(\n \"No getSubscriptionToken handler provided. Pass an Inngest client or provide a token key.\",\n );\n }\n\n public close(reason = \"Userland closed connection\") {\n if (!this.#running) {\n return;\n }\n\n this.#debug(\"close() called; closing connection...\");\n this.#running = false;\n this.#ws?.close(1000, reason);\n this.#ws = null;\n\n for (const { controller } of this.#chunkStreams.values()) {\n try {\n controller.close();\n } catch {\n // no-op\n }\n }\n this.#chunkStreams.clear();\n\n this.#debug(`Closing ${this.#fanout.size()} streams...`);\n this.#fanout.close();\n }\n\n public getJsonStream() {\n return this.#fanout.createStream();\n }\n\n public getEncodedStream() {\n return this.#fanout.createStream((chunk) => {\n return this.#encoder.encode(`data: ${JSON.stringify(chunk)}\\n\\n`);\n });\n }\n\n public useCallback(\n callback: Realtime.Subscribe.Callback,\n stream: ReadableStream<Realtime.Message> = this.getJsonStream(),\n onError?: (err: unknown) => void,\n ) {\n void (async () => {\n const reader = stream.getReader();\n try {\n while (this.#running) {\n const { done, value } = await reader.read();\n if (done || !this.#running) break;\n try {\n await callback(value);\n } catch (err) {\n if (onError) {\n onError(err);\n } else {\n console.error(\"Realtime subscription callback failed:\", err);\n }\n }\n }\n } finally {\n reader.releaseLock();\n }\n })();\n }\n}\n"],"mappings":";;;;;;;;AAMA,MAAM,iBAAiB,eAAsD;AAC3E,KAAI,CAAC,cAAc,OAAO,eAAe,SACvC;AAGF,KAAI,YAAY,cAAc,WAAW,OACvC,QAAO,WAAW;;AAkBtB,IAAa,oBAAb,MAA+B;CAC7B;CACA;CACA,4BAAe,mBAAmB;CAClC,WAAW,IAAI,aAAa;CAC5B,UAAU,IAAIA,mCAAgC;CAC9C,WAAW;CACX;CACA,MAAwB;CACxB;CACA;CACA;CACA;CAKA,gCAAgB,IAAI,KAGjB;CAEH,AAAO;CAEP,YAAY,SAAmC;AAC7C,OAAK,QAAQ,QAAQ;AACrB,QAAKC,aAAc,QAAQ;AAC3B,QAAKC,aAAc,QAAQ;AAC3B,QAAKC,qBAAsB,QAAQ;AACnC,QAAKC,WAAY,QAAQ,YAAY;AACrC,QAAKC,uBAAwB,QAAQ;EAErC,MAAM,UAAU,KAAK,MAAM;AAE3B,MAAI,OAAO,YAAY,UAAU;AAC/B,SAAKC,YAAa;AAKlB,SAAKC,SAAU,IAAI,IACjB,KAAK,MAAM,OAAO,KAAK,SAAS,CAAC,MAAM,OAAU,CAAC,CACnD;SACI;AACL,SAAKD,YAAa,QAAQ;AAK1B,SAAKC,SAAU,IAAI,IACjB,KAAK,MAAM,OAAO,KAAK,SAAS,CAC9B,MAEE,QAAQ,SACN,MACL,CAAC,CACH;;;CAIL,AAAQ,SAAS,OAAoB;EACnC,MAAM,OAAO;EAEb,IAAIC;AAEJ,MAAI,MAAKP,WACP,OAAM,IAAI,IAAI,MAAM,MAAKA,WAAY;MAErC,OAAM,IAAI,IAAI,MAAM,2BAA2B;AAGjD,MAAI,WAAW,IAAI,aAAa,UAAU,QAAQ;AAClD,MAAI,aAAa,IAAI,SAAS,MAAM;AAEpC,SAAO;;CAGT,AAAQ,kBAAkB,SAA0B;AAClD,MAAI,YAAY,MAAKK,UACnB,QAAO;AAGT,QAAKG,MACH,4CAA4C,QAAQ,eAAe,MAAKH,UAAW,IACpF;AACD,SAAO;;CAGT,MAAa,UAAU;AACrB,QAAKG,MACH,uCACE,MAAKH,UACN,gBAAgB,KAAK,UAAU,CAAC,GAAG,MAAKC,OAAQ,MAAM,CAAC,CAAC,CAAC,KAC3D;AAED,MAAI,OAAO,cAAc,YACvB,OAAM,IAAI,MAAM,kDAAkD;EAGpE,IAAI,MAAM,KAAK,MAAM;AACrB,MAAI,CAAC,KAAK;AACR,SAAKE,MACH,gFACD;AAED,SAAM,MAAM,KAAK,4BAA4B;AAE7C,OAAI,CAAC,IACH,OAAM,IAAI,MACR,4EACD;;EAIL,MAAM,MAAMC,wCAA6B;EACzC,IAAI,mBAAmB;EACvB,IAAI,YAAY;EAEhB,MAAM,uBAAuB;AAC3B,OAAI,iBACF;AAEF,sBAAmB;AACnB,OAAI,SAAS;;EAGf,MAAM,iBAAiB,QAAiB;AACtC,OAAI,iBACF;AAEF,sBAAmB;AACnB,OAAI,OAAO,IAAI;;AAGjB,MAAI;AACF,SAAKC,UAAW;AAChB,SAAKC,KAAM,IAAI,UAAU,KAAK,SAAS,IAAI,CAAC;AAE5C,SAAKA,GAAI,eAAe;AACtB,UAAKH,MAAO,mCAAmC;AAC/C,gBAAY;AACZ,oBAAgB;;AAGlB,SAAKG,GAAI,YAAY,OAAO,UAAU;IACpC,IAAIC;AACJ,QAAI;AACF,kBAAa,KAAK,MAAM,MAAM,KAAe;aACtC,KAAK;AACZ,WAAKJ,MAAO,8BAA8B,IAAI;AAC9C;;IAGF,MAAM,WACJ,MAAMK,uBAAS,cAAc,eAAe,WAAW;AAEzD,QAAI,CAAC,SAAS,SAAS;AACrB,WAAKL,MAAO,6BAA6B,SAAS,MAAM;AACxD;;IAGF,MAAM,MAAM,SAAS;AAErB,QAAI,CAAC,MAAKE,SAAU;AAClB,WAAKF,MACH,gCAAgC,IAAI,QAAQ,eAAe,IAAI,MAAM,wBACtE;AACD;;AAGF,YAAQ,IAAI,MAAZ;KACE,KAAK,QAAQ;AACX,UAAI,CAAC,IAAI,SAAS;AAChB,aAAKA,MACH,gCAAgC,IAAI,QAAQ,mBAC7C;AACD;;AAGF,UAAI,CAAC,KAAK,kBAAkB,IAAI,QAAQ,CACtC;AAGF,UAAI,CAAC,IAAI,OAAO;AACd,aAAKA,MACH,gCAAgC,IAAI,QAAQ,iBAC7C;AACD;;AAGF,UAAI,CAAC,MAAKF,OAAQ,IAAI,IAAI,MAAM,EAAE;AAChC,aAAKE,MACH,gCAAgC,IAAI,QAAQ,uBAAuB,IAAI,MAAM,GAC9E;AACD;;MAIF,MAAM,SAAS,cADG,MAAKF,OAAQ,IAAI,IAAI,MAAM,CACN;AACvC,UAAI,MAAKH,YAAa,QAAQ;OAC5B,MAAM,cAAc,MAAM,OAAO,aAAa,SAAS,IAAI,KAAK;AAChE,WAAI,YAAY,QAAQ;AACtB,gBAAQ,MACN,gCAAgC,IAAI,QAAQ,eAAe,IAAI,MAAM,mCACrE,YAAY,OACb;AACD;;AAGF,WAAI,OAAO,YAAY;;AAGzB,YAAKK,MACH,gCAAgC,IAAI,QAAQ,eAAe,IAAI,MAAM,KACrE,IAAI,KACL;AACD,aAAO,MAAKM,OAAQ,MAAM;OACxB,SAAS,IAAI;OACb,OAAO,IAAI;OACX,MAAM,IAAI;OACV,MAAM,IAAI;OACV,WAAW,IAAI,8BAAc,IAAI,MAAM;OACvC,OAAO,IAAI;OACX,MAAM;OACN,OAAO,IAAI;OACZ,CAAC;;KAGJ,KAAK;AACH,UAAI,IAAI,WAAW,CAAC,KAAK,kBAAkB,IAAI,QAAQ,CACrD;AAGF,YAAKN,MAAO,sCAAsC,IAAI,QAAQ,GAAG;AACjE,aAAO,MAAKM,OAAQ,MAAM;OACxB,SAAS,IAAI;OACb,OAAO,IAAI;OACX,MAAM,IAAI;OACV,MAAM,IAAI;OACV,WAAW,IAAI,8BAAc,IAAI,MAAM;OACvC,OAAO,IAAI;OACX,MAAM;OACN,OAAO,IAAI;OACZ,CAAC;KAGJ,KAAK,oBAAoB;AACvB,UAAI,CAAC,IAAI,WAAW,CAAC,IAAI,OAAO;AAC9B,aAAKN,MACH,gCAAgC,IAAI,QAAQ,4BAC7C;AACD;;AAGF,UAAI,CAAC,KAAK,kBAAkB,IAAI,QAAQ,CACtC;MAGF,MAAMO,WAAoB,IAAI;AAC9B,UAAI,OAAO,aAAa,YAAY,CAAC,UAAU;AAC7C,aAAKP,MACH,gCAAgC,IAAI,QAAQ,qBAC7C;AACD;;AAGF,UAAI,MAAKQ,aAAc,IAAI,SAAS,EAAE;AACpC,aAAKR,MACH,gCAAgC,IAAI,QAAQ,yBAAyB,SAAS,uBAC/E;AACD;;MAGF,MAAM,SAAS,IAAI,eAAe;OAChC,QAAQ,eAAe;AACrB,cAAKQ,aAAc,IAAI,UAAU;SAAE;SAAQ;SAAY,CAAC;;OAE1D,cAAc;AACZ,cAAKA,aAAc,OAAO,SAAS;;OAEtC,CAAC;AAEF,YAAKR,MACH,sBAAsB,SAAS,gBAAgB,IAAI,QAAQ,GAC5D;AACD,aAAO,MAAKM,OAAQ,MAAM;OACxB,SAAS,IAAI;OACb,OAAO,IAAI;OACX,MAAM;OACN,MAAM;OACN;OACA,MAAM,IAAI;OACV,OAAO,IAAI;OACX;OACD,CAAC;;KAGJ,KAAK,kBAAkB;AACrB,UAAI,CAAC,IAAI,WAAW,CAAC,IAAI,OAAO;AAC9B,aAAKN,MACH,gCAAgC,IAAI,QAAQ,4BAC7C;AACD;;AAGF,UAAI,CAAC,KAAK,kBAAkB,IAAI,QAAQ,CACtC;MAGF,MAAMS,cAAuB,IAAI;AACjC,UAAI,OAAO,gBAAgB,YAAY,CAAC,aAAa;AACnD,aAAKT,MACH,gCAAgC,IAAI,QAAQ,qBAC7C;AACD;;MAGF,MAAM,YAAY,MAAKQ,aAAc,IAAI,YAAY;AACrD,UAAI,CAAC,WAAW;AACd,aAAKR,MACH,gCAAgC,IAAI,QAAQ,wBAAwB,YAAY,sBACjF;AACD;;AAGF,gBAAU,WAAW,OAAO;AAC5B,YAAKQ,aAAc,OAAO,YAAY;AAEtC,YAAKR,MACH,qBAAqB,YAAY,gBAAgB,IAAI,QAAQ,GAC9D;AACD,aAAO,MAAKM,OAAQ,MAAM;OACxB,SAAS,IAAI;OACb,OAAO,IAAI;OACX,MAAM;OACN,MAAM;OACN,UAAU;OACV,MAAM,IAAI;OACV,OAAO,IAAI;OACX,QAAQ,UAAU;OACnB,CAAC;;KAGJ,KAAK,SAAS;AACZ,UAAI,CAAC,IAAI,WAAW,CAAC,IAAI,OAAO;AAC9B,aAAKN,MACH,gCAAgC,IAAI,QAAQ,4BAC7C;AACD;;AAGF,UAAI,CAAC,KAAK,kBAAkB,IAAI,QAAQ,CACtC;AAGF,UAAI,CAAC,IAAI,WAAW;AAClB,aAAKA,MACH,gCAAgC,IAAI,QAAQ,qBAC7C;AACD;;MAGF,MAAM,cAAc,MAAKQ,aAAc,IAAI,IAAI,UAAU;AACzD,UAAI,CAAC,aAAa;AAChB,aAAKR,MACH,gCAAgC,IAAI,QAAQ,2BAA2B,IAAI,UAAU,GACtF;AACD;;AAGF,YAAKA,MACH,8BAA8B,IAAI,QAAQ,mBAAmB,IAAI,UAAU,KAC3E,IAAI,KACL;AAED,kBAAY,WAAW,QAAQ,IAAI,KAAK;AAExC,aAAO,MAAKM,OAAQ,MAAM;OACxB,SAAS,IAAI;OACb,OAAO,IAAI;OACX,MAAM;OACN,MAAM,IAAI;OACV,UAAU,IAAI;OACd,MAAM,IAAI;OACV,OAAO,IAAI;OACX,QAAQ,YAAY;OACrB,CAAC;;KAGJ;AACE,YAAKN,MACH,gCAAgC,IAAI,QAAQ,yBAAyB,IAAI,KAAK,GAC/E;AACD;;;AAKN,SAAKG,GAAI,WAAW,UAAU;AAC5B,YAAQ,MAAM,6BAA6B,MAAM;AACjD,kBAAc,MAAM;;AAGtB,SAAKA,GAAI,WAAW,UAAU;AAC5B,UAAKH,MAAO,qBAAqB,MAAM,OAAO;AAC9C,QAAI,CAAC,UACH,+BACE,IAAI,MACF,kCACE,MAAM,SAAS,KAAK,MAAM,WAAW,KAExC,CACF;AAEH,SAAK,OAAO;;WAEP,KAAK;AACZ,SAAKE,UAAW;AAChB,OAAI,OAAO,IAAI;;AAGjB,SAAO,IAAI;;CAGb,MAAc,6BAA8C;EAC1D,MAAM,YAAY,MAAKL;AAEvB,MAAI,CAAC,UACH,OAAM,IAAI,MAAM,wDAAwD;AAG1E,MAAI,MAAKD,qBACP,QAAO,MAAKA,qBACV,WACA,KAAK,MAAM,OACZ;AAMH,QAAM,IAAI,MACR,2FACD;;CAGH,AAAO,MAAM,SAAS,8BAA8B;AAClD,MAAI,CAAC,MAAKM,QACR;AAGF,QAAKF,MAAO,wCAAwC;AACpD,QAAKE,UAAW;AAChB,QAAKC,IAAK,MAAM,KAAM,OAAO;AAC7B,QAAKA,KAAM;AAEX,OAAK,MAAM,EAAE,gBAAgB,MAAKK,aAAc,QAAQ,CACtD,KAAI;AACF,cAAW,OAAO;UACZ;AAIV,QAAKA,aAAc,OAAO;AAE1B,QAAKR,MAAO,WAAW,MAAKM,OAAQ,MAAM,CAAC,aAAa;AACxD,QAAKA,OAAQ,OAAO;;CAGtB,AAAO,gBAAgB;AACrB,SAAO,MAAKA,OAAQ,cAAc;;CAGpC,AAAO,mBAAmB;AACxB,SAAO,MAAKA,OAAQ,cAAc,UAAU;AAC1C,UAAO,MAAKI,QAAS,OAAO,SAAS,KAAK,UAAU,MAAM,CAAC,MAAM;IACjE;;CAGJ,AAAO,YACL,UACA,SAA2C,KAAK,eAAe,EAC/D,SACA;AACA,GAAM,YAAY;GAChB,MAAM,SAAS,OAAO,WAAW;AACjC,OAAI;AACF,WAAO,MAAKR,SAAU;KACpB,MAAM,EAAE,MAAM,UAAU,MAAM,OAAO,MAAM;AAC3C,SAAI,QAAQ,CAAC,MAAKA,QAAU;AAC5B,SAAI;AACF,YAAM,SAAS,MAAM;cACd,KAAK;AACZ,UAAI,QACF,SAAQ,IAAI;UAEZ,SAAQ,MAAM,0CAA0C,IAAI;;;aAI1D;AACR,WAAO,aAAa;;MAEpB"}