@inglorious/store 9.0.1 → 9.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +9 -9
- package/README.md +184 -0
- package/package.json +1 -1
- package/src/select.js +58 -8
- package/src/store.js +20 -1
- package/types/client/multiplayer-middleware.d.ts +55 -0
- package/types/select.d.ts +21 -7
- package/types/store.d.ts +1 -0
package/LICENSE
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
The MIT License (MIT)
|
|
2
|
-
|
|
3
|
-
Copyright © 2025 Inglorious Coderz Srl.
|
|
4
|
-
|
|
5
|
-
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
|
6
|
-
|
|
7
|
-
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
|
8
|
-
|
|
9
|
-
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
1
|
+
The MIT License (MIT)
|
|
2
|
+
|
|
3
|
+
Copyright © 2025 Inglorious Coderz Srl.
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
|
6
|
+
|
|
7
|
+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
|
8
|
+
|
|
9
|
+
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
package/README.md
CHANGED
|
@@ -651,6 +651,125 @@ Instead of re-rendering after each event, you can batch them and re-render once.
|
|
|
651
651
|
|
|
652
652
|
---
|
|
653
653
|
|
|
654
|
+
## 🧮 Derived State with `compute`
|
|
655
|
+
|
|
656
|
+
Inglorious Store is fully compatible with Redux selectors, but it also provides a simpler, more explicit primitive for derived state: **`compute`**.
|
|
657
|
+
|
|
658
|
+
`compute` lets you derive values from the store state in a predictable and memoized way, without hidden reactivity or dependency graphs.
|
|
659
|
+
|
|
660
|
+
### Basic example
|
|
661
|
+
|
|
662
|
+
```js
|
|
663
|
+
import { compute } from "@inglorious/store"
|
|
664
|
+
|
|
665
|
+
const selectCounter = (state) => state.counter1.value
|
|
666
|
+
const selectMultiplier = (state) => state.settings.multiplier
|
|
667
|
+
|
|
668
|
+
const selectResult = compute(
|
|
669
|
+
(count, multiplier) => count * multiplier,
|
|
670
|
+
[selectCounter, selectMultiplier],
|
|
671
|
+
)
|
|
672
|
+
```
|
|
673
|
+
|
|
674
|
+
The returned function is a standard selector:
|
|
675
|
+
|
|
676
|
+
```js
|
|
677
|
+
const result = selectResult(api.getEntities())
|
|
678
|
+
```
|
|
679
|
+
|
|
680
|
+
And it works seamlessly with `react-redux` or `@inglorious/react-store`:
|
|
681
|
+
|
|
682
|
+
```js
|
|
683
|
+
const value = useSelector(selectResult)
|
|
684
|
+
```
|
|
685
|
+
|
|
686
|
+
---
|
|
687
|
+
|
|
688
|
+
### How `compute` works
|
|
689
|
+
|
|
690
|
+
- Each **input selector** is called with the current state
|
|
691
|
+
- The results are compared using **strict equality (`===`)**
|
|
692
|
+
- If none of the inputs changed, the **previous result is reused**
|
|
693
|
+
- If at least one input changed, the result is recomputed
|
|
694
|
+
|
|
695
|
+
There is:
|
|
696
|
+
|
|
697
|
+
- ❌ no deep comparison
|
|
698
|
+
- ❌ no proxy-based reactivity
|
|
699
|
+
- ❌ no implicit dependency tracking
|
|
700
|
+
|
|
701
|
+
Memoization is **explicit and predictable**, based purely on the values returned by your selectors.
|
|
702
|
+
|
|
703
|
+
---
|
|
704
|
+
|
|
705
|
+
### Why `compute` instead of magic reactivity?
|
|
706
|
+
|
|
707
|
+
`compute` matches the core philosophy of Inglorious Store:
|
|
708
|
+
|
|
709
|
+
- derived state is just a function
|
|
710
|
+
- updates are explicit
|
|
711
|
+
- behavior is easy to reason about
|
|
712
|
+
- performance characteristics are obvious
|
|
713
|
+
|
|
714
|
+
If an input selector returns a new reference, the computation runs again.
|
|
715
|
+
If it doesn’t, it won’t.
|
|
716
|
+
|
|
717
|
+
No surprises.
|
|
718
|
+
|
|
719
|
+
---
|
|
720
|
+
|
|
721
|
+
### Zero or single input selectors
|
|
722
|
+
|
|
723
|
+
`compute` supports any number of inputs — including zero:
|
|
724
|
+
|
|
725
|
+
```js
|
|
726
|
+
const selectConstant = compute(() => 42)
|
|
727
|
+
```
|
|
728
|
+
|
|
729
|
+
Or just one:
|
|
730
|
+
|
|
731
|
+
```js
|
|
732
|
+
const selectDouble = compute(
|
|
733
|
+
(count) => count * 2,
|
|
734
|
+
[(entities) => entities.counter1.value],
|
|
735
|
+
)
|
|
736
|
+
```
|
|
737
|
+
|
|
738
|
+
---
|
|
739
|
+
|
|
740
|
+
### `createSelector` (Redux compatibility)
|
|
741
|
+
|
|
742
|
+
For migration and familiarity, Inglorious Store also exports `createSelector`, which is fully compatible with Redux:
|
|
743
|
+
|
|
744
|
+
```js
|
|
745
|
+
import { createSelector } from "@inglorious/store"
|
|
746
|
+
|
|
747
|
+
const selectResult = createSelector(
|
|
748
|
+
[(state) => state.counter1.value],
|
|
749
|
+
(count) => count * 2,
|
|
750
|
+
)
|
|
751
|
+
```
|
|
752
|
+
|
|
753
|
+
Internally, `createSelector` is just an alias for `compute`.
|
|
754
|
+
|
|
755
|
+
> **Note:** `compute` is the preferred API for new code.
|
|
756
|
+
> `createSelector` exists mainly for Redux compatibility and migration.
|
|
757
|
+
|
|
758
|
+
---
|
|
759
|
+
|
|
760
|
+
### When to use `compute`
|
|
761
|
+
|
|
762
|
+
Use `compute` when:
|
|
763
|
+
|
|
764
|
+
- you need derived or aggregated state
|
|
765
|
+
- you want memoization without magic
|
|
766
|
+
- you want selectors that are easy to test
|
|
767
|
+
- you want predictable recomputation rules
|
|
768
|
+
|
|
769
|
+
If you already know Redux selectors, you already know how to use `compute` — just with fewer rules and less ceremony.
|
|
770
|
+
|
|
771
|
+
---
|
|
772
|
+
|
|
654
773
|
## Comparison with Other State Libraries
|
|
655
774
|
|
|
656
775
|
| Feature | Redux | RTK | Zustand | Jotai | Pinia | MobX | Inglorious Store |
|
|
@@ -675,12 +794,75 @@ const store = createStore({
|
|
|
675
794
|
types, // Object: entity type definitions
|
|
676
795
|
entities, // Object: initial entities
|
|
677
796
|
systems, // Array (optional): global state handlers
|
|
797
|
+
autoCreateEntities, // Boolean (optional): false (default) or true
|
|
678
798
|
updateMode, // String (optional): 'auto' (default) or 'manual'
|
|
679
799
|
})
|
|
680
800
|
```
|
|
681
801
|
|
|
682
802
|
**Returns:** A Redux-compatible store
|
|
683
803
|
|
|
804
|
+
**Options:**
|
|
805
|
+
|
|
806
|
+
- **`types`** (required) - Object defining entity type behaviors
|
|
807
|
+
- **`entities`** (required) - Object containing initial entity instances
|
|
808
|
+
- **`systems`** (optional) - Array of global state handlers
|
|
809
|
+
- **`autoCreateEntities`** (optional) - Automatically create singleton entities for types not defined in `entities`:
|
|
810
|
+
- `false` (default) - Only use explicitly defined entities
|
|
811
|
+
- `true` - Auto-create entities matching their type name
|
|
812
|
+
- **`updateMode`** (optional) - Controls when React components re-render:
|
|
813
|
+
- `'auto'` (default) - Automatic updates after each event
|
|
814
|
+
- `'manual'` - Manual control via `api.update()`
|
|
815
|
+
|
|
816
|
+
#### Auto-Create Entities
|
|
817
|
+
|
|
818
|
+
When `autoCreateEntities: true`, the store automatically creates singleton entities for any type that doesn't have a corresponding entity defined. This is particularly useful for singleton-type entities that behave like components, eliminating the need to switch between type definitions and entity declarations.
|
|
819
|
+
|
|
820
|
+
```javascript
|
|
821
|
+
const types = {
|
|
822
|
+
settings: {
|
|
823
|
+
setTheme(entity, theme) {
|
|
824
|
+
entity.theme = theme
|
|
825
|
+
},
|
|
826
|
+
},
|
|
827
|
+
analytics: {
|
|
828
|
+
track(entity, event) {
|
|
829
|
+
entity.events.push(event)
|
|
830
|
+
},
|
|
831
|
+
},
|
|
832
|
+
}
|
|
833
|
+
|
|
834
|
+
// Without autoCreateEntities (default)
|
|
835
|
+
const entities = {
|
|
836
|
+
settings: { type: "settings", theme: "dark" },
|
|
837
|
+
analytics: { type: "analytics", events: [] },
|
|
838
|
+
}
|
|
839
|
+
|
|
840
|
+
// With autoCreateEntities: true
|
|
841
|
+
const entities = {
|
|
842
|
+
// settings and analytics will be auto-created as:
|
|
843
|
+
// settings: { type: "settings" }
|
|
844
|
+
// analytics: { type: "analytics" }
|
|
845
|
+
}
|
|
846
|
+
|
|
847
|
+
const store = createStore({
|
|
848
|
+
types,
|
|
849
|
+
entities,
|
|
850
|
+
autoCreateEntities: true,
|
|
851
|
+
})
|
|
852
|
+
|
|
853
|
+
// Both approaches work the same way
|
|
854
|
+
store.notify("settings:setTheme", "light")
|
|
855
|
+
store.notify("analytics:track", { action: "click" })
|
|
856
|
+
```
|
|
857
|
+
|
|
858
|
+
**When to use `autoCreateEntities`:**
|
|
859
|
+
|
|
860
|
+
- ✅ Building web applications with singleton services (settings, auth, analytics)
|
|
861
|
+
- ✅ Component-like entities that only need one instance
|
|
862
|
+
- ✅ Rapid prototyping where you want to add types without ceremony
|
|
863
|
+
- ❌ Game development with multiple entity instances (players, enemies, items)
|
|
864
|
+
- ❌ When you need fine control over initial entity state
|
|
865
|
+
|
|
684
866
|
### Types Definition
|
|
685
867
|
|
|
686
868
|
```javascript
|
|
@@ -733,6 +915,8 @@ store.notify("eventName", payload)
|
|
|
733
915
|
store.dispatch({ type: "eventName", payload }) // Redux-compatible alternative
|
|
734
916
|
```
|
|
735
917
|
|
|
918
|
+
---
|
|
919
|
+
|
|
736
920
|
### 🧩 Type Safety
|
|
737
921
|
|
|
738
922
|
Inglorious Store is written in JavaScript but comes with powerful TypeScript support out of the box, allowing for a fully type-safe experience similar to Redux Toolkit, but with less boilerplate.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@inglorious/store",
|
|
3
|
-
"version": "9.0
|
|
3
|
+
"version": "9.2.0",
|
|
4
4
|
"description": "A state manager for real-time, collaborative apps, inspired by game development patterns and compatible with Redux.",
|
|
5
5
|
"author": "IceOnFire <antony.mistretta@gmail.com> (https://ingloriouscoderz.it)",
|
|
6
6
|
"license": "MIT",
|
package/src/select.js
CHANGED
|
@@ -1,17 +1,43 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
* @param {Array<Function>} inputSelectors - An array of input selector functions.
|
|
5
|
-
* @param {Function} resultFunc - A function that receives the results of the input selectors and returns a computed value.
|
|
6
|
-
* @returns {Function} A memoized selector function that, when called, returns the selected state.
|
|
2
|
+
* @typedef {import('../types/select').InputSelector} InputSelector
|
|
3
|
+
* @typedef {import('../types/select').OutputSelector} OutputSelector
|
|
7
4
|
*/
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Creates a memoized derived computation from one or more input selectors.
|
|
8
|
+
*
|
|
9
|
+
* `compute` returns a function that, given the application state, will:
|
|
10
|
+
* - run each input selector with the state
|
|
11
|
+
* - compare the results with the previous invocation using strict equality
|
|
12
|
+
* - recompute the result only if at least one input has changed
|
|
13
|
+
*
|
|
14
|
+
* This is a simple, explicit memoization utility.
|
|
15
|
+
* There is no dependency tracking, deep comparison, or reactive graph:
|
|
16
|
+
* memoization is based solely on referential equality of selector outputs.
|
|
17
|
+
*
|
|
18
|
+
* @template TState
|
|
19
|
+
* @template TResult
|
|
20
|
+
*
|
|
21
|
+
* @param {(…inputs: any[]) => TResult} resultFunc
|
|
22
|
+
* A function that receives the results of the input selectors and returns
|
|
23
|
+
* the computed value.
|
|
24
|
+
*
|
|
25
|
+
* @param {InputSelector<TState, any>[]} inputSelectors
|
|
26
|
+
* An array of selector functions used to extract inputs from the state (optional).
|
|
27
|
+
*
|
|
28
|
+
* @returns {OutputSelector<TState, TResult>}
|
|
29
|
+
* A memoized function that computes and returns the derived value.
|
|
30
|
+
*/
|
|
31
|
+
export function compute(resultFunc, inputSelectors = []) {
|
|
32
|
+
let lastInputs
|
|
33
|
+
let lastResult
|
|
34
|
+
let initialized = false
|
|
11
35
|
|
|
12
36
|
return (state) => {
|
|
13
37
|
const nextInputs = inputSelectors.map((selector) => selector(state))
|
|
38
|
+
|
|
14
39
|
const inputsChanged =
|
|
40
|
+
!initialized ||
|
|
15
41
|
lastInputs.length !== nextInputs.length ||
|
|
16
42
|
nextInputs.some((input, index) => input !== lastInputs[index])
|
|
17
43
|
|
|
@@ -21,6 +47,30 @@ export function createSelector(inputSelectors, resultFunc) {
|
|
|
21
47
|
|
|
22
48
|
lastInputs = nextInputs
|
|
23
49
|
lastResult = resultFunc(...nextInputs)
|
|
50
|
+
initialized = true
|
|
24
51
|
return lastResult
|
|
25
52
|
}
|
|
26
53
|
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Redux-compatible alias for {@link compute}.
|
|
57
|
+
*
|
|
58
|
+
* This function exists for familiarity and migration purposes.
|
|
59
|
+
* Prefer using `compute` directly in new code.
|
|
60
|
+
*
|
|
61
|
+
* @template TState
|
|
62
|
+
* @template TResult
|
|
63
|
+
*
|
|
64
|
+
* @param {InputSelector<TState, any>[]} inputSelectors
|
|
65
|
+
* An array of input selector functions.
|
|
66
|
+
*
|
|
67
|
+
* @param {(…inputs: any[]) => TResult} resultFunc
|
|
68
|
+
* A function that receives the results of the input selectors and returns
|
|
69
|
+
* a computed value.
|
|
70
|
+
*
|
|
71
|
+
* @returns {OutputSelector<TState, TResult>}
|
|
72
|
+
* A memoized selector function.
|
|
73
|
+
*/
|
|
74
|
+
export function createSelector(inputSelectors, resultFunc) {
|
|
75
|
+
return compute(resultFunc, inputSelectors)
|
|
76
|
+
}
|
package/src/store.js
CHANGED
|
@@ -13,6 +13,7 @@ import { augmentType, augmentTypes } from "./types.js"
|
|
|
13
13
|
* @param {Object} [config.entities] - The initial entities configuration.
|
|
14
14
|
* @param {Array} [config.systems] - The initial systems configuration.
|
|
15
15
|
* @param {Array} [config.middlewares] - The initial middlewares configuration.
|
|
16
|
+
* @param {boolean} [config.autoCreateEntities] - Creates entities if not defined in `config.entities`.
|
|
16
17
|
* @param {"auto" | "manual"} [config.updateMode] - The update mode (defaults to "auto").
|
|
17
18
|
* @returns {Object} The store with methods to interact with state and events.
|
|
18
19
|
*/
|
|
@@ -21,6 +22,7 @@ export function createStore({
|
|
|
21
22
|
entities: originalEntities = {},
|
|
22
23
|
systems = [],
|
|
23
24
|
middlewares = [],
|
|
25
|
+
autoCreateEntities = false,
|
|
24
26
|
updateMode = "auto",
|
|
25
27
|
} = {}) {
|
|
26
28
|
const listeners = new Set()
|
|
@@ -220,8 +222,25 @@ export function createStore({
|
|
|
220
222
|
const oldEntities = state ?? {}
|
|
221
223
|
const newEntities = augmentEntities(nextState)
|
|
222
224
|
|
|
225
|
+
if (autoCreateEntities) {
|
|
226
|
+
for (const typeName of Object.keys(types)) {
|
|
227
|
+
// Check if entity already exists
|
|
228
|
+
const hasEntity = Object.values(newEntities).some(
|
|
229
|
+
(entity) => entity.type === typeName,
|
|
230
|
+
)
|
|
231
|
+
|
|
232
|
+
if (!hasEntity) {
|
|
233
|
+
// No entity for this type → auto-create minimal entity
|
|
234
|
+
newEntities[typeName] = {
|
|
235
|
+
id: typeName,
|
|
236
|
+
type: typeName,
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
223
242
|
state = newEntities
|
|
224
|
-
eventMap = new EventMap(types,
|
|
243
|
+
eventMap = new EventMap(types, newEntities)
|
|
225
244
|
incomingEvents = []
|
|
226
245
|
isProcessing = false
|
|
227
246
|
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import type { BaseEntity, EntitiesState, Middleware } from "../store"
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* A generic event dispatched through the store.
|
|
5
|
+
*/
|
|
6
|
+
export interface MultiplayerEvent {
|
|
7
|
+
type: string
|
|
8
|
+
payload?: unknown
|
|
9
|
+
fromServer?: boolean
|
|
10
|
+
[key: string]: unknown
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Configuration options for the multiplayer middleware.
|
|
15
|
+
*/
|
|
16
|
+
export interface MultiplayerMiddlewareConfig {
|
|
17
|
+
/**
|
|
18
|
+
* WebSocket server URL.
|
|
19
|
+
* Defaults to ws://<current-hostname>:3000
|
|
20
|
+
*/
|
|
21
|
+
serverUrl?: string
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Delay (in milliseconds) before attempting to reconnect
|
|
25
|
+
* after the WebSocket connection is closed.
|
|
26
|
+
*
|
|
27
|
+
* @default 1000
|
|
28
|
+
*/
|
|
29
|
+
reconnectionDelay?: number
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Event types that should NOT be sent to the server.
|
|
33
|
+
*/
|
|
34
|
+
blacklist?: string[]
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Event types that SHOULD be sent to the server.
|
|
38
|
+
* If provided, only these events are sent.
|
|
39
|
+
*/
|
|
40
|
+
whitelist?: string[]
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Custom predicate to decide whether an event
|
|
44
|
+
* should be sent to the server.
|
|
45
|
+
*/
|
|
46
|
+
filter?: (event: MultiplayerEvent) => boolean
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Creates the multiplayer middleware.
|
|
51
|
+
*/
|
|
52
|
+
export function multiplayerMiddleware<
|
|
53
|
+
T extends BaseEntity = BaseEntity,
|
|
54
|
+
S extends EntitiesState<T> = EntitiesState<T>,
|
|
55
|
+
>(config?: MultiplayerMiddlewareConfig): Middleware<T, S>
|
package/types/select.d.ts
CHANGED
|
@@ -13,12 +13,26 @@ export type OutputSelector<TState = any, TResult = any> = (
|
|
|
13
13
|
) => TResult
|
|
14
14
|
|
|
15
15
|
/**
|
|
16
|
-
* Creates a memoized
|
|
17
|
-
* @param inputSelectors - An array of input selector functions.
|
|
18
|
-
* @param resultFunc - A function that receives the results of the input selectors and returns a computed value.
|
|
19
|
-
* @returns A memoized selector function that, when called, returns the selected state.
|
|
16
|
+
* Creates a memoized derived computation from input selectors.
|
|
20
17
|
*/
|
|
21
|
-
export function
|
|
22
|
-
|
|
23
|
-
|
|
18
|
+
export function compute<TState, TInputs extends readonly unknown[], TResult>(
|
|
19
|
+
resultFunc: (...args: TInputs) => TResult,
|
|
20
|
+
inputSelectors: {
|
|
21
|
+
[K in keyof TInputs]: InputSelector<TState, TInputs[K]>
|
|
22
|
+
},
|
|
23
|
+
): OutputSelector<TState, TResult>
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Redux-compatible alias for `compute`.
|
|
27
|
+
* Prefer `compute` in new code.
|
|
28
|
+
*/
|
|
29
|
+
export function createSelector<
|
|
30
|
+
TState,
|
|
31
|
+
TInputs extends readonly unknown[],
|
|
32
|
+
TResult,
|
|
33
|
+
>(
|
|
34
|
+
inputSelectors: {
|
|
35
|
+
[K in keyof TInputs]: InputSelector<TState, TInputs[K]>
|
|
36
|
+
},
|
|
37
|
+
resultFunc: (...args: TInputs) => TResult,
|
|
24
38
|
): OutputSelector<TState, TResult>
|