@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/state.js
CHANGED
|
@@ -1,9 +1,434 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Default state configuration options
|
|
3
|
+
*/
|
|
4
|
+
const DEFAULT_OPTIONS = {
|
|
5
|
+
includeEntityState: true,
|
|
6
|
+
includePagination: true,
|
|
7
|
+
includeFiltering: true,
|
|
8
|
+
includeSorting: true,
|
|
9
|
+
includeSelection: true,
|
|
10
|
+
includeValidation: false,
|
|
11
|
+
includeMetadata: true,
|
|
12
|
+
customFields: {},
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Standard loading states for async operations
|
|
17
|
+
*/
|
|
18
|
+
const LOADING_STATES = {
|
|
19
|
+
IDLE: 'idle',
|
|
20
|
+
PENDING: 'pending',
|
|
21
|
+
FULFILLED: 'fulfilled',
|
|
22
|
+
REJECTED: 'rejected',
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Standard sort orders
|
|
27
|
+
*/
|
|
28
|
+
const SORT_ORDERS = {
|
|
29
|
+
ASC: 'asc',
|
|
30
|
+
DESC: 'desc',
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Validates the namespace parameter
|
|
35
|
+
* @param {string} namespace - The namespace for the state
|
|
36
|
+
* @throws {Error} If namespace is invalid
|
|
37
|
+
*/
|
|
38
|
+
const validateNamespace = (namespace) => {
|
|
39
|
+
if (!namespace || typeof namespace !== 'string') {
|
|
40
|
+
throw new Error('Namespace must be a non-empty string');
|
|
41
|
+
}
|
|
42
|
+
if (!/^[a-zA-Z][a-zA-Z0-9_]*$/.test(namespace)) {
|
|
43
|
+
throw new Error('Namespace must be a valid identifier (letters, numbers, underscore, starting with letter)');
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Creates pagination state
|
|
49
|
+
* @returns {Object} Pagination state object
|
|
50
|
+
*/
|
|
51
|
+
const createPaginationState = () => ({
|
|
52
|
+
currentPage: 1,
|
|
53
|
+
pageSize: 10,
|
|
54
|
+
totalItems: 0,
|
|
55
|
+
totalPages: 0,
|
|
56
|
+
hasNextPage: false,
|
|
57
|
+
hasPreviousPage: false,
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Creates filtering state
|
|
62
|
+
* @returns {Object} Filtering state object
|
|
63
|
+
*/
|
|
64
|
+
const createFilteringState = () => ({
|
|
65
|
+
filters: {},
|
|
66
|
+
activeFilters: [],
|
|
67
|
+
searchTerm: '',
|
|
68
|
+
searchFields: [],
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Creates sorting state
|
|
73
|
+
* @returns {Object} Sorting state object
|
|
74
|
+
*/
|
|
75
|
+
const createSortingState = () => ({
|
|
76
|
+
sortBy: null,
|
|
77
|
+
sortOrder: SORT_ORDERS.ASC,
|
|
78
|
+
multiSort: [],
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Creates selection state
|
|
83
|
+
* @returns {Object} Selection state object
|
|
84
|
+
*/
|
|
85
|
+
const createSelectionState = () => ({
|
|
86
|
+
selectedItems: [],
|
|
87
|
+
selectedIds: [],
|
|
88
|
+
allSelected: false,
|
|
89
|
+
selectionMode: 'multiple', // 'single', 'multiple', 'none'
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Creates validation state
|
|
94
|
+
* @returns {Object} Validation state object
|
|
95
|
+
*/
|
|
96
|
+
const createValidationState = () => ({
|
|
97
|
+
validationErrors: {},
|
|
98
|
+
isValid: true,
|
|
99
|
+
fieldErrors: {},
|
|
100
|
+
touched: {},
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Creates metadata state for tracking additional information
|
|
105
|
+
* @returns {Object} Metadata state object
|
|
106
|
+
*/
|
|
107
|
+
const createMetadataState = () => ({
|
|
108
|
+
lastFetch: null,
|
|
109
|
+
lastUpdate: null,
|
|
110
|
+
version: 0,
|
|
111
|
+
source: null,
|
|
112
|
+
cached: false,
|
|
113
|
+
stale: false,
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Creates async operation state for tracking multiple operations
|
|
118
|
+
* @returns {Object} Async operations state
|
|
119
|
+
*/
|
|
120
|
+
const createAsyncOperationsState = () => ({
|
|
121
|
+
operations: {},
|
|
122
|
+
globalLoading: false,
|
|
123
|
+
operationQueue: [],
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Deep merges two objects
|
|
128
|
+
* @param {Object} target - Target object
|
|
129
|
+
* @param {Object} source - Source object
|
|
130
|
+
* @returns {Object} Merged object
|
|
131
|
+
*/
|
|
132
|
+
const deepMerge = (target, source) => {
|
|
133
|
+
if (!source || typeof source !== 'object') return target;
|
|
134
|
+
|
|
135
|
+
const result = { ...target };
|
|
136
|
+
|
|
137
|
+
Object.keys(source).forEach(key => {
|
|
138
|
+
if (source[key] && typeof source[key] === 'object' && !Array.isArray(source[key])) {
|
|
139
|
+
result[key] = deepMerge(target[key] || {}, source[key]);
|
|
140
|
+
} else {
|
|
141
|
+
result[key] = source[key];
|
|
142
|
+
}
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
return result;
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Creates a comprehensive Redux state structure for an entity
|
|
150
|
+
* @param {string} namespace - The entity namespace/name
|
|
151
|
+
* @param {Object} options - Configuration options for state creation
|
|
152
|
+
* @param {boolean} options.includeEntityState - Include entity-specific state
|
|
153
|
+
* @param {boolean} options.includePagination - Include pagination state
|
|
154
|
+
* @param {boolean} options.includeFiltering - Include filtering state
|
|
155
|
+
* @param {boolean} options.includeSorting - Include sorting state
|
|
156
|
+
* @param {boolean} options.includeSelection - Include selection state
|
|
157
|
+
* @param {boolean} options.includeValidation - Include validation state
|
|
158
|
+
* @param {boolean} options.includeMetadata - Include metadata state
|
|
159
|
+
* @param {Object} options.customFields - Custom fields to add to state
|
|
160
|
+
* @returns {Object} Complete initial state object
|
|
161
|
+
*/
|
|
162
|
+
export const StateFactory = (namespace, options = {}) => {
|
|
163
|
+
validateNamespace(namespace);
|
|
164
|
+
|
|
165
|
+
const config = { ...DEFAULT_OPTIONS, ...options };
|
|
166
|
+
|
|
167
|
+
// Base state - always included
|
|
168
|
+
const baseState = {
|
|
169
|
+
// Core data
|
|
3
170
|
data: [],
|
|
4
171
|
loading: false,
|
|
5
172
|
success: false,
|
|
6
|
-
error: null
|
|
7
|
-
|
|
173
|
+
error: null,
|
|
174
|
+
|
|
175
|
+
// Enhanced loading states
|
|
176
|
+
loadingState: LOADING_STATES.IDLE,
|
|
177
|
+
|
|
178
|
+
// Entity-specific state (dynamic key)
|
|
179
|
+
...(config.includeEntityState && {
|
|
180
|
+
[namespace]: null,
|
|
181
|
+
[`${namespace}List`]: [],
|
|
182
|
+
[`current${namespace.charAt(0).toUpperCase() + namespace.slice(1)}`]: null,
|
|
183
|
+
}),
|
|
184
|
+
};
|
|
185
|
+
|
|
186
|
+
// Optional state sections
|
|
187
|
+
const optionalStates = {
|
|
188
|
+
...(config.includePagination && {
|
|
189
|
+
pagination: createPaginationState(),
|
|
190
|
+
}),
|
|
191
|
+
|
|
192
|
+
...(config.includeFiltering && {
|
|
193
|
+
filtering: createFilteringState(),
|
|
194
|
+
}),
|
|
195
|
+
|
|
196
|
+
...(config.includeSorting && {
|
|
197
|
+
sorting: createSortingState(),
|
|
198
|
+
}),
|
|
199
|
+
|
|
200
|
+
...(config.includeSelection && {
|
|
201
|
+
selection: createSelectionState(),
|
|
202
|
+
}),
|
|
203
|
+
|
|
204
|
+
...(config.includeValidation && {
|
|
205
|
+
validation: createValidationState(),
|
|
206
|
+
}),
|
|
207
|
+
|
|
208
|
+
...(config.includeMetadata && {
|
|
209
|
+
metadata: createMetadataState(),
|
|
210
|
+
}),
|
|
211
|
+
|
|
212
|
+
// Async operations tracking
|
|
213
|
+
async: createAsyncOperationsState(),
|
|
214
|
+
};
|
|
215
|
+
|
|
216
|
+
// Merge all states
|
|
217
|
+
const initialState = {
|
|
218
|
+
...baseState,
|
|
219
|
+
...optionalStates,
|
|
220
|
+
...config.customFields,
|
|
221
|
+
};
|
|
222
|
+
|
|
223
|
+
return initialState;
|
|
224
|
+
};
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* Creates a minimal state for simple use cases
|
|
228
|
+
* @param {string} namespace - The entity namespace
|
|
229
|
+
* @returns {Object} Minimal state object
|
|
230
|
+
*/
|
|
231
|
+
StateFactory.minimal = (namespace) => {
|
|
232
|
+
return StateFactory(namespace, {
|
|
233
|
+
includeEntityState: true,
|
|
234
|
+
includePagination: false,
|
|
235
|
+
includeFiltering: false,
|
|
236
|
+
includeSorting: false,
|
|
237
|
+
includeSelection: false,
|
|
238
|
+
includeValidation: false,
|
|
239
|
+
includeMetadata: false,
|
|
240
|
+
});
|
|
241
|
+
};
|
|
242
|
+
|
|
243
|
+
/**
|
|
244
|
+
* Creates state optimized for lists/tables
|
|
245
|
+
* @param {string} namespace - The entity namespace
|
|
246
|
+
* @returns {Object} List-optimized state object
|
|
247
|
+
*/
|
|
248
|
+
StateFactory.forList = (namespace) => {
|
|
249
|
+
return StateFactory(namespace, {
|
|
250
|
+
includeEntityState: true,
|
|
251
|
+
includePagination: true,
|
|
252
|
+
includeFiltering: true,
|
|
253
|
+
includeSorting: true,
|
|
254
|
+
includeSelection: true,
|
|
255
|
+
includeValidation: false,
|
|
256
|
+
includeMetadata: true,
|
|
257
|
+
});
|
|
258
|
+
};
|
|
259
|
+
|
|
260
|
+
/**
|
|
261
|
+
* Creates state optimized for forms
|
|
262
|
+
* @param {string} namespace - The entity namespace
|
|
263
|
+
* @returns {Object} Form-optimized state object
|
|
264
|
+
*/
|
|
265
|
+
StateFactory.forForm = (namespace) => {
|
|
266
|
+
return StateFactory(namespace, {
|
|
267
|
+
includeEntityState: true,
|
|
268
|
+
includePagination: false,
|
|
269
|
+
includeFiltering: false,
|
|
270
|
+
includeSorting: false,
|
|
271
|
+
includeSelection: false,
|
|
272
|
+
includeValidation: true,
|
|
273
|
+
includeMetadata: true,
|
|
274
|
+
customFields: {
|
|
275
|
+
formData: {},
|
|
276
|
+
isDirty: false,
|
|
277
|
+
isSubmitting: false,
|
|
278
|
+
submitCount: 0,
|
|
279
|
+
},
|
|
280
|
+
});
|
|
281
|
+
};
|
|
282
|
+
|
|
283
|
+
/**
|
|
284
|
+
* Creates state for real-time/live data
|
|
285
|
+
* @param {string} namespace - The entity namespace
|
|
286
|
+
* @returns {Object} Real-time optimized state object
|
|
287
|
+
*/
|
|
288
|
+
StateFactory.forRealTime = (namespace) => {
|
|
289
|
+
return StateFactory(namespace, {
|
|
290
|
+
includeEntityState: true,
|
|
291
|
+
includePagination: false,
|
|
292
|
+
includeFiltering: false,
|
|
293
|
+
includeSorting: false,
|
|
294
|
+
includeSelection: false,
|
|
295
|
+
includeValidation: false,
|
|
296
|
+
includeMetadata: true,
|
|
297
|
+
customFields: {
|
|
298
|
+
connected: false,
|
|
299
|
+
connectionStatus: 'disconnected',
|
|
300
|
+
lastHeartbeat: null,
|
|
301
|
+
subscriptions: [],
|
|
302
|
+
liveUpdates: true,
|
|
303
|
+
},
|
|
304
|
+
});
|
|
305
|
+
};
|
|
306
|
+
|
|
307
|
+
/**
|
|
308
|
+
* State factory for normalized entities (works well with RTK's createEntityAdapter)
|
|
309
|
+
* @param {string} namespace - The entity namespace
|
|
310
|
+
* @returns {Object} Normalized state structure
|
|
311
|
+
*/
|
|
312
|
+
StateFactory.normalized = (namespace) => {
|
|
313
|
+
return StateFactory(namespace, {
|
|
314
|
+
includeEntityState: false,
|
|
315
|
+
customFields: {
|
|
316
|
+
ids: [],
|
|
317
|
+
entities: {},
|
|
318
|
+
},
|
|
319
|
+
});
|
|
320
|
+
};
|
|
321
|
+
|
|
322
|
+
/**
|
|
323
|
+
* Creates state with custom async operations tracking
|
|
324
|
+
* @param {string} namespace - The entity namespace
|
|
325
|
+
* @param {string[]} operations - List of operation names to track
|
|
326
|
+
* @returns {Object} State with operation tracking
|
|
327
|
+
*/
|
|
328
|
+
StateFactory.withOperations = (namespace, operations = []) => {
|
|
329
|
+
const operationStates = operations.reduce((acc, op) => {
|
|
330
|
+
acc[`${op}Loading`] = false;
|
|
331
|
+
acc[`${op}Success`] = false;
|
|
332
|
+
acc[`${op}Error`] = null;
|
|
333
|
+
return acc;
|
|
334
|
+
}, {});
|
|
335
|
+
|
|
336
|
+
return StateFactory(namespace, {
|
|
337
|
+
customFields: {
|
|
338
|
+
...operationStates,
|
|
339
|
+
operations: operations.reduce((acc, op) => {
|
|
340
|
+
acc[op] = {
|
|
341
|
+
loading: false,
|
|
342
|
+
success: false,
|
|
343
|
+
error: null,
|
|
344
|
+
lastRun: null,
|
|
345
|
+
};
|
|
346
|
+
return acc;
|
|
347
|
+
}, {}),
|
|
348
|
+
},
|
|
349
|
+
});
|
|
350
|
+
};
|
|
351
|
+
|
|
352
|
+
/**
|
|
353
|
+
* Utility functions for working with state
|
|
354
|
+
*/
|
|
355
|
+
StateFactory.utils = {
|
|
356
|
+
/**
|
|
357
|
+
* Gets loading states enum
|
|
358
|
+
* @returns {Object} Loading states
|
|
359
|
+
*/
|
|
360
|
+
getLoadingStates: () => ({ ...LOADING_STATES }),
|
|
361
|
+
|
|
362
|
+
/**
|
|
363
|
+
* Gets sort orders enum
|
|
364
|
+
* @returns {Object} Sort orders
|
|
365
|
+
*/
|
|
366
|
+
getSortOrders: () => ({ ...SORT_ORDERS }),
|
|
367
|
+
|
|
368
|
+
/**
|
|
369
|
+
* Creates a loading state updater
|
|
370
|
+
* @param {string} operation - Operation name
|
|
371
|
+
* @returns {Object} Loading state updates
|
|
372
|
+
*/
|
|
373
|
+
createLoadingUpdates: (operation = 'default') => ({
|
|
374
|
+
pending: {
|
|
375
|
+
loading: true,
|
|
376
|
+
success: false,
|
|
377
|
+
error: null,
|
|
378
|
+
loadingState: LOADING_STATES.PENDING,
|
|
379
|
+
[`async.operations.${operation}.loading`]: true,
|
|
380
|
+
},
|
|
381
|
+
fulfilled: {
|
|
382
|
+
loading: false,
|
|
383
|
+
success: true,
|
|
384
|
+
error: null,
|
|
385
|
+
loadingState: LOADING_STATES.FULFILLED,
|
|
386
|
+
[`async.operations.${operation}.loading`]: false,
|
|
387
|
+
[`async.operations.${operation}.success`]: true,
|
|
388
|
+
[`async.operations.${operation}.lastRun`]: new Date().toISOString(),
|
|
389
|
+
},
|
|
390
|
+
rejected: (error) => ({
|
|
391
|
+
loading: false,
|
|
392
|
+
success: false,
|
|
393
|
+
error,
|
|
394
|
+
loadingState: LOADING_STATES.REJECTED,
|
|
395
|
+
[`async.operations.${operation}.loading`]: false,
|
|
396
|
+
[`async.operations.${operation}.error`]: error,
|
|
397
|
+
[`async.operations.${operation}.lastRun`]: new Date().toISOString(),
|
|
398
|
+
}),
|
|
399
|
+
}),
|
|
400
|
+
|
|
401
|
+
/**
|
|
402
|
+
* Validates state structure
|
|
403
|
+
* @param {Object} state - State to validate
|
|
404
|
+
* @param {string} namespace - Expected namespace
|
|
405
|
+
* @returns {boolean} Whether state is valid
|
|
406
|
+
*/
|
|
407
|
+
validateState: (state, namespace) => {
|
|
408
|
+
if (!state || typeof state !== 'object') return false;
|
|
409
|
+
if (!Array.isArray(state.data)) return false;
|
|
410
|
+
if (typeof state.loading !== 'boolean') return false;
|
|
411
|
+
if (typeof state.success !== 'boolean') return false;
|
|
412
|
+
return true;
|
|
413
|
+
},
|
|
414
|
+
|
|
415
|
+
/**
|
|
416
|
+
* Deep merges state updates
|
|
417
|
+
* @param {Object} currentState - Current state
|
|
418
|
+
* @param {Object} updates - State updates
|
|
419
|
+
* @returns {Object} Merged state
|
|
420
|
+
*/
|
|
421
|
+
mergeState: deepMerge,
|
|
422
|
+
|
|
423
|
+
/**
|
|
424
|
+
* Resets state to initial values
|
|
425
|
+
* @param {string} namespace - Entity namespace
|
|
426
|
+
* @param {Object} options - State options
|
|
427
|
+
* @returns {Object} Reset state
|
|
428
|
+
*/
|
|
429
|
+
resetState: (namespace, options = {}) => {
|
|
430
|
+
return StateFactory(namespace, options);
|
|
431
|
+
},
|
|
432
|
+
};
|
|
8
433
|
|
|
9
|
-
export default StateFactory;
|
|
434
|
+
export default StateFactory;
|