Skip to content

Commit

Permalink
feat: Allow overriding response MSH content Bugs5382#103 (Bugs5382#106)
Browse files Browse the repository at this point in the history
BREAKING CHANGE: MSH Override Changes
  • Loading branch information
Bugs5382 authored Sep 21, 2024
2 parents b999313 + 20c3d77 commit 1dcef70
Show file tree
Hide file tree
Showing 6 changed files with 71 additions and 71 deletions.
44 changes: 19 additions & 25 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,9 +53,9 @@ import { Server } from 'node-hl7-server'
const server = new Server()

const IB = server.createInbound({port: 3000}, async (req, res) => {
const messageReq = req.getMessage()
const messageRes = res.getAckMessage()
// do your code here
const messageReq = req.getMessage()
const messageRes = res.getAckMessage()
// do your code here
})
```

Expand All @@ -78,23 +78,23 @@ TLS:
import { Server } from 'node-hl7-server'

const server = new Server(
{
tls:
{
key: fs.readFileSync(path.join('certs/', 'server-key.pem')), // where your certs are
cert: fs.readFileSync(path.join('certs/', 'server-crt.pem')), // where your certs are
rejectUnauthorized: false
}
})
{
tls:
{
key: fs.readFileSync(path.join('certs/', 'server-key.pem')), // where your certs are
cert: fs.readFileSync(path.join('certs/', 'server-crt.pem')), // where your certs are
rejectUnauthorized: false
}
})
```

When you get a message, you can then parse any segment of the message and do you need to in order for your app to work.

```ts
const IB_ADT = server.createInbound({port: LISTEN_PORT}, async (req, res) => {
const messageReq = req.getMessage()
const messageRes = res.getAckMessage()
const hl7Version = messageReq.get('MSH.12').toString()
const messageReq = req.getMessage()
const messageRes = res.getAckMessage()
const hl7Version = messageReq.get('MSH.12').toString()
})
```

Expand All @@ -103,21 +103,15 @@ Then you can query the segment ```MSH.12``` in this instance and get its result.

Please consult [node-hl7-client](https://www.npmjs.com/package/node-hl7-client) documentation for further ways to parse the message segment.

### Override MSH Header
### Override response MSH fields

In HL7 specification 2.1 through 2.3.1, MSH header segment could not be 'ACK'.
It was a combination of ACK and MSH 9.2 (e.g. ADT_A01) for the full reference of ```ACK^A01^ADT_A01```.
From 2.4 specification to current,
a client might be looking for just ```ACK``` instead of ```ADT_A01``` given now the output of ```ACK^A01^ACK```.
This is not default behavior as the two pair results of ```ADT_A01``` give more information back
since MSH 9.1 is usually ACK to start with.
Individual response MSH segment fields can be overridden by passing the optional `mshOverrides` prop to `server.createInbound`.

To override this please setup your listener with:
For example, the following overrides the default MSH field 9.3 value to "ACK":

```ts
const listener = server.createInbound({port: 3000, overrideMSH: true }, async (req, res) => {})
const listener = server.createInbound({ port: 3000, mshOverrides: { '9.3': 'ACK' }}, async (req, res) => {})
```
... and if the specification covers the MSH 9.3 as ACK, it will make it so. Otherwise, it will just be empty.

## Docker

Expand All @@ -127,7 +121,7 @@ npm run docker:build

This package, if you download from source,
comes with a DockerFile to build a simple docker image with a basic node-hl7-server running.
All the server does is respond "success" to all properly formatted HL7 messages.
All the server does is respond "success" to all properly formatted HL7 messages.

If you want more a custom instance of this server, download the GIT,
and modify ```docker/server.js``` to your liking and then build the docker image and run it.
Expand Down
42 changes: 22 additions & 20 deletions __tests__/hl7.end2end.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -154,18 +154,20 @@ describe('node hl7 end to end - client', () => {

})

test('...simple connect ... MSH 9.3 override', async () => {
test('...simple connect ... MSH overrides', async () => {

let dfd = createDeferred<void>()

const server = new Server({bindAddress: '0.0.0.0'})
const listener = server.createInbound({port: 3000, overrideMSH: true }, async (req, res) => {
// override MSH field 9.3 to "ACK" and field 18 to "UNICODE UTF-8"
const listener = server.createInbound({port: 3000, mshOverrides: { '9.3': 'ACK', '18': 'UNICODE UTF-8' } }, async (req, res) => {
const messageReq = req.getMessage()
expect(messageReq.get('MSH.12').toString()).toBe('2.7')
await res.sendResponse('AA')
const messageRes = res.getAckMessage()
expect(messageRes?.get('MSA.1').toString()).toBe('AA')
expect(messageRes?.get('MSH.9.3').toString()).toBe('ACK')
expect(messageRes?.get('MSH.18').toString()).toBe('UNICODE UTF-8')
})

await expectEvent(listener, 'listen')
Expand Down Expand Up @@ -334,15 +336,15 @@ describe('node hl7 end to end - client', () => {
let dfd = createDeferred<void>()

const server = new Server(
{
bindAddress: '0.0.0.0',
tls:
{
key: fs.readFileSync(path.join('certs/', 'server-key.pem')),
cert: fs.readFileSync(path.join('certs/', 'server-crt.pem')),
rejectUnauthorized: false
}
})
{
bindAddress: '0.0.0.0',
tls:
{
key: fs.readFileSync(path.join('certs/', 'server-key.pem')),
cert: fs.readFileSync(path.join('certs/', 'server-crt.pem')),
rejectUnauthorized: false
}
})
const inbound = server.createInbound({ port: 3000 }, async (req, res) => {
const messageReq = req.getMessage()
expect(messageReq.get('MSH.12').toString()).toBe('2.7')
Expand Down Expand Up @@ -393,15 +395,15 @@ describe('node hl7 end to end - client', () => {

const server = new Server({ bindAddress: "0.0.0.0" });
const listener = server.createInbound(
{ port: 3000 },
async (req, res) => {
const messageReq = req.getMessage();
expect(messageReq.get("MSH.12").toString()).toBe("2.7");
expect(messageReq.get("OBX.3.1").toString()).toBe("SOME-PDF");
await res.sendResponse("AA");
const messageRes = res.getAckMessage();
expect(messageRes?.get("MSA.1").toString()).toBe("AA");
}
{ port: 3000 },
async (req, res) => {
const messageReq = req.getMessage();
expect(messageReq.get("MSH.12").toString()).toBe("2.7");
expect(messageReq.get("OBX.3.1").toString()).toBe("SOME-PDF");
await res.sendResponse("AA");
const messageRes = res.getAckMessage();
expect(messageRes?.get("MSA.1").toString()).toBe("AA");
}
);

await expectEvent(listener, "listen");
Expand Down
16 changes: 8 additions & 8 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -65,23 +65,23 @@
"@semantic-release/git": "^10.0.1",
"@semantic-release/release-notes-generator": "^14.0.1",
"@the-rabbit-hole/semantic-release-config": "^1.5.0",
"@types/node": "^22.4.0",
"@types/node": "^22.5.5",
"@types/tcp-port-used": "^1.0.4",
"@typescript-eslint/parser": "^8.1.0",
"@vitest/coverage-v8": "^2.0.5",
"@vitest/ui": "^2.0.5",
"@vitest/coverage-v8": "^2.1.1",
"@vitest/ui": "^2.1.1",
"npm-package-json-lint": "^8.0.0",
"portfinder": "^1.0.32",
"pre-commit": "^1.2.2",
"semantic-release": "^24.0.0",
"semantic-release": "^24.1.1",
"snazzy": "^9.0.0",
"tcp-port-used": "^1.0.2",
"ts-node": "^10.9.2",
"ts-standard": "^12.0.2",
"tsd": "^0.31.1",
"typedoc": "^0.26.5",
"typescript": "5.5.4",
"vitest": "^2.0.5"
"tsd": "^0.31.2",
"typedoc": "^0.26.7",
"typescript": "5.6.2",
"vitest": "^2.1.1"
},
"dependencies": {
"node-hl7-client": "^2.3.1"
Expand Down
6 changes: 3 additions & 3 deletions src/server/inbound.ts
Original file line number Diff line number Diff line change
Expand Up @@ -180,7 +180,7 @@ export class Inbound extends EventEmitter implements Inbound {
// create the inbound request
const req = new InboundRequest(messageParsed, { type: 'file' })
// create the send response function
const res = new SendResponse(socket, message, this._opt.overrideMSH)
const res = new SendResponse(socket, message, this._opt.mshOverrides)
// on a response sent, tell the inbound listener
res.on('response.sent', () => {
this.emit('response.sent')
Expand All @@ -201,7 +201,7 @@ export class Inbound extends EventEmitter implements Inbound {
// create the inbound request
const req = new InboundRequest(messageParsed, { type: 'file' })
// create the send response function
const res = new SendResponse(socket, messageParsed, this._opt.overrideMSH)
const res = new SendResponse(socket, messageParsed, this._opt.mshOverrides)
// on a response sent, tell the inbound listener
void this._handler(req, res)
})
Expand All @@ -213,7 +213,7 @@ export class Inbound extends EventEmitter implements Inbound {
// create the inbound request
const req = new InboundRequest(messageParsed, { type: 'file' })
// create the send response function
const res = new SendResponse(socket, messageParsed, this._opt.overrideMSH)
const res = new SendResponse(socket, messageParsed, this._opt.mshOverrides)
// on a response sent, tell the inbound listener
void this._handler(req, res)
}
Expand Down
14 changes: 10 additions & 4 deletions src/server/modules/sendResponse.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
HL7_2_5_1, HL7_2_6, HL7_2_7, HL7_2_7_1, HL7_2_8
} from 'node-hl7-client/hl7'
import { PROTOCOL_MLLP_FOOTER, PROTOCOL_MLLP_HEADER } from '../../utils/constants.js'
import type { ListenerOptions } from '../../utils/normalize.js'

/**
* Send Response
Expand All @@ -24,14 +25,14 @@ export class SendResponse extends EventEmitter {
/** @internal */
private readonly _message: Message
/** @internal */
private readonly _overrideMSH: boolean
private readonly _mshOverrides: ListenerOptions['mshOverrides']

constructor (socket: Socket, message: Message, overrideMSH: boolean) {
constructor (socket: Socket, message: Message, mshOverrides?: ListenerOptions['mshOverrides']) {
super()
this._ack = undefined
this._message = message
this._mshOverrides = mshOverrides
this._socket = socket
this._overrideMSH = overrideMSH
}

/**
Expand Down Expand Up @@ -140,7 +141,6 @@ export class SendResponse extends EventEmitter {
messageHeader: {
msh_9_1: 'ACK',
msh_9_2: message.get('MSH.9.2').toString(),
msh_9_3: this._overrideMSH ? 'ACK' : undefined,
msh_10: 'ACK',
msh_11_1: message.get('MSH.11.1').toString() as 'P' | 'D' | 'T'
}
Expand All @@ -152,6 +152,12 @@ export class SendResponse extends EventEmitter {
ackMessage.set('MSH.6', message.get('MSH.4').toString())
ackMessage.set('MSH.12', message.get('MSH.12').toString())

if (typeof this._mshOverrides === 'object') {
Object.entries(this._mshOverrides).forEach(([path, value]) => {
ackMessage.set(`MSH.${path}`, value)
})
}

const segment = ackMessage.addSegment('MSA')
segment.set('1', type)
segment.set('2', message.get('MSH.10').toString())
Expand Down
20 changes: 9 additions & 11 deletions src/utils/normalize.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ const DEFAULT_SERVER_OPTS = {
}

const DEFAULT_LISTENER_OPTS = {
overrideMSH: false,
encoding: 'utf-8'
}

Expand Down Expand Up @@ -39,12 +38,11 @@ export interface ServerOptions {
* @since 1.0.0
*/
export interface ListenerOptions {
/** The HL7 spec we are going to be creating
* Set this to true if the spec that you expect to be receiving from the client
* can support MSH 9.3 as type 'ACK'.
* Otherwise, MSH 9.3 will remain a combined string of ACK and MSH 9.2.
* @since 2.3.0 */
overrideMSH?: boolean
/** Optional MSH segment overrides
* syntax: "field path as numbers separated by dots": "field value"
* e.g. { '9.3': 'ACK' } → MSH field 9.3 set to "ACK"
* @since 2.5.0 */
mshOverrides?: Record<string, string>
/** Name of the Listener (e.g., IB_EPIC_ADT)
* @default Randomized String */
name?: string
Expand All @@ -61,17 +59,17 @@ export interface ListenerOptions {
* @since 1.0.0
*/
type ValidatedKeys =
| 'name'
| 'port'
| 'encoding'
| 'name'
| 'port'
| 'encoding'

/**
* @since 1.0.0
*/
interface ValidatedOptions extends Pick<Required<ListenerOptions>, ValidatedKeys> {
mshOverrides?: Record<string, string>
name: string
port: number
overrideMSH: boolean
}

/** @internal */
Expand Down

0 comments on commit 1dcef70

Please sign in to comment.