@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/LICENSE +1 -1
- package/README.md +650 -153
- package/dist/index.cjs +2 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.ts +234 -6
- package/dist/index.js +298 -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 -1160
- 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,701 @@
|
|
|
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>
|
|
123
162
|
);
|
|
124
163
|
}
|
|
125
164
|
```
|
|
126
165
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
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
|
-
|
|
144
|
-
```js
|
|
145
|
-
// setKeyValue
|
|
146
|
-
import { cdeebeeActions } form '@recats/cdeebee';
|
|
275
|
+
**Example:**
|
|
147
276
|
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
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
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
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
|
-
|
|
160
|
-
entityList: string,
|
|
161
|
-
entityID: EntityID,
|
|
162
|
-
valueList: cdeebeeValueList,
|
|
163
|
-
)
|
|
306
|
+
### Non-List Data
|
|
164
307
|
|
|
165
|
-
|
|
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
|
-
|
|
172
|
-
|
|
173
|
-
|
|
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
|
-
|
|
178
|
-
```js
|
|
179
|
-
import { cdeebeeHelpers } from '@recats/cdeebee';
|
|
322
|
+
These will be stored as-is in the storage.
|
|
180
323
|
|
|
181
|
-
|
|
182
|
-
cdeebeeHelpers.requestCancel(activeRequest: cdeebeActiveRequest) => void;
|
|
324
|
+
## Advanced Usage
|
|
183
325
|
|
|
184
|
-
|
|
185
|
-
cdeebeeHelpers.checkNetworkActivity(activeRequest: cdeebeActiveRequest, apiUrl: string | Array<string>) => boolean;
|
|
326
|
+
### File Uploads
|
|
186
327
|
|
|
187
|
-
|
|
188
|
-
|
|
328
|
+
```typescript
|
|
329
|
+
const file = new File(['content'], 'document.pdf', { type: 'application/pdf' });
|
|
189
330
|
|
|
190
|
-
|
|
191
|
-
|
|
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
|
-
|
|
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
|
-
|
|
197
|
-
|
|
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
|
-
|
|
201
|
-
|
|
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
|
-
|
|
204
|
-
- *replace* overrides the object
|
|
701
|
+
MIT
|