@inglorious/store 9.0.1 → 9.1.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 +121 -0
- package/package.json +1 -1
- package/src/select.js +58 -8
- package/types/client/multiplayer-middleware.d.ts +55 -0
- package/types/select.d.ts +21 -7
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 |
|
|
@@ -733,6 +852,8 @@ store.notify("eventName", payload)
|
|
|
733
852
|
store.dispatch({ type: "eventName", payload }) // Redux-compatible alternative
|
|
734
853
|
```
|
|
735
854
|
|
|
855
|
+
---
|
|
856
|
+
|
|
736
857
|
### 🧩 Type Safety
|
|
737
858
|
|
|
738
859
|
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.1.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
|
+
}
|
|
@@ -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>
|