hanzio 1.0.0 → 1.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +30 -1
- package/dist/cool-console-log/index.d.ts +108 -0
- package/dist/cool-console-log/index.js +111 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +109 -0
- package/package.json +1 -37
- package/src/cool-console-log/index.ts +229 -0
- package/src/index.ts +1 -0
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
A small TypeScript-first utility library for common application code: arrays,
|
|
4
4
|
strings, promises, caching, state machines, dates, URLs, Zod helpers, API clients,
|
|
5
|
-
JWTs, gzip, sitemap crawling, and
|
|
5
|
+
JWTs, gzip, sitemap crawling, queues, and colorful terminal logging.
|
|
6
6
|
|
|
7
7
|
## Install
|
|
8
8
|
|
|
@@ -30,6 +30,7 @@ import { jwtSign } from 'hanzio/jwt'
|
|
|
30
30
|
import { compressString } from 'hanzio/gzip'
|
|
31
31
|
import { getDomainSitemap } from 'hanzio/sitemap'
|
|
32
32
|
import PQueue from 'hanzio/p-queue'
|
|
33
|
+
import { createCoolLogger } from 'hanzio/cool-console-log'
|
|
33
34
|
```
|
|
34
35
|
|
|
35
36
|
The package currently publishes `src/**/*.ts` intentionally so Bun can consume
|
|
@@ -500,6 +501,34 @@ try {
|
|
|
500
501
|
}
|
|
501
502
|
```
|
|
502
503
|
|
|
504
|
+
### Cool console log
|
|
505
|
+
|
|
506
|
+
ANSI-colored logging for terminals. Exports include `terminalColors`, `createCoolLogger`,
|
|
507
|
+
`logOperationSummary`, `colorize`, and `logBanner`. By default colors and banners are
|
|
508
|
+
development-only (`NODE_ENV !== 'production'`); loggers honor `colorsInDevOnly`
|
|
509
|
+
and callbacks via `CoolLoggerOptions.onLog`.
|
|
510
|
+
|
|
511
|
+
```ts
|
|
512
|
+
import {
|
|
513
|
+
createCoolLogger,
|
|
514
|
+
colorize,
|
|
515
|
+
logBanner,
|
|
516
|
+
logOperationSummary,
|
|
517
|
+
terminalColors
|
|
518
|
+
} from 'hanzio'
|
|
519
|
+
|
|
520
|
+
const log = createCoolLogger()
|
|
521
|
+
log.info('User logged in', { userId: '123' })
|
|
522
|
+
log.warn('Rate limit approaching', { remaining: 10 })
|
|
523
|
+
|
|
524
|
+
const start = performance.now()
|
|
525
|
+
await doSomething()
|
|
526
|
+
logOperationSummary('user.create', performance.now() - start, true)
|
|
527
|
+
|
|
528
|
+
console.log(colorize('OK', 'brightGreen'))
|
|
529
|
+
logBanner('Server started', 'cyan')
|
|
530
|
+
```
|
|
531
|
+
|
|
503
532
|
## Development
|
|
504
533
|
|
|
505
534
|
```bash
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
/** ANSI color codes for terminal output */
|
|
2
|
+
export declare const terminalColors: {
|
|
3
|
+
readonly reset: "\u001B[0m";
|
|
4
|
+
readonly bold: "\u001B[1m";
|
|
5
|
+
readonly dim: "\u001B[2m";
|
|
6
|
+
readonly italic: "\u001B[3m";
|
|
7
|
+
readonly underline: "\u001B[4m";
|
|
8
|
+
readonly black: "\u001B[30m";
|
|
9
|
+
readonly red: "\u001B[31m";
|
|
10
|
+
readonly green: "\u001B[32m";
|
|
11
|
+
readonly yellow: "\u001B[33m";
|
|
12
|
+
readonly blue: "\u001B[34m";
|
|
13
|
+
readonly magenta: "\u001B[35m";
|
|
14
|
+
readonly cyan: "\u001B[36m";
|
|
15
|
+
readonly white: "\u001B[37m";
|
|
16
|
+
readonly gray: "\u001B[90m";
|
|
17
|
+
readonly brightRed: "\u001B[91m";
|
|
18
|
+
readonly brightGreen: "\u001B[92m";
|
|
19
|
+
readonly brightYellow: "\u001B[93m";
|
|
20
|
+
readonly brightBlue: "\u001B[94m";
|
|
21
|
+
readonly brightMagenta: "\u001B[95m";
|
|
22
|
+
readonly brightCyan: "\u001B[96m";
|
|
23
|
+
readonly brightWhite: "\u001B[97m";
|
|
24
|
+
readonly bgBlack: "\u001B[40m";
|
|
25
|
+
readonly bgRed: "\u001B[41m";
|
|
26
|
+
readonly bgGreen: "\u001B[42m";
|
|
27
|
+
readonly bgYellow: "\u001B[43m";
|
|
28
|
+
readonly bgBlue: "\u001B[44m";
|
|
29
|
+
readonly bgMagenta: "\u001B[45m";
|
|
30
|
+
readonly bgCyan: "\u001B[46m";
|
|
31
|
+
readonly bgWhite: "\u001B[47m";
|
|
32
|
+
};
|
|
33
|
+
export type TerminalColor = keyof typeof terminalColors;
|
|
34
|
+
/** Log levels supported by the logger */
|
|
35
|
+
export type LogLevel = 'debug' | 'info' | 'warn' | 'error';
|
|
36
|
+
/** Attributes that can be passed to log methods */
|
|
37
|
+
export type LogAttributes = Record<string, string | number | boolean>;
|
|
38
|
+
/** Log method signature */
|
|
39
|
+
export type LogMethod = (message: string, attributes?: LogAttributes) => void;
|
|
40
|
+
/** Logger interface with colored output */
|
|
41
|
+
export type CoolLogger = {
|
|
42
|
+
debug: LogMethod;
|
|
43
|
+
info: LogMethod;
|
|
44
|
+
warn: LogMethod;
|
|
45
|
+
error: LogMethod;
|
|
46
|
+
};
|
|
47
|
+
/**
|
|
48
|
+
* Options for creating a cool logger
|
|
49
|
+
*/
|
|
50
|
+
export type CoolLoggerOptions = {
|
|
51
|
+
/** Only use colors in development (default: true) */
|
|
52
|
+
colorsInDevOnly?: boolean;
|
|
53
|
+
/** Callback for each log entry (e.g., to add to OpenTelemetry span) */
|
|
54
|
+
onLog?: (level: LogLevel, message: string, attributes?: LogAttributes) => void;
|
|
55
|
+
};
|
|
56
|
+
/**
|
|
57
|
+
* Creates a logger with colorful console output.
|
|
58
|
+
*
|
|
59
|
+
* @example
|
|
60
|
+
* ```ts
|
|
61
|
+
* const logger = createCoolLogger()
|
|
62
|
+
* logger.info('User logged in', { userId: '123' })
|
|
63
|
+
* logger.warn('Rate limit approaching', { remaining: 10 })
|
|
64
|
+
* logger.error('Payment failed', { reason: 'insufficient_funds' })
|
|
65
|
+
* ```
|
|
66
|
+
*/
|
|
67
|
+
export declare function createCoolLogger(options?: CoolLoggerOptions): CoolLogger;
|
|
68
|
+
/**
|
|
69
|
+
* Logs a request/operation summary with colored status and duration.
|
|
70
|
+
* Shows: `operationName ✓ · 12.3ms` (green) or `operationName ✗ · 45.2ms` (red)
|
|
71
|
+
*
|
|
72
|
+
* @example
|
|
73
|
+
* ```ts
|
|
74
|
+
* const start = performance.now()
|
|
75
|
+
* try {
|
|
76
|
+
* await doSomething()
|
|
77
|
+
* logOperationSummary('user.create', performance.now() - start, true)
|
|
78
|
+
* } catch {
|
|
79
|
+
* logOperationSummary('user.create', performance.now() - start, false)
|
|
80
|
+
* }
|
|
81
|
+
* ```
|
|
82
|
+
*/
|
|
83
|
+
export declare function logOperationSummary(operationName: string, durationMs: number, success: boolean, options?: {
|
|
84
|
+
devOnly?: boolean;
|
|
85
|
+
error?: Error | unknown;
|
|
86
|
+
}): void;
|
|
87
|
+
/**
|
|
88
|
+
* Helper to colorize text for console output
|
|
89
|
+
*
|
|
90
|
+
* @example
|
|
91
|
+
* ```ts
|
|
92
|
+
* console.log(colorize('Success!', 'green', 'bold'))
|
|
93
|
+
* console.log(colorize('Warning', 'yellow'))
|
|
94
|
+
* ```
|
|
95
|
+
*/
|
|
96
|
+
export declare function colorize(text: string, color: TerminalColor, ...modifiers: TerminalColor[]): string;
|
|
97
|
+
/**
|
|
98
|
+
* Log a styled box/banner to the console (dev only)
|
|
99
|
+
*
|
|
100
|
+
* @example
|
|
101
|
+
* ```ts
|
|
102
|
+
* logBanner('Server Started', 'green')
|
|
103
|
+
* logBanner('⚠️ Warning', 'yellow')
|
|
104
|
+
* ```
|
|
105
|
+
*/
|
|
106
|
+
export declare function logBanner(text: string, color?: TerminalColor, options?: {
|
|
107
|
+
devOnly?: boolean;
|
|
108
|
+
}): void;
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
// src/cool-console-log/index.ts
|
|
2
|
+
var terminalColors = {
|
|
3
|
+
reset: "\x1B[0m",
|
|
4
|
+
bold: "\x1B[1m",
|
|
5
|
+
dim: "\x1B[2m",
|
|
6
|
+
italic: "\x1B[3m",
|
|
7
|
+
underline: "\x1B[4m",
|
|
8
|
+
black: "\x1B[30m",
|
|
9
|
+
red: "\x1B[31m",
|
|
10
|
+
green: "\x1B[32m",
|
|
11
|
+
yellow: "\x1B[33m",
|
|
12
|
+
blue: "\x1B[34m",
|
|
13
|
+
magenta: "\x1B[35m",
|
|
14
|
+
cyan: "\x1B[36m",
|
|
15
|
+
white: "\x1B[37m",
|
|
16
|
+
gray: "\x1B[90m",
|
|
17
|
+
brightRed: "\x1B[91m",
|
|
18
|
+
brightGreen: "\x1B[92m",
|
|
19
|
+
brightYellow: "\x1B[93m",
|
|
20
|
+
brightBlue: "\x1B[94m",
|
|
21
|
+
brightMagenta: "\x1B[95m",
|
|
22
|
+
brightCyan: "\x1B[96m",
|
|
23
|
+
brightWhite: "\x1B[97m",
|
|
24
|
+
bgBlack: "\x1B[40m",
|
|
25
|
+
bgRed: "\x1B[41m",
|
|
26
|
+
bgGreen: "\x1B[42m",
|
|
27
|
+
bgYellow: "\x1B[43m",
|
|
28
|
+
bgBlue: "\x1B[44m",
|
|
29
|
+
bgMagenta: "\x1B[45m",
|
|
30
|
+
bgCyan: "\x1B[46m",
|
|
31
|
+
bgWhite: "\x1B[47m"
|
|
32
|
+
};
|
|
33
|
+
var levelColors = {
|
|
34
|
+
debug: { badge: terminalColors.gray, text: terminalColors.dim },
|
|
35
|
+
info: { badge: terminalColors.cyan, text: terminalColors.reset },
|
|
36
|
+
warn: { badge: terminalColors.yellow, text: terminalColors.yellow },
|
|
37
|
+
error: { badge: terminalColors.red, text: terminalColors.brightRed }
|
|
38
|
+
};
|
|
39
|
+
function formatAttributes(attrs) {
|
|
40
|
+
if (!attrs || Object.keys(attrs).length === 0)
|
|
41
|
+
return "";
|
|
42
|
+
const formatted = Object.entries(attrs).map(([k, v]) => `${terminalColors.dim}${k}=${terminalColors.reset}${v}`).join(" ");
|
|
43
|
+
return ` ${formatted}`;
|
|
44
|
+
}
|
|
45
|
+
function createCoolLogger(options = {}) {
|
|
46
|
+
const { colorsInDevOnly = true, onLog } = options;
|
|
47
|
+
const isDev = true;
|
|
48
|
+
const useColors = colorsInDevOnly ? isDev : true;
|
|
49
|
+
const createLogMethod = (level) => (message, attributes) => {
|
|
50
|
+
onLog?.(level, message, attributes);
|
|
51
|
+
if (level === "debug" && !isDev)
|
|
52
|
+
return;
|
|
53
|
+
if (useColors) {
|
|
54
|
+
const { badge, text } = levelColors[level];
|
|
55
|
+
const label = level.toUpperCase().padEnd(5);
|
|
56
|
+
const attrStr = formatAttributes(attributes);
|
|
57
|
+
console.log(`${badge}[${label}]${terminalColors.reset} ${text}${message}${terminalColors.reset}${attrStr}`);
|
|
58
|
+
} else {
|
|
59
|
+
console[level](`[${level.toUpperCase()}]`, message, attributes ?? "");
|
|
60
|
+
}
|
|
61
|
+
};
|
|
62
|
+
return {
|
|
63
|
+
debug: createLogMethod("debug"),
|
|
64
|
+
info: createLogMethod("info"),
|
|
65
|
+
warn: createLogMethod("warn"),
|
|
66
|
+
error: createLogMethod("error")
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
function logOperationSummary(operationName, durationMs, success, options = {}) {
|
|
70
|
+
const { devOnly = true, error } = options;
|
|
71
|
+
const isDev = true;
|
|
72
|
+
if (devOnly && !isDev)
|
|
73
|
+
return;
|
|
74
|
+
const c = terminalColors;
|
|
75
|
+
const statusIcon = success ? `${c.brightGreen}✓${c.reset}` : `${c.brightRed}✗${c.reset}`;
|
|
76
|
+
const statusColor = success ? c.green : c.red;
|
|
77
|
+
const durationStr = `${c.dim}${durationMs.toFixed(1)}ms${c.reset}`;
|
|
78
|
+
const errorMessage = error ? ` ${c.dim}·${c.reset} ${c.red}${error instanceof Error ? error.message : String(error)}${c.reset}` : "";
|
|
79
|
+
console.log(`${statusColor}${c.bold}${operationName}${c.reset} ${statusIcon} ${c.dim}·${c.reset} ${durationStr}${errorMessage}`);
|
|
80
|
+
}
|
|
81
|
+
function colorize(text, color, ...modifiers) {
|
|
82
|
+
const isDev = true;
|
|
83
|
+
if (!isDev)
|
|
84
|
+
return text;
|
|
85
|
+
const colorCodes = [
|
|
86
|
+
terminalColors[color],
|
|
87
|
+
...modifiers.map((m) => terminalColors[m])
|
|
88
|
+
].join("");
|
|
89
|
+
return `${colorCodes}${text}${terminalColors.reset}`;
|
|
90
|
+
}
|
|
91
|
+
function logBanner(text, color = "cyan", options = {}) {
|
|
92
|
+
const { devOnly = true } = options;
|
|
93
|
+
const isDev = true;
|
|
94
|
+
if (devOnly && !isDev) {
|
|
95
|
+
console.log(`=== ${text} ===`);
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
const c = terminalColors;
|
|
99
|
+
const padding = 2;
|
|
100
|
+
const line = "═".repeat(text.length + padding * 2);
|
|
101
|
+
console.log(`${c[color]}╔${line}╗${c.reset}`);
|
|
102
|
+
console.log(`${c[color]}║${" ".repeat(padding)}${c.bold}${text}${c.reset}${c[color]}${" ".repeat(padding)}║${c.reset}`);
|
|
103
|
+
console.log(`${c[color]}╚${line}╝${c.reset}`);
|
|
104
|
+
}
|
|
105
|
+
export {
|
|
106
|
+
terminalColors,
|
|
107
|
+
logOperationSummary,
|
|
108
|
+
logBanner,
|
|
109
|
+
createCoolLogger,
|
|
110
|
+
colorize
|
|
111
|
+
};
|
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
|
@@ -1514,6 +1514,110 @@ function formatPeriodLabel(start, end, includeDay = false) {
|
|
|
1514
1514
|
function getDomainFaviconUrl(domain, size = 128) {
|
|
1515
1515
|
return `https://www.google.com/s2/favicons?domain=${encodeURIComponent(domain)}&sz=${size}`;
|
|
1516
1516
|
}
|
|
1517
|
+
// src/cool-console-log/index.ts
|
|
1518
|
+
var terminalColors = {
|
|
1519
|
+
reset: "\x1B[0m",
|
|
1520
|
+
bold: "\x1B[1m",
|
|
1521
|
+
dim: "\x1B[2m",
|
|
1522
|
+
italic: "\x1B[3m",
|
|
1523
|
+
underline: "\x1B[4m",
|
|
1524
|
+
black: "\x1B[30m",
|
|
1525
|
+
red: "\x1B[31m",
|
|
1526
|
+
green: "\x1B[32m",
|
|
1527
|
+
yellow: "\x1B[33m",
|
|
1528
|
+
blue: "\x1B[34m",
|
|
1529
|
+
magenta: "\x1B[35m",
|
|
1530
|
+
cyan: "\x1B[36m",
|
|
1531
|
+
white: "\x1B[37m",
|
|
1532
|
+
gray: "\x1B[90m",
|
|
1533
|
+
brightRed: "\x1B[91m",
|
|
1534
|
+
brightGreen: "\x1B[92m",
|
|
1535
|
+
brightYellow: "\x1B[93m",
|
|
1536
|
+
brightBlue: "\x1B[94m",
|
|
1537
|
+
brightMagenta: "\x1B[95m",
|
|
1538
|
+
brightCyan: "\x1B[96m",
|
|
1539
|
+
brightWhite: "\x1B[97m",
|
|
1540
|
+
bgBlack: "\x1B[40m",
|
|
1541
|
+
bgRed: "\x1B[41m",
|
|
1542
|
+
bgGreen: "\x1B[42m",
|
|
1543
|
+
bgYellow: "\x1B[43m",
|
|
1544
|
+
bgBlue: "\x1B[44m",
|
|
1545
|
+
bgMagenta: "\x1B[45m",
|
|
1546
|
+
bgCyan: "\x1B[46m",
|
|
1547
|
+
bgWhite: "\x1B[47m"
|
|
1548
|
+
};
|
|
1549
|
+
var levelColors = {
|
|
1550
|
+
debug: { badge: terminalColors.gray, text: terminalColors.dim },
|
|
1551
|
+
info: { badge: terminalColors.cyan, text: terminalColors.reset },
|
|
1552
|
+
warn: { badge: terminalColors.yellow, text: terminalColors.yellow },
|
|
1553
|
+
error: { badge: terminalColors.red, text: terminalColors.brightRed }
|
|
1554
|
+
};
|
|
1555
|
+
function formatAttributes(attrs) {
|
|
1556
|
+
if (!attrs || Object.keys(attrs).length === 0)
|
|
1557
|
+
return "";
|
|
1558
|
+
const formatted = Object.entries(attrs).map(([k, v]) => `${terminalColors.dim}${k}=${terminalColors.reset}${v}`).join(" ");
|
|
1559
|
+
return ` ${formatted}`;
|
|
1560
|
+
}
|
|
1561
|
+
function createCoolLogger(options = {}) {
|
|
1562
|
+
const { colorsInDevOnly = true, onLog } = options;
|
|
1563
|
+
const isDev = true;
|
|
1564
|
+
const useColors = colorsInDevOnly ? isDev : true;
|
|
1565
|
+
const createLogMethod = (level) => (message, attributes) => {
|
|
1566
|
+
onLog?.(level, message, attributes);
|
|
1567
|
+
if (level === "debug" && !isDev)
|
|
1568
|
+
return;
|
|
1569
|
+
if (useColors) {
|
|
1570
|
+
const { badge, text } = levelColors[level];
|
|
1571
|
+
const label = level.toUpperCase().padEnd(5);
|
|
1572
|
+
const attrStr = formatAttributes(attributes);
|
|
1573
|
+
console.log(`${badge}[${label}]${terminalColors.reset} ${text}${message}${terminalColors.reset}${attrStr}`);
|
|
1574
|
+
} else {
|
|
1575
|
+
console[level](`[${level.toUpperCase()}]`, message, attributes ?? "");
|
|
1576
|
+
}
|
|
1577
|
+
};
|
|
1578
|
+
return {
|
|
1579
|
+
debug: createLogMethod("debug"),
|
|
1580
|
+
info: createLogMethod("info"),
|
|
1581
|
+
warn: createLogMethod("warn"),
|
|
1582
|
+
error: createLogMethod("error")
|
|
1583
|
+
};
|
|
1584
|
+
}
|
|
1585
|
+
function logOperationSummary(operationName, durationMs, success, options = {}) {
|
|
1586
|
+
const { devOnly = true, error } = options;
|
|
1587
|
+
const isDev = true;
|
|
1588
|
+
if (devOnly && !isDev)
|
|
1589
|
+
return;
|
|
1590
|
+
const c = terminalColors;
|
|
1591
|
+
const statusIcon = success ? `${c.brightGreen}✓${c.reset}` : `${c.brightRed}✗${c.reset}`;
|
|
1592
|
+
const statusColor = success ? c.green : c.red;
|
|
1593
|
+
const durationStr = `${c.dim}${durationMs.toFixed(1)}ms${c.reset}`;
|
|
1594
|
+
const errorMessage = error ? ` ${c.dim}·${c.reset} ${c.red}${error instanceof Error ? error.message : String(error)}${c.reset}` : "";
|
|
1595
|
+
console.log(`${statusColor}${c.bold}${operationName}${c.reset} ${statusIcon} ${c.dim}·${c.reset} ${durationStr}${errorMessage}`);
|
|
1596
|
+
}
|
|
1597
|
+
function colorize(text, color, ...modifiers) {
|
|
1598
|
+
const isDev = true;
|
|
1599
|
+
if (!isDev)
|
|
1600
|
+
return text;
|
|
1601
|
+
const colorCodes = [
|
|
1602
|
+
terminalColors[color],
|
|
1603
|
+
...modifiers.map((m) => terminalColors[m])
|
|
1604
|
+
].join("");
|
|
1605
|
+
return `${colorCodes}${text}${terminalColors.reset}`;
|
|
1606
|
+
}
|
|
1607
|
+
function logBanner(text, color = "cyan", options = {}) {
|
|
1608
|
+
const { devOnly = true } = options;
|
|
1609
|
+
const isDev = true;
|
|
1610
|
+
if (devOnly && !isDev) {
|
|
1611
|
+
console.log(`=== ${text} ===`);
|
|
1612
|
+
return;
|
|
1613
|
+
}
|
|
1614
|
+
const c = terminalColors;
|
|
1615
|
+
const padding = 2;
|
|
1616
|
+
const line = "═".repeat(text.length + padding * 2);
|
|
1617
|
+
console.log(`${c[color]}╔${line}╗${c.reset}`);
|
|
1618
|
+
console.log(`${c[color]}║${" ".repeat(padding)}${c.bold}${text}${c.reset}${c[color]}${" ".repeat(padding)}║${c.reset}`);
|
|
1619
|
+
console.log(`${c[color]}╚${line}╝${c.reset}`);
|
|
1620
|
+
}
|
|
1517
1621
|
export {
|
|
1518
1622
|
zodToTypeString,
|
|
1519
1623
|
zodToExample,
|
|
@@ -1524,6 +1628,7 @@ export {
|
|
|
1524
1628
|
typedSwitch,
|
|
1525
1629
|
tryCatch,
|
|
1526
1630
|
toGteLteFilter,
|
|
1631
|
+
terminalColors,
|
|
1527
1632
|
slugify,
|
|
1528
1633
|
seriesColorKey,
|
|
1529
1634
|
resolveSeriesColorMap,
|
|
@@ -1531,6 +1636,8 @@ export {
|
|
|
1531
1636
|
pickItemsInArray,
|
|
1532
1637
|
partition,
|
|
1533
1638
|
normalizeSingleOrArray,
|
|
1639
|
+
logOperationSummary,
|
|
1640
|
+
logBanner,
|
|
1534
1641
|
linearRegressionTrend,
|
|
1535
1642
|
keyBy,
|
|
1536
1643
|
jsonToZodSchemaMapper,
|
|
@@ -1549,9 +1656,11 @@ export {
|
|
|
1549
1656
|
extractNumber,
|
|
1550
1657
|
createZId,
|
|
1551
1658
|
createResolveColorsByKeys,
|
|
1659
|
+
createCoolLogger,
|
|
1552
1660
|
createApiWrapper,
|
|
1553
1661
|
createApiClient,
|
|
1554
1662
|
compact,
|
|
1663
|
+
colorize,
|
|
1555
1664
|
chunkArray,
|
|
1556
1665
|
cacheFunction,
|
|
1557
1666
|
backgroundPromiseSync,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "hanzio",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.2",
|
|
4
4
|
"description": "A set of utility functions for TypeScript",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"module": "./dist/index.js",
|
|
@@ -34,42 +34,6 @@
|
|
|
34
34
|
"bun": "./src/index.ts",
|
|
35
35
|
"import": "./dist/index.js",
|
|
36
36
|
"default": "./dist/index.js"
|
|
37
|
-
},
|
|
38
|
-
"./zod": {
|
|
39
|
-
"types": "./dist/zod/index.d.ts",
|
|
40
|
-
"bun": "./src/zod/index.ts",
|
|
41
|
-
"import": "./dist/zod/index.js",
|
|
42
|
-
"default": "./dist/zod/index.js"
|
|
43
|
-
},
|
|
44
|
-
"./api-wrapper": {
|
|
45
|
-
"types": "./dist/api-wrapper/index.d.ts",
|
|
46
|
-
"bun": "./src/api-wrapper/index.ts",
|
|
47
|
-
"import": "./dist/api-wrapper/index.js",
|
|
48
|
-
"default": "./dist/api-wrapper/index.js"
|
|
49
|
-
},
|
|
50
|
-
"./jwt": {
|
|
51
|
-
"types": "./dist/jwt/index.d.ts",
|
|
52
|
-
"bun": "./src/jwt/index.ts",
|
|
53
|
-
"import": "./dist/jwt/index.js",
|
|
54
|
-
"default": "./dist/jwt/index.js"
|
|
55
|
-
},
|
|
56
|
-
"./gzip": {
|
|
57
|
-
"types": "./dist/gzip/index.d.ts",
|
|
58
|
-
"bun": "./src/gzip/index.ts",
|
|
59
|
-
"import": "./dist/gzip/index.js",
|
|
60
|
-
"default": "./dist/gzip/index.js"
|
|
61
|
-
},
|
|
62
|
-
"./sitemap": {
|
|
63
|
-
"types": "./dist/sitemap/index.d.ts",
|
|
64
|
-
"bun": "./src/sitemap/index.ts",
|
|
65
|
-
"import": "./dist/sitemap/index.js",
|
|
66
|
-
"default": "./dist/sitemap/index.js"
|
|
67
|
-
},
|
|
68
|
-
"./p-queue": {
|
|
69
|
-
"types": "./dist/p-queue/index.d.ts",
|
|
70
|
-
"bun": "./src/p-queue/index.ts",
|
|
71
|
-
"import": "./dist/p-queue/index.js",
|
|
72
|
-
"default": "./dist/p-queue/index.js"
|
|
73
37
|
}
|
|
74
38
|
},
|
|
75
39
|
"scripts": {
|
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
2
|
+
// Cool Console Log - Colorful Terminal Logging Utility
|
|
3
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
4
|
+
|
|
5
|
+
/** ANSI color codes for terminal output */
|
|
6
|
+
export const terminalColors = {
|
|
7
|
+
reset: '\x1b[0m',
|
|
8
|
+
bold: '\x1b[1m',
|
|
9
|
+
dim: '\x1b[2m',
|
|
10
|
+
italic: '\x1b[3m',
|
|
11
|
+
underline: '\x1b[4m',
|
|
12
|
+
// Foreground colors
|
|
13
|
+
black: '\x1b[30m',
|
|
14
|
+
red: '\x1b[31m',
|
|
15
|
+
green: '\x1b[32m',
|
|
16
|
+
yellow: '\x1b[33m',
|
|
17
|
+
blue: '\x1b[34m',
|
|
18
|
+
magenta: '\x1b[35m',
|
|
19
|
+
cyan: '\x1b[36m',
|
|
20
|
+
white: '\x1b[37m',
|
|
21
|
+
gray: '\x1b[90m',
|
|
22
|
+
// Bright variants
|
|
23
|
+
brightRed: '\x1b[91m',
|
|
24
|
+
brightGreen: '\x1b[92m',
|
|
25
|
+
brightYellow: '\x1b[93m',
|
|
26
|
+
brightBlue: '\x1b[94m',
|
|
27
|
+
brightMagenta: '\x1b[95m',
|
|
28
|
+
brightCyan: '\x1b[96m',
|
|
29
|
+
brightWhite: '\x1b[97m',
|
|
30
|
+
// Background colors
|
|
31
|
+
bgBlack: '\x1b[40m',
|
|
32
|
+
bgRed: '\x1b[41m',
|
|
33
|
+
bgGreen: '\x1b[42m',
|
|
34
|
+
bgYellow: '\x1b[43m',
|
|
35
|
+
bgBlue: '\x1b[44m',
|
|
36
|
+
bgMagenta: '\x1b[45m',
|
|
37
|
+
bgCyan: '\x1b[46m',
|
|
38
|
+
bgWhite: '\x1b[47m'
|
|
39
|
+
} as const
|
|
40
|
+
|
|
41
|
+
export type TerminalColor = keyof typeof terminalColors
|
|
42
|
+
|
|
43
|
+
/** Log levels supported by the logger */
|
|
44
|
+
export type LogLevel = 'debug' | 'info' | 'warn' | 'error'
|
|
45
|
+
|
|
46
|
+
/** Attributes that can be passed to log methods */
|
|
47
|
+
export type LogAttributes = Record<string, string | number | boolean>
|
|
48
|
+
|
|
49
|
+
/** Log method signature */
|
|
50
|
+
export type LogMethod = (message: string, attributes?: LogAttributes) => void
|
|
51
|
+
|
|
52
|
+
/** Logger interface with colored output */
|
|
53
|
+
export type CoolLogger = {
|
|
54
|
+
debug: LogMethod
|
|
55
|
+
info: LogMethod
|
|
56
|
+
warn: LogMethod
|
|
57
|
+
error: LogMethod
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/** Color config per log level */
|
|
61
|
+
const levelColors: Record<LogLevel, { badge: string; text: string }> = {
|
|
62
|
+
debug: { badge: terminalColors.gray, text: terminalColors.dim },
|
|
63
|
+
info: { badge: terminalColors.cyan, text: terminalColors.reset },
|
|
64
|
+
warn: { badge: terminalColors.yellow, text: terminalColors.yellow },
|
|
65
|
+
error: { badge: terminalColors.red, text: terminalColors.brightRed }
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/** Format attributes as a compact colored string */
|
|
69
|
+
function formatAttributes(attrs?: LogAttributes): string {
|
|
70
|
+
if (!attrs || Object.keys(attrs).length === 0) return ''
|
|
71
|
+
const formatted = Object.entries(attrs)
|
|
72
|
+
.map(([k, v]) => `${terminalColors.dim}${k}=${terminalColors.reset}${v}`)
|
|
73
|
+
.join(' ')
|
|
74
|
+
return ` ${formatted}`
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Options for creating a cool logger
|
|
79
|
+
*/
|
|
80
|
+
export type CoolLoggerOptions = {
|
|
81
|
+
/** Only use colors in development (default: true) */
|
|
82
|
+
colorsInDevOnly?: boolean
|
|
83
|
+
/** Callback for each log entry (e.g., to add to OpenTelemetry span) */
|
|
84
|
+
onLog?: (level: LogLevel, message: string, attributes?: LogAttributes) => void
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Creates a logger with colorful console output.
|
|
89
|
+
*
|
|
90
|
+
* @example
|
|
91
|
+
* ```ts
|
|
92
|
+
* const logger = createCoolLogger()
|
|
93
|
+
* logger.info('User logged in', { userId: '123' })
|
|
94
|
+
* logger.warn('Rate limit approaching', { remaining: 10 })
|
|
95
|
+
* logger.error('Payment failed', { reason: 'insufficient_funds' })
|
|
96
|
+
* ```
|
|
97
|
+
*/
|
|
98
|
+
export function createCoolLogger(options: CoolLoggerOptions = {}): CoolLogger {
|
|
99
|
+
const { colorsInDevOnly = true, onLog } = options
|
|
100
|
+
const isDev = process.env.NODE_ENV !== 'production'
|
|
101
|
+
const useColors = colorsInDevOnly ? isDev : true
|
|
102
|
+
|
|
103
|
+
const createLogMethod =
|
|
104
|
+
(level: LogLevel): LogMethod =>
|
|
105
|
+
(message, attributes) => {
|
|
106
|
+
// Call the onLog callback if provided
|
|
107
|
+
onLog?.(level, message, attributes)
|
|
108
|
+
|
|
109
|
+
// Skip debug logs in production
|
|
110
|
+
if (level === 'debug' && !isDev) return
|
|
111
|
+
|
|
112
|
+
if (useColors) {
|
|
113
|
+
const { badge, text } = levelColors[level]
|
|
114
|
+
const label = level.toUpperCase().padEnd(5)
|
|
115
|
+
const attrStr = formatAttributes(attributes)
|
|
116
|
+
console.log(
|
|
117
|
+
`${badge}[${label}]${terminalColors.reset} ${text}${message}${terminalColors.reset}${attrStr}`
|
|
118
|
+
)
|
|
119
|
+
} else {
|
|
120
|
+
console[level](`[${level.toUpperCase()}]`, message, attributes ?? '')
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
return {
|
|
125
|
+
debug: createLogMethod('debug'),
|
|
126
|
+
info: createLogMethod('info'),
|
|
127
|
+
warn: createLogMethod('warn'),
|
|
128
|
+
error: createLogMethod('error')
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Logs a request/operation summary with colored status and duration.
|
|
134
|
+
* Shows: `operationName ✓ · 12.3ms` (green) or `operationName ✗ · 45.2ms` (red)
|
|
135
|
+
*
|
|
136
|
+
* @example
|
|
137
|
+
* ```ts
|
|
138
|
+
* const start = performance.now()
|
|
139
|
+
* try {
|
|
140
|
+
* await doSomething()
|
|
141
|
+
* logOperationSummary('user.create', performance.now() - start, true)
|
|
142
|
+
* } catch {
|
|
143
|
+
* logOperationSummary('user.create', performance.now() - start, false)
|
|
144
|
+
* }
|
|
145
|
+
* ```
|
|
146
|
+
*/
|
|
147
|
+
export function logOperationSummary(
|
|
148
|
+
operationName: string,
|
|
149
|
+
durationMs: number,
|
|
150
|
+
success: boolean,
|
|
151
|
+
options: { devOnly?: boolean; error?: Error | unknown } = {}
|
|
152
|
+
): void {
|
|
153
|
+
const { devOnly = true, error } = options
|
|
154
|
+
const isDev = process.env.NODE_ENV !== 'production'
|
|
155
|
+
|
|
156
|
+
if (devOnly && !isDev) return
|
|
157
|
+
|
|
158
|
+
const c = terminalColors
|
|
159
|
+
const statusIcon = success
|
|
160
|
+
? `${c.brightGreen}✓${c.reset}`
|
|
161
|
+
: `${c.brightRed}✗${c.reset}`
|
|
162
|
+
const statusColor = success ? c.green : c.red
|
|
163
|
+
const durationStr = `${c.dim}${durationMs.toFixed(1)}ms${c.reset}`
|
|
164
|
+
|
|
165
|
+
const errorMessage = error
|
|
166
|
+
? ` ${c.dim}·${c.reset} ${c.red}${error instanceof Error ? error.message : String(error)}${c.reset}`
|
|
167
|
+
: ''
|
|
168
|
+
|
|
169
|
+
console.log(
|
|
170
|
+
`${statusColor}${c.bold}${operationName}${c.reset} ${statusIcon} ${c.dim}·${c.reset} ${durationStr}${errorMessage}`
|
|
171
|
+
)
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Helper to colorize text for console output
|
|
176
|
+
*
|
|
177
|
+
* @example
|
|
178
|
+
* ```ts
|
|
179
|
+
* console.log(colorize('Success!', 'green', 'bold'))
|
|
180
|
+
* console.log(colorize('Warning', 'yellow'))
|
|
181
|
+
* ```
|
|
182
|
+
*/
|
|
183
|
+
export function colorize(
|
|
184
|
+
text: string,
|
|
185
|
+
color: TerminalColor,
|
|
186
|
+
...modifiers: TerminalColor[]
|
|
187
|
+
): string {
|
|
188
|
+
const isDev = process.env.NODE_ENV !== 'production'
|
|
189
|
+
if (!isDev) return text
|
|
190
|
+
|
|
191
|
+
const colorCodes = [
|
|
192
|
+
terminalColors[color],
|
|
193
|
+
...modifiers.map((m) => terminalColors[m])
|
|
194
|
+
].join('')
|
|
195
|
+
return `${colorCodes}${text}${terminalColors.reset}`
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* Log a styled box/banner to the console (dev only)
|
|
200
|
+
*
|
|
201
|
+
* @example
|
|
202
|
+
* ```ts
|
|
203
|
+
* logBanner('Server Started', 'green')
|
|
204
|
+
* logBanner('⚠️ Warning', 'yellow')
|
|
205
|
+
* ```
|
|
206
|
+
*/
|
|
207
|
+
export function logBanner(
|
|
208
|
+
text: string,
|
|
209
|
+
color: TerminalColor = 'cyan',
|
|
210
|
+
options: { devOnly?: boolean } = {}
|
|
211
|
+
): void {
|
|
212
|
+
const { devOnly = true } = options
|
|
213
|
+
const isDev = process.env.NODE_ENV !== 'production'
|
|
214
|
+
|
|
215
|
+
if (devOnly && !isDev) {
|
|
216
|
+
console.log(`=== ${text} ===`)
|
|
217
|
+
return
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
const c = terminalColors
|
|
221
|
+
const padding = 2
|
|
222
|
+
const line = '═'.repeat(text.length + padding * 2)
|
|
223
|
+
|
|
224
|
+
console.log(`${c[color]}╔${line}╗${c.reset}`)
|
|
225
|
+
console.log(
|
|
226
|
+
`${c[color]}║${' '.repeat(padding)}${c.bold}${text}${c.reset}${c[color]}${' '.repeat(padding)}║${c.reset}`
|
|
227
|
+
)
|
|
228
|
+
console.log(`${c[color]}╚${line}╝${c.reset}`)
|
|
229
|
+
}
|
package/src/index.ts
CHANGED