Created
January 30, 2023 16:22
-
-
Save daftAnorak/c36c5f5189760c9907346da4a6c0bcfe to your computer and use it in GitHub Desktop.
Typescript utility for safely, and quickly, building upon given structures.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| /** | |
| * Type for the utility helper | |
| * @paramtype T - type of object the utils are designed around | |
| */ | |
| export type Utils<T> = { | |
| build: (a?: Partial<T>) => T; | |
| } & { | |
| [K in keyof T as `buildOn${Capitalize<string & K>}`]: (a: T[K]) => T; | |
| } & { | |
| [K in keyof T as `get${Capitalize<string & K>}`]: (a: Partial<T>) => T[K] | undefined; | |
| } & { | |
| [K in keyof T as `getSafe${Capitalize<string & K>}`]: (a: unknown) => T[K]; | |
| } & { | |
| [K in keyof T as `set${Capitalize<string & K>}`]: (a: T[K], b: T) => T; | |
| } & { | |
| [K in keyof T as `setSafe${Capitalize<string & K>}`]: (a: unknown, b: unknown) => T; | |
| }; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import {lensPath, path as pathFn, set, view} from 'ramda'; | |
| import {capitalize} from './capitalize'; | |
| import {clone} from './clone'; | |
| import {Utils} from './utils-type'; | |
| /** | |
| * Method factory which takes a default value of type `T`, and produces | |
| * a record of lenses, getters, and setters that can be used to | |
| * access/set it, or another object of that same `T` type. | |
| * | |
| * ```typescript | |
| * const defaults: T = {foo: 'hello', bar: 1 }; | |
| * const utils = toUtils(defaults); | |
| * | |
| * // builders | |
| * utils.build(); // => defaults | |
| * utils.buildOnBar(4); // => {foo: 'hello', bar: 4} | |
| * | |
| * // settters | |
| * utils.setFoo('ciao', defaults); // => {foo: 'ciao', bar: 1} | |
| * utils.setFoo('ciao', {foo: 'hi', bar: 2}) // => {foo: 'ciao', bar: 2} | |
| * utils.setSafeBar(2, undefined); // => {foo: 'hello', bar: 2} (merges with defaults) | |
| * | |
| * // accessors / getters | |
| * utils.getBar(defaults); // => 1 | |
| * utils.getBar({bar: 5}); // => 5 | |
| * utils.getBar({foo: 'hi'}); // => undefined | |
| * utils.getSafeBar({bar: 4}); // => 4 | |
| * utils.getSafeBar(undefined); // => 1 (pulls from defaults) | |
| * ``` | |
| * | |
| * > ### Notice: | |
| * > To ensure any generated value is automatically mutable, the given | |
| * > defaultValues are masked as `Readonly`. | |
| * > This does NOT mean the defaultValues have to be Readonly in order to be | |
| * > passed. | |
| * @typeparam T - the type of the given value | |
| * @param {Readonly<T>} init - default values for type "T", or a function that returns those defaults | |
| * @returns {Utils<T>} a record of accessors, and setters bound to type "T" | |
| * @see [[Utils]] | |
| */ | |
| export const toUtils = <T>( | |
| init: Readonly<T> | ((values?: Partial<T>) => Readonly<T>) | |
| ): Utils<T> => { | |
| const util: Utils<T> = { | |
| build: | |
| typeof init === 'function' ? init : (a?: Partial<T>): Readonly<T> => ({...clone(init), ...a}), | |
| } as Utils<T>; | |
| const defaultValues = util.build(); | |
| Object.entries(defaultValues).forEach(([key, val]) => { | |
| const lensT = lensPath<T, typeof val>([key]); | |
| const getValue = view(lensT); | |
| const setValue = set(lensT); | |
| const capitalizedKey = capitalize(key); | |
| // == Accessors == | |
| // NOTE: get* is for obtaining property on maybe<T> | |
| // -- could be undefined | |
| util[`get${capitalizedKey}`] = getValue; | |
| // NOTE: getSafe* is for obtaining property on maybe<T> with default backup | |
| // -- should never be `undefined` | |
| util[`getSafe${capitalizedKey}`] = (a: Partial<T>) => getValue(util.build(a)); | |
| // == Setters == | |
| // NOTE: set* is for partially changing a prop/path on maybe<T> | |
| // -- could result in a non Partial<T> | |
| util[`set${capitalizedKey}`] = setValue; | |
| // NOTE: setSafe* is a non-curried method which takes the value and adds it | |
| // to the combined partial and default values | |
| util[`setSafe${capitalizedKey}`] = (a: typeof val, b: Partial<T> = {}): T => { | |
| let newValue; | |
| if (typeof a === typeof val) { | |
| newValue = a; | |
| } else { | |
| const bVal = pathFn([key], b); | |
| newValue = typeof bVal === typeof val ? bVal : val; | |
| } | |
| return setValue(newValue, util.build(b)); | |
| }; | |
| // == Builders == | |
| // NOTE: build* is a method which takes a single value, and | |
| // creates the entire T object, using defaultValues as the seed | |
| util[`buildOn${capitalizedKey}`] = (a: typeof val): T => setValue(a, util.build()); | |
| }); | |
| return util; | |
| }; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment