@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.
- package/lib/browsers/getFileFromDataTransfer.js +2 -2
- package/lib/crypto/randomUUIDv7.js +63 -0
- package/lib/crypto/randomUUIDv7.test.js +79 -0
- package/lib/fetch/createFetchWith.js +1 -1
- package/lib/index.js +2 -1
- package/lib/io/dump.js +1 -1
- package/lib/langs/mixin.test.js +0 -3
- package/lib/maths/clamp.js +5 -84
- package/lib/maths/clamp.test.js +33 -35
- package/lib/maths/createRandom.test.js +271 -29
- package/lib/maths/random.js +176 -162
- package/lib/web/getGlobalThis.js +0 -2
- package/lib/web/getRandomValues.js +5 -11
- package/package.json +4 -4
- package/src/crypto/randomUUIDv7.test.ts +82 -0
- package/src/crypto/randomUUIDv7.ts +79 -0
- package/src/index.ts +9 -2
- package/src/langs/mixin.test.ts +0 -3
- package/src/langs/parseBoolean.ts +13 -4
- package/src/maths/clamp.test.ts +12 -9
- package/src/maths/clamp.ts +10 -16
- package/src/maths/createRandom.test.ts +199 -11
- package/src/maths/random.ts +190 -33
- package/src/web/getGlobalThis.ts +1 -2
- package/src/web/getRandomValues.ts +14 -15
- package/tsconfig.json +1 -10
- package/src/schema/README.md +0 -2
package/src/maths/random.ts
CHANGED
|
@@ -1,52 +1,209 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
random(
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
1
|
+
export type RNG = {
|
|
2
|
+
/** 返回 [0,1) 或 [0,a) 或 [a,b) 范围的随机浮点数 */
|
|
3
|
+
random(a?: number, b?: number): number;
|
|
4
|
+
/** 返回 [0, max] 或 [min, max] 范围的随机整数(包含两端) */
|
|
5
|
+
randomInt(max: number): number;
|
|
6
|
+
randomInt(min: number, max: number): number;
|
|
7
|
+
/** 生成随机字节,填充到提供的数组或返回指定长度的新数组 */
|
|
8
|
+
randomBytes(n: number): Uint8Array;
|
|
9
|
+
randomBytes(buf: Uint8Array): Uint8Array;
|
|
10
|
+
/** Fisher-Yates 洗牌算法,原地打乱数组 */
|
|
11
|
+
shuffle<T>(arr: T[]): T[];
|
|
12
|
+
/** 从数组中随机采样 n 个元素(不重复) */
|
|
13
|
+
sample<T>(arr: T[], n: number): T[];
|
|
14
|
+
/** 从数组中随机选择一个元素 */
|
|
15
|
+
pick<T>(arr: T[]): T;
|
|
16
|
+
/** 重置随机数生成器到初始种子或指定种子 */
|
|
17
|
+
reset(seed?: SeedSource): void;
|
|
18
|
+
/** 当前种子值 */
|
|
19
|
+
readonly seed: number;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
type SeedSource = string | number;
|
|
23
|
+
|
|
24
|
+
function resolveSeed(seed: SeedSource = Date.now()) {
|
|
25
|
+
let v = 0;
|
|
8
26
|
|
|
9
|
-
if (typeof
|
|
10
|
-
|
|
11
|
-
|
|
27
|
+
if (typeof seed === 'number') {
|
|
28
|
+
v = seed;
|
|
29
|
+
} else {
|
|
30
|
+
seed = String(seed);
|
|
31
|
+
for (let i = 0; i < seed.length; i++) {
|
|
32
|
+
v = (Math.imul(31, v) + seed.charCodeAt(i)) | 0;
|
|
12
33
|
}
|
|
13
|
-
} else if (typeof _seed === 'number') {
|
|
14
|
-
seed = _seed;
|
|
15
34
|
}
|
|
16
35
|
|
|
17
|
-
//
|
|
18
|
-
|
|
36
|
+
// uint32
|
|
37
|
+
v = v >>> 0;
|
|
19
38
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
39
|
+
return v;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function createRandom(s: SeedSource): RNG {
|
|
43
|
+
// seealso https://github.com/skeeto/rng-js/blob/master/rng.js
|
|
44
|
+
const initialSeed = resolveSeed(s);
|
|
45
|
+
const rng = createXorshift128plus(initialSeed);
|
|
25
46
|
|
|
26
|
-
const random = (
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
47
|
+
const random = (a?: number, b?: number) => {
|
|
48
|
+
const r = rng.next();
|
|
49
|
+
if (a !== undefined && b !== undefined) {
|
|
50
|
+
return r * (b - a) + a;
|
|
51
|
+
}
|
|
52
|
+
if (a !== undefined) {
|
|
53
|
+
return r * a;
|
|
31
54
|
}
|
|
32
55
|
return r;
|
|
33
56
|
};
|
|
34
57
|
|
|
35
|
-
const
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
58
|
+
const randomInt = (min: number, max?: number) => {
|
|
59
|
+
if (max === undefined) {
|
|
60
|
+
max = min;
|
|
61
|
+
min = 0;
|
|
62
|
+
}
|
|
63
|
+
return Math.floor(random(min, max + 1));
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
const shuffle = <T>(arr: T[]): T[] => {
|
|
67
|
+
// Fisher-Yates shuffle
|
|
68
|
+
for (let i = arr.length - 1; i > 0; i--) {
|
|
69
|
+
const j = randomInt(0, i);
|
|
70
|
+
const tmp = arr[i]!;
|
|
71
|
+
arr[i] = arr[j]!;
|
|
72
|
+
arr[j] = tmp;
|
|
73
|
+
}
|
|
74
|
+
return arr;
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
const sample = <T>(arr: T[], n: number): T[] => {
|
|
78
|
+
if (n >= arr.length) {
|
|
79
|
+
return shuffle([...arr]);
|
|
80
|
+
}
|
|
81
|
+
// 使用 partial Fisher-Yates
|
|
82
|
+
const copy = [...arr];
|
|
83
|
+
const result: T[] = [];
|
|
84
|
+
for (let i = 0; i < n; i++) {
|
|
85
|
+
const j = randomInt(i, copy.length - 1);
|
|
86
|
+
const tmp = copy[i]!;
|
|
87
|
+
copy[i] = copy[j]!;
|
|
88
|
+
copy[j] = tmp;
|
|
89
|
+
result.push(copy[i]!);
|
|
39
90
|
}
|
|
40
|
-
return
|
|
91
|
+
return result;
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
const pick = <T>(arr: T[]): T => {
|
|
95
|
+
return arr[randomInt(0, arr.length - 1)]!;
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
const randomBytes = (input: number | Uint8Array): Uint8Array => {
|
|
99
|
+
const buf = typeof input === 'number' ? new Uint8Array(input) : input;
|
|
100
|
+
const len = buf.length;
|
|
101
|
+
// 每次 nextInt32 产生 4 字节
|
|
102
|
+
let i = 0;
|
|
103
|
+
while (i + 4 <= len) {
|
|
104
|
+
const v = rng.nextInt32();
|
|
105
|
+
buf[i] = v & 0xff;
|
|
106
|
+
buf[i + 1] = (v >>> 8) & 0xff;
|
|
107
|
+
buf[i + 2] = (v >>> 16) & 0xff;
|
|
108
|
+
buf[i + 3] = (v >>> 24) & 0xff;
|
|
109
|
+
i += 4;
|
|
110
|
+
}
|
|
111
|
+
// 处理剩余字节
|
|
112
|
+
if (i < len) {
|
|
113
|
+
const v = rng.nextInt32();
|
|
114
|
+
for (let j = 0; i < len; i++, j++) {
|
|
115
|
+
buf[i] = (v >>> (j * 8)) & 0xff;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
return buf;
|
|
41
119
|
};
|
|
42
120
|
|
|
43
121
|
return {
|
|
44
122
|
random,
|
|
123
|
+
randomInt,
|
|
45
124
|
randomBytes,
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
125
|
+
shuffle,
|
|
126
|
+
sample,
|
|
127
|
+
pick,
|
|
128
|
+
reset: (seed?: SeedSource) => rng.reset(seed !== undefined ? resolveSeed(seed) : undefined),
|
|
129
|
+
get seed() {
|
|
130
|
+
return rng.seed;
|
|
131
|
+
},
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
export function resolveRandom(r: RNG | SeedSource = Date.now()): RNG {
|
|
136
|
+
if (typeof r !== 'string' && typeof r !== 'number') {
|
|
137
|
+
return r;
|
|
138
|
+
}
|
|
139
|
+
return createRandom(r);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* xorshift128+ 算法
|
|
144
|
+
* 周期: 2^128 - 1,统计特性优于 LCG
|
|
145
|
+
* 参考: https://prng.di.unimi.it/
|
|
146
|
+
*/
|
|
147
|
+
function createXorshift128plus(seed: number): {
|
|
148
|
+
next: () => number;
|
|
149
|
+
nextFloat: () => number;
|
|
150
|
+
nextDouble: () => number;
|
|
151
|
+
nextInt32: () => number;
|
|
152
|
+
reset: (seed?: number) => void;
|
|
153
|
+
readonly seed: number;
|
|
154
|
+
} {
|
|
155
|
+
const initialSeed = seed;
|
|
156
|
+
// 使用 SplitMix64 风格初始化两个状态
|
|
157
|
+
let s0 = seed >>> 0;
|
|
158
|
+
let s1 = (seed * 1812433253 + 1) >>> 0;
|
|
159
|
+
|
|
160
|
+
// 确保状态非零
|
|
161
|
+
if (s0 === 0) s0 = 0xdeadbeef;
|
|
162
|
+
if (s1 === 0) s1 = 0xcafebabe;
|
|
163
|
+
|
|
164
|
+
const nextInt32 = () => {
|
|
165
|
+
// xorshift128+ 核心算法(32位简化版)
|
|
166
|
+
let x = s0;
|
|
167
|
+
const y = s1;
|
|
168
|
+
|
|
169
|
+
s0 = y;
|
|
170
|
+
x ^= x << 23;
|
|
171
|
+
x ^= x >>> 17;
|
|
172
|
+
x ^= y;
|
|
173
|
+
x ^= y >>> 26;
|
|
174
|
+
s1 = x >>> 0;
|
|
175
|
+
|
|
176
|
+
// 返回 uint32
|
|
177
|
+
return (s0 + s1) >>> 0;
|
|
178
|
+
};
|
|
179
|
+
|
|
180
|
+
// 32 位精度 [0, 1)
|
|
181
|
+
const nextFloat = () => nextInt32() / 0x100000000;
|
|
182
|
+
|
|
183
|
+
// 53 位精度 [0, 1),两次迭代组合
|
|
184
|
+
const nextDouble = () => {
|
|
185
|
+
const hi = nextInt32() >>> 11; // 21 位
|
|
186
|
+
const lo = nextInt32(); // 32 位
|
|
187
|
+
return hi / (1 << 21) + lo / ((1 << 21) * 0x100000000);
|
|
188
|
+
};
|
|
189
|
+
|
|
190
|
+
const next = nextFloat;
|
|
191
|
+
|
|
192
|
+
const reset = (newSeed: number = initialSeed) => {
|
|
193
|
+
s0 = newSeed >>> 0;
|
|
194
|
+
s1 = (newSeed * 1812433253 + 1) >>> 0;
|
|
195
|
+
if (s0 === 0) s0 = 0xdeadbeef;
|
|
196
|
+
if (s1 === 0) s1 = 0xcafebabe;
|
|
197
|
+
};
|
|
198
|
+
|
|
199
|
+
return {
|
|
200
|
+
next,
|
|
201
|
+
nextFloat,
|
|
202
|
+
nextDouble,
|
|
203
|
+
nextInt32,
|
|
204
|
+
reset,
|
|
205
|
+
get seed() {
|
|
206
|
+
return initialSeed;
|
|
50
207
|
},
|
|
51
208
|
};
|
|
52
209
|
}
|
package/src/web/getGlobalThis.ts
CHANGED
|
@@ -14,7 +14,6 @@ export const getGlobalThis = (): typeof globalThis => {
|
|
|
14
14
|
if (typeof globalThis !== 'undefined') return globalThis;
|
|
15
15
|
if (typeof self !== 'undefined') return self;
|
|
16
16
|
if (typeof window !== 'undefined') return window;
|
|
17
|
-
if (typeof global !== 'undefined') return global
|
|
18
|
-
if (typeof this !== 'undefined') return this as any;
|
|
17
|
+
if (typeof global !== 'undefined') return global;
|
|
19
18
|
throw new Error('Unable to locate global `this`');
|
|
20
19
|
};
|
|
@@ -1,35 +1,34 @@
|
|
|
1
|
-
// eslint-disable-next-line @typescript-eslint/consistent-type-imports
|
|
2
1
|
import { getNodeCrypto } from '../crypto/getNodeCrypto';
|
|
3
2
|
import type { TypedArray } from '../io/types';
|
|
4
3
|
import { getGlobalThis } from './getGlobalThis';
|
|
5
4
|
|
|
6
|
-
|
|
5
|
+
type RandomValuesArray = Exclude<TypedArray, Float32Array | Float64Array>;
|
|
6
|
+
|
|
7
|
+
const _globalThis = getGlobalThis();
|
|
7
8
|
|
|
8
9
|
// chrome 11+, safari 5+, nodejs 17.4+
|
|
9
10
|
// https://developer.mozilla.org/en-US/docs/Web/API/Crypto/getRandomValues
|
|
10
|
-
export let getRandomValues: <T extends
|
|
11
|
-
|
|
12
|
-
|| (
|
|
13
|
-
||
|
|
14
|
-
throw new Error('[getRandomValues]: No secure random number generator available.');
|
|
15
|
-
});
|
|
11
|
+
export let getRandomValues: <T extends RandomValuesArray>(typedArray: T) => T =
|
|
12
|
+
_globalThis.crypto?.getRandomValues?.bind(_globalThis.crypto)
|
|
13
|
+
|| (_globalThis as any).msCrypto?.getRandomValues?.bind((_globalThis as any).msCrypto)
|
|
14
|
+
|| _getRandomValues;
|
|
16
15
|
|
|
17
|
-
function _getRandomValues<T extends
|
|
16
|
+
function _getRandomValues<T extends RandomValuesArray>(buf: T): T {
|
|
18
17
|
const nodeCrypto = getNodeCrypto();
|
|
19
|
-
|
|
20
|
-
let wc = nodeCrypto?.webcrypto as any;
|
|
18
|
+
const wc = nodeCrypto?.webcrypto as Crypto | undefined;
|
|
21
19
|
if (wc?.getRandomValues) {
|
|
22
|
-
getRandomValues = wc.getRandomValues
|
|
23
|
-
return
|
|
20
|
+
getRandomValues = wc.getRandomValues.bind(wc);
|
|
21
|
+
return getRandomValues(buf);
|
|
24
22
|
}
|
|
25
23
|
if (nodeCrypto?.randomBytes) {
|
|
26
24
|
if (!(buf instanceof Uint8Array)) {
|
|
27
25
|
throw new TypeError('expected Uint8Array');
|
|
28
26
|
}
|
|
29
27
|
if (buf.length > 65536) {
|
|
30
|
-
const e
|
|
28
|
+
const e = new Error(
|
|
29
|
+
`Failed to execute 'getRandomValues' on 'Crypto': The ArrayBufferView's byte length (${buf.length}) exceeds the number of bytes of entropy available via this API (65536).`,
|
|
30
|
+
) as Error & { code: number; name: string };
|
|
31
31
|
e.code = 22;
|
|
32
|
-
e.message = `Failed to execute 'getRandomValues' on 'Crypto': The ArrayBufferView's byte length (${buf.length}) exceeds the number of bytes of entropy available via this API (65536).`;
|
|
33
32
|
e.name = 'QuotaExceededError';
|
|
34
33
|
throw e;
|
|
35
34
|
}
|
package/tsconfig.json
CHANGED
|
@@ -1,16 +1,8 @@
|
|
|
1
1
|
{
|
|
2
|
+
"extends": ["../../tsconfig.base.json"],
|
|
2
3
|
"compilerOptions": {
|
|
3
|
-
"moduleResolution": "bundler",
|
|
4
|
-
"target": "ESNext",
|
|
5
|
-
"module": "ESNext",
|
|
6
|
-
"lib": ["dom", "dom.iterable", "ESNext"],
|
|
7
|
-
"strict": true,
|
|
8
4
|
"sourceMap": true,
|
|
9
|
-
"skipLibCheck": true,
|
|
10
|
-
"esModuleInterop": true,
|
|
11
|
-
"verbatimModuleSyntax": true,
|
|
12
5
|
"erasableSyntaxOnly": false,
|
|
13
|
-
"allowSyntheticDefaultImports": true,
|
|
14
6
|
"noImplicitAny": true,
|
|
15
7
|
"noImplicitReturns": true,
|
|
16
8
|
"noUnusedLocals": false,
|
|
@@ -18,7 +10,6 @@
|
|
|
18
10
|
"pretty": true,
|
|
19
11
|
"removeComments": false,
|
|
20
12
|
"stripInternal": true,
|
|
21
|
-
"isolatedModules": true,
|
|
22
13
|
"outDir": "lib",
|
|
23
14
|
"baseUrl": ".",
|
|
24
15
|
"rootDir": "./src",
|
package/src/schema/README.md
DELETED