3a-ecommerce-utils 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. package/README.md +163 -0
  2. package/dist/chunk-PEAZVBSD.mjs +597 -0
  3. package/dist/client-DYGi_pyp.d.mts +87 -0
  4. package/dist/client-DYGi_pyp.d.ts +87 -0
  5. package/dist/index.d.mts +496 -0
  6. package/dist/index.d.ts +496 -0
  7. package/dist/index.js +17707 -0
  8. package/dist/index.mjs +17043 -0
  9. package/dist/validation/server.d.mts +50 -0
  10. package/dist/validation/server.d.ts +50 -0
  11. package/dist/validation/server.js +518 -0
  12. package/dist/validation/server.mjs +168 -0
  13. package/package.json +69 -0
  14. package/src/api/address.queries.ts +96 -0
  15. package/src/api/category.queries.ts +85 -0
  16. package/src/api/coupon.queries.ts +120 -0
  17. package/src/api/dashboard.queries.ts +35 -0
  18. package/src/api/errorHandler.ts +164 -0
  19. package/src/api/graphqlClient.ts +113 -0
  20. package/src/api/index.ts +10 -0
  21. package/src/api/logger.client.ts +89 -0
  22. package/src/api/logger.ts +135 -0
  23. package/src/api/order.queries.ts +211 -0
  24. package/src/api/product.queries.ts +144 -0
  25. package/src/api/review.queries.ts +56 -0
  26. package/src/api/user.queries.ts +232 -0
  27. package/src/assets/3A.png +0 -0
  28. package/src/assets/index.ts +1 -0
  29. package/src/assets.d.ts +29 -0
  30. package/src/auth.ts +176 -0
  31. package/src/config/jest.backend.config.js +42 -0
  32. package/src/config/jest.frontend.config.js +50 -0
  33. package/src/config/postcss.config.js +6 -0
  34. package/src/config/tailwind.config.ts +70 -0
  35. package/src/config/tsconfig.base.json +36 -0
  36. package/src/config/vite.config.ts +86 -0
  37. package/src/config/vitest.base.config.ts +74 -0
  38. package/src/config/webpack.base.config.ts +126 -0
  39. package/src/constants/index.ts +312 -0
  40. package/src/cookies.ts +104 -0
  41. package/src/helpers.ts +400 -0
  42. package/src/index.ts +32 -0
  43. package/src/validation/client.ts +287 -0
  44. package/src/validation/index.ts +3 -0
  45. package/src/validation/server.ts +32 -0
@@ -0,0 +1,164 @@
1
+ import { ErrorType } from "@e-commerce/types";
2
+
3
+ export interface AppError {
4
+ type: ErrorType;
5
+ message: string;
6
+ originalError?: any;
7
+ statusCode?: number;
8
+ }
9
+
10
+ export class ErrorHandler {
11
+ static handle(error: any): AppError {
12
+ if (error.code === 'ECONNABORTED' || error.message === 'Network Error') {
13
+ return {
14
+ type: ErrorType.NETWORK_ERROR,
15
+ message: 'Unable to connect to the server. Please check your internet connection.',
16
+ originalError: error,
17
+ };
18
+ }
19
+
20
+ if (error.response) {
21
+ const { status, data } = error.response;
22
+
23
+ switch (status) {
24
+ case 401:
25
+ return {
26
+ type: ErrorType.AUTHENTICATION_ERROR,
27
+ message: data?.message || 'Authentication failed. Please log in again.',
28
+ statusCode: status,
29
+ originalError: error,
30
+ };
31
+
32
+ case 403:
33
+ return {
34
+ type: ErrorType.AUTHORIZATION_ERROR,
35
+ message: data?.message || 'You do not have permission to perform this action.',
36
+ statusCode: status,
37
+ originalError: error,
38
+ };
39
+
40
+ case 404:
41
+ return {
42
+ type: ErrorType.NOT_FOUND_ERROR,
43
+ message: data?.message || 'The requested resource was not found.',
44
+ statusCode: status,
45
+ originalError: error,
46
+ };
47
+
48
+ case 422:
49
+ return {
50
+ type: ErrorType.VALIDATION_ERROR,
51
+ message: data?.message || 'Invalid input data.',
52
+ statusCode: status,
53
+ originalError: error,
54
+ };
55
+
56
+ case 500:
57
+ case 502:
58
+ case 503:
59
+ case 504:
60
+ return {
61
+ type: ErrorType.SERVER_ERROR,
62
+ message: data?.message || 'A server error occurred. Please try again later.',
63
+ statusCode: status,
64
+ originalError: error,
65
+ };
66
+
67
+ default:
68
+ return {
69
+ type: ErrorType.UNKNOWN_ERROR,
70
+ message: data?.message || 'An unexpected error occurred.',
71
+ statusCode: status,
72
+ originalError: error,
73
+ };
74
+ }
75
+ }
76
+
77
+ if (error.graphQLErrors && error.graphQLErrors.length > 0) {
78
+ const gqlError = error.graphQLErrors[0];
79
+ return {
80
+ type: ErrorType.UNKNOWN_ERROR,
81
+ message: gqlError.message || 'A GraphQL error occurred.',
82
+ originalError: error,
83
+ };
84
+ }
85
+
86
+ return {
87
+ type: ErrorType.UNKNOWN_ERROR,
88
+ message: error.message || 'An unexpected error occurred.',
89
+ originalError: error,
90
+ };
91
+ }
92
+
93
+ static getErrorMessage(error: any): string {
94
+ return this.handle(error).message;
95
+ }
96
+
97
+ static isAuthError(error: any): boolean {
98
+ const handled = this.handle(error);
99
+ return (
100
+ handled.type === ErrorType.AUTHENTICATION_ERROR ||
101
+ handled.type === ErrorType.AUTHORIZATION_ERROR
102
+ );
103
+ }
104
+
105
+ static shouldRetry(error: any): boolean {
106
+ const handled = this.handle(error);
107
+ return handled.type === ErrorType.NETWORK_ERROR || handled.type === ErrorType.SERVER_ERROR;
108
+ }
109
+ }
110
+
111
+ export interface NotificationOptions {
112
+ type: 'success' | 'error' | 'warning' | 'info';
113
+ message: string;
114
+ duration?: number;
115
+ }
116
+
117
+ export class ErrorNotifier {
118
+ private static notifyCallback?: (options: NotificationOptions) => void;
119
+
120
+ static setNotifyCallback(callback: (options: NotificationOptions) => void): void {
121
+ this.notifyCallback = callback;
122
+ }
123
+
124
+ static notifyError(error: any): void {
125
+ const handled = ErrorHandler.handle(error);
126
+ if (this.notifyCallback) {
127
+ this.notifyCallback({
128
+ type: 'error',
129
+ message: handled.message,
130
+ duration: 5000,
131
+ });
132
+ }
133
+ }
134
+
135
+ static notifySuccess(message: string): void {
136
+ if (this.notifyCallback) {
137
+ this.notifyCallback({
138
+ type: 'success',
139
+ message,
140
+ duration: 3000,
141
+ });
142
+ }
143
+ }
144
+
145
+ static notifyWarning(message: string): void {
146
+ if (this.notifyCallback) {
147
+ this.notifyCallback({
148
+ type: 'warning',
149
+ message,
150
+ duration: 4000,
151
+ });
152
+ }
153
+ }
154
+
155
+ static notifyInfo(message: string): void {
156
+ if (this.notifyCallback) {
157
+ this.notifyCallback({
158
+ type: 'info',
159
+ message,
160
+ duration: 3000,
161
+ });
162
+ }
163
+ }
164
+ }
@@ -0,0 +1,113 @@
1
+ import axios, { AxiosInstance, AxiosRequestConfig } from 'axios';
2
+
3
+ export interface GraphQLResponse<T = any> {
4
+ data?: T;
5
+ errors?: Array<{
6
+ message: string;
7
+ locations?: Array<{ line: number; column: number }>;
8
+ path?: string[];
9
+ extensions?: any;
10
+ }>;
11
+ }
12
+
13
+ export interface GraphQLClientConfig {
14
+ url: string;
15
+ headers?: Record<string, string>;
16
+ tokenStorageKey?: string;
17
+ onError?: (error: Error) => void;
18
+ }
19
+
20
+ export class GraphQLClient {
21
+ private client: AxiosInstance;
22
+ private tokenStorageKey: string;
23
+ private onError?: (error: Error) => void;
24
+
25
+ constructor(config: GraphQLClientConfig) {
26
+ this.tokenStorageKey = config.tokenStorageKey || 'auth_token';
27
+ this.onError = config.onError;
28
+
29
+ this.client = axios.create({
30
+ baseURL: config.url,
31
+ headers: {
32
+ 'Content-Type': 'application/json',
33
+ ...config.headers,
34
+ },
35
+ });
36
+
37
+ this.setupInterceptors();
38
+ }
39
+
40
+ private setupInterceptors(): void {
41
+
42
+ this.client.interceptors.request.use(
43
+ (config) => {
44
+ if (typeof window !== 'undefined') {
45
+ const token = localStorage.getItem(this.tokenStorageKey);
46
+ if (token) {
47
+ config.headers.Authorization = `Bearer ${token}`;
48
+ }
49
+ }
50
+ return config;
51
+ },
52
+ (error) => {
53
+ return Promise.reject(error);
54
+ }
55
+ );
56
+
57
+ this.client.interceptors.response.use(
58
+ (response) => {
59
+
60
+ if (response.data.errors && response.data.errors.length > 0) {
61
+ const error = new Error(response.data.errors[0].message);
62
+ if (this.onError) {
63
+ this.onError(error);
64
+ }
65
+ throw error;
66
+ }
67
+ return response;
68
+ },
69
+ (error) => {
70
+ if (this.onError) {
71
+ this.onError(error);
72
+ }
73
+ return Promise.reject(error);
74
+ }
75
+ );
76
+ }
77
+
78
+ async request<T = any>(
79
+ query: string,
80
+ variables?: Record<string, any>,
81
+ config?: AxiosRequestConfig
82
+ ): Promise<T> {
83
+ try {
84
+ const response = await this.client.post<GraphQLResponse<T>>('', { query, variables }, config);
85
+ return response.data.data as T;
86
+ } catch (error: any) {
87
+ throw new Error(error?.response?.data?.errors?.[0]?.message || error.message);
88
+ }
89
+ }
90
+
91
+ setToken(token: string): void {
92
+ if (typeof window !== 'undefined') {
93
+ localStorage.setItem(this.tokenStorageKey, token);
94
+ }
95
+ }
96
+
97
+ clearToken(): void {
98
+ if (typeof window !== 'undefined') {
99
+ localStorage.removeItem(this.tokenStorageKey);
100
+ }
101
+ }
102
+
103
+ getToken(): string | null {
104
+ if (typeof window !== 'undefined') {
105
+ return localStorage.getItem(this.tokenStorageKey);
106
+ }
107
+ return null;
108
+ }
109
+ }
110
+
111
+ export function createGraphQLClient(config: GraphQLClientConfig): GraphQLClient {
112
+ return new GraphQLClient(config);
113
+ }
@@ -0,0 +1,10 @@
1
+ export * from './user.queries';
2
+ export * from './product.queries';
3
+ export * from './order.queries';
4
+ export * from './coupon.queries';
5
+ export * from './category.queries';
6
+ export * from './dashboard.queries';
7
+ export * from './review.queries';
8
+ export * from './address.queries';
9
+ export * from './graphqlClient';
10
+ export * from './logger.client';
@@ -0,0 +1,89 @@
1
+ import { LogLevel } from '@e-commerce/types';
2
+
3
+ export interface LogEntry {
4
+ level: LogLevel;
5
+ message: string;
6
+ timestamp: string;
7
+ data?: any;
8
+ context?: string;
9
+ }
10
+
11
+ /**
12
+ * Client-safe Logger for frontend applications
13
+ * Uses console only, no file system access
14
+ */
15
+ export class Logger {
16
+ private static logs: LogEntry[] = [];
17
+ private static maxLogs = 1000;
18
+ private static enableConsole = true;
19
+
20
+ static configure(options: { maxLogs?: number; enableConsole?: boolean }) {
21
+ if (options.maxLogs !== undefined) this.maxLogs = options.maxLogs;
22
+ if (options.enableConsole !== undefined) this.enableConsole = options.enableConsole;
23
+ }
24
+
25
+ private static addLog(entry: LogEntry) {
26
+ this.logs.push(entry);
27
+ if (this.logs.length > this.maxLogs) this.logs.shift();
28
+ }
29
+
30
+ private static format(entry: LogEntry) {
31
+ return `[${entry.timestamp}] [${entry.level}]${entry.context ? ` [${entry.context}]` : ''}: ${
32
+ entry.message
33
+ }`;
34
+ }
35
+
36
+ static debug(message: string, data?: any, context?: string) {
37
+ this.log(LogLevel.DEBUG, message, data, context);
38
+ }
39
+
40
+ static info(message: string, data?: any, context?: string) {
41
+ this.log(LogLevel.INFO, message, data, context);
42
+ }
43
+
44
+ static warn(message: string, data?: any, context?: string) {
45
+ this.log(LogLevel.WARN, message, data, context);
46
+ }
47
+
48
+ static error(message: string, error?: any, context?: string) {
49
+ this.log(LogLevel.ERROR, message, error, context);
50
+ }
51
+
52
+ private static log(level: LogLevel, message: string, data?: any, context?: string) {
53
+ const entry: LogEntry = {
54
+ level,
55
+ message,
56
+ timestamp: new Date().toISOString(),
57
+ data,
58
+ context,
59
+ };
60
+
61
+ this.addLog(entry);
62
+
63
+ if (!this.enableConsole) return;
64
+
65
+ const formatted = this.format(entry);
66
+
67
+ switch (level) {
68
+ case LogLevel.ERROR:
69
+ console.error(formatted, data ?? '');
70
+ break;
71
+ case LogLevel.WARN:
72
+ console.warn(formatted, data ?? '');
73
+ break;
74
+ case LogLevel.INFO:
75
+ console.info(formatted, data ?? '');
76
+ break;
77
+ default:
78
+ console.debug(formatted, data ?? '');
79
+ }
80
+ }
81
+
82
+ static getLogs(level?: LogLevel): LogEntry[] {
83
+ return level ? this.logs.filter((l) => l.level === level) : [...this.logs];
84
+ }
85
+
86
+ static clearLogs() {
87
+ this.logs = [];
88
+ }
89
+ }
@@ -0,0 +1,135 @@
1
+ /**
2
+ * Server-side Logger with file system support
3
+ * For frontend apps, use logger.client.ts instead
4
+ */
5
+
6
+ import fs from 'fs';
7
+ import path from 'path';
8
+ import { LogLevel } from '@e-commerce/types';
9
+
10
+ export interface LogEntry {
11
+ level: LogLevel;
12
+ message: string;
13
+ timestamp: string;
14
+ data?: any;
15
+ context?: string;
16
+ }
17
+
18
+ // Log level priority for filtering
19
+ const LOG_LEVEL_PRIORITY: Record<LogLevel, number> = {
20
+ [LogLevel.DEBUG]: 0,
21
+ [LogLevel.INFO]: 1,
22
+ [LogLevel.WARN]: 2,
23
+ [LogLevel.ERROR]: 3,
24
+ };
25
+
26
+ export class Logger {
27
+ private static logs: LogEntry[] = [];
28
+ private static maxLogs = 1000;
29
+ private static enableConsole = true;
30
+ private static enableFile = false;
31
+ private static logFilePath = path.join(process.cwd(), 'logs/app.log');
32
+ private static logLevel: LogLevel = LogLevel.DEBUG;
33
+
34
+ static configure(options: {
35
+ maxLogs?: number;
36
+ enableConsole?: boolean;
37
+ enableFile?: boolean;
38
+ logFilePath?: string;
39
+ logLevel?: LogLevel | string;
40
+ }) {
41
+ if (options.maxLogs !== undefined) this.maxLogs = options.maxLogs;
42
+ if (options.enableConsole !== undefined) this.enableConsole = options.enableConsole;
43
+ if (options.enableFile !== undefined) this.enableFile = options.enableFile;
44
+ if (options.logFilePath) this.logFilePath = options.logFilePath;
45
+ if (options.logLevel) {
46
+ const level =
47
+ typeof options.logLevel === 'string'
48
+ ? (options.logLevel.toUpperCase() as LogLevel)
49
+ : options.logLevel;
50
+ if (Object.values(LogLevel).includes(level)) {
51
+ this.logLevel = level;
52
+ }
53
+ }
54
+ }
55
+
56
+ private static shouldLog(level: LogLevel): boolean {
57
+ return LOG_LEVEL_PRIORITY[level] >= LOG_LEVEL_PRIORITY[this.logLevel];
58
+ }
59
+
60
+ private static addLog(entry: LogEntry) {
61
+ this.logs.push(entry);
62
+ if (this.logs.length > this.maxLogs) this.logs.shift();
63
+
64
+ if (this.enableFile) {
65
+ fs.mkdirSync(path.dirname(this.logFilePath), { recursive: true });
66
+ fs.appendFileSync(this.logFilePath, JSON.stringify(entry) + '\n');
67
+ }
68
+ }
69
+
70
+ private static format(entry: LogEntry) {
71
+ return `[${entry.timestamp}] [${entry.level}]${entry.context ? ` [${entry.context}]` : ''}: ${
72
+ entry.message
73
+ }`;
74
+ }
75
+
76
+ static debug(message: string, data?: any, context?: string) {
77
+ this.log(LogLevel.DEBUG, message, data, context);
78
+ }
79
+
80
+ static info(message: string, data?: any, context?: string) {
81
+ this.log(LogLevel.INFO, message, data, context);
82
+ }
83
+
84
+ static warn(message: string, data?: any, context?: string) {
85
+ this.log(LogLevel.WARN, message, data, context);
86
+ }
87
+
88
+ static error(message: string, error?: any, context?: string) {
89
+ this.log(LogLevel.ERROR, message, error, context);
90
+ }
91
+
92
+ private static log(level: LogLevel, message: string, data?: any, context?: string) {
93
+ // Check if this log level should be logged based on configured level
94
+ if (!this.shouldLog(level)) return;
95
+
96
+ const entry: LogEntry = {
97
+ level,
98
+ message,
99
+ timestamp: new Date().toISOString(),
100
+ data,
101
+ context,
102
+ };
103
+
104
+ this.addLog(entry);
105
+
106
+ if (!this.enableConsole) return;
107
+
108
+ const formatted = this.format(entry);
109
+
110
+ switch (level) {
111
+ case LogLevel.ERROR:
112
+ console.error(formatted, data ?? '');
113
+ break;
114
+ case LogLevel.WARN:
115
+ console.warn(formatted, data ?? '');
116
+ break;
117
+ case LogLevel.INFO:
118
+ console.info(formatted, data ?? '');
119
+ break;
120
+ default:
121
+ console.debug(formatted, data ?? '');
122
+ }
123
+ }
124
+
125
+ static getLogs(level?: LogLevel): LogEntry[] {
126
+ return level ? this.logs.filter((l) => l.level === level) : [...this.logs];
127
+ }
128
+
129
+ static clearLogs() {
130
+ this.logs = [];
131
+ if (this.enableFile && fs.existsSync(this.logFilePath)) {
132
+ fs.unlinkSync(this.logFilePath);
133
+ }
134
+ }
135
+ }
@@ -0,0 +1,211 @@
1
+ export const GET_ORDERS_QUERY = `
2
+ query GetOrders($page: Int, $limit: Int, $customerId: String) {
3
+ orders(page: $page, limit: $limit, customerId: $customerId) {
4
+ orders {
5
+ id
6
+ orderNumber
7
+ customerId
8
+ customerEmail
9
+ items {
10
+ productId
11
+ productName
12
+ quantity
13
+ price
14
+ subtotal
15
+ }
16
+ subtotal
17
+ tax
18
+ shipping
19
+ discount
20
+ total
21
+ orderStatus
22
+ paymentStatus
23
+ paymentMethod
24
+ shippingAddress {
25
+ street
26
+ city
27
+ state
28
+ zip
29
+ country
30
+ }
31
+ notes
32
+ createdAt
33
+ updatedAt
34
+ }
35
+ pagination {
36
+ page
37
+ limit
38
+ total
39
+ pages
40
+ }
41
+ }
42
+ }
43
+ `;
44
+
45
+ export const GET_ORDER_QUERY = `
46
+ query GetOrder($id: ID!) {
47
+ order(id: $id) {
48
+ id
49
+ orderNumber
50
+ customerId
51
+ customerEmail
52
+ items {
53
+ productId
54
+ productName
55
+ quantity
56
+ price
57
+ subtotal
58
+ }
59
+ subtotal
60
+ tax
61
+ shipping
62
+ total
63
+ orderStatus
64
+ paymentStatus
65
+ paymentMethod
66
+ shippingAddress {
67
+ street
68
+ city
69
+ state
70
+ zip
71
+ country
72
+ }
73
+ notes
74
+ createdAt
75
+ updatedAt
76
+ }
77
+ }
78
+ `;
79
+
80
+ export const GET_ORDERS_BY_CUSTOMER_QUERY = `
81
+ query GetOrdersByCustomer($customerId: String!) {
82
+ ordersByCustomer(customerId: $customerId) {
83
+ id
84
+ orderNumber
85
+ customerId
86
+ customerEmail
87
+ items {
88
+ productId
89
+ productName
90
+ quantity
91
+ price
92
+ subtotal
93
+ }
94
+ subtotal
95
+ tax
96
+ shipping
97
+ total
98
+ orderStatus
99
+ paymentStatus
100
+ paymentMethod
101
+ shippingAddress {
102
+ street
103
+ city
104
+ state
105
+ zip
106
+ country
107
+ }
108
+ notes
109
+ createdAt
110
+ updatedAt
111
+ }
112
+ }
113
+ `;
114
+
115
+ export const CREATE_ORDER_MUTATION = `
116
+ mutation CreateOrder($input: CreateOrderInput!) {
117
+ createOrder(input: $input) {
118
+ order {
119
+ id
120
+ orderNumber
121
+ customerId
122
+ customerEmail
123
+ sellerId
124
+ items {
125
+ productId
126
+ productName
127
+ quantity
128
+ price
129
+ sellerId
130
+ subtotal
131
+ }
132
+ subtotal
133
+ tax
134
+ shipping
135
+ discount
136
+ couponCode
137
+ total
138
+ orderStatus
139
+ paymentStatus
140
+ paymentMethod
141
+ shippingAddress {
142
+ street
143
+ city
144
+ state
145
+ zip
146
+ country
147
+ }
148
+ notes
149
+ createdAt
150
+ updatedAt
151
+ }
152
+ orders {
153
+ id
154
+ orderNumber
155
+ customerId
156
+ customerEmail
157
+ sellerId
158
+ items {
159
+ productId
160
+ productName
161
+ quantity
162
+ price
163
+ sellerId
164
+ subtotal
165
+ }
166
+ subtotal
167
+ tax
168
+ shipping
169
+ discount
170
+ total
171
+ orderStatus
172
+ paymentStatus
173
+ createdAt
174
+ }
175
+ orderCount
176
+ }
177
+ }
178
+ `;
179
+
180
+ export const UPDATE_ORDER_STATUS_MUTATION = `
181
+ mutation UpdateOrderStatus($id: ID!, $status: OrderStatus!) {
182
+ updateOrderStatus(id: $id, status: $status) {
183
+ id
184
+ orderNumber
185
+ orderStatus
186
+ updatedAt
187
+ }
188
+ }
189
+ `;
190
+
191
+ export const UPDATE_PAYMENT_STATUS_MUTATION = `
192
+ mutation UpdatePaymentStatus($id: ID!, $status: PaymentStatus!) {
193
+ updatePaymentStatus(id: $id, status: $status) {
194
+ id
195
+ orderNumber
196
+ paymentStatus
197
+ updatedAt
198
+ }
199
+ }
200
+ `;
201
+
202
+ export const CANCEL_ORDER_MUTATION = `
203
+ mutation CancelOrder($id: ID!) {
204
+ cancelOrder(id: $id) {
205
+ id
206
+ orderNumber
207
+ orderStatus
208
+ updatedAt
209
+ }
210
+ }
211
+ `;