@recats/cdeebee 2.3.6 → 3.0.0-beta.10

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,683 @@
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>
162
+ );
163
+ }
164
+ ```
165
+
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
+ }
212
+ ```
213
+
214
+ ## Data Merging Strategies
215
+
216
+ cdeebee supports three strategies for merging data:
217
+
218
+ - **`merge`**: Merges new data with existing data, preserving existing keys not in the response
219
+ - **`replace`**: Completely replaces the list with new data
220
+ - **`skip`**: Skips updating the list entirely, preserving existing data unchanged
221
+
222
+ ```typescript
223
+ listStrategy: {
224
+ forumList: 'merge', // New forums are merged with existing ones
225
+ threadList: 'replace', // Thread list is completely replaced
226
+ userList: 'skip', // User list is never updated, existing data is preserved
227
+ }
228
+ ```
229
+
230
+ ## API Response Format
231
+
232
+ 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.
233
+
234
+ ### List Format
235
+
236
+ For lists (collections of entities), the API should return data in the following format:
237
+
238
+ ```typescript
239
+ {
240
+ forumList: {
241
+ data: [
242
+ { id: 1, title: 'Forum 1' },
243
+ { id: 2, title: 'Forum 2' },
244
+ ],
245
+ primaryKey: 'id',
246
+ },
247
+ threadList: {
248
+ data: [
249
+ { id: 101, title: 'Thread 1', forumID: 1 },
250
+ { id: 102, title: 'Thread 2', forumID: 1 },
251
+ ],
252
+ primaryKey: 'id',
253
+ }
254
+ }
255
+ ```
256
+
257
+ cdeebee automatically converts this format into normalized storage:
258
+
259
+ ```typescript
260
+ {
261
+ forumList: {
262
+ 1: { id: 1, title: 'Forum 1' },
263
+ 2: { id: 2, title: 'Forum 2' },
264
+ },
265
+ threadList: {
266
+ 101: { id: 101, title: 'Thread 1', forumID: 1 },
267
+ 102: { id: 102, title: 'Thread 2', forumID: 1 },
268
+ }
269
+ }
270
+ ```
271
+
272
+ 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.
273
+
274
+ **Example:**
275
+
276
+ If your API returns:
277
+ ```typescript
278
+ {
279
+ sessionList: {
280
+ data: [
281
+ {
282
+ sessionID: 1,
283
+ token: 'da6ec385bc7e4f84a510c3ecca07f3',
284
+ expiresAt: '2034-03-28T22:36:09'
285
+ }
286
+ ],
287
+ primaryKey: 'sessionID',
288
+ }
289
+ }
290
+ ```
291
+
292
+ It will be automatically normalized to:
293
+ ```typescript
294
+ {
295
+ sessionList: {
296
+ '1': {
297
+ sessionID: 1,
298
+ token: 'da6ec385bc7e4f84a510c3ecca07f3',
299
+ expiresAt: '2034-03-28T22:36:09'
300
+ }
301
+ }
302
+ }
303
+ ```
304
+
305
+ ### Non-List Data
306
+
307
+ For non-list data (configuration objects, simple values, etc.), you can return them as regular objects:
308
+
309
+ ```typescript
310
+ {
311
+ config: {
312
+ theme: 'dark',
313
+ language: 'en',
314
+ },
315
+ userPreferences: {
316
+ notifications: true,
317
+ }
318
+ }
319
+ ```
320
+
321
+ These will be stored as-is in the storage.
322
+
323
+ ## Advanced Usage
324
+
325
+ ### File Uploads
326
+
327
+ ```typescript
328
+ const file = new File(['content'], 'document.pdf', { type: 'application/pdf' });
329
+
330
+ dispatch(request({
331
+ api: '/api/upload',
332
+ method: 'POST',
333
+ files: [file],
334
+ body: { description: 'My document' },
335
+ fileKey: 'file', // Optional: override default
336
+ bodyKey: 'metadata', // Optional: override default
337
+ }));
338
+ ```
339
+
340
+ ### Handling Different Response Types
341
+
342
+ By default, cdeebee parses responses as JSON. For other response types (CSV, text files, images, etc.), use the `responseType` option:
343
+
344
+ ```typescript
345
+ // CSV/text response
346
+ dispatch(request({
347
+ api: '/api/export',
348
+ responseType: 'text',
349
+ ignore: true, // Don't store in storage
350
+ onResult: (csvData) => {
351
+ // csvData is a string
352
+ downloadCSV(csvData);
353
+ },
354
+ }));
355
+
356
+ // Binary file (image, PDF, etc.)
357
+ dispatch(request({
358
+ api: '/api/image/123',
359
+ responseType: 'blob',
360
+ ignore: true,
361
+ onResult: (blob) => {
362
+ // blob is a Blob object
363
+ const url = URL.createObjectURL(blob);
364
+ setImageUrl(url);
365
+ },
366
+ }));
367
+
368
+ // JSON (default)
369
+ dispatch(request({
370
+ api: '/api/data',
371
+ // responseType: 'json' is default
372
+ onResult: (data) => {
373
+ console.log(data); // Already parsed JSON
374
+ },
375
+ }));
376
+ ```
377
+
378
+ ### Ignoring Storage Updates
379
+
380
+ Use the `ignore` option to prevent storing the response in storage while still receiving it in the `onResult` callback:
381
+
382
+ ```typescript
383
+ // Export CSV without storing in storage
384
+ dispatch(request({
385
+ api: '/api/export',
386
+ responseType: 'text',
387
+ ignore: true,
388
+ onResult: (csvData) => {
389
+ // Handle CSV data directly
390
+ downloadFile(csvData, 'export.csv');
391
+ },
392
+ }));
393
+ ```
394
+
395
+ ### Custom Headers
396
+
397
+ ```typescript
398
+ // Global headers (in settings)
399
+ mergeWithHeaders: {
400
+ 'Authorization': 'Bearer token',
401
+ 'X-Custom-Header': 'value',
402
+ }
403
+
404
+ // Per-request headers (override global)
405
+ dispatch(request({
406
+ api: '/api/data',
407
+ headers: {
408
+ 'Authorization': 'Bearer different-token', // Overrides global
409
+ 'X-Request-ID': '123', // Additional header
410
+ },
411
+ }));
412
+ ```
413
+
414
+ ### Request Cancellation
415
+
416
+ When the `cancelation` module is enabled, cdeebee automatically cancels previous requests to the same API endpoint when a new request is made:
417
+
418
+ ```typescript
419
+ // First request
420
+ dispatch(request({ api: '/api/data', body: { query: 'slow' } }));
421
+
422
+ // Second request to same API - first one is automatically cancelled
423
+ dispatch(request({ api: '/api/data', body: { query: 'fast' } }));
424
+ ```
425
+
426
+ ### Sequential Request Processing (queryQueue)
427
+
428
+ When the `queryQueue` module is enabled, all requests are processed sequentially in the order they were sent. This ensures that:
429
+
430
+ - Requests complete in the exact order they were dispatched
431
+ - Data is stored in the store in the correct sequence
432
+ - Even if a faster request is sent after a slower one, it will wait for the previous request to complete
433
+
434
+ This is particularly useful when you need to maintain data consistency and ensure that updates happen in the correct order.
435
+
436
+ ```typescript
437
+ // Enable queryQueue module
438
+ const cdeebeeSlice = factory<Storage>({
439
+ modules: ['history', 'listener', 'storage', 'queryQueue'],
440
+ // ... other settings
441
+ });
442
+
443
+ // Send multiple requests - they will be processed sequentially
444
+ dispatch(request({ api: '/api/data', body: { id: 1 } })); // Completes first
445
+ dispatch(request({ api: '/api/data', body: { id: 2 } })); // Waits for #1, then completes
446
+ dispatch(request({ api: '/api/data', body: { id: 3 } })); // Waits for #2, then completes
447
+
448
+ // Even if request #3 is faster, it will still complete last
449
+ // All requests are stored in the store in order: 1 → 2 → 3
450
+ ```
451
+
452
+ **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.
453
+
454
+ ### Manual State Updates
455
+
456
+ You can manually update the storage using the `set` action:
457
+
458
+ ```typescript
459
+ import { batchingUpdate } from '@recats/cdeebee';
460
+
461
+ // Update multiple values at once
462
+ const updates = [
463
+ { key: ['forumList', '1', 'title'], value: 'New Title' },
464
+ { key: ['forumList', '1', 'views'], value: 100 },
465
+ { key: ['threadList', '101', 'title'], value: 'Updated Thread' },
466
+ ];
467
+
468
+ dispatch(cdeebeeSlice.actions.set(updates));
469
+ ```
470
+
471
+ ### Accessing Request History
472
+
473
+ You can access request history using hooks or selectors:
474
+
475
+ ```typescript
476
+ import { useRequestHistory, useRequestErrors } from '@recats/cdeebee';
477
+
478
+ // Using hooks (recommended)
479
+ const apiHistory = useRequestHistory('/api/forums');
480
+ const apiErrors = useRequestErrors('/api/forums');
481
+
482
+ // Or using selectors
483
+ const doneRequests = useAppSelector(state => state.cdeebee.request.done);
484
+ const errors = useAppSelector(state => state.cdeebee.request.errors);
485
+ ```
486
+
487
+ ## React Hooks
488
+
489
+ 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`).
490
+
491
+ ### Loading State Hooks
492
+
493
+ #### `useLoading(apiList: string[])`
494
+
495
+ Check if any of the specified APIs are currently loading.
496
+
497
+ ```typescript
498
+ import { useLoading } from '@recats/cdeebee';
499
+
500
+ function MyComponent() {
501
+ // Check if any of these APIs are loading
502
+ const isLoading = useLoading(['/api/forums', '/api/threads']);
503
+
504
+ if (isLoading) return <Spinner />;
505
+
506
+ return <div>Content</div>;
507
+ }
508
+ ```
509
+
510
+ #### `useIsLoading()`
511
+
512
+ Check if any request is currently loading across all APIs.
513
+
514
+ ```typescript
515
+ import { useIsLoading } from '@recats/cdeebee';
516
+
517
+ function GlobalSpinner() {
518
+ const isAnythingLoading = useIsLoading();
519
+
520
+ return isAnythingLoading ? <GlobalSpinner /> : null;
521
+ }
522
+ ```
523
+
524
+ ### Storage Hooks
525
+
526
+ #### `useStorageList<Storage, K>(listName: K)`
527
+
528
+ Get a specific list from storage with full type safety.
529
+
530
+ ```typescript
531
+ import { useStorageList } from '@recats/cdeebee';
532
+
533
+ interface MyStorage {
534
+ forumList: Record<string, { id: string; title: string }>;
535
+ }
536
+
537
+ function ForumsList() {
538
+ const forums = useStorageList<MyStorage, 'forumList'>('forumList');
539
+
540
+ return (
541
+ <div>
542
+ {Object.values(forums).map(forum => (
543
+ <div key={forum.id}>{forum.title}</div>
544
+ ))}
545
+ </div>
123
546
  );
124
547
  }
125
548
  ```
126
549
 
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
550
+ #### `useStorage<Storage>()`
551
+
552
+ Get the entire cdeebee storage.
553
+
554
+ ```typescript
555
+ import { useStorage } from '@recats/cdeebee';
556
+
557
+ interface MyStorage {
558
+ forumList: Record<string, Forum>;
559
+ threadList: Record<string, Thread>;
560
+ }
561
+
562
+ function DataDebug() {
563
+ const storage = useStorage<MyStorage>();
564
+
565
+ return <pre>{JSON.stringify(storage, null, 2)}</pre>;
566
+ }
567
+ ```
568
+
569
+ ### History Hooks
570
+
571
+ #### `useRequestHistory(api: string)`
572
+
573
+ Get successful request history for a specific API endpoint.
574
+
575
+ ```typescript
576
+ import { useRequestHistory } from '@recats/cdeebee';
577
+
578
+ function RequestStats({ api }: { api: string }) {
579
+ const history = useRequestHistory(api);
580
+
581
+ return (
582
+ <div>
583
+ Total successful requests to {api}: {history.length}
584
+ </div>
585
+ );
586
+ }
140
587
  ```
141
588
 
589
+ #### `useRequestErrors(api: string)`
142
590
 
143
- ## Actions
144
- ```js
145
- // setKeyValue
146
- import { cdeebeeActions } form '@recats/cdeebee';
591
+ Get error history for a specific API endpoint.
147
592
 
148
- this.props.cdeebeeActions.dropRequestByApiUrl: (api: string);
149
- this.props.cdeebeeActions.dropErrorsByApiUrl: (api: string);
150
- this.props.cdeebeeActions.dropCdeebeePath(path: (string | number)[]);
593
+ ```typescript
594
+ import { useRequestErrors } from '@recats/cdeebee';
151
595
 
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
- )
596
+ function ErrorDisplay({ api }: { api: string }) {
597
+ const errors = useRequestErrors(api);
158
598
 
159
- this.props.cdeebeeActions.setKeyValue(
160
- entityList: string,
161
- entityID: EntityID,
162
- valueList: cdeebeeValueList,
163
- )
599
+ if (errors.length === 0) return null;
164
600
 
165
- this.props.cdeebeeActions.commitEntity(
166
- entityList: string,
167
- entityID: EntityID,
168
- entity: object,
169
- )
601
+ const lastError = errors[errors.length - 1];
602
+ return <div className="error">Last error: {lastError.request.message}</div>;
603
+ }
604
+ ```
170
605
 
171
- this.props.cdeebeeActions.resetEntity(
172
- entityList: string,
173
- entityID: EntityID,
174
- )
606
+ ### Advanced: Custom State Path
607
+
608
+ If you're **not** using `combineSlices` or have cdeebee at a custom path in your state (not `state.cdeebee`), use `createCdeebeeHooks`:
609
+
610
+ ```typescript
611
+ // hooks.ts - Create once in your app
612
+ import { createCdeebeeHooks } from '@recats/cdeebee';
613
+ import type { RootState, MyStorage } from './store';
614
+
615
+ // Tell the hooks where to find cdeebee in your state
616
+ export const {
617
+ useLoading,
618
+ useStorageList,
619
+ useStorage,
620
+ useRequestHistory,
621
+ useRequestErrors,
622
+ useIsLoading,
623
+ } = createCdeebeeHooks<RootState, MyStorage>(
624
+ state => state.myCustomPath // Your custom path
625
+ );
175
626
  ```
176
627
 
177
- ## Helpers
178
- ```js
179
- import { cdeebeeHelpers } from '@recats/cdeebee';
628
+ **Note:** Most users won't need `createCdeebeeHooks` because `combineSlices` automatically places the slice at `state.cdeebee`.
180
629
 
181
- // requestCancel
182
- cdeebeeHelpers.requestCancel(activeRequest: cdeebeActiveRequest) => void;
630
+ ## TypeScript Support
183
631
 
184
- // checkNetworkActivity
185
- cdeebeeHelpers.checkNetworkActivity(activeRequest: cdeebeActiveRequest, apiUrl: string | Array<string>) => boolean;
632
+ cdeebee is fully typed. Define your storage type and get full type safety:
186
633
 
187
- // getSubEntity element in cdeebee list
188
- cdeebeeHelpers.getSubEntity(entity: object) => object;
634
+ ```typescript
635
+ interface MyStorage {
636
+ userList: Record<string, { id: string; name: string; email: string }>;
637
+ postList: Record<number, { id: number; title: string; userId: string }>;
638
+ }
189
639
 
190
- // getEntityState element in cdeebee list
191
- cdeebeeHelpers.getEntityState(entity: object) => string;
640
+ const slice = factory<MyStorage>(settings);
192
641
 
193
- // commitEntity element in cdeebee list
194
- cdeebeeHelpers.commitEntity(entity: object) => void;
642
+ // TypeScript knows the structure
643
+ const users = useSelector(state => state.cdeebee.storage.userList);
644
+ // users: Record<string, { id: string; name: string; email: string }>
645
+ ```
195
646
 
196
- // resetEntity element in cdeebee list
197
- cdeebeeHelpers.resetEntity(entity: object) => void;
647
+ ## Exports
648
+
649
+ ```typescript
650
+ // Main exports
651
+ export { factory } from '@recats/cdeebee'; // Create cdeebee slice
652
+ export { request } from '@recats/cdeebee'; // Request thunk
653
+ export { batchingUpdate } from '@recats/cdeebee'; // Batch update helper
654
+
655
+ // React hooks
656
+ export {
657
+ createCdeebeeHooks, // Hook factory for custom state paths
658
+ useLoading, // Check if APIs are loading
659
+ useIsLoading, // Check if any request is loading
660
+ useStorageList, // Get a list from storage
661
+ useStorage, // Get entire storage
662
+ useRequestHistory, // Get successful request history
663
+ useRequestErrors, // Get error history
664
+ } from '@recats/cdeebee';
665
+
666
+ // Types
667
+ export type {
668
+ CdeebeeState,
669
+ CdeebeeSettings,
670
+ CdeebeeRequestOptions,
671
+ CdeebeeValueList,
672
+ CdeebeeActiveRequest,
673
+ CdeebeeModule,
674
+ } from '@recats/cdeebee';
198
675
  ```
199
676
 
200
- ## Data merging behavior
201
- During data merging cdeebee could behave in different ways according to the enum value which is passed during request
677
+ ## Examples
678
+
679
+ See the `example/` directory in the repository for a complete working example with Next.js.
680
+
681
+ ## License
202
682
 
203
- - *merge* uses ramda mergeDeepRight strategy to merge existing object with the new one
204
- - *replace* overrides the object
683
+ MIT