@wener/utils 1.1.56 → 1.1.58

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.
@@ -7,32 +7,26 @@ function _instanceof(left, right) {
7
7
  }
8
8
  }
9
9
  var _globalThis_crypto_getRandomValues, _globalThis_crypto, _globalThis_msCrypto_getRandomValues, _globalThis_msCrypto;
10
- // eslint-disable-next-line @typescript-eslint/consistent-type-imports
11
10
  import { getNodeCrypto } from "../crypto/getNodeCrypto.js";
12
11
  import { getGlobalThis } from "./getGlobalThis.js";
13
- var globalThis = getGlobalThis();
12
+ var _globalThis = getGlobalThis();
14
13
  // chrome 11+, safari 5+, nodejs 17.4+
15
14
  // https://developer.mozilla.org/en-US/docs/Web/API/Crypto/getRandomValues
16
- export var getRandomValues = ((_globalThis_crypto = globalThis.crypto) === null || _globalThis_crypto === void 0 ? void 0 : (_globalThis_crypto_getRandomValues = _globalThis_crypto.getRandomValues) === null || _globalThis_crypto_getRandomValues === void 0 ? void 0 : _globalThis_crypto_getRandomValues.bind(globalThis.crypto)) || ((_globalThis_msCrypto = globalThis.msCrypto) === null || _globalThis_msCrypto === void 0 ? void 0 : (_globalThis_msCrypto_getRandomValues = _globalThis_msCrypto.getRandomValues) === null || _globalThis_msCrypto_getRandomValues === void 0 ? void 0 : _globalThis_msCrypto_getRandomValues.bind(globalThis.msCrypto)) || function () {
17
- throw new Error("[getRandomValues]: No secure random number generator available.");
18
- };
15
+ export var getRandomValues = ((_globalThis_crypto = _globalThis.crypto) === null || _globalThis_crypto === void 0 ? void 0 : (_globalThis_crypto_getRandomValues = _globalThis_crypto.getRandomValues) === null || _globalThis_crypto_getRandomValues === void 0 ? void 0 : _globalThis_crypto_getRandomValues.bind(_globalThis.crypto)) || ((_globalThis_msCrypto = _globalThis.msCrypto) === null || _globalThis_msCrypto === void 0 ? void 0 : (_globalThis_msCrypto_getRandomValues = _globalThis_msCrypto.getRandomValues) === null || _globalThis_msCrypto_getRandomValues === void 0 ? void 0 : _globalThis_msCrypto_getRandomValues.bind(_globalThis.msCrypto)) || _getRandomValues;
19
16
  function _getRandomValues(buf) {
20
17
  var nodeCrypto = getNodeCrypto();
21
- // avoid type error
22
18
  var wc = nodeCrypto === null || nodeCrypto === void 0 ? void 0 : nodeCrypto.webcrypto;
23
19
  if (wc === null || wc === void 0 ? void 0 : wc.getRandomValues) {
24
- var _wc_getRandomValues;
25
- getRandomValues = (_wc_getRandomValues = wc.getRandomValues) === null || _wc_getRandomValues === void 0 ? void 0 : _wc_getRandomValues.bind(nodeCrypto === null || nodeCrypto === void 0 ? void 0 : nodeCrypto.webcrypto);
26
- return wc.getRandomValues(buf);
20
+ getRandomValues = wc.getRandomValues.bind(wc);
21
+ return getRandomValues(buf);
27
22
  }
28
23
  if (nodeCrypto === null || nodeCrypto === void 0 ? void 0 : nodeCrypto.randomBytes) {
29
24
  if (!_instanceof(buf, Uint8Array)) {
30
25
  throw new TypeError("expected Uint8Array");
31
26
  }
32
27
  if (buf.length > 65536) {
33
- var e = new Error();
28
+ var e = new Error("Failed to execute 'getRandomValues' on 'Crypto': The ArrayBufferView's byte length (".concat(buf.length, ") exceeds the number of bytes of entropy available via this API (65536)."));
34
29
  e.code = 22;
35
- e.message = "Failed to execute 'getRandomValues' on 'Crypto': The ArrayBufferView's byte length (".concat(buf.length, ") exceeds the number of bytes of entropy available via this API (65536).");
36
30
  e.name = "QuotaExceededError";
37
31
  throw e;
38
32
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wener/utils",
3
- "version": "1.1.56",
3
+ "version": "1.1.58",
4
4
  "type": "module",
5
5
  "description": "Utils for daily use",
6
6
  "repository": {
@@ -69,12 +69,12 @@
69
69
  "lodash"
70
70
  ],
71
71
  "devDependencies": {
72
- "@sinclair/typebox": "^0.34.41",
72
+ "@sinclair/typebox": "^0.34.47",
73
73
  "@types/ws": "^8.18.1",
74
74
  "https-proxy-agent": "^7.0.6",
75
75
  "node-fetch": "^3.3.2",
76
- "undici": "^7.16.0",
77
- "zod": "^4.1.13"
76
+ "undici": "^7.18.2",
77
+ "zod": "^4.3.5"
78
78
  },
79
79
  "publishConfig": {
80
80
  "registry": "https://registry.npmjs.org",
@@ -0,0 +1,82 @@
1
+ import { describe, expect, test } from 'vitest';
2
+ import { createRandomUUIDv7, isUUIDv7, parseUUIDv7Timestamp, randomUUIDv7 } from './randomUUIDv7';
3
+
4
+ describe('randomUUIDv7', () => {
5
+ test('generates valid UUIDv7', () => {
6
+ const uuid = randomUUIDv7();
7
+ expect(uuid).toMatch(/^[0-9a-f]{8}-[0-9a-f]{4}-7[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i);
8
+ });
9
+
10
+ test('generates unique UUIDs', () => {
11
+ const uuids = new Set<string>();
12
+ for (let i = 0; i < 1000; i++) {
13
+ uuids.add(randomUUIDv7());
14
+ }
15
+ expect(uuids.size).toBe(1000);
16
+ });
17
+
18
+ test('respects provided timestamp', () => {
19
+ const ts = 1704067200000; // 2024-01-01 00:00:00 UTC
20
+ const uuid = randomUUIDv7(ts);
21
+ const extractedTs = parseUUIDv7Timestamp(uuid);
22
+ expect(extractedTs).toBe(ts);
23
+ });
24
+
25
+ test('UUIDs are sortable by time', () => {
26
+ const ts1 = 1704067200000;
27
+ const ts2 = 1704067201000;
28
+ const uuid1 = randomUUIDv7(ts1);
29
+ const uuid2 = randomUUIDv7(ts2);
30
+ expect(uuid1 < uuid2).toBe(true);
31
+ });
32
+ });
33
+
34
+ describe('isUUIDv7', () => {
35
+ test('returns true for valid UUIDv7', () => {
36
+ const uuid = randomUUIDv7();
37
+ expect(isUUIDv7(uuid)).toBe(true);
38
+ });
39
+
40
+ test('returns false for UUIDv4', () => {
41
+ expect(isUUIDv7('550e8400-e29b-41d4-a716-446655440000')).toBe(false);
42
+ });
43
+
44
+ test('returns false for invalid strings', () => {
45
+ expect(isUUIDv7('')).toBe(false);
46
+ expect(isUUIDv7(null)).toBe(false);
47
+ expect(isUUIDv7(undefined)).toBe(false);
48
+ expect(isUUIDv7('not-a-uuid')).toBe(false);
49
+ });
50
+ });
51
+
52
+ describe('parseUUIDv7Timestamp', () => {
53
+ test('extracts correct timestamp', () => {
54
+ const ts = Date.now();
55
+ const uuid = randomUUIDv7(ts);
56
+ expect(parseUUIDv7Timestamp(uuid)).toBe(ts);
57
+ });
58
+
59
+ test('throws for invalid UUID format', () => {
60
+ expect(() => parseUUIDv7Timestamp('invalid')).toThrow('Invalid UUID format');
61
+ });
62
+ });
63
+
64
+ describe('createRandomUUIDv7', () => {
65
+ test('creates generator with custom now function', () => {
66
+ let currentTime = 1704067200000;
67
+ const generator = createRandomUUIDv7({ now: () => currentTime });
68
+
69
+ const uuid1 = generator();
70
+ expect(parseUUIDv7Timestamp(uuid1)).toBe(currentTime);
71
+
72
+ currentTime = 1704067201000;
73
+ const uuid2 = generator();
74
+ expect(parseUUIDv7Timestamp(uuid2)).toBe(currentTime);
75
+ });
76
+
77
+ test('generator accepts explicit timestamp override', () => {
78
+ const generator = createRandomUUIDv7({ now: () => 1000 });
79
+ const uuid = generator(2000);
80
+ expect(parseUUIDv7Timestamp(uuid)).toBe(2000);
81
+ });
82
+ });
@@ -1 +1,80 @@
1
1
  // https://github.com/LiosK/uuidv7/blob/main/src/index.ts
2
+ // https://www.rfc-editor.org/rfc/rfc9562.html#name-uuid-version-7
3
+
4
+ import { getRandomValues } from '../web/getRandomValues';
5
+
6
+ /**
7
+ * Generate a UUIDv7 string
8
+ *
9
+ * UUIDv7 format (RFC 9562):
10
+ * - 48 bits: Unix timestamp in milliseconds
11
+ * - 4 bits: version (7)
12
+ * - 12 bits: random (rand_a)
13
+ * - 2 bits: variant (10)
14
+ * - 62 bits: random (rand_b)
15
+ *
16
+ * Format: xxxxxxxx-xxxx-7xxx-yxxx-xxxxxxxxxxxx
17
+ * where y is 8, 9, a, or b (variant bits)
18
+ */
19
+ export function randomUUIDv7(timestamp?: number): string {
20
+ const ts = timestamp ?? Date.now();
21
+ const bytes = new Uint8Array(16);
22
+ getRandomValues(bytes);
23
+
24
+ // timestamp (48 bits)
25
+ bytes[0] = (ts / 2 ** 40) & 0xff;
26
+ bytes[1] = (ts / 2 ** 32) & 0xff;
27
+ bytes[2] = (ts / 2 ** 24) & 0xff;
28
+ bytes[3] = (ts / 2 ** 16) & 0xff;
29
+ bytes[4] = (ts / 2 ** 8) & 0xff;
30
+ bytes[5] = ts & 0xff;
31
+
32
+ // version (4 bits) = 7
33
+ bytes[6] = (bytes[6] & 0x0f) | 0x70;
34
+
35
+ // variant (2 bits) = 10
36
+ bytes[8] = (bytes[8] & 0x3f) | 0x80;
37
+
38
+ return formatUUID(bytes);
39
+ }
40
+
41
+ function formatUUID(bytes: Uint8Array): string {
42
+ const hex = Array.from(bytes)
43
+ .map((b) => b.toString(16).padStart(2, '0'))
44
+ .join('');
45
+ return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
46
+ }
47
+
48
+ /**
49
+ * Extract timestamp from UUIDv7
50
+ */
51
+ export function parseUUIDv7Timestamp(uuid: string): number {
52
+ const hex = uuid.replace(/-/g, '');
53
+ if (hex.length !== 32) {
54
+ throw new Error('Invalid UUID format');
55
+ }
56
+ const tsHex = hex.slice(0, 12);
57
+ return parseInt(tsHex, 16);
58
+ }
59
+
60
+ /**
61
+ * Check if a string is a valid UUIDv7
62
+ */
63
+ export function isUUIDv7(uuid: string | null | undefined): boolean {
64
+ if (!uuid) return false;
65
+ const match = uuid.match(/^[0-9a-f]{8}-[0-9a-f]{4}-7[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i);
66
+ return match !== null;
67
+ }
68
+
69
+ export interface CreateRandomUUIDv7Options {
70
+ now?: () => number;
71
+ }
72
+
73
+ /**
74
+ * Create a UUIDv7 generator with custom options
75
+ */
76
+ export function createRandomUUIDv7({ now = Date.now }: CreateRandomUUIDv7Options = {}) {
77
+ return function uuidv7(timestamp?: number): string {
78
+ return randomUUIDv7(timestamp ?? now());
79
+ };
80
+ }
package/src/index.ts CHANGED
@@ -52,7 +52,7 @@ export { parseDate } from './langs/parseDate';
52
52
  export { shallowClone } from './langs/shallowClone';
53
53
  export { shallowEqual } from './langs/shallowEqual';
54
54
 
55
- export type { MixinFunction, MixinInstance, MixinReturnValue } from './langs/mixin';
55
+ export type { MixinFn } from './langs/mixin';
56
56
 
57
57
  export { AsyncCloser } from './langs/AsyncCloser';
58
58
  export { Closer } from './langs/Closer';
@@ -101,10 +101,17 @@ export { sha1, sha256, sha384, sha512, hmac, type DigestOptions } from './crypto
101
101
  export { md5 } from './crypto/md5';
102
102
  export { hex } from './crypto/base';
103
103
  export { isULID, createULID, ulid, parseULID } from './crypto/ulid';
104
+ export {
105
+ randomUUIDv7,
106
+ isUUIDv7,
107
+ parseUUIDv7Timestamp,
108
+ createRandomUUIDv7,
109
+ type CreateRandomUUIDv7Options,
110
+ } from './crypto/randomUUIDv7';
104
111
  export { PEM } from './crypto/pem/pem';
105
112
 
106
113
  // math
107
- export { createRandom } from './maths/random';
114
+ export { createRandom, resolveRandom, type RNG } from './maths/random';
108
115
  export { clamp } from './maths/clamp';
109
116
 
110
117
  // network
@@ -3,9 +3,6 @@ import { mixin } from './mixin';
3
3
 
4
4
  // import type { Constructor } from '#/types';
5
5
  type Constructor<T = {}> = new (...args: any[]) => T;
6
- function Ent<T extends Function>(): (target: T) => void {
7
- return () => {};
8
- }
9
6
 
10
7
  test('mixin', () => {
11
8
  // @Ent()
@@ -2,13 +2,22 @@ export interface ParseBooleanOptions {
2
2
  strict?: boolean;
3
3
  }
4
4
 
5
- export function parseBoolean(s: string | boolean | number | null | undefined, options: { strict: true }): boolean | undefined;
6
- export function parseBoolean(s: string | boolean | number | null | undefined | any, options?: ParseBooleanOptions): boolean;
5
+ export function parseBoolean(
6
+ s: string | boolean | number | null | undefined,
7
+ options: { strict: true },
8
+ ): boolean | undefined;
9
+ export function parseBoolean(
10
+ s: string | boolean | number | null | undefined | any,
11
+ options?: ParseBooleanOptions,
12
+ ): boolean;
7
13
  /** @deprecated Use `parseBoolean(s, { strict: true })` instead */
8
14
  export function parseBoolean(s: string | boolean | number | null | undefined, strict: true): boolean | undefined;
9
15
  export function parseBoolean(s: string | boolean | number | null | undefined | any): boolean;
10
- export function parseBoolean(s?: string | boolean | number | null, options?: boolean | ParseBooleanOptions): boolean | undefined {
11
- const strict = typeof options === 'boolean' ? options : options?.strict ?? false;
16
+ export function parseBoolean(
17
+ s?: string | boolean | number | null,
18
+ options?: boolean | ParseBooleanOptions,
19
+ ): boolean | undefined {
20
+ const strict = typeof options === 'boolean' ? options : (options?.strict ?? false);
12
21
  if (typeof s === 'boolean') {
13
22
  return s;
14
23
  }
@@ -3,21 +3,24 @@ import { clamp } from './clamp';
3
3
 
4
4
  test('clamp', () => {
5
5
  for (const [a, b] of [
6
+ // 基本用法
6
7
  [[null, 0, 0], 0],
7
8
  [[null, 1, 10], 1],
8
9
  [[undefined, 1, 10], 1],
9
10
  [[undefined, 1, 10, 5], 5],
10
11
  [[2, 1, 10, 5], 2],
11
12
  [[11, 1, 10, 5], 10],
12
- [[11, { min: 1, max: 10 }], 10],
13
- [[null, { min: 1, max: 10 }], 1],
14
- [[null, { min: 1, max: 10, default: 5 }], 5],
15
- [[2n, 1, 10, 5], 2n],
16
- [[1, 2], 2], // min
17
- [[1, undefined, 0], 0], // max
18
- [[], undefined],
19
- [[null], undefined],
20
- ]) {
13
+ [[0, 1, 10, 5], 1],
14
+ // 只限制 min
15
+ [[1, 2, undefined], 2],
16
+ [[5, 2, undefined], 5],
17
+ // 只限制 max
18
+ [[1, undefined, 0], 0],
19
+ [[5, undefined, 10], 5],
20
+ // BigInt
21
+ [[2n, 1n, 10n, 5n], 2n],
22
+ [[11n, 1n, 10n, 5n], 10n],
23
+ ] as const) {
21
24
  expect(clamp.apply(null, a as any), `${a} -> ${b}`).toBe(b);
22
25
  }
23
26
  });
@@ -1,22 +1,16 @@
1
- import { isDefined } from '../langs/isDefined';
2
- import { isNil } from '../langs/isNil';
3
-
4
- // export function clamp<T>(value: T | null | undefined, opts: { min?: T; max?: T; default?: T }): T;
5
- export function clamp<T>(value: T | null | undefined, min: T, max: T, def?: T): T;
6
- export function clamp<T>(value: T | null | undefined, ...o: any[]): T {
7
- let min: T, max: T, def: T;
8
- if (o.length === 1 && o[0] && typeof o[0] === 'object') {
9
- ({ min, max, default: def = min! } = o[0]);
10
- } else {
11
- [min, max, def = min!] = o;
1
+ export function clamp<T>(
2
+ value: T | null | undefined,
3
+ min: T | null | undefined,
4
+ max: T | null | undefined,
5
+ def?: T,
6
+ ): T {
7
+ if (value == null) {
8
+ return def ?? min!;
12
9
  }
13
- if (isNil(value)) {
14
- return def;
15
- }
16
- if (isDefined(min) && value < min) {
10
+ if (min != null && value < min) {
17
11
  return min;
18
12
  }
19
- if (isDefined(max) && value > max) {
13
+ if (max != null && value > max) {
20
14
  return max;
21
15
  }
22
16
  return value;
@@ -1,12 +1,200 @@
1
- import { expect, test } from 'vitest';
2
- import { createRandom } from './random';
3
-
4
- test('createRandom', () => {
5
- let random = createRandom(0);
6
- expect(random.random(100)).toBe(47);
7
- for (const a of random) {
8
- expect(a).toBe(0.557133817113936);
9
- break;
10
- }
11
- expect(random.randomBytes(4)).toEqual(Uint8Array.from([163, 85, 196, 62]));
1
+ import { describe, expect, test } from 'vitest';
2
+ import { createRandom, resolveRandom } from './random';
3
+
4
+ describe('createRandom', () => {
5
+ test('deterministic with same seed', () => {
6
+ const r1 = createRandom(12345);
7
+ const r2 = createRandom(12345);
8
+ expect(r1.random()).toBe(r2.random());
9
+ expect(r1.randomInt(100)).toBe(r2.randomInt(100));
10
+ });
11
+
12
+ test('random() returns [0, 1)', () => {
13
+ const r = createRandom(0);
14
+ for (let i = 0; i < 100; i++) {
15
+ const v = r.random();
16
+ expect(v).toBeGreaterThanOrEqual(0);
17
+ expect(v).toBeLessThan(1);
18
+ }
19
+ });
20
+
21
+ test('random(a) returns [0, a)', () => {
22
+ const r = createRandom(42);
23
+ for (let i = 0; i < 100; i++) {
24
+ const v = r.random(50);
25
+ expect(v).toBeGreaterThanOrEqual(0);
26
+ expect(v).toBeLessThan(50);
27
+ }
28
+ });
29
+
30
+ test('random(a, b) returns [a, b)', () => {
31
+ const r = createRandom(42);
32
+ for (let i = 0; i < 100; i++) {
33
+ const v = r.random(10, 20);
34
+ expect(v).toBeGreaterThanOrEqual(10);
35
+ expect(v).toBeLessThan(20);
36
+ }
37
+ });
38
+
39
+ test('randomInt(max) returns [0, max]', () => {
40
+ const r = createRandom(42);
41
+ const results = new Set<number>();
42
+ for (let i = 0; i < 1000; i++) {
43
+ const v = r.randomInt(5);
44
+ expect(v).toBeGreaterThanOrEqual(0);
45
+ expect(v).toBeLessThanOrEqual(5);
46
+ expect(Number.isInteger(v)).toBe(true);
47
+ results.add(v);
48
+ }
49
+ // should hit all values 0-5
50
+ expect(results.size).toBe(6);
51
+ });
52
+
53
+ test('randomInt(min, max) returns [min, max]', () => {
54
+ const r = createRandom(42);
55
+ const results = new Set<number>();
56
+ for (let i = 0; i < 1000; i++) {
57
+ const v = r.randomInt(10, 15);
58
+ expect(v).toBeGreaterThanOrEqual(10);
59
+ expect(v).toBeLessThanOrEqual(15);
60
+ expect(Number.isInteger(v)).toBe(true);
61
+ results.add(v);
62
+ }
63
+ // should hit all values 10-15
64
+ expect(results.size).toBe(6);
65
+ });
66
+
67
+ test('shuffle', () => {
68
+ const r = createRandom(42);
69
+ const arr = [1, 2, 3, 4, 5];
70
+ const shuffled = r.shuffle([...arr]);
71
+ expect(shuffled).toHaveLength(5);
72
+ expect(shuffled.sort()).toEqual(arr);
73
+ });
74
+
75
+ test('sample', () => {
76
+ const r = createRandom(42);
77
+ const arr = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
78
+ const sampled = r.sample(arr, 3);
79
+ expect(sampled).toHaveLength(3);
80
+ // all sampled items should be from original array
81
+ for (const v of sampled) {
82
+ expect(arr).toContain(v);
83
+ }
84
+ // no duplicates
85
+ expect(new Set(sampled).size).toBe(3);
86
+ });
87
+
88
+ test('sample with n >= arr.length returns shuffled copy', () => {
89
+ const r = createRandom(42);
90
+ const arr = [1, 2, 3];
91
+ const sampled = r.sample(arr, 5);
92
+ expect(sampled).toHaveLength(3);
93
+ expect(sampled.sort()).toEqual(arr);
94
+ });
95
+
96
+ test('pick', () => {
97
+ const r = createRandom(42);
98
+ const arr = [1, 2, 3, 4, 5];
99
+ const picked = r.pick(arr);
100
+ expect(arr).toContain(picked);
101
+ });
102
+
103
+ test('reset restores initial state', () => {
104
+ const r = createRandom(42);
105
+ const first = r.random();
106
+ r.random();
107
+ r.random();
108
+ r.reset();
109
+ expect(r.random()).toBe(first);
110
+ });
111
+
112
+ test('reset with new seed', () => {
113
+ const r = createRandom(42);
114
+ r.reset(100);
115
+ const r2 = createRandom(100);
116
+ expect(r.random()).toBe(r2.random());
117
+ });
118
+
119
+ test('reset with string seed', () => {
120
+ const r = createRandom(42);
121
+ r.reset('hello');
122
+ const r2 = createRandom('hello');
123
+ expect(r.random()).toBe(r2.random());
124
+ });
125
+
126
+ test('seed property', () => {
127
+ const r = createRandom(12345);
128
+ expect(r.seed).toBe(12345);
129
+ });
130
+
131
+ test('string seed', () => {
132
+ const r1 = createRandom('test-seed');
133
+ const r2 = createRandom('test-seed');
134
+ expect(r1.random()).toBe(r2.random());
135
+ });
136
+
137
+ test('randomBytes(n) returns Uint8Array of length n', () => {
138
+ const r = createRandom(42);
139
+ const bytes = r.randomBytes(16);
140
+ expect(bytes).toBeInstanceOf(Uint8Array);
141
+ expect(bytes.length).toBe(16);
142
+ });
143
+
144
+ test('randomBytes fills provided buffer', () => {
145
+ const r = createRandom(42);
146
+ const buf = new Uint8Array(8);
147
+ const result = r.randomBytes(buf);
148
+ expect(result).toBe(buf);
149
+ // should not be all zeros
150
+ expect(buf.some((v) => v !== 0)).toBe(true);
151
+ });
152
+
153
+ test('randomBytes is deterministic', () => {
154
+ const r1 = createRandom(42);
155
+ const r2 = createRandom(42);
156
+ expect(r1.randomBytes(16)).toEqual(r2.randomBytes(16));
157
+ });
158
+
159
+ test('randomBytes handles non-multiple of 4', () => {
160
+ const r = createRandom(42);
161
+ const bytes1 = r.randomBytes(1);
162
+ expect(bytes1.length).toBe(1);
163
+
164
+ const r2 = createRandom(42);
165
+ const bytes5 = r2.randomBytes(5);
166
+ expect(bytes5.length).toBe(5);
167
+
168
+ const r3 = createRandom(42);
169
+ const bytes7 = r3.randomBytes(7);
170
+ expect(bytes7.length).toBe(7);
171
+ });
172
+
173
+ test('randomBytes produces values in range 0-255', () => {
174
+ const r = createRandom(42);
175
+ const bytes = r.randomBytes(1000);
176
+ for (const b of bytes) {
177
+ expect(b).toBeGreaterThanOrEqual(0);
178
+ expect(b).toBeLessThanOrEqual(255);
179
+ }
180
+ });
181
+ });
182
+
183
+ describe('resolveRandom', () => {
184
+ test('returns existing RNG', () => {
185
+ const r = createRandom(42);
186
+ expect(resolveRandom(r)).toBe(r);
187
+ });
188
+
189
+ test('creates RNG from number seed', () => {
190
+ const r = resolveRandom(42);
191
+ const r2 = createRandom(42);
192
+ expect(r.random()).toBe(r2.random());
193
+ });
194
+
195
+ test('creates RNG from string seed', () => {
196
+ const r = resolveRandom('test');
197
+ const r2 = createRandom('test');
198
+ expect(r.random()).toBe(r2.random());
199
+ });
12
200
  });