@rimbu/deep 0.11.2 → 0.12.0

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 (53) hide show
  1. package/dist/main/deep.js +211 -0
  2. package/dist/main/deep.js.map +1 -0
  3. package/dist/main/index.js +7 -9
  4. package/dist/main/index.js.map +1 -1
  5. package/dist/main/internal.js +6 -4
  6. package/dist/main/internal.js.map +1 -1
  7. package/dist/main/match.js +160 -199
  8. package/dist/main/match.js.map +1 -1
  9. package/dist/main/patch.js +101 -125
  10. package/dist/main/patch.js.map +1 -1
  11. package/dist/main/path.js +109 -62
  12. package/dist/main/path.js.map +1 -1
  13. package/dist/main/protected.js +0 -19
  14. package/dist/main/protected.js.map +1 -1
  15. package/dist/main/selector.js +40 -0
  16. package/dist/main/selector.js.map +1 -0
  17. package/dist/main/tuple.js.map +1 -1
  18. package/dist/module/deep.js +192 -0
  19. package/dist/module/deep.js.map +1 -0
  20. package/dist/module/index.js +9 -1
  21. package/dist/module/index.js.map +1 -1
  22. package/dist/module/internal.js +4 -4
  23. package/dist/module/internal.js.map +1 -1
  24. package/dist/module/match.js +159 -179
  25. package/dist/module/match.js.map +1 -1
  26. package/dist/module/patch.js +91 -112
  27. package/dist/module/patch.js.map +1 -1
  28. package/dist/module/path.js +99 -44
  29. package/dist/module/path.js.map +1 -1
  30. package/dist/module/protected.js +1 -17
  31. package/dist/module/protected.js.map +1 -1
  32. package/dist/module/selector.js +36 -0
  33. package/dist/module/selector.js.map +1 -0
  34. package/dist/module/tuple.js.map +1 -1
  35. package/dist/types/deep.d.ts +284 -0
  36. package/dist/types/index.d.ts +10 -1
  37. package/dist/types/internal.d.ts +7 -4
  38. package/dist/types/match.d.ts +74 -80
  39. package/dist/types/patch.d.ts +57 -50
  40. package/dist/types/path.d.ts +177 -34
  41. package/dist/types/protected.d.ts +1 -16
  42. package/dist/types/selector.d.ts +47 -0
  43. package/dist/types/tuple.d.ts +10 -0
  44. package/package.json +4 -4
  45. package/src/deep.ts +362 -0
  46. package/src/index.ts +14 -10
  47. package/src/internal.ts +7 -4
  48. package/src/match.ts +396 -212
  49. package/src/patch.ts +173 -176
  50. package/src/path.ts +400 -74
  51. package/src/protected.ts +14 -25
  52. package/src/selector.ts +90 -0
  53. package/src/tuple.ts +12 -0
package/src/match.ts CHANGED
@@ -1,318 +1,502 @@
1
1
  import {
2
- RimbuError,
3
- type IsPlainObj,
4
- type PlainObj,
2
+ IsAnyFunc,
3
+ IsArray,
5
4
  isPlainObj,
6
- isIterable,
5
+ IsPlainObj,
6
+ NotIterable,
7
7
  } from '@rimbu/base';
8
-
9
8
  import type { Protected } from './internal';
9
+ import type { Tuple } from './tuple';
10
10
 
11
11
  /**
12
- * The type to determine the allowed input values for the `match` functions.
12
+ * The type to determine the allowed input values for the `match` function.
13
13
  * @typeparam T - the type of value to match
14
+ * @typeparam C - utility type
14
15
  */
15
- export type Match<T> = Match.Options<T, T>;
16
+ export type Match<T, C extends Partial<T> = Partial<T>> = Match.Entry<
17
+ T,
18
+ C,
19
+ T,
20
+ T
21
+ >;
16
22
 
17
23
  export namespace Match {
18
24
  /**
19
- * The types of supported match input.
20
- * @typeparam T - the type of value to match
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 parant type
21
29
  * @typeparam R - the root object type
22
30
  */
23
- export type Options<T, R> =
24
- | Every<T, R>
25
- | Some<T, R>
26
- | None<T, R>
27
- | Single<T, R>
28
- | Match.Obj<T, R>;
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> | Match.Func<T, P, R, Match.Arr<T, C, P, R>>
40
+ : // only accept values with same interface
41
+ Match.WithResult<T, P, R, { [K in keyof C]: C[K & keyof T] }>;
29
42
 
30
43
  /**
31
- * The type to determine allowed matchers for objects.
32
- * @typeparam T - the type of value to match
44
+ * The type that determines allowed matchers for objects.
45
+ * @typeparam T - the input value type
46
+ * @typeparam C - utility type
47
+ * @typeparam P - the parant type
33
48
  * @typeparam R - the root object type
34
49
  */
35
- export type Obj<T, R> = {
36
- [K in keyof T]?:
37
- | Match.ObjItem<T[K], R>
38
- | ((
39
- current: Protected<T[K]>,
40
- parent: Protected<T>,
41
- root: Protected<R>
42
- ) => boolean | Match.ObjItem<T[K], R>);
43
- };
50
+ export type Obj<T, C, P, R> =
51
+ | Match.ObjProps<T, C, R>
52
+ | Match.CompoundForObj<T, C, P, R>;
44
53
 
45
54
  /**
46
55
  * The type to determine allowed matchers for object properties.
47
- * @typeparam T - the type of value to match
56
+ * @typeparam T - the input value type
57
+ * @typeparam C - utility type
48
58
  * @typeparam R - the root object type
49
59
  */
50
- export type ObjItem<T, R> = IsPlainObj<T> extends true
51
- ? Match.Options<T, R>
52
- : T extends Iterable<infer U>
53
- ? T | Iterable<U>
54
- : T;
60
+ export type ObjProps<T, C, R> = {
61
+ [K in keyof C]?: K extends keyof T ? Match.Entry<T[K], C[K], T, R> : never;
62
+ };
55
63
 
56
64
  /**
57
- * Returns a matcher that returns true if every given `matchItem` matches the given value.
58
- * @typeparam T - the type of value to match
65
+ * The type that determines allowed matchers for arrays/tuples.
66
+ * @typeparam T - the input value type
67
+ * @typeparam C - utility type
68
+ * @typeparam P - the parant type
59
69
  * @typeparam R - the root object type
60
- * @typeparam Q - a utility type for the matcher
61
- * @param matchItems - the match specifications to test
62
- * @example
63
- * ```ts
64
- * const input = { a: 1, b: { c: true, d: 'a' } }
65
- * match(input, Match.every({ a: 1, { c: true } } )) // => true
66
- * match(input, Match.every({ a: 1, { c: false } } )) // => false
67
- * ```
68
70
  */
69
- export function every<T, R, Q extends T = T>(
70
- ...matchItems: Match.Options<Q, R>[]
71
- ): Every<T, R, Q> {
72
- return new Every(matchItems);
73
- }
71
+ export type Arr<T, C, P, R> =
72
+ | C
73
+ | Match.CompoundForArr<T, C, P, R>
74
+ | (Match.TupIndices<T, C, R> & { [K in Match.CompoundType]?: never });
75
+
76
+ export type WithResult<T, P, R, S> = S | Match.Func<T, P, R, S>;
77
+
74
78
  /**
75
- * Returns a matcher that returns true if at least one of given `matchItem` matches the given value.
76
- * @typeparam T - the type of value to match
79
+ * Type used to determine the allowed function types. Always includes booleans.
80
+ * @typeparam T - the input value type
81
+ * @typeparam P - the parant type
77
82
  * @typeparam R - the root object type
78
- * @typeparam Q - a utility type for the matcher
79
- * @param matchItems - the match specifications to test
80
- * @example
81
- * ```ts
82
- * const input = { a: 1, b: { c: true, d: 'a' } }
83
- * match(input, Match.some({ a: 5, { c: true } } )) // => true
84
- * match(input, Match.some({ a: 5, { c: false } } )) // => false
85
- * ```
83
+ * @typeparam S - the allowed return value type
86
84
  */
87
- export function some<T, R, Q extends T = T>(
88
- ...matchItems: Match.Options<Q, R>[]
89
- ): Some<T, R, Q> {
90
- return new Some(matchItems);
91
- }
85
+ export type Func<T, P, R, S> = (
86
+ current: Protected<T>,
87
+ parent: Protected<P>,
88
+ root: Protected<R>
89
+ ) => boolean | S;
90
+
92
91
  /**
93
- * Returns a matcher that returns true if none of given `matchItem` matches the given value.
94
- * @typeparam T - the type of value to match
92
+ * Type used to indicate an object containing matches for tuple indices.
93
+ * @typeparam T - the input value type
94
+ * @typeparam C - utility type
95
95
  * @typeparam R - the root object type
96
- * @typeparam Q - a utility type for the matcher
97
- * @param matchItems - the match specifications to test
98
- * @example
99
- * ```ts
100
- * const input = { a: 1, b: { c: true, d: 'a' } }
101
- * match(input, Match.none({ a: 5, { c: true } } )) // => false
102
- * match(input, Match.none({ a: 5, { c: false } } )) // => true
103
- * ```
104
96
  */
105
- export function none<T, R, Q extends T = T>(
106
- ...matchItems: Match.Options<Q, R>[]
107
- ): None<T, R, Q> {
108
- return new None(matchItems);
109
- }
97
+ export type TupIndices<T, C, R> = {
98
+ [K in Tuple.KeysOf<C>]?: Match.Entry<T[K & keyof T], C[K], T, R>;
99
+ } & NotIterable;
100
+
110
101
  /**
111
- * Returns a matcher that returns true if exactly one of given `matchItem` matches the given value.
112
- * @typeparam T - the type of value to match
113
- * @typeparam R - the root object type
114
- * @typeparam Q - a utility type for the matcher
115
- * @param matchItems - the match specifications to test
116
- * @example
117
- * ```ts
118
- * const input = { a: 1, b: { c: true, d: 'a' } }
119
- * match(input, Match.single({ a: 1, { c: true } } )) // => false
120
- * match(input, Match.single({ a: 1, { c: false } } )) // => true
121
- * ```
102
+ * Compound keys used to indicate the type of compound.
122
103
  */
123
- export function single<T, R, Q extends T = T>(
124
- ...matchItems: Match.Options<Q, R>[]
125
- ): Single<T, R, Q> {
126
- return new Single(matchItems);
127
- }
104
+ export type CompoundType = 'every' | 'some' | 'none' | 'single';
128
105
 
129
106
  /**
130
- * The functions that are optionally provided to a match function.
107
+ * Compount matcher for objects, can only be an array staring with a compound type keyword.
108
+ * @typeparam T - the input value type
109
+ * @typeparam C - utility type
110
+ * @typeparam P - the parent type
111
+ * @typeparam R - the root object type
131
112
  */
132
- export type Api = typeof Match;
133
- }
134
-
135
- class Every<T, R, Q extends T = T> {
136
- constructor(readonly matchItems: Match.Options<Q, R>[]) {}
137
- }
113
+ export type CompoundForObj<T, C, P, R> = [
114
+ Match.CompoundType,
115
+ ...Match.Entry<T, C, P, R>[]
116
+ ];
138
117
 
139
- class Some<T, R, Q extends T = T> {
140
- constructor(readonly matchItems: Match.Options<Q, R>[]) {}
141
- }
142
-
143
- class None<T, R, Q extends T = T> {
144
- constructor(readonly matchItems: Match.Options<Q, R>[]) {}
145
- }
118
+ /**
119
+ * Defines an object containing exactly one `CompoundType` key, having an array of matchers.
120
+ * @typeparam T - the input value type
121
+ * @typeparam C - utility type
122
+ * @typeparam P - the parent type
123
+ * @typeparam R - the root object type
124
+ */
125
+ export type CompoundForArr<T, C, P, R> = {
126
+ [K in CompoundType]: {
127
+ [K2 in CompoundType]?: K2 extends K ? Match.Entry<T, C, P, R>[] : never;
128
+ };
129
+ }[CompoundType];
146
130
 
147
- class Single<T, R, Q extends T = T> {
148
- constructor(readonly matchItems: Match.Options<Q, R>[]) {}
131
+ /**
132
+ * Utility type for collecting errors
133
+ */
134
+ export type ErrorCollector = string[] | undefined;
149
135
  }
150
136
 
151
137
  /**
152
138
  * Returns true if the given `value` object matches the given `matcher`, false otherwise.
153
139
  * @typeparam T - the input value type
154
- * @param value - the value to match (should be a plain object)
140
+ * @typeparam C - utility type
141
+ * @param source - the value to match (should be a plain object)
155
142
  * @param matcher - a matcher object or a function taking the matcher API and returning a match object
143
+ * @param errorCollector - (optional) a string array that can be passed to collect reasons why the match failed
156
144
  * @example
157
145
  * ```ts
158
146
  * const input = { a: 1, b: { c: true, d: 'a' } }
159
147
  * match(input, { a: 1 }) // => true
160
148
  * match(input, { a: 2 }) // => false
161
- * match(input, { a: v => v > 10 }) // => false
149
+ * match(input, { a: (v) => v > 10 }) // => false
162
150
  * match(input, { b: { c: true }}) // => true
163
- * match(input, ({ every }) => every({ a: v => v > 0 }, { b: { c: true } } )) // => true
151
+ * match(input, (['every', { a: (v) => v > 0 }, { b: { c: true } }]) // => true
164
152
  * match(input, { b: { c: (v, parent, root) => v && parent.d.length > 0 && root.a > 0 } })
165
153
  * // => true
166
154
  * ```
167
155
  */
168
- export function match<T>(
169
- value: T & PlainObj<T>,
170
- matcher: Match<T> | ((matchApi: Match.Api) => Match<T>)
156
+ export function match<T, C extends Partial<T> = Partial<T>>(
157
+ source: T,
158
+ matcher: Match<T, C>,
159
+ errorCollector: Match.ErrorCollector = undefined
171
160
  ): boolean {
172
- if (matcher instanceof Function) {
173
- return matchOptions(value, value, matcher(Match));
174
- }
175
-
176
- return matchOptions(value, value, matcher);
161
+ return matchEntry(source, source, source, matcher as any, errorCollector);
177
162
  }
178
163
 
179
- function matchOptions<T, R>(
180
- value: T,
164
+ /**
165
+ * Match a generic match entry against the given source.
166
+ */
167
+ function matchEntry<T, C, P, R>(
168
+ source: T,
169
+ parent: P,
181
170
  root: R,
182
- matcher: Match.Options<T, R>
171
+ matcher: Match.Entry<T, C, P, R>,
172
+ errorCollector: Match.ErrorCollector
183
173
  ): boolean {
184
- if (matcher instanceof Every) {
185
- let i = -1;
186
- const { matchItems } = matcher;
187
- const len = matchItems.length;
174
+ if (Object.is(source, matcher)) {
175
+ // value and target are exactly the same, always will be true
176
+ return true;
177
+ }
188
178
 
189
- while (++i < len) {
190
- if (!matchOptions(value, root, matchItems[i])) {
191
- return false;
192
- }
179
+ if (matcher === null || matcher === undefined) {
180
+ // these matchers can only be direct matches, and previously it was determined that
181
+ // they are not equal
182
+ errorCollector?.push(
183
+ `value ${JSON.stringify(source)} did not match matcher ${matcher}`
184
+ );
185
+
186
+ return false;
187
+ }
188
+
189
+ if (typeof source === 'function') {
190
+ // function source values can only be directly matched
191
+ const result = Object.is(source, matcher);
192
+
193
+ if (!result) {
194
+ errorCollector?.push(
195
+ `both value and matcher are functions, but they do not have the same reference`
196
+ );
193
197
  }
194
198
 
195
- return true;
199
+ return result;
196
200
  }
197
- if (matcher instanceof Some) {
198
- let i = -1;
199
- const { matchItems } = matcher;
200
- const len = matchItems.length;
201
- while (++i < len) {
202
- if (matchOptions(value, root, matchItems[i])) {
203
- return true;
201
+
202
+ if (typeof matcher === 'function') {
203
+ // resolve match function first
204
+ const matcherResult = matcher(source, parent, root);
205
+
206
+ if (typeof matcherResult === 'boolean') {
207
+ // function resulted in a direct match result
208
+
209
+ if (!matcherResult) {
210
+ errorCollector?.push(
211
+ `function matcher returned false for value ${JSON.stringify(source)}`
212
+ );
204
213
  }
214
+
215
+ return matcherResult;
205
216
  }
206
217
 
207
- return false;
218
+ // function resulted in a value that needs to be further matched
219
+ return matchEntry(source, parent, root, matcherResult, errorCollector);
220
+ }
221
+
222
+ if (isPlainObj(source)) {
223
+ // source ia a plain object, can be partially matched
224
+ return matchPlainObj(source, parent, root, matcher as any, errorCollector);
208
225
  }
209
- if (matcher instanceof None) {
210
- let i = -1;
211
- const { matchItems } = matcher;
212
- const len = matchItems.length;
213
- while (++i < len) {
214
- if (matchOptions(value, root, matchItems[i])) {
226
+
227
+ if (Array.isArray(source)) {
228
+ // source is an array
229
+ return matchArr(source, root, matcher as any, errorCollector);
230
+ }
231
+
232
+ // already determined above that the source and matcher are not equal
233
+
234
+ errorCollector?.push(
235
+ `value ${JSON.stringify(
236
+ source
237
+ )} does not match given matcher ${JSON.stringify(matcher)}`
238
+ );
239
+
240
+ return false;
241
+ }
242
+
243
+ /**
244
+ * Match an array matcher against the given source.
245
+ */
246
+ function matchArr<T extends any[], C, P, R>(
247
+ source: T,
248
+ root: R,
249
+ matcher: Match.Arr<T, C, P, R>,
250
+ errorCollector: Match.ErrorCollector
251
+ ): boolean {
252
+ if (Array.isArray(matcher)) {
253
+ // directly compare array contents
254
+ const length = source.length;
255
+
256
+ if (length !== matcher.length) {
257
+ // if lengths not equal, arrays are not equal
258
+
259
+ errorCollector?.push(
260
+ `array lengths are not equal: value length ${source.length} !== matcher length ${matcher.length}`
261
+ );
262
+
263
+ return false;
264
+ }
265
+
266
+ // loop over arrays, matching every value
267
+ let index = -1;
268
+ while (++index < length) {
269
+ if (!Object.is(source[index], matcher[index])) {
270
+ // item did not match, return false
271
+
272
+ errorCollector?.push(
273
+ `index ${index} does not match with value ${JSON.stringify(
274
+ source[index]
275
+ )} and matcher ${matcher[index]}`
276
+ );
277
+
215
278
  return false;
216
279
  }
217
280
  }
218
281
 
282
+ // all items are equal
219
283
  return true;
220
284
  }
221
- if (matcher instanceof Single) {
222
- let i = -1;
223
- const { matchItems } = matcher;
224
- const len = matchItems.length;
225
- let matched = false;
226
-
227
- while (++i < len) {
228
- if (matchOptions(value, root, matchItems[i])) {
229
- if (matched) {
230
- return false;
231
- }
232
285
 
233
- matched = true;
234
- }
286
+ // matcher is plain object with index keys
287
+
288
+ for (const index in matcher as any) {
289
+ const matcherAtIndex = (matcher as any)[index];
290
+
291
+ if (!(index in source)) {
292
+ // source does not have item at given index
293
+
294
+ errorCollector?.push(
295
+ `index ${index} does not exist in source ${JSON.stringify(
296
+ source
297
+ )} but should match matcher ${JSON.stringify(matcherAtIndex)}`
298
+ );
299
+
300
+ return false;
235
301
  }
236
302
 
237
- return matched;
238
- }
303
+ // match the source item at the given index
304
+ const result = matchEntry(
305
+ (source as any)[index],
306
+ source,
307
+ root,
308
+ matcherAtIndex,
309
+ errorCollector
310
+ );
311
+
312
+ if (!result) {
313
+ // item did not match
314
+
315
+ errorCollector?.push(
316
+ `index ${index} does not match with value ${JSON.stringify(
317
+ (source as any)[index]
318
+ )} and matcher ${JSON.stringify(matcherAtIndex)}`
319
+ );
239
320
 
240
- if (isPlainObj(matcher)) {
241
- return matchRecord(value, root, matcher);
321
+ return false;
322
+ }
242
323
  }
243
324
 
244
- return Object.is(value, matcher);
325
+ // all items match
326
+
327
+ return true;
245
328
  }
246
329
 
247
- function matchRecord<T, R>(
248
- value: T,
330
+ /**
331
+ * Match an object matcher against the given source.
332
+ */
333
+ function matchPlainObj<T, C, P, R>(
334
+ source: T,
335
+ parent: P,
249
336
  root: R,
250
- matcher: Match.Obj<T, R>
337
+ matcher: Match.Obj<T, C, P, R>,
338
+ errorCollector: Match.ErrorCollector
251
339
  ): boolean {
252
- if (!isPlainObj(matcher)) {
253
- RimbuError.throwInvalidUsageError(
254
- 'match: to prevent accidental errors, match only supports plain objects as input.'
255
- );
340
+ if (Array.isArray(matcher)) {
341
+ // the matcher is of compound type
342
+ return matchCompound(source, parent, root, matcher as any, errorCollector);
256
343
  }
257
344
 
258
- for (const key in matcher) {
259
- if (!(key in value)) return false;
260
-
261
- const matchValue = matcher[key];
262
- const target = value[key];
345
+ // partial object props matcher
263
346
 
264
- if (matchValue instanceof Function) {
265
- if (target instanceof Function && Object.is(target, matchValue)) {
266
- return true;
267
- }
347
+ for (const key in matcher) {
348
+ if (!(key in source)) {
349
+ // the source does not have the given key
268
350
 
269
- const result = matchValue(target, value, root);
351
+ errorCollector?.push(
352
+ `key ${key} is specified in matcher but not present in value ${JSON.stringify(
353
+ source
354
+ )}`
355
+ );
270
356
 
271
- if (typeof result === 'boolean') {
272
- if (result) {
273
- continue;
274
- }
357
+ return false;
358
+ }
275
359
 
276
- return false;
277
- }
360
+ // match the source value at the given key with the matcher at given key
361
+ const result = matchEntry(
362
+ (source as any)[key],
363
+ source,
364
+ root,
365
+ matcher[key],
366
+ errorCollector
367
+ );
278
368
 
279
- if (!matchRecordItem(target, root, result)) {
280
- return false;
281
- }
282
- } else {
283
- if (!matchRecordItem(target, root, matchValue as any)) {
284
- return false;
285
- }
369
+ if (!result) {
370
+ errorCollector?.push(
371
+ `key ${key} does not match in value ${JSON.stringify(
372
+ (source as any)[key]
373
+ )} with matcher ${JSON.stringify(matcher[key])}`
374
+ );
375
+ return false;
286
376
  }
287
377
  }
288
378
 
379
+ // all properties match
380
+
289
381
  return true;
290
382
  }
291
383
 
292
- function matchRecordItem<T, R>(
293
- value: T,
384
+ /**
385
+ * Match a compound matcher against the given source.
386
+ */
387
+ function matchCompound<T, C, P, R>(
388
+ source: T,
389
+ parent: P,
294
390
  root: R,
295
- matcher: Match.ObjItem<T, R>
391
+ compound: [Match.CompoundType, ...Match.Entry<T, C, P, R>[]],
392
+ errorCollector: string[] | undefined
296
393
  ): boolean {
297
- if (isIterable(matcher) && isIterable(value)) {
298
- const it1 = (value as any)[Symbol.iterator]() as Iterator<unknown>;
299
- const it2 = (matcher as any)[Symbol.iterator]() as Iterator<unknown>;
394
+ // first item indicates compound match type
395
+ const matchType = compound[0];
300
396
 
301
- while (true) {
302
- const v1 = it1.next();
303
- const v2 = it2.next();
397
+ const length = compound.length;
304
398
 
305
- if (v1.done !== v2.done || v1.value !== v2.value) {
306
- return false;
399
+ // start at index 1
400
+ let index = 0;
401
+
402
+ type Entry = Match.Entry<T, C, P, R>;
403
+
404
+ switch (matchType) {
405
+ case 'every': {
406
+ while (++index < length) {
407
+ // if any item does not match, return false
408
+ const result = matchEntry(
409
+ source,
410
+ parent,
411
+ root,
412
+ compound[index] as Entry,
413
+ errorCollector
414
+ );
415
+
416
+ if (!result) {
417
+ errorCollector?.push(
418
+ `in compound "every": match at index ${index} failed`
419
+ );
420
+
421
+ return false;
422
+ }
307
423
  }
308
- if (v1.done) {
309
- return true;
424
+
425
+ return true;
426
+ }
427
+ case 'none': {
428
+ // if any item matches, return false
429
+ while (++index < length) {
430
+ const result = matchEntry(
431
+ source,
432
+ parent,
433
+ root,
434
+ compound[index] as Entry,
435
+ errorCollector
436
+ );
437
+
438
+ if (result) {
439
+ errorCollector?.push(
440
+ `in compound "none": match at index ${index} succeeded`
441
+ );
442
+
443
+ return false;
444
+ }
310
445
  }
446
+
447
+ return true;
311
448
  }
312
- }
313
- if (isPlainObj(value)) {
314
- return matchOptions(value, root, matcher as any);
315
- }
449
+ case 'single': {
450
+ // if not exactly one item matches, return false
451
+ let onePassed = false;
452
+
453
+ while (++index < length) {
454
+ const result = matchEntry(
455
+ source,
456
+ parent,
457
+ root,
458
+ compound[index] as Entry,
459
+ errorCollector
460
+ );
316
461
 
317
- return Object.is(value, matcher);
462
+ if (result) {
463
+ if (onePassed) {
464
+ errorCollector?.push(
465
+ `in compound "single": multiple matches succeeded`
466
+ );
467
+
468
+ return false;
469
+ }
470
+
471
+ onePassed = true;
472
+ }
473
+ }
474
+
475
+ if (!onePassed) {
476
+ errorCollector?.push(`in compound "single": no matches succeeded`);
477
+ }
478
+
479
+ return onePassed;
480
+ }
481
+ case 'some': {
482
+ // if any item matches, return true
483
+ while (++index < length) {
484
+ const result = matchEntry(
485
+ source,
486
+ parent,
487
+ root,
488
+ compound[index] as Entry,
489
+ errorCollector
490
+ );
491
+
492
+ if (result) {
493
+ return true;
494
+ }
495
+ }
496
+
497
+ errorCollector?.push(`in compound "some": no matches succeeded`);
498
+
499
+ return false;
500
+ }
501
+ }
318
502
  }