@fuzdev/fuz_util 0.52.1 → 0.53.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Binary data conversion helpers.
3
+ *
4
+ * @module
5
+ */
6
+ /**
7
+ * Converts string or binary data to a `Uint8Array`.
8
+ * Strings are UTF-8 encoded. `Uint8Array` inputs are returned as-is.
9
+ *
10
+ * @param data - String or `BufferSource` to convert.
11
+ * @returns `Uint8Array` view of the data.
12
+ */
13
+ export declare const to_bytes: (data: BufferSource | string) => Uint8Array;
14
+ //# sourceMappingURL=bytes.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bytes.d.ts","sourceRoot":"../src/lib/","sources":["../src/lib/bytes.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAIH;;;;;;GAMG;AACH,eAAO,MAAM,QAAQ,GAAI,MAAM,YAAY,GAAG,MAAM,KAAG,UAKtD,CAAC"}
package/dist/bytes.js ADDED
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Binary data conversion helpers.
3
+ *
4
+ * @module
5
+ */
6
+ const encoder = new TextEncoder();
7
+ /**
8
+ * Converts string or binary data to a `Uint8Array`.
9
+ * Strings are UTF-8 encoded. `Uint8Array` inputs are returned as-is.
10
+ *
11
+ * @param data - String or `BufferSource` to convert.
12
+ * @returns `Uint8Array` view of the data.
13
+ */
14
+ export const to_bytes = (data) => {
15
+ if (typeof data === 'string')
16
+ return encoder.encode(data);
17
+ if (data instanceof Uint8Array)
18
+ return data;
19
+ if (data instanceof ArrayBuffer)
20
+ return new Uint8Array(data);
21
+ return new Uint8Array(data.buffer, data.byteOffset, data.byteLength);
22
+ };
package/dist/hash.d.ts CHANGED
@@ -1,18 +1,18 @@
1
1
  /**
2
2
  * Hash utilities for content comparison and cache invalidation.
3
3
  *
4
- * Provides both secure (cryptographic) and insecure (fast) hash functions.
4
+ * Provides `hash_sha256` (Web Crypto, async) and `hash_insecure` (DJB2, fast non-cryptographic).
5
+ * For BLAKE3, see `hash_blake3` in `hash_blake3.ts`.
5
6
  *
6
7
  * @module
7
8
  */
8
9
  /**
9
- * Computes a cryptographic hash using Web Crypto API.
10
+ * Computes a SHA-256 hash using Web Crypto API.
10
11
  *
11
12
  * @param data - String or binary data to hash. Strings are UTF-8 encoded.
12
- * @param algorithm - Hash algorithm. Defaults to SHA-256.
13
- * @returns Hexadecimal hash string.
13
+ * @returns 64-character hexadecimal hash string.
14
14
  */
15
- export declare const hash_secure: (data: BufferSource | string, algorithm?: "SHA-256" | "SHA-384" | "SHA-512") => Promise<string>;
15
+ export declare const hash_sha256: (data: BufferSource | string) => Promise<string>;
16
16
  /**
17
17
  * Computes a fast non-cryptographic hash using DJB2 algorithm.
18
18
  * Use for content comparison and cache keys, not security.
@@ -1 +1 @@
1
- {"version":3,"file":"hash.d.ts","sourceRoot":"../src/lib/","sources":["../src/lib/hash.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAgBH;;;;;;GAMG;AACH,eAAO,MAAM,WAAW,GACvB,MAAM,YAAY,GAAG,MAAM,EAC3B,YAAW,SAAS,GAAG,SAAS,GAAG,SAAqB,KACtD,OAAO,CAAC,MAAM,CAUhB,CAAC;AAEF;;;;;;;;;GASG;AACH,eAAO,MAAM,aAAa,GAAI,MAAM,YAAY,GAAG,MAAM,KAAG,MAkB3D,CAAC"}
1
+ {"version":3,"file":"hash.d.ts","sourceRoot":"../src/lib/","sources":["../src/lib/hash.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAMH;;;;;GAKG;AACH,eAAO,MAAM,WAAW,GAAU,MAAM,YAAY,GAAG,MAAM,KAAG,OAAO,CAAC,MAAM,CAI7E,CAAC;AAEF;;;;;;;;;GASG;AACH,eAAO,MAAM,aAAa,GAAI,MAAM,YAAY,GAAG,MAAM,KAAG,MAkB3D,CAAC"}
package/dist/hash.js CHANGED
@@ -1,39 +1,23 @@
1
1
  /**
2
2
  * Hash utilities for content comparison and cache invalidation.
3
3
  *
4
- * Provides both secure (cryptographic) and insecure (fast) hash functions.
4
+ * Provides `hash_sha256` (Web Crypto, async) and `hash_insecure` (DJB2, fast non-cryptographic).
5
+ * For BLAKE3, see `hash_blake3` in `hash_blake3.ts`.
5
6
  *
6
7
  * @module
7
8
  */
9
+ import { to_hex } from './hex.js';
8
10
  const encoder = new TextEncoder();
9
- // Lazily computed lookup table for byte to hex conversion
10
- let byte_to_hex;
11
- const get_byte_to_hex = () => {
12
- if (byte_to_hex === undefined) {
13
- byte_to_hex = new Array(256); // 256 possible byte values (0x00-0xff)
14
- for (let i = 0; i < 256; i++) {
15
- byte_to_hex[i] = i.toString(16).padStart(2, '0');
16
- }
17
- }
18
- return byte_to_hex;
19
- };
20
11
  /**
21
- * Computes a cryptographic hash using Web Crypto API.
12
+ * Computes a SHA-256 hash using Web Crypto API.
22
13
  *
23
14
  * @param data - String or binary data to hash. Strings are UTF-8 encoded.
24
- * @param algorithm - Hash algorithm. Defaults to SHA-256.
25
- * @returns Hexadecimal hash string.
15
+ * @returns 64-character hexadecimal hash string.
26
16
  */
27
- export const hash_secure = async (data, algorithm = 'SHA-256') => {
17
+ export const hash_sha256 = async (data) => {
28
18
  const buffer = typeof data === 'string' ? encoder.encode(data) : data;
29
- const digested = await crypto.subtle.digest(algorithm, buffer);
30
- const bytes = new Uint8Array(digested);
31
- const lookup = get_byte_to_hex();
32
- let hex = '';
33
- for (const byte of bytes) {
34
- hex += lookup[byte];
35
- }
36
- return hex;
19
+ const digested = await crypto.subtle.digest('SHA-256', buffer);
20
+ return to_hex(new Uint8Array(digested));
37
21
  };
38
22
  /**
39
23
  * Computes a fast non-cryptographic hash using DJB2 algorithm.
@@ -0,0 +1,15 @@
1
+ /**
2
+ * BLAKE3 cryptographic hashing via `@fuzdev/blake3_wasm`.
3
+ *
4
+ * Synchronous and fast. Returns hex-encoded 256-bit (32-byte) digests.
5
+ *
6
+ * @module
7
+ */
8
+ /**
9
+ * Computes a BLAKE3 hash synchronously.
10
+ *
11
+ * @param data - String or binary data to hash. Strings are UTF-8 encoded.
12
+ * @returns 64-character hexadecimal hash string (32 bytes).
13
+ */
14
+ export declare const hash_blake3: (data: BufferSource | string) => string;
15
+ //# sourceMappingURL=hash_blake3.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hash_blake3.d.ts","sourceRoot":"../src/lib/","sources":["../src/lib/hash_blake3.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAOH;;;;;GAKG;AACH,eAAO,MAAM,WAAW,GAAI,MAAM,YAAY,GAAG,MAAM,KAAG,MAAsC,CAAC"}
@@ -0,0 +1,17 @@
1
+ /**
2
+ * BLAKE3 cryptographic hashing via `@fuzdev/blake3_wasm`.
3
+ *
4
+ * Synchronous and fast. Returns hex-encoded 256-bit (32-byte) digests.
5
+ *
6
+ * @module
7
+ */
8
+ import { hash } from '@fuzdev/blake3_wasm';
9
+ import { to_hex } from './hex.js';
10
+ import { to_bytes } from './bytes.js';
11
+ /**
12
+ * Computes a BLAKE3 hash synchronously.
13
+ *
14
+ * @param data - String or binary data to hash. Strings are UTF-8 encoded.
15
+ * @returns 64-character hexadecimal hash string (32 bytes).
16
+ */
17
+ export const hash_blake3 = (data) => to_hex(hash(to_bytes(data)));
package/dist/hex.d.ts ADDED
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Hex encoding helpers.
3
+ *
4
+ * @module
5
+ */
6
+ /**
7
+ * Converts a `Uint8Array` to a lowercase hex string.
8
+ *
9
+ * @param bytes - Binary data to encode.
10
+ * @returns Hex string with two characters per byte.
11
+ */
12
+ export declare const to_hex: (bytes: Uint8Array) => string;
13
+ //# sourceMappingURL=hex.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hex.d.ts","sourceRoot":"../src/lib/","sources":["../src/lib/hex.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH;;;;;GAKG;AACH,eAAO,MAAM,MAAM,GAAI,OAAO,UAAU,KAAG,MAO1C,CAAC"}
package/dist/hex.js ADDED
@@ -0,0 +1,30 @@
1
+ /**
2
+ * Hex encoding helpers.
3
+ *
4
+ * @module
5
+ */
6
+ /**
7
+ * Converts a `Uint8Array` to a lowercase hex string.
8
+ *
9
+ * @param bytes - Binary data to encode.
10
+ * @returns Hex string with two characters per byte.
11
+ */
12
+ export const to_hex = (bytes) => {
13
+ const lookup = get_byte_to_hex();
14
+ let hex = '';
15
+ for (const byte of bytes) {
16
+ hex += lookup[byte];
17
+ }
18
+ return hex;
19
+ };
20
+ // Lazily computed lookup table for byte to hex conversion
21
+ let byte_to_hex;
22
+ const get_byte_to_hex = () => {
23
+ if (byte_to_hex === undefined) {
24
+ byte_to_hex = new Array(256); // 256 possible byte values (0x00-0xff)
25
+ for (let i = 0; i < 256; i++) {
26
+ byte_to_hex[i] = i.toString(16).padStart(2, '0');
27
+ }
28
+ }
29
+ return byte_to_hex;
30
+ };
package/dist/log.d.ts.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"log.d.ts","sourceRoot":"../src/lib/","sources":["../src/lib/log.ts"],"names":[],"mappings":"AAGA;;;;;;;GAOG;AACH,MAAM,MAAM,QAAQ,GAAG,KAAK,GAAG,OAAO,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC;AAEnE;;;GAGG;AACH,MAAM,MAAM,UAAU,GAAG,IAAI,CAAC,OAAO,OAAO,EAAE,OAAO,GAAG,MAAM,GAAG,KAAK,CAAC,CAAC;AAwBxE;;;;;GAKG;AACH,eAAO,MAAM,mBAAmB,GAAI,OAAO,QAAQ,KAAG,MAAiC,CAAC;AAExF;;;;;GAKG;AACH,eAAO,MAAM,eAAe,GAAI,OAAO,MAAM,GAAG,SAAS,KAAG,QAAQ,GAAG,SAItE,CAAC;AASF;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,qBAAa,MAAM;;IAClB,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IAkBzB;;;;;;;OAOG;gBACS,KAAK,CAAC,EAAE,MAAM,EAAE,OAAO,GAAE,aAAkB;IAiBvD;;OAEG;IACH,IAAI,KAAK,IAAI,QAAQ,CAQpB;IAED;;OAEG;IACH,IAAI,KAAK,CAAC,KAAK,EAAE,QAAQ,EAGxB;IAED;;;;;;OAMG;IACH,IAAI,MAAM,IAAI,OAAO,CAWpB;IAED;;OAEG;IACH,IAAI,MAAM,CAAC,KAAK,EAAE,OAAO,EAExB;IAED;;OAEG;IACH,IAAI,OAAO,IAAI,UAAU,CAQxB;IAED;;OAEG;IACH,IAAI,OAAO,CAAC,KAAK,EAAE,UAAU,EAE5B;IAED;;;;OAIG;IACH,IAAI,IAAI,IAAI,MAAM,CAMjB;IAED;;;;OAIG;IACH,oBAAoB,IAAI,IAAI;IAM5B;;;;OAIG;IACH,qBAAqB,IAAI,IAAI;IAU7B;;;;OAIG;IACH,sBAAsB,IAAI,IAAI;IA8G9B;;;;;;;;;;;;;;;;;OAiBG;IACH,KAAK,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,GAAE,aAAkB,GAAG,MAAM;IAezD;;;OAGG;IACH,KAAK,CAAC,GAAG,IAAI,EAAE,KAAK,CAAC,OAAO,CAAC,GAAG,IAAI;IAKpC;;;OAGG;IACH,IAAI,CAAC,GAAG,IAAI,EAAE,KAAK,CAAC,OAAO,CAAC,GAAG,IAAI;IAKnC;;;;;OAKG;IACH,IAAI,CAAC,GAAG,IAAI,EAAE,KAAK,CAAC,OAAO,CAAC,GAAG,IAAI;IAKnC;;;OAGG;IACH,KAAK,CAAC,GAAG,IAAI,EAAE,KAAK,CAAC,OAAO,CAAC,GAAG,IAAI;IAKpC;;;;;;;;;OASG;IACH,GAAG,CAAC,GAAG,IAAI,EAAE,KAAK,CAAC,OAAO,CAAC,GAAG,IAAI;CAGlC;AAED,MAAM,WAAW,aAAa;IAC7B;;;OAGG;IACH,KAAK,CAAC,EAAE,QAAQ,CAAC;IAEjB;;;;OAIG;IACH,OAAO,CAAC,EAAE,UAAU,CAAC;IAErB;;;OAGG;IACH,MAAM,CAAC,EAAE,OAAO,CAAC;CACjB"}
1
+ {"version":3,"file":"log.d.ts","sourceRoot":"../src/lib/","sources":["../src/lib/log.ts"],"names":[],"mappings":"AAGA;;;;;;;GAOG;AACH,MAAM,MAAM,QAAQ,GAAG,KAAK,GAAG,OAAO,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC;AAEnE;;;GAGG;AACH,MAAM,MAAM,UAAU,GAAG,IAAI,CAAC,OAAO,OAAO,EAAE,OAAO,GAAG,MAAM,GAAG,KAAK,CAAC,CAAC;AAwBxE;;;;;GAKG;AACH,eAAO,MAAM,mBAAmB,GAAI,OAAO,QAAQ,KAAG,MAAiC,CAAC;AAExF;;;;;GAKG;AACH,eAAO,MAAM,eAAe,GAAI,OAAO,MAAM,GAAG,SAAS,KAAG,QAAQ,GAAG,SAItE,CAAC;AASF;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,qBAAa,MAAM;;IAClB,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IAkBzB;;;;;;;OAOG;gBACS,KAAK,CAAC,EAAE,MAAM,EAAE,OAAO,GAAE,aAAkB;IAiBvD;;OAEG;IACH,IAAI,KAAK,IAAI,QAAQ,CAQpB;IAED;;OAEG;IACH,IAAI,KAAK,CAAC,KAAK,EAAE,QAAQ,EAGxB;IAED;;;;;;OAMG;IACH,IAAI,MAAM,IAAI,OAAO,CAWpB;IAED;;OAEG;IACH,IAAI,MAAM,CAAC,KAAK,EAAE,OAAO,EAExB;IAED;;OAEG;IACH,IAAI,OAAO,IAAI,UAAU,CAQxB;IAED;;OAEG;IACH,IAAI,OAAO,CAAC,KAAK,EAAE,UAAU,EAE5B;IAED;;;;OAIG;IACH,IAAI,IAAI,IAAI,MAAM,CAMjB;IAED;;;;OAIG;IACH,oBAAoB,IAAI,IAAI;IAM5B;;;;OAIG;IACH,qBAAqB,IAAI,IAAI;IAU7B;;;;OAIG;IACH,sBAAsB,IAAI,IAAI;IA8G9B;;;;;;;;;;;;;;;;;OAiBG;IACH,KAAK,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,GAAE,aAAkB,GAAG,MAAM;IAezD;;;OAGG;IACH,KAAK,CAAC,GAAG,IAAI,EAAE,KAAK,CAAC,OAAO,CAAC,GAAG,IAAI;IAKpC;;;OAGG;IACH,IAAI,CAAC,GAAG,IAAI,EAAE,KAAK,CAAC,OAAO,CAAC,GAAG,IAAI;IAKnC;;;;;OAKG;IACH,IAAI,CAAC,GAAG,IAAI,EAAE,KAAK,CAAC,OAAO,CAAC,GAAG,IAAI;IAUnC;;;OAGG;IACH,KAAK,CAAC,GAAG,IAAI,EAAE,KAAK,CAAC,OAAO,CAAC,GAAG,IAAI;IAKpC;;;;;;;;;OASG;IACH,GAAG,CAAC,GAAG,IAAI,EAAE,KAAK,CAAC,OAAO,CAAC,GAAG,IAAI;CAGlC;AAED,MAAM,WAAW,aAAa;IAC7B;;;OAGG;IACH,KAAK,CAAC,EAAE,QAAQ,CAAC;IAEjB;;;;OAIG;IACH,OAAO,CAAC,EAAE,UAAU,CAAC;IAErB;;;OAGG;IACH,MAAM,CAAC,EAAE,OAAO,CAAC;CACjB"}
package/dist/log.js CHANGED
@@ -366,7 +366,13 @@ export class Logger {
366
366
  info(...args) {
367
367
  if (this.#get_cached_level() < LOG_LEVEL_VALUES.info)
368
368
  return;
369
- this.console.log(this.#get_info_prefix(), ...args);
369
+ const prefix = this.#get_info_prefix();
370
+ if (prefix) {
371
+ this.console.log(prefix, ...args);
372
+ }
373
+ else {
374
+ this.console.log(...args);
375
+ }
370
376
  }
371
377
  /**
372
378
  * Logs a debug message with `┆debug┆` prefix.
package/dist/random.d.ts CHANGED
@@ -1,3 +1,12 @@
1
+ /**
2
+ * Random helpers that accept an optional `random` parameter,
3
+ * defaulting to `Math.random`. Pass a seeded PRNG for reproducible results:
4
+ *
5
+ * - {@link create_random_xoshiro} — fast, high-quality numeric seeding (recommended)
6
+ * - {@link create_random_alea} — supports string and variadic seeds
7
+ *
8
+ * @module
9
+ */
1
10
  import type { ArrayElement } from './types.js';
2
11
  /**
3
12
  * Generates a random `number` between `min` and `max`.
@@ -1 +1 @@
1
- {"version":3,"file":"random.d.ts","sourceRoot":"../src/lib/","sources":["../src/lib/random.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,YAAY,EAAC,MAAM,YAAY,CAAC;AAE7C;;GAEG;AACH,eAAO,MAAM,YAAY,GAAI,KAAK,MAAM,EAAE,KAAK,MAAM,EAAE,qBAAoB,KAAG,MACjD,CAAC;AAE9B;;;;GAIG;AACH,eAAO,MAAM,UAAU,GAAI,KAAK,MAAM,EAAE,KAAK,MAAM,EAAE,qBAAoB,KAAG,MAC/B,CAAC;AAE9C;;GAEG;AACH,eAAO,MAAM,cAAc,GAAI,qBAAoB,KAAG,OAAyB,CAAC;AAEhF;;GAEG;AACH,eAAO,MAAM,WAAW,GAAI,CAAC,SAAS,aAAa,CAAC,GAAG,CAAC,EACvD,KAAK,CAAC,EACN,qBAAoB,KAClB,YAAY,CAAC,CAAC,CAA+C,CAAC;AAEjE;;;GAGG;AACH,eAAO,MAAM,OAAO,EAAE,CAAC,CAAC,SAAS,KAAK,CAAC,GAAG,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,MAAM,CAAC,EAAE,OAAO,UAAU,KAAK,CAcrF,CAAC"}
1
+ {"version":3,"file":"random.d.ts","sourceRoot":"../src/lib/","sources":["../src/lib/random.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAC,YAAY,EAAC,MAAM,YAAY,CAAC;AAE7C;;GAEG;AACH,eAAO,MAAM,YAAY,GAAI,KAAK,MAAM,EAAE,KAAK,MAAM,EAAE,qBAAoB,KAAG,MACjD,CAAC;AAE9B;;;;GAIG;AACH,eAAO,MAAM,UAAU,GAAI,KAAK,MAAM,EAAE,KAAK,MAAM,EAAE,qBAAoB,KAAG,MAC/B,CAAC;AAE9C;;GAEG;AACH,eAAO,MAAM,cAAc,GAAI,qBAAoB,KAAG,OAAyB,CAAC;AAEhF;;GAEG;AACH,eAAO,MAAM,WAAW,GAAI,CAAC,SAAS,aAAa,CAAC,GAAG,CAAC,EACvD,KAAK,CAAC,EACN,qBAAoB,KAClB,YAAY,CAAC,CAAC,CAA+C,CAAC;AAEjE;;;GAGG;AACH,eAAO,MAAM,OAAO,EAAE,CAAC,CAAC,SAAS,KAAK,CAAC,GAAG,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,MAAM,CAAC,EAAE,OAAO,UAAU,KAAK,CAcrF,CAAC"}
package/dist/random.js CHANGED
@@ -1,3 +1,12 @@
1
+ /**
2
+ * Random helpers that accept an optional `random` parameter,
3
+ * defaulting to `Math.random`. Pass a seeded PRNG for reproducible results:
4
+ *
5
+ * - {@link create_random_xoshiro} — fast, high-quality numeric seeding (recommended)
6
+ * - {@link create_random_alea} — supports string and variadic seeds
7
+ *
8
+ * @module
9
+ */
1
10
  /**
2
11
  * Generates a random `number` between `min` and `max`.
3
12
  */
@@ -1,4 +1,35 @@
1
- export interface Alea {
1
+ /**
2
+ * Alea: a seedable pseudo-random number generator by Johannes Baagøe.
3
+ * Supports variadic and string seeds (`create_random_alea('my', 3, 'seeds')`).
4
+ * For numeric seeds, prefer `create_random_xoshiro` which is faster with equal quality.
5
+ *
6
+ * DO NOT USE when security matters — use the Web Crypto API (`crypto.getRandomValues`) instead.
7
+ *
8
+ * Alea passes all 11 distribution quality tests at 10M samples,
9
+ * performing on par with `Math.random` (V8's xorshift128+):
10
+ *
11
+ * - mean, variance, chi-squared uniformity, Kolmogorov-Smirnov
12
+ * - lag-1 through lag-8 autocorrelation
13
+ * - runs test, gap test, permutation test (triples)
14
+ * - bit-level frequency (bits 0-7)
15
+ * - 2D serial pairs (25x25 through 200x200 grids)
16
+ * - birthday spacings (Marsaglia parameters)
17
+ *
18
+ * Speed is ~19% slower than `Math.random` (~12.2M ops/sec vs ~15.0M ops/sec).
19
+ *
20
+ * To reproduce:
21
+ *
22
+ * ```bash
23
+ * npm run benchmark_random_quality # distribution tests (N=1M)
24
+ * npm run benchmark_random_quality -- --deep # thorough (N=10M, multi-trial)
25
+ * npm run benchmark_random # speed comparison
26
+ * ```
27
+ *
28
+ * @see https://github.com/nquinlan/better-random-numbers-for-javascript-mirror
29
+ *
30
+ * @module
31
+ */
32
+ export interface RandomAlea {
2
33
  (): number;
3
34
  uint32: () => number;
4
35
  fract53: () => number;
@@ -9,15 +40,7 @@ export interface Alea {
9
40
  * Seeded pseudo-random number generator.
10
41
  * DO NOT USE when security matters, use webcrypto APIs instead.
11
42
  *
12
- * @see http://baagoe.com/en/RandomMusings/javascript/
13
43
  * @see https://github.com/nquinlan/better-random-numbers-for-javascript-mirror
14
44
  */
15
- export declare const create_random_alea: (...seed: Array<unknown>) => Alea;
16
- type Mash = (data: any) => number;
17
- /**
18
- * @source http://baagoe.com/en/RandomMusings/javascript/
19
- * @copyright Johannes Baagøe <baagoe@baagoe.com>, 2010
20
- */
21
- export declare const masher: () => Mash;
22
- export {};
45
+ export declare const create_random_alea: (...seed: Array<unknown>) => RandomAlea;
23
46
  //# sourceMappingURL=random_alea.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"random_alea.d.ts","sourceRoot":"../src/lib/","sources":["../src/lib/random_alea.ts"],"names":[],"mappings":"AA8BA,MAAM,WAAW,IAAI;IACpB,IAAI,MAAM,CAAC;IACX,MAAM,EAAE,MAAM,MAAM,CAAC;IACrB,OAAO,EAAE,MAAM,MAAM,CAAC;IACtB,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,KAAK,CAAC,OAAO,CAAC,CAAC;CACtB;AAED;;;;;;GAMG;AACH,eAAO,MAAM,kBAAkB,GAAI,GAAG,MAAM,KAAK,CAAC,OAAO,CAAC,KAAG,IAqC5D,CAAC;AAEF,KAAK,IAAI,GAAG,CAAC,IAAI,EAAE,GAAG,KAAK,MAAM,CAAC;AAElC;;;GAGG;AACH,eAAO,MAAM,MAAM,QAAO,IAgBzB,CAAC"}
1
+ {"version":3,"file":"random_alea.d.ts","sourceRoot":"../src/lib/","sources":["../src/lib/random_alea.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AA0BH,MAAM,WAAW,UAAU;IAC1B,IAAI,MAAM,CAAC;IACX,MAAM,EAAE,MAAM,MAAM,CAAC;IACrB,OAAO,EAAE,MAAM,MAAM,CAAC;IACtB,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,KAAK,CAAC,OAAO,CAAC,CAAC;CACtB;AAED;;;;;GAKG;AACH,eAAO,MAAM,kBAAkB,GAAI,GAAG,MAAM,KAAK,CAAC,OAAO,CAAC,KAAG,UAqC5D,CAAC"}
@@ -1,37 +1,38 @@
1
- /*
2
-
3
- DO NOT USE when security matters, use webcrypto APIs instead.
4
- This is the Alea pseudo-random number generator by Johannes Baagøe.
5
-
6
- From http://baagoe.com/en/RandomMusings/javascript/
7
- via https://github.com/nquinlan/better-random-numbers-for-javascript-mirror
8
-
9
- Copyright (C) 2010 by Johannes Baagøe <baagoe@baagoe.org>
10
-
11
- Permission is hereby granted, free of charge, to any person obtaining a copy
12
- of this software and associated documentation files (the "Software"), to deal
13
- in the Software without restriction, including without limitation the rights
14
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
15
- copies of the Software, and to permit persons to whom the Software is
16
- furnished to do so, subject to the following conditions:
17
-
18
- The above copyright notice and this permission notice shall be included in
19
- all copies or substantial portions of the Software.
20
-
21
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
22
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
23
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
24
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
25
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
26
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
27
- THE SOFTWARE.
28
-
29
- */
1
+ /**
2
+ * Alea: a seedable pseudo-random number generator by Johannes Baagøe.
3
+ * Supports variadic and string seeds (`create_random_alea('my', 3, 'seeds')`).
4
+ * For numeric seeds, prefer `create_random_xoshiro` which is faster with equal quality.
5
+ *
6
+ * DO NOT USE when security matters — use the Web Crypto API (`crypto.getRandomValues`) instead.
7
+ *
8
+ * Alea passes all 11 distribution quality tests at 10M samples,
9
+ * performing on par with `Math.random` (V8's xorshift128+):
10
+ *
11
+ * - mean, variance, chi-squared uniformity, Kolmogorov-Smirnov
12
+ * - lag-1 through lag-8 autocorrelation
13
+ * - runs test, gap test, permutation test (triples)
14
+ * - bit-level frequency (bits 0-7)
15
+ * - 2D serial pairs (25x25 through 200x200 grids)
16
+ * - birthday spacings (Marsaglia parameters)
17
+ *
18
+ * Speed is ~19% slower than `Math.random` (~12.2M ops/sec vs ~15.0M ops/sec).
19
+ *
20
+ * To reproduce:
21
+ *
22
+ * ```bash
23
+ * npm run benchmark_random_quality # distribution tests (N=1M)
24
+ * npm run benchmark_random_quality -- --deep # thorough (N=10M, multi-trial)
25
+ * npm run benchmark_random # speed comparison
26
+ * ```
27
+ *
28
+ * @see https://github.com/nquinlan/better-random-numbers-for-javascript-mirror
29
+ *
30
+ * @module
31
+ */
30
32
  /**
31
33
  * Seeded pseudo-random number generator.
32
34
  * DO NOT USE when security matters, use webcrypto APIs instead.
33
35
  *
34
- * @see http://baagoe.com/en/RandomMusings/javascript/
35
36
  * @see https://github.com/nquinlan/better-random-numbers-for-javascript-mirror
36
37
  */
37
38
  export const create_random_alea = (...seed) => {
@@ -73,10 +74,10 @@ export const create_random_alea = (...seed) => {
73
74
  return random;
74
75
  };
75
76
  /**
76
- * @source http://baagoe.com/en/RandomMusings/javascript/
77
+ * @source https://github.com/nquinlan/better-random-numbers-for-javascript-mirror
77
78
  * @copyright Johannes Baagøe <baagoe@baagoe.com>, 2010
78
79
  */
79
- export const masher = () => {
80
+ const masher = () => {
80
81
  let n = 0xefc8249d;
81
82
  return (data) => {
82
83
  const d = data + '';
@@ -0,0 +1,51 @@
1
+ /**
2
+ * Xoshiro128**: a seedable pseudo-random number generator by Blackman & Vigna (2018).
3
+ * Recommended for reproducible randomness (testing, simulations, procedural generation).
4
+ *
5
+ * DO NOT USE when security matters — use the Web Crypto API (`crypto.getRandomValues`) instead.
6
+ *
7
+ * Xoshiro128** has a 2^128-1 period and passes BigCrush.
8
+ * It uses pure 32-bit arithmetic (shifts, rotates, XOR, multiply)
9
+ * making it very fast in JavaScript via `Math.imul`.
10
+ *
11
+ * Xoshiro128** passes all 11 distribution quality tests at 10M samples,
12
+ * performing on par with `Math.random` (V8's xorshift128+):
13
+ *
14
+ * - mean, variance, chi-squared uniformity, Kolmogorov-Smirnov
15
+ * - lag-1 through lag-8 autocorrelation
16
+ * - runs test, gap test, permutation test (triples)
17
+ * - bit-level frequency (bits 0-7)
18
+ * - 2D serial pairs (25x25 through 200x200 grids)
19
+ * - birthday spacings (Marsaglia parameters)
20
+ *
21
+ * Speed is near-native (~4% slower than `Math.random`) and ~18% faster than Alea
22
+ * (~14.4M ops/sec vs ~15.0M and ~12.2M respectively).
23
+ *
24
+ * To reproduce:
25
+ *
26
+ * ```bash
27
+ * npm run benchmark_random_quality # distribution tests (N=1M)
28
+ * npm run benchmark_random_quality -- --deep # thorough (N=10M, multi-trial)
29
+ * npm run benchmark_random # speed comparison
30
+ * ```
31
+ *
32
+ * @see https://prng.di.unimi.it/
33
+ * @see https://vigna.di.unimi.it/ftp/papers/ScrambledLinear.pdf
34
+ *
35
+ * @module
36
+ */
37
+ export interface RandomXoshiro {
38
+ (): number;
39
+ uint32: () => number;
40
+ fract53: () => number;
41
+ version: string;
42
+ seed: number;
43
+ }
44
+ /**
45
+ * Seeded pseudo-random number generator using the Xoshiro128** algorithm.
46
+ * DO NOT USE when security matters, use webcrypto APIs instead.
47
+ *
48
+ * @see https://prng.di.unimi.it/
49
+ */
50
+ export declare const create_random_xoshiro: (seed?: number) => RandomXoshiro;
51
+ //# sourceMappingURL=random_xoshiro.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"random_xoshiro.d.ts","sourceRoot":"../src/lib/","sources":["../src/lib/random_xoshiro.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmCG;AAcH,MAAM,WAAW,aAAa;IAC7B,IAAI,MAAM,CAAC;IACX,MAAM,EAAE,MAAM,MAAM,CAAC;IACrB,OAAO,EAAE,MAAM,MAAM,CAAC;IACtB,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;CACb;AAED;;;;;GAKG;AACH,eAAO,MAAM,qBAAqB,GAAI,OAAO,MAAM,KAAG,aA8CrD,CAAC"}
@@ -0,0 +1,92 @@
1
+ /**
2
+ * Xoshiro128**: a seedable pseudo-random number generator by Blackman & Vigna (2018).
3
+ * Recommended for reproducible randomness (testing, simulations, procedural generation).
4
+ *
5
+ * DO NOT USE when security matters — use the Web Crypto API (`crypto.getRandomValues`) instead.
6
+ *
7
+ * Xoshiro128** has a 2^128-1 period and passes BigCrush.
8
+ * It uses pure 32-bit arithmetic (shifts, rotates, XOR, multiply)
9
+ * making it very fast in JavaScript via `Math.imul`.
10
+ *
11
+ * Xoshiro128** passes all 11 distribution quality tests at 10M samples,
12
+ * performing on par with `Math.random` (V8's xorshift128+):
13
+ *
14
+ * - mean, variance, chi-squared uniformity, Kolmogorov-Smirnov
15
+ * - lag-1 through lag-8 autocorrelation
16
+ * - runs test, gap test, permutation test (triples)
17
+ * - bit-level frequency (bits 0-7)
18
+ * - 2D serial pairs (25x25 through 200x200 grids)
19
+ * - birthday spacings (Marsaglia parameters)
20
+ *
21
+ * Speed is near-native (~4% slower than `Math.random`) and ~18% faster than Alea
22
+ * (~14.4M ops/sec vs ~15.0M and ~12.2M respectively).
23
+ *
24
+ * To reproduce:
25
+ *
26
+ * ```bash
27
+ * npm run benchmark_random_quality # distribution tests (N=1M)
28
+ * npm run benchmark_random_quality -- --deep # thorough (N=10M, multi-trial)
29
+ * npm run benchmark_random # speed comparison
30
+ * ```
31
+ *
32
+ * @see https://prng.di.unimi.it/
33
+ * @see https://vigna.di.unimi.it/ftp/papers/ScrambledLinear.pdf
34
+ *
35
+ * @module
36
+ */
37
+ /**
38
+ * Seeded pseudo-random number generator using the Xoshiro128** algorithm.
39
+ * DO NOT USE when security matters, use webcrypto APIs instead.
40
+ *
41
+ * @see https://prng.di.unimi.it/
42
+ */
43
+ export const create_random_xoshiro = (seed) => {
44
+ const actual_seed = seed ?? Date.now();
45
+ let sm_state = actual_seed | 0;
46
+ // expand single seed into 4 state words via SplitMix32
47
+ let r = splitmix32_next(sm_state);
48
+ let s0 = r.value;
49
+ sm_state = r.state;
50
+ r = splitmix32_next(sm_state);
51
+ let s1 = r.value;
52
+ sm_state = r.state;
53
+ r = splitmix32_next(sm_state);
54
+ let s2 = r.value;
55
+ sm_state = r.state;
56
+ r = splitmix32_next(sm_state);
57
+ let s3 = r.value;
58
+ // guard against all-zero state (absorbing fixed point)
59
+ if ((s0 | s1 | s2 | s3) === 0)
60
+ s0 = 1;
61
+ const next_uint32 = () => {
62
+ const result = Math.imul(rotl(Math.imul(s1, 5), 7), 9);
63
+ const t = s1 << 9;
64
+ s2 ^= s0;
65
+ s3 ^= s1;
66
+ s1 ^= s2;
67
+ s0 ^= s3;
68
+ s2 ^= t;
69
+ s3 = rotl(s3, 11);
70
+ return result >>> 0;
71
+ };
72
+ const random = () => next_uint32() / 0x100000000; // 2^32
73
+ random.uint32 = next_uint32;
74
+ random.fract53 = () => {
75
+ return random() + ((random() * 0x200000) | 0) * 1.1102230246251565e-16; // 2^-53
76
+ };
77
+ random.version = 'Xoshiro128** 1.0';
78
+ random.seed = actual_seed;
79
+ return random;
80
+ };
81
+ /**
82
+ * Expands a 32-bit state by one step using the SplitMix32 algorithm,
83
+ * producing the four state words needed by Xoshiro128**.
84
+ */
85
+ const splitmix32_next = (prev_state) => {
86
+ const next_state = (prev_state + 0x9e3779b9) | 0;
87
+ let z = next_state;
88
+ z = Math.imul(z ^ (z >>> 16), 0x85ebca6b);
89
+ z = Math.imul(z ^ (z >>> 13), 0xc2b2ae35);
90
+ return { value: (z ^ (z >>> 16)) >>> 0, state: next_state };
91
+ };
92
+ const rotl = (x, k) => (x << k) | (x >>> (32 - k));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fuzdev/fuz_util",
3
- "version": "0.52.1",
3
+ "version": "0.53.0",
4
4
  "description": "utility belt for JS",
5
5
  "glyph": "🦕",
6
6
  "logo": "logo.svg",
@@ -31,7 +31,9 @@
31
31
  "benchmark:save": "gro run src/benchmarks/run.ts --save",
32
32
  "benchmark_slugify": "gro run src/benchmarks/slugify.benchmark.ts",
33
33
  "benchmark_deep_equal": "gro run src/benchmarks/deep_equal.benchmark.ts",
34
- "benchmark_deep_equal_comparison": "gro run src/benchmarks/deep_equal_comparison.benchmark.ts"
34
+ "benchmark_deep_equal_comparison": "gro run src/benchmarks/deep_equal_comparison.benchmark.ts",
35
+ "benchmark_random": "gro run src/benchmarks/random.benchmark.ts",
36
+ "benchmark_random_quality": "gro run src/benchmarks/random_quality.ts"
35
37
  },
36
38
  "type": "module",
37
39
  "engines": {
@@ -44,6 +46,7 @@
44
46
  "web"
45
47
  ],
46
48
  "peerDependencies": {
49
+ "@fuzdev/blake3_wasm": "^0.1.0",
47
50
  "@types/estree": "^1",
48
51
  "@types/node": "^24",
49
52
  "esm-env": "^1.2.2",
@@ -51,13 +54,16 @@
51
54
  "zod": "^4.0.14"
52
55
  },
53
56
  "peerDependenciesMeta": {
54
- "@types/node": {
57
+ "@fuzdev/blake3_wasm": {
55
58
  "optional": true
56
59
  },
57
- "esm-env": {
60
+ "@types/estree": {
58
61
  "optional": true
59
62
  },
60
- "@types/estree": {
63
+ "@types/node": {
64
+ "optional": true
65
+ },
66
+ "esm-env": {
61
67
  "optional": true
62
68
  },
63
69
  "svelte": {
@@ -69,10 +75,11 @@
69
75
  },
70
76
  "devDependencies": {
71
77
  "@changesets/changelog-git": "^0.2.1",
78
+ "@fuzdev/blake3_wasm": "^0.1.0",
72
79
  "@fuzdev/fuz_code": "^0.45.1",
73
- "@fuzdev/fuz_css": "^0.53.0",
74
- "@fuzdev/fuz_ui": "^0.184.0",
75
- "@fuzdev/gro": "^0.195.0",
80
+ "@fuzdev/fuz_css": "^0.54.0",
81
+ "@fuzdev/fuz_ui": "^0.185.2",
82
+ "@fuzdev/gro": "^0.196.0",
76
83
  "@jridgewell/trace-mapping": "^0.3.31",
77
84
  "@ryanatkn/eslint-config": "^0.9.0",
78
85
  "@sveltejs/adapter-static": "^3.0.10",
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Binary data conversion helpers.
3
+ *
4
+ * @module
5
+ */
6
+
7
+ const encoder = new TextEncoder();
8
+
9
+ /**
10
+ * Converts string or binary data to a `Uint8Array`.
11
+ * Strings are UTF-8 encoded. `Uint8Array` inputs are returned as-is.
12
+ *
13
+ * @param data - String or `BufferSource` to convert.
14
+ * @returns `Uint8Array` view of the data.
15
+ */
16
+ export const to_bytes = (data: BufferSource | string): Uint8Array => {
17
+ if (typeof data === 'string') return encoder.encode(data);
18
+ if (data instanceof Uint8Array) return data;
19
+ if (data instanceof ArrayBuffer) return new Uint8Array(data);
20
+ return new Uint8Array(data.buffer, data.byteOffset, data.byteLength);
21
+ };
package/src/lib/hash.ts CHANGED
@@ -1,45 +1,26 @@
1
1
  /**
2
2
  * Hash utilities for content comparison and cache invalidation.
3
3
  *
4
- * Provides both secure (cryptographic) and insecure (fast) hash functions.
4
+ * Provides `hash_sha256` (Web Crypto, async) and `hash_insecure` (DJB2, fast non-cryptographic).
5
+ * For BLAKE3, see `hash_blake3` in `hash_blake3.ts`.
5
6
  *
6
7
  * @module
7
8
  */
8
9
 
9
- const encoder = new TextEncoder();
10
+ import {to_hex} from './hex.js';
10
11
 
11
- // Lazily computed lookup table for byte to hex conversion
12
- let byte_to_hex: Array<string> | undefined;
13
- const get_byte_to_hex = (): Array<string> => {
14
- if (byte_to_hex === undefined) {
15
- byte_to_hex = new Array(256); // 256 possible byte values (0x00-0xff)
16
- for (let i = 0; i < 256; i++) {
17
- byte_to_hex[i] = i.toString(16).padStart(2, '0');
18
- }
19
- }
20
- return byte_to_hex;
21
- };
12
+ const encoder = new TextEncoder();
22
13
 
23
14
  /**
24
- * Computes a cryptographic hash using Web Crypto API.
15
+ * Computes a SHA-256 hash using Web Crypto API.
25
16
  *
26
17
  * @param data - String or binary data to hash. Strings are UTF-8 encoded.
27
- * @param algorithm - Hash algorithm. Defaults to SHA-256.
28
- * @returns Hexadecimal hash string.
18
+ * @returns 64-character hexadecimal hash string.
29
19
  */
30
- export const hash_secure = async (
31
- data: BufferSource | string,
32
- algorithm: 'SHA-256' | 'SHA-384' | 'SHA-512' = 'SHA-256',
33
- ): Promise<string> => {
20
+ export const hash_sha256 = async (data: BufferSource | string): Promise<string> => {
34
21
  const buffer = typeof data === 'string' ? encoder.encode(data) : data;
35
- const digested = await crypto.subtle.digest(algorithm, buffer);
36
- const bytes = new Uint8Array(digested);
37
- const lookup = get_byte_to_hex();
38
- let hex = '';
39
- for (const byte of bytes) {
40
- hex += lookup[byte];
41
- }
42
- return hex;
22
+ const digested = await crypto.subtle.digest('SHA-256', buffer);
23
+ return to_hex(new Uint8Array(digested));
43
24
  };
44
25
 
45
26
  /**
@@ -0,0 +1,20 @@
1
+ /**
2
+ * BLAKE3 cryptographic hashing via `@fuzdev/blake3_wasm`.
3
+ *
4
+ * Synchronous and fast. Returns hex-encoded 256-bit (32-byte) digests.
5
+ *
6
+ * @module
7
+ */
8
+
9
+ import {hash} from '@fuzdev/blake3_wasm';
10
+
11
+ import {to_hex} from './hex.js';
12
+ import {to_bytes} from './bytes.js';
13
+
14
+ /**
15
+ * Computes a BLAKE3 hash synchronously.
16
+ *
17
+ * @param data - String or binary data to hash. Strings are UTF-8 encoded.
18
+ * @returns 64-character hexadecimal hash string (32 bytes).
19
+ */
20
+ export const hash_blake3 = (data: BufferSource | string): string => to_hex(hash(to_bytes(data)));
package/src/lib/hex.ts ADDED
@@ -0,0 +1,32 @@
1
+ /**
2
+ * Hex encoding helpers.
3
+ *
4
+ * @module
5
+ */
6
+
7
+ /**
8
+ * Converts a `Uint8Array` to a lowercase hex string.
9
+ *
10
+ * @param bytes - Binary data to encode.
11
+ * @returns Hex string with two characters per byte.
12
+ */
13
+ export const to_hex = (bytes: Uint8Array): string => {
14
+ const lookup = get_byte_to_hex();
15
+ let hex = '';
16
+ for (const byte of bytes) {
17
+ hex += lookup[byte];
18
+ }
19
+ return hex;
20
+ };
21
+
22
+ // Lazily computed lookup table for byte to hex conversion
23
+ let byte_to_hex: Array<string> | undefined;
24
+ const get_byte_to_hex = (): Array<string> => {
25
+ if (byte_to_hex === undefined) {
26
+ byte_to_hex = new Array(256); // 256 possible byte values (0x00-0xff)
27
+ for (let i = 0; i < 256; i++) {
28
+ byte_to_hex[i] = i.toString(16).padStart(2, '0');
29
+ }
30
+ }
31
+ return byte_to_hex;
32
+ };
package/src/lib/log.ts CHANGED
@@ -415,7 +415,12 @@ export class Logger {
415
415
  */
416
416
  info(...args: Array<unknown>): void {
417
417
  if (this.#get_cached_level() < LOG_LEVEL_VALUES.info) return;
418
- this.console.log(this.#get_info_prefix(), ...args);
418
+ const prefix = this.#get_info_prefix();
419
+ if (prefix) {
420
+ this.console.log(prefix, ...args);
421
+ } else {
422
+ this.console.log(...args);
423
+ }
419
424
  }
420
425
 
421
426
  /**
package/src/lib/random.ts CHANGED
@@ -1,3 +1,13 @@
1
+ /**
2
+ * Random helpers that accept an optional `random` parameter,
3
+ * defaulting to `Math.random`. Pass a seeded PRNG for reproducible results:
4
+ *
5
+ * - {@link create_random_xoshiro} — fast, high-quality numeric seeding (recommended)
6
+ * - {@link create_random_alea} — supports string and variadic seeds
7
+ *
8
+ * @module
9
+ */
10
+
1
11
  import type {ArrayElement} from './types.js';
2
12
 
3
13
  /**
@@ -1,10 +1,36 @@
1
- /*
2
-
3
- DO NOT USE when security matters, use webcrypto APIs instead.
4
- This is the Alea pseudo-random number generator by Johannes Baagøe.
1
+ /**
2
+ * Alea: a seedable pseudo-random number generator by Johannes Baagøe.
3
+ * Supports variadic and string seeds (`create_random_alea('my', 3, 'seeds')`).
4
+ * For numeric seeds, prefer `create_random_xoshiro` which is faster with equal quality.
5
+ *
6
+ * DO NOT USE when security matters — use the Web Crypto API (`crypto.getRandomValues`) instead.
7
+ *
8
+ * Alea passes all 11 distribution quality tests at 10M samples,
9
+ * performing on par with `Math.random` (V8's xorshift128+):
10
+ *
11
+ * - mean, variance, chi-squared uniformity, Kolmogorov-Smirnov
12
+ * - lag-1 through lag-8 autocorrelation
13
+ * - runs test, gap test, permutation test (triples)
14
+ * - bit-level frequency (bits 0-7)
15
+ * - 2D serial pairs (25x25 through 200x200 grids)
16
+ * - birthday spacings (Marsaglia parameters)
17
+ *
18
+ * Speed is ~19% slower than `Math.random` (~12.2M ops/sec vs ~15.0M ops/sec).
19
+ *
20
+ * To reproduce:
21
+ *
22
+ * ```bash
23
+ * npm run benchmark_random_quality # distribution tests (N=1M)
24
+ * npm run benchmark_random_quality -- --deep # thorough (N=10M, multi-trial)
25
+ * npm run benchmark_random # speed comparison
26
+ * ```
27
+ *
28
+ * @see https://github.com/nquinlan/better-random-numbers-for-javascript-mirror
29
+ *
30
+ * @module
31
+ */
5
32
 
6
- From http://baagoe.com/en/RandomMusings/javascript/
7
- via https://github.com/nquinlan/better-random-numbers-for-javascript-mirror
33
+ /*
8
34
 
9
35
  Copyright (C) 2010 by Johannes Baagøe <baagoe@baagoe.org>
10
36
 
@@ -28,7 +54,7 @@ THE SOFTWARE.
28
54
 
29
55
  */
30
56
 
31
- export interface Alea {
57
+ export interface RandomAlea {
32
58
  (): number;
33
59
  uint32: () => number;
34
60
  fract53: () => number;
@@ -40,10 +66,9 @@ export interface Alea {
40
66
  * Seeded pseudo-random number generator.
41
67
  * DO NOT USE when security matters, use webcrypto APIs instead.
42
68
  *
43
- * @see http://baagoe.com/en/RandomMusings/javascript/
44
69
  * @see https://github.com/nquinlan/better-random-numbers-for-javascript-mirror
45
70
  */
46
- export const create_random_alea = (...seed: Array<unknown>): Alea => {
71
+ export const create_random_alea = (...seed: Array<unknown>): RandomAlea => {
47
72
  let s0 = 0;
48
73
  let s1 = 0;
49
74
  let s2 = 0;
@@ -65,7 +90,7 @@ export const create_random_alea = (...seed: Array<unknown>): Alea => {
65
90
  }
66
91
  mash = null;
67
92
 
68
- const random: Alea = (): number => {
93
+ const random: RandomAlea = (): number => {
69
94
  const t = 2091639 * s0 + c * 2.3283064365386963e-10; // 2^-32
70
95
  s0 = s1;
71
96
  s1 = s2;
@@ -85,10 +110,10 @@ export const create_random_alea = (...seed: Array<unknown>): Alea => {
85
110
  type Mash = (data: any) => number;
86
111
 
87
112
  /**
88
- * @source http://baagoe.com/en/RandomMusings/javascript/
113
+ * @source https://github.com/nquinlan/better-random-numbers-for-javascript-mirror
89
114
  * @copyright Johannes Baagøe <baagoe@baagoe.com>, 2010
90
115
  */
91
- export const masher = (): Mash => {
116
+ const masher = (): Mash => {
92
117
  let n = 0xefc8249d;
93
118
  return (data) => {
94
119
  const d = data + '';
@@ -0,0 +1,124 @@
1
+ /**
2
+ * Xoshiro128**: a seedable pseudo-random number generator by Blackman & Vigna (2018).
3
+ * Recommended for reproducible randomness (testing, simulations, procedural generation).
4
+ *
5
+ * DO NOT USE when security matters — use the Web Crypto API (`crypto.getRandomValues`) instead.
6
+ *
7
+ * Xoshiro128** has a 2^128-1 period and passes BigCrush.
8
+ * It uses pure 32-bit arithmetic (shifts, rotates, XOR, multiply)
9
+ * making it very fast in JavaScript via `Math.imul`.
10
+ *
11
+ * Xoshiro128** passes all 11 distribution quality tests at 10M samples,
12
+ * performing on par with `Math.random` (V8's xorshift128+):
13
+ *
14
+ * - mean, variance, chi-squared uniformity, Kolmogorov-Smirnov
15
+ * - lag-1 through lag-8 autocorrelation
16
+ * - runs test, gap test, permutation test (triples)
17
+ * - bit-level frequency (bits 0-7)
18
+ * - 2D serial pairs (25x25 through 200x200 grids)
19
+ * - birthday spacings (Marsaglia parameters)
20
+ *
21
+ * Speed is near-native (~4% slower than `Math.random`) and ~18% faster than Alea
22
+ * (~14.4M ops/sec vs ~15.0M and ~12.2M respectively).
23
+ *
24
+ * To reproduce:
25
+ *
26
+ * ```bash
27
+ * npm run benchmark_random_quality # distribution tests (N=1M)
28
+ * npm run benchmark_random_quality -- --deep # thorough (N=10M, multi-trial)
29
+ * npm run benchmark_random # speed comparison
30
+ * ```
31
+ *
32
+ * @see https://prng.di.unimi.it/
33
+ * @see https://vigna.di.unimi.it/ftp/papers/ScrambledLinear.pdf
34
+ *
35
+ * @module
36
+ */
37
+
38
+ /*
39
+
40
+ Written in 2018 by David Blackman and Sebastiano Vigna (vigna@acm.org)
41
+
42
+ To the extent possible under law, the author has dedicated all copyright
43
+ and related and neighboring rights to this software to the public domain
44
+ worldwide. This software is distributed without any warranty.
45
+
46
+ See <https://creativecommons.org/publicdomain/zero/1.0/>.
47
+
48
+ */
49
+
50
+ export interface RandomXoshiro {
51
+ (): number;
52
+ uint32: () => number;
53
+ fract53: () => number;
54
+ version: string;
55
+ seed: number;
56
+ }
57
+
58
+ /**
59
+ * Seeded pseudo-random number generator using the Xoshiro128** algorithm.
60
+ * DO NOT USE when security matters, use webcrypto APIs instead.
61
+ *
62
+ * @see https://prng.di.unimi.it/
63
+ */
64
+ export const create_random_xoshiro = (seed?: number): RandomXoshiro => {
65
+ const actual_seed = seed ?? Date.now();
66
+ let sm_state = actual_seed | 0;
67
+
68
+ // expand single seed into 4 state words via SplitMix32
69
+ let r = splitmix32_next(sm_state);
70
+ let s0 = r.value;
71
+ sm_state = r.state;
72
+ r = splitmix32_next(sm_state);
73
+ let s1 = r.value;
74
+ sm_state = r.state;
75
+ r = splitmix32_next(sm_state);
76
+ let s2 = r.value;
77
+ sm_state = r.state;
78
+ r = splitmix32_next(sm_state);
79
+ let s3 = r.value;
80
+
81
+ // guard against all-zero state (absorbing fixed point)
82
+ if ((s0 | s1 | s2 | s3) === 0) s0 = 1;
83
+
84
+ const next_uint32 = (): number => {
85
+ const result = Math.imul(rotl(Math.imul(s1, 5), 7), 9);
86
+ const t = s1 << 9;
87
+
88
+ s2 ^= s0;
89
+ s3 ^= s1;
90
+ s1 ^= s2;
91
+ s0 ^= s3;
92
+ s2 ^= t;
93
+ s3 = rotl(s3, 11);
94
+
95
+ return result >>> 0;
96
+ };
97
+
98
+ const random: RandomXoshiro = (): number => next_uint32() / 0x100000000; // 2^32
99
+
100
+ random.uint32 = next_uint32;
101
+
102
+ random.fract53 = (): number => {
103
+ return random() + ((random() * 0x200000) | 0) * 1.1102230246251565e-16; // 2^-53
104
+ };
105
+
106
+ random.version = 'Xoshiro128** 1.0';
107
+ random.seed = actual_seed;
108
+
109
+ return random;
110
+ };
111
+
112
+ /**
113
+ * Expands a 32-bit state by one step using the SplitMix32 algorithm,
114
+ * producing the four state words needed by Xoshiro128**.
115
+ */
116
+ const splitmix32_next = (prev_state: number): {value: number; state: number} => {
117
+ const next_state = (prev_state + 0x9e3779b9) | 0;
118
+ let z = next_state;
119
+ z = Math.imul(z ^ (z >>> 16), 0x85ebca6b);
120
+ z = Math.imul(z ^ (z >>> 13), 0xc2b2ae35);
121
+ return {value: (z ^ (z >>> 16)) >>> 0, state: next_state};
122
+ };
123
+
124
+ const rotl = (x: number, k: number): number => (x << k) | (x >>> (32 - k));