@mks2508/better-logger 0.18.2-alpha.1 → 0.18.3

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.
Files changed (207) hide show
  1. package/README.md +38 -336
  2. package/dist/Logger.d.ts +356 -157
  3. package/dist/Logger.d.ts.map +1 -1
  4. package/dist/ScopedLogger.d.ts +549 -9
  5. package/dist/ScopedLogger.d.ts.map +1 -1
  6. package/dist/chunks/HookBridge-C-AvXmPD.cjs +504 -0
  7. package/dist/chunks/HookBridge-C-AvXmPD.cjs.map +1 -0
  8. package/dist/chunks/HookBridge-CI2PH79S.js +487 -0
  9. package/dist/chunks/HookBridge-CI2PH79S.js.map +1 -0
  10. package/dist/chunks/{LogContext-L7HzynFw.cjs → LogContext-BaMXleWj.cjs} +19 -4
  11. package/dist/chunks/LogContext-BaMXleWj.cjs.map +1 -0
  12. package/dist/chunks/LogContext-DZasm_P5.cjs +141 -0
  13. package/dist/chunks/LogContext-DZasm_P5.cjs.map +1 -0
  14. package/dist/chunks/{LogContext-Cwn-1Zzb.js → LogContext-DjlITOzZ.js} +19 -4
  15. package/dist/chunks/LogContext-DjlITOzZ.js.map +1 -0
  16. package/dist/chunks/LogContext-Dyzs61XG.js +136 -0
  17. package/dist/chunks/LogContext-Dyzs61XG.js.map +1 -0
  18. package/dist/chunks/SerializerBridge-Ba43Mk7j.js +393 -0
  19. package/dist/chunks/SerializerBridge-Ba43Mk7j.js.map +1 -0
  20. package/dist/chunks/SerializerBridge-C4a9Z37F.cjs +410 -0
  21. package/dist/chunks/SerializerBridge-C4a9Z37F.cjs.map +1 -0
  22. package/dist/chunks/{StyleManager-CIpd6wbO.cjs → StyleManager-DQ6UNRB-.cjs} +37 -11
  23. package/dist/chunks/StyleManager-DQ6UNRB-.cjs.map +1 -0
  24. package/dist/chunks/StyleManager-DjwAYbxE.js +113 -0
  25. package/dist/chunks/StyleManager-DjwAYbxE.js.map +1 -0
  26. package/dist/chunks/{core-PoT7RrTK.js → core-Blfi2klP.js} +1 -4
  27. package/dist/chunks/core-Blfi2klP.js.map +1 -0
  28. package/dist/chunks/{core-Dzz7agGa.cjs → core-CqS_UBzJ.cjs} +1 -4
  29. package/dist/chunks/core-CqS_UBzJ.cjs.map +1 -0
  30. package/dist/chunks/{environment-detector-CI3TrWK_.js → environment-detector-7NvnYUfr.js} +11 -11
  31. package/dist/chunks/environment-detector-7NvnYUfr.js.map +1 -0
  32. package/dist/chunks/{environment-detector-Cnn6wr6O.cjs → environment-detector-D-tHkKWA.cjs} +11 -11
  33. package/dist/chunks/environment-detector-D-tHkKWA.cjs.map +1 -0
  34. package/dist/chunks/server-fallback-CaCPjWby.cjs +119 -0
  35. package/dist/chunks/server-fallback-CaCPjWby.cjs.map +1 -0
  36. package/dist/chunks/server-fallback-jj0T6XaK.js +114 -0
  37. package/dist/chunks/server-fallback-jj0T6XaK.js.map +1 -0
  38. package/dist/chunks/{spinner-DNvxbM9a.cjs → spinner-BHyYEXsM.cjs} +319 -37
  39. package/dist/chunks/spinner-BHyYEXsM.cjs.map +1 -0
  40. package/dist/chunks/{spinner-D3FsF78o.js → spinner-BtkwpzYv.js} +319 -37
  41. package/dist/chunks/spinner-BtkwpzYv.js.map +1 -0
  42. package/dist/chunks/styling-CRw3KQW4.js +1592 -0
  43. package/dist/chunks/styling-CRw3KQW4.js.map +1 -0
  44. package/dist/chunks/styling-Cel2wPRy.cjs +1645 -0
  45. package/dist/chunks/styling-Cel2wPRy.cjs.map +1 -0
  46. package/dist/chunks/transports-BGfwwakw.js +1237 -0
  47. package/dist/chunks/transports-BGfwwakw.js.map +1 -0
  48. package/dist/chunks/transports-BZ-Mc2IT.cjs +1450 -0
  49. package/dist/chunks/transports-BZ-Mc2IT.cjs.map +1 -0
  50. package/dist/chunks/transports-DvaLAeGJ.js +1403 -0
  51. package/dist/chunks/transports-DvaLAeGJ.js.map +1 -0
  52. package/dist/chunks/transports-yK6CL0Ml.cjs +1278 -0
  53. package/dist/chunks/transports-yK6CL0Ml.cjs.map +1 -0
  54. package/dist/chunks/{utils-BqlFYocD.cjs → utils-W_cxqriN.cjs} +44 -37
  55. package/dist/chunks/utils-W_cxqriN.cjs.map +1 -0
  56. package/dist/chunks/{utils-VETbVpkR.js → utils-tKfBAWUM.js} +44 -37
  57. package/dist/chunks/utils-tKfBAWUM.js.map +1 -0
  58. package/dist/cli/CommandProcessor.d.ts +166 -19
  59. package/dist/cli/CommandProcessor.d.ts.map +1 -1
  60. package/dist/cli/commands/ConfigCommand.d.ts +36 -1
  61. package/dist/cli/commands/ConfigCommand.d.ts.map +1 -1
  62. package/dist/cli/commands/ExportCommand.d.ts +49 -3
  63. package/dist/cli/commands/ExportCommand.d.ts.map +1 -1
  64. package/dist/cli/commands/ThemeCommand.d.ts +62 -3
  65. package/dist/cli/commands/ThemeCommand.d.ts.map +1 -1
  66. package/dist/cli/help.d.ts +30 -2
  67. package/dist/cli/help.d.ts.map +1 -1
  68. package/dist/cli/index.d.ts +29 -2
  69. package/dist/cli/index.d.ts.map +1 -1
  70. package/dist/cli.cjs +2 -2
  71. package/dist/cli.js +2 -2
  72. package/dist/context/LogContext.d.ts +167 -70
  73. package/dist/context/LogContext.d.ts.map +1 -1
  74. package/dist/context.cjs +1 -1
  75. package/dist/context.js +1 -1
  76. package/dist/core.cjs +52 -29
  77. package/dist/core.cjs.map +1 -1
  78. package/dist/core.d.ts +50 -27
  79. package/dist/core.d.ts.map +1 -1
  80. package/dist/core.js +52 -29
  81. package/dist/core.js.map +1 -1
  82. package/dist/hooks/HookBridge.d.ts +15 -9
  83. package/dist/hooks/HookBridge.d.ts.map +1 -1
  84. package/dist/hooks/HookManager.d.ts +295 -2
  85. package/dist/hooks/HookManager.d.ts.map +1 -1
  86. package/dist/hooks.cjs +1 -1
  87. package/dist/hooks.js +1 -1
  88. package/dist/index.cjs +1415 -214
  89. package/dist/index.cjs.map +1 -1
  90. package/dist/index.d.ts +5 -1
  91. package/dist/index.d.ts.map +1 -1
  92. package/dist/index.js +1413 -215
  93. package/dist/index.js.map +1 -1
  94. package/dist/playground/TerminalBridge.d.ts +23 -16
  95. package/dist/playground/TerminalBridge.d.ts.map +1 -1
  96. package/dist/playground/box.d.ts +36 -5
  97. package/dist/playground/box.d.ts.map +1 -1
  98. package/dist/playground/cli-table.d.ts +41 -5
  99. package/dist/playground/cli-table.d.ts.map +1 -1
  100. package/dist/playground/divider.d.ts +22 -3
  101. package/dist/playground/divider.d.ts.map +1 -1
  102. package/dist/playground/header.d.ts +21 -4
  103. package/dist/playground/header.d.ts.map +1 -1
  104. package/dist/playground/server-fallback.d.ts +96 -9
  105. package/dist/playground/server-fallback.d.ts.map +1 -1
  106. package/dist/playground/spinner.d.ts +159 -10
  107. package/dist/playground/spinner.d.ts.map +1 -1
  108. package/dist/playground/step.d.ts +25 -6
  109. package/dist/playground/step.d.ts.map +1 -1
  110. package/dist/playground.cjs +1 -1
  111. package/dist/playground.js +1 -1
  112. package/dist/serializers/SerializerBridge.d.ts +13 -6
  113. package/dist/serializers/SerializerBridge.d.ts.map +1 -1
  114. package/dist/serializers/SerializerRegistry.d.ts +236 -0
  115. package/dist/serializers/SerializerRegistry.d.ts.map +1 -1
  116. package/dist/serializers.cjs +1 -1
  117. package/dist/serializers.js +1 -1
  118. package/dist/styles/StyleManager.d.ts +138 -31
  119. package/dist/styles/StyleManager.d.ts.map +1 -1
  120. package/dist/styles.cjs +2 -2
  121. package/dist/styles.js +2 -2
  122. package/dist/styling/SmartPresets.d.ts +100 -6
  123. package/dist/styling/SmartPresets.d.ts.map +1 -1
  124. package/dist/styling/StyleBuilder.d.ts +453 -32
  125. package/dist/styling/StyleBuilder.d.ts.map +1 -1
  126. package/dist/styling/banners.d.ts +83 -7
  127. package/dist/styling/banners.d.ts.map +1 -1
  128. package/dist/styling/themes.d.ts +41 -2
  129. package/dist/styling/themes.d.ts.map +1 -1
  130. package/dist/transports/ConsoleTransport.d.ts +95 -0
  131. package/dist/transports/ConsoleTransport.d.ts.map +1 -1
  132. package/dist/transports/FileTransport.d.ts +99 -18
  133. package/dist/transports/FileTransport.d.ts.map +1 -1
  134. package/dist/transports/HttpTransport.d.ts +135 -22
  135. package/dist/transports/HttpTransport.d.ts.map +1 -1
  136. package/dist/transports/OtlpTraceTransport.d.ts +118 -0
  137. package/dist/transports/OtlpTraceTransport.d.ts.map +1 -0
  138. package/dist/transports/OtlpTransport.d.ts +80 -37
  139. package/dist/transports/OtlpTransport.d.ts.map +1 -1
  140. package/dist/transports/SpanRuntime.d.ts +73 -0
  141. package/dist/transports/SpanRuntime.d.ts.map +1 -0
  142. package/dist/transports/TransportBridge.d.ts +19 -13
  143. package/dist/transports/TransportBridge.d.ts.map +1 -1
  144. package/dist/transports/TransportManager.d.ts +232 -10
  145. package/dist/transports/TransportManager.d.ts.map +1 -1
  146. package/dist/transports/index.d.ts +1 -0
  147. package/dist/transports/index.d.ts.map +1 -1
  148. package/dist/transports-module.d.ts +3 -1
  149. package/dist/transports-module.d.ts.map +1 -1
  150. package/dist/transports.cjs +2 -1
  151. package/dist/transports.js +2 -2
  152. package/dist/types/core.d.ts +155 -59
  153. package/dist/types/core.d.ts.map +1 -1
  154. package/dist/types/hooks.d.ts +92 -14
  155. package/dist/types/hooks.d.ts.map +1 -1
  156. package/dist/types/index.d.ts +1 -1
  157. package/dist/types/index.d.ts.map +1 -1
  158. package/dist/types/serializers.d.ts +72 -0
  159. package/dist/types/serializers.d.ts.map +1 -1
  160. package/dist/types/transports.d.ts +163 -39
  161. package/dist/types/transports.d.ts.map +1 -1
  162. package/dist/utils/ansi-colors.d.ts +15 -15
  163. package/dist/utils/asyncLocalStorage.d.ts +33 -0
  164. package/dist/utils/asyncLocalStorage.d.ts.map +1 -0
  165. package/dist/utils/environment-detector.d.ts +11 -11
  166. package/dist/utils/formatting.d.ts +8 -8
  167. package/dist/utils/output.d.ts +9 -9
  168. package/dist/utils/output.d.ts.map +1 -1
  169. package/dist/utils/stackTrace.d.ts +2 -2
  170. package/package.json +24 -25
  171. package/dist/chunks/HookBridge-CiRfR67f.cjs +0 -190
  172. package/dist/chunks/HookBridge-CiRfR67f.cjs.map +0 -1
  173. package/dist/chunks/HookBridge-SgMmbXZB.js +0 -173
  174. package/dist/chunks/HookBridge-SgMmbXZB.js.map +0 -1
  175. package/dist/chunks/LogContext-Cwn-1Zzb.js.map +0 -1
  176. package/dist/chunks/LogContext-L7HzynFw.cjs.map +0 -1
  177. package/dist/chunks/SerializerBridge-BaOQOb8q.cjs +0 -166
  178. package/dist/chunks/SerializerBridge-BaOQOb8q.cjs.map +0 -1
  179. package/dist/chunks/SerializerBridge-BkpQu5c9.js +0 -149
  180. package/dist/chunks/SerializerBridge-BkpQu5c9.js.map +0 -1
  181. package/dist/chunks/StyleManager-CIpd6wbO.cjs.map +0 -1
  182. package/dist/chunks/StyleManager-LCvVlVdx.js +0 -87
  183. package/dist/chunks/StyleManager-LCvVlVdx.js.map +0 -1
  184. package/dist/chunks/core-Dzz7agGa.cjs.map +0 -1
  185. package/dist/chunks/core-PoT7RrTK.js.map +0 -1
  186. package/dist/chunks/environment-detector-CI3TrWK_.js.map +0 -1
  187. package/dist/chunks/environment-detector-Cnn6wr6O.cjs.map +0 -1
  188. package/dist/chunks/server-fallback-BUKjdLS7.cjs +0 -42
  189. package/dist/chunks/server-fallback-BUKjdLS7.cjs.map +0 -1
  190. package/dist/chunks/server-fallback-BsuWH1Dk.js +0 -37
  191. package/dist/chunks/server-fallback-BsuWH1Dk.js.map +0 -1
  192. package/dist/chunks/spinner-D3FsF78o.js.map +0 -1
  193. package/dist/chunks/spinner-DNvxbM9a.cjs.map +0 -1
  194. package/dist/chunks/styling-84N8T97t.js +0 -941
  195. package/dist/chunks/styling-84N8T97t.js.map +0 -1
  196. package/dist/chunks/styling-Cg5saQ46.cjs +0 -994
  197. package/dist/chunks/styling-Cg5saQ46.cjs.map +0 -1
  198. package/dist/chunks/transports-BiDk345e.js +0 -715
  199. package/dist/chunks/transports-BiDk345e.js.map +0 -1
  200. package/dist/chunks/transports-COPWmlF1.cjs +0 -756
  201. package/dist/chunks/transports-COPWmlF1.cjs.map +0 -1
  202. package/dist/chunks/utils-BqlFYocD.cjs.map +0 -1
  203. package/dist/chunks/utils-VETbVpkR.js.map +0 -1
  204. package/dist/example.d.ts +0 -18
  205. package/dist/example.d.ts.map +0 -1
  206. package/dist/main.d.ts +0 -2
  207. package/dist/main.d.ts.map +0 -1
@@ -0,0 +1,487 @@
1
+ //#region src/hooks/HookManager.ts
2
+ /**
3
+ * Profundidad máxima de reentrancia permitida para el evento `onError`.
4
+ *
5
+ * Cuando un hook registrado para `onError` lanza una excepción, el manager
6
+ * re-emite el error como un nuevo evento `onError`, lo que podría entrar en
7
+ * un bucle infinito. Este límite corta la cadena tras N niveles de anidamiento
8
+ * y deja caer el entry al console para no acaparar el event loop.
9
+ *
10
+ * @internal Constantante de implementación; no forma parte de la API pública.
11
+ */
12
+ const MAX_ONERROR_DEPTH = 5;
13
+ /**
14
+ * Genera un identificador corto y único para cada registro de hook/middleware.
15
+ *
16
+ * Combina timestamp con un segmento aleatorio base36. Suficiente para
17
+ * desambiguar registros dentro de un mismo proceso; no es un UUID criptográfico.
18
+ *
19
+ * @internal Helper interno; no es API pública.
20
+ * @returns {string} ID tipo `<epoch>-<random9>`.
21
+ */
22
+ function generateId() {
23
+ return `${Date.now()}-${Math.random().toString(36).slice(2, 11)}`;
24
+ }
25
+ /**
26
+ * Implementación por defecto de {@link IHookManager} que orquesta el ciclo de
27
+ * vida de los hooks del logger: `beforeLog`, `afterLog` y `onError`.
28
+ *
29
+ * Permite:
30
+ * - Registrar callbacks por evento con **prioridad** (mayor número = se
31
+ * ejecuta primero; las registraciones se ordenan descendentemente).
32
+ * - Encadenar middlewares sobre el entry previo al dispatch.
33
+ * - Propagar mutaciones: cada hook puede retornar un partial de
34
+ * {@link HookLogEntry} que se mergea al entry actual antes del siguiente hook.
35
+ * - Acotar la recursión de `onError` con un guard de profundidad
36
+ * ({@link MAX_ONERROR_DEPTH}) para que un hook que lanza no loopée para
37
+ * siempre.
38
+ *
39
+ * El flujo típico lo orquesta el logger vía {@link process} (emite `beforeLog`
40
+ * + ejecuta middlewares) y {@link afterProcess} (emite `afterLog`).
41
+ *
42
+ * @example
43
+ * ```ts
44
+ * const hooks = new HookManager();
45
+ *
46
+ * // Mayor prioridad => corre primero
47
+ * hooks.on('beforeLog', (entry) => {
48
+ * return { ...entry, attributes: { ...entry.attributes, traced: true } };
49
+ * }, 90);
50
+ *
51
+ * hooks.on('afterLog', (entry) => {
52
+ * metrics.increment('log_emitted', { level: entry.level });
53
+ * });
54
+ *
55
+ * hooks.on('onError', (entry) => {
56
+ * telemetry.capture(entry.error);
57
+ * });
58
+ * ```
59
+ *
60
+ * @example
61
+ * ```ts
62
+ * // Middleware que añade timestamp si falta y delega al siguiente
63
+ * hooks.use(async (entry, next) => {
64
+ * if (!entry.time) entry.time = Date.now();
65
+ * await next();
66
+ * });
67
+ * ```
68
+ *
69
+ * @see {@link IHookManager}
70
+ * @see {@link HookEvent}
71
+ */
72
+ var HookManager = class {
73
+ hooks = /* @__PURE__ */ new Map();
74
+ middlewares = [];
75
+ /**
76
+ * Lleva el conteo de la profundidad de recursión actual por cada evento de
77
+ * hook, para que las cadenas de `onError` que lanzan excepciones no puedan
78
+ * loopéar para siempre. Se resetea cuando el `emit` externo retorna.
79
+ */
80
+ _onErrorDepth = /* @__PURE__ */ new Map();
81
+ constructor() {
82
+ this.hooks.set("beforeLog", []);
83
+ this.hooks.set("afterLog", []);
84
+ this.hooks.set("onError", []);
85
+ }
86
+ /**
87
+ * Registra un callback persistente para un evento. Se ejecuta en cada
88
+ * emisión hasta que se cancele con la función devuelta o con {@link off}.
89
+ *
90
+ * Los hooks se ordenan por `priority` **descendente**: mayor número =
91
+ * se ejecuta primero. Misma prioridad respeta el orden de registro.
92
+ *
93
+ * @param event - Uno de `'beforeLog' | 'afterLog' | 'onError'`.
94
+ * @param callback - Función asíncrona que recibe el {@link HookLogEntry}
95
+ * actual y puede retornar un partial para mutar el entry que verán los
96
+ * hooks siguientes.
97
+ * @param priority - Peso de ordenamiento. Default `50`. Mayor = primero.
98
+ * @returns {() => void} Función de cancelación; llamarla desregistra el hook.
99
+ *
100
+ * @example
101
+ * ```ts
102
+ * const off = hooks.on('beforeLog', async (entry) => {
103
+ * return { attributes: { ...entry.attributes, requestId: getReqId() } };
104
+ * }, 80);
105
+ *
106
+ * // ...en shutdown:
107
+ * off();
108
+ * ```
109
+ *
110
+ * @see {@link once} para hooks de un solo disparo.
111
+ * @see {@link off} para desregistro por referencia de callback.
112
+ */
113
+ on(event, callback, priority = 50) {
114
+ const registration = {
115
+ id: generateId(),
116
+ event,
117
+ callback,
118
+ priority,
119
+ once: false
120
+ };
121
+ const hooks = this.hooks.get(event);
122
+ hooks.push(registration);
123
+ hooks.sort((a, b) => b.priority - a.priority);
124
+ return () => this.removeHook(event, registration.id);
125
+ }
126
+ /**
127
+ * Igual que {@link on}, pero el hook se auto-desregistra después del primer
128
+ * disparo exitoso. Útil para setup one-shot (warm-up de caché, captura del
129
+ * primer log, ...).
130
+ *
131
+ * Si el callback lanza, el hook NO se elimina (la excepción se deriva a
132
+ * `onError`); la limpieza solo ocurre cuando el callback retorna sin Throw.
133
+ *
134
+ * @param event - Evento a escuchar.
135
+ * @param callback - Handler que se ejecutará una sola vez.
136
+ * @param priority - Peso de ordenamiento (mayor = primero). Default `50`.
137
+ * @returns {() => void} Cancelación manual por si se quiere retirar antes
138
+ * del primer disparo.
139
+ *
140
+ * @example
141
+ * ```ts
142
+ * hooks.once('afterLog', async (entry) => {
143
+ * console.log('Primer log emitido:', entry.msg);
144
+ * });
145
+ * ```
146
+ *
147
+ * @see {@link on}
148
+ */
149
+ once(event, callback, priority = 50) {
150
+ const registration = {
151
+ id: generateId(),
152
+ event,
153
+ callback,
154
+ priority,
155
+ once: true
156
+ };
157
+ const hooks = this.hooks.get(event);
158
+ hooks.push(registration);
159
+ hooks.sort((a, b) => b.priority - a.priority);
160
+ return () => this.removeHook(event, registration.id);
161
+ }
162
+ /**
163
+ * Desregistra un hook por referencia de callback. Solo elimina la primera
164
+ * coincidencia encontrada para el evento.
165
+ *
166
+ * Para hooks registrados con {@link on} o {@link once} es preferible usar
167
+ * la función de cancelación devuelta (que usa el id interno y es O(n) más
168
+ * directa). `off` es útil cuando se perdió la referencia al cancelador o
169
+ * cuando se integra con APIs que piden un método `removeListener(cb)`.
170
+ *
171
+ * @param event - Evento del que se quiere desregistrar.
172
+ * @param callback - Misma referencia de función pasada a {@link on}/{@link once}.
173
+ * @returns {boolean} `true` si se eliminó un hook, `false` si no había match.
174
+ *
175
+ * @example
176
+ * ```ts
177
+ * const handler = async (entry) => { /* ... *\/ };
178
+ * hooks.on('afterLog', handler);
179
+ * hooks.off('afterLog', handler); // true
180
+ * ```
181
+ *
182
+ * @see {@link on}
183
+ */
184
+ off(event, callback) {
185
+ const hooks = this.hooks.get(event);
186
+ if (!hooks) return false;
187
+ const index = hooks.findIndex((h) => h.callback === callback);
188
+ if (index >= 0) {
189
+ hooks.splice(index, 1);
190
+ return true;
191
+ }
192
+ return false;
193
+ }
194
+ removeHook(event, id) {
195
+ const hooks = this.hooks.get(event);
196
+ if (hooks) {
197
+ const index = hooks.findIndex((h) => h.id === id);
198
+ if (index >= 0) hooks.splice(index, 1);
199
+ }
200
+ }
201
+ /**
202
+ * Registra un middleware que se encadena sobre el {@link HookLogEntry}
203
+ * **antes** de que se emita el log al resto del pipeline. Los middlewares
204
+ * corren después del evento `beforeLog` y se ejecutan en cascada vía `next()`.
205
+ *
206
+ * Cada middleware recibe `(entry, next)` y debe llamar a `next()` para
207
+ * ceder el control al siguiente. Si NO llama a `next()`, corta la cadena
208
+ * (patrón short-circuit).
209
+ *
210
+ * Ordenamiento: `priority` descendente (mayor = primero), igual que los
211
+ * hooks. La secuencia respetada es la del sort, no la del orden de
212
+ * llamada a `use`.
213
+ *
214
+ * @param middleware - Función `(entry, next) => Promise<void>`.
215
+ * @param priority - Peso de ordenamiento. Default `50`.
216
+ * @returns {() => void} Función de cancelación para retirar el middleware.
217
+ *
218
+ * @example
219
+ * ```ts
220
+ * // PII redaction: enmascara passwords antes de loguear
221
+ * hooks.use(async (entry, next) => {
222
+ * if (entry.attributes?.password) {
223
+ * entry.attributes.password = '***';
224
+ * }
225
+ * await next();
226
+ * }, 90);
227
+ *
228
+ * // Short-circuit: si es level=debug y env=prod, no baja
229
+ * const stop = hooks.use(async (entry, next) => {
230
+ * if (entry.level === 'debug' && ENV === 'production') return;
231
+ * await next();
232
+ * }, 100);
233
+ * ```
234
+ *
235
+ * @see {@link process} para el orquestador que ejecuta la cadena.
236
+ */
237
+ use(middleware, priority = 50) {
238
+ const registration = {
239
+ id: generateId(),
240
+ fn: middleware,
241
+ priority
242
+ };
243
+ this.middlewares.push(registration);
244
+ this.middlewares.sort((a, b) => b.priority - a.priority);
245
+ return () => {
246
+ const index = this.middlewares.findIndex((m) => m.id === registration.id);
247
+ if (index >= 0) this.middlewares.splice(index, 1);
248
+ };
249
+ }
250
+ /**
251
+ * Emite un evento a todos sus hooks registrados, en orden de prioridad, y
252
+ * retorna el entry resultante tras aplicar todos los mutations.
253
+ *
254
+ * Cada hook puede retornar un partial de {@link HookLogEntry}; ese partial
255
+ * se mergea sobre el entry actual antes de invocar al siguiente hook, así
256
+ * los hooks se encadenan como una pipeline de transformaciones.
257
+ *
258
+ * Manejo de errores:
259
+ * - Si un hook de `beforeLog`/`afterLog` lanza, la excepción se captura y
260
+ * se re-emite como evento `onError` con el campo `error` poblado.
261
+ * - Si un hook de `onError` lanza, **NO** se re-emite (rompería el ciclo);
262
+ * se loguea a `console.error` y se traga.
263
+ * - Guard de reentrancia: si `onError` se re-entra más de
264
+ * {@link MAX_ONERROR_DEPTH} veces, se corta y se loguea el entry al
265
+ * console. Previene loops infinitos por errores en cascada.
266
+ *
267
+ * Los hooks marcados como `once` se eliminan tras un disparo exitoso.
268
+ *
269
+ * @param event - Evento a emitir.
270
+ * @param entry - Entry inicial; no se muta in-place (se clona por nivel).
271
+ * @returns {Promise<HookLogEntry>} Entry transformado tras todos los hooks.
272
+ *
273
+ * @example
274
+ * ```ts
275
+ * const enriched = await hooks.emit('beforeLog', rawEntry);
276
+ * // enriched.attributes puede traer merges de varios hooks
277
+ * ```
278
+ *
279
+ * @see {@link on}
280
+ * @see {@link once}
281
+ * @see {@link MAX_ONERROR_DEPTH}
282
+ */
283
+ async emit(event, entry) {
284
+ const depth = this._onErrorDepth.get(entry.hookEvent ?? event) ?? 0;
285
+ if (event === "onError" && depth >= MAX_ONERROR_DEPTH) {
286
+ console.error("HookManager: onError recursion limit reached, dropping entry:", entry);
287
+ return entry;
288
+ }
289
+ this._onErrorDepth.set(event, depth + 1);
290
+ try {
291
+ const hooks = this.hooks.get(event) || [];
292
+ let currentEntry = { ...entry };
293
+ const toRemove = [];
294
+ for (const hook of hooks) try {
295
+ const result = await hook.callback(currentEntry);
296
+ if (result) currentEntry = {
297
+ ...currentEntry,
298
+ ...result
299
+ };
300
+ if (hook.once) toRemove.push(hook.id);
301
+ } catch (error) {
302
+ if (event !== "onError") await this.emit("onError", {
303
+ ...currentEntry,
304
+ error: error instanceof Error ? error : new Error(String(error)),
305
+ hookEvent: event
306
+ });
307
+ else console.error("HookManager: onError hook threw, swallowed to break recursion:", error);
308
+ }
309
+ toRemove.forEach((id) => this.removeHook(event, id));
310
+ return currentEntry;
311
+ } finally {
312
+ this._onErrorDepth.set(event, depth);
313
+ }
314
+ }
315
+ /**
316
+ * Orquesta el pipeline pre-emit de un log. Orden de ejecución:
317
+ *
318
+ * 1. Emite `beforeLog` (todos sus hooks corren en orden de prioridad y
319
+ * pueden mutar el entry vía return partial).
320
+ * 2. Si hay middlewares registrados ({@link use}), los ejecuta en cascada
321
+ * sobre el entry ya enriquecido por los hooks de `beforeLog`.
322
+ *
323
+ * Es el punto de entrada que el logger invoca **antes** de despachar el
324
+ * record a los transports. El entry devuelto es el que finalmente se loguea.
325
+ *
326
+ * No emite `afterLog`; para eso usar {@link afterProcess} una vez que el
327
+ * dispatch al transport haya terminado.
328
+ *
329
+ * @param entry - Entry crudo entrante al pipeline.
330
+ * @returns {Promise<HookLogEntry>} Entry final listo para mandar a transports.
331
+ *
332
+ * @example
333
+ * ```ts
334
+ * const processed = await hooks.process(rawEntry);
335
+ * await transport.write(processed);
336
+ * await hooks.afterProcess(processed);
337
+ * ```
338
+ *
339
+ * @see {@link afterProcess}
340
+ * @see {@link use}
341
+ */
342
+ async process(entry) {
343
+ let currentEntry = { ...entry };
344
+ currentEntry = await this.emit("beforeLog", currentEntry);
345
+ if (this.middlewares.length > 0) {
346
+ let index = 0;
347
+ const executeNext = async () => {
348
+ if (index < this.middlewares.length) {
349
+ const middleware = this.middlewares[index++];
350
+ if (middleware) await middleware.fn(currentEntry, executeNext);
351
+ }
352
+ };
353
+ await executeNext();
354
+ }
355
+ return currentEntry;
356
+ }
357
+ /**
358
+ * Emite el evento `afterLog` con el entry ya procesado y dispatcheado a
359
+ * los transports. Pensado para side-effects post-log: métricas, audit
360
+ * trail, flush de buffers externos, etc.
361
+ *
362
+ * Los hooks de `afterLog` pueden retornar un partial pero el entry ya se
363
+ * ha publicado, así que la mutación no tiene efecto sobre el log emitido;
364
+ * solo queda disponible en el return de {@link emit}, que aquí se descarta.
365
+ *
366
+ * @param entry - Entry final (mismo objeto devuelto por {@link process}).
367
+ * @returns {Promise<void>} Resuelve cuando todos los hooks terminaron.
368
+ *
369
+ * @example
370
+ * ```ts
371
+ * hooks.on('afterLog', async (entry) => {
372
+ * metrics.increment('logs_total', { level: entry.level });
373
+ * });
374
+ *
375
+ * const processed = await hooks.process(rawEntry);
376
+ * await transport.write(processed);
377
+ * await hooks.afterProcess(processed); // dispara métricas
378
+ * ```
379
+ *
380
+ * @see {@link process}
381
+ */
382
+ async afterProcess(entry) {
383
+ await this.emit("afterLog", entry);
384
+ }
385
+ /**
386
+ * Vacía todos los hooks y middlewares registrados. Útil para resetear el
387
+ * estado entre tests o en hot-reload.
388
+ *
389
+ * Afecta a los tres eventos (`beforeLog`, `afterLog`, `onError`) y a la
390
+ * pila de middlewares. Las funciones de cancelación devueltas por
391
+ * {@link on}/{@link once}/{@link use} se vuelven no-ops pero pueden
392
+ * llamarse sin error.
393
+ *
394
+ * @example
395
+ * ```ts
396
+ * afterEach(() => hooks.clear());
397
+ * ```
398
+ */
399
+ clear() {
400
+ this.hooks.forEach((hooks) => hooks.length = 0);
401
+ this.middlewares.length = 0;
402
+ }
403
+ /**
404
+ * Snapshot del estado interno para observabilidad y debugging.
405
+ *
406
+ * @returns {Object} stats
407
+ * @returns {Record<HookEvent, number>} stats.hooks - Conteo de hooks
408
+ * registrados por evento (`beforeLog`, `afterLog`, `onError`).
409
+ * @returns {number} stats.middlewares - Total de middlewares activos.
410
+ *
411
+ * @example
412
+ * ```ts
413
+ * const stats = hooks.getStats();
414
+ * // { hooks: { beforeLog: 2, afterLog: 1, onError: 3 }, middlewares: 1 }
415
+ * if (stats.hooks.onError === 0) {
416
+ * console.warn('Sin handlers onError registrados');
417
+ * }
418
+ * ```
419
+ */
420
+ getStats() {
421
+ return {
422
+ hooks: {
423
+ beforeLog: this.hooks.get("beforeLog")?.length || 0,
424
+ afterLog: this.hooks.get("afterLog")?.length || 0,
425
+ onError: this.hooks.get("onError")?.length || 0
426
+ },
427
+ middlewares: this.middlewares.length
428
+ };
429
+ }
430
+ };
431
+ let _defaultHookManager = null;
432
+ /**
433
+ * Devuelve la instancia singleton de {@link HookManager} compartida por todo
434
+ * el proceso. La crea perezosamente en la primera llamada.
435
+ *
436
+ * Pensada como hook manager por defecto del logger; los consumidores que
437
+ * necesiten aislamiento (tests, múltiples loggers con pipelines distintos)
438
+ * deben instanciar su propio `new HookManager()` en lugar de usar este shared.
439
+ *
440
+ * @returns {HookManager} La instancia singleton.
441
+ *
442
+ * @example
443
+ * ```ts
444
+ * import { getDefaultHookManager } from '@mks2508/better-logger/hooks';
445
+ *
446
+ * getDefaultHookManager().on('onError', async (entry) => {
447
+ * telemetry.capture(entry.error);
448
+ * });
449
+ * ```
450
+ *
451
+ * @see {@link HookManager}
452
+ */
453
+ function getDefaultHookManager() {
454
+ if (!_defaultHookManager) _defaultHookManager = new HookManager();
455
+ return _defaultHookManager;
456
+ }
457
+ //#endregion
458
+ //#region src/hooks/HookBridge.ts
459
+ /**
460
+ * Crea una instancia de {@link HookBridge}.
461
+ *
462
+ * @internal
463
+ */
464
+ function createHookBridge() {
465
+ const hookManager = new HookManager();
466
+ return {
467
+ on(event, callback, priority) {
468
+ return hookManager.on(event, callback, priority);
469
+ },
470
+ once(event, callback, priority) {
471
+ return hookManager.once(event, callback, priority);
472
+ },
473
+ off(event, callback) {
474
+ return hookManager.off(event, callback);
475
+ },
476
+ use(middleware, priority) {
477
+ return hookManager.use(middleware, priority);
478
+ },
479
+ getHookManager() {
480
+ return hookManager;
481
+ }
482
+ };
483
+ }
484
+ //#endregion
485
+ export { HookManager as n, getDefaultHookManager as r, createHookBridge as t };
486
+
487
+ //# sourceMappingURL=HookBridge-CI2PH79S.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"HookBridge-CI2PH79S.js","names":[],"sources":["../../src/hooks/HookManager.ts","../../src/hooks/HookBridge.ts"],"sourcesContent":["import type {\n HookLogEntry,\n HookEvent,\n HookCallback,\n MiddlewareFn,\n HookRegistration,\n MiddlewareRegistration,\n IHookManager\n} from '../types/index.js';\n\n/**\n * Profundidad máxima de reentrancia permitida para el evento `onError`.\n *\n * Cuando un hook registrado para `onError` lanza una excepción, el manager\n * re-emite el error como un nuevo evento `onError`, lo que podría entrar en\n * un bucle infinito. Este límite corta la cadena tras N niveles de anidamiento\n * y deja caer el entry al console para no acaparar el event loop.\n *\n * @internal Constantante de implementación; no forma parte de la API pública.\n */\nconst MAX_ONERROR_DEPTH = 5;\n\n/**\n * Genera un identificador corto y único para cada registro de hook/middleware.\n *\n * Combina timestamp con un segmento aleatorio base36. Suficiente para\n * desambiguar registros dentro de un mismo proceso; no es un UUID criptográfico.\n *\n * @internal Helper interno; no es API pública.\n * @returns {string} ID tipo `<epoch>-<random9>`.\n */\nfunction generateId(): string {\n return `${Date.now()}-${Math.random().toString(36).slice(2, 11)}`;\n}\n\n/**\n * Implementación por defecto de {@link IHookManager} que orquesta el ciclo de\n * vida de los hooks del logger: `beforeLog`, `afterLog` y `onError`.\n *\n * Permite:\n * - Registrar callbacks por evento con **prioridad** (mayor número = se\n * ejecuta primero; las registraciones se ordenan descendentemente).\n * - Encadenar middlewares sobre el entry previo al dispatch.\n * - Propagar mutaciones: cada hook puede retornar un partial de\n * {@link HookLogEntry} que se mergea al entry actual antes del siguiente hook.\n * - Acotar la recursión de `onError` con un guard de profundidad\n * ({@link MAX_ONERROR_DEPTH}) para que un hook que lanza no loopée para\n * siempre.\n *\n * El flujo típico lo orquesta el logger vía {@link process} (emite `beforeLog`\n * + ejecuta middlewares) y {@link afterProcess} (emite `afterLog`).\n *\n * @example\n * ```ts\n * const hooks = new HookManager();\n *\n * // Mayor prioridad => corre primero\n * hooks.on('beforeLog', (entry) => {\n * return { ...entry, attributes: { ...entry.attributes, traced: true } };\n * }, 90);\n *\n * hooks.on('afterLog', (entry) => {\n * metrics.increment('log_emitted', { level: entry.level });\n * });\n *\n * hooks.on('onError', (entry) => {\n * telemetry.capture(entry.error);\n * });\n * ```\n *\n * @example\n * ```ts\n * // Middleware que añade timestamp si falta y delega al siguiente\n * hooks.use(async (entry, next) => {\n * if (!entry.time) entry.time = Date.now();\n * await next();\n * });\n * ```\n *\n * @see {@link IHookManager}\n * @see {@link HookEvent}\n */\nexport class HookManager implements IHookManager {\n private hooks: Map<HookEvent, HookRegistration[]> = new Map();\n private middlewares: MiddlewareRegistration[] = [];\n /**\n * Lleva el conteo de la profundidad de recursión actual por cada evento de\n * hook, para que las cadenas de `onError` que lanzan excepciones no puedan\n * loopéar para siempre. Se resetea cuando el `emit` externo retorna.\n */\n private _onErrorDepth: Map<HookEvent, number> = new Map();\n\n constructor() {\n this.hooks.set('beforeLog', []);\n this.hooks.set('afterLog', []);\n this.hooks.set('onError', []);\n }\n\n /**\n * Registra un callback persistente para un evento. Se ejecuta en cada\n * emisión hasta que se cancele con la función devuelta o con {@link off}.\n *\n * Los hooks se ordenan por `priority` **descendente**: mayor número =\n * se ejecuta primero. Misma prioridad respeta el orden de registro.\n *\n * @param event - Uno de `'beforeLog' | 'afterLog' | 'onError'`.\n * @param callback - Función asíncrona que recibe el {@link HookLogEntry}\n * actual y puede retornar un partial para mutar el entry que verán los\n * hooks siguientes.\n * @param priority - Peso de ordenamiento. Default `50`. Mayor = primero.\n * @returns {() => void} Función de cancelación; llamarla desregistra el hook.\n *\n * @example\n * ```ts\n * const off = hooks.on('beforeLog', async (entry) => {\n * return { attributes: { ...entry.attributes, requestId: getReqId() } };\n * }, 80);\n *\n * // ...en shutdown:\n * off();\n * ```\n *\n * @see {@link once} para hooks de un solo disparo.\n * @see {@link off} para desregistro por referencia de callback.\n */\n on(event: HookEvent, callback: HookCallback, priority: number = 50): () => void {\n const registration: HookRegistration = {\n id: generateId(),\n event,\n callback,\n priority,\n once: false\n };\n\n const hooks = this.hooks.get(event)!;\n hooks.push(registration);\n hooks.sort((a, b) => b.priority - a.priority);\n\n return () => this.removeHook(event, registration.id);\n }\n\n /**\n * Igual que {@link on}, pero el hook se auto-desregistra después del primer\n * disparo exitoso. Útil para setup one-shot (warm-up de caché, captura del\n * primer log, ...).\n *\n * Si el callback lanza, el hook NO se elimina (la excepción se deriva a\n * `onError`); la limpieza solo ocurre cuando el callback retorna sin Throw.\n *\n * @param event - Evento a escuchar.\n * @param callback - Handler que se ejecutará una sola vez.\n * @param priority - Peso de ordenamiento (mayor = primero). Default `50`.\n * @returns {() => void} Cancelación manual por si se quiere retirar antes\n * del primer disparo.\n *\n * @example\n * ```ts\n * hooks.once('afterLog', async (entry) => {\n * console.log('Primer log emitido:', entry.msg);\n * });\n * ```\n *\n * @see {@link on}\n */\n once(event: HookEvent, callback: HookCallback, priority: number = 50): () => void {\n const registration: HookRegistration = {\n id: generateId(),\n event,\n callback,\n priority,\n once: true\n };\n\n const hooks = this.hooks.get(event)!;\n hooks.push(registration);\n hooks.sort((a, b) => b.priority - a.priority);\n\n return () => this.removeHook(event, registration.id);\n }\n\n /**\n * Desregistra un hook por referencia de callback. Solo elimina la primera\n * coincidencia encontrada para el evento.\n *\n * Para hooks registrados con {@link on} o {@link once} es preferible usar\n * la función de cancelación devuelta (que usa el id interno y es O(n) más\n * directa). `off` es útil cuando se perdió la referencia al cancelador o\n * cuando se integra con APIs que piden un método `removeListener(cb)`.\n *\n * @param event - Evento del que se quiere desregistrar.\n * @param callback - Misma referencia de función pasada a {@link on}/{@link once}.\n * @returns {boolean} `true` si se eliminó un hook, `false` si no había match.\n *\n * @example\n * ```ts\n * const handler = async (entry) => { /* ... *\\/ };\n * hooks.on('afterLog', handler);\n * hooks.off('afterLog', handler); // true\n * ```\n *\n * @see {@link on}\n */\n off(event: HookEvent, callback: HookCallback): boolean {\n const hooks = this.hooks.get(event);\n if (!hooks) return false;\n\n const index = hooks.findIndex(h => h.callback === callback);\n if (index >= 0) {\n hooks.splice(index, 1);\n return true;\n }\n return false;\n }\n\n private removeHook(event: HookEvent, id: string): void {\n const hooks = this.hooks.get(event);\n if (hooks) {\n const index = hooks.findIndex(h => h.id === id);\n if (index >= 0) {\n hooks.splice(index, 1);\n }\n }\n }\n\n /**\n * Registra un middleware que se encadena sobre el {@link HookLogEntry}\n * **antes** de que se emita el log al resto del pipeline. Los middlewares\n * corren después del evento `beforeLog` y se ejecutan en cascada vía `next()`.\n *\n * Cada middleware recibe `(entry, next)` y debe llamar a `next()` para\n * ceder el control al siguiente. Si NO llama a `next()`, corta la cadena\n * (patrón short-circuit).\n *\n * Ordenamiento: `priority` descendente (mayor = primero), igual que los\n * hooks. La secuencia respetada es la del sort, no la del orden de\n * llamada a `use`.\n *\n * @param middleware - Función `(entry, next) => Promise<void>`.\n * @param priority - Peso de ordenamiento. Default `50`.\n * @returns {() => void} Función de cancelación para retirar el middleware.\n *\n * @example\n * ```ts\n * // PII redaction: enmascara passwords antes de loguear\n * hooks.use(async (entry, next) => {\n * if (entry.attributes?.password) {\n * entry.attributes.password = '***';\n * }\n * await next();\n * }, 90);\n *\n * // Short-circuit: si es level=debug y env=prod, no baja\n * const stop = hooks.use(async (entry, next) => {\n * if (entry.level === 'debug' && ENV === 'production') return;\n * await next();\n * }, 100);\n * ```\n *\n * @see {@link process} para el orquestador que ejecuta la cadena.\n */\n use(middleware: MiddlewareFn, priority: number = 50): () => void {\n const registration: MiddlewareRegistration = {\n id: generateId(),\n fn: middleware,\n priority\n };\n\n this.middlewares.push(registration);\n this.middlewares.sort((a, b) => b.priority - a.priority);\n\n return () => {\n const index = this.middlewares.findIndex(m => m.id === registration.id);\n if (index >= 0) {\n this.middlewares.splice(index, 1);\n }\n };\n }\n\n /**\n * Emite un evento a todos sus hooks registrados, en orden de prioridad, y\n * retorna el entry resultante tras aplicar todos los mutations.\n *\n * Cada hook puede retornar un partial de {@link HookLogEntry}; ese partial\n * se mergea sobre el entry actual antes de invocar al siguiente hook, así\n * los hooks se encadenan como una pipeline de transformaciones.\n *\n * Manejo de errores:\n * - Si un hook de `beforeLog`/`afterLog` lanza, la excepción se captura y\n * se re-emite como evento `onError` con el campo `error` poblado.\n * - Si un hook de `onError` lanza, **NO** se re-emite (rompería el ciclo);\n * se loguea a `console.error` y se traga.\n * - Guard de reentrancia: si `onError` se re-entra más de\n * {@link MAX_ONERROR_DEPTH} veces, se corta y se loguea el entry al\n * console. Previene loops infinitos por errores en cascada.\n *\n * Los hooks marcados como `once` se eliminan tras un disparo exitoso.\n *\n * @param event - Evento a emitir.\n * @param entry - Entry inicial; no se muta in-place (se clona por nivel).\n * @returns {Promise<HookLogEntry>} Entry transformado tras todos los hooks.\n *\n * @example\n * ```ts\n * const enriched = await hooks.emit('beforeLog', rawEntry);\n * // enriched.attributes puede traer merges de varios hooks\n * ```\n *\n * @see {@link on}\n * @see {@link once}\n * @see {@link MAX_ONERROR_DEPTH}\n */\n async emit(event: HookEvent, entry: HookLogEntry): Promise<HookLogEntry> {\n // Guard de reentrancia. Sin esto, un hook de onError que lance\n // re-entraría a emit('onError', ...) y loopearía para siempre o\n // recursaría hasta hacer volar el stack. Permitimos un burst pequeño\n // (MAX_ONERROR_DEPTH) para que el fan-out legítimo siga funcionando,\n // pero cortamos el ciclo.\n const depth = (this._onErrorDepth.get(entry.hookEvent ?? event) ?? 0);\n if (event === 'onError' && depth >= MAX_ONERROR_DEPTH) {\n // eslint-disable-next-line no-console\n console.error('HookManager: onError recursion limit reached, dropping entry:', entry);\n return entry;\n }\n this._onErrorDepth.set(event, depth + 1);\n\n try {\n const hooks = this.hooks.get(event) || [];\n let currentEntry = { ...entry };\n const toRemove: string[] = [];\n\n for (const hook of hooks) {\n try {\n const result = await hook.callback(currentEntry);\n if (result) {\n currentEntry = { ...currentEntry, ...result };\n }\n if (hook.once) {\n toRemove.push(hook.id);\n }\n } catch (error) {\n if (event !== 'onError') {\n await this.emit('onError', {\n ...currentEntry,\n error: error instanceof Error ? error : new Error(String(error)),\n hookEvent: event\n });\n } else {\n // Un hook de onError lanzó — se loguea al console (single shot)\n // sin emitir otro onError (lo que recursaría).\n // eslint-disable-next-line no-console\n console.error('HookManager: onError hook threw, swallowed to break recursion:', error);\n }\n }\n }\n\n toRemove.forEach(id => this.removeHook(event, id));\n return currentEntry;\n } finally {\n this._onErrorDepth.set(event, depth);\n }\n }\n\n /**\n * Orquesta el pipeline pre-emit de un log. Orden de ejecución:\n *\n * 1. Emite `beforeLog` (todos sus hooks corren en orden de prioridad y\n * pueden mutar el entry vía return partial).\n * 2. Si hay middlewares registrados ({@link use}), los ejecuta en cascada\n * sobre el entry ya enriquecido por los hooks de `beforeLog`.\n *\n * Es el punto de entrada que el logger invoca **antes** de despachar el\n * record a los transports. El entry devuelto es el que finalmente se loguea.\n *\n * No emite `afterLog`; para eso usar {@link afterProcess} una vez que el\n * dispatch al transport haya terminado.\n *\n * @param entry - Entry crudo entrante al pipeline.\n * @returns {Promise<HookLogEntry>} Entry final listo para mandar a transports.\n *\n * @example\n * ```ts\n * const processed = await hooks.process(rawEntry);\n * await transport.write(processed);\n * await hooks.afterProcess(processed);\n * ```\n *\n * @see {@link afterProcess}\n * @see {@link use}\n */\n async process(entry: HookLogEntry): Promise<HookLogEntry> {\n let currentEntry = { ...entry };\n\n currentEntry = await this.emit('beforeLog', currentEntry);\n\n if (this.middlewares.length > 0) {\n let index = 0;\n\n const executeNext = async (): Promise<void> => {\n if (index < this.middlewares.length) {\n const middleware = this.middlewares[index++];\n if (middleware) {\n await middleware.fn(currentEntry, executeNext);\n }\n }\n };\n\n await executeNext();\n }\n\n return currentEntry;\n }\n\n /**\n * Emite el evento `afterLog` con el entry ya procesado y dispatcheado a\n * los transports. Pensado para side-effects post-log: métricas, audit\n * trail, flush de buffers externos, etc.\n *\n * Los hooks de `afterLog` pueden retornar un partial pero el entry ya se\n * ha publicado, así que la mutación no tiene efecto sobre el log emitido;\n * solo queda disponible en el return de {@link emit}, que aquí se descarta.\n *\n * @param entry - Entry final (mismo objeto devuelto por {@link process}).\n * @returns {Promise<void>} Resuelve cuando todos los hooks terminaron.\n *\n * @example\n * ```ts\n * hooks.on('afterLog', async (entry) => {\n * metrics.increment('logs_total', { level: entry.level });\n * });\n *\n * const processed = await hooks.process(rawEntry);\n * await transport.write(processed);\n * await hooks.afterProcess(processed); // dispara métricas\n * ```\n *\n * @see {@link process}\n */\n async afterProcess(entry: HookLogEntry): Promise<void> {\n await this.emit('afterLog', entry);\n }\n\n /**\n * Vacía todos los hooks y middlewares registrados. Útil para resetear el\n * estado entre tests o en hot-reload.\n *\n * Afecta a los tres eventos (`beforeLog`, `afterLog`, `onError`) y a la\n * pila de middlewares. Las funciones de cancelación devueltas por\n * {@link on}/{@link once}/{@link use} se vuelven no-ops pero pueden\n * llamarse sin error.\n *\n * @example\n * ```ts\n * afterEach(() => hooks.clear());\n * ```\n */\n clear(): void {\n this.hooks.forEach(hooks => hooks.length = 0);\n this.middlewares.length = 0;\n }\n\n /**\n * Snapshot del estado interno para observabilidad y debugging.\n *\n * @returns {Object} stats\n * @returns {Record<HookEvent, number>} stats.hooks - Conteo de hooks\n * registrados por evento (`beforeLog`, `afterLog`, `onError`).\n * @returns {number} stats.middlewares - Total de middlewares activos.\n *\n * @example\n * ```ts\n * const stats = hooks.getStats();\n * // { hooks: { beforeLog: 2, afterLog: 1, onError: 3 }, middlewares: 1 }\n * if (stats.hooks.onError === 0) {\n * console.warn('Sin handlers onError registrados');\n * }\n * ```\n */\n getStats(): { hooks: Record<HookEvent, number>; middlewares: number } {\n return {\n hooks: {\n beforeLog: this.hooks.get('beforeLog')?.length || 0,\n afterLog: this.hooks.get('afterLog')?.length || 0,\n onError: this.hooks.get('onError')?.length || 0\n },\n middlewares: this.middlewares.length\n };\n }\n}\n\nlet _defaultHookManager: HookManager | null = null;\n\n/**\n * Devuelve la instancia singleton de {@link HookManager} compartida por todo\n * el proceso. La crea perezosamente en la primera llamada.\n *\n * Pensada como hook manager por defecto del logger; los consumidores que\n * necesiten aislamiento (tests, múltiples loggers con pipelines distintos)\n * deben instanciar su propio `new HookManager()` en lugar de usar este shared.\n *\n * @returns {HookManager} La instancia singleton.\n *\n * @example\n * ```ts\n * import { getDefaultHookManager } from '@mks2508/better-logger/hooks';\n *\n * getDefaultHookManager().on('onError', async (entry) => {\n * telemetry.capture(entry.error);\n * });\n * ```\n *\n * @see {@link HookManager}\n */\nexport function getDefaultHookManager(): HookManager {\n if (!_defaultHookManager) {\n _defaultHookManager = new HookManager();\n }\n return _defaultHookManager;\n}\n","/**\n * @fileoverview HookBridge — facade de HookManager.\n * Encapsula registro de hooks, middleware pipeline y emisión de eventos.\n *\n * @internal\n */\n\nimport type { HookEvent, HookCallback, MiddlewareFn } from '../types/index.js';\nimport { HookManager } from './index.js';\n\n/**\n * Bridge para la gestión de hooks y middleware.\n *\n * @internal\n */\nexport interface HookBridge {\n /** Registra un hook para un evento. Devuelve función de unsubscribe. */\n on(event: HookEvent, callback: HookCallback, priority?: number): () => void;\n /** Registra un hook one-time. Devuelve función de unsubscribe. */\n once(event: HookEvent, callback: HookCallback, priority?: number): () => void;\n /** Elimina un hook registrado. Devuelve `true` si se eliminó. */\n off(event: HookEvent, callback: HookCallback): boolean;\n /** Añade middleware al pipeline. Devuelve función de unsubscribe. */\n use(middleware: MiddlewareFn, priority?: number): () => void;\n /** Devuelve el HookManager subyacente. */\n getHookManager(): HookManager;\n}\n\n/**\n * Crea una instancia de {@link HookBridge}.\n *\n * @internal\n */\nexport function createHookBridge(): HookBridge {\n const hookManager = new HookManager();\n\n return {\n on(event: HookEvent, callback: HookCallback, priority?: number): () => void {\n return hookManager.on(event, callback, priority);\n },\n\n once(event: HookEvent, callback: HookCallback, priority?: number): () => void {\n return hookManager.once(event, callback, priority);\n },\n\n off(event: HookEvent, callback: HookCallback): boolean {\n return hookManager.off(event, callback);\n },\n\n use(middleware: MiddlewareFn, priority?: number): () => void {\n return hookManager.use(middleware, priority);\n },\n\n getHookManager(): HookManager {\n return hookManager;\n }\n };\n}\n"],"mappings":";;;;;;;;;;;AAoBA,MAAM,oBAAoB;;;;;;;;;;AAW1B,SAAS,aAAqB;CAC1B,OAAO,GAAG,KAAK,IAAI,EAAE,GAAG,KAAK,OAAO,CAAC,CAAC,SAAS,EAAE,CAAC,CAAC,MAAM,GAAG,EAAE;AAClE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAiDA,IAAa,cAAb,MAAiD;CAC7C,wBAAoD,IAAI,IAAI;CAC5D,cAAgD,CAAC;;;;;;CAMjD,gCAAgD,IAAI,IAAI;CAExD,cAAc;EACV,KAAK,MAAM,IAAI,aAAa,CAAC,CAAC;EAC9B,KAAK,MAAM,IAAI,YAAY,CAAC,CAAC;EAC7B,KAAK,MAAM,IAAI,WAAW,CAAC,CAAC;CAChC;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA6BA,GAAG,OAAkB,UAAwB,WAAmB,IAAgB;EAC5E,MAAM,eAAiC;GACnC,IAAI,WAAW;GACf;GACA;GACA;GACA,MAAM;EACV;EAEA,MAAM,QAAQ,KAAK,MAAM,IAAI,KAAK;EAClC,MAAM,KAAK,YAAY;EACvB,MAAM,MAAM,GAAG,MAAM,EAAE,WAAW,EAAE,QAAQ;EAE5C,aAAa,KAAK,WAAW,OAAO,aAAa,EAAE;CACvD;;;;;;;;;;;;;;;;;;;;;;;;CAyBA,KAAK,OAAkB,UAAwB,WAAmB,IAAgB;EAC9E,MAAM,eAAiC;GACnC,IAAI,WAAW;GACf;GACA;GACA;GACA,MAAM;EACV;EAEA,MAAM,QAAQ,KAAK,MAAM,IAAI,KAAK;EAClC,MAAM,KAAK,YAAY;EACvB,MAAM,MAAM,GAAG,MAAM,EAAE,WAAW,EAAE,QAAQ;EAE5C,aAAa,KAAK,WAAW,OAAO,aAAa,EAAE;CACvD;;;;;;;;;;;;;;;;;;;;;;;CAwBA,IAAI,OAAkB,UAAiC;EACnD,MAAM,QAAQ,KAAK,MAAM,IAAI,KAAK;EAClC,IAAI,CAAC,OAAO,OAAO;EAEnB,MAAM,QAAQ,MAAM,WAAU,MAAK,EAAE,aAAa,QAAQ;EAC1D,IAAI,SAAS,GAAG;GACZ,MAAM,OAAO,OAAO,CAAC;GACrB,OAAO;EACX;EACA,OAAO;CACX;CAEA,WAAmB,OAAkB,IAAkB;EACnD,MAAM,QAAQ,KAAK,MAAM,IAAI,KAAK;EAClC,IAAI,OAAO;GACP,MAAM,QAAQ,MAAM,WAAU,MAAK,EAAE,OAAO,EAAE;GAC9C,IAAI,SAAS,GACT,MAAM,OAAO,OAAO,CAAC;EAE7B;CACJ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAsCA,IAAI,YAA0B,WAAmB,IAAgB;EAC7D,MAAM,eAAuC;GACzC,IAAI,WAAW;GACf,IAAI;GACJ;EACJ;EAEA,KAAK,YAAY,KAAK,YAAY;EAClC,KAAK,YAAY,MAAM,GAAG,MAAM,EAAE,WAAW,EAAE,QAAQ;EAEvD,aAAa;GACT,MAAM,QAAQ,KAAK,YAAY,WAAU,MAAK,EAAE,OAAO,aAAa,EAAE;GACtE,IAAI,SAAS,GACT,KAAK,YAAY,OAAO,OAAO,CAAC;EAExC;CACJ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAmCA,MAAM,KAAK,OAAkB,OAA4C;EAMrE,MAAM,QAAS,KAAK,cAAc,IAAI,MAAM,aAAa,KAAK,KAAK;EACnE,IAAI,UAAU,aAAa,SAAS,mBAAmB;GAEnD,QAAQ,MAAM,iEAAiE,KAAK;GACpF,OAAO;EACX;EACA,KAAK,cAAc,IAAI,OAAO,QAAQ,CAAC;EAEvC,IAAI;GACA,MAAM,QAAQ,KAAK,MAAM,IAAI,KAAK,KAAK,CAAC;GACxC,IAAI,eAAe,EAAE,GAAG,MAAM;GAC9B,MAAM,WAAqB,CAAC;GAE5B,KAAK,MAAM,QAAQ,OACf,IAAI;IACA,MAAM,SAAS,MAAM,KAAK,SAAS,YAAY;IAC/C,IAAI,QACA,eAAe;KAAE,GAAG;KAAc,GAAG;IAAO;IAEhD,IAAI,KAAK,MACL,SAAS,KAAK,KAAK,EAAE;GAE7B,SAAS,OAAO;IACZ,IAAI,UAAU,WACV,MAAM,KAAK,KAAK,WAAW;KACvB,GAAG;KACH,OAAO,iBAAiB,QAAQ,QAAQ,IAAI,MAAM,OAAO,KAAK,CAAC;KAC/D,WAAW;IACf,CAAC;SAKD,QAAQ,MAAM,kEAAkE,KAAK;GAE7F;GAGJ,SAAS,SAAQ,OAAM,KAAK,WAAW,OAAO,EAAE,CAAC;GACjD,OAAO;EACX,UAAU;GACN,KAAK,cAAc,IAAI,OAAO,KAAK;EACvC;CACJ;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA6BA,MAAM,QAAQ,OAA4C;EACtD,IAAI,eAAe,EAAE,GAAG,MAAM;EAE9B,eAAe,MAAM,KAAK,KAAK,aAAa,YAAY;EAExD,IAAI,KAAK,YAAY,SAAS,GAAG;GAC7B,IAAI,QAAQ;GAEZ,MAAM,cAAc,YAA2B;IAC3C,IAAI,QAAQ,KAAK,YAAY,QAAQ;KACjC,MAAM,aAAa,KAAK,YAAY;KACpC,IAAI,YACA,MAAM,WAAW,GAAG,cAAc,WAAW;IAErD;GACJ;GAEA,MAAM,YAAY;EACtB;EAEA,OAAO;CACX;;;;;;;;;;;;;;;;;;;;;;;;;;CA2BA,MAAM,aAAa,OAAoC;EACnD,MAAM,KAAK,KAAK,YAAY,KAAK;CACrC;;;;;;;;;;;;;;;CAgBA,QAAc;EACV,KAAK,MAAM,SAAQ,UAAS,MAAM,SAAS,CAAC;EAC5C,KAAK,YAAY,SAAS;CAC9B;;;;;;;;;;;;;;;;;;CAmBA,WAAsE;EAClE,OAAO;GACH,OAAO;IACH,WAAW,KAAK,MAAM,IAAI,WAAW,CAAC,EAAE,UAAU;IAClD,UAAU,KAAK,MAAM,IAAI,UAAU,CAAC,EAAE,UAAU;IAChD,SAAS,KAAK,MAAM,IAAI,SAAS,CAAC,EAAE,UAAU;GAClD;GACA,aAAa,KAAK,YAAY;EAClC;CACJ;AACJ;AAEA,IAAI,sBAA0C;;;;;;;;;;;;;;;;;;;;;;AAuB9C,SAAgB,wBAAqC;CACjD,IAAI,CAAC,qBACD,sBAAsB,IAAI,YAAY;CAE1C,OAAO;AACX;;;;;;;;ACpeA,SAAgB,mBAA+B;CAC3C,MAAM,cAAc,IAAI,YAAY;CAEpC,OAAO;EACH,GAAG,OAAkB,UAAwB,UAA+B;GACxE,OAAO,YAAY,GAAG,OAAO,UAAU,QAAQ;EACnD;EAEA,KAAK,OAAkB,UAAwB,UAA+B;GAC1E,OAAO,YAAY,KAAK,OAAO,UAAU,QAAQ;EACrD;EAEA,IAAI,OAAkB,UAAiC;GACnD,OAAO,YAAY,IAAI,OAAO,QAAQ;EAC1C;EAEA,IAAI,YAA0B,UAA+B;GACzD,OAAO,YAAY,IAAI,YAAY,QAAQ;EAC/C;EAEA,iBAA8B;GAC1B,OAAO;EACX;CACJ;AACJ"}
@@ -1,10 +1,25 @@
1
1
  //#region src/context/LogContext.ts
2
2
  const alsInstance = typeof AsyncLocalStorage !== "undefined" ? new AsyncLocalStorage() : void 0;
3
3
  /**
4
- * Creates a LogContext instance.
4
+ * Factory que crea una instancia de {@link LogContext}.
5
5
  *
6
- * @param options - Configuration options
7
- * @returns A LogContext instance
6
+ * @param options - Configuración (ver {@link ILogContextOptions})
7
+ * @returns Una instancia de LogContext lista para usar
8
+ *
9
+ * @example
10
+ * const logContext = createLogContext({
11
+ * childLoggerFactory: (cfg) => new Logger(cfg),
12
+ * initialContext: { service: 'orders-api' },
13
+ * initialResource: { serviceName: 'orders-api', environment: 'prod' }
14
+ * });
15
+ *
16
+ * // Child inmutable con contexto persistente
17
+ * const requestLog = logContext.child({ requestId: 'abc-123' });
18
+ *
19
+ * // Scope transitorio vía ALS (Node; en browser sin ALS es no-op)
20
+ * logContext.withContext({ traceId: 't-9' }, () => {
21
+ * requestLog.info('procesando orden');
22
+ * });
8
23
  */
9
24
  function createLogContext(options) {
10
25
  let context = { ...options.initialContext };
@@ -87,4 +102,4 @@ Object.defineProperty(exports, "createLogContext", {
87
102
  }
88
103
  });
89
104
 
90
- //# sourceMappingURL=LogContext-L7HzynFw.cjs.map
105
+ //# sourceMappingURL=LogContext-BaMXleWj.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"LogContext-BaMXleWj.cjs","names":[],"sources":["../../src/context/LogContext.ts"],"sourcesContent":["/**\n * @fileoverview Bridge de LogContext — gestión de MDC (Mapped Diagnostic Context).\n *\n * Encapsula el contexto estructurado por logger, la creación de child loggers y\n * el merge de resource OTel en cada record emitido.\n *\n * Modelo de API:\n * - `withContext(bindings, fn?)` — si se pasa `fn`, lo ejecuta dentro de un\n * scope de AsyncLocalStorage mergeando `bindings`. Sin `fn`: no-op (shim de\n * backwards compat para la vieja forma de setter).\n * - `withContextAsync(bindings, fn)` — variante async callback.\n * - `child(bindings)` — inmutable (patrón canónico de MDC).\n * - Feature-detect de AsyncLocalStorage; en browser sin ALS es no-op.\n */\n\nimport type { ILogResourceRef } from '../types/index.js';\nimport type { LoggerConfig } from '../types/index.js';\n\n/**\n * Snapshot del contexto bound. Lo retorna {@link LogContext.getContext}.\n *\n * Es `Readonly` para marcar contractually que el objeto devuelto es una shallow\n * copy: mutarlo no afecta a los records que emitan futuras llamadas de log.\n */\nexport type ContextSnapshot = Readonly<Record<string, unknown>>;\n\n/**\n * Tipo de la factory function para crear instancias child de Logger.\n *\n * Se inyecta en LogContext para que `child()` pueda instanciar nuevos loggers\n * sin introducir un import circular entre `Logger.ts` y `LogContext.ts`.\n * Retorna `unknown` — la clase Logger concreta la maneja el caller, y la\n * instancia devuelta tiene su campo `context` escrito por LogContext tras la\n * creación.\n */\nexport type ChildLoggerFactory = (config: Partial<LoggerConfig>) => unknown;\n\n/**\n * Shape mínima de una instancia de Logger que LogContext necesita ver.\n *\n * Evita dependencias circulares entre LogContext y Logger. El campo\n * `_parentContextRecord` lo setea el Logger padre después de que\n * `LogContext.child()` retorna, estableciendo la cadena de contextos.\n */\nexport interface ChildLoggerShape {\n _parentContextRecord?: Record<string, unknown>;\n /** Campo legacy — ya no es la fuente canónica del contexto. */\n context?: Record<string, unknown>;\n}\n\n/**\n * Options que se pasan a {@link createLogContext}.\n */\nexport interface ILogContextOptions {\n /** Pares key-value iniciales del contexto. */\n initialContext?: Record<string, unknown>;\n /** Factory para crear instancias child de logger. */\n childLoggerFactory: ChildLoggerFactory;\n /** Resource OTel inicial a mergear en cada record emitido. */\n initialResource?: Partial<ILogResourceRef>;\n /**\n * Retorna el record de contexto mergeado del logger padre en el momento\n * de creación del child. Lo usa `_getContextRecord()` para construir la\n * cadena de contextos.\n * @internal\n */\n getParentContextRecord?: () => Record<string, unknown>;\n /**\n * Instancia de AsyncLocalStorage a usar para el scoping de `withContext`.\n * @internal\n */\n alsInstance?: ALS;\n}\n\n/**\n * Contrato que retorna {@link createLogContext}.\n *\n * Fachada de MDC (Mapped Diagnostic Context) por logger. Combina tres fuentes\n * de contexto:\n * - **base inmutable** vía `child()` (snapshot capturado al crear el child),\n * - **scope transitorio** vía `withContext()` / `withContextAsync()` sobre\n * AsyncLocalStorage,\n * - **resource OTel** mergeado en cada record.\n *\n * En entornos browser sin `AsyncLocalStorage`, las variantes `withContext*`\n * degradan a no-op: ejecutan `fn` sin scoping (o lo skipan si no hay `fn`).\n * `child()` sigue operativo en browser porque no depende de ALS.\n */\nexport interface LogContext {\n /**\n * Snapshot actual del contexto bound.\n *\n * @returns Copia inmutable (shallow) del contexto; mutarla no afecta a los\n * records que emitan futuras llamadas de log.\n *\n * @example\n * const ctx = logContext.getContext();\n * console.log(ctx.requestId); // 'abc-123'\n */\n getContext(): ContextSnapshot;\n\n /**\n * Ejecuta `fn` dentro de un scope de AsyncLocalStorage donde `bindings`\n * se mergean al contexto para todas las llamadas de log dentro de `fn`.\n *\n * Si no se pasa `fn` (la vieja forma de setter), es no-op por backwards\n * compatibility. Para binding persistente prefiere `child()`; para\n * callbacks async usa `withContextAsync()`.\n *\n * **Browser fallback**: sin ALS, ejecuta `fn` directamente sin scoping\n * (los bindings NO se mergean). Si tampoco hay `fn`, retorna `undefined`.\n *\n * @param bindings - Pares key-value a attachar durante la ejecución de `fn`\n * @param fn - Función sincrónica opcional a ejecutar bajo el scope ALS\n * @returns El valor de retorno de `fn`, o `undefined` si no se pasa `fn`\n *\n * @example\n * logContext.withContext({ requestId: 'abc-123' }, () => {\n * logger.info('procesando'); // el record lleva requestId=abc-123\n * });\n * // fuera de fn: requestId ya no está presente en próximos logs\n *\n * @see {@link LogContext.withContextAsync} para callbacks async\n * @see {@link LogContext.child} para binding persistente inmutable (sin ALS)\n */\n withContext<R>(bindings: Record<string, unknown>, fn?: () => R): R | undefined;\n\n /**\n * Variante async de {@link withContext}. Ejecuta `fn` dentro de un scope\n * de AsyncLocalStorage para que los bindings queden disponibles a todas las\n * llamadas de log async dentro de `fn` (incluso tras `await`).\n *\n * **Browser fallback**: sin ALS, ejecuta `fn` directamente sin scoping.\n *\n * @param bindings - Pares key-value a attachar durante la ejecución de `fn`\n * @param fn - Función async a ejecutar bajo el scope ALS\n * @returns El Promise retornado por `fn`\n *\n * @example\n * await logContext.withContextAsync({ traceId }, async () => {\n * const user = await fetchUser();\n * logger.info('user cargado', { id: user.id });\n * // el record lleva el traceId aunque el log ocurra tras un await\n * });\n */\n withContextAsync<R>(bindings: Record<string, unknown>, fn: () => Promise<R>): Promise<R>;\n\n /**\n * Droppea todas las keys del contexto bound. Tras esta llamada, los\n * records emitidos ya no llevan `attributes` hasta que\n * {@link withContext} o {@link child} restablezcan uno.\n *\n * @returns La misma instancia de LogContext, ahora sin contexto\n *\n * @example\n * logContext.clearContext();\n * logger.info('limpio'); // sin attributes\n */\n clearContext(): this;\n\n /**\n * Actualiza el resource OTel por defecto (service.name, version,\n * deployment.environment, ...).\n *\n * Se persiste en el campo `resource` de cada record emitido, salvo que el\n * propio record lo overridee.\n *\n * @param resource - Resource OTel parcial a mergear con el actual\n * @returns La misma instancia de LogContext, para encadenar calls\n *\n * @example\n * logContext.setResource({ serviceName: 'api-gateway', environment: 'prod' });\n */\n setResource(resource: Partial<ILogResourceRef>): this;\n\n /**\n * Devuelve una copia inmutable de este logger con el contexto extra bound.\n *\n * Las llamadas futuras sobre el child emiten con el contexto mergeado, sin\n * mutar al padre — patrón canónico de MDC.\n *\n * A diferencia de {@link withContext}, **no involucra AsyncLocalStorage**:\n * el binding es persistente y queda capturado en el snapshot del child al\n * crearse. Por eso `child()` es operativo también en browser sin ALS.\n *\n * Los bindings transitorios de ALS activos en el momento de `child()` NO\n * se bakean en el child — solo se captura el contexto base. ALS se aplica\n * fresco en cada dispatch vía `_getContextRecord()`.\n *\n * @param extra - Pares key-value a attachar (requestId, userId, ...)\n * @returns Un nuevo Logger con el contexto mergeado\n *\n * @example\n * const requestLog = logContext.child({ requestId: 'abc-123' });\n * requestLog.info('inicio'); // siempre lleva requestId=abc-123\n * requestLog.info('fin');\n * // el logger padre no se ve afectado por estos bindings\n *\n * @see {@link LogContext.withContext} para scoping transitorio (ALS)\n */\n child(extra: Record<string, unknown>): ChildLoggerShape;\n\n /**\n * Record de contexto interno. Expuesto para el ensamblado de TransportRecord\n * (base + overlay ALS si hay store activo).\n * @internal\n */\n _getContextRecord(): Record<string, unknown>;\n /**\n * Retorna el contexto base SIN el overlay de ALS.\n *\n * Lo usa `Logger.child()` para capturar el snapshot del contexto padre al\n * crear un child logger, garantizando que el binding ALS transitorio no\n * se bakeé en el child.\n * @internal\n */\n _getBaseContextRecord(): Record<string, unknown>;\n /**\n * Record de resource interno. Expuesto para el ensamblado de TransportRecord.\n * @internal\n */\n _getResource(): Partial<ILogResourceRef> | undefined;\n /**\n * Retorna el store actual de AsyncLocalStorage, si ALS está activo en el\n * call stack corriente.\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 * Factory que crea una instancia de {@link LogContext}.\n *\n * @param options - Configuración (ver {@link ILogContextOptions})\n * @returns Una instancia de LogContext lista para usar\n *\n * @example\n * const logContext = createLogContext({\n * childLoggerFactory: (cfg) => new Logger(cfg),\n * initialContext: { service: 'orders-api' },\n * initialResource: { serviceName: 'orders-api', environment: 'prod' }\n * });\n *\n * // Child inmutable con contexto persistente\n * const requestLog = logContext.child({ requestId: 'abc-123' });\n *\n * // Scope transitorio vía ALS (Node; en browser sin ALS es no-op)\n * logContext.withContext({ traceId: 't-9' }, () => {\n * requestLog.info('procesando orden');\n * });\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":";AA+OA,MAAM,cADS,OAAO,sBAAsB,cACE,IAAI,kBAAkB,IAAI,KAAA;;;;;;;;;;;;;;;;;;;;;;AAuBxE,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"}