Refactor routing in App component to enhance navigation and improve error handling by integrating dynamic routes and updating the NotFound route.

This commit is contained in:
becarta
2025-05-23 12:43:00 +02:00
parent f40db0f5c9
commit a544759a3b
11127 changed files with 1647032 additions and 0 deletions

126
node_modules/p-queue/dist/index.d.ts generated vendored Normal file
View File

@@ -0,0 +1,126 @@
import { EventEmitter } from 'eventemitter3';
import { type Queue, type RunFunction } from './queue.js';
import PriorityQueue from './priority-queue.js';
import { type QueueAddOptions, type Options, type TaskOptions } from './options.js';
type Task<TaskResultType> = ((options: TaskOptions) => PromiseLike<TaskResultType>) | ((options: TaskOptions) => TaskResultType);
type EventName = 'active' | 'idle' | 'empty' | 'add' | 'next' | 'completed' | 'error';
/**
Promise queue with concurrency control.
*/
export default class PQueue<QueueType extends Queue<RunFunction, EnqueueOptionsType> = PriorityQueue, EnqueueOptionsType extends QueueAddOptions = QueueAddOptions> extends EventEmitter<EventName> {
#private;
/**
Per-operation timeout in milliseconds. Operations fulfill once `timeout` elapses if they haven't already.
Applies to each future operation.
*/
timeout?: number;
constructor(options?: Options<QueueType, EnqueueOptionsType>);
get concurrency(): number;
set concurrency(newConcurrency: number);
/**
Updates the priority of a promise function by its id, affecting its execution order. Requires a defined concurrency limit to take effect.
For example, this can be used to prioritize a promise function to run earlier.
```js
import PQueue from 'p-queue';
const queue = new PQueue({concurrency: 1});
queue.add(async () => '🦄', {priority: 1});
queue.add(async () => '🦀', {priority: 0, id: '🦀'});
queue.add(async () => '🦄', {priority: 1});
queue.add(async () => '🦄', {priority: 1});
queue.setPriority('🦀', 2);
```
In this case, the promise function with `id: '🦀'` runs second.
You can also deprioritize a promise function to delay its execution:
```js
import PQueue from 'p-queue';
const queue = new PQueue({concurrency: 1});
queue.add(async () => '🦄', {priority: 1});
queue.add(async () => '🦀', {priority: 1, id: '🦀'});
queue.add(async () => '🦄');
queue.add(async () => '🦄', {priority: 0});
queue.setPriority('🦀', -1);
```
Here, the promise function with `id: '🦀'` executes last.
*/
setPriority(id: string, priority: number): void;
/**
Adds a sync or async task to the queue. Always returns a promise.
*/
add<TaskResultType>(function_: Task<TaskResultType>, options: {
throwOnTimeout: true;
} & Exclude<EnqueueOptionsType, 'throwOnTimeout'>): Promise<TaskResultType>;
add<TaskResultType>(function_: Task<TaskResultType>, options?: Partial<EnqueueOptionsType>): Promise<TaskResultType | void>;
/**
Same as `.add()`, but accepts an array of sync or async functions.
@returns A promise that resolves when all functions are resolved.
*/
addAll<TaskResultsType>(functions: ReadonlyArray<Task<TaskResultsType>>, options?: {
throwOnTimeout: true;
} & Partial<Exclude<EnqueueOptionsType, 'throwOnTimeout'>>): Promise<TaskResultsType[]>;
addAll<TaskResultsType>(functions: ReadonlyArray<Task<TaskResultsType>>, options?: Partial<EnqueueOptionsType>): Promise<Array<TaskResultsType | void>>;
/**
Start (or resume) executing enqueued tasks within concurrency limit. No need to call this if queue is not paused (via `options.autoStart = false` or by `.pause()` method.)
*/
start(): this;
/**
Put queue execution on hold.
*/
pause(): void;
/**
Clear the queue.
*/
clear(): void;
/**
Can be called multiple times. Useful if you for example add additional items at a later time.
@returns A promise that settles when the queue becomes empty.
*/
onEmpty(): Promise<void>;
/**
@returns A promise that settles when the queue size is less than the given limit: `queue.size < limit`.
If you want to avoid having the queue grow beyond a certain size you can `await queue.onSizeLessThan()` before adding a new item.
Note that this only limits the number of items waiting to start. There could still be up to `concurrency` jobs already running that this call does not include in its calculation.
*/
onSizeLessThan(limit: number): Promise<void>;
/**
The difference with `.onEmpty` is that `.onIdle` guarantees that all work from the queue has finished. `.onEmpty` merely signals that the queue is empty, but it could mean that some promises haven't completed yet.
@returns A promise that settles when the queue becomes empty, and all promises have completed; `queue.size === 0 && queue.pending === 0`.
*/
onIdle(): Promise<void>;
/**
Size of the queue, the number of queued items waiting to run.
*/
get size(): number;
/**
Size of the queue, filtered by the given options.
For example, this can be used to find the number of items remaining in the queue with a specific priority level.
*/
sizeBy(options: Readonly<Partial<EnqueueOptionsType>>): number;
/**
Number of running items (no longer in the queue).
*/
get pending(): number;
/**
Whether the queue is currently paused.
*/
get isPaused(): boolean;
}
export type { Queue } from './queue.js';
export { type QueueAddOptions, type Options } from './options.js';

353
node_modules/p-queue/dist/index.js generated vendored Normal file
View File

@@ -0,0 +1,353 @@
import { EventEmitter } from 'eventemitter3';
import pTimeout, { TimeoutError } from 'p-timeout';
import PriorityQueue from './priority-queue.js';
/**
Promise queue with concurrency control.
*/
export default class PQueue extends EventEmitter {
#carryoverConcurrencyCount;
#isIntervalIgnored;
#intervalCount = 0;
#intervalCap;
#interval;
#intervalEnd = 0;
#intervalId;
#timeoutId;
#queue;
#queueClass;
#pending = 0;
// The `!` is needed because of https://github.com/microsoft/TypeScript/issues/32194
#concurrency;
#isPaused;
#throwOnTimeout;
// Use to assign a unique identifier to a promise function, if not explicitly specified
#idAssigner = 1n;
/**
Per-operation timeout in milliseconds. Operations fulfill once `timeout` elapses if they haven't already.
Applies to each future operation.
*/
timeout;
// TODO: The `throwOnTimeout` option should affect the return types of `add()` and `addAll()`
constructor(options) {
super();
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
options = {
carryoverConcurrencyCount: false,
intervalCap: Number.POSITIVE_INFINITY,
interval: 0,
concurrency: Number.POSITIVE_INFINITY,
autoStart: true,
queueClass: PriorityQueue,
...options,
};
if (!(typeof options.intervalCap === 'number' && options.intervalCap >= 1)) {
throw new TypeError(`Expected \`intervalCap\` to be a number from 1 and up, got \`${options.intervalCap?.toString() ?? ''}\` (${typeof options.intervalCap})`);
}
if (options.interval === undefined || !(Number.isFinite(options.interval) && options.interval >= 0)) {
throw new TypeError(`Expected \`interval\` to be a finite number >= 0, got \`${options.interval?.toString() ?? ''}\` (${typeof options.interval})`);
}
this.#carryoverConcurrencyCount = options.carryoverConcurrencyCount;
this.#isIntervalIgnored = options.intervalCap === Number.POSITIVE_INFINITY || options.interval === 0;
this.#intervalCap = options.intervalCap;
this.#interval = options.interval;
this.#queue = new options.queueClass();
this.#queueClass = options.queueClass;
this.concurrency = options.concurrency;
this.timeout = options.timeout;
this.#throwOnTimeout = options.throwOnTimeout === true;
this.#isPaused = options.autoStart === false;
}
get #doesIntervalAllowAnother() {
return this.#isIntervalIgnored || this.#intervalCount < this.#intervalCap;
}
get #doesConcurrentAllowAnother() {
return this.#pending < this.#concurrency;
}
#next() {
this.#pending--;
this.#tryToStartAnother();
this.emit('next');
}
#onResumeInterval() {
this.#onInterval();
this.#initializeIntervalIfNeeded();
this.#timeoutId = undefined;
}
get #isIntervalPaused() {
const now = Date.now();
if (this.#intervalId === undefined) {
const delay = this.#intervalEnd - now;
if (delay < 0) {
// Act as the interval was done
// We don't need to resume it here because it will be resumed on line 160
this.#intervalCount = (this.#carryoverConcurrencyCount) ? this.#pending : 0;
}
else {
// Act as the interval is pending
if (this.#timeoutId === undefined) {
this.#timeoutId = setTimeout(() => {
this.#onResumeInterval();
}, delay);
}
return true;
}
}
return false;
}
#tryToStartAnother() {
if (this.#queue.size === 0) {
// We can clear the interval ("pause")
// Because we can redo it later ("resume")
if (this.#intervalId) {
clearInterval(this.#intervalId);
}
this.#intervalId = undefined;
this.emit('empty');
if (this.#pending === 0) {
this.emit('idle');
}
return false;
}
if (!this.#isPaused) {
const canInitializeInterval = !this.#isIntervalPaused;
if (this.#doesIntervalAllowAnother && this.#doesConcurrentAllowAnother) {
const job = this.#queue.dequeue();
if (!job) {
return false;
}
this.emit('active');
job();
if (canInitializeInterval) {
this.#initializeIntervalIfNeeded();
}
return true;
}
}
return false;
}
#initializeIntervalIfNeeded() {
if (this.#isIntervalIgnored || this.#intervalId !== undefined) {
return;
}
this.#intervalId = setInterval(() => {
this.#onInterval();
}, this.#interval);
this.#intervalEnd = Date.now() + this.#interval;
}
#onInterval() {
if (this.#intervalCount === 0 && this.#pending === 0 && this.#intervalId) {
clearInterval(this.#intervalId);
this.#intervalId = undefined;
}
this.#intervalCount = this.#carryoverConcurrencyCount ? this.#pending : 0;
this.#processQueue();
}
/**
Executes all queued functions until it reaches the limit.
*/
#processQueue() {
// eslint-disable-next-line no-empty
while (this.#tryToStartAnother()) { }
}
get concurrency() {
return this.#concurrency;
}
set concurrency(newConcurrency) {
if (!(typeof newConcurrency === 'number' && newConcurrency >= 1)) {
throw new TypeError(`Expected \`concurrency\` to be a number from 1 and up, got \`${newConcurrency}\` (${typeof newConcurrency})`);
}
this.#concurrency = newConcurrency;
this.#processQueue();
}
async #throwOnAbort(signal) {
return new Promise((_resolve, reject) => {
signal.addEventListener('abort', () => {
reject(signal.reason);
}, { once: true });
});
}
/**
Updates the priority of a promise function by its id, affecting its execution order. Requires a defined concurrency limit to take effect.
For example, this can be used to prioritize a promise function to run earlier.
```js
import PQueue from 'p-queue';
const queue = new PQueue({concurrency: 1});
queue.add(async () => '🦄', {priority: 1});
queue.add(async () => '🦀', {priority: 0, id: '🦀'});
queue.add(async () => '🦄', {priority: 1});
queue.add(async () => '🦄', {priority: 1});
queue.setPriority('🦀', 2);
```
In this case, the promise function with `id: '🦀'` runs second.
You can also deprioritize a promise function to delay its execution:
```js
import PQueue from 'p-queue';
const queue = new PQueue({concurrency: 1});
queue.add(async () => '🦄', {priority: 1});
queue.add(async () => '🦀', {priority: 1, id: '🦀'});
queue.add(async () => '🦄');
queue.add(async () => '🦄', {priority: 0});
queue.setPriority('🦀', -1);
```
Here, the promise function with `id: '🦀'` executes last.
*/
setPriority(id, priority) {
this.#queue.setPriority(id, priority);
}
async add(function_, options = {}) {
// In case `id` is not defined.
options.id ??= (this.#idAssigner++).toString();
options = {
timeout: this.timeout,
throwOnTimeout: this.#throwOnTimeout,
...options,
};
return new Promise((resolve, reject) => {
this.#queue.enqueue(async () => {
this.#pending++;
this.#intervalCount++;
try {
options.signal?.throwIfAborted();
let operation = function_({ signal: options.signal });
if (options.timeout) {
operation = pTimeout(Promise.resolve(operation), { milliseconds: options.timeout });
}
if (options.signal) {
operation = Promise.race([operation, this.#throwOnAbort(options.signal)]);
}
const result = await operation;
resolve(result);
this.emit('completed', result);
}
catch (error) {
if (error instanceof TimeoutError && !options.throwOnTimeout) {
resolve();
return;
}
reject(error);
this.emit('error', error);
}
finally {
this.#next();
}
}, options);
this.emit('add');
this.#tryToStartAnother();
});
}
async addAll(functions, options) {
return Promise.all(functions.map(async (function_) => this.add(function_, options)));
}
/**
Start (or resume) executing enqueued tasks within concurrency limit. No need to call this if queue is not paused (via `options.autoStart = false` or by `.pause()` method.)
*/
start() {
if (!this.#isPaused) {
return this;
}
this.#isPaused = false;
this.#processQueue();
return this;
}
/**
Put queue execution on hold.
*/
pause() {
this.#isPaused = true;
}
/**
Clear the queue.
*/
clear() {
this.#queue = new this.#queueClass();
}
/**
Can be called multiple times. Useful if you for example add additional items at a later time.
@returns A promise that settles when the queue becomes empty.
*/
async onEmpty() {
// Instantly resolve if the queue is empty
if (this.#queue.size === 0) {
return;
}
await this.#onEvent('empty');
}
/**
@returns A promise that settles when the queue size is less than the given limit: `queue.size < limit`.
If you want to avoid having the queue grow beyond a certain size you can `await queue.onSizeLessThan()` before adding a new item.
Note that this only limits the number of items waiting to start. There could still be up to `concurrency` jobs already running that this call does not include in its calculation.
*/
async onSizeLessThan(limit) {
// Instantly resolve if the queue is empty.
if (this.#queue.size < limit) {
return;
}
await this.#onEvent('next', () => this.#queue.size < limit);
}
/**
The difference with `.onEmpty` is that `.onIdle` guarantees that all work from the queue has finished. `.onEmpty` merely signals that the queue is empty, but it could mean that some promises haven't completed yet.
@returns A promise that settles when the queue becomes empty, and all promises have completed; `queue.size === 0 && queue.pending === 0`.
*/
async onIdle() {
// Instantly resolve if none pending and if nothing else is queued
if (this.#pending === 0 && this.#queue.size === 0) {
return;
}
await this.#onEvent('idle');
}
async #onEvent(event, filter) {
return new Promise(resolve => {
const listener = () => {
if (filter && !filter()) {
return;
}
this.off(event, listener);
resolve();
};
this.on(event, listener);
});
}
/**
Size of the queue, the number of queued items waiting to run.
*/
get size() {
return this.#queue.size;
}
/**
Size of the queue, filtered by the given options.
For example, this can be used to find the number of items remaining in the queue with a specific priority level.
*/
sizeBy(options) {
// eslint-disable-next-line unicorn/no-array-callback-reference
return this.#queue.filter(options).length;
}
/**
Number of running items (no longer in the queue).
*/
get pending() {
return this.#pending;
}
/**
Whether the queue is currently paused.
*/
get isPaused() {
return this.#isPaused;
}
}

1
node_modules/p-queue/dist/lower-bound.d.ts generated vendored Normal file
View File

@@ -0,0 +1 @@
export default function lowerBound<T>(array: readonly T[], value: T, comparator: (a: T, b: T) => number): number;

18
node_modules/p-queue/dist/lower-bound.js generated vendored Normal file
View File

@@ -0,0 +1,18 @@
// Port of lower_bound from https://en.cppreference.com/w/cpp/algorithm/lower_bound
// Used to compute insertion index to keep queue sorted after insertion
export default function lowerBound(array, value, comparator) {
let first = 0;
let count = array.length;
while (count > 0) {
const step = Math.trunc(count / 2);
let it = first + step;
if (comparator(array[it], value) <= 0) {
first = ++it;
count -= step + 1;
}
else {
count = step;
}
}
return first;
}

106
node_modules/p-queue/dist/options.d.ts generated vendored Normal file
View File

@@ -0,0 +1,106 @@
import { type Queue, type RunFunction } from './queue.js';
type TimeoutOptions = {
/**
Per-operation timeout in milliseconds. Operations fulfill once `timeout` elapses if they haven't already.
*/
timeout?: number;
/**
Whether or not a timeout is considered an exception.
@default false
*/
throwOnTimeout?: boolean;
};
export type Options<QueueType extends Queue<RunFunction, QueueOptions>, QueueOptions extends QueueAddOptions> = {
/**
Concurrency limit.
Minimum: `1`.
@default Infinity
*/
readonly concurrency?: number;
/**
Whether queue tasks within concurrency limit, are auto-executed as soon as they're added.
@default true
*/
readonly autoStart?: boolean;
/**
Class with a `enqueue` and `dequeue` method, and a `size` getter. See the [Custom QueueClass](https://github.com/sindresorhus/p-queue#custom-queueclass) section.
*/
readonly queueClass?: new () => QueueType;
/**
The max number of runs in the given interval of time.
Minimum: `1`.
@default Infinity
*/
readonly intervalCap?: number;
/**
The length of time in milliseconds before the interval count resets. Must be finite.
Minimum: `0`.
@default 0
*/
readonly interval?: number;
/**
Whether the task must finish in the given interval or will be carried over into the next interval count.
@default false
*/
readonly carryoverConcurrencyCount?: boolean;
} & TimeoutOptions;
export type QueueAddOptions = {
/**
Priority of operation. Operations with greater priority will be scheduled first.
@default 0
*/
readonly priority?: number;
/**
Unique identifier for the promise function, used to update its priority before execution. If not specified, it is auto-assigned an incrementing BigInt starting from `1n`.
*/
id?: string;
} & TaskOptions & TimeoutOptions;
export type TaskOptions = {
/**
[`AbortSignal`](https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal) for cancellation of the operation. When aborted, it will be removed from the queue and the `queue.add()` call will reject with an `AbortError`. If the operation is already running, the signal will need to be handled by the operation itself.
@example
```
import PQueue, {AbortError} from 'p-queue';
import got, {CancelError} from 'got';
const queue = new PQueue();
const controller = new AbortController();
try {
await queue.add(({signal}) => {
const request = got('https://sindresorhus.com');
signal.addEventListener('abort', () => {
request.cancel();
});
try {
return await request;
} catch (error) {
if (!(error instanceof CancelError)) {
throw error;
}
}
}, {signal: controller.signal});
} catch (error) {
if (!(error instanceof AbortError)) {
throw error;
}
}
```
*/
readonly signal?: AbortSignal;
};
export {};

1
node_modules/p-queue/dist/options.js generated vendored Normal file
View File

@@ -0,0 +1 @@
export {};

13
node_modules/p-queue/dist/priority-queue.d.ts generated vendored Normal file
View File

@@ -0,0 +1,13 @@
import { type Queue, type RunFunction } from './queue.js';
import { type QueueAddOptions } from './options.js';
export type PriorityQueueOptions = {
priority?: number;
} & QueueAddOptions;
export default class PriorityQueue implements Queue<RunFunction, PriorityQueueOptions> {
#private;
enqueue(run: RunFunction, options?: Partial<PriorityQueueOptions>): void;
setPriority(id: string, priority: number): void;
dequeue(): RunFunction | undefined;
filter(options: Readonly<Partial<PriorityQueueOptions>>): RunFunction[];
get size(): number;
}

39
node_modules/p-queue/dist/priority-queue.js generated vendored Normal file
View File

@@ -0,0 +1,39 @@
import lowerBound from './lower-bound.js';
export default class PriorityQueue {
#queue = [];
enqueue(run, options) {
options = {
priority: 0,
...options,
};
const element = {
priority: options.priority,
id: options.id,
run,
};
if (this.size === 0 || this.#queue[this.size - 1].priority >= options.priority) {
this.#queue.push(element);
return;
}
const index = lowerBound(this.#queue, element, (a, b) => b.priority - a.priority);
this.#queue.splice(index, 0, element);
}
setPriority(id, priority) {
const index = this.#queue.findIndex((element) => element.id === id);
if (index === -1) {
throw new ReferenceError(`No promise function with the id "${id}" exists in the queue.`);
}
const [item] = this.#queue.splice(index, 1);
this.enqueue(item.run, { priority, id });
}
dequeue() {
const item = this.#queue.shift();
return item?.run;
}
filter(options) {
return this.#queue.filter((element) => element.priority === options.priority).map((element) => element.run);
}
get size() {
return this.#queue.length;
}
}

8
node_modules/p-queue/dist/queue.d.ts generated vendored Normal file
View File

@@ -0,0 +1,8 @@
export type RunFunction = () => Promise<unknown>;
export type Queue<Element, Options> = {
size: number;
filter: (options: Readonly<Partial<Options>>) => Element[];
dequeue: () => Element | undefined;
enqueue: (run: Element, options?: Partial<Options>) => void;
setPriority: (id: string, priority: number) => void;
};

1
node_modules/p-queue/dist/queue.js generated vendored Normal file
View File

@@ -0,0 +1 @@
export {};