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 🙏

© 2026 – Pkg Stats / Ryan Hefner

make-keyed-data

v1.0.3

Published

A function for simplifying the creation of static data in code

Readme

Overview

This is a simple function to make the creation and typing of static data in code more consistent, DRY, and repeatable. You would turn something like this:

export type ThingID = "foo" | "bar";

export type Thing = {
  id: ThingID;
  name: string;
};

export const THINGS: Record<ThingID, Thing> = {
  foo: { id: "foo", name: "Foo" },
  bar: { id: "bar", name: "Bar" },
}

Into this:

export const THINGS = makeKeyedData<{
  name: string;
}>()({
  foo: { name: "Foo" },
  bar: { name: "Bar" },
});

export type ThingID = keyof typeof THINGS;
export type Thing = typeof THINGS[ThingID];

It might not look like much, but it provides several benefits:

  • You no longer have to repeat your IDs three times.
  • You no longer have to update both the ThingID type and the THINGS object to add a new thing.
  • You could refactor the original example, but it's tricky to do while staying DRY and maintaining strong typing.

Motivation

I would often find myself defining static data like this:

export type ThingID = "foo" | "bar";

export type Thing = {
  id: ThingID;
  name: string;
};

export const THINGS: Record<ThingID, Thing> = {
  foo: { id: "foo", name: "Foo" },
  bar: { id: "bar", name: "Bar" },
};

And then I'd get annoyed that I had to repeat the IDs three times, so I'd change it to something like this:

const THING_VALUES = {
  foo: { name: "Foo" },
  bar: { name: "Bar" },
} as const;

export type ThingID = keyof typeof THING_VALUES;

export type Thing = {
  id: ThingID;
} & (typeof THING_VALUES)[ThingID];

export const THINGS: Record<ThingID, Thing> = Object.fromEntries(
  Object.entries(THING_VALUES).map(([key, value]) => [
    key,
    { id: key, ...value },
  ])
) as Record<ThingID, Thing>;

More DRY, but then I'd realize I'd lost strong typing on the THINGS object.

Third try:

type ThingBase = {
  name: string;
};

// Define your data once, strongly typed
const thingValues = {
  foo: { name: "Foo" },
  bar: { name: "Bar" },
} satisfies Record<string, ThingBase>;

// Derive types from the object
export type ThingID = keyof typeof thingValues;
export type Thing = { id: ThingID } & ThingBase;

// Build final THINGS map with `id` injected
export const THINGS: Record<ThingID, Thing> = Object.fromEntries(
  Object.entries(thingValues).map(([key, value]) => [
    key,
    { id: key, ...value },
  ])
) as Record<ThingID, Thing>;

Success! I finally had a single source of truth for my keys and data, all with strong typing. The problem is that I'd have to copy and paste all of this boilerplate for every data type. And I'd inevitably have to go through this whole iterative process on my next project, because I have the memory of a goldfish.

So, instead, I made this simple package to keep myself from constantly reinventing the wheel, turning all of the above into:

export const THINGS = makeKeyedData<{
  name: string;
}>()({
  foo: { name: "Foo" },
  bar: { name: "Bar" },
});

export type ThingID = keyof typeof THINGS;
export type Thing = typeof THINGS[ThingID];

Examples

Basic usage

export const DOGS = makeKeyedData<{
  name: string;
  breed: "poodle" | "rottweiler";
}>()({
  tiny: { name: "Tiny", breed: "rottweiler" },
  godzilla: { name: "Godzilla", breed: "poodle" },
});
/* {
  tiny: { id: "tiny", name: "Tiny", breed: "rottweiler" },
  godzilla: { id: "godzilla", name: "Godzilla", breed: "poodle" },
} */

export type DogID = keyof typeof DOGS;  // "tiny" | "godzilla"
export type Dog = typeof DOGS[DogID];   // { id: DogID, name: string, breed: "poodle" | "rottweiler" }

Customizing the ID key

export const USERS = makeKeyedData<{ name: string, home: string }>()({
  willq: { name: "Will Q.", home: "/home/willq" },
  chrism: { name: "Chris M.", home: "/home/chrism" },
}, { idKey: "username" });
/* {
  willq: { username: "willq", name: "Will Q.", home: "/home/wills" },
  chrism: { username: "chrism", name: "Chris M.", home: "/home/chrism" },
} */

FAQ

  • Why do I have to type makeKeyedData()({...})? Why not just makeKeyedData({...})?
    • This is a well-known workaround to a limitation of TypeScript's generics. There's a StackOverflow answer that provides a good summary of the issue.
  • Can I use this without TypeScript?
    • Yes, although most of its value comes from the types it provides.