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 +35 -2
- package/dist/index.cjs +1 -1
- package/dist/index.d.cts +153 -56
- package/dist/index.d.ts +153 -56
- package/dist/index.js +1 -1
- package/package.json +3 -1
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
|
|
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 :
|
|
275
|
+
} ? P : unknown;
|
|
171
276
|
/**
|
|
172
|
-
* Helper to extract result from a schema or return
|
|
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 :
|
|
281
|
+
} ? R : unknown;
|
|
177
282
|
/**
|
|
178
283
|
* Options for invoking a method.
|
|
179
284
|
*/
|
|
180
|
-
interface InvokeOptions<K extends string = string, P =
|
|
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 =
|
|
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
|
|
258
|
-
private
|
|
259
|
-
private
|
|
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
|
|
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 :
|
|
275
|
+
} ? P : unknown;
|
|
171
276
|
/**
|
|
172
|
-
* Helper to extract result from a schema or return
|
|
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 :
|
|
281
|
+
} ? R : unknown;
|
|
177
282
|
/**
|
|
178
283
|
* Options for invoking a method.
|
|
179
284
|
*/
|
|
180
|
-
interface InvokeOptions<K extends string = string, P =
|
|
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 =
|
|
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
|
|
258
|
-
private
|
|
259
|
-
private
|
|
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.
|
|
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
|
}
|