@glion/mllp-client 0.16.0 → 0.19.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.
Files changed (86) hide show
  1. package/README.md +370 -195
  2. package/dist/assertions.d.ts +10 -0
  3. package/dist/assertions.d.ts.map +1 -0
  4. package/dist/client.d.ts +108 -0
  5. package/dist/client.d.ts.map +1 -0
  6. package/dist/constants.d.ts +23 -0
  7. package/dist/constants.d.ts.map +1 -0
  8. package/dist/errors/base.d.ts +31 -0
  9. package/dist/errors/base.d.ts.map +1 -0
  10. package/dist/errors/index.d.ts +15 -0
  11. package/dist/errors/index.d.ts.map +1 -0
  12. package/dist/errors/mllp-already-sending-error.d.ts +14 -0
  13. package/dist/errors/mllp-already-sending-error.d.ts.map +1 -0
  14. package/dist/errors/mllp-client-closed-error.d.ts +13 -0
  15. package/dist/errors/mllp-client-closed-error.d.ts.map +1 -0
  16. package/dist/errors/mllp-connection-error.d.ts +9 -0
  17. package/dist/errors/mllp-connection-error.d.ts.map +1 -0
  18. package/dist/errors/mllp-connection-failed-error.d.ts +12 -0
  19. package/dist/errors/mllp-connection-failed-error.d.ts.map +1 -0
  20. package/dist/errors/mllp-connection-lost-error.d.ts +13 -0
  21. package/dist/errors/mllp-connection-lost-error.d.ts.map +1 -0
  22. package/dist/errors/mllp-connection-timeout-error.d.ts +13 -0
  23. package/dist/errors/mllp-connection-timeout-error.d.ts.map +1 -0
  24. package/dist/errors/mllp-invalid-message-error.d.ts +13 -0
  25. package/dist/errors/mllp-invalid-message-error.d.ts.map +1 -0
  26. package/dist/errors/mllp-invalid-option-error.d.ts +8 -0
  27. package/dist/errors/mllp-invalid-option-error.d.ts.map +1 -0
  28. package/dist/errors/mllp-invalid-response-error.d.ts +16 -0
  29. package/dist/errors/mllp-invalid-response-error.d.ts.map +1 -0
  30. package/dist/errors/mllp-send-aborted-error.d.ts +12 -0
  31. package/dist/errors/mllp-send-aborted-error.d.ts.map +1 -0
  32. package/dist/errors/mllp-send-timeout-error.d.ts +13 -0
  33. package/dist/errors/mllp-send-timeout-error.d.ts.map +1 -0
  34. package/dist/errors/nak-exception.d.ts +17 -0
  35. package/dist/errors/nak-exception.d.ts.map +1 -0
  36. package/dist/events.d.ts +35 -0
  37. package/dist/events.d.ts.map +1 -0
  38. package/dist/index.d.ts +13 -20
  39. package/dist/index.d.ts.map +1 -1
  40. package/dist/index.js +805 -3
  41. package/dist/index.js.map +1 -0
  42. package/dist/messages.d.ts +55 -0
  43. package/dist/messages.d.ts.map +1 -0
  44. package/dist/reconnect.d.ts +18 -0
  45. package/dist/reconnect.d.ts.map +1 -0
  46. package/dist/runtime/node.d.ts +39 -0
  47. package/dist/runtime/node.d.ts.map +1 -0
  48. package/dist/runtime/node.js +100 -0
  49. package/dist/runtime/node.js.map +1 -0
  50. package/dist/session.d.ts +63 -0
  51. package/dist/session.d.ts.map +1 -0
  52. package/dist/state.d.ts +39 -0
  53. package/dist/state.d.ts.map +1 -0
  54. package/dist/types.d.ts +109 -0
  55. package/dist/types.d.ts.map +1 -0
  56. package/dist/utils.d.ts +9 -0
  57. package/dist/utils.d.ts.map +1 -0
  58. package/package.json +22 -48
  59. package/dist/client.js +0 -549
  60. package/dist/client.js.map +0 -1
  61. package/dist/core/acknowledgment.d.ts +0 -103
  62. package/dist/core/acknowledgment.d.ts.map +0 -1
  63. package/dist/core/client.d.ts +0 -256
  64. package/dist/core/client.d.ts.map +0 -1
  65. package/dist/core/connect.d.ts +0 -101
  66. package/dist/core/connect.d.ts.map +0 -1
  67. package/dist/core/errors.d.ts +0 -81
  68. package/dist/core/errors.d.ts.map +0 -1
  69. package/dist/core/index.d.ts +0 -23
  70. package/dist/core/index.d.ts.map +0 -1
  71. package/dist/core/index.js +0 -2
  72. package/dist/core/internal/ack-parser-stream.d.ts +0 -22
  73. package/dist/core/internal/ack-parser-stream.d.ts.map +0 -1
  74. package/dist/core/internal/subscribe-abort.d.ts +0 -30
  75. package/dist/core/internal/subscribe-abort.d.ts.map +0 -1
  76. package/dist/node.js +0 -214
  77. package/dist/node.js.map +0 -1
  78. package/dist/runtimes/internal/node-error-mapping.d.ts +0 -68
  79. package/dist/runtimes/internal/node-error-mapping.d.ts.map +0 -1
  80. package/dist/runtimes/node.d.ts +0 -47
  81. package/dist/runtimes/node.d.ts.map +0 -1
  82. package/dist/runtimes/node.js +0 -3
  83. package/dist/runtimes/workers.d.ts +0 -64
  84. package/dist/runtimes/workers.d.ts.map +0 -1
  85. package/dist/runtimes/workers.js +0 -143
  86. package/dist/runtimes/workers.js.map +0 -1
@@ -1,143 +0,0 @@
1
- import { i as MllpClientErrorCode, n as subscribeAbort, r as MllpClientError, t as MllpClient$1 } from "../client.js";
2
- import { connect } from "cloudflare:sockets";
3
- //#region src/runtimes/workers.ts
4
- /**
5
- * Cloudflare Workers runtime adapter for `@glion/mllp-client`.
6
- *
7
- * Re-exports the core `MllpClient` pre-wired with a `connect`
8
- * implementation that uses the `cloudflare:sockets` API. Application
9
- * code on Workers imports from this entry:
10
- *
11
- * ```ts
12
- * import { MllpClient } from "@glion/mllp-client/workers";
13
- *
14
- * const client = new MllpClient({ host: "127.0.0.1", port: 2575 });
15
- * const ack = await client.send(rawHl7Message);
16
- * ```
17
- *
18
- * The `cloudflare:sockets` module is only available inside the
19
- * Workers (`workerd`) runtime. Importing this entry from a Node or
20
- * Deno build will fail at module-resolution time — that's the
21
- * intended behaviour, since each runtime should pick its own
22
- * adapter.
23
- *
24
- * @module
25
- */
26
- /**
27
- * MLLP client pre-wired with the Cloudflare Workers connector.
28
- * API-identical to the core class — the only difference is that
29
- * callers do not need to supply `connect`.
30
- */
31
- var MllpClient = class extends MllpClient$1 {
32
- constructor(options) {
33
- super({
34
- ...options,
35
- connect: workersConnect
36
- });
37
- }
38
- };
39
- /**
40
- * `MllpConnect` implementation backed by Cloudflare's
41
- * `cloudflare:sockets` module. Returns a {@link MllpDuplexStream} that
42
- * mirrors the Worker socket's already-Web-Streams shape.
43
- *
44
- * TLS is on iff `params.tls` is set (the core normalises `tls: true`
45
- * to `tls: {}` before reaching the adapter). Workers exposes a small
46
- * subset of TLS options compared to Node — `ca`, `cert`, `key`,
47
- * `passphrase`, and `servername` cannot be supplied programmatically
48
- * and are rejected with `INVALID_INPUT`. `insecure: true` is also
49
- * rejected: on Node it means "TLS on, certificate verification off",
50
- * but Workers does not expose a way to disable verification, so
51
- * silently downgrading to plain TCP would be a security regression.
52
- * Callers wanting plain TCP must pass `tls: false` (the constructor
53
- * defaults `tls` to `true`).
54
- *
55
- * **Error-code divergence vs. the Node adapter.** `cloudflare:sockets`
56
- * does not surface enough detail to distinguish a TLS handshake
57
- * failure from a TCP connect failure. Both surface here as
58
- * `CONNECTION_REFUSED`. The Node adapter, by contrast, sniffs the
59
- * underlying error and routes handshake failures to
60
- * `TLS_HANDSHAKE_FAILED`. Callers writing cross-runtime code that
61
- * switches on `error.code` must accept this asymmetry, or treat
62
- * `CONNECTION_REFUSED` as the supertype.
63
- */
64
- const workersConnect = async (params) => {
65
- rejectUnsupportedTlsMaterial(params.tls);
66
- const socket = connect({
67
- hostname: params.host,
68
- port: params.port
69
- }, {
70
- allowHalfOpen: true,
71
- secureTransport: params.tls ? "on" : "off"
72
- });
73
- const dispose = params.signal ? subscribeAbort(params.signal, () => {
74
- socket.close().catch(() => {});
75
- }) : null;
76
- try {
77
- await socket.opened;
78
- } catch (error) {
79
- if (params.signal?.aborted) throw toAbortError(params.signal.reason, params.host, params.port, error);
80
- const wrapped = error instanceof Error ? error : new Error(String(error));
81
- throw new MllpClientError(MllpClientErrorCode.CONNECTION_REFUSED, `Could not connect to ${params.host}:${params.port}: ${wrapped.message}`, { cause: wrapped });
82
- } finally {
83
- dispose?.();
84
- }
85
- return {
86
- async close() {
87
- try {
88
- await socket.close();
89
- } catch {}
90
- },
91
- readable: socket.readable,
92
- writable: socket.writable
93
- };
94
- };
95
- /**
96
- * Translate an abort `reason` from the connect-phase signal into a typed
97
- * {@link MllpClientError}. Mirrors the precedence in `normaliseSendError` in
98
- * `core/client.ts`: typed reasons pass through, anything else maps to TIMEOUT
99
- * with the reason chained as `cause`.
100
- */
101
- function toAbortError(reason, host, port, fallbackCause) {
102
- if (reason instanceof MllpClientError) return reason;
103
- let cause;
104
- if (reason instanceof Error) cause = reason;
105
- else if (fallbackCause instanceof Error) cause = fallbackCause;
106
- return new MllpClientError(MllpClientErrorCode.TIMEOUT, `Connect to ${host}:${port} aborted`, { cause });
107
- }
108
- /**
109
- * Reject TLS configuration the Workers runtime cannot honour.
110
- * Surfacing the mismatch as `INVALID_INPUT` is more honest than
111
- * silently dropping the material — operators who supply a custom CA,
112
- * client cert, key, passphrase, or servername expect it to take
113
- * effect.
114
- *
115
- * `insecure: true` is rejected separately because the rationale is
116
- * different: Workers cannot disable certificate verification
117
- * independently, and silently dropping to plain TCP would downgrade an
118
- * explicitly TLS-typed configuration to plaintext — a runtime-dependent
119
- * security regression vs. the Node adapter.
120
- *
121
- * For all rejected fields the error message names the field, points to
122
- * the Cloudflare TCP-sockets docs, and lists the platform-side
123
- * mechanisms (Hyperdrive, Worker bindings, upstream TLS termination)
124
- * so an operator porting from Node has a concrete next step.
125
- */
126
- function rejectUnsupportedTlsMaterial(tls) {
127
- if (!tls) return;
128
- const unsupported = [];
129
- if (tls.ca) unsupported.push("ca");
130
- if (tls.cert) unsupported.push("cert");
131
- if (tls.key) unsupported.push("key");
132
- if (tls.passphrase !== void 0) unsupported.push("passphrase");
133
- if (tls.servername !== void 0) unsupported.push("servername");
134
- if (unsupported.length > 0) {
135
- const fields = unsupported.map((f) => `\`tls.${f}\``).join(", ");
136
- throw new MllpClientError(MllpClientErrorCode.INVALID_INPUT, `Cloudflare Workers does not accept programmatic TLS material (${fields}). Configure TLS via the Cloudflare platform — Hyperdrive, Worker bindings, or terminate TLS upstream of the receiver. See https://developers.cloudflare.com/workers/runtime-apis/tcp-sockets/`);
137
- }
138
- if (tls.insecure === true) throw new MllpClientError(MllpClientErrorCode.INVALID_INPUT, "Cloudflare Workers does not support disabling TLS certificate verification (`tls.insecure: true`). Pass `tls: false` for plain TCP (caller takes the security trade-off explicitly), or terminate TLS upstream of the receiver via the Cloudflare platform. See https://developers.cloudflare.com/workers/runtime-apis/tcp-sockets/");
139
- }
140
- //#endregion
141
- export { MllpClient, MllpClientError, MllpClientErrorCode, workersConnect };
142
-
143
- //# sourceMappingURL=workers.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"workers.js","names":["CoreMllpClient","workerSocketConnect"],"sources":["../../src/runtimes/workers.ts"],"sourcesContent":["/**\n * Cloudflare Workers runtime adapter for `@glion/mllp-client`.\n *\n * Re-exports the core `MllpClient` pre-wired with a `connect`\n * implementation that uses the `cloudflare:sockets` API. Application\n * code on Workers imports from this entry:\n *\n * ```ts\n * import { MllpClient } from \"@glion/mllp-client/workers\";\n *\n * const client = new MllpClient({ host: \"127.0.0.1\", port: 2575 });\n * const ack = await client.send(rawHl7Message);\n * ```\n *\n * The `cloudflare:sockets` module is only available inside the\n * Workers (`workerd`) runtime. Importing this entry from a Node or\n * Deno build will fail at module-resolution time — that's the\n * intended behaviour, since each runtime should pick its own\n * adapter.\n *\n * @module\n */\n\n// `cloudflare:sockets` is a runtime-provided module — its shape is\n// declared in `./cloudflare-sockets.d.ts`. See that file for why we\n// do not use `@cloudflare/workers-types` (it pollutes Web Streams\n// globals project-wide, which breaks the Node adapter's\n// `node:stream/web` types).\nimport { connect as workerSocketConnect } from \"cloudflare:sockets\";\n\nimport { MllpClient as CoreMllpClient } from \"../core/client\";\nimport type {\n BoundMllpClientOptions,\n MllpClientTlsOptions,\n} from \"../core/client\";\nimport type { MllpConnect, MllpDuplexStream } from \"../core/connect\";\nimport { MllpClientError, MllpClientErrorCode } from \"../core/errors\";\nimport { subscribeAbort } from \"../core/internal/subscribe-abort\";\n\n// ---------------------------------------------------------------------------\n// Public class\n// ---------------------------------------------------------------------------\n\n/**\n * MLLP client pre-wired with the Cloudflare Workers connector.\n * API-identical to the core class — the only difference is that\n * callers do not need to supply `connect`.\n */\nexport class MllpClient extends CoreMllpClient {\n constructor(options: BoundMllpClientOptions) {\n super({ ...options, connect: workersConnect });\n }\n}\n\n// ---------------------------------------------------------------------------\n// Re-exports\n// ---------------------------------------------------------------------------\n\nexport type { Acknowledgment } from \"../core/acknowledgment\";\nexport type {\n MllpConnect,\n MllpConnectParams,\n MllpDuplexStream,\n} from \"../core/connect\";\nexport type {\n BoundMllpClientOptions,\n MllpClientOptions,\n MllpClientTlsOptions,\n SendMode,\n SendOptions,\n} from \"../core/client\";\nexport { MllpClientError, MllpClientErrorCode } from \"../core/errors\";\n\n// ---------------------------------------------------------------------------\n// Workers connector\n// ---------------------------------------------------------------------------\n\n/**\n * `MllpConnect` implementation backed by Cloudflare's\n * `cloudflare:sockets` module. Returns a {@link MllpDuplexStream} that\n * mirrors the Worker socket's already-Web-Streams shape.\n *\n * TLS is on iff `params.tls` is set (the core normalises `tls: true`\n * to `tls: {}` before reaching the adapter). Workers exposes a small\n * subset of TLS options compared to Node — `ca`, `cert`, `key`,\n * `passphrase`, and `servername` cannot be supplied programmatically\n * and are rejected with `INVALID_INPUT`. `insecure: true` is also\n * rejected: on Node it means \"TLS on, certificate verification off\",\n * but Workers does not expose a way to disable verification, so\n * silently downgrading to plain TCP would be a security regression.\n * Callers wanting plain TCP must pass `tls: false` (the constructor\n * defaults `tls` to `true`).\n *\n * **Error-code divergence vs. the Node adapter.** `cloudflare:sockets`\n * does not surface enough detail to distinguish a TLS handshake\n * failure from a TCP connect failure. Both surface here as\n * `CONNECTION_REFUSED`. The Node adapter, by contrast, sniffs the\n * underlying error and routes handshake failures to\n * `TLS_HANDSHAKE_FAILED`. Callers writing cross-runtime code that\n * switches on `error.code` must accept this asymmetry, or treat\n * `CONNECTION_REFUSED` as the supertype.\n */\nexport const workersConnect: MllpConnect = async (params) => {\n rejectUnsupportedTlsMaterial(params.tls);\n\n const socket = workerSocketConnect(\n { hostname: params.host, port: params.port },\n {\n allowHalfOpen: true,\n secureTransport: params.tls ? \"on\" : \"off\",\n }\n );\n\n // Honour the caller's abort signal — when it fires we close the\n // pending socket so `socket.opened` rejects with the abort.\n const dispose = params.signal\n ? subscribeAbort(params.signal, () => {\n // oxlint-disable-next-line promise/prefer-await-to-then\n socket.close().catch(() => {\n /* socket already closing — non-actionable */\n });\n })\n : null;\n\n try {\n await socket.opened;\n } catch (error) {\n if (params.signal?.aborted) {\n throw toAbortError(params.signal.reason, params.host, params.port, error);\n }\n const wrapped = error instanceof Error ? error : new Error(String(error));\n throw new MllpClientError(\n MllpClientErrorCode.CONNECTION_REFUSED,\n `Could not connect to ${params.host}:${params.port}: ${wrapped.message}`,\n { cause: wrapped }\n );\n } finally {\n dispose?.();\n }\n\n return {\n // The contract requires `close()` to resolve (never reject) and to\n // be idempotent. workerd's `socket.close()` is genuinely async; we\n // await it and swallow any rejection so the contract holds.\n // Teardown errors at this point are non-actionable — the request\n // lifecycle is already ending.\n async close() {\n try {\n await socket.close();\n } catch {\n /* idempotent close or close-time error — non-actionable */\n }\n },\n readable: socket.readable,\n writable: socket.writable,\n };\n};\n\n/**\n * Translate an abort `reason` from the connect-phase signal into a typed\n * {@link MllpClientError}. Mirrors the precedence in `normaliseSendError` in\n * `core/client.ts`: typed reasons pass through, anything else maps to TIMEOUT\n * with the reason chained as `cause`.\n */\nfunction toAbortError(\n reason: unknown,\n host: string,\n port: number,\n fallbackCause: unknown\n): MllpClientError {\n if (reason instanceof MllpClientError) {\n return reason;\n }\n let cause: Error | undefined;\n if (reason instanceof Error) {\n cause = reason;\n } else if (fallbackCause instanceof Error) {\n cause = fallbackCause;\n }\n return new MllpClientError(\n MllpClientErrorCode.TIMEOUT,\n `Connect to ${host}:${port} aborted`,\n { cause }\n );\n}\n\n/**\n * Reject TLS configuration the Workers runtime cannot honour.\n * Surfacing the mismatch as `INVALID_INPUT` is more honest than\n * silently dropping the material — operators who supply a custom CA,\n * client cert, key, passphrase, or servername expect it to take\n * effect.\n *\n * `insecure: true` is rejected separately because the rationale is\n * different: Workers cannot disable certificate verification\n * independently, and silently dropping to plain TCP would downgrade an\n * explicitly TLS-typed configuration to plaintext — a runtime-dependent\n * security regression vs. the Node adapter.\n *\n * For all rejected fields the error message names the field, points to\n * the Cloudflare TCP-sockets docs, and lists the platform-side\n * mechanisms (Hyperdrive, Worker bindings, upstream TLS termination)\n * so an operator porting from Node has a concrete next step.\n */\nfunction rejectUnsupportedTlsMaterial(\n tls: MllpClientTlsOptions | undefined\n): void {\n if (!tls) {\n return;\n }\n\n const unsupported: string[] = [];\n if (tls.ca) {\n unsupported.push(\"ca\");\n }\n if (tls.cert) {\n unsupported.push(\"cert\");\n }\n if (tls.key) {\n unsupported.push(\"key\");\n }\n if (tls.passphrase !== undefined) {\n unsupported.push(\"passphrase\");\n }\n if (tls.servername !== undefined) {\n unsupported.push(\"servername\");\n }\n\n if (unsupported.length > 0) {\n const fields = unsupported.map((f) => `\\`tls.${f}\\``).join(\", \");\n throw new MllpClientError(\n MllpClientErrorCode.INVALID_INPUT,\n `Cloudflare Workers does not accept programmatic TLS material (${fields}). Configure TLS via the Cloudflare platform — Hyperdrive, Worker bindings, or terminate TLS upstream of the receiver. See https://developers.cloudflare.com/workers/runtime-apis/tcp-sockets/`\n );\n }\n\n if (tls.insecure === true) {\n throw new MllpClientError(\n MllpClientErrorCode.INVALID_INPUT,\n \"Cloudflare Workers does not support disabling TLS certificate verification (`tls.insecure: true`). Pass `tls: false` for plain TCP (caller takes the security trade-off explicitly), or terminate TLS upstream of the receiver via the Cloudflare platform. See https://developers.cloudflare.com/workers/runtime-apis/tcp-sockets/\"\n );\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAgDA,IAAa,aAAb,cAAgCA,aAAe;CAC7C,YAAY,SAAiC;AAC3C,QAAM;GAAE,GAAG;GAAS,SAAS;GAAgB,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAoDlD,MAAa,iBAA8B,OAAO,WAAW;AAC3D,8BAA6B,OAAO,IAAI;CAExC,MAAM,SAASC,QACb;EAAE,UAAU,OAAO;EAAM,MAAM,OAAO;EAAM,EAC5C;EACE,eAAe;EACf,iBAAiB,OAAO,MAAM,OAAO;EACtC,CACF;CAID,MAAM,UAAU,OAAO,SACnB,eAAe,OAAO,cAAc;AAElC,SAAO,OAAO,CAAC,YAAY,GAEzB;GACF,GACF;AAEJ,KAAI;AACF,QAAM,OAAO;UACN,OAAO;AACd,MAAI,OAAO,QAAQ,QACjB,OAAM,aAAa,OAAO,OAAO,QAAQ,OAAO,MAAM,OAAO,MAAM,MAAM;EAE3E,MAAM,UAAU,iBAAiB,QAAQ,QAAQ,IAAI,MAAM,OAAO,MAAM,CAAC;AACzE,QAAM,IAAI,gBACR,oBAAoB,oBACpB,wBAAwB,OAAO,KAAK,GAAG,OAAO,KAAK,IAAI,QAAQ,WAC/D,EAAE,OAAO,SAAS,CACnB;WACO;AACR,aAAW;;AAGb,QAAO;EAML,MAAM,QAAQ;AACZ,OAAI;AACF,UAAM,OAAO,OAAO;WACd;;EAIV,UAAU,OAAO;EACjB,UAAU,OAAO;EAClB;;;;;;;;AASH,SAAS,aACP,QACA,MACA,MACA,eACiB;AACjB,KAAI,kBAAkB,gBACpB,QAAO;CAET,IAAI;AACJ,KAAI,kBAAkB,MACpB,SAAQ;UACC,yBAAyB,MAClC,SAAQ;AAEV,QAAO,IAAI,gBACT,oBAAoB,SACpB,cAAc,KAAK,GAAG,KAAK,WAC3B,EAAE,OAAO,CACV;;;;;;;;;;;;;;;;;;;;AAqBH,SAAS,6BACP,KACM;AACN,KAAI,CAAC,IACH;CAGF,MAAM,cAAwB,EAAE;AAChC,KAAI,IAAI,GACN,aAAY,KAAK,KAAK;AAExB,KAAI,IAAI,KACN,aAAY,KAAK,OAAO;AAE1B,KAAI,IAAI,IACN,aAAY,KAAK,MAAM;AAEzB,KAAI,IAAI,eAAe,KAAA,EACrB,aAAY,KAAK,aAAa;AAEhC,KAAI,IAAI,eAAe,KAAA,EACrB,aAAY,KAAK,aAAa;AAGhC,KAAI,YAAY,SAAS,GAAG;EAC1B,MAAM,SAAS,YAAY,KAAK,MAAM,SAAS,EAAE,IAAI,CAAC,KAAK,KAAK;AAChE,QAAM,IAAI,gBACR,oBAAoB,eACpB,iEAAiE,OAAO,gMACzE;;AAGH,KAAI,IAAI,aAAa,KACnB,OAAM,IAAI,gBACR,oBAAoB,eACpB,sUACD"}