nightingale 14.2.1 → 16.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (108) hide show
  1. package/CHANGELOG.md +33 -0
  2. package/README.md +204 -57
  3. package/dist/definitions/config.d.ts +1 -1
  4. package/dist/definitions/debug/debug.d.ts +5 -0
  5. package/dist/definitions/debug/debug.d.ts.map +1 -0
  6. package/dist/definitions/debug/debug.test.d.ts +2 -0
  7. package/dist/definitions/debug/debug.test.d.ts.map +1 -0
  8. package/dist/definitions/formatter-utils/formatObject.d.ts +9 -0
  9. package/dist/definitions/formatter-utils/formatObject.d.ts.map +1 -0
  10. package/dist/definitions/formatter-utils/formatObject.test.d.ts +2 -0
  11. package/dist/definitions/formatter-utils/formatObject.test.d.ts.map +1 -0
  12. package/dist/definitions/formatter-utils/formatRecordToString.d.ts +4 -0
  13. package/dist/definitions/formatter-utils/formatRecordToString.d.ts.map +1 -0
  14. package/dist/definitions/formatter-utils/index.d.ts +15 -0
  15. package/dist/definitions/formatter-utils/index.d.ts.map +1 -0
  16. package/dist/definitions/formatter-utils/index.test.d.ts +2 -0
  17. package/dist/definitions/formatter-utils/index.test.d.ts.map +1 -0
  18. package/dist/definitions/formatter-utils/levelToStyles.d.ts +3 -0
  19. package/dist/definitions/formatter-utils/levelToStyles.d.ts.map +1 -0
  20. package/dist/definitions/formatter-utils/levelToSymbol.d.ts +3 -0
  21. package/dist/definitions/formatter-utils/levelToSymbol.d.ts.map +1 -0
  22. package/dist/definitions/formatter-utils/styleToHexColor.d.ts +7 -0
  23. package/dist/definitions/formatter-utils/styleToHexColor.d.ts.map +1 -0
  24. package/dist/definitions/formatter-utils/styleToHtmlStyle.d.ts +109 -0
  25. package/dist/definitions/formatter-utils/styleToHtmlStyle.d.ts.map +1 -0
  26. package/dist/definitions/formatters/ANSIFormatter.d.ts +6 -0
  27. package/dist/definitions/formatters/ANSIFormatter.d.ts.map +1 -0
  28. package/dist/definitions/formatters/ANSIFormatter.test.d.ts +2 -0
  29. package/dist/definitions/formatters/ANSIFormatter.test.d.ts.map +1 -0
  30. package/dist/definitions/formatters/BrowserConsoleFormatter.d.ts +10 -0
  31. package/dist/definitions/formatters/BrowserConsoleFormatter.d.ts.map +1 -0
  32. package/dist/definitions/formatters/BrowserConsoleFormatter.test.d.ts +2 -0
  33. package/dist/definitions/formatters/BrowserConsoleFormatter.test.d.ts.map +1 -0
  34. package/dist/definitions/formatters/HTMLFormatter.d.ts +5 -0
  35. package/dist/definitions/formatters/HTMLFormatter.d.ts.map +1 -0
  36. package/dist/definitions/formatters/HTMLFormatter.test.d.ts +2 -0
  37. package/dist/definitions/formatters/HTMLFormatter.test.d.ts.map +1 -0
  38. package/dist/definitions/formatters/JSONFormatter.d.ts +4 -0
  39. package/dist/definitions/formatters/JSONFormatter.d.ts.map +1 -0
  40. package/dist/definitions/formatters/JSONFormatter.test.d.ts +2 -0
  41. package/dist/definitions/formatters/JSONFormatter.test.d.ts.map +1 -0
  42. package/dist/definitions/formatters/MarkdownFormatter.d.ts +5 -0
  43. package/dist/definitions/formatters/MarkdownFormatter.d.ts.map +1 -0
  44. package/dist/definitions/formatters/MarkdownFormatter.test.d.ts +2 -0
  45. package/dist/definitions/formatters/MarkdownFormatter.test.d.ts.map +1 -0
  46. package/dist/definitions/formatters/RawFormatter.d.ts +5 -0
  47. package/dist/definitions/formatters/RawFormatter.d.ts.map +1 -0
  48. package/dist/definitions/formatters/RawFormatter.test.d.ts +2 -0
  49. package/dist/definitions/formatters/RawFormatter.test.d.ts.map +1 -0
  50. package/dist/definitions/handlers/BrowserConsoleHandler.d.ts +14 -0
  51. package/dist/definitions/handlers/BrowserConsoleHandler.d.ts.map +1 -0
  52. package/dist/definitions/handlers/ConsoleCLIHandler.d.ts +12 -0
  53. package/dist/definitions/handlers/ConsoleCLIHandler.d.ts.map +1 -0
  54. package/dist/definitions/handlers/ConsoleHandler.d.ts +14 -0
  55. package/dist/definitions/handlers/ConsoleHandler.d.ts.map +1 -0
  56. package/dist/definitions/handlers/StringHandler.d.ts +9 -0
  57. package/dist/definitions/handlers/StringHandler.d.ts.map +1 -0
  58. package/dist/definitions/index.d.ts +18 -4
  59. package/dist/definitions/index.d.ts.map +1 -1
  60. package/dist/definitions/loggers/LoggerCLI.d.ts +23 -0
  61. package/dist/definitions/loggers/LoggerCLI.d.ts.map +1 -0
  62. package/dist/definitions/outputs/cliConsoleOutput.d.ts +3 -0
  63. package/dist/definitions/outputs/cliConsoleOutput.d.ts.map +1 -0
  64. package/dist/definitions/outputs/consoleOutput.d.ts +3 -0
  65. package/dist/definitions/outputs/consoleOutput.d.ts.map +1 -0
  66. package/dist/index-browser.es.js +936 -46
  67. package/dist/index-browser.es.js.map +1 -1
  68. package/dist/index-node20.mjs +1023 -0
  69. package/dist/index-node20.mjs.map +1 -0
  70. package/package.json +31 -37
  71. package/src/config.ts +12 -12
  72. package/src/debug/debug.test.ts +50 -0
  73. package/src/debug/debug.ts +100 -0
  74. package/src/formatter-utils/formatObject.test.ts +153 -0
  75. package/src/formatter-utils/formatObject.ts +462 -0
  76. package/src/formatter-utils/formatRecordToString.ts +67 -0
  77. package/src/formatter-utils/index.test.ts +33 -0
  78. package/src/formatter-utils/index.ts +20 -0
  79. package/src/formatter-utils/levelToStyles.ts +14 -0
  80. package/src/formatter-utils/levelToSymbol.ts +14 -0
  81. package/src/formatter-utils/styleToHexColor.ts +9 -0
  82. package/src/formatter-utils/styleToHtmlStyle.ts +69 -0
  83. package/src/formatters/ANSIFormatter.test.ts +27 -0
  84. package/src/formatters/ANSIFormatter.ts +68 -0
  85. package/src/formatters/BrowserConsoleFormatter.test.ts +59 -0
  86. package/src/formatters/BrowserConsoleFormatter.ts +45 -0
  87. package/src/formatters/HTMLFormatter.test.ts +23 -0
  88. package/src/formatters/HTMLFormatter.ts +28 -0
  89. package/src/formatters/JSONFormatter.test.ts +62 -0
  90. package/src/formatters/JSONFormatter.ts +62 -0
  91. package/src/formatters/MarkdownFormatter.test.ts +19 -0
  92. package/src/formatters/MarkdownFormatter.ts +31 -0
  93. package/src/formatters/RawFormatter.test.ts +21 -0
  94. package/src/formatters/RawFormatter.ts +13 -0
  95. package/src/handlers/BrowserConsoleHandler.ts +78 -0
  96. package/src/handlers/ConsoleCLIHandler.ts +41 -0
  97. package/src/handlers/ConsoleHandler.ts +55 -0
  98. package/src/handlers/StringHandler.ts +21 -0
  99. package/src/index.test.ts +29 -29
  100. package/src/index.ts +24 -10
  101. package/src/loggers/LoggerCLI.ts +91 -0
  102. package/src/outputs/cliConsoleOutput.ts +17 -0
  103. package/src/outputs/consoleOutput.ts +18 -0
  104. package/dist/index-browsermodern.es.js +0 -120
  105. package/dist/index-browsermodern.es.js.map +0 -1
  106. package/dist/index-node18.mjs +0 -120
  107. package/dist/index-node18.mjs.map +0 -1
  108. package/src/.eslintrc.json +0 -30
@@ -0,0 +1,153 @@
1
+ import type { Styles } from "nightingale-types";
2
+ import { formatObject } from "./formatObject";
3
+
4
+ test("empty object should return empty string", () => {
5
+ expect(formatObject({})).toBe("");
6
+ });
7
+
8
+ const styleFn = (styles: Styles, value: string): string =>
9
+ styles && styles.length > 0
10
+ ? `[styles:${styles.join(",")}]${value}[/styles]`
11
+ : value;
12
+
13
+ const noStyleFn = (styles: Styles, value: string): string => value;
14
+
15
+ test("simple object", () => {
16
+ expect(formatObject({ a: 1 }, styleFn)).toBe(
17
+ "{ [styles:gray-light,bold]a:[/styles] [styles:yellow]1[/styles] }",
18
+ );
19
+ });
20
+
21
+ test("simple without prototype", () => {
22
+ expect(
23
+ formatObject(
24
+ Object.assign(Object.create(null) as Record<string, number>, { a: 1 }),
25
+ styleFn,
26
+ ),
27
+ ).toBe("{ [styles:gray-light,bold]a:[/styles] [styles:yellow]1[/styles] }");
28
+ });
29
+
30
+ test("long object", () => {
31
+ expect(
32
+ formatObject(
33
+ {
34
+ obj: {
35
+ a: 10_000_000_000_000_000_000,
36
+ b: 10_000_000_000_000_000_000,
37
+ c: 10_000_000_000_000_000_000,
38
+ d: 10_000_000_000_000_000_000,
39
+ e: 10_000_000_000_000_000_000,
40
+ f: 10_000_000_000_000_000_000,
41
+ },
42
+ },
43
+ noStyleFn,
44
+ ),
45
+ ).toBe(
46
+ "{\n obj: { a: 10000000000000000000, b: 10000000000000000000, c: 10000000000000000000, d: 10000000000000000000, e: 10000000000000000000, f: 10000000000000000000 },\n}",
47
+ );
48
+ });
49
+
50
+ test("multiple values", () => {
51
+ expect(
52
+ formatObject(
53
+ {
54
+ // eslint-disable-next-line object-shorthand
55
+ undefined: undefined,
56
+ null: null,
57
+ number: 1,
58
+ string: "s",
59
+ bigInt: BigInt(1),
60
+ symbol: Symbol("symbol"),
61
+ },
62
+ styleFn,
63
+ ),
64
+ ).toBe(
65
+ '{ [styles:gray-light,bold]undefined:[/styles] [styles:cyan]undefined[/styles][styles:gray],[/styles] [styles:gray-light,bold]null:[/styles] [styles:cyan]null[/styles][styles:gray],[/styles] [styles:gray-light,bold]number:[/styles] [styles:yellow]1[/styles][styles:gray],[/styles] [styles:gray-light,bold]string:[/styles] [styles:orange]"s"[/styles][styles:gray],[/styles] [styles:gray-light,bold]bigInt:[/styles] [styles:red]1[/styles][styles:gray],[/styles] [styles:gray-light,bold]symbol:[/styles] [styles:magenta]Symbol(symbol)[/styles] }',
66
+ );
67
+ });
68
+
69
+ test("simple embeded empty object", () => {
70
+ expect(formatObject({ a: {} }, noStyleFn)).toBe("{ a: {} }");
71
+ });
72
+
73
+ test("simple embeded object", () => {
74
+ expect(formatObject({ a: { b: 1 } }, noStyleFn)).toBe("{ a: { b: 1 } }");
75
+ });
76
+
77
+ test("simple recursive object", () => {
78
+ const a: { a: any } = { a: 1 };
79
+ a.a = a;
80
+ expect(a.a).toBe(a);
81
+ expect(formatObject({ a }, noStyleFn)).toBe(
82
+ "{ a: { a: {Circular Object} } }",
83
+ );
84
+ });
85
+
86
+ test("empty map", () => {
87
+ expect(formatObject({ a: new Map() }, noStyleFn)).toBe("{ a: Map {} }");
88
+ });
89
+
90
+ test("simple map", () => {
91
+ expect(
92
+ formatObject(
93
+ {
94
+ a: new Map<any, any>([
95
+ ["key1", "value1"],
96
+ [{ b: 1 }, "value2"],
97
+ ]),
98
+ },
99
+ noStyleFn,
100
+ ),
101
+ ).toBe('{ a: Map { "key1": "value1", { b: 1 }: "value2" } }');
102
+ });
103
+
104
+ test("empty array", () => {
105
+ expect(formatObject({ a: [] }, noStyleFn)).toBe("{ a: [] }");
106
+ });
107
+
108
+ test("simple array", () => {
109
+ expect(formatObject({ a: [1, "2", 3, 4, 5] }, noStyleFn)).toBe(
110
+ '{ a: [1, "2", 3, 4, 5] }',
111
+ );
112
+ });
113
+
114
+ test("object in array", () => {
115
+ const obj = { a: 1, b: 2 };
116
+ expect(formatObject({ a: [obj] }, noStyleFn)).toBe("{ a: [{ a: 1, b: 2 }] }");
117
+ });
118
+
119
+ test("objects in array", () => {
120
+ const obj = { a: 1, b: 2 };
121
+ expect(formatObject({ a: [obj, obj] }, noStyleFn)).toBe(
122
+ "{ a: [{ a: 1, b: 2 }, { a: 1, b: 2 }] }",
123
+ );
124
+ });
125
+
126
+ test("objects with breaking lines in array", () => {
127
+ const obj = {
128
+ a: 10_000_000_000_000_000_000,
129
+ b: 10_000_000_000_000_000_000,
130
+ c: 10_000_000_000_000_000_000,
131
+ d: 10_000_000_000_000_000_000,
132
+ e: 10_000_000_000_000_000_000,
133
+ f: 10_000_000_000_000_000_000,
134
+ };
135
+ expect(formatObject({ a: [obj, obj] }, noStyleFn)).toBe(
136
+ `{
137
+ a: [
138
+ { a: 10000000000000000000, b: 10000000000000000000, c: 10000000000000000000, d: 10000000000000000000, e: 10000000000000000000, f: 10000000000000000000 },
139
+ { a: 10000000000000000000, b: 10000000000000000000, c: 10000000000000000000, d: 10000000000000000000, e: 10000000000000000000, f: 10000000000000000000 },
140
+ ],
141
+ }`,
142
+ );
143
+ });
144
+
145
+ test("empty set", () => {
146
+ expect(formatObject({ a: new Set() }, noStyleFn)).toBe("{ a: Set [] }");
147
+ });
148
+
149
+ test("simple set", () => {
150
+ expect(formatObject({ a: new Set(["value1", "value2"]) }, noStyleFn)).toBe(
151
+ '{ a: Set [ "value1", "value2" ] }',
152
+ );
153
+ });
@@ -0,0 +1,462 @@
1
+ /* eslint-disable @typescript-eslint/no-use-before-define */
2
+ import type { Styles } from "nightingale-types";
3
+
4
+ export interface FormatObjectOptions {
5
+ padding?: string;
6
+ maxDepth?: number;
7
+ }
8
+
9
+ export type StyleFn = (styles: Styles, value: string) => string;
10
+
11
+ export type ObjectStyles<Keys extends string = string> = Record<Keys, Styles>;
12
+
13
+ const noStyleFn: StyleFn = (styles: Styles, value: string): string => value;
14
+
15
+ interface InternalFormatParams {
16
+ padding: string;
17
+ depth: number;
18
+ maxDepth: number;
19
+ objects: Set<unknown>;
20
+ }
21
+
22
+ interface FormattedKey {
23
+ stringKey: string;
24
+ formattedKey: string;
25
+ }
26
+
27
+ type FormatKey<Key> = (
28
+ key: Key,
29
+ styleFn: StyleFn,
30
+ internalFormatParams: InternalFormatParams,
31
+ ) => FormattedKey;
32
+
33
+ interface Value<Key> {
34
+ key: Key;
35
+ value: unknown;
36
+ }
37
+
38
+ interface FormattedValue {
39
+ stringValue: string;
40
+ formattedValue: string;
41
+ }
42
+
43
+ type Values<Key> = Value<Key>[];
44
+
45
+ interface InternalFormatIteratorParams<Key> {
46
+ prefix: string;
47
+ suffix: string;
48
+ formatKey: FormatKey<Key>;
49
+ prefixSuffixSpace?: string;
50
+ }
51
+
52
+ function tryStringify(arg: unknown): string {
53
+ try {
54
+ return JSON.stringify(arg).replace(/\\n/g, "\n");
55
+ } catch {
56
+ return "[Circular]";
57
+ }
58
+ }
59
+
60
+ const sameRawFormattedValue = (value: string): FormattedValue => ({
61
+ stringValue: value,
62
+ formattedValue: value,
63
+ });
64
+
65
+ function internalFormatValue(
66
+ value: unknown,
67
+ styleFn: StyleFn,
68
+ styles: Styles,
69
+ { padding, depth, maxDepth, objects }: InternalFormatParams,
70
+ ): FormattedValue {
71
+ const typeofValue = typeof value;
72
+
73
+ if (!styles) {
74
+ if (value == null) {
75
+ styles = ["cyan"];
76
+ } else {
77
+ switch (typeofValue) {
78
+ case "undefined":
79
+ styles = ["cyan"];
80
+ break;
81
+ case "boolean":
82
+ styles = ["green"];
83
+ break;
84
+ case "number":
85
+ styles = ["yellow"];
86
+ break;
87
+ case "bigint":
88
+ styles = ["red"];
89
+ break;
90
+ case "string":
91
+ styles = ["orange"];
92
+ break;
93
+ case "symbol":
94
+ styles = ["magenta"];
95
+ break;
96
+ case "object":
97
+ case "function":
98
+ default:
99
+ break;
100
+ }
101
+ }
102
+ }
103
+
104
+ let stringValue: string;
105
+ if (value === null) {
106
+ stringValue = "null";
107
+ } else if (value === undefined) {
108
+ stringValue = "undefined";
109
+ } else if (typeofValue === "boolean") {
110
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-call
111
+ stringValue = (value as any).toString() as string;
112
+ } else if ((value as () => unknown).constructor === Object) {
113
+ if (depth >= maxDepth) {
114
+ stringValue = "{Object...}";
115
+ } else {
116
+ return internalFormatObject(
117
+ value as Record<string, unknown>,
118
+ styleFn,
119
+ undefined,
120
+ {
121
+ padding,
122
+ depth: depth + 1,
123
+ maxDepth,
124
+ objects,
125
+ },
126
+ );
127
+ }
128
+ } else if (Array.isArray(value)) {
129
+ if (depth >= maxDepth) {
130
+ stringValue = "[Array...]";
131
+ } else {
132
+ return internalFormatArray(value, styleFn, {
133
+ padding,
134
+ depth: depth + 1,
135
+ maxDepth,
136
+ objects,
137
+ });
138
+ }
139
+ } else if (value instanceof Error) {
140
+ const stack = value.stack;
141
+ stringValue =
142
+ stack?.startsWith(value.message) ||
143
+ stack?.startsWith(`${value.name}: ${value.message}`)
144
+ ? stack
145
+ : `${value.message}\n${stack || ""}`;
146
+ } else if (value instanceof Map) {
147
+ const name = value.constructor.name;
148
+ if (depth >= maxDepth) {
149
+ stringValue = `{${name}...}`;
150
+ } else {
151
+ return internalFormatMap(name, value, styleFn, {
152
+ padding,
153
+ depth: depth + 1,
154
+ maxDepth,
155
+ objects,
156
+ });
157
+ }
158
+ } else if (typeofValue === "bigint") {
159
+ stringValue = (value as bigint).toString();
160
+ } else if (typeofValue === "symbol") {
161
+ stringValue = (value as symbol).toString();
162
+ } else if (value instanceof Set) {
163
+ const name = value.constructor.name;
164
+ if (depth >= maxDepth) {
165
+ stringValue = `{${name}...}`;
166
+ } else {
167
+ return internalFormatSet(name, value, styleFn, {
168
+ padding,
169
+ depth: depth + 1,
170
+ maxDepth,
171
+ objects,
172
+ });
173
+ }
174
+ } else if (value instanceof WeakMap) {
175
+ stringValue = "{WeakMap...}";
176
+ } else if (value instanceof WeakSet) {
177
+ stringValue = "{WeakSet...}";
178
+ } else {
179
+ stringValue = tryStringify(value);
180
+ }
181
+
182
+ const formattedValue = styleFn(styles, stringValue);
183
+
184
+ return {
185
+ stringValue,
186
+ formattedValue,
187
+ };
188
+ }
189
+
190
+ const separator = ",";
191
+
192
+ const internalFormatKey: FormatKey<string> = (
193
+ key: string,
194
+ styleFn: StyleFn,
195
+ internalFormatParams: InternalFormatParams,
196
+ ): FormattedKey => {
197
+ return {
198
+ stringKey: `${key}: `,
199
+ formattedKey: `${styleFn(["gray-light", "bold"], `${key}:`)} `,
200
+ };
201
+ };
202
+
203
+ const internalNoKey: FormatKey<undefined> = (
204
+ key: string | undefined,
205
+ styleFn: StyleFn,
206
+ internalFormatParams: InternalFormatParams,
207
+ ): FormattedKey => {
208
+ return { stringKey: "", formattedKey: "" };
209
+ };
210
+
211
+ const internalFormatMapKey: FormatKey<unknown> = (
212
+ key: unknown,
213
+ styleFn: StyleFn,
214
+ internalFormatParams: InternalFormatParams,
215
+ ): FormattedKey => {
216
+ const { stringValue, formattedValue } = internalFormatValue(
217
+ key,
218
+ noStyleFn,
219
+ undefined,
220
+ internalFormatParams,
221
+ );
222
+ return {
223
+ stringKey: `${stringValue} => `,
224
+ formattedKey: `${styleFn(["gray-light", "bold"], `${formattedValue}:`)} `,
225
+ };
226
+ };
227
+
228
+ const internalFormatIterator = <Key>(
229
+ values: Values<Key>,
230
+ styleFn: StyleFn,
231
+ objectStyles: ObjectStyles | undefined,
232
+ { padding, depth, maxDepth, objects }: InternalFormatParams,
233
+ {
234
+ prefix,
235
+ suffix,
236
+ prefixSuffixSpace = " ",
237
+ formatKey,
238
+ }: InternalFormatIteratorParams<Key>,
239
+ ): FormattedValue => {
240
+ let breakLine = false;
241
+ const formattedSeparator = (): string => styleFn(["gray"], separator);
242
+
243
+ const valuesMaxIndex = values.length - 1;
244
+ const formattedValues: FormattedValue[] = values.map(
245
+ ({ key, value }, index: number) => {
246
+ const nextDepth = depth + 1;
247
+ const internalFormatParams = {
248
+ padding,
249
+ depth: nextDepth,
250
+ maxDepth,
251
+ objects,
252
+ };
253
+
254
+ // key must be formatted before value (browser-formatter needs order)
255
+ const { stringKey, formattedKey } = formatKey(
256
+ key,
257
+ styleFn,
258
+ internalFormatParams,
259
+ );
260
+
261
+ let { stringValue, formattedValue } = internalFormatValue(
262
+ value,
263
+ styleFn,
264
+ key && objectStyles
265
+ ? objectStyles[key as unknown as string]
266
+ : undefined,
267
+ internalFormatParams,
268
+ );
269
+
270
+ if (
271
+ stringValue &&
272
+ (stringValue.length > 80 || stringValue.includes("\n"))
273
+ ) {
274
+ breakLine = true;
275
+ stringValue = stringValue.replace(/\n/g, `\n${padding}`);
276
+ formattedValue = formattedValue.replace(/\n/g, `\n${padding}`);
277
+ }
278
+
279
+ return {
280
+ stringValue:
281
+ stringKey + stringValue + (index === valuesMaxIndex ? "" : separator),
282
+ formattedValue:
283
+ formattedKey +
284
+ formattedValue +
285
+ (index === valuesMaxIndex ? "" : formattedSeparator()),
286
+ // note: we need to format the separator for each values for browser-formatter
287
+ };
288
+ },
289
+ );
290
+
291
+ return {
292
+ stringValue:
293
+ prefix +
294
+ formattedValues
295
+ .map(
296
+ breakLine
297
+ ? (v) => `\n${padding}${v.stringValue}`
298
+ : (fv) => fv.stringValue,
299
+ )
300
+ .join(breakLine ? "\n" : " ") +
301
+ suffix,
302
+ formattedValue: `${prefix}${
303
+ breakLine ? "" : prefixSuffixSpace
304
+ }${formattedValues
305
+ .map(
306
+ breakLine
307
+ ? (v) => `\n${padding}${v.formattedValue}`
308
+ : (v) => v.formattedValue,
309
+ )
310
+ .join(breakLine ? "" : " ")}${
311
+ breakLine ? ",\n" : prefixSuffixSpace
312
+ }${suffix}`,
313
+ };
314
+ };
315
+
316
+ function internalFormatObject(
317
+ object: Record<string, unknown>,
318
+ styleFn: StyleFn,
319
+ objectStyles: ObjectStyles | undefined,
320
+ { padding, depth, maxDepth, objects }: InternalFormatParams,
321
+ ): FormattedValue {
322
+ if (objects.has(object)) {
323
+ return sameRawFormattedValue("{Circular Object}");
324
+ }
325
+
326
+ const keys: string[] = Object.keys(object);
327
+ if (keys.length === 0) {
328
+ return sameRawFormattedValue("{}");
329
+ }
330
+
331
+ objects.add(object);
332
+
333
+ const result = internalFormatIterator(
334
+ keys.map((key) => ({ key, value: object[key] })),
335
+ styleFn,
336
+ objectStyles,
337
+ { padding, depth, maxDepth, objects },
338
+ { prefix: "{", suffix: "}", formatKey: internalFormatKey },
339
+ );
340
+
341
+ objects.delete(object);
342
+
343
+ return result;
344
+ }
345
+
346
+ function internalFormatMap(
347
+ name: string,
348
+ map: Map<unknown, unknown>,
349
+ styleFn: StyleFn,
350
+ { padding, depth, maxDepth, objects }: InternalFormatParams,
351
+ ): FormattedValue {
352
+ if (objects.has(map)) {
353
+ return sameRawFormattedValue(`{Circular ${name}}`);
354
+ }
355
+
356
+ const keys = [...map.keys()];
357
+ if (keys.length === 0) {
358
+ return sameRawFormattedValue(`${name} {}`);
359
+ }
360
+
361
+ objects.add(map);
362
+
363
+ const result = internalFormatIterator(
364
+ keys.map((key) => ({ key, value: map.get(key) })),
365
+ styleFn,
366
+ undefined,
367
+ { padding, depth, maxDepth, objects },
368
+ { prefix: `${name} {`, suffix: "}", formatKey: internalFormatMapKey },
369
+ );
370
+
371
+ objects.delete(map);
372
+
373
+ return result;
374
+ }
375
+
376
+ function internalFormatArray(
377
+ array: unknown[],
378
+ styleFn: StyleFn,
379
+ { padding, depth, maxDepth, objects }: InternalFormatParams,
380
+ ): FormattedValue {
381
+ if (objects.has(array)) {
382
+ return sameRawFormattedValue("{Circular Array}");
383
+ }
384
+
385
+ if (array.length === 0) {
386
+ return sameRawFormattedValue("[]");
387
+ }
388
+
389
+ objects.add(array);
390
+
391
+ const result = internalFormatIterator(
392
+ array.map((value) => ({ key: undefined, value })),
393
+ styleFn,
394
+ undefined,
395
+ { padding, depth, maxDepth, objects },
396
+ {
397
+ prefix: "[",
398
+ suffix: "]",
399
+ prefixSuffixSpace: "",
400
+ formatKey: internalNoKey,
401
+ },
402
+ );
403
+
404
+ objects.delete(array);
405
+
406
+ return result;
407
+ }
408
+
409
+ function internalFormatSet(
410
+ name: string,
411
+ set: Set<unknown>,
412
+ styleFn: StyleFn,
413
+ { padding, depth, maxDepth, objects }: InternalFormatParams,
414
+ ): FormattedValue {
415
+ if (objects.has(set)) {
416
+ return sameRawFormattedValue(`{Circular ${name}}`);
417
+ }
418
+
419
+ const values = [...set.values()];
420
+ if (values.length === 0) {
421
+ return sameRawFormattedValue(`${name} []`);
422
+ }
423
+
424
+ objects.add(set);
425
+
426
+ const result = internalFormatIterator(
427
+ values.map((value) => ({ key: undefined, value })),
428
+ styleFn,
429
+ undefined,
430
+ { padding, depth, maxDepth, objects },
431
+ { prefix: `${name} [`, suffix: "]", formatKey: internalNoKey },
432
+ );
433
+
434
+ objects.delete(set);
435
+
436
+ return result;
437
+ }
438
+
439
+ export function formatObject(
440
+ object: Record<string, unknown>,
441
+ styleFn: StyleFn = noStyleFn,
442
+ objectStyles?: ObjectStyles,
443
+ { padding = " ", maxDepth = 10 }: FormatObjectOptions = {},
444
+ ): string {
445
+ const { formattedValue: result } = internalFormatObject(
446
+ object,
447
+ styleFn,
448
+ objectStyles,
449
+ {
450
+ padding,
451
+ maxDepth,
452
+ depth: 0,
453
+ objects: new Set(),
454
+ },
455
+ );
456
+
457
+ if (result === "{}") {
458
+ return "";
459
+ }
460
+
461
+ return result;
462
+ }
@@ -0,0 +1,67 @@
1
+ import type { LogRecord, Metadata } from "nightingale-types";
2
+ import type { ObjectStyles, StyleFn } from "./formatObject";
3
+ import { formatObject } from "./formatObject";
4
+ import { levelToStyles } from "./levelToStyles";
5
+ import { levelToSymbol } from "./levelToSymbol";
6
+
7
+ export function formatRecordToString<T extends Metadata>(
8
+ record: LogRecord<T>,
9
+ style: StyleFn,
10
+ ): string {
11
+ const parts: string[] = [];
12
+
13
+ if (record.displayName) {
14
+ parts.push(style(["gray-light"], record.displayName));
15
+ } else if (record.key) {
16
+ parts.push(style(["gray-light"], record.key));
17
+ }
18
+
19
+ if (record.datetime) {
20
+ parts.push(
21
+ style(["gray", "bold"], record.datetime.toTimeString().split(" ", 2)[0]!),
22
+ );
23
+ /* new Date().toFormat('HH24:MI:SS') */
24
+ }
25
+
26
+ let message: string = record.symbol || levelToSymbol[record.level] || "";
27
+ const styles = record.styles || levelToStyles[record.level];
28
+
29
+ if (record.message) {
30
+ if (message) {
31
+ message += ` ${record.message}`;
32
+ } else {
33
+ message = record.message;
34
+ }
35
+ }
36
+
37
+ if (message) {
38
+ if (styles) {
39
+ message = style(styles, message);
40
+ }
41
+ parts.push(message);
42
+ }
43
+
44
+ const formatRecordObject = (
45
+ key: string,
46
+ object: Record<string, unknown> | undefined,
47
+ objectStyles: ObjectStyles | undefined,
48
+ ): void => {
49
+ if (!object) {
50
+ return;
51
+ }
52
+
53
+ const stringObject = formatObject(object, style, objectStyles);
54
+
55
+ if (!stringObject) {
56
+ return;
57
+ }
58
+
59
+ parts.push(stringObject);
60
+ };
61
+
62
+ formatRecordObject("metadata", record.metadata, record.metadataStyles);
63
+ formatRecordObject("extra", record.extra, undefined);
64
+ formatRecordObject("context", record.context, undefined);
65
+
66
+ return parts.join(" ");
67
+ }
@@ -0,0 +1,33 @@
1
+ import {
2
+ formatObject,
3
+ formatRecordToString,
4
+ levelToStyles,
5
+ levelToSymbol,
6
+ styleToHexColor,
7
+ styleToHtmlStyleThemeDark,
8
+ styleToHtmlStyleThemeLight,
9
+ } from ".";
10
+
11
+ describe("test exports", () => {
12
+ test("levelToStyles", () => {
13
+ expect(typeof levelToStyles).toBe("object");
14
+ });
15
+ test("levelToSymbol", () => {
16
+ expect(typeof levelToSymbol).toBe("object");
17
+ });
18
+ test("styleToHtmlStyleThemeLight", () => {
19
+ expect(typeof styleToHtmlStyleThemeLight).toBe("object");
20
+ });
21
+ test("styleToHtmlStyleThemeDark", () => {
22
+ expect(typeof styleToHtmlStyleThemeDark).toBe("object");
23
+ });
24
+ test("styleToHexColor", () => {
25
+ expect(typeof styleToHexColor).toBe("object");
26
+ });
27
+ test("formatObject", () => {
28
+ expect(typeof formatObject).toBe("function");
29
+ });
30
+ test("formatRecordToString", () => {
31
+ expect(typeof formatRecordToString).toBe("function");
32
+ });
33
+ });