remeda
Version:
A utility library for JavaScript and Typescript.
1 lines • 6.44 kB
Source Map (JSON)
{"version":3,"file":"pick.cjs","names":["purry","out: Partial<Pick<T, Keys[number]>>"],"sources":["../src/pick.ts"],"sourcesContent":["import type { EmptyObject, IsNever, KeysOfUnion, Writable } from \"type-fest\";\nimport type { IsBounded } from \"./internal/types/IsBounded\";\nimport type { IsBoundedRecord } from \"./internal/types/IsBoundedRecord\";\nimport type { PartitionByUnion } from \"./internal/types/PartitionByUnion\";\nimport type { TupleParts } from \"./internal/types/TupleParts\";\nimport { purry } from \"./purry\";\n\ntype PickFromArray<T, Keys extends ReadonlyArray<KeysOfUnion<T>>> =\n // Distribute unions for both object types and key arrays.\n T extends unknown\n ? Keys extends unknown\n ? // When T is a union (or when Keys is empty) the picked props might\n // not exist in some of its sub-types, e.g.,\n // `pick(... as { a: string } | { b: number }, ['a'])`,\n // if we simply let the regular \"constructive\" logic run, the\n // resulting type would be `{}` which doesn't behave like an empty\n // object! instead, we want to use a more explicit *empty* type.\n IsNever<Extract<Keys[number], keyof T>> extends true\n ? EmptyObject\n : // Remove `readonly` modifiers from picked props since we return a\n // new, mutable, object. We don't wrap the result with `Simplify` to\n // flatten it because `Writable` does the same thing implicitly.\n Writable<\n IsBoundedRecord<T> extends true\n ? PickBoundedFromArray<T, Keys>\n : PickUnbounded<T, Extract<Keys[number], keyof T>>\n >\n : never\n : never;\n\n/**\n * Bounded records have bounded keys and result in a bounded output. The only\n * question left is whether to add the prop as-is, or make it optional. This\n * can be determined by the part of the keys array the prop is defined in, and\n * the way that element is defined: if the array contains a singular literal\n * key in either the required prefix or the suffix, we know that prop should be\n * picked as-is, otherwise, the key might not be present in the keys array so it\n * can only be picked optionally.\n */\ntype PickBoundedFromArray<T, Keys extends ReadonlyArray<KeysOfUnion<T>>> =\n // Literal keys in the prefix/suffix are guaranteed present.\n Pick<\n T,\n // When T is a union the keys need to be narrowed to just those that are\n // keys of the specific sub-type being built\n Extract<\n | PartitionByUnion<TupleParts<Keys>[\"required\"]>[\"singular\"]\n | PartitionByUnion<TupleParts<Keys>[\"suffix\"]>[\"singular\"],\n keyof T\n >\n > &\n // Union keys, optional elements, and rest elements are optional.\n Partial<\n Pick<\n T,\n // When T is a union the keys need to be narrowed to just those that are\n // keys of the specific sub-type being built.\n Extract<\n | PartitionByUnion<TupleParts<Keys>[\"required\"]>[\"union\"]\n // TODO: the optional part of the keys array will always be empty because its impossible to provide the pick function with a tuple with optional elements; this is because optional elements are always implicitly `undefined` too; which breaks the constraint that all keys are keys of T (`undefined` is not a key of anything). We can lift this restriction by supporting `undefined` in the runtime and relaxing the type constraint to allow it, but this relaxed constraint enables a niche feature (optional tuple elements) at the expense of better type-safety for the more common cases of fixed tuples and arrays. Anyway... if we ever change it, this part of the output type will ensure the output is still correct:\n | TupleParts<Keys>[\"optional\"][number]\n | TupleParts<Keys>[\"item\"]\n | PartitionByUnion<TupleParts<Keys>[\"suffix\"]>[\"union\"],\n keyof T\n >\n >\n >;\n\n/**\n * The built-in `Pick` is weird when it comes to picking bounded keys from\n * unbounded records. It reconstructs the output object regardless of the shape\n * of the input: `Pick<Record<string, \"world\">, \"hello\">` results in the type\n * `{ hello: \"world\" }`, but you'd expect it to be optional because we don't\n * know if the record contains a `hello` prop or not!\n *\n * !Important: We assume T is unbounded and don't test for it!\n *\n * See: https://www.typescriptlang.org/play/?#code/PTAEE0HsFcHIBNQFMAeAHJBjALqAGqNpKAEZKigAGA3qABZIA2jkA-AFygBEA7pAE6N4XUAF9KAGlLRcAQ0ayAzgChsATwz5QAXlAAFAJaYA1gB4ASlgHxTi7PwMA7AOZTeAoVwB8bhs0jeANzKIBSgAHqsykA.\n */\ntype PickUnbounded<T, Keys extends keyof T> =\n IsBounded<Keys> extends true ? Partial<Pick<T, Keys>> : Pick<T, Keys>;\n\n/**\n * Creates an object composed of the picked `data` properties.\n *\n * @param keys - The property names.\n * @signature R.pick([prop1, prop2])(object)\n * @example\n * R.pipe({ a: 1, b: 2, c: 3, d: 4 }, R.pick(['a', 'd'])) // => { a: 1, d: 4 }\n * @dataLast\n * @category Object\n */\nexport function pick<\n T extends object,\n const Keys extends ReadonlyArray<KeysOfUnion<T>>,\n>(keys: Keys): (data: T) => PickFromArray<T, Keys>;\n\n/**\n * Creates an object composed of the picked `data` properties.\n *\n * @param data - The target object.\n * @param keys - The property names.\n * @signature R.pick(object, [prop1, prop2])\n * @example\n * R.pick({ a: 1, b: 2, c: 3, d: 4 }, ['a', 'd']) // => { a: 1, d: 4 }\n * @dataFirst\n * @category Object\n */\nexport function pick<\n T extends object,\n const Keys extends ReadonlyArray<KeysOfUnion<T>>,\n>(data: T, keys: Keys): PickFromArray<T, Keys>;\n\nexport function pick(...args: ReadonlyArray<unknown>): unknown {\n return purry(pickImplementation, args);\n}\n\nfunction pickImplementation<\n T extends object,\n Keys extends ReadonlyArray<keyof T>,\n>(object: T, keys: Keys): PickFromArray<T, Keys> {\n const out: Partial<Pick<T, Keys[number]>> = {};\n for (const key of keys) {\n if (key in object) {\n out[key] = object[key];\n }\n }\n // @ts-expect-error [ts2322] - We build the type incrementally, there's no way to make typescript infer that we \"finished\" building the object and to treat it as such.\n return out;\n}\n"],"mappings":"wCAiHA,SAAgB,EAAK,GAAG,EAAuC,CAC7D,OAAOA,EAAAA,EAAM,EAAoB,EAAK,CAGxC,SAAS,EAGP,EAAW,EAAoC,CAC/C,IAAMC,EAAsC,EAAE,CAC9C,IAAK,IAAM,KAAO,EACZ,KAAO,IACT,EAAI,GAAO,EAAO,IAItB,OAAO"}