@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.
@@ -1,2 +1,4 @@
1
- export declare const template: import("../../../templates/template.model.js").Template<import("../../../types.js").Record<string, boolean>, any, any>;
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<import("../../../types.js").Record<string, boolean>, any, any>;
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.20",
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.5"
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.8",
124
- "@typescript-eslint/parser": "6.8",
123
+ "@typescript-eslint/eslint-plugin": "6.9",
124
+ "@typescript-eslint/parser": "6.9",
125
125
  "concurrently": "8.2",
126
126
  "esbuild": "0.19",
127
127
  "eslint": "8.52",
@@ -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.26",
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 { RpcMessage } from '../model.js';
5
- import { RpcEndpoint } from '../rpc-endpoint.js';
6
- type BrowserSource = Worker | MessagePort | Window | SharedWorker;
7
- type NodeSource = NodeWorkerThreads.MessagePort | NodeWorkerThreads.Worker;
8
- export type MessagePortRpcEndpointSource = BrowserSource | NodeSource;
9
- export declare class MessagePortRpcEndpoint extends RpcEndpoint {
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
- readonly supportsTransfers: boolean;
14
- readonly message$: Observable<RpcMessage>;
15
- constructor(source: MessagePortRpcEndpointSource);
16
- start(): void;
25
+ private transport;
26
+ readonly messagePortMessage$: Observable<MessagePortMessage<RpcChannelMessage<Data, Req, Res>>>;
27
+ channelMessages$: Observable<RpcChannelMessage<Data, Req, Res>>;
28
+ readonly supportsTransfers = true;
29
+ readonly endpoint: MessagePortRpcEndpoint;
30
+ sequence: number;
31
+ lastSequence: number;
32
+ constructor(id: string, transport: MessagePortRpcTransport | undefined, endpoint: MessagePortRpcEndpoint);
33
+ setTransport(transport: MessagePortRpcTransport): void;
34
+ postPortMessage(message: MessagePortMessage<RpcChannelMessage<Data, Req, Res>>, transfer?: any[]): void;
35
+ close(): void;
36
+ postMessage(message: RpcChannelMessage<Data, Req, Res>, transfer?: any[] | undefined): void | Promise<void>;
37
+ }
38
+ export declare class MessagePortRpcEndpoint extends RpcEndpoint {
39
+ #private;
40
+ private readonly transport;
41
+ private readonly mainChannel;
42
+ constructor(source: MessagePortRpcTransport);
43
+ static from(transport: MessagePortRpcTransport): MessagePortRpcEndpoint;
44
+ openChannel<Data, Req, Res>(id?: string): MessagePortRpcChannel<Data, Req, Res>;
45
+ getChannel<Data, Req, Res>(id: string): MessagePortRpcChannel<Data, Req, Res>;
46
+ getOrOpenChannel<Data, Req, Res>(id: string): RpcChannel<Data, Req, Res>;
17
47
  close(): void;
18
- postMessage(data: any, transfer?: any[] | undefined): void;
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 { RpcEndpoint } from '../rpc-endpoint.js';
4
- export class MessagePortRpcEndpoint extends RpcEndpoint {
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
- message$;
14
+ endpoint;
15
+ sequence = 1;
16
+ lastSequence = 0;
17
+ constructor(id, transport, endpoint) {
18
+ super(id);
19
+ this.endpoint = endpoint;
20
+ if (isDefined(transport)) {
21
+ this.setTransport(transport);
22
+ }
23
+ this.messagePortMessage$ = this.#transportSubject.pipe(startWith(transport), filter(isDefined), switchMap((newTransport) => fromEvent(newTransport, 'message')), takeUntil(this.#closeSubject), map((message) => ((message instanceof MessageEvent) ? message.data : message)), shareReplay({ bufferSize: 0, refCount: true }));
24
+ this.channelMessages$ = this.messagePortMessage$.pipe(filter((message) => message.type == 'data'), map((message) => message.data));
25
+ this._postMessage = isBrowser
26
+ ? (data, transfer) => this.transport.postMessage(data, { transfer })
27
+ : (data, transfer) => this.transport.postMessage(data, transfer);
28
+ if (transport instanceof MessagePort) {
29
+ transport.start();
30
+ }
31
+ this.messagePortMessage$.subscribe((message) => {
32
+ if (message.type == 'close-channel') {
33
+ this.close();
34
+ }
35
+ });
36
+ }
37
+ setTransport(transport) {
38
+ this.transport = transport;
39
+ this.#transportSubject.next(transport);
40
+ this.#transportSubject.complete();
41
+ }
42
+ postPortMessage(message, transfer) {
43
+ this._postMessage(message, transfer);
44
+ }
45
+ close() {
46
+ this.postPortMessage({ seq: this.sequence++, type: 'close-channel' });
47
+ if (this.transport instanceof MessagePort) {
48
+ this.transport.close();
49
+ }
50
+ this.#closeSubject.next();
51
+ this.endpoint.forgetChannel(this.id);
52
+ }
53
+ postMessage(message, transfer) {
54
+ this.postPortMessage({ seq: this.sequence++, type: 'data', data: message }, transfer);
55
+ }
56
+ }
57
+ export class MessagePortRpcEndpoint extends RpcEndpoint {
58
+ #closeSubject = new Subject();
59
+ #channels = new Map();
60
+ transport;
61
+ mainChannel;
10
62
  constructor(source) {
11
63
  super();
12
- this.source = (isBrowser && ((typeof SharedWorker == 'function') && (source instanceof SharedWorker)))
64
+ this.transport = (isBrowser && ((typeof SharedWorker == 'function') && (source instanceof SharedWorker)))
13
65
  ? source.port
14
66
  : source;
15
- this.message$ = fromEvent(this.source, 'message').pipe(takeUntil(this.#closeSubject), map((message) => ((message instanceof MessageEvent) ? { ...message.data, metadata: { source: message } } : message)));
16
- this._postMessage = isBrowser
17
- ? (data, transfer) => this.source.postMessage(data, { transfer })
18
- : (data, transfer) => this.source.postMessage(data, transfer);
67
+ this.mainChannel = new MessagePortRpcChannel('main', this.transport, this);
68
+ this.mainChannel.messagePortMessage$.subscribe((message) => {
69
+ if (message.type == 'open-channel') {
70
+ this.handleOpenChannelMessage(message);
71
+ }
72
+ });
19
73
  }
20
- start() {
21
- if (this.source instanceof MessagePort) {
22
- this.source.start();
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.source instanceof MessagePort) {
27
- this.source.close();
98
+ if (this.transport instanceof MessagePort) {
99
+ this.transport.close();
28
100
  }
29
101
  this.#closeSubject.next();
30
102
  }
31
- postMessage(data, transfer) {
32
- this._postMessage(data, transfer);
103
+ forgetChannel(id) {
104
+ this.#channels.delete(id);
105
+ }
106
+ handleOpenChannelMessage(message) {
107
+ const { id, port } = message;
108
+ if (this.#channels.has(id)) {
109
+ this.#channels.get(id).setTransport(port);
110
+ return;
111
+ }
112
+ const channel = new MessagePortRpcChannel(id, port, this);
113
+ this.#channels.set(id, channel);
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 RpcApplyMessage = RpcMessageWithProxyIdBase<'apply'> & {
16
+ export type RpcControlRequestMessage = RpcConnectMessage;
17
+ export type RpcProxyApplyMessage = RpcMessageBase<'apply'> & {
22
18
  path: PropertyKey[];
23
- args: RpcMessageValue[];
19
+ args: RpcValue[];
24
20
  };
25
- export type RpcConstructMessage = RpcMessageWithProxyIdBase<'construct'> & {
21
+ export type RpcProxyConstructMessage = RpcMessageBase<'construct'> & {
26
22
  path: PropertyKey[];
27
- args: RpcMessageValue[];
23
+ args: RpcValue[];
28
24
  };
29
- export type RpcGetMessage = RpcMessageWithProxyIdBase<'get'> & {
25
+ export type RpcProxyGetMessage = RpcMessageBase<'get'> & {
30
26
  path: PropertyKey[];
31
27
  };
32
- export type RpcSetMessage = RpcMessageWithProxyIdBase<'set'> & {
28
+ export type RpcProxySetMessage = RpcMessageBase<'set'> & {
33
29
  path: PropertyKey[];
34
- value: RpcMessageValue;
35
- };
36
- export type RpcReleaseProxyMessage = RpcMessageWithProxyIdBase<'release-proxy'>;
37
- export type RpcResponseMessage = RpcMessageBase<'response'> & {
38
- value: RpcMessageValue;
30
+ value: RpcValue;
39
31
  };
40
- export type RpcMessage = RpcConnectMessage | RpcApplyMessage | RpcConstructMessage | RpcGetMessage | RpcSetMessage | RpcReleaseProxyMessage | RpcResponseMessage;
32
+ export type RpcProxyRequestMessage = RpcProxyApplyMessage | RpcProxyConstructMessage | RpcProxyGetMessage | RpcProxySetMessage;
41
33
  export type RpcMessageRawValue = {
42
34
  type: 'raw';
43
35
  value: any;
@@ -49,24 +41,24 @@ export type RpcMessageSerializedValue = {
49
41
  };
50
42
  export type RpcMessageProxyValue = {
51
43
  type: 'proxy';
52
- id: string;
53
- port?: MessagePort;
44
+ channel: string;
45
+ };
46
+ export type RpcMessageAdapterValue = {
47
+ type: 'adapter';
48
+ adapter: string;
49
+ channel: string;
50
+ data: any;
54
51
  };
55
52
  export type RpcMessageThrowValue = {
56
53
  type: 'throw';
57
54
  error: unknown;
58
55
  };
59
- export type RpcMessageValue = RpcMessageRawValue | RpcMessageSerializedValue | RpcMessageProxyValue | RpcMessageThrowValue;
56
+ export type RpcValue = RpcMessageRawValue | RpcMessageSerializedValue | RpcMessageProxyValue | RpcMessageAdapterValue | RpcMessageThrowValue;
60
57
  export type RpcPostMessageArrayData = {
61
- value: RpcMessageValue[];
58
+ value: RpcValue[];
62
59
  transfer?: any[];
63
60
  };
64
61
  export type RpcPostMessageData = {
65
- value: RpcMessageValue;
62
+ value: RpcValue;
66
63
  transfer?: any[];
67
64
  };
68
- export declare function createRpcMessage<Type extends RpcMessage['type']>(type: Type, message: Omit<Extract<RpcMessage, {
69
- type: Type;
70
- }>, 'id' | 'type'>): Extract<RpcMessage, {
71
- type: Type;
72
- }>;
package/rpc/model.js CHANGED
@@ -1,8 +1 @@
1
- import { getRandomString } from '../utils/random.js';
2
- export function createRpcMessage(type, message) {
3
- return {
4
- id: getRandomString(24),
5
- type,
6
- ...message
7
- };
8
- }
1
+ export {};
@@ -0,0 +1,19 @@
1
+ import type { NonPrimitive } from '../serializer/types.js';
2
+ import type { RpcChannel } from './rpc.endpoint.js';
3
+ export type AdaptSourceResult<Data> = Data extends void ? (void | {
4
+ data?: undefined;
5
+ transfer?: any[];
6
+ }) : {
7
+ data: Data;
8
+ transfer?: any[];
9
+ };
10
+ export type RpcAdapterNonPrimitive = NonPrimitive<string, RpcAdapterNonPrimitiveData>;
11
+ export type RpcAdapterNonPrimitiveData = {
12
+ channel: string;
13
+ data: any;
14
+ };
15
+ export interface RpcAdapter<T extends object = any, Data = any, ChannelData = any, Req = any, Res = any> {
16
+ name: string;
17
+ adaptSource(value: T, channel: RpcChannel<ChannelData, Req, Res>): AdaptSourceResult<Data>;
18
+ adaptTarget(data: Data, channel: RpcChannel<ChannelData, Req, Res>): T;
19
+ }
@@ -0,0 +1 @@
1
+ export {};
package/rpc/rpc.d.ts CHANGED
@@ -1,19 +1,21 @@
1
1
  import type { SerializationOptions } from '../serializer/index.js';
2
- import type { MessagePortRpcEndpointSource } from './endpoints/message-port.rpc-endpoint.js';
3
2
  import type { RpcRemote, RpcRemoteInput } from './model.js';
4
- import { RpcEndpoint } from './rpc-endpoint.js';
5
- export type RpcEndpointSource = RpcEndpoint | MessagePortRpcEndpointSource;
3
+ import type { RpcAdapter } from './rpc.adapter.js';
4
+ import type { RpcEndpoint } from './rpc.endpoint.js';
6
5
  export declare const Rpc: {
7
- connect<T extends RpcRemoteInput>(endpointOrSource: RpcEndpointSource, name?: string): Promise<RpcRemote<T>>;
8
- expose(object: RpcRemoteInput, endpointOrSource: RpcEndpointSource, name?: string): void;
6
+ listen(endpoint: RpcEndpoint): void;
7
+ connect<T extends RpcRemoteInput>(endpoint: RpcEndpoint, name?: string): Promise<RpcRemote<T>>;
8
+ expose(object: RpcRemoteInput, name?: string): void;
9
+ registerAdapter<T_1 extends object, Data>(adapter: RpcAdapter<T_1, Data, any, any, any>): void;
9
10
  /**
10
11
  * mark object for proxy forward
11
12
  * @param object object to forward as proxy
12
13
  * @param root if object is a child of the actual passed value (to function calls, returns or whatever), this must be set to to mark the parent for serialization (required for children proxies)
13
14
  * @returns
14
15
  */
15
- proxy<T_1 extends object>(object: T_1, root?: object): T_1;
16
- transfer<T_2 extends object>(object: T_2, transfer: any[]): T_2;
17
- serialize<T_3 extends object>(object: T_3, options?: SerializationOptions): T_3;
16
+ proxy<T_2 extends object>(object: T_2, root?: object): T_2;
17
+ transfer<T_3 extends object>(object: T_3, transfer: any[]): T_3;
18
+ serialize<T_4 extends object>(object: T_4, options?: SerializationOptions): T_4;
19
+ adapt<T_5 extends object>(object: T_5, adapter: RpcAdapter<T_5, any, any, any, any>, root?: object): T_5;
18
20
  isProxied(object: object): boolean;
19
21
  };
@@ -0,0 +1,45 @@
1
+ import { type Observable } from 'rxjs';
2
+ export type RpcChannelDataMessage<T> = {
3
+ type: 'data';
4
+ data: T;
5
+ };
6
+ export type RpcChannelRequestMessage<T> = {
7
+ type: 'request';
8
+ id: string;
9
+ data: T;
10
+ };
11
+ export type RpcChannelResponseMessage<T> = {
12
+ type: 'response';
13
+ id: string;
14
+ data: T;
15
+ };
16
+ export type RpcChannelMessage<Data, Req, Res> = RpcChannelDataMessage<Data> | RpcChannelRequestMessage<Req> | RpcChannelResponseMessage<Res>;
17
+ export type RpcChannelRequest<T> = {
18
+ id: string;
19
+ data: T;
20
+ };
21
+ export declare abstract class RpcEndpoint {
22
+ abstract openChannel<Data, Req, Res>(id?: string): RpcChannel<Data, Req, Res>;
23
+ abstract getChannel<Data, Req, Res>(id: string): RpcChannel<Data, Req, Res>;
24
+ abstract getOrOpenChannel<Data, Req, Res>(id: string): RpcChannel<Data, Req, Res>;
25
+ abstract close(): void;
26
+ }
27
+ /**
28
+ * @template Data - type of data-messages
29
+ * @template Req - type of request-messages
30
+ * @template Res - type of response-messages
31
+ */
32
+ export declare abstract class RpcChannel<Data = unknown, Req = unknown, Res = unknown> {
33
+ readonly id: string;
34
+ readonly message$: Observable<Data>;
35
+ readonly request$: Observable<RpcChannelRequest<Req>>;
36
+ protected abstract readonly channelMessages$: Observable<RpcChannelMessage<Data, Req, Res>>;
37
+ abstract readonly supportsTransfers: boolean;
38
+ abstract readonly endpoint: RpcEndpoint;
39
+ constructor(id: string);
40
+ request<U extends Res>(data: Req, transfer?: any[]): Promise<U>;
41
+ respond(id: string, data: Res, transfer?: any[]): Promise<void>;
42
+ send(data: Data, transfer?: any[]): Promise<void>;
43
+ abstract close(): void;
44
+ protected abstract postMessage(message: RpcChannelMessage<Data, Req, Res>, transfer?: any[]): void | Promise<void>;
45
+ }
@@ -0,0 +1,46 @@
1
+ import { defer, filter, map } from 'rxjs';
2
+ export class RpcEndpoint {
3
+ }
4
+ /**
5
+ * @template Data - type of data-messages
6
+ * @template Req - type of request-messages
7
+ * @template Res - type of response-messages
8
+ */
9
+ export class RpcChannel {
10
+ id;
11
+ message$;
12
+ request$;
13
+ constructor(id) {
14
+ this.id = id;
15
+ this.message$ = defer(() => this.channelMessages$.pipe(filter((message) => message.type == 'data'), map((message) => message.data)));
16
+ this.request$ = defer(() => this.channelMessages$.pipe(filter((message) => message.type == 'request'), map((message) => message)));
17
+ }
18
+ async request(data, transfer) {
19
+ const id = crypto.randomUUID();
20
+ const $response = getResponsePromise(this.channelMessages$, id);
21
+ await this.postMessage({ type: 'request', id, data }, transfer);
22
+ const response = await $response;
23
+ return response;
24
+ }
25
+ async respond(id, data, transfer) {
26
+ await this.postMessage({ type: 'response', id, data }, transfer);
27
+ }
28
+ async send(data, transfer) {
29
+ await this.postMessage({ type: 'data', data }, transfer);
30
+ }
31
+ }
32
+ async function getResponsePromise(channelMessage$, requestId) {
33
+ return new Promise((resolve, reject) => {
34
+ const subscription = channelMessage$.subscribe({
35
+ next(message) {
36
+ if ((message.type == 'response') && (message.id == requestId)) {
37
+ resolve(message.data);
38
+ subscription.unsubscribe();
39
+ }
40
+ },
41
+ complete() {
42
+ reject(new Error('RpcEndpoint was closed while waiting for response.'));
43
+ }
44
+ });
45
+ });
46
+ }
package/rpc/rpc.js CHANGED
@@ -1,44 +1,66 @@
1
1
  /* eslint-disable @typescript-eslint/no-unsafe-return */
2
2
  /* eslint-disable @typescript-eslint/no-unsafe-argument */
3
- import { filter, Subject, takeUntil } from 'rxjs';
3
+ import { NotFoundError } from '../errors/not-found.error.js';
4
4
  import { NotImplementedError } from '../errors/not-implemented.error.js';
5
+ import { NotSupportedError } from '../errors/not-supported.error.js';
5
6
  import { deserialize, registerSerializer, serialize } from '../serializer/index.js';
7
+ import { valueOfType } from '../utils/helpers.js';
6
8
  import { hasOwnProperty } from '../utils/object/object.js';
7
9
  import { reflectMethods } from '../utils/proxy.js';
8
- import { getRandomString } from '../utils/random.js';
9
10
  import { _throw, deferThrow } from '../utils/throw.js';
10
- import { assert, isDefined } from '../utils/type-guards.js';
11
- import { MessagePortRpcEndpoint } from './endpoints/message-port.rpc-endpoint.js';
12
- import { createRpcMessage } from './model.js';
13
- import { RpcEndpoint } from './rpc-endpoint.js';
14
- import { RpcError, RpcRemoteError } from './rpc-error.js';
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 responsibleEndpoints = new WeakSet();
19
- const proxyFinalizationRegistry = new FinalizationRegistry(async (data) => {
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
- async connect(endpointOrSource, name = 'default') {
31
- const endpoint = (endpointOrSource instanceof RpcEndpoint) ? endpointOrSource : new MessagePortRpcEndpoint(endpointOrSource);
32
- endpoint.start();
33
- const connectMessage = createRpcMessage('connect', { name });
34
- const response = await endpoint.request(connectMessage);
35
- assert(response.type == 'proxy', 'Rpc connect must result in proxy.');
24
+ listen(endpoint) {
25
+ const controlChannel = endpoint.getChannel('control');
26
+ controlChannel.request$.subscribe(async ({ id: messageId, data: message }) => {
27
+ switch (message.type) {
28
+ case 'connect': {
29
+ try {
30
+ const exposing = exposings.get(message.name);
31
+ if (isUndefined(exposing)) {
32
+ throw new NotFoundError(`Could not connect to "${message.name}" as nothing with that name is exposed.`);
33
+ }
34
+ const value = getAdapterOrProxy(exposing, endpoint);
35
+ await controlChannel.respond(messageId, value);
36
+ }
37
+ catch (error) {
38
+ await controlChannel.respond(messageId, { type: 'throw', error });
39
+ }
40
+ break;
41
+ }
42
+ default:
43
+ throw new NotSupportedError(`Message type ${message.type} not supported in listen.`);
44
+ }
45
+ });
46
+ },
47
+ async connect(endpoint, name = 'default') {
48
+ const channel = endpoint.getOrOpenChannel('control');
49
+ const response = await channel.request({ type: 'connect', name });
50
+ assert((response.type == 'proxy') || (response.type == 'adapter') || (response.type == 'throw'), 'Rpc connect must result in proxy, adapter or throw.');
36
51
  return parseRpcMessageValue(response, endpoint);
37
52
  },
38
- expose(object, endpointOrSource, name = 'default') {
39
- const endpoint = (endpointOrSource instanceof RpcEndpoint) ? endpointOrSource : new MessagePortRpcEndpoint(endpointOrSource);
40
- endpoint.start();
41
- exposeConnectable(object, endpoint, name);
53
+ expose(object, name = 'default') {
54
+ exposings.set(name, object);
55
+ },
56
+ registerAdapter(adapter) {
57
+ const adapterName = `RpcAdapter:${adapter.name}`;
58
+ adapters.set(adapterName, adapter);
59
+ const klass = {
60
+ [adapterName]: class {
61
+ }
62
+ }[adapterName];
63
+ registerSerializer(klass, klass.name, () => _throw(new NotSupportedError('Serialization not supported for rpc adapter.')), ({ channel, data }, _, { rpcEndpoint }) => adapter.adaptTarget(data, rpcEndpoint.getChannel(channel)));
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.set(object, transfer);
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(endpoint, id, path = []) {
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
- const message = createRpcMessage('apply', { proxyId: id, path, args: postMessageData.value });
75
- return endpoint.request(message, postMessageData.transfer).then((responseValue) => parseRpcMessageValue(responseValue, endpoint));
105
+ return channel
106
+ .request({ type: 'apply', path, args: postMessageData.value }, postMessageData.transfer)
107
+ .then((responseValue) => parseRpcMessageValue(responseValue, endpoint));
76
108
  },
77
109
  construct(_target, args) {
78
110
  const postMessageData = getPostMessageDataFromArray(args, endpoint);
79
- const message = createRpcMessage('construct', { proxyId: id, path, args: postMessageData.value });
80
- return endpoint.request(message, postMessageData.transfer).then((responseValue) => parseRpcMessageValue(responseValue, endpoint));
111
+ return channel
112
+ .request({ type: 'construct', path, args: postMessageData.value }, postMessageData.transfer)
113
+ .then((responseValue) => parseRpcMessageValue(responseValue, endpoint));
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 message = createRpcMessage('get', { proxyId: id, path });
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(endpoint, id, [...path, property]);
123
+ return createProxy(channel, [...path, property]);
92
124
  },
93
125
  set(_target, property, value) {
94
126
  const postMessageData = getPostMessageData(value, endpoint);
95
- const message = createRpcMessage('set', { proxyId: id, path: [...path, property], value: postMessageData.value });
96
- return endpoint.request(message, postMessageData.transfer).then((responseValue) => parseRpcMessageValue(responseValue, endpoint));
127
+ return channel
128
+ .request({ type: 'set', path: [...path, property], value: postMessageData.value }, postMessageData.transfer)
129
+ .then((responseValue) => parseRpcMessageValue(responseValue, endpoint));
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 Error(`${method} not supported on rpc proxies.`));
137
+ handlers[method] = deferThrow(() => new NotSupportedError(`${method} not supported on rpc proxies.`));
105
138
  }
106
139
  }
107
- proxy = new Proxy(RpcProxy, handlers);
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 exposeConnectable(object, endpoint, name) {
114
- endpoint.message$
115
- .pipe(filter((message) => message.type == 'connect'), filter((message) => message.name == name))
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 endpoint.respond(message.id, postMessageData.value, postMessageData.transfer);
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 endpoint.respond(message.id, postMessageData.value, postMessageData.transfer);
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 endpoint.respond(message.id, postMessageData.value, postMessageData.transfer);
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 [proxy, transfer] = createProxyValue(result, endpoint);
157
- await endpoint.respond(message.id, proxy, transfer);
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 endpoint.respond(message.id, { type: 'throw', error });
179
+ await channel.respond(messageId, { type: 'throw', error });
177
180
  }
178
181
  });
179
182
  }
180
183
  function getPostMessageDataFromArray(values, endpoint) {
181
- const mappedValues = values.map((value) => getRpcMessageValue(value, endpoint));
184
+ const messageValues = values.map((value) => getRpcMessageValue(value, endpoint));
182
185
  const transfer = values.flatMap((value) => markedTransfers.get(value) ?? []);
183
- const messageValues = mappedValues.map((mappedValue) => mappedValue[0]);
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 [messageValue, additionalTransfers] = getRpcMessageValue(value, endpoint);
189
- const transfer = markedTransfers.get(value) ?? [];
190
- return { value: messageValue, transfer: [...transfer, ...additionalTransfers] };
189
+ const messageValue = getRpcMessageValue(value, endpoint);
190
+ const transfer = markedTransfers.get(value);
191
+ return { value: messageValue, transfer };
192
+ }
193
+ function getAdapterOrProxy(exposing, endpoint) {
194
+ return adapterTargets.has(exposing)
195
+ ? createAdapterValue(exposing, adapterTargets.get(exposing), endpoint)
196
+ : createProxyValue(exposing, endpoint);
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 transfer = [];
199
- const replacer = getRpcProxySerializationReplacer(endpoint, transfer);
200
- const serialized = serialize(value, { ...options, replacers: [replacer, ...options?.replacers ?? []] });
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 [{ type: 'raw', value }, []];
212
+ return { type: 'raw', value };
204
213
  }
205
214
  function createProxyValue(value, endpoint) {
206
- const id = getRandomString(24);
207
- if (endpoint.supportsTransfers) {
208
- const { port1, port2 } = new MessageChannel();
209
- const newEndpoint = new MessagePortRpcEndpoint(port1);
210
- responsibleEndpoints.add(newEndpoint);
211
- newEndpoint.start();
212
- exposeObject(value, newEndpoint, id);
213
- return [{ type: 'proxy', id, port: port2 }, [port2]];
215
+ const channel = endpoint.openChannel();
216
+ exposeObject(value, channel);
217
+ return { type: 'proxy', channel: channel.id };
218
+ }
219
+ function createAdapterValue(value, adapter, endpoint) {
220
+ const channel = endpoint.openChannel();
221
+ const { data, transfer } = adapter.adaptSource(value, channel) ?? {}; // eslint-disable-line @typescript-eslint/no-unnecessary-condition
222
+ if (isDefined(transfer)) {
223
+ for (const transferValue of transfer) {
224
+ Rpc.transfer(value, transferValue);
225
+ }
214
226
  }
215
- exposeObject(value, endpoint, id);
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('Received error from remote.', remoteError);
242
+ throw new RpcError(`Received error from remote: ${remoteError.message}`, remoteError);
232
243
  }
233
244
  case 'proxy': {
234
- const proxyEndpoint = (isDefined(value.port)) ? new MessagePortRpcEndpoint(value.port) : endpoint;
235
- if (isDefined(value.port)) {
236
- responsibleEndpoints.add(proxyEndpoint);
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
- proxyEndpoint.start();
239
- return createProxy(proxyEndpoint, value.id);
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 Error('Not supported.')), (data, _, { rpcEndpoint }) => parseRpcMessageValue({ type: 'proxy', ...data }, rpcEndpoint));
258
- 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) {
259
278
  function rpcProxySerializationReplacer(value) {
260
279
  if (!proxyTargets.has(value)) {
261
280
  return value;
262
281
  }
263
- const [proxy, proxyTransfer] = createProxyValue(value, endpoint);
264
- transfer.push(...proxyTransfer);
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];
@@ -19,7 +19,7 @@ export function serialize(_value, options = {}, references = new Map(), queue =
19
19
  let value = _value;
20
20
  if (isDefined(options.replacers)) {
21
21
  for (const replacer of options.replacers) {
22
- value = replacer(value);
22
+ value = replacer(value, options.data);
23
23
  }
24
24
  }
25
25
  const type = typeof value;
@@ -31,7 +31,7 @@ export function serialize(_value, options = {}, references = new Map(), queue =
31
31
  }
32
32
  if ((path == '$') && isDefined(options.context)) {
33
33
  for (const entry of objectEntries(options.context)) {
34
- references.set(entry[1], `$['__context__']['${entry[0]}']`);
34
+ references.set(entry[1], `$['__context__']['${String(entry[0])}']`);
35
35
  }
36
36
  }
37
37
  const reference = references.get(value);
@@ -97,7 +97,7 @@ export function serialize(_value, options = {}, references = new Map(), queue =
97
97
  const typeString = getTypeString(serializableType);
98
98
  const nonPrimitive = { [typeString]: null };
99
99
  queue.add(() => {
100
- const data = registration.serializer.call(value, value, options.data ?? {});
100
+ const data = registration.serializer.call(value, value, options.data);
101
101
  (nonPrimitive[typeString] = (registration.serializeData != false) ? serialize(data, options, references, queue, `${path}['${typeString}']`) : data);
102
102
  });
103
103
  result = nonPrimitive;
@@ -130,7 +130,7 @@ function _deserialize(serialized, context, path, depth) {
130
130
  if (type == 'object') {
131
131
  if ((depth == 0) && isDefined(context.options.context)) {
132
132
  for (const entry of objectEntries(context.options.context)) {
133
- context.references.set(`$['__context__']['${entry[0]}']`, entry[1]);
133
+ context.references.set(`$['__context__']['${String(entry[0])}']`, entry[1]);
134
134
  }
135
135
  }
136
136
  const entries = objectEntries(serialized);
@@ -180,7 +180,7 @@ function _deserialize(serialized, context, path, depth) {
180
180
  context.addToDeserializeQueue(() => {
181
181
  const deserializedData = _deserialize(nonPrimitiveData, context, `${path}['<${nonPrimitiveType}>']`, depth + 1);
182
182
  context.addToDeserializeQueue(() => {
183
- const deserialized = registration.deserializer(deserializedData, context.tryAddToDerefQueue, context.options.data ?? {});
183
+ const deserialized = registration.deserializer(deserializedData, context.tryAddToDerefQueue, context.options.data);
184
184
  ForwardRef.setRef(forwardRef, deserialized);
185
185
  }, depth);
186
186
  }, depth);
@@ -1,8 +1,8 @@
1
- import type { AbstractConstructor, JsonPrimitive, Nested, Record, StringMap } from '../types.js';
1
+ import type { AbstractConstructor, JsonPrimitive, Nested, Record } from '../types.js';
2
2
  declare const serializedSymbol: unique symbol;
3
3
  declare const stringSerializedSymbol: unique symbol;
4
4
  declare const decycledSymbol: unique symbol;
5
- export type SerializationReplacer = (value: any) => any;
5
+ export type SerializationReplacer = (value: any, data?: Record) => any;
6
6
  export type TypeField<T extends string> = `<${T}>`;
7
7
  export type NonPrimitive<TypeName extends string = string, Data = unknown> = Record<TypeField<TypeName>, Data>;
8
8
  export type RawNonPrimitive = NonPrimitive<typeof rawNonPrimitiveType, any>;
@@ -45,14 +45,14 @@ export type SerializationOptions = {
45
45
  *
46
46
  * Only first level of object is referenced. For example for `{ foo: { bar: 'baz' } }` there will be one context entry having { bar: 'baz' }
47
47
  */
48
- context?: StringMap<object | Function | string | symbol | bigint>;
48
+ context?: Record;
49
49
  /**
50
50
  * Disables dereferencing of ForwardRefs. Only useful for debugging (when implementing custom serializers) and curiosity
51
51
  * @default false
52
52
  */
53
53
  doNotDereferenceForwardRefs?: boolean;
54
54
  /**
55
- * Data to be used on de/serialization handlers
55
+ * Data to be used on de/serialization handlers and replacers
56
56
  */
57
57
  data?: Record;
58
58
  };
@@ -1,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>;
@@ -35,5 +35,5 @@ JsxTemplateResolver = __decorate([
35
35
  ], JsxTemplateResolver);
36
36
  export { JsxTemplateResolver };
37
37
  export function jsxTemplateField(field) {
38
- return { resolver: 'string', ...field };
38
+ return { resolver: 'string', renderer: 'jsx', ...field };
39
39
  }
@@ -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 {};
@@ -2,6 +2,7 @@ import { disposeAsync } from '../disposable/index.js';
2
2
  import { isNode } from '../environment.js';
3
3
  import { dynamicImport } from '../import.js';
4
4
  import { Pool } from '../pool/index.js';
5
+ import { MessagePortRpcEndpoint } from '../rpc/endpoints/message-port.rpc-endpoint.js';
5
6
  import { Rpc } from '../rpc/index.js';
6
7
  let spawnWorker;
7
8
  if (isNode) {
@@ -35,12 +36,12 @@ export class ThreadPool {
35
36
  }
36
37
  async process(name, ...args) {
37
38
  return this.pool.use(async (entry) => {
38
- const hasRemote = entry.remotes.has(name);
39
- const remote = hasRemote ? entry.remotes.get(name) : await Rpc.connect(entry.worker, `thread-worker:${name}`);
40
- if (!hasRemote) {
39
+ if (!entry.remotes.has(name)) {
40
+ const rpcEndpoint = MessagePortRpcEndpoint.from(entry.worker);
41
+ const remote = await Rpc.connect(rpcEndpoint, `thread-worker:${name}`);
41
42
  entry.remotes.set(name, remote);
42
43
  }
43
- return remote(...args);
44
+ return entry.remotes.get(name)(...args);
44
45
  });
45
46
  }
46
47
  async spawn() {
@@ -1,3 +1,2 @@
1
- import type { RpcEndpointSource } from '../rpc/index.js';
2
1
  export type ThreadWorker = (...args: any[]) => any | Promise<any>;
3
- export declare function exposeThreadWorker<T extends ThreadWorker>(worker: T, endpointOrSource: RpcEndpointSource, name?: string): void;
2
+ export declare function exposeThreadWorker<T extends ThreadWorker>(worker: T, name?: string): void;
@@ -1,4 +1,4 @@
1
1
  import { Rpc } from '../rpc/index.js';
2
- export function exposeThreadWorker(worker, endpointOrSource, name = 'default') {
3
- Rpc.expose(worker, endpointOrSource, `thread-worker:${name}`);
2
+ export function exposeThreadWorker(worker, name = 'default') {
3
+ Rpc.expose(worker, `thread-worker:${name}`);
4
4
  }
package/utils/timer.d.ts CHANGED
@@ -2,6 +2,7 @@ export declare class Timer {
2
2
  private elapsedNanoseconds;
3
3
  private begin?;
4
4
  constructor(start?: boolean);
5
+ static startNew(): Timer;
5
6
  static measure(func: () => any): number;
6
7
  static measureAsync(func: () => Promise<any>): Promise<number>;
7
8
  start(): void;
package/utils/timer.js CHANGED
@@ -20,14 +20,16 @@ else {
20
20
  getDuration = (begin) => (Date.now() - begin) * nsPerMs;
21
21
  }
22
22
  export class Timer {
23
- elapsedNanoseconds;
23
+ elapsedNanoseconds = 0;
24
24
  begin;
25
25
  constructor(start = false) {
26
- this.elapsedNanoseconds = 0;
27
26
  if (start) {
28
27
  this.start();
29
28
  }
30
29
  }
30
+ static startNew() {
31
+ return new Timer(true);
32
+ }
31
33
  static measure(func) {
32
34
  const timer = new Timer(true);
33
35
  func();
@@ -1,11 +0,0 @@
1
- import type { Observable } from 'rxjs';
2
- import type { RpcMessage, RpcMessageValue } from './model.js';
3
- export declare abstract class RpcEndpoint {
4
- abstract readonly supportsTransfers: boolean;
5
- abstract readonly message$: Observable<RpcMessage>;
6
- request(message: RpcMessage, transfer?: any[]): Promise<RpcMessageValue>;
7
- respond(id: string, value: RpcMessageValue, transfer?: any[]): Promise<void>;
8
- abstract start(): void;
9
- abstract close(): void;
10
- abstract postMessage(data: any, transfer?: any[]): void | Promise<void>;
11
- }
@@ -1,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