@xentobias/worker-rpc 1.0.13 → 1.0.15

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.
@@ -80,8 +80,12 @@ export declare class Endpoint {
80
80
  protected remoteCallbacks: Map<`cb:${string}`, Function>;
81
81
  /** Message handler bound to this instance */
82
82
  private boundMessageHandler;
83
+ /** Close/error handler bound to this instance (detects worker death) */
84
+ private boundCloseHandler;
83
85
  /** Whether this endpoint has been released */
84
86
  protected released: boolean;
87
+ /** Release handlers from returned objects with [CALLBACK_RELEASE] */
88
+ protected releaseHandlers: Set<() => void>;
85
89
  constructor(target: MessageTarget, options?: EndpointOptions);
86
90
  /**
87
91
  * Generate a unique call ID for this endpoint
@@ -99,6 +103,13 @@ export declare class Endpoint {
99
103
  * Detach the message listener from the target
100
104
  */
101
105
  private detachListener;
106
+ /**
107
+ * Handle target close/error events (worker death or port disconnection).
108
+ * This triggers the same cleanup as receiving an EndpointRelease message,
109
+ * but is invoked by transport-level events when the remote side dies
110
+ * without sending a proper release message.
111
+ */
112
+ private handleTargetClose;
102
113
  /**
103
114
  * Log a debug message
104
115
  */
@@ -0,0 +1,11 @@
1
+ import type { SerializedError } from './types';
2
+ /**
3
+ * Serialize an Error into a wire-format object.
4
+ * Captures message, name, stack, custom own enumerable properties, and cause chain.
5
+ */
6
+ export declare function serializeError(err: Error): SerializedError;
7
+ /**
8
+ * Deserialize a wire-format error back into an Error instance.
9
+ * Restores message, name, stack, custom properties, and cause chain.
10
+ */
11
+ export declare function deserializeError(data: SerializedError): Error;
package/dist/index.d.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  import { Endpoint, type EndpointOptions } from "./endpoint";
2
2
  import { type MessageTarget, type RemoteObject } from "./types";
3
3
  export * from "./types";
4
+ export * from "./error";
4
5
  export * from "./endpoint";
5
6
  export * from "./proxy";
6
7
  export * from "./broadcast";
package/dist/index.js CHANGED
@@ -1,2 +1,2 @@
1
1
  // @bun
2
- var V;((K)=>{K[K.Call=0]="Call";K[K.Result=1]="Result";K[K.Error=2]="Error";K[K.Callback=3]="Callback";K[K.CallbackRelease=4]="CallbackRelease";K[K.EndpointRelease=5]="EndpointRelease"})(V||={});var f=30000;class S{static generateRandomId(){return Math.random().toString(36).slice(2,8)}static extractEndpointId(q){return q.split(":")[1]??""}target;options;exposedApi=null;exposeOptions={};id;callCounter=0;callbackCounter=0;pendingCalls=new Map;callbacks=new Map;remoteCallbacks=new Map;boundMessageHandler;released=!1;constructor(q,z={}){this.target=q,this.id=z.id??S.generateRandomId(),this.options={id:this.id,timeout:z.timeout??f,onError:z.onError??console.error,debug:z.debug??!1,onRelease:z.onRelease??(()=>{})},this.boundMessageHandler=this.handleMessage.bind(this),this.attachListener()}generateCallId(){return`c:${this.id}:${++this.callCounter}`}generateCallbackId(){return`cb:${this.id}:${++this.callbackCounter}`}attachListener(){if(this.target.addEventListener)this.target.addEventListener("message",this.boundMessageHandler);else if(this.target.onmessage!==void 0)this.target.onmessage=this.boundMessageHandler}detachListener(){if(this.target.removeEventListener)this.target.removeEventListener("message",this.boundMessageHandler);else if(this.target.onmessage!==void 0)this.target.onmessage=null}debug(...q){if(this.options.debug)console.log("[worker-rpc]",...q)}expose(q,z={}){this.exposedApi=q,this.exposeOptions={maxDepth:z.maxDepth??10}}handleMessage(q){let z=q.data;if(typeof z!=="object"||z===null||!("t"in z))return;switch(this.debug("received",V[z.t],z),z.t){case 0:this.handleCall(z);break;case 1:this.handleResult(z);break;case 2:this.handleError(z);break;case 3:this.handleCallback(z);break;case 4:this.handleCallbackRelease(z);break;case 5:this.handleEndpointRelease();break}}async handleCall(q){let{id:z,p:G,a:Q,c:Z}=q;try{let{method:$,thisArg:K}=this.resolveMethod(G);if(typeof $!=="function")throw Error(`Method not found: ${G.join(".")}`);let J=Q.map((Y,L)=>{let N=Z?.[L];if(N)return this.createRemoteCallback(N);return Y}),U=await $.apply(K,J),{rawValue:W,callbackMap:H}=this.processResult(U);this.send({t:1,id:z,v:W,...Object.keys(H).length>0&&{cm:H}})}catch($){let K=$ instanceof Error?$:Error(String($));this.send({t:2,id:z,e:K.message,n:K.name,s:K.stack})}}resolveMethod(q){if(!this.exposedApi)throw Error("No API exposed");let z=this.exposedApi,G=null;for(let Q=0;Q<q.length;Q++){let Z=q[Q];if(z===null||z===void 0)throw Error(`Cannot access property '${Z}' of ${z}`);if(Z===void 0)throw Error(`Invalid path at index ${Q}`);if(G=z,z=z[Z],Q>=(this.exposeOptions.maxDepth??10))throw Error("Maximum nesting depth exceeded")}return{target:this.exposedApi,method:z,thisArg:G}}handleResult(q){let z=this.pendingCalls.get(q.id);if(!z){this.debug("Received result for unknown call:",q.id);return}if(this.pendingCalls.delete(q.id),z.timer)clearTimeout(z.timer);if(q.cm&&Object.keys(q.cm).length>0)z.resolve(this.reconstructResult(q.v,q.cm));else z.resolve(q.v)}handleError(q){let z=this.pendingCalls.get(q.id);if(!z){this.debug("Received error for unknown call:",q.id);return}if(this.pendingCalls.delete(q.id),z.timer)clearTimeout(z.timer);let G=Error(q.e);if(q.n)G.name=q.n;if(q.s)G.stack=q.s;z.reject(G)}async handleCallback(q){let{id:z,c:G,a:Q,cb:Z}=q,$=this.callbacks.get(G);if(!$){this.send({t:2,id:z,e:`Callback not found: ${G}`});return}try{let K=Q.map((H,Y)=>{let L=Z?.[Y];if(L)return this.createRemoteCallback(L);return H}),J=await $.fn(...K);if($.remaining>0){if($.remaining--,$.remaining===0)this.callbacks.delete(G)}let{rawValue:U,callbackMap:W}=this.processResult(J);this.send({t:1,id:z,v:U,...Object.keys(W).length>0&&{cm:W}})}catch(K){let J=K instanceof Error?K:Error(String(K));this.send({t:2,id:z,e:J.message,n:J.name,s:J.stack})}}handleCallbackRelease(q){for(let z of q.c)this.callbacks.delete(z)}handleEndpointRelease(){this.debug("Remote endpoint released"),this.options.onRelease(),this.release({silent:!0})}registerCallback(q,z=-1){let G=this.generateCallbackId();return this.callbacks.set(G,{fn:q,remaining:z}),G}createRemoteCallback(q){let z=this.remoteCallbacks.get(q);if(z)return z;return z=async(...G)=>{return this.invokeCallback(q,G)},this.remoteCallbacks.set(q,z),z}async invokeCallback(q,z){let G=this.generateCallId(),Q=[],Z={},$=[];for(let K=0;K<z.length;K++){let J=z[K];if(X(J))Z[K]=this.registerCallback(J),Q.push(null);else if(j(J))$.push(J),Q.push(J);else Q.push(J)}return new Promise((K,J)=>{let U=setTimeout(()=>{this.pendingCalls.delete(G),J(Error(`Callback invocation timed out: ${q}`))},this.options.timeout);this.pendingCalls.set(G,{resolve:K,reject:J,timer:U});let W={t:3,id:G,c:q,a:Q,...Object.keys(Z).length>0&&{cb:Z}};this.send(W,$)})}processArgs(q){let z=[],G={},Q=[];for(let Z=0;Z<q.length;Z++){let $=q[Z];if(X($))G[Z]=this.registerCallback($),z.push(null);else if(j($))Q.push($),z.push($);else z.push($)}return{rawArgs:z,callbackMap:G,transferables:Q}}processResult(q){if(X(q))return{rawValue:null,callbackMap:{"":this.registerCallback(q)}};if(q!==null&&typeof q==="object"&&!Array.isArray(q)&&!j(q)&&!(q instanceof ArrayBuffer)){let z={},G={};for(let Q in q){let Z=q[Q];if(X(Z))z[Q]=this.registerCallback(Z);else G[Q]=Z}if(Object.keys(z).length>0)return{rawValue:G,callbackMap:z}}return{rawValue:q,callbackMap:{}}}reconstructResult(q,z){if(""in z)return this.createRemoteCallback(z[""]);let G=q&&typeof q==="object"?{...q}:{};for(let Q in z){let Z=z[Q];if(Z)G[Q]=this.createRemoteCallback(Z)}return G}buildCallMessage(q,z,G,Q){return{t:0,id:q,p:z,a:G,...Object.keys(Q).length>0&&{c:Q}}}call(q,z){if(this.released)return Promise.reject(Error("Endpoint has been released"));let{rawArgs:G,callbackMap:Q,transferables:Z}=this.processArgs(z),$=this.generateCallId(),K=this.buildCallMessage($,q,G,Q);return new Promise((J,U)=>{let W=setTimeout(()=>{this.pendingCalls.delete($),U(Error(`Call timed out: ${q.join(".")}`))},this.options.timeout);this.pendingCalls.set($,{resolve:J,reject:U,timer:W}),this.send(K,Z)})}send(q,z=[]){this.debug("sending",V[q.t],q),this.target.postMessage(q,z)}getPendingCallCount(){return this.pendingCalls.size}hasPendingCalls(){return this.pendingCalls.size>0}async shutdown(q={}){let{timeout:z=f}=q;if(this.released)return{success:!0,timeout:!1};if(!this.hasPendingCalls())return this.release(),{success:!0,timeout:!1};return new Promise((G)=>{let Q=!1,Z=(K)=>{if(Q)return;if(Q=!0,clearTimeout($),!this.released)this.release();G(K)},$=setTimeout(()=>{Z({success:!1,timeout:!0})},z);for(let[,K]of this.pendingCalls){let{resolve:J,reject:U}=K;K.resolve=(W)=>{if(J(W),!this.hasPendingCalls())Z({success:!0,timeout:!1})},K.reject=(W)=>{if(U(W),!this.hasPendingCalls())Z({success:!0,timeout:!1})}}})}release(q={}){if(this.released)return;this.released=!0;for(let[,G]of this.pendingCalls){if(G.timer)clearTimeout(G.timer);G.reject(Error("Endpoint released"))}this.pendingCalls.clear();let{silent:z=!1}=q;if(!z){if(this.remoteCallbacks.size>0){let G=[...this.remoteCallbacks.keys()];this.send({t:4,id:this.generateCallId(),c:G})}this.send({t:5,id:this.generateCallId()})}this.remoteCallbacks.clear(),this.callbacks.clear(),this.detachListener()}getTarget(){return this.target}}function X(q){return typeof q==="function"}function j(q){return typeof MessagePort<"u"&&q instanceof MessagePort}function D(q,z){return new S(q,z)}var F=Symbol("rpc:path"),B=Symbol("rpc:proxy-endpoint"),_=Symbol.for("rpc:remote-proxy"),C=Symbol.for("rpc:endpoint"),R=Symbol.for("rpc:release"),w={get(q,z){if(z===_)return!0;if(z===C)return q[B];if(z===R)return()=>{q[B].release()};if(z==="then"){if(q[F].length===0)return;let G=q[B],Q=q[F],Z=G.call(Q,[]);return Z.then.bind(Z)}if(z==="toJSON")return;if(z===Symbol.toStringTag||z===Symbol.iterator||z===Symbol.asyncIterator||z==="constructor"||z==="prototype")return;if(typeof z==="string"){let G=q[B],Z=[...q[F],z];return O(G,Z)}},apply(q,z,G){let Q=q[B],Z=q[F];return Q.call(Z,G)},set(){throw Error("Cannot set properties on a remote proxy")},deleteProperty(){throw Error("Cannot delete properties on a remote proxy")},getPrototypeOf(){return Function.prototype},has(q,z){return z===_||z===C||z===R}};function O(q,z){let G=function(){};return G[B]=q,G[F]=z,new Proxy(G,w)}function x(q){return O(q,[])}function A(q){if(q===null||q===void 0)return!1;try{return q[_]===!0}catch{return!1}}function T(q){if(A(q))return q[C]}function v(q){if(A(q))q[R]()}class P extends S{pendingBroadcastCalls=new Map;get broadcastTarget(){return this.target}call(q,z){if(this.released)return Promise.reject(Error("Endpoint has been released"));let{rawArgs:G,callbackMap:Q,transferables:Z}=this.processArgs(z),$=this.generateCallId(),K=this.buildCallMessage($,q,G,Q);return this.sendBroadcast($,K,Z,q)}sendBroadcast(q,z,G,Q){return this.debug("sending broadcast",V[z.t],z),new Promise((Z,$)=>{let K={resolve:Z,reject:$,timer:setTimeout(()=>this.handleTimeout(q,Q),this.options.timeout),expectedCount:1/0,results:[],errors:[]};this.pendingBroadcastCalls.set(q,K),Promise.resolve(this.broadcastTarget.postMessage(z,G)).then((J)=>this.handleSubscriberCount(q,J))})}handleTimeout(q,z){let G=this.pendingBroadcastCalls.get(q);if(!G)return;this.pendingBroadcastCalls.delete(q);let Q=G.results.length+G.errors.length,Z=G.expectedCount===1/0?0:G.expectedCount-Q;if(Z>0){let $=Error(`Broadcast timed out: ${z.join(".")}`);$.name="TimeoutError";for(let K=0;K<Z;K++)G.errors.push($)}if(this.debug(`Broadcast timed out with ${G.results.length} results, ${G.errors.length} errors (${Z} timed out)`),G.results.length>0||G.errors.length>0)G.resolve({results:G.results,errors:G.errors});else G.reject(Error(`Broadcast timed out: ${z.join(".")}`))}handleSubscriberCount(q,z){let G=this.pendingBroadcastCalls.get(q);if(!G)return;if(z===0){this.completeBroadcast(q,G);return}G.expectedCount=z,this.checkCompletion(q,G)}checkCompletion(q,z){if(z.results.length+z.errors.length>=z.expectedCount)this.completeBroadcast(q,z)}completeBroadcast(q,z){this.pendingBroadcastCalls.delete(q),clearTimeout(z.timer),z.resolve({results:z.results,errors:z.errors})}handleResult(q){let z=this.pendingBroadcastCalls.get(q.id);if(!z){super.handleResult(q);return}if(q.cm&&Object.keys(q.cm).length>0)z.results.push(this.reconstructResult(q.v,q.cm));else z.results.push(q.v);this.debug(`Broadcast received ${z.results.length}/${z.expectedCount} responses`),this.checkCompletion(q.id,z)}handleError(q){let z=this.pendingBroadcastCalls.get(q.id);if(!z){super.handleError(q);return}let G=Error(q.e);if(q.n)G.name=q.n;if(q.s)G.stack=q.s;z.errors.push(G),this.debug(`Broadcast received error (${z.results.length} results, ${z.errors.length} errors / ${z.expectedCount} expected)`),this.checkCompletion(q.id,z)}getPendingCallCount(){return super.getPendingCallCount()+this.pendingBroadcastCalls.size}hasPendingCalls(){return super.hasPendingCalls()||this.pendingBroadcastCalls.size>0}release(q={}){for(let[,z]of this.pendingBroadcastCalls)clearTimeout(z.timer),z.reject(Error("Endpoint released"));this.pendingBroadcastCalls.clear(),super.release(q)}}function y(q,z){return new P(q,z)}function l(q,z){let Q=D(globalThis,z);return Q.expose(q),Q}function o(q,z){let G=D(q,z);return x(G)}export{x as wrap,o as remote,v as releaseProxy,A as isProxy,j as isMessagePort,X as isFunction,T as getEndpoint,l as expose,D as createEndpoint,y as createBroadcastEndpoint,_ as REMOTE_PROXY,R as RELEASE,B as PROXY_ENDPOINT,F as PATH,V as MessageType,S as Endpoint,C as ENDPOINT,P as BroadcastEndpoint};
2
+ var B;(($)=>{$[$.Call=0]="Call";$[$.Result=1]="Result";$[$.Error=2]="Error";$[$.Callback=3]="Callback";$[$.CallbackRelease=4]="CallbackRelease";$[$.EndpointRelease=5]="EndpointRelease"})(B||={});var E=new Set(["message","name","stack","cause"]);function Y(q){let G={e:q.message,n:q.name,s:q.stack},Q={};for(let Z of Object.keys(q))if(!E.has(Z))Q[Z]=q[Z];if(Object.keys(Q).length>0)G.d=Q;if(q.cause instanceof Error)G.c=Y(q.cause);else if(q.cause!==void 0)G.c={e:String(q.cause),d:{cause:q.cause}};return G}function z(q){let G=q.c?{cause:z(q.c)}:void 0,Q=Error(q.e,G);if(q.n)Q.name=q.n;if(q.s)Q.stack=q.s;if(q.d)Object.assign(Q,q.d);return Q}var H=Symbol("rpc:path"),F=Symbol("rpc:proxy-endpoint"),_=Symbol.for("rpc:remote-proxy"),C=Symbol.for("rpc:endpoint"),N=Symbol.for("rpc:release"),x=Symbol.for("rpc:callback-release"),T={get(q,G){if(G===_)return!0;if(G===C)return q[F];if(G===N)return()=>{q[F].release()};if(G==="then"){if(q[H].length===0)return;let Q=q[F],Z=q[H],J=Q.call(Z,[]);return J.then.bind(J)}if(G==="toJSON")return;if(G===Symbol.toStringTag||G===Symbol.iterator||G===Symbol.asyncIterator||G==="constructor"||G==="prototype")return;if(typeof G==="string"){let Q=q[F],J=[...q[H],G];return P(Q,J)}},apply(q,G,Q){let Z=q[F],J=q[H];return Z.call(J,Q)},set(){throw Error("Cannot set properties on a remote proxy")},deleteProperty(){throw Error("Cannot delete properties on a remote proxy")},getPrototypeOf(){return Function.prototype},has(q,G){return G===_||G===C||G===N}};function P(q,G){let Q=function(){};return Q[F]=q,Q[H]=G,new Proxy(Q,T)}function w(q){return P(q,[])}function A(q){if(q===null||q===void 0)return!1;try{return q[_]===!0}catch{return!1}}function M(q){if(A(q))return q[C]}function b(q){if(A(q))q[N]()}var I=30000;class S{static generateRandomId(){return Math.random().toString(36).slice(2,8)}static extractEndpointId(q){return q.split(":")[1]??""}target;options;exposedApi=null;exposeOptions={};id;callCounter=0;callbackCounter=0;pendingCalls=new Map;callbacks=new Map;remoteCallbacks=new Map;boundMessageHandler;boundCloseHandler;released=!1;releaseHandlers=new Set;constructor(q,G={}){this.target=q,this.id=G.id??S.generateRandomId(),this.options={id:this.id,timeout:G.timeout??I,onError:G.onError??console.error,debug:G.debug??!1,onRelease:G.onRelease??(()=>{})},this.boundMessageHandler=this.handleMessage.bind(this),this.boundCloseHandler=this.handleTargetClose.bind(this),this.attachListener()}generateCallId(){return`c:${this.id}:${++this.callCounter}`}generateCallbackId(){return`cb:${this.id}:${++this.callbackCounter}`}attachListener(){if(this.target.addEventListener)this.target.addEventListener("message",this.boundMessageHandler),this.target.addEventListener("close",this.boundCloseHandler),this.target.addEventListener("error",this.boundCloseHandler);else if(this.target.onmessage!==void 0)this.target.onmessage=this.boundMessageHandler}detachListener(){if(this.target.removeEventListener)this.target.removeEventListener("message",this.boundMessageHandler),this.target.removeEventListener("close",this.boundCloseHandler),this.target.removeEventListener("error",this.boundCloseHandler);else if(this.target.onmessage!==void 0)this.target.onmessage=null}handleTargetClose(){if(this.released)return;this.debug("Target closed or errored (remote endpoint likely died)");for(let[q,G]of this.pendingCalls){if(G.timer)clearTimeout(G.timer);G.reject(Error("Remote endpoint disconnected"))}this.pendingCalls.clear();for(let q of this.releaseHandlers)try{q()}catch{}this.releaseHandlers.clear(),this.options.onRelease(),this.released=!0,this.remoteCallbacks.clear(),this.callbacks.clear(),this.detachListener()}debug(...q){if(this.options.debug)console.log("[worker-rpc]",...q)}expose(q,G={}){this.exposedApi=q,this.exposeOptions={maxDepth:G.maxDepth??10}}handleMessage(q){let G=q.data;if(typeof G!=="object"||G===null||!("t"in G))return;switch(this.debug("received",B[G.t],G),G.t){case 0:this.handleCall(G);break;case 1:this.handleResult(G);break;case 2:this.handleError(G);break;case 3:this.handleCallback(G);break;case 4:this.handleCallbackRelease(G);break;case 5:this.handleEndpointRelease();break}}async handleCall(q){let{id:G,p:Q,a:Z,c:J}=q;try{let{method:K,thisArg:$}=this.resolveMethod(Q);if(typeof K!=="function")throw Error(`Method not found: ${Q.join(".")}`);let W=Z.map((D,X)=>{let O=J?.[X];if(O)return this.createRemoteCallback(O);return D}),V=await K.apply($,W),{rawValue:U,callbackMap:L}=this.processResult(V);this.send({t:1,id:G,v:U,...Object.keys(L).length>0&&{cm:L}})}catch(K){let $=K instanceof Error?K:Error(String(K));this.send({t:2,id:G,...Y($)})}}resolveMethod(q){if(!this.exposedApi)throw Error("No API exposed");let G=this.exposedApi,Q=null;for(let Z=0;Z<q.length;Z++){let J=q[Z];if(G===null||G===void 0)throw Error(`Cannot access property '${J}' of ${G}`);if(J===void 0)throw Error(`Invalid path at index ${Z}`);if(Q=G,G=G[J],Z>=(this.exposeOptions.maxDepth??10))throw Error("Maximum nesting depth exceeded")}return{target:this.exposedApi,method:G,thisArg:Q}}handleResult(q){let G=this.pendingCalls.get(q.id);if(!G){this.debug("Received result for unknown call:",q.id);return}if(this.pendingCalls.delete(q.id),G.timer)clearTimeout(G.timer);if(q.cm&&Object.keys(q.cm).length>0)G.resolve(this.reconstructResult(q.v,q.cm));else G.resolve(q.v)}handleError(q){let G=this.pendingCalls.get(q.id);if(!G){this.debug("Received error for unknown call:",q.id);return}if(this.pendingCalls.delete(q.id),G.timer)clearTimeout(G.timer);G.reject(z(q))}async handleCallback(q){let{id:G,c:Q,a:Z,cb:J}=q,K=this.callbacks.get(Q);if(!K){this.send({t:2,id:G,e:`Callback not found: ${Q}`});return}try{let $=Z.map((L,D)=>{let X=J?.[D];if(X)return this.createRemoteCallback(X);return L}),W=await K.fn(...$);if(K.remaining>0){if(K.remaining--,K.remaining===0)this.callbacks.delete(Q)}let{rawValue:V,callbackMap:U}=this.processResult(W);this.send({t:1,id:G,v:V,...Object.keys(U).length>0&&{cm:U}})}catch($){let W=$ instanceof Error?$:Error(String($));this.send({t:2,id:G,...Y(W)})}}handleCallbackRelease(q){for(let G of q.c)this.callbacks.delete(G)}handleEndpointRelease(){this.debug("Remote endpoint released");for(let q of this.releaseHandlers)try{q()}catch{}this.releaseHandlers.clear(),this.options.onRelease(),this.release({silent:!0})}registerCallback(q,G=-1){let Q=this.generateCallbackId();return this.callbacks.set(Q,{fn:q,remaining:G}),Q}createRemoteCallback(q){let G=this.remoteCallbacks.get(q);if(G)return G;return G=async(...Q)=>{return this.invokeCallback(q,Q)},this.remoteCallbacks.set(q,G),G}async invokeCallback(q,G){let Q=this.generateCallId(),Z=[],J={},K=[];for(let $=0;$<G.length;$++){let W=G[$];if(j(W))J[$]=this.registerCallback(W),Z.push(null);else if(R(W))K.push(W),Z.push(W);else Z.push(W)}return new Promise(($,W)=>{let V=setTimeout(()=>{this.pendingCalls.delete(Q),W(Error(`Callback invocation timed out: ${q}`))},this.options.timeout);this.pendingCalls.set(Q,{resolve:$,reject:W,timer:V});let U={t:3,id:Q,c:q,a:Z,...Object.keys(J).length>0&&{cb:J}};this.send(U,K)})}processArgs(q){let G=[],Q={},Z=[];for(let J=0;J<q.length;J++){let K=q[J];if(j(K))Q[J]=this.registerCallback(K),G.push(null);else if(R(K))Z.push(K),G.push(K);else G.push(K)}return{rawArgs:G,callbackMap:Q,transferables:Z}}processResult(q){if(j(q))return{rawValue:null,callbackMap:{"":this.registerCallback(q)}};if(q!==null&&typeof q==="object"&&!Array.isArray(q)&&!R(q)&&!(q instanceof ArrayBuffer)){let G={},Q={};for(let J in q){let K=q[J];if(j(K))G[J]=this.registerCallback(K);else Q[J]=K}let Z=q[x];if(typeof Z==="function")this.releaseHandlers.add(Z);if(Object.keys(G).length>0)return{rawValue:Q,callbackMap:G}}return{rawValue:q,callbackMap:{}}}reconstructResult(q,G){if(""in G)return this.createRemoteCallback(G[""]);let Q=q&&typeof q==="object"?{...q}:{};for(let Z in G){let J=G[Z];if(J)Q[Z]=this.createRemoteCallback(J)}return Q}buildCallMessage(q,G,Q,Z){return{t:0,id:q,p:G,a:Q,...Object.keys(Z).length>0&&{c:Z}}}call(q,G){if(this.released)return Promise.reject(Error("Endpoint has been released"));let{rawArgs:Q,callbackMap:Z,transferables:J}=this.processArgs(G),K=this.generateCallId(),$=this.buildCallMessage(K,q,Q,Z);return new Promise((W,V)=>{let U=setTimeout(()=>{this.pendingCalls.delete(K),V(Error(`Call timed out: ${q.join(".")}`))},this.options.timeout);this.pendingCalls.set(K,{resolve:W,reject:V,timer:U}),this.send($,J)})}send(q,G=[]){this.debug("sending",B[q.t],q),this.target.postMessage(q,G)}getPendingCallCount(){return this.pendingCalls.size}hasPendingCalls(){return this.pendingCalls.size>0}async shutdown(q={}){let{timeout:G=I}=q;if(this.released)return{success:!0,timeout:!1};if(!this.hasPendingCalls())return this.release(),{success:!0,timeout:!1};return new Promise((Q)=>{let Z=!1,J=($)=>{if(Z)return;if(Z=!0,clearTimeout(K),!this.released)this.release();Q($)},K=setTimeout(()=>{J({success:!1,timeout:!0})},G);for(let[,$]of this.pendingCalls){let{resolve:W,reject:V}=$;$.resolve=(U)=>{if(W(U),!this.hasPendingCalls())J({success:!0,timeout:!1})},$.reject=(U)=>{if(V(U),!this.hasPendingCalls())J({success:!0,timeout:!1})}}})}release(q={}){if(this.released)return;this.released=!0;for(let[,Q]of this.pendingCalls){if(Q.timer)clearTimeout(Q.timer);Q.reject(Error("Endpoint released"))}this.pendingCalls.clear();let{silent:G=!1}=q;if(!G){if(this.remoteCallbacks.size>0){let Q=[...this.remoteCallbacks.keys()];this.send({t:4,id:this.generateCallId(),c:Q})}this.send({t:5,id:this.generateCallId()})}this.remoteCallbacks.clear(),this.callbacks.clear(),this.releaseHandlers.clear(),this.detachListener()}getTarget(){return this.target}}function j(q){return typeof q==="function"}function R(q){return typeof MessagePort<"u"&&q instanceof MessagePort}function f(q,G){return new S(q,G)}class h extends S{pendingBroadcastCalls=new Map;get broadcastTarget(){return this.target}call(q,G){if(this.released)return Promise.reject(Error("Endpoint has been released"));let{rawArgs:Q,callbackMap:Z,transferables:J}=this.processArgs(G),K=this.generateCallId(),$=this.buildCallMessage(K,q,Q,Z);return this.sendBroadcast(K,$,J,q)}sendBroadcast(q,G,Q,Z){return this.debug("sending broadcast",B[G.t],G),new Promise((J,K)=>{let $={resolve:J,reject:K,timer:setTimeout(()=>this.handleTimeout(q,Z),this.options.timeout),expectedCount:1/0,results:[],errors:[]};this.pendingBroadcastCalls.set(q,$),Promise.resolve(this.broadcastTarget.postMessage(G,Q)).then((W)=>this.handleSubscriberCount(q,W))})}handleTimeout(q,G){let Q=this.pendingBroadcastCalls.get(q);if(!Q)return;this.pendingBroadcastCalls.delete(q);let Z=Q.results.length+Q.errors.length,J=Q.expectedCount===1/0?0:Q.expectedCount-Z;if(J>0){let K=Error(`Broadcast timed out: ${G.join(".")}`);K.name="TimeoutError";for(let $=0;$<J;$++)Q.errors.push(K)}if(this.debug(`Broadcast timed out with ${Q.results.length} results, ${Q.errors.length} errors (${J} timed out)`),Q.results.length>0||Q.errors.length>0)Q.resolve({results:Q.results,errors:Q.errors});else Q.reject(Error(`Broadcast timed out: ${G.join(".")}`))}handleSubscriberCount(q,G){let Q=this.pendingBroadcastCalls.get(q);if(!Q)return;if(G===0){this.completeBroadcast(q,Q);return}Q.expectedCount=G,this.checkCompletion(q,Q)}checkCompletion(q,G){if(G.results.length+G.errors.length>=G.expectedCount)this.completeBroadcast(q,G)}completeBroadcast(q,G){this.pendingBroadcastCalls.delete(q),clearTimeout(G.timer),G.resolve({results:G.results,errors:G.errors})}handleResult(q){let G=this.pendingBroadcastCalls.get(q.id);if(!G){super.handleResult(q);return}if(q.cm&&Object.keys(q.cm).length>0)G.results.push(this.reconstructResult(q.v,q.cm));else G.results.push(q.v);this.debug(`Broadcast received ${G.results.length}/${G.expectedCount} responses`),this.checkCompletion(q.id,G)}handleError(q){let G=this.pendingBroadcastCalls.get(q.id);if(!G){super.handleError(q);return}G.errors.push(z(q)),this.debug(`Broadcast received error (${G.results.length} results, ${G.errors.length} errors / ${G.expectedCount} expected)`),this.checkCompletion(q.id,G)}getPendingCallCount(){return super.getPendingCallCount()+this.pendingBroadcastCalls.size}hasPendingCalls(){return super.hasPendingCalls()||this.pendingBroadcastCalls.size>0}release(q={}){for(let[,G]of this.pendingBroadcastCalls)clearTimeout(G.timer),G.reject(Error("Endpoint released"));this.pendingBroadcastCalls.clear(),super.release(q)}}function g(q,G){return new h(q,G)}function r(q,G){let Z=f(globalThis,G);return Z.expose(q),Z}function a(q,G){let Q=f(q,G);return w(Q)}export{w as wrap,Y as serializeError,a as remote,b as releaseProxy,A as isProxy,R as isMessagePort,j as isFunction,M as getEndpoint,r as expose,z as deserializeError,f as createEndpoint,g as createBroadcastEndpoint,_ as REMOTE_PROXY,N as RELEASE,F as PROXY_ENDPOINT,H as PATH,B as MessageType,S as Endpoint,C as ENDPOINT,x as CALLBACK_RELEASE,h as BroadcastEndpoint};
package/dist/proxy.d.ts CHANGED
@@ -11,6 +11,8 @@ export declare const REMOTE_PROXY: unique symbol;
11
11
  export declare const ENDPOINT: unique symbol;
12
12
  /** Symbol for releasing a proxy */
13
13
  export declare const RELEASE: unique symbol;
14
+ /** Symbol for registering a cleanup handler on a returned object, invoked when the remote endpoint releases */
15
+ export declare const CALLBACK_RELEASE: unique symbol;
14
16
  /**
15
17
  * Wrap a worker/endpoint to create a type-safe remote API proxy
16
18
  *
package/dist/types.d.ts CHANGED
@@ -41,6 +41,8 @@ export interface ResultMessage extends BaseMessage {
41
41
  /** Callback map: property key -> callbackId (empty string key "" for single function return) */
42
42
  cm?: Record<string, CallbackId>;
43
43
  }
44
+ /** Serialized error data (used for error cause chains) */
45
+ export type SerializedError = Omit<ErrorMessage, 't' | 'id'>;
44
46
  /** Error response */
45
47
  export interface ErrorMessage extends BaseMessage {
46
48
  t: MessageType.Error;
@@ -50,6 +52,10 @@ export interface ErrorMessage extends BaseMessage {
50
52
  n?: string;
51
53
  /** Error stack trace */
52
54
  s?: string;
55
+ /** Custom error properties (own enumerable properties beyond message/name/stack) */
56
+ d?: Record<string, unknown>;
57
+ /** Serialized error cause (recursive chain) */
58
+ c?: SerializedError;
53
59
  }
54
60
  /** Callback invocation request */
55
61
  export interface CallbackMessage extends BaseMessage {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xentobias/worker-rpc",
3
- "version": "1.0.13",
3
+ "version": "1.0.15",
4
4
  "description": "High-performance, type-safe RPC for Workers",
5
5
  "module": "src/index.ts",
6
6
  "main": "dist/index.js",