Add settings page for managing forecasting API key and URL via UI

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
jtricerolph 2026-07-20 14:37:36 +00:00
parent cae411eae7
commit 1c411e402e
19809 changed files with 1962608 additions and 97 deletions

View file

@ -0,0 +1,31 @@
//#region src/promise/allKeyed.d.ts
/**
* Resolves an object of promises concurrently, returning an object with the same keys and resolved values.
*
* Similar to `Promise.all`, but accepts an object of promises instead of an array,
* preserving the keys in the result. This makes it easy to destructure the resolved values
* by name instead of relying on positional indices.
*
* Based on the [TC39 `Promise.allKeyed` proposal](https://github.com/tc39/proposal-await-dictionary).
*
* @template T - A record type where each value is a promise or a value.
* @param tasks - An object whose values are promises (or plain values) to resolve concurrently.
* @returns>} A promise that resolves to an object with the same keys and resolved values.
*
* @example
* const { user, posts } = await allKeyed({
* user: fetchUser(),
* posts: fetchPosts(),
* });
*
* @example
* // Plain values are also supported
* const result = await allKeyed({
* a: Promise.resolve(1),
* b: 2,
* });
* // { a: 1, b: 2 }
*/
declare function allKeyed<T extends Record<string, unknown>>(tasks: T): Promise<{ [K in keyof T]: Awaited<T[K]> }>;
//#endregion
export { allKeyed };

View file

@ -0,0 +1,31 @@
//#region src/promise/allKeyed.d.ts
/**
* Resolves an object of promises concurrently, returning an object with the same keys and resolved values.
*
* Similar to `Promise.all`, but accepts an object of promises instead of an array,
* preserving the keys in the result. This makes it easy to destructure the resolved values
* by name instead of relying on positional indices.
*
* Based on the [TC39 `Promise.allKeyed` proposal](https://github.com/tc39/proposal-await-dictionary).
*
* @template T - A record type where each value is a promise or a value.
* @param tasks - An object whose values are promises (or plain values) to resolve concurrently.
* @returns>} A promise that resolves to an object with the same keys and resolved values.
*
* @example
* const { user, posts } = await allKeyed({
* user: fetchUser(),
* posts: fetchPosts(),
* });
*
* @example
* // Plain values are also supported
* const result = await allKeyed({
* a: Promise.resolve(1),
* b: 2,
* });
* // { a: 1, b: 2 }
*/
declare function allKeyed<T extends Record<string, unknown>>(tasks: T): Promise<{ [K in keyof T]: Awaited<T[K]> }>;
//#endregion
export { allKeyed };

View file

@ -0,0 +1,37 @@
//#region src/promise/allKeyed.ts
/**
* Resolves an object of promises concurrently, returning an object with the same keys and resolved values.
*
* Similar to `Promise.all`, but accepts an object of promises instead of an array,
* preserving the keys in the result. This makes it easy to destructure the resolved values
* by name instead of relying on positional indices.
*
* Based on the [TC39 `Promise.allKeyed` proposal](https://github.com/tc39/proposal-await-dictionary).
*
* @template T - A record type where each value is a promise or a value.
* @param tasks - An object whose values are promises (or plain values) to resolve concurrently.
* @returns>} A promise that resolves to an object with the same keys and resolved values.
*
* @example
* const { user, posts } = await allKeyed({
* user: fetchUser(),
* posts: fetchPosts(),
* });
*
* @example
* // Plain values are also supported
* const result = await allKeyed({
* a: Promise.resolve(1),
* b: 2,
* });
* // { a: 1, b: 2 }
*/
async function allKeyed(tasks) {
const keys = Object.keys(tasks);
const values = await Promise.all(keys.map((key) => tasks[key]));
const result = {};
for (let i = 0; i < keys.length; i++) result[keys[i]] = values[i];
return result;
}
//#endregion
exports.allKeyed = allKeyed;

View file

@ -0,0 +1,37 @@
//#region src/promise/allKeyed.ts
/**
* Resolves an object of promises concurrently, returning an object with the same keys and resolved values.
*
* Similar to `Promise.all`, but accepts an object of promises instead of an array,
* preserving the keys in the result. This makes it easy to destructure the resolved values
* by name instead of relying on positional indices.
*
* Based on the [TC39 `Promise.allKeyed` proposal](https://github.com/tc39/proposal-await-dictionary).
*
* @template T - A record type where each value is a promise or a value.
* @param tasks - An object whose values are promises (or plain values) to resolve concurrently.
* @returns>} A promise that resolves to an object with the same keys and resolved values.
*
* @example
* const { user, posts } = await allKeyed({
* user: fetchUser(),
* posts: fetchPosts(),
* });
*
* @example
* // Plain values are also supported
* const result = await allKeyed({
* a: Promise.resolve(1),
* b: 2,
* });
* // { a: 1, b: 2 }
*/
async function allKeyed(tasks) {
const keys = Object.keys(tasks);
const values = await Promise.all(keys.map((key) => tasks[key]));
const result = {};
for (let i = 0; i < keys.length; i++) result[keys[i]] = values[i];
return result;
}
//#endregion
export { allKeyed };

View file

@ -0,0 +1,41 @@
//#region src/promise/delay.d.ts
interface DelayOptions {
signal?: AbortSignal;
}
/**
* Delays the execution of code for a specified number of milliseconds.
*
* This function returns a Promise that resolves after the specified delay, allowing you to use it
* with async/await to pause execution.
*
* @param ms - The number of milliseconds to delay.
* @param options - The options object.
* @param options.signal - An optional AbortSignal to cancel the delay.
* @returns A Promise that resolves after the specified delay.
*
* @example
* async function foo() {
* console.log('Start');
* await delay(1000); // Delays execution for 1 second
* console.log('End');
* }
*
* foo();
*
* // With AbortSignal
* const controller = new AbortController();
* const { signal } = controller;
*
* setTimeout(() => controller.abort(), 50); // Will cancel the delay after 50ms
* try {
* await delay(100, { signal });
* } catch (error) {
* console.error(error); // Will log 'AbortError'
* }
* }
*/
declare function delay(ms: number, {
signal
}?: DelayOptions): Promise<void>;
//#endregion
export { delay };

View file

@ -0,0 +1,41 @@
//#region src/promise/delay.d.ts
interface DelayOptions {
signal?: AbortSignal;
}
/**
* Delays the execution of code for a specified number of milliseconds.
*
* This function returns a Promise that resolves after the specified delay, allowing you to use it
* with async/await to pause execution.
*
* @param ms - The number of milliseconds to delay.
* @param options - The options object.
* @param options.signal - An optional AbortSignal to cancel the delay.
* @returns A Promise that resolves after the specified delay.
*
* @example
* async function foo() {
* console.log('Start');
* await delay(1000); // Delays execution for 1 second
* console.log('End');
* }
*
* foo();
*
* // With AbortSignal
* const controller = new AbortController();
* const { signal } = controller;
*
* setTimeout(() => controller.abort(), 50); // Will cancel the delay after 50ms
* try {
* await delay(100, { signal });
* } catch (error) {
* console.error(error); // Will log 'AbortError'
* }
* }
*/
declare function delay(ms: number, {
signal
}?: DelayOptions): Promise<void>;
//#endregion
export { delay };

53
frontend/node_modules/es-toolkit/dist/promise/delay.js generated vendored Normal file
View file

@ -0,0 +1,53 @@
const require_AbortError = require("../error/AbortError.js");
//#region src/promise/delay.ts
/**
* Delays the execution of code for a specified number of milliseconds.
*
* This function returns a Promise that resolves after the specified delay, allowing you to use it
* with async/await to pause execution.
*
* @param ms - The number of milliseconds to delay.
* @param options - The options object.
* @param options.signal - An optional AbortSignal to cancel the delay.
* @returns A Promise that resolves after the specified delay.
*
* @example
* async function foo() {
* console.log('Start');
* await delay(1000); // Delays execution for 1 second
* console.log('End');
* }
*
* foo();
*
* // With AbortSignal
* const controller = new AbortController();
* const { signal } = controller;
*
* setTimeout(() => controller.abort(), 50); // Will cancel the delay after 50ms
* try {
* await delay(100, { signal });
* } catch (error) {
* console.error(error); // Will log 'AbortError'
* }
* }
*/
function delay(ms, { signal } = {}) {
return new Promise((resolve, reject) => {
const abortError = () => {
reject(new require_AbortError.AbortError());
};
const abortHandler = () => {
clearTimeout(timeoutId);
abortError();
};
if (signal?.aborted) return abortError();
const timeoutId = setTimeout(() => {
signal?.removeEventListener("abort", abortHandler);
resolve();
}, ms);
signal?.addEventListener("abort", abortHandler, { once: true });
});
}
//#endregion
exports.delay = delay;

View file

@ -0,0 +1,53 @@
import { AbortError } from "../error/AbortError.mjs";
//#region src/promise/delay.ts
/**
* Delays the execution of code for a specified number of milliseconds.
*
* This function returns a Promise that resolves after the specified delay, allowing you to use it
* with async/await to pause execution.
*
* @param ms - The number of milliseconds to delay.
* @param options - The options object.
* @param options.signal - An optional AbortSignal to cancel the delay.
* @returns A Promise that resolves after the specified delay.
*
* @example
* async function foo() {
* console.log('Start');
* await delay(1000); // Delays execution for 1 second
* console.log('End');
* }
*
* foo();
*
* // With AbortSignal
* const controller = new AbortController();
* const { signal } = controller;
*
* setTimeout(() => controller.abort(), 50); // Will cancel the delay after 50ms
* try {
* await delay(100, { signal });
* } catch (error) {
* console.error(error); // Will log 'AbortError'
* }
* }
*/
function delay(ms, { signal } = {}) {
return new Promise((resolve, reject) => {
const abortError = () => {
reject(new AbortError());
};
const abortHandler = () => {
clearTimeout(timeoutId);
abortError();
};
if (signal?.aborted) return abortError();
const timeoutId = setTimeout(() => {
signal?.removeEventListener("abort", abortHandler);
resolve();
}, ms);
signal?.addEventListener("abort", abortHandler, { once: true });
});
}
//#endregion
export { delay };

View file

@ -0,0 +1,7 @@
import { allKeyed } from "./allKeyed.mjs";
import { delay } from "./delay.mjs";
import { Mutex } from "./mutex.mjs";
import { Semaphore } from "./semaphore.mjs";
import { timeout } from "./timeout.mjs";
import { withTimeout } from "./withTimeout.mjs";
export { Mutex, Semaphore, allKeyed, delay, timeout, withTimeout };

View file

@ -0,0 +1,7 @@
import { allKeyed } from "./allKeyed.js";
import { delay } from "./delay.js";
import { Mutex } from "./mutex.js";
import { Semaphore } from "./semaphore.js";
import { timeout } from "./timeout.js";
import { withTimeout } from "./withTimeout.js";
export { Mutex, Semaphore, allKeyed, delay, timeout, withTimeout };

13
frontend/node_modules/es-toolkit/dist/promise/index.js generated vendored Normal file
View file

@ -0,0 +1,13 @@
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
const require_semaphore = require("./semaphore.js");
const require_delay = require("./delay.js");
const require_allKeyed = require("./allKeyed.js");
const require_mutex = require("./mutex.js");
const require_timeout = require("./timeout.js");
const require_withTimeout = require("./withTimeout.js");
exports.Mutex = require_mutex.Mutex;
exports.Semaphore = require_semaphore.Semaphore;
exports.allKeyed = require_allKeyed.allKeyed;
exports.delay = require_delay.delay;
exports.timeout = require_timeout.timeout;
exports.withTimeout = require_withTimeout.withTimeout;

View file

@ -0,0 +1,7 @@
import { Semaphore } from "./semaphore.mjs";
import { delay } from "./delay.mjs";
import { allKeyed } from "./allKeyed.mjs";
import { Mutex } from "./mutex.mjs";
import { timeout } from "./timeout.mjs";
import { withTimeout } from "./withTimeout.mjs";
export { Mutex, Semaphore, allKeyed, delay, timeout, withTimeout };

View file

@ -0,0 +1,65 @@
//#region src/promise/mutex.d.ts
/**
* A Mutex (mutual exclusion lock) for async functions.
* It allows only one async task to access a critical section at a time.
*
* @example
* const mutex = new Mutex();
*
* async function criticalSection() {
* await mutex.acquire();
* try {
* // This code section cannot be executed simultaneously
* } finally {
* mutex.release();
* }
* }
*
* criticalSection();
* criticalSection(); // This call will wait until the first call releases the mutex.
*/
declare class Mutex {
private semaphore;
/**
* Checks if the mutex is currently locked.
* @returns True if the mutex is locked, false otherwise.
*
* @example
* const mutex = new Mutex();
* console.log(mutex.isLocked); // false
* await mutex.acquire();
* console.log(mutex.isLocked); // true
* mutex.release();
* console.log(mutex.isLocked); // false
*/
get isLocked(): boolean;
/**
* Acquires the mutex, blocking if necessary until it is available.
* @returns A promise that resolves when the mutex is acquired.
*
* @example
* const mutex = new Mutex();
* await mutex.acquire();
* try {
* // This code section cannot be executed simultaneously
* } finally {
* mutex.release();
* }
*/
acquire(): Promise<void>;
/**
* Releases the mutex, allowing another waiting task to proceed.
*
* @example
* const mutex = new Mutex();
* await mutex.acquire();
* try {
* // This code section cannot be executed simultaneously
* } finally {
* mutex.release(); // Allows another waiting task to proceed.
* }
*/
release(): void;
}
//#endregion
export { Mutex };

View file

@ -0,0 +1,65 @@
//#region src/promise/mutex.d.ts
/**
* A Mutex (mutual exclusion lock) for async functions.
* It allows only one async task to access a critical section at a time.
*
* @example
* const mutex = new Mutex();
*
* async function criticalSection() {
* await mutex.acquire();
* try {
* // This code section cannot be executed simultaneously
* } finally {
* mutex.release();
* }
* }
*
* criticalSection();
* criticalSection(); // This call will wait until the first call releases the mutex.
*/
declare class Mutex {
private semaphore;
/**
* Checks if the mutex is currently locked.
* @returns True if the mutex is locked, false otherwise.
*
* @example
* const mutex = new Mutex();
* console.log(mutex.isLocked); // false
* await mutex.acquire();
* console.log(mutex.isLocked); // true
* mutex.release();
* console.log(mutex.isLocked); // false
*/
get isLocked(): boolean;
/**
* Acquires the mutex, blocking if necessary until it is available.
* @returns A promise that resolves when the mutex is acquired.
*
* @example
* const mutex = new Mutex();
* await mutex.acquire();
* try {
* // This code section cannot be executed simultaneously
* } finally {
* mutex.release();
* }
*/
acquire(): Promise<void>;
/**
* Releases the mutex, allowing another waiting task to proceed.
*
* @example
* const mutex = new Mutex();
* await mutex.acquire();
* try {
* // This code section cannot be executed simultaneously
* } finally {
* mutex.release(); // Allows another waiting task to proceed.
* }
*/
release(): void;
}
//#endregion
export { Mutex };

72
frontend/node_modules/es-toolkit/dist/promise/mutex.js generated vendored Normal file
View file

@ -0,0 +1,72 @@
const require_semaphore = require("./semaphore.js");
//#region src/promise/mutex.ts
/**
* A Mutex (mutual exclusion lock) for async functions.
* It allows only one async task to access a critical section at a time.
*
* @example
* const mutex = new Mutex();
*
* async function criticalSection() {
* await mutex.acquire();
* try {
* // This code section cannot be executed simultaneously
* } finally {
* mutex.release();
* }
* }
*
* criticalSection();
* criticalSection(); // This call will wait until the first call releases the mutex.
*/
var Mutex = class {
semaphore = new require_semaphore.Semaphore(1);
/**
* Checks if the mutex is currently locked.
* @returns True if the mutex is locked, false otherwise.
*
* @example
* const mutex = new Mutex();
* console.log(mutex.isLocked); // false
* await mutex.acquire();
* console.log(mutex.isLocked); // true
* mutex.release();
* console.log(mutex.isLocked); // false
*/
get isLocked() {
return this.semaphore.available === 0;
}
/**
* Acquires the mutex, blocking if necessary until it is available.
* @returns A promise that resolves when the mutex is acquired.
*
* @example
* const mutex = new Mutex();
* await mutex.acquire();
* try {
* // This code section cannot be executed simultaneously
* } finally {
* mutex.release();
* }
*/
async acquire() {
return this.semaphore.acquire();
}
/**
* Releases the mutex, allowing another waiting task to proceed.
*
* @example
* const mutex = new Mutex();
* await mutex.acquire();
* try {
* // This code section cannot be executed simultaneously
* } finally {
* mutex.release(); // Allows another waiting task to proceed.
* }
*/
release() {
this.semaphore.release();
}
};
//#endregion
exports.Mutex = Mutex;

View file

@ -0,0 +1,72 @@
import { Semaphore } from "./semaphore.mjs";
//#region src/promise/mutex.ts
/**
* A Mutex (mutual exclusion lock) for async functions.
* It allows only one async task to access a critical section at a time.
*
* @example
* const mutex = new Mutex();
*
* async function criticalSection() {
* await mutex.acquire();
* try {
* // This code section cannot be executed simultaneously
* } finally {
* mutex.release();
* }
* }
*
* criticalSection();
* criticalSection(); // This call will wait until the first call releases the mutex.
*/
var Mutex = class {
semaphore = new Semaphore(1);
/**
* Checks if the mutex is currently locked.
* @returns True if the mutex is locked, false otherwise.
*
* @example
* const mutex = new Mutex();
* console.log(mutex.isLocked); // false
* await mutex.acquire();
* console.log(mutex.isLocked); // true
* mutex.release();
* console.log(mutex.isLocked); // false
*/
get isLocked() {
return this.semaphore.available === 0;
}
/**
* Acquires the mutex, blocking if necessary until it is available.
* @returns A promise that resolves when the mutex is acquired.
*
* @example
* const mutex = new Mutex();
* await mutex.acquire();
* try {
* // This code section cannot be executed simultaneously
* } finally {
* mutex.release();
* }
*/
async acquire() {
return this.semaphore.acquire();
}
/**
* Releases the mutex, allowing another waiting task to proceed.
*
* @example
* const mutex = new Mutex();
* await mutex.acquire();
* try {
* // This code section cannot be executed simultaneously
* } finally {
* mutex.release(); // Allows another waiting task to proceed.
* }
*/
release() {
this.semaphore.release();
}
};
//#endregion
export { Mutex };

View file

@ -0,0 +1,82 @@
//#region src/promise/semaphore.d.ts
/**
* A counting semaphore for async functions that manages available permits.
* Semaphores are mainly used to limit the number of concurrent async tasks.
*
* Each `acquire` operation takes a permit or waits until one is available.
* Each `release` operation adds a permit, potentially allowing a waiting task to proceed.
*
* The semaphore ensures fairness by maintaining a FIFO (First In, First Out) order for acquirers.
*
* @example
* const sema = new Semaphore(2);
*
* async function task() {
* await sema.acquire();
* try {
* // This code can only be executed by two tasks at the same time
* } finally {
* sema.release();
* }
* }
*
* task();
* task();
* task(); // This task will wait until one of the previous tasks releases the semaphore.
*/
declare class Semaphore {
/**
* The maximum number of concurrent operations allowed.
* @type {number}
*/
capacity: number;
/**
* The number of available permits.
* @type {number}
*/
available: number;
private deferredTasks;
/**
* Creates an instance of Semaphore.
* @param capacity - The maximum number of concurrent operations allowed.
*
* @example
* const sema = new Semaphore(3); // Allows up to 3 concurrent operations.
*/
constructor(capacity: number);
/**
* Acquires a semaphore, blocking if necessary until one is available.
* @returns A promise that resolves when the semaphore is acquired.
*
* @example
* const sema = new Semaphore(1);
*
* async function criticalSection() {
* await sema.acquire();
* try {
* // This code section cannot be executed simultaneously
* } finally {
* sema.release();
* }
* }
*/
acquire(): Promise<void>;
/**
* Releases a semaphore, allowing one more operation to proceed.
*
* @example
* const sema = new Semaphore(1);
*
* async function task() {
* await sema.acquire();
* try {
* // This code can only be executed by two tasks at the same time
* } finally {
* sema.release(); // Allows another waiting task to proceed.
* }
* }
*/
release(): void;
}
//#endregion
export { Semaphore };

View file

@ -0,0 +1,82 @@
//#region src/promise/semaphore.d.ts
/**
* A counting semaphore for async functions that manages available permits.
* Semaphores are mainly used to limit the number of concurrent async tasks.
*
* Each `acquire` operation takes a permit or waits until one is available.
* Each `release` operation adds a permit, potentially allowing a waiting task to proceed.
*
* The semaphore ensures fairness by maintaining a FIFO (First In, First Out) order for acquirers.
*
* @example
* const sema = new Semaphore(2);
*
* async function task() {
* await sema.acquire();
* try {
* // This code can only be executed by two tasks at the same time
* } finally {
* sema.release();
* }
* }
*
* task();
* task();
* task(); // This task will wait until one of the previous tasks releases the semaphore.
*/
declare class Semaphore {
/**
* The maximum number of concurrent operations allowed.
* @type {number}
*/
capacity: number;
/**
* The number of available permits.
* @type {number}
*/
available: number;
private deferredTasks;
/**
* Creates an instance of Semaphore.
* @param capacity - The maximum number of concurrent operations allowed.
*
* @example
* const sema = new Semaphore(3); // Allows up to 3 concurrent operations.
*/
constructor(capacity: number);
/**
* Acquires a semaphore, blocking if necessary until one is available.
* @returns A promise that resolves when the semaphore is acquired.
*
* @example
* const sema = new Semaphore(1);
*
* async function criticalSection() {
* await sema.acquire();
* try {
* // This code section cannot be executed simultaneously
* } finally {
* sema.release();
* }
* }
*/
acquire(): Promise<void>;
/**
* Releases a semaphore, allowing one more operation to proceed.
*
* @example
* const sema = new Semaphore(1);
*
* async function task() {
* await sema.acquire();
* try {
* // This code can only be executed by two tasks at the same time
* } finally {
* sema.release(); // Allows another waiting task to proceed.
* }
* }
*/
release(): void;
}
//#endregion
export { Semaphore };

View file

@ -0,0 +1,100 @@
//#region src/promise/semaphore.ts
/**
* A counting semaphore for async functions that manages available permits.
* Semaphores are mainly used to limit the number of concurrent async tasks.
*
* Each `acquire` operation takes a permit or waits until one is available.
* Each `release` operation adds a permit, potentially allowing a waiting task to proceed.
*
* The semaphore ensures fairness by maintaining a FIFO (First In, First Out) order for acquirers.
*
* @example
* const sema = new Semaphore(2);
*
* async function task() {
* await sema.acquire();
* try {
* // This code can only be executed by two tasks at the same time
* } finally {
* sema.release();
* }
* }
*
* task();
* task();
* task(); // This task will wait until one of the previous tasks releases the semaphore.
*/
var Semaphore = class {
/**
* The maximum number of concurrent operations allowed.
* @type {number}
*/
capacity;
/**
* The number of available permits.
* @type {number}
*/
available;
deferredTasks = [];
/**
* Creates an instance of Semaphore.
* @param capacity - The maximum number of concurrent operations allowed.
*
* @example
* const sema = new Semaphore(3); // Allows up to 3 concurrent operations.
*/
constructor(capacity) {
this.capacity = capacity;
this.available = capacity;
}
/**
* Acquires a semaphore, blocking if necessary until one is available.
* @returns A promise that resolves when the semaphore is acquired.
*
* @example
* const sema = new Semaphore(1);
*
* async function criticalSection() {
* await sema.acquire();
* try {
* // This code section cannot be executed simultaneously
* } finally {
* sema.release();
* }
* }
*/
async acquire() {
if (this.available > 0) {
this.available--;
return;
}
return new Promise((resolve) => {
this.deferredTasks.push(resolve);
});
}
/**
* Releases a semaphore, allowing one more operation to proceed.
*
* @example
* const sema = new Semaphore(1);
*
* async function task() {
* await sema.acquire();
* try {
* // This code can only be executed by two tasks at the same time
* } finally {
* sema.release(); // Allows another waiting task to proceed.
* }
* }
*/
release() {
const deferredTask = this.deferredTasks.shift();
if (deferredTask != null) {
deferredTask();
return;
}
if (this.available < this.capacity) this.available++;
}
};
//#endregion
exports.Semaphore = Semaphore;

View file

@ -0,0 +1,100 @@
//#region src/promise/semaphore.ts
/**
* A counting semaphore for async functions that manages available permits.
* Semaphores are mainly used to limit the number of concurrent async tasks.
*
* Each `acquire` operation takes a permit or waits until one is available.
* Each `release` operation adds a permit, potentially allowing a waiting task to proceed.
*
* The semaphore ensures fairness by maintaining a FIFO (First In, First Out) order for acquirers.
*
* @example
* const sema = new Semaphore(2);
*
* async function task() {
* await sema.acquire();
* try {
* // This code can only be executed by two tasks at the same time
* } finally {
* sema.release();
* }
* }
*
* task();
* task();
* task(); // This task will wait until one of the previous tasks releases the semaphore.
*/
var Semaphore = class {
/**
* The maximum number of concurrent operations allowed.
* @type {number}
*/
capacity;
/**
* The number of available permits.
* @type {number}
*/
available;
deferredTasks = [];
/**
* Creates an instance of Semaphore.
* @param capacity - The maximum number of concurrent operations allowed.
*
* @example
* const sema = new Semaphore(3); // Allows up to 3 concurrent operations.
*/
constructor(capacity) {
this.capacity = capacity;
this.available = capacity;
}
/**
* Acquires a semaphore, blocking if necessary until one is available.
* @returns A promise that resolves when the semaphore is acquired.
*
* @example
* const sema = new Semaphore(1);
*
* async function criticalSection() {
* await sema.acquire();
* try {
* // This code section cannot be executed simultaneously
* } finally {
* sema.release();
* }
* }
*/
async acquire() {
if (this.available > 0) {
this.available--;
return;
}
return new Promise((resolve) => {
this.deferredTasks.push(resolve);
});
}
/**
* Releases a semaphore, allowing one more operation to proceed.
*
* @example
* const sema = new Semaphore(1);
*
* async function task() {
* await sema.acquire();
* try {
* // This code can only be executed by two tasks at the same time
* } finally {
* sema.release(); // Allows another waiting task to proceed.
* }
* }
*/
release() {
const deferredTask = this.deferredTasks.shift();
if (deferredTask != null) {
deferredTask();
return;
}
if (this.available < this.capacity) this.available++;
}
};
//#endregion
export { Semaphore };

View file

@ -0,0 +1,41 @@
//#region src/promise/timeout.d.ts
interface TimeoutOptions {
signal?: AbortSignal;
}
/**
* Returns a promise that rejects with a `TimeoutError` after a specified delay.
*
* You can pass an `AbortSignal` to cancel the timeout. Unlike most `AbortSignal`-aware
* APIs, aborting does **not** reject the promise. A `timeout` only exists to lose a
* `Promise.race`, so cancelling it leaves the promise pending forever, allowing the
* operation it guards to settle on its own. The underlying timer and abort listener
* are cleared on abort, so nothing is leaked.
*
* @param ms - The delay duration in milliseconds.
* @param options - The options object.
* @param options.signal - An optional AbortSignal to cancel the timeout. When aborted, the returned promise never settles.
* @returns A promise that rejects with a `TimeoutError` after the specified delay, or never settles if aborted.
* @throws {TimeoutError} Throws a `TimeoutError` after the specified delay.
*
* @example
* try {
* await timeout(1000); // Timeout exception after 1 second
* } catch (error) {
* console.error(error); // Will log 'The operation was timed out'
* }
*
* @example
* // Cancelling the timeout lifts the time limit instead of throwing.
* const controller = new AbortController();
* setTimeout(() => controller.abort(), 50);
*
* const result = await Promise.race([
* doWork(),
* timeout(1000, { signal: controller.signal }), // never rejects once aborted
* ]);
*/
declare function timeout(ms: number, {
signal
}?: TimeoutOptions): Promise<never>;
//#endregion
export { timeout };

View file

@ -0,0 +1,41 @@
//#region src/promise/timeout.d.ts
interface TimeoutOptions {
signal?: AbortSignal;
}
/**
* Returns a promise that rejects with a `TimeoutError` after a specified delay.
*
* You can pass an `AbortSignal` to cancel the timeout. Unlike most `AbortSignal`-aware
* APIs, aborting does **not** reject the promise. A `timeout` only exists to lose a
* `Promise.race`, so cancelling it leaves the promise pending forever, allowing the
* operation it guards to settle on its own. The underlying timer and abort listener
* are cleared on abort, so nothing is leaked.
*
* @param ms - The delay duration in milliseconds.
* @param options - The options object.
* @param options.signal - An optional AbortSignal to cancel the timeout. When aborted, the returned promise never settles.
* @returns A promise that rejects with a `TimeoutError` after the specified delay, or never settles if aborted.
* @throws {TimeoutError} Throws a `TimeoutError` after the specified delay.
*
* @example
* try {
* await timeout(1000); // Timeout exception after 1 second
* } catch (error) {
* console.error(error); // Will log 'The operation was timed out'
* }
*
* @example
* // Cancelling the timeout lifts the time limit instead of throwing.
* const controller = new AbortController();
* setTimeout(() => controller.abort(), 50);
*
* const result = await Promise.race([
* doWork(),
* timeout(1000, { signal: controller.signal }), // never rejects once aborted
* ]);
*/
declare function timeout(ms: number, {
signal
}?: TimeoutOptions): Promise<never>;
//#endregion
export { timeout };

View file

@ -0,0 +1,49 @@
const require_TimeoutError = require("../error/TimeoutError.js");
//#region src/promise/timeout.ts
/**
* Returns a promise that rejects with a `TimeoutError` after a specified delay.
*
* You can pass an `AbortSignal` to cancel the timeout. Unlike most `AbortSignal`-aware
* APIs, aborting does **not** reject the promise. A `timeout` only exists to lose a
* `Promise.race`, so cancelling it leaves the promise pending forever, allowing the
* operation it guards to settle on its own. The underlying timer and abort listener
* are cleared on abort, so nothing is leaked.
*
* @param ms - The delay duration in milliseconds.
* @param options - The options object.
* @param options.signal - An optional AbortSignal to cancel the timeout. When aborted, the returned promise never settles.
* @returns A promise that rejects with a `TimeoutError` after the specified delay, or never settles if aborted.
* @throws {TimeoutError} Throws a `TimeoutError` after the specified delay.
*
* @example
* try {
* await timeout(1000); // Timeout exception after 1 second
* } catch (error) {
* console.error(error); // Will log 'The operation was timed out'
* }
*
* @example
* // Cancelling the timeout lifts the time limit instead of throwing.
* const controller = new AbortController();
* setTimeout(() => controller.abort(), 50);
*
* const result = await Promise.race([
* doWork(),
* timeout(1000, { signal: controller.signal }), // never rejects once aborted
* ]);
*/
function timeout(ms, { signal } = {}) {
return new Promise((_resolve, reject) => {
const abortHandler = () => {
clearTimeout(timeoutId);
};
if (signal?.aborted) return;
const timeoutId = setTimeout(() => {
signal?.removeEventListener("abort", abortHandler);
reject(new require_TimeoutError.TimeoutError());
}, ms);
signal?.addEventListener("abort", abortHandler, { once: true });
});
}
//#endregion
exports.timeout = timeout;

View file

@ -0,0 +1,49 @@
import { TimeoutError } from "../error/TimeoutError.mjs";
//#region src/promise/timeout.ts
/**
* Returns a promise that rejects with a `TimeoutError` after a specified delay.
*
* You can pass an `AbortSignal` to cancel the timeout. Unlike most `AbortSignal`-aware
* APIs, aborting does **not** reject the promise. A `timeout` only exists to lose a
* `Promise.race`, so cancelling it leaves the promise pending forever, allowing the
* operation it guards to settle on its own. The underlying timer and abort listener
* are cleared on abort, so nothing is leaked.
*
* @param ms - The delay duration in milliseconds.
* @param options - The options object.
* @param options.signal - An optional AbortSignal to cancel the timeout. When aborted, the returned promise never settles.
* @returns A promise that rejects with a `TimeoutError` after the specified delay, or never settles if aborted.
* @throws {TimeoutError} Throws a `TimeoutError` after the specified delay.
*
* @example
* try {
* await timeout(1000); // Timeout exception after 1 second
* } catch (error) {
* console.error(error); // Will log 'The operation was timed out'
* }
*
* @example
* // Cancelling the timeout lifts the time limit instead of throwing.
* const controller = new AbortController();
* setTimeout(() => controller.abort(), 50);
*
* const result = await Promise.race([
* doWork(),
* timeout(1000, { signal: controller.signal }), // never rejects once aborted
* ]);
*/
function timeout(ms, { signal } = {}) {
return new Promise((_resolve, reject) => {
const abortHandler = () => {
clearTimeout(timeoutId);
};
if (signal?.aborted) return;
const timeoutId = setTimeout(() => {
signal?.removeEventListener("abort", abortHandler);
reject(new TimeoutError());
}, ms);
signal?.addEventListener("abort", abortHandler, { once: true });
});
}
//#endregion
export { timeout };

View file

@ -0,0 +1,47 @@
//#region src/promise/withTimeout.d.ts
interface WithTimeoutOptions {
signal?: AbortSignal;
}
/**
* Executes an async function and enforces a timeout.
*
* If the promise does not resolve within the specified time,
* the timeout will trigger and the returned promise will be rejected.
*
* You can pass an `AbortSignal` to cancel the timeout. Aborting the signal lifts the
* time limit: the timeout stops counting and `run`'s promise is awaited without a
* deadline. It does not reject the returned promise or abort `run` itself pass the
* same signal into `run` if you also want to cancel the underlying work.
*
* @template T
* @param run - A function that returns a promise to be executed.
* @param ms - The timeout duration in milliseconds.
* @param options - The options object.
* @param options.signal - An optional AbortSignal to cancel the timeout. When aborted, the time limit is lifted.
* @returns A promise that resolves with the result of the `run` function or rejects if the timeout is reached.
*
* @example
* async function fetchData() {
* const response = await fetch('https://example.com/data');
* return response.json();
* }
*
* try {
* const data = await withTimeout(fetchData, 1000);
* console.log(data); // Logs the fetched data if `fetchData` is resolved within 1 second.
* } catch (error) {
* console.error(error); // Will log 'TimeoutError' if `fetchData` is not resolved within 1 second.
* }
*
* @example
* // Lift the time limit when the user opts to keep waiting.
* const controller = new AbortController();
* keepWaitingButton.onclick = () => controller.abort();
*
* const data = await withTimeout(fetchData, 1000, { signal: controller.signal });
*/
declare function withTimeout<T>(run: () => Promise<T>, ms: number, {
signal
}?: WithTimeoutOptions): Promise<T>;
//#endregion
export { withTimeout };

View file

@ -0,0 +1,47 @@
//#region src/promise/withTimeout.d.ts
interface WithTimeoutOptions {
signal?: AbortSignal;
}
/**
* Executes an async function and enforces a timeout.
*
* If the promise does not resolve within the specified time,
* the timeout will trigger and the returned promise will be rejected.
*
* You can pass an `AbortSignal` to cancel the timeout. Aborting the signal lifts the
* time limit: the timeout stops counting and `run`'s promise is awaited without a
* deadline. It does not reject the returned promise or abort `run` itself pass the
* same signal into `run` if you also want to cancel the underlying work.
*
* @template T
* @param run - A function that returns a promise to be executed.
* @param ms - The timeout duration in milliseconds.
* @param options - The options object.
* @param options.signal - An optional AbortSignal to cancel the timeout. When aborted, the time limit is lifted.
* @returns A promise that resolves with the result of the `run` function or rejects if the timeout is reached.
*
* @example
* async function fetchData() {
* const response = await fetch('https://example.com/data');
* return response.json();
* }
*
* try {
* const data = await withTimeout(fetchData, 1000);
* console.log(data); // Logs the fetched data if `fetchData` is resolved within 1 second.
* } catch (error) {
* console.error(error); // Will log 'TimeoutError' if `fetchData` is not resolved within 1 second.
* }
*
* @example
* // Lift the time limit when the user opts to keep waiting.
* const controller = new AbortController();
* keepWaitingButton.onclick = () => controller.abort();
*
* const data = await withTimeout(fetchData, 1000, { signal: controller.signal });
*/
declare function withTimeout<T>(run: () => Promise<T>, ms: number, {
signal
}?: WithTimeoutOptions): Promise<T>;
//#endregion
export { withTimeout };

View file

@ -0,0 +1,45 @@
const require_timeout = require("./timeout.js");
//#region src/promise/withTimeout.ts
/**
* Executes an async function and enforces a timeout.
*
* If the promise does not resolve within the specified time,
* the timeout will trigger and the returned promise will be rejected.
*
* You can pass an `AbortSignal` to cancel the timeout. Aborting the signal lifts the
* time limit: the timeout stops counting and `run`'s promise is awaited without a
* deadline. It does not reject the returned promise or abort `run` itself pass the
* same signal into `run` if you also want to cancel the underlying work.
*
* @template T
* @param run - A function that returns a promise to be executed.
* @param ms - The timeout duration in milliseconds.
* @param options - The options object.
* @param options.signal - An optional AbortSignal to cancel the timeout. When aborted, the time limit is lifted.
* @returns A promise that resolves with the result of the `run` function or rejects if the timeout is reached.
*
* @example
* async function fetchData() {
* const response = await fetch('https://example.com/data');
* return response.json();
* }
*
* try {
* const data = await withTimeout(fetchData, 1000);
* console.log(data); // Logs the fetched data if `fetchData` is resolved within 1 second.
* } catch (error) {
* console.error(error); // Will log 'TimeoutError' if `fetchData` is not resolved within 1 second.
* }
*
* @example
* // Lift the time limit when the user opts to keep waiting.
* const controller = new AbortController();
* keepWaitingButton.onclick = () => controller.abort();
*
* const data = await withTimeout(fetchData, 1000, { signal: controller.signal });
*/
async function withTimeout(run, ms, { signal } = {}) {
return Promise.race([run(), require_timeout.timeout(ms, { signal })]);
}
//#endregion
exports.withTimeout = withTimeout;

View file

@ -0,0 +1,45 @@
import { timeout } from "./timeout.mjs";
//#region src/promise/withTimeout.ts
/**
* Executes an async function and enforces a timeout.
*
* If the promise does not resolve within the specified time,
* the timeout will trigger and the returned promise will be rejected.
*
* You can pass an `AbortSignal` to cancel the timeout. Aborting the signal lifts the
* time limit: the timeout stops counting and `run`'s promise is awaited without a
* deadline. It does not reject the returned promise or abort `run` itself pass the
* same signal into `run` if you also want to cancel the underlying work.
*
* @template T
* @param run - A function that returns a promise to be executed.
* @param ms - The timeout duration in milliseconds.
* @param options - The options object.
* @param options.signal - An optional AbortSignal to cancel the timeout. When aborted, the time limit is lifted.
* @returns A promise that resolves with the result of the `run` function or rejects if the timeout is reached.
*
* @example
* async function fetchData() {
* const response = await fetch('https://example.com/data');
* return response.json();
* }
*
* try {
* const data = await withTimeout(fetchData, 1000);
* console.log(data); // Logs the fetched data if `fetchData` is resolved within 1 second.
* } catch (error) {
* console.error(error); // Will log 'TimeoutError' if `fetchData` is not resolved within 1 second.
* }
*
* @example
* // Lift the time limit when the user opts to keep waiting.
* const controller = new AbortController();
* keepWaitingButton.onclick = () => controller.abort();
*
* const data = await withTimeout(fetchData, 1000, { signal: controller.signal });
*/
async function withTimeout(run, ms, { signal } = {}) {
return Promise.race([run(), timeout(ms, { signal })]);
}
//#endregion
export { withTimeout };