@byloth/core 2.1.3 → 2.1.5

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@byloth/core",
3
- "version": "2.1.3",
3
+ "version": "2.1.5",
4
4
  "description": "An unopinionated collection of useful functions and classes that I use widely in all my projects. 🔧",
5
5
  "keywords": [
6
6
  "Core",
@@ -50,15 +50,15 @@
50
50
  "types": "src/index.ts",
51
51
  "devDependencies": {
52
52
  "@byloth/eslint-config-typescript": "^3.1.0",
53
- "@eslint/compat": "^1.2.9",
54
- "@types/node": "^22.15.30",
55
- "@vitest/coverage-v8": "^3.2.2",
56
- "eslint": "^9.28.0",
53
+ "@eslint/compat": "^1.3.0",
54
+ "@types/node": "^22.15.32",
55
+ "@vitest/coverage-v8": "^3.2.3",
56
+ "eslint": "^9.29.0",
57
57
  "husky": "^9.1.7",
58
58
  "jsdom": "^26.1.0",
59
59
  "typescript": "^5.8.3",
60
60
  "vite": "^6.3.5",
61
- "vitest": "^3.2.2"
61
+ "vitest": "^3.2.3"
62
62
  },
63
63
  "scripts": {
64
64
  "dev": "vite",
package/src/core/types.ts CHANGED
@@ -74,7 +74,7 @@ export type Timeout = ReturnType<typeof setTimeout>;
74
74
  * public greet() { console.log("Hello, world!"); }
75
75
  * }
76
76
  *
77
- * type MyObjectProperties = ValueOf<MyObject>; // number | (() => void)
77
+ * type MyObjectProperties = ValueOf<MyObject>; // number | (() => void)
78
78
  * ```
79
79
  *
80
80
  * ---
package/src/index.ts CHANGED
@@ -1,4 +1,4 @@
1
- export const VERSION = "2.1.3";
1
+ export const VERSION = "2.1.5";
2
2
 
3
3
  export type { Constructor, Interval, Timeout, ValueOf } from "./core/types.js";
4
4
 
@@ -24,6 +24,7 @@ export {
24
24
  NotImplementedException,
25
25
  NetworkException,
26
26
  PermissionException,
27
+ PromiseQueue,
27
28
  Publisher,
28
29
  RangeException,
29
30
  ReducedIterator,
@@ -52,7 +52,7 @@ export default class AggregatedAsyncIterator<K extends PropertyKey, T>
52
52
  /**
53
53
  * The internal {@link SmartAsyncIterator} object that holds the elements to aggregate.
54
54
  */
55
- protected _elements: SmartAsyncIterator<[K, T]>;
55
+ protected readonly _elements: SmartAsyncIterator<[K, T]>;
56
56
 
57
57
  /**
58
58
  * Initializes a new instance of the {@link AggregatedAsyncIterator} class.
@@ -43,7 +43,7 @@ export default class AggregatedIterator<K extends PropertyKey, T>
43
43
  /**
44
44
  * The internal {@link SmartIterator} object that holds the elements to aggregate.
45
45
  */
46
- protected _elements: SmartIterator<[K, T]>;
46
+ protected readonly _elements: SmartIterator<[K, T]>;
47
47
 
48
48
  /**
49
49
  * Initializes a new instance of the {@link AggregatedIterator} class.
@@ -45,7 +45,7 @@ export default class ReducedIterator<K extends PropertyKey, T>
45
45
  /**
46
46
  * The internal {@link SmartIterator} object that holds the reduced elements.
47
47
  */
48
- protected _elements: SmartIterator<[K, T]>;
48
+ protected readonly _elements: SmartIterator<[K, T]>;
49
49
 
50
50
  /**
51
51
  * Initializes a new instance of the {@link ReducedIterator} class.
@@ -1,6 +1,6 @@
1
1
  import { ReferenceException } from "../exceptions/index.js";
2
2
 
3
- import type { Callback, CallbackMap } from "./types.js";
3
+ import type { Callback, CallbackMap, WithWildcard } from "./types.js";
4
4
 
5
5
  /**
6
6
  * A class implementing the
@@ -39,8 +39,10 @@ import type { Callback, CallbackMap } from "./types.js";
39
39
  * A map containing the names of the emittable events and the
40
40
  * related callback signatures that can be subscribed to them.
41
41
  * Default is `Record<string, (...args: unknown[]) => unknown>`.
42
+ *
43
+ * @template E An utility type that extends the `T` map with a wildcard event.
42
44
  */
43
- export default class Publisher<T extends CallbackMap<T> = CallbackMap>
45
+ export default class Publisher<T extends CallbackMap<T> = CallbackMap, W extends WithWildcard<T> = WithWildcard<T>>
44
46
  {
45
47
  /**
46
48
  * A map containing all the subscribers for each event.
@@ -48,7 +50,7 @@ export default class Publisher<T extends CallbackMap<T> = CallbackMap>
48
50
  * The keys are the names of the events they are subscribed to.
49
51
  * The values are the arrays of the subscribers themselves.
50
52
  */
51
- protected _subscribers: Map<string, Callback<unknown[], unknown>[]>;
53
+ protected readonly _subscribers: Map<string, Callback<unknown[], unknown>[]>;
52
54
 
53
55
  /**
54
56
  * Initializes a new instance of the {@link Publisher} class.
@@ -87,9 +89,63 @@ export default class Publisher<T extends CallbackMap<T> = CallbackMap>
87
89
  */
88
90
  public clear(): void
89
91
  {
92
+ // @ts-expect-error It's an internal event, not part of the public API.
93
+ this.publish("__internals__:clear");
94
+
90
95
  this._subscribers.clear();
91
96
  }
92
97
 
98
+ /**
99
+ * Creates a new scoped instance of the {@link Publisher} class,
100
+ * which can be used to publish and subscribe events within a specific context.
101
+ *
102
+ * It can receive all events published to the parent publisher while also allowing
103
+ * the scoped publisher to handle its own events independently.
104
+ * In fact, events published to the scoped publisher won't be propagated back to the parent publisher.
105
+ *
106
+ * ---
107
+ *
108
+ * @example
109
+ * ```ts
110
+ * const publisher = new Publisher();
111
+ * const context = publisher.createScope();
112
+ *
113
+ * publisher.subscribe("player:death", () => { console.log(`Player has died.`); });
114
+ * context.subscribe("player:spawn", () => { console.log(`Player has spawned.`); });
115
+ *
116
+ * publisher.publish("player:spawn"); // Player has spawned.
117
+ * context.publish("player:death"); // * no output *
118
+ * ```
119
+ *
120
+ * ---
121
+ *
122
+ * @template U
123
+ * A map containing the names of the emittable events and the
124
+ * related callback signatures that can be subscribed to them.
125
+ * Default is `T`.
126
+ *
127
+ * @template X An utility type that extends the `U` map with a wildcard event.
128
+ *
129
+ * @return
130
+ * A new instance of the {@link Publisher} class that can be
131
+ * used to publish and subscribe events within a specific context.
132
+ */
133
+ public createScope<U extends T = T, X extends WithWildcard<U> = WithWildcard<U>>(): Publisher<U, X>
134
+ {
135
+ const scope = new Publisher<U, X>();
136
+
137
+ const propagator = (event: (keyof T) & string, ...args: Parameters<T[keyof T]>): void =>
138
+ {
139
+ scope.publish(event, ...args);
140
+ };
141
+
142
+ // @ts-expect-error It's an internal event, not part of the public API.
143
+ this.subscribe("__internals__:clear", () => scope.clear());
144
+ this.subscribe("*", propagator as W["*"]);
145
+
146
+ return scope;
147
+ }
148
+
93
149
  /**
94
150
  * Publishes an event to all the subscribers.
95
151
  *
@@ -115,11 +171,26 @@ export default class Publisher<T extends CallbackMap<T> = CallbackMap>
115
171
  */
116
172
  public publish<K extends keyof T>(event: K & string, ...args: Parameters<T[K]>): ReturnType<T[K]>[]
117
173
  {
118
- const subscribers = this._subscribers.get(event);
119
- if (!(subscribers)) { return []; }
174
+ let results: ReturnType<T[K]>[];
175
+ let subscribers = this._subscribers.get(event);
176
+ if (subscribers)
177
+ {
178
+ results = subscribers.slice()
179
+ .map((subscriber) => subscriber(...args)) as ReturnType<T[K]>[];
180
+ }
181
+ else { results = []; }
182
+
183
+ if (!(event.startsWith("__")))
184
+ {
185
+ subscribers = this._subscribers.get("*");
186
+ if (subscribers)
187
+ {
188
+ subscribers.slice()
189
+ .forEach((subscriber) => subscriber(event, ...args));
190
+ }
191
+ }
120
192
 
121
- return subscribers.slice()
122
- .map((subscriber) => subscriber(...args)) as ReturnType<T[K]>[];
193
+ return results;
123
194
  }
124
195
 
125
196
  /**
@@ -146,7 +217,7 @@ export default class Publisher<T extends CallbackMap<T> = CallbackMap>
146
217
  *
147
218
  * @returns A function that can be used to unsubscribe the subscriber from the event.
148
219
  */
149
- public subscribe<K extends keyof T>(event: K & string, subscriber: T[K]): () => void
220
+ public subscribe<K extends keyof W>(event: K & string, subscriber: W[K]): () => void
150
221
  {
151
222
  const subscribers = this._subscribers.get(event) ?? [];
152
223
  subscribers.push(subscriber);
@@ -186,7 +257,7 @@ export default class Publisher<T extends CallbackMap<T> = CallbackMap>
186
257
  * @param event The name of the event to unsubscribe from.
187
258
  * @param subscriber The subscriber to remove from the event.
188
259
  */
189
- public unsubscribe<K extends keyof T>(event: K & string, subscriber: T[K]): void
260
+ public unsubscribe<K extends keyof W>(event: K & string, subscriber: W[K]): void
190
261
  {
191
262
  const subscribers = this._subscribers.get(event);
192
263
  if (!(subscribers))
@@ -43,7 +43,7 @@ export default class SwitchableCallback<T extends Callback<any[], any> = Callbac
43
43
  * The keys are the names of the implementations they were registered with.
44
44
  * The values are the implementations themselves.
45
45
  */
46
- protected _callbacks: Map<string, T>;
46
+ protected readonly _callbacks: Map<string, T>;
47
47
 
48
48
  /**
49
49
  * A flag indicating whether the callback is enabled or not.
@@ -157,3 +157,16 @@ export interface Subscribable<T extends CallbackMap<T> = CallbackMap>
157
157
  */
158
158
  unsubscribe<K extends keyof T>(event: K & string, subscriber: T[K]): void;
159
159
  }
160
+
161
+ /**
162
+ * An utility type that may be used to wrap a {@link CallbackMap} and extend it with a wildcard event.
163
+ * The resulting type will be the same as the original map, but with an additional `"*"` key.
164
+ *
165
+ * It's natively used by the {@link Publisher} class to allow subscribers to listen to all events.
166
+ *
167
+ * ---
168
+ *
169
+ * @template T A `CallbackMap` compatible interface that defines the map of callbacks.
170
+ *
171
+ */
172
+ export type WithWildcard<T extends CallbackMap<T>> = T & { "*"?: (type: string, ...args: unknown[]) => void };
@@ -1,4 +1,5 @@
1
1
  import Publisher from "../callbacks/publisher.js";
2
+ import type { WithWildcard } from "../callbacks/types.js";
2
3
 
3
4
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
4
5
  import type SetView from "./set-view.js";
@@ -172,7 +173,9 @@ export default class MapView<K, V> extends Map<K, V>
172
173
  *
173
174
  * @returns A function that can be used to unsubscribe the callback from the event.
174
175
  */
175
- public subscribe<T extends keyof MapViewEventsMap<K, V>>(event: T, callback: MapViewEventsMap<K, V>[T]): () => void
176
+ public subscribe<T extends keyof WithWildcard<MapViewEventsMap<K, V>>>(
177
+ event: T & string, callback: WithWildcard<MapViewEventsMap<K, V>>[T]
178
+ ): () => void
176
179
  {
177
180
  return this._publisher.subscribe(event, callback);
178
181
  }
@@ -201,7 +204,8 @@ export default class MapView<K, V> extends Map<K, V>
201
204
  * @param event The name of the event to unsubscribe from.
202
205
  * @param callback The callback to remove from the event.
203
206
  */
204
- public unsubscribe<T extends keyof MapViewEventsMap<K, V>>(event: T, callback: MapViewEventsMap<K, V>[T]): void
207
+ public unsubscribe<T extends keyof WithWildcard<MapViewEventsMap<K, V>>>(
208
+ event: T & string, callback: WithWildcard<MapViewEventsMap<K, V>>[T]): void
205
209
  {
206
210
  this._publisher.unsubscribe(event, callback);
207
211
  }
@@ -1,4 +1,5 @@
1
1
  import Publisher from "../callbacks/publisher.js";
2
+ import type { WithWildcard } from "../callbacks/types.js";
2
3
 
3
4
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
4
5
  import type MapView from "./map-view.js";
@@ -166,7 +167,9 @@ export default class SetView<T> extends Set<T>
166
167
  *
167
168
  * @returns A function that can be used to unsubscribe the callback from the event.
168
169
  */
169
- public subscribe<K extends keyof SetViewEventsMap<T>>(event: K, callback: SetViewEventsMap<T>[K]): () => void
170
+ public subscribe<K extends keyof WithWildcard<SetViewEventsMap<T>>>(
171
+ event: K & string, callback: WithWildcard<SetViewEventsMap<T>>[K]
172
+ ): () => void
170
173
  {
171
174
  return this._publisher.subscribe(event, callback);
172
175
  }
@@ -195,7 +198,9 @@ export default class SetView<T> extends Set<T>
195
198
  * @param event The name of the event to unsubscribe from.
196
199
  * @param callback The callback to remove from the event.
197
200
  */
198
- public unsubscribe<K extends keyof SetViewEventsMap<T>>(event: K, callback: SetViewEventsMap<T>[K]): void
201
+ public unsubscribe<K extends keyof WithWildcard<SetViewEventsMap<T>>>(
202
+ event: K & string, callback: WithWildcard<SetViewEventsMap<T>>[K]
203
+ ): void
199
204
  {
200
205
  this._publisher.unsubscribe(event, callback);
201
206
  }
@@ -29,5 +29,5 @@ export {
29
29
 
30
30
  export { SmartIterator, SmartAsyncIterator } from "./iterators/index.js";
31
31
  export { JSONStorage } from "./json/index.js";
32
- export { DeferredPromise, SmartPromise, TimedPromise } from "./promises/index.js";
32
+ export { DeferredPromise, PromiseQueue, SmartPromise, TimedPromise } from "./promises/index.js";
33
33
  export { Clock, Countdown, GameLoop } from "./timers/index.js";
@@ -51,7 +51,7 @@ export default class SmartAsyncIterator<T, R = void, N = undefined> implements A
51
51
  /**
52
52
  * The native {@link AsyncIterator} object that is being wrapped by this instance.
53
53
  */
54
- protected _iterator: AsyncIterator<T, R, N>;
54
+ protected readonly _iterator: AsyncIterator<T, R, N>;
55
55
 
56
56
  /**
57
57
  * Initializes a new instance of the {@link SmartAsyncIterator} class.
@@ -42,7 +42,7 @@ export default class SmartIterator<T, R = void, N = undefined> implements Iterat
42
42
  /**
43
43
  * The native {@link Iterator} object that is being wrapped by this instance.
44
44
  */
45
- protected _iterator: Iterator<T, R, N>;
45
+ protected readonly _iterator: Iterator<T, R, N>;
46
46
 
47
47
  /**
48
48
  * Initializes a new instance of the {@link SmartIterator} class.
@@ -29,17 +29,17 @@ export default class JSONStorage
29
29
  * If `true`, the persistent storage is preferred. If `false`, the volatile storage is preferred.
30
30
  * Default is `true`.
31
31
  */
32
- protected _preferPersistence: boolean;
32
+ protected readonly _preferPersistence: boolean;
33
33
 
34
34
  /**
35
35
  * A reference to the volatile {@link sessionStorage} storage.
36
36
  */
37
- protected _volatile: Storage;
37
+ protected readonly _volatile: Storage;
38
38
 
39
39
  /**
40
40
  * A reference to the persistent {@link localStorage} storage.
41
41
  */
42
- protected _persistent: Storage;
42
+ protected readonly _persistent: Storage;
43
43
 
44
44
  /**
45
45
  * Initializes a new instance of the {@link JSONStorage} class.
@@ -40,7 +40,7 @@ export default class DeferredPromise<T = void, F = T, R = never> extends SmartPr
40
40
  * This protected property is the only one that can be modified directly by the derived classes.
41
41
  * If you're looking for the public and readonly property, use the {@link DeferredPromise.resolve} getter instead.
42
42
  */
43
- protected _resolve: PromiseResolver<T>;
43
+ protected readonly _resolve: PromiseResolver<T>;
44
44
 
45
45
  /**
46
46
  * The exposed function that allows to reject the promise.
@@ -53,13 +53,18 @@ export default class DeferredPromise<T = void, F = T, R = never> extends SmartPr
53
53
  * This protected property is the only one that can be modified directly by the derived classes.
54
54
  * If you're looking for the public and readonly property, use the {@link DeferredPromise.reject} getter instead.
55
55
  */
56
- protected _reject: PromiseRejecter;
56
+ protected readonly _reject: PromiseRejecter;
57
57
 
58
58
  /**
59
59
  * The exposed function that allows to reject the promise.
60
60
  */
61
61
  public get reject(): PromiseRejecter { return this._reject; }
62
62
 
63
+ /**
64
+ * The native {@link Promise} object wrapped by this instance.
65
+ */
66
+ declare protected readonly _promise: Promise<F | R>;
67
+
63
68
  /**
64
69
  * Initializes a new instance of the {@link DeferredPromise} class.
65
70
  *
@@ -1,5 +1,6 @@
1
1
  import DeferredPromise from "./deferred-promise.js";
2
+ import PromiseQueue from "./promise-queue.js";
2
3
  import SmartPromise from "./smart-promise.js";
3
4
  import TimedPromise from "./timed-promise.js";
4
5
 
5
- export { DeferredPromise, SmartPromise, TimedPromise };
6
+ export { DeferredPromise, PromiseQueue, SmartPromise, TimedPromise };
@@ -0,0 +1,222 @@
1
+ import type { Callback } from "../callbacks/types.js";
2
+
3
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
4
+ import { TimeoutException, ValueException } from "../exceptions/index.js";
5
+
6
+ import DeferredPromise from "./deferred-promise.js";
7
+ import SmartPromise from "./smart-promise.js";
8
+ import TimedPromise from "./timed-promise.js";
9
+ import type { MaybePromise, PromiseRejecter, PromiseResolver } from "./types.js";
10
+
11
+ /**
12
+ * A class that represents a queue of asynchronous operations, allowing them to be executed sequentially.
13
+ *
14
+ * It extends the {@link SmartPromise} class, providing a way to manage multiple promises in a controlled manner.
15
+ * This class is useful for scenarios where you need to ensure
16
+ * that only one asynchronous operation is executed at a time,
17
+ * such as when dealing with API requests, file operations or any other
18
+ * asynchronous tasks that need to be handled in a specific order.
19
+ *
20
+ * ---
21
+ *
22
+ * @example
23
+ * ```ts
24
+ * const queue = new PromiseQueue();
25
+ *
26
+ * queue.enqueue(() => new Promise((resolve) => setTimeout(() => resolve("First"), 2000)))
27
+ * queue.enqueue(() => new Promise((resolve) => setTimeout(() => resolve("Second"), 500)))
28
+ * queue.enqueue(() => new Promise((resolve) => setTimeout(() => resolve("Third"), 1000)))
29
+ *
30
+ * await queue; // "First", "Second", "Third"
31
+ * ```
32
+ */
33
+ export default class PromiseQueue extends SmartPromise<void>
34
+ {
35
+ /**
36
+ * The number of promises currently in the queue.
37
+ */
38
+ protected _count: number;
39
+
40
+ /**
41
+ * A flag indicating whether the promise is still pending or not.
42
+ */
43
+ public override get isPending(): boolean
44
+ {
45
+ return this._count > 0;
46
+ }
47
+ /**
48
+ * A flag indicating whether the promise has been fulfilled or not.
49
+ */
50
+ public override get isFulfilled(): boolean
51
+ {
52
+ return this._count === 0;
53
+ }
54
+
55
+ /**
56
+ * A flag indicating whether the promise has been rejected or not.
57
+ *
58
+ * Please note the {@link PromiseQueue} doesn't support rejection states.
59
+ * Accessing this property will always result in a {@link ValueException}.
60
+ */
61
+ public override get isRejected(): never
62
+ {
63
+ throw new ValueException("`PromiseQueue` doesn't support rejection states.");
64
+ }
65
+
66
+ /**
67
+ * The native {@link Promise} object wrapped by this instance.
68
+ */
69
+ declare protected _promise: Promise<void>;
70
+
71
+ /**
72
+ * Initializes a new instance of the {@link PromiseQueue} class.
73
+ */
74
+ public constructor()
75
+ {
76
+ super((resolve) => resolve());
77
+
78
+ this._count = 0;
79
+
80
+ this._isPending = false;
81
+ this._isFulfilled = false;
82
+ this._isRejected = false;
83
+ }
84
+
85
+ /**
86
+ * Enqueues a {@link DeferredPromise} into the queue.
87
+ *
88
+ * The promise will be executed in sequence after previously enqueued promises.
89
+ *
90
+ * ---
91
+ *
92
+ * @example
93
+ * ```ts
94
+ * const queue = new PromiseQueue();
95
+ * const deferred = new DeferredPromise(() => console.log("Hello, world!"));
96
+ *
97
+ * queue.enqueue(deferred); // "Hello, world!"
98
+ * ```
99
+ *
100
+ * ---
101
+ *
102
+ * @template T The type of value the promise will eventually resolve to.
103
+ *
104
+ * @param promise A `DeferredPromise<void, T>` instance to enqueue.
105
+ *
106
+ * @returns A {@link Promise} that resolves to the value of the enqueued promise.
107
+ */
108
+ public enqueue<T>(promise: DeferredPromise<void, T>): Promise<T>;
109
+
110
+ /**
111
+ * Enqueues a {@link DeferredPromise} into the queue with an optional timeout.
112
+ *
113
+ * The promise will be executed in sequence after previously enqueued promises.
114
+ * If the promise takes longer than the specified timeout, it will be rejected with a {@link TimeoutException}.
115
+ *
116
+ * ---
117
+ *
118
+ * @example
119
+ * ```ts
120
+ * const queue = new PromiseQueue();
121
+ * const deferred = new DeferredPromise(() => console.log("Hello, world!"));
122
+ *
123
+ * queue.enqueue(deferred, 5000); // "Hello, world!"
124
+ * ```
125
+ *
126
+ * ---
127
+ *
128
+ * @template T The type of value the promise will eventually resolve to.
129
+ *
130
+ * @param promise A `DeferredPromise<void, T>` instance to enqueue.
131
+ * @param timeout The maximum time in milliseconds that the operation can take before timing out.
132
+ *
133
+ * @returns
134
+ * A {@link TimedPromise} that resolves to the value of the enqueued promise or rejects
135
+ * with a `TimeoutException` if the operation takes longer than the specified timeout.
136
+ */
137
+ public enqueue<T>(promise: DeferredPromise<void, T>, timeout: number): TimedPromise<T>;
138
+
139
+ /**
140
+ * Enqueues a callback that returns a {@link MaybePromise} value of type `T` into the queue.
141
+ *
142
+ * The executor will be executed in sequence after previously enqueued promises.
143
+ *
144
+ * ---
145
+ *
146
+ * @example
147
+ * ```ts
148
+ * const queue = new PromiseQueue();
149
+ *
150
+ * queue.enqueue(() => console.log("Hello, world!")); // "Hello, world!"
151
+ * ```
152
+ *
153
+ * ---
154
+ *
155
+ * @template T The type of value the promise will eventually resolve to.
156
+ *
157
+ * @param executor A callback that returns a `MaybePromise<T>` value to enqueue.
158
+ *
159
+ * @returns A {@link Promise} that resolves to the value of the enqueued executor.
160
+ */
161
+ public enqueue<T>(executor: Callback<[], MaybePromise<T>>): Promise<T>;
162
+
163
+ /**
164
+ * Enqueues a callback that returns a {@link MaybePromise}
165
+ * value of type `T` into the queue with an optional timeout.
166
+ *
167
+ * The executor will be executed in sequence after previously enqueued promises.
168
+ * If the executor takes longer than the specified timeout, it will be rejected with a {@link TimeoutException}.
169
+ *
170
+ * ---
171
+ *
172
+ * @example
173
+ * ```ts
174
+ * const queue = new PromiseQueue();
175
+ *
176
+ * queue.enqueue(() => console.log("Hello, world!"), 5000); // "Hello, world!"
177
+ * ```
178
+ *
179
+ * ---
180
+ *
181
+ * @template T The type of value the promise will eventually resolve to.
182
+ *
183
+ * @param executor A callback that returns a `MaybePromise<T>` value to enqueue.
184
+ * @param timeout The maximum time in milliseconds that the operation can take before timing out.
185
+ *
186
+ * @returns
187
+ * A {@link TimedPromise} that resolves to the value of the enqueued executor or rejects
188
+ * with a `TimeoutException` if the operation takes longer than the specified timeout.
189
+ */
190
+ public enqueue<T>(executor: Callback<[], MaybePromise<T>>, timeout?: number): TimedPromise<T>;
191
+ public enqueue<T>(executor: DeferredPromise<void, T> | Callback<[], MaybePromise<T>>, timeout?: number)
192
+ : Promise<T> | TimedPromise<T>
193
+ {
194
+ this._count += 1;
195
+
196
+ if (executor instanceof DeferredPromise)
197
+ {
198
+ const _executor = executor as DeferredPromise<void, T>;
199
+
200
+ executor = () =>
201
+ {
202
+ _executor.resolve();
203
+
204
+ return _executor;
205
+ };
206
+ }
207
+
208
+ const _executor = (resolve: PromiseResolver<T>, reject: PromiseRejecter) =>
209
+ {
210
+ this._promise = this._promise
211
+ .then(executor)
212
+ .then((value) => { this._count -= 1; resolve(value); })
213
+ .catch((value) => { this._count -= 1; reject(value); });
214
+ };
215
+
216
+ if (timeout) { return new TimedPromise<T>(_executor, timeout); }
217
+
218
+ return new Promise<T>(_executor);
219
+ }
220
+
221
+ public override readonly [Symbol.toStringTag]: string = "PromiseQueue";
222
+ }
@@ -110,7 +110,7 @@ export default class SmartPromise<T = void> implements Promise<T>
110
110
  /**
111
111
  * The native {@link Promise} object wrapped by this instance.
112
112
  */
113
- protected _promise: Promise<T>;
113
+ protected readonly _promise: Promise<T>;
114
114
 
115
115
  /**
116
116
  * Initializes a new instance of the {@link SmartPromise} class.
@@ -2,18 +2,14 @@ import { TimeUnit } from "../../utils/date.js";
2
2
 
3
3
  import Publisher from "../callbacks/publisher.js";
4
4
  import { FatalErrorException, RangeException, RuntimeException } from "../exceptions/index.js";
5
- import type { Callback } from "../types.js";
6
5
 
7
6
  import GameLoop from "./game-loop.js";
8
7
 
9
- interface ClockEventMap
8
+ interface ClockEventsMap
10
9
  {
11
10
  start: () => void;
12
11
  stop: () => void;
13
12
  tick: (elapsedTime: number) => void;
14
-
15
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
16
- [key: string]: Callback<any[], any>;
17
13
  }
18
14
 
19
15
  /**
@@ -40,7 +36,7 @@ export default class Clock extends GameLoop
40
36
  /**
41
37
  * The {@link Publisher} object that will be used to publish the events of the clock.
42
38
  */
43
- protected override _publisher: Publisher<ClockEventMap>;
39
+ declare protected readonly _publisher: Publisher<ClockEventsMap>;
44
40
 
45
41
  /**
46
42
  * Initializes a new instance of the {@link Clock} class.
@@ -61,8 +57,6 @@ export default class Clock extends GameLoop
61
57
  public constructor(msIfNotBrowser: number = TimeUnit.Second)
62
58
  {
63
59
  super((elapsedTime) => this._publisher.publish("tick", elapsedTime), msIfNotBrowser);
64
-
65
- this._publisher = new Publisher();
66
60
  }
67
61
 
68
62
  /**