@vielzeug/rune 2.1.1 → 2.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/dist/errors.cjs +2 -0
- package/dist/errors.cjs.map +1 -0
- package/dist/errors.d.ts +6 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +10 -0
- package/dist/errors.js.map +1 -0
- package/dist/index.cjs +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -4
- package/dist/rune.cjs +1 -1
- package/dist/rune.cjs.map +1 -1
- package/dist/rune.iife.js +1 -1
- package/dist/rune.iife.js.map +1 -1
- package/dist/rune.js +1 -1
- package/dist/rune.js.map +1 -1
- package/dist/transports.cjs +1 -1
- package/dist/transports.cjs.map +1 -1
- package/dist/transports.d.ts.map +1 -1
- package/dist/transports.js +62 -61
- package/dist/transports.js.map +1 -1
- package/package.json +1 -1
package/dist/transports.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"transports.js","names":[],"sources":["../src/transports.ts"],"sourcesContent":["import { warn } from './_dev';\nimport { isUnsafeObjectKey } from './_prototype';\nimport type {\n BatchHandle,\n BatchTransportOptions,\n Bindings,\n JsonTransportOptions,\n LogEntry,\n PipeOptions,\n RedactTransportOptions,\n RemoteLogData,\n RemoteTransportOptions,\n SampleTransportOptions,\n Transport,\n} from './types';\nimport { isLevelEnabled } from './types';\n\n/* ─── Environment detection ─── */\n\nfunction detectEnv(): 'development' | 'production' {\n if (typeof window === 'undefined') {\n return (globalThis as Record<string, unknown> & { process?: { env?: { NODE_ENV?: string } } }).process?.env\n ?.NODE_ENV === 'production'\n ? 'production'\n : 'development';\n }\n\n return 'development';\n}\n\n/* ─── remoteTransport ─── */\n\n/**\n * Forwards log entries asynchronously to a remote handler.\n * The handler is fire-and-forget. Use onError to observe delivery failures.\n * Console and remote thresholds are fully independent.\n *\n * **Security note:** serialized `Error` objects include the full `stack` trace,\n * which may expose internal file paths. Use `redactTransport` or a middleware\n * to strip `err.stack` before forwarding in production if this is a concern.\n *\n * @example\n * remoteTransport({\n * handler: async (type, data) => {\n * await fetch('/api/logs', { body: JSON.stringify(data), method: 'POST' });\n * },\n * level: 'error',\n * })\n */\nexport function remoteTransport(options: RemoteTransportOptions): Transport {\n const { handler } = options;\n const level = options.level ?? 'debug';\n const env = options.env ?? detectEnv();\n const onError = options.onError ?? ((err: unknown) => warn(`remote transport error: ${String(err)}`));\n\n return (entry: LogEntry): void => {\n if (!isLevelEnabled(level, entry.level)) return;\n\n const hasData = Object.keys(entry.data).length > 0;\n const payload: RemoteLogData = {\n data: hasData ? entry.data : undefined,\n env,\n level: entry.level,\n message: entry.message,\n namespace: entry.namespace || undefined,\n timestamp: entry.timestamp.toISOString(),\n };\n\n Promise.resolve()\n .then(() => handler(entry.level, payload))\n .catch((err: unknown) => onError(err, payload));\n };\n}\n\n/* ─── jsonTransport ─── */\n\nfunction makeCircularReplacer(): (_key: string, value: unknown) => unknown {\n const seen = new WeakSet();\n\n return (_key: string, value: unknown): unknown => {\n if (typeof value === 'object' && value !== null) {\n if (seen.has(value)) return '[Circular]';\n\n seen.add(value);\n }\n\n return value;\n };\n}\n\n/**\n * Writes newline-delimited JSON (NDJSON) to stdout or a custom output function.\n * Useful for structured log aggregation pipelines in Node.js (ELK, Datadog, etc.).\n *\n * @example\n * jsonTransport({ level: 'info' })\n * jsonTransport({ safe: true }) // handles circular references gracefully\n * jsonTransport({ output: (line) => fs.appendFileSync('app.log', line + '\\n') })\n */\nexport function jsonTransport(options: JsonTransportOptions = {}): Transport {\n const level = options.level ?? 'debug';\n const f = options.fields ?? {};\n const fLevel = f.level ?? 'level';\n const fTime = f.time ?? 'time';\n const fNs = f.ns ?? 'ns';\n const fMsg = f.msg ?? 'msg';\n const safe = options.safe ?? false;\n const output =\n options.output ??\n ((line: string) => {\n (\n globalThis as Record<string, unknown> & { process?: { stdout?: { write: (s: string) => void } } }\n ).process?.stdout?.write(`${line}\\n`);\n });\n\n return (entry: LogEntry): void => {\n if (!isLevelEnabled(level, entry.level)) return;\n\n const record: Record<string, unknown> = {\n ...entry.data,\n [fLevel]: entry.level,\n [fTime]: entry.timestamp.toISOString(),\n ...(entry.namespace && { [fNs]: entry.namespace }),\n ...(entry.message !== undefined && { [fMsg]: entry.message }),\n };\n\n output(JSON.stringify(record, safe ? makeCircularReplacer() : undefined));\n };\n}\n\n/* ─── Transport option validation ─── */\n\nfunction assertFiniteNumber(value: number, name: string): void {\n if (!Number.isFinite(value)) throw new RangeError(`${name} must be a finite number`);\n}\n\nfunction assertNonNegativeInteger(value: number, name: string): void {\n assertFiniteNumber(value, name);\n\n if (!Number.isInteger(value) || value < 0) throw new RangeError(`${name} must be a non-negative integer`);\n}\n\nfunction assertPositiveInteger(value: number, name: string): void {\n assertFiniteNumber(value, name);\n\n if (!Number.isInteger(value) || value <= 0) throw new RangeError(`${name} must be a positive integer`);\n}\n\n/* ─── batchTransport ─── */\n\n/**\n * Buffers log entries and flushes them in batches, reducing I/O overhead.\n * Flushes when the buffer reaches maxSize or after the interval elapses.\n *\n * Returns a `BatchHandle` with `.transport`, `.flush()`, and `.dispose()` methods:\n * - `.transport` — pass to `createLogger({ transports: [handle.transport] })`\n * - `.flush()` — immediately send buffered entries and wait for delivery without stopping the timer\n * - `.dispose()` — stop the interval, flush remaining entries, and wait for delivery\n *\n * Use `onFlushError` to observe failed flushes (e.g. dead-letter queue).\n *\n * @example\n * const batch = batchTransport({\n * onFlush: (entries) => sendToCollector(entries),\n * onFlushError: (entries, err) => deadLetter.push(entries),\n * interval: 10_000,\n * maxSize: 100,\n * });\n * createLogger({ transports: [batch.transport] });\n * await batch.dispose(); // call during graceful shutdown\n */\nexport function batchTransport(options: BatchTransportOptions): BatchHandle {\n const level = options.level ?? 'debug';\n const maxSize = options.maxSize ?? 50;\n const maxBuffer = options.maxBuffer;\n const interval = options.interval ?? 5000;\n\n assertFiniteNumber(interval, 'batchTransport interval');\n\n if (interval <= 0) throw new RangeError('batchTransport interval must be greater than zero');\n\n assertPositiveInteger(maxSize, 'batchTransport maxSize');\n\n if (maxBuffer !== undefined) assertNonNegativeInteger(maxBuffer, 'batchTransport maxBuffer');\n\n let buffer: LogEntry[] = [];\n let timer: ReturnType<typeof setInterval> | undefined;\n let automaticFailure: { error: unknown } | undefined;\n let batchDisposed = false;\n let deliveryTail: Promise<void> = Promise.resolve();\n let disposePromise: Promise<void> | undefined;\n\n const deliver = async (entries: LogEntry[]): Promise<void> => {\n try {\n await options.onFlush(entries);\n } catch (err) {\n try {\n options.onFlushError?.(entries, err);\n } catch (observerErr) {\n warn(`batch transport error observer threw: ${String(observerErr)}`);\n }\n\n throw err;\n }\n };\n\n const enqueue = (entries: LogEntry[]): Promise<void> => {\n const delivery = deliveryTail.then(() => deliver(entries));\n\n deliveryTail = delivery.catch(() => undefined);\n\n return delivery;\n };\n\n const flush = (): Promise<void> => {\n if (buffer.length === 0) return deliveryTail;\n\n const entries = buffer;\n\n buffer = [];\n\n return enqueue(entries);\n };\n\n const flushAutomatically = (): void => {\n void flush().catch((error: unknown) => {\n automaticFailure ??= { error };\n });\n };\n\n const transportFn: Transport = (entry: LogEntry): void => {\n if (batchDisposed) return;\n\n if (!isLevelEnabled(level, entry.level)) return;\n\n if (!timer) timer = setInterval(flushAutomatically, interval);\n\n buffer.push(entry);\n\n if (maxBuffer !== undefined && buffer.length > maxBuffer) {\n buffer = buffer.slice(buffer.length - maxBuffer);\n }\n\n if (buffer.length >= maxSize) flushAutomatically();\n };\n\n const handle: BatchHandle = {\n dispose(): Promise<void> {\n if (disposePromise) return disposePromise;\n\n batchDisposed = true;\n\n if (timer) {\n clearInterval(timer);\n timer = undefined;\n }\n\n disposePromise = flush().then(() => {\n if (automaticFailure) throw automaticFailure.error;\n });\n\n return disposePromise;\n },\n get disposed(): boolean {\n return batchDisposed;\n },\n flush,\n [Symbol.asyncDispose](): Promise<void> {\n return handle.dispose();\n },\n transport: transportFn,\n };\n\n return handle;\n}\n\n/* ─── sampleTransport ─── */\n\n/**\n * Probabilistically forwards entries to a downstream transport.\n * Useful for reducing volume of high-frequency debug logs in production.\n *\n * @example\n * sampleTransport({ rate: 0.1, transport: remoteTransport({ handler }) })\n */\nexport function sampleTransport(options: SampleTransportOptions): Transport {\n const { rate, transport } = options;\n const level = options.level ?? 'debug';\n\n assertFiniteNumber(rate, 'sampleTransport rate');\n\n if (rate < 0 || rate > 1) throw new RangeError('sampleTransport rate must be between zero and one');\n\n return (entry: LogEntry): void => {\n if (!isLevelEnabled(level, entry.level)) return;\n\n if (Math.random() < rate) transport(entry);\n };\n}\n\n/* ─── redactTransport ─── */\n\n/**\n * Strips sensitive fields from `data` before forwarding to a downstream transport.\n * Redaction is applied recursively at any depth, including inside arrays.\n *\n * @example\n * redactTransport({\n * keys: ['password', 'token', 'ssn'],\n * replacement: '[REDACTED]',\n * transport: remoteTransport({ handler }),\n * })\n */\nexport function redactTransport(options: RedactTransportOptions): Transport {\n const { keys, maxDepth = 20, replacement = '[REDACTED]', transport } = options;\n\n assertNonNegativeInteger(maxDepth, 'redactTransport maxDepth');\n\n for (const key of keys) {\n if (key.includes('.')) {\n warn(\n `redactTransport: key \"${key}\" contains a dot. Dot-path notation is not supported — use the plain field name (e.g. 'password') to redact at any depth.`,\n );\n }\n }\n\n const keySet = new Set(keys);\n\n function redactValue(v: unknown, depth = 0): unknown {\n if (depth > maxDepth) {\n warn(\n `redactTransport: object nesting depth exceeded ${maxDepth} — redaction truncated at this level. Sensitive fields below depth ${maxDepth} may not be redacted.`,\n );\n\n return v;\n }\n\n if (Array.isArray(v)) return v.map((item) => redactValue(item, depth + 1));\n\n if (typeof v === 'object' && v !== null) return redactObject(v as Bindings, depth + 1);\n\n return v;\n }\n\n function redactObject(obj: Bindings, depth = 0): Bindings {\n const result: Bindings = {};\n\n for (const [k, v] of Object.entries(obj)) {\n // Guard against a `__proto__`/`constructor`/`prototype` field name hijacking result's own\n // prototype via the bracket-assignment accessor — see _prototype.ts.\n if (isUnsafeObjectKey(k)) continue;\n\n result[k] = keySet.has(k) ? replacement : redactValue(v, depth);\n }\n\n return result;\n }\n\n return (entry: LogEntry): void => {\n transport({ ...entry, data: redactObject(entry.data as Bindings) });\n };\n}\n\n/* ─── pipe — fault-tolerant fan-out ─── */\n\n/**\n * Fan-out: dispatches each entry to all provided transports independently.\n * A throw in one transport does not prevent others from receiving the entry.\n * Pass `onError` to observe individual transport failures; without it, errors are silently swallowed.\n *\n * @example\n * createLogger({\n * transports: [pipe(consoleTransport(), remoteTransport({ handler }))],\n * })\n *\n * // With error observer:\n * pipe({ onError: (err) => console.error('[pipe]', err) }, consoleTransport(), remoteTransport({ handler }))\n */\nexport function pipe(...transports: Transport[]): Transport;\nexport function pipe(options: PipeOptions, ...transports: Transport[]): Transport;\nexport function pipe(optionsOrTransport: PipeOptions | Transport, ...rest: Transport[]): Transport {\n let opts: PipeOptions;\n let transports: Transport[];\n\n if (typeof optionsOrTransport === 'function') {\n opts = {};\n transports = [optionsOrTransport, ...rest];\n } else {\n opts = optionsOrTransport;\n transports = rest;\n }\n\n return (entry: LogEntry): void => {\n for (const t of transports) {\n try {\n t(entry);\n } catch (err) {\n opts.onError?.(err, entry);\n }\n }\n };\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"transports.js","names":[],"sources":["../src/transports.ts"],"sourcesContent":["import { warn } from './_dev';\nimport { isUnsafeObjectKey } from './_prototype';\nimport { RuneConfigError } from './errors';\nimport type {\n BatchHandle,\n BatchTransportOptions,\n Bindings,\n JsonTransportOptions,\n LogEntry,\n PipeOptions,\n RedactTransportOptions,\n RemoteLogData,\n RemoteTransportOptions,\n SampleTransportOptions,\n Transport,\n} from './types';\nimport { isLevelEnabled } from './types';\n\n/* ─── Environment detection ─── */\n\nfunction detectEnv(): 'development' | 'production' {\n if (typeof window === 'undefined') {\n return (globalThis as Record<string, unknown> & { process?: { env?: { NODE_ENV?: string } } }).process?.env\n ?.NODE_ENV === 'production'\n ? 'production'\n : 'development';\n }\n\n return 'development';\n}\n\n/* ─── remoteTransport ─── */\n\n/**\n * Forwards log entries asynchronously to a remote handler.\n * The handler is fire-and-forget. Use onError to observe delivery failures.\n * Console and remote thresholds are fully independent.\n *\n * **Security note:** serialized `Error` objects include the full `stack` trace,\n * which may expose internal file paths. Use `redactTransport` or a middleware\n * to strip `err.stack` before forwarding in production if this is a concern.\n *\n * @example\n * remoteTransport({\n * handler: async (type, data) => {\n * await fetch('/api/logs', { body: JSON.stringify(data), method: 'POST' });\n * },\n * level: 'error',\n * })\n */\nexport function remoteTransport(options: RemoteTransportOptions): Transport {\n const { handler } = options;\n const level = options.level ?? 'debug';\n const env = options.env ?? detectEnv();\n const onError = options.onError ?? ((err: unknown) => warn(`remote transport error: ${String(err)}`));\n\n return (entry: LogEntry): void => {\n if (!isLevelEnabled(level, entry.level)) return;\n\n const hasData = Object.keys(entry.data).length > 0;\n const payload: RemoteLogData = {\n data: hasData ? entry.data : undefined,\n env,\n level: entry.level,\n message: entry.message,\n namespace: entry.namespace || undefined,\n timestamp: entry.timestamp.toISOString(),\n };\n\n Promise.resolve()\n .then(() => handler(entry.level, payload))\n .catch((err: unknown) => onError(err, payload));\n };\n}\n\n/* ─── jsonTransport ─── */\n\nfunction makeCircularReplacer(): (_key: string, value: unknown) => unknown {\n const seen = new WeakSet();\n\n return (_key: string, value: unknown): unknown => {\n if (typeof value === 'object' && value !== null) {\n if (seen.has(value)) return '[Circular]';\n\n seen.add(value);\n }\n\n return value;\n };\n}\n\n/**\n * Writes newline-delimited JSON (NDJSON) to stdout or a custom output function.\n * Useful for structured log aggregation pipelines in Node.js (ELK, Datadog, etc.).\n *\n * @example\n * jsonTransport({ level: 'info' })\n * jsonTransport({ safe: true }) // handles circular references gracefully\n * jsonTransport({ output: (line) => fs.appendFileSync('app.log', line + '\\n') })\n */\nexport function jsonTransport(options: JsonTransportOptions = {}): Transport {\n const level = options.level ?? 'debug';\n const f = options.fields ?? {};\n const fLevel = f.level ?? 'level';\n const fTime = f.time ?? 'time';\n const fNs = f.ns ?? 'ns';\n const fMsg = f.msg ?? 'msg';\n const safe = options.safe ?? false;\n const output =\n options.output ??\n ((line: string) => {\n (\n globalThis as Record<string, unknown> & { process?: { stdout?: { write: (s: string) => void } } }\n ).process?.stdout?.write(`${line}\\n`);\n });\n\n return (entry: LogEntry): void => {\n if (!isLevelEnabled(level, entry.level)) return;\n\n const record: Record<string, unknown> = {\n ...entry.data,\n [fLevel]: entry.level,\n [fTime]: entry.timestamp.toISOString(),\n ...(entry.namespace && { [fNs]: entry.namespace }),\n ...(entry.message !== undefined && { [fMsg]: entry.message }),\n };\n\n output(JSON.stringify(record, safe ? makeCircularReplacer() : undefined));\n };\n}\n\n/* ─── Transport option validation ─── */\n\nfunction assertFiniteNumber(value: number, name: string): void {\n if (!Number.isFinite(value)) throw new RuneConfigError(`${name} must be a finite number`);\n}\n\nfunction assertNonNegativeInteger(value: number, name: string): void {\n assertFiniteNumber(value, name);\n\n if (!Number.isInteger(value) || value < 0) throw new RuneConfigError(`${name} must be a non-negative integer`);\n}\n\nfunction assertPositiveInteger(value: number, name: string): void {\n assertFiniteNumber(value, name);\n\n if (!Number.isInteger(value) || value <= 0) throw new RuneConfigError(`${name} must be a positive integer`);\n}\n\n/* ─── batchTransport ─── */\n\n/**\n * Buffers log entries and flushes them in batches, reducing I/O overhead.\n * Flushes when the buffer reaches maxSize or after the interval elapses.\n *\n * Returns a `BatchHandle` with `.transport`, `.flush()`, and `.dispose()` methods:\n * - `.transport` — pass to `createLogger({ transports: [handle.transport] })`\n * - `.flush()` — immediately send buffered entries and wait for delivery without stopping the timer\n * - `.dispose()` — stop the interval, flush remaining entries, and wait for delivery\n *\n * Use `onFlushError` to observe failed flushes (e.g. dead-letter queue).\n *\n * @example\n * const batch = batchTransport({\n * onFlush: (entries) => sendToCollector(entries),\n * onFlushError: (entries, err) => deadLetter.push(entries),\n * interval: 10_000,\n * maxSize: 100,\n * });\n * createLogger({ transports: [batch.transport] });\n * await batch.dispose(); // call during graceful shutdown\n */\nexport function batchTransport(options: BatchTransportOptions): BatchHandle {\n const level = options.level ?? 'debug';\n const maxSize = options.maxSize ?? 50;\n const maxBuffer = options.maxBuffer;\n const interval = options.interval ?? 5000;\n\n assertFiniteNumber(interval, 'batchTransport interval');\n\n if (interval <= 0) throw new RuneConfigError('batchTransport interval must be greater than zero');\n\n assertPositiveInteger(maxSize, 'batchTransport maxSize');\n\n if (maxBuffer !== undefined) assertNonNegativeInteger(maxBuffer, 'batchTransport maxBuffer');\n\n let buffer: LogEntry[] = [];\n let timer: ReturnType<typeof setInterval> | undefined;\n let automaticFailure: { error: unknown } | undefined;\n let batchDisposed = false;\n let deliveryTail: Promise<void> = Promise.resolve();\n let disposePromise: Promise<void> | undefined;\n\n const deliver = async (entries: LogEntry[]): Promise<void> => {\n try {\n await options.onFlush(entries);\n } catch (err) {\n try {\n options.onFlushError?.(entries, err);\n } catch (observerErr) {\n warn(`batch transport error observer threw: ${String(observerErr)}`);\n }\n\n throw err;\n }\n };\n\n const enqueue = (entries: LogEntry[]): Promise<void> => {\n const delivery = deliveryTail.then(() => deliver(entries));\n\n deliveryTail = delivery.catch(() => undefined);\n\n return delivery;\n };\n\n const flush = (): Promise<void> => {\n if (buffer.length === 0) return deliveryTail;\n\n const entries = buffer;\n\n buffer = [];\n\n return enqueue(entries);\n };\n\n const flushAutomatically = (): void => {\n void flush().catch((error: unknown) => {\n automaticFailure ??= { error };\n });\n };\n\n const transportFn: Transport = (entry: LogEntry): void => {\n if (batchDisposed) return;\n\n if (!isLevelEnabled(level, entry.level)) return;\n\n if (!timer) timer = setInterval(flushAutomatically, interval);\n\n buffer.push(entry);\n\n if (maxBuffer !== undefined && buffer.length > maxBuffer) {\n buffer = buffer.slice(buffer.length - maxBuffer);\n }\n\n if (buffer.length >= maxSize) flushAutomatically();\n };\n\n const handle: BatchHandle = {\n dispose(): Promise<void> {\n if (disposePromise) return disposePromise;\n\n batchDisposed = true;\n\n if (timer) {\n clearInterval(timer);\n timer = undefined;\n }\n\n disposePromise = flush().then(() => {\n if (automaticFailure) throw automaticFailure.error;\n });\n\n return disposePromise;\n },\n get disposed(): boolean {\n return batchDisposed;\n },\n flush,\n [Symbol.asyncDispose](): Promise<void> {\n return handle.dispose();\n },\n transport: transportFn,\n };\n\n return handle;\n}\n\n/* ─── sampleTransport ─── */\n\n/**\n * Probabilistically forwards entries to a downstream transport.\n * Useful for reducing volume of high-frequency debug logs in production.\n *\n * @example\n * sampleTransport({ rate: 0.1, transport: remoteTransport({ handler }) })\n */\nexport function sampleTransport(options: SampleTransportOptions): Transport {\n const { rate, transport } = options;\n const level = options.level ?? 'debug';\n\n assertFiniteNumber(rate, 'sampleTransport rate');\n\n if (rate < 0 || rate > 1) throw new RuneConfigError('sampleTransport rate must be between zero and one');\n\n return (entry: LogEntry): void => {\n if (!isLevelEnabled(level, entry.level)) return;\n\n if (Math.random() < rate) transport(entry);\n };\n}\n\n/* ─── redactTransport ─── */\n\n/**\n * Strips sensitive fields from `data` before forwarding to a downstream transport.\n * Redaction is applied recursively at any depth, including inside arrays.\n *\n * @example\n * redactTransport({\n * keys: ['password', 'token', 'ssn'],\n * replacement: '[REDACTED]',\n * transport: remoteTransport({ handler }),\n * })\n */\nexport function redactTransport(options: RedactTransportOptions): Transport {\n const { keys, maxDepth = 20, replacement = '[REDACTED]', transport } = options;\n\n assertNonNegativeInteger(maxDepth, 'redactTransport maxDepth');\n\n for (const key of keys) {\n if (key.includes('.')) {\n warn(\n `redactTransport: key \"${key}\" contains a dot. Dot-path notation is not supported — use the plain field name (e.g. 'password') to redact at any depth.`,\n );\n }\n }\n\n const keySet = new Set(keys);\n\n function redactValue(v: unknown, depth = 0): unknown {\n if (depth > maxDepth) {\n warn(\n `redactTransport: object nesting depth exceeded ${maxDepth} — redaction truncated at this level. Sensitive fields below depth ${maxDepth} may not be redacted.`,\n );\n\n return v;\n }\n\n if (Array.isArray(v)) return v.map((item) => redactValue(item, depth + 1));\n\n if (typeof v === 'object' && v !== null) return redactObject(v as Bindings, depth + 1);\n\n return v;\n }\n\n function redactObject(obj: Bindings, depth = 0): Bindings {\n const result: Bindings = {};\n\n for (const [k, v] of Object.entries(obj)) {\n // Guard against a `__proto__`/`constructor`/`prototype` field name hijacking result's own\n // prototype via the bracket-assignment accessor — see _prototype.ts.\n if (isUnsafeObjectKey(k)) continue;\n\n result[k] = keySet.has(k) ? replacement : redactValue(v, depth);\n }\n\n return result;\n }\n\n return (entry: LogEntry): void => {\n transport({ ...entry, data: redactObject(entry.data as Bindings) });\n };\n}\n\n/* ─── pipe — fault-tolerant fan-out ─── */\n\n/**\n * Fan-out: dispatches each entry to all provided transports independently.\n * A throw in one transport does not prevent others from receiving the entry.\n * Pass `onError` to observe individual transport failures; without it, errors are silently swallowed.\n *\n * @example\n * createLogger({\n * transports: [pipe(consoleTransport(), remoteTransport({ handler }))],\n * })\n *\n * // With error observer:\n * pipe({ onError: (err) => console.error('[pipe]', err) }, consoleTransport(), remoteTransport({ handler }))\n */\nexport function pipe(...transports: Transport[]): Transport;\nexport function pipe(options: PipeOptions, ...transports: Transport[]): Transport;\nexport function pipe(optionsOrTransport: PipeOptions | Transport, ...rest: Transport[]): Transport {\n let opts: PipeOptions;\n let transports: Transport[];\n\n if (typeof optionsOrTransport === 'function') {\n opts = {};\n transports = [optionsOrTransport, ...rest];\n } else {\n opts = optionsOrTransport;\n transports = rest;\n }\n\n return (entry: LogEntry): void => {\n for (const t of transports) {\n try {\n t(entry);\n } catch (err) {\n opts.onError?.(err, entry);\n }\n }\n };\n}\n"],"mappings":"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"}
|