fastify-hl7 3.3.0 → 4.0.1

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 (43) hide show
  1. package/CHANGELOG.md +316 -0
  2. package/README.md +535 -60
  3. package/dist/api.d.ts +5 -0
  4. package/{lib/types → dist}/class/hL7Client.d.ts +49 -13
  5. package/{lib/esm → dist}/class/hL7Server.d.ts +2 -2
  6. package/dist/index.cjs +403 -0
  7. package/dist/index.cjs.map +1 -0
  8. package/{lib/types → dist}/index.d.ts +3 -3
  9. package/dist/index.mjs +377 -0
  10. package/dist/index.mjs.map +1 -0
  11. package/{lib/esm → dist}/types.d.ts +26 -20
  12. package/dist/validation.d.ts +6 -0
  13. package/package.json +44 -50
  14. package/lib/cjs/api.js +0 -13
  15. package/lib/cjs/class/hL7Client.js +0 -238
  16. package/lib/cjs/class/hL7Server.js +0 -81
  17. package/lib/cjs/decorate.js +0 -2
  18. package/lib/cjs/errors.js +0 -13
  19. package/lib/cjs/index.js +0 -144
  20. package/lib/cjs/package.json +0 -3
  21. package/lib/cjs/types.js +0 -2
  22. package/lib/cjs/validation.js +0 -15
  23. package/lib/esm/api.d.ts +0 -7
  24. package/lib/esm/api.js +0 -5
  25. package/lib/esm/class/hL7Client.d.ts +0 -99
  26. package/lib/esm/class/hL7Client.js +0 -201
  27. package/lib/esm/class/hL7Server.js +0 -77
  28. package/lib/esm/decorate.js +0 -1
  29. package/lib/esm/errors.js +0 -7
  30. package/lib/esm/index.d.ts +0 -5
  31. package/lib/esm/index.js +0 -125
  32. package/lib/esm/package.json +0 -3
  33. package/lib/esm/types.js +0 -1
  34. package/lib/esm/validation.d.ts +0 -6
  35. package/lib/esm/validation.js +0 -11
  36. package/lib/types/api.d.ts +0 -7
  37. package/lib/types/class/hL7Server.d.ts +0 -38
  38. package/lib/types/decorate.d.ts +0 -38
  39. package/lib/types/errors.d.ts +0 -10
  40. package/lib/types/types.d.ts +0 -69
  41. package/lib/types/validation.d.ts +0 -6
  42. package/{lib/esm → dist}/decorate.d.ts +16 -16
  43. /package/{lib/esm → dist}/errors.d.ts +0 -0
package/README.md CHANGED
@@ -1,121 +1,596 @@
1
- # Fastify HL7
1
+ # 🏥 Fastify HL7
2
2
 
3
- A Fastify Hl7 Plugin Developed in Pure TypeScript.
4
- It uses the [node-hl7-client](https://github.com/Bugs5382/node-hl7-client) and [node-hl7-server](https://github.com/Bugs5382/node-hl7-server) plugin as a wrapper.
3
+ A Fastify HL7 plugin developed in pure TypeScript.
4
+ It wraps the `node-hl7-client` and `node-hl7-server` packages — both shipped from the
5
+ [`node-hl7`](https://github.com/Bugs5382/node-hl7) repo — so a Fastify app can send and
6
+ receive HL7 v2.x messages over MLLP.
5
7
 
6
- The build exports this to valid ESM and CJS for ease of cross-compatibility.
8
+ The build exports valid ESM and CJS for cross-compatibility.
7
9
 
8
- If you are using this NPM package, please consider giving it a :star: star.
9
- This will increase its visibility and solicit more contribution from the outside.
10
+ If you use this package, please consider giving it a ⭐ — it raises visibility and brings in more
11
+ contribution from the outside.
10
12
 
11
- This documentation is how to use this plugin, not on how to use the libraries above.
12
- Head [here](#external-libraries-options) if you need help with those.
13
+ > This documentation covers **how to use the plugin**. It does not re-document the underlying
14
+ > libraries (segment-by-segment message building, transport internals). For those, see
15
+ > [External Libraries](#-external-libraries).
13
16
 
14
- ## Table of Contents
17
+ > 🟢 **Requires Node.js ≥ 22** (inherited from the underlying `node-hl7` packages).
15
18
 
16
- 1. [Install](#install)
17
- 2. [Basic Usage](#basic-usage)
18
- 1. [Server Quick Start](#server-quick-start)
19
- 2. [Client Quick Start](#client-quick-start)
20
- 3. [Full Documentation](#full-documentation)
21
- 1. [This Library Options](#this-library-options)
22
- 2. [External Libraries Options](#external-libraries-options)
23
- 4. [Acknowledgements](#acknowledgements)
24
- 5. [License](#license)
19
+ > ⚠️ **HL7 version is required.** Every client and every inbound listener must pin an explicit
20
+ > `version` — one of `"2.1" | "2.2" | "2.3" | "2.3.1" | "2.4" | "2.5" | "2.5.1" | "2.6" | "2.7" |
21
+ > "2.7.1" | "2.8"`. There is no default. A client's `version` must match the `MSH.12` of every
22
+ > message it sends; an inbound listener rejects (`AR`) any message whose `MSH.12` differs.
25
23
 
26
- ## Install
24
+ ## Table of Contents
27
25
 
28
- ```
26
+ 1. [Install](#-install)
27
+ 2. [Basic Usage](#-basic-usage)
28
+ 1. [Register the plugin](#register-the-plugin)
29
+ 2. [Server quick start](#server-quick-start)
30
+ 3. [Client quick start](#client-quick-start)
31
+ 3. [Recipes](#-recipes)
32
+ 1. [Full round-trip in one app](#1-full-round-trip-in-one-app)
33
+ 2. [Route multiple inbound listeners](#2-route-multiple-inbound-listeners)
34
+ 3. [Multiple clients and outbound connections](#3-multiple-clients-and-outbound-connections)
35
+ 4. [Build messages, batches, and file batches](#4-build-messages-batches-and-file-batches)
36
+ 5. [Parse inbound HL7 and read files](#5-parse-inbound-hl7-and-read-files)
37
+ 6. [ACK / NAK responses](#6-ack--nak-responses)
38
+ 7. [Graceful shutdown](#7-graceful-shutdown)
39
+ 8. [TLS and server options](#8-tls-and-server-options)
40
+ 9. [Look up live clients and listeners](#9-look-up-live-clients-and-listeners)
41
+ 10. [Error handling when the server is disabled](#10-error-handling-when-the-server-is-disabled)
42
+ 11. [Encapsulate sending in your own plugin](#11-encapsulate-sending-in-your-own-plugin)
43
+ 4. [API Reference](#-api-reference-fastifyhl7)
44
+ 5. [Plugin Options](#-plugin-options)
45
+ 6. [External Libraries](#-external-libraries)
46
+ 7. [Acknowledgements](#-acknowledgements)
47
+ 8. [License](#-license)
48
+
49
+ ## 📦 Install
50
+
51
+ ```shell
29
52
  npm install fastify-hl7
30
53
  ```
31
54
 
32
- ## Basic Usage
55
+ Requires Node.js ≥ 22.
56
+
57
+ ## 🚀 Basic Usage
33
58
 
34
- Register this as a plugin.
59
+ ### Register the plugin
35
60
 
36
61
  ```ts
37
- await fastify.register(fastifyHL7);
62
+ import fastify from "fastify";
63
+ import fastifyHL7 from "fastify-hl7";
64
+
65
+ const app = fastify();
66
+
67
+ await app.register(fastifyHL7);
38
68
  ```
39
69
 
40
- ### Server Quick Start
70
+ Registering decorates the Fastify instance with `app.hl7` — the single entry point for HL7
71
+ clients, inbound listeners, and message builders. By default the inbound **server** is enabled;
72
+ pass `{ enableServer: false }` for a client-only app (see [Plugin Options](#-plugin-options)).
73
+
74
+ ### Server quick start
75
+
76
+ Create an inbound listener. It must pin an HL7 `version`. The handler receives each inbound message
77
+ and replies with an acknowledgement code (`"AA"` accept, `"AE"` error, `"AR"` reject):
41
78
 
42
79
  ```ts
43
- const listener = fastify.hl7.createInbound(
80
+ const listener = app.hl7.createInbound(
44
81
  "ib_adt",
45
- { port: 3001 },
82
+ { port: 3001, version: "2.7" },
46
83
  async (req, res) => {
47
- const messageReq = req.getMessage();
48
- const messageType = req.getType();
49
- // your logic here
84
+ const message = req.getMessage();
85
+ const type = req.getType();
86
+ app.log.info("received %s", type);
87
+ // ...your logic here...
50
88
  await res.sendResponse("AA");
51
89
  },
52
90
  );
53
91
  ```
54
92
 
55
- This will create a inbound connection. You can optionally return it to a variable to attach advanced listeners.
93
+ `createInbound` returns the listener so you can attach advanced event handlers if needed. Any inbound
94
+ message whose `MSH.12` is not `"2.7"` is rejected with an `AR` before your handler runs.
95
+
96
+ ### Client quick start
97
+
98
+ A **client** is a named handle to one remote host and pins the HL7 `version` for everything it
99
+ sends. Outbound **connections** are attached to that client by its name — so the first argument to
100
+ `createConnection` is the **client name**, not a new identifier (it inherits the client's version):
101
+
102
+ ```ts
103
+ // 1. Register a named client pointed at a remote host, pinned to an HL7 version.
104
+ app.hl7.createClient("adt_host", { host: "127.0.0.1", version: "2.7" });
105
+
106
+ // 2. Attach an outbound connection to that client (note: "adt_host" matches above).
107
+ const connection = app.hl7.createConnection(
108
+ "adt_host",
109
+ { port: 3001 },
110
+ async (res) => {
111
+ const reply = res.getMessage();
112
+ // Handle the ACK/NAK the remote returned, e.g. res.getMessage().get("MSA.1").
113
+ },
114
+ );
115
+
116
+ // 3. Build a message (its MSH.12 must match the client version) and send it.
117
+ const message = app.hl7.buildMessage({
118
+ messageHeader: {
119
+ msh_9_1: "ADT",
120
+ msh_9_2: "A01",
121
+ msh_10: "MSG00001",
122
+ msh_11_1: "P",
123
+ msh_12: "2.7",
124
+ },
125
+ });
126
+
127
+ await connection.sendMessage(message);
128
+ ```
129
+
130
+ The client name is a unique identifier for a host, so you can attach several outbound connections
131
+ (different ports) to the same host, and create several clients for different hosts.
132
+
133
+ > ⚠️ A client name must be unique and may not contain spaces or the characters
134
+ > `` `!@#$%^&*()+-=[]{};':"\|,.<>/?~ ``. The same rule applies to inbound listener names.
135
+
136
+ ## 🧩 Recipes
137
+
138
+ ### 1. Full round-trip in one app
56
139
 
57
- ### Client Quick Start
140
+ A single Fastify app that listens for inbound HL7 **and** sends outbound HL7 to itself — useful for
141
+ local testing or a relay. Keep the versions aligned across the listener, the client, and the
142
+ message:
58
143
 
59
144
  ```ts
60
145
  import fastify from "fastify";
146
+ import fastifyHL7 from "fastify-hl7";
61
147
 
62
- fastify.hl7.createClient("localhost", { host: "0.0.0.0" });
148
+ const app = fastify();
149
+ await app.register(fastifyHL7);
150
+
151
+ // Inbound: accept everything, echo back an "AA".
152
+ app.hl7.createInbound("ib_adt", { port: 3001, version: "2.7" }, async (req, res) => {
153
+ app.log.info("inbound %s", req.getType());
154
+ await res.sendResponse("AA");
155
+ });
156
+
157
+ // Outbound: a client pointed at our own listener, same version.
158
+ app.hl7.createClient("self", { host: "127.0.0.1", version: "2.7" });
159
+ const out = app.hl7.createConnection("self", { port: 3001 }, async (res) => {
160
+ app.log.info("ack: %s", res.getMessage().get("MSA.1").toString());
161
+ });
162
+
163
+ await app.listen({ port: 3000 });
164
+
165
+ const message = app.hl7.buildMessage({
166
+ messageHeader: { msh_9_1: "ADT", msh_9_2: "A01", msh_11_1: "P", msh_12: "2.7" },
167
+ });
168
+ await out.sendMessage(message);
63
169
  ```
64
170
 
65
- This will create a "client" class that will then allow you to attach different outbound connections.
66
- The name `localhost` in a unique identifier to this host, so it can be used to attach different outbound connections to this server/broker.
171
+ ### 2. Route multiple inbound listeners
172
+
173
+ One server hosts many inbound listeners on different ports — for example, one per feed. Each pins
174
+ its own version:
175
+
176
+ ```ts
177
+ app.hl7.createInbound("adt_feed", { port: 3001, version: "2.7" }, async (req, res) => {
178
+ // ADT (admit/discharge/transfer) feed.
179
+ await res.sendResponse("AA");
180
+ });
181
+
182
+ app.hl7.createInbound("oru_feed", { port: 3002, version: "2.7" }, async (req, res) => {
183
+ // ORU (observation result) feed; branch on the message type.
184
+ if (req.getType() === "ORU") {
185
+ // ...persist the result...
186
+ await res.sendResponse("AA");
187
+ } else {
188
+ await res.sendResponse("AR"); // reject anything unexpected on this port
189
+ }
190
+ });
191
+ ```
67
192
 
68
- There might be times when your HL7 messages need to cross-over to different hosts/server/inbound endpoints and this is how you would get them.
193
+ > There is only **one** server per host (the machine this runs on), but it can host any number of
194
+ > inbound listeners on distinct ports.
69
195
 
70
- Within your code now you can use the fastify context and access the `hl7` decorator,
71
- and create an outbound connection.
196
+ ### 3. Multiple clients and outbound connections
197
+
198
+ An interface engine often talks to several downstream systems. Create one client per host (each with
199
+ its version), and one connection per port on that host:
72
200
 
73
201
  ```ts
74
- const client = fastify.hl7.createConnection(
75
- "ob_adt",
76
- { port: 3001 },
77
- async (res) => {
78
- const messageRes = res.getMessage();
79
- // Your code here. Either a failure or a success, but still do your work here.
80
- },
202
+ app.hl7.createClient("lab", { host: "10.0.0.10", version: "2.5.1" });
203
+ app.hl7.createClient("pharmacy", { host: "10.0.0.20", version: "2.7" });
204
+
205
+ const labOrders = app.hl7.createConnection("lab", { port: 6661 }, async () => {});
206
+ const labResults = app.hl7.createConnection("lab", { port: 6662 }, async () => {});
207
+ const rxOrders = app.hl7.createConnection("pharmacy", { port: 6661 }, async () => {});
208
+
209
+ await labOrders.sendMessage(
210
+ app.hl7.buildMessage({
211
+ messageHeader: { msh_9_1: "ORM", msh_9_2: "O01", msh_11_1: "P", msh_12: "2.5.1" },
212
+ }),
81
213
  );
214
+ ```
82
215
 
83
- // building a HL7 Message Segment
84
- const message = app.hl7.buildMessage({
85
- messageHeader: {
216
+ Reusing the same port on the same client throws — pick a distinct outbound port per connection.
217
+
218
+ ### 4. Build messages, batches, and file batches
219
+
220
+ For a **validated** message, use `createBuilder(version)`. It returns node-hl7-client's
221
+ version-pinned builder, which validates every field against that HL7 version (withdrawn fields throw,
222
+ backward-compatibility fields warn, segments not in the version are rejected) and sets `MSH.12` to
223
+ the version for you. Chain `build*` calls and finish with `toMessage()`:
224
+
225
+ ```ts
226
+ const message = app.hl7
227
+ .createBuilder("2.7")
228
+ .buildMSH({
229
+ msh_3: "MY_APP",
230
+ msh_4: "MY_FAC",
231
+ msh_5: "EPIC",
232
+ msh_6: "HOSP",
86
233
  msh_9_1: "ADT",
87
234
  msh_9_2: "A01",
88
- msh_11_1: "D",
235
+ msh_10: "MSG00001",
236
+ msh_11_1: "P",
237
+ })
238
+ .buildPID({ pid_3: "MRN12345", pid_5: "DOE^JANE^A", pid_8: "F" })
239
+ .toMessage();
240
+ ```
241
+
242
+ Because the builder pins the version, its `MSH.12` always matches a client created with the same
243
+ `version`. For a lightweight, **unvalidated** message, `buildMessage` constructs one directly (set
244
+ `messageHeader.msh_12` yourself):
245
+
246
+ ```ts
247
+ const quick = app.hl7.buildMessage({
248
+ messageHeader: { msh_9_1: "ADT", msh_9_2: "A01", msh_11_1: "P", msh_12: "2.7" },
249
+ });
250
+
251
+ // A batch (BHS) that groups several messages.
252
+ const batch = app.hl7.buildBatch();
253
+ batch.start();
254
+ batch.add(message);
255
+ batch.end();
256
+
257
+ // A file batch (FHS) for writing HL7 to disk.
258
+ const fileBatch = app.hl7.buildFileBatch();
259
+
260
+ // An HL7-formatted timestamp (length 8, 12, or 14 — 14 is the default).
261
+ const stamp = app.hl7.buildDate(new Date(), 14);
262
+ ```
263
+
264
+ > `buildFileBatch` is for *creating* a file batch — to read an existing one, use `readFile` /
265
+ > `readFileBuffer` ([recipe 5](#5-parse-inbound-hl7-and-read-files)).
266
+
267
+ ### 5. Parse inbound HL7 and read files
268
+
269
+ ```ts
270
+ // Parse a raw string — returns a Batch if it starts with BHS, otherwise a Message.
271
+ const parsed = app.hl7.processHL7(rawHl7String);
272
+
273
+ // Read a file batch from disk.
274
+ const fromPath = app.hl7.readFile("temp/hl7.readTestBHS.20231208.hl7");
275
+
276
+ // Or from a Buffer you already have in memory.
277
+ import { readFileSync } from "node:fs";
278
+ const fromBuffer = app.hl7.readFileBuffer(readFileSync("temp/hl7.readTestBHS.20231208.hl7"));
279
+ ```
280
+
281
+ ### 6. ACK / NAK responses
282
+
283
+ Inside an inbound handler, reply with the acknowledgement code that fits the outcome:
284
+
285
+ ```ts
286
+ app.hl7.createInbound("ib_adt", { port: 3001, version: "2.7" }, async (req, res) => {
287
+ try {
288
+ const message = req.getMessage();
289
+ // ...process the message...
290
+ await res.sendResponse("AA"); // Application Accept
291
+ } catch (err) {
292
+ req.log?.error(err);
293
+ await res.sendResponse("AE"); // Application Error
294
+ }
295
+ });
296
+ ```
297
+
298
+ Use `"AR"` (Application Reject) for messages you will not process at all (wrong type, unsupported
299
+ trigger, etc.). For verbatim, vendor-shaped acknowledgements, `node-hl7-server` exposes
300
+ `sendCustomResponse`.
301
+
302
+ ### 7. Graceful shutdown
303
+
304
+ You do **not** need to close clients or listeners by hand. The plugin registers Fastify `preClose`
305
+ hooks that close every outbound connection and inbound listener when the app shuts down:
306
+
307
+ ```ts
308
+ const app = fastify();
309
+ await app.register(fastifyHL7);
310
+ // ...create clients and listeners...
311
+
312
+ // On app.close() / SIGINT, all HL7 connections close automatically.
313
+ await app.close();
314
+ ```
315
+
316
+ To close a single listener early, use `app.hl7.closeServer(port)`; to close them all,
317
+ `app.hl7.closeServerAll()`.
318
+
319
+ ### 8. TLS and server options
320
+
321
+ Server options pass straight through to `node-hl7-server` and can only be set at registration time
322
+ (you cannot change them after the server is created):
323
+
324
+ ```ts
325
+ import { readFileSync } from "node:fs";
326
+
327
+ await app.register(fastifyHL7, {
328
+ serverOptions: {
329
+ // e.g. bindAddress, IPv6, or TLS — see node-hl7-server's ServerOptions.
330
+ bindAddress: "0.0.0.0",
331
+ tls: {
332
+ key: readFileSync("server.key"),
333
+ cert: readFileSync("server.crt"),
334
+ },
89
335
  },
90
336
  });
337
+ ```
338
+
339
+ Client-side TLS is set per client via the `tls` option on `createClient`
340
+ (`{ host, version, tls: true | ConnectionOptions }`).
341
+
342
+ ### 9. Look up live clients and listeners
343
+
344
+ Retrieve handles you created earlier, by name or by port:
345
+
346
+ ```ts
347
+ const labClient = app.hl7.getClientByName("lab"); // Client | undefined
348
+ const conn = app.hl7.getClientConnectionByPort("6661"); // Connection | undefined
349
+
350
+ const adtListener = app.hl7.getServerByName("adt_feed"); // Inbound | undefined
351
+ const onPort = app.hl7.getServerByPort("3001"); // Inbound | undefined
352
+ ```
91
353
 
92
- await client.sendMessage(message);
354
+ ### 10. Error handling when the server is disabled
355
+
356
+ If you register with `{ enableServer: false }`, every server-side method throws a usage error.
357
+ Guard accordingly:
358
+
359
+ ```ts
360
+ await app.register(fastifyHL7, { enableServer: false });
361
+
362
+ try {
363
+ app.hl7.createInbound("ib", { port: 3001, version: "2.7" }, async () => {});
364
+ } catch (err) {
365
+ // FASTIFY_HL7_ERR_USAGE: "server was not started.
366
+ // re-register plugin with enableServer set to true."
367
+ app.log.error(err);
368
+ }
93
369
  ```
94
370
 
95
- ## Full Documentation
371
+ Registering the plugin twice also throws (`FASTIFY_HL7_ERR_SETUP_ERRORS: "Already registered."`).
372
+
373
+ ### 11. Encapsulate sending in your own plugin
96
374
 
97
- ### This Library Options
375
+ This is the pattern the plugin is built for, and the reason it is a plugin at all. Fastify's
376
+ encapsulation lets you keep every HL7 concern — registering `fastify-hl7`, the version pin, the
377
+ client, the outbound connection, and the ACK handling — in **one plugin**, and expose just a small,
378
+ intent-named surface (a decorator like `app.adt`) to the rest of the app. Routes then send a message
379
+ in one call; they never touch clients, connections, or message headers.
380
+
381
+ Wrap your plugin with [`fastify-plugin`](https://github.com/fastify/fastify-plugin) so the decorator
382
+ is visible to sibling plugins and routes. Without `fp`, the decorator would be trapped inside this
383
+ plugin's own encapsulation context and the rest of the app could not see it.
384
+
385
+ The helper below builds a **validated** `ADT^A01` with `createBuilder("2.7")`, sends it over a
386
+ connection created once at startup, and resolves with the remote's acknowledgement code (`MSA.1`):
387
+
388
+ ```ts
389
+ // plugins/adt.ts
390
+ import fp from "fastify-plugin";
391
+ import fastifyHL7 from "fastify-hl7";
392
+
393
+ // The HL7 version is pinned in one place. The client, the connection, and the
394
+ // builder all use it, so MSH.12 can never drift out of sync.
395
+ const HL7_VERSION = "2.7" as const;
396
+
397
+ // A small, route-facing input — the business shape, not an HL7 message.
398
+ interface Patient {
399
+ mrn: string;
400
+ name: string; // HL7 XPN, e.g. "DOE^JANE^A"
401
+ sex?: string; // HL7 administrative sex, e.g. "F"
402
+ }
403
+
404
+ declare module "fastify" {
405
+ interface FastifyInstance {
406
+ adt: {
407
+ /** Send an ADT^A01 (patient admit) and resolve with the ACK code from MSA.1. */
408
+ sendA01: (patient: Patient) => Promise<string>;
409
+ };
410
+ }
411
+ }
412
+
413
+ export default fp(
414
+ async (app) => {
415
+ // 1. Register fastify-hl7. This app only sends, so the inbound server is off.
416
+ await app.register(fastifyHL7, { enableServer: false });
417
+
418
+ // 2. Wire the client and one outbound connection once, at startup. The ACK
419
+ // handler resolves a pending promise so the helper can await the reply.
420
+ const host = process.env.ADT_HOST ?? "127.0.0.1";
421
+ const port = Number(process.env.ADT_PORT ?? 3001);
422
+
423
+ app.hl7.createClient("adt_host", { host, version: HL7_VERSION });
424
+
425
+ let resolveAck: ((code: string) => void) | undefined;
426
+ const connection = app.hl7.createConnection(
427
+ "adt_host",
428
+ { port, version: HL7_VERSION },
429
+ async (res) => {
430
+ // The remote replies with an ACK/NAK message; MSA.1 carries the code.
431
+ const code = res.getMessage().get("MSA.1").toString();
432
+ resolveAck?.(code);
433
+ },
434
+ );
435
+
436
+ // 3. Expose one intent-named helper. Routes call app.adt.sendA01(patient)
437
+ // and stay ignorant of HL7 framing, the connection, and the version.
438
+ app.decorate("adt", {
439
+ sendA01: async (patient: Patient): Promise<string> => {
440
+ const message = app.hl7
441
+ .createBuilder(HL7_VERSION)
442
+ .buildMSH({
443
+ msh_3: "MY_APP",
444
+ msh_4: "MY_FAC",
445
+ msh_5: "EPIC",
446
+ msh_6: "HOSP",
447
+ msh_9_1: "ADT",
448
+ msh_9_2: "A01",
449
+ msh_10: app.hl7.buildDate(new Date()),
450
+ msh_11_1: "P",
451
+ })
452
+ .buildEVN({ evn_1: "A01", evn_2: new Date() })
453
+ .buildPID({ pid_3: patient.mrn, pid_5: patient.name, pid_8: patient.sex })
454
+ .buildPV1({ pv1_2: "I" }) // patient class: I = inpatient
455
+ .toMessage();
456
+
457
+ const ack = new Promise<string>((resolve) => {
458
+ resolveAck = resolve;
459
+ });
460
+ await connection.sendMessage(message);
461
+ return ack;
462
+ },
463
+ });
464
+
465
+ // 4. No teardown to write: fastify-hl7 registers preClose hooks that close
466
+ // the client and connection automatically when the app shuts down.
467
+ },
468
+ { name: "adt" },
469
+ );
470
+ ```
471
+
472
+ > The ACK handler above resolves a single pending promise, which keeps the example focused on the
473
+ > encapsulation pattern. The handler is per-connection and is not correlated to a specific outgoing
474
+ > message, so if you send several messages concurrently over the same connection you should match each
475
+ > reply to its request yourself — e.g. key pending promises by the `MSH.10` message-control id you set
476
+ > on the outgoing message and read back from the ACK's `MSA.2`.
477
+
478
+ Register it once, then admit a patient from anywhere with a single call:
479
+
480
+ ```ts
481
+ import fastify from "fastify";
482
+ import adt from "./plugins/adt";
483
+
484
+ const app = fastify();
485
+ await app.register(adt);
486
+
487
+ app.post("/admit", async (request) => {
488
+ const ackCode = await app.adt.sendA01(request.body as never);
489
+ return { accepted: ackCode === "AA", ackCode };
490
+ });
491
+
492
+ await app.listen({ port: 3000 });
493
+ ```
494
+
495
+ Why this shape works well:
496
+
497
+ - **One place owns HL7.** Registration, version pin, client, connection, and message building live
498
+ together; the rest of the app depends only on `app.adt`.
499
+ - **Startup wires, routes send.** The client and connection are created once at boot, so the first
500
+ request does not rebuild the connection or re-pin the version.
501
+ - **The version cannot drift.** `HL7_VERSION` feeds `createClient` and `createBuilder`, and the
502
+ builder stamps it into `MSH.12` — so the message a client sends always matches the version that
503
+ client was created with.
504
+ - **Lifecycle is handled for you.** `fastify-hl7`'s `preClose` hooks close the client and connection
505
+ with the app (see [Graceful shutdown](#7-graceful-shutdown)) — important for clean restarts and for
506
+ tests that start and stop Fastify repeatedly.
507
+ - **Swappable.** Because routes only know `app.adt`, you can repoint the host, add a second trigger
508
+ (`sendA08`, an `ORU` result via [recipe 4](#4-build-messages-batches-and-file-batches)), or stub the
509
+ decorator in a test without touching route code.
510
+
511
+ > **Registration order:** register your wrapper plugin (which registers `fastify-hl7` internally)
512
+ > before any plugin or route that uses `app.adt`. Because the wrapper is an `fp` plugin, Fastify
513
+ > guarantees its decorators are in place before sibling plugins and routes load.
514
+ >
515
+ > The types you need — `HL7`, `FastifyHL7Options`, and the client/server option types — are described
516
+ > in the [API Reference](#-api-reference-fastifyhl7); the underlying message, segment, and builder
517
+ > types come from `node-hl7-client` (see [External Libraries](#-external-libraries)).
518
+
519
+ ## 📖 API Reference (`fastify.hl7`)
520
+
521
+ All methods hang off the `hl7` decorator on the Fastify instance.
522
+
523
+ ### Inbound / server
524
+
525
+ | Method | Returns | Description |
526
+ |---|---|---|
527
+ | `createInbound(name, options, handler)` | `Inbound` | Start an inbound listener on `options.port` pinned to `options.version`; `handler(req, res)` handles each message. |
528
+ | `closeServer(port)` | `Promise<boolean>` | Close the listener on `port`. |
529
+ | `closeServerAll()` | `Promise<boolean>` | Close all listeners. |
530
+ | `getServerByName(name)` | `Inbound \| undefined` | Look up a listener by name. |
531
+ | `getServerByPort(port)` | `Inbound \| undefined` | Look up a listener by port. |
532
+
533
+ The server methods throw `FASTIFY_HL7_ERR_USAGE` when the plugin was registered with
534
+ `enableServer: false`.
535
+
536
+ ### Outbound / client
537
+
538
+ | Method | Returns | Description |
539
+ |---|---|---|
540
+ | `createClient(name, options)` | `Client` | Register a uniquely named client pointed at `options.host` and pinned to `options.version`. |
541
+ | `createConnection(name, options, handler)` | `Connection` | Attach an outbound connection (on `options.port`) to the client called `name`; `handler(res)` handles the reply. |
542
+ | `getClientByName(name)` | `Client \| undefined` | Look up a client by name. |
543
+ | `getClientConnectionByPort(port)` | `Connection \| undefined` | Look up an outbound connection by port. |
544
+
545
+ ### Builders
546
+
547
+ | Method | Returns | Description |
548
+ |---|---|---|
549
+ | `createBuilder(version, options?)` | `HL7_2_x` | Version-pinned, **validated** builder; chain `build*` then `.toMessage()`. Rejects fields/segments not valid for the version. |
550
+ | `buildMessage(options?)` | `Message` | Build a single HL7 message directly, **unvalidated** (set `messageHeader.msh_12` to the client version). |
551
+ | `buildBatch(options?)` | `Batch` | Build an HL7 batch (BHS). |
552
+ | `buildFileBatch(options?)` | `FileBatch` | Build an HL7 file batch (FHS) for writing. |
553
+ | `buildDate(date, length?)` | `string` | Format a `Date` as an HL7 timestamp (length `8`, `12`, or `14`; default `14`). |
554
+
555
+ ### Parsing & files
556
+
557
+ | Method | Returns | Description |
558
+ |---|---|---|
559
+ | `processHL7(text)` | `Batch \| Message` | Parse raw HL7 — `Batch` if it starts with BHS, else `Message`. |
560
+ | `readFile(fullFilePath)` | `FileBatch` | Read a file batch from a path. |
561
+ | `readFileBuffer(buffer)` | `FileBatch` | Read a file batch from a `Buffer`. |
562
+
563
+ ## ⚙️ Plugin Options
564
+
565
+ Pass these to `app.register(fastifyHL7, options)`:
98
566
 
99
567
  ### `enableServer`
100
568
 
101
- If this is not set, enableServer will be set to `true`. You need to set this to `false` will turn off the server capabilities.
569
+ `boolean` — defaults to `true`. Set to `false` to turn off the inbound server (client-only app).
570
+ While disabled, the server-side methods throw `FASTIFY_HL7_ERR_USAGE`.
102
571
 
103
572
  ### `serverOptions`
104
573
 
105
- Set this using the options from the [node-hl7-serer](https://github.com/Bugs5382/node-hl7-server/blob/main/README.md) library ServerOptions values to override server creation.
106
- This could be enabling IPv4 or IPv6 and other server options including TLS. Since you cannot set this after run time, it has to be set during registration.
574
+ The `ServerOptions` from
575
+ [`node-hl7-server`](https://github.com/Bugs5382/node-hl7) — `bindAddress`, encoding, TLS, and other
576
+ server-creation settings. It can only be set at registration time.
577
+
578
+ > Per-client and per-listener settings (including the required HL7 `version`, host, port, and TLS)
579
+ > are passed to `createClient` / `createConnection` / `createInbound`, not here.
107
580
 
108
- ### External Libraries Options
581
+ ## 🔌 External Libraries
109
582
 
110
- - [node-hl7-client](https://github.com/Bugs5382/node-hl7-client/blob/main/README.md) - For the Client, Parser, and Builder to review their options that can be passed as props.
111
- - [node-hl7-server](https://github.com/Bugs5382/node-hl7-server/blob/main/README.md) - For the Server to the options that can be passed as props.
583
+ This plugin documents only its own surface. For segment-by-segment message building (the class-based
584
+ `HL7_2_x` builders), transport internals, parsing, and the full client/server option sets, see the
585
+ [`node-hl7`](https://github.com/Bugs5382/node-hl7) repo, which ships both packages:
112
586
 
113
- Please review the libraries for more complete documentation.
587
+ - `node-hl7-client` — Client, Parser, and Builder options.
588
+ - `node-hl7-server` — Server and Inbound options.
114
589
 
115
- ## Acknowledgements
590
+ ## 🙏 Acknowledgements
116
591
 
117
592
  - My Wife and Baby Girl.
118
593
 
119
- ## License
594
+ ## 📄 License
120
595
 
121
596
  Licensed under [MIT](./LICENSE).
package/dist/api.d.ts ADDED
@@ -0,0 +1,5 @@
1
+ export { default, default as fastifyHL7 } from ".";
2
+ export { HL7Client } from "./class/hL7Client.js";
3
+ export { HL7Server } from "./class/hL7Server.js";
4
+ export { AServers, FastifyHL7Options } from "./decorate.js";
5
+ export { HL7 } from "fastify";