Repository: remeda/remeda
Pull request #683 (2024-05-29 07:44:35)

fix(hasSubObject): incorrect type narrowing in negate condition
Fixes: #678

The only way I come up to fix the issue with the negate condition in case when the guard resolves to the same type as `data` is by using something that will make the guard always different from `data`. One solution can be by using a brand type. Brand type is an internal type that doesn't affect anything but can help us identify the type result better. The users of the library can safely ignore that brand type.

This is the first occurrence of a brand type so let me know if I need to change its format/naming, etc.

Next, in a separate PR, I'm going to try to improve the overall typing of `hasSubObject` to make the narrowing more smarter. I already did some progress but stuck on making it work well with `pipe` function.

---

Make sure that you:

- [x] Typedoc added for new methods and updated for changed
- [x] Tests added for new methods and updated for changed
- [x] New methods added to `src/index.ts`
- [x] New methods added to `mapping.md`

---

<details><summary>We use semantic PR titles to automate the release process!</summary>

https://conventionalcommits.org

PRs should be titled following using the format: `< TYPE >(< scope >)?: description`

### Available Types:

- `feat`: new functions, and changes to a function's type that would impact users.
- `fix`: changes to the runtime behavior of an existing function, or refinements to it's type that shouldn't impact most users.
- `perf`: changes to function implementations that improve a functions _runtime_ performance.
- `refactor`: changes to function implementations that are neither `fix` nor `perf`
- `test`: tests-only changes (transparent to users of the function).
- `docs`: changes to the documentation of a function **or the documentation site**.
- `build`, `ci`, `style`, `chore`, and `revert`: are only relevant for the internals of the library.

For scope put the name of the function you are working on (either new or
existing).

</details>

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/683/builds/509441) or the icon next to each commit SHA.

Latest deployment of this branch, based on commit 478988eb6a7ad2bbd98d959705dc59f8ab81eb9d:

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

codecov[bot]: ## [Codecov](https://app.codecov.io/gh/remeda/remeda/pull/683?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 98.61%. Comparing base [(`96a4e82`)](https://app.codecov.io/gh/remeda/remeda/commit/96a4e8276231ba124b6ff3ac6ff997112a56d2d6?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=remeda) to head [(`478988e`)](https://app.codecov.io/gh/remeda/remeda/commit/478988eb6a7ad2bbd98d959705dc59f8ab81eb9d?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=remeda).
> Report is 91 commits behind head on master.


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


```diff
@@            Coverage Diff             @@
##           master     #683      +/-   ##
==========================================
+ Coverage   98.41%   98.61%   +0.20%     
==========================================
  Files         127      154      +27     
  Lines        7954    11131    +3177     
  Branches      724      939     +215     
==========================================
+ Hits         7828    10977    +3149     
- Misses        123      151      +28     
  Partials        3        3              
```



</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/remeda/remeda/pull/683?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).

lesha1201: > Next, in a separate PR, I'm going to try to improve the overall typing of hasSubObject to make the narrowing more smarter. I already did some progress but stuck on making it work well with pipe function.

Opened PR - https://github.com/remeda/remeda/pull/686.
lesha1201: Fixes: https://github.com/remeda/remeda/issues/678

Patch:
diff --git a/.codesandbox/ci.json b/.codesandbox/ci.json
index 1fc1121..85acb21 100644
--- a/.codesandbox/ci.json
+++ b/.codesandbox/ci.json
@@ -1,4 +1,4 @@
 {
   "sandboxes": ["/examples/vanilla"],
-  "node": "18"
+  "node": "20"
 }
diff --git a/.github/workflows/codecov.yml b/.github/workflows/codecov.yml
index 83b1da3..b833c7a 100644
--- a/.github/workflows/codecov.yml
+++ b/.github/workflows/codecov.yml
@@ -26,3 +26,5 @@ jobs:
 
       - name: 🗺️ Upload coverage
         uses: codecov/codecov-action@v4
+        env:
+          CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
diff --git a/docs/.gitignore b/docs/.gitignore
index afc82a5..edb03d8 100644
--- a/docs/.gitignore
+++ b/docs/.gitignore
@@ -2,6 +2,7 @@
 dist/
 build/
 src/data/data.json
+public/dist/
 
 # generated types
 .astro/
@@ -11,13 +12,6 @@ node_modules/
 
 # logs
 npm-debug.log*
-yarn-debug.log*
-yarn-error.log*
-pnpm-debug.log*
-
-# environment variables
-.env
-.env.production
 
 # macOS-specific files
 .DS_Store
diff --git a/docs/package-lock.json b/docs/package-lock.json
index ecd2cd2..ceba634 100644
--- a/docs/package-lock.json
+++ b/docs/package-lock.json
@@ -11,7 +11,7 @@
       "dependencies": {
         "@astrojs/check": "^0.7.0",
         "@astrojs/mdx": "^3.0.1",
-        "@astrojs/react": "^3.3.4",
+        "@astrojs/react": "^3.4.0",
         "@astrojs/tailwind": "^5.1.0",
         "@radix-ui/react-collapsible": "^1.0.3",
         "@radix-ui/react-dialog": "^1.0.5",
@@ -20,12 +20,12 @@
         "@radix-ui/react-scroll-area": "^1.0.5",
         "@radix-ui/react-select": "^2.0.0",
         "@radix-ui/react-slot": "^1.0.2",
-        "astro": "^4.8.6",
+        "astro": "^4.9.2",
         "class-variance-authority": "^0.7.0",
         "clsx": "^2.1.1",
         "react": "^18.3.1",
         "react-dom": "^18.3.1",
-        "remeda": "^2.0.0",
+        "remeda": "^2.0.3",
         "tailwind-merge": "^2.3.0"
       },
       "devDependencies": {
@@ -33,31 +33,31 @@
         "@tailwindcss/typography": "^0.5.13",
         "@types/dompurify": "^3.0.5",
         "@types/eslint": "^8.56.10",
-        "@types/jsdom": "^21.1.6",
-        "@types/node": "^20.12.12",
-        "@types/react": "^18.3.2",
+        "@types/jsdom": "^21.1.7",
+        "@types/node": "^20.13.0",
+        "@types/react": "^18.3.3",
         "@types/react-dom": "^18.3.0",
-        "@typescript-eslint/eslint-plugin": "^7.10.0",
-        "@typescript-eslint/parser": "^7.10.0",
-        "dompurify": "^3.1.4",
+        "@typescript-eslint/eslint-plugin": "^7.11.0",
+        "@typescript-eslint/parser": "^7.11.0",
+        "dompurify": "^3.1.5",
         "eslint": "^8.57.0",
         "eslint-config-prettier": "^9.1.0",
         "eslint-plugin-astro": "^1.2.0",
         "eslint-plugin-jsx-a11y": "^6.8.0",
-        "eslint-plugin-react": "^7.34.1",
+        "eslint-plugin-react": "^7.34.2",
         "eslint-plugin-react-hooks": "^4.6.2",
         "eslint-plugin-unicorn": "^53.0.0",
-        "jsdom": "^24.0.0",
+        "jsdom": "^24.1.0",
         "marked": "^12.0.2",
         "open-cli": "^8.0.0",
         "prettier": "^3.2.5",
-        "prettier-plugin-astro": "^0.13.0",
-        "prettier-plugin-tailwindcss": "^0.5.14",
+        "prettier-plugin-astro": "^0.14.0",
+        "prettier-plugin-tailwindcss": "^0.6.1",
         "rollup-plugin-visualizer": "^5.12.0",
         "tailwindcss": "^3.4.3",
         "tailwindcss-animate": "^1.0.7",
         "tiny-invariant": "^1.3.3",
-        "type-fest": "^4.18.2",
+        "type-fest": "^4.18.3",
         "typedoc": "^0.25.13",
         "typescript": "^5.4.5"
       }
@@ -349,9 +349,9 @@
       }
     },
     "node_modules/@astrojs/react": {
-      "version": "3.3.4",
-      "resolved": "https://registry.npmjs.org/@astrojs/react/-/react-3.3.4.tgz",
-      "integrity": "sha512-azFYckxcMnHcc5kfXW38oNXR3Na+OMFnzimXVBxRJ906tHkO/sBOBXhsmgQ8JGr/4WEjyaz8zKZj76TWjNMCdw==",
+      "version": "3.4.0",
+      "resolved": "https://registry.npmjs.org/@astrojs/react/-/react-3.4.0.tgz",
+      "integrity": "sha512-5MpU+1WVqirEIoEmF1r6HkxqjOWEMjni/qCzPX/oaSyumOZNdCGfDN9GoGJhgSxiZOVfob+MNy46H4d3AgrYpQ==",
       "dependencies": {
         "@vitejs/plugin-react": "^4.2.1",
         "ultrahtml": "^1.5.3"
@@ -3016,9 +3016,9 @@
       }
     },
     "node_modules/@types/jsdom": {
-      "version": "21.1.6",
-      "resolved": "https://registry.npmjs.org/@types/jsdom/-/jsdom-21.1.6.tgz",
-      "integrity": "sha512-/7kkMsC+/kMs7gAYmmBR9P0vGTnOoLhQhyhQJSlXGI5bzTHp6xdo0TtKWQAsz6pmSAeVqKSbqeyP6hytqr9FDw==",
+      "version": "21.1.7",
+      "resolved": "https://registry.npmjs.org/@types/jsdom/-/jsdom-21.1.7.tgz",
+      "integrity": "sha512-yOriVnggzrnQ3a9OKOCxaVuSug3w3/SbOj5i7VwXWZEyUNl3bLF9V3MfxGbZKuwqJOQyRfqXyROBB1CoZLFWzA==",
       "dev": true,
       "dependencies": {
         "@types/node": "*",
@@ -3064,9 +3064,9 @@
       "integrity": "sha512-IfYcSBWE3hLpBg8+X2SEa8LVkJdJEkT2Ese2aaLs3ptGdVtABxndrMaxuFlQ1qdFf9Q5rDvDpxI3WwgvKFAsQA=="
     },
     "node_modules/@types/node": {
-      "version": "20.12.12",
-      "resolved": "https://registry.npmjs.org/@types/node/-/node-20.12.12.tgz",
-      "integrity": "sha512-eWLDGF/FOSPtAvEqeRAQ4C8LSA7M1I7i0ky1I8U7kD1J5ITyW3AsRhQrKVoWf5pFKZ2kILsEGJhsI9r93PYnOw==",
+      "version": "20.13.0",
+      "resolved": "https://registry.npmjs.org/@types/node/-/node-20.13.0.tgz",
+      "integrity": "sha512-FM6AOb3khNkNIXPnHFDYaHerSv8uN22C91z098AnGccVu+Pcdhi+pNUFDi0iLmPIsVE0JBD0KVS7mzUYt4nRzQ==",
       "devOptional": true,
       "dependencies": {
         "undici-types": "~5.26.4"
@@ -3084,9 +3084,9 @@
       "integrity": "sha512-5zvhXYtRNRluoE/jAp4GVsSduVUzNWKkOZrCDBWYtE7biZywwdC2AcEzg+cSMLFRfVgeAFqpfNabiPjxFddV1Q=="
     },
     "node_modules/@types/react": {
-      "version": "18.3.2",
-      "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.2.tgz",
-      "integrity": "sha512-Btgg89dAnqD4vV7R3hlwOxgqobUQKgx3MmrQRi0yYbs/P0ym8XozIAlkqVilPqHQwXs4e9Tf63rrCgl58BcO4w==",
+      "version": "18.3.3",
+      "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.3.tgz",
+      "integrity": "sha512-hti/R0pS0q1/xx+TsI73XIqk26eBsISZ2R0wUijXIngRK9R/e7Xw/cXVxQK7R5JjW+SV4zGcn5hXjudkN/pLIw==",
       "dependencies": {
         "@types/prop-types": "*",
         "csstype": "^3.0.2"
@@ -3118,16 +3118,16 @@
       "integrity": "sha512-dqId9J8K/vGi5Zr7oo212BGii5m3q5Hxlkwy3WpYuKPklmBEvsbMYYyLxAQpSffdLl/gdW0XUpKWFvYmyoWCoQ=="
     },
     "node_modules/@typescript-eslint/eslint-plugin": {
-      "version": "7.10.0",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.10.0.tgz",
-      "integrity": "sha512-PzCr+a/KAef5ZawX7nbyNwBDtM1HdLIT53aSA2DDlxmxMngZ43O8SIePOeX8H5S+FHXeI6t97mTt/dDdzY4Fyw==",
+      "version": "7.11.0",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.11.0.tgz",
+      "integrity": "sha512-P+qEahbgeHW4JQ/87FuItjBj8O3MYv5gELDzr8QaQ7fsll1gSMTYb6j87MYyxwf3DtD7uGFB9ShwgmCJB5KmaQ==",
       "dev": true,
       "dependencies": {
         "@eslint-community/regexpp": "^4.10.0",
-        "@typescript-eslint/scope-manager": "7.10.0",
-        "@typescript-eslint/type-utils": "7.10.0",
-        "@typescript-eslint/utils": "7.10.0",
-        "@typescript-eslint/visitor-keys": "7.10.0",
+        "@typescript-eslint/scope-manager": "7.11.0",
+        "@typescript-eslint/type-utils": "7.11.0",
+        "@typescript-eslint/utils": "7.11.0",
+        "@typescript-eslint/visitor-keys": "7.11.0",
         "graphemer": "^1.4.0",
         "ignore": "^5.3.1",
         "natural-compare": "^1.4.0",
@@ -3151,15 +3151,15 @@
       }
     },
     "node_modules/@typescript-eslint/parser": {
-      "version": "7.10.0",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-7.10.0.tgz",
-      "integrity": "sha512-2EjZMA0LUW5V5tGQiaa2Gys+nKdfrn2xiTIBLR4fxmPmVSvgPcKNW+AE/ln9k0A4zDUti0J/GZXMDupQoI+e1w==",
+      "version": "7.11.0",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-7.11.0.tgz",
+      "integrity": "sha512-yimw99teuaXVWsBcPO1Ais02kwJ1jmNA1KxE7ng0aT7ndr1pT1wqj0OJnsYVGKKlc4QJai86l/025L6z8CljOg==",
       "dev": true,
       "dependencies": {
-        "@typescript-eslint/scope-manager": "7.10.0",
-        "@typescript-eslint/types": "7.10.0",
-        "@typescript-eslint/typescript-estree": "7.10.0",
-        "@typescript-eslint/visitor-keys": "7.10.0",
+        "@typescript-eslint/scope-manager": "7.11.0",
+        "@typescript-eslint/types": "7.11.0",
+        "@typescript-eslint/typescript-estree": "7.11.0",
+        "@typescript-eslint/visitor-keys": "7.11.0",
         "debug": "^4.3.4"
       },
       "engines": {
@@ -3179,13 +3179,13 @@
       }
     },
     "node_modules/@typescript-eslint/scope-manager": {
-      "version": "7.10.0",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.10.0.tgz",
-      "integrity": "sha512-7L01/K8W/VGl7noe2mgH0K7BE29Sq6KAbVmxurj8GGaPDZXPr8EEQ2seOeAS+mEV9DnzxBQB6ax6qQQ5C6P4xg==",
+      "version": "7.11.0",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.11.0.tgz",
+      "integrity": "sha512-27tGdVEiutD4POirLZX4YzT180vevUURJl4wJGmm6TrQoiYwuxTIY98PBp6L2oN+JQxzE0URvYlzJaBHIekXAw==",
       "dev": true,
       "dependencies": {
-        "@typescript-eslint/types": "7.10.0",
-        "@typescript-eslint/visitor-keys": "7.10.0"
+        "@typescript-eslint/types": "7.11.0",
+        "@typescript-eslint/visitor-keys": "7.11.0"
       },
       "engines": {
         "node": "^18.18.0 || >=20.0.0"
@@ -3196,13 +3196,13 @@
       }
     },
     "node_modules/@typescript-eslint/type-utils": {
-      "version": "7.10.0",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-7.10.0.tgz",
-      "integrity": "sha512-D7tS4WDkJWrVkuzgm90qYw9RdgBcrWmbbRkrLA4d7Pg3w0ttVGDsvYGV19SH8gPR5L7OtcN5J1hTtyenO9xE9g==",
+      "version": "7.11.0",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-7.11.0.tgz",
+      "integrity": "sha512-WmppUEgYy+y1NTseNMJ6mCFxt03/7jTOy08bcg7bxJJdsM4nuhnchyBbE8vryveaJUf62noH7LodPSo5Z0WUCg==",
       "dev": true,
       "dependencies": {
-        "@typescript-eslint/typescript-estree": "7.10.0",
-        "@typescript-eslint/utils": "7.10.0",
+        "@typescript-eslint/typescript-estree": "7.11.0",
+        "@typescript-eslint/utils": "7.11.0",
         "debug": "^4.3.4",
         "ts-api-utils": "^1.3.0"
       },
@@ -3223,9 +3223,9 @@
       }
     },
     "node_modules/@typescript-eslint/types": {
-      "version": "7.10.0",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.10.0.tgz",
-      "integrity": "sha512-7fNj+Ya35aNyhuqrA1E/VayQX9Elwr8NKZ4WueClR3KwJ7Xx9jcCdOrLW04h51de/+gNbyFMs+IDxh5xIwfbNg==",
+      "version": "7.11.0",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.11.0.tgz",
+      "integrity": "sha512-MPEsDRZTyCiXkD4vd3zywDCifi7tatc4K37KqTprCvaXptP7Xlpdw0NR2hRJTetG5TxbWDB79Ys4kLmHliEo/w==",
       "dev": true,
       "engines": {
         "node": "^18.18.0 || >=20.0.0"
@@ -3236,13 +3236,13 @@
       }
     },
     "node_modules/@typescript-eslint/typescript-estree": {
-      "version": "7.10.0",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.10.0.tgz",
-      "integrity": "sha512-LXFnQJjL9XIcxeVfqmNj60YhatpRLt6UhdlFwAkjNc6jSUlK8zQOl1oktAP8PlWFzPQC1jny/8Bai3/HPuvN5g==",
+      "version": "7.11.0",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.11.0.tgz",
+      "integrity": "sha512-cxkhZ2C/iyi3/6U9EPc5y+a6csqHItndvN/CzbNXTNrsC3/ASoYQZEt9uMaEp+xFNjasqQyszp5TumAVKKvJeQ==",
       "dev": true,
       "dependencies": {
-        "@typescript-eslint/types": "7.10.0",
-        "@typescript-eslint/visitor-keys": "7.10.0",
+        "@typescript-eslint/types": "7.11.0",
+        "@typescript-eslint/visitor-keys": "7.11.0",
         "debug": "^4.3.4",
         "globby": "^11.1.0",
         "is-glob": "^4.0.3",
@@ -3264,15 +3264,15 @@
       }
     },
     "node_modules/@typescript-eslint/utils": {
-      "version": "7.10.0",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-7.10.0.tgz",
-      "integrity": "sha512-olzif1Fuo8R8m/qKkzJqT7qwy16CzPRWBvERS0uvyc+DHd8AKbO4Jb7kpAvVzMmZm8TrHnI7hvjN4I05zow+tg==",
+      "version": "7.11.0",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-7.11.0.tgz",
+      "integrity": "sha512-xlAWwPleNRHwF37AhrZurOxA1wyXowW4PqVXZVUNCLjB48CqdPJoJWkrpH2nij9Q3Lb7rtWindtoXwxjxlKKCA==",
       "dev": true,
       "dependencies": {
         "@eslint-community/eslint-utils": "^4.4.0",
-        "@typescript-eslint/scope-manager": "7.10.0",
-        "@typescript-eslint/types": "7.10.0",
-        "@typescript-eslint/typescript-estree": "7.10.0"
+        "@typescript-eslint/scope-manager": "7.11.0",
+        "@typescript-eslint/types": "7.11.0",
+        "@typescript-eslint/typescript-estree": "7.11.0"
       },
       "engines": {
         "node": "^18.18.0 || >=20.0.0"
@@ -3286,12 +3286,12 @@
       }
     },
     "node_modules/@typescript-eslint/visitor-keys": {
-      "version": "7.10.0",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.10.0.tgz",
-      "integrity": "sha512-9ntIVgsi6gg6FIq9xjEO4VQJvwOqA3jaBFQJ/6TK5AvEup2+cECI6Fh7QiBxmfMHXU0V0J4RyPeOU1VDNzl9cg==",
+      "version": "7.11.0",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.11.0.tgz",
+      "integrity": "sha512-7syYk4MzjxTEk0g/w3iqtgxnFQspDJfn6QKD36xMuuhTzjcxY7F8EmBLnALjVyaOF1/bVocu3bS/2/F7rXrveQ==",
       "dev": true,
       "dependencies": {
-        "@typescript-eslint/types": "7.10.0",
+        "@typescript-eslint/types": "7.11.0",
         "eslint-visitor-keys": "^3.4.3"
       },
       "engines": {
@@ -3763,9 +3763,9 @@
       }
     },
     "node_modules/astro": {
-      "version": "4.8.6",
-      "resolved": "https://registry.npmjs.org/astro/-/astro-4.8.6.tgz",
-      "integrity": "sha512-psHIfK+e+bMPhRwghV9yCGH/uc1jvY4DHmDZdoEepax9yA7kzYH0wt3dpkqlcrO2zxl5jzSC3DmqZfkD6wnW9A==",
+      "version": "4.9.2",
+      "resolved": "https://registry.npmjs.org/astro/-/astro-4.9.2.tgz",
+      "integrity": "sha512-apzpRoISk6L8g99m3zKIW5SYxw2GhOju/1Gq0Ccf3SH+X2UcFXF+MTdxPOFl5yJUQg7X0qsVQDOIVf8zHRRKFg==",
       "dependencies": {
         "@astrojs/compiler": "^2.8.0",
         "@astrojs/internal-helpers": "0.4.0",
@@ -4988,9 +4988,9 @@
       }
     },
     "node_modules/dompurify": {
-      "version": "3.1.4",
-      "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.1.4.tgz",
-      "integrity": "sha512-2gnshi6OshmuKil8rMZuQCGiUF3cUxHY3NGDzUAdUx/NPEe5DVnO8BDoAQouvgwnx0R/+a6jUn36Z0FSdq8vww==",
+      "version": "3.1.5",
+      "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.1.5.tgz",
+      "integrity": "sha512-lwG+n5h8QNpxtyrJW/gJWckL+1/DQiYMX8f7t8Z2AZTPw1esVrqjI63i7Zc2Gz0aKzLVMYC1V1PL/ky+aY/NgA==",
       "dev": true
     },
     "node_modules/dset": {
@@ -5448,29 +5448,29 @@
       }
     },
     "node_modules/eslint-plugin-react": {
-      "version": "7.34.1",
-      "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.34.1.tgz",
-      "integrity": "sha512-N97CxlouPT1AHt8Jn0mhhN2RrADlUAsk1/atcT2KyA/l9Q/E6ll7OIGwNumFmWfZ9skV3XXccYS19h80rHtgkw==",
+      "version": "7.34.2",
+      "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.34.2.tgz",
+      "integrity": "sha512-2HCmrU+/JNigDN6tg55cRDKCQWicYAPB38JGSFDQt95jDm8rrvSUo7YPkOIm5l6ts1j1zCvysNcasvfTMQzUOw==",
       "dev": true,
       "dependencies": {
-        "array-includes": "^3.1.7",
-        "array.prototype.findlast": "^1.2.4",
+        "array-includes": "^3.1.8",
+        "array.prototype.findlast": "^1.2.5",
         "array.prototype.flatmap": "^1.3.2",
         "array.prototype.toreversed": "^1.1.2",
         "array.prototype.tosorted": "^1.1.3",
         "doctrine": "^2.1.0",
-        "es-iterator-helpers": "^1.0.17",
+        "es-iterator-helpers": "^1.0.19",
         "estraverse": "^5.3.0",
         "jsx-ast-utils": "^2.4.1 || ^3.0.0",
         "minimatch": "^3.1.2",
-        "object.entries": "^1.1.7",
-        "object.fromentries": "^2.0.7",
-        "object.hasown": "^1.1.3",
-        "object.values": "^1.1.7",
+        "object.entries": "^1.1.8",
+        "object.fromentries": "^2.0.8",
+        "object.hasown": "^1.1.4",
+        "object.values": "^1.2.0",
         "prop-types": "^15.8.1",
         "resolve": "^2.0.0-next.5",
         "semver": "^6.3.1",
-        "string.prototype.matchall": "^4.0.10"
+        "string.prototype.matchall": "^4.0.11"
       },
       "engines": {
         "node": ">=4"
@@ -7681,9 +7681,9 @@
       }
     },
     "node_modules/jsdom": {
-      "version": "24.0.0",
-      "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-24.0.0.tgz",
-      "integrity": "sha512-UDS2NayCvmXSXVP6mpTj+73JnNQadZlr9N68189xib2tx5Mls7swlTNao26IoHv46BZJFvXygyRtyXd1feAk1A==",
+      "version": "24.1.0",
+      "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-24.1.0.tgz",
+      "integrity": "sha512-6gpM7pRXCwIOKxX47cgOyvyQDN/Eh0f1MeKySBV2xGdKtqJBLj8P25eY3EVCWo2mglDDzozR2r2MW4T+JiNUZA==",
       "dev": true,
       "dependencies": {
         "cssstyle": "^4.0.1",
@@ -7691,21 +7691,21 @@
         "decimal.js": "^10.4.3",
         "form-data": "^4.0.0",
         "html-encoding-sniffer": "^4.0.0",
-        "http-proxy-agent": "^7.0.0",
-        "https-proxy-agent": "^7.0.2",
+        "http-proxy-agent": "^7.0.2",
+        "https-proxy-agent": "^7.0.4",
         "is-potential-custom-element-name": "^1.0.1",
-        "nwsapi": "^2.2.7",
+        "nwsapi": "^2.2.10",
         "parse5": "^7.1.2",
-        "rrweb-cssom": "^0.6.0",
+        "rrweb-cssom": "^0.7.0",
         "saxes": "^6.0.0",
         "symbol-tree": "^3.2.4",
-        "tough-cookie": "^4.1.3",
+        "tough-cookie": "^4.1.4",
         "w3c-xmlserializer": "^5.0.0",
         "webidl-conversions": "^7.0.0",
         "whatwg-encoding": "^3.1.1",
         "whatwg-mimetype": "^4.0.0",
         "whatwg-url": "^14.0.0",
-        "ws": "^8.16.0",
+        "ws": "^8.17.0",
         "xml-name-validator": "^5.0.0"
       },
       "engines": {
@@ -7720,6 +7720,12 @@
         }
       }
     },
+    "node_modules/jsdom/node_modules/rrweb-cssom": {
+      "version": "0.7.0",
+      "resolved": "https://registry.npmjs.org/rrweb-cssom/-/rrweb-cssom-0.7.0.tgz",
+      "integrity": "sha512-KlSv0pm9kgQSRxXEMgtivPJ4h826YHsuob8pSHcfSZsSXGtvpEAie8S0AnXuObEJ7nhikOb4ahwxDm0H2yW17g==",
+      "dev": true
+    },
     "node_modules/jsesc": {
       "version": "2.5.2",
       "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz",
@@ -10052,9 +10058,9 @@
       }
     },
     "node_modules/prettier-plugin-astro": {
-      "version": "0.13.0",
-      "resolved": "https://registry.npmjs.org/prettier-plugin-astro/-/prettier-plugin-astro-0.13.0.tgz",
-      "integrity": "sha512-5HrJNnPmZqTUNoA97zn4gNQv9BgVhv+et03314WpQ9H9N8m2L9OSV798olwmG2YLXPl1iSstlJCR1zB3x5xG4g==",
+      "version": "0.14.0",
+      "resolved": "https://registry.npmjs.org/prettier-plugin-astro/-/prettier-plugin-astro-0.14.0.tgz",
+      "integrity": "sha512-7jRGJsexaRIyUzTk8uzXlP45cw6DQ5Ci4bTe0xCBCcuO1Fff8jJy9oI+kRCQKSdDFTSAArMSg8GpvzlKBtSaZA==",
       "devOptional": true,
       "dependencies": {
         "@astrojs/compiler": "^1.5.5",
@@ -10072,9 +10078,9 @@
       "devOptional": true
     },
     "node_modules/prettier-plugin-tailwindcss": {
-      "version": "0.5.14",
-      "resolved": "https://registry.npmjs.org/prettier-plugin-tailwindcss/-/prettier-plugin-tailwindcss-0.5.14.tgz",
-      "integrity": "sha512-Puaz+wPUAhFp8Lo9HuciYKM2Y2XExESjeT+9NQoVFXZsPPnc9VYss2SpxdQ6vbatmt8/4+SN0oe0I1cPDABg9Q==",
+      "version": "0.6.1",
+      "resolved": "https://registry.npmjs.org/prettier-plugin-tailwindcss/-/prettier-plugin-tailwindcss-0.6.1.tgz",
+      "integrity": "sha512-AnbeYZu0WGj+QgKciUgdMnRxrqcxltleZPgdwfA5104BHM3siBLONN/HLW1yS2HvzSNkzpQ/JAj+LN0jcJO+0w==",
       "dev": true,
       "engines": {
         "node": ">=14.21.3"
@@ -10708,9 +10714,10 @@
       }
     },
     "node_modules/remeda": {
-      "version": "2.0.0",
-      "resolved": "https://registry.npmjs.org/remeda/-/remeda-2.0.0.tgz",
-      "integrity": "sha512-KBvW1gjFitqspnhSV+SLAy3g13Kj/NLb/3lLZyIIFC+D+qOYT7UnSQfNBc3+uh9KDBjFfu6lvcuV52TQhubuJg==",
+      "version": "2.0.3",
+      "resolved": "https://registry.npmjs.org/remeda/-/remeda-2.0.3.tgz",
+      "integrity": "sha512-3OtBvbFnhPJPNDxrP4dQ+Y7ghIErMeTy7KQ2vkC6XzrQHbFdlD0tT6mYaJ2S96lUu3umNnhkqni2lhVOdL9UOQ==",
+      "license": "MIT",
       "dependencies": {
         "type-fest": "^4.18.2"
       }
@@ -12331,9 +12338,9 @@
       }
     },
     "node_modules/type-fest": {
-      "version": "4.18.2",
-      "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.18.2.tgz",
-      "integrity": "sha512-+suCYpfJLAe4OXS6+PPXjW3urOS4IoP9waSiLuXfLgqZODKw/aWwASvzqE886wA0kQgGy0mIWyhd87VpqIy6Xg==",
+      "version": "4.18.3",
+      "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.18.3.tgz",
+      "integrity": "sha512-Q08/0IrpvM+NMY9PA2rti9Jb+JejTddwmwmVQGskAlhtcrw1wsRzoR6ode6mR+OAabNa75w/dxedSUY2mlphaQ==",
       "engines": {
         "node": ">=16"
       },
diff --git a/docs/package.json b/docs/package.json
index de46fd3..c4b8ef7 100644
--- a/docs/package.json
+++ b/docs/package.json
@@ -6,11 +6,12 @@
   "author": "Łukasz Sentkiewicz",
   "type": "module",
   "scripts": {
-    "build": "astro check && astro build",
+    "build": "astro check && npm run build:scripts && astro build",
     "build:all": "npm run clean && npm run typedoc && npm run build",
     "build:debug": "npm run build && open-cli node_modules/.cache/bundleVisualizer.html",
-    "check": "npx tsc && astro check",
-    "clean": "rm -rf dist && rm -f src/data/data.json",
+    "build:scripts": "vite build --config vite.scripts.config.ts",
+    "check": "tsc && astro check",
+    "clean": "rm -rf dist && rm -f src/data/data.json && rm -rf public/dist",
     "dev": "astro dev",
     "preview": "astro preview",
     "typedoc": "typedoc --json src/data/data.json"
@@ -18,7 +19,7 @@
   "dependencies": {
     "@astrojs/check": "^0.7.0",
     "@astrojs/mdx": "^3.0.1",
-    "@astrojs/react": "^3.3.4",
+    "@astrojs/react": "^3.4.0",
     "@astrojs/tailwind": "^5.1.0",
     "@radix-ui/react-collapsible": "^1.0.3",
     "@radix-ui/react-dialog": "^1.0.5",
@@ -27,12 +28,12 @@
     "@radix-ui/react-scroll-area": "^1.0.5",
     "@radix-ui/react-select": "^2.0.0",
     "@radix-ui/react-slot": "^1.0.2",
-    "astro": "^4.8.6",
+    "astro": "^4.9.2",
     "class-variance-authority": "^0.7.0",
     "clsx": "^2.1.1",
     "react": "^18.3.1",
     "react-dom": "^18.3.1",
-    "remeda": "^2.0.0",
+    "remeda": "^2.0.3",
     "tailwind-merge": "^2.3.0"
   },
   "devDependencies": {
@@ -40,31 +41,31 @@
     "@tailwindcss/typography": "^0.5.13",
     "@types/dompurify": "^3.0.5",
     "@types/eslint": "^8.56.10",
-    "@types/jsdom": "^21.1.6",
-    "@types/node": "^20.12.12",
-    "@types/react": "^18.3.2",
+    "@types/jsdom": "^21.1.7",
+    "@types/node": "^20.13.0",
+    "@types/react": "^18.3.3",
     "@types/react-dom": "^18.3.0",
-    "@typescript-eslint/eslint-plugin": "^7.10.0",
-    "@typescript-eslint/parser": "^7.10.0",
-    "dompurify": "^3.1.4",
+    "@typescript-eslint/eslint-plugin": "^7.11.0",
+    "@typescript-eslint/parser": "^7.11.0",
+    "dompurify": "^3.1.5",
     "eslint": "^8.57.0",
     "eslint-config-prettier": "^9.1.0",
     "eslint-plugin-astro": "^1.2.0",
     "eslint-plugin-jsx-a11y": "^6.8.0",
-    "eslint-plugin-react": "^7.34.1",
+    "eslint-plugin-react": "^7.34.2",
     "eslint-plugin-react-hooks": "^4.6.2",
     "eslint-plugin-unicorn": "^53.0.0",
-    "jsdom": "^24.0.0",
+    "jsdom": "^24.1.0",
     "marked": "^12.0.2",
     "open-cli": "^8.0.0",
     "prettier": "^3.2.5",
-    "prettier-plugin-astro": "^0.13.0",
-    "prettier-plugin-tailwindcss": "^0.5.14",
+    "prettier-plugin-astro": "^0.14.0",
+    "prettier-plugin-tailwindcss": "^0.6.1",
     "rollup-plugin-visualizer": "^5.12.0",
     "tailwindcss": "^3.4.3",
     "tailwindcss-animate": "^1.0.7",
     "tiny-invariant": "^1.3.3",
-    "type-fest": "^4.18.2",
+    "type-fest": "^4.18.3",
     "typedoc": "^0.25.13",
     "typescript": "^5.4.5"
   }
diff --git a/docs/src/components/header.astro b/docs/src/components/header.astro
index e7e96fe..52a1d10 100644
--- a/docs/src/components/header.astro
+++ b/docs/src/components/header.astro
@@ -19,9 +19,11 @@ const navbarEntries = await getNavbarEntries(
 ---
 
 <header
-  class="sticky top-0 z-50 flex h-16 flex-none items-center gap-2 bg-background/95 px-4 shadow backdrop-blur supports-[backdrop-filter]:bg-background/60 sm:px-8"
+  class="sticky top-0 z-50 flex h-16 flex-none items-center bg-background/95 px-3 shadow backdrop-blur supports-[backdrop-filter]:bg-background/60 sm:px-8"
 >
-  <nav class="flex flex-1 items-center gap-4 text-xs sm:gap-8 sm:text-base">
+  <nav
+    class="flex flex-1 items-center gap-3 overflow-hidden text-xs sm:gap-8 sm:text-base"
+  >
     <a href="/" class="font-bold">Remeda</a>
     <NavLink href="/docs" additionalHrefs={["/v1"]}>Documentation</NavLink>
     <NavLink href="/mapping">Lodash/Ramda</NavLink>
diff --git a/docs/src/components/nav-link.astro b/docs/src/components/nav-link.astro
index 9bc8ba4..5f36690 100644
--- a/docs/src/components/nav-link.astro
+++ b/docs/src/components/nav-link.astro
@@ -15,7 +15,7 @@ const isActive = [href, ...additionalHrefs].some((path) => path === trimmed);
 <a
   href={href}
   class:list={[
-    "transition-colors hover:text-foreground/80",
+    "truncate transition-colors hover:text-foreground/80",
     isActive ? "font-semibold text-primary" : "text-foreground/60",
   ]}
 >
diff --git a/docs/src/layouts/base.astro b/docs/src/layouts/base.astro
index 9cfde86..26b67c0 100644
--- a/docs/src/layouts/base.astro
+++ b/docs/src/layouts/base.astro
@@ -14,10 +14,7 @@ import "@/styles/globals.css";
     <meta name="viewport" content="width=device-width" />
     <meta name="generator" content={Astro.generator} />
     <title>Remeda</title>
-    <script>
-      import { initTheme } from "@/lib/theme";
-      initTheme();
-    </script>
+    <script is:inline src="/dist/scripts/init-theme.js"></script>
   </head>
   <body class="flex w-full flex-col items-stretch">
     <Header />
diff --git a/docs/src/layouts/docs-page.astro b/docs/src/layouts/docs-page.astro
index f416318..d4b55ed 100644
--- a/docs/src/layouts/docs-page.astro
+++ b/docs/src/layouts/docs-page.astro
@@ -48,7 +48,7 @@ const { pathname } = Astro.url;
         <VersionSelector pathname={pathname} client:load /></Navbar
       >
     </aside>
-    <main class="flex-1 space-y-8">
+    <main class="flex-1 space-y-8 overflow-hidden">
       {
         renderedCollection.map(({ slug, rendered: { Content } }) => (
           <Prose id={slug} class="scroll-mt-24">
diff --git a/docs/src/layouts/wiki.astro b/docs/src/layouts/wiki.astro
index d8440f2..069a9ca 100644
--- a/docs/src/layouts/wiki.astro
+++ b/docs/src/layouts/wiki.astro
@@ -4,7 +4,7 @@ import BaseLayout from "./base.astro";
 ---
 
 <BaseLayout>
-  <main class="p-6">
+  <main class="overflow-hidden p-6">
     <Prose class="container max-w-4xl">
       <slot />
     </Prose>
diff --git a/docs/src/lib/get-tags.ts b/docs/src/lib/get-tags.ts
index d534f6b..11e8360 100644
--- a/docs/src/lib/get-tags.ts
+++ b/docs/src/lib/get-tags.ts
@@ -19,7 +19,7 @@ export function getTags([
     out.push("Indexed");
   }
 
-  if (method.pipeable) {
+  if (method.pipeable ?? method.lazy) {
     out.push("Lazy");
   }
 
diff --git a/docs/src/lib/transform.ts b/docs/src/lib/transform.ts
index e4c5e21..4aa70c2 100644
--- a/docs/src/lib/transform.ts
+++ b/docs/src/lib/transform.ts
@@ -12,7 +12,7 @@ export type FunctionParam = ReturnType<typeof getParameter>;
 export type FunctionReturn = ReturnType<typeof transformReturns>;
 
 export type SourceTags = Readonly<
-  Partial<Record<"pipeable" | "strict" | "indexed", boolean>>
+  Partial<Record<"pipeable" | "strict" | "indexed" | "lazy", boolean>>
 >;
 
 export function transformProject(project: typeof DATA) {
@@ -96,7 +96,7 @@ const transformComment = (comment: JSONOutput.Comment) =>
 
 const extractTags = (comment: JSONOutput.Comment): SourceTags =>
   ({
-    pipeable: hasTag(comment, "pipeable"),
+    lazy: hasTag(comment, "lazy"),
   }) as const;
 
 const transformArgs = (
diff --git a/docs/src/pages/index.mdx b/docs/src/pages/index.mdx
index a5400d9..11ba630 100644
--- a/docs/src/pages/index.mdx
+++ b/docs/src/pages/index.mdx
@@ -57,7 +57,7 @@ expected.
 ```ts
 const DATA = [1, 2, 3] as const;
 
-const builtin = DATA.map(DATA, (x) => x.toString());
+const builtin = DATA.map((x) => x.toString());
 //    ^? string[]
 
 const withRemeda = R.map(DATA, (x) => x.toString());
diff --git a/docs/src/scripts/README.md b/docs/src/scripts/README.md
new file mode 100644
index 0000000..dbedd17
--- /dev/null
+++ b/docs/src/scripts/README.md
@@ -0,0 +1,17 @@
+# Warning!
+
+The scripts in this folder are not bundled into our main project! Instead, they
+are built in a pre-processing step **before** astro builds the project. They are
+then used in our project as static imports so they can run **before** the page
+is loaded.
+
+The scripts are built via vite (see `vite.scripts.config.ts`) into the
+`public/dist/scripts` folder.
+
+### Example
+
+```html
+<script is:inline src="/dist/scripts/<__SCRIPT_NAME__>.js"></script>
+```
+
+**This mechanism is rarely needed!**
diff --git a/docs/src/scripts/init-theme.ts b/docs/src/scripts/init-theme.ts
new file mode 100644
index 0000000..778ed6d
--- /dev/null
+++ b/docs/src/scripts/init-theme.ts
@@ -0,0 +1,2 @@
+import { initTheme } from "../lib/theme";
+initTheme();
diff --git a/docs/tsconfig.json b/docs/tsconfig.json
index a083fac..9c890dc 100644
--- a/docs/tsconfig.json
+++ b/docs/tsconfig.json
@@ -1,6 +1,6 @@
 {
   "extends": "astro/tsconfigs/strictest",
-  "include": ["src/**/*", ".eslintrc.cjs", "*.config.js"],
+  "include": ["src/**/*", ".eslintrc.cjs", "*.config.js", "*.config.ts"],
   "compilerOptions": {
     "baseUrl": ".",
     "paths": {
diff --git a/docs/vite.scripts.config.ts b/docs/vite.scripts.config.ts
new file mode 100644
index 0000000..cc93a96
--- /dev/null
+++ b/docs/vite.scripts.config.ts
@@ -0,0 +1,56 @@
+import fs from "node:fs/promises";
+import path from "node:path";
+import { filter, pipe, pullObject } from "remeda";
+import { defineConfig, resolveConfig } from "vite";
+import astroConfig from "./astro.config.mjs";
+
+const SOURCE_DIRECTORY = "src/scripts";
+const OUTPUT_DIR = "dist/scripts";
+
+// This is not the main vite config for the astro project! It uses vite because
+// it already comes bundled with astro to build our scripts folder so they can
+// be imported and run **before page load** in our project. You would rarely
+// need this.
+export default defineConfig({
+  build: {
+    rollupOptions: {
+      input: await getSourceFiles(SOURCE_DIRECTORY),
+
+      output: {
+        dir: await getPublicDirectory(OUTPUT_DIR),
+        entryFileNames: "[name].js",
+      },
+    },
+  },
+
+  // Disable the publicDir for the scripts because we aren't really building a
+  // server here and the name clash makes vite unhappy (very unhappy!).
+  publicDir: false,
+});
+
+async function getSourceFiles(
+  directory: string,
+): Promise<Record<string, string>> {
+  const sourceDir = path.resolve(import.meta.dirname, directory);
+  // @ts-expect-error [ts2322] - TODO: Our typing for pullObject (and fromKeys!) make the results Partial even for simple records!
+  return pipe(
+    await fs.readdir(sourceDir),
+    filter((fileName) => fileName.endsWith(".ts")),
+    pullObject(
+      (fileName) => path.basename(fileName, ".ts"),
+      (fileName) => path.resolve(sourceDir, fileName),
+    ),
+  );
+}
+
+/**
+ * This function ensures that even if the public directory is changed in the
+ * astro config we will still output the scripts to the correct location.
+ */
+async function getPublicDirectory(subDir: string): Promise<string> {
+  const { publicDir } = await resolveConfig(
+    astroConfig.vite ?? {},
+    "build" /* command */,
+  );
+  return path.resolve(publicDir, subDir);
+}
diff --git a/eslint.config.js b/eslint.config.js
index c325d8b..988d589 100644
--- a/eslint.config.js
+++ b/eslint.config.js
@@ -156,7 +156,7 @@ export default config(
             "dataFirst",
             "dataLast",
             "indexed",
-            "pipeable",
+            "lazy",
             "strict",
             "category",
           ],
@@ -233,7 +233,7 @@ export default config(
                 "dataFirst",
                 "dataLast",
                 "indexed",
-                "pipeable",
+                "lazy",
                 "strict",
                 "category",
               ],
diff --git a/package-lock.json b/package-lock.json
index df17db8..4c66b06 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -26,7 +26,7 @@
         "lint-staged": "^15.2.2",
         "prettier": "^3.2.5",
         "publint": "^0.2.7",
-        "semantic-release": "^23.0.8",
+        "semantic-release": "^24.0.0",
         "tsup": "^8.0.2",
         "typescript": "^5.4.5",
         "typescript-eslint": "^7.8.0",
@@ -263,9 +263,9 @@
       }
     },
     "node_modules/@es-joy/jsdoccomment": {
-      "version": "0.43.0",
-      "resolved": "https://registry.npmjs.org/@es-joy/jsdoccomment/-/jsdoccomment-0.43.0.tgz",
-      "integrity": "sha512-Q1CnsQrytI3TlCB1IVWXWeqUIPGVEKGaE7IbVdt13Nq/3i0JESAkQQERrfiQkmlpijl+++qyqPgaS31Bvc1jRQ==",
+      "version": "0.43.1",
+      "resolved": "https://registry.npmjs.org/@es-joy/jsdoccomment/-/jsdoccomment-0.43.1.tgz",
+      "integrity": "sha512-I238eDtOolvCuvtxrnqtlBaw0BwdQuYqK7eA6XIonicMdOOOb75mqdIzkGDUbS04+1Di007rgm9snFRNeVrOog==",
       "dev": true,
       "dependencies": {
         "@types/eslint": "^8.56.5",
@@ -1346,14 +1346,15 @@
       "dev": true
     },
     "node_modules/@semantic-release/commit-analyzer": {
-      "version": "12.0.0",
-      "resolved": "https://registry.npmjs.org/@semantic-release/commit-analyzer/-/commit-analyzer-12.0.0.tgz",
-      "integrity": "sha512-qG+md5gdes+xa8zP7lIo1fWE17zRdO8yMCaxh9lyL65TQleoSv8WHHOqRURfghTytUh+NpkSyBprQ5hrkxOKVQ==",
+      "version": "13.0.0",
+      "resolved": "https://registry.npmjs.org/@semantic-release/commit-analyzer/-/commit-analyzer-13.0.0.tgz",
+      "integrity": "sha512-KtXWczvTAB1ZFZ6B4O+w8HkfYm/OgQb1dUGNFZtDgQ0csggrmkq8sTxhd+lwGF8kMb59/RnG9o4Tn7M/I8dQ9Q==",
       "dev": true,
       "dependencies": {
-        "conventional-changelog-angular": "^7.0.0",
-        "conventional-commits-filter": "^4.0.0",
-        "conventional-commits-parser": "^5.0.0",
+        "conventional-changelog-angular": "^8.0.0",
+        "conventional-changelog-writer": "^8.0.0",
+        "conventional-commits-filter": "^5.0.0",
+        "conventional-commits-parser": "^6.0.0",
         "debug": "^4.0.0",
         "import-from-esm": "^1.0.3",
         "lodash-es": "^4.17.21",
@@ -1583,15 +1584,15 @@
       }
     },
     "node_modules/@semantic-release/release-notes-generator": {
-      "version": "13.0.0",
-      "resolved": "https://registry.npmjs.org/@semantic-release/release-notes-generator/-/release-notes-generator-13.0.0.tgz",
-      "integrity": "sha512-LEeZWb340keMYuREMyxrODPXJJ0JOL8D/mCl74B4LdzbxhtXV2LrPN2QBEcGJrlQhoqLO0RhxQb6masHytKw+A==",
+      "version": "14.0.0",
+      "resolved": "https://registry.npmjs.org/@semantic-release/release-notes-generator/-/release-notes-generator-14.0.0.tgz",
+      "integrity": "sha512-XRxwr4e46yUMaXT8KGFBlRJlp5+NOMaufdq8qaEWlcJ7cT4Pn/iRmDGglZ2TgDe6GVP+u1boXFEnSs7N8Yzhng==",
       "dev": true,
       "dependencies": {
-        "conventional-changelog-angular": "^7.0.0",
-        "conventional-changelog-writer": "^7.0.0",
-        "conventional-commits-filter": "^4.0.0",
-        "conventional-commits-parser": "^5.0.0",
+        "conventional-changelog-angular": "^8.0.0",
+        "conventional-changelog-writer": "^8.0.0",
+        "conventional-commits-filter": "^5.0.0",
+        "conventional-commits-parser": "^6.0.0",
         "debug": "^4.0.0",
         "get-stream": "^7.0.0",
         "import-from-esm": "^1.0.3",
@@ -1767,9 +1768,9 @@
       "dev": true
     },
     "node_modules/@types/node": {
-      "version": "20.12.11",
-      "resolved": "https://registry.npmjs.org/@types/node/-/node-20.12.11.tgz",
-      "integrity": "sha512-vDg9PZ/zi+Nqp6boSOT7plNuthRugEKixDv5sFTIpkE89MmNtEArAShI4mxuX2+UrLEe9pxC1vm2cjm9YlWbJw==",
+      "version": "20.13.0",
+      "resolved": "https://registry.npmjs.org/@types/node/-/node-20.13.0.tgz",
+      "integrity": "sha512-FM6AOb3khNkNIXPnHFDYaHerSv8uN22C91z098AnGccVu+Pcdhi+pNUFDi0iLmPIsVE0JBD0KVS7mzUYt4nRzQ==",
       "dev": true,
       "dependencies": {
         "undici-types": "~5.26.4"
@@ -1781,6 +1782,12 @@
       "integrity": "sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA==",
       "dev": true
     },
+    "node_modules/@types/semver": {
+      "version": "7.5.8",
+      "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.8.tgz",
+      "integrity": "sha512-I8EUhyrgfLrcTkzV3TSsGyl1tSuPrEDzr0yd5m90UgNxQkyDXULk3b6MlQqTCpZpNtWe1K0hzclnZkTcLBe2UQ==",
+      "dev": true
+    },
     "node_modules/@typescript-eslint/eslint-plugin": {
       "version": "7.11.0",
       "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.11.0.tgz",
@@ -2434,12 +2441,12 @@
       }
     },
     "node_modules/braces": {
-      "version": "3.0.2",
-      "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz",
-      "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==",
+      "version": "3.0.3",
+      "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz",
+      "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==",
       "dev": true,
       "dependencies": {
-        "fill-range": "^7.0.1"
+        "fill-range": "^7.1.1"
       },
       "engines": {
         "node": ">=8"
@@ -3006,62 +3013,58 @@
       }
     },
     "node_modules/conventional-changelog-angular": {
-      "version": "7.0.0",
-      "resolved": "https://registry.npmjs.org/conventional-changelog-angular/-/conventional-changelog-angular-7.0.0.tgz",
-      "integrity": "sha512-ROjNchA9LgfNMTTFSIWPzebCwOGFdgkEq45EnvvrmSLvCtAw0HSmrCs7/ty+wAeYUZyNay0YMUNYFTRL72PkBQ==",
+      "version": "8.0.0",
+      "resolved": "https://registry.npmjs.org/conventional-changelog-angular/-/conventional-changelog-angular-8.0.0.tgz",
+      "integrity": "sha512-CLf+zr6St0wIxos4bmaKHRXWAcsCXrJU6F4VdNDrGRK3B8LDLKoX3zuMV5GhtbGkVR/LohZ6MT6im43vZLSjmA==",
       "dev": true,
       "dependencies": {
         "compare-func": "^2.0.0"
       },
       "engines": {
-        "node": ">=16"
+        "node": ">=18"
       }
     },
     "node_modules/conventional-changelog-writer": {
-      "version": "7.0.1",
-      "resolved": "https://registry.npmjs.org/conventional-changelog-writer/-/conventional-changelog-writer-7.0.1.tgz",
-      "integrity": "sha512-Uo+R9neH3r/foIvQ0MKcsXkX642hdm9odUp7TqgFS7BsalTcjzRlIfWZrZR1gbxOozKucaKt5KAbjW8J8xRSmA==",
+      "version": "8.0.0",
+      "resolved": "https://registry.npmjs.org/conventional-changelog-writer/-/conventional-changelog-writer-8.0.0.tgz",
+      "integrity": "sha512-TQcoYGRatlAnT2qEWDON/XSfnVG38JzA7E0wcGScu7RElQBkg9WWgZd1peCWFcWDh1xfb2CfsrcvOn1bbSzztA==",
       "dev": true,
       "dependencies": {
-        "conventional-commits-filter": "^4.0.0",
+        "@types/semver": "^7.5.5",
+        "conventional-commits-filter": "^5.0.0",
         "handlebars": "^4.7.7",
-        "json-stringify-safe": "^5.0.1",
-        "meow": "^12.0.1",
-        "semver": "^7.5.2",
-        "split2": "^4.0.0"
+        "meow": "^13.0.0",
+        "semver": "^7.5.2"
       },
       "bin": {
-        "conventional-changelog-writer": "cli.mjs"
+        "conventional-changelog-writer": "dist/cli/index.js"
       },
       "engines": {
-        "node": ">=16"
+        "node": ">=18"
       }
     },
     "node_modules/conventional-commits-filter": {
-      "version": "4.0.0",
-      "resolved": "https://registry.npmjs.org/conventional-commits-filter/-/conventional-commits-filter-4.0.0.tgz",
-      "integrity": "sha512-rnpnibcSOdFcdclpFwWa+pPlZJhXE7l+XK04zxhbWrhgpR96h33QLz8hITTXbcYICxVr3HZFtbtUAQ+4LdBo9A==",
+      "version": "5.0.0",
+      "resolved": "https://registry.npmjs.org/conventional-commits-filter/-/conventional-commits-filter-5.0.0.tgz",
+      "integrity": "sha512-tQMagCOC59EVgNZcC5zl7XqO30Wki9i9J3acbUvkaosCT6JX3EeFwJD7Qqp4MCikRnzS18WXV3BLIQ66ytu6+Q==",
       "dev": true,
       "engines": {
-        "node": ">=16"
+        "node": ">=18"
       }
     },
     "node_modules/conventional-commits-parser": {
-      "version": "5.0.0",
-      "resolved": "https://registry.npmjs.org/conventional-commits-parser/-/conventional-commits-parser-5.0.0.tgz",
-      "integrity": "sha512-ZPMl0ZJbw74iS9LuX9YIAiW8pfM5p3yh2o/NbXHbkFuZzY5jvdi5jFycEOkmBW5H5I7nA+D6f3UcsCLP2vvSEA==",
+      "version": "6.0.0",
+      "resolved": "https://registry.npmjs.org/conventional-commits-parser/-/conventional-commits-parser-6.0.0.tgz",
+      "integrity": "sha512-TbsINLp48XeMXR8EvGjTnKGsZqBemisPoyWESlpRyR8lif0lcwzqz+NMtYSj1ooF/WYjSuu7wX0CtdeeMEQAmA==",
       "dev": true,
       "dependencies": {
-        "is-text-path": "^2.0.0",
-        "JSONStream": "^1.3.5",
-        "meow": "^12.0.1",
-        "split2": "^4.0.0"
+        "meow": "^13.0.0"
       },
       "bin": {
-        "conventional-commits-parser": "cli.mjs"
+        "conventional-commits-parser": "dist/cli/index.js"
       },
       "engines": {
-        "node": ">=16"
+        "node": ">=18"
       }
     },
     "node_modules/convert-hrtime": {
@@ -3651,19 +3654,18 @@
       }
     },
     "node_modules/eslint-plugin-jsdoc": {
-      "version": "48.2.4",
-      "resolved": "https://registry.npmjs.org/eslint-plugin-jsdoc/-/eslint-plugin-jsdoc-48.2.4.tgz",
-      "integrity": "sha512-3ebvVgCJFy06gpmuS2ynz13uh9iFSzZ1C1dDkgcSAqVVg82zlORKMk2fvjq708pAO6bwfs5YLttknFEbaoDiGw==",
+      "version": "48.2.7",
+      "resolved": "https://registry.npmjs.org/eslint-plugin-jsdoc/-/eslint-plugin-jsdoc-48.2.7.tgz",
+      "integrity": "sha512-fYj3roTnkFL9OFFTB129rico8lerC5G8Vp2ZW9SjO9RNWG0exVvI+i/Y8Bpm1ufjR0uvT38xtoab/U0Hp8Ybog==",
       "dev": true,
       "dependencies": {
-        "@es-joy/jsdoccomment": "~0.43.0",
+        "@es-joy/jsdoccomment": "~0.43.1",
         "are-docs-informative": "^0.0.2",
         "comment-parser": "1.4.1",
         "debug": "^4.3.4",
         "escape-string-regexp": "^4.0.0",
         "esquery": "^1.5.0",
-        "is-builtin-module": "^3.2.1",
-        "semver": "^7.6.0",
+        "semver": "^7.6.2",
         "spdx-expression-parse": "^4.0.0"
       },
       "engines": {
@@ -3997,9 +3999,9 @@
       }
     },
     "node_modules/fill-range": {
-      "version": "7.0.1",
-      "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
-      "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==",
+      "version": "7.1.1",
+      "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
+      "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
       "dev": true,
       "dependencies": {
         "to-regex-range": "^5.0.1"
@@ -5092,18 +5094,6 @@
         "url": "https://github.com/sponsors/ljharb"
       }
     },
-    "node_modules/is-text-path": {
-      "version": "2.0.0",
-      "resolved": "https://registry.npmjs.org/is-text-path/-/is-text-path-2.0.0.tgz",
-      "integrity": "sha512-+oDTluR6WEjdXEJMnC2z6A4FRwFoYuvShVVEGsS7ewc0UTi2QtAKMDJuL4BDEVt+5T7MjFo12RP8ghOM75oKJw==",
-      "dev": true,
-      "dependencies": {
-        "text-extensions": "^2.0.0"
-      },
-      "engines": {
-        "node": ">=8"
-      }
-    },
     "node_modules/is-typed-array": {
       "version": "1.1.13",
       "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.13.tgz",
@@ -5326,12 +5316,6 @@
       "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==",
       "dev": true
     },
-    "node_modules/json-stringify-safe": {
-      "version": "5.0.1",
-      "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz",
-      "integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==",
-      "dev": true
-    },
     "node_modules/jsonfile": {
       "version": "6.1.0",
       "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz",
@@ -5344,31 +5328,6 @@
         "graceful-fs": "^4.1.6"
       }
     },
-    "node_modules/jsonparse": {
-      "version": "1.3.1",
-      "resolved": "https://registry.npmjs.org/jsonparse/-/jsonparse-1.3.1.tgz",
-      "integrity": "sha512-POQXvpdL69+CluYsillJ7SUhKvytYjW9vG/GKpnf+xP8UWgYEM/RaMzHHofbALDiKbbP1W8UEYmgGl39WkPZsg==",
-      "dev": true,
-      "engines": [
-        "node >= 0.2.0"
-      ]
-    },
-    "node_modules/JSONStream": {
-      "version": "1.3.5",
-      "resolved": "https://registry.npmjs.org/JSONStream/-/JSONStream-1.3.5.tgz",
-      "integrity": "sha512-E+iruNOY8VV9s4JEbe1aNEm6MiszPRr/UfcHMz0TQh1BXSxHK+ASV1R6W4HpjBhSeS+54PIsAMCBmwD06LLsqQ==",
-      "dev": true,
-      "dependencies": {
-        "jsonparse": "^1.2.0",
-        "through": ">=2.2.7 <3"
-      },
-      "bin": {
-        "JSONStream": "bin.js"
-      },
-      "engines": {
-        "node": "*"
-      }
-    },
     "node_modules/keyv": {
       "version": "4.5.4",
       "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz",
@@ -5392,12 +5351,15 @@
       }
     },
     "node_modules/lilconfig": {
-      "version": "3.0.0",
-      "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.0.0.tgz",
-      "integrity": "sha512-K2U4W2Ff5ibV7j7ydLr+zLAkIg5JJ4lPn1Ltsdt+Tz/IjQ8buJ55pZAxoP34lqIiwtF9iAvtLv3JGv7CAyAg+g==",
+      "version": "3.1.1",
+      "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.1.tgz",
+      "integrity": "sha512-O18pf7nyvHTckunPWCV1XUNXU1piu01y2b7ATJ0ppkUkk8ocqVWBrYjJBCwHDjD/ZWcfyrA0P4gKhzWGi5EINQ==",
       "dev": true,
       "engines": {
         "node": ">=14"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/antonk52"
       }
     },
     "node_modules/lines-and-columns": {
@@ -5407,21 +5369,21 @@
       "dev": true
     },
     "node_modules/lint-staged": {
-      "version": "15.2.2",
-      "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-15.2.2.tgz",
-      "integrity": "sha512-TiTt93OPh1OZOsb5B7k96A/ATl2AjIZo+vnzFZ6oHK5FuTk63ByDtxGQpHm+kFETjEWqgkF95M8FRXKR/LEBcw==",
-      "dev": true,
-      "dependencies": {
-        "chalk": "5.3.0",
-        "commander": "11.1.0",
-        "debug": "4.3.4",
-        "execa": "8.0.1",
-        "lilconfig": "3.0.0",
-        "listr2": "8.0.1",
-        "micromatch": "4.0.5",
-        "pidtree": "0.6.0",
-        "string-argv": "0.3.2",
-        "yaml": "2.3.4"
+      "version": "15.2.5",
+      "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-15.2.5.tgz",
+      "integrity": "sha512-j+DfX7W9YUvdzEZl3Rk47FhDF6xwDBV5wwsCPw6BwWZVPYJemusQmvb9bRsW23Sqsaa+vRloAWogbK4BUuU2zA==",
+      "dev": true,
+      "dependencies": {
+        "chalk": "~5.3.0",
+        "commander": "~12.1.0",
+        "debug": "~4.3.4",
+        "execa": "~8.0.1",
+        "lilconfig": "~3.1.1",
+        "listr2": "~8.2.1",
+        "micromatch": "~4.0.7",
+        "pidtree": "~0.6.0",
+        "string-argv": "~0.3.2",
+        "yaml": "~2.4.2"
       },
       "bin": {
         "lint-staged": "bin/lint-staged.js"
@@ -5446,25 +5408,25 @@
       }
     },
     "node_modules/lint-staged/node_modules/commander": {
-      "version": "11.1.0",
-      "resolved": "https://registry.npmjs.org/commander/-/commander-11.1.0.tgz",
-      "integrity": "sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ==",
+      "version": "12.1.0",
+      "resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz",
+      "integrity": "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==",
       "dev": true,
       "engines": {
-        "node": ">=16"
+        "node": ">=18"
       }
     },
     "node_modules/listr2": {
-      "version": "8.0.1",
-      "resolved": "https://registry.npmjs.org/listr2/-/listr2-8.0.1.tgz",
-      "integrity": "sha512-ovJXBXkKGfq+CwmKTjluEqFi3p4h8xvkxGQQAQan22YCgef4KZ1mKGjzfGh6PL6AW5Csw0QiQPNuQyH+6Xk3hA==",
+      "version": "8.2.1",
+      "resolved": "https://registry.npmjs.org/listr2/-/listr2-8.2.1.tgz",
+      "integrity": "sha512-irTfvpib/rNiD637xeevjO2l3Z5loZmuaRi0L0YE5LfijwVY96oyVn0DFD3o/teAok7nfobMG1THvvcHh/BP6g==",
       "dev": true,
       "dependencies": {
         "cli-truncate": "^4.0.0",
         "colorette": "^2.0.20",
         "eventemitter3": "^5.0.1",
         "log-update": "^6.0.0",
-        "rfdc": "^1.3.0",
+        "rfdc": "^1.3.1",
         "wrap-ansi": "^9.0.0"
       },
       "engines": {
@@ -5774,12 +5736,12 @@
       }
     },
     "node_modules/meow": {
-      "version": "12.1.1",
-      "resolved": "https://registry.npmjs.org/meow/-/meow-12.1.1.tgz",
-      "integrity": "sha512-BhXM0Au22RwUneMPwSCnyhTOizdWoIEPU9sp0Aqa1PnDMR5Wv2FGXYDjuzJEIX+Eo2Rb8xuYe5jrnm5QowQFkw==",
+      "version": "13.2.0",
+      "resolved": "https://registry.npmjs.org/meow/-/meow-13.2.0.tgz",
+      "integrity": "sha512-pxQJQzB6djGPXh08dacEloMFopsOqGVRKFPYvPOt9XDZ1HasbgDZA74CJGreSU4G3Ak7EFJGoiH2auq+yXISgA==",
       "dev": true,
       "engines": {
-        "node": ">=16.10"
+        "node": ">=18"
       },
       "funding": {
         "url": "https://github.com/sponsors/sindresorhus"
@@ -5801,12 +5763,12 @@
       }
     },
     "node_modules/micromatch": {
-      "version": "4.0.5",
-      "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz",
-      "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==",
+      "version": "4.0.7",
+      "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.7.tgz",
+      "integrity": "sha512-LPP/3KorzCwBxfeUuZmaR6bG2kdeHSbe0P2tY3FLRU4vYrjYz5hI4QZwV0njUx3jeuKe67YukQ1LSPZBKDqO/Q==",
       "dev": true,
       "dependencies": {
-        "braces": "^3.0.2",
+        "braces": "^3.0.3",
         "picomatch": "^2.3.1"
       },
       "engines": {
@@ -9035,9 +8997,9 @@
       }
     },
     "node_modules/picocolors": {
-      "version": "1.0.0",
-      "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz",
-      "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==",
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.1.tgz",
+      "integrity": "sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew==",
       "dev": true
     },
     "node_modules/picomatch": {
@@ -9323,13 +9285,13 @@
       "dev": true
     },
     "node_modules/publint": {
-      "version": "0.2.7",
-      "resolved": "https://registry.npmjs.org/publint/-/publint-0.2.7.tgz",
-      "integrity": "sha512-tLU4ee3110BxWfAmCZggJmCUnYWgPTr0QLnx08sqpLYa8JHRiOudd+CgzdpfU5x5eOaW2WMkpmOrFshRFYK7Mw==",
+      "version": "0.2.8",
+      "resolved": "https://registry.npmjs.org/publint/-/publint-0.2.8.tgz",
+      "integrity": "sha512-C5MjGJ7gpanqaDpgBN+6QhjvXcoj0/YpbucoW29oO5729CGTMzfr3wZTIYcpzB1xl9ZfEqj4KL86P2Z50pt/JA==",
       "dev": true,
       "dependencies": {
         "npm-packlist": "^5.1.3",
-        "picocolors": "^1.0.0",
+        "picocolors": "^1.0.1",
         "sade": "^1.8.1"
       },
       "bin": {
@@ -9938,16 +9900,16 @@
       }
     },
     "node_modules/semantic-release": {
-      "version": "23.1.1",
-      "resolved": "https://registry.npmjs.org/semantic-release/-/semantic-release-23.1.1.tgz",
-      "integrity": "sha512-qqJDBhbtHsjUEMsojWKGuL5lQFCJuPtiXKEIlFKyTzDDGTAE/oyvznaP8GeOr5PvcqBJ6LQz4JCENWPLeehSpA==",
+      "version": "24.0.0",
+      "resolved": "https://registry.npmjs.org/semantic-release/-/semantic-release-24.0.0.tgz",
+      "integrity": "sha512-v46CRPw+9eI3ZuYGF2oAjqPqsfbnfFTwLBgQsv/lch4goD09ytwOTESMN4QIrx/wPLxUGey60/NMx+ANQtWRsA==",
       "dev": true,
       "dependencies": {
-        "@semantic-release/commit-analyzer": "^12.0.0",
+        "@semantic-release/commit-analyzer": "^13.0.0-beta.1",
         "@semantic-release/error": "^4.0.0",
         "@semantic-release/github": "^10.0.0",
         "@semantic-release/npm": "^12.0.0",
-        "@semantic-release/release-notes-generator": "^13.0.0",
+        "@semantic-release/release-notes-generator": "^14.0.0-beta.1",
         "aggregate-error": "^5.0.0",
         "cosmiconfig": "^9.0.0",
         "debug": "^4.0.0",
@@ -10487,15 +10449,6 @@
       "integrity": "sha512-sh8PWc/ftMqAAdFiBu6Fy6JUOYjqDJBJvIhpfDMyHrr0Rbp5liZqd4TjtQ/RgfLjKFZb+LMx5hpml5qOWy0qvg==",
       "dev": true
     },
-    "node_modules/split2": {
-      "version": "4.2.0",
-      "resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz",
-      "integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==",
-      "dev": true,
-      "engines": {
-        "node": ">= 10.x"
-      }
-    },
     "node_modules/stackback": {
       "version": "0.0.2",
       "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz",
@@ -10923,18 +10876,6 @@
         "url": "https://github.com/sponsors/isaacs"
       }
     },
-    "node_modules/text-extensions": {
-      "version": "2.4.0",
-      "resolved": "https://registry.npmjs.org/text-extensions/-/text-extensions-2.4.0.tgz",
-      "integrity": "sha512-te/NtwBwfiNRLf9Ijqx3T0nlqZiQ2XrrtBvu+cLL8ZRrGkO0NHTug8MYFKyoSrv/sHTaSKfilUkizV6XhxMJ3g==",
-      "dev": true,
-      "engines": {
-        "node": ">=8"
-      },
-      "funding": {
-        "url": "https://github.com/sponsors/sindresorhus"
-      }
-    },
     "node_modules/text-table": {
       "version": "0.2.0",
       "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz",
@@ -10962,12 +10903,6 @@
         "node": ">=0.8"
       }
     },
-    "node_modules/through": {
-      "version": "2.3.8",
-      "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz",
-      "integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==",
-      "dev": true
-    },
     "node_modules/through2": {
       "version": "2.0.5",
       "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz",
@@ -11325,9 +11260,9 @@
       }
     },
     "node_modules/type-fest": {
-      "version": "4.18.2",
-      "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.18.2.tgz",
-      "integrity": "sha512-+suCYpfJLAe4OXS6+PPXjW3urOS4IoP9waSiLuXfLgqZODKw/aWwASvzqE886wA0kQgGy0mIWyhd87VpqIy6Xg==",
+      "version": "4.18.3",
+      "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.18.3.tgz",
+      "integrity": "sha512-Q08/0IrpvM+NMY9PA2rti9Jb+JejTddwmwmVQGskAlhtcrw1wsRzoR6ode6mR+OAabNa75w/dxedSUY2mlphaQ==",
       "engines": {
         "node": ">=16"
       },
@@ -12409,10 +12344,13 @@
       }
     },
     "node_modules/yaml": {
-      "version": "2.3.4",
-      "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.3.4.tgz",
-      "integrity": "sha512-8aAvwVUSHpfEqTQ4w/KMlf3HcRdt50E5ODIQJBw1fQ5RL34xabzxtUlzTXVqc4rkZsPbvrXKWnABCD7kWSmocA==",
+      "version": "2.4.2",
+      "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.4.2.tgz",
+      "integrity": "sha512-B3VqDZ+JAg1nZpaEmWtTXUlBneoGx6CPM9b0TENK6aoSu5t73dItudwdgmi6tHlIZZId4dZ9skcAQ2UbcyAeVA==",
       "dev": true,
+      "bin": {
+        "yaml": "bin.mjs"
+      },
       "engines": {
         "node": ">= 14"
       }
diff --git a/package.json b/package.json
index 1c7cb81..5afb04b 100644
--- a/package.json
+++ b/package.json
@@ -82,7 +82,7 @@
     "lint-staged": "^15.2.2",
     "prettier": "^3.2.5",
     "publint": "^0.2.7",
-    "semantic-release": "^23.0.8",
+    "semantic-release": "^24.0.0",
     "tsup": "^8.0.2",
     "typescript": "^5.4.5",
     "typescript-eslint": "^7.8.0",
diff --git a/src/addProp.ts b/src/addProp.ts
index d6be098..7e2ebd4 100644
--- a/src/addProp.ts
+++ b/src/addProp.ts
@@ -1,8 +1,15 @@
+import type { UpsertProp } from "./internal/types";
 import { purry } from "./purry";
 
 /**
  * Add a new property to an object.
  *
+ * The function doesn't do any checks on the input object. If the property
+ * already exists it will be overwritten, and the type of the new value is not
+ * checked against the previous type.
+ *
+ * Use `set` to override values explicitly with better protections.
+ *
  * @param obj - The target object.
  * @param prop - The property name.
  * @param value - The property value.
@@ -13,15 +20,21 @@ import { purry } from "./purry";
  * @dataFirst
  * @category Object
  */
-export function addProp<
-  T extends Record<PropertyKey, unknown>,
-  K extends PropertyKey,
-  V,
->(obj: T, prop: K, value: V): T & { [x in K]: V };
+export function addProp<T, K extends PropertyKey, V>(
+  obj: T,
+  prop: K,
+  value: V,
+): UpsertProp<T, K, V>;
 
 /**
  * Add a new property to an object.
  *
+ * The function doesn't do any checks on the input object. If the property
+ * already exists it will be overwritten, and the type of the new value is not
+ * checked against the previous type.
+ *
+ * Use `set` to override values explicitly with better protections.
+ *
  * @param prop - The property name.
  * @param value - The property value.
  * @signature
@@ -31,22 +44,19 @@ export function addProp<
  * @dataLast
  * @category Object
  */
-export function addProp<
-  T extends Record<PropertyKey, unknown>,
-  K extends PropertyKey,
-  V,
->(prop: K, value: V): (obj: T) => T & { [x in K]: V };
+export function addProp<T, K extends PropertyKey, V>(
+  prop: K,
+  value: V,
+): (obj: T) => UpsertProp<T, K, V>;
 
 export function addProp(...args: ReadonlyArray<unknown>): unknown {
   return purry(addPropImplementation, args);
 }
 
-const addPropImplementation = <
-  T extends Record<PropertyKey, unknown>,
-  K extends PropertyKey,
-  V,
->(
+const addPropImplementation = <T, K extends PropertyKey, V>(
   obj: T,
   prop: K,
   value: V,
-): T & { [x in K]: V } => ({ ...obj, [prop]: value });
+): UpsertProp<T, K, V> =>
+  // @ts-expect-error [ts2322] - Hard to type...
+  ({ ...obj, [prop]: value });
diff --git a/src/debounce.ts b/src/debounce.ts
index 8065320..7b36599 100644
--- a/src/debounce.ts
+++ b/src/debounce.ts
@@ -87,7 +87,7 @@ type DebounceOptions = {
  * @signature
  *   R.debounce(func, options);
  * @example
- *   const debouncer = debounce(identity, { timing: 'trailing', waitMs: 1000 });
+ *   const debouncer = debounce(identity(), { timing: 'trailing', waitMs: 1000 });
  *   const result1 = debouncer.call(1); // => undefined
  *   const result2 = debouncer.call(2); // => undefined
  *   // after 1 second
diff --git a/src/difference.ts b/src/difference.ts
index 4cd2c5c..9bc493e 100644
--- a/src/difference.ts
+++ b/src/difference.ts
@@ -15,7 +15,7 @@ import type { LazyEvaluator } from "./pipe";
  *    R.difference([1, 2, 3, 4], [2, 5, 3]); // => [1, 4]
  *    R.difference([1, 1, 2, 2], [1]); // => [1, 2, 2]
  * @dataFirst
- * @pipeable
+ * @lazy
  * @category Array
  */
 export function difference<T>(
@@ -35,7 +35,7 @@ export function difference<T>(
  *    R.pipe([1, 2, 3, 4], R.difference([2, 5, 3])); // => [1, 4]
  *    R.pipe([1, 1, 2, 2], R.difference([1])); // => [1, 2, 2]
  * @dataFirst
- * @pipeable
+ * @lazy
  * @category Array
  */
 export function difference<T>(
diff --git a/src/differenceWith.ts b/src/differenceWith.ts
index 22f49b1..3a61ede 100644
--- a/src/differenceWith.ts
+++ b/src/differenceWith.ts
@@ -20,7 +20,7 @@ type IsEquals<TFirst, TSecond> = (a: TFirst, b: TSecond) => boolean;
  *      R.equals,
  *    ) // => [{a: 1}, {a: 4}]
  * @dataFirst
- * @pipeable
+ * @lazy
  * @category Array
  */
 export function differenceWith<TFirst, TSecond>(
@@ -48,7 +48,7 @@ export function differenceWith<TFirst, TSecond>(
  *      R.take(2),
  *    ) // => [{a: 1}, {a: 4}]
  * @dataLast
- * @pipeable
+ * @lazy
  * @category Array
  */
 export function differenceWith<TFirst, TSecond>(
diff --git a/src/drop.ts b/src/drop.ts
index b2f5660..73a479e 100644
--- a/src/drop.ts
+++ b/src/drop.ts
@@ -12,7 +12,7 @@ import { purry } from "./purry";
  * @example
  *    R.drop([1, 2, 3, 4, 5], 2) // => [3, 4, 5]
  * @dataFirst
- * @pipeable
+ * @lazy
  * @category Array
  */
 export function drop<T>(array: ReadonlyArray<T>, n: number): Array<T>;
@@ -26,7 +26,7 @@ export function drop<T>(array: ReadonlyArray<T>, n: number): Array<T>;
  * @example
  *    R.drop(2)([1, 2, 3, 4, 5]) // => [3, 4, 5]
  * @dataLast
- * @pipeable
+ * @lazy
  * @category Array
  */
 export function drop<T>(n: number): (array: ReadonlyArray<T>) => Array<T>;
diff --git a/src/filter.ts b/src/filter.ts
index c030ddb..8a89bcb 100644
--- a/src/filter.ts
+++ b/src/filter.ts
@@ -18,7 +18,7 @@ import { purry } from "./purry";
  * @example
  *    R.filter([1, 2, 3], x => x % 2 === 1) // => [1, 3]
  * @dataFirst
- * @pipeable
+ * @lazy
  * @category Array
  */
 export function filter<T, S extends T>(
@@ -45,7 +45,7 @@ export function filter<T>(
  * @example
  *    R.pipe([1, 2, 3], R.filter(x => x % 2 === 1)) // => [1, 3]
  * @dataLast
- * @pipeable
+ * @lazy
  * @category Array
  */
 export function filter<T, S extends T>(
diff --git a/src/find.ts b/src/find.ts
index f366d62..d4a2a3d 100644
--- a/src/find.ts
+++ b/src/find.ts
@@ -27,7 +27,7 @@ import { purry } from "./purry";
  * @example
  *    R.find([1, 3, 4, 6], n => n % 2 === 0) // => 4
  * @dataFirst
- * @pipeable
+ * @lazy
  * @category Array
  */
 export function find<T, S extends T>(
@@ -65,7 +65,7 @@ export function find<T>(
  *      R.find(n => n % 2 === 0)
  *    ) // => 4
  * @dataLast
- * @pipeable
+ * @lazy
  * @category Array
  */
 export function find<T, S extends T>(
diff --git a/src/first.ts b/src/first.ts
index fc97073..7120aa1 100644
--- a/src/first.ts
+++ b/src/first.ts
@@ -22,7 +22,7 @@ type First<T extends IterableContainer> = T extends []
  *    R.first([1, 2, 3]) // => 1
  *    R.first([]) // => undefined
  * @dataFirst
- * @pipeable
+ * @lazy
  * @category Array
  */
 export function first<T extends IterableContainer>(data: T): First<T>;
@@ -41,7 +41,7 @@ export function first<T extends IterableContainer>(data: T): First<T>;
  *      x => x + 1
  *    ); // => 5
  * @dataLast
- * @pipeable
+ * @lazy
  * @category Array
  */
 export function first(): <T extends IterableContainer>(data: T) => First<T>;
diff --git a/src/firstBy.ts b/src/firstBy.ts
index eaf077e..1078986 100644
--- a/src/firstBy.ts
+++ b/src/firstBy.ts
@@ -26,7 +26,7 @@ type FirstBy<T extends IterableContainer> =
  * @signature
  *   R.firstBy(...rules)(data);
  * @example
- *   const max = R.pipe([1,2,3], R.firstBy([R.identity, "desc"])); // => 3;
+ *   const max = R.pipe([1,2,3], R.firstBy([R.identity(), "desc"])); // => 3;
  *   const min = R.pipe([1,2,3], R.firstBy(R.identity())); // => 1;
  *
  *   const data = [{ a: "a" }, { a: "aa" }, { a: "aaa" }] as const;
@@ -55,8 +55,8 @@ export function firstBy<T extends IterableContainer>(
  * @signature
  *   R.firstBy(data, ...rules);
  * @example
- *   const max = R.firstBy([1,2,3], [R.identity, "desc"]); // => 3;
- *   const min = R.firstBy([1,2,3], R.identity); // => 1;
+ *   const max = R.firstBy([1,2,3], [R.identity(), "desc"]); // => 3;
+ *   const min = R.firstBy([1,2,3], R.identity()); // => 1;
  *
  *   const data = [{ a: "a" }, { a: "aa" }, { a: "aaa" }] as const;
  *   const maxBy = R.firstBy(data, [(item) => item.a.length, "desc"]); // => { a: "aaa" };
diff --git a/src/flat.ts b/src/flat.ts
index 7e776d3..a373035 100644
--- a/src/flat.ts
+++ b/src/flat.ts
@@ -77,7 +77,7 @@ type FlatSimpleArrayItems<
  *   R.flat([[1, 2], [3, 4], [5], [[6]]]); // => [1, 2, 3, 4, 5, [6]]
  *   R.flat([[[1]], [[2]]], 2); // => [1, 2]
  * @dataFirst
- * @pipeable
+ * @lazy
  * @category Array
  */
 export function flat<
@@ -102,7 +102,7 @@ export function flat<
  *   R.pipe([[1, 2], [3, 4], [5], [[6]]], R.flat()); // => [1, 2, 3, 4, 5, [6]]
  *   R.pipe([[[1]], [[2]]], R.flat(2)); // => [1, 2]
  * @dataLast
- * @pipeable
+ * @lazy
  * @category Array
  */
 export function flat<Depth extends number = typeof DEFAULT_DEPTH>(
diff --git a/src/flatMap.ts b/src/flatMap.ts
index 33f33d9..178f395 100644
--- a/src/flatMap.ts
+++ b/src/flatMap.ts
@@ -19,7 +19,7 @@ import { purry } from "./purry";
  * @example
  *    R.flatMap([1, 2, 3], x => [x, x * 10]) // => [1, 10, 2, 20, 3, 30]
  * @dataFirst
- * @pipeable
+ * @lazy
  * @category Array
  */
 export function flatMap<T, U>(
@@ -48,7 +48,7 @@ export function flatMap<T, U>(
  * @example
  *    R.pipe([1, 2, 3], R.flatMap(x => [x, x * 10])) // => [1, 10, 2, 20, 3, 30]
  * @dataLast
- * @pipeable
+ * @lazy
  * @category Array
  */
 export function flatMap<T, U>(
diff --git a/src/forEach.ts b/src/forEach.ts
index 0e2971e..df8fa9a 100644
--- a/src/forEach.ts
+++ b/src/forEach.ts
@@ -19,7 +19,7 @@ import { purry } from "./purry";
  *      console.log(x)
  *    });
  * @dataFirst
- * @pipeable
+ * @lazy
  * @category Array
  */
 export function forEach<T>(
@@ -47,7 +47,7 @@ export function forEach<T>(
  *      })
  *    ) // => [1, 2, 3]
  * @dataLast
- * @pipeable
+ * @lazy
  * @category Array
  */
 export function forEach<T>(
diff --git a/src/hasSubObject.ts b/src/hasSubObject.ts
index 2d89b78..1ed6728 100644
--- a/src/hasSubObject.ts
+++ b/src/hasSubObject.ts
@@ -1,7 +1,15 @@
-import type { Simplify } from "type-fest";
+import type { Merge } from "type-fest";
+import type { Branded } from "./internal/types";
 import { isDeepEqual } from "./isDeepEqual";
 import { purry } from "./purry";
 
+declare const HAS_SUB_OBJECT_BRAND: unique symbol;
+
+type HasSubObjectGuard<T, S> = Branded<
+  Merge<T, S>,
+  typeof HAS_SUB_OBJECT_BRAND
+>;
+
 /**
  * Checks if `subObject` is a sub-object of `object`, which means for every
  * property and value in `subObject`, there's the same property in `object`
@@ -19,10 +27,9 @@ import { purry } from "./purry";
  * @category Guard
  */
 export function hasSubObject<T, S extends Partial<T>>(
-  data: T,
+  data: Merge<T, S> | T,
   subObject: S,
-): // TODO: want to use data is Merge<T, S> here, but it's a typescript error. fix after we allow @ts-expect-error in the build process
-data is Simplify<S & T>;
+): data is HasSubObjectGuard<T, S>;
 
 /**
  * Checks if `subObject` is a sub-object of `object`, which means for every
@@ -41,16 +48,16 @@ data is Simplify<S & T>;
  */
 export function hasSubObject<T, S extends Partial<T>>(
   subObject: S,
-): (data: T) => data is Simplify<S & T>;
+): (data: Merge<T, S> | T) => data is HasSubObjectGuard<T, S>;
 
 export function hasSubObject(...args: ReadonlyArray<unknown>): unknown {
   return purry(hasSubObjectImplementation, args);
 }
 
 function hasSubObjectImplementation<T extends object, S extends Partial<T>>(
-  data: T,
+  data: Merge<T, S> | T,
   subObject: S,
-): data is Simplify<S & T> {
+): data is HasSubObjectGuard<T, S> {
   for (const [key, value] of Object.entries(subObject)) {
     if (!Object.hasOwn(data, key)) {
       return false;
diff --git a/src/index.ts b/src/index.ts
index 6ad41e6..330fc21 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -10,6 +10,7 @@ export * from "./concat";
 export * from "./conditional";
 export * from "./constant";
 export * from "./debounce";
+export * from "./difference";
 export * from "./differenceWith";
 export * from "./divide";
 export * from "./doNothing";
@@ -39,6 +40,7 @@ export * from "./hasAtLeast";
 export * from "./hasSubObject";
 export * from "./identity";
 export * from "./indexBy";
+export * from "./intersection";
 export * from "./intersectionWith";
 export * from "./invert";
 export * from "./isArray";
@@ -121,7 +123,7 @@ export * from "./swapIndices";
 export * from "./swapProps";
 export * from "./take";
 export * from "./takeFirstBy";
-export * from "./takeLast.js";
+export * from "./takeLast";
 export * from "./takeLastWhile";
 export * from "./takeWhile";
 export * from "./tap";
diff --git a/src/internal/types.ts b/src/internal/types.ts
index 3ac336f..078b668 100644
--- a/src/internal/types.ts
+++ b/src/internal/types.ts
@@ -1,4 +1,8 @@
-import type { IsAny, IsNever } from "type-fest";
+import type { IsAny, IsLiteral, IsNever, Simplify } from "type-fest";
+
+declare const __brand: unique symbol;
+
+export type Branded<T, Brand extends symbol> = T & { [__brand]: Brand };
 
 export type NonEmptyArray<T> = [T, ...Array<T>];
 
@@ -19,6 +23,27 @@ export type Mapped<T extends IterableContainer, K> = {
  */
 export type IterableContainer<T = unknown> = ReadonlyArray<T> | readonly [];
 
+/**
+ * We define a Simple record as one that doesn't define it's properties beyond
+ * their general type. This is the case for string and number keys (and their
+ * combination). Another way of thinking about simple records is that the number
+ * of properties are unbound (they can be empty, or they can have 1000 props).
+ *
+ * TODO: Template string literals (e.g. `prefix_${number}`) should also be
+ * considered simple, but we don't know how to check for them...
+ */
+export type IfSimpleRecord<
+  T,
+  TypeIfSimpleRecord = true,
+  TypeIfNotSimpleRecord = false,
+> = string extends keyof T
+  ? TypeIfSimpleRecord
+  : number extends keyof T
+    ? TypeIfSimpleRecord
+    : number | string extends keyof T
+      ? TypeIfSimpleRecord
+      : TypeIfNotSimpleRecord;
+
 /**
  * A union of all keys of T which are not symbols, and where number keys are
  * converted to strings, following the definition of `Object.keys` and
@@ -38,6 +63,15 @@ export type EnumerableStringKeyedValueOf<T> = {
   [K in keyof T]-?: K extends symbol ? never : T[K];
 }[keyof T];
 
+/**
+ * This is the type you'd get from doing:
+ * `Object.fromEntries(Object.entries(x))`.
+ */
+export type ReconstructedRecord<T> = Record<
+  EnumerableStringKeyOf<T>,
+  EnumerableStringKeyedValueOf<T>
+>;
+
 /**
  * An extension of Extract for type predicates which falls back to the base
  * in order to narrow the `unknown` case.
@@ -63,23 +97,48 @@ export type CompareFunction<T> = (a: T, b: T) => number;
 // to those with a a union of literal values (e.g. 'cat' | 'dog') when using
 // 'noUncheckedIndexedAccess', the former being implicitly `Partial` whereas
 // the latter are implicitly `Required`.
-// TODO: Support template string literals (e.g. `prefix_${number}`)
-export type ExactRecord<Key extends PropertyKey, Value> =
-  // If either string, number or symbol extend Key it means that Key is at least
-  // as wide as them, so we don't need to wrap the returned record with Partial.
-  string extends Key
-    ? Record<Key, Value>
-    : number extends Key
-      ? Record<Key, Value>
-      : // If the key is specific, e.g. 'cat' | 'dog', the result is partial
-        // because we can't statically know what values the mapper would return on
-        // a specific input
-        Partial<Record<Key, Value>>;
+export type ExactRecord<Key extends PropertyKey, Value> = IfSimpleRecord<
+  Record<Key, Value>,
+  // If either string or number extend Key it means that Key is at least as wide
+  // as them, so we don't need to wrap the returned record with Partial.
+  Record<Key, Value>,
+  // If the key is specific, e.g. 'cat' | 'dog', the result is partial
+  // because we can't statically know what values the mapper would return on
+  // a specific input.
+  Partial<Record<Key, Value>>
+>;
 
 export type ReorderedArray<T extends IterableContainer> = {
   -readonly [P in keyof T]: T[number];
 };
 
+export type UpsertProp<T, K extends PropertyKey, V> = Simplify<
+  // Copy any uninvolved props from the object, they are unaffected by the type.
+  Omit<T, K> &
+    // Rebuild the object for the rest:
+    (IsSingleLiteral<K> extends true
+      ? // If it's a single literal we need to remove the optionality and set
+        // the value as we know this prop would be exactly this value in the
+        // output.
+        { -readonly [P in K]-?: V }
+      : // The key is either a broad type (`string`) or union of literals
+        // ('cat' | 'dog') so we can't say anything for sure, we need to narrow
+        // the types of all relevant props, this has 2 parts, for props already
+        // in the object this means the value _might_ change, or it might not.
+        {
+          -readonly [P in keyof T as P extends K ? P : never]: T[P] | V;
+        } & {
+          // And for new props they might have been added to the object, or they
+          // might not have been, so we need to set them as optional.
+          -readonly [P in K as P extends keyof T ? never : P]?: V;
+        })
+>;
+
+// This type attempts to detect when a type is a single literal value (e.g.
+// "cat"), and not anything else (e.g. "cat" | "dog", string, etc...)
+type IsSingleLiteral<K> =
+  IsLiteral<K> extends true ? (IsUnion<K> extends true ? false : true) : false;
+
 // TODO [2024-10-01]: This type is copied from type-fest because it isn't
 // exported. It's part of the "internal" types. We should check back in a while
 // to see if this type is added to the public offering.
diff --git a/src/intersection.ts b/src/intersection.ts
index 7e9b4e8..ecd2a5f 100644
--- a/src/intersection.ts
+++ b/src/intersection.ts
@@ -15,7 +15,7 @@ import type { LazyEvaluator } from "./pipe";
  *    R.intersection([1, 2, 3], [2, 3, 5]); // => [2, 3]
  *    R.intersection([1, 1, 2, 2], [1]); // => [1]
  * @dataFirst
- * @pipeable
+ * @lazy
  * @category Array
  */
 export function intersection<T, S>(
@@ -35,7 +35,7 @@ export function intersection<T, S>(
  *    R.pipe([1, 2, 3], R.intersection([2, 3, 5])); // => [2, 3]
  *    R.pipe([1, 1, 2, 2], R.intersection([1])); // => [1]
  * @dataFirst
- * @pipeable
+ * @lazy
  * @category Array
  */
 export function intersection<S>(
diff --git a/src/intersectionWith.ts b/src/intersectionWith.ts
index 8c22b58..846c752 100644
--- a/src/intersectionWith.ts
+++ b/src/intersectionWith.ts
@@ -23,7 +23,7 @@ type Comparator<TFirst, TSecond> = (a: TFirst, b: TSecond) => boolean;
  *      (a, b) => a.id === b,
  *    ) // => [{ id: 3, name: 'Emma' }]
  * @dataFirst
- * @pipeable
+ * @lazy
  * @category Array
  */
 export function intersectionWith<TFirst, TSecond>(
@@ -49,7 +49,7 @@ export function intersectionWith<TFirst, TSecond>(
  *        { id: 3, name: 'Emma' },
  *      ]); // => [{ id: 3, name: 'Emma' }]
  * @dataLast
- * @pipeable
+ * @lazy
  * @category Array
  */
 export function intersectionWith<TFirst, TSecond>(
diff --git a/src/invert.ts b/src/invert.ts
index f1944c9..7f8d415 100644
--- a/src/invert.ts
+++ b/src/invert.ts
@@ -19,7 +19,6 @@ type Inverted<T extends object> = Simplify<{
  * @example
  *    R.invert({ a: "d", b: "e", c: "f" }) // => { d: "a", e: "b", f: "c" }
  * @dataFirst
- * @pipeable
  * @category Object
  */
 export function invert<T extends object>(object: T): Inverted<T>;
@@ -33,7 +32,6 @@ export function invert<T extends object>(object: T): Inverted<T>;
  * @example
  *    R.pipe({ a: "d", b: "e", c: "f" }, R.invert()); // => { d: "a", e: "b", f: "c" }
  * @dataLast
- * @pipeable
  * @category Object
  */
 export function invert<T extends object>(): (object: T) => Inverted<T>;
diff --git a/src/join.ts b/src/join.ts
index 6128802..9cbecd9 100644
--- a/src/join.ts
+++ b/src/join.ts
@@ -1,33 +1,8 @@
-import type { IterableContainer } from "./internal/types";
+import type { Join } from "type-fest";
 import { purry } from "./purry";
 
-type Joinable = bigint | boolean | number | string | null | undefined;
-
-export type Joined<T extends IterableContainer, Glue extends string> =
-  // Empty tuple
-  T[number] extends never
-    ? ""
-    : // Single item tuple (could be optional too!)
-      T extends readonly [Joinable?]
-      ? `${NullishCoalesce<T[0], "">}`
-      : // Tuple with non-rest element (head)
-        T extends readonly [infer First, ...infer Tail]
-        ? `${NullishCoalesce<First, "">}${Glue}${Joined<Tail, Glue>}`
-        : // Tuple with non-rest element (tail)
-          T extends readonly [...infer Head, infer Last]
-          ? `${Joined<Head, Glue>}${Glue}${NullishCoalesce<Last, "">}`
-          : // Arrays and tuple rest-elements, we can't say anything about the output
-            string;
-
-// `undefined` and `null` are special-cased by join. In typescript
-// `${undefined}` === 'undefined' (and similarly for null), but specifically in
-// the builtin `join` method, they should result in an empty string!
-// @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/join#description
-type NullishCoalesce<T, Fallback> = T extends Joinable
-  ? T extends null | undefined
-    ? Fallback | NonNullable<T>
-    : T
-  : never;
+// Copied from type-fest, from the Join type.
+type JoinableItem = bigint | boolean | number | string | null | undefined;
 
 /**
  * Joins the elements of the array by: casting them to a string and
@@ -49,9 +24,9 @@ type NullishCoalesce<T, Fallback> = T extends Joinable
  * @category Array
  */
 export function join<
-  T extends ReadonlyArray<Joinable> | [],
+  T extends ReadonlyArray<JoinableItem> | [],
   Glue extends string,
->(data: T, glue: Glue): Joined<T, Glue>;
+>(data: T, glue: Glue): Join<T, Glue>;
 
 /**
  * Joins the elements of the array by: casting them to a string and
@@ -72,9 +47,9 @@ export function join<
  * @category Array
  */
 export function join<
-  T extends ReadonlyArray<Joinable> | [],
+  T extends ReadonlyArray<JoinableItem> | [],
   Glue extends string,
->(glue: Glue): (data: T) => Joined<T, Glue>;
+>(glue: Glue): (data: T) => Join<T, Glue>;
 
 export function join(...args: ReadonlyArray<unknown>): unknown {
   return purry(joinImplementation, args);
diff --git a/src/keys.ts b/src/keys.ts
index ff6024b..b2bc910 100644
--- a/src/keys.ts
+++ b/src/keys.ts
@@ -62,7 +62,6 @@ type ObjectKeys<T> =
  *    R.keys(['x', 'y', 'z']); // => ['0', '1', '2']
  *    R.keys({ a: 'x', b: 'y', 5: 'z' }); // => ['a', 'b', '5']
  * @dataFirst
- * @pipeable
  * @category Object
  */
 export function keys<T extends object>(data: T): Keys<T>;
@@ -76,7 +75,6 @@ export function keys<T extends object>(data: T): Keys<T>;
  *    R.Pipe(['x', 'y', 'z'], keys()); // => ['0', '1', '2']
  *    R.pipe({ a: 'x', b: 'y', 5: 'z' } as const, R.keys()) // => ['a', 'b', '5']
  * @dataLast
- * @pipeable
  * @category Object
  */
 export function keys(): <T extends object>(data: T) => Keys<T>;
diff --git a/src/last.ts b/src/last.ts
index a5f2132..f828842 100644
--- a/src/last.ts
+++ b/src/last.ts
@@ -21,7 +21,6 @@ type Last<T extends IterableContainer> = LastArrayElement<
  *    R.last([1, 2, 3]) // => 3
  *    R.last([]) // => undefined
  * @dataFirst
- * @pipeable
  * @category Array
  */
 export function last<T extends IterableContainer>(data: T): Last<T>;
@@ -39,7 +38,6 @@ export function last<T extends IterableContainer>(data: T): Last<T>;
  *      x => x + 1
  *    ); // => 17
  * @dataLast
- * @pipeable
  * @category Array
  */
 export function last(): <T extends IterableContainer>(data: T) => Last<T>;
diff --git a/src/map.ts b/src/map.ts
index c1302bc..b3a8024 100644
--- a/src/map.ts
+++ b/src/map.ts
@@ -18,7 +18,7 @@ import { purry } from "./purry";
  *    R.map([0, 0], R.add(1)); // => [1, 1]
  *    R.map([0, 0], (value, index) => value + index); // => [0, 1]
  * @dataFirst
- * @pipeable
+ * @lazy
  * @category Array
  */
 export function map<T extends IterableContainer, U>(
@@ -41,7 +41,7 @@ export function map<T extends IterableContainer, U>(
  *    R.pipe([0, 0], R.map(R.add(1))); // => [1, 1]
  *    R.pipe([0, 0], R.map((value, index) => value + index)); // => [0, 1]
  * @dataLast
- * @pipeable
+ * @lazy
  * @category Array
  */
 export function map<T extends IterableContainer, U>(
diff --git a/src/mapWithFeedback.ts b/src/mapWithFeedback.ts
index b58dba6..fc07d95 100644
--- a/src/mapWithFeedback.ts
+++ b/src/mapWithFeedback.ts
@@ -22,7 +22,7 @@ import type { LazyEvaluator } from "./pipe";
  *      100,
  *    ); // => [101, 103, 106, 110, 115]
  * @dataFirst
- * @pipeable
+ * @lazy
  * @category Array
  */
 export function mapWithFeedback<T extends IterableContainer, U>(
@@ -54,7 +54,7 @@ export function mapWithFeedback<T extends IterableContainer, U>(
  *      R.mapWithFeedback((prev, x) => prev + x, 100),
  *    ); // => [101, 103, 106, 110, 115]
  * @dataLast
- * @pipeable
+ * @lazy
  * @category Array
  */
 export function mapWithFeedback<T extends IterableContainer, U>(
diff --git a/src/nthBy.ts b/src/nthBy.ts
index 2839304..4d15017 100644
--- a/src/nthBy.ts
+++ b/src/nthBy.ts
@@ -21,7 +21,7 @@ import type {
  * @signature
  *   R.nthBy(data, index, ...rules);
  * @example
- *   R.nthBy([2,1,4,5,3,], 2, identity); // => 3
+ *   R.nthBy([2,1,4,5,3,], 2, identity()); // => 3
  * @dataFirst
  * @category Array
  */
@@ -42,7 +42,7 @@ export function nthBy<T extends IterableContainer>(
  * @signature
  *   R.nthBy(index, ...rules)(data);
  * @example
- *   R.pipe([2,1,4,5,3,], R.nthBy(2, identity)); // => 3
+ *   R.pipe([2,1,4,5,3,], R.nthBy(2, identity())); // => 3
  * @dataLast
  * @category Array
  */
diff --git a/src/omitBy.ts b/src/omitBy.ts
index 9ec1d46..b187553 100644
--- a/src/omitBy.ts
+++ b/src/omitBy.ts
@@ -2,6 +2,8 @@ import { type IfNever, type Simplify } from "type-fest";
 import {
   type EnumerableStringKeyOf,
   type EnumerableStringKeyedValueOf,
+  type IfSimpleRecord,
+  type ReconstructedRecord,
 } from "./internal/types";
 import { purry } from "./purry";
 
@@ -18,9 +20,13 @@ type PickSymbolKeys<T extends object> = {
 // filtered out. This means that we effectively make all (enumerable) keys
 // optional.
 type PartialEnumerableKeys<T extends object> = Simplify<
-  PickSymbolKeys<T> & {
-    -readonly [P in keyof T as P extends symbol ? never : P]?: Required<T>[P];
-  }
+  IfSimpleRecord<
+    T,
+    ReconstructedRecord<T>,
+    PickSymbolKeys<T> & {
+      -readonly [P in keyof T as P extends symbol ? never : P]?: Required<T>[P];
+    }
+  >
 >;
 
 // When the predicate is a type-guard we have more information to work with when
diff --git a/src/only.ts b/src/only.ts
index 78bee86..a29c879 100644
--- a/src/only.ts
+++ b/src/only.ts
@@ -22,7 +22,6 @@ type Only<T extends IterableContainer> = T extends
  *    R.only([1]) // => 1
  *    R.only([1, 2]) // => undefined
  * @dataFirst
- * @pipeable
  * @category Array
  */
 export function only<T extends IterableContainer>(array: Readonly<T>): Only<T>;
@@ -37,7 +36,6 @@ export function only<T extends IterableContainer>(array: Readonly<T>): Only<T>;
  *    R.pipe([1], R.only()); // => 1
  *    R.pipe([1, 2], R.only()); // => undefined
  * @dataLast
- * @pipeable
  * @category Array
  */
 export function only<T extends IterableContainer>(): (
diff --git a/src/pickBy.ts b/src/pickBy.ts
index 5ea1a87..57f9a67 100644
--- a/src/pickBy.ts
+++ b/src/pickBy.ts
@@ -2,6 +2,8 @@ import { type IfNever, type Simplify } from "type-fest";
 import {
   type EnumerableStringKeyOf,
   type EnumerableStringKeyedValueOf,
+  type IfSimpleRecord,
+  type ReconstructedRecord,
 } from "./internal/types";
 import { purry } from "./purry";
 
@@ -13,9 +15,15 @@ type EnumerableKey<T> = `${T extends number | string ? T : never}`;
 // When the predicate isn't a type-guard we don't know which properties would be
 // part of the output and which wouldn't so we can only safely downgrade the
 // whole object to a Partial of the input.
-type EnumeratedPartial<T> = Simplify<{
-  -readonly [P in keyof T as EnumerableKey<P>]?: Required<T>[P];
-}>;
+type EnumeratedPartial<T> = Simplify<
+  IfSimpleRecord<
+    T,
+    ReconstructedRecord<T>,
+    {
+      -readonly [P in keyof T as EnumerableKey<P>]?: Required<T>[P];
+    }
+  >
+>;
 
 // When the predicate is a type-guard we have more information to work with when
 // constructing the type of the output object. We can safely remove any property
diff --git a/src/pipe.ts b/src/pipe.ts
index feda29e..b0234ef 100644
--- a/src/pipe.ts
+++ b/src/pipe.ts
@@ -332,7 +332,7 @@ function processItem(
             return true;
           }
         }
-        return false;
+        return isDone;
       }
       currentItem = lazyResult.next;
     }
@@ -348,10 +348,7 @@ function processItem(
   if (lazyResult.hasNext) {
     accumulator.push(currentItem);
   }
-  if (isDone) {
-    return true;
-  }
-  return false;
+  return isDone;
 }
 
 function prepareLazyOperation(op: LazyOp): PreparedLazyOperation {
diff --git a/src/sample.ts b/src/sample.ts
index bb198f1..293e50f 100644
--- a/src/sample.ts
+++ b/src/sample.ts
@@ -65,7 +65,6 @@ type SampledLiteral<
  *    R.sample(["hello", "world"], 1); // => ["hello"] // typed string[]
  *    R.sample(["hello", "world"] as const, 1); // => ["world"] // typed ["hello" | "world"]
  * @dataFirst
- * @pipeable
  * @category Array
  */
 export function sample<T extends IterableContainer, N extends number = number>(
@@ -91,7 +90,6 @@ export function sample<T extends IterableContainer, N extends number = number>(
  *    R.sample(1)(["hello", "world"]); // => ["hello"] // typed string[]
  *    R.sample(1)(["hello", "world"] as const); // => ["world"] // typed ["hello" | "world"]
  * @dataLast
- * @pipeable
  * @category Array
  */
 export function sample<T extends IterableContainer, N extends number = number>(
diff --git a/src/set.ts b/src/set.ts
index 7d8f77e..d289357 100644
--- a/src/set.ts
+++ b/src/set.ts
@@ -1,8 +1,12 @@
+import type { UpsertProp } from "./internal/types";
 import { purry } from "./purry";
 
 /**
  * Sets the `value` at `prop` of `object`.
  *
+ * To add a new property to an object, or to override its type, use `addProp`
+ * instead, and to set a property within a nested object use `setPath`.
+ *
  * @param obj - The target method.
  * @param prop - The property name.
  * @param value - The value to set.
@@ -13,11 +17,18 @@ import { purry } from "./purry";
  * @dataFirst
  * @category Object
  */
-export function set<T, K extends keyof T>(obj: T, prop: K, value: T[K]): T;
+export function set<T, K extends keyof T, V extends Required<T>[K]>(
+  obj: T,
+  prop: K,
+  value: V,
+): UpsertProp<T, K, V>;
 
 /**
  * Sets the `value` at `prop` of `object`.
  *
+ * To add a new property to an object, or to override it's type use `addProp`
+ * instead.
+ *
  * @param prop - The property name.
  * @param value - The value to set.
  * @signature
@@ -27,14 +38,19 @@ export function set<T, K extends keyof T>(obj: T, prop: K, value: T[K]): T;
  * @dataLast
  * @category Object
  */
-export function set<T, K extends keyof T>(prop: K, value: T[K]): (obj: T) => T;
+export function set<T, K extends keyof T, V extends Required<T>[K]>(
+  prop: K,
+  value: V,
+): (obj: T) => UpsertProp<T, K, V>;
 
 export function set(...args: ReadonlyArray<unknown>): unknown {
   return purry(setImplementation, args);
 }
 
-const setImplementation = <T, K extends keyof T>(
+const setImplementation = <T, K extends keyof T, V extends Required<T>[K]>(
   obj: T,
   prop: K,
-  value: T[K],
-): T => ({ ...obj, [prop]: value });
+  value: V,
+): UpsertProp<T, K, V> =>
+  // @ts-expect-error [ts2322] - Hard to type
+  ({ ...obj, [prop]: value });
diff --git a/src/setPath.ts b/src/setPath.ts
index ed97122..a7096f1 100644
--- a/src/setPath.ts
+++ b/src/setPath.ts
@@ -25,6 +25,8 @@ type ValueAtPath<T, TPath extends Path<T>> = TPath extends readonly [
 /**
  * Sets the value at `path` of `object`.
  *
+ * For simple cases where the path is only one level deep, prefer `set` instead.
+ *
  * @param data - The target method.
  * @param path - The array of properties.
  * @param value - The value to set.
diff --git a/src/swapIndices.ts b/src/swapIndices.ts
index d7a42dc..10c6bc8 100644
--- a/src/swapIndices.ts
+++ b/src/swapIndices.ts
@@ -1,5 +1,5 @@
+import { type Join } from "type-fest";
 import type { IterableContainer } from "./internal/types";
-import type { Joined } from "./join";
 import { purry } from "./purry";
 
 /**
@@ -64,11 +64,10 @@ type SwapArrayInternal<
     ]
   : T;
 
-type SwapString<
-  T extends string,
-  K1 extends number,
-  K2 extends number,
-> = Joined<SwapArray<CharactersTuple<T>, K1, K2>, "">;
+type SwapString<T extends string, K1 extends number, K2 extends number> = Join<
+  SwapArray<CharactersTuple<T>, K1, K2>,
+  ""
+>;
 
 type SwapArray<
   T extends IterableContainer,
diff --git a/src/take.ts b/src/take.ts
index 6d6530c..16f9ea8 100644
--- a/src/take.ts
+++ b/src/take.ts
@@ -12,7 +12,7 @@ import { purry } from "./purry";
  * @example
  *    R.take([1, 2, 3, 4, 3, 2, 1], 3) // => [1, 2, 3]
  * @dataFirst
- * @pipeable
+ * @lazy
  * @category Array
  */
 export function take<T>(array: ReadonlyArray<T>, n: number): Array<T>;
@@ -26,7 +26,7 @@ export function take<T>(array: ReadonlyArray<T>, n: number): Array<T>;
  * @example
  *    R.pipe([1, 2, 3, 4, 3, 2, 1], R.take(n)) // => [1, 2, 3]
  * @dataLast
- * @pipeable
+ * @lazy
  * @category Array
  */
 export function take<T>(n: number): (array: ReadonlyArray<T>) => Array<T>;
diff --git a/src/times.ts b/src/times.ts
index 194ab91..fdb156f 100644
--- a/src/times.ts
+++ b/src/times.ts
@@ -10,7 +10,7 @@ import { purry } from "./purry";
  * @param count - A value between `0` and `n - 1`. Increments after each function call.
  * @param fn - The function to invoke. Passed one argument, the current value of `n`.
  * @returns An array containing the return values of all calls to `fn`.
- * @example times(5, identity); //=> [0, 1, 2, 3, 4]
+ * @example times(5, identity()); //=> [0, 1, 2, 3, 4]
  * @dataFirst
  */
 export function times<T>(count: number, fn: (n: number) => T): Array<T>;
@@ -24,7 +24,7 @@ export function times<T>(count: number, fn: (n: number) => T): Array<T>;
  *
  * @param fn - The function to invoke. Passed one argument, the current value of `n`.
  * @returns An array containing the return values of all calls to `fn`.
- * @example times(identity)(5); //=> [0, 1, 2, 3, 4]
+ * @example times(identity())(5); //=> [0, 1, 2, 3, 4]
  * @dataLast
  */
 export function times<T>(fn: (n: number) => T): (count: number) => Array<T>;
diff --git a/src/unique.ts b/src/unique.ts
index f0b272e..0680f72 100644
--- a/src/unique.ts
+++ b/src/unique.ts
@@ -12,7 +12,7 @@ import type { LazyEvaluator } from "./pipe";
  * @example
  *    R.unique([1, 2, 2, 5, 1, 6, 7]) // => [1, 2, 5, 6, 7]
  * @dataFirst
- * @pipeable
+ * @lazy
  * @category Array
  */
 export function unique<T>(array: ReadonlyArray<T>): Array<T>;
@@ -30,7 +30,7 @@ export function unique<T>(array: ReadonlyArray<T>): Array<T>;
  *      R.take(3)
  *    ) // => [1, 2, 5]
  * @dataLast
- * @pipeable
+ * @lazy
  * @category Array
  */
 export function unique<T>(): (array: ReadonlyArray<T>) => Array<T>;
diff --git a/src/uniqueBy.ts b/src/uniqueBy.ts
index f7e77f5..633d810 100644
--- a/src/uniqueBy.ts
+++ b/src/uniqueBy.ts
@@ -16,7 +16,7 @@ import type { LazyEvaluator } from "./pipe";
  *     (obj) => obj.n,
  *    ) // => [{n: 1}, {n: 2}, {n: 5}, {n: 6}, {n: 7}]
  * @dataFirst
- * @pipeable
+ * @lazy
  * @category Array
  */
 export function uniqueBy<T, K>(
@@ -38,7 +38,7 @@ export function uniqueBy<T, K>(
  *      R.take(3)
  *    ) // => [{n: 1}, {n: 2}, {n: 5}]
  * @dataLast
- * @pipeable
+ * @lazy
  * @category Array
  */
 export function uniqueBy<T, K>(
diff --git a/src/uniqueWith.ts b/src/uniqueWith.ts
index 8899bc1..6969504 100644
--- a/src/uniqueWith.ts
+++ b/src/uniqueWith.ts
@@ -18,6 +18,7 @@ type IsEquals<T> = (a: T, b: T) => boolean;
  *      R.equals,
  *    ) // => [{a: 1}, {a: 2}, {a: 5}, {a: 6}, {a: 7}]
  * @dataFirst
+ * @lazy
  * @category Array
  */
 export function uniqueWith<T>(
@@ -41,7 +42,8 @@ export function uniqueWith<T>(
  *      R.take(3)
  *    ) // => [{a: 1}, {a: 2}, {a: 5}]
  * @dataLast
- * @category Object
+ * @lazy
+ * @category Array
  */
 export function uniqueWith<T>(
   isEquals: IsEquals<T>,
diff --git a/src/values.ts b/src/values.ts
index d687f26..3ca26a5 100644
--- a/src/values.ts
+++ b/src/values.ts
@@ -18,7 +18,6 @@ type Values<T extends object> = T extends IterableContainer
  *    R.values(['x', 'y', 'z']) // => ['x', 'y', 'z']
  *    R.values({ a: 'x', b: 'y', c: 'z' }) // => ['x', 'y', 'z']
  * @dataFirst
- * @pipeable
  * @category Object
  */
 export function values<T extends object>(data: T): Values<T>;
@@ -37,7 +36,6 @@ export function values<T extends object>(data: T): Values<T>;
  *      R.first(),
  *    ) // => 'x'
  * @dataLast
- * @pipeable
  * @category Object
  */
 export function values(): <T extends object>(data: T) => Values<T>;
diff --git a/src/zip.ts b/src/zip.ts
index 33559f2..95a86ce 100644
--- a/src/zip.ts
+++ b/src/zip.ts
@@ -34,7 +34,7 @@ type Zipped<Left extends IterableContainer, Right extends IterableContainer> =
  * @example
  *   R.zip([1, 2], ['a', 'b']) // => [[1, 'a'], [2, 'b']]
  * @dataFirst
- * @pipeable
+ * @lazy
  * @category Array
  */
 export function zip<F extends IterableContainer, S extends IterableContainer>(
@@ -53,7 +53,7 @@ export function zip<F extends IterableContainer, S extends IterableContainer>(
  * @example
  *   R.zip(['a', 'b'])([1, 2]) // => [[1, 'a'], [2, 'b']]
  * @dataLast
- * @pipeable
+ * @lazy
  * @category Array
  */
 export function zip<S extends IterableContainer>(
diff --git a/src/zipWith.ts b/src/zipWith.ts
index 7d6eede..130dc3b 100644
--- a/src/zipWith.ts
+++ b/src/zipWith.ts
@@ -42,7 +42,7 @@ export function zipWith<TItem1, TItem2, Value>(
  * @example
  *   R.pipe(['1', '2', '3'], R.zipWith(['a', 'b', 'c'], (a, b) => a + b)) // => ['1a', '2b', '3c']
  * @dataLast
- * @pipeable
+ * @lazy
  * @category Array
  */
 export function zipWith<
@@ -63,7 +63,7 @@ export function zipWith<
  * @example
  *   R.zipWith(['1', '2', '3'], ['a', 'b', 'c'], (a, b) => a + b) // => ['1a', '2b', '3c']
  * @dataFirst
- * @pipeable
+ * @lazy
  * @category Array
  */
 export function zipWith<


Test patch:
diff --git a/src/addProp.test.ts b/src/addProp.test.ts
index 9cef5ac..d608422 100644
--- a/src/addProp.test.ts
+++ b/src/addProp.test.ts
@@ -1,16 +1,54 @@
 import { addProp } from "./addProp";
 import { pipe } from "./pipe";
 
-describe("data first", () => {
-  test("addProp", () => {
-    const actual = addProp({ a: 1 }, "b", 2);
-    expect(actual).toEqual({ a: 1, b: 2 });
+describe("runtime", () => {
+  describe("data first", () => {
+    test("simple", () => {
+      const actual = addProp({ a: 1 }, "b", 2);
+      expect(actual).toEqual({ a: 1, b: 2 });
+    });
+  });
+
+  describe("data last", () => {
+    test("simple", () => {
+      const actual = pipe({ a: 1 }, addProp("b", 2));
+      expect(actual).toEqual({ a: 1, b: 2 });
+    });
   });
 });
 
-describe("data last", () => {
-  test("addProp", () => {
-    const actual = pipe({ a: 1 }, addProp("b", 2));
-    expect(actual).toEqual({ a: 1, b: 2 });
+describe("typing", () => {
+  it("allows redefining prop types", () => {
+    const result = addProp({} as { a: string }, "a", 1);
+    expectTypeOf(result).toEqualTypeOf<{ a: number }>();
+  });
+
+  it("makes optional fields required", () => {
+    const result = addProp({} as { a?: string }, "a", "hello");
+    expectTypeOf(result).toEqualTypeOf<{ a: string }>();
+  });
+
+  it("allows setting an unknown prop", () => {
+    const result = addProp({ a: "foo" }, "b", "bar");
+    expectTypeOf(result).toEqualTypeOf<{ a: string; b: string }>();
+  });
+
+  it("sets literal unions as optional", () => {
+    const result = addProp({} as { a: string }, "b" as "b" | "c", 123);
+    expectTypeOf(result).toEqualTypeOf<{ a: string; b?: number; c?: number }>();
+  });
+
+  it("keeps the prop optional when the key isn't literal", () => {
+    const result = addProp(
+      {} as { a?: string; b?: number },
+      "a" as "a" | "b",
+      "foo" as const,
+    );
+    expectTypeOf(result).toEqualTypeOf<{ a?: string; b?: number | "foo" }>();
+  });
+
+  it("works on simple objects", () => {
+    const result = addProp({} as Record<string, string>, "a", "foo" as const);
+    expectTypeOf(result).toEqualTypeOf<{ [x: string]: string; a: "foo" }>();
   });
 });
diff --git a/src/hasSubObject.test.ts b/src/hasSubObject.test.ts
index 2cecea9..764da81 100644
--- a/src/hasSubObject.test.ts
+++ b/src/hasSubObject.test.ts
@@ -2,18 +2,18 @@ import { hasSubObject } from "./hasSubObject";
 import { pipe } from "./pipe";
 
 describe("data first", () => {
-  test("works with empty sub-object", () => {
+  it("works with empty sub-object", () => {
     expect(hasSubObject({ a: 1, b: "b", c: 3 }, {})).toBe(true);
     expect(hasSubObject({}, {})).toBe(true);
   });
 
-  test("works with primitives", () => {
+  it("works with primitives", () => {
     expect(hasSubObject({ a: 1, b: "b", c: 3 }, { a: 1, b: "b" })).toBe(true);
     expect(hasSubObject({ a: 1, b: "c", c: 3 }, { a: 1, b: "b" })).toBe(false);
     expect(hasSubObject({ a: 2, b: "b", c: 3 }, { a: 1, b: "b" })).toBe(false);
   });
 
-  test("works with deep objects", () => {
+  it("works with deep objects", () => {
     expect(hasSubObject({ a: { b: 1, c: 2 } }, { a: { b: 1, c: 2 } })).toBe(
       true,
     );
@@ -22,19 +22,19 @@ describe("data first", () => {
     );
   });
 
-  test("checks for matching key", () => {
+  it("checks for matching key", () => {
     const data = {} as { a?: undefined };
     expect(hasSubObject(data, { a: undefined })).toBe(false);
   });
 });
 
 describe("data last", () => {
-  test("empty sub-object", () => {
+  it("works with empty sub-object", () => {
     expect(pipe({ a: 1, b: 2, c: 3 }, hasSubObject({}))).toBe(true);
     expect(pipe({}, hasSubObject({}))).toBe(true);
   });
 
-  test("works with primitives", () => {
+  it("works with primitives", () => {
     expect(pipe({ a: 1, b: "b", c: 3 }, hasSubObject({ a: 1, b: "b" }))).toBe(
       true,
     );
@@ -46,7 +46,7 @@ describe("data last", () => {
     );
   });
 
-  test("works with deep objects", () => {
+  it("works with deep objects", () => {
     expect(
       pipe({ a: { b: 1, c: 2 } }, hasSubObject({ a: { b: 1, c: 2 } })),
     ).toBe(true);
@@ -59,7 +59,7 @@ describe("data last", () => {
 
 describe("typing", () => {
   describe("data-first", () => {
-    test("must have matching keys and values", () => {
+    it("must have matching keys and values", () => {
       expectTypeOf(hasSubObject({ a: 2 }, { a: 1 })).toEqualTypeOf<boolean>();
 
       // @ts-expect-error [ts2353] - missing a key
@@ -81,7 +81,7 @@ describe("typing", () => {
       hasSubObject({ a: 2 }, { a: 1 } as const);
     });
 
-    test("allows nested objects", () => {
+    it("allows nested objects", () => {
       // ok - nested object has extra keys
       hasSubObject({ a: { b: 4 } }, { a: { b: 1, c: 2 } });
 
@@ -92,25 +92,49 @@ describe("typing", () => {
       hasSubObject({ a: { b: 4, c: "c" } }, { a: { b: 1, c: 2 } });
     });
 
-    test("narrows type", () => {
+    it("narrows with empty object", () => {
       const obj = {} as { a?: string; b?: number };
 
       if (hasSubObject(obj, {})) {
-        expectTypeOf(obj).toEqualTypeOf<{ a?: string; b?: number }>();
+        expectTypeOf(obj).toMatchTypeOf<{ a?: string; b?: number }>();
+      } else {
+        expectTypeOf(obj).toMatchTypeOf<{ a?: string; b?: number }>();
       }
+    });
+
+    it("narrows with same object", () => {
+      const obj = {} as { a?: string; b?: number };
+
+      if (hasSubObject(obj, obj)) {
+        expectTypeOf(obj).toMatchTypeOf<{ a?: string; b?: number }>();
+      } else {
+        expectTypeOf(obj).toMatchTypeOf<{ a?: string; b?: number }>();
+      }
+    });
+
+    it("narrows optional field to required", () => {
+      const obj = {} as { a?: string; b?: number };
 
       if (hasSubObject(obj, { a: "a" })) {
-        expectTypeOf(obj).toEqualTypeOf<{ a: string; b?: number }>();
+        expectTypeOf(obj).toMatchTypeOf<{ a: string; b?: number }>();
+      } else {
+        expectTypeOf(obj).toMatchTypeOf<{ a?: string; b?: number }>();
       }
+    });
+
+    it("narrows field to constant type", () => {
+      const obj = {} as { a?: string; b?: number };
 
       if (hasSubObject(obj, { a: "a" } as const)) {
-        expectTypeOf(obj).toEqualTypeOf<{ a: "a"; b?: number }>();
+        expectTypeOf(obj).toMatchTypeOf<{ readonly a: "a"; b?: number }>();
+      } else {
+        expectTypeOf(obj).toMatchTypeOf<{ a?: string; b?: number }>();
       }
     });
   });
 
   describe("data-last", () => {
-    test("must have matching keys and values", () => {
+    it("must have matching keys and values", () => {
       expectTypeOf(
         pipe({ a: 2 }, hasSubObject({ a: 1 })),
       ).toEqualTypeOf<boolean>();
@@ -134,7 +158,7 @@ describe("typing", () => {
       pipe({ a: 2 }, hasSubObject({ a: 1 } as const));
     });
 
-    test("allows nested objects", () => {
+    it("allows nested objects", () => {
       // ok - nested object has extra keys
       pipe({ a: { b: 4 } }, hasSubObject({ a: { b: 1, c: 2 } }));
 
diff --git a/src/mapValues.test.ts b/src/mapValues.test.ts
index 1b045bf..478d415 100644
--- a/src/mapValues.test.ts
+++ b/src/mapValues.test.ts
@@ -104,6 +104,23 @@ describe("typing", () => {
     });
   });
 
+  describe("branded types", () => {
+    test("should infer types correctly in the mapper", () => {
+      type Branded<K, T> = K & { _type: T };
+      type UserID = Branded<string, "UserId">;
+
+      const userValues: Record<UserID, number> = {
+        ["U1" as UserID]: 1,
+        ["U2" as UserID]: 2,
+      };
+
+      mapValues(userValues, (value, key) => {
+        expectTypeOf(value).toEqualTypeOf<number>();
+        expectTypeOf(key).toEqualTypeOf<`${UserID}`>();
+      });
+    });
+  });
+
   test("symbols are filtered out", () => {
     const mySymbol = Symbol("mySymbol");
     const result = mapValues({ [mySymbol]: 1, a: "hello" }, constant(true));
diff --git a/src/omitBy.test.ts b/src/omitBy.test.ts
index 9f5c1c9..246bfae 100644
--- a/src/omitBy.test.ts
+++ b/src/omitBy.test.ts
@@ -187,6 +187,27 @@ describe("typing", () => {
       optionalNullish?: string;
     }>();
   });
+
+  // @see https://github.com/remeda/remeda/issues/696
+  describe("Records with non-narrowing predicates (Issue #696)", () => {
+    test("string keys", () => {
+      const data = {} as Record<string, string>;
+      const result = omitBy(data, constant(true));
+      expectTypeOf(result).toEqualTypeOf(data);
+    });
+
+    test("number keys", () => {
+      const data = {} as Record<number, string>;
+      const result = omitBy(data, constant(true));
+      expectTypeOf(result).toEqualTypeOf<Record<`${number}`, string>>();
+    });
+
+    test("combined numbers and strings", () => {
+      const data = {} as Record<number | string, string>;
+      const result = omitBy(data, constant(true));
+      expectTypeOf(result).toEqualTypeOf<Record<string, string>>();
+    });
+  });
 });
 
 const isUndefined = <T>(value: T | undefined): value is undefined =>
diff --git a/src/pickBy.test.ts b/src/pickBy.test.ts
index 63f2c9c..acde289 100644
--- a/src/pickBy.test.ts
+++ b/src/pickBy.test.ts
@@ -162,4 +162,25 @@ describe("typing", () => {
       optionalNullish?: string;
     }>();
   });
+
+  // @see https://github.com/remeda/remeda/issues/696
+  describe("Records with non-narrowing predicates (Issue #696)", () => {
+    test("string keys", () => {
+      const data = {} as Record<string, string>;
+      const result = pickBy(data, constant(true));
+      expectTypeOf(result).toEqualTypeOf(data);
+    });
+
+    test("number keys", () => {
+      const data = {} as Record<number, string>;
+      const result = pickBy(data, constant(true));
+      expectTypeOf(result).toEqualTypeOf<Record<`${number}`, string>>();
+    });
+
+    test("combined numbers and strings", () => {
+      const data = {} as Record<number | string, string>;
+      const result = pickBy(data, constant(true));
+      expectTypeOf(result).toEqualTypeOf<Record<string, string>>();
+    });
+  });
 });
diff --git a/src/pipe.test.ts b/src/pipe.test.ts
index 97387b9..f4aacf8 100644
--- a/src/pipe.test.ts
+++ b/src/pipe.test.ts
@@ -1,4 +1,5 @@
 import { filter } from "./filter";
+import { flat } from "./flat";
 import { identity } from "./identity";
 import { map } from "./map";
 import { pipe } from "./pipe";
@@ -115,4 +116,17 @@ describe("lazy", () => {
     expect(count2).toHaveBeenCalledTimes(2);
     expect(result).toEqual([100, 200]);
   });
+
+  it("lazy early exit with hasMany", () => {
+    const result = pipe(
+      [
+        [1, 2],
+        [3, 4],
+        [5, 6],
+      ],
+      take(1),
+      flat(),
+    );
+    expect(result).toEqual([1, 2]);
+  });
 });
diff --git a/src/set.test.ts b/src/set.test.ts
index 9c6686a..df72f36 100644
--- a/src/set.test.ts
+++ b/src/set.test.ts
@@ -1,14 +1,59 @@
 import { pipe } from "./pipe";
 import { set } from "./set";
 
-describe("data first", () => {
-  test("set", () => {
-    expect(set({ a: 1 }, "a", 2)).toEqual({ a: 2 });
+describe("runtime", () => {
+  describe("data first", () => {
+    test("simple", () => {
+      expect(set({ a: 1 }, "a", 2)).toEqual({ a: 2 });
+    });
+  });
+
+  describe("data last", () => {
+    test("simple", () => {
+      expect(pipe({ a: 1 }, set("a", 2))).toEqual({ a: 2 });
+    });
   });
 });
 
-describe("data last", () => {
-  test("set", () => {
-    expect(pipe({ a: 1 }, set("a", 2))).toEqual({ a: 2 });
+describe("typing", () => {
+  describe("parameter enforcing", () => {
+    it("prevents setting the wrong type", () => {
+      // @ts-expect-error [ts2345] - 'number' is not assignable to 'string'.
+      set({} as { a: string }, "a", 1);
+    });
+
+    it("doesn't expand narrow types", () => {
+      // @ts-expect-error [ts2345] - 'mouse' is not assignable to 'cat' | 'dog'.
+      set({} as { a: "cat" | "dog" }, "a", "mouse");
+    });
+
+    it("prevents setting `undefined` on optional fields", () => {
+      // @ts-expect-error [ts2345] - 'a' cannot be `undefined`.
+      set({} as { a?: string }, "a", undefined);
+    });
+
+    it("prevents setting an unknown prop", () => {
+      // @ts-expect-error [ts2345] - 'b' does not exist on type '{ a: string; }'.
+      set({ a: "foo" }, "b", "bar");
+    });
+  });
+
+  it("narrows the prop type", () => {
+    const result = set({} as { a?: string }, "a", "hello" as const);
+    expectTypeOf(result).toEqualTypeOf<{ a: "hello" }>();
+  });
+
+  it("keeps the prop optional when the key isn't literal", () => {
+    const result = set(
+      {} as { a?: string; b?: number },
+      "a" as "a" | "b",
+      "foo" as const,
+    );
+    expectTypeOf(result).toEqualTypeOf<{ a?: string; b?: number | "foo" }>();
+  });
+
+  it("works on simple objects", () => {
+    const result = set({} as Record<string, string>, "a", "foo" as const);
+    expectTypeOf(result).toEqualTypeOf<{ [x: string]: string; a: "foo" }>();
   });
 });