@meadown/logger 1.8.10 → 1.9.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 (142) hide show
  1. package/README.md +149 -51
  2. package/dist/cjs/{config.js → config/index.js} +1 -1
  3. package/dist/cjs/{constants.d.ts → const/index.d.ts} +0 -5
  4. package/dist/cjs/{constants.js → const/index.js} +2 -8
  5. package/dist/cjs/{caller → domain/caller}/getCaller.js +1 -1
  6. package/dist/cjs/{colors → domain/colors}/color.js +1 -1
  7. package/dist/cjs/{decorations → domain/decorations}/link.js +1 -1
  8. package/dist/cjs/{terminal → domain/terminal}/isTTY.js +1 -1
  9. package/dist/cjs/{time → domain/time}/getTimeStamp.js +1 -1
  10. package/dist/cjs/domain/write/helpers/buildContext.d.ts +16 -0
  11. package/dist/cjs/domain/write/helpers/buildContext.js +40 -0
  12. package/dist/cjs/{core/writeLog → domain/write/helpers}/formatLocation.js +3 -2
  13. package/dist/cjs/domain/write/helpers/index.d.ts +4 -0
  14. package/dist/cjs/domain/write/helpers/index.js +20 -0
  15. package/dist/cjs/{core/writeLog → domain/write/helpers}/renderMessage.js +6 -5
  16. package/dist/cjs/{core/writeLog → domain/write/helpers}/visibleLines.js +3 -3
  17. package/dist/cjs/domain/write/index.d.ts +2 -0
  18. package/dist/cjs/domain/write/index.js +14 -0
  19. package/dist/{core/writeLog/index.d.ts → cjs/domain/write/writeLog.d.ts} +2 -3
  20. package/dist/cjs/domain/write/writeLog.js +24 -0
  21. package/dist/cjs/features/logger/index.d.ts +1 -0
  22. package/dist/cjs/features/logger/index.js +21 -0
  23. package/dist/cjs/features/logger-error/index.d.ts +1 -0
  24. package/dist/cjs/features/logger-error/index.js +21 -0
  25. package/dist/cjs/features/logger-group/createGroup.d.ts +11 -0
  26. package/dist/cjs/features/logger-group/createGroup.js +21 -0
  27. package/dist/cjs/features/logger-group/index.d.ts +1 -0
  28. package/dist/cjs/features/logger-group/index.js +14 -0
  29. package/dist/cjs/features/logger-group/writeGroup.d.ts +8 -0
  30. package/dist/cjs/features/logger-group/writeGroup.js +20 -0
  31. package/dist/cjs/features/logger-max-lines/index.d.ts +1 -0
  32. package/dist/cjs/features/logger-max-lines/index.js +12 -0
  33. package/dist/cjs/features/logger-tap/createTap.d.ts +5 -0
  34. package/dist/cjs/features/logger-tap/createTap.js +33 -0
  35. package/dist/cjs/features/logger-tap/index.d.ts +1 -0
  36. package/dist/cjs/features/logger-tap/index.js +14 -0
  37. package/dist/cjs/features/logger-tap/tapAsync/helpers/buildBlock.d.ts +19 -0
  38. package/dist/cjs/features/logger-tap/tapAsync/helpers/buildBlock.js +56 -0
  39. package/dist/cjs/features/logger-tap/tapAsync/helpers/format.d.ts +4 -0
  40. package/dist/cjs/features/logger-tap/tapAsync/helpers/format.js +30 -0
  41. package/dist/cjs/features/logger-tap/tapAsync/helpers/index.d.ts +4 -0
  42. package/dist/cjs/features/logger-tap/tapAsync/helpers/index.js +20 -0
  43. package/dist/cjs/features/logger-tap/tapAsync/helpers/isThenable.d.ts +2 -0
  44. package/dist/cjs/features/logger-tap/tapAsync/helpers/isThenable.js +15 -0
  45. package/dist/cjs/features/logger-tap/tapAsync/helpers/response.d.ts +21 -0
  46. package/dist/cjs/features/logger-tap/tapAsync/helpers/response.js +62 -0
  47. package/dist/cjs/features/logger-tap/tapAsync/index.d.ts +2 -0
  48. package/dist/cjs/features/logger-tap/tapAsync/index.js +13 -0
  49. package/dist/cjs/{tap → features/logger-tap/tapAsync}/tapAsync.d.ts +1 -3
  50. package/dist/cjs/features/logger-tap/tapAsync/tapAsync.js +95 -0
  51. package/dist/cjs/features/logger-warn/index.d.ts +1 -0
  52. package/dist/cjs/features/logger-warn/index.js +21 -0
  53. package/dist/cjs/index.d.ts +63 -18
  54. package/dist/cjs/index.js +29 -15
  55. package/dist/cjs/types/index.d.ts +2 -0
  56. package/dist/cjs/types/index.js +8 -0
  57. package/dist/{config.js → config/index.js} +1 -1
  58. package/dist/{constants.d.ts → const/index.d.ts} +0 -5
  59. package/dist/{constants.js → const/index.js} +1 -7
  60. package/dist/{caller → domain/caller}/getCaller.js +1 -1
  61. package/dist/{colors → domain/colors}/color.js +1 -1
  62. package/dist/{decorations → domain/decorations}/link.js +1 -1
  63. package/dist/{terminal → domain/terminal}/isTTY.js +1 -1
  64. package/dist/{time → domain/time}/getTimeStamp.js +1 -1
  65. package/dist/domain/write/helpers/buildContext.d.ts +16 -0
  66. package/dist/domain/write/helpers/buildContext.js +33 -0
  67. package/dist/{core/writeLog → domain/write/helpers}/formatLocation.js +3 -2
  68. package/dist/domain/write/helpers/index.d.ts +4 -0
  69. package/dist/domain/write/helpers/index.js +10 -0
  70. package/dist/{core/writeLog → domain/write/helpers}/renderMessage.js +5 -4
  71. package/dist/{core/writeLog → domain/write/helpers}/visibleLines.js +2 -2
  72. package/dist/domain/write/index.d.ts +2 -0
  73. package/dist/domain/write/index.js +8 -0
  74. package/dist/{cjs/core/writeLog/index.d.ts → domain/write/writeLog.d.ts} +2 -3
  75. package/dist/domain/write/writeLog.js +21 -0
  76. package/dist/features/logger/index.d.ts +1 -0
  77. package/dist/features/logger/index.js +15 -0
  78. package/dist/features/logger-error/index.d.ts +1 -0
  79. package/dist/features/logger-error/index.js +15 -0
  80. package/dist/features/logger-group/createGroup.d.ts +11 -0
  81. package/dist/features/logger-group/createGroup.js +15 -0
  82. package/dist/features/logger-group/index.d.ts +1 -0
  83. package/dist/features/logger-group/index.js +7 -0
  84. package/dist/features/logger-group/writeGroup.d.ts +8 -0
  85. package/dist/features/logger-group/writeGroup.js +17 -0
  86. package/dist/features/logger-max-lines/index.d.ts +1 -0
  87. package/dist/features/logger-max-lines/index.js +7 -0
  88. package/dist/features/logger-tap/createTap.d.ts +5 -0
  89. package/dist/features/logger-tap/createTap.js +27 -0
  90. package/dist/features/logger-tap/index.d.ts +1 -0
  91. package/dist/features/logger-tap/index.js +7 -0
  92. package/dist/features/logger-tap/tapAsync/helpers/buildBlock.d.ts +19 -0
  93. package/dist/features/logger-tap/tapAsync/helpers/buildBlock.js +53 -0
  94. package/dist/features/logger-tap/tapAsync/helpers/format.d.ts +4 -0
  95. package/dist/features/logger-tap/tapAsync/helpers/format.js +26 -0
  96. package/dist/features/logger-tap/tapAsync/helpers/index.d.ts +4 -0
  97. package/dist/features/logger-tap/tapAsync/helpers/index.js +10 -0
  98. package/dist/features/logger-tap/tapAsync/helpers/isThenable.d.ts +2 -0
  99. package/dist/features/logger-tap/tapAsync/helpers/isThenable.js +12 -0
  100. package/dist/features/logger-tap/tapAsync/helpers/response.d.ts +21 -0
  101. package/dist/features/logger-tap/tapAsync/helpers/response.js +57 -0
  102. package/dist/features/logger-tap/tapAsync/index.d.ts +2 -0
  103. package/dist/features/logger-tap/tapAsync/index.js +8 -0
  104. package/dist/{tap → features/logger-tap/tapAsync}/tapAsync.d.ts +1 -3
  105. package/dist/features/logger-tap/tapAsync/tapAsync.js +92 -0
  106. package/dist/features/logger-warn/index.d.ts +1 -0
  107. package/dist/features/logger-warn/index.js +15 -0
  108. package/dist/index.d.ts +63 -18
  109. package/dist/index.js +29 -15
  110. package/dist/types/index.d.ts +2 -0
  111. package/dist/types/index.js +7 -0
  112. package/package.json +7 -4
  113. package/dist/cjs/core/createLog.d.ts +0 -8
  114. package/dist/cjs/core/createLog.js +0 -29
  115. package/dist/cjs/core/writeLog/index.js +0 -48
  116. package/dist/cjs/tap/createTap.d.ts +0 -18
  117. package/dist/cjs/tap/createTap.js +0 -51
  118. package/dist/cjs/tap/tapAsync.js +0 -192
  119. package/dist/core/createLog.d.ts +0 -8
  120. package/dist/core/createLog.js +0 -23
  121. package/dist/core/writeLog/index.js +0 -39
  122. package/dist/tap/createTap.d.ts +0 -18
  123. package/dist/tap/createTap.js +0 -45
  124. package/dist/tap/tapAsync.js +0 -188
  125. /package/dist/cjs/{config.d.ts → config/index.d.ts} +0 -0
  126. /package/dist/{caller → cjs/domain/caller}/getCaller.d.ts +0 -0
  127. /package/dist/cjs/{colors → domain/colors}/color.d.ts +0 -0
  128. /package/dist/cjs/{decorations → domain/decorations}/link.d.ts +0 -0
  129. /package/dist/cjs/{terminal → domain/terminal}/isTTY.d.ts +0 -0
  130. /package/dist/cjs/{time → domain/time}/getTimeStamp.d.ts +0 -0
  131. /package/dist/cjs/{core/writeLog → domain/write/helpers}/formatLocation.d.ts +0 -0
  132. /package/dist/cjs/{core/writeLog → domain/write/helpers}/renderMessage.d.ts +0 -0
  133. /package/dist/cjs/{core/writeLog → domain/write/helpers}/visibleLines.d.ts +0 -0
  134. /package/dist/{config.d.ts → config/index.d.ts} +0 -0
  135. /package/dist/{cjs → domain}/caller/getCaller.d.ts +0 -0
  136. /package/dist/{colors → domain/colors}/color.d.ts +0 -0
  137. /package/dist/{decorations → domain/decorations}/link.d.ts +0 -0
  138. /package/dist/{terminal → domain/terminal}/isTTY.d.ts +0 -0
  139. /package/dist/{time → domain/time}/getTimeStamp.d.ts +0 -0
  140. /package/dist/{core/writeLog → domain/write/helpers}/formatLocation.d.ts +0 -0
  141. /package/dist/{core/writeLog → domain/write/helpers}/renderMessage.d.ts +0 -0
  142. /package/dist/{core/writeLog → domain/write/helpers}/visibleLines.d.ts +0 -0
@@ -0,0 +1,12 @@
1
+ /*
2
+ * isThenable.ts
3
+ * Created by Dewan Mobashirul
4
+ * Copyright (c) 2026 meadown
5
+ * All rights reserved
6
+ */
7
+ /** Whether `value` is thenable (a promise we can await + time). */
8
+ export function isThenable(value) {
9
+ return (typeof value === "object" &&
10
+ value !== null &&
11
+ typeof value.then === "function");
12
+ }
@@ -0,0 +1,21 @@
1
+ export type ResponseLike = {
2
+ status: number;
3
+ statusText?: unknown;
4
+ headers?: {
5
+ get?: (name: string) => string | null;
6
+ };
7
+ clone: () => ResponseLike;
8
+ text: () => Promise<string>;
9
+ };
10
+ /** Whether the resolved value is a fetch `Response` we can read. */
11
+ export declare function isResponse(value: unknown): value is ResponseLike;
12
+ export declare function formatStatus(res: ResponseLike, useColor: boolean): string;
13
+ /**
14
+ * Reads a (cloned) response body and returns both the parsed value and the
15
+ * actual byte size. Size is calculated from the body text — not `Content-Length`,
16
+ * which is absent on compressed responses — so size is always shown.
17
+ */
18
+ export declare function readBody(res: ResponseLike): Promise<{
19
+ data: unknown;
20
+ size: string;
21
+ }>;
@@ -0,0 +1,57 @@
1
+ /*
2
+ * response.ts
3
+ * Created by Dewan Mobashirul
4
+ * Copyright (c) 2026 meadown
5
+ * All rights reserved
6
+ */
7
+ import { formatBytes } from "./format.js";
8
+ import { colorize } from "../../../../domain/colors/color.js";
9
+ /** Whether the resolved value is a fetch `Response` we can read. */
10
+ export function isResponse(value) {
11
+ const v = value;
12
+ return (typeof v?.status === "number" &&
13
+ typeof v.clone === "function" &&
14
+ typeof v.text === "function");
15
+ }
16
+ /**
17
+ * Status badge color:
18
+ * 2xx → green · 3xx → cyan · 4xx → yellow · 5xx → red
19
+ */
20
+ function statusColor(status) {
21
+ if (status >= 500)
22
+ return "red";
23
+ if (status >= 400)
24
+ return "yellow";
25
+ if (status >= 300)
26
+ return "cyan";
27
+ return "green";
28
+ }
29
+ export function formatStatus(res, useColor) {
30
+ const text = typeof res.statusText === "string" && res.statusText
31
+ ? `${res.status} ${res.statusText}`
32
+ : `${res.status}`;
33
+ return useColor ? colorize(text, statusColor(res.status)) : text;
34
+ }
35
+ /**
36
+ * Reads a (cloned) response body and returns both the parsed value and the
37
+ * actual byte size. Size is calculated from the body text — not `Content-Length`,
38
+ * which is absent on compressed responses — so size is always shown.
39
+ */
40
+ export async function readBody(res) {
41
+ try {
42
+ const text = await res.text();
43
+ const bytes = new TextEncoder().encode(text).length;
44
+ const size = formatBytes(bytes);
45
+ if (text === "")
46
+ return { data: undefined, size };
47
+ try {
48
+ return { data: JSON.parse(text), size };
49
+ }
50
+ catch {
51
+ return { data: text, size };
52
+ }
53
+ }
54
+ catch {
55
+ return { data: undefined, size: "unknown" };
56
+ }
57
+ }
@@ -0,0 +1,2 @@
1
+ export { tapAsync } from "./tapAsync.js";
2
+ export { isThenable } from "./helpers/index.js";
@@ -0,0 +1,8 @@
1
+ /*
2
+ * index.ts
3
+ * Created by Dewan Mobashirul
4
+ * Copyright (c) 2026 meadown
5
+ * All rights reserved
6
+ */
7
+ export { tapAsync } from "./tapAsync.js";
8
+ export { isThenable } from "./helpers/index.js";
@@ -1,6 +1,4 @@
1
- import { type Caller } from "../caller/getCaller.js";
2
- /** Whether `value` is thenable (a promise we can await + time). */
3
- export declare function isThenable(value: unknown): value is PromiseLike<unknown>;
1
+ import { type Caller } from "../../../domain/caller/getCaller.js";
4
2
  /**
5
3
  * The async tap. Fire-and-forget: returns `promise` immediately (unchanged),
6
4
  * and logs a rich block once it resolves. For a `Response`, reads the body from
@@ -0,0 +1,92 @@
1
+ /*
2
+ * tapAsync.ts
3
+ * Created by Dewan Mobashirul
4
+ * Copyright (c) 2026 meadown
5
+ * All rights reserved
6
+ */
7
+ import { performance } from "node:perf_hooks";
8
+ import { formatDuration, formatBytes, isResponse, readBody, buildBlock, } from "./helpers/index.js";
9
+ import { isTTY } from "../../../domain/terminal/isTTY.js";
10
+ import { writeLog } from "../../../domain/write/index.js";
11
+ import { isLogAllowed } from "../../../config/index.js";
12
+ /**
13
+ * The async tap. Fire-and-forget: returns `promise` immediately (unchanged),
14
+ * and logs a rich block once it resolves. For a `Response`, reads the body from
15
+ * a clone so the caller's original stays consumable. A rejection logs an error
16
+ * to stderr. `caller` MUST be resolved by `tap` (the user-facing function) so
17
+ * the logged location points at the user's file.
18
+ */
19
+ export function tapAsync(promise, label, caller) {
20
+ if (!isLogAllowed())
21
+ return;
22
+ const useColor = isTTY("stdout");
23
+ const start = performance.now();
24
+ void promise.then((resolved) => {
25
+ const ms = Math.round(performance.now() - start);
26
+ if (isResponse(resolved)) {
27
+ let clone = null;
28
+ try {
29
+ clone = resolved.clone();
30
+ }
31
+ catch {
32
+ clone = null;
33
+ }
34
+ if (clone === null) {
35
+ writeLog({
36
+ channel: "log",
37
+ tag: "[TAP]",
38
+ args: buildBlock(label, ms, resolved, { data: undefined, size: "unknown" }, useColor),
39
+ caller,
40
+ });
41
+ return;
42
+ }
43
+ const cl = resolved.headers?.get?.("content-length");
44
+ const tooLarge = cl != null && cl !== "" && Number(cl) > 512 * 1024;
45
+ if (tooLarge) {
46
+ writeLog({
47
+ channel: "log",
48
+ tag: "[TAP]",
49
+ args: buildBlock(label, ms, resolved, {
50
+ data: "(body too large to display)",
51
+ size: formatBytes(Number(cl)),
52
+ }, useColor),
53
+ caller,
54
+ });
55
+ return;
56
+ }
57
+ void readBody(clone).then((body) => {
58
+ writeLog({
59
+ channel: "log",
60
+ tag: "[TAP]",
61
+ args: buildBlock(label, ms, resolved, body, useColor),
62
+ caller,
63
+ });
64
+ });
65
+ return;
66
+ }
67
+ // Non-Response promise — plain value with elapsed time.
68
+ const elapsed = formatDuration(ms, useColor);
69
+ writeLog({
70
+ channel: "log",
71
+ tag: "[TAP]",
72
+ args: label === undefined
73
+ ? [elapsed, resolved]
74
+ : [`${label} ${elapsed}`, resolved],
75
+ caller,
76
+ });
77
+ }, (err) => {
78
+ const ms = Math.round(performance.now() - start);
79
+ const elapsed = formatDuration(ms, useColor);
80
+ writeLog({
81
+ channel: "error",
82
+ tag: "[ERROR]",
83
+ args: [
84
+ label === undefined
85
+ ? `rejected after ${elapsed}`
86
+ : `${label} rejected after ${elapsed}`,
87
+ err,
88
+ ],
89
+ caller,
90
+ });
91
+ });
92
+ }
@@ -0,0 +1 @@
1
+ export default function logWarn(...args: unknown[]): void;
@@ -0,0 +1,15 @@
1
+ /*
2
+ * index.ts
3
+ * Created by Dewan Mobashirul
4
+ * Copyright (c) 2026 meadown
5
+ * All rights reserved
6
+ */
7
+ import { isLogAllowed } from "../../config/index.js";
8
+ import getCaller from "../../domain/caller/getCaller.js";
9
+ import { writeLog } from "../../domain/write/writeLog.js";
10
+ export default function logWarn(...args) {
11
+ if (!isLogAllowed())
12
+ return;
13
+ const caller = getCaller();
14
+ writeLog({ channel: "warn", tag: "[WARN]", args, caller });
15
+ }
package/dist/index.d.ts CHANGED
@@ -1,32 +1,77 @@
1
- /** The logger: a callable for info logs, plus `.error`, `.warn`, and `.tap`. */
2
- export interface LogFN {
1
+ import { type Tap } from "./features/logger-tap/index.js";
2
+ import { type Group } from "./features/logger-group/index.js";
3
+ /** Type of the logger — use this to annotate variables or parameters that accept it. */
4
+ export interface Logger {
5
+ /** Log at info level — `stdout`, cyan `[INFO]` tag. */
3
6
  (...args: unknown[]): void;
7
+ /** Log at error level — `stderr`, red `[ERROR]` tag. */
4
8
  error(...args: unknown[]): void;
9
+ /** Log at warn level — `stderr`, yellow `[WARN]` tag. */
5
10
  warn(...args: unknown[]): void;
6
11
  /**
7
- * Logs `value` (optionally tagged with `label`) and returns it **unchanged**,
8
- * so it drops into any expression: `const u = logger.tap(getUser(), "user")`.
9
- * Pass a **promise** (e.g. a `fetch`) and you get the same promise back while
10
- * its elapsed time and the HTTP status if it's a `Response` — is logged in
11
- * the background. Silent in production; the value always flows through.
12
+ * Logs `value` with an optional `label` and gives it straight back unchanged
13
+ * so you can drop it into any expression without adding an extra line.
14
+ *
15
+ * **Sync value**logged immediately, returned as-is:
16
+ * ```ts
17
+ * const port = logger.tap(3000, "port") // logs it, port is still 3000
18
+ * const user = logger.tap(getUser(), "user") // logs the user object, returns it
19
+ * ```
20
+ *
21
+ * **Promise** — the same promise comes back; timing and HTTP status are logged
22
+ * in the background once it settles, without blocking your code:
23
+ * ```ts
24
+ * const res = await logger.tap(fetch(url), "GET /users") // logs status + ms
25
+ * const data = await logger.tap(loadConfig(), "config") // logs value + ms
26
+ * ```
27
+ *
28
+ * @param value Any value or promise — always returned as-is.
29
+ * @param label Optional label shown next to the value in the log line.
12
30
  */
13
- tap<T>(value: T, label?: string): T;
31
+ tap: Tap;
14
32
  /**
15
- * How many lines of a multi-line message to show before collapsing the rest
16
- * into a `… N more lines` summary. `0` (the default) shows everything.
33
+ * Log multiple related items as one block under a shared name and single
34
+ * timestamp. Each item in `logs` renders on its own `├──` branch — any value
35
+ * is accepted (string, object, function, Promise, …).
36
+ *
37
+ * `type` sets the channel and tag color: `"info"` (default), `"warn"`, `"error"`.
38
+ *
39
+ * @example
40
+ * ```ts
41
+ * logger.group({ name: "Server setup", logs: [`port: ${port}`, `env: ${env}`] })
42
+ * logger.group({ name: "Validation failed", type: "error", logs: ["email invalid"] })
43
+ * ```
44
+ */
45
+ group: Group;
46
+ /**
47
+ * How many lines to show before the rest collapses into a
48
+ * `… N more lines` summary. `0` (default) shows everything.
49
+ *
50
+ * @example
51
+ * logger.maxLines = 5
17
52
  */
18
53
  maxLines: number;
19
54
  }
20
55
  /**
21
- * Logs to the console, but only outside production. Each line is prefixed with
22
- * a level tag, a short local timestamp, and a clickable link to the file it was
23
- * called from; all arguments are then printed as-is.
24
- * @example
25
- * logger("Auth", "user logged in")
26
- * // [INFO] 05-30 04:00:00 PM (server.ts:42) Auth user logged in
56
+ * A logger built for development colored tags, a local timestamp, and a
57
+ * clickable `(file:line)` link on every line so you always know where a
58
+ * message came from. Works like `console.log`: pass anything and it prints.
59
+ *
60
+ * Logs only when `NODE_ENV !== "production"`.
61
+ *
27
62
  * @example
28
- * logger.maxLines = 5 // long messages collapse to 5 lines; 0 = show all
63
+ * ```ts
64
+ * logger("server started", { port: 3000 })
65
+ * logger.error("db failed", new Error("ECONNREFUSED"))
66
+ * logger.warn("disk above 80%")
67
+ * ```
68
+ *
69
+ * @example Tap — keep the value flowing, log it on the side
70
+ * ```ts
71
+ * const user = logger.tap(await getUser(id), "user")
72
+ * const res = await logger.tap(fetch(url), "GET /users")
73
+ * ```
29
74
  */
30
- declare const logger: LogFN;
75
+ declare const logger: Logger;
31
76
  export { logger };
32
77
  export default logger;
package/dist/index.js CHANGED
@@ -1,26 +1,40 @@
1
1
  /*
2
2
  * index.ts
3
3
  * Created by Dewan Mobashirul
4
- * Copyright (c) 2026 dewan-meadown
4
+ * Copyright (c) 2026 meadown
5
5
  * All rights reserved
6
6
  */
7
- import createLog from "./core/createLog.js";
8
- import createTap from "./tap/createTap.js";
9
- import { getVisibleLines, setVisibleLines } from "./core/writeLog/index.js";
7
+ import { getVisibleLines, setVisibleLines, } from "./features/logger-max-lines/index.js";
8
+ import logInfo from "./features/logger/index.js";
9
+ import logWarn from "./features/logger-warn/index.js";
10
+ import logError from "./features/logger-error/index.js";
11
+ import tap from "./features/logger-tap/index.js";
12
+ import group from "./features/logger-group/index.js";
10
13
  /**
11
- * Logs to the console, but only outside production. Each line is prefixed with
12
- * a level tag, a short local timestamp, and a clickable link to the file it was
13
- * called from; all arguments are then printed as-is.
14
+ * A logger built for development colored tags, a local timestamp, and a
15
+ * clickable `(file:line)` link on every line so you always know where a
16
+ * message came from. Works like `console.log`: pass anything and it prints.
17
+ *
18
+ * Logs only when `NODE_ENV !== "production"`.
19
+ *
14
20
  * @example
15
- * logger("Auth", "user logged in")
16
- * // [INFO] 05-30 04:00:00 PM (server.ts:42) Auth user logged in
17
- * @example
18
- * logger.maxLines = 5 // long messages collapse to 5 lines; 0 = show all
21
+ * ```ts
22
+ * logger("server started", { port: 3000 })
23
+ * logger.error("db failed", new Error("ECONNREFUSED"))
24
+ * logger.warn("disk above 80%")
25
+ * ```
26
+ *
27
+ * @example Tap — keep the value flowing, log it on the side
28
+ * ```ts
29
+ * const user = logger.tap(await getUser(id), "user")
30
+ * const res = await logger.tap(fetch(url), "GET /users")
31
+ * ```
19
32
  */
20
- const logger = Object.assign(createLog("log", "[INFO]"), {
21
- error: createLog("error", "[ERROR]"),
22
- warn: createLog("warn", "[WARN]"),
23
- tap: createTap(),
33
+ const logger = Object.assign(logInfo, {
34
+ error: logError,
35
+ warn: logWarn,
36
+ tap,
37
+ group,
24
38
  });
25
39
  // `maxLines` is a live getter/setter backed by the shared collapse setting, so
26
40
  // setting it once affects info, error, and warn alike.
@@ -0,0 +1,2 @@
1
+ /** The console channels the logger writes to. */
2
+ export type LogChannel = "log" | "error" | "warn";
@@ -0,0 +1,7 @@
1
+ /*
2
+ * index.ts
3
+ * Created by Dewan Mobashirul
4
+ * Copyright (c) 2026 meadown
5
+ * All rights reserved
6
+ */
7
+ export {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@meadown/logger",
3
- "version": "1.8.10",
3
+ "version": "1.9.0",
4
4
  "description": "A development-focused logger for Node.js and TypeScript — zero dependencies, clickable source links, and API response logging built in.",
5
5
  "keywords": [
6
6
  "logger",
@@ -53,9 +53,12 @@
53
53
  "test": "tsc && node --test \"test/**/*.test.mjs\"",
54
54
  "lint": "eslint src",
55
55
  "lint:fix": "eslint src --fix",
56
- "demo": "pnpm build && node examples/demo.mjs",
57
- "demo:calls": "pnpm build && node examples/call-sites.mjs",
58
- "demo:api": "pnpm build && node examples/api.mjs",
56
+ "demo": "pnpm build && node --experimental-strip-types examples/ts/demo.ts",
57
+ "demo:calls": "pnpm build && node --experimental-strip-types examples/ts/call-sites.ts",
58
+ "demo:api": "pnpm build && node --experimental-strip-types examples/ts/api.ts",
59
+ "demo-mjs": "pnpm build && node examples/mjs/demo.mjs",
60
+ "demo:calls-mjs": "pnpm build && node examples/mjs/call-sites.mjs",
61
+ "demo:api-mjs": "pnpm build && node examples/mjs/api.mjs",
59
62
  "prepublishOnly": "pnpm run lint && pnpm test && pnpm run build",
60
63
  "prepare": "husky",
61
64
  "release": "semantic-release"
@@ -1,8 +0,0 @@
1
- import { type LogChannel } from "../constants.js";
2
- /**
3
- * Builds a log function bound to a console channel and tag. The returned closure
4
- * is what the caller invokes directly, so {@link getCaller} resolves the caller's
5
- * own frame; the resolved caller is then handed to {@link writeLog}, which never
6
- * touches the stack. Logs only outside production — see {@link isLogAllowed}.
7
- */
8
- export default function createLog(channel: LogChannel, tag: string): (...args: unknown[]) => void;
@@ -1,29 +0,0 @@
1
- "use strict";
2
- /*
3
- * createLog.ts
4
- * Created by Dewan Mobashirul
5
- * Copyright (c) 2026 dewan-meadown
6
- * All rights reserved
7
- */
8
- var __importDefault = (this && this.__importDefault) || function (mod) {
9
- return (mod && mod.__esModule) ? mod : { "default": mod };
10
- };
11
- Object.defineProperty(exports, "__esModule", { value: true });
12
- exports.default = createLog;
13
- const getCaller_js_1 = __importDefault(require("../caller/getCaller.js"));
14
- const index_js_1 = require("./writeLog/index.js");
15
- const config_js_1 = require("../config.js");
16
- /**
17
- * Builds a log function bound to a console channel and tag. The returned closure
18
- * is what the caller invokes directly, so {@link getCaller} resolves the caller's
19
- * own frame; the resolved caller is then handed to {@link writeLog}, which never
20
- * touches the stack. Logs only outside production — see {@link isLogAllowed}.
21
- */
22
- function createLog(channel, tag) {
23
- return (...args) => {
24
- if (!(0, config_js_1.isLogAllowed)())
25
- return;
26
- const caller = (0, getCaller_js_1.default)();
27
- (0, index_js_1.writeLog)({ channel, tag, args, caller });
28
- };
29
- }
@@ -1,48 +0,0 @@
1
- "use strict";
2
- /*
3
- * index.ts
4
- * Created by Dewan Mobashirul
5
- * Copyright (c) 2026 dewan-meadown
6
- * All rights reserved
7
- */
8
- var __importDefault = (this && this.__importDefault) || function (mod) {
9
- return (mod && mod.__esModule) ? mod : { "default": mod };
10
- };
11
- Object.defineProperty(exports, "__esModule", { value: true });
12
- exports.setVisibleLines = exports.getVisibleLines = void 0;
13
- exports.writeLog = writeLog;
14
- const constants_js_1 = require("../../constants.js");
15
- const getTimeStamp_js_1 = __importDefault(require("../../time/getTimeStamp.js"));
16
- const color_js_1 = require("../../colors/color.js");
17
- const isTTY_js_1 = require("../../terminal/isTTY.js");
18
- const renderMessage_js_1 = require("./renderMessage.js");
19
- const formatLocation_js_1 = require("./formatLocation.js");
20
- var visibleLines_js_1 = require("./visibleLines.js");
21
- Object.defineProperty(exports, "getVisibleLines", { enumerable: true, get: function () { return visibleLines_js_1.getVisibleLines; } });
22
- Object.defineProperty(exports, "setVisibleLines", { enumerable: true, get: function () { return visibleLines_js_1.setVisibleLines; } });
23
- /**
24
- * Renders and writes one log entry. The `caller` is resolved by the *caller* of
25
- * this function (the log closure or `tap`) and passed in, so this helper never
26
- * touches the stack — keeping {@link getCaller}'s frame depth correct no matter
27
- * which user-facing function delegates here.
28
- */
29
- function writeLog(opts) {
30
- const { channel, tag, args, caller } = opts;
31
- const streamName = channel === "log" ? "stdout" : "stderr";
32
- // One terminal check drives both color and clickable links — `isTTY` is the
33
- // single source of truth (DRY). Off when output is piped/redirected.
34
- const useColor = (0, isTTY_js_1.isTTY)(streamName);
35
- const paint = (s, c) => useColor ? (0, color_js_1.colorize)(s, c) : s;
36
- const location = (0, formatLocation_js_1.formatLocation)(caller, useColor);
37
- const tagOut = paint(tag, constants_js_1.TAG_COLOR[channel]);
38
- const timeStamp = paint((0, getTimeStamp_js_1.default)(), "teal");
39
- const locOut = paint(`(${location})`, "dimTeal");
40
- const connector = paint(constants_js_1.BRANCH, "gray");
41
- const connectorBottom = paint(constants_js_1.BRANCH_END, "gray");
42
- const separator = paint(constants_js_1.SEPARATOR, "gray");
43
- // Layout: the tag, the message hanging off a `├──` branch, then the timestamp
44
- // and location on a `└──` branch below. Leading `\n` spaces entries apart.
45
- const message = (0, renderMessage_js_1.renderMessage)(args, useColor);
46
- const meta = `\n${connectorBottom} ${timeStamp} ${separator} ${locOut}`;
47
- console[channel](`\n${tagOut}`, `\n${connector}`, message, meta);
48
- }
@@ -1,18 +0,0 @@
1
- /** Logs a value and returns it unchanged. Promises route to the timed path. */
2
- export interface Tap {
3
- <T>(value: T, label?: string): T;
4
- }
5
- /**
6
- * Builds `tap` — logs a value and returns it **unchanged**, so it drops into any
7
- * expression (`const u = logger.tap(getUser(), "user")`). The consumer always
8
- * gets back exactly what they passed; the only effect is a clean log.
9
- *
10
- * - A plain value is logged synchronously and returned.
11
- * - A **promise** is returned as-is (same object, never awaited or wrapped), and
12
- * its elapsed time — plus the HTTP status if it resolves to a `Response` — is
13
- * logged in the background (fire-and-forget, non-blocking).
14
- *
15
- * The returned closure is what the caller invokes directly, so {@link getCaller}
16
- * resolves the caller's own frame. Silent in production; the value still flows.
17
- */
18
- export default function createTap(): Tap;
@@ -1,51 +0,0 @@
1
- "use strict";
2
- /*
3
- * createTap.ts
4
- * Created by Dewan Mobashirul
5
- * Copyright (c) 2026 dewan-meadown
6
- * All rights reserved
7
- */
8
- var __importDefault = (this && this.__importDefault) || function (mod) {
9
- return (mod && mod.__esModule) ? mod : { "default": mod };
10
- };
11
- Object.defineProperty(exports, "__esModule", { value: true });
12
- exports.default = createTap;
13
- const getCaller_js_1 = __importDefault(require("../caller/getCaller.js"));
14
- const index_js_1 = require("../core/writeLog/index.js");
15
- const config_js_1 = require("../config.js");
16
- const tapAsync_js_1 = require("./tapAsync.js");
17
- /**
18
- * Builds `tap` — logs a value and returns it **unchanged**, so it drops into any
19
- * expression (`const u = logger.tap(getUser(), "user")`). The consumer always
20
- * gets back exactly what they passed; the only effect is a clean log.
21
- *
22
- * - A plain value is logged synchronously and returned.
23
- * - A **promise** is returned as-is (same object, never awaited or wrapped), and
24
- * its elapsed time — plus the HTTP status if it resolves to a `Response` — is
25
- * logged in the background (fire-and-forget, non-blocking).
26
- *
27
- * The returned closure is what the caller invokes directly, so {@link getCaller}
28
- * resolves the caller's own frame. Silent in production; the value still flows.
29
- */
30
- function createTap() {
31
- const tap = (value, label) => {
32
- if (!(0, config_js_1.isLogAllowed)())
33
- return value;
34
- const caller = (0, getCaller_js_1.default)();
35
- if ((0, tapAsync_js_1.isThenable)(value)) {
36
- // value is a promise → hand off to the async tap (caller passed in so the
37
- // location stays on the user's file, not on this helper).
38
- (0, tapAsync_js_1.tapAsync)(value, label, caller);
39
- }
40
- else {
41
- (0, index_js_1.writeLog)({
42
- channel: "log",
43
- tag: "[TAP]",
44
- args: label === undefined ? [value] : [label, value],
45
- caller,
46
- });
47
- }
48
- return value;
49
- };
50
- return tap;
51
- }