@tstdl/base 0.90.18 → 0.90.20

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -111,7 +111,7 @@ let ApiGateway = class ApiGateway {
111
111
  };
112
112
  this.apis.set(resource, resourceApis);
113
113
  }
114
- const endpointImplementation = implementation[name]?.bind(implementation) ?? deferThrow(new NotImplementedError(`Endpoint ${name} for resource ${resource} not implemented.`));
114
+ const endpointImplementation = implementation[name]?.bind(implementation) ?? deferThrow(() => new NotImplementedError(`Endpoint ${name} for resource ${resource} not implemented.`));
115
115
  for (const method of methods) {
116
116
  resourceApis.endpoints.set(method, { definition: endpointDefinition, implementation: endpointImplementation });
117
117
  }
@@ -88,7 +88,7 @@ export declare class CancellationSignal implements PromiseLike<void>, Subscribab
88
88
  export declare class CancellationToken extends CancellationSignal {
89
89
  #private;
90
90
  /** Signal for this token */
91
- readonly signal: CancellationSignal;
91
+ get signal(): CancellationSignal;
92
92
  /**
93
93
  * @param initialState which state to initialze this token to
94
94
  * - `false`: unset
@@ -86,8 +86,11 @@ export class CancellationSignal {
86
86
  }
87
87
  export class CancellationToken extends CancellationSignal {
88
88
  #stateSubject;
89
+ #signal;
89
90
  /** Signal for this token */
90
- signal;
91
+ get signal() {
92
+ return (this.#signal ??= new CancellationSignal(this.#stateSubject));
93
+ }
91
94
  /**
92
95
  * @param initialState which state to initialze this token to
93
96
  * - `false`: unset
@@ -97,8 +100,7 @@ export class CancellationToken extends CancellationSignal {
97
100
  constructor(initialState = false) {
98
101
  const stateSubject = new BehaviorSubject(initialState);
99
102
  super(stateSubject);
100
- this.#stateSubject = new BehaviorSubject(initialState);
101
- this.signal = new CancellationSignal(this.#stateSubject);
103
+ this.#stateSubject = stateSubject;
102
104
  }
103
105
  static from(source, config) {
104
106
  const source$ = (source instanceof AbortSignal) ? fromEvent(source, 'abort', () => true)
@@ -126,7 +128,7 @@ export class CancellationToken extends CancellationSignal {
126
128
  * Become a child of the provided parent. Events from the parent are propagated to this token. Events from this token are *not* propagated to the parent.
127
129
  */
128
130
  inherit(parent, config) {
129
- const state$ = ((parent instanceof CancellationToken) ? parent.signal : parent).state$;
131
+ const { state$ } = (parent instanceof CancellationToken) ? parent.signal : parent;
130
132
  CancellationToken.connect(state$, this, config);
131
133
  return this;
132
134
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tstdl/base",
3
- "version": "0.90.18",
3
+ "version": "0.90.20",
4
4
  "author": "Patrick Hein",
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -124,9 +124,9 @@
124
124
  "@typescript-eslint/parser": "6.8",
125
125
  "concurrently": "8.2",
126
126
  "esbuild": "0.19",
127
- "eslint": "8.51",
127
+ "eslint": "8.52",
128
128
  "eslint-import-resolver-typescript": "3.6",
129
- "eslint-plugin-import": "2.28",
129
+ "eslint-plugin-import": "2.29",
130
130
  "tsc-alias": "1.8",
131
131
  "typedoc": "0.25",
132
132
  "typedoc-plugin-missing-exports": "2.1",
@@ -7,6 +7,7 @@ type BrowserSource = Worker | MessagePort | Window | SharedWorker;
7
7
  type NodeSource = NodeWorkerThreads.MessagePort | NodeWorkerThreads.Worker;
8
8
  export type MessagePortRpcEndpointSource = BrowserSource | NodeSource;
9
9
  export declare class MessagePortRpcEndpoint extends RpcEndpoint {
10
+ #private;
10
11
  private readonly source;
11
12
  private readonly _postMessage;
12
13
  readonly supportsTransfers: boolean;
@@ -1,7 +1,8 @@
1
- import { fromEvent, map } from 'rxjs';
1
+ import { Subject, fromEvent, map, takeUntil } from 'rxjs';
2
2
  import { isBrowser } from '../../environment.js';
3
3
  import { RpcEndpoint } from '../rpc-endpoint.js';
4
4
  export class MessagePortRpcEndpoint extends RpcEndpoint {
5
+ #closeSubject = new Subject();
5
6
  source;
6
7
  _postMessage;
7
8
  supportsTransfers = true;
@@ -11,7 +12,7 @@ export class MessagePortRpcEndpoint extends RpcEndpoint {
11
12
  this.source = (isBrowser && ((typeof SharedWorker == 'function') && (source instanceof SharedWorker)))
12
13
  ? source.port
13
14
  : source;
14
- this.message$ = fromEvent(this.source, 'message').pipe(map((message) => ((message instanceof MessageEvent) ? { ...message.data, metadata: { source: message } } : message)));
15
+ this.message$ = fromEvent(this.source, 'message').pipe(takeUntil(this.#closeSubject), map((message) => ((message instanceof MessageEvent) ? { ...message.data, metadata: { source: message } } : message)));
15
16
  this._postMessage = isBrowser
16
17
  ? (data, transfer) => this.source.postMessage(data, { transfer })
17
18
  : (data, transfer) => this.source.postMessage(data, transfer);
@@ -25,6 +26,7 @@ export class MessagePortRpcEndpoint extends RpcEndpoint {
25
26
  if (this.source instanceof MessagePort) {
26
27
  this.source.close();
27
28
  }
29
+ this.#closeSubject.next();
28
30
  }
29
31
  postMessage(data, transfer) {
30
32
  this._postMessage(data, transfer);
@@ -1,8 +1,7 @@
1
- import { filter, firstValueFrom } from 'rxjs';
1
+ import { filter } from 'rxjs';
2
2
  export class RpcEndpoint {
3
3
  async request(message, transfer) {
4
- const response$ = this.message$.pipe(filter((incomingMessage) => (incomingMessage.type == 'response') && (incomingMessage.id == message.id)));
5
- const $response = firstValueFrom(response$);
4
+ const $response = getResponsePromise(this.message$, message.id);
6
5
  await this.postMessage(message, transfer);
7
6
  const response = await $response;
8
7
  return response.value;
@@ -16,3 +15,17 @@ export class RpcEndpoint {
16
15
  await this.postMessage(message, transfer);
17
16
  }
18
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
+ }
package/rpc/rpc.js CHANGED
@@ -1,13 +1,13 @@
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
4
  import { NotImplementedError } from '../errors/not-implemented.error.js';
4
5
  import { deserialize, registerSerializer, serialize } from '../serializer/index.js';
5
6
  import { hasOwnProperty } from '../utils/object/object.js';
6
7
  import { reflectMethods } from '../utils/proxy.js';
7
8
  import { getRandomString } from '../utils/random.js';
8
- import { _throw } from '../utils/throw.js';
9
+ import { _throw, deferThrow } from '../utils/throw.js';
9
10
  import { assert, isDefined } from '../utils/type-guards.js';
10
- import { filter, Subject, takeUntil } from 'rxjs';
11
11
  import { MessagePortRpcEndpoint } from './endpoints/message-port.rpc-endpoint.js';
12
12
  import { createRpcMessage } from './model.js';
13
13
  import { RpcEndpoint } from './rpc-endpoint.js';
@@ -15,10 +15,16 @@ import { RpcError, RpcRemoteError } from './rpc-error.js';
15
15
  const markedTransfers = new WeakMap();
16
16
  const serializationTargets = new WeakMap();
17
17
  const proxyTargets = new WeakSet();
18
+ const responsibleEndpoints = new WeakSet();
18
19
  const proxyFinalizationRegistry = new FinalizationRegistry(async (data) => {
19
20
  const message = createRpcMessage('release-proxy', { proxyId: data.id });
20
21
  await data.endpoint.postMessage(message);
22
+ if (responsibleEndpoints.has(data.endpoint)) {
23
+ data.endpoint.close();
24
+ }
21
25
  });
26
+ class RpcProxy {
27
+ }
22
28
  // eslint-disable-next-line @typescript-eslint/no-redeclare, @typescript-eslint/naming-convention
23
29
  export const Rpc = {
24
30
  async connect(endpointOrSource, name = 'default') {
@@ -60,8 +66,6 @@ export const Rpc = {
60
66
  }
61
67
  };
62
68
  function createProxy(endpoint, id, path = []) {
63
- class RpcProxy {
64
- }
65
69
  // eslint-disable-next-line prefer-const
66
70
  let proxy;
67
71
  const handlers = {
@@ -97,13 +101,13 @@ function createProxy(endpoint, id, path = []) {
97
101
  };
98
102
  for (const method of reflectMethods) {
99
103
  if (!hasOwnProperty(handlers, method)) {
100
- handlers[method] = () => {
101
- throw new Error(`${method} not supported on rpc proxies.`);
102
- };
104
+ handlers[method] = deferThrow(() => new Error(`${method} not supported on rpc proxies.`));
103
105
  }
104
106
  }
105
107
  proxy = new Proxy(RpcProxy, handlers);
106
- proxyFinalizationRegistry.register(proxy, { id, endpoint });
108
+ if (path.length == 0) {
109
+ proxyFinalizationRegistry.register(proxy, { id, endpoint });
110
+ }
107
111
  return proxy;
108
112
  }
109
113
  function exposeConnectable(object, endpoint, name) {
@@ -120,9 +124,9 @@ function exposeConnectable(object, endpoint, name) {
120
124
  });
121
125
  }
122
126
  function exposeObject(object, endpoint, proxyId) {
123
- const abortSubject = new Subject();
127
+ const releaseSubject = new Subject();
124
128
  endpoint.message$
125
- .pipe(takeUntil(abortSubject), filter((message) => (message.proxyId == proxyId)))
129
+ .pipe(takeUntil(releaseSubject), filter((message) => (message.proxyId == proxyId)))
126
130
  .subscribe(async (message) => {
127
131
  try {
128
132
  switch (message.type) {
@@ -154,7 +158,10 @@ function exposeObject(object, endpoint, proxyId) {
154
158
  break;
155
159
  }
156
160
  case 'release-proxy': {
157
- abortSubject.next();
161
+ releaseSubject.next();
162
+ if (responsibleEndpoints.has(endpoint)) {
163
+ endpoint.close();
164
+ }
158
165
  break;
159
166
  }
160
167
  case 'response':
@@ -200,6 +207,7 @@ function createProxyValue(value, endpoint) {
200
207
  if (endpoint.supportsTransfers) {
201
208
  const { port1, port2 } = new MessageChannel();
202
209
  const newEndpoint = new MessagePortRpcEndpoint(port1);
210
+ responsibleEndpoints.add(newEndpoint);
203
211
  newEndpoint.start();
204
212
  exposeObject(value, newEndpoint, id);
205
213
  return [{ type: 'proxy', id, port: port2 }, [port2]];
@@ -224,6 +232,9 @@ function parseRpcMessageValue(value, endpoint) {
224
232
  }
225
233
  case 'proxy': {
226
234
  const proxyEndpoint = (isDefined(value.port)) ? new MessagePortRpcEndpoint(value.port) : endpoint;
235
+ if (isDefined(value.port)) {
236
+ responsibleEndpoints.add(proxyEndpoint);
237
+ }
227
238
  proxyEndpoint.start();
228
239
  return createProxy(proxyEndpoint, value.id);
229
240
  }
@@ -243,8 +254,6 @@ function deref(object, path, skipLast = false) {
243
254
  }
244
255
  return { parent, value };
245
256
  }
246
- class RpcProxy {
247
- }
248
257
  registerSerializer(RpcProxy, 'RpcProxy', () => _throw(new Error('Not supported.')), (data, _, { rpcEndpoint }) => parseRpcMessageValue({ type: 'proxy', ...data }, rpcEndpoint));
249
258
  function getRpcProxySerializationReplacer(endpoint, transfer) {
250
259
  function rpcProxySerializationReplacer(value) {
@@ -1,8 +1,9 @@
1
1
  /// <reference types="node" resolution-mode="require"/>
2
+ import type * as NodeWorkerThreads from 'node:worker_threads';
3
+ import type { LiteralUnion } from 'type-fest';
2
4
  import type { AsyncDisposable } from '../disposable/index.js';
3
5
  import { disposeAsync } from '../disposable/index.js';
4
6
  import type { Logger } from '../logger/index.js';
5
- import type * as NodeWorkerThreads from 'node:worker_threads';
6
7
  import type { ThreadWorker } from './thread-worker.js';
7
8
  export type ThreadOptions = (WorkerOptions | NodeWorkerThreads.WorkerOptions) & {
8
9
  threadCount?: number;
@@ -15,6 +16,6 @@ export declare class ThreadPool implements AsyncDisposable {
15
16
  dispose(): Promise<void>;
16
17
  [disposeAsync](): Promise<void>;
17
18
  getProcessor<T extends ThreadWorker>(name?: string): (...args: Parameters<T>) => Promise<ReturnType<T>>;
18
- process<T extends ThreadWorker>(name: string, ...args: Parameters<T>): Promise<ReturnType<T>>;
19
+ process<T extends ThreadWorker>(name: LiteralUnion<'default', string>, ...args: Parameters<T>): Promise<ReturnType<T>>;
19
20
  private spawn;
20
21
  }
@@ -6,7 +6,6 @@ export declare class FeedableAsyncIterable<T> implements AsyncIterable<T> {
6
6
  get $empty(): Promise<void>;
7
7
  get closed(): boolean;
8
8
  get bufferSize(): number;
9
- constructor();
10
9
  feed(item: T): void;
11
10
  end(): void;
12
11
  throw(error: Error): void;
@@ -2,9 +2,9 @@ import { firstValueFrom, Subject } from 'rxjs';
2
2
  import { CancellationToken } from '../cancellation/token.js';
3
3
  import { CircularBuffer } from '../data-structures/circular-buffer.js';
4
4
  export class FeedableAsyncIterable {
5
- readSubject;
6
- closedToken;
7
- buffer;
5
+ readSubject = new Subject();
6
+ closedToken = new CancellationToken();
7
+ buffer = new CircularBuffer();
8
8
  get $read() {
9
9
  return firstValueFrom(this.readSubject);
10
10
  }
@@ -17,14 +17,9 @@ export class FeedableAsyncIterable {
17
17
  get bufferSize() {
18
18
  return this.buffer.size;
19
19
  }
20
- constructor() {
21
- this.readSubject = new Subject();
22
- this.closedToken = new CancellationToken();
23
- this.buffer = new CircularBuffer();
24
- }
25
20
  feed(item) {
26
21
  if (this.closed) {
27
- throw new Error('feedable is already closed');
22
+ throw new Error('Feedable is already closed.');
28
23
  }
29
24
  this.buffer.add({ item, error: undefined });
30
25
  }
@@ -33,7 +28,7 @@ export class FeedableAsyncIterable {
33
28
  }
34
29
  throw(error) {
35
30
  if (this.closed) {
36
- throw new Error('feedable is already closed');
31
+ throw new Error('Feedable is already closed.');
37
32
  }
38
33
  this.buffer.add({ item: undefined, error });
39
34
  this.end();
package/utils/throw.d.ts CHANGED
@@ -1,2 +1,2 @@
1
1
  export declare function _throw(value: any): never;
2
- export declare function deferThrow(value: any): () => never;
2
+ export declare function deferThrow(valueProvider: () => any): () => never;
package/utils/throw.js CHANGED
@@ -1,9 +1,8 @@
1
1
  export function _throw(value) {
2
2
  throw value;
3
3
  }
4
- export function deferThrow(value) {
5
- // eslint-disable-next-line @typescript-eslint/no-shadow
4
+ export function deferThrow(valueProvider) {
6
5
  return function deferThrow() {
7
- throw value;
6
+ throw valueProvider();
8
7
  };
9
8
  }