@sohanemon/utils 5.2.5 → 5.2.6
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.
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Deeply cleans any value by converting all `null` values to `undefined`,
|
|
3
|
+
* and merges in a fallback object for default values.
|
|
4
|
+
*
|
|
5
|
+
* Features:
|
|
6
|
+
* - Circular reference detection to prevent infinite loops
|
|
7
|
+
* - Proper handling of special objects (Date, Map, Set, RegExp, etc.)
|
|
8
|
+
* - Strict type safety with improved generics
|
|
9
|
+
* - Better error messages and validation
|
|
10
|
+
* - Support for nested structures with Symbol properties
|
|
11
|
+
* - Optional maximum recursion depth to prevent stack overflow
|
|
12
|
+
* - Configurable null-to-undefined conversion
|
|
13
|
+
*
|
|
14
|
+
* @param data - Any input data (object, array, primitive)
|
|
15
|
+
* @param fallback - Optional fallback values to merge with
|
|
16
|
+
* @param options - Configuration options
|
|
17
|
+
* @param options.maxDepth - Maximum recursion depth (default: 100)
|
|
18
|
+
* @param options.throwOnCircular - Whether to throw on circular refs (default: false)
|
|
19
|
+
* @param options.convertNullToUndefined - Convert null to undefined (default: true)
|
|
20
|
+
* @returns Same type as input, but with all nulls replaced by undefined
|
|
21
|
+
*
|
|
22
|
+
* @throws {TypeError} If data or fallback are invalid types when strict validation is enabled
|
|
23
|
+
* @throws {RangeError} If circular reference detected and throwOnCircular is true
|
|
24
|
+
*
|
|
25
|
+
* @example
|
|
26
|
+
* // Basic usage
|
|
27
|
+
* hydrate({ a: null, b: 'test' }) // { a: undefined, b: 'test' }
|
|
28
|
+
*
|
|
29
|
+
* @example
|
|
30
|
+
* // With fallback values
|
|
31
|
+
* hydrate({ a: null }, { a: 'default' }) // { a: 'default' }
|
|
32
|
+
*
|
|
33
|
+
* @example
|
|
34
|
+
* // Keep nulls as-is
|
|
35
|
+
* hydrate({ a: null }, undefined, { convertNullToUndefined: false }) // { a: null }
|
|
36
|
+
*/
|
|
37
|
+
export declare function hydrate<T>(data: T, fallback?: Partial<T>, options?: {
|
|
38
|
+
maxDepth?: number;
|
|
39
|
+
throwOnCircular?: boolean;
|
|
40
|
+
convertNullToUndefined?: boolean;
|
|
41
|
+
}): T;
|
|
42
|
+
/**
|
|
43
|
+
* Type guard utility for checking if a value is an object with a specific shape
|
|
44
|
+
*/
|
|
45
|
+
export declare function isWithFallbackCompatible<T extends object>(value: unknown): value is T;
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Deeply cleans any value by converting all `null` values to `undefined`,
|
|
3
|
+
* and merges in a fallback object for default values.
|
|
4
|
+
*
|
|
5
|
+
* Features:
|
|
6
|
+
* - Circular reference detection to prevent infinite loops
|
|
7
|
+
* - Proper handling of special objects (Date, Map, Set, RegExp, etc.)
|
|
8
|
+
* - Strict type safety with improved generics
|
|
9
|
+
* - Better error messages and validation
|
|
10
|
+
* - Support for nested structures with Symbol properties
|
|
11
|
+
* - Optional maximum recursion depth to prevent stack overflow
|
|
12
|
+
* - Configurable null-to-undefined conversion
|
|
13
|
+
*
|
|
14
|
+
* @param data - Any input data (object, array, primitive)
|
|
15
|
+
* @param fallback - Optional fallback values to merge with
|
|
16
|
+
* @param options - Configuration options
|
|
17
|
+
* @param options.maxDepth - Maximum recursion depth (default: 100)
|
|
18
|
+
* @param options.throwOnCircular - Whether to throw on circular refs (default: false)
|
|
19
|
+
* @param options.convertNullToUndefined - Convert null to undefined (default: true)
|
|
20
|
+
* @returns Same type as input, but with all nulls replaced by undefined
|
|
21
|
+
*
|
|
22
|
+
* @throws {TypeError} If data or fallback are invalid types when strict validation is enabled
|
|
23
|
+
* @throws {RangeError} If circular reference detected and throwOnCircular is true
|
|
24
|
+
*
|
|
25
|
+
* @example
|
|
26
|
+
* // Basic usage
|
|
27
|
+
* hydrate({ a: null, b: 'test' }) // { a: undefined, b: 'test' }
|
|
28
|
+
*
|
|
29
|
+
* @example
|
|
30
|
+
* // With fallback values
|
|
31
|
+
* hydrate({ a: null }, { a: 'default' }) // { a: 'default' }
|
|
32
|
+
*
|
|
33
|
+
* @example
|
|
34
|
+
* // Keep nulls as-is
|
|
35
|
+
* hydrate({ a: null }, undefined, { convertNullToUndefined: false }) // { a: null }
|
|
36
|
+
*/
|
|
37
|
+
export function hydrate(data, fallback, options) {
|
|
38
|
+
const maxDepth = options?.maxDepth ?? 100;
|
|
39
|
+
const throwOnCircular = options?.throwOnCircular ?? false;
|
|
40
|
+
const convertNullToUndefined = options?.convertNullToUndefined ?? true;
|
|
41
|
+
// Use WeakSet for O(1) circular reference detection
|
|
42
|
+
const visited = new WeakSet();
|
|
43
|
+
return processValue(data, fallback, 0, visited);
|
|
44
|
+
function processValue(value, fallbackValue, depth, visited) {
|
|
45
|
+
// Check recursion depth
|
|
46
|
+
if (depth > maxDepth) {
|
|
47
|
+
console.warn(`[hydrate] Maximum recursion depth (${maxDepth}) exceeded. Returning value as-is.`);
|
|
48
|
+
return value ?? fallbackValue;
|
|
49
|
+
}
|
|
50
|
+
if (value === null) {
|
|
51
|
+
return convertNullToUndefined ? undefined : (fallbackValue ?? null);
|
|
52
|
+
}
|
|
53
|
+
// Handle undefined - use fallback or return undefined
|
|
54
|
+
if (value === undefined) {
|
|
55
|
+
return fallbackValue ?? undefined;
|
|
56
|
+
}
|
|
57
|
+
// Handle primitives: string, number, boolean, symbol, bigint
|
|
58
|
+
const type = typeof value;
|
|
59
|
+
if (type !== 'object') {
|
|
60
|
+
return value;
|
|
61
|
+
}
|
|
62
|
+
if (visited.has(value)) {
|
|
63
|
+
if (throwOnCircular) {
|
|
64
|
+
throw new RangeError('[hydrate] Circular reference detected');
|
|
65
|
+
}
|
|
66
|
+
// Return the value as-is to break the cycle
|
|
67
|
+
return value;
|
|
68
|
+
}
|
|
69
|
+
// Mark as visited to detect circular references
|
|
70
|
+
visited.add(value);
|
|
71
|
+
if (isSpecialObject(value)) {
|
|
72
|
+
return handleSpecialObject(value, fallbackValue);
|
|
73
|
+
}
|
|
74
|
+
// Handle arrays
|
|
75
|
+
if (Array.isArray(value)) {
|
|
76
|
+
const fallbackArray = Array.isArray(fallbackValue)
|
|
77
|
+
? fallbackValue
|
|
78
|
+
: undefined;
|
|
79
|
+
return value.map((item, index) => processValue(item, fallbackArray?.[index], depth + 1, visited));
|
|
80
|
+
}
|
|
81
|
+
// Handle plain objects
|
|
82
|
+
if (isPlainObject(value)) {
|
|
83
|
+
const fallbackObj = isPlainObject(fallbackValue)
|
|
84
|
+
? fallbackValue
|
|
85
|
+
: {};
|
|
86
|
+
const result = { ...fallbackObj };
|
|
87
|
+
// Process all enumerable properties including symbols
|
|
88
|
+
const keys = [
|
|
89
|
+
...Object.keys(value),
|
|
90
|
+
...Object.getOwnPropertySymbols(value),
|
|
91
|
+
];
|
|
92
|
+
for (const k of keys) {
|
|
93
|
+
const propValue = value[k];
|
|
94
|
+
const fallbackProp = fallbackObj[k];
|
|
95
|
+
result[k] = processValue(propValue, fallbackProp, depth + 1, visited);
|
|
96
|
+
}
|
|
97
|
+
return result;
|
|
98
|
+
}
|
|
99
|
+
// For other objects, return as-is (instances, etc.)
|
|
100
|
+
return value ?? fallbackValue;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Checks if a value is a plain object (not a special type)
|
|
105
|
+
*/
|
|
106
|
+
function isPlainObject(value) {
|
|
107
|
+
if (typeof value !== 'object' || value === null) {
|
|
108
|
+
return false;
|
|
109
|
+
}
|
|
110
|
+
if (Object.prototype.toString.call(value) !== '[object Object]') {
|
|
111
|
+
return false;
|
|
112
|
+
}
|
|
113
|
+
// Objects with null prototype are still plain objects
|
|
114
|
+
const proto = Object.getPrototypeOf(value);
|
|
115
|
+
return proto === null || proto === Object.prototype;
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Checks if a value is a special object type that needs custom handling
|
|
119
|
+
*/
|
|
120
|
+
function isSpecialObject(value) {
|
|
121
|
+
if (typeof value !== 'object' || value === null) {
|
|
122
|
+
return false;
|
|
123
|
+
}
|
|
124
|
+
const stringTag = Object.prototype.toString.call(value);
|
|
125
|
+
const specialTypes = [
|
|
126
|
+
'[object Date]',
|
|
127
|
+
'[object RegExp]',
|
|
128
|
+
'[object Map]',
|
|
129
|
+
'[object Set]',
|
|
130
|
+
'[object WeakMap]',
|
|
131
|
+
'[object WeakSet]',
|
|
132
|
+
'[object Promise]',
|
|
133
|
+
'[object Error]',
|
|
134
|
+
'[object ArrayBuffer]',
|
|
135
|
+
];
|
|
136
|
+
return specialTypes.includes(stringTag);
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Handles special object types that shouldn't be deeply cloned
|
|
140
|
+
*/
|
|
141
|
+
function handleSpecialObject(value, fallbackValue) {
|
|
142
|
+
// For special types, return the original value or fallback
|
|
143
|
+
// These shouldn't be deeply cloned as they have internal state
|
|
144
|
+
return value ?? fallbackValue;
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* Type guard utility for checking if a value is an object with a specific shape
|
|
148
|
+
*/
|
|
149
|
+
export function isWithFallbackCompatible(value) {
|
|
150
|
+
return (typeof value === 'object' &&
|
|
151
|
+
(value === null ||
|
|
152
|
+
Array.isArray(value) ||
|
|
153
|
+
(typeof value === 'object' &&
|
|
154
|
+
Object.prototype.toString.call(value) === '[object Object]')));
|
|
155
|
+
}
|
package/dist/functions/index.js
CHANGED