@xentobias/worker-rpc 1.0.1

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.
@@ -0,0 +1,55 @@
1
+ /**
2
+ * Worker RPC - Channel Module
3
+ * Direct worker-to-worker communication without routing through main thread
4
+ */
5
+ import { Endpoint } from "./endpoint";
6
+ import { type MessageTarget, type EndpointOptions, type RemoteObject } from "./types";
7
+ /** Unique identifier for workers */
8
+ export type WorkerId = string;
9
+ /** Worker registry for managing direct channels */
10
+ export interface WorkerRegistry {
11
+ /** Register a worker with an ID */
12
+ register(id: WorkerId, worker: MessageTarget): void;
13
+ /** Unregister a worker */
14
+ unregister(id: WorkerId): void;
15
+ /** Get a direct channel to another worker */
16
+ getChannel<T extends object>(targetId: WorkerId): Promise<RemoteObject<T>>;
17
+ /** Release all channels */
18
+ releaseAll(): void;
19
+ }
20
+ /**
21
+ * Create a worker registry for the main thread
22
+ * This allows workers to establish direct communication channels
23
+ */
24
+ export declare function createRegistry(): WorkerRegistry;
25
+ /**
26
+ * Create a direct channel from within a worker to another worker
27
+ * This bypasses the main thread for all subsequent communication
28
+ */
29
+ export declare function createWorkerChannel<T extends object>(mainThreadEndpoint: Endpoint, targetWorkerId: WorkerId, options?: EndpointOptions): Promise<RemoteObject<T>>;
30
+ /**
31
+ * Accept a channel connection from another worker
32
+ * Call this in the worker that receives the channel request
33
+ */
34
+ export declare function acceptChannel<T extends object>(port: MessagePort, api: T, options?: EndpointOptions): RemoteObject<T>;
35
+ /**
36
+ * Set up a worker to handle incoming channel requests
37
+ */
38
+ export declare function setupChannelHandler(mainThread: MessageTarget, getApi: () => object, options?: EndpointOptions): void;
39
+ /**
40
+ * Peer-to-peer channel utilities
41
+ */
42
+ export declare class PeerChannel<T extends object> {
43
+ private endpoint;
44
+ private remote;
45
+ constructor(port: MessagePort, localApi: object, options?: EndpointOptions);
46
+ /** Get the remote API proxy */
47
+ getRemote(): RemoteObject<T>;
48
+ /** Release this channel */
49
+ release(): void;
50
+ }
51
+ /**
52
+ * Create a pair of connected peer channels for testing or in-process communication
53
+ */
54
+ export declare function createPeerPair<A extends object, B extends object>(apiA: A, apiB: B, options?: EndpointOptions): [RemoteObject<B>, RemoteObject<A>];
55
+ //# sourceMappingURL=channel.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"channel.d.ts","sourceRoot":"","sources":["../src/channel.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,QAAQ,EAAkB,MAAM,YAAY,CAAC;AAEtD,OAAO,EAEL,KAAK,aAAa,EAClB,KAAK,eAAe,EACpB,KAAK,YAAY,EAClB,MAAM,SAAS,CAAC;AAEjB,oCAAoC;AACpC,MAAM,MAAM,QAAQ,GAAG,MAAM,CAAC;AAE9B,mDAAmD;AACnD,MAAM,WAAW,cAAc;IAC7B,mCAAmC;IACnC,QAAQ,CAAC,EAAE,EAAE,QAAQ,EAAE,MAAM,EAAE,aAAa,GAAG,IAAI,CAAC;IACpD,0BAA0B;IAC1B,UAAU,CAAC,EAAE,EAAE,QAAQ,GAAG,IAAI,CAAC;IAC/B,6CAA6C;IAC7C,UAAU,CAAC,CAAC,SAAS,MAAM,EAAE,QAAQ,EAAE,QAAQ,GAAG,OAAO,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC,CAAC;IAC3E,2BAA2B;IAC3B,UAAU,IAAI,IAAI,CAAC;CACpB;AAQD;;;GAGG;AACH,wBAAgB,cAAc,IAAI,cAAc,CAyF/C;AAED;;;GAGG;AACH,wBAAsB,mBAAmB,CAAC,CAAC,SAAS,MAAM,EACxD,kBAAkB,EAAE,QAAQ,EAC5B,cAAc,EAAE,QAAQ,EACxB,OAAO,CAAC,EAAE,eAAe,GACxB,OAAO,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC,CAkD1B;AAED;;;GAGG;AACH,wBAAgB,aAAa,CAAC,CAAC,SAAS,MAAM,EAC5C,IAAI,EAAE,WAAW,EACjB,GAAG,EAAE,CAAC,EACN,OAAO,CAAC,EAAE,eAAe,GACxB,YAAY,CAAC,CAAC,CAAC,CAKjB;AAED;;GAEG;AACH,wBAAgB,mBAAmB,CACjC,UAAU,EAAE,aAAa,EACzB,MAAM,EAAE,MAAM,MAAM,EACpB,OAAO,CAAC,EAAE,eAAe,GACxB,IAAI,CAoCN;AAED;;GAEG;AACH,qBAAa,WAAW,CAAC,CAAC,SAAS,MAAM;IACvC,OAAO,CAAC,QAAQ,CAAW;IAC3B,OAAO,CAAC,MAAM,CAAkB;gBAEpB,IAAI,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,eAAe;IAO1E,+BAA+B;IAC/B,SAAS,IAAI,YAAY,CAAC,CAAC,CAAC;IAI5B,2BAA2B;IAC3B,OAAO,IAAI,IAAI;CAGhB;AAED;;GAEG;AACH,wBAAgB,cAAc,CAAC,CAAC,SAAS,MAAM,EAAE,CAAC,SAAS,MAAM,EAC/D,IAAI,EAAE,CAAC,EACP,IAAI,EAAE,CAAC,EACP,OAAO,CAAC,EAAE,eAAe,GACxB,CAAC,YAAY,CAAC,CAAC,CAAC,EAAE,YAAY,CAAC,CAAC,CAAC,CAAC,CAapC"}
@@ -0,0 +1,103 @@
1
+ /**
2
+ * Worker RPC - Endpoint Module
3
+ * Core RPC endpoint handling message passing and method invocation
4
+ * Uses structured clone for data, custom handling only for callbacks
5
+ */
6
+ import { type MethodPath, type MessageTarget, type EndpointOptions, type ExposeOptions } from "./types";
7
+ /**
8
+ * RPC Endpoint - handles bidirectional communication with a worker
9
+ */
10
+ export declare class Endpoint {
11
+ private target;
12
+ private options;
13
+ private exposedApi;
14
+ private exposeOptions;
15
+ /** Pending calls awaiting response */
16
+ private pendingCalls;
17
+ /** Registered callbacks (functions passed as arguments) */
18
+ private callbacks;
19
+ /** Remote callbacks (proxies for functions on the other side) */
20
+ private remoteCallbacks;
21
+ /** Message handler bound to this instance */
22
+ private boundMessageHandler;
23
+ /** Whether this endpoint has been released */
24
+ private released;
25
+ constructor(target: MessageTarget, options?: EndpointOptions);
26
+ /**
27
+ * Attach the message listener to the target
28
+ */
29
+ private attachListener;
30
+ /**
31
+ * Detach the message listener from the target
32
+ */
33
+ private detachListener;
34
+ /**
35
+ * Log a debug message
36
+ */
37
+ private debug;
38
+ /**
39
+ * Expose an API object for remote invocation
40
+ */
41
+ expose(api: object, options?: ExposeOptions): void;
42
+ /**
43
+ * Handle incoming messages
44
+ */
45
+ private handleMessage;
46
+ /**
47
+ * Handle a method call request
48
+ */
49
+ private handleCall;
50
+ /**
51
+ * Resolve a method from the exposed API by path
52
+ */
53
+ private resolveMethod;
54
+ /**
55
+ * Handle a successful result
56
+ */
57
+ private handleResult;
58
+ /**
59
+ * Handle an error result
60
+ */
61
+ private handleError;
62
+ /**
63
+ * Handle a callback invocation
64
+ */
65
+ private handleCallback;
66
+ /**
67
+ * Handle release of callbacks
68
+ */
69
+ private handleRelease;
70
+ /**
71
+ * Register a local callback function
72
+ */
73
+ private registerCallback;
74
+ /**
75
+ * Create a proxy function for a remote callback
76
+ */
77
+ private createRemoteCallback;
78
+ /**
79
+ * Invoke a remote callback
80
+ */
81
+ private invokeCallback;
82
+ /**
83
+ * Call a remote method
84
+ */
85
+ call(path: MethodPath, args: unknown[]): Promise<unknown>;
86
+ /**
87
+ * Send a message to the target
88
+ */
89
+ private send;
90
+ /**
91
+ * Release this endpoint and clean up resources
92
+ */
93
+ release(): void;
94
+ /**
95
+ * Get the underlying message target
96
+ */
97
+ getTarget(): MessageTarget;
98
+ }
99
+ /**
100
+ * Create an endpoint for a worker or message port
101
+ */
102
+ export declare function createEndpoint(target: MessageTarget, options?: EndpointOptions): Endpoint;
103
+ //# sourceMappingURL=endpoint.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"endpoint.d.ts","sourceRoot":"","sources":["../src/endpoint.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAUL,KAAK,UAAU,EAGf,KAAK,aAAa,EAClB,KAAK,eAAe,EACpB,KAAK,aAAa,EAEnB,MAAM,SAAS,CAAC;AAejB;;GAEG;AACH,qBAAa,QAAQ;IACnB,OAAO,CAAC,MAAM,CAAgB;IAC9B,OAAO,CAAC,OAAO,CAA4B;IAC3C,OAAO,CAAC,UAAU,CAAuB;IACzC,OAAO,CAAC,aAAa,CAAqB;IAE1C,sCAAsC;IACtC,OAAO,CAAC,YAAY,CAAkC;IAEtD,2DAA2D;IAC3D,OAAO,CAAC,SAAS,CAA+C;IAEhE,iEAAiE;IACjE,OAAO,CAAC,eAAe,CAAmC;IAE1D,6CAA6C;IAC7C,OAAO,CAAC,mBAAmB,CAAgC;IAE3D,8CAA8C;IAC9C,OAAO,CAAC,QAAQ,CAAS;gBAEb,MAAM,EAAE,aAAa,EAAE,OAAO,GAAE,eAAoB;IAYhE;;OAEG;IACH,OAAO,CAAC,cAAc;IAQtB;;OAEG;IACH,OAAO,CAAC,cAAc;IAQtB;;OAEG;IACH,OAAO,CAAC,KAAK;IAMb;;OAEG;IACH,MAAM,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,GAAE,aAAkB,GAAG,IAAI;IAOtD;;OAEG;IACH,OAAO,CAAC,aAAa;IA6BrB;;OAEG;YACW,UAAU;IAqDxB;;OAEG;IACH,OAAO,CAAC,aAAa;IAgCrB;;OAEG;IACH,OAAO,CAAC,YAAY;IAoBpB;;OAEG;IACH,OAAO,CAAC,WAAW;IAuBnB;;OAEG;YACW,cAAc;IA8D5B;;OAEG;IACH,OAAO,CAAC,aAAa;IAMrB;;OAEG;IACH,OAAO,CAAC,gBAAgB;IAMxB;;OAEG;IACH,OAAO,CAAC,oBAAoB;IAkB5B;;OAEG;YACW,cAAc;IA6C5B;;OAEG;IACG,IAAI,CAAC,IAAI,EAAE,UAAU,EAAE,IAAI,EAAE,OAAO,EAAE,GAAG,OAAO,CAAC,OAAO,CAAC;IAkD/D;;OAEG;IACH,OAAO,CAAC,IAAI;IAKZ;;OAEG;IACH,OAAO,IAAI,IAAI;IAkCf;;OAEG;IACH,SAAS,IAAI,aAAa;CAG3B;AAED;;GAEG;AACH,wBAAgB,cAAc,CAAC,MAAM,EAAE,aAAa,EAAE,OAAO,CAAC,EAAE,eAAe,GAAG,QAAQ,CAEzF"}
@@ -0,0 +1,132 @@
1
+ /**
2
+ * Worker RPC - High-performance worker communication for Bun.js
3
+ *
4
+ * A type-safe, ergonomic library for seamless RPC between Bun workers.
5
+ * Supports nested methods, callback functions, and direct worker-to-worker channels.
6
+ *
7
+ * @example
8
+ * ```typescript
9
+ * // worker.ts
10
+ * import { expose } from "worker-rpc";
11
+ *
12
+ * const api = {
13
+ * add: (a: number, b: number) => a + b,
14
+ * db: {
15
+ * users: {
16
+ * find: async (id: string) => ({ id, name: "John" }),
17
+ * },
18
+ * },
19
+ * processWithCallback: async (data: number[], cb: (progress: number) => void) => {
20
+ * for (let i = 0; i < data.length; i++) {
21
+ * await someWork(data[i]);
22
+ * cb((i + 1) / data.length);
23
+ * }
24
+ * return "done";
25
+ * },
26
+ * };
27
+ *
28
+ * expose(api);
29
+ *
30
+ * export type WorkerApi = typeof api;
31
+ *
32
+ * // main.ts
33
+ * import { wrap, createEndpoint } from "worker-rpc";
34
+ * import type { WorkerApi } from "./worker";
35
+ *
36
+ * const worker = new Worker("./worker.ts");
37
+ * const api = wrap<WorkerApi>(createEndpoint(worker));
38
+ *
39
+ * // Type-safe, async calls
40
+ * const sum = await api.add(1, 2);
41
+ * const user = await api.db.users.find("123");
42
+ *
43
+ * // Callbacks work seamlessly
44
+ * await api.processWithCallback([1, 2, 3], (progress) => {
45
+ * console.log(`Progress: ${progress * 100}%`);
46
+ * });
47
+ * ```
48
+ */
49
+ export { type CallId, type CallbackId, type MethodPath, MessageType, type RpcMessage, type EndpointOptions, type ExposeOptions, type RemoteObject, type RemoteFunction, type MessageTarget, type BunWorker, REMOTE_PROXY, ENDPOINT, RELEASE, CREATE_CHANNEL, } from "./types";
50
+ export { Endpoint, createEndpoint } from "./endpoint";
51
+ export { wrap, isProxy, getEndpoint, releaseProxy } from "./proxy";
52
+ export { type WorkerId, type WorkerRegistry, createRegistry, createWorkerChannel, acceptChannel, setupChannelHandler, PeerChannel, createPeerPair, } from "./channel";
53
+ export { serializeError, deserializeError, isFunction, isMessagePort, isTransferable, type SerializedError, } from "./serialize";
54
+ import { Endpoint } from "./endpoint";
55
+ import type { MessageTarget, EndpointOptions, RemoteObject } from "./types";
56
+ /**
57
+ * Expose an API object for remote invocation
58
+ * Call this in the worker to expose methods to the parent
59
+ *
60
+ * @param api - The API object to expose
61
+ * @param options - Optional configuration
62
+ *
63
+ * @example
64
+ * ```typescript
65
+ * const api = {
66
+ * greet: (name: string) => `Hello, ${name}!`,
67
+ * math: {
68
+ * add: (a: number, b: number) => a + b,
69
+ * },
70
+ * };
71
+ *
72
+ * expose(api);
73
+ * ```
74
+ */
75
+ export declare function expose(api: object, options?: EndpointOptions): Endpoint;
76
+ /**
77
+ * Create a wrapped remote API from a worker
78
+ * Convenience function that combines createEndpoint and wrap
79
+ *
80
+ * @param worker - The worker or message target
81
+ * @param options - Optional configuration
82
+ * @returns A type-safe proxy for the remote API
83
+ *
84
+ * @example
85
+ * ```typescript
86
+ * const worker = new Worker("./worker.ts");
87
+ * const api = remote<WorkerApi>(worker);
88
+ *
89
+ * const result = await api.someMethod();
90
+ * ```
91
+ */
92
+ export declare function remote<T extends object>(worker: MessageTarget, options?: EndpointOptions): RemoteObject<T>;
93
+ /**
94
+ * Transfer a value by reference (marks it for zero-copy transfer)
95
+ * Currently supports ArrayBuffer and MessagePort
96
+ *
97
+ * Note: With the optimized serialization, ArrayBuffers are passed through
98
+ * structured clone which handles transfer automatically.
99
+ *
100
+ * @param value - The value to transfer
101
+ * @returns The same value
102
+ *
103
+ * @example
104
+ * ```typescript
105
+ * const buffer = new ArrayBuffer(1024);
106
+ * await api.processBuffer(transfer(buffer));
107
+ * // buffer is now unusable in this context (transferred)
108
+ * ```
109
+ */
110
+ export declare function transfer<T extends ArrayBuffer | MessagePort>(value: T): T;
111
+ /**
112
+ * Create a proxy function that can be passed to workers
113
+ * Useful for creating callbacks with specific invocation limits
114
+ *
115
+ * @param fn - The function to wrap
116
+ * @param options - Options for the callback
117
+ * @returns The wrapped function
118
+ *
119
+ * @example
120
+ * ```typescript
121
+ * // Single-use callback
122
+ * await api.onComplete(callback(() => console.log("done"), { once: true }));
123
+ *
124
+ * // Limited invocations
125
+ * await api.onProgress(callback((p) => console.log(p), { times: 10 }));
126
+ * ```
127
+ */
128
+ export declare function callback<T extends (...args: any[]) => any>(fn: T, _options?: {
129
+ once?: boolean;
130
+ times?: number;
131
+ }): T;
132
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+CG;AAGH,OAAO,EACL,KAAK,MAAM,EACX,KAAK,UAAU,EACf,KAAK,UAAU,EACf,WAAW,EACX,KAAK,UAAU,EACf,KAAK,eAAe,EACpB,KAAK,aAAa,EAClB,KAAK,YAAY,EACjB,KAAK,cAAc,EACnB,KAAK,aAAa,EAClB,KAAK,SAAS,EACd,YAAY,EACZ,QAAQ,EACR,OAAO,EACP,cAAc,GACf,MAAM,SAAS,CAAC;AAGjB,OAAO,EAAE,QAAQ,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAGtD,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAGnE,OAAO,EACL,KAAK,QAAQ,EACb,KAAK,cAAc,EACnB,cAAc,EACd,mBAAmB,EACnB,aAAa,EACb,mBAAmB,EACnB,WAAW,EACX,cAAc,GACf,MAAM,WAAW,CAAC;AAGnB,OAAO,EACL,cAAc,EACd,gBAAgB,EAChB,UAAU,EACV,aAAa,EACb,cAAc,EACd,KAAK,eAAe,GACrB,MAAM,aAAa,CAAC;AAErB,OAAO,EAAE,QAAQ,EAAkB,MAAM,YAAY,CAAC;AAEtD,OAAO,KAAK,EAAE,aAAa,EAAE,eAAe,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAE5E;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,MAAM,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,eAAe,GAAG,QAAQ,CAMvE;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,MAAM,CAAC,CAAC,SAAS,MAAM,EACrC,MAAM,EAAE,aAAa,EACrB,OAAO,CAAC,EAAE,eAAe,GACxB,YAAY,CAAC,CAAC,CAAC,CAGjB;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,QAAQ,CAAC,CAAC,SAAS,WAAW,GAAG,WAAW,EAAE,KAAK,EAAE,CAAC,GAAG,CAAC,CAIzE;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,QAAQ,CAAC,CAAC,SAAS,CAAC,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,GAAG,EACxD,EAAE,EAAE,CAAC,EACL,QAAQ,CAAC,EAAE;IAAE,IAAI,CAAC,EAAE,OAAO,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,GAC5C,CAAC,CAIH"}
package/dist/index.js ADDED
@@ -0,0 +1,678 @@
1
+ // @bun
2
+ // src/types.ts
3
+ var MessageType;
4
+ ((MessageType2) => {
5
+ MessageType2[MessageType2["Call"] = 0] = "Call";
6
+ MessageType2[MessageType2["Result"] = 1] = "Result";
7
+ MessageType2[MessageType2["Error"] = 2] = "Error";
8
+ MessageType2[MessageType2["Callback"] = 3] = "Callback";
9
+ MessageType2[MessageType2["Release"] = 4] = "Release";
10
+ MessageType2[MessageType2["Channel"] = 5] = "Channel";
11
+ MessageType2[MessageType2["ChannelAck"] = 6] = "ChannelAck";
12
+ })(MessageType ||= {});
13
+ var REMOTE_PROXY = Symbol.for("worker-rpc:remote-proxy");
14
+ var ENDPOINT = Symbol.for("worker-rpc:endpoint");
15
+ var RELEASE = Symbol.for("worker-rpc:release");
16
+ var CREATE_CHANNEL = Symbol.for("worker-rpc:create-channel");
17
+ // src/serialize.ts
18
+ function serializeError(error) {
19
+ return {
20
+ e: error.message,
21
+ n: error.name,
22
+ s: error.stack
23
+ };
24
+ }
25
+ function deserializeError(data) {
26
+ const error = new Error(data.e);
27
+ error.name = data.n;
28
+ if (data.s) {
29
+ error.stack = data.s;
30
+ }
31
+ return error;
32
+ }
33
+ function isFunction(value) {
34
+ return typeof value === "function";
35
+ }
36
+ function isMessagePort(value) {
37
+ return typeof MessagePort !== "undefined" && value instanceof MessagePort;
38
+ }
39
+ function isTransferable(value) {
40
+ return value instanceof ArrayBuffer || typeof MessagePort !== "undefined" && value instanceof MessagePort;
41
+ }
42
+
43
+ // src/endpoint.ts
44
+ var DEFAULT_TIMEOUT = 30000;
45
+ var idCounter = 0;
46
+ function generateId() {
47
+ return `${++idCounter}:${Date.now().toString(36)}`;
48
+ }
49
+
50
+ class Endpoint {
51
+ target;
52
+ options;
53
+ exposedApi = null;
54
+ exposeOptions = {};
55
+ pendingCalls = new Map;
56
+ callbacks = new Map;
57
+ remoteCallbacks = new Map;
58
+ boundMessageHandler;
59
+ released = false;
60
+ constructor(target, options = {}) {
61
+ this.target = target;
62
+ this.options = {
63
+ timeout: options.timeout ?? DEFAULT_TIMEOUT,
64
+ onError: options.onError ?? console.error,
65
+ debug: options.debug ?? false
66
+ };
67
+ this.boundMessageHandler = this.handleMessage.bind(this);
68
+ this.attachListener();
69
+ }
70
+ attachListener() {
71
+ if (this.target.addEventListener) {
72
+ this.target.addEventListener("message", this.boundMessageHandler);
73
+ } else if (this.target.onmessage !== undefined) {
74
+ this.target.onmessage = this.boundMessageHandler;
75
+ }
76
+ }
77
+ detachListener() {
78
+ if (this.target.removeEventListener) {
79
+ this.target.removeEventListener("message", this.boundMessageHandler);
80
+ } else if (this.target.onmessage !== undefined) {
81
+ this.target.onmessage = null;
82
+ }
83
+ }
84
+ debug(...args) {
85
+ if (this.options.debug) {
86
+ console.log("[worker-rpc]", ...args);
87
+ }
88
+ }
89
+ expose(api, options = {}) {
90
+ this.exposedApi = api;
91
+ this.exposeOptions = {
92
+ maxDepth: options.maxDepth ?? 10
93
+ };
94
+ }
95
+ handleMessage(event) {
96
+ const message = event.data;
97
+ if (typeof message !== "object" || message === null || !("t" in message)) {
98
+ return;
99
+ }
100
+ this.debug("received", MessageType[message.t], message);
101
+ switch (message.t) {
102
+ case 0 /* Call */:
103
+ this.handleCall(message);
104
+ break;
105
+ case 1 /* Result */:
106
+ this.handleResult(message);
107
+ break;
108
+ case 2 /* Error */:
109
+ this.handleError(message);
110
+ break;
111
+ case 3 /* Callback */:
112
+ this.handleCallback(message);
113
+ break;
114
+ case 4 /* Release */:
115
+ this.handleRelease(message);
116
+ break;
117
+ }
118
+ }
119
+ async handleCall(message) {
120
+ const { id, p: path, a: args, c: callbackMap } = message;
121
+ try {
122
+ const { method, thisArg } = this.resolveMethod(path);
123
+ if (typeof method !== "function") {
124
+ throw new Error(`Method not found: ${path.join(".")}`);
125
+ }
126
+ const resolvedArgs = args.map((arg, index) => {
127
+ const callbackId = callbackMap?.[index];
128
+ if (callbackId) {
129
+ return this.createRemoteCallback(callbackId);
130
+ }
131
+ return arg;
132
+ });
133
+ const result = await method.apply(thisArg, resolvedArgs);
134
+ if (isFunction(result)) {
135
+ const callbackId = this.registerCallback(result);
136
+ this.send({
137
+ t: 1 /* Result */,
138
+ id,
139
+ v: null,
140
+ c: callbackId
141
+ });
142
+ } else {
143
+ this.send({
144
+ t: 1 /* Result */,
145
+ id,
146
+ v: result
147
+ });
148
+ }
149
+ } catch (error) {
150
+ const err = error instanceof Error ? error : new Error(String(error));
151
+ this.send({
152
+ t: 2 /* Error */,
153
+ id,
154
+ e: err.message,
155
+ n: err.name,
156
+ s: err.stack
157
+ });
158
+ }
159
+ }
160
+ resolveMethod(path) {
161
+ if (!this.exposedApi) {
162
+ throw new Error("No API exposed");
163
+ }
164
+ let current = this.exposedApi;
165
+ let parent = null;
166
+ for (let i = 0;i < path.length; i++) {
167
+ const key = path[i];
168
+ if (current === null || current === undefined) {
169
+ throw new Error(`Cannot access property '${key}' of ${current}`);
170
+ }
171
+ if (key === undefined) {
172
+ throw new Error(`Invalid path at index ${i}`);
173
+ }
174
+ parent = current;
175
+ current = current[key];
176
+ if (i >= (this.exposeOptions.maxDepth ?? 10)) {
177
+ throw new Error(`Maximum nesting depth exceeded`);
178
+ }
179
+ }
180
+ return { target: this.exposedApi, method: current, thisArg: parent };
181
+ }
182
+ handleResult(message) {
183
+ const pending = this.pendingCalls.get(message.id);
184
+ if (!pending) {
185
+ this.debug("Received result for unknown call:", message.id);
186
+ return;
187
+ }
188
+ this.pendingCalls.delete(message.id);
189
+ if (pending.timer) {
190
+ clearTimeout(pending.timer);
191
+ }
192
+ if (message.c) {
193
+ pending.resolve(this.createRemoteCallback(message.c));
194
+ } else {
195
+ pending.resolve(message.v);
196
+ }
197
+ }
198
+ handleError(message) {
199
+ const pending = this.pendingCalls.get(message.id);
200
+ if (!pending) {
201
+ this.debug("Received error for unknown call:", message.id);
202
+ return;
203
+ }
204
+ this.pendingCalls.delete(message.id);
205
+ if (pending.timer) {
206
+ clearTimeout(pending.timer);
207
+ }
208
+ const error = new Error(message.e);
209
+ if (message.n) {
210
+ error.name = message.n;
211
+ }
212
+ if (message.s) {
213
+ error.stack = message.s;
214
+ }
215
+ pending.reject(error);
216
+ }
217
+ async handleCallback(message) {
218
+ const { id, c: callbackId, a: args, cb: callbackMap } = message;
219
+ const registration = this.callbacks.get(callbackId);
220
+ if (!registration) {
221
+ this.send({
222
+ t: 2 /* Error */,
223
+ id,
224
+ e: `Callback not found: ${callbackId}`
225
+ });
226
+ return;
227
+ }
228
+ try {
229
+ const resolvedArgs = args.map((arg, index) => {
230
+ const cbId = callbackMap?.[index];
231
+ if (cbId) {
232
+ return this.createRemoteCallback(cbId);
233
+ }
234
+ return arg;
235
+ });
236
+ const result = await registration.fn(...resolvedArgs);
237
+ if (registration.remaining > 0) {
238
+ registration.remaining--;
239
+ if (registration.remaining === 0) {
240
+ this.callbacks.delete(callbackId);
241
+ }
242
+ }
243
+ if (isFunction(result)) {
244
+ const cbId = this.registerCallback(result);
245
+ this.send({
246
+ t: 1 /* Result */,
247
+ id,
248
+ v: null,
249
+ c: cbId
250
+ });
251
+ } else {
252
+ this.send({
253
+ t: 1 /* Result */,
254
+ id,
255
+ v: result
256
+ });
257
+ }
258
+ } catch (error) {
259
+ const err = error instanceof Error ? error : new Error(String(error));
260
+ this.send({
261
+ t: 2 /* Error */,
262
+ id,
263
+ e: err.message,
264
+ n: err.name,
265
+ s: err.stack
266
+ });
267
+ }
268
+ }
269
+ handleRelease(message) {
270
+ for (const callbackId of message.c) {
271
+ this.callbacks.delete(callbackId);
272
+ }
273
+ }
274
+ registerCallback(fn, remaining = -1) {
275
+ const id = `cb:${generateId()}`;
276
+ this.callbacks.set(id, { fn, remaining });
277
+ return id;
278
+ }
279
+ createRemoteCallback(callbackId) {
280
+ let proxy = this.remoteCallbacks.get(callbackId);
281
+ if (proxy) {
282
+ return proxy;
283
+ }
284
+ proxy = async (...args) => {
285
+ return this.invokeCallback(callbackId, args);
286
+ };
287
+ this.remoteCallbacks.set(callbackId, proxy);
288
+ return proxy;
289
+ }
290
+ async invokeCallback(callbackId, args) {
291
+ const id = generateId();
292
+ const rawArgs = [];
293
+ const callbackMap = {};
294
+ const transferables = [];
295
+ for (let i = 0;i < args.length; i++) {
296
+ const arg = args[i];
297
+ if (isFunction(arg)) {
298
+ callbackMap[i] = this.registerCallback(arg);
299
+ rawArgs.push(null);
300
+ } else if (isMessagePort(arg)) {
301
+ transferables.push(arg);
302
+ rawArgs.push(arg);
303
+ } else {
304
+ rawArgs.push(arg);
305
+ }
306
+ }
307
+ return new Promise((resolve, reject) => {
308
+ const timer = setTimeout(() => {
309
+ this.pendingCalls.delete(id);
310
+ reject(new Error(`Callback invocation timed out: ${callbackId}`));
311
+ }, this.options.timeout);
312
+ this.pendingCalls.set(id, { resolve, reject, timer });
313
+ const message = {
314
+ t: 3 /* Callback */,
315
+ id,
316
+ c: callbackId,
317
+ a: rawArgs,
318
+ ...Object.keys(callbackMap).length > 0 && { cb: callbackMap }
319
+ };
320
+ this.send(message, transferables);
321
+ });
322
+ }
323
+ async call(path, args) {
324
+ if (this.released) {
325
+ throw new Error("Endpoint has been released");
326
+ }
327
+ const id = generateId();
328
+ const rawArgs = [];
329
+ const callbackMap = {};
330
+ const transferables = [];
331
+ for (let i = 0;i < args.length; i++) {
332
+ const arg = args[i];
333
+ if (isFunction(arg)) {
334
+ callbackMap[i] = this.registerCallback(arg);
335
+ rawArgs.push(null);
336
+ } else if (isMessagePort(arg)) {
337
+ transferables.push(arg);
338
+ rawArgs.push(arg);
339
+ } else {
340
+ rawArgs.push(arg);
341
+ }
342
+ }
343
+ return new Promise((resolve, reject) => {
344
+ const timer = setTimeout(() => {
345
+ this.pendingCalls.delete(id);
346
+ reject(new Error(`Call timed out: ${path.join(".")}`));
347
+ }, this.options.timeout);
348
+ this.pendingCalls.set(id, { resolve, reject, timer });
349
+ const message = {
350
+ t: 0 /* Call */,
351
+ id,
352
+ p: path,
353
+ a: rawArgs,
354
+ ...Object.keys(callbackMap).length > 0 && { c: callbackMap }
355
+ };
356
+ this.send(message, transferables);
357
+ });
358
+ }
359
+ send(message, transferables = []) {
360
+ this.debug("sending", MessageType[message.t], message);
361
+ this.target.postMessage(message, transferables);
362
+ }
363
+ release() {
364
+ if (this.released) {
365
+ return;
366
+ }
367
+ this.released = true;
368
+ for (const [, pending] of this.pendingCalls) {
369
+ if (pending.timer) {
370
+ clearTimeout(pending.timer);
371
+ }
372
+ pending.reject(new Error("Endpoint released"));
373
+ }
374
+ this.pendingCalls.clear();
375
+ if (this.remoteCallbacks.size > 0) {
376
+ const callbackIds = Array.from(this.remoteCallbacks.keys());
377
+ this.send({
378
+ t: 4 /* Release */,
379
+ id: generateId(),
380
+ c: callbackIds
381
+ });
382
+ this.remoteCallbacks.clear();
383
+ }
384
+ this.callbacks.clear();
385
+ this.detachListener();
386
+ }
387
+ getTarget() {
388
+ return this.target;
389
+ }
390
+ }
391
+ function createEndpoint(target, options) {
392
+ return new Endpoint(target, options);
393
+ }
394
+ // src/proxy.ts
395
+ var PATH = Symbol("worker-rpc:path");
396
+ var PROXY_ENDPOINT = Symbol("worker-rpc:proxy-endpoint");
397
+ var proxyHandler = {
398
+ get(target, prop, receiver) {
399
+ if (prop === REMOTE_PROXY) {
400
+ return true;
401
+ }
402
+ if (prop === ENDPOINT) {
403
+ return target[PROXY_ENDPOINT];
404
+ }
405
+ if (prop === RELEASE) {
406
+ return () => {
407
+ const endpoint = target[PROXY_ENDPOINT];
408
+ endpoint.release();
409
+ };
410
+ }
411
+ if (prop === "then") {
412
+ if (target[PATH].length === 0) {
413
+ return;
414
+ }
415
+ const endpoint = target[PROXY_ENDPOINT];
416
+ const path = target[PATH];
417
+ const promise = endpoint.call(path, []);
418
+ return promise.then.bind(promise);
419
+ }
420
+ if (prop === "toJSON") {
421
+ return;
422
+ }
423
+ if (prop === Symbol.toStringTag || prop === Symbol.iterator || prop === Symbol.asyncIterator || prop === "constructor" || prop === "prototype") {
424
+ return;
425
+ }
426
+ if (typeof prop === "string") {
427
+ const endpoint = target[PROXY_ENDPOINT];
428
+ const currentPath = target[PATH];
429
+ const newPath = [...currentPath, prop];
430
+ return createProxyInternal(endpoint, newPath);
431
+ }
432
+ return;
433
+ },
434
+ apply(target, _thisArg, args) {
435
+ const endpoint = target[PROXY_ENDPOINT];
436
+ const path = target[PATH];
437
+ return endpoint.call(path, args);
438
+ },
439
+ set() {
440
+ throw new Error("Cannot set properties on a remote proxy");
441
+ },
442
+ deleteProperty() {
443
+ throw new Error("Cannot delete properties on a remote proxy");
444
+ },
445
+ getPrototypeOf() {
446
+ return Function.prototype;
447
+ },
448
+ has(_target, prop) {
449
+ return prop === REMOTE_PROXY || prop === ENDPOINT || prop === RELEASE;
450
+ }
451
+ };
452
+ function createProxyInternal(endpoint, path) {
453
+ const target = function() {};
454
+ target[PROXY_ENDPOINT] = endpoint;
455
+ target[PATH] = path;
456
+ return new Proxy(target, proxyHandler);
457
+ }
458
+ function wrap(endpoint) {
459
+ return createProxyInternal(endpoint, []);
460
+ }
461
+ function isProxy(value) {
462
+ if (value === null || value === undefined) {
463
+ return false;
464
+ }
465
+ try {
466
+ return value[REMOTE_PROXY] === true;
467
+ } catch {
468
+ return false;
469
+ }
470
+ }
471
+ function getEndpoint(proxy) {
472
+ if (isProxy(proxy)) {
473
+ return proxy[ENDPOINT];
474
+ }
475
+ return;
476
+ }
477
+ function releaseProxy(proxy) {
478
+ if (isProxy(proxy)) {
479
+ proxy[RELEASE]();
480
+ }
481
+ }
482
+ // src/channel.ts
483
+ function createRegistry() {
484
+ const workers = new Map;
485
+ const channels = new Map;
486
+ const pendingChannels = new Map;
487
+ function handleWorkerMessage(workerId, event) {
488
+ const message = event.data;
489
+ if (typeof message !== "object" || message === null || message.t !== 5 /* Channel */) {
490
+ return;
491
+ }
492
+ const { target: targetId, port, id } = message;
493
+ const targetWorker = workers.get(targetId);
494
+ if (!targetWorker) {
495
+ const worker = workers.get(workerId);
496
+ if (worker) {
497
+ worker.postMessage({
498
+ t: 2 /* Error */,
499
+ id,
500
+ e: `Worker not found: ${targetId}`
501
+ });
502
+ }
503
+ return;
504
+ }
505
+ targetWorker.postMessage({
506
+ t: 5 /* Channel */,
507
+ id,
508
+ source: workerId,
509
+ port
510
+ }, [port]);
511
+ }
512
+ return {
513
+ register(id, worker) {
514
+ workers.set(id, worker);
515
+ const handler = (event) => handleWorkerMessage(id, event);
516
+ if (worker.addEventListener) {
517
+ worker.addEventListener("message", handler);
518
+ }
519
+ },
520
+ unregister(id) {
521
+ workers.delete(id);
522
+ for (const [key, state] of channels) {
523
+ if (key.includes(id)) {
524
+ state.endpoint.release();
525
+ state.port.close();
526
+ channels.delete(key);
527
+ }
528
+ }
529
+ },
530
+ async getChannel(targetId) {
531
+ throw new Error("getChannel should be called from within a worker using createWorkerChannel");
532
+ },
533
+ releaseAll() {
534
+ for (const [, state] of channels) {
535
+ state.endpoint.release();
536
+ state.port.close();
537
+ }
538
+ channels.clear();
539
+ workers.clear();
540
+ }
541
+ };
542
+ }
543
+ async function createWorkerChannel(mainThreadEndpoint, targetWorkerId, options) {
544
+ const channel = new MessageChannel;
545
+ return new Promise((resolve, reject) => {
546
+ const id = `channel:${Date.now()}:${Math.random().toString(36).slice(2)}`;
547
+ const originalHandler = mainThreadEndpoint.getTarget().onmessage;
548
+ const responseHandler = (event) => {
549
+ const message = event.data;
550
+ if (message?.id === id) {
551
+ if (mainThreadEndpoint.getTarget().onmessage === responseHandler) {
552
+ mainThreadEndpoint.getTarget().onmessage = originalHandler;
553
+ }
554
+ if (message.t === 2 /* Error */) {
555
+ reject(new Error(message.e));
556
+ return;
557
+ }
558
+ const endpoint = createEndpoint(channel.port1, options);
559
+ resolve(wrap(endpoint));
560
+ }
561
+ };
562
+ const target = mainThreadEndpoint.getTarget();
563
+ if (target.addEventListener) {
564
+ target.addEventListener("message", responseHandler);
565
+ } else {
566
+ target.onmessage = responseHandler;
567
+ }
568
+ target.postMessage({
569
+ t: 5 /* Channel */,
570
+ id,
571
+ target: targetWorkerId,
572
+ port: channel.port2
573
+ }, [channel.port2]);
574
+ });
575
+ }
576
+ function acceptChannel(port, api, options) {
577
+ const endpoint = createEndpoint(port, options);
578
+ endpoint.expose(api);
579
+ port.start();
580
+ return wrap(endpoint);
581
+ }
582
+ function setupChannelHandler(mainThread, getApi, options) {
583
+ const handler = (event) => {
584
+ const message = event.data;
585
+ if (typeof message !== "object" || message === null || message.t !== 5 /* Channel */) {
586
+ return;
587
+ }
588
+ const { port, source, id } = message;
589
+ if (!port) {
590
+ return;
591
+ }
592
+ const endpoint = createEndpoint(port, options);
593
+ endpoint.expose(getApi());
594
+ port.start();
595
+ mainThread.postMessage({
596
+ t: 6 /* ChannelAck */,
597
+ id,
598
+ source
599
+ });
600
+ };
601
+ if (mainThread.addEventListener) {
602
+ mainThread.addEventListener("message", handler);
603
+ } else {
604
+ mainThread.onmessage = handler;
605
+ }
606
+ }
607
+
608
+ class PeerChannel {
609
+ endpoint;
610
+ remote;
611
+ constructor(port, localApi, options) {
612
+ this.endpoint = createEndpoint(port, options);
613
+ this.endpoint.expose(localApi);
614
+ this.remote = wrap(this.endpoint);
615
+ port.start();
616
+ }
617
+ getRemote() {
618
+ return this.remote;
619
+ }
620
+ release() {
621
+ this.endpoint.release();
622
+ }
623
+ }
624
+ function createPeerPair(apiA, apiB, options) {
625
+ const channel = new MessageChannel;
626
+ const endpointA = createEndpoint(channel.port1, options);
627
+ endpointA.expose(apiA);
628
+ const endpointB = createEndpoint(channel.port2, options);
629
+ endpointB.expose(apiB);
630
+ channel.port1.start();
631
+ channel.port2.start();
632
+ return [wrap(endpointA), wrap(endpointB)];
633
+ }
634
+ // src/index.ts
635
+ function expose(api, options) {
636
+ const self = globalThis;
637
+ const endpoint = createEndpoint(self, options);
638
+ endpoint.expose(api);
639
+ return endpoint;
640
+ }
641
+ function remote(worker, options) {
642
+ const endpoint = createEndpoint(worker, options);
643
+ return wrap(endpoint);
644
+ }
645
+ function transfer(value) {
646
+ return value;
647
+ }
648
+ function callback(fn, _options) {
649
+ return fn;
650
+ }
651
+ export {
652
+ wrap,
653
+ transfer,
654
+ setupChannelHandler,
655
+ serializeError,
656
+ remote,
657
+ releaseProxy,
658
+ isTransferable,
659
+ isProxy,
660
+ isMessagePort,
661
+ isFunction,
662
+ getEndpoint,
663
+ expose,
664
+ deserializeError,
665
+ createWorkerChannel,
666
+ createRegistry,
667
+ createPeerPair,
668
+ createEndpoint,
669
+ callback,
670
+ acceptChannel,
671
+ REMOTE_PROXY,
672
+ RELEASE,
673
+ PeerChannel,
674
+ MessageType,
675
+ Endpoint,
676
+ ENDPOINT,
677
+ CREATE_CHANNEL
678
+ };
@@ -0,0 +1,45 @@
1
+ /**
2
+ * Worker RPC - Proxy Module
3
+ * Creates type-safe proxies for remote API access
4
+ */
5
+ import { Endpoint } from "./endpoint";
6
+ import { type RemoteObject } from "./types";
7
+ /**
8
+ * Wrap a worker/endpoint to create a type-safe remote API proxy
9
+ *
10
+ * @example
11
+ * ```typescript
12
+ * interface WorkerApi {
13
+ * add(a: number, b: number): number;
14
+ * db: {
15
+ * users: {
16
+ * find(id: string): Promise<User>;
17
+ * };
18
+ * };
19
+ * }
20
+ *
21
+ * const worker = new Worker("./worker.ts");
22
+ * const api = wrap<WorkerApi>(worker);
23
+ *
24
+ * // All calls are async and type-safe
25
+ * const sum = await api.add(1, 2);
26
+ * const user = await api.db.users.find("123");
27
+ * ```
28
+ */
29
+ export declare function wrap<T extends object>(endpoint: Endpoint): RemoteObject<T>;
30
+ export declare function wrap<T extends object>(endpoint: Endpoint, options?: {
31
+ timeout?: number;
32
+ }): RemoteObject<T>;
33
+ /**
34
+ * Check if a value is a remote proxy
35
+ */
36
+ export declare function isProxy(value: unknown): boolean;
37
+ /**
38
+ * Get the endpoint from a proxy
39
+ */
40
+ export declare function getEndpoint(proxy: unknown): Endpoint | undefined;
41
+ /**
42
+ * Release a proxy and its underlying endpoint
43
+ */
44
+ export declare function releaseProxy(proxy: unknown): void;
45
+ //# sourceMappingURL=proxy.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"proxy.d.ts","sourceRoot":"","sources":["../src/proxy.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AACtC,OAAO,EAEL,KAAK,YAAY,EAIlB,MAAM,SAAS,CAAC;AA6GjB;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAgB,IAAI,CAAC,CAAC,SAAS,MAAM,EAAE,QAAQ,EAAE,QAAQ,GAAG,YAAY,CAAC,CAAC,CAAC,CAAC;AAC5E,wBAAgB,IAAI,CAAC,CAAC,SAAS,MAAM,EACnC,QAAQ,EAAE,QAAQ,EAClB,OAAO,CAAC,EAAE;IAAE,OAAO,CAAC,EAAE,MAAM,CAAA;CAAE,GAC7B,YAAY,CAAC,CAAC,CAAC,CAAC;AAKnB;;GAEG;AACH,wBAAgB,OAAO,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAU/C;AAED;;GAEG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,OAAO,GAAG,QAAQ,GAAG,SAAS,CAKhE;AAED;;GAEG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,OAAO,GAAG,IAAI,CAIjD"}
@@ -0,0 +1,37 @@
1
+ /**
2
+ * Worker RPC - Serialization Module
3
+ * Minimal serialization - only handles Error objects for name/stack preservation
4
+ * All other data passes through structured clone directly
5
+ */
6
+ /** Serialized error for transmission */
7
+ export interface SerializedError {
8
+ /** Error message */
9
+ e: string;
10
+ /** Error name (e.g., "TypeError") */
11
+ n: string;
12
+ /** Error stack trace */
13
+ s?: string;
14
+ }
15
+ /**
16
+ * Serialize an Error object for transmission
17
+ * Preserves name and stack which structured clone doesn't handle well
18
+ */
19
+ export declare function serializeError(error: Error): SerializedError;
20
+ /**
21
+ * Deserialize an error from transmission
22
+ * Reconstructs the Error with proper name and stack
23
+ */
24
+ export declare function deserializeError(data: SerializedError): Error;
25
+ /**
26
+ * Check if a value is a function
27
+ */
28
+ export declare function isFunction(value: unknown): value is Function;
29
+ /**
30
+ * Check if a value is a MessagePort
31
+ */
32
+ export declare function isMessagePort(value: unknown): value is MessagePort;
33
+ /**
34
+ * Check if a value is transferable (ArrayBuffer or MessagePort)
35
+ */
36
+ export declare function isTransferable(value: unknown): value is Transferable;
37
+ //# sourceMappingURL=serialize.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"serialize.d.ts","sourceRoot":"","sources":["../src/serialize.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,wCAAwC;AACxC,MAAM,WAAW,eAAe;IAC9B,oBAAoB;IACpB,CAAC,EAAE,MAAM,CAAC;IACV,qCAAqC;IACrC,CAAC,EAAE,MAAM,CAAC;IACV,wBAAwB;IACxB,CAAC,CAAC,EAAE,MAAM,CAAC;CACZ;AAED;;;GAGG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,KAAK,GAAG,eAAe,CAM5D;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,eAAe,GAAG,KAAK,CAO7D;AAED;;GAEG;AACH,wBAAgB,UAAU,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,QAAQ,CAE5D;AAED;;GAEG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,WAAW,CAKlE;AAED;;GAEG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,YAAY,CAKpE"}
@@ -0,0 +1,154 @@
1
+ /**
2
+ * Worker RPC - Type Definitions
3
+ * High-performance, type-safe worker communication for Bun.js
4
+ */
5
+ /** Unique identifier for tracking RPC calls */
6
+ export type CallId = string;
7
+ /** Unique identifier for callback functions */
8
+ export type CallbackId = string;
9
+ /** Path to a nested method (e.g., ['db', 'users', 'find']) */
10
+ export type MethodPath = string[];
11
+ /** Message types for the RPC protocol */
12
+ export declare enum MessageType {
13
+ /** Request to invoke a method */
14
+ Call = 0,
15
+ /** Successful response */
16
+ Result = 1,
17
+ /** Error response */
18
+ Error = 2,
19
+ /** Callback invocation */
20
+ Callback = 3,
21
+ /** Release a callback reference */
22
+ Release = 4,
23
+ /** Establish a direct channel between workers */
24
+ Channel = 5,
25
+ /** Acknowledgment for channel establishment */
26
+ ChannelAck = 6
27
+ }
28
+ /** Base message structure */
29
+ export interface BaseMessage {
30
+ /** Message type discriminator */
31
+ t: MessageType;
32
+ /** Call ID for request/response correlation */
33
+ id: CallId;
34
+ }
35
+ /** Request to invoke a remote method */
36
+ export interface CallMessage extends BaseMessage {
37
+ t: MessageType.Call;
38
+ /** Method path (supports nested objects) */
39
+ p: MethodPath;
40
+ /** Arguments (passed raw via structured clone) */
41
+ a: unknown[];
42
+ /** Callback mappings: argIndex -> callbackId (only for function arguments) */
43
+ c?: Record<number, CallbackId>;
44
+ }
45
+ /** Successful response */
46
+ export interface ResultMessage extends BaseMessage {
47
+ t: MessageType.Result;
48
+ /** Return value (passed raw via structured clone) */
49
+ v: unknown;
50
+ /** Callback ID if return value is a function */
51
+ c?: CallbackId;
52
+ }
53
+ /** Error response */
54
+ export interface ErrorMessage extends BaseMessage {
55
+ t: MessageType.Error;
56
+ /** Error message */
57
+ e: string;
58
+ /** Error name (e.g., "TypeError", "RangeError") */
59
+ n?: string;
60
+ /** Error stack trace */
61
+ s?: string;
62
+ }
63
+ /** Callback invocation request */
64
+ export interface CallbackMessage extends BaseMessage {
65
+ t: MessageType.Callback;
66
+ /** Callback ID */
67
+ c: CallbackId;
68
+ /** Arguments (passed raw via structured clone) */
69
+ a: unknown[];
70
+ /** Callback mappings for nested callbacks in args */
71
+ cb?: Record<number, CallbackId>;
72
+ }
73
+ /** Release a callback reference */
74
+ export interface ReleaseMessage extends BaseMessage {
75
+ t: MessageType.Release;
76
+ /** Callback IDs to release */
77
+ c: CallbackId[];
78
+ }
79
+ /** Channel establishment message */
80
+ export interface ChannelMessage extends BaseMessage {
81
+ t: MessageType.Channel;
82
+ /** Target worker identifier */
83
+ target: string;
84
+ /** MessagePort for direct communication */
85
+ port: MessagePort;
86
+ }
87
+ /** Channel acknowledgment */
88
+ export interface ChannelAckMessage extends BaseMessage {
89
+ t: MessageType.ChannelAck;
90
+ /** Source worker identifier */
91
+ source: string;
92
+ }
93
+ /** Union of all message types */
94
+ export type RpcMessage = CallMessage | ResultMessage | ErrorMessage | CallbackMessage | ReleaseMessage | ChannelMessage | ChannelAckMessage;
95
+ /** Unwrap a Promise type */
96
+ export type Unpromise<T> = T extends Promise<infer U> ? U : T;
97
+ /** Make a function async if it isn't already */
98
+ export type Promisify<T> = T extends (...args: infer A) => infer R ? (...args: A) => Promise<Unpromise<R>> : never;
99
+ /** Convert a function type to its remote callable version */
100
+ export type RemoteFunction<T> = T extends (...args: infer A) => infer R ? (...args: A) => Promise<Unpromise<R>> : never;
101
+ /** Convert an object type to its remote callable version */
102
+ export type RemoteObject<T> = {
103
+ [K in keyof T]: T[K] extends (...args: any[]) => any ? RemoteFunction<T[K]> : T[K] extends object ? RemoteObject<T[K]> : T[K];
104
+ };
105
+ /** Symbol to mark a proxy as a remote reference */
106
+ export declare const REMOTE_PROXY: unique symbol;
107
+ /** Symbol for accessing the underlying endpoint */
108
+ export declare const ENDPOINT: unique symbol;
109
+ /** Symbol for releasing a proxy */
110
+ export declare const RELEASE: unique symbol;
111
+ /** Symbol for creating a direct channel */
112
+ export declare const CREATE_CHANNEL: unique symbol;
113
+ /** Configuration options for the RPC endpoint */
114
+ export interface EndpointOptions {
115
+ /** Timeout for RPC calls in milliseconds (default: 30000) */
116
+ timeout?: number;
117
+ /** Custom error handler */
118
+ onError?: (error: Error) => void;
119
+ /** Enable debug logging */
120
+ debug?: boolean;
121
+ }
122
+ /** Configuration for exposing an API */
123
+ export interface ExposeOptions {
124
+ /** Maximum depth for nested objects (default: 10) */
125
+ maxDepth?: number;
126
+ }
127
+ /** Pending call information */
128
+ export interface PendingCall {
129
+ resolve: (value: any) => void;
130
+ reject: (error: Error) => void;
131
+ timer?: ReturnType<typeof setTimeout>;
132
+ }
133
+ /** Callback registration */
134
+ export interface CallbackRegistration {
135
+ fn: Function;
136
+ /** Number of times this callback can be invoked (-1 = unlimited) */
137
+ remaining: number;
138
+ }
139
+ /** Transferable types */
140
+ export type TransferableValue = ArrayBuffer | MessagePort;
141
+ /** Worker-like message target */
142
+ export interface MessageTarget {
143
+ postMessage(message: any, transfer?: any[]): void;
144
+ addEventListener?(type: string, listener: (event: any) => void): void;
145
+ removeEventListener?(type: string, listener: (event: any) => void): void;
146
+ onmessage?: ((event: any) => void) | null;
147
+ start?(): void;
148
+ close?(): void;
149
+ }
150
+ /** Extended Worker type with Bun-specific features */
151
+ export interface BunWorker extends MessageTarget {
152
+ terminate?(): void;
153
+ }
154
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,46 @@
1
+ {
2
+ "name": "@xentobias/worker-rpc",
3
+ "version": "1.0.1",
4
+ "description": "High-performance, type-safe RPC for Bun.js Workers",
5
+ "module": "src/index.ts",
6
+ "main": "dist/index.js",
7
+ "types": "dist/index.d.ts",
8
+ "type": "module",
9
+ "files": [
10
+ "dist"
11
+ ],
12
+ "exports": {
13
+ ".": {
14
+ "types": "./dist/index.d.ts",
15
+ "import": "./dist/index.js"
16
+ }
17
+ },
18
+ "scripts": {
19
+ "build": "bun build ./src/index.ts --outdir ./dist --target bun && tsc --declaration",
20
+ "typecheck": "tsc --noEmit",
21
+ "test": "bun test",
22
+ "example": "bun run examples/basic/main.ts",
23
+ "example:nested": "bun run examples/nested/main.ts",
24
+ "example:callbacks": "bun run examples/callbacks/main.ts",
25
+ "example:w2w": "bun run examples/worker-to-worker/main.ts",
26
+ "benchmark": "bun run benchmark/run.ts"
27
+ },
28
+ "keywords": [
29
+ "bun",
30
+ "worker",
31
+ "rpc",
32
+ "comlink",
33
+ "typescript",
34
+ "proxy",
35
+ "message-passing"
36
+ ],
37
+ "license": "MIT",
38
+ "devDependencies": {
39
+ "@types/bun": "latest",
40
+ "comlink": "^4.4.2",
41
+ "typescript": "^5"
42
+ },
43
+ "peerDependencies": {
44
+ "typescript": "^5"
45
+ }
46
+ }