@rimbu/deep 0.14.5 → 1.0.0-alpha.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/{src/deep.ts → dist/bun/deep.mts} +7 -7
- package/{src/index.ts → dist/bun/index.mts} +5 -5
- package/dist/bun/internal.mts +8 -0
- package/{src/match.ts → dist/bun/match.mts} +6 -6
- package/{src/patch.ts → dist/bun/patch.mts} +9 -4
- package/{src/path.ts → dist/bun/path.mts} +2 -2
- package/{src/selector.ts → dist/bun/selector.mts} +1 -1
- package/dist/cjs/deep.js +590 -0
- package/dist/cjs/index.js +663 -0
- package/dist/cjs/internal.js +583 -0
- package/dist/cjs/match.js +376 -0
- package/dist/cjs/patch.js +105 -0
- package/dist/cjs/path.js +585 -0
- package/dist/cjs/protected.js +18 -0
- package/dist/cjs/selector.js +581 -0
- package/dist/cjs/tuple.js +73 -0
- package/dist/{module/deep.js → esm/deep.mjs} +6 -6
- package/dist/esm/deep.mjs.map +1 -0
- package/dist/{module/index.js → esm/index.mjs} +5 -5
- package/dist/esm/index.mjs.map +1 -0
- package/dist/esm/internal.mjs +5 -0
- package/dist/esm/internal.mjs.map +1 -0
- package/dist/{module/match.js → esm/match.mjs} +21 -21
- package/dist/esm/match.mjs.map +1 -0
- package/dist/{module/patch.js → esm/patch.mjs} +5 -5
- package/dist/esm/patch.mjs.map +1 -0
- package/dist/{module/path.js → esm/path.mjs} +2 -2
- package/dist/esm/path.mjs.map +1 -0
- package/dist/esm/protected.mjs +2 -0
- package/dist/esm/protected.mjs.map +1 -0
- package/dist/{module/selector.js → esm/selector.mjs} +2 -2
- package/dist/esm/selector.mjs.map +1 -0
- package/dist/{module/tuple.js → esm/tuple.mjs} +1 -1
- package/dist/esm/tuple.mjs.map +1 -0
- package/dist/types/{deep.d.ts → deep.d.mts} +6 -6
- package/dist/types/{index.d.ts → index.d.mts} +5 -5
- package/dist/types/internal.d.mts +7 -0
- package/dist/types/{match.d.ts → match.d.mts} +3 -3
- package/dist/types/{patch.d.ts → patch.d.mts} +3 -3
- package/dist/types/{path.d.ts → path.d.mts} +2 -2
- package/dist/types/{selector.d.ts → selector.d.mts} +1 -1
- package/package.json +29 -23
- package/src/deep.mts +364 -0
- package/src/index.mts +23 -0
- package/src/internal.mts +8 -0
- package/src/match.mts +700 -0
- package/src/patch.mts +262 -0
- package/src/path.mts +430 -0
- package/src/protected.mts +30 -0
- package/src/selector.mts +90 -0
- package/src/tuple.mts +197 -0
- package/dist/main/deep.js +0 -211
- package/dist/main/deep.js.map +0 -1
- package/dist/main/index.js +0 -19
- package/dist/main/index.js.map +0 -1
- package/dist/main/internal.js +0 -9
- package/dist/main/internal.js.map +0 -1
- package/dist/main/match.js +0 -284
- package/dist/main/match.js.map +0 -1
- package/dist/main/patch.js +0 -133
- package/dist/main/patch.js.map +0 -1
- package/dist/main/path.js +0 -126
- package/dist/main/path.js.map +0 -1
- package/dist/main/protected.js +0 -3
- package/dist/main/protected.js.map +0 -1
- package/dist/main/selector.js +0 -40
- package/dist/main/selector.js.map +0 -1
- package/dist/main/tuple.js +0 -164
- package/dist/main/tuple.js.map +0 -1
- package/dist/module/deep.js.map +0 -1
- package/dist/module/index.js.map +0 -1
- package/dist/module/internal.js +0 -4
- package/dist/module/internal.js.map +0 -1
- package/dist/module/match.js.map +0 -1
- package/dist/module/patch.js.map +0 -1
- package/dist/module/path.js.map +0 -1
- package/dist/module/protected.js +0 -2
- package/dist/module/protected.js.map +0 -1
- package/dist/module/selector.js.map +0 -1
- package/dist/module/tuple.js.map +0 -1
- package/dist/types/internal.d.ts +0 -7
- package/src/internal.ts +0 -8
- /package/{src/protected.ts → dist/bun/protected.mts} +0 -0
- /package/{src/tuple.ts → dist/bun/tuple.mts} +0 -0
- /package/dist/types/{protected.d.ts → protected.d.mts} +0 -0
- /package/dist/types/{tuple.d.ts → tuple.d.mts} +0 -0
package/src/match.mts
ADDED
|
@@ -0,0 +1,700 @@
|
|
|
1
|
+
import {
|
|
2
|
+
type IsAnyFunc,
|
|
3
|
+
type IsArray,
|
|
4
|
+
isPlainObj,
|
|
5
|
+
type IsPlainObj,
|
|
6
|
+
type NotIterable,
|
|
7
|
+
} from '@rimbu/base';
|
|
8
|
+
import type { Protected } from './internal.mjs';
|
|
9
|
+
import type { Tuple } from './tuple.mjs';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* The type to determine the allowed input values for the `match` function.
|
|
13
|
+
* @typeparam T - the type of value to match
|
|
14
|
+
* @typeparam C - utility type
|
|
15
|
+
*/
|
|
16
|
+
export type Match<T, C extends Partial<T> = Partial<T>> = Match.Entry<
|
|
17
|
+
T,
|
|
18
|
+
C,
|
|
19
|
+
T,
|
|
20
|
+
T
|
|
21
|
+
>;
|
|
22
|
+
|
|
23
|
+
export namespace Match {
|
|
24
|
+
/**
|
|
25
|
+
* Determines the various allowed match types for given type `T`.
|
|
26
|
+
* @typeparam T - the input value type
|
|
27
|
+
* @typeparam C - utility type
|
|
28
|
+
* @typeparam P - the parent type
|
|
29
|
+
* @typeparam R - the root object type
|
|
30
|
+
*/
|
|
31
|
+
export type Entry<T, C, P, R> = IsAnyFunc<T> extends true
|
|
32
|
+
? // function can only be directly matched
|
|
33
|
+
T
|
|
34
|
+
: IsPlainObj<T> extends true
|
|
35
|
+
? // determine allowed match values for object
|
|
36
|
+
Match.WithResult<T, P, R, Match.Obj<T, C, P, R>>
|
|
37
|
+
: IsArray<T> extends true
|
|
38
|
+
? // determine allowed match values for array or tuple
|
|
39
|
+
| Match.Arr<T, C, P, R>
|
|
40
|
+
| Match.Entry<T[number & keyof T], C[number & keyof C], P, R>[]
|
|
41
|
+
| Match.Func<
|
|
42
|
+
T,
|
|
43
|
+
P,
|
|
44
|
+
R,
|
|
45
|
+
| Match.Arr<T, C, P, R>
|
|
46
|
+
| Match.Entry<T[number & keyof T], C[number & keyof C], P, R>[]
|
|
47
|
+
>
|
|
48
|
+
: // only accept values with same interface
|
|
49
|
+
Match.WithResult<T, P, R, { [K in keyof C]: C[K & keyof T] }>;
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* The type that determines allowed matchers for objects.
|
|
53
|
+
* @typeparam T - the input value type
|
|
54
|
+
* @typeparam C - utility type
|
|
55
|
+
* @typeparam P - the parent type
|
|
56
|
+
* @typeparam R - the root object type
|
|
57
|
+
*/
|
|
58
|
+
export type Obj<T, C, P, R> =
|
|
59
|
+
| Match.ObjProps<T, C, R>
|
|
60
|
+
| Match.CompoundForObj<T, C, P, R>;
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* The type to determine allowed matchers for object properties.
|
|
64
|
+
* @typeparam T - the input value type
|
|
65
|
+
* @typeparam C - utility type
|
|
66
|
+
* @typeparam R - the root object type
|
|
67
|
+
*/
|
|
68
|
+
export type ObjProps<T, C, R> = {
|
|
69
|
+
[K in keyof C]?: K extends keyof T ? Match.Entry<T[K], C[K], T, R> : never;
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* The type that determines allowed matchers for arrays/tuples.
|
|
74
|
+
* @typeparam T - the input value type
|
|
75
|
+
* @typeparam C - utility type
|
|
76
|
+
* @typeparam P - the parent type
|
|
77
|
+
* @typeparam R - the root object type
|
|
78
|
+
*/
|
|
79
|
+
export type Arr<T, C, P, R> =
|
|
80
|
+
| C
|
|
81
|
+
| Match.CompoundForArr<T, C, P, R>
|
|
82
|
+
| Match.TraversalForArr<T, C, R>
|
|
83
|
+
| (Match.TupIndices<T, C, R> & {
|
|
84
|
+
[K in Match.CompoundType | Match.ArrayTraversalType]?: never;
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* A type that either directly results in result type `S` or is a function taking the value, parent, and root values, and
|
|
89
|
+
* returns a value of type `S`.
|
|
90
|
+
* @typeparam T - the input value type
|
|
91
|
+
* @typeparam P - the parent type
|
|
92
|
+
* @typeparam R - the root object type
|
|
93
|
+
* @typeparam S - the result type
|
|
94
|
+
*/
|
|
95
|
+
export type WithResult<T, P, R, S> = S | Match.Func<T, P, R, S>;
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Type used to determine the allowed function types. Always includes booleans.
|
|
99
|
+
* @typeparam T - the input value type
|
|
100
|
+
* @typeparam P - the parent type
|
|
101
|
+
* @typeparam R - the root object type
|
|
102
|
+
* @typeparam S - the allowed return value type
|
|
103
|
+
*/
|
|
104
|
+
export type Func<T, P, R, S> = (
|
|
105
|
+
current: Protected<T>,
|
|
106
|
+
parent: Protected<P>,
|
|
107
|
+
root: Protected<R>
|
|
108
|
+
) => boolean | S;
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Type used to indicate an object containing matches for tuple indices.
|
|
112
|
+
* @typeparam T - the input value type
|
|
113
|
+
* @typeparam C - utility type
|
|
114
|
+
* @typeparam R - the root object type
|
|
115
|
+
*/
|
|
116
|
+
export type TupIndices<T, C, R> = {
|
|
117
|
+
[K in Tuple.KeysOf<C>]?: Match.Entry<T[K & keyof T], C[K], T, R>;
|
|
118
|
+
} & NotIterable;
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Compound keys used to indicate the type of compound.
|
|
122
|
+
*/
|
|
123
|
+
export type CompoundType = 'every' | 'some' | 'none' | 'single';
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Keys used to indicate an array match traversal.
|
|
127
|
+
*/
|
|
128
|
+
export type ArrayTraversalType = `${CompoundType}Item`;
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Compount matcher for objects, can only be an array staring with a compound type keyword.
|
|
132
|
+
* @typeparam T - the input value type
|
|
133
|
+
* @typeparam C - utility type
|
|
134
|
+
* @typeparam P - the parent type
|
|
135
|
+
* @typeparam R - the root object type
|
|
136
|
+
*/
|
|
137
|
+
export type CompoundForObj<T, C, P, R> = [
|
|
138
|
+
Match.CompoundType,
|
|
139
|
+
...Match.Entry<T, C, P, R>[]
|
|
140
|
+
];
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Defines an object containing exactly one `CompoundType` key, having an array of matchers.
|
|
144
|
+
* @typeparam T - the input value type
|
|
145
|
+
* @typeparam C - utility type
|
|
146
|
+
* @typeparam P - the parent type
|
|
147
|
+
* @typeparam R - the root object type
|
|
148
|
+
*/
|
|
149
|
+
export type CompoundForArr<T, C, P, R> = {
|
|
150
|
+
[K in Match.CompoundType]: {
|
|
151
|
+
[K2 in Match.CompoundType]?: K2 extends K
|
|
152
|
+
? Match.Entry<T, C, P, R>[]
|
|
153
|
+
: never;
|
|
154
|
+
};
|
|
155
|
+
}[Match.CompoundType];
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Defines an object containing exactly one `TraversalType` key, having a matcher for the array element type.
|
|
159
|
+
* @typeparam T - the input value type
|
|
160
|
+
* @typeparam C - utility type
|
|
161
|
+
* @typeparam R - the root object type
|
|
162
|
+
*/
|
|
163
|
+
export type TraversalForArr<T, C, R> = {
|
|
164
|
+
[K in Match.ArrayTraversalType]: {
|
|
165
|
+
[K2 in Match.ArrayTraversalType]?: K2 extends K
|
|
166
|
+
? Match.Entry<T[number & keyof T], C[number & keyof C], T, R>
|
|
167
|
+
: never;
|
|
168
|
+
};
|
|
169
|
+
}[Match.ArrayTraversalType];
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Utility type for collecting match failure reasons
|
|
173
|
+
*/
|
|
174
|
+
export type FailureLog = string[];
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Returns true if the given `value` object matches the given `matcher`, false otherwise.
|
|
179
|
+
* @typeparam T - the input value type
|
|
180
|
+
* @typeparam C - utility type
|
|
181
|
+
* @param source - the value to match (should be a plain object)
|
|
182
|
+
* @param matcher - a matcher object or a function taking the matcher API and returning a match object
|
|
183
|
+
* @param failureLog - (optional) a string array that can be passed to collect reasons why the match failed
|
|
184
|
+
* @example
|
|
185
|
+
* ```ts
|
|
186
|
+
* const input = { a: 1, b: { c: true, d: 'a' } }
|
|
187
|
+
* match(input, { a: 1 }) // => true
|
|
188
|
+
* match(input, { a: 2 }) // => false
|
|
189
|
+
* match(input, { a: (v) => v > 10 }) // => false
|
|
190
|
+
* match(input, { b: { c: true }}) // => true
|
|
191
|
+
* match(input, (['every', { a: (v) => v > 0 }, { b: { c: true } }]) // => true
|
|
192
|
+
* match(input, { b: { c: (v, parent, root) => v && parent.d.length > 0 && root.a > 0 } })
|
|
193
|
+
* // => true
|
|
194
|
+
* ```
|
|
195
|
+
*/
|
|
196
|
+
export function match<T, C extends Partial<T> = Partial<T>>(
|
|
197
|
+
source: T,
|
|
198
|
+
matcher: Match<T, C>,
|
|
199
|
+
failureLog?: Match.FailureLog
|
|
200
|
+
): boolean {
|
|
201
|
+
return matchEntry(source, source, source, matcher as any, failureLog);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* Match a generic match entry against the given source.
|
|
206
|
+
*/
|
|
207
|
+
function matchEntry<T, C, P, R>(
|
|
208
|
+
source: T,
|
|
209
|
+
parent: P,
|
|
210
|
+
root: R,
|
|
211
|
+
matcher: Match.Entry<T, C, P, R>,
|
|
212
|
+
failureLog?: Match.FailureLog
|
|
213
|
+
): boolean {
|
|
214
|
+
if (Object.is(source, matcher)) {
|
|
215
|
+
// value and target are exactly the same, always will be true
|
|
216
|
+
return true;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
if (matcher === null || matcher === undefined) {
|
|
220
|
+
// these matchers can only be direct matches, and previously it was determined that
|
|
221
|
+
// they are not equal
|
|
222
|
+
failureLog?.push(
|
|
223
|
+
`value ${JSON.stringify(source)} did not match matcher ${matcher}`
|
|
224
|
+
);
|
|
225
|
+
|
|
226
|
+
return false;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
if (typeof source === 'function') {
|
|
230
|
+
// function source values can only be directly matched
|
|
231
|
+
const result = Object.is(source, matcher);
|
|
232
|
+
|
|
233
|
+
if (!result) {
|
|
234
|
+
failureLog?.push(
|
|
235
|
+
`both value and matcher are functions, but they do not have the same reference`
|
|
236
|
+
);
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
return result;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
if (typeof matcher === 'function') {
|
|
243
|
+
// resolve match function first
|
|
244
|
+
const matcherResult = matcher(source, parent, root);
|
|
245
|
+
|
|
246
|
+
if (typeof matcherResult === 'boolean') {
|
|
247
|
+
// function resulted in a direct match result
|
|
248
|
+
|
|
249
|
+
if (!matcherResult) {
|
|
250
|
+
failureLog?.push(
|
|
251
|
+
`function matcher returned false for value ${JSON.stringify(source)}`
|
|
252
|
+
);
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
return matcherResult;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
// function resulted in a value that needs to be further matched
|
|
259
|
+
return matchEntry(source, parent, root, matcherResult, failureLog);
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
if (isPlainObj(source)) {
|
|
263
|
+
// source ia a plain object, can be partially matched
|
|
264
|
+
return matchPlainObj(source, parent, root, matcher as any, failureLog);
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
if (Array.isArray(source)) {
|
|
268
|
+
// source is an array
|
|
269
|
+
return matchArr(source, parent, root, matcher as any, failureLog);
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
// already determined above that the source and matcher are not equal
|
|
273
|
+
|
|
274
|
+
failureLog?.push(
|
|
275
|
+
`value ${JSON.stringify(
|
|
276
|
+
source
|
|
277
|
+
)} does not match given matcher ${JSON.stringify(matcher)}`
|
|
278
|
+
);
|
|
279
|
+
|
|
280
|
+
return false;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
/**
|
|
284
|
+
* Match an array matcher against the given source.
|
|
285
|
+
*/
|
|
286
|
+
function matchArr<T extends any[], C, P, R>(
|
|
287
|
+
source: T,
|
|
288
|
+
parent: P,
|
|
289
|
+
root: R,
|
|
290
|
+
matcher: Match.Arr<T, C, P, R>,
|
|
291
|
+
failureLog?: Match.FailureLog
|
|
292
|
+
): boolean {
|
|
293
|
+
if (Array.isArray(matcher)) {
|
|
294
|
+
// directly compare array contents
|
|
295
|
+
const length = source.length;
|
|
296
|
+
|
|
297
|
+
if (length !== matcher.length) {
|
|
298
|
+
// if lengths not equal, arrays are not equal
|
|
299
|
+
|
|
300
|
+
failureLog?.push(
|
|
301
|
+
`array lengths are not equal: value length ${source.length} !== matcher length ${matcher.length}`
|
|
302
|
+
);
|
|
303
|
+
|
|
304
|
+
return false;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
// loop over arrays, matching every value
|
|
308
|
+
let index = -1;
|
|
309
|
+
while (++index < length) {
|
|
310
|
+
if (
|
|
311
|
+
!matchEntry(source[index], source, root, matcher[index], failureLog)
|
|
312
|
+
) {
|
|
313
|
+
// item did not match, return false
|
|
314
|
+
|
|
315
|
+
failureLog?.push(
|
|
316
|
+
`index ${index} does not match with value ${JSON.stringify(
|
|
317
|
+
source[index]
|
|
318
|
+
)} and matcher ${matcher[index]}`
|
|
319
|
+
);
|
|
320
|
+
|
|
321
|
+
return false;
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
// all items are equal
|
|
326
|
+
return true;
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
// matcher is plain object
|
|
330
|
+
|
|
331
|
+
if (typeof matcher === 'object' && null !== matcher) {
|
|
332
|
+
if (`every` in matcher) {
|
|
333
|
+
return matchCompound(
|
|
334
|
+
source,
|
|
335
|
+
parent,
|
|
336
|
+
root,
|
|
337
|
+
['every', ...(matcher.every as any)],
|
|
338
|
+
failureLog
|
|
339
|
+
);
|
|
340
|
+
}
|
|
341
|
+
if (`some` in matcher) {
|
|
342
|
+
return matchCompound(
|
|
343
|
+
source,
|
|
344
|
+
parent,
|
|
345
|
+
root,
|
|
346
|
+
['some', ...(matcher.some as any)],
|
|
347
|
+
failureLog
|
|
348
|
+
);
|
|
349
|
+
}
|
|
350
|
+
if (`none` in matcher) {
|
|
351
|
+
return matchCompound(
|
|
352
|
+
source,
|
|
353
|
+
parent,
|
|
354
|
+
root,
|
|
355
|
+
['none', ...(matcher.none as any)],
|
|
356
|
+
failureLog
|
|
357
|
+
);
|
|
358
|
+
}
|
|
359
|
+
if (`single` in matcher) {
|
|
360
|
+
return matchCompound(
|
|
361
|
+
source,
|
|
362
|
+
parent,
|
|
363
|
+
root,
|
|
364
|
+
['single', ...(matcher.single as any)],
|
|
365
|
+
failureLog
|
|
366
|
+
);
|
|
367
|
+
}
|
|
368
|
+
if (`someItem` in matcher) {
|
|
369
|
+
return matchTraversal(
|
|
370
|
+
source,
|
|
371
|
+
root,
|
|
372
|
+
'someItem',
|
|
373
|
+
matcher.someItem as any,
|
|
374
|
+
failureLog
|
|
375
|
+
);
|
|
376
|
+
}
|
|
377
|
+
if (`everyItem` in matcher) {
|
|
378
|
+
return matchTraversal(
|
|
379
|
+
source,
|
|
380
|
+
root,
|
|
381
|
+
'everyItem',
|
|
382
|
+
matcher.everyItem as any,
|
|
383
|
+
failureLog
|
|
384
|
+
);
|
|
385
|
+
}
|
|
386
|
+
if (`noneItem` in matcher) {
|
|
387
|
+
return matchTraversal(
|
|
388
|
+
source,
|
|
389
|
+
root,
|
|
390
|
+
'noneItem',
|
|
391
|
+
matcher.noneItem as any,
|
|
392
|
+
failureLog
|
|
393
|
+
);
|
|
394
|
+
}
|
|
395
|
+
if (`singleItem` in matcher) {
|
|
396
|
+
return matchTraversal(
|
|
397
|
+
source,
|
|
398
|
+
root,
|
|
399
|
+
'singleItem',
|
|
400
|
+
matcher.singleItem as any,
|
|
401
|
+
failureLog
|
|
402
|
+
);
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
// matcher is plain object with index keys
|
|
407
|
+
|
|
408
|
+
for (const index in matcher as any) {
|
|
409
|
+
const matcherAtIndex = (matcher as any)[index];
|
|
410
|
+
|
|
411
|
+
if (!(index in source)) {
|
|
412
|
+
// source does not have item at given index
|
|
413
|
+
|
|
414
|
+
failureLog?.push(
|
|
415
|
+
`index ${index} does not exist in source ${JSON.stringify(
|
|
416
|
+
source
|
|
417
|
+
)} but should match matcher ${JSON.stringify(matcherAtIndex)}`
|
|
418
|
+
);
|
|
419
|
+
|
|
420
|
+
return false;
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
// match the source item at the given index
|
|
424
|
+
const result = matchEntry(
|
|
425
|
+
(source as any)[index],
|
|
426
|
+
source,
|
|
427
|
+
root,
|
|
428
|
+
matcherAtIndex,
|
|
429
|
+
failureLog
|
|
430
|
+
);
|
|
431
|
+
|
|
432
|
+
if (!result) {
|
|
433
|
+
// item did not match
|
|
434
|
+
|
|
435
|
+
failureLog?.push(
|
|
436
|
+
`index ${index} does not match with value ${JSON.stringify(
|
|
437
|
+
(source as any)[index]
|
|
438
|
+
)} and matcher ${JSON.stringify(matcherAtIndex)}`
|
|
439
|
+
);
|
|
440
|
+
|
|
441
|
+
return false;
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
// all items match
|
|
446
|
+
|
|
447
|
+
return true;
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
/**
|
|
451
|
+
* Match an object matcher against the given source.
|
|
452
|
+
*/
|
|
453
|
+
function matchPlainObj<T extends object, C, P, R>(
|
|
454
|
+
source: T,
|
|
455
|
+
parent: P,
|
|
456
|
+
root: R,
|
|
457
|
+
matcher: Match.Obj<T, C, P, R>,
|
|
458
|
+
failureLog?: Match.FailureLog
|
|
459
|
+
): boolean {
|
|
460
|
+
if (Array.isArray(matcher)) {
|
|
461
|
+
// the matcher is of compound type
|
|
462
|
+
return matchCompound(source, parent, root, matcher as any, failureLog);
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
// partial object props matcher
|
|
466
|
+
|
|
467
|
+
for (const key in matcher) {
|
|
468
|
+
if (!(key in source)) {
|
|
469
|
+
// the source does not have the given key
|
|
470
|
+
|
|
471
|
+
failureLog?.push(
|
|
472
|
+
`key ${key} is specified in matcher but not present in value ${JSON.stringify(
|
|
473
|
+
source
|
|
474
|
+
)}`
|
|
475
|
+
);
|
|
476
|
+
|
|
477
|
+
return false;
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
// match the source value at the given key with the matcher at given key
|
|
481
|
+
const result = matchEntry(
|
|
482
|
+
(source as any)[key],
|
|
483
|
+
source,
|
|
484
|
+
root,
|
|
485
|
+
matcher[key],
|
|
486
|
+
failureLog
|
|
487
|
+
);
|
|
488
|
+
|
|
489
|
+
if (!result) {
|
|
490
|
+
failureLog?.push(
|
|
491
|
+
`key ${key} does not match in value ${JSON.stringify(
|
|
492
|
+
(source as any)[key]
|
|
493
|
+
)} with matcher ${JSON.stringify(matcher[key])}`
|
|
494
|
+
);
|
|
495
|
+
return false;
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
// all properties match
|
|
500
|
+
|
|
501
|
+
return true;
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
/**
|
|
505
|
+
* Match a compound matcher against the given source.
|
|
506
|
+
*/
|
|
507
|
+
function matchCompound<T, C, P, R>(
|
|
508
|
+
source: T,
|
|
509
|
+
parent: P,
|
|
510
|
+
root: R,
|
|
511
|
+
compound: [Match.CompoundType, ...Match.Entry<T, C, P, R>[]],
|
|
512
|
+
failureLog?: Match.FailureLog
|
|
513
|
+
): boolean {
|
|
514
|
+
// first item indicates compound match type
|
|
515
|
+
const matchType = compound[0];
|
|
516
|
+
|
|
517
|
+
const length = compound.length;
|
|
518
|
+
|
|
519
|
+
// start at index 1
|
|
520
|
+
let index = 0;
|
|
521
|
+
|
|
522
|
+
type Entry = Match.Entry<T, C, P, R>;
|
|
523
|
+
|
|
524
|
+
switch (matchType) {
|
|
525
|
+
case 'every': {
|
|
526
|
+
while (++index < length) {
|
|
527
|
+
// if any item does not match, return false
|
|
528
|
+
const result = matchEntry(
|
|
529
|
+
source,
|
|
530
|
+
parent,
|
|
531
|
+
root,
|
|
532
|
+
compound[index] as Entry,
|
|
533
|
+
failureLog
|
|
534
|
+
);
|
|
535
|
+
|
|
536
|
+
if (!result) {
|
|
537
|
+
failureLog?.push(
|
|
538
|
+
`in compound "every": match at index ${index} failed`
|
|
539
|
+
);
|
|
540
|
+
|
|
541
|
+
return false;
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
return true;
|
|
546
|
+
}
|
|
547
|
+
case 'none': {
|
|
548
|
+
// if any item matches, return false
|
|
549
|
+
while (++index < length) {
|
|
550
|
+
const result = matchEntry(
|
|
551
|
+
source,
|
|
552
|
+
parent,
|
|
553
|
+
root,
|
|
554
|
+
compound[index] as Entry,
|
|
555
|
+
failureLog
|
|
556
|
+
);
|
|
557
|
+
|
|
558
|
+
if (result) {
|
|
559
|
+
failureLog?.push(
|
|
560
|
+
`in compound "none": match at index ${index} succeeded`
|
|
561
|
+
);
|
|
562
|
+
|
|
563
|
+
return false;
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
return true;
|
|
568
|
+
}
|
|
569
|
+
case 'single': {
|
|
570
|
+
// if not exactly one item matches, return false
|
|
571
|
+
let onePassed = false;
|
|
572
|
+
|
|
573
|
+
while (++index < length) {
|
|
574
|
+
const result = matchEntry(
|
|
575
|
+
source,
|
|
576
|
+
parent,
|
|
577
|
+
root,
|
|
578
|
+
compound[index] as Entry,
|
|
579
|
+
failureLog
|
|
580
|
+
);
|
|
581
|
+
|
|
582
|
+
if (result) {
|
|
583
|
+
if (onePassed) {
|
|
584
|
+
failureLog?.push(
|
|
585
|
+
`in compound "single": multiple matches succeeded`
|
|
586
|
+
);
|
|
587
|
+
|
|
588
|
+
return false;
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
onePassed = true;
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
if (!onePassed) {
|
|
596
|
+
failureLog?.push(`in compound "single": no matches succeeded`);
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
return onePassed;
|
|
600
|
+
}
|
|
601
|
+
case 'some': {
|
|
602
|
+
// if any item matches, return true
|
|
603
|
+
while (++index < length) {
|
|
604
|
+
const result = matchEntry(
|
|
605
|
+
source,
|
|
606
|
+
parent,
|
|
607
|
+
root,
|
|
608
|
+
compound[index] as Entry,
|
|
609
|
+
failureLog
|
|
610
|
+
);
|
|
611
|
+
|
|
612
|
+
if (result) {
|
|
613
|
+
return true;
|
|
614
|
+
}
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
failureLog?.push(`in compound "some": no matches succeeded`);
|
|
618
|
+
|
|
619
|
+
return false;
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
function matchTraversal<T extends any[], C extends any[], R>(
|
|
625
|
+
source: T,
|
|
626
|
+
root: R,
|
|
627
|
+
matchType: Match.ArrayTraversalType,
|
|
628
|
+
matcher: Match.Entry<T[keyof T], C[keyof C], T, R>,
|
|
629
|
+
failureLog?: Match.FailureLog
|
|
630
|
+
): boolean {
|
|
631
|
+
let index = -1;
|
|
632
|
+
const length = source.length;
|
|
633
|
+
|
|
634
|
+
switch (matchType) {
|
|
635
|
+
case 'someItem': {
|
|
636
|
+
while (++index < length) {
|
|
637
|
+
if (matchEntry(source[index], source, root, matcher, failureLog)) {
|
|
638
|
+
return true;
|
|
639
|
+
}
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
failureLog?.push(
|
|
643
|
+
`in array traversal "someItem": no items matched given matcher`
|
|
644
|
+
);
|
|
645
|
+
|
|
646
|
+
return false;
|
|
647
|
+
}
|
|
648
|
+
case 'everyItem': {
|
|
649
|
+
while (++index < length) {
|
|
650
|
+
if (!matchEntry(source[index], source, root, matcher, failureLog)) {
|
|
651
|
+
failureLog?.push(
|
|
652
|
+
`in array traversal "everyItem": at least one item did not match given matcher`
|
|
653
|
+
);
|
|
654
|
+
return false;
|
|
655
|
+
}
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
return true;
|
|
659
|
+
}
|
|
660
|
+
case 'noneItem': {
|
|
661
|
+
while (++index < length) {
|
|
662
|
+
if (matchEntry(source[index], source, root, matcher, failureLog)) {
|
|
663
|
+
failureLog?.push(
|
|
664
|
+
`in array traversal "noneItem": at least one item matched given matcher`
|
|
665
|
+
);
|
|
666
|
+
return false;
|
|
667
|
+
}
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
return true;
|
|
671
|
+
}
|
|
672
|
+
case 'singleItem': {
|
|
673
|
+
let singleMatched = false;
|
|
674
|
+
|
|
675
|
+
while (++index < length) {
|
|
676
|
+
if (matchEntry(source[index], source, root, matcher, failureLog)) {
|
|
677
|
+
if (singleMatched) {
|
|
678
|
+
failureLog?.push(
|
|
679
|
+
`in array traversal "singleItem": more than one item matched given matcher`
|
|
680
|
+
);
|
|
681
|
+
|
|
682
|
+
return false;
|
|
683
|
+
}
|
|
684
|
+
|
|
685
|
+
singleMatched = true;
|
|
686
|
+
}
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
if (!singleMatched) {
|
|
690
|
+
failureLog?.push(
|
|
691
|
+
`in array traversal "singleItem": no item matched given matcher`
|
|
692
|
+
);
|
|
693
|
+
|
|
694
|
+
return false;
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
return true;
|
|
698
|
+
}
|
|
699
|
+
}
|
|
700
|
+
}
|