@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
@@ -0,0 +1,192 @@
1
+ import { Deep } from './internal';
2
+ export { match } from './match';
3
+ export { patch } from './patch';
4
+ export { getAt, patchAt } from './path';
5
+ export { select } from './selector';
6
+ /**
7
+ * Returns the same value wrapped in the `Protected` type.
8
+ * @typeparam T - the source value type
9
+ * @param source - the value to wrap
10
+ * @note does not perform any runtime protection, it is only a utility to easily add the `Protected`
11
+ * type to a value
12
+ * @example
13
+ * ```ts
14
+ * const obj = Deep.protect({ a: 1, b: { c: true, d: [1] } })
15
+ * obj.a = 2 // compiler error: a is readonly
16
+ * obj.b.c = false // compiler error: c is readonly
17
+ * obj.b.d.push(2) // compiler error: d is a readonly array
18
+ * (obj as any).b.d.push(2) // will actually mutate the object
19
+ * ```
20
+ */
21
+ export function protect(source) {
22
+ return source;
23
+ }
24
+ /**
25
+ * Returns a function that gets the value at the given string `path` inside an object.
26
+ * @typeparam T - the input value type
27
+ * @typeparam P - the string literal path type in the object
28
+ * @param path - the string path in the object
29
+ * @param source - the value from which to extract the path value
30
+ * @example
31
+ * ```ts
32
+ * const items = [{ a: { b: 1, c: 'a' } }, { a: { b: 2, c: 'b' } }];
33
+ * items.map(Deep.getAtWith('a.c'));
34
+ * // => ['a', 'b']
35
+ * ```
36
+ */
37
+ export function getAtWith(path) {
38
+ return (source) => Deep.getAt(source, path);
39
+ }
40
+ /**
41
+ * Returns a function that patches a given `source` with the given `patchItems`.
42
+ * @typeparam T - the patch value type
43
+ * @typeparam TE - utility type
44
+ * @typeparam TT - utility type
45
+ * @param patchItem - the `Patch` definition to update the given value of type `T` with.
46
+ * @param source - the value to use the given `patchItem` on.
47
+ * @example
48
+ * ```ts
49
+ * const items = [{ a: 1, b: 'a' }, { a: 2, b: 'b' }];
50
+ * items.map(Deep.patchWith([{ a: v => v + 1 }]));
51
+ * // => [{ a: 2, b: 'a' }, { a: 3, b: 'b' }]
52
+ * ```
53
+ */
54
+ export function patchWith(patchItem) {
55
+ return (source) => Deep.patch(source, patchItem);
56
+ }
57
+ /**
58
+ * Returns a function that patches a given `value` with the given `patchItems` at the given `path`.
59
+ * @typeparam T - the patch value type
60
+ * @typeparam P - the string literal path type in the object
61
+ * @typeparam TE - utility type
62
+ * @typeparam TT - utility type
63
+ * @param path - the string path in the object
64
+ * @param patchItem - the `Patch` definition to update the value at the given `path` in `T` with.
65
+ * @param source - the value to use the given `patchItem` on at the given `path`.
66
+ * @example
67
+ * ```ts
68
+ * const items = [{ a: { b: 1, c: 'a' } }, { a: { b: 2, c: 'b' } }];
69
+ * items.map(Deep.patchAtWith('a', [{ b: (v) => v + 1 }]));
70
+ * // => [{ a: { b: 2, c: 'a' } }, { a: { b: 3, c: 'b' } }]
71
+ * ```
72
+ */
73
+ export function patchAtWith(path, patchItem) {
74
+ return (source) => Deep.patchAt(source, path, patchItem);
75
+ }
76
+ /**
77
+ * Returns a function that matches a given `value` with the given `matcher`.
78
+ * @typeparam T - the patch value type
79
+ * @param matcher - a matcher object that matches input values.
80
+ * @param source - the value to use the given `patchItem` on at the given `path`.
81
+ * @example
82
+ * ```ts
83
+ * const items = [{ a: 1, b: 'a' }, { a: 2, b: 'b' }];
84
+ * items.filter(Deep.matchWith({ a: 2 }));
85
+ * // => [{ a: 2, b: 'b' }]
86
+ * ```
87
+ */
88
+ export function matchWith(matcher) {
89
+ return (source) => Deep.match(source, matcher);
90
+ }
91
+ /**
92
+ * Returns true if the given `value` object matches the given `matcher` at the given `path`, false otherwise.
93
+ * @typeparam T - the input value type
94
+ * @typeparam P - the string literal path type in the object
95
+ * @param source - the input value
96
+ * @param path - the string path in the object
97
+ * @param matcher - a matcher object or a function taking the matcher API and returning a match object
98
+ * @example
99
+ * ```ts
100
+ * const input = { a: 1, b: { c: true, d: 'a' } }
101
+ * Deep.matchAt(input, 'b', { c: true })
102
+ * // => true
103
+ * ```
104
+ */
105
+ export function matchAt(source, path, matcher) {
106
+ return Deep.match(Deep.getAt(source, path), matcher);
107
+ }
108
+ /**
109
+ * Returns a function that matches a given `value` with the given `matcher` at the given string `path`.
110
+ * @typeparam T - the patch value type
111
+ * @typeparam P - the string literal path type in the object
112
+ * @typeparam TE - utility type
113
+ * @param path - the string path in the object
114
+ * @param matcher - a matcher object that matches input values.
115
+ * @param source - the value to use the given `matcher` on at the given `path`.
116
+ * @example
117
+ * ```ts
118
+ * const items = [{ a: { b: 1, c: 'a' } }, { a: { b: 2, c: 'b' } }];
119
+ * items.filter(Deep.matchAtWith('a.b', 2));
120
+ * // => [{ a: 2, b: 'b' }]
121
+ * ```
122
+ */
123
+ export function matchAtWith(path, matcher) {
124
+ return (source) => Deep.matchAt(source, path, matcher);
125
+ }
126
+ /**
127
+ * Returns a function that selects a certain shape from a given `value` with the given `selector`.
128
+ * @typeparam T - the patch value type
129
+ * @typeparam SL - the selector shape type
130
+ * @param selector - a shape indicating the selection from the source values
131
+ * @param source - the value to use the given `selector` on.
132
+ * @example
133
+ * ```ts
134
+ * const items = [{ a: { b: 1, c: 'a' } }, { a: { b: 2, c: 'b' } }];
135
+ * items.map(Deep.selectWith({ q: 'a.c', z: ['a.b', v => v.a.b + 1] as const }));
136
+ * // => [{ q: 'a', z: [1, 2] }, { q: 'b', z: [2, 3] }]
137
+ * ```
138
+ */
139
+ export function selectWith(selector) {
140
+ return (source) => Deep.select(source, selector);
141
+ }
142
+ /**
143
+ * Returns the result of applying the given `selector` shape to the given `source` value.
144
+ * @typeparam T - the patch value type
145
+ * @typeparam P - the string literal path type in the object
146
+ * @typeparam SL - the selector shape type
147
+ * @param source - the source value to select from
148
+ * @param path - the string path in the object
149
+ * @param selector - a shape indicating the selection from the source value at the given path
150
+ * @example
151
+ * ```ts
152
+ * const item = { a: { b: 1, c: 'a' } };
153
+ * Deep.selectAt(item, 'a', { q: 'c', z: ['b', v => v.b + 1] as const });
154
+ * // => { q: 'a', z: [1, 2] }
155
+ * ```
156
+ */
157
+ export function selectAt(source, path, selector) {
158
+ return Deep.select(Deep.getAt(source, path), selector);
159
+ }
160
+ /**
161
+ * Returns a function that selects a certain shape from a given `value` with the given `selector` at the given string `path`.
162
+ * @typeparam T - the patch value type
163
+ * @typeparam P - the string literal path type in the object
164
+ * @typeparam SL - the selector shape type
165
+ * @param path - the string path in the object
166
+ * @param selector - a shape indicating the selection from the source values
167
+ * @example
168
+ * ```ts
169
+ * const items = [{ a: { b: 1, c: 'a' } }, { a: { b: 2, c: 'b' } }];
170
+ * items.map(Deep.selectAtWith('a', { q: 'c', z: ['b', v => v.b + 1] as const }));
171
+ * // => [{ q: 'a', z: [1, 2] }, { q: 'b', z: [2, 3] }]
172
+ * ```
173
+ */
174
+ export function selectAtWith(path, selector) {
175
+ return (source) => Deep.selectAt(source, path, selector);
176
+ }
177
+ /**
178
+ * Returns a curried API with a known target type. This can be useful for using the methods in
179
+ * contexts where the target type can be inferred from the usage.
180
+ * @typeparam T - the target type
181
+ * @example
182
+ * ```ts
183
+ * const s = { a: 1, b: { c: 'a', d: true }}
184
+ * const upd = Deep.withType<typeof s>().patchWith([{ d: (v) => !v }])
185
+ * upd(s)
186
+ * // => { a: 1, b: { c: 'a', d: false }}
187
+ * ```
188
+ */
189
+ export function withType() {
190
+ return Deep;
191
+ }
192
+ //# sourceMappingURL=deep.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"deep.js","sourceRoot":"","sources":["../../src/deep.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,IAAI,EAAE,MAAM,YAAY,CAAC;AAElC,OAAO,EAAE,KAAK,EAAc,MAAM,SAAS,CAAC;AAC5C,OAAO,EAAE,KAAK,EAAc,MAAM,SAAS,CAAC;AAC5C,OAAO,EAAE,KAAK,EAAE,OAAO,EAAa,MAAM,QAAQ,CAAC;AACnD,OAAO,EAAE,MAAM,EAAiB,MAAM,YAAY,CAAC;AAGnD;;;;;;;;;;;;;;GAcG;AACH,MAAM,UAAU,OAAO,CAAI,MAAS;IAClC,OAAO,MAAsB,CAAC;AAChC,CAAC;AAED;;;;;;;;;;;;GAYG;AACH,MAAM,UAAU,SAAS,CACvB,IAAO;IAEP,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;AAC9C,CAAC;AAED;;;;;;;;;;;;;GAaG;AACH,MAAM,UAAU,SAAS,CACvB,SAAwB;IAExB,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC;AACnD,CAAC;AAED;;;;;;;;;;;;;;;GAeG;AACH,MAAM,UAAU,WAAW,CACzB,IAAO,EACP,SAAwD;IAExD,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,IAAI,CAAC,OAAO,CAAC,MAAM,EAAE,IAAI,EAAE,SAAgB,CAAC,CAAC;AAClE,CAAC;AAED;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,SAAS,CAAI,OAAiB;IAC5C,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AACjD,CAAC;AAED;;;;;;;;;;;;;GAaG;AACH,MAAM,UAAU,OAAO,CACrB,MAAS,EACT,IAAO,EACP,OAAiC;IAEjC,OAAO,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,MAAM,EAAE,IAAI,CAAC,EAAE,OAAO,CAAC,CAAC;AACvD,CAAC;AAED;;;;;;;;;;;;;;GAcG;AACH,MAAM,UAAU,WAAW,CACzB,IAAO,EACP,OAAsC;IAEtC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,IAAI,CAAC,OAAO,CAAC,MAAM,EAAE,IAAI,EAAE,OAAc,CAAC,CAAC;AAChE,CAAC;AAED;;;;;;;;;;;;GAYG;AACH,MAAM,UAAU,UAAU,CACxB,QAA4B;IAE5B,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,IAAI,CAAC,MAAM,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC;AACnD,CAAC;AAED;;;;;;;;;;;;;;GAcG;AACH,MAAM,UAAU,QAAQ,CAKtB,MAAS,EACT,IAAO,EACP,QAA4B;IAE5B,OAAO,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,MAAM,EAAE,IAAI,CAAC,EAAE,QAAQ,CAAC,CAAC;AACzD,CAAC;AAED;;;;;;;;;;;;;GAaG;AACH,MAAM,UAAU,YAAY,CAK1B,IAAO,EACP,QAA4B;IAE5B,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,IAAI,CAAC,QAAQ,CAAC,MAAM,EAAE,IAAI,EAAE,QAAQ,CAAC,CAAC;AAC3D,CAAC;AAgID;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,QAAQ;IACtB,OAAO,IAAI,CAAC;AACd,CAAC"}
@@ -5,6 +5,14 @@
5
5
  * <br/>
6
6
  * See the [Rimbu docs Deep overview page](/docs/deep/overview) for more information.
7
7
  */
8
- export { patch, patchNested, Patch, match, Match, Path, Protected, } from './internal';
9
8
  export { Tuple } from './tuple';
9
+ export { Path } from './internal';
10
+ export * from './deep';
11
+ import * as Deep from './deep';
12
+ export {
13
+ /**
14
+ * Convenience namespace offering access to most common functions used in the `@rimbu/deep` package.
15
+ * These are mainly utilities to patch and match plain JavaScript objects.
16
+ */
17
+ Deep, };
10
18
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EACL,KAAK,EACL,WAAW,EACX,KAAK,EACL,KAAK,EACL,KAAK,EACL,IAAI,EACJ,SAAS,GACV,MAAM,YAAY,CAAC;AAEpB,OAAO,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;AAGhC,OAAO,EAAE,IAAI,EAA6B,MAAM,YAAY,CAAC;AAE7D,cAAc,QAAQ,CAAC;AAEvB,OAAO,KAAK,IAAI,MAAM,QAAQ,CAAC;AAC/B,OAAO;AACL;;;GAGG;AACH,IAAI,GACL,CAAC"}
@@ -1,5 +1,5 @@
1
- export * from './protected';
2
- export * from './match';
3
- export * from './patch';
4
- export * from './path';
1
+ export { Path } from './path';
2
+ export {} from './match';
3
+ import * as Deep from './deep';
4
+ export { Deep };
5
5
  //# sourceMappingURL=internal.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"internal.js","sourceRoot":"","sources":["../../src/internal.ts"],"names":[],"mappings":"AAAA,cAAc,aAAa,CAAC;AAE5B,cAAc,SAAS,CAAC;AACxB,cAAc,SAAS,CAAC;AACxB,cAAc,QAAQ,CAAC"}
1
+ {"version":3,"file":"internal.js","sourceRoot":"","sources":["../../src/internal.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAC;AAC9B,OAAO,EAAc,MAAM,SAAS,CAAC;AAIrC,OAAO,KAAK,IAAI,MAAM,QAAQ,CAAC;AAC/B,OAAO,EAAE,IAAI,EAAE,CAAC"}
@@ -1,222 +1,202 @@
1
- import { RimbuError, isPlainObj, isIterable, } from '@rimbu/base';
2
- export var Match;
3
- (function (Match) {
4
- /**
5
- * Returns a matcher that returns true if every given `matchItem` matches the given value.
6
- * @typeparam T - the type of value to match
7
- * @typeparam R - the root object type
8
- * @typeparam Q - a utility type for the matcher
9
- * @param matchItems - the match specifications to test
10
- * @example
11
- * ```ts
12
- * const input = { a: 1, b: { c: true, d: 'a' } }
13
- * match(input, Match.every({ a: 1, { c: true } } )) // => true
14
- * match(input, Match.every({ a: 1, { c: false } } )) // => false
15
- * ```
16
- */
17
- function every(...matchItems) {
18
- return new Every(matchItems);
19
- }
20
- Match.every = every;
21
- /**
22
- * Returns a matcher that returns true if at least one of given `matchItem` matches the given value.
23
- * @typeparam T - the type of value to match
24
- * @typeparam R - the root object type
25
- * @typeparam Q - a utility type for the matcher
26
- * @param matchItems - the match specifications to test
27
- * @example
28
- * ```ts
29
- * const input = { a: 1, b: { c: true, d: 'a' } }
30
- * match(input, Match.some({ a: 5, { c: true } } )) // => true
31
- * match(input, Match.some({ a: 5, { c: false } } )) // => false
32
- * ```
33
- */
34
- function some(...matchItems) {
35
- return new Some(matchItems);
36
- }
37
- Match.some = some;
38
- /**
39
- * Returns a matcher that returns true if none of given `matchItem` matches the given value.
40
- * @typeparam T - the type of value to match
41
- * @typeparam R - the root object type
42
- * @typeparam Q - a utility type for the matcher
43
- * @param matchItems - the match specifications to test
44
- * @example
45
- * ```ts
46
- * const input = { a: 1, b: { c: true, d: 'a' } }
47
- * match(input, Match.none({ a: 5, { c: true } } )) // => false
48
- * match(input, Match.none({ a: 5, { c: false } } )) // => true
49
- * ```
50
- */
51
- function none(...matchItems) {
52
- return new None(matchItems);
53
- }
54
- Match.none = none;
55
- /**
56
- * Returns a matcher that returns true if exactly one of given `matchItem` matches the given value.
57
- * @typeparam T - the type of value to match
58
- * @typeparam R - the root object type
59
- * @typeparam Q - a utility type for the matcher
60
- * @param matchItems - the match specifications to test
61
- * @example
62
- * ```ts
63
- * const input = { a: 1, b: { c: true, d: 'a' } }
64
- * match(input, Match.single({ a: 1, { c: true } } )) // => false
65
- * match(input, Match.single({ a: 1, { c: false } } )) // => true
66
- * ```
67
- */
68
- function single(...matchItems) {
69
- return new Single(matchItems);
70
- }
71
- Match.single = single;
72
- })(Match || (Match = {}));
73
- class Every {
74
- constructor(matchItems) {
75
- this.matchItems = matchItems;
76
- }
77
- }
78
- class Some {
79
- constructor(matchItems) {
80
- this.matchItems = matchItems;
81
- }
82
- }
83
- class None {
84
- constructor(matchItems) {
85
- this.matchItems = matchItems;
86
- }
87
- }
88
- class Single {
89
- constructor(matchItems) {
90
- this.matchItems = matchItems;
91
- }
92
- }
1
+ import { isPlainObj, } from '@rimbu/base';
93
2
  /**
94
3
  * Returns true if the given `value` object matches the given `matcher`, false otherwise.
95
4
  * @typeparam T - the input value type
96
- * @param value - the value to match (should be a plain object)
5
+ * @typeparam C - utility type
6
+ * @param source - the value to match (should be a plain object)
97
7
  * @param matcher - a matcher object or a function taking the matcher API and returning a match object
8
+ * @param errorCollector - (optional) a string array that can be passed to collect reasons why the match failed
98
9
  * @example
99
10
  * ```ts
100
11
  * const input = { a: 1, b: { c: true, d: 'a' } }
101
12
  * match(input, { a: 1 }) // => true
102
13
  * match(input, { a: 2 }) // => false
103
- * match(input, { a: v => v > 10 }) // => false
14
+ * match(input, { a: (v) => v > 10 }) // => false
104
15
  * match(input, { b: { c: true }}) // => true
105
- * match(input, ({ every }) => every({ a: v => v > 0 }, { b: { c: true } } )) // => true
16
+ * match(input, (['every', { a: (v) => v > 0 }, { b: { c: true } }]) // => true
106
17
  * match(input, { b: { c: (v, parent, root) => v && parent.d.length > 0 && root.a > 0 } })
107
18
  * // => true
108
19
  * ```
109
20
  */
110
- export function match(value, matcher) {
111
- if (matcher instanceof Function) {
112
- return matchOptions(value, value, matcher(Match));
113
- }
114
- return matchOptions(value, value, matcher);
21
+ export function match(source, matcher, errorCollector = undefined) {
22
+ return matchEntry(source, source, source, matcher, errorCollector);
115
23
  }
116
- function matchOptions(value, root, matcher) {
117
- if (matcher instanceof Every) {
118
- let i = -1;
119
- const { matchItems } = matcher;
120
- const len = matchItems.length;
121
- while (++i < len) {
122
- if (!matchOptions(value, root, matchItems[i])) {
123
- return false;
124
- }
125
- }
24
+ /**
25
+ * Match a generic match entry against the given source.
26
+ */
27
+ function matchEntry(source, parent, root, matcher, errorCollector) {
28
+ if (Object.is(source, matcher)) {
29
+ // value and target are exactly the same, always will be true
126
30
  return true;
127
31
  }
128
- if (matcher instanceof Some) {
129
- let i = -1;
130
- const { matchItems } = matcher;
131
- const len = matchItems.length;
132
- while (++i < len) {
133
- if (matchOptions(value, root, matchItems[i])) {
134
- return true;
32
+ if (matcher === null || matcher === undefined) {
33
+ // these matchers can only be direct matches, and previously it was determined that
34
+ // they are not equal
35
+ errorCollector === null || errorCollector === void 0 ? void 0 : errorCollector.push(`value ${JSON.stringify(source)} did not match matcher ${matcher}`);
36
+ return false;
37
+ }
38
+ if (typeof source === 'function') {
39
+ // function source values can only be directly matched
40
+ const result = Object.is(source, matcher);
41
+ if (!result) {
42
+ errorCollector === null || errorCollector === void 0 ? void 0 : errorCollector.push(`both value and matcher are functions, but they do not have the same reference`);
43
+ }
44
+ return result;
45
+ }
46
+ if (typeof matcher === 'function') {
47
+ // resolve match function first
48
+ const matcherResult = matcher(source, parent, root);
49
+ if (typeof matcherResult === 'boolean') {
50
+ // function resulted in a direct match result
51
+ if (!matcherResult) {
52
+ errorCollector === null || errorCollector === void 0 ? void 0 : errorCollector.push(`function matcher returned false for value ${JSON.stringify(source)}`);
135
53
  }
54
+ return matcherResult;
136
55
  }
137
- return false;
56
+ // function resulted in a value that needs to be further matched
57
+ return matchEntry(source, parent, root, matcherResult, errorCollector);
138
58
  }
139
- if (matcher instanceof None) {
140
- let i = -1;
141
- const { matchItems } = matcher;
142
- const len = matchItems.length;
143
- while (++i < len) {
144
- if (matchOptions(value, root, matchItems[i])) {
59
+ if (isPlainObj(source)) {
60
+ // source ia a plain object, can be partially matched
61
+ return matchPlainObj(source, parent, root, matcher, errorCollector);
62
+ }
63
+ if (Array.isArray(source)) {
64
+ // source is an array
65
+ return matchArr(source, root, matcher, errorCollector);
66
+ }
67
+ // already determined above that the source and matcher are not equal
68
+ errorCollector === null || errorCollector === void 0 ? void 0 : errorCollector.push(`value ${JSON.stringify(source)} does not match given matcher ${JSON.stringify(matcher)}`);
69
+ return false;
70
+ }
71
+ /**
72
+ * Match an array matcher against the given source.
73
+ */
74
+ function matchArr(source, root, matcher, errorCollector) {
75
+ if (Array.isArray(matcher)) {
76
+ // directly compare array contents
77
+ const length = source.length;
78
+ if (length !== matcher.length) {
79
+ // if lengths not equal, arrays are not equal
80
+ errorCollector === null || errorCollector === void 0 ? void 0 : errorCollector.push(`array lengths are not equal: value length ${source.length} !== matcher length ${matcher.length}`);
81
+ return false;
82
+ }
83
+ // loop over arrays, matching every value
84
+ let index = -1;
85
+ while (++index < length) {
86
+ if (!Object.is(source[index], matcher[index])) {
87
+ // item did not match, return false
88
+ errorCollector === null || errorCollector === void 0 ? void 0 : errorCollector.push(`index ${index} does not match with value ${JSON.stringify(source[index])} and matcher ${matcher[index]}`);
145
89
  return false;
146
90
  }
147
91
  }
92
+ // all items are equal
148
93
  return true;
149
94
  }
150
- if (matcher instanceof Single) {
151
- let i = -1;
152
- const { matchItems } = matcher;
153
- const len = matchItems.length;
154
- let matched = false;
155
- while (++i < len) {
156
- if (matchOptions(value, root, matchItems[i])) {
157
- if (matched) {
158
- return false;
159
- }
160
- matched = true;
161
- }
95
+ // matcher is plain object with index keys
96
+ for (const index in matcher) {
97
+ const matcherAtIndex = matcher[index];
98
+ if (!(index in source)) {
99
+ // source does not have item at given index
100
+ errorCollector === null || errorCollector === void 0 ? void 0 : errorCollector.push(`index ${index} does not exist in source ${JSON.stringify(source)} but should match matcher ${JSON.stringify(matcherAtIndex)}`);
101
+ return false;
102
+ }
103
+ // match the source item at the given index
104
+ const result = matchEntry(source[index], source, root, matcherAtIndex, errorCollector);
105
+ if (!result) {
106
+ // item did not match
107
+ errorCollector === null || errorCollector === void 0 ? void 0 : errorCollector.push(`index ${index} does not match with value ${JSON.stringify(source[index])} and matcher ${JSON.stringify(matcherAtIndex)}`);
108
+ return false;
162
109
  }
163
- return matched;
164
- }
165
- if (isPlainObj(matcher)) {
166
- return matchRecord(value, root, matcher);
167
110
  }
168
- return Object.is(value, matcher);
111
+ // all items match
112
+ return true;
169
113
  }
170
- function matchRecord(value, root, matcher) {
171
- if (!isPlainObj(matcher)) {
172
- RimbuError.throwInvalidUsageError('match: to prevent accidental errors, match only supports plain objects as input.');
114
+ /**
115
+ * Match an object matcher against the given source.
116
+ */
117
+ function matchPlainObj(source, parent, root, matcher, errorCollector) {
118
+ if (Array.isArray(matcher)) {
119
+ // the matcher is of compound type
120
+ return matchCompound(source, parent, root, matcher, errorCollector);
173
121
  }
122
+ // partial object props matcher
174
123
  for (const key in matcher) {
175
- if (!(key in value))
124
+ if (!(key in source)) {
125
+ // the source does not have the given key
126
+ errorCollector === null || errorCollector === void 0 ? void 0 : errorCollector.push(`key ${key} is specified in matcher but not present in value ${JSON.stringify(source)}`);
176
127
  return false;
177
- const matchValue = matcher[key];
178
- const target = value[key];
179
- if (matchValue instanceof Function) {
180
- if (target instanceof Function && Object.is(target, matchValue)) {
181
- return true;
128
+ }
129
+ // match the source value at the given key with the matcher at given key
130
+ const result = matchEntry(source[key], source, root, matcher[key], errorCollector);
131
+ if (!result) {
132
+ errorCollector === null || errorCollector === void 0 ? void 0 : errorCollector.push(`key ${key} does not match in value ${JSON.stringify(source[key])} with matcher ${JSON.stringify(matcher[key])}`);
133
+ return false;
134
+ }
135
+ }
136
+ // all properties match
137
+ return true;
138
+ }
139
+ /**
140
+ * Match a compound matcher against the given source.
141
+ */
142
+ function matchCompound(source, parent, root, compound, errorCollector) {
143
+ // first item indicates compound match type
144
+ const matchType = compound[0];
145
+ const length = compound.length;
146
+ // start at index 1
147
+ let index = 0;
148
+ switch (matchType) {
149
+ case 'every': {
150
+ while (++index < length) {
151
+ // if any item does not match, return false
152
+ const result = matchEntry(source, parent, root, compound[index], errorCollector);
153
+ if (!result) {
154
+ errorCollector === null || errorCollector === void 0 ? void 0 : errorCollector.push(`in compound "every": match at index ${index} failed`);
155
+ return false;
156
+ }
182
157
  }
183
- const result = matchValue(target, value, root);
184
- if (typeof result === 'boolean') {
158
+ return true;
159
+ }
160
+ case 'none': {
161
+ // if any item matches, return false
162
+ while (++index < length) {
163
+ const result = matchEntry(source, parent, root, compound[index], errorCollector);
185
164
  if (result) {
186
- continue;
165
+ errorCollector === null || errorCollector === void 0 ? void 0 : errorCollector.push(`in compound "none": match at index ${index} succeeded`);
166
+ return false;
187
167
  }
188
- return false;
189
- }
190
- if (!matchRecordItem(target, root, result)) {
191
- return false;
192
168
  }
169
+ return true;
193
170
  }
194
- else {
195
- if (!matchRecordItem(target, root, matchValue)) {
196
- return false;
171
+ case 'single': {
172
+ // if not exactly one item matches, return false
173
+ let onePassed = false;
174
+ while (++index < length) {
175
+ const result = matchEntry(source, parent, root, compound[index], errorCollector);
176
+ if (result) {
177
+ if (onePassed) {
178
+ errorCollector === null || errorCollector === void 0 ? void 0 : errorCollector.push(`in compound "single": multiple matches succeeded`);
179
+ return false;
180
+ }
181
+ onePassed = true;
182
+ }
197
183
  }
198
- }
199
- }
200
- return true;
201
- }
202
- function matchRecordItem(value, root, matcher) {
203
- if (isIterable(matcher) && isIterable(value)) {
204
- const it1 = value[Symbol.iterator]();
205
- const it2 = matcher[Symbol.iterator]();
206
- while (true) {
207
- const v1 = it1.next();
208
- const v2 = it2.next();
209
- if (v1.done !== v2.done || v1.value !== v2.value) {
210
- return false;
184
+ if (!onePassed) {
185
+ errorCollector === null || errorCollector === void 0 ? void 0 : errorCollector.push(`in compound "single": no matches succeeded`);
211
186
  }
212
- if (v1.done) {
213
- return true;
187
+ return onePassed;
188
+ }
189
+ case 'some': {
190
+ // if any item matches, return true
191
+ while (++index < length) {
192
+ const result = matchEntry(source, parent, root, compound[index], errorCollector);
193
+ if (result) {
194
+ return true;
195
+ }
214
196
  }
197
+ errorCollector === null || errorCollector === void 0 ? void 0 : errorCollector.push(`in compound "some": no matches succeeded`);
198
+ return false;
215
199
  }
216
200
  }
217
- if (isPlainObj(value)) {
218
- return matchOptions(value, root, matcher);
219
- }
220
- return Object.is(value, matcher);
221
201
  }
222
202
  //# sourceMappingURL=match.js.map