@tstdl/base 0.90.20 → 0.90.22
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/examples/template/templates/hello-jsx.d.ts +3 -1
- package/examples/template/templates/hello-name.d.ts +3 -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 +43 -12
- package/rpc/endpoints/message-port.rpc-endpoint.js +98 -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 +149 -114
- package/serializer/serializable.d.ts +2 -2
- package/serializer/serializer.js +5 -5
- package/serializer/types.d.ts +4 -4
- package/templates/resolvers/jsx.template-resolver.d.ts +2 -2
- package/templates/resolvers/jsx.template-resolver.js +1 -1
- package/templates/template.model.d.ts +3 -1
- package/threading/thread-pool.js +5 -4
- package/threading/thread-worker.d.ts +1 -2
- package/threading/thread-worker.js +2 -2
- 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 -31
- /package/rpc/{rpc-error.d.ts → rpc.error.d.ts} +0 -0
- /package/rpc/{rpc-error.js → rpc.error.js} +0 -0
|
@@ -1,2 +1,4 @@
|
|
|
1
|
-
export declare const template: import("../../../templates/template.model.js").Template<
|
|
1
|
+
export declare const template: import("../../../templates/template.model.js").Template<{
|
|
2
|
+
template: true;
|
|
3
|
+
}, any, any>;
|
|
2
4
|
export default template;
|
|
@@ -1,2 +1,4 @@
|
|
|
1
|
-
declare const template: import("../../../templates/template.model.js").Template<
|
|
1
|
+
declare const template: import("../../../templates/template.model.js").Template<{
|
|
2
|
+
template: true;
|
|
3
|
+
}, any, any>;
|
|
2
4
|
export default template;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tstdl/base",
|
|
3
|
-
"version": "0.90.
|
|
3
|
+
"version": "0.90.22",
|
|
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,8 +120,8 @@
|
|
|
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",
|
|
@@ -150,7 +150,7 @@
|
|
|
150
150
|
"playwright": "^1.39",
|
|
151
151
|
"preact": "^10.18",
|
|
152
152
|
"preact-render-to-string": "^6.2",
|
|
153
|
-
"undici": "^5.
|
|
153
|
+
"undici": "^5.27",
|
|
154
154
|
"urlpattern-polyfill": "^9.0"
|
|
155
155
|
},
|
|
156
156
|
"peerDependenciesMeta": {
|
|
@@ -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,20 +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
|
-
|
|
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> {
|
|
10
23
|
#private;
|
|
11
|
-
private readonly source;
|
|
12
24
|
private readonly _postMessage;
|
|
13
|
-
|
|
14
|
-
readonly
|
|
15
|
-
|
|
16
|
-
|
|
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>;
|
|
17
47
|
close(): void;
|
|
18
|
-
|
|
48
|
+
forgetChannel(id: string): void;
|
|
49
|
+
private handleOpenChannelMessage;
|
|
19
50
|
}
|
|
20
51
|
export {};
|
|
@@ -1,34 +1,115 @@
|
|
|
1
|
-
import { Subject, fromEvent, map, takeUntil } 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
|
-
|
|
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);
|
|
5
8
|
#closeSubject = new Subject();
|
|
6
|
-
source;
|
|
7
9
|
_postMessage;
|
|
10
|
+
transport = { postMessage: deferThrow(() => new Error('Rpc transport not yet initialized.')) };
|
|
11
|
+
messagePortMessage$;
|
|
12
|
+
channelMessages$;
|
|
8
13
|
supportsTransfers = true;
|
|
9
|
-
|
|
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;
|
|
10
62
|
constructor(source) {
|
|
11
63
|
super();
|
|
12
|
-
this.
|
|
64
|
+
this.transport = (isBrowser && ((typeof SharedWorker == 'function') && (source instanceof SharedWorker)))
|
|
13
65
|
? source.port
|
|
14
66
|
: source;
|
|
15
|
-
this.
|
|
16
|
-
this.
|
|
17
|
-
|
|
18
|
-
|
|
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
|
+
});
|
|
19
73
|
}
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
74
|
+
static from(transport) {
|
|
75
|
+
return new MessagePortRpcEndpoint(transport);
|
|
76
|
+
}
|
|
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;
|
|
23
91
|
}
|
|
92
|
+
return channel;
|
|
93
|
+
}
|
|
94
|
+
getOrOpenChannel(id) {
|
|
95
|
+
return this.#channels.get(id) ?? this.openChannel(id);
|
|
24
96
|
}
|
|
25
97
|
close() {
|
|
26
|
-
if (this.
|
|
27
|
-
this.
|
|
98
|
+
if (this.transport instanceof MessagePort) {
|
|
99
|
+
this.transport.close();
|
|
28
100
|
}
|
|
29
101
|
this.#closeSubject.next();
|
|
30
102
|
}
|
|
31
|
-
|
|
32
|
-
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);
|
|
33
114
|
}
|
|
34
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,44 +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 { getRandomString } from '../utils/random.js';
|
|
9
10
|
import { _throw, deferThrow } from '../utils/throw.js';
|
|
10
|
-
import { assert, isDefined } from '../utils/type-guards.js';
|
|
11
|
-
import {
|
|
12
|
-
import { createRpcMessage } from './model.js';
|
|
13
|
-
import { RpcEndpoint } from './rpc-endpoint.js';
|
|
14
|
-
import { RpcError, RpcRemoteError } from './rpc-error.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
|
-
const
|
|
20
|
-
const message = createRpcMessage('release-proxy', { proxyId: data.id });
|
|
21
|
-
await data.endpoint.postMessage(message);
|
|
22
|
-
if (responsibleEndpoints.has(data.endpoint)) {
|
|
23
|
-
data.endpoint.close();
|
|
24
|
-
}
|
|
25
|
-
});
|
|
18
|
+
const exposings = new Map();
|
|
19
|
+
const channelFinalizationRegistry = new FinalizationRegistry((data) => data.channel.close());
|
|
26
20
|
class RpcProxy {
|
|
27
21
|
}
|
|
28
22
|
// eslint-disable-next-line @typescript-eslint/no-redeclare, @typescript-eslint/naming-convention
|
|
29
23
|
export const Rpc = {
|
|
30
|
-
|
|
31
|
-
const
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
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.');
|
|
36
51
|
return parseRpcMessageValue(response, endpoint);
|
|
37
52
|
},
|
|
38
|
-
expose(object,
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
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)));
|
|
42
64
|
},
|
|
43
65
|
/**
|
|
44
66
|
* mark object for proxy forward
|
|
@@ -54,46 +76,57 @@ export const Rpc = {
|
|
|
54
76
|
return object;
|
|
55
77
|
},
|
|
56
78
|
transfer(object, transfer) {
|
|
57
|
-
markedTransfers.
|
|
79
|
+
const existingTransfers = markedTransfers.get(object);
|
|
80
|
+
markedTransfers.set(object, isDefined(existingTransfers) ? [...existingTransfers, ...transfer] : transfer);
|
|
58
81
|
return object;
|
|
59
82
|
},
|
|
60
83
|
serialize(object, options) {
|
|
61
84
|
serializationTargets.set(object, options);
|
|
62
85
|
return object;
|
|
63
86
|
},
|
|
87
|
+
adapt(object, adapter, root) {
|
|
88
|
+
adapterTargets.set(object, adapter);
|
|
89
|
+
if (isDefined(root)) {
|
|
90
|
+
Rpc.serialize(root);
|
|
91
|
+
}
|
|
92
|
+
return object;
|
|
93
|
+
},
|
|
64
94
|
isProxied(object) {
|
|
65
95
|
return proxyTargets.has(object);
|
|
66
96
|
}
|
|
67
97
|
};
|
|
68
|
-
function createProxy(
|
|
98
|
+
function createProxy(channel, path = []) {
|
|
69
99
|
// eslint-disable-next-line prefer-const
|
|
70
100
|
let proxy;
|
|
101
|
+
const { endpoint } = channel;
|
|
71
102
|
const handlers = {
|
|
72
103
|
apply(_target, _this, args) {
|
|
73
104
|
const postMessageData = getPostMessageDataFromArray(args, endpoint);
|
|
74
|
-
|
|
75
|
-
|
|
105
|
+
return channel
|
|
106
|
+
.request({ type: 'apply', path, args: postMessageData.value }, postMessageData.transfer)
|
|
107
|
+
.then((responseValue) => parseRpcMessageValue(responseValue, endpoint));
|
|
76
108
|
},
|
|
77
109
|
construct(_target, args) {
|
|
78
110
|
const postMessageData = getPostMessageDataFromArray(args, endpoint);
|
|
79
|
-
|
|
80
|
-
|
|
111
|
+
return channel
|
|
112
|
+
.request({ type: 'construct', path, args: postMessageData.value }, postMessageData.transfer)
|
|
113
|
+
.then((responseValue) => parseRpcMessageValue(responseValue, endpoint));
|
|
81
114
|
},
|
|
82
115
|
get(_target, property) {
|
|
83
116
|
if (property == 'then') {
|
|
84
117
|
if (path.length == 0) {
|
|
85
118
|
return { then: () => proxy };
|
|
86
119
|
}
|
|
87
|
-
const
|
|
88
|
-
const resultValue = endpoint.request(message).then((responseValue) => parseRpcMessageValue(responseValue, endpoint));
|
|
120
|
+
const resultValue = channel.request({ type: 'get', path }).then((responseValue) => parseRpcMessageValue(responseValue, endpoint));
|
|
89
121
|
return resultValue.then.bind(resultValue);
|
|
90
122
|
}
|
|
91
|
-
return createProxy(
|
|
123
|
+
return createProxy(channel, [...path, property]);
|
|
92
124
|
},
|
|
93
125
|
set(_target, property, value) {
|
|
94
126
|
const postMessageData = getPostMessageData(value, endpoint);
|
|
95
|
-
|
|
96
|
-
|
|
127
|
+
return channel
|
|
128
|
+
.request({ type: 'set', path: [...path, property], value: postMessageData.value }, postMessageData.transfer)
|
|
129
|
+
.then((responseValue) => parseRpcMessageValue(responseValue, endpoint));
|
|
97
130
|
},
|
|
98
131
|
getPrototypeOf() {
|
|
99
132
|
return null;
|
|
@@ -101,119 +134,97 @@ function createProxy(endpoint, id, path = []) {
|
|
|
101
134
|
};
|
|
102
135
|
for (const method of reflectMethods) {
|
|
103
136
|
if (!hasOwnProperty(handlers, method)) {
|
|
104
|
-
handlers[method] = deferThrow(() => new
|
|
137
|
+
handlers[method] = deferThrow(() => new NotSupportedError(`${method} not supported on rpc proxies.`));
|
|
105
138
|
}
|
|
106
139
|
}
|
|
107
|
-
|
|
108
|
-
if (path.length == 0) {
|
|
109
|
-
proxyFinalizationRegistry.register(proxy, { id, endpoint });
|
|
110
|
-
}
|
|
111
|
-
return proxy;
|
|
140
|
+
return new Proxy(RpcProxy, handlers);
|
|
112
141
|
}
|
|
113
|
-
function
|
|
114
|
-
endpoint
|
|
115
|
-
|
|
116
|
-
.subscribe(async (message) => {
|
|
117
|
-
try {
|
|
118
|
-
const [proxy, transfer] = createProxyValue(object, endpoint);
|
|
119
|
-
await endpoint.respond(message.id, proxy, transfer);
|
|
120
|
-
}
|
|
121
|
-
catch (error) {
|
|
122
|
-
await endpoint.respond(message.id, { type: 'throw', error });
|
|
123
|
-
}
|
|
124
|
-
});
|
|
125
|
-
}
|
|
126
|
-
function exposeObject(object, endpoint, proxyId) {
|
|
127
|
-
const releaseSubject = new Subject();
|
|
128
|
-
endpoint.message$
|
|
129
|
-
.pipe(takeUntil(releaseSubject), filter((message) => (message.proxyId == proxyId)))
|
|
130
|
-
.subscribe(async (message) => {
|
|
142
|
+
function exposeObject(object, channel) {
|
|
143
|
+
const { endpoint } = channel;
|
|
144
|
+
channel.request$.subscribe(async ({ id: messageId, data: message }) => {
|
|
131
145
|
try {
|
|
132
146
|
switch (message.type) {
|
|
133
147
|
case 'get': {
|
|
134
148
|
const { value } = deref(object, message.path);
|
|
135
149
|
const postMessageData = getPostMessageData(await value, endpoint);
|
|
136
|
-
await
|
|
150
|
+
await channel.respond(messageId, postMessageData.value, postMessageData.transfer);
|
|
137
151
|
break;
|
|
138
152
|
}
|
|
139
153
|
case 'set': {
|
|
140
154
|
const { value } = deref(object, message.path, true);
|
|
141
|
-
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));
|
|
142
156
|
const postMessageData = getPostMessageData(result, endpoint);
|
|
143
|
-
await
|
|
157
|
+
await channel.respond(messageId, postMessageData.value, postMessageData.transfer);
|
|
144
158
|
break;
|
|
145
159
|
}
|
|
146
160
|
case 'apply': {
|
|
147
161
|
const { parent, value } = deref(object, message.path);
|
|
148
|
-
const result = await Reflect.apply(value, parent, parseRpcMessageValues(message.args, endpoint));
|
|
162
|
+
const result = await Reflect.apply(value, parent, parseRpcMessageValues(message.args, channel.endpoint));
|
|
149
163
|
const postMessageData = getPostMessageData(result, endpoint);
|
|
150
|
-
await
|
|
164
|
+
await channel.respond(messageId, postMessageData.value, postMessageData.transfer);
|
|
151
165
|
break;
|
|
152
166
|
}
|
|
153
167
|
case 'construct': {
|
|
154
168
|
const { value } = deref(object, message.path);
|
|
155
|
-
const result = Reflect.construct(value, parseRpcMessageValues(message.args, endpoint));
|
|
156
|
-
const
|
|
157
|
-
await
|
|
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 'release-proxy': {
|
|
161
|
-
releaseSubject.next();
|
|
162
|
-
if (responsibleEndpoints.has(endpoint)) {
|
|
163
|
-
endpoint.close();
|
|
164
|
-
}
|
|
165
|
-
break;
|
|
166
|
-
}
|
|
167
|
-
case 'response':
|
|
168
|
-
case 'connect':
|
|
169
|
-
// we do not handle these here
|
|
170
|
-
break;
|
|
171
174
|
default:
|
|
172
|
-
throw new NotImplementedError(`Unsupported message type ${message.type}
|
|
175
|
+
throw new NotImplementedError(`Unsupported message type ${message.type}.`);
|
|
173
176
|
}
|
|
174
177
|
}
|
|
175
178
|
catch (error) {
|
|
176
|
-
await
|
|
179
|
+
await channel.respond(messageId, { type: 'throw', error });
|
|
177
180
|
}
|
|
178
181
|
});
|
|
179
182
|
}
|
|
180
183
|
function getPostMessageDataFromArray(values, endpoint) {
|
|
181
|
-
const
|
|
184
|
+
const messageValues = values.map((value) => getRpcMessageValue(value, endpoint));
|
|
182
185
|
const transfer = values.flatMap((value) => markedTransfers.get(value) ?? []);
|
|
183
|
-
|
|
184
|
-
const additionalTransfers = mappedValues.flatMap((mappedValue) => mappedValue[1]);
|
|
185
|
-
return { value: messageValues, transfer: [...transfer, ...additionalTransfers] };
|
|
186
|
+
return { value: messageValues, transfer };
|
|
186
187
|
}
|
|
187
188
|
function getPostMessageData(value, endpoint) {
|
|
188
|
-
const
|
|
189
|
-
const transfer = markedTransfers.get(value)
|
|
190
|
-
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);
|
|
191
197
|
}
|
|
192
198
|
function getRpcMessageValue(value, endpoint) {
|
|
199
|
+
const adapter = adapterTargets.get(value);
|
|
200
|
+
if (isDefined(adapter)) {
|
|
201
|
+
return createAdapterValue(value, adapter, endpoint);
|
|
202
|
+
}
|
|
193
203
|
if (proxyTargets.has(value)) {
|
|
194
204
|
return createProxyValue(value, endpoint);
|
|
195
205
|
}
|
|
196
206
|
if (serializationTargets.has(value)) {
|
|
197
207
|
const options = serializationTargets.get(value);
|
|
198
|
-
const
|
|
199
|
-
const
|
|
200
|
-
|
|
201
|
-
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 };
|
|
202
211
|
}
|
|
203
|
-
return
|
|
212
|
+
return { type: 'raw', value };
|
|
204
213
|
}
|
|
205
214
|
function createProxyValue(value, endpoint) {
|
|
206
|
-
const
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
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
|
+
}
|
|
214
226
|
}
|
|
215
|
-
|
|
216
|
-
return [{ type: 'proxy', id }, []];
|
|
227
|
+
return { type: 'adapter', adapter: `RpcAdapter:${adapter.name}`, channel: channel.id, data };
|
|
217
228
|
}
|
|
218
229
|
function parseRpcMessageValues(values, endpoint) {
|
|
219
230
|
return values.map((value) => parseRpcMessageValue(value, endpoint));
|
|
@@ -228,15 +239,23 @@ function parseRpcMessageValue(value, endpoint) {
|
|
|
228
239
|
}
|
|
229
240
|
case 'throw': {
|
|
230
241
|
const remoteError = new RpcRemoteError(value.error);
|
|
231
|
-
throw new RpcError(
|
|
242
|
+
throw new RpcError(`Received error from remote: ${remoteError.message}`, remoteError);
|
|
232
243
|
}
|
|
233
244
|
case 'proxy': {
|
|
234
|
-
const
|
|
235
|
-
|
|
236
|
-
|
|
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.`);
|
|
237
255
|
}
|
|
238
|
-
|
|
239
|
-
|
|
256
|
+
const adaptedValue = adapter.adaptTarget(value.data, channel);
|
|
257
|
+
channelFinalizationRegistry.register(adaptedValue, { channel });
|
|
258
|
+
return adaptedValue;
|
|
240
259
|
}
|
|
241
260
|
default:
|
|
242
261
|
throw new Error(`Type ${value.type} not supported`);
|
|
@@ -254,15 +273,31 @@ function deref(object, path, skipLast = false) {
|
|
|
254
273
|
}
|
|
255
274
|
return { parent, value };
|
|
256
275
|
}
|
|
257
|
-
registerSerializer(RpcProxy, 'RpcProxy', () => _throw(new
|
|
258
|
-
function getRpcProxySerializationReplacer(endpoint
|
|
276
|
+
registerSerializer(RpcProxy, 'RpcProxy', () => _throw(new NotSupportedError('Serialization not supported for rpc proxy.')), (data, _, { rpcEndpoint }) => parseRpcMessageValue({ type: 'proxy', ...data }, rpcEndpoint));
|
|
277
|
+
function getRpcProxySerializationReplacer(endpoint) {
|
|
259
278
|
function rpcProxySerializationReplacer(value) {
|
|
260
279
|
if (!proxyTargets.has(value)) {
|
|
261
280
|
return value;
|
|
262
281
|
}
|
|
263
|
-
const
|
|
264
|
-
|
|
265
|
-
return { '<RpcProxy>': { id: proxy.id, port: proxy.port } };
|
|
282
|
+
const proxy = createProxyValue(value, endpoint);
|
|
283
|
+
return { '<RpcProxy>': valueOfType({ channel: proxy.channel }) };
|
|
266
284
|
}
|
|
267
285
|
return rpcProxySerializationReplacer;
|
|
268
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,4 +1,4 @@
|
|
|
1
|
-
import type { Record, TypedOmit } from '../../types.js';
|
|
1
|
+
import type { PartialProperty, Record, TypedOmit } from '../../types.js';
|
|
2
2
|
import type { ComponentClass, FunctionComponent } from 'preact';
|
|
3
3
|
import { TemplateField } from '../template.model.js';
|
|
4
4
|
import type { TemplateRenderer, TemplateRendererOptions, TemplateRendererString } from '../template.renderer.js';
|
|
@@ -12,4 +12,4 @@ export declare class JsxTemplateResolver extends TemplateResolver<JsxTemplateFie
|
|
|
12
12
|
canHandle(resolver: string): boolean;
|
|
13
13
|
resolve(field: JsxTemplateField): JsxTemplate;
|
|
14
14
|
}
|
|
15
|
-
export declare function jsxTemplateField<Renderer extends TemplateRenderer, Context extends Record = any>(field: TypedOmit<JsxTemplateField<Renderer[TemplateRendererString], Renderer[TemplateRendererOptions], Context>, 'resolver'>): JsxTemplateField<Renderer[TemplateRendererString], Renderer[TemplateRendererOptions], Context>;
|
|
15
|
+
export declare function jsxTemplateField<Renderer extends TemplateRenderer, Context extends Record = any>(field: PartialProperty<TypedOmit<JsxTemplateField<Renderer[TemplateRendererString], Renderer[TemplateRendererOptions], Context>, 'resolver'>, 'renderer'>): JsxTemplateField<Renderer[TemplateRendererString], Renderer[TemplateRendererOptions], Context>;
|
|
@@ -19,5 +19,7 @@ export declare abstract class Template<Fields extends Record<string, boolean> =
|
|
|
19
19
|
fields: TemplateFields<Fields, string, string, any, Context>;
|
|
20
20
|
options?: TemplateOptions;
|
|
21
21
|
}
|
|
22
|
-
export declare function simpleTemplate(name: string, template: TemplateField): Template
|
|
22
|
+
export declare function simpleTemplate(name: string, template: TemplateField): Template<{
|
|
23
|
+
template: true;
|
|
24
|
+
}>;
|
|
23
25
|
export {};
|
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/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,31 +0,0 @@
|
|
|
1
|
-
import { filter } from 'rxjs';
|
|
2
|
-
export class RpcEndpoint {
|
|
3
|
-
async request(message, transfer) {
|
|
4
|
-
const $response = getResponsePromise(this.message$, message.id);
|
|
5
|
-
await this.postMessage(message, transfer);
|
|
6
|
-
const response = await $response;
|
|
7
|
-
return response.value;
|
|
8
|
-
}
|
|
9
|
-
async respond(id, value, transfer) {
|
|
10
|
-
const message = {
|
|
11
|
-
id,
|
|
12
|
-
type: 'response',
|
|
13
|
-
value
|
|
14
|
-
};
|
|
15
|
-
await this.postMessage(message, transfer);
|
|
16
|
-
}
|
|
17
|
-
}
|
|
18
|
-
async function getResponsePromise(message$, messageId) {
|
|
19
|
-
const response$ = message$.pipe(filter((incomingMessage) => (incomingMessage.type == 'response') && (incomingMessage.id == messageId)));
|
|
20
|
-
return new Promise((resolve, reject) => {
|
|
21
|
-
const subscription = response$.subscribe({
|
|
22
|
-
next(value) {
|
|
23
|
-
resolve(value);
|
|
24
|
-
subscription.unsubscribe();
|
|
25
|
-
},
|
|
26
|
-
complete() {
|
|
27
|
-
reject(new Error('RpcEndpoint was closed while waiting for response.'));
|
|
28
|
-
}
|
|
29
|
-
});
|
|
30
|
-
});
|
|
31
|
-
}
|
|
File without changes
|
|
File without changes
|