@inglorious/store 6.1.0 → 6.1.2
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/README.md +101 -22
- package/package.json +7 -1
package/README.md
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
|
|
6
6
|
A Redux-compatible, ECS-inspired state library that makes state management as elegant as game logic.
|
|
7
7
|
|
|
8
|
-
**Drop-in replacement for Redux.** Works with `react-redux` and Redux DevTools.
|
|
8
|
+
**Drop-in replacement for Redux.** Works with `react-redux` and Redux DevTools. Borrows concepts from Entity-Component-System architectures and Functional Programming to provide an environment where you can write simple, predictable, and testable code.
|
|
9
9
|
|
|
10
10
|
```javascript
|
|
11
11
|
// from redux
|
|
@@ -20,7 +20,7 @@ import { createStore } from "@inglorious/store"
|
|
|
20
20
|
|
|
21
21
|
Redux is powerful but verbose. You need action creators, reducers, middleware for async operations, and a bunch of decisions about where logic should live. Redux Toolkit cuts the boilerplate, but you're still writing a lot of ceremony.
|
|
22
22
|
|
|
23
|
-
Inglorious Store
|
|
23
|
+
Inglorious Store eliminates the boilerplate entirely with an **entity-based architecture** inspired by game engines. Some of the patterns that power AAA games now power your state management.
|
|
24
24
|
|
|
25
25
|
Game engines solved state complexity years ago — Inglorious Store brings those lessons to web development.
|
|
26
26
|
|
|
@@ -187,7 +187,7 @@ const entities = {
|
|
|
187
187
|
|
|
188
188
|
Even though it looks like types expose methods, they are actually **event handlers**, very similar to Redux reducers. There are a few differences though:
|
|
189
189
|
|
|
190
|
-
1. Just like RTK reducers, you can mutate the entity directly since event handlers are using an immutability library under the hood. Not Immer, but Mutative
|
|
190
|
+
1. Just like RTK reducers, you can mutate the entity directly since event handlers are using an immutability library under the hood. Not Immer, but Mutative — which claims to be 10x faster than Immer.
|
|
191
191
|
|
|
192
192
|
```javascript
|
|
193
193
|
const types = {
|
|
@@ -218,7 +218,7 @@ const types = {
|
|
|
218
218
|
|
|
219
219
|
## Installation & Setup
|
|
220
220
|
|
|
221
|
-
The Inglorious store, just like Redux, can be used standalone.
|
|
221
|
+
The Inglorious store, just like Redux, can be used standalone. However, it's commonly used together with component libraries such as React.
|
|
222
222
|
|
|
223
223
|
### Basic Setup with `react-redux`
|
|
224
224
|
|
|
@@ -272,6 +272,8 @@ function Counter() {
|
|
|
272
272
|
|
|
273
273
|
### With `@inglorious/react-store` (Recommended)
|
|
274
274
|
|
|
275
|
+
For React applications, `@inglorious/react-store` provides a set of hooks and a Provider that are tightly integrated with the store. It's a lightweight wrapper around `react-redux` that offers a more ergonomic API.
|
|
276
|
+
|
|
275
277
|
```javascript
|
|
276
278
|
import { createStore } from "@inglorious/store"
|
|
277
279
|
import { createReactStore } from "@inglorious/react-store"
|
|
@@ -296,13 +298,16 @@ function Counter() {
|
|
|
296
298
|
return (
|
|
297
299
|
<div>
|
|
298
300
|
<p>{count}</p>
|
|
299
|
-
<button onClick={() => notify("increment")}>+</button> //
|
|
301
|
+
<button onClick={() => notify("increment")}>+</button> // simplified
|
|
302
|
+
syntax
|
|
300
303
|
<button onClick={() => notify("decrement")}>-</button>
|
|
301
304
|
</div>
|
|
302
305
|
)
|
|
303
306
|
}
|
|
304
307
|
```
|
|
305
308
|
|
|
309
|
+
The package is fully typed, providing a great developer experience with TypeScript.
|
|
310
|
+
|
|
306
311
|
---
|
|
307
312
|
|
|
308
313
|
## Core Features
|
|
@@ -382,7 +387,7 @@ const types = {
|
|
|
382
387
|
|
|
383
388
|
### 🔊 Event Broadcasting
|
|
384
389
|
|
|
385
|
-
Events are broadcast to all entities via pub/sub. Every entity handler receives every event of that type, just like in Redux.
|
|
390
|
+
Events are broadcast to all entities via pub/sub. Every entity handler receives every event of that type, just like it does in Redux.
|
|
386
391
|
|
|
387
392
|
```javascript
|
|
388
393
|
const types = {
|
|
@@ -433,7 +438,7 @@ store.notify("toggle", "todo1")
|
|
|
433
438
|
|
|
434
439
|
### ⚡ Async Operations
|
|
435
440
|
|
|
436
|
-
In **Redux/RTK**, logic should be written inside
|
|
441
|
+
In **Redux/RTK**, logic should be written inside pure functions as much as possible — specifically in reducers, not action creators. But what if I need to access some other part of the state that is not visible to the reducer? What if I need to combine async behavior with sync behavior? This is where the choice of "where does my logic live?" matters.
|
|
437
442
|
|
|
438
443
|
In **Inglorious Store:** your event handlers can be async, and you get deterministic behavior automatically. Inside an async handler, you can access other parts of state (read-only), and you can trigger other events via `api.notify()`. Even if we give up on some purity, everything still maintains predictability because of the underlying **event queue**:
|
|
439
444
|
|
|
@@ -661,32 +666,77 @@ store.notify("eventName", payload)
|
|
|
661
666
|
store.dispatch({ type: "eventName", payload }) // Redux-compatible alternative
|
|
662
667
|
```
|
|
663
668
|
|
|
664
|
-
### 🧩 Type Safety
|
|
669
|
+
### 🧩 Type Safety
|
|
665
670
|
|
|
666
|
-
Inglorious Store
|
|
671
|
+
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.
|
|
667
672
|
|
|
668
|
-
You
|
|
673
|
+
You can achieve strong type safety by defining an interface for your `types` configuration. This allows you to statically define the shape of your entity handlers, ensuring that all required handlers are present and correctly typed.
|
|
669
674
|
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
+
Here’s how you can set it up for a TodoMVC-style application:
|
|
676
|
+
|
|
677
|
+
**1. Define Your Types**
|
|
678
|
+
|
|
679
|
+
First, create an interface that describes your entire `types` configuration. This interface will enforce the structure of your event handlers.
|
|
680
|
+
|
|
681
|
+
```typescript
|
|
682
|
+
// src/store/types.ts
|
|
683
|
+
import type {
|
|
684
|
+
FormEntity,
|
|
685
|
+
ListEntity,
|
|
686
|
+
FooterEntity,
|
|
687
|
+
// ... other payload types
|
|
688
|
+
} from "../../types"
|
|
689
|
+
|
|
690
|
+
// Define the static shape of the types configuration
|
|
691
|
+
interface TodoListTypes {
|
|
692
|
+
form: {
|
|
693
|
+
inputChange: (entity: FormEntity, value: string) => void
|
|
694
|
+
formSubmit: (entity: FormEntity) => void
|
|
695
|
+
}
|
|
696
|
+
list: {
|
|
697
|
+
formSubmit: (entity: ListEntity, value: string) => void
|
|
698
|
+
toggleClick: (entity: ListEntity, id: number) => void
|
|
699
|
+
// ... other handlers
|
|
700
|
+
}
|
|
701
|
+
footer: {
|
|
702
|
+
filterClick: (entity: FooterEntity, id: string) => void
|
|
703
|
+
}
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
export const types: TodoListTypes = {
|
|
707
|
+
form: {
|
|
708
|
+
inputChange(entity, value) {
|
|
709
|
+
entity.value = value
|
|
710
|
+
},
|
|
711
|
+
formSubmit(entity) {
|
|
712
|
+
entity.value = ""
|
|
675
713
|
},
|
|
676
714
|
},
|
|
715
|
+
// ... other type implementations
|
|
677
716
|
}
|
|
717
|
+
```
|
|
678
718
|
|
|
679
|
-
|
|
680
|
-
c1: { type: "counter", value: 0 },
|
|
681
|
-
}
|
|
719
|
+
With `TodoListTypes`, TypeScript will throw an error if you forget a handler (e.g., `formSubmit`) or if its signature is incorrect.
|
|
682
720
|
|
|
683
|
-
|
|
721
|
+
**2. Create the Store**
|
|
722
|
+
|
|
723
|
+
When creating your store, you'll pass the `types` object. To satisfy the store's generic `TypesConfig`, you may need to use a double cast (`as unknown as`). This is a safe and intentional way to bridge your specific, statically-checked configuration with the store's more generic type.
|
|
724
|
+
|
|
725
|
+
```typescript
|
|
726
|
+
// src/store/index.ts
|
|
727
|
+
import { createStore, type TypesConfig } from "@inglorious/store"
|
|
728
|
+
import { types } from "./types"
|
|
729
|
+
import type { TodoListEntity, TodoListState } from "../../types"
|
|
684
730
|
|
|
685
|
-
store
|
|
686
|
-
|
|
731
|
+
export const store = createStore<TodoListEntity, TodoListState>({
|
|
732
|
+
types: types as unknown as TypesConfig<TodoListEntity>,
|
|
733
|
+
// ... other store config
|
|
734
|
+
})
|
|
687
735
|
```
|
|
688
736
|
|
|
689
|
-
|
|
737
|
+
**3. Enjoy Full Type Safety**
|
|
738
|
+
|
|
739
|
+
Now, your store is fully type-safe. The hooks provided by `@inglorious/react-store` will also be correctly typed.
|
|
690
740
|
|
|
691
741
|
---
|
|
692
742
|
|
|
@@ -714,11 +764,40 @@ Check out the following demos to see the Inglorious Store in action on real-case
|
|
|
714
764
|
- **[TodoMVC](https://github.com/IngloriousCoderz/inglorious-engine/tree/main/examples/apps/todomvc)** - An (ugly) clone of Kent Dodds' [TodoMVC](https://todomvc.com/) experiments, showing the full compatibility with react-redux and The Redux DevTools.
|
|
715
765
|
- **[TodoMVC-CS](https://github.com/IngloriousCoderz/inglorious-engine/tree/main/examples/apps/todomvc-cs)** - A client-server version of the TodoMVC, which showcases the use of `notify` as a cleaner alternative to `dispatch` and async event handlers.
|
|
716
766
|
- **[TodoMVC-RT](https://github.com/IngloriousCoderz/inglorious-engine/tree/main/examples/apps/todomvc-rt)** - A "multiplayer" version, in which multiple clients are able to synchronize through a real-time server.
|
|
767
|
+
- **[TodoMVC-TS](https://github.com/IngloriousCoderz/inglorious-engine/tree/main/examples/apps/todomvc-ts)** - A typesafe version of the base TodoMVC.
|
|
768
|
+
|
|
769
|
+
---
|
|
717
770
|
|
|
718
771
|
## Part of the Inglorious Engine
|
|
719
772
|
|
|
720
773
|
This store powers the [Inglorious Engine](https://github.com/IngloriousCoderz/inglorious-engine), a functional game engine. The same patterns that power games power your web apps.
|
|
721
774
|
|
|
775
|
+
## Frequently Unsolicited Complaints (FUCs)
|
|
776
|
+
|
|
777
|
+
It's hard to accept the new, especially on Reddit. Here are the main objections to the Inglorious Store.
|
|
778
|
+
|
|
779
|
+
**"This is not ECS."**
|
|
780
|
+
|
|
781
|
+
It's not. The Inglorious Store is _inspired_ by ECS, but doesn't strictly follow ECS. Heck, not even the major game engines out there follow ECS by the book!
|
|
782
|
+
|
|
783
|
+
Let's compare the two:
|
|
784
|
+
|
|
785
|
+
| ECS Architecture | Inglorious Store |
|
|
786
|
+
| ------------------------------------- | -------------------------------------- |
|
|
787
|
+
| Entities are ids | Entities have an id |
|
|
788
|
+
| Components are pure, consecutive data | Entities are pure bags of related data |
|
|
789
|
+
| Data and behavior are separated | Data and behavior are separated |
|
|
790
|
+
| Systems operate on the whole state | Systems operate on the whole state |
|
|
791
|
+
| Usually written in an OOP environment | Written in an FP environment |
|
|
792
|
+
|
|
793
|
+
**"This is not FP."**
|
|
794
|
+
|
|
795
|
+
It looks like it's not, and that's a feature. If you're used to classes and instances, the Inglorious Store will feel natural to you. Even behavior composition looks like inheritance, but it's actually function composition. The same [Three Principles](https://redux.js.org/understanding/thinking-in-redux/three-principles) that describe Redux are applied here (with some degree of freedom on function purity).
|
|
796
|
+
|
|
797
|
+
**"This is not Data-Oriented Design."**
|
|
798
|
+
|
|
799
|
+
It's not. Please grep this README and count how many occurrences of DoD you can find. This is not [Data-Oriented Design](https://en.wikipedia.org/wiki/Data-oriented_design), which is related to low-level CPU cache optimization. It's more similar to [Data-Driven Programming](https://en.wikipedia.org/wiki/Data-driven_programming), which is related to separating data and behavior. The Inglorious Engine separates behavior in... behaviors (grouped into so-called types), while the data is stored in plain objects called entities.
|
|
800
|
+
|
|
722
801
|
---
|
|
723
802
|
|
|
724
803
|
## License
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@inglorious/store",
|
|
3
|
-
"version": "6.1.
|
|
3
|
+
"version": "6.1.2",
|
|
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",
|
|
@@ -28,9 +28,15 @@
|
|
|
28
28
|
"type": "module",
|
|
29
29
|
"exports": {
|
|
30
30
|
".": {
|
|
31
|
+
"types": "./types/index.d.ts",
|
|
31
32
|
"import": "./src/store.js"
|
|
32
33
|
},
|
|
34
|
+
"./client/devtools": {
|
|
35
|
+
"types": "./types/client/devtools.d.ts",
|
|
36
|
+
"import": "./src/client/devtools.js"
|
|
37
|
+
},
|
|
33
38
|
"./*": {
|
|
39
|
+
"types": "./types/*.d.ts",
|
|
34
40
|
"import": "./src/*"
|
|
35
41
|
}
|
|
36
42
|
},
|