@ls-stack/utils 3.23.0 → 3.24.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.
@@ -1,3 +1,5 @@
1
+ import { YamlStringifyOptions } from './yamlStringify.js';
2
+
1
3
  declare function createLoggerStore({ filterKeys: defaultFilterKeys, rejectKeys: defaultRejectKeys, splitLongLines: defaultSplitLongLines, maxLineLengthBeforeSplit: defaultMaxLineLengthBeforeSplit, fromLastSnapshot: defaultFromLastSnapshot, arrays: defaultArrays, changesOnly: defaultChangesOnly, useEmojiForBooleans: defaultUseEmojiForBooleans, }?: {
2
4
  filterKeys?: string[];
3
5
  rejectKeys?: string[];
@@ -39,5 +41,69 @@ declare function waitController(): {
39
41
  stopWaiting: () => void;
40
42
  stopWaitingAfter: (ms: number) => void;
41
43
  };
44
+ declare function compactSnapshot(value: unknown, { collapseObjects, maxLineLength, showUndefined, showBooleansAs, rejectKeys, filterKeys, ...options }?: YamlStringifyOptions & {
45
+ showBooleansAs?: boolean | {
46
+ props?: Record<string, {
47
+ trueText?: string;
48
+ falseText?: string;
49
+ } | true>;
50
+ ignoreProps?: string[];
51
+ trueText?: string;
52
+ falseText?: string;
53
+ };
54
+ /**
55
+ * Reject (exclude) keys from the snapshot using pattern matching.
56
+ *
57
+ * **Pattern Syntax:**
58
+ * - `'prop'` - Only root-level properties named 'prop'
59
+ * - `'prop.nested'` - Exact nested property paths like `obj.prop.nested`
60
+ * - `'*prop'` - Any property named exactly 'prop' at any level (root or nested)
61
+ * - `'*.prop'` - Any nested property named 'prop' (excludes root-level matches)
62
+ *
63
+ * **Examples:**
64
+ * ```typescript
65
+ * // Reject root-level 'secret' only
66
+ * rejectKeys: ['secret']
67
+ *
68
+ * // Reject nested 'password' properties only
69
+ * rejectKeys: ['*.password']
70
+ *
71
+ * // Reject any 'apiKey' property at any level
72
+ * rejectKeys: ['*apiKey']
73
+ *
74
+ * // Reject specific nested path
75
+ * rejectKeys: ['user.settings.theme']
76
+ * ```
77
+ */
78
+ rejectKeys?: string[] | string;
79
+ /**
80
+ * Filter (include only) keys that match the specified patterns.
81
+ *
82
+ * **Pattern Syntax:** (same as rejectKeys)
83
+ * - `'prop'` - Only root-level properties named 'prop'
84
+ * - `'prop.nested'` - Exact nested property paths like `obj.prop.nested`
85
+ * - `'*prop'` - Any property named exactly 'prop' at any level (root or nested)
86
+ * - `'*.prop'` - Any nested property named 'prop' (excludes root-level matches)
87
+ *
88
+ * **Examples:**
89
+ * ```typescript
90
+ * // Include only root-level 'user'
91
+ * filterKeys: ['user']
92
+ *
93
+ * // Include all 'name' properties at any level
94
+ * filterKeys: ['*name']
95
+ *
96
+ * // Include only nested 'id' properties
97
+ * filterKeys: ['*.id']
98
+ *
99
+ * // Include specific nested paths
100
+ * filterKeys: ['user.profile.email', 'settings.theme']
101
+ * ```
102
+ *
103
+ * **Note:** When filtering, parent paths are automatically included if needed
104
+ * to preserve the structure for nested matches.
105
+ */
106
+ filterKeys?: string[] | string;
107
+ }): string;
42
108
 
43
- export { createLoggerStore, getResultFn, waitController };
109
+ export { compactSnapshot, createLoggerStore, getResultFn, waitController };
package/lib/testUtils.js CHANGED
@@ -1,7 +1,11 @@
1
+ import {
2
+ yamlStringify
3
+ } from "./chunk-JAPKLFIK.js";
1
4
  import {
2
5
  omit,
3
6
  pick
4
7
  } from "./chunk-GHAQOUA6.js";
8
+ import "./chunk-IATIXMCE.js";
5
9
  import {
6
10
  deepEqual
7
11
  } from "./chunk-JQFUKJU5.js";
@@ -11,6 +15,7 @@ import {
11
15
  import {
12
16
  clampMin
13
17
  } from "./chunk-HTCYUMDR.js";
18
+ import "./chunk-4REIIZQY.js";
14
19
  import {
15
20
  arrayWithPrevAndIndex,
16
21
  filterAndMap
@@ -18,7 +23,9 @@ import {
18
23
  import {
19
24
  isObject
20
25
  } from "./chunk-C2SVCIWE.js";
21
- import "./chunk-JF2MDHOJ.js";
26
+ import {
27
+ isPlainObject
28
+ } from "./chunk-JF2MDHOJ.js";
22
29
 
23
30
  // src/testUtils.ts
24
31
  function createLoggerStore({
@@ -246,7 +253,209 @@ function waitController() {
246
253
  }
247
254
  };
248
255
  }
256
+ function matchesKeyPattern(fullPath, key, pattern, currentPath) {
257
+ if (fullPath === pattern) {
258
+ return true;
259
+ }
260
+ if (pattern.startsWith("*.")) {
261
+ const propName = pattern.slice(2);
262
+ return currentPath !== "" && key === propName;
263
+ }
264
+ if (pattern.startsWith("*") && !pattern.startsWith("*.")) {
265
+ const propName = pattern.slice(1);
266
+ return key === propName;
267
+ }
268
+ if (!pattern.includes("*")) {
269
+ return currentPath === "" && key === pattern;
270
+ }
271
+ return false;
272
+ }
273
+ function isParentOfPattern(path, pattern) {
274
+ if (pattern.includes("*")) {
275
+ const patternParts = pattern.split(".");
276
+ const pathParts = path.split(".");
277
+ if (pathParts.length >= patternParts.length) {
278
+ return false;
279
+ }
280
+ for (let i = 0; i < pathParts.length; i++) {
281
+ const pathPart = pathParts[i];
282
+ const patternPart = patternParts[i];
283
+ if (patternPart === "*") {
284
+ continue;
285
+ }
286
+ if (pathPart !== patternPart) {
287
+ return false;
288
+ }
289
+ }
290
+ return true;
291
+ } else {
292
+ return pattern.startsWith(`${path}.`);
293
+ }
294
+ }
295
+ function applyKeyFiltering(value, { rejectKeys, filterKeys }, currentPath = "", visited = /* @__PURE__ */ new Set()) {
296
+ if (!isPlainObject(value) && !Array.isArray(value)) {
297
+ return value;
298
+ }
299
+ if (Array.isArray(value)) {
300
+ if (visited.has(value)) {
301
+ throw new Error(
302
+ "Circular reference detected in array during key filtering"
303
+ );
304
+ }
305
+ visited.add(value);
306
+ try {
307
+ return value.map(
308
+ (item, index) => applyKeyFiltering(
309
+ item,
310
+ { rejectKeys, filterKeys },
311
+ currentPath ? `${currentPath}[${index}]` : `[${index}]`,
312
+ visited
313
+ )
314
+ );
315
+ } finally {
316
+ visited.delete(value);
317
+ }
318
+ }
319
+ if (isPlainObject(value)) {
320
+ if (visited.has(value)) {
321
+ throw new Error(
322
+ "Circular reference detected in object during key filtering"
323
+ );
324
+ }
325
+ visited.add(value);
326
+ try {
327
+ const result = {};
328
+ for (const [key, itemValue] of Object.entries(value)) {
329
+ const fullPath = currentPath ? `${currentPath}.${key}` : key;
330
+ if (rejectKeys?.some(
331
+ (rejectPath) => matchesKeyPattern(fullPath, key, rejectPath, currentPath)
332
+ )) {
333
+ continue;
334
+ }
335
+ if (filterKeys) {
336
+ const exactMatch = filterKeys.some(
337
+ (filterPath) => matchesKeyPattern(fullPath, key, filterPath, currentPath)
338
+ );
339
+ const isParent = filterKeys.some(
340
+ (filterPath) => isParentOfPattern(fullPath, filterPath)
341
+ );
342
+ if (!exactMatch && !isParent) {
343
+ continue;
344
+ }
345
+ if (exactMatch) {
346
+ result[key] = applyKeyFiltering(
347
+ itemValue,
348
+ { rejectKeys },
349
+ fullPath,
350
+ visited
351
+ );
352
+ } else {
353
+ result[key] = applyKeyFiltering(
354
+ itemValue,
355
+ { rejectKeys, filterKeys },
356
+ fullPath,
357
+ visited
358
+ );
359
+ }
360
+ } else {
361
+ result[key] = applyKeyFiltering(
362
+ itemValue,
363
+ { rejectKeys, filterKeys },
364
+ fullPath,
365
+ visited
366
+ );
367
+ }
368
+ }
369
+ return result;
370
+ } finally {
371
+ visited.delete(value);
372
+ }
373
+ }
374
+ return value;
375
+ }
376
+ function compactSnapshot(value, {
377
+ collapseObjects = true,
378
+ maxLineLength = 100,
379
+ showUndefined = false,
380
+ showBooleansAs = true,
381
+ rejectKeys,
382
+ filterKeys,
383
+ ...options
384
+ } = {}) {
385
+ let processedValue = value;
386
+ if (rejectKeys || filterKeys) {
387
+ processedValue = applyKeyFiltering(processedValue, {
388
+ rejectKeys: Array.isArray(rejectKeys) ? rejectKeys : rejectKeys ? [rejectKeys] : void 0,
389
+ filterKeys: Array.isArray(filterKeys) ? filterKeys : filterKeys ? [filterKeys] : void 0
390
+ });
391
+ }
392
+ processedValue = showBooleansAs ? replaceBooleansWithEmoji(processedValue, showBooleansAs) : processedValue;
393
+ return `
394
+ ${yamlStringify(processedValue, {
395
+ collapseObjects,
396
+ maxLineLength,
397
+ showUndefined,
398
+ ...options
399
+ })}`;
400
+ }
401
+ function replaceBooleansWithEmoji(value, showBooleansAs, visited = /* @__PURE__ */ new Set()) {
402
+ if (showBooleansAs === false) {
403
+ return value;
404
+ }
405
+ const defaultTrueText = "\u2705";
406
+ const defaultFalseText = "\u274C";
407
+ const config = typeof showBooleansAs === "boolean" ? { trueText: defaultTrueText, falseText: defaultFalseText } : {
408
+ trueText: showBooleansAs.trueText ?? defaultTrueText,
409
+ falseText: showBooleansAs.falseText ?? defaultFalseText,
410
+ props: showBooleansAs.props ?? {},
411
+ ignoreProps: showBooleansAs.ignoreProps ?? []
412
+ };
413
+ function processValue(val, propName) {
414
+ if (typeof val === "boolean") {
415
+ if (propName && config.ignoreProps?.includes(propName)) {
416
+ return val;
417
+ }
418
+ if (propName && config.props?.[propName]) {
419
+ const propConfig = config.props[propName];
420
+ if (propConfig === true) {
421
+ return val ? config.trueText : config.falseText;
422
+ }
423
+ return val ? propConfig.trueText ?? config.trueText : propConfig.falseText ?? config.falseText;
424
+ }
425
+ return val ? config.trueText : config.falseText;
426
+ }
427
+ if (Array.isArray(val)) {
428
+ if (visited.has(val)) {
429
+ throw new Error("Circular reference detected in array");
430
+ }
431
+ visited.add(val);
432
+ try {
433
+ return val.map((item) => processValue(item));
434
+ } finally {
435
+ visited.delete(val);
436
+ }
437
+ }
438
+ if (isPlainObject(val)) {
439
+ if (visited.has(val)) {
440
+ throw new Error("Circular reference detected in object");
441
+ }
442
+ visited.add(val);
443
+ try {
444
+ const result = {};
445
+ for (const [key, itemValue] of Object.entries(val)) {
446
+ result[key] = processValue(itemValue, key);
447
+ }
448
+ return result;
449
+ } finally {
450
+ visited.delete(val);
451
+ }
452
+ }
453
+ return val;
454
+ }
455
+ return processValue(value);
456
+ }
249
457
  export {
458
+ compactSnapshot,
250
459
  createLoggerStore,
251
460
  getResultFn,
252
461
  waitController
@@ -24,26 +24,6 @@ __export(yamlStringify_exports, {
24
24
  });
25
25
  module.exports = __toCommonJS(yamlStringify_exports);
26
26
 
27
- // src/typeGuards.ts
28
- function isObject(value) {
29
- return typeof value === "object" && value !== null && !Array.isArray(value);
30
- }
31
- function isPlainObject(value) {
32
- if (!value || typeof value !== "object") return false;
33
- const proto = Object.getPrototypeOf(value);
34
- if (proto === null) {
35
- return true;
36
- }
37
- const Ctor = Object.hasOwnProperty.call(proto, "constructor") && proto.constructor;
38
- if (Ctor === Object) return true;
39
- const objectCtorString = Object.prototype.constructor.toString();
40
- return typeof Ctor == "function" && Function.toString.call(Ctor) === objectCtorString;
41
- }
42
-
43
- // src/assertions.ts
44
- var isObject2 = isObject;
45
- var isPlainObject2 = isPlainObject;
46
-
47
27
  // src/conversions.ts
48
28
  function bytesToHumanReadable(bytes) {
49
29
  if (bytes < 1024) {
@@ -67,26 +47,80 @@ function truncateString(str, length, ellipsis = "\u2026") {
67
47
  return str.slice(0, length - 1) + ellipsis;
68
48
  }
69
49
 
50
+ // src/typeGuards.ts
51
+ function isObject(value) {
52
+ return typeof value === "object" && value !== null && !Array.isArray(value);
53
+ }
54
+ function isPlainObject(value) {
55
+ if (!value || typeof value !== "object") return false;
56
+ const proto = Object.getPrototypeOf(value);
57
+ if (proto === null) {
58
+ return true;
59
+ }
60
+ const Ctor = Object.hasOwnProperty.call(proto, "constructor") && proto.constructor;
61
+ if (Ctor === Object) return true;
62
+ const objectCtorString = Object.prototype.constructor.toString();
63
+ return typeof Ctor == "function" && Function.toString.call(Ctor) === objectCtorString;
64
+ }
65
+
70
66
  // src/yamlStringify.ts
71
67
  function yamlStringify(obj, {
72
68
  maxLineLength = 100,
73
69
  showUndefined,
74
70
  maxDepth = 50,
71
+ collapseObjects = false,
75
72
  addRootObjSpaces = "beforeAndAfter"
76
73
  } = {}) {
77
- if (isObject2(obj) || Array.isArray(obj) || typeof obj === "function") {
78
- return `${stringifyValue(obj, "", maxLineLength, !!showUndefined, maxDepth, 0, addRootObjSpaces)}
74
+ if (isObject(obj) || Array.isArray(obj) || typeof obj === "function") {
75
+ return `${stringifyValue(obj, "", maxLineLength, !!showUndefined, maxDepth, 0, collapseObjects, addRootObjSpaces)}
79
76
  `;
80
77
  }
81
78
  return JSON.stringify(obj) || "undefined";
82
79
  }
83
- function stringifyValue(value, indent, maxLineLength, showUndefined, maxDepth, depth, addObjSpaces) {
80
+ function stringifyValue(value, indent, maxLineLength, showUndefined, maxDepth, depth, collapseObjects, addObjSpaces) {
84
81
  let result = "";
85
82
  const childIndent = `${indent} `;
86
- if (isPlainObject2(value)) {
83
+ if (isPlainObject(value)) {
87
84
  if (Object.keys(value).length === 0) {
88
85
  return "{}";
89
86
  }
87
+ if (collapseObjects && depth > 0) {
88
+ const entries = Object.entries(value).filter(
89
+ ([, val]) => val !== void 0 || showUndefined
90
+ );
91
+ const isSimpleObject = entries.every(
92
+ ([, val]) => {
93
+ if (typeof val === "string") {
94
+ return !val.includes("'") && !val.includes('"') && !val.includes("\\");
95
+ }
96
+ return typeof val === "number" || typeof val === "boolean" || val === null || val === void 0;
97
+ }
98
+ );
99
+ if (isSimpleObject && entries.length > 0) {
100
+ let line = "{ ";
101
+ line += entries.map(([key, val]) => {
102
+ let valueStr;
103
+ if (typeof val === "string") {
104
+ if (val.includes("'") && !val.includes('"')) {
105
+ valueStr = `"${val}"`;
106
+ } else if (val.includes('"') && !val.includes("'")) {
107
+ valueStr = `'${val}'`;
108
+ } else if (val.includes("'") && val.includes('"')) {
109
+ valueStr = `"${val.replace(/"/g, '\\"')}"`;
110
+ } else {
111
+ valueStr = `'${val}'`;
112
+ }
113
+ } else {
114
+ valueStr = String(val);
115
+ }
116
+ return `${key}: ${valueStr}`;
117
+ }).join(", ");
118
+ line += " }";
119
+ if (line.length <= maxLineLength) {
120
+ return line;
121
+ }
122
+ }
123
+ }
90
124
  let prevValue;
91
125
  let afterSpaceWasAdded = false;
92
126
  for (let [key, objVal] of Object.entries(value)) {
@@ -108,9 +142,22 @@ function stringifyValue(value, indent, maxLineLength, showUndefined, maxDepth, d
108
142
  showUndefined,
109
143
  maxDepth,
110
144
  depth + 1,
145
+ collapseObjects,
111
146
  addObjSpaces
112
147
  );
113
- if (!afterSpaceWasAdded && indent === "" && isObject2(objVal) && prevValue && (addObjSpaces === "before" || addObjSpaces === "beforeAndAfter")) {
148
+ const willBeCollapsed = isObject(objVal) && (Object.keys(objVal).length === 0 || collapseObjects && depth + 1 > 0 && Object.entries(objVal).filter(([, val]) => val !== void 0 || showUndefined).every(([, val]) => {
149
+ if (typeof val === "string") {
150
+ return !val.includes("'") && !val.includes('"') && !val.includes("\\");
151
+ }
152
+ return typeof val === "number" || typeof val === "boolean" || val === null || val === void 0;
153
+ }));
154
+ const prevWasCollapsed = prevValue && isObject(prevValue) && (Object.keys(prevValue).length === 0 || collapseObjects && depth + 1 > 0 && Object.entries(prevValue).filter(([, val]) => val !== void 0 || showUndefined).every(([, val]) => {
155
+ if (typeof val === "string") {
156
+ return !val.includes("'") && !val.includes('"') && !val.includes("\\");
157
+ }
158
+ return typeof val === "number" || typeof val === "boolean" || val === null || val === void 0;
159
+ }));
160
+ if (!afterSpaceWasAdded && indent === "" && isObject(objVal) && !willBeCollapsed && prevValue && !prevWasCollapsed && (addObjSpaces === "before" || addObjSpaces === "beforeAndAfter")) {
114
161
  result += "\n";
115
162
  }
116
163
  if (Array.isArray(objVal)) {
@@ -121,8 +168,9 @@ function stringifyValue(value, indent, maxLineLength, showUndefined, maxDepth, d
121
168
  result += `${indent}${key}:
122
169
  `;
123
170
  }
124
- } else if (isObject2(objVal)) {
125
- if (Object.keys(objVal).length === 0) {
171
+ } else if (isObject(objVal)) {
172
+ const isCollapsedObject = valueString.startsWith("{") && !valueString.includes("\n");
173
+ if (Object.keys(objVal).length === 0 || isCollapsedObject) {
126
174
  result += `${indent}${key}: `;
127
175
  } else {
128
176
  result += `${indent}${key}:
@@ -134,7 +182,8 @@ function stringifyValue(value, indent, maxLineLength, showUndefined, maxDepth, d
134
182
  result += valueString;
135
183
  result += "\n";
136
184
  if (indent === "") {
137
- if (isObject2(objVal)) {
185
+ const isCollapsedObject = valueString.startsWith("{") && !valueString.includes("\n") && valueString.length > 2;
186
+ if (isObject(objVal) && !isCollapsedObject) {
138
187
  if (addObjSpaces === "after" || addObjSpaces === "beforeAndAfter") {
139
188
  result += "\n";
140
189
  afterSpaceWasAdded = true;
@@ -169,6 +218,7 @@ function stringifyValue(value, indent, maxLineLength, showUndefined, maxDepth, d
169
218
  showUndefined,
170
219
  maxDepth,
171
220
  depth + 1,
221
+ collapseObjects,
172
222
  addObjSpaces
173
223
  );
174
224
  }).join(", ");
@@ -184,7 +234,7 @@ function stringifyValue(value, indent, maxLineLength, showUndefined, maxDepth, d
184
234
  item = `{max depth reached}`;
185
235
  }
186
236
  result += `${indent}- `;
187
- if (Array.isArray(item) || isObject2(item)) {
237
+ if (Array.isArray(item) || isObject(item)) {
188
238
  let arrayString = stringifyValue(
189
239
  item,
190
240
  childIndent,
@@ -192,6 +242,7 @@ function stringifyValue(value, indent, maxLineLength, showUndefined, maxDepth, d
192
242
  showUndefined,
193
243
  maxDepth,
194
244
  depth + 1,
245
+ collapseObjects,
195
246
  addObjSpaces
196
247
  );
197
248
  arrayString = arrayString.trimStart();
@@ -204,6 +255,7 @@ function stringifyValue(value, indent, maxLineLength, showUndefined, maxDepth, d
204
255
  showUndefined,
205
256
  maxDepth,
206
257
  depth + 1,
258
+ collapseObjects,
207
259
  addObjSpaces
208
260
  );
209
261
  }
@@ -257,13 +309,14 @@ ${indent}${line}
257
309
  showUndefined,
258
310
  maxDepth,
259
311
  depth + 1,
312
+ collapseObjects,
260
313
  addObjSpaces
261
314
  );
262
315
  }
263
316
  return JSON.stringify(value);
264
317
  }
265
318
  function normalizeValue(value) {
266
- if (value === null || isPlainObject2(value) || Array.isArray(value)) {
319
+ if (value === null || isPlainObject(value) || Array.isArray(value)) {
267
320
  return null;
268
321
  }
269
322
  if (value instanceof Map) {
@@ -1,8 +1,10 @@
1
- declare function yamlStringify(obj: unknown, { maxLineLength, showUndefined, maxDepth, addRootObjSpaces, }?: {
1
+ type YamlStringifyOptions = {
2
2
  maxLineLength?: number;
3
3
  showUndefined?: boolean;
4
4
  maxDepth?: number;
5
+ collapseObjects?: boolean;
5
6
  addRootObjSpaces?: 'before' | 'after' | 'beforeAndAfter' | false;
6
- }): string;
7
+ };
8
+ declare function yamlStringify(obj: unknown, { maxLineLength, showUndefined, maxDepth, collapseObjects, addRootObjSpaces, }?: YamlStringifyOptions): string;
7
9
 
8
- export { yamlStringify };
10
+ export { type YamlStringifyOptions, yamlStringify };
@@ -1,8 +1,10 @@
1
- declare function yamlStringify(obj: unknown, { maxLineLength, showUndefined, maxDepth, addRootObjSpaces, }?: {
1
+ type YamlStringifyOptions = {
2
2
  maxLineLength?: number;
3
3
  showUndefined?: boolean;
4
4
  maxDepth?: number;
5
+ collapseObjects?: boolean;
5
6
  addRootObjSpaces?: 'before' | 'after' | 'beforeAndAfter' | false;
6
- }): string;
7
+ };
8
+ declare function yamlStringify(obj: unknown, { maxLineLength, showUndefined, maxDepth, collapseObjects, addRootObjSpaces, }?: YamlStringifyOptions): string;
7
9
 
8
- export { yamlStringify };
10
+ export { type YamlStringifyOptions, yamlStringify };