@4riders/reform 3.0.24

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 (48) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +266 -0
  3. package/dist/index.d.ts +2715 -0
  4. package/dist/index.es.js +1715 -0
  5. package/dist/index.es.js.map +1 -0
  6. package/package.json +70 -0
  7. package/src/index.ts +90 -0
  8. package/src/reform/ArrayHelper.ts +164 -0
  9. package/src/reform/Form.tsx +81 -0
  10. package/src/reform/FormManager.ts +494 -0
  11. package/src/reform/Reform.ts +15 -0
  12. package/src/reform/components/BaseCheckboxField.tsx +72 -0
  13. package/src/reform/components/BaseDateField.tsx +84 -0
  14. package/src/reform/components/BaseRadioField.tsx +72 -0
  15. package/src/reform/components/BaseSelectField.tsx +103 -0
  16. package/src/reform/components/BaseTextAreaField.tsx +87 -0
  17. package/src/reform/components/BaseTextField.tsx +135 -0
  18. package/src/reform/components/InputHTMLProps.tsx +89 -0
  19. package/src/reform/observers/observer.ts +131 -0
  20. package/src/reform/observers/observerPath.ts +327 -0
  21. package/src/reform/observers/useObservers.ts +232 -0
  22. package/src/reform/useForm.ts +246 -0
  23. package/src/reform/useFormContext.tsx +37 -0
  24. package/src/reform/useFormField.ts +75 -0
  25. package/src/reform/useRender.ts +12 -0
  26. package/src/yop/MessageProvider.ts +204 -0
  27. package/src/yop/Metadata.ts +304 -0
  28. package/src/yop/ObjectsUtil.ts +811 -0
  29. package/src/yop/TypesUtil.ts +148 -0
  30. package/src/yop/ValidationContext.ts +207 -0
  31. package/src/yop/Yop.ts +430 -0
  32. package/src/yop/constraints/CommonConstraints.ts +124 -0
  33. package/src/yop/constraints/Constraint.ts +135 -0
  34. package/src/yop/constraints/MinMaxConstraints.ts +53 -0
  35. package/src/yop/constraints/OneOfConstraint.ts +40 -0
  36. package/src/yop/constraints/TestConstraint.ts +176 -0
  37. package/src/yop/decorators/array.ts +157 -0
  38. package/src/yop/decorators/boolean.ts +69 -0
  39. package/src/yop/decorators/date.ts +73 -0
  40. package/src/yop/decorators/email.ts +66 -0
  41. package/src/yop/decorators/file.ts +69 -0
  42. package/src/yop/decorators/id.ts +35 -0
  43. package/src/yop/decorators/ignored.ts +40 -0
  44. package/src/yop/decorators/instance.ts +110 -0
  45. package/src/yop/decorators/number.ts +73 -0
  46. package/src/yop/decorators/string.ts +90 -0
  47. package/src/yop/decorators/test.ts +41 -0
  48. package/src/yop/decorators/time.ts +112 -0
@@ -0,0 +1,811 @@
1
+ /**
2
+ * State constants for path parsing.
3
+ * @ignore
4
+ */
5
+ const DOT = 1
6
+ const OPEN_BRACKET = 2
7
+ const SINGLE_QUOTE = 3
8
+ const DOUBLE_QUOTE = 4
9
+ const CLOSE_QUOTE = 5
10
+ const CLOSE_BRACKET = 6
11
+
12
+ /**
13
+ * Type for path parser state.
14
+ * @ignore
15
+ */
16
+ type State = typeof DOT | typeof OPEN_BRACKET | typeof SINGLE_QUOTE | typeof DOUBLE_QUOTE | typeof CLOSE_QUOTE | typeof CLOSE_BRACKET | undefined
17
+
18
+ /**
19
+ * Type for a parsed path, as an array of string or number segments. Numbers represent array indices, while strings represent object keys. This type
20
+ * is used for efficient access to nested properties in objects or arrays.
21
+ * @see {@link splitPath}
22
+ * @category Utilities
23
+ */
24
+ export type Path = (string | number)[]
25
+
26
+ const identifier = /^[$_\p{ID_Start}][$\p{ID_Continue}]*$/u
27
+ /**
28
+ * Checks if a string is a valid JavaScript identifier.
29
+ * @param segment - The string segment to check.
30
+ * @returns True if valid identifier, false otherwise.
31
+ * @ignore
32
+ */
33
+ function isValidIdentifier(segment: string): boolean {
34
+ return identifier.test(segment)
35
+ }
36
+
37
+ /**
38
+ * Splits a string path into segments, handling dot/bracket/quote notation.
39
+ * @param path - The path string to split.
40
+ * @param cache - Optional cache for parsed paths.
41
+ * @returns The parsed path as an array, or undefined if invalid.
42
+ * @category Utilities
43
+ */
44
+ export function splitPath(path: string, cache?: Map<string, Path>): Path | undefined {
45
+
46
+ if (cache != null) {
47
+ const cached = cache.get(path)
48
+ if (cached != null)
49
+ return cached.slice()
50
+ }
51
+
52
+ const segments = []
53
+
54
+ let state: State = undefined,
55
+ escape = false,
56
+ segment = ""
57
+
58
+ for (let i = 0; i < path.length; i++) {
59
+ let c = path.charAt(i)
60
+
61
+ switch (c) {
62
+
63
+ case '\\':
64
+ if (state !== SINGLE_QUOTE && state !== DOUBLE_QUOTE)
65
+ return undefined
66
+ if (escape)
67
+ segment += '\\'
68
+ escape = !escape
69
+ continue
70
+
71
+ case ' ': case '\t': case '\r': case '\n':
72
+ if (state === SINGLE_QUOTE || state === DOUBLE_QUOTE)
73
+ segment += c
74
+ else {
75
+ while (++i < path.length && ((c = path.charAt(i)) === ' ' || c === '\t' || c === '\r' || c === '\n'))
76
+ ;
77
+ if (state === OPEN_BRACKET && path.charAt(i) !== ']' && segment)
78
+ return undefined
79
+ --i
80
+ }
81
+ break
82
+
83
+ case '.':
84
+ if (state === SINGLE_QUOTE || state === DOUBLE_QUOTE)
85
+ segment += c
86
+ else if (state === CLOSE_BRACKET) {
87
+ if (segment)
88
+ return undefined
89
+ state = DOT
90
+ }
91
+ else if (state === undefined || state === DOT) {
92
+ if (!isValidIdentifier(segment))
93
+ return undefined
94
+ segments.push(segment)
95
+ segment = ""
96
+ state = DOT
97
+ }
98
+ else
99
+ return undefined
100
+ break
101
+
102
+ case '[':
103
+ if (state === SINGLE_QUOTE || state === DOUBLE_QUOTE)
104
+ segment += c
105
+ else if (state === DOT) {
106
+ if (!isValidIdentifier(segment))
107
+ return undefined
108
+ segments.push(segment)
109
+ segment = ""
110
+ state = OPEN_BRACKET
111
+ }
112
+ else if (state === CLOSE_BRACKET) {
113
+ if (segment)
114
+ return undefined
115
+ state = OPEN_BRACKET
116
+ }
117
+ else if (state === undefined) {
118
+ if (segment) {
119
+ if (!isValidIdentifier(segment))
120
+ return undefined
121
+ segments.push(segment)
122
+ segment = ""
123
+ }
124
+ state = OPEN_BRACKET
125
+ }
126
+ else
127
+ return undefined
128
+ break
129
+
130
+ case ']':
131
+ if (state === SINGLE_QUOTE || state === DOUBLE_QUOTE)
132
+ segment += c
133
+ else if (state === OPEN_BRACKET) {
134
+ if (!segment)
135
+ return undefined
136
+ segments.push(parseInt(segment, 10))
137
+ segment = ""
138
+ state = CLOSE_BRACKET
139
+ }
140
+ else if (state === CLOSE_QUOTE) {
141
+ segments.push(segment)
142
+ segment = ""
143
+ state = CLOSE_BRACKET
144
+ }
145
+ else
146
+ return undefined
147
+ break
148
+
149
+ case '\'':
150
+ if (escape || state === DOUBLE_QUOTE)
151
+ segment += c
152
+ else if (state === SINGLE_QUOTE)
153
+ state = CLOSE_QUOTE
154
+ else if (state === OPEN_BRACKET && !segment)
155
+ state = SINGLE_QUOTE
156
+ else
157
+ return undefined
158
+ break
159
+
160
+ case '"':
161
+ if (escape || state === SINGLE_QUOTE)
162
+ segment += c
163
+ else if (state === DOUBLE_QUOTE)
164
+ state = CLOSE_QUOTE
165
+ else if (state === OPEN_BRACKET && !segment)
166
+ state = DOUBLE_QUOTE
167
+ else
168
+ return undefined
169
+ break
170
+
171
+ default:
172
+ if (state === CLOSE_QUOTE || (state === OPEN_BRACKET && (c < '0' || c > '9')))
173
+ return undefined
174
+ segment += c
175
+ break
176
+ }
177
+
178
+ escape = false
179
+ }
180
+
181
+ switch (state) {
182
+ case undefined:
183
+ if (segment) {
184
+ if (!isValidIdentifier(segment))
185
+ return undefined
186
+ segments.push(segment)
187
+ }
188
+ break
189
+ case CLOSE_BRACKET:
190
+ if (segment)
191
+ return undefined
192
+ break
193
+ case DOT:
194
+ if (!isValidIdentifier(segment))
195
+ return undefined
196
+ segments.push(segment)
197
+ break
198
+ default:
199
+ return undefined
200
+ }
201
+
202
+ if (cache != null) {
203
+ if (cache.size >= 500)
204
+ cache.clear()
205
+ cache.set(path, segments.slice())
206
+ }
207
+
208
+ return segments
209
+ }
210
+
211
+ /**
212
+ * Joins path segments into a string, using dot/bracket notation as needed.
213
+ * @param segments - The path segments to join.
214
+ * @returns The joined path string.
215
+ * @category Utilities
216
+ */
217
+ export function joinPath(segments: Path): string {
218
+ let path = ""
219
+ for (let segment of segments) {
220
+ if (typeof segment === "number")
221
+ path += "[" + (Number.isNaN(segment) ? "?" : segment) + "]"
222
+ else if (isValidIdentifier(segment))
223
+ path += (path ? "." : "") + segment
224
+ else
225
+ path += "['" + segment.replaceAll("'", "\\'") + "']"
226
+ }
227
+ return path
228
+ }
229
+
230
+ /**
231
+ * Result type for set operation, including root and previous value.
232
+ * @category Utilities
233
+ */
234
+ export type SetResult = undefined | {
235
+ root: unknown
236
+ previousValue?: unknown
237
+ }
238
+
239
+ /**
240
+ * Retrieves a value from an object or array using a string or parsed path.
241
+ * @param value - The root object or array.
242
+ * @param path - The path string or array.
243
+ * @param cache - Optional cache for parsed paths.
244
+ * @returns The value at the path, or undefined if not found.
245
+ * @category Utilities
246
+ */
247
+ export function get<T = any>(value: unknown, path: string | Path, cache?: Map<string, Path>): T | undefined {
248
+ const keys = typeof path === "string" ? splitPath(path, cache) : path
249
+ if (keys == null)
250
+ return undefined
251
+ let parent: any = value
252
+ for (const key of keys) {
253
+ if (parent == null)
254
+ return undefined
255
+ parent = parent[key]
256
+ }
257
+ return parent
258
+ }
259
+
260
+ /**
261
+ * Sets a value in an object or array at a given path, optionally cloning and using a condition.
262
+ * @param value - The root object or array.
263
+ * @param path - The path string or array.
264
+ * @param newValue - The value to set.
265
+ * @param cache - Optional cache for parsed paths.
266
+ * @param options - Optional settings for cloning and condition.
267
+ * @returns The set result, including root and previous value.
268
+ * @category Utilities
269
+ */
270
+ export function set(value: unknown, path: string | Path, newValue: unknown, cache?: Map<string, Path>, options: {
271
+ clone?: boolean
272
+ condition?: (currentValue: unknown) => boolean
273
+ } = { clone: false }): SetResult {
274
+
275
+ const keys = typeof path === "string" ? splitPath(path, cache) : path
276
+ if (keys == null)
277
+ return undefined
278
+
279
+ if (options.clone)
280
+ newValue = clone(newValue)
281
+
282
+ if (keys.length === 0)
283
+ return { root: newValue }
284
+
285
+ const lastKeyIndex = keys.length - 1
286
+ const lastKey = keys[lastKeyIndex]
287
+
288
+ const root = (
289
+ typeof (keys[0] ?? lastKey) === "number" ?
290
+ Array.isArray(value) ? value : [] :
291
+ value != null && typeof value === "object" ? value : {}
292
+ )
293
+
294
+ let parent: any = root
295
+ for (let i = 0; i < lastKeyIndex; i++) {
296
+ const key = keys[i]
297
+ const array = typeof (keys[i + 1] ?? lastKey) === "number"
298
+
299
+ if (parent[key] == null)
300
+ parent[key] = array ? [] : {}
301
+ else if (array) {
302
+ if (!Array.isArray(parent[key]))
303
+ parent[key] = []
304
+ }
305
+ else if (!(parent[key] instanceof Object))
306
+ parent[key] = {}
307
+ parent = parent[key]
308
+ }
309
+
310
+ const previousValue = parent[lastKey]
311
+ if (options.condition?.(previousValue) !== false)
312
+ parent[lastKey] = newValue
313
+ return { root, previousValue }
314
+ }
315
+
316
+ /**
317
+ * Removes a value from an object or array at a given path.
318
+ * @param value - The root object or array.
319
+ * @param path - The path string or array.
320
+ * @param cache - Optional cache for parsed paths.
321
+ * @returns True if unset, false otherwise.
322
+ * @category Utilities
323
+ */
324
+ export function unset(value: unknown, path: string | Path, cache?: Map<string, Path>): boolean | undefined {
325
+ if (value == null)
326
+ return false
327
+
328
+ const keys = typeof path === "string" ? splitPath(path, cache) : path
329
+ if (keys == null || keys.length === 0)
330
+ return undefined
331
+
332
+ const lastKeyIndex = keys.length - 1
333
+ const lastKey = keys[lastKeyIndex]
334
+ let parent: any = value
335
+
336
+ for (let i = 0; i < lastKeyIndex; i++) {
337
+ parent = parent[keys[i]]
338
+ if (parent == null)
339
+ return false
340
+ }
341
+
342
+ if (!(lastKey in parent))
343
+ return false
344
+
345
+ try {
346
+ delete parent[lastKey]
347
+ }
348
+ catch {
349
+ return false
350
+ }
351
+ return true
352
+ }
353
+
354
+ /**
355
+ * Type for a diff result between two values.
356
+ * @template A - The first value type.
357
+ * @template B - The second value type.
358
+ * @category Utilities
359
+ */
360
+ export type Diff<A = any, B = any> = {
361
+
362
+ /**
363
+ * The first value in the diff comparison.
364
+ */
365
+ a: A
366
+
367
+ /**
368
+ * The second value in the diff comparison.
369
+ */
370
+ b: B
371
+
372
+ /**
373
+ * The diff tree representing differences between the two values. The tree is a nested object where keys are path segments and values are
374
+ * either further nested objects or empty objects indicating a difference at that path. If a path is not present in the tree, it means
375
+ * the values are equal at that path. When `equal` is true, the tree is an empty object: `{}`.
376
+ */
377
+ tree: { [key: string | number]: any }
378
+
379
+ /**
380
+ * Indicates whether the two values are equal.
381
+ */
382
+ equal: boolean
383
+ }
384
+
385
+ /**
386
+ * Checks if two values differ at a given path, using a diff tree.
387
+ * @param diff - The diff result.
388
+ * @param path - The path to check.
389
+ * @returns True if values differ at the path, false otherwise.
390
+ * @category Utilities
391
+ */
392
+ export function differs(diff: Diff, path: Path): boolean {
393
+ if (diff.equal)
394
+ return false
395
+
396
+ let { a, b, tree } = diff
397
+ for (let key of path) {
398
+ if (tree[key] == null) {
399
+ a = get(a, path)
400
+ b = get(b, path)
401
+ return !equal(a, b) && (a != null || b != null)
402
+ }
403
+ tree = tree[key]
404
+ }
405
+
406
+ return true
407
+ }
408
+
409
+ /**
410
+ * Computes the diff between two values, returning a diff tree and equality flag.
411
+ * @template A - The first value type.
412
+ * @template B - The second value type.
413
+ * @param a - The first value.
414
+ * @param b - The second value.
415
+ * @returns The diff result.
416
+ * @category Utilities
417
+ */
418
+ export function diff<A = any, B = any>(a: A, b: B): Diff<A, B> {
419
+ const paths: Path[] = []
420
+ _diff(a, b, new Map(), [], paths)
421
+
422
+ const result = {
423
+ a,
424
+ b,
425
+ tree: {} as { [key: string | number]: any },
426
+ equal: paths.length === 0
427
+ }
428
+
429
+ paths.forEach(path => {
430
+ let tree = result.tree
431
+ path.forEach(key => {
432
+ if (tree[key] == null)
433
+ tree[key] = {}
434
+ tree = tree[key]
435
+ })
436
+ })
437
+
438
+ return result
439
+ }
440
+
441
+ /**
442
+ * Internal recursive diff function for comparing two values.
443
+ * @param a - The first value.
444
+ * @param b - The second value.
445
+ * @param known - Map of known comparisons.
446
+ * @param path - Current path.
447
+ * @param diffPaths - Array of differing paths.
448
+ * @ignore
449
+ */
450
+ function _diff(a: any, b: any, known: Map<any, any>, path: Path, diffPaths: Path[]): void {
451
+ if (a === b)
452
+ return
453
+
454
+ if (a == null || b == null) {
455
+ diffPaths.push(path)
456
+ return
457
+ }
458
+
459
+ if ((typeof a == 'object') && (typeof b == 'object')) {
460
+ if (a.constructor !== b.constructor) {
461
+ diffPaths.push(path)
462
+ return
463
+ }
464
+
465
+ if (a instanceof Date) {
466
+ if (a.getTime() !== (b as Date).getTime())
467
+ diffPaths.push(path)
468
+ return
469
+ }
470
+
471
+ if (a instanceof RegExp) {
472
+ if (a.source !== (b as RegExp).source || a.flags !== (b as RegExp).flags)
473
+ diffPaths.push(path)
474
+ return
475
+ }
476
+
477
+ if (a instanceof File) {
478
+ if (a.name !== (b as File).name || a.size !== (b as File).size || a.type !== (b as File).type || a.lastModified !== (b as File).lastModified)
479
+ diffPaths.push(path)
480
+ return
481
+ }
482
+
483
+ if (a instanceof Set) {
484
+ if (a.size !== (b as Set<any>).size) {
485
+ diffPaths.push(path)
486
+ return
487
+ }
488
+ for (const value of a.values()) {
489
+ if (!(b as Set<any>).has(value)) {
490
+ diffPaths.push(path)
491
+ return
492
+ }
493
+ }
494
+ return
495
+ }
496
+
497
+ if (a instanceof ArrayBuffer || ArrayBuffer.isView(a)) {
498
+ if (ArrayBuffer.isView(a)) {
499
+ if (a.byteLength !== (b as ArrayBufferView).byteLength || a.byteOffset !== (b as ArrayBufferView).byteOffset) {
500
+ diffPaths.push(path)
501
+ return
502
+ }
503
+ a = new Uint8Array(a.buffer, a.byteOffset, a.byteLength)
504
+ b = new Uint8Array((b as ArrayBufferView).buffer, (b as ArrayBufferView).byteOffset, (b as ArrayBufferView).byteLength)
505
+ }
506
+ else {
507
+ if (a.byteLength !== (b as ArrayBuffer).byteLength) {
508
+ diffPaths.push(path)
509
+ return
510
+ }
511
+ a = new Uint8Array(a)
512
+ b = new Uint8Array(b)
513
+ }
514
+ for (let i = (a as Uint8Array).length; i-- !== 0; ) {
515
+ if ((a as Uint8Array)[i] !== (b as Uint8Array)[i]) {
516
+ diffPaths.push(path)
517
+ return
518
+ }
519
+ }
520
+ return
521
+ }
522
+
523
+ if (known.get(a) === b)
524
+ return
525
+ known.set(a, b).set(b, a)
526
+
527
+ if (Array.isArray(a)) {
528
+ const length = Math.max(a.length, (b as any[]).length)
529
+ for (let i = 0; i < length; i++)
530
+ _diff(a[i], (b as any[])[i], known, [...path, i], diffPaths)
531
+ return
532
+ }
533
+
534
+ if (a instanceof Map) {
535
+ const keys = new Set<any>()
536
+ for (const entry of a.entries()) {
537
+ const key = entry[0]
538
+ keys.add(key)
539
+ _diff(entry[1], (b as Map<any, any>).get(key), known, [...path, key], diffPaths)
540
+ }
541
+ for (const entry of (b as Map<any, any>).entries()) {
542
+ const key = entry[0]
543
+ if (!keys.has(key))
544
+ _diff(a.get(key), entry[1], known, [...path, key], diffPaths)
545
+ }
546
+ return
547
+ }
548
+
549
+ const keys = new Set<any>()
550
+ for (let key of Object.keys(a)) {
551
+ keys.add(key)
552
+ _diff(a[key], b[key], known, [...path, key], diffPaths)
553
+ }
554
+ for (let key of Object.keys(b)) {
555
+ if (!keys.has(key))
556
+ _diff(a[key], b[key], known, [...path, key], diffPaths)
557
+ }
558
+ return
559
+ }
560
+
561
+ if (!(a !== a && b !== b))
562
+ diffPaths.push(path)
563
+ }
564
+
565
+ /**
566
+ * Checks deep equality between two values, optionally ignoring a path.
567
+ * @param a - The first value.
568
+ * @param b - The second value.
569
+ * @param ignoredPath - Optional path to ignore.
570
+ * @returns True if equal, false otherwise.
571
+ * @category Utilities
572
+ */
573
+ export function equal(a: any, b: any, ignoredPath?: string | Path) {
574
+ return _equal(a, b, new Map(), ignoredPath ? typeof ignoredPath === "string" ? splitPath(ignoredPath) : ignoredPath : undefined)
575
+ }
576
+
577
+ /**
578
+ * Internal recursive equality function for comparing two values.
579
+ * @param a - The first value.
580
+ * @param b - The second value.
581
+ * @param known - Map of known comparisons.
582
+ * @param ignoredPath - Optional path to ignore.
583
+ * @returns True if equal, false otherwise.
584
+ * @ignore
585
+ */
586
+ function _equal(a: any, b: any, known: Map<any, any>, ignoredPath?: Path): boolean {
587
+ if (a === b)
588
+ return true
589
+
590
+ if (a == null || b == null)
591
+ return false
592
+
593
+ if ((typeof a == 'object') && (typeof b == 'object')) {
594
+ if (a.constructor !== b.constructor)
595
+ return false
596
+
597
+ if (a instanceof Date)
598
+ return a.getTime() === (b as Date).getTime()
599
+
600
+ if (a instanceof RegExp)
601
+ return a.source === (b as RegExp).source && a.flags === (b as RegExp).flags
602
+
603
+ if (a instanceof File)
604
+ return a.name === (b as File).name && a.size === (b as File).size && a.type === (b as File).type && a.lastModified === (b as File).lastModified
605
+
606
+ if (a instanceof Set) {
607
+ if (a.size !== (b as Set<any>).size)
608
+ return false
609
+ for (const entry of a.entries()) {
610
+ if (!(b as Set<any>).has(entry[0]))
611
+ return false
612
+ }
613
+ return true
614
+ }
615
+
616
+ if (a instanceof ArrayBuffer || ArrayBuffer.isView(a)) {
617
+ if (ArrayBuffer.isView(a)) {
618
+ if (a.byteLength !== (b as ArrayBufferView).byteLength || a.byteOffset !== (b as ArrayBufferView).byteOffset)
619
+ return false
620
+ a = new Uint8Array(a.buffer, a.byteOffset, a.byteLength)
621
+ b = new Uint8Array((b as ArrayBufferView).buffer, (b as ArrayBufferView).byteOffset, (b as ArrayBufferView).byteLength)
622
+ }
623
+ else {
624
+ if (a.byteLength !== (b as ArrayBuffer).byteLength)
625
+ return false
626
+ a = new Uint8Array(a)
627
+ b = new Uint8Array(b)
628
+ }
629
+ for (let i = (a as Uint8Array).length; i-- !== 0; ) {
630
+ if ((a as Uint8Array)[i] !== (b as Uint8Array)[i])
631
+ return false
632
+ }
633
+ return true
634
+ }
635
+
636
+ if (known.get(a) === b)
637
+ return true
638
+ known.set(a, b).set(b, a)
639
+
640
+ if (Array.isArray(a)) {
641
+ const length = a.length
642
+ if (length !== (b as any[]).length)
643
+ return false
644
+ for (let i = length; i-- !== 0; ) {
645
+ if (ignoredPath != null && i === ignoredPath[0]) {
646
+ if (ignoredPath.length === 1)
647
+ continue
648
+ if (!_equal(a[i], (b as any[])[i], known, ignoredPath.slice(1)))
649
+ return false
650
+ }
651
+ if (!_equal(a[i], (b as any[])[i], known))
652
+ return false
653
+ }
654
+ return true
655
+ }
656
+
657
+ if (a instanceof Map) {
658
+ if (a.size !== (b as Map<any, any>).size)
659
+ return false
660
+ for (const entry of a.entries()) {
661
+ if (!(b as Map<any, any>).has(entry[0]))
662
+ return false
663
+ }
664
+ for (const entry of a.entries()) {
665
+ const key = entry[0]
666
+ if (ignoredPath != null && key === ignoredPath[0]) {
667
+ if (ignoredPath.length === 1)
668
+ continue
669
+ if (!_equal(entry[1], (b as Map<any, any>).get(key), known, ignoredPath.slice(1)))
670
+ return false
671
+ }
672
+ if (!_equal(entry[1], (b as Map<any, any>).get(key), known))
673
+ return false
674
+ }
675
+ return true
676
+ }
677
+
678
+ const keys = Object.keys(a)
679
+ const length = keys.length
680
+ if (length !== Object.keys(b).length)
681
+ return false
682
+ for (let i = length; i-- !== 0; ) {
683
+ if (!Object.prototype.hasOwnProperty.call(b, keys[i]))
684
+ return false
685
+ }
686
+ for (let i = length; i-- !== 0; ) {
687
+ const key = keys[i]
688
+ if (ignoredPath != null && key === ignoredPath[0]) {
689
+ if (ignoredPath.length === 1)
690
+ continue
691
+ if (!_equal(a[key], b[key], known, ignoredPath.slice(1)))
692
+ return false
693
+ }
694
+ if (!_equal(a[key], b[key], known))
695
+ return false
696
+ }
697
+
698
+ return true
699
+ }
700
+
701
+ return a !== a && b !== b
702
+ }
703
+
704
+ /**
705
+ * Deep clones a value, handling arrays, objects, dates, maps, sets, and files.
706
+ * @template T - The value type.
707
+ * @param value - The value to clone.
708
+ * @param cloned - Optional map of already cloned values.
709
+ * @returns The cloned value.
710
+ * @category Utilities
711
+ */
712
+ export function clone<T>(value: T, cloned?: Map<any, any>): T {
713
+ if (value == null || typeof value !== 'object')
714
+ return value
715
+
716
+ if (cloned == null)
717
+ cloned = new Map<any, any>()
718
+ else {
719
+ const copy = cloned.get(value)
720
+ if (copy != null)
721
+ return copy
722
+ }
723
+
724
+ if (Array.isArray(value)) {
725
+ const copy = new Array()
726
+ cloned.set(value, copy)
727
+ value.forEach(item => copy.push(clone(item, cloned)))
728
+ return copy as T
729
+ }
730
+ if (value instanceof Date) {
731
+ const copy = new Date(value)
732
+ cloned.set(value, copy)
733
+ return copy as T
734
+ }
735
+ if (value instanceof RegExp) {
736
+ const copy = new RegExp(value)
737
+ cloned.set(value, copy)
738
+ return copy as T
739
+ }
740
+ if (value instanceof Set) {
741
+ const copy = new Set()
742
+ cloned.set(value, copy)
743
+ value.forEach(item => copy.add(clone(item, cloned)))
744
+ return copy as T
745
+ }
746
+ if (value instanceof Map) {
747
+ const copy = new Map()
748
+ cloned.set(value, copy)
749
+ value.forEach((val, key) => copy.set(clone(key, cloned), clone(val, cloned)))
750
+ return copy as T
751
+ }
752
+ if (value instanceof File) {
753
+ cloned.set(value, value)
754
+ return value
755
+ }
756
+
757
+ const copy = Object.create(Object.getPrototypeOf(value))
758
+ cloned.set(value, copy)
759
+ const descriptors = Object.getOwnPropertyDescriptors(value)
760
+ for (const descriptor of Object.values(descriptors)) {
761
+ if (descriptor.get == null)
762
+ descriptor.value = clone(descriptor.value, cloned)
763
+ }
764
+ Object.defineProperties(copy, descriptors)
765
+
766
+ return copy as T
767
+ }
768
+
769
+ /**
770
+ * Defines a lazily-evaluated property on an object.
771
+ * @template T - The object type.
772
+ * @param o - The object.
773
+ * @param name - The property name.
774
+ * @param get - The getter function.
775
+ * @ignore
776
+ */
777
+ export function defineLazyProperty<T>(o: T, name: PropertyKey, get: ((_this: T) => unknown)) {
778
+ Object.defineProperty(o, name, { configurable: true, enumerable: true, get: function() {
779
+ const value = get(this)
780
+ Object.defineProperty(this, name, { value, configurable: true, enumerable: true, writable: true })
781
+ return value
782
+ }})
783
+ }
784
+
785
+ /**
786
+ * Assigns properties from source to target, with options for skipping undefined, including, or excluding keys.
787
+ * @template T - The target type.
788
+ * @template U - The source type.
789
+ * @param target - The target object.
790
+ * @param source - The source object.
791
+ * @param options - Optional settings for assignment.
792
+ * @returns The merged object.
793
+ * @category Utilities
794
+ */
795
+ export function assign<T extends {}, U>(target: T, source: U, options?: { skipUndefined?: boolean, includes?: (keyof U)[], excludes?: (keyof U)[] }): T & U {
796
+ const descriptors = Object.getOwnPropertyDescriptors(source)
797
+ if (options && (options.skipUndefined || options.includes || options.excludes)) {
798
+ for (const [name, descriptor] of Object.entries(descriptors)) {
799
+ if (options.skipUndefined && descriptor.get == null && descriptor.value === undefined)
800
+ delete descriptors[name]
801
+ else if (options.includes && !options.includes.includes(name as keyof U))
802
+ delete descriptors[name]
803
+ else if (options.excludes?.includes(name as keyof U))
804
+ delete descriptors[name]
805
+ }
806
+ }
807
+ Object.defineProperties(target, descriptors)
808
+ return target as T & U
809
+ }
810
+
811
+