@inglorious/store 9.5.2 → 9.5.3
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/package.json +2 -1
- package/src/migration/rtk.js +161 -21
- package/types/migration/rtk.d.ts +7 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@inglorious/store",
|
|
3
|
-
"version": "9.5.
|
|
3
|
+
"version": "9.5.3",
|
|
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",
|
|
@@ -62,6 +62,7 @@
|
|
|
62
62
|
"@inglorious/utils": "3.7.3"
|
|
63
63
|
},
|
|
64
64
|
"devDependencies": {
|
|
65
|
+
"@reduxjs/toolkit": "^2.11.2",
|
|
65
66
|
"prettier": "^3.6.2",
|
|
66
67
|
"vite": "^7.1.3",
|
|
67
68
|
"vitest": "^1.6.1",
|
package/src/migration/rtk.js
CHANGED
|
@@ -5,11 +5,14 @@
|
|
|
5
5
|
* Inglorious types, enabling gradual migration from RTK to Inglorious Store.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
+
import { extend } from "@inglorious/utils/data-structures/objects.js"
|
|
9
|
+
|
|
8
10
|
import { handleAsync } from "../async"
|
|
9
11
|
|
|
10
12
|
const SEP_LENGTH = 50
|
|
11
13
|
const INDENT_SPACES = 2
|
|
12
14
|
const SLICE_PLUS_ACTION = 2
|
|
15
|
+
const THUNK_STAGES = ["pending", "fulfilled", "rejected", "settled"]
|
|
13
16
|
|
|
14
17
|
/**
|
|
15
18
|
* Converts an RTK createAsyncThunk to Inglorious handleAsync handlers
|
|
@@ -50,7 +53,7 @@ const SLICE_PLUS_ACTION = 2
|
|
|
50
53
|
*
|
|
51
54
|
* // Use in type
|
|
52
55
|
* const todoList = {
|
|
53
|
-
*
|
|
56
|
+
* create(entity) { entity.todos = []; entity.status = 'idle' },
|
|
54
57
|
* ...todoHandlers
|
|
55
58
|
* }
|
|
56
59
|
*/
|
|
@@ -74,8 +77,16 @@ export function convertAsyncThunk(name, payloadCreator, options = {}) {
|
|
|
74
77
|
|
|
75
78
|
run: async (payload, api) => {
|
|
76
79
|
// RTK payloadCreator signature: (arg, thunkAPI)
|
|
77
|
-
//
|
|
78
|
-
|
|
80
|
+
// Use dispatch for action compatibility, with notify fallback in tests/minimal APIs.
|
|
81
|
+
const dispatch = api.dispatch
|
|
82
|
+
? api.dispatch.bind(api)
|
|
83
|
+
: (action) => {
|
|
84
|
+
if (typeof action === "object" && action !== null) {
|
|
85
|
+
api.notify(action.type, action.payload)
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
return await payloadCreator(payload, { dispatch })
|
|
79
90
|
},
|
|
80
91
|
|
|
81
92
|
success: onFulfilled
|
|
@@ -156,19 +167,88 @@ export function convertAsyncThunk(name, payloadCreator, options = {}) {
|
|
|
156
167
|
* })
|
|
157
168
|
*/
|
|
158
169
|
export function convertSlice(slice, options = {}) {
|
|
159
|
-
const { asyncThunks = {}, extraHandlers = {} } = options
|
|
170
|
+
const { asyncThunks = {}, extraHandlers = {}, extraActions = [] } = options
|
|
160
171
|
|
|
161
172
|
const type = {
|
|
162
|
-
// Convert initialState to
|
|
163
|
-
|
|
173
|
+
// Convert initialState to create handler.
|
|
174
|
+
// Preserve explicitly preloaded fields from entities (common in tests/SSR hydration).
|
|
175
|
+
create(entity) {
|
|
164
176
|
const initialState = slice.getInitialState()
|
|
165
|
-
Object.assign(entity, initialState)
|
|
177
|
+
Object.assign(entity, extend(initialState, entity))
|
|
166
178
|
},
|
|
167
179
|
}
|
|
168
180
|
|
|
169
181
|
// Convert each reducer to an event handler
|
|
170
182
|
for (const [actionName, reducer] of Object.entries(slice.caseReducers)) {
|
|
171
|
-
|
|
183
|
+
const thunkConfig = asyncThunks[actionName]
|
|
184
|
+
const isAsyncThunkReducer =
|
|
185
|
+
reducer &&
|
|
186
|
+
typeof reducer === "object" &&
|
|
187
|
+
THUNK_STAGES.some((stage) => typeof reducer[stage] === "function")
|
|
188
|
+
|
|
189
|
+
if (isAsyncThunkReducer) {
|
|
190
|
+
const pending = toLifecycleHandler(
|
|
191
|
+
slice.name,
|
|
192
|
+
actionName,
|
|
193
|
+
"pending",
|
|
194
|
+
reducer.pending,
|
|
195
|
+
)
|
|
196
|
+
const fulfilled = toLifecycleHandler(
|
|
197
|
+
slice.name,
|
|
198
|
+
actionName,
|
|
199
|
+
"fulfilled",
|
|
200
|
+
reducer.fulfilled,
|
|
201
|
+
)
|
|
202
|
+
const rejected = toLifecycleHandler(
|
|
203
|
+
slice.name,
|
|
204
|
+
actionName,
|
|
205
|
+
"rejected",
|
|
206
|
+
reducer.rejected,
|
|
207
|
+
)
|
|
208
|
+
const settled = toLifecycleHandler(
|
|
209
|
+
slice.name,
|
|
210
|
+
actionName,
|
|
211
|
+
"settled",
|
|
212
|
+
reducer.settled,
|
|
213
|
+
)
|
|
214
|
+
|
|
215
|
+
if (thunkConfig?.payloadCreator) {
|
|
216
|
+
const asyncHandlers = convertAsyncThunk(
|
|
217
|
+
actionName,
|
|
218
|
+
thunkConfig.payloadCreator,
|
|
219
|
+
{
|
|
220
|
+
onPending: thunkConfig.onPending || pending,
|
|
221
|
+
onFulfilled: thunkConfig.onFulfilled || fulfilled,
|
|
222
|
+
onRejected: thunkConfig.onRejected || rejected,
|
|
223
|
+
onSettled: thunkConfig.onSettled || settled,
|
|
224
|
+
scope: thunkConfig.scope,
|
|
225
|
+
},
|
|
226
|
+
)
|
|
227
|
+
|
|
228
|
+
Object.assign(type, asyncHandlers)
|
|
229
|
+
} else {
|
|
230
|
+
if (pending) {
|
|
231
|
+
type[`${actionName}Pending`] = pending
|
|
232
|
+
type[`${slice.name}/${actionName}/pending`] = pending
|
|
233
|
+
}
|
|
234
|
+
if (fulfilled) {
|
|
235
|
+
type[`${actionName}Fulfilled`] = fulfilled
|
|
236
|
+
type[`${slice.name}/${actionName}/fulfilled`] = fulfilled
|
|
237
|
+
}
|
|
238
|
+
if (rejected) {
|
|
239
|
+
type[`${actionName}Rejected`] = rejected
|
|
240
|
+
type[`${slice.name}/${actionName}/rejected`] = rejected
|
|
241
|
+
}
|
|
242
|
+
if (settled) {
|
|
243
|
+
type[`${actionName}Settled`] = settled
|
|
244
|
+
type[`${slice.name}/${actionName}/settled`] = settled
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
continue
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
const handler = (entity, payload) => {
|
|
172
252
|
// Create a mock action object for the RTK reducer
|
|
173
253
|
const action = { type: `${slice.name}/${actionName}`, payload }
|
|
174
254
|
|
|
@@ -177,23 +257,42 @@ export function convertSlice(slice, options = {}) {
|
|
|
177
257
|
// So we can call the reducer directly on the entity
|
|
178
258
|
reducer(entity, action)
|
|
179
259
|
}
|
|
260
|
+
|
|
261
|
+
// Expose both Inglorious-style event names and RTK action-type aliases.
|
|
262
|
+
// This enables direct dispatch of RTK slice actions without extra bridge wiring.
|
|
263
|
+
type[actionName] = handler
|
|
264
|
+
type[`${slice.name}/${actionName}`] = handler
|
|
180
265
|
}
|
|
181
266
|
|
|
182
|
-
// Convert async thunks
|
|
267
|
+
// Convert explicitly configured async thunks that are not embedded in slice reducers.
|
|
183
268
|
for (const [thunkName, thunkHandlers] of Object.entries(asyncThunks)) {
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
269
|
+
if (thunkHandlers?.payloadCreator && !type[thunkName]) {
|
|
270
|
+
const asyncHandlers = convertAsyncThunk(
|
|
271
|
+
thunkName,
|
|
272
|
+
thunkHandlers.payloadCreator,
|
|
273
|
+
{
|
|
274
|
+
onPending: thunkHandlers.onPending,
|
|
275
|
+
onFulfilled: thunkHandlers.onFulfilled,
|
|
276
|
+
onRejected: thunkHandlers.onRejected,
|
|
277
|
+
onSettled: thunkHandlers.onSettled,
|
|
278
|
+
scope: thunkHandlers.scope,
|
|
279
|
+
},
|
|
280
|
+
)
|
|
281
|
+
|
|
282
|
+
Object.assign(type, asyncHandlers)
|
|
283
|
+
}
|
|
284
|
+
}
|
|
195
285
|
|
|
196
|
-
|
|
286
|
+
// Register additional RTK action types (e.g. createAction + extraReducers).
|
|
287
|
+
// RTK does not expose extraReducers through slice.caseReducers, so callers can
|
|
288
|
+
// provide the action creators/types explicitly and we route them through slice.reducer.
|
|
289
|
+
for (const action of extraActions) {
|
|
290
|
+
const actionType = typeof action === "string" ? action : action?.type
|
|
291
|
+
if (typeof actionType !== "string" || type[actionType]) continue
|
|
292
|
+
|
|
293
|
+
type[actionType] = (entity, payload) => {
|
|
294
|
+
applySliceReducer(entity, slice, actionType, payload)
|
|
295
|
+
}
|
|
197
296
|
}
|
|
198
297
|
|
|
199
298
|
// Add any extra handlers
|
|
@@ -324,3 +423,44 @@ export function createRTKCompatDispatch(api, entityId) {
|
|
|
324
423
|
}
|
|
325
424
|
}
|
|
326
425
|
}
|
|
426
|
+
|
|
427
|
+
function createActionObject(sliceName, actionName, stage, payload) {
|
|
428
|
+
const action = {
|
|
429
|
+
type: `${sliceName}/${actionName}/${stage}`,
|
|
430
|
+
payload,
|
|
431
|
+
meta: { arg: payload },
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
if (stage === "pending") {
|
|
435
|
+
action.payload = undefined
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
if (stage === "rejected") {
|
|
439
|
+
action.error = payload
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
return action
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
function toLifecycleHandler(sliceName, actionName, stage, reducer) {
|
|
446
|
+
if (typeof reducer !== "function") return undefined
|
|
447
|
+
|
|
448
|
+
return (entity, payload) => {
|
|
449
|
+
reducer(entity, createActionObject(sliceName, actionName, stage, payload))
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
function applySliceReducer(entity, slice, actionType, payload) {
|
|
454
|
+
const action = { type: actionType, payload }
|
|
455
|
+
const nextState = slice.reducer(toPlainState(entity), action)
|
|
456
|
+
|
|
457
|
+
for (const key of Object.keys(entity)) {
|
|
458
|
+
delete entity[key]
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
Object.assign(entity, nextState)
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
function toPlainState(entity) {
|
|
465
|
+
return JSON.parse(JSON.stringify(entity))
|
|
466
|
+
}
|
package/types/migration/rtk.d.ts
CHANGED
|
@@ -147,6 +147,12 @@ export interface ConvertSliceOptions<TEntity extends BaseEntity = BaseEntity> {
|
|
|
147
147
|
string,
|
|
148
148
|
(entity: TEntity, payload?: any, api?: Api) => void
|
|
149
149
|
>
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Additional RTK actions to map (useful for createAction + extraReducers).
|
|
153
|
+
* Accepts action creators (with a `type` field) or raw action type strings.
|
|
154
|
+
*/
|
|
155
|
+
extraActions?: Array<string | { type: string }>
|
|
150
156
|
}
|
|
151
157
|
|
|
152
158
|
/**
|
|
@@ -156,7 +162,7 @@ export interface InglorisousType<TEntity extends BaseEntity = BaseEntity> {
|
|
|
156
162
|
/**
|
|
157
163
|
* Initialization handler
|
|
158
164
|
*/
|
|
159
|
-
|
|
165
|
+
create?: (entity: TEntity, payload?: any, api?: Api) => void
|
|
160
166
|
|
|
161
167
|
/**
|
|
162
168
|
* Other event handlers
|