contentlayerdev / contentlayer

Contentlayer turns your content into data - making it super easy to import MD(X) and CMS content in your app
https://www.contentlayer.dev
MIT License
3.27k stars 201 forks source link

Provide more utility functions (pick, ...) #24

Open schickling opened 2 years ago

schickling commented 2 years ago

There's a number of common utility functions which are useful in Contentlayer projects (e.g. a type-safe pick function) like below or here:

image

We should provide these utility functions out of the box with Contentlayer. Candidates:

If you'd like to see an other utility function added, please share your requests/ideas in the comments below. 🙏

alvesvin commented 2 years ago

A omit function would be cool also. Sometimes you need to get all but a few fields.

export const omit = <Obj, Keys extends keyof Obj>(obj: Obj, keys: Keys[]): Omit<Obj, Keys> => {
    return keys.reduce((acc, key) => {
        const { [key]: omit, ...rest } = acc;
        return rest;
    }, obj as any)
}

// Maybe this is better because it creates a new object just once
export const omit2 = <Obj, Keys extends keyof Obj>(obj: Obj, keys: Keys[]): Omit<Obj, Keys> => {
    const result = Object.assign({}, obj);
    keys.forEach((key) => {
        delete result[key];
    });
    return result;
}

Maybe deeply picking and omiting by specifing a schema.

dpick(obj, {
    key: true, // Picks all keys from nested object 'key'
    key2: ["key"], // Picks only 'key' field from nested object 'key2'
    key3: {  // Picks all from 'key' nested object but only field 'key' of 'key2' object from object 'key3'
        key: true,
        key2: ["key"]
    }
});
pmarsceill commented 2 years ago

It would be great if pick could be used with non-required fields. For example, if featuredImage was non-required in this case it would return null if it didn't exist on a Document.

pick(doc, [
  'slug',
  'date',
  'title',
  'description',
  'featuredImage',  // non-required field
  ])
)
schickling commented 2 years ago

@pmarsceill that's a great suggestion that's often needed with Next.js getStaticProps. Here's at least a temporary solution (or as TS playground):

type ConvertUndefined<T> = OrNull<{
  [K in keyof T as undefined extends T[K] ? K : never]-?: T[K];
}>;
type OrNull<T> = { [K in keyof T]: Exclude<T[K], undefined> | null };
type PickRequired<T> = {
  [K in keyof T as undefined extends T[K] ? never : K]: T[K];
};
type ConvertPick<T> = ConvertUndefined<T> & PickRequired<T>;

export const pick = <Obj, Keys extends keyof Obj>(
  obj: Obj,
  keys: Keys[]
): ConvertPick<{ [K in Keys]: Obj[K] }> => {
  return keys.reduce((acc, key) => {
    acc[key] = obj[key] ?? null;
    return acc;
  }, {} as any);
};