where-log 0.1.1 → 0.2.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.
package/README.md CHANGED
@@ -1,17 +1,17 @@
1
- # callsite-log
1
+ # where-log
2
2
 
3
3
  Log a value with the caller file and line number.
4
4
 
5
5
  ## Install
6
6
 
7
7
  ```bash
8
- npm install callsite-log
8
+ npm install where-log
9
9
  ```
10
10
 
11
11
  ## Usage
12
12
 
13
13
  ```ts
14
- import { log } from "callsite-log";
14
+ import { log } from "where-log";
15
15
 
16
16
  const user = { id: 1, name: "Shovon" };
17
17
  log(user);
@@ -41,6 +41,8 @@ log("user", user, {
41
41
  valueLine: `${label}: ${JSON.stringify(value)}`,
42
42
  }),
43
43
  });
44
+
45
+ log(user, { mode: "fast", includeLocation: false });
44
46
  ```
45
47
 
46
48
  ## Notes
@@ -49,11 +51,22 @@ log("user", user, {
49
51
  - Browser/Next.js: call-site depends on source maps and bundler/devtools behavior.
50
52
  - If stack parsing fails, the package prints `unknown:0` on line 1.
51
53
 
54
+ ## Performance Tuning
55
+
56
+ - `mode: "pretty"` (default): rich formatting via Node inspect.
57
+ - `mode: "fast"`: lower overhead formatting for hot paths.
58
+ - `includeLocation: false`: skips stack capture/parsing.
59
+ - `inspectDepth`: limit inspect depth in pretty mode.
60
+
52
61
  ## API
53
62
 
54
63
  - `log(value: unknown): void`
55
64
  - `log(value: unknown, options?: LogOptions): void`
56
65
  - `log(label: string, value: unknown, options?: LogOptions): void`
66
+ - `LogMode = "pretty" | "fast"`
57
67
  - `LogOptions`
68
+ - `mode?: LogMode` (default `"pretty"`)
69
+ - `includeLocation?: boolean` (default `true`)
70
+ - `inspectDepth?: number` (pretty mode only)
58
71
  - `colors?: boolean` (Node only, default `true`)
59
72
  - `formatter?: (input) => { locationLine: unknown; valueLine: unknown }`
package/dist/index.cjs CHANGED
@@ -34,18 +34,71 @@ __export(index_exports, {
34
34
  log: () => log
35
35
  });
36
36
  module.exports = __toCommonJS(index_exports);
37
+
38
+ // src/core/format.ts
37
39
  var import_node_util = __toESM(require("util"), 1);
38
- var import_node_path = __toESM(require("path"), 1);
39
40
  function isNodeRuntime() {
40
41
  return typeof process !== "undefined" && !!process.versions?.node;
41
42
  }
43
+ function safeFastStringify(value) {
44
+ const seen = /* @__PURE__ */ new WeakSet();
45
+ return JSON.stringify(value, (_k, v) => {
46
+ if (typeof v === "object" && v !== null) {
47
+ if (seen.has(v)) return "[Circular]";
48
+ seen.add(v);
49
+ }
50
+ return v;
51
+ });
52
+ }
53
+ function formatFastValue(value) {
54
+ if (typeof value === "string") return value;
55
+ if (typeof value === "number") return String(value);
56
+ if (typeof value === "boolean") return String(value);
57
+ if (typeof value === "bigint") return value.toString();
58
+ if (typeof value === "undefined") return "undefined";
59
+ if (value === null) return "null";
60
+ if (typeof value === "symbol") return value.toString();
61
+ if (typeof value === "function") {
62
+ return `[Function ${value.name || "anonymous"}]`;
63
+ }
64
+ try {
65
+ return safeFastStringify(value);
66
+ } catch {
67
+ return "[Unserializable]";
68
+ }
69
+ }
70
+ function formatValue(value, options) {
71
+ const mode = options?.mode ?? "pretty";
72
+ if (mode === "fast") {
73
+ return formatFastValue(value);
74
+ }
75
+ if (!isNodeRuntime()) {
76
+ return value;
77
+ }
78
+ return import_node_util.default.inspect(value, {
79
+ depth: options?.inspectDepth ?? null,
80
+ colors: options?.colors ?? true,
81
+ compact: false
82
+ });
83
+ }
84
+ function formatLabeledValue(label, formattedValue) {
85
+ if (!label) return formattedValue;
86
+ if (typeof formattedValue === "string") {
87
+ return `${label}: ${formattedValue}`;
88
+ }
89
+ return `${label}: ${String(formattedValue)}`;
90
+ }
91
+
92
+ // src/core/stack.ts
42
93
  function safeToInt(input) {
43
94
  const n = Number.parseInt(input, 10);
44
95
  return Number.isFinite(n) ? n : 0;
45
96
  }
46
- function normalizeFile(filePath) {
97
+ function basename(filePath) {
47
98
  const clean = filePath.replace(/^file:\/\//, "");
48
- return import_node_path.default.basename(clean);
99
+ const normalized = clean.replace(/\\/g, "/");
100
+ const last = normalized.lastIndexOf("/");
101
+ return last >= 0 ? normalized.slice(last + 1) : normalized;
49
102
  }
50
103
  function parseFrameLine(frame) {
51
104
  const cleaned = frame.trim();
@@ -53,14 +106,14 @@ function parseFrameLine(frame) {
53
106
  const v8Match = cleaned.match(/(?:at\s+)?(?:.+\s+\()?(.+):(\d+):(\d+)\)?$/);
54
107
  if (v8Match) {
55
108
  return {
56
- file: normalizeFile(v8Match[1]),
109
+ file: basename(v8Match[1]),
57
110
  line: safeToInt(v8Match[2])
58
111
  };
59
112
  }
60
113
  const ffMatch = cleaned.match(/@(.+):(\d+):(\d+)$/);
61
114
  if (ffMatch) {
62
115
  return {
63
- file: normalizeFile(ffMatch[1]),
116
+ file: basename(ffMatch[1]),
64
117
  line: safeToInt(ffMatch[2])
65
118
  };
66
119
  }
@@ -68,39 +121,28 @@ function parseFrameLine(frame) {
68
121
  }
69
122
  function getCallerFromStack(stack) {
70
123
  if (!stack) return { file: "unknown", line: 0 };
71
- const frames = stack.split("\n").map((line) => line.trim()).filter(Boolean);
72
- for (const frame of frames) {
124
+ const frames = stack.split("\n");
125
+ for (let i = 0; i < frames.length; i += 1) {
126
+ const frame = frames[i].trim();
127
+ if (!frame) continue;
73
128
  if (frame.includes("getCallerFromStack")) continue;
74
129
  if (frame.includes("at log")) continue;
75
130
  if (frame.includes("at Object.log")) continue;
76
131
  if (frame.includes("/src/index.ts")) continue;
77
132
  if (frame.includes("\\src\\index.ts")) continue;
133
+ if (frame.includes("/src/core/")) continue;
134
+ if (frame.includes("\\src\\core\\")) continue;
78
135
  const parsed = parseFrameLine(frame);
79
136
  if (parsed) return parsed;
80
137
  }
81
138
  return { file: "unknown", line: 0 };
82
139
  }
140
+
141
+ // src/index.ts
83
142
  function isLogOptions(value) {
84
143
  if (!value || typeof value !== "object") return false;
85
144
  const record = value;
86
- return "colors" in record || "formatter" in record;
87
- }
88
- function formatValue(value, options) {
89
- if (!isNodeRuntime()) {
90
- return value;
91
- }
92
- return import_node_util.default.inspect(value, {
93
- depth: null,
94
- colors: options?.colors ?? true,
95
- compact: false
96
- });
97
- }
98
- function formatLabeledValue(label, formattedValue) {
99
- if (!label) return formattedValue;
100
- if (typeof formattedValue === "string") {
101
- return `${label}: ${formattedValue}`;
102
- }
103
- return `${label}: ${String(formattedValue)}`;
145
+ return "colors" in record || "formatter" in record || "mode" in record || "includeLocation" in record || "inspectDepth" in record;
104
146
  }
105
147
  function log(arg1, arg2, arg3) {
106
148
  let label;
@@ -114,10 +156,16 @@ function log(arg1, arg2, arg3) {
114
156
  value = arg1;
115
157
  options = isLogOptions(arg2) ? arg2 : void 0;
116
158
  }
117
- const stack = new Error().stack;
118
- const caller = getCallerFromStack(stack);
159
+ const includeLocation = options?.includeLocation ?? true;
160
+ const stack = includeLocation ? new Error().stack : void 0;
161
+ const caller = includeLocation ? getCallerFromStack(stack) : { file: "disabled", line: 0 };
119
162
  const location = `${caller.file}:${caller.line}`;
120
- const formattedValue = formatValue(value, options);
163
+ const formatOptions = {
164
+ colors: options?.colors,
165
+ mode: options?.mode,
166
+ inspectDepth: options?.inspectDepth
167
+ };
168
+ const formattedValue = formatValue(value, formatOptions);
121
169
  if (options?.formatter) {
122
170
  const formatted = options.formatter({
123
171
  location,
@@ -129,7 +177,9 @@ function log(arg1, arg2, arg3) {
129
177
  console.log(formatted.valueLine);
130
178
  return;
131
179
  }
132
- console.log(location);
180
+ if (includeLocation) {
181
+ console.log(location);
182
+ }
133
183
  console.log(formatLabeledValue(label, formattedValue));
134
184
  }
135
185
  var __internal = {
package/dist/index.d.cts CHANGED
@@ -1,7 +1,13 @@
1
+ type LogMode = "pretty" | "fast";
2
+ declare function formatLabeledValue(label: string | undefined, formattedValue: unknown): unknown;
3
+
1
4
  type ParsedFrame = {
2
5
  file: string;
3
6
  line: number;
4
7
  };
8
+ declare function parseFrameLine(frame: string): ParsedFrame | null;
9
+ declare function getCallerFromStack(stack?: string): ParsedFrame;
10
+
5
11
  type FormatterInput = {
6
12
  location: string;
7
13
  label?: string;
@@ -13,13 +19,13 @@ type FormatterOutput = {
13
19
  valueLine: unknown;
14
20
  };
15
21
  type LogOptions = {
22
+ mode?: LogMode;
23
+ includeLocation?: boolean;
24
+ inspectDepth?: number;
16
25
  colors?: boolean;
17
26
  formatter?: (input: FormatterInput) => FormatterOutput;
18
27
  };
19
- declare function parseFrameLine(frame: string): ParsedFrame | null;
20
- declare function getCallerFromStack(stack?: string): ParsedFrame;
21
28
  declare function isLogOptions(value: unknown): value is LogOptions;
22
- declare function formatLabeledValue(label: string | undefined, formattedValue: unknown): unknown;
23
29
  declare function log(value: unknown, options?: LogOptions): void;
24
30
  declare function log(label: string, value: unknown, options?: LogOptions): void;
25
31
  declare const __internal: {
@@ -29,4 +35,4 @@ declare const __internal: {
29
35
  isLogOptions: typeof isLogOptions;
30
36
  };
31
37
 
32
- export { type FormatterInput, type FormatterOutput, type LogOptions, __internal, log };
38
+ export { type FormatterInput, type FormatterOutput, type LogMode, type LogOptions, __internal, log };
package/dist/index.d.ts CHANGED
@@ -1,7 +1,13 @@
1
+ type LogMode = "pretty" | "fast";
2
+ declare function formatLabeledValue(label: string | undefined, formattedValue: unknown): unknown;
3
+
1
4
  type ParsedFrame = {
2
5
  file: string;
3
6
  line: number;
4
7
  };
8
+ declare function parseFrameLine(frame: string): ParsedFrame | null;
9
+ declare function getCallerFromStack(stack?: string): ParsedFrame;
10
+
5
11
  type FormatterInput = {
6
12
  location: string;
7
13
  label?: string;
@@ -13,13 +19,13 @@ type FormatterOutput = {
13
19
  valueLine: unknown;
14
20
  };
15
21
  type LogOptions = {
22
+ mode?: LogMode;
23
+ includeLocation?: boolean;
24
+ inspectDepth?: number;
16
25
  colors?: boolean;
17
26
  formatter?: (input: FormatterInput) => FormatterOutput;
18
27
  };
19
- declare function parseFrameLine(frame: string): ParsedFrame | null;
20
- declare function getCallerFromStack(stack?: string): ParsedFrame;
21
28
  declare function isLogOptions(value: unknown): value is LogOptions;
22
- declare function formatLabeledValue(label: string | undefined, formattedValue: unknown): unknown;
23
29
  declare function log(value: unknown, options?: LogOptions): void;
24
30
  declare function log(label: string, value: unknown, options?: LogOptions): void;
25
31
  declare const __internal: {
@@ -29,4 +35,4 @@ declare const __internal: {
29
35
  isLogOptions: typeof isLogOptions;
30
36
  };
31
37
 
32
- export { type FormatterInput, type FormatterOutput, type LogOptions, __internal, log };
38
+ export { type FormatterInput, type FormatterOutput, type LogMode, type LogOptions, __internal, log };
package/dist/index.mjs CHANGED
@@ -1,16 +1,67 @@
1
- // src/index.ts
1
+ // src/core/format.ts
2
2
  import util from "util";
3
- import path from "path";
4
3
  function isNodeRuntime() {
5
4
  return typeof process !== "undefined" && !!process.versions?.node;
6
5
  }
6
+ function safeFastStringify(value) {
7
+ const seen = /* @__PURE__ */ new WeakSet();
8
+ return JSON.stringify(value, (_k, v) => {
9
+ if (typeof v === "object" && v !== null) {
10
+ if (seen.has(v)) return "[Circular]";
11
+ seen.add(v);
12
+ }
13
+ return v;
14
+ });
15
+ }
16
+ function formatFastValue(value) {
17
+ if (typeof value === "string") return value;
18
+ if (typeof value === "number") return String(value);
19
+ if (typeof value === "boolean") return String(value);
20
+ if (typeof value === "bigint") return value.toString();
21
+ if (typeof value === "undefined") return "undefined";
22
+ if (value === null) return "null";
23
+ if (typeof value === "symbol") return value.toString();
24
+ if (typeof value === "function") {
25
+ return `[Function ${value.name || "anonymous"}]`;
26
+ }
27
+ try {
28
+ return safeFastStringify(value);
29
+ } catch {
30
+ return "[Unserializable]";
31
+ }
32
+ }
33
+ function formatValue(value, options) {
34
+ const mode = options?.mode ?? "pretty";
35
+ if (mode === "fast") {
36
+ return formatFastValue(value);
37
+ }
38
+ if (!isNodeRuntime()) {
39
+ return value;
40
+ }
41
+ return util.inspect(value, {
42
+ depth: options?.inspectDepth ?? null,
43
+ colors: options?.colors ?? true,
44
+ compact: false
45
+ });
46
+ }
47
+ function formatLabeledValue(label, formattedValue) {
48
+ if (!label) return formattedValue;
49
+ if (typeof formattedValue === "string") {
50
+ return `${label}: ${formattedValue}`;
51
+ }
52
+ return `${label}: ${String(formattedValue)}`;
53
+ }
54
+
55
+ // src/core/stack.ts
7
56
  function safeToInt(input) {
8
57
  const n = Number.parseInt(input, 10);
9
58
  return Number.isFinite(n) ? n : 0;
10
59
  }
11
- function normalizeFile(filePath) {
60
+ function basename(filePath) {
12
61
  const clean = filePath.replace(/^file:\/\//, "");
13
- return path.basename(clean);
62
+ const normalized = clean.replace(/\\/g, "/");
63
+ const last = normalized.lastIndexOf("/");
64
+ return last >= 0 ? normalized.slice(last + 1) : normalized;
14
65
  }
15
66
  function parseFrameLine(frame) {
16
67
  const cleaned = frame.trim();
@@ -18,14 +69,14 @@ function parseFrameLine(frame) {
18
69
  const v8Match = cleaned.match(/(?:at\s+)?(?:.+\s+\()?(.+):(\d+):(\d+)\)?$/);
19
70
  if (v8Match) {
20
71
  return {
21
- file: normalizeFile(v8Match[1]),
72
+ file: basename(v8Match[1]),
22
73
  line: safeToInt(v8Match[2])
23
74
  };
24
75
  }
25
76
  const ffMatch = cleaned.match(/@(.+):(\d+):(\d+)$/);
26
77
  if (ffMatch) {
27
78
  return {
28
- file: normalizeFile(ffMatch[1]),
79
+ file: basename(ffMatch[1]),
29
80
  line: safeToInt(ffMatch[2])
30
81
  };
31
82
  }
@@ -33,39 +84,28 @@ function parseFrameLine(frame) {
33
84
  }
34
85
  function getCallerFromStack(stack) {
35
86
  if (!stack) return { file: "unknown", line: 0 };
36
- const frames = stack.split("\n").map((line) => line.trim()).filter(Boolean);
37
- for (const frame of frames) {
87
+ const frames = stack.split("\n");
88
+ for (let i = 0; i < frames.length; i += 1) {
89
+ const frame = frames[i].trim();
90
+ if (!frame) continue;
38
91
  if (frame.includes("getCallerFromStack")) continue;
39
92
  if (frame.includes("at log")) continue;
40
93
  if (frame.includes("at Object.log")) continue;
41
94
  if (frame.includes("/src/index.ts")) continue;
42
95
  if (frame.includes("\\src\\index.ts")) continue;
96
+ if (frame.includes("/src/core/")) continue;
97
+ if (frame.includes("\\src\\core\\")) continue;
43
98
  const parsed = parseFrameLine(frame);
44
99
  if (parsed) return parsed;
45
100
  }
46
101
  return { file: "unknown", line: 0 };
47
102
  }
103
+
104
+ // src/index.ts
48
105
  function isLogOptions(value) {
49
106
  if (!value || typeof value !== "object") return false;
50
107
  const record = value;
51
- return "colors" in record || "formatter" in record;
52
- }
53
- function formatValue(value, options) {
54
- if (!isNodeRuntime()) {
55
- return value;
56
- }
57
- return util.inspect(value, {
58
- depth: null,
59
- colors: options?.colors ?? true,
60
- compact: false
61
- });
62
- }
63
- function formatLabeledValue(label, formattedValue) {
64
- if (!label) return formattedValue;
65
- if (typeof formattedValue === "string") {
66
- return `${label}: ${formattedValue}`;
67
- }
68
- return `${label}: ${String(formattedValue)}`;
108
+ return "colors" in record || "formatter" in record || "mode" in record || "includeLocation" in record || "inspectDepth" in record;
69
109
  }
70
110
  function log(arg1, arg2, arg3) {
71
111
  let label;
@@ -79,10 +119,16 @@ function log(arg1, arg2, arg3) {
79
119
  value = arg1;
80
120
  options = isLogOptions(arg2) ? arg2 : void 0;
81
121
  }
82
- const stack = new Error().stack;
83
- const caller = getCallerFromStack(stack);
122
+ const includeLocation = options?.includeLocation ?? true;
123
+ const stack = includeLocation ? new Error().stack : void 0;
124
+ const caller = includeLocation ? getCallerFromStack(stack) : { file: "disabled", line: 0 };
84
125
  const location = `${caller.file}:${caller.line}`;
85
- const formattedValue = formatValue(value, options);
126
+ const formatOptions = {
127
+ colors: options?.colors,
128
+ mode: options?.mode,
129
+ inspectDepth: options?.inspectDepth
130
+ };
131
+ const formattedValue = formatValue(value, formatOptions);
86
132
  if (options?.formatter) {
87
133
  const formatted = options.formatter({
88
134
  location,
@@ -94,7 +140,9 @@ function log(arg1, arg2, arg3) {
94
140
  console.log(formatted.valueLine);
95
141
  return;
96
142
  }
97
- console.log(location);
143
+ if (includeLocation) {
144
+ console.log(location);
145
+ }
98
146
  console.log(formatLabeledValue(label, formattedValue));
99
147
  }
100
148
  var __internal = {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "where-log",
3
- "version": "0.1.1",
3
+ "version": "0.2.0",
4
4
  "description": "Log values with caller file and line number.",
5
5
  "license": "MIT",
6
6
  "private": false,
@@ -24,6 +24,8 @@
24
24
  "typecheck": "tsc --noEmit",
25
25
  "test": "vitest run --pool=threads",
26
26
  "dev": "vitest --pool=threads",
27
+ "bench": "node bench/run-bench.mjs",
28
+ "bench:ci": "node bench/run-bench.mjs --ci",
27
29
  "pack:dry-run": "npm pack --dry-run --cache ./.npm-cache",
28
30
  "publish:dry-run": "npm publish --dry-run --cache ./.npm-cache",
29
31
  "release:check": "npm run pack:dry-run && npm run publish:dry-run"
@@ -43,4 +45,4 @@
43
45
  "typescript": "^5.5.4",
44
46
  "vitest": "^2.0.5"
45
47
  }
46
- }
48
+ }