@vampgg/utils 1.0.0-beta.1 → 1.0.0-beta.3

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,111 +1,24 @@
1
1
  import { Message } from "./bebop.mjs";
2
- import { createEventIterator } from "./create-event-iterator.mjs";
3
- import { Deadline, Metadata, MethodType, TempoError, TempoStatusCode, TempoUtil, stringifyCredential } from "@tempojs/common";
4
- import { EventEmitter } from "tseep";
5
- import { BaseRouter, ServerContext, TempoRouterConfiguration } from "@tempojs/server";
2
+ import { RouterCore } from "./router-core.mjs";
3
+ import { TempoError, TempoStatusCode } from "@tempojs/common";
4
+ import { BaseRouter, TempoRouterConfiguration } from "@tempojs/server";
6
5
  //#region src/worker-router.ts
7
6
  var TempoWorkerRouter = class extends BaseRouter {
8
- events = new EventEmitter();
9
- clientStreams = /* @__PURE__ */ new Map();
7
+ core;
10
8
  serverStreams = /* @__PURE__ */ new Map();
11
9
  constructor(logger, registry, configuration = new TempoRouterConfiguration(), authInterceptor) {
12
10
  super(logger, registry, configuration, authInterceptor);
13
- }
14
- async setAuthContext(request, context) {
15
- const authHeader = request.authorization;
16
- if (authHeader !== void 0 && this.authInterceptor !== void 0) {
17
- const authContext = await this.authInterceptor.intercept(context, authHeader);
18
- if (authContext !== void 0) context.authContext = authContext;
19
- }
20
- }
21
- async invokeUnaryMethod(request, context, method, contentType) {
22
- await this.setAuthContext(request, context);
23
- if (this.hooks !== void 0) await this.hooks.executeRequestHooks(context);
24
- const requestData = request.data;
25
- const record = this.deserializeRequest(requestData, method, contentType);
26
- if (this.hooks !== void 0) await this.hooks.executeDecodeHooks(context, record);
27
- return await method.invoke(record, context);
28
- }
29
- async invokeClientStreamMethod(request, context, method, contentType) {
30
- await this.setAuthContext(request, context);
31
- if (this.hooks !== void 0) await this.hooks.executeRequestHooks(context);
32
- const messageId = request.messageId;
33
- if (this.clientStreams.has(messageId)) {
34
- const invocation = this.clientStreams.get(messageId);
35
- this.events.emit(messageId, request);
36
- return await invocation;
37
- }
38
- const generator = () => {
39
- return createEventIterator(({ emit, cancel }) => {
40
- const eventHandler = async (message) => {
41
- if (this.hooks !== void 0) await this.hooks.executeRequestHooks(context);
42
- if (message.status === TempoStatusCode.CANCELLED) {
43
- cancel();
44
- return;
45
- }
46
- const requestData = message.data;
47
- const record = this.deserializeRequest(requestData, method, contentType);
48
- if (this.hooks !== void 0) await this.hooks.executeDecodeHooks(context, record);
49
- emit(record);
50
- };
51
- this.events.on(messageId, eventHandler);
52
- return () => {
53
- this.events.off(messageId, eventHandler);
54
- this.clientStreams.delete(messageId);
55
- };
56
- });
57
- };
58
- const invocation = method.invoke(generator, context);
59
- this.clientStreams.set(messageId, invocation);
60
- this.events.emit(messageId, request);
61
- return await invocation.finally(() => {
62
- this.clientStreams.delete(messageId);
11
+ this.core = new RouterCore({
12
+ logger,
13
+ maxRetryAttempts: this.maxRetryAttempts,
14
+ transmitInternalErrors: this.transmitInternalErrors,
15
+ authInterceptor,
16
+ getMethod: (methodId) => this.registry.getMethod(methodId),
17
+ getHooks: () => this.hooks,
18
+ deserializeRequest: (data, method, contentType) => this.deserializeRequest(data, method, contentType),
19
+ serializeResponse: (record, method, contentType) => this.serializeResponse(record, method, contentType)
63
20
  });
64
21
  }
65
- async invokeServerStreamMethod(request, context, method, contentType) {
66
- await this.setAuthContext(request, context);
67
- if (this.hooks !== void 0) await this.hooks.executeRequestHooks(context);
68
- const requestData = request.data;
69
- const record = this.deserializeRequest(requestData, method, contentType);
70
- if (!TempoUtil.isAsyncGeneratorFunction(method.invoke.bind(method))) throw new TempoError(TempoStatusCode.INTERNAL, "service method incorrect: method must be async generator");
71
- if (this.hooks !== void 0) await this.hooks.executeDecodeHooks(context, record);
72
- return method.invoke(record, context);
73
- }
74
- async invokeDuplexStreamMethod(request, context, method, contentType) {
75
- await this.setAuthContext(request, context);
76
- if (this.hooks !== void 0) await this.hooks.executeRequestHooks(context);
77
- const messageId = request.messageId;
78
- if (this.clientStreams.has(messageId)) {
79
- const invocation = this.clientStreams.get(messageId);
80
- this.events.emit(messageId, request);
81
- return invocation;
82
- }
83
- if (!TempoUtil.isAsyncGeneratorFunction(method.invoke.bind(method))) throw new TempoError(TempoStatusCode.INTERNAL, "service method incorrect: method must be async generator");
84
- const generator = () => {
85
- return createEventIterator(({ emit, cancel }) => {
86
- const eventHandler = async (message) => {
87
- if (this.hooks !== void 0) await this.hooks.executeRequestHooks(context);
88
- if (message.status === TempoStatusCode.CANCELLED) {
89
- cancel();
90
- return;
91
- }
92
- const requestData = message.data;
93
- const record = this.deserializeRequest(requestData, method, contentType);
94
- if (this.hooks !== void 0) await this.hooks.executeDecodeHooks(context, record);
95
- emit(record);
96
- };
97
- this.events.on(messageId, eventHandler);
98
- this.events.emit(messageId, request);
99
- return () => {
100
- this.events.off(messageId, eventHandler);
101
- this.clientStreams.delete(messageId);
102
- };
103
- });
104
- };
105
- const invocation = method.invoke(generator, context);
106
- this.clientStreams.set(messageId, invocation);
107
- return invocation;
108
- }
109
22
  /**
110
23
  * Tear down all open streams for this connection (driven by the host
111
24
  * close/error lifecycle). Returns each server-stream generator and emits a
@@ -117,20 +30,11 @@ var TempoWorkerRouter = class extends BaseRouter {
117
30
  await info.generator.return(void 0).catch(() => {});
118
31
  this.serverStreams.delete(id);
119
32
  }
120
- for (const id of [...this.clientStreams.keys()]) {
121
- this.events.emit(id, Message({
122
- messageId: id,
123
- status: TempoStatusCode.CANCELLED,
124
- data: new Uint8Array()
125
- }));
126
- this.clientStreams.delete(id);
127
- }
128
- this.events.removeAllListeners();
33
+ this.core.cancelClientStreams();
34
+ this.core.removeAllListeners();
129
35
  }
130
36
  async process(req, response, env) {
131
- let request = Message.decode(req);
132
- let context;
133
- const contentType = "bebop";
37
+ const request = Message.decode(req);
134
38
  if (request.status === TempoStatusCode.CANCELLED && request.messageId) {
135
39
  const streamInfo = this.serverStreams.get(request.messageId);
136
40
  if (streamInfo) {
@@ -140,111 +44,31 @@ var TempoWorkerRouter = class extends BaseRouter {
140
44
  }
141
45
  return;
142
46
  }
143
- try {
144
- const methodId = request.methodId;
145
- const method = this.registry.getMethod(methodId);
146
- if (!method) throw new TempoError(TempoStatusCode.NOT_FOUND, `no service is registered which contains a method of '${methodId}'`);
147
- const metadataHeader = request.customMetadata;
148
- const metadata = metadataHeader ? Metadata.fromHttpHeader(metadataHeader) : new Metadata();
149
- const previousAttempts = request.previousAttempts;
150
- if (previousAttempts !== void 0 && previousAttempts > this.maxRetryAttempts) throw new TempoError(TempoStatusCode.RESOURCE_EXHAUSTED, "max retry attempts exceeded");
151
- let deadline;
152
- const deadlineHeader = request.deadline;
153
- if (deadlineHeader !== void 0) deadline = Deadline.fromUnixTimestamp(deadlineHeader.valueOf());
154
- if (deadline !== void 0 && deadline.isExpired()) throw new TempoError(TempoStatusCode.DEADLINE_EXCEEDED, "incoming request has already exceeded its deadline");
155
- const outgoingMetadata = new Metadata();
156
- const incomingContext = {
157
- headers: new Headers(),
158
- metadata
159
- };
160
- if (deadline !== void 0) incomingContext.deadline = deadline;
161
- context = new ServerContext(incomingContext, { metadata: outgoingMetadata }, env);
162
- const handleRequest = async () => {
163
- let recordGenerator = void 0;
164
- let record;
165
- switch (method.type) {
166
- case MethodType.Unary:
167
- record = await this.invokeUnaryMethod(request, context, method, contentType);
168
- break;
169
- case MethodType.ClientStream:
170
- record = await this.invokeClientStreamMethod(request, context, method, contentType);
171
- break;
172
- case MethodType.ServerStream:
173
- recordGenerator = await this.invokeServerStreamMethod(request, context, method, contentType);
174
- break;
175
- case MethodType.DuplexStream:
176
- recordGenerator = await this.invokeDuplexStreamMethod(request, context, method, contentType);
177
- break;
178
- default: throw new TempoError(TempoStatusCode.INTERNAL, "service method incorrect: unknown method type");
179
- }
180
- const outgoingCredential = context.outgoingCredential;
181
- if (outgoingCredential) response.credential = stringifyCredential(outgoingCredential);
182
- response.methodId = request.methodId;
183
- response.messageId = request.messageId;
184
- response.status = TempoStatusCode.OK;
185
- response.timestamp = /* @__PURE__ */ new Date();
186
- if (this.hooks !== void 0) await this.hooks.executeResponseHooks(context);
187
- outgoingMetadata.freeze();
188
- if (outgoingMetadata.size() > 0) response.customMetadata = outgoingMetadata.data;
189
- if (recordGenerator !== void 0) {
190
- const messageId = request.messageId;
191
- const streamInfo = {
192
- generator: recordGenerator,
193
- cancelled: false
194
- };
195
- this.serverStreams.set(messageId, streamInfo);
196
- const writeFrames = async () => {
197
- try {
198
- for await (const value of recordGenerator) {
199
- if (streamInfo.cancelled) break;
200
- const responseData = this.serializeResponse(value, method, "bebop");
201
- response.data = new Uint8Array(responseData);
202
- const encoded = Message.encode(response).slice();
203
- postMessage(encoded, [encoded.buffer]);
204
- }
205
- } finally {
206
- this.serverStreams.delete(messageId);
207
- response.data = new Uint8Array();
208
- response.status = TempoStatusCode.CANCELLED;
209
- const encoded = Message.encode(response).slice();
210
- postMessage(encoded, [encoded.buffer]);
211
- }
212
- };
213
- if (deadline) await deadline.executeWithinDeadline(writeFrames);
214
- else await writeFrames();
215
- } else {
216
- if (record === void 0) throw new TempoError(TempoStatusCode.INTERNAL, "service method did not return a record");
217
- const responseData = this.serializeResponse(record, method, contentType);
218
- response.data = new Uint8Array(responseData);
219
- const encoded = Message.encode(response).slice();
220
- postMessage(encoded, [encoded.buffer]);
221
- }
222
- };
223
- if (deadline !== void 0) await deadline.executeWithinDeadline(handleRequest);
224
- else await handleRequest();
225
- } catch (e) {
226
- let status = TempoStatusCode.UNKNOWN;
227
- let message = "unknown error";
228
- if (e instanceof TempoError) {
229
- status = e.status;
230
- message = e.message;
231
- if (e.status === TempoStatusCode.INTERNAL && this.transmitInternalErrors !== true) message = "internal error";
232
- if (e.status === TempoStatusCode.INTERNAL) this.logger.critical(e.message, void 0, e);
233
- else this.logger.error(message, void 0, e);
234
- } else if (e instanceof Error) {
235
- message = e.message;
236
- this.logger.error(message, void 0, e);
237
- }
238
- if (e instanceof Error && this.hooks !== void 0) await this.hooks.executeErrorHooks(context, e);
239
- this.clientStreams.delete(request.messageId);
240
- response.status = status;
241
- response.msg = message;
242
- response.messageId = request.messageId;
243
- response.timestamp = request.timestamp;
244
- response.methodId = request.methodId;
245
- const encoded = Message.encode(response).slice();
47
+ const send = (message) => {
48
+ const encoded = Message.encode(message).slice();
246
49
  postMessage(encoded, [encoded.buffer]);
247
- }
50
+ };
51
+ await this.core.processRequest(request, response, env, {
52
+ send,
53
+ stampErrorResponse: true,
54
+ writeStreamFrames: async (generator, resp, method) => {
55
+ const messageId = request.messageId;
56
+ const streamInfo = {
57
+ generator,
58
+ cancelled: false
59
+ };
60
+ this.serverStreams.set(messageId, streamInfo);
61
+ await this.core.writeStreamFrames({
62
+ generator,
63
+ response: resp,
64
+ method,
65
+ send,
66
+ shouldStop: () => streamInfo.cancelled,
67
+ alwaysTerminate: true,
68
+ onEnd: () => this.serverStreams.delete(messageId)
69
+ });
70
+ }
71
+ });
248
72
  }
249
73
  handle(_request, _env) {
250
74
  throw new TempoError(TempoStatusCode.UNIMPLEMENTED, "Method not implemented.");
@@ -1,9 +1,8 @@
1
1
  import { Message } from "./bebop.mjs";
2
- import { BebopRecord } from "bebop";
2
+ import { CoreChannel } from "./channel-core.mjs";
3
+ import { CallCredential, TempoChannelOptions } from "@tempojs/client";
3
4
  import * as _tempojs_common0 from "@tempojs/common";
4
- import { BebopContentType, Credential, Deadline, TempoError } from "@tempojs/common";
5
- import { BaseChannel, CallCredential, CallOptions, ClientContext, MethodInfo, RetryPolicy, TempoChannelOptions } from "@tempojs/client";
6
- import { EventEmitter } from "tseep";
5
+ import { BebopContentType, Credential } from "@tempojs/common";
7
6
  import { Websocket, WebsocketOptions } from "websocket-ts";
8
7
 
9
8
  //#region src/ws-channel.d.ts
@@ -13,19 +12,17 @@ type TempoWSChannelOptions = TempoChannelOptions & {
13
12
  /**
14
13
  * Represents a Tempo channel for communication with a remote server.
15
14
  */
16
- declare class TempoWSChannel extends BaseChannel {
15
+ declare class TempoWSChannel extends CoreChannel {
17
16
  static readonly defaultMaxRetryAttempts: number;
18
17
  static readonly defaultMaxReceiveMessageSize: number;
19
18
  static readonly defaultMaxSendMessageSize: number;
20
19
  static readonly defaultCredential: CallCredential;
21
20
  static readonly defaultContentType: BebopContentType;
22
21
  ws: Websocket;
23
- readonly events: EventEmitter<Record<string, (message: Message) => void>>;
24
- readonly pending: Map<string, (e: unknown) => void>;
25
- private closed;
22
+ /** Teardown of a duplex consumer must tell the server to stop streaming. */
23
+ protected readonly cancelDuplexOnCleanup: boolean;
26
24
  private readonly isSecure;
27
25
  private readonly credential;
28
- private readonly userAgent;
29
26
  private readonly maxReceiveMessageSize;
30
27
  private readonly maxSendMessageSize;
31
28
  private readonly _url;
@@ -59,50 +56,22 @@ declare class TempoWSChannel extends BaseChannel {
59
56
  private _reconnect;
60
57
  removeCredential(): Promise<void>;
61
58
  getCredential(): Promise<Credential | undefined>;
59
+ /** Every RPC waits for the socket to open before sending. */
60
+ protected beforeCall(): Promise<void>;
61
+ /** {@inheritDoc CoreChannel.getAuthorizationValue} */
62
+ protected getAuthorizationValue(): Promise<string | undefined>;
62
63
  /**
63
- * Executes a function with retries according to the provided retry policy.
64
- * The function will be retried if it fails with a TempoError and its status code is included in the retryableStatusCodes of the retry policy.
65
- * If a deadline is provided, the deadline for each attempt will be managed by the provided deadline, but the deadline will not be reset upon each retry.
64
+ * {@inheritDoc CoreChannel.storeResponseCredential}
66
65
  *
67
- * @template T - The type of the result returned by the function.
68
- * @param {((retryAttempt: number) => Promise<T>)} func - A function that returns a Promise with a result. The function will receive a number indicating the current retry attempt.
69
- * @param {RetryPolicy} retryPolicy - An object defining the retry policy, including maxAttempts, initialBackoff, maxBackoff, backoffMultiplier, and retryableStatusCodes.
70
- * @param {Deadline} [deadline] - An optional deadline object that manages the timeout for each attempt.
71
- * @param {AbortController} [abortController] - An optional AbortController instance to cancel the function execution.
72
- * @returns {Promise<T>} - A Promise that resolves with the result of the function if it completes within the deadline and retry policy constraints.
73
- * @throws {Error} - If the function execution fails and the error does not match the retry policy, or if the maximum number of attempts is reached without a successful result.
66
+ * @throws {TempoError} If the credential received on the response cannot be parsed.
74
67
  */
75
- executeWithRetry<T>(func: (retryAttempt: number) => Promise<T>, retryPolicy: RetryPolicy, deadline?: Deadline, abortController?: AbortController): Promise<T>;
76
- private fetchUnary;
77
- private fetchClientStream;
78
- private fetchServerStream;
79
- private fetchDuplexStream;
80
- private createRequest;
81
- private processResponseHeaders;
68
+ protected storeResponseCredential(responseCredential: string): Promise<void>;
82
69
  waitForOpen(): Promise<void>;
83
70
  send(message: Message): void;
84
- /**
85
- * Tear down the channel: reject every in-flight unary/client-stream call,
86
- * remove all event listeners, and close the socket. Idempotent and safe to
87
- * invoke from the socket `close`/`error` events or by an explicit caller.
88
- */
89
- close(reason?: TempoError): void;
90
- /**
91
- * {@inheritDoc BaseChannel.startUnary}
92
- */
93
- startUnary<TRequest extends BebopRecord, TResponse extends BebopRecord>(request: TRequest, context: ClientContext, method: MethodInfo<TRequest, TResponse>, options?: CallOptions): Promise<TResponse>;
94
- /**
95
- * {@inheritDoc BaseChannel.startClientStream}
96
- */
97
- startClientStream<TRequest extends BebopRecord, TResponse extends BebopRecord>(generator: () => AsyncGenerator<TRequest, void, undefined>, context: ClientContext, method: MethodInfo<TRequest, TResponse>, options?: CallOptions): Promise<TResponse>;
98
- /**
99
- * {@inheritDoc BaseChannel.startServerStream}
100
- */
101
- startServerStream<TRequest extends BebopRecord, TResponse extends BebopRecord>(request: TRequest, context: ClientContext, method: MethodInfo<TRequest, TResponse>, options?: CallOptions): Promise<AsyncGenerator<TResponse, void, undefined>>;
102
- /**
103
- * {@inheritDoc BaseChannel.startDuplexStream}
104
- */
105
- startDuplexStream<TRequest extends BebopRecord, TResponse extends BebopRecord>(generator: () => AsyncGenerator<TRequest, void, undefined>, context: ClientContext, method: MethodInfo<TRequest, TResponse>, options?: CallOptions): Promise<AsyncGenerator<TResponse, void, undefined>>;
71
+ /** {@inheritDoc CoreChannel.sendFrame} */
72
+ protected sendFrame(message: Message): void;
73
+ /** Close the socket on {@link CoreChannel.close}. */
74
+ protected closeTransport(): void;
106
75
  }
107
76
  //#endregion
108
77
  export { TempoWSChannel, TempoWSChannelOptions };