@rimbu/deep 0.11.3 → 0.12.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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 +3 -3
  45. package/src/deep.ts +364 -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/path.ts CHANGED
@@ -1,104 +1,430 @@
1
- import type { Update } from '@rimbu/common';
2
- import type { IsPlainObj, PlainObj } from '@rimbu/base';
3
-
4
- import { patch, patchNested } from './internal';
5
-
6
- /**
7
- * A string representing a path into an (nested) object of type T.
8
- * @typeparam T - the object type to select in
9
- * @example
10
- * ```ts
11
- * const p: Path<{ a: { b: { c : 5 }}}> = 'a.b'
12
- * ```
13
- */
14
- export type Path<T> = IsPlainObj<T> extends true
15
- ? { [K in string & keyof T]: `${K}` | `${K}.${Path<T[K]>}` }[string & keyof T]
16
- : never;
1
+ import type { IsAnyFunc, IsArray, IsPlainObj } from '@rimbu/base';
2
+ import { Deep, Patch } from './internal';
3
+ import type { Tuple } from './tuple';
17
4
 
18
5
  export namespace Path {
19
6
  /**
20
- * The result type when selecting from object type T a path with type P.
7
+ * A string representing a path into an (nested) object of type T.
21
8
  * @typeparam T - the object type to select in
22
- * @typeparam P - a Path in object type T
23
9
  * @example
24
10
  * ```ts
25
- * let r!: Path.Result<{ a: { b: { c: number } } }, 'a.b'>;
26
- * // => type of r: { c: number }
11
+ * const p: Path.Get<{ a: { b: { c : 5 } } }> = 'a.b'
27
12
  * ```
28
13
  */
29
- export type Result<T, P extends Path<T> = Path<T>> = T extends Record<
30
- string,
31
- unknown
32
- >
33
- ? P extends `${infer Head}.${infer Rest}`
34
- ? Head extends keyof T
35
- ? Path.Result<T[Head], Rest & Path<T[Head]>>
36
- : never
37
- : P extends `${infer K}`
38
- ? T[K]
39
- : never
40
- : never;
14
+ export type Get<T> = Path.Internal.Generic<T, false, false, true>;
41
15
 
42
16
  /**
43
- * Returns the value resulting from selecting the given `path` in the given `source` object.
17
+ * A string representing a path into an (nested) object of type T.
44
18
  * @typeparam T - the object type to select in
45
- * @typeparam P - a Path in object type T
46
- * @param source - the object to select in
47
- * @param path - the path into the object
48
19
  * @example
49
20
  * ```ts
50
- * console.log(Path.get({ a: { b: { c: 5 } } }), 'a.b')
51
- * // => { c: 5 }
52
- * console.log(Path.get({ a: { b: { c: 5 } } }), 'a.b.c')
53
- * // => 5
21
+ * const p: Path.Set<{ a: { b: { c : 5 } } }> = 'a.b'
54
22
  * ```
55
23
  */
56
- export function get<T, P extends Path<T> = Path<T>>(
57
- source: T & PlainObj<T>,
58
- path: P
59
- ): Path.Result<T, P> {
60
- const items = path.split('.');
24
+ export type Set<T> = Path.Internal.Generic<T, true, false, true>;
61
25
 
62
- let result: any = source;
26
+ export namespace Internal {
27
+ /**
28
+ * Determines the allowed paths into a value of type `T`.
29
+ * @typeparam T - the source type
30
+ * @typeparam Write - if true the path should be writable (no optional chaining)
31
+ * @typeparam Maybe - if true the value at the current path is optional
32
+ * @typeparam First - if true this is the root call
33
+ * @note type is mapped as template literal to prevent non-string types to leak through
34
+ */
35
+ export type Generic<
36
+ T,
37
+ Write extends boolean,
38
+ Maybe extends boolean,
39
+ First extends boolean = false
40
+ > = `${IsAnyFunc<T> extends true
41
+ ? // functions can not be further decomposed
42
+ ''
43
+ : // empty string is always an option
44
+ '' | Path.Internal.NonEmpty<T, Write, Maybe, First>}`;
63
45
 
64
- for (const item of items) {
65
- result = result[item];
66
- }
46
+ /**
47
+ * Determines the allowed non-empty paths into a value of type `T`.
48
+ * @typeparam T - the source type
49
+ * @typeparam Write - if true the path should be writable (no optional chaining)
50
+ * @typeparam Maybe - if true the value at the current path is optional
51
+ * @typeparam First - if true this is the root call
52
+ */
53
+ export type NonEmpty<
54
+ T,
55
+ Write extends boolean,
56
+ Maybe extends boolean,
57
+ First extends boolean
58
+ > = Path.Internal.IsOptional<T> extends true
59
+ ? // the value T may be null or undefined, check whether further chaining is allowed
60
+ Write extends false
61
+ ? // path is not used to write to, so optional chaining is allowed
62
+ Path.Internal.Generic<Exclude<T, undefined | null>, Write, true>
63
+ : // path can be written to, no optional chaining allowed
64
+ never
65
+ : // determine separator, and continue with non-optional value
66
+ `${Path.Internal.Separator<
67
+ First,
68
+ Maybe,
69
+ IsArray<T>
70
+ >}${Path.Internal.NonOptional<T, Write, Maybe>}`;
71
+
72
+ /**
73
+ * Determines the allowed paths into a non-optional value of type `T`.
74
+ * @typeparam T - the source type
75
+ * @typeparam Write - if true the path should be writable (no optional chaining)
76
+ * @typeparam Maybe - if true the value at the current path is optional
77
+ * @typeparam First - if true this is the root call
78
+ */
79
+ export type NonOptional<
80
+ T,
81
+ Write extends boolean,
82
+ Maybe extends boolean
83
+ > = Tuple.IsTuple<T> extends true
84
+ ? // determine allowed paths for tuple
85
+ Path.Internal.Tup<T, Write, Maybe>
86
+ : T extends readonly any[]
87
+ ? // determine allowed paths for array
88
+ Write extends false
89
+ ? // path is not writable so arrays are allowed
90
+ Path.Internal.Arr<T>
91
+ : // path is writable, no arrays allowed
92
+ never
93
+ : IsPlainObj<T> extends true
94
+ ? // determine allowed paths for object
95
+ Path.Internal.Obj<T, Write, Maybe>
96
+ : // no match
97
+ never;
98
+
99
+ /**
100
+ * Determines the allowed paths for a tuple. Since tuples have fixed types, they do not
101
+ * need to be optional, in contrast to arrays.
102
+ * @typeparam T - the input tuple type
103
+ * @typeparam Write - if true the path should be writable (no optional chaining)
104
+ * @typeparam Maybe - if true the value at the current path is optional
105
+ */
106
+ export type Tup<T, Write extends boolean, Maybe extends boolean> = {
107
+ [K in Tuple.KeysOf<T>]: `[${K}]${Path.Internal.Generic<
108
+ T[K],
109
+ Write,
110
+ Maybe
111
+ >}`;
112
+ }[Tuple.KeysOf<T>];
113
+
114
+ /**
115
+ * Determines the allowed paths for an array.
116
+ * @typeparam T - the input array type
117
+ */
118
+ export type Arr<T extends readonly any[]> =
119
+ // first `[index]` and then the rest of the path, which cannot be Write (since optional) and must be Maybe
120
+ `[${number}]${Path.Internal.Generic<T[number], false, true>}`;
121
+
122
+ /**
123
+ * Determines the allowed paths for an object.
124
+ * @typeparam T - the input object type
125
+ * @typeparam Write - if true the path should be writable (no optional chaining)
126
+ * @typeparam Maybe - if true the value at the current path is optional
127
+ */
128
+ export type Obj<T, Write extends boolean, Maybe extends boolean> = {
129
+ [K in keyof T]: `${K & string}${Path.Internal.Generic<
130
+ T[K],
131
+ Write,
132
+ // If writable (not optional), Maybe is false. If value is optional, Maybe is true. Otherwise, forward current Maybe.
133
+ Write extends true ? false : Path.Internal.IsOptional<T[K], true, Maybe>
134
+ >}`;
135
+ }[keyof T];
136
+
137
+ /**
138
+ * Determines the allowed path part seperator based on the input types.
139
+ * @typeparam First - if true, this is the first call
140
+ * @typeparam Maybe - if true, the value is optional
141
+ * @typeparam IsArray - if true, the value is an array
142
+ */
143
+ export type Separator<
144
+ First extends boolean,
145
+ Maybe extends boolean,
146
+ IsArray extends boolean
147
+ > = Maybe extends true
148
+ ? First extends true
149
+ ? // first optional value cannot have separator
150
+ never
151
+ : // non-first optional value must have separator
152
+ '?.'
153
+ : First extends true
154
+ ? // first non-optional value has empty separator
155
+ ''
156
+ : IsArray extends true
157
+ ? // array selectors do not have separator
158
+ ''
159
+ : // normal separator
160
+ '.';
67
161
 
68
- return result;
162
+ /**
163
+ * Determines whether the given type `T` is optional, that is, whether it can be null or undefined.
164
+ * @typeparam T - the input type
165
+ * @typeparam True - the value to return if `T` is optional
166
+ * @typeparam False - the value to return if `T` is mandatory
167
+ */
168
+ export type IsOptional<T, True = true, False = false> = undefined extends T
169
+ ? // is optional
170
+ True
171
+ : null extends T
172
+ ? // is optional
173
+ True
174
+ : // not optional
175
+ False;
176
+
177
+ /**
178
+ * Returns type `T` if `Maybe` is false, `T | undefined` otherwise.
179
+ * @typeparam T - the input type
180
+ * @typeparam Maybe - if true, the return type value should be optional
181
+ */
182
+ export type MaybeValue<T, Maybe extends boolean> = Maybe extends true
183
+ ? T | undefined
184
+ : T;
185
+
186
+ /**
187
+ * Utility type to only add non-empty string types to a string array.
188
+ * @typeparma A - the input string array
189
+ * @typeparam T - the string value to optionally add
190
+ */
191
+ export type AppendIfNotEmpty<
192
+ A extends string[],
193
+ T extends string
194
+ > = T extends ''
195
+ ? // empty string, do not add
196
+ A
197
+ : // non-empty string, add to array
198
+ [...A, T];
69
199
  }
70
200
 
71
201
  /**
72
- * Sets the value at the given path in the source to the given value.
73
- * @param source - the object to update
74
- * @param path - the path in the object to update
75
- * @param value - the new value to set at the given position
202
+ * The result type when selecting from object type T a path with type P.
203
+ * @typeparam T - the object type to select in
204
+ * @typeparam P - a Path in object type T
76
205
  * @example
77
206
  * ```ts
78
- * console.log(Path.update({ a: { b: { c: 5 } } }, 'a.b.c', v => v + 5)
79
- * // => { a: { b: { c: 6 } } }
207
+ * let r!: Path.Result<{ a: { b: { c: number } } }, 'a.b'>;
208
+ * // => type of r: { c: number }
80
209
  * ```
81
210
  */
82
- export function update<T, P extends Path<T> = Path<T>>(
83
- source: T & PlainObj<T>,
84
- path: P,
85
- value: Update<Path.Result<T, P>>
86
- ): T {
87
- const items = path.split('.');
88
- const last = items.pop()!;
89
-
90
- const root: Record<string, any> = {};
91
-
92
- let current = root;
93
-
94
- for (const item of items) {
95
- const next = {};
96
- current[item] = patchNested(next);
97
- current = next;
211
+ export type Result<T, P extends string> = Path.Result.For<
212
+ T,
213
+ Path.Result.Tokenize<P>,
214
+ false
215
+ >;
216
+
217
+ export namespace Result {
218
+ /**
219
+ * Determines the result type for an array of tokens representing subpaths in type `T`.
220
+ * @typeparam T - the current source type
221
+ * @typeparam Tokens - an array of elements indicating a path into the source type
222
+ * @typeparam Maybe - if true indicates that the path may be undefined
223
+ */
224
+ export type For<
225
+ T,
226
+ Tokens,
227
+ Maybe extends boolean = Path.Internal.IsOptional<T>
228
+ > = Tokens extends []
229
+ ? // no more token
230
+ Path.Internal.MaybeValue<T, Maybe>
231
+ : Path.Internal.IsOptional<T> extends true
232
+ ? // T can be null or undefined, so continue with Maybe set to true
233
+ Path.Result.For<Exclude<T, undefined | null>, Tokens, Maybe>
234
+ : Tokens extends ['?.', infer Key, ...infer Rest]
235
+ ? // optional chaining, process first part and set Maybe to true
236
+ Path.Result.For<Path.Result.Part<T, Key, Maybe>, Rest, true>
237
+ : Tokens extends ['.', infer Key, ...infer Rest]
238
+ ? // normal chaining, process first part and continue
239
+ Path.Result.For<Path.Result.Part<T, Key, false>, Rest, Maybe>
240
+ : Tokens extends [infer Key, ...infer Rest]
241
+ ? // process first part, and continue
242
+ Path.Result.For<Path.Result.Part<T, Key, false>, Rest, Maybe>
243
+ : never;
244
+
245
+ /**
246
+ * Determines the result of getting the property/index `K` from type `T`, taking into
247
+ * account that the value may be optional.
248
+ * @typeparam T - the current source type
249
+ * @typeparam K - the key to get from the source type
250
+ * @typeparam Maybe - if true indicates that the path may be undefined
251
+ */
252
+ export type Part<T, K, Maybe extends boolean> = IsArray<T> extends true
253
+ ? // for arrays, Maybe needs to be set to true to force optional chaining
254
+ // for tuples, Maybe should be false
255
+ Path.Internal.MaybeValue<
256
+ T[K & keyof T],
257
+ Tuple.IsTuple<T> extends true ? Maybe : true
258
+ >
259
+ : // Return the type at the given key, and take `Maybe` into account
260
+ Path.Internal.MaybeValue<T[K & keyof T], Maybe>;
261
+
262
+ /**
263
+ * Converts a path string into separate tokens in a string array.
264
+ * @typeparam P - the literal string path type
265
+ * @typeparam Token - the token currently being produced
266
+ * @typeparam Res - the resulting literal string token array
267
+ */
268
+ export type Tokenize<
269
+ P extends string,
270
+ Token extends string = '',
271
+ Res extends string[] = []
272
+ > = P extends ''
273
+ ? // no more input to process, return result
274
+ Path.Internal.AppendIfNotEmpty<Res, Token>
275
+ : P extends `[${infer Index}]${infer Rest}`
276
+ ? // input is an array selector, append index to tokens. Continue with new token
277
+ Tokenize<
278
+ Rest,
279
+ '',
280
+ [...Path.Internal.AppendIfNotEmpty<Res, Token>, Index]
281
+ >
282
+ : P extends `?.${infer Rest}`
283
+ ? // optional chaining, append to tokens. Continue with new token
284
+ Tokenize<
285
+ Rest,
286
+ '',
287
+ [...Path.Internal.AppendIfNotEmpty<Res, Token>, '?.']
288
+ >
289
+ : P extends `.${infer Rest}`
290
+ ? // normal chaining, append to tokens. Continue with new token
291
+ Tokenize<Rest, '', [...Path.Internal.AppendIfNotEmpty<Res, Token>, '.']>
292
+ : P extends `${infer First}${infer Rest}`
293
+ ? // process next character
294
+ Tokenize<Rest, `${Token}${First}`, Res>
295
+ : never;
296
+ }
297
+
298
+ /**
299
+ * Regular expression used to split a path string into tokens.
300
+ */
301
+ export const stringSplitRegex = /\?\.|\.|\[|\]/g;
302
+
303
+ /**
304
+ * The allowed values of a split path.
305
+ */
306
+ export type StringSplit = (string | number | undefined)[];
307
+
308
+ /**
309
+ * Return the given `path` string split into an array of subpaths.
310
+ * @param path - the input string path
311
+ */
312
+ export function stringSplit(path: string): Path.StringSplit {
313
+ return path.split(Path.stringSplitRegex);
314
+ }
315
+ }
316
+
317
+ /**
318
+ * Returns the value resulting from selecting the given `path` in the given `source` object.
319
+ * It supports optional chaining for nullable values or values that may be undefined, and also
320
+ * for accessing objects inside an array.
321
+ * There is currently no support for forcing non-null (the `!` operator).
322
+ * @typeparam T - the object type to select in
323
+ * @typeparam P - a Path in object type T
324
+ * @param source - the object to select in
325
+ * @param path - the path into the object
326
+ * @example
327
+ * ```ts
328
+ * const value = { a: { b: { c: [{ d: 5 }, { d: 6 }] } } }
329
+ * Deep.getAt(value, 'a.b');
330
+ * // => { c: 5 }
331
+ * Deep.getAt(value, 'a.b.c');
332
+ * // => [{ d: 5 }, { d: 5 }]
333
+ * Deep.getAt(value, 'a.b.c[1]');
334
+ * // => { d: 6 }
335
+ * Deep.getAt(value, 'a.b.c[1]?.d');
336
+ * // => 6
337
+ * ```
338
+ */
339
+ export function getAt<T, P extends Path.Get<T>>(
340
+ source: T,
341
+ path: P
342
+ ): Path.Result<T, P> {
343
+ if (path === '') {
344
+ // empty path always directly returns source value
345
+ return source as any;
346
+ }
347
+
348
+ const items = Path.stringSplit(path);
349
+
350
+ // start with `source` as result value
351
+ let result = source as any;
352
+
353
+ for (const item of items) {
354
+ if (undefined === item || item === '' || item === '[') {
355
+ // ignore irrelevant items
356
+ continue;
357
+ }
358
+
359
+ if (undefined === result || null === result) {
360
+ // optional chaining assumed and no value available, skip rest of path and return undefined
361
+ return undefined as any;
362
+ }
363
+
364
+ // set current result to subpath value
365
+ result = result[item];
366
+ }
367
+
368
+ return result;
369
+ }
370
+
371
+ /**
372
+ * Patches the value at the given path in the source to the given value.
373
+ * Because the path to update must exist in the `source` object, optional
374
+ * chaining and array indexing is not allowed.
375
+ * @param source - the object to update
376
+ * @param path - the path in the object to update
377
+ * @param patchItem - the patch for the value at the given path
378
+ * @example
379
+ * ```ts
380
+ * const value = { a: { b: { c: 5 } } };
381
+ * Deep.patchAt(value, 'a.b.c', v => v + 5);
382
+ * // => { a: { b: { c: 6 } } }
383
+ * ```
384
+ */
385
+ export function patchAt<T, P extends Path.Set<T>, C = Path.Result<T, P>>(
386
+ source: T,
387
+ path: P,
388
+ patchItem: Patch<Path.Result<T, P>, Path.Result<T, P> & C>
389
+ ): T {
390
+ if (path === '') {
391
+ return Deep.patch(source, patchItem as any);
392
+ }
393
+
394
+ const items = Path.stringSplit(path);
395
+
396
+ // creates a patch object based on the current path
397
+ function createPatchPart(index: number, target: any): any {
398
+ if (index === items.length) {
399
+ // processed all items, return the input `patchItem`
400
+ return patchItem;
98
401
  }
99
402
 
100
- current[last] = value;
403
+ const item = items[index];
404
+
405
+ if (undefined === item || item === '') {
406
+ // empty items can be ignored
407
+ return createPatchPart(index + 1, target);
408
+ }
409
+
410
+ if (item === '[') {
411
+ // next item is array index, set arrayMode to true
412
+ return createPatchPart(index + 1, target);
413
+ }
101
414
 
102
- return patch<T>(source, root);
415
+ // create object with subPart as property key, and the restuls of processing next parts as value
416
+ const result = {
417
+ [item]: createPatchPart(index + 1, target[item]),
418
+ };
419
+
420
+ if (Array.isArray(target)) {
421
+ // target in source object is array/tuple, so the patch should be object
422
+ return result;
423
+ }
424
+
425
+ // target in source is not an array, so it patch should be an array
426
+ return [result];
103
427
  }
428
+
429
+ return Deep.patch(source, createPatchPart(0, source));
104
430
  }
package/src/protected.ts CHANGED
@@ -12,33 +12,22 @@ import type { IsAny, IsPlainObj } from '@rimbu/base';
12
12
  * @typeparam T - the input type
13
13
  */
14
14
  export type Protected<T> = IsAny<T> extends true
15
- ? T
15
+ ? // to prevent infinite recursion, any will be any
16
+ T
16
17
  : T extends readonly any[] & infer A
17
- ? { readonly [K in keyof A]: Protected<A[K]> }
18
+ ? // convert all keys to readonly and all values to `Protected`
19
+ { readonly [K in keyof A]: Protected<A[K]> }
18
20
  : T extends Map<infer K, infer V>
19
- ? Map<Protected<K>, Protected<V>>
21
+ ? // return keys and values as `Protected` and omit mutable methods
22
+ Omit<Map<Protected<K>, Protected<V>>, 'clear' | 'delete' | 'set'>
20
23
  : T extends Set<infer E>
21
- ? Set<Protected<E>>
24
+ ? // return values as `Protected` and omit mutable methods
25
+ Omit<Set<Protected<E>>, 'add' | 'clear' | 'delete'>
22
26
  : T extends Promise<infer E>
23
- ? Promise<Protected<E>>
27
+ ? // return promise value as `Protected`
28
+ Promise<Protected<E>>
24
29
  : IsPlainObj<T> extends true
25
- ? { readonly [K in keyof T]: Protected<T[K]> }
26
- : T;
27
-
28
- /**
29
- * Returns the same value wrapped in the `Protected` type.
30
- * @param value - the value to wrap
31
- * @note does not perform any runtime protection, it is only a utility to easily add the `Protected`
32
- * type to a value
33
- * @example
34
- * ```ts
35
- * const obj = Protected({ a: 1, b: { c: true, d: [1] } })
36
- * obj.a = 2 // compiler error: a is readonly
37
- * obj.b.c = false // compiler error: c is readonly
38
- * obj.b.d.push(2) // compiler error: d is a readonly array
39
- * (obj as any).b.d.push(2) // will actually mutate the object
40
- * ```
41
- */
42
- export function Protected<T>(value: T): Protected<T> {
43
- return value as any;
44
- }
30
+ ? // convert all keys to readonly and all values to `Protected`
31
+ { readonly [K in keyof T]: Protected<T[K]> }
32
+ : // nothing to do, just return `T`
33
+ T;
@@ -0,0 +1,90 @@
1
+ import type { IsAnyFunc, IsArray } from '@rimbu/base';
2
+ import { Deep, Path, type Protected } from './internal';
3
+
4
+ /**
5
+ * Type defining the allowed selectors on an object of type `T`.
6
+ * Selectors can be:
7
+ * - a path string into type `T`.
8
+ * - a function receiving a `Protected` version of type `T`, and returning an arbitrary value.
9
+ * - a tuple of `Selectors` for type `T`
10
+ * - an object where the property values are `Selectors` for type `T`.
11
+ * @typeparam T - the source value type.
12
+ */
13
+ export type Selector<T> =
14
+ | Path.Get<T>
15
+ | ((value: Protected<T>) => any)
16
+ | readonly Selector<T>[]
17
+ | { readonly [key: string | symbol]: Selector<T> };
18
+
19
+ export namespace Selector {
20
+ /**
21
+ * Type defining the shape of allowed selectors, used to improve compiler checking.
22
+ * @typeparam SL - the selector type
23
+ */
24
+ export type Shape<SL> = IsAnyFunc<SL> extends true
25
+ ? // functions are allowed, type provided by `Selector`
26
+ SL
27
+ : IsArray<SL> extends true
28
+ ? // ensure tuple type is preserved
29
+ readonly [...(SL extends readonly unknown[] ? SL : never)]
30
+ : SL extends { readonly [key: string | number | symbol]: unknown }
31
+ ? // ensure all object properties satisfy `Shape`
32
+ { readonly [K in keyof SL]: Selector.Shape<SL[K]> }
33
+ : // nothing to check
34
+ SL;
35
+
36
+ /**
37
+ * Type defining the result type of applying the SL selector type to the T value type.
38
+ * @typeparam T - the source value type
39
+ * @typeparam SL - the selector type
40
+ */
41
+ export type Result<T, SL> = Selector<T> extends SL
42
+ ? never
43
+ : SL extends (...args: any[]) => infer R
44
+ ? R
45
+ : SL extends string
46
+ ? Path.Result<T, SL>
47
+ : {
48
+ readonly [K in keyof SL]: Selector.Result<T, SL[K]>;
49
+ };
50
+ }
51
+
52
+ /**
53
+ * Returns the result of applying the given `selector` shape to the given `source` value.
54
+ * @typeparam T - the patch value type
55
+ * @typeparam SL - the selector shape type
56
+ * @param source - the source value to select from
57
+ * @param selector - a shape indicating the selection from the source values
58
+ * @example
59
+ * ```ts
60
+ * const item = { a: { b: 1, c: 'a' } };
61
+ * Deep.select(item, { q: 'a.c', y: ['a.b', 'a.c'], z: (v) => v.a.b + 1 });
62
+ * // => { q: 'a', y: [1, 'a'], z: 2 }
63
+ * ```
64
+ */
65
+ export function select<T, SL extends Selector<T>>(
66
+ source: T,
67
+ selector: Selector.Shape<SL>
68
+ ): Selector.Result<T, SL> {
69
+ if (typeof selector === 'function') {
70
+ // selector is function, resolve selector function
71
+ return (selector as any)(source as Protected<T>);
72
+ } else if (typeof selector === 'string') {
73
+ // selector is string path, get the value at the given path
74
+ return Deep.getAt(source, selector as Path.Get<T>) as any;
75
+ } else if (Array.isArray(selector)) {
76
+ // selector is tuple, get each tuple item value
77
+ return selector.map((s) => select(source, s)) as any;
78
+ }
79
+
80
+ // selector is object
81
+
82
+ const result: any = {};
83
+
84
+ for (const key in selector as any) {
85
+ // set each selected object key to the selector value
86
+ result[key] = select(source, (selector as any)[key]);
87
+ }
88
+
89
+ return result;
90
+ }
package/src/tuple.ts CHANGED
@@ -17,6 +17,18 @@ export namespace Tuple {
17
17
  */
18
18
  export type Source = readonly unknown[];
19
19
 
20
+ export type IsTuple<T> = T extends { length: infer L }
21
+ ? 0 extends L
22
+ ? false
23
+ : true
24
+ : false;
25
+
26
+ /**
27
+ * Returns the indices/keys that are in a tuple.
28
+ * @typeparam T - the input tuple type
29
+ */
30
+ export type KeysOf<T> = { [K in keyof T]: K }[keyof T & number];
31
+
20
32
  /**
21
33
  * Convenience method to type Tuple types
22
34
  * @param values - the values of the tuple