Skip to main content

Handle FHIR, X12, C-CDA, JSON and text

A channel's format says how its transform receives each message. The message is stored as it arrived; it is parsed in the channel's worker, right before the transform runs.

formatThe transform receivesType
hl7v2 (default)an Hl7Message from @cosyte/hl7TransformFor<"hl7v2">
fhira FHIR R4 resource from @cosyte/fhir's JSON readerTransformFor<"fhir">
x12an X12Interchange from @cosyte/x12TransformFor<"x12">
ccdaa CcdaDocument from @cosyte/ccdaTransformFor<"ccda">
jsonthe value JSON.parse returnsTransformFor<"json">
textthe body as a stringTransformFor<"text">

FHIR, X12, C-CDA and JSON are read as UTF-8, and a body that is not valid UTF-8 fails to parse rather than being changed in silence. text turns bytes that are not UTF-8 into U+FFFD. A json body with a number that a JavaScript number cannot hold exactly (a long id, say) fails with INEXACT_JSON_NUMBER rather than being rounded; use text to pass it through untouched.

A message that does not parse is marked failed with the parser's stable code, such as NO_MSH_SEGMENT or X12_NO_ISA_HEADER, and the transform never sees it.

MLLP carries HL7 v2 only, so a channel with an MLLP source or destination keeps format: hl7v2.

Read a FHIR resource​

channels/patients-in/transform.ts
import { resourceType } from "@cosyte/fhir";
import type { TransformFor } from "@cosyte/pathways";

// Patients go on; any other resource is filtered out.
export default ((resource) =>
resourceType(resource) === "Patient" ? resource : null) satisfies TransformFor<"fhir">;

The resource is @cosyte/fhir's immutable model, which keeps every decimal exactly as written. To call a library's helpers, install it in the project (npm install @cosyte/fhir).

Read an X12 interchange​

channels/claims-in/transform.ts
import type { TransformFor } from "@cosyte/pathways";

// Only interchanges that carry a claim group (GS-01 HC) go on.
export default ((interchange) =>
interchange.groups.some((group) => group.gs.elements[1] === "HC")
? interchange
: null) satisfies TransformFor<"x12">;

Turn one format into another​

What a transform returns is written back like this, whatever the channel's format:

  • a string is sent as it is, in UTF-8;
  • bytes (a Uint8Array, which a Buffer is) are sent exactly as they are, so a body in another character set, such as Latin-1, or a binary one keeps its bytes;
  • a model from @cosyte/hl7, @cosyte/fhir, @cosyte/x12 or @cosyte/ccda is written by its own library;
  • on a json channel, a plain object, an array, a number or a boolean is written with JSON.stringify.

So a transform can take one format in and send another out. This one turns an HL7 v2 admission into a FHIR Patient:

channels/adt-to-fhir/transform.ts
import type { Transform } from "@cosyte/pathways";

export default ((msg) => {
if (!msg.is("ADT^A01")) return null;
const patient = {
resourceType: "Patient",
identifier: [{ value: msg.get("PID.3.1") }],
name: [{ family: msg.get("PID.5.1"), given: [msg.get("PID.5.2")] }],
};
return JSON.stringify(patient);
}) satisfies Transform;

The channel's format still names what comes in, and so it sets the file extension a file destination writes and the Content-Type an HTTP destination sends. Set the header for what goes out:

channels/adt-to-fhir/channel.yaml
format: hl7v2
source:
file: { dir: ../../data/adt-to-fhir/in }
transform: ./transform.ts
destinations:
- http:
url: https://fhir.example.com/Patient
headers:
Content-Type: application/fhir+json
Authorization: { env: FHIR_SERVER_AUTH }