@recats/cdeebee 2.3.7 → 3.0.0-beta.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE CHANGED
@@ -1,6 +1,6 @@
1
1
  MIT License
2
2
 
3
- Copyright (c) 2017 __strelkov
3
+ Copyright (c) 2026 Dmitrii Strelkov
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
package/README.md CHANGED
@@ -1,204 +1,363 @@
1
1
  # cdeebee
2
2
 
3
3
  [![npm](https://img.shields.io/npm/v/@recats/cdeebee.svg)](https://www.npmjs.com/package/@recats/cdeebee)
4
- [![Greenkeeper badge](https://badges.greenkeeper.io/recats/cdeebee.svg)](https://greenkeeper.io/)
5
- [![Travis badge](https://travis-ci.com/recats/cdeebee.svg?branch=master)](https://travis-ci.com/recats/cdeebee)
6
- ![Recats Digital](https://img.shields.io/badge/recats-digital-1abc9c.svg?style=flat)
4
+
5
+ A Redux-based data management library that provides a uniform way to access, fetch, update, and manage application data with minimal boilerplate code.
6
+
7
+ ## Installation
7
8
 
8
9
  ```sh
9
10
  npm i @recats/cdeebee
10
11
  # or
11
12
  yarn add @recats/cdeebee
13
+ # or
14
+ pnpm add @recats/cdeebee
12
15
  ```
13
- # cdeebee
14
- cdeebee is an alpha version of library which goals are:
15
- - Provide uniform way of access to data
16
- - Decrease boilerplate code amount when working with data fetching, updating, committing and rollbacking
17
16
 
18
- The library is highly linked with Redux library and uses Redux as an internal storage
17
+ ## What is cdeebee?
18
+
19
+ cdeebee is a data management library built on top of Redux Toolkit that aims to:
20
+ - Provide a uniform way of accessing data
21
+ - Decrease boilerplate code when working with data fetching, updating, committing, and rollbacking
22
+ - Manage normalized data structures similar to relational databases
23
+ - Handle API requests with built-in normalization, error handling, and request cancellation
19
24
 
20
- cdeebee works like traditional relational database and stores data lists in JS object (*list*) with keys as table names
25
+ ## Core Concepts
21
26
 
22
- For instance simple example how to structure forum case:
23
- ```js
27
+ ### Normalized Storage
28
+
29
+ cdeebee stores data in a normalized structure similar to a relational database. Data is organized into **lists** (tables) where each list is a JavaScript object with keys representing primary keys (IDs) of entities.
30
+
31
+ For example, a forum application might have this structure:
32
+
33
+ ```typescript
24
34
  {
25
- forumList: { },
26
- threadList: { },
27
- postList: { }
35
+ forumList: {},
36
+ threadList: {},
37
+ postList: {}
28
38
  }
29
39
  ```
30
40
 
31
- Each *list* is JS object with keys ~~~ primary keys (ID) of objects
32
- For instance for the sample above the whole cdeebee inside Redux will look like for the forum devoted to the discussion about Universe
33
- ```js
41
+ After fetching data, the storage might look like:
42
+
43
+ ```typescript
34
44
  {
35
- forumList: { 1: { title: ‘Milky Way Galaxy’ } },
36
- threadList: { 10001: { title: ‘Solar system’, forumID: 1 } },
37
- postList: { 2: { title: ‘Earth’, threadID: 10001 } }
45
+ forumList: {
46
+ 1: { id: 1, title: 'Milky Way Galaxy' }
47
+ },
48
+ threadList: {
49
+ 10001: { id: 10001, title: 'Solar system', forumID: 1 }
50
+ },
51
+ postList: {
52
+ 2: { id: 2, title: 'Earth', threadID: 10001 }
53
+ }
38
54
  }
39
-
40
55
  ```
41
56
 
42
- There are several standard functions to work with this structure:
43
- 1. Edit field set of object
44
- 2. Create new object
45
- 3. Remove object
46
- 4. Commit changes
47
- 5. Revert changes
48
- 6. Set new entity for the key
49
- 7. Atomic update for many lists (this option is used when client receives new data from API)
57
+ ### Modules
50
58
 
51
- In addition it’s highly recommended to adopt API to work with cdeebee. But otherwise you could use normalizr before using cdeebee to change your data structure from tree-like JSON to normalised lists
59
+ cdeebee uses a modular architecture with the following modules:
52
60
 
53
- Finally there is a set of tools to work with API:
54
- - CdeebeeRequest function with is making request to server and manage queue with active requests
55
- - data normalisation and updating in cdeebee
56
- - necessary set of callbacks (preUpdate, preSuccess, postSuccess, preError)
61
+ - **`storage`**: Automatically normalizes and stores API responses
62
+ - **`history`**: Tracks request history (successful and failed requests)
63
+ - **`listener`**: Tracks active requests for loading states
64
+ - **`cancelation`**: Manages request cancellation (automatically cancels previous requests to the same API)
57
65
 
58
- ## Install
59
- ```js
60
- // reducer/index.js
61
- import { cdeebee, requestManager } from '@recats/cdeebee';
66
+ ## Quick Start
62
67
 
63
- export default combineReducers({
64
- cdeebeee,
65
- requestManager, // optional (checkNetworkActivity, cancelationRequest)
66
- })
68
+ ### 1. Setup Redux Store
67
69
 
70
+ ```typescript
71
+ import { configureStore, combineSlices } from '@reduxjs/toolkit';
72
+ import { factory } from '@recats/cdeebee';
68
73
 
69
- // Usage
70
- // actions/*.js
71
- import { CdeebeeRequest, cdeebeeMergeStrategy } from '@recats/cdeebee';
74
+ // Define your storage structure
75
+ interface Storage {
76
+ forumList: Record<number, { id: number; title: string }>;
77
+ threadList: Record<number, { id: number; title: string; forumID: number }>;
78
+ postList: Record<number, { id: number; title: string; threadID: number }>;
79
+ }
72
80
 
73
- const request = new CdeebeeRequest(
81
+ // Create cdeebee slice
82
+ export const cdeebeeSlice = factory<Storage>(
74
83
  {
75
- // defaultRequest data
76
- data: { sessionToken: 'cdeebee master' },
84
+ modules: ['history', 'listener', 'cancelation', 'storage'],
85
+ fileKey: 'file',
86
+ bodyKey: 'value',
87
+ primaryKey: 'id',
88
+ listStrategy: {
89
+ forumList: 'merge',
90
+ threadList: 'replace',
91
+ },
92
+ mergeWithData: {
93
+ sessionToken: 'your-session-token',
94
+ },
95
+ mergeWithHeaders: {
96
+ 'Authorization': 'Bearer token',
97
+ },
77
98
  },
78
99
  {
79
- // option params
80
- globalErrorHandler: (error, request, meta) => (dispatch, getState) => void,
81
- // default request options
82
- fileKey: 'files',
83
- bodyKey: 'body',
84
- method: 'POST',
85
- primaryKey: 'primaryKey',
86
- normalize: defaultNormalize,
87
- responseKeyCode: 'responseStatus',
88
- header: { 'content-type': 'application/json' },
100
+ // Optional initial storage state
101
+ forumList: {},
102
+ threadList: {},
103
+ postList: {},
89
104
  }
90
- ).send;
91
-
92
- export function requestServer(fn: () => void) {
93
- return (dispatch: Function, getState: Function) => (
94
- request({
95
- api: apiUrl.requestCdeebeee,
96
-
97
- data?: { cdeebee: 'cool' },
98
- files?: File,
99
-
100
- cdeebeeMergeStrategy?: { [listName]: cdeebeeMergeStrategy // default cdeebeeMergeStrategy.merge },
101
- requestStrategy?: $Keys<typeof cdeebeeRequestStrategy> # default undefined,
102
- primaryKey?: string // default 'primaryKey',
103
-
104
- method?: 'POST' | 'GET' | 'PUT' | 'DELETE',
105
- headers?: object,
106
- responseCode?: string,
107
- requestCancel?: boolean, // default true
108
- updateStore?: boolean, // default true
109
- normalize?: ({ response, cdeebee, mergeStrategy }) => Object,
110
-
111
- // files
112
- bodyKey: 'body',
113
- fileKey: 'file',
114
-
115
- // key
116
- responseKeyCode: 'responseStatus',
117
-
118
- preUpdate?: (payload: object) => void,
119
- postUpdate?: (payload: object) => void,
120
- preError?: (payload: object) => void,
121
- postError?: (payload: object) => void,
122
- })(dispatch, getState)
105
+ );
106
+
107
+ // Combine with other reducers
108
+ const rootReducer = combineSlices(cdeebeeSlice);
109
+
110
+ export const store = configureStore({
111
+ reducer: rootReducer,
112
+ });
113
+ ```
114
+
115
+ ### 2. Make API Requests
116
+
117
+ ```typescript
118
+ import { request } from '@recats/cdeebee';
119
+ import { useAppDispatch } from './hooks';
120
+
121
+ function MyComponent() {
122
+ const dispatch = useAppDispatch();
123
+
124
+ const fetchForums = () => {
125
+ dispatch(request({
126
+ api: '/api/forums',
127
+ method: 'POST',
128
+ body: { filter: 'active' },
129
+ onResult: (result) => {
130
+ console.log('Request completed:', result);
131
+ },
132
+ }));
133
+ };
134
+
135
+ return <button onClick={fetchForums}>Load Forums</button>;
136
+ }
137
+ ```
138
+
139
+ ### 3. Access Data and Loading States
140
+
141
+ ```typescript
142
+ import { useAppSelector } from './hooks';
143
+
144
+ function ForumsList() {
145
+ const forums = useAppSelector(state => state.cdeebee.storage.forumList);
146
+ const activeRequests = useAppSelector(state => state.cdeebee.request.active);
147
+ const isLoading = activeRequests.some(req => req.api === '/api/forums');
148
+
149
+ return (
150
+ <div>
151
+ {isLoading && <p>Loading...</p>}
152
+ {Object.values(forums).map(forum => (
153
+ <div key={forum.id}>{forum.title}</div>
154
+ ))}
155
+ </div>
123
156
  );
124
157
  }
125
158
  ```
126
159
 
127
- ## Methods
128
- ```js
129
- cdeebee
130
- cdeebeeHelpers
131
- requestManager
132
- CdeebeeRequest // class
133
- cdeebeeTypes
134
- cdeebeeEntityState
135
- cdeebeeMergeStrategy // merge storage strategy
136
- cdeebeeValueList
137
- cdeebeeActions
138
- cdeebeActiveRequest
139
- cdeebeeIActions
160
+ ## Configuration
161
+
162
+ ### Settings
163
+
164
+ The `factory` function accepts a settings object with the following options:
165
+
166
+ ```typescript
167
+ interface CdeebeeSettings<T> {
168
+ modules: CdeebeeModule[]; // Active modules: 'history' | 'listener' | 'storage' | 'cancelation'
169
+ fileKey: string; // Key name for file uploads in FormData
170
+ bodyKey: string; // Key name for request body in FormData
171
+ primaryKey: string; // Primary key field name in API responses (default: 'primaryKey')
172
+ listStrategy?: CdeebeeListStrategy<T>; // Merge strategy per list: 'merge' | 'replace'
173
+ mergeWithData?: unknown; // Data to merge with every request body
174
+ mergeWithHeaders?: Record<string, string>; // Headers to merge with every request
175
+ normalize?: (storage, result, strategyList) => T; // Custom normalization function
176
+ }
140
177
  ```
141
178
 
179
+ ### Request Options
180
+
181
+ ```typescript
182
+ interface CdeebeeRequestOptions<T> {
183
+ api: string; // API endpoint URL
184
+ method?: 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH';
185
+ body?: unknown; // Request body
186
+ headers?: Record<string, string>; // Additional headers (merged with mergeWithHeaders)
187
+ files?: File[]; // Files to upload
188
+ fileKey?: string; // Override default fileKey
189
+ bodyKey?: string; // Override default bodyKey
190
+ listStrategy?: CdeebeeListStrategy<T>; // Override list strategy for this request
191
+ normalize?: (storage, result, strategyList) => T; // Override normalization
192
+ onResult?: (response: T) => void; // Callback called with response data on success
193
+ }
194
+ ```
142
195
 
143
- ## Actions
144
- ```js
145
- // setKeyValue
146
- import { cdeebeeActions } form '@recats/cdeebee';
196
+ ## Data Merging Strategies
147
197
 
148
- this.props.cdeebeeActions.dropRequestByApiUrl: (api: string);
149
- this.props.cdeebeeActions.dropErrorsByApiUrl: (api: string);
150
- this.props.cdeebeeActions.dropCdeebeePath(path: (string | number)[]);
198
+ cdeebee supports two strategies for merging data:
151
199
 
152
- // these method are not recommended for usage because is not safe
153
- this.props.cdeebeeActions.unsafe_updateStore(
154
- entityList: string,
155
- entityID: EntityID,
156
- value: any, // any value
157
- )
200
+ - **`merge`**: Merges new data with existing data, preserving existing keys not in the response
201
+ - **`replace`**: Completely replaces the list with new data
158
202
 
159
- this.props.cdeebeeActions.setKeyValue(
160
- entityList: string,
161
- entityID: EntityID,
162
- valueList: cdeebeeValueList,
163
- )
203
+ ```typescript
204
+ listStrategy: {
205
+ forumList: 'merge', // New forums are merged with existing ones
206
+ threadList: 'replace', // Thread list is completely replaced
207
+ }
208
+ ```
209
+
210
+ ## API Response Format
164
211
 
165
- this.props.cdeebeeActions.commitEntity(
166
- entityList: string,
167
- entityID: EntityID,
168
- entity: object,
169
- )
212
+ cdeebee expects API responses in a specific format for automatic normalization:
170
213
 
171
- this.props.cdeebeeActions.resetEntity(
172
- entityList: string,
173
- entityID: EntityID,
174
- )
214
+ ```typescript
215
+ {
216
+ forumList: {
217
+ primaryKey: 'id', // The field name specified in settings.primaryKey
218
+ data: [
219
+ { id: 1, title: 'Forum 1' },
220
+ { id: 2, title: 'Forum 2' },
221
+ ]
222
+ },
223
+ threadList: {
224
+ primaryKey: 'id',
225
+ data: [
226
+ { id: 101, title: 'Thread 1', forumID: 1 },
227
+ ]
228
+ }
229
+ }
230
+ ```
231
+
232
+ The library automatically normalizes this into:
233
+
234
+ ```typescript
235
+ {
236
+ forumList: {
237
+ 1: { id: 1, title: 'Forum 1' },
238
+ 2: { id: 2, title: 'Forum 2' },
239
+ },
240
+ threadList: {
241
+ 101: { id: 101, title: 'Thread 1', forumID: 1 },
242
+ }
243
+ }
244
+ ```
245
+
246
+ ## Advanced Usage
247
+
248
+ ### File Uploads
249
+
250
+ ```typescript
251
+ const file = new File(['content'], 'document.pdf', { type: 'application/pdf' });
252
+
253
+ dispatch(request({
254
+ api: '/api/upload',
255
+ method: 'POST',
256
+ files: [file],
257
+ body: { description: 'My document' },
258
+ fileKey: 'file', // Optional: override default
259
+ bodyKey: 'metadata', // Optional: override default
260
+ }));
261
+ ```
262
+
263
+ ### Custom Headers
264
+
265
+ ```typescript
266
+ // Global headers (in settings)
267
+ mergeWithHeaders: {
268
+ 'Authorization': 'Bearer token',
269
+ 'X-Custom-Header': 'value',
270
+ }
271
+
272
+ // Per-request headers (override global)
273
+ dispatch(request({
274
+ api: '/api/data',
275
+ headers: {
276
+ 'Authorization': 'Bearer different-token', // Overrides global
277
+ 'X-Request-ID': '123', // Additional header
278
+ },
279
+ }));
280
+ ```
281
+
282
+ ### Request Cancellation
283
+
284
+ When the `cancelation` module is enabled, cdeebee automatically cancels previous requests to the same API endpoint when a new request is made:
285
+
286
+ ```typescript
287
+ // First request
288
+ dispatch(request({ api: '/api/data', body: { query: 'slow' } }));
289
+
290
+ // Second request to same API - first one is automatically cancelled
291
+ dispatch(request({ api: '/api/data', body: { query: 'fast' } }));
175
292
  ```
176
293
 
177
- ## Helpers
178
- ```js
179
- import { cdeebeeHelpers } from '@recats/cdeebee';
294
+ ### Manual State Updates
295
+
296
+ You can manually update the storage using the `set` action:
180
297
 
181
- // requestCancel
182
- cdeebeeHelpers.requestCancel(activeRequest: cdeebeActiveRequest) => void;
298
+ ```typescript
299
+ import { batchingUpdate } from '@recats/cdeebee';
183
300
 
184
- // checkNetworkActivity
185
- cdeebeeHelpers.checkNetworkActivity(activeRequest: cdeebeActiveRequest, apiUrl: string | Array<string>) => boolean;
301
+ // Update multiple values at once
302
+ const updates = [
303
+ { key: ['forumList', '1', 'title'], value: 'New Title' },
304
+ { key: ['forumList', '1', 'views'], value: 100 },
305
+ { key: ['threadList', '101', 'title'], value: 'Updated Thread' },
306
+ ];
186
307
 
187
- // getSubEntity element in cdeebee list
188
- cdeebeeHelpers.getSubEntity(entity: object) => object;
308
+ dispatch(cdeebeeSlice.actions.set(updates));
309
+ ```
189
310
 
190
- // getEntityState element in cdeebee list
191
- cdeebeeHelpers.getEntityState(entity: object) => string;
311
+ ### Accessing Request History
192
312
 
193
- // commitEntity element in cdeebee list
194
- cdeebeeHelpers.commitEntity(entity: object) => void;
313
+ ```typescript
314
+ const doneRequests = useAppSelector(state => state.cdeebee.request.done);
315
+ const errors = useAppSelector(state => state.cdeebee.request.errors);
195
316
 
196
- // resetEntity element in cdeebee list
197
- cdeebeeHelpers.resetEntity(entity: object) => void;
317
+ // Get history for specific API
318
+ const apiHistory = doneRequests['/api/forums'] || [];
319
+ const apiErrors = errors['/api/forums'] || [];
198
320
  ```
199
321
 
200
- ## Data merging behavior
201
- During data merging cdeebee could behave in different ways according to the enum value which is passed during request
322
+ ## TypeScript Support
323
+
324
+ cdeebee is fully typed. Define your storage type and get full type safety:
325
+
326
+ ```typescript
327
+ interface MyStorage {
328
+ userList: Record<string, { id: string; name: string; email: string }>;
329
+ postList: Record<number, { id: number; title: string; userId: string }>;
330
+ }
331
+
332
+ const slice = factory<MyStorage>(settings);
333
+
334
+ // TypeScript knows the structure
335
+ const users = useSelector(state => state.cdeebee.storage.userList);
336
+ // users: Record<string, { id: string; name: string; email: string }>
337
+ ```
338
+
339
+ ## Exports
340
+
341
+ ```typescript
342
+ // Main exports
343
+ export { factory } from '@recats/cdeebee'; // Create cdeebee slice
344
+ export { request } from '@recats/cdeebee'; // Request thunk
345
+ export { batchingUpdate } from '@recats/cdeebee'; // Batch update helper
346
+
347
+ // Types
348
+ export type {
349
+ CdeebeeState,
350
+ CdeebeeSettings,
351
+ CdeebeeRequestOptions,
352
+ CdeebeeValueList,
353
+ CdeebeeActiveRequest,
354
+ } from '@recats/cdeebee';
355
+ ```
356
+
357
+ ## Examples
358
+
359
+ See the `example/` directory in the repository for a complete working example with Next.js.
360
+
361
+ ## License
202
362
 
203
- - *merge* uses ramda mergeDeepRight strategy to merge existing object with the new one
204
- - *replace* overrides the object
363
+ MIT
package/dist/index.cjs ADDED
@@ -0,0 +1,2 @@
1
+ "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const K=require("@reduxjs/toolkit");function d(r,n,i){r.modules.includes(n)&&i()}function f(r){return r!==null&&typeof r=="object"&&!Array.isArray(r)}function C(r){return f(r)&&Array.isArray(r.data)}function z(r,n){return f(r)&&Object.prototype.hasOwnProperty.call(r,n)}function w(r,n){if(!f(r)||!f(n))return n;const i={...r},a=n;for(const e in a)if(Object.prototype.hasOwnProperty.call(a,e)){const c=i[e],t=a[e];f(c)&&f(t)&&!Array.isArray(c)&&!Array.isArray(t)?i[e]=w(c,t):i[e]=t}return i}function O(r,n){const i={...n};for(const a of r)delete i[a];return i}function D(r,n){for(let i=0;i<n.length;i++){const a=n[i],e=a.key,c=a.value;if(e.length===0)continue;let t=r;for(let o=0;o<e.length-1;o++){const s=e[o];if(Array.isArray(t)){const l=typeof s=="number"?s:Number(s);(!(l in t)||!f(t[l]))&&(t[l]={}),t=t[l]}else{const l=String(s);if(!(l in t)){const y=typeof e[o+1]=="number"||!isNaN(Number(e[o+1]))&&String(Number(e[o+1]))===String(e[o+1]);t[l]=y?[]:{}}const u=t[l];t=Array.isArray(u)||f(u)?u:{}}}Array.isArray(t)||(t[String(e[e.length-1])]=c)}}class I{constructor(){this.byRequestId=new Map,this.byApi=new Map}add(n,i,a){const e={requestId:i,controller:a,api:n};this.byRequestId.set(i,e),this.byApi.has(n)||this.byApi.set(n,new Set),this.byApi.get(n).add(i)}delete(n){const i=this.byRequestId.get(n);if(!i)return;this.byRequestId.delete(n);const a=this.byApi.get(i.api);a&&(a.delete(n),a.size===0&&this.byApi.delete(i.api))}abortAllForApi(n,i){const a=this.byApi.get(n);a&&a.forEach(e=>{if(e!==i){const c=this.byRequestId.get(e);c&&(c.controller.abort(),this.delete(e))}})}}const R=new I;function N(r,n){R.abortAllForApi(r,n)}function P(r,n,i){const a=new AbortController,e=()=>{R.delete(i)};return r.addEventListener("abort",()=>{a.abort(),e()}),{controller:a,init:()=>R.add(n,i,a),drop:e}}const q=K.createAsyncThunk("cdeebee/request",async(r,{rejectWithValue:n,getState:i,requestId:a,signal:e})=>{const c=new Date().toUTCString(),{cdeebee:{settings:t}}=i(),o=P(e,r.api,a);d(t,"cancelation",o.init);try{const{method:s="POST",body:l,headers:u={}}=r,y={...t.mergeWithHeaders??{},...u},A={...t.mergeWithData??{},...l??{}};let b=JSON.stringify(A);if(r.files){const p=new FormData,m=r.fileKey||t.fileKey,S=r.bodyKey||t.bodyKey;for(let k=0;k<r.files.length;k+=1)m&&p.append(m,r.files[k]);S&&p.append(S,b),b=p}const g=await fetch(r.api,{method:s,headers:{"ui-request-id":a,"Content-Type":"application/json",...y},signal:o.controller.signal,body:b});if(d(t,"cancelation",o.drop),!g.ok)return n(g);const h=await g.json();return r.onResult&&typeof r.onResult=="function"&&r.onResult(h),{result:h,startedAt:c,endedAt:new Date().toUTCString()}}catch(s){return d(t,"cancelation",o.drop),s instanceof Error&&s.name==="AbortError"?n({message:"Request was cancelled",cancelled:!0}):n({message:s instanceof Error?s.message:"Unknown error occurred"})}});function j(r,n,i){const a=Object.keys(n),e=r.settings.primaryKey,c=f(r.storage)?r.storage:{},t={...c},o=new Set;for(const s of a){const l=n[s];if(l==null||typeof l=="string"){o.add(s);continue}if(C(l)&&z(l,e)){const u=l[e];if(typeof u!="string"){console.warn(`Cdeebee: Primary key "${e}" is not a string for API "${s}". Skipping normalization.`),t[s]=l;continue}const y={},A=l.data,b=A.length;for(let p=0;p<b;p++){const m=A[p];if(f(m)&&m[u]){const S=m[u];y[S]=m}}const g=i[s]??"merge",h=s in c?c[s]:{};g==="replace"?t[s]=y:(g==="merge"||console.warn(`Cdeebee: Unknown strategy "${g}" for key "${s}". Skipping normalization.`),t[s]=w(h,y))}else t[s]=l}return o.size>0?O(Array.from(o),t):t}const v={settings:{modules:["history","listener","storage","cancelation"],fileKey:"file",bodyKey:"value",primaryKey:"primaryKey",listStrategy:{},mergeWithData:{},mergeWithHeaders:{}},storage:{},request:{active:[],errors:{},done:{}}},T=(r,n)=>K.createSlice({name:"cdeebee",initialState:w(v,{settings:r,storage:n??{}}),reducers:{set(a,e){D(a.storage,e.payload)}},extraReducers:a=>{a.addCase(q.pending,(e,c)=>{const t=c.meta.arg.api,o=c.meta.requestId;d(e.settings,"cancelation",()=>{N(t,o)}),d(e.settings,"listener",()=>{e.request.active.push({api:t,requestId:o})})}).addCase(q.fulfilled,(e,c)=>{const t=c.meta.requestId,o=c.meta.arg.api;d(e.settings,"listener",()=>{e.request.active=e.request.active.filter(s=>!(s.api===o&&s.requestId===t))}),d(e.settings,"history",()=>{e.request.done[o]||(e.request.done[o]=[]),e.request.done[o].push({api:o,request:c.payload,requestId:t})}),d(e.settings,"storage",()=>{const s=c.meta.arg.listStrategy??e.settings.listStrategy??{},l=c.meta.arg.normalize??e.settings.normalize??j,u=K.current(e),y=l(u,c.payload.result,s);e.storage=y})}).addCase(q.rejected,(e,c)=>{const t=c.meta.requestId,o=c.meta.arg.api;d(e.settings,"listener",()=>{e.request.active=e.request.active.filter(s=>!(s.api===o&&s.requestId===t))}),d(e.settings,"history",()=>{e.request.errors[o]||(e.request.errors[o]=[]),e.request.errors[o].push({requestId:t,api:o,request:c.error})})})}});exports.batchingUpdate=D;exports.factory=T;exports.request=q;
2
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.cjs","sources":["../lib/reducer/helpers.ts","../lib/reducer/abortController.ts","../lib/reducer/request.ts","../lib/reducer/storage.ts","../lib/reducer/index.ts"],"sourcesContent":["import { type CdeebeeSettings, type CdeebeeModule, CdeebeeValueList } from './types';\n\nexport function checkModule(settings: CdeebeeSettings<unknown>, module: CdeebeeModule, result: () => void) {\n if (settings.modules.includes(module)) {\n result();\n }\n}\nexport function isRecord(value: unknown): value is Record<string, unknown> {\n return value !== null && typeof value === 'object' && !Array.isArray(value);\n}\n\nexport function hasDataProperty(value: unknown): value is Record<string, unknown> & { data: unknown[] } {\n return isRecord(value) && Array.isArray(value.data);\n}\n\nexport function hasProperty(value: unknown, prop: string): boolean {\n return isRecord(value) && Object.prototype.hasOwnProperty.call(value, prop);\n}\n\nexport function mergeDeepRight<T>(\n left: T,\n right: Partial<T> | Record<string, unknown>\n): T {\n if (!isRecord(left) || !isRecord(right)) {\n return right as T;\n }\n\n const result = { ...left } as Record<string, unknown>;\n const rightRecord = right as Record<string, unknown>;\n\n for (const key in rightRecord) {\n if (Object.prototype.hasOwnProperty.call(rightRecord, key)) {\n const leftValue = result[key];\n const rightValue = rightRecord[key];\n\n if (\n isRecord(leftValue) &&\n isRecord(rightValue) &&\n !Array.isArray(leftValue) &&\n !Array.isArray(rightValue)\n ) {\n result[key] = mergeDeepRight(leftValue, rightValue);\n } else {\n result[key] = rightValue;\n }\n }\n }\n\n return result as T;\n}\n\nexport function omit<T extends Record<string, unknown>>(keys: string[], obj: T): Omit<T, keyof T> {\n const result = { ...obj };\n for (const key of keys) {\n delete result[key];\n }\n return result as Omit<T, keyof T>;\n}\n\nexport function assocPath<T>(path: (string | number)[], value: unknown, obj: T): T {\n if (path.length === 0) {\n return value as T;\n }\n\n const [first, ...rest] = path;\n const firstKey = String(first);\n const result = Array.isArray(obj) ? [...obj] : { ...obj } as Record<string, unknown>;\n\n if (rest.length === 0) {\n (result as Record<string, unknown>)[firstKey] = value;\n } else {\n const currentValue = (result as Record<string, unknown>)[firstKey];\n (result as Record<string, unknown>)[firstKey] = assocPath(rest, value, currentValue ?? {});\n }\n\n return result as T;\n}\n\nexport function batchingUpdate<T extends Record<string, unknown>>(\n state: T,\n valueList: CdeebeeValueList<T>\n): void {\n for (let i = 0; i < valueList.length; i++) {\n const item = valueList[i] as { key: readonly (string | number)[]; value: unknown };\n const path = item.key;\n const value = item.value;\n \n if (path.length === 0) {\n continue;\n }\n\n let current: Record<string, unknown> | unknown[] = state as Record<string, unknown>;\n \n for (let j = 0; j < path.length - 1; j++) {\n const pathKey = path[j];\n \n if (Array.isArray(current)) {\n const index = typeof pathKey === 'number' ? pathKey : Number(pathKey);\n if (!(index in current) || !isRecord(current[index])) {\n current[index] = {};\n }\n current = current[index] as Record<string, unknown>;\n } else {\n const key = String(pathKey);\n if (!(key in current)) {\n const nextIsNumeric = typeof path[j + 1] === 'number' || (!isNaN(Number(path[j + 1])) && String(Number(path[j + 1])) === String(path[j + 1]));\n current[key] = nextIsNumeric ? [] : {};\n }\n const next = current[key];\n current = (Array.isArray(next) ? next : (isRecord(next) ? next : {})) as Record<string, unknown> | unknown[];\n }\n }\n \n if (Array.isArray(current)) {\n continue; // Can't update array element directly\n }\n current[String(path[path.length - 1])] = value;\n }\n}\n","interface RequestController {\n requestId: string;\n controller: AbortController;\n api: string;\n}\n\nclass AbortControllerStore {\n private byRequestId = new Map<string, RequestController>();\n private byApi = new Map<string, Set<string>>();\n\n add(api: string, requestId: string, controller: AbortController): void {\n const item: RequestController = { requestId, controller, api };\n this.byRequestId.set(requestId, item);\n\n if (!this.byApi.has(api)) {\n this.byApi.set(api, new Set());\n }\n this.byApi.get(api)!.add(requestId);\n }\n\n delete(requestId: string): void {\n const item = this.byRequestId.get(requestId);\n if (!item) return;\n\n this.byRequestId.delete(requestId);\n const apiSet = this.byApi.get(item.api);\n if (apiSet) {\n apiSet.delete(requestId);\n if (apiSet.size === 0) {\n this.byApi.delete(item.api);\n }\n }\n }\n\n abortAllForApi(api: string, excludeRequestId: string): void {\n const requestIds = this.byApi.get(api);\n if (!requestIds) return;\n\n requestIds.forEach(requestId => {\n if (requestId !== excludeRequestId) {\n const item = this.byRequestId.get(requestId);\n if (item) {\n item.controller.abort();\n this.delete(requestId);\n }\n }\n });\n }\n}\n\nconst abortStore = new AbortControllerStore();\n\nexport function abortQuery(api: string, currentRequestId: string): void {\n abortStore.abortAllForApi(api, currentRequestId);\n}\n\nexport function abortManager(signal: AbortSignal, api: string, requestId: string) {\n const controller = new AbortController();\n\n const cleanup = () => {\n abortStore.delete(requestId);\n };\n\n signal.addEventListener('abort', () => {\n controller.abort();\n cleanup();\n });\n\n return {\n controller,\n init: () => abortStore.add(api, requestId, controller),\n drop: cleanup,\n };\n}\n","import { createAsyncThunk } from '@reduxjs/toolkit';\nimport { checkModule } from './helpers';\nimport { abortManager } from './abortController';\nimport { type CdeebeeState, type CdeebeeRequestOptions } from './types';\n\nexport const request = createAsyncThunk(\n 'cdeebee/request',\n async (options: CdeebeeRequestOptions<unknown>, { rejectWithValue, getState, requestId, signal }) => {\n const startedAt = new Date().toUTCString();\n const { cdeebee: { settings } } = getState() as { cdeebee: CdeebeeState<unknown> };\n\n const abort = abortManager(signal, options.api, requestId);\n\n checkModule(settings, 'cancelation', abort.init);\n\n try {\n const { method = 'POST', body, headers = {} } = options;\n const extraHeaders: Record<string, string> = { ...(settings.mergeWithHeaders ?? {}), ...headers };\n\n const b = { ...(settings.mergeWithData ?? {}), ...(body ?? {}) };\n let requestData: FormData | string = JSON.stringify(b);\n\n // handling files\n if (options.files) {\n const formData = new FormData();\n const fileKey = options.fileKey || settings.fileKey;\n const bodyKey = options.bodyKey || settings.bodyKey;\n\n for (let i = 0; i < options.files.length; i += 1) {\n if (fileKey) {\n formData.append(fileKey, options.files[i]);\n }\n }\n\n if (bodyKey) {\n formData.append(bodyKey, requestData);\n }\n requestData = formData;\n }\n // [end] handling files\n \n const response = await fetch(options.api, {\n method,\n headers: {\n 'ui-request-id': requestId,\n 'Content-Type': 'application/json',\n ...extraHeaders,\n },\n signal: abort.controller.signal,\n body: requestData,\n });\n\n checkModule(settings, 'cancelation', abort.drop);\n\n if (!response.ok) {\n return rejectWithValue(response);\n }\n const result = await response.json();\n if (options.onResult && typeof options.onResult === 'function') {\n options.onResult(result);\n }\n return { result, startedAt, endedAt: new Date().toUTCString() };\n } catch (error) {\n checkModule(settings, 'cancelation', abort.drop);\n if (error instanceof Error && error.name === 'AbortError') {\n return rejectWithValue({\n message: 'Request was cancelled',\n cancelled: true,\n });\n }\n return rejectWithValue({\n message: error instanceof Error ? error.message : 'Unknown error occurred',\n });\n }\n },\n);\n\n","import { type CdeebeeListStrategy, type CdeebeeState } from './types';\nimport { hasDataProperty, hasProperty, isRecord, mergeDeepRight, omit } from './helpers';\n\ntype ResponseValue = Record<string, unknown> & {\n data?: unknown[];\n [key: string]: unknown;\n};\n\ntype IResponse = Record<string, ResponseValue>;\n\ntype StorageData = Record<string, unknown>;\n\nexport function defaultNormalize<T>(\n cdeebee: CdeebeeState<T>,\n response: IResponse,\n strategyList: CdeebeeListStrategy<T> \n): Record<string, ResponseValue> {\n const keyList = Object.keys(response);\n const primaryKey = cdeebee.settings.primaryKey;\n const currentStorage = isRecord(cdeebee.storage) ? (cdeebee.storage as Record<string, unknown>) : {};\n \n // Start with existing storage to preserve keys not in response\n const result = { ...currentStorage } as Record<string, ResponseValue>;\n const keyListToOmit = new Set<string>();\n\n for (const key of keyList) {\n const responseValue = response[key];\n\n if (responseValue === null || responseValue === undefined || typeof responseValue === 'string') {\n keyListToOmit.add(key);\n continue;\n }\n\n if (hasDataProperty(responseValue) && hasProperty(responseValue, primaryKey)) {\n const primaryKeyValue = responseValue[primaryKey];\n \n if (typeof primaryKeyValue !== 'string') {\n console.warn(`Cdeebee: Primary key \"${primaryKey}\" is not a string for API \"${key}\". Skipping normalization.`);\n result[key] = responseValue;\n continue;\n }\n\n // Pre-allocate storage data object\n const newStorageData: StorageData = {};\n const dataArray = responseValue.data;\n const dataLength = dataArray.length;\n\n for (let i = 0; i < dataLength; i++) {\n const element = dataArray[i];\n if (isRecord(element) && element[primaryKeyValue]) {\n const elementKey = element[primaryKeyValue] as string;\n newStorageData[elementKey] = element;\n }\n }\n\n const strategy = strategyList[key as keyof T] ?? 'merge';\n const existingValue = key in currentStorage ? (currentStorage[key] as StorageData) : {};\n\n if (strategy === 'replace') {\n // Replace: completely replace the value\n result[key] = newStorageData as ResponseValue;\n } else if (strategy === 'merge') {\n // Merge: merge with existing value\n result[key] = mergeDeepRight(existingValue, newStorageData) as ResponseValue;\n } else {\n // Unknown strategy: warn and fall back to merge\n console.warn(`Cdeebee: Unknown strategy \"${strategy}\" for key \"${key}\". Skipping normalization.`);\n result[key] = mergeDeepRight(existingValue, newStorageData) as ResponseValue;\n }\n } else {\n result[key] = responseValue;\n }\n }\n\n return keyListToOmit.size > 0 ? omit(Array.from(keyListToOmit), result) : result;\n}\n","import { createSlice, current } from '@reduxjs/toolkit';\n\nimport { type CdeebeeSettings, type CdeebeeState, type CdeebeeValueList } from './types';\nimport { checkModule, mergeDeepRight, batchingUpdate } from './helpers';\nimport { abortQuery } from './abortController';\nimport { request } from './request';\nimport { defaultNormalize } from './storage';\n\nconst initialState: CdeebeeState<unknown> = {\n settings: {\n modules: ['history', 'listener', 'storage', 'cancelation'],\n fileKey: 'file',\n bodyKey: 'value',\n primaryKey: 'primaryKey',\n listStrategy: {},\n mergeWithData: {},\n mergeWithHeaders: {},\n },\n storage: {},\n request: {\n active: [],\n errors: {},\n done: {}\n },\n};\n\nexport const factory = <T>(settings: CdeebeeSettings<T>, storage?: T) => {\n const slice = createSlice({\n name: 'cdeebee',\n initialState: mergeDeepRight(initialState, { settings, storage: storage ?? {} }) as CdeebeeState<T>,\n reducers: {\n set(state, action: { payload: CdeebeeValueList<T> }) {\n // Directly mutate state.storage using Immer Draft\n // This is more performant than creating a new object\n // Immer will track changes and create minimal updates\n batchingUpdate(state.storage as Record<string, unknown>, action.payload);\n }\n },\n extraReducers: builder => {\n builder\n .addCase(request.pending, (state, action) => {\n const api = action.meta.arg.api;\n const requestId = action.meta.requestId;\n\n checkModule(state.settings, 'cancelation', () => {\n abortQuery(api, requestId);\n });\n checkModule(state.settings, 'listener', () => {\n state.request.active.push({ api, requestId });\n });\n })\n .addCase(request.fulfilled, (state, action) => {\n const requestId = action.meta.requestId;\n const api = action.meta.arg.api;\n\n checkModule(state.settings, 'listener', () => {\n state.request.active = state.request.active.filter(q => !(q.api === api && q.requestId === requestId));\n });\n checkModule(state.settings, 'history', () => {\n if (!state.request.done[api]) state.request.done[api] = [];\n state.request.done[api].push({ api, request: action.payload, requestId });\n });\n checkModule(state.settings, 'storage', () => {\n const strategyList = action.meta.arg.listStrategy ?? state.settings.listStrategy ?? {};\n const normalize = action.meta.arg.normalize ?? state.settings.normalize ?? defaultNormalize;\n\n const currentState = current(state) as CdeebeeState<T>;\n const normalizedData = normalize(currentState, action.payload.result, strategyList);\n\n // Normalize already handles merge/replace and preserves keys not in response\n // Simply apply the result\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n (state.storage as any) = normalizedData;\n });\n })\n .addCase(request.rejected, (state, action) => {\n const requestId = action.meta.requestId;\n const api = action.meta.arg.api;\n \n checkModule(state.settings, 'listener', () => {\n state.request.active = state.request.active.filter(q => !(q.api === api && q.requestId === requestId));\n });\n checkModule(state.settings, 'history', () => {\n if (!state.request.errors[api]) state.request.errors[api] = [];\n state.request.errors[api].push({ requestId: requestId, api, request: action.error });\n });\n });\n },\n });\n\n return slice;\n};\n"],"names":["checkModule","settings","module","result","isRecord","value","hasDataProperty","hasProperty","prop","mergeDeepRight","left","right","rightRecord","key","leftValue","rightValue","omit","keys","obj","batchingUpdate","state","valueList","item","path","current","j","pathKey","index","nextIsNumeric","next","AbortControllerStore","api","requestId","controller","apiSet","excludeRequestId","requestIds","abortStore","abortQuery","currentRequestId","abortManager","signal","cleanup","request","createAsyncThunk","options","rejectWithValue","getState","startedAt","abort","method","body","headers","extraHeaders","b","requestData","formData","fileKey","bodyKey","i","response","error","defaultNormalize","cdeebee","strategyList","keyList","primaryKey","currentStorage","keyListToOmit","responseValue","primaryKeyValue","newStorageData","dataArray","dataLength","element","elementKey","strategy","existingValue","initialState","factory","storage","createSlice","action","builder","q","normalize","currentState","normalizedData"],"mappings":"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"}