{"version":3,"file":"redact.mjs","names":[],"sources":["../../../../../../../@warlock.js/logger/src/redact/redact.ts"],"sourcesContent":["import type { LoggingData, RedactCensor, RedactConfig } from \"../types\";\n\n/**\n * Deep-clone a value with structural fidelity for log entries — handles plain\n * objects, arrays, `Date`, `Error`, and primitives. Anything else is copied\n * by reference (we only redact paths through plain objects/arrays anyway,\n * and rebuilding e.g. a `Buffer` would change semantics).\n *\n * Purpose-built rather than reaching for `structuredClone`: `Error` instances\n * lose their `message`/`stack` under `structuredClone` in some Node versions,\n * and the logger pipeline carries them often.\n */\nfunction cloneEntry<T>(value: T, seen = new WeakMap<object, any>()): T {\n  if (value === null || typeof value !== \"object\") {\n    return value;\n  }\n\n  if (seen.has(value as unknown as object)) {\n    return seen.get(value as unknown as object);\n  }\n\n  if (value instanceof Date) {\n    return new Date(value.getTime()) as unknown as T;\n  }\n\n  if (value instanceof Error) {\n    const copy = new (value.constructor as ErrorConstructor)(value.message);\n    copy.stack = value.stack;\n    copy.name = value.name;\n    return copy as unknown as T;\n  }\n\n  if (Array.isArray(value)) {\n    const arr: any[] = [];\n    seen.set(value as unknown as object, arr);\n    for (const item of value) {\n      arr.push(cloneEntry(item, seen));\n    }\n    return arr as unknown as T;\n  }\n\n  const out: Record<string, any> = {};\n  seen.set(value as unknown as object, out);\n  for (const key of Object.keys(value as Record<string, any>)) {\n    out[key] = cloneEntry((value as Record<string, any>)[key], seen);\n  }\n  return out as unknown as T;\n}\n\n/**\n * Apply a single censor decision to a value. String censors are returned\n * verbatim; function censors receive the original value plus the dotted\n * path so callers can implement value-aware redaction (mask all but the\n * last 4 chars, hash, etc.).\n */\nfunction applyCensor(value: any, censor: RedactCensor, path: string[]): any {\n  if (typeof censor === \"function\") {\n    return censor(value, path.join(\".\"));\n  }\n  return censor;\n}\n\n/**\n * Walk `target` following the remaining `segments` of a path pattern,\n * replacing matched leaves via `censor`. Operates in place — the caller\n * is responsible for cloning before calling.\n *\n * Wildcards:\n * - `*` matches exactly one segment (any key on a plain object, any index\n *   on an array — stringified for the path that's passed to a function\n *   censor).\n * - `**` matches zero or more segments greedily; the rest of the pattern\n *   is then attempted at the current level and at every descendant.\n */\nfunction redactAtPath(\n  target: any,\n  segments: string[],\n  censor: RedactCensor,\n  pathTrail: string[],\n): void {\n  if (target === null || typeof target !== \"object\") {\n    return;\n  }\n\n  if (segments.length === 0) {\n    return;\n  }\n\n  const [head, ...rest] = segments;\n\n  if (head === \"**\") {\n    // Try matching `rest` at the current level (the zero-segment match\n    // case), then recurse into every child carrying the `**` forward so\n    // it keeps matching at deeper levels too.\n    if (rest.length > 0) {\n      redactAtPath(target, rest, censor, pathTrail);\n    }\n    const keys = Array.isArray(target)\n      ? target.map((_, index) => String(index))\n      : Object.keys(target);\n    for (const key of keys) {\n      redactAtPath(target[key], segments, censor, [...pathTrail, key]);\n    }\n    return;\n  }\n\n  const keysToVisit =\n    head === \"*\"\n      ? Array.isArray(target)\n        ? target.map((_, index) => String(index))\n        : Object.keys(target)\n      : Array.isArray(target)\n        ? // Numeric segment can index into an array.\n          /^\\d+$/.test(head) && Number(head) < target.length\n          ? [head]\n          : []\n        : Object.prototype.hasOwnProperty.call(target, head)\n          ? [head]\n          : [];\n\n  for (const key of keysToVisit) {\n    if (rest.length === 0) {\n      target[key] = applyCensor(target[key], censor, [...pathTrail, key]);\n    } else {\n      redactAtPath(target[key], rest, censor, [...pathTrail, key]);\n    }\n  }\n}\n\n/**\n * Produce a new `LoggingData` with every path in `config.paths` replaced\n * by `config.censor`. The original entry is never mutated — channels and\n * other call sites can hold references to the input safely.\n *\n * No-op (returns the input by reference) when `config` is `undefined` or\n * its `paths` array is empty, so the fast path stays fast.\n */\nexport function applyRedact(\n  data: LoggingData,\n  config: RedactConfig | undefined,\n): LoggingData {\n  if (!config || config.paths.length === 0) {\n    return data;\n  }\n\n  const censor = config.censor ?? \"[REDACTED]\";\n  const cloned = cloneEntry(data);\n\n  for (const pattern of config.paths) {\n    const segments = pattern.split(\".\").filter((segment) => segment.length > 0);\n    if (segments.length === 0) continue;\n    redactAtPath(cloned, segments, censor, []);\n  }\n\n  return cloned;\n}\n\n/**\n * Combine two redact configs into one effective config. Used to merge a\n * channel's additive paths on top of the logger-wide floor.\n *\n * - `paths` are concatenated; duplicates are kept (the matcher tolerates\n *   them, and de-duping cross-config would mask a developer typo).\n * - `censor` from the channel wins; falls back to the logger's; falls back\n *   to the default `\"[REDACTED]\"`.\n */\nexport function mergeRedact(\n  base: RedactConfig | undefined,\n  extra: RedactConfig | undefined,\n): RedactConfig | undefined {\n  if (!base && !extra) return undefined;\n  if (!base) return extra;\n  if (!extra) return base;\n\n  return {\n    paths: [...base.paths, ...extra.paths],\n    censor: extra.censor ?? base.censor,\n  };\n}\n"],"mappings":"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"}