Repository: remeda/remeda
Pull request #733 (2024-06-18 09:42:58)

chore(readme): Migration Guides Links + New Badges

codesandbox-ci[bot]: This pull request is automatically built and testable in [CodeSandbox](https://codesandbox.io).

  To see build info of the built libraries, click [here](https://ci.codesandbox.io/status/remeda/remeda/pr/733/builds/515275) or the icon next to each commit SHA.

Latest deployment of this branch, based on commit 90f14307ab822d510a6bcbb9c3f097f631478ce1:

|Sandbox| Source |
|--|--|
|[remeda-example-vanilla](https://codesandbox.io/s/remeda-example-vanilla-gdmfpq)| Configuration |

codecov[bot]: ## [Codecov](https://app.codecov.io/gh/remeda/remeda/pull/733?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=remeda) Report
All modified and coverable lines are covered by tests :white_check_mark:
> Project coverage is 100.00%. Comparing base [(`e3a8214`)](https://app.codecov.io/gh/remeda/remeda/commit/e3a821408cf7f92cae5e09ce392cd6b7eb483cb8?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=remeda) to head [(`90f1430`)](https://app.codecov.io/gh/remeda/remeda/commit/90f14307ab822d510a6bcbb9c3f097f631478ce1?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=remeda).


<details><summary>Additional details and impacted files</summary>


```diff
@@            Coverage Diff            @@
##              main      #733   +/-   ##
=========================================
  Coverage   100.00%   100.00%           
=========================================
  Files          147       147           
  Lines        10185     10185           
  Branches       731       731           
=========================================
  Hits         10185     10185           
```



</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/remeda/remeda/pull/733?dropdown=coverage&src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=remeda).   
:loudspeaker: Have feedback on the report? [Share it here](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=remeda).

eranhirsch: I don't know how people use the badges 🙃 so I don't know how to optimize this experience.

I reviewed several similar projects, and this is the result of taking the best out of each. I looked for stuff that communicates our main principles and the quality of the project at a glance.

The only one I'm not 100% behind is the number of files in the project, which, IMO does not translate into any actionable insight from viewers. wdyt?

Patch:
diff --git a/README.md b/README.md
index 1cdeee0..19a8bb2 100644
--- a/README.md
+++ b/README.md
@@ -2,16 +2,24 @@
 
 The first "data-first" and "data-last" utility library designed especially for TypeScript.
 
-![GitHub CI](https://img.shields.io/github/actions/workflow/status/remeda/remeda/ci.yml?branch=main&label=github-ci)
-[![Codecov](https://img.shields.io/codecov/c/github/remeda/remeda/main)](https://codecov.io/gh/remeda/remeda)
-[![NPM](https://img.shields.io/npm/v/remeda)](https://www.npmjs.org/package/remeda)
-![Dependencies](https://img.shields.io/librariesio/release/npm/remeda)
+[![GitHub License](https://img.shields.io/github/license/remeda/remeda?style=flat-square)](https://github.com/remeda/remeda?tab=MIT-1-ov-file#readme)
+![GitHub top language](https://img.shields.io/github/languages/top/remeda/remeda?logo=typescript&style=flat-square)
+[![NPM](https://img.shields.io/npm/v/remeda?logo=npm&style=flat-square)](https://www.npmjs.org/package/remeda)
+[![NPM Downloads](https://img.shields.io/npm/dm/remeda?logo=npm&style=flat-square)](https://npmtrends.com/remeda)
+![GitHub Repo stars](https://img.shields.io/github/stars/remeda/remeda?logo=github&style=flat-square)
+[![Libraries.io SourceRank](https://img.shields.io/librariesio/sourcerank/npm/remeda?logo=librariesdotio&style=flat-square)](https://libraries.io/npm/remeda/sourcerank)<br />
+[![Codecov](https://img.shields.io/codecov/c/github/remeda/remeda?logo=codecov&style=flat-square)](https://codecov.io/gh/remeda/remeda)
+[![GitHub branch status](https://img.shields.io/github/checks-status/remeda/remeda/main?logo=github&style=flat-square)](https://github.com/remeda/remeda/actions/workflows)
+[![GitHub Release Date](https://img.shields.io/github/release-date/remeda/remeda?logo=npm&style=flat-square)](https://github.com/remeda/remeda/releases)
+[![Commits](https://img.shields.io/github/commit-activity/y/remeda/remeda?logo=github&style=flat-square)](https://github.com/remeda/remeda/commits/main/)
+[![All contributors](https://img.shields.io/github/contributors/remeda/remeda?logo=github&style=flat-square)](https://github.com/remeda/remeda/graphs/contributors)
+
 
 ## Documentation
 
 Read the full docs and API reference on [remedajs.com/docs](https://remedajs.com/docs).
 
-Migrating from Lodash or Ramda? Check the function mapping on [remedajs.com/mapping](https://remedajs.com/mapping).
+Migrating from other libraries? Check out our migration guides for [Lodash](https://remedajs.com/migrate/lodash) and [Ramda](https://remedajs.com/migrate/ramda)!
 
 ## Features
 
diff --git a/src/fromKeys.ts b/src/fromKeys.ts
index bf6cdac..9c196a5 100644
--- a/src/fromKeys.ts
+++ b/src/fromKeys.ts
@@ -1,5 +1,5 @@
 import type { Simplify } from "type-fest";
-import type { IterableContainer } from "./internal/types";
+import type { ExactRecord, IterableContainer } from "./internal/types";
 import { purry } from "./purry";
 
 // Takes a union of literals and creates a union of records with the value V for
@@ -13,7 +13,7 @@ type FromKeys<T extends IterableContainer, V> = T extends readonly []
   : T extends readonly [infer Head, ...infer Rest]
     ? ExactlyOneKey<Head, V> & FromKeys<Rest, V>
     : T[number] extends PropertyKey
-      ? Partial<Record<T[number], V>>
+      ? ExactRecord<T[number], V>
       : never;
 
 /**
diff --git a/src/pullObject.ts b/src/pullObject.ts
index e105e9f..0832d77 100644
--- a/src/pullObject.ts
+++ b/src/pullObject.ts
@@ -1,4 +1,4 @@
-import type { IterableContainer } from "./internal/types";
+import type { ExactRecord, IterableContainer } from "./internal/types";
 import { purry } from "./purry";
 
 /**
@@ -40,7 +40,7 @@ export function pullObject<
   data: T,
   keyExtractor: (item: T[number], index: number, data: T) => K,
   valueExtractor: (item: T[number], index: number, data: T) => V,
-): Partial<Record<K, V>>;
+): ExactRecord<K, V>;
 
 /**
  * Creates an object that maps the result of `valueExtractor` with a key
@@ -78,7 +78,7 @@ export function pullObject<
 >(
   keyExtractor: (item: T[number], index: number, data: T) => K,
   valueExtractor: (item: T[number], index: number, data: T) => V,
-): (data: T) => Partial<Record<K, V>>;
+): (data: T) => ExactRecord<K, V>;
 
 export function pullObject(...args: ReadonlyArray<unknown>): unknown {
   return purry(pullObjectImplementation, args);


Test patch:
diff --git a/src/fromKeys.test.ts b/src/fromKeys.test.ts
index 140b608..9c54dc0 100644
--- a/src/fromKeys.test.ts
+++ b/src/fromKeys.test.ts
@@ -1,5 +1,6 @@
 import type { Simplify } from "type-fest";
 import { add } from "./add";
+import { constant } from "./constant";
 import { fromKeys } from "./fromKeys";
 import { pipe } from "./pipe";
 
@@ -71,74 +72,74 @@ describe("runtime", () => {
       [symbol]: "symbol",
     });
   });
-});
 
-describe("dataLast", () => {
-  it("works on trivially empty arrays", () => {
-    expect(
-      pipe(
-        [] as Array<string>,
-        fromKeys((item) => `${item}_`),
-      ),
-    ).toEqual({});
-  });
+  describe("dataLast", () => {
+    it("works on trivially empty arrays", () => {
+      expect(
+        pipe(
+          [] as Array<string>,
+          fromKeys((item) => `${item}_`),
+        ),
+      ).toEqual({});
+    });
 
-  it("works on regular arrays", () => {
-    expect(
-      pipe(
-        ["a"],
-        fromKeys((item) => `${item}_`),
-      ),
-    ).toEqual({ a: "a_" });
-  });
+    it("works on regular arrays", () => {
+      expect(
+        pipe(
+          ["a"],
+          fromKeys((item) => `${item}_`),
+        ),
+      ).toEqual({ a: "a_" });
+    });
 
-  it("works with duplicates", () => {
-    expect(
-      pipe(
-        ["a", "a"],
-        fromKeys((item) => `${item}_`),
-      ),
-    ).toEqual({ a: "a_" });
-  });
+    it("works with duplicates", () => {
+      expect(
+        pipe(
+          ["a", "a"],
+          fromKeys((item) => `${item}_`),
+        ),
+      ).toEqual({ a: "a_" });
+    });
 
-  it("uses the last value", () => {
-    let counter = 0;
-    expect(
-      pipe(
-        ["a", "a"],
-        fromKeys(() => {
-          counter += 1;
-          return counter;
-        }),
-      ),
-    ).toEqual({ a: 2 });
-  });
+    it("uses the last value", () => {
+      let counter = 0;
+      expect(
+        pipe(
+          ["a", "a"],
+          fromKeys(() => {
+            counter += 1;
+            return counter;
+          }),
+        ),
+      ).toEqual({ a: 2 });
+    });
 
-  it("works with number keys", () => {
-    expect(pipe([123], fromKeys(add(1)))).toEqual({ 123: 124 });
-  });
+    it("works with number keys", () => {
+      expect(pipe([123], fromKeys(add(1)))).toEqual({ 123: 124 });
+    });
 
-  it("works with symbols", () => {
-    const symbol = Symbol("a");
-    expect(
-      pipe(
-        [symbol],
-        fromKeys(() => 1),
-      ),
-    ).toEqual({ [symbol]: 1 });
-  });
+    it("works with symbols", () => {
+      const symbol = Symbol("a");
+      expect(
+        pipe(
+          [symbol],
+          fromKeys(() => 1),
+        ),
+      ).toEqual({ [symbol]: 1 });
+    });
 
-  it("works with a mix of key types", () => {
-    const symbol = Symbol("a");
-    expect(
-      pipe(
-        ["a", 123, symbol],
-        fromKeys((item) => typeof item),
-      ),
-    ).toEqual({
-      a: "string",
-      123: "number",
-      [symbol]: "symbol",
+    it("works with a mix of key types", () => {
+      const symbol = Symbol("a");
+      expect(
+        pipe(
+          ["a", 123, symbol],
+          fromKeys((item) => typeof item),
+        ),
+      ).toEqual({
+        a: "string",
+        123: "number",
+        [symbol]: "symbol",
+      });
     });
   });
 });
@@ -147,11 +148,11 @@ describe("typing", () => {
   test("empty array", () => {
     const data = [] as const;
 
-    const dataFirst = fromKeys(data, getVal);
+    const dataFirst = fromKeys(data, constant(1));
     // eslint-disable-next-line @typescript-eslint/ban-types -- That's just what we return
     expectTypeOf(dataFirst).toEqualTypeOf<{}>();
 
-    const dataLast = pipe(data, fromKeys(getVal));
+    const dataLast = pipe(data, fromKeys(constant(1)));
     // eslint-disable-next-line @typescript-eslint/ban-types -- That's just what we return
     expectTypeOf(dataLast).toEqualTypeOf<{}>();
   });
@@ -159,10 +160,10 @@ describe("typing", () => {
   test("fixed tuple", () => {
     const data = ["cat", "dog"] as const;
 
-    const dataFirst = fromKeys(data, getVal);
+    const dataFirst = fromKeys(data, constant(1));
     expectTypeOf(dataFirst).toEqualTypeOf<Record<"cat" | "dog", number>>();
 
-    const dataLast = pipe(data, fromKeys(getVal));
+    const dataLast = pipe(data, fromKeys(constant(1)));
     expectTypeOf(dataLast).toEqualTypeOf<Record<"cat" | "dog", number>>();
   });
 
@@ -170,30 +171,30 @@ describe("typing", () => {
     test("regular array", () => {
       const data = [] as Array<string>;
 
-      const dataFirst = fromKeys(data, getVal);
-      expectTypeOf(dataFirst).toEqualTypeOf<Partial<Record<string, number>>>();
+      const dataFirst = fromKeys(data, constant(1));
+      expectTypeOf(dataFirst).toEqualTypeOf<Record<string, number>>();
 
-      const dataLast = pipe(data, fromKeys(getVal));
-      expectTypeOf(dataLast).toEqualTypeOf<Partial<Record<string, number>>>();
+      const dataLast = pipe(data, fromKeys(constant(1)));
+      expectTypeOf(dataLast).toEqualTypeOf<Record<string, number>>();
     });
 
     test("non-empty array", () => {
       const data = ["cat"] as [string, ...Array<string>];
 
-      const dataFirst = fromKeys(data, getVal);
+      const dataFirst = fromKeys(data, constant(1));
       expectTypeOf(dataFirst).toEqualTypeOf<Record<string, number>>();
 
-      const dataLast = pipe(data, fromKeys(getVal));
+      const dataLast = pipe(data, fromKeys(constant(1)));
       expectTypeOf(dataLast).toEqualTypeOf<Record<string, number>>();
     });
 
     test("fixed tuple", () => {
       const data = ["cat", "dog"] as [string, string];
 
-      const dataFirst = fromKeys(data, getVal);
+      const dataFirst = fromKeys(data, constant(1));
       expectTypeOf(dataFirst).toEqualTypeOf<Record<string, number>>();
 
-      const dataLast = pipe(data, fromKeys(getVal));
+      const dataLast = pipe(data, fromKeys(constant(1)));
       expectTypeOf(dataLast).toEqualTypeOf<Record<string, number>>();
     });
   });
@@ -202,12 +203,12 @@ describe("typing", () => {
     test("regular array", () => {
       const data = [] as Array<"cat" | "dog">;
 
-      const dataFirst = fromKeys(data, getVal);
+      const dataFirst = fromKeys(data, constant(1));
       expectTypeOf(dataFirst).toEqualTypeOf<
         Partial<Record<"cat" | "dog", number>>
       >();
 
-      const dataLast = pipe(data, fromKeys(getVal));
+      const dataLast = pipe(data, fromKeys(constant(1)));
       expectTypeOf(dataLast).toEqualTypeOf<
         Partial<Record<"cat" | "dog", number>>
       >();
@@ -216,7 +217,7 @@ describe("typing", () => {
     test("non-empty array", () => {
       const data = ["cat"] as ["cat" | "dog", ...Array<"mouse" | "pig">];
 
-      const dataFirst = fromKeys(data, getVal);
+      const dataFirst = fromKeys(data, constant(1));
       expectTypeOf(dataFirst).toEqualTypeOf<
         Simplify<
           Partial<Record<"mouse" | "pig", number>> &
@@ -224,7 +225,7 @@ describe("typing", () => {
         >
       >();
 
-      const dataLast = pipe(data, fromKeys(getVal));
+      const dataLast = pipe(data, fromKeys(constant(1)));
       expectTypeOf(dataLast).toEqualTypeOf<
         Simplify<
           Partial<Record<"mouse" | "pig", number>> &
@@ -236,7 +237,7 @@ describe("typing", () => {
     test("fixed tuple", () => {
       const data = ["cat", "mouse"] as ["cat" | "dog", "mouse" | "pig"];
 
-      const dataFirst = fromKeys(data, getVal);
+      const dataFirst = fromKeys(data, constant(1));
       expectTypeOf(dataFirst).toEqualTypeOf<
         Simplify<
           ({ cat: number } | { dog: number }) &
@@ -244,7 +245,7 @@ describe("typing", () => {
         >
       >();
 
-      const dataLast = pipe(data, fromKeys(getVal));
+      const dataLast = pipe(data, fromKeys(constant(1)));
       expectTypeOf(dataLast).toEqualTypeOf<
         Simplify<
           ({ cat: number } | { dog: number }) &
@@ -258,14 +259,14 @@ describe("typing", () => {
     test("regular array", () => {
       const data = [] as Array<`prefix_${number}`>;
 
-      const dataFirst = fromKeys(data, getVal);
+      const dataFirst = fromKeys(data, constant(1));
       expectTypeOf(dataFirst).toEqualTypeOf<
-        Partial<Record<`prefix_${number}`, number>>
+        Record<`prefix_${number}`, number>
       >();
 
-      const dataLast = pipe(data, fromKeys(getVal));
+      const dataLast = pipe(data, fromKeys(constant(1)));
       expectTypeOf(dataLast).toEqualTypeOf<
-        Partial<Record<`prefix_${number}`, number>>
+        Record<`prefix_${number}`, number>
       >();
     });
 
@@ -275,12 +276,12 @@ describe("typing", () => {
         ...Array<`prefix_${number}`>,
       ];
 
-      const dataFirst = fromKeys(data, getVal);
+      const dataFirst = fromKeys(data, constant(1));
       expectTypeOf(dataFirst).toEqualTypeOf<
         Record<`prefix_${number}`, number>
       >();
 
-      const dataLast = pipe(data, fromKeys(getVal));
+      const dataLast = pipe(data, fromKeys(constant(1)));
       expectTypeOf(dataLast).toEqualTypeOf<
         Record<`prefix_${number}`, number>
       >();
@@ -292,12 +293,12 @@ describe("typing", () => {
         `${number}_suffix`,
       ];
 
-      const dataFirst = fromKeys(data, getVal);
+      const dataFirst = fromKeys(data, constant(1));
       expectTypeOf(dataFirst).toEqualTypeOf<
         Record<`${number}_suffix` | `prefix_${number}`, number>
       >();
 
-      const dataLast = pipe(data, fromKeys(getVal));
+      const dataLast = pipe(data, fromKeys(constant(1)));
       expectTypeOf(dataLast).toEqualTypeOf<
         Record<`${number}_suffix` | `prefix_${number}`, number>
       >();
@@ -307,42 +308,40 @@ describe("typing", () => {
       test("regular array", () => {
         const data = [] as Array<number>;
 
-        const dataFirst = fromKeys(data, getVal);
-        expectTypeOf(dataFirst).toEqualTypeOf<
-          Partial<Record<number, number>>
-        >();
+        const dataFirst = fromKeys(data, constant(1));
+        expectTypeOf(dataFirst).toEqualTypeOf<Record<number, number>>();
 
-        const dataLast = pipe(data, fromKeys(getVal));
-        expectTypeOf(dataLast).toEqualTypeOf<Partial<Record<number, number>>>();
+        const dataLast = pipe(data, fromKeys(constant(1)));
+        expectTypeOf(dataLast).toEqualTypeOf<Record<number, number>>();
       });
 
       test("non-empty array", () => {
         const data = [1] as [number, ...Array<number>];
 
-        const dataFirst = fromKeys(data, getVal);
+        const dataFirst = fromKeys(data, constant(1));
         expectTypeOf(dataFirst).toEqualTypeOf<Record<number, number>>();
 
-        const dataLast = pipe(data, fromKeys(getVal));
+        const dataLast = pipe(data, fromKeys(constant(1)));
         expectTypeOf(dataLast).toEqualTypeOf<Record<number, number>>();
       });
 
       test("fixed tuple", () => {
         const data = [1, 2] as [number, number];
 
-        const dataFirst = fromKeys(data, getVal);
+        const dataFirst = fromKeys(data, constant(1));
         expectTypeOf(dataFirst).toEqualTypeOf<Record<number, number>>();
 
-        const dataLast = pipe(data, fromKeys(getVal));
+        const dataLast = pipe(data, fromKeys(constant(1)));
         expectTypeOf(dataLast).toEqualTypeOf<Record<number, number>>();
       });
 
       test("literals", () => {
         const data = [1, 2, 3] as const;
 
-        const dataFirst = fromKeys(data, getVal);
+        const dataFirst = fromKeys(data, constant(1));
         expectTypeOf(dataFirst).toEqualTypeOf<Record<1 | 2 | 3, number>>();
 
-        const dataLast = pipe(data, fromKeys(getVal));
+        const dataLast = pipe(data, fromKeys(constant(1)));
         expectTypeOf(dataLast).toEqualTypeOf<Record<1 | 2 | 3, number>>();
       });
     });
@@ -351,18 +350,14 @@ describe("typing", () => {
   test("typescript doesn't choke on huge literal unions", () => {
     const data = [] as Array<`${Letter}${Letter}`>;
 
-    const dataFirst = fromKeys(data, getVal);
+    const dataFirst = fromKeys(data, constant(1));
     expectTypeOf(dataFirst).toEqualTypeOf<
       Partial<Record<`${Letter}${Letter}`, number>>
     >();
 
-    const dataLast = pipe(data, fromKeys(getVal));
+    const dataLast = pipe(data, fromKeys(constant(1)));
     expectTypeOf(dataLast).toEqualTypeOf<
       Partial<Record<`${Letter}${Letter}`, number>>
     >();
   });
 });
-
-function getVal(): number {
-  return 1;
-}
diff --git a/src/pullObject.test.ts b/src/pullObject.test.ts
index 76aac03..57f96f2 100644
--- a/src/pullObject.test.ts
+++ b/src/pullObject.test.ts
@@ -174,30 +174,30 @@ describe("typing", () => {
     const data = ["a", "b"];
 
     const dataFirst = pullObject(data, identity(), constant("value"));
-    expectTypeOf(dataFirst).toEqualTypeOf<Partial<Record<string, string>>>();
+    expectTypeOf(dataFirst).toEqualTypeOf<Record<string, string>>();
 
     const dataLast = pipe(data, pullObject(identity(), constant("value")));
-    expectTypeOf(dataLast).toEqualTypeOf<Partial<Record<string, string>>>();
+    expectTypeOf(dataLast).toEqualTypeOf<Record<string, string>>();
   });
 
   test("number keys", () => {
     const data = [1, 2];
 
     const dataFirst = pullObject(data, identity(), constant(3));
-    expectTypeOf(dataFirst).toEqualTypeOf<Partial<Record<number, number>>>();
+    expectTypeOf(dataFirst).toEqualTypeOf<Record<number, number>>();
 
     const dataLast = pipe(data, pullObject(identity(), constant(3)));
-    expectTypeOf(dataLast).toEqualTypeOf<Partial<Record<number, number>>>();
+    expectTypeOf(dataLast).toEqualTypeOf<Record<number, number>>();
   });
 
   test("symbol keys", () => {
     const data = [Symbol("a"), Symbol("b")];
 
     const dataFirst = pullObject(data, identity(), constant(Symbol("c")));
-    expectTypeOf(dataFirst).toEqualTypeOf<Partial<Record<symbol, symbol>>>();
+    expectTypeOf(dataFirst).toEqualTypeOf<Record<symbol, symbol>>();
 
     const dataLast = pipe(data, pullObject(identity(), constant(Symbol("c"))));
-    expectTypeOf(dataLast).toEqualTypeOf<Partial<Record<symbol, symbol>>>();
+    expectTypeOf(dataLast).toEqualTypeOf<Record<symbol, symbol>>();
   });
 
   test("number constants", () => {
@@ -249,16 +249,12 @@ describe("typing", () => {
       (item) => `prefix_${item}`,
       constant("value"),
     );
-    expectTypeOf(dataFirst).toEqualTypeOf<
-      Partial<Record<`prefix_${number}`, string>>
-    >();
+    expectTypeOf(dataFirst).toEqualTypeOf<Record<`prefix_${number}`, string>>();
 
     const dataLast = pipe(
       data,
       pullObject((item) => `prefix_${item}`, constant("value")),
     );
-    expectTypeOf(dataLast).toEqualTypeOf<
-      Partial<Record<`prefix_${number}`, string>>
-    >();
+    expectTypeOf(dataLast).toEqualTypeOf<Record<`prefix_${number}`, string>>();
   });
 });