
Security News
GitHub Actions Pricing Whiplash: Self-Hosted Actions Billing Change Postponed
GitHub postponed a new billing model for self-hosted Actions after developer pushback, but moved forward with hosted runner price cuts on January 1.
A JS/TS/Node utility for deep-assigning default property values to arbitrary objects, like Object.assign with superpowers.
npm i deffo
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 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.
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.
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,
// },
// },
// }
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"];
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>;
MIT © Tobias Fried
FAQs
Deep defaults for complex objects
The npm package deffo receives a total of 1 weekly downloads. As such, deffo popularity was classified as not popular.
We found that deffo demonstrated a not healthy version release cadence and project activity because the last version was released a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Security News
GitHub postponed a new billing model for self-hosted Actions after developer pushback, but moved forward with hosted runner price cuts on January 1.

Research
Destructive malware is rising across open source registries, using delays and kill switches to wipe code, break builds, and disrupt CI/CD.

Security News
Socket CTO Ahmad Nassri shares practical AI coding techniques, tools, and team workflows, plus what still feels noisy and why shipping remains human-led.