@fuzdev/fuz_util 0.42.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 (135) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +83 -0
  3. package/dist/array.d.ts +15 -0
  4. package/dist/array.d.ts.map +1 -0
  5. package/dist/array.js +25 -0
  6. package/dist/async.d.ts +62 -0
  7. package/dist/async.d.ts.map +1 -0
  8. package/dist/async.js +147 -0
  9. package/dist/colors.d.ts +41 -0
  10. package/dist/colors.d.ts.map +1 -0
  11. package/dist/colors.js +106 -0
  12. package/dist/counter.d.ts +7 -0
  13. package/dist/counter.d.ts.map +1 -0
  14. package/dist/counter.js +7 -0
  15. package/dist/deep_equal.d.ts +18 -0
  16. package/dist/deep_equal.d.ts.map +1 -0
  17. package/dist/deep_equal.js +152 -0
  18. package/dist/dom.d.ts +35 -0
  19. package/dist/dom.d.ts.map +1 -0
  20. package/dist/dom.js +95 -0
  21. package/dist/error.d.ts +15 -0
  22. package/dist/error.d.ts.map +1 -0
  23. package/dist/error.js +18 -0
  24. package/dist/fetch.d.ts +81 -0
  25. package/dist/fetch.d.ts.map +1 -0
  26. package/dist/fetch.js +162 -0
  27. package/dist/fs.d.ts +34 -0
  28. package/dist/fs.d.ts.map +1 -0
  29. package/dist/fs.js +73 -0
  30. package/dist/function.d.ts +27 -0
  31. package/dist/function.d.ts.map +1 -0
  32. package/dist/function.js +21 -0
  33. package/dist/git.d.ts +132 -0
  34. package/dist/git.d.ts.map +1 -0
  35. package/dist/git.js +288 -0
  36. package/dist/id.d.ts +18 -0
  37. package/dist/id.d.ts.map +1 -0
  38. package/dist/id.js +18 -0
  39. package/dist/iterator.d.ts +5 -0
  40. package/dist/iterator.d.ts.map +1 -0
  41. package/dist/iterator.js +9 -0
  42. package/dist/json.d.ts +30 -0
  43. package/dist/json.d.ts.map +1 -0
  44. package/dist/json.js +44 -0
  45. package/dist/library_json.d.ts +42 -0
  46. package/dist/library_json.d.ts.map +1 -0
  47. package/dist/library_json.js +76 -0
  48. package/dist/log.d.ts +188 -0
  49. package/dist/log.d.ts.map +1 -0
  50. package/dist/log.js +393 -0
  51. package/dist/map.d.ts +12 -0
  52. package/dist/map.d.ts.map +1 -0
  53. package/dist/map.js +14 -0
  54. package/dist/maths.d.ts +85 -0
  55. package/dist/maths.d.ts.map +1 -0
  56. package/dist/maths.js +87 -0
  57. package/dist/object.d.ts +46 -0
  58. package/dist/object.d.ts.map +1 -0
  59. package/dist/object.js +89 -0
  60. package/dist/package_json.d.ts +90 -0
  61. package/dist/package_json.d.ts.map +1 -0
  62. package/dist/package_json.js +112 -0
  63. package/dist/path.d.ts +63 -0
  64. package/dist/path.d.ts.map +1 -0
  65. package/dist/path.js +83 -0
  66. package/dist/print.d.ts +52 -0
  67. package/dist/print.d.ts.map +1 -0
  68. package/dist/print.js +89 -0
  69. package/dist/process.d.ts +77 -0
  70. package/dist/process.d.ts.map +1 -0
  71. package/dist/process.js +148 -0
  72. package/dist/random.d.ts +25 -0
  73. package/dist/random.d.ts.map +1 -0
  74. package/dist/random.js +35 -0
  75. package/dist/random_alea.d.ts +23 -0
  76. package/dist/random_alea.d.ts.map +1 -0
  77. package/dist/random_alea.js +95 -0
  78. package/dist/regexp.d.ts +12 -0
  79. package/dist/regexp.d.ts.map +1 -0
  80. package/dist/regexp.js +16 -0
  81. package/dist/result.d.ts +64 -0
  82. package/dist/result.d.ts.map +1 -0
  83. package/dist/result.js +48 -0
  84. package/dist/source_json.d.ts +375 -0
  85. package/dist/source_json.d.ts.map +1 -0
  86. package/dist/source_json.js +189 -0
  87. package/dist/string.d.ts +51 -0
  88. package/dist/string.d.ts.map +1 -0
  89. package/dist/string.js +92 -0
  90. package/dist/throttle.d.ts +26 -0
  91. package/dist/throttle.d.ts.map +1 -0
  92. package/dist/throttle.js +53 -0
  93. package/dist/timings.d.ts +33 -0
  94. package/dist/timings.d.ts.map +1 -0
  95. package/dist/timings.js +75 -0
  96. package/dist/types.d.ts +77 -0
  97. package/dist/types.d.ts.map +1 -0
  98. package/dist/types.js +10 -0
  99. package/dist/url.d.ts +10 -0
  100. package/dist/url.d.ts.map +1 -0
  101. package/dist/url.js +8 -0
  102. package/package.json +125 -0
  103. package/src/lib/array.ts +30 -0
  104. package/src/lib/async.ts +182 -0
  105. package/src/lib/colors.ts +132 -0
  106. package/src/lib/counter.ts +11 -0
  107. package/src/lib/deep_equal.ts +155 -0
  108. package/src/lib/dom.ts +108 -0
  109. package/src/lib/error.ts +22 -0
  110. package/src/lib/fetch.ts +231 -0
  111. package/src/lib/fs.ts +128 -0
  112. package/src/lib/function.ts +32 -0
  113. package/src/lib/git.ts +390 -0
  114. package/src/lib/id.ts +30 -0
  115. package/src/lib/iterator.ts +8 -0
  116. package/src/lib/json.ts +61 -0
  117. package/src/lib/library_json.ts +122 -0
  118. package/src/lib/log.ts +469 -0
  119. package/src/lib/map.ts +18 -0
  120. package/src/lib/maths.ts +91 -0
  121. package/src/lib/object.ts +110 -0
  122. package/src/lib/package_json.ts +135 -0
  123. package/src/lib/path.ts +137 -0
  124. package/src/lib/print.ts +111 -0
  125. package/src/lib/process.ts +207 -0
  126. package/src/lib/random.ts +48 -0
  127. package/src/lib/random_alea.ts +107 -0
  128. package/src/lib/regexp.ts +17 -0
  129. package/src/lib/result.ts +67 -0
  130. package/src/lib/source_json.ts +209 -0
  131. package/src/lib/string.ts +99 -0
  132. package/src/lib/throttle.ts +70 -0
  133. package/src/lib/timings.ts +93 -0
  134. package/src/lib/types.ts +99 -0
  135. package/src/lib/url.ts +14 -0
package/src/lib/log.ts ADDED
@@ -0,0 +1,469 @@
1
+ import {styleText} from 'node:util';
2
+ import {DEV} from 'esm-env';
3
+
4
+ /**
5
+ * Log level hierarchy from least to most verbose.
6
+ * - `'off'`: No logging
7
+ * - `'error'`: Only errors
8
+ * - `'warn'`: Errors and warnings
9
+ * - `'info'`: Errors, warnings, and info (default)
10
+ * - `'debug'`: All messages including debug
11
+ */
12
+ export type LogLevel = 'off' | 'error' | 'warn' | 'info' | 'debug';
13
+
14
+ /**
15
+ * Console interface subset used by Logger for output.
16
+ * Allows custom console implementations for testing.
17
+ */
18
+ export type LogConsole = Pick<typeof console, 'error' | 'warn' | 'log'>;
19
+
20
+ const CHAR_ERROR = '🞩';
21
+ const CHAR_WARN = '⚑';
22
+ // Info logs have no character prefix - they only show the label.
23
+ // This is by design: info is the "default" log level for standard output,
24
+ // so it gets minimal visual noise. Error, warn, and debug have distinctive
25
+ // prefixes to make them stand out from normal info logs.
26
+ const CHAR_DEBUG = '┆';
27
+
28
+ // Pre-computed method prefix strings
29
+ const PREFIX_ERROR = `${CHAR_ERROR}error${CHAR_ERROR}`;
30
+ const PREFIX_WARN = `${CHAR_WARN}warn${CHAR_WARN}`;
31
+ // Info has no prefix - see CHAR_DEBUG comment above
32
+ const PREFIX_DEBUG = `${CHAR_DEBUG}debug${CHAR_DEBUG}`;
33
+
34
+ const LOG_LEVEL_VALUES: Record<LogLevel, number> = {
35
+ off: 0,
36
+ error: 1,
37
+ warn: 2,
38
+ info: 3,
39
+ debug: 4,
40
+ };
41
+
42
+ /**
43
+ * Converts a log level to its numeric value for comparison.
44
+ * Higher numbers indicate more verbose logging.
45
+ * @param level The log level to convert
46
+ * @returns Numeric value (0-4)
47
+ */
48
+ export const log_level_to_number = (level: LogLevel): number => LOG_LEVEL_VALUES[level];
49
+
50
+ /**
51
+ * Parses and validates a log level string.
52
+ * @param value The value to parse as a log level
53
+ * @returns The validated log level, or undefined if value is undefined
54
+ * @throws Error if value is provided but invalid
55
+ */
56
+ export const log_level_parse = (value: string | undefined): LogLevel | undefined => {
57
+ if (!value) return undefined;
58
+ if (value in LOG_LEVEL_VALUES) return value as LogLevel;
59
+ throw new Error(`Invalid log level: '${value}'`);
60
+ };
61
+
62
+ const DEFAULT_LOG_LEVEL: LogLevel =
63
+ (typeof process === 'undefined' ? undefined : log_level_parse(process.env.PUBLIC_LOG_LEVEL)) ??
64
+ (process.env.VITEST ? 'off' : DEV ? 'debug' : 'info');
65
+
66
+ // Identity function for when colors are disabled
67
+ const NO_COLOR_ST: typeof styleText = (_: string | Array<string>, s: string) => s;
68
+
69
+ /**
70
+ * Simple, flexible logger with support for child loggers and automatic context.
71
+ *
72
+ * Features:
73
+ * - Instance-based configuration (no global state)
74
+ * - Child loggers with automatic label concatenation
75
+ * - Parent chain inheritance for level, console, and colors
76
+ * - Respects NO_COLOR environment variable
77
+ *
78
+ * @example
79
+ * ```ts
80
+ * // Basic logger
81
+ * const log = new Logger('app');
82
+ * log.info('starting'); // [app] ➤ starting
83
+ *
84
+ * // Child logger
85
+ * const db_log = log.child('db');
86
+ * db_log.info('connected'); // [app:db] ➤ connected
87
+ *
88
+ * // Custom configuration
89
+ * const verbose_log = new Logger('debug', { level: 'debug', colors: true });
90
+ * ```
91
+ */
92
+ export class Logger {
93
+ readonly label?: string;
94
+ readonly parent?: Logger;
95
+
96
+ // Private override fields (undefined = inherit from parent)
97
+ #level_override?: LogLevel;
98
+ #colors_override?: boolean;
99
+ #console_override?: LogConsole;
100
+
101
+ // Lazy cache for formatted prefixes (individually cached and invalidated when colors change)
102
+ #cached_colors?: boolean;
103
+ #cached_st?: typeof styleText;
104
+ #cached_error?: string;
105
+ #cached_warn?: string;
106
+ #cached_info?: string;
107
+ #cached_debug?: string;
108
+
109
+ #cached_level_string?: LogLevel;
110
+ #cached_level?: number;
111
+
112
+ /**
113
+ * Creates a new Logger instance.
114
+ *
115
+ * @param label Optional label for this logger. Can be `undefined` for no label, or an
116
+ * empty string `''` which is functionally equivalent (both produce no brackets in output).
117
+ * Note: Empty strings are only allowed for root loggers - child loggers cannot have empty labels.
118
+ * @param options Optional configuration for level, colors, and console
119
+ */
120
+ constructor(label?: string, options: LoggerOptions = {}) {
121
+ this.label = label;
122
+ this.parent = (options as InternalLoggerOptions).parent;
123
+
124
+ // Set overrides if provided (undefined = inherit from parent)
125
+ if (options.level !== undefined) {
126
+ log_level_parse(options.level); // throws if invalid
127
+ this.#level_override = options.level;
128
+ }
129
+ if (options.colors !== undefined) {
130
+ this.#colors_override = options.colors;
131
+ }
132
+ if (options.console !== undefined) {
133
+ this.#console_override = options.console;
134
+ }
135
+ }
136
+
137
+ /**
138
+ * Dynamic getter for level - checks override, then parent, then default.
139
+ */
140
+ get level(): LogLevel {
141
+ if (this.#level_override !== undefined) {
142
+ return this.#level_override;
143
+ }
144
+ if (this.parent) {
145
+ return this.parent.level;
146
+ }
147
+ return DEFAULT_LOG_LEVEL;
148
+ }
149
+
150
+ /**
151
+ * Setter for level - creates override.
152
+ */
153
+ set level(value: LogLevel) {
154
+ log_level_parse(value); // throws if invalid
155
+ this.#level_override = value;
156
+ }
157
+
158
+ /**
159
+ * Dynamic getter for colors - checks override, then parent, then environment variables.
160
+ *
161
+ * Colors are disabled if either the `NO_COLOR` or `CLAUDECODE` environment variable is set.
162
+ * The `CLAUDECODE` check disables colors in Claude Code environments where ANSI color codes
163
+ * may not render correctly in the output.
164
+ */
165
+ get colors(): boolean {
166
+ if (this.#colors_override !== undefined) {
167
+ return this.#colors_override;
168
+ }
169
+ if (this.parent) {
170
+ return this.parent.colors;
171
+ }
172
+ const has_no_color =
173
+ typeof process !== 'undefined' &&
174
+ (process.env.NO_COLOR !== undefined || process.env.CLAUDECODE !== undefined);
175
+ return !has_no_color;
176
+ }
177
+
178
+ /**
179
+ * Setter for colors - creates override.
180
+ */
181
+ set colors(value: boolean) {
182
+ this.#colors_override = value;
183
+ }
184
+
185
+ /**
186
+ * Dynamic getter for console - checks override, then parent, then global console.
187
+ */
188
+ get console(): LogConsole {
189
+ if (this.#console_override !== undefined) {
190
+ return this.#console_override;
191
+ }
192
+ if (this.parent) {
193
+ return this.parent.console;
194
+ }
195
+ return console;
196
+ }
197
+
198
+ /**
199
+ * Setter for console - creates override.
200
+ */
201
+ set console(value: LogConsole) {
202
+ this.#console_override = value;
203
+ }
204
+
205
+ /**
206
+ * Gets the root logger by walking up the parent chain.
207
+ * Useful for setting global configuration that affects all child loggers.
208
+ * @returns The root logger (the one without a parent)
209
+ */
210
+ get root(): Logger {
211
+ let current: Logger = this; // eslint-disable-line consistent-this, @typescript-eslint/no-this-alias
212
+ while (current.parent) {
213
+ current = current.parent;
214
+ }
215
+ return current;
216
+ }
217
+
218
+ /**
219
+ * Clears the level override for this logger, restoring inheritance from parent.
220
+ * After calling this, the logger will dynamically inherit the level from its parent
221
+ * (or use the default level if it has no parent).
222
+ */
223
+ clear_level_override(): void {
224
+ this.#level_override = undefined;
225
+ this.#cached_level_string = undefined;
226
+ this.#cached_level = undefined;
227
+ }
228
+
229
+ /**
230
+ * Clears the colors override for this logger, restoring inheritance from parent.
231
+ * After calling this, the logger will dynamically inherit colors from its parent
232
+ * (or use the default colors behavior if it has no parent).
233
+ */
234
+ clear_colors_override(): void {
235
+ this.#colors_override = undefined;
236
+ // Invalidate prefix caches since colors affect them
237
+ this.#cached_colors = undefined;
238
+ this.#cached_error = undefined;
239
+ this.#cached_warn = undefined;
240
+ this.#cached_info = undefined;
241
+ this.#cached_debug = undefined;
242
+ }
243
+
244
+ /**
245
+ * Clears the console override for this logger, restoring inheritance from parent.
246
+ * After calling this, the logger will dynamically inherit the console from its parent
247
+ * (or use the global console if it has no parent).
248
+ */
249
+ clear_console_override(): void {
250
+ this.#console_override = undefined;
251
+ }
252
+
253
+ /**
254
+ * Ensures prefix cache is valid by checking if colors configuration changed.
255
+ * Uses pull-based invalidation: checks colors on each access and invalidates cached
256
+ * prefixes if colors changed. This automatically handles inheritance changes since
257
+ * `this.colors` getter walks the parent chain on each access.
258
+ *
259
+ * Invalidates all 4 cached prefix strings when colors change, since they all depend
260
+ * on the color configuration.
261
+ */
262
+ #ensure_cache_valid(): void {
263
+ const current_colors = this.colors;
264
+ if (this.#cached_colors !== current_colors) {
265
+ this.#cached_colors = current_colors;
266
+ this.#cached_st = current_colors ? styleText : NO_COLOR_ST;
267
+ this.#cached_error = undefined;
268
+ this.#cached_warn = undefined;
269
+ this.#cached_info = undefined;
270
+ this.#cached_debug = undefined;
271
+ }
272
+ }
273
+
274
+ /**
275
+ * Formats the label portion of log output with given styleText function.
276
+ * Applies color styling if enabled, otherwise returns plain bracketed label.
277
+ */
278
+ #format_label(st: typeof styleText, colored: boolean): string {
279
+ if (!this.label) return '';
280
+
281
+ return colored
282
+ ? `${st('gray', '[')}${st('magenta', this.label)}${st('gray', ']')}`
283
+ : `[${this.label}]`;
284
+ }
285
+
286
+ /**
287
+ * Gets the formatted error prefix, lazily computing and caching if needed.
288
+ * Lazy computation means prefixes are only built when the corresponding log method
289
+ * is first called, avoiding work for unused log levels.
290
+ */
291
+ #get_error_prefix(): string {
292
+ this.#ensure_cache_valid();
293
+ if (this.#cached_error === undefined) {
294
+ const st = this.#cached_st!;
295
+ const prefix = st('red', PREFIX_ERROR);
296
+ const label = this.#format_label(st, this.#cached_colors!);
297
+ this.#cached_error = label ? `${prefix} ${label}` : prefix;
298
+ }
299
+ return this.#cached_error;
300
+ }
301
+
302
+ /**
303
+ * Gets the formatted warn prefix, lazily computing and caching if needed.
304
+ */
305
+ #get_warn_prefix(): string {
306
+ this.#ensure_cache_valid();
307
+ if (this.#cached_warn === undefined) {
308
+ const st = this.#cached_st!;
309
+ const prefix = st('yellow', PREFIX_WARN);
310
+ const label = this.#format_label(st, this.#cached_colors!);
311
+ this.#cached_warn = label ? `${prefix} ${label}` : prefix;
312
+ }
313
+ return this.#cached_warn;
314
+ }
315
+
316
+ /**
317
+ * Gets the formatted info prefix, lazily computing and caching if needed.
318
+ * Note: info has no colored prefix character, only the label.
319
+ */
320
+ #get_info_prefix(): string {
321
+ this.#ensure_cache_valid();
322
+ if (this.#cached_info === undefined) {
323
+ const st = this.#cached_st!;
324
+ const label = this.#format_label(st, this.#cached_colors!);
325
+ this.#cached_info = label || '';
326
+ }
327
+ return this.#cached_info;
328
+ }
329
+
330
+ /**
331
+ * Gets the formatted debug prefix, lazily computing and caching if needed.
332
+ */
333
+ #get_debug_prefix(): string {
334
+ this.#ensure_cache_valid();
335
+ if (this.#cached_debug === undefined) {
336
+ const st = this.#cached_st!;
337
+ const prefix = st('gray', PREFIX_DEBUG);
338
+ const label = this.#format_label(st, this.#cached_colors!);
339
+ this.#cached_debug = label ? `${prefix} ${label}` : prefix;
340
+ }
341
+ return this.#cached_debug;
342
+ }
343
+
344
+ /**
345
+ * Gets the cached numeric level value, updating cache if level changed.
346
+ * Called on every log method invocation to check if the message should be filtered.
347
+ * Caches the numeric value (from LOG_LEVEL_VALUES dictionary) to avoid repeated lookups.
348
+ * Uses pull-based invalidation: checks `this.level` getter which handles inheritance.
349
+ */
350
+ #get_cached_level(): number {
351
+ const current_level_string = this.level;
352
+ if (this.#cached_level_string !== current_level_string) {
353
+ this.#cached_level_string = current_level_string;
354
+ this.#cached_level = LOG_LEVEL_VALUES[current_level_string];
355
+ }
356
+ return this.#cached_level!;
357
+ }
358
+
359
+ /**
360
+ * Creates a child logger with automatic label concatenation.
361
+ * Children inherit parent configuration unless overridden.
362
+ *
363
+ * @param label Child label (will be concatenated with parent label using `:`).
364
+ * Cannot be an empty string - empty labels would result in confusing output like `parent:`
365
+ * with a trailing colon. Use `undefined` or `''` only for root loggers.
366
+ * @param options Optional configuration overrides
367
+ * @returns New Logger instance with concatenated label
368
+ * @throws Error if label is an empty string
369
+ *
370
+ * @example
371
+ * ```ts
372
+ * const app_log = new Logger('app');
373
+ * const db_log = app_log.child('db'); // label: 'app:db'
374
+ * const query_log = db_log.child('query'); // label: 'app:db:query'
375
+ * ```
376
+ */
377
+ child(label: string, options: LoggerOptions = {}): Logger {
378
+ if (label === '') {
379
+ throw new Error('Logger label cannot be empty when creating child');
380
+ }
381
+
382
+ const child_label = this.label ? `${this.label}:${label}` : label;
383
+
384
+ // Pass parent reference and all config options
385
+ const internal_options: InternalLoggerOptions = {
386
+ ...options,
387
+ parent: this,
388
+ };
389
+ return new Logger(child_label, internal_options);
390
+ }
391
+
392
+ /**
393
+ * Logs an error message with `🞩error🞩` prefix.
394
+ * Only outputs if current level is `error` or higher.
395
+ */
396
+ error(...args: Array<unknown>): void {
397
+ if (this.#get_cached_level() < LOG_LEVEL_VALUES.error) return;
398
+ this.console.error(this.#get_error_prefix(), ...args);
399
+ }
400
+
401
+ /**
402
+ * Logs a warning message with `⚑warn⚑` prefix.
403
+ * Only outputs if current level is `warn` or higher.
404
+ */
405
+ warn(...args: Array<unknown>): void {
406
+ if (this.#get_cached_level() < LOG_LEVEL_VALUES.warn) return;
407
+ this.console.warn(this.#get_warn_prefix(), ...args);
408
+ }
409
+
410
+ /**
411
+ * Logs an informational message.
412
+ * Unlike error/warn/debug, info has no character prefix - only the label is shown.
413
+ * This keeps standard output clean since info is the default log level.
414
+ * Only outputs if current level is `info` or higher.
415
+ */
416
+ info(...args: Array<unknown>): void {
417
+ if (this.#get_cached_level() < LOG_LEVEL_VALUES.info) return;
418
+ this.console.log(this.#get_info_prefix(), ...args);
419
+ }
420
+
421
+ /**
422
+ * Logs a debug message with `┆debug┆` prefix.
423
+ * Only outputs if current level is `debug`.
424
+ */
425
+ debug(...args: Array<unknown>): void {
426
+ if (this.#get_cached_level() < LOG_LEVEL_VALUES.debug) return;
427
+ this.console.log(this.#get_debug_prefix(), ...args);
428
+ }
429
+
430
+ /**
431
+ * Logs raw output without any prefix, formatting, or level filtering.
432
+ * Bypasses the logger's level checking, prefix formatting, and color application entirely.
433
+ * Useful for outputting structured data or when you need full control over formatting.
434
+ *
435
+ * Note: This method ignores the configured log level - it always outputs regardless of
436
+ * whether the logger is set to 'off' or any other level.
437
+ *
438
+ * @param args Values to log directly to console
439
+ */
440
+ raw(...args: Array<unknown>): void {
441
+ this.console.log(...args);
442
+ }
443
+ }
444
+
445
+ export interface LoggerOptions {
446
+ /**
447
+ * Log level for this logger instance.
448
+ * Inherits from parent or defaults to 'info'.
449
+ */
450
+ level?: LogLevel;
451
+
452
+ /**
453
+ * Console interface for output.
454
+ * Inherits from parent or defaults to global console.
455
+ * Useful for testing.
456
+ */
457
+ console?: LogConsole;
458
+
459
+ /**
460
+ * Whether to use colors in output.
461
+ * Inherits from parent or defaults to enabled (unless NO_COLOR env var is set).
462
+ */
463
+ colors?: boolean;
464
+ }
465
+
466
+ // Internal type for child() implementation
467
+ interface InternalLoggerOptions extends LoggerOptions {
468
+ parent?: Logger;
469
+ }
package/src/lib/map.ts ADDED
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Sorts a map by `comparator`, a function that compares two entries,
3
+ * defaulting to using `localCompare` and `>`.
4
+ */
5
+ export const sort_map = <T extends Map<any, any>>(
6
+ map: T,
7
+ comparator = compare_simple_map_entries,
8
+ ): T => new Map([...map].sort(comparator)) as T;
9
+
10
+ /**
11
+ * Compares two map entries for sorting purposes.
12
+ * If the key is a string, it uses `localeCompare` for comparison.
13
+ * For other types, it uses `>`.
14
+ */
15
+ export const compare_simple_map_entries = (a: [any, any], b: [any, any]): number => {
16
+ const a_key = a[0];
17
+ return typeof a_key === 'string' ? a_key.localeCompare(b[0]) : a[0] > b[0] ? 1 : -1;
18
+ };
@@ -0,0 +1,91 @@
1
+ /**
2
+ * Returns `n` bounded to `min` and `max`.
3
+ */
4
+ export const clamp = (n: number, min: number, max: number): number =>
5
+ Math.min(Math.max(n, min), max);
6
+
7
+ /**
8
+ * Linear interpolation between `a` and `b` by `amount`.
9
+ */
10
+ export const lerp = (a: number, b: number, amount: number): number => (1 - amount) * a + amount * b;
11
+
12
+ /**
13
+ * Rounds a number to a specified number of decimal places.
14
+ */
15
+ export const round = (n: number, decimals: number): number => {
16
+ const mult = 10 ** decimals;
17
+ return Math.round(n * mult) / mult;
18
+ };
19
+
20
+ /**
21
+ * golden ratio/mean constants, useful for scaling: https://wikipedia.org/wiki/Golden_ratio
22
+ */
23
+ export const GR = 1.618033988749895;
24
+ /**
25
+ * golden ratio/mean constants, `1/GR`, useful for scaling: https://wikipedia.org/wiki/Golden_ratio
26
+ */
27
+ export const GR_i = 0.6180339887498948;
28
+ /**
29
+ * golden ratio/mean constants, `GR**2`, useful for scaling: https://wikipedia.org/wiki/Golden_ratio
30
+ */
31
+ export const GR_2 = 2.618033988749895;
32
+ /**
33
+ * golden ratio/mean constants, `1/(GR**2)`, useful for scaling: https://wikipedia.org/wiki/Golden_ratio
34
+ */
35
+ export const GR_2i = 0.38196601125010515;
36
+ /**
37
+ * golden ratio/mean constants, `GR**3`, useful for scaling: https://wikipedia.org/wiki/Golden_ratio
38
+ */
39
+ export const GR_3 = 4.23606797749979;
40
+ /**
41
+ * golden ratio/mean constants, `1/(GR**3)`, useful for scaling: https://wikipedia.org/wiki/Golden_ratio
42
+ */
43
+ export const GR_3i = 0.2360679774997897;
44
+ /**
45
+ * golden ratio/mean constants, `GR**4`, useful for scaling: https://wikipedia.org/wiki/Golden_ratio
46
+ */
47
+ export const GR_4 = 6.854101966249686;
48
+ /**
49
+ * golden ratio/mean constants, `1/(GR**4)`, useful for scaling: https://wikipedia.org/wiki/Golden_ratio
50
+ */
51
+ export const GR_4i = 0.14589803375031543;
52
+ /**
53
+ * golden ratio/mean constants, `GR**5`, useful for scaling: https://wikipedia.org/wiki/Golden_ratio
54
+ */
55
+ export const GR_5 = 11.090169943749476;
56
+ /**
57
+ * golden ratio/mean constants, `1/(GR**5)`, useful for scaling: https://wikipedia.org/wiki/Golden_ratio
58
+ */
59
+ export const GR_5i = 0.09016994374947422;
60
+ /**
61
+ * golden ratio/mean constants, `GR**6`, useful for scaling: https://wikipedia.org/wiki/Golden_ratio
62
+ */
63
+ export const GR_6 = 17.944271909999163;
64
+ /**
65
+ * golden ratio/mean constants, `1/(GR**6)`, useful for scaling: https://wikipedia.org/wiki/Golden_ratio
66
+ */
67
+ export const GR_6i = 0.0557280900008412;
68
+ /**
69
+ * golden ratio/mean constants, `GR**7`, useful for scaling: https://wikipedia.org/wiki/Golden_ratio
70
+ */
71
+ export const GR_7 = 29.03444185374864;
72
+ /**
73
+ * golden ratio/mean constants, `1/(GR**7)`, useful for scaling: https://wikipedia.org/wiki/Golden_ratio
74
+ */
75
+ export const GR_7i = 0.03444185374863302;
76
+ /**
77
+ * golden ratio/mean constants, `GR**8`, useful for scaling: https://wikipedia.org/wiki/Golden_ratio
78
+ */
79
+ export const GR_8 = 46.978713763747805;
80
+ /**
81
+ * golden ratio/mean constants, `1/(GR**8)`, useful for scaling: https://wikipedia.org/wiki/Golden_ratio
82
+ */
83
+ export const GR_8i = 0.02128623625220818;
84
+ /**
85
+ * golden ratio/mean constants, `GR**9`, useful for scaling: https://wikipedia.org/wiki/Golden_ratio
86
+ */
87
+ export const GR_9 = 76.01315561749645;
88
+ /**
89
+ * golden ratio/mean constants, `1/(GR**9)`, useful for scaling: https://wikipedia.org/wiki/Golden_ratio
90
+ */
91
+ export const GR_9i = 0.013155617496424835;
@@ -0,0 +1,110 @@
1
+ import type {OmitStrict} from './types.js';
2
+
3
+ /**
4
+ * Returns a boolean indicating if `value` is
5
+ * a plain object, possibly created with `Object.create(null)`.
6
+ * But warning! This fails for some obscure corner cases, use a proper library for weird things.
7
+ */
8
+ export const is_plain_object = (value: any): boolean =>
9
+ value ? value.constructor === Object || value.constructor === undefined : false;
10
+
11
+ /**
12
+ * Iterated keys in `for..in` are always returned as strings,
13
+ * so to prevent usage errors the key type of `mapper` is always a string.
14
+ * Symbols are not enumerable as keys, so they're excluded.
15
+ */
16
+ export const map_record = <T, K extends string | number, U>(
17
+ obj: Record<K, T>,
18
+ mapper: (value: T, key: string) => U,
19
+ ): Record<K, U> => {
20
+ const result = {} as Record<K, U>;
21
+ for (const key in obj) {
22
+ result[key] = mapper(obj[key], key);
23
+ }
24
+ return result;
25
+ };
26
+
27
+ /**
28
+ * Creates a new object without the specified `keys`.
29
+ */
30
+ export const omit = <T extends Record<K, any>, K extends keyof T>(
31
+ obj: T,
32
+ keys: Array<K>,
33
+ ): OmitStrict<T, K> => {
34
+ const result = {} as T;
35
+ for (const key in obj) {
36
+ if (!keys.includes(key as any)) {
37
+ result[key] = obj[key];
38
+ }
39
+ }
40
+ return result;
41
+ };
42
+
43
+ /**
44
+ * Creates a new object with properties that pass the `should_pick` predicate.
45
+ */
46
+ export const pick_by = <T extends Record<K, any>, K extends string | number>(
47
+ obj: T,
48
+ should_pick: (value: any, key: K) => boolean,
49
+ ): Partial<T> => {
50
+ const result = {} as Partial<T>;
51
+ for (const key in obj) {
52
+ const value = obj[key];
53
+ if (should_pick(value, key as any)) {
54
+ result[key] = value;
55
+ }
56
+ }
57
+ return result;
58
+ };
59
+
60
+ /**
61
+ * `omit_undefined` is a commonly used form of `pick_by`.
62
+ * See this issue for why it's used so much:
63
+ * https://github.com/Microsoft/TypeScript/issues/13195
64
+ * @param obj
65
+ * @returns `obj` with all `undefined` properties removed
66
+ */
67
+ export const omit_undefined = <T extends Record<string | number, any>>(obj: T): T =>
68
+ pick_by(obj, (v) => v !== undefined) as T;
69
+
70
+ /**
71
+ * A more explicit form of `{put_this_first: obj.put_this_first, ...obj}`.
72
+ */
73
+ export const reorder = <T extends Record<K, any>, K extends string | number>(
74
+ obj: T,
75
+ keys: Array<K>,
76
+ ): T => {
77
+ const result = {} as T;
78
+ for (const k of keys) result[k] = obj[k];
79
+ // overwriting is probably faster than using
80
+ // a `Set` to track what's already been added
81
+ for (const k in obj) result[k] = obj[k];
82
+ return result;
83
+ };
84
+
85
+ /**
86
+ * Frozen empty object with no properties, good for options default values.
87
+ */
88
+ export const EMPTY_OBJECT: Record<string | number | symbol, undefined> & object = Object.freeze({}); // eslint-disable-line @typescript-eslint/no-redundant-type-constituents
89
+
90
+ /**
91
+ * Performs a depth-first traversal of an object's enumerable properties,
92
+ * calling `cb` for every key and value with the current `obj` context.
93
+ * @param obj - any object with enumerable properties
94
+ * @param cb - receives the key, value, and `obj` for every enumerable property on `obj` and its descendents
95
+ */
96
+ export const traverse = (obj: any, cb: (key: string, value: any, obj: any) => void): void => {
97
+ if (!obj || typeof obj !== 'object') return;
98
+ for (const k in obj) {
99
+ const v = obj[k];
100
+ cb(k, v, obj);
101
+ traverse(v, cb);
102
+ }
103
+ };
104
+
105
+ export const transform_empty_object_to_undefined = <T>(obj: T): T | undefined => {
106
+ if (obj && Object.keys(obj).length === 0) {
107
+ return;
108
+ }
109
+ return obj;
110
+ };