UNPKG

@rimbu/deep

Version:

Tools to use handle plain JS objects as immutable objects

197 lines (196 loc) 10.7 kB
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;