@recats/cdeebee 2.3.7 → 3.0.0-beta.11

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
@@ -1,204 +1,701 @@
1
1
  # cdeebee
2
2
 
3
- [![npm](https://img.shields.io/npm/v/@recats/cdeebee.svg)](https://www.npmjs.com/package/@recats/cdeebee)
4
- [![Greenkeeper badge](https://badges.greenkeeper.io/recats/cdeebee.svg)](https://greenkeeper.io/)
5
- [![Travis badge](https://travis-ci.com/recats/cdeebee.svg?branch=master)](https://travis-ci.com/recats/cdeebee)
6
- ![Recats Digital](https://img.shields.io/badge/recats-digital-1abc9c.svg?style=flat)
3
+ A Redux-based data management library that provides a uniform way to access, fetch, update, and manage application data with minimal boilerplate code.
4
+
5
+ ## Installation
7
6
 
8
7
  ```sh
9
- npm i @recats/cdeebee
8
+ npm i @recats/cdeebee@beta
9
+ # or
10
+ yarn add @recats/cdeebee@beta
10
11
  # or
11
- yarn add @recats/cdeebee
12
+ pnpm add @recats/cdeebee@beta
12
13
  ```
13
- # cdeebee
14
- cdeebee is an alpha version of library which goals are:
15
- - Provide uniform way of access to data
16
- - Decrease boilerplate code amount when working with data fetching, updating, committing and rollbacking
17
14
 
18
- The library is highly linked with Redux library and uses Redux as an internal storage
15
+ ## What is cdeebee?
16
+
17
+ cdeebee is a data management library built on top of Redux Toolkit that aims to:
18
+ - Provide a uniform way of accessing data
19
+ - Decrease boilerplate code when working with data fetching, updating, committing, and rollbacking
20
+ - Manage normalized data structures similar to relational databases
21
+ - Handle API requests with built-in normalization, error handling, and request cancellation
22
+
23
+ ## Core Concepts
24
+
25
+ ### Normalized Storage
19
26
 
20
- cdeebee works like traditional relational database and stores data lists in JS object (*list*) with keys as table names
27
+ cdeebee stores data in a normalized structure similar to a relational database. Data is organized into **lists** (tables) where each list is a JavaScript object with keys representing primary keys (IDs) of entities.
21
28
 
22
- For instance simple example how to structure forum case:
23
- ```js
29
+ For example, a forum application might have this structure:
30
+
31
+ ```typescript
24
32
  {
25
- forumList: { },
26
- threadList: { },
27
- postList: { }
33
+ forumList: {},
34
+ threadList: {},
35
+ postList: {}
28
36
  }
29
37
  ```
30
38
 
31
- Each *list* is JS object with keys ~~~ primary keys (ID) of objects
32
- For instance for the sample above the whole cdeebee inside Redux will look like for the forum devoted to the discussion about Universe
33
- ```js
39
+ After fetching data from the API (which returns data in the format `{ data: [...], primaryKey: 'id' }`), cdeebee automatically normalizes it and the storage might look like:
40
+
41
+ ```typescript
34
42
  {
35
- forumList: { 1: { title: ‘Milky Way Galaxy’ } },
36
- threadList: { 10001: { title: ‘Solar system’, forumID: 1 } },
37
- postList: { 2: { title: ‘Earth’, threadID: 10001 } }
43
+ forumList: {
44
+ 1: { id: 1, title: 'Milky Way Galaxy' }
45
+ },
46
+ threadList: {
47
+ 10001: { id: 10001, title: 'Solar system', forumID: 1 }
48
+ },
49
+ postList: {
50
+ 2: { id: 2, title: 'Earth', threadID: 10001 }
51
+ }
38
52
  }
39
-
40
53
  ```
41
54
 
42
- There are several standard functions to work with this structure:
43
- 1. Edit field set of object
44
- 2. Create new object
45
- 3. Remove object
46
- 4. Commit changes
47
- 5. Revert changes
48
- 6. Set new entity for the key
49
- 7. Atomic update for many lists (this option is used when client receives new data from API)
55
+ **Note:** The API should return list data in the format `{ data: [...], primaryKey: 'fieldName' }`. cdeebee automatically converts this format into the normalized storage structure shown above. See the [API Response Format](#api-response-format) section for details.
50
56
 
51
- In addition it’s highly recommended to adopt API to work with cdeebee. But otherwise you could use normalizr before using cdeebee to change your data structure from tree-like JSON to normalised lists
57
+ ### Modules
52
58
 
53
- Finally there is a set of tools to work with API:
54
- - CdeebeeRequest function with is making request to server and manage queue with active requests
55
- - data normalisation and updating in cdeebee
56
- - necessary set of callbacks (preUpdate, preSuccess, postSuccess, preError)
59
+ cdeebee uses a modular architecture with the following modules:
57
60
 
58
- ## Install
59
- ```js
60
- // reducer/index.js
61
- import { cdeebee, requestManager } from '@recats/cdeebee';
61
+ - **`storage`**: Automatically normalizes and stores API responses
62
+ - **`history`**: Tracks request history (successful and failed requests)
63
+ - **`listener`**: Tracks active requests for loading states
64
+ - **`cancelation`**: Manages request cancellation (automatically cancels previous requests to the same API)
65
+ - **`queryQueue`**: Processes requests sequentially in the order they were sent, ensuring they complete and are stored in the correct sequence
62
66
 
63
- export default combineReducers({
64
- cdeebeee,
65
- requestManager, // optional (checkNetworkActivity, cancelationRequest)
66
- })
67
+ ## Quick Start
67
68
 
69
+ ### 1. Setup Redux Store
68
70
 
69
- // Usage
70
- // actions/*.js
71
- import { CdeebeeRequest, cdeebeeMergeStrategy } from '@recats/cdeebee';
71
+ ```typescript
72
+ import { configureStore, combineSlices } from '@reduxjs/toolkit';
73
+ import { factory } from '@recats/cdeebee';
72
74
 
73
- const request = new CdeebeeRequest(
75
+ // Define your storage structure
76
+ interface Storage {
77
+ forumList: Record<number, { id: number; title: string }>;
78
+ threadList: Record<number, { id: number; title: string; forumID: number }>;
79
+ postList: Record<number, { id: number; title: string; threadID: number }>;
80
+ }
81
+
82
+ // Create cdeebee slice
83
+ export const cdeebeeSlice = factory<Storage>(
74
84
  {
75
- // defaultRequest data
76
- data: { sessionToken: 'cdeebee master' },
85
+ modules: ['history', 'listener', 'cancelation', 'storage', 'queryQueue'],
86
+ fileKey: 'file',
87
+ bodyKey: 'value',
88
+ listStrategy: {
89
+ forumList: 'merge',
90
+ threadList: 'replace',
91
+ },
92
+ mergeWithData: {
93
+ sessionToken: 'your-session-token',
94
+ },
95
+ mergeWithHeaders: {
96
+ 'Authorization': 'Bearer token',
97
+ },
77
98
  },
78
99
  {
79
- // option params
80
- globalErrorHandler: (error, request, meta) => (dispatch, getState) => void,
81
- // default request options
82
- fileKey: 'files',
83
- bodyKey: 'body',
84
- method: 'POST',
85
- primaryKey: 'primaryKey',
86
- normalize: defaultNormalize,
87
- responseKeyCode: 'responseStatus',
88
- header: { 'content-type': 'application/json' },
100
+ // Optional initial storage state
101
+ forumList: {},
102
+ threadList: {},
103
+ postList: {},
89
104
  }
90
- ).send;
91
-
92
- export function requestServer(fn: () => void) {
93
- return (dispatch: Function, getState: Function) => (
94
- request({
95
- api: apiUrl.requestCdeebeee,
96
-
97
- data?: { cdeebee: 'cool' },
98
- files?: File,
99
-
100
- cdeebeeMergeStrategy?: { [listName]: cdeebeeMergeStrategy // default cdeebeeMergeStrategy.merge },
101
- requestStrategy?: $Keys<typeof cdeebeeRequestStrategy> # default undefined,
102
- primaryKey?: string // default 'primaryKey',
103
-
104
- method?: 'POST' | 'GET' | 'PUT' | 'DELETE',
105
- headers?: object,
106
- responseCode?: string,
107
- requestCancel?: boolean, // default true
108
- updateStore?: boolean, // default true
109
- normalize?: ({ response, cdeebee, mergeStrategy }) => Object,
110
-
111
- // files
112
- bodyKey: 'body',
113
- fileKey: 'file',
114
-
115
- // key
116
- responseKeyCode: 'responseStatus',
117
-
118
- preUpdate?: (payload: object) => void,
119
- postUpdate?: (payload: object) => void,
120
- preError?: (payload: object) => void,
121
- postError?: (payload: object) => void,
122
- })(dispatch, getState)
105
+ );
106
+
107
+ // Combine with other reducers
108
+ const rootReducer = combineSlices(cdeebeeSlice);
109
+
110
+ export const store = configureStore({
111
+ reducer: rootReducer,
112
+ });
113
+ ```
114
+
115
+ ### 2. Make API Requests
116
+
117
+ ```typescript
118
+ import { request } from '@recats/cdeebee';
119
+ import { useAppDispatch } from './hooks';
120
+
121
+ function MyComponent() {
122
+ const dispatch = useAppDispatch();
123
+
124
+ const fetchForums = () => {
125
+ dispatch(request({
126
+ api: '/api/forums',
127
+ method: 'POST',
128
+ body: { filter: 'active' },
129
+ onResult: (result) => {
130
+ // onResult is always called with the response data
131
+ // For JSON responses, result is already parsed
132
+ console.log('Request completed:', result);
133
+ },
134
+ }));
135
+ };
136
+
137
+ return <button onClick={fetchForums}>Load Forums</button>;
138
+ }
139
+ ```
140
+
141
+ ### 3. Access Data and Loading States with Hooks
142
+
143
+ cdeebee provides React hooks to easily access data and track loading states:
144
+
145
+ ```typescript
146
+ import { useLoading, useStorageList } from '@recats/cdeebee';
147
+
148
+ function ForumsList() {
149
+ // Check if specific APIs are loading
150
+ const isLoading = useLoading(['/api/forums']);
151
+
152
+ // Get data from storage with full type safety
153
+ const forums = useStorageList<Storage, 'forumList'>('forumList');
154
+
155
+ return (
156
+ <div>
157
+ {isLoading && <p>Loading...</p>}
158
+ {Object.values(forums).map(forum => (
159
+ <div key={forum.id}>{forum.title}</div>
160
+ ))}
161
+ </div>
123
162
  );
124
163
  }
125
164
  ```
126
165
 
127
- ## Methods
128
- ```js
129
- cdeebee
130
- cdeebeeHelpers
131
- requestManager
132
- CdeebeeRequest // class
133
- cdeebeeTypes
134
- cdeebeeEntityState
135
- cdeebeeMergeStrategy // merge storage strategy
136
- cdeebeeValueList
137
- cdeebeeActions
138
- cdeebeActiveRequest
139
- cdeebeeIActions
166
+ **Available hooks:**
167
+
168
+ - `useLoading(apiList: string[])` - Check if any of the specified APIs are loading
169
+ - `useIsLoading()` - Check if any request is loading
170
+ - `useStorageList(listName)` - Get a specific list from storage
171
+ - `useStorage()` - Get the entire storage
172
+ - `useRequestHistory(api)` - Get successful request history for an API
173
+ - `useRequestErrors(api)` - Get error history for an API
174
+
175
+ See the [React Hooks](#react-hooks) section for detailed documentation.
176
+
177
+ ## Configuration
178
+
179
+ ### Settings
180
+
181
+ The `factory` function accepts a settings object with the following options:
182
+
183
+ ```typescript
184
+ interface CdeebeeSettings<T> {
185
+ modules: CdeebeeModule[]; // Active modules: 'history' | 'listener' | 'storage' | 'cancelation' | 'queryQueue'
186
+ fileKey: string; // Key name for file uploads in FormData
187
+ bodyKey: string; // Key name for request body in FormData
188
+ listStrategy?: CdeebeeListStrategy<T>; // Merge strategy per list: 'merge' | 'replace' | 'skip'
189
+ mergeWithData?: unknown; // Data to merge with every request body
190
+ mergeWithHeaders?: Record<string, string>; // Headers to merge with every request
191
+ normalize?: (storage, result, strategyList) => T; // Custom normalization function
192
+ }
193
+ ```
194
+
195
+ ### Request Options
196
+
197
+ ```typescript
198
+ interface CdeebeeRequestOptions<T> {
199
+ api: string; // API endpoint URL
200
+ method?: 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH';
201
+ body?: unknown; // Request body
202
+ headers?: Record<string, string>; // Additional headers (merged with mergeWithHeaders)
203
+ files?: File[]; // Files to upload
204
+ fileKey?: string; // Override default fileKey
205
+ bodyKey?: string; // Override default bodyKey
206
+ listStrategy?: CdeebeeListStrategy<T>; // Override list strategy for this request
207
+ normalize?: (storage, result, strategyList) => T; // Override normalization
208
+ onResult?: (response: T) => void; // Callback called with response data (always called, even on errors)
209
+ ignore?: boolean; // Skip storing result in storage
210
+ responseType?: 'json' | 'text' | 'blob'; // Response parsing type (default: 'json')
211
+ historyClear?: boolean; // Auto-clear history for this API before making the request
212
+ }
213
+ ```
214
+
215
+ ## Data Merging Strategies
216
+
217
+ cdeebee supports three strategies for merging data:
218
+
219
+ - **`merge`**: Merges new data with existing data, preserving existing keys not in the response
220
+ - **`replace`**: Completely replaces the list with new data
221
+ - **`skip`**: Skips updating the list entirely, preserving existing data unchanged
222
+
223
+ ```typescript
224
+ listStrategy: {
225
+ forumList: 'merge', // New forums are merged with existing ones
226
+ threadList: 'replace', // Thread list is completely replaced
227
+ userList: 'skip', // User list is never updated, existing data is preserved
228
+ }
229
+ ```
230
+
231
+ ## API Response Format
232
+
233
+ cdeebee expects API responses in a format where list data is provided as arrays with a `primaryKey` field. The library automatically normalizes this data into the storage structure.
234
+
235
+ ### List Format
236
+
237
+ For lists (collections of entities), the API should return data in the following format:
238
+
239
+ ```typescript
240
+ {
241
+ forumList: {
242
+ data: [
243
+ { id: 1, title: 'Forum 1' },
244
+ { id: 2, title: 'Forum 2' },
245
+ ],
246
+ primaryKey: 'id',
247
+ },
248
+ threadList: {
249
+ data: [
250
+ { id: 101, title: 'Thread 1', forumID: 1 },
251
+ { id: 102, title: 'Thread 2', forumID: 1 },
252
+ ],
253
+ primaryKey: 'id',
254
+ }
255
+ }
256
+ ```
257
+
258
+ cdeebee automatically converts this format into normalized storage:
259
+
260
+ ```typescript
261
+ {
262
+ forumList: {
263
+ 1: { id: 1, title: 'Forum 1' },
264
+ 2: { id: 2, title: 'Forum 2' },
265
+ },
266
+ threadList: {
267
+ 101: { id: 101, title: 'Thread 1', forumID: 1 },
268
+ 102: { id: 102, title: 'Thread 2', forumID: 1 },
269
+ }
270
+ }
140
271
  ```
141
272
 
273
+ The `primaryKey` field specifies which property of each item should be used as the key in the normalized structure. The `primaryKey` value is converted to a string to ensure consistent key types.
142
274
 
143
- ## Actions
144
- ```js
145
- // setKeyValue
146
- import { cdeebeeActions } form '@recats/cdeebee';
275
+ **Example:**
147
276
 
148
- this.props.cdeebeeActions.dropRequestByApiUrl: (api: string);
149
- this.props.cdeebeeActions.dropErrorsByApiUrl: (api: string);
150
- this.props.cdeebeeActions.dropCdeebeePath(path: (string | number)[]);
277
+ If your API returns:
278
+ ```typescript
279
+ {
280
+ sessionList: {
281
+ data: [
282
+ {
283
+ sessionID: 1,
284
+ token: 'da6ec385bc7e4f84a510c3ecca07f3',
285
+ expiresAt: '2034-03-28T22:36:09'
286
+ }
287
+ ],
288
+ primaryKey: 'sessionID',
289
+ }
290
+ }
291
+ ```
151
292
 
152
- // these method are not recommended for usage because is not safe
153
- this.props.cdeebeeActions.unsafe_updateStore(
154
- entityList: string,
155
- entityID: EntityID,
156
- value: any, // any value
157
- )
293
+ It will be automatically normalized to:
294
+ ```typescript
295
+ {
296
+ sessionList: {
297
+ '1': {
298
+ sessionID: 1,
299
+ token: 'da6ec385bc7e4f84a510c3ecca07f3',
300
+ expiresAt: '2034-03-28T22:36:09'
301
+ }
302
+ }
303
+ }
304
+ ```
158
305
 
159
- this.props.cdeebeeActions.setKeyValue(
160
- entityList: string,
161
- entityID: EntityID,
162
- valueList: cdeebeeValueList,
163
- )
306
+ ### Non-List Data
164
307
 
165
- this.props.cdeebeeActions.commitEntity(
166
- entityList: string,
167
- entityID: EntityID,
168
- entity: object,
169
- )
308
+ For non-list data (configuration objects, simple values, etc.), you can return them as regular objects:
170
309
 
171
- this.props.cdeebeeActions.resetEntity(
172
- entityList: string,
173
- entityID: EntityID,
174
- )
310
+ ```typescript
311
+ {
312
+ config: {
313
+ theme: 'dark',
314
+ language: 'en',
315
+ },
316
+ userPreferences: {
317
+ notifications: true,
318
+ }
319
+ }
175
320
  ```
176
321
 
177
- ## Helpers
178
- ```js
179
- import { cdeebeeHelpers } from '@recats/cdeebee';
322
+ These will be stored as-is in the storage.
180
323
 
181
- // requestCancel
182
- cdeebeeHelpers.requestCancel(activeRequest: cdeebeActiveRequest) => void;
324
+ ## Advanced Usage
183
325
 
184
- // checkNetworkActivity
185
- cdeebeeHelpers.checkNetworkActivity(activeRequest: cdeebeActiveRequest, apiUrl: string | Array<string>) => boolean;
326
+ ### File Uploads
186
327
 
187
- // getSubEntity element in cdeebee list
188
- cdeebeeHelpers.getSubEntity(entity: object) => object;
328
+ ```typescript
329
+ const file = new File(['content'], 'document.pdf', { type: 'application/pdf' });
189
330
 
190
- // getEntityState element in cdeebee list
191
- cdeebeeHelpers.getEntityState(entity: object) => string;
331
+ dispatch(request({
332
+ api: '/api/upload',
333
+ method: 'POST',
334
+ files: [file],
335
+ body: { description: 'My document' },
336
+ fileKey: 'file', // Optional: override default
337
+ bodyKey: 'metadata', // Optional: override default
338
+ }));
339
+ ```
340
+
341
+ ### Handling Different Response Types
342
+
343
+ By default, cdeebee parses responses as JSON. For other response types (CSV, text files, images, etc.), use the `responseType` option:
344
+
345
+ ```typescript
346
+ // CSV/text response
347
+ dispatch(request({
348
+ api: '/api/export',
349
+ responseType: 'text',
350
+ ignore: true, // Don't store in storage
351
+ onResult: (csvData) => {
352
+ // csvData is a string
353
+ downloadCSV(csvData);
354
+ },
355
+ }));
356
+
357
+ // Binary file (image, PDF, etc.)
358
+ dispatch(request({
359
+ api: '/api/image/123',
360
+ responseType: 'blob',
361
+ ignore: true,
362
+ onResult: (blob) => {
363
+ // blob is a Blob object
364
+ const url = URL.createObjectURL(blob);
365
+ setImageUrl(url);
366
+ },
367
+ }));
368
+
369
+ // JSON (default)
370
+ dispatch(request({
371
+ api: '/api/data',
372
+ // responseType: 'json' is default
373
+ onResult: (data) => {
374
+ console.log(data); // Already parsed JSON
375
+ },
376
+ }));
377
+ ```
378
+
379
+ ### Ignoring Storage Updates
192
380
 
193
- // commitEntity element in cdeebee list
194
- cdeebeeHelpers.commitEntity(entity: object) => void;
381
+ Use the `ignore` option to prevent storing the response in storage while still receiving it in the `onResult` callback:
195
382
 
196
- // resetEntity element in cdeebee list
197
- cdeebeeHelpers.resetEntity(entity: object) => void;
383
+ ```typescript
384
+ // Export CSV without storing in storage
385
+ dispatch(request({
386
+ api: '/api/export',
387
+ responseType: 'text',
388
+ ignore: true,
389
+ onResult: (csvData) => {
390
+ // Handle CSV data directly
391
+ downloadFile(csvData, 'export.csv');
392
+ },
393
+ }));
198
394
  ```
199
395
 
200
- ## Data merging behavior
201
- During data merging cdeebee could behave in different ways according to the enum value which is passed during request
396
+ ### Custom Headers
397
+
398
+ ```typescript
399
+ // Global headers (in settings)
400
+ mergeWithHeaders: {
401
+ 'Authorization': 'Bearer token',
402
+ 'X-Custom-Header': 'value',
403
+ }
404
+
405
+ // Per-request headers (override global)
406
+ dispatch(request({
407
+ api: '/api/data',
408
+ headers: {
409
+ 'Authorization': 'Bearer different-token', // Overrides global
410
+ 'X-Request-ID': '123', // Additional header
411
+ },
412
+ }));
413
+ ```
414
+
415
+ ### Request Cancellation
416
+
417
+ When the `cancelation` module is enabled, cdeebee automatically cancels previous requests to the same API endpoint when a new request is made:
418
+
419
+ ```typescript
420
+ // First request
421
+ dispatch(request({ api: '/api/data', body: { query: 'slow' } }));
422
+
423
+ // Second request to same API - first one is automatically cancelled
424
+ dispatch(request({ api: '/api/data', body: { query: 'fast' } }));
425
+ ```
426
+
427
+ ### Sequential Request Processing (queryQueue)
428
+
429
+ When the `queryQueue` module is enabled, all requests are processed sequentially in the order they were sent. This ensures that:
430
+
431
+ - Requests complete in the exact order they were dispatched
432
+ - Data is stored in the store in the correct sequence
433
+ - Even if a faster request is sent after a slower one, it will wait for the previous request to complete
434
+
435
+ This is particularly useful when you need to maintain data consistency and ensure that updates happen in the correct order.
436
+
437
+ ```typescript
438
+ // Enable queryQueue module
439
+ const cdeebeeSlice = factory<Storage>({
440
+ modules: ['history', 'listener', 'storage', 'queryQueue'],
441
+ // ... other settings
442
+ });
443
+
444
+ // Send multiple requests - they will be processed sequentially
445
+ dispatch(request({ api: '/api/data', body: { id: 1 } })); // Completes first
446
+ dispatch(request({ api: '/api/data', body: { id: 2 } })); // Waits for #1, then completes
447
+ dispatch(request({ api: '/api/data', body: { id: 3 } })); // Waits for #2, then completes
448
+
449
+ // Even if request #3 is faster, it will still complete last
450
+ // All requests are stored in the store in order: 1 → 2 → 3
451
+ ```
452
+
453
+ **Note:** The `queryQueue` module processes requests sequentially across all APIs. If you need parallel processing for different APIs, you would need separate cdeebee instances or disable the module for those specific requests.
454
+
455
+ ### Manual State Updates
456
+
457
+ You can manually update the storage using the `set` action:
458
+
459
+ ```typescript
460
+ import { batchingUpdate } from '@recats/cdeebee';
461
+
462
+ // Update multiple values at once
463
+ const updates = [
464
+ { key: ['forumList', '1', 'title'], value: 'New Title' },
465
+ { key: ['forumList', '1', 'views'], value: 100 },
466
+ { key: ['threadList', '101', 'title'], value: 'Updated Thread' },
467
+ ];
468
+
469
+ dispatch(cdeebeeSlice.actions.set(updates));
470
+ ```
471
+
472
+ ### Accessing Request History
473
+
474
+ You can access request history using hooks or selectors:
475
+
476
+ ```typescript
477
+ import { useRequestHistory, useRequestErrors } from '@recats/cdeebee';
478
+
479
+ // Using hooks (recommended)
480
+ const apiHistory = useRequestHistory('/api/forums');
481
+ const apiErrors = useRequestErrors('/api/forums');
482
+
483
+ // Or using selectors
484
+ const doneRequests = useAppSelector(state => state.cdeebee.request.done);
485
+ const errors = useAppSelector(state => state.cdeebee.request.errors);
486
+ ```
487
+
488
+ ### Clearing Request History
489
+
490
+ Clear old success/error history when needed (useful for forms that get reopened):
491
+
492
+ ```typescript
493
+ // Automatic: clear before request
494
+ dispatch(request({
495
+ api: '/api/posts',
496
+ historyClear: true, // Clears old history for this API
497
+ body: formData,
498
+ }));
499
+
500
+ // Manual: clear anytime
501
+ dispatch(cdeebeeSlice.actions.historyClear('/api/posts')); // Specific API
502
+ dispatch(cdeebeeSlice.actions.historyClear()); // All APIs
503
+ ```
504
+
505
+ ## React Hooks
506
+
507
+ cdeebee provides a comprehensive set of React hooks for accessing state without writing selectors. These hooks assume your cdeebee slice is at `state.cdeebee` (which is the default when using `combineSlices`).
508
+
509
+ ### Loading State Hooks
510
+
511
+ #### `useLoading(apiList: string[])`
512
+
513
+ Check if any of the specified APIs are currently loading.
514
+
515
+ ```typescript
516
+ import { useLoading } from '@recats/cdeebee';
517
+
518
+ function MyComponent() {
519
+ // Check if any of these APIs are loading
520
+ const isLoading = useLoading(['/api/forums', '/api/threads']);
521
+
522
+ if (isLoading) return <Spinner />;
523
+
524
+ return <div>Content</div>;
525
+ }
526
+ ```
527
+
528
+ #### `useIsLoading()`
529
+
530
+ Check if any request is currently loading across all APIs.
531
+
532
+ ```typescript
533
+ import { useIsLoading } from '@recats/cdeebee';
534
+
535
+ function GlobalSpinner() {
536
+ const isAnythingLoading = useIsLoading();
537
+
538
+ return isAnythingLoading ? <GlobalSpinner /> : null;
539
+ }
540
+ ```
541
+
542
+ ### Storage Hooks
543
+
544
+ #### `useStorageList<Storage, K>(listName: K)`
545
+
546
+ Get a specific list from storage with full type safety.
547
+
548
+ ```typescript
549
+ import { useStorageList } from '@recats/cdeebee';
550
+
551
+ interface MyStorage {
552
+ forumList: Record<string, { id: string; title: string }>;
553
+ }
554
+
555
+ function ForumsList() {
556
+ const forums = useStorageList<MyStorage, 'forumList'>('forumList');
557
+
558
+ return (
559
+ <div>
560
+ {Object.values(forums).map(forum => (
561
+ <div key={forum.id}>{forum.title}</div>
562
+ ))}
563
+ </div>
564
+ );
565
+ }
566
+ ```
567
+
568
+ #### `useStorage<Storage>()`
569
+
570
+ Get the entire cdeebee storage.
571
+
572
+ ```typescript
573
+ import { useStorage } from '@recats/cdeebee';
574
+
575
+ interface MyStorage {
576
+ forumList: Record<string, Forum>;
577
+ threadList: Record<string, Thread>;
578
+ }
579
+
580
+ function DataDebug() {
581
+ const storage = useStorage<MyStorage>();
582
+
583
+ return <pre>{JSON.stringify(storage, null, 2)}</pre>;
584
+ }
585
+ ```
586
+
587
+ ### History Hooks
588
+
589
+ #### `useRequestHistory(api: string)`
590
+
591
+ Get successful request history for a specific API endpoint.
592
+
593
+ ```typescript
594
+ import { useRequestHistory } from '@recats/cdeebee';
595
+
596
+ function RequestStats({ api }: { api: string }) {
597
+ const history = useRequestHistory(api);
598
+
599
+ return (
600
+ <div>
601
+ Total successful requests to {api}: {history.length}
602
+ </div>
603
+ );
604
+ }
605
+ ```
606
+
607
+ #### `useRequestErrors(api: string)`
608
+
609
+ Get error history for a specific API endpoint.
610
+
611
+ ```typescript
612
+ import { useRequestErrors } from '@recats/cdeebee';
613
+
614
+ function ErrorDisplay({ api }: { api: string }) {
615
+ const errors = useRequestErrors(api);
616
+
617
+ if (errors.length === 0) return null;
618
+
619
+ const lastError = errors[errors.length - 1];
620
+ return <div className="error">Last error: {lastError.request.message}</div>;
621
+ }
622
+ ```
623
+
624
+ ### Advanced: Custom State Path
625
+
626
+ If you're **not** using `combineSlices` or have cdeebee at a custom path in your state (not `state.cdeebee`), use `createCdeebeeHooks`:
627
+
628
+ ```typescript
629
+ // hooks.ts - Create once in your app
630
+ import { createCdeebeeHooks } from '@recats/cdeebee';
631
+ import type { RootState, MyStorage } from './store';
632
+
633
+ // Tell the hooks where to find cdeebee in your state
634
+ export const {
635
+ useLoading,
636
+ useStorageList,
637
+ useStorage,
638
+ useRequestHistory,
639
+ useRequestErrors,
640
+ useIsLoading,
641
+ } = createCdeebeeHooks<RootState, MyStorage>(
642
+ state => state.myCustomPath // Your custom path
643
+ );
644
+ ```
645
+
646
+ **Note:** Most users won't need `createCdeebeeHooks` because `combineSlices` automatically places the slice at `state.cdeebee`.
647
+
648
+ ## TypeScript Support
649
+
650
+ cdeebee is fully typed. Define your storage type and get full type safety:
651
+
652
+ ```typescript
653
+ interface MyStorage {
654
+ userList: Record<string, { id: string; name: string; email: string }>;
655
+ postList: Record<number, { id: number; title: string; userId: string }>;
656
+ }
657
+
658
+ const slice = factory<MyStorage>(settings);
659
+
660
+ // TypeScript knows the structure
661
+ const users = useSelector(state => state.cdeebee.storage.userList);
662
+ // users: Record<string, { id: string; name: string; email: string }>
663
+ ```
664
+
665
+ ## Exports
666
+
667
+ ```typescript
668
+ // Main exports
669
+ export { factory } from '@recats/cdeebee'; // Create cdeebee slice
670
+ export { request } from '@recats/cdeebee'; // Request thunk
671
+ export { batchingUpdate } from '@recats/cdeebee'; // Batch update helper
672
+
673
+ // React hooks
674
+ export {
675
+ createCdeebeeHooks, // Hook factory for custom state paths
676
+ useLoading, // Check if APIs are loading
677
+ useIsLoading, // Check if any request is loading
678
+ useStorageList, // Get a list from storage
679
+ useStorage, // Get entire storage
680
+ useRequestHistory, // Get successful request history
681
+ useRequestErrors, // Get error history
682
+ } from '@recats/cdeebee';
683
+
684
+ // Types
685
+ export type {
686
+ CdeebeeState,
687
+ CdeebeeSettings,
688
+ CdeebeeRequestOptions,
689
+ CdeebeeValueList,
690
+ CdeebeeActiveRequest,
691
+ CdeebeeModule,
692
+ } from '@recats/cdeebee';
693
+ ```
694
+
695
+ ## Examples
696
+
697
+ See the `example/` directory in the repository for a complete working example with Next.js.
698
+
699
+ ## License
202
700
 
203
- - *merge* uses ramda mergeDeepRight strategy to merge existing object with the new one
204
- - *replace* overrides the object
701
+ MIT