@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/patch.ts CHANGED
@@ -1,257 +1,254 @@
1
- import {
2
- RimbuError,
3
- type AnyFunc,
4
- isPlainObj,
5
- type PlainObj,
6
- } from '@rimbu/base';
1
+ import { IsAnyFunc, IsArray, isPlainObj, IsPlainObj } from '@rimbu/base';
7
2
 
8
3
  import type { Protected } from './internal';
4
+ import type { Tuple } from './tuple';
9
5
 
10
6
  /**
11
7
  * A type to determine the allowed input type for the `patch` function.
12
8
  * @typeparam T - the input type to be patched
13
9
  */
14
- export type Patch<T> = Patch.Entry<T, T>;
10
+ export type Patch<T, C = T> = Patch.Entry<T, C, T, T>;
15
11
 
16
12
  export namespace Patch {
17
13
  /**
18
14
  * The entry type for a (nested) patch. Can be either a patch object or a function accepting the nested patch function and returning a patch object.
19
15
  * @typeparam T - the input value type
16
+ * @typeparam C - a utility type
17
+ * @typeparam P - the parent type
20
18
  * @typeparam R - the root object type
21
19
  */
22
- export type Entry<T, R> =
23
- | Patch.Obj<T, R>
24
- | ((patchNested: Patch.Nested) => Patch.Obj<T, R>);
20
+ export type Entry<T, C, P, R> = IsAnyFunc<T> extends true
21
+ ? T
22
+ : IsPlainObj<T> extends true
23
+ ? Patch.WithResult<T, P, R, Patch.Obj<T, C, R>>
24
+ : Tuple.IsTuple<T> extends true
25
+ ? Patch.WithResult<T, P, R, T | Patch.Tup<T, C, R>>
26
+ : IsArray<T> extends true
27
+ ? Patch.WithResult<T, P, R, T>
28
+ : Patch.WithResult<T, P, R, T>;
25
29
 
26
30
  /**
27
- * The object patch type, allows the user to specify keys in T that should be patched, and each given key contains either a new value or a nested patch, or a function receiving
28
- * the current value, the parent object, and the root object, and returning a new value or a nested patch.
29
- * @typeparam T - the input value type
30
- * @typeparam R - the root object type
31
+ * Either result type S, or a patch function with the value type, the parent type, and the root type.
32
+ * @typeparam T - the value type
33
+ * @typeparam P - the parent type
34
+ * @typeparam R - the root type
35
+ * @typeparam S - the result type
31
36
  */
32
- export type Obj<T, R> = {
33
- [K in keyof T]?:
34
- | (T[K] extends AnyFunc ? never : ObjItem<T[K], R>)
35
- | ((
36
- cur: Protected<T[K]>,
37
- parent: Protected<T>,
38
- root: Protected<R>
39
- ) => ObjItem<T[K], R>);
40
- };
37
+ export type WithResult<T, P, R, S> = S | Patch.Func<T, P, R, S>;
41
38
 
42
39
  /**
43
- * A patch object can have as update either a new value or a nested patch object
44
- * @typeparam T - the input value type
45
- * @typeparam R - the root object type
40
+ * A function patch type that is a function taking the current value, the parent and root values,
41
+ * and returns a return value.
42
+ * @typeparam T - the value type
43
+ * @typeparam P - the parent type
44
+ * @typeparam R - the root type
45
+ * @typeparam S - the result type
46
46
  */
47
- export type ObjItem<T, R> = T | NestedObj<T, R>;
47
+ export type Func<T, P, R, S> = (
48
+ current: Protected<T>,
49
+ parent: Protected<P>,
50
+ root: Protected<R>
51
+ ) => Protected<S>;
48
52
 
49
53
  /**
50
- * The function type to create a nested Patch object.
54
+ * A type defining the allowed patch values for tuples.
55
+ * @typeparam T - the input tuple type
56
+ * @typeparam C - a utility type
57
+ * @typeparam R - the root type
51
58
  */
52
- export type Nested = typeof patchNested;
59
+ export type Tup<T, C, R> = {
60
+ [K in Tuple.KeysOf<T>]?: Patch.Entry<T[K & keyof T], C[K & keyof C], T, R>;
61
+ } & NotIterable;
53
62
 
54
63
  /**
55
- * Returns a function that patches a given `value` with the given `patchItems`.
56
- * @typeparam T - the patch value type
57
- * @typeparam Q - the input value type
58
- * @param patchItems - a number of `Patch` objects that patch a given value of type T.
59
- * @example
60
- * ```ts
61
- * const items = [{ a: 1, b: 'a' }, { a: 2, b: 'b' }]
62
- * items.map(Patch.create({ a: v => v + 1 }))
63
- * // => [{ a: 2, b: 'a' }, { a: 3, b: 'b' }]
64
- * ```
64
+ * Utility type to exclude Iterable types.
65
65
  */
66
- export function create<T, Q extends T = T>(
67
- ...patchItems: Patch<T & Q>[]
68
- ): (value: Q & PlainObj<Q>) => Q {
69
- return (value) => patch<Q>(value, ...patchItems);
70
- }
71
- }
66
+ export type NotIterable = {
67
+ [Symbol.iterator]?: never;
68
+ };
72
69
 
73
- class NestedObj<T, R, Q extends T = T> {
74
- constructor(readonly patchDataItems: Patch.Obj<Q, R>[]) {}
75
- }
70
+ /**
71
+ * A type defining the allowed patch values for objects.
72
+ * @typeparam T - the input value type
73
+ * @typeparam C - a utility type
74
+ * @typeparam R - the root object type
75
+ */
76
+ export type Obj<T, C, R> = T | Patch.ObjProps<T, C, R>[];
76
77
 
77
- /**
78
- * Returns a nested patch object based on the given `patchDataItems` that work on a subpart
79
- * of a larger object to be patched.
80
- * @typeparam T - the input value type
81
- * @typeparam R - the root object type
82
- * @typeparam Q - the patch type
83
- * @param patchDataItems - a number of `Patch` objects to be applied to the subpart of the object
84
- * @example
85
- * ```ts
86
- * patch({ a: 1, b: { c: true, d: 'a' } }, { b: patchNested({ d: 'b' }) })
87
- * // => { a: 1, b: { c: true, d: 'b' } }
88
- * ```
89
- */
90
- export function patchNested<T, R, Q extends T = T>(
91
- ...patchDataItems: Patch.Obj<Q, R>[]
92
- ): NestedObj<T, R, Q> {
93
- return new NestedObj(patchDataItems);
78
+ /**
79
+ * A type defining the allowed patch values for object properties.
80
+ * @typeparam T - the input value type
81
+ * @typeparam C - a utility type
82
+ * @typeparam R - the root object type
83
+ */
84
+ export type ObjProps<T, C, R> = {
85
+ [K in keyof C]?: K extends keyof T ? Patch.Entry<T[K], C[K], T, R> : never;
86
+ };
94
87
  }
95
88
 
96
89
  /**
97
90
  * Returns an immutably updated version of the given `value` where the given `patchItems` have been
98
91
  * applied to the result.
92
+ * The Rimbu patch notation is as follows:
93
+ * - if the target is a simple value or array, the patch can be the same type or a function returning the same type
94
+ * - if the target is a tuple (array of fixed length), the patch be the same type or an object containing numeric keys with patches indicating the tuple index to patch
95
+ * - if the target is an object, the patch can be the same type, or an array containing partial keys with their patches for the object
96
+ * @typeparam T - the type of the value to patch
97
+ * @typeparam TE - a utility type
98
+ * @typeparam TT - a utility type
99
99
  * @param value - the input value to patch
100
- * @param patchItems - the `Patch` objects to apply to the input value
100
+ * @param patchItem - the `Patch` value to apply to the input value
101
101
  * @example
102
102
  * ```ts
103
103
  * const input = { a: 1, b: { c: true, d: 'a' } }
104
- * patch(input, { a: 2 }) // => { a: 2, b: { c: true, d: 'a' } }
105
- * patch(input: ($) => ({ b: $({ c: v => !v }) }) )
104
+ * patch(input, [{ a: 2 }]) // => { a: 2, b: { c: true, d: 'a' } }
105
+ * patch(input, [{ b: [{ c: (v) => !v }] }] )
106
106
  * // => { a: 1, b: { c: false, d: 'a' } }
107
- * patch(input: ($) => ({ a: v => v + 1, b: $({ d: 'q' }) }) )
107
+ * patch(input: [{ a: (v) => v + 1, b: [{ d: 'q' }] }] )
108
108
  * // => { a: 2, b: { c: true, d: 'q' } }
109
109
  * ```
110
110
  */
111
- export function patch<T>(value: T & PlainObj<T>, ...patchItems: Patch<T>[]): T {
112
- const newValue = isPlainObj(value) ? { ...value } : value;
113
- const changedRef = { changed: false };
114
-
115
- const result = processPatch(newValue, newValue, patchItems, changedRef);
116
-
117
- if (changedRef.changed) return result;
118
-
119
- return value;
120
- }
121
-
122
- /**
123
- * Interface providing a shared reference to a `changed` boolean.
124
- */
125
- interface ChangedRef {
126
- changed: boolean;
127
- }
128
-
129
- function processPatch<T, R>(
111
+ export function patch<T, TE extends T = T, TT = T>(
130
112
  value: T,
131
- root: R,
132
- patchDataItems: Patch.Entry<T, R>[],
133
- changedRef: ChangedRef
113
+ patchItem: Patch<TE, TT>
134
114
  ): T {
135
- let i = -1;
136
- const len = patchDataItems.length;
137
-
138
- while (++i < len) {
139
- const patchItem = patchDataItems[i];
140
- if (patchItem instanceof Function) {
141
- const item = patchItem(patchNested);
142
- if (item instanceof NestedObj) {
143
- processPatch(value, root, item.patchDataItems, changedRef);
144
- } else {
145
- processPatchObj(value, root, item, changedRef);
146
- }
147
- } else {
148
- processPatchObj(value, root, patchItem, changedRef);
149
- }
150
- }
151
-
152
- return value;
115
+ return patchEntry(value, value, value, patchItem as Patch<T>);
153
116
  }
154
117
 
155
- function processPatchObj<T, R>(
118
+ function patchEntry<T, C, P, R>(
156
119
  value: T,
120
+ parent: P,
157
121
  root: R,
158
- patchData: Patch.Obj<T, R>,
159
- changedRef: ChangedRef
122
+ patchItem: Patch.Entry<T, C, P, R>
160
123
  ): T {
161
- if (undefined === value || null === value) {
124
+ if (Object.is(value, patchItem)) {
125
+ // patching a value with itself never changes the value
162
126
  return value;
163
127
  }
164
128
 
165
- if (!isPlainObj(patchData)) {
166
- RimbuError.throwInvalidUsageError(
167
- 'patch: received patch object should be a plain object'
168
- );
129
+ if (typeof value === 'function') {
130
+ // function input, directly return patch
131
+ return patchItem as T;
169
132
  }
170
133
 
171
- if (!isPlainObj(value)) {
172
- RimbuError.throwInvalidUsageError(
173
- 'patch: received source object should be a plain object'
174
- );
134
+ if (typeof patchItem === 'function') {
135
+ // function patch always needs to be resolved first
136
+ const item = patchItem(value, parent, root);
137
+
138
+ return patchEntry(value, parent, root, item);
175
139
  }
176
140
 
177
- for (const key in patchData) {
178
- const target = value[key];
141
+ if (isPlainObj(value)) {
142
+ // value is plain object
143
+ return patchPlainObj(value, root, patchItem as any);
144
+ }
179
145
 
180
- // prevent prototype pollution
181
- if (
182
- key === '__proto__' ||
183
- (key === 'constructor' && target instanceof Function)
184
- ) {
185
- RimbuError.throwInvalidUsageError(
186
- `patch: received patch object key '${key}' which is not allowed to prevent prototype pollution`
187
- );
188
- }
146
+ if (Array.isArray(value)) {
147
+ // value is tuple or array
148
+ return patchArr(value, root, patchItem as any);
149
+ }
189
150
 
190
- const update = patchData[key];
151
+ // value is primitive type or complex object
191
152
 
192
- if (!(key in value) && update instanceof Function) {
193
- RimbuError.throwInvalidUsageError(
194
- `patch: received update function object key ${key} but the key was not present in the source object. Either explicitely set the value in the source to undefined or use a direct value.`
195
- );
196
- }
153
+ return patchItem as T;
154
+ }
197
155
 
198
- if (undefined === update) {
199
- RimbuError.throwInvalidUsageError(
200
- "patch: received 'undefined' as patch value. Due to type system issues we cannot prevent this through typing, but please use '() => undefined' or '() => yourVar' instead. This value will be ignored for safety."
201
- );
202
- }
156
+ function patchPlainObj<T, C, R>(
157
+ value: T,
158
+ root: R,
159
+ patchItem: T | Patch.Obj<T, C, R>
160
+ ): T {
161
+ if (!Array.isArray(patchItem)) {
162
+ // the patch is a complete replacement of the current value
203
163
 
204
- let newValue: typeof target;
164
+ return patchItem as T;
165
+ }
166
+
167
+ // patch is an array of partial updates
168
+
169
+ // copy the input value
170
+ const result = { ...value };
171
+
172
+ let anyChange = false;
173
+
174
+ // loop over patches in array
175
+ for (const entry of patchItem) {
176
+ // update root if needed
177
+ const currentRoot = (value as any) === root ? { ...result } : root;
205
178
 
206
- if (update instanceof Function) {
207
- newValue = processPatchObjItem(
208
- target,
209
- root,
210
- update(target, value, root),
211
- changedRef
179
+ // loop over all the patch keys
180
+ for (const key in entry as T) {
181
+ // patch the value at the given key with the patch at that key
182
+ const currentValue = result[key];
183
+ const newValue = patchEntry(
184
+ currentValue,
185
+ value,
186
+ currentRoot,
187
+ (entry as any)[key]
212
188
  );
213
- } else {
214
- newValue = processPatchObjItem(
215
- target,
216
- root,
217
- update as any,
218
- changedRef
219
- ) as any;
220
- }
221
189
 
222
- if (!Object.is(newValue, target)) {
223
- value[key] = newValue;
224
- changedRef.changed = true;
190
+ if (!Object.is(currentValue, newValue)) {
191
+ // if value changed, set it in result and mark change
192
+ anyChange = true;
193
+ result[key] = newValue;
194
+ }
225
195
  }
226
196
  }
227
197
 
198
+ if (anyChange) {
199
+ // something changed, return new value
200
+ return result;
201
+ }
202
+
203
+ // nothing changed, return old value
228
204
  return value;
229
205
  }
230
206
 
231
- function processPatchObjItem<T, R>(
207
+ function patchArr<T extends any[], C, R>(
232
208
  value: T,
233
209
  root: R,
234
- patchResult: Patch.ObjItem<T, R>,
235
- superChangedRef: ChangedRef
210
+ patchItem: T | Patch.Tup<T, C, R>
236
211
  ): T {
237
- if (patchResult instanceof NestedObj) {
238
- const newValue = isPlainObj(value) ? { ...value } : value;
239
- const changedRef = { changed: false };
212
+ if (Array.isArray(patchItem)) {
213
+ // value is a normal array
214
+ // patch is a complete replacement of current array
240
215
 
241
- const result = processPatch(
242
- newValue,
216
+ return patchItem;
217
+ }
218
+
219
+ // value is a tuple
220
+ // patch is an object containing numeric keys with function values
221
+ // that update the tuple at the given indices
222
+
223
+ // copy the tuple
224
+ const result = [...value] as T;
225
+ let anyChange = false;
226
+
227
+ // loop over all index keys in object
228
+ for (const index in patchItem) {
229
+ const numIndex = index as any as number;
230
+
231
+ // patch the tuple at the given index
232
+ const currentValue = result[numIndex];
233
+ const newValue = patchEntry(
234
+ currentValue,
235
+ value,
243
236
  root,
244
- patchResult.patchDataItems,
245
- changedRef
237
+ (patchItem as any)[index]
246
238
  );
247
239
 
248
- if (changedRef.changed) {
249
- superChangedRef.changed = true;
250
- return result;
240
+ if (!Object.is(newValue, currentValue)) {
241
+ // if value changed, set it in result and mark change
242
+ anyChange = true;
243
+ result[numIndex] = newValue;
251
244
  }
245
+ }
252
246
 
253
- return value;
247
+ if (anyChange) {
248
+ // something changed, return new value
249
+ return result;
254
250
  }
255
251
 
256
- return patchResult;
252
+ // nothing changed, return old value
253
+ return value;
257
254
  }