@recats/cdeebee 2.3.7 → 3.0.0-beta.3
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 +309 -150
- package/dist/index.cjs +2 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.ts +93 -6
- package/dist/index.js +220 -0
- package/dist/index.js.map +1 -0
- package/package.json +35 -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/LICENSE
CHANGED
package/README.md
CHANGED
|
@@ -1,204 +1,363 @@
|
|
|
1
1
|
# cdeebee
|
|
2
2
|
|
|
3
3
|
[](https://www.npmjs.com/package/@recats/cdeebee)
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
4
|
+
|
|
5
|
+
A Redux-based data management library that provides a uniform way to access, fetch, update, and manage application data with minimal boilerplate code.
|
|
6
|
+
|
|
7
|
+
## Installation
|
|
7
8
|
|
|
8
9
|
```sh
|
|
9
10
|
npm i @recats/cdeebee
|
|
10
11
|
# or
|
|
11
12
|
yarn add @recats/cdeebee
|
|
13
|
+
# or
|
|
14
|
+
pnpm add @recats/cdeebee
|
|
12
15
|
```
|
|
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
16
|
|
|
18
|
-
|
|
17
|
+
## What is cdeebee?
|
|
18
|
+
|
|
19
|
+
cdeebee is a data management library built on top of Redux Toolkit that aims to:
|
|
20
|
+
- Provide a uniform way of accessing data
|
|
21
|
+
- Decrease boilerplate code when working with data fetching, updating, committing, and rollbacking
|
|
22
|
+
- Manage normalized data structures similar to relational databases
|
|
23
|
+
- Handle API requests with built-in normalization, error handling, and request cancellation
|
|
19
24
|
|
|
20
|
-
|
|
25
|
+
## Core Concepts
|
|
21
26
|
|
|
22
|
-
|
|
23
|
-
|
|
27
|
+
### Normalized Storage
|
|
28
|
+
|
|
29
|
+
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.
|
|
30
|
+
|
|
31
|
+
For example, a forum application might have this structure:
|
|
32
|
+
|
|
33
|
+
```typescript
|
|
24
34
|
{
|
|
25
|
-
forumList: {
|
|
26
|
-
threadList: {
|
|
27
|
-
postList: {
|
|
35
|
+
forumList: {},
|
|
36
|
+
threadList: {},
|
|
37
|
+
postList: {}
|
|
28
38
|
}
|
|
29
39
|
```
|
|
30
40
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
```
|
|
41
|
+
After fetching data, the storage might look like:
|
|
42
|
+
|
|
43
|
+
```typescript
|
|
34
44
|
{
|
|
35
|
-
forumList: {
|
|
36
|
-
|
|
37
|
-
|
|
45
|
+
forumList: {
|
|
46
|
+
1: { id: 1, title: 'Milky Way Galaxy' }
|
|
47
|
+
},
|
|
48
|
+
threadList: {
|
|
49
|
+
10001: { id: 10001, title: 'Solar system', forumID: 1 }
|
|
50
|
+
},
|
|
51
|
+
postList: {
|
|
52
|
+
2: { id: 2, title: 'Earth', threadID: 10001 }
|
|
53
|
+
}
|
|
38
54
|
}
|
|
39
|
-
|
|
40
55
|
```
|
|
41
56
|
|
|
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)
|
|
57
|
+
### Modules
|
|
50
58
|
|
|
51
|
-
|
|
59
|
+
cdeebee uses a modular architecture with the following modules:
|
|
52
60
|
|
|
53
|
-
|
|
54
|
-
-
|
|
55
|
-
-
|
|
56
|
-
-
|
|
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)
|
|
57
65
|
|
|
58
|
-
##
|
|
59
|
-
```js
|
|
60
|
-
// reducer/index.js
|
|
61
|
-
import { cdeebee, requestManager } from '@recats/cdeebee';
|
|
66
|
+
## Quick Start
|
|
62
67
|
|
|
63
|
-
|
|
64
|
-
cdeebeee,
|
|
65
|
-
requestManager, // optional (checkNetworkActivity, cancelationRequest)
|
|
66
|
-
})
|
|
68
|
+
### 1. Setup Redux Store
|
|
67
69
|
|
|
70
|
+
```typescript
|
|
71
|
+
import { configureStore, combineSlices } from '@reduxjs/toolkit';
|
|
72
|
+
import { factory } from '@recats/cdeebee';
|
|
68
73
|
|
|
69
|
-
//
|
|
70
|
-
|
|
71
|
-
|
|
74
|
+
// Define your storage structure
|
|
75
|
+
interface Storage {
|
|
76
|
+
forumList: Record<number, { id: number; title: string }>;
|
|
77
|
+
threadList: Record<number, { id: number; title: string; forumID: number }>;
|
|
78
|
+
postList: Record<number, { id: number; title: string; threadID: number }>;
|
|
79
|
+
}
|
|
72
80
|
|
|
73
|
-
|
|
81
|
+
// Create cdeebee slice
|
|
82
|
+
export const cdeebeeSlice = factory<Storage>(
|
|
74
83
|
{
|
|
75
|
-
|
|
76
|
-
|
|
84
|
+
modules: ['history', 'listener', 'cancelation', 'storage'],
|
|
85
|
+
fileKey: 'file',
|
|
86
|
+
bodyKey: 'value',
|
|
87
|
+
primaryKey: 'id',
|
|
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
|
+
console.log('Request completed:', result);
|
|
131
|
+
},
|
|
132
|
+
}));
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
return <button onClick={fetchForums}>Load Forums</button>;
|
|
136
|
+
}
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
### 3. Access Data and Loading States
|
|
140
|
+
|
|
141
|
+
```typescript
|
|
142
|
+
import { useAppSelector } from './hooks';
|
|
143
|
+
|
|
144
|
+
function ForumsList() {
|
|
145
|
+
const forums = useAppSelector(state => state.cdeebee.storage.forumList);
|
|
146
|
+
const activeRequests = useAppSelector(state => state.cdeebee.request.active);
|
|
147
|
+
const isLoading = activeRequests.some(req => req.api === '/api/forums');
|
|
148
|
+
|
|
149
|
+
return (
|
|
150
|
+
<div>
|
|
151
|
+
{isLoading && <p>Loading...</p>}
|
|
152
|
+
{Object.values(forums).map(forum => (
|
|
153
|
+
<div key={forum.id}>{forum.title}</div>
|
|
154
|
+
))}
|
|
155
|
+
</div>
|
|
123
156
|
);
|
|
124
157
|
}
|
|
125
158
|
```
|
|
126
159
|
|
|
127
|
-
##
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
160
|
+
## Configuration
|
|
161
|
+
|
|
162
|
+
### Settings
|
|
163
|
+
|
|
164
|
+
The `factory` function accepts a settings object with the following options:
|
|
165
|
+
|
|
166
|
+
```typescript
|
|
167
|
+
interface CdeebeeSettings<T> {
|
|
168
|
+
modules: CdeebeeModule[]; // Active modules: 'history' | 'listener' | 'storage' | 'cancelation'
|
|
169
|
+
fileKey: string; // Key name for file uploads in FormData
|
|
170
|
+
bodyKey: string; // Key name for request body in FormData
|
|
171
|
+
primaryKey: string; // Primary key field name in API responses (default: 'primaryKey')
|
|
172
|
+
listStrategy?: CdeebeeListStrategy<T>; // Merge strategy per list: 'merge' | 'replace'
|
|
173
|
+
mergeWithData?: unknown; // Data to merge with every request body
|
|
174
|
+
mergeWithHeaders?: Record<string, string>; // Headers to merge with every request
|
|
175
|
+
normalize?: (storage, result, strategyList) => T; // Custom normalization function
|
|
176
|
+
}
|
|
140
177
|
```
|
|
141
178
|
|
|
179
|
+
### Request Options
|
|
180
|
+
|
|
181
|
+
```typescript
|
|
182
|
+
interface CdeebeeRequestOptions<T> {
|
|
183
|
+
api: string; // API endpoint URL
|
|
184
|
+
method?: 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH';
|
|
185
|
+
body?: unknown; // Request body
|
|
186
|
+
headers?: Record<string, string>; // Additional headers (merged with mergeWithHeaders)
|
|
187
|
+
files?: File[]; // Files to upload
|
|
188
|
+
fileKey?: string; // Override default fileKey
|
|
189
|
+
bodyKey?: string; // Override default bodyKey
|
|
190
|
+
listStrategy?: CdeebeeListStrategy<T>; // Override list strategy for this request
|
|
191
|
+
normalize?: (storage, result, strategyList) => T; // Override normalization
|
|
192
|
+
onResult?: (response: T) => void; // Callback called with response data on success
|
|
193
|
+
}
|
|
194
|
+
```
|
|
142
195
|
|
|
143
|
-
##
|
|
144
|
-
```js
|
|
145
|
-
// setKeyValue
|
|
146
|
-
import { cdeebeeActions } form '@recats/cdeebee';
|
|
196
|
+
## Data Merging Strategies
|
|
147
197
|
|
|
148
|
-
|
|
149
|
-
this.props.cdeebeeActions.dropErrorsByApiUrl: (api: string);
|
|
150
|
-
this.props.cdeebeeActions.dropCdeebeePath(path: (string | number)[]);
|
|
198
|
+
cdeebee supports two strategies for merging data:
|
|
151
199
|
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
entityList: string,
|
|
155
|
-
entityID: EntityID,
|
|
156
|
-
value: any, // any value
|
|
157
|
-
)
|
|
200
|
+
- **`merge`**: Merges new data with existing data, preserving existing keys not in the response
|
|
201
|
+
- **`replace`**: Completely replaces the list with new data
|
|
158
202
|
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
203
|
+
```typescript
|
|
204
|
+
listStrategy: {
|
|
205
|
+
forumList: 'merge', // New forums are merged with existing ones
|
|
206
|
+
threadList: 'replace', // Thread list is completely replaced
|
|
207
|
+
}
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
## API Response Format
|
|
164
211
|
|
|
165
|
-
|
|
166
|
-
entityList: string,
|
|
167
|
-
entityID: EntityID,
|
|
168
|
-
entity: object,
|
|
169
|
-
)
|
|
212
|
+
cdeebee expects API responses in a specific format for automatic normalization:
|
|
170
213
|
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
214
|
+
```typescript
|
|
215
|
+
{
|
|
216
|
+
forumList: {
|
|
217
|
+
primaryKey: 'id', // The field name specified in settings.primaryKey
|
|
218
|
+
data: [
|
|
219
|
+
{ id: 1, title: 'Forum 1' },
|
|
220
|
+
{ id: 2, title: 'Forum 2' },
|
|
221
|
+
]
|
|
222
|
+
},
|
|
223
|
+
threadList: {
|
|
224
|
+
primaryKey: 'id',
|
|
225
|
+
data: [
|
|
226
|
+
{ id: 101, title: 'Thread 1', forumID: 1 },
|
|
227
|
+
]
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
The library automatically normalizes this into:
|
|
233
|
+
|
|
234
|
+
```typescript
|
|
235
|
+
{
|
|
236
|
+
forumList: {
|
|
237
|
+
1: { id: 1, title: 'Forum 1' },
|
|
238
|
+
2: { id: 2, title: 'Forum 2' },
|
|
239
|
+
},
|
|
240
|
+
threadList: {
|
|
241
|
+
101: { id: 101, title: 'Thread 1', forumID: 1 },
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
## Advanced Usage
|
|
247
|
+
|
|
248
|
+
### File Uploads
|
|
249
|
+
|
|
250
|
+
```typescript
|
|
251
|
+
const file = new File(['content'], 'document.pdf', { type: 'application/pdf' });
|
|
252
|
+
|
|
253
|
+
dispatch(request({
|
|
254
|
+
api: '/api/upload',
|
|
255
|
+
method: 'POST',
|
|
256
|
+
files: [file],
|
|
257
|
+
body: { description: 'My document' },
|
|
258
|
+
fileKey: 'file', // Optional: override default
|
|
259
|
+
bodyKey: 'metadata', // Optional: override default
|
|
260
|
+
}));
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
### Custom Headers
|
|
264
|
+
|
|
265
|
+
```typescript
|
|
266
|
+
// Global headers (in settings)
|
|
267
|
+
mergeWithHeaders: {
|
|
268
|
+
'Authorization': 'Bearer token',
|
|
269
|
+
'X-Custom-Header': 'value',
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
// Per-request headers (override global)
|
|
273
|
+
dispatch(request({
|
|
274
|
+
api: '/api/data',
|
|
275
|
+
headers: {
|
|
276
|
+
'Authorization': 'Bearer different-token', // Overrides global
|
|
277
|
+
'X-Request-ID': '123', // Additional header
|
|
278
|
+
},
|
|
279
|
+
}));
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
### Request Cancellation
|
|
283
|
+
|
|
284
|
+
When the `cancelation` module is enabled, cdeebee automatically cancels previous requests to the same API endpoint when a new request is made:
|
|
285
|
+
|
|
286
|
+
```typescript
|
|
287
|
+
// First request
|
|
288
|
+
dispatch(request({ api: '/api/data', body: { query: 'slow' } }));
|
|
289
|
+
|
|
290
|
+
// Second request to same API - first one is automatically cancelled
|
|
291
|
+
dispatch(request({ api: '/api/data', body: { query: 'fast' } }));
|
|
175
292
|
```
|
|
176
293
|
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
294
|
+
### Manual State Updates
|
|
295
|
+
|
|
296
|
+
You can manually update the storage using the `set` action:
|
|
180
297
|
|
|
181
|
-
|
|
182
|
-
|
|
298
|
+
```typescript
|
|
299
|
+
import { batchingUpdate } from '@recats/cdeebee';
|
|
183
300
|
|
|
184
|
-
//
|
|
185
|
-
|
|
301
|
+
// Update multiple values at once
|
|
302
|
+
const updates = [
|
|
303
|
+
{ key: ['forumList', '1', 'title'], value: 'New Title' },
|
|
304
|
+
{ key: ['forumList', '1', 'views'], value: 100 },
|
|
305
|
+
{ key: ['threadList', '101', 'title'], value: 'Updated Thread' },
|
|
306
|
+
];
|
|
186
307
|
|
|
187
|
-
|
|
188
|
-
|
|
308
|
+
dispatch(cdeebeeSlice.actions.set(updates));
|
|
309
|
+
```
|
|
189
310
|
|
|
190
|
-
|
|
191
|
-
cdeebeeHelpers.getEntityState(entity: object) => string;
|
|
311
|
+
### Accessing Request History
|
|
192
312
|
|
|
193
|
-
|
|
194
|
-
|
|
313
|
+
```typescript
|
|
314
|
+
const doneRequests = useAppSelector(state => state.cdeebee.request.done);
|
|
315
|
+
const errors = useAppSelector(state => state.cdeebee.request.errors);
|
|
195
316
|
|
|
196
|
-
//
|
|
197
|
-
|
|
317
|
+
// Get history for specific API
|
|
318
|
+
const apiHistory = doneRequests['/api/forums'] || [];
|
|
319
|
+
const apiErrors = errors['/api/forums'] || [];
|
|
198
320
|
```
|
|
199
321
|
|
|
200
|
-
##
|
|
201
|
-
|
|
322
|
+
## TypeScript Support
|
|
323
|
+
|
|
324
|
+
cdeebee is fully typed. Define your storage type and get full type safety:
|
|
325
|
+
|
|
326
|
+
```typescript
|
|
327
|
+
interface MyStorage {
|
|
328
|
+
userList: Record<string, { id: string; name: string; email: string }>;
|
|
329
|
+
postList: Record<number, { id: number; title: string; userId: string }>;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
const slice = factory<MyStorage>(settings);
|
|
333
|
+
|
|
334
|
+
// TypeScript knows the structure
|
|
335
|
+
const users = useSelector(state => state.cdeebee.storage.userList);
|
|
336
|
+
// users: Record<string, { id: string; name: string; email: string }>
|
|
337
|
+
```
|
|
338
|
+
|
|
339
|
+
## Exports
|
|
340
|
+
|
|
341
|
+
```typescript
|
|
342
|
+
// Main exports
|
|
343
|
+
export { factory } from '@recats/cdeebee'; // Create cdeebee slice
|
|
344
|
+
export { request } from '@recats/cdeebee'; // Request thunk
|
|
345
|
+
export { batchingUpdate } from '@recats/cdeebee'; // Batch update helper
|
|
346
|
+
|
|
347
|
+
// Types
|
|
348
|
+
export type {
|
|
349
|
+
CdeebeeState,
|
|
350
|
+
CdeebeeSettings,
|
|
351
|
+
CdeebeeRequestOptions,
|
|
352
|
+
CdeebeeValueList,
|
|
353
|
+
CdeebeeActiveRequest,
|
|
354
|
+
} from '@recats/cdeebee';
|
|
355
|
+
```
|
|
356
|
+
|
|
357
|
+
## Examples
|
|
358
|
+
|
|
359
|
+
See the `example/` directory in the repository for a complete working example with Next.js.
|
|
360
|
+
|
|
361
|
+
## License
|
|
202
362
|
|
|
203
|
-
|
|
204
|
-
- *replace* overrides the object
|
|
363
|
+
MIT
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const K=require("@reduxjs/toolkit");function d(r,n,i){r.modules.includes(n)&&i()}function f(r){return r!==null&&typeof r=="object"&&!Array.isArray(r)}function C(r){return f(r)&&Array.isArray(r.data)}function z(r,n){return f(r)&&Object.prototype.hasOwnProperty.call(r,n)}function w(r,n){if(!f(r)||!f(n))return n;const i={...r},a=n;for(const e in a)if(Object.prototype.hasOwnProperty.call(a,e)){const c=i[e],t=a[e];f(c)&&f(t)&&!Array.isArray(c)&&!Array.isArray(t)?i[e]=w(c,t):i[e]=t}return i}function O(r,n){const i={...n};for(const a of r)delete i[a];return i}function D(r,n){for(let i=0;i<n.length;i++){const a=n[i],e=a.key,c=a.value;if(e.length===0)continue;let t=r;for(let o=0;o<e.length-1;o++){const s=e[o];if(Array.isArray(t)){const l=typeof s=="number"?s:Number(s);(!(l in t)||!f(t[l]))&&(t[l]={}),t=t[l]}else{const l=String(s);if(!(l in t)){const y=typeof e[o+1]=="number"||!isNaN(Number(e[o+1]))&&String(Number(e[o+1]))===String(e[o+1]);t[l]=y?[]:{}}const u=t[l];t=Array.isArray(u)||f(u)?u:{}}}Array.isArray(t)||(t[String(e[e.length-1])]=c)}}class I{constructor(){this.byRequestId=new Map,this.byApi=new Map}add(n,i,a){const e={requestId:i,controller:a,api:n};this.byRequestId.set(i,e),this.byApi.has(n)||this.byApi.set(n,new Set),this.byApi.get(n).add(i)}delete(n){const i=this.byRequestId.get(n);if(!i)return;this.byRequestId.delete(n);const a=this.byApi.get(i.api);a&&(a.delete(n),a.size===0&&this.byApi.delete(i.api))}abortAllForApi(n,i){const a=this.byApi.get(n);a&&a.forEach(e=>{if(e!==i){const c=this.byRequestId.get(e);c&&(c.controller.abort(),this.delete(e))}})}}const R=new I;function N(r,n){R.abortAllForApi(r,n)}function P(r,n,i){const a=new AbortController,e=()=>{R.delete(i)};return r.addEventListener("abort",()=>{a.abort(),e()}),{controller:a,init:()=>R.add(n,i,a),drop:e}}const q=K.createAsyncThunk("cdeebee/request",async(r,{rejectWithValue:n,getState:i,requestId:a,signal:e})=>{const c=new Date().toUTCString(),{cdeebee:{settings:t}}=i(),o=P(e,r.api,a);d(t,"cancelation",o.init);try{const{method:s="POST",body:l,headers:u={}}=r,y={...t.mergeWithHeaders??{},...u},A={...t.mergeWithData??{},...l??{}};let b=JSON.stringify(A);if(r.files){const p=new FormData,m=r.fileKey||t.fileKey,S=r.bodyKey||t.bodyKey;for(let k=0;k<r.files.length;k+=1)m&&p.append(m,r.files[k]);S&&p.append(S,b),b=p}const g=await fetch(r.api,{method:s,headers:{"ui-request-id":a,"Content-Type":"application/json",...y},signal:o.controller.signal,body:b});if(d(t,"cancelation",o.drop),!g.ok)return n(g);const h=await g.json();return r.onResult&&typeof r.onResult=="function"&&r.onResult(h),{result:h,startedAt:c,endedAt:new Date().toUTCString()}}catch(s){return d(t,"cancelation",o.drop),s instanceof Error&&s.name==="AbortError"?n({message:"Request was cancelled",cancelled:!0}):n({message:s instanceof Error?s.message:"Unknown error occurred"})}});function j(r,n,i){const a=Object.keys(n),e=r.settings.primaryKey,c=f(r.storage)?r.storage:{},t={...c},o=new Set;for(const s of a){const l=n[s];if(l==null||typeof l=="string"){o.add(s);continue}if(C(l)&&z(l,e)){const u=l[e];if(typeof u!="string"){console.warn(`Cdeebee: Primary key "${e}" is not a string for API "${s}". Skipping normalization.`),t[s]=l;continue}const y={},A=l.data,b=A.length;for(let p=0;p<b;p++){const m=A[p];if(f(m)&&m[u]){const S=m[u];y[S]=m}}const g=i[s]??"merge",h=s in c?c[s]:{};g==="replace"?t[s]=y:(g==="merge"||console.warn(`Cdeebee: Unknown strategy "${g}" for key "${s}". Skipping normalization.`),t[s]=w(h,y))}else t[s]=l}return o.size>0?O(Array.from(o),t):t}const v={settings:{modules:["history","listener","storage","cancelation"],fileKey:"file",bodyKey:"value",primaryKey:"primaryKey",listStrategy:{},mergeWithData:{},mergeWithHeaders:{}},storage:{},request:{active:[],errors:{},done:{}}},T=(r,n)=>K.createSlice({name:"cdeebee",initialState:w(v,{settings:r,storage:n??{}}),reducers:{set(a,e){D(a.storage,e.payload)}},extraReducers:a=>{a.addCase(q.pending,(e,c)=>{const t=c.meta.arg.api,o=c.meta.requestId;d(e.settings,"cancelation",()=>{N(t,o)}),d(e.settings,"listener",()=>{e.request.active.push({api:t,requestId:o})})}).addCase(q.fulfilled,(e,c)=>{const t=c.meta.requestId,o=c.meta.arg.api;d(e.settings,"listener",()=>{e.request.active=e.request.active.filter(s=>!(s.api===o&&s.requestId===t))}),d(e.settings,"history",()=>{e.request.done[o]||(e.request.done[o]=[]),e.request.done[o].push({api:o,request:c.payload,requestId:t})}),d(e.settings,"storage",()=>{const s=c.meta.arg.listStrategy??e.settings.listStrategy??{},l=c.meta.arg.normalize??e.settings.normalize??j,u=K.current(e),y=l(u,c.payload.result,s);e.storage=y})}).addCase(q.rejected,(e,c)=>{const t=c.meta.requestId,o=c.meta.arg.api;d(e.settings,"listener",()=>{e.request.active=e.request.active.filter(s=>!(s.api===o&&s.requestId===t))}),d(e.settings,"history",()=>{e.request.errors[o]||(e.request.errors[o]=[]),e.request.errors[o].push({requestId:t,api:o,request:c.error})})})}});exports.batchingUpdate=D;exports.factory=T;exports.request=q;
|
|
2
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.cjs","sources":["../lib/reducer/helpers.ts","../lib/reducer/abortController.ts","../lib/reducer/request.ts","../lib/reducer/storage.ts","../lib/reducer/index.ts"],"sourcesContent":["import { type CdeebeeSettings, type CdeebeeModule, CdeebeeValueList } from './types';\n\nexport function checkModule(settings: CdeebeeSettings<unknown>, module: CdeebeeModule, result: () => void) {\n if (settings.modules.includes(module)) {\n result();\n }\n}\nexport function isRecord(value: unknown): value is Record<string, unknown> {\n return value !== null && typeof value === 'object' && !Array.isArray(value);\n}\n\nexport function hasDataProperty(value: unknown): value is Record<string, unknown> & { data: unknown[] } {\n return isRecord(value) && Array.isArray(value.data);\n}\n\nexport function hasProperty(value: unknown, prop: string): boolean {\n return isRecord(value) && Object.prototype.hasOwnProperty.call(value, prop);\n}\n\nexport function mergeDeepRight<T>(\n left: T,\n right: Partial<T> | Record<string, unknown>\n): T {\n if (!isRecord(left) || !isRecord(right)) {\n return right as T;\n }\n\n const result = { ...left } as Record<string, unknown>;\n const rightRecord = right as Record<string, unknown>;\n\n for (const key in rightRecord) {\n if (Object.prototype.hasOwnProperty.call(rightRecord, key)) {\n const leftValue = result[key];\n const rightValue = rightRecord[key];\n\n if (\n isRecord(leftValue) &&\n isRecord(rightValue) &&\n !Array.isArray(leftValue) &&\n !Array.isArray(rightValue)\n ) {\n result[key] = mergeDeepRight(leftValue, rightValue);\n } else {\n result[key] = rightValue;\n }\n }\n }\n\n return result as T;\n}\n\nexport function omit<T extends Record<string, unknown>>(keys: string[], obj: T): Omit<T, keyof T> {\n const result = { ...obj };\n for (const key of keys) {\n delete result[key];\n }\n return result as Omit<T, keyof T>;\n}\n\nexport function assocPath<T>(path: (string | number)[], value: unknown, obj: T): T {\n if (path.length === 0) {\n return value as T;\n }\n\n const [first, ...rest] = path;\n const firstKey = String(first);\n const result = Array.isArray(obj) ? [...obj] : { ...obj } as Record<string, unknown>;\n\n if (rest.length === 0) {\n (result as Record<string, unknown>)[firstKey] = value;\n } else {\n const currentValue = (result as Record<string, unknown>)[firstKey];\n (result as Record<string, unknown>)[firstKey] = assocPath(rest, value, currentValue ?? {});\n }\n\n return result as T;\n}\n\nexport function batchingUpdate<T extends Record<string, unknown>>(\n state: T,\n valueList: CdeebeeValueList<T>\n): void {\n for (let i = 0; i < valueList.length; i++) {\n const item = valueList[i] as { key: readonly (string | number)[]; value: unknown };\n const path = item.key;\n const value = item.value;\n \n if (path.length === 0) {\n continue;\n }\n\n let current: Record<string, unknown> | unknown[] = state as Record<string, unknown>;\n \n for (let j = 0; j < path.length - 1; j++) {\n const pathKey = path[j];\n \n if (Array.isArray(current)) {\n const index = typeof pathKey === 'number' ? pathKey : Number(pathKey);\n if (!(index in current) || !isRecord(current[index])) {\n current[index] = {};\n }\n current = current[index] as Record<string, unknown>;\n } else {\n const key = String(pathKey);\n if (!(key in current)) {\n const nextIsNumeric = typeof path[j + 1] === 'number' || (!isNaN(Number(path[j + 1])) && String(Number(path[j + 1])) === String(path[j + 1]));\n current[key] = nextIsNumeric ? [] : {};\n }\n const next = current[key];\n current = (Array.isArray(next) ? next : (isRecord(next) ? next : {})) as Record<string, unknown> | unknown[];\n }\n }\n \n if (Array.isArray(current)) {\n continue; // Can't update array element directly\n }\n current[String(path[path.length - 1])] = value;\n }\n}\n","interface RequestController {\n requestId: string;\n controller: AbortController;\n api: string;\n}\n\nclass AbortControllerStore {\n private byRequestId = new Map<string, RequestController>();\n private byApi = new Map<string, Set<string>>();\n\n add(api: string, requestId: string, controller: AbortController): void {\n const item: RequestController = { requestId, controller, api };\n this.byRequestId.set(requestId, item);\n\n if (!this.byApi.has(api)) {\n this.byApi.set(api, new Set());\n }\n this.byApi.get(api)!.add(requestId);\n }\n\n delete(requestId: string): void {\n const item = this.byRequestId.get(requestId);\n if (!item) return;\n\n this.byRequestId.delete(requestId);\n const apiSet = this.byApi.get(item.api);\n if (apiSet) {\n apiSet.delete(requestId);\n if (apiSet.size === 0) {\n this.byApi.delete(item.api);\n }\n }\n }\n\n abortAllForApi(api: string, excludeRequestId: string): void {\n const requestIds = this.byApi.get(api);\n if (!requestIds) return;\n\n requestIds.forEach(requestId => {\n if (requestId !== excludeRequestId) {\n const item = this.byRequestId.get(requestId);\n if (item) {\n item.controller.abort();\n this.delete(requestId);\n }\n }\n });\n }\n}\n\nconst abortStore = new AbortControllerStore();\n\nexport function abortQuery(api: string, currentRequestId: string): void {\n abortStore.abortAllForApi(api, currentRequestId);\n}\n\nexport function abortManager(signal: AbortSignal, api: string, requestId: string) {\n const controller = new AbortController();\n\n const cleanup = () => {\n abortStore.delete(requestId);\n };\n\n signal.addEventListener('abort', () => {\n controller.abort();\n cleanup();\n });\n\n return {\n controller,\n init: () => abortStore.add(api, requestId, controller),\n drop: cleanup,\n };\n}\n","import { createAsyncThunk } from '@reduxjs/toolkit';\nimport { checkModule } from './helpers';\nimport { abortManager } from './abortController';\nimport { type CdeebeeState, type CdeebeeRequestOptions } from './types';\n\nexport const request = createAsyncThunk(\n 'cdeebee/request',\n async (options: CdeebeeRequestOptions<unknown>, { rejectWithValue, getState, requestId, signal }) => {\n const startedAt = new Date().toUTCString();\n const { cdeebee: { settings } } = getState() as { cdeebee: CdeebeeState<unknown> };\n\n const abort = abortManager(signal, options.api, requestId);\n\n checkModule(settings, 'cancelation', abort.init);\n\n try {\n const { method = 'POST', body, headers = {} } = options;\n const extraHeaders: Record<string, string> = { ...(settings.mergeWithHeaders ?? {}), ...headers };\n\n const b = { ...(settings.mergeWithData ?? {}), ...(body ?? {}) };\n let requestData: FormData | string = JSON.stringify(b);\n\n // handling files\n if (options.files) {\n const formData = new FormData();\n const fileKey = options.fileKey || settings.fileKey;\n const bodyKey = options.bodyKey || settings.bodyKey;\n\n for (let i = 0; i < options.files.length; i += 1) {\n if (fileKey) {\n formData.append(fileKey, options.files[i]);\n }\n }\n\n if (bodyKey) {\n formData.append(bodyKey, requestData);\n }\n requestData = formData;\n }\n // [end] handling files\n \n const response = await fetch(options.api, {\n method,\n headers: {\n 'ui-request-id': requestId,\n 'Content-Type': 'application/json',\n ...extraHeaders,\n },\n signal: abort.controller.signal,\n body: requestData,\n });\n\n checkModule(settings, 'cancelation', abort.drop);\n\n if (!response.ok) {\n return rejectWithValue(response);\n }\n const result = await response.json();\n if (options.onResult && typeof options.onResult === 'function') {\n options.onResult(result);\n }\n return { result, startedAt, endedAt: new Date().toUTCString() };\n } catch (error) {\n checkModule(settings, 'cancelation', abort.drop);\n if (error instanceof Error && error.name === 'AbortError') {\n return rejectWithValue({\n message: 'Request was cancelled',\n cancelled: true,\n });\n }\n return rejectWithValue({\n message: error instanceof Error ? error.message : 'Unknown error occurred',\n });\n }\n },\n);\n\n","import { type CdeebeeListStrategy, type CdeebeeState } from './types';\nimport { hasDataProperty, hasProperty, isRecord, mergeDeepRight, omit } from './helpers';\n\ntype ResponseValue = Record<string, unknown> & {\n data?: unknown[];\n [key: string]: unknown;\n};\n\ntype IResponse = Record<string, ResponseValue>;\n\ntype StorageData = Record<string, unknown>;\n\nexport function defaultNormalize<T>(\n cdeebee: CdeebeeState<T>,\n response: IResponse,\n strategyList: CdeebeeListStrategy<T> \n): Record<string, ResponseValue> {\n const keyList = Object.keys(response);\n const primaryKey = cdeebee.settings.primaryKey;\n const currentStorage = isRecord(cdeebee.storage) ? (cdeebee.storage as Record<string, unknown>) : {};\n \n // Start with existing storage to preserve keys not in response\n const result = { ...currentStorage } as Record<string, ResponseValue>;\n const keyListToOmit = new Set<string>();\n\n for (const key of keyList) {\n const responseValue = response[key];\n\n if (responseValue === null || responseValue === undefined || typeof responseValue === 'string') {\n keyListToOmit.add(key);\n continue;\n }\n\n if (hasDataProperty(responseValue) && hasProperty(responseValue, primaryKey)) {\n const primaryKeyValue = responseValue[primaryKey];\n \n if (typeof primaryKeyValue !== 'string') {\n console.warn(`Cdeebee: Primary key \"${primaryKey}\" is not a string for API \"${key}\". Skipping normalization.`);\n result[key] = responseValue;\n continue;\n }\n\n // Pre-allocate storage data object\n const newStorageData: StorageData = {};\n const dataArray = responseValue.data;\n const dataLength = dataArray.length;\n\n for (let i = 0; i < dataLength; i++) {\n const element = dataArray[i];\n if (isRecord(element) && element[primaryKeyValue]) {\n const elementKey = element[primaryKeyValue] as string;\n newStorageData[elementKey] = element;\n }\n }\n\n const strategy = strategyList[key as keyof T] ?? 'merge';\n const existingValue = key in currentStorage ? (currentStorage[key] as StorageData) : {};\n\n if (strategy === 'replace') {\n // Replace: completely replace the value\n result[key] = newStorageData as ResponseValue;\n } else if (strategy === 'merge') {\n // Merge: merge with existing value\n result[key] = mergeDeepRight(existingValue, newStorageData) as ResponseValue;\n } else {\n // Unknown strategy: warn and fall back to merge\n console.warn(`Cdeebee: Unknown strategy \"${strategy}\" for key \"${key}\". Skipping normalization.`);\n result[key] = mergeDeepRight(existingValue, newStorageData) as ResponseValue;\n }\n } else {\n result[key] = responseValue;\n }\n }\n\n return keyListToOmit.size > 0 ? omit(Array.from(keyListToOmit), result) : result;\n}\n","import { createSlice, current } from '@reduxjs/toolkit';\n\nimport { type CdeebeeSettings, type CdeebeeState, type CdeebeeValueList } from './types';\nimport { checkModule, mergeDeepRight, batchingUpdate } from './helpers';\nimport { abortQuery } from './abortController';\nimport { request } from './request';\nimport { defaultNormalize } from './storage';\n\nconst initialState: CdeebeeState<unknown> = {\n settings: {\n modules: ['history', 'listener', 'storage', 'cancelation'],\n fileKey: 'file',\n bodyKey: 'value',\n primaryKey: 'primaryKey',\n listStrategy: {},\n mergeWithData: {},\n mergeWithHeaders: {},\n },\n storage: {},\n request: {\n active: [],\n errors: {},\n done: {}\n },\n};\n\nexport const factory = <T>(settings: CdeebeeSettings<T>, storage?: T) => {\n const slice = createSlice({\n name: 'cdeebee',\n initialState: mergeDeepRight(initialState, { settings, storage: storage ?? {} }) as CdeebeeState<T>,\n reducers: {\n set(state, action: { payload: CdeebeeValueList<T> }) {\n // Directly mutate state.storage using Immer Draft\n // This is more performant than creating a new object\n // Immer will track changes and create minimal updates\n batchingUpdate(state.storage as Record<string, unknown>, action.payload);\n }\n },\n extraReducers: builder => {\n builder\n .addCase(request.pending, (state, action) => {\n const api = action.meta.arg.api;\n const requestId = action.meta.requestId;\n\n checkModule(state.settings, 'cancelation', () => {\n abortQuery(api, requestId);\n });\n checkModule(state.settings, 'listener', () => {\n state.request.active.push({ api, requestId });\n });\n })\n .addCase(request.fulfilled, (state, action) => {\n const requestId = action.meta.requestId;\n const api = action.meta.arg.api;\n\n checkModule(state.settings, 'listener', () => {\n state.request.active = state.request.active.filter(q => !(q.api === api && q.requestId === requestId));\n });\n checkModule(state.settings, 'history', () => {\n if (!state.request.done[api]) state.request.done[api] = [];\n state.request.done[api].push({ api, request: action.payload, requestId });\n });\n checkModule(state.settings, 'storage', () => {\n const strategyList = action.meta.arg.listStrategy ?? state.settings.listStrategy ?? {};\n const normalize = action.meta.arg.normalize ?? state.settings.normalize ?? defaultNormalize;\n\n const currentState = current(state) as CdeebeeState<T>;\n const normalizedData = normalize(currentState, action.payload.result, strategyList);\n\n // Normalize already handles merge/replace and preserves keys not in response\n // Simply apply the result\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n (state.storage as any) = normalizedData;\n });\n })\n .addCase(request.rejected, (state, action) => {\n const requestId = action.meta.requestId;\n const api = action.meta.arg.api;\n \n checkModule(state.settings, 'listener', () => {\n state.request.active = state.request.active.filter(q => !(q.api === api && q.requestId === requestId));\n });\n checkModule(state.settings, 'history', () => {\n if (!state.request.errors[api]) state.request.errors[api] = [];\n state.request.errors[api].push({ requestId: requestId, api, request: action.error });\n });\n });\n },\n });\n\n return slice;\n};\n"],"names":["checkModule","settings","module","result","isRecord","value","hasDataProperty","hasProperty","prop","mergeDeepRight","left","right","rightRecord","key","leftValue","rightValue","omit","keys","obj","batchingUpdate","state","valueList","item","path","current","j","pathKey","index","nextIsNumeric","next","AbortControllerStore","api","requestId","controller","apiSet","excludeRequestId","requestIds","abortStore","abortQuery","currentRequestId","abortManager","signal","cleanup","request","createAsyncThunk","options","rejectWithValue","getState","startedAt","abort","method","body","headers","extraHeaders","b","requestData","formData","fileKey","bodyKey","i","response","error","defaultNormalize","cdeebee","strategyList","keyList","primaryKey","currentStorage","keyListToOmit","responseValue","primaryKeyValue","newStorageData","dataArray","dataLength","element","elementKey","strategy","existingValue","initialState","factory","storage","createSlice","action","builder","q","normalize","currentState","normalizedData"],"mappings":"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"}
|