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 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 queues.
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
@@ -10,3 +10,4 @@ export * from './url';
10
10
  export * from './zod';
11
11
  export * from './api-wrapper';
12
12
  export * from './p-queue';
13
+ export * from './cool-console-log';
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.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
@@ -10,3 +10,4 @@ export * from './url'
10
10
  export * from './zod'
11
11
  export * from './api-wrapper'
12
12
  export * from './p-queue'
13
+ export * from './cool-console-log'