@inglorious/store 3.0.0 → 4.0.1

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
@@ -21,7 +21,7 @@ npm install @inglorious/store
21
21
 
22
22
  The state management is built on a few simple principles:
23
23
 
24
- 1. **Entities and Properties**: The state is composed of **entities**, which are unique objects. Each entity has a **type** and a set of properties (e.g., `position: [0, 0, 0]`, `health: 100`). Unlike a traditional ECS, properties are not grouped into explicit components.
24
+ 1. **Entities and Properties**: The state is composed of **entities**, which are unique objects. Each entity has a **type** and a set of properties (e.g., `position: v(0, 0, 0)`, `health: 100`). Unlike a traditional ECS, properties are not grouped into explicit components.
25
25
 
26
26
  2. **Types and Behaviors**: The logic for how entities and the overall state change is defined in **types** and **systems**.
27
27
  - **Types** are arrays of **behaviors**. A behavior is an object that contains event handlers (e.g., `update(entity, dt) { ... }`). Behaviors are composable, allowing you to define a type by combining multiple sets of properties and event handlers.
@@ -31,7 +31,7 @@ The state management is built on a few simple principles:
31
31
  - The store processes events by first applying behaviors defined on an entity's type, and then running the logic in the systems.
32
32
  - An `update` event with a `dt` (delta time) payload is automatically dispatched on every `store.update()` call, making it suitable for a game loop.
33
33
 
34
- 4. **Immutability**: The state is immutable. Updates are handled internally by **[Immer](https://immerjs.github.io/immer/)**, so you can "mutate" the state directly within a type's or system's behavior function, and a new, immutable state will be produced.
34
+ 4. **Immutability**: The state is immutable. Updates are handled internally by **[Mutative](https://mutative.js.org/)**, so you can "mutate" the state directly within a type's or system's behavior function, and a new, immutable state will be created.
35
35
 
36
36
  ---
37
37
 
@@ -101,7 +101,7 @@ Here is a simple example of a player entity that moves based on events.
101
101
 
102
102
  ```javascript
103
103
  import { createStore, createApi } from "@inglorious/store"
104
- import { add, scale } from "@inglorious/utils/math/linear-algebra/vectors.js"
104
+ import { add, scale } from "@inglorious/utils/math/vectors.js"
105
105
 
106
106
  // 1. Define the behaviors
107
107
  const transform = {
@@ -133,8 +133,8 @@ const types = {
133
133
  const entities = {
134
134
  player1: {
135
135
  type: "player",
136
- position: [0, 0, 0],
137
- velocity: [0.0625, 0, 0],
136
+ position: v(0, 0, 0),
137
+ velocity: v(0.0625, 0, 0),
138
138
  },
139
139
  }
140
140
 
@@ -154,17 +154,17 @@ store.subscribe(() => {
154
154
  })
155
155
 
156
156
  // 7. Notify the store of an event
157
- console.log("Initial player position:", selectPlayerPosition()) // => [0, 0, 0]
157
+ console.log("Initial player position:", selectPlayerPosition()) // => v(0, 0, 0)
158
158
 
159
159
  // Dispatch a custom `move` event with a payload
160
160
  api.notify("move", { id: "player1", dx: 5, dz: 5 })
161
161
 
162
162
  // Events are queued but not yet processed
163
- console.log("Position after notify:", selectPlayerPosition()) // => [0, 0, 0]
163
+ console.log("Position after notify:", selectPlayerPosition()) // => v(0, 0, 0)
164
164
 
165
165
  // 8. Run the update loop to process the queue and trigger `update` behaviors
166
166
  store.update(16) // Pass delta time
167
- // Console output from subscriber: "State updated! [6, 0, 5]"
167
+ // Console output from subscriber: "State updated! v(6, 0, 5)"
168
168
 
169
- console.log("Final position:", selectPlayerPosition()) // => [6, 0, 5]
169
+ console.log("Final position:", selectPlayerPosition()) // => v(6, 0, 5)
170
170
  ```
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@inglorious/store",
3
- "version": "3.0.0",
3
+ "version": "4.0.1",
4
4
  "description": "A state manager inspired by Redux, but tailored for the specific needs of game development.",
5
5
  "author": "IceOnFire <antony.mistretta@gmail.com> (https://ingloriouscoderz.it)",
6
6
  "license": "MIT",
@@ -27,19 +27,23 @@
27
27
  "./*": "./src/*"
28
28
  },
29
29
  "files": [
30
- "src/*"
30
+ "src"
31
31
  ],
32
32
  "publishConfig": {
33
33
  "access": "public"
34
34
  },
35
35
  "dependencies": {
36
- "immer": "^10.1.1",
37
- "@inglorious/utils": "2.0.0"
36
+ "mutative": "^1.3.0",
37
+ "@inglorious/utils": "3.5.0"
38
+ },
39
+ "peerDependencies": {
40
+ "@inglorious/utils": "3.5.0"
38
41
  },
39
42
  "devDependencies": {
40
43
  "prettier": "^3.6.2",
41
44
  "vite": "^7.1.3",
42
- "vitest": "^1.6.1"
45
+ "vitest": "^1.6.1",
46
+ "@inglorious/eslint-config": "1.0.0"
43
47
  },
44
48
  "engines": {
45
49
  "node": ">= 22"
@@ -0,0 +1,47 @@
1
+ import { compose } from "@inglorious/utils/functions/functions.js"
2
+
3
+ /**
4
+ * Applies a list of middleware functions to a store's dispatch method.
5
+ * @param {...Function} middlewares The middleware functions to apply.
6
+ * @returns {Function} A store enhancer function.
7
+ */
8
+ export function applyMiddlewares(...middlewares) {
9
+ return (store, baseApi) => {
10
+ let dispatch = () => {
11
+ throw new Error(
12
+ "Dispatching while constructing your middleware is not allowed.",
13
+ )
14
+ }
15
+
16
+ // Enhanced store interface that middlewares receive
17
+ const middlewareStore = {
18
+ getState: store.getState,
19
+ setState: store.setState,
20
+ dispatch: (...args) => dispatch(...args),
21
+ notify: (type, payload) => dispatch({ type, payload }),
22
+ }
23
+
24
+ // Enhanced API interface that middlewares receive
25
+ const middlewareApi = {
26
+ ...baseApi,
27
+ dispatch: (...args) => dispatch(...args),
28
+ notify: (type, payload) => dispatch({ type, payload }),
29
+ }
30
+
31
+ // Create middleware chain - each middleware gets the store and can access baseApi
32
+ const chain = middlewares.map((middleware) =>
33
+ middleware(middlewareStore, middlewareApi),
34
+ )
35
+
36
+ // Compose the middleware chain to create enhanced dispatch
37
+ dispatch = compose(...chain)(store.dispatch)
38
+
39
+ // Return enhanced API that external code will use
40
+ return {
41
+ ...middlewareApi,
42
+ // Override dispatch/notify with the middleware-enhanced versions
43
+ dispatch,
44
+ notify: (type, payload) => dispatch({ type, payload }),
45
+ }
46
+ }
47
+ }
package/src/store.js CHANGED
@@ -1,12 +1,13 @@
1
- import { produce } from "immer"
1
+ import { create } from "mutative"
2
2
 
3
+ import { createApi } from "./api.js"
3
4
  import { augmentEntities, augmentEntity } from "./entities.js"
4
5
  import { EventMap } from "./event-map.js"
6
+ import { applyMiddlewares } from "./middlewares.js"
5
7
  import { augmentType, augmentTypes } from "./types.js"
6
8
 
7
9
  /**
8
10
  * Creates a store to manage state and events.
9
- *
10
11
  * @param {Object} config - Configuration options for the store.
11
12
  * @param {Object} [config.types] - The initial types configuration.
12
13
  * @param {Object} [config.entities] - The initial entities configuration.
@@ -16,6 +17,7 @@ export function createStore({
16
17
  types: originalTypes,
17
18
  entities: originalEntities,
18
19
  systems = [],
20
+ middlewares = [],
19
21
  }) {
20
22
  const listeners = new Set()
21
23
 
@@ -24,11 +26,12 @@ export function createStore({
24
26
  let state, eventMap, incomingEvents
25
27
  reset()
26
28
 
27
- return {
29
+ const baseStore = {
28
30
  subscribe,
29
31
  update,
30
32
  notify,
31
33
  dispatch, // needed for compatibility with Redux
34
+ getApi,
32
35
  getTypes,
33
36
  getOriginalTypes,
34
37
  getState,
@@ -36,9 +39,16 @@ export function createStore({
36
39
  reset,
37
40
  }
38
41
 
42
+ const baseApi = createApi(baseStore)
43
+
44
+ const api = middlewares.length
45
+ ? applyMiddlewares(...middlewares)(baseStore, baseApi)
46
+ : baseApi
47
+
48
+ return baseStore
49
+
39
50
  /**
40
51
  * Subscribes a listener to state updates.
41
- *
42
52
  * @param {Function} listener - The listener function to call on updates.
43
53
  * @returns {Function} A function to unsubscribe the listener.
44
54
  */
@@ -52,14 +62,12 @@ export function createStore({
52
62
 
53
63
  /**
54
64
  * Updates the state based on elapsed time and processes events.
55
- *
56
65
  * @param {number} dt - The delta time since the last update in milliseconds.
57
- * @param {Object} api - The engine's public API.
58
66
  */
59
- function update(api) {
67
+ function update() {
60
68
  const processedEvents = []
61
69
 
62
- state = produce(state, (state) => {
70
+ state = create(state, (draft) => {
63
71
  while (incomingEvents.length) {
64
72
  const event = incomingEvents.shift()
65
73
  processedEvents.push(event)
@@ -67,7 +75,7 @@ export function createStore({
67
75
  if (event.type === "morph") {
68
76
  const { id, type } = event.payload
69
77
 
70
- const entity = state.entities[id]
78
+ const entity = draft.entities[id]
71
79
  const oldType = types[entity.type]
72
80
 
73
81
  originalTypes[id] = type
@@ -80,7 +88,7 @@ export function createStore({
80
88
 
81
89
  if (event.type === "add") {
82
90
  const { id, ...entity } = event.payload
83
- state.entities[id] = augmentEntity(id, entity)
91
+ draft.entities[id] = augmentEntity(id, entity)
84
92
  const type = types[entity.type]
85
93
 
86
94
  eventMap.addEntity(id, type)
@@ -89,9 +97,9 @@ export function createStore({
89
97
 
90
98
  if (event.type === "remove") {
91
99
  const id = event.payload
92
- const entity = state.entities[id]
100
+ const entity = draft.entities[id]
93
101
  const type = types[entity.type]
94
- delete state.entities[id]
102
+ delete draft.entities[id]
95
103
 
96
104
  eventMap.removeEntity(id, type)
97
105
  incomingEvents.unshift({ type: "destroy", payload: id })
@@ -99,7 +107,7 @@ export function createStore({
99
107
 
100
108
  const entityIds = eventMap.getEntitiesForEvent(event.type)
101
109
  for (const id of entityIds) {
102
- const entity = state.entities[id]
110
+ const entity = draft.entities[id]
103
111
  const type = types[entity.type]
104
112
  const handle = type[event.type]
105
113
  handle(entity, event.payload, api)
@@ -107,7 +115,7 @@ export function createStore({
107
115
 
108
116
  systems.forEach((system) => {
109
117
  const handle = system[event.type]
110
- handle?.(state, event.payload, api)
118
+ handle?.(draft, event.payload, api)
111
119
  })
112
120
  }
113
121
  })
@@ -119,7 +127,6 @@ export function createStore({
119
127
 
120
128
  /**
121
129
  * Notifies the store of a new event.
122
- *
123
130
  * @param {string} type - The event object type to notify.
124
131
  * @param {any} payload - The event object payload to notify.
125
132
  */
@@ -129,7 +136,6 @@ export function createStore({
129
136
 
130
137
  /**
131
138
  * Dispatches an event to be processed in the next update cycle.
132
- *
133
139
  * @param {Object} event - The event object.
134
140
  * @param {string} event.type - The type of the event.
135
141
  * @param {any} [event.payload] - The payload of the event.
@@ -138,10 +144,17 @@ export function createStore({
138
144
  incomingEvents.push(event)
139
145
  }
140
146
 
147
+ /**
148
+ * Retrieves the store's API, which includes methods for interacting with the store.
149
+ * @returns {Object} The API object for the store.
150
+ */
151
+ function getApi() {
152
+ return api
153
+ }
154
+
141
155
  /**
142
156
  * Retrieves the augmented types configuration.
143
157
  * This includes composed behaviors and event handlers wrapped for immutability.
144
- *
145
158
  * @returns {Object} The augmented types configuration.
146
159
  */
147
160
  function getTypes() {
@@ -150,7 +163,6 @@ export function createStore({
150
163
 
151
164
  /**
152
165
  * Retrieves the original, un-augmented types configuration.
153
- *
154
166
  * @returns {Object} The original types configuration.
155
167
  */
156
168
  function getOriginalTypes() {
@@ -159,7 +171,6 @@ export function createStore({
159
171
 
160
172
  /**
161
173
  * Retrieves the current state.
162
- *
163
174
  * @returns {Object} The current state.
164
175
  */
165
176
  function getState() {
@@ -169,7 +180,6 @@ export function createStore({
169
180
  /**
170
181
  * Sets the entire state of the store.
171
182
  * This is useful for importing state or setting initial state from a server.
172
- *
173
183
  * @param {Object} nextState - The new state to set.
174
184
  */
175
185
  function setState(nextState) {