@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.
- package/api/server/gateway.js +1 -1
- package/package.json +5 -5
- package/rpc/adapters/index.d.ts +1 -0
- package/rpc/adapters/index.js +1 -0
- package/rpc/adapters/readable-stream.adapter.d.ts +25 -0
- package/rpc/adapters/readable-stream.adapter.js +67 -0
- package/rpc/endpoints/message-port.rpc-endpoint.d.ts +44 -12
- package/rpc/endpoints/message-port.rpc-endpoint.js +100 -17
- package/rpc/index.d.ts +4 -2
- package/rpc/index.js +4 -2
- package/rpc/model.d.ts +19 -27
- package/rpc/model.js +1 -8
- package/rpc/rpc.adapter.d.ts +19 -0
- package/rpc/rpc.adapter.js +1 -0
- package/rpc/rpc.d.ts +10 -8
- package/rpc/rpc.endpoint.d.ts +45 -0
- package/rpc/rpc.endpoint.js +46 -0
- package/rpc/rpc.js +153 -109
- package/serializer/serializable.d.ts +2 -2
- package/serializer/serializer.js +5 -5
- package/serializer/types.d.ts +4 -4
- package/threading/thread-pool.d.ts +3 -2
- package/threading/thread-pool.js +5 -4
- package/threading/thread-worker.d.ts +1 -2
- package/threading/thread-worker.js +2 -2
- package/utils/throw.d.ts +1 -1
- package/utils/throw.js +2 -3
- package/utils/timer.d.ts +1 -0
- package/utils/timer.js +4 -2
- package/rpc/rpc-endpoint.d.ts +0 -11
- package/rpc/rpc-endpoint.js +0 -18
- /package/rpc/{rpc-error.d.ts → rpc.error.d.ts} +0 -0
- /package/rpc/{rpc-error.js → rpc.error.js} +0 -0
package/api/server/gateway.js
CHANGED
|
@@ -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.
|
|
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.
|
|
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.
|
|
124
|
-
"@typescript-eslint/parser": "6.
|
|
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.
|
|
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 {
|
|
5
|
-
import { RpcEndpoint } from '../rpc
|
|
6
|
-
type
|
|
7
|
-
type
|
|
8
|
-
export type
|
|
9
|
-
|
|
10
|
-
|
|
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
|
-
|
|
13
|
-
readonly
|
|
14
|
-
|
|
15
|
-
|
|
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
|
-
|
|
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 {
|
|
4
|
-
|
|
5
|
-
|
|
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
|
-
|
|
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.
|
|
64
|
+
this.transport = (isBrowser && ((typeof SharedWorker == 'function') && (source instanceof SharedWorker)))
|
|
12
65
|
? source.port
|
|
13
66
|
: source;
|
|
14
|
-
this.
|
|
15
|
-
this.
|
|
16
|
-
|
|
17
|
-
|
|
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
|
-
|
|
20
|
-
|
|
21
|
-
|
|
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.
|
|
26
|
-
this.
|
|
98
|
+
if (this.transport instanceof MessagePort) {
|
|
99
|
+
this.transport.close();
|
|
27
100
|
}
|
|
101
|
+
this.#closeSubject.next();
|
|
28
102
|
}
|
|
29
|
-
|
|
30
|
-
this.
|
|
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
|
|
16
|
+
export type RpcControlRequestMessage = RpcConnectMessage;
|
|
17
|
+
export type RpcProxyApplyMessage = RpcMessageBase<'apply'> & {
|
|
22
18
|
path: PropertyKey[];
|
|
23
|
-
args:
|
|
19
|
+
args: RpcValue[];
|
|
24
20
|
};
|
|
25
|
-
export type
|
|
21
|
+
export type RpcProxyConstructMessage = RpcMessageBase<'construct'> & {
|
|
26
22
|
path: PropertyKey[];
|
|
27
|
-
args:
|
|
23
|
+
args: RpcValue[];
|
|
28
24
|
};
|
|
29
|
-
export type
|
|
25
|
+
export type RpcProxyGetMessage = RpcMessageBase<'get'> & {
|
|
30
26
|
path: PropertyKey[];
|
|
31
27
|
};
|
|
32
|
-
export type
|
|
28
|
+
export type RpcProxySetMessage = RpcMessageBase<'set'> & {
|
|
33
29
|
path: PropertyKey[];
|
|
34
|
-
value:
|
|
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
|
|
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
|
-
|
|
53
|
-
|
|
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
|
|
56
|
+
export type RpcValue = RpcMessageRawValue | RpcMessageSerializedValue | RpcMessageProxyValue | RpcMessageAdapterValue | RpcMessageThrowValue;
|
|
60
57
|
export type RpcPostMessageArrayData = {
|
|
61
|
-
value:
|
|
58
|
+
value: RpcValue[];
|
|
62
59
|
transfer?: any[];
|
|
63
60
|
};
|
|
64
61
|
export type RpcPostMessageData = {
|
|
65
|
-
value:
|
|
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
|
@@ -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 {
|
|
5
|
-
|
|
3
|
+
import type { RpcAdapter } from './rpc.adapter.js';
|
|
4
|
+
import type { RpcEndpoint } from './rpc.endpoint.js';
|
|
6
5
|
export declare const Rpc: {
|
|
7
|
-
|
|
8
|
-
|
|
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<
|
|
16
|
-
transfer<
|
|
17
|
-
serialize<
|
|
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 {
|
|
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 {
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
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
|
|
19
|
-
|
|
20
|
-
|
|
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
|
-
|
|
25
|
-
const
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
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,
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
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.
|
|
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(
|
|
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
|
-
|
|
71
|
-
|
|
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
|
-
|
|
76
|
-
|
|
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
|
|
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(
|
|
123
|
+
return createProxy(channel, [...path, property]);
|
|
88
124
|
},
|
|
89
125
|
set(_target, property, value) {
|
|
90
126
|
const postMessageData = getPostMessageData(value, endpoint);
|
|
91
|
-
|
|
92
|
-
|
|
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
|
-
|
|
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,
|
|
123
|
-
const
|
|
124
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
153
|
-
await
|
|
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
|
|
179
|
+
await channel.respond(messageId, { type: 'throw', error });
|
|
170
180
|
}
|
|
171
181
|
});
|
|
172
182
|
}
|
|
173
183
|
function getPostMessageDataFromArray(values, endpoint) {
|
|
174
|
-
const
|
|
184
|
+
const messageValues = values.map((value) => getRpcMessageValue(value, endpoint));
|
|
175
185
|
const transfer = values.flatMap((value) => markedTransfers.get(value) ?? []);
|
|
176
|
-
|
|
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
|
|
182
|
-
const transfer = markedTransfers.get(value)
|
|
183
|
-
return { value: messageValue, transfer
|
|
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
|
|
192
|
-
const
|
|
193
|
-
|
|
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
|
|
212
|
+
return { type: 'raw', value };
|
|
197
213
|
}
|
|
198
214
|
function createProxyValue(value, endpoint) {
|
|
199
|
-
const
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
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
|
-
|
|
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(
|
|
242
|
+
throw new RpcError(`Received error from remote: ${remoteError.message}`, remoteError);
|
|
224
243
|
}
|
|
225
244
|
case 'proxy': {
|
|
226
|
-
const
|
|
227
|
-
|
|
228
|
-
|
|
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
|
-
|
|
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
|
|
255
|
-
|
|
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];
|
package/serializer/serializer.js
CHANGED
|
@@ -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);
|
package/serializer/types.d.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import type { AbstractConstructor, JsonPrimitive, Nested, Record
|
|
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?:
|
|
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:
|
|
19
|
+
process<T extends ThreadWorker>(name: LiteralUnion<'default', string>, ...args: Parameters<T>): Promise<ReturnType<T>>;
|
|
19
20
|
private spawn;
|
|
20
21
|
}
|
package/threading/thread-pool.js
CHANGED
|
@@ -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
|
-
|
|
39
|
-
|
|
40
|
-
|
|
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
|
|
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,
|
|
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,
|
|
3
|
-
Rpc.expose(worker,
|
|
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(
|
|
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(
|
|
5
|
-
// eslint-disable-next-line @typescript-eslint/no-shadow
|
|
4
|
+
export function deferThrow(valueProvider) {
|
|
6
5
|
return function deferThrow() {
|
|
7
|
-
throw
|
|
6
|
+
throw valueProvider();
|
|
8
7
|
};
|
|
9
8
|
}
|
package/utils/timer.d.ts
CHANGED
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();
|
package/rpc/rpc-endpoint.d.ts
DELETED
|
@@ -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
|
-
}
|
package/rpc/rpc-endpoint.js
DELETED
|
@@ -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
|