@eventferry/kafka 3.3.1 → 3.5.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.
@@ -0,0 +1,114 @@
1
+ /**
2
+ * Consumer-side helpers — paired with the publisher's outbound surface.
3
+ *
4
+ * eventferry is a publisher-only library, but the messages it produces are
5
+ * consumed somewhere downstream. This module normalizes the message shape
6
+ * that both `kafkajs` and `@confluentinc/kafka-javascript` deliver to
7
+ * consumer callbacks, decodes the payload, and extracts the W3C trace
8
+ * context the publisher injected (see {@link KafkaTracer.inject}).
9
+ *
10
+ * Imported via subpath so consumer code paths don't pull in the producer:
11
+ *
12
+ * import { decode, extractTraceContext } from "@eventferry/kafka/consume";
13
+ *
14
+ * There is intentionally NO Kafka client here — bring your own consumer
15
+ * (kafkajs's `Consumer`, librdkafka's, whatever) and call `decode()` /
16
+ * `extractTraceContext()` on the message you receive.
17
+ */
18
+ /**
19
+ * Raw incoming Kafka message — structural subset both kafkajs and confluent
20
+ * (via the kafkaJS-compat layer) deliver. Fields are optional because
21
+ * different consumer APIs surface different subsets.
22
+ */
23
+ interface IncomingKafkaMessage {
24
+ key?: Buffer | string | null;
25
+ value?: Buffer | string | null;
26
+ headers?: IncomingHeaders;
27
+ /** ISO ms string or numeric epoch ms — depends on the client. */
28
+ timestamp?: string | number;
29
+ /** Numeric or string per client. */
30
+ offset?: string | number;
31
+ partition?: number;
32
+ }
33
+ /** Headers as the underlying clients deliver them: bytes, strings, or undefined. */
34
+ type IncomingHeaders = Record<string, Buffer | string | undefined>;
35
+ /** Headers normalized to UTF-8 strings (the form most application code wants). */
36
+ type DecodedHeaders = Record<string, string>;
37
+ /** Payload decoder. Buffer in, decoded value out. */
38
+ type Decoder<T> = (bytes: Buffer) => T;
39
+ /** Decoded message wrapper — value plus normalized headers, key, metadata. */
40
+ interface DecodedMessage<V = unknown> {
41
+ key: string | null;
42
+ value: V | null;
43
+ headers: DecodedHeaders;
44
+ /** Epoch ms when the broker stamped the record. */
45
+ timestamp?: number;
46
+ /** Stringified offset (Kafka offsets exceed 2^53 — strings stay safe). */
47
+ offset?: string;
48
+ partition?: number;
49
+ }
50
+ interface DecodeOptions<V> {
51
+ /**
52
+ * Decoder for the payload bytes. Built-ins:
53
+ *
54
+ * - `"json"` (default) — `JSON.parse(value.toString("utf8"))`. Empty
55
+ * value returns `null` (matches Kafka tombstones on compacted topics).
56
+ * - `"utf8"` — raw text. Returns the string as-is.
57
+ * - `"none"` — returns the raw `Buffer` unchanged.
58
+ *
59
+ * Or pass your own `(bytes: Buffer) => V` for Avro / Protobuf / MessagePack.
60
+ */
61
+ decoder?: "json" | "utf8" | "none" | Decoder<V>;
62
+ }
63
+ /**
64
+ * Normalize headers to a plain string→string map. Buffers are read as UTF-8;
65
+ * `undefined` entries are dropped (consumers occasionally surface absent
66
+ * headers as `undefined` values).
67
+ */
68
+ declare function decodeHeaders(raw?: IncomingHeaders): DecodedHeaders;
69
+ /**
70
+ * Decode a Kafka message: normalize the key + headers, decode the value
71
+ * with the chosen decoder, and surface the broker metadata.
72
+ *
73
+ * Tombstones (null/empty value) come back with `value: null` regardless of
74
+ * the decoder — compaction-friendly.
75
+ *
76
+ * @throws when `decoder: "json"` (the default) and the payload is non-empty
77
+ * but not valid JSON. Catch the error and decide whether to DLQ the
78
+ * record or skip it — eventferry does not assume.
79
+ */
80
+ declare function decode<V = unknown>(msg: IncomingKafkaMessage, opts?: DecodeOptions<V>): DecodedMessage<V>;
81
+ /**
82
+ * W3C Trace Context extracted from message headers.
83
+ *
84
+ * - `traceparent`: full header value, format `version-traceId-spanId-flags`.
85
+ * - `tracestate`: optional vendor-specific state (W3C `tracestate` header).
86
+ * - `traceId`: 32 hex chars, parsed from `traceparent`.
87
+ * - `spanId`: 16 hex chars (the PARENT span id from the producer).
88
+ * - `sampled`: parsed from the `traceparent` flags (bit 0 = sampled).
89
+ *
90
+ * Returns `null` when no `traceparent` header is present or the value
91
+ * fails W3C validation.
92
+ *
93
+ * Spec: https://www.w3.org/TR/trace-context/
94
+ */
95
+ interface TraceContext {
96
+ traceparent: string;
97
+ tracestate?: string;
98
+ traceId: string;
99
+ spanId: string;
100
+ sampled: boolean;
101
+ }
102
+ /**
103
+ * Extract the W3C trace context the publisher injected into headers.
104
+ * Headers may be raw (Buffer values) or already-decoded (string values) —
105
+ * both shapes work, so you can call this before OR after `decode()`.
106
+ *
107
+ * Validation follows the W3C spec strictly: invalid all-zero trace/span
108
+ * IDs are rejected, version `ff` is rejected, malformed hex is rejected.
109
+ * On any of these, the function returns `null` rather than throwing —
110
+ * consumer code should fall back to starting a fresh trace.
111
+ */
112
+ declare function extractTraceContext(headers: IncomingHeaders | DecodedHeaders | undefined): TraceContext | null;
113
+
114
+ export { type DecodeOptions, type DecodedHeaders, type DecodedMessage, type Decoder, type IncomingHeaders, type IncomingKafkaMessage, type TraceContext, decode, decodeHeaders, extractTraceContext };
@@ -0,0 +1,114 @@
1
+ /**
2
+ * Consumer-side helpers — paired with the publisher's outbound surface.
3
+ *
4
+ * eventferry is a publisher-only library, but the messages it produces are
5
+ * consumed somewhere downstream. This module normalizes the message shape
6
+ * that both `kafkajs` and `@confluentinc/kafka-javascript` deliver to
7
+ * consumer callbacks, decodes the payload, and extracts the W3C trace
8
+ * context the publisher injected (see {@link KafkaTracer.inject}).
9
+ *
10
+ * Imported via subpath so consumer code paths don't pull in the producer:
11
+ *
12
+ * import { decode, extractTraceContext } from "@eventferry/kafka/consume";
13
+ *
14
+ * There is intentionally NO Kafka client here — bring your own consumer
15
+ * (kafkajs's `Consumer`, librdkafka's, whatever) and call `decode()` /
16
+ * `extractTraceContext()` on the message you receive.
17
+ */
18
+ /**
19
+ * Raw incoming Kafka message — structural subset both kafkajs and confluent
20
+ * (via the kafkaJS-compat layer) deliver. Fields are optional because
21
+ * different consumer APIs surface different subsets.
22
+ */
23
+ interface IncomingKafkaMessage {
24
+ key?: Buffer | string | null;
25
+ value?: Buffer | string | null;
26
+ headers?: IncomingHeaders;
27
+ /** ISO ms string or numeric epoch ms — depends on the client. */
28
+ timestamp?: string | number;
29
+ /** Numeric or string per client. */
30
+ offset?: string | number;
31
+ partition?: number;
32
+ }
33
+ /** Headers as the underlying clients deliver them: bytes, strings, or undefined. */
34
+ type IncomingHeaders = Record<string, Buffer | string | undefined>;
35
+ /** Headers normalized to UTF-8 strings (the form most application code wants). */
36
+ type DecodedHeaders = Record<string, string>;
37
+ /** Payload decoder. Buffer in, decoded value out. */
38
+ type Decoder<T> = (bytes: Buffer) => T;
39
+ /** Decoded message wrapper — value plus normalized headers, key, metadata. */
40
+ interface DecodedMessage<V = unknown> {
41
+ key: string | null;
42
+ value: V | null;
43
+ headers: DecodedHeaders;
44
+ /** Epoch ms when the broker stamped the record. */
45
+ timestamp?: number;
46
+ /** Stringified offset (Kafka offsets exceed 2^53 — strings stay safe). */
47
+ offset?: string;
48
+ partition?: number;
49
+ }
50
+ interface DecodeOptions<V> {
51
+ /**
52
+ * Decoder for the payload bytes. Built-ins:
53
+ *
54
+ * - `"json"` (default) — `JSON.parse(value.toString("utf8"))`. Empty
55
+ * value returns `null` (matches Kafka tombstones on compacted topics).
56
+ * - `"utf8"` — raw text. Returns the string as-is.
57
+ * - `"none"` — returns the raw `Buffer` unchanged.
58
+ *
59
+ * Or pass your own `(bytes: Buffer) => V` for Avro / Protobuf / MessagePack.
60
+ */
61
+ decoder?: "json" | "utf8" | "none" | Decoder<V>;
62
+ }
63
+ /**
64
+ * Normalize headers to a plain string→string map. Buffers are read as UTF-8;
65
+ * `undefined` entries are dropped (consumers occasionally surface absent
66
+ * headers as `undefined` values).
67
+ */
68
+ declare function decodeHeaders(raw?: IncomingHeaders): DecodedHeaders;
69
+ /**
70
+ * Decode a Kafka message: normalize the key + headers, decode the value
71
+ * with the chosen decoder, and surface the broker metadata.
72
+ *
73
+ * Tombstones (null/empty value) come back with `value: null` regardless of
74
+ * the decoder — compaction-friendly.
75
+ *
76
+ * @throws when `decoder: "json"` (the default) and the payload is non-empty
77
+ * but not valid JSON. Catch the error and decide whether to DLQ the
78
+ * record or skip it — eventferry does not assume.
79
+ */
80
+ declare function decode<V = unknown>(msg: IncomingKafkaMessage, opts?: DecodeOptions<V>): DecodedMessage<V>;
81
+ /**
82
+ * W3C Trace Context extracted from message headers.
83
+ *
84
+ * - `traceparent`: full header value, format `version-traceId-spanId-flags`.
85
+ * - `tracestate`: optional vendor-specific state (W3C `tracestate` header).
86
+ * - `traceId`: 32 hex chars, parsed from `traceparent`.
87
+ * - `spanId`: 16 hex chars (the PARENT span id from the producer).
88
+ * - `sampled`: parsed from the `traceparent` flags (bit 0 = sampled).
89
+ *
90
+ * Returns `null` when no `traceparent` header is present or the value
91
+ * fails W3C validation.
92
+ *
93
+ * Spec: https://www.w3.org/TR/trace-context/
94
+ */
95
+ interface TraceContext {
96
+ traceparent: string;
97
+ tracestate?: string;
98
+ traceId: string;
99
+ spanId: string;
100
+ sampled: boolean;
101
+ }
102
+ /**
103
+ * Extract the W3C trace context the publisher injected into headers.
104
+ * Headers may be raw (Buffer values) or already-decoded (string values) —
105
+ * both shapes work, so you can call this before OR after `decode()`.
106
+ *
107
+ * Validation follows the W3C spec strictly: invalid all-zero trace/span
108
+ * IDs are rejected, version `ff` is rejected, malformed hex is rejected.
109
+ * On any of these, the function returns `null` rather than throwing —
110
+ * consumer code should fall back to starting a fresh trace.
111
+ */
112
+ declare function extractTraceContext(headers: IncomingHeaders | DecodedHeaders | undefined): TraceContext | null;
113
+
114
+ export { type DecodeOptions, type DecodedHeaders, type DecodedMessage, type Decoder, type IncomingHeaders, type IncomingKafkaMessage, type TraceContext, decode, decodeHeaders, extractTraceContext };
@@ -0,0 +1,88 @@
1
+ // src/consume.ts
2
+ function decodeHeaders(raw) {
3
+ if (!raw) return {};
4
+ const out = {};
5
+ for (const [k, v] of Object.entries(raw)) {
6
+ if (v === void 0 || v === null) continue;
7
+ out[k] = Buffer.isBuffer(v) ? v.toString("utf8") : v;
8
+ }
9
+ return out;
10
+ }
11
+ function decode(msg, opts = {}) {
12
+ const headers = decodeHeaders(msg.headers);
13
+ const key = normalizeKey(msg.key);
14
+ const value = decodeValue(msg.value, opts.decoder ?? "json");
15
+ const timestamp = msg.timestamp !== void 0 ? Number(msg.timestamp) : void 0;
16
+ const offset = msg.offset !== void 0 ? String(msg.offset) : void 0;
17
+ return {
18
+ key,
19
+ value,
20
+ headers,
21
+ timestamp,
22
+ offset,
23
+ partition: msg.partition
24
+ };
25
+ }
26
+ function normalizeKey(key) {
27
+ if (key === null || key === void 0) return null;
28
+ return Buffer.isBuffer(key) ? key.toString("utf8") : key;
29
+ }
30
+ function decodeValue(value, decoder) {
31
+ if (value === null || value === void 0) return null;
32
+ const buf = Buffer.isBuffer(value) ? value : Buffer.from(value);
33
+ if (buf.length === 0) return null;
34
+ if (typeof decoder === "function") return decoder(buf);
35
+ switch (decoder) {
36
+ case "utf8":
37
+ return buf.toString("utf8");
38
+ case "none":
39
+ return buf;
40
+ case "json":
41
+ case void 0:
42
+ default: {
43
+ const text = buf.toString("utf8");
44
+ try {
45
+ return JSON.parse(text);
46
+ } catch (err) {
47
+ throw new Error(
48
+ `decode: JSON.parse failed on message value: ${err.message}`
49
+ );
50
+ }
51
+ }
52
+ }
53
+ }
54
+ var TRACEPARENT_RE = /^([0-9a-f]{2})-([0-9a-f]{32})-([0-9a-f]{16})-([0-9a-f]{2})$/;
55
+ var INVALID_TRACE_ID = "0".repeat(32);
56
+ var INVALID_SPAN_ID = "0".repeat(16);
57
+ function extractTraceContext(headers) {
58
+ if (!headers) return null;
59
+ const tp = readHeader(headers, "traceparent");
60
+ if (!tp) return null;
61
+ const match = TRACEPARENT_RE.exec(tp);
62
+ if (!match) return null;
63
+ const [, version, traceId, spanId, flags] = match;
64
+ if (version === "ff") return null;
65
+ if (traceId === INVALID_TRACE_ID || spanId === INVALID_SPAN_ID) return null;
66
+ const sampled = (parseInt(flags, 16) & 1) === 1;
67
+ const ts = readHeader(headers, "tracestate");
68
+ return {
69
+ traceparent: tp,
70
+ tracestate: ts && ts.length > 0 ? ts : void 0,
71
+ traceId,
72
+ spanId,
73
+ sampled
74
+ };
75
+ }
76
+ function readHeader(headers, name) {
77
+ const v = headers[name];
78
+ if (v === void 0 || v === null) return void 0;
79
+ if (typeof v === "string") return v;
80
+ if (Buffer.isBuffer(v)) return v.toString("utf8");
81
+ return void 0;
82
+ }
83
+ export {
84
+ decode,
85
+ decodeHeaders,
86
+ extractTraceContext
87
+ };
88
+ //# sourceMappingURL=consume.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/consume.ts"],"sourcesContent":["/**\n * Consumer-side helpers — paired with the publisher's outbound surface.\n *\n * eventferry is a publisher-only library, but the messages it produces are\n * consumed somewhere downstream. This module normalizes the message shape\n * that both `kafkajs` and `@confluentinc/kafka-javascript` deliver to\n * consumer callbacks, decodes the payload, and extracts the W3C trace\n * context the publisher injected (see {@link KafkaTracer.inject}).\n *\n * Imported via subpath so consumer code paths don't pull in the producer:\n *\n * import { decode, extractTraceContext } from \"@eventferry/kafka/consume\";\n *\n * There is intentionally NO Kafka client here — bring your own consumer\n * (kafkajs's `Consumer`, librdkafka's, whatever) and call `decode()` /\n * `extractTraceContext()` on the message you receive.\n */\n\n/**\n * Raw incoming Kafka message — structural subset both kafkajs and confluent\n * (via the kafkaJS-compat layer) deliver. Fields are optional because\n * different consumer APIs surface different subsets.\n */\nexport interface IncomingKafkaMessage {\n key?: Buffer | string | null;\n value?: Buffer | string | null;\n headers?: IncomingHeaders;\n /** ISO ms string or numeric epoch ms — depends on the client. */\n timestamp?: string | number;\n /** Numeric or string per client. */\n offset?: string | number;\n partition?: number;\n}\n\n/** Headers as the underlying clients deliver them: bytes, strings, or undefined. */\nexport type IncomingHeaders = Record<string, Buffer | string | undefined>;\n\n/** Headers normalized to UTF-8 strings (the form most application code wants). */\nexport type DecodedHeaders = Record<string, string>;\n\n/** Payload decoder. Buffer in, decoded value out. */\nexport type Decoder<T> = (bytes: Buffer) => T;\n\n/** Decoded message wrapper — value plus normalized headers, key, metadata. */\nexport interface DecodedMessage<V = unknown> {\n key: string | null;\n value: V | null;\n headers: DecodedHeaders;\n /** Epoch ms when the broker stamped the record. */\n timestamp?: number;\n /** Stringified offset (Kafka offsets exceed 2^53 — strings stay safe). */\n offset?: string;\n partition?: number;\n}\n\nexport interface DecodeOptions<V> {\n /**\n * Decoder for the payload bytes. Built-ins:\n *\n * - `\"json\"` (default) — `JSON.parse(value.toString(\"utf8\"))`. Empty\n * value returns `null` (matches Kafka tombstones on compacted topics).\n * - `\"utf8\"` — raw text. Returns the string as-is.\n * - `\"none\"` — returns the raw `Buffer` unchanged.\n *\n * Or pass your own `(bytes: Buffer) => V` for Avro / Protobuf / MessagePack.\n */\n decoder?: \"json\" | \"utf8\" | \"none\" | Decoder<V>;\n}\n\n/**\n * Normalize headers to a plain string→string map. Buffers are read as UTF-8;\n * `undefined` entries are dropped (consumers occasionally surface absent\n * headers as `undefined` values).\n */\nexport function decodeHeaders(raw?: IncomingHeaders): DecodedHeaders {\n if (!raw) return {};\n const out: DecodedHeaders = {};\n for (const [k, v] of Object.entries(raw)) {\n if (v === undefined || v === null) continue;\n out[k] = Buffer.isBuffer(v) ? v.toString(\"utf8\") : v;\n }\n return out;\n}\n\n/**\n * Decode a Kafka message: normalize the key + headers, decode the value\n * with the chosen decoder, and surface the broker metadata.\n *\n * Tombstones (null/empty value) come back with `value: null` regardless of\n * the decoder — compaction-friendly.\n *\n * @throws when `decoder: \"json\"` (the default) and the payload is non-empty\n * but not valid JSON. Catch the error and decide whether to DLQ the\n * record or skip it — eventferry does not assume.\n */\nexport function decode<V = unknown>(\n msg: IncomingKafkaMessage,\n opts: DecodeOptions<V> = {},\n): DecodedMessage<V> {\n const headers = decodeHeaders(msg.headers);\n const key = normalizeKey(msg.key);\n const value = decodeValue<V>(msg.value, opts.decoder ?? \"json\");\n const timestamp =\n msg.timestamp !== undefined ? Number(msg.timestamp) : undefined;\n const offset = msg.offset !== undefined ? String(msg.offset) : undefined;\n return {\n key,\n value,\n headers,\n timestamp,\n offset,\n partition: msg.partition,\n };\n}\n\nfunction normalizeKey(key?: Buffer | string | null): string | null {\n if (key === null || key === undefined) return null;\n return Buffer.isBuffer(key) ? key.toString(\"utf8\") : key;\n}\n\nfunction decodeValue<V>(\n value: Buffer | string | null | undefined,\n decoder: DecodeOptions<V>[\"decoder\"],\n): V | null {\n if (value === null || value === undefined) return null;\n const buf = Buffer.isBuffer(value) ? value : Buffer.from(value);\n // Tombstones: an empty buffer is a kafka \"delete me\" on compacted\n // topics. Surface as null for every decoder — applications usually\n // want the same null-handling for both.\n if (buf.length === 0) return null;\n if (typeof decoder === \"function\") return decoder(buf);\n switch (decoder) {\n case \"utf8\":\n return buf.toString(\"utf8\") as unknown as V;\n case \"none\":\n return buf as unknown as V;\n case \"json\":\n case undefined:\n default: {\n const text = buf.toString(\"utf8\");\n try {\n return JSON.parse(text) as V;\n } catch (err) {\n throw new Error(\n `decode: JSON.parse failed on message value: ${(err as Error).message}`,\n );\n }\n }\n }\n}\n\n/**\n * W3C Trace Context extracted from message headers.\n *\n * - `traceparent`: full header value, format `version-traceId-spanId-flags`.\n * - `tracestate`: optional vendor-specific state (W3C `tracestate` header).\n * - `traceId`: 32 hex chars, parsed from `traceparent`.\n * - `spanId`: 16 hex chars (the PARENT span id from the producer).\n * - `sampled`: parsed from the `traceparent` flags (bit 0 = sampled).\n *\n * Returns `null` when no `traceparent` header is present or the value\n * fails W3C validation.\n *\n * Spec: https://www.w3.org/TR/trace-context/\n */\nexport interface TraceContext {\n traceparent: string;\n tracestate?: string;\n traceId: string;\n spanId: string;\n sampled: boolean;\n}\n\nconst TRACEPARENT_RE =\n /^([0-9a-f]{2})-([0-9a-f]{32})-([0-9a-f]{16})-([0-9a-f]{2})$/;\nconst INVALID_TRACE_ID = \"0\".repeat(32);\nconst INVALID_SPAN_ID = \"0\".repeat(16);\n\n/**\n * Extract the W3C trace context the publisher injected into headers.\n * Headers may be raw (Buffer values) or already-decoded (string values) —\n * both shapes work, so you can call this before OR after `decode()`.\n *\n * Validation follows the W3C spec strictly: invalid all-zero trace/span\n * IDs are rejected, version `ff` is rejected, malformed hex is rejected.\n * On any of these, the function returns `null` rather than throwing —\n * consumer code should fall back to starting a fresh trace.\n */\nexport function extractTraceContext(\n headers: IncomingHeaders | DecodedHeaders | undefined,\n): TraceContext | null {\n if (!headers) return null;\n const tp = readHeader(headers, \"traceparent\");\n if (!tp) return null;\n const match = TRACEPARENT_RE.exec(tp);\n if (!match) return null;\n const [, version, traceId, spanId, flags] = match as unknown as [\n string,\n string,\n string,\n string,\n string,\n ];\n // Spec §3.2.2.5: version \"ff\" is forbidden (reserved sentinel).\n if (version === \"ff\") return null;\n if (traceId === INVALID_TRACE_ID || spanId === INVALID_SPAN_ID) return null;\n const sampled = (parseInt(flags, 16) & 0x01) === 1;\n const ts = readHeader(headers, \"tracestate\");\n return {\n traceparent: tp,\n tracestate: ts && ts.length > 0 ? ts : undefined,\n traceId,\n spanId,\n sampled,\n };\n}\n\nfunction readHeader(\n headers: IncomingHeaders | DecodedHeaders,\n name: string,\n): string | undefined {\n const v = (headers as Record<string, Buffer | string | undefined>)[name];\n if (v === undefined || v === null) return undefined;\n if (typeof v === \"string\") return v;\n if (Buffer.isBuffer(v)) return v.toString(\"utf8\");\n return undefined;\n}\n"],"mappings":";AA0EO,SAAS,cAAc,KAAuC;AACnE,MAAI,CAAC,IAAK,QAAO,CAAC;AAClB,QAAM,MAAsB,CAAC;AAC7B,aAAW,CAAC,GAAG,CAAC,KAAK,OAAO,QAAQ,GAAG,GAAG;AACxC,QAAI,MAAM,UAAa,MAAM,KAAM;AACnC,QAAI,CAAC,IAAI,OAAO,SAAS,CAAC,IAAI,EAAE,SAAS,MAAM,IAAI;AAAA,EACrD;AACA,SAAO;AACT;AAaO,SAAS,OACd,KACA,OAAyB,CAAC,GACP;AACnB,QAAM,UAAU,cAAc,IAAI,OAAO;AACzC,QAAM,MAAM,aAAa,IAAI,GAAG;AAChC,QAAM,QAAQ,YAAe,IAAI,OAAO,KAAK,WAAW,MAAM;AAC9D,QAAM,YACJ,IAAI,cAAc,SAAY,OAAO,IAAI,SAAS,IAAI;AACxD,QAAM,SAAS,IAAI,WAAW,SAAY,OAAO,IAAI,MAAM,IAAI;AAC/D,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,WAAW,IAAI;AAAA,EACjB;AACF;AAEA,SAAS,aAAa,KAA6C;AACjE,MAAI,QAAQ,QAAQ,QAAQ,OAAW,QAAO;AAC9C,SAAO,OAAO,SAAS,GAAG,IAAI,IAAI,SAAS,MAAM,IAAI;AACvD;AAEA,SAAS,YACP,OACA,SACU;AACV,MAAI,UAAU,QAAQ,UAAU,OAAW,QAAO;AAClD,QAAM,MAAM,OAAO,SAAS,KAAK,IAAI,QAAQ,OAAO,KAAK,KAAK;AAI9D,MAAI,IAAI,WAAW,EAAG,QAAO;AAC7B,MAAI,OAAO,YAAY,WAAY,QAAO,QAAQ,GAAG;AACrD,UAAQ,SAAS;AAAA,IACf,KAAK;AACH,aAAO,IAAI,SAAS,MAAM;AAAA,IAC5B,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AAAA,IACL,KAAK;AAAA,IACL,SAAS;AACP,YAAM,OAAO,IAAI,SAAS,MAAM;AAChC,UAAI;AACF,eAAO,KAAK,MAAM,IAAI;AAAA,MACxB,SAAS,KAAK;AACZ,cAAM,IAAI;AAAA,UACR,+CAAgD,IAAc,OAAO;AAAA,QACvE;AAAA,MACF;AAAA,IACF;AAAA,EACF;AACF;AAwBA,IAAM,iBACJ;AACF,IAAM,mBAAmB,IAAI,OAAO,EAAE;AACtC,IAAM,kBAAkB,IAAI,OAAO,EAAE;AAY9B,SAAS,oBACd,SACqB;AACrB,MAAI,CAAC,QAAS,QAAO;AACrB,QAAM,KAAK,WAAW,SAAS,aAAa;AAC5C,MAAI,CAAC,GAAI,QAAO;AAChB,QAAM,QAAQ,eAAe,KAAK,EAAE;AACpC,MAAI,CAAC,MAAO,QAAO;AACnB,QAAM,CAAC,EAAE,SAAS,SAAS,QAAQ,KAAK,IAAI;AAQ5C,MAAI,YAAY,KAAM,QAAO;AAC7B,MAAI,YAAY,oBAAoB,WAAW,gBAAiB,QAAO;AACvE,QAAM,WAAW,SAAS,OAAO,EAAE,IAAI,OAAU;AACjD,QAAM,KAAK,WAAW,SAAS,YAAY;AAC3C,SAAO;AAAA,IACL,aAAa;AAAA,IACb,YAAY,MAAM,GAAG,SAAS,IAAI,KAAK;AAAA,IACvC;AAAA,IACA;AAAA,IACA;AAAA,EACF;AACF;AAEA,SAAS,WACP,SACA,MACoB;AACpB,QAAM,IAAK,QAAwD,IAAI;AACvE,MAAI,MAAM,UAAa,MAAM,KAAM,QAAO;AAC1C,MAAI,OAAO,MAAM,SAAU,QAAO;AAClC,MAAI,OAAO,SAAS,CAAC,EAAG,QAAO,EAAE,SAAS,MAAM;AAChD,SAAO;AACT;","names":[]}