@xentobias/worker-rpc 1.0.15 → 1.0.16
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/dist/broadcast.d.ts +0 -4
- package/dist/endpoint.d.ts +64 -110
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -1
- package/dist/transformer.d.ts +96 -0
- package/dist/transformers/index.d.ts +19 -0
- package/dist/transformers/index.js +2 -0
- package/dist/transformers/request.d.ts +36 -0
- package/dist/transformers/response.d.ts +30 -0
- package/dist/transformers/utils.d.ts +2 -0
- package/dist/types.d.ts +4 -3
- package/package.json +6 -2
package/dist/broadcast.d.ts
CHANGED
|
@@ -34,10 +34,6 @@ export declare class BroadcastEndpoint extends Endpoint {
|
|
|
34
34
|
* @returns Promise that resolves with a BroadcastResult containing results and errors
|
|
35
35
|
*/
|
|
36
36
|
call(path: MethodPath, args: unknown[]): Promise<BroadcastResult>;
|
|
37
|
-
/**
|
|
38
|
-
* Send a broadcast message and set up multi-response handling
|
|
39
|
-
*/
|
|
40
|
-
private sendBroadcast;
|
|
41
37
|
/**
|
|
42
38
|
* Handle timeout for a broadcast call
|
|
43
39
|
*/
|
package/dist/endpoint.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { type RpcMessage, type CallMessage, type ResultMessage, type ErrorMessage, type MethodPath, type PendingCall, type CallbackRegistration, type MessageTarget, type TransferableValue } from './types';
|
|
2
|
+
import { type Transformer } from './transformer';
|
|
2
3
|
/** Unique identifier for tracking RPC calls */
|
|
3
4
|
export type CallId = `c:${string}`;
|
|
4
5
|
/** Unique identifier for callback functions */
|
|
@@ -19,6 +20,32 @@ export interface EndpointOptions {
|
|
|
19
20
|
onError?: (error: Error) => void;
|
|
20
21
|
/** Callback invoked when the remote endpoint is released */
|
|
21
22
|
onRelease?: () => void;
|
|
23
|
+
/**
|
|
24
|
+
* Codecs for non-cloneable values. Checked in order; first match wins.
|
|
25
|
+
* The scanning depth into plain objects is controlled by `transformDepth`.
|
|
26
|
+
*
|
|
27
|
+
* @example
|
|
28
|
+
* ```typescript
|
|
29
|
+
* import { responseTransformer } from '@xentobias/worker-rpc/transformers';
|
|
30
|
+
*
|
|
31
|
+
* const endpoint = createEndpoint(worker, {
|
|
32
|
+
* transformers: [responseTransformer],
|
|
33
|
+
* });
|
|
34
|
+
* ```
|
|
35
|
+
*/
|
|
36
|
+
transformers?: Transformer[];
|
|
37
|
+
/**
|
|
38
|
+
* How many levels deep to scan plain-object properties for transformer-
|
|
39
|
+
* matched values.
|
|
40
|
+
*
|
|
41
|
+
* - `1` (default) — only first-level properties are scanned (backward-compatible).
|
|
42
|
+
* - `2` — scans two levels deep (e.g. `{ a: { req: new Request(...) } }`).
|
|
43
|
+
* - `Infinity` — recurse without limit.
|
|
44
|
+
*
|
|
45
|
+
* The top-level value itself is always checked regardless of this setting.
|
|
46
|
+
* Function extraction in return values is unaffected and remains first-level only.
|
|
47
|
+
*/
|
|
48
|
+
transformDepth?: number;
|
|
22
49
|
}
|
|
23
50
|
/** Configuration for exposing an API */
|
|
24
51
|
export interface ExposeOptions {
|
|
@@ -41,10 +68,6 @@ export interface ShutdownResult {
|
|
|
41
68
|
* RPC Endpoint - handles bidirectional communication with a worker
|
|
42
69
|
*/
|
|
43
70
|
export declare class Endpoint {
|
|
44
|
-
/**
|
|
45
|
-
* Generate a random ID string for use as a default endpoint ID.
|
|
46
|
-
* @returns A random 6-character alphanumeric string
|
|
47
|
-
*/
|
|
48
71
|
private static generateRandomId;
|
|
49
72
|
/**
|
|
50
73
|
* Extract the endpoint ID from a call ID.
|
|
@@ -63,7 +86,9 @@ export declare class Endpoint {
|
|
|
63
86
|
*/
|
|
64
87
|
static extractEndpointId(callId: CallId): string;
|
|
65
88
|
protected target: MessageTarget;
|
|
66
|
-
protected options: Required<EndpointOptions
|
|
89
|
+
protected options: Required<Omit<EndpointOptions, 'transformers'>> & {
|
|
90
|
+
transformers: Transformer[];
|
|
91
|
+
};
|
|
67
92
|
private exposedApi;
|
|
68
93
|
private exposeOptions;
|
|
69
94
|
/** Unique identifier for this endpoint */
|
|
@@ -87,136 +112,77 @@ export declare class Endpoint {
|
|
|
87
112
|
/** Release handlers from returned objects with [CALLBACK_RELEASE] */
|
|
88
113
|
protected releaseHandlers: Set<() => void>;
|
|
89
114
|
constructor(target: MessageTarget, options?: EndpointOptions);
|
|
90
|
-
/**
|
|
91
|
-
* Generate a unique call ID for this endpoint
|
|
92
|
-
*/
|
|
93
115
|
protected generateCallId(): CallId;
|
|
94
|
-
/**
|
|
95
|
-
* Generate a unique callback ID for this endpoint
|
|
96
|
-
*/
|
|
97
116
|
protected generateCallbackId(): CallbackId;
|
|
98
|
-
/**
|
|
99
|
-
* Attach the message listener to the target
|
|
100
|
-
*/
|
|
101
117
|
private attachListener;
|
|
102
|
-
/**
|
|
103
|
-
* Detach the message listener from the target
|
|
104
|
-
*/
|
|
105
118
|
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
|
-
*/
|
|
119
|
+
/** Handles worker death / port close — rejects pending calls and cleans up without sending messages. */
|
|
112
120
|
private handleTargetClose;
|
|
113
|
-
/**
|
|
114
|
-
* Log a debug message
|
|
115
|
-
*/
|
|
116
121
|
protected debug(...args: unknown[]): void;
|
|
117
|
-
/**
|
|
118
|
-
* Expose an API object for remote invocation
|
|
119
|
-
*/
|
|
120
122
|
expose(api: object, options?: ExposeOptions): void;
|
|
121
|
-
/**
|
|
122
|
-
* Handle incoming messages
|
|
123
|
-
*/
|
|
124
123
|
private handleMessage;
|
|
125
|
-
/**
|
|
126
|
-
* Handle a method call request
|
|
127
|
-
*/
|
|
128
124
|
private handleCall;
|
|
129
|
-
/**
|
|
130
|
-
* Resolve a method from the exposed API by path
|
|
131
|
-
*/
|
|
132
125
|
private resolveMethod;
|
|
133
|
-
|
|
134
|
-
* Handle a successful result
|
|
135
|
-
*/
|
|
126
|
+
protected resolveResult(value: unknown, callbackMap?: Record<string, CallbackId>): Promise<unknown>;
|
|
136
127
|
protected handleResult(message: ResultMessage): void;
|
|
137
|
-
/**
|
|
138
|
-
* Handle an error result
|
|
139
|
-
*/
|
|
140
128
|
protected handleError(message: ErrorMessage): void;
|
|
141
|
-
/**
|
|
142
|
-
* Handle a callback invocation
|
|
143
|
-
*/
|
|
144
129
|
private handleCallback;
|
|
145
|
-
/**
|
|
146
|
-
* Handle release of callbacks
|
|
147
|
-
*/
|
|
148
130
|
private handleCallbackRelease;
|
|
149
|
-
/**
|
|
150
|
-
* Handle notification that the remote endpoint has been released
|
|
151
|
-
*/
|
|
152
131
|
private handleEndpointRelease;
|
|
153
|
-
/**
|
|
154
|
-
* Register a local callback function
|
|
155
|
-
*/
|
|
156
132
|
protected registerCallback(fn: Function, remaining?: number): CallbackId;
|
|
157
|
-
/**
|
|
158
|
-
* Create a proxy function for a remote callback
|
|
159
|
-
*/
|
|
160
133
|
protected createRemoteCallback(callbackId: CallbackId): Function;
|
|
134
|
+
private invokeCallback;
|
|
161
135
|
/**
|
|
162
|
-
*
|
|
136
|
+
* Encode a single value through transformers, recursively scanning plain-
|
|
137
|
+
* object properties up to `depth` levels deep.
|
|
138
|
+
* This is the symmetric counterpart of `decodeValue`.
|
|
139
|
+
*
|
|
140
|
+
* @param value - The value to encode.
|
|
141
|
+
* @param depth - Remaining levels of plain-object recursion. The top-level
|
|
142
|
+
* transformer match is always attempted regardless of this value.
|
|
163
143
|
*/
|
|
164
|
-
private
|
|
144
|
+
private encodeValue;
|
|
165
145
|
/**
|
|
166
|
-
*
|
|
146
|
+
* Decode a single value through transformers, recursively scanning plain-
|
|
147
|
+
* object properties up to `depth` levels deep.
|
|
148
|
+
* This is the symmetric counterpart of `encodeValue`.
|
|
149
|
+
*
|
|
150
|
+
* @param value - The value to decode.
|
|
151
|
+
* @param depth - Remaining levels of plain-object recursion. Encoded
|
|
152
|
+
* envelopes are always decoded regardless of this value.
|
|
167
153
|
*/
|
|
168
|
-
|
|
154
|
+
private decodeValue;
|
|
155
|
+
/** Extracts functions and MessagePorts from args. Used as the no-transformer fast path. */
|
|
156
|
+
protected processArgsSync(args: unknown[]): {
|
|
169
157
|
rawArgs: unknown[];
|
|
170
158
|
callbackMap: Record<number, CallbackId>;
|
|
171
159
|
transferables: TransferableValue[];
|
|
172
160
|
};
|
|
161
|
+
/** Like `processArgsSync`, but also encodes values through transformers. */
|
|
162
|
+
protected processArgs(args: unknown[]): Promise<{
|
|
163
|
+
rawArgs: unknown[];
|
|
164
|
+
callbackMap: Record<number, CallbackId>;
|
|
165
|
+
transferables: TransferableValue[];
|
|
166
|
+
}>;
|
|
173
167
|
/**
|
|
174
|
-
*
|
|
175
|
-
*
|
|
168
|
+
* Extracts first-level functions into the callback map and encodes
|
|
169
|
+
* non-cloneable values through transformers.
|
|
176
170
|
*/
|
|
177
|
-
protected processResult(value: unknown): {
|
|
171
|
+
protected processResult(value: unknown): Promise<{
|
|
178
172
|
rawValue: unknown;
|
|
179
173
|
callbackMap: Record<string, CallbackId>;
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
*/
|
|
184
|
-
protected reconstructResult(value: unknown, callbackMap: Record<string, CallbackId>): unknown;
|
|
185
|
-
/**
|
|
186
|
-
* Build a call message
|
|
187
|
-
*/
|
|
174
|
+
transferables: TransferableValue[];
|
|
175
|
+
}>;
|
|
176
|
+
protected reconstructResult(value: unknown, callbackMap: Record<string, CallbackId>): Promise<unknown>;
|
|
188
177
|
protected buildCallMessage(id: CallId, path: MethodPath, rawArgs: unknown[], callbackMap: Record<number, CallbackId>): CallMessage;
|
|
189
178
|
/**
|
|
190
179
|
* Call a remote method
|
|
191
180
|
*/
|
|
192
181
|
call(path: MethodPath, args: unknown[]): Promise<unknown>;
|
|
193
|
-
/**
|
|
194
|
-
* Send a message to the target
|
|
195
|
-
*/
|
|
196
182
|
protected send(message: RpcMessage, transferables?: TransferableValue[]): void;
|
|
197
|
-
/**
|
|
198
|
-
* Get the number of pending calls
|
|
199
|
-
*/
|
|
200
183
|
getPendingCallCount(): number;
|
|
201
|
-
/**
|
|
202
|
-
* Check if there are any pending calls
|
|
203
|
-
*/
|
|
204
184
|
hasPendingCalls(): boolean;
|
|
205
|
-
/**
|
|
206
|
-
* Gracefully shut down the endpoint, waiting for pending calls to complete
|
|
207
|
-
*
|
|
208
|
-
* @param options - Shutdown options
|
|
209
|
-
* @returns Promise that resolves with the shutdown result
|
|
210
|
-
*
|
|
211
|
-
* @example
|
|
212
|
-
* ```typescript
|
|
213
|
-
* // Wait up to 5 seconds for pending calls to complete
|
|
214
|
-
* const result = await endpoint.shutdown({ timeout: 5000 });
|
|
215
|
-
* if (result.timedOut) {
|
|
216
|
-
* console.log(`${result.terminatedCalls} calls were forcefully terminated`);
|
|
217
|
-
* }
|
|
218
|
-
* ```
|
|
219
|
-
*/
|
|
185
|
+
/** Waits for pending calls to finish, then releases. Resolves with `{ timeout: true }` if they don't finish in time. */
|
|
220
186
|
shutdown(options?: ShutdownOptions): Promise<ShutdownResult>;
|
|
221
187
|
/**
|
|
222
188
|
* Release this endpoint and clean up resources
|
|
@@ -227,20 +193,8 @@ export declare class Endpoint {
|
|
|
227
193
|
release(options?: {
|
|
228
194
|
silent?: boolean;
|
|
229
195
|
}): void;
|
|
230
|
-
/**
|
|
231
|
-
* Get the underlying message target
|
|
232
|
-
*/
|
|
233
196
|
getTarget(): MessageTarget;
|
|
234
197
|
}
|
|
235
|
-
/**
|
|
236
|
-
* Check if a value is a function
|
|
237
|
-
*/
|
|
238
198
|
export declare function isFunction(value: unknown): value is Function;
|
|
239
|
-
/**
|
|
240
|
-
* Check if a value is a MessagePort
|
|
241
|
-
*/
|
|
242
199
|
export declare function isMessagePort(value: unknown): value is MessagePort;
|
|
243
|
-
/**
|
|
244
|
-
* Create an endpoint for a worker or message port
|
|
245
|
-
*/
|
|
246
200
|
export declare function createEndpoint(target: MessageTarget, options?: EndpointOptions): Endpoint;
|
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
// @bun
|
|
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};
|
|
2
|
+
var z;((J)=>{J[J.Call=0]="Call";J[J.Result=1]="Result";J[J.Error=2]="Error";J[J.Callback=3]="Callback";J[J.CallbackRelease=4]="CallbackRelease";J[J.EndpointRelease=5]="EndpointRelease"})(z||={});var y=new Set(["message","name","stack","cause"]);function D(q){let G={e:q.message,n:q.name,s:q.stack},Q={};for(let Z of Object.keys(q))if(!y.has(Z))Q[Z]=q[Z];if(Object.keys(Q).length>0)G.d=Q;if(q.cause instanceof Error)G.c=D(q.cause);else if(q.cause!==void 0)G.c={e:String(q.cause),d:{cause:q.cause}};return G}function S(q){let G=q.c?{cause:S(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 X=Symbol("rpc:path"),V=Symbol("rpc:proxy-endpoint"),C=Symbol.for("rpc:remote-proxy"),_=Symbol.for("rpc:endpoint"),N=Symbol.for("rpc:release"),A=Symbol.for("rpc:callback-release"),b={get(q,G){if(G===C)return!0;if(G===_)return q[V];if(G===N)return()=>{q[V].release()};if(G==="then"){if(q[X].length===0)return;let Q=q[V],Z=q[X],$=Q.call(Z,[]);return $.then.bind($)}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[V],$=[...q[X],G];return w(Q,$)}},apply(q,G,Q){let Z=q[V],$=q[X];return Z.call($,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===C||G===_||G===N}};function w(q,G){let Q=function(){};return Q[V]=q,Q[X]=G,new Proxy(Q,b)}function h(q){return w(q,[])}function T(q){if(q===null||q===void 0)return!1;try{return q[C]===!0}catch{return!1}}function u(q){if(T(q))return q[_]}function c(q){if(T(q))q[N]()}function O(q){return q!==null&&typeof q==="object"&&typeof q.__rpc_type==="string"}async function x(q,G){for(let Q of G)if(Q.canHandle(q)){let{transferables:Z=[],payload:$}=await Q.encode(q);return{transferables:Z,value:{__rpc_type:Q.type,...$}}}return{value:q,transferables:[]}}async function f(q,G){if(!O(q))return q;let{__rpc_type:Q,...Z}=q;for(let $ of G)if($.type===Q)return $.decode(Z);return q}var E=30000;class Y{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??Y.generateRandomId(),this.options={id:this.id,timeout:G.timeout??E,onError:G.onError??console.error,debug:G.debug??!1,onRelease:G.onRelease??(()=>{}),transformers:G.transformers??[],transformDepth:G.transformDepth??1},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",z[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:$}=q;try{let{method:K,thisArg:J}=this.resolveMethod(Q);if(typeof K!=="function")throw Error(`Method not found: ${Q.join(".")}`);let F=await Promise.all(Z.map(async(L,j)=>{let I=$?.[j];if(I)return this.createRemoteCallback(I);return this.decodeValue(L)})),U=await K.apply(J,F),{rawValue:W,callbackMap:B,transferables:H}=await this.processResult(U);this.send({t:1,id:G,v:W,...Object.keys(B).length>0&&{cm:B}},H)}catch(K){let J=K instanceof Error?K:Error(String(K));this.send({t:2,id:G,...D(J)})}}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 $=q[Z];if(G===null||G===void 0)throw Error(`Cannot access property '${$}' of ${G}`);if($===void 0)throw Error(`Invalid path at index ${Z}`);if(Q=G,G=G[$],Z>=(this.exposeOptions.maxDepth??10))throw Error("Maximum nesting depth exceeded")}return{target:this.exposedApi,method:G,thisArg:Q}}resolveResult(q,G){return G&&Object.keys(G).length>0?this.reconstructResult(q,G):this.decodeValue(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);this.resolveResult(q.v,q.cm).then(G.resolve,G.reject)}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(S(q))}async handleCallback(q){let{id:G,c:Q,a:Z,cb:$}=q,K=this.callbacks.get(Q);if(!K){this.send({t:2,id:G,e:`Callback not found: ${Q}`});return}try{let J=await Promise.all(Z.map(async(H,L)=>{let j=$?.[L];if(j)return this.createRemoteCallback(j);return this.decodeValue(H)})),F=await K.fn(...J);if(K.remaining>0){if(K.remaining--,K.remaining===0)this.callbacks.delete(Q)}let{rawValue:U,callbackMap:W,transferables:B}=await this.processResult(F);this.send({t:1,id:G,v:U,...Object.keys(W).length>0&&{cm:W}},B)}catch(J){let F=J instanceof Error?J:Error(String(J));this.send({t:2,id:G,...D(F)})}}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)=>this.invokeCallback(q,Q),this.remoteCallbacks.set(q,G),G}invokeCallback(q,G){let Q=this.generateCallId();return new Promise((Z,$)=>{let K=setTimeout(()=>{this.pendingCalls.delete(Q),$(Error(`Callback invocation timed out: ${q}`))},this.options.timeout);this.pendingCalls.set(Q,{resolve:Z,reject:$,timer:K});let J=({rawArgs:U,callbackMap:W,transferables:B})=>{if(!this.released)this.send({t:3,id:Q,c:q,a:U,...Object.keys(W).length>0&&{cb:W}},B)},F=(U)=>{let W=this.pendingCalls.get(Q);if(W)this.pendingCalls.delete(Q),clearTimeout(W.timer),W.reject(U instanceof Error?U:Error(String(U)))};if(this.options.transformers.length===0)J(this.processArgsSync(G));else this.processArgs(G).then(J,F)})}async encodeValue(q,G=this.options.transformDepth){let{value:Q,transferables:Z}=await x(q,this.options.transformers);if(Q!==q)return{encoded:Q,transferables:Z};if(G>0&&q!==null&&typeof q==="object"&&Object.getPrototypeOf(q)===Object.prototype){let $={},K=[];for(let J in q){let F=q[J],{encoded:U,transferables:W}=await this.encodeValue(F,G-1);$[J]=U,K.push(...W)}return{encoded:$,transferables:K}}return{encoded:q,transferables:[]}}async decodeValue(q,G=this.options.transformDepth){if(O(q))return f(q,this.options.transformers);if(G>0&&q!==null&&typeof q==="object"&&Object.getPrototypeOf(q)===Object.prototype){let Q={...q};for(let Z of Object.keys(Q))Q[Z]=await this.decodeValue(Q[Z],G-1);return Q}return q}processArgsSync(q){let G=[],Q={},Z=[];for(let $=0;$<q.length;$++){let K=q[$];if(R(K))Q[$]=this.registerCallback(K),G.push(null);else if(M(K))Z.push(K),G.push(K);else G.push(K)}return{rawArgs:G,callbackMap:Q,transferables:Z}}async processArgs(q){let{rawArgs:G,callbackMap:Q,transferables:Z}=this.processArgsSync(q);for(let $=0;$<G.length;$++){if(G[$]===null&&Q[$]!==void 0)continue;let{encoded:K,transferables:J}=await this.encodeValue(G[$]);G[$]=K,Z.push(...J)}return{rawArgs:G,callbackMap:Q,transferables:Z}}async processResult(q){let G=[];if(R(q))return{rawValue:null,callbackMap:{"":this.registerCallback(q)},transferables:G};if(q!==null&&typeof q==="object"&&!Array.isArray(q)&&!M(q)&&!(q instanceof ArrayBuffer)){let{value:$,transferables:K}=await x(q,this.options.transformers);if($!==q)return G.push(...K),{rawValue:$,callbackMap:{},transferables:G};let J={},F={};for(let W in q){let B=q[W];if(R(B))J[W]=this.registerCallback(B);else{let{encoded:H,transferables:L}=await this.encodeValue(B);F[W]=H,G.push(...L)}}let U=q[A];if(typeof U==="function")this.releaseHandlers.add(U);if(Object.keys(J).length>0)return{rawValue:F,callbackMap:J,transferables:G};return{rawValue:F,callbackMap:{},transferables:G}}let{encoded:Q,transferables:Z}=await this.encodeValue(q);return G.push(...Z),{rawValue:Q,callbackMap:{},transferables:G}}async reconstructResult(q,G){if(""in G)return this.createRemoteCallback(G[""]);let Q=q&&typeof q==="object"?{...q}:{};for(let Z of Object.keys(Q))Q[Z]=await this.decodeValue(Q[Z]);for(let Z in G){let $=G[Z];if($)Q[Z]=this.createRemoteCallback($)}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 Q=this.generateCallId();return new Promise((Z,$)=>{let K=setTimeout(()=>{this.pendingCalls.delete(Q),$(Error(`Call timed out: ${q.join(".")}`))},this.options.timeout);this.pendingCalls.set(Q,{resolve:Z,reject:$,timer:K});let J=({rawArgs:U,callbackMap:W,transferables:B})=>{if(!this.released)this.send(this.buildCallMessage(Q,q,U,W),B)},F=(U)=>{let W=this.pendingCalls.get(Q);if(W)this.pendingCalls.delete(Q),clearTimeout(W.timer),W.reject(U instanceof Error?U:Error(String(U)))};if(this.options.transformers.length===0)J(this.processArgsSync(G));else this.processArgs(G).then(J,F)})}send(q,G=[]){this.debug("sending",z[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=E}=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(J)},K=setTimeout(()=>{$({success:!1,timeout:!0})},G);for(let[,J]of this.pendingCalls){let{resolve:F,reject:U}=J;J.resolve=(W)=>{if(F(W),!this.hasPendingCalls())$({success:!0,timeout:!1})},J.reject=(W)=>{if(U(W),!this.hasPendingCalls())$({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 R(q){return typeof q==="function"}function M(q){return typeof MessagePort<"u"&&q instanceof MessagePort}function P(q,G){return new Y(q,G)}class k extends Y{pendingBroadcastCalls=new Map;get broadcastTarget(){return this.target}call(q,G){if(this.released)return Promise.reject(Error("Endpoint has been released"));let Q=this.generateCallId(),Z=($,K,J)=>{let F=this.buildCallMessage(Q,q,$,K);this.debug("sending broadcast",z[F.t],F),Promise.resolve(this.broadcastTarget.postMessage(F,J)).then((U)=>this.handleSubscriberCount(Q,U))};return new Promise(($,K)=>{let J={resolve:$,reject:K,timer:setTimeout(()=>this.handleTimeout(Q,q),this.options.timeout),expectedCount:1/0,results:[],errors:[]};if(this.pendingBroadcastCalls.set(Q,J),this.options.transformers.length===0){let{rawArgs:F,callbackMap:U,transferables:W}=this.processArgsSync(G);Z(F,U,W)}else this.processArgs(G).then(({rawArgs:F,callbackMap:U,transferables:W})=>{if(this.released)return;Z(F,U,W)},(F)=>{let U=this.pendingBroadcastCalls.get(Q);if(U)this.pendingBroadcastCalls.delete(Q),clearTimeout(U.timer),U.reject(F instanceof Error?F:Error(String(F)))})})}handleTimeout(q,G){let Q=this.pendingBroadcastCalls.get(q);if(!Q)return;this.pendingBroadcastCalls.delete(q);let Z=Q.results.length+Q.errors.length,$=Q.expectedCount===1/0?0:Q.expectedCount-Z;if($>0){let K=Error(`Broadcast timed out: ${G.join(".")}`);K.name="TimeoutError";for(let J=0;J<$;J++)Q.errors.push(K)}if(this.debug(`Broadcast timed out with ${Q.results.length} results, ${Q.errors.length} errors (${$} 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}this.resolveResult(q.v,q.cm).then((Q)=>{G.results.push(Q),this.debug(`Broadcast received ${G.results.length}/${G.expectedCount} responses`),this.checkCompletion(q.id,G)},(Q)=>{G.errors.push(Q instanceof Error?Q:Error(String(Q))),this.checkCompletion(q.id,G)})}handleError(q){let G=this.pendingBroadcastCalls.get(q.id);if(!G){super.handleError(q);return}G.errors.push(S(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 a(q,G){return new k(q,G)}function Zq(q,G){let Z=P(globalThis,G);return Z.expose(q),Z}function $q(q,G){let Q=P(q,G);return h(Q)}export{h as wrap,D as serializeError,$q as remote,c as releaseProxy,T as isProxy,M as isMessagePort,R as isFunction,O as isEncodedValue,u as getEndpoint,Zq as expose,S as deserializeError,P as createEndpoint,a as createBroadcastEndpoint,x as applyEncoders,f as applyDecoders,C as REMOTE_PROXY,N as RELEASE,V as PROXY_ENDPOINT,X as PATH,z as MessageType,Y as Endpoint,_ as ENDPOINT,A as CALLBACK_RELEASE,k as BroadcastEndpoint};
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Marker interface for types that cross the worker boundary via a transformer.
|
|
3
|
+
* A return type that extends (or intersects) `Transferred` is passed through
|
|
4
|
+
* `RemoteReturnValue` unchanged — the transformer handles encoding/decoding at
|
|
5
|
+
* runtime, so the remote type stays identical to the local type.
|
|
6
|
+
*
|
|
7
|
+
* The built-in `Response` and `Request` globals extend this automatically.
|
|
8
|
+
* For custom transformer-managed types, intersect at the return site:
|
|
9
|
+
*
|
|
10
|
+
* ```typescript
|
|
11
|
+
* interface WorkerApi {
|
|
12
|
+
* build(): MyClass & Transferred;
|
|
13
|
+
* }
|
|
14
|
+
* ```
|
|
15
|
+
*/
|
|
16
|
+
export interface Transferred {
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Wire envelope written by the library before sending and read after receiving.
|
|
20
|
+
* `__rpc_type` is injected by `applyEncoders` and stripped by `applyDecoders` —
|
|
21
|
+
* transformer implementations never touch this field.
|
|
22
|
+
*/
|
|
23
|
+
export interface EncodedValue {
|
|
24
|
+
__rpc_type: string;
|
|
25
|
+
[key: string]: unknown;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Return value of `encode`: the plain data payload plus optional transferables.
|
|
29
|
+
*
|
|
30
|
+
* @template P - Shape of the payload (default: `Record<string, unknown>`).
|
|
31
|
+
*/
|
|
32
|
+
export interface EncodeResult<P extends Record<string, unknown> = Record<string, unknown>> {
|
|
33
|
+
/** Structured-clone-safe payload. */
|
|
34
|
+
payload: P;
|
|
35
|
+
/** Transferables to zero-copy transfer via postMessage. */
|
|
36
|
+
transferables?: Transferable[];
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Encodes a non-cloneable value into a structured-clone-safe payload before
|
|
40
|
+
* sending, and decodes it back on the receiving side.
|
|
41
|
+
*
|
|
42
|
+
* The library injects/strips `__rpc_type` automatically, so `encode` returns
|
|
43
|
+
* plain data and `decode` receives plain data.
|
|
44
|
+
*
|
|
45
|
+
* @template T - The type this transformer handles.
|
|
46
|
+
* @template P - Shape of the encoded payload. Typing this removes casts in `decode`.
|
|
47
|
+
*
|
|
48
|
+
* @example
|
|
49
|
+
* ```typescript
|
|
50
|
+
* interface MyPayload extends Record<string, unknown> { data: string }
|
|
51
|
+
*
|
|
52
|
+
* const myTransformer: Transformer<MyClass, MyPayload> = {
|
|
53
|
+
* type: 'MyClass',
|
|
54
|
+
* canHandle: (value): value is MyClass => value instanceof MyClass,
|
|
55
|
+
* async encode(value) {
|
|
56
|
+
* return { payload: { data: value.serialize() } };
|
|
57
|
+
* },
|
|
58
|
+
* decode(payload) {
|
|
59
|
+
* return MyClass.deserialize(payload.data);
|
|
60
|
+
* },
|
|
61
|
+
* };
|
|
62
|
+
* ```
|
|
63
|
+
*/
|
|
64
|
+
export interface Transformer<T = unknown, P extends Record<string, unknown> = Record<string, unknown>> {
|
|
65
|
+
/**
|
|
66
|
+
* Unique wire tag. The library sets `__rpc_type` to this value when encoding
|
|
67
|
+
* and uses it to route incoming envelopes to the correct decoder.
|
|
68
|
+
*/
|
|
69
|
+
type: string;
|
|
70
|
+
/** Return true if this transformer can handle the given value. */
|
|
71
|
+
canHandle(value: unknown): value is T;
|
|
72
|
+
/**
|
|
73
|
+
* Encode a value into a structured-clone-safe payload.
|
|
74
|
+
*/
|
|
75
|
+
encode(value: T): Promise<EncodeResult<P>>;
|
|
76
|
+
/**
|
|
77
|
+
* Decode a payload back into the original type.
|
|
78
|
+
*/
|
|
79
|
+
decode(payload: P): T | Promise<T>;
|
|
80
|
+
}
|
|
81
|
+
/** Returns true if `value` is a transformer-encoded wire envelope. */
|
|
82
|
+
export declare function isEncodedValue(value: unknown): value is EncodedValue;
|
|
83
|
+
/**
|
|
84
|
+
* Run the first matching transformer's encoder on `value`.
|
|
85
|
+
* Injects `__rpc_type` automatically. Returns the value unchanged if no transformer matches.
|
|
86
|
+
*/
|
|
87
|
+
export declare function applyEncoders(value: unknown, transformers: Transformer[]): Promise<{
|
|
88
|
+
value: unknown;
|
|
89
|
+
transferables: Transferable[];
|
|
90
|
+
}>;
|
|
91
|
+
/**
|
|
92
|
+
* Run the first matching transformer's decoder on a wire envelope.
|
|
93
|
+
* Strips `__rpc_type` before calling `decode`. Returns the value unchanged
|
|
94
|
+
* if it is not an encoded envelope or no transformer matches.
|
|
95
|
+
*/
|
|
96
|
+
export declare function applyDecoders(value: unknown, transformers: Transformer[]): Promise<unknown>;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Built-in transformers for non-cloneable Web API types.
|
|
3
|
+
*
|
|
4
|
+
* @example
|
|
5
|
+
* ```typescript
|
|
6
|
+
* import { createEndpoint } from '@xentobias/worker-rpc';
|
|
7
|
+
* import { responseTransformer, requestTransformer } from '@xentobias/worker-rpc/transformers';
|
|
8
|
+
*
|
|
9
|
+
* const endpoint = createEndpoint(worker, {
|
|
10
|
+
* transformers: [responseTransformer, requestTransformer],
|
|
11
|
+
* });
|
|
12
|
+
* ```
|
|
13
|
+
*/
|
|
14
|
+
export { responseTransformer } from './response';
|
|
15
|
+
export { requestTransformer } from './request';
|
|
16
|
+
export { headersToTuples } from './utils';
|
|
17
|
+
export type { ResponsePayload } from './response';
|
|
18
|
+
export type { RequestPayload } from './request';
|
|
19
|
+
export type { Transformer, EncodedValue, EncodeResult, Transferred } from '../transformer';
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
// @bun
|
|
2
|
+
function T(g){let x=[];return g.forEach((m,H)=>x.push([H,m])),x}var j={type:"Response",canHandle(g){return typeof Response<"u"&&g instanceof Response},async encode(g){let x=g.bodyUsed?null:await g.arrayBuffer();return{transferables:x!==null?[x]:[],payload:{body:x,status:g.status,statusText:g.statusText,headers:T(g.headers)}}},decode(g){return new Response(g.body,{status:g.status,statusText:g.statusText,headers:new Headers(g.headers)})}};var E={type:"Request",canHandle(g){return typeof Request<"u"&&g instanceof Request},async encode(g){let x=g.bodyUsed?null:await g.arrayBuffer();return{transferables:x!==null?[x]:[],payload:{url:g.url,method:g.method,headers:T(g.headers),body:x,mode:g.mode,credentials:g.credentials,cache:g.cache,redirect:g.redirect,referrer:g.referrer,integrity:g.integrity}}},decode(g){return new Request(g.url,{method:g.method,headers:new Headers(g.headers),body:g.body,mode:g.mode,credentials:g.credentials,cache:g.cache,redirect:g.redirect,referrer:g.referrer,integrity:g.integrity})}};export{j as responseTransformer,E as requestTransformer,T as headersToTuples};
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { Transformer, Transferred } from '../transformer';
|
|
2
|
+
declare global {
|
|
3
|
+
interface Request extends Transferred {
|
|
4
|
+
}
|
|
5
|
+
}
|
|
6
|
+
/** Wire-safe payload for a `Request`. */
|
|
7
|
+
export interface RequestPayload extends Record<string, unknown> {
|
|
8
|
+
url: string;
|
|
9
|
+
method: string;
|
|
10
|
+
headers: [string, string][];
|
|
11
|
+
body: ArrayBuffer | null;
|
|
12
|
+
mode: RequestMode;
|
|
13
|
+
credentials: RequestCredentials;
|
|
14
|
+
cache: RequestCache;
|
|
15
|
+
redirect: RequestRedirect;
|
|
16
|
+
referrer: string;
|
|
17
|
+
integrity: string;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Built-in transformer for the Web `Request` API.
|
|
21
|
+
* Buffers the body into an `ArrayBuffer` and zero-copy transfers it.
|
|
22
|
+
*
|
|
23
|
+
* Note: if `request.bodyUsed` is `true` when encoding, the decoded
|
|
24
|
+
* `Request` will have a `null` body. Read the body only after the RPC call returns.
|
|
25
|
+
*
|
|
26
|
+
* @example
|
|
27
|
+
* ```typescript
|
|
28
|
+
* import { createEndpoint } from '@xentobias/worker-rpc';
|
|
29
|
+
* import { requestTransformer } from '@xentobias/worker-rpc/transformers';
|
|
30
|
+
*
|
|
31
|
+
* const endpoint = createEndpoint(worker, {
|
|
32
|
+
* transformers: [requestTransformer],
|
|
33
|
+
* });
|
|
34
|
+
* ```
|
|
35
|
+
*/
|
|
36
|
+
export declare const requestTransformer: Transformer<Request, RequestPayload>;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { Transformer, Transferred } from '../transformer';
|
|
2
|
+
declare global {
|
|
3
|
+
interface Response extends Transferred {
|
|
4
|
+
}
|
|
5
|
+
}
|
|
6
|
+
/** Wire-safe payload for a `Response`. */
|
|
7
|
+
export interface ResponsePayload extends Record<string, unknown> {
|
|
8
|
+
body: ArrayBuffer | null;
|
|
9
|
+
status: number;
|
|
10
|
+
statusText: string;
|
|
11
|
+
headers: [string, string][];
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Built-in transformer for the Web `Response` API.
|
|
15
|
+
* Buffers the body into an `ArrayBuffer` and zero-copy transfers it.
|
|
16
|
+
*
|
|
17
|
+
* Note: if `response.bodyUsed` is `true` when encoding, the decoded
|
|
18
|
+
* `Response` will have a `null` body. Read the body only after the RPC call returns.
|
|
19
|
+
*
|
|
20
|
+
* @example
|
|
21
|
+
* ```typescript
|
|
22
|
+
* import { createEndpoint } from '@xentobias/worker-rpc';
|
|
23
|
+
* import { responseTransformer } from '@xentobias/worker-rpc/transformers';
|
|
24
|
+
*
|
|
25
|
+
* const endpoint = createEndpoint(worker, {
|
|
26
|
+
* transformers: [responseTransformer],
|
|
27
|
+
* });
|
|
28
|
+
* ```
|
|
29
|
+
*/
|
|
30
|
+
export declare const responseTransformer: Transformer<Response, ResponsePayload>;
|
package/dist/types.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { CallbackId, CallId } from './endpoint';
|
|
2
|
+
import type { Transferred } from './transformer';
|
|
2
3
|
/** Path to a nested method (e.g., ['db', 'users', 'find']) */
|
|
3
4
|
export type MethodPath = string[];
|
|
4
5
|
/** Message types for the RPC protocol */
|
|
@@ -83,10 +84,10 @@ export type RpcMessage = CallMessage | ResultMessage | ErrorMessage | CallbackMe
|
|
|
83
84
|
export type Unpromise<T> = T extends Promise<infer U> ? U : T;
|
|
84
85
|
/**
|
|
85
86
|
* Transform first-level functions in a return value to async versions.
|
|
86
|
-
*
|
|
87
|
-
*
|
|
87
|
+
* Types that extend `Transferred` (e.g. `Response`, `Request`) are passed
|
|
88
|
+
* through unchanged — their transformer handles encoding/decoding at runtime.
|
|
88
89
|
*/
|
|
89
|
-
export type RemoteReturnValue<T> = T extends (...args: infer A) => infer R ? (...args: A) => Promise<Unpromise<R>> : T extends object ? {
|
|
90
|
+
export type RemoteReturnValue<T> = T extends Transferred ? T : T extends (...args: infer A) => infer R ? (...args: A) => Promise<Unpromise<R>> : T extends object ? {
|
|
90
91
|
[K in keyof T]: T[K] extends (...args: infer A) => infer R ? (...args: A) => Promise<Unpromise<R>> : T[K];
|
|
91
92
|
} : T;
|
|
92
93
|
/** Helper to convert a single property to its remote version */
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@xentobias/worker-rpc",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.16",
|
|
4
4
|
"description": "High-performance, type-safe RPC for Workers",
|
|
5
5
|
"module": "src/index.ts",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -13,10 +13,14 @@
|
|
|
13
13
|
".": {
|
|
14
14
|
"types": "./dist/index.d.ts",
|
|
15
15
|
"import": "./dist/index.js"
|
|
16
|
+
},
|
|
17
|
+
"./transformers": {
|
|
18
|
+
"types": "./dist/transformers/index.d.ts",
|
|
19
|
+
"import": "./dist/transformers/index.js"
|
|
16
20
|
}
|
|
17
21
|
},
|
|
18
22
|
"scripts": {
|
|
19
|
-
"build": "bun build ./src/index.ts --minify --outdir ./dist --target bun && tsc --declaration",
|
|
23
|
+
"build": "bun build ./src/index.ts --minify --outdir ./dist --target bun && bun build ./src/transformers/index.ts --minify --outdir ./dist/transformers --target bun && tsc --declaration",
|
|
20
24
|
"typecheck": "tsc --noEmit",
|
|
21
25
|
"test": "bun test",
|
|
22
26
|
"prepublishOnly": "bun run build",
|