@vampgg/utils 1.0.0-beta.1

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.
@@ -0,0 +1,39 @@
1
+ import { AsyncQueue, AsyncQueueOptions } from "./async-queue.mjs";
2
+
3
+ //#region src/create-event-iterator.d.ts
4
+ type Context<T> = {
5
+ emit: (value: T) => void; /** Terminate the stream as a clean completion. */
6
+ cancel: () => void; /** Terminate the stream with an error thrown into the consumer's `for await`. */
7
+ error: (e: unknown) => void;
8
+ };
9
+ type CleanupFn = () => void | Promise<void>;
10
+ type Subscriber<T> = (context: Context<T>) => void | CleanupFn | Promise<CleanupFn | void>;
11
+ /**
12
+ * Wrap an {@link AsyncQueue} in a hand-rolled async iterator that delegates
13
+ * `next`/`return`/`throw` directly to the queue.
14
+ *
15
+ * This deliberately does NOT use an `async function*` wrapper. An async
16
+ * generator serializes `next`/`return`/`throw` through an internal request
17
+ * queue, so a `return()` issued while a `next()` is still parked (the common
18
+ * "consume in the background, then call `return()` to stop" pattern) is queued
19
+ * *behind* that pending `next()`. The pending `next()` only settles once the
20
+ * queue is closed — which is exactly what the queued `return()` would do — so
21
+ * the two deadlock and the stream hangs forever. A plain object iterator has no
22
+ * such serialization: `return()` runs immediately, `queue.return()` resolves the
23
+ * parked `next()`, and the consumer's `for await` ends cleanly.
24
+ *
25
+ * `start` is invoked lazily on the first pull (matching the lazy semantics of
26
+ * the previous `async function*` body, whose code did not run until the first
27
+ * `next()`), and is awaited by `return()`/`throw()` so a subscription that has
28
+ * begun establishing always has its cleanup captured before dispose runs.
29
+ */
30
+ declare function queueIterator<T>(queue: AsyncQueue<T>, start: () => Promise<void>): AsyncGenerator<T>;
31
+ /**
32
+ * Bridge a callback-style event source into an async iterator backed by a
33
+ * bounded {@link AsyncQueue}. The subscriber receives `{ emit, cancel, error }`
34
+ * and may return a cleanup function, which runs exactly once when the stream
35
+ * terminates (close / error / consumer `break` / consumer `return()` / throw).
36
+ */
37
+ declare function createEventIterator<T>(subscriber: Subscriber<T>, options?: AsyncQueueOptions): AsyncGenerator<T>;
38
+ //#endregion
39
+ export { CleanupFn, Context, Subscriber, createEventIterator, queueIterator };
@@ -0,0 +1,67 @@
1
+ import { AsyncQueue } from "./async-queue.mjs";
2
+ //#region src/create-event-iterator.ts
3
+ /**
4
+ * Wrap an {@link AsyncQueue} in a hand-rolled async iterator that delegates
5
+ * `next`/`return`/`throw` directly to the queue.
6
+ *
7
+ * This deliberately does NOT use an `async function*` wrapper. An async
8
+ * generator serializes `next`/`return`/`throw` through an internal request
9
+ * queue, so a `return()` issued while a `next()` is still parked (the common
10
+ * "consume in the background, then call `return()` to stop" pattern) is queued
11
+ * *behind* that pending `next()`. The pending `next()` only settles once the
12
+ * queue is closed — which is exactly what the queued `return()` would do — so
13
+ * the two deadlock and the stream hangs forever. A plain object iterator has no
14
+ * such serialization: `return()` runs immediately, `queue.return()` resolves the
15
+ * parked `next()`, and the consumer's `for await` ends cleanly.
16
+ *
17
+ * `start` is invoked lazily on the first pull (matching the lazy semantics of
18
+ * the previous `async function*` body, whose code did not run until the first
19
+ * `next()`), and is awaited by `return()`/`throw()` so a subscription that has
20
+ * begun establishing always has its cleanup captured before dispose runs.
21
+ */
22
+ function queueIterator(queue, start) {
23
+ let started;
24
+ const ensureStarted = () => {
25
+ started ??= start();
26
+ return started;
27
+ };
28
+ return {
29
+ async next() {
30
+ await ensureStarted();
31
+ return queue.next();
32
+ },
33
+ async return(value) {
34
+ if (started !== void 0) await started.catch(() => {});
35
+ return queue.return(value);
36
+ },
37
+ async throw(e) {
38
+ if (started !== void 0) await started.catch(() => {});
39
+ return queue.throw(e);
40
+ },
41
+ [Symbol.asyncIterator]() {
42
+ return this;
43
+ }
44
+ };
45
+ }
46
+ /**
47
+ * Bridge a callback-style event source into an async iterator backed by a
48
+ * bounded {@link AsyncQueue}. The subscriber receives `{ emit, cancel, error }`
49
+ * and may return a cleanup function, which runs exactly once when the stream
50
+ * terminates (close / error / consumer `break` / consumer `return()` / throw).
51
+ */
52
+ function createEventIterator(subscriber, options) {
53
+ let unsubscribe;
54
+ const queue = new AsyncQueue(options, () => unsubscribe?.());
55
+ const context = {
56
+ emit: (value) => {
57
+ queue.push(value);
58
+ },
59
+ cancel: () => queue.close(),
60
+ error: (e) => queue.fail(e)
61
+ };
62
+ return queueIterator(queue, async () => {
63
+ unsubscribe = await subscriber(context);
64
+ });
65
+ }
66
+ //#endregion
67
+ export { createEventIterator, queueIterator };
@@ -0,0 +1,43 @@
1
+ import { ContextLogger } from "./context-logger.mjs";
2
+ import { ConsoleLogger, TempoLogLevel, TempoLogger } from "@tempojs/common";
3
+
4
+ //#region src/debug-logger.d.ts
5
+ declare class DebugLogger extends ContextLogger implements ConsoleLogger {
6
+ bindings: Record<string, unknown>;
7
+ private debuggers;
8
+ sourceName: string;
9
+ logLevel: TempoLogLevel;
10
+ protected readonly children: Set<TempoLogger>;
11
+ constructor(sourceName: string, logLevel?: TempoLogLevel, parent?: TempoLogger, bindings?: Record<string, unknown>);
12
+ correlate<TLogger extends TempoLogger>(sourceName: string, correlationId: string, asOrphan?: boolean, bindings?: Record<string, unknown>): TLogger;
13
+ clone<TLogger extends TempoLogger>(sourceName: string, asOrphan?: boolean, bindings?: Record<string, unknown>): TLogger;
14
+ write(level: TempoLogLevel, message: string, data?: Record<string, unknown>, error?: Error): void;
15
+ /**
16
+ * @inheritDoc
17
+ */
18
+ trace(message: string, data?: Record<string, unknown>, error?: Error): void;
19
+ /**
20
+ * @inheritDoc
21
+ */
22
+ debug(message: string, data?: Record<string, unknown>, error?: Error): void;
23
+ /**
24
+ * @inheritDoc
25
+ */
26
+ info(message: string, data?: Record<string, unknown>, error?: Error): void;
27
+ /**
28
+ * @inheritDoc
29
+ */
30
+ warn(message: string, data?: Record<string, unknown>, error?: Error): void;
31
+ /**
32
+ * @inheritDoc
33
+ */
34
+ error(message: string, data?: Record<string, unknown>, error?: Error): void;
35
+ /**
36
+ * @inheritDoc
37
+ */
38
+ critical(message: string, data?: Record<string, unknown>, error?: Error): void;
39
+ setLogLevel(level: TempoLogLevel): void;
40
+ formatErrorMessage(error: Error, indentLevel?: number): string;
41
+ }
42
+ //#endregion
43
+ export { DebugLogger };
@@ -0,0 +1,122 @@
1
+ import { ContextLogger } from "./context-logger.mjs";
2
+ import { TempoLogLevel } from "@tempojs/common";
3
+ import debug from "debug";
4
+ //#region src/debug-logger.ts
5
+ var DebugLogger = class DebugLogger extends ContextLogger {
6
+ bindings = {};
7
+ debuggers;
8
+ sourceName;
9
+ logLevel;
10
+ children = /* @__PURE__ */ new Set();
11
+ constructor(sourceName, logLevel = TempoLogLevel.Info, parent, bindings = {}) {
12
+ super(sourceName, logLevel, parent, bindings);
13
+ this.sourceName = sourceName;
14
+ this.logLevel = logLevel;
15
+ this.debuggers = {
16
+ trace: debug(`${sourceName}:trace`),
17
+ debug: debug(`${sourceName}:debug`),
18
+ info: debug(`${sourceName}:info`),
19
+ warn: debug(`${sourceName}:warn`),
20
+ error: debug(`${sourceName}:error`),
21
+ critical: debug(`${sourceName}:critical`)
22
+ };
23
+ }
24
+ correlate(sourceName, correlationId, asOrphan, bindings) {
25
+ return this.clone(sourceName, asOrphan, {
26
+ ...bindings,
27
+ correlationId
28
+ });
29
+ }
30
+ clone(sourceName, asOrphan, bindings) {
31
+ const logger = new DebugLogger(asOrphan ? sourceName : `${this.sourceName}:${sourceName}`, this.logLevel, asOrphan !== true ? this : void 0, {
32
+ ...this.bindings,
33
+ ...bindings
34
+ });
35
+ if (asOrphan !== true) this.children.add(logger);
36
+ return logger;
37
+ }
38
+ write(level, message, data = {}, error) {
39
+ if (level < this.logLevel) return;
40
+ let logError = "";
41
+ if (error) {
42
+ logError = `\n${this.formatErrorMessage(error)}`;
43
+ data.error = logError;
44
+ }
45
+ const fullMessage = `${message}${` ${JSON.stringify({
46
+ ...this.bindings,
47
+ ...data
48
+ }, null, 2)}`}`;
49
+ switch (level) {
50
+ case TempoLogLevel.Trace:
51
+ this.debuggers.trace(fullMessage);
52
+ break;
53
+ case TempoLogLevel.Debug:
54
+ this.debuggers.debug(fullMessage);
55
+ break;
56
+ case TempoLogLevel.Info:
57
+ this.debuggers.info(fullMessage);
58
+ break;
59
+ case TempoLogLevel.Warn:
60
+ this.debuggers.warn(fullMessage);
61
+ break;
62
+ case TempoLogLevel.Error:
63
+ this.debuggers.error(fullMessage);
64
+ break;
65
+ case TempoLogLevel.Critical:
66
+ this.debuggers.critical(fullMessage);
67
+ break;
68
+ }
69
+ }
70
+ /**
71
+ * @inheritDoc
72
+ */
73
+ trace(message, data, error) {
74
+ this.write(TempoLogLevel.Trace, message, data, error);
75
+ }
76
+ /**
77
+ * @inheritDoc
78
+ */
79
+ debug(message, data, error) {
80
+ this.write(TempoLogLevel.Debug, message, data, error);
81
+ }
82
+ /**
83
+ * @inheritDoc
84
+ */
85
+ info(message, data, error) {
86
+ this.write(TempoLogLevel.Info, message, data, error);
87
+ }
88
+ /**
89
+ * @inheritDoc
90
+ */
91
+ warn(message, data, error) {
92
+ this.write(TempoLogLevel.Warn, message, data, error);
93
+ }
94
+ /**
95
+ * @inheritDoc
96
+ */
97
+ error(message, data, error) {
98
+ this.write(TempoLogLevel.Error, message, data, error);
99
+ }
100
+ /**
101
+ * @inheritDoc
102
+ */
103
+ critical(message, data, error) {
104
+ this.write(TempoLogLevel.Critical, message, data, error);
105
+ }
106
+ setLogLevel(level) {
107
+ this.logLevel = level;
108
+ }
109
+ formatErrorMessage(error, indentLevel = 0) {
110
+ const indent = " ".repeat(indentLevel);
111
+ const message = error.message || "Unknown error";
112
+ const indentedStack = (error.stack || "Stack unavailable").split("\n").map((line) => `${indent}${line}`).join("\n");
113
+ let cause = "";
114
+ if ("cause" in error) cause = error?.cause instanceof Error ? this.formatErrorMessage(error.cause, indentLevel + 1) : "";
115
+ return `
116
+ ${indent}Error message: ${message}
117
+ ${indentedStack}
118
+ ${indent}${cause ? `Cause: ${cause}` : ""}`.trimEnd();
119
+ }
120
+ };
121
+ //#endregion
122
+ export { DebugLogger };
@@ -0,0 +1,27 @@
1
+ import { Error } from "./bebop.mjs";
2
+ import { TempoError } from "@tempojs/common";
3
+
4
+ //#region src/error.d.ts
5
+ declare class SystemError<Tag extends string> extends TempoError {
6
+ readonly _tag: Tag;
7
+ readonly cause: Error;
8
+ constructor(tag: Tag, error?: Partial<Omit<Error, "tag">>);
9
+ static generic(err: unknown, tag?: ErrorTags, opts?: Partial<Omit<Error, "tag">>): SystemError<ErrorTags>;
10
+ }
11
+ declare enum ErrorTags {
12
+ PgOutage = "PgOutage",
13
+ RedisOutage = "RedisOutage",
14
+ Unknown = "Unknown",
15
+ NeedPermissions = "NeedPermissions",
16
+ Validation = "Validation",
17
+ Authorization = "Authorization",
18
+ GameStateGeneration = "GameStateGeneration",
19
+ InvalidPrompt = "InvalidPrompt",
20
+ UnknownAIProvider = "UnknownAIProvider",
21
+ FailedToStart = "FailedToStart",
22
+ Unimplemented = "Unimplemented",
23
+ IncompatibleVersion = "IncompatibleVersion",
24
+ PingUnacceptable = "PingUnacceptable"
25
+ }
26
+ //#endregion
27
+ export { ErrorTags, SystemError };
package/dist/error.mjs ADDED
@@ -0,0 +1,49 @@
1
+ import { TempoError, TempoStatusCode } from "@tempojs/common";
2
+ import defaults from "lodash-es/defaults";
3
+ //#region src/error.ts
4
+ var SystemError = class SystemError extends TempoError {
5
+ _tag;
6
+ cause;
7
+ constructor(tag, error) {
8
+ const err = defaults({ ...error }, {
9
+ code: TempoStatusCode.UNKNOWN,
10
+ stack: "",
11
+ msg: "Unknown error occured",
12
+ details: /* @__PURE__ */ new Map()
13
+ });
14
+ const cause = {
15
+ ...err,
16
+ tag
17
+ };
18
+ super(err.code, err.msg, cause);
19
+ this._tag = tag;
20
+ this.cause = cause;
21
+ }
22
+ static generic(err, tag, opts) {
23
+ return new SystemError(tag || "Unknown", {
24
+ msg: err instanceof Error ? err.message : "Unknown error occured",
25
+ code: err instanceof TempoError ? err.status : TempoStatusCode.INTERNAL,
26
+ details: err instanceof Error ? err.cause instanceof Map ? err.cause : /* @__PURE__ */ new Map() : new Map([["cause", JSON.stringify(err?.cause || "")]]),
27
+ stack: err?.stack || "",
28
+ ...opts
29
+ });
30
+ }
31
+ };
32
+ let ErrorTags = /* @__PURE__ */ function(ErrorTags) {
33
+ ErrorTags["PgOutage"] = "PgOutage";
34
+ ErrorTags["RedisOutage"] = "RedisOutage";
35
+ ErrorTags["Unknown"] = "Unknown";
36
+ ErrorTags["NeedPermissions"] = "NeedPermissions";
37
+ ErrorTags["Validation"] = "Validation";
38
+ ErrorTags["Authorization"] = "Authorization";
39
+ ErrorTags["GameStateGeneration"] = "GameStateGeneration";
40
+ ErrorTags["InvalidPrompt"] = "InvalidPrompt";
41
+ ErrorTags["UnknownAIProvider"] = "UnknownAIProvider";
42
+ ErrorTags["FailedToStart"] = "FailedToStart";
43
+ ErrorTags["Unimplemented"] = "Unimplemented";
44
+ ErrorTags["IncompatibleVersion"] = "IncompatibleVersion";
45
+ ErrorTags["PingUnacceptable"] = "PingUnacceptable";
46
+ return ErrorTags;
47
+ }({});
48
+ //#endregion
49
+ export { ErrorTags, SystemError };
@@ -0,0 +1,96 @@
1
+ import { Message } from "./bebop.mjs";
2
+ import { BebopRecord } from "bebop";
3
+ import { BebopContentType, Credential, Deadline, TempoError } from "@tempojs/common";
4
+ import { BaseChannel, CallOptions, ClientContext, MethodInfo, RetryPolicy, TempoChannelOptions } from "@tempojs/client";
5
+
6
+ //#region src/extension-channel.d.ts
7
+ /**
8
+ * Represents a Tempo channel for communication with a remote server.
9
+ */
10
+ declare class TempoExtensionChannel extends BaseChannel {
11
+ static readonly defaultMaxRetryAttempts: number;
12
+ static readonly defaultMaxReceiveMessageSize: number;
13
+ static readonly defaultMaxSendMessageSize: number;
14
+ static readonly defaultContentType: BebopContentType;
15
+ private readonly events;
16
+ private readonly userAgent;
17
+ readonly pending: Map<string, (e: unknown) => void>;
18
+ private closed;
19
+ private readonly onMessage;
20
+ /**
21
+ * Constructs a new TempoChannel instance.
22
+ *
23
+ * @param {URL} target - The target URL for the channel.
24
+ * @param {TempoWSChannelOptions} options - The configuration options for the channel.
25
+ * @protected
26
+ */
27
+ constructor(options?: TempoChannelOptions);
28
+ /**
29
+ * Tear down the channel: remove the runtime message listener, reject every
30
+ * in-flight call, and clear all event listeners. Idempotent.
31
+ */
32
+ close(reason?: TempoError): void;
33
+ removeCredential(): Promise<void>;
34
+ getCredential(): Promise<Credential | undefined>;
35
+ /**
36
+ * Executes a function with retries according to the provided retry policy.
37
+ * The function will be retried if it fails with a TempoError and its status code is included in the retryableStatusCodes of the retry policy.
38
+ * 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.
39
+ *
40
+ * @template T - The type of the result returned by the function.
41
+ * @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.
42
+ * @param {RetryPolicy} retryPolicy - An object defining the retry policy, including maxAttempts, initialBackoff, maxBackoff, backoffMultiplier, and retryableStatusCodes.
43
+ * @param {Deadline} [deadline] - An optional deadline object that manages the timeout for each attempt.
44
+ * @param {AbortController} [abortController] - An optional AbortController instance to cancel the function execution.
45
+ * @returns {Promise<T>} - A Promise that resolves with the result of the function if it completes within the deadline and retry policy constraints.
46
+ * @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.
47
+ */
48
+ executeWithRetry<T>(func: (retryAttempt: number) => Promise<T>, retryPolicy: RetryPolicy, deadline?: Deadline, abortController?: AbortController): Promise<T>;
49
+ private fetchUnary;
50
+ private fetchClientStream;
51
+ private fetchServerStream;
52
+ private fetchDuplexStream;
53
+ /**
54
+ * Creates a `RequestInit` object for a given payload, context, method and optional call options.
55
+ * This object can be used to make an HTTP request using the Fetch API.
56
+ *
57
+ * @private
58
+ * @param {Uint8Array} payload - The payload to be sent in the request.
59
+ * @param {ClientContext} context - The context of the client making the request.
60
+ * @param {MethodInfo<BebopRecord, BebopRecord>} method - Information about the method being called.
61
+ * @param {CallOptions | undefined} options - Optional configuration for the call.
62
+ * @returns {Promise<RequestInit>} A Promise resolving to the created `RequestInit` object.
63
+ * @throws {TempoError} Throws an error if there's a problem while getting the credential header.
64
+ */
65
+ private createRequest;
66
+ /**
67
+ * Processes the headers of the response from the server, validating their integrity and correctness.
68
+ * Also sets the incoming metadata from the response headers to the provided context.
69
+ *
70
+ * @private
71
+ * @param {Response} response - The response received from the server.
72
+ * @param {ClientContext} context - The context of the client making the request.
73
+ * @param {MethodType} methodType - The type of method being called.
74
+ * @throws {TempoError} Throws an error if any validation checks fail or if there's a problem parsing or storing credentials.
75
+ */
76
+ private processResponseHeaders;
77
+ /**
78
+ * {@inheritDoc BaseChannel.startUnary}
79
+ */
80
+ startUnary<TRequest extends BebopRecord, TResponse extends BebopRecord>(request: TRequest, context: ClientContext, method: MethodInfo<TRequest, TResponse>, options?: CallOptions): Promise<TResponse>;
81
+ /**
82
+ * {@inheritDoc BaseChannel.startClientStream}
83
+ */
84
+ startClientStream<TRequest extends BebopRecord, TResponse extends BebopRecord>(generator: () => AsyncGenerator<TRequest, void, undefined>, context: ClientContext, method: MethodInfo<TRequest, TResponse>, options?: CallOptions): Promise<TResponse>;
85
+ /**
86
+ * {@inheritDoc BaseChannel.startServerStream}
87
+ */
88
+ startServerStream<TRequest extends BebopRecord, TResponse extends BebopRecord>(request: TRequest, context: ClientContext, method: MethodInfo<TRequest, TResponse>, options?: CallOptions): Promise<AsyncGenerator<TResponse, void, undefined>>;
89
+ /**
90
+ * {@inheritDoc BaseChannel.startDuplexStream}
91
+ */
92
+ 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>>;
93
+ protected sendMessage(message: Message): Promise<unknown>;
94
+ }
95
+ //#endregion
96
+ export { TempoExtensionChannel };