@feardread/feature-factory 3.0.2 → 4.0.2

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.
@@ -1,28 +1,422 @@
1
- // src/services/myApi.js
2
1
  import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react';
3
- const USERS_URL = "auth"
4
2
 
5
- const ApiFactory = ( sliceName ) => {
6
- const _this = {};
7
- _this.API_BASE_URL = _this.base_url || "http://localhost:4000/fear/api/";
8
- _this.baseQuery = { baseUrl: _this.API_BASE_URL };
3
+ /**
4
+ * Default configuration for the API factory
5
+ */
6
+ const DEFAULT_CONFIG = {
7
+ baseUrl: process.env.REACT_APP_API_BASE_URL || 'http://localhost:4000/fear/api/',
8
+ timeout: 30000,
9
+ credentials: 'include',
10
+ prepareHeaders: (headers, { getState }) => {
11
+ // Add common headers here
12
+ headers.set('Content-Type', 'application/json');
13
+
14
+ // Add auth token if available
15
+ const token = getState()?.auth?.token;
16
+ if (token) {
17
+ headers.set('Authorization', `Bearer ${token}`);
18
+ }
19
+
20
+ return headers;
21
+ },
22
+ };
23
+
24
+ /**
25
+ * Standard HTTP methods
26
+ */
27
+ const HTTP_METHODS = {
28
+ GET: 'GET',
29
+ POST: 'POST',
30
+ PUT: 'PUT',
31
+ PATCH: 'PATCH',
32
+ DELETE: 'DELETE',
33
+ };
34
+
35
+ /**
36
+ * Standard endpoint configurations for common CRUD operations
37
+ */
38
+ const STANDARD_ENDPOINTS = {
39
+ getAll: {
40
+ method: HTTP_METHODS.GET,
41
+ url: (entity) => `${entity}/all`,
42
+ providesTags: (entity) => [{ type: entity, id: 'LIST' }],
43
+ },
44
+ getById: {
45
+ method: HTTP_METHODS.GET,
46
+ url: (entity, id) => `${entity}/${id}`,
47
+ providesTags: (entity, id) => [{ type: entity, id }],
48
+ },
49
+ create: {
50
+ method: HTTP_METHODS.POST,
51
+ url: (entity) => `${entity}`,
52
+ invalidatesTags: (entity) => [{ type: entity, id: 'LIST' }],
53
+ },
54
+ update: {
55
+ method: HTTP_METHODS.PUT,
56
+ url: (entity, id) => `${entity}/${id}`,
57
+ invalidatesTags: (entity, id) => [
58
+ { type: entity, id },
59
+ { type: entity, id: 'LIST' },
60
+ ],
61
+ },
62
+ patch: {
63
+ method: HTTP_METHODS.PATCH,
64
+ url: (entity, id) => `${entity}/${id}`,
65
+ invalidatesTags: (entity, id) => [
66
+ { type: entity, id },
67
+ { type: entity, id: 'LIST' },
68
+ ],
69
+ },
70
+ delete: {
71
+ method: HTTP_METHODS.DELETE,
72
+ url: (entity, id) => `${entity}/${id}`,
73
+ invalidatesTags: (entity, id) => [
74
+ { type: entity, id },
75
+ { type: entity, id: 'LIST' },
76
+ ],
77
+ },
78
+ search: {
79
+ method: HTTP_METHODS.GET,
80
+ url: (entity) => `${entity}/search`,
81
+ providesTags: (entity) => [{ type: entity, id: 'SEARCH' }],
82
+ },
83
+ };
84
+
85
+ /**
86
+ * Validates input parameters
87
+ * @param {string} sliceName - The slice/entity name
88
+ * @throws {Error} If validation fails
89
+ */
90
+ const validateParams = (sliceName) => {
91
+ if (!sliceName || typeof sliceName !== 'string') {
92
+ throw new Error('SliceName must be a non-empty string');
93
+ }
94
+ };
95
+
96
+ /**
97
+ * Transforms entity name to proper case for tag types
98
+ * @param {string} entity - Entity name
99
+ * @returns {string} Capitalized entity name
100
+ */
101
+ const toTagType = (entity) => {
102
+ return entity.charAt(0).toUpperCase() + entity.slice(1);
103
+ };
104
+
105
+ /**
106
+ * Creates a standardized error handler
107
+ * @param {Object} error - Error object from RTK Query
108
+ * @returns {Object} Formatted error
109
+ */
110
+ const handleError = (error) => {
111
+ if (error.status) {
112
+ return {
113
+ status: error.status,
114
+ message: error.data?.message || `HTTP Error ${error.status}`,
115
+ data: error.data,
116
+ };
117
+ }
118
+
119
+ return {
120
+ status: 'FETCH_ERROR',
121
+ message: error.message || 'Network error occurred',
122
+ error: error.error,
123
+ };
124
+ };
125
+
126
+ /**
127
+ * Creates query configuration for RTK Query
128
+ * @param {Object} config - Endpoint configuration
129
+ * @param {string} entity - Entity name
130
+ * @param {any} args - Arguments passed to the query
131
+ * @returns {Object} RTK Query configuration
132
+ */
133
+ const createQueryConfig = (config, entity, args = {}) => {
134
+ const { method, url: urlFn } = config;
135
+
136
+ let queryConfig = {
137
+ method,
138
+ url: typeof urlFn === 'function' ? urlFn(entity, args.id, args) : urlFn,
139
+ };
140
+
141
+ // Add body for mutations
142
+ if (method !== HTTP_METHODS.GET && method !== HTTP_METHODS.DELETE) {
143
+ queryConfig.body = args.data || args;
144
+ }
145
+
146
+ // Add params for GET requests with parameters
147
+ if (method === HTTP_METHODS.GET && args.params) {
148
+ queryConfig.params = args.params;
149
+ }
150
+
151
+ return queryConfig;
152
+ };
153
+
154
+ /**
155
+ * Factory function to create RTK Query APIs with standardized patterns
156
+ * @param {string} sliceName - The slice/entity name
157
+ * @param {Object} options - Configuration options
158
+ * @param {string[]} options.tagTypes - Additional tag types beyond the entity
159
+ * @param {Object} options.baseQuery - Custom base query configuration
160
+ * @param {boolean} options.includeStandardEndpoints - Whether to include CRUD endpoints
161
+ * @returns {Object} ApiFactory instance with methods to build and configure the API
162
+ */
163
+ const ApiFactory = (sliceName, options = {}) => {
164
+ validateParams(sliceName);
165
+
166
+ const config = {
167
+ tagTypes: ['Product', 'Order', 'User', 'Category'],
168
+ baseQuery: {},
169
+ includeStandardEndpoints: true,
170
+ ...options,
171
+ };
172
+
173
+ // Ensure the entity tag type is included
174
+ const entityTagType = toTagType(sliceName);
175
+ if (!config.tagTypes.includes(entityTagType)) {
176
+ config.tagTypes.push(entityTagType);
177
+ }
178
+
179
+ // Create base query with merged configuration
180
+ const baseQueryConfig = {
181
+ ...DEFAULT_CONFIG,
182
+ ...config.baseQuery,
183
+ };
184
+
185
+ // Create the base API
186
+ const baseApi = createApi({
187
+ reducerPath: `${sliceName}Api`,
188
+ tagTypes: config.tagTypes,
189
+ baseQuery: fetchBaseQuery(baseQueryConfig),
190
+ endpoints: () => ({}),
191
+ });
192
+
193
+ /**
194
+ * Injects a single endpoint into the API
195
+ * @param {string} name - Endpoint name
196
+ * @param {Object} endpointConfig - Endpoint configuration
197
+ * @param {string} endpointConfig.method - HTTP method
198
+ * @param {string|Function} endpointConfig.url - URL or URL generator function
199
+ * @param {string} endpointConfig.type - 'query' or 'mutation'
200
+ * @param {Function} endpointConfig.providesTags - Tags provided by this endpoint
201
+ * @param {Function} endpointConfig.invalidatesTags - Tags invalidated by this endpoint
202
+ * @param {Function} endpointConfig.transformResponse - Response transformer
203
+ * @param {Function} endpointConfig.transformErrorResponse - Error transformer
204
+ * @returns {Object} Enhanced API with injected endpoint
205
+ */
206
+ const injectEndpoint = (name, endpointConfig) => {
207
+ const {
208
+ method = HTTP_METHODS.GET,
209
+ url,
210
+ type = method === HTTP_METHODS.GET ? 'query' : 'mutation',
211
+ providesTags,
212
+ invalidatesTags,
213
+ transformResponse,
214
+ transformErrorResponse = handleError,
215
+ } = endpointConfig;
216
+
217
+ return baseApi.injectEndpoints({
218
+ endpoints: (builder) => ({
219
+ [name]: builder[type]({
220
+ query: (args = {}) => createQueryConfig({ method, url }, sliceName, args),
221
+ providesTags: providesTags
222
+ ? (result, error, args) => providesTags(sliceName, args?.id, result, error, args)
223
+ : undefined,
224
+ invalidatesTags: invalidatesTags
225
+ ? (result, error, args) => invalidatesTags(sliceName, args?.id, result, error, args)
226
+ : undefined,
227
+ transformResponse,
228
+ transformErrorResponse,
229
+ }),
230
+ }),
231
+ });
232
+ };
233
+
234
+ /**
235
+ * Injects multiple endpoints at once
236
+ * @param {Object} endpoints - Object with endpoint configurations
237
+ * @returns {Object} Enhanced API with all injected endpoints
238
+ */
239
+ const injectEndpoints = (endpoints) => {
240
+ return baseApi.injectEndpoints({
241
+ endpoints: (builder) => {
242
+ const builtEndpoints = {};
9
243
 
10
- _this.baseApi = createApi({
11
- reducerPath: sliceName,
12
- tagTypes: ["Product", "Order", "User", "Category"],
13
- baseQuery: fetchBaseQuery(_this.baseQuery),
14
- endpoints: () => ({})
244
+ Object.entries(endpoints).forEach(([name, config]) => {
245
+ const {
246
+ method = HTTP_METHODS.GET,
247
+ url,
248
+ type = method === HTTP_METHODS.GET ? 'query' : 'mutation',
249
+ providesTags,
250
+ invalidatesTags,
251
+ transformResponse,
252
+ transformErrorResponse = handleError,
253
+ } = config;
254
+
255
+ builtEndpoints[name] = builder[type]({
256
+ query: (args = {}) => createQueryConfig({ method, url }, sliceName, args),
257
+ providesTags: providesTags
258
+ ? (result, error, args) => providesTags(sliceName, args?.id, result, error, args)
259
+ : undefined,
260
+ invalidatesTags: invalidatesTags
261
+ ? (result, error, args) => invalidatesTags(sliceName, args?.id, result, error, args)
262
+ : undefined,
263
+ transformResponse,
264
+ transformErrorResponse,
265
+ });
15
266
  });
267
+
268
+ return builtEndpoints;
269
+ },
270
+ });
271
+ };
16
272
 
17
- _this.create = (routes) => {
18
- const endpoints = (routes) ? routes : () => ({});
19
- console.log('routes = ', routes);
20
- const api = _this.baseApi.injectEndpoints((builder) => (routes));
273
+ /**
274
+ * Creates standard CRUD endpoints for the entity
275
+ * @returns {Object} Enhanced API with standard endpoints
276
+ */
277
+ const createStandardEndpoints = () => {
278
+ const endpoints = {};
279
+
280
+ Object.entries(STANDARD_ENDPOINTS).forEach(([name, config]) => {
281
+ endpoints[name] = {
282
+ method: config.method,
283
+ url: config.url,
284
+ providesTags: config.providesTags,
285
+ invalidatesTags: config.invalidatesTags,
286
+ };
287
+ });
288
+
289
+ return injectEndpoints(endpoints);
290
+ };
21
291
 
22
- return api;
292
+ /**
293
+ * Creates the final API with all configured endpoints
294
+ * @returns {Object} Complete RTK Query API
295
+ */
296
+ const create = () => {
297
+ let api = baseApi;
298
+
299
+ if (config.includeStandardEndpoints) {
300
+ api = createStandardEndpoints();
23
301
  }
302
+
303
+ return api;
304
+ };
305
+
306
+ /**
307
+ * Creates a custom endpoint with advanced configuration
308
+ * @param {string} name - Endpoint name
309
+ * @param {Object} config - Advanced endpoint configuration
310
+ * @returns {Object} Enhanced API with custom endpoint
311
+ */
312
+ const createCustomEndpoint = (name, config) => {
313
+ return injectEndpoint(name, config);
314
+ };
315
+
316
+ /**
317
+ * Creates endpoints for bulk operations
318
+ * @returns {Object} Enhanced API with bulk endpoints
319
+ */
320
+ const createBulkEndpoints = () => {
321
+ const bulkEndpoints = {
322
+ bulkCreate: {
323
+ method: HTTP_METHODS.POST,
324
+ url: `${sliceName}/bulk`,
325
+ invalidatesTags: () => [{ type: entityTagType, id: 'LIST' }],
326
+ },
327
+ bulkUpdate: {
328
+ method: HTTP_METHODS.PUT,
329
+ url: `${sliceName}/bulk`,
330
+ invalidatesTags: () => [{ type: entityTagType, id: 'LIST' }],
331
+ },
332
+ bulkDelete: {
333
+ method: HTTP_METHODS.DELETE,
334
+ url: `${sliceName}/bulk`,
335
+ invalidatesTags: () => [{ type: entityTagType, id: 'LIST' }],
336
+ },
337
+ };
338
+
339
+ return injectEndpoints(bulkEndpoints);
340
+ };
341
+
342
+ // Public API
343
+ return {
344
+ // Core properties
345
+ sliceName,
346
+ entityTagType,
347
+ baseApi,
348
+
349
+ // Methods
350
+ inject: injectEndpoint,
351
+ injectMany: injectEndpoints,
352
+ create,
353
+ createStandard: createStandardEndpoints,
354
+ createBulk: createBulkEndpoints,
355
+ createCustom: createCustomEndpoint,
356
+
357
+ // Utilities
358
+ getTagTypes: () => [...config.tagTypes],
359
+ getBaseUrl: () => baseQueryConfig.baseUrl,
360
+ getReducerPath: () => baseApi.reducerPath,
361
+
362
+ // Advanced methods
363
+ withAuth: (token) => {
364
+ return ApiFactory(sliceName, {
365
+ ...config,
366
+ baseQuery: {
367
+ ...baseQueryConfig,
368
+ prepareHeaders: (headers, api) => {
369
+ headers.set('Authorization', `Bearer ${token}`);
370
+ return DEFAULT_CONFIG.prepareHeaders(headers, api);
371
+ },
372
+ },
373
+ });
374
+ },
375
+
376
+ withBaseUrl: (baseUrl) => {
377
+ return ApiFactory(sliceName, {
378
+ ...config,
379
+ baseQuery: {
380
+ ...baseQueryConfig,
381
+ baseUrl,
382
+ },
383
+ });
384
+ },
385
+ };
386
+ };
387
+
388
+ /**
389
+ * Creates a complete API factory with all standard endpoints
390
+ * @param {string} sliceName - Entity name
391
+ * @param {Object} options - Configuration options
392
+ * @returns {Object} Complete RTK Query API
393
+ */
394
+ ApiFactory.createComplete = (sliceName, options = {}) => {
395
+ return ApiFactory(sliceName, options).create();
396
+ };
397
+
398
+ /**
399
+ * Creates an API factory with only custom endpoints
400
+ * @param {string} sliceName - Entity name
401
+ * @param {Object} endpoints - Custom endpoints
402
+ * @param {Object} options - Configuration options
403
+ * @returns {Object} RTK Query API with custom endpoints
404
+ */
405
+ ApiFactory.createCustom = (sliceName, endpoints, options = {}) => {
406
+ return ApiFactory(sliceName, { ...options, includeStandardEndpoints: false })
407
+ .injectMany(endpoints);
408
+ };
409
+
410
+ /**
411
+ * Utility to get standard endpoint configurations
412
+ * @returns {Object} Standard endpoint configurations
413
+ */
414
+ ApiFactory.getStandardEndpoints = () => ({ ...STANDARD_ENDPOINTS });
24
415
 
25
- return _this;
26
- }
416
+ /**
417
+ * Utility to get HTTP methods
418
+ * @returns {Object} HTTP methods
419
+ */
420
+ ApiFactory.getHttpMethods = () => ({ ...HTTP_METHODS });
27
421
 
28
422
  export default ApiFactory;