where-log 0.2.0 → 0.2.1

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/dist/index.mjs CHANGED
@@ -1,3 +1,32 @@
1
+ // src/core/context.ts
2
+ function isRecord(value) {
3
+ return typeof value === "object" && value !== null && !Array.isArray(value);
4
+ }
5
+ function mergeContexts(base, incoming) {
6
+ if (!base && !incoming) return void 0;
7
+ return {
8
+ ...base ?? {},
9
+ ...incoming ?? {}
10
+ };
11
+ }
12
+ function injectContext(value, context) {
13
+ if (!context) return value;
14
+ if (isRecord(value)) {
15
+ const existing = isRecord(value.context) ? value.context : void 0;
16
+ return {
17
+ ...value,
18
+ context: {
19
+ ...existing ?? {},
20
+ ...context
21
+ }
22
+ };
23
+ }
24
+ return {
25
+ value,
26
+ context
27
+ };
28
+ }
29
+
1
30
  // src/core/format.ts
2
31
  import util from "util";
3
32
  function isNodeRuntime() {
@@ -52,6 +81,62 @@ function formatLabeledValue(label, formattedValue) {
52
81
  return `${label}: ${String(formattedValue)}`;
53
82
  }
54
83
 
84
+ // src/core/level.ts
85
+ var LEVEL_METHOD_MAP = {
86
+ info: "info",
87
+ success: "log",
88
+ warn: "warn",
89
+ error: "error",
90
+ debug: "debug"
91
+ };
92
+ function levelToConsoleMethod(level) {
93
+ return LEVEL_METHOD_MAP[level];
94
+ }
95
+ function levelToTag(level) {
96
+ return `[${level.toUpperCase()}]`;
97
+ }
98
+ function resolveConsoleMethod(level, override) {
99
+ if (override) return override;
100
+ if (!level) return "log";
101
+ return levelToConsoleMethod(level);
102
+ }
103
+
104
+ // src/core/session.ts
105
+ var onceKeys = /* @__PURE__ */ new Set();
106
+ var timers = /* @__PURE__ */ new Map();
107
+ function checkAndMarkOnce(key) {
108
+ if (onceKeys.has(key)) return false;
109
+ onceKeys.add(key);
110
+ return true;
111
+ }
112
+ function clearOnce(keys) {
113
+ if (!keys || keys.length === 0) {
114
+ onceKeys.clear();
115
+ return;
116
+ }
117
+ for (const key of keys) {
118
+ onceKeys.delete(key);
119
+ }
120
+ }
121
+ function startTimer(key, now) {
122
+ timers.set(key, { startedAt: now });
123
+ }
124
+ function endTimer(key, now) {
125
+ const entry = timers.get(key);
126
+ if (!entry) return null;
127
+ timers.delete(key);
128
+ return Math.max(0, now - entry.startedAt);
129
+ }
130
+ function clearTimers(keys) {
131
+ if (!keys || keys.length === 0) {
132
+ timers.clear();
133
+ return;
134
+ }
135
+ for (const key of keys) {
136
+ timers.delete(key);
137
+ }
138
+ }
139
+
55
140
  // src/core/stack.ts
56
141
  function safeToInt(input) {
57
142
  const n = Number.parseInt(input, 10);
@@ -101,13 +186,88 @@ function getCallerFromStack(stack) {
101
186
  return { file: "unknown", line: 0 };
102
187
  }
103
188
 
189
+ // src/core/transform.ts
190
+ function isObjectLike(value) {
191
+ return typeof value === "object" && value !== null;
192
+ }
193
+ function cloneDeep(value, seen = /* @__PURE__ */ new WeakMap()) {
194
+ if (!isObjectLike(value)) return value;
195
+ if (seen.has(value)) return seen.get(value);
196
+ if (Array.isArray(value)) {
197
+ const arr = [];
198
+ seen.set(value, arr);
199
+ for (let i = 0; i < value.length; i += 1) {
200
+ arr.push(cloneDeep(value[i], seen));
201
+ }
202
+ return arr;
203
+ }
204
+ const obj = {};
205
+ seen.set(value, obj);
206
+ for (const [k, v] of Object.entries(value)) {
207
+ obj[k] = cloneDeep(v, seen);
208
+ }
209
+ return obj;
210
+ }
211
+ function redactPath(target, path) {
212
+ if (!isObjectLike(target) || path.length === 0) return;
213
+ const [head, ...rest] = path;
214
+ if (Array.isArray(target)) {
215
+ const idx = Number.parseInt(head, 10);
216
+ if (!Number.isFinite(idx) || idx < 0 || idx >= target.length) return;
217
+ if (rest.length === 0) {
218
+ target[idx] = "[REDACTED]";
219
+ return;
220
+ }
221
+ redactPath(target[idx], rest);
222
+ return;
223
+ }
224
+ if (!(head in target)) return;
225
+ if (rest.length === 0) {
226
+ target[head] = "[REDACTED]";
227
+ return;
228
+ }
229
+ redactPath(target[head], rest);
230
+ }
231
+ function truncateArrays(target, maxArrayLength) {
232
+ if (!isObjectLike(target)) return target;
233
+ if (Array.isArray(target)) {
234
+ const sliced = target.slice(0, maxArrayLength).map((item) => truncateArrays(item, maxArrayLength));
235
+ if (target.length > maxArrayLength) {
236
+ sliced.push(`[... ${target.length - maxArrayLength} more items]`);
237
+ }
238
+ return sliced;
239
+ }
240
+ const out = {};
241
+ for (const [k, v] of Object.entries(target)) {
242
+ out[k] = truncateArrays(v, maxArrayLength);
243
+ }
244
+ return out;
245
+ }
246
+ function transformValue(value, options) {
247
+ if (!options?.redact?.length && options?.maxArrayLength == null) {
248
+ return value;
249
+ }
250
+ const cloned = cloneDeep(value);
251
+ if (options.redact?.length) {
252
+ for (const rawPath of options.redact) {
253
+ const path = rawPath.split(".").map((part) => part.trim()).filter(Boolean);
254
+ if (path.length === 0) continue;
255
+ redactPath(cloned, path);
256
+ }
257
+ }
258
+ if (typeof options.maxArrayLength === "number" && options.maxArrayLength >= 0) {
259
+ return truncateArrays(cloned, options.maxArrayLength);
260
+ }
261
+ return cloned;
262
+ }
263
+
104
264
  // src/index.ts
105
265
  function isLogOptions(value) {
106
266
  if (!value || typeof value !== "object") return false;
107
267
  const record = value;
108
- return "colors" in record || "formatter" in record || "mode" in record || "includeLocation" in record || "inspectDepth" in record;
268
+ return "colors" in record || "formatter" in record || "enabled" in record || "mode" in record || "includeLocation" in record || "inspectDepth" in record || "maxArrayLength" in record || "redact" in record || "level" in record || "showLevelTag" in record || "levelTagStyle" in record || "consoleMethod" in record || "context" in record || "clockNow" in record || "warnThresholdMs" in record || "errorThresholdMs" in record || "includeDurationOnly" in record;
109
269
  }
110
- function log(arg1, arg2, arg3) {
270
+ function resolveLogArgs(arg1, arg2, arg3) {
111
271
  let label;
112
272
  let value;
113
273
  let options;
@@ -119,39 +279,338 @@ function log(arg1, arg2, arg3) {
119
279
  value = arg1;
120
280
  options = isLogOptions(arg2) ? arg2 : void 0;
121
281
  }
282
+ return { label, value, options };
283
+ }
284
+ function resolveOnceArgs(key, arg2, arg3, arg4) {
285
+ if (typeof arg2 === "string" && arg3 !== void 0 && !isLogOptions(arg3)) {
286
+ return {
287
+ key,
288
+ label: arg2,
289
+ value: arg3,
290
+ options: arg4
291
+ };
292
+ }
293
+ return {
294
+ key,
295
+ value: arg2,
296
+ options: isLogOptions(arg3) ? arg3 : void 0
297
+ };
298
+ }
299
+ function resolveTimeEndArgs(key, arg2, arg3, arg4) {
300
+ if (arg2 === void 0) {
301
+ return { key };
302
+ }
303
+ if (isLogOptions(arg2)) {
304
+ return { key, options: arg2 };
305
+ }
306
+ if (typeof arg2 === "string" && arg3 !== void 0 && !isLogOptions(arg3)) {
307
+ return {
308
+ key,
309
+ label: arg2,
310
+ value: arg3,
311
+ options: arg4
312
+ };
313
+ }
314
+ return {
315
+ key,
316
+ value: arg2,
317
+ options: isLogOptions(arg3) ? arg3 : void 0
318
+ };
319
+ }
320
+ function writeLine(method, line) {
321
+ const sink = console[method];
322
+ if (typeof sink === "function") {
323
+ sink(line);
324
+ return;
325
+ }
326
+ console.log(line);
327
+ }
328
+ function runLog(args, ctx) {
329
+ const { label, value, options } = args;
330
+ if (options?.enabled === false) {
331
+ return;
332
+ }
333
+ const level = options?.level ?? ctx?.forceLevel;
334
+ const consoleMethod = resolveConsoleMethod(level, options?.consoleMethod);
335
+ const showLevelTag = options?.showLevelTag ?? (ctx?.defaultShowLevelTag ?? false);
122
336
  const includeLocation = options?.includeLocation ?? true;
123
337
  const stack = includeLocation ? new Error().stack : void 0;
124
338
  const caller = includeLocation ? getCallerFromStack(stack) : { file: "disabled", line: 0 };
125
339
  const location = `${caller.file}:${caller.line}`;
340
+ const mergedContext = mergeContexts(ctx?.defaultContext, options?.context);
341
+ const contextualValue = injectContext(value, mergedContext);
342
+ const transformedValue = transformValue(contextualValue, {
343
+ redact: options?.redact,
344
+ maxArrayLength: options?.maxArrayLength
345
+ });
126
346
  const formatOptions = {
127
347
  colors: options?.colors,
128
348
  mode: options?.mode,
129
349
  inspectDepth: options?.inspectDepth
130
350
  };
131
- const formattedValue = formatValue(value, formatOptions);
351
+ const formattedValue = formatValue(transformedValue, formatOptions);
352
+ const levelTag = level ? levelToTag(level) : void 0;
132
353
  if (options?.formatter) {
133
354
  const formatted = options.formatter({
134
355
  location,
135
356
  label,
136
- value,
137
- formattedValue
357
+ value: transformedValue,
358
+ formattedValue,
359
+ level,
360
+ levelTag
138
361
  });
139
- console.log(formatted.locationLine);
140
- console.log(formatted.valueLine);
362
+ writeLine(consoleMethod, formatted.locationLine);
363
+ writeLine(consoleMethod, formatted.valueLine);
141
364
  return;
142
365
  }
143
366
  if (includeLocation) {
144
- console.log(location);
367
+ writeLine(consoleMethod, location);
145
368
  }
146
- console.log(formatLabeledValue(label, formattedValue));
369
+ const labeledValue = formatLabeledValue(label, formattedValue);
370
+ const valueLine = showLevelTag && levelTag ? `${levelTag} ${String(labeledValue)}` : labeledValue;
371
+ writeLine(consoleMethod, valueLine);
372
+ }
373
+ var DEV_PRESET = {
374
+ mode: "pretty",
375
+ includeLocation: true,
376
+ colors: true
377
+ };
378
+ var PROD_PRESET = {
379
+ mode: "fast",
380
+ includeLocation: false,
381
+ colors: false
382
+ };
383
+ function mergeOptions(base, incoming) {
384
+ if (!base && !incoming) return void 0;
385
+ const merged = {
386
+ ...base ?? {},
387
+ ...incoming ?? {}
388
+ };
389
+ merged.context = mergeContexts(base?.context, incoming?.context);
390
+ return merged;
391
+ }
392
+ function resolveTimingLevel(durationMs, options) {
393
+ const errorThreshold = options?.errorThresholdMs;
394
+ const warnThreshold = options?.warnThresholdMs;
395
+ if (typeof errorThreshold === "number" && durationMs >= errorThreshold) return "error";
396
+ if (typeof warnThreshold === "number" && durationMs >= warnThreshold) return "warn";
397
+ return "info";
398
+ }
399
+ function log(arg1, arg2, arg3) {
400
+ runLog(resolveLogArgs(arg1, arg2, arg3));
401
+ }
402
+ function once(key, arg2, arg3, arg4) {
403
+ const resolved = resolveOnceArgs(key, arg2, arg3, arg4);
404
+ if (resolved.options?.enabled === false) return;
405
+ if (!checkAndMarkOnce(key)) return;
406
+ runLog({ label: resolved.label, value: resolved.value, options: resolved.options });
407
+ }
408
+ function resetOnce(keys) {
409
+ clearOnce(keys);
410
+ }
411
+ function time(key, options) {
412
+ if (options?.enabled === false) return;
413
+ const now = options?.clockNow?.() ?? Date.now();
414
+ startTimer(key, now);
415
+ }
416
+ function timeEnd(key, arg2, arg3, arg4) {
417
+ const resolved = resolveTimeEndArgs(key, arg2, arg3, arg4);
418
+ if (resolved.options?.enabled === false) return;
419
+ const now = resolved.options?.clockNow?.() ?? Date.now();
420
+ const durationMs = endTimer(key, now);
421
+ if (durationMs == null) {
422
+ runLog(
423
+ {
424
+ label: resolved.label ?? "timer",
425
+ value: {
426
+ key,
427
+ error: "timer_not_started"
428
+ },
429
+ options: resolved.options
430
+ },
431
+ {
432
+ forceLevel: "warn",
433
+ defaultShowLevelTag: true
434
+ }
435
+ );
436
+ return;
437
+ }
438
+ const timedPayload = resolved.options?.includeDurationOnly ? { durationMs } : {
439
+ key,
440
+ durationMs,
441
+ ...resolved.value !== void 0 ? { value: resolved.value } : {}
442
+ };
443
+ const timingLevel = resolveTimingLevel(durationMs, resolved.options);
444
+ runLog(
445
+ {
446
+ label: resolved.label ?? "timer",
447
+ value: timedPayload,
448
+ options: resolved.options
449
+ },
450
+ {
451
+ forceLevel: timingLevel,
452
+ defaultShowLevelTag: true
453
+ }
454
+ );
455
+ }
456
+ function resetTimers(keys) {
457
+ clearTimers(keys);
458
+ }
459
+ function logDev(arg1, arg2, arg3) {
460
+ const resolved = resolveLogArgs(arg1, arg2, arg3);
461
+ runLog({
462
+ ...resolved,
463
+ options: mergeOptions(DEV_PRESET, resolved.options)
464
+ });
465
+ }
466
+ function logProd(arg1, arg2, arg3) {
467
+ const resolved = resolveLogArgs(arg1, arg2, arg3);
468
+ runLog({
469
+ ...resolved,
470
+ options: mergeOptions(PROD_PRESET, resolved.options)
471
+ });
472
+ }
473
+ function makeLevelMethod(level, presetOptions, defaultContext) {
474
+ const method = (arg1, arg2, arg3) => {
475
+ const resolved = resolveLogArgs(arg1, arg2, arg3);
476
+ runLog(
477
+ {
478
+ ...resolved,
479
+ options: mergeOptions(presetOptions, resolved.options)
480
+ },
481
+ {
482
+ forceLevel: level,
483
+ defaultShowLevelTag: true,
484
+ defaultContext
485
+ }
486
+ );
487
+ };
488
+ return method;
489
+ }
490
+ function createLogger(presetOptions) {
491
+ const baseContext = presetOptions?.context;
492
+ const fn = (arg1, arg2, arg3) => {
493
+ const resolved = resolveLogArgs(arg1, arg2, arg3);
494
+ runLog(
495
+ {
496
+ ...resolved,
497
+ options: mergeOptions(presetOptions, resolved.options)
498
+ },
499
+ {
500
+ defaultContext: baseContext
501
+ }
502
+ );
503
+ };
504
+ const logger = fn;
505
+ logger.info = makeLevelMethod("info", presetOptions, baseContext);
506
+ logger.success = makeLevelMethod("success", presetOptions, baseContext);
507
+ logger.warn = makeLevelMethod("warn", presetOptions, baseContext);
508
+ logger.error = makeLevelMethod("error", presetOptions, baseContext);
509
+ logger.debug = makeLevelMethod("debug", presetOptions, baseContext);
510
+ logger.once = ((key, arg2, arg3, arg4) => {
511
+ const resolved = resolveOnceArgs(key, arg2, arg3, arg4);
512
+ const merged = mergeOptions(presetOptions, resolved.options);
513
+ if (merged?.enabled === false) return;
514
+ if (!checkAndMarkOnce(key)) return;
515
+ runLog(
516
+ {
517
+ label: resolved.label,
518
+ value: resolved.value,
519
+ options: merged
520
+ },
521
+ {
522
+ defaultContext: baseContext
523
+ }
524
+ );
525
+ });
526
+ logger.time = ((key, options) => {
527
+ const merged = mergeOptions(presetOptions, options);
528
+ if (merged?.enabled === false) return;
529
+ const now = merged?.clockNow?.() ?? Date.now();
530
+ startTimer(key, now);
531
+ });
532
+ logger.timeEnd = ((key, arg2, arg3, arg4) => {
533
+ const resolved = resolveTimeEndArgs(key, arg2, arg3, arg4);
534
+ const merged = mergeOptions(presetOptions, resolved.options);
535
+ if (merged?.enabled === false) return;
536
+ const now = merged?.clockNow?.() ?? Date.now();
537
+ const durationMs = endTimer(key, now);
538
+ if (durationMs == null) {
539
+ runLog(
540
+ {
541
+ label: resolved.label ?? "timer",
542
+ value: { key, error: "timer_not_started" },
543
+ options: merged
544
+ },
545
+ {
546
+ forceLevel: "warn",
547
+ defaultShowLevelTag: true,
548
+ defaultContext: baseContext
549
+ }
550
+ );
551
+ return;
552
+ }
553
+ const timedPayload = merged?.includeDurationOnly ? { durationMs } : {
554
+ key,
555
+ durationMs,
556
+ ...resolved.value !== void 0 ? { value: resolved.value } : {}
557
+ };
558
+ const level = resolveTimingLevel(durationMs, merged);
559
+ runLog(
560
+ {
561
+ label: resolved.label ?? "timer",
562
+ value: timedPayload,
563
+ options: merged
564
+ },
565
+ {
566
+ forceLevel: level,
567
+ defaultShowLevelTag: true,
568
+ defaultContext: baseContext
569
+ }
570
+ );
571
+ });
572
+ logger.withContext = (context) => {
573
+ return createLogger(mergeOptions(presetOptions, { context }));
574
+ };
575
+ logger.resetOnce = (keys) => clearOnce(keys);
576
+ logger.resetTimers = (keys) => clearTimers(keys);
577
+ return logger;
578
+ }
579
+ function withContext(context, presetOptions) {
580
+ return createLogger(mergeOptions(presetOptions, { context }));
147
581
  }
582
+ var info = makeLevelMethod("info");
583
+ var success = makeLevelMethod("success");
584
+ var warn = makeLevelMethod("warn");
585
+ var error = makeLevelMethod("error");
586
+ var debug = makeLevelMethod("debug");
148
587
  var __internal = {
149
588
  getCallerFromStack,
150
589
  parseFrameLine,
151
590
  formatLabeledValue,
152
- isLogOptions
591
+ isLogOptions,
592
+ resolveLogArgs,
593
+ mergeOptions,
594
+ writeLine,
595
+ resolveOnceArgs,
596
+ resolveTimeEndArgs,
597
+ resolveTimingLevel
153
598
  };
154
599
  export {
155
600
  __internal,
156
- log
601
+ createLogger,
602
+ debug,
603
+ error,
604
+ info,
605
+ log,
606
+ logDev,
607
+ logProd,
608
+ once,
609
+ resetOnce,
610
+ resetTimers,
611
+ success,
612
+ time,
613
+ timeEnd,
614
+ warn,
615
+ withContext
157
616
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "where-log",
3
- "version": "0.2.0",
3
+ "version": "0.2.1",
4
4
  "description": "Log values with caller file and line number.",
5
5
  "license": "MIT",
6
6
  "private": false,
@@ -23,6 +23,7 @@
23
23
  "build": "tsup --config tsup.config.ts",
24
24
  "typecheck": "tsc --noEmit",
25
25
  "test": "vitest run --pool=threads",
26
+ "smoke:imports": "node scripts/smoke-imports.mjs",
26
27
  "dev": "vitest --pool=threads",
27
28
  "bench": "node bench/run-bench.mjs",
28
29
  "bench:ci": "node bench/run-bench.mjs --ci",