deffo
A JS/TS/Node utility for deep-assigning default property values to arbitrary objects, like Object.assign
with superpowers.
Installation
npm i deffo
Usage
The Defaults
class recursively merges two objects -- a defaults
object and a target
object -- into a new object combining their properties, using the target
property values where present.
Class Signature
class Defaults<T extends object, U extends DeepPartial<T> = DeepPartial<T>> {
constructor(defaults: T, replacer?: ReplacerObject<T>);
static with<T extends object, U extends DeepPartial<T>>(
defaults: T,
target: U,
replacer?: Replacer<T> | ReplacerObject<T>
): T & U;
assign(target: U, replacer?: Replacer<T> | ReplacerObject<T>): T & U;
}
import { Defaults } from "deffo";
const requestInit = new Defaults<RequestInit>({
method: "POST",
mode: "cors",
cache: "no-cache",
headers: {
"Content-Type": "application/json",
},
redirect: "follow",
});
const cacheNoFollow = requestInit.assign({
cache: "default",
headers: {
Foo: "bar",
},
redirect: "manual",
});
// {
// method: 'POST',
// mode: 'cors',
// cache: 'default',
// headers: {
// "Content-Type": "application/json",
// "Foo": "bar",
// },
// redirect: 'manual',
// }
The target
object will always be a DeepPartial
version of the defaults
object, in which all the properties are (recursively) optional. Providing a value for any property at any level will override that property, and that property only.
Replacers
Sometimes you may want to customize the merge behavior, for example replacing a entire object, combining the default
and target
values, or otherwise transforming specific values. You can do this by providing a ReplacerObject
argument, which has a shape matching the defaults
object, in which any value can be a function that takes in both the default
and target
values for the property, and must return a value of the same type.
const postTemplate = {
id: 1,
type: "blog",
content: {
title: "TEMPLATE POST",
body: "This is a test",
},
};
const postDefaults = new Defaults(postTemplate, {
// The new `id` is the sum of the provided `id` and the default `id`
id: (defaultValue, targetValue) => defaultValue + targetValue,
// The post type "tweet" is replaced with "toot"; we're on Mastodon now
type: (_, targetValue) => {
if (targetValue === "tweet") {
return "toot";
} else {
return targetValue;
}
},
// All titles should be in uppercase
content: {
title: (_, targetValue) => {
return targetValue.toUpperCase();
},
},
});
const socialPost = postDefaults.assign({
id: 6,
type: "tweet",
content: {
title: "listen up",
},
});
// {
// id: 7,
// type: "toot",
// content: {
// title: "LISTEN UP",
// body: "This is a test",
// },
// };
Replacers will only be called when the property is present on the target
, and will only modify the target property.
Static Form
If you don't wish to keep the Defaults
object around, you can apply defaults using the static method form. An optional replacer can be passed as the third argument.
const user = Defaults.with(
{
name: "Jane Doe",
role: "admin",
details: {
interests: ["coding"],
location: {
city: "Boulder",
state: "CO",
zip: 80302,
},
},
},
{
name: "Julie Sands",
details: {
city: "Denver",
zip: 80220,
},
}
);
// {
// name: "Julie Sands",
// role: "admin",
// details: {
// interests: ["coding"],
// location: {
// city: "Denver",
// state: "CO",
// zip: 80220,
// },
// },
// }
Arrays
Array-based defaults and replacers are supported, and treat each indexed value as a numeric-keyed property. Because of this, it is recommended to use it only for tuple-like types with a fixed length. If you need to transform a dynamic list with defaults, transform them at the element level instead of at the whole array level.
type Token = { type: string; start: number; end: number };
const templatePair: [Token, string] = [
{ type: "identifier", start: 0, end: 1 },
"a",
];
const tokenDefaults = new Defaults(templatePair, [
undefined, // Do not replace the first element
(_, tv) => `_${tv}`, // Prefix the second element with "_"
]);
const tok = tokenDefaults.assign([{ start: 12, end: 13 }, "b"]);
// [{ type: "identifier", start: 12, end: 13 }, "_b"];
Utility Types
Type Signatures
type DeepPartial<T> = T extends object
? {
[P in keyof T]?: DeepPartial<T[P]>;
}
: T;
type ReplacerFunction<T> = (defaultValue: T, targetValue: DeepPartial<T>) => T;
type ReplacerObject<T extends object> = {
[P in keyof T]?: Replacer<T[P]>;
};
type Replacer<T> = T extends object
? ReplacerObject<T> | ReplacerFunction<T>
: ReplacerFunction<T>;
License
MIT © Tobias Fried