@kikiutils/shared 18.0.1 → 18.2.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.
- package/dist/buffer.d.ts +19 -21
- package/dist/buffer.d.ts.map +1 -1
- package/dist/buffer.js +18 -19
- package/dist/buffer.js.map +1 -1
- package/dist/classes/enhanced-date.d.ts +457 -0
- package/dist/classes/enhanced-date.d.ts.map +1 -0
- package/dist/classes/enhanced-date.js +635 -0
- package/dist/classes/enhanced-date.js.map +1 -0
- package/dist/classes/path.d.ts +211 -48
- package/dist/classes/path.d.ts.map +1 -1
- package/dist/classes/path.js +188 -54
- package/dist/classes/path.js.map +1 -1
- package/dist/classes/precision-number.d.ts +44 -39
- package/dist/classes/precision-number.d.ts.map +1 -1
- package/dist/classes/precision-number.js +51 -45
- package/dist/classes/precision-number.js.map +1 -1
- package/dist/classes/ssh-client.d.ts +3 -4
- package/dist/classes/ssh-client.d.ts.map +1 -1
- package/dist/classes/ssh-client.js +2 -2
- package/dist/classes/ssh-client.js.map +1 -1
- package/dist/clipboard.d.ts +25 -26
- package/dist/clipboard.d.ts.map +1 -1
- package/dist/clipboard.js +23 -23
- package/dist/clipboard.js.map +1 -1
- package/dist/consola.d.ts +12 -15
- package/dist/consola.d.ts.map +1 -1
- package/dist/consola.js +10 -12
- package/dist/consola.js.map +1 -1
- package/dist/crypto-hash.d.ts +65 -56
- package/dist/crypto-hash.d.ts.map +1 -1
- package/dist/crypto-hash.js +55 -45
- package/dist/crypto-hash.js.map +1 -1
- package/dist/datetime.d.ts +41 -53
- package/dist/datetime.d.ts.map +1 -1
- package/dist/datetime.js +53 -66
- package/dist/datetime.js.map +1 -1
- package/dist/element-plus.d.ts +13 -14
- package/dist/element-plus.d.ts.map +1 -1
- package/dist/element-plus.js +11 -11
- package/dist/element-plus.js.map +1 -1
- package/dist/enum.d.ts +39 -36
- package/dist/enum.d.ts.map +1 -1
- package/dist/enum.js +36 -32
- package/dist/enum.js.map +1 -1
- package/dist/env.d.ts +22 -15
- package/dist/env.d.ts.map +1 -1
- package/dist/env.js +20 -12
- package/dist/env.js.map +1 -1
- package/dist/event-awaiter.d.ts +47 -52
- package/dist/event-awaiter.d.ts.map +1 -1
- package/dist/event-awaiter.js +46 -50
- package/dist/event-awaiter.js.map +1 -1
- package/dist/general.d.ts +22 -17
- package/dist/general.d.ts.map +1 -1
- package/dist/general.js.map +1 -1
- package/dist/hash.d.ts +24 -21
- package/dist/hash.d.ts.map +1 -1
- package/dist/hash.js +20 -16
- package/dist/hash.js.map +1 -1
- package/dist/math.d.ts +20 -19
- package/dist/math.d.ts.map +1 -1
- package/dist/math.js +14 -12
- package/dist/math.js.map +1 -1
- package/dist/number.d.ts +10 -9
- package/dist/number.d.ts.map +1 -1
- package/dist/number.js +9 -7
- package/dist/number.js.map +1 -1
- package/dist/object.d.ts +22 -10
- package/dist/object.d.ts.map +1 -1
- package/dist/object.js +21 -8
- package/dist/object.js.map +1 -1
- package/dist/pino.d.ts +13 -15
- package/dist/pino.d.ts.map +1 -1
- package/dist/pino.js +10 -15
- package/dist/pino.js.map +1 -1
- package/dist/random.d.ts +17 -15
- package/dist/random.d.ts.map +1 -1
- package/dist/random.js +16 -13
- package/dist/random.js.map +1 -1
- package/dist/redis/script-runner.d.ts +57 -0
- package/dist/redis/script-runner.d.ts.map +1 -0
- package/dist/redis/script-runner.js +46 -0
- package/dist/redis/script-runner.js.map +1 -0
- package/dist/storages/lru/keyed-store.d.ts +18 -14
- package/dist/storages/lru/keyed-store.d.ts.map +1 -1
- package/dist/storages/lru/keyed-store.js +17 -12
- package/dist/storages/lru/keyed-store.js.map +1 -1
- package/dist/storages/redis/keyed-store.d.ts +11 -21
- package/dist/storages/redis/keyed-store.d.ts.map +1 -1
- package/dist/storages/redis/keyed-store.js +10 -19
- package/dist/storages/redis/keyed-store.js.map +1 -1
- package/dist/storages/redis/msgpack.d.ts +33 -13
- package/dist/storages/redis/msgpack.d.ts.map +1 -1
- package/dist/storages/redis/msgpack.js +32 -11
- package/dist/storages/redis/msgpack.js.map +1 -1
- package/dist/storages/redis/types.d.ts +34 -17
- package/dist/storages/redis/types.d.ts.map +1 -1
- package/dist/string.d.ts +12 -11
- package/dist/string.d.ts.map +1 -1
- package/dist/string.js +20 -15
- package/dist/string.js.map +1 -1
- package/dist/time.d.ts +26 -21
- package/dist/time.d.ts.map +1 -1
- package/dist/time.js +24 -18
- package/dist/time.js.map +1 -1
- package/dist/types/filtered-key-path.d.ts +4 -15
- package/dist/types/filtered-key-path.d.ts.map +1 -1
- package/dist/types/index.d.ts +10 -10
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/vue.d.ts +7 -7
- package/dist/types/vue.d.ts.map +1 -1
- package/dist/url.d.ts +15 -14
- package/dist/url.d.ts.map +1 -1
- package/dist/url.js +12 -10
- package/dist/url.js.map +1 -1
- package/dist/vue.d.ts +19 -21
- package/dist/vue.d.ts.map +1 -1
- package/dist/vue.js +15 -16
- package/dist/vue.js.map +1 -1
- package/dist/web.d.ts +14 -11
- package/dist/web.d.ts.map +1 -1
- package/dist/web.js +12 -8
- package/dist/web.js.map +1 -1
- package/package.json +21 -21
- package/src/buffer.ts +18 -19
- package/src/classes/enhanced-date.ts +715 -0
- package/src/classes/path.ts +248 -96
- package/src/classes/precision-number.ts +61 -53
- package/src/classes/ssh-client.ts +8 -5
- package/src/clipboard.ts +23 -23
- package/src/consola.ts +10 -12
- package/src/crypto-hash.ts +55 -45
- package/src/datetime.ts +65 -85
- package/src/element-plus.ts +11 -11
- package/src/enum.ts +36 -32
- package/src/env.ts +20 -12
- package/src/event-awaiter.ts +49 -50
- package/src/general.ts +20 -14
- package/src/hash.ts +20 -16
- package/src/math.ts +18 -16
- package/src/number.ts +9 -7
- package/src/object.ts +21 -8
- package/src/pino.ts +11 -15
- package/src/random.ts +16 -13
- package/src/redis/script-runner.ts +107 -0
- package/src/storages/lru/keyed-store.ts +17 -12
- package/src/storages/redis/keyed-store.ts +10 -19
- package/src/storages/redis/msgpack.ts +32 -11
- package/src/storages/redis/types.ts +32 -14
- package/src/string.ts +10 -8
- package/src/time.ts +24 -18
- package/src/types/filtered-key-path.ts +9 -14
- package/src/types/vue.ts +6 -5
- package/src/url.ts +12 -10
- package/src/vue.ts +15 -16
- package/src/web.ts +12 -8
package/src/hash.ts
CHANGED
|
@@ -12,15 +12,16 @@ import {
|
|
|
12
12
|
/**
|
|
13
13
|
* Computes the SHA-3 hash of the given data using the 224-bit digest.
|
|
14
14
|
*
|
|
15
|
-
* @param
|
|
15
|
+
* @param data - The UTF-8 string or raw bytes to hash.
|
|
16
16
|
*
|
|
17
|
-
* @returns
|
|
17
|
+
* @returns The hexadecimal hash digest.
|
|
18
18
|
*
|
|
19
19
|
* @example
|
|
20
|
-
*
|
|
20
|
+
*
|
|
21
|
+
* ```ts
|
|
21
22
|
* import { sha3224 } from '@kikiutils/shared/hash';
|
|
22
23
|
*
|
|
23
|
-
* console.log(sha3224('test')); // 3797bf0afbbfca4a7bbba7602a2b552746876517a7f9b7ce2db0ae7b
|
|
24
|
+
* console.log(sha3224('test')); // => 3797bf0afbbfca4a7bbba7602a2b552746876517a7f9b7ce2db0ae7b
|
|
24
25
|
* ```
|
|
25
26
|
*/
|
|
26
27
|
export function sha3224(data: string | Uint8Array) {
|
|
@@ -30,15 +31,16 @@ export function sha3224(data: string | Uint8Array) {
|
|
|
30
31
|
/**
|
|
31
32
|
* Computes the SHA-3 hash of the given data using the 256-bit digest.
|
|
32
33
|
*
|
|
33
|
-
* @param
|
|
34
|
+
* @param data - The UTF-8 string or raw bytes to hash.
|
|
34
35
|
*
|
|
35
|
-
* @returns
|
|
36
|
+
* @returns The hexadecimal hash digest.
|
|
36
37
|
*
|
|
37
38
|
* @example
|
|
38
|
-
*
|
|
39
|
+
*
|
|
40
|
+
* ```ts
|
|
39
41
|
* import { sha3256 } from '@kikiutils/shared/hash';
|
|
40
42
|
*
|
|
41
|
-
* console.log(sha3256('test')); // 36f028580bb02cc8272a9a020f4200e346e276ae664e45ee80745574e2f5ab80
|
|
43
|
+
* console.log(sha3256('test')); // => 36f028580bb02cc8272a9a020f4200e346e276ae664e45ee80745574e2f5ab80
|
|
42
44
|
* ```
|
|
43
45
|
*/
|
|
44
46
|
export function sha3256(data: string | Uint8Array) {
|
|
@@ -48,15 +50,16 @@ export function sha3256(data: string | Uint8Array) {
|
|
|
48
50
|
/**
|
|
49
51
|
* Computes the SHA-3 hash of the given data using the 384-bit digest.
|
|
50
52
|
*
|
|
51
|
-
* @param
|
|
53
|
+
* @param data - The UTF-8 string or raw bytes to hash.
|
|
52
54
|
*
|
|
53
|
-
* @returns
|
|
55
|
+
* @returns The hexadecimal hash digest.
|
|
54
56
|
*
|
|
55
57
|
* @example
|
|
56
|
-
*
|
|
58
|
+
*
|
|
59
|
+
* ```ts
|
|
57
60
|
* import { sha3384 } from '@kikiutils/shared/hash';
|
|
58
61
|
*
|
|
59
|
-
* console.log(sha3384('test')); // e516dabb23b6e30026863543282780a3ae0dccf05551cf0295178d7f...
|
|
62
|
+
* console.log(sha3384('test')); // => e516dabb23b6e30026863543282780a3ae0dccf05551cf0295178d7f...
|
|
60
63
|
* ```
|
|
61
64
|
*/
|
|
62
65
|
export function sha3384(data: string | Uint8Array) {
|
|
@@ -66,15 +69,16 @@ export function sha3384(data: string | Uint8Array) {
|
|
|
66
69
|
/**
|
|
67
70
|
* Computes the SHA-3 hash of the given data using the 512-bit digest.
|
|
68
71
|
*
|
|
69
|
-
* @param
|
|
72
|
+
* @param data - The UTF-8 string or raw bytes to hash.
|
|
70
73
|
*
|
|
71
|
-
* @returns
|
|
74
|
+
* @returns The hexadecimal hash digest.
|
|
72
75
|
*
|
|
73
76
|
* @example
|
|
74
|
-
*
|
|
77
|
+
*
|
|
78
|
+
* ```ts
|
|
75
79
|
* import { sha3512 } from '@kikiutils/shared/hash';
|
|
76
80
|
*
|
|
77
|
-
* console.log(sha3512('test')); // 9ece086e9bac491fac5c1d1046ca11d737b92a2b2ebd93f005d7...
|
|
81
|
+
* console.log(sha3512('test')); // => 9ece086e9bac491fac5c1d1046ca11d737b92a2b2ebd93f005d7...
|
|
78
82
|
* ```
|
|
79
83
|
*/
|
|
80
84
|
export function sha3512(data: string | Uint8Array) {
|
package/src/math.ts
CHANGED
|
@@ -7,40 +7,42 @@ type CalculableValue = Decimal.Value | { toString: () => string };
|
|
|
7
7
|
*/
|
|
8
8
|
export interface ToPercentageStringOptions {
|
|
9
9
|
/**
|
|
10
|
-
*
|
|
10
|
+
* The number of decimal places to include in the result.
|
|
11
11
|
*
|
|
12
|
-
* @
|
|
12
|
+
* @defaultValue 2
|
|
13
13
|
*/
|
|
14
14
|
decimalPlaces?: number;
|
|
15
15
|
|
|
16
16
|
/**
|
|
17
|
-
* Whether to include the
|
|
17
|
+
* Whether to include the `%` symbol in the result.
|
|
18
18
|
*
|
|
19
|
-
* @
|
|
19
|
+
* @defaultValue true
|
|
20
20
|
*/
|
|
21
21
|
withSymbol?: boolean;
|
|
22
22
|
}
|
|
23
23
|
|
|
24
24
|
/**
|
|
25
|
-
* Converts a
|
|
25
|
+
* Converts a numerator and denominator into a formatted percentage.
|
|
26
26
|
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
27
|
+
* @remarks
|
|
28
|
+
* A `NaN` or infinite quotient produces `0.00`, regardless of the configured decimal places;
|
|
29
|
+
* the percent symbol is appended according to `options.withSymbol`.
|
|
30
|
+
* Invalid decimal input and formatting options propagate decimal.js exceptions.
|
|
30
31
|
*
|
|
31
|
-
* @param
|
|
32
|
-
* @param
|
|
33
|
-
* @param
|
|
32
|
+
* @param molecular - The numerator of the fraction.
|
|
33
|
+
* @param denominator - The denominator of the fraction.
|
|
34
|
+
* @param options - Output precision and percent-symbol settings.
|
|
34
35
|
*
|
|
35
|
-
* @returns
|
|
36
|
+
* @returns The formatted percentage string.
|
|
36
37
|
*
|
|
37
38
|
* @example
|
|
38
|
-
*
|
|
39
|
+
*
|
|
40
|
+
* ```ts
|
|
39
41
|
* import { toPercentageString } from '@kikiutils/shared/math';
|
|
40
42
|
*
|
|
41
|
-
* console.log(toPercentageString(50, 200)); // 25.00%
|
|
42
|
-
* console.log(toPercentageString(50, 200, { withSymbol: false })); // 25.00
|
|
43
|
-
* console.log(toPercentageString(50, 200, { decimalPlaces: 1 })); // 25.0%
|
|
43
|
+
* console.log(toPercentageString(50, 200)); // => '25.00%'
|
|
44
|
+
* console.log(toPercentageString(50, 200, { withSymbol: false })); // => '25.00'
|
|
45
|
+
* console.log(toPercentageString(50, 200, { decimalPlaces: 1 })); // => '25.0%'
|
|
44
46
|
* ```
|
|
45
47
|
*/
|
|
46
48
|
export function toPercentageString(
|
package/src/number.ts
CHANGED
|
@@ -3,19 +3,21 @@ import { millify } from 'millify';
|
|
|
3
3
|
/**
|
|
4
4
|
* Converts a large number into a compact, human-readable string using `millify`.
|
|
5
5
|
*
|
|
6
|
-
*
|
|
6
|
+
* @remarks
|
|
7
|
+
* Uses lowercase units, for example `k` and `m`, and a precision of `2` unless overridden.
|
|
7
8
|
*
|
|
8
|
-
* @param
|
|
9
|
-
* @param
|
|
9
|
+
* @param value - The number to format.
|
|
10
|
+
* @param options - Optional configuration passed to `millify`.
|
|
10
11
|
*
|
|
11
|
-
* @returns
|
|
12
|
+
* @returns The compact number string.
|
|
12
13
|
*
|
|
13
14
|
* @example
|
|
14
|
-
*
|
|
15
|
+
*
|
|
16
|
+
* ```ts
|
|
15
17
|
* import { toCompactNumberString } from '@kikiutils/shared/number';
|
|
16
18
|
*
|
|
17
|
-
* console.log(toCompactNumberString(1234567)); // 1.23m
|
|
18
|
-
* console.log(toCompactNumberString(1234567, { precision: 3 })); // 1.235m
|
|
19
|
+
* console.log(toCompactNumberString(1234567)); // => 1.23m
|
|
20
|
+
* console.log(toCompactNumberString(1234567, { precision: 3 })); // => 1.235m
|
|
19
21
|
* ```
|
|
20
22
|
*/
|
|
21
23
|
export function toCompactNumberString(value: number, options?: Parameters<typeof millify>[1]) {
|
package/src/object.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Serializes a nested object into a deterministic, flat string format.
|
|
3
3
|
*
|
|
4
|
+
* @remarks
|
|
4
5
|
* This function recursively traverses the input object (including nested objects and arrays),
|
|
5
6
|
* flattens it into key paths using dot notation (e.g. "a.b.0.c"), sorts all keys,
|
|
6
7
|
* and joins each key-value pair into a string with customizable separators.
|
|
@@ -11,18 +12,30 @@
|
|
|
11
12
|
* Note: Empty objects and arrays produce an empty string, which is useful for
|
|
12
13
|
* generating deterministic signature payloads.
|
|
13
14
|
*
|
|
14
|
-
* @param
|
|
15
|
-
* @param
|
|
16
|
-
* @param
|
|
17
|
-
*
|
|
15
|
+
* @param input - The object to serialize. Can contain nested objects and arrays.
|
|
16
|
+
* @param kvSeparator - The string used to separate each key from its value (default: '=').
|
|
17
|
+
* @param pairSeparator - The string used to separate each key-value pair (default: '&').
|
|
18
|
+
*
|
|
19
|
+
* @returns A deterministic string representation of the input object.
|
|
18
20
|
*
|
|
19
21
|
* @example
|
|
20
|
-
*
|
|
22
|
+
*
|
|
23
|
+
* ```ts
|
|
21
24
|
* import { stringifyObjectDeterministically } from '@kikiutils/shared/object';
|
|
22
25
|
*
|
|
23
|
-
* console.log(stringifyObjectDeterministically({
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
+
* console.log(stringifyObjectDeterministically({
|
|
27
|
+
* b: 2,
|
|
28
|
+
* a: {
|
|
29
|
+
* x: 1,
|
|
30
|
+
* y: [
|
|
31
|
+
* 3,
|
|
32
|
+
* 4,
|
|
33
|
+
* ],
|
|
34
|
+
* },
|
|
35
|
+
* })); // => a.x=1&a.y.0=3&a.y.1=4&b=2
|
|
36
|
+
*
|
|
37
|
+
* console.log(stringifyObjectDeterministically({ foo: 'bar' }, ':', '|')); // => foo:bar
|
|
38
|
+
* console.log(stringifyObjectDeterministically({})); // => ''
|
|
26
39
|
* ```
|
|
27
40
|
*/
|
|
28
41
|
export function stringifyObjectDeterministically(
|
package/src/pino.ts
CHANGED
|
@@ -1,9 +1,7 @@
|
|
|
1
1
|
import { pino } from 'pino';
|
|
2
2
|
import { PinoPretty } from 'pino-pretty';
|
|
3
3
|
|
|
4
|
-
|
|
5
|
-
* Configure pinoPretty to enhance the log output.
|
|
6
|
-
*/
|
|
4
|
+
// Configure pretty-printed log output.
|
|
7
5
|
const stream = PinoPretty({
|
|
8
6
|
colorize: true, // Enable colored output for better readability
|
|
9
7
|
ignore: 'hostname,pid', // Exclude 'hostname' and 'pid' fields from the logs
|
|
@@ -11,25 +9,23 @@ const stream = PinoPretty({
|
|
|
11
9
|
});
|
|
12
10
|
|
|
13
11
|
/**
|
|
14
|
-
* A pino logger
|
|
15
|
-
*
|
|
16
|
-
* The logger's level is determined based on the `PINO_LOGGER_LEVEL` and `NODE_ENV` environment variables.
|
|
17
|
-
* If `PINO_LOGGER_LEVEL` is set, it will be used; otherwise, if `NODE_ENV` is `production`,
|
|
18
|
-
* the level will be set to `error`.
|
|
19
|
-
*
|
|
20
|
-
* To manually change the level, assign the desired level to `logger.level`.
|
|
12
|
+
* A pino logger with environment-controlled verbosity.
|
|
21
13
|
*
|
|
22
|
-
*
|
|
14
|
+
* @remarks
|
|
15
|
+
* `PINO_LOGGER_LEVEL` sets the log level when nonempty at module initialization;
|
|
16
|
+
* otherwise, `NODE_ENV=production` selects `error`, and other environments retain the logger's default level.
|
|
17
|
+
* Assign `logger.level` to change verbosity after initialization.
|
|
23
18
|
*
|
|
24
19
|
* @example
|
|
25
|
-
* ```typescript
|
|
26
|
-
* import { logger } from '@kikiutils/shared/pino';
|
|
27
20
|
*
|
|
28
|
-
*
|
|
21
|
+
* ```ts
|
|
22
|
+
* import { logger } from '@kikiutils/shared/pino';
|
|
29
23
|
*
|
|
30
|
-
* // Manually change the level
|
|
31
24
|
* logger.level = 'info';
|
|
25
|
+
* logger.info('Application started');
|
|
32
26
|
* ```
|
|
27
|
+
*
|
|
28
|
+
* @see {@link https://getpino.io/#/docs/api?id=level-string | Pino log levels}
|
|
33
29
|
*/
|
|
34
30
|
export const pinoLogger = pino({}, stream);
|
|
35
31
|
export const logger = pinoLogger;
|
package/src/random.ts
CHANGED
|
@@ -1,22 +1,25 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
* is determined by two levels of nested random ranges:
|
|
2
|
+
* Calls a generator once with a length sampled from two nested random ranges.
|
|
4
3
|
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
4
|
+
* @remarks
|
|
5
|
+
* Integer bounds are inclusive. First, a lower bound is sampled between `minMin` and `minMax`;
|
|
6
|
+
* the final length is sampled between the larger of that value and `maxMin`, and `maxMax`.
|
|
7
|
+
* Inputs must be finite integers and `minMax` must not exceed `maxMax` for valid nested ranges;
|
|
8
|
+
* these requirements are not checked beyond the two individual bound-order checks.
|
|
9
|
+
* The callback is called synchronously once after sampling. Its return value, including a promise,
|
|
10
|
+
* is returned unchanged, and any synchronous exception propagates.
|
|
8
11
|
*
|
|
9
|
-
*
|
|
12
|
+
* @typeParam T - The generator's return value, preserved without awaiting or copying.
|
|
10
13
|
*
|
|
11
|
-
* @
|
|
14
|
+
* @param generator - The callback that receives the sampled length.
|
|
15
|
+
* @param minMin - The inclusive lower bound for the first sample.
|
|
16
|
+
* @param minMax - The inclusive upper bound for the first sample.
|
|
17
|
+
* @param maxMin - The minimum inclusive lower bound for the final sample.
|
|
18
|
+
* @param maxMax - The inclusive upper bound for the final sample.
|
|
12
19
|
*
|
|
13
|
-
* @
|
|
14
|
-
* @param {number} minMin - Lower bound of the first random range
|
|
15
|
-
* @param {number} minMax - Upper bound of the first random range
|
|
16
|
-
* @param {number} maxMin - Lower bound of the second random range
|
|
17
|
-
* @param {number} maxMax - Upper bound of the second random range
|
|
20
|
+
* @returns The generator's result for the sampled length.
|
|
18
21
|
*
|
|
19
|
-
* @
|
|
22
|
+
* @throws Error if `minMin` exceeds `minMax` or `maxMin` exceeds `maxMax`.
|
|
20
23
|
*/
|
|
21
24
|
export function generateWithNestedRandomLength<T = string>(
|
|
22
25
|
generator: (length: number) => T,
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* An asynchronous Lua script runner with caller-declared results.
|
|
5
|
+
*
|
|
6
|
+
* @remarks
|
|
7
|
+
* Input arrays are unchanged, and numeric arguments are converted to strings before execution.
|
|
8
|
+
* Results are not cached or validated against the declared type.
|
|
9
|
+
*
|
|
10
|
+
* @typeParam T - The expected Lua result type, without runtime validation.
|
|
11
|
+
*
|
|
12
|
+
* @param keys - The script's Redis keys, in `KEYS` order.
|
|
13
|
+
* @param args - The script's arguments, in `ARGV` order.
|
|
14
|
+
*
|
|
15
|
+
* @returns A promise resolving to the Redis script result after execution completes.
|
|
16
|
+
*/
|
|
17
|
+
export type RedisScriptRunner<T = unknown> = (keys: readonly string[], args: readonly (number | string)[]) => Promise<
|
|
18
|
+
T
|
|
19
|
+
>;
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* A caller-owned Redis client exposing raw command execution.
|
|
23
|
+
*
|
|
24
|
+
* @remarks
|
|
25
|
+
* The client manages connections and command routing. `SCRIPT LOAD` and `EVALSHA` must target the same server
|
|
26
|
+
* for script-cache recovery to succeed. Missing-script rejections must be `Error` instances whose messages
|
|
27
|
+
* begin with the Redis error code `NOSCRIPT`, followed by whitespace or the end of the message.
|
|
28
|
+
*/
|
|
29
|
+
export interface RedisLikeScriptClient {
|
|
30
|
+
/**
|
|
31
|
+
* Sends a raw Redis command and resolves to its reply, or rejects on failure.
|
|
32
|
+
*
|
|
33
|
+
* @param command - The Redis command name.
|
|
34
|
+
* @param args - The command arguments in protocol order.
|
|
35
|
+
*/
|
|
36
|
+
send: (command: string, args: string[]) => Promise<unknown>;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Creates a Lua script runner that reloads missing cached scripts before retrying execution.
|
|
41
|
+
*
|
|
42
|
+
* @remarks
|
|
43
|
+
* Calls use `EVALSHA` with the SHA-1 digest of the source. A `NOSCRIPT` rejection loads the script with
|
|
44
|
+
* `SCRIPT LOAD` before another execution attempt, provided the attempt limit has not been reached.
|
|
45
|
+
* Concurrent cache misses on the same runner share an in-flight load; reuse the runner to share that load.
|
|
46
|
+
* Load failures and errors other than `NOSCRIPT` propagate without retries. The final execution error is
|
|
47
|
+
* preserved when the attempt limit is reached. The runner does not cache results or close the client.
|
|
48
|
+
*
|
|
49
|
+
* @typeParam T - The expected Lua result type, without runtime validation.
|
|
50
|
+
*
|
|
51
|
+
* @param client - The caller-owned client used for raw Redis commands.
|
|
52
|
+
* @param source - The Lua source loaded when the script is absent from the server's cache.
|
|
53
|
+
* @param maxAttempts - The maximum `EVALSHA` attempts per invocation, including the initial attempt.
|
|
54
|
+
* Must be a positive safe integer. Defaults to `3`; `1` disables script loading and retries.
|
|
55
|
+
*
|
|
56
|
+
* @returns A reusable runner sharing the client and any in-flight script load.
|
|
57
|
+
*
|
|
58
|
+
* @throws RangeError if `maxAttempts` is not a positive safe integer.
|
|
59
|
+
*/
|
|
60
|
+
export function createRedisScriptRunner<T = unknown>(
|
|
61
|
+
client: RedisLikeScriptClient,
|
|
62
|
+
source: string,
|
|
63
|
+
maxAttempts: number = 3,
|
|
64
|
+
): RedisScriptRunner<T> {
|
|
65
|
+
if (!Number.isSafeInteger(maxAttempts) || maxAttempts < 1) {
|
|
66
|
+
throw new RangeError('maxAttempts must be a positive safe integer');
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const digest = createHash('sha1').update(source).digest('hex');
|
|
70
|
+
let loadingPromise: Promise<unknown> | undefined;
|
|
71
|
+
|
|
72
|
+
return async (keys, args) => {
|
|
73
|
+
for (let attempt = 1; ; attempt += 1) {
|
|
74
|
+
try {
|
|
75
|
+
return await client.send(
|
|
76
|
+
'EVALSHA',
|
|
77
|
+
[
|
|
78
|
+
digest,
|
|
79
|
+
String(keys.length),
|
|
80
|
+
...keys,
|
|
81
|
+
...args.map(String),
|
|
82
|
+
],
|
|
83
|
+
) as T;
|
|
84
|
+
} catch (error) {
|
|
85
|
+
if (
|
|
86
|
+
!(error instanceof Error)
|
|
87
|
+
|| !/^NOSCRIPT(?:\s|$)/.test(error.message)
|
|
88
|
+
|| attempt >= maxAttempts
|
|
89
|
+
) throw error;
|
|
90
|
+
|
|
91
|
+
if (loadingPromise === undefined) {
|
|
92
|
+
loadingPromise = client
|
|
93
|
+
.send(
|
|
94
|
+
'SCRIPT',
|
|
95
|
+
[
|
|
96
|
+
'LOAD',
|
|
97
|
+
source,
|
|
98
|
+
],
|
|
99
|
+
)
|
|
100
|
+
.finally(() => loadingPromise = undefined);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
await loadingPromise;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
};
|
|
107
|
+
}
|
|
@@ -1,33 +1,38 @@
|
|
|
1
1
|
import type { LRUCache } from 'lru-cache';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
* Creates a keyed
|
|
4
|
+
* Creates a keyed cache factory sharing a caller-owned LRU cache.
|
|
5
5
|
*
|
|
6
|
-
* @
|
|
6
|
+
* @remarks
|
|
7
|
+
* The key resolver is called synchronously once per operation, and its exceptions propagate.
|
|
8
|
+
* Reads return the cached value without copying it, or `null` for missing or nullish values.
|
|
9
|
+
* `getItemTtl` exposes the underlying remaining TTL in milliseconds; `setItem` returns the underlying cache.
|
|
10
|
+
* The facade does not dispose the supplied cache, and LRU defaults remain those of the supplied instance.
|
|
7
11
|
*
|
|
8
|
-
* @
|
|
12
|
+
* @typeParam D - The stored value type used by reads and writes.
|
|
9
13
|
*
|
|
10
|
-
* @
|
|
11
|
-
*
|
|
12
|
-
*
|
|
14
|
+
* @param lruInstance - The shared LRU cache instance.
|
|
15
|
+
*
|
|
16
|
+
* @returns A factory accepting a key resolver and returning a new frozen keyed facade sharing the cache.
|
|
13
17
|
*
|
|
14
18
|
* @example
|
|
15
|
-
*
|
|
19
|
+
*
|
|
20
|
+
* ```ts
|
|
16
21
|
* import { createLruKeyedStore } from '@kikiutils/shared/storages/lru/keyed-store';
|
|
17
22
|
* import { LRUCache } from 'lru-cache';
|
|
18
23
|
*
|
|
19
|
-
* const
|
|
20
|
-
* const keyedStore = createLruKeyedStore(
|
|
24
|
+
* const cache = new LRUCache<string, { name: string }>({ max: 100 });
|
|
25
|
+
* const keyedStore = createLruKeyedStore<{ name: string }>(cache)((userId: string) => `user:${userId}`);
|
|
21
26
|
* keyedStore.setItem({ name: 'Alice' }, 'user-123');
|
|
22
|
-
*
|
|
27
|
+
* console.log(keyedStore.getItem('user-123')); // => { name: 'Alice' }
|
|
23
28
|
* ```
|
|
24
29
|
*/
|
|
25
30
|
export function createLruKeyedStore<D = unknown>(lruInstance: LRUCache<any, any, any>) {
|
|
26
31
|
return <P extends any[]>(keyFn: (...args: P) => string) => Object.freeze({
|
|
27
32
|
/**
|
|
28
|
-
*
|
|
33
|
+
* Returns the cached value and applies the underlying cache's read-recency policy.
|
|
29
34
|
*
|
|
30
|
-
*
|
|
35
|
+
* @returns The cached value without copying it, or `null` when absent or nullish.
|
|
31
36
|
*/
|
|
32
37
|
getItem(...args: P) {
|
|
33
38
|
const rawValue = lruInstance.get(keyFn(...args));
|
|
@@ -3,29 +3,20 @@ import type { MaybeReadonly } from '../../types';
|
|
|
3
3
|
import type { RedisLikeStorage } from './types';
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
|
-
* Creates a keyed store
|
|
6
|
+
* Creates a keyed store factory sharing a caller-owned Redis-like storage facade.
|
|
7
7
|
*
|
|
8
|
-
* @
|
|
8
|
+
* @remarks
|
|
9
|
+
* The key resolver is called synchronously once per operation with the operation's key arguments.
|
|
10
|
+
* Resolver exceptions propagate synchronously; asynchronous storage failures retain the underlying rejection.
|
|
11
|
+
* `setItemWithTtl` accepts its TTL in seconds before the value and key arguments.
|
|
12
|
+
* The factory does not open or close the underlying connection; cleanup remains the caller's responsibility.
|
|
9
13
|
*
|
|
10
|
-
* @
|
|
14
|
+
* @typeParam D - The stored value type used by reads and writes.
|
|
11
15
|
*
|
|
12
|
-
* @
|
|
13
|
-
* getItem, getItemTtl, hasItem, removeItem, resolveKey, setItem, setItemWithTtl
|
|
14
|
-
* }>} A factory that accepts a key resolver and returns a frozen Redis keyed-store facade
|
|
16
|
+
* @param storage - The shared Redis-like storage instance.
|
|
15
17
|
*
|
|
16
|
-
* @
|
|
17
|
-
*
|
|
18
|
-
* import { createRedisKeyedStore } from '@kikiutils/shared/storages/redis/keyed-store';
|
|
19
|
-
* import { createRedisMsgpackStorage } from '@kikiutils/shared/storages/redis/msgpack';
|
|
20
|
-
* import { createClient } from 'redis';
|
|
21
|
-
*
|
|
22
|
-
* const client = createClient();
|
|
23
|
-
* await client.connect();
|
|
24
|
-
* const storage = createRedisMsgpackStorage(client as unknown as RedisLikeAdapter);
|
|
25
|
-
* const keyedStore = createRedisKeyedStore(storage)((userId: string) => `user:${userId}`);
|
|
26
|
-
* await keyedStore.setItem({ name: 'Alice' }, 'user-123');
|
|
27
|
-
* const user = await keyedStore.getItem('user-123'); // { name: 'Alice' }
|
|
28
|
-
* ```
|
|
18
|
+
* @returns A factory accepting a key resolver and returning a new frozen keyed facade sharing the storage.
|
|
19
|
+
* Reads, writes, removals, and TTL results retain the underlying storage contract.
|
|
29
20
|
*/
|
|
30
21
|
export function createRedisKeyedStore<D = unknown>(storage: MaybeReadonly<RedisLikeStorage>) {
|
|
31
22
|
return <P extends any[]>(keyFn: (...args: P) => string) => Object.freeze({
|
|
@@ -14,23 +14,44 @@ if (!isNativeAccelerationEnabled) {
|
|
|
14
14
|
}
|
|
15
15
|
|
|
16
16
|
/**
|
|
17
|
-
* Creates a Redis-like storage using
|
|
17
|
+
* Creates a frozen Redis-like storage facade using MessagePack serialization.
|
|
18
18
|
*
|
|
19
|
-
* @
|
|
19
|
+
* @remarks
|
|
20
|
+
* Adapter rejections and MessagePack serialization errors propagate to the caller.
|
|
21
|
+
* Reads resolve to `null` when no raw value exists. Writes resolve to `true` only when the adapter returns `OK`;
|
|
22
|
+
* removal resolves to `true` only when exactly one key is deleted.
|
|
23
|
+
* The adapter remains caller-owned; creating the facade does not open or close a connection.
|
|
20
24
|
*
|
|
21
|
-
* @
|
|
22
|
-
*
|
|
25
|
+
* @param adapter - The caller-owned adapter for raw binary operations.
|
|
26
|
+
*
|
|
27
|
+
* @returns A new frozen facade sharing the supplied adapter.
|
|
23
28
|
*
|
|
24
29
|
* @example
|
|
25
|
-
*
|
|
30
|
+
*
|
|
31
|
+
* ```ts
|
|
26
32
|
* import { createRedisMsgpackStorage } from '@kikiutils/shared/storages/redis/msgpack';
|
|
27
|
-
* import {
|
|
33
|
+
* import type { RedisLikeAdapter } from '@kikiutils/shared/storages/redis/types';
|
|
34
|
+
*
|
|
35
|
+
* // This in-memory adapter demonstrates non-expiring operations without an external service.
|
|
36
|
+
* const values = new Map<string, Uint8Array>();
|
|
37
|
+
* const adapter: RedisLikeAdapter = {
|
|
38
|
+
* delete: async (key) => values.delete(key) ? 1 : 0,
|
|
39
|
+
* getBuffer: async (key) => values.get(key) ?? null,
|
|
40
|
+
* has: async (key) => values.has(key),
|
|
41
|
+
* setBuffer: async (key, value) => {
|
|
42
|
+
* values.set(key, value);
|
|
43
|
+
* return 'OK';
|
|
44
|
+
* },
|
|
45
|
+
* setBufferEx: async () => {
|
|
46
|
+
* throw new Error('TTL writes are not supported by this example adapter');
|
|
47
|
+
* },
|
|
48
|
+
* ttl: async (key) => values.has(key) ? -1 : -2,
|
|
49
|
+
* };
|
|
28
50
|
*
|
|
29
|
-
* const
|
|
30
|
-
* await
|
|
31
|
-
* const
|
|
32
|
-
*
|
|
33
|
-
* const value = await storage.getItem('key'); // { foo: 'bar' }
|
|
51
|
+
* const storage = createRedisMsgpackStorage(adapter);
|
|
52
|
+
* await storage.setItem('profile', { name: 'Alice' });
|
|
53
|
+
* const profile = await storage.getItem<{ name: string }>('profile');
|
|
54
|
+
* console.log(profile); // => { name: 'Alice' }
|
|
34
55
|
* ```
|
|
35
56
|
*/
|
|
36
57
|
export function createRedisMsgpackStorage(adapter: RedisLikeAdapter): Readonly<RedisLikeStorage> {
|