From 833bf9eefebe87482ec86e8ac19a22f221000ed6 Mon Sep 17 00:00:00 2001 From: yahyha benzha Date: Sat, 23 Mar 2024 18:21:23 +0000 Subject: [PATCH] object utilities --- src/object/at-path-n.spec.ts | 64 ++++++++++++++++++++ src/object/at-path-n.ts | 35 +++++++++++ src/object/at-path.ts | 11 ++-- src/object/index.ts | 3 + src/object/update-n.spec.ts | 113 +++++++++++++++++++++++++++++++++++ src/object/update-n.ts | 43 +++++++++++++ src/object/update.spec.ts | 65 ++++++++++++++++++++ src/object/update.ts | 37 ++++++++++++ 8 files changed, 364 insertions(+), 7 deletions(-) create mode 100644 src/object/at-path-n.spec.ts create mode 100644 src/object/at-path-n.ts create mode 100644 src/object/update-n.spec.ts create mode 100644 src/object/update-n.ts create mode 100644 src/object/update.spec.ts create mode 100644 src/object/update.ts diff --git a/src/object/at-path-n.spec.ts b/src/object/at-path-n.spec.ts new file mode 100644 index 000000000..7d59d959d --- /dev/null +++ b/src/object/at-path-n.spec.ts @@ -0,0 +1,64 @@ +import { $, Test, Object } from '..' + +/** + * Tests for `Object.AtPath`, which returns the value at a given path in an + * object. The path is specified as a tuple of keys. + */ +type AtPath_NSpec = [ + /** + * Can get the value at a key and path. + */ + Test.Expect< + $< + $, + { + name: { + first: 'foo' + last: string + } + age: 30 + } + >, + 'foo' | 30 + >, + + /** + * Can get the value at a path and key in a union. + */ + Test.Expect< + $< + $, + | { + name: { + first: 'foo' + last: string + } + age: 20 + } + | { + name: { + first: 'bar' + last: string + } + age: 30 + } + >, + 'foo' | 'bar' | 20 | 30 + >, + + /** + * Will emit never if the path does not exist. + */ + Test.Expect< + $< + $, + { name: { last: string }; age: number } + >, + never + >, + /** + * Emits an error if applied to a non-object. + */ + // @ts-expect-error + $<$, number> +] diff --git a/src/object/at-path-n.ts b/src/object/at-path-n.ts new file mode 100644 index 000000000..18a6c5309 --- /dev/null +++ b/src/object/at-path-n.ts @@ -0,0 +1,35 @@ +import { Kind, Type } from '..' +import { _$atPath } from './at-path' + +export type KeyOrPath = PropertyKey | PropertyKey[] + +export type _$atPathN< + Path extends KeyOrPath[], + T, + Acc extends any[] = [], + Output = Path extends [ + infer Head extends KeyOrPath, + ...infer Tail extends KeyOrPath[] + ] + ? _$atPathN< + Tail, + T, + [ + ...Acc, + Head extends PropertyKey[] + ? _$atPath + : T[Type._$cast] + ] + > + : Acc[number] +> = Output + +interface AtPathN_T extends Kind.Kind { + f( + x: Type._$cast> + ): _$atPathN +} + +export interface AtPathN extends Kind.Kind { + f(x: Type._$cast): AtPathN_T +} diff --git a/src/object/at-path.ts b/src/object/at-path.ts index fbfa1000d..539b1382a 100644 --- a/src/object/at-path.ts +++ b/src/object/at-path.ts @@ -1,6 +1,6 @@ import { Kind, Type } from '..' -export type _$atPath = Path extends [ +export type _$atPath = Path extends [ infer Head, ...infer Tail ] @@ -8,18 +8,15 @@ export type _$atPath = Path extends [ ? Head extends keyof T ? T[Head] : never - : _$atPath< - Type._$cast, - T[Type._$cast] - > + : _$atPath, T[Type._$cast]> : never -interface AtPath_T extends Kind.Kind { +interface AtPath_T extends Kind.Kind { f( x: Type._$cast> ): _$atPath } export interface AtPath extends Kind.Kind { - f(x: Type._$cast): AtPath_T + f(x: Type._$cast): AtPath_T } diff --git a/src/object/index.ts b/src/object/index.ts index 554412b86..b25bc2e18 100644 --- a/src/object/index.ts +++ b/src/object/index.ts @@ -1,4 +1,5 @@ export * from './at-path' +export * from './at-path-n' export * from './at' export * from './deep-input-of' export * from './deep-map-values' @@ -9,3 +10,5 @@ export * from './map-values' export * from './merge' export * from './paths' export * from './values' +export * from './update' +export * from './update-n' diff --git a/src/object/update-n.spec.ts b/src/object/update-n.spec.ts new file mode 100644 index 000000000..0f64511e8 --- /dev/null +++ b/src/object/update-n.spec.ts @@ -0,0 +1,113 @@ +import { $, Test, Object } from '..' + +type UpdateN_Spec = [ + /** + * Can Update List Of Keys + */ + Test.Expect< + $< + $<$, ['x', 20]>, + { + name: { + first: 'Joe' + } + age: number + } + >, + { + name: 'x' + age: 20 + } + >, + /** + * Can Update nested paths + */ + Test.Expect< + $< + $< + $, + ['David', { state: 'California'; city: 'Sacramento' }] + >, + { + name: { + first: 'Joe' + } + location: { + country: { + city: 'Michigan' + } + } + } + >, + { + name: { + first: 'David' + } + location: { + country: { state: 'California'; city: 'Sacramento' } + } + } + >, + /** + * Can Update a list of keys and nested paths + */ + Test.Expect< + $< + $< + $< + Object.UpdateN, + ['age', ['name', 'first'], ['location', 'country', 'city']] + >, + [30, 'David', 'SF'] + >, + { + name: { + first: 'Joe' + } + age: number + location: { + country: { + city: 'Michigan' + } + } + } + >, + { + name: { + first: 'David' + } + age: 30 + location: { + country: { + city: 'SF' + } + } + } + >, + + /** + * Keeps original object if the path is invalid + */ + Test.Expect< + $< + $<$, ['bar']>, + { + name: { + first: 'Joe' + } + age: number + } + >, + { + name: { + first: 'Joe' + } + age: number + } + >, + /** + * Running 'Update' on a non-Object type should emit an error. + */ + //@ts-expect-error + $<$<$, 'bar'>, number> +] diff --git a/src/object/update-n.ts b/src/object/update-n.ts new file mode 100644 index 000000000..472e245f9 --- /dev/null +++ b/src/object/update-n.ts @@ -0,0 +1,43 @@ +import { Kind, Type } from '..' +import { KeyOrPath } from './at-path-n' +import { _$update } from './update' + +export type _$updateN< + P extends KeyOrPath[], + V extends unknown[], + O extends Record +> = [[P], [V]] extends [ + [[infer Head, ...infer Tail extends KeyOrPath[]]], + [[infer VHead, ...infer VTail]] +] + ? _$updateN< + Tail, + VTail, + Type._$cast< + _$update< + Type._$cast< + Head extends PropertyKey[] ? Head : [Head], + PropertyKey[] + >, + VHead, + O + >, + Record + > + > + : O + +interface UpdateN_T2 + extends Kind.Kind { + f( + x: Type._$cast> + ): _$updateN +} + +interface UpdateN_T extends Kind.Kind { + f(x: Type._$cast): UpdateN_T2 +} + +export interface UpdateN extends Kind.Kind { + f(x: Type._$cast): UpdateN_T +} diff --git a/src/object/update.spec.ts b/src/object/update.spec.ts new file mode 100644 index 000000000..047d0a47c --- /dev/null +++ b/src/object/update.spec.ts @@ -0,0 +1,65 @@ +import { $, Test, Object } from '..' + +type Update_Spec = [ + /** + * Can Update at key. + */ + Test.Expect< + $< + $<$, { fullname: 'Joe Doe' }>, + { + name: { + first: 'Joe' + } + age: number + } + >, + { + name: { fullname: 'Joe Doe' } + age: number + } + >, + /** + * Can Update at nestd path. + */ + Test.Expect< + $< + $<$, 'Jane'>, + { + name: { + first: 'Joe' + } + age: number + } + >, + { + name: { first: 'Jane' } + age: number + } + >, + /** + * Keeps original object if the path is invalid + */ + Test.Expect< + $< + $<$, 'bar'>, + { + name: { + first: 'Joe' + } + age: number + } + >, + { + name: { + first: 'Joe' + } + age: number + } + >, + /** + * Running 'Update' on a non-Object type should emit an error. + */ + //@ts-expect-error + $<$<$, 'bar'>, number> +] diff --git a/src/object/update.ts b/src/object/update.ts new file mode 100644 index 000000000..c204de6e9 --- /dev/null +++ b/src/object/update.ts @@ -0,0 +1,37 @@ +import { Kind, Type } from '..' + +export type _$update< + P extends PropertyKey[], + V extends unknown, + A extends Record, + Output = Type._$display<{ + [K in keyof A]: P extends [PropertyKey] + ? P[0] extends K + ? V + : A[K] + : A[K] extends Record + ? _$update, V, A[K]> + : A[K] + }> +> = Output + +type RemoveItem = T extends [infer Head, ...infer Tail] + ? Head extends U + ? Tail + : [Head, ...RemoveItem] + : T + +interface Update_T2 + extends Kind.Kind { + f( + x: Type._$cast> + ): _$update +} + +interface Update_T extends Kind.Kind { + f(x: Type._$cast): Update_T2 +} + +export interface Update extends Kind.Kind { + f(x: Type._$cast): Update_T +}