@feardread/feature-factory 3.0.2 → 4.0.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/dist/bundle.min.js +2 -2
- package/dist/index.esm.js +3 -3
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +3 -3
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/factory/api.js +521 -56
- package/src/factory/cache.js +479 -27
- package/src/factory/index.js +171 -100
- package/src/factory/service.js +412 -18
- package/src/factory/state.js +430 -5
- package/src/factory/thunk.js +264 -38
- package/src/factories/api.js +0 -72
- package/src/factories/api.ts +0 -72
- package/src/factories/cache.js +0 -71
- package/src/factories/cache.ts +0 -71
- package/src/factories/factory.js +0 -149
- package/src/factories/factory.ts +0 -158
- package/src/factories/service.js +0 -28
- package/src/factories/service.ts +0 -28
- package/src/factories/state.js +0 -10
- package/src/factories/state.ts +0 -10
- package/src/factories/thunk.js +0 -53
- package/src/factories/thunk.ts +0 -53
- package/src/index.ts +0 -7
- package/tsconfig.json +0 -11
package/src/factory/index.js
CHANGED
|
@@ -1,115 +1,186 @@
|
|
|
1
1
|
import { createSlice, createEntityAdapter, combineReducers } from '@reduxjs/toolkit';
|
|
2
2
|
import ThunkFactory from './thunk';
|
|
3
|
-
import StateFactory from
|
|
4
|
-
//import ApiFactory from './service';
|
|
5
|
-
|
|
3
|
+
import StateFactory from './state';
|
|
6
4
|
|
|
5
|
+
/**
|
|
6
|
+
* Creates a Redux feature with standardized structure including slice, async actions, and reducers
|
|
7
|
+
* @param {string} entity - The entity name for the feature
|
|
8
|
+
* @param {Object} reducers - Custom reducers to include in the slice
|
|
9
|
+
* @param {Object|null} endpoints - API endpoints (currently unused)
|
|
10
|
+
* @returns {Object} Feature factory instance with methods to create slices and manage reducers
|
|
11
|
+
*/
|
|
7
12
|
export function FeatureFactory(entity, reducers = {}, endpoints = null) {
|
|
13
|
+
if (!entity || typeof entity !== 'string') {
|
|
14
|
+
throw new Error('Entity name must be provided as a string');
|
|
15
|
+
}
|
|
8
16
|
|
|
9
|
-
|
|
10
|
-
entity,
|
|
11
|
-
reducers,
|
|
12
|
-
//api: ApiFactory,
|
|
13
|
-
thunk: ThunkFactory,
|
|
14
|
-
state: StateFactory,
|
|
15
|
-
adapter: createEntityAdapter(),
|
|
16
|
-
|
|
17
|
-
manager: (initialReducers) => {
|
|
18
|
-
const reducers = { ...initialReducers };
|
|
19
|
-
let combinedReducer = combineReducers(reducers);
|
|
17
|
+
const adapter = createEntityAdapter();
|
|
20
18
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
for (var prop in source) {
|
|
38
|
-
if (source.hasOwnProperty(prop)) {
|
|
39
|
-
dest[prop] = source[prop];
|
|
40
|
-
}
|
|
19
|
+
/**
|
|
20
|
+
* Creates a dynamic reducer manager for adding/removing reducers at runtime
|
|
21
|
+
* @param {Object} initialReducers - Initial set of reducers
|
|
22
|
+
* @returns {Object} Reducer manager with methods to manipulate reducers
|
|
23
|
+
*/
|
|
24
|
+
const createReducerManager = (initialReducers = {}) => {
|
|
25
|
+
const reducersMap = { ...initialReducers };
|
|
26
|
+
let combinedReducer = combineReducers(reducersMap);
|
|
27
|
+
|
|
28
|
+
return {
|
|
29
|
+
reduce: (state, action) => combinedReducer(state, action),
|
|
30
|
+
|
|
31
|
+
add: (key, reducer) => {
|
|
32
|
+
if (!key || typeof key !== 'string') {
|
|
33
|
+
console.warn('Invalid reducer key provided');
|
|
34
|
+
return;
|
|
41
35
|
}
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
const { service, initialState } = options;
|
|
46
|
-
const sliceName = _this.entity;
|
|
47
|
-
const standard = {
|
|
48
|
-
fetch: _this.thunk.create(sliceName, 'all'),
|
|
49
|
-
fetchOne: _this.thunk.create(sliceName, 'one'),
|
|
50
|
-
search: _this.thunk.create(sliceName, 'search')
|
|
36
|
+
if (reducersMap[key]) {
|
|
37
|
+
console.warn(`Reducer with key '${key}' already exists`);
|
|
38
|
+
return;
|
|
51
39
|
}
|
|
40
|
+
|
|
41
|
+
reducersMap[key] = reducer;
|
|
42
|
+
combinedReducer = combineReducers(reducersMap);
|
|
43
|
+
},
|
|
44
|
+
|
|
45
|
+
remove: (key) => {
|
|
46
|
+
if (!key || !reducersMap[key]) {
|
|
47
|
+
console.warn(`Reducer with key '${key}' does not exist`);
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
delete reducersMap[key];
|
|
52
|
+
combinedReducer = combineReducers(reducersMap);
|
|
53
|
+
},
|
|
54
|
+
|
|
55
|
+
getReducerMap: () => ({ ...reducersMap }),
|
|
56
|
+
};
|
|
57
|
+
};
|
|
52
58
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
})
|
|
64
|
-
.addCase(standard[key].fulfilled, (state, action) => {
|
|
65
|
-
state.loading = false;
|
|
66
|
-
state.success = true;
|
|
67
|
-
state.data = action.payload;
|
|
68
|
-
state[sliceName] = action.payload[0];
|
|
69
|
-
if (key == 'fetchOne') {
|
|
70
|
-
state[sliceName] = action.payload[0];
|
|
71
|
-
}
|
|
72
|
-
})
|
|
73
|
-
.addCase(standard[key].rejected, (state, action) => {
|
|
74
|
-
state.loading = false;
|
|
75
|
-
state.success = false;
|
|
76
|
-
state.error = action.error;
|
|
77
|
-
});
|
|
78
|
-
})
|
|
79
|
-
if (service) {
|
|
80
|
-
Object.keys(service).forEach(key => {
|
|
81
|
-
if (service[key] != standard[key]) {
|
|
82
|
-
builder
|
|
83
|
-
.addCase(service[key].pending, (state) => {
|
|
84
|
-
state.loading = true;
|
|
85
|
-
state.error = null;
|
|
86
|
-
})
|
|
87
|
-
.addCase(service[key].fulfilled, (state, action) => {
|
|
88
|
-
state.loading = false;
|
|
89
|
-
state.success = true;
|
|
90
|
-
state.data = action.payload;
|
|
91
|
-
state[sliceName] = action.payload[0]
|
|
92
|
-
})
|
|
93
|
-
.addCase(service[key].rejected, (state, action) => {
|
|
94
|
-
state.loading = false;
|
|
95
|
-
state.success = false;
|
|
96
|
-
state.error = action.payload;
|
|
97
|
-
});
|
|
98
|
-
}
|
|
99
|
-
})
|
|
100
|
-
}}
|
|
101
|
-
}
|
|
102
|
-
)
|
|
59
|
+
/**
|
|
60
|
+
* Merges properties from source object into destination object
|
|
61
|
+
* @param {Object} source - Source object
|
|
62
|
+
* @param {Object} destination - Destination object
|
|
63
|
+
* @returns {Object} Merged destination object
|
|
64
|
+
*/
|
|
65
|
+
const mergeObjects = (source, destination) => {
|
|
66
|
+
if (!source || !destination) {
|
|
67
|
+
throw new Error('Both source and destination objects must be provided');
|
|
68
|
+
}
|
|
103
69
|
|
|
104
|
-
|
|
70
|
+
return Object.keys(source).reduce((acc, key) => {
|
|
71
|
+
if (Object.prototype.hasOwnProperty.call(source, key)) {
|
|
72
|
+
acc[key] = source[key];
|
|
73
|
+
}
|
|
74
|
+
return acc;
|
|
75
|
+
}, { ...destination });
|
|
76
|
+
};
|
|
105
77
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
78
|
+
/**
|
|
79
|
+
* Creates standard async thunks for common operations
|
|
80
|
+
* @param {string} sliceName - Name of the slice
|
|
81
|
+
* @returns {Object} Standard async thunks
|
|
82
|
+
*/
|
|
83
|
+
const createStandardThunks = (sliceName) => ({
|
|
84
|
+
fetch: ThunkFactory.create(sliceName, 'all'),
|
|
85
|
+
fetchOne: ThunkFactory.create(sliceName, 'one'),
|
|
86
|
+
search: ThunkFactory.create(sliceName, 'search'),
|
|
87
|
+
});
|
|
111
88
|
|
|
112
|
-
|
|
89
|
+
/**
|
|
90
|
+
* Adds standard async action handlers to builder
|
|
91
|
+
* @param {Object} builder - RTK builder object
|
|
92
|
+
* @param {Object} asyncActions - Async actions to handle
|
|
93
|
+
* @param {string} sliceName - Name of the slice
|
|
94
|
+
*/
|
|
95
|
+
const addAsyncActionHandlers = (builder, asyncActions, sliceName) => {
|
|
96
|
+
Object.entries(asyncActions).forEach(([key, action]) => {
|
|
97
|
+
builder
|
|
98
|
+
.addCase(action.pending, (state) => {
|
|
99
|
+
state.loading = true;
|
|
100
|
+
state.error = null;
|
|
101
|
+
})
|
|
102
|
+
.addCase(action.fulfilled, (state, actionPayload) => {
|
|
103
|
+
state.loading = false;
|
|
104
|
+
state.success = true;
|
|
105
|
+
state.data = actionPayload.payload;
|
|
106
|
+
|
|
107
|
+
// Handle single item for fetchOne operation
|
|
108
|
+
if (key === 'fetchOne' && Array.isArray(actionPayload.payload)) {
|
|
109
|
+
state[sliceName] = actionPayload.payload[0];
|
|
110
|
+
} else if (Array.isArray(actionPayload.payload) && actionPayload.payload.length > 0) {
|
|
111
|
+
state[sliceName] = actionPayload.payload[0];
|
|
112
|
+
}
|
|
113
|
+
})
|
|
114
|
+
.addCase(action.rejected, (state, actionPayload) => {
|
|
115
|
+
state.loading = false;
|
|
116
|
+
state.success = false;
|
|
117
|
+
state.error = actionPayload.error || actionPayload.payload;
|
|
118
|
+
});
|
|
119
|
+
});
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Creates a Redux slice with standard and custom async actions
|
|
124
|
+
* @param {Object} options - Configuration options
|
|
125
|
+
* @param {Object} options.service - Custom service actions
|
|
126
|
+
* @param {Object} options.initialState - Custom initial state
|
|
127
|
+
* @returns {Object} Created slice and async actions
|
|
128
|
+
*/
|
|
129
|
+
const createFactorySlice = (options = {}) => {
|
|
130
|
+
const { service = null, initialState = null } = options;
|
|
131
|
+
const sliceName = entity;
|
|
132
|
+
|
|
133
|
+
// Create standard thunks
|
|
134
|
+
const standardThunks = createStandardThunks(sliceName);
|
|
135
|
+
|
|
136
|
+
// Create the slice
|
|
137
|
+
const factorySlice = createSlice({
|
|
138
|
+
name: sliceName,
|
|
139
|
+
initialState: initialState || StateFactory(sliceName),
|
|
140
|
+
reducers,
|
|
141
|
+
extraReducers: (builder) => {
|
|
142
|
+
// Add standard async action handlers
|
|
143
|
+
addAsyncActionHandlers(builder, standardThunks, sliceName);
|
|
144
|
+
|
|
145
|
+
// Add custom service action handlers
|
|
146
|
+
if (service && typeof service === 'object') {
|
|
147
|
+
const customActions = Object.entries(service).reduce((acc, [key, action]) => {
|
|
148
|
+
// Only add if it's not already in standard thunks
|
|
149
|
+
if (!standardThunks[key]) {
|
|
150
|
+
acc[key] = action;
|
|
151
|
+
}
|
|
152
|
+
return acc;
|
|
153
|
+
}, {});
|
|
154
|
+
|
|
155
|
+
addAsyncActionHandlers(builder, customActions, sliceName);
|
|
156
|
+
}
|
|
157
|
+
},
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
// Merge standard and custom async actions
|
|
161
|
+
const asyncActions = service
|
|
162
|
+
? mergeObjects(standardThunks, service)
|
|
163
|
+
: standardThunks;
|
|
164
|
+
|
|
165
|
+
return {
|
|
166
|
+
slice: factorySlice,
|
|
167
|
+
asyncActions,
|
|
168
|
+
};
|
|
169
|
+
};
|
|
170
|
+
|
|
171
|
+
// Public API
|
|
172
|
+
return {
|
|
173
|
+
entity,
|
|
174
|
+
reducers,
|
|
175
|
+
adapter,
|
|
176
|
+
manager: createReducerManager,
|
|
177
|
+
inject: mergeObjects,
|
|
178
|
+
create: createFactorySlice,
|
|
179
|
+
|
|
180
|
+
// Utility methods
|
|
181
|
+
getEntityName: () => entity,
|
|
182
|
+
getAdapter: () => adapter,
|
|
183
|
+
};
|
|
113
184
|
}
|
|
114
185
|
|
|
115
186
|
export default FeatureFactory;
|