@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.
Files changed (156) hide show
  1. package/dist/buffer.d.ts +19 -21
  2. package/dist/buffer.d.ts.map +1 -1
  3. package/dist/buffer.js +18 -19
  4. package/dist/buffer.js.map +1 -1
  5. package/dist/classes/enhanced-date.d.ts +457 -0
  6. package/dist/classes/enhanced-date.d.ts.map +1 -0
  7. package/dist/classes/enhanced-date.js +635 -0
  8. package/dist/classes/enhanced-date.js.map +1 -0
  9. package/dist/classes/path.d.ts +211 -48
  10. package/dist/classes/path.d.ts.map +1 -1
  11. package/dist/classes/path.js +188 -54
  12. package/dist/classes/path.js.map +1 -1
  13. package/dist/classes/precision-number.d.ts +44 -39
  14. package/dist/classes/precision-number.d.ts.map +1 -1
  15. package/dist/classes/precision-number.js +51 -45
  16. package/dist/classes/precision-number.js.map +1 -1
  17. package/dist/classes/ssh-client.d.ts +3 -4
  18. package/dist/classes/ssh-client.d.ts.map +1 -1
  19. package/dist/classes/ssh-client.js +2 -2
  20. package/dist/classes/ssh-client.js.map +1 -1
  21. package/dist/clipboard.d.ts +25 -26
  22. package/dist/clipboard.d.ts.map +1 -1
  23. package/dist/clipboard.js +23 -23
  24. package/dist/clipboard.js.map +1 -1
  25. package/dist/consola.d.ts +12 -15
  26. package/dist/consola.d.ts.map +1 -1
  27. package/dist/consola.js +10 -12
  28. package/dist/consola.js.map +1 -1
  29. package/dist/crypto-hash.d.ts +65 -56
  30. package/dist/crypto-hash.d.ts.map +1 -1
  31. package/dist/crypto-hash.js +55 -45
  32. package/dist/crypto-hash.js.map +1 -1
  33. package/dist/datetime.d.ts +41 -53
  34. package/dist/datetime.d.ts.map +1 -1
  35. package/dist/datetime.js +53 -66
  36. package/dist/datetime.js.map +1 -1
  37. package/dist/element-plus.d.ts +13 -14
  38. package/dist/element-plus.d.ts.map +1 -1
  39. package/dist/element-plus.js +11 -11
  40. package/dist/element-plus.js.map +1 -1
  41. package/dist/enum.d.ts +39 -36
  42. package/dist/enum.d.ts.map +1 -1
  43. package/dist/enum.js +36 -32
  44. package/dist/enum.js.map +1 -1
  45. package/dist/env.d.ts +22 -15
  46. package/dist/env.d.ts.map +1 -1
  47. package/dist/env.js +20 -12
  48. package/dist/env.js.map +1 -1
  49. package/dist/event-awaiter.d.ts +47 -52
  50. package/dist/event-awaiter.d.ts.map +1 -1
  51. package/dist/event-awaiter.js +46 -50
  52. package/dist/event-awaiter.js.map +1 -1
  53. package/dist/general.d.ts +22 -17
  54. package/dist/general.d.ts.map +1 -1
  55. package/dist/general.js.map +1 -1
  56. package/dist/hash.d.ts +24 -21
  57. package/dist/hash.d.ts.map +1 -1
  58. package/dist/hash.js +20 -16
  59. package/dist/hash.js.map +1 -1
  60. package/dist/math.d.ts +20 -19
  61. package/dist/math.d.ts.map +1 -1
  62. package/dist/math.js +14 -12
  63. package/dist/math.js.map +1 -1
  64. package/dist/number.d.ts +10 -9
  65. package/dist/number.d.ts.map +1 -1
  66. package/dist/number.js +9 -7
  67. package/dist/number.js.map +1 -1
  68. package/dist/object.d.ts +22 -10
  69. package/dist/object.d.ts.map +1 -1
  70. package/dist/object.js +21 -8
  71. package/dist/object.js.map +1 -1
  72. package/dist/pino.d.ts +13 -15
  73. package/dist/pino.d.ts.map +1 -1
  74. package/dist/pino.js +10 -15
  75. package/dist/pino.js.map +1 -1
  76. package/dist/random.d.ts +17 -15
  77. package/dist/random.d.ts.map +1 -1
  78. package/dist/random.js +16 -13
  79. package/dist/random.js.map +1 -1
  80. package/dist/redis/script-runner.d.ts +57 -0
  81. package/dist/redis/script-runner.d.ts.map +1 -0
  82. package/dist/redis/script-runner.js +46 -0
  83. package/dist/redis/script-runner.js.map +1 -0
  84. package/dist/storages/lru/keyed-store.d.ts +18 -14
  85. package/dist/storages/lru/keyed-store.d.ts.map +1 -1
  86. package/dist/storages/lru/keyed-store.js +17 -12
  87. package/dist/storages/lru/keyed-store.js.map +1 -1
  88. package/dist/storages/redis/keyed-store.d.ts +11 -21
  89. package/dist/storages/redis/keyed-store.d.ts.map +1 -1
  90. package/dist/storages/redis/keyed-store.js +10 -19
  91. package/dist/storages/redis/keyed-store.js.map +1 -1
  92. package/dist/storages/redis/msgpack.d.ts +33 -13
  93. package/dist/storages/redis/msgpack.d.ts.map +1 -1
  94. package/dist/storages/redis/msgpack.js +32 -11
  95. package/dist/storages/redis/msgpack.js.map +1 -1
  96. package/dist/storages/redis/types.d.ts +34 -17
  97. package/dist/storages/redis/types.d.ts.map +1 -1
  98. package/dist/string.d.ts +12 -11
  99. package/dist/string.d.ts.map +1 -1
  100. package/dist/string.js +20 -15
  101. package/dist/string.js.map +1 -1
  102. package/dist/time.d.ts +26 -21
  103. package/dist/time.d.ts.map +1 -1
  104. package/dist/time.js +24 -18
  105. package/dist/time.js.map +1 -1
  106. package/dist/types/filtered-key-path.d.ts +4 -15
  107. package/dist/types/filtered-key-path.d.ts.map +1 -1
  108. package/dist/types/index.d.ts +10 -10
  109. package/dist/types/index.d.ts.map +1 -1
  110. package/dist/types/vue.d.ts +7 -7
  111. package/dist/types/vue.d.ts.map +1 -1
  112. package/dist/url.d.ts +15 -14
  113. package/dist/url.d.ts.map +1 -1
  114. package/dist/url.js +12 -10
  115. package/dist/url.js.map +1 -1
  116. package/dist/vue.d.ts +19 -21
  117. package/dist/vue.d.ts.map +1 -1
  118. package/dist/vue.js +15 -16
  119. package/dist/vue.js.map +1 -1
  120. package/dist/web.d.ts +14 -11
  121. package/dist/web.d.ts.map +1 -1
  122. package/dist/web.js +12 -8
  123. package/dist/web.js.map +1 -1
  124. package/package.json +21 -21
  125. package/src/buffer.ts +18 -19
  126. package/src/classes/enhanced-date.ts +715 -0
  127. package/src/classes/path.ts +248 -96
  128. package/src/classes/precision-number.ts +61 -53
  129. package/src/classes/ssh-client.ts +8 -5
  130. package/src/clipboard.ts +23 -23
  131. package/src/consola.ts +10 -12
  132. package/src/crypto-hash.ts +55 -45
  133. package/src/datetime.ts +65 -85
  134. package/src/element-plus.ts +11 -11
  135. package/src/enum.ts +36 -32
  136. package/src/env.ts +20 -12
  137. package/src/event-awaiter.ts +49 -50
  138. package/src/general.ts +20 -14
  139. package/src/hash.ts +20 -16
  140. package/src/math.ts +18 -16
  141. package/src/number.ts +9 -7
  142. package/src/object.ts +21 -8
  143. package/src/pino.ts +11 -15
  144. package/src/random.ts +16 -13
  145. package/src/redis/script-runner.ts +107 -0
  146. package/src/storages/lru/keyed-store.ts +17 -12
  147. package/src/storages/redis/keyed-store.ts +10 -19
  148. package/src/storages/redis/msgpack.ts +32 -11
  149. package/src/storages/redis/types.ts +32 -14
  150. package/src/string.ts +10 -8
  151. package/src/time.ts +24 -18
  152. package/src/types/filtered-key-path.ts +9 -14
  153. package/src/types/vue.ts +6 -5
  154. package/src/url.ts +12 -10
  155. package/src/vue.ts +15 -16
  156. 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 {string | Uint8Array} data - The input data to hash
15
+ * @param data - The UTF-8 string or raw bytes to hash.
16
16
  *
17
- * @returns {string} The hexadecimal hash digest
17
+ * @returns The hexadecimal hash digest.
18
18
  *
19
19
  * @example
20
- * ```typescript
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 {string | Uint8Array} data - The input data to hash
34
+ * @param data - The UTF-8 string or raw bytes to hash.
34
35
  *
35
- * @returns {string} The hexadecimal hash digest
36
+ * @returns The hexadecimal hash digest.
36
37
  *
37
38
  * @example
38
- * ```typescript
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 {string | Uint8Array} data - The input data to hash
53
+ * @param data - The UTF-8 string or raw bytes to hash.
52
54
  *
53
- * @returns {string} The hexadecimal hash digest
55
+ * @returns The hexadecimal hash digest.
54
56
  *
55
57
  * @example
56
- * ```typescript
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 {string | Uint8Array} data - The input data to hash
72
+ * @param data - The UTF-8 string or raw bytes to hash.
70
73
  *
71
- * @returns {string} The hexadecimal hash digest
74
+ * @returns The hexadecimal hash digest.
72
75
  *
73
76
  * @example
74
- * ```typescript
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
- * Number of decimal places to include in the result.
10
+ * The number of decimal places to include in the result.
11
11
  *
12
- * @default 2
12
+ * @defaultValue 2
13
13
  */
14
14
  decimalPlaces?: number;
15
15
 
16
16
  /**
17
- * Whether to include the '%' symbol in the result.
17
+ * Whether to include the `%` symbol in the result.
18
18
  *
19
- * @default true
19
+ * @defaultValue true
20
20
  */
21
21
  withSymbol?: boolean;
22
22
  }
23
23
 
24
24
  /**
25
- * Converts a fraction (numerator / denominator) into a percentage string.
25
+ * Converts a numerator and denominator into a formatted percentage.
26
26
  *
27
- * - Uses `decimal.js` for precise decimal calculations.
28
- * - Supports custom decimal places and optional percentage symbol.
29
- * - Returns `'0.00%'` if result is `NaN` or division is invalid.
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 {CalculableValue} molecular - The numerator of the fraction
32
- * @param {CalculableValue} denominator - The denominator of the fraction
33
- * @param {ToPercentageStringOptions} [options] - Optional output settings
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 {string} Formatted percentage string
36
+ * @returns The formatted percentage string.
36
37
  *
37
38
  * @example
38
- * ```typescript
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
- * Applies lowercase units (e.g. 'k', 'm') and default precision of 2, unless overridden.
6
+ * @remarks
7
+ * Uses lowercase units, for example `k` and `m`, and a precision of `2` unless overridden.
7
8
  *
8
- * @param {number} value - The number to format
9
- * @param {Parameters<typeof millify>[1]} [options] - Optional configuration passed to `millify`
9
+ * @param value - The number to format.
10
+ * @param options - Optional configuration passed to `millify`.
10
11
  *
11
- * @returns {string} The compact number string
12
+ * @returns The compact number string.
12
13
  *
13
14
  * @example
14
- * ```typescript
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 {Record<string, any>} input - The object to serialize. Can contain nested objects and arrays
15
- * @param {string} kvSeparator - The string used to separate each key from its value (default: '=')
16
- * @param {string} pairSeparator - The string used to separate each key-value pair (default: '&')
17
- * @returns {string} A deterministic string representation of the input object
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
- * ```typescript
22
+ *
23
+ * ```ts
21
24
  * import { stringifyObjectDeterministically } from '@kikiutils/shared/object';
22
25
  *
23
- * console.log(stringifyObjectDeterministically({ b: 2, a: { x: 1, y: [3, 4] } })); // a.x=1&a.y.0=3&a.y.1=4&b=2
24
- * console.log(stringifyObjectDeterministically({ foo: 'bar' }, ':', '|')); // foo:bar
25
- * console.log(stringifyObjectDeterministically({})); // ''
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 instance with the configured stream.
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
- * See available levels [here](https://getpino.io/#/docs/api?id=level-string).
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
- * logger.info('test'); // [2024-07-11 12:12:30.085] INFO: test
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
- * Generates a value using a provided generator function, where the input length
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
- * 1. First, a random number (`innerMin`) is chosen between `minMin` and `minMax`.
6
- * 2. Then, a final length is chosen between `Math.max(innerMin, maxMin)` and `maxMax`.
7
- * 3. The generator is called with the final length and its result is returned.
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
- * This function supports any return type by using a generic type parameter.
12
+ * @typeParam T - The generator's return value, preserved without awaiting or copying.
10
13
  *
11
- * @template T - The return type of the generator function
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
- * @param {(length: number) => T} generator - A function that accepts a length and returns a value of type T
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
- * @returns {T} The result of the generator function using the computed final length
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 store wrapper around an LRU cache instance.
4
+ * Creates a keyed cache factory sharing a caller-owned LRU cache.
5
5
  *
6
- * @template D - The data type stored in the keyed store
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
- * @param {LRUCache<any, any, any>} lruInstance - The underlying lru-cache instance
12
+ * @typeParam D - The stored value type used by reads and writes.
9
13
  *
10
- * @returns {(keyFn: (...args: P) => string) => Readonly<{
11
- * getItem, getItemTtl, hasItem, removeItem, resolveKey, setItem
12
- * }>} A factory that accepts a key resolver and returns a frozen keyed cache facade
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
- * ```typescript
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 lru = new LRUCache({ max: 100 });
20
- * const keyedStore = createLruKeyedStore(lru)((userId: string) => `user:${userId}`);
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
- * const user = keyedStore.getItem('user-123'); // { name: 'Alice' }
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
- * Return a value from the cache. Will update the recency of the cache entry found.
33
+ * Returns the cached value and applies the underlying cache's read-recency policy.
29
34
  *
30
- * If the key is not found, returns `null`.
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 wrapper around a Redis-like storage.
6
+ * Creates a keyed store factory sharing a caller-owned Redis-like storage facade.
7
7
  *
8
- * @template D - The data type stored in the keyed store
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
- * @param {MaybeReadonly<RedisLikeStorage>} storage - The underlying Redis-like storage instance
14
+ * @typeParam D - The stored value type used by reads and writes.
11
15
  *
12
- * @returns {(keyFn: (...args: P) => string) => Readonly<{
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
- * @example
17
- * ```typescript
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 msgpack serialization.
17
+ * Creates a frozen Redis-like storage facade using MessagePack serialization.
18
18
  *
19
- * @param {RedisLikeAdapter} adapter - The Redis-like adapter to use for raw buffer operations
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
- * @returns {Readonly<RedisLikeStorage>} A readonly storage instance
22
- * with getItem, setItem, hasItem, removeItem, getItemTtl, setItemWithTtl
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
- * ```typescript
30
+ *
31
+ * ```ts
26
32
  * import { createRedisMsgpackStorage } from '@kikiutils/shared/storages/redis/msgpack';
27
- * import { createClient } from 'redis';
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 client = createClient();
30
- * await client.connect();
31
- * const storage = createRedisMsgpackStorage(client as unknown as RedisLikeAdapter);
32
- * await storage.setItem('key', { foo: 'bar' });
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> {