message-nexus 1.1.4 → 1.3.0

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.
package/README.md CHANGED
@@ -22,8 +22,9 @@ pnpm add message-nexus
22
22
  - **Retry Mechanism**: Automatic retry on request failure, configurable retry counts and delays
23
23
  - **Message Validation**: Runtime message format validation to prevent illegal messages
24
24
  - **Monitoring Metrics**: Built-in message statistics and performance monitoring
25
+ - **Message Interceptors**: Hook into the outgoing request and incoming response pipelines to modify payloads, inject metadata, or implement global logging
26
+ - **Cross-Context Errors**: Preserves error `name` and `stack` traces across communication bridges for seamless debugging
25
27
  - **Structured Logging**: Supports adjustable log levels (DEBUG, INFO, WARN, ERROR) and custom log handlers or simple loggers (like `console`) for easy debugging and production monitoring.
26
-
27
28
  - **Resource Management**: All drivers support the `destroy()` method to properly clean up resources.
28
29
 
29
30
  ## Quick Start
@@ -346,18 +347,50 @@ nexus.onError((error, context) => {
346
347
  })
347
348
  ```
348
349
 
350
+ ##### useRequestInterceptor()
351
+
352
+ Register a hook to intercept and potentially modify outgoing messages before they are sent to the driver. Interceptors can be synchronous or asynchronous.
353
+
354
+ ```typescript
355
+ nexus.useRequestInterceptor(
356
+ (message: Message) => Message | Promise<Message>
357
+ ): () => void
358
+ ```
359
+
360
+ **Example:**
361
+
362
+ ```typescript
363
+ const unsubscribe = nexus.useRequestInterceptor(async (message) => {
364
+ // Inject an authentication token into the metadata
365
+ message.metadata = { ...message.metadata, token: await getAuthToken() }
366
+ return message
367
+ })
368
+ ```
369
+
370
+ ##### useResponseInterceptor()
371
+
372
+ Register a hook to intercept and modify incoming messages before they are processed by handlers or resolve pending invokes.
373
+
374
+ ```typescript
375
+ nexus.useResponseInterceptor(
376
+ (message: Message) => Message | Promise<Message>
377
+ ): () => void
378
+ ```
379
+
349
380
  #### Errors
350
381
 
351
382
  MessageNexus provides a structured error system based on the JSON-RPC 2.0 specification.
352
383
 
353
384
  ##### NexusError
354
385
 
355
- A custom error class that includes a numeric code and optional data.
386
+ A custom error class that includes a numeric code and optional data. `MessageNexus` automatically preserves and serializes the `name` and `stack` properties of errors thrown in handlers, enabling seamless cross-context debugging.
356
387
 
357
388
  ```typescript
358
389
  class NexusError<D = any> extends Error {
359
390
  code: number // JSON-RPC or Nexus-specific error code
360
391
  data?: D // Optional additional error information
392
+ name: string // Preserved error name from original context
393
+ stack?: string // Preserved stack trace from original context
361
394
  }
362
395
  ```
363
396
 
package/dist/index.cjs CHANGED
@@ -1 +1 @@
1
- "use strict";var q=Object.create;var b=Object.defineProperty;var W=Object.getOwnPropertyDescriptor;var j=Object.getOwnPropertyNames;var F=Object.getPrototypeOf,G=Object.prototype.hasOwnProperty;var Q=(r,t)=>{for(var e in t)b(r,e,{get:t[e],enumerable:!0})},E=(r,t,e,s)=>{if(t&&typeof t=="object"||typeof t=="function")for(let n of j(t))!G.call(r,n)&&n!==e&&b(r,n,{get:()=>t[n],enumerable:!(s=W(t,n))||s.enumerable});return r};var U=(r,t,e)=>(e=r!=null?q(F(r)):{},E(t||!r||!r.__esModule?b(e,"default",{value:r,enumerable:!0}):e,r)),A=r=>E(b({},"__esModule",{value:!0}),r);var V={};Q(V,{BaseDriver:()=>c,BroadcastDriver:()=>f,LogLevel:()=>I,MittDriver:()=>v,NexusError:()=>u,NexusErrorCode:()=>T,PostMessageDriver:()=>y,WebSocketDriver:()=>M,createEmitter:()=>O,default:()=>k});module.exports=A(V);var c=class{constructor(){this.onMessage=null,this.onConnect=null,this.onDisconnect=null}send(t){throw new Error("Not implemented")}destroy(){}};var S="message-nexus-v1";function z(r){return typeof r=="object"&&r!==null&&"__messageBridge"in r&&r.__messageBridge===S}var f=class extends c{constructor(e){super();this.messageHandler=null;if(!e.channel)throw new Error("BroadcastDriver requires a channel name");this.channel=new BroadcastChannel(e.channel),this.messageHandler=s=>{if(!z(s.data))return;let{__messageBridge:n,...i}=s.data;this.onMessage?.(i)},this.channel.addEventListener("message",this.messageHandler)}send(e){let s={...e,__messageBridge:S};this.channel.postMessage(s)}destroy(){this.channel&&this.channel.close(),this.messageHandler&&(this.channel.removeEventListener("message",this.messageHandler),this.messageHandler=null),this.onMessage=null}};var x=Symbol("message_nexus_internal"),v=class extends c{constructor(t){super(),this.emitter=t,this.listener=()=>{};let e=s=>{s&&this.onMessage?.(s)};this.emitter.on(x,e),this.listener=()=>{this.emitter.off(x,e)}}send(t){this.emitter.emit(x,t)}destroy(){this.listener(),this.onMessage=null}};var H="message-nexus-v1";function X(r){return typeof r=="object"&&r!==null&&"__messageBridge"in r&&r.__messageBridge===H}var y=class extends c{constructor(e,s){super();this.messageHandler=null;if(!s||s==="*")throw new Error('PostMessageDriver requires explicit targetOrigin for security. Do not use "*" as it allows any origin.');this.targetWindow=e,this.targetOrigin=s,this.messageHandler=n=>{if(n.origin!==this.targetOrigin||!X(n.data))return;let{__messageBridge:i,...g}=n.data;this.onMessage?.(g)},window.addEventListener("message",this.messageHandler)}send(e){let s={...e,__messageBridge:H};this.targetWindow.postMessage(s,this.targetOrigin)}destroy(){this.messageHandler&&(window.removeEventListener("message",this.messageHandler),this.messageHandler=null),this.onMessage=null}};var I=(n=>(n.DEBUG="debug",n.INFO="info",n.WARN="warn",n.ERROR="error",n))(I||{});function _(r){if(r==null||typeof r!="object")return!1;let t=r;return typeof t.debug=="function"&&typeof t.info=="function"&&typeof t.warn=="function"&&typeof t.error=="function"&&typeof t.addHandler=="function"&&typeof t.setMinLevel=="function"&&typeof t.enable=="function"&&typeof t.disable=="function"&&typeof t.isEnabled=="function"}function N(r){if(r==null||typeof r!="object")return!1;let t=r;return typeof t.debug=="function"&&typeof t.info=="function"&&typeof t.warn=="function"&&typeof t.error=="function"}var h=class{constructor(t,e="info",s=!1){this.handlers=[];this.context=t,this.minLevel=e,this.enabled=s}enable(){this.enabled=!0}disable(){this.enabled=!1}isEnabled(){return this.enabled}addHandler(t){this.handlers.push(t)}setMinLevel(t){this.minLevel=t}shouldLog(t){let e=["debug","info","warn","error"];return e.indexOf(t)>=e.indexOf(this.minLevel)}log(t,e,s){if(!this.enabled||!this.shouldLog(t))return;let n={level:t,timestamp:Date.now(),message:e,metadata:s,context:this.context};this.handlers.forEach(i=>i(n))}debug(t,e){this.log("debug",t,e)}info(t,e){this.log("info",t,e)}warn(t,e){this.log("warn",t,e)}error(t,e){this.log("error",t,e)}};function $(){return new Date().toLocaleTimeString("zh-CN",{hour12:!1,hour:"2-digit",minute:"2-digit",second:"2-digit",fractionalSecondDigits:3})}var w=()=>r=>{let e=`[${$()}] [${r.level.toUpperCase()}] [${r.context||"app"}]`,s=r.level==="debug"?console.debug:r.level==="info"?console.info:r.level==="warn"?console.warn:console.error;r.metadata?s(e,r.message,r.metadata):s(e,r.message)};var D="message-nexus-v1",M=class extends c{constructor(e){super();this.ws=null;this.retryCount=0;this.reconnectTimer=null;this.isManuallyClosed=!1;this.url=e.url,this.reconnectEnabled=e.reconnect!==!1,this.maxRetries=(typeof e.reconnect=="object"?e.reconnect.maxRetries:void 0)??1/0,this.retryInterval=(typeof e.reconnect=="object"?e.reconnect.retryInterval:void 0)??5e3,this.logger=e.logger||new h("WebSocketDriver"),this.logger.addHandler(w()),this.onStatusChange=e.onStatusChange,this.connect()}connect(){this.onStatusChange?.("connecting"),this.ws=new WebSocket(this.url),this.ws.addEventListener("open",()=>{this.logger.info("WebSocket connected",{url:this.url}),this.retryCount=0,this.onStatusChange?.("connected"),this.onConnect?.()}),this.ws.addEventListener("message",e=>{try{let s=JSON.parse(e.data);if(typeof s=="object"&&s!==null&&"__messageBridge"in s&&s.__messageBridge===D){let{__messageBridge:n,...i}=s;this.logger.debug("Message received",{data:i}),this.onMessage?.(i)}else this.logger.debug("Ignored non-bridge message",{data:s})}catch(s){this.logger.error("Failed to parse WebSocket message",{error:s,data:e.data})}}),this.ws.addEventListener("error",e=>{this.logger.error("WebSocket error",{event:e}),this.onStatusChange?.("error")}),this.ws.addEventListener("close",()=>{this.logger.info("WebSocket connection closed",{manuallyClosed:this.isManuallyClosed,retryCount:this.retryCount,maxRetries:this.maxRetries}),!this.isManuallyClosed&&this.reconnectEnabled&&this.retryCount<this.maxRetries?this.scheduleReconnect():this.onStatusChange?.("disconnected"),this.onDisconnect?.()})}scheduleReconnect(){this.retryCount++;let e=3e4,s=this.retryInterval*Math.pow(2,this.retryCount),n=Math.min(s,e);this.logger.info("Reconnecting scheduled",{delay:n,attempt:this.retryCount,maxRetries:this.maxRetries,url:this.url}),this.onStatusChange?.("connecting"),this.reconnectTimer=window.setTimeout(()=>{this.connect()},n)}send(e){if(!this.ws||this.ws.readyState!==WebSocket.OPEN)throw this.logger.error("WebSocket is not open",{state:this.ws?.readyState,url:this.url}),new Error("WebSocket is not open");this.logger.debug("Sending message",{data:e});let s={...e,__messageBridge:D};this.ws.send(JSON.stringify(s))}close(){this.logger.info("Closing WebSocket connection",{url:this.url}),this.isManuallyClosed=!0,this.reconnectTimer&&(clearTimeout(this.reconnectTimer),this.reconnectTimer=null),this.ws&&(this.ws.close(),this.ws=null),this.onStatusChange?.("disconnected"),this.onDisconnect?.()}destroy(){this.close(),this.onMessage=null,this.onConnect=null,this.onDisconnect=null}};var C=U(require("mitt"),1);function O(){return(0,C.default)()}var T=(o=>(o[o.ParseError=-32700]="ParseError",o[o.InvalidRequest=-32600]="InvalidRequest",o[o.MethodNotFound=-32601]="MethodNotFound",o[o.InvalidParams=-32602]="InvalidParams",o[o.InternalError=-32603]="InternalError",o[o.Timeout=-32001]="Timeout",o[o.SendFailed=-32002]="SendFailed",o[o.InvalidResponse=-32003]="InvalidResponse",o))(T||{}),u=class r extends Error{constructor(t,e=-32603,s){super(t),this.name="NexusError",this.code=e,this.data=s,Object.setPrototypeOf(this,r.prototype)}},k=class{constructor(t,e){this.cleanupInterval=null;this.messageQueue=[];this.maxQueueSize=100;this.errorHandler=null;this.metrics={messagesSent:0,messagesReceived:0,messagesFailed:0,pendingMessages:0,queuedMessages:0,totalLatency:0,averageLatency:0};this.metricsCallbacks=new Set;this.driver=t,this.instanceId=e?.instanceId||crypto.randomUUID(),this.timeout=e?.timeout??1e4;let s=e?.logLevel??"info",n=e?.loggerEnabled??!1;if(e?.logger&&_(e.logger))this.logger=e.logger;else if(this.logger=new h("MessageNexus",s,n),e?.logger&&N(e.logger)){let i=e.logger;this.logger.addHandler(g=>{let{level:a,message:o,metadata:d}=g;a==="debug"?i.debug(o,d):a==="info"?i.info(o,d):a==="warn"?i.warn(o,d):a==="error"&&i.error(o,d)})}else n&&this.logger.addHandler(w());n&&(this.logger.enable(),this.logger.info("MessageNexus initialized",{instanceId:this.instanceId,timeout:this.timeout,logLevel:s})),this.pendingTasks=new Map,this.invokeHandlers=new Map,this.notificationHandlers=new Map,this.cleanupInterval=null,this.driver.onMessage=i=>this._handleIncoming(i),this.driver.onConnect=()=>{this.logger.info("Driver connected, flushing message queue"),this.flushQueue()}}async invoke(t){let e=crypto.randomUUID(),s,n,i,g,a,o=0,d=1e3;if(typeof t=="string")s=t,n=void 0,i=void 0,g={},a=this.timeout;else{let l=t;s=l.method,n=l.params,i=l.to,g=l.metadata||{},a=l.timeout??this.timeout,o=l.retryCount??0,d=l.retryDelay??1e3}let p=async l=>new Promise((L,R)=>{let B=setTimeout(()=>{this.pendingTasks.delete(e),this.metrics.messagesFailed++,this.metrics.pendingMessages--,R(new u(`Message timeout: ${s} (${e})`,-32001))},a);this.pendingTasks.set(e,{resolve:L,reject:R,timer:B,timestamp:Date.now()});let P={jsonrpc:"2.0",method:s,params:n,id:e},J={from:this.instanceId,to:i,metadata:{...g,timestamp:Date.now()},payload:P},K=l>=o;this._sendMessage(J,!K)}).catch(L=>{if(l<o)return new Promise(R=>setTimeout(()=>R(p(l+1)),d*(l+1)));throw this.metrics.messagesFailed++,this.metrics.pendingMessages--,L});return p(0)}_sendMessage(t,e=!1){let s=t.payload,n="method"in s,i="id"in s?String(s.id):void 0,g=n?s.method:"RESPONSE";try{this.driver.send(t),this.metrics.messagesSent++,n&&i!==void 0&&this.metrics.pendingMessages++,this.logger.debug("Message sent",{messageId:i,type:g})}catch(a){let o=a instanceof Error?a:new Error(String(a));this.metrics.messagesFailed++,this.logger.error("Failed to send message",{error:o.message,messageId:i}),this.errorHandler?.(o,{message:t}),e?this.logger.debug("Message failed but skipQueue is true (likely retrying)",{messageId:i}):this.messageQueue.length<this.maxQueueSize?(this.messageQueue.push(t),this.logger.debug("Message queued",{messageId:i,queueSize:this.messageQueue.length+1})):(this.logger.warn("Message queue full, dropping oldest message",{queueSize:this.messageQueue.length}),this.messageQueue.shift(),this.messageQueue.push(t))}this.metrics.queuedMessages=this.messageQueue.length,this._notifyMetrics()}onError(t){return this.errorHandler=t,()=>{this.errorHandler=null}}flushQueue(){for(;this.messageQueue.length>0;){let t=this.messageQueue.shift();if(t)try{this.driver.send(t)}catch{this.messageQueue.unshift(t);break}}this.metrics.queuedMessages=this.messageQueue.length,this._notifyMetrics()}notify(t){let e,s,n,i;if(typeof t=="string")e=t,s=void 0,n=void 0,i={};else{let o=t;e=o.method,s=o.params,n=o.to,i=o.metadata||{}}let g={jsonrpc:"2.0",method:e,params:s},a={from:this.instanceId,to:n,metadata:{...i,timestamp:Date.now()},payload:g};this._sendMessage(a)}_validateMessage(t){if(!t||typeof t!="object")return!1;let e=t;if(typeof e.from!="string"||e.to!==void 0&&typeof e.to!="string"||e.metadata!==void 0&&typeof e.metadata!="object")return!1;let s=e.payload;if(!s||typeof s!="object"||s.jsonrpc!=="2.0")return!1;let n="method"in s,i="result"in s||"error"in s;return!(!n&&!i)}async _handleIncoming(t){if(!this._validateMessage(t)){this.logger.error("Invalid message format received",{data:t}),this.errorHandler?.(new Error("Invalid message format received"),{data:t}),this.metrics.messagesFailed++;return}let e=t,s=e.payload;if(e.to&&e.to!==this.instanceId){this.logger.debug("Message filtered: not for this instance",{messageId:"id"in s?s.id:void 0,to:e.to,instanceId:this.instanceId});return}if("result"in s||"error"in s){let n=s,i=String(n.id);if(this.pendingTasks.has(i)){let{resolve:g,reject:a,timer:o,timestamp:d}=this.pendingTasks.get(i);clearTimeout(o),this.pendingTasks.delete(i);let p=Date.now()-d;if(this.metrics.messagesReceived++,this.metrics.pendingMessages--,this.metrics.totalLatency+=p,this.metrics.averageLatency=this.metrics.totalLatency/this.metrics.messagesReceived,this.logger.debug("Response received",{messageId:i,latency:p}),n.error){let l=new u(n.error.message,n.error.code,n.error.data);a(l)}else g(n.result);this._notifyMetrics()}else this.logger.warn("Orphaned response received",{messageId:i});return}if("method"in s)if("id"in s){let n=s,i=String(n.id);this.logger.debug("Invoke message received",{messageId:i,type:n.method,from:e.from});let g={messageId:i,from:e.from,to:e.to,metadata:e.metadata},a=this.invokeHandlers.get(n.method);if(a)try{let o=await a(n.params,g);this._reply(i,e.from,o)}catch(o){this._replyError(i,e.from,o)}else{let o=new u(`Method not found: ${n.method}`,-32601);this._replyError(i,e.from,o)}}else{let n=s;this.logger.debug("Notification message received",{type:n.method,from:e.from});let i={from:e.from,to:e.to,metadata:e.metadata},g=this.notificationHandlers.get(n.method);g&&g.forEach(a=>{try{a(n.params,i)}catch(o){this.logger.error("Error in notification handler",{error:String(o)})}})}}getMetrics(){return{...this.metrics,pendingMessages:this.pendingTasks.size}}onMetrics(t){return this.metricsCallbacks.add(t),()=>this.metricsCallbacks.delete(t)}_notifyMetrics(){let t=this.getMetrics();this.metricsCallbacks.forEach(e=>e(t))}handle(t,e){return this.invokeHandlers.has(t)&&this.logger.warn(`Overriding existing handler for method: ${t}`),this.invokeHandlers.set(t,e),()=>this.invokeHandlers.delete(t)}removeHandler(t){this.invokeHandlers.delete(t)}onNotification(t,e){return this.notificationHandlers.has(t)||this.notificationHandlers.set(t,new Set),this.notificationHandlers.get(t).add(e),()=>this.offNotification(t,e)}offNotification(t,e){let s=this.notificationHandlers.get(t);s&&(s.delete(e),s.size===0&&this.notificationHandlers.delete(t))}_reply(t,e,s){let n={jsonrpc:"2.0",id:t,result:s},i={from:this.instanceId,to:e,payload:n};this.driver.send(i)}_replyError(t,e,s){let n=s instanceof u?s:s instanceof Error?new u(s.message,-32603):new u(String(s),-32603),i={jsonrpc:"2.0",id:t,error:{code:n.code,message:n.message,data:n.data}},g={from:this.instanceId,to:e,payload:i};this.driver.send(g)}destroy(){this.logger.info("MessageNexus destroying",{instanceId:this.instanceId,pendingMessages:this.pendingTasks.size,queuedMessages:this.messageQueue.length,metrics:this.getMetrics()}),this.driver.destroy?.(),this.cleanupInterval&&(clearInterval(this.cleanupInterval),this.cleanupInterval=null),this.invokeHandlers.clear(),this.notificationHandlers.clear(),this.metricsCallbacks.clear()}};0&&(module.exports={BaseDriver,BroadcastDriver,LogLevel,MittDriver,NexusError,NexusErrorCode,PostMessageDriver,WebSocketDriver,createEmitter});
1
+ "use strict";var K=Object.create;var S=Object.defineProperty;var F=Object.getOwnPropertyDescriptor;var W=Object.getOwnPropertyNames;var z=Object.getPrototypeOf,G=Object.prototype.hasOwnProperty;var A=(i,e)=>{for(var t in e)S(i,t,{get:e[t],enumerable:!0})},_=(i,e,t,s)=>{if(e&&typeof e=="object"||typeof e=="function")for(let n of W(e))!G.call(i,n)&&n!==t&&S(i,n,{get:()=>e[n],enumerable:!(s=F(e,n))||s.enumerable});return i};var Q=(i,e,t)=>(t=i!=null?K(z(i)):{},_(e||!i||!i.__esModule?S(t,"default",{value:i,enumerable:!0}):t,i)),U=i=>_(S({},"__esModule",{value:!0}),i);var Y={};A(Y,{BaseDriver:()=>m,BroadcastDriver:()=>k,EventRouter:()=>w,LogLevel:()=>C,MessageQueue:()=>E,MiddlewarePipeline:()=>M,MittDriver:()=>b,NexusError:()=>g,NexusErrorCode:()=>v,PostMessageDriver:()=>x,RpcScheduler:()=>I,WebSocketDriver:()=>R,createEmitter:()=>B,default:()=>H});module.exports=U(Y);var m=class{constructor(){this.onMessage=null,this.onConnect=null,this.onDisconnect=null}send(e){throw new Error("Not implemented")}destroy(){}};var h="message-nexus-v1";var v=(o=>(o[o.ParseError=-32700]="ParseError",o[o.InvalidRequest=-32600]="InvalidRequest",o[o.MethodNotFound=-32601]="MethodNotFound",o[o.InvalidParams=-32602]="InvalidParams",o[o.InternalError=-32603]="InternalError",o[o.Timeout=-32001]="Timeout",o[o.SendFailed=-32002]="SendFailed",o[o.InvalidResponse=-32003]="InvalidResponse",o[o.InstanceDestroyed=-32004]="InstanceDestroyed",o))(v||{}),g=class i extends Error{constructor(e,t=-32603,s,n,r){super(e),this.name=n||"NexusError",this.code=t,this.data=s,r&&(this.stack=r),Object.setPrototypeOf(this,i.prototype)}};function X(i){return typeof i=="object"&&i!==null&&"__messageBridge"in i&&i.__messageBridge===h}var k=class extends m{constructor(t){super();this.messageHandler=null;if(!t.channel)throw new Error("BroadcastDriver requires a channel name");this.channel=new BroadcastChannel(t.channel),this.messageHandler=s=>{if(!X(s.data))return;let{__messageBridge:n,...r}=s.data;this.onMessage?.(r)},this.channel.addEventListener("message",this.messageHandler)}send(t){let s={...t,__messageBridge:h};try{this.channel.postMessage(s)}catch(n){throw typeof DOMException<"u"&&n instanceof DOMException&&n.name==="DataCloneError"?new g("Message payload cannot be cloned",-32602):n}}destroy(){this.channel&&this.channel.close(),this.messageHandler&&(this.channel.removeEventListener("message",this.messageHandler),this.messageHandler=null),this.onMessage=null}};var D=Symbol("message_nexus_internal"),b=class extends m{constructor(e){super(),this.emitter=e,this.listener=()=>{};let t=s=>{s&&this.onMessage?.(s)};this.emitter.on(D,t),this.listener=()=>{this.emitter.off(D,t)}}send(e){this.emitter.emit(D,e)}destroy(){this.listener(),this.onMessage=null}};function $(i){return typeof i=="object"&&i!==null&&"__messageBridge"in i&&i.__messageBridge===h}var x=class extends m{constructor(t,s){super();this.messageHandler=null;if(!s||s==="*")throw new Error('PostMessageDriver requires explicit targetOrigin for security. Do not use "*" as it allows any origin.');this.targetWindow=t,this.targetOrigin=s,this.messageHandler=n=>{if(n.origin!==this.targetOrigin||!$(n.data))return;let{__messageBridge:r,...a}=n.data;this.onMessage?.(a)},window.addEventListener("message",this.messageHandler)}send(t){let s={...t,__messageBridge:h};try{this.targetWindow.postMessage(s,this.targetOrigin)}catch(n){throw typeof DOMException<"u"&&n instanceof DOMException&&n.name==="DataCloneError"?new g("Message payload cannot be cloned",-32602):n}}destroy(){this.messageHandler&&(window.removeEventListener("message",this.messageHandler),this.messageHandler=null),this.onMessage=null}};var C=(n=>(n.DEBUG="debug",n.INFO="info",n.WARN="warn",n.ERROR="error",n))(C||{});function q(i){if(i==null||typeof i!="object")return!1;let e=i;return typeof e.debug=="function"&&typeof e.info=="function"&&typeof e.warn=="function"&&typeof e.error=="function"&&typeof e.addHandler=="function"&&typeof e.setMinLevel=="function"&&typeof e.enable=="function"&&typeof e.disable=="function"&&typeof e.isEnabled=="function"}function P(i){if(i==null||typeof i!="object")return!1;let e=i;return typeof e.debug=="function"&&typeof e.info=="function"&&typeof e.warn=="function"&&typeof e.error=="function"}var y=class{constructor(e,t="info",s=!1){this.handlers=[];this.context=e,this.minLevel=t,this.enabled=s}enable(){this.enabled=!0}disable(){this.enabled=!1}isEnabled(){return this.enabled}addHandler(e){this.handlers.push(e)}setMinLevel(e){this.minLevel=e}shouldLog(e){let t=["debug","info","warn","error"];return t.indexOf(e)>=t.indexOf(this.minLevel)}log(e,t,s){if(!this.enabled||!this.shouldLog(e))return;let n={level:e,timestamp:Date.now(),message:t,metadata:s,context:this.context};this.handlers.forEach(r=>r(n))}debug(e,t){this.log("debug",e,t)}info(e,t){this.log("info",e,t)}warn(e,t){this.log("warn",e,t)}error(e,t){this.log("error",e,t)}};function V(){return new Date().toLocaleTimeString("zh-CN",{hour12:!1,hour:"2-digit",minute:"2-digit",second:"2-digit",fractionalSecondDigits:3})}var T=()=>i=>{let t=`[${V()}] [${i.level.toUpperCase()}] [${i.context||"app"}]`,s=i.level==="debug"?console.debug:i.level==="info"?console.info:i.level==="warn"?console.warn:console.error;i.metadata?s(t,i.message,i.metadata):s(t,i.message)};var R=class extends m{constructor(t){super();this.ws=null;this.retryCount=0;this.reconnectTimer=null;this.isManuallyClosed=!1;this.url=t.url,this.reconnectEnabled=t.reconnect!==!1,this.maxRetries=(typeof t.reconnect=="object"?t.reconnect.maxRetries:void 0)??1/0,this.retryInterval=(typeof t.reconnect=="object"?t.reconnect.retryInterval:void 0)??5e3,this.logger=t.logger||new y("WebSocketDriver"),this.logger.addHandler(T()),this.onStatusChange=t.onStatusChange,this.connect()}connect(){this.onStatusChange?.("connecting"),this.ws=new WebSocket(this.url),this.ws.addEventListener("open",()=>{this.logger.info("WebSocket connected",{url:this.url}),this.retryCount=0,this.onStatusChange?.("connected"),this.onConnect?.()}),this.ws.addEventListener("message",t=>{try{let s=JSON.parse(t.data);if(typeof s=="object"&&s!==null&&"__messageBridge"in s&&s.__messageBridge===h){let{__messageBridge:n,...r}=s;this.logger.debug("Message received",{data:r}),this.onMessage?.(r)}else this.logger.debug("Ignored non-bridge message",{data:s})}catch(s){this.logger.error("Failed to parse WebSocket message",{error:s,data:t.data})}}),this.ws.addEventListener("error",t=>{this.logger.error("WebSocket error",{event:t}),this.onStatusChange?.("error")}),this.ws.addEventListener("close",()=>{this.logger.info("WebSocket connection closed",{manuallyClosed:this.isManuallyClosed,retryCount:this.retryCount,maxRetries:this.maxRetries}),!this.isManuallyClosed&&this.reconnectEnabled&&this.retryCount<this.maxRetries?this.scheduleReconnect():this.onStatusChange?.("disconnected"),this.onDisconnect?.()})}scheduleReconnect(){this.retryCount++;let t=3e4,s=this.retryInterval*Math.pow(2,this.retryCount),n=Math.min(s,t)+Math.random()*1e3;this.logger.info("Reconnecting scheduled",{delay:n,attempt:this.retryCount,maxRetries:this.maxRetries,url:this.url}),this.onStatusChange?.("connecting"),this.reconnectTimer=window.setTimeout(()=>{this.connect()},n)}send(t){if(!this.ws||this.ws.readyState!==WebSocket.OPEN)throw this.logger.error("WebSocket is not open",{state:this.ws?.readyState,url:this.url}),new Error("WebSocket is not open");this.logger.debug("Sending message",{data:t});let s={...t,__messageBridge:h};this.ws.send(JSON.stringify(s))}close(){this.logger.info("Closing WebSocket connection",{url:this.url}),this.isManuallyClosed=!0,this.reconnectTimer&&(clearTimeout(this.reconnectTimer),this.reconnectTimer=null),this.ws&&(this.ws.close(),this.ws=null),this.onStatusChange?.("disconnected"),this.onDisconnect?.()}destroy(){this.close(),this.onMessage=null,this.onConnect=null,this.onDisconnect=null}};var O=Q(require("mitt"),1);function B(){return(0,O.default)()}function f(i,e){try{return i()}catch(t){if(e)try{e(t)}catch(s){console.error("Safe execution error handler failed:",s)}return}}var E=class{constructor(e={}){this.queue=[];this.maxQueueSize=e.maxQueueSize??100,this.logger=e.logger,this.onMessageDropped=e.onMessageDropped}enqueue(e){if(this.queue.length<this.maxQueueSize)this.queue.push(e),this.logger&&"debug"in this.logger&&this.logger.debug("Message queued",{queueSize:this.queue.length});else{this.logger&&"warn"in this.logger&&this.logger.warn("Message queue full, dropping oldest message",{queueSize:this.queue.length});let t=this.queue.shift();t&&this.onMessageDropped&&this.onMessageDropped(t),this.queue.push(e)}}dequeue(){return this.queue.shift()}unshift(e){this.queue.unshift(e)}get length(){return this.queue.length}get isFull(){return this.queue.length>=this.maxQueueSize}get isEmpty(){return this.queue.length===0}clear(){this.queue=[]}};var I=class{constructor(e={}){this.pendingTasks=new Map;this.defaultTimeout=e.defaultTimeout??1e4}createTask(e,t,s=this.defaultTimeout,n){return new Promise((r,a)=>{let l=setTimeout(()=>{this.pendingTasks.delete(e),n&&n(e),a(new g(`Message timeout: ${t} (${e})`,-32001))},s);this.pendingTasks.set(e,{resolve:r,reject:a,timer:l,timestamp:Date.now()})})}hasTask(e){return this.pendingTasks.has(e)}resolveTask(e,t){let s=this.pendingTasks.get(e);if(s){clearTimeout(s.timer),this.pendingTasks.delete(e);let n=Date.now()-s.timestamp;return s.resolve(t),{latency:n}}return null}rejectTask(e,t){let s=this.pendingTasks.get(e);if(s){clearTimeout(s.timer),this.pendingTasks.delete(e);let n=Date.now()-s.timestamp;return s.reject(t),{latency:n}}return null}get size(){return this.pendingTasks.size}clearTasks(e){for(let[t,s]of this.pendingTasks.entries())clearTimeout(s.timer),e!==void 0&&s.reject(e);this.pendingTasks.clear()}};var M=class{constructor(){this.middlewares=[]}use(e){return this.middlewares.push(e),()=>{this.middlewares=this.middlewares.filter(t=>t!==e)}}async execute(e){let t=-1,s=async n=>{if(n<=t)throw new Error("next() called multiple times");t=n;let r=this.middlewares[n];n!==this.middlewares.length&&r&&await r(e,s.bind(null,n+1))};await s(0)}clear(){this.middlewares=[]}get isEmpty(){return this.middlewares.length===0}};var w=class{constructor(){this.invokeHandlers=new Map;this.notificationHandlers=new Map}handle(e,t){return this.invokeHandlers.set(e,t),()=>this.invokeHandlers.delete(e)}removeHandler(e){return this.invokeHandlers.delete(e)}onNotification(e,t){return this.notificationHandlers.has(e)||this.notificationHandlers.set(e,new Set),this.notificationHandlers.get(e).add(t),()=>this.offNotification(e,t)}offNotification(e,t){let s=this.notificationHandlers.get(e);s&&(s.delete(t),s.size===0&&this.notificationHandlers.delete(e))}hasInvokeHandler(e){return this.invokeHandlers.has(e)}getInvokeHandler(e){return this.invokeHandlers.get(e)}getNotificationHandlers(e){return this.notificationHandlers.get(e)}get invokeHandlersCount(){return this.invokeHandlers.size}get notificationHandlersCount(){return this.notificationHandlers.size}clear(e){(!e||e==="invoke")&&this.invokeHandlers.clear(),(!e||e==="notification")&&this.notificationHandlers.clear()}static validateMessage(e){if(!e||typeof e!="object")return!1;let t=e;if(typeof t.from!="string"||t.from.trim()===""||t.to!==void 0&&(typeof t.to!="string"||t.to.trim()==="")||t.metadata!==void 0&&(typeof t.metadata!="object"||t.metadata===null))return!1;let s=t.payload;if(!s||typeof s!="object"||Array.isArray(s)||s.jsonrpc!=="2.0")return!1;if("id"in s){let l=s.id;if(l!==null&&typeof l!="string"&&typeof l!="number")return!1}let n=typeof s.method=="string"&&s.method.trim()!=="",r="result"in s,a=s.error!==void 0&&typeof s.error=="object"&&s.error!==null;return n?!(r||a):r||a?!(!("id"in s)||r&&a||a&&(typeof s.error.code!="number"||typeof s.error.message!="string")):!1}};var H=class{constructor(e,t){this.errorHandler=null;this.metrics={messagesSent:0,messagesReceived:0,messagesFailed:0,pendingMessages:0,queuedMessages:0,totalLatency:0,averageLatency:0};this.metricsCallbacks=new Set;this.metricsThrottleTimer=null;this._isDestroyed=!1;this.driver=e,this.instanceId=t?.instanceId||crypto.randomUUID(),this.timeout=t?.timeout??1e4;let s=t?.logLevel??"info",n=t?.loggerEnabled??!1;if(t?.logger&&q(t.logger))this.logger=t.logger;else if(this.logger=new y("MessageNexus",s,n),t?.logger&&P(t.logger)){let r=t.logger;this.logger.addHandler(a=>{let{level:l,message:c,metadata:o}=a;l==="debug"?r.debug(c,o):l==="info"?r.info(c,o):l==="warn"?r.warn(c,o):l==="error"&&r.error(c,o)})}else n&&this.logger.addHandler(T());this.queue=new E({maxQueueSize:t?.maxQueueSize??100,logger:this.logger,onMessageDropped:r=>{let a=r.payload;if(a&&"id"in a){let l=String(a.id);this.scheduler.rejectTask(l,new g("Message dropped due to queue overflow",-32002))}}}),this.scheduler=new I({defaultTimeout:this.timeout}),this.requestPipeline=new M,this.responsePipeline=new M,this.router=new w,n&&(this.logger.enable(),this.logger.info("MessageNexus initialized",{instanceId:this.instanceId,timeout:this.timeout,logLevel:s})),this.driver.onMessage=r=>this._handleIncoming(r),this.driver.onConnect=()=>{this.logger.info("Driver connected, flushing message queue"),this.flushQueue()}}async invoke(e){let t=crypto.randomUUID(),s,n,r,a,l,c=0,o=1e3;if(typeof e=="string")s=e,n=void 0,r=void 0,a={},l=this.timeout;else{let d=e;s=d.method,n=d.params,r=d.to,a=d.metadata||{},l=d.timeout??this.timeout,c=d.retryCount??0,o=d.retryDelay??1e3}let p=async d=>{try{let L=this.scheduler.createTask(t,s,l,()=>{this.metrics.messagesFailed++}),N={jsonrpc:"2.0",method:s,params:n,id:t},j={from:this.instanceId,to:r,metadata:{...a,timestamp:Date.now()},payload:N},J=d>=c;return this._sendMessage(j,!J).catch(()=>{}),await L}catch(L){if(this._isDestroyed)throw L;if(d<c)return new Promise(N=>setTimeout(()=>N(p(d+1)),o*(d+1)));throw this.metrics.messagesFailed++,L}};return p(0)}async _sendMessage(e,t=!1){let s={message:e,direction:"outbound",nexusInstanceId:this.instanceId};try{this.requestPipeline.isEmpty||await this.requestPipeline.execute(s)}catch(o){this.logger.error("Request interceptor failed",{error:String(o)}),this.metrics.messagesFailed++;let p=o instanceof Error?o:new Error(String(o));f(()=>this.errorHandler?.(p,{message:s.message}));let d=s.message.payload;d&&"id"in d&&this.scheduler.rejectTask(String(d.id),o instanceof Error?o:new Error(String(o)));return}let n=s.message,r=n.payload,a="method"in r,l="id"in r?String(r.id):void 0,c=a?r.method:"RESPONSE";try{this.driver.send(n),this.metrics.messagesSent++,this.logger.debug("Message sent",{messageId:l,type:c})}catch(o){let p=o instanceof Error?o:new Error(String(o));this.metrics.messagesFailed++,this.logger.error("Failed to send message",{error:p.message,messageId:l}),f(()=>this.errorHandler?.(p,{message:n}));let d=p.name==="NexusError"&&p.code===-32602&&p.message==="Message payload cannot be cloned";!t&&!d?this.queue.enqueue(n):this.logger.debug(d?"Message dropped due to data error":"Message failed but skipQueue is true (likely retrying)",{messageId:l})}this._notifyMetrics()}useRequestInterceptor(e){return this.requestPipeline.use(async(t,s)=>{t.message=await Promise.race([e(t.message),new Promise((n,r)=>setTimeout(()=>r(new Error("Request interceptor timed out")),3e3))]),await s()})}useResponseInterceptor(e){return this.responsePipeline.use(async(t,s)=>{t.message=await Promise.race([e(t.message),new Promise((n,r)=>setTimeout(()=>r(new Error("Response interceptor timed out")),3e3))]),await s()})}onError(e){return this.errorHandler=e,()=>{this.errorHandler=null}}flushQueue(){for(;!this.queue.isEmpty;){let e=this.queue.dequeue();if(e)try{this.driver.send(e)}catch(t){let s=t instanceof Error?t:new Error(String(t));if(s.name==="NexusError"&&s.code===-32602&&s.message==="Message payload cannot be cloned"){this.logger.error("Message payload cannot be cloned during flush, dropping",{error:s.message});continue}this.queue.unshift(e);break}}this._notifyMetrics(!0)}async notify(e){let t,s,n,r;if(typeof e=="string")t=e,s=void 0,n=void 0,r={};else{let c=e;t=c.method,s=c.params,n=c.to,r=c.metadata||{}}let a={jsonrpc:"2.0",method:t,params:s},l={from:this.instanceId,to:n,metadata:{...r,timestamp:Date.now()},payload:a};await this._sendMessage(l)}async _handleIncoming(e){if(!w.validateMessage(e)){this.logger.error("Invalid message format received",{data:e}),f(()=>this.errorHandler?.(new Error("Invalid message format received"),{data:e})),this.metrics.messagesFailed++;return}let t={message:e,direction:"inbound",nexusInstanceId:this.instanceId};try{this.responsePipeline.isEmpty||await this.responsePipeline.execute(t)}catch(r){this.logger.error("Response interceptor failed",{error:String(r)}),this.metrics.messagesFailed++;let a=r instanceof Error?r:new Error(String(r));f(()=>this.errorHandler?.(a,{message:t.message}));return}let s=t.message,n=s.payload;if(s.to&&s.to!==this.instanceId){this.logger.debug("Message filtered: not for this instance",{messageId:"id"in n?n.id:void 0,to:s.to,instanceId:this.instanceId});return}if("result"in n||"error"in n){let r=n,a=String(r.id);if(this.scheduler.hasTask(a)){let l;if(r.error){let c=new g(r.error.message,r.error.code,r.error.data,r.error.name,r.error.stack);l=this.scheduler.rejectTask(a,c)}else l=this.scheduler.resolveTask(a,r.result);l&&(this.metrics.messagesReceived++,this.metrics.totalLatency+=l.latency,this.metrics.averageLatency=this.metrics.totalLatency/this.metrics.messagesReceived,this.logger.debug("Response received",{messageId:a,latency:l.latency}),this._notifyMetrics())}else this.logger.warn("Orphaned response received",{messageId:a});return}if("method"in n)if("id"in n){let r=n,a=String(r.id);this.logger.debug("Invoke message received",{messageId:a,type:r.method,from:s.from});let l={messageId:a,from:s.from,to:s.to,metadata:s.metadata},c=this.router.getInvokeHandler(r.method);if(c)try{let o=await c(r.params,l);await this._reply(a,s.from,o)}catch(o){await this._replyError(a,s.from,o)}else{let o=new g(`Method not found: ${r.method}`,-32601);await this._replyError(a,s.from,o)}}else{let r=n;this.logger.debug("Notification message received",{type:r.method,from:s.from});let a={from:s.from,to:s.to,metadata:s.metadata},l=this.router.getNotificationHandlers(r.method);l&&l.forEach(c=>{f(()=>c(r.params,a),o=>{this.logger.error("Error in notification handler",{error:String(o)})})})}}getMetrics(){return{...this.metrics,pendingMessages:this.scheduler.size,queuedMessages:this.queue.length}}getQueueLength(){return this.queue.length}getQueueSnapshot(){return this.queue.queue.slice()}getPendingTasksCount(){return this.scheduler.size}getHandlersCount(){return this.router.invokeHandlersCount}getNotificationMethodsCount(){return this.router.notificationHandlersCount}hasHandler(e){return this.router.hasInvokeHandler(e)}onMetrics(e){return this.metricsCallbacks.add(e),()=>this.metricsCallbacks.delete(e)}_notifyMetrics(e=!1){if(e){this.metricsThrottleTimer&&(clearTimeout(this.metricsThrottleTimer),this.metricsThrottleTimer=null);let t=this.getMetrics();this.metricsCallbacks.forEach(s=>{f(()=>s(t),n=>{this.logger.error("Error in metrics callback",{error:String(n)})})});return}this.metricsThrottleTimer||(this.metricsThrottleTimer=setTimeout(()=>{this.metricsThrottleTimer=null;let t=this.getMetrics();this.metricsCallbacks.forEach(s=>{f(()=>s(t),n=>{this.logger.error("Error in metrics callback",{error:String(n)})})})},100))}handle(e,t){return this.router.hasInvokeHandler(e)&&this.logger.warn(`Overriding existing handler for method: ${e}`),this.router.handle(e,t)}removeHandler(e){this.router.removeHandler(e)}onNotification(e,t){return this.router.onNotification(e,t)}offNotification(e,t){this.router.offNotification(e,t)}async _reply(e,t,s){let n={jsonrpc:"2.0",id:e,result:s},r={from:this.instanceId,to:t,payload:n};await this._sendMessage(r)}async _replyError(e,t,s){let n=s instanceof g?s:s instanceof Error?new g(s.message,-32603,void 0,s.name,s.stack):new g(String(s),-32603),r={jsonrpc:"2.0",id:e,error:{code:n.code,message:n.message,data:n.data,name:n.name,stack:n.stack}},a={from:this.instanceId,to:t,payload:r};await this._sendMessage(a)}clearHandlers(e="all"){e==="all"?this.router.clear():this.router.clear(e),this.logger.info(`Cleared handlers of type: ${e}`)}destroy(){this._isDestroyed=!0,this.metricsThrottleTimer&&(clearTimeout(this.metricsThrottleTimer),this.metricsThrottleTimer=null),this._notifyMetrics(!0),this.logger.info("MessageNexus destroying",{instanceId:this.instanceId,pendingMessages:this.scheduler.size,queuedMessages:this.queue.length,metrics:this.getMetrics()}),this.driver.destroy?.(),this.scheduler.clearTasks(new g("MessageNexus instance destroyed",-32004)),this.queue.clear(),this.router.clear(),this.requestPipeline.clear(),this.responsePipeline.clear(),this.metricsCallbacks.clear()}};0&&(module.exports={BaseDriver,BroadcastDriver,EventRouter,LogLevel,MessageQueue,MiddlewarePipeline,MittDriver,NexusError,NexusErrorCode,PostMessageDriver,RpcScheduler,WebSocketDriver,createEmitter});
package/dist/index.d.cts CHANGED
@@ -19,6 +19,8 @@ interface JsonRpcResponse {
19
19
  code: number;
20
20
  message: string;
21
21
  data?: unknown;
22
+ name?: string;
23
+ stack?: string;
22
24
  };
23
25
  id: JsonRpcId;
24
26
  }
@@ -144,9 +146,112 @@ declare class WebSocketDriver extends BaseDriver {
144
146
 
145
147
  declare function createEmitter(): Emitter<Record<EventType, Message>>;
146
148
 
149
+ /**
150
+ * Standard JSON-RPC 2.0 and Nexus-specific error codes.
151
+ */
152
+ declare enum NexusErrorCode {
153
+ ParseError = -32700,
154
+ InvalidRequest = -32600,
155
+ MethodNotFound = -32601,
156
+ InvalidParams = -32602,
157
+ InternalError = -32603,
158
+ Timeout = -32001,
159
+ SendFailed = -32002,
160
+ InvalidResponse = -32003,
161
+ InstanceDestroyed = -32004
162
+ }
163
+ /**
164
+ * Custom error class for MessageNexus.
165
+ */
166
+ declare class NexusError<D = any> extends Error {
167
+ readonly code: number;
168
+ readonly data?: D;
169
+ constructor(message: string, code?: number, data?: D, name?: string, stack?: string);
170
+ }
171
+
172
+ interface MessageQueueOptions {
173
+ maxQueueSize?: number;
174
+ logger?: LoggerInterface | SimpleLogger;
175
+ onMessageDropped?: (message: Message) => void;
176
+ }
177
+ declare class MessageQueue {
178
+ private queue;
179
+ private maxQueueSize;
180
+ private logger?;
181
+ private onMessageDropped?;
182
+ constructor(options?: MessageQueueOptions);
183
+ enqueue(message: Message): void;
184
+ dequeue(): Message | undefined;
185
+ unshift(message: Message): void;
186
+ get length(): number;
187
+ get isFull(): boolean;
188
+ get isEmpty(): boolean;
189
+ clear(): void;
190
+ }
191
+
192
+ interface RpcSchedulerOptions {
193
+ defaultTimeout?: number;
194
+ }
195
+ declare class RpcScheduler {
196
+ private pendingTasks;
197
+ private defaultTimeout;
198
+ constructor(options?: RpcSchedulerOptions);
199
+ createTask<R = unknown>(id: string, method: string, timeout?: number, onTimeout?: (id: string) => void): Promise<R>;
200
+ hasTask(id: string): boolean;
201
+ resolveTask(id: string, result: unknown): {
202
+ latency: number;
203
+ } | null;
204
+ rejectTask(id: string, error: unknown): {
205
+ latency: number;
206
+ } | null;
207
+ get size(): number;
208
+ clearTasks(error?: unknown): void;
209
+ }
210
+
211
+ type MiddlewareContext = {
212
+ message: Message;
213
+ direction: 'inbound' | 'outbound';
214
+ nexusInstanceId: string;
215
+ [key: string]: unknown;
216
+ };
217
+ type Next = () => Promise<void>;
218
+ type Middleware = (ctx: MiddlewareContext, next: Next) => Promise<void>;
219
+ declare class MiddlewarePipeline {
220
+ private middlewares;
221
+ use(middleware: Middleware): () => void;
222
+ execute(ctx: MiddlewareContext): Promise<void>;
223
+ clear(): void;
224
+ get isEmpty(): boolean;
225
+ }
226
+
227
+ interface InvokeContext {
228
+ messageId?: string;
229
+ from: string;
230
+ to?: string;
231
+ metadata?: Record<string, unknown>;
232
+ }
233
+ type InvokeHandler<P = unknown, R = unknown> = (params: P, context: InvokeContext) => R | Promise<R>;
234
+ type NotificationHandler<P = unknown> = (params: P, context: InvokeContext) => void;
235
+ declare class EventRouter<InvokeMap extends object = Record<string, any>, NotificationMap extends object = Record<string, any>> {
236
+ private invokeHandlers;
237
+ private notificationHandlers;
238
+ handle<K extends keyof InvokeMap>(method: string, handler: InvokeHandler<any, any>): () => boolean;
239
+ removeHandler(method: string): boolean;
240
+ onNotification<K extends keyof NotificationMap>(method: string, handler: NotificationHandler<any>): () => void;
241
+ offNotification<K extends keyof NotificationMap>(method: string, handler: NotificationHandler<any>): void;
242
+ hasInvokeHandler(method: string): boolean;
243
+ getInvokeHandler(method: string): InvokeHandler | undefined;
244
+ getNotificationHandlers(method: string): Set<NotificationHandler> | undefined;
245
+ get invokeHandlersCount(): number;
246
+ get notificationHandlersCount(): number;
247
+ clear(type?: 'invoke' | 'notification'): void;
248
+ static validateMessage(data: unknown): data is Message;
249
+ }
250
+
147
251
  interface MessageNexusOptions {
148
252
  instanceId?: string;
149
253
  timeout?: number;
254
+ maxQueueSize?: number;
150
255
  logger?: LoggerInterface | SimpleLogger;
151
256
  loggerEnabled?: boolean;
152
257
  logLevel?: LogLevel;
@@ -163,21 +268,21 @@ interface MethodSchema {
163
268
  */
164
269
  type DefaultRegistry = Record<string, MethodSchema>;
165
270
  /**
166
- * Helper to extract params from a schema or return any if not present.
271
+ * Helper to extract params from a schema or return unknown if not present.
167
272
  */
168
273
  type GetParams<T> = T extends {
169
274
  params: infer P;
170
- } ? P : any;
275
+ } ? P : unknown;
171
276
  /**
172
- * Helper to extract result from a schema or return any if not present.
277
+ * Helper to extract result from a schema or return unknown if not present.
173
278
  */
174
279
  type GetResult<T> = T extends {
175
280
  result: infer R;
176
- } ? R : any;
281
+ } ? R : unknown;
177
282
  /**
178
283
  * Options for invoking a method.
179
284
  */
180
- interface InvokeOptions<K extends string = string, P = any> {
285
+ interface InvokeOptions<K extends string = string, P = unknown> {
181
286
  method: K;
182
287
  params?: P;
183
288
  to?: string;
@@ -189,48 +294,15 @@ interface InvokeOptions<K extends string = string, P = any> {
189
294
  /**
190
295
  * Options for sending a notification.
191
296
  */
192
- interface NotificationOptions<K extends string = string, P = any> {
297
+ interface NotificationOptions<K extends string = string, P = unknown> {
193
298
  method: K;
194
299
  params?: P;
195
300
  to?: string;
196
301
  metadata?: Record<string, unknown>;
197
302
  }
198
- interface InvokeContext {
199
- messageId?: string;
200
- from: string;
201
- to?: string;
202
- metadata?: Record<string, unknown>;
203
- }
204
- /**
205
- * Handler for an invoked method.
206
- */
207
- type InvokeHandler<P = any, R = any> = (params: P, context: InvokeContext) => R | Promise<R>;
208
- /**
209
- * Handler for a notification.
210
- */
211
- type NotificationHandler<P = any> = (params: P, context: InvokeContext) => void;
212
- /**
213
- * Standard JSON-RPC 2.0 and Nexus-specific error codes.
214
- */
215
- declare enum NexusErrorCode {
216
- ParseError = -32700,
217
- InvalidRequest = -32600,
218
- MethodNotFound = -32601,
219
- InvalidParams = -32602,
220
- InternalError = -32603,
221
- Timeout = -32001,
222
- SendFailed = -32002,
223
- InvalidResponse = -32003
224
- }
225
- /**
226
- * Custom error class for MessageNexus.
227
- */
228
- declare class NexusError<D = any> extends Error {
229
- readonly code: number;
230
- readonly data?: D;
231
- constructor(message: string, code?: number, data?: D);
232
- }
233
303
  type ErrorHandler = (error: Error | NexusError, context?: Record<string, unknown>) => void;
304
+ type RequestInterceptor = (message: Message) => Message | Promise<Message>;
305
+ type ResponseInterceptor = (message: Message) => Message | Promise<Message>;
234
306
  interface Metrics {
235
307
  messagesSent: number;
236
308
  messagesReceived: number;
@@ -243,33 +315,53 @@ interface Metrics {
243
315
  type MetricsCallback = (metrics: Metrics) => void;
244
316
  declare class MessageNexus<InvokeMap extends object = DefaultRegistry, NotificationMap extends object = Record<string, any>> {
245
317
  driver: BaseDriver;
246
- pendingTasks: Map<string, {
247
- resolve: (value: any) => void;
248
- reject: (reason?: unknown) => void;
249
- timer: ReturnType<typeof setTimeout>;
250
- to?: string;
251
- timestamp: number;
252
- }>;
253
- invokeHandlers: Map<string, InvokeHandler>;
254
- notificationHandlers: Map<string, Set<NotificationHandler>>;
255
318
  timeout: number;
256
319
  instanceId: string;
257
- private cleanupInterval;
258
- private messageQueue;
259
- private maxQueueSize;
320
+ private queue;
321
+ private scheduler;
322
+ private requestPipeline;
323
+ private responsePipeline;
324
+ private router;
260
325
  private errorHandler;
261
326
  private logger;
262
327
  private metrics;
263
328
  private metricsCallbacks;
329
+ private metricsThrottleTimer;
330
+ private _isDestroyed;
264
331
  constructor(driver: BaseDriver, options?: MessageNexusOptions);
265
332
  invoke<K extends keyof InvokeMap>(methodOrOptions: K | InvokeOptions<K & string, GetParams<InvokeMap[K]>>): Promise<GetResult<InvokeMap[K]>>;
266
333
  private _sendMessage;
334
+ useRequestInterceptor(interceptor: RequestInterceptor): () => void;
335
+ useResponseInterceptor(interceptor: ResponseInterceptor): () => void;
267
336
  onError(handler: ErrorHandler): () => void;
268
337
  flushQueue(): void;
269
- notify<K extends keyof NotificationMap>(methodOrOptions: K | NotificationOptions<K & string, NotificationMap[K]>): void;
270
- private _validateMessage;
338
+ notify<K extends keyof NotificationMap>(methodOrOptions: K | NotificationOptions<K & string, NotificationMap[K]>): Promise<void>;
271
339
  _handleIncoming(data: unknown): Promise<void>;
272
340
  getMetrics(): Metrics;
341
+ /**
342
+ * Returns the number of messages currently in the offline queue.
343
+ */
344
+ getQueueLength(): number;
345
+ /**
346
+ * Returns a snapshot of the current message queue.
347
+ */
348
+ getQueueSnapshot(): Message[];
349
+ /**
350
+ * Returns the number of pending RPC tasks waiting for a response.
351
+ */
352
+ getPendingTasksCount(): number;
353
+ /**
354
+ * Returns the number of registered invoke handlers.
355
+ */
356
+ getHandlersCount(): number;
357
+ /**
358
+ * Returns the number of registered notification methods.
359
+ */
360
+ getNotificationMethodsCount(): number;
361
+ /**
362
+ * Returns true if a specific invoke handler is registered.
363
+ */
364
+ hasHandler(method: string): boolean;
273
365
  onMetrics(callback: MetricsCallback): () => boolean;
274
366
  private _notifyMetrics;
275
367
  handle<K extends keyof InvokeMap>(method: K, handler: InvokeHandler<GetParams<InvokeMap[K]>, GetResult<InvokeMap[K]>>): () => boolean;
@@ -278,7 +370,12 @@ declare class MessageNexus<InvokeMap extends object = DefaultRegistry, Notificat
278
370
  offNotification<K extends keyof NotificationMap>(method: K, handler: NotificationHandler<NotificationMap[K]>): void;
279
371
  private _reply;
280
372
  private _replyError;
373
+ /**
374
+ * Clears registered handlers by type.
375
+ * @param type The type of handlers to clear: 'invoke', 'notification', or 'all' (default).
376
+ */
377
+ clearHandlers(type?: 'invoke' | 'notification' | 'all'): void;
281
378
  destroy(): void;
282
379
  }
283
380
 
284
- export { BaseDriver, BroadcastDriver, type DefaultRegistry, type ErrorHandler, type InvokeContext, type InvokeHandler, type InvokeOptions, LogLevel, type LoggerInterface, type Message, type MessageNexusOptions, type MethodSchema, type Metrics, type MetricsCallback, MittDriver, NexusError, NexusErrorCode, type NotificationHandler, type NotificationOptions, PostMessageDriver, type SimpleLogger, WebSocketDriver, createEmitter, MessageNexus as default };
381
+ export { BaseDriver, BroadcastDriver, type DefaultRegistry, type ErrorHandler, EventRouter, type InvokeContext, type InvokeHandler, type InvokeOptions, LogLevel, type LoggerInterface, type Message, type MessageNexusOptions, MessageQueue, type MethodSchema, type Metrics, type MetricsCallback, type MiddlewareContext, MiddlewarePipeline, MittDriver, NexusError, NexusErrorCode, type NotificationHandler, type NotificationOptions, PostMessageDriver, type RequestInterceptor, type ResponseInterceptor, RpcScheduler, type SimpleLogger, WebSocketDriver, createEmitter, MessageNexus as default };
package/dist/index.d.ts CHANGED
@@ -19,6 +19,8 @@ interface JsonRpcResponse {
19
19
  code: number;
20
20
  message: string;
21
21
  data?: unknown;
22
+ name?: string;
23
+ stack?: string;
22
24
  };
23
25
  id: JsonRpcId;
24
26
  }
@@ -144,9 +146,112 @@ declare class WebSocketDriver extends BaseDriver {
144
146
 
145
147
  declare function createEmitter(): Emitter<Record<EventType, Message>>;
146
148
 
149
+ /**
150
+ * Standard JSON-RPC 2.0 and Nexus-specific error codes.
151
+ */
152
+ declare enum NexusErrorCode {
153
+ ParseError = -32700,
154
+ InvalidRequest = -32600,
155
+ MethodNotFound = -32601,
156
+ InvalidParams = -32602,
157
+ InternalError = -32603,
158
+ Timeout = -32001,
159
+ SendFailed = -32002,
160
+ InvalidResponse = -32003,
161
+ InstanceDestroyed = -32004
162
+ }
163
+ /**
164
+ * Custom error class for MessageNexus.
165
+ */
166
+ declare class NexusError<D = any> extends Error {
167
+ readonly code: number;
168
+ readonly data?: D;
169
+ constructor(message: string, code?: number, data?: D, name?: string, stack?: string);
170
+ }
171
+
172
+ interface MessageQueueOptions {
173
+ maxQueueSize?: number;
174
+ logger?: LoggerInterface | SimpleLogger;
175
+ onMessageDropped?: (message: Message) => void;
176
+ }
177
+ declare class MessageQueue {
178
+ private queue;
179
+ private maxQueueSize;
180
+ private logger?;
181
+ private onMessageDropped?;
182
+ constructor(options?: MessageQueueOptions);
183
+ enqueue(message: Message): void;
184
+ dequeue(): Message | undefined;
185
+ unshift(message: Message): void;
186
+ get length(): number;
187
+ get isFull(): boolean;
188
+ get isEmpty(): boolean;
189
+ clear(): void;
190
+ }
191
+
192
+ interface RpcSchedulerOptions {
193
+ defaultTimeout?: number;
194
+ }
195
+ declare class RpcScheduler {
196
+ private pendingTasks;
197
+ private defaultTimeout;
198
+ constructor(options?: RpcSchedulerOptions);
199
+ createTask<R = unknown>(id: string, method: string, timeout?: number, onTimeout?: (id: string) => void): Promise<R>;
200
+ hasTask(id: string): boolean;
201
+ resolveTask(id: string, result: unknown): {
202
+ latency: number;
203
+ } | null;
204
+ rejectTask(id: string, error: unknown): {
205
+ latency: number;
206
+ } | null;
207
+ get size(): number;
208
+ clearTasks(error?: unknown): void;
209
+ }
210
+
211
+ type MiddlewareContext = {
212
+ message: Message;
213
+ direction: 'inbound' | 'outbound';
214
+ nexusInstanceId: string;
215
+ [key: string]: unknown;
216
+ };
217
+ type Next = () => Promise<void>;
218
+ type Middleware = (ctx: MiddlewareContext, next: Next) => Promise<void>;
219
+ declare class MiddlewarePipeline {
220
+ private middlewares;
221
+ use(middleware: Middleware): () => void;
222
+ execute(ctx: MiddlewareContext): Promise<void>;
223
+ clear(): void;
224
+ get isEmpty(): boolean;
225
+ }
226
+
227
+ interface InvokeContext {
228
+ messageId?: string;
229
+ from: string;
230
+ to?: string;
231
+ metadata?: Record<string, unknown>;
232
+ }
233
+ type InvokeHandler<P = unknown, R = unknown> = (params: P, context: InvokeContext) => R | Promise<R>;
234
+ type NotificationHandler<P = unknown> = (params: P, context: InvokeContext) => void;
235
+ declare class EventRouter<InvokeMap extends object = Record<string, any>, NotificationMap extends object = Record<string, any>> {
236
+ private invokeHandlers;
237
+ private notificationHandlers;
238
+ handle<K extends keyof InvokeMap>(method: string, handler: InvokeHandler<any, any>): () => boolean;
239
+ removeHandler(method: string): boolean;
240
+ onNotification<K extends keyof NotificationMap>(method: string, handler: NotificationHandler<any>): () => void;
241
+ offNotification<K extends keyof NotificationMap>(method: string, handler: NotificationHandler<any>): void;
242
+ hasInvokeHandler(method: string): boolean;
243
+ getInvokeHandler(method: string): InvokeHandler | undefined;
244
+ getNotificationHandlers(method: string): Set<NotificationHandler> | undefined;
245
+ get invokeHandlersCount(): number;
246
+ get notificationHandlersCount(): number;
247
+ clear(type?: 'invoke' | 'notification'): void;
248
+ static validateMessage(data: unknown): data is Message;
249
+ }
250
+
147
251
  interface MessageNexusOptions {
148
252
  instanceId?: string;
149
253
  timeout?: number;
254
+ maxQueueSize?: number;
150
255
  logger?: LoggerInterface | SimpleLogger;
151
256
  loggerEnabled?: boolean;
152
257
  logLevel?: LogLevel;
@@ -163,21 +268,21 @@ interface MethodSchema {
163
268
  */
164
269
  type DefaultRegistry = Record<string, MethodSchema>;
165
270
  /**
166
- * Helper to extract params from a schema or return any if not present.
271
+ * Helper to extract params from a schema or return unknown if not present.
167
272
  */
168
273
  type GetParams<T> = T extends {
169
274
  params: infer P;
170
- } ? P : any;
275
+ } ? P : unknown;
171
276
  /**
172
- * Helper to extract result from a schema or return any if not present.
277
+ * Helper to extract result from a schema or return unknown if not present.
173
278
  */
174
279
  type GetResult<T> = T extends {
175
280
  result: infer R;
176
- } ? R : any;
281
+ } ? R : unknown;
177
282
  /**
178
283
  * Options for invoking a method.
179
284
  */
180
- interface InvokeOptions<K extends string = string, P = any> {
285
+ interface InvokeOptions<K extends string = string, P = unknown> {
181
286
  method: K;
182
287
  params?: P;
183
288
  to?: string;
@@ -189,48 +294,15 @@ interface InvokeOptions<K extends string = string, P = any> {
189
294
  /**
190
295
  * Options for sending a notification.
191
296
  */
192
- interface NotificationOptions<K extends string = string, P = any> {
297
+ interface NotificationOptions<K extends string = string, P = unknown> {
193
298
  method: K;
194
299
  params?: P;
195
300
  to?: string;
196
301
  metadata?: Record<string, unknown>;
197
302
  }
198
- interface InvokeContext {
199
- messageId?: string;
200
- from: string;
201
- to?: string;
202
- metadata?: Record<string, unknown>;
203
- }
204
- /**
205
- * Handler for an invoked method.
206
- */
207
- type InvokeHandler<P = any, R = any> = (params: P, context: InvokeContext) => R | Promise<R>;
208
- /**
209
- * Handler for a notification.
210
- */
211
- type NotificationHandler<P = any> = (params: P, context: InvokeContext) => void;
212
- /**
213
- * Standard JSON-RPC 2.0 and Nexus-specific error codes.
214
- */
215
- declare enum NexusErrorCode {
216
- ParseError = -32700,
217
- InvalidRequest = -32600,
218
- MethodNotFound = -32601,
219
- InvalidParams = -32602,
220
- InternalError = -32603,
221
- Timeout = -32001,
222
- SendFailed = -32002,
223
- InvalidResponse = -32003
224
- }
225
- /**
226
- * Custom error class for MessageNexus.
227
- */
228
- declare class NexusError<D = any> extends Error {
229
- readonly code: number;
230
- readonly data?: D;
231
- constructor(message: string, code?: number, data?: D);
232
- }
233
303
  type ErrorHandler = (error: Error | NexusError, context?: Record<string, unknown>) => void;
304
+ type RequestInterceptor = (message: Message) => Message | Promise<Message>;
305
+ type ResponseInterceptor = (message: Message) => Message | Promise<Message>;
234
306
  interface Metrics {
235
307
  messagesSent: number;
236
308
  messagesReceived: number;
@@ -243,33 +315,53 @@ interface Metrics {
243
315
  type MetricsCallback = (metrics: Metrics) => void;
244
316
  declare class MessageNexus<InvokeMap extends object = DefaultRegistry, NotificationMap extends object = Record<string, any>> {
245
317
  driver: BaseDriver;
246
- pendingTasks: Map<string, {
247
- resolve: (value: any) => void;
248
- reject: (reason?: unknown) => void;
249
- timer: ReturnType<typeof setTimeout>;
250
- to?: string;
251
- timestamp: number;
252
- }>;
253
- invokeHandlers: Map<string, InvokeHandler>;
254
- notificationHandlers: Map<string, Set<NotificationHandler>>;
255
318
  timeout: number;
256
319
  instanceId: string;
257
- private cleanupInterval;
258
- private messageQueue;
259
- private maxQueueSize;
320
+ private queue;
321
+ private scheduler;
322
+ private requestPipeline;
323
+ private responsePipeline;
324
+ private router;
260
325
  private errorHandler;
261
326
  private logger;
262
327
  private metrics;
263
328
  private metricsCallbacks;
329
+ private metricsThrottleTimer;
330
+ private _isDestroyed;
264
331
  constructor(driver: BaseDriver, options?: MessageNexusOptions);
265
332
  invoke<K extends keyof InvokeMap>(methodOrOptions: K | InvokeOptions<K & string, GetParams<InvokeMap[K]>>): Promise<GetResult<InvokeMap[K]>>;
266
333
  private _sendMessage;
334
+ useRequestInterceptor(interceptor: RequestInterceptor): () => void;
335
+ useResponseInterceptor(interceptor: ResponseInterceptor): () => void;
267
336
  onError(handler: ErrorHandler): () => void;
268
337
  flushQueue(): void;
269
- notify<K extends keyof NotificationMap>(methodOrOptions: K | NotificationOptions<K & string, NotificationMap[K]>): void;
270
- private _validateMessage;
338
+ notify<K extends keyof NotificationMap>(methodOrOptions: K | NotificationOptions<K & string, NotificationMap[K]>): Promise<void>;
271
339
  _handleIncoming(data: unknown): Promise<void>;
272
340
  getMetrics(): Metrics;
341
+ /**
342
+ * Returns the number of messages currently in the offline queue.
343
+ */
344
+ getQueueLength(): number;
345
+ /**
346
+ * Returns a snapshot of the current message queue.
347
+ */
348
+ getQueueSnapshot(): Message[];
349
+ /**
350
+ * Returns the number of pending RPC tasks waiting for a response.
351
+ */
352
+ getPendingTasksCount(): number;
353
+ /**
354
+ * Returns the number of registered invoke handlers.
355
+ */
356
+ getHandlersCount(): number;
357
+ /**
358
+ * Returns the number of registered notification methods.
359
+ */
360
+ getNotificationMethodsCount(): number;
361
+ /**
362
+ * Returns true if a specific invoke handler is registered.
363
+ */
364
+ hasHandler(method: string): boolean;
273
365
  onMetrics(callback: MetricsCallback): () => boolean;
274
366
  private _notifyMetrics;
275
367
  handle<K extends keyof InvokeMap>(method: K, handler: InvokeHandler<GetParams<InvokeMap[K]>, GetResult<InvokeMap[K]>>): () => boolean;
@@ -278,7 +370,12 @@ declare class MessageNexus<InvokeMap extends object = DefaultRegistry, Notificat
278
370
  offNotification<K extends keyof NotificationMap>(method: K, handler: NotificationHandler<NotificationMap[K]>): void;
279
371
  private _reply;
280
372
  private _replyError;
373
+ /**
374
+ * Clears registered handlers by type.
375
+ * @param type The type of handlers to clear: 'invoke', 'notification', or 'all' (default).
376
+ */
377
+ clearHandlers(type?: 'invoke' | 'notification' | 'all'): void;
281
378
  destroy(): void;
282
379
  }
283
380
 
284
- export { BaseDriver, BroadcastDriver, type DefaultRegistry, type ErrorHandler, type InvokeContext, type InvokeHandler, type InvokeOptions, LogLevel, type LoggerInterface, type Message, type MessageNexusOptions, type MethodSchema, type Metrics, type MetricsCallback, MittDriver, NexusError, NexusErrorCode, type NotificationHandler, type NotificationOptions, PostMessageDriver, type SimpleLogger, WebSocketDriver, createEmitter, MessageNexus as default };
381
+ export { BaseDriver, BroadcastDriver, type DefaultRegistry, type ErrorHandler, EventRouter, type InvokeContext, type InvokeHandler, type InvokeOptions, LogLevel, type LoggerInterface, type Message, type MessageNexusOptions, MessageQueue, type MethodSchema, type Metrics, type MetricsCallback, type MiddlewareContext, MiddlewarePipeline, MittDriver, NexusError, NexusErrorCode, type NotificationHandler, type NotificationOptions, PostMessageDriver, type RequestInterceptor, type ResponseInterceptor, RpcScheduler, type SimpleLogger, WebSocketDriver, createEmitter, MessageNexus as default };
package/dist/index.js CHANGED
@@ -1 +1 @@
1
- var c=class{constructor(){this.onMessage=null,this.onConnect=null,this.onDisconnect=null}send(t){throw new Error("Not implemented")}destroy(){}};var I="message-nexus-v1";function B(o){return typeof o=="object"&&o!==null&&"__messageBridge"in o&&o.__messageBridge===I}var y=class extends c{constructor(e){super();this.messageHandler=null;if(!e.channel)throw new Error("BroadcastDriver requires a channel name");this.channel=new BroadcastChannel(e.channel),this.messageHandler=s=>{if(!B(s.data))return;let{__messageBridge:n,...r}=s.data;this.onMessage?.(r)},this.channel.addEventListener("message",this.messageHandler)}send(e){let s={...e,__messageBridge:I};this.channel.postMessage(s)}destroy(){this.channel&&this.channel.close(),this.messageHandler&&(this.channel.removeEventListener("message",this.messageHandler),this.messageHandler=null),this.onMessage=null}};var L=Symbol("message_nexus_internal"),M=class extends c{constructor(t){super(),this.emitter=t,this.listener=()=>{};let e=s=>{s&&this.onMessage?.(s)};this.emitter.on(L,e),this.listener=()=>{this.emitter.off(L,e)}}send(t){this.emitter.emit(L,t)}destroy(){this.listener(),this.onMessage=null}};var E="message-nexus-v1";function P(o){return typeof o=="object"&&o!==null&&"__messageBridge"in o&&o.__messageBridge===E}var R=class extends c{constructor(e,s){super();this.messageHandler=null;if(!s||s==="*")throw new Error('PostMessageDriver requires explicit targetOrigin for security. Do not use "*" as it allows any origin.');this.targetWindow=e,this.targetOrigin=s,this.messageHandler=n=>{if(n.origin!==this.targetOrigin||!P(n.data))return;let{__messageBridge:r,...g}=n.data;this.onMessage?.(g)},window.addEventListener("message",this.messageHandler)}send(e){let s={...e,__messageBridge:E};this.targetWindow.postMessage(s,this.targetOrigin)}destroy(){this.messageHandler&&(window.removeEventListener("message",this.messageHandler),this.messageHandler=null),this.onMessage=null}};var S=(n=>(n.DEBUG="debug",n.INFO="info",n.WARN="warn",n.ERROR="error",n))(S||{});function H(o){if(o==null||typeof o!="object")return!1;let t=o;return typeof t.debug=="function"&&typeof t.info=="function"&&typeof t.warn=="function"&&typeof t.error=="function"&&typeof t.addHandler=="function"&&typeof t.setMinLevel=="function"&&typeof t.enable=="function"&&typeof t.disable=="function"&&typeof t.isEnabled=="function"}function _(o){if(o==null||typeof o!="object")return!1;let t=o;return typeof t.debug=="function"&&typeof t.info=="function"&&typeof t.warn=="function"&&typeof t.error=="function"}var p=class{constructor(t,e="info",s=!1){this.handlers=[];this.context=t,this.minLevel=e,this.enabled=s}enable(){this.enabled=!0}disable(){this.enabled=!1}isEnabled(){return this.enabled}addHandler(t){this.handlers.push(t)}setMinLevel(t){this.minLevel=t}shouldLog(t){let e=["debug","info","warn","error"];return e.indexOf(t)>=e.indexOf(this.minLevel)}log(t,e,s){if(!this.enabled||!this.shouldLog(t))return;let n={level:t,timestamp:Date.now(),message:e,metadata:s,context:this.context};this.handlers.forEach(r=>r(n))}debug(t,e){this.log("debug",t,e)}info(t,e){this.log("info",t,e)}warn(t,e){this.log("warn",t,e)}error(t,e){this.log("error",t,e)}};function J(){return new Date().toLocaleTimeString("zh-CN",{hour12:!1,hour:"2-digit",minute:"2-digit",second:"2-digit",fractionalSecondDigits:3})}var b=()=>o=>{let e=`[${J()}] [${o.level.toUpperCase()}] [${o.context||"app"}]`,s=o.level==="debug"?console.debug:o.level==="info"?console.info:o.level==="warn"?console.warn:console.error;o.metadata?s(e,o.message,o.metadata):s(e,o.message)};var N="message-nexus-v1",w=class extends c{constructor(e){super();this.ws=null;this.retryCount=0;this.reconnectTimer=null;this.isManuallyClosed=!1;this.url=e.url,this.reconnectEnabled=e.reconnect!==!1,this.maxRetries=(typeof e.reconnect=="object"?e.reconnect.maxRetries:void 0)??1/0,this.retryInterval=(typeof e.reconnect=="object"?e.reconnect.retryInterval:void 0)??5e3,this.logger=e.logger||new p("WebSocketDriver"),this.logger.addHandler(b()),this.onStatusChange=e.onStatusChange,this.connect()}connect(){this.onStatusChange?.("connecting"),this.ws=new WebSocket(this.url),this.ws.addEventListener("open",()=>{this.logger.info("WebSocket connected",{url:this.url}),this.retryCount=0,this.onStatusChange?.("connected"),this.onConnect?.()}),this.ws.addEventListener("message",e=>{try{let s=JSON.parse(e.data);if(typeof s=="object"&&s!==null&&"__messageBridge"in s&&s.__messageBridge===N){let{__messageBridge:n,...r}=s;this.logger.debug("Message received",{data:r}),this.onMessage?.(r)}else this.logger.debug("Ignored non-bridge message",{data:s})}catch(s){this.logger.error("Failed to parse WebSocket message",{error:s,data:e.data})}}),this.ws.addEventListener("error",e=>{this.logger.error("WebSocket error",{event:e}),this.onStatusChange?.("error")}),this.ws.addEventListener("close",()=>{this.logger.info("WebSocket connection closed",{manuallyClosed:this.isManuallyClosed,retryCount:this.retryCount,maxRetries:this.maxRetries}),!this.isManuallyClosed&&this.reconnectEnabled&&this.retryCount<this.maxRetries?this.scheduleReconnect():this.onStatusChange?.("disconnected"),this.onDisconnect?.()})}scheduleReconnect(){this.retryCount++;let e=3e4,s=this.retryInterval*Math.pow(2,this.retryCount),n=Math.min(s,e);this.logger.info("Reconnecting scheduled",{delay:n,attempt:this.retryCount,maxRetries:this.maxRetries,url:this.url}),this.onStatusChange?.("connecting"),this.reconnectTimer=window.setTimeout(()=>{this.connect()},n)}send(e){if(!this.ws||this.ws.readyState!==WebSocket.OPEN)throw this.logger.error("WebSocket is not open",{state:this.ws?.readyState,url:this.url}),new Error("WebSocket is not open");this.logger.debug("Sending message",{data:e});let s={...e,__messageBridge:N};this.ws.send(JSON.stringify(s))}close(){this.logger.info("Closing WebSocket connection",{url:this.url}),this.isManuallyClosed=!0,this.reconnectTimer&&(clearTimeout(this.reconnectTimer),this.reconnectTimer=null),this.ws&&(this.ws.close(),this.ws=null),this.onStatusChange?.("disconnected"),this.onDisconnect?.()}destroy(){this.close(),this.onMessage=null,this.onConnect=null,this.onDisconnect=null}};import K from"mitt";function q(){return K()}var W=(i=>(i[i.ParseError=-32700]="ParseError",i[i.InvalidRequest=-32600]="InvalidRequest",i[i.MethodNotFound=-32601]="MethodNotFound",i[i.InvalidParams=-32602]="InvalidParams",i[i.InternalError=-32603]="InternalError",i[i.Timeout=-32001]="Timeout",i[i.SendFailed=-32002]="SendFailed",i[i.InvalidResponse=-32003]="InvalidResponse",i))(W||{}),u=class o extends Error{constructor(t,e=-32603,s){super(t),this.name="NexusError",this.code=e,this.data=s,Object.setPrototypeOf(this,o.prototype)}},x=class{constructor(t,e){this.cleanupInterval=null;this.messageQueue=[];this.maxQueueSize=100;this.errorHandler=null;this.metrics={messagesSent:0,messagesReceived:0,messagesFailed:0,pendingMessages:0,queuedMessages:0,totalLatency:0,averageLatency:0};this.metricsCallbacks=new Set;this.driver=t,this.instanceId=e?.instanceId||crypto.randomUUID(),this.timeout=e?.timeout??1e4;let s=e?.logLevel??"info",n=e?.loggerEnabled??!1;if(e?.logger&&H(e.logger))this.logger=e.logger;else if(this.logger=new p("MessageNexus",s,n),e?.logger&&_(e.logger)){let r=e.logger;this.logger.addHandler(g=>{let{level:a,message:i,metadata:d}=g;a==="debug"?r.debug(i,d):a==="info"?r.info(i,d):a==="warn"?r.warn(i,d):a==="error"&&r.error(i,d)})}else n&&this.logger.addHandler(b());n&&(this.logger.enable(),this.logger.info("MessageNexus initialized",{instanceId:this.instanceId,timeout:this.timeout,logLevel:s})),this.pendingTasks=new Map,this.invokeHandlers=new Map,this.notificationHandlers=new Map,this.cleanupInterval=null,this.driver.onMessage=r=>this._handleIncoming(r),this.driver.onConnect=()=>{this.logger.info("Driver connected, flushing message queue"),this.flushQueue()}}async invoke(t){let e=crypto.randomUUID(),s,n,r,g,a,i=0,d=1e3;if(typeof t=="string")s=t,n=void 0,r=void 0,g={},a=this.timeout;else{let l=t;s=l.method,n=l.params,r=l.to,g=l.metadata||{},a=l.timeout??this.timeout,i=l.retryCount??0,d=l.retryDelay??1e3}let f=async l=>new Promise((k,v)=>{let D=setTimeout(()=>{this.pendingTasks.delete(e),this.metrics.messagesFailed++,this.metrics.pendingMessages--,v(new u(`Message timeout: ${s} (${e})`,-32001))},a);this.pendingTasks.set(e,{resolve:k,reject:v,timer:D,timestamp:Date.now()});let C={jsonrpc:"2.0",method:s,params:n,id:e},O={from:this.instanceId,to:r,metadata:{...g,timestamp:Date.now()},payload:C},T=l>=i;this._sendMessage(O,!T)}).catch(k=>{if(l<i)return new Promise(v=>setTimeout(()=>v(f(l+1)),d*(l+1)));throw this.metrics.messagesFailed++,this.metrics.pendingMessages--,k});return f(0)}_sendMessage(t,e=!1){let s=t.payload,n="method"in s,r="id"in s?String(s.id):void 0,g=n?s.method:"RESPONSE";try{this.driver.send(t),this.metrics.messagesSent++,n&&r!==void 0&&this.metrics.pendingMessages++,this.logger.debug("Message sent",{messageId:r,type:g})}catch(a){let i=a instanceof Error?a:new Error(String(a));this.metrics.messagesFailed++,this.logger.error("Failed to send message",{error:i.message,messageId:r}),this.errorHandler?.(i,{message:t}),e?this.logger.debug("Message failed but skipQueue is true (likely retrying)",{messageId:r}):this.messageQueue.length<this.maxQueueSize?(this.messageQueue.push(t),this.logger.debug("Message queued",{messageId:r,queueSize:this.messageQueue.length+1})):(this.logger.warn("Message queue full, dropping oldest message",{queueSize:this.messageQueue.length}),this.messageQueue.shift(),this.messageQueue.push(t))}this.metrics.queuedMessages=this.messageQueue.length,this._notifyMetrics()}onError(t){return this.errorHandler=t,()=>{this.errorHandler=null}}flushQueue(){for(;this.messageQueue.length>0;){let t=this.messageQueue.shift();if(t)try{this.driver.send(t)}catch{this.messageQueue.unshift(t);break}}this.metrics.queuedMessages=this.messageQueue.length,this._notifyMetrics()}notify(t){let e,s,n,r;if(typeof t=="string")e=t,s=void 0,n=void 0,r={};else{let i=t;e=i.method,s=i.params,n=i.to,r=i.metadata||{}}let g={jsonrpc:"2.0",method:e,params:s},a={from:this.instanceId,to:n,metadata:{...r,timestamp:Date.now()},payload:g};this._sendMessage(a)}_validateMessage(t){if(!t||typeof t!="object")return!1;let e=t;if(typeof e.from!="string"||e.to!==void 0&&typeof e.to!="string"||e.metadata!==void 0&&typeof e.metadata!="object")return!1;let s=e.payload;if(!s||typeof s!="object"||s.jsonrpc!=="2.0")return!1;let n="method"in s,r="result"in s||"error"in s;return!(!n&&!r)}async _handleIncoming(t){if(!this._validateMessage(t)){this.logger.error("Invalid message format received",{data:t}),this.errorHandler?.(new Error("Invalid message format received"),{data:t}),this.metrics.messagesFailed++;return}let e=t,s=e.payload;if(e.to&&e.to!==this.instanceId){this.logger.debug("Message filtered: not for this instance",{messageId:"id"in s?s.id:void 0,to:e.to,instanceId:this.instanceId});return}if("result"in s||"error"in s){let n=s,r=String(n.id);if(this.pendingTasks.has(r)){let{resolve:g,reject:a,timer:i,timestamp:d}=this.pendingTasks.get(r);clearTimeout(i),this.pendingTasks.delete(r);let f=Date.now()-d;if(this.metrics.messagesReceived++,this.metrics.pendingMessages--,this.metrics.totalLatency+=f,this.metrics.averageLatency=this.metrics.totalLatency/this.metrics.messagesReceived,this.logger.debug("Response received",{messageId:r,latency:f}),n.error){let l=new u(n.error.message,n.error.code,n.error.data);a(l)}else g(n.result);this._notifyMetrics()}else this.logger.warn("Orphaned response received",{messageId:r});return}if("method"in s)if("id"in s){let n=s,r=String(n.id);this.logger.debug("Invoke message received",{messageId:r,type:n.method,from:e.from});let g={messageId:r,from:e.from,to:e.to,metadata:e.metadata},a=this.invokeHandlers.get(n.method);if(a)try{let i=await a(n.params,g);this._reply(r,e.from,i)}catch(i){this._replyError(r,e.from,i)}else{let i=new u(`Method not found: ${n.method}`,-32601);this._replyError(r,e.from,i)}}else{let n=s;this.logger.debug("Notification message received",{type:n.method,from:e.from});let r={from:e.from,to:e.to,metadata:e.metadata},g=this.notificationHandlers.get(n.method);g&&g.forEach(a=>{try{a(n.params,r)}catch(i){this.logger.error("Error in notification handler",{error:String(i)})}})}}getMetrics(){return{...this.metrics,pendingMessages:this.pendingTasks.size}}onMetrics(t){return this.metricsCallbacks.add(t),()=>this.metricsCallbacks.delete(t)}_notifyMetrics(){let t=this.getMetrics();this.metricsCallbacks.forEach(e=>e(t))}handle(t,e){return this.invokeHandlers.has(t)&&this.logger.warn(`Overriding existing handler for method: ${t}`),this.invokeHandlers.set(t,e),()=>this.invokeHandlers.delete(t)}removeHandler(t){this.invokeHandlers.delete(t)}onNotification(t,e){return this.notificationHandlers.has(t)||this.notificationHandlers.set(t,new Set),this.notificationHandlers.get(t).add(e),()=>this.offNotification(t,e)}offNotification(t,e){let s=this.notificationHandlers.get(t);s&&(s.delete(e),s.size===0&&this.notificationHandlers.delete(t))}_reply(t,e,s){let n={jsonrpc:"2.0",id:t,result:s},r={from:this.instanceId,to:e,payload:n};this.driver.send(r)}_replyError(t,e,s){let n=s instanceof u?s:s instanceof Error?new u(s.message,-32603):new u(String(s),-32603),r={jsonrpc:"2.0",id:t,error:{code:n.code,message:n.message,data:n.data}},g={from:this.instanceId,to:e,payload:r};this.driver.send(g)}destroy(){this.logger.info("MessageNexus destroying",{instanceId:this.instanceId,pendingMessages:this.pendingTasks.size,queuedMessages:this.messageQueue.length,metrics:this.getMetrics()}),this.driver.destroy?.(),this.cleanupInterval&&(clearInterval(this.cleanupInterval),this.cleanupInterval=null),this.invokeHandlers.clear(),this.notificationHandlers.clear(),this.metricsCallbacks.clear()}};export{c as BaseDriver,y as BroadcastDriver,S as LogLevel,M as MittDriver,u as NexusError,W as NexusErrorCode,R as PostMessageDriver,w as WebSocketDriver,q as createEmitter,x as default};
1
+ var p=class{constructor(){this.onMessage=null,this.onConnect=null,this.onDisconnect=null}send(e){throw new Error("Not implemented")}destroy(){}};var f="message-nexus-v1";var M=(i=>(i[i.ParseError=-32700]="ParseError",i[i.InvalidRequest=-32600]="InvalidRequest",i[i.MethodNotFound=-32601]="MethodNotFound",i[i.InvalidParams=-32602]="InvalidParams",i[i.InternalError=-32603]="InternalError",i[i.Timeout=-32001]="Timeout",i[i.SendFailed=-32002]="SendFailed",i[i.InvalidResponse=-32003]="InvalidResponse",i[i.InstanceDestroyed=-32004]="InstanceDestroyed",i))(M||{}),g=class o extends Error{constructor(e,t=-32603,s,n,r){super(e),this.name=n||"NexusError",this.code=t,this.data=s,r&&(this.stack=r),Object.setPrototypeOf(this,o.prototype)}};function B(o){return typeof o=="object"&&o!==null&&"__messageBridge"in o&&o.__messageBridge===f}var x=class extends p{constructor(t){super();this.messageHandler=null;if(!t.channel)throw new Error("BroadcastDriver requires a channel name");this.channel=new BroadcastChannel(t.channel),this.messageHandler=s=>{if(!B(s.data))return;let{__messageBridge:n,...r}=s.data;this.onMessage?.(r)},this.channel.addEventListener("message",this.messageHandler)}send(t){let s={...t,__messageBridge:f};try{this.channel.postMessage(s)}catch(n){throw typeof DOMException<"u"&&n instanceof DOMException&&n.name==="DataCloneError"?new g("Message payload cannot be cloned",-32602):n}}destroy(){this.channel&&this.channel.close(),this.messageHandler&&(this.channel.removeEventListener("message",this.messageHandler),this.messageHandler=null),this.onMessage=null}};var N=Symbol("message_nexus_internal"),R=class extends p{constructor(e){super(),this.emitter=e,this.listener=()=>{};let t=s=>{s&&this.onMessage?.(s)};this.emitter.on(N,t),this.listener=()=>{this.emitter.off(N,t)}}send(e){this.emitter.emit(N,e)}destroy(){this.listener(),this.onMessage=null}};function j(o){return typeof o=="object"&&o!==null&&"__messageBridge"in o&&o.__messageBridge===f}var E=class extends p{constructor(t,s){super();this.messageHandler=null;if(!s||s==="*")throw new Error('PostMessageDriver requires explicit targetOrigin for security. Do not use "*" as it allows any origin.');this.targetWindow=t,this.targetOrigin=s,this.messageHandler=n=>{if(n.origin!==this.targetOrigin||!j(n.data))return;let{__messageBridge:r,...a}=n.data;this.onMessage?.(a)},window.addEventListener("message",this.messageHandler)}send(t){let s={...t,__messageBridge:f};try{this.targetWindow.postMessage(s,this.targetOrigin)}catch(n){throw typeof DOMException<"u"&&n instanceof DOMException&&n.name==="DataCloneError"?new g("Message payload cannot be cloned",-32602):n}}destroy(){this.messageHandler&&(window.removeEventListener("message",this.messageHandler),this.messageHandler=null),this.onMessage=null}};var C=(n=>(n.DEBUG="debug",n.INFO="info",n.WARN="warn",n.ERROR="error",n))(C||{});function _(o){if(o==null||typeof o!="object")return!1;let e=o;return typeof e.debug=="function"&&typeof e.info=="function"&&typeof e.warn=="function"&&typeof e.error=="function"&&typeof e.addHandler=="function"&&typeof e.setMinLevel=="function"&&typeof e.enable=="function"&&typeof e.disable=="function"&&typeof e.isEnabled=="function"}function q(o){if(o==null||typeof o!="object")return!1;let e=o;return typeof e.debug=="function"&&typeof e.info=="function"&&typeof e.warn=="function"&&typeof e.error=="function"}var y=class{constructor(e,t="info",s=!1){this.handlers=[];this.context=e,this.minLevel=t,this.enabled=s}enable(){this.enabled=!0}disable(){this.enabled=!1}isEnabled(){return this.enabled}addHandler(e){this.handlers.push(e)}setMinLevel(e){this.minLevel=e}shouldLog(e){let t=["debug","info","warn","error"];return t.indexOf(e)>=t.indexOf(this.minLevel)}log(e,t,s){if(!this.enabled||!this.shouldLog(e))return;let n={level:e,timestamp:Date.now(),message:t,metadata:s,context:this.context};this.handlers.forEach(r=>r(n))}debug(e,t){this.log("debug",e,t)}info(e,t){this.log("info",e,t)}warn(e,t){this.log("warn",e,t)}error(e,t){this.log("error",e,t)}};function J(){return new Date().toLocaleTimeString("zh-CN",{hour12:!1,hour:"2-digit",minute:"2-digit",second:"2-digit",fractionalSecondDigits:3})}var I=()=>o=>{let t=`[${J()}] [${o.level.toUpperCase()}] [${o.context||"app"}]`,s=o.level==="debug"?console.debug:o.level==="info"?console.info:o.level==="warn"?console.warn:console.error;o.metadata?s(t,o.message,o.metadata):s(t,o.message)};var L=class extends p{constructor(t){super();this.ws=null;this.retryCount=0;this.reconnectTimer=null;this.isManuallyClosed=!1;this.url=t.url,this.reconnectEnabled=t.reconnect!==!1,this.maxRetries=(typeof t.reconnect=="object"?t.reconnect.maxRetries:void 0)??1/0,this.retryInterval=(typeof t.reconnect=="object"?t.reconnect.retryInterval:void 0)??5e3,this.logger=t.logger||new y("WebSocketDriver"),this.logger.addHandler(I()),this.onStatusChange=t.onStatusChange,this.connect()}connect(){this.onStatusChange?.("connecting"),this.ws=new WebSocket(this.url),this.ws.addEventListener("open",()=>{this.logger.info("WebSocket connected",{url:this.url}),this.retryCount=0,this.onStatusChange?.("connected"),this.onConnect?.()}),this.ws.addEventListener("message",t=>{try{let s=JSON.parse(t.data);if(typeof s=="object"&&s!==null&&"__messageBridge"in s&&s.__messageBridge===f){let{__messageBridge:n,...r}=s;this.logger.debug("Message received",{data:r}),this.onMessage?.(r)}else this.logger.debug("Ignored non-bridge message",{data:s})}catch(s){this.logger.error("Failed to parse WebSocket message",{error:s,data:t.data})}}),this.ws.addEventListener("error",t=>{this.logger.error("WebSocket error",{event:t}),this.onStatusChange?.("error")}),this.ws.addEventListener("close",()=>{this.logger.info("WebSocket connection closed",{manuallyClosed:this.isManuallyClosed,retryCount:this.retryCount,maxRetries:this.maxRetries}),!this.isManuallyClosed&&this.reconnectEnabled&&this.retryCount<this.maxRetries?this.scheduleReconnect():this.onStatusChange?.("disconnected"),this.onDisconnect?.()})}scheduleReconnect(){this.retryCount++;let t=3e4,s=this.retryInterval*Math.pow(2,this.retryCount),n=Math.min(s,t)+Math.random()*1e3;this.logger.info("Reconnecting scheduled",{delay:n,attempt:this.retryCount,maxRetries:this.maxRetries,url:this.url}),this.onStatusChange?.("connecting"),this.reconnectTimer=window.setTimeout(()=>{this.connect()},n)}send(t){if(!this.ws||this.ws.readyState!==WebSocket.OPEN)throw this.logger.error("WebSocket is not open",{state:this.ws?.readyState,url:this.url}),new Error("WebSocket is not open");this.logger.debug("Sending message",{data:t});let s={...t,__messageBridge:f};this.ws.send(JSON.stringify(s))}close(){this.logger.info("Closing WebSocket connection",{url:this.url}),this.isManuallyClosed=!0,this.reconnectTimer&&(clearTimeout(this.reconnectTimer),this.reconnectTimer=null),this.ws&&(this.ws.close(),this.ws=null),this.onStatusChange?.("disconnected"),this.onDisconnect?.()}destroy(){this.close(),this.onMessage=null,this.onConnect=null,this.onDisconnect=null}};import K from"mitt";function F(){return K()}function v(o,e){try{return o()}catch(t){if(e)try{e(t)}catch(s){console.error("Safe execution error handler failed:",s)}return}}var S=class{constructor(e={}){this.queue=[];this.maxQueueSize=e.maxQueueSize??100,this.logger=e.logger,this.onMessageDropped=e.onMessageDropped}enqueue(e){if(this.queue.length<this.maxQueueSize)this.queue.push(e),this.logger&&"debug"in this.logger&&this.logger.debug("Message queued",{queueSize:this.queue.length});else{this.logger&&"warn"in this.logger&&this.logger.warn("Message queue full, dropping oldest message",{queueSize:this.queue.length});let t=this.queue.shift();t&&this.onMessageDropped&&this.onMessageDropped(t),this.queue.push(e)}}dequeue(){return this.queue.shift()}unshift(e){this.queue.unshift(e)}get length(){return this.queue.length}get isFull(){return this.queue.length>=this.maxQueueSize}get isEmpty(){return this.queue.length===0}clear(){this.queue=[]}};var T=class{constructor(e={}){this.pendingTasks=new Map;this.defaultTimeout=e.defaultTimeout??1e4}createTask(e,t,s=this.defaultTimeout,n){return new Promise((r,a)=>{let l=setTimeout(()=>{this.pendingTasks.delete(e),n&&n(e),a(new g(`Message timeout: ${t} (${e})`,-32001))},s);this.pendingTasks.set(e,{resolve:r,reject:a,timer:l,timestamp:Date.now()})})}hasTask(e){return this.pendingTasks.has(e)}resolveTask(e,t){let s=this.pendingTasks.get(e);if(s){clearTimeout(s.timer),this.pendingTasks.delete(e);let n=Date.now()-s.timestamp;return s.resolve(t),{latency:n}}return null}rejectTask(e,t){let s=this.pendingTasks.get(e);if(s){clearTimeout(s.timer),this.pendingTasks.delete(e);let n=Date.now()-s.timestamp;return s.reject(t),{latency:n}}return null}get size(){return this.pendingTasks.size}clearTasks(e){for(let[t,s]of this.pendingTasks.entries())clearTimeout(s.timer),e!==void 0&&s.reject(e);this.pendingTasks.clear()}};var w=class{constructor(){this.middlewares=[]}use(e){return this.middlewares.push(e),()=>{this.middlewares=this.middlewares.filter(t=>t!==e)}}async execute(e){let t=-1,s=async n=>{if(n<=t)throw new Error("next() called multiple times");t=n;let r=this.middlewares[n];n!==this.middlewares.length&&r&&await r(e,s.bind(null,n+1))};await s(0)}clear(){this.middlewares=[]}get isEmpty(){return this.middlewares.length===0}};var k=class{constructor(){this.invokeHandlers=new Map;this.notificationHandlers=new Map}handle(e,t){return this.invokeHandlers.set(e,t),()=>this.invokeHandlers.delete(e)}removeHandler(e){return this.invokeHandlers.delete(e)}onNotification(e,t){return this.notificationHandlers.has(e)||this.notificationHandlers.set(e,new Set),this.notificationHandlers.get(e).add(t),()=>this.offNotification(e,t)}offNotification(e,t){let s=this.notificationHandlers.get(e);s&&(s.delete(t),s.size===0&&this.notificationHandlers.delete(e))}hasInvokeHandler(e){return this.invokeHandlers.has(e)}getInvokeHandler(e){return this.invokeHandlers.get(e)}getNotificationHandlers(e){return this.notificationHandlers.get(e)}get invokeHandlersCount(){return this.invokeHandlers.size}get notificationHandlersCount(){return this.notificationHandlers.size}clear(e){(!e||e==="invoke")&&this.invokeHandlers.clear(),(!e||e==="notification")&&this.notificationHandlers.clear()}static validateMessage(e){if(!e||typeof e!="object")return!1;let t=e;if(typeof t.from!="string"||t.from.trim()===""||t.to!==void 0&&(typeof t.to!="string"||t.to.trim()==="")||t.metadata!==void 0&&(typeof t.metadata!="object"||t.metadata===null))return!1;let s=t.payload;if(!s||typeof s!="object"||Array.isArray(s)||s.jsonrpc!=="2.0")return!1;if("id"in s){let l=s.id;if(l!==null&&typeof l!="string"&&typeof l!="number")return!1}let n=typeof s.method=="string"&&s.method.trim()!=="",r="result"in s,a=s.error!==void 0&&typeof s.error=="object"&&s.error!==null;return n?!(r||a):r||a?!(!("id"in s)||r&&a||a&&(typeof s.error.code!="number"||typeof s.error.message!="string")):!1}};var D=class{constructor(e,t){this.errorHandler=null;this.metrics={messagesSent:0,messagesReceived:0,messagesFailed:0,pendingMessages:0,queuedMessages:0,totalLatency:0,averageLatency:0};this.metricsCallbacks=new Set;this.metricsThrottleTimer=null;this._isDestroyed=!1;this.driver=e,this.instanceId=t?.instanceId||crypto.randomUUID(),this.timeout=t?.timeout??1e4;let s=t?.logLevel??"info",n=t?.loggerEnabled??!1;if(t?.logger&&_(t.logger))this.logger=t.logger;else if(this.logger=new y("MessageNexus",s,n),t?.logger&&q(t.logger)){let r=t.logger;this.logger.addHandler(a=>{let{level:l,message:c,metadata:i}=a;l==="debug"?r.debug(c,i):l==="info"?r.info(c,i):l==="warn"?r.warn(c,i):l==="error"&&r.error(c,i)})}else n&&this.logger.addHandler(I());this.queue=new S({maxQueueSize:t?.maxQueueSize??100,logger:this.logger,onMessageDropped:r=>{let a=r.payload;if(a&&"id"in a){let l=String(a.id);this.scheduler.rejectTask(l,new g("Message dropped due to queue overflow",-32002))}}}),this.scheduler=new T({defaultTimeout:this.timeout}),this.requestPipeline=new w,this.responsePipeline=new w,this.router=new k,n&&(this.logger.enable(),this.logger.info("MessageNexus initialized",{instanceId:this.instanceId,timeout:this.timeout,logLevel:s})),this.driver.onMessage=r=>this._handleIncoming(r),this.driver.onConnect=()=>{this.logger.info("Driver connected, flushing message queue"),this.flushQueue()}}async invoke(e){let t=crypto.randomUUID(),s,n,r,a,l,c=0,i=1e3;if(typeof e=="string")s=e,n=void 0,r=void 0,a={},l=this.timeout;else{let d=e;s=d.method,n=d.params,r=d.to,a=d.metadata||{},l=d.timeout??this.timeout,c=d.retryCount??0,i=d.retryDelay??1e3}let h=async d=>{try{let b=this.scheduler.createTask(t,s,l,()=>{this.metrics.messagesFailed++}),H={jsonrpc:"2.0",method:s,params:n,id:t},P={from:this.instanceId,to:r,metadata:{...a,timestamp:Date.now()},payload:H},O=d>=c;return this._sendMessage(P,!O).catch(()=>{}),await b}catch(b){if(this._isDestroyed)throw b;if(d<c)return new Promise(H=>setTimeout(()=>H(h(d+1)),i*(d+1)));throw this.metrics.messagesFailed++,b}};return h(0)}async _sendMessage(e,t=!1){let s={message:e,direction:"outbound",nexusInstanceId:this.instanceId};try{this.requestPipeline.isEmpty||await this.requestPipeline.execute(s)}catch(i){this.logger.error("Request interceptor failed",{error:String(i)}),this.metrics.messagesFailed++;let h=i instanceof Error?i:new Error(String(i));v(()=>this.errorHandler?.(h,{message:s.message}));let d=s.message.payload;d&&"id"in d&&this.scheduler.rejectTask(String(d.id),i instanceof Error?i:new Error(String(i)));return}let n=s.message,r=n.payload,a="method"in r,l="id"in r?String(r.id):void 0,c=a?r.method:"RESPONSE";try{this.driver.send(n),this.metrics.messagesSent++,this.logger.debug("Message sent",{messageId:l,type:c})}catch(i){let h=i instanceof Error?i:new Error(String(i));this.metrics.messagesFailed++,this.logger.error("Failed to send message",{error:h.message,messageId:l}),v(()=>this.errorHandler?.(h,{message:n}));let d=h.name==="NexusError"&&h.code===-32602&&h.message==="Message payload cannot be cloned";!t&&!d?this.queue.enqueue(n):this.logger.debug(d?"Message dropped due to data error":"Message failed but skipQueue is true (likely retrying)",{messageId:l})}this._notifyMetrics()}useRequestInterceptor(e){return this.requestPipeline.use(async(t,s)=>{t.message=await Promise.race([e(t.message),new Promise((n,r)=>setTimeout(()=>r(new Error("Request interceptor timed out")),3e3))]),await s()})}useResponseInterceptor(e){return this.responsePipeline.use(async(t,s)=>{t.message=await Promise.race([e(t.message),new Promise((n,r)=>setTimeout(()=>r(new Error("Response interceptor timed out")),3e3))]),await s()})}onError(e){return this.errorHandler=e,()=>{this.errorHandler=null}}flushQueue(){for(;!this.queue.isEmpty;){let e=this.queue.dequeue();if(e)try{this.driver.send(e)}catch(t){let s=t instanceof Error?t:new Error(String(t));if(s.name==="NexusError"&&s.code===-32602&&s.message==="Message payload cannot be cloned"){this.logger.error("Message payload cannot be cloned during flush, dropping",{error:s.message});continue}this.queue.unshift(e);break}}this._notifyMetrics(!0)}async notify(e){let t,s,n,r;if(typeof e=="string")t=e,s=void 0,n=void 0,r={};else{let c=e;t=c.method,s=c.params,n=c.to,r=c.metadata||{}}let a={jsonrpc:"2.0",method:t,params:s},l={from:this.instanceId,to:n,metadata:{...r,timestamp:Date.now()},payload:a};await this._sendMessage(l)}async _handleIncoming(e){if(!k.validateMessage(e)){this.logger.error("Invalid message format received",{data:e}),v(()=>this.errorHandler?.(new Error("Invalid message format received"),{data:e})),this.metrics.messagesFailed++;return}let t={message:e,direction:"inbound",nexusInstanceId:this.instanceId};try{this.responsePipeline.isEmpty||await this.responsePipeline.execute(t)}catch(r){this.logger.error("Response interceptor failed",{error:String(r)}),this.metrics.messagesFailed++;let a=r instanceof Error?r:new Error(String(r));v(()=>this.errorHandler?.(a,{message:t.message}));return}let s=t.message,n=s.payload;if(s.to&&s.to!==this.instanceId){this.logger.debug("Message filtered: not for this instance",{messageId:"id"in n?n.id:void 0,to:s.to,instanceId:this.instanceId});return}if("result"in n||"error"in n){let r=n,a=String(r.id);if(this.scheduler.hasTask(a)){let l;if(r.error){let c=new g(r.error.message,r.error.code,r.error.data,r.error.name,r.error.stack);l=this.scheduler.rejectTask(a,c)}else l=this.scheduler.resolveTask(a,r.result);l&&(this.metrics.messagesReceived++,this.metrics.totalLatency+=l.latency,this.metrics.averageLatency=this.metrics.totalLatency/this.metrics.messagesReceived,this.logger.debug("Response received",{messageId:a,latency:l.latency}),this._notifyMetrics())}else this.logger.warn("Orphaned response received",{messageId:a});return}if("method"in n)if("id"in n){let r=n,a=String(r.id);this.logger.debug("Invoke message received",{messageId:a,type:r.method,from:s.from});let l={messageId:a,from:s.from,to:s.to,metadata:s.metadata},c=this.router.getInvokeHandler(r.method);if(c)try{let i=await c(r.params,l);await this._reply(a,s.from,i)}catch(i){await this._replyError(a,s.from,i)}else{let i=new g(`Method not found: ${r.method}`,-32601);await this._replyError(a,s.from,i)}}else{let r=n;this.logger.debug("Notification message received",{type:r.method,from:s.from});let a={from:s.from,to:s.to,metadata:s.metadata},l=this.router.getNotificationHandlers(r.method);l&&l.forEach(c=>{v(()=>c(r.params,a),i=>{this.logger.error("Error in notification handler",{error:String(i)})})})}}getMetrics(){return{...this.metrics,pendingMessages:this.scheduler.size,queuedMessages:this.queue.length}}getQueueLength(){return this.queue.length}getQueueSnapshot(){return this.queue.queue.slice()}getPendingTasksCount(){return this.scheduler.size}getHandlersCount(){return this.router.invokeHandlersCount}getNotificationMethodsCount(){return this.router.notificationHandlersCount}hasHandler(e){return this.router.hasInvokeHandler(e)}onMetrics(e){return this.metricsCallbacks.add(e),()=>this.metricsCallbacks.delete(e)}_notifyMetrics(e=!1){if(e){this.metricsThrottleTimer&&(clearTimeout(this.metricsThrottleTimer),this.metricsThrottleTimer=null);let t=this.getMetrics();this.metricsCallbacks.forEach(s=>{v(()=>s(t),n=>{this.logger.error("Error in metrics callback",{error:String(n)})})});return}this.metricsThrottleTimer||(this.metricsThrottleTimer=setTimeout(()=>{this.metricsThrottleTimer=null;let t=this.getMetrics();this.metricsCallbacks.forEach(s=>{v(()=>s(t),n=>{this.logger.error("Error in metrics callback",{error:String(n)})})})},100))}handle(e,t){return this.router.hasInvokeHandler(e)&&this.logger.warn(`Overriding existing handler for method: ${e}`),this.router.handle(e,t)}removeHandler(e){this.router.removeHandler(e)}onNotification(e,t){return this.router.onNotification(e,t)}offNotification(e,t){this.router.offNotification(e,t)}async _reply(e,t,s){let n={jsonrpc:"2.0",id:e,result:s},r={from:this.instanceId,to:t,payload:n};await this._sendMessage(r)}async _replyError(e,t,s){let n=s instanceof g?s:s instanceof Error?new g(s.message,-32603,void 0,s.name,s.stack):new g(String(s),-32603),r={jsonrpc:"2.0",id:e,error:{code:n.code,message:n.message,data:n.data,name:n.name,stack:n.stack}},a={from:this.instanceId,to:t,payload:r};await this._sendMessage(a)}clearHandlers(e="all"){e==="all"?this.router.clear():this.router.clear(e),this.logger.info(`Cleared handlers of type: ${e}`)}destroy(){this._isDestroyed=!0,this.metricsThrottleTimer&&(clearTimeout(this.metricsThrottleTimer),this.metricsThrottleTimer=null),this._notifyMetrics(!0),this.logger.info("MessageNexus destroying",{instanceId:this.instanceId,pendingMessages:this.scheduler.size,queuedMessages:this.queue.length,metrics:this.getMetrics()}),this.driver.destroy?.(),this.scheduler.clearTasks(new g("MessageNexus instance destroyed",-32004)),this.queue.clear(),this.router.clear(),this.requestPipeline.clear(),this.responsePipeline.clear(),this.metricsCallbacks.clear()}};export{p as BaseDriver,x as BroadcastDriver,k as EventRouter,C as LogLevel,S as MessageQueue,w as MiddlewarePipeline,R as MittDriver,g as NexusError,M as NexusErrorCode,E as PostMessageDriver,T as RpcScheduler,L as WebSocketDriver,F as createEmitter,D as default};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "message-nexus",
3
- "version": "1.1.4",
3
+ "version": "1.3.0",
4
4
  "type": "module",
5
5
  "description": "A unified, type-safe, multi-protocol cross-context message communication library",
6
6
  "author": "wuyax",
@@ -37,6 +37,7 @@
37
37
  },
38
38
  "devDependencies": {
39
39
  "@types/node": "^24.10.9",
40
+ "@vitest/coverage-v8": "^4.0.18",
40
41
  "tsup": "^8.4.0",
41
42
  "typescript": "~5.9.3",
42
43
  "vitest": "^4.0.18",
@@ -58,6 +59,7 @@
58
59
  "dev": "tsup --watch",
59
60
  "test": "vitest",
60
61
  "test:run": "vitest run",
62
+ "test:coverage": "vitest run --coverage",
61
63
  "type-check": "tsc --noEmit"
62
64
  }
63
65
  }