├── src ├── version.js ├── log.js ├── index.js ├── Statistics.js ├── Client.js ├── Server.js ├── Network.js └── Hl7.js ├── .gitignore ├── .prettierrc.json ├── examples ├── tsconfig.json ├── index.js └── index.ts ├── .eslintrc.json ├── .npmignore ├── .github └── workflows │ └── build.yml ├── LICENSE.txt ├── .jsdocrc.json ├── webpack.config.js ├── test ├── Statistics.test.js ├── Hl7.test.js └── Network.test.js ├── README.md ├── package.json ├── index.test-d.ts └── index.d.ts /src/version.js: -------------------------------------------------------------------------------- 1 | module.exports = '0.0.9'; 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | build 3 | documentation 4 | coverage 5 | .DS_Store 6 | -------------------------------------------------------------------------------- /.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "printWidth": 100, 3 | "singleQuote": true, 4 | "tabWidth": 2, 5 | "trailingComma": "es5", 6 | "useTabs": false 7 | } 8 | -------------------------------------------------------------------------------- /examples/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "ts-node": { 3 | "compilerOptions": { 4 | "esModuleInterop": true, 5 | "module": "commonjs", 6 | "target": "es2021" 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "node": true, 4 | "es6": true 5 | }, 6 | "extends": [ 7 | "eslint:recommended" 8 | ], 9 | "parserOptions": { 10 | "sourceType": "module", 11 | "ecmaVersion": 2021 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | ## All non-dist directories 2 | .github/ 3 | coverage/ 4 | examples/ 5 | src/ 6 | test/ 7 | documentation/ 8 | 9 | ## Root files 10 | .eslintrc.json 11 | .gitignore 12 | .jsdocrc.json 13 | .prettierrc.json 14 | index.test-d.ts 15 | webpack.config.js 16 | -------------------------------------------------------------------------------- /src/log.js: -------------------------------------------------------------------------------- 1 | const log = require('loglevel'); 2 | const prefix = require('loglevel-plugin-prefix'); 3 | 4 | prefix.reg(log); 5 | log.enableAll(false); 6 | 7 | prefix.apply(log, { 8 | // eslint-disable-next-line no-unused-vars 9 | format(level, name, timestamp) { 10 | return `${timestamp} -- ${level.toUpperCase()} --`; 11 | }, 12 | timestampFormatter(timestamp) { 13 | return timestamp.toISOString(); 14 | }, 15 | }); 16 | 17 | //#region Exports 18 | module.exports = log; 19 | //#endregion 20 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | const { Hl7, Hl7Message, Tag } = require('./Hl7'); 2 | const { Hl7MessageHandler, Server } = require('./Server'); 3 | const Client = require('./Client'); 4 | const Statistics = require('./Statistics'); 5 | const log = require('./log'); 6 | const version = require('./version'); 7 | 8 | const hl7Mllp = { 9 | Client, 10 | Hl7, 11 | Hl7Message, 12 | Hl7MessageHandler, 13 | log, 14 | Server, 15 | Statistics, 16 | Tag, 17 | version, 18 | }; 19 | 20 | //#region Exports 21 | module.exports = hl7Mllp; 22 | //#endregion 23 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Node.js CI 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | 9 | permissions: 10 | actions: read 11 | contents: read 12 | security-events: write 13 | 14 | jobs: 15 | build: 16 | runs-on: ubuntu-latest 17 | 18 | strategy: 19 | matrix: 20 | node-version: [16.x, 18.x, 20.x, 22.x] 21 | 22 | steps: 23 | - name: Checkout 24 | uses: actions/checkout@v4 25 | - name: Initialize CodeQL 26 | uses: github/codeql-action/init@v3 27 | with: 28 | languages: javascript 29 | - name: Use Node.js ${{ matrix.node-version }} 30 | uses: actions/setup-node@v4 31 | with: 32 | node-version: ${{ matrix.node-version }} 33 | - name: Install dependencies 34 | run: npm install 35 | - name: Build 36 | run: npm run build 37 | - name: Perform CodeQL analysis 38 | uses: github/codeql-action/analyze@v3 39 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2022-2025 Pantelis Georgiadis 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /examples/index.js: -------------------------------------------------------------------------------- 1 | const hl7Mllp = require('./../src'); 2 | const { Client, Hl7Message, Hl7MessageHandler, Server } = hl7Mllp; 3 | 4 | class Hl7MllpMessageHandler extends Hl7MessageHandler { 5 | constructor(socket, opts) { 6 | super(socket, opts); 7 | } 8 | 9 | onMessage(message, callback) { 10 | console.log(message.toString('\n')); 11 | callback(Hl7Message.createAcknowledgeMessage(message)); 12 | } 13 | } 14 | 15 | const port = 6000; 16 | 17 | const server = new Server(Hl7MllpMessageHandler); 18 | server.on('networkError', (e) => { 19 | console.log('Network error: ', e); 20 | }); 21 | server.listen(port); 22 | 23 | const client = new Client(); 24 | const hl7Message = new Hl7Message( 25 | `MSH|^~\&|SENDINGAPP|SENDINGFACILITY|RECEIVINGAPP|RECEIVINGFACILITY|200001010000||ADT|1234567890|D|2.2| 26 | PID||123456^^^2^ID 1|654321||DOE^JOHN^^^^|DOE^JOHN^^^^|19480203|M|` 27 | ); 28 | hl7Message.on('acknowledge', (ackMessage) => { 29 | console.log(ackMessage.toString('\n')); 30 | }); 31 | 32 | client.addMessage(hl7Message); 33 | client.send('127.0.0.1', port); 34 | 35 | setTimeout(() => { 36 | server.close(); 37 | const statistics = server.getStatistics(); 38 | console.log('Server statistics:', statistics.toString()); 39 | }, 3000); 40 | -------------------------------------------------------------------------------- /examples/index.ts: -------------------------------------------------------------------------------- 1 | import { Client, Hl7Message, Hl7MessageHandler, Server } from './..'; 2 | import { Socket } from 'net'; 3 | 4 | class Hl7MllpMessageHandler extends Hl7MessageHandler { 5 | constructor( 6 | socket: Socket, 7 | opts: { 8 | connectTimeout?: number; 9 | logMessages?: boolean; 10 | } 11 | ) { 12 | super(socket, opts); 13 | } 14 | 15 | onMessage(message: Hl7Message, callback: (message: Hl7Message) => void) { 16 | console.log(message.toString('\n')); 17 | callback(Hl7Message.createAcknowledgeMessage(message)); 18 | } 19 | } 20 | 21 | const port = 6000; 22 | 23 | const server = new Server(Hl7MllpMessageHandler); 24 | server.on('networkError', (e) => { 25 | console.log('Network error: ', e); 26 | }); 27 | server.listen(port); 28 | 29 | const client = new Client(); 30 | const hl7Message = new Hl7Message( 31 | `MSH|^~\&|SENDINGAPP|SENDINGFACILITY|RECEIVINGAPP|RECEIVINGFACILITY|200001010000||ADT|1234567890|D|2.2| 32 | PID||123456^^^2^ID 1|654321||DOE^JOHN^^^^|DOE^JOHN^^^^|19480203|M|` 33 | ); 34 | hl7Message.on('acknowledge', (ackMessage) => { 35 | console.log(ackMessage.toString('\n')); 36 | }); 37 | 38 | client.addMessage(hl7Message); 39 | client.send('127.0.0.1', port); 40 | 41 | setTimeout(() => { 42 | server.close(); 43 | const statistics = server.getStatistics(); 44 | console.log('Server statistics:', statistics.toString()); 45 | }, 3000); 46 | -------------------------------------------------------------------------------- /.jsdocrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "tags": { 3 | "allowUnknownTags": true 4 | }, 5 | "source": { 6 | "include": ["./src", "README.md"], 7 | "includePattern": ".js$", 8 | "excludePattern": "(node_modules/|docs)" 9 | }, 10 | "templates": { 11 | "cleverLinks": false, 12 | "monospaceLinks": false, 13 | "default": { 14 | "outputSourceFiles": true, 15 | "includeDate": false, 16 | "useLongnameInNav": true 17 | } 18 | }, 19 | "plugins": ["plugins/markdown"], 20 | "markdown": { 21 | "parser": "gfm", 22 | "hardwrap": true, 23 | "idInHeadings": true 24 | }, 25 | "opts": { 26 | "destination": "./documentation/", 27 | "encoding": "utf8", 28 | "private": true, 29 | "recurse": true, 30 | "template": "./node_modules/docdash" 31 | }, 32 | "docdash": { 33 | "static": true, 34 | "sort": true, 35 | "meta": { 36 | "title": "hl7-mllp", 37 | "description": "HL7 Minimum Lower Layer Protocol (MLLP) implementation for Node.js", 38 | "keyword": "HL7, MLLP" 39 | }, 40 | "search": true, 41 | "collapse": true, 42 | "typedefs": true, 43 | "removeQuotes": "none", 44 | "scripts": [], 45 | "menu": { 46 | "Github repo": { 47 | "href": "https://github.com/PantelisGeorgiadis/hl7-mllp", 48 | "target": "_blank", 49 | "class": "menu-item", 50 | "id": "repository" 51 | } 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | const { BannerPlugin } = require('webpack'); 2 | const path = require('path'); 3 | const TerserPlugin = require('terser-webpack-plugin'); 4 | const pkg = require('./package.json'); 5 | 6 | const rootPath = process.cwd(); 7 | const context = path.join(rootPath, 'src'); 8 | const outputPath = path.join(rootPath, 'build'); 9 | const filename = path.parse(pkg.main).base; 10 | 11 | const getCurrentDate = () => { 12 | const today = new Date(); 13 | const year = today.getFullYear(); 14 | const month = ('0' + (today.getMonth() + 1)).slice(-2); 15 | const date = ('0' + today.getDate()).slice(-2); 16 | return `${year}-${month}-${date}`; 17 | }; 18 | 19 | const getBanner = () => { 20 | return ( 21 | `/*! ${pkg.name} - ${pkg.version} - ` + 22 | `${getCurrentDate()} ` + 23 | `| (c) 2022-2025 ${pkg.author} | ${pkg.homepage} */` 24 | ); 25 | }; 26 | 27 | module.exports = { 28 | mode: 'production', 29 | context, 30 | entry: { 31 | hl7Mllp: './index.js', 32 | }, 33 | target: 'node', 34 | output: { 35 | filename, 36 | library: pkg.name, 37 | libraryTarget: 'umd', 38 | path: outputPath, 39 | umdNamedDefine: true, 40 | }, 41 | optimization: { 42 | minimize: true, 43 | minimizer: [ 44 | new TerserPlugin({ 45 | extractComments: false, 46 | parallel: true, 47 | terserOptions: { 48 | sourceMap: true, 49 | }, 50 | }), 51 | ], 52 | }, 53 | plugins: [ 54 | new BannerPlugin({ 55 | banner: getBanner(), 56 | entryOnly: true, 57 | raw: true, 58 | }), 59 | ], 60 | }; 61 | -------------------------------------------------------------------------------- /test/Statistics.test.js: -------------------------------------------------------------------------------- 1 | const Statistics = require('./../src/Statistics'); 2 | 3 | const chai = require('chai'); 4 | const expect = chai.expect; 5 | 6 | describe('Statistics', () => { 7 | it('should correctly construct Statistics', () => { 8 | const statistics = new Statistics(); 9 | 10 | expect(statistics.getBytesReceived()).to.be.eq(0); 11 | expect(statistics.getBytesSent()).to.be.eq(0); 12 | }); 13 | 14 | it('should correctly add and reset statistics values', () => { 15 | const statistics = new Statistics(); 16 | 17 | statistics.addBytesReceived(10); 18 | statistics.addBytesSent(20); 19 | expect(statistics.getBytesReceived()).to.be.eq(10); 20 | expect(statistics.getBytesSent()).to.be.eq(20); 21 | 22 | statistics.addBytesReceived(30); 23 | statistics.addBytesSent(40); 24 | expect(statistics.getBytesReceived()).to.be.eq(40); 25 | expect(statistics.getBytesSent()).to.be.eq(60); 26 | 27 | statistics.reset(); 28 | expect(statistics.getBytesReceived()).to.be.eq(0); 29 | expect(statistics.getBytesSent()).to.be.eq(0); 30 | }); 31 | 32 | it('should correctly add other statistics values', () => { 33 | const statistics1 = new Statistics(); 34 | const statistics2 = new Statistics(); 35 | 36 | statistics1.addBytesReceived(10); 37 | statistics1.addBytesSent(20); 38 | 39 | statistics2.addBytesReceived(30); 40 | statistics2.addBytesSent(40); 41 | 42 | statistics1.addFromOtherStatistics(statistics2); 43 | expect(statistics1.getBytesReceived()).to.be.eq(40); 44 | expect(statistics1.getBytesSent()).to.be.eq(60); 45 | expect(statistics1.toString()).to.be.a('string'); 46 | }); 47 | }); 48 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![NPM version][npm-version-image]][npm-url] [![NPM downloads][npm-downloads-image]][npm-url] [![build][build-image]][build-url] [![MIT License][license-image]][license-url] 2 | 3 | # hl7-mllp 4 | HL7 Minimum Lower Layer Protocol (MLLP) implementation for Node.js. 5 | 6 | ### Note 7 | **This effort is a work-in-progress and should not be used for production or clinical purposes.** 8 | 9 | ### Install 10 | 11 | npm install hl7-mllp 12 | 13 | ### Build 14 | 15 | npm install 16 | npm run build 17 | 18 | ### Examples 19 | 20 | #### Client 21 | ```js 22 | const hl7Mllp = require('hl7-mllp'); 23 | const { Client, Hl7Message } = hl7Mllp; 24 | 25 | const client = new Client(); 26 | const hl7Message = new Hl7Message( 27 | `MSH|^~\&|SENDINGAPP|SENDINGFACILITY|RECEIVINGAPP|RECEIVINGFACILITY|200001010000||ADT|1234567890|D|2.2| 28 | PID||123456^^^2^ID 1|654321||DOE^JOHN^^^^|DOE^JOHN^^^^|19480203|M|` 29 | ); 30 | hl7Message.on('acknowledge', (ackMessage) => { 31 | console.log(ackMessage.toString()); 32 | }); 33 | 34 | client.addMessage(hl7Message); 35 | client.send('127.0.0.1', 12345); 36 | ``` 37 | 38 | #### Server 39 | ```js 40 | const hl7Mllp = require('hl7-mllp'); 41 | const { Server, Hl7Message, Hl7MessageHandler } = hl7Mllp; 42 | 43 | class Hl7MllpMessageHandler extends Hl7MessageHandler { 44 | constructor(socket, opts) { 45 | super(socket, opts); 46 | } 47 | 48 | // Handle incoming messages 49 | onMessage(message, callback) { 50 | console.log(message.toString()); 51 | callback(Hl7Message.createAcknowledgeMessage(message)); 52 | } 53 | } 54 | 55 | const server = new Server(Hl7MllpMessageHandler); 56 | server.on('networkError', (e) => { 57 | console.log('Network error: ', e); 58 | }); 59 | server.listen(port); 60 | 61 | // When done 62 | server.close(); 63 | ``` 64 | 65 | ### License 66 | hl7-mllp is released under the MIT License. 67 | 68 | [npm-url]: https://npmjs.org/package/hl7-mllp 69 | [npm-version-image]: https://img.shields.io/npm/v/hl7-mllp.svg?style=flat 70 | [npm-downloads-image]: http://img.shields.io/npm/dm/hl7-mllp.svg?style=flat 71 | 72 | [build-url]: https://github.com/PantelisGeorgiadis/hl7-mllp/actions/workflows/build.yml 73 | [build-image]: https://github.com/PantelisGeorgiadis/hl7-mllp/actions/workflows/build.yml/badge.svg?branch=master 74 | 75 | [license-image]: https://img.shields.io/badge/license-MIT-blue.svg?style=flat 76 | [license-url]: LICENSE.txt 77 | -------------------------------------------------------------------------------- /src/Statistics.js: -------------------------------------------------------------------------------- 1 | //#region Statistics 2 | class Statistics { 3 | /** 4 | * Creates an instance of Statistics. 5 | * @constructor 6 | */ 7 | constructor() { 8 | this.bytesReceived = 0; 9 | this.bytesSent = 0; 10 | } 11 | 12 | /** 13 | * Gets the received bytes. 14 | * @method 15 | * @returns {number} Received bytes. 16 | */ 17 | getBytesReceived() { 18 | return this.bytesReceived; 19 | } 20 | 21 | /** 22 | * Gets the sent bytes. 23 | * @method 24 | * @returns {number} Sent bytes. 25 | */ 26 | getBytesSent() { 27 | return this.bytesSent; 28 | } 29 | 30 | /** 31 | * Adds bytes to the received bytes. 32 | * @method 33 | * @param {number} bytes - Bytes to add. 34 | */ 35 | addBytesReceived(bytes) { 36 | this.bytesReceived += bytes; 37 | } 38 | 39 | /** 40 | * Adds bytes to the sent bytes. 41 | * @method 42 | * @param {number} bytes - Bytes to add. 43 | */ 44 | addBytesSent(bytes) { 45 | this.bytesSent += bytes; 46 | } 47 | 48 | /** 49 | * Adds values from other statistics. 50 | * @method 51 | * @param {Statistics} statistics - Statistics to add. 52 | */ 53 | addFromOtherStatistics(statistics) { 54 | this.addBytesReceived(statistics.getBytesReceived()); 55 | this.addBytesSent(statistics.getBytesSent()); 56 | } 57 | 58 | /** 59 | * Resets received and sent bytes. 60 | * @method 61 | */ 62 | reset() { 63 | this.bytesReceived = 0; 64 | this.bytesSent = 0; 65 | } 66 | 67 | /** 68 | * Gets the statistics description. 69 | * @method 70 | * @return {string} Statistics description. 71 | */ 72 | toString() { 73 | return `Sent: ${this._formatBytes(this.getBytesSent())}, Received: ${this._formatBytes( 74 | this.getBytesReceived() 75 | )}`; 76 | } 77 | 78 | //#region Private Methods 79 | /** 80 | * Formats bytes to size in KB, MB, GB, TB, and PB. 81 | * @method 82 | * @private 83 | * @returns {string} Formatted size. 84 | */ 85 | _formatBytes(bytes) { 86 | if (bytes === undefined || bytes === 0) { 87 | return '0 Bytes'; 88 | } 89 | const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB']; 90 | const i = Math.floor(Math.log(bytes) / Math.log(1024)); 91 | 92 | return `${parseFloat((bytes / Math.pow(1024, i)).toFixed(3))} ${sizes[i]}`; 93 | } 94 | //#endregion 95 | } 96 | //#endregion 97 | 98 | //#region Exports 99 | module.exports = Statistics; 100 | //#endregion 101 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "hl7-mllp", 3 | "version": "0.0.9", 4 | "description": "HL7 Minimum Lower Layer Protocol (MLLP) implementation for Node.js", 5 | "main": "build/hl7-mllp.min.js", 6 | "module": "build/hl7-mllp.min.js", 7 | "types": "index.d.ts", 8 | "scripts": { 9 | "clean": "npm run clean:build && npm run clean:coverage && npm run clean:docs", 10 | "clean:build": "shx rm -rf build", 11 | "clean:coverage": "shx rm -rf coverage", 12 | "clean:docs": "shx rm -rf documentation", 13 | "coverage": "c8 --check-coverage --lines 80 --functions 80 --branches 80 --statements 80 --clean --reporter=text --reporter=html --include=src/*.js --exclude=src/index.js --exclude=src/version.js --exclude=src/log.js mocha test/**/*.test.js", 14 | "doc": "npm run doc:generate && open-cli documentation/index.html", 15 | "doc:generate": "npm run clean:docs && jsdoc -c .jsdocrc.json", 16 | "start:examples:js": "node examples/index.js", 17 | "start:examples:ts": "ts-node examples/index.ts", 18 | "build": "npm run version && npm run lint && npm run test && npm run coverage && npm run webpack", 19 | "start": "npm run webpack", 20 | "version": "node -p -e \"'module.exports = \\'' + require('./package.json').version + '\\';'\" > src/version.js", 21 | "webpack": "webpack --progress --config webpack.config.js", 22 | "test:node": "mocha test/**/*.test.js", 23 | "test:types": "tsd", 24 | "test": "npm run test:types && npm run test:node", 25 | "lint": "eslint -c .eslintrc.json --fix src && prettier --config .prettierrc.json --write src/**/*.js test/**/*.js examples/**/*.{js,ts} *.ts" 26 | }, 27 | "repository": { 28 | "type": "git", 29 | "url": "git+https://github.com/PantelisGeorgiadis/hl7-mllp.git" 30 | }, 31 | "keywords": [ 32 | "HL7", 33 | "MLLP" 34 | ], 35 | "author": "Pantelis Georgiadis", 36 | "license": "MIT", 37 | "bugs": { 38 | "url": "https://github.com/PantelisGeorgiadis/hl7-mllp/issues" 39 | }, 40 | "homepage": "https://github.com/PantelisGeorgiadis/hl7-mllp", 41 | "dependencies": { 42 | "async-eventemitter": "^0.2.4", 43 | "dayjs": "^1.11.13", 44 | "loglevel": "^1.9.2", 45 | "loglevel-plugin-prefix": "^0.8.4" 46 | }, 47 | "devDependencies": { 48 | "@types/async-eventemitter": "^0.2.4", 49 | "@types/node": "^22.13.4", 50 | "c8": "^9.1.0", 51 | "chai": "^4.3.10", 52 | "docdash": "^2.0.2", 53 | "eslint": "^8.57.0", 54 | "jsdoc": "^4.0.4", 55 | "mocha": "^11.1.0", 56 | "open-cli": "^7.2.0", 57 | "prettier": "^3.4.2", 58 | "shx": "^0.3.3", 59 | "terser-webpack-plugin": "^5.3.11", 60 | "ts-node": "^10.9.2", 61 | "tsd": "^0.31.2", 62 | "typescript": "^5.7.3", 63 | "webpack": "^5.97.1", 64 | "webpack-cli": "^6.0.1" 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/Client.js: -------------------------------------------------------------------------------- 1 | const { Hl7Message } = require('./Hl7'); 2 | const Network = require('./Network'); 3 | const Statistics = require('./Statistics'); 4 | const log = require('./log'); 5 | 6 | const AsyncEventEmitter = require('async-eventemitter'); 7 | const net = require('net'); 8 | 9 | //#region Client 10 | class Client extends AsyncEventEmitter { 11 | /** 12 | * Creates an instance of Client. 13 | * @constructor 14 | */ 15 | constructor() { 16 | super(); 17 | this.messages = []; 18 | this.statistics = new Statistics(); 19 | } 20 | 21 | /** 22 | * Adds an HL7 message. 23 | * @method 24 | * @param {Hl7Message} message - HL7 message object. 25 | * @throws {Error} If message is not an instance of the Hl7Message class. 26 | */ 27 | addMessage(message) { 28 | if (!(message instanceof Hl7Message)) { 29 | throw new Error(`${message.toString()} is not an instance of Hl7Message`); 30 | } 31 | // Prevent duplicates 32 | if (this.messages.includes(message)) { 33 | return; 34 | } 35 | this.messages.push(message); 36 | } 37 | 38 | /** 39 | * Clears all HL7 messages. 40 | * @method 41 | */ 42 | clearMessages() { 43 | this.messages.length = 0; 44 | } 45 | 46 | /** 47 | * Sends messages to the remote host. 48 | * @method 49 | * @param {string} host - Remote host. 50 | * @param {number} port - Remote port. 51 | * @param {Object} [opts] - Network options. 52 | * @param {number} [opts.connectTimeout] - Connection timeout in milliseconds. 53 | * @param {boolean} [opts.logMessages] - Log messages. 54 | * @throws {Error} If there are no messages to send. 55 | */ 56 | send(host, port, opts) { 57 | opts = opts || {}; 58 | 59 | // Check for messages 60 | if (this.messages.length === 0) { 61 | throw new Error('There are no messages to send'); 62 | } 63 | 64 | // Connect 65 | log.info(`Connecting to ${host}:${port}`); 66 | const socket = net.connect({ host, port }); 67 | 68 | const network = new Network(socket, opts); 69 | network.on('connect', () => { 70 | this.emit('connected'); 71 | network.sendMessages(this.messages); 72 | }); 73 | network.on('done', () => { 74 | socket.end(); 75 | }); 76 | network.on('networkError', (err) => { 77 | socket.end(); 78 | this.emit('networkError', err); 79 | }); 80 | network.on('close', () => { 81 | this.statistics.addFromOtherStatistics(network.getStatistics()); 82 | this.emit('closed'); 83 | }); 84 | } 85 | 86 | /** 87 | * Gets network statistics. 88 | * @method 89 | * @returns {Statistics} Network statistics. 90 | */ 91 | getStatistics() { 92 | return this.statistics; 93 | } 94 | } 95 | //#endregion 96 | 97 | //#region Exports 98 | module.exports = Client; 99 | //#endregion 100 | -------------------------------------------------------------------------------- /test/Hl7.test.js: -------------------------------------------------------------------------------- 1 | const { Hl7, Hl7Message, Tag } = require('./../src/Hl7'); 2 | 3 | const chai = require('chai'); 4 | const expect = chai.expect; 5 | 6 | function getRandomInteger(min, max) { 7 | return Math.floor(Math.random() * (max - min + 1)) + min; 8 | } 9 | 10 | function getRandomString(len) { 11 | const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; 12 | var randomString = ''; 13 | for (let i = 0; i < len; i++) { 14 | const randomPosition = Math.floor(Math.random() * chars.length); 15 | randomString += chars.substring(randomPosition, randomPosition + 1); 16 | } 17 | 18 | return randomString; 19 | } 20 | 21 | describe('Hl7', () => { 22 | it('should correctly construct a Tag', () => { 23 | const tag1 = new Tag('OBR[0].1.2.3'); 24 | 25 | expect(tag1.getSegment()).to.be.eq('OBR'); 26 | expect(tag1.getSegmentNumber()).to.be.eq(0); 27 | expect(tag1.getField()).to.be.eq(1); 28 | expect(tag1.getComponent()).to.be.eq(2); 29 | expect(tag1.getSubComponent()).to.be.eq(3); 30 | 31 | const tag2 = new Tag('PID[1].1'); 32 | 33 | expect(tag2.getSegment()).to.be.eq('PID'); 34 | expect(tag2.getSegmentNumber()).to.be.eq(1); 35 | expect(tag2.getField()).to.be.eq(1); 36 | expect(tag2.getComponent()).to.be.eq(0); 37 | expect(tag2.getSubComponent()).to.be.eq(0); 38 | 39 | const tag3 = new Tag('PID.4'); 40 | 41 | expect(tag3.getSegment()).to.be.eq('PID'); 42 | expect(tag3.getSegmentNumber()).to.be.eq(1); 43 | expect(tag3.getField()).to.be.eq(4); 44 | expect(tag3.getComponent()).to.be.eq(0); 45 | expect(tag3.getSubComponent()).to.be.eq(0); 46 | expect(tag3.toString()).to.be.a('string'); 47 | }); 48 | 49 | it('should throw in case of constructing an invalid Tag', () => { 50 | expect(() => { 51 | const tag = new Tag('OB'); 52 | }).to.throw(); 53 | 54 | expect(() => { 55 | const tag = new Tag('PID[1].1.2.3.4'); 56 | }).to.throw(); 57 | }); 58 | 59 | it('should correctly set and get a Tag for an Hl7 object and Hl7Message object', () => { 60 | const hl7 = Hl7.parse( 61 | 'MSH|^~&|SENDINGAPP|SENDINGFACILITY|RECEIVINGAPP|RECEIVINGFACILITY|200001010000||ADT|1234567890|D|2.2|' 62 | ); 63 | const hl7Message = new Hl7Message(hl7); 64 | const segmentLen = getRandomInteger(2, 8); 65 | const fieldLen = getRandomInteger(2, 8); 66 | const componentLen = getRandomInteger(2, 8); 67 | const subComponentLen = getRandomInteger(2, 8); 68 | 69 | const segments = ['EVN', 'OBR', 'OBX', 'ORC', 'PID']; 70 | const randomSegment = segments[Math.floor(Math.random() * segments.length)]; 71 | 72 | for (let i = 1; i < segmentLen; i++) { 73 | for (let j = 1; j < fieldLen; j++) { 74 | for (let k = 1; k < componentLen; k++) { 75 | for (let l = 1; l < subComponentLen; l++) { 76 | const strLen = getRandomInteger(4, 64); 77 | const randomTag = new Tag(`${randomSegment}[${i}].${j}.${k}.${l}`); 78 | const randomString = getRandomString(strLen); 79 | 80 | hl7.set(randomTag, randomString); 81 | hl7Message.set(randomTag, randomString); 82 | 83 | const hl7String = hl7.toString(); 84 | const hl7MessageString = hl7Message.toString(); 85 | 86 | const hl7Parsed = Hl7.parse(hl7String); 87 | const hl7MessageParsed = new Hl7Message(hl7MessageString); 88 | 89 | expect(hl7Parsed.get(randomTag)).to.be.eq(randomString); 90 | expect(hl7MessageParsed.get(randomTag)).to.be.eq(randomString); 91 | } 92 | } 93 | } 94 | } 95 | }); 96 | }); 97 | -------------------------------------------------------------------------------- /src/Server.js: -------------------------------------------------------------------------------- 1 | const { Hl7Message } = require('./Hl7'); 2 | const Network = require('./Network'); 3 | const Statistics = require('./Statistics'); 4 | const log = require('./log'); 5 | 6 | const AsyncEventEmitter = require('async-eventemitter'); 7 | const net = require('net'); 8 | 9 | //#region Hl7MessageHandler 10 | class Hl7MessageHandler extends Network { 11 | /** 12 | * Creates an instance of Hl7MessageHandler. 13 | * @constructor 14 | * @param {Socket} socket - Network socket. 15 | * @param {Object} [opts] - Network options. 16 | * @param {number} [opts.connectTimeout] - Connection timeout in milliseconds. 17 | * @param {boolean} [opts.logMessages] - Log messages. 18 | */ 19 | constructor(socket, opts) { 20 | super(socket, opts); 21 | 22 | this.on('message', (message, callback) => { 23 | this.onMessage(message, callback); 24 | }); 25 | } 26 | 27 | /** 28 | * Message received. 29 | * @method 30 | * @param {Hl7Message} message - HL7 message object. 31 | * @param {function(Hl7Message)} callback - Message acknowledge callback function. 32 | */ 33 | onMessage(message, callback) { 34 | const error = 'onMessage method must be implemented'; 35 | log.error(error); 36 | callback(Hl7Message.createAcknowledgeMessage(message, { error })); 37 | } 38 | } 39 | //#endregion 40 | 41 | //#region Server 42 | class Server extends AsyncEventEmitter { 43 | /** 44 | * Creates an instance of Server. 45 | * @constructor 46 | * @param {Hl7MessageHandler} handlerClass - The handling class to receive incoming HL7 messages. 47 | */ 48 | constructor(handlerClass) { 49 | super(); 50 | this.handler = { class: handlerClass }; 51 | this.server = undefined; 52 | this.clients = []; 53 | this.statistics = new Statistics(); 54 | } 55 | 56 | /** 57 | * Listens for incoming connections. 58 | * @method 59 | * @param {number} port - Remote port. 60 | * @param {Object} [opts] - Network options. 61 | * @param {number} [opts.connectTimeout] - Connection timeout in milliseconds. 62 | * @param {boolean} [opts.logMessages] - Log messages. 63 | */ 64 | listen(port, opts) { 65 | opts = opts || {}; 66 | 67 | // Initialize network 68 | this.server = net.createServer((socket) => { 69 | log.info(`Client connecting from ${socket.remoteAddress}:${socket.remotePort}`); 70 | const client = new this.handler.class(socket, opts); 71 | client.connected = true; 72 | client.on('close', () => { 73 | this.statistics.addFromOtherStatistics(client.getStatistics()); 74 | }); 75 | client.on('networkError', (err) => { 76 | socket.end(); 77 | this.emit('networkError', err); 78 | }); 79 | this.clients.push(client); 80 | 81 | this.clients = this.clients.filter((item) => item.connected); 82 | }); 83 | this.server.on('listening', () => { 84 | log.info(`MLLP server listening on port ${port}`); 85 | }); 86 | this.server.on('error', (err) => { 87 | const error = `Server error: ${err.message}`; 88 | log.error(error); 89 | this.emit('networkError', err); 90 | }); 91 | this.server.listen(port); 92 | } 93 | 94 | /** 95 | * Gets network statistics. 96 | * @method 97 | * @returns {Statistics} Network statistics. 98 | */ 99 | getStatistics() { 100 | return this.statistics; 101 | } 102 | 103 | /** 104 | * Closes the server. 105 | * @method 106 | */ 107 | close() { 108 | if (this.server && this.server.listening) { 109 | this.server.close(); 110 | } 111 | 112 | // Close all live sockets 113 | this.clients.forEach((client) => client.socket.destroy()); 114 | this.clients = []; 115 | } 116 | } 117 | //#endregion 118 | 119 | //#region Exports 120 | module.exports = { Server, Hl7MessageHandler }; 121 | //#endregion 122 | -------------------------------------------------------------------------------- /test/Network.test.js: -------------------------------------------------------------------------------- 1 | const { Hl7Message, Tag } = require('./../src/Hl7'); 2 | const { Hl7MessageHandler, Server } = require('./../src/Server'); 3 | const Client = require('./../src/Client'); 4 | const log = require('./../src/log'); 5 | 6 | const chai = require('chai'); 7 | const expect = chai.expect; 8 | 9 | class PositiveAcknowledgementHl7MessageHandler extends Hl7MessageHandler { 10 | constructor(socket, opts) { 11 | super(socket, opts); 12 | } 13 | onMessage(message, callback) { 14 | callback(Hl7Message.createAcknowledgeMessage(message)); 15 | } 16 | } 17 | 18 | class ApplicationErrorAcknowledgementHl7MessageHandler extends Hl7MessageHandler { 19 | constructor(socket, opts) { 20 | super(socket, opts); 21 | } 22 | onMessage(message, callback) { 23 | callback(Hl7Message.createAcknowledgeMessage(message, { error: 'Error :-(' })); 24 | } 25 | } 26 | 27 | describe('Network', () => { 28 | before(() => { 29 | log.setLevel('error'); 30 | }); 31 | 32 | it('should throw in case of bad client input', () => { 33 | expect(() => { 34 | const client1 = new Client(); 35 | client1.addMessage('this is a message'); 36 | }).to.throw(); 37 | 38 | expect(() => { 39 | const client2 = new Client(); 40 | const message2 = new Hl7Message( 41 | 'MSH|^~&|SENDINGAPP|SENDINGFACILITY|RECEIVINGAPP|RECEIVINGFACILITY|200001010000||ADT|1234567890|D|2.2|' 42 | ); 43 | client2.addMessage(message2); 44 | client2.clearMessages(); 45 | client2.send('127.0.0.1', 2100); 46 | }).to.throw(); 47 | }); 48 | 49 | it('should correctly send an HL7 message and receive a positive acknowledgement', (done) => { 50 | const expectedMcId = `${Math.floor(Math.random() * 1000000000)}`; 51 | let ack = false; 52 | let mcId = undefined; 53 | let aa = undefined; 54 | 55 | const server = new Server(PositiveAcknowledgementHl7MessageHandler); 56 | server.on('networkError', (e) => { 57 | throw e; 58 | }); 59 | server.listen(2101); 60 | 61 | const client = new Client(); 62 | const message = new Hl7Message( 63 | `MSH|^~\&|SENDINGAPP|SENDINGFACILITY|RECEIVINGAPP|RECEIVINGFACILITY|200001010000||ADT|${expectedMcId}|D|2.2| 64 | PID||123456^^^2^ID 1|654321||DOE^JOHN^^^^|DOE^JOHN^^^^|19480203|M|` 65 | ); 66 | message.on('acknowledge', (m) => { 67 | ack = true; 68 | mcId = m.getMessageControlId(); 69 | aa = m.get(new Tag('MSA[0].1')); 70 | }); 71 | client.addMessage(message); 72 | client.on('closed', () => { 73 | expect(ack).to.be.true; 74 | expect(mcId).to.be.eq(expectedMcId); 75 | expect(aa).to.be.eq('AA'); 76 | server.close(); 77 | done(); 78 | }); 79 | client.on('networkError', (e) => { 80 | throw e; 81 | }); 82 | client.send('127.0.0.1', 2101); 83 | }); 84 | 85 | it('should correctly send an HL7 message and receive an acknowledgement with application error', (done) => { 86 | const expectedMcId = `${Math.floor(Math.random() * 1000000000)}`; 87 | let ack = false; 88 | let mcId = undefined; 89 | let ae = undefined; 90 | 91 | const server = new Server(ApplicationErrorAcknowledgementHl7MessageHandler); 92 | server.on('networkError', (e) => { 93 | throw e; 94 | }); 95 | server.listen(2102); 96 | 97 | const client = new Client(); 98 | const message = new Hl7Message( 99 | `MSH|^~\&|SENDINGAPP|SENDINGFACILITY|RECEIVINGAPP|RECEIVINGFACILITY|200001010000||ADT|${expectedMcId}|D|2.2| 100 | PID||123456^^^2^ID 1|654321||DOE^JOHN^^^^|DOE^JOHN^^^^|19480203|M|` 101 | ); 102 | message.on('acknowledge', (m) => { 103 | ack = true; 104 | mcId = m.getMessageControlId(); 105 | ae = m.get(new Tag('MSA[0].1')); 106 | }); 107 | client.addMessage(message); 108 | client.on('closed', () => { 109 | expect(ack).to.be.true; 110 | expect(mcId).to.be.eq(expectedMcId); 111 | expect(ae).to.be.eq('AE'); 112 | server.close(); 113 | done(); 114 | }); 115 | client.on('networkError', (e) => { 116 | throw e; 117 | }); 118 | client.send('127.0.0.1', 2102); 119 | }); 120 | }); 121 | -------------------------------------------------------------------------------- /index.test-d.ts: -------------------------------------------------------------------------------- 1 | import { expectType, expectError } from 'tsd'; 2 | import { 3 | Tag, 4 | Hl7, 5 | Hl7Message, 6 | Client, 7 | Server, 8 | Hl7MessageHandler, 9 | Statistics, 10 | log, 11 | version, 12 | } from '.'; 13 | import { Logger } from 'loglevel'; 14 | 15 | // log 16 | expectType(log); 17 | 18 | // version 19 | expectType(version); 20 | 21 | // Tag 22 | expectError(new Tag()); 23 | expectError(new Tag(1)); 24 | expectType(new Tag('OBR[0].1.2.3')); 25 | 26 | const tag = new Tag('OBR[0].1.2.3'); 27 | expectType(tag.getSegment()); 28 | expectType(tag.getSegmentNumber()); 29 | expectType(tag.getField()); 30 | expectType(tag.getComponent()); 31 | expectType(tag.getSubComponent()); 32 | expectType(tag.toString()); 33 | 34 | // Hl7 35 | expectType(new Hl7()); 36 | expectError(Hl7.parse(1)); 37 | expectType(Hl7.parse('MSH|^~&|||||200001010000||ADT|1234567890|D|2.2|')); 38 | expectError(Hl7.isHeaderSegment(1)); 39 | expectType(Hl7.isHeaderSegment('MSH')); 40 | 41 | const hl7 = Hl7.parse('MSH|^~&|||||200001010000||ADT|1234567890|D|2.2|'); 42 | expectError(hl7.get('1')); 43 | expectError(hl7.get('1', 2)); 44 | expectType(hl7.get(tag, '')); 45 | expectError(hl7.set('1')); 46 | expectError(hl7.set('1', 2)); 47 | expectType(hl7.set(tag, '1')); 48 | expectError(hl7.getSegments(1)); 49 | expectType>(hl7.getSegments('OBR')); 50 | expectType>(hl7.getSegments()); 51 | expectError(hl7.countSegments(1)); 52 | expectType(hl7.countSegments('OBR')); 53 | expectType(hl7.countSegments()); 54 | expectError(hl7.toString(1)); 55 | expectType(hl7.toString()); 56 | 57 | // Hl7Message 58 | expectError(new Hl7Message(1)); 59 | expectType(new Hl7Message()); 60 | expectType(new Hl7Message('MSH|^~&|||||200001010000||ADT|1234567890|D|2.2|')); 61 | expectType( 62 | new Hl7Message(Hl7.parse('MSH|^~&|||||200001010000||ADT|1234567890|D|2.2|')) 63 | ); 64 | expectError(Hl7Message.createAcknowledgeMessage(1)); 65 | expectError(Hl7Message.createAcknowledgeMessage(1, '2')); 66 | expectType( 67 | Hl7Message.createAcknowledgeMessage( 68 | new Hl7Message('MSH|^~&|||||200001010000||ADT|1234567890|D|2.2|') 69 | ) 70 | ); 71 | 72 | const hl7Message = new Hl7Message(Hl7.parse('MSH|^~&|||||200001010000||ADT|1234567890|D|2.2|')); 73 | expectError(hl7Message.get('1')); 74 | expectError(hl7Message.get('1', 2)); 75 | expectType(hl7Message.get(tag, '')); 76 | expectError(hl7Message.set('1')); 77 | expectError(hl7Message.set('1', 2)); 78 | expectType(hl7Message.set(tag, '1')); 79 | expectType(hl7Message.getMessageControlId()); 80 | expectError(hl7Message.toString(1)); 81 | expectType(hl7Message.toString()); 82 | 83 | // Statistics 84 | const statistics = new Statistics(); 85 | expectType(statistics.getBytesReceived()); 86 | expectType(statistics.getBytesSent()); 87 | expectError(statistics.addBytesReceived('1')); 88 | expectError(statistics.addBytesSent('1')); 89 | expectError(statistics.addFromOtherStatistics('1')); 90 | expectType(statistics.reset()); 91 | expectType(statistics.toString()); 92 | 93 | // Server 94 | class Hl7MessageHandlerTest extends Hl7MessageHandler { 95 | onMessage(message: Hl7Message, callback: (message: Hl7Message) => void) {} 96 | } 97 | 98 | expectError(new Server(1)); 99 | expectType(new Server(Hl7MessageHandlerTest)); 100 | 101 | const server = new Server(Hl7MessageHandlerTest); 102 | expectError(server.listen('2104')); 103 | expectError(server.listen(2104, '1')); 104 | expectType(server.listen(2104)); 105 | expectType(server.close()); 106 | expectType(server.getStatistics()); 107 | 108 | // Client 109 | expectType(new Client()); 110 | 111 | const client = new Client(); 112 | expectType(client.clearMessages()); 113 | expectError(client.addMessage('1')); 114 | expectType( 115 | client.addMessage(new Hl7Message('MSH|^~&|||||200001010000||ADT|1234567890|D|2.2|')) 116 | ); 117 | expectType(client.getStatistics()); 118 | expectError(client.send('1')); 119 | expectError(client.send('1', '2')); 120 | expectError(client.send('1', '2', 3)); 121 | -------------------------------------------------------------------------------- /index.d.ts: -------------------------------------------------------------------------------- 1 | import { Logger } from 'loglevel'; 2 | import { Socket } from 'net'; 3 | import AsyncEventEmitter from 'async-eventemitter'; 4 | 5 | declare class Tag { 6 | /** 7 | * Creates an instance of Tag. 8 | */ 9 | constructor(tag: string); 10 | 11 | /** 12 | * Gets the segment. 13 | */ 14 | getSegment(): string; 15 | 16 | /** 17 | * Gets the segment number. 18 | */ 19 | getSegmentNumber(): number; 20 | 21 | /** 22 | * Gets the field. 23 | */ 24 | getField(): number; 25 | 26 | /** 27 | * Gets the component. 28 | */ 29 | getComponent(): number; 30 | 31 | /** 32 | * Gets the sub-component. 33 | */ 34 | getSubComponent(): number; 35 | 36 | /** 37 | * Gets the tag description. 38 | */ 39 | toString(): string; 40 | } 41 | 42 | declare class Hl7 { 43 | /** 44 | * Creates an instance of Hl7. 45 | */ 46 | constructor(); 47 | 48 | /** 49 | * Parses an HL7 message string. 50 | */ 51 | static parse(hl7: string): Hl7; 52 | 53 | /** 54 | * Checks whether the segment ID is a header segment. 55 | */ 56 | static isHeaderSegment(segmentId: string): boolean; 57 | 58 | /** 59 | * Gets a tag value. 60 | */ 61 | get(tag: Tag, defaultValue: string): string; 62 | 63 | /** 64 | * Sets a tag value. 65 | */ 66 | set(tag: Tag, value: string): void; 67 | 68 | /** 69 | * Gets segments. 70 | */ 71 | getSegments(segmentId?: string): Array; 72 | 73 | /** 74 | * Counts segments. 75 | */ 76 | countSegments(segmentId?: string): number; 77 | 78 | /** 79 | * Builds the HL7 message. 80 | */ 81 | toString(segmentSeparator?: string): string; 82 | } 83 | 84 | declare class Hl7Message extends AsyncEventEmitter { 85 | /** 86 | * Creates an instance of Hl7Message. 87 | */ 88 | constructor(hl7?: Hl7 | string); 89 | 90 | /** 91 | * Creates an acknowledge message from a message. 92 | */ 93 | static createAcknowledgeMessage( 94 | message: Hl7Message, 95 | opts?: { 96 | sendingApplication?: string; 97 | sendingFacility?: string; 98 | error?: string; 99 | } 100 | ): Hl7Message; 101 | 102 | /** 103 | * Gets a tag value. 104 | */ 105 | get(tag: Tag, defaultValue: string): string; 106 | 107 | /** 108 | * Sets a tag value. 109 | */ 110 | set(tag: Tag, value: string): void; 111 | 112 | /** 113 | * Gets message control ID. 114 | */ 115 | getMessageControlId(): string; 116 | 117 | /** 118 | * Gets the HL7 message. 119 | */ 120 | toString(segmentSeparator?: string): string; 121 | } 122 | 123 | declare class Network extends AsyncEventEmitter { 124 | /** 125 | * Underlying socket. 126 | */ 127 | socket: Socket; 128 | 129 | /** 130 | * Creates an instance of Network. 131 | */ 132 | constructor( 133 | socket: Socket, 134 | opts?: { 135 | connectTimeout?: number; 136 | logMessages?: boolean; 137 | [key: string]: any; 138 | } 139 | ); 140 | 141 | /** 142 | * Sends HL7 messages. 143 | */ 144 | sendMessages(messages: Array | Hl7Message): void; 145 | } 146 | 147 | declare class Statistics { 148 | /** 149 | * Creates an instance of Statistics. 150 | */ 151 | constructor(); 152 | 153 | /** 154 | * Gets the received bytes. 155 | */ 156 | getBytesReceived(): number; 157 | 158 | /** 159 | * Gets the sent bytes. 160 | */ 161 | getBytesSent(): number; 162 | 163 | /** 164 | * Adds bytes to the received bytes. 165 | */ 166 | addBytesReceived(bytes: number): void; 167 | 168 | /** 169 | * Adds bytes to the sent bytes. 170 | */ 171 | addBytesSent(bytes: number): void; 172 | 173 | /** 174 | * Adds values from other statistics. 175 | */ 176 | addFromOtherStatistics(statistics: Statistics): void; 177 | 178 | /** 179 | * Resets received and sent bytes. 180 | */ 181 | reset(): void; 182 | 183 | /** 184 | * Gets the statistics description. 185 | */ 186 | toString(): string; 187 | } 188 | 189 | declare class Hl7MessageHandler extends Network { 190 | /** 191 | * Message received. 192 | */ 193 | onMessage(message: Hl7Message, callback: (message: Hl7Message) => void): void; 194 | } 195 | 196 | declare class Server extends AsyncEventEmitter { 197 | /** 198 | * Creates an instance of Server. 199 | */ 200 | constructor(handlerClass: typeof Hl7MessageHandler); 201 | 202 | /** 203 | * Listens for incoming connections. 204 | */ 205 | listen( 206 | port: number, 207 | opts?: { 208 | connectTimeout?: number; 209 | logMessages?: boolean; 210 | [key: string]: any; 211 | } 212 | ): void; 213 | 214 | /** 215 | * Gets network statistics. 216 | */ 217 | getStatistics(): Statistics; 218 | 219 | /** 220 | * Closes the server. 221 | */ 222 | close(): void; 223 | } 224 | 225 | declare class Client extends AsyncEventEmitter { 226 | /** 227 | * Creates an instance of Client. 228 | */ 229 | constructor(); 230 | 231 | /** 232 | * Adds an HL7 message. 233 | */ 234 | addMessage(message: Hl7Message): void; 235 | 236 | /** 237 | * Clears all HL7 messages. 238 | */ 239 | clearMessages(): void; 240 | 241 | /** 242 | * Sends messages to the remote host. 243 | */ 244 | send( 245 | host: string, 246 | port: number, 247 | opts?: { 248 | connectTimeout?: number; 249 | logMessages?: boolean; 250 | } 251 | ): void; 252 | 253 | /** 254 | * Gets network statistics. 255 | */ 256 | getStatistics(): Statistics; 257 | } 258 | 259 | /** 260 | * Logger. 261 | */ 262 | declare const log: Logger; 263 | 264 | /** 265 | * Version. 266 | */ 267 | declare const version: string; 268 | 269 | export { Tag, Hl7, Hl7Message, Client, Server, Hl7MessageHandler, Statistics, log, version }; 270 | -------------------------------------------------------------------------------- /src/Network.js: -------------------------------------------------------------------------------- 1 | const { Hl7Message } = require('./Hl7'); 2 | const Statistics = require('./Statistics'); 3 | const log = require('./log'); 4 | 5 | const AsyncEventEmitter = require('async-eventemitter'); 6 | 7 | //#region Constants 8 | /** 9 | * Vertical tab character. 10 | * @constant {string} 11 | */ 12 | const Vt = String.fromCharCode(0x0b); 13 | Object.freeze(Vt); 14 | 15 | /** 16 | * Field separator character. 17 | * @constant {string} 18 | */ 19 | const Fs = String.fromCharCode(0x1c); 20 | Object.freeze(Fs); 21 | 22 | /** 23 | * Carriage return character. 24 | * @constant {string} 25 | */ 26 | const Cr = String.fromCharCode(0x0d); 27 | Object.freeze(Cr); 28 | //#endregion 29 | 30 | //#region Network 31 | class Network extends AsyncEventEmitter { 32 | /** 33 | * Creates an instance of Network. 34 | * @constructor 35 | * @param {Socket} socket - Network socket. 36 | * @param {Object} [opts] - Network options. 37 | * @param {number} [opts.connectTimeout] - Connection timeout in milliseconds. 38 | * @param {boolean} [opts.logMessages] - Log messages. 39 | */ 40 | constructor(socket, opts) { 41 | super(); 42 | this.socket = socket; 43 | this.messages = []; 44 | this.pending = []; 45 | 46 | opts = opts || {}; 47 | this.connectTimeout = opts.connectTimeout || 3 * 60 * 1000; 48 | this.logMessages = opts.logMessages || false; 49 | this.connected = false; 50 | this.logId = socket.remoteAddress || ''; 51 | this.statistics = new Statistics(); 52 | 53 | this.socket.setTimeout(this.connectTimeout); 54 | this.socket.on('connect', () => { 55 | this.connected = true; 56 | this.emit('connect'); 57 | }); 58 | const messageProcessor = new Hl7MessageProcessor(); 59 | messageProcessor.on('message', (hl7) => { 60 | this._processMessage(hl7); 61 | }); 62 | this.socket.on('data', (data) => { 63 | messageProcessor.process(data); 64 | this.statistics.addBytesReceived(data.length); 65 | }); 66 | this.socket.on('error', (err) => { 67 | this.connected = false; 68 | const error = `${this.logId} -> Connection error: ${err.message}`; 69 | log.error(error); 70 | this.emit('networkError', new Error(error)); 71 | }); 72 | this.socket.on('timeout', () => { 73 | this.connected = false; 74 | const error = `${this.logId} -> Connection timeout`; 75 | log.error(error); 76 | this.emit('networkError', new Error(error)); 77 | }); 78 | this.socket.on('close', () => { 79 | this.connected = false; 80 | log.info(`${this.logId} -> Connection closed`); 81 | this.emit('close'); 82 | }); 83 | } 84 | 85 | /** 86 | * Sends HL7 messages. 87 | * @method 88 | * @param {Hl7Message|Array} messageOrMessages - HL7 message(s) to send. 89 | */ 90 | sendMessages(messageOrMessages) { 91 | const msgs = Array.isArray(messageOrMessages) ? messageOrMessages : [messageOrMessages]; 92 | this.messages.push(...msgs); 93 | this._sendNextMessages(); 94 | } 95 | 96 | /** 97 | * Gets network statistics. 98 | * @method 99 | * @returns {Statistics} Network statistics. 100 | */ 101 | getStatistics() { 102 | return this.statistics; 103 | } 104 | 105 | //#region Private Methods 106 | /** 107 | * Sends messages over the network. 108 | * @method 109 | * @private 110 | */ 111 | _sendNextMessages() { 112 | const processNextMessage = () => { 113 | const message = this.messages.shift(); 114 | if (!message) { 115 | this.emit('done'); 116 | return; 117 | } 118 | this.pending.push(message); 119 | message.on('done', () => { 120 | processNextMessage(); 121 | }); 122 | this._sendMessage(message); 123 | }; 124 | processNextMessage(); 125 | } 126 | 127 | /** 128 | * Sends HL7 message. 129 | * @method 130 | * @private 131 | * @param {Hl7Message} message - HL7 message object. 132 | */ 133 | _sendMessage(message) { 134 | try { 135 | log.info( 136 | `${this.logId} -> Sending message [id: ${message.getMessageControlId()}] ${ 137 | this.logMessages ? message.toString('\n') : '' 138 | }` 139 | ); 140 | 141 | const data = Vt + message.toString() + Fs + Cr; 142 | this.socket.write(data); 143 | this.statistics.addBytesSent(data.length); 144 | } catch (err) { 145 | log.error(`${this.logId} -> Error sending HL7 message: ${err.message}`); 146 | this.emit('networkError', err); 147 | } 148 | } 149 | 150 | /** 151 | * Processes an HL7 message. 152 | * @method 153 | * @private 154 | * @param {string} hl7 - HL7 message. 155 | */ 156 | _processMessage(hl7) { 157 | try { 158 | const message = new Hl7Message(hl7); 159 | 160 | log.info( 161 | `${this.logId} <- Received message [id: ${message.getMessageControlId()}] ${ 162 | this.logMessages ? message.toString('\n') : '' 163 | }` 164 | ); 165 | 166 | const messageToAcknowledge = this.pending.find( 167 | (r) => r.getMessageControlId() === message.getMessageControlId() 168 | ); 169 | if (messageToAcknowledge) { 170 | messageToAcknowledge.raiseAcknowledgeEvent(message); 171 | messageToAcknowledge.raiseDoneEvent(); 172 | 173 | return; 174 | } 175 | 176 | this.emit('message', message, (acknowledgeMessage) => { 177 | this._sendMessage(acknowledgeMessage); 178 | }); 179 | } catch (err) { 180 | log.error(`${this.logId} -> Error processing HL7 message: ${err.message}`); 181 | this.emit('networkError', err); 182 | } 183 | } 184 | //#endregion 185 | } 186 | //#endregion 187 | 188 | //#region Hl7MessageProcessor 189 | class Hl7MessageProcessor extends AsyncEventEmitter { 190 | /** 191 | * Creates an instance of Hl7MessageProcessor. 192 | * @constructor 193 | */ 194 | constructor() { 195 | super(); 196 | 197 | this.hl7Message = ''; 198 | } 199 | 200 | /** 201 | * Processes the received data until a full HL7 message is received. 202 | * @method 203 | * @param {Buffer} data - The received data. 204 | */ 205 | process(data) { 206 | // Accumulate received data 207 | this.hl7Message += data.toString(); 208 | 209 | // Find start of MLLP frame, a VT character 210 | const startOfMllpEnvelope = this.hl7Message.indexOf(Vt); 211 | if (startOfMllpEnvelope >= 0) { 212 | // Look for the end of the frame, a FS and CR character 213 | const endOfMllpEnvelope = this.hl7Message.indexOf(Fs + Cr); 214 | 215 | // End of block received 216 | if (endOfMllpEnvelope >= startOfMllpEnvelope) { 217 | const hl7Message = this.hl7Message.substring( 218 | startOfMllpEnvelope + 1 /* Skip VT */, 219 | endOfMllpEnvelope - startOfMllpEnvelope 220 | ); 221 | 222 | // Emit the received message 223 | this.emit('message', hl7Message); 224 | this.hl7Message = ''; 225 | } 226 | } 227 | } 228 | } 229 | //#endregion 230 | 231 | //#region Exports 232 | module.exports = Network; 233 | //#endregion 234 | -------------------------------------------------------------------------------- /src/Hl7.js: -------------------------------------------------------------------------------- 1 | const dayjs = require('dayjs'); 2 | const AsyncEventEmitter = require('async-eventemitter'); 3 | 4 | //#region Tag 5 | class Tag { 6 | /** 7 | * Creates an instance of Tag. 8 | * @constructor 9 | * @param {string} tag - Tag that represents a path within an HL7 message. 10 | * @throws {Error} If the tag does not contain at least the segment information or 11 | * contains more than four parts. 12 | */ 13 | constructor(tag) { 14 | this.tag = tag || ''; 15 | if (this.tag.length < 3) { 16 | throw new Error('Tag needs to contain at least the segment information'); 17 | } 18 | 19 | this.segment = this.tag.substring(0, 3).toUpperCase(); 20 | 21 | this.segmentNumber = 1; 22 | if (tag[3] === '[') { 23 | const segmentNumberStr = this.tag.substring(4, this.tag.indexOf(']')); 24 | this.segmentNumber = parseInt(segmentNumberStr); 25 | } 26 | 27 | const parts = this.tag.split('.'); 28 | if (parts.length > 4) { 29 | throw new Error('Tag should not contain more than four parts'); 30 | } 31 | 32 | this.field = 0; 33 | this.component = 0; 34 | this.subComponent = 0; 35 | 36 | if (parts.length >= 2) { 37 | this.field = parseInt(parts[1]); 38 | } 39 | if (parts.length >= 3) { 40 | this.component = parseInt(parts[2]); 41 | } 42 | if (parts.length === 4) { 43 | this.subComponent = parseInt(parts[3]); 44 | } 45 | } 46 | 47 | /** 48 | * Gets the segment. 49 | * @method 50 | * @returns {string} Segment. 51 | */ 52 | getSegment() { 53 | return this.segment; 54 | } 55 | 56 | /** 57 | * Gets the segment number. 58 | * @method 59 | * @returns {number} Segment number. 60 | */ 61 | getSegmentNumber() { 62 | return this.segmentNumber; 63 | } 64 | 65 | /** 66 | * Gets the field. 67 | * @method 68 | * @returns {number} Field. 69 | */ 70 | getField() { 71 | return this.field; 72 | } 73 | 74 | /** 75 | * Gets the component. 76 | * @method 77 | * @returns {number} Component. 78 | */ 79 | getComponent() { 80 | return this.component; 81 | } 82 | 83 | /** 84 | * Gets the sub-component. 85 | * @method 86 | * @returns {number} Sub-component. 87 | */ 88 | getSubComponent() { 89 | return this.subComponent; 90 | } 91 | 92 | /** 93 | * Gets the tag description. 94 | * @method 95 | * @returns {string} Tag description. 96 | */ 97 | toString() { 98 | let tag = this.getSegment(); 99 | if (this.getSegmentNumber() > 1) { 100 | tag += `[${this.getSegmentNumber()}]`; 101 | } 102 | if (this.getField() > 0) { 103 | tag += `.${this.getField()}`; 104 | } 105 | if (this.getComponent() > 0) { 106 | tag += `.${this.getComponent()}`; 107 | } 108 | if (this.getSubComponent() > 0) { 109 | tag += `.${this.getSubComponent()}`; 110 | } 111 | 112 | return tag; 113 | } 114 | } 115 | //#endregion 116 | 117 | //#region Hl7 118 | class Hl7 { 119 | /** 120 | * Creates an instance of Hl7. 121 | * @constructor 122 | */ 123 | constructor() { 124 | this.segments = []; 125 | this.fieldDelimiter = '|'; 126 | this.componentDelimiter = '^'; 127 | this.subComponentDelimiter = '&'; 128 | } 129 | 130 | /** 131 | * Parses an HL7 message string. 132 | * @method 133 | * @static 134 | * @param {string} hl7 - HL7 message string. 135 | * @returns {Hl7} The parsed HL7 message object. 136 | */ 137 | static parse(hl7) { 138 | const message = new Hl7(); 139 | const lines = hl7.split(/\r|\n|\r\n/); 140 | 141 | lines.forEach((line) => { 142 | line = line.trim(); 143 | if (!line) { 144 | return; 145 | } 146 | 147 | const segmentId = line.substring(0, 3); 148 | if (Hl7.isHeaderSegment(segmentId)) { 149 | this.fieldDelimiter = line[3]; 150 | this.componentDelimiter = line[4]; 151 | this.subComponentDelimiter = line[7]; 152 | 153 | const segment = []; 154 | segment.push(segmentId); 155 | segment.push(this.fieldDelimiter); 156 | segment.push(...line.substring(4).split(this.fieldDelimiter)); 157 | 158 | message.segments.push(segment); 159 | } else { 160 | message.segments.push(line.split(this.fieldDelimiter)); 161 | } 162 | }); 163 | 164 | return message; 165 | } 166 | 167 | /** 168 | * Checks whether the segment ID is a header segment. 169 | * @method 170 | * @static 171 | * @param {string} segmentId - Segment ID. 172 | * @returns {boolean} Flag indicating whether the segment ID is a header segment. 173 | */ 174 | static isHeaderSegment(segmentId) { 175 | return segmentId === 'MSH' || segmentId === 'BHS' || segmentId === 'FHS'; 176 | } 177 | 178 | /** 179 | * Gets a tag value. 180 | * @method 181 | * @param {Tag} tag - Tag. 182 | * @param {string} defaultValue - Default value. 183 | * @returns {string} Value or default. 184 | * @throws {Error} If the tag is invalid for get operation. 185 | */ 186 | get(tag, defaultValue) { 187 | let value = undefined; 188 | if (tag.getSubComponent() > 0) { 189 | value = this._getSubComponent(tag); 190 | } else if (tag.getComponent() > 0) { 191 | value = this._getComponent(tag); 192 | } else if (tag.getField() > 0) { 193 | value = this._getField(tag); 194 | } else { 195 | throw new Error(`Invalid tag for get operation [${tag.toString()}]`); 196 | } 197 | 198 | return value !== undefined ? value : defaultValue; 199 | } 200 | 201 | /** 202 | * Sets a tag value. 203 | * @method 204 | * @param {Tag} tag - Tag. 205 | * @param {string} value - Value. 206 | * @throws {Error} If the tag is invalid for set operation. 207 | */ 208 | set(tag, value) { 209 | if (tag.getSubComponent() > 0) { 210 | this._setSubComponent(tag, value); 211 | } else if (tag.getComponent() > 0) { 212 | this._setComponent(tag, value); 213 | } else if (tag.getField() > 0) { 214 | this._setField(tag, value); 215 | } else { 216 | throw new Error(`Invalid tag for set operation [${tag.toString()}]`); 217 | } 218 | } 219 | 220 | /** 221 | * Gets segments. 222 | * @method 223 | * @param {string} [segmentId] - Segment ID. 224 | * @returns {Array} Segment tags. 225 | */ 226 | getSegments(segmentId) { 227 | let tags = []; 228 | if (segmentId === undefined) { 229 | const segments = []; 230 | this.segments.forEach((s) => { 231 | let segment = segments.find((seg) => seg.id === s[0]); 232 | segments.push(segment === undefined ? { id: s[0], n: 0 } : { id: s[0], n: segment.n + 1 }); 233 | }); 234 | segments.forEach((s) => { 235 | tags.push(new Tag(`${s.id}[${s.n}]`)); 236 | }); 237 | } else { 238 | let n = 0; 239 | this.segments.forEach((s) => { 240 | if (s[0] === segmentId) { 241 | tags.push(new Tag(`${segmentId}[${++n}]`)); 242 | } 243 | }); 244 | } 245 | 246 | return tags; 247 | } 248 | 249 | /** 250 | * Counts segments. 251 | * @method 252 | * @param {string} [segmentId] - Segment ID. 253 | * @returns {number} Segment count. 254 | */ 255 | countSegments(segmentId) { 256 | if (segmentId === undefined) { 257 | return this.segments.length; 258 | } 259 | 260 | let n = 0; 261 | this.segments.forEach((s) => { 262 | if (s[0] === segmentId) { 263 | n++; 264 | } 265 | }); 266 | 267 | return n; 268 | } 269 | 270 | /** 271 | * Builds the HL7 message. 272 | * @method 273 | * @param {string} [segmentSeparator] - Segment separator. 274 | * @returns {string} HL7 message. 275 | */ 276 | toString(segmentSeparator) { 277 | segmentSeparator = segmentSeparator || '\r'; 278 | let fieldDelimiter = '|'; 279 | const message = []; 280 | 281 | this.segments.forEach((segment) => { 282 | const segmentId = segment[0]; 283 | if (Hl7.isHeaderSegment(segmentId)) { 284 | fieldDelimiter = segment[1]; 285 | 286 | message.push(segmentId); 287 | message.push(fieldDelimiter); 288 | message.push(segment.slice(2, segment.length).join(fieldDelimiter)); 289 | } else { 290 | message.push(segment.join(fieldDelimiter)); 291 | } 292 | message.push(segmentSeparator); 293 | }); 294 | 295 | return message.join(''); 296 | } 297 | 298 | //#region Private Methods 299 | /** 300 | * Gets segment value. 301 | * @method 302 | * @private 303 | * @param {Tag} tag - Tag. 304 | * @returns {string} Segment. 305 | */ 306 | _getSegment(tag) { 307 | let segment = undefined; 308 | let number = Math.max(1, tag.getSegmentNumber()); 309 | this.segments.forEach((s) => { 310 | if (s[0] === tag.getSegment()) { 311 | if (--number === 0) { 312 | segment = s; 313 | } 314 | } 315 | }); 316 | 317 | return segment; 318 | } 319 | 320 | /** 321 | * Gets field value. 322 | * @method 323 | * @private 324 | * @param {Tag} tag - Tag. 325 | * @returns {string} Field. 326 | */ 327 | _getField(tag) { 328 | const segments = this._getSegment(tag); 329 | if (segments !== undefined) { 330 | if (tag.getField() < segments.length) { 331 | return segments[tag.getField()]; 332 | } 333 | } 334 | 335 | return undefined; 336 | } 337 | 338 | /** 339 | * Sets field value. 340 | * @method 341 | * @private 342 | * @param {Tag} tag - Tag. 343 | * @param {string} value - Value. 344 | */ 345 | _setField(tag, value) { 346 | let segment = this._getSegment(tag); 347 | if (segment === undefined) { 348 | segment = []; 349 | segment.push(tag.getSegment()); 350 | if (Hl7.isHeaderSegment(tag.getSegment())) { 351 | segment.push('|'); 352 | segment.push('^~\\&'); 353 | } 354 | this.segments.push(segment); 355 | } 356 | 357 | while (segment.length <= tag.getField()) { 358 | segment.push(''); 359 | } 360 | segment[tag.getField()] = value; 361 | 362 | if (Hl7.isHeaderSegment(tag.getSegment())) { 363 | if (tag.getField() === 1) { 364 | this.fieldDelimiter = value[0]; 365 | } else if (tag.getField() === 2) { 366 | this.componentDelimiter = value[0]; 367 | this.subComponentDelimiter = value[3]; 368 | } 369 | } 370 | } 371 | 372 | /** 373 | * Gets component value. 374 | * @method 375 | * @private 376 | * @param {Tag} tag - Tag. 377 | * @returns {string} Component. 378 | */ 379 | _getComponent(tag) { 380 | const field = this._getField(tag); 381 | if (field === undefined) { 382 | return undefined; 383 | } 384 | 385 | const components = field.split(this.componentDelimiter); 386 | if (tag.getComponent() <= components.length) { 387 | return components[tag.getComponent() - 1]; 388 | } 389 | 390 | return undefined; 391 | } 392 | 393 | /** 394 | * Sets component value. 395 | * @method 396 | * @private 397 | * @param {Tag} tag - Tag. 398 | * @param {string} value - Value. 399 | */ 400 | _setComponent(tag, value) { 401 | let field = this._getField(tag); 402 | if (field === undefined) { 403 | field = ''; 404 | } 405 | 406 | const components = field.split(this.componentDelimiter); 407 | while (components.length < tag.getComponent()) { 408 | components.push(''); 409 | } 410 | components[tag.getComponent() - 1] = value; 411 | 412 | this._setField(tag, components.join(this.componentDelimiter)); 413 | } 414 | 415 | /** 416 | * Gets sub-component value. 417 | * @method 418 | * @private 419 | * @param {Tag} tag - Tag. 420 | * @returns {string} Sub-component. 421 | */ 422 | _getSubComponent(tag) { 423 | const component = this._getComponent(tag); 424 | if (component === undefined) { 425 | return undefined; 426 | } 427 | 428 | const subs = component.split(this.subComponentDelimiter); 429 | if (tag.getSubComponent() <= subs.length) { 430 | return subs[tag.getSubComponent() - 1]; 431 | } 432 | 433 | return undefined; 434 | } 435 | 436 | /** 437 | * Sets sub-component value. 438 | * @method 439 | * @private 440 | * @param {Tag} tag - Tag. 441 | * @param {string} value - Value. 442 | */ 443 | _setSubComponent(tag, value) { 444 | let component = this._getComponent(tag); 445 | if (component === undefined) { 446 | component = ''; 447 | } 448 | 449 | const subs = component.split(this.subComponentDelimiter); 450 | while (subs.length < tag.getSubComponent) { 451 | subs.push(''); 452 | } 453 | subs[tag.getSubComponent() - 1] = value; 454 | 455 | this._setComponent(tag, subs.join(this.subComponentDelimiter)); 456 | } 457 | //#endregion 458 | } 459 | //#endregion 460 | 461 | //#region Hl7Message 462 | class Hl7Message extends AsyncEventEmitter { 463 | /** 464 | * Creates an instance of Hl7Message. 465 | * @constructor 466 | * @param {Hl7|string} [hl7] - HL7 object or string. 467 | */ 468 | constructor(hl7) { 469 | super(); 470 | 471 | if (hl7 instanceof Hl7) { 472 | this.hl7 = hl7; 473 | return; 474 | } 475 | if (typeof hl7 === 'string' || hl7 instanceof String) { 476 | this.hl7 = Hl7.parse(hl7); 477 | return; 478 | } 479 | this.hl7 = new Hl7(); 480 | } 481 | 482 | /** 483 | * Gets a tag value. 484 | * @method 485 | * @param {Tag} tag - Tag. 486 | * @param {string} defaultValue - Default value. 487 | * @returns {string} Value or default. 488 | */ 489 | get(tag, defaultValue) { 490 | return this.hl7.get(tag, defaultValue); 491 | } 492 | 493 | /** 494 | * Sets a tag value. 495 | * @method 496 | * @param {Tag} tag - Tag. 497 | * @param {string} value - Value. 498 | */ 499 | set(tag, value) { 500 | this.hl7.set(tag, value); 501 | } 502 | 503 | /** 504 | * Gets message control ID. 505 | * @method 506 | * @return {string} Message control ID. 507 | */ 508 | getMessageControlId() { 509 | const msaCount = this.hl7.countSegments('MSA'); 510 | 511 | return msaCount > 0 ? this.hl7.get(new Tag('MSA[0].2')) : this.hl7.get(new Tag('MSH[0].10')); 512 | } 513 | 514 | /** 515 | * Creates an acknowledge message from a message. 516 | * @method 517 | * @static 518 | * @param {Hl7Message} message - HL7 message object. 519 | * @param {Object} [opts] - Acknowledge options. 520 | * @param {string} [opts.sendingApplication] - Sending application. 521 | * @param {string} [opts.sendingFacility] - Sending facility. 522 | * @param {string} [opts.error] - Application error. 523 | * @returns {Hl7Message} Acknowledge HL7 message object. 524 | * @throws {Error} If message is not an instance of Hl7Message. 525 | */ 526 | static createAcknowledgeMessage(message, opts) { 527 | if (!(message instanceof Hl7Message)) { 528 | throw new Error('Message should be an instance of Hl7Message'); 529 | } 530 | opts = opts || {}; 531 | const noError = !opts.error || !opts.error.trim(); 532 | 533 | const receivingApplication = message.get(new Tag('MSH[0].3'), ''); 534 | const receivingFacility = message.get(new Tag('MSH[0].4'), ''); 535 | const messageControlId = message.get(new Tag('MSH[0].10'), ''); 536 | const versionId = message.get(new Tag('MSH[0].12'), ''); 537 | 538 | const ackMessage = new Hl7Message(); 539 | ackMessage.set(new Tag('MSH[0].3'), opts.sendingApplication || 'HL7-MLLP'); 540 | ackMessage.set(new Tag('MSH[0].4'), opts.sendingFacility || ''); 541 | ackMessage.set(new Tag('MSH[0].5'), receivingApplication); 542 | ackMessage.set(new Tag('MSH[0].6'), receivingFacility); 543 | ackMessage.set(new Tag('MSH[0].7'), dayjs(new Date()).format('YYYYMMDDHHmmss')); 544 | ackMessage.set(new Tag('MSH[0].9'), 'ACK'); 545 | ackMessage.set(new Tag('MSH[0].10'), messageControlId); 546 | ackMessage.set(new Tag('MSH[0].11'), 'P'); 547 | ackMessage.set(new Tag('MSH[0].12'), versionId); 548 | 549 | ackMessage.set(new Tag('MSA[0].1'), noError ? 'AA' : 'AE'); 550 | ackMessage.set(new Tag('MSA[0].2'), messageControlId); 551 | ackMessage.set(new Tag('MSA[0].3'), noError ? '' : opts.error); 552 | 553 | return ackMessage; 554 | } 555 | 556 | /** 557 | * Raises message event. 558 | * @method 559 | * @param {Hl7Message} message - HL7 message object. 560 | */ 561 | raiseAcknowledgeEvent(message) { 562 | this.emit('acknowledge', message); 563 | } 564 | 565 | /** 566 | * Raises done event. 567 | * @method 568 | */ 569 | raiseDoneEvent() { 570 | this.emit('done'); 571 | } 572 | 573 | /** 574 | * Gets the HL7 message. 575 | * @method 576 | * @param {string} [segmentSeparator] - Segment separator. 577 | * @returns {string} HL7 message. 578 | */ 579 | toString(segmentSeparator) { 580 | return this.hl7.toString(segmentSeparator); 581 | } 582 | } 583 | //#endregion 584 | 585 | //#region Exports 586 | module.exports = { Hl7, Hl7Message, Tag }; 587 | //#endregion 588 | --------------------------------------------------------------------------------