@vielzeug/rune 2.2.0 → 3.0.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 +3 -29
- package/dist/index.cjs +1 -1
- package/dist/index.d.ts +3 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -3
- package/dist/logger.cjs +1 -1
- package/dist/logger.cjs.map +1 -1
- package/dist/logger.d.ts +0 -1
- package/dist/logger.d.ts.map +1 -1
- package/dist/logger.js +11 -16
- package/dist/logger.js.map +1 -1
- 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 +4 -72
- package/dist/transports.d.ts.map +1 -1
- package/dist/transports.js +22 -29
- package/dist/transports.js.map +1 -1
- package/dist/types.cjs.map +1 -1
- package/dist/types.d.ts +28 -83
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/package.json +6 -6
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 { 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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"}
|
|
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 RedactTransportOptions,\n RemoteLogData,\n RemoteTransportOptions,\n SampleTransportOptions,\n Transport,\n} from './types';\nimport { isLevelEnabled } from './types';\n\n/* ─── remoteTransport ─── */\n\nfunction detectEnv(): 'development' | 'production' {\n if (typeof window !== 'undefined') return 'development';\n\n return (globalThis as { process?: { env?: { NODE_ENV?: string } } }).process?.env?.NODE_ENV === 'production'\n ? 'production'\n : 'development';\n}\n\n/** Forward entries asynchronously to a remote delivery handler. */\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 ?? ((error: unknown) => warn(`remote transport error: ${String(error)}`));\n\n return (entry): void => {\n if (!isLevelEnabled(level, entry.level)) return;\n\n const payload: RemoteLogData = {\n data: Object.keys(entry.data).length > 0 ? 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((error: unknown) => {\n try {\n onError(error, payload);\n } catch (observerError) {\n warn(`remote transport error observer threw: ${String(observerError)}`);\n }\n });\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\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 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 if (!Number.isInteger(value) || value <= 0) throw new RuneConfigError(`${name} must be a positive integer`);\n}\n\n/* ─── batchTransport ─── */\n\n/** Buffer entries and deliver accepted batches serially. */\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 if (interval <= 0) throw new RuneConfigError('batchTransport interval must be greater than zero');\n assertPositiveInteger(maxSize, 'batchTransport maxSize');\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 disposed = 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 (error) {\n try {\n options.onFlushError?.(entries, error);\n } catch (observerError) {\n warn(`batch transport error observer threw: ${String(observerError)}`);\n }\n throw error;\n }\n };\n\n const enqueue = (entries: LogEntry[]): Promise<void> => {\n const delivery = deliveryTail.then(() => deliver(entries));\n deliveryTail = delivery.catch(() => undefined);\n return delivery;\n };\n\n const flush = (): Promise<void> => {\n if (buffer.length === 0) return deliveryTail;\n\n const entries = buffer;\n buffer = [];\n return enqueue(entries);\n };\n\n const flushAutomatically = (): void => {\n void flush().catch((error: unknown) => {\n automaticFailure ??= { error };\n });\n };\n\n const transport: Transport = (entry): void => {\n if (disposed || !isLevelEnabled(level, entry.level)) return;\n\n if (!timer) timer = setInterval(flushAutomatically, interval);\n buffer.push(entry);\n\n if (maxBuffer !== undefined && buffer.length > maxBuffer) buffer = buffer.slice(buffer.length - maxBuffer);\n if (buffer.length >= maxSize) flushAutomatically();\n };\n\n const handle: BatchHandle = {\n dispose(): Promise<void> {\n if (disposePromise) return disposePromise;\n\n disposed = true;\n if (timer) clearInterval(timer);\n timer = undefined;\n disposePromise = flush().then(() => {\n if (automaticFailure) throw automaticFailure.error;\n });\n return disposePromise;\n },\n get disposed(): boolean {\n return disposed;\n },\n flush,\n [Symbol.asyncDispose](): Promise<void> {\n return handle.dispose();\n },\n transport,\n };\n\n return handle;\n}\n\n/* ─── sampleTransport ─── */\n\n/** Forward a random fraction of entries to a downstream transport. */\nexport function sampleTransport(options: SampleTransportOptions): Transport {\n const { rate, transport } = options;\n const level = options.level ?? 'debug';\n\n assertFiniteNumber(rate, 'sampleTransport rate');\n if (rate < 0 || rate > 1) throw new RuneConfigError('sampleTransport rate must be between zero and one');\n\n return (entry): void => {\n if (isLevelEnabled(level, entry.level) && Math.random() < rate) transport(entry);\n };\n}\n\n/* ─── redactTransport ─── */\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(`redactTransport: key \"${key}\" contains a dot; keys match exact field names, not paths`);\n }\n }\n\n const keySet = new Set(keys);\n let warnedAboutDepth = false;\n\n const redact = (value: unknown, depth: number): unknown => {\n if (typeof value !== 'object' || value === null) return value;\n\n if (depth > maxDepth) {\n if (!warnedAboutDepth) {\n warn(`redactTransport: object nesting depth exceeded ${maxDepth}; deeper subtrees were redacted entirely`);\n warnedAboutDepth = true;\n }\n return replacement;\n }\n\n if (Array.isArray(value)) return value.map((item) => redact(item, depth + 1));\n\n const result: Bindings = {};\n for (const [key, item] of Object.entries(value)) {\n if (isUnsafeObjectKey(key)) continue;\n result[key] = keySet.has(key) ? replacement : redact(item, depth + 1);\n }\n return result;\n };\n\n return (entry) => transport({ ...entry, data: redact(entry.data, 0) as Bindings });\n}\n"],"mappings":";;;;;AAmBA,SAAS,IAA0C;CAGjD,OAFI,OAAO,SAAW,MAAoB,gBAElC,WAA6D,SAAS,KAAK,aAAa,eAC5F,eACA;AACN;AAGA,SAAgB,EAAgB,GAA4C;CAC1E,IAAM,EAAE,eAAY,GACd,IAAQ,EAAQ,SAAS,SACzB,IAAM,EAAQ,OAAO,EAAU,GAC/B,IAAU,EAAQ,aAAa,MAAmB,kBAAK,2BAA2B,OAAO,CAAK,GAAG;CAEvG,QAAQ,MAAgB;EACtB,IAAI,CAAC,EAAe,GAAO,EAAM,KAAK,GAAG;EAEzC,IAAM,IAAyB;GAC7B,MAAM,OAAO,KAAK,EAAM,IAAI,CAAC,CAAC,SAAS,IAAI,EAAM,OAAO,KAAA;GACxD;GACA,OAAO,EAAM;GACb,SAAS,EAAM;GACf,WAAW,EAAM,aAAa,KAAA;GAC9B,WAAW,EAAM,UAAU,YAAY;EACzC;EAEA,QAAQ,QAAQ,CAAC,CACd,WAAW,EAAQ,EAAM,OAAO,CAAO,CAAC,CAAC,CACzC,OAAO,MAAmB;GACzB,IAAI;IACF,EAAQ,GAAO,CAAO;GACxB,SAAS,GAAe;IACtB,AAAK,GAA0C,OAAO,CAAa,EAA9D;GACP;EACF,CAAC;CACL;AACF;AAIA,SAAS,IAAkE;CACzE,IAAM,oBAAO,IAAI,QAAQ;CAEzB,QAAQ,GAAc,MAA4B;EAChD,IAAI,OAAO,KAAU,YAAY,GAAgB;GAC/C,IAAI,EAAK,IAAI,CAAK,GAAG,OAAO;GAE5B,EAAK,IAAI,CAAK;EAChB;EAEA,OAAO;CACT;AACF;AAWA,SAAgB,EAAc,IAAgC,CAAC,GAAc;CAC3E,IAAM,IAAQ,EAAQ,SAAS,SACzB,IAAI,EAAQ,UAAU,CAAC,GACvB,IAAS,EAAE,SAAS,SACpB,IAAQ,EAAE,QAAQ,QAClB,IAAM,EAAE,MAAM,MACd,IAAO,EAAE,OAAO,OAChB,IAAO,EAAQ,QAAQ,IACvB,IACJ,EAAQ,YACN,MAAiB;EACjB,WAEE,SAAS,QAAQ,MAAM,GAAG,EAAK,GAAG;CACtC;CAEF,QAAQ,MAA0B;EAChC,IAAI,CAAC,EAAe,GAAO,EAAM,KAAK,GAAG;EAEzC,IAAM,IAAkC;GACtC,GAAG,EAAM;IACR,IAAS,EAAM;IACf,IAAQ,EAAM,UAAU,YAAY;GACrC,GAAI,EAAM,aAAa,GAAG,IAAM,EAAM,UAAU;GAChD,GAAI,EAAM,YAAY,KAAA,KAAa,GAAG,IAAO,EAAM,QAAQ;EAC7D;EAEA,EAAO,KAAK,UAAU,GAAQ,IAAO,EAAqB,IAAI,KAAA,CAAS,CAAC;CAC1E;AACF;AAEA,SAAS,EAAmB,GAAe,GAAoB;CAC7D,IAAI,CAAC,OAAO,SAAS,CAAK,GAAG,MAAM,IAAI,EAAgB,GAAG,EAAK,yBAAyB;AAC1F;AAEA,SAAS,EAAyB,GAAe,GAAoB;CAEnE,IADA,EAAmB,GAAO,CAAI,GAC1B,CAAC,OAAO,UAAU,CAAK,KAAK,IAAQ,GAAG,MAAM,IAAI,EAAgB,GAAG,EAAK,gCAAgC;AAC/G;AAEA,SAAS,EAAsB,GAAe,GAAoB;CAEhE,IADA,EAAmB,GAAO,CAAI,GAC1B,CAAC,OAAO,UAAU,CAAK,KAAK,KAAS,GAAG,MAAM,IAAI,EAAgB,GAAG,EAAK,4BAA4B;AAC5G;AAKA,SAAgB,EAAe,GAA6C;CAC1E,IAAM,IAAQ,EAAQ,SAAS,SACzB,IAAU,EAAQ,WAAW,IAC7B,IAAY,EAAQ,WACpB,IAAW,EAAQ,YAAY;CAGrC,IADA,EAAmB,GAAU,yBAAyB,GAClD,KAAY,GAAG,MAAM,IAAI,EAAgB,mDAAmD;CAEhG,AADA,EAAsB,GAAS,wBAAwB,GACnD,MAAc,KAAA,KAAW,EAAyB,GAAW,0BAA0B;CAE3F,IAAI,IAAqB,CAAC,GACtB,GACA,GACA,IAAW,IACX,IAA8B,QAAQ,QAAQ,GAC9C,GAEE,IAAU,OAAO,MAAuC;EAC5D,IAAI;GACF,MAAM,EAAQ,QAAQ,CAAO;EAC/B,SAAS,GAAO;GACd,IAAI;IACF,EAAQ,eAAe,GAAS,CAAK;GACvC,SAAS,GAAe;IACtB,AAAK,GAAyC,OAAO,CAAa,EAA7D;GACP;GACA,MAAM;EACR;CACF,GAEM,KAAW,MAAuC;EACtD,IAAM,IAAW,EAAa,WAAW,EAAQ,CAAO,CAAC;EAEzD,OADA,IAAe,EAAS,YAAY,KAAA,CAAS,GACtC;CACT,GAEM,UAA6B;EACjC,IAAI,EAAO,WAAW,GAAG,OAAO;EAEhC,IAAM,IAAU;EAEhB,OADA,IAAS,CAAC,GACH,EAAQ,CAAO;CACxB,GAEM,UAAiC;EACrC,EAAW,CAAC,CAAC,OAAO,MAAmB;GACrC,MAAqB,EAAE,SAAM;EAC/B,CAAC;CACH,GAEM,KAAwB,MAAgB;EACxC,MAAa,EAAe,GAAO,EAAM,KAAK,MAElD,AAAY,MAAQ,YAAY,GAAoB,CAAQ,GAC5D,EAAO,KAAK,CAAK,GAEb,MAAc,KAAA,KAAa,EAAO,SAAS,MAAW,IAAS,EAAO,MAAM,EAAO,SAAS,CAAS,IACrG,EAAO,UAAU,KAAS,EAAmB;CACnD,GAEM,IAAsB;EAC1B,UAAyB;GASvB,OARI,MAEJ,IAAW,IACP,KAAO,cAAc,CAAK,GAC9B,IAAQ,KAAA,GACR,IAAiB,EAAM,CAAC,CAAC,WAAW;IAClC,IAAI,GAAkB,MAAM,EAAiB;GAC/C,CAAC,GACM;EACT;EACA,IAAI,WAAoB;GACtB,OAAO;EACT;EACA;EACA,CAAC,OAAO,gBAA+B;GACrC,OAAO,EAAO,QAAQ;EACxB;EACA;CACF;CAEA,OAAO;AACT;AAKA,SAAgB,EAAgB,GAA4C;CAC1E,IAAM,EAAE,SAAM,iBAAc,GACtB,IAAQ,EAAQ,SAAS;CAG/B,IADA,EAAmB,GAAM,sBAAsB,GAC3C,IAAO,KAAK,IAAO,GAAG,MAAM,IAAI,EAAgB,mDAAmD;CAEvG,QAAQ,MAAgB;EACtB,AAAI,EAAe,GAAO,EAAM,KAAK,KAAK,KAAK,OAAO,IAAI,KAAM,EAAU,CAAK;CACjF;AACF;AAIA,SAAgB,EAAgB,GAA4C;CAC1E,IAAM,EAAE,SAAM,cAAW,IAAI,iBAAc,cAAc,iBAAc;CAEvE,EAAyB,GAAU,0BAA0B;CAE7D,KAAK,IAAM,KAAO,GAChB,AAAI,EAAI,SAAS,GAAG,KACb,GAAyB,EAAzB;CAIT,IAAM,IAAS,IAAI,IAAI,CAAI,GACvB,IAAmB,IAEjB,KAAU,GAAgB,MAA2B;EACzD,IAAI,OAAO,KAAU,aAAY,GAAgB,OAAO;EAExD,IAAI,IAAQ,GAKV,OAJA,AAEE,OADK,GAAkD,EAAlD,GACc,KAEd;EAGT,IAAI,MAAM,QAAQ,CAAK,GAAG,OAAO,EAAM,KAAK,MAAS,EAAO,GAAM,IAAQ,CAAC,CAAC;EAE5E,IAAM,IAAmB,CAAC;EAC1B,KAAK,IAAM,CAAC,GAAK,MAAS,OAAO,QAAQ,CAAK,GACxC,EAAkB,CAAG,MACzB,EAAO,KAAO,EAAO,IAAI,CAAG,IAAI,IAAc,EAAO,GAAM,IAAQ,CAAC;EAEtE,OAAO;CACT;CAEA,QAAQ,MAAU,EAAU;EAAE,GAAG;EAAO,MAAM,EAAO,EAAM,MAAM,CAAC;CAAc,CAAC;AACnF"}
|
package/dist/types.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.cjs","names":[],"sources":["../src/types.ts"],"sourcesContent":["/* ─── Log levels ─── */\n\nexport type LogType = 'debug' | 'error' | 'fatal' | 'info' | 'warn';\nexport type LogLevel = LogType | 'off';\n\n/** Numeric priority for each level. Lower = more verbose. Exported for transport authors. */\nexport const PRIORITY: Record<LogLevel, number> = {\n debug: 0,\n error: 3,\n fatal: 4,\n info: 1,\n off: 5,\n warn: 2,\n};\n\n/** Returns true if `level` passes the `threshold`. Returns false when `level` is 'off'. Exported for transport/middleware authors. */\nexport function isLevelEnabled(threshold: LogLevel, level: LogLevel): boolean {\n if (level === 'off') return false;\n\n return PRIORITY[threshold] <= PRIORITY[level];\n}\n\n/* ─── Bindings ─── */\n\nexport type Bindings = Record<string, unknown>;\n\n/* ─── Log entry ─── */\n\n/**\n * The structured record produced by every log call and dispatched to all transports.\n * `data` is the merged result of pinned bindings and per-call context — transports\n * receive a single flat object and do not need to merge anything themselves.\n * Any `Error` instances — whether from a pinned binding (`bindings`/`withBindings()`) or\n * per-call context — are automatically serialized to `{ message, name, stack }`.\n * **Shallow only** — an `Error` nested inside a plain object (e.g. `{ meta: { err } }`) is left as-is;\n * only top-level fields of `data` are checked.\n */\nexport type LogEntry = {\n /**\n * Merged structured data: pinned bindings overlaid with per-call context.\n * Already shallow-copied and immutable — do not mutate.\n */\n data: Readonly<Bindings>;\n level: LogType;\n message?: string;\n namespace: string;\n /** Exact moment of the log call — shared across all transports for the same entry. */\n timestamp: Date;\n};\n\n/* ─── Transport ─── */\n\n/**\n * A transport receives a log entry and is responsible for its own delivery and formatting.\n * If a transport throws, the logger catches it, reports it via a dev-only warning, and continues\n * dispatching the entry to remaining transports — a single misbehaving transport can never crash\n * the caller of `log.info()`/etc. or block its siblings.\n */\nexport type Transport = (entry: LogEntry) => void;\n\n/**\n * Middleware function that transforms or filters log entries before dispatch. Return null to drop the entry.\n * If middleware throws, the logger catches it, reports it via a dev-only warning, and drops the entry\n * (no transports run for it) rather than crashing the caller.\n */\nexport type LogMiddleware = (entry: LogEntry) => LogEntry | null;\n\n/* ─── Transport option types ─── */\n\nexport type RemoteLogData = {\n data?: Bindings;\n env: 'development' | 'production';\n level: LogType;\n message?: string;\n namespace?: string;\n timestamp: string;\n};\n\nexport type RemoteTransportOptions = {\n /** Override the detected runtime environment. Default: auto-detected. */\n env?: 'development' | 'production';\n /** Remote delivery handler — receives the log type and structured payload. */\n handler: (type: LogType, data: RemoteLogData) => void;\n /** Minimum level to forward. Default: 'debug'. */\n level?: LogLevel;\n /**\n * Called when the handler throws or rejects.\n * The async error path is separate from any synchronous errors in the emit call stack.\n * Default: a dev-only `console.warn` (gated by `__RUNE_PROD__`). In production builds,\n * unhandled remote transport errors are silently swallowed — pass an explicit `onError`\n * if you need delivery-failure observability in production.\n */\n onError?: (error: unknown, data: RemoteLogData) => void;\n};\n\nexport type JsonTransportOptions = {\n /**\n * Custom output field names. Useful for adapting to aggregator conventions\n * (Datadog, ELK, Loki, etc.).\n *\n * @example\n * jsonTransport({ fields: { level: 'severity', time: '@timestamp', msg: 'message' } })\n */\n fields?: {\n level?: string;\n msg?: string;\n ns?: string;\n time?: string;\n };\n /** Minimum level to output. Default: 'debug'. */\n level?: LogLevel;\n /** Custom output function. Default: process.stdout.write. */\n output?: (line: string) => void;\n /**\n * Replace circular references with `'[Circular]'` instead of throwing a TypeError.\n * Useful in environments where log payloads may contain complex object graphs.\n * Default: false.\n */\n safe?: boolean;\n};\n\n/** Handle returned by `batchTransport()`. Pass `handle.transport` to `createLogger({ transports })`. */\nexport type BatchHandle = {\n /** Delegates to `dispose()`. Enables `await using` declarations. */\n [Symbol.asyncDispose]: () => Promise<void>;\n /** Stop the interval timer, flush remaining entries, and wait for all accepted batches. Idempotent. */\n dispose: () => Promise<void>;\n /** `true` after `dispose()` has been called. */\n readonly disposed: boolean;\n /** Immediately flush buffered entries and wait for their downstream delivery without stopping the timer. */\n flush: () => Promise<void>;\n /** The transport function to pass to `createLogger({ transports: [handle.transport] })`. */\n transport: Transport;\n};\n\nexport type BatchTransportOptions = {\n /** Flush interval in milliseconds. Must be finite and greater than zero. Default: 5000. */\n interval?: number;\n /** Minimum level to buffer. Default: 'debug'. */\n level?: LogLevel;\n /**\n * Hard limit on the in-memory buffer size. Must be a finite non-negative integer.\n * When the buffer exceeds this value, the oldest entries are dropped to prevent\n * unbounded memory growth. Unlike `maxSize`, this does NOT trigger a flush.\n * Default: unbounded.\n */\n maxBuffer?: number;\n /** Maximum buffer size before an early flush. Must be a finite positive integer. Default: 50. */\n maxSize?: number;\n /** Callback to receive flushed batches. May return a Promise. */\n onFlush: (entries: LogEntry[]) => void | Promise<void>;\n /**\n * Called when onFlush throws synchronously or rejects asynchronously. Observes the\n * failure; the corresponding `flush()` or `dispose()` promise still rejects.\n */\n onFlushError?: (entries: LogEntry[], error: unknown) => void;\n};\n\nexport type PipeOptions = {\n /**\n * Called when one of the piped transports throws.\n * Receives the thrown error and the log entry that triggered it.\n * Default: silent (errors are swallowed to protect remaining transports).\n */\n onError?: (error: unknown, entry: LogEntry) => void;\n};\n\nexport type SampleTransportOptions = {\n /** Minimum level to sample. Default: 'debug'. */\n level?: LogLevel;\n /** Finite fraction of entries to forward (0–1). */\n rate: number;\n /** Downstream transport to receive sampled entries. */\n transport: Transport;\n};\n\nexport type RedactTransportOptions = {\n /**\n * Field names to replace at any depth in `data`.\n * Matched by exact field name — dot-path notation (e.g. `'user.password'`) is NOT supported.\n * A key like `'password'` will redact every field named `'password'` at any nesting level.\n */\n keys: string[];\n /**\n * Finite non-negative integer maximum object nesting depth to traverse during redaction.\n * Objects deeper than this limit are returned as-is (not redacted).\n * A dev-only warning is emitted when the cap is hit.\n * Default: 20.\n * @security In production builds, the depth warning is suppressed — deeply-nested sensitive\n * fields beyond `maxDepth` will pass through unredacted without any indication. Ensure that\n * sensitive payloads are not nested beyond this limit, or lower `maxDepth` as needed.\n */\n maxDepth?: number;\n /** Replacement value for redacted fields. Default: '[REDACTED]'. */\n replacement?: string;\n /** Downstream transport to receive the redacted entry. */\n transport: Transport;\n};\n\n/* ─── Logger options ─── */\n\nexport type RuneOptions = {\n /** Initial pinned bindings for this logger instance. `Error` values are auto-serialized, same as per-call context. */\n bindings?: Bindings;\n /** Minimum log level for this logger instance. Default: 'debug'. */\n logLevel?: LogLevel;\n /** Middleware pipeline applied to every entry before dispatch to transports. */\n middleware?: LogMiddleware[];\n /**\n * Namespace for this logger. When passed to `child()`, it is automatically\n * dot-joined to the parent namespace (e.g. parent `'api'` + child `'auth'` → `'api.auth'`).\n */\n namespace?: string;\n /**\n * Transport pipeline. Each transport receives every entry that passes the level threshold.\n * Default: [consoleTransport()].\n */\n transports?: Transport[];\n};\n\n/* ─── Log method ─── */\n\n/**\n * Signature shared by all five log-level methods.\n *\n * - `log.info('message')` — string-only, most common.\n * - `log.info({ ...fields }, 'message')` — structured context + optional message.\n * `Error` values in `fields` are automatically serialized to `{ message, name, stack }`.\n * Serialization is shallow only — an `Error` nested inside a nested object is left as-is.\n * - `log.error(err, { ...fields }, 'message')` — Error first, then optional context + message.\n * Shorthand for the pattern where an Error is the primary subject of the log call.\n * The context object may be omitted entirely: `log.error(err, 'message')`.\n *\n * @example\n * log.error({ err: new Error('timeout'), requestId }, 'request failed')\n * log.error(new Error('timeout'), { requestId }, 'request failed')\n */\nexport type LogMethod = {\n (message: string): void;\n (error: Error, message?: string): void;\n (error: Error, context: Bindings, message?: string): void;\n (context: Bindings, message?: string): void;\n};\n\n/* ─── Logger interface ─── */\n\nexport type Logger = {\n /** Delegates to `dispose()`. Enables `using` declarations. */\n [Symbol.dispose]: () => void;\n /** Snapshot of currently pinned bindings. */\n readonly bindings: Readonly<Bindings>;\n /** Create a child logger with config overrides. Inherits all config and bindings by default. */\n child: (overrides?: RuneOptions) => Logger;\n debug: LogMethod;\n /** `AbortSignal` aborted when `dispose()` is called. Use to tie external lifetimes to this logger. */\n readonly disposalSignal: AbortSignal;\n /**\n * Marks the logger as disposed — all subsequent log calls become no-ops.\n * Aborts `disposalSignal`. Does NOT auto-discover or dispose batch transports;\n * hold a direct reference to `batchTransport` and call its `dispose()` on shutdown.\n * Idempotent — safe to call multiple times.\n */\n dispose: () => void;\n /** `true` after `dispose()` has been called. */\n readonly disposed: boolean;\n /** Returns true if entries at this level will pass the configured threshold. */\n enabled: (type: LogLevel) => boolean;\n error: LogMethod;\n fatal: LogMethod;\n /**\n * Wrap a callback in a console group, closing it even on throw/reject.\n * Pass a `level` to gate the group header on the configured log threshold\n * (e.g. `level: 'debug'` suppresses the group when `logLevel` is above `'debug'`).\n * Default: always renders (unless `logLevel` is `'off'`).\n */\n group: <T>(label: string, fn: () => T, level?: LogType) => T;\n /**\n * Same as `group`, using `console.groupCollapsed`.\n * Pass a `level` to gate the group on the configured log threshold.\n */\n groupCollapsed: <T>(label: string, fn: () => T, level?: LogType) => T;\n info: LogMethod;\n /** Active log level for this logger instance. */\n readonly logLevel: LogLevel;\n /** Middleware pipeline applied before dispatch. */\n readonly middleware: readonly LogMiddleware[];\n /** Namespace string for this logger instance. */\n readonly namespace: string;\n /**\n * Measure execution time of `fn` and emit a structured log entry.\n * The entry message is `label`; `data` contains `{ duration_ms }` (rounded to 2 dp).\n * When `fn` throws or rejects, `data` also includes `{ err }` with the serialized error.\n * @param label - Human-readable description of the operation.\n * @param fn - Synchronous or async function to time.\n * @param level - Log level for the timing entry. Default: `'debug'`.\n */\n time: <T>(label: string, fn: () => T, level?: LogType) => T;\n /** Transport pipeline for this logger instance. */\n readonly transports: readonly Transport[];\n /**\n * Add a middleware function to the pipeline. Returns a **new** logger — the original is unchanged.\n * Discarding the return value is a common mistake: always assign the result.\n * @example\n * const log = baseLog.use(tracingMiddleware); // ✓ keep the result\n */\n use: (middleware: LogMiddleware) => Logger;\n warn: LogMethod;\n /**\n * Derive a child logger with additional pinned bindings.\n * The returned logger is fully independent — disposing it does not affect the parent,\n * and disposing the parent does not affect child loggers.\n */\n withBindings: (bindings: Bindings) => Logger;\n};\n"],"mappings":"AAMA,IAAa,EAAqC,CAChD,MAAO,EACP,MAAO,EACP,MAAO,EACP,KAAM,EACN,IAAK,EACL,KAAM,CACR,EAGA,SAAgB,EAAe,EAAqB,EAA0B,CAG5E,OAFI,IAAU,OAEP,EAAS,IAAc,EAAS,EACzC"}
|
|
1
|
+
{"version":3,"file":"types.cjs","names":[],"sources":["../src/types.ts"],"sourcesContent":["/* ─── Log levels ─── */\n\nexport type LogType = 'debug' | 'error' | 'fatal' | 'info' | 'warn';\nexport type LogLevel = LogType | 'off';\n\n/** Numeric priority for each level. Lower = more verbose. Exported for transport authors. */\nexport const PRIORITY: Record<LogLevel, number> = {\n debug: 0,\n error: 3,\n fatal: 4,\n info: 1,\n off: 5,\n warn: 2,\n};\n\n/** Returns true if `level` passes the `threshold`. Returns false when `level` is 'off'. Exported for transport authors. */\nexport function isLevelEnabled(threshold: LogLevel, level: LogLevel): boolean {\n if (level === 'off') return false;\n\n return PRIORITY[threshold] <= PRIORITY[level];\n}\n\n/* ─── Bindings ─── */\n\nexport type Bindings = Record<string, unknown>;\n\n/* ─── Log entry ─── */\n\n/**\n * The structured record produced by every log call and dispatched to all transports.\n * `data` is the merged result of pinned bindings and per-call context — transports\n * receive a single flat object and do not need to merge anything themselves.\n * Any `Error` instances — whether from a pinned binding (`bindings`/`withBindings()`) or\n * per-call context — are automatically serialized to `{ message, name, stack }`.\n * **Shallow only** — an `Error` nested inside a plain object (e.g. `{ meta: { err } }`) is left as-is;\n * only top-level fields of `data` are checked.\n */\nexport type LogEntry = {\n /**\n * Merged structured data: pinned bindings overlaid with per-call context.\n * Already shallow-copied and immutable — do not mutate.\n */\n data: Readonly<Bindings>;\n level: LogType;\n message?: string;\n namespace: string;\n /** Exact moment of the log call — shared across all transports for the same entry. */\n timestamp: Date;\n};\n\n/* ─── Transport ─── */\n\n/**\n * A transport receives a log entry and is responsible for its own delivery and formatting.\n * If a transport throws, the logger catches it, reports it via a dev-only warning, and continues\n * dispatching the entry to remaining transports — a single misbehaving transport can never crash\n * the caller of `log.info()`/etc. or block its siblings.\n */\nexport type Transport = (entry: LogEntry) => void;\n\n/** Transform or filter entries before dispatch. Return `null` to drop an entry. */\nexport type LogMiddleware = (entry: LogEntry) => LogEntry | null;\n\n/* ─── Transport option types ─── */\n\nexport type RemoteLogData = {\n data?: Readonly<Bindings>;\n env: 'development' | 'production';\n level: LogType;\n message?: string;\n namespace?: string;\n timestamp: string;\n};\n\nexport type RemoteTransportOptions = {\n /** Override the detected runtime environment. Default: auto-detected. */\n env?: 'development' | 'production';\n /** Remote delivery handler. */\n handler: (type: LogType, data: RemoteLogData) => void | Promise<unknown>;\n /** Minimum level to forward. Default: 'debug'. */\n level?: LogLevel;\n /** Observe synchronous throws and asynchronous rejections from `handler`. */\n onError?: (error: unknown, data: RemoteLogData) => void;\n};\n\nexport type JsonTransportOptions = {\n /**\n * Custom output field names. Useful for adapting to aggregator conventions\n * (Datadog, ELK, Loki, etc.).\n *\n * @example\n * jsonTransport({ fields: { level: 'severity', time: '@timestamp', msg: 'message' } })\n */\n fields?: {\n level?: string;\n msg?: string;\n ns?: string;\n time?: string;\n };\n /** Minimum level to output. Default: 'debug'. */\n level?: LogLevel;\n /** Custom output function. Default: process.stdout.write. */\n output?: (line: string) => void;\n /**\n * Replace circular references with `'[Circular]'` instead of throwing a TypeError.\n * Useful in environments where log payloads may contain complex object graphs.\n * Default: false.\n */\n safe?: boolean;\n};\n\n/** Handle returned by `batchTransport()`. */\nexport type BatchHandle = {\n [Symbol.asyncDispose]: () => Promise<void>;\n dispose: () => Promise<void>;\n readonly disposed: boolean;\n flush: () => Promise<void>;\n transport: Transport;\n};\n\nexport type BatchTransportOptions = {\n /** Flush interval in milliseconds. Default: 5000. */\n interval?: number;\n /** Minimum level to buffer. Default: 'debug'. */\n level?: LogLevel;\n /** Hard limit on buffered entries. Oldest entries are dropped first. Default: unbounded. */\n maxBuffer?: number;\n /** Flush once this many entries are buffered. Default: 50. */\n maxSize?: number;\n /** Deliver one accepted batch. Batches are delivered serially. */\n onFlush: (entries: LogEntry[]) => void | Promise<void>;\n /** Observe delivery failures. Manual failures reject `flush()`; automatic failures reject `dispose()`. */\n onFlushError?: (entries: LogEntry[], error: unknown) => void;\n};\n\nexport type SampleTransportOptions = {\n /** Minimum level to sample. Default: 'debug'. */\n level?: LogLevel;\n /** Finite fraction of entries to forward, from 0 to 1. */\n rate: number;\n transport: Transport;\n};\n\nexport type RedactTransportOptions = {\n /** Exact field names to replace at any depth. */\n keys: readonly string[];\n /** Maximum nested object depth to inspect. Deeper subtrees are replaced entirely. Default: 20. */\n maxDepth?: number;\n /** Replacement used for matching fields and depth-limited subtrees. Default: '[REDACTED]'. */\n replacement?: string;\n transport: Transport;\n};\n\n/* ─── Logger options ─── */\n\nexport type RuneOptions = {\n /** Initial pinned bindings for this logger instance. `Error` values are auto-serialized, same as per-call context. */\n bindings?: Bindings;\n /** Minimum log level for this logger instance. Default: 'debug'. */\n logLevel?: LogLevel;\n /** Middleware applied once, in order, before dispatch to every transport. */\n middleware?: LogMiddleware[];\n /**\n * Namespace for this logger. When passed to `child()`, it is automatically\n * dot-joined to the parent namespace (e.g. parent `'api'` + child `'auth'` → `'api.auth'`).\n */\n namespace?: string;\n /**\n * Transport pipeline. Each transport receives every entry that passes the level threshold.\n * Default: [consoleTransport()].\n */\n transports?: Transport[];\n};\n\n/* ─── Log method ─── */\n\n/**\n * Signature shared by all five log-level methods.\n *\n * Message-first calls are preferred for ordinary application logging. Context-first and error-first\n * calls support structured events, adapters, and error forwarding without synthetic messages.\n * `Error` values in context are serialized shallowly to `{ message, name, stack }`.\n *\n * @example\n * log.info('request started', { requestId: 'abc' })\n * log.debug(event, `bus:${event.type}`)\n * log.error(new Error('timeout'), { requestId: 'abc' }, 'request failed')\n */\nexport type LogMethod = {\n (message: string, context?: Bindings): void;\n (error: Error, message?: string): void;\n (error: Error, context: Bindings, message?: string): void;\n (context: Bindings, message?: string): void;\n};\n\n/* ─── Logger interface ─── */\n\nexport type Logger = {\n /** Delegates to `dispose()`. Enables `using` declarations. */\n [Symbol.dispose]: () => void;\n /** Snapshot of currently pinned bindings. */\n readonly bindings: Readonly<Bindings>;\n /** Create a child logger with config overrides. Inherits all config and bindings by default. */\n child: (overrides?: RuneOptions) => Logger;\n debug: LogMethod;\n /** `AbortSignal` aborted when `dispose()` is called. Use to tie external lifetimes to this logger. */\n readonly disposalSignal: AbortSignal;\n /**\n * Marks the logger as disposed — all subsequent log calls become no-ops.\n * Aborts `disposalSignal`. Idempotent — safe to call multiple times.\n */\n dispose: () => void;\n /** `true` after `dispose()` has been called. */\n readonly disposed: boolean;\n /** Returns true if entries at this level will pass the configured threshold. */\n enabled: (type: LogLevel) => boolean;\n error: LogMethod;\n fatal: LogMethod;\n /**\n * Wrap a callback in a console group, closing it even on throw/reject.\n * Pass a `level` to gate the group header on the configured log threshold\n * (e.g. `level: 'debug'` suppresses the group when `logLevel` is above `'debug'`).\n * Default: always renders (unless `logLevel` is `'off'`).\n */\n group: <T>(label: string, fn: () => T, level?: LogType) => T;\n /**\n * Same as `group`, using `console.groupCollapsed`.\n * Pass a `level` to gate the group on the configured log threshold.\n */\n groupCollapsed: <T>(label: string, fn: () => T, level?: LogType) => T;\n info: LogMethod;\n /** Active log level for this logger instance. */\n readonly logLevel: LogLevel;\n /** Middleware pipeline snapshot. */\n readonly middleware: readonly LogMiddleware[];\n /** Namespace string for this logger instance. */\n readonly namespace: string;\n /**\n * Measure execution time of `fn` and emit a structured log entry.\n * The entry message is `label`; `data` contains `{ duration_ms }` (rounded to 2 dp).\n * When `fn` throws or rejects, `data` also includes `{ err }` with the serialized error.\n * @param label - Human-readable description of the operation.\n * @param fn - Synchronous or async function to time.\n * @param level - Log level for the timing entry. Default: `'debug'`.\n */\n time: <T>(label: string, fn: () => T, level?: LogType) => T;\n /** Transport pipeline for this logger instance. */\n readonly transports: readonly Transport[];\n /** Return a new logger with one additional middleware function. */\n use: (middleware: LogMiddleware) => Logger;\n warn: LogMethod;\n /**\n * Derive a child logger with additional pinned bindings.\n * The returned logger is fully independent — disposing it does not affect the parent,\n * and disposing the parent does not affect child loggers.\n */\n withBindings: (bindings: Bindings) => Logger;\n};\n"],"mappings":"AAMA,IAAa,EAAqC,CAChD,MAAO,EACP,MAAO,EACP,MAAO,EACP,KAAM,EACN,IAAK,EACL,KAAM,CACR,EAGA,SAAgB,EAAe,EAAqB,EAA0B,CAG5E,OAFI,IAAU,OAEP,EAAS,IAAc,EAAS,EACzC"}
|
package/dist/types.d.ts
CHANGED
|
@@ -2,7 +2,7 @@ export type LogType = 'debug' | 'error' | 'fatal' | 'info' | 'warn';
|
|
|
2
2
|
export type LogLevel = LogType | 'off';
|
|
3
3
|
/** Numeric priority for each level. Lower = more verbose. Exported for transport authors. */
|
|
4
4
|
export declare const PRIORITY: Record<LogLevel, number>;
|
|
5
|
-
/** Returns true if `level` passes the `threshold`. Returns false when `level` is 'off'. Exported for transport
|
|
5
|
+
/** Returns true if `level` passes the `threshold`. Returns false when `level` is 'off'. Exported for transport authors. */
|
|
6
6
|
export declare function isLevelEnabled(threshold: LogLevel, level: LogLevel): boolean;
|
|
7
7
|
export type Bindings = Record<string, unknown>;
|
|
8
8
|
/**
|
|
@@ -33,14 +33,10 @@ export type LogEntry = {
|
|
|
33
33
|
* the caller of `log.info()`/etc. or block its siblings.
|
|
34
34
|
*/
|
|
35
35
|
export type Transport = (entry: LogEntry) => void;
|
|
36
|
-
/**
|
|
37
|
-
* Middleware function that transforms or filters log entries before dispatch. Return null to drop the entry.
|
|
38
|
-
* If middleware throws, the logger catches it, reports it via a dev-only warning, and drops the entry
|
|
39
|
-
* (no transports run for it) rather than crashing the caller.
|
|
40
|
-
*/
|
|
36
|
+
/** Transform or filter entries before dispatch. Return `null` to drop an entry. */
|
|
41
37
|
export type LogMiddleware = (entry: LogEntry) => LogEntry | null;
|
|
42
38
|
export type RemoteLogData = {
|
|
43
|
-
data?: Bindings
|
|
39
|
+
data?: Readonly<Bindings>;
|
|
44
40
|
env: 'development' | 'production';
|
|
45
41
|
level: LogType;
|
|
46
42
|
message?: string;
|
|
@@ -50,17 +46,11 @@ export type RemoteLogData = {
|
|
|
50
46
|
export type RemoteTransportOptions = {
|
|
51
47
|
/** Override the detected runtime environment. Default: auto-detected. */
|
|
52
48
|
env?: 'development' | 'production';
|
|
53
|
-
/** Remote delivery handler
|
|
54
|
-
handler: (type: LogType, data: RemoteLogData) => void
|
|
49
|
+
/** Remote delivery handler. */
|
|
50
|
+
handler: (type: LogType, data: RemoteLogData) => void | Promise<unknown>;
|
|
55
51
|
/** Minimum level to forward. Default: 'debug'. */
|
|
56
52
|
level?: LogLevel;
|
|
57
|
-
/**
|
|
58
|
-
* Called when the handler throws or rejects.
|
|
59
|
-
* The async error path is separate from any synchronous errors in the emit call stack.
|
|
60
|
-
* Default: a dev-only `console.warn` (gated by `__RUNE_PROD__`). In production builds,
|
|
61
|
-
* unhandled remote transport errors are silently swallowed — pass an explicit `onError`
|
|
62
|
-
* if you need delivery-failure observability in production.
|
|
63
|
-
*/
|
|
53
|
+
/** Observe synchronous throws and asynchronous rejections from `handler`. */
|
|
64
54
|
onError?: (error: unknown, data: RemoteLogData) => void;
|
|
65
55
|
};
|
|
66
56
|
export type JsonTransportOptions = {
|
|
@@ -88,77 +78,42 @@ export type JsonTransportOptions = {
|
|
|
88
78
|
*/
|
|
89
79
|
safe?: boolean;
|
|
90
80
|
};
|
|
91
|
-
/** Handle returned by `batchTransport()`.
|
|
81
|
+
/** Handle returned by `batchTransport()`. */
|
|
92
82
|
export type BatchHandle = {
|
|
93
|
-
/** Delegates to `dispose()`. Enables `await using` declarations. */
|
|
94
83
|
[Symbol.asyncDispose]: () => Promise<void>;
|
|
95
|
-
/** Stop the interval timer, flush remaining entries, and wait for all accepted batches. Idempotent. */
|
|
96
84
|
dispose: () => Promise<void>;
|
|
97
|
-
/** `true` after `dispose()` has been called. */
|
|
98
85
|
readonly disposed: boolean;
|
|
99
|
-
/** Immediately flush buffered entries and wait for their downstream delivery without stopping the timer. */
|
|
100
86
|
flush: () => Promise<void>;
|
|
101
|
-
/** The transport function to pass to `createLogger({ transports: [handle.transport] })`. */
|
|
102
87
|
transport: Transport;
|
|
103
88
|
};
|
|
104
89
|
export type BatchTransportOptions = {
|
|
105
|
-
/** Flush interval in milliseconds.
|
|
90
|
+
/** Flush interval in milliseconds. Default: 5000. */
|
|
106
91
|
interval?: number;
|
|
107
92
|
/** Minimum level to buffer. Default: 'debug'. */
|
|
108
93
|
level?: LogLevel;
|
|
109
|
-
/**
|
|
110
|
-
* Hard limit on the in-memory buffer size. Must be a finite non-negative integer.
|
|
111
|
-
* When the buffer exceeds this value, the oldest entries are dropped to prevent
|
|
112
|
-
* unbounded memory growth. Unlike `maxSize`, this does NOT trigger a flush.
|
|
113
|
-
* Default: unbounded.
|
|
114
|
-
*/
|
|
94
|
+
/** Hard limit on buffered entries. Oldest entries are dropped first. Default: unbounded. */
|
|
115
95
|
maxBuffer?: number;
|
|
116
|
-
/**
|
|
96
|
+
/** Flush once this many entries are buffered. Default: 50. */
|
|
117
97
|
maxSize?: number;
|
|
118
|
-
/**
|
|
98
|
+
/** Deliver one accepted batch. Batches are delivered serially. */
|
|
119
99
|
onFlush: (entries: LogEntry[]) => void | Promise<void>;
|
|
120
|
-
/**
|
|
121
|
-
* Called when onFlush throws synchronously or rejects asynchronously. Observes the
|
|
122
|
-
* failure; the corresponding `flush()` or `dispose()` promise still rejects.
|
|
123
|
-
*/
|
|
100
|
+
/** Observe delivery failures. Manual failures reject `flush()`; automatic failures reject `dispose()`. */
|
|
124
101
|
onFlushError?: (entries: LogEntry[], error: unknown) => void;
|
|
125
102
|
};
|
|
126
|
-
export type PipeOptions = {
|
|
127
|
-
/**
|
|
128
|
-
* Called when one of the piped transports throws.
|
|
129
|
-
* Receives the thrown error and the log entry that triggered it.
|
|
130
|
-
* Default: silent (errors are swallowed to protect remaining transports).
|
|
131
|
-
*/
|
|
132
|
-
onError?: (error: unknown, entry: LogEntry) => void;
|
|
133
|
-
};
|
|
134
103
|
export type SampleTransportOptions = {
|
|
135
104
|
/** Minimum level to sample. Default: 'debug'. */
|
|
136
105
|
level?: LogLevel;
|
|
137
|
-
/** Finite fraction of entries to forward
|
|
106
|
+
/** Finite fraction of entries to forward, from 0 to 1. */
|
|
138
107
|
rate: number;
|
|
139
|
-
/** Downstream transport to receive sampled entries. */
|
|
140
108
|
transport: Transport;
|
|
141
109
|
};
|
|
142
110
|
export type RedactTransportOptions = {
|
|
143
|
-
/**
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
* A key like `'password'` will redact every field named `'password'` at any nesting level.
|
|
147
|
-
*/
|
|
148
|
-
keys: string[];
|
|
149
|
-
/**
|
|
150
|
-
* Finite non-negative integer maximum object nesting depth to traverse during redaction.
|
|
151
|
-
* Objects deeper than this limit are returned as-is (not redacted).
|
|
152
|
-
* A dev-only warning is emitted when the cap is hit.
|
|
153
|
-
* Default: 20.
|
|
154
|
-
* @security In production builds, the depth warning is suppressed — deeply-nested sensitive
|
|
155
|
-
* fields beyond `maxDepth` will pass through unredacted without any indication. Ensure that
|
|
156
|
-
* sensitive payloads are not nested beyond this limit, or lower `maxDepth` as needed.
|
|
157
|
-
*/
|
|
111
|
+
/** Exact field names to replace at any depth. */
|
|
112
|
+
keys: readonly string[];
|
|
113
|
+
/** Maximum nested object depth to inspect. Deeper subtrees are replaced entirely. Default: 20. */
|
|
158
114
|
maxDepth?: number;
|
|
159
|
-
/** Replacement
|
|
115
|
+
/** Replacement used for matching fields and depth-limited subtrees. Default: '[REDACTED]'. */
|
|
160
116
|
replacement?: string;
|
|
161
|
-
/** Downstream transport to receive the redacted entry. */
|
|
162
117
|
transport: Transport;
|
|
163
118
|
};
|
|
164
119
|
export type RuneOptions = {
|
|
@@ -166,7 +121,7 @@ export type RuneOptions = {
|
|
|
166
121
|
bindings?: Bindings;
|
|
167
122
|
/** Minimum log level for this logger instance. Default: 'debug'. */
|
|
168
123
|
logLevel?: LogLevel;
|
|
169
|
-
/** Middleware
|
|
124
|
+
/** Middleware applied once, in order, before dispatch to every transport. */
|
|
170
125
|
middleware?: LogMiddleware[];
|
|
171
126
|
/**
|
|
172
127
|
* Namespace for this logger. When passed to `child()`, it is automatically
|
|
@@ -182,20 +137,17 @@ export type RuneOptions = {
|
|
|
182
137
|
/**
|
|
183
138
|
* Signature shared by all five log-level methods.
|
|
184
139
|
*
|
|
185
|
-
* -
|
|
186
|
-
*
|
|
187
|
-
*
|
|
188
|
-
* Serialization is shallow only — an `Error` nested inside a nested object is left as-is.
|
|
189
|
-
* - `log.error(err, { ...fields }, 'message')` — Error first, then optional context + message.
|
|
190
|
-
* Shorthand for the pattern where an Error is the primary subject of the log call.
|
|
191
|
-
* The context object may be omitted entirely: `log.error(err, 'message')`.
|
|
140
|
+
* Message-first calls are preferred for ordinary application logging. Context-first and error-first
|
|
141
|
+
* calls support structured events, adapters, and error forwarding without synthetic messages.
|
|
142
|
+
* `Error` values in context are serialized shallowly to `{ message, name, stack }`.
|
|
192
143
|
*
|
|
193
144
|
* @example
|
|
194
|
-
* log.
|
|
195
|
-
* log.
|
|
145
|
+
* log.info('request started', { requestId: 'abc' })
|
|
146
|
+
* log.debug(event, `bus:${event.type}`)
|
|
147
|
+
* log.error(new Error('timeout'), { requestId: 'abc' }, 'request failed')
|
|
196
148
|
*/
|
|
197
149
|
export type LogMethod = {
|
|
198
|
-
(message: string): void;
|
|
150
|
+
(message: string, context?: Bindings): void;
|
|
199
151
|
(error: Error, message?: string): void;
|
|
200
152
|
(error: Error, context: Bindings, message?: string): void;
|
|
201
153
|
(context: Bindings, message?: string): void;
|
|
@@ -212,9 +164,7 @@ export type Logger = {
|
|
|
212
164
|
readonly disposalSignal: AbortSignal;
|
|
213
165
|
/**
|
|
214
166
|
* Marks the logger as disposed — all subsequent log calls become no-ops.
|
|
215
|
-
* Aborts `disposalSignal`.
|
|
216
|
-
* hold a direct reference to `batchTransport` and call its `dispose()` on shutdown.
|
|
217
|
-
* Idempotent — safe to call multiple times.
|
|
167
|
+
* Aborts `disposalSignal`. Idempotent — safe to call multiple times.
|
|
218
168
|
*/
|
|
219
169
|
dispose: () => void;
|
|
220
170
|
/** `true` after `dispose()` has been called. */
|
|
@@ -238,7 +188,7 @@ export type Logger = {
|
|
|
238
188
|
info: LogMethod;
|
|
239
189
|
/** Active log level for this logger instance. */
|
|
240
190
|
readonly logLevel: LogLevel;
|
|
241
|
-
/** Middleware pipeline
|
|
191
|
+
/** Middleware pipeline snapshot. */
|
|
242
192
|
readonly middleware: readonly LogMiddleware[];
|
|
243
193
|
/** Namespace string for this logger instance. */
|
|
244
194
|
readonly namespace: string;
|
|
@@ -253,12 +203,7 @@ export type Logger = {
|
|
|
253
203
|
time: <T>(label: string, fn: () => T, level?: LogType) => T;
|
|
254
204
|
/** Transport pipeline for this logger instance. */
|
|
255
205
|
readonly transports: readonly Transport[];
|
|
256
|
-
/**
|
|
257
|
-
* Add a middleware function to the pipeline. Returns a **new** logger — the original is unchanged.
|
|
258
|
-
* Discarding the return value is a common mistake: always assign the result.
|
|
259
|
-
* @example
|
|
260
|
-
* const log = baseLog.use(tracingMiddleware); // ✓ keep the result
|
|
261
|
-
*/
|
|
206
|
+
/** Return a new logger with one additional middleware function. */
|
|
262
207
|
use: (middleware: LogMiddleware) => Logger;
|
|
263
208
|
warn: LogMethod;
|
|
264
209
|
/**
|
package/dist/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAEA,MAAM,MAAM,OAAO,GAAG,OAAO,GAAG,OAAO,GAAG,OAAO,GAAG,MAAM,GAAG,MAAM,CAAC;AACpE,MAAM,MAAM,QAAQ,GAAG,OAAO,GAAG,KAAK,CAAC;AAEvC,6FAA6F;AAC7F,eAAO,MAAM,QAAQ,EAAE,MAAM,CAAC,QAAQ,EAAE,MAAM,CAO7C,CAAC;AAEF,
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAEA,MAAM,MAAM,OAAO,GAAG,OAAO,GAAG,OAAO,GAAG,OAAO,GAAG,MAAM,GAAG,MAAM,CAAC;AACpE,MAAM,MAAM,QAAQ,GAAG,OAAO,GAAG,KAAK,CAAC;AAEvC,6FAA6F;AAC7F,eAAO,MAAM,QAAQ,EAAE,MAAM,CAAC,QAAQ,EAAE,MAAM,CAO7C,CAAC;AAEF,2HAA2H;AAC3H,wBAAgB,cAAc,CAAC,SAAS,EAAE,QAAQ,EAAE,KAAK,EAAE,QAAQ,GAAG,OAAO,CAI5E;AAID,MAAM,MAAM,QAAQ,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AAI/C;;;;;;;;GAQG;AACH,MAAM,MAAM,QAAQ,GAAG;IACrB;;;OAGG;IACH,IAAI,EAAE,QAAQ,CAAC,QAAQ,CAAC,CAAC;IACzB,KAAK,EAAE,OAAO,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,sFAAsF;IACtF,SAAS,EAAE,IAAI,CAAC;CACjB,CAAC;AAIF;;;;;GAKG;AACH,MAAM,MAAM,SAAS,GAAG,CAAC,KAAK,EAAE,QAAQ,KAAK,IAAI,CAAC;AAElD,mFAAmF;AACnF,MAAM,MAAM,aAAa,GAAG,CAAC,KAAK,EAAE,QAAQ,KAAK,QAAQ,GAAG,IAAI,CAAC;AAIjE,MAAM,MAAM,aAAa,GAAG;IAC1B,IAAI,CAAC,EAAE,QAAQ,CAAC,QAAQ,CAAC,CAAC;IAC1B,GAAG,EAAE,aAAa,GAAG,YAAY,CAAC;IAClC,KAAK,EAAE,OAAO,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;CACnB,CAAC;AAEF,MAAM,MAAM,sBAAsB,GAAG;IACnC,yEAAyE;IACzE,GAAG,CAAC,EAAE,aAAa,GAAG,YAAY,CAAC;IACnC,+BAA+B;IAC/B,OAAO,EAAE,CAAC,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,aAAa,KAAK,IAAI,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IACzE,kDAAkD;IAClD,KAAK,CAAC,EAAE,QAAQ,CAAC;IACjB,6EAA6E;IAC7E,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,aAAa,KAAK,IAAI,CAAC;CACzD,CAAC;AAEF,MAAM,MAAM,oBAAoB,GAAG;IACjC;;;;;;OAMG;IACH,MAAM,CAAC,EAAE;QACP,KAAK,CAAC,EAAE,MAAM,CAAC;QACf,GAAG,CAAC,EAAE,MAAM,CAAC;QACb,EAAE,CAAC,EAAE,MAAM,CAAC;QACZ,IAAI,CAAC,EAAE,MAAM,CAAC;KACf,CAAC;IACF,iDAAiD;IACjD,KAAK,CAAC,EAAE,QAAQ,CAAC;IACjB,6DAA6D;IAC7D,MAAM,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IAChC;;;;OAIG;IACH,IAAI,CAAC,EAAE,OAAO,CAAC;CAChB,CAAC;AAEF,6CAA6C;AAC7C,MAAM,MAAM,WAAW,GAAG;IACxB,CAAC,MAAM,CAAC,YAAY,CAAC,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAC3C,OAAO,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAC7B,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC;IAC3B,KAAK,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAC3B,SAAS,EAAE,SAAS,CAAC;CACtB,CAAC;AAEF,MAAM,MAAM,qBAAqB,GAAG;IAClC,qDAAqD;IACrD,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,iDAAiD;IACjD,KAAK,CAAC,EAAE,QAAQ,CAAC;IACjB,4FAA4F;IAC5F,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,8DAA8D;IAC9D,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,kEAAkE;IAClE,OAAO,EAAE,CAAC,OAAO,EAAE,QAAQ,EAAE,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACvD,0GAA0G;IAC1G,YAAY,CAAC,EAAE,CAAC,OAAO,EAAE,QAAQ,EAAE,EAAE,KAAK,EAAE,OAAO,KAAK,IAAI,CAAC;CAC9D,CAAC;AAEF,MAAM,MAAM,sBAAsB,GAAG;IACnC,iDAAiD;IACjD,KAAK,CAAC,EAAE,QAAQ,CAAC;IACjB,0DAA0D;IAC1D,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,SAAS,CAAC;CACtB,CAAC;AAEF,MAAM,MAAM,sBAAsB,GAAG;IACnC,iDAAiD;IACjD,IAAI,EAAE,SAAS,MAAM,EAAE,CAAC;IACxB,kGAAkG;IAClG,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,8FAA8F;IAC9F,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,SAAS,EAAE,SAAS,CAAC;CACtB,CAAC;AAIF,MAAM,MAAM,WAAW,GAAG;IACxB,sHAAsH;IACtH,QAAQ,CAAC,EAAE,QAAQ,CAAC;IACpB,oEAAoE;IACpE,QAAQ,CAAC,EAAE,QAAQ,CAAC;IACpB,6EAA6E;IAC7E,UAAU,CAAC,EAAE,aAAa,EAAE,CAAC;IAC7B;;;OAGG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;;OAGG;IACH,UAAU,CAAC,EAAE,SAAS,EAAE,CAAC;CAC1B,CAAC;AAIF;;;;;;;;;;;GAWG;AACH,MAAM,MAAM,SAAS,GAAG;IACtB,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,QAAQ,GAAG,IAAI,CAAC;IAC5C,CAAC,KAAK,EAAE,KAAK,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACvC,CAAC,KAAK,EAAE,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1D,CAAC,OAAO,EAAE,QAAQ,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC7C,CAAC;AAIF,MAAM,MAAM,MAAM,GAAG;IACnB,8DAA8D;IAC9D,CAAC,MAAM,CAAC,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IAC7B,6CAA6C;IAC7C,QAAQ,CAAC,QAAQ,EAAE,QAAQ,CAAC,QAAQ,CAAC,CAAC;IACtC,gGAAgG;IAChG,KAAK,EAAE,CAAC,SAAS,CAAC,EAAE,WAAW,KAAK,MAAM,CAAC;IAC3C,KAAK,EAAE,SAAS,CAAC;IACjB,sGAAsG;IACtG,QAAQ,CAAC,cAAc,EAAE,WAAW,CAAC;IACrC;;;OAGG;IACH,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB,gDAAgD;IAChD,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC;IAC3B,gFAAgF;IAChF,OAAO,EAAE,CAAC,IAAI,EAAE,QAAQ,KAAK,OAAO,CAAC;IACrC,KAAK,EAAE,SAAS,CAAC;IACjB,KAAK,EAAE,SAAS,CAAC;IACjB;;;;;OAKG;IACH,KAAK,EAAE,CAAC,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,CAAC,EAAE,KAAK,CAAC,EAAE,OAAO,KAAK,CAAC,CAAC;IAC7D;;;OAGG;IACH,cAAc,EAAE,CAAC,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,CAAC,EAAE,KAAK,CAAC,EAAE,OAAO,KAAK,CAAC,CAAC;IACtE,IAAI,EAAE,SAAS,CAAC;IAChB,iDAAiD;IACjD,QAAQ,CAAC,QAAQ,EAAE,QAAQ,CAAC;IAC5B,oCAAoC;IACpC,QAAQ,CAAC,UAAU,EAAE,SAAS,aAAa,EAAE,CAAC;IAC9C,iDAAiD;IACjD,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B;;;;;;;OAOG;IACH,IAAI,EAAE,CAAC,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,CAAC,EAAE,KAAK,CAAC,EAAE,OAAO,KAAK,CAAC,CAAC;IAC5D,mDAAmD;IACnD,QAAQ,CAAC,UAAU,EAAE,SAAS,SAAS,EAAE,CAAC;IAC1C,mEAAmE;IACnE,GAAG,EAAE,CAAC,UAAU,EAAE,aAAa,KAAK,MAAM,CAAC;IAC3C,IAAI,EAAE,SAAS,CAAC;IAChB;;;;OAIG;IACH,YAAY,EAAE,CAAC,QAAQ,EAAE,QAAQ,KAAK,MAAM,CAAC;CAC9C,CAAC"}
|
package/dist/types.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.js","names":[],"sources":["../src/types.ts"],"sourcesContent":["/* ─── Log levels ─── */\n\nexport type LogType = 'debug' | 'error' | 'fatal' | 'info' | 'warn';\nexport type LogLevel = LogType | 'off';\n\n/** Numeric priority for each level. Lower = more verbose. Exported for transport authors. */\nexport const PRIORITY: Record<LogLevel, number> = {\n debug: 0,\n error: 3,\n fatal: 4,\n info: 1,\n off: 5,\n warn: 2,\n};\n\n/** Returns true if `level` passes the `threshold`. Returns false when `level` is 'off'. Exported for transport/middleware authors. */\nexport function isLevelEnabled(threshold: LogLevel, level: LogLevel): boolean {\n if (level === 'off') return false;\n\n return PRIORITY[threshold] <= PRIORITY[level];\n}\n\n/* ─── Bindings ─── */\n\nexport type Bindings = Record<string, unknown>;\n\n/* ─── Log entry ─── */\n\n/**\n * The structured record produced by every log call and dispatched to all transports.\n * `data` is the merged result of pinned bindings and per-call context — transports\n * receive a single flat object and do not need to merge anything themselves.\n * Any `Error` instances — whether from a pinned binding (`bindings`/`withBindings()`) or\n * per-call context — are automatically serialized to `{ message, name, stack }`.\n * **Shallow only** — an `Error` nested inside a plain object (e.g. `{ meta: { err } }`) is left as-is;\n * only top-level fields of `data` are checked.\n */\nexport type LogEntry = {\n /**\n * Merged structured data: pinned bindings overlaid with per-call context.\n * Already shallow-copied and immutable — do not mutate.\n */\n data: Readonly<Bindings>;\n level: LogType;\n message?: string;\n namespace: string;\n /** Exact moment of the log call — shared across all transports for the same entry. */\n timestamp: Date;\n};\n\n/* ─── Transport ─── */\n\n/**\n * A transport receives a log entry and is responsible for its own delivery and formatting.\n * If a transport throws, the logger catches it, reports it via a dev-only warning, and continues\n * dispatching the entry to remaining transports — a single misbehaving transport can never crash\n * the caller of `log.info()`/etc. or block its siblings.\n */\nexport type Transport = (entry: LogEntry) => void;\n\n/**\n * Middleware function that transforms or filters log entries before dispatch. Return null to drop the entry.\n * If middleware throws, the logger catches it, reports it via a dev-only warning, and drops the entry\n * (no transports run for it) rather than crashing the caller.\n */\nexport type LogMiddleware = (entry: LogEntry) => LogEntry | null;\n\n/* ─── Transport option types ─── */\n\nexport type RemoteLogData = {\n data?: Bindings;\n env: 'development' | 'production';\n level: LogType;\n message?: string;\n namespace?: string;\n timestamp: string;\n};\n\nexport type RemoteTransportOptions = {\n /** Override the detected runtime environment. Default: auto-detected. */\n env?: 'development' | 'production';\n /** Remote delivery handler — receives the log type and structured payload. */\n handler: (type: LogType, data: RemoteLogData) => void;\n /** Minimum level to forward. Default: 'debug'. */\n level?: LogLevel;\n /**\n * Called when the handler throws or rejects.\n * The async error path is separate from any synchronous errors in the emit call stack.\n * Default: a dev-only `console.warn` (gated by `__RUNE_PROD__`). In production builds,\n * unhandled remote transport errors are silently swallowed — pass an explicit `onError`\n * if you need delivery-failure observability in production.\n */\n onError?: (error: unknown, data: RemoteLogData) => void;\n};\n\nexport type JsonTransportOptions = {\n /**\n * Custom output field names. Useful for adapting to aggregator conventions\n * (Datadog, ELK, Loki, etc.).\n *\n * @example\n * jsonTransport({ fields: { level: 'severity', time: '@timestamp', msg: 'message' } })\n */\n fields?: {\n level?: string;\n msg?: string;\n ns?: string;\n time?: string;\n };\n /** Minimum level to output. Default: 'debug'. */\n level?: LogLevel;\n /** Custom output function. Default: process.stdout.write. */\n output?: (line: string) => void;\n /**\n * Replace circular references with `'[Circular]'` instead of throwing a TypeError.\n * Useful in environments where log payloads may contain complex object graphs.\n * Default: false.\n */\n safe?: boolean;\n};\n\n/** Handle returned by `batchTransport()`. Pass `handle.transport` to `createLogger({ transports })`. */\nexport type BatchHandle = {\n /** Delegates to `dispose()`. Enables `await using` declarations. */\n [Symbol.asyncDispose]: () => Promise<void>;\n /** Stop the interval timer, flush remaining entries, and wait for all accepted batches. Idempotent. */\n dispose: () => Promise<void>;\n /** `true` after `dispose()` has been called. */\n readonly disposed: boolean;\n /** Immediately flush buffered entries and wait for their downstream delivery without stopping the timer. */\n flush: () => Promise<void>;\n /** The transport function to pass to `createLogger({ transports: [handle.transport] })`. */\n transport: Transport;\n};\n\nexport type BatchTransportOptions = {\n /** Flush interval in milliseconds. Must be finite and greater than zero. Default: 5000. */\n interval?: number;\n /** Minimum level to buffer. Default: 'debug'. */\n level?: LogLevel;\n /**\n * Hard limit on the in-memory buffer size. Must be a finite non-negative integer.\n * When the buffer exceeds this value, the oldest entries are dropped to prevent\n * unbounded memory growth. Unlike `maxSize`, this does NOT trigger a flush.\n * Default: unbounded.\n */\n maxBuffer?: number;\n /** Maximum buffer size before an early flush. Must be a finite positive integer. Default: 50. */\n maxSize?: number;\n /** Callback to receive flushed batches. May return a Promise. */\n onFlush: (entries: LogEntry[]) => void | Promise<void>;\n /**\n * Called when onFlush throws synchronously or rejects asynchronously. Observes the\n * failure; the corresponding `flush()` or `dispose()` promise still rejects.\n */\n onFlushError?: (entries: LogEntry[], error: unknown) => void;\n};\n\nexport type PipeOptions = {\n /**\n * Called when one of the piped transports throws.\n * Receives the thrown error and the log entry that triggered it.\n * Default: silent (errors are swallowed to protect remaining transports).\n */\n onError?: (error: unknown, entry: LogEntry) => void;\n};\n\nexport type SampleTransportOptions = {\n /** Minimum level to sample. Default: 'debug'. */\n level?: LogLevel;\n /** Finite fraction of entries to forward (0–1). */\n rate: number;\n /** Downstream transport to receive sampled entries. */\n transport: Transport;\n};\n\nexport type RedactTransportOptions = {\n /**\n * Field names to replace at any depth in `data`.\n * Matched by exact field name — dot-path notation (e.g. `'user.password'`) is NOT supported.\n * A key like `'password'` will redact every field named `'password'` at any nesting level.\n */\n keys: string[];\n /**\n * Finite non-negative integer maximum object nesting depth to traverse during redaction.\n * Objects deeper than this limit are returned as-is (not redacted).\n * A dev-only warning is emitted when the cap is hit.\n * Default: 20.\n * @security In production builds, the depth warning is suppressed — deeply-nested sensitive\n * fields beyond `maxDepth` will pass through unredacted without any indication. Ensure that\n * sensitive payloads are not nested beyond this limit, or lower `maxDepth` as needed.\n */\n maxDepth?: number;\n /** Replacement value for redacted fields. Default: '[REDACTED]'. */\n replacement?: string;\n /** Downstream transport to receive the redacted entry. */\n transport: Transport;\n};\n\n/* ─── Logger options ─── */\n\nexport type RuneOptions = {\n /** Initial pinned bindings for this logger instance. `Error` values are auto-serialized, same as per-call context. */\n bindings?: Bindings;\n /** Minimum log level for this logger instance. Default: 'debug'. */\n logLevel?: LogLevel;\n /** Middleware pipeline applied to every entry before dispatch to transports. */\n middleware?: LogMiddleware[];\n /**\n * Namespace for this logger. When passed to `child()`, it is automatically\n * dot-joined to the parent namespace (e.g. parent `'api'` + child `'auth'` → `'api.auth'`).\n */\n namespace?: string;\n /**\n * Transport pipeline. Each transport receives every entry that passes the level threshold.\n * Default: [consoleTransport()].\n */\n transports?: Transport[];\n};\n\n/* ─── Log method ─── */\n\n/**\n * Signature shared by all five log-level methods.\n *\n * - `log.info('message')` — string-only, most common.\n * - `log.info({ ...fields }, 'message')` — structured context + optional message.\n * `Error` values in `fields` are automatically serialized to `{ message, name, stack }`.\n * Serialization is shallow only — an `Error` nested inside a nested object is left as-is.\n * - `log.error(err, { ...fields }, 'message')` — Error first, then optional context + message.\n * Shorthand for the pattern where an Error is the primary subject of the log call.\n * The context object may be omitted entirely: `log.error(err, 'message')`.\n *\n * @example\n * log.error({ err: new Error('timeout'), requestId }, 'request failed')\n * log.error(new Error('timeout'), { requestId }, 'request failed')\n */\nexport type LogMethod = {\n (message: string): void;\n (error: Error, message?: string): void;\n (error: Error, context: Bindings, message?: string): void;\n (context: Bindings, message?: string): void;\n};\n\n/* ─── Logger interface ─── */\n\nexport type Logger = {\n /** Delegates to `dispose()`. Enables `using` declarations. */\n [Symbol.dispose]: () => void;\n /** Snapshot of currently pinned bindings. */\n readonly bindings: Readonly<Bindings>;\n /** Create a child logger with config overrides. Inherits all config and bindings by default. */\n child: (overrides?: RuneOptions) => Logger;\n debug: LogMethod;\n /** `AbortSignal` aborted when `dispose()` is called. Use to tie external lifetimes to this logger. */\n readonly disposalSignal: AbortSignal;\n /**\n * Marks the logger as disposed — all subsequent log calls become no-ops.\n * Aborts `disposalSignal`. Does NOT auto-discover or dispose batch transports;\n * hold a direct reference to `batchTransport` and call its `dispose()` on shutdown.\n * Idempotent — safe to call multiple times.\n */\n dispose: () => void;\n /** `true` after `dispose()` has been called. */\n readonly disposed: boolean;\n /** Returns true if entries at this level will pass the configured threshold. */\n enabled: (type: LogLevel) => boolean;\n error: LogMethod;\n fatal: LogMethod;\n /**\n * Wrap a callback in a console group, closing it even on throw/reject.\n * Pass a `level` to gate the group header on the configured log threshold\n * (e.g. `level: 'debug'` suppresses the group when `logLevel` is above `'debug'`).\n * Default: always renders (unless `logLevel` is `'off'`).\n */\n group: <T>(label: string, fn: () => T, level?: LogType) => T;\n /**\n * Same as `group`, using `console.groupCollapsed`.\n * Pass a `level` to gate the group on the configured log threshold.\n */\n groupCollapsed: <T>(label: string, fn: () => T, level?: LogType) => T;\n info: LogMethod;\n /** Active log level for this logger instance. */\n readonly logLevel: LogLevel;\n /** Middleware pipeline applied before dispatch. */\n readonly middleware: readonly LogMiddleware[];\n /** Namespace string for this logger instance. */\n readonly namespace: string;\n /**\n * Measure execution time of `fn` and emit a structured log entry.\n * The entry message is `label`; `data` contains `{ duration_ms }` (rounded to 2 dp).\n * When `fn` throws or rejects, `data` also includes `{ err }` with the serialized error.\n * @param label - Human-readable description of the operation.\n * @param fn - Synchronous or async function to time.\n * @param level - Log level for the timing entry. Default: `'debug'`.\n */\n time: <T>(label: string, fn: () => T, level?: LogType) => T;\n /** Transport pipeline for this logger instance. */\n readonly transports: readonly Transport[];\n /**\n * Add a middleware function to the pipeline. Returns a **new** logger — the original is unchanged.\n * Discarding the return value is a common mistake: always assign the result.\n * @example\n * const log = baseLog.use(tracingMiddleware); // ✓ keep the result\n */\n use: (middleware: LogMiddleware) => Logger;\n warn: LogMethod;\n /**\n * Derive a child logger with additional pinned bindings.\n * The returned logger is fully independent — disposing it does not affect the parent,\n * and disposing the parent does not affect child loggers.\n */\n withBindings: (bindings: Bindings) => Logger;\n};\n"],"mappings":";AAMA,IAAa,IAAqC;CAChD,OAAO;CACP,OAAO;CACP,OAAO;CACP,MAAM;CACN,KAAK;CACL,MAAM;AACR;AAGA,SAAgB,EAAe,GAAqB,GAA0B;CAG5E,OAFI,MAAU,SAEP,EAAS,MAAc,EAAS;AACzC"}
|
|
1
|
+
{"version":3,"file":"types.js","names":[],"sources":["../src/types.ts"],"sourcesContent":["/* ─── Log levels ─── */\n\nexport type LogType = 'debug' | 'error' | 'fatal' | 'info' | 'warn';\nexport type LogLevel = LogType | 'off';\n\n/** Numeric priority for each level. Lower = more verbose. Exported for transport authors. */\nexport const PRIORITY: Record<LogLevel, number> = {\n debug: 0,\n error: 3,\n fatal: 4,\n info: 1,\n off: 5,\n warn: 2,\n};\n\n/** Returns true if `level` passes the `threshold`. Returns false when `level` is 'off'. Exported for transport authors. */\nexport function isLevelEnabled(threshold: LogLevel, level: LogLevel): boolean {\n if (level === 'off') return false;\n\n return PRIORITY[threshold] <= PRIORITY[level];\n}\n\n/* ─── Bindings ─── */\n\nexport type Bindings = Record<string, unknown>;\n\n/* ─── Log entry ─── */\n\n/**\n * The structured record produced by every log call and dispatched to all transports.\n * `data` is the merged result of pinned bindings and per-call context — transports\n * receive a single flat object and do not need to merge anything themselves.\n * Any `Error` instances — whether from a pinned binding (`bindings`/`withBindings()`) or\n * per-call context — are automatically serialized to `{ message, name, stack }`.\n * **Shallow only** — an `Error` nested inside a plain object (e.g. `{ meta: { err } }`) is left as-is;\n * only top-level fields of `data` are checked.\n */\nexport type LogEntry = {\n /**\n * Merged structured data: pinned bindings overlaid with per-call context.\n * Already shallow-copied and immutable — do not mutate.\n */\n data: Readonly<Bindings>;\n level: LogType;\n message?: string;\n namespace: string;\n /** Exact moment of the log call — shared across all transports for the same entry. */\n timestamp: Date;\n};\n\n/* ─── Transport ─── */\n\n/**\n * A transport receives a log entry and is responsible for its own delivery and formatting.\n * If a transport throws, the logger catches it, reports it via a dev-only warning, and continues\n * dispatching the entry to remaining transports — a single misbehaving transport can never crash\n * the caller of `log.info()`/etc. or block its siblings.\n */\nexport type Transport = (entry: LogEntry) => void;\n\n/** Transform or filter entries before dispatch. Return `null` to drop an entry. */\nexport type LogMiddleware = (entry: LogEntry) => LogEntry | null;\n\n/* ─── Transport option types ─── */\n\nexport type RemoteLogData = {\n data?: Readonly<Bindings>;\n env: 'development' | 'production';\n level: LogType;\n message?: string;\n namespace?: string;\n timestamp: string;\n};\n\nexport type RemoteTransportOptions = {\n /** Override the detected runtime environment. Default: auto-detected. */\n env?: 'development' | 'production';\n /** Remote delivery handler. */\n handler: (type: LogType, data: RemoteLogData) => void | Promise<unknown>;\n /** Minimum level to forward. Default: 'debug'. */\n level?: LogLevel;\n /** Observe synchronous throws and asynchronous rejections from `handler`. */\n onError?: (error: unknown, data: RemoteLogData) => void;\n};\n\nexport type JsonTransportOptions = {\n /**\n * Custom output field names. Useful for adapting to aggregator conventions\n * (Datadog, ELK, Loki, etc.).\n *\n * @example\n * jsonTransport({ fields: { level: 'severity', time: '@timestamp', msg: 'message' } })\n */\n fields?: {\n level?: string;\n msg?: string;\n ns?: string;\n time?: string;\n };\n /** Minimum level to output. Default: 'debug'. */\n level?: LogLevel;\n /** Custom output function. Default: process.stdout.write. */\n output?: (line: string) => void;\n /**\n * Replace circular references with `'[Circular]'` instead of throwing a TypeError.\n * Useful in environments where log payloads may contain complex object graphs.\n * Default: false.\n */\n safe?: boolean;\n};\n\n/** Handle returned by `batchTransport()`. */\nexport type BatchHandle = {\n [Symbol.asyncDispose]: () => Promise<void>;\n dispose: () => Promise<void>;\n readonly disposed: boolean;\n flush: () => Promise<void>;\n transport: Transport;\n};\n\nexport type BatchTransportOptions = {\n /** Flush interval in milliseconds. Default: 5000. */\n interval?: number;\n /** Minimum level to buffer. Default: 'debug'. */\n level?: LogLevel;\n /** Hard limit on buffered entries. Oldest entries are dropped first. Default: unbounded. */\n maxBuffer?: number;\n /** Flush once this many entries are buffered. Default: 50. */\n maxSize?: number;\n /** Deliver one accepted batch. Batches are delivered serially. */\n onFlush: (entries: LogEntry[]) => void | Promise<void>;\n /** Observe delivery failures. Manual failures reject `flush()`; automatic failures reject `dispose()`. */\n onFlushError?: (entries: LogEntry[], error: unknown) => void;\n};\n\nexport type SampleTransportOptions = {\n /** Minimum level to sample. Default: 'debug'. */\n level?: LogLevel;\n /** Finite fraction of entries to forward, from 0 to 1. */\n rate: number;\n transport: Transport;\n};\n\nexport type RedactTransportOptions = {\n /** Exact field names to replace at any depth. */\n keys: readonly string[];\n /** Maximum nested object depth to inspect. Deeper subtrees are replaced entirely. Default: 20. */\n maxDepth?: number;\n /** Replacement used for matching fields and depth-limited subtrees. Default: '[REDACTED]'. */\n replacement?: string;\n transport: Transport;\n};\n\n/* ─── Logger options ─── */\n\nexport type RuneOptions = {\n /** Initial pinned bindings for this logger instance. `Error` values are auto-serialized, same as per-call context. */\n bindings?: Bindings;\n /** Minimum log level for this logger instance. Default: 'debug'. */\n logLevel?: LogLevel;\n /** Middleware applied once, in order, before dispatch to every transport. */\n middleware?: LogMiddleware[];\n /**\n * Namespace for this logger. When passed to `child()`, it is automatically\n * dot-joined to the parent namespace (e.g. parent `'api'` + child `'auth'` → `'api.auth'`).\n */\n namespace?: string;\n /**\n * Transport pipeline. Each transport receives every entry that passes the level threshold.\n * Default: [consoleTransport()].\n */\n transports?: Transport[];\n};\n\n/* ─── Log method ─── */\n\n/**\n * Signature shared by all five log-level methods.\n *\n * Message-first calls are preferred for ordinary application logging. Context-first and error-first\n * calls support structured events, adapters, and error forwarding without synthetic messages.\n * `Error` values in context are serialized shallowly to `{ message, name, stack }`.\n *\n * @example\n * log.info('request started', { requestId: 'abc' })\n * log.debug(event, `bus:${event.type}`)\n * log.error(new Error('timeout'), { requestId: 'abc' }, 'request failed')\n */\nexport type LogMethod = {\n (message: string, context?: Bindings): void;\n (error: Error, message?: string): void;\n (error: Error, context: Bindings, message?: string): void;\n (context: Bindings, message?: string): void;\n};\n\n/* ─── Logger interface ─── */\n\nexport type Logger = {\n /** Delegates to `dispose()`. Enables `using` declarations. */\n [Symbol.dispose]: () => void;\n /** Snapshot of currently pinned bindings. */\n readonly bindings: Readonly<Bindings>;\n /** Create a child logger with config overrides. Inherits all config and bindings by default. */\n child: (overrides?: RuneOptions) => Logger;\n debug: LogMethod;\n /** `AbortSignal` aborted when `dispose()` is called. Use to tie external lifetimes to this logger. */\n readonly disposalSignal: AbortSignal;\n /**\n * Marks the logger as disposed — all subsequent log calls become no-ops.\n * Aborts `disposalSignal`. Idempotent — safe to call multiple times.\n */\n dispose: () => void;\n /** `true` after `dispose()` has been called. */\n readonly disposed: boolean;\n /** Returns true if entries at this level will pass the configured threshold. */\n enabled: (type: LogLevel) => boolean;\n error: LogMethod;\n fatal: LogMethod;\n /**\n * Wrap a callback in a console group, closing it even on throw/reject.\n * Pass a `level` to gate the group header on the configured log threshold\n * (e.g. `level: 'debug'` suppresses the group when `logLevel` is above `'debug'`).\n * Default: always renders (unless `logLevel` is `'off'`).\n */\n group: <T>(label: string, fn: () => T, level?: LogType) => T;\n /**\n * Same as `group`, using `console.groupCollapsed`.\n * Pass a `level` to gate the group on the configured log threshold.\n */\n groupCollapsed: <T>(label: string, fn: () => T, level?: LogType) => T;\n info: LogMethod;\n /** Active log level for this logger instance. */\n readonly logLevel: LogLevel;\n /** Middleware pipeline snapshot. */\n readonly middleware: readonly LogMiddleware[];\n /** Namespace string for this logger instance. */\n readonly namespace: string;\n /**\n * Measure execution time of `fn` and emit a structured log entry.\n * The entry message is `label`; `data` contains `{ duration_ms }` (rounded to 2 dp).\n * When `fn` throws or rejects, `data` also includes `{ err }` with the serialized error.\n * @param label - Human-readable description of the operation.\n * @param fn - Synchronous or async function to time.\n * @param level - Log level for the timing entry. Default: `'debug'`.\n */\n time: <T>(label: string, fn: () => T, level?: LogType) => T;\n /** Transport pipeline for this logger instance. */\n readonly transports: readonly Transport[];\n /** Return a new logger with one additional middleware function. */\n use: (middleware: LogMiddleware) => Logger;\n warn: LogMethod;\n /**\n * Derive a child logger with additional pinned bindings.\n * The returned logger is fully independent — disposing it does not affect the parent,\n * and disposing the parent does not affect child loggers.\n */\n withBindings: (bindings: Bindings) => Logger;\n};\n"],"mappings":";AAMA,IAAa,IAAqC;CAChD,OAAO;CACP,OAAO;CACP,OAAO;CACP,MAAM;CACN,KAAK;CACL,MAAM;AACR;AAGA,SAAgB,EAAe,GAAqB,GAA0B;CAG5E,OAFI,MAAU,SAEP,EAAS,MAAc,EAAS;AACzC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vielzeug/rune",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "3.0.0",
|
|
4
4
|
"description": "Structured logging — scoped loggers, pluggable transports, log levels, and remote log draining",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -30,19 +30,19 @@
|
|
|
30
30
|
"registry": "https://registry.npmjs.org/"
|
|
31
31
|
},
|
|
32
32
|
"scripts": {
|
|
33
|
-
"build": "vite build && pnpm run build:bundle && pnpm run build:types",
|
|
33
|
+
"build": "vite build && pnpm --silent run build:bundle && pnpm --silent run build:types",
|
|
34
34
|
"build:bundle": "vite build --config vite.bundle.config.ts",
|
|
35
35
|
"build:types": "tsc -p tsconfig.declarations.json",
|
|
36
36
|
"fix": "biome check --write src",
|
|
37
37
|
"lint": "biome ci src",
|
|
38
|
-
"prepublishOnly": "pnpm run build",
|
|
38
|
+
"prepublishOnly": "pnpm --silent run build",
|
|
39
39
|
"preview": "vite preview",
|
|
40
40
|
"test": "vitest"
|
|
41
41
|
},
|
|
42
42
|
"devDependencies": {
|
|
43
|
-
"@types/node": "^26.
|
|
43
|
+
"@types/node": "^26.4.1",
|
|
44
44
|
"typescript": "^6.0.3",
|
|
45
|
-
"vite": "^8.2.
|
|
46
|
-
"vitest": "^
|
|
45
|
+
"vite": "^8.2.2",
|
|
46
|
+
"vitest": "^5.0.0"
|
|
47
47
|
}
|
|
48
48
|
}
|