@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 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.1",
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
- * Creates a memoized selector function.
3
- * NB: this implementation does not support spreading the input selectors for clarity, please just put them in an array.
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
- export function createSelector(inputSelectors, resultFunc) {
9
- let lastInputs = []
10
- let lastResult = null
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 selector function.
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 createSelector<TState = any, TResult = any>(
22
- inputSelectors: InputSelector<TState, any>[],
23
- resultFunc: (...args: any[]) => TResult,
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>