Module
Rimbu is a TypeScript library focused on immutable, performant, and type-safe collections and other tools.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258import { IsAnyFunc, IsArray, isPlainObj, IsPlainObj } from '../base/mod.ts';
import type { Protected } from './internal.ts';import type { Tuple } from './tuple.ts';
/** * A type to determine the allowed input type for the `patch` function. * @typeparam T - the input type to be patched */export type Patch<T, C = T> = Patch.Entry<T, C, T, T>;
export namespace Patch { /** * 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. * @typeparam T - the input value type * @typeparam C - a utility type * @typeparam P - the parent type * @typeparam R - the root object type */ export type Entry<T, C, P, R> = IsAnyFunc<T> extends true ? T : IsPlainObj<T> extends true ? Patch.WithResult<T, P, R, Patch.Obj<T, C, R>> : Tuple.IsTuple<T> extends true ? Patch.WithResult<T, P, R, T | Patch.Tup<T, C, R>> : IsArray<T> extends true ? Patch.WithResult<T, P, R, T> : Patch.WithResult<T, P, R, T>;
/** * Either result type S, or a patch function with the value type, the parent type, and the root type. * @typeparam T - the value type * @typeparam P - the parent type * @typeparam R - the root type * @typeparam S - the result type */ export type WithResult<T, P, R, S> = S | Patch.Func<T, P, R, S>;
/** * A function patch type that is a function taking the current value, the parent and root values, * and returns a return value. * @typeparam T - the value type * @typeparam P - the parent type * @typeparam R - the root type * @typeparam S - the result type */ export type Func<T, P, R, S> = ( current: Protected<T>, parent: Protected<P>, root: Protected<R> ) => Protected<S>;
/** * A type defining the allowed patch values for tuples. * @typeparam T - the input tuple type * @typeparam C - a utility type * @typeparam R - the root type */ export type Tup<T, C, R> = { [K in Tuple.KeysOf<T>]?: Patch.Entry<T[K & keyof T], C[K & keyof C], T, R>; } & NotIterable;
/** * Utility type to exclude Iterable types. */ export type NotIterable = { [Symbol.iterator]?: never; };
/** * A type defining the allowed patch values for objects. * @typeparam T - the input value type * @typeparam C - a utility type * @typeparam R - the root object type */ export type Obj<T, C, R> = T | Patch.ObjProps<T, C, R>[];
/** * A type defining the allowed patch values for object properties. * @typeparam T - the input value type * @typeparam C - a utility type * @typeparam R - the root object type */ export type ObjProps<T, C, R> = { [K in keyof C]?: K extends keyof T ? Patch.Entry<T[K], C[K], T, R> : never; };}
/** * Returns an immutably updated version of the given `value` where the given `patchItems` have been * applied to the result. * The Rimbu patch notation is as follows: * - if the target is a simple value or array, the patch can be the same type or a function returning the same type * - 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 * - 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 * @typeparam T - the type of the value to patch * @typeparam TE - a utility type * @typeparam TT - a utility type * @param value - the input value to patch * @param patchItem - the `Patch` value to apply to the input value * @example * ```ts * const input = { a: 1, b: { c: true, d: 'a' } } * patch(input, [{ a: 2 }]) // => { a: 2, b: { c: true, d: 'a' } } * patch(input, [{ b: [{ c: (v) => !v }] }] ) * // => { a: 1, b: { c: false, d: 'a' } } * patch(input: [{ a: (v) => v + 1, b: [{ d: 'q' }] }] ) * // => { a: 2, b: { c: true, d: 'q' } } * ``` */export function patch<T, TE extends T = T, TT = T>( value: T, patchItem: Patch<TE, T & TT>): T { return patchEntry(value, value, value, patchItem as Patch<T>);}
function patchEntry<T, C, P, R>( value: T, parent: P, root: R, patchItem: Patch.Entry<T, C, P, R>): T { if (Object.is(value, patchItem)) { // patching a value with itself never changes the value return value; }
if (typeof value === 'function') { // function input, directly return patch return patchItem as T; }
if (typeof patchItem === 'function') { // function patch always needs to be resolved first const item = patchItem(value, parent, root);
return patchEntry(value, parent, root, item); }
if (isPlainObj(value)) { // value is plain object return patchPlainObj(value, root, patchItem as any); }
if (Array.isArray(value)) { // value is tuple or array return patchArr(value, root, patchItem as any); }
// value is primitive type or complex object
return patchItem as T;}
function patchPlainObj<T, C, R>( value: T, root: R, patchItem: T | Patch.Obj<T, C, R>): T { if (!Array.isArray(patchItem)) { // the patch is a complete replacement of the current value
return patchItem as T; }
// patch is an array of partial updates
// copy the input value const result = { ...value };
let anyChange = false;
// loop over patches in array for (const entry of patchItem) { // update root if needed const currentRoot = (value as any) === root ? { ...result } : root;
// keep current updated result as parent const parent = { ...result };
// loop over all the patch keys for (const key in entry as T) { // patch the value at the given key with the patch at that key const currentValue = result[key]; const newValue = patchEntry( currentValue, parent, currentRoot, (entry as any)[key] );
if (!Object.is(currentValue, newValue)) { // if value changed, set it in result and mark change anyChange = true; result[key] = newValue; } } }
if (anyChange) { // something changed, return new value return result; }
// nothing changed, return old value return value;}
function patchArr<T extends any[], C, R>( value: T, root: R, patchItem: T | Patch.Tup<T, C, R>): T { if (Array.isArray(patchItem)) { // value is a normal array // patch is a complete replacement of current array
return patchItem; }
// value is a tuple // patch is an object containing numeric keys with function values // that update the tuple at the given indices
// copy the tuple const result = [...value] as T; let anyChange = false;
// loop over all index keys in object for (const index in patchItem) { const numIndex = index as any as number;
// patch the tuple at the given index const currentValue = result[numIndex]; const newValue = patchEntry( currentValue, value, root, (patchItem as any)[index] );
if (!Object.is(newValue, currentValue)) { // if value changed, set it in result and mark change anyChange = true; result[numIndex] = newValue; } }
if (anyChange) { // something changed, return new value return result; }
// nothing changed, return old value return value;}