@medplum/hl7 5.1.4 → 5.1.6

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.
@@ -1,2 +1,2 @@
1
- "use strict";var B=Object.create;var v=Object.defineProperty;var R=Object.getOwnPropertyDescriptor;var Q=Object.getOwnPropertyNames;var j=Object.getPrototypeOf,N=Object.prototype.hasOwnProperty;var F=(r,s,e)=>s in r?v(r,s,{enumerable:!0,configurable:!0,writable:!0,value:e}):r[s]=e;var K=(r,s)=>{for(var e in s)v(r,e,{get:s[e],enumerable:!0})},S=(r,s,e,t)=>{if(s&&typeof s=="object"||typeof s=="function")for(let n of Q(s))!N.call(r,n)&&n!==e&&v(r,n,{get:()=>s[n],enumerable:!(t=R(s,n))||t.enumerable});return r};var C=(r,s,e)=>(e=r!=null?B(j(r)):{},S(s||!r||!r.__esModule?v(e,"default",{value:r,enumerable:!0}):e,r)),W=r=>S(v({},"__esModule",{value:!0}),r);var i=(r,s,e)=>F(r,typeof s!="symbol"?s+"":s,e);var U={};K(U,{CR:()=>x,DEFAULT_ENCODING:()=>P,DEFAULT_FORCE_DRAIN_TIMEOUT_MS:()=>I,FS:()=>b,Hl7Base:()=>g,Hl7Client:()=>w,Hl7CloseEvent:()=>m,Hl7Connection:()=>f,Hl7EnhancedAckSentEvent:()=>E,Hl7ErrorEvent:()=>h,Hl7MessageEvent:()=>M,Hl7Server:()=>y,ReturnAckCategory:()=>L.ReturnAckCategory,VT:()=>A});module.exports=W(U);var g=class extends EventTarget{addEventListener(s,e,t){super.addEventListener(s,e,t)}removeEventListener(s,e,t){super.removeEventListener(s,e,t)}};var k=C(require("node:assert"),1),O=require("node:net");var a=require("@medplum/core"),H=C(require("iconv-lite"),1);var A=11,x=13,b=28;var M=class extends Event{constructor(e,t){super("message");i(this,"connection");i(this,"message");this.connection=e,this.message=t}},E=class extends Event{constructor(e,t){super("enhancedAckSent");i(this,"connection");i(this,"message");this.connection=e,this.message=t}},h=class extends Event{constructor(e){super("error");i(this,"error");this.error=e}},m=class extends Event{constructor(){super("close")}};var L=require("@medplum/core");var P="utf-8",_=60*1e3,f=class extends g{constructor(e,t=P,n,o={}){super();i(this,"socket");i(this,"encoding");i(this,"enhancedMode");i(this,"messagesPerMin");i(this,"chunks",[]);i(this,"pendingMessages",new Map);i(this,"responseQueue",[]);i(this,"lastMessageDispatchedTime",0);i(this,"responseQueueProcessing",!1);this.socket=e,this.encoding=t,this.enhancedMode=n,this.messagesPerMin=o.messagesPerMin,e.on("data",d=>{try{this.appendData(d);let c=this.parseMessages();for(let l of c)this.responseQueue.push(new M(this,l));this.processResponseQueue().catch(l=>{this.dispatchEvent(new h(l))})}catch(c){this.dispatchEvent(new h(c))}}),e.on("error",d=>{this.resetBuffer(),this.dispatchEvent(new h(d))}),e.on("close",()=>{this.dispatchEvent(new m)}),this.addEventListener("message",d=>{let c;this.enhancedMode==="standard"?c=d.message.buildAck({ackCode:"CA"}):this.enhancedMode==="aaMode"&&(c=d.message.buildAck({ackCode:"AA"})),c&&(this.send(c),this.dispatchEvent(new E(this,c)));let l=d.message.getSegment("MSA")?.getField(2)?.toString();if(!l)return;let p=this.pendingMessages.get(l);if(!p){this.dispatchEvent(new h(new a.OperationOutcomeError({resourceType:"OperationOutcome",issue:[{severity:"warning",code:"not-found",details:{text:"Response received for unknown message control ID"},diagnostics:`Received ACK for message control ID '${l}' but there was no pending message with this control ID`}]})));return}let u=d.message.getSegment("MSA")?.getField(1)?.toString()?.toUpperCase();u&&(p.returnAck===a.ReturnAckCategory.APPLICATION&&u==="CA"||(p.resolve(d.message),this.pendingMessages.delete(l)))})}isClosed(){return this.socket.closed}sendImpl(e){let t=e.toString(),n=H.default.encode(t,this.encoding),o=Buffer.alloc(n.length+3);o.writeInt8(11,0),n.copy(o,1),o.writeInt8(28,n.length+1),o.writeInt8(13,n.length+2),this.socket.write(o)}async processResponseQueue(){if(!this.responseQueueProcessing){for(this.responseQueueProcessing=!0;this.responseQueue.length;){if(this.messagesPerMin){let t=_/this.messagesPerMin,n=Date.now()-this.lastMessageDispatchedTime;t>n&&await(0,a.sleep)(t-n)}let e=this.responseQueue.shift();e&&this.dispatchEvent(e),this.lastMessageDispatchedTime=Date.now()}this.responseQueueProcessing=!1}}parseMessages(){let e=[],t=Buffer.concat(this.chunks);if(this.resetBuffer(),t.length===0)return e;let n=0;for(;n<t.length;){for(;t[n]!==11&&n<t.length;)n++;let o=-1;for(let u=n+1;u<t.length-1;u++)if(t[u]===28&&t[u+1]===13){o=u+1;break}if(o===-1)break;let c=t.subarray(n,o+1).subarray(1,-2),l=H.default.decode(c,this.encoding),p=a.Hl7Message.parse(l);e.push(p),n=o+1}return this.chunks=n<t.length?[t.subarray(n)]:[],e}send(e){this.sendImpl(e)}async sendAndWait(e,t){return new Promise((n,o)=>{let d=e.getSegment("MSH")?.getField(10)?.toString();if(!d){o(new a.OperationOutcomeError((0,a.validationError)("Required field missing: MSH.10")));return}let c;t?.timeoutMs&&(c=setTimeout(()=>{this.pendingMessages.delete(d),o(new a.OperationOutcomeError({resourceType:"OperationOutcome",issue:[{severity:"error",code:"timeout",details:{text:"Client timeout"},diagnostics:`Request timed out after waiting ${t.timeoutMs} milliseconds for response`}]}))},t.timeoutMs)),this.pendingMessages.set(d,{message:e,resolve:n,reject:o,returnAck:t?.returnAck??a.ReturnAckCategory.APPLICATION,timer:c}),this.sendImpl(e)})}async close(){if(!this.isClosed()){if(this.socket.end(),this.socket.destroy(),this.pendingMessages.size){for(let e of this.pendingMessages.values())e.timer&&clearTimeout(e.timer),e.reject(new a.OperationOutcomeError({resourceType:"OperationOutcome",issue:[{severity:"warning",code:"incomplete",details:{text:"Message was still pending when connection closed"}}]}));this.dispatchEvent(new h(new a.OperationOutcomeError({resourceType:"OperationOutcome",issue:[{severity:"warning",code:"incomplete",details:{text:"Messages were still pending when connection closed"},diagnostics:`Hl7Connection closed while ${this.pendingMessages.size} messages were pending`}]}))),this.pendingMessages.clear()}await new Promise(e=>{this.socket.once("close",e)})}}appendData(e){this.chunks.push(e)}resetBuffer(){this.chunks=[]}setEncoding(e){this.encoding=e??P}getEncoding(){return this.encoding}setEnhancedMode(e){this.enhancedMode=e}getEnhancedMode(){return this.enhancedMode}setMessagesPerMin(e){this.messagesPerMin=e}getMessagesPerMin(){return this.messagesPerMin}getPendingMessageCount(){return this.pendingMessages.size}};var w=class extends g{constructor(e){super();i(this,"options");i(this,"host");i(this,"port");i(this,"encoding");i(this,"connection");i(this,"keepAlive");i(this,"socket");i(this,"connectTimeout");i(this,"deferredConnectionPromise");this.options=e,this.host=this.options.host,this.port=this.options.port,this.encoding=this.options.encoding,this.keepAlive=this.options.keepAlive??!1,this.connectTimeout=this.options.connectTimeout??3e4}connect(){if(this.deferredConnectionPromise)return this.deferredConnectionPromise.promise;let e=this.deferredConnectionPromise=this.createDeferredConnectionPromise();return this.socket=(0,O.connect)({host:this.host,port:this.port,keepAlive:this.keepAlive}),this.connectTimeout>0&&(this.socket.setTimeout(this.connectTimeout),this.registerSocketTimeoutListener(e)),this.registerSocketConnectListener(e),this.registerSocketErrorListener(e),this.registerSocketCloseListener(e),e.promise}registerSocketTimeoutListener(e){(0,k.default)(this.socket);let t=this.socket,n=()=>{this.cleanupSocket(t);let o=new Error(`Connection timeout after ${this.connectTimeout}ms`);this.rejectDeferredPromise(e,o)};t.on("timeout",n)}registerSocketConnectListener(e){(0,k.default)(this.socket);let t=this.socket,n=()=>{if(t!==this.socket){this.cleanupSocket(t);return}let o;this.connection=o=new f(t,this.encoding),t.setTimeout(0),this.registerHl7ConnectionListeners(o),e.resolve(o)};t.on("connect",n)}registerSocketErrorListener(e){(0,k.default)(this.socket);let t=this.socket,n=o=>{this.cleanupSocket(t),o.constructor.name==="AggregateError"?this.rejectDeferredPromise(e,o.errors[0]):this.rejectDeferredPromise(e,o)};t.on("error",n)}registerSocketCloseListener(e){(0,k.default)(this.socket);let t=this.socket,n=()=>{this.cleanupSocket(t),this.rejectDeferredPromise(e,new Error("Socket closed before connection finished"))};t.on("close",n)}registerHl7ConnectionListeners(e){e.addEventListener("close",()=>{this.socket=void 0,this.connection=void 0,this.deferredConnectionPromise=void 0,this.dispatchEvent(new m)}),e.addEventListener("error",t=>{this.dispatchEvent(new h(t.error))})}createDeferredConnectionPromise(){let e,t;return{promise:new Promise((o,d)=>{e=o,t=d}),resolve:e,reject:t}}rejectDeferredPromise(e,t){e.reject(t),this.deferredConnectionPromise===e&&(this.deferredConnectionPromise=void 0)}cleanupSocket(e){e.destroyed||e.destroy(),e===this.socket&&(this.socket=void 0)}async send(e){return(await this.connect()).send(e)}async sendAndWait(e,t){return(await this.connect()).sendAndWait(e,t)}async close(){if(this.deferredConnectionPromise&&this.rejectDeferredPromise(this.deferredConnectionPromise,new Error("Client closed while connecting")),this.connection){let e=this.connection;delete this.connection,await e.close()}else this.dispatchEvent(new m);this.socket&&(this.socket.removeAllListeners(),this.socket.destroy(),this.socket=void 0)}};var T=require("@medplum/core"),D=C(require("node:net"),1);var I=1e4,y=class{constructor(s){i(this,"handler");i(this,"server");i(this,"encoding");i(this,"enhancedMode");i(this,"messagesPerMin");i(this,"connections",new Set);this.handler=s}async start(s,e,t,n){e&&this.setEncoding(e),t!==void 0&&this.setEnhancedMode(t),n?.messagesPerMin!==void 0&&this.setMessagesPerMin(n.messagesPerMin);let o=D.default.createServer(d=>{let c=new f(d,this.encoding,this.enhancedMode,{messagesPerMin:this.messagesPerMin});this.handler(c),this.connections.add(c),c.addEventListener("close",()=>{this.connections.delete(c)})});await new Promise(d=>{let c=p=>{o.listen(p,d)},l=async p=>{p?.code==="EADDRINUSE"&&(await(0,T.sleep)(50),o.close(),c(s))};o.on("error",l),o.once("listening",()=>{o.off("error",l)}),c(s),this.server=o})}async stop(s){return new Promise((e,t)=>{if(!this.server){t(new Error("Stop was called but there is no server running"));return}let n;s?.forceDrainTimeoutMs!==-1&&(n=setTimeout(()=>{for(let o of this.connections)o.close().catch(console.error)},s?.forceDrainTimeoutMs??I)),this.server.close(o=>{if(o){t(o);return}n&&clearTimeout(n),this.connections.clear(),this.server=void 0,e()})})}setEnhancedMode(s){this.enhancedMode=s}getEnhancedMode(){return this.enhancedMode}setEncoding(s){this.encoding=s}getEncoding(){return this.encoding}setMessagesPerMin(s){this.messagesPerMin=s}getMessagesPerMin(){return this.messagesPerMin}};0&&(module.exports={CR,DEFAULT_ENCODING,DEFAULT_FORCE_DRAIN_TIMEOUT_MS,FS,Hl7Base,Hl7Client,Hl7CloseEvent,Hl7Connection,Hl7EnhancedAckSentEvent,Hl7ErrorEvent,Hl7MessageEvent,Hl7Server,ReturnAckCategory,VT});
1
+ "use strict";var Q=Object.create;var M=Object.defineProperty;var j=Object.getOwnPropertyDescriptor;var N=Object.getOwnPropertyNames;var W=Object.getPrototypeOf,F=Object.prototype.hasOwnProperty;var K=(i,t,e)=>t in i?M(i,t,{enumerable:!0,configurable:!0,writable:!0,value:e}):i[t]=e;var _=(i,t)=>{for(var e in t)M(i,e,{get:t[e],enumerable:!0})},A=(i,t,e,n)=>{if(t&&typeof t=="object"||typeof t=="function")for(let s of N(t))!F.call(i,s)&&s!==e&&M(i,s,{get:()=>t[s],enumerable:!(n=j(t,s))||n.enumerable});return i};var H=(i,t,e)=>(e=i!=null?Q(W(i)):{},A(t||!i||!i.__esModule?M(e,"default",{value:i,enumerable:!0}):e,i)),U=i=>A(M({},"__esModule",{value:!0}),i);var r=(i,t,e)=>K(i,typeof t!="symbol"?t+"":t,e);var $={};_($,{CR:()=>b,DEFAULT_ENCODING:()=>w,DEFAULT_FORCE_DRAIN_TIMEOUT_MS:()=>B,FS:()=>L,Hl7Base:()=>m,Hl7Client:()=>y,Hl7CloseEvent:()=>g,Hl7Connection:()=>v,Hl7EnhancedAckSentEvent:()=>k,Hl7ErrorEvent:()=>p,Hl7MessageEvent:()=>E,Hl7Server:()=>S,Hl7WarningEvent:()=>f,ReturnAckCategory:()=>O.ReturnAckCategory,VT:()=>x});module.exports=U($);var m=class extends EventTarget{addEventListener(t,e,n){super.addEventListener(t,e,n)}removeEventListener(t,e,n){super.removeEventListener(t,e,n)}};var C=H(require("node:assert"),1),T=require("node:net");var a=require("@medplum/core"),P=H(require("iconv-lite"),1);var x=11,b=13,L=28;var E=class extends Event{constructor(e,n){super("message");r(this,"connection");r(this,"message");this.connection=e,this.message=n}},k=class extends Event{constructor(e,n){super("enhancedAckSent");r(this,"connection");r(this,"message");this.connection=e,this.message=n}},p=class extends Event{constructor(e){super("error");r(this,"error");this.error=e}},f=class extends Event{constructor(e){super("warning");r(this,"error");this.error=e}},g=class extends Event{constructor(){super("close")}};var O=require("@medplum/core");var w="utf-8",q=60*1e3,v=class extends m{constructor(e,n=w,s,o={}){super();r(this,"socket");r(this,"encoding");r(this,"enhancedMode");r(this,"messagesPerMin");r(this,"chunks",[]);r(this,"pendingMessages",new Map);r(this,"responseQueue",[]);r(this,"lastMessageDispatchedTime",0);r(this,"responseQueueProcessing",!1);this.socket=e,this.encoding=n,this.enhancedMode=s,this.messagesPerMin=o.messagesPerMin,e.on("data",d=>{try{this.appendData(d);let c=this.parseMessages();for(let l of c)this.responseQueue.push(new E(this,l));this.processResponseQueue().catch(l=>{this.dispatchEvent(new p(l))})}catch(c){this.dispatchEvent(new p(c))}}),e.on("error",d=>{this.resetBuffer(),this.dispatchEvent(new p(d))}),e.on("close",()=>{this.drainPendingMessages(),this.dispatchEvent(new g)}),this.addEventListener("message",d=>{let c;this.enhancedMode==="standard"?c=d.message.buildAck({ackCode:"CA"}):this.enhancedMode==="aaMode"&&(c=d.message.buildAck({ackCode:"AA"})),c&&(this.send(c),this.dispatchEvent(new k(this,c)));let l=d.message.getSegment("MSA")?.getField(2)?.toString();if(!l)return;let u=this.pendingMessages.get(l);if(!u){this.dispatchEvent(new f(new a.OperationOutcomeError({resourceType:"OperationOutcome",issue:[{severity:"warning",code:"not-found",details:{text:"Response received for unknown message control ID"},diagnostics:`Received ACK for message control ID '${l}' but there was no pending message with this control ID`}]})));return}let h=d.message.getSegment("MSA")?.getField(1)?.toString()?.toUpperCase();h&&(u.returnAck===a.ReturnAckCategory.APPLICATION&&h==="CA"||(u.resolve(d.message),this.pendingMessages.delete(l)))})}isClosed(){return this.socket.closed}sendImpl(e){let n=e.toString(),s=P.default.encode(n,this.encoding),o=Buffer.alloc(s.length+3);o.writeInt8(11,0),s.copy(o,1),o.writeInt8(28,s.length+1),o.writeInt8(13,s.length+2),this.socket.write(o)}async processResponseQueue(){if(!this.responseQueueProcessing){for(this.responseQueueProcessing=!0;this.responseQueue.length;){if(this.messagesPerMin){let n=q/this.messagesPerMin,s=Date.now()-this.lastMessageDispatchedTime;n>s&&await(0,a.sleep)(n-s)}let e=this.responseQueue.shift();e&&this.dispatchEvent(e),this.lastMessageDispatchedTime=Date.now()}this.responseQueueProcessing=!1}}parseMessages(){let e=[],n=Buffer.concat(this.chunks);if(this.resetBuffer(),n.length===0)return e;let s=0;for(;s<n.length;){for(;n[s]!==11&&s<n.length;)s++;let o=-1;for(let h=s+1;h<n.length-1;h++)if(n[h]===28&&n[h+1]===13){o=h+1;break}if(o===-1)break;let c=n.subarray(s,o+1).subarray(1,-2),l=P.default.decode(c,this.encoding),u=a.Hl7Message.parse(l);e.push(u),s=o+1}return this.chunks=s<n.length?[n.subarray(s)]:[],e}send(e){this.sendImpl(e)}async sendAndWait(e,n){return new Promise((s,o)=>{let d=e.getSegment("MSH")?.getField(10)?.toString();if(!d){o(new a.OperationOutcomeError((0,a.validationError)("Required field missing: MSH.10")));return}let c;n?.timeoutMs&&(c=setTimeout(()=>{this.pendingMessages.delete(d),o(new a.OperationOutcomeError({resourceType:"OperationOutcome",issue:[{severity:"error",code:"timeout",details:{text:"Client timeout"},diagnostics:`Request timed out after waiting ${n.timeoutMs} milliseconds for response`}]}))},n.timeoutMs)),this.pendingMessages.set(d,{message:e,resolve:s,reject:o,returnAck:n?.returnAck??a.ReturnAckCategory.APPLICATION,timer:c}),this.sendImpl(e)})}async close(){this.isClosed()||(this.socket.end(),this.socket.destroy(),this.drainPendingMessages(),await new Promise(e=>{this.socket.once("close",e)}))}drainPendingMessages(){if(!this.pendingMessages.size)return;let e=this.pendingMessages.size;for(let n of this.pendingMessages.values())n.timer&&clearTimeout(n.timer),n.reject(new a.OperationOutcomeError({resourceType:"OperationOutcome",issue:[{severity:"warning",code:"incomplete",details:{text:"Message was still pending when connection closed"}}]}));this.dispatchEvent(new p(new a.OperationOutcomeError({resourceType:"OperationOutcome",issue:[{severity:"warning",code:"incomplete",details:{text:"Messages were still pending when connection closed"},diagnostics:`Hl7Connection closed while ${e} messages were pending`}]}))),this.pendingMessages.clear()}appendData(e){this.chunks.push(e)}resetBuffer(){this.chunks=[]}setEncoding(e){this.encoding=e??w}getEncoding(){return this.encoding}setEnhancedMode(e){this.enhancedMode=e}getEnhancedMode(){return this.enhancedMode}setMessagesPerMin(e){this.messagesPerMin=e}getMessagesPerMin(){return this.messagesPerMin}getPendingMessageCount(){return this.pendingMessages.size}};var y=class extends m{constructor(e){super();r(this,"options");r(this,"host");r(this,"port");r(this,"encoding");r(this,"connection");r(this,"keepAlive");r(this,"socket");r(this,"connectTimeout");r(this,"deferredConnectionPromise");this.options=e,this.host=this.options.host,this.port=this.options.port,this.encoding=this.options.encoding,this.keepAlive=this.options.keepAlive??!1,this.connectTimeout=this.options.connectTimeout??3e4}connect(){if(this.deferredConnectionPromise)return this.deferredConnectionPromise.promise;let e=this.deferredConnectionPromise=this.createDeferredConnectionPromise();return this.socket=(0,T.connect)({host:this.host,port:this.port,keepAlive:this.keepAlive}),this.connectTimeout>0&&(this.socket.setTimeout(this.connectTimeout),this.registerSocketTimeoutListener(e)),this.registerSocketConnectListener(e),this.registerSocketErrorListener(e),this.registerSocketCloseListener(e),e.promise}registerSocketTimeoutListener(e){(0,C.default)(this.socket);let n=this.socket,s=()=>{this.cleanupSocket(n);let o=new Error(`Connection timeout after ${this.connectTimeout}ms`);this.rejectDeferredPromise(e,o)};n.on("timeout",s)}registerSocketConnectListener(e){(0,C.default)(this.socket);let n=this.socket,s=()=>{if(n!==this.socket){this.cleanupSocket(n);return}let o;this.connection=o=new v(n,this.encoding),n.setTimeout(0),this.registerHl7ConnectionListeners(o),e.resolve(o)};n.on("connect",s)}registerSocketErrorListener(e){(0,C.default)(this.socket);let n=this.socket,s=o=>{this.cleanupSocket(n),o.constructor.name==="AggregateError"?this.rejectDeferredPromise(e,o.errors[0]):this.rejectDeferredPromise(e,o)};n.on("error",s)}registerSocketCloseListener(e){(0,C.default)(this.socket);let n=this.socket,s=()=>{this.cleanupSocket(n),this.rejectDeferredPromise(e,new Error("Socket closed before connection finished"))};n.on("close",s)}registerHl7ConnectionListeners(e){e.addEventListener("close",()=>{this.socket=void 0,this.connection=void 0,this.deferredConnectionPromise=void 0,this.dispatchEvent(new g)}),e.addEventListener("error",n=>{this.dispatchEvent(new p(n.error))}),e.addEventListener("warning",n=>{this.dispatchEvent(new f(n.error))})}createDeferredConnectionPromise(){let e,n;return{promise:new Promise((o,d)=>{e=o,n=d}),resolve:e,reject:n}}rejectDeferredPromise(e,n){e.reject(n),this.deferredConnectionPromise===e&&(this.deferredConnectionPromise=void 0)}cleanupSocket(e){e.destroyed||e.destroy(),e===this.socket&&(this.socket=void 0)}async send(e){return(await this.connect()).send(e)}async sendAndWait(e,n){return(await this.connect()).sendAndWait(e,n)}async close(){if(this.deferredConnectionPromise&&this.rejectDeferredPromise(this.deferredConnectionPromise,new Error("Client closed while connecting")),this.connection){let e=this.connection;delete this.connection,await e.close()}else this.dispatchEvent(new g);this.socket&&(this.socket.removeAllListeners(),this.socket.destroy(),this.socket=void 0)}};var D=require("@medplum/core"),I=H(require("node:net"),1);var B=1e4,S=class{constructor(t){r(this,"handler");r(this,"server");r(this,"encoding");r(this,"enhancedMode");r(this,"messagesPerMin");r(this,"connections",new Set);this.handler=t}async start(t,e,n,s){e&&this.setEncoding(e),n!==void 0&&this.setEnhancedMode(n),s?.messagesPerMin!==void 0&&this.setMessagesPerMin(s.messagesPerMin);let o=I.default.createServer(d=>{let c=new v(d,this.encoding,this.enhancedMode,{messagesPerMin:this.messagesPerMin});this.handler(c),this.connections.add(c),c.addEventListener("close",()=>{this.connections.delete(c)})});return new Promise((d,c)=>{let l=h=>{o.listen(h,()=>{let R=o.address().port;d(R)})},u=h=>{h?.code==="EADDRINUSE"?o.close(()=>(0,D.sleep)(50).then(()=>l(t))):c(h)};o.on("error",u),o.once("listening",()=>{o.off("error",u)}),l(t),this.server=o})}async stop(t){return new Promise((e,n)=>{if(!this.server){n(new Error("Stop was called but there is no server running"));return}let s;t?.forceDrainTimeoutMs!==-1&&(s=setTimeout(()=>{for(let o of this.connections)o.close().catch(console.error)},t?.forceDrainTimeoutMs??B)),this.server.close(o=>{if(o){n(o);return}s&&clearTimeout(s),this.connections.clear(),this.server=void 0,e()})})}setEnhancedMode(t){this.enhancedMode=t}getEnhancedMode(){return this.enhancedMode}setEncoding(t){this.encoding=t}getEncoding(){return this.encoding}setMessagesPerMin(t){this.messagesPerMin=t}getMessagesPerMin(){return this.messagesPerMin}};0&&(module.exports={CR,DEFAULT_ENCODING,DEFAULT_FORCE_DRAIN_TIMEOUT_MS,FS,Hl7Base,Hl7Client,Hl7CloseEvent,Hl7Connection,Hl7EnhancedAckSentEvent,Hl7ErrorEvent,Hl7MessageEvent,Hl7Server,Hl7WarningEvent,ReturnAckCategory,VT});
2
2
  //# sourceMappingURL=index.cjs.map
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/index.ts", "../../src/base.ts", "../../src/client.ts", "../../src/connection.ts", "../../src/constants.ts", "../../src/events.ts", "../../src/server.ts"],
4
- "sourcesContent": ["// SPDX-FileCopyrightText: Copyright Orangebot, Inc. and Medplum contributors\n// SPDX-License-Identifier: Apache-2.0\nexport * from './base';\nexport * from './client';\nexport * from './connection';\nexport * from './constants';\nexport * from './events';\nexport * from './server';\n", "// SPDX-FileCopyrightText: Copyright Orangebot, Inc. and Medplum contributors\n// SPDX-License-Identifier: Apache-2.0\nimport type { Hl7CloseEvent, Hl7EnhancedAckSentEvent, Hl7ErrorEvent, Hl7MessageEvent } from './events';\n\nexport interface Hl7EventMap {\n message: Hl7MessageEvent;\n error: Hl7ErrorEvent;\n close: Hl7CloseEvent;\n enhancedAckSent: Hl7EnhancedAckSentEvent;\n}\n\nexport abstract class Hl7Base extends EventTarget {\n addEventListener<K extends keyof Hl7EventMap>(\n type: K,\n listener: ((event: Hl7EventMap[K]) => void) | EventListenerObject | null,\n options?: boolean | AddEventListenerOptions\n ): void;\n\n addEventListener(\n type: string,\n listener: EventListenerOrEventListenerObject | null,\n options?: boolean | AddEventListenerOptions\n ): void {\n super.addEventListener(type, listener, options);\n }\n removeEventListener<K extends keyof Hl7EventMap>(\n type: K,\n listener: ((event: Hl7EventMap[K]) => void) | EventListenerObject | null,\n options?: boolean | AddEventListenerOptions\n ): void;\n\n removeEventListener(\n type: string,\n listener: EventListenerOrEventListenerObject | null,\n options?: boolean | AddEventListenerOptions\n ): void {\n super.removeEventListener(type, listener, options);\n }\n}\n", "// SPDX-FileCopyrightText: Copyright Orangebot, Inc. and Medplum contributors\n// SPDX-License-Identifier: Apache-2.0\nimport type { Hl7Message } from '@medplum/core';\nimport assert from 'node:assert';\nimport type { Socket } from 'node:net';\nimport { connect } from 'node:net';\nimport { Hl7Base } from './base';\nimport type { SendAndWaitOptions } from './connection';\nimport { Hl7Connection } from './connection';\nimport { Hl7CloseEvent, Hl7ErrorEvent } from './events';\n\nexport interface Hl7ClientOptions {\n host: string;\n port: number;\n encoding?: string;\n keepAlive?: boolean;\n connectTimeout?: number; // Add timeout option\n}\n\nexport interface DeferredConnectionPromise {\n promise: Promise<Hl7Connection>;\n resolve: (connection: Hl7Connection) => void;\n reject: (err: Error) => void;\n}\n\nexport class Hl7Client extends Hl7Base {\n options: Hl7ClientOptions;\n host: string;\n port: number;\n encoding?: string;\n connection?: Hl7Connection;\n keepAlive: boolean;\n private socket?: Socket;\n private connectTimeout: number;\n private deferredConnectionPromise?: DeferredConnectionPromise;\n\n constructor(options: Hl7ClientOptions) {\n super();\n this.options = options;\n this.host = this.options.host;\n this.port = this.options.port;\n this.encoding = this.options.encoding;\n this.keepAlive = this.options.keepAlive ?? false;\n this.connectTimeout = this.options.connectTimeout ?? 30000; // Default 30 seconds\n }\n\n connect(): Promise<Hl7Connection> {\n // If we are already waiting for a pending connection attempt, just return the deferred promise to that\n // In the case that the promise is already resolve, we will also return a resolved connection\n if (this.deferredConnectionPromise) {\n return this.deferredConnectionPromise.promise;\n }\n\n const deferredPromise = (this.deferredConnectionPromise = this.createDeferredConnectionPromise());\n\n // Create the socket\n this.socket = connect({\n host: this.host,\n port: this.port,\n keepAlive: this.keepAlive,\n });\n\n if (this.connectTimeout > 0) {\n this.socket.setTimeout(this.connectTimeout);\n this.registerSocketTimeoutListener(deferredPromise);\n }\n\n this.registerSocketConnectListener(deferredPromise);\n this.registerSocketErrorListener(deferredPromise);\n this.registerSocketCloseListener(deferredPromise);\n\n return deferredPromise.promise;\n }\n\n private registerSocketTimeoutListener(deferredPromise: DeferredConnectionPromise): void {\n assert(this.socket);\n const socket = this.socket;\n\n // Handle timeout event\n const timeoutListener = (): void => {\n this.cleanupSocket(socket);\n const error = new Error(`Connection timeout after ${this.connectTimeout}ms`);\n this.rejectDeferredPromise(deferredPromise, error);\n };\n\n socket.on('timeout', timeoutListener);\n }\n\n private registerSocketConnectListener(deferredPromise: DeferredConnectionPromise): void {\n assert(this.socket);\n const socket = this.socket;\n\n // Handle successful connection\n const connectListener = (): void => {\n if (socket !== this.socket) {\n this.cleanupSocket(socket);\n return;\n }\n\n // Create the HL7 connection\n let connection: Hl7Connection;\n this.connection = connection = new Hl7Connection(socket, this.encoding);\n\n // Remove the timeout listener as we're now connected\n socket.setTimeout(0);\n\n this.registerHl7ConnectionListeners(connection);\n\n deferredPromise.resolve(connection);\n };\n\n socket.on('connect', connectListener);\n }\n\n private registerSocketErrorListener(deferredPromise: DeferredConnectionPromise): void {\n assert(this.socket);\n const socket = this.socket;\n\n // Handle connection errors\n const errorListener = (err: Error | AggregateError): void => {\n this.cleanupSocket(socket);\n\n if (err.constructor.name === 'AggregateError') {\n this.rejectDeferredPromise(deferredPromise, (err as AggregateError).errors[0]);\n } else {\n this.rejectDeferredPromise(deferredPromise, err);\n }\n };\n\n socket.on('error', errorListener);\n }\n\n private registerSocketCloseListener(deferredPromise: DeferredConnectionPromise): void {\n assert(this.socket);\n const socket = this.socket;\n\n // Handle connection errors\n const closeListener = (): void => {\n this.cleanupSocket(socket);\n this.rejectDeferredPromise(deferredPromise, new Error('Socket closed before connection finished'));\n };\n socket.on('close', closeListener);\n }\n\n private registerHl7ConnectionListeners(connection: Hl7Connection): void {\n // Set up event handlers\n connection.addEventListener('close', () => {\n this.socket = undefined;\n this.connection = undefined;\n this.deferredConnectionPromise = undefined;\n this.dispatchEvent(new Hl7CloseEvent());\n });\n\n connection.addEventListener('error', (event) => {\n this.dispatchEvent(new Hl7ErrorEvent(event.error));\n });\n }\n\n private createDeferredConnectionPromise(): DeferredConnectionPromise {\n // Setup our deferred connection promise\n let resolve!: (connection: Hl7Connection) => void;\n let reject!: (err: Error) => void;\n\n const promise = new Promise<Hl7Connection>((_resolve, _reject) => {\n resolve = _resolve;\n reject = _reject;\n });\n\n return {\n promise,\n resolve,\n reject,\n };\n }\n\n private rejectDeferredPromise(deferredPromise: DeferredConnectionPromise, err: Error): void {\n // Reject this deferred promise with the given error\n deferredPromise.reject(err);\n\n // If the currently tracked deferred promise is this deferred promise, remove it from the client\n if (this.deferredConnectionPromise === deferredPromise) {\n this.deferredConnectionPromise = undefined;\n }\n }\n\n private cleanupSocket(socket: Socket): void {\n if (!socket.destroyed) {\n socket.destroy();\n }\n if (socket === this.socket) {\n this.socket = undefined;\n }\n }\n\n async send(msg: Hl7Message): Promise<void> {\n return (await this.connect()).send(msg);\n }\n\n async sendAndWait(msg: Hl7Message, options?: SendAndWaitOptions): Promise<Hl7Message> {\n return (await this.connect()).sendAndWait(msg, options);\n }\n\n async close(): Promise<void> {\n if (this.deferredConnectionPromise) {\n this.rejectDeferredPromise(this.deferredConnectionPromise, new Error('Client closed while connecting'));\n }\n\n // Close established connection if it exists\n if (this.connection) {\n const connection = this.connection;\n delete this.connection;\n await connection.close();\n } else {\n // Emit close event because the connection will not be able to emit it for us\n // Since it has not connected at this point\n this.dispatchEvent(new Hl7CloseEvent());\n }\n // Close the socket if it exists\n if (this.socket) {\n this.socket.removeAllListeners();\n this.socket.destroy();\n this.socket = undefined;\n }\n }\n}\n", "// SPDX-FileCopyrightText: Copyright Orangebot, Inc. and Medplum contributors\n// SPDX-License-Identifier: Apache-2.0\nimport { Hl7Message, OperationOutcomeError, ReturnAckCategory, sleep, validationError } from '@medplum/core';\nimport iconv from 'iconv-lite';\nimport type net from 'node:net';\nimport { Hl7Base } from './base';\nimport { CR, FS, VT } from './constants';\nimport { Hl7CloseEvent, Hl7EnhancedAckSentEvent, Hl7ErrorEvent, Hl7MessageEvent } from './events';\n\n// Export `ReturnAckCategory` for backwards-compat\nexport { ReturnAckCategory } from '@medplum/core';\n\n// iconv-lite docs have great examples and explanations for how to use Buffers with iconv-lite:\n// See: https://github.com/ashtuchkin/iconv-lite/wiki/Use-Buffers-when-decoding\n\nexport type Hl7MessageQueueItem = {\n message: Hl7Message;\n resolve: (reply: Hl7Message) => void;\n reject: (err: Error) => void;\n returnAck: ReturnAckCategory;\n timer?: NodeJS.Timeout;\n};\n\nexport interface SendAndWaitOptions {\n /** The ACK-level that the Promise should resolve on. The default is `ReturnAckCategory.APPLICATION` (returns on the first application-level ACK). */\n returnAck?: ReturnAckCategory;\n /** The amount of milliseconds to wait before timing out when waiting for the response to a message. */\n timeoutMs?: number;\n}\n\nexport interface Hl7ConnectionOptions {\n messagesPerMin?: number;\n}\n\n/**\n * Enhanced mode for HL7 connections.\n * - `'standard'`: Standard enhanced mode behavior\n * - `'aaMode'`: AA mode - special enhanced mode that only accepts AA acknowledgements\n * - `undefined`: Enhanced mode is not enabled (standard behavior)\n */\nexport type EnhancedMode = 'standard' | 'aaMode' | undefined;\n\nexport const DEFAULT_ENCODING = 'utf-8';\nconst ONE_MINUTE = 60 * 1000;\n\nexport class Hl7Connection extends Hl7Base {\n readonly socket: net.Socket;\n encoding: string;\n enhancedMode: EnhancedMode = undefined;\n private messagesPerMin: number | undefined = undefined;\n private chunks: Buffer[] = [];\n private readonly pendingMessages: Map<string, Hl7MessageQueueItem> = new Map<string, Hl7MessageQueueItem>();\n private readonly responseQueue: Hl7MessageEvent[] = [];\n private lastMessageDispatchedTime = 0;\n private responseQueueProcessing = false;\n\n constructor(\n socket: net.Socket,\n encoding: string = DEFAULT_ENCODING,\n enhancedMode?: EnhancedMode,\n options: Hl7ConnectionOptions = {}\n ) {\n super();\n\n this.socket = socket;\n this.encoding = encoding;\n this.enhancedMode = enhancedMode;\n this.messagesPerMin = options.messagesPerMin;\n\n socket.on('data', (data: Buffer) => {\n try {\n this.appendData(data);\n const messages = this.parseMessages();\n for (const message of messages) {\n this.responseQueue.push(new Hl7MessageEvent(this, message));\n }\n this.processResponseQueue().catch((err) => {\n this.dispatchEvent(new Hl7ErrorEvent(err));\n });\n } catch (err) {\n this.dispatchEvent(new Hl7ErrorEvent(err as Error));\n }\n });\n\n socket.on('error', (err) => {\n this.resetBuffer();\n this.dispatchEvent(new Hl7ErrorEvent(err));\n });\n\n // The difference between \"end\" and \"close\", is that \"end\" is only emitted on half-close from the other side\n // If the connection from the other side does not close gracefully, but instead we destroy the socket, then the Hl7Connection will not emit close\n // if we listen only for \"end\"; \"close\" is always emitted, whether the close is graceful or forceful\n socket.on('close', () => {\n this.dispatchEvent(new Hl7CloseEvent());\n });\n\n this.addEventListener('message', (event) => {\n // In standard enhanced mode, send commit ACK (CA) immediately, then later forward app-level ACKs\n // In aaMode, send application ACK (AA) immediately, then ignore any later app-level ACKs\n let response: Hl7Message | undefined;\n if (this.enhancedMode === 'standard') {\n response = event.message.buildAck({ ackCode: 'CA' });\n } else if (this.enhancedMode === 'aaMode') {\n response = event.message.buildAck({ ackCode: 'AA' });\n }\n if (response) {\n this.send(response);\n this.dispatchEvent(new Hl7EnhancedAckSentEvent(this, response));\n }\n const origMsgCtrlId = event.message.getSegment('MSA')?.getField(2)?.toString();\n // If there is no message control ID, just return\n if (!origMsgCtrlId) {\n return;\n }\n const queueItem = this.pendingMessages.get(origMsgCtrlId);\n if (!queueItem) {\n this.dispatchEvent(\n new Hl7ErrorEvent(\n new OperationOutcomeError({\n resourceType: 'OperationOutcome',\n issue: [\n {\n severity: 'warning',\n code: 'not-found',\n details: {\n text: 'Response received for unknown message control ID',\n },\n diagnostics: `Received ACK for message control ID '${origMsgCtrlId}' but there was no pending message with this control ID`,\n },\n ],\n })\n )\n );\n return;\n }\n // Check the ACK type we should return on\n const ackCode = event.message.getSegment('MSA')?.getField(1)?.toString()?.toUpperCase();\n if (!ackCode) {\n return;\n }\n\n // Two modes:\n // Application-level or first ACK\n\n // First should always return on any ACK message, this is the default\n // The exception is APPLICATION, which should not resolve when the ACK is a CA, but should resolve on all other ACK types\n // On CA, we return early\n if (queueItem.returnAck === ReturnAckCategory.APPLICATION && ackCode === 'CA') {\n return;\n }\n\n // Resolve the promise if there is one pending for this message and we didn't exit already because the ACK type matches\n queueItem.resolve(event.message);\n this.pendingMessages.delete(origMsgCtrlId);\n });\n }\n\n /** @returns A boolean representing whether the socket attached to this Hl7Connection has emitted the close event already or not. */\n isClosed(): boolean {\n return this.socket.closed;\n }\n\n private sendImpl(reply: Hl7Message): void {\n const replyString = reply.toString();\n const replyBuffer = iconv.encode(replyString, this.encoding);\n const outputBuffer = Buffer.alloc(replyBuffer.length + 3);\n outputBuffer.writeInt8(VT, 0);\n replyBuffer.copy(outputBuffer, 1);\n outputBuffer.writeInt8(FS, replyBuffer.length + 1);\n outputBuffer.writeInt8(CR, replyBuffer.length + 2);\n this.socket.write(outputBuffer);\n }\n\n private async processResponseQueue(): Promise<void> {\n if (this.responseQueueProcessing) {\n return;\n }\n\n this.responseQueueProcessing = true;\n while (this.responseQueue.length) {\n if (this.messagesPerMin) {\n const millisBetweenMsgs = ONE_MINUTE / this.messagesPerMin;\n const elapsedMillis = Date.now() - this.lastMessageDispatchedTime;\n if (millisBetweenMsgs > elapsedMillis) {\n await sleep(millisBetweenMsgs - elapsedMillis);\n }\n }\n const messageEvent = this.responseQueue.shift() as Hl7MessageEvent;\n if (messageEvent) {\n this.dispatchEvent(messageEvent);\n }\n this.lastMessageDispatchedTime = Date.now();\n }\n this.responseQueueProcessing = false;\n }\n\n /**\n * Parses complete HL7 messages from the accumulated buffer.\n * Continues parsing while the buffer starts with VT and contains FS+CR.\n * Keeps any incomplete message data in the buffer for the next chunk.\n * @returns An array of parsed HL7 messages.\n */\n private parseMessages(): Hl7Message[] {\n const messages: Hl7Message[] = [];\n const buffer = Buffer.concat(this.chunks);\n this.resetBuffer();\n\n // Check if buffer starts with VT (Vertical Tab)\n if (buffer.length === 0) {\n return messages;\n }\n\n let bufferIdx = 0;\n\n // Keep parsing while we have complete messages\n while (bufferIdx < buffer.length) {\n // Ignore bytes between message frames\n while (buffer[bufferIdx] !== VT && bufferIdx < buffer.length) {\n bufferIdx++;\n }\n\n // Look for FS+CR sequence to mark end of message\n let messageEndIndex = -1;\n\n for (let i = bufferIdx + 1; i < buffer.length - 1; i++) {\n if (buffer[i] === FS && buffer[i + 1] === CR) {\n messageEndIndex = i + 1; // Index of CR (end of message)\n break;\n }\n }\n\n // If we don't have a complete message yet, wait for more data\n if (messageEndIndex === -1) {\n break;\n }\n\n // Extract the complete message (including VT, FS, and CR)\n const messageBuffer = buffer.subarray(bufferIdx, messageEndIndex + 1);\n // Extract the content (without VT at start and FS+CR at end)\n const contentBuffer = messageBuffer.subarray(1, -2);\n const contentString = iconv.decode(contentBuffer, this.encoding);\n const message = Hl7Message.parse(contentString);\n\n messages.push(message);\n\n // Move past this message\n bufferIdx = messageEndIndex + 1;\n }\n\n // Keep any remaining unfinished chunk in this.chunks\n this.chunks = bufferIdx < buffer.length ? [buffer.subarray(bufferIdx)] : [];\n\n return messages;\n }\n\n send(reply: Hl7Message): void {\n this.sendImpl(reply);\n }\n\n async sendAndWait(msg: Hl7Message, options?: SendAndWaitOptions): Promise<Hl7Message> {\n return new Promise<Hl7Message>((resolve, reject) => {\n const msgCtrlId = msg.getSegment('MSH')?.getField(10)?.toString();\n if (!msgCtrlId) {\n reject(new OperationOutcomeError(validationError('Required field missing: MSH.10')));\n return;\n }\n\n let timer: NodeJS.Timeout | undefined;\n\n if (options?.timeoutMs) {\n timer = setTimeout(() => {\n this.pendingMessages.delete(msgCtrlId);\n reject(\n new OperationOutcomeError({\n resourceType: 'OperationOutcome',\n issue: [\n {\n severity: 'error',\n code: 'timeout',\n details: {\n text: 'Client timeout',\n },\n diagnostics: `Request timed out after waiting ${options.timeoutMs} milliseconds for response`,\n },\n ],\n })\n );\n }, options.timeoutMs);\n }\n\n this.pendingMessages.set(msgCtrlId, {\n message: msg,\n resolve,\n reject,\n returnAck: options?.returnAck ?? ReturnAckCategory.APPLICATION,\n timer,\n });\n this.sendImpl(msg);\n });\n }\n\n async close(): Promise<void> {\n // If we have already received the close event, then we can just return immediately\n if (this.isClosed()) {\n return;\n }\n this.socket.end();\n this.socket.destroy();\n // Before clearing out messages, we should propagate a message to the consumer that we are closing the connection while some messages were still pending a response\n if (this.pendingMessages.size) {\n for (const queueItem of this.pendingMessages.values()) {\n if (queueItem.timer) {\n clearTimeout(queueItem.timer);\n }\n queueItem.reject(\n new OperationOutcomeError({\n resourceType: 'OperationOutcome',\n issue: [\n {\n severity: 'warning',\n code: 'incomplete',\n details: {\n text: 'Message was still pending when connection closed',\n },\n },\n ],\n })\n );\n }\n this.dispatchEvent(\n new Hl7ErrorEvent(\n new OperationOutcomeError({\n resourceType: 'OperationOutcome',\n issue: [\n {\n severity: 'warning',\n code: 'incomplete',\n details: {\n text: 'Messages were still pending when connection closed',\n },\n diagnostics: `Hl7Connection closed while ${this.pendingMessages.size} messages were pending`,\n },\n ],\n })\n )\n );\n // Clear out any pending messages\n this.pendingMessages.clear();\n }\n await new Promise((resolve) => {\n // Register a temporary listener to help resolve the promise once close has been emitted\n this.socket.once('close', resolve);\n });\n }\n\n private appendData(data: Buffer): void {\n this.chunks.push(data);\n }\n\n private resetBuffer(): void {\n this.chunks = [];\n }\n\n setEncoding(encoding: string | undefined): void {\n this.encoding = encoding ?? DEFAULT_ENCODING;\n }\n\n getEncoding(): string {\n return this.encoding;\n }\n\n setEnhancedMode(enhancedMode: EnhancedMode): void {\n this.enhancedMode = enhancedMode;\n }\n\n getEnhancedMode(): EnhancedMode {\n return this.enhancedMode;\n }\n\n setMessagesPerMin(messagesPerMin: number | undefined): void {\n this.messagesPerMin = messagesPerMin;\n }\n\n getMessagesPerMin(): number | undefined {\n return this.messagesPerMin;\n }\n\n getPendingMessageCount(): number {\n return this.pendingMessages.size;\n }\n}\n", "// SPDX-FileCopyrightText: Copyright Orangebot, Inc. and Medplum contributors\n// SPDX-License-Identifier: Apache-2.0\n\n/**\n * VT (Vertical Tab) character.\n *\n * In HL7 messages, this character is used to indicate the start of a message.\n */\nexport const VT = 0x0b;\n\n/**\n * CR (Carriage Return) character.\n *\n * In HL7 messages, this character is used to indicate the end of a message.\n */\nexport const CR = 0x0d;\n\n/**\n * FS (File Separator) character.\n *\n * In HL7 messages, this character is used to separate fields.\n */\nexport const FS = 0x1c;\n", "// SPDX-FileCopyrightText: Copyright Orangebot, Inc. and Medplum contributors\n// SPDX-License-Identifier: Apache-2.0\nimport type { Hl7Message } from '@medplum/core';\nimport type { Hl7Connection } from './connection';\n\nexport class Hl7MessageEvent extends Event {\n readonly connection: Hl7Connection;\n readonly message: Hl7Message;\n\n constructor(connection: Hl7Connection, message: Hl7Message) {\n super('message');\n this.connection = connection;\n this.message = message;\n }\n}\n\nexport class Hl7EnhancedAckSentEvent extends Event {\n readonly connection: Hl7Connection;\n readonly message: Hl7Message;\n\n constructor(connection: Hl7Connection, message: Hl7Message) {\n super('enhancedAckSent');\n this.connection = connection;\n this.message = message;\n }\n}\n\nexport class Hl7ErrorEvent extends Event {\n readonly error: Error;\n\n constructor(error: Error) {\n super('error');\n this.error = error;\n }\n}\n\nexport class Hl7CloseEvent extends Event {\n constructor() {\n super('close');\n }\n}\n", "// SPDX-FileCopyrightText: Copyright Orangebot, Inc. and Medplum contributors\n// SPDX-License-Identifier: Apache-2.0\nimport { sleep } from '@medplum/core';\nimport net from 'node:net';\nimport type { EnhancedMode, Hl7ConnectionOptions } from './connection';\nimport { Hl7Connection } from './connection';\n\n/**\n * Options for configuring the `Hl7Server#stop` method.\n */\nexport interface Hl7ServerStopOptions {\n /**\n * Time in milliseconds to allow client connections to gracefully close after the stop method has been called, before forcefully closing them.\n *\n * Can be set to `-1` to disable forceful draining of connections during stop.\n *\n * Defaults to `10_000`.\n */\n forceDrainTimeoutMs?: number;\n}\n\nexport const DEFAULT_FORCE_DRAIN_TIMEOUT_MS = 10_000;\n\nexport class Hl7Server {\n readonly handler: (connection: Hl7Connection) => void;\n server?: net.Server;\n private encoding: string | undefined = undefined;\n private enhancedMode: EnhancedMode = undefined;\n private messagesPerMin: number | undefined = undefined;\n private readonly connections = new Set<Hl7Connection>();\n\n constructor(handler: (connection: Hl7Connection) => void) {\n this.handler = handler;\n }\n\n async start(\n port: number,\n encoding?: string,\n enhancedMode?: EnhancedMode,\n options?: Hl7ConnectionOptions\n ): Promise<void> {\n if (encoding) {\n this.setEncoding(encoding);\n }\n if (enhancedMode !== undefined) {\n this.setEnhancedMode(enhancedMode);\n }\n if (options?.messagesPerMin !== undefined) {\n this.setMessagesPerMin(options.messagesPerMin);\n }\n\n const server = net.createServer((socket) => {\n const connection = new Hl7Connection(socket, this.encoding, this.enhancedMode, {\n messagesPerMin: this.messagesPerMin,\n });\n this.handler(connection);\n this.connections.add(connection);\n connection.addEventListener('close', () => {\n this.connections.delete(connection);\n });\n });\n\n await new Promise<void>((resolve) => {\n const listenOnPort = (port: number): void => {\n server.listen(port, resolve);\n };\n\n // Node errors have a code\n const errorListener = async (e: Error & { code?: string }): Promise<void> => {\n if (e?.code === 'EADDRINUSE') {\n await sleep(50);\n server.close();\n listenOnPort(port);\n }\n };\n\n server.on('error', errorListener);\n\n server.once('listening', () => {\n server.off('error', errorListener);\n });\n\n listenOnPort(port);\n\n this.server = server;\n });\n }\n\n /**\n * Stops the HL7 server.\n *\n * By default, the server will stop accepting new connections after this method is called, and wait for current connections to close naturally.\n *\n * If all connections don't close within 10 seconds, the server will forcefully close them before shutting down.\n *\n * The default time to wait before forcefully closing connections can be changed by passing an integer value for the optional `options.forceDrainTimeoutMs`.\n *\n * Forced drain can also be disabled by passing `-1` for `options.forceDrainTimeoutMs`.\n * @param options - Optional options to configure the stopping of the server.\n * @returns Promise that resolves when the server has stopped, or rejects if an error prevents server from stopping.\n */\n async stop(options?: Hl7ServerStopOptions): Promise<void> {\n return new Promise<void>((resolve, reject) => {\n if (!this.server) {\n reject(new Error('Stop was called but there is no server running'));\n return;\n }\n let forceDrainTimeout: NodeJS.Timeout | undefined;\n if (options?.forceDrainTimeoutMs !== -1) {\n forceDrainTimeout = setTimeout(() => {\n for (const connection of this.connections) {\n // Theoretically close should almost never throw as most errors are caught internal to the method and emitted as error events\n // We put a .catch here to prevent floating promises and log any errors that somehow make it through\n connection.close().catch(console.error);\n }\n }, options?.forceDrainTimeoutMs ?? DEFAULT_FORCE_DRAIN_TIMEOUT_MS);\n }\n this.server.close((err) => {\n if (err) {\n reject(err);\n return;\n }\n if (forceDrainTimeout) {\n clearTimeout(forceDrainTimeout);\n }\n this.connections.clear();\n this.server = undefined;\n resolve();\n });\n });\n }\n\n setEnhancedMode(enhancedMode: EnhancedMode): void {\n this.enhancedMode = enhancedMode;\n }\n\n getEnhancedMode(): EnhancedMode {\n return this.enhancedMode;\n }\n\n setEncoding(encoding: string | undefined): void {\n this.encoding = encoding;\n }\n\n getEncoding(): string | undefined {\n return this.encoding;\n }\n\n setMessagesPerMin(messagesPerMin: number | undefined): void {\n this.messagesPerMin = messagesPerMin;\n }\n\n getMessagesPerMin(): number | undefined {\n return this.messagesPerMin;\n }\n}\n"],
5
- "mappings": "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",
6
- "names": ["index_exports", "__export", "CR", "DEFAULT_ENCODING", "DEFAULT_FORCE_DRAIN_TIMEOUT_MS", "FS", "Hl7Base", "Hl7Client", "Hl7CloseEvent", "Hl7Connection", "Hl7EnhancedAckSentEvent", "Hl7ErrorEvent", "Hl7MessageEvent", "Hl7Server", "VT", "__toCommonJS", "Hl7Base", "type", "listener", "options", "import_node_assert", "import_node_net", "import_core", "import_iconv_lite", "VT", "CR", "FS", "Hl7MessageEvent", "connection", "message", "__publicField", "Hl7EnhancedAckSentEvent", "Hl7ErrorEvent", "error", "Hl7CloseEvent", "import_core", "DEFAULT_ENCODING", "ONE_MINUTE", "Hl7Connection", "Hl7Base", "socket", "encoding", "enhancedMode", "options", "__publicField", "data", "messages", "message", "Hl7MessageEvent", "err", "Hl7ErrorEvent", "Hl7CloseEvent", "event", "response", "Hl7EnhancedAckSentEvent", "origMsgCtrlId", "queueItem", "ackCode", "reply", "replyString", "replyBuffer", "iconv", "outputBuffer", "millisBetweenMsgs", "elapsedMillis", "messageEvent", "buffer", "bufferIdx", "messageEndIndex", "i", "contentBuffer", "contentString", "msg", "resolve", "reject", "msgCtrlId", "timer", "messagesPerMin", "Hl7Client", "Hl7Base", "options", "__publicField", "deferredPromise", "assert", "socket", "timeoutListener", "error", "connectListener", "connection", "Hl7Connection", "errorListener", "err", "closeListener", "Hl7CloseEvent", "event", "Hl7ErrorEvent", "resolve", "reject", "_resolve", "_reject", "msg", "import_core", "import_node_net", "DEFAULT_FORCE_DRAIN_TIMEOUT_MS", "Hl7Server", "handler", "__publicField", "port", "encoding", "enhancedMode", "options", "server", "net", "socket", "connection", "Hl7Connection", "resolve", "listenOnPort", "errorListener", "e", "reject", "forceDrainTimeout", "err", "messagesPerMin"]
4
+ "sourcesContent": ["// SPDX-FileCopyrightText: Copyright Orangebot, Inc. and Medplum contributors\n// SPDX-License-Identifier: Apache-2.0\nexport * from './base';\nexport * from './client';\nexport * from './connection';\nexport * from './constants';\nexport * from './events';\nexport * from './server';\n", "// SPDX-FileCopyrightText: Copyright Orangebot, Inc. and Medplum contributors\n// SPDX-License-Identifier: Apache-2.0\nimport type { Hl7CloseEvent, Hl7EnhancedAckSentEvent, Hl7ErrorEvent, Hl7MessageEvent, Hl7WarningEvent } from './events';\n\nexport interface Hl7EventMap {\n message: Hl7MessageEvent;\n error: Hl7ErrorEvent;\n warning: Hl7WarningEvent;\n close: Hl7CloseEvent;\n enhancedAckSent: Hl7EnhancedAckSentEvent;\n}\n\nexport abstract class Hl7Base extends EventTarget {\n addEventListener<K extends keyof Hl7EventMap>(\n type: K,\n listener: ((event: Hl7EventMap[K]) => void) | EventListenerObject | null,\n options?: boolean | AddEventListenerOptions\n ): void;\n\n addEventListener(\n type: string,\n listener: EventListenerOrEventListenerObject | null,\n options?: boolean | AddEventListenerOptions\n ): void {\n super.addEventListener(type, listener, options);\n }\n removeEventListener<K extends keyof Hl7EventMap>(\n type: K,\n listener: ((event: Hl7EventMap[K]) => void) | EventListenerObject | null,\n options?: boolean | AddEventListenerOptions\n ): void;\n\n removeEventListener(\n type: string,\n listener: EventListenerOrEventListenerObject | null,\n options?: boolean | AddEventListenerOptions\n ): void {\n super.removeEventListener(type, listener, options);\n }\n}\n", "// SPDX-FileCopyrightText: Copyright Orangebot, Inc. and Medplum contributors\n// SPDX-License-Identifier: Apache-2.0\nimport type { Hl7Message } from '@medplum/core';\nimport assert from 'node:assert';\nimport type { Socket } from 'node:net';\nimport { connect } from 'node:net';\nimport { Hl7Base } from './base';\nimport type { SendAndWaitOptions } from './connection';\nimport { Hl7Connection } from './connection';\nimport { Hl7CloseEvent, Hl7ErrorEvent, Hl7WarningEvent } from './events';\n\nexport interface Hl7ClientOptions {\n host: string;\n port: number;\n encoding?: string;\n keepAlive?: boolean;\n connectTimeout?: number; // Add timeout option\n}\n\nexport interface DeferredConnectionPromise {\n promise: Promise<Hl7Connection>;\n resolve: (connection: Hl7Connection) => void;\n reject: (err: Error) => void;\n}\n\nexport class Hl7Client extends Hl7Base {\n options: Hl7ClientOptions;\n host: string;\n port: number;\n encoding?: string;\n connection?: Hl7Connection;\n keepAlive: boolean;\n private socket?: Socket;\n private connectTimeout: number;\n private deferredConnectionPromise?: DeferredConnectionPromise;\n\n constructor(options: Hl7ClientOptions) {\n super();\n this.options = options;\n this.host = this.options.host;\n this.port = this.options.port;\n this.encoding = this.options.encoding;\n this.keepAlive = this.options.keepAlive ?? false;\n this.connectTimeout = this.options.connectTimeout ?? 30000; // Default 30 seconds\n }\n\n connect(): Promise<Hl7Connection> {\n // If we are already waiting for a pending connection attempt, just return the deferred promise to that\n // In the case that the promise is already resolve, we will also return a resolved connection\n if (this.deferredConnectionPromise) {\n return this.deferredConnectionPromise.promise;\n }\n\n const deferredPromise = (this.deferredConnectionPromise = this.createDeferredConnectionPromise());\n\n // Create the socket\n this.socket = connect({\n host: this.host,\n port: this.port,\n keepAlive: this.keepAlive,\n });\n\n if (this.connectTimeout > 0) {\n this.socket.setTimeout(this.connectTimeout);\n this.registerSocketTimeoutListener(deferredPromise);\n }\n\n this.registerSocketConnectListener(deferredPromise);\n this.registerSocketErrorListener(deferredPromise);\n this.registerSocketCloseListener(deferredPromise);\n\n return deferredPromise.promise;\n }\n\n private registerSocketTimeoutListener(deferredPromise: DeferredConnectionPromise): void {\n assert(this.socket);\n const socket = this.socket;\n\n // Handle timeout event\n const timeoutListener = (): void => {\n this.cleanupSocket(socket);\n const error = new Error(`Connection timeout after ${this.connectTimeout}ms`);\n this.rejectDeferredPromise(deferredPromise, error);\n };\n\n socket.on('timeout', timeoutListener);\n }\n\n private registerSocketConnectListener(deferredPromise: DeferredConnectionPromise): void {\n assert(this.socket);\n const socket = this.socket;\n\n // Handle successful connection\n const connectListener = (): void => {\n if (socket !== this.socket) {\n this.cleanupSocket(socket);\n return;\n }\n\n // Create the HL7 connection\n let connection: Hl7Connection;\n this.connection = connection = new Hl7Connection(socket, this.encoding);\n\n // Remove the timeout listener as we're now connected\n socket.setTimeout(0);\n\n this.registerHl7ConnectionListeners(connection);\n\n deferredPromise.resolve(connection);\n };\n\n socket.on('connect', connectListener);\n }\n\n private registerSocketErrorListener(deferredPromise: DeferredConnectionPromise): void {\n assert(this.socket);\n const socket = this.socket;\n\n // Handle connection errors\n const errorListener = (err: Error | AggregateError): void => {\n this.cleanupSocket(socket);\n\n if (err.constructor.name === 'AggregateError') {\n this.rejectDeferredPromise(deferredPromise, (err as AggregateError).errors[0]);\n } else {\n this.rejectDeferredPromise(deferredPromise, err);\n }\n };\n\n socket.on('error', errorListener);\n }\n\n private registerSocketCloseListener(deferredPromise: DeferredConnectionPromise): void {\n assert(this.socket);\n const socket = this.socket;\n\n // Handle connection errors\n const closeListener = (): void => {\n this.cleanupSocket(socket);\n this.rejectDeferredPromise(deferredPromise, new Error('Socket closed before connection finished'));\n };\n socket.on('close', closeListener);\n }\n\n private registerHl7ConnectionListeners(connection: Hl7Connection): void {\n // Set up event handlers\n connection.addEventListener('close', () => {\n this.socket = undefined;\n this.connection = undefined;\n this.deferredConnectionPromise = undefined;\n this.dispatchEvent(new Hl7CloseEvent());\n });\n\n connection.addEventListener('error', (event) => {\n this.dispatchEvent(new Hl7ErrorEvent(event.error));\n });\n\n connection.addEventListener('warning', (event) => {\n this.dispatchEvent(new Hl7WarningEvent(event.error));\n });\n }\n\n private createDeferredConnectionPromise(): DeferredConnectionPromise {\n // Setup our deferred connection promise\n let resolve!: (connection: Hl7Connection) => void;\n let reject!: (err: Error) => void;\n\n const promise = new Promise<Hl7Connection>((_resolve, _reject) => {\n resolve = _resolve;\n reject = _reject;\n });\n\n return {\n promise,\n resolve,\n reject,\n };\n }\n\n private rejectDeferredPromise(deferredPromise: DeferredConnectionPromise, err: Error): void {\n // Reject this deferred promise with the given error\n deferredPromise.reject(err);\n\n // If the currently tracked deferred promise is this deferred promise, remove it from the client\n if (this.deferredConnectionPromise === deferredPromise) {\n this.deferredConnectionPromise = undefined;\n }\n }\n\n private cleanupSocket(socket: Socket): void {\n if (!socket.destroyed) {\n socket.destroy();\n }\n if (socket === this.socket) {\n this.socket = undefined;\n }\n }\n\n async send(msg: Hl7Message): Promise<void> {\n return (await this.connect()).send(msg);\n }\n\n async sendAndWait(msg: Hl7Message, options?: SendAndWaitOptions): Promise<Hl7Message> {\n return (await this.connect()).sendAndWait(msg, options);\n }\n\n async close(): Promise<void> {\n if (this.deferredConnectionPromise) {\n this.rejectDeferredPromise(this.deferredConnectionPromise, new Error('Client closed while connecting'));\n }\n\n // Close established connection if it exists\n if (this.connection) {\n const connection = this.connection;\n delete this.connection;\n await connection.close();\n } else {\n // Emit close event because the connection will not be able to emit it for us\n // Since it has not connected at this point\n this.dispatchEvent(new Hl7CloseEvent());\n }\n // Close the socket if it exists\n if (this.socket) {\n this.socket.removeAllListeners();\n this.socket.destroy();\n this.socket = undefined;\n }\n }\n}\n", "// SPDX-FileCopyrightText: Copyright Orangebot, Inc. and Medplum contributors\n// SPDX-License-Identifier: Apache-2.0\nimport { Hl7Message, OperationOutcomeError, ReturnAckCategory, sleep, validationError } from '@medplum/core';\nimport iconv from 'iconv-lite';\nimport type net from 'node:net';\nimport { Hl7Base } from './base';\nimport { CR, FS, VT } from './constants';\nimport { Hl7CloseEvent, Hl7EnhancedAckSentEvent, Hl7ErrorEvent, Hl7MessageEvent, Hl7WarningEvent } from './events';\n\n// Export `ReturnAckCategory` for backwards-compat\nexport { ReturnAckCategory } from '@medplum/core';\n\n// iconv-lite docs have great examples and explanations for how to use Buffers with iconv-lite:\n// See: https://github.com/ashtuchkin/iconv-lite/wiki/Use-Buffers-when-decoding\n\nexport type Hl7MessageQueueItem = {\n message: Hl7Message;\n resolve: (reply: Hl7Message) => void;\n reject: (err: Error) => void;\n returnAck: ReturnAckCategory;\n timer?: NodeJS.Timeout;\n};\n\nexport interface SendAndWaitOptions {\n /** The ACK-level that the Promise should resolve on. The default is `ReturnAckCategory.APPLICATION` (returns on the first application-level ACK). */\n returnAck?: ReturnAckCategory;\n /** The amount of milliseconds to wait before timing out when waiting for the response to a message. */\n timeoutMs?: number;\n}\n\nexport interface Hl7ConnectionOptions {\n messagesPerMin?: number;\n}\n\n/**\n * Enhanced mode for HL7 connections.\n * - `'standard'`: Standard enhanced mode behavior\n * - `'aaMode'`: AA mode - special enhanced mode that only accepts AA acknowledgements\n * - `undefined`: Enhanced mode is not enabled (standard behavior)\n */\nexport type EnhancedMode = 'standard' | 'aaMode' | undefined;\n\nexport const DEFAULT_ENCODING = 'utf-8';\nconst ONE_MINUTE = 60 * 1000;\n\nexport class Hl7Connection extends Hl7Base {\n readonly socket: net.Socket;\n encoding: string;\n enhancedMode: EnhancedMode = undefined;\n private messagesPerMin: number | undefined = undefined;\n private chunks: Buffer[] = [];\n private readonly pendingMessages: Map<string, Hl7MessageQueueItem> = new Map<string, Hl7MessageQueueItem>();\n private readonly responseQueue: Hl7MessageEvent[] = [];\n private lastMessageDispatchedTime = 0;\n private responseQueueProcessing = false;\n\n constructor(\n socket: net.Socket,\n encoding: string = DEFAULT_ENCODING,\n enhancedMode?: EnhancedMode,\n options: Hl7ConnectionOptions = {}\n ) {\n super();\n\n this.socket = socket;\n this.encoding = encoding;\n this.enhancedMode = enhancedMode;\n this.messagesPerMin = options.messagesPerMin;\n\n socket.on('data', (data: Buffer) => {\n try {\n this.appendData(data);\n const messages = this.parseMessages();\n for (const message of messages) {\n this.responseQueue.push(new Hl7MessageEvent(this, message));\n }\n this.processResponseQueue().catch((err) => {\n this.dispatchEvent(new Hl7ErrorEvent(err));\n });\n } catch (err) {\n this.dispatchEvent(new Hl7ErrorEvent(err as Error));\n }\n });\n\n socket.on('error', (err) => {\n this.resetBuffer();\n this.dispatchEvent(new Hl7ErrorEvent(err));\n });\n\n // The difference between \"end\" and \"close\", is that \"end\" is only emitted on half-close from the other side\n // If the connection from the other side does not close gracefully, but instead we destroy the socket, then the Hl7Connection will not emit close\n // if we listen only for \"end\"; \"close\" is always emitted, whether the close is graceful or forceful\n socket.on('close', () => {\n // Reject any messages that were still pending when the connection was closed externally (e.g. closed by peer)\n this.drainPendingMessages();\n this.dispatchEvent(new Hl7CloseEvent());\n });\n\n this.addEventListener('message', (event) => {\n // In standard enhanced mode, send commit ACK (CA) immediately, then later forward app-level ACKs\n // In aaMode, send application ACK (AA) immediately, then ignore any later app-level ACKs\n let response: Hl7Message | undefined;\n if (this.enhancedMode === 'standard') {\n response = event.message.buildAck({ ackCode: 'CA' });\n } else if (this.enhancedMode === 'aaMode') {\n response = event.message.buildAck({ ackCode: 'AA' });\n }\n if (response) {\n this.send(response);\n this.dispatchEvent(new Hl7EnhancedAckSentEvent(this, response));\n }\n const origMsgCtrlId = event.message.getSegment('MSA')?.getField(2)?.toString();\n // If there is no message control ID, just return\n if (!origMsgCtrlId) {\n return;\n }\n const queueItem = this.pendingMessages.get(origMsgCtrlId);\n if (!queueItem) {\n this.dispatchEvent(\n new Hl7WarningEvent(\n new OperationOutcomeError({\n resourceType: 'OperationOutcome',\n issue: [\n {\n severity: 'warning',\n code: 'not-found',\n details: {\n text: 'Response received for unknown message control ID',\n },\n diagnostics: `Received ACK for message control ID '${origMsgCtrlId}' but there was no pending message with this control ID`,\n },\n ],\n })\n )\n );\n return;\n }\n // Check the ACK type we should return on\n const ackCode = event.message.getSegment('MSA')?.getField(1)?.toString()?.toUpperCase();\n if (!ackCode) {\n return;\n }\n\n // Two modes:\n // Application-level or first ACK\n\n // First should always return on any ACK message, this is the default\n // The exception is APPLICATION, which should not resolve when the ACK is a CA, but should resolve on all other ACK types\n // On CA, we return early\n if (queueItem.returnAck === ReturnAckCategory.APPLICATION && ackCode === 'CA') {\n return;\n }\n\n // Resolve the promise if there is one pending for this message and we didn't exit already because the ACK type matches\n queueItem.resolve(event.message);\n this.pendingMessages.delete(origMsgCtrlId);\n });\n }\n\n /** @returns A boolean representing whether the socket attached to this Hl7Connection has emitted the close event already or not. */\n isClosed(): boolean {\n return this.socket.closed;\n }\n\n private sendImpl(reply: Hl7Message): void {\n const replyString = reply.toString();\n const replyBuffer = iconv.encode(replyString, this.encoding);\n const outputBuffer = Buffer.alloc(replyBuffer.length + 3);\n outputBuffer.writeInt8(VT, 0);\n replyBuffer.copy(outputBuffer, 1);\n outputBuffer.writeInt8(FS, replyBuffer.length + 1);\n outputBuffer.writeInt8(CR, replyBuffer.length + 2);\n this.socket.write(outputBuffer);\n }\n\n private async processResponseQueue(): Promise<void> {\n if (this.responseQueueProcessing) {\n return;\n }\n\n this.responseQueueProcessing = true;\n while (this.responseQueue.length) {\n if (this.messagesPerMin) {\n const millisBetweenMsgs = ONE_MINUTE / this.messagesPerMin;\n const elapsedMillis = Date.now() - this.lastMessageDispatchedTime;\n if (millisBetweenMsgs > elapsedMillis) {\n await sleep(millisBetweenMsgs - elapsedMillis);\n }\n }\n const messageEvent = this.responseQueue.shift() as Hl7MessageEvent;\n if (messageEvent) {\n this.dispatchEvent(messageEvent);\n }\n this.lastMessageDispatchedTime = Date.now();\n }\n this.responseQueueProcessing = false;\n }\n\n /**\n * Parses complete HL7 messages from the accumulated buffer.\n * Continues parsing while the buffer starts with VT and contains FS+CR.\n * Keeps any incomplete message data in the buffer for the next chunk.\n * @returns An array of parsed HL7 messages.\n */\n private parseMessages(): Hl7Message[] {\n const messages: Hl7Message[] = [];\n const buffer = Buffer.concat(this.chunks);\n this.resetBuffer();\n\n // Check if buffer starts with VT (Vertical Tab)\n if (buffer.length === 0) {\n return messages;\n }\n\n let bufferIdx = 0;\n\n // Keep parsing while we have complete messages\n while (bufferIdx < buffer.length) {\n // Ignore bytes between message frames\n while (buffer[bufferIdx] !== VT && bufferIdx < buffer.length) {\n bufferIdx++;\n }\n\n // Look for FS+CR sequence to mark end of message\n let messageEndIndex = -1;\n\n for (let i = bufferIdx + 1; i < buffer.length - 1; i++) {\n if (buffer[i] === FS && buffer[i + 1] === CR) {\n messageEndIndex = i + 1; // Index of CR (end of message)\n break;\n }\n }\n\n // If we don't have a complete message yet, wait for more data\n if (messageEndIndex === -1) {\n break;\n }\n\n // Extract the complete message (including VT, FS, and CR)\n const messageBuffer = buffer.subarray(bufferIdx, messageEndIndex + 1);\n // Extract the content (without VT at start and FS+CR at end)\n const contentBuffer = messageBuffer.subarray(1, -2);\n const contentString = iconv.decode(contentBuffer, this.encoding);\n const message = Hl7Message.parse(contentString);\n\n messages.push(message);\n\n // Move past this message\n bufferIdx = messageEndIndex + 1;\n }\n\n // Keep any remaining unfinished chunk in this.chunks\n this.chunks = bufferIdx < buffer.length ? [buffer.subarray(bufferIdx)] : [];\n\n return messages;\n }\n\n send(reply: Hl7Message): void {\n this.sendImpl(reply);\n }\n\n async sendAndWait(msg: Hl7Message, options?: SendAndWaitOptions): Promise<Hl7Message> {\n return new Promise<Hl7Message>((resolve, reject) => {\n const msgCtrlId = msg.getSegment('MSH')?.getField(10)?.toString();\n if (!msgCtrlId) {\n reject(new OperationOutcomeError(validationError('Required field missing: MSH.10')));\n return;\n }\n\n let timer: NodeJS.Timeout | undefined;\n\n if (options?.timeoutMs) {\n timer = setTimeout(() => {\n this.pendingMessages.delete(msgCtrlId);\n reject(\n new OperationOutcomeError({\n resourceType: 'OperationOutcome',\n issue: [\n {\n severity: 'error',\n code: 'timeout',\n details: {\n text: 'Client timeout',\n },\n diagnostics: `Request timed out after waiting ${options.timeoutMs} milliseconds for response`,\n },\n ],\n })\n );\n }, options.timeoutMs);\n }\n\n this.pendingMessages.set(msgCtrlId, {\n message: msg,\n resolve,\n reject,\n returnAck: options?.returnAck ?? ReturnAckCategory.APPLICATION,\n timer,\n });\n this.sendImpl(msg);\n });\n }\n\n async close(): Promise<void> {\n // If we have already received the close event, then we can just return immediately\n if (this.isClosed()) {\n return;\n }\n this.socket.end();\n this.socket.destroy();\n // drainPendingMessages is also called by the socket 'close' handler, but we call it here first so\n // that rejections are delivered before the close event is dispatched when close() is called explicitly.\n // The socket 'close' handler's call will be a no-op since the map will already be empty.\n this.drainPendingMessages();\n await new Promise((resolve) => {\n // Register a temporary listener to help resolve the promise once close has been emitted\n this.socket.once('close', resolve);\n });\n }\n\n /**\n * Rejects all pending sendAndWait promises and clears the pending messages map.\n * Safe to call multiple times \u2014 subsequent calls are no-ops once the map is empty.\n */\n private drainPendingMessages(): void {\n if (!this.pendingMessages.size) {\n return;\n }\n const pendingCount = this.pendingMessages.size;\n for (const queueItem of this.pendingMessages.values()) {\n if (queueItem.timer) {\n clearTimeout(queueItem.timer);\n }\n queueItem.reject(\n new OperationOutcomeError({\n resourceType: 'OperationOutcome',\n issue: [\n {\n severity: 'warning',\n code: 'incomplete',\n details: {\n text: 'Message was still pending when connection closed',\n },\n },\n ],\n })\n );\n }\n this.dispatchEvent(\n new Hl7ErrorEvent(\n new OperationOutcomeError({\n resourceType: 'OperationOutcome',\n issue: [\n {\n severity: 'warning',\n code: 'incomplete',\n details: {\n text: 'Messages were still pending when connection closed',\n },\n diagnostics: `Hl7Connection closed while ${pendingCount} messages were pending`,\n },\n ],\n })\n )\n );\n this.pendingMessages.clear();\n }\n\n private appendData(data: Buffer): void {\n this.chunks.push(data);\n }\n\n private resetBuffer(): void {\n this.chunks = [];\n }\n\n setEncoding(encoding: string | undefined): void {\n this.encoding = encoding ?? DEFAULT_ENCODING;\n }\n\n getEncoding(): string {\n return this.encoding;\n }\n\n setEnhancedMode(enhancedMode: EnhancedMode): void {\n this.enhancedMode = enhancedMode;\n }\n\n getEnhancedMode(): EnhancedMode {\n return this.enhancedMode;\n }\n\n setMessagesPerMin(messagesPerMin: number | undefined): void {\n this.messagesPerMin = messagesPerMin;\n }\n\n getMessagesPerMin(): number | undefined {\n return this.messagesPerMin;\n }\n\n getPendingMessageCount(): number {\n return this.pendingMessages.size;\n }\n}\n", "// SPDX-FileCopyrightText: Copyright Orangebot, Inc. and Medplum contributors\n// SPDX-License-Identifier: Apache-2.0\n\n/**\n * VT (Vertical Tab) character.\n *\n * In HL7 messages, this character is used to indicate the start of a message.\n */\nexport const VT = 0x0b;\n\n/**\n * CR (Carriage Return) character.\n *\n * In HL7 messages, this character is used to indicate the end of a message.\n */\nexport const CR = 0x0d;\n\n/**\n * FS (File Separator) character.\n *\n * In HL7 messages, this character is used to separate fields.\n */\nexport const FS = 0x1c;\n", "// SPDX-FileCopyrightText: Copyright Orangebot, Inc. and Medplum contributors\n// SPDX-License-Identifier: Apache-2.0\nimport type { Hl7Message } from '@medplum/core';\nimport type { Hl7Connection } from './connection';\n\nexport class Hl7MessageEvent extends Event {\n readonly connection: Hl7Connection;\n readonly message: Hl7Message;\n\n constructor(connection: Hl7Connection, message: Hl7Message) {\n super('message');\n this.connection = connection;\n this.message = message;\n }\n}\n\nexport class Hl7EnhancedAckSentEvent extends Event {\n readonly connection: Hl7Connection;\n readonly message: Hl7Message;\n\n constructor(connection: Hl7Connection, message: Hl7Message) {\n super('enhancedAckSent');\n this.connection = connection;\n this.message = message;\n }\n}\n\nexport class Hl7ErrorEvent extends Event {\n readonly error: Error;\n\n constructor(error: Error) {\n super('error');\n this.error = error;\n }\n}\n\nexport class Hl7WarningEvent extends Event {\n readonly error: Error;\n\n constructor(error: Error) {\n super('warning');\n this.error = error;\n }\n}\n\nexport class Hl7CloseEvent extends Event {\n constructor() {\n super('close');\n }\n}\n", "// SPDX-FileCopyrightText: Copyright Orangebot, Inc. and Medplum contributors\n// SPDX-License-Identifier: Apache-2.0\nimport { sleep } from '@medplum/core';\nimport net from 'node:net';\nimport type { EnhancedMode, Hl7ConnectionOptions } from './connection';\nimport { Hl7Connection } from './connection';\n\n/**\n * Options for configuring the `Hl7Server#stop` method.\n */\nexport interface Hl7ServerStopOptions {\n /**\n * Time in milliseconds to allow client connections to gracefully close after the stop method has been called, before forcefully closing them.\n *\n * Can be set to `-1` to disable forceful draining of connections during stop.\n *\n * Defaults to `10_000`.\n */\n forceDrainTimeoutMs?: number;\n}\n\nexport const DEFAULT_FORCE_DRAIN_TIMEOUT_MS = 10_000;\n\nexport class Hl7Server {\n readonly handler: (connection: Hl7Connection) => void;\n server?: net.Server;\n private encoding: string | undefined = undefined;\n private enhancedMode: EnhancedMode = undefined;\n private messagesPerMin: number | undefined = undefined;\n private readonly connections = new Set<Hl7Connection>();\n\n constructor(handler: (connection: Hl7Connection) => void) {\n this.handler = handler;\n }\n\n async start(\n port: number,\n encoding?: string,\n enhancedMode?: EnhancedMode,\n connectionOptions?: Hl7ConnectionOptions\n ): Promise<number> {\n if (encoding) {\n this.setEncoding(encoding);\n }\n if (enhancedMode !== undefined) {\n this.setEnhancedMode(enhancedMode);\n }\n if (connectionOptions?.messagesPerMin !== undefined) {\n this.setMessagesPerMin(connectionOptions.messagesPerMin);\n }\n\n const server = net.createServer((socket) => {\n const connection = new Hl7Connection(socket, this.encoding, this.enhancedMode, {\n messagesPerMin: this.messagesPerMin,\n });\n this.handler(connection);\n this.connections.add(connection);\n connection.addEventListener('close', () => {\n this.connections.delete(connection);\n });\n });\n\n return new Promise<number>((resolve, reject) => {\n const listenOnPort = (port: number): void => {\n server.listen(port, () => {\n const boundPort = (server.address() as { port: number }).port;\n resolve(boundPort);\n });\n };\n\n const errorListener = (e: Error & { code?: string }): void => {\n if (e?.code === 'EADDRINUSE') {\n server.close(() => sleep(50).then(() => listenOnPort(port)));\n } else {\n reject(e);\n }\n };\n\n server.on('error', errorListener);\n\n server.once('listening', () => {\n server.off('error', errorListener);\n });\n\n listenOnPort(port);\n\n this.server = server;\n });\n }\n\n /**\n * Stops the HL7 server.\n *\n * By default, the server will stop accepting new connections after this method is called, and wait for current connections to close naturally.\n *\n * If all connections don't close within 10 seconds, the server will forcefully close them before shutting down.\n *\n * The default time to wait before forcefully closing connections can be changed by passing an integer value for the optional `options.forceDrainTimeoutMs`.\n *\n * Forced drain can also be disabled by passing `-1` for `options.forceDrainTimeoutMs`.\n * @param options - Optional options to configure the stopping of the server.\n * @returns Promise that resolves when the server has stopped, or rejects if an error prevents server from stopping.\n */\n async stop(options?: Hl7ServerStopOptions): Promise<void> {\n return new Promise<void>((resolve, reject) => {\n if (!this.server) {\n reject(new Error('Stop was called but there is no server running'));\n return;\n }\n let forceDrainTimeout: NodeJS.Timeout | undefined;\n if (options?.forceDrainTimeoutMs !== -1) {\n forceDrainTimeout = setTimeout(() => {\n for (const connection of this.connections) {\n // Theoretically close should almost never throw as most errors are caught internal to the method and emitted as error events\n // We put a .catch here to prevent floating promises and log any errors that somehow make it through\n connection.close().catch(console.error);\n }\n }, options?.forceDrainTimeoutMs ?? DEFAULT_FORCE_DRAIN_TIMEOUT_MS);\n }\n this.server.close((err) => {\n if (err) {\n reject(err);\n return;\n }\n if (forceDrainTimeout) {\n clearTimeout(forceDrainTimeout);\n }\n this.connections.clear();\n this.server = undefined;\n resolve();\n });\n });\n }\n\n setEnhancedMode(enhancedMode: EnhancedMode): void {\n this.enhancedMode = enhancedMode;\n }\n\n getEnhancedMode(): EnhancedMode {\n return this.enhancedMode;\n }\n\n setEncoding(encoding: string | undefined): void {\n this.encoding = encoding;\n }\n\n getEncoding(): string | undefined {\n return this.encoding;\n }\n\n setMessagesPerMin(messagesPerMin: number | undefined): void {\n this.messagesPerMin = messagesPerMin;\n }\n\n getMessagesPerMin(): number | undefined {\n return this.messagesPerMin;\n }\n}\n"],
5
+ "mappings": "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",
6
+ "names": ["index_exports", "__export", "CR", "DEFAULT_ENCODING", "DEFAULT_FORCE_DRAIN_TIMEOUT_MS", "FS", "Hl7Base", "Hl7Client", "Hl7CloseEvent", "Hl7Connection", "Hl7EnhancedAckSentEvent", "Hl7ErrorEvent", "Hl7MessageEvent", "Hl7Server", "Hl7WarningEvent", "VT", "__toCommonJS", "Hl7Base", "type", "listener", "options", "import_node_assert", "import_node_net", "import_core", "import_iconv_lite", "VT", "CR", "FS", "Hl7MessageEvent", "connection", "message", "__publicField", "Hl7EnhancedAckSentEvent", "Hl7ErrorEvent", "error", "Hl7WarningEvent", "Hl7CloseEvent", "import_core", "DEFAULT_ENCODING", "ONE_MINUTE", "Hl7Connection", "Hl7Base", "socket", "encoding", "enhancedMode", "options", "__publicField", "data", "messages", "message", "Hl7MessageEvent", "err", "Hl7ErrorEvent", "Hl7CloseEvent", "event", "response", "Hl7EnhancedAckSentEvent", "origMsgCtrlId", "queueItem", "Hl7WarningEvent", "ackCode", "reply", "replyString", "replyBuffer", "iconv", "outputBuffer", "millisBetweenMsgs", "elapsedMillis", "messageEvent", "buffer", "bufferIdx", "messageEndIndex", "i", "contentBuffer", "contentString", "msg", "resolve", "reject", "msgCtrlId", "timer", "pendingCount", "messagesPerMin", "Hl7Client", "Hl7Base", "options", "__publicField", "deferredPromise", "assert", "socket", "timeoutListener", "error", "connectListener", "connection", "Hl7Connection", "errorListener", "err", "closeListener", "Hl7CloseEvent", "event", "Hl7ErrorEvent", "Hl7WarningEvent", "resolve", "reject", "_resolve", "_reject", "msg", "import_core", "import_node_net", "DEFAULT_FORCE_DRAIN_TIMEOUT_MS", "Hl7Server", "handler", "__publicField", "port", "encoding", "enhancedMode", "connectionOptions", "server", "net", "socket", "connection", "Hl7Connection", "resolve", "reject", "listenOnPort", "boundPort", "errorListener", "e", "options", "forceDrainTimeout", "err", "messagesPerMin"]
7
7
  }
@@ -101,6 +101,11 @@ export declare class Hl7Connection extends Hl7Base {
101
101
  send(reply: Hl7Message): void;
102
102
  sendAndWait(msg: Hl7Message, options?: SendAndWaitOptions): Promise<Hl7Message>;
103
103
  close(): Promise<void>;
104
+ /**
105
+ * Rejects all pending sendAndWait promises and clears the pending messages map.
106
+ * Safe to call multiple times — subsequent calls are no-ops once the map is empty.
107
+ */
108
+ private drainPendingMessages;
104
109
  private appendData;
105
110
  private resetBuffer;
106
111
  setEncoding(encoding: string | undefined): void;
@@ -130,6 +135,7 @@ export declare class Hl7ErrorEvent extends Event {
130
135
  export declare interface Hl7EventMap {
131
136
  message: Hl7MessageEvent;
132
137
  error: Hl7ErrorEvent;
138
+ warning: Hl7WarningEvent;
133
139
  close: Hl7CloseEvent;
134
140
  enhancedAckSent: Hl7EnhancedAckSentEvent;
135
141
  }
@@ -156,7 +162,7 @@ export declare class Hl7Server {
156
162
  private messagesPerMin;
157
163
  private readonly connections;
158
164
  constructor(handler: (connection: Hl7Connection) => void);
159
- start(port: number, encoding?: string, enhancedMode?: EnhancedMode, options?: Hl7ConnectionOptions): Promise<void>;
165
+ start(port: number, encoding?: string, enhancedMode?: EnhancedMode, connectionOptions?: Hl7ConnectionOptions): Promise<number>;
160
166
  /**
161
167
  * Stops the HL7 server.
162
168
  *
@@ -193,6 +199,11 @@ export declare interface Hl7ServerStopOptions {
193
199
  forceDrainTimeoutMs?: number;
194
200
  }
195
201
 
202
+ export declare class Hl7WarningEvent extends Event {
203
+ readonly error: Error;
204
+ constructor(error: Error);
205
+ }
206
+
196
207
  export { ReturnAckCategory }
197
208
 
198
209
  export declare interface SendAndWaitOptions {
@@ -101,6 +101,11 @@ export declare class Hl7Connection extends Hl7Base {
101
101
  send(reply: Hl7Message): void;
102
102
  sendAndWait(msg: Hl7Message, options?: SendAndWaitOptions): Promise<Hl7Message>;
103
103
  close(): Promise<void>;
104
+ /**
105
+ * Rejects all pending sendAndWait promises and clears the pending messages map.
106
+ * Safe to call multiple times — subsequent calls are no-ops once the map is empty.
107
+ */
108
+ private drainPendingMessages;
104
109
  private appendData;
105
110
  private resetBuffer;
106
111
  setEncoding(encoding: string | undefined): void;
@@ -130,6 +135,7 @@ export declare class Hl7ErrorEvent extends Event {
130
135
  export declare interface Hl7EventMap {
131
136
  message: Hl7MessageEvent;
132
137
  error: Hl7ErrorEvent;
138
+ warning: Hl7WarningEvent;
133
139
  close: Hl7CloseEvent;
134
140
  enhancedAckSent: Hl7EnhancedAckSentEvent;
135
141
  }
@@ -156,7 +162,7 @@ export declare class Hl7Server {
156
162
  private messagesPerMin;
157
163
  private readonly connections;
158
164
  constructor(handler: (connection: Hl7Connection) => void);
159
- start(port: number, encoding?: string, enhancedMode?: EnhancedMode, options?: Hl7ConnectionOptions): Promise<void>;
165
+ start(port: number, encoding?: string, enhancedMode?: EnhancedMode, connectionOptions?: Hl7ConnectionOptions): Promise<number>;
160
166
  /**
161
167
  * Stops the HL7 server.
162
168
  *
@@ -193,6 +199,11 @@ export declare interface Hl7ServerStopOptions {
193
199
  forceDrainTimeoutMs?: number;
194
200
  }
195
201
 
202
+ export declare class Hl7WarningEvent extends Event {
203
+ readonly error: Error;
204
+ constructor(error: Error);
205
+ }
206
+
196
207
  export { ReturnAckCategory }
197
208
 
198
209
  export declare interface SendAndWaitOptions {
@@ -1,2 +1,2 @@
1
- var x=Object.defineProperty;var b=(d,i,e)=>i in d?x(d,i,{enumerable:!0,configurable:!0,writable:!0,value:e}):d[i]=e;var o=(d,i,e)=>b(d,typeof i!="symbol"?i+"":i,e);var m=class extends EventTarget{addEventListener(i,e,t){super.addEventListener(i,e,t)}removeEventListener(i,e,t){super.removeEventListener(i,e,t)}};import E from"node:assert";import{connect as I}from"node:net";import{Hl7Message as L,OperationOutcomeError as f,ReturnAckCategory as P,sleep as O,validationError as T}from"@medplum/core";import w from"iconv-lite";var k=11,C=13,H=28;var v=class extends Event{constructor(e,t){super("message");o(this,"connection");o(this,"message");this.connection=e,this.message=t}},M=class extends Event{constructor(e,t){super("enhancedAckSent");o(this,"connection");o(this,"message");this.connection=e,this.message=t}},l=class extends Event{constructor(e){super("error");o(this,"error");this.error=e}},u=class extends Event{constructor(){super("close")}};import{ReturnAckCategory as V}from"@medplum/core";var y="utf-8",D=60*1e3,g=class extends m{constructor(e,t=y,s,n={}){super();o(this,"socket");o(this,"encoding");o(this,"enhancedMode");o(this,"messagesPerMin");o(this,"chunks",[]);o(this,"pendingMessages",new Map);o(this,"responseQueue",[]);o(this,"lastMessageDispatchedTime",0);o(this,"responseQueueProcessing",!1);this.socket=e,this.encoding=t,this.enhancedMode=s,this.messagesPerMin=n.messagesPerMin,e.on("data",c=>{try{this.appendData(c);let r=this.parseMessages();for(let a of r)this.responseQueue.push(new v(this,a));this.processResponseQueue().catch(a=>{this.dispatchEvent(new l(a))})}catch(r){this.dispatchEvent(new l(r))}}),e.on("error",c=>{this.resetBuffer(),this.dispatchEvent(new l(c))}),e.on("close",()=>{this.dispatchEvent(new u)}),this.addEventListener("message",c=>{let r;this.enhancedMode==="standard"?r=c.message.buildAck({ackCode:"CA"}):this.enhancedMode==="aaMode"&&(r=c.message.buildAck({ackCode:"AA"})),r&&(this.send(r),this.dispatchEvent(new M(this,r)));let a=c.message.getSegment("MSA")?.getField(2)?.toString();if(!a)return;let h=this.pendingMessages.get(a);if(!h){this.dispatchEvent(new l(new f({resourceType:"OperationOutcome",issue:[{severity:"warning",code:"not-found",details:{text:"Response received for unknown message control ID"},diagnostics:`Received ACK for message control ID '${a}' but there was no pending message with this control ID`}]})));return}let p=c.message.getSegment("MSA")?.getField(1)?.toString()?.toUpperCase();p&&(h.returnAck===P.APPLICATION&&p==="CA"||(h.resolve(c.message),this.pendingMessages.delete(a)))})}isClosed(){return this.socket.closed}sendImpl(e){let t=e.toString(),s=w.encode(t,this.encoding),n=Buffer.alloc(s.length+3);n.writeInt8(11,0),s.copy(n,1),n.writeInt8(28,s.length+1),n.writeInt8(13,s.length+2),this.socket.write(n)}async processResponseQueue(){if(!this.responseQueueProcessing){for(this.responseQueueProcessing=!0;this.responseQueue.length;){if(this.messagesPerMin){let t=D/this.messagesPerMin,s=Date.now()-this.lastMessageDispatchedTime;t>s&&await O(t-s)}let e=this.responseQueue.shift();e&&this.dispatchEvent(e),this.lastMessageDispatchedTime=Date.now()}this.responseQueueProcessing=!1}}parseMessages(){let e=[],t=Buffer.concat(this.chunks);if(this.resetBuffer(),t.length===0)return e;let s=0;for(;s<t.length;){for(;t[s]!==11&&s<t.length;)s++;let n=-1;for(let p=s+1;p<t.length-1;p++)if(t[p]===28&&t[p+1]===13){n=p+1;break}if(n===-1)break;let r=t.subarray(s,n+1).subarray(1,-2),a=w.decode(r,this.encoding),h=L.parse(a);e.push(h),s=n+1}return this.chunks=s<t.length?[t.subarray(s)]:[],e}send(e){this.sendImpl(e)}async sendAndWait(e,t){return new Promise((s,n)=>{let c=e.getSegment("MSH")?.getField(10)?.toString();if(!c){n(new f(T("Required field missing: MSH.10")));return}let r;t?.timeoutMs&&(r=setTimeout(()=>{this.pendingMessages.delete(c),n(new f({resourceType:"OperationOutcome",issue:[{severity:"error",code:"timeout",details:{text:"Client timeout"},diagnostics:`Request timed out after waiting ${t.timeoutMs} milliseconds for response`}]}))},t.timeoutMs)),this.pendingMessages.set(c,{message:e,resolve:s,reject:n,returnAck:t?.returnAck??P.APPLICATION,timer:r}),this.sendImpl(e)})}async close(){if(!this.isClosed()){if(this.socket.end(),this.socket.destroy(),this.pendingMessages.size){for(let e of this.pendingMessages.values())e.timer&&clearTimeout(e.timer),e.reject(new f({resourceType:"OperationOutcome",issue:[{severity:"warning",code:"incomplete",details:{text:"Message was still pending when connection closed"}}]}));this.dispatchEvent(new l(new f({resourceType:"OperationOutcome",issue:[{severity:"warning",code:"incomplete",details:{text:"Messages were still pending when connection closed"},diagnostics:`Hl7Connection closed while ${this.pendingMessages.size} messages were pending`}]}))),this.pendingMessages.clear()}await new Promise(e=>{this.socket.once("close",e)})}}appendData(e){this.chunks.push(e)}resetBuffer(){this.chunks=[]}setEncoding(e){this.encoding=e??y}getEncoding(){return this.encoding}setEnhancedMode(e){this.enhancedMode=e}getEnhancedMode(){return this.enhancedMode}setMessagesPerMin(e){this.messagesPerMin=e}getMessagesPerMin(){return this.messagesPerMin}getPendingMessageCount(){return this.pendingMessages.size}};var S=class extends m{constructor(e){super();o(this,"options");o(this,"host");o(this,"port");o(this,"encoding");o(this,"connection");o(this,"keepAlive");o(this,"socket");o(this,"connectTimeout");o(this,"deferredConnectionPromise");this.options=e,this.host=this.options.host,this.port=this.options.port,this.encoding=this.options.encoding,this.keepAlive=this.options.keepAlive??!1,this.connectTimeout=this.options.connectTimeout??3e4}connect(){if(this.deferredConnectionPromise)return this.deferredConnectionPromise.promise;let e=this.deferredConnectionPromise=this.createDeferredConnectionPromise();return this.socket=I({host:this.host,port:this.port,keepAlive:this.keepAlive}),this.connectTimeout>0&&(this.socket.setTimeout(this.connectTimeout),this.registerSocketTimeoutListener(e)),this.registerSocketConnectListener(e),this.registerSocketErrorListener(e),this.registerSocketCloseListener(e),e.promise}registerSocketTimeoutListener(e){E(this.socket);let t=this.socket,s=()=>{this.cleanupSocket(t);let n=new Error(`Connection timeout after ${this.connectTimeout}ms`);this.rejectDeferredPromise(e,n)};t.on("timeout",s)}registerSocketConnectListener(e){E(this.socket);let t=this.socket,s=()=>{if(t!==this.socket){this.cleanupSocket(t);return}let n;this.connection=n=new g(t,this.encoding),t.setTimeout(0),this.registerHl7ConnectionListeners(n),e.resolve(n)};t.on("connect",s)}registerSocketErrorListener(e){E(this.socket);let t=this.socket,s=n=>{this.cleanupSocket(t),n.constructor.name==="AggregateError"?this.rejectDeferredPromise(e,n.errors[0]):this.rejectDeferredPromise(e,n)};t.on("error",s)}registerSocketCloseListener(e){E(this.socket);let t=this.socket,s=()=>{this.cleanupSocket(t),this.rejectDeferredPromise(e,new Error("Socket closed before connection finished"))};t.on("close",s)}registerHl7ConnectionListeners(e){e.addEventListener("close",()=>{this.socket=void 0,this.connection=void 0,this.deferredConnectionPromise=void 0,this.dispatchEvent(new u)}),e.addEventListener("error",t=>{this.dispatchEvent(new l(t.error))})}createDeferredConnectionPromise(){let e,t;return{promise:new Promise((n,c)=>{e=n,t=c}),resolve:e,reject:t}}rejectDeferredPromise(e,t){e.reject(t),this.deferredConnectionPromise===e&&(this.deferredConnectionPromise=void 0)}cleanupSocket(e){e.destroyed||e.destroy(),e===this.socket&&(this.socket=void 0)}async send(e){return(await this.connect()).send(e)}async sendAndWait(e,t){return(await this.connect()).sendAndWait(e,t)}async close(){if(this.deferredConnectionPromise&&this.rejectDeferredPromise(this.deferredConnectionPromise,new Error("Client closed while connecting")),this.connection){let e=this.connection;delete this.connection,await e.close()}else this.dispatchEvent(new u);this.socket&&(this.socket.removeAllListeners(),this.socket.destroy(),this.socket=void 0)}};import{sleep as B}from"@medplum/core";import R from"node:net";var Q=1e4,A=class{constructor(i){o(this,"handler");o(this,"server");o(this,"encoding");o(this,"enhancedMode");o(this,"messagesPerMin");o(this,"connections",new Set);this.handler=i}async start(i,e,t,s){e&&this.setEncoding(e),t!==void 0&&this.setEnhancedMode(t),s?.messagesPerMin!==void 0&&this.setMessagesPerMin(s.messagesPerMin);let n=R.createServer(c=>{let r=new g(c,this.encoding,this.enhancedMode,{messagesPerMin:this.messagesPerMin});this.handler(r),this.connections.add(r),r.addEventListener("close",()=>{this.connections.delete(r)})});await new Promise(c=>{let r=h=>{n.listen(h,c)},a=async h=>{h?.code==="EADDRINUSE"&&(await B(50),n.close(),r(i))};n.on("error",a),n.once("listening",()=>{n.off("error",a)}),r(i),this.server=n})}async stop(i){return new Promise((e,t)=>{if(!this.server){t(new Error("Stop was called but there is no server running"));return}let s;i?.forceDrainTimeoutMs!==-1&&(s=setTimeout(()=>{for(let n of this.connections)n.close().catch(console.error)},i?.forceDrainTimeoutMs??Q)),this.server.close(n=>{if(n){t(n);return}s&&clearTimeout(s),this.connections.clear(),this.server=void 0,e()})})}setEnhancedMode(i){this.enhancedMode=i}getEnhancedMode(){return this.enhancedMode}setEncoding(i){this.encoding=i}getEncoding(){return this.encoding}setMessagesPerMin(i){this.messagesPerMin=i}getMessagesPerMin(){return this.messagesPerMin}};export{C as CR,y as DEFAULT_ENCODING,Q as DEFAULT_FORCE_DRAIN_TIMEOUT_MS,H as FS,m as Hl7Base,S as Hl7Client,u as Hl7CloseEvent,g as Hl7Connection,M as Hl7EnhancedAckSentEvent,l as Hl7ErrorEvent,v as Hl7MessageEvent,A as Hl7Server,V as ReturnAckCategory,k as VT};
1
+ var L=Object.defineProperty;var O=(d,r,e)=>r in d?L(d,r,{enumerable:!0,configurable:!0,writable:!0,value:e}):d[r]=e;var o=(d,r,e)=>O(d,typeof r!="symbol"?r+"":r,e);var g=class extends EventTarget{addEventListener(r,e,n){super.addEventListener(r,e,n)}removeEventListener(r,e,n){super.removeEventListener(r,e,n)}};import k from"node:assert";import{connect as R}from"node:net";import{Hl7Message as T,OperationOutcomeError as v,ReturnAckCategory as w,sleep as D,validationError as I}from"@medplum/core";import y from"iconv-lite";var C=11,H=13,P=28;var M=class extends Event{constructor(e,n){super("message");o(this,"connection");o(this,"message");this.connection=e,this.message=n}},E=class extends Event{constructor(e,n){super("enhancedAckSent");o(this,"connection");o(this,"message");this.connection=e,this.message=n}},h=class extends Event{constructor(e){super("error");o(this,"error");this.error=e}},m=class extends Event{constructor(e){super("warning");o(this,"error");this.error=e}},u=class extends Event{constructor(){super("close")}};import{ReturnAckCategory as X}from"@medplum/core";var S="utf-8",B=60*1e3,f=class extends g{constructor(e,n=S,s,t={}){super();o(this,"socket");o(this,"encoding");o(this,"enhancedMode");o(this,"messagesPerMin");o(this,"chunks",[]);o(this,"pendingMessages",new Map);o(this,"responseQueue",[]);o(this,"lastMessageDispatchedTime",0);o(this,"responseQueueProcessing",!1);this.socket=e,this.encoding=n,this.enhancedMode=s,this.messagesPerMin=t.messagesPerMin,e.on("data",c=>{try{this.appendData(c);let i=this.parseMessages();for(let a of i)this.responseQueue.push(new M(this,a));this.processResponseQueue().catch(a=>{this.dispatchEvent(new h(a))})}catch(i){this.dispatchEvent(new h(i))}}),e.on("error",c=>{this.resetBuffer(),this.dispatchEvent(new h(c))}),e.on("close",()=>{this.drainPendingMessages(),this.dispatchEvent(new u)}),this.addEventListener("message",c=>{let i;this.enhancedMode==="standard"?i=c.message.buildAck({ackCode:"CA"}):this.enhancedMode==="aaMode"&&(i=c.message.buildAck({ackCode:"AA"})),i&&(this.send(i),this.dispatchEvent(new E(this,i)));let a=c.message.getSegment("MSA")?.getField(2)?.toString();if(!a)return;let p=this.pendingMessages.get(a);if(!p){this.dispatchEvent(new m(new v({resourceType:"OperationOutcome",issue:[{severity:"warning",code:"not-found",details:{text:"Response received for unknown message control ID"},diagnostics:`Received ACK for message control ID '${a}' but there was no pending message with this control ID`}]})));return}let l=c.message.getSegment("MSA")?.getField(1)?.toString()?.toUpperCase();l&&(p.returnAck===w.APPLICATION&&l==="CA"||(p.resolve(c.message),this.pendingMessages.delete(a)))})}isClosed(){return this.socket.closed}sendImpl(e){let n=e.toString(),s=y.encode(n,this.encoding),t=Buffer.alloc(s.length+3);t.writeInt8(11,0),s.copy(t,1),t.writeInt8(28,s.length+1),t.writeInt8(13,s.length+2),this.socket.write(t)}async processResponseQueue(){if(!this.responseQueueProcessing){for(this.responseQueueProcessing=!0;this.responseQueue.length;){if(this.messagesPerMin){let n=B/this.messagesPerMin,s=Date.now()-this.lastMessageDispatchedTime;n>s&&await D(n-s)}let e=this.responseQueue.shift();e&&this.dispatchEvent(e),this.lastMessageDispatchedTime=Date.now()}this.responseQueueProcessing=!1}}parseMessages(){let e=[],n=Buffer.concat(this.chunks);if(this.resetBuffer(),n.length===0)return e;let s=0;for(;s<n.length;){for(;n[s]!==11&&s<n.length;)s++;let t=-1;for(let l=s+1;l<n.length-1;l++)if(n[l]===28&&n[l+1]===13){t=l+1;break}if(t===-1)break;let i=n.subarray(s,t+1).subarray(1,-2),a=y.decode(i,this.encoding),p=T.parse(a);e.push(p),s=t+1}return this.chunks=s<n.length?[n.subarray(s)]:[],e}send(e){this.sendImpl(e)}async sendAndWait(e,n){return new Promise((s,t)=>{let c=e.getSegment("MSH")?.getField(10)?.toString();if(!c){t(new v(I("Required field missing: MSH.10")));return}let i;n?.timeoutMs&&(i=setTimeout(()=>{this.pendingMessages.delete(c),t(new v({resourceType:"OperationOutcome",issue:[{severity:"error",code:"timeout",details:{text:"Client timeout"},diagnostics:`Request timed out after waiting ${n.timeoutMs} milliseconds for response`}]}))},n.timeoutMs)),this.pendingMessages.set(c,{message:e,resolve:s,reject:t,returnAck:n?.returnAck??w.APPLICATION,timer:i}),this.sendImpl(e)})}async close(){this.isClosed()||(this.socket.end(),this.socket.destroy(),this.drainPendingMessages(),await new Promise(e=>{this.socket.once("close",e)}))}drainPendingMessages(){if(!this.pendingMessages.size)return;let e=this.pendingMessages.size;for(let n of this.pendingMessages.values())n.timer&&clearTimeout(n.timer),n.reject(new v({resourceType:"OperationOutcome",issue:[{severity:"warning",code:"incomplete",details:{text:"Message was still pending when connection closed"}}]}));this.dispatchEvent(new h(new v({resourceType:"OperationOutcome",issue:[{severity:"warning",code:"incomplete",details:{text:"Messages were still pending when connection closed"},diagnostics:`Hl7Connection closed while ${e} messages were pending`}]}))),this.pendingMessages.clear()}appendData(e){this.chunks.push(e)}resetBuffer(){this.chunks=[]}setEncoding(e){this.encoding=e??S}getEncoding(){return this.encoding}setEnhancedMode(e){this.enhancedMode=e}getEnhancedMode(){return this.enhancedMode}setMessagesPerMin(e){this.messagesPerMin=e}getMessagesPerMin(){return this.messagesPerMin}getPendingMessageCount(){return this.pendingMessages.size}};var A=class extends g{constructor(e){super();o(this,"options");o(this,"host");o(this,"port");o(this,"encoding");o(this,"connection");o(this,"keepAlive");o(this,"socket");o(this,"connectTimeout");o(this,"deferredConnectionPromise");this.options=e,this.host=this.options.host,this.port=this.options.port,this.encoding=this.options.encoding,this.keepAlive=this.options.keepAlive??!1,this.connectTimeout=this.options.connectTimeout??3e4}connect(){if(this.deferredConnectionPromise)return this.deferredConnectionPromise.promise;let e=this.deferredConnectionPromise=this.createDeferredConnectionPromise();return this.socket=R({host:this.host,port:this.port,keepAlive:this.keepAlive}),this.connectTimeout>0&&(this.socket.setTimeout(this.connectTimeout),this.registerSocketTimeoutListener(e)),this.registerSocketConnectListener(e),this.registerSocketErrorListener(e),this.registerSocketCloseListener(e),e.promise}registerSocketTimeoutListener(e){k(this.socket);let n=this.socket,s=()=>{this.cleanupSocket(n);let t=new Error(`Connection timeout after ${this.connectTimeout}ms`);this.rejectDeferredPromise(e,t)};n.on("timeout",s)}registerSocketConnectListener(e){k(this.socket);let n=this.socket,s=()=>{if(n!==this.socket){this.cleanupSocket(n);return}let t;this.connection=t=new f(n,this.encoding),n.setTimeout(0),this.registerHl7ConnectionListeners(t),e.resolve(t)};n.on("connect",s)}registerSocketErrorListener(e){k(this.socket);let n=this.socket,s=t=>{this.cleanupSocket(n),t.constructor.name==="AggregateError"?this.rejectDeferredPromise(e,t.errors[0]):this.rejectDeferredPromise(e,t)};n.on("error",s)}registerSocketCloseListener(e){k(this.socket);let n=this.socket,s=()=>{this.cleanupSocket(n),this.rejectDeferredPromise(e,new Error("Socket closed before connection finished"))};n.on("close",s)}registerHl7ConnectionListeners(e){e.addEventListener("close",()=>{this.socket=void 0,this.connection=void 0,this.deferredConnectionPromise=void 0,this.dispatchEvent(new u)}),e.addEventListener("error",n=>{this.dispatchEvent(new h(n.error))}),e.addEventListener("warning",n=>{this.dispatchEvent(new m(n.error))})}createDeferredConnectionPromise(){let e,n;return{promise:new Promise((t,c)=>{e=t,n=c}),resolve:e,reject:n}}rejectDeferredPromise(e,n){e.reject(n),this.deferredConnectionPromise===e&&(this.deferredConnectionPromise=void 0)}cleanupSocket(e){e.destroyed||e.destroy(),e===this.socket&&(this.socket=void 0)}async send(e){return(await this.connect()).send(e)}async sendAndWait(e,n){return(await this.connect()).sendAndWait(e,n)}async close(){if(this.deferredConnectionPromise&&this.rejectDeferredPromise(this.deferredConnectionPromise,new Error("Client closed while connecting")),this.connection){let e=this.connection;delete this.connection,await e.close()}else this.dispatchEvent(new u);this.socket&&(this.socket.removeAllListeners(),this.socket.destroy(),this.socket=void 0)}};import{sleep as Q}from"@medplum/core";import j from"node:net";var N=1e4,x=class{constructor(r){o(this,"handler");o(this,"server");o(this,"encoding");o(this,"enhancedMode");o(this,"messagesPerMin");o(this,"connections",new Set);this.handler=r}async start(r,e,n,s){e&&this.setEncoding(e),n!==void 0&&this.setEnhancedMode(n),s?.messagesPerMin!==void 0&&this.setMessagesPerMin(s.messagesPerMin);let t=j.createServer(c=>{let i=new f(c,this.encoding,this.enhancedMode,{messagesPerMin:this.messagesPerMin});this.handler(i),this.connections.add(i),i.addEventListener("close",()=>{this.connections.delete(i)})});return new Promise((c,i)=>{let a=l=>{t.listen(l,()=>{let b=t.address().port;c(b)})},p=l=>{l?.code==="EADDRINUSE"?t.close(()=>Q(50).then(()=>a(r))):i(l)};t.on("error",p),t.once("listening",()=>{t.off("error",p)}),a(r),this.server=t})}async stop(r){return new Promise((e,n)=>{if(!this.server){n(new Error("Stop was called but there is no server running"));return}let s;r?.forceDrainTimeoutMs!==-1&&(s=setTimeout(()=>{for(let t of this.connections)t.close().catch(console.error)},r?.forceDrainTimeoutMs??N)),this.server.close(t=>{if(t){n(t);return}s&&clearTimeout(s),this.connections.clear(),this.server=void 0,e()})})}setEnhancedMode(r){this.enhancedMode=r}getEnhancedMode(){return this.enhancedMode}setEncoding(r){this.encoding=r}getEncoding(){return this.encoding}setMessagesPerMin(r){this.messagesPerMin=r}getMessagesPerMin(){return this.messagesPerMin}};export{H as CR,S as DEFAULT_ENCODING,N as DEFAULT_FORCE_DRAIN_TIMEOUT_MS,P as FS,g as Hl7Base,A as Hl7Client,u as Hl7CloseEvent,f as Hl7Connection,E as Hl7EnhancedAckSentEvent,h as Hl7ErrorEvent,M as Hl7MessageEvent,x as Hl7Server,m as Hl7WarningEvent,X as ReturnAckCategory,C as VT};
2
2
  //# sourceMappingURL=index.mjs.map
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/base.ts", "../../src/client.ts", "../../src/connection.ts", "../../src/constants.ts", "../../src/events.ts", "../../src/server.ts"],
4
- "sourcesContent": ["// SPDX-FileCopyrightText: Copyright Orangebot, Inc. and Medplum contributors\n// SPDX-License-Identifier: Apache-2.0\nimport type { Hl7CloseEvent, Hl7EnhancedAckSentEvent, Hl7ErrorEvent, Hl7MessageEvent } from './events';\n\nexport interface Hl7EventMap {\n message: Hl7MessageEvent;\n error: Hl7ErrorEvent;\n close: Hl7CloseEvent;\n enhancedAckSent: Hl7EnhancedAckSentEvent;\n}\n\nexport abstract class Hl7Base extends EventTarget {\n addEventListener<K extends keyof Hl7EventMap>(\n type: K,\n listener: ((event: Hl7EventMap[K]) => void) | EventListenerObject | null,\n options?: boolean | AddEventListenerOptions\n ): void;\n\n addEventListener(\n type: string,\n listener: EventListenerOrEventListenerObject | null,\n options?: boolean | AddEventListenerOptions\n ): void {\n super.addEventListener(type, listener, options);\n }\n removeEventListener<K extends keyof Hl7EventMap>(\n type: K,\n listener: ((event: Hl7EventMap[K]) => void) | EventListenerObject | null,\n options?: boolean | AddEventListenerOptions\n ): void;\n\n removeEventListener(\n type: string,\n listener: EventListenerOrEventListenerObject | null,\n options?: boolean | AddEventListenerOptions\n ): void {\n super.removeEventListener(type, listener, options);\n }\n}\n", "// SPDX-FileCopyrightText: Copyright Orangebot, Inc. and Medplum contributors\n// SPDX-License-Identifier: Apache-2.0\nimport type { Hl7Message } from '@medplum/core';\nimport assert from 'node:assert';\nimport type { Socket } from 'node:net';\nimport { connect } from 'node:net';\nimport { Hl7Base } from './base';\nimport type { SendAndWaitOptions } from './connection';\nimport { Hl7Connection } from './connection';\nimport { Hl7CloseEvent, Hl7ErrorEvent } from './events';\n\nexport interface Hl7ClientOptions {\n host: string;\n port: number;\n encoding?: string;\n keepAlive?: boolean;\n connectTimeout?: number; // Add timeout option\n}\n\nexport interface DeferredConnectionPromise {\n promise: Promise<Hl7Connection>;\n resolve: (connection: Hl7Connection) => void;\n reject: (err: Error) => void;\n}\n\nexport class Hl7Client extends Hl7Base {\n options: Hl7ClientOptions;\n host: string;\n port: number;\n encoding?: string;\n connection?: Hl7Connection;\n keepAlive: boolean;\n private socket?: Socket;\n private connectTimeout: number;\n private deferredConnectionPromise?: DeferredConnectionPromise;\n\n constructor(options: Hl7ClientOptions) {\n super();\n this.options = options;\n this.host = this.options.host;\n this.port = this.options.port;\n this.encoding = this.options.encoding;\n this.keepAlive = this.options.keepAlive ?? false;\n this.connectTimeout = this.options.connectTimeout ?? 30000; // Default 30 seconds\n }\n\n connect(): Promise<Hl7Connection> {\n // If we are already waiting for a pending connection attempt, just return the deferred promise to that\n // In the case that the promise is already resolve, we will also return a resolved connection\n if (this.deferredConnectionPromise) {\n return this.deferredConnectionPromise.promise;\n }\n\n const deferredPromise = (this.deferredConnectionPromise = this.createDeferredConnectionPromise());\n\n // Create the socket\n this.socket = connect({\n host: this.host,\n port: this.port,\n keepAlive: this.keepAlive,\n });\n\n if (this.connectTimeout > 0) {\n this.socket.setTimeout(this.connectTimeout);\n this.registerSocketTimeoutListener(deferredPromise);\n }\n\n this.registerSocketConnectListener(deferredPromise);\n this.registerSocketErrorListener(deferredPromise);\n this.registerSocketCloseListener(deferredPromise);\n\n return deferredPromise.promise;\n }\n\n private registerSocketTimeoutListener(deferredPromise: DeferredConnectionPromise): void {\n assert(this.socket);\n const socket = this.socket;\n\n // Handle timeout event\n const timeoutListener = (): void => {\n this.cleanupSocket(socket);\n const error = new Error(`Connection timeout after ${this.connectTimeout}ms`);\n this.rejectDeferredPromise(deferredPromise, error);\n };\n\n socket.on('timeout', timeoutListener);\n }\n\n private registerSocketConnectListener(deferredPromise: DeferredConnectionPromise): void {\n assert(this.socket);\n const socket = this.socket;\n\n // Handle successful connection\n const connectListener = (): void => {\n if (socket !== this.socket) {\n this.cleanupSocket(socket);\n return;\n }\n\n // Create the HL7 connection\n let connection: Hl7Connection;\n this.connection = connection = new Hl7Connection(socket, this.encoding);\n\n // Remove the timeout listener as we're now connected\n socket.setTimeout(0);\n\n this.registerHl7ConnectionListeners(connection);\n\n deferredPromise.resolve(connection);\n };\n\n socket.on('connect', connectListener);\n }\n\n private registerSocketErrorListener(deferredPromise: DeferredConnectionPromise): void {\n assert(this.socket);\n const socket = this.socket;\n\n // Handle connection errors\n const errorListener = (err: Error | AggregateError): void => {\n this.cleanupSocket(socket);\n\n if (err.constructor.name === 'AggregateError') {\n this.rejectDeferredPromise(deferredPromise, (err as AggregateError).errors[0]);\n } else {\n this.rejectDeferredPromise(deferredPromise, err);\n }\n };\n\n socket.on('error', errorListener);\n }\n\n private registerSocketCloseListener(deferredPromise: DeferredConnectionPromise): void {\n assert(this.socket);\n const socket = this.socket;\n\n // Handle connection errors\n const closeListener = (): void => {\n this.cleanupSocket(socket);\n this.rejectDeferredPromise(deferredPromise, new Error('Socket closed before connection finished'));\n };\n socket.on('close', closeListener);\n }\n\n private registerHl7ConnectionListeners(connection: Hl7Connection): void {\n // Set up event handlers\n connection.addEventListener('close', () => {\n this.socket = undefined;\n this.connection = undefined;\n this.deferredConnectionPromise = undefined;\n this.dispatchEvent(new Hl7CloseEvent());\n });\n\n connection.addEventListener('error', (event) => {\n this.dispatchEvent(new Hl7ErrorEvent(event.error));\n });\n }\n\n private createDeferredConnectionPromise(): DeferredConnectionPromise {\n // Setup our deferred connection promise\n let resolve!: (connection: Hl7Connection) => void;\n let reject!: (err: Error) => void;\n\n const promise = new Promise<Hl7Connection>((_resolve, _reject) => {\n resolve = _resolve;\n reject = _reject;\n });\n\n return {\n promise,\n resolve,\n reject,\n };\n }\n\n private rejectDeferredPromise(deferredPromise: DeferredConnectionPromise, err: Error): void {\n // Reject this deferred promise with the given error\n deferredPromise.reject(err);\n\n // If the currently tracked deferred promise is this deferred promise, remove it from the client\n if (this.deferredConnectionPromise === deferredPromise) {\n this.deferredConnectionPromise = undefined;\n }\n }\n\n private cleanupSocket(socket: Socket): void {\n if (!socket.destroyed) {\n socket.destroy();\n }\n if (socket === this.socket) {\n this.socket = undefined;\n }\n }\n\n async send(msg: Hl7Message): Promise<void> {\n return (await this.connect()).send(msg);\n }\n\n async sendAndWait(msg: Hl7Message, options?: SendAndWaitOptions): Promise<Hl7Message> {\n return (await this.connect()).sendAndWait(msg, options);\n }\n\n async close(): Promise<void> {\n if (this.deferredConnectionPromise) {\n this.rejectDeferredPromise(this.deferredConnectionPromise, new Error('Client closed while connecting'));\n }\n\n // Close established connection if it exists\n if (this.connection) {\n const connection = this.connection;\n delete this.connection;\n await connection.close();\n } else {\n // Emit close event because the connection will not be able to emit it for us\n // Since it has not connected at this point\n this.dispatchEvent(new Hl7CloseEvent());\n }\n // Close the socket if it exists\n if (this.socket) {\n this.socket.removeAllListeners();\n this.socket.destroy();\n this.socket = undefined;\n }\n }\n}\n", "// SPDX-FileCopyrightText: Copyright Orangebot, Inc. and Medplum contributors\n// SPDX-License-Identifier: Apache-2.0\nimport { Hl7Message, OperationOutcomeError, ReturnAckCategory, sleep, validationError } from '@medplum/core';\nimport iconv from 'iconv-lite';\nimport type net from 'node:net';\nimport { Hl7Base } from './base';\nimport { CR, FS, VT } from './constants';\nimport { Hl7CloseEvent, Hl7EnhancedAckSentEvent, Hl7ErrorEvent, Hl7MessageEvent } from './events';\n\n// Export `ReturnAckCategory` for backwards-compat\nexport { ReturnAckCategory } from '@medplum/core';\n\n// iconv-lite docs have great examples and explanations for how to use Buffers with iconv-lite:\n// See: https://github.com/ashtuchkin/iconv-lite/wiki/Use-Buffers-when-decoding\n\nexport type Hl7MessageQueueItem = {\n message: Hl7Message;\n resolve: (reply: Hl7Message) => void;\n reject: (err: Error) => void;\n returnAck: ReturnAckCategory;\n timer?: NodeJS.Timeout;\n};\n\nexport interface SendAndWaitOptions {\n /** The ACK-level that the Promise should resolve on. The default is `ReturnAckCategory.APPLICATION` (returns on the first application-level ACK). */\n returnAck?: ReturnAckCategory;\n /** The amount of milliseconds to wait before timing out when waiting for the response to a message. */\n timeoutMs?: number;\n}\n\nexport interface Hl7ConnectionOptions {\n messagesPerMin?: number;\n}\n\n/**\n * Enhanced mode for HL7 connections.\n * - `'standard'`: Standard enhanced mode behavior\n * - `'aaMode'`: AA mode - special enhanced mode that only accepts AA acknowledgements\n * - `undefined`: Enhanced mode is not enabled (standard behavior)\n */\nexport type EnhancedMode = 'standard' | 'aaMode' | undefined;\n\nexport const DEFAULT_ENCODING = 'utf-8';\nconst ONE_MINUTE = 60 * 1000;\n\nexport class Hl7Connection extends Hl7Base {\n readonly socket: net.Socket;\n encoding: string;\n enhancedMode: EnhancedMode = undefined;\n private messagesPerMin: number | undefined = undefined;\n private chunks: Buffer[] = [];\n private readonly pendingMessages: Map<string, Hl7MessageQueueItem> = new Map<string, Hl7MessageQueueItem>();\n private readonly responseQueue: Hl7MessageEvent[] = [];\n private lastMessageDispatchedTime = 0;\n private responseQueueProcessing = false;\n\n constructor(\n socket: net.Socket,\n encoding: string = DEFAULT_ENCODING,\n enhancedMode?: EnhancedMode,\n options: Hl7ConnectionOptions = {}\n ) {\n super();\n\n this.socket = socket;\n this.encoding = encoding;\n this.enhancedMode = enhancedMode;\n this.messagesPerMin = options.messagesPerMin;\n\n socket.on('data', (data: Buffer) => {\n try {\n this.appendData(data);\n const messages = this.parseMessages();\n for (const message of messages) {\n this.responseQueue.push(new Hl7MessageEvent(this, message));\n }\n this.processResponseQueue().catch((err) => {\n this.dispatchEvent(new Hl7ErrorEvent(err));\n });\n } catch (err) {\n this.dispatchEvent(new Hl7ErrorEvent(err as Error));\n }\n });\n\n socket.on('error', (err) => {\n this.resetBuffer();\n this.dispatchEvent(new Hl7ErrorEvent(err));\n });\n\n // The difference between \"end\" and \"close\", is that \"end\" is only emitted on half-close from the other side\n // If the connection from the other side does not close gracefully, but instead we destroy the socket, then the Hl7Connection will not emit close\n // if we listen only for \"end\"; \"close\" is always emitted, whether the close is graceful or forceful\n socket.on('close', () => {\n this.dispatchEvent(new Hl7CloseEvent());\n });\n\n this.addEventListener('message', (event) => {\n // In standard enhanced mode, send commit ACK (CA) immediately, then later forward app-level ACKs\n // In aaMode, send application ACK (AA) immediately, then ignore any later app-level ACKs\n let response: Hl7Message | undefined;\n if (this.enhancedMode === 'standard') {\n response = event.message.buildAck({ ackCode: 'CA' });\n } else if (this.enhancedMode === 'aaMode') {\n response = event.message.buildAck({ ackCode: 'AA' });\n }\n if (response) {\n this.send(response);\n this.dispatchEvent(new Hl7EnhancedAckSentEvent(this, response));\n }\n const origMsgCtrlId = event.message.getSegment('MSA')?.getField(2)?.toString();\n // If there is no message control ID, just return\n if (!origMsgCtrlId) {\n return;\n }\n const queueItem = this.pendingMessages.get(origMsgCtrlId);\n if (!queueItem) {\n this.dispatchEvent(\n new Hl7ErrorEvent(\n new OperationOutcomeError({\n resourceType: 'OperationOutcome',\n issue: [\n {\n severity: 'warning',\n code: 'not-found',\n details: {\n text: 'Response received for unknown message control ID',\n },\n diagnostics: `Received ACK for message control ID '${origMsgCtrlId}' but there was no pending message with this control ID`,\n },\n ],\n })\n )\n );\n return;\n }\n // Check the ACK type we should return on\n const ackCode = event.message.getSegment('MSA')?.getField(1)?.toString()?.toUpperCase();\n if (!ackCode) {\n return;\n }\n\n // Two modes:\n // Application-level or first ACK\n\n // First should always return on any ACK message, this is the default\n // The exception is APPLICATION, which should not resolve when the ACK is a CA, but should resolve on all other ACK types\n // On CA, we return early\n if (queueItem.returnAck === ReturnAckCategory.APPLICATION && ackCode === 'CA') {\n return;\n }\n\n // Resolve the promise if there is one pending for this message and we didn't exit already because the ACK type matches\n queueItem.resolve(event.message);\n this.pendingMessages.delete(origMsgCtrlId);\n });\n }\n\n /** @returns A boolean representing whether the socket attached to this Hl7Connection has emitted the close event already or not. */\n isClosed(): boolean {\n return this.socket.closed;\n }\n\n private sendImpl(reply: Hl7Message): void {\n const replyString = reply.toString();\n const replyBuffer = iconv.encode(replyString, this.encoding);\n const outputBuffer = Buffer.alloc(replyBuffer.length + 3);\n outputBuffer.writeInt8(VT, 0);\n replyBuffer.copy(outputBuffer, 1);\n outputBuffer.writeInt8(FS, replyBuffer.length + 1);\n outputBuffer.writeInt8(CR, replyBuffer.length + 2);\n this.socket.write(outputBuffer);\n }\n\n private async processResponseQueue(): Promise<void> {\n if (this.responseQueueProcessing) {\n return;\n }\n\n this.responseQueueProcessing = true;\n while (this.responseQueue.length) {\n if (this.messagesPerMin) {\n const millisBetweenMsgs = ONE_MINUTE / this.messagesPerMin;\n const elapsedMillis = Date.now() - this.lastMessageDispatchedTime;\n if (millisBetweenMsgs > elapsedMillis) {\n await sleep(millisBetweenMsgs - elapsedMillis);\n }\n }\n const messageEvent = this.responseQueue.shift() as Hl7MessageEvent;\n if (messageEvent) {\n this.dispatchEvent(messageEvent);\n }\n this.lastMessageDispatchedTime = Date.now();\n }\n this.responseQueueProcessing = false;\n }\n\n /**\n * Parses complete HL7 messages from the accumulated buffer.\n * Continues parsing while the buffer starts with VT and contains FS+CR.\n * Keeps any incomplete message data in the buffer for the next chunk.\n * @returns An array of parsed HL7 messages.\n */\n private parseMessages(): Hl7Message[] {\n const messages: Hl7Message[] = [];\n const buffer = Buffer.concat(this.chunks);\n this.resetBuffer();\n\n // Check if buffer starts with VT (Vertical Tab)\n if (buffer.length === 0) {\n return messages;\n }\n\n let bufferIdx = 0;\n\n // Keep parsing while we have complete messages\n while (bufferIdx < buffer.length) {\n // Ignore bytes between message frames\n while (buffer[bufferIdx] !== VT && bufferIdx < buffer.length) {\n bufferIdx++;\n }\n\n // Look for FS+CR sequence to mark end of message\n let messageEndIndex = -1;\n\n for (let i = bufferIdx + 1; i < buffer.length - 1; i++) {\n if (buffer[i] === FS && buffer[i + 1] === CR) {\n messageEndIndex = i + 1; // Index of CR (end of message)\n break;\n }\n }\n\n // If we don't have a complete message yet, wait for more data\n if (messageEndIndex === -1) {\n break;\n }\n\n // Extract the complete message (including VT, FS, and CR)\n const messageBuffer = buffer.subarray(bufferIdx, messageEndIndex + 1);\n // Extract the content (without VT at start and FS+CR at end)\n const contentBuffer = messageBuffer.subarray(1, -2);\n const contentString = iconv.decode(contentBuffer, this.encoding);\n const message = Hl7Message.parse(contentString);\n\n messages.push(message);\n\n // Move past this message\n bufferIdx = messageEndIndex + 1;\n }\n\n // Keep any remaining unfinished chunk in this.chunks\n this.chunks = bufferIdx < buffer.length ? [buffer.subarray(bufferIdx)] : [];\n\n return messages;\n }\n\n send(reply: Hl7Message): void {\n this.sendImpl(reply);\n }\n\n async sendAndWait(msg: Hl7Message, options?: SendAndWaitOptions): Promise<Hl7Message> {\n return new Promise<Hl7Message>((resolve, reject) => {\n const msgCtrlId = msg.getSegment('MSH')?.getField(10)?.toString();\n if (!msgCtrlId) {\n reject(new OperationOutcomeError(validationError('Required field missing: MSH.10')));\n return;\n }\n\n let timer: NodeJS.Timeout | undefined;\n\n if (options?.timeoutMs) {\n timer = setTimeout(() => {\n this.pendingMessages.delete(msgCtrlId);\n reject(\n new OperationOutcomeError({\n resourceType: 'OperationOutcome',\n issue: [\n {\n severity: 'error',\n code: 'timeout',\n details: {\n text: 'Client timeout',\n },\n diagnostics: `Request timed out after waiting ${options.timeoutMs} milliseconds for response`,\n },\n ],\n })\n );\n }, options.timeoutMs);\n }\n\n this.pendingMessages.set(msgCtrlId, {\n message: msg,\n resolve,\n reject,\n returnAck: options?.returnAck ?? ReturnAckCategory.APPLICATION,\n timer,\n });\n this.sendImpl(msg);\n });\n }\n\n async close(): Promise<void> {\n // If we have already received the close event, then we can just return immediately\n if (this.isClosed()) {\n return;\n }\n this.socket.end();\n this.socket.destroy();\n // Before clearing out messages, we should propagate a message to the consumer that we are closing the connection while some messages were still pending a response\n if (this.pendingMessages.size) {\n for (const queueItem of this.pendingMessages.values()) {\n if (queueItem.timer) {\n clearTimeout(queueItem.timer);\n }\n queueItem.reject(\n new OperationOutcomeError({\n resourceType: 'OperationOutcome',\n issue: [\n {\n severity: 'warning',\n code: 'incomplete',\n details: {\n text: 'Message was still pending when connection closed',\n },\n },\n ],\n })\n );\n }\n this.dispatchEvent(\n new Hl7ErrorEvent(\n new OperationOutcomeError({\n resourceType: 'OperationOutcome',\n issue: [\n {\n severity: 'warning',\n code: 'incomplete',\n details: {\n text: 'Messages were still pending when connection closed',\n },\n diagnostics: `Hl7Connection closed while ${this.pendingMessages.size} messages were pending`,\n },\n ],\n })\n )\n );\n // Clear out any pending messages\n this.pendingMessages.clear();\n }\n await new Promise((resolve) => {\n // Register a temporary listener to help resolve the promise once close has been emitted\n this.socket.once('close', resolve);\n });\n }\n\n private appendData(data: Buffer): void {\n this.chunks.push(data);\n }\n\n private resetBuffer(): void {\n this.chunks = [];\n }\n\n setEncoding(encoding: string | undefined): void {\n this.encoding = encoding ?? DEFAULT_ENCODING;\n }\n\n getEncoding(): string {\n return this.encoding;\n }\n\n setEnhancedMode(enhancedMode: EnhancedMode): void {\n this.enhancedMode = enhancedMode;\n }\n\n getEnhancedMode(): EnhancedMode {\n return this.enhancedMode;\n }\n\n setMessagesPerMin(messagesPerMin: number | undefined): void {\n this.messagesPerMin = messagesPerMin;\n }\n\n getMessagesPerMin(): number | undefined {\n return this.messagesPerMin;\n }\n\n getPendingMessageCount(): number {\n return this.pendingMessages.size;\n }\n}\n", "// SPDX-FileCopyrightText: Copyright Orangebot, Inc. and Medplum contributors\n// SPDX-License-Identifier: Apache-2.0\n\n/**\n * VT (Vertical Tab) character.\n *\n * In HL7 messages, this character is used to indicate the start of a message.\n */\nexport const VT = 0x0b;\n\n/**\n * CR (Carriage Return) character.\n *\n * In HL7 messages, this character is used to indicate the end of a message.\n */\nexport const CR = 0x0d;\n\n/**\n * FS (File Separator) character.\n *\n * In HL7 messages, this character is used to separate fields.\n */\nexport const FS = 0x1c;\n", "// SPDX-FileCopyrightText: Copyright Orangebot, Inc. and Medplum contributors\n// SPDX-License-Identifier: Apache-2.0\nimport type { Hl7Message } from '@medplum/core';\nimport type { Hl7Connection } from './connection';\n\nexport class Hl7MessageEvent extends Event {\n readonly connection: Hl7Connection;\n readonly message: Hl7Message;\n\n constructor(connection: Hl7Connection, message: Hl7Message) {\n super('message');\n this.connection = connection;\n this.message = message;\n }\n}\n\nexport class Hl7EnhancedAckSentEvent extends Event {\n readonly connection: Hl7Connection;\n readonly message: Hl7Message;\n\n constructor(connection: Hl7Connection, message: Hl7Message) {\n super('enhancedAckSent');\n this.connection = connection;\n this.message = message;\n }\n}\n\nexport class Hl7ErrorEvent extends Event {\n readonly error: Error;\n\n constructor(error: Error) {\n super('error');\n this.error = error;\n }\n}\n\nexport class Hl7CloseEvent extends Event {\n constructor() {\n super('close');\n }\n}\n", "// SPDX-FileCopyrightText: Copyright Orangebot, Inc. and Medplum contributors\n// SPDX-License-Identifier: Apache-2.0\nimport { sleep } from '@medplum/core';\nimport net from 'node:net';\nimport type { EnhancedMode, Hl7ConnectionOptions } from './connection';\nimport { Hl7Connection } from './connection';\n\n/**\n * Options for configuring the `Hl7Server#stop` method.\n */\nexport interface Hl7ServerStopOptions {\n /**\n * Time in milliseconds to allow client connections to gracefully close after the stop method has been called, before forcefully closing them.\n *\n * Can be set to `-1` to disable forceful draining of connections during stop.\n *\n * Defaults to `10_000`.\n */\n forceDrainTimeoutMs?: number;\n}\n\nexport const DEFAULT_FORCE_DRAIN_TIMEOUT_MS = 10_000;\n\nexport class Hl7Server {\n readonly handler: (connection: Hl7Connection) => void;\n server?: net.Server;\n private encoding: string | undefined = undefined;\n private enhancedMode: EnhancedMode = undefined;\n private messagesPerMin: number | undefined = undefined;\n private readonly connections = new Set<Hl7Connection>();\n\n constructor(handler: (connection: Hl7Connection) => void) {\n this.handler = handler;\n }\n\n async start(\n port: number,\n encoding?: string,\n enhancedMode?: EnhancedMode,\n options?: Hl7ConnectionOptions\n ): Promise<void> {\n if (encoding) {\n this.setEncoding(encoding);\n }\n if (enhancedMode !== undefined) {\n this.setEnhancedMode(enhancedMode);\n }\n if (options?.messagesPerMin !== undefined) {\n this.setMessagesPerMin(options.messagesPerMin);\n }\n\n const server = net.createServer((socket) => {\n const connection = new Hl7Connection(socket, this.encoding, this.enhancedMode, {\n messagesPerMin: this.messagesPerMin,\n });\n this.handler(connection);\n this.connections.add(connection);\n connection.addEventListener('close', () => {\n this.connections.delete(connection);\n });\n });\n\n await new Promise<void>((resolve) => {\n const listenOnPort = (port: number): void => {\n server.listen(port, resolve);\n };\n\n // Node errors have a code\n const errorListener = async (e: Error & { code?: string }): Promise<void> => {\n if (e?.code === 'EADDRINUSE') {\n await sleep(50);\n server.close();\n listenOnPort(port);\n }\n };\n\n server.on('error', errorListener);\n\n server.once('listening', () => {\n server.off('error', errorListener);\n });\n\n listenOnPort(port);\n\n this.server = server;\n });\n }\n\n /**\n * Stops the HL7 server.\n *\n * By default, the server will stop accepting new connections after this method is called, and wait for current connections to close naturally.\n *\n * If all connections don't close within 10 seconds, the server will forcefully close them before shutting down.\n *\n * The default time to wait before forcefully closing connections can be changed by passing an integer value for the optional `options.forceDrainTimeoutMs`.\n *\n * Forced drain can also be disabled by passing `-1` for `options.forceDrainTimeoutMs`.\n * @param options - Optional options to configure the stopping of the server.\n * @returns Promise that resolves when the server has stopped, or rejects if an error prevents server from stopping.\n */\n async stop(options?: Hl7ServerStopOptions): Promise<void> {\n return new Promise<void>((resolve, reject) => {\n if (!this.server) {\n reject(new Error('Stop was called but there is no server running'));\n return;\n }\n let forceDrainTimeout: NodeJS.Timeout | undefined;\n if (options?.forceDrainTimeoutMs !== -1) {\n forceDrainTimeout = setTimeout(() => {\n for (const connection of this.connections) {\n // Theoretically close should almost never throw as most errors are caught internal to the method and emitted as error events\n // We put a .catch here to prevent floating promises and log any errors that somehow make it through\n connection.close().catch(console.error);\n }\n }, options?.forceDrainTimeoutMs ?? DEFAULT_FORCE_DRAIN_TIMEOUT_MS);\n }\n this.server.close((err) => {\n if (err) {\n reject(err);\n return;\n }\n if (forceDrainTimeout) {\n clearTimeout(forceDrainTimeout);\n }\n this.connections.clear();\n this.server = undefined;\n resolve();\n });\n });\n }\n\n setEnhancedMode(enhancedMode: EnhancedMode): void {\n this.enhancedMode = enhancedMode;\n }\n\n getEnhancedMode(): EnhancedMode {\n return this.enhancedMode;\n }\n\n setEncoding(encoding: string | undefined): void {\n this.encoding = encoding;\n }\n\n getEncoding(): string | undefined {\n return this.encoding;\n }\n\n setMessagesPerMin(messagesPerMin: number | undefined): void {\n this.messagesPerMin = messagesPerMin;\n }\n\n getMessagesPerMin(): number | undefined {\n return this.messagesPerMin;\n }\n}\n"],
5
- "mappings": "oKAWO,IAAeA,EAAf,cAA+B,WAAY,CAOhD,iBACEC,EACAC,EACAC,EACM,CACN,MAAM,iBAAiBF,EAAMC,EAAUC,CAAO,CAChD,CAOA,oBACEF,EACAC,EACAC,EACM,CACN,MAAM,oBAAoBF,EAAMC,EAAUC,CAAO,CACnD,CACF,ECnCA,OAAOC,MAAY,cAEnB,OAAS,WAAAC,MAAe,WCHxB,OAAS,cAAAC,EAAY,yBAAAC,EAAuB,qBAAAC,EAAmB,SAAAC,EAAO,mBAAAC,MAAuB,gBAC7F,OAAOC,MAAW,aCKX,IAAMC,EAAK,GAOLC,EAAK,GAOLC,EAAK,GCjBX,IAAMC,EAAN,cAA8B,KAAM,CAIzC,YAAYC,EAA2BC,EAAqB,CAC1D,MAAM,SAAS,EAJjBC,EAAA,KAAS,cACTA,EAAA,KAAS,WAIP,KAAK,WAAaF,EAClB,KAAK,QAAUC,CACjB,CACF,EAEaE,EAAN,cAAsC,KAAM,CAIjD,YAAYH,EAA2BC,EAAqB,CAC1D,MAAM,iBAAiB,EAJzBC,EAAA,KAAS,cACTA,EAAA,KAAS,WAIP,KAAK,WAAaF,EAClB,KAAK,QAAUC,CACjB,CACF,EAEaG,EAAN,cAA4B,KAAM,CAGvC,YAAYC,EAAc,CACxB,MAAM,OAAO,EAHfH,EAAA,KAAS,SAIP,KAAK,MAAQG,CACf,CACF,EAEaC,EAAN,cAA4B,KAAM,CACvC,aAAc,CACZ,MAAM,OAAO,CACf,CACF,EF9BA,OAAS,qBAAAC,MAAyB,gBAgC3B,IAAMC,EAAmB,QAC1BC,EAAa,GAAK,IAEXC,EAAN,cAA4BC,CAAQ,CAWzC,YACEC,EACAC,EAAmBL,EACnBM,EACAC,EAAgC,CAAC,EACjC,CACA,MAAM,EAhBRC,EAAA,KAAS,UACTA,EAAA,iBACAA,EAAA,qBACAA,EAAA,KAAQ,kBACRA,EAAA,KAAQ,SAAmB,CAAC,GAC5BA,EAAA,KAAiB,kBAAoD,IAAI,KACzEA,EAAA,KAAiB,gBAAmC,CAAC,GACrDA,EAAA,KAAQ,4BAA4B,GACpCA,EAAA,KAAQ,0BAA0B,IAUhC,KAAK,OAASJ,EACd,KAAK,SAAWC,EAChB,KAAK,aAAeC,EACpB,KAAK,eAAiBC,EAAQ,eAE9BH,EAAO,GAAG,OAASK,GAAiB,CAClC,GAAI,CACF,KAAK,WAAWA,CAAI,EACpB,IAAMC,EAAW,KAAK,cAAc,EACpC,QAAWC,KAAWD,EACpB,KAAK,cAAc,KAAK,IAAIE,EAAgB,KAAMD,CAAO,CAAC,EAE5D,KAAK,qBAAqB,EAAE,MAAOE,GAAQ,CACzC,KAAK,cAAc,IAAIC,EAAcD,CAAG,CAAC,CAC3C,CAAC,CACH,OAASA,EAAK,CACZ,KAAK,cAAc,IAAIC,EAAcD,CAAY,CAAC,CACpD,CACF,CAAC,EAEDT,EAAO,GAAG,QAAUS,GAAQ,CAC1B,KAAK,YAAY,EACjB,KAAK,cAAc,IAAIC,EAAcD,CAAG,CAAC,CAC3C,CAAC,EAKDT,EAAO,GAAG,QAAS,IAAM,CACvB,KAAK,cAAc,IAAIW,CAAe,CACxC,CAAC,EAED,KAAK,iBAAiB,UAAYC,GAAU,CAG1C,IAAIC,EACA,KAAK,eAAiB,WACxBA,EAAWD,EAAM,QAAQ,SAAS,CAAE,QAAS,IAAK,CAAC,EAC1C,KAAK,eAAiB,WAC/BC,EAAWD,EAAM,QAAQ,SAAS,CAAE,QAAS,IAAK,CAAC,GAEjDC,IACF,KAAK,KAAKA,CAAQ,EAClB,KAAK,cAAc,IAAIC,EAAwB,KAAMD,CAAQ,CAAC,GAEhE,IAAME,EAAgBH,EAAM,QAAQ,WAAW,KAAK,GAAG,SAAS,CAAC,GAAG,SAAS,EAE7E,GAAI,CAACG,EACH,OAEF,IAAMC,EAAY,KAAK,gBAAgB,IAAID,CAAa,EACxD,GAAI,CAACC,EAAW,CACd,KAAK,cACH,IAAIN,EACF,IAAIO,EAAsB,CACxB,aAAc,mBACd,MAAO,CACL,CACE,SAAU,UACV,KAAM,YACN,QAAS,CACP,KAAM,kDACR,EACA,YAAa,wCAAwCF,CAAa,yDACpE,CACF,CACF,CAAC,CACH,CACF,EACA,MACF,CAEA,IAAMG,EAAUN,EAAM,QAAQ,WAAW,KAAK,GAAG,SAAS,CAAC,GAAG,SAAS,GAAG,YAAY,EACjFM,IAUDF,EAAU,YAAcG,EAAkB,aAAeD,IAAY,OAKzEF,EAAU,QAAQJ,EAAM,OAAO,EAC/B,KAAK,gBAAgB,OAAOG,CAAa,GAC3C,CAAC,CACH,CAGA,UAAoB,CAClB,OAAO,KAAK,OAAO,MACrB,CAEQ,SAASK,EAAyB,CACxC,IAAMC,EAAcD,EAAM,SAAS,EAC7BE,EAAcC,EAAM,OAAOF,EAAa,KAAK,QAAQ,EACrDG,EAAe,OAAO,MAAMF,EAAY,OAAS,CAAC,EACxDE,EAAa,UAAU,GAAI,CAAC,EAC5BF,EAAY,KAAKE,EAAc,CAAC,EAChCA,EAAa,UAAU,GAAIF,EAAY,OAAS,CAAC,EACjDE,EAAa,UAAU,GAAIF,EAAY,OAAS,CAAC,EACjD,KAAK,OAAO,MAAME,CAAY,CAChC,CAEA,MAAc,sBAAsC,CAClD,GAAI,MAAK,wBAKT,KADA,KAAK,wBAA0B,GACxB,KAAK,cAAc,QAAQ,CAChC,GAAI,KAAK,eAAgB,CACvB,IAAMC,EAAoB5B,EAAa,KAAK,eACtC6B,EAAgB,KAAK,IAAI,EAAI,KAAK,0BACpCD,EAAoBC,GACtB,MAAMC,EAAMF,EAAoBC,CAAa,CAEjD,CACA,IAAME,EAAe,KAAK,cAAc,MAAM,EAC1CA,GACF,KAAK,cAAcA,CAAY,EAEjC,KAAK,0BAA4B,KAAK,IAAI,CAC5C,CACA,KAAK,wBAA0B,GACjC,CAQQ,eAA8B,CACpC,IAAMtB,EAAyB,CAAC,EAC1BuB,EAAS,OAAO,OAAO,KAAK,MAAM,EAIxC,GAHA,KAAK,YAAY,EAGbA,EAAO,SAAW,EACpB,OAAOvB,EAGT,IAAIwB,EAAY,EAGhB,KAAOA,EAAYD,EAAO,QAAQ,CAEhC,KAAOA,EAAOC,CAAS,IAAM,IAAMA,EAAYD,EAAO,QACpDC,IAIF,IAAIC,EAAkB,GAEtB,QAASC,EAAIF,EAAY,EAAGE,EAAIH,EAAO,OAAS,EAAGG,IACjD,GAAIH,EAAOG,CAAC,IAAM,IAAMH,EAAOG,EAAI,CAAC,IAAM,GAAI,CAC5CD,EAAkBC,EAAI,EACtB,KACF,CAIF,GAAID,IAAoB,GACtB,MAMF,IAAME,EAFgBJ,EAAO,SAASC,EAAWC,EAAkB,CAAC,EAEhC,SAAS,EAAG,EAAE,EAC5CG,EAAgBX,EAAM,OAAOU,EAAe,KAAK,QAAQ,EACzD1B,EAAU4B,EAAW,MAAMD,CAAa,EAE9C5B,EAAS,KAAKC,CAAO,EAGrBuB,EAAYC,EAAkB,CAChC,CAGA,YAAK,OAASD,EAAYD,EAAO,OAAS,CAACA,EAAO,SAASC,CAAS,CAAC,EAAI,CAAC,EAEnExB,CACT,CAEA,KAAKc,EAAyB,CAC5B,KAAK,SAASA,CAAK,CACrB,CAEA,MAAM,YAAYgB,EAAiBjC,EAAmD,CACpF,OAAO,IAAI,QAAoB,CAACkC,EAASC,IAAW,CAClD,IAAMC,EAAYH,EAAI,WAAW,KAAK,GAAG,SAAS,EAAE,GAAG,SAAS,EAChE,GAAI,CAACG,EAAW,CACdD,EAAO,IAAIrB,EAAsBuB,EAAgB,gCAAgC,CAAC,CAAC,EACnF,MACF,CAEA,IAAIC,EAEAtC,GAAS,YACXsC,EAAQ,WAAW,IAAM,CACvB,KAAK,gBAAgB,OAAOF,CAAS,EACrCD,EACE,IAAIrB,EAAsB,CACxB,aAAc,mBACd,MAAO,CACL,CACE,SAAU,QACV,KAAM,UACN,QAAS,CACP,KAAM,gBACR,EACA,YAAa,mCAAmCd,EAAQ,SAAS,4BACnE,CACF,CACF,CAAC,CACH,CACF,EAAGA,EAAQ,SAAS,GAGtB,KAAK,gBAAgB,IAAIoC,EAAW,CAClC,QAASH,EACT,QAAAC,EACA,OAAAC,EACA,UAAWnC,GAAS,WAAagB,EAAkB,YACnD,MAAAsB,CACF,CAAC,EACD,KAAK,SAASL,CAAG,CACnB,CAAC,CACH,CAEA,MAAM,OAAuB,CAE3B,GAAI,MAAK,SAAS,EAMlB,IAHA,KAAK,OAAO,IAAI,EAChB,KAAK,OAAO,QAAQ,EAEhB,KAAK,gBAAgB,KAAM,CAC7B,QAAWpB,KAAa,KAAK,gBAAgB,OAAO,EAC9CA,EAAU,OACZ,aAAaA,EAAU,KAAK,EAE9BA,EAAU,OACR,IAAIC,EAAsB,CACxB,aAAc,mBACd,MAAO,CACL,CACE,SAAU,UACV,KAAM,aACN,QAAS,CACP,KAAM,kDACR,CACF,CACF,CACF,CAAC,CACH,EAEF,KAAK,cACH,IAAIP,EACF,IAAIO,EAAsB,CACxB,aAAc,mBACd,MAAO,CACL,CACE,SAAU,UACV,KAAM,aACN,QAAS,CACP,KAAM,oDACR,EACA,YAAa,8BAA8B,KAAK,gBAAgB,IAAI,wBACtE,CACF,CACF,CAAC,CACH,CACF,EAEA,KAAK,gBAAgB,MAAM,CAC7B,CACA,MAAM,IAAI,QAASoB,GAAY,CAE7B,KAAK,OAAO,KAAK,QAASA,CAAO,CACnC,CAAC,EACH,CAEQ,WAAWhC,EAAoB,CACrC,KAAK,OAAO,KAAKA,CAAI,CACvB,CAEQ,aAAoB,CAC1B,KAAK,OAAS,CAAC,CACjB,CAEA,YAAYJ,EAAoC,CAC9C,KAAK,SAAWA,GAAYL,CAC9B,CAEA,aAAsB,CACpB,OAAO,KAAK,QACd,CAEA,gBAAgBM,EAAkC,CAChD,KAAK,aAAeA,CACtB,CAEA,iBAAgC,CAC9B,OAAO,KAAK,YACd,CAEA,kBAAkBwC,EAA0C,CAC1D,KAAK,eAAiBA,CACxB,CAEA,mBAAwC,CACtC,OAAO,KAAK,cACd,CAEA,wBAAiC,CAC/B,OAAO,KAAK,gBAAgB,IAC9B,CACF,ED7WO,IAAMC,EAAN,cAAwBC,CAAQ,CAWrC,YAAYC,EAA2B,CACrC,MAAM,EAXRC,EAAA,gBACAA,EAAA,aACAA,EAAA,aACAA,EAAA,iBACAA,EAAA,mBACAA,EAAA,kBACAA,EAAA,KAAQ,UACRA,EAAA,KAAQ,kBACRA,EAAA,KAAQ,6BAIN,KAAK,QAAUD,EACf,KAAK,KAAO,KAAK,QAAQ,KACzB,KAAK,KAAO,KAAK,QAAQ,KACzB,KAAK,SAAW,KAAK,QAAQ,SAC7B,KAAK,UAAY,KAAK,QAAQ,WAAa,GAC3C,KAAK,eAAiB,KAAK,QAAQ,gBAAkB,GACvD,CAEA,SAAkC,CAGhC,GAAI,KAAK,0BACP,OAAO,KAAK,0BAA0B,QAGxC,IAAME,EAAmB,KAAK,0BAA4B,KAAK,gCAAgC,EAG/F,YAAK,OAASC,EAAQ,CACpB,KAAM,KAAK,KACX,KAAM,KAAK,KACX,UAAW,KAAK,SAClB,CAAC,EAEG,KAAK,eAAiB,IACxB,KAAK,OAAO,WAAW,KAAK,cAAc,EAC1C,KAAK,8BAA8BD,CAAe,GAGpD,KAAK,8BAA8BA,CAAe,EAClD,KAAK,4BAA4BA,CAAe,EAChD,KAAK,4BAA4BA,CAAe,EAEzCA,EAAgB,OACzB,CAEQ,8BAA8BA,EAAkD,CACtFE,EAAO,KAAK,MAAM,EAClB,IAAMC,EAAS,KAAK,OAGdC,EAAkB,IAAY,CAClC,KAAK,cAAcD,CAAM,EACzB,IAAME,EAAQ,IAAI,MAAM,4BAA4B,KAAK,cAAc,IAAI,EAC3E,KAAK,sBAAsBL,EAAiBK,CAAK,CACnD,EAEAF,EAAO,GAAG,UAAWC,CAAe,CACtC,CAEQ,8BAA8BJ,EAAkD,CACtFE,EAAO,KAAK,MAAM,EAClB,IAAMC,EAAS,KAAK,OAGdG,EAAkB,IAAY,CAClC,GAAIH,IAAW,KAAK,OAAQ,CAC1B,KAAK,cAAcA,CAAM,EACzB,MACF,CAGA,IAAII,EACJ,KAAK,WAAaA,EAAa,IAAIC,EAAcL,EAAQ,KAAK,QAAQ,EAGtEA,EAAO,WAAW,CAAC,EAEnB,KAAK,+BAA+BI,CAAU,EAE9CP,EAAgB,QAAQO,CAAU,CACpC,EAEAJ,EAAO,GAAG,UAAWG,CAAe,CACtC,CAEQ,4BAA4BN,EAAkD,CACpFE,EAAO,KAAK,MAAM,EAClB,IAAMC,EAAS,KAAK,OAGdM,EAAiBC,GAAsC,CAC3D,KAAK,cAAcP,CAAM,EAErBO,EAAI,YAAY,OAAS,iBAC3B,KAAK,sBAAsBV,EAAkBU,EAAuB,OAAO,CAAC,CAAC,EAE7E,KAAK,sBAAsBV,EAAiBU,CAAG,CAEnD,EAEAP,EAAO,GAAG,QAASM,CAAa,CAClC,CAEQ,4BAA4BT,EAAkD,CACpFE,EAAO,KAAK,MAAM,EAClB,IAAMC,EAAS,KAAK,OAGdQ,EAAgB,IAAY,CAChC,KAAK,cAAcR,CAAM,EACzB,KAAK,sBAAsBH,EAAiB,IAAI,MAAM,0CAA0C,CAAC,CACnG,EACAG,EAAO,GAAG,QAASQ,CAAa,CAClC,CAEQ,+BAA+BJ,EAAiC,CAEtEA,EAAW,iBAAiB,QAAS,IAAM,CACzC,KAAK,OAAS,OACd,KAAK,WAAa,OAClB,KAAK,0BAA4B,OACjC,KAAK,cAAc,IAAIK,CAAe,CACxC,CAAC,EAEDL,EAAW,iBAAiB,QAAUM,GAAU,CAC9C,KAAK,cAAc,IAAIC,EAAcD,EAAM,KAAK,CAAC,CACnD,CAAC,CACH,CAEQ,iCAA6D,CAEnE,IAAIE,EACAC,EAOJ,MAAO,CACL,QANc,IAAI,QAAuB,CAACC,EAAUC,IAAY,CAChEH,EAAUE,EACVD,EAASE,CACX,CAAC,EAIC,QAAAH,EACA,OAAAC,CACF,CACF,CAEQ,sBAAsBhB,EAA4CU,EAAkB,CAE1FV,EAAgB,OAAOU,CAAG,EAGtB,KAAK,4BAA8BV,IACrC,KAAK,0BAA4B,OAErC,CAEQ,cAAcG,EAAsB,CACrCA,EAAO,WACVA,EAAO,QAAQ,EAEbA,IAAW,KAAK,SAClB,KAAK,OAAS,OAElB,CAEA,MAAM,KAAKgB,EAAgC,CACzC,OAAQ,MAAM,KAAK,QAAQ,GAAG,KAAKA,CAAG,CACxC,CAEA,MAAM,YAAYA,EAAiBrB,EAAmD,CACpF,OAAQ,MAAM,KAAK,QAAQ,GAAG,YAAYqB,EAAKrB,CAAO,CACxD,CAEA,MAAM,OAAuB,CAM3B,GALI,KAAK,2BACP,KAAK,sBAAsB,KAAK,0BAA2B,IAAI,MAAM,gCAAgC,CAAC,EAIpG,KAAK,WAAY,CACnB,IAAMS,EAAa,KAAK,WACxB,OAAO,KAAK,WACZ,MAAMA,EAAW,MAAM,CACzB,MAGE,KAAK,cAAc,IAAIK,CAAe,EAGpC,KAAK,SACP,KAAK,OAAO,mBAAmB,EAC/B,KAAK,OAAO,QAAQ,EACpB,KAAK,OAAS,OAElB,CACF,EI9NA,OAAS,SAAAQ,MAAa,gBACtB,OAAOC,MAAS,WAkBT,IAAMC,EAAiC,IAEjCC,EAAN,KAAgB,CAQrB,YAAYC,EAA8C,CAP1DC,EAAA,KAAS,WACTA,EAAA,eACAA,EAAA,KAAQ,YACRA,EAAA,KAAQ,gBACRA,EAAA,KAAQ,kBACRA,EAAA,KAAiB,cAAc,IAAI,KAGjC,KAAK,QAAUD,CACjB,CAEA,MAAM,MACJE,EACAC,EACAC,EACAC,EACe,CACXF,GACF,KAAK,YAAYA,CAAQ,EAEvBC,IAAiB,QACnB,KAAK,gBAAgBA,CAAY,EAE/BC,GAAS,iBAAmB,QAC9B,KAAK,kBAAkBA,EAAQ,cAAc,EAG/C,IAAMC,EAASC,EAAI,aAAcC,GAAW,CAC1C,IAAMC,EAAa,IAAIC,EAAcF,EAAQ,KAAK,SAAU,KAAK,aAAc,CAC7E,eAAgB,KAAK,cACvB,CAAC,EACD,KAAK,QAAQC,CAAU,EACvB,KAAK,YAAY,IAAIA,CAAU,EAC/BA,EAAW,iBAAiB,QAAS,IAAM,CACzC,KAAK,YAAY,OAAOA,CAAU,CACpC,CAAC,CACH,CAAC,EAED,MAAM,IAAI,QAAeE,GAAY,CACnC,IAAMC,EAAgBV,GAAuB,CAC3CI,EAAO,OAAOJ,EAAMS,CAAO,CAC7B,EAGME,EAAgB,MAAOC,GAAgD,CACvEA,GAAG,OAAS,eACd,MAAMC,EAAM,EAAE,EACdT,EAAO,MAAM,EACbM,EAAaV,CAAI,EAErB,EAEAI,EAAO,GAAG,QAASO,CAAa,EAEhCP,EAAO,KAAK,YAAa,IAAM,CAC7BA,EAAO,IAAI,QAASO,CAAa,CACnC,CAAC,EAEDD,EAAaV,CAAI,EAEjB,KAAK,OAASI,CAChB,CAAC,CACH,CAeA,MAAM,KAAKD,EAA+C,CACxD,OAAO,IAAI,QAAc,CAACM,EAASK,IAAW,CAC5C,GAAI,CAAC,KAAK,OAAQ,CAChBA,EAAO,IAAI,MAAM,gDAAgD,CAAC,EAClE,MACF,CACA,IAAIC,EACAZ,GAAS,sBAAwB,KACnCY,EAAoB,WAAW,IAAM,CACnC,QAAWR,KAAc,KAAK,YAG5BA,EAAW,MAAM,EAAE,MAAM,QAAQ,KAAK,CAE1C,EAAGJ,GAAS,qBAAuBP,CAA8B,GAEnE,KAAK,OAAO,MAAOoB,GAAQ,CACzB,GAAIA,EAAK,CACPF,EAAOE,CAAG,EACV,MACF,CACID,GACF,aAAaA,CAAiB,EAEhC,KAAK,YAAY,MAAM,EACvB,KAAK,OAAS,OACdN,EAAQ,CACV,CAAC,CACH,CAAC,CACH,CAEA,gBAAgBP,EAAkC,CAChD,KAAK,aAAeA,CACtB,CAEA,iBAAgC,CAC9B,OAAO,KAAK,YACd,CAEA,YAAYD,EAAoC,CAC9C,KAAK,SAAWA,CAClB,CAEA,aAAkC,CAChC,OAAO,KAAK,QACd,CAEA,kBAAkBgB,EAA0C,CAC1D,KAAK,eAAiBA,CACxB,CAEA,mBAAwC,CACtC,OAAO,KAAK,cACd,CACF",
6
- "names": ["Hl7Base", "type", "listener", "options", "assert", "connect", "Hl7Message", "OperationOutcomeError", "ReturnAckCategory", "sleep", "validationError", "iconv", "VT", "CR", "FS", "Hl7MessageEvent", "connection", "message", "__publicField", "Hl7EnhancedAckSentEvent", "Hl7ErrorEvent", "error", "Hl7CloseEvent", "ReturnAckCategory", "DEFAULT_ENCODING", "ONE_MINUTE", "Hl7Connection", "Hl7Base", "socket", "encoding", "enhancedMode", "options", "__publicField", "data", "messages", "message", "Hl7MessageEvent", "err", "Hl7ErrorEvent", "Hl7CloseEvent", "event", "response", "Hl7EnhancedAckSentEvent", "origMsgCtrlId", "queueItem", "OperationOutcomeError", "ackCode", "ReturnAckCategory", "reply", "replyString", "replyBuffer", "iconv", "outputBuffer", "millisBetweenMsgs", "elapsedMillis", "sleep", "messageEvent", "buffer", "bufferIdx", "messageEndIndex", "i", "contentBuffer", "contentString", "Hl7Message", "msg", "resolve", "reject", "msgCtrlId", "validationError", "timer", "messagesPerMin", "Hl7Client", "Hl7Base", "options", "__publicField", "deferredPromise", "connect", "assert", "socket", "timeoutListener", "error", "connectListener", "connection", "Hl7Connection", "errorListener", "err", "closeListener", "Hl7CloseEvent", "event", "Hl7ErrorEvent", "resolve", "reject", "_resolve", "_reject", "msg", "sleep", "net", "DEFAULT_FORCE_DRAIN_TIMEOUT_MS", "Hl7Server", "handler", "__publicField", "port", "encoding", "enhancedMode", "options", "server", "net", "socket", "connection", "Hl7Connection", "resolve", "listenOnPort", "errorListener", "e", "sleep", "reject", "forceDrainTimeout", "err", "messagesPerMin"]
4
+ "sourcesContent": ["// SPDX-FileCopyrightText: Copyright Orangebot, Inc. and Medplum contributors\n// SPDX-License-Identifier: Apache-2.0\nimport type { Hl7CloseEvent, Hl7EnhancedAckSentEvent, Hl7ErrorEvent, Hl7MessageEvent, Hl7WarningEvent } from './events';\n\nexport interface Hl7EventMap {\n message: Hl7MessageEvent;\n error: Hl7ErrorEvent;\n warning: Hl7WarningEvent;\n close: Hl7CloseEvent;\n enhancedAckSent: Hl7EnhancedAckSentEvent;\n}\n\nexport abstract class Hl7Base extends EventTarget {\n addEventListener<K extends keyof Hl7EventMap>(\n type: K,\n listener: ((event: Hl7EventMap[K]) => void) | EventListenerObject | null,\n options?: boolean | AddEventListenerOptions\n ): void;\n\n addEventListener(\n type: string,\n listener: EventListenerOrEventListenerObject | null,\n options?: boolean | AddEventListenerOptions\n ): void {\n super.addEventListener(type, listener, options);\n }\n removeEventListener<K extends keyof Hl7EventMap>(\n type: K,\n listener: ((event: Hl7EventMap[K]) => void) | EventListenerObject | null,\n options?: boolean | AddEventListenerOptions\n ): void;\n\n removeEventListener(\n type: string,\n listener: EventListenerOrEventListenerObject | null,\n options?: boolean | AddEventListenerOptions\n ): void {\n super.removeEventListener(type, listener, options);\n }\n}\n", "// SPDX-FileCopyrightText: Copyright Orangebot, Inc. and Medplum contributors\n// SPDX-License-Identifier: Apache-2.0\nimport type { Hl7Message } from '@medplum/core';\nimport assert from 'node:assert';\nimport type { Socket } from 'node:net';\nimport { connect } from 'node:net';\nimport { Hl7Base } from './base';\nimport type { SendAndWaitOptions } from './connection';\nimport { Hl7Connection } from './connection';\nimport { Hl7CloseEvent, Hl7ErrorEvent, Hl7WarningEvent } from './events';\n\nexport interface Hl7ClientOptions {\n host: string;\n port: number;\n encoding?: string;\n keepAlive?: boolean;\n connectTimeout?: number; // Add timeout option\n}\n\nexport interface DeferredConnectionPromise {\n promise: Promise<Hl7Connection>;\n resolve: (connection: Hl7Connection) => void;\n reject: (err: Error) => void;\n}\n\nexport class Hl7Client extends Hl7Base {\n options: Hl7ClientOptions;\n host: string;\n port: number;\n encoding?: string;\n connection?: Hl7Connection;\n keepAlive: boolean;\n private socket?: Socket;\n private connectTimeout: number;\n private deferredConnectionPromise?: DeferredConnectionPromise;\n\n constructor(options: Hl7ClientOptions) {\n super();\n this.options = options;\n this.host = this.options.host;\n this.port = this.options.port;\n this.encoding = this.options.encoding;\n this.keepAlive = this.options.keepAlive ?? false;\n this.connectTimeout = this.options.connectTimeout ?? 30000; // Default 30 seconds\n }\n\n connect(): Promise<Hl7Connection> {\n // If we are already waiting for a pending connection attempt, just return the deferred promise to that\n // In the case that the promise is already resolve, we will also return a resolved connection\n if (this.deferredConnectionPromise) {\n return this.deferredConnectionPromise.promise;\n }\n\n const deferredPromise = (this.deferredConnectionPromise = this.createDeferredConnectionPromise());\n\n // Create the socket\n this.socket = connect({\n host: this.host,\n port: this.port,\n keepAlive: this.keepAlive,\n });\n\n if (this.connectTimeout > 0) {\n this.socket.setTimeout(this.connectTimeout);\n this.registerSocketTimeoutListener(deferredPromise);\n }\n\n this.registerSocketConnectListener(deferredPromise);\n this.registerSocketErrorListener(deferredPromise);\n this.registerSocketCloseListener(deferredPromise);\n\n return deferredPromise.promise;\n }\n\n private registerSocketTimeoutListener(deferredPromise: DeferredConnectionPromise): void {\n assert(this.socket);\n const socket = this.socket;\n\n // Handle timeout event\n const timeoutListener = (): void => {\n this.cleanupSocket(socket);\n const error = new Error(`Connection timeout after ${this.connectTimeout}ms`);\n this.rejectDeferredPromise(deferredPromise, error);\n };\n\n socket.on('timeout', timeoutListener);\n }\n\n private registerSocketConnectListener(deferredPromise: DeferredConnectionPromise): void {\n assert(this.socket);\n const socket = this.socket;\n\n // Handle successful connection\n const connectListener = (): void => {\n if (socket !== this.socket) {\n this.cleanupSocket(socket);\n return;\n }\n\n // Create the HL7 connection\n let connection: Hl7Connection;\n this.connection = connection = new Hl7Connection(socket, this.encoding);\n\n // Remove the timeout listener as we're now connected\n socket.setTimeout(0);\n\n this.registerHl7ConnectionListeners(connection);\n\n deferredPromise.resolve(connection);\n };\n\n socket.on('connect', connectListener);\n }\n\n private registerSocketErrorListener(deferredPromise: DeferredConnectionPromise): void {\n assert(this.socket);\n const socket = this.socket;\n\n // Handle connection errors\n const errorListener = (err: Error | AggregateError): void => {\n this.cleanupSocket(socket);\n\n if (err.constructor.name === 'AggregateError') {\n this.rejectDeferredPromise(deferredPromise, (err as AggregateError).errors[0]);\n } else {\n this.rejectDeferredPromise(deferredPromise, err);\n }\n };\n\n socket.on('error', errorListener);\n }\n\n private registerSocketCloseListener(deferredPromise: DeferredConnectionPromise): void {\n assert(this.socket);\n const socket = this.socket;\n\n // Handle connection errors\n const closeListener = (): void => {\n this.cleanupSocket(socket);\n this.rejectDeferredPromise(deferredPromise, new Error('Socket closed before connection finished'));\n };\n socket.on('close', closeListener);\n }\n\n private registerHl7ConnectionListeners(connection: Hl7Connection): void {\n // Set up event handlers\n connection.addEventListener('close', () => {\n this.socket = undefined;\n this.connection = undefined;\n this.deferredConnectionPromise = undefined;\n this.dispatchEvent(new Hl7CloseEvent());\n });\n\n connection.addEventListener('error', (event) => {\n this.dispatchEvent(new Hl7ErrorEvent(event.error));\n });\n\n connection.addEventListener('warning', (event) => {\n this.dispatchEvent(new Hl7WarningEvent(event.error));\n });\n }\n\n private createDeferredConnectionPromise(): DeferredConnectionPromise {\n // Setup our deferred connection promise\n let resolve!: (connection: Hl7Connection) => void;\n let reject!: (err: Error) => void;\n\n const promise = new Promise<Hl7Connection>((_resolve, _reject) => {\n resolve = _resolve;\n reject = _reject;\n });\n\n return {\n promise,\n resolve,\n reject,\n };\n }\n\n private rejectDeferredPromise(deferredPromise: DeferredConnectionPromise, err: Error): void {\n // Reject this deferred promise with the given error\n deferredPromise.reject(err);\n\n // If the currently tracked deferred promise is this deferred promise, remove it from the client\n if (this.deferredConnectionPromise === deferredPromise) {\n this.deferredConnectionPromise = undefined;\n }\n }\n\n private cleanupSocket(socket: Socket): void {\n if (!socket.destroyed) {\n socket.destroy();\n }\n if (socket === this.socket) {\n this.socket = undefined;\n }\n }\n\n async send(msg: Hl7Message): Promise<void> {\n return (await this.connect()).send(msg);\n }\n\n async sendAndWait(msg: Hl7Message, options?: SendAndWaitOptions): Promise<Hl7Message> {\n return (await this.connect()).sendAndWait(msg, options);\n }\n\n async close(): Promise<void> {\n if (this.deferredConnectionPromise) {\n this.rejectDeferredPromise(this.deferredConnectionPromise, new Error('Client closed while connecting'));\n }\n\n // Close established connection if it exists\n if (this.connection) {\n const connection = this.connection;\n delete this.connection;\n await connection.close();\n } else {\n // Emit close event because the connection will not be able to emit it for us\n // Since it has not connected at this point\n this.dispatchEvent(new Hl7CloseEvent());\n }\n // Close the socket if it exists\n if (this.socket) {\n this.socket.removeAllListeners();\n this.socket.destroy();\n this.socket = undefined;\n }\n }\n}\n", "// SPDX-FileCopyrightText: Copyright Orangebot, Inc. and Medplum contributors\n// SPDX-License-Identifier: Apache-2.0\nimport { Hl7Message, OperationOutcomeError, ReturnAckCategory, sleep, validationError } from '@medplum/core';\nimport iconv from 'iconv-lite';\nimport type net from 'node:net';\nimport { Hl7Base } from './base';\nimport { CR, FS, VT } from './constants';\nimport { Hl7CloseEvent, Hl7EnhancedAckSentEvent, Hl7ErrorEvent, Hl7MessageEvent, Hl7WarningEvent } from './events';\n\n// Export `ReturnAckCategory` for backwards-compat\nexport { ReturnAckCategory } from '@medplum/core';\n\n// iconv-lite docs have great examples and explanations for how to use Buffers with iconv-lite:\n// See: https://github.com/ashtuchkin/iconv-lite/wiki/Use-Buffers-when-decoding\n\nexport type Hl7MessageQueueItem = {\n message: Hl7Message;\n resolve: (reply: Hl7Message) => void;\n reject: (err: Error) => void;\n returnAck: ReturnAckCategory;\n timer?: NodeJS.Timeout;\n};\n\nexport interface SendAndWaitOptions {\n /** The ACK-level that the Promise should resolve on. The default is `ReturnAckCategory.APPLICATION` (returns on the first application-level ACK). */\n returnAck?: ReturnAckCategory;\n /** The amount of milliseconds to wait before timing out when waiting for the response to a message. */\n timeoutMs?: number;\n}\n\nexport interface Hl7ConnectionOptions {\n messagesPerMin?: number;\n}\n\n/**\n * Enhanced mode for HL7 connections.\n * - `'standard'`: Standard enhanced mode behavior\n * - `'aaMode'`: AA mode - special enhanced mode that only accepts AA acknowledgements\n * - `undefined`: Enhanced mode is not enabled (standard behavior)\n */\nexport type EnhancedMode = 'standard' | 'aaMode' | undefined;\n\nexport const DEFAULT_ENCODING = 'utf-8';\nconst ONE_MINUTE = 60 * 1000;\n\nexport class Hl7Connection extends Hl7Base {\n readonly socket: net.Socket;\n encoding: string;\n enhancedMode: EnhancedMode = undefined;\n private messagesPerMin: number | undefined = undefined;\n private chunks: Buffer[] = [];\n private readonly pendingMessages: Map<string, Hl7MessageQueueItem> = new Map<string, Hl7MessageQueueItem>();\n private readonly responseQueue: Hl7MessageEvent[] = [];\n private lastMessageDispatchedTime = 0;\n private responseQueueProcessing = false;\n\n constructor(\n socket: net.Socket,\n encoding: string = DEFAULT_ENCODING,\n enhancedMode?: EnhancedMode,\n options: Hl7ConnectionOptions = {}\n ) {\n super();\n\n this.socket = socket;\n this.encoding = encoding;\n this.enhancedMode = enhancedMode;\n this.messagesPerMin = options.messagesPerMin;\n\n socket.on('data', (data: Buffer) => {\n try {\n this.appendData(data);\n const messages = this.parseMessages();\n for (const message of messages) {\n this.responseQueue.push(new Hl7MessageEvent(this, message));\n }\n this.processResponseQueue().catch((err) => {\n this.dispatchEvent(new Hl7ErrorEvent(err));\n });\n } catch (err) {\n this.dispatchEvent(new Hl7ErrorEvent(err as Error));\n }\n });\n\n socket.on('error', (err) => {\n this.resetBuffer();\n this.dispatchEvent(new Hl7ErrorEvent(err));\n });\n\n // The difference between \"end\" and \"close\", is that \"end\" is only emitted on half-close from the other side\n // If the connection from the other side does not close gracefully, but instead we destroy the socket, then the Hl7Connection will not emit close\n // if we listen only for \"end\"; \"close\" is always emitted, whether the close is graceful or forceful\n socket.on('close', () => {\n // Reject any messages that were still pending when the connection was closed externally (e.g. closed by peer)\n this.drainPendingMessages();\n this.dispatchEvent(new Hl7CloseEvent());\n });\n\n this.addEventListener('message', (event) => {\n // In standard enhanced mode, send commit ACK (CA) immediately, then later forward app-level ACKs\n // In aaMode, send application ACK (AA) immediately, then ignore any later app-level ACKs\n let response: Hl7Message | undefined;\n if (this.enhancedMode === 'standard') {\n response = event.message.buildAck({ ackCode: 'CA' });\n } else if (this.enhancedMode === 'aaMode') {\n response = event.message.buildAck({ ackCode: 'AA' });\n }\n if (response) {\n this.send(response);\n this.dispatchEvent(new Hl7EnhancedAckSentEvent(this, response));\n }\n const origMsgCtrlId = event.message.getSegment('MSA')?.getField(2)?.toString();\n // If there is no message control ID, just return\n if (!origMsgCtrlId) {\n return;\n }\n const queueItem = this.pendingMessages.get(origMsgCtrlId);\n if (!queueItem) {\n this.dispatchEvent(\n new Hl7WarningEvent(\n new OperationOutcomeError({\n resourceType: 'OperationOutcome',\n issue: [\n {\n severity: 'warning',\n code: 'not-found',\n details: {\n text: 'Response received for unknown message control ID',\n },\n diagnostics: `Received ACK for message control ID '${origMsgCtrlId}' but there was no pending message with this control ID`,\n },\n ],\n })\n )\n );\n return;\n }\n // Check the ACK type we should return on\n const ackCode = event.message.getSegment('MSA')?.getField(1)?.toString()?.toUpperCase();\n if (!ackCode) {\n return;\n }\n\n // Two modes:\n // Application-level or first ACK\n\n // First should always return on any ACK message, this is the default\n // The exception is APPLICATION, which should not resolve when the ACK is a CA, but should resolve on all other ACK types\n // On CA, we return early\n if (queueItem.returnAck === ReturnAckCategory.APPLICATION && ackCode === 'CA') {\n return;\n }\n\n // Resolve the promise if there is one pending for this message and we didn't exit already because the ACK type matches\n queueItem.resolve(event.message);\n this.pendingMessages.delete(origMsgCtrlId);\n });\n }\n\n /** @returns A boolean representing whether the socket attached to this Hl7Connection has emitted the close event already or not. */\n isClosed(): boolean {\n return this.socket.closed;\n }\n\n private sendImpl(reply: Hl7Message): void {\n const replyString = reply.toString();\n const replyBuffer = iconv.encode(replyString, this.encoding);\n const outputBuffer = Buffer.alloc(replyBuffer.length + 3);\n outputBuffer.writeInt8(VT, 0);\n replyBuffer.copy(outputBuffer, 1);\n outputBuffer.writeInt8(FS, replyBuffer.length + 1);\n outputBuffer.writeInt8(CR, replyBuffer.length + 2);\n this.socket.write(outputBuffer);\n }\n\n private async processResponseQueue(): Promise<void> {\n if (this.responseQueueProcessing) {\n return;\n }\n\n this.responseQueueProcessing = true;\n while (this.responseQueue.length) {\n if (this.messagesPerMin) {\n const millisBetweenMsgs = ONE_MINUTE / this.messagesPerMin;\n const elapsedMillis = Date.now() - this.lastMessageDispatchedTime;\n if (millisBetweenMsgs > elapsedMillis) {\n await sleep(millisBetweenMsgs - elapsedMillis);\n }\n }\n const messageEvent = this.responseQueue.shift() as Hl7MessageEvent;\n if (messageEvent) {\n this.dispatchEvent(messageEvent);\n }\n this.lastMessageDispatchedTime = Date.now();\n }\n this.responseQueueProcessing = false;\n }\n\n /**\n * Parses complete HL7 messages from the accumulated buffer.\n * Continues parsing while the buffer starts with VT and contains FS+CR.\n * Keeps any incomplete message data in the buffer for the next chunk.\n * @returns An array of parsed HL7 messages.\n */\n private parseMessages(): Hl7Message[] {\n const messages: Hl7Message[] = [];\n const buffer = Buffer.concat(this.chunks);\n this.resetBuffer();\n\n // Check if buffer starts with VT (Vertical Tab)\n if (buffer.length === 0) {\n return messages;\n }\n\n let bufferIdx = 0;\n\n // Keep parsing while we have complete messages\n while (bufferIdx < buffer.length) {\n // Ignore bytes between message frames\n while (buffer[bufferIdx] !== VT && bufferIdx < buffer.length) {\n bufferIdx++;\n }\n\n // Look for FS+CR sequence to mark end of message\n let messageEndIndex = -1;\n\n for (let i = bufferIdx + 1; i < buffer.length - 1; i++) {\n if (buffer[i] === FS && buffer[i + 1] === CR) {\n messageEndIndex = i + 1; // Index of CR (end of message)\n break;\n }\n }\n\n // If we don't have a complete message yet, wait for more data\n if (messageEndIndex === -1) {\n break;\n }\n\n // Extract the complete message (including VT, FS, and CR)\n const messageBuffer = buffer.subarray(bufferIdx, messageEndIndex + 1);\n // Extract the content (without VT at start and FS+CR at end)\n const contentBuffer = messageBuffer.subarray(1, -2);\n const contentString = iconv.decode(contentBuffer, this.encoding);\n const message = Hl7Message.parse(contentString);\n\n messages.push(message);\n\n // Move past this message\n bufferIdx = messageEndIndex + 1;\n }\n\n // Keep any remaining unfinished chunk in this.chunks\n this.chunks = bufferIdx < buffer.length ? [buffer.subarray(bufferIdx)] : [];\n\n return messages;\n }\n\n send(reply: Hl7Message): void {\n this.sendImpl(reply);\n }\n\n async sendAndWait(msg: Hl7Message, options?: SendAndWaitOptions): Promise<Hl7Message> {\n return new Promise<Hl7Message>((resolve, reject) => {\n const msgCtrlId = msg.getSegment('MSH')?.getField(10)?.toString();\n if (!msgCtrlId) {\n reject(new OperationOutcomeError(validationError('Required field missing: MSH.10')));\n return;\n }\n\n let timer: NodeJS.Timeout | undefined;\n\n if (options?.timeoutMs) {\n timer = setTimeout(() => {\n this.pendingMessages.delete(msgCtrlId);\n reject(\n new OperationOutcomeError({\n resourceType: 'OperationOutcome',\n issue: [\n {\n severity: 'error',\n code: 'timeout',\n details: {\n text: 'Client timeout',\n },\n diagnostics: `Request timed out after waiting ${options.timeoutMs} milliseconds for response`,\n },\n ],\n })\n );\n }, options.timeoutMs);\n }\n\n this.pendingMessages.set(msgCtrlId, {\n message: msg,\n resolve,\n reject,\n returnAck: options?.returnAck ?? ReturnAckCategory.APPLICATION,\n timer,\n });\n this.sendImpl(msg);\n });\n }\n\n async close(): Promise<void> {\n // If we have already received the close event, then we can just return immediately\n if (this.isClosed()) {\n return;\n }\n this.socket.end();\n this.socket.destroy();\n // drainPendingMessages is also called by the socket 'close' handler, but we call it here first so\n // that rejections are delivered before the close event is dispatched when close() is called explicitly.\n // The socket 'close' handler's call will be a no-op since the map will already be empty.\n this.drainPendingMessages();\n await new Promise((resolve) => {\n // Register a temporary listener to help resolve the promise once close has been emitted\n this.socket.once('close', resolve);\n });\n }\n\n /**\n * Rejects all pending sendAndWait promises and clears the pending messages map.\n * Safe to call multiple times \u2014 subsequent calls are no-ops once the map is empty.\n */\n private drainPendingMessages(): void {\n if (!this.pendingMessages.size) {\n return;\n }\n const pendingCount = this.pendingMessages.size;\n for (const queueItem of this.pendingMessages.values()) {\n if (queueItem.timer) {\n clearTimeout(queueItem.timer);\n }\n queueItem.reject(\n new OperationOutcomeError({\n resourceType: 'OperationOutcome',\n issue: [\n {\n severity: 'warning',\n code: 'incomplete',\n details: {\n text: 'Message was still pending when connection closed',\n },\n },\n ],\n })\n );\n }\n this.dispatchEvent(\n new Hl7ErrorEvent(\n new OperationOutcomeError({\n resourceType: 'OperationOutcome',\n issue: [\n {\n severity: 'warning',\n code: 'incomplete',\n details: {\n text: 'Messages were still pending when connection closed',\n },\n diagnostics: `Hl7Connection closed while ${pendingCount} messages were pending`,\n },\n ],\n })\n )\n );\n this.pendingMessages.clear();\n }\n\n private appendData(data: Buffer): void {\n this.chunks.push(data);\n }\n\n private resetBuffer(): void {\n this.chunks = [];\n }\n\n setEncoding(encoding: string | undefined): void {\n this.encoding = encoding ?? DEFAULT_ENCODING;\n }\n\n getEncoding(): string {\n return this.encoding;\n }\n\n setEnhancedMode(enhancedMode: EnhancedMode): void {\n this.enhancedMode = enhancedMode;\n }\n\n getEnhancedMode(): EnhancedMode {\n return this.enhancedMode;\n }\n\n setMessagesPerMin(messagesPerMin: number | undefined): void {\n this.messagesPerMin = messagesPerMin;\n }\n\n getMessagesPerMin(): number | undefined {\n return this.messagesPerMin;\n }\n\n getPendingMessageCount(): number {\n return this.pendingMessages.size;\n }\n}\n", "// SPDX-FileCopyrightText: Copyright Orangebot, Inc. and Medplum contributors\n// SPDX-License-Identifier: Apache-2.0\n\n/**\n * VT (Vertical Tab) character.\n *\n * In HL7 messages, this character is used to indicate the start of a message.\n */\nexport const VT = 0x0b;\n\n/**\n * CR (Carriage Return) character.\n *\n * In HL7 messages, this character is used to indicate the end of a message.\n */\nexport const CR = 0x0d;\n\n/**\n * FS (File Separator) character.\n *\n * In HL7 messages, this character is used to separate fields.\n */\nexport const FS = 0x1c;\n", "// SPDX-FileCopyrightText: Copyright Orangebot, Inc. and Medplum contributors\n// SPDX-License-Identifier: Apache-2.0\nimport type { Hl7Message } from '@medplum/core';\nimport type { Hl7Connection } from './connection';\n\nexport class Hl7MessageEvent extends Event {\n readonly connection: Hl7Connection;\n readonly message: Hl7Message;\n\n constructor(connection: Hl7Connection, message: Hl7Message) {\n super('message');\n this.connection = connection;\n this.message = message;\n }\n}\n\nexport class Hl7EnhancedAckSentEvent extends Event {\n readonly connection: Hl7Connection;\n readonly message: Hl7Message;\n\n constructor(connection: Hl7Connection, message: Hl7Message) {\n super('enhancedAckSent');\n this.connection = connection;\n this.message = message;\n }\n}\n\nexport class Hl7ErrorEvent extends Event {\n readonly error: Error;\n\n constructor(error: Error) {\n super('error');\n this.error = error;\n }\n}\n\nexport class Hl7WarningEvent extends Event {\n readonly error: Error;\n\n constructor(error: Error) {\n super('warning');\n this.error = error;\n }\n}\n\nexport class Hl7CloseEvent extends Event {\n constructor() {\n super('close');\n }\n}\n", "// SPDX-FileCopyrightText: Copyright Orangebot, Inc. and Medplum contributors\n// SPDX-License-Identifier: Apache-2.0\nimport { sleep } from '@medplum/core';\nimport net from 'node:net';\nimport type { EnhancedMode, Hl7ConnectionOptions } from './connection';\nimport { Hl7Connection } from './connection';\n\n/**\n * Options for configuring the `Hl7Server#stop` method.\n */\nexport interface Hl7ServerStopOptions {\n /**\n * Time in milliseconds to allow client connections to gracefully close after the stop method has been called, before forcefully closing them.\n *\n * Can be set to `-1` to disable forceful draining of connections during stop.\n *\n * Defaults to `10_000`.\n */\n forceDrainTimeoutMs?: number;\n}\n\nexport const DEFAULT_FORCE_DRAIN_TIMEOUT_MS = 10_000;\n\nexport class Hl7Server {\n readonly handler: (connection: Hl7Connection) => void;\n server?: net.Server;\n private encoding: string | undefined = undefined;\n private enhancedMode: EnhancedMode = undefined;\n private messagesPerMin: number | undefined = undefined;\n private readonly connections = new Set<Hl7Connection>();\n\n constructor(handler: (connection: Hl7Connection) => void) {\n this.handler = handler;\n }\n\n async start(\n port: number,\n encoding?: string,\n enhancedMode?: EnhancedMode,\n connectionOptions?: Hl7ConnectionOptions\n ): Promise<number> {\n if (encoding) {\n this.setEncoding(encoding);\n }\n if (enhancedMode !== undefined) {\n this.setEnhancedMode(enhancedMode);\n }\n if (connectionOptions?.messagesPerMin !== undefined) {\n this.setMessagesPerMin(connectionOptions.messagesPerMin);\n }\n\n const server = net.createServer((socket) => {\n const connection = new Hl7Connection(socket, this.encoding, this.enhancedMode, {\n messagesPerMin: this.messagesPerMin,\n });\n this.handler(connection);\n this.connections.add(connection);\n connection.addEventListener('close', () => {\n this.connections.delete(connection);\n });\n });\n\n return new Promise<number>((resolve, reject) => {\n const listenOnPort = (port: number): void => {\n server.listen(port, () => {\n const boundPort = (server.address() as { port: number }).port;\n resolve(boundPort);\n });\n };\n\n const errorListener = (e: Error & { code?: string }): void => {\n if (e?.code === 'EADDRINUSE') {\n server.close(() => sleep(50).then(() => listenOnPort(port)));\n } else {\n reject(e);\n }\n };\n\n server.on('error', errorListener);\n\n server.once('listening', () => {\n server.off('error', errorListener);\n });\n\n listenOnPort(port);\n\n this.server = server;\n });\n }\n\n /**\n * Stops the HL7 server.\n *\n * By default, the server will stop accepting new connections after this method is called, and wait for current connections to close naturally.\n *\n * If all connections don't close within 10 seconds, the server will forcefully close them before shutting down.\n *\n * The default time to wait before forcefully closing connections can be changed by passing an integer value for the optional `options.forceDrainTimeoutMs`.\n *\n * Forced drain can also be disabled by passing `-1` for `options.forceDrainTimeoutMs`.\n * @param options - Optional options to configure the stopping of the server.\n * @returns Promise that resolves when the server has stopped, or rejects if an error prevents server from stopping.\n */\n async stop(options?: Hl7ServerStopOptions): Promise<void> {\n return new Promise<void>((resolve, reject) => {\n if (!this.server) {\n reject(new Error('Stop was called but there is no server running'));\n return;\n }\n let forceDrainTimeout: NodeJS.Timeout | undefined;\n if (options?.forceDrainTimeoutMs !== -1) {\n forceDrainTimeout = setTimeout(() => {\n for (const connection of this.connections) {\n // Theoretically close should almost never throw as most errors are caught internal to the method and emitted as error events\n // We put a .catch here to prevent floating promises and log any errors that somehow make it through\n connection.close().catch(console.error);\n }\n }, options?.forceDrainTimeoutMs ?? DEFAULT_FORCE_DRAIN_TIMEOUT_MS);\n }\n this.server.close((err) => {\n if (err) {\n reject(err);\n return;\n }\n if (forceDrainTimeout) {\n clearTimeout(forceDrainTimeout);\n }\n this.connections.clear();\n this.server = undefined;\n resolve();\n });\n });\n }\n\n setEnhancedMode(enhancedMode: EnhancedMode): void {\n this.enhancedMode = enhancedMode;\n }\n\n getEnhancedMode(): EnhancedMode {\n return this.enhancedMode;\n }\n\n setEncoding(encoding: string | undefined): void {\n this.encoding = encoding;\n }\n\n getEncoding(): string | undefined {\n return this.encoding;\n }\n\n setMessagesPerMin(messagesPerMin: number | undefined): void {\n this.messagesPerMin = messagesPerMin;\n }\n\n getMessagesPerMin(): number | undefined {\n return this.messagesPerMin;\n }\n}\n"],
5
+ "mappings": "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",
6
+ "names": ["Hl7Base", "type", "listener", "options", "assert", "connect", "Hl7Message", "OperationOutcomeError", "ReturnAckCategory", "sleep", "validationError", "iconv", "VT", "CR", "FS", "Hl7MessageEvent", "connection", "message", "__publicField", "Hl7EnhancedAckSentEvent", "Hl7ErrorEvent", "error", "Hl7WarningEvent", "Hl7CloseEvent", "ReturnAckCategory", "DEFAULT_ENCODING", "ONE_MINUTE", "Hl7Connection", "Hl7Base", "socket", "encoding", "enhancedMode", "options", "__publicField", "data", "messages", "message", "Hl7MessageEvent", "err", "Hl7ErrorEvent", "Hl7CloseEvent", "event", "response", "Hl7EnhancedAckSentEvent", "origMsgCtrlId", "queueItem", "Hl7WarningEvent", "OperationOutcomeError", "ackCode", "ReturnAckCategory", "reply", "replyString", "replyBuffer", "iconv", "outputBuffer", "millisBetweenMsgs", "elapsedMillis", "sleep", "messageEvent", "buffer", "bufferIdx", "messageEndIndex", "i", "contentBuffer", "contentString", "Hl7Message", "msg", "resolve", "reject", "msgCtrlId", "validationError", "timer", "pendingCount", "messagesPerMin", "Hl7Client", "Hl7Base", "options", "__publicField", "deferredPromise", "connect", "assert", "socket", "timeoutListener", "error", "connectListener", "connection", "Hl7Connection", "errorListener", "err", "closeListener", "Hl7CloseEvent", "event", "Hl7ErrorEvent", "Hl7WarningEvent", "resolve", "reject", "_resolve", "_reject", "msg", "sleep", "net", "DEFAULT_FORCE_DRAIN_TIMEOUT_MS", "Hl7Server", "handler", "__publicField", "port", "encoding", "enhancedMode", "connectionOptions", "server", "net", "socket", "connection", "Hl7Connection", "resolve", "reject", "listenOnPort", "boundPort", "errorListener", "e", "sleep", "options", "forceDrainTimeout", "err", "messagesPerMin"]
7
7
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@medplum/hl7",
3
- "version": "5.1.4",
3
+ "version": "5.1.6",
4
4
  "description": "Medplum HL7 Utilities",
5
5
  "keywords": [
6
6
  "medplum",
@@ -56,11 +56,11 @@
56
56
  "test": "jest"
57
57
  },
58
58
  "dependencies": {
59
- "@medplum/core": "5.1.4",
59
+ "@medplum/core": "5.1.6",
60
60
  "iconv-lite": "0.7.2"
61
61
  },
62
62
  "devDependencies": {
63
- "@medplum/fhirtypes": "5.1.4"
63
+ "@medplum/fhirtypes": "5.1.6"
64
64
  },
65
65
  "engines": {
66
66
  "node": "^22.18.0 || >=24.2.0"