@recats/cdeebee 3.0.0-beta.8 → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +297 -27
- package/dist/index.cjs +2 -2
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +196 -10
- package/dist/index.js +347 -234
- package/dist/index.js.map +1 -1
- package/package.json +17 -10
package/README.md
CHANGED
|
@@ -5,11 +5,11 @@ A Redux-based data management library that provides a uniform way to access, fet
|
|
|
5
5
|
## Installation
|
|
6
6
|
|
|
7
7
|
```sh
|
|
8
|
-
npm i @recats/cdeebee
|
|
8
|
+
npm i @recats/cdeebee
|
|
9
9
|
# or
|
|
10
|
-
yarn add @recats/cdeebee
|
|
10
|
+
yarn add @recats/cdeebee
|
|
11
11
|
# or
|
|
12
|
-
pnpm add @recats/cdeebee
|
|
12
|
+
pnpm add @recats/cdeebee
|
|
13
13
|
```
|
|
14
14
|
|
|
15
15
|
## What is cdeebee?
|
|
@@ -40,14 +40,14 @@ After fetching data from the API (which returns data in the format `{ data: [...
|
|
|
40
40
|
|
|
41
41
|
```typescript
|
|
42
42
|
{
|
|
43
|
-
forumList: {
|
|
44
|
-
1: { id: 1, title: 'Milky Way Galaxy' }
|
|
43
|
+
forumList: {
|
|
44
|
+
1: { id: 1, title: 'Milky Way Galaxy' }
|
|
45
45
|
},
|
|
46
|
-
threadList: {
|
|
47
|
-
10001: { id: 10001, title: 'Solar system', forumID: 1 }
|
|
46
|
+
threadList: {
|
|
47
|
+
10001: { id: 10001, title: 'Solar system', forumID: 1 }
|
|
48
48
|
},
|
|
49
|
-
postList: {
|
|
50
|
-
2: { id: 2, title: 'Earth', threadID: 10001 }
|
|
49
|
+
postList: {
|
|
50
|
+
2: { id: 2, title: 'Earth', threadID: 10001 }
|
|
51
51
|
}
|
|
52
52
|
}
|
|
53
53
|
```
|
|
@@ -88,7 +88,9 @@ export const cdeebeeSlice = factory<Storage>(
|
|
|
88
88
|
listStrategy: {
|
|
89
89
|
forumList: 'merge',
|
|
90
90
|
threadList: 'replace',
|
|
91
|
+
postList: 'merge',
|
|
91
92
|
},
|
|
93
|
+
maxHistorySize: 50,
|
|
92
94
|
mergeWithData: {
|
|
93
95
|
sessionToken: 'your-session-token',
|
|
94
96
|
},
|
|
@@ -138,15 +140,19 @@ function MyComponent() {
|
|
|
138
140
|
}
|
|
139
141
|
```
|
|
140
142
|
|
|
141
|
-
### 3. Access Data and Loading States
|
|
143
|
+
### 3. Access Data and Loading States with Hooks
|
|
144
|
+
|
|
145
|
+
cdeebee provides React hooks to easily access data and track loading states:
|
|
142
146
|
|
|
143
147
|
```typescript
|
|
144
|
-
import {
|
|
148
|
+
import { useLoading, useStorageList } from '@recats/cdeebee';
|
|
145
149
|
|
|
146
150
|
function ForumsList() {
|
|
147
|
-
|
|
148
|
-
const
|
|
149
|
-
|
|
151
|
+
// Check if specific APIs are loading
|
|
152
|
+
const isLoading = useLoading(['/api/forums']);
|
|
153
|
+
|
|
154
|
+
// Get data from storage with full type safety
|
|
155
|
+
const forums = useStorageList<Storage, 'forumList'>('forumList');
|
|
150
156
|
|
|
151
157
|
return (
|
|
152
158
|
<div>
|
|
@@ -159,6 +165,18 @@ function ForumsList() {
|
|
|
159
165
|
}
|
|
160
166
|
```
|
|
161
167
|
|
|
168
|
+
**Available hooks:**
|
|
169
|
+
|
|
170
|
+
- `useLoading(apiList: string[])` - Check if any of the specified APIs are loading
|
|
171
|
+
- `useIsLoading()` - Check if any request is loading
|
|
172
|
+
- `useStorageList(listName)` - Get a specific list from storage
|
|
173
|
+
- `useStorage()` - Get the entire storage
|
|
174
|
+
- `useRequestHistory(api)` - Get successful request history for an API
|
|
175
|
+
- `useRequestErrors(api)` - Get error history for an API
|
|
176
|
+
- `useLastResultIdList(api, listName)` - Get the IDs returned by the last request for a specific list (for filtering storage)
|
|
177
|
+
|
|
178
|
+
See the [React Hooks](#react-hooks) section for detailed documentation.
|
|
179
|
+
|
|
162
180
|
## Configuration
|
|
163
181
|
|
|
164
182
|
### Settings
|
|
@@ -170,10 +188,11 @@ interface CdeebeeSettings<T> {
|
|
|
170
188
|
modules: CdeebeeModule[]; // Active modules: 'history' | 'listener' | 'storage' | 'cancelation' | 'queryQueue'
|
|
171
189
|
fileKey: string; // Key name for file uploads in FormData
|
|
172
190
|
bodyKey: string; // Key name for request body in FormData
|
|
173
|
-
listStrategy?: CdeebeeListStrategy<T>; // Merge strategy per list: 'merge' | 'replace'
|
|
174
|
-
mergeWithData?: unknown;
|
|
175
|
-
mergeWithHeaders?: Record<string, string
|
|
176
|
-
normalize?: (storage
|
|
191
|
+
listStrategy?: CdeebeeListStrategy<T>; // Merge strategy per list: 'merge' | 'replace' | 'skip'
|
|
192
|
+
mergeWithData?: Record<string, unknown> | (() => Record<string, unknown>); // Data to merge with every request body (static or dynamic)
|
|
193
|
+
mergeWithHeaders?: Record<string, string> | (() => Record<string, string>); // Headers to merge with every request (static or dynamic)
|
|
194
|
+
normalize?: (storage: CdeebeeState<T>, result: unknown, strategyList: CdeebeeListStrategy<T>) => Record<string, unknown>; // Custom normalization function
|
|
195
|
+
maxHistorySize?: number; // Max history entries per API (prevents unbounded growth)
|
|
177
196
|
}
|
|
178
197
|
```
|
|
179
198
|
|
|
@@ -183,33 +202,64 @@ interface CdeebeeSettings<T> {
|
|
|
183
202
|
interface CdeebeeRequestOptions<T> {
|
|
184
203
|
api: string; // API endpoint URL
|
|
185
204
|
method?: 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH';
|
|
186
|
-
body?: unknown; // Request body
|
|
205
|
+
body?: unknown; // Request body (omitted for GET requests)
|
|
187
206
|
headers?: Record<string, string>; // Additional headers (merged with mergeWithHeaders)
|
|
188
|
-
files?: File[]; // Files to upload
|
|
207
|
+
files?: File[]; // Files to upload (Content-Type is set automatically)
|
|
189
208
|
fileKey?: string; // Override default fileKey
|
|
190
209
|
bodyKey?: string; // Override default bodyKey
|
|
191
|
-
listStrategy?: CdeebeeListStrategy<T
|
|
192
|
-
normalize?: (storage
|
|
210
|
+
listStrategy?: Partial<CdeebeeListStrategy<T>>; // Override list strategy for this request (partial)
|
|
211
|
+
normalize?: (storage: CdeebeeState<T>, result: unknown, strategyList: CdeebeeListStrategy<T>) => Record<string, unknown>; // Override normalization
|
|
193
212
|
onResult?: (response: T) => void; // Callback called with response data (always called, even on errors)
|
|
194
213
|
ignore?: boolean; // Skip storing result in storage
|
|
195
214
|
responseType?: 'json' | 'text' | 'blob'; // Response parsing type (default: 'json')
|
|
215
|
+
historyClear?: boolean; // Auto-clear history for this API before making the request
|
|
196
216
|
}
|
|
197
217
|
```
|
|
198
218
|
|
|
219
|
+
**Error handling:** When a request fails (non-2xx response), the rejected action payload contains `{ status, statusText, data }` where `data` is the parsed response body.
|
|
220
|
+
|
|
199
221
|
## Data Merging Strategies
|
|
200
222
|
|
|
201
|
-
cdeebee supports
|
|
223
|
+
cdeebee supports three strategies for merging data:
|
|
202
224
|
|
|
203
225
|
- **`merge`**: Merges new data with existing data, preserving existing keys not in the response
|
|
204
226
|
- **`replace`**: Completely replaces the list with new data
|
|
227
|
+
- **`skip`**: Skips updating the list entirely, preserving existing data unchanged
|
|
205
228
|
|
|
206
229
|
```typescript
|
|
207
230
|
listStrategy: {
|
|
208
231
|
forumList: 'merge', // New forums are merged with existing ones
|
|
209
232
|
threadList: 'replace', // Thread list is completely replaced
|
|
233
|
+
userList: 'skip', // User list is never updated, existing data is preserved
|
|
210
234
|
}
|
|
211
235
|
```
|
|
212
236
|
|
|
237
|
+
## Dynamic Headers and Data
|
|
238
|
+
|
|
239
|
+
`mergeWithHeaders` and `mergeWithData` support both static objects and dynamic functions. Functions are called on each request, making them ideal for auth tokens:
|
|
240
|
+
|
|
241
|
+
```typescript
|
|
242
|
+
const cdeebeeSlice = factory<Storage>({
|
|
243
|
+
modules: ['storage', 'history', 'listener'],
|
|
244
|
+
|
|
245
|
+
// Static headers (evaluated once at factory creation)
|
|
246
|
+
mergeWithHeaders: { 'X-App': 'myapp' },
|
|
247
|
+
|
|
248
|
+
// OR Dynamic headers (evaluated on each request)
|
|
249
|
+
mergeWithHeaders: () => ({
|
|
250
|
+
'Authorization': `Bearer ${getSessionToken()}`,
|
|
251
|
+
}),
|
|
252
|
+
|
|
253
|
+
// Same for mergeWithData
|
|
254
|
+
mergeWithData: () => ({
|
|
255
|
+
timestamp: Date.now(),
|
|
256
|
+
clientVersion: APP_VERSION,
|
|
257
|
+
}),
|
|
258
|
+
});
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
**Note**: When using functions, Redux will warn about non-serializable values in state. Configure your store's `serializableCheck.ignoredPaths` to include `cdeebee.settings.mergeWithHeaders` and `cdeebee.settings.mergeWithData`.
|
|
262
|
+
|
|
213
263
|
## API Response Format
|
|
214
264
|
|
|
215
265
|
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.
|
|
@@ -453,15 +503,221 @@ dispatch(cdeebeeSlice.actions.set(updates));
|
|
|
453
503
|
|
|
454
504
|
### Accessing Request History
|
|
455
505
|
|
|
506
|
+
You can access request history using hooks or selectors:
|
|
507
|
+
|
|
456
508
|
```typescript
|
|
509
|
+
import { useRequestHistory, useRequestErrors } from '@recats/cdeebee';
|
|
510
|
+
|
|
511
|
+
// Using hooks (recommended)
|
|
512
|
+
const apiHistory = useRequestHistory('/api/forums');
|
|
513
|
+
const apiErrors = useRequestErrors('/api/forums');
|
|
514
|
+
|
|
515
|
+
// Or using selectors
|
|
457
516
|
const doneRequests = useAppSelector(state => state.cdeebee.request.done);
|
|
458
517
|
const errors = useAppSelector(state => state.cdeebee.request.errors);
|
|
518
|
+
```
|
|
519
|
+
|
|
520
|
+
### Clearing Request History
|
|
521
|
+
|
|
522
|
+
Clear old success/error history when needed (useful for forms that get reopened):
|
|
523
|
+
|
|
524
|
+
```typescript
|
|
525
|
+
// Automatic: clear before request
|
|
526
|
+
dispatch(request({
|
|
527
|
+
api: '/api/posts',
|
|
528
|
+
historyClear: true, // Clears old history for this API
|
|
529
|
+
body: formData,
|
|
530
|
+
}));
|
|
531
|
+
|
|
532
|
+
// Manual: clear anytime
|
|
533
|
+
dispatch(cdeebeeSlice.actions.historyClear('/api/posts')); // Specific API
|
|
534
|
+
dispatch(cdeebeeSlice.actions.historyClear()); // All APIs
|
|
535
|
+
```
|
|
536
|
+
|
|
537
|
+
## React Hooks
|
|
538
|
+
|
|
539
|
+
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`).
|
|
459
540
|
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
541
|
+
### Loading State Hooks
|
|
542
|
+
|
|
543
|
+
#### `useLoading(apiList: string[])`
|
|
544
|
+
|
|
545
|
+
Check if any of the specified APIs are currently loading.
|
|
546
|
+
|
|
547
|
+
```typescript
|
|
548
|
+
import { useLoading } from '@recats/cdeebee';
|
|
549
|
+
|
|
550
|
+
function MyComponent() {
|
|
551
|
+
// Check if any of these APIs are loading
|
|
552
|
+
const isLoading = useLoading(['/api/forums', '/api/threads']);
|
|
553
|
+
|
|
554
|
+
if (isLoading) return <Spinner />;
|
|
555
|
+
|
|
556
|
+
return <div>Content</div>;
|
|
557
|
+
}
|
|
558
|
+
```
|
|
559
|
+
|
|
560
|
+
#### `useIsLoading()`
|
|
561
|
+
|
|
562
|
+
Check if any request is currently loading across all APIs.
|
|
563
|
+
|
|
564
|
+
```typescript
|
|
565
|
+
import { useIsLoading } from '@recats/cdeebee';
|
|
566
|
+
|
|
567
|
+
function GlobalSpinner() {
|
|
568
|
+
const isAnythingLoading = useIsLoading();
|
|
569
|
+
|
|
570
|
+
return isAnythingLoading ? <GlobalSpinner /> : null;
|
|
571
|
+
}
|
|
463
572
|
```
|
|
464
573
|
|
|
574
|
+
### Storage Hooks
|
|
575
|
+
|
|
576
|
+
#### `useStorageList<Storage, K>(listName: K)`
|
|
577
|
+
|
|
578
|
+
Get a specific list from storage with full type safety.
|
|
579
|
+
|
|
580
|
+
```typescript
|
|
581
|
+
import { useStorageList } from '@recats/cdeebee';
|
|
582
|
+
|
|
583
|
+
interface MyStorage {
|
|
584
|
+
forumList: Record<string, { id: string; title: string }>;
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
function ForumsList() {
|
|
588
|
+
const forums = useStorageList<MyStorage, 'forumList'>('forumList');
|
|
589
|
+
|
|
590
|
+
return (
|
|
591
|
+
<div>
|
|
592
|
+
{Object.values(forums).map(forum => (
|
|
593
|
+
<div key={forum.id}>{forum.title}</div>
|
|
594
|
+
))}
|
|
595
|
+
</div>
|
|
596
|
+
);
|
|
597
|
+
}
|
|
598
|
+
```
|
|
599
|
+
|
|
600
|
+
#### `useStorage<Storage>()`
|
|
601
|
+
|
|
602
|
+
Get the entire cdeebee storage.
|
|
603
|
+
|
|
604
|
+
```typescript
|
|
605
|
+
import { useStorage } from '@recats/cdeebee';
|
|
606
|
+
|
|
607
|
+
interface MyStorage {
|
|
608
|
+
forumList: Record<string, Forum>;
|
|
609
|
+
threadList: Record<string, Thread>;
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
function DataDebug() {
|
|
613
|
+
const storage = useStorage<MyStorage>();
|
|
614
|
+
|
|
615
|
+
return <pre>{JSON.stringify(storage, null, 2)}</pre>;
|
|
616
|
+
}
|
|
617
|
+
```
|
|
618
|
+
|
|
619
|
+
### History Hooks
|
|
620
|
+
|
|
621
|
+
#### `useRequestHistory(api: string)`
|
|
622
|
+
|
|
623
|
+
Get successful request history for a specific API endpoint.
|
|
624
|
+
|
|
625
|
+
```typescript
|
|
626
|
+
import { useRequestHistory } from '@recats/cdeebee';
|
|
627
|
+
|
|
628
|
+
function RequestStats({ api }: { api: string }) {
|
|
629
|
+
const history = useRequestHistory(api);
|
|
630
|
+
|
|
631
|
+
return (
|
|
632
|
+
<div>
|
|
633
|
+
Total successful requests to {api}: {history.length}
|
|
634
|
+
</div>
|
|
635
|
+
);
|
|
636
|
+
}
|
|
637
|
+
```
|
|
638
|
+
|
|
639
|
+
#### `useRequestErrors(api: string)`
|
|
640
|
+
|
|
641
|
+
Get error history for a specific API endpoint.
|
|
642
|
+
|
|
643
|
+
```typescript
|
|
644
|
+
import { useRequestErrors } from '@recats/cdeebee';
|
|
645
|
+
|
|
646
|
+
function ErrorDisplay({ api }: { api: string }) {
|
|
647
|
+
const errors = useRequestErrors(api);
|
|
648
|
+
|
|
649
|
+
if (errors.length === 0) return null;
|
|
650
|
+
|
|
651
|
+
const lastError = errors[errors.length - 1];
|
|
652
|
+
return <div className="error">Last error: {lastError.request.message}</div>;
|
|
653
|
+
}
|
|
654
|
+
```
|
|
655
|
+
|
|
656
|
+
### Result ID List Hook
|
|
657
|
+
|
|
658
|
+
#### `useLastResultIdList(api: string, listName: string)`
|
|
659
|
+
|
|
660
|
+
Get the list of IDs returned by the last successful request to an API for a specific list. This is useful for filtering storage data when using `merge` strategy, so you can display only the results from the current search/request.
|
|
661
|
+
|
|
662
|
+
```typescript
|
|
663
|
+
import { useStorageList, useLastResultIdList } from '@recats/cdeebee';
|
|
664
|
+
|
|
665
|
+
interface MyStorage {
|
|
666
|
+
productList: Record<string, { id: string; name: string; price: number }>;
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
function SearchResults() {
|
|
670
|
+
// Get all products from storage (accumulated via merge strategy)
|
|
671
|
+
const products = useStorageList<MyStorage, 'productList'>('productList');
|
|
672
|
+
|
|
673
|
+
// Get only the IDs from the last search request for productList
|
|
674
|
+
const lastSearchIDList = useLastResultIdList('/api/search', 'productList');
|
|
675
|
+
|
|
676
|
+
// Filter to show only results from current search
|
|
677
|
+
const displayResults = lastSearchIDList
|
|
678
|
+
.map(id => products[id])
|
|
679
|
+
.filter(Boolean);
|
|
680
|
+
|
|
681
|
+
return (
|
|
682
|
+
<div>
|
|
683
|
+
{displayResults.map(product => (
|
|
684
|
+
<div key={product.id}>{product.name} - ${product.price}</div>
|
|
685
|
+
))}
|
|
686
|
+
</div>
|
|
687
|
+
);
|
|
688
|
+
}
|
|
689
|
+
```
|
|
690
|
+
|
|
691
|
+
**Why use this?** When using `replace` strategy with search/filter pages, navigating away and using browser back loses the previous results. With `merge` strategy + `useLastResultIdList`:
|
|
692
|
+
- Data accumulates in storage (never lost on navigation)
|
|
693
|
+
- `lastResultIdList[api][listName]` tracks which IDs belong to the current request per list
|
|
694
|
+
- Filter storage by those IDs to display the correct results
|
|
695
|
+
|
|
696
|
+
### Advanced: Custom State Path
|
|
697
|
+
|
|
698
|
+
If you're **not** using `combineSlices` or have cdeebee at a custom path in your state (not `state.cdeebee`), use `createCdeebeeHooks`:
|
|
699
|
+
|
|
700
|
+
```typescript
|
|
701
|
+
// hooks.ts - Create once in your app
|
|
702
|
+
import { createCdeebeeHooks } from '@recats/cdeebee';
|
|
703
|
+
import type { RootState, MyStorage } from './store';
|
|
704
|
+
|
|
705
|
+
// Tell the hooks where to find cdeebee in your state
|
|
706
|
+
export const {
|
|
707
|
+
useLoading,
|
|
708
|
+
useStorageList,
|
|
709
|
+
useStorage,
|
|
710
|
+
useRequestHistory,
|
|
711
|
+
useRequestErrors,
|
|
712
|
+
useIsLoading,
|
|
713
|
+
useLastResultIdList,
|
|
714
|
+
} = createCdeebeeHooks<RootState, MyStorage>(
|
|
715
|
+
state => state.myCustomPath // Your custom path
|
|
716
|
+
);
|
|
717
|
+
```
|
|
718
|
+
|
|
719
|
+
**Note:** Most users won't need `createCdeebeeHooks` because `combineSlices` automatically places the slice at `state.cdeebee`.
|
|
720
|
+
|
|
465
721
|
## TypeScript Support
|
|
466
722
|
|
|
467
723
|
cdeebee is fully typed. Define your storage type and get full type safety:
|
|
@@ -486,14 +742,28 @@ const users = useSelector(state => state.cdeebee.storage.userList);
|
|
|
486
742
|
export { factory } from '@recats/cdeebee'; // Create cdeebee slice
|
|
487
743
|
export { request } from '@recats/cdeebee'; // Request thunk
|
|
488
744
|
export { batchingUpdate } from '@recats/cdeebee'; // Batch update helper
|
|
745
|
+
export { defaultNormalize } from '@recats/cdeebee'; // Default normalization function
|
|
746
|
+
|
|
747
|
+
// React hooks
|
|
748
|
+
export {
|
|
749
|
+
createCdeebeeHooks, // Hook factory for custom state paths
|
|
750
|
+
useLoading, // Check if APIs are loading
|
|
751
|
+
useIsLoading, // Check if any request is loading
|
|
752
|
+
useStorageList, // Get a list from storage
|
|
753
|
+
useStorage, // Get entire storage
|
|
754
|
+
useRequestHistory, // Get successful request history
|
|
755
|
+
useRequestErrors, // Get error history
|
|
756
|
+
useLastResultIdList, // Get IDs from last request for a list (for filtering storage)
|
|
757
|
+
} from '@recats/cdeebee';
|
|
489
758
|
|
|
490
759
|
// Types
|
|
491
760
|
export type {
|
|
492
761
|
CdeebeeState,
|
|
493
|
-
CdeebeeSettings,
|
|
494
762
|
CdeebeeRequestOptions,
|
|
495
763
|
CdeebeeValueList,
|
|
764
|
+
CdeebeeListStrategy,
|
|
496
765
|
CdeebeeActiveRequest,
|
|
766
|
+
CdeebeeHistoryState,
|
|
497
767
|
CdeebeeModule,
|
|
498
768
|
} from '@recats/cdeebee';
|
|
499
769
|
```
|
package/dist/index.cjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
|
|
2
|
-
//# sourceMappingURL=index.cjs.map
|
|
1
|
+
Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});let e=require(`@reduxjs/toolkit`),t=require(`react-redux`);function n(e,t,n){e.modules.includes(t)&&n()}function r(e){return typeof e==`object`&&!!e&&!Array.isArray(e)}function i(e,t){if(!r(e)||!r(t))return t;let n={...e},a=t;for(let e in a)if(Object.prototype.hasOwnProperty.call(a,e)){let t=n[e],o=a[e];r(t)&&r(o)&&!Array.isArray(t)&&!Array.isArray(o)?n[e]=i(t,o):n[e]=o}return n}function a(e,t){let n={...t};for(let t of e)delete n[t];return n}function o(e){if(!r(e))return{};let t={};for(let n of Object.keys(e)){let i=e[n];if(r(i)&&Array.isArray(i.data)&&typeof i.primaryKey==`string`){let e=i.primaryKey,a=[];for(let t of i.data)r(t)&&e in t&&a.push(String(t[e]));t[n]=a}}return t}function s(e,t){for(let n=0;n<t.length;n++){let i=t[n],a=i.key,o=i.value;if(a.length===0)continue;let s=e;for(let e=0;e<a.length-1;e++){let t=a[e];if(Array.isArray(s)){let e=typeof t==`number`?t:Number(t);(!(e in s)||!r(s[e]))&&(s[e]={}),s=s[e]}else{let n=String(t);if(!(n in s)){let t=typeof a[e+1]==`number`||!isNaN(Number(a[e+1]))&&String(Number(a[e+1]))===String(a[e+1]);s[n]=t?[]:{}}let i=s[n];s=Array.isArray(i)||r(i)?i:{}}}let c=a[a.length-1];if(Array.isArray(s)){let e=typeof c==`number`?c:Number(c);s[e]=o}else s[String(c)]=o}}var c=new class{constructor(){this.byRequestId=new Map,this.byApi=new Map}add(e,t,n){let r={requestId:t,controller:n,api:e};this.byRequestId.set(t,r),this.byApi.has(e)||this.byApi.set(e,new Set),this.byApi.get(e).add(t)}delete(e){let t=this.byRequestId.get(e);if(!t)return;this.byRequestId.delete(e);let n=this.byApi.get(t.api);n&&(n.delete(e),n.size===0&&this.byApi.delete(t.api))}abortAllForApi(e,t){let n=this.byApi.get(e);if(!n)return;let r=Array.from(n).filter(e=>e!==t);for(let e of r){let t=this.byRequestId.get(e);t&&(t.controller.abort(),this.delete(e))}}};function l(e,t){c.abortAllForApi(e,t)}function u(e,t,n){let r=new AbortController,i=()=>{c.delete(n)};return e.addEventListener(`abort`,()=>{r.abort(),i()}),{controller:r,init:()=>c.add(t,n,r),drop:i}}var d=new class{constructor(){this.currentPromise=Promise.resolve(),this.queueLength=0}async enqueue(e){return this.queueLength++,this.currentPromise=this.currentPromise.then(()=>e(),()=>e()).finally(()=>{this.queueLength--}),this.currentPromise}getQueueLength(){return this.queueLength}clear(){this.queueLength=0,this.currentPromise=Promise.resolve()}},f=(0,e.createAsyncThunk)(`cdeebee/request`,async(e,{rejectWithValue:t,getState:r,requestId:i,signal:a})=>{let o=new Date().toUTCString(),{cdeebee:{settings:s}}=r(),c=u(a,e.api,i),l=e.onResult&&typeof e.onResult==`function`;n(s,`cancelation`,c.init);let f=async()=>{try{let{method:r=`POST`,body:a,headers:u={}}=e,d={...typeof s.mergeWithHeaders==`function`?s.mergeWithHeaders():s.mergeWithHeaders??{},...u},f={...typeof s.mergeWithData==`function`?s.mergeWithData():s.mergeWithData??{},...a??{}},p=JSON.stringify(f),m=!!e.files;if(e.files){let t=new FormData,n=e.fileKey||s.fileKey,r=e.bodyKey||s.bodyKey;for(let r=0;r<e.files.length;r+=1)n&&t.append(n,e.files[r]);r&&t.append(r,p),p=t}let h={"ui-request-id":i,...m?{}:{"Content-Type":`application/json`},...d},g=r===`GET`,_=await fetch(e.api,{method:r,headers:h,signal:c.controller.signal,...g?{}:{body:p}});n(s,`cancelation`,c.drop);let v,y=e.responseType||`json`;return v=y===`text`?await _.text():y===`blob`?await _.blob():await _.json(),_.ok?(l&&e.onResult(v),{result:v,startedAt:o,endedAt:new Date().toUTCString()}):(l&&e.onResult(v),t({status:_.status,statusText:_.statusText,data:v}))}catch(r){return n(s,`cancelation`,c.drop),l&&e.onResult(r),r instanceof Error&&r.name===`AbortError`?t({message:`Request was cancelled`,cancelled:!0}):t({message:r instanceof Error?r.message:`Unknown error occurred`})}};return s.modules.includes(`queryQueue`)?d.enqueue(f):f()});function p(e){return r(e)&&Array.isArray(e.data)&&typeof e.primaryKey==`string`}function m(e,t){let n={};for(let i of e)if(r(i)&&t in i){let e=String(i[t]);n[e]=i}return n}function h(e,t,n,r){return n===`replace`?t:n===`merge`?i(e,t):n===`skip`?e:(console.warn(`Cdeebee: Unknown strategy "${n}" for key "${r}". Skipping normalization.`),i(e,t))}function g(e,t,n){let i=Object.keys(t),o=r(e.storage)?e.storage:{},s={...o},c=new Set;for(let e of i){let i=t[e];if(i==null||typeof i==`string`){c.add(e);continue}let a=n[e]??`merge`;if(a===`skip`&&!(e in o))continue;let l=e in o?o[e]:{};if(p(i)){s[e]=h(l,m(i.data,i.primaryKey),a,e);continue}r(i)?s[e]=h(l,i,a,e):s[e]=i}return c.size>0?a(Array.from(c),s):s}var _={settings:{modules:[`history`,`listener`,`storage`,`cancelation`],fileKey:`file`,bodyKey:`value`,listStrategy:{},mergeWithData:{},mergeWithHeaders:{}},storage:{},request:{active:[],errors:{},done:{},lastResultIdList:{}}},v=(t,r)=>(0,e.createSlice)({name:`cdeebee`,initialState:i(_,{settings:t,storage:r??{}}),reducers:{set(e,t){s(e.storage,t.payload)},historyClear(e,t){let n=t.payload;n?(delete e.request.done[n],delete e.request.errors[n]):(e.request.done={},e.request.errors={})}},extraReducers:t=>{t.addCase(f.pending,(e,t)=>{let r=t.meta.arg.api,i=t.meta.requestId;t.meta.arg.historyClear&&n(e.settings,`history`,()=>{delete e.request.done[r],delete e.request.errors[r]}),n(e.settings,`cancelation`,()=>{l(r,i)}),n(e.settings,`listener`,()=>{e.request.active.push({api:r,requestId:i})})}).addCase(f.fulfilled,(t,r)=>{let i=r.meta.requestId,a=r.meta.arg.api;n(t.settings,`listener`,()=>{t.request.active=t.request.active.filter(e=>!(e.api===a&&e.requestId===i))}),n(t.settings,`history`,()=>{t.request.done[a]||(t.request.done[a]=[]),t.request.done[a].push({api:a,request:r.payload,requestId:i});let e=t.settings.maxHistorySize;e&&t.request.done[a].length>e&&(t.request.done[a]=t.request.done[a].slice(-e))}),n(t.settings,`storage`,()=>{if(r.meta.arg.ignore)return;let n=r.meta.arg.listStrategy??t.settings.listStrategy??{};t.storage=(r.meta.arg.normalize??t.settings.normalize??g)((0,e.current)(t),r.payload.result,n),t.request.lastResultIdList[a]=o(r.payload.result)})}).addCase(f.rejected,(e,t)=>{let r=t.meta.requestId,i=t.meta.arg.api;n(e.settings,`listener`,()=>{e.request.active=e.request.active.filter(e=>!(e.api===i&&e.requestId===r))}),n(e.settings,`history`,()=>{e.request.errors[i]||(e.request.errors[i]=[]),e.request.errors[i].push({requestId:r,api:i,request:t.error});let n=e.settings.maxHistorySize;n&&e.request.errors[i].length>n&&(e.request.errors[i]=e.request.errors[i].slice(-n))})})}}),y=[],b=y,x=y;function S(e){function n(n){return(0,t.useSelector)(t=>e(t).request.active.some(e=>n.includes(e.api)))}function r(n){return(0,t.useSelector)(t=>e(t).request.done[n]??b)}function i(n){return(0,t.useSelector)(t=>e(t).request.errors[n]??b)}function a(n){return(0,t.useSelector)(t=>e(t).storage[n])}function o(){return(0,t.useSelector)(t=>e(t).storage)}function s(){return(0,t.useSelector)(t=>e(t).request.active.length>0)}function c(n,r){return(0,t.useSelector)(t=>e(t).request.lastResultIdList[n]?.[r]??x)}return{useLoading:n,useRequestHistory:r,useRequestErrors:i,useStorageList:a,useStorage:o,useIsLoading:s,useLastResultIdList:c}}var C=[],w=C,T=C;function E(e){return(0,t.useSelector)(t=>t.cdeebee.request.active.some(t=>e.includes(t.api)))}function D(e){return(0,t.useSelector)(t=>t.cdeebee.request.done[e]??w)}function O(e){return(0,t.useSelector)(t=>t.cdeebee.request.errors[e]??w)}function k(e){return(0,t.useSelector)(t=>t.cdeebee.storage[e])}function A(){return(0,t.useSelector)(e=>e.cdeebee.storage)}function j(){return(0,t.useSelector)(e=>e.cdeebee.request.active.length>0)}function M(e,n){return(0,t.useSelector)(t=>t.cdeebee.request.lastResultIdList[e]?.[n]??T)}exports.batchingUpdate=s,exports.createCdeebeeHooks=S,exports.defaultNormalize=g,exports.factory=v,exports.request=f,exports.useIsLoading=j,exports.useLastResultIdList=M,exports.useLoading=E,exports.useRequestErrors=O,exports.useRequestHistory=D,exports.useStorage=A,exports.useStorageList=k;
|
|
2
|
+
//# sourceMappingURL=index.cjs.map
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.cjs","sources":["../lib/reducer/helpers.ts","../lib/reducer/abortController.ts","../lib/reducer/queryQueue.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","class QueryQueue {\n private currentPromise: Promise<unknown> = Promise.resolve();\n private queueLength = 0;\n\n async enqueue<T>(task: () => Promise<T>): Promise<T> {\n this.queueLength++;\n \n const previousPromise = this.currentPromise;\n \n this.currentPromise = previousPromise\n .then(() => task(), () => task())\n .finally(() => {\n this.queueLength--;\n });\n\n return this.currentPromise as Promise<T>;\n }\n\n getQueueLength(): number {\n return this.queueLength;\n }\n\n clear(): void {\n this.queueLength = 0;\n }\n}\n\nexport const queryQueue = new QueryQueue();\n\n","import { createAsyncThunk } from '@reduxjs/toolkit';\nimport { checkModule } from './helpers';\nimport { abortManager } from './abortController';\nimport { queryQueue } from './queryQueue';\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 const withCallback = options.onResult && typeof options.onResult === 'function';\n\n checkModule(settings, 'cancelation', abort.init);\n\n const executeRequest = async () => {\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 let result: unknown;\n const responseType = options.responseType || 'json';\n \n if (responseType === 'text') {\n result = await response.text();\n } else if (responseType === 'blob') {\n result = await response.blob();\n } else {\n // default: json\n result = await response.json();\n }\n\n if (!response.ok) {\n if (withCallback) options.onResult!(result);\n return rejectWithValue(response);\n }\n\n if (withCallback) options.onResult!(result);\n return { result, startedAt, endedAt: new Date().toUTCString() };\n } catch (error) {\n checkModule(settings, 'cancelation', abort.drop);\n\n if (withCallback) options.onResult!(error); \n\n if (error instanceof Error && error.name === 'AbortError') {\n return rejectWithValue({ message: 'Request was cancelled', cancelled: true });\n }\n\n return rejectWithValue({ message: error instanceof Error ? error.message : 'Unknown error occurred' });\n }\n };\n\n if (settings.modules.includes('queryQueue')) {\n return queryQueue.enqueue(executeRequest);\n }\n\n return executeRequest();\n },\n);\n\n","import { type CdeebeeListStrategy, type CdeebeeState } from './types';\nimport { isRecord, mergeDeepRight, omit } from './helpers';\n\ntype ResponseValue = Record<string, unknown>;\n\ntype IResponse = Record<string, ResponseValue>;\n\ntype StorageData = Record<string, unknown>;\n\nfunction isDataWithPrimaryKey(value: unknown): value is { data: unknown[]; primaryKey: string } {\n return (\n isRecord(value) &&\n Array.isArray(value.data) &&\n typeof value.primaryKey === 'string'\n );\n}\nfunction normalizeDataWithPrimaryKey(data: unknown[], primaryKey: string): Record<string, unknown> {\n const result: Record<string, unknown> = {};\n \n for (const item of data) {\n if (isRecord(item) && primaryKey in item) {\n const key = String(item[primaryKey]);\n result[key] = item;\n }\n }\n \n return result;\n}\n\nfunction applyStrategy(\n existingValue: StorageData,\n newValue: StorageData | ResponseValue,\n strategy: string,\n key: string\n): ResponseValue {\n if (strategy === 'replace') {\n return newValue as ResponseValue;\n } else if (strategy === 'merge') {\n return mergeDeepRight(existingValue, newValue as StorageData) as ResponseValue;\n } else {\n console.warn(`Cdeebee: Unknown strategy \"${strategy}\" for key \"${key}\". Skipping normalization.`);\n return mergeDeepRight(existingValue, newValue as StorageData) as ResponseValue;\n }\n}\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 currentStorage = isRecord(cdeebee.storage) ? (cdeebee.storage as Record<string, unknown>) : {};\n \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 const strategy = strategyList[key as keyof T] ?? 'merge';\n const existingValue = key in currentStorage ? (currentStorage[key] as StorageData) : {};\n\n if (isDataWithPrimaryKey(responseValue)) {\n const normalizedValue = normalizeDataWithPrimaryKey(responseValue.data, responseValue.primaryKey);\n result[key] = applyStrategy(existingValue, normalizedValue, strategy, key);\n continue;\n }\n\n if (isRecord(responseValue)) {\n result[key] = applyStrategy(existingValue, responseValue as StorageData, strategy, key);\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 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 if (action.meta.arg.ignore) {\n return;\n }\n \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 // Type assertion is safe here because we've already checked isRecord\n const normalizedData = normalize(currentState, action.payload.result as Record<string, Record<string, unknown>>, 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","mergeDeepRight","left","right","rightRecord","key","leftValue","rightValue","omit","keys","obj","batchingUpdate","state","valueList","i","item","path","current","j","pathKey","index","nextIsNumeric","next","AbortControllerStore","api","requestId","controller","apiSet","excludeRequestId","requestIds","abortStore","abortQuery","currentRequestId","abortManager","signal","cleanup","QueryQueue","task","previousPromise","queryQueue","request","createAsyncThunk","options","rejectWithValue","getState","startedAt","abort","withCallback","executeRequest","method","body","headers","extraHeaders","b","requestData","formData","fileKey","bodyKey","response","responseType","error","isDataWithPrimaryKey","normalizeDataWithPrimaryKey","data","primaryKey","applyStrategy","existingValue","newValue","strategy","defaultNormalize","cdeebee","strategyList","keyList","currentStorage","keyListToOmit","responseValue","normalizedValue","initialState","factory","storage","createSlice","action","builder","q","normalize","currentState","normalizedData"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.cjs","names":[],"sources":["../lib/reducer/helpers.ts","../lib/reducer/abortController.ts","../lib/reducer/queryQueue.ts","../lib/reducer/request.ts","../lib/reducer/storage.ts","../lib/reducer/index.ts","../lib/hooks/createCdeebeeHooks.ts","../lib/hooks/selectors.ts"],"sourcesContent":["import { type WritableDraft } from '@reduxjs/toolkit';\nimport { type CdeebeeSettings, type CdeebeeModule, CdeebeeValueList } from './types';\n\nexport function checkModule<T = unknown>(settings: CdeebeeSettings<T> | WritableDraft<CdeebeeSettings<T>>, module: CdeebeeModule, result: () => void) {\n if (settings.modules.includes(module)) {\n result();\n }\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 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\n/**\n * Extract primary key values from API response data per list.\n * Handles responses with format: { listName: { data: [...], primaryKey: 'id' } }\n * Returns a map of listName -> array of IDs.\n */\nexport function extractLastResultIdList(response: unknown): Record<string, string[]> {\n if (!isRecord(response)) {\n return {};\n }\n\n const result: Record<string, string[]> = {};\n\n for (const listName of Object.keys(response)) {\n const value = response[listName];\n\n if (\n isRecord(value) &&\n Array.isArray(value.data) &&\n typeof value.primaryKey === 'string'\n ) {\n const primaryKey = value.primaryKey;\n const idList: string[] = [];\n\n for (const item of value.data) {\n if (isRecord(item) && primaryKey in item) {\n idList.push(String(item[primaryKey]));\n }\n }\n\n result[listName] = idList;\n }\n }\n\n return result;\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 const lastKey = path[path.length - 1];\n if (Array.isArray(current)) {\n const index = typeof lastKey === 'number' ? lastKey : Number(lastKey);\n current[index] = value;\n } else {\n current[String(lastKey)] = value;\n }\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 requestIDList = this.byApi.get(api);\n if (!requestIDList) return;\n\n const toAbort = Array.from(requestIDList).filter(id => id !== excludeRequestId);\n\n for (const requestId of toAbort) {\n const item = this.byRequestId.get(requestId);\n if (item) {\n item.controller.abort();\n this.delete(requestId);\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","class QueryQueue {\n private currentPromise: Promise<unknown> = Promise.resolve();\n private queueLength = 0;\n\n async enqueue<T>(task: () => Promise<T>): Promise<T> {\n this.queueLength++;\n\n const previousPromise = this.currentPromise;\n\n this.currentPromise = previousPromise\n .then(() => task(), () => task())\n .finally(() => {\n this.queueLength--;\n });\n\n return this.currentPromise as Promise<T>;\n }\n\n getQueueLength(): number {\n return this.queueLength;\n }\n\n clear(): void {\n this.queueLength = 0;\n this.currentPromise = Promise.resolve();\n }\n}\n\nexport const queryQueue = new QueryQueue();\n\n","import { createAsyncThunk } from '@reduxjs/toolkit';\nimport { checkModule } from './helpers';\nimport { abortManager } from './abortController';\nimport { queryQueue } from './queryQueue';\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 const withCallback = options.onResult && typeof options.onResult === 'function';\n\n checkModule(settings, 'cancelation', abort.init);\n\n const executeRequest = async () => {\n try {\n const { method = 'POST', body, headers = {} } = options;\n const baseHeaders = typeof settings.mergeWithHeaders === 'function'\n ? settings.mergeWithHeaders()\n : (settings.mergeWithHeaders ?? {});\n\n const extraHeaders: Record<string, string> = { ...baseHeaders, ...headers };\n\n const baseData = typeof settings.mergeWithData === 'function'\n ? settings.mergeWithData()\n : (settings.mergeWithData ?? {});\n\n const b = { ...baseData, ...(body ?? {}) };\n let requestData: FormData | string = JSON.stringify(b);\n const isFormData = !!options.files;\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 fetchHeaders: Record<string, string> = {\n 'ui-request-id': requestId,\n ...(isFormData ? {} : { 'Content-Type': 'application/json' }),\n ...extraHeaders,\n };\n\n const isGet = method === 'GET';\n\n const response = await fetch(options.api, {\n method,\n headers: fetchHeaders,\n signal: abort.controller.signal,\n ...(isGet ? {} : { body: requestData }),\n });\n\n checkModule(settings, 'cancelation', abort.drop);\n\n let result: unknown;\n const responseType = options.responseType || 'json';\n\n if (responseType === 'text') {\n result = await response.text();\n } else if (responseType === 'blob') {\n result = await response.blob();\n } else {\n // default: json\n result = await response.json();\n }\n\n if (!response.ok) {\n if (withCallback) options.onResult!(result);\n return rejectWithValue({ status: response.status, statusText: response.statusText, data: result });\n }\n\n if (withCallback) options.onResult!(result);\n return { result, startedAt, endedAt: new Date().toUTCString() };\n } catch (error) {\n checkModule(settings, 'cancelation', abort.drop);\n\n if (withCallback) options.onResult!(error);\n\n if (error instanceof Error && error.name === 'AbortError') {\n return rejectWithValue({ message: 'Request was cancelled', cancelled: true });\n }\n\n return rejectWithValue({ message: error instanceof Error ? error.message : 'Unknown error occurred' });\n }\n };\n\n if (settings.modules.includes('queryQueue')) {\n return queryQueue.enqueue(executeRequest);\n }\n\n return executeRequest();\n },\n);\n\n","import { type CdeebeeListStrategy, type CdeebeeState } from './types';\nimport { isRecord, mergeDeepRight, omit } from './helpers';\n\ntype ResponseValue = Record<string, unknown>;\n\ntype IResponse = Record<string, ResponseValue>;\n\ntype StorageData = Record<string, unknown>;\n\nfunction isDataWithPrimaryKey(value: unknown): value is { data: unknown[]; primaryKey: string } {\n return (\n isRecord(value) &&\n Array.isArray(value.data) &&\n typeof value.primaryKey === 'string'\n );\n}\nfunction normalizeDataWithPrimaryKey(data: unknown[], primaryKey: string): Record<string, unknown> {\n const result: Record<string, unknown> = {};\n\n for (const item of data) {\n if (isRecord(item) && primaryKey in item) {\n const key = String(item[primaryKey]);\n result[key] = item;\n }\n }\n\n return result;\n}\n\nfunction applyStrategy(\n existingValue: StorageData,\n newValue: StorageData | ResponseValue,\n strategy: string,\n key: string\n): ResponseValue {\n if (strategy === 'replace') {\n return newValue as ResponseValue;\n } else if (strategy === 'merge') {\n return mergeDeepRight(existingValue, newValue as StorageData) as ResponseValue;\n } else if (strategy === 'skip') {\n return existingValue as ResponseValue;\n } else {\n console.warn(`Cdeebee: Unknown strategy \"${strategy}\" for key \"${key}\". Skipping normalization.`);\n return mergeDeepRight(existingValue, newValue as StorageData) as ResponseValue;\n }\n}\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 currentStorage = isRecord(cdeebee.storage) ? (cdeebee.storage as Record<string, unknown>) : {};\n\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 const strategy = strategyList[key as keyof T] ?? 'merge';\n\n // For 'skip' strategy, if key doesn't exist in storage, skip it entirely\n if (strategy === 'skip' && !(key in currentStorage)) {\n continue;\n }\n\n const existingValue = key in currentStorage ? (currentStorage[key] as StorageData) : {};\n\n if (isDataWithPrimaryKey(responseValue)) {\n const normalizedValue = normalizeDataWithPrimaryKey(responseValue.data, responseValue.primaryKey);\n result[key] = applyStrategy(existingValue, normalizedValue, strategy, key);\n continue;\n }\n\n if (isRecord(responseValue)) {\n result[key] = applyStrategy(existingValue, responseValue as StorageData, strategy, key);\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, type PayloadAction } from '@reduxjs/toolkit';\n\nimport { type CdeebeeSettings, type CdeebeeState, type CdeebeeValueList, type CdeebeeListStrategy } from './types';\nimport { checkModule, mergeDeepRight, batchingUpdate, extractLastResultIdList } 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 listStrategy: {},\n mergeWithData: {},\n mergeWithHeaders: {},\n },\n storage: {},\n request: {\n active: [],\n errors: {},\n done: {},\n lastResultIdList: {},\n },\n};\n\nexport const factory = <T>(settings: CdeebeeSettings<T>, storage?: T) => {\n const slice = createSlice({\n name: 'cdeebee',\n initialState: mergeDeepRight(initialState as CdeebeeState<T>, { 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 historyClear(state, action: PayloadAction<string | undefined>) {\n const api = action.payload;\n\n if (api) {\n delete state.request.done[api];\n delete state.request.errors[api];\n } else {\n state.request.done = {};\n state.request.errors = {};\n }\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 if (action.meta.arg.historyClear) {\n checkModule(state.settings, 'history', () => {\n delete state.request.done[api];\n delete state.request.errors[api];\n });\n }\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 const max = state.settings.maxHistorySize;\n if (max && state.request.done[api].length > max) {\n state.request.done[api] = state.request.done[api].slice(-max);\n }\n });\n checkModule(state.settings, 'storage', () => {\n if (action.meta.arg.ignore) {\n return;\n }\n\n const strategyList = (action.meta.arg.listStrategy ?? state.settings.listStrategy ?? {}) as CdeebeeListStrategy<T>;\n const normalize = (action.meta.arg.normalize ?? state.settings.normalize ?? defaultNormalize) as NonNullable<CdeebeeSettings<T>['normalize']>;\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/skip and preserves keys not in response\n // Simply apply the result\n (state.storage as any) = normalizedData;\n\n // Extract and store result IDs for filtering per list\n state.request.lastResultIdList[api] = extractLastResultIdList(action.payload.result);\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 const max = state.settings.maxHistorySize;\n if (max && state.request.errors[api].length > max) {\n state.request.errors[api] = state.request.errors[api].slice(-max);\n }\n });\n });\n },\n });\n\n return slice;\n};\n","import { useSelector } from 'react-redux';\nimport { type CdeebeeState, type CdeebeeHistoryState } from '../reducer/types';\n\nconst EMPTY_ARRAY: readonly never[] = [];\nconst EMPTY_HISTORY: readonly CdeebeeHistoryState[] = EMPTY_ARRAY;\nconst EMPTY_ID_LIST: readonly string[] = EMPTY_ARRAY;\n\n/**\n * Generic hook factory that creates a selector hook for cdeebee state.\n * This allows the hooks to work with any Redux root state structure.\n *\n * @template RootState - The shape of the Redux root state\n * @template Storage - The shape of the cdeebee storage\n * @param selectCdeebee - Function to select the cdeebee slice from root state\n * @returns An object containing all cdeebee hooks\n */\nexport function createCdeebeeHooks<RootState, Storage>(\n selectCdeebee: (state: RootState) => CdeebeeState<Storage>\n) {\n /**\n * Check if any of the specified APIs are currently loading.\n *\n * @param apiList - Array of API endpoints to check\n * @returns true if any of the APIs are currently active/loading\n *\n * @example\n * const isLoading = useLoading(['/api/forums', '/api/threads']);\n * if (isLoading) return <Spinner />;\n */\n function useLoading(apiList: string[]): boolean {\n return useSelector((state: RootState) => {\n const cdeebee = selectCdeebee(state);\n return cdeebee.request.active.some(q => apiList.includes(q.api));\n });\n }\n\n /**\n * Get the successful request history for a specific API endpoint.\n *\n * @param api - The API endpoint\n * @returns Array of successful request history entries\n *\n * @example\n * const history = useRequestHistory('/api/forums');\n * console.log(`Made ${history.length} successful requests`);\n */\n function useRequestHistory(api: string) {\n return useSelector((state: RootState) => {\n const cdeebee = selectCdeebee(state);\n return cdeebee.request.done[api] ?? EMPTY_HISTORY;\n });\n }\n\n /**\n * Get the error history for a specific API endpoint.\n *\n * @param api - The API endpoint\n * @returns Array of error history entries\n *\n * @example\n * const errors = useRequestErrors('/api/forums');\n * if (errors.length > 0) {\n * console.error('Last error:', errors[errors.length - 1]);\n * }\n */\n function useRequestErrors(api: string) {\n return useSelector((state: RootState) => {\n const cdeebee = selectCdeebee(state);\n return cdeebee.request.errors[api] ?? EMPTY_HISTORY;\n });\n }\n\n /**\n * Get a specific list from storage with full type safety.\n *\n * @param listName - The name of the list in storage\n * @returns The list data\n *\n * @example\n * const forums = useStorageList('forumList');\n * const forumArray = Object.values(forums);\n */\n function useStorageList<K extends keyof Storage>(listName: K): Storage[K] {\n return useSelector((state: RootState) => {\n const cdeebee = selectCdeebee(state);\n return cdeebee.storage[listName];\n });\n }\n\n /**\n * Get the entire cdeebee storage.\n *\n * @returns The complete storage object\n *\n * @example\n * const storage = useStorage();\n * console.log(Object.keys(storage)); // ['forumList', 'threadList', ...]\n */\n function useStorage(): Storage {\n return useSelector((state: RootState) => {\n const cdeebee = selectCdeebee(state);\n return cdeebee.storage;\n });\n }\n\n /**\n * Check if any request is currently loading (across all APIs).\n *\n * @returns true if any request is active\n *\n * @example\n * const isAnythingLoading = useIsLoading();\n * if (isAnythingLoading) return <GlobalSpinner />;\n */\n function useIsLoading(): boolean {\n return useSelector((state: RootState) => {\n const cdeebee = selectCdeebee(state);\n return cdeebee.request.active.length > 0;\n });\n }\n\n /**\n * Get the list of IDs returned by the last successful request to an API for a specific list.\n * Useful for filtering storage data to show only results from a specific request.\n *\n * @param api - The API endpoint\n * @param listName - The name of the list in storage (typed from Storage)\n * @returns Array of primary key IDs from the last response for that list\n *\n * @example\n * const productList = useStorageList('productList');\n * const lastIDList = useLastResultIdList('/api/search', 'productList');\n * const displayResults = lastIDList.map(id => productList[id]).filter(Boolean);\n */\n function useLastResultIdList<K extends keyof Storage>(api: string, listName: K): string[] {\n return useSelector((state: RootState) => {\n const cdeebee = selectCdeebee(state);\n return cdeebee.request.lastResultIdList[api]?.[listName as string] ?? EMPTY_ID_LIST;\n });\n }\n\n return {\n useLoading,\n useRequestHistory,\n useRequestErrors,\n useStorageList,\n useStorage,\n useIsLoading,\n useLastResultIdList,\n };\n}\n","import { useSelector } from 'react-redux';\nimport { type CdeebeeState, type CdeebeeHistoryState } from '../reducer/types';\n\nconst EMPTY_ARRAY: readonly never[] = [];\nconst EMPTY_HISTORY: readonly CdeebeeHistoryState[] = EMPTY_ARRAY;\nconst EMPTY_ID_LIST: readonly string[] = EMPTY_ARRAY;\n\n/**\n * Standalone hook that can be used without createCdeebeeHooks.\n * Assumes the cdeebee slice is at state.cdeebee.\n *\n * @param apiList - Array of API endpoints to check\n * @returns true if any of the APIs are currently active/loading\n *\n * @example\n * const isLoading = useLoading(['/api/forums', '/api/threads']);\n */\nexport function useLoading<Storage = unknown>(apiList: string[]): boolean {\n return useSelector((state: { cdeebee: CdeebeeState<Storage> }) => {\n return state.cdeebee.request.active.some(q => apiList.includes(q.api));\n });\n}\n\n/**\n * Standalone hook that can be used without createCdeebeeHooks.\n * Assumes the cdeebee slice is at state.cdeebee.\n *\n * @param api - The API endpoint\n * @returns Array of successful request history entries\n */\nexport function useRequestHistory<Storage = unknown>(api: string) {\n return useSelector((state: { cdeebee: CdeebeeState<Storage> }) => {\n return state.cdeebee.request.done[api] ?? EMPTY_HISTORY;\n });\n}\n\n/**\n * Standalone hook that can be used without createCdeebeeHooks.\n * Assumes the cdeebee slice is at state.cdeebee.\n *\n * @param api - The API endpoint\n * @returns Array of error history entries\n */\nexport function useRequestErrors<Storage = unknown>(api: string) {\n return useSelector((state: { cdeebee: CdeebeeState<Storage> }) => {\n return state.cdeebee.request.errors[api] ?? EMPTY_HISTORY;\n });\n}\n\n/**\n * Standalone hook that can be used without createCdeebeeHooks.\n * Assumes the cdeebee slice is at state.cdeebee.\n *\n * @param listName - The name of the list in storage\n * @returns The list data\n */\nexport function useStorageList<Storage, K extends keyof Storage>(listName: K): Storage[K] {\n return useSelector((state: { cdeebee: CdeebeeState<Storage> }) => {\n return state.cdeebee.storage[listName];\n });\n}\n\n/**\n * Standalone hook that can be used without createCdeebeeHooks.\n * Assumes the cdeebee slice is at state.cdeebee.\n *\n * @returns The complete storage object\n */\nexport function useStorage<Storage>(): Storage {\n return useSelector((state: { cdeebee: CdeebeeState<Storage> }) => {\n return state.cdeebee.storage;\n });\n}\n\n/**\n * Standalone hook that can be used without createCdeebeeHooks.\n * Assumes the cdeebee slice is at state.cdeebee.\n *\n * @returns true if any request is active\n */\nexport function useIsLoading<Storage = unknown>(): boolean {\n return useSelector((state: { cdeebee: CdeebeeState<Storage> }) => {\n return state.cdeebee.request.active.length > 0;\n });\n}\n\n/**\n * Standalone hook that can be used without createCdeebeeHooks.\n * Assumes the cdeebee slice is at state.cdeebee.\n *\n * Get the list of IDs returned by the last successful request to an API for a specific list.\n * Useful for filtering storage data to show only results from a specific request.\n *\n * @param api - The API endpoint\n * @param listName - The name of the list in storage (typed from Storage)\n * @returns Array of primary key IDs from the last response for that list\n *\n * @example\n * const productList = useStorageList('productList');\n * const lastIDList = useLastResultIdList('/api/search', 'productList');\n * const displayResults = lastIDList.map(id => productList[id]).filter(Boolean);\n */\nexport function useLastResultIdList<Storage, K extends keyof Storage>(api: string, listName: K): string[] {\n return useSelector((state: { cdeebee: CdeebeeState<Storage> }) => {\n return state.cdeebee.request.lastResultIdList[api]?.[listName as string] ?? EMPTY_ID_LIST;\n });\n}\n"],"mappings":"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"}
|