@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/LICENSE +1 -1
- package/README.md +632 -153
- package/dist/index.cjs +2 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.ts +199 -6
- package/dist/index.js +292 -0
- package/dist/index.js.map +1 -0
- package/package.json +40 -24
- package/dist/actions.d.ts +0 -24
- package/dist/cdeebee.js +0 -1174
- package/dist/cdeebee.umd.cjs +0 -3
- package/dist/definition.d.ts +0 -210
- package/dist/helpers.d.ts +0 -22
- package/dist/reducer.d.ts +0 -8
- package/dist/request.d.ts +0 -8
package/README.md
CHANGED
|
@@ -1,204 +1,683 @@
|
|
|
1
1
|
# cdeebee
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-

|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
23
|
-
|
|
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
|
-
|
|
32
|
-
|
|
33
|
-
```
|
|
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: {
|
|
36
|
-
|
|
37
|
-
|
|
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
|
-
|
|
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
|
-
|
|
57
|
+
### Modules
|
|
52
58
|
|
|
53
|
-
|
|
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
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
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
|
-
|
|
64
|
-
cdeebeee,
|
|
65
|
-
requestManager, // optional (checkNetworkActivity, cancelationRequest)
|
|
66
|
-
})
|
|
67
|
+
## Quick Start
|
|
67
68
|
|
|
69
|
+
### 1. Setup Redux Store
|
|
68
70
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
import {
|
|
71
|
+
```typescript
|
|
72
|
+
import { configureStore, combineSlices } from '@reduxjs/toolkit';
|
|
73
|
+
import { factory } from '@recats/cdeebee';
|
|
72
74
|
|
|
73
|
-
|
|
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
|
-
|
|
76
|
-
|
|
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
|
-
//
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
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
|
-
)
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
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
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
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
|
-
|
|
144
|
-
```js
|
|
145
|
-
// setKeyValue
|
|
146
|
-
import { cdeebeeActions } form '@recats/cdeebee';
|
|
591
|
+
Get error history for a specific API endpoint.
|
|
147
592
|
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
this.props.cdeebeeActions.dropCdeebeePath(path: (string | number)[]);
|
|
593
|
+
```typescript
|
|
594
|
+
import { useRequestErrors } from '@recats/cdeebee';
|
|
151
595
|
|
|
152
|
-
|
|
153
|
-
|
|
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
|
-
|
|
160
|
-
entityList: string,
|
|
161
|
-
entityID: EntityID,
|
|
162
|
-
valueList: cdeebeeValueList,
|
|
163
|
-
)
|
|
599
|
+
if (errors.length === 0) return null;
|
|
164
600
|
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
)
|
|
601
|
+
const lastError = errors[errors.length - 1];
|
|
602
|
+
return <div className="error">Last error: {lastError.request.message}</div>;
|
|
603
|
+
}
|
|
604
|
+
```
|
|
170
605
|
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
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
|
-
|
|
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
|
-
|
|
182
|
-
cdeebeeHelpers.requestCancel(activeRequest: cdeebeActiveRequest) => void;
|
|
630
|
+
## TypeScript Support
|
|
183
631
|
|
|
184
|
-
|
|
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
|
-
|
|
188
|
-
|
|
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
|
-
|
|
191
|
-
cdeebeeHelpers.getEntityState(entity: object) => string;
|
|
640
|
+
const slice = factory<MyStorage>(settings);
|
|
192
641
|
|
|
193
|
-
//
|
|
194
|
-
|
|
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
|
-
|
|
197
|
-
|
|
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
|
-
##
|
|
201
|
-
|
|
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
|
-
|
|
204
|
-
- *replace* overrides the object
|
|
683
|
+
MIT
|