@recats/cdeebee 2.3.7 → 3.0.0-beta.4

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,339 @@
1
1
  # cdeebee
2
2
 
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)
3
+ A Redux-based data management library that provides a uniform way to access, fetch, update, and manage application data with minimal boilerplate code.
4
+
5
+ ## Installation
7
6
 
8
7
  ```sh
9
- npm i @recats/cdeebee
8
+ npm i @recats/cdeebee@beta
9
+ # or
10
+ yarn add @recats/cdeebee@beta
10
11
  # or
11
- yarn add @recats/cdeebee
12
+ pnpm add @recats/cdeebee@beta
12
13
  ```
13
- # cdeebee
14
- cdeebee is an alpha version of library which goals are:
15
- - Provide uniform way of access to data
16
- - Decrease boilerplate code amount when working with data fetching, updating, committing and rollbacking
17
14
 
18
- The library is highly linked with Redux library and uses Redux as an internal storage
15
+ ## What is cdeebee?
16
+
17
+ cdeebee is a data management library built on top of Redux Toolkit that aims to:
18
+ - Provide a uniform way of accessing data
19
+ - Decrease boilerplate code when working with data fetching, updating, committing, and rollbacking
20
+ - Manage normalized data structures similar to relational databases
21
+ - Handle API requests with built-in normalization, error handling, and request cancellation
22
+
23
+ ## Core Concepts
19
24
 
20
- cdeebee works like traditional relational database and stores data lists in JS object (*list*) with keys as table names
25
+ ### Normalized Storage
21
26
 
22
- For instance simple example how to structure forum case:
23
- ```js
27
+ cdeebee stores data in a normalized structure similar to a relational database. Data is organized into **lists** (tables) where each list is a JavaScript object with keys representing primary keys (IDs) of entities.
28
+
29
+ For example, a forum application might have this structure:
30
+
31
+ ```typescript
24
32
  {
25
- forumList: { },
26
- threadList: { },
27
- postList: { }
33
+ forumList: {},
34
+ threadList: {},
35
+ postList: {}
28
36
  }
29
37
  ```
30
38
 
31
- 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
39
+ After fetching data, the storage might look like:
40
+
41
+ ```typescript
34
42
  {
35
- forumList: { 1: { title: ‘Milky Way Galaxy’ } },
36
- threadList: { 10001: { title: ‘Solar system’, forumID: 1 } },
37
- postList: { 2: { title: ‘Earth’, threadID: 10001 } }
43
+ forumList: {
44
+ 1: { id: 1, title: 'Milky Way Galaxy' }
45
+ },
46
+ threadList: {
47
+ 10001: { id: 10001, title: 'Solar system', forumID: 1 }
48
+ },
49
+ postList: {
50
+ 2: { id: 2, title: 'Earth', threadID: 10001 }
51
+ }
38
52
  }
39
-
40
53
  ```
41
54
 
42
- 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)
55
+ ### Modules
50
56
 
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
57
+ cdeebee uses a modular architecture with the following modules:
52
58
 
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)
59
+ - **`storage`**: Automatically normalizes and stores API responses
60
+ - **`history`**: Tracks request history (successful and failed requests)
61
+ - **`listener`**: Tracks active requests for loading states
62
+ - **`cancelation`**: Manages request cancellation (automatically cancels previous requests to the same API)
57
63
 
58
- ## Install
59
- ```js
60
- // reducer/index.js
61
- import { cdeebee, requestManager } from '@recats/cdeebee';
64
+ ## Quick Start
62
65
 
63
- export default combineReducers({
64
- cdeebeee,
65
- requestManager, // optional (checkNetworkActivity, cancelationRequest)
66
- })
66
+ ### 1. Setup Redux Store
67
67
 
68
+ ```typescript
69
+ import { configureStore, combineSlices } from '@reduxjs/toolkit';
70
+ import { factory } from '@recats/cdeebee';
68
71
 
69
- // Usage
70
- // actions/*.js
71
- import { CdeebeeRequest, cdeebeeMergeStrategy } from '@recats/cdeebee';
72
+ // Define your storage structure
73
+ interface Storage {
74
+ forumList: Record<number, { id: number; title: string }>;
75
+ threadList: Record<number, { id: number; title: string; forumID: number }>;
76
+ postList: Record<number, { id: number; title: string; threadID: number }>;
77
+ }
72
78
 
73
- const request = new CdeebeeRequest(
79
+ // Create cdeebee slice
80
+ export const cdeebeeSlice = factory<Storage>(
74
81
  {
75
- // defaultRequest data
76
- data: { sessionToken: 'cdeebee master' },
82
+ modules: ['history', 'listener', 'cancelation', 'storage'],
83
+ fileKey: 'file',
84
+ bodyKey: 'value',
85
+ listStrategy: {
86
+ forumList: 'merge',
87
+ threadList: 'replace',
88
+ },
89
+ mergeWithData: {
90
+ sessionToken: 'your-session-token',
91
+ },
92
+ mergeWithHeaders: {
93
+ 'Authorization': 'Bearer token',
94
+ },
77
95
  },
78
96
  {
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' },
97
+ // Optional initial storage state
98
+ forumList: {},
99
+ threadList: {},
100
+ postList: {},
89
101
  }
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)
102
+ );
103
+
104
+ // Combine with other reducers
105
+ const rootReducer = combineSlices(cdeebeeSlice);
106
+
107
+ export const store = configureStore({
108
+ reducer: rootReducer,
109
+ });
110
+ ```
111
+
112
+ ### 2. Make API Requests
113
+
114
+ ```typescript
115
+ import { request } from '@recats/cdeebee';
116
+ import { useAppDispatch } from './hooks';
117
+
118
+ function MyComponent() {
119
+ const dispatch = useAppDispatch();
120
+
121
+ const fetchForums = () => {
122
+ dispatch(request({
123
+ api: '/api/forums',
124
+ method: 'POST',
125
+ body: { filter: 'active' },
126
+ onResult: (result) => {
127
+ console.log('Request completed:', result);
128
+ },
129
+ }));
130
+ };
131
+
132
+ return <button onClick={fetchForums}>Load Forums</button>;
133
+ }
134
+ ```
135
+
136
+ ### 3. Access Data and Loading States
137
+
138
+ ```typescript
139
+ import { useAppSelector } from './hooks';
140
+
141
+ function ForumsList() {
142
+ const forums = useAppSelector(state => state.cdeebee.storage.forumList);
143
+ const activeRequests = useAppSelector(state => state.cdeebee.request.active);
144
+ const isLoading = activeRequests.some(req => req.api === '/api/forums');
145
+
146
+ return (
147
+ <div>
148
+ {isLoading && <p>Loading...</p>}
149
+ {Object.values(forums).map(forum => (
150
+ <div key={forum.id}>{forum.title}</div>
151
+ ))}
152
+ </div>
123
153
  );
124
154
  }
125
155
  ```
126
156
 
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
157
+ ## Configuration
158
+
159
+ ### Settings
160
+
161
+ The `factory` function accepts a settings object with the following options:
162
+
163
+ ```typescript
164
+ interface CdeebeeSettings<T> {
165
+ modules: CdeebeeModule[]; // Active modules: 'history' | 'listener' | 'storage' | 'cancelation'
166
+ fileKey: string; // Key name for file uploads in FormData
167
+ bodyKey: string; // Key name for request body in FormData
168
+ listStrategy?: CdeebeeListStrategy<T>; // Merge strategy per list: 'merge' | 'replace'
169
+ mergeWithData?: unknown; // Data to merge with every request body
170
+ mergeWithHeaders?: Record<string, string>; // Headers to merge with every request
171
+ normalize?: (storage, result, strategyList) => T; // Custom normalization function
172
+ }
140
173
  ```
141
174
 
175
+ ### Request Options
176
+
177
+ ```typescript
178
+ interface CdeebeeRequestOptions<T> {
179
+ api: string; // API endpoint URL
180
+ method?: 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH';
181
+ body?: unknown; // Request body
182
+ headers?: Record<string, string>; // Additional headers (merged with mergeWithHeaders)
183
+ files?: File[]; // Files to upload
184
+ fileKey?: string; // Override default fileKey
185
+ bodyKey?: string; // Override default bodyKey
186
+ listStrategy?: CdeebeeListStrategy<T>; // Override list strategy for this request
187
+ normalize?: (storage, result, strategyList) => T; // Override normalization
188
+ onResult?: (response: T) => void; // Callback called with response data on success
189
+ }
190
+ ```
142
191
 
143
- ## Actions
144
- ```js
145
- // setKeyValue
146
- import { cdeebeeActions } form '@recats/cdeebee';
192
+ ## Data Merging Strategies
147
193
 
148
- this.props.cdeebeeActions.dropRequestByApiUrl: (api: string);
149
- this.props.cdeebeeActions.dropErrorsByApiUrl: (api: string);
150
- this.props.cdeebeeActions.dropCdeebeePath(path: (string | number)[]);
194
+ cdeebee supports two strategies for merging data:
151
195
 
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
- )
196
+ - **`merge`**: Merges new data with existing data, preserving existing keys not in the response
197
+ - **`replace`**: Completely replaces the list with new data
158
198
 
159
- this.props.cdeebeeActions.setKeyValue(
160
- entityList: string,
161
- entityID: EntityID,
162
- valueList: cdeebeeValueList,
163
- )
199
+ ```typescript
200
+ listStrategy: {
201
+ forumList: 'merge', // New forums are merged with existing ones
202
+ threadList: 'replace', // Thread list is completely replaced
203
+ }
204
+ ```
164
205
 
165
- this.props.cdeebeeActions.commitEntity(
166
- entityList: string,
167
- entityID: EntityID,
168
- entity: object,
169
- )
206
+ ## API Response Format
170
207
 
171
- this.props.cdeebeeActions.resetEntity(
172
- entityList: string,
173
- entityID: EntityID,
174
- )
208
+ cdeebee expects API responses in a normalized format where data is already organized as objects with keys representing entity IDs:
209
+
210
+ ```typescript
211
+ {
212
+ forumList: {
213
+ 1: { id: 1, title: 'Forum 1' },
214
+ 2: { id: 2, title: 'Forum 2' },
215
+ },
216
+ threadList: {
217
+ 101: { id: 101, title: 'Thread 1', forumID: 1 },
218
+ }
219
+ }
220
+ ```
221
+
222
+ ## Advanced Usage
223
+
224
+ ### File Uploads
225
+
226
+ ```typescript
227
+ const file = new File(['content'], 'document.pdf', { type: 'application/pdf' });
228
+
229
+ dispatch(request({
230
+ api: '/api/upload',
231
+ method: 'POST',
232
+ files: [file],
233
+ body: { description: 'My document' },
234
+ fileKey: 'file', // Optional: override default
235
+ bodyKey: 'metadata', // Optional: override default
236
+ }));
237
+ ```
238
+
239
+ ### Custom Headers
240
+
241
+ ```typescript
242
+ // Global headers (in settings)
243
+ mergeWithHeaders: {
244
+ 'Authorization': 'Bearer token',
245
+ 'X-Custom-Header': 'value',
246
+ }
247
+
248
+ // Per-request headers (override global)
249
+ dispatch(request({
250
+ api: '/api/data',
251
+ headers: {
252
+ 'Authorization': 'Bearer different-token', // Overrides global
253
+ 'X-Request-ID': '123', // Additional header
254
+ },
255
+ }));
256
+ ```
257
+
258
+ ### Request Cancellation
259
+
260
+ When the `cancelation` module is enabled, cdeebee automatically cancels previous requests to the same API endpoint when a new request is made:
261
+
262
+ ```typescript
263
+ // First request
264
+ dispatch(request({ api: '/api/data', body: { query: 'slow' } }));
265
+
266
+ // Second request to same API - first one is automatically cancelled
267
+ dispatch(request({ api: '/api/data', body: { query: 'fast' } }));
175
268
  ```
176
269
 
177
- ## Helpers
178
- ```js
179
- import { cdeebeeHelpers } from '@recats/cdeebee';
270
+ ### Manual State Updates
180
271
 
181
- // requestCancel
182
- cdeebeeHelpers.requestCancel(activeRequest: cdeebeActiveRequest) => void;
272
+ You can manually update the storage using the `set` action:
183
273
 
184
- // checkNetworkActivity
185
- cdeebeeHelpers.checkNetworkActivity(activeRequest: cdeebeActiveRequest, apiUrl: string | Array<string>) => boolean;
274
+ ```typescript
275
+ import { batchingUpdate } from '@recats/cdeebee';
186
276
 
187
- // getSubEntity element in cdeebee list
188
- cdeebeeHelpers.getSubEntity(entity: object) => object;
277
+ // Update multiple values at once
278
+ const updates = [
279
+ { key: ['forumList', '1', 'title'], value: 'New Title' },
280
+ { key: ['forumList', '1', 'views'], value: 100 },
281
+ { key: ['threadList', '101', 'title'], value: 'Updated Thread' },
282
+ ];
189
283
 
190
- // getEntityState element in cdeebee list
191
- cdeebeeHelpers.getEntityState(entity: object) => string;
284
+ dispatch(cdeebeeSlice.actions.set(updates));
285
+ ```
192
286
 
193
- // commitEntity element in cdeebee list
194
- cdeebeeHelpers.commitEntity(entity: object) => void;
287
+ ### Accessing Request History
195
288
 
196
- // resetEntity element in cdeebee list
197
- cdeebeeHelpers.resetEntity(entity: object) => void;
289
+ ```typescript
290
+ const doneRequests = useAppSelector(state => state.cdeebee.request.done);
291
+ const errors = useAppSelector(state => state.cdeebee.request.errors);
292
+
293
+ // Get history for specific API
294
+ const apiHistory = doneRequests['/api/forums'] || [];
295
+ const apiErrors = errors['/api/forums'] || [];
198
296
  ```
199
297
 
200
- ## Data merging behavior
201
- During data merging cdeebee could behave in different ways according to the enum value which is passed during request
298
+ ## TypeScript Support
299
+
300
+ cdeebee is fully typed. Define your storage type and get full type safety:
301
+
302
+ ```typescript
303
+ interface MyStorage {
304
+ userList: Record<string, { id: string; name: string; email: string }>;
305
+ postList: Record<number, { id: number; title: string; userId: string }>;
306
+ }
307
+
308
+ const slice = factory<MyStorage>(settings);
309
+
310
+ // TypeScript knows the structure
311
+ const users = useSelector(state => state.cdeebee.storage.userList);
312
+ // users: Record<string, { id: string; name: string; email: string }>
313
+ ```
314
+
315
+ ## Exports
316
+
317
+ ```typescript
318
+ // Main exports
319
+ export { factory } from '@recats/cdeebee'; // Create cdeebee slice
320
+ export { request } from '@recats/cdeebee'; // Request thunk
321
+ export { batchingUpdate } from '@recats/cdeebee'; // Batch update helper
322
+
323
+ // Types
324
+ export type {
325
+ CdeebeeState,
326
+ CdeebeeSettings,
327
+ CdeebeeRequestOptions,
328
+ CdeebeeValueList,
329
+ CdeebeeActiveRequest,
330
+ } from '@recats/cdeebee';
331
+ ```
332
+
333
+ ## Examples
334
+
335
+ See the `example/` directory in the repository for a complete working example with Next.js.
336
+
337
+ ## License
202
338
 
203
- - *merge* uses ramda mergeDeepRight strategy to merge existing object with the new one
204
- - *replace* overrides the object
339
+ MIT
package/dist/index.cjs ADDED
@@ -0,0 +1,2 @@
1
+ "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const q=require("@reduxjs/toolkit");function d(n,s,i){n.modules.includes(s)&&i()}function f(n){return n!==null&&typeof n=="object"&&!Array.isArray(n)}function b(n,s){if(!f(n)||!f(s))return s;const i={...n},a=s;for(const e in a)if(Object.prototype.hasOwnProperty.call(a,e)){const o=i[e],t=a[e];f(o)&&f(t)&&!Array.isArray(o)&&!Array.isArray(t)?i[e]=b(o,t):i[e]=t}return i}function v(n,s){const i={...s};for(const a of n)delete i[a];return i}function z(n,s){for(let i=0;i<s.length;i++){const a=s[i],e=a.key,o=a.value;if(e.length===0)continue;let t=n;for(let r=0;r<e.length-1;r++){const c=e[r];if(Array.isArray(t)){const l=typeof c=="number"?c:Number(c);(!(l in t)||!f(t[l]))&&(t[l]={}),t=t[l]}else{const l=String(c);if(!(l in t)){const y=typeof e[r+1]=="number"||!isNaN(Number(e[r+1]))&&String(Number(e[r+1]))===String(e[r+1]);t[l]=y?[]:{}}const u=t[l];t=Array.isArray(u)||f(u)?u:{}}}Array.isArray(t)||(t[String(e[e.length-1])]=o)}}class C{constructor(){this.byRequestId=new Map,this.byApi=new Map}add(s,i,a){const e={requestId:i,controller:a,api:s};this.byRequestId.set(i,e),this.byApi.has(s)||this.byApi.set(s,new Set),this.byApi.get(s).add(i)}delete(s){const i=this.byRequestId.get(s);if(!i)return;this.byRequestId.delete(s);const a=this.byApi.get(i.api);a&&(a.delete(s),a.size===0&&this.byApi.delete(i.api))}abortAllForApi(s,i){const a=this.byApi.get(s);a&&a.forEach(e=>{if(e!==i){const o=this.byRequestId.get(e);o&&(o.controller.abort(),this.delete(e))}})}}const S=new C;function D(n,s){S.abortAllForApi(n,s)}function K(n,s,i){const a=new AbortController,e=()=>{S.delete(i)};return n.addEventListener("abort",()=>{a.abort(),e()}),{controller:a,init:()=>S.add(s,i,a),drop:e}}const g=q.createAsyncThunk("cdeebee/request",async(n,{rejectWithValue:s,getState:i,requestId:a,signal:e})=>{const o=new Date().toUTCString(),{cdeebee:{settings:t}}=i(),r=K(e,n.api,a);d(t,"cancelation",r.init);try{const{method:c="POST",body:l,headers:u={}}=n,y={...t.mergeWithHeaders??{},...u},N={...t.mergeWithData??{},...l??{}};let m=JSON.stringify(N);if(n.files){const h=new FormData,w=n.fileKey||t.fileKey,R=n.bodyKey||t.bodyKey;for(let A=0;A<n.files.length;A+=1)w&&h.append(w,n.files[A]);R&&h.append(R,m),m=h}const p=await fetch(n.api,{method:c,headers:{"ui-request-id":a,"Content-Type":"application/json",...y},signal:r.controller.signal,body:m});if(d(t,"cancelation",r.drop),!p.ok)return s(p);const k=await p.json();return n.onResult&&typeof n.onResult=="function"&&n.onResult(k),{result:k,startedAt:o,endedAt:new Date().toUTCString()}}catch(c){return d(t,"cancelation",r.drop),c instanceof Error&&c.name==="AbortError"?s({message:"Request was cancelled",cancelled:!0}):s({message:c instanceof Error?c.message:"Unknown error occurred"})}});function O(n,s,i){const a=Object.keys(s),e=f(n.storage)?n.storage:{},o={...e},t=new Set;for(const r of a){const c=s[r];if(c==null||typeof c=="string"){t.add(r);continue}if(f(c)&&Object.keys(c).length>0){const u=i[r]??"merge",y=r in e?e[r]:{};u==="replace"?o[r]=c:(u==="merge"||console.warn(`Cdeebee: Unknown strategy "${u}" for key "${r}". Skipping normalization.`),o[r]=b(y,c))}else o[r]=c}return t.size>0?v(Array.from(t),o):o}const j={settings:{modules:["history","listener","storage","cancelation"],fileKey:"file",bodyKey:"value",listStrategy:{},mergeWithData:{},mergeWithHeaders:{}},storage:{},request:{active:[],errors:{},done:{}}},I=(n,s)=>q.createSlice({name:"cdeebee",initialState:b(j,{settings:n,storage:s??{}}),reducers:{set(a,e){z(a.storage,e.payload)}},extraReducers:a=>{a.addCase(g.pending,(e,o)=>{const t=o.meta.arg.api,r=o.meta.requestId;d(e.settings,"cancelation",()=>{D(t,r)}),d(e.settings,"listener",()=>{e.request.active.push({api:t,requestId:r})})}).addCase(g.fulfilled,(e,o)=>{const t=o.meta.requestId,r=o.meta.arg.api;d(e.settings,"listener",()=>{e.request.active=e.request.active.filter(c=>!(c.api===r&&c.requestId===t))}),d(e.settings,"history",()=>{e.request.done[r]||(e.request.done[r]=[]),e.request.done[r].push({api:r,request:o.payload,requestId:t})}),d(e.settings,"storage",()=>{const c=o.meta.arg.listStrategy??e.settings.listStrategy??{},l=o.meta.arg.normalize??e.settings.normalize??O,u=q.current(e),y=l(u,o.payload.result,c);e.storage=y})}).addCase(g.rejected,(e,o)=>{const t=o.meta.requestId,r=o.meta.arg.api;d(e.settings,"listener",()=>{e.request.active=e.request.active.filter(c=>!(c.api===r&&c.requestId===t))}),d(e.settings,"history",()=>{e.request.errors[r]||(e.request.errors[r]=[]),e.request.errors[r].push({requestId:t,api:r,request:o.error})})})}});exports.batchingUpdate=z;exports.factory=I;exports.request=g;
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 { isRecord, mergeDeepRight, omit } from './helpers';\n\ntype ResponseValue = Record<string, unknown>;\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 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 const isNormalized = isRecord(responseValue) && Object.keys(responseValue).length > 0;\n\n if (isNormalized) {\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] = responseValue as ResponseValue;\n } else if (strategy === 'merge') {\n // Merge: merge with existing value\n result[key] = mergeDeepRight(existingValue, responseValue as StorageData) 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, responseValue as StorageData) as ResponseValue;\n }\n } else {\n // Not a normalized object, store as-is\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 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","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","currentStorage","keyListToOmit","responseValue","strategy","existingValue","initialState","factory","storage","createSlice","action","builder","q","normalize","currentState","normalizedData"],"mappings":"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"}