lifecycleion 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +22 -0
- package/README.md +125 -0
- package/dist/index.cjs +7 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +2 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +5 -0
- package/dist/index.js.map +1 -0
- package/dist/lib/arrays.cjs +95 -0
- package/dist/lib/arrays.cjs.map +1 -0
- package/dist/lib/arrays.d.cts +15 -0
- package/dist/lib/arrays.d.ts +15 -0
- package/dist/lib/arrays.js +63 -0
- package/dist/lib/arrays.js.map +1 -0
- package/dist/lib/ascii-tables/index.cjs +642 -0
- package/dist/lib/ascii-tables/index.cjs.map +1 -0
- package/dist/lib/ascii-tables/index.d.cts +66 -0
- package/dist/lib/ascii-tables/index.d.ts +66 -0
- package/dist/lib/ascii-tables/index.js +603 -0
- package/dist/lib/ascii-tables/index.js.map +1 -0
- package/dist/lib/clamp.cjs +41 -0
- package/dist/lib/clamp.cjs.map +1 -0
- package/dist/lib/clamp.d.cts +26 -0
- package/dist/lib/clamp.d.ts +26 -0
- package/dist/lib/clamp.js +15 -0
- package/dist/lib/clamp.js.map +1 -0
- package/dist/lib/constants.cjs +73 -0
- package/dist/lib/constants.cjs.map +1 -0
- package/dist/lib/constants.d.cts +17 -0
- package/dist/lib/constants.d.ts +17 -0
- package/dist/lib/constants.js +34 -0
- package/dist/lib/constants.js.map +1 -0
- package/dist/lib/curly-brackets.cjs +77 -0
- package/dist/lib/curly-brackets.cjs.map +1 -0
- package/dist/lib/curly-brackets.d.cts +17 -0
- package/dist/lib/curly-brackets.d.ts +17 -0
- package/dist/lib/curly-brackets.js +52 -0
- package/dist/lib/curly-brackets.js.map +1 -0
- package/dist/lib/deep-clone.cjs +87 -0
- package/dist/lib/deep-clone.cjs.map +1 -0
- package/dist/lib/deep-clone.d.cts +19 -0
- package/dist/lib/deep-clone.d.ts +19 -0
- package/dist/lib/deep-clone.js +62 -0
- package/dist/lib/deep-clone.js.map +1 -0
- package/dist/lib/error-to-string.cjs +743 -0
- package/dist/lib/error-to-string.cjs.map +1 -0
- package/dist/lib/error-to-string.d.cts +3 -0
- package/dist/lib/error-to-string.d.ts +3 -0
- package/dist/lib/error-to-string.js +706 -0
- package/dist/lib/error-to-string.js.map +1 -0
- package/dist/lib/event-emitter.cjs +899 -0
- package/dist/lib/event-emitter.cjs.map +1 -0
- package/dist/lib/event-emitter.d.cts +78 -0
- package/dist/lib/event-emitter.d.ts +78 -0
- package/dist/lib/event-emitter.js +861 -0
- package/dist/lib/event-emitter.js.map +1 -0
- package/dist/lib/id-helpers.cjs +205 -0
- package/dist/lib/id-helpers.cjs.map +1 -0
- package/dist/lib/id-helpers.d.cts +198 -0
- package/dist/lib/id-helpers.d.ts +198 -0
- package/dist/lib/id-helpers.js +170 -0
- package/dist/lib/id-helpers.js.map +1 -0
- package/dist/lib/is-boolean.cjs +33 -0
- package/dist/lib/is-boolean.cjs.map +1 -0
- package/dist/lib/is-boolean.d.cts +19 -0
- package/dist/lib/is-boolean.d.ts +19 -0
- package/dist/lib/is-boolean.js +8 -0
- package/dist/lib/is-boolean.js.map +1 -0
- package/dist/lib/is-function.cjs +33 -0
- package/dist/lib/is-function.cjs.map +1 -0
- package/dist/lib/is-function.d.cts +3 -0
- package/dist/lib/is-function.d.ts +3 -0
- package/dist/lib/is-function.js +8 -0
- package/dist/lib/is-function.js.map +1 -0
- package/dist/lib/is-number.cjs +38 -0
- package/dist/lib/is-number.cjs.map +1 -0
- package/dist/lib/is-number.d.cts +38 -0
- package/dist/lib/is-number.d.ts +38 -0
- package/dist/lib/is-number.js +12 -0
- package/dist/lib/is-number.js.map +1 -0
- package/dist/lib/is-plain-object.cjs +33 -0
- package/dist/lib/is-plain-object.cjs.map +1 -0
- package/dist/lib/is-plain-object.d.cts +20 -0
- package/dist/lib/is-plain-object.d.ts +20 -0
- package/dist/lib/is-plain-object.js +8 -0
- package/dist/lib/is-plain-object.js.map +1 -0
- package/dist/lib/is-promise.cjs +34 -0
- package/dist/lib/is-promise.cjs.map +1 -0
- package/dist/lib/is-promise.d.cts +3 -0
- package/dist/lib/is-promise.d.ts +3 -0
- package/dist/lib/is-promise.js +9 -0
- package/dist/lib/is-promise.js.map +1 -0
- package/dist/lib/json-helpers.cjs +49 -0
- package/dist/lib/json-helpers.cjs.map +1 -0
- package/dist/lib/json-helpers.d.cts +10 -0
- package/dist/lib/json-helpers.d.ts +10 -0
- package/dist/lib/json-helpers.js +22 -0
- package/dist/lib/json-helpers.js.map +1 -0
- package/dist/lib/lifecycle-manager/index.cjs +5594 -0
- package/dist/lib/lifecycle-manager/index.cjs.map +1 -0
- package/dist/lib/lifecycle-manager/index.d.cts +2044 -0
- package/dist/lib/lifecycle-manager/index.d.ts +2044 -0
- package/dist/lib/lifecycle-manager/index.js +5543 -0
- package/dist/lib/lifecycle-manager/index.js.map +1 -0
- package/dist/lib/logger/index.cjs +2514 -0
- package/dist/lib/logger/index.cjs.map +1 -0
- package/dist/lib/logger/index.d.cts +630 -0
- package/dist/lib/logger/index.d.ts +630 -0
- package/dist/lib/logger/index.js +2470 -0
- package/dist/lib/logger/index.js.map +1 -0
- package/dist/lib/padding-utils.cjs +77 -0
- package/dist/lib/padding-utils.cjs.map +1 -0
- package/dist/lib/padding-utils.d.cts +44 -0
- package/dist/lib/padding-utils.d.ts +44 -0
- package/dist/lib/padding-utils.js +46 -0
- package/dist/lib/padding-utils.js.map +1 -0
- package/dist/lib/process-signal-manager.cjs +1306 -0
- package/dist/lib/process-signal-manager.cjs.map +1 -0
- package/dist/lib/process-signal-manager.d.cts +305 -0
- package/dist/lib/process-signal-manager.d.ts +305 -0
- package/dist/lib/process-signal-manager.js +1269 -0
- package/dist/lib/process-signal-manager.js.map +1 -0
- package/dist/lib/promise-protected-resolver.cjs +828 -0
- package/dist/lib/promise-protected-resolver.cjs.map +1 -0
- package/dist/lib/promise-protected-resolver.d.cts +17 -0
- package/dist/lib/promise-protected-resolver.d.ts +17 -0
- package/dist/lib/promise-protected-resolver.js +791 -0
- package/dist/lib/promise-protected-resolver.js.map +1 -0
- package/dist/lib/retry-utils/index.cjs +2183 -0
- package/dist/lib/retry-utils/index.cjs.map +1 -0
- package/dist/lib/retry-utils/index.d.cts +321 -0
- package/dist/lib/retry-utils/index.d.ts +321 -0
- package/dist/lib/retry-utils/index.js +2133 -0
- package/dist/lib/retry-utils/index.js.map +1 -0
- package/dist/lib/safe-handle-callback.cjs +818 -0
- package/dist/lib/safe-handle-callback.cjs.map +1 -0
- package/dist/lib/safe-handle-callback.d.cts +43 -0
- package/dist/lib/safe-handle-callback.d.ts +43 -0
- package/dist/lib/safe-handle-callback.js +780 -0
- package/dist/lib/safe-handle-callback.js.map +1 -0
- package/dist/lib/serialize-error/index.cjs +93 -0
- package/dist/lib/serialize-error/index.cjs.map +1 -0
- package/dist/lib/serialize-error/index.d.cts +26 -0
- package/dist/lib/serialize-error/index.d.ts +26 -0
- package/dist/lib/serialize-error/index.js +64 -0
- package/dist/lib/serialize-error/index.js.map +1 -0
- package/dist/lib/single-event-observer.cjs +841 -0
- package/dist/lib/single-event-observer.cjs.map +1 -0
- package/dist/lib/single-event-observer.d.cts +54 -0
- package/dist/lib/single-event-observer.d.ts +54 -0
- package/dist/lib/single-event-observer.js +803 -0
- package/dist/lib/single-event-observer.js.map +1 -0
- package/dist/lib/sleep.cjs +37 -0
- package/dist/lib/sleep.cjs.map +1 -0
- package/dist/lib/sleep.d.cts +11 -0
- package/dist/lib/sleep.d.ts +11 -0
- package/dist/lib/sleep.js +12 -0
- package/dist/lib/sleep.js.map +1 -0
- package/dist/lib/strings.cjs +186 -0
- package/dist/lib/strings.cjs.map +1 -0
- package/dist/lib/strings.d.cts +107 -0
- package/dist/lib/strings.d.ts +107 -0
- package/dist/lib/strings.js +149 -0
- package/dist/lib/strings.js.map +1 -0
- package/dist/lib/tmp-dir.cjs +254 -0
- package/dist/lib/tmp-dir.cjs.map +1 -0
- package/dist/lib/tmp-dir.d.cts +63 -0
- package/dist/lib/tmp-dir.d.ts +63 -0
- package/dist/lib/tmp-dir.js +211 -0
- package/dist/lib/tmp-dir.js.map +1 -0
- package/dist/lib/unix-time-helpers.cjs +53 -0
- package/dist/lib/unix-time-helpers.cjs.map +1 -0
- package/dist/lib/unix-time-helpers.d.cts +56 -0
- package/dist/lib/unix-time-helpers.d.ts +56 -0
- package/dist/lib/unix-time-helpers.js +24 -0
- package/dist/lib/unix-time-helpers.js.map +1 -0
- package/package.json +220 -0
|
@@ -0,0 +1,2470 @@
|
|
|
1
|
+
// src/lib/strings.ts
|
|
2
|
+
function isString(value) {
|
|
3
|
+
return typeof value === "string";
|
|
4
|
+
}
|
|
5
|
+
function splitGraphemes(text) {
|
|
6
|
+
const graphemes = [];
|
|
7
|
+
let grapheme = "";
|
|
8
|
+
let zwjSequence = "";
|
|
9
|
+
for (let i = 0; i < text.length; i++) {
|
|
10
|
+
const char = text[i];
|
|
11
|
+
const nextChar = text[i + 1] || "";
|
|
12
|
+
const code = char.charCodeAt(0);
|
|
13
|
+
if (code >= 768 && code <= 879 || // Combining Diacritical Marks
|
|
14
|
+
code >= 6832 && code <= 6911 || // Combining Diacritical Marks Extended
|
|
15
|
+
code >= 7616 && code <= 7679 || // Combining Diacritical Marks Supplement
|
|
16
|
+
code >= 65056 && code <= 65071 || // Combining Half Marks
|
|
17
|
+
code >= 3633 && code <= 3642 || // Thai combining marks
|
|
18
|
+
code >= 3655 && code <= 3662) {
|
|
19
|
+
grapheme += char;
|
|
20
|
+
} else if (char === "\u200D") {
|
|
21
|
+
zwjSequence += grapheme + char;
|
|
22
|
+
grapheme = "";
|
|
23
|
+
} else {
|
|
24
|
+
if (grapheme) {
|
|
25
|
+
if (zwjSequence) {
|
|
26
|
+
graphemes.push(zwjSequence + grapheme);
|
|
27
|
+
zwjSequence = "";
|
|
28
|
+
} else {
|
|
29
|
+
graphemes.push(grapheme);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
grapheme = char;
|
|
33
|
+
if (char >= "\uD800" && char <= "\uDBFF" && nextChar >= "\uDC00" && nextChar <= "\uDFFF") {
|
|
34
|
+
grapheme += nextChar;
|
|
35
|
+
i++;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
if (grapheme) {
|
|
40
|
+
if (zwjSequence) {
|
|
41
|
+
graphemes.push(zwjSequence + grapheme);
|
|
42
|
+
} else {
|
|
43
|
+
graphemes.push(grapheme);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
return graphemes;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// src/lib/constants.ts
|
|
50
|
+
var BLANK_SPACE = " ";
|
|
51
|
+
var EOL = "\n";
|
|
52
|
+
var DOUBLE_EOL = EOL + EOL;
|
|
53
|
+
var INDENT = " ".repeat(4);
|
|
54
|
+
var DOUBLE_INDENT = INDENT + INDENT;
|
|
55
|
+
var ASCII_LOWERCASE = "abcdefghijklmnopqrstuvwxyz";
|
|
56
|
+
var ASCII_UPPERCASE = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
|
|
57
|
+
var ASCII_LETTERS = ASCII_LOWERCASE + ASCII_UPPERCASE;
|
|
58
|
+
var DIGITS = "0123456789";
|
|
59
|
+
var HEX_DIGITS = DIGITS + "abcdefABCDEF";
|
|
60
|
+
var PUNCTUATION = "!\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~";
|
|
61
|
+
var WHITESPACE = " \n\r\v\f";
|
|
62
|
+
var PRINTABLE = DIGITS + ASCII_LETTERS + PUNCTUATION + WHITESPACE;
|
|
63
|
+
|
|
64
|
+
// src/lib/padding-utils.ts
|
|
65
|
+
function padLeft(str, length, padStr = BLANK_SPACE) {
|
|
66
|
+
return str.padStart(length, padStr);
|
|
67
|
+
}
|
|
68
|
+
function padRight(str, length, padStr = BLANK_SPACE) {
|
|
69
|
+
return str.padEnd(length, padStr);
|
|
70
|
+
}
|
|
71
|
+
function padCenter(str, length, prefer = "left", padStr = BLANK_SPACE) {
|
|
72
|
+
const midStrLength = length - str.length;
|
|
73
|
+
if (midStrLength > 0) {
|
|
74
|
+
const padLeftAmount = prefer === "left" ? Math.ceil(midStrLength / 2) : Math.floor(midStrLength / 2);
|
|
75
|
+
const padRightAmount = prefer === "left" ? Math.floor(midStrLength / 2) : Math.ceil(midStrLength / 2);
|
|
76
|
+
return padLeft("", padLeftAmount, padStr) + str + padRight("", padRightAmount, padStr);
|
|
77
|
+
} else {
|
|
78
|
+
return str;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
function padCenterPreferRight(str, length, padStr = BLANK_SPACE) {
|
|
82
|
+
return padCenter(str, length, "right", padStr);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// src/lib/ascii-tables/ascii-table-utils.ts
|
|
86
|
+
import stringWidth from "string-width";
|
|
87
|
+
var ASCIITableUtils = class _ASCIITableUtils {
|
|
88
|
+
static centerText(text, width) {
|
|
89
|
+
return padCenterPreferRight(text, width, " ");
|
|
90
|
+
}
|
|
91
|
+
static createSeparator(columnWidths, character = "=") {
|
|
92
|
+
const totalWidth = columnWidths.reduce((sum, width) => sum + width + 3, 0) - 1;
|
|
93
|
+
return `+${padRight("", totalWidth, character)}+`;
|
|
94
|
+
}
|
|
95
|
+
static wrapText(text, maxLength) {
|
|
96
|
+
const words = text.split(" ");
|
|
97
|
+
const lines = [];
|
|
98
|
+
let currentLine = "";
|
|
99
|
+
for (const word of words) {
|
|
100
|
+
if (stringWidth(currentLine) + stringWidth(word) + 1 <= maxLength) {
|
|
101
|
+
currentLine += (currentLine ? " " : "") + word;
|
|
102
|
+
} else {
|
|
103
|
+
if (currentLine) {
|
|
104
|
+
lines.push(currentLine);
|
|
105
|
+
}
|
|
106
|
+
if (stringWidth(word) <= maxLength) {
|
|
107
|
+
currentLine = word;
|
|
108
|
+
} else {
|
|
109
|
+
const subWords = _ASCIITableUtils.splitWord(word, maxLength);
|
|
110
|
+
lines.push(...subWords.slice(0, -1));
|
|
111
|
+
currentLine = subWords[subWords.length - 1];
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
if (currentLine) {
|
|
116
|
+
lines.push(currentLine);
|
|
117
|
+
}
|
|
118
|
+
return lines;
|
|
119
|
+
}
|
|
120
|
+
static splitWord(word, maxLength) {
|
|
121
|
+
const graphemes = splitGraphemes(word);
|
|
122
|
+
const subWords = [];
|
|
123
|
+
let currentSubWord = "";
|
|
124
|
+
for (const grapheme of graphemes) {
|
|
125
|
+
if (stringWidth(currentSubWord + grapheme) <= maxLength) {
|
|
126
|
+
currentSubWord += grapheme;
|
|
127
|
+
} else {
|
|
128
|
+
subWords.push(currentSubWord);
|
|
129
|
+
currentSubWord = grapheme;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
if (currentSubWord) {
|
|
133
|
+
subWords.push(currentSubWord);
|
|
134
|
+
}
|
|
135
|
+
return subWords;
|
|
136
|
+
}
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
// src/lib/ascii-tables/multi-column-ascii-table.ts
|
|
140
|
+
import stringWidth2 from "string-width";
|
|
141
|
+
var MultiColumnASCIITable = class {
|
|
142
|
+
headers;
|
|
143
|
+
rows;
|
|
144
|
+
tableWidth;
|
|
145
|
+
emptyMessage;
|
|
146
|
+
widthMode;
|
|
147
|
+
constructor(headers, options = {}) {
|
|
148
|
+
this.headers = headers;
|
|
149
|
+
this.rows = [];
|
|
150
|
+
this.tableWidth = options.tableWidth || 80;
|
|
151
|
+
this.emptyMessage = options.emptyMessage || "";
|
|
152
|
+
this.widthMode = options.widthMode || "flex";
|
|
153
|
+
const minTableWidth = this.getMinimumWidth();
|
|
154
|
+
if (this.tableWidth < minTableWidth) {
|
|
155
|
+
throw new Error(
|
|
156
|
+
`Table width must be at least ${minTableWidth} to accommodate the headers.`
|
|
157
|
+
);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
getMinimumWidth() {
|
|
161
|
+
return this.headers.length * 4 + 1;
|
|
162
|
+
}
|
|
163
|
+
addRow(row) {
|
|
164
|
+
if (row.length !== this.headers.length) {
|
|
165
|
+
throw new Error(
|
|
166
|
+
`Number of values in the row (${row.length}) must match the number of headers (${this.headers.length}).`
|
|
167
|
+
);
|
|
168
|
+
}
|
|
169
|
+
this.rows.push(row);
|
|
170
|
+
}
|
|
171
|
+
toString(options = {}) {
|
|
172
|
+
const tableWidth = options.tableWidth || this.tableWidth;
|
|
173
|
+
const emptyMessage = options.emptyMessage || this.emptyMessage;
|
|
174
|
+
if (this.rows.length === 0) {
|
|
175
|
+
const emptyTableWidth = Math.min(tableWidth, 40);
|
|
176
|
+
const separator = "+" + "-".repeat(emptyTableWidth - 2) + "+";
|
|
177
|
+
const emptyMessageLines = ASCIITableUtils.wrapText(
|
|
178
|
+
emptyMessage,
|
|
179
|
+
emptyTableWidth - 4
|
|
180
|
+
);
|
|
181
|
+
const emptyRows = emptyMessageLines.map((line) => {
|
|
182
|
+
const paddingLeft = " ".repeat(
|
|
183
|
+
Math.floor((emptyTableWidth - stringWidth2(line) - 4) / 2)
|
|
184
|
+
);
|
|
185
|
+
const paddingRight = " ".repeat(
|
|
186
|
+
Math.ceil((emptyTableWidth - stringWidth2(line) - 4) / 2)
|
|
187
|
+
);
|
|
188
|
+
return `| ${paddingLeft}${line}${paddingRight} |`;
|
|
189
|
+
});
|
|
190
|
+
if (emptyRows.length === 0) {
|
|
191
|
+
emptyRows.push(`| ${" ".repeat(emptyTableWidth - 4)} |`);
|
|
192
|
+
}
|
|
193
|
+
return [separator, ...emptyRows, separator].join("\n");
|
|
194
|
+
}
|
|
195
|
+
const columnWidths = this.calculateColumnWidths(options);
|
|
196
|
+
const headerSeparator = ASCIITableUtils.createSeparator(columnWidths);
|
|
197
|
+
const rowSeparator = ASCIITableUtils.createSeparator(columnWidths, "-");
|
|
198
|
+
let tableString = headerSeparator + "\n";
|
|
199
|
+
const header = this.renderRow(this.headers, columnWidths);
|
|
200
|
+
tableString += header + "\n" + rowSeparator + "\n";
|
|
201
|
+
const rows = this.rows.map((row) => {
|
|
202
|
+
return this.renderRow(row, columnWidths);
|
|
203
|
+
});
|
|
204
|
+
tableString += rows.join("\n" + rowSeparator + "\n");
|
|
205
|
+
tableString += "\n" + headerSeparator;
|
|
206
|
+
return tableString;
|
|
207
|
+
}
|
|
208
|
+
calculateColumnWidths(options = {}) {
|
|
209
|
+
const tableWidth = options.tableWidth || this.tableWidth;
|
|
210
|
+
const widthMode = options.widthMode || this.widthMode;
|
|
211
|
+
const numColumns = this.headers.length;
|
|
212
|
+
if (widthMode === "fixed") {
|
|
213
|
+
const availableWidth2 = tableWidth - (numColumns + 1) * 3 + 1;
|
|
214
|
+
const columnWidth = Math.floor(availableWidth2 / numColumns);
|
|
215
|
+
const extraWidth = availableWidth2 % numColumns;
|
|
216
|
+
const columnWidths = new Array(numColumns).fill(columnWidth);
|
|
217
|
+
for (let i = 0; i < extraWidth; i++) {
|
|
218
|
+
columnWidths[i] += 1;
|
|
219
|
+
}
|
|
220
|
+
return columnWidths;
|
|
221
|
+
}
|
|
222
|
+
const availableWidth = tableWidth - (numColumns + 1) * 3 + 1;
|
|
223
|
+
const maxColumnWidth = Math.floor(availableWidth / numColumns);
|
|
224
|
+
const totalContentWidth = this.headers.reduce(
|
|
225
|
+
(sum, header) => sum + stringWidth2(header),
|
|
226
|
+
0
|
|
227
|
+
);
|
|
228
|
+
if (availableWidth >= totalContentWidth) {
|
|
229
|
+
const remainingWidth = availableWidth - totalContentWidth;
|
|
230
|
+
const extraCharWidth = Math.floor(remainingWidth / numColumns);
|
|
231
|
+
const extraCharRemainder = remainingWidth % numColumns;
|
|
232
|
+
const columnWidths = this.headers.map((header, index) => {
|
|
233
|
+
const extraWidth = index < extraCharRemainder ? 1 : 0;
|
|
234
|
+
return stringWidth2(header) + extraCharWidth + extraWidth;
|
|
235
|
+
});
|
|
236
|
+
return columnWidths;
|
|
237
|
+
} else {
|
|
238
|
+
const columnWidths = this.headers.map(() => maxColumnWidth);
|
|
239
|
+
return columnWidths;
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
renderRow(row, columnWidths) {
|
|
243
|
+
const wrappedCells = row.map((value, index) => {
|
|
244
|
+
const wrappedLines = ASCIITableUtils.wrapText(value, columnWidths[index]);
|
|
245
|
+
return wrappedLines.map((line) => line.padEnd(columnWidths[index])).join("\n");
|
|
246
|
+
});
|
|
247
|
+
const maxLines = Math.max(
|
|
248
|
+
...wrappedCells.map((cell) => cell.split("\n").length)
|
|
249
|
+
);
|
|
250
|
+
const paddedRows = [];
|
|
251
|
+
for (let i = 0; i < maxLines; i++) {
|
|
252
|
+
const rowLine = wrappedCells.map((cell, index) => {
|
|
253
|
+
const cellLines = cell.split("\n");
|
|
254
|
+
const cellLine = cellLines[i] || "";
|
|
255
|
+
const padding = " ".repeat(columnWidths[index] - stringWidth2(cellLine));
|
|
256
|
+
return " " + cellLine + padding + " ";
|
|
257
|
+
});
|
|
258
|
+
paddedRows.push("|" + rowLine.join("|") + "|");
|
|
259
|
+
}
|
|
260
|
+
return paddedRows.join("\n");
|
|
261
|
+
}
|
|
262
|
+
};
|
|
263
|
+
|
|
264
|
+
// src/lib/ascii-tables/key-value-ascii-table.ts
|
|
265
|
+
import stringWidth3 from "string-width";
|
|
266
|
+
|
|
267
|
+
// src/lib/clamp.ts
|
|
268
|
+
function clamp(value, min, max) {
|
|
269
|
+
return Math.max(min, Math.min(value, max));
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
// src/lib/ascii-tables/key-value-ascii-table.ts
|
|
273
|
+
var KeyValueASCIITable = class _KeyValueASCIITable {
|
|
274
|
+
tableWidth;
|
|
275
|
+
emptyMessage;
|
|
276
|
+
autoAdjustWidthWhenPossible = true;
|
|
277
|
+
rows = [];
|
|
278
|
+
constructor(options = {}) {
|
|
279
|
+
const minTableWidth = this.getMinimumWidth();
|
|
280
|
+
if (options.tableWidth && options.tableWidth < minTableWidth) {
|
|
281
|
+
throw new Error(
|
|
282
|
+
`Table width must be at least ${minTableWidth} to accommodate the table structure.`
|
|
283
|
+
);
|
|
284
|
+
}
|
|
285
|
+
this.tableWidth = options.tableWidth || 80;
|
|
286
|
+
this.autoAdjustWidthWhenPossible = options.autoAdjustWidthWhenPossible ?? true;
|
|
287
|
+
this.emptyMessage = options.emptyMessage || "";
|
|
288
|
+
}
|
|
289
|
+
getMinimumWidth() {
|
|
290
|
+
return 9;
|
|
291
|
+
}
|
|
292
|
+
/**
|
|
293
|
+
* Adds key and value to the table but placing the value on its own row.
|
|
294
|
+
*
|
|
295
|
+
* @param key
|
|
296
|
+
* @param value
|
|
297
|
+
*/
|
|
298
|
+
addValueOnSeparateRow(key, value) {
|
|
299
|
+
const row = { kind: "own", key, value };
|
|
300
|
+
this.rows.push(row);
|
|
301
|
+
}
|
|
302
|
+
/**
|
|
303
|
+
* Adds key and value to the table.
|
|
304
|
+
*
|
|
305
|
+
* If provided value is an instance of ASCIITable or MultiColumnASCIITable, it will be rendered as a nested table on its own row for readability.
|
|
306
|
+
*
|
|
307
|
+
* @param key
|
|
308
|
+
* @param value
|
|
309
|
+
*/
|
|
310
|
+
addRow(key, value) {
|
|
311
|
+
const row = { kind: "regular", key, value };
|
|
312
|
+
this.rows.push(row);
|
|
313
|
+
}
|
|
314
|
+
toString(options = {}) {
|
|
315
|
+
const tableWidth = options.tableWidth || this.tableWidth;
|
|
316
|
+
const canAutoAdjustWidthWhenPossible = options.autoAdjustWidthWhenPossible ?? this.autoAdjustWidthWhenPossible;
|
|
317
|
+
const emptyMessage = options.emptyMessage || this.emptyMessage;
|
|
318
|
+
if (this.rows.length === 0) {
|
|
319
|
+
const emptyTableWidth = Math.min(tableWidth, 40);
|
|
320
|
+
const separator = "+" + "-".repeat(emptyTableWidth - 2) + "+";
|
|
321
|
+
const emptyMessageLines = ASCIITableUtils.wrapText(
|
|
322
|
+
emptyMessage,
|
|
323
|
+
emptyTableWidth - 4
|
|
324
|
+
);
|
|
325
|
+
const emptyRows = emptyMessageLines.map((line) => {
|
|
326
|
+
const paddingLeft = padRight(
|
|
327
|
+
"",
|
|
328
|
+
Math.floor((emptyTableWidth - stringWidth3(line) - 4) / 2),
|
|
329
|
+
" "
|
|
330
|
+
);
|
|
331
|
+
const paddingRight = padRight(
|
|
332
|
+
"",
|
|
333
|
+
Math.ceil((emptyTableWidth - stringWidth3(line) - 4) / 2),
|
|
334
|
+
" "
|
|
335
|
+
);
|
|
336
|
+
return `| ${paddingLeft}${line}${paddingRight} |`;
|
|
337
|
+
});
|
|
338
|
+
if (emptyRows.length === 0) {
|
|
339
|
+
emptyRows.push(`| ${" ".repeat(emptyTableWidth - 4)} |`);
|
|
340
|
+
}
|
|
341
|
+
return [separator, ...emptyRows, separator].join("\n");
|
|
342
|
+
}
|
|
343
|
+
const columnWidths = this.calculateColumnWidths(options);
|
|
344
|
+
const headerSeparator = ASCIITableUtils.createSeparator(columnWidths);
|
|
345
|
+
const rowSeparator = ASCIITableUtils.createSeparator(columnWidths, "-");
|
|
346
|
+
let tableString = headerSeparator + "\n";
|
|
347
|
+
for (const [rowIndex, row] of this.rows.entries()) {
|
|
348
|
+
const { kind, key, value } = row;
|
|
349
|
+
if (kind === "own" || value instanceof _KeyValueASCIITable || value instanceof MultiColumnASCIITable || Array.isArray(value)) {
|
|
350
|
+
const keyString = ASCIITableUtils.centerText(
|
|
351
|
+
key,
|
|
352
|
+
columnWidths[0] + columnWidths[1] + 3
|
|
353
|
+
);
|
|
354
|
+
tableString += `| ${keyString} |
|
|
355
|
+
`;
|
|
356
|
+
tableString += rowSeparator + "\n";
|
|
357
|
+
let valueString = "";
|
|
358
|
+
if (value instanceof _KeyValueASCIITable) {
|
|
359
|
+
valueString = this.formatValue(
|
|
360
|
+
value,
|
|
361
|
+
tableWidth - 4,
|
|
362
|
+
canAutoAdjustWidthWhenPossible,
|
|
363
|
+
""
|
|
364
|
+
);
|
|
365
|
+
} else if (value instanceof MultiColumnASCIITable) {
|
|
366
|
+
valueString = this.formatValue(
|
|
367
|
+
value,
|
|
368
|
+
tableWidth - 4,
|
|
369
|
+
canAutoAdjustWidthWhenPossible,
|
|
370
|
+
""
|
|
371
|
+
);
|
|
372
|
+
} else if (Array.isArray(value)) {
|
|
373
|
+
valueString = this.formatValue(
|
|
374
|
+
value,
|
|
375
|
+
tableWidth - 4,
|
|
376
|
+
canAutoAdjustWidthWhenPossible,
|
|
377
|
+
""
|
|
378
|
+
);
|
|
379
|
+
} else if (row.kind === "own") {
|
|
380
|
+
valueString = this.formatTableRowOnOwnRow(
|
|
381
|
+
value,
|
|
382
|
+
tableWidth - 4,
|
|
383
|
+
tableWidth
|
|
384
|
+
);
|
|
385
|
+
}
|
|
386
|
+
const valueLines = valueString.split("\n");
|
|
387
|
+
const paddedValueLines = valueLines.map((line) => {
|
|
388
|
+
const padding = padRight("", tableWidth - stringWidth3(line) - 4, " ");
|
|
389
|
+
return `| ${line}${padding} |`;
|
|
390
|
+
});
|
|
391
|
+
tableString += paddedValueLines.join("\n") + "\n";
|
|
392
|
+
tableString += headerSeparator + "\n";
|
|
393
|
+
} else {
|
|
394
|
+
const keyLines = ASCIITableUtils.wrapText(key, columnWidths[0]);
|
|
395
|
+
const valueLines = ASCIITableUtils.wrapText(
|
|
396
|
+
this.formatValue(
|
|
397
|
+
value,
|
|
398
|
+
columnWidths[1],
|
|
399
|
+
canAutoAdjustWidthWhenPossible,
|
|
400
|
+
""
|
|
401
|
+
),
|
|
402
|
+
columnWidths[1]
|
|
403
|
+
);
|
|
404
|
+
const maxLines = Math.max(keyLines.length, valueLines.length);
|
|
405
|
+
for (let i = 0; i < maxLines; i++) {
|
|
406
|
+
const keyLine = keyLines[i] || "";
|
|
407
|
+
const valueLine = valueLines[i] || "";
|
|
408
|
+
const keyPadding = " ".repeat(columnWidths[0] - stringWidth3(keyLine));
|
|
409
|
+
const valuePadding = " ".repeat(
|
|
410
|
+
columnWidths[1] - stringWidth3(valueLine)
|
|
411
|
+
);
|
|
412
|
+
tableString += `| ${keyLine}${keyPadding} | ${valueLine}${valuePadding} |
|
|
413
|
+
`;
|
|
414
|
+
if (i === maxLines - 1) {
|
|
415
|
+
if (rowIndex === this.rows.length - 1) {
|
|
416
|
+
tableString += headerSeparator + "\n";
|
|
417
|
+
} else {
|
|
418
|
+
tableString += rowSeparator + "\n";
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
return tableString.trim();
|
|
425
|
+
}
|
|
426
|
+
calculateColumnWidths(options = {}) {
|
|
427
|
+
const tableWidth = options.tableWidth || this.tableWidth;
|
|
428
|
+
const canAutoAdjustWidthWhenPossible = options.autoAdjustWidthWhenPossible ?? this.autoAdjustWidthWhenPossible;
|
|
429
|
+
const columnWidths = [0, 0];
|
|
430
|
+
for (const row of this.rows) {
|
|
431
|
+
const { key, value } = row;
|
|
432
|
+
const keyWidth = stringWidth3(key);
|
|
433
|
+
const maxKeyWidth = Math.floor((tableWidth - 7) / 2);
|
|
434
|
+
if (keyWidth > columnWidths[0]) {
|
|
435
|
+
columnWidths[0] = Math.min(keyWidth, maxKeyWidth);
|
|
436
|
+
columnWidths[1] = Math.max(0, tableWidth - columnWidths[0] - 7);
|
|
437
|
+
}
|
|
438
|
+
if (typeof value === "string") {
|
|
439
|
+
const valueWidth = Math.max(
|
|
440
|
+
...value.split("\n").map((line) => stringWidth3(line))
|
|
441
|
+
);
|
|
442
|
+
if (valueWidth > columnWidths[1]) {
|
|
443
|
+
columnWidths[1] = Math.min(
|
|
444
|
+
valueWidth,
|
|
445
|
+
tableWidth - columnWidths[0] - 7
|
|
446
|
+
);
|
|
447
|
+
columnWidths[0] = Math.max(0, tableWidth - columnWidths[1] - 7);
|
|
448
|
+
}
|
|
449
|
+
} else if (row.kind === "own") {
|
|
450
|
+
let valueWidth = 0;
|
|
451
|
+
if (isString(value)) {
|
|
452
|
+
valueWidth = Math.max(
|
|
453
|
+
...value.split("\n").map((line) => stringWidth3(line))
|
|
454
|
+
);
|
|
455
|
+
}
|
|
456
|
+
if (valueWidth > columnWidths[1]) {
|
|
457
|
+
columnWidths[1] = Math.min(
|
|
458
|
+
valueWidth,
|
|
459
|
+
tableWidth - columnWidths[0] - 7
|
|
460
|
+
);
|
|
461
|
+
columnWidths[0] = Math.max(0, tableWidth - columnWidths[1] - 7);
|
|
462
|
+
}
|
|
463
|
+
} else if (value instanceof _KeyValueASCIITable) {
|
|
464
|
+
let nestedTableColumnWidths = [];
|
|
465
|
+
if (canAutoAdjustWidthWhenPossible) {
|
|
466
|
+
const minWidth = value.getMinimumWidth();
|
|
467
|
+
const availableWidth2 = tableWidth - columnWidths[0] - 7;
|
|
468
|
+
const adjustedWidth = clamp(availableWidth2, minWidth, availableWidth2);
|
|
469
|
+
nestedTableColumnWidths = value.calculateColumnWidths({
|
|
470
|
+
tableWidth: adjustedWidth
|
|
471
|
+
});
|
|
472
|
+
} else {
|
|
473
|
+
nestedTableColumnWidths = value.calculateColumnWidths();
|
|
474
|
+
}
|
|
475
|
+
const nestedTableWidth = nestedTableColumnWidths.reduce((sum, width) => sum + width, 0) + nestedTableColumnWidths.length * 3 - 1;
|
|
476
|
+
const availableWidth = tableWidth - columnWidths[0] - 7;
|
|
477
|
+
if (nestedTableWidth > availableWidth) {
|
|
478
|
+
columnWidths[1] = availableWidth;
|
|
479
|
+
} else {
|
|
480
|
+
columnWidths[1] = Math.max(columnWidths[1], nestedTableWidth);
|
|
481
|
+
}
|
|
482
|
+
} else if (value instanceof MultiColumnASCIITable) {
|
|
483
|
+
let nestedTableColumnWidths = [];
|
|
484
|
+
if (canAutoAdjustWidthWhenPossible) {
|
|
485
|
+
const minWidth = value.getMinimumWidth();
|
|
486
|
+
const availableWidth2 = tableWidth - columnWidths[0] - 7;
|
|
487
|
+
const adjustedWidth = clamp(availableWidth2, minWidth, availableWidth2);
|
|
488
|
+
nestedTableColumnWidths = value.calculateColumnWidths({
|
|
489
|
+
tableWidth: adjustedWidth
|
|
490
|
+
});
|
|
491
|
+
} else {
|
|
492
|
+
nestedTableColumnWidths = value.calculateColumnWidths();
|
|
493
|
+
}
|
|
494
|
+
const nestedTableWidth = nestedTableColumnWidths.reduce((sum, width) => sum + width, 0) + nestedTableColumnWidths.length * 3 - 1;
|
|
495
|
+
const availableWidth = tableWidth - columnWidths[0] - 7;
|
|
496
|
+
if (nestedTableWidth > availableWidth) {
|
|
497
|
+
columnWidths[1] = availableWidth;
|
|
498
|
+
} else {
|
|
499
|
+
columnWidths[1] = Math.max(columnWidths[1], nestedTableWidth);
|
|
500
|
+
}
|
|
501
|
+
} else if (Array.isArray(value)) {
|
|
502
|
+
for (const nestedCell of value) {
|
|
503
|
+
const nestedKeyWidth = stringWidth3(nestedCell.key);
|
|
504
|
+
const maxNestedKeyWidth = Math.floor((tableWidth - 7) / 2);
|
|
505
|
+
if (nestedKeyWidth > columnWidths[0]) {
|
|
506
|
+
columnWidths[0] = Math.min(nestedKeyWidth, maxNestedKeyWidth);
|
|
507
|
+
columnWidths[1] = Math.max(0, tableWidth - columnWidths[0] - 7);
|
|
508
|
+
}
|
|
509
|
+
if (typeof nestedCell.value === "string") {
|
|
510
|
+
const nestedValueWidth = Math.max(
|
|
511
|
+
...nestedCell.value.split("\n").map((line) => stringWidth3(line))
|
|
512
|
+
);
|
|
513
|
+
if (nestedValueWidth > columnWidths[1]) {
|
|
514
|
+
columnWidths[1] = Math.min(
|
|
515
|
+
nestedValueWidth,
|
|
516
|
+
tableWidth - columnWidths[0] - 7
|
|
517
|
+
);
|
|
518
|
+
columnWidths[0] = Math.max(0, tableWidth - columnWidths[1] - 7);
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
return columnWidths;
|
|
525
|
+
}
|
|
526
|
+
formatValue(value, cellWidth, canAutoAdjustWidthWhenPossible, indent = "") {
|
|
527
|
+
if (typeof value === "string") {
|
|
528
|
+
return value;
|
|
529
|
+
} else if (typeof value === "number") {
|
|
530
|
+
return String(value);
|
|
531
|
+
} else if (typeof value === "boolean") {
|
|
532
|
+
return String(value);
|
|
533
|
+
} else if (value === null) {
|
|
534
|
+
return "null";
|
|
535
|
+
} else if (value === void 0) {
|
|
536
|
+
return "undefined";
|
|
537
|
+
} else if (value instanceof _KeyValueASCIITable) {
|
|
538
|
+
let nestedTableLines;
|
|
539
|
+
if (canAutoAdjustWidthWhenPossible) {
|
|
540
|
+
const minWidth = value.getMinimumWidth();
|
|
541
|
+
const adjustedWidth = clamp(cellWidth, minWidth, cellWidth);
|
|
542
|
+
nestedTableLines = value.toString({ tableWidth: adjustedWidth }).split("\n");
|
|
543
|
+
} else {
|
|
544
|
+
nestedTableLines = value.toString().split("\n");
|
|
545
|
+
}
|
|
546
|
+
const indentedLines = nestedTableLines.map((line) => `${indent}${line}`);
|
|
547
|
+
return indentedLines.join("\n");
|
|
548
|
+
} else if (value instanceof MultiColumnASCIITable) {
|
|
549
|
+
let nestedTableLines;
|
|
550
|
+
if (canAutoAdjustWidthWhenPossible) {
|
|
551
|
+
const minWidth = value.getMinimumWidth();
|
|
552
|
+
const adjustedWidth = clamp(cellWidth, minWidth, cellWidth);
|
|
553
|
+
nestedTableLines = value.toString({ tableWidth: adjustedWidth }).split("\n");
|
|
554
|
+
} else {
|
|
555
|
+
nestedTableLines = value.toString().split("\n");
|
|
556
|
+
}
|
|
557
|
+
const indentedLines = nestedTableLines.map((line) => `${indent}${line}`);
|
|
558
|
+
return indentedLines.join("\n");
|
|
559
|
+
} else if (Array.isArray(value)) {
|
|
560
|
+
const nestedValueLines = [];
|
|
561
|
+
for (const { key, value: nestedValue } of value) {
|
|
562
|
+
const formattedKey = `${indent}${key}:`;
|
|
563
|
+
const formattedValue = this.formatValue(
|
|
564
|
+
nestedValue,
|
|
565
|
+
cellWidth - indent.length - stringWidth3(key) - 2,
|
|
566
|
+
canAutoAdjustWidthWhenPossible,
|
|
567
|
+
`${indent}`
|
|
568
|
+
);
|
|
569
|
+
const wrappedSpacer = padRight("", 4, " ");
|
|
570
|
+
const wrappedValue = formattedValue.split("\n").map((line) => `${indent}${wrappedSpacer}${line}`);
|
|
571
|
+
nestedValueLines.push(formattedKey);
|
|
572
|
+
nestedValueLines.push(...wrappedValue);
|
|
573
|
+
nestedValueLines.push("");
|
|
574
|
+
}
|
|
575
|
+
return nestedValueLines.slice(0, -1).join("\n");
|
|
576
|
+
} else {
|
|
577
|
+
throw new TypeError("Invalid value type provided");
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
formatTableRowOnOwnRow(value, width, maxRowLength) {
|
|
581
|
+
const lines = value.split("\n");
|
|
582
|
+
const paddedLines = lines.map((line) => {
|
|
583
|
+
const wrappedLines = ASCIITableUtils.wrapText(line, maxRowLength - 4);
|
|
584
|
+
return wrappedLines.map((wrappedLine) => {
|
|
585
|
+
const padding = padRight(
|
|
586
|
+
"",
|
|
587
|
+
width - stringWidth3(wrappedLine) - 2,
|
|
588
|
+
" "
|
|
589
|
+
);
|
|
590
|
+
return `${wrappedLine}${padding}`;
|
|
591
|
+
}).join("\n");
|
|
592
|
+
});
|
|
593
|
+
return paddedLines.join("\n");
|
|
594
|
+
}
|
|
595
|
+
};
|
|
596
|
+
|
|
597
|
+
// src/lib/error-to-string.ts
|
|
598
|
+
function safeStringify(value) {
|
|
599
|
+
if (value === null || value === void 0) {
|
|
600
|
+
return String(value);
|
|
601
|
+
}
|
|
602
|
+
switch (typeof value) {
|
|
603
|
+
case "string":
|
|
604
|
+
return value;
|
|
605
|
+
case "number":
|
|
606
|
+
case "boolean":
|
|
607
|
+
case "bigint":
|
|
608
|
+
return String(value);
|
|
609
|
+
case "object":
|
|
610
|
+
return JSON.stringify(value);
|
|
611
|
+
case "function":
|
|
612
|
+
return "[Function]";
|
|
613
|
+
case "symbol":
|
|
614
|
+
return value.toString();
|
|
615
|
+
default:
|
|
616
|
+
return String(value);
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
function errorToString(error, maxRowLength = 80) {
|
|
620
|
+
const table = errorToASCIITable(error, maxRowLength);
|
|
621
|
+
return table.toString();
|
|
622
|
+
}
|
|
623
|
+
function errorToASCIITable(error, maxRowLength) {
|
|
624
|
+
const table = new KeyValueASCIITable({
|
|
625
|
+
tableWidth: maxRowLength,
|
|
626
|
+
autoAdjustWidthWhenPossible: true
|
|
627
|
+
});
|
|
628
|
+
if (error && typeof error === "object") {
|
|
629
|
+
const err = error;
|
|
630
|
+
table.addRow("Key", "Value");
|
|
631
|
+
if (err["message"]) {
|
|
632
|
+
table.addRow("Message", safeStringify(err["message"]));
|
|
633
|
+
}
|
|
634
|
+
if (err["name"]) {
|
|
635
|
+
table.addRow("Name", safeStringify(err["name"]));
|
|
636
|
+
}
|
|
637
|
+
if (err["code"]) {
|
|
638
|
+
table.addRow("Code", safeStringify(err["code"]));
|
|
639
|
+
}
|
|
640
|
+
if (err["errno"]) {
|
|
641
|
+
table.addRow("Errno", safeStringify(err["errno"]));
|
|
642
|
+
}
|
|
643
|
+
if (err["errPrefix"]) {
|
|
644
|
+
table.addRow("Prefix", safeStringify(err["errPrefix"]));
|
|
645
|
+
}
|
|
646
|
+
if (err["errType"]) {
|
|
647
|
+
table.addRow("errType", safeStringify(err["errType"]));
|
|
648
|
+
}
|
|
649
|
+
if (err["errCode"]) {
|
|
650
|
+
table.addRow("errCode", safeStringify(err["errCode"]));
|
|
651
|
+
}
|
|
652
|
+
if (err["additionalInfo"]) {
|
|
653
|
+
const additionalInfo = err["additionalInfo"];
|
|
654
|
+
const sensitiveFieldNames = err["sensitiveFieldNames"] || [];
|
|
655
|
+
for (const key in additionalInfo) {
|
|
656
|
+
if (sensitiveFieldNames.includes(key)) {
|
|
657
|
+
table.addRow(`AdditionalInfo.${key}`, "***");
|
|
658
|
+
} else {
|
|
659
|
+
const value = additionalInfo[key];
|
|
660
|
+
table.addRow(
|
|
661
|
+
`AdditionalInfo.${key}`,
|
|
662
|
+
stringifyValue(value, table, maxRowLength)
|
|
663
|
+
);
|
|
664
|
+
}
|
|
665
|
+
}
|
|
666
|
+
}
|
|
667
|
+
if (err["stack"]) {
|
|
668
|
+
table.addValueOnSeparateRow("Stack", safeStringify(err["stack"]));
|
|
669
|
+
}
|
|
670
|
+
}
|
|
671
|
+
return table;
|
|
672
|
+
}
|
|
673
|
+
function stringifyValue(value, table, maxRowLength) {
|
|
674
|
+
if (typeof value === "string") {
|
|
675
|
+
return value;
|
|
676
|
+
} else if (Array.isArray(value)) {
|
|
677
|
+
return value.map((item) => {
|
|
678
|
+
const result = stringifyValue(item, table, maxRowLength);
|
|
679
|
+
if (typeof result === "string") {
|
|
680
|
+
return result;
|
|
681
|
+
} else if (result instanceof KeyValueASCIITable) {
|
|
682
|
+
return result.toString();
|
|
683
|
+
} else {
|
|
684
|
+
return JSON.stringify(result);
|
|
685
|
+
}
|
|
686
|
+
}).join(", ");
|
|
687
|
+
} else if (typeof value === "object" && value !== null) {
|
|
688
|
+
if (value instanceof Error) {
|
|
689
|
+
return errorToASCIITable(value, maxRowLength - 4);
|
|
690
|
+
} else {
|
|
691
|
+
const entries = Object.entries(value).map(
|
|
692
|
+
([key, val]) => ({
|
|
693
|
+
key,
|
|
694
|
+
value: stringifyValue(val, table, maxRowLength - 4)
|
|
695
|
+
})
|
|
696
|
+
);
|
|
697
|
+
return entries;
|
|
698
|
+
}
|
|
699
|
+
} else {
|
|
700
|
+
return String(value);
|
|
701
|
+
}
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
// src/lib/is-promise.ts
|
|
705
|
+
function isPromise(obj) {
|
|
706
|
+
return !!obj && (typeof obj === "object" || typeof obj === "function") && // @ts-expect-error - obj is checked to be object/function, then property access works at runtime
|
|
707
|
+
typeof obj["then"] === "function";
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
// src/lib/is-function.ts
|
|
711
|
+
function isFunction(value) {
|
|
712
|
+
return typeof value === "function" || value instanceof Function;
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
// src/lib/safe-handle-callback.ts
|
|
716
|
+
function safeHandleCallback(callbackName, callback, ...args) {
|
|
717
|
+
const handleError = (error) => {
|
|
718
|
+
if (typeof globalThis.dispatchEvent === "function") {
|
|
719
|
+
globalThis.dispatchEvent(
|
|
720
|
+
new ErrorEvent("reportError", {
|
|
721
|
+
error: new Error(
|
|
722
|
+
`Error in a callback ${callbackName}: ${DOUBLE_EOL}${errorToString(error)}`
|
|
723
|
+
)
|
|
724
|
+
})
|
|
725
|
+
);
|
|
726
|
+
}
|
|
727
|
+
};
|
|
728
|
+
if (isFunction(callback)) {
|
|
729
|
+
try {
|
|
730
|
+
const result = callback(...args);
|
|
731
|
+
if (isPromise(result)) {
|
|
732
|
+
result.catch((error) => {
|
|
733
|
+
handleError(error);
|
|
734
|
+
});
|
|
735
|
+
}
|
|
736
|
+
} catch (error) {
|
|
737
|
+
handleError(error);
|
|
738
|
+
}
|
|
739
|
+
} else {
|
|
740
|
+
handleError(
|
|
741
|
+
new Error(`Callback provided for ${callbackName} is not a function`)
|
|
742
|
+
);
|
|
743
|
+
}
|
|
744
|
+
}
|
|
745
|
+
async function safeHandleCallbackAndWait(callbackName, callback, ...args) {
|
|
746
|
+
const handleError = (error) => {
|
|
747
|
+
if (typeof globalThis.dispatchEvent === "function") {
|
|
748
|
+
globalThis.dispatchEvent(
|
|
749
|
+
new ErrorEvent("reportError", {
|
|
750
|
+
error: new Error(
|
|
751
|
+
`Error in a callback ${callbackName}: ${DOUBLE_EOL}${errorToString(error)}`
|
|
752
|
+
)
|
|
753
|
+
})
|
|
754
|
+
);
|
|
755
|
+
}
|
|
756
|
+
return { success: false, error };
|
|
757
|
+
};
|
|
758
|
+
if (isFunction(callback)) {
|
|
759
|
+
try {
|
|
760
|
+
const result = callback(...args);
|
|
761
|
+
if (isPromise(result)) {
|
|
762
|
+
const value = await result;
|
|
763
|
+
return { success: true, value };
|
|
764
|
+
} else {
|
|
765
|
+
return { success: true, value: result };
|
|
766
|
+
}
|
|
767
|
+
} catch (error) {
|
|
768
|
+
return handleError(error);
|
|
769
|
+
}
|
|
770
|
+
} else {
|
|
771
|
+
return handleError(
|
|
772
|
+
new Error(`Callback provided for ${callbackName} is not a function`)
|
|
773
|
+
);
|
|
774
|
+
}
|
|
775
|
+
}
|
|
776
|
+
|
|
777
|
+
// src/lib/event-emitter.ts
|
|
778
|
+
var EventEmitterProtected = class {
|
|
779
|
+
events;
|
|
780
|
+
constructor() {
|
|
781
|
+
this.events = /* @__PURE__ */ new Map();
|
|
782
|
+
}
|
|
783
|
+
/**
|
|
784
|
+
* Subscribe to an event
|
|
785
|
+
* @param event The event name to subscribe to
|
|
786
|
+
* @param callback The callback function to be called when the event is emitted
|
|
787
|
+
* @returns A function to unsubscribe from the event
|
|
788
|
+
*/
|
|
789
|
+
on(event, callback) {
|
|
790
|
+
if (!this.events.has(event)) {
|
|
791
|
+
this.events.set(event, /* @__PURE__ */ new Set());
|
|
792
|
+
}
|
|
793
|
+
const callbacks = this.events.get(event);
|
|
794
|
+
if (callbacks) {
|
|
795
|
+
callbacks.add(callback);
|
|
796
|
+
}
|
|
797
|
+
return () => {
|
|
798
|
+
const callbacks2 = this.events.get(event);
|
|
799
|
+
if (callbacks2) {
|
|
800
|
+
callbacks2.delete(callback);
|
|
801
|
+
if (callbacks2.size === 0) {
|
|
802
|
+
this.events.delete(event);
|
|
803
|
+
}
|
|
804
|
+
}
|
|
805
|
+
};
|
|
806
|
+
}
|
|
807
|
+
/**
|
|
808
|
+
* Subscribe to an event once - automatically unsubscribes after first emission
|
|
809
|
+
* @param event The event name to subscribe to
|
|
810
|
+
* @param callback The callback function to be called when the event is emitted
|
|
811
|
+
* @returns A function to unsubscribe from the event before it's called
|
|
812
|
+
*/
|
|
813
|
+
once(event, callback) {
|
|
814
|
+
const unsubscribe = this.on(event, (data) => {
|
|
815
|
+
unsubscribe();
|
|
816
|
+
return callback(data);
|
|
817
|
+
});
|
|
818
|
+
return unsubscribe;
|
|
819
|
+
}
|
|
820
|
+
/**
|
|
821
|
+
* Check if a specific callback is registered for an event.
|
|
822
|
+
* Note: For 'once' handlers, this will return true for the wrapper function, not the original callback.
|
|
823
|
+
* This means hasListener will return false when checking for the original callback of a 'once' subscription.
|
|
824
|
+
*
|
|
825
|
+
* @param event The event name to check
|
|
826
|
+
* @param callback The callback function to look for
|
|
827
|
+
* @returns true if the exact callback is registered, false otherwise
|
|
828
|
+
*/
|
|
829
|
+
hasListener(event, callback) {
|
|
830
|
+
const callbacks = this.events.get(event);
|
|
831
|
+
return callbacks?.has(callback) ?? false;
|
|
832
|
+
}
|
|
833
|
+
/**
|
|
834
|
+
* Check if an event has any subscribers
|
|
835
|
+
* @param event The event name to check
|
|
836
|
+
* @returns true if the event has subscribers, false otherwise
|
|
837
|
+
*/
|
|
838
|
+
hasListeners(event) {
|
|
839
|
+
const callbacks = this.events.get(event);
|
|
840
|
+
return callbacks !== void 0 && callbacks.size > 0;
|
|
841
|
+
}
|
|
842
|
+
/**
|
|
843
|
+
* Get the number of subscribers for an event
|
|
844
|
+
* @param event The event name to check
|
|
845
|
+
* @returns The number of subscribers
|
|
846
|
+
*/
|
|
847
|
+
listenerCount(event) {
|
|
848
|
+
const callbacks = this.events.get(event);
|
|
849
|
+
return callbacks ? callbacks.size : 0;
|
|
850
|
+
}
|
|
851
|
+
/**
|
|
852
|
+
* Remove all event listeners
|
|
853
|
+
* @param event Optional event name. If not provided, removes all listeners for all events
|
|
854
|
+
*/
|
|
855
|
+
clear(event) {
|
|
856
|
+
if (event) {
|
|
857
|
+
this.events.delete(event);
|
|
858
|
+
} else {
|
|
859
|
+
this.events.clear();
|
|
860
|
+
}
|
|
861
|
+
}
|
|
862
|
+
/**
|
|
863
|
+
* Emit an event with optional data
|
|
864
|
+
* This method is protected to allow only derived classes to trigger events.
|
|
865
|
+
* @param event The event name to emit
|
|
866
|
+
* @param data Optional data to pass to the event handlers
|
|
867
|
+
*/
|
|
868
|
+
emit(event, data) {
|
|
869
|
+
const callbacks = this.events.get(event);
|
|
870
|
+
if (callbacks) {
|
|
871
|
+
for (const callback of callbacks) {
|
|
872
|
+
safeHandleCallback(`event handler for ${event}`, callback, data);
|
|
873
|
+
}
|
|
874
|
+
}
|
|
875
|
+
}
|
|
876
|
+
};
|
|
877
|
+
var EventEmitter = class extends EventEmitterProtected {
|
|
878
|
+
/**
|
|
879
|
+
* Emit an event with optional data
|
|
880
|
+
* This method is public, allowing any code with access to the emitter to trigger events.
|
|
881
|
+
* @param event The event name to emit
|
|
882
|
+
* @param data Optional data to pass to the event handlers
|
|
883
|
+
*/
|
|
884
|
+
emit(event, data) {
|
|
885
|
+
super.emit(event, data);
|
|
886
|
+
}
|
|
887
|
+
};
|
|
888
|
+
|
|
889
|
+
// src/lib/unix-time-helpers.ts
|
|
890
|
+
function ms() {
|
|
891
|
+
return Date.now();
|
|
892
|
+
}
|
|
893
|
+
|
|
894
|
+
// src/lib/curly-brackets.ts
|
|
895
|
+
var CurlyBrackets = function(str = "", locals = {}, fallback = "(null)") {
|
|
896
|
+
if (!str.includes("{{")) {
|
|
897
|
+
return str;
|
|
898
|
+
}
|
|
899
|
+
const compiled = CurlyBrackets.compileTemplate(str, fallback);
|
|
900
|
+
return compiled(locals);
|
|
901
|
+
};
|
|
902
|
+
CurlyBrackets.compileTemplate = function(str, fallback = "(null)") {
|
|
903
|
+
const pattern = /(?:\\)?{{(\s*[\w.]+?)(?:\\)?\s*}}/g;
|
|
904
|
+
return (locals) => {
|
|
905
|
+
return str.replace(pattern, (match, p1) => {
|
|
906
|
+
if (typeof p1 !== "string") {
|
|
907
|
+
return match;
|
|
908
|
+
}
|
|
909
|
+
const hasLeadingEscape = match.startsWith("\\");
|
|
910
|
+
const hasEndingEscape = match.endsWith("\\}}");
|
|
911
|
+
const isFullyEscaped = hasLeadingEscape && hasEndingEscape;
|
|
912
|
+
if (isFullyEscaped) {
|
|
913
|
+
return match.slice(1, -3) + "}}";
|
|
914
|
+
}
|
|
915
|
+
if (hasLeadingEscape) {
|
|
916
|
+
return match.slice(1);
|
|
917
|
+
}
|
|
918
|
+
if (hasEndingEscape) {
|
|
919
|
+
return "{{" + p1.trim() + "}}";
|
|
920
|
+
}
|
|
921
|
+
const key = p1.trim();
|
|
922
|
+
const parts = key.split(".");
|
|
923
|
+
let replacement = locals;
|
|
924
|
+
for (const part of parts) {
|
|
925
|
+
if (replacement !== void 0 && replacement !== null && typeof replacement === "object" && part in replacement) {
|
|
926
|
+
replacement = replacement[part];
|
|
927
|
+
} else {
|
|
928
|
+
replacement = void 0;
|
|
929
|
+
break;
|
|
930
|
+
}
|
|
931
|
+
}
|
|
932
|
+
if (replacement === void 0 || replacement === null) {
|
|
933
|
+
return fallback;
|
|
934
|
+
}
|
|
935
|
+
return String(replacement);
|
|
936
|
+
});
|
|
937
|
+
};
|
|
938
|
+
};
|
|
939
|
+
CurlyBrackets.escape = function(str) {
|
|
940
|
+
return str.replace(/(\\)?{{/g, (match, backslash) => backslash ? match : "\\{{").replace(/(\\)?}}/g, (match, backslash) => backslash ? match : "\\}}");
|
|
941
|
+
};
|
|
942
|
+
|
|
943
|
+
// src/lib/is-number.ts
|
|
944
|
+
function isNumber(value) {
|
|
945
|
+
return typeof value === "number" && !isNaN(value);
|
|
946
|
+
}
|
|
947
|
+
|
|
948
|
+
// src/lib/logger/sinks/array.ts
|
|
949
|
+
var ArraySink = class {
|
|
950
|
+
logs = [];
|
|
951
|
+
transformer;
|
|
952
|
+
closed = false;
|
|
953
|
+
constructor(options) {
|
|
954
|
+
this.transformer = options?.transformer;
|
|
955
|
+
}
|
|
956
|
+
write(entry) {
|
|
957
|
+
if (this.closed) {
|
|
958
|
+
return;
|
|
959
|
+
}
|
|
960
|
+
if (this.transformer) {
|
|
961
|
+
try {
|
|
962
|
+
const transformed = this.transformer(entry);
|
|
963
|
+
if (transformed !== false) {
|
|
964
|
+
this.logs.push(transformed);
|
|
965
|
+
return;
|
|
966
|
+
}
|
|
967
|
+
} catch {
|
|
968
|
+
}
|
|
969
|
+
}
|
|
970
|
+
this.logs.push(entry);
|
|
971
|
+
}
|
|
972
|
+
/**
|
|
973
|
+
* Clear all stored logs
|
|
974
|
+
*/
|
|
975
|
+
clear() {
|
|
976
|
+
this.logs = [];
|
|
977
|
+
}
|
|
978
|
+
/**
|
|
979
|
+
* Get logs in a snapshot-friendly format for testing
|
|
980
|
+
*/
|
|
981
|
+
getSnapshotFriendlyLogs() {
|
|
982
|
+
return this.logs.map((log) => `${log.type}: ${log.message}`);
|
|
983
|
+
}
|
|
984
|
+
/**
|
|
985
|
+
* Close the sink and stop accepting new logs
|
|
986
|
+
*/
|
|
987
|
+
close() {
|
|
988
|
+
this.closed = true;
|
|
989
|
+
}
|
|
990
|
+
};
|
|
991
|
+
|
|
992
|
+
// src/lib/logger/sinks/console.ts
|
|
993
|
+
import { format } from "date-fns";
|
|
994
|
+
|
|
995
|
+
// src/lib/logger/types.ts
|
|
996
|
+
var LogLevel = /* @__PURE__ */ ((LogLevel2) => {
|
|
997
|
+
LogLevel2[LogLevel2["ERROR"] = 0] = "ERROR";
|
|
998
|
+
LogLevel2[LogLevel2["WARN"] = 1] = "WARN";
|
|
999
|
+
LogLevel2[LogLevel2["NOTICE"] = 2] = "NOTICE";
|
|
1000
|
+
LogLevel2[LogLevel2["SUCCESS"] = 3] = "SUCCESS";
|
|
1001
|
+
LogLevel2[LogLevel2["INFO"] = 3] = "INFO";
|
|
1002
|
+
LogLevel2[LogLevel2["DEBUG"] = 4] = "DEBUG";
|
|
1003
|
+
LogLevel2[LogLevel2["RAW"] = 99] = "RAW";
|
|
1004
|
+
return LogLevel2;
|
|
1005
|
+
})(LogLevel || {});
|
|
1006
|
+
function getLogLevel(type) {
|
|
1007
|
+
switch (type) {
|
|
1008
|
+
case "error":
|
|
1009
|
+
return 0 /* ERROR */;
|
|
1010
|
+
case "warn":
|
|
1011
|
+
return 1 /* WARN */;
|
|
1012
|
+
case "notice":
|
|
1013
|
+
return 2 /* NOTICE */;
|
|
1014
|
+
case "success":
|
|
1015
|
+
return 3 /* SUCCESS */;
|
|
1016
|
+
case "info":
|
|
1017
|
+
return 3 /* INFO */;
|
|
1018
|
+
case "debug":
|
|
1019
|
+
return 4 /* DEBUG */;
|
|
1020
|
+
case "raw":
|
|
1021
|
+
return 99 /* RAW */;
|
|
1022
|
+
}
|
|
1023
|
+
}
|
|
1024
|
+
|
|
1025
|
+
// src/lib/logger/utils/color.ts
|
|
1026
|
+
import chalk from "chalk";
|
|
1027
|
+
var browserColors = {
|
|
1028
|
+
error: "color: #a95450;",
|
|
1029
|
+
// red
|
|
1030
|
+
info: "color: #ffffff;",
|
|
1031
|
+
// white
|
|
1032
|
+
warn: "color: #f5f566;",
|
|
1033
|
+
// yellow
|
|
1034
|
+
success: "color: #56b97f;",
|
|
1035
|
+
// green
|
|
1036
|
+
notice: "color: #5883bf;",
|
|
1037
|
+
// blue
|
|
1038
|
+
debug: "color: #808080;"
|
|
1039
|
+
// gray
|
|
1040
|
+
};
|
|
1041
|
+
var chalkColors = {
|
|
1042
|
+
error: "red",
|
|
1043
|
+
info: "white",
|
|
1044
|
+
warn: "yellow",
|
|
1045
|
+
success: "green",
|
|
1046
|
+
notice: "blue",
|
|
1047
|
+
debug: "gray"
|
|
1048
|
+
};
|
|
1049
|
+
function colorize(type, text) {
|
|
1050
|
+
const isBrowser = typeof globalThis !== "undefined" && "window" in globalThis && "document" in globalThis;
|
|
1051
|
+
if (isBrowser) {
|
|
1052
|
+
return {
|
|
1053
|
+
coloredText: `%c${text}`,
|
|
1054
|
+
style: browserColors[type]
|
|
1055
|
+
};
|
|
1056
|
+
} else {
|
|
1057
|
+
const colorName = chalkColors[type];
|
|
1058
|
+
const chalkColor = chalk[colorName];
|
|
1059
|
+
return {
|
|
1060
|
+
coloredText: chalkColor(text)
|
|
1061
|
+
};
|
|
1062
|
+
}
|
|
1063
|
+
}
|
|
1064
|
+
|
|
1065
|
+
// src/lib/logger/sinks/console.ts
|
|
1066
|
+
var ConsoleSink = class {
|
|
1067
|
+
colors;
|
|
1068
|
+
timestamps;
|
|
1069
|
+
typeLabels;
|
|
1070
|
+
closed = false;
|
|
1071
|
+
muted;
|
|
1072
|
+
minLevel;
|
|
1073
|
+
constructor(options = {}) {
|
|
1074
|
+
this.colors = options.colors ?? true;
|
|
1075
|
+
this.timestamps = options.timestamps ?? false;
|
|
1076
|
+
this.typeLabels = options.typeLabels ?? false;
|
|
1077
|
+
this.muted = options.muted ?? false;
|
|
1078
|
+
this.minLevel = options.minLevel ?? 3 /* INFO */;
|
|
1079
|
+
}
|
|
1080
|
+
write(entry) {
|
|
1081
|
+
if (this.closed || this.muted) {
|
|
1082
|
+
return;
|
|
1083
|
+
}
|
|
1084
|
+
if (entry.type === "raw") {
|
|
1085
|
+
console.log(entry.message);
|
|
1086
|
+
return;
|
|
1087
|
+
}
|
|
1088
|
+
const logLevel = getLogLevel(entry.type);
|
|
1089
|
+
if (logLevel > this.minLevel) {
|
|
1090
|
+
return;
|
|
1091
|
+
}
|
|
1092
|
+
let formattedMessage = "";
|
|
1093
|
+
if (this.timestamps) {
|
|
1094
|
+
const formattedTimestamp = format(entry.timestamp, "MM-dd-yyyy HH:mm:ss");
|
|
1095
|
+
formattedMessage = "[" + formattedTimestamp + "] ";
|
|
1096
|
+
}
|
|
1097
|
+
if (this.typeLabels) {
|
|
1098
|
+
formattedMessage += `[${entry.type.toUpperCase()}] `;
|
|
1099
|
+
}
|
|
1100
|
+
if (entry.serviceName) {
|
|
1101
|
+
formattedMessage += `[${entry.serviceName}] `;
|
|
1102
|
+
}
|
|
1103
|
+
if (entry.entityName) {
|
|
1104
|
+
formattedMessage += `[${entry.entityName}] `;
|
|
1105
|
+
}
|
|
1106
|
+
formattedMessage += entry.message;
|
|
1107
|
+
if (this.colors) {
|
|
1108
|
+
const { coloredText, style } = colorize(entry.type, formattedMessage);
|
|
1109
|
+
switch (entry.type) {
|
|
1110
|
+
case "error":
|
|
1111
|
+
if (style) {
|
|
1112
|
+
console.error(coloredText, style);
|
|
1113
|
+
} else {
|
|
1114
|
+
console.error(coloredText);
|
|
1115
|
+
}
|
|
1116
|
+
break;
|
|
1117
|
+
case "info":
|
|
1118
|
+
if (style) {
|
|
1119
|
+
console.info(coloredText, style);
|
|
1120
|
+
} else {
|
|
1121
|
+
console.info(coloredText);
|
|
1122
|
+
}
|
|
1123
|
+
break;
|
|
1124
|
+
case "warn":
|
|
1125
|
+
if (style) {
|
|
1126
|
+
console.warn(coloredText, style);
|
|
1127
|
+
} else {
|
|
1128
|
+
console.warn(coloredText);
|
|
1129
|
+
}
|
|
1130
|
+
break;
|
|
1131
|
+
case "success":
|
|
1132
|
+
case "notice":
|
|
1133
|
+
case "debug":
|
|
1134
|
+
if (style) {
|
|
1135
|
+
console.log(coloredText, style);
|
|
1136
|
+
} else {
|
|
1137
|
+
console.log(coloredText);
|
|
1138
|
+
}
|
|
1139
|
+
break;
|
|
1140
|
+
}
|
|
1141
|
+
} else {
|
|
1142
|
+
switch (entry.type) {
|
|
1143
|
+
case "error":
|
|
1144
|
+
console.error(formattedMessage);
|
|
1145
|
+
break;
|
|
1146
|
+
case "info":
|
|
1147
|
+
console.info(formattedMessage);
|
|
1148
|
+
break;
|
|
1149
|
+
case "warn":
|
|
1150
|
+
console.warn(formattedMessage);
|
|
1151
|
+
break;
|
|
1152
|
+
case "success":
|
|
1153
|
+
case "notice":
|
|
1154
|
+
case "debug":
|
|
1155
|
+
console.log(formattedMessage);
|
|
1156
|
+
break;
|
|
1157
|
+
}
|
|
1158
|
+
}
|
|
1159
|
+
}
|
|
1160
|
+
/**
|
|
1161
|
+
* Set the minimum log level for this sink
|
|
1162
|
+
*/
|
|
1163
|
+
setMinLevel(level) {
|
|
1164
|
+
this.minLevel = level;
|
|
1165
|
+
}
|
|
1166
|
+
/**
|
|
1167
|
+
* Get the current minimum log level
|
|
1168
|
+
*/
|
|
1169
|
+
getMinLevel() {
|
|
1170
|
+
return this.minLevel;
|
|
1171
|
+
}
|
|
1172
|
+
/**
|
|
1173
|
+
* Mute the sink to stop writing logs to console
|
|
1174
|
+
*/
|
|
1175
|
+
mute() {
|
|
1176
|
+
this.muted = true;
|
|
1177
|
+
}
|
|
1178
|
+
/**
|
|
1179
|
+
* Unmute the sink to resume writing logs to console
|
|
1180
|
+
*/
|
|
1181
|
+
unmute() {
|
|
1182
|
+
this.muted = false;
|
|
1183
|
+
}
|
|
1184
|
+
/**
|
|
1185
|
+
* Check if the sink is currently muted
|
|
1186
|
+
*/
|
|
1187
|
+
isMuted() {
|
|
1188
|
+
return this.muted;
|
|
1189
|
+
}
|
|
1190
|
+
/**
|
|
1191
|
+
* Close the sink and stop accepting new logs
|
|
1192
|
+
*/
|
|
1193
|
+
close() {
|
|
1194
|
+
this.closed = true;
|
|
1195
|
+
}
|
|
1196
|
+
};
|
|
1197
|
+
|
|
1198
|
+
// src/lib/logger/utils/redaction.ts
|
|
1199
|
+
import datamask from "datamask";
|
|
1200
|
+
|
|
1201
|
+
// src/lib/deep-clone.ts
|
|
1202
|
+
function deepClone(obj) {
|
|
1203
|
+
return cloneInternal(obj, /* @__PURE__ */ new WeakMap());
|
|
1204
|
+
}
|
|
1205
|
+
function cloneInternal(obj, seen) {
|
|
1206
|
+
if (obj === null || typeof obj !== "object") {
|
|
1207
|
+
return obj;
|
|
1208
|
+
}
|
|
1209
|
+
if (seen.has(obj)) {
|
|
1210
|
+
return seen.get(obj);
|
|
1211
|
+
}
|
|
1212
|
+
if (obj instanceof Date) {
|
|
1213
|
+
return new Date(obj);
|
|
1214
|
+
}
|
|
1215
|
+
if (obj instanceof RegExp) {
|
|
1216
|
+
const flags = obj.flags;
|
|
1217
|
+
const cloned2 = new RegExp(obj.source, flags);
|
|
1218
|
+
cloned2.lastIndex = obj.lastIndex;
|
|
1219
|
+
return cloned2;
|
|
1220
|
+
}
|
|
1221
|
+
if (obj instanceof Map) {
|
|
1222
|
+
const cloned2 = /* @__PURE__ */ new Map();
|
|
1223
|
+
seen.set(obj, cloned2);
|
|
1224
|
+
for (const [key, value] of obj) {
|
|
1225
|
+
cloned2.set(cloneInternal(key, seen), cloneInternal(value, seen));
|
|
1226
|
+
}
|
|
1227
|
+
return cloned2;
|
|
1228
|
+
}
|
|
1229
|
+
if (obj instanceof Set) {
|
|
1230
|
+
const cloned2 = /* @__PURE__ */ new Set();
|
|
1231
|
+
seen.set(obj, cloned2);
|
|
1232
|
+
for (const value of obj) {
|
|
1233
|
+
cloned2.add(cloneInternal(value, seen));
|
|
1234
|
+
}
|
|
1235
|
+
return cloned2;
|
|
1236
|
+
}
|
|
1237
|
+
if (ArrayBuffer.isView(obj) && !(obj instanceof DataView)) {
|
|
1238
|
+
const typedArray = obj;
|
|
1239
|
+
const cloned2 = typedArray.slice();
|
|
1240
|
+
return cloned2;
|
|
1241
|
+
}
|
|
1242
|
+
if (Array.isArray(obj)) {
|
|
1243
|
+
const cloned2 = [];
|
|
1244
|
+
seen.set(obj, cloned2);
|
|
1245
|
+
for (const item of obj) {
|
|
1246
|
+
cloned2.push(cloneInternal(item, seen));
|
|
1247
|
+
}
|
|
1248
|
+
return cloned2;
|
|
1249
|
+
}
|
|
1250
|
+
const cloned = {};
|
|
1251
|
+
seen.set(obj, cloned);
|
|
1252
|
+
for (const key in obj) {
|
|
1253
|
+
if (Object.prototype.hasOwnProperty.call(obj, key)) {
|
|
1254
|
+
cloned[key] = cloneInternal(obj[key], seen);
|
|
1255
|
+
}
|
|
1256
|
+
}
|
|
1257
|
+
return cloned;
|
|
1258
|
+
}
|
|
1259
|
+
|
|
1260
|
+
// src/lib/logger/utils/redaction.ts
|
|
1261
|
+
var defaultRedactFunction = (_keyName, value) => {
|
|
1262
|
+
if (typeof value === "string") {
|
|
1263
|
+
return datamask.string(value, "*", 60);
|
|
1264
|
+
}
|
|
1265
|
+
return "***REDACTED***";
|
|
1266
|
+
};
|
|
1267
|
+
function setNestedValue(obj, path, value) {
|
|
1268
|
+
const parts = path.split(".");
|
|
1269
|
+
let current = obj;
|
|
1270
|
+
for (let i = 0; i < parts.length - 1; i++) {
|
|
1271
|
+
const part = parts[i];
|
|
1272
|
+
const next = current[part];
|
|
1273
|
+
if (next === void 0 || next === null || typeof next !== "object") {
|
|
1274
|
+
return;
|
|
1275
|
+
}
|
|
1276
|
+
current = next;
|
|
1277
|
+
}
|
|
1278
|
+
const lastPart = parts[parts.length - 1];
|
|
1279
|
+
if (lastPart !== void 0 && lastPart in current) {
|
|
1280
|
+
current[lastPart] = value;
|
|
1281
|
+
}
|
|
1282
|
+
}
|
|
1283
|
+
function getNestedValue(obj, path) {
|
|
1284
|
+
const parts = path.split(".");
|
|
1285
|
+
let current = obj;
|
|
1286
|
+
for (const part of parts) {
|
|
1287
|
+
if (current === void 0 || current === null || typeof current !== "object" || !(part in current)) {
|
|
1288
|
+
return void 0;
|
|
1289
|
+
}
|
|
1290
|
+
current = current[part];
|
|
1291
|
+
}
|
|
1292
|
+
return current;
|
|
1293
|
+
}
|
|
1294
|
+
function applyRedaction(params, redactedKeys, redactFunction) {
|
|
1295
|
+
if (!redactedKeys || redactedKeys.length === 0) {
|
|
1296
|
+
return params;
|
|
1297
|
+
}
|
|
1298
|
+
const redactFn = redactFunction || defaultRedactFunction;
|
|
1299
|
+
const redactedParams = deepClone(params);
|
|
1300
|
+
for (const key of redactedKeys) {
|
|
1301
|
+
if (key.includes(".")) {
|
|
1302
|
+
const value = getNestedValue(redactedParams, key);
|
|
1303
|
+
if (value !== void 0) {
|
|
1304
|
+
const redactedValue = redactFn(key, value);
|
|
1305
|
+
setNestedValue(redactedParams, key, redactedValue);
|
|
1306
|
+
}
|
|
1307
|
+
} else {
|
|
1308
|
+
if (key in redactedParams) {
|
|
1309
|
+
redactedParams[key] = redactFn(key, redactedParams[key]);
|
|
1310
|
+
}
|
|
1311
|
+
}
|
|
1312
|
+
}
|
|
1313
|
+
return redactedParams;
|
|
1314
|
+
}
|
|
1315
|
+
|
|
1316
|
+
// src/lib/logger/utils/error-object.ts
|
|
1317
|
+
function prepareErrorObjectLog(prefix, error) {
|
|
1318
|
+
prefix = prefix.trim();
|
|
1319
|
+
let prefixLine = "";
|
|
1320
|
+
if (prefix.length > 0) {
|
|
1321
|
+
prefixLine = prefix + ": " + DOUBLE_EOL;
|
|
1322
|
+
}
|
|
1323
|
+
return prefixLine + errorToString(error);
|
|
1324
|
+
}
|
|
1325
|
+
|
|
1326
|
+
// src/lib/logger/logger-service.ts
|
|
1327
|
+
var LoggerService = class _LoggerService {
|
|
1328
|
+
handleLog;
|
|
1329
|
+
serviceName;
|
|
1330
|
+
entityName;
|
|
1331
|
+
constructor(handleLog, serviceName, entityName) {
|
|
1332
|
+
this.handleLog = handleLog;
|
|
1333
|
+
this.serviceName = serviceName;
|
|
1334
|
+
this.entityName = entityName;
|
|
1335
|
+
}
|
|
1336
|
+
/**
|
|
1337
|
+
* Create a scoped logger for a specific entity within this service
|
|
1338
|
+
*/
|
|
1339
|
+
entity(entityName) {
|
|
1340
|
+
return new _LoggerService(this.handleLog, this.serviceName, entityName);
|
|
1341
|
+
}
|
|
1342
|
+
/**
|
|
1343
|
+
* Log an error message
|
|
1344
|
+
*/
|
|
1345
|
+
error(message, options) {
|
|
1346
|
+
this.handleLog("error", message, {
|
|
1347
|
+
...options ?? {},
|
|
1348
|
+
serviceName: this.serviceName,
|
|
1349
|
+
entityName: this.entityName
|
|
1350
|
+
});
|
|
1351
|
+
}
|
|
1352
|
+
/**
|
|
1353
|
+
* Log an error object with optional prefix
|
|
1354
|
+
*/
|
|
1355
|
+
errorObject(prefix, error, options) {
|
|
1356
|
+
const message = prepareErrorObjectLog(prefix, error);
|
|
1357
|
+
this.handleLog("error", message, {
|
|
1358
|
+
...options ?? {},
|
|
1359
|
+
serviceName: this.serviceName,
|
|
1360
|
+
entityName: this.entityName,
|
|
1361
|
+
error
|
|
1362
|
+
});
|
|
1363
|
+
}
|
|
1364
|
+
/**
|
|
1365
|
+
* Log an informational message
|
|
1366
|
+
*/
|
|
1367
|
+
info(message, options) {
|
|
1368
|
+
this.handleLog("info", message, {
|
|
1369
|
+
...options ?? {},
|
|
1370
|
+
serviceName: this.serviceName,
|
|
1371
|
+
entityName: this.entityName
|
|
1372
|
+
});
|
|
1373
|
+
}
|
|
1374
|
+
/**
|
|
1375
|
+
* Log a warning message
|
|
1376
|
+
*/
|
|
1377
|
+
warn(message, options) {
|
|
1378
|
+
this.handleLog("warn", message, {
|
|
1379
|
+
...options ?? {},
|
|
1380
|
+
serviceName: this.serviceName,
|
|
1381
|
+
entityName: this.entityName
|
|
1382
|
+
});
|
|
1383
|
+
}
|
|
1384
|
+
/**
|
|
1385
|
+
* Log a success message
|
|
1386
|
+
*/
|
|
1387
|
+
success(message, options) {
|
|
1388
|
+
this.handleLog("success", message, {
|
|
1389
|
+
...options ?? {},
|
|
1390
|
+
serviceName: this.serviceName,
|
|
1391
|
+
entityName: this.entityName
|
|
1392
|
+
});
|
|
1393
|
+
}
|
|
1394
|
+
/**
|
|
1395
|
+
* Log a notice message
|
|
1396
|
+
*/
|
|
1397
|
+
notice(message, options) {
|
|
1398
|
+
this.handleLog("notice", message, {
|
|
1399
|
+
...options ?? {},
|
|
1400
|
+
serviceName: this.serviceName,
|
|
1401
|
+
entityName: this.entityName
|
|
1402
|
+
});
|
|
1403
|
+
}
|
|
1404
|
+
/**
|
|
1405
|
+
* Log a debug message
|
|
1406
|
+
*/
|
|
1407
|
+
debug(message, options) {
|
|
1408
|
+
this.handleLog("debug", message, {
|
|
1409
|
+
...options ?? {},
|
|
1410
|
+
serviceName: this.serviceName,
|
|
1411
|
+
entityName: this.entityName
|
|
1412
|
+
});
|
|
1413
|
+
}
|
|
1414
|
+
/**
|
|
1415
|
+
* Log a raw message without any formatting
|
|
1416
|
+
*/
|
|
1417
|
+
raw(message, options) {
|
|
1418
|
+
this.handleLog("raw", message, {
|
|
1419
|
+
...options ?? {},
|
|
1420
|
+
serviceName: this.serviceName,
|
|
1421
|
+
entityName: this.entityName
|
|
1422
|
+
});
|
|
1423
|
+
}
|
|
1424
|
+
};
|
|
1425
|
+
|
|
1426
|
+
// src/lib/logger/sinks/file.ts
|
|
1427
|
+
import fs, { promises as fsPromises } from "fs";
|
|
1428
|
+
var FileSinkError = class extends Error {
|
|
1429
|
+
constructor(message, cause) {
|
|
1430
|
+
super(message);
|
|
1431
|
+
this.cause = cause;
|
|
1432
|
+
this.name = "FileSinkError";
|
|
1433
|
+
}
|
|
1434
|
+
};
|
|
1435
|
+
var FileSink = class {
|
|
1436
|
+
logDir;
|
|
1437
|
+
basename;
|
|
1438
|
+
maxSizeMB;
|
|
1439
|
+
jsonFormat;
|
|
1440
|
+
maxRetries;
|
|
1441
|
+
minLevel;
|
|
1442
|
+
onError;
|
|
1443
|
+
logFileStream;
|
|
1444
|
+
currentLogFile;
|
|
1445
|
+
currentLogSize = 0;
|
|
1446
|
+
writeQueue = [];
|
|
1447
|
+
isInitialized = false;
|
|
1448
|
+
initPromise;
|
|
1449
|
+
isProcessing = false;
|
|
1450
|
+
lastError;
|
|
1451
|
+
consecutiveFailures = 0;
|
|
1452
|
+
totalEntriesWritten = 0;
|
|
1453
|
+
totalEntriesFailed = 0;
|
|
1454
|
+
closing = false;
|
|
1455
|
+
closed = false;
|
|
1456
|
+
closeTimeoutMS;
|
|
1457
|
+
constructor(options) {
|
|
1458
|
+
this.logDir = options.logDir;
|
|
1459
|
+
this.basename = options.basename;
|
|
1460
|
+
this.maxSizeMB = options.maxSizeMB ?? 10;
|
|
1461
|
+
this.jsonFormat = options.jsonFormat ?? false;
|
|
1462
|
+
this.maxRetries = options.maxRetries ?? 3;
|
|
1463
|
+
this.closeTimeoutMS = options.closeTimeoutMS ?? 3e4;
|
|
1464
|
+
this.minLevel = options.minLevel ?? 3 /* INFO */;
|
|
1465
|
+
this.onError = options.onError;
|
|
1466
|
+
this.initPromise = this.initialize();
|
|
1467
|
+
}
|
|
1468
|
+
write(entry) {
|
|
1469
|
+
if (this.closing || this.closed) {
|
|
1470
|
+
return;
|
|
1471
|
+
}
|
|
1472
|
+
if (entry.type !== "raw") {
|
|
1473
|
+
const logLevel = getLogLevel(entry.type);
|
|
1474
|
+
if (logLevel > this.minLevel) {
|
|
1475
|
+
return;
|
|
1476
|
+
}
|
|
1477
|
+
}
|
|
1478
|
+
this.writeQueue.push({ entry, attempts: 0 });
|
|
1479
|
+
if (this.isInitialized) {
|
|
1480
|
+
void this.processQueue();
|
|
1481
|
+
} else if (this.initPromise) {
|
|
1482
|
+
void this.initPromise.then(() => this.processQueue());
|
|
1483
|
+
}
|
|
1484
|
+
}
|
|
1485
|
+
/**
|
|
1486
|
+
* Set the minimum log level for this sink
|
|
1487
|
+
*/
|
|
1488
|
+
setMinLevel(level) {
|
|
1489
|
+
this.minLevel = level;
|
|
1490
|
+
}
|
|
1491
|
+
/**
|
|
1492
|
+
* Get the current minimum log level
|
|
1493
|
+
*/
|
|
1494
|
+
getMinLevel() {
|
|
1495
|
+
return this.minLevel;
|
|
1496
|
+
}
|
|
1497
|
+
/**
|
|
1498
|
+
* Get current health status of the sink
|
|
1499
|
+
*/
|
|
1500
|
+
getHealth() {
|
|
1501
|
+
return {
|
|
1502
|
+
isHealthy: this.consecutiveFailures === 0 && this.isInitialized,
|
|
1503
|
+
queueSize: this.writeQueue.length,
|
|
1504
|
+
lastError: this.lastError,
|
|
1505
|
+
consecutiveFailures: this.consecutiveFailures,
|
|
1506
|
+
isInitialized: this.isInitialized
|
|
1507
|
+
};
|
|
1508
|
+
}
|
|
1509
|
+
/**
|
|
1510
|
+
* Flush all pending writes and wait for completion
|
|
1511
|
+
* Returns statistics about the flush operation
|
|
1512
|
+
* @param timeoutMS Maximum time to wait in milliseconds (default: 30000ms / 30s)
|
|
1513
|
+
*/
|
|
1514
|
+
async flush(timeoutMS = 3e4) {
|
|
1515
|
+
if (this.initPromise) {
|
|
1516
|
+
await this.initPromise;
|
|
1517
|
+
}
|
|
1518
|
+
const startWritten = this.totalEntriesWritten;
|
|
1519
|
+
const startFailed = this.totalEntriesFailed;
|
|
1520
|
+
const startTime = Date.now();
|
|
1521
|
+
while (this.writeQueue.length > 0 || this.isProcessing) {
|
|
1522
|
+
if (Date.now() - startTime > timeoutMS) {
|
|
1523
|
+
const entriesWritten2 = this.totalEntriesWritten - startWritten;
|
|
1524
|
+
const entriesFailed2 = this.totalEntriesFailed - startFailed;
|
|
1525
|
+
return {
|
|
1526
|
+
success: false,
|
|
1527
|
+
entriesWritten: entriesWritten2,
|
|
1528
|
+
entriesFailed: entriesFailed2,
|
|
1529
|
+
timedOut: true
|
|
1530
|
+
};
|
|
1531
|
+
}
|
|
1532
|
+
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
1533
|
+
}
|
|
1534
|
+
const entriesWritten = this.totalEntriesWritten - startWritten;
|
|
1535
|
+
const entriesFailed = this.totalEntriesFailed - startFailed;
|
|
1536
|
+
return {
|
|
1537
|
+
success: entriesFailed === 0,
|
|
1538
|
+
entriesWritten,
|
|
1539
|
+
entriesFailed,
|
|
1540
|
+
timedOut: false
|
|
1541
|
+
};
|
|
1542
|
+
}
|
|
1543
|
+
/**
|
|
1544
|
+
* Close the log file and wait for all pending writes
|
|
1545
|
+
*/
|
|
1546
|
+
async close() {
|
|
1547
|
+
this.closing = true;
|
|
1548
|
+
const startTime = Date.now();
|
|
1549
|
+
if (this.initPromise) {
|
|
1550
|
+
let timeoutHandle;
|
|
1551
|
+
const timeoutSentinel = { timedOut: true };
|
|
1552
|
+
try {
|
|
1553
|
+
const timeoutPromise = new Promise(
|
|
1554
|
+
(resolve) => {
|
|
1555
|
+
timeoutHandle = setTimeout(
|
|
1556
|
+
() => resolve(timeoutSentinel),
|
|
1557
|
+
this.closeTimeoutMS
|
|
1558
|
+
);
|
|
1559
|
+
}
|
|
1560
|
+
);
|
|
1561
|
+
const result = await Promise.race([
|
|
1562
|
+
this.initPromise.then(() => void 0),
|
|
1563
|
+
timeoutPromise
|
|
1564
|
+
]);
|
|
1565
|
+
if (result === timeoutSentinel) {
|
|
1566
|
+
Promise.resolve(this.initPromise).catch(() => {
|
|
1567
|
+
});
|
|
1568
|
+
}
|
|
1569
|
+
} finally {
|
|
1570
|
+
if (timeoutHandle) {
|
|
1571
|
+
clearTimeout(timeoutHandle);
|
|
1572
|
+
}
|
|
1573
|
+
}
|
|
1574
|
+
}
|
|
1575
|
+
while (this.writeQueue.length > 0 || this.isProcessing) {
|
|
1576
|
+
if (Date.now() - startTime > this.closeTimeoutMS) {
|
|
1577
|
+
break;
|
|
1578
|
+
}
|
|
1579
|
+
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
1580
|
+
}
|
|
1581
|
+
this.closed = true;
|
|
1582
|
+
if (this.logFileStream) {
|
|
1583
|
+
return new Promise((resolve) => {
|
|
1584
|
+
if (!this.logFileStream) {
|
|
1585
|
+
return resolve();
|
|
1586
|
+
}
|
|
1587
|
+
this.logFileStream.end(() => {
|
|
1588
|
+
this.logFileStream = void 0;
|
|
1589
|
+
resolve();
|
|
1590
|
+
});
|
|
1591
|
+
});
|
|
1592
|
+
}
|
|
1593
|
+
}
|
|
1594
|
+
/**
|
|
1595
|
+
* Initialize the file sink asynchronously
|
|
1596
|
+
*/
|
|
1597
|
+
async initialize() {
|
|
1598
|
+
try {
|
|
1599
|
+
await fsPromises.mkdir(this.logDir, { recursive: true });
|
|
1600
|
+
await this.setupLogFile();
|
|
1601
|
+
this.isInitialized = true;
|
|
1602
|
+
await this.processQueue();
|
|
1603
|
+
} catch {
|
|
1604
|
+
}
|
|
1605
|
+
}
|
|
1606
|
+
/**
|
|
1607
|
+
* Process the write queue
|
|
1608
|
+
* Processes entries one at a time with retry logic
|
|
1609
|
+
*/
|
|
1610
|
+
async processQueue() {
|
|
1611
|
+
if (this.isProcessing || this.closed || this.writeQueue.length === 0) {
|
|
1612
|
+
return;
|
|
1613
|
+
}
|
|
1614
|
+
this.isProcessing = true;
|
|
1615
|
+
try {
|
|
1616
|
+
while (this.writeQueue.length > 0) {
|
|
1617
|
+
const queuedEntry = this.writeQueue.shift();
|
|
1618
|
+
if (!queuedEntry) {
|
|
1619
|
+
break;
|
|
1620
|
+
}
|
|
1621
|
+
try {
|
|
1622
|
+
await this.writeEntry(queuedEntry.entry);
|
|
1623
|
+
this.consecutiveFailures = 0;
|
|
1624
|
+
this.totalEntriesWritten++;
|
|
1625
|
+
} catch (error) {
|
|
1626
|
+
const err = error instanceof Error ? error : new Error(String(error));
|
|
1627
|
+
this.lastError = err;
|
|
1628
|
+
this.consecutiveFailures++;
|
|
1629
|
+
const willRetry = queuedEntry.attempts < this.maxRetries;
|
|
1630
|
+
if (this.onError) {
|
|
1631
|
+
try {
|
|
1632
|
+
this.onError(
|
|
1633
|
+
err,
|
|
1634
|
+
queuedEntry.entry,
|
|
1635
|
+
queuedEntry.attempts + 1,
|
|
1636
|
+
willRetry
|
|
1637
|
+
);
|
|
1638
|
+
} catch {
|
|
1639
|
+
}
|
|
1640
|
+
}
|
|
1641
|
+
if (willRetry) {
|
|
1642
|
+
queuedEntry.attempts++;
|
|
1643
|
+
this.writeQueue.push(queuedEntry);
|
|
1644
|
+
} else {
|
|
1645
|
+
this.totalEntriesFailed++;
|
|
1646
|
+
}
|
|
1647
|
+
}
|
|
1648
|
+
}
|
|
1649
|
+
} finally {
|
|
1650
|
+
this.isProcessing = false;
|
|
1651
|
+
}
|
|
1652
|
+
}
|
|
1653
|
+
/**
|
|
1654
|
+
* Write a single entry to the file
|
|
1655
|
+
* If stream is broken, it will be recreated on next attempt
|
|
1656
|
+
*/
|
|
1657
|
+
async writeEntry(entry) {
|
|
1658
|
+
if (this.closed) {
|
|
1659
|
+
throw new FileSinkError("Cannot write to closed sink");
|
|
1660
|
+
}
|
|
1661
|
+
if (!this.logFileStream) {
|
|
1662
|
+
await this.setupLogFile();
|
|
1663
|
+
}
|
|
1664
|
+
if (!this.logFileStream) {
|
|
1665
|
+
throw new FileSinkError("No log file stream available");
|
|
1666
|
+
}
|
|
1667
|
+
await this.rotateIfNeeded();
|
|
1668
|
+
const messageToWrite = this.formatEntry(entry);
|
|
1669
|
+
const messageBytes = Buffer.byteLength(messageToWrite, "utf8");
|
|
1670
|
+
const maxSizeBytes = this.maxSizeMB * 1024 * 1024;
|
|
1671
|
+
if (this.currentLogSize + messageBytes > maxSizeBytes) {
|
|
1672
|
+
await this.rotateFile();
|
|
1673
|
+
}
|
|
1674
|
+
return new Promise((resolve, reject) => {
|
|
1675
|
+
if (!this.logFileStream) {
|
|
1676
|
+
return resolve();
|
|
1677
|
+
}
|
|
1678
|
+
this.logFileStream.write(messageToWrite, (err) => {
|
|
1679
|
+
if (err) {
|
|
1680
|
+
this.destroyStream();
|
|
1681
|
+
reject(new FileSinkError("Error writing to log file", err));
|
|
1682
|
+
} else {
|
|
1683
|
+
this.currentLogSize += messageBytes;
|
|
1684
|
+
resolve();
|
|
1685
|
+
}
|
|
1686
|
+
});
|
|
1687
|
+
});
|
|
1688
|
+
}
|
|
1689
|
+
/**
|
|
1690
|
+
* Format a log entry for file output
|
|
1691
|
+
*/
|
|
1692
|
+
formatEntry(entry) {
|
|
1693
|
+
let formatted;
|
|
1694
|
+
if (this.jsonFormat) {
|
|
1695
|
+
formatted = JSON.stringify({
|
|
1696
|
+
timestamp: entry.timestamp,
|
|
1697
|
+
type: entry.type,
|
|
1698
|
+
serviceName: entry.serviceName,
|
|
1699
|
+
entityName: entry.entityName,
|
|
1700
|
+
message: entry.message,
|
|
1701
|
+
params: entry.redactedParams
|
|
1702
|
+
// Use redacted params for file output
|
|
1703
|
+
});
|
|
1704
|
+
} else {
|
|
1705
|
+
let text = "";
|
|
1706
|
+
if (entry.type !== "raw") {
|
|
1707
|
+
text = `[${entry.type}] `;
|
|
1708
|
+
if (entry.serviceName) {
|
|
1709
|
+
text += `[${entry.serviceName}] `;
|
|
1710
|
+
}
|
|
1711
|
+
if (entry.entityName) {
|
|
1712
|
+
text += `[${entry.entityName}] `;
|
|
1713
|
+
}
|
|
1714
|
+
}
|
|
1715
|
+
text += entry.message;
|
|
1716
|
+
formatted = text;
|
|
1717
|
+
}
|
|
1718
|
+
return formatted + "\n";
|
|
1719
|
+
}
|
|
1720
|
+
/**
|
|
1721
|
+
* Setup the log file
|
|
1722
|
+
*/
|
|
1723
|
+
async setupLogFile() {
|
|
1724
|
+
const currentDate = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
1725
|
+
const currentLogFile = `${this.logDir}/${this.basename}-${currentDate}.log`;
|
|
1726
|
+
try {
|
|
1727
|
+
await fsPromises.mkdir(this.logDir, { recursive: true });
|
|
1728
|
+
try {
|
|
1729
|
+
await fsPromises.access(currentLogFile);
|
|
1730
|
+
} catch {
|
|
1731
|
+
await fsPromises.writeFile(currentLogFile, "", { flag: "a" });
|
|
1732
|
+
}
|
|
1733
|
+
this.logFileStream = fs.createWriteStream(currentLogFile, { flags: "a" });
|
|
1734
|
+
this.currentLogFile = currentLogFile;
|
|
1735
|
+
this.logFileStream.on("error", () => {
|
|
1736
|
+
this.destroyStream();
|
|
1737
|
+
});
|
|
1738
|
+
try {
|
|
1739
|
+
const stats = await fsPromises.stat(currentLogFile);
|
|
1740
|
+
this.currentLogSize = stats.size;
|
|
1741
|
+
} catch {
|
|
1742
|
+
this.currentLogSize = 0;
|
|
1743
|
+
}
|
|
1744
|
+
const maxSizeBytes = this.maxSizeMB * 1024 * 1024;
|
|
1745
|
+
if (this.currentLogSize >= maxSizeBytes) {
|
|
1746
|
+
await this.rotateFile();
|
|
1747
|
+
}
|
|
1748
|
+
} catch (error) {
|
|
1749
|
+
throw new FileSinkError(
|
|
1750
|
+
`Failed to setup log file: ${currentLogFile}`,
|
|
1751
|
+
error
|
|
1752
|
+
);
|
|
1753
|
+
}
|
|
1754
|
+
}
|
|
1755
|
+
/**
|
|
1756
|
+
* Destroy the current stream
|
|
1757
|
+
*/
|
|
1758
|
+
destroyStream() {
|
|
1759
|
+
if (this.logFileStream) {
|
|
1760
|
+
try {
|
|
1761
|
+
this.logFileStream.destroy();
|
|
1762
|
+
} catch {
|
|
1763
|
+
} finally {
|
|
1764
|
+
this.logFileStream = void 0;
|
|
1765
|
+
}
|
|
1766
|
+
}
|
|
1767
|
+
}
|
|
1768
|
+
/**
|
|
1769
|
+
* Rotate log file if needed based on size or date
|
|
1770
|
+
*/
|
|
1771
|
+
async rotateIfNeeded() {
|
|
1772
|
+
if (!this.logFileStream || !this.currentLogFile) {
|
|
1773
|
+
return;
|
|
1774
|
+
}
|
|
1775
|
+
const currentDate = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
1776
|
+
const expectedFile = `${this.logDir}/${this.basename}-${currentDate}.log`;
|
|
1777
|
+
if (this.currentLogFile !== expectedFile) {
|
|
1778
|
+
await this.setupLogFile();
|
|
1779
|
+
return;
|
|
1780
|
+
}
|
|
1781
|
+
const maxSizeBytes = this.maxSizeMB * 1024 * 1024;
|
|
1782
|
+
if (this.currentLogSize >= maxSizeBytes) {
|
|
1783
|
+
await this.rotateFile();
|
|
1784
|
+
}
|
|
1785
|
+
}
|
|
1786
|
+
/**
|
|
1787
|
+
* Rotate the current log file
|
|
1788
|
+
* Queue processing pauses during rotation, then resumes
|
|
1789
|
+
*/
|
|
1790
|
+
async rotateFile() {
|
|
1791
|
+
if (!this.logFileStream || !this.currentLogFile) {
|
|
1792
|
+
return;
|
|
1793
|
+
}
|
|
1794
|
+
const currentDate = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
1795
|
+
await new Promise((resolve) => {
|
|
1796
|
+
if (!this.logFileStream) {
|
|
1797
|
+
return resolve();
|
|
1798
|
+
}
|
|
1799
|
+
this.logFileStream.end(() => {
|
|
1800
|
+
this.logFileStream = void 0;
|
|
1801
|
+
resolve();
|
|
1802
|
+
});
|
|
1803
|
+
});
|
|
1804
|
+
const timestamp = Math.floor(Date.now() / 1e3);
|
|
1805
|
+
const rotatedFile = `${this.logDir}/${this.basename}-${currentDate}-${timestamp}.log`;
|
|
1806
|
+
try {
|
|
1807
|
+
await fsPromises.rename(this.currentLogFile, rotatedFile);
|
|
1808
|
+
} catch (error) {
|
|
1809
|
+
throw new FileSinkError(
|
|
1810
|
+
`Error rotating log file from ${this.currentLogFile} to ${rotatedFile}`,
|
|
1811
|
+
error
|
|
1812
|
+
);
|
|
1813
|
+
}
|
|
1814
|
+
await this.setupLogFile();
|
|
1815
|
+
}
|
|
1816
|
+
};
|
|
1817
|
+
|
|
1818
|
+
// src/lib/logger/sinks/named-pipe.ts
|
|
1819
|
+
import * as fs2 from "fs";
|
|
1820
|
+
import { promises as fsPromises2 } from "fs";
|
|
1821
|
+
import * as os from "os";
|
|
1822
|
+
var PipeErrorType = /* @__PURE__ */ ((PipeErrorType2) => {
|
|
1823
|
+
PipeErrorType2["WRITE"] = "write";
|
|
1824
|
+
PipeErrorType2["CLOSE"] = "close";
|
|
1825
|
+
PipeErrorType2["NOT_FOUND"] = "not_found";
|
|
1826
|
+
PipeErrorType2["NOT_A_PIPE"] = "not_a_pipe";
|
|
1827
|
+
PipeErrorType2["PERMISSION"] = "permission";
|
|
1828
|
+
PipeErrorType2["UNSUPPORTED_PLATFORM"] = "unsupported_platform";
|
|
1829
|
+
return PipeErrorType2;
|
|
1830
|
+
})(PipeErrorType || {});
|
|
1831
|
+
var NamedPipeSink = class {
|
|
1832
|
+
pipePath;
|
|
1833
|
+
jsonFormat;
|
|
1834
|
+
onError;
|
|
1835
|
+
formatter;
|
|
1836
|
+
pipeStream;
|
|
1837
|
+
writeQueue = [];
|
|
1838
|
+
isInitialized = false;
|
|
1839
|
+
_isReconnecting = false;
|
|
1840
|
+
initPromise;
|
|
1841
|
+
closing = false;
|
|
1842
|
+
closed = false;
|
|
1843
|
+
closeTimeoutMS;
|
|
1844
|
+
constructor(options) {
|
|
1845
|
+
this.pipePath = options.pipePath;
|
|
1846
|
+
this.jsonFormat = options.jsonFormat ?? false;
|
|
1847
|
+
this.onError = options.onError;
|
|
1848
|
+
this.formatter = options.formatter;
|
|
1849
|
+
this.closeTimeoutMS = options.closeTimeoutMS ?? 3e4;
|
|
1850
|
+
this.initPromise = this.initializePipe();
|
|
1851
|
+
}
|
|
1852
|
+
write(entry) {
|
|
1853
|
+
if (this.closing || this.closed) {
|
|
1854
|
+
return;
|
|
1855
|
+
}
|
|
1856
|
+
if (!this.isInitialized) {
|
|
1857
|
+
this.writeQueue.push(entry);
|
|
1858
|
+
return;
|
|
1859
|
+
}
|
|
1860
|
+
this.writeEntry(entry);
|
|
1861
|
+
}
|
|
1862
|
+
/**
|
|
1863
|
+
* Attempt to reconnect to the named pipe.
|
|
1864
|
+
* Useful when the pipe reader restarts or after a temporary error.
|
|
1865
|
+
* Queued writes during the outage will be flushed on successful reconnection.
|
|
1866
|
+
*/
|
|
1867
|
+
get isReconnecting() {
|
|
1868
|
+
return this._isReconnecting;
|
|
1869
|
+
}
|
|
1870
|
+
async reconnect() {
|
|
1871
|
+
if (this._isReconnecting) {
|
|
1872
|
+
await this.initPromise;
|
|
1873
|
+
return { success: false, reason: "already_reconnecting" };
|
|
1874
|
+
}
|
|
1875
|
+
this._isReconnecting = true;
|
|
1876
|
+
try {
|
|
1877
|
+
if (this.pipeStream && !this.pipeStream.destroyed) {
|
|
1878
|
+
this.pipeStream.end();
|
|
1879
|
+
this.pipeStream = void 0;
|
|
1880
|
+
}
|
|
1881
|
+
this.isInitialized = false;
|
|
1882
|
+
this.initPromise = this.initializePipe();
|
|
1883
|
+
await this.initPromise;
|
|
1884
|
+
if (this.isInitialized) {
|
|
1885
|
+
return { success: true };
|
|
1886
|
+
} else {
|
|
1887
|
+
return {
|
|
1888
|
+
success: false,
|
|
1889
|
+
reason: "error",
|
|
1890
|
+
error: new Error("Failed to initialize pipe connection")
|
|
1891
|
+
};
|
|
1892
|
+
}
|
|
1893
|
+
} finally {
|
|
1894
|
+
this._isReconnecting = false;
|
|
1895
|
+
}
|
|
1896
|
+
}
|
|
1897
|
+
async close() {
|
|
1898
|
+
this.closing = true;
|
|
1899
|
+
let timeoutHandle;
|
|
1900
|
+
const timeoutSentinel = { timedOut: true };
|
|
1901
|
+
try {
|
|
1902
|
+
const timeoutPromise = new Promise((resolve) => {
|
|
1903
|
+
timeoutHandle = setTimeout(
|
|
1904
|
+
() => resolve(timeoutSentinel),
|
|
1905
|
+
this.closeTimeoutMS
|
|
1906
|
+
);
|
|
1907
|
+
});
|
|
1908
|
+
const result = await Promise.race([
|
|
1909
|
+
this.initPromise.then(() => void 0),
|
|
1910
|
+
timeoutPromise
|
|
1911
|
+
]);
|
|
1912
|
+
if (result === timeoutSentinel) {
|
|
1913
|
+
Promise.resolve(this.initPromise).catch(() => {
|
|
1914
|
+
});
|
|
1915
|
+
}
|
|
1916
|
+
} finally {
|
|
1917
|
+
if (timeoutHandle) {
|
|
1918
|
+
clearTimeout(timeoutHandle);
|
|
1919
|
+
}
|
|
1920
|
+
}
|
|
1921
|
+
this.closed = true;
|
|
1922
|
+
if (this.pipeStream && !this.pipeStream.destroyed) {
|
|
1923
|
+
return new Promise((resolve) => {
|
|
1924
|
+
try {
|
|
1925
|
+
this.pipeStream?.end(() => {
|
|
1926
|
+
this.pipeStream = void 0;
|
|
1927
|
+
resolve();
|
|
1928
|
+
});
|
|
1929
|
+
} catch (error) {
|
|
1930
|
+
this.handleError(
|
|
1931
|
+
"close" /* CLOSE */,
|
|
1932
|
+
error instanceof Error ? error : new Error(String(error))
|
|
1933
|
+
);
|
|
1934
|
+
resolve();
|
|
1935
|
+
}
|
|
1936
|
+
});
|
|
1937
|
+
}
|
|
1938
|
+
}
|
|
1939
|
+
/**
|
|
1940
|
+
* Initialize the named pipe connection
|
|
1941
|
+
*/
|
|
1942
|
+
async initializePipe() {
|
|
1943
|
+
const platform2 = os.platform();
|
|
1944
|
+
if (platform2 !== "linux" && platform2 !== "darwin") {
|
|
1945
|
+
this.handleError(
|
|
1946
|
+
"unsupported_platform" /* UNSUPPORTED_PLATFORM */,
|
|
1947
|
+
new Error(
|
|
1948
|
+
`Named pipes are only supported on Linux and macOS, current platform: ${platform2}`
|
|
1949
|
+
)
|
|
1950
|
+
);
|
|
1951
|
+
return;
|
|
1952
|
+
}
|
|
1953
|
+
try {
|
|
1954
|
+
const stats = await fsPromises2.stat(this.pipePath);
|
|
1955
|
+
if (!stats.isFIFO()) {
|
|
1956
|
+
this.handleError(
|
|
1957
|
+
"not_a_pipe" /* NOT_A_PIPE */,
|
|
1958
|
+
new Error(`${this.pipePath} exists but is not a named pipe (FIFO)`)
|
|
1959
|
+
);
|
|
1960
|
+
return;
|
|
1961
|
+
}
|
|
1962
|
+
this.pipeStream = fs2.createWriteStream(this.pipePath, {
|
|
1963
|
+
flags: "a"
|
|
1964
|
+
// Append mode
|
|
1965
|
+
});
|
|
1966
|
+
this.pipeStream.on("error", (err) => {
|
|
1967
|
+
this.handleError("write" /* WRITE */, err);
|
|
1968
|
+
this.pipeStream = void 0;
|
|
1969
|
+
});
|
|
1970
|
+
this.isInitialized = true;
|
|
1971
|
+
this.processQueue();
|
|
1972
|
+
} catch (error) {
|
|
1973
|
+
this.handleError(
|
|
1974
|
+
"not_found" /* NOT_FOUND */,
|
|
1975
|
+
new Error(
|
|
1976
|
+
`Could not open named pipe at ${this.pipePath}: ${error.message}`
|
|
1977
|
+
)
|
|
1978
|
+
);
|
|
1979
|
+
}
|
|
1980
|
+
}
|
|
1981
|
+
/**
|
|
1982
|
+
* Process queued entries
|
|
1983
|
+
*/
|
|
1984
|
+
processQueue() {
|
|
1985
|
+
while (this.writeQueue.length > 0 && !this.closed) {
|
|
1986
|
+
const entry = this.writeQueue.shift();
|
|
1987
|
+
if (entry) {
|
|
1988
|
+
this.writeEntry(entry);
|
|
1989
|
+
}
|
|
1990
|
+
}
|
|
1991
|
+
}
|
|
1992
|
+
/**
|
|
1993
|
+
* Write a single entry
|
|
1994
|
+
*/
|
|
1995
|
+
writeEntry(entry) {
|
|
1996
|
+
if (this.closed) {
|
|
1997
|
+
return;
|
|
1998
|
+
}
|
|
1999
|
+
if (!this.pipeStream || this.pipeStream.destroyed) {
|
|
2000
|
+
return;
|
|
2001
|
+
}
|
|
2002
|
+
try {
|
|
2003
|
+
const messageToWrite = this.formatEntry(entry);
|
|
2004
|
+
if (!this.pipeStream.write(messageToWrite)) {
|
|
2005
|
+
this.pipeStream.once("drain", () => {
|
|
2006
|
+
});
|
|
2007
|
+
}
|
|
2008
|
+
} catch (error) {
|
|
2009
|
+
this.handleError(
|
|
2010
|
+
"write" /* WRITE */,
|
|
2011
|
+
error instanceof Error ? error : new Error(String(error))
|
|
2012
|
+
);
|
|
2013
|
+
}
|
|
2014
|
+
}
|
|
2015
|
+
/**
|
|
2016
|
+
* Format a log entry for pipe output
|
|
2017
|
+
*/
|
|
2018
|
+
formatEntry(entry) {
|
|
2019
|
+
if (this.formatter) {
|
|
2020
|
+
try {
|
|
2021
|
+
return this.formatter(entry) + "\n";
|
|
2022
|
+
} catch {
|
|
2023
|
+
}
|
|
2024
|
+
}
|
|
2025
|
+
let formatted;
|
|
2026
|
+
if (this.jsonFormat) {
|
|
2027
|
+
formatted = JSON.stringify({
|
|
2028
|
+
timestamp: entry.timestamp,
|
|
2029
|
+
type: entry.type,
|
|
2030
|
+
serviceName: entry.serviceName,
|
|
2031
|
+
entityName: entry.entityName,
|
|
2032
|
+
message: entry.message,
|
|
2033
|
+
params: entry.redactedParams
|
|
2034
|
+
});
|
|
2035
|
+
} else {
|
|
2036
|
+
let text = "";
|
|
2037
|
+
if (entry.type !== "raw") {
|
|
2038
|
+
text = `[${entry.type}] `;
|
|
2039
|
+
if (entry.serviceName) {
|
|
2040
|
+
text += `[${entry.serviceName}] `;
|
|
2041
|
+
}
|
|
2042
|
+
if (entry.entityName) {
|
|
2043
|
+
text += `[${entry.entityName}] `;
|
|
2044
|
+
}
|
|
2045
|
+
}
|
|
2046
|
+
text += entry.message;
|
|
2047
|
+
formatted = text;
|
|
2048
|
+
}
|
|
2049
|
+
return formatted + "\n";
|
|
2050
|
+
}
|
|
2051
|
+
/**
|
|
2052
|
+
* Handle errors
|
|
2053
|
+
*/
|
|
2054
|
+
handleError(errorType, error) {
|
|
2055
|
+
if (this.onError) {
|
|
2056
|
+
this.onError(errorType, error, this.pipePath);
|
|
2057
|
+
} else {
|
|
2058
|
+
console.error(`NamedPipeSink error (${errorType}): ${error.message}`);
|
|
2059
|
+
}
|
|
2060
|
+
}
|
|
2061
|
+
};
|
|
2062
|
+
|
|
2063
|
+
// src/lib/logger/index.ts
|
|
2064
|
+
var Logger = class _Logger extends EventEmitter {
|
|
2065
|
+
isLoggerClass = true;
|
|
2066
|
+
sinks;
|
|
2067
|
+
redactFunction;
|
|
2068
|
+
callProcessExit;
|
|
2069
|
+
beforeExitCallback;
|
|
2070
|
+
onSinkError;
|
|
2071
|
+
_didExit = false;
|
|
2072
|
+
_exitCode = 0;
|
|
2073
|
+
_exitRequested = false;
|
|
2074
|
+
_isPendingExit = false;
|
|
2075
|
+
_closed = false;
|
|
2076
|
+
_reportErrorListenerRegistered = false;
|
|
2077
|
+
_reportErrorListener = null;
|
|
2078
|
+
constructor(options = {}) {
|
|
2079
|
+
super();
|
|
2080
|
+
this.sinks = options.sinks || [];
|
|
2081
|
+
this.redactFunction = options.redactFunction;
|
|
2082
|
+
this.callProcessExit = options.callProcessExit ?? true;
|
|
2083
|
+
this.beforeExitCallback = options.beforeExitCallback;
|
|
2084
|
+
this.onSinkError = options.onSinkError;
|
|
2085
|
+
}
|
|
2086
|
+
get didExit() {
|
|
2087
|
+
return this._didExit;
|
|
2088
|
+
}
|
|
2089
|
+
get exitCode() {
|
|
2090
|
+
return this._exitCode;
|
|
2091
|
+
}
|
|
2092
|
+
get isPendingExit() {
|
|
2093
|
+
return this._isPendingExit;
|
|
2094
|
+
}
|
|
2095
|
+
get hasExitedOrPending() {
|
|
2096
|
+
return this._didExit || this._isPendingExit;
|
|
2097
|
+
}
|
|
2098
|
+
get closed() {
|
|
2099
|
+
return this._closed;
|
|
2100
|
+
}
|
|
2101
|
+
/**
|
|
2102
|
+
* Exit the process with the specified code
|
|
2103
|
+
*/
|
|
2104
|
+
exit(code) {
|
|
2105
|
+
const isFirstExit = !this._exitRequested;
|
|
2106
|
+
this._exitRequested = true;
|
|
2107
|
+
if (!this._didExit) {
|
|
2108
|
+
this._isPendingExit = true;
|
|
2109
|
+
}
|
|
2110
|
+
this.emit("logger", { eventType: "exit-called", code, isFirstExit });
|
|
2111
|
+
if (this.beforeExitCallback) {
|
|
2112
|
+
safeHandleCallbackAndWait(
|
|
2113
|
+
"beforeExit",
|
|
2114
|
+
this.beforeExitCallback,
|
|
2115
|
+
code,
|
|
2116
|
+
isFirstExit
|
|
2117
|
+
).then((result) => {
|
|
2118
|
+
if (result.success && result.value?.action === "wait") {
|
|
2119
|
+
return;
|
|
2120
|
+
}
|
|
2121
|
+
this.processExit(code);
|
|
2122
|
+
}).catch(() => {
|
|
2123
|
+
this.processExit(code);
|
|
2124
|
+
});
|
|
2125
|
+
} else {
|
|
2126
|
+
this.processExit(code);
|
|
2127
|
+
}
|
|
2128
|
+
}
|
|
2129
|
+
/**
|
|
2130
|
+
* Set or update the beforeExit callback
|
|
2131
|
+
*
|
|
2132
|
+
* This allows setting the callback after Logger construction, which is useful
|
|
2133
|
+
* when the callback needs to reference objects that depend on the Logger instance.
|
|
2134
|
+
*
|
|
2135
|
+
* **Note:** This method overwrites any existing beforeExit callback (including
|
|
2136
|
+
* one set in the Logger constructor). Pass `undefined` to remove the callback.
|
|
2137
|
+
*
|
|
2138
|
+
* **Error Handling:** If the callback throws an error or rejects, the logger will
|
|
2139
|
+
* proceed with exit anyway to prevent the process from hanging. The error will be
|
|
2140
|
+
* reported via the global `reportError` event.
|
|
2141
|
+
*
|
|
2142
|
+
* @param callback - Function to call before process exit (receives exitCode and isFirstExit).
|
|
2143
|
+
* Must return BeforeExitResult indicating whether to proceed with exit or wait.
|
|
2144
|
+
* Return `{ action: 'proceed' }` to continue with exit.
|
|
2145
|
+
* Return `{ action: 'wait' }` to prevent exit (e.g., shutdown already in progress).
|
|
2146
|
+
* If the callback throws, exit proceeds automatically.
|
|
2147
|
+
*
|
|
2148
|
+
* Pass undefined to remove the callback.
|
|
2149
|
+
*
|
|
2150
|
+
* @example
|
|
2151
|
+
* ```typescript
|
|
2152
|
+
* const logger = new Logger();
|
|
2153
|
+
* const lifecycle = new LifecycleManager({ logger });
|
|
2154
|
+
*
|
|
2155
|
+
* // Set callback after both are constructed
|
|
2156
|
+
* logger.setBeforeExitCallback(async (exitCode, isFirstExit) => {
|
|
2157
|
+
* if (isFirstExit) {
|
|
2158
|
+
* await lifecycle.stopAllComponents();
|
|
2159
|
+
* }
|
|
2160
|
+
* return { action: 'proceed' };
|
|
2161
|
+
* });
|
|
2162
|
+
*
|
|
2163
|
+
* // Later, remove the callback
|
|
2164
|
+
* logger.setBeforeExitCallback(undefined);
|
|
2165
|
+
* ```
|
|
2166
|
+
*/
|
|
2167
|
+
setBeforeExitCallback(callback) {
|
|
2168
|
+
this.beforeExitCallback = callback;
|
|
2169
|
+
}
|
|
2170
|
+
/**
|
|
2171
|
+
* Log an error message
|
|
2172
|
+
*/
|
|
2173
|
+
error(message, options) {
|
|
2174
|
+
this.handleLog("error", message, options);
|
|
2175
|
+
}
|
|
2176
|
+
/**
|
|
2177
|
+
* Log an error object with optional prefix
|
|
2178
|
+
*/
|
|
2179
|
+
errorObject(prefix, error, options) {
|
|
2180
|
+
const message = prepareErrorObjectLog(prefix, error);
|
|
2181
|
+
this.handleLog("error", message, { ...options ?? {}, error });
|
|
2182
|
+
}
|
|
2183
|
+
/**
|
|
2184
|
+
* Log an informational message
|
|
2185
|
+
*/
|
|
2186
|
+
info(message, options) {
|
|
2187
|
+
this.handleLog("info", message, options);
|
|
2188
|
+
}
|
|
2189
|
+
/**
|
|
2190
|
+
* Log a warning message
|
|
2191
|
+
*/
|
|
2192
|
+
warn(message, options) {
|
|
2193
|
+
this.handleLog("warn", message, options);
|
|
2194
|
+
}
|
|
2195
|
+
/**
|
|
2196
|
+
* Log a success message
|
|
2197
|
+
*/
|
|
2198
|
+
success(message, options) {
|
|
2199
|
+
this.handleLog("success", message, options);
|
|
2200
|
+
}
|
|
2201
|
+
/**
|
|
2202
|
+
* Log a notice message
|
|
2203
|
+
*/
|
|
2204
|
+
notice(message, options) {
|
|
2205
|
+
this.handleLog("notice", message, options);
|
|
2206
|
+
}
|
|
2207
|
+
/**
|
|
2208
|
+
* Log a debug message
|
|
2209
|
+
*/
|
|
2210
|
+
debug(message, options) {
|
|
2211
|
+
this.handleLog("debug", message, options);
|
|
2212
|
+
}
|
|
2213
|
+
/**
|
|
2214
|
+
* Log a raw message without any formatting
|
|
2215
|
+
*/
|
|
2216
|
+
raw(message, options) {
|
|
2217
|
+
this.handleLog("raw", message, options);
|
|
2218
|
+
}
|
|
2219
|
+
/**
|
|
2220
|
+
* Create a scoped logger with a service name
|
|
2221
|
+
*/
|
|
2222
|
+
service(serviceName) {
|
|
2223
|
+
return new LoggerService(this.handleLog.bind(this), serviceName);
|
|
2224
|
+
}
|
|
2225
|
+
/**
|
|
2226
|
+
* Registers a listener for the 'reportError' event.
|
|
2227
|
+
*
|
|
2228
|
+
* If the listener is already registered, it returns 'already_registered'.
|
|
2229
|
+
* If 'globalThis.reportError' is not available, it returns 'not_available'.
|
|
2230
|
+
* Otherwise, it registers the listener and returns 'success'.
|
|
2231
|
+
*
|
|
2232
|
+
* @param prefix - The prefix to use when logging the error object. Default is 'Uncaught exception'.
|
|
2233
|
+
* @returns 'success' if the listener is registered successfully,
|
|
2234
|
+
* 'already_registered' if the listener is already registered,
|
|
2235
|
+
* 'not_available' if 'globalThis.reportError' is not available.
|
|
2236
|
+
*/
|
|
2237
|
+
registerReportErrorListener(prefix = "Uncaught exception") {
|
|
2238
|
+
if (this._reportErrorListenerRegistered) {
|
|
2239
|
+
return "already_registered";
|
|
2240
|
+
}
|
|
2241
|
+
if (typeof globalThis.reportError === "undefined") {
|
|
2242
|
+
return "not_available";
|
|
2243
|
+
}
|
|
2244
|
+
this._reportErrorListener = (event) => {
|
|
2245
|
+
const errorEvent = event;
|
|
2246
|
+
this.errorObject(prefix, errorEvent.error);
|
|
2247
|
+
this.emit("logger", {
|
|
2248
|
+
eventType: "uncaughtException",
|
|
2249
|
+
error: errorEvent.error
|
|
2250
|
+
});
|
|
2251
|
+
};
|
|
2252
|
+
globalThis.addEventListener("reportError", this._reportErrorListener);
|
|
2253
|
+
this._reportErrorListenerRegistered = true;
|
|
2254
|
+
return "success";
|
|
2255
|
+
}
|
|
2256
|
+
/**
|
|
2257
|
+
* Unregister the listener for the 'reportError' event.
|
|
2258
|
+
*
|
|
2259
|
+
* If the listener is not registered, it returns 'not_registered'.
|
|
2260
|
+
* Otherwise, it unregister the listener and returns 'success'.
|
|
2261
|
+
*
|
|
2262
|
+
* @returns 'success' if the listener is unregistered successfully,
|
|
2263
|
+
* 'not_registered' if the listener is not registered.
|
|
2264
|
+
*/
|
|
2265
|
+
unregisterReportErrorListener() {
|
|
2266
|
+
if (!this._reportErrorListenerRegistered || !this._reportErrorListener) {
|
|
2267
|
+
return "not_registered";
|
|
2268
|
+
}
|
|
2269
|
+
globalThis.removeEventListener("reportError", this._reportErrorListener);
|
|
2270
|
+
this._reportErrorListener = null;
|
|
2271
|
+
this._reportErrorListenerRegistered = false;
|
|
2272
|
+
return "success";
|
|
2273
|
+
}
|
|
2274
|
+
/**
|
|
2275
|
+
* Check if the 'reportError' event listener is registered
|
|
2276
|
+
*
|
|
2277
|
+
* @returns 'true' if the listener is registered, 'false' otherwise.
|
|
2278
|
+
*/
|
|
2279
|
+
isReportErrorListenerRegistered() {
|
|
2280
|
+
return this._reportErrorListenerRegistered;
|
|
2281
|
+
}
|
|
2282
|
+
/**
|
|
2283
|
+
* Check if 'globalThis.reportError' is available
|
|
2284
|
+
*
|
|
2285
|
+
* @returns 'true' if 'globalThis.reportError' is available, 'false' otherwise.
|
|
2286
|
+
*/
|
|
2287
|
+
isReportErrorAvailable() {
|
|
2288
|
+
return typeof globalThis.reportError !== "undefined";
|
|
2289
|
+
}
|
|
2290
|
+
/**
|
|
2291
|
+
* Add a sink to the logger
|
|
2292
|
+
*/
|
|
2293
|
+
addSink(sink) {
|
|
2294
|
+
this.sinks.push(sink);
|
|
2295
|
+
}
|
|
2296
|
+
/**
|
|
2297
|
+
* Remove a sink from the logger
|
|
2298
|
+
* Returns true if the sink was found and removed, false otherwise
|
|
2299
|
+
*/
|
|
2300
|
+
removeSink(sink) {
|
|
2301
|
+
const index = this.sinks.indexOf(sink);
|
|
2302
|
+
if (index !== -1) {
|
|
2303
|
+
this.sinks.splice(index, 1);
|
|
2304
|
+
return true;
|
|
2305
|
+
}
|
|
2306
|
+
return false;
|
|
2307
|
+
}
|
|
2308
|
+
/**
|
|
2309
|
+
* Get a readonly copy of the current sinks
|
|
2310
|
+
*/
|
|
2311
|
+
getSinks() {
|
|
2312
|
+
return [...this.sinks];
|
|
2313
|
+
}
|
|
2314
|
+
/**
|
|
2315
|
+
* Close all sinks and cleanup resources
|
|
2316
|
+
* After closing, the logger is marked as closed and all sinks are removed
|
|
2317
|
+
*/
|
|
2318
|
+
async close() {
|
|
2319
|
+
this._closed = true;
|
|
2320
|
+
await Promise.all(
|
|
2321
|
+
this.sinks.map(async (sink) => {
|
|
2322
|
+
if (sink.close) {
|
|
2323
|
+
try {
|
|
2324
|
+
await sink.close();
|
|
2325
|
+
} catch (error) {
|
|
2326
|
+
this.handleSinkError(error, "close", sink);
|
|
2327
|
+
}
|
|
2328
|
+
}
|
|
2329
|
+
})
|
|
2330
|
+
);
|
|
2331
|
+
this.sinks = [];
|
|
2332
|
+
this.emit("logger", { eventType: "close" });
|
|
2333
|
+
}
|
|
2334
|
+
/**
|
|
2335
|
+
* Create a logger optimized for testing.
|
|
2336
|
+
* Includes an ArraySink by default for easy log inspection.
|
|
2337
|
+
* Process exit is disabled to prevent tests from terminating.
|
|
2338
|
+
*/
|
|
2339
|
+
static createTestOptimizedLogger(options) {
|
|
2340
|
+
const arraySink = new ArraySink({
|
|
2341
|
+
transformer: options?.arrayLogTransformer
|
|
2342
|
+
});
|
|
2343
|
+
const consoleSink = options?.includeConsoleSink ? new ConsoleSink({ muted: options?.muteConsole ?? true }) : void 0;
|
|
2344
|
+
const sinks = [arraySink];
|
|
2345
|
+
if (consoleSink) {
|
|
2346
|
+
sinks.push(consoleSink);
|
|
2347
|
+
}
|
|
2348
|
+
sinks.push(...options?.sinks || []);
|
|
2349
|
+
return {
|
|
2350
|
+
logger: new _Logger({
|
|
2351
|
+
sinks,
|
|
2352
|
+
callProcessExit: false
|
|
2353
|
+
}),
|
|
2354
|
+
arraySink,
|
|
2355
|
+
consoleSink
|
|
2356
|
+
};
|
|
2357
|
+
}
|
|
2358
|
+
/**
|
|
2359
|
+
* Create a logger optimized for frontend/browser use.
|
|
2360
|
+
* Includes a ConsoleSink by default for browser devtools output.
|
|
2361
|
+
* Process exit is disabled since browsers don't have process.exit.
|
|
2362
|
+
*/
|
|
2363
|
+
static createFrontendOptimizedLogger(options) {
|
|
2364
|
+
const consoleSink = new ConsoleSink({
|
|
2365
|
+
muted: options?.muteConsole ?? false
|
|
2366
|
+
});
|
|
2367
|
+
return {
|
|
2368
|
+
logger: new _Logger({
|
|
2369
|
+
sinks: [consoleSink, ...options?.sinks || []],
|
|
2370
|
+
callProcessExit: false
|
|
2371
|
+
}),
|
|
2372
|
+
consoleSink
|
|
2373
|
+
};
|
|
2374
|
+
}
|
|
2375
|
+
/**
|
|
2376
|
+
* Internal method to handle all log operations
|
|
2377
|
+
*/
|
|
2378
|
+
handleLog(type, template, options) {
|
|
2379
|
+
if (this._closed) {
|
|
2380
|
+
return;
|
|
2381
|
+
}
|
|
2382
|
+
const timestamp = ms();
|
|
2383
|
+
const exitCode = options?.exitCode;
|
|
2384
|
+
const serviceName = options?.serviceName?.trim() || void 0;
|
|
2385
|
+
const entityName = options?.entityName?.trim() || void 0;
|
|
2386
|
+
const params = options?.params;
|
|
2387
|
+
const tags = options?.tags;
|
|
2388
|
+
const redactedKeys = options?.redactedKeys;
|
|
2389
|
+
const message = params ? CurlyBrackets(template, params) : template;
|
|
2390
|
+
const redactedParams = params ? applyRedaction(params, redactedKeys, this.redactFunction) : void 0;
|
|
2391
|
+
const entry = {
|
|
2392
|
+
timestamp,
|
|
2393
|
+
type,
|
|
2394
|
+
serviceName,
|
|
2395
|
+
entityName,
|
|
2396
|
+
template,
|
|
2397
|
+
message,
|
|
2398
|
+
params,
|
|
2399
|
+
redactedParams,
|
|
2400
|
+
redactedKeys: params && redactedKeys && redactedKeys.length > 0 ? redactedKeys : void 0,
|
|
2401
|
+
error: options?.error,
|
|
2402
|
+
exitCode: isNumber(exitCode) ? exitCode : void 0,
|
|
2403
|
+
tags: tags && tags.length > 0 ? tags : void 0
|
|
2404
|
+
};
|
|
2405
|
+
for (const sink of this.sinks) {
|
|
2406
|
+
try {
|
|
2407
|
+
const result = sink.write(entry);
|
|
2408
|
+
if (isPromise(result)) {
|
|
2409
|
+
result.catch((error) => {
|
|
2410
|
+
this.handleSinkError(error, "write", sink);
|
|
2411
|
+
});
|
|
2412
|
+
}
|
|
2413
|
+
} catch (error) {
|
|
2414
|
+
this.handleSinkError(error, "write", sink);
|
|
2415
|
+
}
|
|
2416
|
+
}
|
|
2417
|
+
this.emit("logger", {
|
|
2418
|
+
eventType: "log",
|
|
2419
|
+
logType: type,
|
|
2420
|
+
message,
|
|
2421
|
+
timestamp
|
|
2422
|
+
});
|
|
2423
|
+
if (isNumber(exitCode)) {
|
|
2424
|
+
this.exit(exitCode);
|
|
2425
|
+
}
|
|
2426
|
+
}
|
|
2427
|
+
/**
|
|
2428
|
+
* Handle sink errors by calling the onSinkError callback or falling back to console.error
|
|
2429
|
+
*/
|
|
2430
|
+
handleSinkError(error, context, sink) {
|
|
2431
|
+
if (this.onSinkError) {
|
|
2432
|
+
try {
|
|
2433
|
+
this.onSinkError(error, context, sink);
|
|
2434
|
+
} catch {
|
|
2435
|
+
console.error(`Error in onSinkError handler: ${error.message}`);
|
|
2436
|
+
}
|
|
2437
|
+
} else {
|
|
2438
|
+
console.error(
|
|
2439
|
+
`Error ${context === "write" ? "writing to" : "closing"} sink: ${error.message}`
|
|
2440
|
+
);
|
|
2441
|
+
}
|
|
2442
|
+
}
|
|
2443
|
+
/**
|
|
2444
|
+
* Process the exit
|
|
2445
|
+
*/
|
|
2446
|
+
processExit(code) {
|
|
2447
|
+
this._didExit = true;
|
|
2448
|
+
this._exitCode = code;
|
|
2449
|
+
this._isPendingExit = false;
|
|
2450
|
+
this.emit("logger", { eventType: "exit-process", code });
|
|
2451
|
+
void this.close().finally(() => {
|
|
2452
|
+
if (this.callProcessExit) {
|
|
2453
|
+
if (typeof globalThis.process !== "undefined" && typeof globalThis.process.exit === "function") {
|
|
2454
|
+
globalThis.process.exit(code);
|
|
2455
|
+
}
|
|
2456
|
+
}
|
|
2457
|
+
});
|
|
2458
|
+
}
|
|
2459
|
+
};
|
|
2460
|
+
export {
|
|
2461
|
+
ArraySink,
|
|
2462
|
+
ConsoleSink,
|
|
2463
|
+
FileSink,
|
|
2464
|
+
LogLevel,
|
|
2465
|
+
Logger,
|
|
2466
|
+
NamedPipeSink,
|
|
2467
|
+
PipeErrorType,
|
|
2468
|
+
getLogLevel
|
|
2469
|
+
};
|
|
2470
|
+
//# sourceMappingURL=index.js.map
|