@byloth/core 2.2.1 → 2.2.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@byloth/core",
3
- "version": "2.2.1",
3
+ "version": "2.2.3",
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.2.2",
53
- "@eslint/compat": "^1.4.1",
54
- "@types/node": "^22.19.0",
55
- "@vitest/coverage-v8": "^4.0.8",
56
- "eslint": "^9.39.1",
53
+ "@eslint/compat": "^2.0.0",
54
+ "@types/node": "^22.19.3",
55
+ "@vitest/coverage-v8": "^4.0.16",
56
+ "eslint": "^9.39.2",
57
57
  "husky": "^9.1.7",
58
- "jsdom": "^27.1.0",
58
+ "jsdom": "^27.4.0",
59
59
  "typescript": "^5.9.3",
60
- "vite": "^7.2.2",
61
- "vitest": "^4.0.8"
60
+ "vite": "^7.3.0",
61
+ "vitest": "^4.0.16"
62
62
  },
63
63
  "scripts": {
64
64
  "dev": "vite",
package/src/index.ts CHANGED
@@ -1,4 +1,4 @@
1
- export const VERSION = "2.2.1";
1
+ export const VERSION = "2.2.3";
2
2
 
3
3
  export type { Constructor, Interval, Timeout, ValueOf } from "./core/types.js";
4
4
 
@@ -1,5 +1,3 @@
1
- import AggregatedIterator from "./aggregated-iterator.js";
2
- import AggregatedAsyncIterator from "./aggregated-async-iterator.js";
3
- import ReducedIterator from "./reduced-iterator.js";
4
-
5
- export { AggregatedIterator, AggregatedAsyncIterator, ReducedIterator };
1
+ export { default as AggregatedIterator } from "./aggregated-iterator.js";
2
+ export { default as AggregatedAsyncIterator } from "./aggregated-async-iterator.js";
3
+ export { default as ReducedIterator } from "./reduced-iterator.js";
@@ -79,7 +79,14 @@ export default class CallbackChain<
79
79
  */
80
80
  protected override _invoke(...args: Parameters<T>): ReturnType<T>[]
81
81
  {
82
- return this._callbacks.map((callback) => callback(...args)) as ReturnType<T>[];
82
+ const length = this._callbacks.length;
83
+ const results = new Array<ReturnType<T>>(length);
84
+ for (let i = 0; i < length; i += 1)
85
+ {
86
+ results[i] = this._callbacks[i](...args);
87
+ }
88
+
89
+ return results;
83
90
  }
84
91
 
85
92
  /**
@@ -1,6 +1,4 @@
1
- import CallableObject from "./callable-object.js";
2
- import CallbackChain from "./callback-chain.js";
3
- import Publisher from "./publisher.js";
4
- import SwitchableCallback from "./switchable-callback.js";
5
-
6
- export { CallableObject, CallbackChain, Publisher, SwitchableCallback };
1
+ export { default as CallableObject } from "./callable-object.js";
2
+ export { default as CallbackChain } from "./callback-chain.js";
3
+ export { default as Publisher } from "./publisher.js";
4
+ export { default as SwitchableCallback } from "./switchable-callback.js";
@@ -170,8 +170,14 @@ export default class Publisher<T extends CallbackMap<T> = CallbackMap>
170
170
  let subscribers = this._subscribers.get(event);
171
171
  if (subscribers)
172
172
  {
173
- results = subscribers.slice()
174
- .map((subscriber) => subscriber(...args));
173
+ const _subscribers = subscribers.slice();
174
+ const _length = _subscribers.length;
175
+
176
+ results = new Array<unknown>(_length);
177
+ for (let i = 0; i < _length; i += 1)
178
+ {
179
+ results[i] = _subscribers[i](...args);
180
+ }
175
181
  }
176
182
  else { results = []; }
177
183
 
@@ -1,4 +1,2 @@
1
- import MapView from "./map-view.js";
2
- import SetView from "./set-view.js";
3
-
4
- export { MapView, SetView };
1
+ export { default as MapView } from "./map-view.js";
2
+ export { default as SetView } from "./set-view.js";
@@ -16,8 +16,7 @@
16
16
  * // Uncaught Exception: The game saves may be corrupted. Try to restart the game.
17
17
  * // at /src/game/index.ts:37:15
18
18
  * // at /src/main.ts:23:17
19
- * //
20
- * // Caused by SyntaxError: Unexpected end of JSON input
19
+ * // Caused by: SyntaxError: Unexpected end of JSON input
21
20
  * // at /src/models/saves.ts:47:17
22
21
  * // at /src/game/index.ts:12:9
23
22
  * // at /src/main.ts:23:17
@@ -59,6 +58,7 @@ export default class Exception extends Error
59
58
  const exc = new Exception(error.message);
60
59
 
61
60
  exc.stack = error.stack;
61
+ exc.cause = error.cause;
62
62
  exc.name = error.name;
63
63
 
64
64
  return exc;
@@ -89,18 +89,6 @@ export default class Exception extends Error
89
89
 
90
90
  this.cause = cause;
91
91
  this.name = name;
92
-
93
- if (cause)
94
- {
95
- if (cause instanceof Error)
96
- {
97
- this.stack += `\n\nCaused by ${cause.stack}`;
98
- }
99
- else
100
- {
101
- this.stack += `\n\nCaused by ${cause}`;
102
- }
103
- }
104
92
  }
105
93
 
106
94
  public readonly [Symbol.toStringTag]: string = "Exception";
@@ -1,4 +1,2 @@
1
- import SmartIterator from "./smart-iterator.js";
2
- import SmartAsyncIterator from "./smart-async-iterator.js";
3
-
4
- export { SmartIterator, SmartAsyncIterator };
1
+ export { default as SmartIterator } from "./smart-iterator.js";
2
+ export { default as SmartAsyncIterator } from "./smart-async-iterator.js";
@@ -1066,7 +1066,7 @@ export default class SmartAsyncIterator<T, R = void, N = undefined> implements A
1066
1066
  * {
1067
1067
  * try
1068
1068
  * {
1069
- * if (value > 5) { throw new Error("The index is too high."); }
1069
+ * if (value > 5) { throw new Exception("The index is too high."); }
1070
1070
  *
1071
1071
  * console.log(value); // 1, 2, 3, 4, 5
1072
1072
  * }
@@ -939,7 +939,7 @@ export default class SmartIterator<T, R = void, N = undefined> implements Iterat
939
939
  * {
940
940
  * try
941
941
  * {
942
- * if (value > 5) { throw new Error("The index is too high."); }
942
+ * if (value > 5) { throw new Exception("The index is too high."); }
943
943
  *
944
944
  * console.log(value); // 1, 2, 3, 4, 5
945
945
  * }
@@ -1,3 +1 @@
1
- import JSONStorage from "./json-storage.js";
2
-
3
- export { JSONStorage };
1
+ export { default as JSONStorage } from "./json-storage.js";
@@ -1,6 +1,4 @@
1
- import DeferredPromise from "./deferred-promise.js";
2
- import PromiseQueue from "./promise-queue.js";
3
- import SmartPromise from "./smart-promise.js";
4
- import TimedPromise from "./timed-promise.js";
5
-
6
- export { DeferredPromise, PromiseQueue, SmartPromise, TimedPromise };
1
+ export { default as DeferredPromise } from "./deferred-promise.js";
2
+ export { default as PromiseQueue } from "./promise-queue.js";
3
+ export { default as SmartPromise } from "./smart-promise.js";
4
+ export { default as TimedPromise } from "./timed-promise.js";
@@ -261,10 +261,10 @@ export default class SmartPromise<T = void> implements Promise<T>
261
261
  * ```ts
262
262
  * const promise = new SmartPromise((resolve, reject) =>
263
263
  * {
264
- * setTimeout(() => reject(new Error("An unknown error occurred.")), 1_000);
264
+ * setTimeout(() => reject(new Exception("An unknown error occurred.")), 1_000);
265
265
  * });
266
266
  *
267
- * promise.catch(); // Uncaught Error: An unknown error occurred.
267
+ * promise.catch(); // "Uncaught Exception: An unknown error occurred."
268
268
  * ```
269
269
  *
270
270
  * ---
@@ -288,10 +288,10 @@ export default class SmartPromise<T = void> implements Promise<T>
288
288
  * ```ts
289
289
  * const promise = new SmartPromise((resolve, reject) =>
290
290
  * {
291
- * setTimeout(() => reject(new Error("An unknown error occurred.")), 1_000);
291
+ * setTimeout(() => reject(new Exception("An unknown error occurred.")), 1_000);
292
292
  * });
293
293
  *
294
- * promise.catch((reason) => console.error(reason)); // "Error: An unknown error occurred."
294
+ * promise.catch((reason) => console.error(reason)); // "Uncaught Exception: An unknown error occurred."
295
295
  * ```
296
296
  *
297
297
  * ---
@@ -21,7 +21,7 @@ import type { MaybePromise, PromiseExecutor } from "./types.js";
21
21
  *
22
22
  * promise
23
23
  * .then((result) => console.log(result)) // "Hello, World!"
24
- * .catch((error) => console.error(error)); // TimeoutException: The operation has timed out.
24
+ * .catch((error) => console.error(error)); // "Uncaught TimeoutException: The operation has timed out."
25
25
  * ```
26
26
  *
27
27
  * ---
@@ -1,5 +1,3 @@
1
- import Clock from "./clock.js";
2
- import Countdown from "./countdown.js";
3
- import GameLoop from "./game-loop.js";
4
-
5
- export { Clock, Countdown, GameLoop };
1
+ export { default as Clock } from "./clock.js";
2
+ export { default as Countdown } from "./countdown.js";
3
+ export { default as GameLoop } from "./game-loop.js";
@@ -1,5 +1,5 @@
1
- import Curve from "./curve.js";
2
- import Random from "./random.js";
1
+ export { default as Curve } from "./curve.js";
2
+ export { default as Random } from "./random.js";
3
3
 
4
4
  export { delay, nextAnimationFrame, yieldToEventLoop } from "./async.js";
5
5
  export { dateDifference, dateRange, dateRound, getWeek, TimeUnit, WeekDay } from "./date.js";
@@ -7,5 +7,3 @@ export { loadScript } from "./dom.js";
7
7
  export { chain, count, enumerate, range, shuffle, unique, zip } from "./iterator.js";
8
8
  export { average, hash, sum } from "./math.js";
9
9
  export { capitalize } from "./string.js";
10
-
11
- export { Curve, Random };
@@ -179,6 +179,127 @@ export default class Random
179
179
  return elements[Random.Index(elements)];
180
180
  }
181
181
 
182
+ /**
183
+ * Picks a random sample of elements from a given array without replacement.
184
+ *
185
+ * Uses the Fisher-Yates shuffle algorithm for uniform sampling,
186
+ * which is O(count) instead of O(n log n) for a full shuffle.
187
+ *
188
+ * ---
189
+ *
190
+ * @example
191
+ * ```ts
192
+ * Random.Sample([1, 2, 3, 4, 5], 3); // e.g., [4, 1, 5]
193
+ * ```
194
+ *
195
+ * ---
196
+ *
197
+ * @template T The type of the elements in the array.
198
+ *
199
+ * @param elements
200
+ * The array of elements to sample from.
201
+ *
202
+ * It must contain at least one element. Otherwise, a {@link ValueException} will be thrown.
203
+ *
204
+ * @param count
205
+ * The number of elements to sample.
206
+ *
207
+ * It must be between `0` and `elements.length`. Otherwise, a {@link ValueException} will be thrown.
208
+ *
209
+ * @returns An array containing the randomly sampled elements.
210
+ */
211
+ public static Sample<T>(elements: readonly T[], count: number): T[];
212
+
213
+ /**
214
+ * Picks a weighted random sample of elements from a given array without replacement.
215
+ *
216
+ * Uses the Efraimidis-Spirakis algorithm for weighted sampling.
217
+ * Elements with higher weights have a higher probability of being selected.
218
+ *
219
+ * ---
220
+ *
221
+ * @example
222
+ * ```ts
223
+ * // Element "a" is 3x more likely to be picked than "b" or "c"
224
+ * Random.Sample(["a", "b", "c"], 2, [3, 1, 1]);
225
+ * ```
226
+ *
227
+ * ---
228
+ *
229
+ * @template T The type of the elements in the array.
230
+ *
231
+ * @param elements
232
+ * The array of elements to sample from.
233
+ *
234
+ * It must contain at least one element. Otherwise, a {@link ValueException} will be thrown.
235
+ *
236
+ * @param count
237
+ * The number of elements to sample.
238
+ *
239
+ * It must be between `0` and `elements.length`. Otherwise, a {@link ValueException} will be thrown.
240
+ *
241
+ * @param weights
242
+ * The weights associated with each element.
243
+ *
244
+ * It must have the same length as the elements array.
245
+ * All weights must be greater than zero. Otherwise, a {@link ValueException} will be thrown.
246
+ *
247
+ * @returns An array containing the randomly sampled elements.
248
+ */
249
+ public static Sample<T>(elements: readonly T[], count: number, weights: readonly number[]): T[];
250
+ public static Sample<T>(elements: readonly T[], count: number, weights?: readonly number[]): T[]
251
+ {
252
+ const length = elements.length;
253
+
254
+ if (length === 0) { throw new ValueException("You must provide at least one element."); }
255
+ if (count < 0) { throw new ValueException("Count must be non-negative."); }
256
+ if (count > length) { throw new ValueException("Count cannot exceed the number of elements."); }
257
+
258
+ if (count === 0) { return []; }
259
+
260
+ if (weights === undefined)
261
+ {
262
+ const pool = [...elements];
263
+ const result: T[] = new Array(count);
264
+
265
+ for (let index = 0; index < count; index += 1)
266
+ {
267
+ const randomIndex = this.Integer(index, length);
268
+
269
+ result[index] = pool[randomIndex];
270
+ pool[randomIndex] = pool[index];
271
+ }
272
+
273
+ return result;
274
+ }
275
+
276
+ if (weights.length !== length)
277
+ {
278
+ throw new ValueException("Weights array must have the same length as elements array.");
279
+ }
280
+
281
+ const keys: ({ index: number, key: number })[] = new Array(length);
282
+ for (let index = 0; index < length; index += 1)
283
+ {
284
+ if (weights[index] <= 0)
285
+ {
286
+ throw new ValueException(`Weight for element #${index} must be greater than zero.`);
287
+ }
288
+
289
+ keys[index] = { index: index, key: Math.pow(Math.random(), 1 / weights[index]) };
290
+ }
291
+
292
+ keys.sort((a, b) => b.key - a.key);
293
+
294
+ const result: T[] = new Array(count);
295
+ for (let index = 0; index < count; index += 1)
296
+ {
297
+ result[index] = elements[keys[index].index];
298
+ }
299
+
300
+ return result;
301
+ }
302
+
182
303
  private constructor() { /* ... */ }
183
304
 
184
305
  public readonly [Symbol.toStringTag]: string = "Random";