@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.
- package/LICENSE +21 -0
- package/README.md +266 -0
- package/dist/index.d.ts +2715 -0
- package/dist/index.es.js +1715 -0
- package/dist/index.es.js.map +1 -0
- package/package.json +70 -0
- package/src/index.ts +90 -0
- package/src/reform/ArrayHelper.ts +164 -0
- package/src/reform/Form.tsx +81 -0
- package/src/reform/FormManager.ts +494 -0
- package/src/reform/Reform.ts +15 -0
- package/src/reform/components/BaseCheckboxField.tsx +72 -0
- package/src/reform/components/BaseDateField.tsx +84 -0
- package/src/reform/components/BaseRadioField.tsx +72 -0
- package/src/reform/components/BaseSelectField.tsx +103 -0
- package/src/reform/components/BaseTextAreaField.tsx +87 -0
- package/src/reform/components/BaseTextField.tsx +135 -0
- package/src/reform/components/InputHTMLProps.tsx +89 -0
- package/src/reform/observers/observer.ts +131 -0
- package/src/reform/observers/observerPath.ts +327 -0
- package/src/reform/observers/useObservers.ts +232 -0
- package/src/reform/useForm.ts +246 -0
- package/src/reform/useFormContext.tsx +37 -0
- package/src/reform/useFormField.ts +75 -0
- package/src/reform/useRender.ts +12 -0
- package/src/yop/MessageProvider.ts +204 -0
- package/src/yop/Metadata.ts +304 -0
- package/src/yop/ObjectsUtil.ts +811 -0
- package/src/yop/TypesUtil.ts +148 -0
- package/src/yop/ValidationContext.ts +207 -0
- package/src/yop/Yop.ts +430 -0
- package/src/yop/constraints/CommonConstraints.ts +124 -0
- package/src/yop/constraints/Constraint.ts +135 -0
- package/src/yop/constraints/MinMaxConstraints.ts +53 -0
- package/src/yop/constraints/OneOfConstraint.ts +40 -0
- package/src/yop/constraints/TestConstraint.ts +176 -0
- package/src/yop/decorators/array.ts +157 -0
- package/src/yop/decorators/boolean.ts +69 -0
- package/src/yop/decorators/date.ts +73 -0
- package/src/yop/decorators/email.ts +66 -0
- package/src/yop/decorators/file.ts +69 -0
- package/src/yop/decorators/id.ts +35 -0
- package/src/yop/decorators/ignored.ts +40 -0
- package/src/yop/decorators/instance.ts +110 -0
- package/src/yop/decorators/number.ts +73 -0
- package/src/yop/decorators/string.ts +90 -0
- package/src/yop/decorators/test.ts +41 -0
- 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
|
+
|