{"version":3,"file":"LogContext-L7HzynFw.cjs","names":[],"sources":["../../src/context/LogContext.ts"],"sourcesContent":["/**\n * @fileoverview LogContext bridge — MDC (Mapped Diagnostic Context) management.\n * Encapsulates per-logger structured context, child logger creation, and\n * OTel resource merging.\n *\n * F4 MDC API reset:\n * - `withContext(bindings, fn?)` — if fn provided, run within AsyncLocalStorage.\n *   Without fn: no-op (backwards compat shim for the old setter shape).\n * - `withContextAsync(bindings, fn)` — async callback variant.\n * - `child(bindings)` remains immutable (canonical MDC pattern).\n * - Feature-detect AsyncLocalStorage; browser fallback is a no-op.\n */\n\nimport type { ILogResourceRef } from '../types/index.js';\nimport type { LoggerConfig } from '../types/index.js';\n\n/**\n * Snapshot of the bound context. Returned by {@link LogContext.getContext}.\n */\nexport type ContextSnapshot = Readonly<Record<string, unknown>>;\n\n/**\n * Factory function type for creating child Logger instances.\n * Passed to LogContext so `child()` can instantiate new loggers without\n * introducing a circular import. Returns unknown — the actual Logger class\n * is used by the caller and the returned instance has its `context` field\n * written to by LogContext.\n */\nexport type ChildLoggerFactory = (config: Partial<LoggerConfig>) => unknown;\n\n/**\n * Minimal shape of a Logger instance needed by LogContext.\n * Used to avoid circular dependencies between LogContext and Logger.\n * The `_parentContextRecord` field is set by the parent Logger after\n * `LogContext.child()` returns, establishing the context chain.\n */\nexport interface ChildLoggerShape {\n    _parentContextRecord?: Record<string, unknown>;\n    /** Legacy field — no longer the canonical context source (F4.5.5 fix). */\n    context?: Record<string, unknown>;\n}\n\n/**\n * Options passed to {@link createLogContext}.\n */\nexport interface ILogContextOptions {\n    /** Initial context key-value pairs. */\n    initialContext?: Record<string, unknown>;\n    /** Factory for creating child logger instances. */\n    childLoggerFactory: ChildLoggerFactory;\n    /** Initial OTel resource to merge into every record. */\n    initialResource?: Partial<ILogResourceRef>;\n    /**\n     * Returns the parent logger's merged context record at child-creation time.\n     * Used by _getContextRecord() to build the context chain.\n     * @internal\n     */\n    getParentContextRecord?: () => Record<string, unknown>;\n    /**\n     * The ALS instance to use for withContext scoping.\n     * @internal\n     */\n    alsInstance?: ALS;\n}\n\n/**\n * Result returned by {@link createLogContext}.\n */\nexport interface LogContext {\n    /**\n     * Current bound context snapshot (shallow copy — mutation does NOT affect\n     * what subsequent log calls emit).\n     */\n    getContext(): ContextSnapshot;\n\n    /**\n     * Runs `fn` within an AsyncLocalStorage scope where `bindings` are\n     * merged into the context for all log calls inside `fn`.\n     *\n     * If `fn` is not provided (the old setter shape), this is a no-op\n     * for backwards compatibility. Prefer `child()` for persistent binding\n     * or `withContextAsync()` for async callbacks.\n     *\n     * @param bindings - Key-value pairs to attach for the duration of `fn`\n     * @param fn - Optional synchronous function to run with the scoped bindings\n     * @returns The return value of `fn`, or undefined if no fn provided\n     */\n    withContext<R>(bindings: Record<string, unknown>, fn?: () => R): R | undefined;\n\n    /**\n     * Async variant of `withContext`. Runs `fn` within an AsyncLocalStorage\n     * scope so bindings are available to all async log calls inside `fn`.\n     *\n     * @param bindings - Key-value pairs to attach for the duration of `fn`\n     * @param fn - Async function to run with the scoped bindings\n     * @returns The return value of `fn`\n     */\n    withContextAsync<R>(bindings: Record<string, unknown>, fn: () => Promise<R>): Promise<R>;\n\n    /**\n     * Drops every key from the bound context. After this call, emitted\n     * records no longer carry `attributes` until {@link withContext} or\n     * {@link child} re-establishes one.\n     *\n     * @returns The same LogContext instance, now context-free\n     */\n    clearContext(): this;\n\n    /**\n     * Updates the default OTel resource (service.name, version, env).\n     * Persisted into every emitted record's `resource` field unless the\n     * record itself overrides it.\n     *\n     * @param resource - Partial OTel resource to merge into the current one\n     * @returns The same LogContext instance, for chaining\n     */\n    setResource(resource: Partial<ILogResourceRef>): this;\n\n    /**\n     * Returns an immutable copy of this logger with the extra context bound.\n     * Future calls on the child emit with the merged context, without\n     * mutating the parent — the canonical MDC pattern.\n     *\n     * @param extra - Key-value pairs to attach (requestId, userId, ...)\n     * @returns A new Logger with merged context\n     */\n    child(extra: Record<string, unknown>): ChildLoggerShape;\n\n    /** Internal context record. Exposed for TransportRecord assembly. */\n    _getContextRecord(): Record<string, unknown>;\n    /**\n     * Returns the base context WITHOUT the ALS overlay.\n     * Used by Logger.child() to capture the parent context snapshot for\n     * child logger creation (F4.5.5 fix).\n     * @internal\n     */\n    _getBaseContextRecord(): Record<string, unknown>;\n    /** Internal resource record. Exposed for TransportRecord assembly. */\n    _getResource(): Partial<ILogResourceRef> | undefined;\n    /**\n     * Returns the current AsyncLocalStorage store, if ALS is active.\n     * @internal\n     */\n    _getAlsStore(): Record<string, unknown> | undefined;\n}\n\n// AsyncLocalStorage type (Node 14+, undefined in browser)\ntype ALS = {\n    run<R>(store: Record<string, unknown>, fn: () => R): R;\n    getStore(): Record<string, unknown> | undefined;\n};\ndeclare const AsyncLocalStorage: new () => ALS;\n\n// Feature-detect AsyncLocalStorage\nconst hasALS = typeof AsyncLocalStorage !== 'undefined';\nconst alsInstance: ALS | undefined = hasALS ? new AsyncLocalStorage() : undefined;\n\n/**\n * Creates a LogContext instance.\n *\n * @param options - Configuration options\n * @returns A LogContext instance\n */\nexport function createLogContext(options: ILogContextOptions): LogContext {\n    let context: Record<string, unknown> = { ...options.initialContext };\n    let resource: Partial<ILogResourceRef> | undefined = options.initialResource\n        ? { ...options.initialResource }\n        : undefined;\n\n    // Use provided ALS instance or fall back to module-level (browser fallback)\n    const als = options.alsInstance ?? alsInstance;\n\n    return {\n        getContext(): ContextSnapshot {\n            return { ...context };\n        },\n\n        withContext<R>(bindings: Record<string, unknown>, fn?: () => R): R | undefined {\n            // No-op without AsyncLocalStorage (browser) — warn once\n            if (!als) {\n                if (fn) return fn();\n                return undefined;\n            }\n            // No fn: backwards-compat no-op setter shim\n            if (!fn) return undefined;\n            // Run fn within AsyncLocalStorage scope\n            const merged = { ...context, ...bindings };\n            return als.run(merged, fn);\n        },\n\n        async withContextAsync<R>(bindings: Record<string, unknown>, fn: () => Promise<R>): Promise<R> {\n            if (!als) return fn();\n            const merged = { ...context, ...bindings };\n            return als.run(merged, fn);\n        },\n\n        clearContext(): typeof this {\n            context = {};\n            return this;\n        },\n\n        setResource(res: Partial<ILogResourceRef>): typeof this {\n            resource = { ...resource, ...res };\n            return this;\n        },\n\n        child(_extra: Record<string, unknown>): ChildLoggerShape {\n            // Creates the child logger via factory. Captures the current\n            // _getBaseContextRecord() snapshot (parent context WITHOUT ALS) at\n            // child-creation time. ALS is transient and should not be baked into\n            // the child's _parentContextRecord — it is applied fresh at dispatch time.\n            // Note: _extra (bindings) are stored by Logger.child() as _bindings.\n            const snapshot = this._getBaseContextRecord();\n            const childLogger = options.childLoggerFactory({}) as ChildLoggerShape;\n            // Store on childLogger for Logger.child() to pick up\n            (childLogger as unknown as Record<string, unknown>)['__parentSnapshot'] = snapshot;\n            return childLogger;\n        },\n\n        _getContextRecord(): Record<string, unknown> {\n            // Returns the full merged context for dispatch purposes.\n            // Base (parent snapshot + own context) plus ALS overlay if active.\n            const base = this._getBaseContextRecord();\n            const alsContext = als?.getStore();\n            if (alsContext && Object.keys(alsContext).length > 0) {\n                return { ...base, ...alsContext };\n            }\n            return base;\n        },\n\n        _getBaseContextRecord(): Record<string, unknown> {\n            // Returns parent context chain + own context (NO ALS overlay).\n            // ALS is applied by _getContextRecord() as a live overlay.\n            let base: Record<string, unknown> = {};\n            const parentRecord = options.getParentContextRecord?.() ?? null;\n            if (parentRecord && Object.keys(parentRecord).length > 0) {\n                base = parentRecord;\n            } else if (Object.keys(context).length > 0) {\n                base = context;\n            }\n            if (Object.keys(context).length > 0) {\n                base = { ...base, ...context };\n            }\n            return base;\n        },\n\n        _getResource(): Partial<ILogResourceRef> | undefined {\n            return resource;\n        },\n\n        _getAlsStore(): Record<string, unknown> | undefined {\n            return als?.getStore();\n        }\n    };\n}\n"],"mappings":";AA2JA,MAAM,cADS,OAAO,sBAAsB,cACE,IAAI,kBAAkB,IAAI,KAAA;;;;;;;AAQxE,SAAgB,iBAAiB,SAAyC;CACtE,IAAI,UAAmC,EAAE,GAAG,QAAQ,eAAe;CACnE,IAAI,WAAiD,QAAQ,kBACvD,EAAE,GAAG,QAAQ,gBAAgB,IAC7B,KAAA;CAGN,MAAM,MAAM,QAAQ,eAAe;CAEnC,OAAO;EACH,aAA8B;GAC1B,OAAO,EAAE,GAAG,QAAQ;EACxB;EAEA,YAAe,UAAmC,IAA6B;GAE3E,IAAI,CAAC,KAAK;IACN,IAAI,IAAI,OAAO,GAAG;IAClB;GACJ;GAEA,IAAI,CAAC,IAAI,OAAO,KAAA;GAEhB,MAAM,SAAS;IAAE,GAAG;IAAS,GAAG;GAAS;GACzC,OAAO,IAAI,IAAI,QAAQ,EAAE;EAC7B;EAEA,MAAM,iBAAoB,UAAmC,IAAkC;GAC3F,IAAI,CAAC,KAAK,OAAO,GAAG;GACpB,MAAM,SAAS;IAAE,GAAG;IAAS,GAAG;GAAS;GACzC,OAAO,IAAI,IAAI,QAAQ,EAAE;EAC7B;EAEA,eAA4B;GACxB,UAAU,CAAC;GACX,OAAO;EACX;EAEA,YAAY,KAA4C;GACpD,WAAW;IAAE,GAAG;IAAU,GAAG;GAAI;GACjC,OAAO;EACX;EAEA,MAAM,QAAmD;GAMrD,MAAM,WAAW,KAAK,sBAAsB;GAC5C,MAAM,cAAc,QAAQ,mBAAmB,CAAC,CAAC;GAEjD,YAAoD,sBAAsB;GAC1E,OAAO;EACX;EAEA,oBAA6C;GAGzC,MAAM,OAAO,KAAK,sBAAsB;GACxC,MAAM,aAAa,KAAK,SAAS;GACjC,IAAI,cAAc,OAAO,KAAK,UAAU,CAAC,CAAC,SAAS,GAC/C,OAAO;IAAE,GAAG;IAAM,GAAG;GAAW;GAEpC,OAAO;EACX;EAEA,wBAAiD;GAG7C,IAAI,OAAgC,CAAC;GACrC,MAAM,eAAe,QAAQ,yBAAyB,KAAK;GAC3D,IAAI,gBAAgB,OAAO,KAAK,YAAY,CAAC,CAAC,SAAS,GACnD,OAAO;QACJ,IAAI,OAAO,KAAK,OAAO,CAAC,CAAC,SAAS,GACrC,OAAO;GAEX,IAAI,OAAO,KAAK,OAAO,CAAC,CAAC,SAAS,GAC9B,OAAO;IAAE,GAAG;IAAM,GAAG;GAAQ;GAEjC,OAAO;EACX;EAEA,eAAqD;GACjD,OAAO;EACX;EAEA,eAAoD;GAChD,OAAO,KAAK,SAAS;EACzB;CACJ;AACJ"}