hl7v2-net 1.0.2

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.
@@ -0,0 +1,154 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.HL7Socket = void 0;
4
+ const tslib_1 = require("tslib");
5
+ const hl7v2_1 = require("hl7v2");
6
+ const iconv_lite_1 = tslib_1.__importDefault(require("iconv-lite"));
7
+ const node_events_async_1 = require("node-events-async");
8
+ const frame_stream_js_1 = require("./helpers/frame-stream.js");
9
+ const hl7_exchange_error_js_1 = require("./helpers/hl7-exchange-error.js");
10
+ class HL7Socket extends node_events_async_1.AsyncEventEmitter {
11
+ constructor(socket, options) {
12
+ super();
13
+ this._waitPromises = new Set();
14
+ this.socket = socket;
15
+ this._options = options;
16
+ const frameStream = new frame_stream_js_1.FrameStream({
17
+ frameStart: hl7v2_1.VT,
18
+ frameEnd: hl7v2_1.FS + hl7v2_1.CR,
19
+ maxBufferSize: options?.maxBufferSize,
20
+ });
21
+ this._frameStream = frameStream;
22
+ socket.on('error', err => this.emit('error', err));
23
+ socket.pipe(frameStream);
24
+ socket.on('connect', () => this.emit('connect'));
25
+ socket.on('ready', () => this.emit('ready'));
26
+ socket.on('lookup', listener => this.emit('lookup', listener));
27
+ socket.on('timeout', () => socket.destroy());
28
+ socket.on('close', () => {
29
+ this.emit('close');
30
+ });
31
+ frameStream.on('data', data => this._parseMessage(data));
32
+ }
33
+ get connected() {
34
+ return !this.socket.closed;
35
+ }
36
+ get closed() {
37
+ return this.socket.closed;
38
+ }
39
+ get readyState() {
40
+ return this.socket.readyState;
41
+ }
42
+ get maxBufferSize() {
43
+ return this._frameStream.maxBufferSize || 0;
44
+ }
45
+ set maxBufferSize(value) {
46
+ this._frameStream.maxBufferSize = value;
47
+ }
48
+ address() {
49
+ return this.socket.address();
50
+ }
51
+ async close(waitRunningHandlers) {
52
+ if (this.closed)
53
+ return;
54
+ /** Stop receiving data */
55
+ this.socket.unpipe(this._frameStream);
56
+ /** Wait for running handlers to finish */
57
+ if (waitRunningHandlers && this._waitPromises.size > 0) {
58
+ await new Promise(resolve => {
59
+ /** Timeout timer will resolve this promise to stop waiting */
60
+ const timer = setTimeout(() => {
61
+ resolve();
62
+ }, waitRunningHandlers).unref();
63
+ Promise.allSettled(Array.from(this._waitPromises)).then(() => {
64
+ clearTimeout(timer);
65
+ resolve();
66
+ });
67
+ });
68
+ }
69
+ return new Promise(resolve => {
70
+ this.socket.once('close', () => resolve());
71
+ this.socket.destroy();
72
+ });
73
+ }
74
+ sendMessage(message) {
75
+ if (!this.connected)
76
+ throw new Error('Socket is not connected');
77
+ let encoding = message.header.field(hl7v2_1.MSHSegment.CharacterSet).value;
78
+ if (!encoding) {
79
+ encoding = 'UTF-8';
80
+ message.header.field(hl7v2_1.MSHSegment.CharacterSet).value = encoding;
81
+ }
82
+ const str = message.toHL7String();
83
+ const buf = iconv_lite_1.default.encode(str, encoding);
84
+ this.socket.write(hl7v2_1.VT);
85
+ this.socket.write(buf);
86
+ this.socket.end(hl7v2_1.FS + hl7v2_1.CR);
87
+ }
88
+ async sendMessageWaitAck(message) {
89
+ this.sendMessage(message);
90
+ const responseTimeout = this.responseTimeout;
91
+ const waitPromise = new Promise((resolve, reject) => {
92
+ let responseTimer;
93
+ const onMessage = (resp) => {
94
+ const msgType2 = resp.header.field(hl7v2_1.MSHSegment.MessageType).value;
95
+ if (msgType2 !== 'ACK')
96
+ return;
97
+ const msa = resp.getSegment('MSA');
98
+ if (!msa) {
99
+ const err = new hl7_exchange_error_js_1.HL7ExchangeError(`Invalid message returned from server. MSA segment not found.`, {
100
+ response: message,
101
+ request: resp,
102
+ segmentType: 'MSA',
103
+ hl7ErrorCode: 100,
104
+ });
105
+ onError(err);
106
+ return;
107
+ }
108
+ const controlId2 = msa.field(hl7v2_1.MSASegment.MessageControlID).value;
109
+ if (controlId2 !== controlId)
110
+ return;
111
+ cleanup();
112
+ resolve(resp);
113
+ };
114
+ const onError = (error) => {
115
+ cleanup();
116
+ reject(error);
117
+ };
118
+ const cleanup = () => {
119
+ clearTimeout(responseTimer);
120
+ this.removeListener('message', onMessage);
121
+ this.socket.removeListener('error', onError);
122
+ };
123
+ const controlId = message.header.field(hl7v2_1.MSHSegment.MessageControlID).value;
124
+ this.socket.once('error', reject);
125
+ this.on('message', onMessage);
126
+ if (responseTimeout) {
127
+ responseTimer = setTimeout(() => {
128
+ onError(new Error('Response timeout'));
129
+ }, responseTimeout).unref();
130
+ }
131
+ });
132
+ this._waitPromises.add(waitPromise);
133
+ waitPromise.finally(() => {
134
+ this._waitPromises.delete(waitPromise);
135
+ });
136
+ return waitPromise;
137
+ }
138
+ setKeepAlive(enable, initialDelay) {
139
+ this._options.keepAlive = enable;
140
+ this._options.keepAliveInitialDelay = initialDelay;
141
+ this.socket.setKeepAlive(enable, initialDelay);
142
+ }
143
+ _parseMessage(data) {
144
+ try {
145
+ const message = new hl7v2_1.HL7Message();
146
+ message.parse(data);
147
+ this.emit('message', message);
148
+ }
149
+ catch (err) {
150
+ this.emit('error', err);
151
+ }
152
+ }
153
+ }
154
+ exports.HL7Socket = HL7Socket;
package/cjs/index.js ADDED
@@ -0,0 +1,11 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const tslib_1 = require("tslib");
4
+ tslib_1.__exportStar(require("./helpers/hl7-exchange-error.js"), exports);
5
+ tslib_1.__exportStar(require("./hl7-client.js"), exports);
6
+ tslib_1.__exportStar(require("./hl7-request.js"), exports);
7
+ tslib_1.__exportStar(require("./hl7-response.js"), exports);
8
+ tslib_1.__exportStar(require("./hl7-router.js"), exports);
9
+ tslib_1.__exportStar(require("./hl7-server.js"), exports);
10
+ tslib_1.__exportStar(require("./hl7-socket.js"), exports);
11
+ tslib_1.__exportStar(require("./types.js"), exports);
@@ -0,0 +1,3 @@
1
+ {
2
+ "type": "commonjs"
3
+ }
package/cjs/types.js ADDED
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,4 @@
1
+ export const CR = '\x0D';
2
+ export const LF = '\x0A';
3
+ export const VT = '\x0B';
4
+ export const FS = '\x1C';
@@ -0,0 +1,133 @@
1
+ import { Buffer } from 'node:buffer';
2
+ import { Transform } from 'node:stream';
3
+ export class FrameStream extends Transform {
4
+ constructor(opts) {
5
+ super({
6
+ ...opts,
7
+ objectMode: true,
8
+ transform: undefined,
9
+ });
10
+ this._chunks = [];
11
+ this._bufferSize = 0;
12
+ this._customTransform = opts?.transform;
13
+ this._frameDelayMs = opts?.frameDelayMs || (opts?.frameEnd ? 0 : 500);
14
+ this.maxBufferSize = opts?.maxBufferSize;
15
+ if (opts?.frameStart) {
16
+ this._frameStart = Buffer.isBuffer(opts.frameStart)
17
+ ? opts.frameStart
18
+ : Buffer.from(opts.frameStart);
19
+ }
20
+ if (opts?.frameEnd) {
21
+ this._frameEnd = Buffer.isBuffer(opts.frameEnd)
22
+ ? opts.frameEnd
23
+ : Buffer.from(opts.frameEnd);
24
+ }
25
+ }
26
+ _transform(chunk, _encoding, callback) {
27
+ // Reset flush timer
28
+ if (this._flushTimeout) {
29
+ clearTimeout(this._flushTimeout);
30
+ }
31
+ try {
32
+ // console.log(chunk);
33
+ if (!Buffer.isBuffer(chunk))
34
+ chunk = Buffer.from(chunk);
35
+ if (this._chunkBuffer) {
36
+ this._assertMaxBuffer(chunk.length);
37
+ chunk = Buffer.concat([this._chunkBuffer, chunk]);
38
+ this._chunkBuffer = undefined;
39
+ }
40
+ const frameStart = this._frameStart;
41
+ const frameEnd = this._frameEnd;
42
+ if (frameStart || frameEnd) {
43
+ const stxLen = frameStart?.length || 0;
44
+ const etxLen = frameEnd?.length || 0;
45
+ let stxPos = 0;
46
+ let etxPos = 0;
47
+ while (etxPos < chunk.length) {
48
+ /** Find the next stx position */
49
+ stxPos = etxPos;
50
+ if (frameStart) {
51
+ stxPos = chunk.indexOf(frameStart, etxPos);
52
+ if (stxPos < 0) {
53
+ this._chunkBuffer = chunk.subarray(etxPos);
54
+ return;
55
+ }
56
+ }
57
+ if (stxPos < 0)
58
+ stxPos = 0;
59
+ etxPos = frameEnd
60
+ ? /** Find the next etx position if frameEnd defined */
61
+ chunk.indexOf(frameEnd, stxPos + stxLen)
62
+ : frameStart
63
+ ? /** Find the next stx if frameStart not defined */
64
+ chunk.indexOf(frameStart, stxPos + stxLen)
65
+ : -1;
66
+ if (etxPos < 0) {
67
+ this._chunkBuffer = chunk.subarray(stxPos);
68
+ return;
69
+ }
70
+ etxPos += etxLen;
71
+ this._pushChunk(chunk.subarray(stxPos, etxPos));
72
+ this.flushBuffer();
73
+ }
74
+ return;
75
+ }
76
+ this._pushChunk(chunk);
77
+ }
78
+ finally {
79
+ if (this._chunks.length && this._frameDelayMs)
80
+ this._flushTimeout = setTimeout(() => this.flushBuffer(), this._frameDelayMs).unref();
81
+ callback();
82
+ }
83
+ }
84
+ _pushChunk(chunk) {
85
+ this._assertMaxBuffer(chunk.length);
86
+ this._chunks.push(chunk);
87
+ this._bufferSize += chunk.length;
88
+ }
89
+ _assertMaxBuffer(newChunkLen = 0) {
90
+ if (this.maxBufferSize &&
91
+ this._bufferSize + newChunkLen + (this._chunkBuffer?.length || 0) >
92
+ this.maxBufferSize) {
93
+ this.emit('error', new Error('Max buffer size exceeded'));
94
+ }
95
+ }
96
+ _flush(callback) {
97
+ if (this._flushTimeout) {
98
+ clearTimeout(this._flushTimeout);
99
+ }
100
+ this.flushBuffer();
101
+ callback();
102
+ }
103
+ reset() {
104
+ this._chunks = [];
105
+ this._chunkBuffer = undefined;
106
+ if (this._flushTimeout) {
107
+ clearTimeout(this._flushTimeout);
108
+ this._flushTimeout = undefined;
109
+ }
110
+ }
111
+ flushBuffer() {
112
+ if (this._chunkBuffer) {
113
+ this._chunks.push(this._chunkBuffer);
114
+ this._chunkBuffer = undefined;
115
+ }
116
+ /* c8 ignore next */
117
+ if (this._chunks.length === 0)
118
+ return;
119
+ const combined = this._chunks.length === 1 ? this._chunks[0] : Buffer.concat(this._chunks);
120
+ this._chunks = [];
121
+ this._bufferSize = 0;
122
+ if (this._customTransform) {
123
+ this._customTransform(combined, 'buffer', (err, data) => {
124
+ if (err)
125
+ this.emit('error', err);
126
+ else
127
+ this.push(data);
128
+ });
129
+ return;
130
+ }
131
+ this.push(combined);
132
+ }
133
+ }
@@ -0,0 +1,10 @@
1
+ import { HL7Error } from 'hl7v2';
2
+ export class HL7ExchangeError extends HL7Error {
3
+ constructor(message, args) {
4
+ super(message, args);
5
+ if (args?.request)
6
+ this.request = args.request;
7
+ if (args?.response)
8
+ this.response = args.response;
9
+ }
10
+ }
@@ -0,0 +1,107 @@
1
+ import net from 'node:net';
2
+ import { AsyncEventEmitter } from 'node-events-async';
3
+ import { HL7Request } from './hl7-request.js';
4
+ import { HL7Response } from './hl7-response.js';
5
+ import { HL7Router } from './hl7-router.js';
6
+ import { HL7Socket } from './hl7-socket.js';
7
+ export class Hl7Client extends AsyncEventEmitter {
8
+ constructor(options) {
9
+ super();
10
+ this._router = new HL7Router();
11
+ this._options = options;
12
+ }
13
+ get connected() {
14
+ return this._socket?.connected ?? false;
15
+ }
16
+ get readyState() {
17
+ return this._socket?.readyState || 'closed';
18
+ }
19
+ get connectTimeout() {
20
+ return this._options.connectTimeout;
21
+ }
22
+ set connectTimeout(value) {
23
+ this._options.connectTimeout = value ?? undefined;
24
+ }
25
+ get responseTimeout() {
26
+ return this._options.responseTimeout;
27
+ }
28
+ set responseTimeout(value) {
29
+ this._options.responseTimeout = value ?? undefined;
30
+ }
31
+ get maxBufferSize() {
32
+ return this._options.maxBufferSize || 0;
33
+ }
34
+ set maxBufferSize(value) {
35
+ this._options.maxBufferSize = value;
36
+ if (this._socket)
37
+ this._socket.maxBufferSize = value;
38
+ }
39
+ connect() {
40
+ return new Promise((resolve, reject) => {
41
+ if (this.connected) {
42
+ resolve();
43
+ return;
44
+ }
45
+ let timeoutTimer;
46
+ const tcpSocket = net.connect(this._options);
47
+ const socket = (this._socket = new HL7Socket(tcpSocket, this._options));
48
+ socket.on('connect', () => this.emit('connect'));
49
+ socket.on('ready', () => this.emit('ready'));
50
+ socket.on('lookup', listener => this.emit('lookup', listener));
51
+ socket.on('close', () => {
52
+ this._socket = undefined;
53
+ this.emit('close');
54
+ });
55
+ socket.on('error', err => this.emit('error', err));
56
+ socket.on('message', message => this._onMessage(message));
57
+ const onReady = () => {
58
+ clearTimeout(timeoutTimer);
59
+ tcpSocket.removeListener('error', onError);
60
+ resolve();
61
+ };
62
+ const onError = (error) => {
63
+ clearTimeout(timeoutTimer);
64
+ tcpSocket.removeListener('ready', onReady);
65
+ tcpSocket.destroy();
66
+ reject(error);
67
+ };
68
+ tcpSocket.once('ready', onReady);
69
+ tcpSocket.once('error', onError);
70
+ if (this.connectTimeout) {
71
+ timeoutTimer = setTimeout(() => {
72
+ this.emit('error', new Error('Connection timeout'));
73
+ tcpSocket.destroy();
74
+ }, this._options.connectTimeout).unref();
75
+ }
76
+ });
77
+ }
78
+ async close(waitRunningHandlers) {
79
+ await this._socket?.close(waitRunningHandlers);
80
+ }
81
+ async sendMessage(message) {
82
+ if (!this.connected)
83
+ await this.connect();
84
+ this._socket.sendMessage(message);
85
+ }
86
+ async sendMessageWaitAck(request) {
87
+ if (!this.connected)
88
+ await this.connect();
89
+ return this._socket.sendMessageWaitAck(request);
90
+ }
91
+ setKeepAlive(enable, initialDelay) {
92
+ this._options.keepAlive = enable;
93
+ this._options.keepAliveInitialDelay = initialDelay;
94
+ this._socket?.setKeepAlive(enable, initialDelay);
95
+ }
96
+ use(handler, priority = 0) {
97
+ this._router.use(handler, priority);
98
+ }
99
+ _onMessage(message) {
100
+ const req = new HL7Request(this._socket, message);
101
+ const res = new HL7Response(req);
102
+ this._router.handle(undefined, req, res, error => {
103
+ if (error)
104
+ this.emit('error', error);
105
+ });
106
+ }
107
+ }
@@ -0,0 +1,6 @@
1
+ export class HL7Request {
2
+ constructor(socket, message) {
3
+ this.socket = socket;
4
+ this.message = message;
5
+ }
6
+ }
@@ -0,0 +1,24 @@
1
+ import { AsyncEventEmitter } from 'node-events-async';
2
+ export class HL7Response extends AsyncEventEmitter {
3
+ constructor(req) {
4
+ super();
5
+ this._finished = false;
6
+ this._req = req;
7
+ }
8
+ get socket() {
9
+ return this._req.socket;
10
+ }
11
+ get request() {
12
+ return this._req;
13
+ }
14
+ get finished() {
15
+ return this._finished || !this.socket.connected;
16
+ }
17
+ send(message) {
18
+ if (this.finished || !this.socket.connected)
19
+ return;
20
+ this.socket.sendMessage(message);
21
+ this._finished = true;
22
+ this.emit('finish', message);
23
+ }
24
+ }
@@ -0,0 +1,100 @@
1
+ export class HL7Router {
2
+ constructor() {
3
+ this._allHandlers = {};
4
+ this._handlers = [];
5
+ this._errorHandlers = [];
6
+ }
7
+ use(handler, priority = 0) {
8
+ let list = this._allHandlers[priority];
9
+ if (!list) {
10
+ list = [];
11
+ this._allHandlers[priority] = list;
12
+ }
13
+ if (typeof handler === 'function')
14
+ list.push(handler);
15
+ else {
16
+ // noinspection SuspiciousTypeOfGuard
17
+ if (handler instanceof HL7Router) {
18
+ list.push((req, res, next) => {
19
+ handler.handle(undefined, req, res, error => {
20
+ if (!res.finished)
21
+ next(error);
22
+ });
23
+ });
24
+ list.push((err, req, res, next) => {
25
+ handler.handle(err, req, res, error => {
26
+ if (!res.finished)
27
+ next(error);
28
+ });
29
+ });
30
+ } /* c8 ignore else */
31
+ else {
32
+ throw new TypeError('Router handler must be a function or HL7Router');
33
+ }
34
+ }
35
+ this._needPrepare = true;
36
+ }
37
+ handle(error, req, res, callback) {
38
+ this._prepareStack();
39
+ let errIdx = -1;
40
+ let handlerIdx = -1;
41
+ let lastErr;
42
+ let callbackCalled = false;
43
+ const doCallback = (err) => {
44
+ if (callbackCalled)
45
+ return;
46
+ callbackCalled = true;
47
+ res.removeListener('finish', onFinish);
48
+ callback(err);
49
+ };
50
+ const onFinish = () => doCallback();
51
+ res.once('finish', onFinish);
52
+ const next = (err) => {
53
+ lastErr = err || lastErr;
54
+ if (res.finished) {
55
+ doCallback();
56
+ return;
57
+ }
58
+ try {
59
+ if (err) {
60
+ errIdx++;
61
+ const handler = this._errorHandlers[errIdx];
62
+ if (!handler) {
63
+ doCallback(lastErr);
64
+ return;
65
+ }
66
+ handler(err, req, res, next);
67
+ return;
68
+ }
69
+ else {
70
+ handlerIdx++;
71
+ const handler = this._handlers[handlerIdx];
72
+ if (!handler) {
73
+ doCallback(lastErr);
74
+ return;
75
+ }
76
+ handler(req, res, next);
77
+ return;
78
+ }
79
+ }
80
+ catch (e) {
81
+ next(e);
82
+ }
83
+ };
84
+ next(error);
85
+ }
86
+ _prepareStack() {
87
+ if (!this._needPrepare)
88
+ return;
89
+ delete this._needPrepare;
90
+ this._errorHandlers = [];
91
+ this._handlers = [];
92
+ Object.keys(this._allHandlers).forEach(p => {
93
+ const h = this._allHandlers[p];
94
+ if (h.length === 4)
95
+ this._errorHandlers.push(...h);
96
+ else
97
+ this._handlers.push(...h);
98
+ });
99
+ }
100
+ }