@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.
- package/lib/arrays/arrayFromAsync.js +11 -3
- package/lib/asyncs/AsyncInterval.js +11 -3
- package/lib/asyncs/Promises.js +6 -5
- package/lib/asyncs/createAsyncIterator.js +11 -3
- package/lib/asyncs/createLazyPromise.test.js +11 -3
- package/lib/asyncs/generatorOfStream.js +11 -3
- package/lib/browsers/download.js +11 -3
- package/lib/browsers/getFileFromDataTransfer.js +2 -2
- package/lib/browsers/loaders.js +11 -3
- package/lib/crypto/hashing.js +11 -3
- package/lib/crypto/hashing.test.js +11 -3
- package/lib/crypto/pem/pem.js +1 -1
- package/lib/crypto/randomUUIDv7.js +63 -0
- package/lib/crypto/randomUUIDv7.test.js +79 -0
- package/lib/fetch/createFetchWith.js +12 -4
- package/lib/fetch/dumpRequest.js +12 -4
- package/lib/fetch/dumpRequest.test.js +11 -3
- package/lib/fetch/dumpResponse.js +11 -3
- package/lib/fetch/dumpResponse.test.js +11 -3
- package/lib/index.js +2 -1
- package/lib/io/ArrayBuffers.js +2 -2
- package/lib/io/ByteBuffer.test.js +11 -3
- package/lib/io/dump.js +1 -1
- package/lib/io/parseDataUri.js +11 -3
- package/lib/io/parseDataUri.test.js +31 -11
- package/lib/langs/AsyncCloser.js +11 -3
- package/lib/langs/deepFreeze.js +5 -5
- package/lib/langs/mixin.js +6 -12
- package/lib/langs/mixin.test.js +50 -5
- package/lib/langs/mixin2.js +26 -0
- package/lib/langs/parseBoolean.js +3 -2
- package/lib/langs/shallowEqual.js +5 -5
- package/lib/libs/ms.js +1 -1
- 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 -154
- package/lib/objects/merge/isMergeableObject.js +1 -1
- package/lib/objects/merge/merge.js +1 -1
- package/lib/objects/merge/merge.test.js +11 -3
- package/lib/objects/set.js +10 -2
- package/lib/objects/set.test.js +2 -2
- package/lib/scripts/getGenerateContext.js +13 -5
- package/lib/server/fetch/createFetchWithProxyByNodeFetch.js +11 -3
- package/lib/server/fetch/createFetchWithProxyByUndici.js +11 -3
- package/lib/server/polyfill/polyfillBrowser.js +11 -3
- package/lib/server/polyfill/polyfillBrowser.test.js +11 -3
- package/lib/server/polyfill/polyfillCrypto.js +11 -3
- package/lib/server/polyfill/polyfillJsDom.js +31 -11
- package/lib/strings/renderTemplate.test.js +2 -2
- package/lib/web/getGlobalThis.js +0 -2
- package/lib/web/getRandomValues.js +5 -11
- package/lib/web/structuredClone.js +2 -2
- package/package.json +10 -6
- package/src/asyncs/Promises.ts +2 -1
- package/src/asyncs/timeout.ts +1 -1
- package/src/crypto/hashing.ts +7 -6
- package/src/crypto/pem/pem.ts +3 -2
- package/src/crypto/randomUUIDv7.test.ts +82 -0
- package/src/crypto/randomUUIDv7.ts +79 -0
- package/src/fetch/dumpRequest.ts +1 -1
- package/src/index.ts +9 -2
- package/src/langs/mixin.test.ts +32 -5
- package/src/langs/mixin.ts +46 -65
- package/src/langs/mixin2.ts +80 -0
- package/src/langs/parseBoolean.ts +18 -1
- 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/objects/set.ts +10 -1
- package/src/types.d.ts +1 -1
- package/src/web/getGlobalThis.ts +1 -2
- package/src/web/getRandomValues.ts +14 -15
- package/tsconfig.json +6 -12
- package/src/schema/README.md +0 -2
|
@@ -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/fetch/dumpRequest.ts
CHANGED
|
@@ -130,7 +130,7 @@ export async function dumpBodyContent({
|
|
|
130
130
|
if (typeof value === 'string') {
|
|
131
131
|
result += `${key}: ${value}\n`;
|
|
132
132
|
} else {
|
|
133
|
-
result += `${key}: [File: ${value.name || 'unknown'}]\n`;
|
|
133
|
+
result += `${key}: [File: ${(value as File).name || 'unknown'}]\n`;
|
|
134
134
|
}
|
|
135
135
|
}
|
|
136
136
|
return result;
|
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 {
|
|
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
|
package/src/langs/mixin.test.ts
CHANGED
|
@@ -1,9 +1,40 @@
|
|
|
1
1
|
import { expect, test } from 'vitest';
|
|
2
2
|
import { mixin } from './mixin';
|
|
3
3
|
|
|
4
|
-
|
|
4
|
+
// import type { Constructor } from '#/types';
|
|
5
|
+
type Constructor<T = {}> = new (...args: any[]) => T;
|
|
5
6
|
|
|
6
7
|
test('mixin', () => {
|
|
8
|
+
// @Ent()
|
|
9
|
+
class User extends mixin(BaseResource, withFooFields) {
|
|
10
|
+
a?: string;
|
|
11
|
+
}
|
|
12
|
+
// @Ent()
|
|
13
|
+
class User2 extends mixin(BaseEnt, withFooFields) {
|
|
14
|
+
x: string = 'x';
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
// type works
|
|
18
|
+
const usr = new User();
|
|
19
|
+
expect(usr.foo, 'foo');
|
|
20
|
+
|
|
21
|
+
const u2 = new User2();
|
|
22
|
+
console.log(u2);
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
// <T extends EntityClass<unknown>>(options?: EntityOptions<T>): (target: T) => void;
|
|
26
|
+
|
|
27
|
+
// @Ent()
|
|
28
|
+
class BaseResource {
|
|
29
|
+
id?: string = '';
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// @Ent()
|
|
33
|
+
class BaseEnt extends BaseResource {
|
|
34
|
+
base: string = 'base';
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
test('mixin deep not working', () => {
|
|
7
38
|
// @ts-ignore
|
|
8
39
|
class User extends mixin(BaseResource, createBarFields()) {}
|
|
9
40
|
|
|
@@ -14,10 +45,6 @@ test('mixin', () => {
|
|
|
14
45
|
expect(usr).toEqual({ foo: 'foo', bar: 'bar', id: '' });
|
|
15
46
|
});
|
|
16
47
|
|
|
17
|
-
class BaseResource {
|
|
18
|
-
id?: string = '';
|
|
19
|
-
}
|
|
20
|
-
|
|
21
48
|
function createBarFields() {
|
|
22
49
|
return <TBase extends Constructor>(Base: TBase) => {
|
|
23
50
|
// nested type not working
|
package/src/langs/mixin.ts
CHANGED
|
@@ -1,79 +1,60 @@
|
|
|
1
|
-
|
|
2
|
-
* Helper type to convert a union to an intersection.
|
|
3
|
-
*
|
|
4
|
-
* @internal
|
|
5
|
-
*/
|
|
6
|
-
type UnionToIntersection<T> = (T extends any ? (x: T) => any : never) extends (x: infer R) => any ? R : never;
|
|
1
|
+
import type { Constructor } from '../types';
|
|
7
2
|
|
|
8
3
|
/**
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
* @typeParam T The type of the instance to create.
|
|
12
|
-
* @example ```ts
|
|
13
|
-
* function Walkable<TBase extends MixinConstructor<Positionable>>(Base: TBase) {
|
|
14
|
-
* ··return class Walkable extends Base {
|
|
15
|
-
* ····public forward() { this.x++; }
|
|
16
|
-
* ····public backward() { this.x--; }
|
|
17
|
-
* ··};
|
|
18
|
-
* }
|
|
19
|
-
* ```
|
|
20
|
-
* @example ```ts
|
|
21
|
-
* function Loggable(Base: MixinConstructor) {
|
|
22
|
-
* ··return class Loggable extends Base {
|
|
23
|
-
* ····public log(message: string) { throw new Error(404); }
|
|
24
|
-
* ··};
|
|
25
|
-
* }
|
|
26
|
-
* ```
|
|
4
|
+
* Mixin function type - takes a base class and returns an extended class
|
|
27
5
|
*/
|
|
28
|
-
export type
|
|
6
|
+
export type MixinFn<TBase extends Constructor = Constructor, TResult extends TBase = TBase> = (Base: TBase) => TResult;
|
|
29
7
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
* @typeParam T The type of the base class.
|
|
34
|
-
* @typeParam R The type of the returned class.
|
|
35
|
-
*/
|
|
36
|
-
export type MixinFunction<T extends MixinConstructor = MixinConstructor, R extends T = T & MixinConstructor> = (
|
|
8
|
+
export function mixin<T extends Constructor>(Base: T): T;
|
|
9
|
+
export function mixin<T extends Constructor, M1 extends MixinFn<T>>(Base: T, m1: M1): ReturnType<M1>;
|
|
10
|
+
export function mixin<T extends Constructor, M1 extends MixinFn<T>, M2 extends MixinFn<ReturnType<M1>>>(
|
|
37
11
|
Base: T,
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
12
|
+
m1: M1,
|
|
13
|
+
m2: M2,
|
|
14
|
+
): ReturnType<M2>;
|
|
15
|
+
export function mixin<
|
|
16
|
+
T extends Constructor,
|
|
17
|
+
M1 extends MixinFn<T>,
|
|
18
|
+
M2 extends MixinFn<ReturnType<M1>>,
|
|
19
|
+
M3 extends MixinFn<ReturnType<M2>>,
|
|
20
|
+
>(Base: T, m1: M1, m2: M2, m3: M3): ReturnType<M3>;
|
|
21
|
+
export function mixin<
|
|
22
|
+
T extends Constructor,
|
|
23
|
+
M1 extends MixinFn<T>,
|
|
24
|
+
M2 extends MixinFn<ReturnType<M1>>,
|
|
25
|
+
M3 extends MixinFn<ReturnType<M2>>,
|
|
26
|
+
M4 extends MixinFn<ReturnType<M3>>,
|
|
27
|
+
>(Base: T, m1: M1, m2: M2, m3: M3, m4: M4): ReturnType<M4>;
|
|
28
|
+
export function mixin<
|
|
29
|
+
T extends Constructor,
|
|
30
|
+
M1 extends MixinFn<T>,
|
|
31
|
+
M2 extends MixinFn<ReturnType<M1>>,
|
|
32
|
+
M3 extends MixinFn<ReturnType<M2>>,
|
|
33
|
+
M4 extends MixinFn<ReturnType<M3>>,
|
|
34
|
+
M5 extends MixinFn<ReturnType<M4>>,
|
|
35
|
+
>(Base: T, m1: M1, m2: M2, m3: M3, m4: M4, m5: M5): ReturnType<M5>;
|
|
36
|
+
export function mixin<
|
|
37
|
+
T extends Constructor,
|
|
38
|
+
M1 extends MixinFn<T>,
|
|
39
|
+
M2 extends MixinFn<ReturnType<M1>>,
|
|
40
|
+
M3 extends MixinFn<ReturnType<M2>>,
|
|
41
|
+
M4 extends MixinFn<ReturnType<M3>>,
|
|
42
|
+
M5 extends MixinFn<ReturnType<M4>>,
|
|
43
|
+
M6 extends MixinFn<ReturnType<M5>>,
|
|
44
|
+
>(Base: T, m1: M1, m2: M2, m3: M3, m4: M4, m5: M5, m6: M6): ReturnType<M6>;
|
|
57
45
|
|
|
58
46
|
/**
|
|
59
|
-
* Applies the given mixins to
|
|
47
|
+
* Applies the given mixins to a common base class.
|
|
60
48
|
*
|
|
61
49
|
* @param Base The base class to apply the mixins to.
|
|
62
|
-
* @param mixins The mixins to apply
|
|
50
|
+
* @param mixins The mixins to apply sequentially.
|
|
63
51
|
* @returns A class constructor with all mixins applied.
|
|
64
52
|
*
|
|
65
|
-
* @
|
|
66
|
-
*
|
|
67
|
-
*
|
|
68
|
-
* @example ```ts
|
|
69
|
-
* class Dog extends mixin(Animal, FourLegged, Carnivore, PackHunting, Barking, Domesticated) {}
|
|
53
|
+
* @example
|
|
54
|
+
* ```ts
|
|
55
|
+
* class Dog extends mixin(Animal, FourLegged, Carnivore) {}
|
|
70
56
|
* ```
|
|
71
57
|
*/
|
|
72
|
-
export function mixin<T extends
|
|
73
|
-
Base
|
|
74
|
-
...mixins: M
|
|
75
|
-
): MixinReturnValue<T, M> {
|
|
76
|
-
return mixins.reduce((mix, applyMixin) => applyMixin(mix), Base) as MixinReturnValue<T, M>;
|
|
58
|
+
export function mixin<T extends Constructor>(Base: T, ...mixins: MixinFn<any>[]): Constructor {
|
|
59
|
+
return mixins.reduce((mix, applyMixin) => applyMixin(mix), Base as Constructor);
|
|
77
60
|
}
|
|
78
|
-
|
|
79
|
-
// https://github.com/1nVitr0/lib-ts-mixin-extended
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Helper type to convert a union to an intersection.
|
|
3
|
+
*
|
|
4
|
+
* @internal
|
|
5
|
+
*/
|
|
6
|
+
type UnionToIntersection<T> = (T extends any ? (x: T) => any : never) extends (x: infer R) => any ? R : never;
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Constructor function, that creates a new instance of the given type.
|
|
10
|
+
*
|
|
11
|
+
* @typeParam T The type of the instance to create.
|
|
12
|
+
* @example ```ts
|
|
13
|
+
* function Walkable<TBase extends MixinConstructor<Positionable>>(Base: TBase) {
|
|
14
|
+
* ··return class Walkable extends Base {
|
|
15
|
+
* ····public forward() { this.x++; }
|
|
16
|
+
* ····public backward() { this.x--; }
|
|
17
|
+
* ··};
|
|
18
|
+
* }
|
|
19
|
+
* ```
|
|
20
|
+
* @example ```ts
|
|
21
|
+
* function Loggable(Base: MixinConstructor) {
|
|
22
|
+
* ··return class Loggable extends Base {
|
|
23
|
+
* ····public log(message: string) { throw new Error(404); }
|
|
24
|
+
* ··};
|
|
25
|
+
* }
|
|
26
|
+
* ```
|
|
27
|
+
*/
|
|
28
|
+
export type MixinConstructor<T = {}> = new (...args: any[]) => T;
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Function that applies a mixin to a given base class.
|
|
32
|
+
*
|
|
33
|
+
* @typeParam T The type of the base class.
|
|
34
|
+
* @typeParam R The type of the returned class.
|
|
35
|
+
*/
|
|
36
|
+
export type MixinFunction<T extends MixinConstructor = MixinConstructor, R extends T = T & MixinConstructor> = (
|
|
37
|
+
Base: T,
|
|
38
|
+
) => R;
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* The return type of the mixin function.
|
|
42
|
+
*
|
|
43
|
+
* @typeParam T The type of the base class.
|
|
44
|
+
* @typeParam M The type of the mixin functions.
|
|
45
|
+
*/
|
|
46
|
+
export type MixinReturnValue<T extends MixinConstructor, M extends MixinFunction<T, any>[]> = UnionToIntersection<
|
|
47
|
+
T | { [K in keyof M]: M[K] extends MixinFunction<any, infer U> ? U : never }[number]
|
|
48
|
+
>;
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* The instance created by a mixin function.
|
|
52
|
+
*
|
|
53
|
+
* @typeParam F The type of the mixin function.
|
|
54
|
+
*/
|
|
55
|
+
export type MixinInstance<F extends MixinFunction<any>> =
|
|
56
|
+
F extends MixinFunction<MixinConstructor<any>, infer R> ? InstanceType<R> : never;
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Applies the given mixins to the a common base class.
|
|
60
|
+
*
|
|
61
|
+
* @param Base The base class to apply the mixins to.
|
|
62
|
+
* @param mixins The mixins to apply. All mixins must extend a common base class or an empty class.
|
|
63
|
+
* @returns A class constructor with all mixins applied.
|
|
64
|
+
*
|
|
65
|
+
* @typeParam T The type of the base class.
|
|
66
|
+
* @typeParam M The type of the mixin functions.
|
|
67
|
+
*
|
|
68
|
+
* @example ```ts
|
|
69
|
+
* class Dog extends mixin(Animal, FourLegged, Carnivore, PackHunting, Barking, Domesticated) {}
|
|
70
|
+
* ```
|
|
71
|
+
*/
|
|
72
|
+
export function mixin<T extends MixinConstructor, M extends MixinFunction<T, any>[]>(
|
|
73
|
+
Base: T,
|
|
74
|
+
...mixins: M
|
|
75
|
+
): MixinReturnValue<T, M> {
|
|
76
|
+
return mixins.reduce((mix, applyMixin) => applyMixin(mix), Base) as MixinReturnValue<T, M>;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// this is complex, may cause ts unable to resolve prototype typing
|
|
80
|
+
// https://github.com/1nVitr0/lib-ts-mixin-extended
|
|
@@ -1,6 +1,23 @@
|
|
|
1
|
+
export interface ParseBooleanOptions {
|
|
2
|
+
strict?: boolean;
|
|
3
|
+
}
|
|
4
|
+
|
|
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;
|
|
13
|
+
/** @deprecated Use `parseBoolean(s, { strict: true })` instead */
|
|
1
14
|
export function parseBoolean(s: string | boolean | number | null | undefined, strict: true): boolean | undefined;
|
|
2
15
|
export function parseBoolean(s: string | boolean | number | null | undefined | any): boolean;
|
|
3
|
-
export function parseBoolean(
|
|
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);
|
|
4
21
|
if (typeof s === 'boolean') {
|
|
5
22
|
return s;
|
|
6
23
|
}
|
package/src/maths/clamp.test.ts
CHANGED
|
@@ -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
|
-
[[
|
|
13
|
-
|
|
14
|
-
[[
|
|
15
|
-
[[
|
|
16
|
-
|
|
17
|
-
[[1, undefined, 0], 0],
|
|
18
|
-
[[
|
|
19
|
-
|
|
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
|
});
|
package/src/maths/clamp.ts
CHANGED
|
@@ -1,22 +1,16 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
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 (
|
|
14
|
-
return def;
|
|
15
|
-
}
|
|
16
|
-
if (isDefined(min) && value < min) {
|
|
10
|
+
if (min != null && value < min) {
|
|
17
11
|
return min;
|
|
18
12
|
}
|
|
19
|
-
if (
|
|
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
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
expect(
|
|
9
|
-
|
|
10
|
-
}
|
|
11
|
-
|
|
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
|
});
|