@reliverse/relinka 1.4.0 → 1.4.1

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/LICENSE CHANGED
@@ -1,6 +1,6 @@
1
1
  # MIT License
2
2
 
3
- Copyright (c) 2025 blefnk Nazar Kornienko
3
+ Copyright (c) 2025 Nazar Kornienko (blefnk), Reliverse
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
package/README.md CHANGED
@@ -2,11 +2,9 @@
2
2
 
3
3
  [💖 GitHub Sponsors](https://github.com/sponsors/blefnk) • [💬 Discord](https://discord.gg/Pb8uKbwpsJ) • [✨ Repo](https://github.com/reliverse/relinka-logger) • [📦 NPM](https://npmjs.com/@reliverse/relinka)
4
4
 
5
- **@reliverse/relinka** is a modern, minimal logging library that actually *feels* right. It's not just pretty output — it's a system: smart formatting, file-safe logging, runtime config support, and a `fatal` mode built for developers who care about correctness.
5
+ **@reliverse/relinka** is a modern, minimal logging library that actually *feels* right. It's not just pretty output — it's a system: smart formatting, file-safe logging, runtime config support, and a `fatal` mode built for developers who care about correctness. Whether you're building CLI tools, SDKs, or full-stack apps — Relinka helps you log with intention.
6
6
 
7
- Whether you're building CLI tools, SDKs, or full-stack apps — Relinka helps you log with intention.
8
-
9
- ## ✨ Why Relinka
7
+ ## Why Relinka
10
8
 
11
9
  - 🧙 Drop-in replacement for `node:console`, `consola`, or your internal logger
12
10
  - 💬 Supports: `info`, `warn`, `success`, `error`, `verbose`, `fatal`, `clear`
@@ -19,13 +17,13 @@ Whether you're building CLI tools, SDKs, or full-stack apps — Relinka helps yo
19
17
 
20
18
  ## Getting Started
21
19
 
22
- ### 2️⃣ Install
20
+ ### 1. Install
23
21
 
24
22
  ```bash
25
23
  bun add @reliverse/relinka
26
24
  ```
27
25
 
28
- ### 2️⃣ Use It
26
+ ### 2. Use It
29
27
 
30
28
  #### Direct Method (Recommended)
31
29
 
@@ -50,6 +48,7 @@ import {
50
48
  relinkaConfig,
51
49
  relinkaShutdown,
52
50
  } from "@reliverse/relinka";
51
+
53
52
  export async function main() {
54
53
  await relinkaAsync(
55
54
  // this automatically loads the config
@@ -74,17 +73,24 @@ export async function main() {
74
73
  // "We should never reach this code! This should never happen! (see <anonymous> line)",
75
74
  // ); // fatal level throws error and halts execution
76
75
  relinka("success", "Thanks for using Relinka!");
76
+
77
77
  // Make sure to shut down the logger at the end of your program
78
78
  // This is important to flush all buffers and close file handles
79
79
  await relinkaShutdown();
80
+
81
+ // Make sure to exit the program after your CLI is done
82
+ // It's not required for Bun-only apps, but recommended
83
+ // for other runtimes, esp. for Node.js (incl. `tsx`)
84
+ process.exit(0);
80
85
  }
86
+
81
87
  await main();
82
88
  ```
83
89
 
84
90
  #### [🔜 Soon] Singleton Method
85
91
 
86
92
  ```ts
87
- const logger = initRelinkaInstance();
93
+ const logger = initRelinkaInstance({/* per-project config */});
88
94
  logger("info", "Looks great!");
89
95
  ```
90
96
 
@@ -267,14 +273,18 @@ defineConfig({ ... }) // helper for relinka.config.ts
267
273
  - [x] Log rotation
268
274
  - [x] `fatal` type
269
275
  - [x] Runtime config
270
- - [ ] CLI interface (to manage logs, config, etc.)
276
+ - [ ] Implement per-project config redefinition
271
277
  - [ ] Plugin support (custom formatters, hooks)
278
+ - [ ] CLI interface (to manage logs, config, etc)
272
279
 
273
- ## Thanks
280
+ ## Shoutouts
274
281
 
275
- Relinka's DX design was inspired by this gem:
282
+ Relinka wouldn't exist without these gems:
276
283
 
277
- - [unjs/consola](https://github.com/unjs/consola)
284
+ - [unjs/consola](https://github.com/unjs/consola#readme)
285
+ - [winston](https://github.com/winstonjs/winston#readme)
286
+ - [pino](https://github.com/pinojs/pino#readme)
287
+ - [node-bunyan](https://github.com/trentm/node-bunyan#readme)
278
288
 
279
289
  ## License
280
290
 
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Compiles a format string with named or positional arguments to a standard format string.
3
+ * Uses @reliverse/repris's built-in compileFormat.
4
+ * @param {string} format - The format string containing named placeholders.
5
+ * @returns {string} The compiled format string with positional indices.
6
+ */
7
+ export declare function compile(format: string): string;
8
+ /**
9
+ * Formats a string using either an array or an object of arguments.
10
+ * Uses @reliverse/repris's built-in formatString.
11
+ * @param {string} format - The format string (named or positional).
12
+ * @param {any[] | Record<string, unknown>} args - Arguments to format into the string.
13
+ * @returns {string} The formatted string.
14
+ */
15
+ export declare function formatStr(format: string, args: any[] | Record<string, unknown>): string;
@@ -0,0 +1,13 @@
1
+ import { compileFormat, formatString } from "@reliverse/repris";
2
+ const _compileCache = {};
3
+ export function compile(format) {
4
+ if (_compileCache[format]) {
5
+ return _compileCache[format];
6
+ }
7
+ const compiled = compileFormat(format);
8
+ _compileCache[format] = compiled;
9
+ return compiled;
10
+ }
11
+ export function formatStr(format, args) {
12
+ return formatString(format, args);
13
+ }
@@ -4,11 +4,11 @@
4
4
  * @param {string} format - The format string containing the placeholders to replace.
5
5
  * @returns {string} The compiled format string with placeholders replaced by positional indices.
6
6
  */
7
- export declare function compileFormat(format: string): any;
7
+ export declare function compileFormatCompat(format: string): string;
8
8
  /**
9
9
  * Formats a string according to a custom format, using vsprintf for string formatting.
10
10
  * @param {string} format - The custom format string.
11
11
  * @param {any[]} argv - The arguments to format into the string.
12
12
  * @returns {string} The formatted string.
13
13
  */
14
- export declare function formatString(format: string, argv: any): string;
14
+ export declare function formatStringCompat(format: string, argv: any): string;
@@ -1,4 +1,4 @@
1
- import { vsprintf } from "printj";
1
+ import { vsprintf } from "@reliverse/repris";
2
2
  const FORMAT_ARGS = [
3
3
  ["additional", 5],
4
4
  ["message", 4],
@@ -7,7 +7,7 @@ const FORMAT_ARGS = [
7
7
  ["tag", 3]
8
8
  ];
9
9
  const _compileCache = {};
10
- export function compileFormat(format) {
10
+ export function compileFormatCompat(format) {
11
11
  if (_compileCache[format]) {
12
12
  return _compileCache[format];
13
13
  }
@@ -21,6 +21,6 @@ export function compileFormat(format) {
21
21
  _compileCache[format] = _format;
22
22
  return _format;
23
23
  }
24
- export function formatString(format, argv) {
25
- return vsprintf(compileFormat(format), argv);
24
+ export function formatStringCompat(format, argv) {
25
+ return vsprintf(compileFormatCompat(format), argv);
26
26
  }
package/bin/impl.d.ts CHANGED
@@ -108,9 +108,7 @@ export type LogFileInfo = {
108
108
  path: string;
109
109
  mtime: number;
110
110
  };
111
- /**
112
- * Promise that resolves once `reconf` loads and merges the config.
113
- */
111
+ /** Promise resolved once the user's config (if any) is merged. */
114
112
  export declare const relinkaConfig: Promise<RelinkaConfig>;
115
113
  /**
116
114
  * Shuts down the logger, flushing all buffers and clearing timers.
package/bin/impl.js CHANGED
@@ -4,7 +4,7 @@ import fs from "fs-extra";
4
4
  import os from "node:os";
5
5
  import path from "pathe";
6
6
  const ENABLE_DEV_DEBUG = false;
7
- const activeTimers = [];
7
+ const EXIT_GUARD = Symbol.for("relinka.exitHandlersRegistered");
8
8
  const DEFAULT_RELINKA_CONFIG = {
9
9
  verbose: false,
10
10
  dirs: {
@@ -90,13 +90,14 @@ function isUnicodeSupported() {
90
90
  let currentConfig = { ...DEFAULT_RELINKA_CONFIG };
91
91
  let isConfigInitialized = false;
92
92
  let resolveRelinkaConfig;
93
- export const relinkaConfig = new Promise((resolve) => {
94
- resolveRelinkaConfig = resolve;
93
+ export const relinkaConfig = new Promise((res) => {
94
+ resolveRelinkaConfig = res;
95
95
  });
96
96
  const logBuffers = /* @__PURE__ */ new Map();
97
+ const activeTimers = [];
98
+ let bufferFlushTimer = null;
97
99
  let lastCleanupTime = 0;
98
100
  let cleanupScheduled = false;
99
- let bufferFlushTimer = null;
100
101
  async function initializeConfig() {
101
102
  try {
102
103
  const result = await loadConfig({
@@ -110,55 +111,42 @@ async function initializeConfig() {
110
111
  });
111
112
  currentConfig = result.config;
112
113
  isConfigInitialized = true;
114
+ resolveRelinkaConfig?.(currentConfig);
115
+ resolveRelinkaConfig = void 0;
113
116
  if (ENABLE_DEV_DEBUG) {
114
117
  console.log("[Dev Debug] Config file used:", result.configFile);
115
118
  console.log("[Dev Debug] All merged layers:", result.layers);
116
119
  console.log("[Dev Debug] Final configuration:", currentConfig);
117
120
  }
118
- if (resolveRelinkaConfig) {
119
- resolveRelinkaConfig(currentConfig);
120
- resolveRelinkaConfig = void 0;
121
- }
122
- setupBufferFlushTimer();
123
121
  } catch (err) {
124
122
  console.error(
125
123
  `[Relinka Config Error] Failed to load config: ${err instanceof Error ? err.message : String(err)}`
126
124
  );
127
125
  currentConfig = { ...DEFAULT_RELINKA_CONFIG };
128
126
  isConfigInitialized = true;
129
- if (resolveRelinkaConfig) {
130
- resolveRelinkaConfig(currentConfig);
131
- resolveRelinkaConfig = void 0;
132
- }
127
+ resolveRelinkaConfig?.(currentConfig);
128
+ resolveRelinkaConfig = void 0;
129
+ } finally {
133
130
  setupBufferFlushTimer();
134
131
  }
135
132
  }
136
133
  function setupBufferFlushTimer() {
137
134
  if (bufferFlushTimer) {
138
135
  clearInterval(bufferFlushTimer);
139
- const index = activeTimers.indexOf(bufferFlushTimer);
140
- if (index !== -1) {
141
- activeTimers.splice(index, 1);
142
- }
136
+ activeTimers.splice(activeTimers.indexOf(bufferFlushTimer), 1);
143
137
  }
144
- const maxBufferAge = getMaxBufferAge(currentConfig);
145
- bufferFlushTimer = setInterval(
146
- () => {
147
- const now = Date.now();
148
- for (const [filePath, buffer] of logBuffers.entries()) {
149
- if (buffer.entries.length > 0 && now - buffer.lastFlush >= maxBufferAge) {
150
- flushLogBuffer(currentConfig, filePath).catch((err) => {
151
- console.error(
152
- `[Relinka Buffer Flush Error] Failed to flush aged buffer: ${err instanceof Error ? err.message : String(err)}`
153
- );
154
- });
155
- }
156
- }
157
- },
158
- Math.min(maxBufferAge / 2, 2500)
159
- // Check at least every 2.5 seconds or half the max age
160
- );
138
+ const maxAge = getMaxBufferAge(currentConfig);
139
+ bufferFlushTimer = setInterval(flushDueBuffers, Math.min(maxAge / 2, 2500));
140
+ bufferFlushTimer.unref();
161
141
  activeTimers.push(bufferFlushTimer);
142
+ function flushDueBuffers() {
143
+ const now = Date.now();
144
+ for (const [fp, buf] of logBuffers) {
145
+ if (buf.entries.length && now - buf.lastFlush >= maxAge) {
146
+ flushLogBuffer(currentConfig, fp).catch(console.error);
147
+ }
148
+ }
149
+ }
162
150
  }
163
151
  initializeConfig().catch((err) => {
164
152
  console.error(
@@ -352,10 +340,14 @@ async function deleteFiles(filePaths, config) {
352
340
  );
353
341
  }
354
342
  }
343
+ let sigintHandler;
344
+ let sigtermHandler;
355
345
  export async function relinkaShutdown() {
356
346
  activeTimers.forEach((timer) => clearTimeout(timer));
357
347
  activeTimers.length = 0;
358
348
  cleanupScheduled = false;
349
+ if (sigintHandler) process.off("SIGINT", sigintHandler);
350
+ if (sigtermHandler) process.off("SIGTERM", sigtermHandler);
359
351
  await flushAllLogBuffers();
360
352
  }
361
353
  async function cleanupOldLogFiles(config) {
@@ -380,6 +372,7 @@ async function cleanupOldLogFiles(config) {
380
372
  }
381
373
  });
382
374
  }, delay);
375
+ timer.unref();
383
376
  activeTimers.push(timer);
384
377
  }
385
378
  return;
@@ -502,6 +495,23 @@ export function casesHandled(unexpectedCase) {
502
495
  `A case was not handled for value: ${truncateString(String(unexpectedCase ?? "unknown"))}`
503
496
  );
504
497
  }
498
+ function registerExitHandlers() {
499
+ if (globalThis[EXIT_GUARD]) return;
500
+ globalThis[EXIT_GUARD] = true;
501
+ process.once("beforeExit", () => {
502
+ flushAllLogBuffers().catch(() => {
503
+ });
504
+ });
505
+ sigintHandler = () => {
506
+ flushAllLogBuffers().finally(() => process.exit(0));
507
+ };
508
+ sigtermHandler = () => {
509
+ flushAllLogBuffers().finally(() => process.exit(0));
510
+ };
511
+ process.once("SIGINT", sigintHandler);
512
+ process.once("SIGTERM", sigtermHandler);
513
+ }
514
+ registerExitHandlers();
505
515
  export function relinka(type, message, ...args) {
506
516
  if (type === "clear") {
507
517
  console.clear();
@@ -588,19 +598,3 @@ export async function relinkaAsync(type, message, ...args) {
588
598
  export function defineConfig(config) {
589
599
  return config;
590
600
  }
591
- process.on("beforeExit", () => {
592
- flushAllLogBuffers().catch(() => {
593
- });
594
- });
595
- process.on("SIGINT", () => {
596
- flushAllLogBuffers().catch(() => {
597
- }).finally(() => {
598
- process.exit(0);
599
- });
600
- });
601
- process.on("SIGTERM", () => {
602
- flushAllLogBuffers().catch(() => {
603
- }).finally(() => {
604
- process.exit(0);
605
- });
606
- });
package/bin/main.d.ts CHANGED
@@ -14,7 +14,7 @@ export { box } from "./deprecated/utils/box.js";
14
14
  export type { ColorName, ColorFunction, } from "./deprecated/utils/deprecatedColors.js";
15
15
  export { colors, getColor, colorize, } from "./deprecated/utils/deprecatedColors.js";
16
16
  export { parseStack } from "./deprecated/utils/error.js";
17
- export { compileFormat, formatString, } from "./deprecated/utils/format.js";
17
+ export { compileFormatCompat, formatStringCompat, } from "./deprecated/utils/format.js";
18
18
  export { isPlainObject, isLogObj } from "./deprecated/utils/log.js";
19
19
  export { writeStream } from "./deprecated/utils/stream.js";
20
20
  export { stripAnsi, centerAlign, rightAlign, leftAlign, align, } from "./deprecated/utils/string.js";
package/bin/main.js CHANGED
@@ -35,8 +35,8 @@ export {
35
35
  } from "./deprecated/utils/deprecatedColors.js";
36
36
  export { parseStack } from "./deprecated/utils/error.js";
37
37
  export {
38
- compileFormat,
39
- formatString
38
+ compileFormatCompat,
39
+ formatStringCompat
40
40
  } from "./deprecated/utils/format.js";
41
41
  export { isPlainObject, isLogObj } from "./deprecated/utils/log.js";
42
42
  export { writeStream } from "./deprecated/utils/stream.js";
package/package.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "dependencies": {
3
3
  "@reliverse/relico": "^1.1.0",
4
+ "@reliverse/repris": "^1.0.0",
4
5
  "@reliverse/runtime": "^1.0.3",
5
6
  "c12": "^3.0.3",
6
7
  "defu": "^6.1.4",
7
8
  "fs-extra": "^11.3.0",
8
9
  "pathe": "^2.0.3",
9
- "printj": "^1.3.1",
10
10
  "std-env": "^3.9.0",
11
11
  "string-width": "^7.2.0"
12
12
  },
@@ -15,7 +15,7 @@
15
15
  "license": "MIT",
16
16
  "name": "@reliverse/relinka",
17
17
  "type": "module",
18
- "version": "1.4.0",
18
+ "version": "1.4.1",
19
19
  "keywords": [
20
20
  "logger",
21
21
  "consola",
@@ -34,22 +34,23 @@
34
34
  ],
35
35
  "devDependencies": {
36
36
  "@biomejs/biome": "1.9.4",
37
- "@eslint/js": "^9.24.0",
37
+ "@eslint/js": "^9.25.1",
38
38
  "@reliverse/relidler-cfg": "^1.1.3",
39
39
  "@stylistic/eslint-plugin": "^4.2.0",
40
40
  "@total-typescript/ts-reset": "^0.6.1",
41
- "@types/bun": "^1.2.9",
41
+ "@types/bun": "^1.2.10",
42
42
  "@types/fs-extra": "^11.0.4",
43
- "@types/node": "^22.14.1",
43
+ "@types/node": "^22.15.2",
44
44
  "@types/sentencer": "^0.2.3",
45
- "eslint": "^9.24.0",
45
+ "eslint": "^9.25.1",
46
46
  "eslint-plugin-no-relative-import-paths": "^1.6.1",
47
- "eslint-plugin-perfectionist": "^4.11.0",
47
+ "eslint-plugin-perfectionist": "^4.12.3",
48
48
  "jiti": "^2.4.2",
49
- "knip": "^5.50.2",
49
+ "knip": "^5.50.5",
50
50
  "sentencer": "^0.2.1",
51
+ "tsx": "^4.19.3",
51
52
  "typescript": "^5.8.3",
52
- "typescript-eslint": "^8.29.1"
53
+ "typescript-eslint": "^8.31.0"
53
54
  },
54
55
  "exports": {
55
56
  ".": "./bin/main.js"