The response has been limited to 50k tokens of the smallest files in the repo. You can remove this limitation by removing the max tokens filter.
├── .codesandbox
    └── ci.json
├── .eslintignore
├── .eslintrc
├── .gitattributes
├── .github
    └── workflows
    │   ├── build-and-test-types.yml
    │   └── publish.yml
├── .gitignore
├── .prettierrc.json
├── .release-it.json
├── .yarn
    └── releases
    │   └── yarn-4.4.1.cjs
├── .yarnrc.yml
├── AUTHORS
├── CHANGELOG.md
├── CNAME
├── CREDITS.md
├── LICENSE
├── README.md
├── codecov.yml
├── docs
    └── examples
    │   ├── FAQ
    │       ├── MyComponent.tsx
    │       ├── createCurriedSelector.ts
    │       ├── createParametricSelectorHook.ts
    │       ├── currySelector.ts
    │       ├── howToTest.test.ts
    │       ├── identity.ts
    │       └── selectorRecomputing.ts
    │   ├── basicUsage.ts
    │   ├── createSelector
    │       ├── annotateResultFunction.ts
    │       ├── createAppSelector.ts
    │       └── withTypes.ts
    │   ├── createStructuredSelector
    │       ├── MyComponent.tsx
    │       ├── modernUseCase.ts
    │       └── withTypes.ts
    │   ├── development-only-stability-checks
    │       ├── identityFunctionCheck.ts
    │       └── inputStabilityCheck.ts
    │   ├── handling-empty-array-results
    │       ├── fallbackToEmptyArray.ts
    │       └── firstPattern.ts
    │   ├── lruMemoize
    │       ├── referenceEqualityCheck.ts
    │       ├── usingWithCreateSelector.ts
    │       └── usingWithCreateSelectorCreator.ts
    │   ├── tsconfig.json
    │   ├── unstable_autotrackMemoize
    │       ├── usingWithCreateSelector.ts
    │       └── usingWithCreateSelectorCreator.ts
    │   └── weakMapMemoize
    │       ├── cacheSizeProblem.ts
    │       ├── cacheSizeSolution.ts
    │       ├── setMaxSize.ts
    │       ├── usingWithCreateSelector.ts
    │       ├── usingWithCreateSelectorCreator.ts
    │       └── withUseMemo.tsx
├── netlify.toml
├── package.json
├── scripts
    └── writeGitVersion.mjs
├── src
    ├── autotrackMemoize
    │   ├── autotrackMemoize.ts
    │   ├── autotracking.ts
    │   ├── proxy.ts
    │   ├── tracking.ts
    │   └── utils.ts
    ├── createSelectorCreator.ts
    ├── createStructuredSelector.ts
    ├── devModeChecks
    │   ├── identityFunctionCheck.ts
    │   ├── inputStabilityCheck.ts
    │   └── setGlobalDevModeChecks.ts
    ├── index.ts
    ├── lruMemoize.ts
    ├── types.ts
    ├── utils.ts
    ├── versionedTypes
    │   ├── index.ts
    │   └── ts47-mergeParameters.ts
    └── weakMapMemoize.ts
├── test
    ├── autotrackMemoize.spec.ts
    ├── benchmarks
    │   ├── orderOfExecution.bench.ts
    │   ├── reselect.bench.ts
    │   ├── resultEqualityCheck.bench.ts
    │   └── weakMapMemoize.bench.ts
    ├── computationComparisons.spec.tsx
    ├── createSelector.withTypes.test.ts
    ├── createStructuredSelector.spec.ts
    ├── createStructuredSelector.withTypes.test.ts
    ├── customMatchers.d.ts
    ├── examples.test.ts
    ├── identityFunctionCheck.test.ts
    ├── inputStabilityCheck.spec.ts
    ├── lruMemoize.test.ts
    ├── perfComparisons.spec.ts
    ├── reselect.spec.ts
    ├── selectorUtils.spec.ts
    ├── setup.vitest.ts
    ├── testTypes.ts
    ├── testUtils.ts
    ├── tsconfig.json
    └── weakmapMemoize.spec.ts
├── tsconfig.json
├── tsup.config.ts
├── type-tests
    ├── argsMemoize.test-d.ts
    ├── createSelector.withTypes.test-d.ts
    ├── createSelectorCreator.test-d.ts
    ├── createStructuredSelector.test-d.ts
    ├── createStructuredSelector.withTypes.test-d.ts
    ├── deepNesting.test-d.ts
    └── tsconfig.json
├── typescript_test
    ├── argsMemoize.typetest.ts
    ├── test.ts
    ├── tsconfig.json
    └── typesTestUtils.ts
├── vitest.config.mts
├── website
    ├── .gitignore
    ├── README.md
    ├── babel.config.js
    ├── compileExamples.ts
    ├── docs
    │   ├── FAQ.mdx
    │   ├── api
    │   │   ├── createSelector.mdx
    │   │   ├── createSelectorCreator.mdx
    │   │   ├── createStructuredSelector.mdx
    │   │   ├── development-only-stability-checks.mdx
    │   │   ├── lruMemoize.mdx
    │   │   ├── unstable_autotrackMemoize.mdx
    │   │   └── weakMapMemoize.mdx
    │   ├── external-references.mdx
    │   ├── introduction
    │   │   ├── getting-started.mdx
    │   │   ├── how-does-reselect-work.mdx
    │   │   └── v5-summary.mdx
    │   ├── related-projects.mdx
    │   └── usage
    │   │   ├── best-practices.mdx
    │   │   ├── common-mistakes.mdx
    │   │   └── handling-empty-array-results.mdx
    ├── docusaurus.config.ts
    ├── insertCodeExamples.ts
    ├── monokaiTheme.js
    ├── package.json
    ├── sidebars.ts
    ├── src
    │   ├── components
    │   │   ├── ExternalLinks.tsx
    │   │   ├── HomepageFeatures
    │   │   │   ├── index.tsx
    │   │   │   └── styles.module.css
    │   │   ├── InternalLinks.tsx
    │   │   └── PackageManagerTabs.tsx
    │   ├── css
    │   │   └── custom.css
    │   └── pages
    │   │   ├── index.module.css
    │   │   └── index.tsx
    ├── static
    │   ├── .nojekyll
    │   └── img
    │   │   ├── diagrams
    │   │       ├── normal-memoization-function.drawio
    │   │       └── reselect-memoization.drawio
    │   │   ├── docusaurus-social-card.jpg
    │   │   ├── docusaurus.png
    │   │   ├── favicon.ico
    │   │   ├── logo.svg
    │   │   ├── normal-memoization-function.png
    │   │   ├── reselect-memoization.png
    │   │   ├── undraw_docusaurus_mountain.svg
    │   │   ├── undraw_docusaurus_react.svg
    │   │   └── undraw_docusaurus_tree.svg
    ├── tsconfig.json
    └── yarn.lock
└── yarn.lock


/.codesandbox/ci.json:
--------------------------------------------------------------------------------
1 | {
2 |   "sandboxes": ["vanilla", "vanilla-ts"],
3 |   "node": "20"
4 | }
5 | 


--------------------------------------------------------------------------------
/.eslintignore:
--------------------------------------------------------------------------------
1 | lib
2 | es
3 | dist
4 | node_modules
5 | vitest.config.ts
6 | tsup.config.ts


--------------------------------------------------------------------------------
/.eslintrc:
--------------------------------------------------------------------------------
 1 | {
 2 |   "env": {
 3 |     "browser": true,
 4 |     "node": true
 5 |   },
 6 |   "extends": "eslint:recommended",
 7 |   "parserOptions": {
 8 |     "sourceType": "module",
 9 |     "ecmaVersion": 2015
10 |   },
11 |   "rules": {
12 |     "array-bracket-spacing": [0],
13 |     "comma-dangle": [2, "never"],
14 |     "eol-last": 2,
15 |     "no-multiple-empty-lines": 2,
16 |     "object-curly-spacing": [2, "always"],
17 |     "quotes": [
18 |       2,
19 |       "single",
20 |       { "avoidEscape": true, "allowTemplateLiterals": true }
21 |     ],
22 |     "semi": [2, "never"],
23 |     "strict": 0,
24 |     "space-before-blocks": [2, "always"],
25 |     "space-before-function-paren": [0]
26 |   },
27 |   "overrides": [
28 |     {
29 |       "parser": "@typescript-eslint/parser",
30 |       "files": ["*.{c,m,}{t,j}s", "*.{t,j}sx"],
31 |       "parserOptions": {
32 |         "ecmaVersion": 2020,
33 |         "sourceType": "module",
34 |         "project": true
35 |       },
36 |       "env": { "jest": true },
37 |       "extends": [
38 |         "eslint:recommended",
39 |         "plugin:@typescript-eslint/eslint-recommended",
40 |         "plugin:@typescript-eslint/recommended"
41 |       ],
42 |       "rules": {
43 |         "@typescript-eslint/ban-ts-comment": "off",
44 |         "@typescript-eslint/explicit-function-return-type": "off",
45 |         "@typescript-eslint/explicit-module-boundary-types": "off",
46 |         "@typescript-eslint/no-explicit-any": "off",
47 |         "@typescript-eslint/no-unused-vars": "off",
48 |         "@typescript-eslint/no-non-null-assertion": "off",
49 |         "@typescript-eslint/no-shadow": ["off"],
50 |         "@typescript-eslint/no-use-before-define": ["off"],
51 |         "@typescript-eslint/ban-types": "off",
52 |         "prefer-rest-params": "off",
53 |         "prefer-spread": "off",
54 |         "@typescript-eslint/consistent-type-imports": [
55 |           2,
56 |           { "fixStyle": "separate-type-imports" }
57 |         ],
58 |         "@typescript-eslint/consistent-type-exports": [2]
59 |       }
60 |     },
61 |     {
62 |       "files": ["**/test/**/*.ts", "**/typescript_test/**/*.ts"],
63 |       "rules": {
64 |         "consistent-return": "off",
65 |         "max-lines": "off",
66 |         "@typescript-eslint/ban-ts-comment": "off",
67 |         "@typescript-eslint/explicit-function-return-type": "off",
68 |         "@typescript-eslint/no-empty-function": "off",
69 |         "@typescript-eslint/no-explicit-any": "off",
70 |         "@typescript-eslint/no-floating-promises": "off",
71 |         "@typescript-eslint/no-non-null-assertion": "off",
72 |         "@typescript-eslint/no-unused-vars": "off",
73 |         "@typescript-eslint/no-shadow": "off"
74 |       }
75 |     },
76 |     {
77 |       "parser": "@typescript-eslint/parser",
78 |       "files": ["./docs/examples/**/*.{js,ts,jsx,tsx}"],
79 |       "parserOptions": {
80 |         "ecmaVersion": 2023,
81 |         "sourceType": "module",
82 |         "ecmaFeatures": { "jsx": true }
83 |       },
84 |       "rules": {
85 |         "no-unused-vars": [0]
86 |       }
87 |     }
88 |   ]
89 | }
90 | 


--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | * text=auto eol=lf
2 | 


--------------------------------------------------------------------------------
/.github/workflows/build-and-test-types.yml:
--------------------------------------------------------------------------------
  1 | name: CI
  2 | 
  3 | on: [push, pull_request]
  4 | 
  5 | jobs:
  6 |   build:
  7 |     name: Build and Test on Node ${{ matrix.node }}
  8 |     runs-on: ubuntu-latest
  9 |     strategy:
 10 |       matrix:
 11 |         node: ['22.x']
 12 | 
 13 |     steps:
 14 |       - name: Checkout code
 15 |         uses: actions/checkout@v4
 16 | 
 17 |       - name: Set up Node
 18 |         uses: actions/setup-node@v4
 19 |         with:
 20 |           node-version: ${{ matrix.node }}
 21 |           cache: 'yarn'
 22 | 
 23 |       - name: Install dependencies
 24 |         run: yarn install
 25 | 
 26 |       # Read existing version, reuse that, add a Git short hash
 27 |       - name: Set build version to Git commit
 28 |         run: node scripts/writeGitVersion.mjs $(git rev-parse --short HEAD)
 29 | 
 30 |       - name: Check updated version
 31 |         run: jq .version package.json
 32 | 
 33 |       - name: Run linter
 34 |         run: yarn lint
 35 | 
 36 |       - name: Run tests
 37 |         run: yarn test
 38 | 
 39 |       - name: Pack
 40 |         run: yarn pack
 41 | 
 42 |       - uses: actions/upload-artifact@v4
 43 |         with:
 44 |           name: package
 45 |           path: ./package.tgz
 46 | 
 47 |   test-types:
 48 |     name: Test Types with TypeScript ${{ matrix.ts }}
 49 | 
 50 |     needs: [build]
 51 |     runs-on: ubuntu-latest
 52 |     strategy:
 53 |       fail-fast: false
 54 |       matrix:
 55 |         node: ['22.x']
 56 |         ts: ['5.0', '5.1', '5.2', '5.3', '5.4', '5.5', '5.6', '5.7', '5.8']
 57 | 
 58 |     steps:
 59 |       - name: Checkout repo
 60 |         uses: actions/checkout@v4
 61 | 
 62 |       - name: Use node ${{ matrix.node }}
 63 |         uses: actions/setup-node@v4
 64 |         with:
 65 |           node-version: ${{ matrix.node }}
 66 |           cache: 'yarn'
 67 | 
 68 |       - name: Install deps
 69 |         run: yarn install
 70 | 
 71 |       # Build with the actual TS version in the repo
 72 |       - name: Pack
 73 |         run: yarn build && yarn pack
 74 | 
 75 |       - name: Install build artifact
 76 |         run: yarn add ./package.tgz
 77 | 
 78 |       # Then install the specific version to test against
 79 |       - name: Install TypeScript ${{ matrix.ts }}
 80 |         run: yarn add --dev typescript@${{ matrix.ts }}
 81 | 
 82 |       - name: 'Remove source to ensure packaged types are used'
 83 |         run: rm -rf src
 84 | 
 85 |         # Remove config line that points "reselect" to the `src` folder,
 86 |         # so that the typetest will use the installed version instead
 87 |       - run: sed -i -e /@remap-prod-remove-line/d ./typescript_test/tsconfig.json vitest.config.mts
 88 | 
 89 |       - name: Test types
 90 |         run: |
 91 |           ./node_modules/.bin/tsc --version
 92 |           yarn test:typescript
 93 | 
 94 |   are-the-types-wrong:
 95 |     name: Check package config with are-the-types-wrong
 96 | 
 97 |     needs: [build]
 98 |     runs-on: ubuntu-latest
 99 |     strategy:
100 |       fail-fast: false
101 |       matrix:
102 |         node: ['22.x']
103 |     steps:
104 |       - name: Checkout repo
105 |         uses: actions/checkout@v4
106 | 
107 |       - name: Use node ${{ matrix.node }}
108 |         uses: actions/setup-node@v4
109 |         with:
110 |           node-version: ${{ matrix.node }}
111 |           cache: 'yarn'
112 | 
113 |       - uses: actions/download-artifact@v4
114 |         with:
115 |           name: package
116 |           path: .
117 | 
118 |       # Note: We currently expect "FalseCJS" failures for Node16 + `moduleResolution: "node16",
119 |       - name: Run are-the-types-wrong
120 |         run: npx @arethetypeswrong/cli@latest ./package.tgz --format table --ignore-rules false-cjs
121 | 
122 |   test-published-artifact:
123 |     name: Test Published Artifact ${{ matrix.example }}
124 | 
125 |     needs: [build]
126 |     runs-on: ubuntu-latest
127 |     strategy:
128 |       fail-fast: false
129 |       matrix:
130 |         node: ['22.x']
131 |         example:
132 |           [
133 |             'cra4',
134 |             'cra5',
135 |             'next',
136 |             'vite',
137 |             'node-standard',
138 |             'node-esm',
139 |             'react-native',
140 |             'expo'
141 |           ]
142 |     steps:
143 |       - name: Checkout repo
144 |         uses: actions/checkout@v4
145 | 
146 |       - name: Use node ${{ matrix.node }}
147 |         uses: actions/setup-node@v4
148 |         with:
149 |           node-version: ${{ matrix.node }}
150 |           cache: 'yarn'
151 | 
152 |       - name: Clone RTK repo
153 |         run: git clone https://github.com/reduxjs/redux-toolkit.git ./redux-toolkit
154 | 
155 |       - name: Check folder contents
156 |         run: ls -l .
157 | 
158 |       - name: Install example deps
159 |         working-directory: ./redux-toolkit/examples/publish-ci/${{ matrix.example }}
160 |         run: yarn install
161 | 
162 |       - name: Install Playwright browser if necessary
163 |         working-directory: ./redux-toolkit/examples/publish-ci/${{ matrix.example }}
164 |         continue-on-error: true
165 |         run: yarn playwright install || true
166 | 
167 |       - uses: actions/download-artifact@v4
168 |         with:
169 |           name: package
170 |           path: ./redux-toolkit/examples/publish-ci/${{ matrix.example }}
171 | 
172 |       - name: Check folder contents
173 |         working-directory: ./redux-toolkit/examples/publish-ci/${{ matrix.example }}
174 |         run: ls -l .
175 | 
176 |       - name: Install build artifact
177 |         working-directory: ./redux-toolkit/examples/publish-ci/${{ matrix.example }}
178 |         run: yarn add ./package.tgz
179 | 
180 |       - name: Show installed package versions
181 |         working-directory: ./redux-toolkit/examples/publish-ci/${{ matrix.example }}
182 |         run: yarn info reselect && yarn why reselect
183 | 
184 |       - name: Set up JDK 17 for React Native build
185 |         if: matrix.example == 'react-native'
186 |         uses: actions/setup-java@v4
187 |         with:
188 |           java-version: '17.x'
189 |           distribution: 'temurin'
190 | 
191 |       - name: Check MSW version
192 |         working-directory: ./redux-toolkit/examples/publish-ci/${{ matrix.example }}
193 |         run: yarn why msw
194 | 
195 |       - name: Build example
196 |         working-directory: ./redux-toolkit/examples/publish-ci/${{ matrix.example }}
197 |         env:
198 |           NODE_OPTIONS: --openssl-legacy-provider
199 |         run: yarn build
200 | 
201 |       - name: Run test step
202 |         working-directory: ./redux-toolkit/examples/publish-ci/${{ matrix.example }}
203 |         run: yarn test
204 | 


--------------------------------------------------------------------------------
/.github/workflows/publish.yml:
--------------------------------------------------------------------------------
 1 | name: Publish Package to npmjs
 2 | on:
 3 |   # keeping it purely manual for now as to not accidentally trigger a release
 4 |   #release:
 5 |   #  types: [published]
 6 |   workflow_dispatch:
 7 | jobs:
 8 |   publish:
 9 |     runs-on: ubuntu-latest
10 |     permissions:
11 |       id-token: write
12 |       contents: read
13 |     steps:
14 |       - uses: actions/checkout@v4
15 |       - uses: actions/setup-node@v4
16 |         with:
17 |           node-version: '22.x'
18 |           registry-url: 'https://registry.npmjs.org'
19 |           cache: 'yarn'
20 |       - run: yarn install --frozen-lockfile
21 |       - run: yarn test
22 |       - run: npm publish --access public --provenance
23 |         env:
24 |           NODE_AUTH_TOKEN: ${{ secrets.NPM_PUBLISH_TOKEN }}
25 | 


--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
 1 | node_modules
 2 | lib
 3 | .nyc_output
 4 | coverage
 5 | dist
 6 | es
 7 | .vscode
 8 | .idea
 9 | typescript_test/should_compile/index.js
10 | typescript_test/should_not_compile/index.js
11 | typescript_test/common.js
12 | flow_test/should_fail/flow-typed/index.js.flow
13 | flow_test/should_pass/flow-typed/index.js.flow
14 | reselect-builds/
15 | trace
16 | 
17 | typesversions
18 | .cache
19 | .yarnrc
20 | .yarn/*
21 | !.yarn/patches
22 | !.yarn/releases
23 | !.yarn/plugins
24 | !.yarn/sdks
25 | !.yarn/versions
26 | .pnp.*
27 | *.tgz
28 | 
29 | website/translated_docs
30 | website/build/
31 | website/node_modules
32 | website/i18n/*
33 | website/.yarn/
34 | 
35 | docs/examples/**/*.js
36 | docs/examples/**/*.jsx
37 | 


--------------------------------------------------------------------------------
/.prettierrc.json:
--------------------------------------------------------------------------------
1 | {
2 |   "semi": false,
3 |   "singleQuote": true,
4 |   "tabWidth": 2,
5 |   "trailingComma": "none",
6 |   "arrowParens": "avoid"
7 | }
8 | 


--------------------------------------------------------------------------------
/.release-it.json:
--------------------------------------------------------------------------------
1 | {
2 |   "hooks": {
3 |     "after:bump": "yarn && git add -u"
4 |   },
5 |   "git": {
6 |     "tagName": "v${version}"
7 |   }
8 | }
9 | 


--------------------------------------------------------------------------------
/.yarnrc.yml:
--------------------------------------------------------------------------------
 1 | compressionLevel: mixed
 2 | 
 3 | enableGlobalCache: false
 4 | 
 5 | enableTransparentWorkspaces: false
 6 | 
 7 | nodeLinker: node-modules
 8 | 
 9 | yarnPath: .yarn/releases/yarn-4.4.1.cjs
10 | 


--------------------------------------------------------------------------------
/AUTHORS:
--------------------------------------------------------------------------------
 1 | Lee Bannard <l_bannard@yahoo.co.uk> (https://github.com/ellbee)
 2 | Martijn Faassen (https://github.com/faassen)
 3 | Ian Ker-Seymer <i.kerseymer@gmail.com> (https://github.com/ianks)
 4 | Mike S (https://github.com/SpainTrain)
 5 | Daniel Bugl <me@omnidan.net> (https://github.com/omnidan)
 6 | Ryan (https://github.com/ryanatkn)
 7 | Alex Guerra <alex@heyimalex.com> (https://github.com/HeyImAlex)
 8 | speedskater (https://github.com/speedskater)
 9 | Daniela Borges (https://github.com/sericaia)
10 | Brian Ng <bng412@gmail.com> (https://github.com/existentialism)
11 | C. T. Lin <chentsulin@gmail.com> (https://github.com/chentsulin)
12 | Jay <wuceh14678@gmail.com> (https://github.com/chungchiehlun)
13 | Christian Schuhmann (https://github.com/madebyherzblut)
14 | Daniel Barreto <daniel.barreto.n@gmail.com> (https://github.com/volrath)
15 | Adam Royle (https://github.com/ifunk)
16 | Elliot Crosby-McCullough <elliot.cm@gmail.com> (https://github.com/elliotcm)
17 | frankwallis (https://github.com/frankwallis)
18 | Jason Huang <chaoju.huang@gmail.com> (https://github.com/kaddopur)
19 | Josh Kelley (https://github.com/joshkel)
20 | Leon Aves (https://github.com/leonaves)
21 | Mark Dalgleish (https://github.com/markdalgleish)
22 | Max Goodman <c@chromako.de> (https://github.com/chromakode)
23 | Michael Lancaster <michaell.llancaster@gmail.com> (https://github.com/weblancaster)
24 | Mihail Diordiev (https://github.com/zalmoxisus)
25 | PSpSynedra (https://github.com/PSpSynedra)
26 | Simen Bekkhus <sbekkhus91@gmail.com> (https://github.com/SimenB)
27 | Wade Peterson (https://github.com/WadePeterson)
28 | 长天之云 <ambar.lee@gmail.com> (https://github.com/ambar)
29 | Courtland Allen <csallen@alum.mit.edu> (https://github.com/courthead)
30 | Henrik Joreteg <henrik@joreteg.com> (https://github.com/HenrikJoreteg)
31 | Kyle Davis (https://github.com/kyldvs)
32 | Salvador Hernandez <s.hernandez5400@gmail.com> (https://github.com/clickclickonsal)
33 | Nick Ball (https://github.com/npbee)
34 | mctep (https://github.com/mctep)
35 | Jacob Rask <jacob@jacobrask.net> (https://github.com/jacobrask)
36 | Luqmaan Dawoodjee <ldawoodjee@gmail.com> (https://github.com/luqmaan)
37 | Walter Breakell (https://github.com/wbreakell)
38 | Matthew Hetherington (https://github.com/matthetherington)
39 | Mike Wilcox <mwilcox56@gmail.com> (https://github.com/mjw56)
40 | David Edmondson (https://github.com/threehams)
41 | Andrey Zaytsev <za@zalab.net> (https://github.com/zandroid)
42 | 1ven (https://github.com/1ven)
43 | Alexey Yurchenko <alexes.dev@gmail.com> (https://github.com/alexesdev)
44 | Douglas Russell (https://github.com/dpwrussell)
45 | Yonatan Kogan (https://github.com/yoni-tock)
46 | Peter Petrov (https://github.com/pesho)
47 | Walter Breakell (https://github.com/wbreakell)
48 | Whien <sal95610@gmail.com> (https://github.com/madeinfree)
49 | Sergei Egorov <bsideup@gmail.com> (https://github.com/bsideup)
50 | Jim Bolla (https://github.com/jimbolla)
51 | Carl Bernardo (https://github.com/carlbernrdo)
52 | Daniel Lytkin <dan.lytkin@gmail.com> (https://github.com/aikoven)
53 | John Haley <john@haley.io> (https://github.com/johnhaley81)
54 | Alexandre <alexr.3165@gmail.com> (https://github.com/alex3165)
55 | 


--------------------------------------------------------------------------------
/CNAME:
--------------------------------------------------------------------------------
1 | reselect.js.org
2 | 


--------------------------------------------------------------------------------
/CREDITS.md:
--------------------------------------------------------------------------------
 1 | # CREDITS
 2 | 
 3 | * Based on a proposal for Redux by [Robert Binna](https://github.com/speedskater) and Philip Spitzlinger. 
 4 |   Lots of the basic structure of the code is thanks to this.
 5 | 
 6 | * Refactored into reselect library by Martijn Faassen and Lee Bannard
 7 |   at the React Europe Hackathon 2015. Also added tests.
 8 | 
 9 | * Contributors: Lee Bannard, Martijn Faassen, Robert Binna, Alex
10 |   Guerra, ryanatkn, Adam Royle, Christian Schuhmann, Jason Huang,
11 |   Daniel Barreto, Mihail Diordiev, Daniela Borges, Philip Spitzlinger,
12 |   C. T. Lin, SpainTrain, Mark Dalgleish, Brian Ng, 长天之云, Michael Lancaster,
13 |   Elliot Crosby-McCullough, Max Goodman, Simen Bekkhus, Wade Peterson,
14 |   chungchiehlun, Dave Hendler, Leon Aves, Ian Ker-Seymer, Josh Kelley,
15 |   Daniel Bugl, Courtland Allen, Henrik Joreteg, Kyle Davis, Nick Ball,
16 |   Salvador Hernandez, mctep, Jacob Rask, Luqmaan Dawoodjee, Walter Breakell,
17 |   Matthew Hetherington, Mike Wilcox, David Edmondson, Andrey Zaytsev, 1ven,
18 |   Alexey Yurchenko, Douglas Russell, Yonatan Kogan, Peter Petrov,
19 |   Whien, Sergei Egorov, Jim Bolla, Carl Bernardo, Daniel Lytkin, John Haley,
20 |   alex3165,
21 | 
22 | * Inspired by getters in Nuclear.js and subscriptions in re-frame.
23 | 
24 | * Special thanks to [David Edmonson](https://github.com/threehams) for maintaining the Typescript type definitions.
25 | 


--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
 1 | The MIT License (MIT)
 2 | 
 3 | Copyright (c) 2015-2018 Reselect Contributors
 4 | 
 5 | Permission is hereby granted, free of charge, to any person obtaining a copy
 6 | of this software and associated documentation files (the "Software"), to deal
 7 | in the Software without restriction, including without limitation the rights
 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 | 
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 | 
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 | 


--------------------------------------------------------------------------------
/codecov.yml:
--------------------------------------------------------------------------------
1 | comment:
2 |   layout: "reach, diff, flags, files"
3 |   behavior: default
4 |   require_changes: false  # if true: only post the comment if coverage changes
5 |   require_base: no        # [yes :: must have a base report to post]
6 |   require_head: no       # [yes :: must have a head report to post]
7 | 


--------------------------------------------------------------------------------
/docs/examples/FAQ/MyComponent.tsx:
--------------------------------------------------------------------------------
 1 | import type { FC } from 'react'
 2 | import { useSelectTodo } from './createParametricSelectorHook'
 3 | 
 4 | interface Props {
 5 |   id: number
 6 | }
 7 | 
 8 | const MyComponent: FC<Props> = ({ id }) => {
 9 |   const todo = useSelectTodo(id)
10 |   return <div>{todo?.title}</div>
11 | }
12 | 


--------------------------------------------------------------------------------
/docs/examples/FAQ/createCurriedSelector.ts:
--------------------------------------------------------------------------------
 1 | import type { weakMapMemoize, SelectorArray, UnknownMemoizer } from 'reselect'
 2 | import { createSelector } from 'reselect'
 3 | import { currySelector } from './currySelector'
 4 | 
 5 | export const createCurriedSelector = <
 6 |   InputSelectors extends SelectorArray,
 7 |   Result,
 8 |   OverrideMemoizeFunction extends UnknownMemoizer = typeof weakMapMemoize,
 9 |   OverrideArgsMemoizeFunction extends UnknownMemoizer = typeof weakMapMemoize
10 | >(
11 |   ...args: Parameters<
12 |     typeof createSelector<
13 |       InputSelectors,
14 |       Result,
15 |       OverrideMemoizeFunction,
16 |       OverrideArgsMemoizeFunction
17 |     >
18 |   >
19 | ) => {
20 |   return currySelector(createSelector(...args))
21 | }
22 | 


--------------------------------------------------------------------------------
/docs/examples/FAQ/createParametricSelectorHook.ts:
--------------------------------------------------------------------------------
 1 | import { useSelector } from 'react-redux'
 2 | import { createSelector } from 'reselect'
 3 | 
 4 | interface RootState {
 5 |   todos: {
 6 |     id: number
 7 |     completed: boolean
 8 |     title: string
 9 |     description: string
10 |   }[]
11 |   alerts: { id: number; read: boolean }[]
12 | }
13 | 
14 | const state: RootState = {
15 |   todos: [
16 |     {
17 |       id: 0,
18 |       completed: false,
19 |       title: 'Figure out if plants are really plotting world domination.',
20 |       description: 'They may be.'
21 |     },
22 |     {
23 |       id: 1,
24 |       completed: true,
25 |       title: 'Practice telekinesis for 15 minutes',
26 |       description: 'Just do it'
27 |     }
28 |   ],
29 |   alerts: [
30 |     { id: 0, read: false },
31 |     { id: 1, read: true }
32 |   ]
33 | }
34 | 
35 | const selectTodoById = createSelector(
36 |   [(state: RootState) => state.todos, (state: RootState, id: number) => id],
37 |   (todos, id) => todos.find(todo => todo.id === id)
38 | )
39 | 
40 | export const createParametricSelectorHook = <
41 |   Result,
42 |   Params extends readonly unknown[]
43 | >(
44 |   selector: (state: RootState, ...params: Params) => Result
45 | ) => {
46 |   return (...args: Params) => {
47 |     return useSelector((state: RootState) => selector(state, ...args))
48 |   }
49 | }
50 | 
51 | export const useSelectTodo = createParametricSelectorHook(selectTodoById)
52 | 


--------------------------------------------------------------------------------
/docs/examples/FAQ/currySelector.ts:
--------------------------------------------------------------------------------
 1 | import { createSelector } from 'reselect'
 2 | import type { RootState } from './selectorRecomputing'
 3 | 
 4 | export const currySelector = <
 5 |   State,
 6 |   Result,
 7 |   Params extends readonly any[],
 8 |   AdditionalFields
 9 | >(
10 |   selector: ((state: State, ...args: Params) => Result) & AdditionalFields
11 | ) => {
12 |   const curriedSelector = (...args: Params) => {
13 |     return (state: State) => {
14 |       return selector(state, ...args)
15 |     }
16 |   }
17 |   return Object.assign(curriedSelector, selector)
18 | }
19 | 
20 | const selectTodoByIdCurried = currySelector(
21 |   createSelector(
22 |     [(state: RootState) => state.todos, (state: RootState, id: number) => id],
23 |     (todos, id) => todos.find(todo => todo.id === id)
24 |   )
25 | )
26 | 


--------------------------------------------------------------------------------
/docs/examples/FAQ/howToTest.test.ts:
--------------------------------------------------------------------------------
 1 | import { createSelector } from 'reselect'
 2 | import { expect, test } from 'vitest'
 3 | 
 4 | interface RootState {
 5 |   todos: { id: number; completed: boolean }[]
 6 |   alerts: { id: number; read: boolean }[]
 7 | }
 8 | 
 9 | const state: RootState = {
10 |   todos: [
11 |     { id: 0, completed: false },
12 |     { id: 1, completed: true }
13 |   ],
14 |   alerts: [
15 |     { id: 0, read: false },
16 |     { id: 1, read: true }
17 |   ]
18 | }
19 | 
20 | // With `Vitest` or `Jest`
21 | test('selector unit test', () => {
22 |   const selectTodoIds = createSelector(
23 |     [(state: RootState) => state.todos],
24 |     todos => todos.map(({ id }) => id)
25 |   )
26 |   const firstResult = selectTodoIds(state)
27 |   const secondResult = selectTodoIds(state)
28 |   // Reference equality should pass.
29 |   expect(firstResult).toBe(secondResult)
30 |   // Deep equality should also pass.
31 |   expect(firstResult).toStrictEqual(secondResult)
32 |   selectTodoIds(state)
33 |   selectTodoIds(state)
34 |   selectTodoIds(state)
35 |   // The result function should not recalculate.
36 |   expect(selectTodoIds.recomputations()).toBe(1)
37 |   // input selectors should not recalculate.
38 |   expect(selectTodoIds.dependencyRecomputations()).toBe(1)
39 | })
40 | 
41 | // With `Chai`
42 | test('selector unit test', () => {
43 |   const selectTodoIds = createSelector(
44 |     [(state: RootState) => state.todos],
45 |     todos => todos.map(({ id }) => id)
46 |   )
47 |   const firstResult = selectTodoIds(state)
48 |   const secondResult = selectTodoIds(state)
49 |   // Reference equality should pass.
50 |   expect(firstResult).to.equal(secondResult)
51 |   // Deep equality should also pass.
52 |   expect(firstResult).to.deep.equal(secondResult)
53 |   selectTodoIds(state)
54 |   selectTodoIds(state)
55 |   selectTodoIds(state)
56 |   // The result function should not recalculate.
57 |   expect(selectTodoIds.recomputations()).to.equal(1)
58 |   // input selectors should not recalculate.
59 |   expect(selectTodoIds.dependencyRecomputations()).to.equal(1)
60 | })
61 | 


--------------------------------------------------------------------------------
/docs/examples/FAQ/identity.ts:
--------------------------------------------------------------------------------
1 | import { createSelectorCreator } from 'reselect'
2 | 
3 | const identity = <Func extends (...args: any[]) => any>(func: Func) => func
4 | 
5 | const createNonMemoizedSelector = createSelectorCreator({
6 |   memoize: identity,
7 |   argsMemoize: identity
8 | })
9 | 


--------------------------------------------------------------------------------
/docs/examples/FAQ/selectorRecomputing.ts:
--------------------------------------------------------------------------------
 1 | import { createSelector, lruMemoize } from 'reselect'
 2 | 
 3 | export interface RootState {
 4 |   todos: { id: number; completed: boolean }[]
 5 |   alerts: { id: number; read: boolean; type: string }[]
 6 | }
 7 | 
 8 | const selectAlertsByType = createSelector(
 9 |   [
10 |     (state: RootState) => state.alerts,
11 |     (state: RootState, type: string) => type
12 |   ],
13 |   (alerts, type) => alerts.filter(todo => todo.type === type),
14 |   {
15 |     argsMemoize: lruMemoize,
16 |     argsMemoizeOptions: {
17 |       // This will check the arguments passed to the output selector.
18 |       equalityCheck: (a, b) => {
19 |         if (a !== b) {
20 |           console.log('Changed argument:', a, 'to', b)
21 |         }
22 |         return a === b
23 |       }
24 |     }
25 |   }
26 | )
27 | 


--------------------------------------------------------------------------------
/docs/examples/basicUsage.ts:
--------------------------------------------------------------------------------
 1 | import { createSelector } from 'reselect'
 2 | 
 3 | interface RootState {
 4 |   todos: { id: number; completed: boolean }[]
 5 |   alerts: { id: number; read: boolean }[]
 6 | }
 7 | 
 8 | const state: RootState = {
 9 |   todos: [
10 |     { id: 0, completed: false },
11 |     { id: 1, completed: true }
12 |   ],
13 |   alerts: [
14 |     { id: 0, read: false },
15 |     { id: 1, read: true }
16 |   ]
17 | }
18 | 
19 | const selectCompletedTodos = (state: RootState) => {
20 |   console.log('selector ran')
21 |   return state.todos.filter(todo => todo.completed === true)
22 | }
23 | 
24 | selectCompletedTodos(state) // selector ran
25 | selectCompletedTodos(state) // selector ran
26 | selectCompletedTodos(state) // selector ran
27 | 
28 | const memoizedSelectCompletedTodos = createSelector(
29 |   [(state: RootState) => state.todos],
30 |   todos => {
31 |     console.log('memoized selector ran')
32 |     return todos.filter(todo => todo.completed === true)
33 |   }
34 | )
35 | 
36 | memoizedSelectCompletedTodos(state) // memoized selector ran
37 | memoizedSelectCompletedTodos(state)
38 | memoizedSelectCompletedTodos(state)
39 | 
40 | console.log(selectCompletedTodos(state) === selectCompletedTodos(state)) //=> false
41 | 
42 | console.log(
43 |   memoizedSelectCompletedTodos(state) === memoizedSelectCompletedTodos(state)
44 | ) //=> true
45 | 


--------------------------------------------------------------------------------
/docs/examples/createSelector/annotateResultFunction.ts:
--------------------------------------------------------------------------------
 1 | import { createSelector } from 'reselect'
 2 | 
 3 | interface Todo {
 4 |   id: number
 5 |   completed: boolean
 6 | }
 7 | 
 8 | interface Alert {
 9 |   id: number
10 |   read: boolean
11 | }
12 | 
13 | export interface RootState {
14 |   todos: Todo[]
15 |   alerts: Alert[]
16 | }
17 | 
18 | export const createAppSelector = createSelector.withTypes<RootState>()
19 | 
20 | const selectTodoIds = createAppSelector(
21 |   // Type of `state` is set to `RootState`, no need to manually set the type
22 |   state => state.todos,
23 |   // ❌ Known limitation: Parameter types are not inferred in this scenario
24 |   // so you will have to manually annotate them.
25 |   // highlight-start
26 |   (todos: Todo[]) => todos.map(({ id }) => id)
27 |   // highlight-end
28 | )
29 | 


--------------------------------------------------------------------------------
/docs/examples/createSelector/createAppSelector.ts:
--------------------------------------------------------------------------------
 1 | import microMemoize from 'micro-memoize'
 2 | import { shallowEqual } from 'react-redux'
 3 | import { createSelectorCreator, lruMemoize } from 'reselect'
 4 | 
 5 | export interface RootState {
 6 |   todos: { id: number; completed: boolean }[]
 7 |   alerts: { id: number; read: boolean }[]
 8 | }
 9 | 
10 | export const createAppSelector = createSelectorCreator({
11 |   memoize: lruMemoize,
12 |   argsMemoize: microMemoize,
13 |   memoizeOptions: {
14 |     maxSize: 10,
15 |     equalityCheck: shallowEqual,
16 |     resultEqualityCheck: shallowEqual
17 |   },
18 |   argsMemoizeOptions: {
19 |     isEqual: shallowEqual,
20 |     maxSize: 10
21 |   },
22 |   devModeChecks: {
23 |     identityFunctionCheck: 'never',
24 |     inputStabilityCheck: 'always'
25 |   }
26 | }).withTypes<RootState>()
27 | 
28 | const selectReadAlerts = createAppSelector(
29 |   [
30 |     // Type of `state` is set to `RootState`, no need to manually set the type
31 |     // highlight-start
32 |     state => state.alerts
33 |     // highlight-end
34 |   ],
35 |   alerts => alerts.filter(({ read }) => read)
36 | )
37 | 


--------------------------------------------------------------------------------
/docs/examples/createSelector/withTypes.ts:
--------------------------------------------------------------------------------
 1 | import { createSelector } from 'reselect'
 2 | 
 3 | export interface RootState {
 4 |   todos: { id: number; completed: boolean }[]
 5 |   alerts: { id: number; read: boolean }[]
 6 | }
 7 | 
 8 | export const createAppSelector = createSelector.withTypes<RootState>()
 9 | 
10 | const selectTodoIds = createAppSelector(
11 |   [
12 |     // Type of `state` is set to `RootState`, no need to manually set the type
13 |     // highlight-start
14 |     state => state.todos
15 |     // highlight-end
16 |   ],
17 |   todos => todos.map(({ id }) => id)
18 | )
19 | 


--------------------------------------------------------------------------------
/docs/examples/createStructuredSelector/MyComponent.tsx:
--------------------------------------------------------------------------------
 1 | import type { RootState } from 'createStructuredSelector/modernUseCase'
 2 | import { structuredSelector } from 'createStructuredSelector/modernUseCase'
 3 | import type { FC } from 'react'
 4 | import { useSelector } from 'react-redux'
 5 | 
 6 | interface Props {
 7 |   id: number
 8 | }
 9 | 
10 | const MyComponent: FC<Props> = ({ id }) => {
11 |   const { todos, alerts, todoById } = useSelector((state: RootState) =>
12 |     structuredSelector(state, id)
13 |   )
14 | 
15 |   return (
16 |     <div>
17 |       Next to do is:
18 |       <h2>{todoById.title}</h2>
19 |       <p>Description: {todoById.description}</p>
20 |       <ul>
21 |         <h3>All other to dos:</h3>
22 |         {todos.map(todo => (
23 |           <li key={todo.id}>{todo.title}</li>
24 |         ))}
25 |       </ul>
26 |     </div>
27 |   )
28 | }
29 | 


--------------------------------------------------------------------------------
/docs/examples/createStructuredSelector/modernUseCase.ts:
--------------------------------------------------------------------------------
 1 | import { createSelector, createStructuredSelector } from 'reselect'
 2 | 
 3 | export interface RootState {
 4 |   todos: {
 5 |     id: number
 6 |     completed: boolean
 7 |     title: string
 8 |     description: string
 9 |   }[]
10 |   alerts: { id: number; read: boolean }[]
11 | }
12 | 
13 | // This:
14 | export const structuredSelector = createStructuredSelector(
15 |   {
16 |     todos: (state: RootState) => state.todos,
17 |     alerts: (state: RootState) => state.alerts,
18 |     todoById: (state: RootState, id: number) => state.todos[id]
19 |   },
20 |   createSelector
21 | )
22 | 
23 | // Is essentially the same as this:
24 | export const selector = createSelector(
25 |   [
26 |     (state: RootState) => state.todos,
27 |     (state: RootState) => state.alerts,
28 |     (state: RootState, id: number) => state.todos[id]
29 |   ],
30 |   (todos, alerts, todoById) => {
31 |     return {
32 |       todos,
33 |       alerts,
34 |       todoById
35 |     }
36 |   }
37 | )
38 | 


--------------------------------------------------------------------------------
/docs/examples/createStructuredSelector/withTypes.ts:
--------------------------------------------------------------------------------
 1 | import { createStructuredSelector } from 'reselect'
 2 | 
 3 | export interface RootState {
 4 |   todos: { id: number; completed: boolean }[]
 5 |   alerts: { id: number; read: boolean }[]
 6 | }
 7 | 
 8 | export const createStructuredAppSelector =
 9 |   createStructuredSelector.withTypes<RootState>()
10 | 
11 | const structuredAppSelector = createStructuredAppSelector({
12 |   // Type of `state` is set to `RootState`, no need to manually set the type
13 |   // highlight-start
14 |   todos: state => state.todos,
15 |   // highlight-end
16 |   alerts: state => state.alerts,
17 |   todoById: (state, id: number) => state.todos[id]
18 | })
19 | 


--------------------------------------------------------------------------------
/docs/examples/development-only-stability-checks/identityFunctionCheck.ts:
--------------------------------------------------------------------------------
 1 | import { createSelector } from 'reselect'
 2 | 
 3 | interface RootState {
 4 |   todos: { id: number; completed: boolean }[]
 5 |   alerts: { id: number; read: boolean }[]
 6 | }
 7 | 
 8 | // Create a selector that checks to see if the result function is an identity function.
 9 | const selectTodos = createSelector(
10 |   // ✔️ GOOD: Contains extraction logic.
11 |   [(state: RootState) => state.todos],
12 |   // ❌ BAD: Does not contain transformation logic.
13 |   todos => todos,
14 |   // Will override the global setting.
15 |   { devModeChecks: { identityFunctionCheck: 'always' } }
16 | )
17 | 


--------------------------------------------------------------------------------
/docs/examples/development-only-stability-checks/inputStabilityCheck.ts:
--------------------------------------------------------------------------------
 1 | import { createSelector } from 'reselect'
 2 | 
 3 | interface RootState {
 4 |   todos: { id: number; completed: boolean }[]
 5 |   alerts: { id: number; read: boolean }[]
 6 | }
 7 | 
 8 | // Create a selector that double-checks the results of input selectors every time it runs.
 9 | const selectCompletedTodosLength = createSelector(
10 |   [
11 |     // ❌ Incorrect Use Case: This input selector will not be
12 |     // memoized properly since it always returns a new reference.
13 |     (state: RootState) =>
14 |       state.todos.filter(({ completed }) => completed === true)
15 |   ],
16 |   completedTodos => completedTodos.length,
17 |   // Will override the global setting.
18 |   { devModeChecks: { inputStabilityCheck: 'always' } }
19 | )
20 | 


--------------------------------------------------------------------------------
/docs/examples/handling-empty-array-results/fallbackToEmptyArray.ts:
--------------------------------------------------------------------------------
 1 | import { createSelector } from 'reselect'
 2 | import type { RootState } from './firstPattern'
 3 | 
 4 | const EMPTY_ARRAY: [] = []
 5 | 
 6 | export const fallbackToEmptyArray = <T>(array: T[]) => {
 7 |   return array.length === 0 ? EMPTY_ARRAY : array
 8 | }
 9 | 
10 | const selectCompletedTodos = createSelector(
11 |   [(state: RootState) => state.todos],
12 |   todos => {
13 |     return fallbackToEmptyArray(todos.filter(todo => todo.completed === true))
14 |   }
15 | )
16 | 


--------------------------------------------------------------------------------
/docs/examples/handling-empty-array-results/firstPattern.ts:
--------------------------------------------------------------------------------
 1 | import { createSelector } from 'reselect'
 2 | 
 3 | export interface RootState {
 4 |   todos: {
 5 |     id: number
 6 |     title: string
 7 |     description: string
 8 |     completed: boolean
 9 |   }[]
10 | }
11 | 
12 | const EMPTY_ARRAY: [] = []
13 | 
14 | const selectCompletedTodos = createSelector(
15 |   [(state: RootState) => state.todos],
16 |   todos => {
17 |     const completedTodos = todos.filter(todo => todo.completed === true)
18 |     return completedTodos.length === 0 ? EMPTY_ARRAY : completedTodos
19 |   }
20 | )
21 | 


--------------------------------------------------------------------------------
/docs/examples/lruMemoize/referenceEqualityCheck.ts:
--------------------------------------------------------------------------------
1 | const referenceEqualityCheck = (previousValue: any, currentValue: any) => {
2 |   return previousValue === currentValue
3 | }
4 | 


--------------------------------------------------------------------------------
/docs/examples/lruMemoize/usingWithCreateSelector.ts:
--------------------------------------------------------------------------------
 1 | import { shallowEqual } from 'react-redux'
 2 | import { createSelector, lruMemoize } from 'reselect'
 3 | 
 4 | export interface RootState {
 5 |   todos: {
 6 |     id: number
 7 |     completed: boolean
 8 |     title: string
 9 |     description: string
10 |   }[]
11 |   alerts: { id: number; read: boolean }[]
12 | }
13 | 
14 | const selectTodoIds = createSelector(
15 |   [(state: RootState) => state.todos],
16 |   todos => todos.map(todo => todo.id),
17 |   {
18 |     memoize: lruMemoize,
19 |     memoizeOptions: {
20 |       equalityCheck: shallowEqual,
21 |       resultEqualityCheck: shallowEqual,
22 |       maxSize: 10
23 |     },
24 |     argsMemoize: lruMemoize,
25 |     argsMemoizeOptions: {
26 |       equalityCheck: shallowEqual,
27 |       resultEqualityCheck: shallowEqual,
28 |       maxSize: 10
29 |     }
30 |   }
31 | )
32 | 


--------------------------------------------------------------------------------
/docs/examples/lruMemoize/usingWithCreateSelectorCreator.ts:
--------------------------------------------------------------------------------
 1 | import { shallowEqual } from 'react-redux'
 2 | import { createSelectorCreator, lruMemoize } from 'reselect'
 3 | 
 4 | export interface RootState {
 5 |   todos: {
 6 |     id: number
 7 |     completed: boolean
 8 |     title: string
 9 |     description: string
10 |   }[]
11 |   alerts: { id: number; read: boolean }[]
12 | }
13 | 
14 | const createSelectorShallowEqual = createSelectorCreator({
15 |   memoize: lruMemoize,
16 |   memoizeOptions: {
17 |     equalityCheck: shallowEqual,
18 |     resultEqualityCheck: shallowEqual,
19 |     maxSize: 10
20 |   },
21 |   argsMemoize: lruMemoize,
22 |   argsMemoizeOptions: {
23 |     equalityCheck: shallowEqual,
24 |     resultEqualityCheck: shallowEqual,
25 |     maxSize: 10
26 |   }
27 | })
28 | 
29 | const selectTodoIds = createSelectorShallowEqual(
30 |   [(state: RootState) => state.todos],
31 |   todos => todos.map(todo => todo.id)
32 | )
33 | 


--------------------------------------------------------------------------------
/docs/examples/tsconfig.json:
--------------------------------------------------------------------------------
 1 | {
 2 |   "compilerOptions": {
 3 |     "allowSyntheticDefaultImports": true,
 4 |     "noUnusedLocals": false,
 5 |     "noUnusedParameters": false,
 6 |     "allowUnusedLabels": true,
 7 |     "isolatedModules": true,
 8 |     "removeComments": false,
 9 |     "checkJs": true,
10 |     "alwaysStrict": false,
11 |     "baseUrl": ".",
12 |     "outDir": "dist",
13 |     "strict": true,
14 |     "target": "ESNext",
15 |     "module": "ESNext",
16 |     "moduleResolution": "Node",
17 |     "esModuleInterop": true,
18 |     "skipLibCheck": true,
19 |     "allowJs": true,
20 |     "jsx": "preserve",
21 |     "noErrorTruncation": true,
22 |     "forceConsistentCasingInFileNames": true,
23 |     "experimentalDecorators": true,
24 |     "paths": {
25 |       "reselect": ["../../src/index"], // @remap-prod-remove-line
26 |       "@internal/*": ["../../src/*"]
27 |     }
28 |   },
29 |   "include": ["**/*.ts", "**/*.tsx"]
30 | }
31 | 


--------------------------------------------------------------------------------
/docs/examples/unstable_autotrackMemoize/usingWithCreateSelector.ts:
--------------------------------------------------------------------------------
 1 | import { createSelector, unstable_autotrackMemoize } from 'reselect'
 2 | 
 3 | export interface RootState {
 4 |   todos: { id: number; completed: boolean }[]
 5 |   alerts: { id: number; read: boolean }[]
 6 | }
 7 | 
 8 | const selectTodoIds = createSelector(
 9 |   [(state: RootState) => state.todos],
10 |   todos => todos.map(todo => todo.id),
11 |   { memoize: unstable_autotrackMemoize }
12 | )
13 | 


--------------------------------------------------------------------------------
/docs/examples/unstable_autotrackMemoize/usingWithCreateSelectorCreator.ts:
--------------------------------------------------------------------------------
 1 | import { createSelectorCreator, unstable_autotrackMemoize } from 'reselect'
 2 | import type { RootState } from './usingWithCreateSelector'
 3 | 
 4 | const createSelectorAutotrack = createSelectorCreator({
 5 |   memoize: unstable_autotrackMemoize
 6 | })
 7 | 
 8 | const selectTodoIds = createSelectorAutotrack(
 9 |   [(state: RootState) => state.todos],
10 |   todos => todos.map(todo => todo.id)
11 | )
12 | 


--------------------------------------------------------------------------------
/docs/examples/weakMapMemoize/cacheSizeProblem.ts:
--------------------------------------------------------------------------------
 1 | import { createSelector } from 'reselect'
 2 | 
 3 | export interface RootState {
 4 |   items: { id: number; category: string; name: string }[]
 5 | }
 6 | 
 7 | const state: RootState = {
 8 |   items: [
 9 |     { id: 1, category: 'Electronics', name: 'Wireless Headphones' },
10 |     { id: 2, category: 'Books', name: 'The Great Gatsby' },
11 |     { id: 3, category: 'Home Appliances', name: 'Blender' },
12 |     { id: 4, category: 'Stationery', name: 'Sticky Notes' }
13 |   ]
14 | }
15 | 
16 | const selectItemsByCategory = createSelector(
17 |   [
18 |     (state: RootState) => state.items,
19 |     (state: RootState, category: string) => category
20 |   ],
21 |   (items, category) => items.filter(item => item.category === category)
22 | )
23 | 
24 | selectItemsByCategory(state, 'Electronics') // Selector runs
25 | selectItemsByCategory(state, 'Electronics')
26 | selectItemsByCategory(state, 'Stationery') // Selector runs
27 | selectItemsByCategory(state, 'Electronics') // Selector runs again!
28 | 


--------------------------------------------------------------------------------
/docs/examples/weakMapMemoize/cacheSizeSolution.ts:
--------------------------------------------------------------------------------
 1 | import { createSelector, weakMapMemoize } from 'reselect'
 2 | import type { RootState } from './cacheSizeProblem'
 3 | 
 4 | const state: RootState = {
 5 |   items: [
 6 |     { id: 1, category: 'Electronics', name: 'Wireless Headphones' },
 7 |     { id: 2, category: 'Books', name: 'The Great Gatsby' },
 8 |     { id: 3, category: 'Home Appliances', name: 'Blender' },
 9 |     { id: 4, category: 'Stationery', name: 'Sticky Notes' }
10 |   ]
11 | }
12 | 
13 | const selectItemsByCategory = createSelector(
14 |   [
15 |     (state: RootState) => state.items,
16 |     (state: RootState, category: string) => category
17 |   ],
18 |   (items, category) => items.filter(item => item.category === category),
19 |   {
20 |     memoize: weakMapMemoize,
21 |     argsMemoize: weakMapMemoize
22 |   }
23 | )
24 | 
25 | selectItemsByCategory(state, 'Electronics') // Selector runs
26 | selectItemsByCategory(state, 'Electronics') // Cached
27 | selectItemsByCategory(state, 'Stationery') // Selector runs
28 | selectItemsByCategory(state, 'Electronics') // Still cached!
29 | 


--------------------------------------------------------------------------------
/docs/examples/weakMapMemoize/setMaxSize.ts:
--------------------------------------------------------------------------------
 1 | import { createSelector, lruMemoize } from 'reselect'
 2 | import type { RootState } from './cacheSizeProblem'
 3 | 
 4 | const selectItemsByCategory = createSelector(
 5 |   [
 6 |     (state: RootState) => state.items,
 7 |     (state: RootState, category: string) => category
 8 |   ],
 9 |   (items, category) => items.filter(item => item.category === category),
10 |   {
11 |     memoize: lruMemoize,
12 |     memoizeOptions: {
13 |       maxSize: 10
14 |     }
15 |   }
16 | )
17 | 


--------------------------------------------------------------------------------
/docs/examples/weakMapMemoize/usingWithCreateSelector.ts:
--------------------------------------------------------------------------------
 1 | import { createSelector, weakMapMemoize } from 'reselect'
 2 | import type { RootState } from './cacheSizeProblem'
 3 | 
 4 | const state: RootState = {
 5 |   items: [
 6 |     { id: 1, category: 'Electronics', name: 'Wireless Headphones' },
 7 |     { id: 2, category: 'Books', name: 'The Great Gatsby' },
 8 |     { id: 3, category: 'Home Appliances', name: 'Blender' },
 9 |     { id: 4, category: 'Stationery', name: 'Sticky Notes' }
10 |   ]
11 | }
12 | 
13 | const selectItemsByCategory = createSelector(
14 |   [
15 |     (state: RootState) => state.items,
16 |     (state: RootState, category: string) => category
17 |   ],
18 |   (items, category) => items.filter(item => item.category === category),
19 |   {
20 |     memoize: weakMapMemoize,
21 |     argsMemoize: weakMapMemoize
22 |   }
23 | )
24 | 
25 | selectItemsByCategory(state, 'Electronics') // Selector runs
26 | selectItemsByCategory(state, 'Electronics')
27 | selectItemsByCategory(state, 'Stationery') // Selector runs
28 | selectItemsByCategory(state, 'Electronics')
29 | 


--------------------------------------------------------------------------------
/docs/examples/weakMapMemoize/usingWithCreateSelectorCreator.ts:
--------------------------------------------------------------------------------
 1 | import { createSelectorCreator, weakMapMemoize } from 'reselect'
 2 | import type { RootState } from './cacheSizeProblem'
 3 | 
 4 | const state: RootState = {
 5 |   items: [
 6 |     { id: 1, category: 'Electronics', name: 'Wireless Headphones' },
 7 |     { id: 2, category: 'Books', name: 'The Great Gatsby' },
 8 |     { id: 3, category: 'Home Appliances', name: 'Blender' },
 9 |     { id: 4, category: 'Stationery', name: 'Sticky Notes' }
10 |   ]
11 | }
12 | 
13 | const createSelectorWeakMap = createSelectorCreator({
14 |   memoize: weakMapMemoize,
15 |   argsMemoize: weakMapMemoize
16 | })
17 | 
18 | const selectItemsByCategory = createSelectorWeakMap(
19 |   [
20 |     (state: RootState) => state.items,
21 |     (state: RootState, category: string) => category
22 |   ],
23 |   (items, category) => items.filter(item => item.category === category)
24 | )
25 | 
26 | selectItemsByCategory(state, 'Electronics') // Selector runs
27 | selectItemsByCategory(state, 'Electronics')
28 | selectItemsByCategory(state, 'Stationery') // Selector runs
29 | selectItemsByCategory(state, 'Electronics')
30 | 


--------------------------------------------------------------------------------
/docs/examples/weakMapMemoize/withUseMemo.tsx:
--------------------------------------------------------------------------------
 1 | import type { FC } from 'react'
 2 | import { useMemo } from 'react'
 3 | import { useSelector } from 'react-redux'
 4 | import { createSelector } from 'reselect'
 5 | import type { RootState } from './cacheSizeProblem'
 6 | 
 7 | const makeSelectItemsByCategory = (category: string) =>
 8 |   createSelector([(state: RootState) => state.items], items =>
 9 |     items.filter(item => item.category === category)
10 |   )
11 | 
12 | interface Props {
13 |   category: string
14 | }
15 | 
16 | const MyComponent: FC<Props> = ({ category }) => {
17 |   const selectItemsByCategory = useMemo(
18 |     () => makeSelectItemsByCategory(category),
19 |     [category]
20 |   )
21 | 
22 |   const itemsByCategory = useSelector(selectItemsByCategory)
23 | 
24 |   return (
25 |     <div>
26 |       {itemsByCategory.map(item => (
27 |         <div key={item.id}>{item.name}</div>
28 |       ))}
29 |     </div>
30 |   )
31 | }
32 | 


--------------------------------------------------------------------------------
/netlify.toml:
--------------------------------------------------------------------------------
 1 | [build]
 2 |   base    = "website"
 3 |   publish = "build"
 4 |   command = "yarn build"
 5 |   ignore  = "git diff --quiet HEAD^ HEAD -- ./docs/ ."
 6 | 
 7 | [build.environment]
 8 | NODE_VERSION = "20"
 9 | NODE_OPTIONS = "--max_old_space_size=4096"
10 | NETLIFY_USE_YARN = "true"
11 | YARN_VERSION = "1.22.10"
12 | 
13 | [[plugins]]
14 |   package = "netlify-plugin-cache"
15 |   [plugins.inputs]
16 |     paths = [
17 |       "node_modules/.cache",
18 |       "website/node_modules/.cache",
19 |       ".yarn/.cache"
20 |     ]
21 | 
22 | 


--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
 1 | {
 2 |   "name": "reselect",
 3 |   "version": "5.1.1",
 4 |   "description": "Selectors for Redux.",
 5 |   "main": "./dist/cjs/index.js",
 6 |   "module": "./dist/reselect.legacy-esm.js",
 7 |   "types": "./dist/reselect.d.ts",
 8 |   "exports": {
 9 |     "./package.json": "./package.json",
10 |     ".": {
11 |       "types": "./dist/reselect.d.ts",
12 |       "import": "./dist/reselect.mjs",
13 |       "default": "./dist/cjs/index.js"
14 |     }
15 |   },
16 |   "files": [
17 |     "src",
18 |     "dist"
19 |   ],
20 |   "sideEffects": false,
21 |   "bugs": {
22 |     "url": "https://github.com/reduxjs/reselect/issues"
23 |   },
24 |   "scripts": {
25 |     "build": "yarn clean && tsup",
26 |     "clean": "rimraf dist",
27 |     "format": "prettier --write \"{src,test}/**/*.{js,ts}\" \"docs/**/*.md\"",
28 |     "lint": "eslint src test",
29 |     "prepack": "yarn build",
30 |     "bench": "vitest --run bench --mode production",
31 |     "test": "node --expose-gc ./node_modules/vitest/dist/cli-wrapper.js --run && vitest --run --typecheck.only",
32 |     "test:watch": "node --expose-gc ./node_modules/vitest/dist/cli-wrapper.js --watch",
33 |     "test:cov": "vitest run --coverage",
34 |     "type-check": "vitest --run --typecheck.only",
35 |     "type-check:trace": "vitest --run --typecheck.only && tsc --noEmit -p typescript_test/tsconfig.json --generateTrace trace && npx @typescript/analyze-trace trace && rimraf trace",
36 |     "test:typescript": "tsc --noEmit -p typescript_test/tsconfig.json",
37 |     "docs:start": "yarn --cwd website start",
38 |     "docs:build": "yarn --cwd website build",
39 |     "docs:clear": "yarn --cwd website clear",
40 |     "docs:serve": "yarn --cwd website serve"
41 |   },
42 |   "keywords": [
43 |     "react",
44 |     "redux"
45 |   ],
46 |   "authors": [
47 |     "Lee Bannard",
48 |     "Robert Binna",
49 |     "Martijn Faassen",
50 |     "Philip Spitzlinger"
51 |   ],
52 |   "repository": {
53 |     "type": "git",
54 |     "url": "https://github.com/reduxjs/reselect.git"
55 |   },
56 |   "license": "MIT",
57 |   "devDependencies": {
58 |     "@reduxjs/toolkit": "^2.0.1",
59 |     "@testing-library/react": "^14.1.2",
60 |     "@types/lodash": "^4.14.175",
61 |     "@types/react": "^18.2.38",
62 |     "@types/react-dom": "^18.2.17",
63 |     "@types/shelljs": "^0.8.11",
64 |     "@typescript-eslint/eslint-plugin": "^6",
65 |     "@typescript-eslint/eslint-plugin-tslint": "^6",
66 |     "@typescript-eslint/parser": "^6",
67 |     "@typescript/analyze-trace": "^0.10.1",
68 |     "eslint": "^8.0.1",
69 |     "eslint-plugin-react": "^7.26.1",
70 |     "eslint-plugin-typescript": "0.14.0",
71 |     "jsdom": "^23.0.0",
72 |     "lodash": "^4.17.21",
73 |     "lodash.memoize": "^4.1.2",
74 |     "memoize-one": "^6.0.0",
75 |     "micro-memoize": "^4.0.9",
76 |     "netlify-plugin-cache": "^1.0.3",
77 |     "prettier": "^2.7.1",
78 |     "react": "^18.2.0",
79 |     "react-dom": "^18.2.0",
80 |     "react-redux": "^9.0.4",
81 |     "rimraf": "^3.0.2",
82 |     "shelljs": "^0.8.5",
83 |     "tsup": "^8.2.4",
84 |     "typescript": "^5.8.2",
85 |     "vitest": "^1.6.0"
86 |   },
87 |   "resolutions": {
88 |     "esbuild": "0.23.0"
89 |   },
90 |   "packageManager": "yarn@4.4.1"
91 | }
92 | 


--------------------------------------------------------------------------------
/scripts/writeGitVersion.mjs:
--------------------------------------------------------------------------------
 1 | import path from 'path'
 2 | import fs from 'fs'
 3 | import { fileURLToPath } from 'node:url'
 4 | 
 5 | const __filename = fileURLToPath(import.meta.url)
 6 | const __dirname = path.dirname(__filename)
 7 | 
 8 | const gitRev = process.argv[2]
 9 | 
10 | const packagePath = path.join(__dirname, '../package.json')
11 | const pkg = JSON.parse(fs.readFileSync(packagePath))
12 | 
13 | pkg.version = `${pkg.version}-${gitRev}`
14 | fs.writeFileSync(packagePath, JSON.stringify(pkg, null, 2))
15 | 


--------------------------------------------------------------------------------
/src/autotrackMemoize/autotrackMemoize.ts:
--------------------------------------------------------------------------------
  1 | import { createNode, updateNode } from './proxy'
  2 | import type { Node } from './tracking'
  3 | 
  4 | import { createCacheKeyComparator, referenceEqualityCheck } from '../lruMemoize'
  5 | import type { AnyFunction, DefaultMemoizeFields, Simplify } from '../types'
  6 | import { createCache } from './autotracking'
  7 | 
  8 | /**
  9 |  * Uses an "auto-tracking" approach inspired by the work of the Ember Glimmer team.
 10 |  * It uses a Proxy to wrap arguments and track accesses to nested fields
 11 |  * in your selector on first read. Later, when the selector is called with
 12 |  * new arguments, it identifies which accessed fields have changed and
 13 |  * only recalculates the result if one or more of those accessed fields have changed.
 14 |  * This allows it to be more precise than the shallow equality checks in `lruMemoize`.
 15 |  *
 16 |  * __Design Tradeoffs for `autotrackMemoize`:__
 17 |  * - Pros:
 18 |  *    - It is likely to avoid excess calculations and recalculate fewer times than `lruMemoize` will,
 19 |  *    which may also result in fewer component re-renders.
 20 |  * - Cons:
 21 |  *    - It only has a cache size of 1.
 22 |  *    - It is slower than `lruMemoize`, because it has to do more work. (How much slower is dependent on the number of accessed fields in a selector, number of calls, frequency of input changes, etc)
 23 |  *    - It can have some unexpected behavior. Because it tracks nested field accesses,
 24 |  *    cases where you don't access a field will not recalculate properly.
 25 |  *    For example, a badly-written selector like:
 26 |  *      ```ts
 27 |  *      createSelector([state => state.todos], todos => todos)
 28 |  *      ```
 29 |  *      that just immediately returns the extracted value will never update, because it doesn't see any field accesses to check.
 30 |  *
 31 |  * __Use Cases for `autotrackMemoize`:__
 32 |  * - It is likely best used for cases where you need to access specific nested fields
 33 |  * in data, and avoid recalculating if other fields in the same data objects are immutably updated.
 34 |  *
 35 |  * @param func - The function to be memoized.
 36 |  * @returns A memoized function with a `.clearCache()` method attached.
 37 |  *
 38 |  * @example
 39 |  * <caption>Using `createSelector`</caption>
 40 |  * ```ts
 41 |  * import { unstable_autotrackMemoize as autotrackMemoize, createSelector } from 'reselect'
 42 |  *
 43 |  * const selectTodoIds = createSelector(
 44 |  *   [(state: RootState) => state.todos],
 45 |  *   (todos) => todos.map(todo => todo.id),
 46 |  *   { memoize: autotrackMemoize }
 47 |  * )
 48 |  * ```
 49 |  *
 50 |  * @example
 51 |  * <caption>Using `createSelectorCreator`</caption>
 52 |  * ```ts
 53 |  * import { unstable_autotrackMemoize as autotrackMemoize, createSelectorCreator } from 'reselect'
 54 |  *
 55 |  * const createSelectorAutotrack = createSelectorCreator({ memoize: autotrackMemoize })
 56 |  *
 57 |  * const selectTodoIds = createSelectorAutotrack(
 58 |  *   [(state: RootState) => state.todos],
 59 |  *   (todos) => todos.map(todo => todo.id)
 60 |  * )
 61 |  * ```
 62 |  *
 63 |  * @template Func - The type of the function that is memoized.
 64 |  *
 65 |  * @see {@link https://reselect.js.org/api/unstable_autotrackMemoize autotrackMemoize}
 66 |  *
 67 |  * @since 5.0.0
 68 |  * @public
 69 |  * @experimental
 70 |  */
 71 | export function autotrackMemoize<Func extends AnyFunction>(func: Func) {
 72 |   // we reference arguments instead of spreading them for performance reasons
 73 | 
 74 |   const node: Node<Record<string, unknown>> = createNode(
 75 |     [] as unknown as Record<string, unknown>
 76 |   )
 77 | 
 78 |   let lastArgs: IArguments | null = null
 79 | 
 80 |   const shallowEqual = createCacheKeyComparator(referenceEqualityCheck)
 81 | 
 82 |   const cache = createCache(() => {
 83 |     const res = func.apply(null, node.proxy as unknown as any[])
 84 |     return res
 85 |   })
 86 | 
 87 |   function memoized() {
 88 |     if (!shallowEqual(lastArgs, arguments)) {
 89 |       updateNode(node, arguments as unknown as Record<string, unknown>)
 90 |       lastArgs = arguments
 91 |     }
 92 |     return cache.value
 93 |   }
 94 | 
 95 |   memoized.clearCache = () => {
 96 |     return cache.clear()
 97 |   }
 98 | 
 99 |   return memoized as Func & Simplify<DefaultMemoizeFields>
100 | }
101 | 


--------------------------------------------------------------------------------
/src/autotrackMemoize/autotracking.ts:
--------------------------------------------------------------------------------
  1 | // Original autotracking implementation source:
  2 | // - https://gist.github.com/pzuraq/79bf862e0f8cd9521b79c4b6eccdc4f9
  3 | // Additional references:
  4 | // - https://www.pzuraq.com/blog/how-autotracking-works
  5 | // - https://v5.chriskrycho.com/journal/autotracking-elegant-dx-via-cutting-edge-cs/
  6 | import type { EqualityFn } from '../types'
  7 | import { assertIsFunction } from '../utils'
  8 | 
  9 | // The global revision clock. Every time state changes, the clock increments.
 10 | export let $REVISION = 0
 11 | 
 12 | // The current dependency tracker. Whenever we compute a cache, we create a Set
 13 | // to track any dependencies that are used while computing. If no cache is
 14 | // computing, then the tracker is null.
 15 | let CURRENT_TRACKER: Set<Cell<any> | TrackingCache> | null = null
 16 | 
 17 | // Storage represents a root value in the system - the actual state of our app.
 18 | export class Cell<T> {
 19 |   revision = $REVISION
 20 | 
 21 |   _value: T
 22 |   _lastValue: T
 23 |   _isEqual: EqualityFn = tripleEq
 24 | 
 25 |   constructor(initialValue: T, isEqual: EqualityFn = tripleEq) {
 26 |     this._value = this._lastValue = initialValue
 27 |     this._isEqual = isEqual
 28 |   }
 29 | 
 30 |   // Whenever a storage value is read, it'll add itself to the current tracker if
 31 |   // one exists, entangling its state with that cache.
 32 |   get value() {
 33 |     CURRENT_TRACKER?.add(this)
 34 | 
 35 |     return this._value
 36 |   }
 37 | 
 38 |   // Whenever a storage value is updated, we bump the global revision clock,
 39 |   // assign the revision for this storage to the new value, _and_ we schedule a
 40 |   // rerender. This is important, and it's what makes autotracking  _pull_
 41 |   // based. We don't actively tell the caches which depend on the storage that
 42 |   // anything has happened. Instead, we recompute the caches when needed.
 43 |   set value(newValue) {
 44 |     if (this.value === newValue) return
 45 | 
 46 |     this._value = newValue
 47 |     this.revision = ++$REVISION
 48 |   }
 49 | }
 50 | 
 51 | function tripleEq(a: unknown, b: unknown) {
 52 |   return a === b
 53 | }
 54 | 
 55 | // Caches represent derived state in the system. They are ultimately functions
 56 | // that are memoized based on what state they use to produce their output,
 57 | // meaning they will only rerun IFF a storage value that could affect the output
 58 | // has changed. Otherwise, they'll return the cached value.
 59 | export class TrackingCache {
 60 |   _cachedValue: any
 61 |   _cachedRevision = -1
 62 |   _deps: any[] = []
 63 |   hits = 0
 64 | 
 65 |   fn: () => any
 66 | 
 67 |   constructor(fn: () => any) {
 68 |     this.fn = fn
 69 |   }
 70 | 
 71 |   clear() {
 72 |     this._cachedValue = undefined
 73 |     this._cachedRevision = -1
 74 |     this._deps = []
 75 |     this.hits = 0
 76 |   }
 77 | 
 78 |   get value() {
 79 |     // When getting the value for a Cache, first we check all the dependencies of
 80 |     // the cache to see what their current revision is. If the current revision is
 81 |     // greater than the cached revision, then something has changed.
 82 |     if (this.revision > this._cachedRevision) {
 83 |       const { fn } = this
 84 | 
 85 |       // We create a new dependency tracker for this cache. As the cache runs
 86 |       // its function, any Storage or Cache instances which are used while
 87 |       // computing will be added to this tracker. In the end, it will be the
 88 |       // full list of dependencies that this Cache depends on.
 89 |       const currentTracker = new Set<Cell<any>>()
 90 |       const prevTracker = CURRENT_TRACKER
 91 | 
 92 |       CURRENT_TRACKER = currentTracker
 93 | 
 94 |       // try {
 95 |       this._cachedValue = fn()
 96 |       // } finally {
 97 |       CURRENT_TRACKER = prevTracker
 98 |       this.hits++
 99 |       this._deps = Array.from(currentTracker)
100 | 
101 |       // Set the cached revision. This is the current clock count of all the
102 |       // dependencies. If any dependency changes, this number will be less
103 |       // than the new revision.
104 |       this._cachedRevision = this.revision
105 |       // }
106 |     }
107 | 
108 |     // If there is a current tracker, it means another Cache is computing and
109 |     // using this one, so we add this one to the tracker.
110 |     CURRENT_TRACKER?.add(this)
111 | 
112 |     // Always return the cached value.
113 |     return this._cachedValue
114 |   }
115 | 
116 |   get revision() {
117 |     // The current revision is the max of all the dependencies' revisions.
118 |     return Math.max(...this._deps.map(d => d.revision), 0)
119 |   }
120 | }
121 | 
122 | export function getValue<T>(cell: Cell<T>): T {
123 |   if (!(cell instanceof Cell)) {
124 |     console.warn('Not a valid cell! ', cell)
125 |   }
126 | 
127 |   return cell.value
128 | }
129 | 
130 | type CellValue<T extends Cell<unknown>> = T extends Cell<infer U> ? U : never
131 | 
132 | export function setValue<T extends Cell<unknown>>(
133 |   storage: T,
134 |   value: CellValue<T>
135 | ): void {
136 |   if (!(storage instanceof Cell)) {
137 |     throw new TypeError(
138 |       'setValue must be passed a tracked store created with `createStorage`.'
139 |     )
140 |   }
141 | 
142 |   storage.value = storage._lastValue = value
143 | }
144 | 
145 | export function createCell<T = unknown>(
146 |   initialValue: T,
147 |   isEqual: EqualityFn = tripleEq
148 | ): Cell<T> {
149 |   return new Cell(initialValue, isEqual)
150 | }
151 | 
152 | export function createCache<T = unknown>(fn: () => T): TrackingCache {
153 |   assertIsFunction(
154 |     fn,
155 |     'the first parameter to `createCache` must be a function'
156 |   )
157 | 
158 |   return new TrackingCache(fn)
159 | }
160 | 


--------------------------------------------------------------------------------
/src/autotrackMemoize/proxy.ts:
--------------------------------------------------------------------------------
  1 | // Original source:
  2 | // - https://github.com/simonihmig/tracked-redux/blob/master/packages/tracked-redux/src/-private/proxy.ts
  3 | 
  4 | import type { Node, Tag } from './tracking'
  5 | import {
  6 |   consumeCollection,
  7 |   consumeTag,
  8 |   createTag,
  9 |   dirtyCollection,
 10 |   dirtyTag
 11 | } from './tracking'
 12 | 
 13 | export const REDUX_PROXY_LABEL = /* @__PURE__ */ Symbol()
 14 | 
 15 | let nextId = 0
 16 | 
 17 | const proto = /* @__PURE__ */ Object.getPrototypeOf({})
 18 | 
 19 | class ObjectTreeNode<T extends Record<string, unknown>> implements Node<T> {
 20 |   proxy: T = new Proxy(this, objectProxyHandler) as unknown as T
 21 |   tag = createTag()
 22 |   tags = {} as Record<string, Tag>
 23 |   children = {} as Record<string, Node>
 24 |   collectionTag = null
 25 |   id = nextId++
 26 | 
 27 |   constructor(public value: T) {
 28 |     this.value = value
 29 |     this.tag.value = value
 30 |   }
 31 | }
 32 | 
 33 | const objectProxyHandler = {
 34 |   get(node: Node, key: string | symbol): unknown {
 35 |     function calculateResult() {
 36 |       const { value } = node
 37 | 
 38 |       const childValue = Reflect.get(value, key)
 39 | 
 40 |       if (typeof key === 'symbol') {
 41 |         return childValue
 42 |       }
 43 | 
 44 |       if (key in proto) {
 45 |         return childValue
 46 |       }
 47 | 
 48 |       if (typeof childValue === 'object' && childValue !== null) {
 49 |         let childNode = node.children[key]
 50 | 
 51 |         if (childNode === undefined) {
 52 |           childNode = node.children[key] = createNode(childValue)
 53 |         }
 54 | 
 55 |         if (childNode.tag) {
 56 |           consumeTag(childNode.tag)
 57 |         }
 58 | 
 59 |         return childNode.proxy
 60 |       } else {
 61 |         let tag = node.tags[key]
 62 | 
 63 |         if (tag === undefined) {
 64 |           tag = node.tags[key] = createTag()
 65 |           tag.value = childValue
 66 |         }
 67 | 
 68 |         consumeTag(tag)
 69 | 
 70 |         return childValue
 71 |       }
 72 |     }
 73 |     const res = calculateResult()
 74 |     return res
 75 |   },
 76 | 
 77 |   ownKeys(node: Node): ArrayLike<string | symbol> {
 78 |     consumeCollection(node)
 79 |     return Reflect.ownKeys(node.value)
 80 |   },
 81 | 
 82 |   getOwnPropertyDescriptor(
 83 |     node: Node,
 84 |     prop: string | symbol
 85 |   ): PropertyDescriptor | undefined {
 86 |     return Reflect.getOwnPropertyDescriptor(node.value, prop)
 87 |   },
 88 | 
 89 |   has(node: Node, prop: string | symbol): boolean {
 90 |     return Reflect.has(node.value, prop)
 91 |   }
 92 | }
 93 | 
 94 | class ArrayTreeNode<T extends Array<unknown>> implements Node<T> {
 95 |   proxy: T = new Proxy([this], arrayProxyHandler) as unknown as T
 96 |   tag = createTag()
 97 |   tags = {}
 98 |   children = {}
 99 |   collectionTag = null
100 |   id = nextId++
101 | 
102 |   constructor(public value: T) {
103 |     this.value = value
104 |     this.tag.value = value
105 |   }
106 | }
107 | 
108 | const arrayProxyHandler = {
109 |   get([node]: [Node], key: string | symbol): unknown {
110 |     if (key === 'length') {
111 |       consumeCollection(node)
112 |     }
113 | 
114 |     return objectProxyHandler.get(node, key)
115 |   },
116 | 
117 |   ownKeys([node]: [Node]): ArrayLike<string | symbol> {
118 |     return objectProxyHandler.ownKeys(node)
119 |   },
120 | 
121 |   getOwnPropertyDescriptor(
122 |     [node]: [Node],
123 |     prop: string | symbol
124 |   ): PropertyDescriptor | undefined {
125 |     return objectProxyHandler.getOwnPropertyDescriptor(node, prop)
126 |   },
127 | 
128 |   has([node]: [Node], prop: string | symbol): boolean {
129 |     return objectProxyHandler.has(node, prop)
130 |   }
131 | }
132 | 
133 | export function createNode<T extends Array<unknown> | Record<string, unknown>>(
134 |   value: T
135 | ): Node<T> {
136 |   if (Array.isArray(value)) {
137 |     return new ArrayTreeNode(value)
138 |   }
139 | 
140 |   return new ObjectTreeNode(value) as Node<T>
141 | }
142 | 
143 | const keysMap = new WeakMap<
144 |   Array<unknown> | Record<string, unknown>,
145 |   Set<string>
146 | >()
147 | 
148 | export function updateNode<T extends Array<unknown> | Record<string, unknown>>(
149 |   node: Node<T>,
150 |   newValue: T
151 | ): void {
152 |   const { value, tags, children } = node
153 | 
154 |   node.value = newValue
155 | 
156 |   if (
157 |     Array.isArray(value) &&
158 |     Array.isArray(newValue) &&
159 |     value.length !== newValue.length
160 |   ) {
161 |     dirtyCollection(node)
162 |   } else {
163 |     if (value !== newValue) {
164 |       let oldKeysSize = 0
165 |       let newKeysSize = 0
166 |       let anyKeysAdded = false
167 | 
168 |       for (const _key in value) {
169 |         oldKeysSize++
170 |       }
171 | 
172 |       for (const key in newValue) {
173 |         newKeysSize++
174 |         if (!(key in value)) {
175 |           anyKeysAdded = true
176 |           break
177 |         }
178 |       }
179 | 
180 |       const isDifferent = anyKeysAdded || oldKeysSize !== newKeysSize
181 | 
182 |       if (isDifferent) {
183 |         dirtyCollection(node)
184 |       }
185 |     }
186 |   }
187 | 
188 |   for (const key in tags) {
189 |     const childValue = (value as Record<string, unknown>)[key]
190 |     const newChildValue = (newValue as Record<string, unknown>)[key]
191 | 
192 |     if (childValue !== newChildValue) {
193 |       dirtyCollection(node)
194 |       dirtyTag(tags[key], newChildValue)
195 |     }
196 | 
197 |     if (typeof newChildValue === 'object' && newChildValue !== null) {
198 |       delete tags[key]
199 |     }
200 |   }
201 | 
202 |   for (const key in children) {
203 |     const childNode = children[key]
204 |     const newChildValue = (newValue as Record<string, unknown>)[key]
205 | 
206 |     const childValue = childNode.value
207 | 
208 |     if (childValue === newChildValue) {
209 |       continue
210 |     } else if (typeof newChildValue === 'object' && newChildValue !== null) {
211 |       updateNode(childNode, newChildValue as Record<string, unknown>)
212 |     } else {
213 |       deleteNode(childNode)
214 |       delete children[key]
215 |     }
216 |   }
217 | }
218 | 
219 | function deleteNode(node: Node): void {
220 |   if (node.tag) {
221 |     dirtyTag(node.tag, null)
222 |   }
223 |   dirtyCollection(node)
224 |   for (const key in node.tags) {
225 |     dirtyTag(node.tags[key], null)
226 |   }
227 |   for (const key in node.children) {
228 |     deleteNode(node.children[key])
229 |   }
230 | }
231 | 


--------------------------------------------------------------------------------
/src/autotrackMemoize/tracking.ts:
--------------------------------------------------------------------------------
 1 | import type { Cell } from './autotracking'
 2 | import {
 3 |   getValue as consumeTag,
 4 |   createCell as createStorage,
 5 |   setValue
 6 | } from './autotracking'
 7 | 
 8 | export type Tag = Cell<unknown>
 9 | 
10 | const neverEq = (a: any, b: any): boolean => false
11 | 
12 | export function createTag(): Tag {
13 |   return createStorage(null, neverEq)
14 | }
15 | export { consumeTag }
16 | export function dirtyTag(tag: Tag, value: any): void {
17 |   setValue(tag, value)
18 | }
19 | 
20 | export interface Node<
21 |   T extends Array<unknown> | Record<string, unknown> =
22 |     | Array<unknown>
23 |     | Record<string, unknown>
24 | > {
25 |   collectionTag: Tag | null
26 |   tag: Tag | null
27 |   tags: Record<string, Tag>
28 |   children: Record<string, Node>
29 |   proxy: T
30 |   value: T
31 |   id: number
32 | }
33 | 
34 | export const consumeCollection = (node: Node): void => {
35 |   let tag = node.collectionTag
36 | 
37 |   if (tag === null) {
38 |     tag = node.collectionTag = createTag()
39 |   }
40 | 
41 |   consumeTag(tag)
42 | }
43 | 
44 | export const dirtyCollection = (node: Node): void => {
45 |   const tag = node.collectionTag
46 | 
47 |   if (tag !== null) {
48 |     dirtyTag(tag, null)
49 |   }
50 | }
51 | 


--------------------------------------------------------------------------------
/src/autotrackMemoize/utils.ts:
--------------------------------------------------------------------------------
 1 | export function assert(
 2 |   condition: any,
 3 |   msg = 'Assertion failed!'
 4 | ): asserts condition {
 5 |   if (!condition) {
 6 |     console.error(msg)
 7 |     throw new Error(msg)
 8 |   }
 9 | }
10 | 


--------------------------------------------------------------------------------
/src/devModeChecks/identityFunctionCheck.ts:
--------------------------------------------------------------------------------
 1 | import type { AnyFunction } from '../types'
 2 | 
 3 | /**
 4 |  * Runs a check to determine if the given result function behaves as an
 5 |  * identity function. An identity function is one that returns its
 6 |  * input unchanged, for example, `x => x`. This check helps ensure
 7 |  * efficient memoization and prevent unnecessary re-renders by encouraging
 8 |  * proper use of transformation logic in result functions and
 9 |  * extraction logic in input selectors.
10 |  *
11 |  * @param resultFunc - The result function to be checked.
12 |  * @param inputSelectorsResults - The results of the input selectors.
13 |  * @param outputSelectorResult - The result of the output selector.
14 |  *
15 |  * @see {@link https://reselect.js.org/api/development-only-stability-checks#identityfunctioncheck `identityFunctionCheck`}
16 |  *
17 |  * @since 5.0.0
18 |  * @internal
19 |  */
20 | export const runIdentityFunctionCheck = (
21 |   resultFunc: AnyFunction,
22 |   inputSelectorsResults: unknown[],
23 |   outputSelectorResult: unknown
24 | ) => {
25 |   if (
26 |     inputSelectorsResults.length === 1 &&
27 |     inputSelectorsResults[0] === outputSelectorResult
28 |   ) {
29 |     let isInputSameAsOutput = false
30 |     try {
31 |       const emptyObject = {}
32 |       if (resultFunc(emptyObject) === emptyObject) isInputSameAsOutput = true
33 |     } catch {
34 |       // Do nothing
35 |     }
36 |     if (isInputSameAsOutput) {
37 |       let stack: string | undefined = undefined
38 |       try {
39 |         throw new Error()
40 |       } catch (e) {
41 |         // eslint-disable-next-line @typescript-eslint/no-extra-semi, no-extra-semi
42 |         ;({ stack } = e as Error)
43 |       }
44 |       console.warn(
45 |         'The result function returned its own inputs without modification. e.g' +
46 |           '\n`createSelector([state => state.todos], todos => todos)`' +
47 |           '\nThis could lead to inefficient memoization and unnecessary re-renders.' +
48 |           '\nEnsure transformation logic is in the result function, and extraction logic is in the input selectors.',
49 |         { stack }
50 |       )
51 |     }
52 |   }
53 | }
54 | 


--------------------------------------------------------------------------------
/src/devModeChecks/inputStabilityCheck.ts:
--------------------------------------------------------------------------------
 1 | import type { CreateSelectorOptions, UnknownMemoizer } from '../types'
 2 | 
 3 | /**
 4 |  * Runs a stability check to ensure the input selector results remain stable
 5 |  * when provided with the same arguments. This function is designed to detect
 6 |  * changes in the output of input selectors, which can impact the performance of memoized selectors.
 7 |  *
 8 |  * @param inputSelectorResultsObject - An object containing two arrays: `inputSelectorResults` and `inputSelectorResultsCopy`, representing the results of input selectors.
 9 |  * @param options - Options object consisting of a `memoize` function and a `memoizeOptions` object.
10 |  * @param inputSelectorArgs - List of arguments being passed to the input selectors.
11 |  *
12 |  * @see {@link https://reselect.js.org/api/development-only-stability-checks/#inputstabilitycheck `inputStabilityCheck`}
13 |  *
14 |  * @since 5.0.0
15 |  * @internal
16 |  */
17 | export const runInputStabilityCheck = (
18 |   inputSelectorResultsObject: {
19 |     inputSelectorResults: unknown[]
20 |     inputSelectorResultsCopy: unknown[]
21 |   },
22 |   options: Required<
23 |     Pick<
24 |       CreateSelectorOptions<UnknownMemoizer, UnknownMemoizer>,
25 |       'memoize' | 'memoizeOptions'
26 |     >
27 |   >,
28 |   inputSelectorArgs: unknown[] | IArguments
29 | ) => {
30 |   const { memoize, memoizeOptions } = options
31 |   const { inputSelectorResults, inputSelectorResultsCopy } =
32 |     inputSelectorResultsObject
33 |   const createAnEmptyObject = memoize(() => ({}), ...memoizeOptions)
34 |   // if the memoize method thinks the parameters are equal, these *should* be the same reference
35 |   const areInputSelectorResultsEqual =
36 |     createAnEmptyObject.apply(null, inputSelectorResults) ===
37 |     createAnEmptyObject.apply(null, inputSelectorResultsCopy)
38 |   if (!areInputSelectorResultsEqual) {
39 |     let stack: string | undefined = undefined
40 |     try {
41 |       throw new Error()
42 |     } catch (e) {
43 |       // eslint-disable-next-line @typescript-eslint/no-extra-semi, no-extra-semi
44 |       ;({ stack } = e as Error)
45 |     }
46 |     console.warn(
47 |       'An input selector returned a different result when passed same arguments.' +
48 |         '\nThis means your output selector will likely run more frequently than intended.' +
49 |         '\nAvoid returning a new reference inside your input selector, e.g.' +
50 |         '\n`createSelector([state => state.todos.map(todo => todo.id)], todoIds => todoIds.length)`',
51 |       {
52 |         arguments: inputSelectorArgs,
53 |         firstInputs: inputSelectorResults,
54 |         secondInputs: inputSelectorResultsCopy,
55 |         stack
56 |       }
57 |     )
58 |   }
59 | }
60 | 


--------------------------------------------------------------------------------
/src/devModeChecks/setGlobalDevModeChecks.ts:
--------------------------------------------------------------------------------
 1 | import type { DevModeChecks } from '../types'
 2 | 
 3 | /**
 4 |  * Global configuration for development mode checks. This specifies the default
 5 |  * frequency at which each development mode check should be performed.
 6 |  *
 7 |  * @since 5.0.0
 8 |  * @internal
 9 |  */
10 | export const globalDevModeChecks: DevModeChecks = {
11 |   inputStabilityCheck: 'once',
12 |   identityFunctionCheck: 'once'
13 | }
14 | 
15 | /**
16 |  * Overrides the development mode checks settings for all selectors.
17 |  *
18 |  * Reselect performs additional checks in development mode to help identify and
19 |  * warn about potential issues in selector behavior. This function allows you to
20 |  * customize the behavior of these checks across all selectors in your application.
21 |  *
22 |  * **Note**: This setting can still be overridden per selector inside `createSelector`'s `options` object.
23 |  * See {@link https://github.com/reduxjs/reselect#2-per-selector-by-passing-an-identityfunctioncheck-option-directly-to-createselector per-selector-configuration}
24 |  * and {@linkcode CreateSelectorOptions.identityFunctionCheck identityFunctionCheck} for more details.
25 |  *
26 |  * _The development mode checks do not run in production builds._
27 |  *
28 |  * @param devModeChecks - An object specifying the desired settings for development mode checks. You can provide partial overrides. Unspecified settings will retain their current values.
29 |  *
30 |  * @example
31 |  * ```ts
32 |  * import { setGlobalDevModeChecks } from 'reselect'
33 |  * import { DevModeChecks } from '../types'
34 |  *
35 |  * // Run only the first time the selector is called. (default)
36 |  * setGlobalDevModeChecks({ inputStabilityCheck: 'once' })
37 |  *
38 |  * // Run every time the selector is called.
39 |  * setGlobalDevModeChecks({ inputStabilityCheck: 'always' })
40 |  *
41 |  * // Never run the input stability check.
42 |  * setGlobalDevModeChecks({ inputStabilityCheck: 'never' })
43 |  *
44 |  * // Run only the first time the selector is called. (default)
45 |  * setGlobalDevModeChecks({ identityFunctionCheck: 'once' })
46 |  *
47 |  * // Run every time the selector is called.
48 |  * setGlobalDevModeChecks({ identityFunctionCheck: 'always' })
49 |  *
50 |  * // Never run the identity function check.
51 |  * setGlobalDevModeChecks({ identityFunctionCheck: 'never' })
52 |  * ```
53 |  * @see {@link https://reselect.js.org/api/development-only-stability-checks Development-Only Stability Checks}
54 |  * @see {@link https://reselect.js.org/api/development-only-stability-checks#1-globally-through-setglobaldevmodechecks global-configuration}
55 |  *
56 |  * @since 5.0.0
57 |  * @public
58 |  */
59 | export const setGlobalDevModeChecks = (
60 |   devModeChecks: Partial<DevModeChecks>
61 | ) => {
62 |   Object.assign(globalDevModeChecks, devModeChecks)
63 | }
64 | 


--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------
 1 | export { autotrackMemoize as unstable_autotrackMemoize } from './autotrackMemoize/autotrackMemoize'
 2 | export { createSelector, createSelectorCreator } from './createSelectorCreator'
 3 | export type { CreateSelectorFunction } from './createSelectorCreator'
 4 | export { createStructuredSelector } from './createStructuredSelector'
 5 | export type {
 6 |   RootStateSelectors,
 7 |   SelectorResultsMap,
 8 |   SelectorsObject,
 9 |   StructuredSelectorCreator,
10 |   TypedStructuredSelectorCreator
11 | } from './createStructuredSelector'
12 | export { setGlobalDevModeChecks } from './devModeChecks/setGlobalDevModeChecks'
13 | export { lruMemoize, referenceEqualityCheck } from './lruMemoize'
14 | export type { LruMemoizeOptions } from './lruMemoize'
15 | export type {
16 |   Combiner,
17 |   CreateSelectorOptions,
18 |   DefaultMemoizeFields,
19 |   DevModeCheckFrequency,
20 |   DevModeChecks,
21 |   DevModeChecksExecutionInfo,
22 |   EqualityFn,
23 |   ExtractMemoizerFields,
24 |   GetParamsFromSelectors,
25 |   GetStateFromSelectors,
26 |   MemoizeOptionsFromParameters,
27 |   OutputSelector,
28 |   OutputSelectorFields,
29 |   OverrideMemoizeOptions,
30 |   Selector,
31 |   SelectorArray,
32 |   SelectorResultArray,
33 |   UnknownMemoizer
34 | } from './types'
35 | export { weakMapMemoize } from './weakMapMemoize'
36 | export type { WeakMapMemoizeOptions } from './weakMapMemoize'
37 | 


--------------------------------------------------------------------------------
/src/lruMemoize.ts:
--------------------------------------------------------------------------------
  1 | import type {
  2 |   AnyFunction,
  3 |   DefaultMemoizeFields,
  4 |   EqualityFn,
  5 |   Simplify
  6 | } from './types'
  7 | 
  8 | import type { NOT_FOUND_TYPE } from './utils'
  9 | import { NOT_FOUND } from './utils'
 10 | 
 11 | // Cache implementation based on Erik Rasmussen's `lru-memoize`:
 12 | // https://github.com/erikras/lru-memoize
 13 | 
 14 | interface Entry {
 15 |   key: unknown
 16 |   value: unknown
 17 | }
 18 | 
 19 | interface Cache {
 20 |   get(key: unknown): unknown | NOT_FOUND_TYPE
 21 |   put(key: unknown, value: unknown): void
 22 |   getEntries(): Entry[]
 23 |   clear(): void
 24 | }
 25 | 
 26 | function createSingletonCache(equals: EqualityFn): Cache {
 27 |   let entry: Entry | undefined
 28 |   return {
 29 |     get(key: unknown) {
 30 |       if (entry && equals(entry.key, key)) {
 31 |         return entry.value
 32 |       }
 33 | 
 34 |       return NOT_FOUND
 35 |     },
 36 | 
 37 |     put(key: unknown, value: unknown) {
 38 |       entry = { key, value }
 39 |     },
 40 | 
 41 |     getEntries() {
 42 |       return entry ? [entry] : []
 43 |     },
 44 | 
 45 |     clear() {
 46 |       entry = undefined
 47 |     }
 48 |   }
 49 | }
 50 | 
 51 | function createLruCache(maxSize: number, equals: EqualityFn): Cache {
 52 |   let entries: Entry[] = []
 53 | 
 54 |   function get(key: unknown) {
 55 |     const cacheIndex = entries.findIndex(entry => equals(key, entry.key))
 56 | 
 57 |     // We found a cached entry
 58 |     if (cacheIndex > -1) {
 59 |       const entry = entries[cacheIndex]
 60 | 
 61 |       // Cached entry not at top of cache, move it to the top
 62 |       if (cacheIndex > 0) {
 63 |         entries.splice(cacheIndex, 1)
 64 |         entries.unshift(entry)
 65 |       }
 66 | 
 67 |       return entry.value
 68 |     }
 69 | 
 70 |     // No entry found in cache, return sentinel
 71 |     return NOT_FOUND
 72 |   }
 73 | 
 74 |   function put(key: unknown, value: unknown) {
 75 |     if (get(key) === NOT_FOUND) {
 76 |       // TODO Is unshift slow?
 77 |       entries.unshift({ key, value })
 78 |       if (entries.length > maxSize) {
 79 |         entries.pop()
 80 |       }
 81 |     }
 82 |   }
 83 | 
 84 |   function getEntries() {
 85 |     return entries
 86 |   }
 87 | 
 88 |   function clear() {
 89 |     entries = []
 90 |   }
 91 | 
 92 |   return { get, put, getEntries, clear }
 93 | }
 94 | 
 95 | /**
 96 |  * Runs a simple reference equality check.
 97 |  * What {@linkcode lruMemoize lruMemoize} uses by default.
 98 |  *
 99 |  * **Note**: This function was previously known as `defaultEqualityCheck`.
100 |  *
101 |  * @public
102 |  */
103 | export const referenceEqualityCheck: EqualityFn = (a, b) => a === b
104 | 
105 | export function createCacheKeyComparator(equalityCheck: EqualityFn) {
106 |   return function areArgumentsShallowlyEqual(
107 |     prev: unknown[] | IArguments | null,
108 |     next: unknown[] | IArguments | null
109 |   ): boolean {
110 |     if (prev === null || next === null || prev.length !== next.length) {
111 |       return false
112 |     }
113 | 
114 |     // Do this in a for loop (and not a `forEach` or an `every`) so we can determine equality as fast as possible.
115 |     const { length } = prev
116 |     for (let i = 0; i < length; i++) {
117 |       if (!equalityCheck(prev[i], next[i])) {
118 |         return false
119 |       }
120 |     }
121 | 
122 |     return true
123 |   }
124 | }
125 | 
126 | /**
127 |  * Options for configuring the behavior of a function memoized with
128 |  * LRU (Least Recently Used) caching.
129 |  *
130 |  * @template Result - The type of the return value of the memoized function.
131 |  *
132 |  * @public
133 |  */
134 | export interface LruMemoizeOptions<Result = any> {
135 |   /**
136 |    * Function used to compare the individual arguments of the
137 |    * provided calculation function.
138 |    *
139 |    * @default referenceEqualityCheck
140 |    */
141 |   equalityCheck?: EqualityFn
142 | 
143 |   /**
144 |    * If provided, used to compare a newly generated output value against
145 |    * previous values in the cache. If a match is found,
146 |    * the old value is returned. This addresses the common
147 |    * ```ts
148 |    * todos.map(todo => todo.id)
149 |    * ```
150 |    * use case, where an update to another field in the original data causes
151 |    * a recalculation due to changed references, but the output is still
152 |    * effectively the same.
153 |    *
154 |    * @since 4.1.0
155 |    */
156 |   resultEqualityCheck?: EqualityFn<Result>
157 | 
158 |   /**
159 |    * The maximum size of the cache used by the selector.
160 |    * A size greater than 1 means the selector will use an
161 |    * LRU (Least Recently Used) cache, allowing for the caching of multiple
162 |    * results based on different sets of arguments.
163 |    *
164 |    * @default 1
165 |    */
166 |   maxSize?: number
167 | }
168 | 
169 | /**
170 |  * Creates a memoized version of a function with an optional
171 |  * LRU (Least Recently Used) cache. The memoized function uses a cache to
172 |  * store computed values. Depending on the `maxSize` option, it will use
173 |  * either a singleton cache (for a single entry) or an
174 |  * LRU cache (for multiple entries).
175 |  *
176 |  * **Note**: This function was previously known as `defaultMemoize`.
177 |  *
178 |  * @param func - The function to be memoized.
179 |  * @param equalityCheckOrOptions - Either an equality check function or an options object.
180 |  * @returns A memoized function with a `.clearCache()` method attached.
181 |  *
182 |  * @template Func - The type of the function that is memoized.
183 |  *
184 |  * @see {@link https://reselect.js.org/api/lruMemoize `lruMemoize`}
185 |  *
186 |  * @public
187 |  */
188 | export function lruMemoize<Func extends AnyFunction>(
189 |   func: Func,
190 |   equalityCheckOrOptions?: EqualityFn | LruMemoizeOptions<ReturnType<Func>>
191 | ) {
192 |   const providedOptions =
193 |     typeof equalityCheckOrOptions === 'object'
194 |       ? equalityCheckOrOptions
195 |       : { equalityCheck: equalityCheckOrOptions }
196 | 
197 |   const {
198 |     equalityCheck = referenceEqualityCheck,
199 |     maxSize = 1,
200 |     resultEqualityCheck
201 |   } = providedOptions
202 | 
203 |   const comparator = createCacheKeyComparator(equalityCheck)
204 | 
205 |   let resultsCount = 0
206 | 
207 |   const cache =
208 |     maxSize <= 1
209 |       ? createSingletonCache(comparator)
210 |       : createLruCache(maxSize, comparator)
211 | 
212 |   function memoized() {
213 |     let value = cache.get(arguments) as ReturnType<Func>
214 |     if (value === NOT_FOUND) {
215 |       // apply arguments instead of spreading for performance.
216 |       // @ts-ignore
217 |       value = func.apply(null, arguments) as ReturnType<Func>
218 |       resultsCount++
219 | 
220 |       if (resultEqualityCheck) {
221 |         const entries = cache.getEntries()
222 |         const matchingEntry = entries.find(entry =>
223 |           resultEqualityCheck(entry.value as ReturnType<Func>, value)
224 |         )
225 | 
226 |         if (matchingEntry) {
227 |           value = matchingEntry.value as ReturnType<Func>
228 |           resultsCount !== 0 && resultsCount--
229 |         }
230 |       }
231 | 
232 |       cache.put(arguments, value)
233 |     }
234 |     return value
235 |   }
236 | 
237 |   memoized.clearCache = () => {
238 |     cache.clear()
239 |     memoized.resetResultsCount()
240 |   }
241 | 
242 |   memoized.resultsCount = () => resultsCount
243 | 
244 |   memoized.resetResultsCount = () => {
245 |     resultsCount = 0
246 |   }
247 | 
248 |   return memoized as Func & Simplify<DefaultMemoizeFields>
249 | }
250 | 


--------------------------------------------------------------------------------
/src/utils.ts:
--------------------------------------------------------------------------------
  1 | import { runIdentityFunctionCheck } from './devModeChecks/identityFunctionCheck'
  2 | import { runInputStabilityCheck } from './devModeChecks/inputStabilityCheck'
  3 | import { globalDevModeChecks } from './devModeChecks/setGlobalDevModeChecks'
  4 | // eslint-disable-next-line @typescript-eslint/consistent-type-imports
  5 | import type {
  6 |   DevModeChecks,
  7 |   Selector,
  8 |   SelectorArray,
  9 |   DevModeChecksExecutionInfo
 10 | } from './types'
 11 | 
 12 | export const NOT_FOUND = /* @__PURE__ */ Symbol('NOT_FOUND')
 13 | export type NOT_FOUND_TYPE = typeof NOT_FOUND
 14 | 
 15 | /**
 16 |  * Assert that the provided value is a function. If the assertion fails,
 17 |  * a `TypeError` is thrown with an optional custom error message.
 18 |  *
 19 |  * @param func - The value to be checked.
 20 |  * @param  errorMessage - An optional custom error message to use if the assertion fails.
 21 |  * @throws A `TypeError` if the assertion fails.
 22 |  */
 23 | export function assertIsFunction<FunctionType extends Function>(
 24 |   func: unknown,
 25 |   errorMessage = `expected a function, instead received ${typeof func}`
 26 | ): asserts func is FunctionType {
 27 |   if (typeof func !== 'function') {
 28 |     throw new TypeError(errorMessage)
 29 |   }
 30 | }
 31 | 
 32 | /**
 33 |  * Assert that the provided value is an object. If the assertion fails,
 34 |  * a `TypeError` is thrown with an optional custom error message.
 35 |  *
 36 |  * @param object - The value to be checked.
 37 |  * @param  errorMessage - An optional custom error message to use if the assertion fails.
 38 |  * @throws A `TypeError` if the assertion fails.
 39 |  */
 40 | export function assertIsObject<ObjectType extends Record<string, unknown>>(
 41 |   object: unknown,
 42 |   errorMessage = `expected an object, instead received ${typeof object}`
 43 | ): asserts object is ObjectType {
 44 |   if (typeof object !== 'object') {
 45 |     throw new TypeError(errorMessage)
 46 |   }
 47 | }
 48 | 
 49 | /**
 50 |  * Assert that the provided array is an array of functions. If the assertion fails,
 51 |  * a `TypeError` is thrown with an optional custom error message.
 52 |  *
 53 |  * @param array - The array to be checked.
 54 |  * @param  errorMessage - An optional custom error message to use if the assertion fails.
 55 |  * @throws A `TypeError` if the assertion fails.
 56 |  */
 57 | export function assertIsArrayOfFunctions<FunctionType extends Function>(
 58 |   array: unknown[],
 59 |   errorMessage = `expected all items to be functions, instead received the following types: `
 60 | ): asserts array is FunctionType[] {
 61 |   if (
 62 |     !array.every((item): item is FunctionType => typeof item === 'function')
 63 |   ) {
 64 |     const itemTypes = array
 65 |       .map(item =>
 66 |         typeof item === 'function'
 67 |           ? `function ${item.name || 'unnamed'}()`
 68 |           : typeof item
 69 |       )
 70 |       .join(', ')
 71 |     throw new TypeError(`${errorMessage}[${itemTypes}]`)
 72 |   }
 73 | }
 74 | 
 75 | /**
 76 |  * Ensure that the input is an array. If it's already an array, it's returned as is.
 77 |  * If it's not an array, it will be wrapped in a new array.
 78 |  *
 79 |  * @param item - The item to be checked.
 80 |  * @returns An array containing the input item. If the input is already an array, it's returned without modification.
 81 |  */
 82 | export const ensureIsArray = (item: unknown) => {
 83 |   return Array.isArray(item) ? item : [item]
 84 | }
 85 | 
 86 | /**
 87 |  * Extracts the "dependencies" / "input selectors" from the arguments of `createSelector`.
 88 |  *
 89 |  * @param createSelectorArgs - Arguments passed to `createSelector` as an array.
 90 |  * @returns An array of "input selectors" / "dependencies".
 91 |  * @throws A `TypeError` if any of the input selectors is not function.
 92 |  */
 93 | export function getDependencies(createSelectorArgs: unknown[]) {
 94 |   const dependencies = Array.isArray(createSelectorArgs[0])
 95 |     ? createSelectorArgs[0]
 96 |     : createSelectorArgs
 97 | 
 98 |   assertIsArrayOfFunctions<Selector>(
 99 |     dependencies,
100 |     `createSelector expects all input-selectors to be functions, but received the following types: `
101 |   )
102 | 
103 |   return dependencies as SelectorArray
104 | }
105 | 
106 | /**
107 |  * Runs each input selector and returns their collective results as an array.
108 |  *
109 |  * @param dependencies - An array of "dependencies" or "input selectors".
110 |  * @param inputSelectorArgs - An array of arguments being passed to the input selectors.
111 |  * @returns An array of input selector results.
112 |  */
113 | export function collectInputSelectorResults(
114 |   dependencies: SelectorArray,
115 |   inputSelectorArgs: unknown[] | IArguments
116 | ) {
117 |   const inputSelectorResults = []
118 |   const { length } = dependencies
119 |   for (let i = 0; i < length; i++) {
120 |     // @ts-ignore
121 |     // apply arguments instead of spreading and mutate a local list of params for performance.
122 |     inputSelectorResults.push(dependencies[i].apply(null, inputSelectorArgs))
123 |   }
124 |   return inputSelectorResults
125 | }
126 | 
127 | /**
128 |  * Retrieves execution information for development mode checks.
129 |  *
130 |  * @param devModeChecks - Custom Settings for development mode checks. These settings will override the global defaults.
131 |  * @param firstRun - Indicates whether it is the first time the selector has run.
132 |  * @returns  An object containing the execution information for each development mode check.
133 |  */
134 | export const getDevModeChecksExecutionInfo = (
135 |   firstRun: boolean,
136 |   devModeChecks: Partial<DevModeChecks>
137 | ) => {
138 |   const { identityFunctionCheck, inputStabilityCheck } = {
139 |     ...globalDevModeChecks,
140 |     ...devModeChecks
141 |   }
142 |   return {
143 |     identityFunctionCheck: {
144 |       shouldRun:
145 |         identityFunctionCheck === 'always' ||
146 |         (identityFunctionCheck === 'once' && firstRun),
147 |       run: runIdentityFunctionCheck
148 |     },
149 |     inputStabilityCheck: {
150 |       shouldRun:
151 |         inputStabilityCheck === 'always' ||
152 |         (inputStabilityCheck === 'once' && firstRun),
153 |       run: runInputStabilityCheck
154 |     }
155 |   } satisfies DevModeChecksExecutionInfo
156 | }
157 | 


--------------------------------------------------------------------------------
/src/versionedTypes/index.ts:
--------------------------------------------------------------------------------
1 | export type { MergeParameters } from './ts47-mergeParameters'
2 | 


--------------------------------------------------------------------------------
/src/versionedTypes/ts47-mergeParameters.ts:
--------------------------------------------------------------------------------
  1 | // This entire implementation courtesy of Anders Hjelsberg:
  2 | // https://github.com/microsoft/TypeScript/pull/50831#issuecomment-1253830522
  3 | 
  4 | import type { AnyFunction } from '../types'
  5 | 
  6 | /**
  7 |  * Represents the longest array within an array of arrays.
  8 |  *
  9 |  * @template ArrayOfTuples An array of arrays.
 10 |  *
 11 |  * @internal
 12 |  */
 13 | type LongestTuple<ArrayOfTuples extends readonly unknown[][]> =
 14 |   ArrayOfTuples extends [infer FirstArray extends unknown[]]
 15 |     ? FirstArray
 16 |     : ArrayOfTuples extends [
 17 |         infer FirstArray,
 18 |         ...infer RestArrays extends unknown[][]
 19 |       ]
 20 |     ? LongerOfTwo<FirstArray, LongestTuple<RestArrays>>
 21 |     : never
 22 | 
 23 | /**
 24 |  * Determines the longer of two array types.
 25 |  *
 26 |  * @template ArrayOne First array type.
 27 |  * @template ArrayTwo Second array type.
 28 |  *
 29 |  * @internal
 30 |  */
 31 | type LongerOfTwo<ArrayOne, ArrayTwo> = keyof ArrayTwo extends keyof ArrayOne
 32 |   ? ArrayOne
 33 |   : ArrayTwo
 34 | 
 35 | /**
 36 |  * Extracts the element at a specific index in an array.
 37 |  *
 38 |  * @template ArrayType The array type.
 39 |  * @template Index The index type.
 40 |  *
 41 |  * @internal
 42 |  */
 43 | type ElementAt<
 44 |   ArrayType extends unknown[],
 45 |   Index extends PropertyKey
 46 | > = Index extends keyof ArrayType ? ArrayType[Index] : unknown
 47 | 
 48 | /**
 49 |  * Maps each array in an array of arrays to its element at a given index.
 50 |  *
 51 |  * @template ArrayOfTuples An array of arrays.
 52 |  * @template Index The index to extract from each array.
 53 |  *
 54 |  * @internal
 55 |  */
 56 | type ElementsAtGivenIndex<
 57 |   ArrayOfTuples extends readonly unknown[][],
 58 |   Index extends PropertyKey
 59 | > = {
 60 |   [ArrayIndex in keyof ArrayOfTuples]: ElementAt<
 61 |     ArrayOfTuples[ArrayIndex],
 62 |     Index
 63 |   >
 64 | }
 65 | 
 66 | /**
 67 |  * Computes the intersection of all types in a tuple.
 68 |  *
 69 |  * @template Tuple A tuple of types.
 70 |  *
 71 |  * @internal
 72 |  */
 73 | type Intersect<Tuple extends readonly unknown[]> = Tuple extends []
 74 |   ? unknown
 75 |   : Tuple extends [infer Head, ...infer Tail]
 76 |   ? Head & Intersect<Tail>
 77 |   : Tuple[number]
 78 | 
 79 | /**
 80 |  * Merges a tuple of arrays into a single tuple, intersecting types at each index.
 81 |  *
 82 |  * @template ArrayOfTuples An array of tuples.
 83 |  * @template LongestArray The longest array in ArrayOfTuples.
 84 |  *
 85 |  * @internal
 86 |  */
 87 | type MergeTuples<
 88 |   ArrayOfTuples extends readonly unknown[][],
 89 |   LongestArray extends unknown[] = LongestTuple<ArrayOfTuples>
 90 | > = {
 91 |   [Index in keyof LongestArray]: Intersect<
 92 |     ElementsAtGivenIndex<ArrayOfTuples, Index>
 93 |   >
 94 | }
 95 | 
 96 | /**
 97 |  * Extracts the parameter types from a tuple of functions.
 98 |  *
 99 |  * @template FunctionsArray An array of function types.
100 |  *
101 |  * @internal
102 |  */
103 | type ExtractParameters<FunctionsArray extends readonly AnyFunction[]> = {
104 |   [Index in keyof FunctionsArray]: Parameters<FunctionsArray[Index]>
105 | }
106 | 
107 | /**
108 |  * Merges the parameters of a tuple of functions into a single tuple.
109 |  *
110 |  * @template FunctionsArray An array of function types.
111 |  *
112 |  * @internal
113 |  */
114 | export type MergeParameters<FunctionsArray extends readonly AnyFunction[]> =
115 |   '0' extends keyof FunctionsArray
116 |     ? MergeTuples<ExtractParameters<FunctionsArray>>
117 |     : Parameters<FunctionsArray[number]>
118 | 


--------------------------------------------------------------------------------
/test/autotrackMemoize.spec.ts:
--------------------------------------------------------------------------------
  1 | import {
  2 |   unstable_autotrackMemoize as autotrackMemoize,
  3 |   createSelectorCreator
  4 | } from 'reselect'
  5 | import { setEnvToProd } from './testUtils'
  6 | 
  7 | // Construct 1E6 states for perf test outside of the perf test so as to not change the execute time of the test function
  8 | const numOfStates = 1_000_000
  9 | interface StateA {
 10 |   a: number
 11 | }
 12 | 
 13 | interface StateAB {
 14 |   a: number
 15 |   b: number
 16 | }
 17 | 
 18 | interface StateSub {
 19 |   sub: {
 20 |     a: number
 21 |   }
 22 | }
 23 | 
 24 | const states: StateAB[] = []
 25 | 
 26 | for (let i = 0; i < numOfStates; i++) {
 27 |   states.push({ a: 1, b: 2 })
 28 | }
 29 | 
 30 | describe('Basic selector behavior with autotrack', () => {
 31 |   const createSelector = createSelectorCreator(autotrackMemoize)
 32 | 
 33 |   test('basic selector', () => {
 34 |     // console.log('Selector test')
 35 |     const selector = createSelector(
 36 |       (state: StateA) => state.a,
 37 |       a => a,
 38 |       { devModeChecks: { identityFunctionCheck: 'never' } }
 39 |     )
 40 |     const firstState = { a: 1 }
 41 |     const firstStateNewPointer = { a: 1 }
 42 |     const secondState = { a: 2 }
 43 | 
 44 |     expect(selector(firstState)).toBe(1)
 45 |     expect(selector(firstState)).toBe(1)
 46 |     expect(selector.recomputations()).toBe(1)
 47 |     expect(selector(firstStateNewPointer)).toBe(1)
 48 |     expect(selector.recomputations()).toBe(1)
 49 |     expect(selector(secondState)).toBe(2)
 50 |     expect(selector.recomputations()).toBe(2)
 51 |   })
 52 | 
 53 |   test("don't pass extra parameters to inputSelector when only called with the state", () => {
 54 |     const selector = createSelector(
 55 |       (...params: any[]) => params.length,
 56 |       a => a,
 57 |       { devModeChecks: { identityFunctionCheck: 'never' } }
 58 |     )
 59 |     expect(selector({})).toBe(1)
 60 |   })
 61 | 
 62 |   test('basic selector multiple keys', () => {
 63 |     const selector = createSelector(
 64 |       (state: StateAB) => state.a,
 65 |       (state: StateAB) => state.b,
 66 |       (a, b) => a + b
 67 |     )
 68 |     const state1 = { a: 1, b: 2 }
 69 |     expect(selector(state1)).toBe(3)
 70 |     expect(selector(state1)).toBe(3)
 71 |     expect(selector.recomputations()).toBe(1)
 72 |     const state2 = { a: 3, b: 2 }
 73 |     expect(selector(state2)).toBe(5)
 74 |     expect(selector(state2)).toBe(5)
 75 |     expect(selector.recomputations()).toBe(2)
 76 |   })
 77 | 
 78 |   test('basic selector invalid input selector', () => {
 79 |     expect(() =>
 80 |       createSelector(
 81 |         // @ts-ignore
 82 |         (state: StateAB) => state.a,
 83 |         function input2(state: StateAB) {
 84 |           return state.b
 85 |         },
 86 |         'not a function',
 87 |         (a: any, b: any) => a + b
 88 |       )
 89 |     ).toThrow(
 90 |       'createSelector expects all input-selectors to be functions, but received the following types: [function unnamed(), function input2(), string]'
 91 |     )
 92 | 
 93 |     expect(() =>
 94 |       // @ts-ignore
 95 |       createSelector((state: StateAB) => state.a, 'not a function')
 96 |     ).toThrow(
 97 |       'createSelector expects an output function after the inputs, but received: [string]'
 98 |     )
 99 |   })
100 | 
101 |   const isCoverage = process.env.COVERAGE
102 | 
103 |   // don't run performance tests for coverage
104 |   describe.skipIf(isCoverage)('performance checks', () => {
105 |     beforeEach(setEnvToProd)
106 | 
107 |     test('basic selector cache hit performance', () => {
108 |       const selector = createSelector(
109 |         (state: StateAB) => state.a,
110 |         (state: StateAB) => state.b,
111 |         (a, b) => a + b,
112 |         { devModeChecks: { identityFunctionCheck: 'never' } }
113 |       )
114 |       const state1 = { a: 1, b: 2 }
115 | 
116 |       const start = performance.now()
117 |       for (let i = 0; i < 1_000_000; i++) {
118 |         selector(state1)
119 |       }
120 |       const totalTime = performance.now() - start
121 | 
122 |       expect(selector(state1)).toBe(3)
123 |       expect(selector.recomputations()).toBe(1)
124 |       // Expected a million calls to a selector with the same arguments to take less than 2 seconds
125 |       expect(totalTime).toBeLessThan(2000)
126 |     })
127 | 
128 |     test('basic selector cache hit performance for state changes but shallowly equal selector args', () => {
129 |       const selector = createSelector(
130 |         (state: StateAB) => state.a,
131 |         (state: StateAB) => state.b,
132 |         (a, b) => a + b,
133 |         { devModeChecks: { identityFunctionCheck: 'never' } }
134 |       )
135 | 
136 |       const start = performance.now()
137 |       for (let i = 0; i < 1_000_000; i++) {
138 |         selector(states[i])
139 |       }
140 |       const totalTime = performance.now() - start
141 | 
142 |       expect(selector(states[0])).toBe(3)
143 |       expect(selector.recomputations()).toBe(1)
144 | 
145 |       // Expected a million calls to a selector with the same arguments to take less than 1 second
146 |       expect(totalTime).toBeLessThan(2000)
147 |     })
148 |   })
149 | 
150 |   test('memoized composite arguments', () => {
151 |     const selector = createSelector(
152 |       (state: StateSub) => state.sub,
153 |       sub => sub.a
154 |     )
155 |     const state1 = { sub: { a: 1 } }
156 |     expect(selector(state1)).toEqual(1)
157 |     expect(selector(state1)).toEqual(1)
158 |     expect(selector.recomputations()).toBe(1)
159 |     const state2 = { sub: { a: 2 } }
160 |     expect(selector(state2)).toEqual(2)
161 |     expect(selector.recomputations()).toBe(2)
162 |   })
163 | 
164 |   test('first argument can be an array', () => {
165 |     const selector = createSelector(
166 |       [state => state.a, state => state.b],
167 |       (a, b) => {
168 |         return a + b
169 |       }
170 |     )
171 |     expect(selector({ a: 1, b: 2 })).toBe(3)
172 |     expect(selector({ a: 1, b: 2 })).toBe(3)
173 |     expect(selector.recomputations()).toBe(1)
174 |     expect(selector({ a: 3, b: 2 })).toBe(5)
175 |     expect(selector.recomputations()).toBe(2)
176 |   })
177 | 
178 |   test('can accept props', () => {
179 |     let called = 0
180 |     const selector = createSelector(
181 |       (state: StateAB) => state.a,
182 |       (state: StateAB) => state.b,
183 |       (state: StateAB, props: { c: number }) => props.c,
184 |       (a, b, c) => {
185 |         called++
186 |         return a + b + c
187 |       }
188 |     )
189 |     expect(selector({ a: 1, b: 2 }, { c: 100 })).toBe(103)
190 |   })
191 | 
192 |   test('recomputes result after exception', () => {
193 |     let called = 0
194 |     const selector = createSelector(
195 |       (state: StateA) => state.a,
196 |       () => {
197 |         called++
198 |         throw Error('test error')
199 |       },
200 |       { devModeChecks: { identityFunctionCheck: 'never' } }
201 |     )
202 |     expect(() => selector({ a: 1 })).toThrow('test error')
203 |     expect(() => selector({ a: 1 })).toThrow('test error')
204 |     expect(called).toBe(2)
205 |   })
206 | 
207 |   test('memoizes previous result before exception', () => {
208 |     let called = 0
209 |     const selector = createSelector(
210 |       (state: StateA) => state.a,
211 |       a => {
212 |         called++
213 |         if (a > 1) throw Error('test error')
214 |         return a
215 |       },
216 |       { devModeChecks: { identityFunctionCheck: 'never' } }
217 |     )
218 |     const state1 = { a: 1 }
219 |     const state2 = { a: 2 }
220 |     expect(selector(state1)).toBe(1)
221 |     expect(() => selector(state2)).toThrow('test error')
222 |     expect(selector(state1)).toBe(1)
223 |     expect(called).toBe(2)
224 |   })
225 | })
226 | 


--------------------------------------------------------------------------------
/test/benchmarks/orderOfExecution.bench.ts:
--------------------------------------------------------------------------------
  1 | import type { OutputSelector, Selector } from 'reselect'
  2 | import { createSelector, lruMemoize } from 'reselect'
  3 | import type { Options } from 'tinybench'
  4 | import { bench } from 'vitest'
  5 | import type { RootState } from '../testUtils'
  6 | import {
  7 |   countRecomputations,
  8 |   expensiveComputation,
  9 |   logFunctionInfo,
 10 |   logSelectorRecomputations,
 11 |   resetSelector,
 12 |   runMultipleTimes,
 13 |   setFunctionNames,
 14 |   setupStore,
 15 |   toggleCompleted,
 16 |   toggleRead
 17 | } from '../testUtils'
 18 | 
 19 | describe('Less vs more computation in input selectors', () => {
 20 |   const store = setupStore()
 21 |   const runSelector = (selector: Selector) => {
 22 |     runMultipleTimes(selector, 100, store.getState())
 23 |   }
 24 |   const selectorLessInInput = createSelector(
 25 |     [(state: RootState) => state.todos],
 26 |     todos => {
 27 |       expensiveComputation()
 28 |       return todos.filter(todo => todo.completed)
 29 |     }
 30 |   )
 31 |   const selectorMoreInInput = createSelector(
 32 |     [
 33 |       (state: RootState) => {
 34 |         expensiveComputation()
 35 |         return state.todos
 36 |       }
 37 |     ],
 38 |     todos => todos.filter(todo => todo.completed)
 39 |   )
 40 | 
 41 |   const nonMemoized = countRecomputations((state: RootState) => {
 42 |     expensiveComputation()
 43 |     return state.todos.filter(todo => todo.completed)
 44 |   })
 45 |   const commonOptions: Options = {
 46 |     iterations: 10,
 47 |     time: 0
 48 |   }
 49 |   setFunctionNames({ selectorLessInInput, selectorMoreInInput, nonMemoized })
 50 |   const createOptions = <S extends OutputSelector>(
 51 |     selector: S,
 52 |     commonOptions: Options = {}
 53 |   ) => {
 54 |     const options: Options = {
 55 |       setup: (task, mode) => {
 56 |         if (mode === 'warmup') return
 57 |         task.opts = {
 58 |           beforeEach: () => {
 59 |             store.dispatch(toggleRead(1))
 60 |           },
 61 |           afterAll: () => {
 62 |             logSelectorRecomputations(selector)
 63 |           }
 64 |         }
 65 |       }
 66 |     }
 67 |     return { ...commonOptions, ...options }
 68 |   }
 69 |   bench(
 70 |     selectorLessInInput,
 71 |     () => {
 72 |       runSelector(selectorLessInInput)
 73 |     },
 74 |     createOptions(selectorLessInInput, commonOptions)
 75 |   )
 76 |   bench(
 77 |     selectorMoreInInput,
 78 |     () => {
 79 |       runSelector(selectorMoreInInput)
 80 |     },
 81 |     createOptions(selectorMoreInInput, commonOptions)
 82 |   )
 83 |   bench(
 84 |     nonMemoized,
 85 |     () => {
 86 |       runSelector(nonMemoized)
 87 |     },
 88 |     {
 89 |       ...commonOptions,
 90 |       setup: (task, mode) => {
 91 |         if (mode === 'warmup') return
 92 |         nonMemoized.resetRecomputations()
 93 |         task.opts = {
 94 |           beforeEach: () => {
 95 |             store.dispatch(toggleCompleted(1))
 96 |           },
 97 |           afterAll: () => {
 98 |             logFunctionInfo(nonMemoized, nonMemoized.recomputations())
 99 |           }
100 |         }
101 |       }
102 |     }
103 |   )
104 | })
105 | 
106 | // This benchmark is made to test to see at what point it becomes beneficial
107 | // to use reselect to memoize a function that is a plain field accessor.
108 | describe('Reselect vs standalone memoization for field access', () => {
109 |   const store = setupStore()
110 |   const runSelector = (selector: Selector) => {
111 |     runMultipleTimes(selector, 1_000_000, store.getState())
112 |   }
113 |   const commonOptions: Options = {
114 |     // warmupIterations: 0,
115 |     // warmupTime: 0,
116 |     // iterations: 10,
117 |     // time: 0
118 |   }
119 |   const fieldAccessorWithReselect = createSelector(
120 |     [(state: RootState) => state.users],
121 |     users => users.appSettings
122 |   )
123 |   const fieldAccessorWithMemoize = countRecomputations(
124 |     lruMemoize((state: RootState) => {
125 |       return state.users.appSettings
126 |     })
127 |   )
128 |   const nonMemoizedAccessor = countRecomputations(
129 |     (state: RootState) => state.users.appSettings
130 |   )
131 | 
132 |   setFunctionNames({
133 |     fieldAccessorWithReselect,
134 |     fieldAccessorWithMemoize,
135 |     nonMemoizedAccessor
136 |   })
137 |   const createOptions = <S extends OutputSelector>(
138 |     selector: S,
139 |     commonOptions: Options = {}
140 |   ) => {
141 |     const options: Options = {
142 |       setup: (task, mode) => {
143 |         if (mode === 'warmup') return
144 |         resetSelector(selector)
145 |         task.opts = {
146 |           beforeEach: () => {
147 |             store.dispatch(toggleCompleted(1))
148 |           },
149 |           afterAll: () => {
150 |             logSelectorRecomputations(selector)
151 |           }
152 |         }
153 |       }
154 |     }
155 |     return { ...commonOptions, ...options }
156 |   }
157 |   bench(
158 |     fieldAccessorWithReselect,
159 |     () => {
160 |       runSelector(fieldAccessorWithReselect)
161 |     },
162 |     createOptions(fieldAccessorWithReselect, commonOptions)
163 |   )
164 |   bench(
165 |     fieldAccessorWithMemoize,
166 |     () => {
167 |       runSelector(fieldAccessorWithMemoize)
168 |     },
169 |     {
170 |       ...commonOptions,
171 |       setup: (task, mode) => {
172 |         if (mode === 'warmup') return
173 |         fieldAccessorWithMemoize.resetRecomputations()
174 |         fieldAccessorWithMemoize.clearCache()
175 |         task.opts = {
176 |           beforeEach: () => {
177 |             store.dispatch(toggleCompleted(1))
178 |           },
179 |           afterAll: () => {
180 |             logFunctionInfo(
181 |               fieldAccessorWithMemoize,
182 |               fieldAccessorWithMemoize.recomputations()
183 |             )
184 |           }
185 |         }
186 |       }
187 |     }
188 |   )
189 |   bench(
190 |     nonMemoizedAccessor,
191 |     () => {
192 |       runSelector(nonMemoizedAccessor)
193 |     },
194 |     {
195 |       ...commonOptions,
196 |       setup: (task, mode) => {
197 |         if (mode === 'warmup') return
198 |         nonMemoizedAccessor.resetRecomputations()
199 |         task.opts = {
200 |           beforeEach: () => {
201 |             store.dispatch(toggleCompleted(1))
202 |           },
203 |           afterAll: () => {
204 |             logFunctionInfo(
205 |               nonMemoizedAccessor,
206 |               nonMemoizedAccessor.recomputations()
207 |             )
208 |           }
209 |         }
210 |       }
211 |     }
212 |   )
213 | })
214 | 


--------------------------------------------------------------------------------
/test/benchmarks/resultEqualityCheck.bench.ts:
--------------------------------------------------------------------------------
  1 | import type { AnyFunction } from '@internal/types'
  2 | import type { OutputSelector, Selector } from 'reselect'
  3 | import {
  4 |   createSelector,
  5 |   lruMemoize,
  6 |   referenceEqualityCheck,
  7 |   weakMapMemoize
  8 | } from 'reselect'
  9 | import type { Options } from 'tinybench'
 10 | import { bench } from 'vitest'
 11 | import {
 12 |   logSelectorRecomputations,
 13 |   setFunctionNames,
 14 |   setupStore,
 15 |   toggleCompleted,
 16 |   type RootState
 17 | } from '../testUtils'
 18 | 
 19 | describe('memoize functions performance with resultEqualityCheck set to referenceEqualityCheck vs. without resultEqualityCheck', () => {
 20 |   describe('comparing selectors created with createSelector', () => {
 21 |     const store = setupStore()
 22 | 
 23 |     const arrayOfNumbers = Array.from({ length: 1_000 }, (num, index) => index)
 24 | 
 25 |     const commonOptions: Options = {
 26 |       iterations: 10_000,
 27 |       time: 0
 28 |     }
 29 | 
 30 |     const runSelector = <S extends Selector>(selector: S) => {
 31 |       arrayOfNumbers.forEach(num => {
 32 |         selector(store.getState())
 33 |       })
 34 |     }
 35 | 
 36 |     const createAppSelector = createSelector.withTypes<RootState>()
 37 | 
 38 |     const selectTodoIdsWeakMap = createAppSelector(
 39 |       [state => state.todos],
 40 |       todos => todos.map(({ id }) => id)
 41 |     )
 42 | 
 43 |     const selectTodoIdsWeakMapWithResultEqualityCheck = createAppSelector(
 44 |       [state => state.todos],
 45 |       todos => todos.map(({ id }) => id),
 46 |       {
 47 |         memoizeOptions: { resultEqualityCheck: referenceEqualityCheck },
 48 |         argsMemoizeOptions: { resultEqualityCheck: referenceEqualityCheck }
 49 |       }
 50 |     )
 51 | 
 52 |     const selectTodoIdsLru = createAppSelector(
 53 |       [state => state.todos],
 54 |       todos => todos.map(({ id }) => id),
 55 |       { memoize: lruMemoize, argsMemoize: lruMemoize }
 56 |     )
 57 | 
 58 |     const selectTodoIdsLruWithResultEqualityCheck = createAppSelector(
 59 |       [state => state.todos],
 60 |       todos => todos.map(({ id }) => id),
 61 |       {
 62 |         memoize: lruMemoize,
 63 |         memoizeOptions: { resultEqualityCheck: referenceEqualityCheck },
 64 |         argsMemoize: lruMemoize,
 65 |         argsMemoizeOptions: { resultEqualityCheck: referenceEqualityCheck }
 66 |       }
 67 |     )
 68 | 
 69 |     const selectors = {
 70 |       selectTodoIdsWeakMap,
 71 |       selectTodoIdsWeakMapWithResultEqualityCheck,
 72 |       selectTodoIdsLru,
 73 |       selectTodoIdsLruWithResultEqualityCheck
 74 |     }
 75 | 
 76 |     setFunctionNames(selectors)
 77 | 
 78 |     const createOptions = <S extends OutputSelector>(selector: S) => {
 79 |       const options: Options = {
 80 |         setup: (task, mode) => {
 81 |           if (mode === 'warmup') return
 82 | 
 83 |           task.opts = {
 84 |             beforeEach: () => {
 85 |               store.dispatch(toggleCompleted(1))
 86 |             },
 87 | 
 88 |             afterAll: () => {
 89 |               logSelectorRecomputations(selector)
 90 |             }
 91 |           }
 92 |         }
 93 |       }
 94 |       return { ...commonOptions, ...options }
 95 |     }
 96 | 
 97 |     Object.values(selectors).forEach(selector => {
 98 |       bench(
 99 |         selector,
100 |         () => {
101 |           runSelector(selector)
102 |         },
103 |         createOptions(selector)
104 |       )
105 |     })
106 |   })
107 | 
108 |   describe('comparing selectors created with memoize functions', () => {
109 |     const store = setupStore()
110 | 
111 |     const arrayOfNumbers = Array.from(
112 |       { length: 100_000 },
113 |       (num, index) => index
114 |     )
115 | 
116 |     const commonOptions: Options = {
117 |       iterations: 1000,
118 |       time: 0
119 |     }
120 | 
121 |     const runSelector = <S extends Selector>(selector: S) => {
122 |       arrayOfNumbers.forEach(num => {
123 |         selector(store.getState())
124 |       })
125 |     }
126 | 
127 |     const selectTodoIdsWeakMap = weakMapMemoize((state: RootState) =>
128 |       state.todos.map(({ id }) => id)
129 |     )
130 | 
131 |     const selectTodoIdsWeakMapWithResultEqualityCheck = weakMapMemoize(
132 |       (state: RootState) => state.todos.map(({ id }) => id),
133 |       { resultEqualityCheck: referenceEqualityCheck }
134 |     )
135 | 
136 |     const selectTodoIdsLru = lruMemoize((state: RootState) =>
137 |       state.todos.map(({ id }) => id)
138 |     )
139 | 
140 |     const selectTodoIdsLruWithResultEqualityCheck = lruMemoize(
141 |       (state: RootState) => state.todos.map(({ id }) => id),
142 |       { resultEqualityCheck: referenceEqualityCheck }
143 |     )
144 | 
145 |     const memoizedFunctions = {
146 |       selectTodoIdsWeakMap,
147 |       selectTodoIdsWeakMapWithResultEqualityCheck,
148 |       selectTodoIdsLru,
149 |       selectTodoIdsLruWithResultEqualityCheck
150 |     }
151 | 
152 |     setFunctionNames(memoizedFunctions)
153 | 
154 |     const createOptions = <
155 |       Func extends AnyFunction & { resultsCount: () => number }
156 |     >(
157 |       memoizedFunction: Func
158 |     ) => {
159 |       const options: Options = {
160 |         setup: (task, mode) => {
161 |           if (mode === 'warmup') return
162 | 
163 |           task.opts = {
164 |             beforeEach: () => {
165 |               store.dispatch(toggleCompleted(1))
166 |             },
167 | 
168 |             afterAll: () => {
169 |               console.log(
170 |                 memoizedFunction.name,
171 |                 memoizedFunction.resultsCount()
172 |               )
173 |             }
174 |           }
175 |         }
176 |       }
177 |       return { ...commonOptions, ...options }
178 |     }
179 | 
180 |     Object.values(memoizedFunctions).forEach(memoizedFunction => {
181 |       bench(
182 |         memoizedFunction,
183 |         () => {
184 |           runSelector(memoizedFunction)
185 |         },
186 |         createOptions(memoizedFunction)
187 |       )
188 |     })
189 |   })
190 | })
191 | 


--------------------------------------------------------------------------------
/test/createSelector.withTypes.test.ts:
--------------------------------------------------------------------------------
 1 | import { createSelector } from 'reselect'
 2 | import type { RootState } from './testUtils'
 3 | import { localTest } from './testUtils'
 4 | 
 5 | describe(createSelector.withTypes, () => {
 6 |   const createTypedSelector = createSelector.withTypes<RootState>()
 7 | 
 8 |   localTest('should return createSelector', ({ state }) => {
 9 |     expect(createTypedSelector.withTypes).to.be.a('function')
10 | 
11 |     expect(createTypedSelector.withTypes().withTypes).to.be.a('function')
12 | 
13 |     expect(createTypedSelector).toBe(createSelector)
14 | 
15 |     const selectTodoIds = createTypedSelector([state => state.todos], todos =>
16 |       todos.map(({ id }) => id)
17 |     )
18 | 
19 |     expect(selectTodoIds).toBeMemoizedSelector()
20 | 
21 |     expect(selectTodoIds(state)).to.be.an('array').that.is.not.empty
22 |   })
23 | })
24 | 


--------------------------------------------------------------------------------
/test/createStructuredSelector.spec.ts:
--------------------------------------------------------------------------------
  1 | import {
  2 |   createSelector,
  3 |   createSelectorCreator,
  4 |   createStructuredSelector,
  5 |   lruMemoize
  6 | } from 'reselect'
  7 | import type { LocalTestContext, RootState } from './testUtils'
  8 | import { setupStore } from './testUtils'
  9 | 
 10 | interface StateAB {
 11 |   a: number
 12 |   b: number
 13 | }
 14 | 
 15 | describe(createStructuredSelector, () => {
 16 |   test('structured selector', () => {
 17 |     const selector = createStructuredSelector({
 18 |       x: (state: StateAB) => state.a,
 19 |       y: (state: StateAB) => state.b
 20 |     })
 21 |     const firstResult = selector({ a: 1, b: 2 })
 22 |     expect(firstResult).toEqual({ x: 1, y: 2 })
 23 |     expect(selector({ a: 1, b: 2 })).toBe(firstResult)
 24 |     const secondResult = selector({ a: 2, b: 2 })
 25 |     expect(secondResult).toEqual({ x: 2, y: 2 })
 26 |     expect(selector({ a: 2, b: 2 })).toBe(secondResult)
 27 |   })
 28 | 
 29 |   test('structured selector with invalid arguments', () => {
 30 |     expect(() =>
 31 |       createStructuredSelector(
 32 |         // @ts-expect-error
 33 |         (state: StateAB) => state.a,
 34 |         (state: StateAB) => state.b
 35 |       )
 36 |     ).toThrow(/expects first argument to be an object.*function/)
 37 |     expect(() =>
 38 |       createStructuredSelector({
 39 |         a: state => state.b,
 40 |         // @ts-expect-error
 41 |         c: 'd'
 42 |       })
 43 |     ).toThrow(
 44 |       'createSelector expects all input-selectors to be functions, but received the following types: [function a(), string]'
 45 |     )
 46 |   })
 47 | 
 48 |   test('structured selector with custom selector creator', () => {
 49 |     const customSelectorCreator = createSelectorCreator(
 50 |       lruMemoize,
 51 |       (a, b) => a === b
 52 |     )
 53 |     const selector = createStructuredSelector(
 54 |       {
 55 |         x: (state: StateAB) => state.a,
 56 |         y: (state: StateAB) => state.b
 57 |       },
 58 |       customSelectorCreator
 59 |     )
 60 |     const firstResult = selector({ a: 1, b: 2 })
 61 |     expect(firstResult).toEqual({ x: 1, y: 2 })
 62 |     expect(selector({ a: 1, b: 2 })).toBe(firstResult)
 63 |     expect(selector({ a: 2, b: 2 })).toEqual({ x: 2, y: 2 })
 64 |   })
 65 | })
 66 | 
 67 | describe<LocalTestContext>('structured selector created with createStructuredSelector', localTest => {
 68 |   beforeEach<LocalTestContext>(context => {
 69 |     const store = setupStore()
 70 |     context.store = store
 71 |     context.state = store.getState()
 72 |   })
 73 |   localTest(
 74 |     'structured selector created with createStructuredSelector and createSelector are the same',
 75 |     ({ state }) => {
 76 |       const structuredSelector = createStructuredSelector(
 77 |         {
 78 |           allTodos: (state: RootState) => state.todos,
 79 |           allAlerts: (state: RootState) => state.alerts,
 80 |           selectedTodo: (state: RootState, id: number) => state.todos[id]
 81 |         },
 82 |         createSelector
 83 |       )
 84 |       const selector = createSelector(
 85 |         [
 86 |           (state: RootState) => state.todos,
 87 |           (state: RootState) => state.alerts,
 88 |           (state: RootState, id: number) => state.todos[id]
 89 |         ],
 90 |         (allTodos, allAlerts, selectedTodo) => {
 91 |           return {
 92 |             allTodos,
 93 |             allAlerts,
 94 |             selectedTodo
 95 |           }
 96 |         }
 97 |       )
 98 |       expect(selector(state, 1).selectedTodo.id).toBe(
 99 |         structuredSelector(state, 1).selectedTodo.id
100 |       )
101 |       expect(structuredSelector.dependencies)
102 |         .to.be.an('array')
103 |         .with.lengthOf(selector.dependencies.length)
104 |       expect(
105 |         structuredSelector.resultFunc(state.todos, state.alerts, state.todos[0])
106 |       ).toStrictEqual(
107 |         selector.resultFunc(state.todos, state.alerts, state.todos[0])
108 |       )
109 |       expect(
110 |         structuredSelector.memoizedResultFunc(
111 |           state.todos,
112 |           state.alerts,
113 |           state.todos[0]
114 |         )
115 |       ).toStrictEqual(
116 |         selector.memoizedResultFunc(state.todos, state.alerts, state.todos[0])
117 |       )
118 |       expect(structuredSelector.argsMemoize).toBe(selector.argsMemoize)
119 |       expect(structuredSelector.memoize).toBe(selector.memoize)
120 |       expect(structuredSelector.recomputations()).toBe(
121 |         selector.recomputations()
122 |       )
123 |       expect(structuredSelector.lastResult()).toStrictEqual(
124 |         selector.lastResult()
125 |       )
126 |       expect(Object.keys(structuredSelector)).toStrictEqual(
127 |         Object.keys(selector)
128 |       )
129 |     }
130 |   )
131 | 
132 |   localTest(
133 |     'structured selector invalid args can throw runtime errors',
134 |     ({ state }) => {
135 |       const structuredSelector = createStructuredSelector(
136 |         {
137 |           allTodos: (state: RootState) => state.todos,
138 |           allAlerts: (state: RootState) => state.alerts,
139 |           selectedTodo: (
140 |             state: RootState,
141 |             id: number,
142 |             field: keyof RootState['todos'][number]
143 |           ) => state.todos[id][field]
144 |         },
145 |         createSelector
146 |       )
147 |       const selector = createSelector(
148 |         [
149 |           (state: RootState) => state.todos,
150 |           (state: RootState) => state.alerts,
151 |           (
152 |             state: RootState,
153 |             id: number,
154 |             field: keyof RootState['todos'][number]
155 |           ) => state.todos[id][field]
156 |         ],
157 |         (allTodos, allAlerts, selectedTodo) => {
158 |           return {
159 |             allTodos,
160 |             allAlerts,
161 |             selectedTodo
162 |           }
163 |         }
164 |       )
165 |       // These two cases are the same.
166 |       // @ts-expect-error
167 |       expect(() => structuredSelector(state)).toThrowError(TypeError)
168 |       // @ts-expect-error
169 |       expect(() => selector(state)).toThrowError(TypeError)
170 |     }
171 |   )
172 | })
173 | 


--------------------------------------------------------------------------------
/test/createStructuredSelector.withTypes.test.ts:
--------------------------------------------------------------------------------
 1 | import { createStructuredSelector } from 'reselect'
 2 | import type { RootState } from './testUtils'
 3 | import { localTest } from './testUtils'
 4 | 
 5 | describe(createStructuredSelector.withTypes, () => {
 6 |   const createTypedStructuredSelector =
 7 |     createStructuredSelector.withTypes<RootState>()
 8 | 
 9 |   localTest('should return createStructuredSelector', ({ state }) => {
10 |     expect(createTypedStructuredSelector.withTypes).to.be.a('function')
11 | 
12 |     expect(createTypedStructuredSelector.withTypes().withTypes).to.be.a(
13 |       'function'
14 |     )
15 | 
16 |     expect(createTypedStructuredSelector).toBe(createStructuredSelector)
17 | 
18 |     const structuredSelector = createTypedStructuredSelector({
19 |       todos: state => state.todos,
20 |       alerts: state => state.alerts
21 |     })
22 | 
23 |     expect(structuredSelector).toBeMemoizedSelector()
24 | 
25 |     expect(structuredSelector(state)).to.be.an('object').that.is.not.empty
26 |   })
27 | })
28 | 


--------------------------------------------------------------------------------
/test/customMatchers.d.ts:
--------------------------------------------------------------------------------
 1 | import type { Assertion, AsymmetricMatchersContaining } from 'vitest'
 2 | 
 3 | interface CustomMatchers<R = unknown> {
 4 |   toBeMemoizedSelector(): R
 5 | }
 6 | 
 7 | declare module 'vitest' {
 8 |   interface Assertion<T = any> extends CustomMatchers<T> {}
 9 |   interface AsymmetricMatchersContaining extends CustomMatchers {}
10 | }
11 | 


--------------------------------------------------------------------------------
/test/selectorUtils.spec.ts:
--------------------------------------------------------------------------------
 1 | import { createSelector, lruMemoize } from 'reselect'
 2 | import type { StateA, StateAB } from 'testTypes'
 3 | 
 4 | describe('createSelector exposed utils', () => {
 5 |   test('resetRecomputations', () => {
 6 |     const selector = createSelector(
 7 |       (state: StateA) => state.a,
 8 |       a => a,
 9 |       {
10 |         memoize: lruMemoize,
11 |         argsMemoize: lruMemoize,
12 |         devModeChecks: { identityFunctionCheck: 'never' }
13 |       }
14 |     )
15 |     expect(selector({ a: 1 })).toBe(1)
16 |     expect(selector({ a: 1 })).toBe(1)
17 |     expect(selector.recomputations()).toBe(1)
18 |     expect(selector({ a: 2 })).toBe(2)
19 |     expect(selector.recomputations()).toBe(2)
20 | 
21 |     selector.resetRecomputations()
22 |     expect(selector.recomputations()).toBe(0)
23 | 
24 |     expect(selector({ a: 1 })).toBe(1)
25 |     expect(selector({ a: 1 })).toBe(1)
26 |     expect(selector.recomputations()).toBe(1)
27 |     expect(selector({ a: 2 })).toBe(2)
28 |     expect(selector.recomputations()).toBe(2)
29 |   })
30 | 
31 |   test('export last function as resultFunc', () => {
32 |     const lastFunction = () => {}
33 |     const selector = createSelector((state: StateA) => state.a, lastFunction)
34 |     expect(selector.resultFunc).toBe(lastFunction)
35 |   })
36 | 
37 |   test('export dependencies as dependencies', () => {
38 |     const dependency1 = (state: StateA) => {
39 |       state.a
40 |     }
41 |     const dependency2 = (state: StateA) => {
42 |       state.a
43 |     }
44 | 
45 |     const selector = createSelector(dependency1, dependency2, () => {})
46 |     expect(selector.dependencies).toEqual([dependency1, dependency2])
47 |   })
48 | 
49 |   test('export lastResult function', () => {
50 |     const selector = createSelector(
51 |       (state: StateAB) => state.a,
52 |       (state: StateAB) => state.b,
53 |       (a, b) => a + b
54 |     )
55 | 
56 |     const result = selector({ a: 1, b: 2 })
57 |     expect(result).toBe(3)
58 |     expect(selector.lastResult()).toBe(3)
59 |   })
60 | })
61 | 


--------------------------------------------------------------------------------
/test/setup.vitest.ts:
--------------------------------------------------------------------------------
 1 | import { isMemoizedSelector } from './testUtils'
 2 | 
 3 | expect.extend({
 4 |   toBeMemoizedSelector(received) {
 5 |     const { isNot } = this
 6 | 
 7 |     return {
 8 |       pass: isMemoizedSelector(received),
 9 |       message: () => `${received} is${isNot ? '' : ' not'} a memoized selector`
10 |     }
11 |   }
12 | })
13 | 


--------------------------------------------------------------------------------
/test/testTypes.ts:
--------------------------------------------------------------------------------
 1 | export interface StateA {
 2 |   a: number
 3 | }
 4 | 
 5 | export interface StateAB {
 6 |   a: number
 7 |   b: number
 8 | }
 9 | 
10 | export interface StateSub {
11 |   sub: {
12 |     a: number
13 |   }
14 | }
15 | 


--------------------------------------------------------------------------------
/test/tsconfig.json:
--------------------------------------------------------------------------------
 1 | {
 2 |   "extends": "../tsconfig.json",
 3 |   "compilerOptions": {
 4 |     "allowSyntheticDefaultImports": true,
 5 |     "esModuleInterop": true,
 6 |     "module": "ESNext",
 7 |     "moduleResolution": "Node",
 8 |     "emitDeclarationOnly": false,
 9 |     "strict": true,
10 |     "noEmit": true,
11 |     "target": "ESNext",
12 |     "jsx": "react",
13 |     "baseUrl": ".",
14 |     "rootDir": "../",
15 |     "skipLibCheck": true,
16 |     "noImplicitReturns": false,
17 |     "noUnusedLocals": false,
18 |     "types": ["vitest/globals"],
19 |     "paths": {
20 |       "reselect": ["../src/index.ts"], // @remap-prod-remove-line
21 |       "@internal/*": ["../src/*"]
22 |     }
23 |   },
24 |   "include": ["**/*.ts*"]
25 | }
26 | 


--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
 1 | {
 2 |   "compilerOptions": {
 3 |     "strict": true,
 4 |     "target": "ESNext",
 5 |     "module": "ESNext",
 6 |     "moduleResolution": "Node",
 7 |     "esModuleInterop": true,
 8 |     "skipLibCheck": true,
 9 |     "allowJs": true,
10 |     "jsx": "react",
11 |     "noErrorTruncation": true,
12 |     "declaration": true,
13 |     "emitDeclarationOnly": true,
14 |     "outDir": "dist",
15 |     "forceConsistentCasingInFileNames": true,
16 |     "experimentalDecorators": true,
17 |     "rootDirs": ["./src"],
18 |     "rootDir": ".",
19 |     "types": ["vitest/globals", "vitest/importMeta"],
20 |     "baseUrl": ".",
21 |     "paths": {
22 |       "reselect": ["src/index.ts"], // @remap-prod-remove-line
23 |       "@internal/*": ["src/*"]
24 |     }
25 |   },
26 |   "include": ["./src/**/*"],
27 |   "exclude": ["node_modules", "dist"]
28 | }
29 | 


--------------------------------------------------------------------------------
/tsup.config.ts:
--------------------------------------------------------------------------------
  1 | import fs from 'node:fs/promises'
  2 | import path from 'node:path'
  3 | import type { Options } from 'tsup'
  4 | import { defineConfig } from 'tsup'
  5 | 
  6 | async function writeCommonJSEntry() {
  7 |   await fs.writeFile(
  8 |     path.join('dist/cjs/', 'index.js'),
  9 |     `'use strict'
 10 | if (process.env.NODE_ENV === 'production') {
 11 |   module.exports = require('./reselect.production.min.cjs')
 12 | } else {
 13 |   module.exports = require('./reselect.development.cjs')
 14 | }`
 15 |   )
 16 | }
 17 | 
 18 | export default defineConfig((options): Options[] => {
 19 |   const commonOptions: Options = {
 20 |     entry: {
 21 |       reselect: 'src/index.ts'
 22 |     },
 23 |     sourcemap: true,
 24 |     target: ['esnext'],
 25 |     clean: true,
 26 |     ...options
 27 |   }
 28 | 
 29 |   return [
 30 |     {
 31 |       ...commonOptions,
 32 |       name: 'Modern ESM',
 33 |       target: ['esnext'],
 34 |       format: ['esm'],
 35 |       outExtension: () => ({ js: '.mjs' })
 36 |     },
 37 | 
 38 |     // Support Webpack 4 by pointing `"module"` to a file with a `.js` extension
 39 |     // and optional chaining compiled away
 40 |     {
 41 |       ...commonOptions,
 42 |       name: 'Legacy ESM, Webpack 4',
 43 |       entry: {
 44 |         'reselect.legacy-esm': 'src/index.ts'
 45 |       },
 46 |       format: ['esm'],
 47 |       outExtension: () => ({ js: '.js' }),
 48 |       target: ['es2017']
 49 |     },
 50 | 
 51 |     // Meant to be served up via CDNs like `unpkg`.
 52 |     {
 53 |       ...commonOptions,
 54 |       name: 'Browser-ready ESM',
 55 |       entry: {
 56 |         'reselect.browser': 'src/index.ts'
 57 |       },
 58 |       platform: 'browser',
 59 |       env: {
 60 |         NODE_ENV: 'production'
 61 |       },
 62 |       format: ['esm'],
 63 |       outExtension: () => ({ js: '.mjs' }),
 64 |       minify: true
 65 |     },
 66 |     {
 67 |       ...commonOptions,
 68 |       name: 'CJS Development',
 69 |       entry: {
 70 |         'reselect.development': 'src/index.ts'
 71 |       },
 72 |       env: {
 73 |         NODE_ENV: 'development'
 74 |       },
 75 |       format: ['cjs'],
 76 |       outDir: './dist/cjs/',
 77 |       outExtension: () => ({ js: '.cjs' })
 78 |     },
 79 |     {
 80 |       ...commonOptions,
 81 |       name: 'CJS production',
 82 |       entry: {
 83 |         'reselect.production.min': 'src/index.ts'
 84 |       },
 85 |       env: {
 86 |         NODE_ENV: 'production'
 87 |       },
 88 |       format: ['cjs'],
 89 |       outDir: './dist/cjs/',
 90 |       outExtension: () => ({ js: '.cjs' }),
 91 |       minify: true,
 92 |       onSuccess: async () => {
 93 |         await writeCommonJSEntry()
 94 |       }
 95 |     },
 96 |     {
 97 |       ...commonOptions,
 98 |       name: 'CJS Type Definitions',
 99 |       format: ['cjs'],
100 |       dts: { only: true }
101 |     }
102 |   ]
103 | })
104 | 


--------------------------------------------------------------------------------
/type-tests/createSelector.withTypes.test-d.ts:
--------------------------------------------------------------------------------
  1 | import { createSelector } from 'reselect'
  2 | import { describe, expectTypeOf, test } from 'vitest'
  3 | 
  4 | interface Todo {
  5 |   id: number
  6 |   completed: boolean
  7 | }
  8 | 
  9 | interface Alert {
 10 |   id: number
 11 |   read: boolean
 12 | }
 13 | 
 14 | interface RootState {
 15 |   todos: Todo[]
 16 |   alerts: Alert[]
 17 | }
 18 | 
 19 | const rootState: RootState = {
 20 |   todos: [
 21 |     { id: 0, completed: false },
 22 |     { id: 1, completed: false }
 23 |   ],
 24 |   alerts: [
 25 |     { id: 0, read: false },
 26 |     { id: 1, read: false }
 27 |   ]
 28 | }
 29 | 
 30 | describe('createSelector.withTypes<RootState>()', () => {
 31 |   const createAppSelector = createSelector.withTypes<RootState>()
 32 | 
 33 |   describe('when input selectors are provided as a single array', () => {
 34 |     test('locks down state type and infers result function parameter types correctly', () => {
 35 |       expectTypeOf(createSelector.withTypes).returns.toEqualTypeOf(
 36 |         createSelector
 37 |       )
 38 | 
 39 |       // Type of state is locked and the parameter types of the result function
 40 |       // are correctly inferred when input selectors are provided as a single array.
 41 |       createAppSelector(
 42 |         [
 43 |           state => {
 44 |             expectTypeOf(state).toEqualTypeOf<RootState>(rootState)
 45 | 
 46 |             return state.todos
 47 |           }
 48 |         ],
 49 |         todos => {
 50 |           expectTypeOf(todos).toEqualTypeOf<Todo[]>(rootState.todos)
 51 | 
 52 |           return todos.map(({ id }) => id)
 53 |         }
 54 |       )
 55 |     })
 56 |   })
 57 | 
 58 |   describe('when input selectors are provided as separate inline arguments', () => {
 59 |     test('locks down state type but does not infer result function parameter types', () => {
 60 |       // Type of state is locked but the parameter types of the
 61 |       // result function are NOT correctly inferred when
 62 |       // input selectors are provided as separate inline arguments.
 63 |       createAppSelector(
 64 |         state => {
 65 |           expectTypeOf(state).toEqualTypeOf<RootState>(rootState)
 66 | 
 67 |           return state.todos
 68 |         },
 69 |         todos => {
 70 |           // Known limitation: Parameter types are not inferred in this scenario
 71 |           expectTypeOf(todos).toBeAny()
 72 | 
 73 |           expectTypeOf(todos).not.toEqualTypeOf<Todo[]>(rootState.todos)
 74 | 
 75 |           // @ts-expect-error A typed `createSelector` currently only infers
 76 |           // the parameter types of the result function when
 77 |           // input selectors are provided as a single array.
 78 |           return todos.map(({ id }) => id)
 79 |         }
 80 |       )
 81 |     })
 82 | 
 83 |     test('handles multiple input selectors with separate inline arguments', () => {
 84 |       // Checking to see if the type of state is correct when multiple
 85 |       // input selectors are provided as separate inline arguments.
 86 |       createAppSelector(
 87 |         state => {
 88 |           expectTypeOf(state).toEqualTypeOf<RootState>(rootState)
 89 | 
 90 |           return state.todos
 91 |         },
 92 |         state => {
 93 |           expectTypeOf(state).toEqualTypeOf<RootState>(rootState)
 94 | 
 95 |           return state.alerts
 96 |         },
 97 |         (todos, alerts) => {
 98 |           // Known limitation: Parameter types are not inferred in this scenario
 99 |           expectTypeOf(todos).toBeAny()
100 | 
101 |           expectTypeOf(alerts).toBeAny()
102 | 
103 |           // @ts-expect-error A typed `createSelector` currently only infers
104 |           // the parameter types of the result function when
105 |           // input selectors are provided as a single array.
106 |           return todos.map(({ id }) => id)
107 |         }
108 |       )
109 |     })
110 | 
111 |     test('can annotate parameter types of the result function to workaround type inference issue', () => {
112 |       createAppSelector(
113 |         state => state.todos,
114 |         (todos: Todo[]) => todos.map(({ id }) => id)
115 |       )
116 |     })
117 |   })
118 | })
119 | 


--------------------------------------------------------------------------------
/type-tests/createSelectorCreator.test-d.ts:
--------------------------------------------------------------------------------
 1 | import lodashMemoize from 'lodash/memoize'
 2 | import memoizeOne from 'memoize-one'
 3 | import microMemoize from 'micro-memoize'
 4 | import {
 5 |   createSelectorCreator,
 6 |   lruMemoize,
 7 |   unstable_autotrackMemoize as autotrackMemoize,
 8 |   weakMapMemoize
 9 | } from 'reselect'
10 | import { describe, test } from 'vitest'
11 | 
12 | interface RootState {
13 |   todos: { id: number; completed: boolean }[]
14 |   alerts: { id: number; read: boolean }[]
15 | }
16 | 
17 | const state: RootState = {
18 |   todos: [
19 |     { id: 0, completed: false },
20 |     { id: 1, completed: true }
21 |   ],
22 |   alerts: [
23 |     { id: 0, read: false },
24 |     { id: 1, read: true }
25 |   ]
26 | }
27 | 
28 | describe('createSelectorCreator', () => {
29 |   test('options object as argument', () => {
30 |     const createSelectorDefault = createSelectorCreator({
31 |       memoize: lruMemoize
32 |     })
33 |     const createSelectorWeakMap = createSelectorCreator({
34 |       memoize: weakMapMemoize
35 |     })
36 |     const createSelectorAutotrack = createSelectorCreator({
37 |       memoize: autotrackMemoize
38 |     })
39 |     const createSelectorMicro = createSelectorCreator({
40 |       memoize: microMemoize
41 |     })
42 |     const createSelectorOne = createSelectorCreator({
43 |       memoize: memoizeOne
44 |     })
45 |     const createSelectorLodash = createSelectorCreator({
46 |       memoize: lodashMemoize
47 |     })
48 |   })
49 | 
50 |   test('memoize function as argument', () => {
51 |     const createSelectorDefault = createSelectorCreator(lruMemoize)
52 |     const createSelectorWeakMap = createSelectorCreator(weakMapMemoize)
53 |     const createSelectorAutotrack = createSelectorCreator(autotrackMemoize)
54 |     const createSelectorMicro = createSelectorCreator(microMemoize)
55 |     const createSelectorOne = createSelectorCreator(memoizeOne)
56 |     const createSelectorLodash = createSelectorCreator(lodashMemoize)
57 |   })
58 | })
59 | 


--------------------------------------------------------------------------------
/type-tests/tsconfig.json:
--------------------------------------------------------------------------------
 1 | {
 2 |   "compilerOptions": {
 3 |     "module": "commonjs",
 4 |     "esModuleInterop": true,
 5 |     "strict": true,
 6 |     "target": "ES2015",
 7 |     "lib": ["ES2021.WeakRef"],
 8 |     "declaration": true,
 9 |     "noEmit": true,
10 |     "skipLibCheck": true,
11 |     "paths": {
12 |       "reselect": ["../src/index"], // @remap-prod-remove-line
13 |       "@internal/*": ["../src/*"]
14 |     }
15 |   },
16 |   "include": ["**/*.ts", "../typescript_test/**/*.ts"]
17 | }
18 | 


--------------------------------------------------------------------------------
/typescript_test/tsconfig.json:
--------------------------------------------------------------------------------
 1 | {
 2 |   "compilerOptions": {
 3 |     "module": "commonjs",
 4 |     "strict": true,
 5 |     "target": "ES2015",
 6 |     "lib": ["ES2021.WeakRef"],
 7 |     "declaration": true,
 8 |     "noEmit": true,
 9 |     "skipLibCheck": true,
10 |     "paths": {
11 |       "reselect": ["../src/index"], // @remap-prod-remove-line
12 |       "@internal/*": ["../src/*"]
13 |     }
14 |   },
15 |   "include": ["test.ts", "argsMemoize.typetest.ts"]
16 | }
17 | 


--------------------------------------------------------------------------------
/typescript_test/typesTestUtils.ts:
--------------------------------------------------------------------------------
 1 | export function expectType<T>(t: T): T {
 2 |   return t
 3 | }
 4 | 
 5 | export declare type IsAny<T, True, False = never> = true | false extends (
 6 |   T extends never ? true : false
 7 | )
 8 |   ? True
 9 |   : False
10 | 
11 | export declare type IsUnknown<T, True, False = never> = unknown extends T
12 |   ? IsAny<T, False, True>
13 |   : False
14 | 
15 | type Equals<T, U> = IsAny<
16 |   T,
17 |   never,
18 |   IsAny<U, never, [T] extends [U] ? ([U] extends [T] ? any : never) : never>
19 | >
20 | 
21 | export type IsEqual<A, B> = (<G>() => G extends A ? 1 : 2) extends <
22 |   G
23 | >() => G extends B ? 1 : 2
24 |   ? true
25 |   : false
26 | 
27 | export function expectExactType<T>(t: T) {
28 |   return <U extends T>(u: U & Equals<T, U>) => {}
29 | }
30 | 


--------------------------------------------------------------------------------
/vitest.config.mts:
--------------------------------------------------------------------------------
 1 | import { defineConfig } from 'vitest/config'
 2 | 
 3 | import path from 'node:path'
 4 | import { fileURLToPath } from 'node:url'
 5 | 
 6 | // No __dirname under Node ESM
 7 | const __filename = fileURLToPath(import.meta.url)
 8 | const __dirname = path.dirname(__filename)
 9 | 
10 | export default defineConfig({
11 |   test: {
12 |     typecheck: { tsconfig: 'type-tests/tsconfig.json' },
13 |     globals: true,
14 |     include: ['./test/**/*.(spec|test).[jt]s?(x)'],
15 |     setupFiles: ['test/setup.vitest.ts'],
16 |     alias: {
17 |       reselect: path.join(__dirname, 'src/index.ts'), // @remap-prod-remove-line
18 | 
19 |       // this mapping is disabled as we want `dist` imports in the tests only to be used for "type-only" imports which don't play a role for jest
20 |       '@internal': path.join(__dirname, 'src')
21 |     }
22 |   }
23 | })
24 | 


--------------------------------------------------------------------------------
/website/.gitignore:
--------------------------------------------------------------------------------
 1 | # Dependencies
 2 | /node_modules
 3 | 
 4 | # Production
 5 | /build
 6 | 
 7 | # Generated files
 8 | .docusaurus
 9 | .cache-loader
10 | 
11 | # Misc
12 | .DS_Store
13 | .env.local
14 | .env.development.local
15 | .env.test.local
16 | .env.production.local
17 | 
18 | npm-debug.log*
19 | yarn-debug.log*
20 | yarn-error.log*
21 | 


--------------------------------------------------------------------------------
/website/README.md:
--------------------------------------------------------------------------------
 1 | # Website
 2 | 
 3 | This website is built using [Docusaurus 2](https://docusaurus.io/), a modern static website generator.
 4 | 
 5 | ### Installation
 6 | 
 7 | ```
 8 | $ yarn
 9 | ```
10 | 
11 | ### Local Development
12 | 
13 | ```
14 | $ yarn start
15 | ```
16 | 
17 | This command starts a local development server and opens up a browser window. Most changes are reflected live without having to restart the server.
18 | 
19 | ### Build
20 | 
21 | ```
22 | $ yarn build
23 | ```
24 | 
25 | This command generates static content into the `build` directory and can be served using any static contents hosting service.
26 | 
27 | ### Deployment
28 | 
29 | Using SSH:
30 | 
31 | ```
32 | $ USE_SSH=true yarn deploy
33 | ```
34 | 
35 | Not using SSH:
36 | 
37 | ```
38 | $ GIT_USER=<Your GitHub username> yarn deploy
39 | ```
40 | 
41 | If you are using GitHub pages for hosting, this command is a convenient way to build the website and push to the `gh-pages` branch.
42 | 


--------------------------------------------------------------------------------
/website/babel.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 |   presets: [require.resolve('@docusaurus/core/lib/babel/preset')]
3 | }
4 | 


--------------------------------------------------------------------------------
/website/docs/api/unstable_autotrackMemoize.mdx:
--------------------------------------------------------------------------------
  1 | ---
  2 | id: unstable_autotrackMemoize
  3 | title: unstable_autotrackMemoize
  4 | sidebar_label: unstable_autotrackMemoize
  5 | hide_title: true
  6 | description: 'unstable_autotrackMemoize'
  7 | ---
  8 | 
  9 | import Tabs from '@theme/Tabs'
 10 | import TabItem from '@theme/TabItem'
 11 | import { InternalLinks } from '@site/src/components/InternalLinks'
 12 | 
 13 | # `unstable_autotrackMemoize`
 14 | 
 15 | Uses an "auto-tracking" approach inspired by the work of the Ember Glimmer team. It uses a Proxy to wrap arguments and track accesses to nested fields in your selector on first read. Later, when the selector is called with new arguments, it identifies which accessed fields have changed and only recalculates the result if one or more of those accessed fields have changed. This allows it to be more precise than the shallow equality checks in `lruMemoize`.
 16 | 
 17 | :::danger
 18 | 
 19 | This API is still experimental and undergoing testing.
 20 | 
 21 | :::
 22 | 
 23 | ## Design Tradeoffs
 24 | 
 25 | - Pros:
 26 | 
 27 |   - It is likely to avoid excess calculations and recalculate fewer times than `lruMemoize` will, which may also result in fewer component re-renders.
 28 | 
 29 | - Cons:
 30 | 
 31 |   - It only has a cache size of 1.
 32 |   - It is slower than `lruMemoize`, because it has to do more work. (How much slower is dependent on the number of accessed fields in a selector, number of calls, frequency of input changes, etc)
 33 |   - It can have some unexpected behavior. Because it tracks nested field accesses, cases where you don't access a field will not recalculate properly. For example, a badly-written selector like:
 34 | 
 35 |   ```ts
 36 |   createSelector([state => state.todos], todos => todos)
 37 |   ```
 38 | 
 39 |   that just immediately returns the extracted value will never update, because it doesn't see any field accesses to check.
 40 | 
 41 | ## Use Cases
 42 | 
 43 | - It is likely best used for cases where you need to access specific nested fields in data, and avoid recalculating if other fields in the same data objects are immutably updated.
 44 | 
 45 | ## Parameters
 46 | 
 47 | | Name   | Description                  |
 48 | | :----- | :--------------------------- |
 49 | | `func` | The function to be memoized. |
 50 | 
 51 | ## Returns
 52 | 
 53 | A memoized function with a `.clearCache()` method attached.
 54 | 
 55 | ## Type Parameters
 56 | 
 57 | | Name   | Description                                |
 58 | | :----- | :----------------------------------------- |
 59 | | `Func` | The type of the function that is memoized. |
 60 | 
 61 | ## Examples
 62 | 
 63 | ### Using `unstable_autotrackMemoize` with `createSelector`
 64 | 
 65 | {/* START: unstable_autotrackMemoize/usingWithCreateSelector.ts */}
 66 | 
 67 | <Tabs
 68 |   groupId='language'
 69 |   defaultValue='ts'
 70 |   values={[
 71 |     {label: 'TypeScript', value: 'ts'},
 72 |     {label: 'JavaScript', value: 'js'},
 73 |   ]}>
 74 |   <TabItem value='ts'>
 75 | 
 76 | ```ts title="unstable_autotrackMemoize/usingWithCreateSelector.ts"
 77 | import { createSelector, unstable_autotrackMemoize } from 'reselect'
 78 | 
 79 | export interface RootState {
 80 |   todos: { id: number; completed: boolean }[]
 81 |   alerts: { id: number; read: boolean }[]
 82 | }
 83 | 
 84 | const selectTodoIds = createSelector(
 85 |   [(state: RootState) => state.todos],
 86 |   todos => todos.map(todo => todo.id),
 87 |   { memoize: unstable_autotrackMemoize }
 88 | )
 89 | ```
 90 | 
 91 |   </TabItem>
 92 |   <TabItem value='js'>
 93 | 
 94 | ```js title="unstable_autotrackMemoize/usingWithCreateSelector.js"
 95 | import { createSelector, unstable_autotrackMemoize } from 'reselect'
 96 | 
 97 | const selectTodoIds = createSelector(
 98 |   [state => state.todos],
 99 |   todos => todos.map(todo => todo.id),
100 |   { memoize: unstable_autotrackMemoize }
101 | )
102 | ```
103 | 
104 |   </TabItem>
105 | </Tabs>
106 | 
107 | {/* END: unstable_autotrackMemoize/usingWithCreateSelector.ts */}
108 | 
109 | ### Using `unstable_autotrackMemoize` with `createSelectorCreator`
110 | 
111 | {/* START: unstable_autotrackMemoize/usingWithCreateSelectorCreator.ts */}
112 | 
113 | <Tabs
114 |   groupId='language'
115 |   defaultValue='ts'
116 |   values={[
117 |     {label: 'TypeScript', value: 'ts'},
118 |     {label: 'JavaScript', value: 'js'},
119 |   ]}>
120 |   <TabItem value='ts'>
121 | 
122 | ```ts title="unstable_autotrackMemoize/usingWithCreateSelectorCreator.ts"
123 | import { createSelectorCreator, unstable_autotrackMemoize } from 'reselect'
124 | import type { RootState } from './usingWithCreateSelector'
125 | 
126 | const createSelectorAutotrack = createSelectorCreator({
127 |   memoize: unstable_autotrackMemoize
128 | })
129 | 
130 | const selectTodoIds = createSelectorAutotrack(
131 |   [(state: RootState) => state.todos],
132 |   todos => todos.map(todo => todo.id)
133 | )
134 | ```
135 | 
136 |   </TabItem>
137 |   <TabItem value='js'>
138 | 
139 | ```js title="unstable_autotrackMemoize/usingWithCreateSelectorCreator.js"
140 | import { createSelectorCreator, unstable_autotrackMemoize } from 'reselect'
141 | 
142 | const createSelectorAutotrack = createSelectorCreator({
143 |   memoize: unstable_autotrackMemoize
144 | })
145 | 
146 | const selectTodoIds = createSelectorAutotrack([state => state.todos], todos =>
147 |   todos.map(todo => todo.id)
148 | )
149 | ```
150 | 
151 |   </TabItem>
152 | </Tabs>
153 | 
154 | {/* END: unstable_autotrackMemoize/usingWithCreateSelectorCreator.ts */}
155 | 


--------------------------------------------------------------------------------
/website/docs/external-references.mdx:
--------------------------------------------------------------------------------
 1 | ---
 2 | id: external-references
 3 | title: External References
 4 | sidebar_label: External References
 5 | hide_title: true
 6 | description: External References
 7 | ---
 8 | 
 9 | import { AllExternalLinks } from '@site/src/components/ExternalLinks'
10 | 
11 | <AllExternalLinks />
12 | 


--------------------------------------------------------------------------------
/website/docs/introduction/getting-started.mdx:
--------------------------------------------------------------------------------
  1 | ---
  2 | id: getting-started
  3 | title: Getting Started
  4 | sidebar_label: Getting Started
  5 | sidebar_position: 1
  6 | hide_title: true
  7 | description: 'Getting Started'
  8 | ---
  9 | 
 10 | import { InternalLinks } from '@site/src/components/InternalLinks'
 11 | import { ExternalLinks } from '@site/src/components/ExternalLinks'
 12 | import PackageTabs from '@site/src/components/PackageManagerTabs'
 13 | import Tabs from '@theme/Tabs'
 14 | import TabItem from '@theme/TabItem'
 15 | import Link from '@docusaurus/Link'
 16 | 
 17 | # Getting Started with Reselect
 18 | 
 19 | A library for creating memoized "selector" functions. Commonly used with Redux, but usable with any plain JS immutable data as well.
 20 | 
 21 | - Selectors can compute derived data, allowing <ExternalLinks.Redux /> to store the minimal possible state.
 22 | - Selectors are efficient. A selector is not recomputed unless one of its arguments changes.
 23 | - Selectors are composable. They can be used as input to other selectors.
 24 | 
 25 | The **Redux docs usage page on <Link to="https://redux.js.org/usage/deriving-data-selectors">Deriving Data with Selectors</Link>** covers the purpose and motivation for selectors, why memoized selectors are useful, typical Reselect usage patterns, and using selectors with <ExternalLinks.ReactRedux />.
 26 | 
 27 | ## Installation
 28 | 
 29 | ### Redux Toolkit
 30 | 
 31 | While Reselect is not exclusive to <ExternalLinks.Redux />, it is already included by default in <ExternalLinks.ReduxToolkit text='the official Redux Toolkit package' /> - no further installation needed.
 32 | 
 33 | ```ts
 34 | import { createSelector } from '@reduxjs/toolkit'
 35 | ```
 36 | 
 37 | ### Standalone
 38 | 
 39 | For standalone usage, install the `reselect` package:
 40 | 
 41 | <PackageTabs />
 42 | 
 43 | ---
 44 | 
 45 | ## Basic Usage
 46 | 
 47 | Reselect exports a `createSelector` API, which generates memoized selector functions. `createSelector` accepts one or more input selectors, which extract values from arguments, and a result function that receives the extracted values and should return a derived value. If the generated output selector is called multiple times, the output will only be recalculated when the extracted values have changed.
 48 | 
 49 | You can play around with the following **example** in <Link to='https://codesandbox.io/s/reselect-example-g3k9gf?file=/src/index.js'>this CodeSandbox</Link>:
 50 | 
 51 | {/* START: basicUsage.ts */}
 52 | 
 53 | <Tabs
 54 |   groupId='language'
 55 |   defaultValue='ts'
 56 |   values={[
 57 |     {label: 'TypeScript', value: 'ts'},
 58 |     {label: 'JavaScript', value: 'js'},
 59 |   ]}>
 60 |   <TabItem value='ts'>
 61 | 
 62 | ```ts title="basicUsage.ts"
 63 | import { createSelector } from 'reselect'
 64 | 
 65 | interface RootState {
 66 |   todos: { id: number; completed: boolean }[]
 67 |   alerts: { id: number; read: boolean }[]
 68 | }
 69 | 
 70 | const state: RootState = {
 71 |   todos: [
 72 |     { id: 0, completed: false },
 73 |     { id: 1, completed: true }
 74 |   ],
 75 |   alerts: [
 76 |     { id: 0, read: false },
 77 |     { id: 1, read: true }
 78 |   ]
 79 | }
 80 | 
 81 | const selectCompletedTodos = (state: RootState) => {
 82 |   console.log('selector ran')
 83 |   return state.todos.filter(todo => todo.completed === true)
 84 | }
 85 | 
 86 | selectCompletedTodos(state) // selector ran
 87 | selectCompletedTodos(state) // selector ran
 88 | selectCompletedTodos(state) // selector ran
 89 | 
 90 | const memoizedSelectCompletedTodos = createSelector(
 91 |   [(state: RootState) => state.todos],
 92 |   todos => {
 93 |     console.log('memoized selector ran')
 94 |     return todos.filter(todo => todo.completed === true)
 95 |   }
 96 | )
 97 | 
 98 | memoizedSelectCompletedTodos(state) // memoized selector ran
 99 | memoizedSelectCompletedTodos(state)
100 | memoizedSelectCompletedTodos(state)
101 | 
102 | console.log(selectCompletedTodos(state) === selectCompletedTodos(state)) //=> false
103 | 
104 | console.log(
105 |   memoizedSelectCompletedTodos(state) === memoizedSelectCompletedTodos(state)
106 | ) //=> true
107 | ```
108 | 
109 |   </TabItem>
110 |   <TabItem value='js'>
111 | 
112 | ```js title="basicUsage.js"
113 | import { createSelector } from 'reselect'
114 | 
115 | const state = {
116 |   todos: [
117 |     { id: 0, completed: false },
118 |     { id: 1, completed: true }
119 |   ],
120 |   alerts: [
121 |     { id: 0, read: false },
122 |     { id: 1, read: true }
123 |   ]
124 | }
125 | 
126 | const selectCompletedTodos = state => {
127 |   console.log('selector ran')
128 |   return state.todos.filter(todo => todo.completed === true)
129 | }
130 | 
131 | selectCompletedTodos(state) // selector ran
132 | selectCompletedTodos(state) // selector ran
133 | selectCompletedTodos(state) // selector ran
134 | 
135 | const memoizedSelectCompletedTodos = createSelector(
136 |   [state => state.todos],
137 |   todos => {
138 |     console.log('memoized selector ran')
139 |     return todos.filter(todo => todo.completed === true)
140 |   }
141 | )
142 | 
143 | memoizedSelectCompletedTodos(state) // memoized selector ran
144 | memoizedSelectCompletedTodos(state)
145 | memoizedSelectCompletedTodos(state)
146 | 
147 | console.log(selectCompletedTodos(state) === selectCompletedTodos(state)) //=> false
148 | 
149 | console.log(
150 |   memoizedSelectCompletedTodos(state) === memoizedSelectCompletedTodos(state)
151 | ) //=> true
152 | ```
153 | 
154 |   </TabItem>
155 | </Tabs>
156 | 
157 | {/* END: basicUsage.ts */}
158 | 
159 | As you can see from the example above, `memoizedSelectCompletedTodos` does not run the second or third time, but we still get the same return value as last time.
160 | 
161 | In addition to skipping unnecessary recalculations, `memoizedSelectCompletedTodos` returns the existing result reference if there is no recalculation. This is important for libraries like React-Redux or React that often rely on reference equality checks to optimize UI updates.
162 | 
163 | ---
164 | 
165 | ## Terminology
166 | 
167 | - <InternalLinks.Selector text={<b>Selector Function</b>} />
168 |   : A function that accepts one or more JavaScript values as arguments, and derives
169 |   a result. When used with <ExternalLinks.Redux />, the first argument is typically
170 |   the entire Redux store state.
171 | - <InternalLinks.InputSelectors text={<b>Input Selectors</b>} />
172 |   : Basic selector functions used as building blocks for creating a memoized selector.
173 |   They are passed as the first argument(s) to <InternalLinks.CreateSelector />, and
174 |   are called with all selector arguments. They are responsible for extracting and
175 |   providing necessary values to the <InternalLinks.ResultFunction />.
176 | - <InternalLinks.OutputSelector text={<b>Output Selector</b>} />
177 |   : The actual memoized selectors created by <InternalLinks.CreateSelector />.
178 | - <InternalLinks.ResultFunction text={<b>Result Function</b>} />
179 |   : The function that comes after the <InternalLinks.InputSelectors />
180 |   . It takes the <InternalLinks.InputSelectors />' return values as arguments
181 |   and returns a result.
182 | - <InternalLinks.Dependencies text={<b>Dependencies</b>} />
183 |   : Same as <InternalLinks.InputSelectors />
184 |   . They are what the <InternalLinks.OutputSelector /> "depends" on.
185 | 
186 | The below example serves as a visual aid:
187 | 
188 | ```ts
189 | const outputSelector = createSelector(
190 |   [inputSelector1, inputSelector2, inputSelector3], // synonymous with `dependencies`.
191 |   resultFunc // Result function
192 | )
193 | ```
194 | 


--------------------------------------------------------------------------------
/website/docs/introduction/how-does-reselect-work.mdx:
--------------------------------------------------------------------------------
  1 | ---
  2 | id: how-does-reselect-work
  3 | title: How Does Reselect Work?
  4 | sidebar_label: How Does Reselect Work?
  5 | hide_title: true
  6 | description: 'How Does Reselect Work?'
  7 | ---
  8 | 
  9 | import { InternalLinks } from '@site/src/components/InternalLinks'
 10 | import { ExternalLinks } from '@site/src/components/ExternalLinks'
 11 | 
 12 | # How Does Reselect Work?
 13 | 
 14 | Reselect, at its core, is a library for creating memoized selectors in JavaScript applications. Its primary role is to efficiently compute derived data based on provided inputs. A key aspect of Reselect's internal mechanism is how it orchestrates the flow of arguments from the final selector to its constituent <InternalLinks.InputSelectors />.
 15 | 
 16 | ```ts
 17 | const finalSelector = (...args) => {
 18 |   const extractedValues = inputSelectors.map(inputSelector =>
 19 |     inputSelector(...args)
 20 |   )
 21 |   return resultFunc(...extractedValues)
 22 | }
 23 | ```
 24 | 
 25 | In this pattern, the `finalSelector` is composed of several <InternalLinks.InputSelectors />, **all receiving the same arguments as the final selector**. Each input selector processes its part of the data, and the results are then combined and further processed by the <InternalLinks.ResultFunction />. Understanding this argument flow is crucial for appreciating how Reselect optimizes data computation and minimizes unnecessary recalculations.
 26 | 
 27 | ## Cascading Memoization
 28 | 
 29 | Reselect uses a two-stage "cascading" approach to memoizing functions:
 30 | 
 31 | The way Reselect works can be broken down into multiple parts:
 32 | 
 33 | 1. **Initial Run**: On the first call, Reselect runs all the <InternalLinks.InputSelectors />, gathers their results, and passes them to the <InternalLinks.ResultFunction />.
 34 | 
 35 | 2. **Subsequent Runs**: For subsequent calls, Reselect performs two levels of checks:
 36 | 
 37 |    - **First Level**: It compares the current arguments with the previous ones (done by `argsMemoize`).
 38 | 
 39 |      - If they're the same, it returns the cached result without running the <InternalLinks.InputSelectors /> or the <InternalLinks.ResultFunction />.
 40 | 
 41 |      - If they differ, it proceeds ("cascades") to the second level.
 42 | 
 43 |    - **Second Level**: It runs the <InternalLinks.InputSelectors /> and compares their current results with the previous ones (done by `memoize`).
 44 |      :::note
 45 | 
 46 |      If any one of the <InternalLinks.InputSelectors /> return a different result, all <InternalLinks.InputSelectors /> will recalculate.
 47 | 
 48 |      :::
 49 | 
 50 |      - If the results are the same, it returns the cached result without running the <InternalLinks.ResultFunction />.
 51 |      - If the results differ, it runs the <InternalLinks.ResultFunction />.
 52 | 
 53 | This behavior is what we call **_Cascading Memoization_**.
 54 | 
 55 | ### Reselect Vs Standard Memoization
 56 | 
 57 | #### Standard Memoization
 58 | 
 59 | ![normal-memoization-function](@site/static/img/normal-memoization-function.png)
 60 | 
 61 | _Standard memoization only compares arguments. If they're the same, it returns the cached result._
 62 | 
 63 | #### Memoization with Reselect
 64 | 
 65 | ![reselect-memoization](@site/static/img/reselect-memoization.png)
 66 | 
 67 | _Reselect adds a second layer of checks with the <InternalLinks.InputSelectors />. This is crucial in <ExternalLinks.Redux /> applications where state references change frequently._
 68 | 
 69 | A normal <ExternalLinks.Memoization /> function will compare the arguments, and if they are the same as last time, it will skip running the function and return the cached result. However, Reselect enhances this by introducing a second tier of checks via its <InternalLinks.InputSelectors />. It's possible that the arguments passed to these <InternalLinks.InputSelectors /> may change, yet their results remain the same. When this occurs, Reselect avoids re-executing the <InternalLinks.ResultFunction />, and returns the cached result.
 70 | 
 71 | This feature becomes crucial in <ExternalLinks.Redux /> applications, where the `state` changes its reference anytime an `action` is dispatched.
 72 | 
 73 | :::note
 74 | 
 75 | The <InternalLinks.InputSelectors /> take the same arguments as the <InternalLinks.OutputSelector />.
 76 | 
 77 | :::
 78 | 
 79 | ## Why Reselect Is Often Used With <ExternalLinks.Redux>Redux</ExternalLinks.Redux>
 80 | 
 81 | While Reselect can be used independently from Redux, it is a standard tool used in most Redux applications to help optimize calculations and UI updates:
 82 | 
 83 | Imagine you have a selector like this:
 84 | 
 85 | ```ts
 86 | const selectCompletedTodos = (state: RootState) =>
 87 |   state.todos.filter(todo => todo.completed === true)
 88 | ```
 89 | 
 90 | So you decide to memoize it:
 91 | 
 92 | ```ts
 93 | const selectCompletedTodos = someMemoizeFunction((state: RootState) =>
 94 |   state.todos.filter(todo => todo.completed === true)
 95 | )
 96 | ```
 97 | 
 98 | Then you update `state.alerts`:
 99 | 
100 | ```ts
101 | store.dispatch(toggleRead(0))
102 | ```
103 | 
104 | Now when you call `selectCompletedTodos`, it re-runs, because we have effectively broken memoization.
105 | 
106 | ```ts
107 | selectCompletedTodos(store.getState())
108 | // Will not run, and the cached result will be returned.
109 | selectCompletedTodos(store.getState())
110 | store.dispatch(toggleRead(0))
111 | // It recalculates.
112 | selectCompletedTodos(store.getState())
113 | ```
114 | 
115 | But why? `selectCompletedTodos` only needs to access `state.todos`, and has nothing to do with `state.alerts`, so why have we broken memoization? Well that's because in <ExternalLinks.Redux /> anytime you make a change to the root `state`, it gets shallowly updated, which means its reference changes, therefore a normal memoization function will always fail the comparison check on the arguments.
116 | 
117 | But with Reselect, we can do something like this:
118 | 
119 | ```ts
120 | const selectCompletedTodos = createSelector(
121 |   [(state: RootState) => state.todos],
122 |   todos => todos.filter(todo => todo.completed === true)
123 | )
124 | ```
125 | 
126 | And now we have achieved memoization:
127 | 
128 | ```ts
129 | selectCompletedTodos(store.getState())
130 | // Will not run, and the cached result will be returned.
131 | selectCompletedTodos(store.getState())
132 | store.dispatch(toggleRead(0))
133 | // The `input selectors` will run, but the `result function` is
134 | // skipped and the cached result will be returned.
135 | selectCompletedTodos(store.getState())
136 | ```
137 | 
138 | Even when the overall `state` changes, Reselect ensures efficient memoization through its unique approach. The <InternalLinks.ResultFunction /> doesn't re-run if the relevant part of the `state` (in this case `state.todos`), remains unchanged. This is due to Reselect's <InternalLinks.CascadingMemoization text="Cascading Memoization" />. The first layer checks the entire `state`, and the second layer checks the results of the <InternalLinks.InputSelectors />. If the first layer fails (due to a change in the overall `state`) but the second layer succeeds (because `state.todos` is unchanged), Reselect skips recalculating the <InternalLinks.ResultFunction />. This dual-check mechanism makes Reselect particularly effective in <ExternalLinks.Redux /> applications, ensuring computations are only done when truly necessary.
139 | 
140 | ---
141 | 


--------------------------------------------------------------------------------
/website/docs/introduction/v5-summary.mdx:
--------------------------------------------------------------------------------
 1 | ---
 2 | id: v5-summary
 3 | title: What's New in 5.0.0?
 4 | sidebar_label: What's New in 5.0.0?
 5 | hide_title: true
 6 | description: What's New in 5.0.0?
 7 | ---
 8 | 
 9 | import { ExternalLinks } from '@site/src/components/ExternalLinks'
10 | import { InternalLinks } from '@site/src/components/InternalLinks'
11 | 
12 | # What's New in 5.0.0?
13 | 
14 | Version 5.0.0 introduces several new features and improvements:
15 | 
16 | ## Customization Enhancements
17 | 
18 | - Added the ability to pass an options object to <InternalLinks.CreateSelectorCreator />, allowing for customized `memoize` and `argsMemoize` functions, alongside their respective options (`memoizeOptions` and `argsMemoizeOptions`).
19 | - The <InternalLinks.CreateSelector /> function now supports direct customization of `memoize` and `argsMemoize` within its options object.
20 | 
21 | ## Memoization Functions
22 | 
23 | - Introduced new experimental memoization functions: `weakMapMemoize` and `unstable_autotrackMemoize`.
24 | - Incorporated `memoize` and `argsMemoize` into the <InternalLinks.OutputSelectorFields /> for debugging purposes.
25 | 
26 | ## TypeScript Support and Performance
27 | 
28 | - Discontinued support for TypeScript versions below 4.7, aligning with modern TypeScript features.
29 | - Significantly improved TypeScript performance for nesting <InternalLinks.OutputSelector text="output selectors" />. The nesting limit has increased from approximately 8 to around 30 <InternalLinks.OutputSelector text="output selectors" />, greatly reducing the occurrence of the infamous `Type instantiation is excessively deep and possibly infinite` error.
30 | 
31 | ## Selector API Enhancements
32 | 
33 | - Removed the second overload of <InternalLinks.CreateStructuredSelector /> due to its susceptibility to runtime errors.
34 | 
35 | ## Additional Functionalities
36 | 
37 | - Added `dependencyRecomputations` and `resetDependencyRecomputations` to the <InternalLinks.OutputSelectorFields />. These additions provide greater control and insight over <InternalLinks.InputSelectors />, complementing the new `argsMemoize` API.
38 | - Introduced `inputStabilityCheck`, a development tool that runs the <InternalLinks.InputSelectors /> twice using the same arguments and triggers a warning If they return differing results for the same call.
39 | - Introduced `identityFunctionCheck`, a development tool that checks to see if the <InternalLinks.ResultFunction /> is an <ExternalLinks.IdentityFunction /> .
40 | 
41 | These updates aim to enhance flexibility, performance, and developer experience. For detailed usage and examples, refer to the updated documentation sections for each feature.
42 | 
43 | ## Breaking Changes
44 | 
45 | - Switched the default memoization function used by `createSelector` to `weakMapMemoize`.
46 | - Renamed `defaultMemoize` to `lruMemoize` as it is no longer the default memoization function passed to `createSelector`.
47 | - Renamed `defaultEqualityCheck` to `referenceEqualityCheck`.
48 | - Renamed `DefaultMemoizeOptions` to `LruMemoizeOptions`.
49 | - Removed `ParametricSelector` and `OutputParametricSelector` types. Their functionalities are now integrated into `Selector` and `OutputSelector` respectively, which inherently support additional parameters.
50 | 


--------------------------------------------------------------------------------
/website/docs/related-projects.mdx:
--------------------------------------------------------------------------------
 1 | ---
 2 | id: related-projects
 3 | title: Related Projects
 4 | sidebar_label: Related Projects
 5 | hide_title: true
 6 | description: Related Projects
 7 | ---
 8 | 
 9 | import { InternalLinks } from '@site/src/components/InternalLinks'
10 | import { ExternalLinks } from '@site/src/components/ExternalLinks'
11 | import Link from '@docusaurus/Link'
12 | 
13 | # Related Projects
14 | 
15 | ## <ExternalLinks.ReReselect>Re-reselect</ExternalLinks.ReReselect>
16 | 
17 | Enhances Reselect selectors by wrapping <InternalLinks.CreateSelector /> and returning a memoized collection of selectors indexed with the cache key returned by a custom resolver function.
18 | 
19 | Useful to reduce selectors recalculation when the same selector is repeatedly called with one/few different arguments.
20 | 
21 | ## <Link to='https://github.com/skortchmark9/reselect-tools'>reselect-tools</Link>
22 | 
23 | - Measure selector recomputations across the app and identify performance bottlenecks
24 | - Check selector dependencies, inputs, outputs, and recomputations at any time with the chrome extension
25 | - Statically export a JSON representation of your selector graph for further analysis
26 | 
27 | ## <Link to='https://github.com/vlanemcev/reselect-debugger-flipper'>reselect-debugger</Link>
28 | 
29 | <!-- prettier-ignore -->
30 | <Link to="https://github.com/vlanemcev/flipper-plugin-reselect-debugger">Flipper plugin</Link> and
31 | <Link to="https://github.com/vlanemcev/reselect-debugger-flipper">the connect app</Link> for debugging selectors in **React Native Apps**.
32 | 
33 | Inspired by Reselect Tools, so it also has all functionality from this library and more, but only for React Native and Flipper.
34 | 
35 | - Selectors Recomputations count in live time across the App for identify performance bottlenecks
36 | - Highlight most recomputed selectors
37 | - Dependency Graph
38 | - Search by Selectors Graph
39 | - Selectors Inputs
40 | - Selectors Output (In case if selector not dependent from external arguments)
41 | - Shows "Not Memoized (NM)" selectors
42 | 


--------------------------------------------------------------------------------
/website/docs/usage/best-practices.mdx:
--------------------------------------------------------------------------------
 1 | ---
 2 | id: best-practices
 3 | title: Best Practices
 4 | sidebar_label: Best Practices
 5 | hide_title: true
 6 | description: Best Practices
 7 | ---
 8 | 
 9 | import { InternalLinks } from '@site/src/components/InternalLinks'
10 | import { ExternalLinks } from '@site/src/components/ExternalLinks'
11 | 
12 | # Best Practices
13 | 
14 | There are a few details that will help you skip running as many functions as possible and get the best possible performance out of Reselect:
15 | 
16 | - Due to the <InternalLinks.CascadingMemoization /> in Reselect, The first layer of checks is upon the arguments that are passed to the <InternalLinks.OutputSelector />, therefore it's best to maintain the same reference for the arguments as much as possible.
17 | - In <ExternalLinks.Redux />, your state will change reference when updated. But it's best to keep the additional arguments as simple as possible, you can pass in objects or array as long as their reference does not change. Or you can pass in primitives like numbers for ids.
18 | - Keep your <InternalLinks.InputSelectors /> as simple as possible. It's best if they mostly consist of field accessors like `state => state.todos` or argument providers like `(state, id) => id`. You should not be doing any sort of calculation inside <InternalLinks.InputSelectors />, and you should definitely not be returning an object or array with a new reference each time.
19 | - The <InternalLinks.ResultFunction /> is only re-run as a last resort. So make sure to put any and all calculations inside your <InternalLinks.ResultFunction />. That way, Reselect will only run those calculations if all other checks fail.
20 | 
21 | This:
22 | 
23 | ```ts
24 | // ✔️ This is optimal because we have less calculations in input selectors and more in the result function.
25 | const selectorGood = createSelector(
26 |   [(state: RootState) => state.todos],
27 |   todos => someExpensiveComputation(todos)
28 | )
29 | ```
30 | 
31 | Is preferable to this:
32 | 
33 | ```ts
34 | // ❌ This is not optimal!
35 | const selectorBad = createSelector(
36 |   [(state: RootState) => someExpensiveComputation(state.todos)],
37 |   someOtherCalculation
38 | )
39 | ```
40 | 


--------------------------------------------------------------------------------
/website/docs/usage/common-mistakes.mdx:
--------------------------------------------------------------------------------
 1 | ---
 2 | id: common-mistakes
 3 | title: Common Mistakes
 4 | sidebar_label: Common Mistakes
 5 | hide_title: true
 6 | description: Common Mistakes
 7 | ---
 8 | 
 9 | import { InternalLinks } from '@site/src/components/InternalLinks'
10 | 
11 | # Common Mistakes
12 | 
13 | A somewhat common mistake is to write an <InternalLinks.InputSelectors text="input selector" /> that extracts a value or does some derivation, and a <InternalLinks.ResultFunction /> that just returns its result:
14 | 
15 | ```ts
16 | // ❌ BROKEN: this will not memoize correctly, and does nothing useful!
17 | const brokenSelector = createSelector(
18 |   [(state: RootState) => state.todos],
19 |   todos => todos
20 | )
21 | ```
22 | 
23 | Any <InternalLinks.ResultFunction /> that just returns its inputs is incorrect! The <InternalLinks.ResultFunction /> should always have the transformation logic.
24 | 
25 | Similarly:
26 | 
27 | ```ts
28 | // ❌ BROKEN: this will not memoize correctly!
29 | const brokenSelector = createSelector(
30 |   [(state: RootState) => state],
31 |   state => state.todos
32 | )
33 | ```
34 | 


--------------------------------------------------------------------------------
/website/docs/usage/handling-empty-array-results.mdx:
--------------------------------------------------------------------------------
  1 | ---
  2 | id: handling-empty-array-results
  3 | title: Handling Empty Array Results
  4 | sidebar_label: Handling Empty Array Results
  5 | hide_title: true
  6 | description: Handling Empty Array Results
  7 | ---
  8 | 
  9 | import Tabs from '@theme/Tabs'
 10 | import TabItem from '@theme/TabItem'
 11 | import { InternalLinks } from '@site/src/components/InternalLinks'
 12 | 
 13 | # Handling Empty Array Results
 14 | 
 15 | To reduce recalculations, use a predefined empty array when `array.filter` or similar methods result in an empty array.
 16 | 
 17 | So you can have a pattern like this:
 18 | 
 19 | {/* START: handling-empty-array-results/firstPattern.ts */}
 20 | 
 21 | <Tabs
 22 |   groupId='language'
 23 |   defaultValue='ts'
 24 |   values={[
 25 |     {label: 'TypeScript', value: 'ts'},
 26 |     {label: 'JavaScript', value: 'js'},
 27 |   ]}>
 28 |   <TabItem value='ts'>
 29 | 
 30 | ```ts title="handling-empty-array-results/firstPattern.ts"
 31 | import { createSelector } from 'reselect'
 32 | 
 33 | export interface RootState {
 34 |   todos: {
 35 |     id: number
 36 |     title: string
 37 |     description: string
 38 |     completed: boolean
 39 |   }[]
 40 | }
 41 | 
 42 | const EMPTY_ARRAY: [] = []
 43 | 
 44 | const selectCompletedTodos = createSelector(
 45 |   [(state: RootState) => state.todos],
 46 |   todos => {
 47 |     const completedTodos = todos.filter(todo => todo.completed === true)
 48 |     return completedTodos.length === 0 ? EMPTY_ARRAY : completedTodos
 49 |   }
 50 | )
 51 | ```
 52 | 
 53 |   </TabItem>
 54 |   <TabItem value='js'>
 55 | 
 56 | ```js title="handling-empty-array-results/firstPattern.js"
 57 | import { createSelector } from 'reselect'
 58 | 
 59 | const EMPTY_ARRAY = []
 60 | 
 61 | const selectCompletedTodos = createSelector([state => state.todos], todos => {
 62 |   const completedTodos = todos.filter(todo => todo.completed === true)
 63 |   return completedTodos.length === 0 ? EMPTY_ARRAY : completedTodos
 64 | })
 65 | ```
 66 | 
 67 |   </TabItem>
 68 | </Tabs>
 69 | 
 70 | {/* END: handling-empty-array-results/firstPattern.ts */}
 71 | 
 72 | Or to avoid repetition, you can create a wrapper function and reuse it:
 73 | 
 74 | {/* START: handling-empty-array-results/fallbackToEmptyArray.ts */}
 75 | 
 76 | <Tabs
 77 |   groupId='language'
 78 |   defaultValue='ts'
 79 |   values={[
 80 |     {label: 'TypeScript', value: 'ts'},
 81 |     {label: 'JavaScript', value: 'js'},
 82 |   ]}>
 83 |   <TabItem value='ts'>
 84 | 
 85 | ```ts title="handling-empty-array-results/fallbackToEmptyArray.ts"
 86 | import { createSelector } from 'reselect'
 87 | import type { RootState } from './firstPattern'
 88 | 
 89 | const EMPTY_ARRAY: [] = []
 90 | 
 91 | export const fallbackToEmptyArray = <T>(array: T[]) => {
 92 |   return array.length === 0 ? EMPTY_ARRAY : array
 93 | }
 94 | 
 95 | const selectCompletedTodos = createSelector(
 96 |   [(state: RootState) => state.todos],
 97 |   todos => {
 98 |     return fallbackToEmptyArray(todos.filter(todo => todo.completed === true))
 99 |   }
100 | )
101 | ```
102 | 
103 |   </TabItem>
104 |   <TabItem value='js'>
105 | 
106 | ```js title="handling-empty-array-results/fallbackToEmptyArray.js"
107 | import { createSelector } from 'reselect'
108 | 
109 | const EMPTY_ARRAY = []
110 | 
111 | export const fallbackToEmptyArray = array => {
112 |   return array.length === 0 ? EMPTY_ARRAY : array
113 | }
114 | 
115 | const selectCompletedTodos = createSelector([state => state.todos], todos => {
116 |   return fallbackToEmptyArray(todos.filter(todo => todo.completed === true))
117 | })
118 | ```
119 | 
120 |   </TabItem>
121 | </Tabs>
122 | 
123 | {/* END: handling-empty-array-results/fallbackToEmptyArray.ts */}
124 | 
125 | This way if the <InternalLinks.ResultFunction /> returns an empty array twice in a row, your component will not re-render due to a stable empty array reference:
126 | 
127 | ```ts
128 | const completedTodos = selectCompletedTodos(store.getState())
129 | 
130 | store.dispatch(addTodo())
131 | 
132 | console.log(completedTodos === selectCompletedTodos(store.getState())) //=> true
133 | ```
134 | 


--------------------------------------------------------------------------------
/website/docusaurus.config.ts:
--------------------------------------------------------------------------------
  1 | import type { Options, ThemeConfig } from '@docusaurus/preset-classic'
  2 | import type { Config } from '@docusaurus/types'
  3 | 
  4 | const config: Config = {
  5 |   title: 'Reselect',
  6 |   tagline: 'A memoized selector library for Redux',
  7 |   favicon: 'img/favicon.ico',
  8 | 
  9 |   // Set the production url of your site here
 10 |   url: 'https://reselect.js.org',
 11 |   // Set the /<baseUrl>/ pathname under which your site is served
 12 |   // For GitHub pages deployment, it is often '/<projectName>/'
 13 |   baseUrl: '/',
 14 | 
 15 |   // GitHub pages deployment config.
 16 |   // If you aren't using GitHub pages, you don't need these.
 17 |   organizationName: 'reduxjs', // Usually your GitHub org/user name.
 18 |   projectName: 'reselect', // Usually your repo name.
 19 | 
 20 |   onBrokenLinks: 'throw',
 21 |   onBrokenMarkdownLinks: 'warn',
 22 | 
 23 |   // Even if you don't use internationalization, you can use this field to set
 24 |   // useful metadata like html lang. For example, if your site is Chinese, you
 25 |   // may want to replace "en" with "zh-Hans".
 26 |   i18n: {
 27 |     defaultLocale: 'en',
 28 |     locales: ['en']
 29 |   },
 30 | 
 31 |   presets: [
 32 |     [
 33 |       'classic',
 34 |       {
 35 |         docs: {
 36 |           path: 'docs',
 37 |           sidebarPath: './sidebars.ts',
 38 |           showLastUpdateTime: true,
 39 |           routeBasePath: '/',
 40 |           // Please change this to your repo.
 41 |           // Remove this to remove the "edit this page" links.
 42 |           editUrl: 'https://github.com/reduxjs/reselect/edit/master/website'
 43 |         },
 44 |         theme: {
 45 |           customCss: './src/css/custom.css'
 46 |         }
 47 |       } satisfies Options
 48 |     ]
 49 |   ],
 50 | 
 51 |   themeConfig: {
 52 |     // Replace with your project's social card
 53 |     // image: 'img/docusaurus-social-card.jpg',
 54 |     navbar: {
 55 |       title: 'Reselect',
 56 | 
 57 |       items: [
 58 |         {
 59 |           type: 'doc',
 60 |           position: 'right',
 61 |           label: 'Getting Started',
 62 |           docId: 'introduction/getting-started'
 63 |         },
 64 |         {
 65 |           type: 'doc',
 66 |           position: 'right',
 67 |           label: 'API',
 68 |           docId: 'api/createSelector'
 69 |         },
 70 |         {
 71 |           href: 'https://www.github.com/reduxjs/reselect',
 72 |           label: 'GitHub',
 73 |           position: 'right'
 74 |         }
 75 |       ]
 76 |     },
 77 |     footer: {
 78 |       style: 'dark',
 79 |       links: [
 80 |         {
 81 |           title: 'Community',
 82 |           items: [
 83 |             {
 84 |               label: 'Stack Overflow',
 85 |               href: 'https://stackoverflow.com/questions/tagged/reselect'
 86 |             },
 87 |             {
 88 |               label: 'Discord',
 89 |               href: 'https://discord.gg/0ZcbPKXt5bZ6au5t'
 90 |             }
 91 |           ]
 92 |         },
 93 |         {
 94 |           title: 'More',
 95 |           items: [
 96 |             {
 97 |               label: 'GitHub',
 98 |               href: 'https://github.com/reduxjs/reselect'
 99 |             }
100 |           ]
101 |         }
102 |       ],
103 |       copyright: `Copyright © ${new Date().getFullYear()} by the Redux Maintainers. Built with Docusaurus.`
104 |     },
105 |     prism: {
106 |       theme: require('./monokaiTheme.js')
107 |     }
108 |   } satisfies ThemeConfig
109 | }
110 | 
111 | export default config
112 | 


--------------------------------------------------------------------------------
/website/insertCodeExamples.ts:
--------------------------------------------------------------------------------
  1 | import { readFileSync, readdirSync, writeFileSync } from 'node:fs'
  2 | import path from 'node:path'
  3 | import {
  4 |   EXAMPLES_DIRECTORY,
  5 |   getTSConfig,
  6 |   hasTSXExtension,
  7 |   tsExtensionRegex
  8 | } from './compileExamples'
  9 | 
 10 | const placeholderRegex =
 11 |   /\{\/\* START: (.*?) \*\/\}([\s\S]*?)\{\/\* END: \1 \*\/\}/g
 12 | 
 13 | const collectMarkdownFiles = (
 14 |   directory: string,
 15 |   files: { path: string; content: string }[] = []
 16 | ) => {
 17 |   readdirSync(directory, {
 18 |     withFileTypes: true
 19 |   }).forEach(entry => {
 20 |     const filePath = path.join(directory, entry.name)
 21 |     if (entry.isDirectory()) {
 22 |       collectMarkdownFiles(filePath, files)
 23 |     } else if (/\.mdx?$/.test(entry.name)) {
 24 |       const content = readFileSync(filePath, 'utf-8')
 25 |       if (content.match(placeholderRegex)) {
 26 |         files.push({ path: filePath, content })
 27 |       }
 28 |     }
 29 |   })
 30 |   return files
 31 | }
 32 | 
 33 | const insertCodeExamples = (examplesDirectory: string) => {
 34 |   const frontMatterRegex = /---\s*[\s\S]*?---/
 35 |   const markdownFilesPaths = collectMarkdownFiles('docs')
 36 |   markdownFilesPaths.forEach(({ path: markdownFilePath, content }) => {
 37 |     const importTabs = content.includes('import Tabs from')
 38 |       ? ''
 39 |       : `import Tabs from '@theme/Tabs'\n`
 40 |     const importTabItem = content.includes('import TabItem from')
 41 |       ? ''
 42 |       : `import TabItem from '@theme/TabItem'\n`
 43 |     content = content.replace(
 44 |       frontMatterRegex,
 45 |       frontMatter => `${frontMatter}\n${importTabs}${importTabItem}`
 46 |     )
 47 | 
 48 |     content = content.replace(
 49 |       placeholderRegex,
 50 |       (placeholder, tsFileName: string) => {
 51 |         const isTSX = hasTSXExtension(tsFileName)
 52 | 
 53 |         const tsFileExtension = isTSX ? 'tsx' : 'ts'
 54 |         const jsFileExtension = isTSX ? 'jsx' : 'js'
 55 | 
 56 |         const jsFileName = tsFileName.replace(
 57 |           tsExtensionRegex,
 58 |           `.${jsFileExtension}`
 59 |         )
 60 | 
 61 |         const tsFilePath = path.join(examplesDirectory, tsFileName)
 62 |         const jsFilePath = path.join(
 63 |           examplesDirectory,
 64 |           getTSConfig(examplesDirectory).compilerOptions.outDir,
 65 |           tsFileName.replace(tsExtensionRegex, `.${jsFileExtension}`)
 66 |         )
 67 | 
 68 |         const tsFileContent = readFileSync(tsFilePath, 'utf-8')
 69 |         const jsFileContent = readFileSync(jsFilePath, 'utf-8')
 70 | 
 71 |         return `{/* START: ${tsFileName} */}
 72 | 
 73 | <Tabs
 74 |   groupId='language'
 75 |   defaultValue='${tsFileExtension}'
 76 |   values={[
 77 |     {label: 'TypeScript', value: '${tsFileExtension}'},
 78 |     {label: 'JavaScript', value: '${jsFileExtension}'},
 79 |   ]}>
 80 |   <TabItem value='${tsFileExtension}'>
 81 | 
 82 |   \`\`\`${tsFileExtension} title="${tsFileName}"
 83 |   ${tsFileContent}
 84 |   \`\`\`
 85 |   </TabItem>
 86 |   <TabItem value='${jsFileExtension}'>
 87 | 
 88 |   \`\`\`${jsFileExtension} title="${jsFileName}"
 89 |   ${jsFileContent}
 90 |   \`\`\`
 91 |   </TabItem>
 92 | </Tabs>
 93 | 
 94 | {/* END: ${tsFileName} */}`
 95 |       }
 96 |     )
 97 |     writeFileSync(markdownFilePath, content)
 98 |   })
 99 | }
100 | 
101 | insertCodeExamples(EXAMPLES_DIRECTORY)
102 | 


--------------------------------------------------------------------------------
/website/monokaiTheme.js:
--------------------------------------------------------------------------------
 1 | module.exports = {
 2 |   plain: {
 3 |     color: '#f8f8f2',
 4 |     backgroundColor: '#272822'
 5 |   },
 6 |   styles: [
 7 |     {
 8 |       types: ['comment', 'prolog', 'doctype', 'cdata'],
 9 |       style: {
10 |         color: '#778090'
11 |       }
12 |     },
13 |     {
14 |       types: ['punctuation'],
15 |       style: {
16 |         color: '#F8F8F2'
17 |       }
18 |     },
19 |     {
20 |       types: ['property', 'tag', 'constant', 'symbol', 'deleted'],
21 |       style: {
22 |         color: '#F92672'
23 |       }
24 |     },
25 |     {
26 |       types: ['boolean', 'number'],
27 |       style: {
28 |         color: '#AE81FF'
29 |       }
30 |     },
31 |     {
32 |       types: ['selector', 'attr-name', 'string', 'char', 'builtin', 'inserted'],
33 |       style: {
34 |         color: '#a6e22e'
35 |       }
36 |     },
37 |     {
38 |       types: ['operator', 'entity', 'url', 'variable'],
39 |       style: {
40 |         color: '#F8F8F2'
41 |       }
42 |     },
43 |     {
44 |       types: ['atrule', 'attr-value', 'function'],
45 |       style: {
46 |         color: '#E6D874'
47 |       }
48 |     },
49 |     {
50 |       types: ['keyword'],
51 |       style: {
52 |         color: '#F92672'
53 |       }
54 |     },
55 |     {
56 |       types: ['regex', 'important'],
57 |       style: {
58 |         color: '#FD971F'
59 |       }
60 |     }
61 |   ]
62 | }
63 | 


--------------------------------------------------------------------------------
/website/package.json:
--------------------------------------------------------------------------------
 1 | {
 2 |   "name": "website",
 3 |   "scripts": {
 4 |     "docusaurus": "docusaurus",
 5 |     "start": "yarn prestart && docusaurus start",
 6 |     "build": "docusaurus build",
 7 |     "swizzle": "docusaurus swizzle",
 8 |     "deploy": "docusaurus deploy",
 9 |     "clear": "docusaurus clear",
10 |     "serve": "docusaurus serve",
11 |     "write-translations": "docusaurus write-translations",
12 |     "write-heading-ids": "docusaurus write-heading-ids",
13 |     "prestart": "yarn examples:clean && yarn examples:build && ts-node insertCodeExamples.ts && yarn format",
14 |     "format": "prettier --write \"**/*.{ts,tsx}\" \"docs\"",
15 |     "examples:clean": "rimraf ../docs/examples/dist",
16 |     "examples:format": "prettier --write ../docs/examples",
17 |     "examples:build": "ts-node compileExamples.ts && yarn examples:format",
18 |     "typecheck": "tsc"
19 |   },
20 |   "dependencies": {
21 |     "@docusaurus/core": "3.0.0",
22 |     "@docusaurus/preset-classic": "3.0.0",
23 |     "@mdx-js/react": "^3.0.0",
24 |     "clsx": "^1.2.1",
25 |     "prism-react-renderer": "^2.1.0",
26 |     "react": "^18.2.0",
27 |     "react-dom": "^18.2.0"
28 |   },
29 |   "devDependencies": {
30 |     "@docusaurus/module-type-aliases": "3.0.0",
31 |     "@docusaurus/tsconfig": "3.0.0",
32 |     "@docusaurus/types": "3.0.0",
33 |     "netlify-plugin-cache": "^1.0.3",
34 |     "prettier": "^3.1.0",
35 |     "rimraf": "^5.0.5",
36 |     "ts-node": "^10.9.1",
37 |     "typescript": "~5.2.2"
38 |   },
39 |   "browserslist": {
40 |     "production": [
41 |       ">0.5%",
42 |       "not dead",
43 |       "not op_mini all"
44 |     ],
45 |     "development": [
46 |       "last 3 chrome version",
47 |       "last 3 firefox version",
48 |       "last 5 safari version"
49 |     ]
50 |   },
51 |   "engines": {
52 |     "node": ">=18.0"
53 |   }
54 | }
55 | 


--------------------------------------------------------------------------------
/website/sidebars.ts:
--------------------------------------------------------------------------------
 1 | import type { SidebarsConfig } from '@docusaurus/plugin-content-docs'
 2 | 
 3 | /**
 4 |  * Creating a sidebar enables you to:
 5 |  - create an ordered group of docs
 6 |  - render a sidebar for each doc of that group
 7 |  - provide next/previous navigation
 8 | 
 9 |  The sidebars can be generated from the filesystem, or explicitly defined here.
10 | 
11 |  Create as many sidebars as you want.
12 |  */
13 | const sidebars: SidebarsConfig = {
14 |   // By default, Docusaurus generates a sidebar from the docs folder structure
15 |   docsSidebar: [
16 |     // { type: 'autogenerated', dirName: '.' }
17 |     {
18 |       type: 'category',
19 |       collapsed: false,
20 |       label: 'Introduction',
21 |       items: [
22 |         'introduction/getting-started',
23 |         'introduction/how-does-reselect-work',
24 |         'introduction/v5-summary'
25 |       ]
26 |     },
27 |     {
28 |       type: 'category',
29 |       collapsed: false,
30 |       label: 'API',
31 |       items: [
32 |         'api/createSelector',
33 |         'api/createSelectorCreator',
34 |         'api/createStructuredSelector',
35 |         'api/development-only-stability-checks',
36 |         {
37 |           type: 'category',
38 |           collapsed: false,
39 |           label: 'Memoization Functions',
40 |           items: [
41 |             'api/lruMemoize',
42 |             'api/weakMapMemoize',
43 |             'api/unstable_autotrackMemoize'
44 |           ]
45 |         }
46 |       ]
47 |     },
48 | 
49 |     {
50 |       type: 'category',
51 |       label: 'Using Reselect',
52 |       items: [
53 |         'usage/best-practices',
54 |         'usage/common-mistakes',
55 |         'usage/handling-empty-array-results'
56 |       ]
57 |     },
58 |     'FAQ',
59 |     'external-references',
60 |     'related-projects'
61 |   ]
62 | }
63 | 
64 | export default sidebars
65 | 


--------------------------------------------------------------------------------
/website/src/components/ExternalLinks.tsx:
--------------------------------------------------------------------------------
 1 | import Link from '@docusaurus/Link'
 2 | import type { FC, ReactNode } from 'react'
 3 | import { memo } from 'react'
 4 | 
 5 | interface Props {
 6 |   readonly text?: ReactNode
 7 | }
 8 | 
 9 | export const ExternalLinks = {
10 |   WeakMap: memo(({ text = 'WeakMap' }) => (
11 |     <Link
12 |       to="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WeakMap"
13 |       title="WeakMap"
14 |     >
15 |       <code>{text}</code>
16 |     </Link>
17 |   )),
18 |   ReferenceEqualityCheck: memo(({ text = 'Reference Equality Check' }) => (
19 |     <Link
20 |       to="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Strict_equality"
21 |       title="Reference Equality Check"
22 |     >
23 |       {text}
24 |     </Link>
25 |   )),
26 |   Memoization: memo(({ text = 'memoization' }) => (
27 |     <Link to="https://en.wikipedia.org/wiki/Memoization" title="Memoization">
28 |       {text}
29 |     </Link>
30 |   )),
31 |   IdentityFunction: memo(({ text = 'Identity Function' }) => (
32 |     <Link
33 |       to="https://en.wikipedia.org/wiki/Identity_function"
34 |       title="Identity Function"
35 |     >
36 |       {text}
37 |     </Link>
38 |   )),
39 |   UseMemo: memo(({ text = 'useMemo' }) => (
40 |     <Link
41 |       to="https://react.dev/reference/react/useMemo#usememo"
42 |       title="useMemo"
43 |     >
44 |       <code>{text}</code>
45 |     </Link>
46 |   )),
47 |   ReReselect: memo(({ text = 'Re-reselect' }) => (
48 |     <Link to="https://github.com/toomuchdesign/re-reselect" title="re-reselect">
49 |       {text}
50 |     </Link>
51 |   )),
52 |   Redux: memo(({ text = 'Redux' }) => (
53 |     <Link to="https://redux.js.org" title="Redux">
54 |       {text}
55 |     </Link>
56 |   )),
57 |   React: memo(({ text = 'React' }) => (
58 |     <Link to="https://react.dev" title="React">
59 |       {text}
60 |     </Link>
61 |   )),
62 |   ReactRedux: memo(({ text = 'React-Redux' }) => (
63 |     <Link to="https://react-redux.js.org" title="React-Redux">
64 |       {text}
65 |     </Link>
66 |   )),
67 |   ReduxToolkit: memo(({ text = 'Redux-Toolkit' }) => (
68 |     <Link to="https://redux-toolkit.js.org" title="Redux-Toolkit">
69 |       {text}
70 |     </Link>
71 |   ))
72 | } as const satisfies Record<string, FC<Props>>
73 | 
74 | export const AllExternalLinks: FC = memo(() => {
75 |   return (
76 |     <ul>
77 |       {Object.values(ExternalLinks).map((ExternalLink, index) => (
78 |         <li key={index}>
79 |           <b>
80 |             <ExternalLink />
81 |           </b>
82 |         </li>
83 |       ))}
84 |     </ul>
85 |   )
86 | })
87 | 


--------------------------------------------------------------------------------
/website/src/components/HomepageFeatures/index.tsx:
--------------------------------------------------------------------------------
 1 | import Heading from '@theme/Heading'
 2 | import clsx from 'clsx'
 3 | import type { FC, JSX } from 'react'
 4 | import { memo } from 'react'
 5 | import styles from './styles.module.css'
 6 | 
 7 | interface FeatureItem {
 8 |   title: string
 9 |   description: JSX.Element
10 | }
11 | 
12 | const FeatureList: FeatureItem[] = [
13 |   {
14 |     title: 'Predictable',
15 |     description: (
16 |       <>
17 |         Like Redux, Reselect gives users a <b>consistent mental model</b> for
18 |         memoizing functions. Extract input values, recalculate when any input
19 |         changes.
20 |       </>
21 |     )
22 |   },
23 |   {
24 |     title: 'Optimized',
25 |     description: (
26 |       <>
27 |         Reselect{' '}
28 |         <b>
29 |           minimizes the number of times expensive computations are performed
30 |         </b>
31 |         , reuses existing result references if nothing has changed, and improves
32 |         performance.
33 |       </>
34 |     )
35 |   },
36 |   {
37 |     title: 'Customizable',
38 |     description: (
39 |       <>
40 |         Reselect comes with fast defaults, but provides{' '}
41 |         <b>flexible customization options</b>. Swap memoization methods, change
42 |         equality checks, and customize for your needs.
43 |       </>
44 |     )
45 |   },
46 |   {
47 |     title: 'Type-Safe',
48 |     description: (
49 |       <>
50 |         Reselect is designed for <b>great TypeScript support</b>. Generated
51 |         selectors infer all types from input selectors.
52 |       </>
53 |     )
54 |   }
55 | ]
56 | 
57 | const Feature: FC<FeatureItem> = memo(({ title, description }) => {
58 |   return (
59 |     <div className={clsx('col col--3')}>
60 |       <div className="text--center padding-horiz--md">
61 |         <Heading as="h3">{title}</Heading>
62 |         <p>{description}</p>
63 |       </div>
64 |     </div>
65 |   )
66 | })
67 | 
68 | const HomepageFeatures: FC = () => {
69 |   return (
70 |     <section className={styles.features}>
71 |       <div className="container">
72 |         <div className="row">
73 |           {FeatureList.map((props, idx) => (
74 |             <Feature key={idx} {...props} />
75 |           ))}
76 |         </div>
77 |       </div>
78 |     </section>
79 |   )
80 | }
81 | 
82 | export default memo(HomepageFeatures)
83 | 


--------------------------------------------------------------------------------
/website/src/components/HomepageFeatures/styles.module.css:
--------------------------------------------------------------------------------
 1 | .features {
 2 |   display: flex;
 3 |   align-items: center;
 4 |   padding: 2rem 0;
 5 |   width: 100%;
 6 | }
 7 | 
 8 | .featureSvg {
 9 |   height: 200px;
10 |   width: 200px;
11 | }
12 | 


--------------------------------------------------------------------------------
/website/src/components/InternalLinks.tsx:
--------------------------------------------------------------------------------
 1 | import Link from '@docusaurus/Link'
 2 | import type { FC, ReactNode } from 'react'
 3 | import { memo } from 'react'
 4 | 
 5 | interface Props {
 6 |   readonly text: ReactNode
 7 | }
 8 | 
 9 | export const InternalLinks = {
10 |   Selector: memo(({ text = 'selector' }) => (
11 |     <Link
12 |       to="/introduction/getting-started#selector-function"
13 |       title="Selector Function"
14 |     >
15 |       {text}
16 |     </Link>
17 |   )),
18 |   InputSelectors: memo(({ text = 'input selectors' }) => (
19 |     <Link
20 |       to="/introduction/getting-started#input-selectors"
21 |       title="Input Selectors"
22 |     >
23 |       {text}
24 |     </Link>
25 |   )),
26 |   OutputSelector: memo(({ text = 'output selector' }) => (
27 |     <Link
28 |       to="/introduction/getting-started#output-selector"
29 |       title="Output Selector"
30 |     >
31 |       {text}
32 |     </Link>
33 |   )),
34 |   ResultFunction: memo(({ text = 'result function' }) => (
35 |     <Link
36 |       to="/introduction/getting-started#result-function"
37 |       title="Result Function"
38 |     >
39 |       {text}
40 |     </Link>
41 |   )),
42 |   Dependencies: memo(({ text = 'dependencies' }) => (
43 |     <Link to="/introduction/getting-started#dependencies" title="Dependencies">
44 |       {text}
45 |     </Link>
46 |   )),
47 |   CascadingMemoization: memo(({ text = 'Cascading Memoization' }) => (
48 |     <Link
49 |       to="/introduction/how-does-reselect-work#cascading-memoization"
50 |       title="Cascading Memoization"
51 |     >
52 |       "{text}"
53 |     </Link>
54 |   )),
55 |   OutputSelectorFields: memo(({ text = 'Output Selector Fields' }) => (
56 |     <Link
57 |       to="/api/createSelector#output-selector-fields"
58 |       title="Output Selector Fields"
59 |     >
60 |       {text}
61 |     </Link>
62 |   )),
63 |   CreateSelector: memo(() => (
64 |     <Link to="/api/createSelector" title="createSelector">
65 |       <code>createSelector</code>
66 |     </Link>
67 |   )),
68 |   CreateSelectorCreator: memo(() => (
69 |     <Link to="/api/createSelectorCreator" title="createSelectorCreator">
70 |       <code>createSelectorCreator</code>
71 |     </Link>
72 |   )),
73 |   LruMemoize: memo(() => (
74 |     <Link to="/api/lruMemoize" title="lruMemoize">
75 |       <code>lruMemoize</code>
76 |     </Link>
77 |   )),
78 |   WeakMapMemoize: memo(() => (
79 |     <Link to="/api/weakMapMemoize" title="weakMapMemoize">
80 |       <code>weakMapMemoize</code>
81 |     </Link>
82 |   )),
83 |   UnstableAutotrackMemoize: memo(() => (
84 |     <Link to="/api/unstable_autotrackMemoize" title="unstable_autotrackMemoize">
85 |       <code>unstable_autotrackMemoize</code>
86 |     </Link>
87 |   )),
88 |   CreateStructuredSelector: memo(() => (
89 |     <Link to="/api/createStructuredSelector" title="createStructuredSelector">
90 |       <code>createStructuredSelector</code>
91 |     </Link>
92 |   ))
93 | } as const satisfies Record<string, FC<Props>>
94 | 


--------------------------------------------------------------------------------
/website/src/components/PackageManagerTabs.tsx:
--------------------------------------------------------------------------------
 1 | import CodeBlock from '@theme/CodeBlock'
 2 | import TabItem from '@theme/TabItem'
 3 | import Tabs from '@theme/Tabs'
 4 | import type { FC } from 'react'
 5 | import { memo } from 'react'
 6 | 
 7 | const PACKAGE_NAME = 'reselect'
 8 | 
 9 | const packageManagers = [
10 |   { value: 'npm', label: 'NPM', command: 'install' },
11 |   { value: 'yarn', label: 'Yarn', command: 'add' },
12 |   { value: 'bun', label: 'Bun', command: 'add' },
13 |   { value: 'pnpm', label: 'PNPM', command: 'add' }
14 | ] as const
15 | 
16 | const PackageManagerTabs: FC = () => {
17 |   return (
18 |     <Tabs groupId="packageManagers" defaultValue={packageManagers[0].value}>
19 |       {packageManagers.map(({ value, command, label }) => (
20 |         <TabItem key={value} value={value} label={label}>
21 |           <CodeBlock language="bash">
22 |             {value} {command} {PACKAGE_NAME}
23 |           </CodeBlock>
24 |         </TabItem>
25 |       ))}
26 |     </Tabs>
27 |   )
28 | }
29 | 
30 | export default memo(PackageManagerTabs)
31 | 


--------------------------------------------------------------------------------
/website/src/css/custom.css:
--------------------------------------------------------------------------------
  1 | /**
  2 |  * Any CSS included here will be global. The classic template
  3 |  * bundles Infima by default. Infima is a CSS framework designed to
  4 |  * work well for content-centric websites.
  5 |  */
  6 | 
  7 | /* You can override the default Infima variables here. */
  8 | :root {
  9 |   --ifm-color-primary: #764abc;
 10 |   --ifm-color-primary-dark: #6a43a9;
 11 |   --ifm-color-primary-darker: #5e3b96;
 12 |   --ifm-color-primary-darkest: #533484;
 13 |   --ifm-color-primary-light: #845cc3;
 14 |   --ifm-color-primary-lighter: #916ec9;
 15 |   --ifm-color-primary-lightest: #9f80d0;
 16 |   --ifm-code-font-size: 95%;
 17 |   --ifm-code-border-radius: 3px;
 18 |   --ifm-code-background: rgba(27, 31, 35, 0.05);
 19 | 
 20 |   --ifm-blockquote-color: #ecf4f9;
 21 |   --ifm-blockquote-color-dark: #cbddea;
 22 |   --blockquote-text-color: var(--ifm-font-base-color);
 23 | 
 24 |   --ifm-code-padding-vertical: 0.1rem;
 25 |   --ifm-code-padding-horizontal: 0.2rem;
 26 | 
 27 |   --ifm-tabs-padding-vertical: 0.2rem;
 28 |   --ifm-tabs-padding-horizontal: 0.4rem;
 29 | 
 30 |   --ifm-pre-background: rgb(39, 40, 34);
 31 |   --ifm-alert-color: black;
 32 | 
 33 |   --ifm-menu-color-active: var(--ifm-blockquote-color);
 34 | 
 35 |   --ra-admonition-color: #ecf4f9;
 36 |   --ra-admonition-color-dark: #2a98b9;
 37 | 
 38 |   --ra-admonition-color-important: #2a98b9;
 39 | 
 40 |   --ra-admonition-color-success: #f1fdf9;
 41 |   --ra-admonition-color-success-dark: #00bf88;
 42 | 
 43 |   --ra-admonition-color-caution: #fffbf5;
 44 |   --ra-admonition-color-caution-dark: #f0ad4e;
 45 | 
 46 |   --ra-admonition-color-error: #fff2f2;
 47 |   --ra-admonition-color-error-dark: #d9534f;
 48 | 
 49 |   --ra-admonition-icon-color: black !important;
 50 | 
 51 |   --docusaurus-highlighted-code-line-bg: rgba(0, 0, 0, 0.1);
 52 | }
 53 | 
 54 | /* For readability concerns, you should choose a lighter palette in dark mode. */
 55 | [data-theme='dark'] {
 56 |   --ifm-color-primary: #ba8fff;
 57 |   --ifm-color-primary-dark: #7431ca;
 58 |   --ifm-color-primary-darker: #6d1cac;
 59 |   --ifm-color-primary-darkest: #730c9a;
 60 |   --ifm-color-primary-light: #b97cfd;
 61 |   --ifm-color-primary-lighter: #cc8ffc;
 62 |   --ifm-color-primary-lightest: #fcf2ff;
 63 |   --ifm-blockquote-color: #ecf4f9;
 64 |   --ifm-blockquote-color-dark: #6d1cac;
 65 |   --ifm-menu-color-active: black;
 66 |   --blockquote-text-color: black;
 67 |   --docusaurus-highlighted-code-line-bg: rgba(0, 0, 0, 0.3);
 68 | }
 69 | 
 70 | :root[data-theme='dark'] .hero.hero--primary {
 71 |   --ifm-hero-background-color: #593d88;
 72 |   --ifm-hero-text-color: #ffffff;
 73 | }
 74 | 
 75 | .admonition a,
 76 | blockquote a {
 77 |   color: var(--ifm-color-primary-darkest);
 78 |   text-decoration: none;
 79 | }
 80 | .admonition a:hover,
 81 | blockquote a:hover {
 82 |   color: var(--blockquote-text-color);
 83 | }
 84 | 
 85 | blockquote {
 86 |   color: var(--blockquote-text-color);
 87 |   background-color: var(--ifm-blockquote-color);
 88 |   border-left: 6px solid var(--ifm-blockquote-color-dark);
 89 |   border-radius: var(--ifm-global-radius);
 90 | }
 91 | 
 92 | .docusaurus-highlight-code-line {
 93 |   background-color: rgb(72, 77, 91);
 94 |   display: block;
 95 |   margin: 0 calc(-1 * var(--ifm-pre-padding));
 96 |   padding-top: 0;
 97 |   padding-bottom: 0;
 98 |   padding-left: calc(-0.25em + var(--ifm-pre-padding));
 99 |   padding-right: var(--ifm-pre-padding);
100 |   border-left: 0.25em solid #1976d2;
101 | }
102 | div[class*='codeBlockTitle'] {
103 |   padding: 0.15rem var(--ifm-pre-padding);
104 | }
105 | 
106 | :root[data-theme='dark'] .admonition code {
107 |   color: var(--ifm-blockquote-color);
108 | }
109 | 
110 | :root[data-theme='dark'] blockquote code {
111 |   color: var(--ifm-blockquote-color);
112 | }
113 | 
114 | code {
115 |   background-color: var(--ifm-color-emphasis-300);
116 |   border-radius: 0.2rem;
117 | }
118 | 
119 | a code,
120 | code a {
121 |   background-color: var(--ifm-color-emphasis-200);
122 |   color: inherit;
123 | }
124 | 
125 | a.contents__link > code {
126 |   color: inherit;
127 | }
128 | 
129 | a.contents__link.contents__link--active {
130 |   font-weight: 600;
131 | }
132 | 
133 | a:visited {
134 |   color: var(--ifm-color-primary);
135 | }
136 | .navbar .navbar__inner {
137 |   flex-wrap: nowrap;
138 | }
139 | .navbar .navbar__items {
140 |   flex: 1 1 auto;
141 | }
142 | .footer__logo {
143 |   width: 50px;
144 |   height: 50px;
145 | }
146 | 
147 | .menu__link {
148 |   font-weight: normal;
149 | }
150 | 
151 | .menu__link--sublist {
152 |   color: var(--ifm-font-base-color);
153 |   font-weight: var(--ifm-font-weight-semibold);
154 | }
155 | 
156 | .menu__link--active:not(.menu__link--sublist) {
157 |   background-color: var(--ifm-color-primary);
158 | }
159 | 
160 | .menu__link--active:not(.menu__link--sublist) {
161 |   color: var(--ifm-menu-color-active);
162 | }
163 | 
164 | .menu .menu__link.menu__link--sublist:after {
165 |   transform: rotateZ(180deg);
166 |   -webkit-transition: -webkit-transform 0.2s linear;
167 |   transition: -webkit-transform 0.2s linear;
168 |   transition-property: transform, -webkit-transform;
169 |   transition-duration: 0.2s, 0.2s;
170 |   transition-timing-function: linear, linear;
171 |   transition-delay: 0s, 0s;
172 |   transition: transform 0.2s linear, -webkit-transform 0.2s linear;
173 |   color: var(--ifm-font-base-color);
174 | }
175 | 
176 | .menu .menu__list-item.menu__list-item--collapsed .menu__link--sublist:after {
177 |   transform: rotateZ(90deg);
178 | }
179 | 
180 | .codesandbox {
181 |   width: 100%;
182 |   height: 500px;
183 |   border: 0;
184 |   border-radius: 4px;
185 |   overflow: hidden;
186 | }
187 | 
188 | .admonition {
189 |   color: black;
190 |   border-radius: var(--ifm-global-radius);
191 |   border-left: 6px solid var(--ra-admonition-color-dark);
192 | }
193 | 
194 | .admonition.admonition-note,
195 | .admonition.admonition-info,
196 | .admonition.admonition-important,
197 | .admonition.admonition-secondary {
198 |   --ra-admonition-color: #ecf4f9;
199 |   background-color: var(--ra-admonition-color);
200 | }
201 | 
202 | .admonition.admonition-success,
203 | .admonition.admonition-tip {
204 |   background-color: var(--ra-admonition-color-success);
205 |   border-left-color: var(--ra-admonition-color-success-dark);
206 | }
207 | 
208 | .admonition.admonition-caution {
209 |   background-color: var(--ra-admonition-color-caution);
210 |   border-left-color: var(--ra-admonition-color-caution-dark);
211 | }
212 | 
213 | .admonition.admonition-warning,
214 | .admonition.admonition-danger {
215 |   background-color: var(--ra-admonition-color-error);
216 |   border-left-color: var(--ra-admonition-color-error-dark);
217 | }
218 | 
219 | .admonition .admonition-icon svg {
220 |   fill: black;
221 |   stroke: black;
222 | }
223 | 
224 | table.checkbox-table tbody td {
225 |   text-align: center;
226 |   vertical-align: center;
227 | }
228 | 
229 | .diagonal-cell {
230 |   background: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' version='1.1' preserveAspectRatio='none' viewBox='0 0 100 100'><line x1='0' y1='0' x2='100' y2='100' stroke='rgb(218,221,225)' vector-effect='non-scaling-stroke'/></svg>");
231 |   background-size: 100% 100%;
232 | }
233 | 
234 | .diagonal-cell--content {
235 |   display: grid;
236 |   width: max-content;
237 |   justify-content: space-between;
238 |   grid-template-columns: repeat(2, 1fr);
239 |   grid-auto-rows: 1fr;
240 | }
241 | 
242 | .diagonal-cell--topRight {
243 |   grid-column-start: 2;
244 |   text-align: right;
245 | }
246 | 
247 | .diagonal-cell--bottomLeft {
248 |   grid-column-start: 1;
249 | }
250 | 
251 | .migration-guide .typescript-only h4:after {
252 |   content: ' TYPESCRIPT';
253 |   color: var(--ifm-color-info);
254 |   position: relative;
255 | }


--------------------------------------------------------------------------------
/website/src/pages/index.module.css:
--------------------------------------------------------------------------------
 1 | /**
 2 |  * CSS files with the .module.css suffix will be treated as CSS modules
 3 |  * and scoped locally.
 4 |  */
 5 | 
 6 | .heroBanner {
 7 |   padding: 4rem 0;
 8 |   text-align: center;
 9 |   position: relative;
10 |   overflow: hidden;
11 | }
12 | 
13 | @media screen and (max-width: 996px) {
14 |   .heroBanner {
15 |     padding: 2rem;
16 |   }
17 | }
18 | 
19 | .buttons {
20 |   display: flex;
21 |   align-items: center;
22 |   justify-content: center;
23 | }
24 | 


--------------------------------------------------------------------------------
/website/src/pages/index.tsx:
--------------------------------------------------------------------------------
 1 | import Link from '@docusaurus/Link'
 2 | import useDocusaurusContext from '@docusaurus/useDocusaurusContext'
 3 | import HomepageFeatures from '@site/src/components/HomepageFeatures'
 4 | import Heading from '@theme/Heading'
 5 | import Layout from '@theme/Layout'
 6 | import clsx from 'clsx'
 7 | import type { FC } from 'react'
 8 | import { memo } from 'react'
 9 | import styles from './index.module.css'
10 | 
11 | const HomepageHeader: FC = memo(() => {
12 |   const { siteConfig } = useDocusaurusContext()
13 |   return (
14 |     <header className={clsx('hero hero--primary', styles.heroBanner)}>
15 |       <div className="container">
16 |         <Heading as="h1" className="hero__title">
17 |           {siteConfig.title}
18 |         </Heading>
19 |         <p className="hero__subtitle">{siteConfig.tagline}</p>
20 |         <div className={styles.buttons}>
21 |           <Link
22 |             className="button button--secondary button--lg"
23 |             to="/introduction/getting-started"
24 |           >
25 |             Get Started
26 |           </Link>
27 |         </div>
28 |       </div>
29 |     </header>
30 |   )
31 | })
32 | 
33 | const Home: FC = () => {
34 |   const { siteConfig } = useDocusaurusContext()
35 |   return (
36 |     <Layout
37 |       title={`${siteConfig.title}: ${siteConfig.tagline}`}
38 |       description="Description will go into a meta tag in <head />"
39 |     >
40 |       <HomepageHeader />
41 |       <main>
42 |         <HomepageFeatures />
43 |       </main>
44 |     </Layout>
45 |   )
46 | }
47 | 
48 | export default memo(Home)
49 | 


--------------------------------------------------------------------------------
/website/static/.nojekyll:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/reduxjs/reselect/7435743fb142afd0ae3cce65af20495f1466cb36/website/static/.nojekyll


--------------------------------------------------------------------------------
/website/static/img/diagrams/normal-memoization-function.drawio:
--------------------------------------------------------------------------------
 1 | <mxfile host="65bd71144e" scale="1" border="0">
 2 |     <diagram id="s5zgOlhoY8HzJwKQ5w-s" name="Page-1">
 3 |         <mxGraphModel dx="1244" dy="636" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="2000" pageHeight="2000" background="#C7FFFB" math="0" shadow="1">
 4 |             <root>
 5 |                 <mxCell id="0"/>
 6 |                 <mxCell id="1" parent="0"/>
 7 |                 <mxCell id="7" value="" style="group;aspect=fixed;shadow=1;" vertex="1" connectable="0" parent="1">
 8 |                     <mxGeometry x="60" y="460" width="681.5" height="290" as="geometry"/>
 9 |                 </mxCell>
10 |                 <mxCell id="2" value="Are arguments same as last time?" style="whiteSpace=wrap;html=1;rounded=1;shadow=1;aspect=fixed;" parent="7" vertex="1">
11 |                     <mxGeometry x="250" y="20" width="150" height="80" as="geometry"/>
12 |                 </mxCell>
13 |                 <mxCell id="3" value="Return result." style="whiteSpace=wrap;html=1;rounded=1;shadow=1;" parent="7" vertex="1">
14 |                     <mxGeometry x="460" y="180" width="110" height="60" as="geometry"/>
15 |                 </mxCell>
16 |                 <object label="Yes" id="4">
17 |                     <mxCell style="edgeStyle=none;html=1;entryX=0;entryY=0;entryDx=0;entryDy=0;shadow=1;" parent="7" source="2" target="3" edge="1">
18 |                         <mxGeometry relative="1" as="geometry"/>
19 |                     </mxCell>
20 |                 </object>
21 |                 <mxCell id="5" value="Run function again." style="whiteSpace=wrap;html=1;rounded=1;shadow=1;" parent="7" vertex="1">
22 |                     <mxGeometry x="30" y="180" width="150" height="60" as="geometry"/>
23 |                 </mxCell>
24 |                 <mxCell id="6" value="No" style="edgeStyle=none;html=1;entryX=1;entryY=0;entryDx=0;entryDy=0;shadow=1;" parent="7" source="2" target="5" edge="1">
25 |                     <mxGeometry relative="1" as="geometry"/>
26 |                 </mxCell>
27 |                 <mxCell id="8" value="Untitled Layer" parent="0"/>
28 |             </root>
29 |         </mxGraphModel>
30 |     </diagram>
31 | </mxfile>


--------------------------------------------------------------------------------
/website/static/img/diagrams/reselect-memoization.drawio:
--------------------------------------------------------------------------------
 1 | <mxfile host="65bd71144e">
 2 |     <diagram id="hFjyuBP9kS8yiWHOacYP" name="Page-1">
 3 |         <mxGraphModel dx="792" dy="1668" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="850" pageHeight="1100" background="#C7FFFB" math="0" shadow="0">
 4 |             <root>
 5 |                 <mxCell id="0"/>
 6 |                 <mxCell id="1" parent="0"/>
 7 |                 <mxCell id="15" value="" style="group" vertex="1" connectable="0" parent="1">
 8 |                     <mxGeometry x="70" y="-110" width="930" height="440" as="geometry"/>
 9 |                 </mxCell>
10 |                 <mxCell id="2" value="Are arguments same as last time?" style="whiteSpace=wrap;html=1;rounded=1;shadow=1;" parent="15" vertex="1">
11 |                     <mxGeometry x="470.735" y="35.2" width="139.57750000000001" height="58.036" as="geometry"/>
12 |                 </mxCell>
13 |                 <mxCell id="3" value="Return result." style="whiteSpace=wrap;html=1;rounded=1;shadow=1;" parent="15" vertex="1">
14 |                     <mxGeometry x="680" y="159.2" width="132.19" height="80" as="geometry"/>
15 |                 </mxCell>
16 |                 <mxCell id="8" value="Yes" style="edgeStyle=none;html=1;entryX=0;entryY=0;entryDx=0;entryDy=0;shadow=1;" parent="15" source="2" target="3" edge="1">
17 |                     <mxGeometry relative="1" as="geometry"/>
18 |                 </mxCell>
19 |                 <mxCell id="4" value="Run input selectors.&lt;br&gt;Are the results of input selectors same as last time?" style="whiteSpace=wrap;html=1;rounded=1;shadow=1;" parent="15" vertex="1">
20 |                     <mxGeometry x="251.88" y="158.4" width="168.12" height="81.6" as="geometry"/>
21 |                 </mxCell>
22 |                 <mxCell id="9" value="No" style="edgeStyle=none;html=1;entryX=1;entryY=0;entryDx=0;entryDy=0;shadow=1;" parent="15" source="2" target="4" edge="1">
23 |                     <mxGeometry relative="1" as="geometry"/>
24 |                 </mxCell>
25 |                 <mxCell id="6" value="Return result." style="whiteSpace=wrap;html=1;rounded=1;shadow=1;" parent="15" vertex="1">
26 |                     <mxGeometry x="470.7375" y="310" width="135.625" height="44" as="geometry"/>
27 |                 </mxCell>
28 |                 <mxCell id="13" value="Yes" style="edgeStyle=none;html=1;entryX=0;entryY=0;entryDx=0;entryDy=0;shadow=1;" parent="15" source="4" target="6" edge="1">
29 |                     <mxGeometry relative="1" as="geometry"/>
30 |                 </mxCell>
31 |                 <mxCell id="7" value="Run result function." style="whiteSpace=wrap;html=1;rounded=1;shadow=1;" parent="15" vertex="1">
32 |                     <mxGeometry x="59.9975" y="310" width="135.625" height="44" as="geometry"/>
33 |                 </mxCell>
34 |                 <mxCell id="14" value="No" style="edgeStyle=none;html=1;entryX=1;entryY=0;entryDx=0;entryDy=0;shadow=1;" parent="15" source="4" target="7" edge="1">
35 |                     <mxGeometry relative="1" as="geometry"/>
36 |                 </mxCell>
37 |             </root>
38 |         </mxGraphModel>
39 |     </diagram>
40 | </mxfile>


--------------------------------------------------------------------------------
/website/static/img/docusaurus-social-card.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/reduxjs/reselect/7435743fb142afd0ae3cce65af20495f1466cb36/website/static/img/docusaurus-social-card.jpg


--------------------------------------------------------------------------------
/website/static/img/docusaurus.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/reduxjs/reselect/7435743fb142afd0ae3cce65af20495f1466cb36/website/static/img/docusaurus.png


--------------------------------------------------------------------------------
/website/static/img/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/reduxjs/reselect/7435743fb142afd0ae3cce65af20495f1466cb36/website/static/img/favicon.ico


--------------------------------------------------------------------------------
/website/static/img/logo.svg:
--------------------------------------------------------------------------------
1 | <svg width="200" height="200" viewBox="0 0 200 200" xmlns="http://www.w3.org/2000/svg"><g fill="none" fill-rule="evenodd"><path fill="#FFF" d="M99 52h84v34H99z"/><path d="M23 163c-7.398 0-13.843-4.027-17.303-10A19.886 19.886 0 0 0 3 163c0 11.046 8.954 20 20 20h20v-20H23z" fill="#3ECC5F"/><path d="M112.98 57.376L183 53V43c0-11.046-8.954-20-20-20H73l-2.5-4.33c-1.112-1.925-3.889-1.925-5 0L63 23l-2.5-4.33c-1.111-1.925-3.889-1.925-5 0L53 23l-2.5-4.33c-1.111-1.925-3.889-1.925-5 0L43 23c-.022 0-.042.003-.065.003l-4.142-4.141c-1.57-1.571-4.252-.853-4.828 1.294l-1.369 5.104-5.192-1.392c-2.148-.575-4.111 1.389-3.535 3.536l1.39 5.193-5.102 1.367c-2.148.576-2.867 3.259-1.296 4.83l4.142 4.142c0 .021-.003.042-.003.064l-4.33 2.5c-1.925 1.111-1.925 3.889 0 5L23 53l-4.33 2.5c-1.925 1.111-1.925 3.889 0 5L23 63l-4.33 2.5c-1.925 1.111-1.925 3.889 0 5L23 73l-4.33 2.5c-1.925 1.111-1.925 3.889 0 5L23 83l-4.33 2.5c-1.925 1.111-1.925 3.889 0 5L23 93l-4.33 2.5c-1.925 1.111-1.925 3.889 0 5L23 103l-4.33 2.5c-1.925 1.111-1.925 3.889 0 5L23 113l-4.33 2.5c-1.925 1.111-1.925 3.889 0 5L23 123l-4.33 2.5c-1.925 1.111-1.925 3.889 0 5L23 133l-4.33 2.5c-1.925 1.111-1.925 3.889 0 5L23 143l-4.33 2.5c-1.925 1.111-1.925 3.889 0 5L23 153l-4.33 2.5c-1.925 1.111-1.925 3.889 0 5L23 163c0 11.046 8.954 20 20 20h120c11.046 0 20-8.954 20-20V83l-70.02-4.376A10.645 10.645 0 0 1 103 68c0-5.621 4.37-10.273 9.98-10.624" fill="#3ECC5F"/><path fill="#3ECC5F" d="M143 183h30v-40h-30z"/><path d="M193 158c-.219 0-.428.037-.639.064-.038-.15-.074-.301-.116-.451A5 5 0 0 0 190.32 148a4.96 4.96 0 0 0-3.016 1.036 26.531 26.531 0 0 0-.335-.336 4.955 4.955 0 0 0 1.011-2.987 5 5 0 0 0-9.599-1.959c-.148-.042-.297-.077-.445-.115.027-.211.064-.42.064-.639a5 5 0 0 0-5-5 5 5 0 0 0-5 5c0 .219.037.428.064.639-.148.038-.297.073-.445.115a4.998 4.998 0 0 0-9.599 1.959c0 1.125.384 2.151 1.011 2.987-3.717 3.632-6.031 8.693-6.031 14.3 0 11.046 8.954 20 20 20 9.339 0 17.16-6.41 19.361-15.064.211.027.42.064.639.064a5 5 0 0 0 5-5 5 5 0 0 0-5-5" fill="#44D860"/><path fill="#3ECC5F" d="M153 123h30v-20h-30z"/><path d="M193 115.5a2.5 2.5 0 1 0 0-5c-.109 0-.214.019-.319.032-.02-.075-.037-.15-.058-.225a2.501 2.501 0 0 0-.963-4.807c-.569 0-1.088.197-1.508.518a6.653 6.653 0 0 0-.168-.168c.314-.417.506-.931.506-1.494a2.5 2.5 0 0 0-4.8-.979A9.987 9.987 0 0 0 183 103c-5.522 0-10 4.478-10 10s4.478 10 10 10c.934 0 1.833-.138 2.69-.377a2.5 2.5 0 0 0 4.8-.979c0-.563-.192-1.077-.506-1.494.057-.055.113-.111.168-.168.42.321.939.518 1.508.518a2.5 2.5 0 0 0 .963-4.807c.021-.074.038-.15.058-.225.105.013.21.032.319.032" fill="#44D860"/><path d="M63 55.5a2.5 2.5 0 0 1-2.5-2.5c0-4.136-3.364-7.5-7.5-7.5s-7.5 3.364-7.5 7.5a2.5 2.5 0 1 1-5 0c0-6.893 5.607-12.5 12.5-12.5S65.5 46.107 65.5 53a2.5 2.5 0 0 1-2.5 2.5" fill="#000"/><path d="M103 183h60c11.046 0 20-8.954 20-20V93h-60c-11.046 0-20 8.954-20 20v70z" fill="#FFFF50"/><path d="M168.02 124h-50.04a1 1 0 1 1 0-2h50.04a1 1 0 1 1 0 2m0 20h-50.04a1 1 0 1 1 0-2h50.04a1 1 0 1 1 0 2m0 20h-50.04a1 1 0 1 1 0-2h50.04a1 1 0 1 1 0 2m0-49.814h-50.04a1 1 0 1 1 0-2h50.04a1 1 0 1 1 0 2m0 19.814h-50.04a1 1 0 1 1 0-2h50.04a1 1 0 1 1 0 2m0 20h-50.04a1 1 0 1 1 0-2h50.04a1 1 0 1 1 0 2M183 61.611c-.012 0-.022-.006-.034-.005-3.09.105-4.552 3.196-5.842 5.923-1.346 2.85-2.387 4.703-4.093 4.647-1.889-.068-2.969-2.202-4.113-4.46-1.314-2.594-2.814-5.536-5.963-5.426-3.046.104-4.513 2.794-5.807 5.167-1.377 2.528-2.314 4.065-4.121 3.994-1.927-.07-2.951-1.805-4.136-3.813-1.321-2.236-2.848-4.75-5.936-4.664-2.994.103-4.465 2.385-5.763 4.4-1.373 2.13-2.335 3.428-4.165 3.351-1.973-.07-2.992-1.51-4.171-3.177-1.324-1.873-2.816-3.993-5.895-3.89-2.928.1-4.399 1.97-5.696 3.618-1.232 1.564-2.194 2.802-4.229 2.724a1 1 0 0 0-.072 2c3.017.101 4.545-1.8 5.872-3.487 1.177-1.496 2.193-2.787 4.193-2.855 1.926-.082 2.829 1.115 4.195 3.045 1.297 1.834 2.769 3.914 5.731 4.021 3.103.104 4.596-2.215 5.918-4.267 1.182-1.834 2.202-3.417 4.15-3.484 1.793-.067 2.769 1.35 4.145 3.681 1.297 2.197 2.766 4.686 5.787 4.796 3.125.108 4.634-2.62 5.949-5.035 1.139-2.088 2.214-4.06 4.119-4.126 1.793-.042 2.728 1.595 4.111 4.33 1.292 2.553 2.757 5.445 5.825 5.556l.169.003c3.064 0 4.518-3.075 5.805-5.794 1.139-2.41 2.217-4.68 4.067-4.773v-2z" fill="#000"/><path fill="#3ECC5F" d="M83 183h40v-40H83z"/><path d="M143 158c-.219 0-.428.037-.639.064-.038-.15-.074-.301-.116-.451A5 5 0 0 0 140.32 148a4.96 4.96 0 0 0-3.016 1.036 26.531 26.531 0 0 0-.335-.336 4.955 4.955 0 0 0 1.011-2.987 5 5 0 0 0-9.599-1.959c-.148-.042-.297-.077-.445-.115.027-.211.064-.42.064-.639a5 5 0 0 0-5-5 5 5 0 0 0-5 5c0 .219.037.428.064.639-.148.038-.297.073-.445.115a4.998 4.998 0 0 0-9.599 1.959c0 1.125.384 2.151 1.011 2.987-3.717 3.632-6.031 8.693-6.031 14.3 0 11.046 8.954 20 20 20 9.339 0 17.16-6.41 19.361-15.064.211.027.42.064.639.064a5 5 0 0 0 5-5 5 5 0 0 0-5-5" fill="#44D860"/><path fill="#3ECC5F" d="M83 123h40v-20H83z"/><path d="M133 115.5a2.5 2.5 0 1 0 0-5c-.109 0-.214.019-.319.032-.02-.075-.037-.15-.058-.225a2.501 2.501 0 0 0-.963-4.807c-.569 0-1.088.197-1.508.518a6.653 6.653 0 0 0-.168-.168c.314-.417.506-.931.506-1.494a2.5 2.5 0 0 0-4.8-.979A9.987 9.987 0 0 0 123 103c-5.522 0-10 4.478-10 10s4.478 10 10 10c.934 0 1.833-.138 2.69-.377a2.5 2.5 0 0 0 4.8-.979c0-.563-.192-1.077-.506-1.494.057-.055.113-.111.168-.168.42.321.939.518 1.508.518a2.5 2.5 0 0 0 .963-4.807c.021-.074.038-.15.058-.225.105.013.21.032.319.032" fill="#44D860"/><path d="M143 41.75c-.16 0-.33-.02-.49-.05a2.52 2.52 0 0 1-.47-.14c-.15-.06-.29-.14-.431-.23-.13-.09-.259-.2-.38-.31-.109-.12-.219-.24-.309-.38s-.17-.28-.231-.43a2.619 2.619 0 0 1-.189-.96c0-.16.02-.33.05-.49.03-.16.08-.31.139-.47.061-.15.141-.29.231-.43.09-.13.2-.26.309-.38.121-.11.25-.22.38-.31.141-.09.281-.17.431-.23.149-.06.31-.11.47-.14.32-.07.65-.07.98 0 .159.03.32.08.47.14.149.06.29.14.43.23.13.09.259.2.38.31.11.12.22.25.31.38.09.14.17.28.23.43.06.16.11.31.14.47.029.16.05.33.05.49 0 .66-.271 1.31-.73 1.77-.121.11-.25.22-.38.31-.14.09-.281.17-.43.23a2.565 2.565 0 0 1-.96.19m20-1.25c-.66 0-1.3-.27-1.771-.73a3.802 3.802 0 0 1-.309-.38c-.09-.14-.17-.28-.231-.43a2.619 2.619 0 0 1-.189-.96c0-.66.27-1.3.729-1.77.121-.11.25-.22.38-.31.141-.09.281-.17.431-.23.149-.06.31-.11.47-.14.32-.07.66-.07.98 0 .159.03.32.08.47.14.149.06.29.14.43.23.13.09.259.2.38.31.459.47.73 1.11.73 1.77 0 .16-.021.33-.05.49-.03.16-.08.32-.14.47-.07.15-.14.29-.23.43-.09.13-.2.26-.31.38-.121.11-.25.22-.38.31-.14.09-.281.17-.43.23a2.565 2.565 0 0 1-.96.19" fill="#000"/></g></svg>


--------------------------------------------------------------------------------
/website/static/img/normal-memoization-function.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/reduxjs/reselect/7435743fb142afd0ae3cce65af20495f1466cb36/website/static/img/normal-memoization-function.png


--------------------------------------------------------------------------------
/website/static/img/reselect-memoization.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/reduxjs/reselect/7435743fb142afd0ae3cce65af20495f1466cb36/website/static/img/reselect-memoization.png


--------------------------------------------------------------------------------
/website/tsconfig.json:
--------------------------------------------------------------------------------
 1 | {
 2 |   // This file is not used in compilation. It is here just for a nice editor experience.
 3 |   "extends": "@docusaurus/tsconfig",
 4 |   "compilerOptions": {
 5 |     "baseUrl": "."
 6 |   },
 7 |   "ts-node": {
 8 |     "compilerOptions": {
 9 |       "module": "CommonJS"
10 |     }
11 |   }
12 | }
13 | 


--------------------------------------------------------------------------------