@glion/mllp-client 0.19.0 → 0.21.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 @@
1
+ {"version":3,"file":"workers.d.ts","sourceRoot":"","sources":["../../src/runtime/workers.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAKH,OAAO,KAAK,EAAE,UAAU,EAAe,MAAM,UAAU,CAAC;AAIxD,MAAM,WAAW,oBAAoB;IACnC,iDAAiD;IACjD,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,qCAAqC;IACrC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB;;;;;;;;OAQG;IACH,QAAQ,CAAC,GAAG,CAAC,EAAE,OAAO,CAAC;CACxB;AAED;;;;;;GAMG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,oBAAoB,GAAG,UAAU,CAoDpE"}
@@ -0,0 +1,70 @@
1
+ import { t as MllpInvalidOptionError } from "../chunks/mllp-invalid-option-error.js";
2
+ import { connect } from "cloudflare:sockets";
3
+ //#region src/runtime/workers.ts
4
+ /**
5
+ * Cloudflare Workers runtime adapter for `MllpClient`: one socket over
6
+ * `cloudflare:sockets`. Framing and stream ownership belong to the layer
7
+ * above.
8
+ *
9
+ * @module
10
+ */
11
+ /**
12
+ * A TCP socket to one remote system, over `cloudflare:sockets`.
13
+ *
14
+ * Each `connect()` opens a fresh socket.
15
+ *
16
+ * @throws {MllpInvalidOptionError} `tls` is not a boolean.
17
+ */
18
+ function workersSocket(opts) {
19
+ const { tls = false } = opts;
20
+ if (typeof tls !== "boolean") throw new MllpInvalidOptionError("tls must be true or false on Workers, which applies no CA, client certificate, or server name.");
21
+ let open;
22
+ return {
23
+ async close() {
24
+ const socket = open;
25
+ open = void 0;
26
+ await socket?.close();
27
+ },
28
+ async connect(signal) {
29
+ signal.throwIfAborted();
30
+ const socket = connect({
31
+ hostname: opts.host,
32
+ port: opts.port
33
+ }, {
34
+ allowHalfOpen: false,
35
+ secureTransport: tls ? "on" : "off"
36
+ });
37
+ const listener = new AbortController();
38
+ try {
39
+ await Promise.race([socket.opened, aborted(signal, listener.signal)]);
40
+ } catch (error) {
41
+ if (!signal.aborted) throw error;
42
+ socket.close().catch(() => {});
43
+ throw signal.reason;
44
+ } finally {
45
+ listener.abort();
46
+ }
47
+ open = socket;
48
+ return {
49
+ readable: socket.readable,
50
+ writable: socket.writable
51
+ };
52
+ }
53
+ };
54
+ }
55
+ /**
56
+ * Rejects with `signal.reason` once `signal` aborts, unless `until` aborts
57
+ * first.
58
+ */
59
+ function aborted(signal, until) {
60
+ return new Promise((_resolve, reject) => {
61
+ signal.addEventListener("abort", () => reject(signal.reason), {
62
+ once: true,
63
+ signal: until
64
+ });
65
+ });
66
+ }
67
+ //#endregion
68
+ export { workersSocket };
69
+
70
+ //# sourceMappingURL=workers.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"workers.js","names":[],"sources":["../../src/runtime/workers.ts"],"sourcesContent":["/**\n * Cloudflare Workers runtime adapter for `MllpClient`: one socket over\n * `cloudflare:sockets`. Framing and stream ownership belong to the layer\n * above.\n *\n * @module\n */\n\nimport { connect } from \"cloudflare:sockets\";\n\nimport { MllpInvalidOptionError } from \"../errors/mllp-invalid-option-error\";\nimport type { MllpSocket, MllpStreams } from \"../types\";\n\ntype Socket = ReturnType<typeof connect>;\n\nexport interface WorkersSocketOptions {\n /** Host name or address of the remote system. */\n readonly host: string;\n /** TCP port of the remote system. */\n readonly port: number;\n /**\n * TLS for the connection. `true`: verify the remote system's certificate\n * against the public CAs Workers trusts, for `host`. `false`: plain TCP.\n *\n * Workers takes no CA, client certificate, or server name. A remote system\n * MUST present a publicly trusted certificate for `host`.\n *\n * @default false\n */\n readonly tls?: boolean;\n}\n\n/**\n * A TCP socket to one remote system, over `cloudflare:sockets`.\n *\n * Each `connect()` opens a fresh socket.\n *\n * @throws {MllpInvalidOptionError} `tls` is not a boolean.\n */\nexport function workersSocket(opts: WorkersSocketOptions): MllpSocket {\n const { tls = false } = opts;\n if (typeof tls !== \"boolean\") {\n throw new MllpInvalidOptionError(\n \"tls must be true or false on Workers, which applies no CA, client certificate, or server name.\"\n );\n }\n let open: Socket | undefined;\n\n return {\n async close(): Promise<void> {\n const socket = open;\n open = undefined;\n await socket?.close();\n },\n\n async connect(signal: AbortSignal): Promise<MllpStreams> {\n signal.throwIfAborted();\n // `connect()` returns at once with the dial in progress; `opened`\n // settles when the handshake does.\n const socket = connect(\n { hostname: opts.host, port: opts.port },\n { allowHalfOpen: false, secureTransport: tls ? \"on\" : \"off\" }\n );\n // Raced, not awaited: `opened` alone would hold a cancelled attempt\n // until the dial times out. `listener` is the abort listener's\n // lifetime: aborting it removes the listener once the race is decided,\n // so nothing stays subscribed to `signal`.\n const listener = new AbortController();\n try {\n await Promise.race([socket.opened, aborted(signal, listener.signal)]);\n } catch (error) {\n // Two ways here: the dial failed (`opened` rejected), or the caller\n // cancelled. A failed dial is reported as it is. A cancelled one is\n // reported as the cancellation; its socket is closed without waiting,\n // since workerd settles that close only once the dial does, and then\n // with the dial's own failure, which nobody is left to act on.\n if (!signal.aborted) {\n throw error;\n }\n // oxlint-disable-next-line promise/prefer-await-to-then -- awaiting would wait out the dial\n socket.close().catch(() => {});\n throw signal.reason;\n } finally {\n listener.abort();\n }\n // Only an opened socket is held, so `close()` has something to end\n // exactly when `connect()` fulfilled.\n open = socket;\n return { readable: socket.readable, writable: socket.writable };\n },\n };\n}\n\n/**\n * Rejects with `signal.reason` once `signal` aborts, unless `until` aborts\n * first.\n */\nfunction aborted(signal: AbortSignal, until: AbortSignal): Promise<never> {\n // oxlint-disable-next-line promise/avoid-new -- wrapping an abort event\n return new Promise((_resolve, reject) => {\n signal.addEventListener(\"abort\", () => reject(signal.reason), {\n once: true,\n signal: until,\n });\n });\n}\n"],"mappings":";;;;;;;;;;;;;;;;;AAuCA,SAAgB,cAAc,MAAwC;CACpE,MAAM,EAAE,MAAM,UAAU;AACxB,KAAI,OAAO,QAAQ,UACjB,OAAM,IAAI,uBACR,iGACD;CAEH,IAAI;AAEJ,QAAO;EACL,MAAM,QAAuB;GAC3B,MAAM,SAAS;AACf,UAAO,KAAA;AACP,SAAM,QAAQ,OAAO;;EAGvB,MAAM,QAAQ,QAA2C;AACvD,UAAO,gBAAgB;GAGvB,MAAM,SAAS,QACb;IAAE,UAAU,KAAK;IAAM,MAAM,KAAK;IAAM,EACxC;IAAE,eAAe;IAAO,iBAAiB,MAAM,OAAO;IAAO,CAC9D;GAKD,MAAM,WAAW,IAAI,iBAAiB;AACtC,OAAI;AACF,UAAM,QAAQ,KAAK,CAAC,OAAO,QAAQ,QAAQ,QAAQ,SAAS,OAAO,CAAC,CAAC;YAC9D,OAAO;AAMd,QAAI,CAAC,OAAO,QACV,OAAM;AAGR,WAAO,OAAO,CAAC,YAAY,GAAG;AAC9B,UAAM,OAAO;aACL;AACR,aAAS,OAAO;;AAIlB,UAAO;AACP,UAAO;IAAE,UAAU,OAAO;IAAU,UAAU,OAAO;IAAU;;EAElE;;;;;;AAOH,SAAS,QAAQ,QAAqB,OAAoC;AAExE,QAAO,IAAI,SAAS,UAAU,WAAW;AACvC,SAAO,iBAAiB,eAAe,OAAO,OAAO,OAAO,EAAE;GAC5D,MAAM;GACN,QAAQ;GACT,CAAC;GACF"}
package/dist/state.d.ts CHANGED
@@ -22,18 +22,21 @@ export type State = {
22
22
  } | {
23
23
  readonly phase: "sending";
24
24
  readonly session: MllpSession;
25
- /** MSH-10 of the message waiting for its acknowledgment. */
26
- readonly controlId: string;
27
- /** Settles when the send is over, however it ended. */
28
- readonly done: Promise<unknown>;
29
25
  } | {
30
26
  readonly phase: "closing";
31
- readonly session: MllpSession;
32
- /** The send `close()` is waiting out. */
33
- readonly done: Promise<unknown>;
27
+ /** The failure the client is closing on, or `null` when the owner is. */
28
+ readonly reason: MllpClientError | null;
29
+ /** The session being ended, or `null` when it is the dial. */
30
+ readonly session: MllpSession | null;
31
+ /** Settles once the client is `closed`. */
32
+ readonly closed: Promise<unknown>;
34
33
  } | {
35
34
  readonly phase: "closed";
36
35
  /** The failure that closed the client, or `null` when the owner did. */
37
36
  readonly reason: MllpClientError | null;
38
37
  };
38
+ /** The phases with something open: the dial, or the session. */
39
+ export type Open = Extract<State, {
40
+ phase: "connecting" | "connected" | "sending";
41
+ }>;
39
42
  //# sourceMappingURL=state.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"state.d.ts","sourceRoot":"","sources":["../src/state.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,UAAU,CAAC;AAChD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AAE7C,MAAM,MAAM,KAAK,GACb;IAAE,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;CAAE,GAC1B;IACE,QAAQ,CAAC,KAAK,EAAE,YAAY,CAAC;IAC7B,gDAAgD;IAChD,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC,IAAI,CAAC,CAAC;IAChC,+DAA+D;IAC/D,QAAQ,CAAC,KAAK,EAAE,eAAe,CAAC;CACjC,GACD;IACE,QAAQ,CAAC,KAAK,EAAE,WAAW,CAAC;IAC5B,QAAQ,CAAC,OAAO,EAAE,WAAW,CAAC;CAC/B,GACD;IACE,QAAQ,CAAC,KAAK,EAAE,SAAS,CAAC;IAC1B,QAAQ,CAAC,OAAO,EAAE,WAAW,CAAC;IAC9B,4DAA4D;IAC5D,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,uDAAuD;IACvD,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC,OAAO,CAAC,CAAC;CACjC,GACD;IACE,QAAQ,CAAC,KAAK,EAAE,SAAS,CAAC;IAC1B,QAAQ,CAAC,OAAO,EAAE,WAAW,CAAC;IAC9B,yCAAyC;IACzC,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC,OAAO,CAAC,CAAC;CACjC,GACD;IACE,QAAQ,CAAC,KAAK,EAAE,QAAQ,CAAC;IACzB,wEAAwE;IACxE,QAAQ,CAAC,MAAM,EAAE,eAAe,GAAG,IAAI,CAAC;CACzC,CAAC"}
1
+ {"version":3,"file":"state.d.ts","sourceRoot":"","sources":["../src/state.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,UAAU,CAAC;AAChD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AAE7C,MAAM,MAAM,KAAK,GACb;IAAE,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;CAAE,GAC1B;IACE,QAAQ,CAAC,KAAK,EAAE,YAAY,CAAC;IAC7B,gDAAgD;IAChD,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC,IAAI,CAAC,CAAC;IAChC,+DAA+D;IAC/D,QAAQ,CAAC,KAAK,EAAE,eAAe,CAAC;CACjC,GACD;IACE,QAAQ,CAAC,KAAK,EAAE,WAAW,CAAC;IAC5B,QAAQ,CAAC,OAAO,EAAE,WAAW,CAAC;CAC/B,GACD;IACE,QAAQ,CAAC,KAAK,EAAE,SAAS,CAAC;IAC1B,QAAQ,CAAC,OAAO,EAAE,WAAW,CAAC;CAC/B,GACD;IACE,QAAQ,CAAC,KAAK,EAAE,SAAS,CAAC;IAC1B,yEAAyE;IACzE,QAAQ,CAAC,MAAM,EAAE,eAAe,GAAG,IAAI,CAAC;IACxC,8DAA8D;IAC9D,QAAQ,CAAC,OAAO,EAAE,WAAW,GAAG,IAAI,CAAC;IACrC,2CAA2C;IAC3C,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC,OAAO,CAAC,CAAC;CACnC,GACD;IACE,QAAQ,CAAC,KAAK,EAAE,QAAQ,CAAC;IACzB,wEAAwE;IACxE,QAAQ,CAAC,MAAM,EAAE,eAAe,GAAG,IAAI,CAAC;CACzC,CAAC;AAEN,gEAAgE;AAChE,MAAM,MAAM,IAAI,GAAG,OAAO,CACxB,KAAK,EACL;IAAE,KAAK,EAAE,YAAY,GAAG,WAAW,GAAG,SAAS,CAAA;CAAE,CAClD,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@glion/mllp-client",
3
- "version": "0.19.0",
3
+ "version": "0.21.0",
4
4
  "description": "Persistent, single-flight MLLP client for HL7v2",
5
5
  "keywords": [
6
6
  "health",
@@ -29,24 +29,30 @@
29
29
  "types": "./dist/index.d.ts",
30
30
  "exports": {
31
31
  ".": "./dist/index.js",
32
- "./node": "./dist/runtime/node.js"
32
+ "./node": "./dist/runtime/node.js",
33
+ "./workers": "./dist/runtime/workers.js"
33
34
  },
34
35
  "publishConfig": {
35
36
  "access": "public"
36
37
  },
37
38
  "dependencies": {
38
- "@glion/ack": "0.19.0",
39
- "@glion/ast": "0.19.0",
40
- "@glion/mllp-codec": "0.19.0",
41
- "@glion/to-hl7v2": "0.19.0",
42
- "@glion/util-charset": "0.19.0",
43
- "@glion/parser": "0.19.0",
44
- "@glion/util-query": "0.19.0"
39
+ "@glion/ack": "0.21.0",
40
+ "@glion/ast": "0.21.0",
41
+ "@glion/mllp-codec": "0.21.0",
42
+ "@glion/parser": "0.21.0",
43
+ "@glion/to-hl7v2": "0.21.0",
44
+ "@glion/util-charset": "0.21.0",
45
+ "@glion/util-query": "0.21.0"
45
46
  },
46
47
  "devDependencies": {
48
+ "@cloudflare/vitest-plugin": "1.3.7",
49
+ "@cloudflare/workers-types": "5.20261007.1",
50
+ "@stryker-mutator/core": "10.0.0",
51
+ "@stryker-mutator/vitest-runner": "10.0.0",
47
52
  "@types/node": "25.6.0",
48
53
  "@vitest/coverage-v8": "4.1.11",
49
54
  "publint": "0.3.18",
55
+ "selfsigned": "5.5.0",
50
56
  "tsdown": "0.21.10",
51
57
  "typescript": "6.0.3",
52
58
  "vitest": "4.1.11",
@@ -54,16 +60,27 @@
54
60
  "@glion/testing": "0.18.0",
55
61
  "@glion/tsconfig": "0.18.0"
56
62
  },
63
+ "peerDependencies": {
64
+ "@types/node": ">=22"
65
+ },
66
+ "peerDependenciesMeta": {
67
+ "@types/node": {
68
+ "optional": true
69
+ }
70
+ },
57
71
  "engines": {
58
72
  "node": ">=22"
59
73
  },
60
74
  "scripts": {
61
75
  "build": "tsdown && tsc --emitDeclarationOnly",
62
- "check-types": "tsc --noEmit && tsc --noEmit -p tests/tsconfig.json",
76
+ "check-types": "tsc --noEmit && tsc --noEmit -p tests/tsconfig.json && tsc -p tsconfig.cloudflare.json",
63
77
  "check:readme": "glion-check-readme",
78
+ "mutate": "stryker run",
79
+ "publint": "publint --strict",
64
80
  "test": "vitest run",
81
+ "test:cf": "vitest run --config vitest.workers.config.ts",
65
82
  "test:coverage": "vitest run --coverage",
66
- "test:watch": "vitest",
67
- "publint": "publint --strict"
83
+ "test:integration": "vitest run --config vitest.integration.config.ts",
84
+ "test:watch": "vitest"
68
85
  }
69
86
  }
@@ -1,14 +0,0 @@
1
- import { MllpClientError } from "./base";
2
- /**
3
- * `send()` was called while another message was still waiting for its
4
- * acknowledgment. The client sends one message at a time.
5
- */
6
- export declare class MllpAlreadySendingError extends MllpClientError {
7
- readonly name = "MllpAlreadySendingError";
8
- readonly code: "ALREADY_SENDING";
9
- readonly delivery = "not-sent";
10
- /** MSH-10 of the message that is still waiting. */
11
- readonly controlId: string;
12
- constructor(controlId: string);
13
- }
14
- //# sourceMappingURL=mllp-already-sending-error.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"mllp-already-sending-error.d.ts","sourceRoot":"","sources":["../../src/errors/mllp-already-sending-error.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAiB,MAAM,QAAQ,CAAC;AAExD;;;GAGG;AACH,qBAAa,uBAAwB,SAAQ,eAAe;IAC1D,SAAkB,IAAI,6BAA6B;IACnD,QAAQ,CAAC,IAAI,oBAAiC;IAC9C,QAAQ,CAAC,QAAQ,cAAc;IAC/B,mDAAmD;IACnD,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;gBAEf,SAAS,EAAE,MAAM;CAM9B"}