@inglorious/store 5.2.0 โ†’ 5.4.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/README.md CHANGED
@@ -13,14 +13,100 @@ Why settle for state management that wasn't designed for real-time sync? Games s
13
13
 
14
14
  ## Why Video Game Patterns?
15
15
 
16
- Video games solved distributed state synchronization decades ago. They handle:
16
+ Games solved the hardest real-time problems: syncing state across laggy networks with hundreds of players at 60fps. They use:
17
17
 
18
- - **60fps updates** with thousands of entities
19
- - **Multiplayer** with lag compensation and state sync
20
- - **Deterministic simulation** for replays and debugging
21
- - **Complex interactions** between many objects
18
+ - **Deterministic event processing** - same events + same handlers = guaranteed identical state
19
+ - **Event queues** - natural ordering and conflict resolution
20
+ - **Serializable state** - trivial to send over the network
21
+ - **Client-side prediction** - responsive UIs that stay in sync
22
22
 
23
- If it works for games, it'll handle your app's state with ease.
23
+ These patterns aren't just for games. They're perfect for any app that might need:
24
+
25
+ - Real-time collaboration (like Notion, Figma)
26
+ - Live updates (dashboards, chat)
27
+ - Undo/redo and time-travel debugging
28
+ - Multiplayer features
29
+
30
+ **The best part?** You get this architecture from day one, even for simple apps. When you need these features later, they're already built-in.
31
+
32
+ ---
33
+
34
+ ## Installation
35
+
36
+ ```bash
37
+ npm install @inglorious/store
38
+ ```
39
+
40
+ **For React apps**, also install the React bindings:
41
+
42
+ ```bash
43
+ npm install @inglorious/react-store
44
+ ```
45
+
46
+ See [@inglorious/react-store](https://github.com/IngloriousCoderz/inglorious-engine/tree/main/packages/react-store) for React-specific documentation.
47
+
48
+ ---
49
+
50
+ ## Update Modes
51
+
52
+ Inglorious Store supports two update modes:
53
+
54
+ ### Eager Mode (default) - Like Redux
55
+
56
+ ```javascript
57
+ const store = createStore({ types, entities }) // mode: "eager" is default
58
+ store.notify("addTodo", { text: "Buy milk" })
59
+ // State updates immediately, no need to call update()
60
+ ```
61
+
62
+ **Best for:** Simple apps with synchronous logic.
63
+
64
+ **Limitation:** If an event handler needs to dispatch another event, only the first event processes. Use batched mode for event chains.
65
+
66
+ ### Batched Mode - Like game engines
67
+
68
+ ```javascript
69
+ const store = createStore({ types, entities, mode: "batched" })
70
+ store.notify("addTodo", { text: "Buy milk" })
71
+ store.notify("toggleTodo", "todo1")
72
+ store.update() // Process all queued events at once
73
+ ```
74
+
75
+ **Best for:**
76
+
77
+ - Apps with async operations (API calls, data fetching)
78
+ - Event handlers that dispatch other events
79
+ - Games, animations, or high-frequency updates
80
+ - Explicit control over when state updates
81
+
82
+ **Why batched mode for async?**
83
+
84
+ When fetching data from an API, you typically need two events: one to initiate the fetch, and another to store the result. Batched mode allows this pattern:
85
+
86
+ ```javascript
87
+ const types = {
88
+ todoList: {
89
+ async fetchTodos(entity, payload, api) {
90
+ const response = await fetch("/api/todos")
91
+ const todos = await response.json()
92
+
93
+ // This event will be processed in the same update cycle
94
+ api.notify("todosReceived", todos)
95
+ },
96
+
97
+ todosReceived(entity, todos) {
98
+ entity.todos = todos
99
+ entity.loading = false
100
+ },
101
+ },
102
+ }
103
+
104
+ // In your app
105
+ store.notify("fetchTodos")
106
+ await store.update() // Both fetchTodos AND todosReceived process together
107
+ ```
108
+
109
+ In eager mode, only `fetchTodos` would process, and `todosReceived` would be ignored.
24
110
 
25
111
  ---
26
112
 
@@ -40,21 +126,23 @@ const todoType = {
40
126
  }
41
127
 
42
128
  // Toggle specific todos
43
- store.notify("toggle", "todo-1")
44
- store.notify("toggle", "todo-2")
129
+ store.notify("toggle", "todo1")
130
+ store.notify("toggle", "todo2")
45
131
  ```
46
132
 
47
133
  > **Important:** `toggle` is not a methodโ€”it's an **event handler**. When you notify an event, it's broadcast to **all entities** that have that handler (pub/sub pattern). Use the payload to filter which entities should respond.
48
134
 
49
135
  ### ๐Ÿ”„ **Event Queue with Batching**
50
136
 
51
- Events are queued and processed together, preventing cascading updates and enabling predictable state changes.
137
+ Events are queued and processed together in batched mode, preventing cascading updates and enabling predictable state changes.
52
138
 
53
139
  ```javascript
140
+ const store = createStore({ types, entities, mode: "batched" })
141
+
54
142
  // Dispatch multiple events
55
- store.notify("increment", "counter-1")
56
- store.notify("increment", "counter-2")
57
- store.notify("increment", "counter-3")
143
+ store.notify("increment", "counter1")
144
+ store.notify("increment", "counter2")
145
+ store.notify("increment", "counter3")
58
146
 
59
147
  // Process all at once (single React re-render)
60
148
  store.update()
@@ -75,9 +163,13 @@ store.setState(snapshot) // Instant undo
75
163
  Synchronize state across clients by sending serializable events. Same events + same handlers = guaranteed sync.
76
164
 
77
165
  ```javascript
78
- socket.on("userAction", (event) => {
166
+ // Start building solo
167
+ store.notify("addTodo", { text: "Buy milk" })
168
+
169
+ // Add multiplayer later in ~10 lines
170
+ socket.on("remote-event", (event) => {
79
171
  store.notify(event.type, event.payload)
80
- // All clients stay in perfect sync
172
+ // States stay perfectly in sync across all clients
81
173
  })
82
174
  ```
83
175
 
@@ -100,14 +192,6 @@ Works with `react-redux` and Redux DevTools. Provides both `notify()` and `dispa
100
192
 
101
193
  ---
102
194
 
103
- ## Installation
104
-
105
- ```bash
106
- npm install @inglorious/store
107
- ```
108
-
109
- ---
110
-
111
195
  ## Quick Start
112
196
 
113
197
  ### Simple Counter Example
@@ -209,8 +293,8 @@ const entities = {
209
293
  const store = createStore({ types, entities })
210
294
 
211
295
  // 4. Create selectors
212
- const selectTasks = ({ entities }) => entities.list.tasks
213
- const selectActiveFilter = ({ entities }) => entities.footer.activeFilter
296
+ const selectTasks = (state) => state.list.tasks
297
+ const selectActiveFilter = (state) => state.footer.activeFilter
214
298
 
215
299
  const selectFilteredTasks = createSelector(
216
300
  [selectTasks, selectActiveFilter],
@@ -234,10 +318,10 @@ store.subscribe(() => {
234
318
  // 6. Dispatch events (use notify or dispatch - both work!)
235
319
  store.notify("inputChange", "Buy milk")
236
320
  store.notify("formSubmit", store.getState().form.value)
237
- store.notify("toggleClick", 1) // Only todo with id=1 will respond
321
+ store.notify("toggleClick", 1) // Only task with id=1 will respond
238
322
  store.notify("filterClick", "active")
239
323
 
240
- // 7. Process event queue
324
+ // 7. Process event queue (in eager mode this happens automatically)
241
325
  store.update()
242
326
  ```
243
327
 
@@ -249,7 +333,7 @@ store.update()
249
333
 
250
334
  **This is not OOP with methodsโ€”it's a pub/sub (publish/subscribe) event system.**
251
335
 
252
- When you call `store.notify('toggle', 'todo-1')`, the `toggle` event is broadcast to **all entities**. Any entity that has a `toggle` handler will process the event and decide whether to respond based on the payload.
336
+ When you call `store.notify('toggle', 'todo1')`, the `toggle` event is broadcast to **all entities**. Any entity that has a `toggle` handler will process the event and decide whether to respond based on the payload.
253
337
 
254
338
  ```javascript
255
339
  const todoType = {
@@ -261,7 +345,7 @@ const todoType = {
261
345
  }
262
346
 
263
347
  // This broadcasts 'toggle' to all entities
264
- store.notify("toggle", "todo-1") // Only todo-1 actually updates
348
+ store.notify("toggle", "todo1") // Only todo1 actually updates
265
349
  ```
266
350
 
267
351
  **Why this matters:**
@@ -355,6 +439,7 @@ store.notify("applyDiscount", { id: "item1", percent: 10 })
355
439
  store.dispatch({ type: "increment", payload: "counter1" })
356
440
 
357
441
  // Process the queue - this is when handlers actually run
442
+ // (In eager mode, this happens automatically)
358
443
  store.update()
359
444
  ```
360
445
 
@@ -362,20 +447,106 @@ store.update()
362
447
 
363
448
  ### Systems (Optional)
364
449
 
365
- For global state logic that doesn't belong to a specific entity type.
450
+ Systems are global event handlers that can coordinate updates across **multiple entities at once**. Unlike entity handlers (which run once per entity), a system runs **once per event** and has write-access to the entire state.
451
+
452
+ **When you need a system:**
453
+
454
+ - Multiple entities need to update based on relationships between them
455
+ - Updates require looking at all entities together (not individually)
456
+ - Logic that can't be expressed as independent entity handlers
457
+
458
+ **Example: Inventory Weight Limits**
459
+
460
+ When adding an item to inventory, you need to check if the **total weight** of all items exceeds the limit. This can't be done in individual item handlers because each item only knows about itself.
461
+
462
+ ```javascript
463
+ const types = {
464
+ item: {
465
+ addToInventory(item, newItemData) {
466
+ // Individual items don't know about other items
467
+ // Can't check total weight here!
468
+ },
469
+ },
470
+ }
471
+
472
+ const systems = [
473
+ {
474
+ addToInventory(state, newItemData) {
475
+ // Calculate total weight across ALL items
476
+ const items = Object.values(state).filter((e) => e.type === "item")
477
+ const currentWeight = items.reduce((sum, item) => sum + item.weight, 0)
478
+ const maxWeight = state.player.maxCarryWeight
479
+
480
+ // Check if adding this item would exceed the limit
481
+ if (currentWeight + newItemData.weight > maxWeight) {
482
+ // Reject the add - drop the heaviest item instead
483
+ const heaviestItem = items.reduce((max, item) =>
484
+ item.weight > max.weight ? item : max,
485
+ )
486
+ delete state[heaviestItem.id]
487
+ state.ui.message = `Dropped ${heaviestItem.name} (too heavy!)`
488
+ }
489
+
490
+ // Add the new item
491
+ const newId = `item${Date.now()}`
492
+ state[newId] = {
493
+ id: newId,
494
+ type: "item",
495
+ ...newItemData,
496
+ }
497
+ },
498
+ },
499
+ ]
500
+ ```
501
+
502
+ **Why this needs a system:**
503
+
504
+ - Requires reading **all items** to calculate total weight
505
+ - Must make a **coordinated decision** (which item to drop)
506
+ - Updates **multiple entities** based on aggregate state (delete one, add another)
507
+ - Can't be split into independent entity handlers
508
+
509
+ **Another example: Multiplayer Turn System**
366
510
 
367
511
  ```javascript
368
512
  const systems = [
369
513
  {
370
- calculateTotal(entities) {
371
- state.cartTotal = Object.values(entities)
372
- .filter((e) => e.type === "cartItem")
373
- .reduce((sum, item) => sum + item.price * item.quantity, 0)
514
+ endTurn(state, playerId) {
515
+ // Find current player
516
+ const players = Object.values(state).filter((e) => e.type === "player")
517
+ const currentPlayer = players.find((p) => p.id === playerId)
518
+
519
+ // Mark current player's turn as ended
520
+ currentPlayer.isTurn = false
521
+ currentPlayer.actionsRemaining = 0
522
+
523
+ // Find next player
524
+ const nextPlayerIndex =
525
+ (players.indexOf(currentPlayer) + 1) % players.length
526
+ const nextPlayer = players[nextPlayerIndex]
527
+
528
+ // Give turn to next player
529
+ nextPlayer.isTurn = true
530
+ nextPlayer.actionsRemaining = 3
531
+
532
+ // Update round counter if we've cycled through all players
533
+ if (nextPlayerIndex === 0) {
534
+ state.gameState.round++
535
+ }
374
536
  },
375
537
  },
376
538
  ]
377
539
  ```
378
540
 
541
+ **This requires a system because:**
542
+
543
+ - Must coordinate between multiple player entities
544
+ - Needs to maintain turn order across all players
545
+ - Updates multiple entities in a specific sequence
546
+ - Logic can't be split per-player
547
+
548
+ **For most apps, you won't need systems.** Use selectors for derived data and entity handlers for individual entity logic.
549
+
379
550
  ---
380
551
 
381
552
  ## API Reference
@@ -389,6 +560,8 @@ Creates a new store instance.
389
560
  - `types` (object): Map of type names to behaviors (single object or array)
390
561
  - `entities` (object): Initial entities by ID
391
562
  - `systems` (array, optional): Global event handlers
563
+ - `middlewares` (array, optional): Middleware functions that enhance store behavior
564
+ - `mode` (`"eager"|"batched"`, optional): Whether `store.update()` is invoked automatically at every `store.notify()` or manually. Defaults to `"eager"`, which makes the store behave like Redux
392
565
 
393
566
  **Returns:**
394
567
 
@@ -396,6 +569,7 @@ Creates a new store instance.
396
569
  - `update(dt)`: Process event queue (optional `dt` for time-based logic)
397
570
  - `notify(type, payload)`: Queue an event
398
571
  - `dispatch(event)`: Redux-compatible event dispatch
572
+ - `getTypes()`: Returns the augmented types configuration
399
573
  - `getState()`: Get current immutable state
400
574
  - `setState(newState)`: Replace entire state
401
575
  - `reset()`: Reset to initial state
@@ -415,8 +589,9 @@ Creates a convenience wrapper with utility methods.
415
589
  Create memoized, performant selectors.
416
590
 
417
591
  ```javascript
418
- const selectCompletedTasks = createSelector([(state) => state.tasks], (tasks) =>
419
- tasks.filter((task) => task.completed),
592
+ const selectCompletedTasks = createSelector(
593
+ [(state) => state.list.tasks],
594
+ (tasks) => tasks.filter((task) => task.completed),
420
595
  )
421
596
  ```
422
597
 
@@ -426,39 +601,42 @@ const selectCompletedTasks = createSelector([(state) => state.tasks], (tasks) =>
426
601
 
427
602
  ### โœ… Perfect For
428
603
 
429
- - **Real-time collaboration** (like Figma, Google Docs)
604
+ - **Apps with async operations** (API calls, data fetching - use batched mode)
605
+ - **Apps that might need collaboration someday** (start simple, scale without refactoring)
606
+ - **Real-time collaboration** (like Figma, Notion, Google Docs)
430
607
  - **Chat and messaging apps**
431
608
  - **Live dashboards and monitoring**
432
609
  - **Interactive data visualizations**
433
610
  - **Apps with undo/redo**
434
- - **Multiplayer features**
435
611
  - **Collection-based UIs** (lists, feeds, boards)
436
612
  - **...and games!**
437
613
 
438
614
  ### ๐Ÿค” Maybe Overkill For
439
615
 
440
- - Simple forms with local state
616
+ - Simple forms with local state only
441
617
  - Static marketing pages
442
- - Basic CRUD with no real-time needs
618
+ - Apps that will **definitely never** need real-time features
619
+
620
+ **But here's the thing:** Most successful apps eventually need collaboration, undo/redo, or live updates. With Inglorious Store, you're ready when that happens.
443
621
 
444
622
  ---
445
623
 
446
624
  ## Comparison
447
625
 
448
- | Feature | Inglorious Store | Redux | Redux Toolkit | Zustand | Jotai | Pinia | MobX |
449
- | --------------------------- | ----------------- | ------------------- | ---------------- | ------------- | ------------- | --------------- | --------------- |
450
- | **Integrated Immutability** | โœ… Mutative | โŒ Manual | โœ… Immer | โŒ Manual | โœ… Optional | โœ… Built-in | โœ… Observables |
451
- | **Event Queue/Batching** | โœ… Built-in | โŒ | โŒ | โŒ | โŒ | โŒ | โœ… Automatic |
452
- | **Dispatch from Handlers** | โœ… Safe (queued) | โŒ Not allowed | โŒ Not allowed | โœ… | โœ… | โœ… | โœ… |
453
- | **Redux DevTools** | โš ๏ธ Limited | โœ… Native | โœ… Native | โœ… Middleware | โš ๏ธ Limited | โœ… Vue DevTools | โš ๏ธ Limited |
454
- | **react-redux Compatible** | โœ… Yes | โœ… Yes | โœ… Yes | โŒ | โŒ | โŒ Vue only | โŒ |
455
- | **Time-Travel Debug** | โœ… Built-in | โœ… Via DevTools | โœ… Via DevTools | โš ๏ธ Manual | โŒ | โš ๏ธ Limited | โŒ |
456
- | **Entity-Based State** | โœ… First-class | โš ๏ธ Manual normalize | โœ… EntityAdapter | โŒ | โŒ | โŒ | โŒ |
457
- | **Pub/Sub Events** | โœ… Core pattern | โŒ | โŒ | โŒ | โŒ | โŒ | โŒ |
458
- | **Multiplayer-Ready** | โœ… Deterministic | โš ๏ธ With work | โš ๏ธ With work | โš ๏ธ With work | โŒ | โŒ | โŒ |
459
- | **Testability** | โœ… Pure functions | โœ… Pure reducers | โœ… Pure reducers | โš ๏ธ With mocks | โš ๏ธ With mocks | โš ๏ธ With mocks | โŒ Side effects |
460
- | **Learning Curve** | Medium | High | Medium | Low | Medium | Low | Medium |
461
- | **Bundle Size** | Small | Small | Medium | Tiny | Small | Medium | Medium |
626
+ | Feature | Inglorious Store | Redux | Redux Toolkit | Zustand | Jotai | Pinia | MobX |
627
+ | --------------------------- | ----------------- | ---------------- | ---------------- | ------------- | ------------- | --------------- | --------------- |
628
+ | **Integrated Immutability** | โœ… Mutative | โŒ Manual | โœ… Immer | โŒ Manual | โœ… Optional | โœ… Built-in | โœ… Observables |
629
+ | **Event Queue/Batching** | โœ… Built-in | โŒ | โŒ | โŒ | โŒ | โŒ | โœ… Automatic |
630
+ | **Dispatch from Handlers** | โœ… Safe (queued) | โŒ Not allowed | โŒ Not allowed | โœ… | โœ… | โœ… | โœ… |
631
+ | **Redux DevTools** | โš ๏ธ Limited | โœ… Native | โœ… Native | โœ… Middleware | โš ๏ธ Limited | โœ… Vue DevTools | โš ๏ธ Limited |
632
+ | **react-redux Compatible** | โœ… Yes | โœ… Yes | โœ… Yes | โŒ | โŒ | โŒ Vue only | โŒ |
633
+ | **Time-Travel Debug** | โœ… Built-in | โœ… Via DevTools | โœ… Via DevTools | โš ๏ธ Manual | โŒ | โš ๏ธ Limited | โŒ |
634
+ | **Entity-Based State** | โœ… First-class | โš ๏ธ Manual | โœ… EntityAdapter | โŒ | โŒ | โŒ | โŒ |
635
+ | **Pub/Sub Events** | โœ… Core pattern | โŒ | โŒ | โŒ | โŒ | โŒ | โŒ |
636
+ | **Multiplayer-Ready** | โœ… Deterministic | โš ๏ธ With work | โš ๏ธ With work | โš ๏ธ With work | โŒ | โŒ | โŒ |
637
+ | **Testability** | โœ… Pure functions | โœ… Pure reducers | โœ… Pure reducers | โš ๏ธ With mocks | โš ๏ธ With mocks | โš ๏ธ With mocks | โŒ Side effects |
638
+ | **Learning Curve** | Medium | High | Medium | Low | Medium | Low | Medium |
639
+ | **Bundle Size** | Small | Small | Medium | Tiny | Small | Medium | Medium |
462
640
 
463
641
  ### Key Differences
464
642
 
@@ -519,25 +697,59 @@ const selectCompletedTasks = createSelector([(state) => state.tasks], (tasks) =>
519
697
 
520
698
  ## Advanced: Real-Time Sync
521
699
 
700
+ Adding multiplayer to an existing app is usually a massive refactor. With Inglorious Store, it's an afternoon project.
701
+
702
+ ### Step 1: Your app already works locally
703
+
704
+ ```javascript
705
+ store.notify("movePlayer", { x: 10, y: 20 })
706
+ store.update()
707
+ ```
708
+
709
+ ### Step 2: Add WebSocket (literally ~10 lines)
710
+
522
711
  ```javascript
523
- // Client-side
524
- socket.on("server-event", (event) => {
712
+ // Receive events from other clients
713
+ socket.on("remote-event", (event) => {
525
714
  store.notify(event.type, event.payload)
526
- store.update()
527
715
  })
528
716
 
529
- // Send local events to server
530
- store.subscribe(() => {
531
- const state = store.getState()
532
- socket.emit("state-update", serializeState(state))
717
+ // Send your events to other clients
718
+ const processedEvents = store.update()
719
+ processedEvents.forEach((event) => {
720
+ socket.emit("event", event)
533
721
  })
534
722
  ```
535
723
 
724
+ **That's it.** Because your event handlers are pure functions and the state is deterministic, all clients stay perfectly in sync.
725
+
726
+ ### Why This Works
727
+
728
+ 1. **Deterministic:** Same events + same state = same result (always)
729
+ 2. **Serializable:** Events are plain objects (easy to send over network)
730
+ 3. **Ordered:** Event queue ensures predictable processing
731
+ 4. **Conflict-free:** Last write wins, or implement custom merge logic
732
+
733
+ ### Example: Collaborative Todo List
734
+
735
+ ```javascript
736
+ // Client A adds a todo
737
+ store.notify("addTodo", { id: "todo1", text: "Buy milk" })
738
+
739
+ // Event gets broadcast to all clients
740
+ // All clients process the same event
741
+ // All clients end up with identical state
742
+
743
+ // Even works offline! Events queue up, sync when reconnected
744
+ ```
745
+
746
+ This is exactly how multiplayer games work. Now your app can too.
747
+
536
748
  ---
537
749
 
538
750
  ## Advanced: Time-Based Updates
539
751
 
540
- For animations or continuous updates (like in games):
752
+ For animations, games, or any time-dependent logic, you can run a continuous update loop:
541
753
 
542
754
  ```javascript
543
755
  const types = {
@@ -553,14 +765,51 @@ const types = {
553
765
  ],
554
766
  }
555
767
 
556
- // In your game/animation loop
557
- function loop(timestamp) {
558
- const dt = timestamp - lastTime
559
- store.update(dt)
768
+ // Run at 30 FPS (good for most UIs)
769
+ setInterval(() => store.update(), 1000 / 30)
770
+
771
+ // Or 60 FPS (for smooth animations/games)
772
+ function loop() {
773
+ store.update()
560
774
  requestAnimationFrame(loop)
561
775
  }
776
+ loop()
777
+ ```
778
+
779
+ **For typical apps (todos, forms, dashboards):** Use eager mode (default). No loop needed.
780
+
781
+ **For real-time apps (games, animations, live data):** Use batched mode with a loop for smooth, consistent updates.
782
+
783
+ ---
784
+
785
+ ## The Path from Solo to Multiplayer
786
+
787
+ ### Week 1: Build a simple todo app
788
+
789
+ ```javascript
790
+ store.notify("addTodo", { text: "Buy milk" })
791
+ ```
792
+
793
+ _Works great. Clean architecture. Nothing fancy._
794
+
795
+ ### Month 6: Users love it, ask for undo/redo
796
+
797
+ ```javascript
798
+ const snapshot = store.getState()
799
+ // ... user makes changes ...
800
+ store.setState(snapshot) // Undo!
801
+ ```
802
+
803
+ _Already built-in. No refactoring needed._
804
+
805
+ ### Year 1: Competitor launches with real-time collaboration
806
+
807
+ ```javascript
808
+ socket.on("remote-event", (e) => store.notify(e.type, e.payload))
562
809
  ```
563
810
 
811
+ _Add multiplayer in an afternoon. You win._
812
+
564
813
  ---
565
814
 
566
815
  ## Part of the Inglorious Engine
@@ -569,11 +818,22 @@ This store powers the [Inglorious Engine](https://github.com/IngloriousCoderz/in
569
818
 
570
819
  ---
571
820
 
821
+ ## What's Next?
822
+
823
+ - ๐Ÿ“– **[@inglorious/react-store](https://github.com/IngloriousCoderz/inglorious-engine/tree/main/packages/react-store)** - React integration with hooks
824
+ - ๐ŸŽฎ **[@inglorious/engine](https://github.com/IngloriousCoderz/inglorious-engine)** - Full game engine built on this store
825
+ - ๐ŸŒ **[@inglorious/server](https://github.com/IngloriousCoderz/inglorious-engine/tree/main/packages/server)** - Server-side multiplayer support
826
+ - ๐Ÿ’ฌ **[GitHub Discussions](https://github.com/IngloriousCoderz/inglorious-engine/discussions)** - Get help and share what you're building
827
+
828
+ ---
829
+
572
830
  ## License
573
831
 
574
- MIT ยฉ [Matteo Antony Mistretta](https://github.com/IngloriousCoderz)
832
+ **MIT License - Free and open source**
833
+
834
+ Created by [Matteo Antony Mistretta](https://github.com/IngloriousCoderz)
575
835
 
576
- This is free and open-source software. Use it however you want!
836
+ You're free to use, modify, and distribute this software. See [LICENSE](../../LICENSE) for details.
577
837
 
578
838
  ---
579
839
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@inglorious/store",
3
- "version": "5.2.0",
3
+ "version": "5.4.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",
@@ -42,10 +42,10 @@
42
42
  },
43
43
  "dependencies": {
44
44
  "mutative": "^1.3.0",
45
- "@inglorious/utils": "3.6.1"
45
+ "@inglorious/utils": "3.6.2"
46
46
  },
47
47
  "peerDependencies": {
48
- "@inglorious/utils": "3.6.1"
48
+ "@inglorious/utils": "3.6.2"
49
49
  },
50
50
  "devDependencies": {
51
51
  "prettier": "^3.6.2",
package/src/store.js CHANGED
@@ -11,6 +11,9 @@ import { augmentType, augmentTypes } from "./types.js"
11
11
  * @param {Object} config - Configuration options for the store.
12
12
  * @param {Object} [config.types] - The initial types configuration.
13
13
  * @param {Object} [config.entities] - The initial entities configuration.
14
+ * @param {Array} [config.systens] - The initial systems configuration.
15
+ * @param {Array} [config.middlewares] - The initial middlewares configuration.
16
+ * @param {"eager" | "batched"} [config.mode] - The dispatch mode (defaults to "eager").
14
17
  * @returns {Object} The store with methods to interact with state and events.
15
18
  */
16
19
  export function createStore({
@@ -18,6 +21,7 @@ export function createStore({
18
21
  entities: originalEntities,
19
22
  systems = [],
20
23
  middlewares = [],
24
+ mode = "eager",
21
25
  }) {
22
26
  const listeners = new Set()
23
27
 
@@ -33,7 +37,6 @@ export function createStore({
33
37
  dispatch, // needed for compatibility with Redux
34
38
  getApi,
35
39
  getTypes,
36
- getOriginalTypes,
37
40
  getState,
38
41
  setState,
39
42
  reset,
@@ -146,6 +149,9 @@ export function createStore({
146
149
  */
147
150
  function dispatch(event) {
148
151
  incomingEvents.push(event)
152
+ if (mode === "eager") {
153
+ update()
154
+ }
149
155
  }
150
156
 
151
157
  /**
@@ -165,14 +171,6 @@ export function createStore({
165
171
  return types
166
172
  }
167
173
 
168
- /**
169
- * Retrieves the original, un-augmented types configuration.
170
- * @returns {Object} The original types configuration.
171
- */
172
- function getOriginalTypes() {
173
- return originalTypes
174
- }
175
-
176
174
  /**
177
175
  * Retrieves the current state.
178
176
  * @returns {Object} The current state.
package/src/store.test.js CHANGED
@@ -43,6 +43,7 @@ test("it should process an event queue in the same update cycle", () => {
43
43
  },
44
44
  },
45
45
  },
46
+
46
47
  entities: {
47
48
  kitty1: { type: "kitty" },
48
49
  },
@@ -65,7 +66,7 @@ test("it should process an event queue in the same update cycle", () => {
65
66
  expect(state).toStrictEqual(afterState)
66
67
  })
67
68
 
68
- test("it should send an event from an entity and process it in the same update cycle", () => {
69
+ test("it should send an event from an entity and process it in the same update cycle in batched mode", () => {
69
70
  const config = {
70
71
  types: {
71
72
  doggo: {
@@ -79,10 +80,13 @@ test("it should send an event from an entity and process it in the same update c
79
80
  },
80
81
  },
81
82
  },
83
+
82
84
  entities: {
83
85
  doggo1: { type: "doggo" },
84
86
  kitty1: { type: "kitty", position: "near" },
85
87
  },
88
+
89
+ mode: "batched",
86
90
  }
87
91
  const afterState = {
88
92
  doggo1: { id: "doggo1", type: "doggo" },