npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2025 – Pkg Stats / Ryan Hefner

deffo

v0.1.0

Published

Deep defaults for complex objects

Readme

deffo

A JS/TS/Node utility for deep-assigning default property values to arbitrary objects, like Object.assign with superpowers.

NPM npm bundle size CI status

GitHub stars GitHub forks GitHub watchers Follow on GitHub

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 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 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