@rimbu/deep
Version:
Tools to use handle plain JS objects as immutable objects
197 lines (196 loc) • 10.7 kB
text/typescript
import type { IsAnyFunc, IsArray, IsPlainObj } from '@rimbu/base';
import { type Patch } from './internal.cjs';
import type { Tuple } from './tuple.cjs';
export declare namespace Path {
/**
* A string representing a path into an (nested) object of type T.
* @typeparam T - the object type to select in
* @example
* ```ts
* const p: Path.Get<{ a: { b: { c : 5 } } }> = 'a.b'
* ```
*/
type Get<T> = Path.Internal.Generic<T, false, false, true>;
/**
* A string representing a path into an (nested) object of type T.
* @typeparam T - the object type to select in
* @example
* ```ts
* const p: Path.Set<{ a: { b: { c : 5 } } }> = 'a.b'
* ```
*/
type Set<T> = Path.Internal.Generic<T, true, false, true>;
namespace Internal {
/**
* Determines the allowed paths into a value of type `T`.
* @typeparam T - the source type
* @typeparam Write - if true the path should be writable (no optional chaining)
* @typeparam Maybe - if true the value at the current path is optional
* @typeparam First - if true this is the root call
* @note type is mapped as template literal to prevent non-string types to leak through
*/
type Generic<T, Write extends boolean, Maybe extends boolean, First extends boolean = false> = `${IsAnyFunc<T> extends true ? '' : // empty string is always an option
'' | Path.Internal.NonEmpty<T, Write, Maybe, First>}`;
/**
* Determines the allowed non-empty paths into a value of type `T`.
* @typeparam T - the source type
* @typeparam Write - if true the path should be writable (no optional chaining)
* @typeparam Maybe - if true the value at the current path is optional
* @typeparam First - if true this is the root call
*/
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>}`;
/**
* Determines the allowed paths into a non-optional value of type `T`.
* @typeparam T - the source type
* @typeparam Write - if true the path should be writable (no optional chaining)
* @typeparam Maybe - if true the value at the current path is optional
* @typeparam First - if true this is the root call
*/
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;
/**
* Determines the allowed paths for a tuple. Since tuples have fixed types, they do not
* need to be optional, in contrast to arrays.
* @typeparam T - the input tuple type
* @typeparam Write - if true the path should be writable (no optional chaining)
* @typeparam Maybe - if true the value at the current path is optional
*/
type Tup<T, Write extends boolean, Maybe extends boolean> = {
[K in Tuple.KeysOf<T>]: `[${K}]${Path.Internal.Generic<T[K], Write, Maybe>}`;
}[Tuple.KeysOf<T>];
/**
* Determines the allowed paths for an array.
* @typeparam T - the input array type
*/
type Arr<T extends readonly any[]> = `[${number}]${Path.Internal.Generic<T[number], false, true>}`;
/**
* Determines the allowed paths for an object.
* @typeparam T - the input object type
* @typeparam Write - if true the path should be writable (no optional chaining)
* @typeparam Maybe - if true the value at the current path is optional
*/
type Obj<T, Write extends boolean, Maybe extends boolean> = {
[K in keyof T]: `${K & string}${Path.Internal.Generic<T[K], Write, Write extends true ? false : Path.Internal.IsOptional<T[K], true, Maybe>>}`;
}[keyof T];
/**
* Determines the allowed path part seperator based on the input types.
* @typeparam First - if true, this is the first call
* @typeparam Maybe - if true, the value is optional
* @typeparam IsArray - if true, the value is an array
*/
type Separator<First extends boolean, Maybe extends boolean, IsArray extends boolean> = Maybe extends true ? First extends true ? never : '?.' : First extends true ? '' : IsArray extends true ? '' : '.';
/**
* Determines whether the given type `T` is optional, that is, whether it can be null or undefined.
* @typeparam T - the input type
* @typeparam True - the value to return if `T` is optional
* @typeparam False - the value to return if `T` is mandatory
*/
type IsOptional<T, True = true, False = false> = undefined extends T ? True : null extends T ? True : False;
/**
* Returns type `T` if `Maybe` is false, `T | undefined` otherwise.
* @typeparam T - the input type
* @typeparam Maybe - if true, the return type value should be optional
*/
type MaybeValue<T, Maybe extends boolean> = Maybe extends true ? T | undefined : T;
/**
* Utility type to only add non-empty string types to a string array.
* @typeparma A - the input string array
* @typeparam T - the string value to optionally add
*/
type AppendIfNotEmpty<A extends string[], T extends string> = T extends '' ? A : [
...A,
T
];
}
/**
* The result type when selecting from object type T a path with type P.
* @typeparam T - the object type to select in
* @typeparam P - a Path in object type T
* @example
* ```ts
* let r!: Path.Result<{ a: { b: { c: number } } }, 'a.b'>;
* // => type of r: { c: number }
* ```
*/
type Result<T, P extends string> = Path.Result.For<T, Path.Result.Tokenize<P>, false>;
namespace Result {
/**
* Determines the result type for an array of tokens representing subpaths in type `T`.
* @typeparam T - the current source type
* @typeparam Tokens - an array of elements indicating a path into the source type
* @typeparam Maybe - if true indicates that the path may be undefined
*/
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;
/**
* Determines the result of getting the property/index `K` from type `T`, taking into
* account that the value may be optional.
* @typeparam T - the current source type
* @typeparam K - the key to get from the source type
* @typeparam Maybe - if true indicates that the path may be undefined
*/
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>;
/**
* Converts a path string into separate tokens in a string array.
* @typeparam P - the literal string path type
* @typeparam Token - the token currently being produced
* @typeparam Res - the resulting literal string token array
*/
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, '', [
...Path.Internal.AppendIfNotEmpty<Res, Token>,
Index
]> : P extends `?.${infer Rest}` ? Tokenize<Rest, '', [
...Path.Internal.AppendIfNotEmpty<Res, Token>,
'?.'
]> : P extends `.${infer Rest}` ? Tokenize<Rest, '', [...Path.Internal.AppendIfNotEmpty<Res, Token>, '.']> : P extends `${infer First}${infer Rest}` ? Tokenize<Rest, `${Token}${First}`, Res> : never;
}
/**
* Regular expression used to split a path string into tokens.
*/
const stringSplitRegex: RegExp;
/**
* The allowed values of a split path.
*/
type StringSplit = (string | number | undefined)[];
/**
* Return the given `path` string split into an array of subpaths.
* @param path - the input string path
*/
function stringSplit(path: string): Path.StringSplit;
}
/**
* Returns the value resulting from selecting the given `path` in the given `source` object.
* It supports optional chaining for nullable values or values that may be undefined, and also
* for accessing objects inside an array.
* There is currently no support for forcing non-null (the `!` operator).
* @typeparam T - the object type to select in
* @typeparam P - a Path in object type T
* @param source - the object to select in
* @param path - the path into the object
* @example
* ```ts
* const value = { a: { b: { c: [{ d: 5 }, { d: 6 }] } } }
* Deep.getAt(value, 'a.b');
* // => { c: 5 }
* Deep.getAt(value, 'a.b.c');
* // => [{ d: 5 }, { d: 5 }]
* Deep.getAt(value, 'a.b.c[1]');
* // => { d: 6 }
* Deep.getAt(value, 'a.b.c[1]?.d');
* // => 6
* ```
*/
export declare function getAt<T, P extends Path.Get<T>>(source: T, path: P): Path.Result<T, P>;
/**
* Patches the value at the given path in the source to the given value.
* Because the path to update must exist in the `source` object, optional
* chaining and array indexing is not allowed.
* @param source - the object to update
* @param path - the path in the object to update
* @param patchItem - the patch for the value at the given path
* @example
* ```ts
* const value = { a: { b: { c: 5 } } };
* Deep.patchAt(value, 'a.b.c', v => v + 5);
* // => { a: { b: { c: 6 } } }
* ```
*/
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>, Path.Result<T, P> & C>): T;