@kikiutils/shared 20.0.1 → 20.1.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/dist/buffer.js.map +1 -1
- package/dist/classes/enhanced-date.d.ts +2 -2
- package/dist/classes/enhanced-date.d.ts.map +1 -1
- package/dist/classes/enhanced-date.js.map +1 -1
- package/dist/classes/path.d.ts +1 -1
- package/dist/classes/path.d.ts.map +1 -1
- package/dist/classes/path.js +30 -30
- package/dist/classes/path.js.map +1 -1
- package/dist/enum.d.ts +4 -3
- package/dist/enum.d.ts.map +1 -1
- package/dist/enum.js +3 -2
- package/dist/enum.js.map +1 -1
- package/dist/event-awaiter.d.ts.map +1 -1
- package/dist/event-awaiter.js +7 -6
- package/dist/event-awaiter.js.map +1 -1
- package/dist/general.d.ts +3 -2
- package/dist/general.d.ts.map +1 -1
- package/dist/general.js.map +1 -1
- package/dist/logger/_internals/formatter.d.ts +12 -4
- package/dist/logger/_internals/formatter.d.ts.map +1 -1
- package/dist/logger/_internals/formatter.js +12 -4
- package/dist/logger/_internals/formatter.js.map +1 -1
- package/dist/logger/_internals/logger.d.ts +6 -2
- package/dist/logger/_internals/logger.d.ts.map +1 -1
- package/dist/logger/_internals/logger.js +6 -3
- package/dist/logger/_internals/logger.js.map +1 -1
- package/dist/logger/node/_internals/file-writer.d.ts +3 -1
- package/dist/logger/node/_internals/file-writer.d.ts.map +1 -1
- package/dist/logger/node/_internals/file-writer.js +13 -9
- package/dist/logger/node/_internals/file-writer.js.map +1 -1
- package/dist/logger/node/_internals/formatter.d.ts +3 -1
- package/dist/logger/node/_internals/formatter.d.ts.map +1 -1
- package/dist/logger/node/_internals/formatter.js +3 -1
- package/dist/logger/node/_internals/formatter.js.map +1 -1
- package/dist/logger/node/index.js +11 -9
- package/dist/logger/node/index.js.map +1 -1
- package/dist/logger/node/types.d.ts +36 -12
- package/dist/logger/node/types.d.ts.map +1 -1
- package/dist/logger/types.d.ts +44 -19
- package/dist/logger/types.d.ts.map +1 -1
- package/dist/redis/script-runner.d.ts +1 -1
- package/dist/redis/script-runner.d.ts.map +1 -1
- package/dist/redis/script-runner.js.map +1 -1
- package/dist/storages/redis/keyed-store.d.ts.map +1 -1
- package/dist/storages/redis/keyed-store.js +10 -10
- package/dist/storages/redis/keyed-store.js.map +1 -1
- package/dist/storages/redis/msgpack.js +2 -2
- package/dist/storages/redis/msgpack.js.map +1 -1
- package/dist/time.d.ts.map +1 -1
- package/dist/time.js +4 -4
- package/dist/time.js.map +1 -1
- package/dist/types/filtered-key-path.d.ts +2 -2
- package/dist/types/filtered-key-path.d.ts.map +1 -1
- package/dist/types/index.d.ts +7 -0
- package/dist/types/index.d.ts.map +1 -1
- package/dist/url.d.ts.map +1 -1
- package/dist/url.js.map +1 -1
- package/package.json +15 -11
- package/src/buffer.ts +0 -1
- package/src/classes/enhanced-date.ts +2 -2
- package/src/classes/path.ts +31 -31
- package/src/enum.ts +11 -5
- package/src/event-awaiter.ts +9 -7
- package/src/general.ts +5 -3
- package/src/logger/_internals/formatter.ts +12 -4
- package/src/logger/_internals/logger.ts +13 -4
- package/src/logger/node/_internals/file-writer.ts +13 -9
- package/src/logger/node/_internals/formatter.ts +3 -1
- package/src/logger/node/index.ts +13 -10
- package/src/logger/node/types.ts +36 -12
- package/src/logger/types.ts +45 -19
- package/src/redis/script-runner.ts +1 -1
- package/src/storages/redis/keyed-store.ts +10 -10
- package/src/storages/redis/msgpack.ts +2 -2
- package/src/time.ts +4 -4
- package/src/types/filtered-key-path.ts +2 -2
- package/src/types/index.ts +6 -0
- package/src/url.ts +0 -1
package/dist/general.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"general.d.ts","names":[],"sources":["../src/general.ts"],"mappings":"
|
|
1
|
+
{"version":3,"file":"general.d.ts","names":[],"sources":["../src/general.ts"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;wBAmCgB,kBAAkB,GAAG,OAAO,UAAU,KAAK;wBAC3C,kBAAkB,GAAG,GAAG,OAAO,UAAU,IAAI,cAAc,IAAI,IAAI,YAAY"}
|
package/dist/general.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"general.js","names":[],"sources":["../src/general.ts"],"sourcesContent":["/* eslint-disable jsdoc/check-param-names */\n\n/**\n * Returns the first array element or the input value, with a nullish fallback.\n *\n * @remarks\n * An empty array, `null`, or `undefined` selects the fallback; omission of a fallback yields `undefined`.\n * The input and selected values are not copied or changed.\n *\n * @typeParam T - The input value or array element type.\n * @typeParam D - The fallback type when the fallback overload is used.\n *\n * @param value - A single value or an array of values.\n * @param defaultValue - The fallback returned when the selected value is nullish.\n *\n * @returns The selected non-nullish value, or the fallback without copying either value.\n *\n * @example\n *\n * ```ts\n * import { extractFirstValue } from '@kikiutils/shared/general';\n *\n * console.log(extractFirstValue([\n * 1,\n * 2,\n * 3,\n * ])); // => 1\n *\n * console.log(extractFirstValue('hello')); // => 'hello'\n * console.log(extractFirstValue([], 'default')); // => 'default'\n * console.log(extractFirstValue(undefined, 'fallback')); // => 'fallback'\n * ```\n */\nexport function extractFirstValue<T>(value: T
|
|
1
|
+
{"version":3,"file":"general.js","names":[],"sources":["../src/general.ts"],"sourcesContent":["import type { Arrayable } from 'type-fest';\n\n/* eslint-disable jsdoc/check-param-names */\n\n/**\n * Returns the first array element or the input value, with a nullish fallback.\n *\n * @remarks\n * An empty array, `null`, or `undefined` selects the fallback; omission of a fallback yields `undefined`.\n * The input and selected values are not copied or changed.\n *\n * @typeParam T - The input value or array element type.\n * @typeParam D - The fallback type when the fallback overload is used.\n *\n * @param value - A single value or an array of values.\n * @param defaultValue - The fallback returned when the selected value is nullish.\n *\n * @returns The selected non-nullish value, or the fallback without copying either value.\n *\n * @example\n *\n * ```ts\n * import { extractFirstValue } from '@kikiutils/shared/general';\n *\n * console.log(extractFirstValue([\n * 1,\n * 2,\n * 3,\n * ])); // => 1\n *\n * console.log(extractFirstValue('hello')); // => 'hello'\n * console.log(extractFirstValue([], 'default')); // => 'default'\n * console.log(extractFirstValue(undefined, 'fallback')); // => 'fallback'\n * ```\n */\nexport function extractFirstValue<T>(value: Arrayable<T>): T | undefined;\nexport function extractFirstValue<T, D>(value: Arrayable<T>, defaultValue: D): D | NonNullable<T>;\nexport function extractFirstValue<T, D>(value: Arrayable<T>, defaultValue?: D) {\n return (Array.isArray(value) ? value[0] : value) ?? defaultValue;\n}\n\n/* eslint-enable jsdoc/check-param-names */\n"],"mappings":";AAqCA,SAAgB,kBAAwB,OAAqB,cAAkB;CAC3E,QAAQ,MAAM,QAAQ,KAAK,IAAI,MAAM,KAAK,UAAU;AACxD"}
|
|
@@ -1,11 +1,19 @@
|
|
|
1
1
|
import { LoggerRecord } from "./types.js";
|
|
2
|
-
/**
|
|
2
|
+
/**
|
|
3
|
+
* Native console formatting keeps user strings literal and objects expandable.
|
|
4
|
+
*/
|
|
3
5
|
export declare function formatLoggerConsoleArgs(record: LoggerRecord): unknown[];
|
|
4
|
-
/**
|
|
6
|
+
/**
|
|
7
|
+
* Keeps data arguments intact so browser developer tools can expand objects.
|
|
8
|
+
*/
|
|
5
9
|
export declare function formatLoggerParts(record: LoggerRecord, colors?: boolean): unknown[];
|
|
6
|
-
/**
|
|
10
|
+
/**
|
|
11
|
+
* Fixed local-time representation, deliberately without a per-second cache.
|
|
12
|
+
*/
|
|
7
13
|
export declare function formatLoggerTime(date: Date): string;
|
|
8
|
-
/**
|
|
14
|
+
/**
|
|
15
|
+
* Use native terminal detection; redirected streams still honor FORCE_COLOR.
|
|
16
|
+
*/
|
|
9
17
|
export declare function supportsLoggerColor(target: {
|
|
10
18
|
hasColors?: () => boolean;
|
|
11
19
|
isTTY?: boolean;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"formatter.d.ts","names":[],"sources":["../../../src/logger/_internals/formatter.ts"],"mappings":"
|
|
1
|
+
{"version":3,"file":"formatter.d.ts","names":[],"sources":["../../../src/logger/_internals/formatter.ts"],"mappings":";;;;wBAWgB,wBAAwB,QAAQ;;;;wBAmChC,kBAAkB,QAAQ,cAAc;;;;wBAcxC,iBAAiB,MAAM;;;;wBAUvB,oBAAoB;EAAU;EAA2B"}
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { getLoggerStreamName, loggerMethods } from "./levels.js";
|
|
2
2
|
// src/logger/_internals/formatter.ts
|
|
3
|
-
/**
|
|
3
|
+
/**
|
|
4
|
+
* Native console formatting keeps user strings literal and objects expandable.
|
|
5
|
+
*/
|
|
4
6
|
function formatLoggerConsoleArgs(record) {
|
|
5
7
|
if (typeof process !== "undefined" && process.versions?.node) {
|
|
6
8
|
const target = process[getLoggerStreamName(record.type)];
|
|
@@ -23,7 +25,9 @@ function formatLoggerConsoleArgs(record) {
|
|
|
23
25
|
if (record.suffix) decorate(record.suffix, "#0891b2");
|
|
24
26
|
return [format.join(" "), ...args];
|
|
25
27
|
}
|
|
26
|
-
/**
|
|
28
|
+
/**
|
|
29
|
+
* Keeps data arguments intact so browser developer tools can expand objects.
|
|
30
|
+
*/
|
|
27
31
|
function formatLoggerParts(record, colors = false) {
|
|
28
32
|
const parts = [];
|
|
29
33
|
const paint = (text, color) => colors ? `\u001B[${color}m${text}\u001B[0m` : text;
|
|
@@ -34,12 +38,16 @@ function formatLoggerParts(record, colors = false) {
|
|
|
34
38
|
if (record.suffix) parts.push(paint(record.suffix, "36"));
|
|
35
39
|
return parts;
|
|
36
40
|
}
|
|
37
|
-
/**
|
|
41
|
+
/**
|
|
42
|
+
* Fixed local-time representation, deliberately without a per-second cache.
|
|
43
|
+
*/
|
|
38
44
|
function formatLoggerTime(date) {
|
|
39
45
|
const pad = (value, width = 2) => String(value).padStart(width, "0");
|
|
40
46
|
return `[${`${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())}`} ${`${pad(date.getHours())}:${pad(date.getMinutes())}:${pad(date.getSeconds())}`}.${pad(date.getMilliseconds(), 3)}]`;
|
|
41
47
|
}
|
|
42
|
-
/**
|
|
48
|
+
/**
|
|
49
|
+
* Use native terminal detection; redirected streams still honor FORCE_COLOR.
|
|
50
|
+
*/
|
|
43
51
|
function supportsLoggerColor(target) {
|
|
44
52
|
if (target.hasColors) return target.hasColors();
|
|
45
53
|
const forced = process.env.FORCE_COLOR;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"formatter.js","names":[],"sources":["../../../src/logger/_internals/formatter.ts"],"sourcesContent":["import {\n getLoggerStreamName,\n loggerMethods,\n} from './levels';\nimport type { LoggerRecord } from './types';\n\n// Functions\n\n
|
|
1
|
+
{"version":3,"file":"formatter.js","names":[],"sources":["../../../src/logger/_internals/formatter.ts"],"sourcesContent":["import {\n getLoggerStreamName,\n loggerMethods,\n} from './levels';\nimport type { LoggerRecord } from './types';\n\n// Functions\n\n/**\n * Native console formatting keeps user strings literal and objects expandable.\n */\nexport function formatLoggerConsoleArgs(record: LoggerRecord): unknown[] {\n if (typeof process !== 'undefined' && process.versions?.node) {\n const target = process[getLoggerStreamName(record.type)];\n const parts = formatLoggerParts(record, supportsLoggerColor(target));\n return [\n parts.map((part) => typeof part === 'string' ? '%s' : '%O').join(' '),\n ...parts,\n ];\n }\n\n const format: string[] = [];\n const args: unknown[] = [];\n const decorate = (text: string, color: string) => {\n format.push('%c%s%c');\n args.push(`color: ${color}`, text, '');\n };\n\n if (record.icons) decorate(loggerMethods[record.type].icon, loggerMethods[record.type].cssColor);\n if (record.timestamp) decorate(formatLoggerTime(record.date), '#808080');\n if (record.prefix) decorate(record.prefix, '#0891b2');\n for (const arg of record.args) {\n format.push(typeof arg === 'string' ? '%s' : '%o');\n args.push(arg);\n }\n\n if (record.suffix) decorate(record.suffix, '#0891b2');\n return [\n format.join(' '),\n ...args,\n ];\n}\n\n/**\n * Keeps data arguments intact so browser developer tools can expand objects.\n */\nexport function formatLoggerParts(record: LoggerRecord, colors = false): unknown[] {\n const parts: unknown[] = [];\n const paint = (text: string, color: string) => colors ? `\\u001B[${color}m${text}\\u001B[0m` : text;\n if (record.icons) parts.push(paint(loggerMethods[record.type].icon, loggerMethods[record.type].ansiColor));\n if (record.timestamp) parts.push(paint(formatLoggerTime(record.date), '90'));\n if (record.prefix) parts.push(paint(record.prefix, '36'));\n parts.push(...record.args);\n if (record.suffix) parts.push(paint(record.suffix, '36'));\n return parts;\n}\n\n/**\n * Fixed local-time representation, deliberately without a per-second cache.\n */\nexport function formatLoggerTime(date: Date): string {\n const pad = (value: number, width = 2) => String(value).padStart(width, '0');\n const day = `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())}`;\n const time = `${pad(date.getHours())}:${pad(date.getMinutes())}:${pad(date.getSeconds())}`;\n return `[${day} ${time}.${pad(date.getMilliseconds(), 3)}]`;\n}\n\n/**\n * Use native terminal detection; redirected streams still honor FORCE_COLOR.\n */\nexport function supportsLoggerColor(target: { hasColors?: () => boolean; isTTY?: boolean }): boolean {\n if (target.hasColors) return target.hasColors();\n const forced = process.env.FORCE_COLOR;\n if (forced !== undefined) {\n return [\n '',\n '1',\n '2',\n '3',\n 'true',\n ].includes(forced);\n }\n\n return Boolean(target.isTTY)\n && !process.env.NO_COLOR\n && !process.env.NODE_DISABLE_COLORS\n && process.env.TERM !== 'dumb';\n}\n"],"mappings":";;;;;AAWA,SAAgB,wBAAwB,QAAiC;CACrE,IAAI,OAAO,YAAY,eAAe,QAAQ,UAAU,MAAM;EAC1D,MAAM,SAAS,QAAQ,oBAAoB,OAAO,IAAI;EACtD,MAAM,QAAQ,kBAAkB,QAAQ,oBAAoB,MAAM,CAAC;EACnE,OAAO,CACH,MAAM,KAAK,SAAS,OAAO,SAAS,WAAW,OAAO,IAAI,CAAC,CAAC,KAAK,GAAG,GACpE,GAAG,KACP;CACJ;CAEA,MAAM,SAAmB,CAAC;CAC1B,MAAM,OAAkB,CAAC;CACzB,MAAM,YAAY,MAAc,UAAkB;EAC9C,OAAO,KAAK,QAAQ;EACpB,KAAK,KAAK,UAAU,SAAS,MAAM,EAAE;CACzC;CAEA,IAAI,OAAO,OAAO,SAAS,cAAc,OAAO,KAAK,CAAC,MAAM,cAAc,OAAO,KAAK,CAAC,QAAQ;CAC/F,IAAI,OAAO,WAAW,SAAS,iBAAiB,OAAO,IAAI,GAAG,SAAS;CACvE,IAAI,OAAO,QAAQ,SAAS,OAAO,QAAQ,SAAS;CACpD,KAAK,MAAM,OAAO,OAAO,MAAM;EAC3B,OAAO,KAAK,OAAO,QAAQ,WAAW,OAAO,IAAI;EACjD,KAAK,KAAK,GAAG;CACjB;CAEA,IAAI,OAAO,QAAQ,SAAS,OAAO,QAAQ,SAAS;CACpD,OAAO,CACH,OAAO,KAAK,GAAG,GACf,GAAG,IACP;AACJ;;;;AAKA,SAAgB,kBAAkB,QAAsB,SAAS,OAAkB;CAC/E,MAAM,QAAmB,CAAC;CAC1B,MAAM,SAAS,MAAc,UAAkB,SAAS,UAAU,MAAM,GAAG,KAAK,aAAa;CAC7F,IAAI,OAAO,OAAO,MAAM,KAAK,MAAM,cAAc,OAAO,KAAK,CAAC,MAAM,cAAc,OAAO,KAAK,CAAC,SAAS,CAAC;CACzG,IAAI,OAAO,WAAW,MAAM,KAAK,MAAM,iBAAiB,OAAO,IAAI,GAAG,IAAI,CAAC;CAC3E,IAAI,OAAO,QAAQ,MAAM,KAAK,MAAM,OAAO,QAAQ,IAAI,CAAC;CACxD,MAAM,KAAK,GAAG,OAAO,IAAI;CACzB,IAAI,OAAO,QAAQ,MAAM,KAAK,MAAM,OAAO,QAAQ,IAAI,CAAC;CACxD,OAAO;AACX;;;;AAKA,SAAgB,iBAAiB,MAAoB;CACjD,MAAM,OAAO,OAAe,QAAQ,MAAM,OAAO,KAAK,CAAC,CAAC,SAAS,OAAO,GAAG;CAG3E,OAAO,IAAI,GAFI,KAAK,YAAY,EAAE,GAAG,IAAI,KAAK,SAAS,IAAI,CAAC,EAAE,GAAG,IAAI,KAAK,QAAQ,CAAC,IAEpE,GAAG,GADF,IAAI,KAAK,SAAS,CAAC,EAAE,GAAG,IAAI,KAAK,WAAW,CAAC,EAAE,GAAG,IAAI,KAAK,WAAW,CAAC,IAChE,GAAG,IAAI,KAAK,gBAAgB,GAAG,CAAC,EAAE;AAC7D;;;;AAKA,SAAgB,oBAAoB,QAAiE;CACjG,IAAI,OAAO,WAAW,OAAO,OAAO,UAAU;CAC9C,MAAM,SAAS,QAAQ,IAAI;CAC3B,IAAI,WAAW,KAAA,GACX,OAAO;EACH;EACA;EACA;EACA;EACA;CACJ,CAAC,CAAC,SAAS,MAAM;CAGrB,OAAO,QAAQ,OAAO,KAAK,KACtB,CAAC,QAAQ,IAAI,YACb,CAAC,QAAQ,IAAI,uBACb,QAAQ,IAAI,SAAS;AAC9B"}
|
|
@@ -1,11 +1,15 @@
|
|
|
1
1
|
import { Logger, LoggerDiagnostic, LoggerOptions } from "../types.js";
|
|
2
2
|
import { LoggerRecord } from "./types.js";
|
|
3
3
|
interface LoggerHooks {
|
|
4
|
-
/**
|
|
4
|
+
/**
|
|
5
|
+
* A non-throwing reporter created by createDiagnosticReporter.
|
|
6
|
+
*/
|
|
5
7
|
report: (event: LoggerDiagnostic) => void;
|
|
6
8
|
write: (record: LoggerRecord) => Promise<void> | void;
|
|
7
9
|
}
|
|
8
|
-
/**
|
|
10
|
+
/**
|
|
11
|
+
* A non-recursive, rate-limited diagnostic boundary shared by server and browser adapters.
|
|
12
|
+
*/
|
|
9
13
|
export declare function createDiagnosticReporter(onError?: (event: Readonly<LoggerDiagnostic>) => void, fallback?: (message: string) => void): (event: LoggerDiagnostic) => void;
|
|
10
14
|
export declare function createLoggerCore(options: LoggerOptions, hooks: LoggerHooks): Readonly<Logger>;
|
|
11
15
|
export {};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"logger.d.ts","names":[],"sources":["../../../src/logger/_internals/logger.ts"],"mappings":";;
|
|
1
|
+
{"version":3,"file":"logger.d.ts","names":[],"sources":["../../../src/logger/_internals/logger.ts"],"mappings":";;UAgBU;;;;EAIN,SAAS,OAAO;EAChB,QAAQ,QAAQ,iBAAiB;;;;;wBAoBrB,yBACZ,WAAW,OAAO,SAAS,4BAC3B,YAAY,4BACZ,OAAO;wBAkEK,iBAAiB,SAAS,eAAe,OAAO,cAAc,SAAS"}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { loggerMethods } from "./levels.js";
|
|
2
|
+
import { objectHasOwn, objectKeys } from "ts-extras";
|
|
2
3
|
// src/logger/_internals/logger.ts
|
|
3
4
|
const booleanPresentationKeys = [
|
|
4
5
|
"console",
|
|
@@ -6,7 +7,9 @@ const booleanPresentationKeys = [
|
|
|
6
7
|
"timestamp"
|
|
7
8
|
];
|
|
8
9
|
const stringPresentationKeys = ["prefix", "suffix"];
|
|
9
|
-
/**
|
|
10
|
+
/**
|
|
11
|
+
* A non-recursive, rate-limited diagnostic boundary shared by server and browser adapters.
|
|
12
|
+
*/
|
|
10
13
|
function createDiagnosticReporter(onError, fallback) {
|
|
11
14
|
let reporting = false;
|
|
12
15
|
let callbackPending = false;
|
|
@@ -106,7 +109,7 @@ function createLoggerCore(options, hooks) {
|
|
|
106
109
|
else refreshLevel();
|
|
107
110
|
}
|
|
108
111
|
};
|
|
109
|
-
for (const type of
|
|
112
|
+
for (const type of objectKeys(loggerMethods)) logger[type] = async (...args) => {
|
|
110
113
|
try {
|
|
111
114
|
refreshLevel();
|
|
112
115
|
if (loggerMethods[type].severity < getLevelSeverity(currentLevel)) return;
|
|
@@ -130,7 +133,7 @@ function getLevelSeverity(level) {
|
|
|
130
133
|
return level === "silent" ? Number.POSITIVE_INFINITY : loggerMethods[level].severity;
|
|
131
134
|
}
|
|
132
135
|
function isLevel(value) {
|
|
133
|
-
return typeof value === "string" && (value === "silent" || value !== "success" &&
|
|
136
|
+
return typeof value === "string" && (value === "silent" || value !== "success" && objectHasOwn(loggerMethods, value));
|
|
134
137
|
}
|
|
135
138
|
function readEnvironmentLevel() {
|
|
136
139
|
return globalThis.process?.env?.LOG_LEVEL;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"logger.js","names":[],"sources":["../../../src/logger/_internals/logger.ts"],"sourcesContent":["import type {\n Logger,\n LoggerDiagnostic,\n LoggerLevel,\n LoggerMethod,\n LoggerOptions,\n} from '../types';\n\nimport { loggerMethods } from './levels';\nimport type { LoggerRecord } from './types';\n\ninterface LoggerHooks {\n /** A non-throwing reporter created by createDiagnosticReporter. */\n report: (event: LoggerDiagnostic) => void;\n write: (record: LoggerRecord) => Promise<void> | void;\n}\n\n// Constants/Variables\nconst booleanPresentationKeys = [\n 'console',\n 'icons',\n 'timestamp',\n] as const;\n\nconst stringPresentationKeys = [\n 'prefix',\n 'suffix',\n] as const;\n\n// Functions\n\n/** A non-recursive, rate-limited diagnostic boundary shared by server and browser adapters. */\nexport function createDiagnosticReporter(\n onError?: (event: Readonly<LoggerDiagnostic>) => void,\n fallback?: (message: string) => void,\n): (event: LoggerDiagnostic) => void {\n let reporting = false;\n let callbackPending = false;\n const stateByType = new Map<LoggerDiagnostic['type'], { lastReportedAt: number; suppressedCount: number }>();\n\n return (event) => {\n if (reporting) return;\n try {\n const now = Date.now();\n const state = stateByType.get(event.type);\n if (state && now - state.lastReportedAt < 1000) {\n state.suppressedCount++;\n return;\n }\n\n reporting = true;\n const suppressedCount = state?.suppressedCount ?? 0;\n if (suppressedCount) {\n event = {\n ...event,\n suppressedCount,\n };\n }\n\n stateByType.set(\n event.type,\n {\n lastReportedAt: now,\n suppressedCount: 0,\n },\n );\n\n try {\n const summary = suppressedCount ? ` (${suppressedCount} similar diagnostics suppressed)` : '';\n fallback?.(`[logger] ${event.message}${summary}\\n`);\n } catch {\n // Diagnostics must never re-enter the normal logging pipeline.\n }\n\n if (!callbackPending) {\n try {\n callbackPending = true;\n const result: unknown = onError?.(Object.freeze({ ...event }));\n if (result !== null && (typeof result === 'object' || typeof result === 'function')) {\n void Promise.resolve(result).then(\n () => {\n callbackPending = false;\n },\n () => {\n callbackPending = false;\n },\n );\n } else {\n callbackPending = false;\n }\n } catch {\n callbackPending = false;\n // User diagnostic callbacks have the same non-throwing boundary.\n }\n }\n } catch { /* Diagnostics must never escape into the logging pipeline. */ } finally {\n reporting = false;\n }\n };\n}\n\nexport function createLoggerCore(options: LoggerOptions, hooks: LoggerHooks): Readonly<Logger> {\n validateLevel(options.level);\n validatePresentation(options);\n if (options.onError !== undefined && typeof options.onError !== 'function') {\n throw new TypeError('Logger onError must be a function');\n }\n\n const presentation = {\n console: options.console ?? true,\n icons: options.icons ?? true,\n prefix: options.prefix ?? '',\n suffix: options.suffix ?? '',\n timestamp: options.timestamp ?? true,\n };\n\n const { report } = hooks;\n\n let override = options.level === 'env' ? undefined : options.level;\n let previousEnvironment: string | undefined;\n let currentLevel: LoggerLevel = override ?? 'info';\n let environmentRead = false;\n\n function refreshLevel() {\n if (override !== undefined) return;\n const input = readEnvironmentLevel();\n if (environmentRead && input === previousEnvironment) return;\n environmentRead = true;\n previousEnvironment = input;\n const normalized = input?.trim().toLowerCase() || undefined;\n currentLevel = isLevel(normalized) ? normalized : 'info';\n if (normalized !== undefined && !isLevel(normalized)) {\n report({\n message: 'Invalid LOG_LEVEL; using info',\n type: 'invalid-level',\n });\n }\n }\n\n const logger = {\n configure(configuration) {\n validatePresentation(configuration);\n for (const name of booleanPresentationKeys) {\n const value = configuration[name];\n if (value !== undefined) presentation[name] = value;\n }\n\n for (const name of stringPresentationKeys) {\n const value = configuration[name];\n if (value !== undefined) presentation[name] = value;\n }\n },\n isLevelEnabled(level) {\n if (level !== 'success' && !isLevel(level)) throw new TypeError('Invalid logger level');\n refreshLevel();\n return level !== 'silent' && getLevelSeverity(level) >= getLevelSeverity(currentLevel);\n },\n setLevel(level) {\n validateLevel(level);\n if (level === undefined) throw new TypeError('Invalid logger level');\n override = level === 'env' ? undefined : level;\n environmentRead = false;\n if (override !== undefined) {\n currentLevel = override;\n } else {\n refreshLevel();\n }\n },\n } as Logger;\n\n for (const type of Object.keys(loggerMethods) as LoggerMethod[]) {\n logger[type] = async (...args: unknown[]) => {\n try {\n refreshLevel();\n if (loggerMethods[type].severity < getLevelSeverity(currentLevel)) return;\n await hooks.write({\n ...presentation,\n args,\n date: new Date(),\n type,\n });\n } catch (error) {\n report({\n error,\n message: 'Logger output failed',\n type: 'format',\n });\n }\n };\n }\n\n return Object.freeze(logger);\n}\n\nfunction getLevelSeverity(level: LoggerLevel | LoggerMethod): number {\n return level === 'silent' ? Number.POSITIVE_INFINITY : loggerMethods[level].severity;\n}\n\nfunction isLevel(value: unknown): value is LoggerLevel {\n return typeof value === 'string'\n && (value === 'silent' || (value !== 'success' && Object.hasOwn(loggerMethods, value)));\n}\n\nfunction readEnvironmentLevel(): string | undefined {\n return (globalThis as { process?: { env?: { LOG_LEVEL?: string } } }).process?.env?.LOG_LEVEL;\n}\n\nfunction validateLevel(value: unknown): asserts value is 'env' | LoggerLevel | undefined {\n if (value !== undefined && value !== 'env' && !isLevel(value)) throw new TypeError('Invalid logger level');\n}\n\nfunction validatePresentation(options: Pick<LoggerOptions, 'console' | 'icons' | 'prefix' | 'suffix' | 'timestamp'>) {\n for (const name of booleanPresentationKeys) {\n if (options[name] !== undefined && typeof options[name] !== 'boolean') {\n throw new TypeError(`Logger ${name} must be a boolean`);\n }\n }\n\n for (const name of stringPresentationKeys) {\n if (options[name] !== undefined && typeof options[name] !== 'string') {\n throw new TypeError(`Logger ${name} must be a string`);\n }\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"logger.js","names":[],"sources":["../../../src/logger/_internals/logger.ts"],"sourcesContent":["import {\n objectHasOwn,\n objectKeys,\n} from 'ts-extras';\n\nimport type {\n Logger,\n LoggerDiagnostic,\n LoggerLevel,\n LoggerMethod,\n LoggerOptions,\n} from '../types';\n\nimport { loggerMethods } from './levels';\nimport type { LoggerRecord } from './types';\n\ninterface LoggerHooks {\n /**\n * A non-throwing reporter created by createDiagnosticReporter.\n */\n report: (event: LoggerDiagnostic) => void;\n write: (record: LoggerRecord) => Promise<void> | void;\n}\n\n// Constants/Variables\nconst booleanPresentationKeys = [\n 'console',\n 'icons',\n 'timestamp',\n] as const;\n\nconst stringPresentationKeys = [\n 'prefix',\n 'suffix',\n] as const;\n\n// Functions\n\n/**\n * A non-recursive, rate-limited diagnostic boundary shared by server and browser adapters.\n */\nexport function createDiagnosticReporter(\n onError?: (event: Readonly<LoggerDiagnostic>) => void,\n fallback?: (message: string) => void,\n): (event: LoggerDiagnostic) => void {\n let reporting = false;\n let callbackPending = false;\n const stateByType = new Map<LoggerDiagnostic['type'], { lastReportedAt: number; suppressedCount: number }>();\n\n return (event) => {\n if (reporting) return;\n try {\n const now = Date.now();\n const state = stateByType.get(event.type);\n if (state && now - state.lastReportedAt < 1000) {\n state.suppressedCount++;\n return;\n }\n\n reporting = true;\n const suppressedCount = state?.suppressedCount ?? 0;\n if (suppressedCount) {\n event = {\n ...event,\n suppressedCount,\n };\n }\n\n stateByType.set(\n event.type,\n {\n lastReportedAt: now,\n suppressedCount: 0,\n },\n );\n\n try {\n const summary = suppressedCount ? ` (${suppressedCount} similar diagnostics suppressed)` : '';\n fallback?.(`[logger] ${event.message}${summary}\\n`);\n } catch {\n // Diagnostics must never re-enter the normal logging pipeline.\n }\n\n if (!callbackPending) {\n try {\n callbackPending = true;\n const result: unknown = onError?.(Object.freeze({ ...event }));\n if (result !== null && (typeof result === 'object' || typeof result === 'function')) {\n void Promise.resolve(result).then(\n () => {\n callbackPending = false;\n },\n () => {\n callbackPending = false;\n },\n );\n } else {\n callbackPending = false;\n }\n } catch {\n callbackPending = false;\n // User diagnostic callbacks have the same non-throwing boundary.\n }\n }\n } catch { /* Diagnostics must never escape into the logging pipeline. */ } finally {\n reporting = false;\n }\n };\n}\n\nexport function createLoggerCore(options: LoggerOptions, hooks: LoggerHooks): Readonly<Logger> {\n validateLevel(options.level);\n validatePresentation(options);\n if (options.onError !== undefined && typeof options.onError !== 'function') {\n throw new TypeError('Logger onError must be a function');\n }\n\n const presentation = {\n console: options.console ?? true,\n icons: options.icons ?? true,\n prefix: options.prefix ?? '',\n suffix: options.suffix ?? '',\n timestamp: options.timestamp ?? true,\n };\n\n const { report } = hooks;\n\n let override = options.level === 'env' ? undefined : options.level;\n let previousEnvironment: string | undefined;\n let currentLevel: LoggerLevel = override ?? 'info';\n let environmentRead = false;\n\n function refreshLevel() {\n if (override !== undefined) return;\n const input = readEnvironmentLevel();\n if (environmentRead && input === previousEnvironment) return;\n environmentRead = true;\n previousEnvironment = input;\n const normalized = input?.trim().toLowerCase() || undefined;\n currentLevel = isLevel(normalized) ? normalized : 'info';\n if (normalized !== undefined && !isLevel(normalized)) {\n report({\n message: 'Invalid LOG_LEVEL; using info',\n type: 'invalid-level',\n });\n }\n }\n\n const logger = {\n configure(configuration) {\n validatePresentation(configuration);\n for (const name of booleanPresentationKeys) {\n const value = configuration[name];\n if (value !== undefined) presentation[name] = value;\n }\n\n for (const name of stringPresentationKeys) {\n const value = configuration[name];\n if (value !== undefined) presentation[name] = value;\n }\n },\n isLevelEnabled(level) {\n if (level !== 'success' && !isLevel(level)) throw new TypeError('Invalid logger level');\n refreshLevel();\n return level !== 'silent' && getLevelSeverity(level) >= getLevelSeverity(currentLevel);\n },\n setLevel(level) {\n validateLevel(level);\n if (level === undefined) throw new TypeError('Invalid logger level');\n override = level === 'env' ? undefined : level;\n environmentRead = false;\n if (override !== undefined) {\n currentLevel = override;\n } else {\n refreshLevel();\n }\n },\n } as Logger;\n\n for (const type of objectKeys(loggerMethods) as LoggerMethod[]) {\n logger[type] = async (...args: unknown[]) => {\n try {\n refreshLevel();\n if (loggerMethods[type].severity < getLevelSeverity(currentLevel)) return;\n await hooks.write({\n ...presentation,\n args,\n date: new Date(),\n type,\n });\n } catch (error) {\n report({\n error,\n message: 'Logger output failed',\n type: 'format',\n });\n }\n };\n }\n\n return Object.freeze(logger);\n}\n\nfunction getLevelSeverity(level: LoggerLevel | LoggerMethod): number {\n return level === 'silent' ? Number.POSITIVE_INFINITY : loggerMethods[level].severity;\n}\n\nfunction isLevel(value: unknown): value is LoggerLevel {\n return typeof value === 'string'\n && (value === 'silent' || (value !== 'success' && objectHasOwn(loggerMethods, value)));\n}\n\nfunction readEnvironmentLevel(): string | undefined {\n return (globalThis as { process?: { env?: { LOG_LEVEL?: string } } }).process?.env?.LOG_LEVEL;\n}\n\nfunction validateLevel(value: unknown): asserts value is 'env' | LoggerLevel | undefined {\n if (value !== undefined && value !== 'env' && !isLevel(value)) throw new TypeError('Invalid logger level');\n}\n\nfunction validatePresentation(options: Pick<LoggerOptions, 'console' | 'icons' | 'prefix' | 'suffix' | 'timestamp'>) {\n for (const name of booleanPresentationKeys) {\n if (options[name] !== undefined && typeof options[name] !== 'boolean') {\n throw new TypeError(`Logger ${name} must be a boolean`);\n }\n }\n\n for (const name of stringPresentationKeys) {\n if (options[name] !== undefined && typeof options[name] !== 'string') {\n throw new TypeError(`Logger ${name} must be a string`);\n }\n }\n}\n"],"mappings":"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"}
|
|
@@ -5,7 +5,9 @@ interface LoggerFileWriter {
|
|
|
5
5
|
getStats: () => Readonly<NodeLoggerStats>;
|
|
6
6
|
write: (text: string) => Promise<void>;
|
|
7
7
|
}
|
|
8
|
-
/**
|
|
8
|
+
/**
|
|
9
|
+
* A bounded admission layer over the stream's own ordered write buffer; not a second FIFO.
|
|
10
|
+
*/
|
|
9
11
|
export declare function createFileWriter(options: LoggerFileOptions, report: (event: LoggerDiagnostic) => void): Promise<Readonly<LoggerFileWriter>>;
|
|
10
12
|
export {};
|
|
11
13
|
//# sourceMappingURL=file-writer.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"file-writer.d.ts","names":[],"sources":["../../../../src/logger/node/_internals/file-writer.ts"],"mappings":";;UAiBU;EACN,aAAa;EACb,gBAAgB,SAAS;EACzB,QAAQ,iBAAiB
|
|
1
|
+
{"version":3,"file":"file-writer.d.ts","names":[],"sources":["../../../../src/logger/node/_internals/file-writer.ts"],"mappings":";;UAiBU;EACN,aAAa;EACb,gBAAgB,SAAS;EACzB,QAAQ,iBAAiB;;;;;wBAWP,iBAClB,SAAS,mBACT,SAAS,OAAO,4BACjB,QAAQ,SAAS"}
|
|
@@ -6,7 +6,9 @@ import { createWriteStream } from "node:fs";
|
|
|
6
6
|
import { fileURLToPath } from "node:url";
|
|
7
7
|
// src/logger/node/_internals/file-writer.ts
|
|
8
8
|
const defaultMaxPendingBytes = 8388608;
|
|
9
|
-
/**
|
|
9
|
+
/**
|
|
10
|
+
* A bounded admission layer over the stream's own ordered write buffer; not a second FIFO.
|
|
11
|
+
*/
|
|
10
12
|
async function createFileWriter(options, report) {
|
|
11
13
|
const { maxPendingBytes, path, rotate } = validateFileOptions(options);
|
|
12
14
|
const createRotatingStream = rotate ? (await import("rotating-file-stream")).createStream : void 0;
|
|
@@ -84,8 +86,8 @@ async function createFileWriter(options, report) {
|
|
|
84
86
|
initializing = false;
|
|
85
87
|
}
|
|
86
88
|
return Object.freeze({
|
|
87
|
-
close() {
|
|
88
|
-
if (closePromise) return closePromise;
|
|
89
|
+
async close() {
|
|
90
|
+
if (closePromise) return await closePromise;
|
|
89
91
|
closing = true;
|
|
90
92
|
closePromise = new Promise((resolveClosed) => {
|
|
91
93
|
if (closed) {
|
|
@@ -99,20 +101,20 @@ async function createFileWriter(options, report) {
|
|
|
99
101
|
fail(error);
|
|
100
102
|
}
|
|
101
103
|
});
|
|
102
|
-
return closePromise;
|
|
104
|
+
return await closePromise;
|
|
103
105
|
},
|
|
104
106
|
getStats: () => Object.freeze({
|
|
105
107
|
droppedCount,
|
|
106
108
|
pendingBytes,
|
|
107
109
|
writeErrorCount
|
|
108
110
|
}),
|
|
109
|
-
write(text) {
|
|
111
|
+
async write(text) {
|
|
110
112
|
if (closing || closed || failed) {
|
|
111
113
|
report({
|
|
112
114
|
message: "File output is closed or unavailable.",
|
|
113
115
|
type: "closed"
|
|
114
116
|
});
|
|
115
|
-
return Promise.resolve();
|
|
117
|
+
return await Promise.resolve();
|
|
116
118
|
}
|
|
117
119
|
const bytes = Buffer.byteLength(text);
|
|
118
120
|
if (bytes > maxPendingBytes - pendingBytes) {
|
|
@@ -122,10 +124,10 @@ async function createFileWriter(options, report) {
|
|
|
122
124
|
message: "File output queue is full; the newest record was discarded.",
|
|
123
125
|
type: "overflow"
|
|
124
126
|
});
|
|
125
|
-
return Promise.resolve();
|
|
127
|
+
return await Promise.resolve();
|
|
126
128
|
}
|
|
127
129
|
pendingBytes += bytes;
|
|
128
|
-
return new Promise((resolveWritten) => {
|
|
130
|
+
return await new Promise((resolveWritten) => {
|
|
129
131
|
let settled = false;
|
|
130
132
|
const settle = () => {
|
|
131
133
|
if (settled) return;
|
|
@@ -147,7 +149,9 @@ async function createFileWriter(options, report) {
|
|
|
147
149
|
}
|
|
148
150
|
});
|
|
149
151
|
}
|
|
150
|
-
/**
|
|
152
|
+
/**
|
|
153
|
+
* Validate before mkdir, stream creation, or other filesystem side effects.
|
|
154
|
+
*/
|
|
151
155
|
function validateFileOptions(options) {
|
|
152
156
|
if (!options || typeof options !== "object" || !(typeof options.path === "string" || options.path instanceof Path || options.path instanceof URL || Buffer.isBuffer(options.path))) throw new TypeError("file.path must be a string, Buffer, file URL, or Path");
|
|
153
157
|
const path = options.path instanceof URL ? fileURLToPath(options.path) : options.path.toString();
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"file-writer.js","names":[],"sources":["../../../../src/logger/node/_internals/file-writer.ts"],"sourcesContent":["import { Buffer } from 'node:buffer';\nimport { createWriteStream } from 'node:fs';\nimport { mkdir } from 'node:fs/promises';\nimport {\n basename,\n dirname,\n} from 'node:path';\nimport type { Writable } from 'node:stream';\nimport { fileURLToPath } from 'node:url';\n\nimport { Path } from '../../../classes/path';\nimport type { LoggerDiagnostic } from '../../types';\nimport type {\n LoggerFileOptions,\n NodeLoggerStats,\n} from '../types';\n\ninterface LoggerFileWriter {\n close: () => Promise<void>;\n getStats: () => Readonly<NodeLoggerStats>;\n write: (text: string) => Promise<void>;\n}\n\n// Constants/Variables\nconst defaultMaxPendingBytes = 8 * 1024 * 1024;\n\n// Functions\n\n/** A bounded admission layer over the stream's own ordered write buffer; not a second FIFO. */\nexport async function createFileWriter(\n options: LoggerFileOptions,\n report: (event: LoggerDiagnostic) => void,\n): Promise<Readonly<LoggerFileWriter>> {\n const {\n maxPendingBytes,\n path,\n rotate,\n } = validateFileOptions(options);\n\n const createRotatingStream = rotate ? (await import('rotating-file-stream')).createStream : undefined;\n await mkdir(dirname(path), { recursive: true });\n const stream: Writable = rotate && createRotatingStream\n ? createRotatingStream(\n basename(path),\n {\n compress: rotate.compress === false ? false : 'gzip',\n ...rotate.interval === undefined ? {} : { interval: rotate.interval === 'hourly' ? '1h' : '1d' },\n intervalBoundary: true,\n intervalUTC: false,\n maxFiles: rotate.keepFiles ?? 30,\n ...rotate.maxTotalSize === undefined ? {} : { maxSize: rotate.maxTotalSize },\n path: dirname(path),\n ...rotate.maxSize === undefined ? {} : { size: rotate.maxSize },\n },\n )\n : createWriteStream(path, { flags: 'a' });\n\n let pendingBytes = 0;\n let droppedCount = 0;\n let writeErrorCount = 0;\n let failed = false;\n let closing = false;\n let closed = false;\n let closePromise: Promise<void> | undefined;\n let initializing = true;\n const unfinished = new Set<() => void>();\n let rejectInitialization: ((error: unknown) => void) | undefined;\n\n const settleAll = () => {\n for (const settle of unfinished) settle();\n };\n\n const fail = (error: unknown) => {\n rejectInitialization?.(error);\n if (!failed) {\n failed = true;\n if (!initializing) {\n writeErrorCount++;\n report({\n error,\n message: 'File output failed and has been disabled.',\n type: 'file',\n });\n }\n }\n\n settleAll();\n if (!stream.destroyed) stream.destroy();\n };\n\n stream.on('error', fail);\n stream.on(\n 'warning',\n (error: unknown) => {\n writeErrorCount++;\n report({\n error,\n message: 'File rotation cleanup failed.',\n type: 'file',\n });\n },\n );\n\n stream.on(\n 'close',\n () => {\n closed = true;\n if (!closing && !failed) fail(new Error('File stream closed unexpectedly.'));\n settleAll();\n },\n );\n\n // A write callback also waits for RFS's asynchronous initialization and initial rotation.\n try {\n await new Promise<void>((resolveReady, rejectReady) => {\n rejectInitialization = rejectReady;\n stream.write(\n Buffer.alloc(0),\n (error) => {\n if (error) rejectReady(error);\n else resolveReady();\n },\n );\n });\n\n if (failed || closed) throw new Error('File output failed during initialization.');\n } catch (error) {\n if (!stream.destroyed) stream.destroy();\n if (!closed) {\n await new Promise<void>((resolveClosed) => {\n stream.once('close', resolveClosed);\n });\n }\n\n throw error;\n } finally {\n rejectInitialization = undefined;\n initializing = false;\n }\n\n return Object.freeze({\n close() {\n if (closePromise) return closePromise;\n closing = true;\n closePromise = new Promise<void>((resolveClosed) => {\n if (closed) {\n resolveClosed();\n return;\n }\n\n stream.once('close', resolveClosed);\n try {\n if (!stream.destroyed) stream.end();\n } catch (error) {\n fail(error);\n }\n });\n\n return closePromise;\n },\n getStats: () => Object.freeze({\n droppedCount,\n pendingBytes,\n writeErrorCount,\n }),\n write(text: string) {\n if (closing || closed || failed) {\n report({\n message: 'File output is closed or unavailable.',\n type: 'closed',\n });\n\n return Promise.resolve();\n }\n\n const bytes = Buffer.byteLength(text);\n if (bytes > maxPendingBytes - pendingBytes) {\n droppedCount++;\n report({\n droppedCount,\n message: 'File output queue is full; the newest record was discarded.',\n type: 'overflow',\n });\n\n return Promise.resolve();\n }\n\n pendingBytes += bytes;\n return new Promise<void>((resolveWritten) => {\n let settled = false;\n const settle = () => {\n if (settled) return;\n settled = true;\n pendingBytes -= bytes;\n unfinished.delete(settle);\n resolveWritten();\n };\n\n unfinished.add(settle);\n try {\n stream.write(\n text,\n (error) => {\n if (error) fail(error);\n settle();\n },\n );\n } catch (error) {\n fail(error);\n }\n });\n },\n });\n}\n\n/** Validate before mkdir, stream creation, or other filesystem side effects. */\nfunction validateFileOptions(options: LoggerFileOptions) {\n if (\n !options\n || typeof options !== 'object'\n || !(\n typeof options.path === 'string'\n || options.path instanceof Path\n || options.path instanceof URL\n || Buffer.isBuffer(options.path)\n )\n ) {\n throw new TypeError('file.path must be a string, Buffer, file URL, or Path');\n }\n\n const path = options.path instanceof URL\n ? fileURLToPath(options.path)\n : options.path.toString();\n\n if (Buffer.isBuffer(options.path) && !Buffer.from(path).equals(options.path)) {\n throw new TypeError('file.path Buffer must contain valid UTF-8');\n }\n\n if (!path.trim() || path.includes('\\0')) {\n throw new TypeError('file.path must be a nonempty path without null bytes');\n }\n\n const maxPendingBytes = options.maxPendingBytes ?? defaultMaxPendingBytes;\n if (!Number.isSafeInteger(maxPendingBytes) || maxPendingBytes <= 0) {\n throw new RangeError('file.maxPendingBytes must be a positive safe integer');\n }\n\n const rotate = options.rotate;\n if (rotate !== undefined && rotate !== false) {\n if (!rotate || typeof rotate !== 'object') {\n throw new TypeError('file.rotate must be false or an options object');\n }\n\n if (rotate.interval !== undefined && rotate.interval !== 'hourly' && rotate.interval !== 'daily') {\n throw new TypeError('file.rotate.interval must be hourly or daily');\n }\n\n if (rotate.interval === undefined && rotate.maxSize === undefined) {\n throw new TypeError('file.rotate requires interval or maxSize');\n }\n\n const keepFiles = rotate.keepFiles ?? 30;\n if (!Number.isSafeInteger(keepFiles) || keepFiles <= 0) {\n throw new RangeError('file.rotate.keepFiles must be a positive safe integer');\n }\n\n if (rotate.compress !== undefined && typeof rotate.compress !== 'boolean') {\n throw new TypeError('file.rotate.compress must be a boolean');\n }\n\n validateSize(rotate.maxSize, 'file.rotate.maxSize');\n validateSize(rotate.maxTotalSize, 'file.rotate.maxTotalSize');\n }\n\n return {\n maxPendingBytes,\n path: Path.resolve(path).value,\n rotate,\n };\n}\n\nfunction validateSize(value: string | undefined, name: string) {\n if (value === undefined) return;\n if (typeof value !== 'string' || !/^[1-9]\\d*[BKMG]$/.test(value)) {\n throw new TypeError(`${name} must be a positive integer followed by B, K, M, or G`);\n }\n\n const multiplier = {\n B: 1,\n G: 1024 ** 3,\n K: 1024,\n M: 1024 ** 2,\n }[value.at(-1) as 'B' | 'G' | 'K' | 'M'];\n\n if (!Number.isSafeInteger(Number(value.slice(0, -1)) * multiplier)) {\n throw new RangeError(`${name} exceeds the supported size range`);\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"file-writer.js","names":[],"sources":["../../../../src/logger/node/_internals/file-writer.ts"],"sourcesContent":["import { Buffer } from 'node:buffer';\nimport { createWriteStream } from 'node:fs';\nimport { mkdir } from 'node:fs/promises';\nimport {\n basename,\n dirname,\n} from 'node:path';\nimport type { Writable } from 'node:stream';\nimport { fileURLToPath } from 'node:url';\n\nimport { Path } from '../../../classes/path';\nimport type { LoggerDiagnostic } from '../../types';\nimport type {\n LoggerFileOptions,\n NodeLoggerStats,\n} from '../types';\n\ninterface LoggerFileWriter {\n close: () => Promise<void>;\n getStats: () => Readonly<NodeLoggerStats>;\n write: (text: string) => Promise<void>;\n}\n\n// Constants/Variables\nconst defaultMaxPendingBytes = 8 * 1024 * 1024;\n\n// Functions\n\n/**\n * A bounded admission layer over the stream's own ordered write buffer; not a second FIFO.\n */\nexport async function createFileWriter(\n options: LoggerFileOptions,\n report: (event: LoggerDiagnostic) => void,\n): Promise<Readonly<LoggerFileWriter>> {\n const {\n maxPendingBytes,\n path,\n rotate,\n } = validateFileOptions(options);\n\n const createRotatingStream = rotate ? (await import('rotating-file-stream')).createStream : undefined;\n await mkdir(dirname(path), { recursive: true });\n const stream: Writable = rotate && createRotatingStream\n ? createRotatingStream(\n basename(path),\n {\n compress: rotate.compress === false ? false : 'gzip',\n ...rotate.interval === undefined ? {} : { interval: rotate.interval === 'hourly' ? '1h' : '1d' },\n intervalBoundary: true,\n intervalUTC: false,\n maxFiles: rotate.keepFiles ?? 30,\n ...rotate.maxTotalSize === undefined ? {} : { maxSize: rotate.maxTotalSize },\n path: dirname(path),\n ...rotate.maxSize === undefined ? {} : { size: rotate.maxSize },\n },\n )\n : createWriteStream(path, { flags: 'a' });\n\n let pendingBytes = 0;\n let droppedCount = 0;\n let writeErrorCount = 0;\n let failed = false;\n let closing = false;\n let closed = false;\n let closePromise: Promise<void> | undefined;\n let initializing = true;\n const unfinished = new Set<() => void>();\n let rejectInitialization: ((error: unknown) => void) | undefined;\n\n const settleAll = () => {\n for (const settle of unfinished) settle();\n };\n\n const fail = (error: unknown) => {\n rejectInitialization?.(error);\n if (!failed) {\n failed = true;\n if (!initializing) {\n writeErrorCount++;\n report({\n error,\n message: 'File output failed and has been disabled.',\n type: 'file',\n });\n }\n }\n\n settleAll();\n if (!stream.destroyed) stream.destroy();\n };\n\n stream.on('error', fail);\n stream.on(\n 'warning',\n (error: unknown) => {\n writeErrorCount++;\n report({\n error,\n message: 'File rotation cleanup failed.',\n type: 'file',\n });\n },\n );\n\n stream.on(\n 'close',\n () => {\n closed = true;\n if (!closing && !failed) fail(new Error('File stream closed unexpectedly.'));\n settleAll();\n },\n );\n\n // A write callback also waits for RFS's asynchronous initialization and initial rotation.\n try {\n await new Promise<void>((resolveReady, rejectReady) => {\n rejectInitialization = rejectReady;\n stream.write(\n Buffer.alloc(0),\n (error) => {\n if (error) rejectReady(error);\n else resolveReady();\n },\n );\n });\n\n if (failed || closed) throw new Error('File output failed during initialization.');\n } catch (error) {\n if (!stream.destroyed) stream.destroy();\n if (!closed) {\n await new Promise<void>((resolveClosed) => {\n stream.once('close', resolveClosed);\n });\n }\n\n throw error;\n } finally {\n rejectInitialization = undefined;\n initializing = false;\n }\n\n return Object.freeze({\n async close() {\n if (closePromise) return await closePromise;\n closing = true;\n closePromise = new Promise<void>((resolveClosed) => {\n if (closed) {\n resolveClosed();\n return;\n }\n\n stream.once('close', resolveClosed);\n try {\n if (!stream.destroyed) stream.end();\n } catch (error) {\n fail(error);\n }\n });\n\n return await closePromise;\n },\n getStats: () => Object.freeze({\n droppedCount,\n pendingBytes,\n writeErrorCount,\n }),\n async write(text: string) {\n if (closing || closed || failed) {\n report({\n message: 'File output is closed or unavailable.',\n type: 'closed',\n });\n\n return await Promise.resolve();\n }\n\n const bytes = Buffer.byteLength(text);\n if (bytes > maxPendingBytes - pendingBytes) {\n droppedCount++;\n report({\n droppedCount,\n message: 'File output queue is full; the newest record was discarded.',\n type: 'overflow',\n });\n\n return await Promise.resolve();\n }\n\n pendingBytes += bytes;\n return await new Promise<void>((resolveWritten) => {\n let settled = false;\n const settle = () => {\n if (settled) return;\n settled = true;\n pendingBytes -= bytes;\n unfinished.delete(settle);\n resolveWritten();\n };\n\n unfinished.add(settle);\n try {\n stream.write(\n text,\n (error) => {\n if (error) fail(error);\n settle();\n },\n );\n } catch (error) {\n fail(error);\n }\n });\n },\n });\n}\n\n/**\n * Validate before mkdir, stream creation, or other filesystem side effects.\n */\nfunction validateFileOptions(options: LoggerFileOptions) {\n if (\n !options\n || typeof options !== 'object'\n || !(\n typeof options.path === 'string'\n || options.path instanceof Path\n || options.path instanceof URL\n || Buffer.isBuffer(options.path)\n )\n ) {\n throw new TypeError('file.path must be a string, Buffer, file URL, or Path');\n }\n\n const path = options.path instanceof URL\n ? fileURLToPath(options.path)\n : options.path.toString();\n\n if (Buffer.isBuffer(options.path) && !Buffer.from(path).equals(options.path)) {\n throw new TypeError('file.path Buffer must contain valid UTF-8');\n }\n\n if (!path.trim() || path.includes('\\0')) {\n throw new TypeError('file.path must be a nonempty path without null bytes');\n }\n\n const maxPendingBytes = options.maxPendingBytes ?? defaultMaxPendingBytes;\n if (!Number.isSafeInteger(maxPendingBytes) || maxPendingBytes <= 0) {\n throw new RangeError('file.maxPendingBytes must be a positive safe integer');\n }\n\n const rotate = options.rotate;\n if (rotate !== undefined && rotate !== false) {\n if (!rotate || typeof rotate !== 'object') {\n throw new TypeError('file.rotate must be false or an options object');\n }\n\n if (rotate.interval !== undefined && rotate.interval !== 'hourly' && rotate.interval !== 'daily') {\n throw new TypeError('file.rotate.interval must be hourly or daily');\n }\n\n if (rotate.interval === undefined && rotate.maxSize === undefined) {\n throw new TypeError('file.rotate requires interval or maxSize');\n }\n\n const keepFiles = rotate.keepFiles ?? 30;\n if (!Number.isSafeInteger(keepFiles) || keepFiles <= 0) {\n throw new RangeError('file.rotate.keepFiles must be a positive safe integer');\n }\n\n if (rotate.compress !== undefined && typeof rotate.compress !== 'boolean') {\n throw new TypeError('file.rotate.compress must be a boolean');\n }\n\n validateSize(rotate.maxSize, 'file.rotate.maxSize');\n validateSize(rotate.maxTotalSize, 'file.rotate.maxTotalSize');\n }\n\n return {\n maxPendingBytes,\n path: Path.resolve(path).value,\n rotate,\n };\n}\n\nfunction validateSize(value: string | undefined, name: string) {\n if (value === undefined) return;\n if (typeof value !== 'string' || !/^[1-9]\\d*[BKMG]$/.test(value)) {\n throw new TypeError(`${name} must be a positive integer followed by B, K, M, or G`);\n }\n\n const multiplier = {\n B: 1,\n G: 1024 ** 3,\n K: 1024,\n M: 1024 ** 2,\n }[value.at(-1) as 'B' | 'G' | 'K' | 'M'];\n\n if (!Number.isSafeInteger(Number(value.slice(0, -1)) * multiplier)) {\n throw new RangeError(`${name} exceeds the supported size range`);\n }\n}\n"],"mappings":"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"}
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { LoggerRecord } from "../../_internals/types.js";
|
|
2
|
-
/**
|
|
2
|
+
/**
|
|
3
|
+
* Preserve user ANSI for console output without invoking custom inspect callbacks/getters.
|
|
4
|
+
*/
|
|
3
5
|
export declare function formatNodeLoggerRecord(record: LoggerRecord, colors?: boolean): string;
|
|
4
6
|
//# sourceMappingURL=formatter.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"formatter.d.ts","names":[],"sources":["../../../../src/logger/node/_internals/formatter.ts"],"mappings":"
|
|
1
|
+
{"version":3,"file":"formatter.d.ts","names":[],"sources":["../../../../src/logger/node/_internals/formatter.ts"],"mappings":";;;;wBAUgB,uBAAuB,QAAQ,cAAc"}
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { formatLoggerParts } from "../../_internals/formatter.js";
|
|
2
2
|
import { inspect } from "node:util";
|
|
3
3
|
// src/logger/node/_internals/formatter.ts
|
|
4
|
-
/**
|
|
4
|
+
/**
|
|
5
|
+
* Preserve user ANSI for console output without invoking custom inspect callbacks/getters.
|
|
6
|
+
*/
|
|
5
7
|
function formatNodeLoggerRecord(record, colors = false) {
|
|
6
8
|
const message = record.args.map((arg) => typeof arg === "string" ? arg : inspect(arg, {
|
|
7
9
|
colors: false,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"formatter.js","names":[],"sources":["../../../../src/logger/node/_internals/formatter.ts"],"sourcesContent":["import { inspect } from 'node:util';\n\nimport { formatLoggerParts } from '../../_internals/formatter';\nimport type { LoggerRecord } from '../../_internals/types';\n\n// Functions\n\n
|
|
1
|
+
{"version":3,"file":"formatter.js","names":[],"sources":["../../../../src/logger/node/_internals/formatter.ts"],"sourcesContent":["import { inspect } from 'node:util';\n\nimport { formatLoggerParts } from '../../_internals/formatter';\nimport type { LoggerRecord } from '../../_internals/types';\n\n// Functions\n\n/**\n * Preserve user ANSI for console output without invoking custom inspect callbacks/getters.\n */\nexport function formatNodeLoggerRecord(record: LoggerRecord, colors = false) {\n const message = record.args.map(\n (arg) => typeof arg === 'string'\n ? arg\n : inspect(\n arg,\n {\n colors: false,\n customInspect: false,\n depth: 6,\n getters: false,\n maxArrayLength: 100,\n maxStringLength: 10_000,\n },\n ),\n ).join(' ');\n\n return formatLoggerParts(\n {\n ...record,\n args: record.args.length ? [message] : [],\n },\n colors,\n ).join(' ');\n}\n"],"mappings":";;;;;;AAUA,SAAgB,uBAAuB,QAAsB,SAAS,OAAO;CACzE,MAAM,UAAU,OAAO,KAAK,KACvB,QAAQ,OAAO,QAAQ,WAClB,MACA,QACE,KACA;EACI,QAAQ;EACR,eAAe;EACf,OAAO;EACP,SAAS;EACT,gBAAgB;EAChB,iBAAiB;CACrB,CACJ,CACR,CAAC,CAAC,KAAK,GAAG;CAEV,OAAO,kBACH;EACI,GAAG;EACH,MAAM,OAAO,KAAK,SAAS,CAAC,OAAO,IAAI,CAAC;CAC5C,GACA,MACJ,CAAC,CAAC,KAAK,GAAG;AACd"}
|
|
@@ -24,7 +24,7 @@ async function createNodeLogger(options = {}) {
|
|
|
24
24
|
const consoleWrites = /* @__PURE__ */ new Set();
|
|
25
25
|
const logger = createLoggerCore(options, {
|
|
26
26
|
report,
|
|
27
|
-
write(record) {
|
|
27
|
+
async write(record) {
|
|
28
28
|
if (closed) {
|
|
29
29
|
report({
|
|
30
30
|
message: "Logger is closed.",
|
|
@@ -42,16 +42,16 @@ async function createNodeLogger(options = {}) {
|
|
|
42
42
|
consoleWrites.add(completion);
|
|
43
43
|
completion.then(() => consoleWrites.delete(completion));
|
|
44
44
|
const fileCompletion = writer?.write(`${stripVTControlCharacters(text)}\n`);
|
|
45
|
-
return fileCompletion ? Promise.all([completion, fileCompletion]).then(() => {}) : completion;
|
|
45
|
+
return fileCompletion ? await Promise.all([completion, fileCompletion]).then(() => {}) : await completion;
|
|
46
46
|
}
|
|
47
|
-
return writer?.write(`${stripVTControlCharacters(text)}\n`);
|
|
47
|
+
return await writer?.write(`${stripVTControlCharacters(text)}\n`);
|
|
48
48
|
}
|
|
49
49
|
});
|
|
50
50
|
if (options.file !== void 0 && options.file !== false) writer = await createFileWriter(options.file, report);
|
|
51
51
|
return Object.freeze({
|
|
52
52
|
...logger,
|
|
53
|
-
close() {
|
|
54
|
-
if (closePromise) return closePromise;
|
|
53
|
+
async close() {
|
|
54
|
+
if (closePromise) return await closePromise;
|
|
55
55
|
closed = true;
|
|
56
56
|
closePromise = (async () => {
|
|
57
57
|
await Promise.all([writer?.close(), ...consoleWrites]);
|
|
@@ -67,7 +67,7 @@ async function createNodeLogger(options = {}) {
|
|
|
67
67
|
}
|
|
68
68
|
consoleTargets.clear();
|
|
69
69
|
})();
|
|
70
|
-
return closePromise;
|
|
70
|
+
return await closePromise;
|
|
71
71
|
},
|
|
72
72
|
getStats: () => writer?.getStats() ?? Object.freeze({
|
|
73
73
|
droppedCount: 0,
|
|
@@ -76,8 +76,10 @@ async function createNodeLogger(options = {}) {
|
|
|
76
76
|
})
|
|
77
77
|
});
|
|
78
78
|
}
|
|
79
|
-
/**
|
|
80
|
-
|
|
79
|
+
/**
|
|
80
|
+
* One error listener per shared output stream; retained until its last owning logger calls close().
|
|
81
|
+
*/
|
|
82
|
+
async function writeConsole(target, text, report, owner) {
|
|
81
83
|
let state = consoleStreams.get(target);
|
|
82
84
|
if (!state) {
|
|
83
85
|
const pending = /* @__PURE__ */ new Set();
|
|
@@ -97,7 +99,7 @@ function writeConsole(target, text, report, owner) {
|
|
|
97
99
|
}
|
|
98
100
|
state.owners.add(owner);
|
|
99
101
|
const current = state;
|
|
100
|
-
return new Promise((resolveWritten) => {
|
|
102
|
+
return await new Promise((resolveWritten) => {
|
|
101
103
|
let settled = false;
|
|
102
104
|
const settle = (error) => {
|
|
103
105
|
if (settled) return;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":[],"sources":["../../../src/logger/node/index.ts"],"sourcesContent":["import { writeSync } from 'node:fs';\nimport { stripVTControlCharacters } from 'node:util';\n\nimport { supportsLoggerColor } from '../_internals/formatter';\nimport { getLoggerStreamName } from '../_internals/levels';\nimport {\n createDiagnosticReporter,\n createLoggerCore,\n} from '../_internals/logger';\nimport type { LoggerDiagnostic } from '../types';\n\nimport { createFileWriter } from './_internals/file-writer';\nimport { formatNodeLoggerRecord } from './_internals/formatter';\nimport type {\n NodeLogger,\n NodeLoggerOptions,\n} from './types';\n\ninterface ConsoleStreamState {\n onClose: () => void;\n onError: (error: Error) => void;\n owners: Set<object>;\n pending: Set<(error?: Error | null) => void>;\n}\n\n// Constants/Variables\nconst consoleStreams = new WeakMap<NodeJS.WriteStream, ConsoleStreamState>();\n\n// Functions\n\n/**\n * Creates a Node.js/Bun logger with optional bounded, append-only file output and rotation.\n *\n * Log calls never throw or reject. Awaiting them waits for processing, not proof of successful\n * persistence: overload drops the newest file record and failures are reported safely. Factory\n * validation/open failures reject. Call close explicitly; no process signal handlers are installed.\n */\nexport async function createNodeLogger(options: NodeLoggerOptions = {}): Promise<Readonly<NodeLogger>> {\n const report = createDiagnosticReporter(options.onError, (message) => writeSync(2, message));\n let writer: Awaited<ReturnType<typeof createFileWriter>> | undefined;\n let closed = false;\n let closePromise: Promise<void> | undefined;\n const consoleOwner = {};\n const consoleTargets = new Set<NodeJS.WriteStream>();\n const consoleWrites = new Set<Promise<void>>();\n const logger = createLoggerCore(\n options,\n {\n report,\n write(record) {\n if (closed) {\n report({\n message: 'Logger is closed.',\n type: 'closed',\n });\n\n return;\n }\n\n if (!record.console && !writer) return;\n const target = process[getLoggerStreamName(record.type)];\n\n const text = formatNodeLoggerRecord(record, record.console && supportsLoggerColor(target));\n if (record.console) {\n const line = `${text}\\n`;\n consoleTargets.add(target);\n const completion = writeConsole(target, line, report, consoleOwner);\n consoleWrites.add(completion);\n void completion.then(() => consoleWrites.delete(completion));\n const fileCompletion = writer?.write(`${stripVTControlCharacters(text)}\\n`);\n return fileCompletion\n ? Promise.all([\n completion,\n fileCompletion,\n ]).then(() => {})\n : completion;\n }\n\n return writer?.write(`${stripVTControlCharacters(text)}\\n`);\n },\n },\n );\n\n if (options.file !== undefined && options.file !== false) writer = await createFileWriter(options.file, report);\n\n return Object.freeze({\n ...logger,\n close() {\n if (closePromise) return closePromise;\n closed = true;\n closePromise = (async () => {\n await Promise.all([\n writer?.close(),\n ...consoleWrites,\n ]);\n\n for (const target of consoleTargets) {\n const state = consoleStreams.get(target);\n if (!state) continue;\n state.owners.delete(consoleOwner);\n if (!state.owners.size) {\n target.off('error', state.onError);\n target.off('close', state.onClose);\n consoleStreams.delete(target);\n }\n }\n\n consoleTargets.clear();\n })();\n return closePromise;\n },\n getStats: () => writer?.getStats() ?? Object.freeze({\n droppedCount: 0,\n pendingBytes: 0,\n writeErrorCount: 0,\n }),\n });\n}\n\n
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../../../src/logger/node/index.ts"],"sourcesContent":["import { writeSync } from 'node:fs';\nimport { stripVTControlCharacters } from 'node:util';\n\nimport { supportsLoggerColor } from '../_internals/formatter';\nimport { getLoggerStreamName } from '../_internals/levels';\nimport {\n createDiagnosticReporter,\n createLoggerCore,\n} from '../_internals/logger';\nimport type { LoggerDiagnostic } from '../types';\n\nimport { createFileWriter } from './_internals/file-writer';\nimport { formatNodeLoggerRecord } from './_internals/formatter';\nimport type {\n NodeLogger,\n NodeLoggerOptions,\n} from './types';\n\ninterface ConsoleStreamState {\n onClose: () => void;\n onError: (error: Error) => void;\n owners: Set<object>;\n pending: Set<(error?: Error | null) => void>;\n}\n\n// Constants/Variables\nconst consoleStreams = new WeakMap<NodeJS.WriteStream, ConsoleStreamState>();\n\n// Functions\n\n/**\n * Creates a Node.js/Bun logger with optional bounded, append-only file output and rotation.\n *\n * Log calls never throw or reject. Awaiting them waits for processing, not proof of successful\n * persistence: overload drops the newest file record and failures are reported safely. Factory\n * validation/open failures reject. Call close explicitly; no process signal handlers are installed.\n */\nexport async function createNodeLogger(options: NodeLoggerOptions = {}): Promise<Readonly<NodeLogger>> {\n const report = createDiagnosticReporter(options.onError, (message) => writeSync(2, message));\n let writer: Awaited<ReturnType<typeof createFileWriter>> | undefined;\n let closed = false;\n let closePromise: Promise<void> | undefined;\n const consoleOwner = {};\n const consoleTargets = new Set<NodeJS.WriteStream>();\n const consoleWrites = new Set<Promise<void>>();\n const logger = createLoggerCore(\n options,\n {\n report,\n async write(record) {\n if (closed) {\n report({\n message: 'Logger is closed.',\n type: 'closed',\n });\n\n return;\n }\n\n if (!record.console && !writer) return;\n const target = process[getLoggerStreamName(record.type)];\n\n const text = formatNodeLoggerRecord(record, record.console && supportsLoggerColor(target));\n if (record.console) {\n const line = `${text}\\n`;\n consoleTargets.add(target);\n const completion = writeConsole(target, line, report, consoleOwner);\n consoleWrites.add(completion);\n void completion.then(() => consoleWrites.delete(completion));\n const fileCompletion = writer?.write(`${stripVTControlCharacters(text)}\\n`);\n return fileCompletion\n ? await Promise.all([\n completion,\n fileCompletion,\n ]).then(() => {})\n : await completion;\n }\n\n return await writer?.write(`${stripVTControlCharacters(text)}\\n`);\n },\n },\n );\n\n if (options.file !== undefined && options.file !== false) writer = await createFileWriter(options.file, report);\n\n return Object.freeze({\n ...logger,\n async close() {\n if (closePromise) return await closePromise;\n closed = true;\n closePromise = (async () => {\n await Promise.all([\n writer?.close(),\n ...consoleWrites,\n ]);\n\n for (const target of consoleTargets) {\n const state = consoleStreams.get(target);\n if (!state) continue;\n state.owners.delete(consoleOwner);\n if (!state.owners.size) {\n target.off('error', state.onError);\n target.off('close', state.onClose);\n consoleStreams.delete(target);\n }\n }\n\n consoleTargets.clear();\n })();\n\n return await closePromise;\n },\n getStats: () => writer?.getStats() ?? Object.freeze({\n droppedCount: 0,\n pendingBytes: 0,\n writeErrorCount: 0,\n }),\n });\n}\n\n/**\n * One error listener per shared output stream; retained until its last owning logger calls close().\n */\nasync function writeConsole(\n target: NodeJS.WriteStream,\n text: string,\n report: (event: LoggerDiagnostic) => void,\n owner: object,\n) {\n let state = consoleStreams.get(target);\n if (!state) {\n const pending = new Set<(error?: Error | null) => void>();\n const onError = (error: Error) => {\n for (const settle of pending) settle(error);\n };\n\n const onClose = () => onError(new Error('Console stream closed.'));\n state = {\n onClose,\n onError,\n owners: new Set(),\n pending,\n };\n\n consoleStreams.set(target, state);\n target.on('error', onError);\n target.on('close', onClose);\n }\n\n state.owners.add(owner);\n const current = state;\n return await new Promise<void>((resolveWritten) => {\n let settled = false;\n const settle = (error?: Error | null) => {\n if (settled) return;\n settled = true;\n current.pending.delete(settle);\n if (error) {\n report({\n error,\n message: 'Console output failed.',\n type: 'console',\n });\n }\n\n resolveWritten();\n };\n\n current.pending.add(settle);\n try {\n target.write(text, settle);\n } catch (error) {\n settle(error instanceof Error ? error : new Error('Console output failed.'));\n }\n });\n}\n"],"mappings":"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"}
|
|
@@ -1,38 +1,62 @@
|
|
|
1
1
|
import { PathLike } from "../../classes/path.js";
|
|
2
2
|
import { Logger, LoggerOptions } from "../types.js";
|
|
3
|
-
/**
|
|
3
|
+
/**
|
|
4
|
+
* Specify interval and/or maxSize. Size limits use positive integers with B, K, M, or G (binary units).
|
|
5
|
+
*/
|
|
4
6
|
export type LoggerRotationOptions = {
|
|
5
|
-
/**
|
|
7
|
+
/**
|
|
8
|
+
* Gzip historical files. Defaults to true.
|
|
9
|
+
*/
|
|
6
10
|
compress?: boolean;
|
|
7
|
-
/**
|
|
11
|
+
/**
|
|
12
|
+
* Number of historical files, excluding the active file. Defaults to 30.
|
|
13
|
+
*/
|
|
8
14
|
keepFiles?: number;
|
|
9
|
-
/**
|
|
15
|
+
/**
|
|
16
|
+
* Maximum combined size of historical files.
|
|
17
|
+
*/
|
|
10
18
|
maxTotalSize?: string;
|
|
11
19
|
} & ({
|
|
12
|
-
/**
|
|
20
|
+
/**
|
|
21
|
+
* Opt-in time rotation aligned to the system's local calendar boundary.
|
|
22
|
+
*/
|
|
13
23
|
interval: 'daily' | 'hourly';
|
|
14
24
|
maxSize?: string;
|
|
15
25
|
} | {
|
|
16
|
-
/**
|
|
26
|
+
/**
|
|
27
|
+
* Opt-in time rotation aligned to the system's local calendar boundary.
|
|
28
|
+
*/
|
|
17
29
|
interval?: 'daily' | 'hourly';
|
|
18
30
|
maxSize: string;
|
|
19
31
|
});
|
|
20
32
|
export interface LoggerFileOptions {
|
|
21
|
-
/**
|
|
33
|
+
/**
|
|
34
|
+
* Maximum unfinished payload bytes, including data already submitted to the stream.
|
|
35
|
+
*/
|
|
22
36
|
maxPendingBytes?: number;
|
|
23
|
-
/**
|
|
37
|
+
/**
|
|
38
|
+
* Resolved once at creation. The file is opened in append mode.
|
|
39
|
+
*/
|
|
24
40
|
path: PathLike;
|
|
25
|
-
/**
|
|
41
|
+
/**
|
|
42
|
+
* Omitted or false disables rotation. An object must specify interval or maxSize.
|
|
43
|
+
*/
|
|
26
44
|
rotate?: false | LoggerRotationOptions;
|
|
27
45
|
}
|
|
28
46
|
export interface NodeLogger extends Logger {
|
|
29
|
-
/**
|
|
47
|
+
/**
|
|
48
|
+
* Stop accepting records, finish accepted file writes, then release the stream. Not fsync.
|
|
49
|
+
*/
|
|
30
50
|
close: () => Promise<void>;
|
|
31
|
-
/**
|
|
51
|
+
/**
|
|
52
|
+
* A fresh, frozen file-output snapshot. Dropped/failed records still resolve their logging promises.
|
|
53
|
+
*/
|
|
32
54
|
getStats: () => Readonly<NodeLoggerStats>;
|
|
33
55
|
}
|
|
34
56
|
export interface NodeLoggerOptions extends LoggerOptions {
|
|
35
|
-
/**
|
|
57
|
+
/**
|
|
58
|
+
* Disabled by default. Each logger owns at most one fixed file destination.
|
|
59
|
+
*/
|
|
36
60
|
file?: false | LoggerFileOptions;
|
|
37
61
|
}
|
|
38
62
|
export interface NodeLoggerStats {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","names":[],"sources":["../../../src/logger/node/types.ts"],"mappings":"
|
|
1
|
+
{"version":3,"file":"types.d.ts","names":[],"sources":["../../../src/logger/node/types.ts"],"mappings":";;;;;YASY;;;;EAIR;;;;EAKA;;;;EAKA;;;;;EAME;EACA;;;;;EAMA;EACA;;iBAIW;;;;EAIb;;;;EAKA,MAAM;;;;EAKN,iBAAiB;;iBAGJ,mBAAmB;;;;EAIhC,aAAa;;;;EAKb,gBAAgB,SAAS;;iBAGZ,0BAA0B;;;;EAIvC,eAAe;;iBAGF;EACb;EACA;EACA"}
|