@wener/utils 1.1.54 → 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.
Files changed (76) hide show
  1. package/lib/arrays/arrayFromAsync.js +11 -3
  2. package/lib/asyncs/AsyncInterval.js +11 -3
  3. package/lib/asyncs/Promises.js +6 -5
  4. package/lib/asyncs/createAsyncIterator.js +11 -3
  5. package/lib/asyncs/createLazyPromise.test.js +11 -3
  6. package/lib/asyncs/generatorOfStream.js +11 -3
  7. package/lib/browsers/download.js +11 -3
  8. package/lib/browsers/getFileFromDataTransfer.js +2 -2
  9. package/lib/browsers/loaders.js +11 -3
  10. package/lib/crypto/hashing.js +11 -3
  11. package/lib/crypto/hashing.test.js +11 -3
  12. package/lib/crypto/pem/pem.js +1 -1
  13. package/lib/crypto/randomUUIDv7.js +63 -0
  14. package/lib/crypto/randomUUIDv7.test.js +79 -0
  15. package/lib/fetch/createFetchWith.js +12 -4
  16. package/lib/fetch/dumpRequest.js +12 -4
  17. package/lib/fetch/dumpRequest.test.js +11 -3
  18. package/lib/fetch/dumpResponse.js +11 -3
  19. package/lib/fetch/dumpResponse.test.js +11 -3
  20. package/lib/index.js +2 -1
  21. package/lib/io/ArrayBuffers.js +2 -2
  22. package/lib/io/ByteBuffer.test.js +11 -3
  23. package/lib/io/dump.js +1 -1
  24. package/lib/io/parseDataUri.js +11 -3
  25. package/lib/io/parseDataUri.test.js +31 -11
  26. package/lib/langs/AsyncCloser.js +11 -3
  27. package/lib/langs/deepFreeze.js +5 -5
  28. package/lib/langs/mixin.js +6 -12
  29. package/lib/langs/mixin.test.js +50 -5
  30. package/lib/langs/mixin2.js +26 -0
  31. package/lib/langs/parseBoolean.js +3 -2
  32. package/lib/langs/shallowEqual.js +5 -5
  33. package/lib/libs/ms.js +1 -1
  34. package/lib/maths/clamp.js +5 -84
  35. package/lib/maths/clamp.test.js +33 -35
  36. package/lib/maths/createRandom.test.js +271 -29
  37. package/lib/maths/random.js +176 -154
  38. package/lib/objects/merge/isMergeableObject.js +1 -1
  39. package/lib/objects/merge/merge.js +1 -1
  40. package/lib/objects/merge/merge.test.js +11 -3
  41. package/lib/objects/set.js +10 -2
  42. package/lib/objects/set.test.js +2 -2
  43. package/lib/scripts/getGenerateContext.js +13 -5
  44. package/lib/server/fetch/createFetchWithProxyByNodeFetch.js +11 -3
  45. package/lib/server/fetch/createFetchWithProxyByUndici.js +11 -3
  46. package/lib/server/polyfill/polyfillBrowser.js +11 -3
  47. package/lib/server/polyfill/polyfillBrowser.test.js +11 -3
  48. package/lib/server/polyfill/polyfillCrypto.js +11 -3
  49. package/lib/server/polyfill/polyfillJsDom.js +31 -11
  50. package/lib/strings/renderTemplate.test.js +2 -2
  51. package/lib/web/getGlobalThis.js +0 -2
  52. package/lib/web/getRandomValues.js +5 -11
  53. package/lib/web/structuredClone.js +2 -2
  54. package/package.json +10 -6
  55. package/src/asyncs/Promises.ts +2 -1
  56. package/src/asyncs/timeout.ts +1 -1
  57. package/src/crypto/hashing.ts +7 -6
  58. package/src/crypto/pem/pem.ts +3 -2
  59. package/src/crypto/randomUUIDv7.test.ts +82 -0
  60. package/src/crypto/randomUUIDv7.ts +79 -0
  61. package/src/fetch/dumpRequest.ts +1 -1
  62. package/src/index.ts +9 -2
  63. package/src/langs/mixin.test.ts +32 -5
  64. package/src/langs/mixin.ts +46 -65
  65. package/src/langs/mixin2.ts +80 -0
  66. package/src/langs/parseBoolean.ts +18 -1
  67. package/src/maths/clamp.test.ts +12 -9
  68. package/src/maths/clamp.ts +10 -16
  69. package/src/maths/createRandom.test.ts +199 -11
  70. package/src/maths/random.ts +190 -33
  71. package/src/objects/set.ts +10 -1
  72. package/src/types.d.ts +1 -1
  73. package/src/web/getGlobalThis.ts +1 -2
  74. package/src/web/getRandomValues.ts +14 -15
  75. package/tsconfig.json +6 -12
  76. package/src/schema/README.md +0 -2
@@ -1,52 +1,209 @@
1
- // LCG pseudo random
2
- export function createRandom(_seed: number | string = Date.now()): {
3
- random(n?: number): number;
4
- randomBytes(length: number): Uint8Array;
5
- [Symbol.iterator](): Generator<number, void, unknown>;
6
- } {
7
- let seed = 0;
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 _seed === 'string') {
10
- for (let i = 0; i < _seed.length; i++) {
11
- seed = (Math.imul(31, seed) + _seed.charCodeAt(i)) | 0;
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
- // int32
18
- seed = seed >>> 0;
36
+ // uint32
37
+ v = v >>> 0;
19
38
 
20
- // alternative 梅森旋转算法 (Mersenne Twister), Xorshift
21
- // LCG
22
- const m = 0x80000000; // 2^31
23
- const a = 1664525;
24
- const c = 1013904223;
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 = (n?: number): number => {
27
- seed = (a * seed + c) % m;
28
- let r = seed / m;
29
- if (n) {
30
- r = Math.floor(r * n);
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 randomBytes = (length: number): Uint8Array => {
36
- const bytes = new Uint8Array(length);
37
- for (let i = 0; i < length; i++) {
38
- bytes[i] = Math.floor(random() * 256);
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 bytes;
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
- [Symbol.iterator]: function* () {
47
- while (true) {
48
- yield random();
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
  }
@@ -13,7 +13,9 @@ export function set<T extends object, V>(obj: T, key: ObjectKey | ObjectPath, va
13
13
  let x, k;
14
14
  while (i < len) {
15
15
  k = path[i++];
16
+ // Security: Prevent prototype pollution
16
17
  if (k === '__proto__' || k === 'constructor' || k === 'prototype') break;
18
+
17
19
  // noinspection PointlessArithmeticExpressionJS
18
20
  current = current[k] =
19
21
  i === len
@@ -22,7 +24,14 @@ export function set<T extends object, V>(obj: T, key: ObjectKey | ObjectPath, va
22
24
  : val
23
25
  : typeof (x = current[k]) === typeof path
24
26
  ? x
25
- : // @ts-expect-error hacky type check
27
+ : // Determine if we should create an Object or an Array for the next level
28
+ // If the next key is NOT an integer-like index, or contains a dot, create an Object.
29
+ // Otherwise, create an Array.
30
+ //
31
+ // path[i] * 0 !== 0 checks if it is NOT a number (NaN * 0 is NaN).
32
+ // !!~('' + path[i]).indexOf('.') checks if it contains a dot.
33
+ //
34
+ // @ts-expect-error hacky type check from dset
26
35
  path[i] * 0 !== 0 || !!~('' + path[i]).indexOf('.') // eslint-disable-line
27
36
  ? {}
28
37
  : [];
package/src/types.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  declare var __DEV__: boolean;
2
2
 
3
- namespace NodeJS {
3
+ declare namespace NodeJS {
4
4
  interface Process {
5
5
  // webpack check
6
6
  readonly browser?: boolean;
@@ -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 as any;
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
- const globalThis = getGlobalThis();
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 Exclude<TypedArray, Float32Array | Float64Array>>(typedArray: T) => T =
11
- globalThis.crypto?.getRandomValues?.bind(globalThis.crypto)
12
- || (globalThis as any).msCrypto?.getRandomValues?.bind((globalThis as any).msCrypto)
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 Exclude<TypedArray, Float32Array | Float64Array>>(buf: T) {
16
+ function _getRandomValues<T extends RandomValuesArray>(buf: T): T {
18
17
  const nodeCrypto = getNodeCrypto();
19
- // avoid type error
20
- let wc = nodeCrypto?.webcrypto as any;
18
+ const wc = nodeCrypto?.webcrypto as Crypto | undefined;
21
19
  if (wc?.getRandomValues) {
22
- getRandomValues = wc.getRandomValues?.bind(nodeCrypto?.webcrypto);
23
- return wc.getRandomValues(buf);
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: any = new Error();
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,11 +10,13 @@
18
10
  "pretty": true,
19
11
  "removeComments": false,
20
12
  "stripInternal": true,
21
- "isolatedModules": true,
22
13
  "outDir": "lib",
23
14
  "baseUrl": ".",
24
- "rootDir": "./src"
15
+ "rootDir": "./src",
16
+ "paths": {
17
+ "#/*": ["./src/*"]
18
+ }
25
19
  },
26
- "exclude": ["node_modules"],
20
+ "exclude": ["node_modules", "**/*.test.ts", "**/*.spec.ts"],
27
21
  "include": ["src/types.d.ts", "src"]
28
22
  }
@@ -1,2 +0,0 @@
1
- - 增加了 literal 处理
2
- - 关闭了内置 prettier