Skip to content

feat(mllp-client): Deno runtime adapter#615

Open
meleksomai wants to merge 1 commit intomainfrom
claude/mllp-client-deno-runtime
Open

feat(mllp-client): Deno runtime adapter#615
meleksomai wants to merge 1 commit intomainfrom
claude/mllp-client-deno-runtime

Conversation

@meleksomai
Copy link
Copy Markdown
Contributor

@meleksomai meleksomai commented Apr 29, 2026

Summary

Adds the Deno runtime adapter to @glion/mllp-client, backed by Deno.connect / Deno.connectTls. Now rebased onto main after PR #609 landed.

import { MllpClient } from "@glion/mllp-client/deno";

const client = new MllpClient({ host: "127.0.0.1", port: 2575 });
const ack = await client.send(rawHl7Message);

The user-facing API (new MllpClient({ host, port }).send(message), client.stream(...), MllpClientError, AckException) is identical to the Node adapter — only the import path changes.

Adapter-specific behaviour

  • TLS material accepted as string or Uint8Array. Non-string CA/cert/key is decoded to a PEM string before being handed to Deno.connectTls (which expects string / string[]).
  • tls.passphrase rejected with INVALID_INPUT. Deno's connectTls does not accept inline passphrases — decrypt the private key before passing it to the client.
  • tls.insecure: true rejected with INVALID_INPUT. Deno has no runtime flag to disable certificate verification; for self-signed local-dev scenarios, run Deno with --unsafely-ignore-certificate-errors=<host> instead.
  • Permission failures route to INVALID_INPUT. Deno.errors.PermissionDenied and NotCapable are mapped with a message naming the host:port the operator must add to --allow-net=…. (Otherwise these would be miscategorised as CONNECTION_REFUSED.)
  • Caller-supplied AbortSignal honoured at connect-phase. Pre-aborted signals short-circuit before Deno.connect runs (no socket allocated). A signal that fires during the open is mapped to TIMEOUT with the abort reason chained as cause.

Files of interest

File Purpose
packages/mllp-client/src/runtimes/deno.ts The adapter — denoConnect + the MllpClient subclass with denoConnect pre-bound
packages/mllp-client/test/deno.test.ts 9 mock-based unit tests
packages/mllp-client/package.json Adds deno conditional export at . and ./deno subpath
packages/mllp-client/tsdown.config.ts Adds runtimes/deno entry-point binding
packages/mllp-client/README.md Marks Deno as shipped in the runtime status table and adds the Deno code example

Tests

test/deno.test.ts monkey-patches globalThis.Deno so the adapter's wiring is exercised in plain Node vitest. Coverage:

  • TCP / TLS connect with parameter forwarding and PEM coercion of Uint8Array inputs.
  • tls.insecure: true rejection.
  • tls.passphrase rejection.
  • Deno.errors.PermissionDenied mapping with --allow-net=… hint.
  • Generic Deno.connect failure → CONNECTION_REFUSED.
  • conn.close() runs after a successful exchange.
  • Pre-aborted AbortSignal short-circuits before the runtime allocates a socket.

For end-to-end verification inside the actual Deno runtime, write a *.deno.test.ts file that runs via deno test against a real server. The MLLP exchange logic itself is covered by the runtime-free test/core.test.ts (already on main).

Test plan

  • CI: build, typecheck, test, lint pass
  • Smoke test in actual Deno runtime against a real HL7v2 receiver:
    • await client.send(message) resolves with AA.
    • TLS connect with a real CA.
    • Without --allow-net, the surfaced error is INVALID_INPUT and names the required permission flag.

https://claude.ai/code/session_01MvBEUcGkRokNw2GWYVHADg

@pkg-pr-new
Copy link
Copy Markdown

pkg-pr-new Bot commented Apr 29, 2026

Open in StackBlitz

@glion/ack

npm i https://pkg.pr.new/rethinkhealth/glion/@glion/ack@615

@glion/annotate-delimiters

npm i https://pkg.pr.new/rethinkhealth/glion/@glion/annotate-delimiters@615

@glion/annotate-profile-context

npm i https://pkg.pr.new/rethinkhealth/glion/@glion/annotate-profile-context@615

@glion/annotate-profile-datatypes

npm i https://pkg.pr.new/rethinkhealth/glion/@glion/annotate-profile-datatypes@615

@glion/annotate-profile-fields

npm i https://pkg.pr.new/rethinkhealth/glion/@glion/annotate-profile-fields@615

@glion/annotate-profile-fields-code-systems

npm i https://pkg.pr.new/rethinkhealth/glion/@glion/annotate-profile-fields-code-systems@615

@glion/annotate-profile-segments

npm i https://pkg.pr.new/rethinkhealth/glion/@glion/annotate-profile-segments@615

@glion/ast

npm i https://pkg.pr.new/rethinkhealth/glion/@glion/ast@615

@glion/builder

npm i https://pkg.pr.new/rethinkhealth/glion/@glion/builder@615

@glion/config

npm i https://pkg.pr.new/rethinkhealth/glion/@glion/config@615

@glion/decode-escapes

npm i https://pkg.pr.new/rethinkhealth/glion/@glion/decode-escapes@615

@glion/encode-escapes

npm i https://pkg.pr.new/rethinkhealth/glion/@glion/encode-escapes@615

@glion/cli

npm i https://pkg.pr.new/rethinkhealth/glion/@glion/cli@615

@glion/hl7v2

npm i https://pkg.pr.new/rethinkhealth/glion/@glion/hl7v2@615

@glion/jsonify

npm i https://pkg.pr.new/rethinkhealth/glion/@glion/jsonify@615

@glion/lint-max-message-size

npm i https://pkg.pr.new/rethinkhealth/glion/@glion/lint-max-message-size@615

@glion/lint-message-version

npm i https://pkg.pr.new/rethinkhealth/glion/@glion/lint-message-version@615

@glion/lint-no-trailing-empty-field

npm i https://pkg.pr.new/rethinkhealth/glion/@glion/lint-no-trailing-empty-field@615

@glion/lint-profile-events-segments-order

npm i https://pkg.pr.new/rethinkhealth/glion/@glion/lint-profile-events-segments-order@615

@glion/lint-profile-extra-components

npm i https://pkg.pr.new/rethinkhealth/glion/@glion/lint-profile-extra-components@615

@glion/lint-profile-extra-fields

npm i https://pkg.pr.new/rethinkhealth/glion/@glion/lint-profile-extra-fields@615

@glion/lint-profile-field-max-length

npm i https://pkg.pr.new/rethinkhealth/glion/@glion/lint-profile-field-max-length@615

@glion/lint-profile-field-repetition

npm i https://pkg.pr.new/rethinkhealth/glion/@glion/lint-profile-field-repetition@615

@glion/lint-profile-required-components

npm i https://pkg.pr.new/rethinkhealth/glion/@glion/lint-profile-required-components@615

@glion/lint-profile-required-fields

npm i https://pkg.pr.new/rethinkhealth/glion/@glion/lint-profile-required-fields@615

@glion/lint-profile-table-values

npm i https://pkg.pr.new/rethinkhealth/glion/@glion/lint-profile-table-values@615

@glion/lint-required-message-header

npm i https://pkg.pr.new/rethinkhealth/glion/@glion/lint-required-message-header@615

@glion/lint-segment-header-length

npm i https://pkg.pr.new/rethinkhealth/glion/@glion/lint-segment-header-length@615

@glion/mllp

npm i https://pkg.pr.new/rethinkhealth/glion/@glion/mllp@615

@glion/mllp-ack

npm i https://pkg.pr.new/rethinkhealth/glion/@glion/mllp-ack@615

@glion/mllp-client

npm i https://pkg.pr.new/rethinkhealth/glion/@glion/mllp-client@615

@glion/mllp-transport

npm i https://pkg.pr.new/rethinkhealth/glion/@glion/mllp-transport@615

@glion/parser

npm i https://pkg.pr.new/rethinkhealth/glion/@glion/parser@615

@glion/preset-annotate-profile-recommended

npm i https://pkg.pr.new/rethinkhealth/glion/@glion/preset-annotate-profile-recommended@615

@glion/preset-lint-profile-recommended

npm i https://pkg.pr.new/rethinkhealth/glion/@glion/preset-lint-profile-recommended@615

@glion/preset-lint-recommended

npm i https://pkg.pr.new/rethinkhealth/glion/@glion/preset-lint-recommended@615

@glion/profiles

npm i https://pkg.pr.new/rethinkhealth/glion/@glion/profiles@615

@glion/to-hl7v2

npm i https://pkg.pr.new/rethinkhealth/glion/@glion/to-hl7v2@615

@glion/util-query

npm i https://pkg.pr.new/rethinkhealth/glion/@glion/util-query@615

@glion/util-semver

npm i https://pkg.pr.new/rethinkhealth/glion/@glion/util-semver@615

@glion/util-timestamp

npm i https://pkg.pr.new/rethinkhealth/glion/@glion/util-timestamp@615

@glion/util-visit

npm i https://pkg.pr.new/rethinkhealth/glion/@glion/util-visit@615

@glion/utils

npm i https://pkg.pr.new/rethinkhealth/glion/@glion/utils@615

commit: 7d2faa6

@codecov
Copy link
Copy Markdown

codecov Bot commented Apr 29, 2026

Codecov Report

❌ Patch coverage is 88.88889% with 6 lines in your changes missing coverage. Please review.
✅ Project coverage is 93.75%. Comparing base (5e3d97e) to head (7d2faa6).

Files with missing lines Patch % Lines
packages/mllp-client/src/runtimes/deno.ts 88.88% 6 Missing ⚠️
Additional details and impacted files

Impacted file tree graph

@@            Coverage Diff             @@
##             main     #615      +/-   ##
==========================================
- Coverage   93.81%   93.75%   -0.07%     
==========================================
  Files         134      135       +1     
  Lines        4093     4147      +54     
  Branches     1051     1069      +18     
==========================================
+ Hits         3840     3888      +48     
- Misses        253      259       +6     
Files with missing lines Coverage Δ
packages/mllp-client/src/runtimes/deno.ts 88.88% <88.88%> (ø)
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@codspeed-hq
Copy link
Copy Markdown

codspeed-hq Bot commented Apr 29, 2026

Merging this PR will not alter performance

✅ 21 untouched benchmarks


Comparing claude/mllp-client-deno-runtime (7d2faa6) with main (5e3d97e)

Open in CodSpeed

meleksomai pushed a commit that referenced this pull request Apr 29, 2026
Replaces the placeholder "(separate PR)" labels in the runtime status
table with concrete PR numbers so readers can navigate to the stacked
work.

https://claude.ai/code/session_01MvBEUcGkRokNw2GWYVHADg
Base automatically changed from claude/create-mllp-client-package-uYzCj to main April 29, 2026 16:01
Adds @glion/mllp-client/deno backed by Deno.connect / Deno.connectTls.
The user-facing API matches the Node adapter exactly — only the
import path changes.

Stacked on PR #609 (mllp-client foundation + Node adapter). Will
rebase onto main once #609 merges.

Adapter-specific behaviour:
- TLS material accepted as string OR Uint8Array; non-string inputs
  are decoded to PEM before being handed to Deno.connectTls.
- tls.passphrase rejected with INVALID_INPUT (Deno does not accept
  inline passphrases).
- tls.insecure: true rejected with INVALID_INPUT (Deno has no
  runtime flag for this; use --unsafely-ignore-certificate-errors).
- Deno.errors.PermissionDenied / NotCapable route to INVALID_INPUT
  with a message naming --allow-net=host:port.
- AbortSignal honoured at connect-phase; pre-aborted signals
  short-circuit before allocating a socket.

Tests: test/deno.test.ts monkey-patches globalThis.Deno so the
adapter wiring is exercisable in plain Node vitest. 9 tests cover
TCP/TLS connect, TLS option coercion, all three INVALID_INPUT
rejections, permission mapping, conn.close after exchange, and
pre-aborted-signal short-circuit.

Total: 54 -> 63 tests on this branch (the +9 are deno-only).
Lint, typecheck, build all clean.

https://claude.ai/code/session_01MvBEUcGkRokNw2GWYVHADg
@meleksomai meleksomai force-pushed the claude/mllp-client-deno-runtime branch from 0922f18 to 7d2faa6 Compare April 29, 2026 16:06
@changeset-bot
Copy link
Copy Markdown

changeset-bot Bot commented Apr 29, 2026

🦋 Changeset detected

Latest commit: 7d2faa6

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 51 packages
Name Type
@glion/mllp-client Minor
@glion/benchmarks Minor
@glion/ack Minor
@glion/annotate-delimiters Minor
@glion/annotate-profile-context Minor
@glion/annotate-profile-datatypes Minor
@glion/annotate-profile-fields-code-systems Minor
@glion/annotate-profile-fields Minor
@glion/annotate-profile-segments Minor
@glion/ast Minor
@glion/builder Minor
@glion/config Minor
@glion/decode-escapes Minor
@glion/encode-escapes Minor
@glion/cli Minor
@glion/hl7v2 Minor
@glion/jsonify Minor
@glion/lint-max-message-size Minor
@glion/lint-message-version Minor
@glion/lint-no-trailing-empty-field Minor
@glion/lint-profile-events-segments-order Minor
@glion/lint-profile-extra-components Minor
@glion/lint-profile-extra-fields Minor
@glion/lint-profile-field-max-length Minor
@glion/lint-profile-field-repetition Minor
@glion/lint-profile-required-components Minor
@glion/lint-profile-required-fields Minor
@glion/lint-profile-table-values Minor
@glion/lint-required-message-header Minor
@glion/lint-segment-header-length Minor
@glion/mllp-ack Minor
@glion/mllp-transport Minor
@glion/mllp Minor
@glion/parser Minor
@glion/preset-annotate-profile-recommended Minor
@glion/preset-lint-profile-recommended Minor
@glion/preset-lint-recommended Minor
@glion/profiles Minor
@glion/to-hl7v2 Minor
@glion/util-query Minor
@glion/util-semver Minor
@glion/util-timestamp Minor
@glion/util-visit Minor
@glion/utils Minor
@glion/qa Minor
@glion/check-readme Minor
@glion/testing Minor
@glion/tsconfig Minor
mllp-send-receive Patch
glion-bun Patch
glion-node Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@meleksomai meleksomai changed the title feat(mllp-client): Deno runtime adapter (stacked on #609) feat(mllp-client): Deno runtime adapter Apr 29, 2026
meleksomai pushed a commit that referenced this pull request Apr 29, 2026
Adds explicit pnpm scripts for each runtime project so the test
matrix is discoverable from package.json:

- test:node — runs the Node adapter + runtime-free core tests
- test:cf  — runs the workerd-based tests
- test     — unchanged, runs both projects (plain `vitest run`)

test:deno and test:bun are not added on this PR. The Deno PR (#615)
will add test:deno once its tests are converted from mocked
globalThis.Deno to running inside actual Deno (mirroring what this
PR did for Workers). Bun support is currently exercised through
test:node because the Node adapter is the codepath Bun uses.

https://claude.ai/code/session_01MvBEUcGkRokNw2GWYVHADg
meleksomai pushed a commit that referenced this pull request Apr 30, 2026
BREAKING CHANGE: Empty fields, repetitions, and components are now always
represented with `children: []`. The `experimental.emptyMode` setting (which
toggled between "legacy" full-skeleton-with-empty-string-leaf and "empty"
empty-children-array) is removed entirely.

Why this matters now
====================
Every @glion/builder factory call (f(), r(), c()) was routing through
loadConfig() from @glion/config to decide which AST shape to produce. That
imported cosmiconfig + Node-only modules (fs, path, os, crypto, module, url)
into every consumer's bundle:

  @glion/mllp-client → @glion/ack → @glion/builder
                                       → @glion/config (loadConfig)
                                       → cosmiconfig
                                       → fs, path, os, crypto, ...

This broke runtime portability: Workers and Deno bundles dragged in Node
builtins they couldn't resolve, even though the harness/runtime never invoked
config-loading code. The architectural fix is to remove disk-based config
discovery from leaf factory functions; sunsetting `legacy` mode is the
cleanest way to do that, since it was the only thing the lookup gated.

Migration
=========

Consumers branching on placeholder leaves of empty fields:

    -if (field.children[0]?.children[0]?.children[0]?.value === "") { ... }
    +if (field.children.length === 0) { ... }

Config files carrying `experimental.emptyMode` are now rejected by the
@glion/config schema validator. Remove that block from .hl7v2rc.* files.

@glion/util-query's `value()` helper already returns `null` for empty
children — most consumers using it need no change.

What changed
============

- @glion/builder: dropped @glion/config dependency entirely; f()/r()/c() now
  always emit `children: []` for empty inputs.
- @glion/parser: dropped emptyMode plumbing from parser.ts/processor.ts/
  types.ts. parseHL7v2's settings argument no longer accepts
  experimental.emptyMode.
- @glion/config: removed ExperimentalSchema from the settings schema. The
  `loadConfig`/`loadConfigAsync` API is unchanged but no longer exposes any
  experimental keys.
- @glion/util-visit: deleted the test-helpers + legacy/empty fixture configs;
  the visit() implementation was already mode-agnostic.
- @glion/jsonify: updated the runtime serializer to materialize empty fields/
  repetitions as "" (preserving the existing JSON output shape — empty
  children now flow through here, where previously legacy always produced a
  full skeleton with "" leaves).

Side effects
============
- @glion/builder/dist/index.js no longer contains `import "@glion/config"`;
  consumers' bundles shrink (cosmiconfig + dependencies dropped).
- The Workers and Deno runtime adapters of @glion/mllp-client (PR #615,
  #616) bundle cleanly without `nodejs_compat` polyfills; the cosmiconfig
  bundle leak is resolved.

Tests
=====
All affected packages green:
- @glion/builder: 25 tests
- @glion/parser:  56 tests (parser.legacy.test.ts + processor.legacy.test.ts
                   deleted; their behaviour is now the default)
- @glion/config:  34 tests
- @glion/util-query: 224 tests
- @glion/util-visit: 31 tests (visit.legacy.test.ts deleted; redundant)
- @glion/jsonify: 12 tests
- @glion/hl7v2:    9 tests

The pre-existing @glion/cli config-discover.test.ts flake (.js vs .ts) is
unrelated and was already failing on main before this PR.

https://claude.ai/code/session_01MvBEUcGkRokNw2GWYVHADg
meleksomai pushed a commit that referenced this pull request Apr 30, 2026
Adds explicit pnpm scripts for each runtime project so the test
matrix is discoverable from package.json:

- test:node — runs the Node adapter + runtime-free core tests
- test:cf  — runs the workerd-based tests
- test     — unchanged, runs both projects (plain `vitest run`)

test:deno and test:bun are not added on this PR. The Deno PR (#615)
will add test:deno once its tests are converted from mocked
globalThis.Deno to running inside actual Deno (mirroring what this
PR did for Workers). Bun support is currently exercised through
test:node because the Node adapter is the codepath Bun uses.

https://claude.ai/code/session_01MvBEUcGkRokNw2GWYVHADg
meleksomai pushed a commit that referenced this pull request Apr 30, 2026
Adds explicit pnpm scripts for each runtime project so the test
matrix is discoverable from package.json:

- test:node — runs the Node adapter + runtime-free core tests
- test:cf  — runs the workerd-based tests
- test     — unchanged, runs both projects (plain `vitest run`)

test:deno and test:bun are not added on this PR. The Deno PR (#615)
will add test:deno once its tests are converted from mocked
globalThis.Deno to running inside actual Deno (mirroring what this
PR did for Workers). Bun support is currently exercised through
test:node because the Node adapter is the codepath Bun uses.

https://claude.ai/code/session_01MvBEUcGkRokNw2GWYVHADg
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants