message-nexus 1.1.2 → 1.1.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -22,7 +22,8 @@ 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
- - **Structured Logging**: Supports custom log handlers for easy debugging and production monitoring
25
+ - **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
+
26
27
  - **Resource Management**: All drivers support the `destroy()` method to properly clean up resources.
27
28
 
28
29
  ## Quick Start
@@ -156,20 +157,39 @@ receiverNexus.handle('SYNC_STATE', (params, context) => {
156
157
  #### Constructor
157
158
 
158
159
  ```typescript
159
- new MessageNexus<RequestPayload, ResponsePayload>(
160
+ new MessageNexus<InvokeMap, NotificationMap>(
160
161
  driver: BaseDriver,
161
162
  options?: MessageNexusOptions
162
163
  )
163
164
  ```
164
165
 
166
+ **Generics:**
167
+
168
+ - `InvokeMap`: A record mapping method names to `{ params: any; result: any }`. Defaults to `DefaultRegistry`.
169
+ - `NotificationMap`: A record mapping notification method names to their parameter types. Defaults to `Record<string, any>`.
170
+
165
171
  **Options:**
166
172
 
167
- | Parameter | Type | Default Value | Description |
168
- | ------------- | ------- | -------------- | ------------------------------------- |
169
- | instanceId | string | auto-generated | Instance ID, used for message routing |
170
- | timeout | number | 10000 | Request timeout (milliseconds) |
171
- | logger | Logger | new Logger() | Logger instance |
172
- | loggerEnabled | boolean | false | Whether to enable logging |
173
+ | Parameter | Type | Default Value | Description |
174
+ | ------------- | ---------------------------- | -------------- | ------------------------------------------ |
175
+ | instanceId | string | auto-generated | Instance ID, used for message routing |
176
+ | timeout | number | 10000 | Request timeout (milliseconds) |
177
+ | logger | LoggerInterface \| SimpleLogger | new Logger() | Logger instance or simple logger (e.g. `console`) |
178
+ | loggerEnabled | boolean | false | Whether to enable logging |
179
+ | logLevel | LogLevel | LogLevel.INFO | Minimum log level to report |
180
+
181
+ **LogLevel:** `DEBUG`, `INFO`, `WARN`, `ERROR`
182
+
183
+ **SimpleLogger Interface:**
184
+
185
+ ```typescript
186
+ interface SimpleLogger {
187
+ debug(message: string, metadata?: Record<string, unknown>): void
188
+ info(message: string, metadata?: Record<string, unknown>): void
189
+ warn(message: string, metadata?: Record<string, unknown>): void
190
+ error(message: string, metadata?: Record<string, unknown>): void
191
+ }
192
+ ```
173
193
 
174
194
  #### Methods
175
195
 
@@ -178,15 +198,17 @@ new MessageNexus<RequestPayload, ResponsePayload>(
178
198
  Send request and wait for response.
179
199
 
180
200
  ```typescript
181
- nexus.invoke<T>(methodOrOptions: string | InvokeOptions): Promise<T>
201
+ nexus.invoke<K extends keyof InvokeMap>(
202
+ methodOrOptions: K | InvokeOptions<K, InvokeMap[K]['params']>
203
+ ): Promise<InvokeMap[K]['result']>
182
204
  ```
183
205
 
184
206
  **Options:**
185
207
 
186
208
  | Parameter | Type | Required | Description |
187
209
  | ---------- | ----------------------- | -------- | ---------------------------- |
188
- | method | string | Yes | Message method |
189
- | params | unknown | No | Request data |
210
+ | method | K | Yes | Message method |
211
+ | params | InvokeMap[K]['params'] | No | Request data |
190
212
  | to | string | No | Target instance ID |
191
213
  | metadata | Record<string, unknown> | No | Metadata |
192
214
  | timeout | number | No | Timeout (overrides global) |
@@ -215,15 +237,17 @@ const result = await nexus.invoke({
215
237
  Send a one-way notification (Fire-and-Forget). Does not wait for a response and does not generate an ID. Complies with JSON-RPC 2.0 Notification specification.
216
238
 
217
239
  ```typescript
218
- nexus.notify(methodOrOptions: string | Omit<InvokeOptions, 'timeout' | 'retryCount' | 'retryDelay'>): void
240
+ nexus.notify<K extends keyof NotificationMap>(
241
+ methodOrOptions: K | NotificationOptions<K, NotificationMap[K]>
242
+ ): void
219
243
  ```
220
244
 
221
245
  **Options:**
222
246
 
223
247
  | Parameter | Type | Required | Description |
224
248
  | --------- | ----------------------- | -------- | ------------------- |
225
- | method | string | Yes | Notification method |
226
- | params | unknown | No | Notification data |
249
+ | method | K | Yes | Notification method |
250
+ | params | NotificationMap[K] | No | Notification data |
227
251
  | to | string | No | Target instance ID |
228
252
  | metadata | Record<string, unknown> | No | Metadata |
229
253
 
@@ -246,7 +270,10 @@ nexus.notify({
246
270
  Register a request handler for a specific method. The return value (or resolved value of a returned Promise) is automatically sent back as the response.
247
271
 
248
272
  ```typescript
249
- nexus.handle<Params, Result>(method: string, handler: InvokeHandler<Params, Result>): () => void
273
+ nexus.handle<K extends keyof InvokeMap>(
274
+ method: K,
275
+ handler: InvokeHandler<InvokeMap[K]['params'], InvokeMap[K]['result']>
276
+ ): () => void
250
277
  ```
251
278
 
252
279
  **Parameters:**
@@ -280,7 +307,10 @@ unsubscribe()
280
307
  Register a handler for a specific notification method (one-way messages).
281
308
 
282
309
  ```typescript
283
- nexus.onNotification<Params>(method: string, handler: NotificationHandler<Params>): () => void
310
+ nexus.onNotification<K extends keyof NotificationMap>(
311
+ method: K,
312
+ handler: NotificationHandler<NotificationMap[K]>
313
+ ): () => void
284
314
  ```
285
315
 
286
316
  **Example:**
@@ -296,22 +326,56 @@ unsubscribe()
296
326
 
297
327
  ##### onError()
298
328
 
299
- Register error handler.
329
+ Register a global error handler for background errors (e.g., driver failures, invalid incoming messages). For request-specific errors, use `try/catch` with `invoke()`.
300
330
 
301
331
  ```typescript
302
- nexus.onError(handler: ErrorHandler): () => void
332
+ nexus.onError(handler: (error: Error | NexusError, context?: Record<string, unknown>) => void): () => void
303
333
  ```
304
334
 
305
335
  **Example:**
306
336
 
307
337
  ```typescript
308
338
  nexus.onError((error, context) => {
309
- console.error('Bridge error:', error.message, context)
339
+ if (error instanceof NexusError) {
340
+ console.error(`Bridge error [${error.code}]: ${error.message}`, error.data)
341
+ } else {
342
+ console.error('System error:', error.message)
343
+ }
310
344
  // Send to error tracking service
311
345
  Sentry.captureException(error, { extra: context })
312
346
  })
313
347
  ```
314
348
 
349
+ #### Errors
350
+
351
+ MessageNexus provides a structured error system based on the JSON-RPC 2.0 specification.
352
+
353
+ ##### NexusError
354
+
355
+ A custom error class that includes a numeric code and optional data.
356
+
357
+ ```typescript
358
+ class NexusError<D = any> extends Error {
359
+ code: number // JSON-RPC or Nexus-specific error code
360
+ data?: D // Optional additional error information
361
+ }
362
+ ```
363
+
364
+ ##### NexusErrorCode
365
+
366
+ Common error codes exported by the library:
367
+
368
+ | Code | Name | Description |
369
+ | --- | --- | --- |
370
+ | -32700 | `ParseError` | Invalid JSON received by the server |
371
+ | -32600 | `InvalidRequest` | The JSON sent is not a valid Request object |
372
+ | -32601 | `MethodNotFound` | The method does not exist / is not registered |
373
+ | -32602 | `InvalidParams` | Invalid method parameter(s) |
374
+ | -32603 | `InternalError` | Internal JSON-RPC error (e.g., handler threw an exception) |
375
+ | -32001 | `Timeout` | Request timed out |
376
+ | -32002 | `SendFailed` | Failed to send message via driver |
377
+ | -32003 | `InvalidResponse` | Received a response that doesn't match the request |
378
+
315
379
  ##### getMetrics()
316
380
 
317
381
  Get monitoring metrics.
@@ -548,28 +612,33 @@ function onUserConfirm(id: string) {
548
612
 
549
613
  ### 1. Type Safety
550
614
 
551
- MessageNexus uses TypeScript generics to provide full type inference:
615
+ MessageNexus uses TypeScript generics and Schema mapping to provide full type inference across method names, parameters, and results:
552
616
 
553
617
  ```typescript
554
- interface UserRequest {
555
- userId: number
618
+ // 1. Define your protocol schemas
619
+ interface MyInvokeMap {
620
+ 'getUser': { params: { id: number }; result: { name: string; age: number } };
621
+ 'calculate': { params: { a: number; b: number }; result: number };
556
622
  }
557
623
 
558
- interface UserResponse {
559
- id: number
560
- name: string
624
+ interface MyNotificationMap {
625
+ 'onLog': { message: string; level: 'info' | 'warn' | 'error' };
561
626
  }
562
627
 
563
- const nexus = new MessageNexus<UserRequest, UserResponse>(driver)
628
+ // 2. Initialize with your schemas
629
+ const nexus = new MessageNexus<MyInvokeMap, MyNotificationMap>(driver)
564
630
 
565
- // Full type inference
566
- const response = await nexus.invoke({
567
- method: 'GET_USER',
568
- params: { userId: 123 }, // Type: UserRequest
569
- })
631
+ // 3. Enjoy full type inference and autocompletion
632
+ const response = await nexus.invoke('getUser', { id: 123 })
633
+ // response Type: { name: string; age: number }
570
634
 
571
- // response Type: UserResponse
572
- console.log(response.name)
635
+ nexus.notify('onLog', { message: 'Ready', level: 'info' })
636
+
637
+ // 4. Type-safe handlers
638
+ nexus.handle('calculate', (params) => {
639
+ // params Type: { a: number; b: number }
640
+ return params.a + params.b // result Type must be number
641
+ })
573
642
  ```
574
643
 
575
644
  ### 2. Memory Safety
@@ -582,12 +651,13 @@ console.log(response.name)
582
651
  - **Driver Lifecycle**: Each driver implements the `destroy()` method to correctly release resources
583
652
  - **Emitter Isolation**: Recommended to use `createEmitter()` to create independent instances, avoiding memory leaks caused by shared singletons
584
653
 
585
- ### 3. Error Recovery
654
+ ### 3. Error Recovery & Handling
586
655
 
587
656
  - **Auto Reconnect**: WebSocket automatic reconnection mechanism with exponential backoff strategy
588
657
  - **Request Retry**: Automatic retry on request failure, configurable retry counts and delays
589
658
  - **Message Queue**: Offline message caching, automatically sent after connection recovery
590
- - **Error Callback**: Unified error handling mechanism
659
+ - **Structured Error Handling**: Dedicated `NexusError` class and standard codes (JSON-RPC 2.0 compatible) for precise diagnostic and fault recovery
660
+ - **Unified Error Callback**: Global `onError` listener for non-request background errors
591
661
 
592
662
  ### 4. Security Hardening
593
663
 
@@ -611,15 +681,6 @@ console.log(`Avg latency: ${metrics.averageLatency}ms`)
611
681
  console.log(`Pending: ${metrics.pendingMessages}, Queued: ${metrics.queuedMessages}`)
612
682
  ```
613
683
 
614
- ## Testing
615
-
616
- Run unit tests:
617
-
618
- ```bash
619
- cd packages/message-nexus
620
- pnpm test:run
621
- ```
622
-
623
684
  ## License
624
685
 
625
686
  MIT
package/dist/index.cjs CHANGED
@@ -1 +1 @@
1
- "use strict";var J=Object.create;var R=Object.defineProperty;var P=Object.getOwnPropertyDescriptor;var W=Object.getOwnPropertyNames;var q=Object.getPrototypeOf,j=Object.prototype.hasOwnProperty;var F=(r,t)=>{for(var e in t)R(r,e,{get:t[e],enumerable:!0})},x=(r,t,e,s)=>{if(t&&typeof t=="object"||typeof t=="function")for(let n of W(t))!j.call(r,n)&&n!==e&&R(r,n,{get:()=>t[n],enumerable:!(s=P(t,n))||s.enumerable});return r};var U=(r,t,e)=>(e=r!=null?J(q(r)):{},x(t||!r||!r.__esModule?R(e,"default",{value:r,enumerable:!0}):e,r)),Q=r=>x(R({},"__esModule",{value:!0}),r);var $={};F($,{BaseDriver:()=>c,BroadcastDriver:()=>p,LogLevel:()=>k,MittDriver:()=>f,PostMessageDriver:()=>v,WebSocketDriver:()=>y,createEmitter:()=>T,default:()=>b});module.exports=Q($);var c=class{constructor(){this.onMessage=null}send(t){throw new Error("Not implemented")}destroy(){}};var S="message-nexus-v1";function G(r){return typeof r=="object"&&r!==null&&"__messageBridge"in r&&r.__messageBridge===S}var p=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(!G(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 E=Symbol("message_nexus_internal"),f=class extends c{constructor(t){super(),this.emitter=t,this.listener=()=>{};let e=s=>{s&&this.onMessage?.(s)};this.emitter.on(E,e),this.listener=()=>{this.emitter.off(E,e)}}send(t){this.emitter.emit(E,t)}destroy(){this.listener(),this.onMessage=null}};var I="message-nexus-v1";function A(r){return typeof r=="object"&&r!==null&&"__messageBridge"in r&&r.__messageBridge===I}var v=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||!A(n.data))return;let{__messageBridge:i,...a}=n.data;this.onMessage?.(a)},window.addEventListener("message",this.messageHandler)}send(e){let s={...e,__messageBridge:I};this.targetWindow.postMessage(s,this.targetOrigin)}destroy(){this.messageHandler&&(window.removeEventListener("message",this.messageHandler),this.messageHandler=null),this.onMessage=null}};var k=(n=>(n.DEBUG="debug",n.INFO="info",n.WARN="warn",n.ERROR="error",n))(k||{});function H(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 _(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 m=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 z(){return new Date().toLocaleTimeString("zh-CN",{hour12:!1,hour:"2-digit",minute:"2-digit",second:"2-digit",fractionalSecondDigits:3})}var w=()=>r=>{let e=`[${z()}] [${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 C="message-nexus-v1",y=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 m("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.ws.addEventListener("message",e=>{try{let s=JSON.parse(e.data);if(typeof s=="object"&&s!==null&&"__messageBridge"in s&&s.__messageBridge===C){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")})}scheduleReconnect(){this.retryCount++;let e=this.retryInterval*this.retryCount;this.logger.info("Reconnecting scheduled",{delay:e,attempt:this.retryCount,maxRetries:this.maxRetries,url:this.url}),this.onStatusChange?.("connecting"),this.reconnectTimer=window.setTimeout(()=>{this.connect()},e)}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:C};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")}destroy(){this.close(),this.onMessage=null}};var N=U(require("mitt"),1);function T(){return(0,N.default)()}var b=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 m("MessageNexus",s,n),e?.logger&&_(e.logger)){let i=e.logger;this.logger.addHandler(a=>{let{level:g,message:o,metadata:d}=a;g==="debug"?i.debug(o,d):g==="info"?i.info(o,d):g==="warn"?i.warn(o,d):g==="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)}async invoke(t){let e=crypto.randomUUID(),s,n,i,a,g,o=0,d=1e3;if(typeof t=="string")s=t,n=void 0,i=void 0,a={},g=this.timeout;else{let l=t;s=l.method,n=l.params,i=l.to,a=l.metadata||{},g=l.timeout??this.timeout,o=l.retryCount??0,d=l.retryDelay??1e3}let h=async l=>new Promise((L,M)=>{let O=setTimeout(()=>{this.pendingTasks.delete(e),this.metrics.messagesFailed++,this.metrics.pendingMessages--,M(new Error(`Message timeout: ${s} (${e})`))},g);this.pendingTasks.set(e,{resolve:L,reject:M,timer:O,timestamp:Date.now()});let B={jsonrpc:"2.0",method:s,params:n,id:e},D={from:this.instanceId,to:i,metadata:{...a,timestamp:Date.now()},payload:B};this._sendMessage(D)}).catch(L=>{if(l<o)return new Promise(M=>setTimeout(()=>M(h(l+1)),d*(l+1)));throw this.metrics.messagesFailed++,this.metrics.pendingMessages--,L});return h(0)}_sendMessage(t){let e=t.payload,s="method"in e,n="id"in e?String(e.id):void 0,i=s?e.method:"RESPONSE";try{this.driver.send(t),this.metrics.messagesSent++,s&&n!==void 0&&this.metrics.pendingMessages++,this.logger.debug("Message sent",{messageId:n,type:i})}catch(a){let g=a instanceof Error?a:new Error(String(a));this.metrics.messagesFailed++,this.logger.error("Failed to send message",{error:g.message,messageId:n}),this.errorHandler?.(g,{message:t}),this.messageQueue.length<this.maxQueueSize?(this.messageQueue.push(t),this.logger.debug("Message queued",{messageId:n,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}}}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 a={jsonrpc:"2.0",method:e,params:s},g={from:this.instanceId,to:n,metadata:{...i,timestamp:Date.now()},payload:a};this._sendMessage(g)}_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:a,reject:g,timer:o,timestamp:d}=this.pendingTasks.get(i);clearTimeout(o),this.pendingTasks.delete(i);let h=Date.now()-d;if(this.metrics.messagesReceived++,this.metrics.pendingMessages--,this.metrics.totalLatency+=h,this.metrics.averageLatency=this.metrics.totalLatency/this.metrics.messagesReceived,this.logger.debug("Response received",{messageId:i,latency:h}),n.error){let l=new Error(n.error.message);l.code=n.error.code,l.data=n.error.data,g(l)}else a(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 a={messageId:i,from:e.from,to:e.to,metadata:e.metadata},g=this.invokeHandlers.get(n.method);if(g)try{let o=await g(n.params,a);this._reply(i,e.from,o)}catch(o){this._replyError(i,e.from,o)}else{let o=new Error(`Method not found: ${n.method}`);o.code=-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},a=this.notificationHandlers.get(n.method);a&&a.forEach(g=>{try{g(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 Error?s:new Error(String(s)),i={jsonrpc:"2.0",id:t,error:{code:n.code||-32e3,message:n.message,data:n.data}},a={from:this.instanceId,to:e,payload:i};this.driver.send(a)}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,PostMessageDriver,WebSocketDriver,createEmitter});
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});
package/dist/index.d.cts CHANGED
@@ -31,6 +31,8 @@ interface NexusEnvelope<T = JsonRpcRequest | JsonRpcResponse | JsonRpcNotificati
31
31
  type Message = NexusEnvelope;
32
32
  declare class BaseDriver {
33
33
  onMessage: ((data: Message) => void) | null;
34
+ onConnect: (() => void) | null;
35
+ onDisconnect: (() => void) | null;
34
36
  constructor();
35
37
  send(data: Message): void;
36
38
  destroy(): void;
@@ -149,18 +151,47 @@ interface MessageNexusOptions {
149
151
  loggerEnabled?: boolean;
150
152
  logLevel?: LogLevel;
151
153
  }
152
- interface InvokeOptions {
153
- method: string;
154
- params?: unknown;
154
+ /**
155
+ * Interface representing a method schema with parameters and results.
156
+ */
157
+ interface MethodSchema {
158
+ params?: any;
159
+ result?: any;
160
+ }
161
+ /**
162
+ * Default registry for method schemas.
163
+ */
164
+ type DefaultRegistry = Record<string, MethodSchema>;
165
+ /**
166
+ * Helper to extract params from a schema or return any if not present.
167
+ */
168
+ type GetParams<T> = T extends {
169
+ params: infer P;
170
+ } ? P : any;
171
+ /**
172
+ * Helper to extract result from a schema or return any if not present.
173
+ */
174
+ type GetResult<T> = T extends {
175
+ result: infer R;
176
+ } ? R : any;
177
+ /**
178
+ * Options for invoking a method.
179
+ */
180
+ interface InvokeOptions<K extends string = string, P = any> {
181
+ method: K;
182
+ params?: P;
155
183
  to?: string;
156
184
  metadata?: Record<string, unknown>;
157
185
  timeout?: number;
158
186
  retryCount?: number;
159
187
  retryDelay?: number;
160
188
  }
161
- interface NotificationOptions {
162
- method: string;
163
- params?: unknown;
189
+ /**
190
+ * Options for sending a notification.
191
+ */
192
+ interface NotificationOptions<K extends string = string, P = any> {
193
+ method: K;
194
+ params?: P;
164
195
  to?: string;
165
196
  metadata?: Record<string, unknown>;
166
197
  }
@@ -170,9 +201,36 @@ interface InvokeContext {
170
201
  to?: string;
171
202
  metadata?: Record<string, unknown>;
172
203
  }
173
- type InvokeHandler<Params = any, Result = any> = (params: Params, context: InvokeContext) => Result | Promise<Result>;
174
- type NotificationHandler<Params = any> = (params: Params, context: InvokeContext) => void;
175
- type ErrorHandler = (error: Error, context?: Record<string, unknown>) => void;
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
+ type ErrorHandler = (error: Error | NexusError, context?: Record<string, unknown>) => void;
176
234
  interface Metrics {
177
235
  messagesSent: number;
178
236
  messagesReceived: number;
@@ -183,7 +241,7 @@ interface Metrics {
183
241
  averageLatency: number;
184
242
  }
185
243
  type MetricsCallback = (metrics: Metrics) => void;
186
- declare class MessageNexus<GlobalRequestPayload = unknown, GlobalResponsePayload = unknown> {
244
+ declare class MessageNexus<InvokeMap extends object = DefaultRegistry, NotificationMap extends object = Record<string, any>> {
187
245
  driver: BaseDriver;
188
246
  pendingTasks: Map<string, {
189
247
  resolve: (value: any) => void;
@@ -204,23 +262,23 @@ declare class MessageNexus<GlobalRequestPayload = unknown, GlobalResponsePayload
204
262
  private metrics;
205
263
  private metricsCallbacks;
206
264
  constructor(driver: BaseDriver, options?: MessageNexusOptions);
207
- invoke<T = GlobalResponsePayload>(methodOrOptions: string | InvokeOptions): Promise<T>;
265
+ invoke<K extends keyof InvokeMap>(methodOrOptions: K | InvokeOptions<K & string, GetParams<InvokeMap[K]>>): Promise<GetResult<InvokeMap[K]>>;
208
266
  private _sendMessage;
209
267
  onError(handler: ErrorHandler): () => void;
210
268
  flushQueue(): void;
211
- notify(methodOrOptions: string | NotificationOptions): void;
269
+ notify<K extends keyof NotificationMap>(methodOrOptions: K | NotificationOptions<K & string, NotificationMap[K]>): void;
212
270
  private _validateMessage;
213
271
  _handleIncoming(data: unknown): Promise<void>;
214
272
  getMetrics(): Metrics;
215
273
  onMetrics(callback: MetricsCallback): () => boolean;
216
274
  private _notifyMetrics;
217
- handle<Params = any, Result = any>(method: string, handler: InvokeHandler<Params, Result>): () => boolean;
218
- removeHandler(method: string): void;
219
- onNotification<Params = any>(method: string, handler: NotificationHandler<Params>): () => void;
220
- offNotification(method: string, handler: NotificationHandler<any>): void;
275
+ handle<K extends keyof InvokeMap>(method: K, handler: InvokeHandler<GetParams<InvokeMap[K]>, GetResult<InvokeMap[K]>>): () => boolean;
276
+ removeHandler(method: keyof InvokeMap): void;
277
+ onNotification<K extends keyof NotificationMap>(method: K, handler: NotificationHandler<NotificationMap[K]>): () => void;
278
+ offNotification<K extends keyof NotificationMap>(method: K, handler: NotificationHandler<NotificationMap[K]>): void;
221
279
  private _reply;
222
280
  private _replyError;
223
281
  destroy(): void;
224
282
  }
225
283
 
226
- export { BaseDriver, BroadcastDriver, type ErrorHandler, type InvokeContext, type InvokeHandler, type InvokeOptions, LogLevel, type LoggerInterface, type Message, type MessageNexusOptions, type Metrics, type MetricsCallback, MittDriver, type NotificationHandler, type NotificationOptions, PostMessageDriver, type SimpleLogger, WebSocketDriver, createEmitter, MessageNexus as default };
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 };
package/dist/index.d.ts CHANGED
@@ -31,6 +31,8 @@ interface NexusEnvelope<T = JsonRpcRequest | JsonRpcResponse | JsonRpcNotificati
31
31
  type Message = NexusEnvelope;
32
32
  declare class BaseDriver {
33
33
  onMessage: ((data: Message) => void) | null;
34
+ onConnect: (() => void) | null;
35
+ onDisconnect: (() => void) | null;
34
36
  constructor();
35
37
  send(data: Message): void;
36
38
  destroy(): void;
@@ -149,18 +151,47 @@ interface MessageNexusOptions {
149
151
  loggerEnabled?: boolean;
150
152
  logLevel?: LogLevel;
151
153
  }
152
- interface InvokeOptions {
153
- method: string;
154
- params?: unknown;
154
+ /**
155
+ * Interface representing a method schema with parameters and results.
156
+ */
157
+ interface MethodSchema {
158
+ params?: any;
159
+ result?: any;
160
+ }
161
+ /**
162
+ * Default registry for method schemas.
163
+ */
164
+ type DefaultRegistry = Record<string, MethodSchema>;
165
+ /**
166
+ * Helper to extract params from a schema or return any if not present.
167
+ */
168
+ type GetParams<T> = T extends {
169
+ params: infer P;
170
+ } ? P : any;
171
+ /**
172
+ * Helper to extract result from a schema or return any if not present.
173
+ */
174
+ type GetResult<T> = T extends {
175
+ result: infer R;
176
+ } ? R : any;
177
+ /**
178
+ * Options for invoking a method.
179
+ */
180
+ interface InvokeOptions<K extends string = string, P = any> {
181
+ method: K;
182
+ params?: P;
155
183
  to?: string;
156
184
  metadata?: Record<string, unknown>;
157
185
  timeout?: number;
158
186
  retryCount?: number;
159
187
  retryDelay?: number;
160
188
  }
161
- interface NotificationOptions {
162
- method: string;
163
- params?: unknown;
189
+ /**
190
+ * Options for sending a notification.
191
+ */
192
+ interface NotificationOptions<K extends string = string, P = any> {
193
+ method: K;
194
+ params?: P;
164
195
  to?: string;
165
196
  metadata?: Record<string, unknown>;
166
197
  }
@@ -170,9 +201,36 @@ interface InvokeContext {
170
201
  to?: string;
171
202
  metadata?: Record<string, unknown>;
172
203
  }
173
- type InvokeHandler<Params = any, Result = any> = (params: Params, context: InvokeContext) => Result | Promise<Result>;
174
- type NotificationHandler<Params = any> = (params: Params, context: InvokeContext) => void;
175
- type ErrorHandler = (error: Error, context?: Record<string, unknown>) => void;
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
+ type ErrorHandler = (error: Error | NexusError, context?: Record<string, unknown>) => void;
176
234
  interface Metrics {
177
235
  messagesSent: number;
178
236
  messagesReceived: number;
@@ -183,7 +241,7 @@ interface Metrics {
183
241
  averageLatency: number;
184
242
  }
185
243
  type MetricsCallback = (metrics: Metrics) => void;
186
- declare class MessageNexus<GlobalRequestPayload = unknown, GlobalResponsePayload = unknown> {
244
+ declare class MessageNexus<InvokeMap extends object = DefaultRegistry, NotificationMap extends object = Record<string, any>> {
187
245
  driver: BaseDriver;
188
246
  pendingTasks: Map<string, {
189
247
  resolve: (value: any) => void;
@@ -204,23 +262,23 @@ declare class MessageNexus<GlobalRequestPayload = unknown, GlobalResponsePayload
204
262
  private metrics;
205
263
  private metricsCallbacks;
206
264
  constructor(driver: BaseDriver, options?: MessageNexusOptions);
207
- invoke<T = GlobalResponsePayload>(methodOrOptions: string | InvokeOptions): Promise<T>;
265
+ invoke<K extends keyof InvokeMap>(methodOrOptions: K | InvokeOptions<K & string, GetParams<InvokeMap[K]>>): Promise<GetResult<InvokeMap[K]>>;
208
266
  private _sendMessage;
209
267
  onError(handler: ErrorHandler): () => void;
210
268
  flushQueue(): void;
211
- notify(methodOrOptions: string | NotificationOptions): void;
269
+ notify<K extends keyof NotificationMap>(methodOrOptions: K | NotificationOptions<K & string, NotificationMap[K]>): void;
212
270
  private _validateMessage;
213
271
  _handleIncoming(data: unknown): Promise<void>;
214
272
  getMetrics(): Metrics;
215
273
  onMetrics(callback: MetricsCallback): () => boolean;
216
274
  private _notifyMetrics;
217
- handle<Params = any, Result = any>(method: string, handler: InvokeHandler<Params, Result>): () => boolean;
218
- removeHandler(method: string): void;
219
- onNotification<Params = any>(method: string, handler: NotificationHandler<Params>): () => void;
220
- offNotification(method: string, handler: NotificationHandler<any>): void;
275
+ handle<K extends keyof InvokeMap>(method: K, handler: InvokeHandler<GetParams<InvokeMap[K]>, GetResult<InvokeMap[K]>>): () => boolean;
276
+ removeHandler(method: keyof InvokeMap): void;
277
+ onNotification<K extends keyof NotificationMap>(method: K, handler: NotificationHandler<NotificationMap[K]>): () => void;
278
+ offNotification<K extends keyof NotificationMap>(method: K, handler: NotificationHandler<NotificationMap[K]>): void;
221
279
  private _reply;
222
280
  private _replyError;
223
281
  destroy(): void;
224
282
  }
225
283
 
226
- export { BaseDriver, BroadcastDriver, type ErrorHandler, type InvokeContext, type InvokeHandler, type InvokeOptions, LogLevel, type LoggerInterface, type Message, type MessageNexusOptions, type Metrics, type MetricsCallback, MittDriver, type NotificationHandler, type NotificationOptions, PostMessageDriver, type SimpleLogger, WebSocketDriver, createEmitter, MessageNexus as default };
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 };
package/dist/index.js CHANGED
@@ -1 +1 @@
1
- var c=class{constructor(){this.onMessage=null}send(t){throw new Error("Not implemented")}destroy(){}};var k="message-nexus-v1";function O(i){return typeof i=="object"&&i!==null&&"__messageBridge"in i&&i.__messageBridge===k}var v=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(!O(s.data))return;let{__messageBridge:n,...r}=s.data;this.onMessage?.(r)},this.channel.addEventListener("message",this.messageHandler)}send(e){let s={...e,__messageBridge:k};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"),y=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 x="message-nexus-v1";function B(i){return typeof i=="object"&&i!==null&&"__messageBridge"in i&&i.__messageBridge===x}var M=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||!B(n.data))return;let{__messageBridge:r,...a}=n.data;this.onMessage?.(a)},window.addEventListener("message",this.messageHandler)}send(e){let s={...e,__messageBridge:x};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 I(i){if(i==null||typeof i!="object")return!1;let t=i;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 H(i){if(i==null||typeof i!="object")return!1;let t=i;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(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 D(){return new Date().toLocaleTimeString("zh-CN",{hour12:!1,hour:"2-digit",minute:"2-digit",second:"2-digit",fractionalSecondDigits:3})}var R=()=>i=>{let e=`[${D()}] [${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(e,i.message,i.metadata):s(e,i.message)};var _="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 h("WebSocketDriver"),this.logger.addHandler(R()),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.ws.addEventListener("message",e=>{try{let s=JSON.parse(e.data);if(typeof s=="object"&&s!==null&&"__messageBridge"in s&&s.__messageBridge===_){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")})}scheduleReconnect(){this.retryCount++;let e=this.retryInterval*this.retryCount;this.logger.info("Reconnecting scheduled",{delay:e,attempt:this.retryCount,maxRetries:this.maxRetries,url:this.url}),this.onStatusChange?.("connecting"),this.reconnectTimer=window.setTimeout(()=>{this.connect()},e)}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:_};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")}destroy(){this.close(),this.onMessage=null}};import J from"mitt";function P(){return J()}var E=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&&I(e.logger))this.logger=e.logger;else if(this.logger=new h("MessageNexus",s,n),e?.logger&&H(e.logger)){let r=e.logger;this.logger.addHandler(a=>{let{level:g,message:o,metadata:d}=a;g==="debug"?r.debug(o,d):g==="info"?r.info(o,d):g==="warn"?r.warn(o,d):g==="error"&&r.error(o,d)})}else n&&this.logger.addHandler(R());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)}async invoke(t){let e=crypto.randomUUID(),s,n,r,a,g,o=0,d=1e3;if(typeof t=="string")s=t,n=void 0,r=void 0,a={},g=this.timeout;else{let l=t;s=l.method,n=l.params,r=l.to,a=l.metadata||{},g=l.timeout??this.timeout,o=l.retryCount??0,d=l.retryDelay??1e3}let p=async l=>new Promise((b,f)=>{let C=setTimeout(()=>{this.pendingTasks.delete(e),this.metrics.messagesFailed++,this.metrics.pendingMessages--,f(new Error(`Message timeout: ${s} (${e})`))},g);this.pendingTasks.set(e,{resolve:b,reject:f,timer:C,timestamp:Date.now()});let N={jsonrpc:"2.0",method:s,params:n,id:e},T={from:this.instanceId,to:r,metadata:{...a,timestamp:Date.now()},payload:N};this._sendMessage(T)}).catch(b=>{if(l<o)return new Promise(f=>setTimeout(()=>f(p(l+1)),d*(l+1)));throw this.metrics.messagesFailed++,this.metrics.pendingMessages--,b});return p(0)}_sendMessage(t){let e=t.payload,s="method"in e,n="id"in e?String(e.id):void 0,r=s?e.method:"RESPONSE";try{this.driver.send(t),this.metrics.messagesSent++,s&&n!==void 0&&this.metrics.pendingMessages++,this.logger.debug("Message sent",{messageId:n,type:r})}catch(a){let g=a instanceof Error?a:new Error(String(a));this.metrics.messagesFailed++,this.logger.error("Failed to send message",{error:g.message,messageId:n}),this.errorHandler?.(g,{message:t}),this.messageQueue.length<this.maxQueueSize?(this.messageQueue.push(t),this.logger.debug("Message queued",{messageId:n,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}}}notify(t){let e,s,n,r;if(typeof t=="string")e=t,s=void 0,n=void 0,r={};else{let o=t;e=o.method,s=o.params,n=o.to,r=o.metadata||{}}let a={jsonrpc:"2.0",method:e,params:s},g={from:this.instanceId,to:n,metadata:{...r,timestamp:Date.now()},payload:a};this._sendMessage(g)}_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:a,reject:g,timer:o,timestamp:d}=this.pendingTasks.get(r);clearTimeout(o),this.pendingTasks.delete(r);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:r,latency:p}),n.error){let l=new Error(n.error.message);l.code=n.error.code,l.data=n.error.data,g(l)}else a(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 a={messageId:r,from:e.from,to:e.to,metadata:e.metadata},g=this.invokeHandlers.get(n.method);if(g)try{let o=await g(n.params,a);this._reply(r,e.from,o)}catch(o){this._replyError(r,e.from,o)}else{let o=new Error(`Method not found: ${n.method}`);o.code=-32601,this._replyError(r,e.from,o)}}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},a=this.notificationHandlers.get(n.method);a&&a.forEach(g=>{try{g(n.params,r)}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},r={from:this.instanceId,to:e,payload:n};this.driver.send(r)}_replyError(t,e,s){let n=s instanceof Error?s:new Error(String(s)),r={jsonrpc:"2.0",id:t,error:{code:n.code||-32e3,message:n.message,data:n.data}},a={from:this.instanceId,to:e,payload:r};this.driver.send(a)}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,v as BroadcastDriver,S as LogLevel,y as MittDriver,M as PostMessageDriver,w as WebSocketDriver,P as createEmitter,E as default};
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};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "message-nexus",
3
- "version": "1.1.2",
3
+ "version": "1.1.4",
4
4
  "type": "module",
5
5
  "description": "A unified, type-safe, multi-protocol cross-context message communication library",
6
6
  "author": "wuyax",