@exyconn/common 2.3.2 → 2.3.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.
Files changed (40) hide show
  1. package/README.md +117 -12
  2. package/dist/client/http/index.d.mts +280 -49
  3. package/dist/client/http/index.d.ts +280 -49
  4. package/dist/client/http/index.js +564 -90
  5. package/dist/client/http/index.js.map +1 -1
  6. package/dist/client/http/index.mjs +520 -80
  7. package/dist/client/http/index.mjs.map +1 -1
  8. package/dist/client/index.d.mts +3 -3
  9. package/dist/client/index.d.ts +3 -3
  10. package/dist/client/index.js +573 -316
  11. package/dist/client/index.js.map +1 -1
  12. package/dist/client/index.mjs +529 -287
  13. package/dist/client/index.mjs.map +1 -1
  14. package/dist/client/utils/index.d.mts +3 -279
  15. package/dist/client/utils/index.d.ts +3 -279
  16. package/dist/{index-D9a9oxQy.d.ts → index-CdbQ8YPt.d.ts} +51 -39
  17. package/dist/{index-D3yCCjBZ.d.mts → index-Ckhm_HaX.d.mts} +21 -2
  18. package/dist/{index-01hoqibP.d.ts → index-br6POSyA.d.ts} +21 -2
  19. package/dist/{index-DuxL84IW.d.mts → index-guYdqefq.d.mts} +51 -39
  20. package/dist/index.d.mts +3 -3
  21. package/dist/index.d.ts +3 -3
  22. package/dist/index.js +1214 -326
  23. package/dist/index.js.map +1 -1
  24. package/dist/index.mjs +1226 -338
  25. package/dist/index.mjs.map +1 -1
  26. package/dist/packageCheck-B_qfsD6R.d.ts +280 -0
  27. package/dist/packageCheck-C2_FT_Rl.d.mts +280 -0
  28. package/dist/server/index.d.mts +1 -1
  29. package/dist/server/index.d.ts +1 -1
  30. package/dist/server/index.js +631 -0
  31. package/dist/server/index.js.map +1 -1
  32. package/dist/server/index.mjs +625 -2
  33. package/dist/server/index.mjs.map +1 -1
  34. package/dist/server/middleware/index.d.mts +283 -2
  35. package/dist/server/middleware/index.d.ts +283 -2
  36. package/dist/server/middleware/index.js +761 -0
  37. package/dist/server/middleware/index.js.map +1 -1
  38. package/dist/server/middleware/index.mjs +751 -1
  39. package/dist/server/middleware/index.mjs.map +1 -1
  40. package/package.json +1 -1
package/README.md CHANGED
@@ -78,10 +78,11 @@ import { successResponse, errorResponse } from '@exyconn/common/server/response'
78
78
  import { createCorsOptions, createRateLimiter } from '@exyconn/common/server/configs';
79
79
  import { createLogger } from '@exyconn/common/server/logger';
80
80
  import { connectDB } from '@exyconn/common/server/db';
81
+ import { createCrudControllers, queryParser, queryPagination } from '@exyconn/common/server/middleware';
81
82
 
82
83
  // Client utilities
83
84
  import { useLocalStorage, useDebounce } from '@exyconn/common/client/hooks';
84
- import { createHttpClient } from '@exyconn/common/client/http';
85
+ import { getRequest, postRequest, extractData, isSuccess } from '@exyconn/common/client/http';
85
86
 
86
87
  // Shared utilities
87
88
  import { isValidEmail, VALIDATION_PATTERNS } from '@exyconn/common/shared/validation';
@@ -491,6 +492,52 @@ await disconnectDB(logger);
491
492
 
492
493
  #### Middleware (`@exyconn/common/server/middleware`)
493
494
 
495
+ ##### CRUD Controller Factory
496
+
497
+ ```typescript
498
+ import { createCrudControllers } from '@exyconn/common/server/middleware';
499
+ import { z } from 'zod';
500
+ import { User } from './models/User';
501
+
502
+ const userController = createCrudControllers({
503
+ model: User,
504
+ resourceName: 'User',
505
+ createSchema: z.object({ name: z.string(), email: z.string().email() }),
506
+ updateSchema: z.object({ name: z.string().optional() }),
507
+ searchFields: ['name', 'email'],
508
+ withOrganization: true,
509
+ });
510
+
511
+ // Auto-generated routes
512
+ router.get('/users', userController.getAll);
513
+ router.get('/users/:id', userController.getById);
514
+ router.post('/users', userController.create);
515
+ router.put('/users/:id', userController.update);
516
+ router.delete('/users/:id', userController.deleteOne);
517
+ router.delete('/users', userController.bulkDelete);
518
+ ```
519
+
520
+ ##### Query Parser & Pagination
521
+
522
+ ```typescript
523
+ import { queryParser, queryPagination } from '@exyconn/common/server/middleware';
524
+
525
+ router.get('/users', queryParser, async (req, res) => {
526
+ const { page, limit, sort, search, filter } = req.parsedQuery;
527
+ // page: 1, limit: 10, sort: { createdAt: 'desc' }, search: 'john'
528
+
529
+ await queryPagination(User, {
530
+ searchFields: ['name', 'email'],
531
+ populate: ['department'],
532
+ }, true)(req, res, () => {});
533
+
534
+ res.json(res.paginatedResult);
535
+ // { data: [...], meta: { total, page, limit, totalPages }, columns: [...] }
536
+ });
537
+ ```
538
+
539
+ ##### Authentication Middleware
540
+
494
541
  ```typescript
495
542
  import {
496
543
  authenticateJWT,
@@ -498,7 +545,6 @@ import {
498
545
  authenticateApiKey,
499
546
  extractOrganization,
500
547
  type AuthRequest,
501
- type JWTPayload,
502
548
  } from '@exyconn/common/server/middleware';
503
549
 
504
550
  // JWT Authentication (required)
@@ -642,18 +688,77 @@ const meta = buildPaginationMeta(totalCount, pagination.page, pagination.limit);
642
688
  #### HTTP Client (`@exyconn/common/client/http`)
643
689
 
644
690
  ```typescript
645
- import { createHttpClient, withFormData, withTimeout } from '@exyconn/common/client/http';
691
+ import {
692
+ getRequest,
693
+ postRequest,
694
+ putRequest,
695
+ patchRequest,
696
+ deleteRequest,
697
+ uploadFile,
698
+ extractData,
699
+ extractPaginatedData,
700
+ isSuccess,
701
+ parseError,
702
+ } from '@exyconn/common/client/http';
703
+
704
+ // GET request
705
+ const response = await getRequest('/api/users');
706
+ const users = extractData(response);
707
+
708
+ // POST request
709
+ const createResponse = await postRequest('/api/users', { name: 'John' });
710
+ if (isSuccess(createResponse)) {
711
+ console.log('User created!');
712
+ }
646
713
 
647
- const api = createHttpClient({
648
- baseURL: 'https://api.example.com',
649
- timeout: 30000,
650
- getAuthToken: () => localStorage.getItem('token'),
651
- onUnauthorized: () => window.location.href = '/login',
652
- });
714
+ // Paginated response
715
+ const paginatedResponse = await getRequest('/api/users', { page: 1, limit: 10 });
716
+ const { items, total, page, totalPages } = extractPaginatedData(paginatedResponse);
717
+
718
+ // File upload
719
+ const uploadResponse = await uploadFile('/api/upload', file, { folder: 'avatars' });
720
+
721
+ // Error handling
722
+ try {
723
+ await postRequest('/api/users', invalidData);
724
+ } catch (error) {
725
+ const { message, statusCode } = parseError(error);
726
+ }
727
+ ```
728
+
729
+ **HTTP Functions:**
730
+
731
+ | Function | Parameters | Returns | Description |
732
+ |----------|------------|---------|-------------|
733
+ | `getRequest` | `url, params?, headers?` | `Promise<AxiosResponse>` | GET request |
734
+ | `postRequest` | `url, data?, headers?` | `Promise<AxiosResponse>` | POST request |
735
+ | `putRequest` | `url, data?, headers?` | `Promise<AxiosResponse>` | PUT request |
736
+ | `patchRequest` | `url, data?, headers?` | `Promise<AxiosResponse>` | PATCH request |
737
+ | `deleteRequest` | `url, params?, headers?` | `Promise<AxiosResponse>` | DELETE request |
738
+ | `uploadFile` | `url, file, data?` | `Promise<AxiosResponse>` | File upload |
739
+
740
+ **Response Utilities:**
741
+
742
+ | Function | Description |
743
+ |----------|-------------|
744
+ | `extractData` | Extract data from axios response |
745
+ | `extractMessage` | Extract message from response |
746
+ | `extractPaginatedData` | Extract paginated data with meta |
747
+ | `isSuccess` | Check if response status is 2xx |
748
+ | `isSuccessResponse` | Check response data for success |
749
+ | `parseError` | Parse error to standardized format |
750
+ | `parseResponseData` | Parse data from raw response |
751
+ | `parsePaginatedResponse` | Parse paginated response data |
752
+ | `safeJsonParse` | Safe JSON parsing with fallback |
753
+
754
+ **Slug Utilities:**
755
+
756
+ ```typescript
757
+ import { generateSlug, generateUrlSlug, generateSnakeSlug } from '@exyconn/common/client/http';
653
758
 
654
- const users = await api.get('/users');
655
- const newUser = await api.post('/users', { name: 'John' });
656
- const file = await api.post('/upload', formData, withFormData());
759
+ generateSlug('Hello World'); // 'helloWorld'
760
+ generateUrlSlug('Hello World'); // 'hello-world'
761
+ generateSnakeSlug('Hello World'); // 'hello_world'
657
762
  ```
658
763
 
659
764
  ---
@@ -1,85 +1,316 @@
1
- import { AxiosError, AxiosInstance, AxiosRequestConfig, AxiosResponse } from 'axios';
1
+ import * as axios from 'axios';
2
+ import { AxiosResponse } from 'axios';
3
+ export { AxiosError, AxiosResponse } from 'axios';
2
4
 
3
5
  /**
4
- * HTTP Client Configuration Options
6
+ * Response Parser Utility
7
+ *
8
+ * Comprehensive response parsing utilities for API responses.
9
+ * Aligned with server-side response structures.
5
10
  */
6
- interface HttpClientOptions {
7
- baseURL: string;
8
- timeout?: number;
9
- withCredentials?: boolean;
10
- getAuthToken?: () => string | null;
11
- onUnauthorized?: () => void;
12
- onServerError?: (error: AxiosError) => void;
11
+ /**
12
+ * Standard API response structure from server
13
+ */
14
+ interface ApiResponse<T = unknown> {
15
+ message: string;
16
+ data: T | null;
17
+ status: string;
18
+ statusCode: number;
19
+ paginationData?: PaginationData;
20
+ columns?: ColumnMetadata[];
13
21
  }
14
22
  /**
15
- * Create configured Axios instance
23
+ * Pagination metadata structure
16
24
  */
17
- declare const createHttpClient: (options: HttpClientOptions) => AxiosInstance;
25
+ interface PaginationData {
26
+ total?: number;
27
+ page?: number;
28
+ limit?: number;
29
+ totalPages?: number;
30
+ }
18
31
  /**
19
- * Default request config helpers
32
+ * Column metadata from server
20
33
  */
21
- declare const withFormData: () => AxiosRequestConfig;
22
- declare const withTimeout: (ms: number) => AxiosRequestConfig;
23
- declare const withAbortSignal: (signal: AbortSignal) => AxiosRequestConfig;
24
-
34
+ interface ColumnMetadata {
35
+ name: string;
36
+ required: boolean;
37
+ datatype: string;
38
+ }
25
39
  /**
26
- * Standard API Response Structure
40
+ * Status codes aligned with backend
27
41
  */
28
- interface ApiResponse<T = unknown> {
29
- success: boolean;
42
+ declare const STATUS_CODES: {
43
+ readonly SUCCESS: 200;
44
+ readonly CREATED: 201;
45
+ readonly NO_CONTENT: 204;
46
+ readonly BAD_REQUEST: 400;
47
+ readonly UNAUTHORIZED: 401;
48
+ readonly FORBIDDEN: 403;
49
+ readonly NOT_FOUND: 404;
50
+ readonly CONFLICT: 409;
51
+ readonly ERROR: 500;
52
+ };
53
+ /**
54
+ * Status messages aligned with backend
55
+ */
56
+ declare const STATUS_MESSAGES: {
57
+ SUCCESS: string;
58
+ CREATED: string;
59
+ NO_CONTENT: string;
60
+ BAD_REQUEST: string;
61
+ UNAUTHORIZED: string;
62
+ FORBIDDEN: string;
63
+ NOT_FOUND: string;
64
+ CONFLICT: string;
65
+ ERROR: string;
66
+ };
67
+ declare const SUCCESS_CODES: number[];
68
+ declare const ERROR_CODES: number[];
69
+ /**
70
+ * Safely extracts data from API response
71
+ */
72
+ declare const parseResponseData: <T = unknown>(response: unknown, fallback?: T | null) => T | null;
73
+ /**
74
+ * Extracts message from response
75
+ */
76
+ declare const parseResponseMessage: (response: unknown, fallback?: string) => string;
77
+ /**
78
+ * Extracts status code from response
79
+ */
80
+ declare const parseResponseStatus: (response: unknown) => number | null;
81
+ /**
82
+ * Extracts status message from response
83
+ */
84
+ declare const parseResponseStatusMessage: (response: unknown, fallback?: string) => string;
85
+ /**
86
+ * Checks if response indicates success
87
+ */
88
+ declare const isSuccessResponse: (response: unknown) => boolean;
89
+ /**
90
+ * Checks if response indicates error
91
+ */
92
+ declare const isErrorResponse: (response: unknown) => boolean;
93
+ /**
94
+ * Paginated response structure
95
+ */
96
+ interface PaginatedResponse<T> {
97
+ items: T[];
98
+ total: number;
99
+ page: number;
100
+ limit: number;
101
+ totalPages?: number;
102
+ columns?: ColumnMetadata[];
103
+ }
104
+ /**
105
+ * Parses paginated response from backend
106
+ */
107
+ declare const parsePaginatedResponse: <T = unknown>(response: unknown) => PaginatedResponse<T>;
108
+ /**
109
+ * Extracts specific nested data with path
110
+ */
111
+ declare const extractNestedData: <T = unknown>(response: unknown, path: string, fallback?: T | null) => T | null;
112
+ /**
113
+ * Safe JSON parse with fallback
114
+ */
115
+ declare const safeJsonParse: <T = unknown>(json: string, fallback?: T | null) => T | null;
116
+ /**
117
+ * Extracts error message from axios error response
118
+ */
119
+ declare const parseAxiosErrorMessage: (error: unknown) => string;
120
+ /**
121
+ * Standardized error object
122
+ */
123
+ interface ParsedError {
30
124
  message: string;
31
- data?: T;
32
- error?: string;
33
- statusCode?: number;
125
+ statusCode: number | null;
126
+ data: unknown;
127
+ status?: string;
34
128
  }
35
129
  /**
36
- * Paginated Response Structure
130
+ * Parses error into standardized format
37
131
  */
38
- interface PaginatedResponse<T = unknown> extends ApiResponse<T[]> {
39
- pagination: {
40
- total: number;
41
- page: number;
42
- limit: number;
43
- totalPages: number;
44
- hasNextPage: boolean;
45
- hasPrevPage: boolean;
132
+ declare const parseError: (error: unknown) => ParsedError;
133
+ interface SimpleApiResponse {
134
+ data?: {
135
+ data?: {
136
+ data?: unknown;
137
+ meta?: Record<string, unknown>;
138
+ };
46
139
  };
47
140
  }
141
+ declare const simpleParseResponse: (response: SimpleApiResponse) => unknown;
142
+ declare const simpleMetaParseResponse: (response: SimpleApiResponse) => Record<string, unknown> | undefined;
143
+ declare const simpleParseDualDataResponse: (response: SimpleApiResponse) => {
144
+ data?: unknown;
145
+ meta?: Record<string, unknown>;
146
+ } | undefined;
147
+
148
+ /**
149
+ * HTTP Configuration Interface
150
+ */
151
+ interface HttpConfig {
152
+ baseUrl?: string;
153
+ apiPrefix?: string;
154
+ timeout?: number;
155
+ defaultHeaders?: Record<string, string>;
156
+ }
157
+ declare const axiosInstance: axios.AxiosInstance;
158
+ /**
159
+ * Get current API Base URL
160
+ */
161
+ declare const getApiBaseUrl: () => string;
48
162
  /**
49
- * Parse successful API response
163
+ * Set API Base URL
50
164
  */
51
- declare const parseResponse: <T>(response: AxiosResponse<ApiResponse<T>>) => T | null;
165
+ declare const setApiBaseUrl: (baseUrl: string) => void;
52
166
  /**
53
- * Parse API response with full metadata
167
+ * Get current API Prefix
54
168
  */
55
- declare const parseFullResponse: <T>(response: AxiosResponse<ApiResponse<T>>) => ApiResponse<T>;
169
+ declare const getApiPrefix: () => string;
56
170
  /**
57
- * Parse error from API response
171
+ * Set API Prefix
58
172
  */
59
- declare const parseError: (error: AxiosError<ApiResponse>) => string;
173
+ declare const setApiPrefix: (prefix: string) => void;
174
+ /**
175
+ * Get all custom headers
176
+ */
177
+ declare const getCustomHeaders: () => Record<string, string>;
178
+ /**
179
+ * Set a custom header globally
180
+ */
181
+ declare const setCustomHeader: (key: string, value: string) => void;
182
+ /**
183
+ * Remove a custom header
184
+ */
185
+ declare const removeCustomHeader: (key: string) => void;
186
+ /**
187
+ * Set multiple custom headers at once
188
+ */
189
+ declare const setCustomHeaders: (headers: Record<string, string>) => void;
190
+ /**
191
+ * Clear all custom headers
192
+ */
193
+ declare const clearCustomHeaders: () => void;
194
+ /**
195
+ * Configure HTTP client with custom settings
196
+ */
197
+ declare const configureHttp: (config: HttpConfig) => void;
198
+ /**
199
+ * Get current HTTP configuration
200
+ */
201
+ declare const getHttpConfig: () => Readonly<HttpConfig>;
202
+ /**
203
+ * Reset HTTP configuration to defaults
204
+ */
205
+ declare const resetHttpConfig: () => void;
206
+ /**
207
+ * @deprecated Use getApiBaseUrl() instead
208
+ */
209
+ declare const API_BASE_URL: string;
210
+ /**
211
+ * @deprecated Use getApiPrefix() instead
212
+ */
213
+ declare const API_PREFIX: string;
214
+ /**
215
+ * GET request
216
+ */
217
+ declare const getRequest: <T = unknown>(url: string, params?: Record<string, unknown>, customHeaders?: Record<string, string>) => Promise<AxiosResponse<T>>;
218
+ /**
219
+ * POST request
220
+ */
221
+ declare const postRequest: <T = unknown>(url: string, data?: unknown, customHeaders?: Record<string, string>) => Promise<AxiosResponse<T>>;
222
+ /**
223
+ * PUT request
224
+ */
225
+ declare const putRequest: <T = unknown>(url: string, data?: unknown, customHeaders?: Record<string, string>) => Promise<AxiosResponse<T>>;
226
+ /**
227
+ * PATCH request
228
+ */
229
+ declare const patchRequest: <T = unknown>(url: string, data?: unknown, customHeaders?: Record<string, string>) => Promise<AxiosResponse<T>>;
230
+ /**
231
+ * DELETE request
232
+ */
233
+ declare const deleteRequest: <T = unknown>(url: string, params?: Record<string, unknown>, customHeaders?: Record<string, string>) => Promise<AxiosResponse<T>>;
234
+ /**
235
+ * File upload with FormData
236
+ */
237
+ declare const uploadFile: <T = unknown>(url: string, file: File, additionalData?: Record<string, unknown>) => Promise<AxiosResponse<T>>;
238
+ /**
239
+ * Extract data from response using response-parser
240
+ */
241
+ declare const extractData: <T = unknown>(response: AxiosResponse) => T | null;
242
+ /**
243
+ * Extract message from response
244
+ */
245
+ declare const extractMessage: (response: unknown) => string;
60
246
  /**
61
247
  * Check if response is successful
62
248
  */
63
- declare const isSuccess: <T>(response: AxiosResponse<ApiResponse<T>>) => boolean;
249
+ declare const isSuccess: (response: AxiosResponse) => boolean;
250
+ /**
251
+ * Extract paginated data from response
252
+ */
253
+ declare const extractPaginatedData: <T = unknown>(response: AxiosResponse) => PaginatedResponse<T>;
254
+
64
255
  /**
65
- * Check if error is a specific status code
256
+ * Slug Utility
257
+ *
258
+ * Generates slugs from text for use in URLs and object properties.
66
259
  */
67
- declare const isStatusError: (error: AxiosError, statusCode: number) => boolean;
68
260
  /**
69
- * Check if error is unauthorized (401)
261
+ * Generates a camelCase slug from a given text for use in object properties
262
+ * @param text - The text to convert to camelCase slug
263
+ * @returns The generated camelCase slug
70
264
  */
71
- declare const isUnauthorized: (error: AxiosError) => boolean;
265
+ declare const generateSlug: (text: string) => string;
72
266
  /**
73
- * Check if error is forbidden (403)
267
+ * Generates a kebab-case slug from a given text for use in URLs
268
+ * @param text - The text to convert to kebab-case slug
269
+ * @returns The generated kebab-case slug
74
270
  */
75
- declare const isForbidden: (error: AxiosError) => boolean;
271
+ declare const generateUrlSlug: (text: string) => string;
76
272
  /**
77
- * Check if error is not found (404)
273
+ * Generates a snake_case slug from a given text
274
+ * @param text - The text to convert to snake_case slug
275
+ * @returns The generated snake_case slug
78
276
  */
79
- declare const isNotFound: (error: AxiosError) => boolean;
277
+ declare const generateSnakeSlug: (text: string) => string;
278
+
80
279
  /**
81
- * Check if error is server error (5xx)
280
+ * Client Logger Utility
281
+ *
282
+ * Centralized logging utility that:
283
+ * - Only logs in development mode
284
+ * - Can be extended to send logs to external services (Sentry, LogRocket, etc.)
285
+ * - Provides consistent logging interface
82
286
  */
83
- declare const isServerError: (error: AxiosError) => boolean;
287
+ interface LogOptions {
288
+ context?: string;
289
+ metadata?: Record<string, unknown>;
290
+ }
291
+ declare class Logger {
292
+ private isDevelopment;
293
+ /**
294
+ * Log informational messages
295
+ */
296
+ info(message: string, data?: unknown, options?: LogOptions): void;
297
+ /**
298
+ * Log warning messages
299
+ */
300
+ warn(message: string, data?: unknown, options?: LogOptions): void;
301
+ /**
302
+ * Log error messages
303
+ */
304
+ error(message: string, error?: unknown, options?: LogOptions): void;
305
+ /**
306
+ * Log debug messages (only in development)
307
+ */
308
+ debug(message: string, data?: unknown, options?: LogOptions): void;
309
+ /**
310
+ * Log API errors with structured information
311
+ */
312
+ apiError(endpoint: string, error: unknown, metadata?: Record<string, unknown>): void;
313
+ }
314
+ declare const logger: Logger;
84
315
 
85
- export { type ApiResponse, type HttpClientOptions, type PaginatedResponse, createHttpClient, isForbidden, isNotFound, isServerError, isStatusError, isSuccess, isUnauthorized, parseError, parseFullResponse, parseResponse, withAbortSignal, withFormData, withTimeout };
316
+ export { API_BASE_URL, API_PREFIX, type ApiResponse, type ColumnMetadata, ERROR_CODES, type PaginatedResponse, type PaginationData, type ParsedError, STATUS_CODES, STATUS_MESSAGES, SUCCESS_CODES, axiosInstance as axios, clearCustomHeaders, configureHttp, deleteRequest, extractData, extractMessage, extractNestedData, extractPaginatedData, generateSlug, generateSnakeSlug, generateUrlSlug, getApiBaseUrl, getApiPrefix, getCustomHeaders, getHttpConfig, getRequest, isErrorResponse, isSuccess, isSuccessResponse, logger, parseAxiosErrorMessage, parseError, parsePaginatedResponse, parseResponseData, parseResponseMessage, parseResponseStatus, parseResponseStatusMessage, patchRequest, postRequest, putRequest, removeCustomHeader, resetHttpConfig, safeJsonParse, setApiBaseUrl, setApiPrefix, setCustomHeader, setCustomHeaders, simpleMetaParseResponse, simpleParseDualDataResponse, simpleParseResponse, uploadFile };