@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 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@beta
8
+ npm i @recats/cdeebee
9
9
  # or
10
- yarn add @recats/cdeebee@beta
10
+ yarn add @recats/cdeebee
11
11
  # or
12
- pnpm add @recats/cdeebee@beta
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 { useAppSelector } from './hooks';
148
+ import { useLoading, useStorageList } from '@recats/cdeebee';
145
149
 
146
150
  function ForumsList() {
147
- const forums = useAppSelector(state => state.cdeebee.storage.forumList);
148
- const activeRequests = useAppSelector(state => state.cdeebee.request.active);
149
- const isLoading = activeRequests.some(req => req.api === '/api/forums');
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; // Data to merge with every request body
175
- mergeWithHeaders?: Record<string, string>; // Headers to merge with every request
176
- normalize?: (storage, result, strategyList) => T; // Custom normalization function
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>; // Override list strategy for this request
192
- normalize?: (storage, result, strategyList) => T; // Override normalization
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 two strategies for merging data:
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
- // Get history for specific API
461
- const apiHistory = doneRequests['/api/forums'] || [];
462
- const apiErrors = errors['/api/forums'] || [];
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
- "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const S=require("@reduxjs/toolkit");function y(t,r,n){t.modules.includes(r)&&n()}function f(t){return t!==null&&typeof t=="object"&&!Array.isArray(t)}function p(t,r){if(!f(t)||!f(r))return r;const n={...t},o=r;for(const e in o)if(Object.prototype.hasOwnProperty.call(o,e)){const a=n[e],s=o[e];f(a)&&f(s)&&!Array.isArray(a)&&!Array.isArray(s)?n[e]=p(a,s):n[e]=s}return n}function L(t,r){const n={...r};for(const o of t)delete n[o];return n}function P(t,r){for(let n=0;n<r.length;n++){const o=r[n],e=o.key,a=o.value;if(e.length===0)continue;let s=t;for(let i=0;i<e.length-1;i++){const c=e[i];if(Array.isArray(s)){const l=typeof c=="number"?c:Number(c);(!(l in s)||!f(s[l]))&&(s[l]={}),s=s[l]}else{const l=String(c);if(!(l in s)){const d=typeof e[i+1]=="number"||!isNaN(Number(e[i+1]))&&String(Number(e[i+1]))===String(e[i+1]);s[l]=d?[]:{}}const u=s[l];s=Array.isArray(u)||f(u)?u:{}}}Array.isArray(s)||(s[String(e[e.length-1])]=a)}}class T{constructor(){this.byRequestId=new Map,this.byApi=new Map}add(r,n,o){const e={requestId:n,controller:o,api:r};this.byRequestId.set(n,e),this.byApi.has(r)||this.byApi.set(r,new Set),this.byApi.get(r).add(n)}delete(r){const n=this.byRequestId.get(r);if(!n)return;this.byRequestId.delete(r);const o=this.byApi.get(n.api);o&&(o.delete(r),o.size===0&&this.byApi.delete(n.api))}abortAllForApi(r,n){const o=this.byApi.get(r);o&&o.forEach(e=>{if(e!==n){const a=this.byRequestId.get(e);a&&(a.controller.abort(),this.delete(e))}})}}const w=new T;function x(t,r){w.abortAllForApi(t,r)}function N(t,r,n){const o=new AbortController,e=()=>{w.delete(n)};return t.addEventListener("abort",()=>{o.abort(),e()}),{controller:o,init:()=>w.add(r,n,o),drop:e}}class j{constructor(){this.currentPromise=Promise.resolve(),this.queueLength=0}async enqueue(r){this.queueLength++;const n=this.currentPromise;return this.currentPromise=n.then(()=>r(),()=>r()).finally(()=>{this.queueLength--}),this.currentPromise}getQueueLength(){return this.queueLength}clear(){this.queueLength=0}}const I=new j,m=S.createAsyncThunk("cdeebee/request",async(t,{rejectWithValue:r,getState:n,requestId:o,signal:e})=>{const a=new Date().toUTCString(),{cdeebee:{settings:s}}=n(),i=N(e,t.api,o),c=t.onResult&&typeof t.onResult=="function";y(s,"cancelation",i.init);const l=async()=>{try{const{method:u="POST",body:d,headers:z={}}=t,C={...s.mergeWithHeaders??{},...z},v={...s.mergeWithData??{},...d??{}};let b=JSON.stringify(v);if(t.files){const q=new FormData,R=t.fileKey||s.fileKey,K=t.bodyKey||s.bodyKey;for(let A=0;A<t.files.length;A+=1)R&&q.append(R,t.files[A]);K&&q.append(K,b),b=q}const h=await fetch(t.api,{method:u,headers:{"ui-request-id":o,"Content-Type":"application/json",...C},signal:i.controller.signal,body:b});y(s,"cancelation",i.drop);let g;const k=t.responseType||"json";return k==="text"?g=await h.text():k==="blob"?g=await h.blob():g=await h.json(),h.ok?(c&&t.onResult(g),{result:g,startedAt:a,endedAt:new Date().toUTCString()}):(c&&t.onResult(g),r(h))}catch(u){return y(s,"cancelation",i.drop),c&&t.onResult(u),u instanceof Error&&u.name==="AbortError"?r({message:"Request was cancelled",cancelled:!0}):r({message:u instanceof Error?u.message:"Unknown error occurred"})}};return s.modules.includes("queryQueue")?I.enqueue(l):l()});function O(t){return f(t)&&Array.isArray(t.data)&&typeof t.primaryKey=="string"}function Q(t,r){const n={};for(const o of t)if(f(o)&&r in o){const e=String(o[r]);n[e]=o}return n}function D(t,r,n,o){return n==="replace"?r:(n==="merge"||console.warn(`Cdeebee: Unknown strategy "${n}" for key "${o}". Skipping normalization.`),p(t,r))}function U(t,r,n){const o=Object.keys(r),e=f(t.storage)?t.storage:{},a={...e},s=new Set;for(const i of o){const c=r[i];if(c==null||typeof c=="string"){s.add(i);continue}const l=n[i]??"merge",u=i in e?e[i]:{};if(O(c)){const d=Q(c.data,c.primaryKey);a[i]=D(u,d,l,i);continue}f(c)?a[i]=D(u,c,l,i):a[i]=c}return s.size>0?L(Array.from(s),a):a}const E={settings:{modules:["history","listener","storage","cancelation"],fileKey:"file",bodyKey:"value",listStrategy:{},mergeWithData:{},mergeWithHeaders:{}},storage:{},request:{active:[],errors:{},done:{}}},M=(t,r)=>S.createSlice({name:"cdeebee",initialState:p(E,{settings:t,storage:r??{}}),reducers:{set(o,e){P(o.storage,e.payload)}},extraReducers:o=>{o.addCase(m.pending,(e,a)=>{const s=a.meta.arg.api,i=a.meta.requestId;y(e.settings,"cancelation",()=>{x(s,i)}),y(e.settings,"listener",()=>{e.request.active.push({api:s,requestId:i})})}).addCase(m.fulfilled,(e,a)=>{const s=a.meta.requestId,i=a.meta.arg.api;y(e.settings,"listener",()=>{e.request.active=e.request.active.filter(c=>!(c.api===i&&c.requestId===s))}),y(e.settings,"history",()=>{e.request.done[i]||(e.request.done[i]=[]),e.request.done[i].push({api:i,request:a.payload,requestId:s})}),y(e.settings,"storage",()=>{if(a.meta.arg.ignore)return;const c=a.meta.arg.listStrategy??e.settings.listStrategy??{},l=a.meta.arg.normalize??e.settings.normalize??U,u=S.current(e),d=l(u,a.payload.result,c);e.storage=d})}).addCase(m.rejected,(e,a)=>{const s=a.meta.requestId,i=a.meta.arg.api;y(e.settings,"listener",()=>{e.request.active=e.request.active.filter(c=>!(c.api===i&&c.requestId===s))}),y(e.settings,"history",()=>{e.request.errors[i]||(e.request.errors[i]=[]),e.request.errors[i].push({requestId:s,api:i,request:a.error})})})}});exports.batchingUpdate=P;exports.factory=M;exports.request=m;
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
@@ -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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"}