@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
@@ -1,53 +1,196 @@
1
- import type { Update } from '@rimbu/common';
2
- import type { IsPlainObj, PlainObj } from '@rimbu/base';
3
- /**
4
- * A string representing a path into an (nested) object of type T.
5
- * @typeparam T - the object type to select in
6
- * @example
7
- * ```ts
8
- * const p: Path<{ a: { b: { c : 5 }}}> = 'a.b'
9
- * ```
10
- */
11
- export declare type Path<T> = IsPlainObj<T> extends true ? {
12
- [K in string & keyof T]: `${K}` | `${K}.${Path<T[K]>}`;
13
- }[string & keyof T] : never;
1
+ import type { IsAnyFunc, IsArray, IsPlainObj } from '@rimbu/base';
2
+ import { Patch } from './internal';
3
+ import type { Tuple } from './tuple';
14
4
  export declare namespace Path {
15
5
  /**
16
- * The result type when selecting from object type T a path with type P.
6
+ * A string representing a path into an (nested) object of type T.
17
7
  * @typeparam T - the object type to select in
18
- * @typeparam P - a Path in object type T
19
8
  * @example
20
9
  * ```ts
21
- * let r!: Path.Result<{ a: { b: { c: number } } }, 'a.b'>;
22
- * // => type of r: { c: number }
10
+ * const p: Path.Get<{ a: { b: { c : 5 } } }> = 'a.b'
23
11
  * ```
24
12
  */
25
- type Result<T, P extends Path<T> = Path<T>> = T extends Record<string, unknown> ? P extends `${infer Head}.${infer Rest}` ? Head extends keyof T ? Path.Result<T[Head], Rest & Path<T[Head]>> : never : P extends `${infer K}` ? T[K] : never : never;
13
+ type Get<T> = Path.Internal.Generic<T, false, false, true>;
26
14
  /**
27
- * Returns the value resulting from selecting the given `path` in the given `source` object.
15
+ * A string representing a path into an (nested) object of type T.
28
16
  * @typeparam T - the object type to select in
29
- * @typeparam P - a Path in object type T
30
- * @param source - the object to select in
31
- * @param path - the path into the object
32
17
  * @example
33
18
  * ```ts
34
- * console.log(Path.get({ a: { b: { c: 5 } } }), 'a.b')
35
- * // => { c: 5 }
36
- * console.log(Path.get({ a: { b: { c: 5 } } }), 'a.b.c')
37
- * // => 5
19
+ * const p: Path.Set<{ a: { b: { c : 5 } } }> = 'a.b'
38
20
  * ```
39
21
  */
40
- function get<T, P extends Path<T> = Path<T>>(source: T & PlainObj<T>, path: P): Path.Result<T, P>;
22
+ type Set<T> = Path.Internal.Generic<T, true, false, true>;
23
+ namespace Internal {
24
+ /**
25
+ * Determines the allowed paths into a value of type `T`.
26
+ * @typeparam T - the source type
27
+ * @typeparam Write - if true the path should be writable (no optional chaining)
28
+ * @typeparam Maybe - if true the value at the current path is optional
29
+ * @typeparam First - if true this is the root call
30
+ * @note type is mapped as template literal to prevent non-string types to leak through
31
+ */
32
+ type Generic<T, Write extends boolean, Maybe extends boolean, First extends boolean = false> = `${IsAnyFunc<T> extends true ? '' : // empty string is always an option
33
+ '' | Path.Internal.NonEmpty<T, Write, Maybe, First>}`;
34
+ /**
35
+ * Determines the allowed non-empty paths into a value of type `T`.
36
+ * @typeparam T - the source type
37
+ * @typeparam Write - if true the path should be writable (no optional chaining)
38
+ * @typeparam Maybe - if true the value at the current path is optional
39
+ * @typeparam First - if true this is the root call
40
+ */
41
+ type NonEmpty<T, Write extends boolean, Maybe extends boolean, First extends boolean> = Path.Internal.IsOptional<T> extends true ? Write extends false ? Path.Internal.Generic<Exclude<T, undefined | null>, Write, true> : never : `${Path.Internal.Separator<First, Maybe, IsArray<T>>}${Path.Internal.NonOptional<T, Write, Maybe>}`;
42
+ /**
43
+ * Determines the allowed paths into a non-optional value of type `T`.
44
+ * @typeparam T - the source type
45
+ * @typeparam Write - if true the path should be writable (no optional chaining)
46
+ * @typeparam Maybe - if true the value at the current path is optional
47
+ * @typeparam First - if true this is the root call
48
+ */
49
+ type NonOptional<T, Write extends boolean, Maybe extends boolean> = Tuple.IsTuple<T> extends true ? Path.Internal.Tup<T, Write, Maybe> : T extends readonly any[] ? Write extends false ? Path.Internal.Arr<T> : never : IsPlainObj<T> extends true ? Path.Internal.Obj<T, Write, Maybe> : never;
50
+ /**
51
+ * Determines the allowed paths for a tuple. Since tuples have fixed types, they do not
52
+ * need to be optional, in contrast to arrays.
53
+ * @typeparam T - the input tuple type
54
+ * @typeparam Write - if true the path should be writable (no optional chaining)
55
+ * @typeparam Maybe - if true the value at the current path is optional
56
+ */
57
+ type Tup<T, Write extends boolean, Maybe extends boolean> = {
58
+ [K in Tuple.KeysOf<T>]: `[${K}]${Path.Internal.Generic<T[K], Write, Maybe>}`;
59
+ }[Tuple.KeysOf<T>];
60
+ /**
61
+ * Determines the allowed paths for an array.
62
+ * @typeparam T - the input array type
63
+ */
64
+ type Arr<T extends readonly any[]> = `[${number}]${Path.Internal.Generic<T[number], false, true>}`;
65
+ /**
66
+ * Determines the allowed paths for an object.
67
+ * @typeparam T - the input object type
68
+ * @typeparam Write - if true the path should be writable (no optional chaining)
69
+ * @typeparam Maybe - if true the value at the current path is optional
70
+ */
71
+ type Obj<T, Write extends boolean, Maybe extends boolean> = {
72
+ [K in keyof T]: `${K & string}${Path.Internal.Generic<T[K], Write, Write extends true ? false : Path.Internal.IsOptional<T[K], true, Maybe>>}`;
73
+ }[keyof T];
74
+ /**
75
+ * Determines the allowed path part seperator based on the input types.
76
+ * @typeparam First - if true, this is the first call
77
+ * @typeparam Maybe - if true, the value is optional
78
+ * @typeparam IsArray - if true, the value is an array
79
+ */
80
+ type Separator<First extends boolean, Maybe extends boolean, IsArray extends boolean> = Maybe extends true ? First extends true ? never : '?.' : First extends true ? '' : IsArray extends true ? '' : '.';
81
+ /**
82
+ * Determines whether the given type `T` is optional, that is, whether it can be null or undefined.
83
+ * @typeparam T - the input type
84
+ * @typeparam True - the value to return if `T` is optional
85
+ * @typeparam False - the value to return if `T` is mandatory
86
+ */
87
+ type IsOptional<T, True = true, False = false> = undefined extends T ? True : null extends T ? True : False;
88
+ /**
89
+ * Returns type `T` if `Maybe` is false, `T | undefined` otherwise.
90
+ * @typeparam T - the input type
91
+ * @typeparam Maybe - if true, the return type value should be optional
92
+ */
93
+ type MaybeValue<T, Maybe extends boolean> = Maybe extends true ? T | undefined : T;
94
+ /**
95
+ * Utility type to only add non-empty string types to a string array.
96
+ * @typeparma A - the input string array
97
+ * @typeparam T - the string value to optionally add
98
+ */
99
+ type AppendIfNotEmpty<A extends string[], T extends string> = T extends '' ? A : [
100
+ ...A,
101
+ T
102
+ ];
103
+ }
41
104
  /**
42
- * Sets the value at the given path in the source to the given value.
43
- * @param source - the object to update
44
- * @param path - the path in the object to update
45
- * @param value - the new value to set at the given position
105
+ * The result type when selecting from object type T a path with type P.
106
+ * @typeparam T - the object type to select in
107
+ * @typeparam P - a Path in object type T
46
108
  * @example
47
109
  * ```ts
48
- * console.log(Path.update({ a: { b: { c: 5 } } }, 'a.b.c', v => v + 5)
49
- * // => { a: { b: { c: 6 } } }
110
+ * let r!: Path.Result<{ a: { b: { c: number } } }, 'a.b'>;
111
+ * // => type of r: { c: number }
50
112
  * ```
51
113
  */
52
- function update<T, P extends Path<T> = Path<T>>(source: T & PlainObj<T>, path: P, value: Update<Path.Result<T, P>>): T;
114
+ type Result<T, P extends string> = Path.Result.For<T, Path.Result.Tokenize<P>, false>;
115
+ namespace Result {
116
+ /**
117
+ * Determines the result type for an array of tokens representing subpaths in type `T`.
118
+ * @typeparam T - the current source type
119
+ * @typeparam Tokens - an array of elements indicating a path into the source type
120
+ * @typeparam Maybe - if true indicates that the path may be undefined
121
+ */
122
+ type For<T, Tokens, Maybe extends boolean = Path.Internal.IsOptional<T>> = Tokens extends [] ? Path.Internal.MaybeValue<T, Maybe> : Path.Internal.IsOptional<T> extends true ? Path.Result.For<Exclude<T, undefined | null>, Tokens, Maybe> : Tokens extends ['?.', infer Key, ...infer Rest] ? Path.Result.For<Path.Result.Part<T, Key, Maybe>, Rest, true> : Tokens extends ['.', infer Key, ...infer Rest] ? Path.Result.For<Path.Result.Part<T, Key, false>, Rest, Maybe> : Tokens extends [infer Key, ...infer Rest] ? Path.Result.For<Path.Result.Part<T, Key, false>, Rest, Maybe> : never;
123
+ /**
124
+ * Determines the result of getting the property/index `K` from type `T`, taking into
125
+ * account that the value may be optional.
126
+ * @typeparam T - the current source type
127
+ * @typeparam K - the key to get from the source type
128
+ * @typeparam Maybe - if true indicates that the path may be undefined
129
+ */
130
+ type Part<T, K, Maybe extends boolean> = IsArray<T> extends true ? Path.Internal.MaybeValue<T[K & keyof T], Tuple.IsTuple<T> extends true ? Maybe : true> : Path.Internal.MaybeValue<T[K & keyof T], Maybe>;
131
+ /**
132
+ * Converts a path string into separate tokens in a string array.
133
+ * @typeparam P - the literal string path type
134
+ * @typeparam Token - the token currently being produced
135
+ * @typeparam Res - the resulting literal string token array
136
+ */
137
+ type Tokenize<P extends string, Token extends string = '', Res extends string[] = []> = P extends '' ? Path.Internal.AppendIfNotEmpty<Res, Token> : P extends `[${infer Index}]${infer Rest}` ? Tokenize<Rest, '', [
138
+ ...Path.Internal.AppendIfNotEmpty<Res, Token>,
139
+ Index
140
+ ]> : P extends `?.${infer Rest}` ? Tokenize<Rest, '', [
141
+ ...Path.Internal.AppendIfNotEmpty<Res, Token>,
142
+ '?.'
143
+ ]> : P extends `.${infer Rest}` ? Tokenize<Rest, '', [...Path.Internal.AppendIfNotEmpty<Res, Token>, '.']> : P extends `${infer First}${infer Rest}` ? Tokenize<Rest, `${Token}${First}`, Res> : never;
144
+ }
145
+ /**
146
+ * Regular expression used to split a path string into tokens.
147
+ */
148
+ const stringSplitRegex: RegExp;
149
+ /**
150
+ * The allowed values of a split path.
151
+ */
152
+ type StringSplit = (string | number | undefined)[];
153
+ /**
154
+ * Return the given `path` string split into an array of subpaths.
155
+ * @param path - the input string path
156
+ */
157
+ function stringSplit(path: string): Path.StringSplit;
53
158
  }
159
+ /**
160
+ * Returns the value resulting from selecting the given `path` in the given `source` object.
161
+ * It supports optional chaining for nullable values or values that may be undefined, and also
162
+ * for accessing objects inside an array.
163
+ * There is currently no support for forcing non-null (the `!` operator).
164
+ * @typeparam T - the object type to select in
165
+ * @typeparam P - a Path in object type T
166
+ * @param source - the object to select in
167
+ * @param path - the path into the object
168
+ * @example
169
+ * ```ts
170
+ * const value = { a: { b: { c: [{ d: 5 }, { d: 6 }] } } }
171
+ * Deep.getAt(value, 'a.b');
172
+ * // => { c: 5 }
173
+ * Deep.getAt(value, 'a.b.c');
174
+ * // => [{ d: 5 }, { d: 5 }]
175
+ * Deep.getAt(value, 'a.b.c[1]');
176
+ * // => { d: 6 }
177
+ * Deep.getAt(value, 'a.b.c[1]?.d');
178
+ * // => 6
179
+ * ```
180
+ */
181
+ export declare function getAt<T, P extends Path.Get<T>>(source: T, path: P): Path.Result<T, P>;
182
+ /**
183
+ * Patches the value at the given path in the source to the given value.
184
+ * Because the path to update must exist in the `source` object, optional
185
+ * chaining and array indexing is not allowed.
186
+ * @param source - the object to update
187
+ * @param path - the path in the object to update
188
+ * @param patchItem - the patch for the value at the given path
189
+ * @example
190
+ * ```ts
191
+ * const value = { a: { b: { c: 5 } } };
192
+ * Deep.patchAt(value, 'a.b.c', v => v + 5);
193
+ * // => { a: { b: { c: 6 } } }
194
+ * ```
195
+ */
196
+ export declare function patchAt<T, P extends Path.Set<T>, C = Path.Result<T, P>>(source: T, path: P, patchItem: Patch<Path.Result<T, P>, C>): T;
@@ -12,21 +12,6 @@ import type { IsAny, IsPlainObj } from '@rimbu/base';
12
12
  */
13
13
  export declare type Protected<T> = IsAny<T> extends true ? T : T extends readonly any[] & infer A ? {
14
14
  readonly [K in keyof A]: Protected<A[K]>;
15
- } : T extends Map<infer K, infer V> ? Map<Protected<K>, Protected<V>> : T extends Set<infer E> ? Set<Protected<E>> : T extends Promise<infer E> ? Promise<Protected<E>> : IsPlainObj<T> extends true ? {
15
+ } : T extends Map<infer K, infer V> ? Omit<Map<Protected<K>, Protected<V>>, 'clear' | 'delete' | 'set'> : T extends Set<infer E> ? Omit<Set<Protected<E>>, 'add' | 'clear' | 'delete'> : T extends Promise<infer E> ? Promise<Protected<E>> : IsPlainObj<T> extends true ? {
16
16
  readonly [K in keyof T]: Protected<T[K]>;
17
17
  } : T;
18
- /**
19
- * Returns the same value wrapped in the `Protected` type.
20
- * @param value - the value to wrap
21
- * @note does not perform any runtime protection, it is only a utility to easily add the `Protected`
22
- * type to a value
23
- * @example
24
- * ```ts
25
- * const obj = Protected({ a: 1, b: { c: true, d: [1] } })
26
- * obj.a = 2 // compiler error: a is readonly
27
- * obj.b.c = false // compiler error: c is readonly
28
- * obj.b.d.push(2) // compiler error: d is a readonly array
29
- * (obj as any).b.d.push(2) // will actually mutate the object
30
- * ```
31
- */
32
- export declare function Protected<T>(value: T): Protected<T>;
@@ -0,0 +1,47 @@
1
+ import type { IsAnyFunc, IsArray } from '@rimbu/base';
2
+ import { Path, type Protected } from './internal';
3
+ /**
4
+ * Type defining the allowed selectors on an object of type `T`.
5
+ * Selectors can be:
6
+ * - a path string into type `T`.
7
+ * - a function receiving a `Protected` version of type `T`, and returning an arbitrary value.
8
+ * - a tuple of `Selectors` for type `T`
9
+ * - an object where the property values are `Selectors` for type `T`.
10
+ * @typeparam T - the source value type.
11
+ */
12
+ export declare type Selector<T> = Path.Get<T> | ((value: Protected<T>) => any) | readonly Selector<T>[] | {
13
+ readonly [key: string | symbol]: Selector<T>;
14
+ };
15
+ export declare namespace Selector {
16
+ /**
17
+ * Type defining the shape of allowed selectors, used to improve compiler checking.
18
+ * @typeparam SL - the selector type
19
+ */
20
+ type Shape<SL> = IsAnyFunc<SL> extends true ? SL : IsArray<SL> extends true ? readonly [...(SL extends readonly unknown[] ? SL : never)] : SL extends {
21
+ readonly [key: string | number | symbol]: unknown;
22
+ } ? {
23
+ readonly [K in keyof SL]: Selector.Shape<SL[K]>;
24
+ } : SL;
25
+ /**
26
+ * Type defining the result type of applying the SL selector type to the T value type.
27
+ * @typeparam T - the source value type
28
+ * @typeparam SL - the selector type
29
+ */
30
+ type Result<T, SL> = Selector<T> extends SL ? never : SL extends (...args: any[]) => infer R ? R : SL extends string ? Path.Result<T, SL> : {
31
+ readonly [K in keyof SL]: Selector.Result<T, SL[K]>;
32
+ };
33
+ }
34
+ /**
35
+ * Returns the result of applying the given `selector` shape to the given `source` value.
36
+ * @typeparam T - the patch value type
37
+ * @typeparam SL - the selector shape type
38
+ * @param source - the source value to select from
39
+ * @param selector - a shape indicating the selection from the source values
40
+ * @example
41
+ * ```ts
42
+ * const item = { a: { b: 1, c: 'a' } };
43
+ * Deep.select(item, { q: 'a.c', y: ['a.b', 'a.c'], z: (v) => v.a.b + 1 });
44
+ * // => { q: 'a', y: [1, 'a'], z: 2 }
45
+ * ```
46
+ */
47
+ export declare function select<T, SL extends Selector<T>>(source: T, selector: Selector.Shape<SL>): Selector.Result<T, SL>;
@@ -12,6 +12,16 @@ export declare namespace Tuple {
12
12
  * A readonly array that can serve as a source for a Tuple.
13
13
  */
14
14
  type Source = readonly unknown[];
15
+ type IsTuple<T> = T extends {
16
+ length: infer L;
17
+ } ? 0 extends L ? false : true : false;
18
+ /**
19
+ * Returns the indices/keys that are in a tuple.
20
+ * @typeparam T - the input tuple type
21
+ */
22
+ type KeysOf<T> = {
23
+ [K in keyof T]: K;
24
+ }[keyof T & number];
15
25
  /**
16
26
  * Convenience method to type Tuple types
17
27
  * @param values - the values of the tuple
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rimbu/deep",
3
- "version": "0.11.2",
3
+ "version": "0.12.0",
4
4
  "description": "Tools to use handle plain JS objects as immutable objects",
5
5
  "keywords": [
6
6
  "immutable",
@@ -57,8 +57,8 @@
57
57
  },
58
58
  "sideEffects": false,
59
59
  "dependencies": {
60
- "@rimbu/base": "^0.9.4",
61
- "@rimbu/common": "^0.10.2",
60
+ "@rimbu/base": "^0.10.0",
61
+ "@rimbu/common": "^0.10.3",
62
62
  "tslib": "^2.4.0"
63
63
  },
64
64
  "publishConfig": {
@@ -68,5 +68,5 @@
68
68
  "index": "src/index.ts",
69
69
  "replacer": "../../config/denoify-rimbu-replacer.js"
70
70
  },
71
- "gitHead": "0d1677b4df9c1b0f1dc337eb38eea7a99ea7fc43"
71
+ "gitHead": "22ae1cadeb885e7fd30a23bd05c001cda3141e86"
72
72
  }