xypriss 9.12.11 → 9.12.13
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/dist/cjs/src/plugins/builtin/xems/XemsBuiltinPlugin.js +9 -3
- package/dist/cjs/src/plugins/builtin/xems/XemsBuiltinPlugin.js.map +1 -1
- package/dist/cjs/src/server/FastServer/index.js +4 -6
- package/dist/cjs/src/server/FastServer/index.js.map +1 -1
- package/dist/cjs/src/xhsc/UtilsApi.js +49 -2
- package/dist/cjs/src/xhsc/UtilsApi.js.map +1 -1
- package/dist/cjs/src/xhsc/utils/ObjectUtils.js +832 -0
- package/dist/cjs/src/xhsc/utils/ObjectUtils.js.map +1 -1
- package/dist/esm/src/plugins/builtin/xems/XemsBuiltinPlugin.js +9 -3
- package/dist/esm/src/plugins/builtin/xems/XemsBuiltinPlugin.js.map +1 -1
- package/dist/esm/src/server/FastServer/index.js +4 -6
- package/dist/esm/src/server/FastServer/index.js.map +1 -1
- package/dist/esm/src/xhsc/UtilsApi.js +49 -2
- package/dist/esm/src/xhsc/UtilsApi.js.map +1 -1
- package/dist/esm/src/xhsc/utils/ObjectUtils.js +832 -1
- package/dist/esm/src/xhsc/utils/ObjectUtils.js.map +1 -1
- package/dist/index.d.ts +619 -2
- package/package.json +72 -71
- package/dist/cjs/src/middleware/XemsSessionMiddleware.js +0 -115
- package/dist/cjs/src/middleware/XemsSessionMiddleware.js.map +0 -1
- package/dist/esm/src/middleware/XemsSessionMiddleware.js +0 -113
- package/dist/esm/src/middleware/XemsSessionMiddleware.js.map +0 -1
|
@@ -1,9 +1,455 @@
|
|
|
1
1
|
import { XStringify } from 'xypriss-security';
|
|
2
2
|
|
|
3
|
+
class ObjectWrapper {
|
|
4
|
+
constructor(obj) {
|
|
5
|
+
this.current = obj;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* **value**
|
|
9
|
+
*
|
|
10
|
+
* Unwraps and returns the underlying plain object held by this wrapper.
|
|
11
|
+
* Call this at the end of a chain to get back a normal object.
|
|
12
|
+
*
|
|
13
|
+
* @returns The current wrapped object.
|
|
14
|
+
*
|
|
15
|
+
* @example
|
|
16
|
+
* ```ts
|
|
17
|
+
* const obj = __sys__.utils.obj.of({ a: 1 });
|
|
18
|
+
* obj.value(); // { a: 1 }
|
|
19
|
+
* ```
|
|
20
|
+
*/
|
|
21
|
+
value() {
|
|
22
|
+
return this.current;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* **raw**
|
|
26
|
+
*
|
|
27
|
+
* Alias for {@link value}. Useful when `value` reads awkwardly in context.
|
|
28
|
+
*
|
|
29
|
+
* @returns The current wrapped object.
|
|
30
|
+
*/
|
|
31
|
+
raw() {
|
|
32
|
+
return this.current;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* **clone**
|
|
36
|
+
*
|
|
37
|
+
* Deep-clones the wrapped object (via `XStringify`) and continues
|
|
38
|
+
* the chain on the cloned copy, leaving the original untouched.
|
|
39
|
+
*
|
|
40
|
+
* @returns `this`, now wrapping a deep copy of the previous value.
|
|
41
|
+
*
|
|
42
|
+
* @example
|
|
43
|
+
* ```ts
|
|
44
|
+
* const source = { nested: { count: 1 } };
|
|
45
|
+
* const obj = __sys__.utils.obj.of(source).clone();
|
|
46
|
+
* obj.value().nested.count = 99;
|
|
47
|
+
* // source.nested.count is still 1
|
|
48
|
+
* ```
|
|
49
|
+
*/
|
|
50
|
+
clone() {
|
|
51
|
+
this.current = JSON.parse(XStringify(this.current));
|
|
52
|
+
return this;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* **pick**
|
|
56
|
+
*
|
|
57
|
+
* Narrows the wrapped object down to only the given keys.
|
|
58
|
+
*
|
|
59
|
+
* @param keys - The keys to keep.
|
|
60
|
+
* @returns `this`, now wrapping only the picked keys.
|
|
61
|
+
*
|
|
62
|
+
* @example
|
|
63
|
+
* ```ts
|
|
64
|
+
* __sys__.utils.obj.of({ a: 1, b: 2, c: 3 }).pick(["a", "c"]).value();
|
|
65
|
+
* // { a: 1, c: 3 }
|
|
66
|
+
* ```
|
|
67
|
+
*/
|
|
68
|
+
pick(keys) {
|
|
69
|
+
const result = keys.reduce((acc, key) => {
|
|
70
|
+
if (key in this.current)
|
|
71
|
+
acc[key] = this.current[key];
|
|
72
|
+
return acc;
|
|
73
|
+
}, {});
|
|
74
|
+
return new ObjectWrapper(result);
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* **omit**
|
|
78
|
+
*
|
|
79
|
+
* Removes the given keys from the wrapped object.
|
|
80
|
+
*
|
|
81
|
+
* @param keys - The keys to remove.
|
|
82
|
+
* @returns `this`, now wrapping the object without those keys.
|
|
83
|
+
*
|
|
84
|
+
* @example
|
|
85
|
+
* ```ts
|
|
86
|
+
* __sys__.utils.obj.of({ a: 1, b: 2, c: 3 }).omit(["b"]).value();
|
|
87
|
+
* // { a: 1, c: 3 }
|
|
88
|
+
* ```
|
|
89
|
+
*/
|
|
90
|
+
omit(keys) {
|
|
91
|
+
const result = { ...this.current };
|
|
92
|
+
keys.forEach((key) => delete result[key]);
|
|
93
|
+
return new ObjectWrapper(result);
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* **isEmpty**
|
|
97
|
+
*
|
|
98
|
+
* Checks whether the wrapped object has no own enumerable keys.
|
|
99
|
+
* This is a terminal read (does not return the wrapper), since it
|
|
100
|
+
* yields a boolean rather than an object.
|
|
101
|
+
*
|
|
102
|
+
* @returns `true` if the object has no own keys.
|
|
103
|
+
*
|
|
104
|
+
* @example
|
|
105
|
+
* ```ts
|
|
106
|
+
* __sys__.utils.obj.of({}).isEmpty(); // true
|
|
107
|
+
* ```
|
|
108
|
+
*/
|
|
109
|
+
isEmpty() {
|
|
110
|
+
return Object.keys(this.current).length === 0;
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* **flatten**
|
|
114
|
+
*
|
|
115
|
+
* Collapses the wrapped object's nested structure into flat
|
|
116
|
+
* dot-notation (or custom separator) keys.
|
|
117
|
+
*
|
|
118
|
+
* @param separator - Path separator (default: `"."`).
|
|
119
|
+
* @returns A new wrapper around the flattened object.
|
|
120
|
+
*
|
|
121
|
+
* @example
|
|
122
|
+
* ```ts
|
|
123
|
+
* __sys__.utils.obj.of({ a: { b: 1 } }).flatten().value();
|
|
124
|
+
* // { "a.b": 1 }
|
|
125
|
+
* ```
|
|
126
|
+
*/
|
|
127
|
+
flatten(separator = ".") {
|
|
128
|
+
const result = {};
|
|
129
|
+
const recurse = (current, path = "") => {
|
|
130
|
+
for (const [key, val] of Object.entries(current)) {
|
|
131
|
+
const newPath = path ? `${path}${separator}${key}` : key;
|
|
132
|
+
if (val &&
|
|
133
|
+
typeof val === "object" &&
|
|
134
|
+
!Array.isArray(val) &&
|
|
135
|
+
Object.keys(val).length > 0) {
|
|
136
|
+
recurse(val, newPath);
|
|
137
|
+
}
|
|
138
|
+
else {
|
|
139
|
+
result[newPath] = val;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
};
|
|
143
|
+
recurse(this.current);
|
|
144
|
+
return new ObjectWrapper(result);
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* **unflatten**
|
|
148
|
+
*
|
|
149
|
+
* Reverses {@link flatten}, expanding dot-notation (or custom
|
|
150
|
+
* separator) keys back into a nested object.
|
|
151
|
+
*
|
|
152
|
+
* @param separator - Path separator used in the flat keys (default: `"."`).
|
|
153
|
+
* @returns A new wrapper around the nested object.
|
|
154
|
+
*
|
|
155
|
+
* @example
|
|
156
|
+
* ```ts
|
|
157
|
+
* __sys__.utils.obj.of({ "a.b": 1, "a.c": 2 }).unflatten().value();
|
|
158
|
+
* // { a: { b: 1, c: 2 } }
|
|
159
|
+
* ```
|
|
160
|
+
*/
|
|
161
|
+
unflatten(separator = ".") {
|
|
162
|
+
const result = {};
|
|
163
|
+
for (const [flatKey, val] of Object.entries(this.current)) {
|
|
164
|
+
const parts = flatKey.split(separator);
|
|
165
|
+
let cursor = result;
|
|
166
|
+
parts.forEach((part, i) => {
|
|
167
|
+
if (i === parts.length - 1) {
|
|
168
|
+
cursor[part] = val;
|
|
169
|
+
}
|
|
170
|
+
else {
|
|
171
|
+
if (typeof cursor[part] !== "object" ||
|
|
172
|
+
cursor[part] === null) {
|
|
173
|
+
cursor[part] = {};
|
|
174
|
+
}
|
|
175
|
+
cursor = cursor[part];
|
|
176
|
+
}
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
return new ObjectWrapper(result);
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* **merge**
|
|
183
|
+
*
|
|
184
|
+
* Deep-merges one or more source objects into the wrapped object.
|
|
185
|
+
* Plain object values are merged recursively; arrays and primitives
|
|
186
|
+
* are overwritten by the last source that defines them.
|
|
187
|
+
*
|
|
188
|
+
* @param sources - One or more partial objects to merge in.
|
|
189
|
+
* @returns `this`, now wrapping the merged result.
|
|
190
|
+
*
|
|
191
|
+
* @example
|
|
192
|
+
* ```ts
|
|
193
|
+
* __sys__.utils.obj.of({ a: 1, nested: { x: 1 } })
|
|
194
|
+
* .merge({ nested: { y: 2 } })
|
|
195
|
+
* .value();
|
|
196
|
+
* // { a: 1, nested: { x: 1, y: 2 } }
|
|
197
|
+
* ```
|
|
198
|
+
*/
|
|
199
|
+
merge(...sources) {
|
|
200
|
+
const isPlainObject = (val) => !!val && typeof val === "object" && !Array.isArray(val);
|
|
201
|
+
const deepMerge = (target, source) => {
|
|
202
|
+
for (const key of Object.keys(source)) {
|
|
203
|
+
if (isPlainObject(source[key]) && isPlainObject(target[key])) {
|
|
204
|
+
deepMerge(target[key], source[key]);
|
|
205
|
+
}
|
|
206
|
+
else {
|
|
207
|
+
target[key] = source[key];
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
return target;
|
|
211
|
+
};
|
|
212
|
+
this.current = sources.reduce((acc, src) => deepMerge(acc, src), this.current);
|
|
213
|
+
return this;
|
|
214
|
+
}
|
|
215
|
+
/**
|
|
216
|
+
* **mapValues**
|
|
217
|
+
*
|
|
218
|
+
* Transforms every value of the wrapped object using `fn`, keeping
|
|
219
|
+
* the same keys.
|
|
220
|
+
*
|
|
221
|
+
* @param fn - Mapping function receiving `(value, key)`.
|
|
222
|
+
* @returns A new wrapper around the transformed object.
|
|
223
|
+
*
|
|
224
|
+
* @example
|
|
225
|
+
* ```ts
|
|
226
|
+
* __sys__.utils.obj.of({ a: 1, b: 2 }).mapValues((v) => v * 10).value();
|
|
227
|
+
* // { a: 10, b: 20 }
|
|
228
|
+
* ```
|
|
229
|
+
*/
|
|
230
|
+
mapValues(fn) {
|
|
231
|
+
const result = {};
|
|
232
|
+
for (const key of Object.keys(this.current)) {
|
|
233
|
+
result[key] = fn(this.current[key], key);
|
|
234
|
+
}
|
|
235
|
+
return new ObjectWrapper(result);
|
|
236
|
+
}
|
|
237
|
+
/**
|
|
238
|
+
* **mapKeys**
|
|
239
|
+
*
|
|
240
|
+
* Transforms every key of the wrapped object using `fn`, keeping
|
|
241
|
+
* the same values.
|
|
242
|
+
*
|
|
243
|
+
* @param fn - Mapping function receiving `(key, value)`. Must return a string.
|
|
244
|
+
* @returns A new wrapper around the object with renamed keys.
|
|
245
|
+
*
|
|
246
|
+
* @example
|
|
247
|
+
* ```ts
|
|
248
|
+
* __sys__.utils.obj.of({ a: 1, b: 2 })
|
|
249
|
+
* .mapKeys((k) => k.toUpperCase())
|
|
250
|
+
* .value();
|
|
251
|
+
* // { A: 1, B: 2 }
|
|
252
|
+
* ```
|
|
253
|
+
*/
|
|
254
|
+
mapKeys(fn) {
|
|
255
|
+
const result = {};
|
|
256
|
+
for (const key of Object.keys(this.current)) {
|
|
257
|
+
result[fn(key, this.current[key])] = this.current[key];
|
|
258
|
+
}
|
|
259
|
+
return new ObjectWrapper(result);
|
|
260
|
+
}
|
|
261
|
+
/**
|
|
262
|
+
* **filter**
|
|
263
|
+
*
|
|
264
|
+
* Keeps only the key-value pairs for which `predicate` returns `true`.
|
|
265
|
+
*
|
|
266
|
+
* @param predicate - Function receiving `(value, key)`.
|
|
267
|
+
* @returns A new wrapper around the filtered object.
|
|
268
|
+
*
|
|
269
|
+
* @example
|
|
270
|
+
* ```ts
|
|
271
|
+
* __sys__.utils.obj.of({ a: 1, b: 2, c: 3 })
|
|
272
|
+
* .filter((v) => v > 1)
|
|
273
|
+
* .value();
|
|
274
|
+
* // { b: 2, c: 3 }
|
|
275
|
+
* ```
|
|
276
|
+
*/
|
|
277
|
+
filter(predicate) {
|
|
278
|
+
const result = {};
|
|
279
|
+
for (const key of Object.keys(this.current)) {
|
|
280
|
+
if (predicate(this.current[key], key)) {
|
|
281
|
+
result[key] = this.current[key];
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
return new ObjectWrapper(result);
|
|
285
|
+
}
|
|
286
|
+
/**
|
|
287
|
+
* **keys**
|
|
288
|
+
*
|
|
289
|
+
* Returns the own enumerable keys of the wrapped object.
|
|
290
|
+
* This is a terminal read.
|
|
291
|
+
*
|
|
292
|
+
* @returns Array of keys.
|
|
293
|
+
*/
|
|
294
|
+
keys() {
|
|
295
|
+
return Object.keys(this.current);
|
|
296
|
+
}
|
|
297
|
+
/**
|
|
298
|
+
* **values**
|
|
299
|
+
*
|
|
300
|
+
* Returns the own enumerable values of the wrapped object.
|
|
301
|
+
* This is a terminal read.
|
|
302
|
+
*
|
|
303
|
+
* @returns Array of values.
|
|
304
|
+
*/
|
|
305
|
+
values() {
|
|
306
|
+
return Object.values(this.current);
|
|
307
|
+
}
|
|
308
|
+
/**
|
|
309
|
+
* **entries**
|
|
310
|
+
*
|
|
311
|
+
* Returns the own enumerable `[key, value]` pairs of the wrapped object.
|
|
312
|
+
* This is a terminal read.
|
|
313
|
+
*
|
|
314
|
+
* @returns Array of `[key, value]` tuples.
|
|
315
|
+
*/
|
|
316
|
+
entries() {
|
|
317
|
+
return Object.entries(this.current);
|
|
318
|
+
}
|
|
319
|
+
/**
|
|
320
|
+
* **has**
|
|
321
|
+
*
|
|
322
|
+
* Checks whether the wrapped object has the given own key.
|
|
323
|
+
* This is a terminal read.
|
|
324
|
+
*
|
|
325
|
+
* @param key - The key to check.
|
|
326
|
+
* @returns `true` if the key exists on the object.
|
|
327
|
+
*/
|
|
328
|
+
has(key) {
|
|
329
|
+
return Object.prototype.hasOwnProperty.call(this.current, key);
|
|
330
|
+
}
|
|
331
|
+
/**
|
|
332
|
+
* **get**
|
|
333
|
+
*
|
|
334
|
+
* Safely reads a possibly-nested value using dot-notation path,
|
|
335
|
+
* returning `fallback` if any part of the path is missing.
|
|
336
|
+
* This is a terminal read.
|
|
337
|
+
*
|
|
338
|
+
* @param path - Dot-notation path (e.g. `"a.b.c"`).
|
|
339
|
+
* @param fallback - Value returned if the path can't be resolved.
|
|
340
|
+
* @returns The resolved value or the fallback.
|
|
341
|
+
*
|
|
342
|
+
* @example
|
|
343
|
+
* ```ts
|
|
344
|
+
* __sys__.utils.obj.of({ a: { b: { c: 42 } } }).get("a.b.c"); // 42
|
|
345
|
+
* __sys__.utils.obj.of({ a: {} }).get("a.b.c", "missing"); // "missing"
|
|
346
|
+
* ```
|
|
347
|
+
*/
|
|
348
|
+
get(path, fallback = undefined) {
|
|
349
|
+
const parts = path.split(".");
|
|
350
|
+
let cursor = this.current;
|
|
351
|
+
for (const part of parts) {
|
|
352
|
+
if (cursor === null || cursor === undefined)
|
|
353
|
+
return fallback;
|
|
354
|
+
cursor = cursor[part];
|
|
355
|
+
}
|
|
356
|
+
return cursor === undefined ? fallback : cursor;
|
|
357
|
+
}
|
|
358
|
+
/**
|
|
359
|
+
* **set**
|
|
360
|
+
*
|
|
361
|
+
* Sets a possibly-nested value using dot-notation path, creating
|
|
362
|
+
* intermediate objects as needed.
|
|
363
|
+
*
|
|
364
|
+
* @param path - Dot-notation path (e.g. `"a.b.c"`).
|
|
365
|
+
* @param value - The value to set.
|
|
366
|
+
* @returns `this`, with the value set at the given path.
|
|
367
|
+
*
|
|
368
|
+
* @example
|
|
369
|
+
* ```ts
|
|
370
|
+
* __sys__.utils.obj.of<any>({}).set("a.b.c", 42).value();
|
|
371
|
+
* // { a: { b: { c: 42 } } }
|
|
372
|
+
* ```
|
|
373
|
+
*/
|
|
374
|
+
set(path, value) {
|
|
375
|
+
const parts = path.split(".");
|
|
376
|
+
let cursor = this.current;
|
|
377
|
+
parts.forEach((part, i) => {
|
|
378
|
+
if (i === parts.length - 1) {
|
|
379
|
+
cursor[part] = value;
|
|
380
|
+
}
|
|
381
|
+
else {
|
|
382
|
+
if (typeof cursor[part] !== "object" || cursor[part] === null) {
|
|
383
|
+
cursor[part] = {};
|
|
384
|
+
}
|
|
385
|
+
cursor = cursor[part];
|
|
386
|
+
}
|
|
387
|
+
});
|
|
388
|
+
return this;
|
|
389
|
+
}
|
|
390
|
+
/**
|
|
391
|
+
* **equals**
|
|
392
|
+
*
|
|
393
|
+
* Performs a deep structural equality check between the wrapped
|
|
394
|
+
* object and `other`. This is a terminal read.
|
|
395
|
+
*
|
|
396
|
+
* @param other - The object to compare against.
|
|
397
|
+
* @returns `true` if both objects are deeply equal.
|
|
398
|
+
*
|
|
399
|
+
* @example
|
|
400
|
+
* ```ts
|
|
401
|
+
* __sys__.utils.obj.of({ a: { b: 1 } }).equals({ a: { b: 1 } }); // true
|
|
402
|
+
* ```
|
|
403
|
+
*/
|
|
404
|
+
equals(other) {
|
|
405
|
+
return __sys__.utils.obj.deepEqual(this.current, other);
|
|
406
|
+
}
|
|
407
|
+
}
|
|
3
408
|
/**
|
|
4
409
|
* **ObjectUtils — XyPriss Object Utilities**
|
|
410
|
+
*
|
|
411
|
+
* All original methods remain fully supported and are **not deprecated**;
|
|
412
|
+
* they are the right choice for one-off, single-call operations. For
|
|
413
|
+
* chaining several operations on the same object without repeating it as
|
|
414
|
+
* an argument each time, use {@link __sys__.utils.obj.of} to get a fluent
|
|
415
|
+
* {@link ObjectWrapper} instance instead:
|
|
416
|
+
*
|
|
417
|
+
* @example
|
|
418
|
+
* ```ts
|
|
419
|
+
* // Classic style (still fully supported):
|
|
420
|
+
* const utils = new ObjectUtils();
|
|
421
|
+
* utils.omit(utils.pick(obj, ["a", "b", "c"]), ["c"]);
|
|
422
|
+
*
|
|
423
|
+
* // Fluent style (new, optional):
|
|
424
|
+
* const result = __sys__.utils.obj.of(obj)
|
|
425
|
+
* .pick(["a", "b", "c"])
|
|
426
|
+
* .omit(["c"])
|
|
427
|
+
* .value();
|
|
428
|
+
* ```
|
|
5
429
|
*/
|
|
6
430
|
class ObjectUtils {
|
|
431
|
+
/**
|
|
432
|
+
* **of**
|
|
433
|
+
*
|
|
434
|
+
* Wraps `obj` in a chainable {@link ObjectWrapper}, so multiple
|
|
435
|
+
* operations can be applied in sequence without re-declaring the
|
|
436
|
+
* object as an argument each time.
|
|
437
|
+
*
|
|
438
|
+
* This does not mutate or replace the classic API — it's an
|
|
439
|
+
* additive convenience entry point.
|
|
440
|
+
*
|
|
441
|
+
* @param obj - The object to wrap.
|
|
442
|
+
* @returns An {@link ObjectWrapper} bound to `obj`.
|
|
443
|
+
*
|
|
444
|
+
* @example
|
|
445
|
+
* ```ts
|
|
446
|
+
* const obj = __sys__.utils.obj.of({ a: 1, b: 2, c: 3 });
|
|
447
|
+
* const picked = obj.pick(["a", "b"]).value(); // { a: 1, b: 2 }
|
|
448
|
+
* ```
|
|
449
|
+
*/
|
|
450
|
+
static of(obj) {
|
|
451
|
+
return new ObjectWrapper(obj);
|
|
452
|
+
}
|
|
7
453
|
/**
|
|
8
454
|
* **Deep Clone an Object**
|
|
9
455
|
*
|
|
@@ -95,6 +541,12 @@ class ObjectUtils {
|
|
|
95
541
|
* @param obj - The object to flatten.
|
|
96
542
|
* @param separator - Optional path separator (default: `"."`).
|
|
97
543
|
* @returns A shallow object with path-based keys.
|
|
544
|
+
*
|
|
545
|
+
* @example
|
|
546
|
+
* ```ts
|
|
547
|
+
* utils.flattenObject({ a: { b: 1, c: { d: 2 } } });
|
|
548
|
+
* // { "a.b": 1, "a.c.d": 2 }
|
|
549
|
+
* ```
|
|
98
550
|
*/
|
|
99
551
|
flattenObject(obj, separator = ".") {
|
|
100
552
|
const result = {};
|
|
@@ -115,6 +567,42 @@ class ObjectUtils {
|
|
|
115
567
|
recurse(obj);
|
|
116
568
|
return result;
|
|
117
569
|
}
|
|
570
|
+
/**
|
|
571
|
+
* **unflattenObject**
|
|
572
|
+
*
|
|
573
|
+
* Reverses {@link flattenObject}, expanding dot-notation (or custom
|
|
574
|
+
* separator) keys back into a nested object structure.
|
|
575
|
+
*
|
|
576
|
+
* @param obj - The flat object to expand.
|
|
577
|
+
* @param separator - Path separator used in the flat keys (default: `"."`).
|
|
578
|
+
* @returns A nested object reconstructed from the flat keys.
|
|
579
|
+
*
|
|
580
|
+
* @example
|
|
581
|
+
* ```ts
|
|
582
|
+
* utils.unflattenObject({ "a.b": 1, "a.c.d": 2 });
|
|
583
|
+
* // { a: { b: 1, c: { d: 2 } } }
|
|
584
|
+
* ```
|
|
585
|
+
*/
|
|
586
|
+
unflattenObject(obj, separator = ".") {
|
|
587
|
+
const result = {};
|
|
588
|
+
for (const [flatKey, val] of Object.entries(obj)) {
|
|
589
|
+
const parts = flatKey.split(separator);
|
|
590
|
+
let cursor = result;
|
|
591
|
+
parts.forEach((part, i) => {
|
|
592
|
+
if (i === parts.length - 1) {
|
|
593
|
+
cursor[part] = val;
|
|
594
|
+
}
|
|
595
|
+
else {
|
|
596
|
+
if (typeof cursor[part] !== "object" ||
|
|
597
|
+
cursor[part] === null) {
|
|
598
|
+
cursor[part] = {};
|
|
599
|
+
}
|
|
600
|
+
cursor = cursor[part];
|
|
601
|
+
}
|
|
602
|
+
});
|
|
603
|
+
}
|
|
604
|
+
return result;
|
|
605
|
+
}
|
|
118
606
|
/**
|
|
119
607
|
* **parse**
|
|
120
608
|
*
|
|
@@ -124,6 +612,12 @@ class ObjectUtils {
|
|
|
124
612
|
* @param json The JSON string to parse.
|
|
125
613
|
* @param fallback The fallback value to return on failure.
|
|
126
614
|
* @returns The parsed object or the fallback value.
|
|
615
|
+
*
|
|
616
|
+
* @example
|
|
617
|
+
* ```ts
|
|
618
|
+
* utils.parse('{"a":1}'); // { a: 1 }
|
|
619
|
+
* utils.parse("not json", {}); // {}
|
|
620
|
+
* ```
|
|
127
621
|
*/
|
|
128
622
|
parse(json, fallback = null) {
|
|
129
623
|
try {
|
|
@@ -133,7 +627,344 @@ class ObjectUtils {
|
|
|
133
627
|
return fallback;
|
|
134
628
|
}
|
|
135
629
|
}
|
|
630
|
+
/**
|
|
631
|
+
* **merge**
|
|
632
|
+
*
|
|
633
|
+
* Deep-merges one or more source objects into a copy of `target`.
|
|
634
|
+
* Plain object values are merged recursively; arrays and primitive
|
|
635
|
+
* values are overwritten by the last source that defines them.
|
|
636
|
+
* The original `target` and `sources` are never mutated.
|
|
637
|
+
*
|
|
638
|
+
* @param target - The base object.
|
|
639
|
+
* @param sources - One or more objects to merge into the base.
|
|
640
|
+
* @returns A new object containing the deep-merged result.
|
|
641
|
+
*
|
|
642
|
+
* @example
|
|
643
|
+
* ```ts
|
|
644
|
+
* utils.merge({ a: 1, nested: { x: 1 } }, { nested: { y: 2 } });
|
|
645
|
+
* // { a: 1, nested: { x: 1, y: 2 } }
|
|
646
|
+
* ```
|
|
647
|
+
*/
|
|
648
|
+
merge(target, ...sources) {
|
|
649
|
+
const isPlainObject = (val) => !!val && typeof val === "object" && !Array.isArray(val);
|
|
650
|
+
const deepMerge = (t, s) => {
|
|
651
|
+
for (const key of Object.keys(s)) {
|
|
652
|
+
if (isPlainObject(s[key]) && isPlainObject(t[key])) {
|
|
653
|
+
deepMerge(t[key], s[key]);
|
|
654
|
+
}
|
|
655
|
+
else {
|
|
656
|
+
t[key] = s[key];
|
|
657
|
+
}
|
|
658
|
+
}
|
|
659
|
+
return t;
|
|
660
|
+
};
|
|
661
|
+
const base = JSON.parse(XStringify(target));
|
|
662
|
+
return sources.reduce((acc, src) => deepMerge(acc, src), base);
|
|
663
|
+
}
|
|
664
|
+
/**
|
|
665
|
+
* **mapValues**
|
|
666
|
+
*
|
|
667
|
+
* Builds a new object by transforming every value of `obj` with `fn`,
|
|
668
|
+
* keeping the same keys.
|
|
669
|
+
*
|
|
670
|
+
* @param obj - The source object.
|
|
671
|
+
* @param fn - Mapping function receiving `(value, key)`.
|
|
672
|
+
* @returns A new object with transformed values.
|
|
673
|
+
*
|
|
674
|
+
* @example
|
|
675
|
+
* ```ts
|
|
676
|
+
* utils.mapValues({ a: 1, b: 2 }, (v) => v * 10);
|
|
677
|
+
* // { a: 10, b: 20 }
|
|
678
|
+
* ```
|
|
679
|
+
*/
|
|
680
|
+
mapValues(obj, fn) {
|
|
681
|
+
const result = {};
|
|
682
|
+
for (const key of Object.keys(obj)) {
|
|
683
|
+
result[key] = fn(obj[key], key);
|
|
684
|
+
}
|
|
685
|
+
return result;
|
|
686
|
+
}
|
|
687
|
+
/**
|
|
688
|
+
* **mapKeys**
|
|
689
|
+
*
|
|
690
|
+
* Builds a new object by transforming every key of `obj` with `fn`,
|
|
691
|
+
* keeping the same values.
|
|
692
|
+
*
|
|
693
|
+
* @param obj - The source object.
|
|
694
|
+
* @param fn - Mapping function receiving `(key, value)`. Must return a string.
|
|
695
|
+
* @returns A new object with renamed keys.
|
|
696
|
+
*
|
|
697
|
+
* @example
|
|
698
|
+
* ```ts
|
|
699
|
+
* utils.mapKeys({ a: 1, b: 2 }, (k) => k.toUpperCase());
|
|
700
|
+
* // { A: 1, B: 2 }
|
|
701
|
+
* ```
|
|
702
|
+
*/
|
|
703
|
+
mapKeys(obj, fn) {
|
|
704
|
+
const result = {};
|
|
705
|
+
for (const key of Object.keys(obj)) {
|
|
706
|
+
result[fn(key, obj[key])] = obj[key];
|
|
707
|
+
}
|
|
708
|
+
return result;
|
|
709
|
+
}
|
|
710
|
+
/**
|
|
711
|
+
* **filter**
|
|
712
|
+
*
|
|
713
|
+
* Builds a new object containing only the key-value pairs of `obj`
|
|
714
|
+
* for which `predicate` returns `true`.
|
|
715
|
+
*
|
|
716
|
+
* @param obj - The source object.
|
|
717
|
+
* @param predicate - Function receiving `(value, key)`.
|
|
718
|
+
* @returns A new, filtered object.
|
|
719
|
+
*
|
|
720
|
+
* @example
|
|
721
|
+
* ```ts
|
|
722
|
+
* utils.filter({ a: 1, b: 2, c: 3 }, (v) => v > 1);
|
|
723
|
+
* // { b: 2, c: 3 }
|
|
724
|
+
* ```
|
|
725
|
+
*/
|
|
726
|
+
filter(obj, predicate) {
|
|
727
|
+
const result = {};
|
|
728
|
+
for (const key of Object.keys(obj)) {
|
|
729
|
+
if (predicate(obj[key], key)) {
|
|
730
|
+
result[key] = obj[key];
|
|
731
|
+
}
|
|
732
|
+
}
|
|
733
|
+
return result;
|
|
734
|
+
}
|
|
735
|
+
/**
|
|
736
|
+
* **get**
|
|
737
|
+
*
|
|
738
|
+
* Safely reads a possibly-nested value from `obj` using a dot-notation
|
|
739
|
+
* path, returning `fallback` if any part of the path is missing.
|
|
740
|
+
*
|
|
741
|
+
* @param obj - The source object.
|
|
742
|
+
* @param path - Dot-notation path (e.g. `"a.b.c"`).
|
|
743
|
+
* @param fallback - Value returned if the path can't be resolved.
|
|
744
|
+
* @returns The resolved value or the fallback.
|
|
745
|
+
*
|
|
746
|
+
* @example
|
|
747
|
+
* ```ts
|
|
748
|
+
* utils.get({ a: { b: { c: 42 } } }, "a.b.c"); // 42
|
|
749
|
+
* utils.get({ a: {} }, "a.b.c", "missing"); // "missing"
|
|
750
|
+
* ```
|
|
751
|
+
*/
|
|
752
|
+
get(obj, path, fallback = undefined) {
|
|
753
|
+
const parts = path.split(".");
|
|
754
|
+
let cursor = obj;
|
|
755
|
+
for (const part of parts) {
|
|
756
|
+
if (cursor === null || cursor === undefined)
|
|
757
|
+
return fallback;
|
|
758
|
+
cursor = cursor[part];
|
|
759
|
+
}
|
|
760
|
+
return cursor === undefined ? fallback : cursor;
|
|
761
|
+
}
|
|
762
|
+
/**
|
|
763
|
+
* **set**
|
|
764
|
+
*
|
|
765
|
+
* Returns a new object equal to `obj` but with a possibly-nested value
|
|
766
|
+
* set at `path` (dot-notation), creating intermediate objects as needed.
|
|
767
|
+
* The original `obj` is not mutated.
|
|
768
|
+
*
|
|
769
|
+
* @param obj - The source object.
|
|
770
|
+
* @param path - Dot-notation path (e.g. `"a.b.c"`).
|
|
771
|
+
* @param value - The value to set.
|
|
772
|
+
* @returns A new object with the value set at the given path.
|
|
773
|
+
*
|
|
774
|
+
* @example
|
|
775
|
+
* ```ts
|
|
776
|
+
* utils.set({}, "a.b.c", 42);
|
|
777
|
+
* // { a: { b: { c: 42 } } }
|
|
778
|
+
* ```
|
|
779
|
+
*/
|
|
780
|
+
set(obj, path, value) {
|
|
781
|
+
const clone = JSON.parse(XStringify(obj));
|
|
782
|
+
const parts = path.split(".");
|
|
783
|
+
let cursor = clone;
|
|
784
|
+
parts.forEach((part, i) => {
|
|
785
|
+
if (i === parts.length - 1) {
|
|
786
|
+
cursor[part] = value;
|
|
787
|
+
}
|
|
788
|
+
else {
|
|
789
|
+
if (typeof cursor[part] !== "object" || cursor[part] === null) {
|
|
790
|
+
cursor[part] = {};
|
|
791
|
+
}
|
|
792
|
+
cursor = cursor[part];
|
|
793
|
+
}
|
|
794
|
+
});
|
|
795
|
+
return clone;
|
|
796
|
+
}
|
|
797
|
+
/**
|
|
798
|
+
* **has**
|
|
799
|
+
*
|
|
800
|
+
* Checks whether `obj` has the given own key (shortcut over
|
|
801
|
+
* `Object.prototype.hasOwnProperty`).
|
|
802
|
+
*
|
|
803
|
+
* @param obj - The object to inspect.
|
|
804
|
+
* @param key - The key to check.
|
|
805
|
+
* @returns `true` if the key exists as an own property.
|
|
806
|
+
*
|
|
807
|
+
* @example
|
|
808
|
+
* ```ts
|
|
809
|
+
* utils.has({ a: 1 }, "a"); // true
|
|
810
|
+
* utils.has({ a: 1 }, "b"); // false
|
|
811
|
+
* ```
|
|
812
|
+
*/
|
|
813
|
+
has(obj, key) {
|
|
814
|
+
return Object.prototype.hasOwnProperty.call(obj, key);
|
|
815
|
+
}
|
|
816
|
+
/**
|
|
817
|
+
* **deepEqual**
|
|
818
|
+
*
|
|
819
|
+
* Performs a deep structural equality check between two values,
|
|
820
|
+
* comparing plain objects and arrays recursively by value rather
|
|
821
|
+
* than by reference.
|
|
822
|
+
*
|
|
823
|
+
* @param a - First value.
|
|
824
|
+
* @param b - Second value.
|
|
825
|
+
* @returns `true` if both values are deeply equal.
|
|
826
|
+
*
|
|
827
|
+
* @example
|
|
828
|
+
* ```ts
|
|
829
|
+
* utils.deepEqual({ a: { b: 1 } }, { a: { b: 1 } }); // true
|
|
830
|
+
* utils.deepEqual({ a: 1 }, { a: 2 }); // false
|
|
831
|
+
* ```
|
|
832
|
+
*/
|
|
833
|
+
static deepEqual(a, b) {
|
|
834
|
+
if (a === b)
|
|
835
|
+
return true;
|
|
836
|
+
if (typeof a !== typeof b)
|
|
837
|
+
return false;
|
|
838
|
+
if (a === null || b === null)
|
|
839
|
+
return a === b;
|
|
840
|
+
if (typeof a !== "object")
|
|
841
|
+
return false;
|
|
842
|
+
if (Array.isArray(a) || Array.isArray(b)) {
|
|
843
|
+
if (!Array.isArray(a) || !Array.isArray(b))
|
|
844
|
+
return false;
|
|
845
|
+
if (a.length !== b.length)
|
|
846
|
+
return false;
|
|
847
|
+
return a.every((item, i) => __sys__.utils.obj.deepEqual(item, b[i]));
|
|
848
|
+
}
|
|
849
|
+
const aKeys = Object.keys(a);
|
|
850
|
+
const bKeys = Object.keys(b);
|
|
851
|
+
if (aKeys.length !== bKeys.length)
|
|
852
|
+
return false;
|
|
853
|
+
return aKeys.every((key) => __sys__.utils.obj.deepEqual(a[key], b[key]));
|
|
854
|
+
}
|
|
855
|
+
/**
|
|
856
|
+
* **deepEqual** (instance method)
|
|
857
|
+
*
|
|
858
|
+
* Instance-bound convenience wrapper around the static
|
|
859
|
+
* {@link __sys__.utils.obj.deepEqual}, for callers already holding an
|
|
860
|
+
* `ObjectUtils` instance.
|
|
861
|
+
*
|
|
862
|
+
* @param a - First value.
|
|
863
|
+
* @param b - Second value.
|
|
864
|
+
* @returns `true` if both values are deeply equal.
|
|
865
|
+
*/
|
|
866
|
+
deepEqual(a, b) {
|
|
867
|
+
return __sys__.utils.obj.deepEqual(a, b);
|
|
868
|
+
}
|
|
869
|
+
/**
|
|
870
|
+
* **invert**
|
|
871
|
+
*
|
|
872
|
+
* Swaps keys and values: returns a new object where each original
|
|
873
|
+
* value (stringified) becomes a key, and each original key becomes
|
|
874
|
+
* its value. If multiple keys share a value, the last one wins.
|
|
875
|
+
*
|
|
876
|
+
* @param obj - The source object. Values are coerced to strings for keys.
|
|
877
|
+
* @returns A new object with keys and values swapped.
|
|
878
|
+
*
|
|
879
|
+
* @example
|
|
880
|
+
* ```ts
|
|
881
|
+
* utils.invert({ a: "x", b: "y" });
|
|
882
|
+
* // { x: "a", y: "b" }
|
|
883
|
+
* ```
|
|
884
|
+
*/
|
|
885
|
+
invert(obj) {
|
|
886
|
+
const result = {};
|
|
887
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
888
|
+
result[String(value)] = key;
|
|
889
|
+
}
|
|
890
|
+
return result;
|
|
891
|
+
}
|
|
892
|
+
/**
|
|
893
|
+
* **defaults**
|
|
894
|
+
*
|
|
895
|
+
* Returns a new object built from `obj`, filling in any keys that are
|
|
896
|
+
* `undefined` with the corresponding value from `defaultsObj`.
|
|
897
|
+
* Does not overwrite keys that already have a defined value.
|
|
898
|
+
*
|
|
899
|
+
* @param obj - The source object.
|
|
900
|
+
* @param defaultsObj - Object providing fallback values.
|
|
901
|
+
* @returns A new object with defaults applied.
|
|
902
|
+
*
|
|
903
|
+
* @example
|
|
904
|
+
* ```ts
|
|
905
|
+
* utils.defaults({ a: 1, b: undefined }, { b: 2, c: 3 });
|
|
906
|
+
* // { a: 1, b: 2, c: 3 }
|
|
907
|
+
* ```
|
|
908
|
+
*/
|
|
909
|
+
defaults(obj, defaultsObj) {
|
|
910
|
+
const result = { ...defaultsObj, ...obj };
|
|
911
|
+
for (const key of Object.keys(result)) {
|
|
912
|
+
if (result[key] === undefined && key in defaultsObj) {
|
|
913
|
+
result[key] = defaultsObj[key];
|
|
914
|
+
}
|
|
915
|
+
}
|
|
916
|
+
return result;
|
|
917
|
+
}
|
|
918
|
+
/**
|
|
919
|
+
* **compact**
|
|
920
|
+
*
|
|
921
|
+
* Returns a new object with all keys whose value is `null` or
|
|
922
|
+
* `undefined` removed.
|
|
923
|
+
*
|
|
924
|
+
* @param obj - The source object.
|
|
925
|
+
* @returns A new object without null/undefined values.
|
|
926
|
+
*
|
|
927
|
+
* @example
|
|
928
|
+
* ```ts
|
|
929
|
+
* utils.compact({ a: 1, b: null, c: undefined, d: 0 });
|
|
930
|
+
* // { a: 1, d: 0 }
|
|
931
|
+
* ```
|
|
932
|
+
*/
|
|
933
|
+
compact(obj) {
|
|
934
|
+
const result = {};
|
|
935
|
+
for (const key of Object.keys(obj)) {
|
|
936
|
+
const val = obj[key];
|
|
937
|
+
if (val !== null && val !== undefined) {
|
|
938
|
+
result[key] = val;
|
|
939
|
+
}
|
|
940
|
+
}
|
|
941
|
+
return result;
|
|
942
|
+
}
|
|
943
|
+
/**
|
|
944
|
+
* **isPlainObject**
|
|
945
|
+
*
|
|
946
|
+
* Checks whether `value` is a plain object (i.e. not `null`, not an
|
|
947
|
+
* array, not a class instance created from a custom prototype, and
|
|
948
|
+
* not a built-in like `Date`, `Map`, or `Set`).
|
|
949
|
+
*
|
|
950
|
+
* @param value - The value to check.
|
|
951
|
+
* @returns `true` if `value` is a plain object.
|
|
952
|
+
*
|
|
953
|
+
* @example
|
|
954
|
+
* ```ts
|
|
955
|
+
* utils.isPlainObject({}); // true
|
|
956
|
+
* utils.isPlainObject([]); // false
|
|
957
|
+
* utils.isPlainObject(new Date()); // false
|
|
958
|
+
* utils.isPlainObject(null); // false
|
|
959
|
+
* ```
|
|
960
|
+
*/
|
|
961
|
+
isPlainObject(value) {
|
|
962
|
+
if (value === null || typeof value !== "object")
|
|
963
|
+
return false;
|
|
964
|
+
const proto = Object.getPrototypeOf(value);
|
|
965
|
+
return proto === Object.prototype || proto === null;
|
|
966
|
+
}
|
|
136
967
|
}
|
|
137
968
|
|
|
138
|
-
export { ObjectUtils };
|
|
969
|
+
export { ObjectUtils, ObjectWrapper };
|
|
139
970
|
//# sourceMappingURL=ObjectUtils.js.map
|