@kikiutils/shared 20.0.1 → 20.1.1

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 (78) hide show
  1. package/dist/buffer.js.map +1 -1
  2. package/dist/classes/enhanced-date.d.ts +2 -2
  3. package/dist/classes/enhanced-date.d.ts.map +1 -1
  4. package/dist/classes/enhanced-date.js.map +1 -1
  5. package/dist/classes/path.d.ts +1 -1
  6. package/dist/classes/path.d.ts.map +1 -1
  7. package/dist/classes/path.js +30 -30
  8. package/dist/classes/path.js.map +1 -1
  9. package/dist/enum.d.ts +4 -3
  10. package/dist/enum.d.ts.map +1 -1
  11. package/dist/enum.js +3 -2
  12. package/dist/enum.js.map +1 -1
  13. package/dist/event-awaiter.d.ts.map +1 -1
  14. package/dist/event-awaiter.js +7 -6
  15. package/dist/event-awaiter.js.map +1 -1
  16. package/dist/general.d.ts +3 -2
  17. package/dist/general.d.ts.map +1 -1
  18. package/dist/general.js.map +1 -1
  19. package/dist/logger/_internals/formatter.d.ts +12 -4
  20. package/dist/logger/_internals/formatter.d.ts.map +1 -1
  21. package/dist/logger/_internals/formatter.js +12 -4
  22. package/dist/logger/_internals/formatter.js.map +1 -1
  23. package/dist/logger/_internals/logger.d.ts +6 -2
  24. package/dist/logger/_internals/logger.d.ts.map +1 -1
  25. package/dist/logger/_internals/logger.js +6 -3
  26. package/dist/logger/_internals/logger.js.map +1 -1
  27. package/dist/logger/node/_internals/file-writer.d.ts +3 -1
  28. package/dist/logger/node/_internals/file-writer.d.ts.map +1 -1
  29. package/dist/logger/node/_internals/file-writer.js +13 -9
  30. package/dist/logger/node/_internals/file-writer.js.map +1 -1
  31. package/dist/logger/node/_internals/formatter.d.ts +3 -1
  32. package/dist/logger/node/_internals/formatter.d.ts.map +1 -1
  33. package/dist/logger/node/_internals/formatter.js +3 -1
  34. package/dist/logger/node/_internals/formatter.js.map +1 -1
  35. package/dist/logger/node/index.js +11 -9
  36. package/dist/logger/node/index.js.map +1 -1
  37. package/dist/logger/node/types.d.ts +36 -12
  38. package/dist/logger/node/types.d.ts.map +1 -1
  39. package/dist/logger/types.d.ts +44 -19
  40. package/dist/logger/types.d.ts.map +1 -1
  41. package/dist/redis/script-runner.d.ts +1 -1
  42. package/dist/redis/script-runner.d.ts.map +1 -1
  43. package/dist/redis/script-runner.js.map +1 -1
  44. package/dist/storages/redis/keyed-store.d.ts.map +1 -1
  45. package/dist/storages/redis/keyed-store.js +10 -10
  46. package/dist/storages/redis/keyed-store.js.map +1 -1
  47. package/dist/storages/redis/msgpack.js +2 -2
  48. package/dist/storages/redis/msgpack.js.map +1 -1
  49. package/dist/time.d.ts.map +1 -1
  50. package/dist/time.js +4 -4
  51. package/dist/time.js.map +1 -1
  52. package/dist/types/filtered-key-path.d.ts +2 -2
  53. package/dist/types/filtered-key-path.d.ts.map +1 -1
  54. package/dist/types/index.d.ts +7 -0
  55. package/dist/types/index.d.ts.map +1 -1
  56. package/dist/url.d.ts.map +1 -1
  57. package/dist/url.js.map +1 -1
  58. package/package.json +15 -11
  59. package/src/buffer.ts +0 -1
  60. package/src/classes/enhanced-date.ts +2 -2
  61. package/src/classes/path.ts +31 -31
  62. package/src/enum.ts +11 -5
  63. package/src/event-awaiter.ts +9 -7
  64. package/src/general.ts +5 -3
  65. package/src/logger/_internals/formatter.ts +12 -4
  66. package/src/logger/_internals/logger.ts +13 -4
  67. package/src/logger/node/_internals/file-writer.ts +13 -9
  68. package/src/logger/node/_internals/formatter.ts +3 -1
  69. package/src/logger/node/index.ts +13 -10
  70. package/src/logger/node/types.ts +36 -12
  71. package/src/logger/types.ts +45 -19
  72. package/src/redis/script-runner.ts +1 -1
  73. package/src/storages/redis/keyed-store.ts +10 -10
  74. package/src/storages/redis/msgpack.ts +2 -2
  75. package/src/time.ts +4 -4
  76. package/src/types/filtered-key-path.ts +2 -2
  77. package/src/types/index.ts +6 -0
  78. package/src/url.ts +0 -1
package/src/enum.ts CHANGED
@@ -1,3 +1,9 @@
1
+ import {
2
+ objectKeys,
3
+ objectValues,
4
+ } from 'ts-extras';
5
+ import type { ValueOf } from 'type-fest';
6
+
1
7
  /**
2
8
  * Extracts only the numeric values from an enumeration-like object.
3
9
  *
@@ -22,7 +28,7 @@
22
28
  * console.log(getEnumNumberValues(RecordType)); // => [0, 1]
23
29
  * ```
24
30
  */
25
- export function getEnumNumberValues<T extends Record<string, any>>(enumObject: T): Extract<T[keyof T], number>[] {
31
+ export function getEnumNumberValues<T extends Record<string, any>>(enumObject: T): Array<Extract<ValueOf<T>, number>> {
26
32
  return getEnumValues(enumObject).filter((value) => typeof value === 'number');
27
33
  }
28
34
 
@@ -50,7 +56,7 @@ export function getEnumNumberValues<T extends Record<string, any>>(enumObject: T
50
56
  * console.log(getEnumStringValues(RecordType)); // => ['unknown']
51
57
  * ```
52
58
  */
53
- export function getEnumStringValues<T extends Record<string, any>>(enumObject: T): Extract<T[keyof T], string>[] {
59
+ export function getEnumStringValues<T extends Record<string, any>>(enumObject: T): Array<Extract<ValueOf<T>, string>> {
54
60
  return getEnumValues(enumObject).filter((value) => typeof value === 'string');
55
61
  }
56
62
 
@@ -102,9 +108,9 @@ export function getEnumStringValues<T extends Record<string, any>>(enumObject: T
102
108
  * console.log(getEnumValues(RecordType)); // => [0, 1, 'unknown']
103
109
  * ```
104
110
  */
105
- export function getEnumValues<T extends Record<string, any>>(enumObject: T): (T[keyof T])[] {
106
- const values = Object.values(enumObject);
111
+ export function getEnumValues<T extends Record<string, any>>(enumObject: T): Array<ValueOf<T>> {
112
+ const values = objectValues(enumObject);
107
113
  const hasNumberValues = values.some((value) => typeof value === 'number');
108
114
  if (!hasNumberValues) return values;
109
- return Object.keys(enumObject).filter((key) => Number.isNaN(Number(key))).map((key) => enumObject[key]);
115
+ return objectKeys(enumObject).filter((key) => Number.isNaN(Number(key))).map((key) => enumObject[key]);
110
116
  }
@@ -11,7 +11,7 @@
11
11
  */
12
12
  export class EventAwaiter<T> {
13
13
  // Private instance properties
14
- #promiseResolvers = new Map<string, ((value: PromiseLike<T | undefined> | T | undefined) => void)[]>();
14
+ #promiseResolvers = new Map<string, Array<(value: PromiseLike<T | undefined> | T | undefined) => void>>();
15
15
 
16
16
  // Public instance methods
17
17
 
@@ -58,8 +58,8 @@ export class EventAwaiter<T> {
58
58
  *
59
59
  * @throws Error through promise rejection if `strict` mode encounters an existing waiter.
60
60
  */
61
- wait(key: string, timeoutMs?: number, mode?: 'override' | 'strict', signal?: AbortSignal) {
62
- return new Promise<T | undefined>((resolve) => {
61
+ async wait(key: string, timeoutMs?: number, mode?: 'override' | 'strict', signal?: AbortSignal) {
62
+ return await new Promise<T | undefined>((resolve) => {
63
63
  const resolvers = this.#promiseResolvers.get(key) || [];
64
64
  if (resolvers.length) {
65
65
  switch (mode) {
@@ -69,6 +69,8 @@ export class EventAwaiter<T> {
69
69
  break;
70
70
  case 'strict':
71
71
  throw new Error(`Duplicate wait detected for key: ${key}`);
72
+ case undefined:
73
+ break;
72
74
  }
73
75
  }
74
76
 
@@ -120,8 +122,8 @@ export class EventAwaiter<T> {
120
122
  *
121
123
  * @see {@link EventAwaiter.wait}
122
124
  */
123
- waitExclusive(key: string, timeoutMs?: number, signal?: AbortSignal) {
124
- return this.wait(key, timeoutMs, 'strict', signal);
125
+ async waitExclusive(key: string, timeoutMs?: number, signal?: AbortSignal) {
126
+ return await this.wait(key, timeoutMs, 'strict', signal);
125
127
  }
126
128
 
127
129
  /**
@@ -138,7 +140,7 @@ export class EventAwaiter<T> {
138
140
  *
139
141
  * @see {@link EventAwaiter.wait}
140
142
  */
141
- waitLatest(key: string, timeoutMs?: number, signal?: AbortSignal) {
142
- return this.wait(key, timeoutMs, 'override', signal);
143
+ async waitLatest(key: string, timeoutMs?: number, signal?: AbortSignal) {
144
+ return await this.wait(key, timeoutMs, 'override', signal);
143
145
  }
144
146
  }
package/src/general.ts CHANGED
@@ -1,3 +1,5 @@
1
+ import type { Arrayable } from 'type-fest';
2
+
1
3
  /* eslint-disable jsdoc/check-param-names */
2
4
 
3
5
  /**
@@ -31,9 +33,9 @@
31
33
  * console.log(extractFirstValue(undefined, 'fallback')); // => 'fallback'
32
34
  * ```
33
35
  */
34
- export function extractFirstValue<T>(value: T | T[]): T | undefined;
35
- export function extractFirstValue<T, D>(value: T | T[], defaultValue: D): D | NonNullable<T>;
36
- export function extractFirstValue<T, D>(value: T | T[], defaultValue?: D) {
36
+ export function extractFirstValue<T>(value: Arrayable<T>): T | undefined;
37
+ export function extractFirstValue<T, D>(value: Arrayable<T>, defaultValue: D): D | NonNullable<T>;
38
+ export function extractFirstValue<T, D>(value: Arrayable<T>, defaultValue?: D) {
37
39
  return (Array.isArray(value) ? value[0] : value) ?? defaultValue;
38
40
  }
39
41
 
@@ -6,7 +6,9 @@ import type { LoggerRecord } from './types';
6
6
 
7
7
  // Functions
8
8
 
9
- /** Native console formatting keeps user strings literal and objects expandable. */
9
+ /**
10
+ * Native console formatting keeps user strings literal and objects expandable.
11
+ */
10
12
  export function formatLoggerConsoleArgs(record: LoggerRecord): unknown[] {
11
13
  if (typeof process !== 'undefined' && process.versions?.node) {
12
14
  const target = process[getLoggerStreamName(record.type)];
@@ -39,7 +41,9 @@ export function formatLoggerConsoleArgs(record: LoggerRecord): unknown[] {
39
41
  ];
40
42
  }
41
43
 
42
- /** Keeps data arguments intact so browser developer tools can expand objects. */
44
+ /**
45
+ * Keeps data arguments intact so browser developer tools can expand objects.
46
+ */
43
47
  export function formatLoggerParts(record: LoggerRecord, colors = false): unknown[] {
44
48
  const parts: unknown[] = [];
45
49
  const paint = (text: string, color: string) => colors ? `\u001B[${color}m${text}\u001B[0m` : text;
@@ -51,7 +55,9 @@ export function formatLoggerParts(record: LoggerRecord, colors = false): unknown
51
55
  return parts;
52
56
  }
53
57
 
54
- /** Fixed local-time representation, deliberately without a per-second cache. */
58
+ /**
59
+ * Fixed local-time representation, deliberately without a per-second cache.
60
+ */
55
61
  export function formatLoggerTime(date: Date): string {
56
62
  const pad = (value: number, width = 2) => String(value).padStart(width, '0');
57
63
  const day = `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())}`;
@@ -59,7 +65,9 @@ export function formatLoggerTime(date: Date): string {
59
65
  return `[${day} ${time}.${pad(date.getMilliseconds(), 3)}]`;
60
66
  }
61
67
 
62
- /** Use native terminal detection; redirected streams still honor FORCE_COLOR. */
68
+ /**
69
+ * Use native terminal detection; redirected streams still honor FORCE_COLOR.
70
+ */
63
71
  export function supportsLoggerColor(target: { hasColors?: () => boolean; isTTY?: boolean }): boolean {
64
72
  if (target.hasColors) return target.hasColors();
65
73
  const forced = process.env.FORCE_COLOR;
@@ -1,3 +1,8 @@
1
+ import {
2
+ objectHasOwn,
3
+ objectKeys,
4
+ } from 'ts-extras';
5
+
1
6
  import type {
2
7
  Logger,
3
8
  LoggerDiagnostic,
@@ -10,7 +15,9 @@ import { loggerMethods } from './levels';
10
15
  import type { LoggerRecord } from './types';
11
16
 
12
17
  interface LoggerHooks {
13
- /** A non-throwing reporter created by createDiagnosticReporter. */
18
+ /**
19
+ * A non-throwing reporter created by createDiagnosticReporter.
20
+ */
14
21
  report: (event: LoggerDiagnostic) => void;
15
22
  write: (record: LoggerRecord) => Promise<void> | void;
16
23
  }
@@ -29,7 +36,9 @@ const stringPresentationKeys = [
29
36
 
30
37
  // Functions
31
38
 
32
- /** A non-recursive, rate-limited diagnostic boundary shared by server and browser adapters. */
39
+ /**
40
+ * A non-recursive, rate-limited diagnostic boundary shared by server and browser adapters.
41
+ */
33
42
  export function createDiagnosticReporter(
34
43
  onError?: (event: Readonly<LoggerDiagnostic>) => void,
35
44
  fallback?: (message: string) => void,
@@ -168,7 +177,7 @@ export function createLoggerCore(options: LoggerOptions, hooks: LoggerHooks): Re
168
177
  },
169
178
  } as Logger;
170
179
 
171
- for (const type of Object.keys(loggerMethods) as LoggerMethod[]) {
180
+ for (const type of objectKeys(loggerMethods) as LoggerMethod[]) {
172
181
  logger[type] = async (...args: unknown[]) => {
173
182
  try {
174
183
  refreshLevel();
@@ -198,7 +207,7 @@ function getLevelSeverity(level: LoggerLevel | LoggerMethod): number {
198
207
 
199
208
  function isLevel(value: unknown): value is LoggerLevel {
200
209
  return typeof value === 'string'
201
- && (value === 'silent' || (value !== 'success' && Object.hasOwn(loggerMethods, value)));
210
+ && (value === 'silent' || (value !== 'success' && objectHasOwn(loggerMethods, value)));
202
211
  }
203
212
 
204
213
  function readEnvironmentLevel(): string | undefined {
@@ -26,7 +26,9 @@ const defaultMaxPendingBytes = 8 * 1024 * 1024;
26
26
 
27
27
  // Functions
28
28
 
29
- /** A bounded admission layer over the stream's own ordered write buffer; not a second FIFO. */
29
+ /**
30
+ * A bounded admission layer over the stream's own ordered write buffer; not a second FIFO.
31
+ */
30
32
  export async function createFileWriter(
31
33
  options: LoggerFileOptions,
32
34
  report: (event: LoggerDiagnostic) => void,
@@ -139,8 +141,8 @@ export async function createFileWriter(
139
141
  }
140
142
 
141
143
  return Object.freeze({
142
- close() {
143
- if (closePromise) return closePromise;
144
+ async close() {
145
+ if (closePromise) return await closePromise;
144
146
  closing = true;
145
147
  closePromise = new Promise<void>((resolveClosed) => {
146
148
  if (closed) {
@@ -156,21 +158,21 @@ export async function createFileWriter(
156
158
  }
157
159
  });
158
160
 
159
- return closePromise;
161
+ return await closePromise;
160
162
  },
161
163
  getStats: () => Object.freeze({
162
164
  droppedCount,
163
165
  pendingBytes,
164
166
  writeErrorCount,
165
167
  }),
166
- write(text: string) {
168
+ async write(text: string) {
167
169
  if (closing || closed || failed) {
168
170
  report({
169
171
  message: 'File output is closed or unavailable.',
170
172
  type: 'closed',
171
173
  });
172
174
 
173
- return Promise.resolve();
175
+ return await Promise.resolve();
174
176
  }
175
177
 
176
178
  const bytes = Buffer.byteLength(text);
@@ -182,11 +184,11 @@ export async function createFileWriter(
182
184
  type: 'overflow',
183
185
  });
184
186
 
185
- return Promise.resolve();
187
+ return await Promise.resolve();
186
188
  }
187
189
 
188
190
  pendingBytes += bytes;
189
- return new Promise<void>((resolveWritten) => {
191
+ return await new Promise<void>((resolveWritten) => {
190
192
  let settled = false;
191
193
  const settle = () => {
192
194
  if (settled) return;
@@ -213,7 +215,9 @@ export async function createFileWriter(
213
215
  });
214
216
  }
215
217
 
216
- /** Validate before mkdir, stream creation, or other filesystem side effects. */
218
+ /**
219
+ * Validate before mkdir, stream creation, or other filesystem side effects.
220
+ */
217
221
  function validateFileOptions(options: LoggerFileOptions) {
218
222
  if (
219
223
  !options
@@ -5,7 +5,9 @@ import type { LoggerRecord } from '../../_internals/types';
5
5
 
6
6
  // Functions
7
7
 
8
- /** Preserve user ANSI for console output without invoking custom inspect callbacks/getters. */
8
+ /**
9
+ * Preserve user ANSI for console output without invoking custom inspect callbacks/getters.
10
+ */
9
11
  export function formatNodeLoggerRecord(record: LoggerRecord, colors = false) {
10
12
  const message = record.args.map(
11
13
  (arg) => typeof arg === 'string'
@@ -47,7 +47,7 @@ export async function createNodeLogger(options: NodeLoggerOptions = {}): Promise
47
47
  options,
48
48
  {
49
49
  report,
50
- write(record) {
50
+ async write(record) {
51
51
  if (closed) {
52
52
  report({
53
53
  message: 'Logger is closed.',
@@ -69,14 +69,14 @@ export async function createNodeLogger(options: NodeLoggerOptions = {}): Promise
69
69
  void completion.then(() => consoleWrites.delete(completion));
70
70
  const fileCompletion = writer?.write(`${stripVTControlCharacters(text)}\n`);
71
71
  return fileCompletion
72
- ? Promise.all([
72
+ ? await Promise.all([
73
73
  completion,
74
74
  fileCompletion,
75
75
  ]).then(() => {})
76
- : completion;
76
+ : await completion;
77
77
  }
78
78
 
79
- return writer?.write(`${stripVTControlCharacters(text)}\n`);
79
+ return await writer?.write(`${stripVTControlCharacters(text)}\n`);
80
80
  },
81
81
  },
82
82
  );
@@ -85,8 +85,8 @@ export async function createNodeLogger(options: NodeLoggerOptions = {}): Promise
85
85
 
86
86
  return Object.freeze({
87
87
  ...logger,
88
- close() {
89
- if (closePromise) return closePromise;
88
+ async close() {
89
+ if (closePromise) return await closePromise;
90
90
  closed = true;
91
91
  closePromise = (async () => {
92
92
  await Promise.all([
@@ -107,7 +107,8 @@ export async function createNodeLogger(options: NodeLoggerOptions = {}): Promise
107
107
 
108
108
  consoleTargets.clear();
109
109
  })();
110
- return closePromise;
110
+
111
+ return await closePromise;
111
112
  },
112
113
  getStats: () => writer?.getStats() ?? Object.freeze({
113
114
  droppedCount: 0,
@@ -117,8 +118,10 @@ export async function createNodeLogger(options: NodeLoggerOptions = {}): Promise
117
118
  });
118
119
  }
119
120
 
120
- /** One error listener per shared output stream; retained until its last owning logger calls close(). */
121
- function writeConsole(
121
+ /**
122
+ * One error listener per shared output stream; retained until its last owning logger calls close().
123
+ */
124
+ async function writeConsole(
122
125
  target: NodeJS.WriteStream,
123
126
  text: string,
124
127
  report: (event: LoggerDiagnostic) => void,
@@ -146,7 +149,7 @@ function writeConsole(
146
149
 
147
150
  state.owners.add(owner);
148
151
  const current = state;
149
- return new Promise<void>((resolveWritten) => {
152
+ return await new Promise<void>((resolveWritten) => {
150
153
  let settled = false;
151
154
  const settle = (error?: Error | null) => {
152
155
  if (settled) return;
@@ -4,50 +4,74 @@ import type {
4
4
  LoggerOptions,
5
5
  } from '../types';
6
6
 
7
- /** Specify interval and/or maxSize. Size limits use positive integers with B, K, M, or G (binary units). */
7
+ /**
8
+ * Specify interval and/or maxSize. Size limits use positive integers with B, K, M, or G (binary units).
9
+ */
8
10
  export type LoggerRotationOptions = {
9
- /** Gzip historical files. Defaults to true. */
11
+ /**
12
+ * Gzip historical files. Defaults to true.
13
+ */
10
14
  compress?: boolean;
11
15
 
12
- /** Number of historical files, excluding the active file. Defaults to 30. */
16
+ /**
17
+ * Number of historical files, excluding the active file. Defaults to 30.
18
+ */
13
19
  keepFiles?: number;
14
20
 
15
- /** Maximum combined size of historical files. */
21
+ /**
22
+ * Maximum combined size of historical files.
23
+ */
16
24
  maxTotalSize?: string;
17
25
  } & (
18
26
  | {
19
- /** Opt-in time rotation aligned to the system's local calendar boundary. */
27
+ /**
28
+ * Opt-in time rotation aligned to the system's local calendar boundary.
29
+ */
20
30
  interval: 'daily' | 'hourly';
21
31
  maxSize?: string;
22
32
  }
23
33
  | {
24
- /** Opt-in time rotation aligned to the system's local calendar boundary. */
34
+ /**
35
+ * Opt-in time rotation aligned to the system's local calendar boundary.
36
+ */
25
37
  interval?: 'daily' | 'hourly';
26
38
  maxSize: string;
27
39
  }
28
40
  );
29
41
 
30
42
  export interface LoggerFileOptions {
31
- /** Maximum unfinished payload bytes, including data already submitted to the stream. */
43
+ /**
44
+ * Maximum unfinished payload bytes, including data already submitted to the stream.
45
+ */
32
46
  maxPendingBytes?: number;
33
47
 
34
- /** Resolved once at creation. The file is opened in append mode. */
48
+ /**
49
+ * Resolved once at creation. The file is opened in append mode.
50
+ */
35
51
  path: PathLike;
36
52
 
37
- /** Omitted or false disables rotation. An object must specify interval or maxSize. */
53
+ /**
54
+ * Omitted or false disables rotation. An object must specify interval or maxSize.
55
+ */
38
56
  rotate?: false | LoggerRotationOptions;
39
57
  }
40
58
 
41
59
  export interface NodeLogger extends Logger {
42
- /** Stop accepting records, finish accepted file writes, then release the stream. Not fsync. */
60
+ /**
61
+ * Stop accepting records, finish accepted file writes, then release the stream. Not fsync.
62
+ */
43
63
  close: () => Promise<void>;
44
64
 
45
- /** A fresh, frozen file-output snapshot. Dropped/failed records still resolve their logging promises. */
65
+ /**
66
+ * A fresh, frozen file-output snapshot. Dropped/failed records still resolve their logging promises.
67
+ */
46
68
  getStats: () => Readonly<NodeLoggerStats>;
47
69
  }
48
70
 
49
71
  export interface NodeLoggerOptions extends LoggerOptions {
50
- /** Disabled by default. Each logger owns at most one fixed file destination. */
72
+ /**
73
+ * Disabled by default. Each logger owns at most one fixed file destination.
74
+ */
51
75
  file?: false | LoggerFileOptions;
52
76
  }
53
77
 
@@ -1,7 +1,13 @@
1
- /** Minimum severity to emit; `success` uses the same threshold as `info`. */
1
+ import type { SafePromise } from '../types';
2
+
3
+ /**
4
+ * Minimum severity to emit; `success` uses the same threshold as `info`.
5
+ */
2
6
  export type LoggerLevel = 'debug' | 'error' | 'fatal' | 'info' | 'silent' | 'trace' | 'warn';
3
7
 
4
- /** A logging method, including the informational success presentation. */
8
+ /**
9
+ * A logging method, including the informational success presentation.
10
+ */
5
11
  export type LoggerMethod = 'success' | Exclude<LoggerLevel, 'silent'>;
6
12
 
7
13
  /**
@@ -13,48 +19,68 @@ export type LoggerMethod = 'success' | Exclude<LoggerLevel, 'silent'>;
13
19
  */
14
20
  export interface Logger {
15
21
  configure: (options: Pick<LoggerOptions, 'console' | 'icons' | 'prefix' | 'suffix' | 'timestamp'>) => void;
16
- debug: (...args: unknown[]) => Promise<void>;
17
- error: (...args: unknown[]) => Promise<void>;
18
- fatal: (...args: unknown[]) => Promise<void>;
19
- info: (...args: unknown[]) => Promise<void>;
22
+ debug: (...args: unknown[]) => SafePromise<void>;
23
+ error: (...args: unknown[]) => SafePromise<void>;
24
+ fatal: (...args: unknown[]) => SafePromise<void>;
25
+ info: (...args: unknown[]) => SafePromise<void>;
20
26
 
21
- /** Accepts success as an informational method; it is not a configurable threshold. */
27
+ /**
28
+ * Accepts success as an informational method; it is not a configurable threshold.
29
+ */
22
30
  isLevelEnabled: (level: LoggerLevel | LoggerMethod) => boolean;
23
31
 
24
- /** Sets a severity threshold (not success); env restores environment-driven filtering. */
32
+ /**
33
+ * Sets a severity threshold (not success); env restores environment-driven filtering.
34
+ */
25
35
  setLevel: (level: 'env' | LoggerLevel) => void;
26
- success: (...args: unknown[]) => Promise<void>;
27
- trace: (...args: unknown[]) => Promise<void>;
28
- warn: (...args: unknown[]) => Promise<void>;
36
+ success: (...args: unknown[]) => SafePromise<void>;
37
+ trace: (...args: unknown[]) => SafePromise<void>;
38
+ warn: (...args: unknown[]) => SafePromise<void>;
29
39
  }
30
40
 
31
- /** A shallow-frozen failure notification; nested error values retain their original ownership. */
41
+ /**
42
+ * A shallow-frozen failure notification; nested error values retain their original ownership.
43
+ */
32
44
  export interface LoggerDiagnostic {
33
45
  droppedCount?: number;
34
46
  error?: unknown;
35
47
  message: string;
36
48
 
37
- /** Same-type notifications suppressed since the previous emitted diagnostic. */
49
+ /**
50
+ * Same-type notifications suppressed since the previous emitted diagnostic.
51
+ */
38
52
  suppressedCount?: number;
39
53
  type: 'closed' | 'console' | 'file' | 'format' | 'invalid-level' | 'overflow';
40
54
  }
41
55
 
42
- /** Configuration shared by browser and server loggers. */
56
+ /**
57
+ * Configuration shared by browser and server loggers.
58
+ */
43
59
  export interface LoggerOptions {
44
- /** Enables console output. Defaults to `true`. */
60
+ /**
61
+ * Enables console output. Defaults to `true`.
62
+ */
45
63
  console?: boolean;
46
64
 
47
- /** Enables Unicode level icons. Defaults to `true`. */
65
+ /**
66
+ * Enables Unicode level icons. Defaults to `true`.
67
+ */
48
68
  icons?: boolean;
49
69
 
50
- /** Overrides LOG_LEVEL; `env` restores the environment-driven threshold. */
70
+ /**
71
+ * Overrides LOG_LEVEL; `env` restores the environment-driven threshold.
72
+ */
51
73
  level?: 'env' | LoggerLevel;
52
74
 
53
- /** Best-effort, rate-limited notification; failures in this callback are contained. */
75
+ /**
76
+ * Best-effort, rate-limited notification; failures in this callback are contained.
77
+ */
54
78
  onError?: (event: Readonly<LoggerDiagnostic>) => void;
55
79
  prefix?: string;
56
80
  suffix?: string;
57
81
 
58
- /** Enables the fixed local-time millisecond prefix. Defaults to `true`. */
82
+ /**
83
+ * Enables the fixed local-time millisecond prefix. Defaults to `true`.
84
+ */
59
85
  timestamp?: boolean;
60
86
  }
@@ -14,7 +14,7 @@ import { createHash } from 'node:crypto';
14
14
  *
15
15
  * @returns A promise resolving to the Redis script result after execution completes.
16
16
  */
17
- export type RedisScriptRunner<T = unknown> = (keys: readonly string[], args: readonly (number | string)[]) => Promise<
17
+ export type RedisScriptRunner<T = unknown> = (keys: readonly string[], args: ReadonlyArray<number | string>) => Promise<
18
18
  T
19
19
  >;
20
20
 
@@ -20,18 +20,18 @@ import type { RedisLikeStorage } from './types';
20
20
  */
21
21
  export function createRedisKeyedStore<D = unknown>(storage: MaybeReadonly<RedisLikeStorage>) {
22
22
  return <P extends any[]>(keyFn: (...args: P) => string) => Object.freeze({
23
- getAndRemoveItem: (...args: P) => storage.getAndRemoveItem<D>(keyFn(...args)),
24
- getItem: (...args: P) => storage.getItem<D>(keyFn(...args)),
25
- getItemTtl: (...args: P) => storage.getItemTtl(keyFn(...args)),
26
- hasItem: (...args: P) => storage.hasItem(keyFn(...args)),
27
- removeItem: (...args: P) => storage.removeItem(keyFn(...args)),
23
+ getAndRemoveItem: async (...args: P) => await storage.getAndRemoveItem<D>(keyFn(...args)),
24
+ getItem: async (...args: P) => await storage.getItem<D>(keyFn(...args)),
25
+ getItemTtl: async (...args: P) => await storage.getItemTtl(keyFn(...args)),
26
+ hasItem: async (...args: P) => await storage.hasItem(keyFn(...args)),
27
+ removeItem: async (...args: P) => await storage.removeItem(keyFn(...args)),
28
28
  resolveKey: (...args: P) => keyFn(...args),
29
- setItem: (value: D, ...args: P) => storage.setItem(keyFn(...args), value),
30
- setItemWithTtl(ttlSeconds: number, value: D, ...args: P) {
31
- return storage.setItemWithTtl(keyFn(...args), ttlSeconds, value);
29
+ setItem: async (value: D, ...args: P) => await storage.setItem(keyFn(...args), value),
30
+ async setItemWithTtl(ttlSeconds: number, value: D, ...args: P) {
31
+ return await storage.setItemWithTtl(keyFn(...args), ttlSeconds, value);
32
32
  },
33
- setItemWithTtlIfAbsent(ttlSeconds: number, value: D, ...args: P) {
34
- return storage.setItemWithTtlIfAbsent(keyFn(...args), ttlSeconds, value);
33
+ async setItemWithTtlIfAbsent(ttlSeconds: number, value: D, ...args: P) {
34
+ return await storage.setItemWithTtlIfAbsent(keyFn(...args), ttlSeconds, value);
35
35
  },
36
36
  });
37
37
  }
@@ -73,8 +73,8 @@ export function createRedisMsgpackStorage(adapter: RedisLikeAdapter): Readonly<R
73
73
  const rawValue = await adapter.getBuffer(key);
74
74
  return rawValue ? decode(rawValue) as T : null;
75
75
  },
76
- getItemTtl: (key: string) => adapter.ttl(key),
77
- hasItem: (key: string) => adapter.has(key),
76
+ getItemTtl: async (key: string) => await adapter.ttl(key),
77
+ hasItem: async (key: string) => await adapter.has(key),
78
78
  removeItem: async (key: string) => await adapter.delete(key) === 1,
79
79
  setItem: async (key: string, value: any) => await adapter.setBuffer(key, encode(value)) === 'OK',
80
80
  async setItemWithTtl(key: string, ttlSeconds: number, value: any) {
package/src/time.ts CHANGED
@@ -18,8 +18,8 @@
18
18
  * await delay(5000, controller.signal);
19
19
  * ```
20
20
  */
21
- export function delay(ms: number, signal?: AbortSignal): Promise<void> {
22
- return new Promise((resolve) => {
21
+ export async function delay(ms: number, signal?: AbortSignal): Promise<void> {
22
+ return await new Promise((resolve) => {
23
23
  if (signal?.aborted) {
24
24
  resolve();
25
25
  return;
@@ -61,8 +61,8 @@ export function delay(ms: number, signal?: AbortSignal): Promise<void> {
61
61
  * }
62
62
  * ```
63
63
  */
64
- export function delayOrThrow(ms: number, signal?: AbortSignal): Promise<void> {
65
- return new Promise((resolve, reject) => {
64
+ export async function delayOrThrow(ms: number, signal?: AbortSignal): Promise<void> {
65
+ return await new Promise((resolve, reject) => {
66
66
  if (signal?.aborted) {
67
67
  reject(signal.reason);
68
68
  return;
@@ -22,7 +22,7 @@ type DefaultPath<
22
22
  export type FilteredKeyPath<T, U = never> = T extends ReadonlyArray<infer V> ? (IsTuple<T> extends true ? { [K in TupleKey<T>]-?: PathImpl<Exclude<K, symbol>, T[K], U> }[TupleKey<T>] : PathImpl<number, V, U>) : { [K in keyof T]-?: PathImpl<Exclude<K, symbol>, T[K], U> }[keyof T];
23
23
  /* eslint-enable style/max-len */
24
24
 
25
- type IsTuple<T extends ReadonlyArray<any>> = number extends T['length'] ? false : true;
25
+ type IsTuple<T extends readonly any[]> = number extends T['length'] ? false : true;
26
26
  type PathImpl<K extends number | string, V, U> = [U] extends [never] ? DefaultPath<K, V> : ConditionalPath<K, V, U>;
27
27
  type TerminalType =
28
28
  | bigint
@@ -38,4 +38,4 @@ type TerminalType =
38
38
  | symbol
39
39
  | undefined;
40
40
 
41
- type TupleKey<T extends ReadonlyArray<any>> = Exclude<keyof T, keyof any[]>;
41
+ type TupleKey<T extends readonly any[]> = Exclude<keyof T, keyof any[]>;
@@ -40,3 +40,9 @@ export type ReadonlyPartialRecord<K extends keyof any, T> = Readonly<PartialReco
40
40
  * A readonly `Record`.
41
41
  */
42
42
  export type ReadonlyRecord<K extends keyof any, T> = Readonly<Record<K, T>>;
43
+
44
+ /**
45
+ * A promise whose rejection is handled internally and can safely be left unawaited.
46
+ * This type only marks that contract; it does not add runtime rejection handling.
47
+ */
48
+ export type SafePromise<T> = Promise<T> & { readonly __safePromise?: true };
package/src/url.ts CHANGED
@@ -12,7 +12,6 @@
12
12
  * @throws Error if `redirectPath` is not a safe same-origin application path.
13
13
  */
14
14
  export function appendRedirectParamToUrl(url: string, redirectPath: string) {
15
- // eslint-disable-next-line style/max-len
16
15
  if (!isSafeRedirectPath(redirectPath)) throw new Error(`Invalid redirect path: "${redirectPath}". Redirect paths must be safe same-origin paths starting with '/'.`);
17
16
 
18
17
  const [baseAndHash, rawQuery = ''] = url.split('?');