@fend/firo 0.0.1 → 0.0.2

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 CHANGED
@@ -4,22 +4,29 @@
4
4
 
5
5
  The logger for Node.js, Bun and Deno you've been looking for.
6
6
 
7
- Beautiful **dev** output - out of the box. High-load ready NDJSON for **prod**.
7
+ Beautiful **dev** output - out of the box. Structured NDJSON for **prod**.
8
8
 
9
- Think of it as pino, but with brilliant DX. **firo** (from *Fir*) is the elegant, refined sibling of the logging forest.
9
+ Think of it as pino, but with brilliant DX.
10
10
 
11
- ![firo output](image.png)
11
+ ## Demo
12
+
13
+ Beautiful colors in dev mode:
14
+
15
+ ![firo in action](https://github.com/fend25/firo/blob/main/img/dev_mode.png?raw=true)
16
+
17
+ Structured NDJSON in production mode:
18
+
19
+ ![firo prod output](https://github.com/fend25/firo/blob/main/img/prod_mode.png)
12
20
 
13
21
  ## Features
14
22
 
15
23
  - **Dev mode** — colored, timestamped, human-readable output with context badges
16
- - **Prod mode** — structured NDJSON, one record per line, ready for log aggregators
17
- - **Async mode** — non-blocking buffered output for high-load production
24
+ - **Prod mode** — structured NDJSON, one record per line
18
25
  - **Context system** — attach key/value pairs that beautifully appear in every subsequent log line
19
26
  - **Child loggers** — inherit parent context, fully isolated from each other
20
27
  - **Per-call context** — attach extra fields to a single log call without mutating state
21
28
  - **Severity Level filtering** — globally or per-mode thresholds to reduce noise
22
- - **30 named colors** — `FIRO_COLORS` palette with IDE autocomplete, plus raw ANSI/256-color/truecolor support
29
+ - **30 named colors** — `FIRO_COLORS` palette with great handpicked colors, plus raw ANSI/256-color/truecolor support
23
30
  - **Zero dependencies** — small and fast, no bloat, no native addons. Works on Node.js, Bun and Deno.
24
31
 
25
32
  ## Install
@@ -29,13 +36,14 @@ Think of it as pino, but with brilliant DX. **firo** (from *Fir*) is the elegant
29
36
  npm install @fend/firo
30
37
  yarn add @fend/firo
31
38
  pnpm add @fend/firo
32
- npx jsr add @fend/firo
33
39
 
34
- # or, for deno:
40
+ # for bun:
41
+ bun add @fend/firo
42
+
43
+ # for deno:
35
44
  deno add jsr:@fend/firo
36
45
  ```
37
46
 
38
-
39
47
  ## Quick start
40
48
 
41
49
  ```ts
@@ -78,19 +86,6 @@ log.info('Request handled', { status: 200 })
78
86
  // {"timestamp":"2024-01-15T14:32:01.204Z","level":"info","message":"Request handled","data":{"status":200}}
79
87
  ```
80
88
 
81
- #### Async mode (Prod only)
82
-
83
- For high-load applications, you can enable asynchronous buffered output. This avoids blocking the event loop when writing to `stdout`, which is critical for maintaining low latency.
84
-
85
- ```ts
86
- const log = createLogger({
87
- mode: 'prod',
88
- async: true // Enables non-blocking buffered output
89
- })
90
- ```
91
-
92
- When `async` is enabled, logs are queued and flushed when the stream is ready (handling backpressure). We also ensure all buffered logs are flushed synchronously if the process exits or crashes.
93
-
94
89
  ## Best practices
95
90
 
96
91
  ### AsyncLocalStorage (Traceability)
@@ -166,15 +161,15 @@ log.info('Started')
166
161
 
167
162
  ```ts
168
163
  // Hide the key, show only the value — useful for IDs
169
- log.addContext({ key: 'userId', value: 'u-789', options: { omitKey: true } })
164
+ log.addContext({ key: 'userId', value: 'u-789', omitKey: true })
170
165
  // renders as [u-789] instead of [userId:u-789]
171
166
 
172
167
  // Pin a specific color (0–9)
173
- log.addContext({ key: 'region', value: 'eu-west', options: { colorIndex: 3 } })
168
+ log.addContext({ key: 'region', value: 'west', colorIndex: 3 })
174
169
 
175
170
  // Use any ANSI color — 256-color, truecolor, anything
176
- log.addContext({ key: 'trace', value: 'abc', options: { color: '38;5;214' } }) // 256-color orange
177
- log.addContext({ key: 'span', value: 'xyz', options: { color: '38;2;255;100;0' } }) // truecolor
171
+ log.addContext({ key: 'trace', value: 'abc', color: '38;5;214' }) // 256-color orange
172
+ log.addContext({ key: 'span', value: 'xyz', color: '38;2;255;100;0' }) // truecolor
178
173
  ```
179
174
 
180
175
  ### Remove context
@@ -220,7 +215,7 @@ Add context to a single log call without touching the logger's state:
220
215
 
221
216
  ```ts
222
217
  log.info('User action', payload, {
223
- ctx: [{ key: 'userId', value: 42, options: { omitKey: true } }]
218
+ ctx: [{ key: 'userId', value: 42, omitKey: true }]
224
219
  })
225
220
  ```
226
221
 
@@ -291,7 +286,7 @@ const log = createLogger({
291
286
 
292
287
  Most loggers give you monochrome walls of text. firo gives you **30 handpicked colors** that make context badges instantly scannable — you stop reading and start seeing.
293
288
 
294
- ![firo color palette](color_madness.png)
289
+ ![firo color palette](https://github.com/fend25/firo/blob/main/img/color_madness.png)
295
290
 
296
291
  ### How it works
297
292
 
@@ -304,9 +299,9 @@ import { createLogger, FIRO_COLORS } from '@fend/firo'
304
299
 
305
300
  const log = createLogger()
306
301
 
307
- log.addContext('region', 'eu-west', { color: FIRO_COLORS.coral })
308
- log.addContext('service', 'auth', { color: FIRO_COLORS.skyBlue })
309
- log.addContext('env', 'staging', { color: FIRO_COLORS.lavender })
302
+ log.addContext('region', { value: 'west', color: FIRO_COLORS.coral })
303
+ log.addContext('service', { value: 'auth', color: FIRO_COLORS.skyBlue })
304
+ log.addContext('env', { value: 'staging', color: FIRO_COLORS.lavender })
310
305
  ```
311
306
 
312
307
  Available colors: `cyan`, `green`, `yellow`, `magenta`, `blue`, `brightCyan`, `brightGreen`, `brightYellow`, `brightMagenta`, `brightBlue`, `orange`, `pink`, `lilac`, `skyBlue`, `mint`, `salmon`, `lemon`, `lavender`, `sage`, `coral`, `teal`, `rose`, `pistachio`, `mauve`, `aqua`, `gold`, `thistle`, `seafoam`, `tangerine`, `periwinkle`.
@@ -316,8 +311,8 @@ Available colors: `cyan`, `green`, `yellow`, `magenta`, `blue`, `brightCyan`, `b
316
311
  You can also pass any raw ANSI code as a string — 256-color, truecolor, go wild:
317
312
 
318
313
  ```ts
319
- log.addContext('trace', 'abc', { color: '38;5;214' }) // 256-color
320
- log.addContext('span', 'xyz', { color: '38;2;255;105;180' }) // truecolor pink
314
+ log.addContext('trace', { value: 'abc', color: '38;5;214' }) // 256-color
315
+ log.addContext('span', { value: 'xyz', color: '38;2;255;105;180' }) // truecolor pink
321
316
  ```
322
317
 
323
318
  ### Use all 30 colors for auto-hash
@@ -329,7 +324,7 @@ const log = createLogger({ useAllColors: true })
329
324
 
330
325
  // Now every context key auto-gets one of 30 distinct colors
331
326
  log.addContext('service', 'api')
332
- log.addContext('region', 'eu-west')
327
+ log.addContext('region', 'west')
333
328
  log.addContext('pod', 'web-3')
334
329
  // Each badge is a different, beautiful color — no configuration needed
335
330
  ```
@@ -363,7 +358,7 @@ In prod it emits clean NDJSON, same as pino. Your log aggregator won't know the
363
358
  | `warn(msg, data?, opts?)` | Warning |
364
359
  | `error(msg, err?, opts?)` | Error — also accepts `error(err)` |
365
360
  | `child(ctx)` | Create a child logger with additional context |
366
- | `addContext(key, value, opts?)` | Add a context entry |
361
+ | `addContext(key, value \| ext)` | Add a context entry |
367
362
  | `addContext(item)` | Add a context entry (object form) |
368
363
  | `removeFromContext(key)` | Remove a context entry by key |
369
364
  | `getContext()` | Return the current context array |
@@ -378,7 +373,6 @@ In prod it emits clean NDJSON, same as pino. Your log aggregator won't know the
378
373
  | `minLevelInProd` | `LogLevel` | — | Overrides `minLevel` in prod mode |
379
374
  | `transport` | `TransportFn` | — | Custom transport, overrides `mode` |
380
375
  | `devTransportConfig` | `DevTransportConfig` | — | Options for the built-in dev transport |
381
- | `async` | `boolean` | `false` | Enable non-blocking output (Prod mode only) |
382
376
  | `useAllColors` | `boolean` | `false` | Use all 30 palette colors for auto-hash (instead of 10 safe) |
383
377
 
384
378
  ## License
package/dist/index.cjs CHANGED
@@ -112,7 +112,6 @@ var colorizeLevel = (level, text) => {
112
112
  // src/transports.ts
113
113
  var import_node_util = require("util");
114
114
  var import_node_process = __toESM(require("process"), 1);
115
- var import_node_fs = __toESM(require("fs"), 1);
116
115
  var createDevTransport = (config = {}) => {
117
116
  const locale = config.locale ?? void 0;
118
117
  const timeOpts = {
@@ -127,9 +126,9 @@ var createDevTransport = (config = {}) => {
127
126
  const now = /* @__PURE__ */ new Date();
128
127
  const timestamp = now.toLocaleTimeString(locale, timeOpts);
129
128
  const contextStr = context.map((ctx) => {
130
- const key = ctx.options?.omitKey ? "" : `${ctx.key}:`;
129
+ const key = ctx.omitKey ? "" : `${ctx.key}:`;
131
130
  const content = `${key}${ctx.value}`;
132
- return colorize(`[${content}]`, ctx.options.colorIndex, ctx.options.color);
131
+ return colorize(`[${content}]`, ctx.colorIndex, ctx.color);
133
132
  }).join(" ");
134
133
  if (level === "error" && data === void 0) {
135
134
  const realError = wrapToError(msg);
@@ -207,40 +206,7 @@ var buildRecord = (level, context, msg, data) => {
207
206
  }
208
207
  return logRecord;
209
208
  };
210
- var createJsonTransport = (config = {}) => {
211
- const queue = [];
212
- const maxQueueSize = config.maxQueueSize ?? 1e3;
213
- let isDraining = false;
214
- const flush = () => {
215
- if (queue.length === 0 || isDraining) return;
216
- while (queue.length > 0) {
217
- const line = queue.shift();
218
- const ok = import_node_process.default.stdout.write(line);
219
- if (!ok) {
220
- isDraining = true;
221
- import_node_process.default.stdout.once("drain", () => {
222
- isDraining = false;
223
- flush();
224
- });
225
- return;
226
- }
227
- }
228
- };
229
- const flushSync = () => {
230
- while (queue.length > 0) {
231
- const line = queue.shift();
232
- if (line) {
233
- try {
234
- import_node_fs.default.writeSync(1, line);
235
- } catch {
236
- }
237
- }
238
- }
239
- };
240
- if (config.async) {
241
- import_node_process.default.on("beforeExit", flushSync);
242
- import_node_process.default.on("exit", flushSync);
243
- }
209
+ var createJsonTransport = () => {
244
210
  return (level, context, msg, data) => {
245
211
  const record = buildRecord(level, context, msg, data);
246
212
  let line;
@@ -259,15 +225,7 @@ var createJsonTransport = (config = {}) => {
259
225
  }) + "\n";
260
226
  }
261
227
  }
262
- if (!config.async) {
263
- import_node_process.default.stdout.write(line);
264
- return;
265
- }
266
- queue.push(line);
267
- if (queue.length > maxQueueSize) {
268
- queue.shift();
269
- }
270
- flush();
228
+ import_node_process.default.stdout.write(line);
271
229
  };
272
230
  };
273
231
 
@@ -275,11 +233,9 @@ var createJsonTransport = (config = {}) => {
275
233
  var fillContextItem = (item, useAllColors = false) => {
276
234
  return {
277
235
  ...item,
278
- options: {
279
- colorIndex: item.options && typeof item.options.colorIndex === "number" ? item.options.colorIndex : getColorIndex(item.key, useAllColors),
280
- color: item.options?.color,
281
- omitKey: item.options?.omitKey ?? false
282
- }
236
+ colorIndex: typeof item.colorIndex === "number" ? item.colorIndex : getColorIndex(item.key, useAllColors),
237
+ color: item.color,
238
+ omitKey: item.omitKey ?? false
283
239
  };
284
240
  };
285
241
  var createLoggerInternal = (config, parentContext) => {
@@ -290,12 +246,23 @@ var createLoggerInternal = (config, parentContext) => {
290
246
  return [...context2, ...invokeContext.map(fill)];
291
247
  };
292
248
  const context = [...parentContext.map(fill)];
293
- const transport = config.transport ?? (config.mode === "prod" ? createJsonTransport({ async: config.async }) : createDevTransport(config.devTransportConfig));
249
+ const transport = config.transport ?? (config.mode === "prod" ? createJsonTransport() : createDevTransport(config.devTransportConfig));
294
250
  const minLevelName = config.mode === "prod" ? config.minLevelInProd ?? config.minLevel : config.minLevelInDev ?? config.minLevel;
295
251
  const minLevel = LOG_LEVELS[minLevelName ?? "debug"];
296
252
  const getContext = () => context;
297
- const addContext = (key, value, options) => {
298
- let item = typeof key === "string" ? { key, value, options } : key;
253
+ const hasInContext = (key) => context.some((ctx) => ctx.key === key);
254
+ const addContext = (key, value) => {
255
+ let item;
256
+ if (typeof key === "string") {
257
+ if (value !== null && value !== void 0 && typeof value === "object") {
258
+ const { value: extValue, ...opts } = value;
259
+ item = { key, value: extValue, ...opts };
260
+ } else {
261
+ item = { key, value };
262
+ }
263
+ } else {
264
+ item = key;
265
+ }
299
266
  context.push(fill(item));
300
267
  };
301
268
  const removeKeyFromContext = (key) => {
@@ -303,11 +270,13 @@ var createLoggerInternal = (config, parentContext) => {
303
270
  if (index !== -1) context.splice(index, 1);
304
271
  };
305
272
  const child = (ctx) => {
306
- const newItems = Object.entries(ctx).map(([key, value]) => ({
307
- key,
308
- value,
309
- options: { colorIndex: getColorIndex(key, useAllColors) }
310
- }));
273
+ const newItems = Object.entries(ctx).map(([key, value]) => {
274
+ if (value !== null && value !== void 0 && typeof value === "object") {
275
+ const { value: extValue, ...opts } = value;
276
+ return { key, value: extValue, ...opts };
277
+ }
278
+ return { key, value, colorIndex: getColorIndex(key, useAllColors) };
279
+ });
311
280
  return createLoggerInternal({ transport, minLevel: minLevelName, useAllColors }, [...context, ...newItems]);
312
281
  };
313
282
  const debug = (msg, data, opts) => {
package/dist/index.d.cts CHANGED
@@ -15,11 +15,16 @@ type ContextOptions = {
15
15
  type ContextItem = {
16
16
  key: string;
17
17
  value: ContextValue;
18
- options?: ContextOptions;
19
- };
18
+ } & Partial<ContextOptions>;
19
+ /** Configuration options for creating a logger child instance. */
20
+ type ContextExtension = {
21
+ value: ContextValue;
22
+ } & Partial<ContextOptions>;
20
23
  /** A context entry where options have been fully resolved with defaults. */
21
24
  type ContextItemWithOptions = ContextItem & {
22
- options: Required<Pick<ContextOptions, 'colorIndex' | 'omitKey'>> & Pick<ContextOptions, 'color'>;
25
+ colorIndex: number;
26
+ omitKey: boolean;
27
+ color?: string;
23
28
  };
24
29
  /** Options that can be passed to a single log call. */
25
30
  type LogOptions = {
@@ -81,26 +86,13 @@ type DevTransportConfig = {
81
86
  * @returns A `TransportFn` that writes to the console.
82
87
  */
83
88
  declare const createDevTransport: (config?: DevTransportConfig) => TransportFn;
84
- /**
85
- * Configuration for the JSON transport.
86
- */
87
- type JsonTransportConfig = {
88
- /**
89
- * Enable asynchronous/buffered output.
90
- * When true, logs are queued and written when the stream is ready (handling backpressure).
91
- */
92
- async?: boolean;
93
- /** Maximum number of log lines to buffer when async is enabled. Defaults to 1000. */
94
- maxQueueSize?: number;
95
- };
96
89
  /**
97
90
  * Creates a built-in transport optimized for production.
98
91
  * Emits strictly structured NDJSON (Newline Delimited JSON) to stdout.
99
92
  *
100
- * @param config Optional configuration for async and buffering behavior.
101
93
  * @returns A `TransportFn` that writes JSON to standard output.
102
94
  */
103
- declare const createJsonTransport: (config?: JsonTransportConfig) => TransportFn;
95
+ declare const createJsonTransport: () => TransportFn;
104
96
 
105
97
  /**
106
98
  * Configuration options for creating a logger instance.
@@ -118,8 +110,6 @@ type LoggerConfig = {
118
110
  transport?: TransportFn;
119
111
  /** Options for fine-tuning the built-in development transport (e.g. timestamp format). */
120
112
  devTransportConfig?: DevTransportConfig;
121
- /** Enable asynchronous/buffered output for 'prod' mode to avoid event loop blocking. */
122
- async?: boolean;
123
113
  /** Use the full extended color palette (30 colors including 256-color) for auto-assigned context badges. Defaults to false (safe 10-color palette). */
124
114
  useAllColors?: boolean;
125
115
  };
@@ -127,7 +117,7 @@ type LoggerConfig = {
127
117
  * The logger instance returned by `createLogger`.
128
118
  * It is a callable object: calling `log(msg)` is shorthand for `log.info(msg)`.
129
119
  */
130
- interface ILogger {
120
+ interface Firo {
131
121
  /** Shorthand for log.info() */
132
122
  (msg: string, data?: unknown, opts?: LogOptions): void;
133
123
  /** Log a debug message (dimmed in dev mode). */
@@ -144,23 +134,25 @@ interface ILogger {
144
134
  * Create a scoped child logger that inherits the current logger's context.
145
135
  * @param ctx An object containing key-value pairs to add to the child logger's context.
146
136
  */
147
- child: (ctx: Record<string, ContextValue>) => ILogger;
137
+ child: (ctx: Record<string, ContextValue | ContextExtension>) => Firo;
148
138
  /** Add a context entry by key and value. */
149
- addContext(key: string, value: ContextValue, opts?: ContextOptions): void;
139
+ addContext(key: string, value: ContextValue | ContextExtension): void;
150
140
  /** Add a context entry using the object form. */
151
141
  addContext(item: ContextItem): void;
152
142
  /** Remove a context entry by its key. */
153
143
  removeFromContext(key: string): void;
154
144
  /** Return the current context array attached to this logger instance. */
155
145
  getContext(): ContextItem[];
146
+ /** Check if a context key exists in the current logger instance. */
147
+ hasInContext(key: string): boolean;
156
148
  }
157
149
 
158
150
  /**
159
151
  * Creates a new logger instance with the specified configuration.
160
152
  *
161
153
  * @param config Optional configuration for log levels, mode, and transports.
162
- * @returns A fully configured `ILogger` instance.
154
+ * @returns A fully configured `Firo` instance.
163
155
  */
164
- declare const createLogger: (config?: LoggerConfig) => ILogger;
156
+ declare const createLogger: (config?: LoggerConfig) => Firo;
165
157
 
166
- export { type ContextItem, type ContextItemWithOptions, type ContextOptions, type ContextValue, type DevTransportConfig, FIRO_COLORS, type ILogger, type JsonTransportConfig, type LogLevel, type LogOptions, type LoggerConfig, type TransportFn, createDevTransport, createJsonTransport, createLogger };
158
+ export { type ContextExtension, type ContextItem, type ContextItemWithOptions, type ContextOptions, type ContextValue, type DevTransportConfig, FIRO_COLORS, type Firo, type LogLevel, type LogOptions, type LoggerConfig, type TransportFn, createDevTransport, createJsonTransport, createLogger };
package/dist/index.d.ts CHANGED
@@ -15,11 +15,16 @@ type ContextOptions = {
15
15
  type ContextItem = {
16
16
  key: string;
17
17
  value: ContextValue;
18
- options?: ContextOptions;
19
- };
18
+ } & Partial<ContextOptions>;
19
+ /** Configuration options for creating a logger child instance. */
20
+ type ContextExtension = {
21
+ value: ContextValue;
22
+ } & Partial<ContextOptions>;
20
23
  /** A context entry where options have been fully resolved with defaults. */
21
24
  type ContextItemWithOptions = ContextItem & {
22
- options: Required<Pick<ContextOptions, 'colorIndex' | 'omitKey'>> & Pick<ContextOptions, 'color'>;
25
+ colorIndex: number;
26
+ omitKey: boolean;
27
+ color?: string;
23
28
  };
24
29
  /** Options that can be passed to a single log call. */
25
30
  type LogOptions = {
@@ -81,26 +86,13 @@ type DevTransportConfig = {
81
86
  * @returns A `TransportFn` that writes to the console.
82
87
  */
83
88
  declare const createDevTransport: (config?: DevTransportConfig) => TransportFn;
84
- /**
85
- * Configuration for the JSON transport.
86
- */
87
- type JsonTransportConfig = {
88
- /**
89
- * Enable asynchronous/buffered output.
90
- * When true, logs are queued and written when the stream is ready (handling backpressure).
91
- */
92
- async?: boolean;
93
- /** Maximum number of log lines to buffer when async is enabled. Defaults to 1000. */
94
- maxQueueSize?: number;
95
- };
96
89
  /**
97
90
  * Creates a built-in transport optimized for production.
98
91
  * Emits strictly structured NDJSON (Newline Delimited JSON) to stdout.
99
92
  *
100
- * @param config Optional configuration for async and buffering behavior.
101
93
  * @returns A `TransportFn` that writes JSON to standard output.
102
94
  */
103
- declare const createJsonTransport: (config?: JsonTransportConfig) => TransportFn;
95
+ declare const createJsonTransport: () => TransportFn;
104
96
 
105
97
  /**
106
98
  * Configuration options for creating a logger instance.
@@ -118,8 +110,6 @@ type LoggerConfig = {
118
110
  transport?: TransportFn;
119
111
  /** Options for fine-tuning the built-in development transport (e.g. timestamp format). */
120
112
  devTransportConfig?: DevTransportConfig;
121
- /** Enable asynchronous/buffered output for 'prod' mode to avoid event loop blocking. */
122
- async?: boolean;
123
113
  /** Use the full extended color palette (30 colors including 256-color) for auto-assigned context badges. Defaults to false (safe 10-color palette). */
124
114
  useAllColors?: boolean;
125
115
  };
@@ -127,7 +117,7 @@ type LoggerConfig = {
127
117
  * The logger instance returned by `createLogger`.
128
118
  * It is a callable object: calling `log(msg)` is shorthand for `log.info(msg)`.
129
119
  */
130
- interface ILogger {
120
+ interface Firo {
131
121
  /** Shorthand for log.info() */
132
122
  (msg: string, data?: unknown, opts?: LogOptions): void;
133
123
  /** Log a debug message (dimmed in dev mode). */
@@ -144,23 +134,25 @@ interface ILogger {
144
134
  * Create a scoped child logger that inherits the current logger's context.
145
135
  * @param ctx An object containing key-value pairs to add to the child logger's context.
146
136
  */
147
- child: (ctx: Record<string, ContextValue>) => ILogger;
137
+ child: (ctx: Record<string, ContextValue | ContextExtension>) => Firo;
148
138
  /** Add a context entry by key and value. */
149
- addContext(key: string, value: ContextValue, opts?: ContextOptions): void;
139
+ addContext(key: string, value: ContextValue | ContextExtension): void;
150
140
  /** Add a context entry using the object form. */
151
141
  addContext(item: ContextItem): void;
152
142
  /** Remove a context entry by its key. */
153
143
  removeFromContext(key: string): void;
154
144
  /** Return the current context array attached to this logger instance. */
155
145
  getContext(): ContextItem[];
146
+ /** Check if a context key exists in the current logger instance. */
147
+ hasInContext(key: string): boolean;
156
148
  }
157
149
 
158
150
  /**
159
151
  * Creates a new logger instance with the specified configuration.
160
152
  *
161
153
  * @param config Optional configuration for log levels, mode, and transports.
162
- * @returns A fully configured `ILogger` instance.
154
+ * @returns A fully configured `Firo` instance.
163
155
  */
164
- declare const createLogger: (config?: LoggerConfig) => ILogger;
156
+ declare const createLogger: (config?: LoggerConfig) => Firo;
165
157
 
166
- export { type ContextItem, type ContextItemWithOptions, type ContextOptions, type ContextValue, type DevTransportConfig, FIRO_COLORS, type ILogger, type JsonTransportConfig, type LogLevel, type LogOptions, type LoggerConfig, type TransportFn, createDevTransport, createJsonTransport, createLogger };
158
+ export { type ContextExtension, type ContextItem, type ContextItemWithOptions, type ContextOptions, type ContextValue, type DevTransportConfig, FIRO_COLORS, type Firo, type LogLevel, type LogOptions, type LoggerConfig, type TransportFn, createDevTransport, createJsonTransport, createLogger };
package/dist/index.js CHANGED
@@ -73,7 +73,6 @@ var colorizeLevel = (level, text) => {
73
73
  // src/transports.ts
74
74
  import { inspect } from "util";
75
75
  import process from "process";
76
- import fs from "fs";
77
76
  var createDevTransport = (config = {}) => {
78
77
  const locale = config.locale ?? void 0;
79
78
  const timeOpts = {
@@ -88,9 +87,9 @@ var createDevTransport = (config = {}) => {
88
87
  const now = /* @__PURE__ */ new Date();
89
88
  const timestamp = now.toLocaleTimeString(locale, timeOpts);
90
89
  const contextStr = context.map((ctx) => {
91
- const key = ctx.options?.omitKey ? "" : `${ctx.key}:`;
90
+ const key = ctx.omitKey ? "" : `${ctx.key}:`;
92
91
  const content = `${key}${ctx.value}`;
93
- return colorize(`[${content}]`, ctx.options.colorIndex, ctx.options.color);
92
+ return colorize(`[${content}]`, ctx.colorIndex, ctx.color);
94
93
  }).join(" ");
95
94
  if (level === "error" && data === void 0) {
96
95
  const realError = wrapToError(msg);
@@ -168,40 +167,7 @@ var buildRecord = (level, context, msg, data) => {
168
167
  }
169
168
  return logRecord;
170
169
  };
171
- var createJsonTransport = (config = {}) => {
172
- const queue = [];
173
- const maxQueueSize = config.maxQueueSize ?? 1e3;
174
- let isDraining = false;
175
- const flush = () => {
176
- if (queue.length === 0 || isDraining) return;
177
- while (queue.length > 0) {
178
- const line = queue.shift();
179
- const ok = process.stdout.write(line);
180
- if (!ok) {
181
- isDraining = true;
182
- process.stdout.once("drain", () => {
183
- isDraining = false;
184
- flush();
185
- });
186
- return;
187
- }
188
- }
189
- };
190
- const flushSync = () => {
191
- while (queue.length > 0) {
192
- const line = queue.shift();
193
- if (line) {
194
- try {
195
- fs.writeSync(1, line);
196
- } catch {
197
- }
198
- }
199
- }
200
- };
201
- if (config.async) {
202
- process.on("beforeExit", flushSync);
203
- process.on("exit", flushSync);
204
- }
170
+ var createJsonTransport = () => {
205
171
  return (level, context, msg, data) => {
206
172
  const record = buildRecord(level, context, msg, data);
207
173
  let line;
@@ -220,15 +186,7 @@ var createJsonTransport = (config = {}) => {
220
186
  }) + "\n";
221
187
  }
222
188
  }
223
- if (!config.async) {
224
- process.stdout.write(line);
225
- return;
226
- }
227
- queue.push(line);
228
- if (queue.length > maxQueueSize) {
229
- queue.shift();
230
- }
231
- flush();
189
+ process.stdout.write(line);
232
190
  };
233
191
  };
234
192
 
@@ -236,11 +194,9 @@ var createJsonTransport = (config = {}) => {
236
194
  var fillContextItem = (item, useAllColors = false) => {
237
195
  return {
238
196
  ...item,
239
- options: {
240
- colorIndex: item.options && typeof item.options.colorIndex === "number" ? item.options.colorIndex : getColorIndex(item.key, useAllColors),
241
- color: item.options?.color,
242
- omitKey: item.options?.omitKey ?? false
243
- }
197
+ colorIndex: typeof item.colorIndex === "number" ? item.colorIndex : getColorIndex(item.key, useAllColors),
198
+ color: item.color,
199
+ omitKey: item.omitKey ?? false
244
200
  };
245
201
  };
246
202
  var createLoggerInternal = (config, parentContext) => {
@@ -251,12 +207,23 @@ var createLoggerInternal = (config, parentContext) => {
251
207
  return [...context2, ...invokeContext.map(fill)];
252
208
  };
253
209
  const context = [...parentContext.map(fill)];
254
- const transport = config.transport ?? (config.mode === "prod" ? createJsonTransport({ async: config.async }) : createDevTransport(config.devTransportConfig));
210
+ const transport = config.transport ?? (config.mode === "prod" ? createJsonTransport() : createDevTransport(config.devTransportConfig));
255
211
  const minLevelName = config.mode === "prod" ? config.minLevelInProd ?? config.minLevel : config.minLevelInDev ?? config.minLevel;
256
212
  const minLevel = LOG_LEVELS[minLevelName ?? "debug"];
257
213
  const getContext = () => context;
258
- const addContext = (key, value, options) => {
259
- let item = typeof key === "string" ? { key, value, options } : key;
214
+ const hasInContext = (key) => context.some((ctx) => ctx.key === key);
215
+ const addContext = (key, value) => {
216
+ let item;
217
+ if (typeof key === "string") {
218
+ if (value !== null && value !== void 0 && typeof value === "object") {
219
+ const { value: extValue, ...opts } = value;
220
+ item = { key, value: extValue, ...opts };
221
+ } else {
222
+ item = { key, value };
223
+ }
224
+ } else {
225
+ item = key;
226
+ }
260
227
  context.push(fill(item));
261
228
  };
262
229
  const removeKeyFromContext = (key) => {
@@ -264,11 +231,13 @@ var createLoggerInternal = (config, parentContext) => {
264
231
  if (index !== -1) context.splice(index, 1);
265
232
  };
266
233
  const child = (ctx) => {
267
- const newItems = Object.entries(ctx).map(([key, value]) => ({
268
- key,
269
- value,
270
- options: { colorIndex: getColorIndex(key, useAllColors) }
271
- }));
234
+ const newItems = Object.entries(ctx).map(([key, value]) => {
235
+ if (value !== null && value !== void 0 && typeof value === "object") {
236
+ const { value: extValue, ...opts } = value;
237
+ return { key, value: extValue, ...opts };
238
+ }
239
+ return { key, value, colorIndex: getColorIndex(key, useAllColors) };
240
+ });
272
241
  return createLoggerInternal({ transport, minLevel: minLevelName, useAllColors }, [...context, ...newItems]);
273
242
  };
274
243
  const debug = (msg, data, opts) => {
package/package.json CHANGED
@@ -1,16 +1,16 @@
1
1
  {
2
2
  "name": "@fend/firo",
3
- "version": "0.0.1",
3
+ "version": "0.0.2",
4
4
  "description": "Elegant logger for Node.js, Bun and Deno with brilliant DX.",
5
5
  "keywords": [
6
- "logger",
7
- "pino",
8
6
  "firo",
7
+ "logger",
9
8
  "fend",
10
9
  "dx",
11
10
  "structured-logging",
12
11
  "ndjson",
13
12
  "color",
13
+ "pino",
14
14
  "zero-dependencies"
15
15
  ],
16
16
  "author": "Alex Saft <fend25@gmail.com>",
@@ -39,8 +39,8 @@
39
39
  ],
40
40
  "scripts": {
41
41
  "build": "tsup",
42
- "publish:jsr": "pnpm dlx jsr publish",
43
42
  "test": "node --import tsx --test test/*.test.ts",
43
+ "check": "tsc --noEmit && node --import tsx --test test/*.test.ts",
44
44
  "typecheck": "tsc --noEmit",
45
45
  "demo": "tsx demo.ts"
46
46
  },