@tstdl/base 0.90.19 → 0.90.21

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.
@@ -111,7 +111,7 @@ let ApiGateway = class ApiGateway {
111
111
  };
112
112
  this.apis.set(resource, resourceApis);
113
113
  }
114
- const endpointImplementation = implementation[name]?.bind(implementation) ?? deferThrow(new NotImplementedError(`Endpoint ${name} for resource ${resource} not implemented.`));
114
+ const endpointImplementation = implementation[name]?.bind(implementation) ?? deferThrow(() => new NotImplementedError(`Endpoint ${name} for resource ${resource} not implemented.`));
115
115
  for (const method of methods) {
116
116
  resourceApis.endpoints.set(method, { definition: endpointDefinition, implementation: endpointImplementation });
117
117
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tstdl/base",
3
- "version": "0.90.19",
3
+ "version": "0.90.21",
4
4
  "author": "Patrick Hein",
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -109,7 +109,7 @@
109
109
  "luxon": "^3.4",
110
110
  "reflect-metadata": "^0.1",
111
111
  "rxjs": "^7.8",
112
- "type-fest": "^4.5"
112
+ "type-fest": "^4.6"
113
113
  },
114
114
  "devDependencies": {
115
115
  "@mxssfd/typedoc-theme": "1.1",
@@ -120,13 +120,13 @@
120
120
  "@types/mjml": "4.7",
121
121
  "@types/node": "20",
122
122
  "@types/nodemailer": "6.4",
123
- "@typescript-eslint/eslint-plugin": "6.8",
124
- "@typescript-eslint/parser": "6.8",
123
+ "@typescript-eslint/eslint-plugin": "6.9",
124
+ "@typescript-eslint/parser": "6.9",
125
125
  "concurrently": "8.2",
126
126
  "esbuild": "0.19",
127
127
  "eslint": "8.52",
128
128
  "eslint-import-resolver-typescript": "3.6",
129
- "eslint-plugin-import": "2.28",
129
+ "eslint-plugin-import": "2.29",
130
130
  "tsc-alias": "1.8",
131
131
  "typedoc": "0.25",
132
132
  "typedoc-plugin-missing-exports": "2.1",
@@ -0,0 +1 @@
1
+ export * from './readable-stream.adapter.js';
@@ -0,0 +1 @@
1
+ export * from './readable-stream.adapter.js';
@@ -0,0 +1,25 @@
1
+ import type { AdaptSourceResult, RpcAdapter } from '../rpc.adapter.js';
2
+ import type { RpcChannel } from '../rpc.endpoint.js';
3
+ type Request = {
4
+ type: 'pull';
5
+ } | {
6
+ type: 'cancel';
7
+ reason?: any;
8
+ };
9
+ type Response<T> = {
10
+ type: 'values';
11
+ values: T[];
12
+ } | {
13
+ type: 'done';
14
+ } | {
15
+ type: 'void';
16
+ };
17
+ export declare class ReadableStreamRpcAdapter<T> implements RpcAdapter<ReadableStream<T>, void, void, Request, Response<T>> {
18
+ readonly name = "ReadableStream";
19
+ readonly maxChunkSize: number;
20
+ constructor(maxChunkSize?: number);
21
+ adaptSource(stream: ReadableStream<T>, channel: RpcChannel<void, Request, Response<T>>): AdaptSourceResult<void>;
22
+ adaptTarget(_data: void, channel: RpcChannel<void, Request, Response<T>>): ReadableStream<T>;
23
+ }
24
+ export declare const defaultReadableStreamRpcAdapter: ReadableStreamRpcAdapter<unknown>;
25
+ export {};
@@ -0,0 +1,67 @@
1
+ import { NotSupportedError } from '../../errors/not-supported.error.js';
2
+ import { Timer } from '../../utils/timer.js';
3
+ export class ReadableStreamRpcAdapter {
4
+ name = 'ReadableStream';
5
+ maxChunkSize;
6
+ constructor(maxChunkSize = 1000) {
7
+ this.maxChunkSize = maxChunkSize;
8
+ }
9
+ adaptSource(stream, channel) {
10
+ const reader = stream.getReader();
11
+ channel.request$.subscribe(async ({ id, data }) => {
12
+ switch (data.type) {
13
+ case 'pull': {
14
+ const values = [];
15
+ const timer = Timer.startNew();
16
+ while ((timer.milliseconds < 3) && (values.length < this.maxChunkSize)) {
17
+ const result = await reader.read();
18
+ if (result.done) {
19
+ break;
20
+ }
21
+ values.push(result.value);
22
+ }
23
+ const response = (values.length > 0)
24
+ ? { type: 'values', values }
25
+ : { type: 'done' };
26
+ await channel.respond(id, response);
27
+ break;
28
+ }
29
+ case 'cancel': {
30
+ await reader.cancel(data.reason);
31
+ await channel.respond(id, { type: 'void' });
32
+ channel.close();
33
+ break;
34
+ }
35
+ default:
36
+ throw new NotSupportedError(`Type ${data.type} is not supported.`);
37
+ }
38
+ });
39
+ }
40
+ adaptTarget(_data, channel) {
41
+ return new ReadableStream({
42
+ async pull(controller) {
43
+ const response = await channel.request({ type: 'pull' });
44
+ switch (response.type) {
45
+ case 'values': {
46
+ for (const value of response.values) {
47
+ controller.enqueue(value);
48
+ }
49
+ break;
50
+ }
51
+ case 'done': {
52
+ controller.close();
53
+ break;
54
+ }
55
+ default: {
56
+ throw new NotSupportedError(`Type ${response.type} is not supported.`);
57
+ }
58
+ }
59
+ },
60
+ async cancel(reason) {
61
+ await channel.request({ type: 'cancel', reason });
62
+ channel.close();
63
+ }
64
+ });
65
+ }
66
+ }
67
+ export const defaultReadableStreamRpcAdapter = new ReadableStreamRpcAdapter();
@@ -1,19 +1,51 @@
1
1
  /// <reference types="node" resolution-mode="require"/>
2
2
  import type * as NodeWorkerThreads from 'node:worker_threads';
3
3
  import type { Observable } from 'rxjs';
4
- import type { RpcMessage } from '../model.js';
5
- import { RpcEndpoint } from '../rpc-endpoint.js';
6
- type BrowserSource = Worker | MessagePort | Window | SharedWorker;
7
- type NodeSource = NodeWorkerThreads.MessagePort | NodeWorkerThreads.Worker;
8
- export type MessagePortRpcEndpointSource = BrowserSource | NodeSource;
9
- export declare class MessagePortRpcEndpoint extends RpcEndpoint {
10
- private readonly source;
4
+ import type { RpcChannelMessage } from '../rpc.endpoint.js';
5
+ import { RpcChannel, RpcEndpoint } from '../rpc.endpoint.js';
6
+ type BrowserTransport = Worker | MessagePort | Window | SharedWorker;
7
+ type NodeTransport = NodeWorkerThreads.MessagePort | NodeWorkerThreads.Worker;
8
+ export type MessagePortRpcTransport = BrowserTransport | NodeTransport;
9
+ type MessagePortMessageBase<Type extends string> = {
10
+ seq: number;
11
+ type: Type;
12
+ };
13
+ type MessagePortOpenChannelMessage = MessagePortMessageBase<'open-channel'> & {
14
+ id: string;
15
+ port: MessagePort | NodeWorkerThreads.MessagePort;
16
+ };
17
+ type MessagePortCloseChannelMessage = MessagePortMessageBase<'close-channel'>;
18
+ type MessagePortDataMessage<T> = MessagePortMessageBase<'data'> & {
19
+ data: T;
20
+ };
21
+ type MessagePortMessage<T> = MessagePortOpenChannelMessage | MessagePortCloseChannelMessage | MessagePortDataMessage<T>;
22
+ export declare class MessagePortRpcChannel<Data = any, Req = any, Res = any> extends RpcChannel<Data, Req, Res> {
23
+ #private;
11
24
  private readonly _postMessage;
12
- readonly supportsTransfers: boolean;
13
- readonly message$: Observable<RpcMessage>;
14
- constructor(source: MessagePortRpcEndpointSource);
15
- start(): void;
25
+ private transport;
26
+ readonly messagePortMessage$: Observable<MessagePortMessage<RpcChannelMessage<Data, Req, Res>>>;
27
+ channelMessages$: Observable<RpcChannelMessage<Data, Req, Res>>;
28
+ readonly supportsTransfers = true;
29
+ readonly endpoint: MessagePortRpcEndpoint;
30
+ sequence: number;
31
+ lastSequence: number;
32
+ constructor(id: string, transport: MessagePortRpcTransport | undefined, endpoint: MessagePortRpcEndpoint);
33
+ setTransport(transport: MessagePortRpcTransport): void;
34
+ postPortMessage(message: MessagePortMessage<RpcChannelMessage<Data, Req, Res>>, transfer?: any[]): void;
35
+ close(): void;
36
+ postMessage(message: RpcChannelMessage<Data, Req, Res>, transfer?: any[] | undefined): void | Promise<void>;
37
+ }
38
+ export declare class MessagePortRpcEndpoint extends RpcEndpoint {
39
+ #private;
40
+ private readonly transport;
41
+ private readonly mainChannel;
42
+ constructor(source: MessagePortRpcTransport);
43
+ static from(transport: MessagePortRpcTransport): MessagePortRpcEndpoint;
44
+ openChannel<Data, Req, Res>(id?: string): MessagePortRpcChannel<Data, Req, Res>;
45
+ getChannel<Data, Req, Res>(id: string): MessagePortRpcChannel<Data, Req, Res>;
46
+ getOrOpenChannel<Data, Req, Res>(id: string): RpcChannel<Data, Req, Res>;
16
47
  close(): void;
17
- postMessage(data: any, transfer?: any[] | undefined): void;
48
+ forgetChannel(id: string): void;
49
+ private handleOpenChannelMessage;
18
50
  }
19
51
  export {};
@@ -1,32 +1,115 @@
1
- import { fromEvent, map } from 'rxjs';
1
+ import { ReplaySubject, Subject, filter, fromEvent, map, shareReplay, startWith, switchMap, takeUntil } from 'rxjs';
2
2
  import { isBrowser } from '../../environment.js';
3
- import { RpcEndpoint } from '../rpc-endpoint.js';
4
- export class MessagePortRpcEndpoint extends RpcEndpoint {
5
- source;
3
+ import { deferThrow } from '../../utils/throw.js';
4
+ import { isDefined, isUndefined } from '../../utils/type-guards.js';
5
+ import { RpcChannel, RpcEndpoint } from '../rpc.endpoint.js';
6
+ export class MessagePortRpcChannel extends RpcChannel {
7
+ #transportSubject = new ReplaySubject(1);
8
+ #closeSubject = new Subject();
6
9
  _postMessage;
10
+ transport = { postMessage: deferThrow(() => new Error('Rpc transport not yet initialized.')) };
11
+ messagePortMessage$;
12
+ channelMessages$;
7
13
  supportsTransfers = true;
8
- message$;
14
+ endpoint;
15
+ sequence = 1;
16
+ lastSequence = 0;
17
+ constructor(id, transport, endpoint) {
18
+ super(id);
19
+ this.endpoint = endpoint;
20
+ if (isDefined(transport)) {
21
+ this.setTransport(transport);
22
+ }
23
+ this.messagePortMessage$ = this.#transportSubject.pipe(startWith(transport), filter(isDefined), switchMap((newTransport) => fromEvent(newTransport, 'message')), takeUntil(this.#closeSubject), map((message) => ((message instanceof MessageEvent) ? message.data : message)), shareReplay({ bufferSize: 0, refCount: true }));
24
+ this.channelMessages$ = this.messagePortMessage$.pipe(filter((message) => message.type == 'data'), map((message) => message.data));
25
+ this._postMessage = isBrowser
26
+ ? (data, transfer) => this.transport.postMessage(data, { transfer })
27
+ : (data, transfer) => this.transport.postMessage(data, transfer);
28
+ if (transport instanceof MessagePort) {
29
+ transport.start();
30
+ }
31
+ this.messagePortMessage$.subscribe((message) => {
32
+ if (message.type == 'close-channel') {
33
+ this.close();
34
+ }
35
+ });
36
+ }
37
+ setTransport(transport) {
38
+ this.transport = transport;
39
+ this.#transportSubject.next(transport);
40
+ this.#transportSubject.complete();
41
+ }
42
+ postPortMessage(message, transfer) {
43
+ this._postMessage(message, transfer);
44
+ }
45
+ close() {
46
+ this.postPortMessage({ seq: this.sequence++, type: 'close-channel' });
47
+ if (this.transport instanceof MessagePort) {
48
+ this.transport.close();
49
+ }
50
+ this.#closeSubject.next();
51
+ this.endpoint.forgetChannel(this.id);
52
+ }
53
+ postMessage(message, transfer) {
54
+ this.postPortMessage({ seq: this.sequence++, type: 'data', data: message }, transfer);
55
+ }
56
+ }
57
+ export class MessagePortRpcEndpoint extends RpcEndpoint {
58
+ #closeSubject = new Subject();
59
+ #channels = new Map();
60
+ transport;
61
+ mainChannel;
9
62
  constructor(source) {
10
63
  super();
11
- this.source = (isBrowser && ((typeof SharedWorker == 'function') && (source instanceof SharedWorker)))
64
+ this.transport = (isBrowser && ((typeof SharedWorker == 'function') && (source instanceof SharedWorker)))
12
65
  ? source.port
13
66
  : source;
14
- this.message$ = fromEvent(this.source, 'message').pipe(map((message) => ((message instanceof MessageEvent) ? { ...message.data, metadata: { source: message } } : message)));
15
- this._postMessage = isBrowser
16
- ? (data, transfer) => this.source.postMessage(data, { transfer })
17
- : (data, transfer) => this.source.postMessage(data, transfer);
67
+ this.mainChannel = new MessagePortRpcChannel('main', this.transport, this);
68
+ this.mainChannel.messagePortMessage$.subscribe((message) => {
69
+ if (message.type == 'open-channel') {
70
+ this.handleOpenChannelMessage(message);
71
+ }
72
+ });
73
+ }
74
+ static from(transport) {
75
+ return new MessagePortRpcEndpoint(transport);
18
76
  }
19
- start() {
20
- if (this.source instanceof MessagePort) {
21
- this.source.start();
77
+ openChannel(id = crypto.randomUUID()) {
78
+ const { port1, port2 } = new MessageChannel();
79
+ const openChannelMessage = { seq: this.mainChannel.sequence++, type: 'open-channel', id, port: port2 };
80
+ this.mainChannel.postPortMessage(openChannelMessage, [port2]);
81
+ const channel = new MessagePortRpcChannel(id, port1, this);
82
+ this.#channels.set(id, channel);
83
+ return channel;
84
+ }
85
+ getChannel(id) {
86
+ const channel = this.#channels.get(id);
87
+ if (isUndefined(channel)) {
88
+ const newChannel = new MessagePortRpcChannel(id, undefined, this);
89
+ this.#channels.set(id, newChannel);
90
+ return newChannel;
22
91
  }
92
+ return channel;
93
+ }
94
+ getOrOpenChannel(id) {
95
+ return this.#channels.get(id) ?? this.openChannel(id);
23
96
  }
24
97
  close() {
25
- if (this.source instanceof MessagePort) {
26
- this.source.close();
98
+ if (this.transport instanceof MessagePort) {
99
+ this.transport.close();
27
100
  }
101
+ this.#closeSubject.next();
28
102
  }
29
- postMessage(data, transfer) {
30
- this._postMessage(data, transfer);
103
+ forgetChannel(id) {
104
+ this.#channels.delete(id);
105
+ }
106
+ handleOpenChannelMessage(message) {
107
+ const { id, port } = message;
108
+ if (this.#channels.has(id)) {
109
+ this.#channels.get(id).setTransport(port);
110
+ return;
111
+ }
112
+ const channel = new MessagePortRpcChannel(id, port, this);
113
+ this.#channels.set(id, channel);
31
114
  }
32
115
  }
package/rpc/index.d.ts CHANGED
@@ -1,4 +1,6 @@
1
+ export * from './adapters/index.js';
1
2
  export * from './model.js';
3
+ export * from './rpc.adapter.js';
4
+ export * from './rpc.endpoint.js';
5
+ export * from './rpc.error.js';
2
6
  export * from './rpc.js';
3
- export * from './rpc-endpoint.js';
4
- export * from './rpc-error.js';
package/rpc/index.js CHANGED
@@ -1,4 +1,6 @@
1
+ export * from './adapters/index.js';
1
2
  export * from './model.js';
3
+ export * from './rpc.adapter.js';
4
+ export * from './rpc.endpoint.js';
5
+ export * from './rpc.error.js';
2
6
  export * from './rpc.js';
3
- export * from './rpc-endpoint.js';
4
- export * from './rpc-error.js';
package/rpc/model.d.ts CHANGED
@@ -9,35 +9,27 @@ export type RpcRemoteInput = ((...args: any) => any) | Record;
9
9
  export type RpcRemote<T extends RpcRemoteInput = RpcRemoteInput> = T extends Constructor ? RpcConstructor<T> : T extends (...args: any) => any ? RpcFunction<T> : RpcObject<T>;
10
10
  export type RpcMessageBase<Type extends string = string> = {
11
11
  type: Type;
12
- id: string;
13
- metadata?: any;
14
- };
15
- export type RpcMessageWithProxyIdBase<Type extends string = string> = RpcMessageBase<Type> & {
16
- proxyId: string;
17
12
  };
18
13
  export type RpcConnectMessage = RpcMessageBase<'connect'> & {
19
14
  name: string;
20
15
  };
21
- export type RpcApplyMessage = RpcMessageWithProxyIdBase<'apply'> & {
16
+ export type RpcControlRequestMessage = RpcConnectMessage;
17
+ export type RpcProxyApplyMessage = RpcMessageBase<'apply'> & {
22
18
  path: PropertyKey[];
23
- args: RpcMessageValue[];
19
+ args: RpcValue[];
24
20
  };
25
- export type RpcConstructMessage = RpcMessageWithProxyIdBase<'construct'> & {
21
+ export type RpcProxyConstructMessage = RpcMessageBase<'construct'> & {
26
22
  path: PropertyKey[];
27
- args: RpcMessageValue[];
23
+ args: RpcValue[];
28
24
  };
29
- export type RpcGetMessage = RpcMessageWithProxyIdBase<'get'> & {
25
+ export type RpcProxyGetMessage = RpcMessageBase<'get'> & {
30
26
  path: PropertyKey[];
31
27
  };
32
- export type RpcSetMessage = RpcMessageWithProxyIdBase<'set'> & {
28
+ export type RpcProxySetMessage = RpcMessageBase<'set'> & {
33
29
  path: PropertyKey[];
34
- value: RpcMessageValue;
35
- };
36
- export type RpcReleaseProxyMessage = RpcMessageWithProxyIdBase<'release-proxy'>;
37
- export type RpcResponseMessage = RpcMessageBase<'response'> & {
38
- value: RpcMessageValue;
30
+ value: RpcValue;
39
31
  };
40
- export type RpcMessage = RpcConnectMessage | RpcApplyMessage | RpcConstructMessage | RpcGetMessage | RpcSetMessage | RpcReleaseProxyMessage | RpcResponseMessage;
32
+ export type RpcProxyRequestMessage = RpcProxyApplyMessage | RpcProxyConstructMessage | RpcProxyGetMessage | RpcProxySetMessage;
41
33
  export type RpcMessageRawValue = {
42
34
  type: 'raw';
43
35
  value: any;
@@ -49,24 +41,24 @@ export type RpcMessageSerializedValue = {
49
41
  };
50
42
  export type RpcMessageProxyValue = {
51
43
  type: 'proxy';
52
- id: string;
53
- port?: MessagePort;
44
+ channel: string;
45
+ };
46
+ export type RpcMessageAdapterValue = {
47
+ type: 'adapter';
48
+ adapter: string;
49
+ channel: string;
50
+ data: any;
54
51
  };
55
52
  export type RpcMessageThrowValue = {
56
53
  type: 'throw';
57
54
  error: unknown;
58
55
  };
59
- export type RpcMessageValue = RpcMessageRawValue | RpcMessageSerializedValue | RpcMessageProxyValue | RpcMessageThrowValue;
56
+ export type RpcValue = RpcMessageRawValue | RpcMessageSerializedValue | RpcMessageProxyValue | RpcMessageAdapterValue | RpcMessageThrowValue;
60
57
  export type RpcPostMessageArrayData = {
61
- value: RpcMessageValue[];
58
+ value: RpcValue[];
62
59
  transfer?: any[];
63
60
  };
64
61
  export type RpcPostMessageData = {
65
- value: RpcMessageValue;
62
+ value: RpcValue;
66
63
  transfer?: any[];
67
64
  };
68
- export declare function createRpcMessage<Type extends RpcMessage['type']>(type: Type, message: Omit<Extract<RpcMessage, {
69
- type: Type;
70
- }>, 'id' | 'type'>): Extract<RpcMessage, {
71
- type: Type;
72
- }>;
package/rpc/model.js CHANGED
@@ -1,8 +1 @@
1
- import { getRandomString } from '../utils/random.js';
2
- export function createRpcMessage(type, message) {
3
- return {
4
- id: getRandomString(24),
5
- type,
6
- ...message
7
- };
8
- }
1
+ export {};
@@ -0,0 +1,19 @@
1
+ import type { NonPrimitive } from '../serializer/types.js';
2
+ import type { RpcChannel } from './rpc.endpoint.js';
3
+ export type AdaptSourceResult<Data> = Data extends void ? (void | {
4
+ data?: undefined;
5
+ transfer?: any[];
6
+ }) : {
7
+ data: Data;
8
+ transfer?: any[];
9
+ };
10
+ export type RpcAdapterNonPrimitive = NonPrimitive<string, RpcAdapterNonPrimitiveData>;
11
+ export type RpcAdapterNonPrimitiveData = {
12
+ channel: string;
13
+ data: any;
14
+ };
15
+ export interface RpcAdapter<T extends object = any, Data = any, ChannelData = any, Req = any, Res = any> {
16
+ name: string;
17
+ adaptSource(value: T, channel: RpcChannel<ChannelData, Req, Res>): AdaptSourceResult<Data>;
18
+ adaptTarget(data: Data, channel: RpcChannel<ChannelData, Req, Res>): T;
19
+ }
@@ -0,0 +1 @@
1
+ export {};
package/rpc/rpc.d.ts CHANGED
@@ -1,19 +1,21 @@
1
1
  import type { SerializationOptions } from '../serializer/index.js';
2
- import type { MessagePortRpcEndpointSource } from './endpoints/message-port.rpc-endpoint.js';
3
2
  import type { RpcRemote, RpcRemoteInput } from './model.js';
4
- import { RpcEndpoint } from './rpc-endpoint.js';
5
- export type RpcEndpointSource = RpcEndpoint | MessagePortRpcEndpointSource;
3
+ import type { RpcAdapter } from './rpc.adapter.js';
4
+ import type { RpcEndpoint } from './rpc.endpoint.js';
6
5
  export declare const Rpc: {
7
- connect<T extends RpcRemoteInput>(endpointOrSource: RpcEndpointSource, name?: string): Promise<RpcRemote<T>>;
8
- expose(object: RpcRemoteInput, endpointOrSource: RpcEndpointSource, name?: string): void;
6
+ listen(endpoint: RpcEndpoint): void;
7
+ connect<T extends RpcRemoteInput>(endpoint: RpcEndpoint, name?: string): Promise<RpcRemote<T>>;
8
+ expose(object: RpcRemoteInput, name?: string): void;
9
+ registerAdapter<T_1 extends object, Data>(adapter: RpcAdapter<T_1, Data, any, any, any>): void;
9
10
  /**
10
11
  * mark object for proxy forward
11
12
  * @param object object to forward as proxy
12
13
  * @param root if object is a child of the actual passed value (to function calls, returns or whatever), this must be set to to mark the parent for serialization (required for children proxies)
13
14
  * @returns
14
15
  */
15
- proxy<T_1 extends object>(object: T_1, root?: object): T_1;
16
- transfer<T_2 extends object>(object: T_2, transfer: any[]): T_2;
17
- serialize<T_3 extends object>(object: T_3, options?: SerializationOptions): T_3;
16
+ proxy<T_2 extends object>(object: T_2, root?: object): T_2;
17
+ transfer<T_3 extends object>(object: T_3, transfer: any[]): T_3;
18
+ serialize<T_4 extends object>(object: T_4, options?: SerializationOptions): T_4;
19
+ adapt<T_5 extends object>(object: T_5, adapter: RpcAdapter<T_5, any, any, any, any>, root?: object): T_5;
18
20
  isProxied(object: object): boolean;
19
21
  };
@@ -0,0 +1,45 @@
1
+ import { type Observable } from 'rxjs';
2
+ export type RpcChannelDataMessage<T> = {
3
+ type: 'data';
4
+ data: T;
5
+ };
6
+ export type RpcChannelRequestMessage<T> = {
7
+ type: 'request';
8
+ id: string;
9
+ data: T;
10
+ };
11
+ export type RpcChannelResponseMessage<T> = {
12
+ type: 'response';
13
+ id: string;
14
+ data: T;
15
+ };
16
+ export type RpcChannelMessage<Data, Req, Res> = RpcChannelDataMessage<Data> | RpcChannelRequestMessage<Req> | RpcChannelResponseMessage<Res>;
17
+ export type RpcChannelRequest<T> = {
18
+ id: string;
19
+ data: T;
20
+ };
21
+ export declare abstract class RpcEndpoint {
22
+ abstract openChannel<Data, Req, Res>(id?: string): RpcChannel<Data, Req, Res>;
23
+ abstract getChannel<Data, Req, Res>(id: string): RpcChannel<Data, Req, Res>;
24
+ abstract getOrOpenChannel<Data, Req, Res>(id: string): RpcChannel<Data, Req, Res>;
25
+ abstract close(): void;
26
+ }
27
+ /**
28
+ * @template Data - type of data-messages
29
+ * @template Req - type of request-messages
30
+ * @template Res - type of response-messages
31
+ */
32
+ export declare abstract class RpcChannel<Data = unknown, Req = unknown, Res = unknown> {
33
+ readonly id: string;
34
+ readonly message$: Observable<Data>;
35
+ readonly request$: Observable<RpcChannelRequest<Req>>;
36
+ protected abstract readonly channelMessages$: Observable<RpcChannelMessage<Data, Req, Res>>;
37
+ abstract readonly supportsTransfers: boolean;
38
+ abstract readonly endpoint: RpcEndpoint;
39
+ constructor(id: string);
40
+ request<U extends Res>(data: Req, transfer?: any[]): Promise<U>;
41
+ respond(id: string, data: Res, transfer?: any[]): Promise<void>;
42
+ send(data: Data, transfer?: any[]): Promise<void>;
43
+ abstract close(): void;
44
+ protected abstract postMessage(message: RpcChannelMessage<Data, Req, Res>, transfer?: any[]): void | Promise<void>;
45
+ }
@@ -0,0 +1,46 @@
1
+ import { defer, filter, map } from 'rxjs';
2
+ export class RpcEndpoint {
3
+ }
4
+ /**
5
+ * @template Data - type of data-messages
6
+ * @template Req - type of request-messages
7
+ * @template Res - type of response-messages
8
+ */
9
+ export class RpcChannel {
10
+ id;
11
+ message$;
12
+ request$;
13
+ constructor(id) {
14
+ this.id = id;
15
+ this.message$ = defer(() => this.channelMessages$.pipe(filter((message) => message.type == 'data'), map((message) => message.data)));
16
+ this.request$ = defer(() => this.channelMessages$.pipe(filter((message) => message.type == 'request'), map((message) => message)));
17
+ }
18
+ async request(data, transfer) {
19
+ const id = crypto.randomUUID();
20
+ const $response = getResponsePromise(this.channelMessages$, id);
21
+ await this.postMessage({ type: 'request', id, data }, transfer);
22
+ const response = await $response;
23
+ return response;
24
+ }
25
+ async respond(id, data, transfer) {
26
+ await this.postMessage({ type: 'response', id, data }, transfer);
27
+ }
28
+ async send(data, transfer) {
29
+ await this.postMessage({ type: 'data', data }, transfer);
30
+ }
31
+ }
32
+ async function getResponsePromise(channelMessage$, requestId) {
33
+ return new Promise((resolve, reject) => {
34
+ const subscription = channelMessage$.subscribe({
35
+ next(message) {
36
+ if ((message.type == 'response') && (message.id == requestId)) {
37
+ resolve(message.data);
38
+ subscription.unsubscribe();
39
+ }
40
+ },
41
+ complete() {
42
+ reject(new Error('RpcEndpoint was closed while waiting for response.'));
43
+ }
44
+ });
45
+ });
46
+ }
package/rpc/rpc.js CHANGED
@@ -1,38 +1,66 @@
1
1
  /* eslint-disable @typescript-eslint/no-unsafe-return */
2
2
  /* eslint-disable @typescript-eslint/no-unsafe-argument */
3
- import { filter, Subject, takeUntil } from 'rxjs';
3
+ import { NotFoundError } from '../errors/not-found.error.js';
4
4
  import { NotImplementedError } from '../errors/not-implemented.error.js';
5
+ import { NotSupportedError } from '../errors/not-supported.error.js';
5
6
  import { deserialize, registerSerializer, serialize } from '../serializer/index.js';
7
+ import { valueOfType } from '../utils/helpers.js';
6
8
  import { hasOwnProperty } from '../utils/object/object.js';
7
9
  import { reflectMethods } from '../utils/proxy.js';
8
- import { getRandomString } from '../utils/random.js';
9
- import { _throw } from '../utils/throw.js';
10
- import { assert, isDefined } from '../utils/type-guards.js';
11
- import { MessagePortRpcEndpoint } from './endpoints/message-port.rpc-endpoint.js';
12
- import { createRpcMessage } from './model.js';
13
- import { RpcEndpoint } from './rpc-endpoint.js';
14
- import { RpcError, RpcRemoteError } from './rpc-error.js';
10
+ import { _throw, deferThrow } from '../utils/throw.js';
11
+ import { assert, isDefined, isUndefined } from '../utils/type-guards.js';
12
+ import { RpcError, RpcRemoteError } from './rpc.error.js';
15
13
  const markedTransfers = new WeakMap();
16
14
  const serializationTargets = new WeakMap();
15
+ const adapters = new Map();
16
+ const adapterTargets = new WeakMap();
17
17
  const proxyTargets = new WeakSet();
18
- const proxyFinalizationRegistry = new FinalizationRegistry(async (data) => {
19
- const message = createRpcMessage('release-proxy', { proxyId: data.id });
20
- await data.endpoint.postMessage(message);
21
- });
18
+ const exposings = new Map();
19
+ const channelFinalizationRegistry = new FinalizationRegistry((data) => data.channel.close());
20
+ class RpcProxy {
21
+ }
22
22
  // eslint-disable-next-line @typescript-eslint/no-redeclare, @typescript-eslint/naming-convention
23
23
  export const Rpc = {
24
- async connect(endpointOrSource, name = 'default') {
25
- const endpoint = (endpointOrSource instanceof RpcEndpoint) ? endpointOrSource : new MessagePortRpcEndpoint(endpointOrSource);
26
- endpoint.start();
27
- const connectMessage = createRpcMessage('connect', { name });
28
- const response = await endpoint.request(connectMessage);
29
- assert(response.type == 'proxy', 'Rpc connect must result in proxy.');
24
+ listen(endpoint) {
25
+ const controlChannel = endpoint.getChannel('control');
26
+ controlChannel.request$.subscribe(async ({ id: messageId, data: message }) => {
27
+ switch (message.type) {
28
+ case 'connect': {
29
+ try {
30
+ const exposing = exposings.get(message.name);
31
+ if (isUndefined(exposing)) {
32
+ throw new NotFoundError(`Could not connect to "${message.name}" as nothing with that name is exposed.`);
33
+ }
34
+ const value = getAdapterOrProxy(exposing, endpoint);
35
+ await controlChannel.respond(messageId, value);
36
+ }
37
+ catch (error) {
38
+ await controlChannel.respond(messageId, { type: 'throw', error });
39
+ }
40
+ break;
41
+ }
42
+ default:
43
+ throw new NotSupportedError(`Message type ${message.type} not supported in listen.`);
44
+ }
45
+ });
46
+ },
47
+ async connect(endpoint, name = 'default') {
48
+ const channel = endpoint.getOrOpenChannel('control');
49
+ const response = await channel.request({ type: 'connect', name });
50
+ assert((response.type == 'proxy') || (response.type == 'adapter') || (response.type == 'throw'), 'Rpc connect must result in proxy, adapter or throw.');
30
51
  return parseRpcMessageValue(response, endpoint);
31
52
  },
32
- expose(object, endpointOrSource, name = 'default') {
33
- const endpoint = (endpointOrSource instanceof RpcEndpoint) ? endpointOrSource : new MessagePortRpcEndpoint(endpointOrSource);
34
- endpoint.start();
35
- exposeConnectable(object, endpoint, name);
53
+ expose(object, name = 'default') {
54
+ exposings.set(name, object);
55
+ },
56
+ registerAdapter(adapter) {
57
+ const adapterName = `RpcAdapter:${adapter.name}`;
58
+ adapters.set(adapterName, adapter);
59
+ const klass = {
60
+ [adapterName]: class {
61
+ }
62
+ }[adapterName];
63
+ registerSerializer(klass, klass.name, () => _throw(new NotSupportedError('Serialization not supported for rpc adapter.')), ({ channel, data }, _, { rpcEndpoint }) => adapter.adaptTarget(data, rpcEndpoint.getChannel(channel)));
36
64
  },
37
65
  /**
38
66
  * mark object for proxy forward
@@ -48,48 +76,57 @@ export const Rpc = {
48
76
  return object;
49
77
  },
50
78
  transfer(object, transfer) {
51
- markedTransfers.set(object, transfer);
79
+ const existingTransfers = markedTransfers.get(object);
80
+ markedTransfers.set(object, isDefined(existingTransfers) ? [...existingTransfers, ...transfer] : transfer);
52
81
  return object;
53
82
  },
54
83
  serialize(object, options) {
55
84
  serializationTargets.set(object, options);
56
85
  return object;
57
86
  },
87
+ adapt(object, adapter, root) {
88
+ adapterTargets.set(object, adapter);
89
+ if (isDefined(root)) {
90
+ Rpc.serialize(root);
91
+ }
92
+ return object;
93
+ },
58
94
  isProxied(object) {
59
95
  return proxyTargets.has(object);
60
96
  }
61
97
  };
62
- function createProxy(endpoint, id, path = []) {
63
- class RpcProxy {
64
- }
98
+ function createProxy(channel, path = []) {
65
99
  // eslint-disable-next-line prefer-const
66
100
  let proxy;
101
+ const { endpoint } = channel;
67
102
  const handlers = {
68
103
  apply(_target, _this, args) {
69
104
  const postMessageData = getPostMessageDataFromArray(args, endpoint);
70
- const message = createRpcMessage('apply', { proxyId: id, path, args: postMessageData.value });
71
- return endpoint.request(message, postMessageData.transfer).then((responseValue) => parseRpcMessageValue(responseValue, endpoint));
105
+ return channel
106
+ .request({ type: 'apply', path, args: postMessageData.value }, postMessageData.transfer)
107
+ .then((responseValue) => parseRpcMessageValue(responseValue, endpoint));
72
108
  },
73
109
  construct(_target, args) {
74
110
  const postMessageData = getPostMessageDataFromArray(args, endpoint);
75
- const message = createRpcMessage('construct', { proxyId: id, path, args: postMessageData.value });
76
- return endpoint.request(message, postMessageData.transfer).then((responseValue) => parseRpcMessageValue(responseValue, endpoint));
111
+ return channel
112
+ .request({ type: 'construct', path, args: postMessageData.value }, postMessageData.transfer)
113
+ .then((responseValue) => parseRpcMessageValue(responseValue, endpoint));
77
114
  },
78
115
  get(_target, property) {
79
116
  if (property == 'then') {
80
117
  if (path.length == 0) {
81
118
  return { then: () => proxy };
82
119
  }
83
- const message = createRpcMessage('get', { proxyId: id, path });
84
- const resultValue = endpoint.request(message).then((responseValue) => parseRpcMessageValue(responseValue, endpoint));
120
+ const resultValue = channel.request({ type: 'get', path }).then((responseValue) => parseRpcMessageValue(responseValue, endpoint));
85
121
  return resultValue.then.bind(resultValue);
86
122
  }
87
- return createProxy(endpoint, id, [...path, property]);
123
+ return createProxy(channel, [...path, property]);
88
124
  },
89
125
  set(_target, property, value) {
90
126
  const postMessageData = getPostMessageData(value, endpoint);
91
- const message = createRpcMessage('set', { proxyId: id, path: [...path, property], value: postMessageData.value });
92
- return endpoint.request(message, postMessageData.transfer).then((responseValue) => parseRpcMessageValue(responseValue, endpoint));
127
+ return channel
128
+ .request({ type: 'set', path: [...path, property], value: postMessageData.value }, postMessageData.transfer)
129
+ .then((responseValue) => parseRpcMessageValue(responseValue, endpoint));
93
130
  },
94
131
  getPrototypeOf() {
95
132
  return null;
@@ -97,115 +134,97 @@ function createProxy(endpoint, id, path = []) {
97
134
  };
98
135
  for (const method of reflectMethods) {
99
136
  if (!hasOwnProperty(handlers, method)) {
100
- handlers[method] = () => {
101
- throw new Error(`${method} not supported on rpc proxies.`);
102
- };
137
+ handlers[method] = deferThrow(() => new NotSupportedError(`${method} not supported on rpc proxies.`));
103
138
  }
104
139
  }
105
- proxy = new Proxy(RpcProxy, handlers);
106
- proxyFinalizationRegistry.register(proxy, { id, endpoint });
107
- return proxy;
108
- }
109
- function exposeConnectable(object, endpoint, name) {
110
- endpoint.message$
111
- .pipe(filter((message) => message.type == 'connect'), filter((message) => message.name == name))
112
- .subscribe(async (message) => {
113
- try {
114
- const [proxy, transfer] = createProxyValue(object, endpoint);
115
- await endpoint.respond(message.id, proxy, transfer);
116
- }
117
- catch (error) {
118
- await endpoint.respond(message.id, { type: 'throw', error });
119
- }
120
- });
140
+ return new Proxy(RpcProxy, handlers);
121
141
  }
122
- function exposeObject(object, endpoint, proxyId) {
123
- const abortSubject = new Subject();
124
- endpoint.message$
125
- .pipe(takeUntil(abortSubject), filter((message) => (message.proxyId == proxyId)))
126
- .subscribe(async (message) => {
142
+ function exposeObject(object, channel) {
143
+ const { endpoint } = channel;
144
+ channel.request$.subscribe(async ({ id: messageId, data: message }) => {
127
145
  try {
128
146
  switch (message.type) {
129
147
  case 'get': {
130
148
  const { value } = deref(object, message.path);
131
149
  const postMessageData = getPostMessageData(await value, endpoint);
132
- await endpoint.respond(message.id, postMessageData.value, postMessageData.transfer);
150
+ await channel.respond(messageId, postMessageData.value, postMessageData.transfer);
133
151
  break;
134
152
  }
135
153
  case 'set': {
136
154
  const { value } = deref(object, message.path, true);
137
- const result = Reflect.set(value, message.path[message.path.length - 1], parseRpcMessageValue(message.value, endpoint));
155
+ const result = Reflect.set(value, message.path[message.path.length - 1], parseRpcMessageValue(message.value, channel.endpoint));
138
156
  const postMessageData = getPostMessageData(result, endpoint);
139
- await endpoint.respond(message.id, postMessageData.value, postMessageData.transfer);
157
+ await channel.respond(messageId, postMessageData.value, postMessageData.transfer);
140
158
  break;
141
159
  }
142
160
  case 'apply': {
143
161
  const { parent, value } = deref(object, message.path);
144
- const result = await Reflect.apply(value, parent, parseRpcMessageValues(message.args, endpoint));
162
+ const result = await Reflect.apply(value, parent, parseRpcMessageValues(message.args, channel.endpoint));
145
163
  const postMessageData = getPostMessageData(result, endpoint);
146
- await endpoint.respond(message.id, postMessageData.value, postMessageData.transfer);
164
+ await channel.respond(messageId, postMessageData.value, postMessageData.transfer);
147
165
  break;
148
166
  }
149
167
  case 'construct': {
150
168
  const { value } = deref(object, message.path);
151
- const result = Reflect.construct(value, parseRpcMessageValues(message.args, endpoint));
152
- const [proxy, transfer] = createProxyValue(result, endpoint);
153
- await endpoint.respond(message.id, proxy, transfer);
154
- break;
155
- }
156
- case 'release-proxy': {
157
- abortSubject.next();
169
+ const result = Reflect.construct(value, parseRpcMessageValues(message.args, channel.endpoint));
170
+ const postMessageData = getPostMessageData(Rpc.proxy(result), endpoint);
171
+ await channel.respond(messageId, postMessageData.value, postMessageData.transfer);
158
172
  break;
159
173
  }
160
- case 'response':
161
- case 'connect':
162
- // we do not handle these here
163
- break;
164
174
  default:
165
- throw new NotImplementedError(`Unsupported message type ${message.type}`);
175
+ throw new NotImplementedError(`Unsupported message type ${message.type}.`);
166
176
  }
167
177
  }
168
178
  catch (error) {
169
- await endpoint.respond(message.id, { type: 'throw', error });
179
+ await channel.respond(messageId, { type: 'throw', error });
170
180
  }
171
181
  });
172
182
  }
173
183
  function getPostMessageDataFromArray(values, endpoint) {
174
- const mappedValues = values.map((value) => getRpcMessageValue(value, endpoint));
184
+ const messageValues = values.map((value) => getRpcMessageValue(value, endpoint));
175
185
  const transfer = values.flatMap((value) => markedTransfers.get(value) ?? []);
176
- const messageValues = mappedValues.map((mappedValue) => mappedValue[0]);
177
- const additionalTransfers = mappedValues.flatMap((mappedValue) => mappedValue[1]);
178
- return { value: messageValues, transfer: [...transfer, ...additionalTransfers] };
186
+ return { value: messageValues, transfer };
179
187
  }
180
188
  function getPostMessageData(value, endpoint) {
181
- const [messageValue, additionalTransfers] = getRpcMessageValue(value, endpoint);
182
- const transfer = markedTransfers.get(value) ?? [];
183
- return { value: messageValue, transfer: [...transfer, ...additionalTransfers] };
189
+ const messageValue = getRpcMessageValue(value, endpoint);
190
+ const transfer = markedTransfers.get(value);
191
+ return { value: messageValue, transfer };
192
+ }
193
+ function getAdapterOrProxy(exposing, endpoint) {
194
+ return adapterTargets.has(exposing)
195
+ ? createAdapterValue(exposing, adapterTargets.get(exposing), endpoint)
196
+ : createProxyValue(exposing, endpoint);
184
197
  }
185
198
  function getRpcMessageValue(value, endpoint) {
199
+ const adapter = adapterTargets.get(value);
200
+ if (isDefined(adapter)) {
201
+ return createAdapterValue(value, adapter, endpoint);
202
+ }
186
203
  if (proxyTargets.has(value)) {
187
204
  return createProxyValue(value, endpoint);
188
205
  }
189
206
  if (serializationTargets.has(value)) {
190
207
  const options = serializationTargets.get(value);
191
- const transfer = [];
192
- const replacer = getRpcProxySerializationReplacer(endpoint, transfer);
193
- const serialized = serialize(value, { ...options, replacers: [replacer, ...options?.replacers ?? []] });
194
- return [{ type: 'serialized', value: serialized, options }, transfer];
208
+ const proxyReplacer = getRpcProxySerializationReplacer(endpoint);
209
+ const serialized = serialize(value, { ...options, replacers: [proxyReplacer, rpcAdapterReplacer, ...options?.replacers ?? []], data: { ...options?.data, rpcEndpoint: endpoint } });
210
+ return { type: 'serialized', value: serialized, options };
195
211
  }
196
- return [{ type: 'raw', value }, []];
212
+ return { type: 'raw', value };
197
213
  }
198
214
  function createProxyValue(value, endpoint) {
199
- const id = getRandomString(24);
200
- if (endpoint.supportsTransfers) {
201
- const { port1, port2 } = new MessageChannel();
202
- const newEndpoint = new MessagePortRpcEndpoint(port1);
203
- newEndpoint.start();
204
- exposeObject(value, newEndpoint, id);
205
- return [{ type: 'proxy', id, port: port2 }, [port2]];
215
+ const channel = endpoint.openChannel();
216
+ exposeObject(value, channel);
217
+ return { type: 'proxy', channel: channel.id };
218
+ }
219
+ function createAdapterValue(value, adapter, endpoint) {
220
+ const channel = endpoint.openChannel();
221
+ const { data, transfer } = adapter.adaptSource(value, channel) ?? {}; // eslint-disable-line @typescript-eslint/no-unnecessary-condition
222
+ if (isDefined(transfer)) {
223
+ for (const transferValue of transfer) {
224
+ Rpc.transfer(value, transferValue);
225
+ }
206
226
  }
207
- exposeObject(value, endpoint, id);
208
- return [{ type: 'proxy', id }, []];
227
+ return { type: 'adapter', adapter: `RpcAdapter:${adapter.name}`, channel: channel.id, data };
209
228
  }
210
229
  function parseRpcMessageValues(values, endpoint) {
211
230
  return values.map((value) => parseRpcMessageValue(value, endpoint));
@@ -220,12 +239,23 @@ function parseRpcMessageValue(value, endpoint) {
220
239
  }
221
240
  case 'throw': {
222
241
  const remoteError = new RpcRemoteError(value.error);
223
- throw new RpcError('Received error from remote.', remoteError);
242
+ throw new RpcError(`Received error from remote: ${remoteError.message}`, remoteError);
224
243
  }
225
244
  case 'proxy': {
226
- const proxyEndpoint = (isDefined(value.port)) ? new MessagePortRpcEndpoint(value.port) : endpoint;
227
- proxyEndpoint.start();
228
- return createProxy(proxyEndpoint, value.id);
245
+ const channel = endpoint.getChannel(value.channel);
246
+ const proxy = createProxy(channel);
247
+ channelFinalizationRegistry.register(proxy, { channel });
248
+ return proxy;
249
+ }
250
+ case 'adapter': {
251
+ const channel = endpoint.getChannel(value.channel);
252
+ const adapter = adapters.get(value.adapter);
253
+ if (isUndefined(adapter)) {
254
+ throw new NotSupportedError(`No adapter registration for "${value.adapter}" found.`);
255
+ }
256
+ const adaptedValue = adapter.adaptTarget(value.data, channel);
257
+ channelFinalizationRegistry.register(adaptedValue, { channel });
258
+ return adaptedValue;
229
259
  }
230
260
  default:
231
261
  throw new Error(`Type ${value.type} not supported`);
@@ -243,17 +273,31 @@ function deref(object, path, skipLast = false) {
243
273
  }
244
274
  return { parent, value };
245
275
  }
246
- class RpcProxy {
247
- }
248
- registerSerializer(RpcProxy, 'RpcProxy', () => _throw(new Error('Not supported.')), (data, _, { rpcEndpoint }) => parseRpcMessageValue({ type: 'proxy', ...data }, rpcEndpoint));
249
- function getRpcProxySerializationReplacer(endpoint, transfer) {
276
+ registerSerializer(RpcProxy, 'RpcProxy', () => _throw(new NotSupportedError('Serialization not supported for rpc proxy.')), (data, _, { rpcEndpoint }) => parseRpcMessageValue({ type: 'proxy', ...data }, rpcEndpoint));
277
+ function getRpcProxySerializationReplacer(endpoint) {
250
278
  function rpcProxySerializationReplacer(value) {
251
279
  if (!proxyTargets.has(value)) {
252
280
  return value;
253
281
  }
254
- const [proxy, proxyTransfer] = createProxyValue(value, endpoint);
255
- transfer.push(...proxyTransfer);
256
- return { '<RpcProxy>': { id: proxy.id, port: proxy.port } };
282
+ const proxy = createProxyValue(value, endpoint);
283
+ return { '<RpcProxy>': valueOfType({ channel: proxy.channel }) };
257
284
  }
258
285
  return rpcProxySerializationReplacer;
259
286
  }
287
+ function rpcAdapterReplacer(value, serializationData) {
288
+ const adapter = adapterTargets.get(value);
289
+ if (isUndefined(adapter)) {
290
+ return value;
291
+ }
292
+ const channel = serializationData.rpcEndpoint.openChannel();
293
+ const { data, transfer } = adapter.adaptSource(value, channel) ?? {}; // eslint-disable-line @typescript-eslint/no-unnecessary-condition
294
+ if (isDefined(transfer)) {
295
+ if (isDefined(serializationData.transfer)) {
296
+ serializationData.transfer.push(...transfer);
297
+ }
298
+ else {
299
+ serializationData.transfer = transfer;
300
+ }
301
+ }
302
+ return { [`<RpcAdapter:${adapter.name}>`]: valueOfType({ channel: channel.id, data }) };
303
+ }
@@ -6,8 +6,8 @@ export declare const Serializable: {
6
6
  export type DereferenceCallback = (dereferenced: unknown) => void;
7
7
  export type TryDereference = (value: unknown, callback: DereferenceCallback) => boolean;
8
8
  export interface Serializable<T, Data> {
9
- [Serializable.serialize](instance: T, context: Record): Data;
10
- [Serializable.deserialize](data: Data, tryDereference: TryDereference, context: Record): T;
9
+ [Serializable.serialize](instance: T, context: Record | undefined): Data;
10
+ [Serializable.deserialize](data: Data, tryDereference: TryDereference, context: Record | undefined): T;
11
11
  }
12
12
  export type SerializableType<T, Data> = Type<Serializable<T, Data>>;
13
13
  export type SerializeFunction<T, Data> = Serializable<T, Data>[typeof Serializable.serialize];
@@ -19,7 +19,7 @@ export function serialize(_value, options = {}, references = new Map(), queue =
19
19
  let value = _value;
20
20
  if (isDefined(options.replacers)) {
21
21
  for (const replacer of options.replacers) {
22
- value = replacer(value);
22
+ value = replacer(value, options.data);
23
23
  }
24
24
  }
25
25
  const type = typeof value;
@@ -31,7 +31,7 @@ export function serialize(_value, options = {}, references = new Map(), queue =
31
31
  }
32
32
  if ((path == '$') && isDefined(options.context)) {
33
33
  for (const entry of objectEntries(options.context)) {
34
- references.set(entry[1], `$['__context__']['${entry[0]}']`);
34
+ references.set(entry[1], `$['__context__']['${String(entry[0])}']`);
35
35
  }
36
36
  }
37
37
  const reference = references.get(value);
@@ -97,7 +97,7 @@ export function serialize(_value, options = {}, references = new Map(), queue =
97
97
  const typeString = getTypeString(serializableType);
98
98
  const nonPrimitive = { [typeString]: null };
99
99
  queue.add(() => {
100
- const data = registration.serializer.call(value, value, options.data ?? {});
100
+ const data = registration.serializer.call(value, value, options.data);
101
101
  (nonPrimitive[typeString] = (registration.serializeData != false) ? serialize(data, options, references, queue, `${path}['${typeString}']`) : data);
102
102
  });
103
103
  result = nonPrimitive;
@@ -130,7 +130,7 @@ function _deserialize(serialized, context, path, depth) {
130
130
  if (type == 'object') {
131
131
  if ((depth == 0) && isDefined(context.options.context)) {
132
132
  for (const entry of objectEntries(context.options.context)) {
133
- context.references.set(`$['__context__']['${entry[0]}']`, entry[1]);
133
+ context.references.set(`$['__context__']['${String(entry[0])}']`, entry[1]);
134
134
  }
135
135
  }
136
136
  const entries = objectEntries(serialized);
@@ -180,7 +180,7 @@ function _deserialize(serialized, context, path, depth) {
180
180
  context.addToDeserializeQueue(() => {
181
181
  const deserializedData = _deserialize(nonPrimitiveData, context, `${path}['<${nonPrimitiveType}>']`, depth + 1);
182
182
  context.addToDeserializeQueue(() => {
183
- const deserialized = registration.deserializer(deserializedData, context.tryAddToDerefQueue, context.options.data ?? {});
183
+ const deserialized = registration.deserializer(deserializedData, context.tryAddToDerefQueue, context.options.data);
184
184
  ForwardRef.setRef(forwardRef, deserialized);
185
185
  }, depth);
186
186
  }, depth);
@@ -1,8 +1,8 @@
1
- import type { AbstractConstructor, JsonPrimitive, Nested, Record, StringMap } from '../types.js';
1
+ import type { AbstractConstructor, JsonPrimitive, Nested, Record } from '../types.js';
2
2
  declare const serializedSymbol: unique symbol;
3
3
  declare const stringSerializedSymbol: unique symbol;
4
4
  declare const decycledSymbol: unique symbol;
5
- export type SerializationReplacer = (value: any) => any;
5
+ export type SerializationReplacer = (value: any, data?: Record) => any;
6
6
  export type TypeField<T extends string> = `<${T}>`;
7
7
  export type NonPrimitive<TypeName extends string = string, Data = unknown> = Record<TypeField<TypeName>, Data>;
8
8
  export type RawNonPrimitive = NonPrimitive<typeof rawNonPrimitiveType, any>;
@@ -45,14 +45,14 @@ export type SerializationOptions = {
45
45
  *
46
46
  * Only first level of object is referenced. For example for `{ foo: { bar: 'baz' } }` there will be one context entry having { bar: 'baz' }
47
47
  */
48
- context?: StringMap<object | Function | string | symbol | bigint>;
48
+ context?: Record;
49
49
  /**
50
50
  * Disables dereferencing of ForwardRefs. Only useful for debugging (when implementing custom serializers) and curiosity
51
51
  * @default false
52
52
  */
53
53
  doNotDereferenceForwardRefs?: boolean;
54
54
  /**
55
- * Data to be used on de/serialization handlers
55
+ * Data to be used on de/serialization handlers and replacers
56
56
  */
57
57
  data?: Record;
58
58
  };
@@ -1,8 +1,9 @@
1
1
  /// <reference types="node" resolution-mode="require"/>
2
+ import type * as NodeWorkerThreads from 'node:worker_threads';
3
+ import type { LiteralUnion } from 'type-fest';
2
4
  import type { AsyncDisposable } from '../disposable/index.js';
3
5
  import { disposeAsync } from '../disposable/index.js';
4
6
  import type { Logger } from '../logger/index.js';
5
- import type * as NodeWorkerThreads from 'node:worker_threads';
6
7
  import type { ThreadWorker } from './thread-worker.js';
7
8
  export type ThreadOptions = (WorkerOptions | NodeWorkerThreads.WorkerOptions) & {
8
9
  threadCount?: number;
@@ -15,6 +16,6 @@ export declare class ThreadPool implements AsyncDisposable {
15
16
  dispose(): Promise<void>;
16
17
  [disposeAsync](): Promise<void>;
17
18
  getProcessor<T extends ThreadWorker>(name?: string): (...args: Parameters<T>) => Promise<ReturnType<T>>;
18
- process<T extends ThreadWorker>(name: string, ...args: Parameters<T>): Promise<ReturnType<T>>;
19
+ process<T extends ThreadWorker>(name: LiteralUnion<'default', string>, ...args: Parameters<T>): Promise<ReturnType<T>>;
19
20
  private spawn;
20
21
  }
@@ -2,6 +2,7 @@ import { disposeAsync } from '../disposable/index.js';
2
2
  import { isNode } from '../environment.js';
3
3
  import { dynamicImport } from '../import.js';
4
4
  import { Pool } from '../pool/index.js';
5
+ import { MessagePortRpcEndpoint } from '../rpc/endpoints/message-port.rpc-endpoint.js';
5
6
  import { Rpc } from '../rpc/index.js';
6
7
  let spawnWorker;
7
8
  if (isNode) {
@@ -35,12 +36,12 @@ export class ThreadPool {
35
36
  }
36
37
  async process(name, ...args) {
37
38
  return this.pool.use(async (entry) => {
38
- const hasRemote = entry.remotes.has(name);
39
- const remote = hasRemote ? entry.remotes.get(name) : await Rpc.connect(entry.worker, `thread-worker:${name}`);
40
- if (!hasRemote) {
39
+ if (!entry.remotes.has(name)) {
40
+ const rpcEndpoint = MessagePortRpcEndpoint.from(entry.worker);
41
+ const remote = await Rpc.connect(rpcEndpoint, `thread-worker:${name}`);
41
42
  entry.remotes.set(name, remote);
42
43
  }
43
- return remote(...args);
44
+ return entry.remotes.get(name)(...args);
44
45
  });
45
46
  }
46
47
  async spawn() {
@@ -1,3 +1,2 @@
1
- import type { RpcEndpointSource } from '../rpc/index.js';
2
1
  export type ThreadWorker = (...args: any[]) => any | Promise<any>;
3
- export declare function exposeThreadWorker<T extends ThreadWorker>(worker: T, endpointOrSource: RpcEndpointSource, name?: string): void;
2
+ export declare function exposeThreadWorker<T extends ThreadWorker>(worker: T, name?: string): void;
@@ -1,4 +1,4 @@
1
1
  import { Rpc } from '../rpc/index.js';
2
- export function exposeThreadWorker(worker, endpointOrSource, name = 'default') {
3
- Rpc.expose(worker, endpointOrSource, `thread-worker:${name}`);
2
+ export function exposeThreadWorker(worker, name = 'default') {
3
+ Rpc.expose(worker, `thread-worker:${name}`);
4
4
  }
package/utils/throw.d.ts CHANGED
@@ -1,2 +1,2 @@
1
1
  export declare function _throw(value: any): never;
2
- export declare function deferThrow(value: any): () => never;
2
+ export declare function deferThrow(valueProvider: () => any): () => never;
package/utils/throw.js CHANGED
@@ -1,9 +1,8 @@
1
1
  export function _throw(value) {
2
2
  throw value;
3
3
  }
4
- export function deferThrow(value) {
5
- // eslint-disable-next-line @typescript-eslint/no-shadow
4
+ export function deferThrow(valueProvider) {
6
5
  return function deferThrow() {
7
- throw value;
6
+ throw valueProvider();
8
7
  };
9
8
  }
package/utils/timer.d.ts CHANGED
@@ -2,6 +2,7 @@ export declare class Timer {
2
2
  private elapsedNanoseconds;
3
3
  private begin?;
4
4
  constructor(start?: boolean);
5
+ static startNew(): Timer;
5
6
  static measure(func: () => any): number;
6
7
  static measureAsync(func: () => Promise<any>): Promise<number>;
7
8
  start(): void;
package/utils/timer.js CHANGED
@@ -20,14 +20,16 @@ else {
20
20
  getDuration = (begin) => (Date.now() - begin) * nsPerMs;
21
21
  }
22
22
  export class Timer {
23
- elapsedNanoseconds;
23
+ elapsedNanoseconds = 0;
24
24
  begin;
25
25
  constructor(start = false) {
26
- this.elapsedNanoseconds = 0;
27
26
  if (start) {
28
27
  this.start();
29
28
  }
30
29
  }
30
+ static startNew() {
31
+ return new Timer(true);
32
+ }
31
33
  static measure(func) {
32
34
  const timer = new Timer(true);
33
35
  func();
@@ -1,11 +0,0 @@
1
- import type { Observable } from 'rxjs';
2
- import type { RpcMessage, RpcMessageValue } from './model.js';
3
- export declare abstract class RpcEndpoint {
4
- abstract readonly supportsTransfers: boolean;
5
- abstract readonly message$: Observable<RpcMessage>;
6
- request(message: RpcMessage, transfer?: any[]): Promise<RpcMessageValue>;
7
- respond(id: string, value: RpcMessageValue, transfer?: any[]): Promise<void>;
8
- abstract start(): void;
9
- abstract close(): void;
10
- abstract postMessage(data: any, transfer?: any[]): void | Promise<void>;
11
- }
@@ -1,18 +0,0 @@
1
- import { filter, firstValueFrom } from 'rxjs';
2
- export class RpcEndpoint {
3
- async request(message, transfer) {
4
- const response$ = this.message$.pipe(filter((incomingMessage) => (incomingMessage.type == 'response') && (incomingMessage.id == message.id)));
5
- const $response = firstValueFrom(response$);
6
- await this.postMessage(message, transfer);
7
- const response = await $response;
8
- return response.value;
9
- }
10
- async respond(id, value, transfer) {
11
- const message = {
12
- id,
13
- type: 'response',
14
- value
15
- };
16
- await this.postMessage(message, transfer);
17
- }
18
- }
File without changes
File without changes