@kikiutils/shared 18.3.0 → 19.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (156) hide show
  1. package/README.md +0 -7
  2. package/dist/buffer.d.ts +0 -2
  3. package/dist/buffer.d.ts.map +1 -1
  4. package/dist/buffer.js +1 -2
  5. package/dist/classes/enhanced-date.d.ts +0 -2
  6. package/dist/classes/enhanced-date.d.ts.map +1 -1
  7. package/dist/classes/enhanced-date.js +1 -2
  8. package/dist/classes/path.d.ts +1 -3
  9. package/dist/classes/path.d.ts.map +1 -1
  10. package/dist/classes/path.js +1 -2
  11. package/dist/classes/precision-number.d.ts +0 -2
  12. package/dist/classes/precision-number.d.ts.map +1 -1
  13. package/dist/classes/precision-number.js +1 -2
  14. package/dist/clipboard.d.ts +0 -2
  15. package/dist/clipboard.d.ts.map +1 -1
  16. package/dist/clipboard.js +1 -2
  17. package/dist/crypto-hash.d.ts +0 -2
  18. package/dist/crypto-hash.d.ts.map +1 -1
  19. package/dist/crypto-hash.js +1 -2
  20. package/dist/datetime.d.ts +0 -2
  21. package/dist/datetime.d.ts.map +1 -1
  22. package/dist/datetime.js +1 -2
  23. package/dist/element-plus.d.ts +0 -2
  24. package/dist/element-plus.d.ts.map +1 -1
  25. package/dist/element-plus.js +1 -2
  26. package/dist/enum.d.ts +0 -2
  27. package/dist/enum.d.ts.map +1 -1
  28. package/dist/enum.js +1 -2
  29. package/dist/env.d.ts +0 -2
  30. package/dist/env.d.ts.map +1 -1
  31. package/dist/env.js +1 -2
  32. package/dist/event-awaiter.d.ts +0 -2
  33. package/dist/event-awaiter.d.ts.map +1 -1
  34. package/dist/event-awaiter.js +1 -2
  35. package/dist/file.d.ts +0 -2
  36. package/dist/file.d.ts.map +1 -1
  37. package/dist/file.js +1 -2
  38. package/dist/general.d.ts +0 -2
  39. package/dist/general.d.ts.map +1 -1
  40. package/dist/general.js +1 -2
  41. package/dist/hash.d.ts +0 -2
  42. package/dist/hash.d.ts.map +1 -1
  43. package/dist/hash.js +1 -2
  44. package/dist/logger/_internals/formatter.d.ts +13 -0
  45. package/dist/logger/_internals/formatter.d.ts.map +1 -0
  46. package/dist/logger/_internals/formatter.js +57 -0
  47. package/dist/logger/_internals/formatter.js.map +1 -0
  48. package/dist/logger/_internals/levels.d.ts +54 -0
  49. package/dist/logger/_internals/levels.d.ts.map +1 -0
  50. package/dist/logger/_internals/levels.js +59 -0
  51. package/dist/logger/_internals/levels.js.map +1 -0
  52. package/dist/logger/_internals/logger.d.ts +12 -0
  53. package/dist/logger/_internals/logger.d.ts.map +1 -0
  54. package/dist/logger/_internals/logger.js +147 -0
  55. package/dist/logger/_internals/logger.js.map +1 -0
  56. package/dist/logger/_internals/types.d.ts +12 -0
  57. package/dist/logger/_internals/types.d.ts.map +1 -0
  58. package/dist/logger/_internals/types.js +1 -0
  59. package/dist/logger/index.d.ts +10 -0
  60. package/dist/logger/index.d.ts.map +1 -0
  61. package/dist/logger/index.js +25 -0
  62. package/dist/logger/index.js.map +1 -0
  63. package/dist/logger/node/_internals/file-writer.d.ts +11 -0
  64. package/dist/logger/node/_internals/file-writer.d.ts.map +1 -0
  65. package/dist/logger/node/_internals/file-writer.js +188 -0
  66. package/dist/logger/node/_internals/file-writer.js.map +1 -0
  67. package/dist/logger/node/_internals/formatter.d.ts +4 -0
  68. package/dist/logger/node/_internals/formatter.d.ts.map +1 -0
  69. package/dist/logger/node/_internals/formatter.js +21 -0
  70. package/dist/logger/node/_internals/formatter.js.map +1 -0
  71. package/dist/logger/node/index.d.ts +10 -0
  72. package/dist/logger/node/index.d.ts.map +1 -0
  73. package/dist/logger/node/index.js +122 -0
  74. package/dist/logger/node/index.js.map +1 -0
  75. package/dist/logger/node/types.d.ts +43 -0
  76. package/dist/logger/node/types.d.ts.map +1 -0
  77. package/dist/logger/node/types.js +1 -0
  78. package/dist/logger/types.d.ts +49 -0
  79. package/dist/logger/types.d.ts.map +1 -0
  80. package/dist/logger/types.js +1 -0
  81. package/dist/math.d.ts +0 -2
  82. package/dist/math.d.ts.map +1 -1
  83. package/dist/math.js +1 -2
  84. package/dist/number.d.ts +0 -2
  85. package/dist/number.d.ts.map +1 -1
  86. package/dist/number.js +1 -2
  87. package/dist/random.d.ts +0 -2
  88. package/dist/random.d.ts.map +1 -1
  89. package/dist/random.js +1 -2
  90. package/dist/redis/script-runner.d.ts +0 -2
  91. package/dist/redis/script-runner.d.ts.map +1 -1
  92. package/dist/redis/script-runner.js +1 -2
  93. package/dist/storages/lru/keyed-store.d.ts +0 -2
  94. package/dist/storages/lru/keyed-store.d.ts.map +1 -1
  95. package/dist/storages/lru/keyed-store.js +1 -2
  96. package/dist/storages/redis/keyed-store.d.ts +0 -2
  97. package/dist/storages/redis/keyed-store.d.ts.map +1 -1
  98. package/dist/storages/redis/keyed-store.js +1 -2
  99. package/dist/storages/redis/msgpack.d.ts +0 -2
  100. package/dist/storages/redis/msgpack.d.ts.map +1 -1
  101. package/dist/storages/redis/msgpack.js +1 -2
  102. package/dist/storages/redis/types.d.ts +0 -2
  103. package/dist/storages/redis/types.d.ts.map +1 -1
  104. package/dist/string.d.ts +0 -2
  105. package/dist/string.d.ts.map +1 -1
  106. package/dist/string.js +1 -2
  107. package/dist/time.d.ts +0 -2
  108. package/dist/time.d.ts.map +1 -1
  109. package/dist/time.js +1 -2
  110. package/dist/types/filtered-key-path.d.ts +0 -2
  111. package/dist/types/filtered-key-path.d.ts.map +1 -1
  112. package/dist/types/index.d.ts +0 -10
  113. package/dist/types/index.d.ts.map +1 -1
  114. package/dist/types/vue.d.ts +0 -2
  115. package/dist/types/vue.d.ts.map +1 -1
  116. package/dist/url.d.ts +0 -2
  117. package/dist/url.d.ts.map +1 -1
  118. package/dist/url.js +1 -2
  119. package/dist/vue.d.ts +0 -2
  120. package/dist/vue.d.ts.map +1 -1
  121. package/dist/vue.js +1 -2
  122. package/dist/web.d.ts +0 -2
  123. package/dist/web.d.ts.map +1 -1
  124. package/dist/web.js +1 -2
  125. package/package.json +73 -45
  126. package/src/logger/_internals/formatter.ts +80 -0
  127. package/src/logger/_internals/levels.ts +60 -0
  128. package/src/logger/_internals/logger.ts +224 -0
  129. package/src/logger/_internals/types.ts +12 -0
  130. package/src/logger/index.ts +34 -0
  131. package/src/logger/node/_internals/file-writer.ts +298 -0
  132. package/src/logger/node/_internals/formatter.ts +33 -0
  133. package/src/logger/node/index.ts +175 -0
  134. package/src/logger/node/types.ts +58 -0
  135. package/src/logger/types.ts +59 -0
  136. package/src/types/index.ts +0 -10
  137. package/dist/classes/ssh-client.d.ts +0 -40
  138. package/dist/classes/ssh-client.d.ts.map +0 -1
  139. package/dist/classes/ssh-client.js +0 -130
  140. package/dist/classes/ssh-client.js.map +0 -1
  141. package/dist/consola.d.ts +0 -24
  142. package/dist/consola.d.ts.map +0 -1
  143. package/dist/consola.js +0 -29
  144. package/dist/consola.js.map +0 -1
  145. package/dist/object.d.ts +0 -44
  146. package/dist/object.d.ts.map +0 -1
  147. package/dist/object.js +0 -59
  148. package/dist/object.js.map +0 -1
  149. package/dist/pino.d.ts +0 -25
  150. package/dist/pino.d.ts.map +0 -1
  151. package/dist/pino.js +0 -34
  152. package/dist/pino.js.map +0 -1
  153. package/src/classes/ssh-client.ts +0 -175
  154. package/src/consola.ts +0 -25
  155. package/src/object.ts +0 -78
  156. package/src/pino.ts +0 -33
@@ -0,0 +1,12 @@
1
+ import type { LoggerMethod } from '../types';
2
+
3
+ export interface LoggerRecord {
4
+ args: readonly unknown[];
5
+ console: boolean;
6
+ date: Date;
7
+ icons: boolean;
8
+ prefix: string;
9
+ suffix: string;
10
+ timestamp: boolean;
11
+ type: LoggerMethod;
12
+ }
@@ -0,0 +1,34 @@
1
+ import { formatLoggerConsoleArgs } from './_internals/formatter';
2
+ import { loggerMethods } from './_internals/levels';
3
+ import {
4
+ createDiagnosticReporter,
5
+ createLoggerCore,
6
+ } from './_internals/logger';
7
+ import type {
8
+ Logger,
9
+ LoggerOptions,
10
+ } from './types';
11
+
12
+ // Functions
13
+
14
+ /**
15
+ * Creates an independent, browser-safe console logger.
16
+ *
17
+ * Log calls never throw or reject for recoverable output failures. Awaiting a call
18
+ * waits for the console invocation, not display or durable delivery. Unspecified
19
+ * levels follow LOG_LEVEL when a process environment is available, otherwise info.
20
+ */
21
+ export function createLogger(options: LoggerOptions = {}): Logger {
22
+ const target = globalThis.console;
23
+ const fallback = target?.error?.bind(target);
24
+ return createLoggerCore(
25
+ options,
26
+ {
27
+ report: createDiagnosticReporter(options.onError, (message) => fallback?.(message.trimEnd())),
28
+ write(record) {
29
+ if (!record.console) return;
30
+ target?.[loggerMethods[record.type].consoleMethod](...formatLoggerConsoleArgs(record));
31
+ },
32
+ },
33
+ );
34
+ }
@@ -0,0 +1,298 @@
1
+ import { Buffer } from 'node:buffer';
2
+ import { createWriteStream } from 'node:fs';
3
+ import { mkdir } from 'node:fs/promises';
4
+ import {
5
+ basename,
6
+ dirname,
7
+ } from 'node:path';
8
+ import type { Writable } from 'node:stream';
9
+ import { fileURLToPath } from 'node:url';
10
+
11
+ import { Path } from '../../../classes/path';
12
+ import type { LoggerDiagnostic } from '../../types';
13
+ import type {
14
+ LoggerFileOptions,
15
+ NodeLoggerStats,
16
+ } from '../types';
17
+
18
+ interface LoggerFileWriter {
19
+ close: () => Promise<void>;
20
+ getStats: () => NodeLoggerStats;
21
+ write: (text: string) => Promise<void>;
22
+ }
23
+
24
+ // Constants/Variables
25
+ const defaultMaxPendingBytes = 8 * 1024 * 1024;
26
+
27
+ // Functions
28
+
29
+ /** A bounded admission layer over the stream's own ordered write buffer; not a second FIFO. */
30
+ export async function createFileWriter(
31
+ options: LoggerFileOptions,
32
+ report: (event: LoggerDiagnostic) => void,
33
+ ): Promise<LoggerFileWriter> {
34
+ const {
35
+ maxPendingBytes,
36
+ path,
37
+ rotate,
38
+ } = validateFileOptions(options);
39
+
40
+ const createRotatingStream = rotate ? (await import('rotating-file-stream')).createStream : undefined;
41
+ await mkdir(dirname(path), { recursive: true });
42
+ const stream: Writable = rotate && createRotatingStream
43
+ ? createRotatingStream(
44
+ basename(path),
45
+ {
46
+ compress: rotate.compress === false ? false : 'gzip',
47
+ ...rotate.interval === undefined ? {} : { interval: rotate.interval === 'hourly' ? '1h' : '1d' },
48
+ intervalBoundary: true,
49
+ intervalUTC: false,
50
+ maxFiles: rotate.keepFiles ?? 30,
51
+ ...rotate.maxTotalSize === undefined ? {} : { maxSize: rotate.maxTotalSize },
52
+ path: dirname(path),
53
+ ...rotate.maxSize === undefined ? {} : { size: rotate.maxSize },
54
+ },
55
+ )
56
+ : createWriteStream(path, { flags: 'a' });
57
+
58
+ let pendingBytes = 0;
59
+ let droppedCount = 0;
60
+ let writeErrorCount = 0;
61
+ let failed = false;
62
+ let closing = false;
63
+ let closed = false;
64
+ let closePromise: Promise<void> | undefined;
65
+ let initializing = true;
66
+ const unfinished = new Set<() => void>();
67
+ let rejectInitialization: ((error: unknown) => void) | undefined;
68
+
69
+ const settleAll = () => {
70
+ for (const settle of unfinished) settle();
71
+ };
72
+
73
+ const fail = (error: unknown) => {
74
+ rejectInitialization?.(error);
75
+ if (!failed) {
76
+ failed = true;
77
+ if (!initializing) {
78
+ writeErrorCount++;
79
+ report({
80
+ error,
81
+ message: 'File output failed and has been disabled.',
82
+ type: 'file',
83
+ });
84
+ }
85
+ }
86
+
87
+ settleAll();
88
+ if (!stream.destroyed) stream.destroy();
89
+ };
90
+
91
+ stream.on('error', fail);
92
+ stream.on(
93
+ 'warning',
94
+ (error: unknown) => {
95
+ writeErrorCount++;
96
+ report({
97
+ error,
98
+ message: 'File rotation cleanup failed.',
99
+ type: 'file',
100
+ });
101
+ },
102
+ );
103
+
104
+ stream.on(
105
+ 'close',
106
+ () => {
107
+ closed = true;
108
+ if (!closing && !failed) fail(new Error('File stream closed unexpectedly.'));
109
+ settleAll();
110
+ },
111
+ );
112
+
113
+ // A write callback also waits for RFS's asynchronous initialization and initial rotation.
114
+ try {
115
+ await new Promise<void>((resolveReady, rejectReady) => {
116
+ rejectInitialization = rejectReady;
117
+ stream.write(
118
+ Buffer.alloc(0),
119
+ (error) => {
120
+ if (error) rejectReady(error);
121
+ else resolveReady();
122
+ },
123
+ );
124
+ });
125
+
126
+ if (failed || closed) throw new Error('File output failed during initialization.');
127
+ } catch (error) {
128
+ if (!stream.destroyed) stream.destroy();
129
+ if (!closed) {
130
+ await new Promise<void>((resolveClosed) => {
131
+ stream.once('close', resolveClosed);
132
+ });
133
+ }
134
+
135
+ throw error;
136
+ } finally {
137
+ rejectInitialization = undefined;
138
+ initializing = false;
139
+ }
140
+
141
+ return {
142
+ close() {
143
+ if (closePromise) return closePromise;
144
+ closing = true;
145
+ closePromise = new Promise<void>((resolveClosed) => {
146
+ if (closed) {
147
+ resolveClosed();
148
+ return;
149
+ }
150
+
151
+ stream.once('close', resolveClosed);
152
+ try {
153
+ if (!stream.destroyed) stream.end();
154
+ } catch (error) {
155
+ fail(error);
156
+ }
157
+ });
158
+
159
+ return closePromise;
160
+ },
161
+ getStats: () => ({
162
+ droppedCount,
163
+ pendingBytes,
164
+ writeErrorCount,
165
+ }),
166
+ write(text) {
167
+ if (closing || closed || failed) {
168
+ report({
169
+ message: 'File output is closed or unavailable.',
170
+ type: 'closed',
171
+ });
172
+
173
+ return Promise.resolve();
174
+ }
175
+
176
+ const bytes = Buffer.byteLength(text);
177
+ if (bytes > maxPendingBytes - pendingBytes) {
178
+ droppedCount++;
179
+ report({
180
+ droppedCount,
181
+ message: 'File output queue is full; the newest record was discarded.',
182
+ type: 'overflow',
183
+ });
184
+
185
+ return Promise.resolve();
186
+ }
187
+
188
+ pendingBytes += bytes;
189
+ return new Promise<void>((resolveWritten) => {
190
+ let settled = false;
191
+ const settle = () => {
192
+ if (settled) return;
193
+ settled = true;
194
+ pendingBytes -= bytes;
195
+ unfinished.delete(settle);
196
+ resolveWritten();
197
+ };
198
+
199
+ unfinished.add(settle);
200
+ try {
201
+ stream.write(
202
+ text,
203
+ (error) => {
204
+ if (error) fail(error);
205
+ settle();
206
+ },
207
+ );
208
+ } catch (error) {
209
+ fail(error);
210
+ }
211
+ });
212
+ },
213
+ };
214
+ }
215
+
216
+ /** Validate before mkdir, stream creation, or other filesystem side effects. */
217
+ function validateFileOptions(options: LoggerFileOptions) {
218
+ if (
219
+ !options
220
+ || typeof options !== 'object'
221
+ || !(
222
+ typeof options.path === 'string'
223
+ || options.path instanceof Path
224
+ || options.path instanceof URL
225
+ || Buffer.isBuffer(options.path)
226
+ )
227
+ ) {
228
+ throw new TypeError('file.path must be a string, Buffer, file URL, or Path');
229
+ }
230
+
231
+ const path = options.path instanceof URL
232
+ ? fileURLToPath(options.path)
233
+ : options.path.toString();
234
+
235
+ if (Buffer.isBuffer(options.path) && !Buffer.from(path).equals(options.path)) {
236
+ throw new TypeError('file.path Buffer must contain valid UTF-8');
237
+ }
238
+
239
+ if (!path.trim() || path.includes('\0')) {
240
+ throw new TypeError('file.path must be a nonempty path without null bytes');
241
+ }
242
+
243
+ const maxPendingBytes = options.maxPendingBytes ?? defaultMaxPendingBytes;
244
+ if (!Number.isSafeInteger(maxPendingBytes) || maxPendingBytes <= 0) {
245
+ throw new RangeError('file.maxPendingBytes must be a positive safe integer');
246
+ }
247
+
248
+ const rotate = options.rotate;
249
+ if (rotate !== undefined && rotate !== false) {
250
+ if (!rotate || typeof rotate !== 'object') {
251
+ throw new TypeError('file.rotate must be false or an options object');
252
+ }
253
+
254
+ if (rotate.interval !== undefined && rotate.interval !== 'hourly' && rotate.interval !== 'daily') {
255
+ throw new TypeError('file.rotate.interval must be hourly or daily');
256
+ }
257
+
258
+ if (rotate.interval === undefined && rotate.maxSize === undefined) {
259
+ throw new TypeError('file.rotate requires interval or maxSize');
260
+ }
261
+
262
+ const keepFiles = rotate.keepFiles ?? 30;
263
+ if (!Number.isSafeInteger(keepFiles) || keepFiles <= 0) {
264
+ throw new RangeError('file.rotate.keepFiles must be a positive safe integer');
265
+ }
266
+
267
+ if (rotate.compress !== undefined && typeof rotate.compress !== 'boolean') {
268
+ throw new TypeError('file.rotate.compress must be a boolean');
269
+ }
270
+
271
+ validateSize(rotate.maxSize, 'file.rotate.maxSize');
272
+ validateSize(rotate.maxTotalSize, 'file.rotate.maxTotalSize');
273
+ }
274
+
275
+ return {
276
+ maxPendingBytes,
277
+ path: Path.resolve(path).value,
278
+ rotate,
279
+ };
280
+ }
281
+
282
+ function validateSize(value: string | undefined, name: string) {
283
+ if (value === undefined) return;
284
+ if (typeof value !== 'string' || !/^[1-9]\d*[BKMG]$/.test(value)) {
285
+ throw new TypeError(`${name} must be a positive integer followed by B, K, M, or G`);
286
+ }
287
+
288
+ const multiplier = {
289
+ B: 1,
290
+ G: 1024 ** 3,
291
+ K: 1024,
292
+ M: 1024 ** 2,
293
+ }[value.at(-1) as 'B' | 'G' | 'K' | 'M'];
294
+
295
+ if (!Number.isSafeInteger(Number(value.slice(0, -1)) * multiplier)) {
296
+ throw new RangeError(`${name} exceeds the supported size range`);
297
+ }
298
+ }
@@ -0,0 +1,33 @@
1
+ import { inspect } from 'node:util';
2
+
3
+ import { formatLoggerParts } from '../../_internals/formatter';
4
+ import type { LoggerRecord } from '../../_internals/types';
5
+
6
+ // Functions
7
+
8
+ /** Preserve user ANSI for console output without invoking custom inspect callbacks/getters. */
9
+ export function formatNodeLoggerRecord(record: LoggerRecord, colors = false) {
10
+ const message = record.args.map(
11
+ (arg) => typeof arg === 'string'
12
+ ? arg
13
+ : inspect(
14
+ arg,
15
+ {
16
+ colors: false,
17
+ customInspect: false,
18
+ depth: 6,
19
+ getters: false,
20
+ maxArrayLength: 100,
21
+ maxStringLength: 10_000,
22
+ },
23
+ ),
24
+ ).join(' ');
25
+
26
+ return formatLoggerParts(
27
+ {
28
+ ...record,
29
+ args: record.args.length ? [message] : [],
30
+ },
31
+ colors,
32
+ ).join(' ');
33
+ }
@@ -0,0 +1,175 @@
1
+ import { writeSync } from 'node:fs';
2
+ import { stripVTControlCharacters } from 'node:util';
3
+
4
+ import { supportsLoggerColor } from '../_internals/formatter';
5
+ import { getLoggerStreamName } from '../_internals/levels';
6
+ import {
7
+ createDiagnosticReporter,
8
+ createLoggerCore,
9
+ } from '../_internals/logger';
10
+ import type { LoggerDiagnostic } from '../types';
11
+
12
+ import { createFileWriter } from './_internals/file-writer';
13
+ import { formatNodeLoggerRecord } from './_internals/formatter';
14
+ import type {
15
+ NodeLogger,
16
+ NodeLoggerOptions,
17
+ } from './types';
18
+
19
+ interface ConsoleStreamState {
20
+ onClose: () => void;
21
+ onError: (error: Error) => void;
22
+ owners: Set<object>;
23
+ pending: Set<(error?: Error | null) => void>;
24
+ }
25
+
26
+ // Constants/Variables
27
+ const consoleStreams = new WeakMap<NodeJS.WriteStream, ConsoleStreamState>();
28
+
29
+ // Functions
30
+
31
+ /**
32
+ * Creates a Node.js/Bun logger with optional bounded, append-only file output and rotation.
33
+ *
34
+ * Log calls never throw or reject. Awaiting them waits for processing, not proof of successful
35
+ * persistence: overload drops the newest file record and failures are reported safely. Factory
36
+ * validation/open failures reject. Call close explicitly; no process signal handlers are installed.
37
+ */
38
+ export async function createNodeLogger(options: NodeLoggerOptions = {}): Promise<NodeLogger> {
39
+ const report = createDiagnosticReporter(options.onError, (message) => writeSync(2, message));
40
+ let writer: Awaited<ReturnType<typeof createFileWriter>> | undefined;
41
+ let closed = false;
42
+ let closePromise: Promise<void> | undefined;
43
+ const consoleOwner = {};
44
+ const consoleTargets = new Set<NodeJS.WriteStream>();
45
+ const consoleWrites = new Set<Promise<void>>();
46
+ const logger = createLoggerCore(
47
+ options,
48
+ {
49
+ report,
50
+ write(record) {
51
+ if (closed) {
52
+ report({
53
+ message: 'Logger is closed.',
54
+ type: 'closed',
55
+ });
56
+
57
+ return;
58
+ }
59
+
60
+ if (!record.console && !writer) return;
61
+ const target = process[getLoggerStreamName(record.type)];
62
+
63
+ const text = formatNodeLoggerRecord(record, record.console && supportsLoggerColor(target));
64
+ if (record.console) {
65
+ const line = `${text}\n`;
66
+ consoleTargets.add(target);
67
+ const completion = writeConsole(target, line, report, consoleOwner);
68
+ consoleWrites.add(completion);
69
+ void completion.then(() => consoleWrites.delete(completion));
70
+ const fileCompletion = writer?.write(`${stripVTControlCharacters(text)}\n`);
71
+ return fileCompletion
72
+ ? Promise.all([
73
+ completion,
74
+ fileCompletion,
75
+ ]).then(() => {})
76
+ : completion;
77
+ }
78
+
79
+ return writer?.write(`${stripVTControlCharacters(text)}\n`);
80
+ },
81
+ },
82
+ );
83
+
84
+ if (options.file !== undefined && options.file !== false) writer = await createFileWriter(options.file, report);
85
+
86
+ return Object.assign(
87
+ logger,
88
+ {
89
+ close() {
90
+ if (closePromise) return closePromise;
91
+ closed = true;
92
+ closePromise = (async () => {
93
+ await Promise.all([
94
+ writer?.close(),
95
+ ...consoleWrites,
96
+ ]);
97
+
98
+ for (const target of consoleTargets) {
99
+ const state = consoleStreams.get(target);
100
+ if (!state) continue;
101
+ state.owners.delete(consoleOwner);
102
+ if (!state.owners.size) {
103
+ target.off('error', state.onError);
104
+ target.off('close', state.onClose);
105
+ consoleStreams.delete(target);
106
+ }
107
+ }
108
+
109
+ consoleTargets.clear();
110
+ })();
111
+ return closePromise;
112
+ },
113
+ getStats: () => writer?.getStats() ?? {
114
+ droppedCount: 0,
115
+ pendingBytes: 0,
116
+ writeErrorCount: 0,
117
+ },
118
+ },
119
+ );
120
+ }
121
+
122
+ /** One error listener per shared output stream; retained until its last owning logger calls close(). */
123
+ function writeConsole(
124
+ target: NodeJS.WriteStream,
125
+ text: string,
126
+ report: (event: LoggerDiagnostic) => void,
127
+ owner: object,
128
+ ) {
129
+ let state = consoleStreams.get(target);
130
+ if (!state) {
131
+ const pending = new Set<(error?: Error | null) => void>();
132
+ const onError = (error: Error) => {
133
+ for (const settle of pending) settle(error);
134
+ };
135
+
136
+ const onClose = () => onError(new Error('Console stream closed.'));
137
+ state = {
138
+ onClose,
139
+ onError,
140
+ owners: new Set(),
141
+ pending,
142
+ };
143
+
144
+ consoleStreams.set(target, state);
145
+ target.on('error', onError);
146
+ target.on('close', onClose);
147
+ }
148
+
149
+ state.owners.add(owner);
150
+ const current = state;
151
+ return new Promise<void>((resolveWritten) => {
152
+ let settled = false;
153
+ const settle = (error?: Error | null) => {
154
+ if (settled) return;
155
+ settled = true;
156
+ current.pending.delete(settle);
157
+ if (error) {
158
+ report({
159
+ error,
160
+ message: 'Console output failed.',
161
+ type: 'console',
162
+ });
163
+ }
164
+
165
+ resolveWritten();
166
+ };
167
+
168
+ current.pending.add(settle);
169
+ try {
170
+ target.write(text, settle);
171
+ } catch (error) {
172
+ settle(error instanceof Error ? error : new Error('Console output failed.'));
173
+ }
174
+ });
175
+ }
@@ -0,0 +1,58 @@
1
+ import type { PathLike } from '../../classes/path';
2
+ import type {
3
+ Logger,
4
+ LoggerOptions,
5
+ } from '../types';
6
+
7
+ /** Specify interval and/or maxSize. Size limits use positive integers with B, K, M, or G (binary units). */
8
+ export type LoggerRotationOptions = {
9
+ /** Gzip historical files. Defaults to true. */
10
+ compress?: boolean;
11
+
12
+ /** Number of historical files, excluding the active file. Defaults to 30. */
13
+ keepFiles?: number;
14
+
15
+ /** Maximum combined size of historical files. */
16
+ maxTotalSize?: string;
17
+ } & (
18
+ | {
19
+ /** Opt-in time rotation aligned to the system's local calendar boundary. */
20
+ interval: 'daily' | 'hourly';
21
+ maxSize?: string;
22
+ }
23
+ | {
24
+ /** Opt-in time rotation aligned to the system's local calendar boundary. */
25
+ interval?: 'daily' | 'hourly';
26
+ maxSize: string;
27
+ }
28
+ );
29
+
30
+ export interface LoggerFileOptions {
31
+ /** Maximum unfinished payload bytes, including data already submitted to the stream. */
32
+ maxPendingBytes?: number;
33
+
34
+ /** Resolved once at creation. The file is opened in append mode. */
35
+ path: PathLike;
36
+
37
+ /** Omitted or false disables rotation. An object must specify interval or maxSize. */
38
+ rotate?: false | LoggerRotationOptions;
39
+ }
40
+
41
+ export interface NodeLogger extends Logger {
42
+ /** Stop accepting records, finish accepted file writes, then release the stream. Not fsync. */
43
+ close: () => Promise<void>;
44
+
45
+ /** A fresh snapshot. Dropped/failed records still resolve their logging promises. */
46
+ getStats: () => NodeLoggerStats;
47
+ }
48
+
49
+ export interface NodeLoggerOptions extends LoggerOptions {
50
+ /** Disabled by default. Each logger owns at most one fixed file destination. */
51
+ file?: false | LoggerFileOptions;
52
+ }
53
+
54
+ export interface NodeLoggerStats {
55
+ droppedCount: number;
56
+ pendingBytes: number;
57
+ writeErrorCount: number;
58
+ }
@@ -0,0 +1,59 @@
1
+ /** Minimum severity to emit; `success` uses the same threshold as `info`. */
2
+ export type LoggerLevel = 'debug' | 'error' | 'fatal' | 'info' | 'silent' | 'trace' | 'warn';
3
+
4
+ /** A logging method, including the informational success presentation. */
5
+ export type LoggerMethod = 'success' | Exclude<LoggerLevel, 'silent'>;
6
+
7
+ /**
8
+ * A logger whose logging methods never throw or reject for recoverable failures.
9
+ *
10
+ * Awaiting a method waits for processing, not guaranteed successful delivery or fsync.
11
+ * Configuration errors may throw from the factory, configure(), or setLevel().
12
+ */
13
+ export interface Logger {
14
+ configure: (options: Pick<LoggerOptions, 'console' | 'icons' | 'prefix' | 'suffix' | 'timestamp'>) => void;
15
+ debug: (...args: unknown[]) => Promise<void>;
16
+ error: (...args: unknown[]) => Promise<void>;
17
+ fatal: (...args: unknown[]) => Promise<void>;
18
+ info: (...args: unknown[]) => Promise<void>;
19
+
20
+ /** Accepts success as an informational method; it is not a configurable threshold. */
21
+ isLevelEnabled: (level: LoggerLevel | LoggerMethod) => boolean;
22
+
23
+ /** Sets a severity threshold (not success); env restores environment-driven filtering. */
24
+ setLevel: (level: 'env' | LoggerLevel) => void;
25
+ success: (...args: unknown[]) => Promise<void>;
26
+ trace: (...args: unknown[]) => Promise<void>;
27
+ warn: (...args: unknown[]) => Promise<void>;
28
+ }
29
+
30
+ /** A recoverable logging failure, reported without throwing from log methods. */
31
+ export interface LoggerDiagnostic {
32
+ droppedCount?: number;
33
+ error?: unknown;
34
+ message: string;
35
+
36
+ /** Same-type notifications suppressed since the previous emitted diagnostic. */
37
+ suppressedCount?: number;
38
+ type: 'closed' | 'console' | 'file' | 'format' | 'invalid-level' | 'overflow';
39
+ }
40
+
41
+ /** Configuration shared by browser and server loggers. */
42
+ export interface LoggerOptions {
43
+ /** Enables console output. Defaults to `true`. */
44
+ console?: boolean;
45
+
46
+ /** Enables Unicode level icons. Defaults to `true`. */
47
+ icons?: boolean;
48
+
49
+ /** Overrides LOG_LEVEL; `env` restores the environment-driven threshold. */
50
+ level?: 'env' | LoggerLevel;
51
+
52
+ /** Best-effort, rate-limited notification; failures in this callback are contained. */
53
+ onError?: (event: LoggerDiagnostic) => void;
54
+ prefix?: string;
55
+ suffix?: string;
56
+
57
+ /** Enables the fixed local-time millisecond prefix. Defaults to `true`. */
58
+ timestamp?: boolean;
59
+ }
@@ -6,11 +6,6 @@ import type {
6
6
 
7
7
  export type { FilteredKeyPath } from './filtered-key-path';
8
8
 
9
- /**
10
- * A generic string-keyed object for utility APIs that intentionally accept any value shape.
11
- */
12
- export type AnyRecord = Record<string, any>;
13
-
14
9
  /**
15
10
  * Binary-like inputs accepted by `toBuffer` across browser and Node.js runtimes.
16
11
  */
@@ -31,11 +26,6 @@ export type MaybePartial<T> = Partial<T> | T;
31
26
  */
32
27
  export type MaybeReadonly<T> = Readonly<T> | T;
33
28
 
34
- /**
35
- * Number-like value represented as a number or a numeric string.
36
- */
37
- export type Numberish = number | string;
38
-
39
29
  /**
40
30
  * A `Record` where every key is optional.
41
31
  */