Skip to content

Instantly share code, notes, and snippets.

@daftAnorak
Created January 30, 2023 16:22
Show Gist options
  • Select an option

  • Save daftAnorak/c36c5f5189760c9907346da4a6c0bcfe to your computer and use it in GitHub Desktop.

Select an option

Save daftAnorak/c36c5f5189760c9907346da4a6c0bcfe to your computer and use it in GitHub Desktop.
Typescript utility for safely, and quickly, building upon given structures.
/**
* 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;
};
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