@exyconn/common 2.1.0 → 2.3.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +969 -261
- package/dist/client/hooks/index.d.mts +1042 -0
- package/dist/client/hooks/index.d.ts +1042 -0
- package/dist/client/hooks/index.js +2276 -0
- package/dist/client/hooks/index.js.map +1 -0
- package/dist/client/hooks/index.mjs +2217 -0
- package/dist/client/hooks/index.mjs.map +1 -0
- package/dist/client/http/index.d.mts +217 -49
- package/dist/client/http/index.d.ts +217 -49
- package/dist/client/http/index.js +473 -94
- package/dist/client/http/index.js.map +1 -1
- package/dist/client/http/index.mjs +441 -84
- package/dist/client/http/index.mjs.map +1 -1
- package/dist/client/index.d.mts +6 -4
- package/dist/client/index.d.ts +6 -4
- package/dist/client/index.js +481 -319
- package/dist/client/index.js.map +1 -1
- package/dist/client/index.mjs +449 -290
- package/dist/client/index.mjs.map +1 -1
- package/dist/client/utils/index.d.mts +3 -279
- package/dist/client/utils/index.d.ts +3 -279
- package/dist/client/web/index.d.mts +1461 -0
- package/dist/client/web/index.d.ts +1461 -0
- package/dist/client/web/index.js +2681 -0
- package/dist/client/web/index.js.map +1 -0
- package/dist/client/web/index.mjs +2618 -0
- package/dist/client/web/index.mjs.map +1 -0
- package/dist/data/brand-identity.d.mts +149 -0
- package/dist/data/brand-identity.d.ts +149 -0
- package/dist/data/brand-identity.js +235 -0
- package/dist/data/brand-identity.js.map +1 -0
- package/dist/data/brand-identity.mjs +220 -0
- package/dist/data/brand-identity.mjs.map +1 -0
- package/dist/data/countries.d.mts +61 -0
- package/dist/data/countries.d.ts +61 -0
- package/dist/data/countries.js +987 -0
- package/dist/data/countries.js.map +1 -0
- package/dist/data/countries.mjs +971 -0
- package/dist/data/countries.mjs.map +1 -0
- package/dist/data/currencies.d.mts +19 -0
- package/dist/data/currencies.d.ts +19 -0
- package/dist/data/currencies.js +162 -0
- package/dist/data/currencies.js.map +1 -0
- package/dist/data/currencies.mjs +153 -0
- package/dist/data/currencies.mjs.map +1 -0
- package/dist/data/index.d.mts +7 -0
- package/dist/data/index.d.ts +7 -0
- package/dist/data/index.js +2087 -0
- package/dist/data/index.js.map +1 -0
- package/dist/data/index.mjs +1948 -0
- package/dist/data/index.mjs.map +1 -0
- package/dist/data/phone-codes.d.mts +15 -0
- package/dist/data/phone-codes.d.ts +15 -0
- package/dist/data/phone-codes.js +219 -0
- package/dist/data/phone-codes.js.map +1 -0
- package/dist/data/phone-codes.mjs +211 -0
- package/dist/data/phone-codes.mjs.map +1 -0
- package/dist/data/regex.d.mts +287 -0
- package/dist/data/regex.d.ts +287 -0
- package/dist/data/regex.js +306 -0
- package/dist/data/regex.js.map +1 -0
- package/dist/data/regex.mjs +208 -0
- package/dist/data/regex.mjs.map +1 -0
- package/dist/data/timezones.d.mts +16 -0
- package/dist/data/timezones.d.ts +16 -0
- package/dist/data/timezones.js +98 -0
- package/dist/data/timezones.js.map +1 -0
- package/dist/data/timezones.mjs +89 -0
- package/dist/data/timezones.mjs.map +1 -0
- package/dist/index-BZf42T3R.d.mts +305 -0
- package/dist/index-CF0D8PGE.d.ts +305 -0
- package/dist/index-Ckhm_HaX.d.mts +138 -0
- package/dist/index-DKn4raO7.d.ts +222 -0
- package/dist/index-NS8dS0p9.d.mts +222 -0
- package/dist/index-Nqm5_lwT.d.ts +188 -0
- package/dist/index-br6POSyA.d.ts +138 -0
- package/dist/index-jBi3V6e5.d.mts +188 -0
- package/dist/index.d.mts +21 -580
- package/dist/index.d.ts +21 -580
- package/dist/index.js +1839 -347
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1850 -359
- package/dist/index.mjs.map +1 -1
- package/dist/packageCheck-B_qfsD6R.d.ts +280 -0
- package/dist/packageCheck-C2_FT_Rl.d.mts +280 -0
- package/dist/server/configs/index.d.mts +602 -0
- package/dist/server/configs/index.d.ts +602 -0
- package/dist/server/configs/index.js +707 -0
- package/dist/server/configs/index.js.map +1 -0
- package/dist/server/configs/index.mjs +665 -0
- package/dist/server/configs/index.mjs.map +1 -0
- package/dist/server/index.d.mts +4 -1
- package/dist/server/index.d.ts +4 -1
- package/dist/server/index.js +1330 -0
- package/dist/server/index.js.map +1 -1
- package/dist/server/index.mjs +1286 -2
- package/dist/server/index.mjs.map +1 -1
- package/dist/server/middleware/index.d.mts +283 -2
- package/dist/server/middleware/index.d.ts +283 -2
- package/dist/server/middleware/index.js +761 -0
- package/dist/server/middleware/index.js.map +1 -1
- package/dist/server/middleware/index.mjs +751 -1
- package/dist/server/middleware/index.mjs.map +1 -1
- package/dist/shared/config/index.d.mts +40 -0
- package/dist/shared/config/index.d.ts +40 -0
- package/dist/shared/config/index.js +58 -0
- package/dist/shared/config/index.js.map +1 -0
- package/dist/shared/config/index.mjs +51 -0
- package/dist/shared/config/index.mjs.map +1 -0
- package/dist/shared/constants/index.d.mts +593 -0
- package/dist/shared/constants/index.d.ts +593 -0
- package/dist/shared/constants/index.js +391 -0
- package/dist/shared/constants/index.js.map +1 -0
- package/dist/shared/constants/index.mjs +360 -0
- package/dist/shared/constants/index.mjs.map +1 -0
- package/dist/shared/index.d.mts +5 -1
- package/dist/shared/index.d.ts +5 -1
- package/dist/shared/types/index.d.mts +140 -0
- package/dist/shared/types/index.d.ts +140 -0
- package/dist/shared/types/index.js +4 -0
- package/dist/shared/types/index.js.map +1 -0
- package/dist/shared/types/index.mjs +3 -0
- package/dist/shared/types/index.mjs.map +1 -0
- package/dist/shared/utils/index.d.mts +255 -0
- package/dist/shared/utils/index.d.ts +255 -0
- package/dist/shared/utils/index.js +623 -0
- package/dist/shared/utils/index.js.map +1 -0
- package/dist/shared/utils/index.mjs +324 -0
- package/dist/shared/utils/index.mjs.map +1 -0
- package/dist/shared/validation/index.d.mts +258 -0
- package/dist/shared/validation/index.d.ts +258 -0
- package/dist/shared/validation/index.js +185 -0
- package/dist/shared/validation/index.js.map +1 -0
- package/dist/shared/validation/index.mjs +172 -0
- package/dist/shared/validation/index.mjs.map +1 -0
- package/package.json +127 -62
- package/dist/index-BcxL4_V4.d.ts +0 -2946
- package/dist/index-DEzgM15j.d.ts +0 -67
- package/dist/index-DNFVgQx8.d.ts +0 -1375
- package/dist/index-DbV04Dx8.d.mts +0 -67
- package/dist/index-DfqEP6Oe.d.mts +0 -1375
- package/dist/index-bvvCev9Q.d.mts +0 -2946
package/README.md
CHANGED
|
@@ -1,6 +1,39 @@
|
|
|
1
1
|
# @exyconn/common
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
[](https://www.npmjs.com/package/@exyconn/common)
|
|
4
|
+
[](https://github.com/exyconn/common/actions/workflows/ci.yml)
|
|
5
|
+
[](https://codecov.io/gh/exyconn/common)
|
|
6
|
+
[](https://opensource.org/licenses/MIT)
|
|
7
|
+
[](https://www.typescriptlang.org/)
|
|
8
|
+
[](https://bundlephobia.com/package/@exyconn/common)
|
|
9
|
+
[](https://common-docs.exyconn.com)
|
|
10
|
+
|
|
11
|
+
Production-ready common utilities, hooks, types, and data shared across all Exyconn projects (botify.life, exyconn.com, partywings.fun, sibera.work, spentiva.com).
|
|
12
|
+
|
|
13
|
+
## 📚 Documentation
|
|
14
|
+
|
|
15
|
+
**Full documentation available at: [common-docs.exyconn.com](https://common-docs.exyconn.com)**
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## Table of Contents
|
|
20
|
+
|
|
21
|
+
- [Installation](#installation)
|
|
22
|
+
- [Features](#features)
|
|
23
|
+
- [Quick Start](#quick-start)
|
|
24
|
+
- [Module Reference](#module-reference)
|
|
25
|
+
- [Server](#server-module)
|
|
26
|
+
- [Client](#client-module)
|
|
27
|
+
- [Shared](#shared-module)
|
|
28
|
+
- [Data](#data-module)
|
|
29
|
+
- [React Hooks](#react-hooks)
|
|
30
|
+
- [API Reference](#api-reference)
|
|
31
|
+
- [Development](#development)
|
|
32
|
+
- [Peer Dependencies](#peer-dependencies)
|
|
33
|
+
- [Contributing](#contributing)
|
|
34
|
+
- [License](#license)
|
|
35
|
+
|
|
36
|
+
---
|
|
4
37
|
|
|
5
38
|
## Installation
|
|
6
39
|
|
|
@@ -12,333 +45,1008 @@ yarn add @exyconn/common
|
|
|
12
45
|
pnpm add @exyconn/common
|
|
13
46
|
```
|
|
14
47
|
|
|
48
|
+
### Requirements
|
|
49
|
+
|
|
50
|
+
- Node.js >= 18.0.0
|
|
51
|
+
- React >= 18.0.0 (for hooks)
|
|
52
|
+
- TypeScript >= 5.0.0 (recommended)
|
|
53
|
+
|
|
15
54
|
## Features
|
|
16
55
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
56
|
+
| Category | Description |
|
|
57
|
+
|----------|-------------|
|
|
58
|
+
| 🖥️ **Server utilities** | Response helpers, middleware, logging, database connections, dynamic configs |
|
|
59
|
+
| 🌐 **Client utilities** | HTTP clients, React hooks (54+), response parsing |
|
|
60
|
+
| 📝 **Shared types** | TypeScript interfaces for API responses, users, etc. |
|
|
61
|
+
| 🗃️ **Data modules** | Countries, currencies, phone codes, timezones, brand logos |
|
|
62
|
+
| ✅ **Validation** | Common patterns, regex, and validation helpers (Zod integration) |
|
|
63
|
+
| 📅 **Date utilities** | Enhanced date-fns with timezone support |
|
|
64
|
+
| 🎨 **Brand Identity** | Complete brand configs with logos, colors, and SEO |
|
|
65
|
+
| 🔢 **Enums & Constants** | STATUS, SORT, ROLE, BREAKPOINTS, and more |
|
|
66
|
+
| ⚙️ **Dynamic Configs** | Production-ready CORS, Rate Limiting, Server configs |
|
|
67
|
+
| 🧪 **Fully Tested** | Comprehensive test coverage with Vitest |
|
|
68
|
+
|
|
69
|
+
---
|
|
70
|
+
|
|
71
|
+
## Quick Start
|
|
72
|
+
|
|
73
|
+
### Import by Module (Recommended)
|
|
74
|
+
|
|
75
|
+
```typescript
|
|
76
|
+
// Server utilities
|
|
77
|
+
import { successResponse, errorResponse } from '@exyconn/common/server/response';
|
|
78
|
+
import { createCorsOptions, createRateLimiter } from '@exyconn/common/server/configs';
|
|
79
|
+
import { createLogger } from '@exyconn/common/server/logger';
|
|
80
|
+
import { connectDB } from '@exyconn/common/server/db';
|
|
81
|
+
import { createCrudControllers, queryParser, queryPagination } from '@exyconn/common/server/middleware';
|
|
82
|
+
|
|
83
|
+
// Client utilities
|
|
84
|
+
import { useLocalStorage, useDebounce } from '@exyconn/common/client/hooks';
|
|
85
|
+
import { getRequest, postRequest, extractData, isSuccess } from '@exyconn/common/client/http';
|
|
86
|
+
|
|
87
|
+
// Shared utilities
|
|
88
|
+
import { isValidEmail, VALIDATION_PATTERNS } from '@exyconn/common/shared/validation';
|
|
89
|
+
import { getEnv, isProd } from '@exyconn/common/shared/config';
|
|
25
90
|
|
|
26
|
-
|
|
91
|
+
// Data modules
|
|
92
|
+
import { countries, getCountryByCode } from '@exyconn/common/data/countries';
|
|
93
|
+
import { BRANDS, getBrandById } from '@exyconn/common/data/brand-identity';
|
|
94
|
+
```
|
|
27
95
|
|
|
28
|
-
### Import Everything
|
|
96
|
+
### Import Everything (Not Recommended for Bundle Size)
|
|
29
97
|
|
|
30
98
|
```typescript
|
|
31
99
|
import { server, client, shared, data } from '@exyconn/common';
|
|
32
100
|
```
|
|
33
101
|
|
|
34
|
-
|
|
102
|
+
---
|
|
103
|
+
|
|
104
|
+
## Module Reference
|
|
105
|
+
|
|
106
|
+
### Server Module
|
|
107
|
+
|
|
108
|
+
**Import:** `@exyconn/common/server` or specific submodules
|
|
109
|
+
|
|
110
|
+
#### Server Configs (`@exyconn/common/server/configs`)
|
|
111
|
+
|
|
112
|
+
Dynamic configuration system for production applications.
|
|
113
|
+
|
|
114
|
+
##### ConfigBuilder
|
|
35
115
|
|
|
36
116
|
```typescript
|
|
37
|
-
|
|
38
|
-
|
|
117
|
+
import {
|
|
118
|
+
createConfig,
|
|
119
|
+
buildConfig,
|
|
120
|
+
ConfigBuilder,
|
|
121
|
+
// Types
|
|
122
|
+
type AppConfig,
|
|
123
|
+
type ServerConfig,
|
|
124
|
+
type DatabaseConfig,
|
|
125
|
+
type AuthConfig,
|
|
126
|
+
type LoggingConfig,
|
|
127
|
+
} from '@exyconn/common/server/configs';
|
|
39
128
|
|
|
40
|
-
//
|
|
41
|
-
|
|
129
|
+
// Method 1: Using ConfigBuilder (fluent API)
|
|
130
|
+
const config = createConfig()
|
|
131
|
+
.setServer({
|
|
132
|
+
name: 'my-api-server',
|
|
133
|
+
port: 4000,
|
|
134
|
+
environment: 'production',
|
|
135
|
+
})
|
|
136
|
+
.setDatabase({
|
|
137
|
+
uri: process.env.MONGODB_URI!,
|
|
138
|
+
maxPoolSize: 50,
|
|
139
|
+
})
|
|
140
|
+
.setAuth({
|
|
141
|
+
jwtSecret: process.env.JWT_SECRET!,
|
|
142
|
+
jwtExpiresIn: '7d',
|
|
143
|
+
})
|
|
144
|
+
.setLogging({
|
|
145
|
+
level: 'info',
|
|
146
|
+
file: true,
|
|
147
|
+
})
|
|
148
|
+
.addProductionOrigin('https://myapp.com')
|
|
149
|
+
.addProductionOrigin('https://api.myapp.com')
|
|
150
|
+
.addCorsPattern('.myapp.com')
|
|
151
|
+
.addRateLimitTier('upload', {
|
|
152
|
+
windowMs: 60000,
|
|
153
|
+
maxRequests: 5,
|
|
154
|
+
message: 'Upload limit exceeded',
|
|
155
|
+
})
|
|
156
|
+
.setCustom('featureFlags', { newFeature: true })
|
|
157
|
+
.loadFromEnv()
|
|
158
|
+
.build();
|
|
42
159
|
|
|
43
|
-
//
|
|
44
|
-
|
|
160
|
+
// Method 2: Quick build from partial config
|
|
161
|
+
const quickConfig = buildConfig({
|
|
162
|
+
server: { name: 'quick-server', port: 3000 },
|
|
163
|
+
database: { uri: 'mongodb://localhost/mydb' },
|
|
164
|
+
});
|
|
45
165
|
|
|
46
|
-
//
|
|
47
|
-
|
|
166
|
+
// Validate configuration
|
|
167
|
+
const validation = createConfig().setServer({ name: '' }).validate();
|
|
168
|
+
if (!validation.valid) {
|
|
169
|
+
console.error('Config errors:', validation.errors);
|
|
170
|
+
}
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
**ConfigBuilder Methods:**
|
|
174
|
+
|
|
175
|
+
| Method | Parameters | Description |
|
|
176
|
+
|--------|------------|-------------|
|
|
177
|
+
| `setServer(config)` | `Partial<ServerConfig>` | Set server configuration |
|
|
178
|
+
| `setDatabase(config)` | `Partial<DatabaseConfig>` | Set database configuration |
|
|
179
|
+
| `setAuth(config)` | `Partial<AuthConfig>` | Set auth configuration |
|
|
180
|
+
| `setLogging(config)` | `Partial<LoggingConfig>` | Set logging configuration |
|
|
181
|
+
| `setCorsOrigins(config)` | `Partial<CorsOriginsConfig>` | Set CORS origins |
|
|
182
|
+
| `addProductionOrigin(origin)` | `string` | Add a production CORS origin |
|
|
183
|
+
| `addDevelopmentOrigin(origin)` | `string` | Add a development CORS origin |
|
|
184
|
+
| `addCorsPattern(pattern)` | `string` | Add subdomain pattern |
|
|
185
|
+
| `setRateLimit(config)` | `Partial<RateLimitConfig>` | Set rate limit config |
|
|
186
|
+
| `addRateLimitTier(name, tier)` | `string, RateLimitTier` | Add custom rate limit tier |
|
|
187
|
+
| `setCustom(key, value)` | `string, unknown` | Set custom config value |
|
|
188
|
+
| `loadFromEnv()` | - | Load config from environment variables |
|
|
189
|
+
| `validate()` | - | Validate configuration, returns `{ valid, errors }` |
|
|
190
|
+
| `build()` | - | Build final configuration |
|
|
48
191
|
|
|
49
|
-
|
|
50
|
-
|
|
192
|
+
##### CORS Configuration
|
|
193
|
+
|
|
194
|
+
```typescript
|
|
195
|
+
import {
|
|
196
|
+
createCorsOptions,
|
|
197
|
+
createBrandCorsOptions,
|
|
198
|
+
createMultiBrandCorsOptions,
|
|
199
|
+
// Presets
|
|
200
|
+
DEFAULT_CORS_CONFIG,
|
|
201
|
+
EXYCONN_CORS_CONFIG,
|
|
202
|
+
STRICT_CORS_CONFIG,
|
|
203
|
+
PERMISSIVE_CORS_CONFIG,
|
|
204
|
+
// Types
|
|
205
|
+
type CorsConfig,
|
|
206
|
+
} from '@exyconn/common/server/configs';
|
|
207
|
+
|
|
208
|
+
// Custom CORS configuration
|
|
209
|
+
const corsOptions = createCorsOptions({
|
|
210
|
+
productionOrigins: ['https://myapp.com', 'https://api.myapp.com'],
|
|
211
|
+
developmentOrigins: ['http://localhost:3000', 'http://localhost:5173'],
|
|
212
|
+
allowedSubdomains: ['.myapp.com'],
|
|
213
|
+
originPatterns: [/\.myapp\.(com|io)$/],
|
|
214
|
+
allowNoOrigin: true, // Allow mobile apps, curl
|
|
215
|
+
allowAllInDev: true, // Allow all in development
|
|
216
|
+
credentials: true,
|
|
217
|
+
methods: ['GET', 'POST', 'PUT', 'DELETE', 'PATCH'],
|
|
218
|
+
allowedHeaders: ['Content-Type', 'Authorization', 'X-API-Key'],
|
|
219
|
+
exposedHeaders: ['X-Total-Count'],
|
|
220
|
+
maxAge: 86400, // 24 hours preflight cache
|
|
221
|
+
customValidator: (origin) => origin.includes('trusted'), // Custom validation
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
// Single brand CORS (auto-includes www and subdomains)
|
|
225
|
+
const brandCors = createBrandCorsOptions('myapp.com', {
|
|
226
|
+
credentials: true,
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
// Multi-brand CORS
|
|
230
|
+
const multiBrandCors = createMultiBrandCorsOptions(
|
|
231
|
+
['myapp.com', 'myapp.io', 'api.myapp.com'],
|
|
232
|
+
{ allowNoOrigin: false }
|
|
233
|
+
);
|
|
234
|
+
|
|
235
|
+
// Usage with Express
|
|
236
|
+
import cors from 'cors';
|
|
237
|
+
app.use(cors(corsOptions));
|
|
51
238
|
```
|
|
52
239
|
|
|
53
|
-
|
|
240
|
+
**CorsConfig Options:**
|
|
241
|
+
|
|
242
|
+
| Option | Type | Default | Required | Description |
|
|
243
|
+
|--------|------|---------|----------|-------------|
|
|
244
|
+
| `productionOrigins` | `string[]` | `[]` | No | Allowed production origins |
|
|
245
|
+
| `developmentOrigins` | `string[]` | Common localhost ports | No | Allowed development origins |
|
|
246
|
+
| `allowedSubdomains` | `string[]` | `[]` | No | Subdomain patterns (e.g., `.myapp.com`) |
|
|
247
|
+
| `originPatterns` | `RegExp[]` | `[]` | No | Regex patterns for origin matching |
|
|
248
|
+
| `allowNoOrigin` | `boolean` | `true` | No | Allow requests with no origin |
|
|
249
|
+
| `allowAllInDev` | `boolean` | `true` | No | Allow all origins in development |
|
|
250
|
+
| `customValidator` | `(origin: string) => boolean` | - | No | Custom origin validator |
|
|
251
|
+
| `credentials` | `boolean` | `true` | No | Include credentials |
|
|
252
|
+
| `methods` | `string[]` | All standard methods | No | Allowed HTTP methods |
|
|
253
|
+
| `allowedHeaders` | `string[]` | Common headers | No | Allowed request headers |
|
|
254
|
+
| `exposedHeaders` | `string[]` | Common response headers | No | Exposed response headers |
|
|
255
|
+
| `maxAge` | `number` | `86400` | No | Preflight cache duration (seconds) |
|
|
256
|
+
|
|
257
|
+
##### Rate Limiter Configuration
|
|
54
258
|
|
|
55
259
|
```typescript
|
|
56
|
-
|
|
57
|
-
|
|
260
|
+
import {
|
|
261
|
+
// Factory functions
|
|
262
|
+
createRateLimiter,
|
|
263
|
+
createStandardRateLimiter,
|
|
264
|
+
createStrictRateLimiter,
|
|
265
|
+
createDdosRateLimiter,
|
|
266
|
+
createApiRateLimiter,
|
|
267
|
+
// Builder
|
|
268
|
+
rateLimiter,
|
|
269
|
+
RateLimiterBuilder,
|
|
270
|
+
// Key generators
|
|
271
|
+
defaultKeyGenerator,
|
|
272
|
+
createPrefixedKeyGenerator,
|
|
273
|
+
createUserKeyGenerator,
|
|
274
|
+
createApiKeyGenerator,
|
|
275
|
+
// Constants
|
|
276
|
+
DEFAULT_RATE_LIMIT_TIERS,
|
|
277
|
+
// Types
|
|
278
|
+
type RateLimitTierConfig,
|
|
279
|
+
} from '@exyconn/common/server/configs';
|
|
280
|
+
|
|
281
|
+
// Method 1: Factory functions with defaults
|
|
282
|
+
const standardLimiter = createStandardRateLimiter(); // 100 req/15min
|
|
283
|
+
const strictLimiter = createStrictRateLimiter(); // 20 req/15min
|
|
284
|
+
const ddosLimiter = createDdosRateLimiter(); // 60 req/1min
|
|
285
|
+
const apiLimiter = createApiRateLimiter(); // 30 req/1min (by API key)
|
|
286
|
+
|
|
287
|
+
// Method 2: Custom configuration
|
|
288
|
+
const customLimiter = createRateLimiter({
|
|
289
|
+
windowMs: 5 * 60 * 1000, // 5 minutes
|
|
290
|
+
maxRequests: 50,
|
|
291
|
+
message: 'Too many requests, please slow down.',
|
|
292
|
+
skipSuccessfulRequests: false,
|
|
293
|
+
skipFailedRequests: false,
|
|
294
|
+
}, {
|
|
295
|
+
standardHeaders: true,
|
|
296
|
+
legacyHeaders: false,
|
|
297
|
+
keyGenerator: defaultKeyGenerator,
|
|
298
|
+
skip: (req) => req.headers['x-skip-rate-limit'] === 'true',
|
|
299
|
+
});
|
|
300
|
+
|
|
301
|
+
// Method 3: Builder pattern (most flexible)
|
|
302
|
+
const uploadLimiter = rateLimiter('STRICT')
|
|
303
|
+
.windowMinutes(5)
|
|
304
|
+
.max(10)
|
|
305
|
+
.message('Upload limit exceeded. Please wait.')
|
|
306
|
+
.keyByIp()
|
|
307
|
+
.skipWhen((req) => req.user?.isPremium)
|
|
308
|
+
.build();
|
|
309
|
+
|
|
310
|
+
// User-based rate limiting
|
|
311
|
+
const userLimiter = rateLimiter()
|
|
312
|
+
.windowHours(1)
|
|
313
|
+
.max(1000)
|
|
314
|
+
.keyBy((req) => req.userId || defaultKeyGenerator(req))
|
|
315
|
+
.build();
|
|
316
|
+
|
|
317
|
+
// API key based rate limiting
|
|
318
|
+
const apiKeyLimiter = rateLimiter('API')
|
|
319
|
+
.keyByApiKey('x-api-key')
|
|
320
|
+
.build();
|
|
58
321
|
|
|
59
|
-
//
|
|
322
|
+
// Usage with Express
|
|
323
|
+
app.use('/api/', ddosLimiter);
|
|
324
|
+
app.use('/api/', standardLimiter);
|
|
325
|
+
app.post('/api/auth/login', strictLimiter);
|
|
326
|
+
app.post('/api/upload', uploadLimiter);
|
|
327
|
+
```
|
|
328
|
+
|
|
329
|
+
**RateLimiterBuilder Methods:**
|
|
330
|
+
|
|
331
|
+
| Method | Parameters | Description |
|
|
332
|
+
|--------|------------|-------------|
|
|
333
|
+
| `windowMs(ms)` | `number` | Set window in milliseconds |
|
|
334
|
+
| `windowMinutes(minutes)` | `number` | Set window in minutes |
|
|
335
|
+
| `windowHours(hours)` | `number` | Set window in hours |
|
|
336
|
+
| `max(requests)` | `number` | Set max requests in window |
|
|
337
|
+
| `message(msg)` | `string` | Set error message |
|
|
338
|
+
| `skipSuccessful(skip?)` | `boolean` | Skip successful requests (default: true) |
|
|
339
|
+
| `skipFailed(skip?)` | `boolean` | Skip failed requests (default: true) |
|
|
340
|
+
| `keyBy(generator)` | `(req) => string` | Custom key generator |
|
|
341
|
+
| `keyByIp()` | - | Key by IP address (default) |
|
|
342
|
+
| `keyByApiKey(headerName?)` | `string` | Key by API key header |
|
|
343
|
+
| `skipWhen(predicate)` | `(req) => boolean` | Skip when condition is true |
|
|
344
|
+
| `build()` | - | Build rate limiter middleware |
|
|
345
|
+
|
|
346
|
+
**Available Rate Limit Tiers:**
|
|
347
|
+
|
|
348
|
+
| Tier | Window | Max Requests | Use Case |
|
|
349
|
+
|------|--------|--------------|----------|
|
|
350
|
+
| `STANDARD` | 15 min | 100 | General API endpoints |
|
|
351
|
+
| `STRICT` | 15 min | 20 | Auth endpoints (login, signup) |
|
|
352
|
+
| `DDOS` | 1 min | 60 | Global DDoS protection |
|
|
353
|
+
| `VERY_STRICT` | 1 hour | 5 | Password reset, sensitive actions |
|
|
354
|
+
| `RELAXED` | 15 min | 500 | High-traffic endpoints |
|
|
355
|
+
| `API` | 1 min | 30 | Third-party API access |
|
|
356
|
+
|
|
357
|
+
#### Response Helpers (`@exyconn/common/server/response`)
|
|
358
|
+
|
|
359
|
+
```typescript
|
|
60
360
|
import {
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
361
|
+
// Success responses
|
|
362
|
+
successResponse, // 200 OK
|
|
363
|
+
successResponseArr, // 200 OK with pagination
|
|
364
|
+
createdResponse, // 201 Created
|
|
365
|
+
noContentResponse, // 204 No Content
|
|
366
|
+
|
|
367
|
+
// Error responses
|
|
368
|
+
badRequestResponse, // 400 Bad Request
|
|
369
|
+
unauthorizedResponse, // 401 Unauthorized
|
|
370
|
+
forbiddenResponse, // 403 Forbidden
|
|
371
|
+
notFoundResponse, // 404 Not Found
|
|
372
|
+
conflictResponse, // 409 Conflict
|
|
373
|
+
serverErrorResponse, // 500 Internal Server Error
|
|
374
|
+
|
|
375
|
+
// Utilities
|
|
376
|
+
extractColumns,
|
|
377
|
+
|
|
378
|
+
// Types
|
|
379
|
+
type ApiResponse,
|
|
380
|
+
type PaginationData,
|
|
381
|
+
type ColumnMetadata,
|
|
382
|
+
} from '@exyconn/common/server/response';
|
|
383
|
+
|
|
384
|
+
// Basic success response
|
|
385
|
+
app.get('/api/user/:id', async (req, res) => {
|
|
386
|
+
const user = await User.findById(req.params.id);
|
|
387
|
+
return successResponse(res, user, 'User retrieved successfully');
|
|
388
|
+
});
|
|
389
|
+
// Response: { status: 'success', statusCode: 200, message: '...', data: {...} }
|
|
390
|
+
|
|
391
|
+
// Paginated array response (auto-extracts columns for table rendering)
|
|
392
|
+
app.get('/api/users', async (req, res) => {
|
|
393
|
+
const { page = 1, limit = 10 } = req.query;
|
|
394
|
+
const users = await User.find().skip((page - 1) * limit).limit(limit);
|
|
395
|
+
const total = await User.countDocuments();
|
|
396
|
+
|
|
397
|
+
return successResponseArr(res, users, {
|
|
398
|
+
total,
|
|
399
|
+
page,
|
|
400
|
+
limit,
|
|
401
|
+
totalPages: Math.ceil(total / limit),
|
|
402
|
+
hasNextPage: page < Math.ceil(total / limit),
|
|
403
|
+
hasPrevPage: page > 1,
|
|
404
|
+
}, 'Users retrieved');
|
|
405
|
+
});
|
|
406
|
+
|
|
407
|
+
// Error responses
|
|
408
|
+
return badRequestResponse(res, 'Invalid email format', { field: 'email' });
|
|
409
|
+
return unauthorizedResponse(res, 'Token expired');
|
|
410
|
+
return forbiddenResponse(res, 'Insufficient permissions');
|
|
411
|
+
return notFoundResponse(res, 'User not found');
|
|
412
|
+
return serverErrorResponse(res, 'Database connection failed');
|
|
413
|
+
```
|
|
414
|
+
|
|
415
|
+
**Response Functions:**
|
|
416
|
+
|
|
417
|
+
| Function | Status | Parameters | Description |
|
|
418
|
+
|----------|--------|------------|-------------|
|
|
419
|
+
| `successResponse` | 200 | `(res, data, message?)` | Standard success |
|
|
420
|
+
| `successResponseArr` | 200 | `(res, data[], pagination?, message?)` | Paginated array |
|
|
421
|
+
| `createdResponse` | 201 | `(res, data, message?)` | Resource created |
|
|
422
|
+
| `noContentResponse` | 200* | `(res, data?, message?)` | No data found |
|
|
423
|
+
| `badRequestResponse` | 400 | `(res, message?, errors?)` | Invalid request |
|
|
424
|
+
| `unauthorizedResponse` | 401 | `(res, message?)` | Authentication required |
|
|
425
|
+
| `forbiddenResponse` | 403 | `(res, message?)` | Access denied |
|
|
426
|
+
| `notFoundResponse` | 404 | `(res, message?)` | Resource not found |
|
|
427
|
+
| `conflictResponse` | 409 | `(res, message?)` | Resource conflict |
|
|
428
|
+
| `serverErrorResponse` | 500 | `(res, message?, error?)` | Server error |
|
|
429
|
+
|
|
430
|
+
#### Logger (`@exyconn/common/server/logger`)
|
|
431
|
+
|
|
432
|
+
```typescript
|
|
75
433
|
import {
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
434
|
+
createLogger,
|
|
435
|
+
logger, // Default instance
|
|
436
|
+
simpleLogger, // Console-only logger
|
|
437
|
+
createMorganStream,
|
|
438
|
+
stream, // Morgan stream for default logger
|
|
439
|
+
type LoggerConfig,
|
|
440
|
+
} from '@exyconn/common/server/logger';
|
|
441
|
+
|
|
442
|
+
// Default logger (writes to console + files)
|
|
443
|
+
logger.info('Server started', { port: 3000 });
|
|
444
|
+
logger.error('Database error', { error: err.message, stack: err.stack });
|
|
445
|
+
logger.warn('Rate limit approaching');
|
|
446
|
+
logger.debug('Request received', { method: 'GET', path: '/api/users' });
|
|
447
|
+
|
|
448
|
+
// Custom logger
|
|
449
|
+
const customLogger = createLogger({
|
|
450
|
+
level: 'debug', // 'error' | 'warn' | 'info' | 'http' | 'debug'
|
|
451
|
+
logsDir: 'logs', // Directory for log files
|
|
452
|
+
maxSize: '20m', // Max file size before rotation
|
|
453
|
+
maxFiles: '14d', // Keep logs for 14 days
|
|
454
|
+
errorMaxFiles: '30d', // Keep error logs longer
|
|
455
|
+
});
|
|
456
|
+
|
|
457
|
+
// Simple logger (no files, just console)
|
|
458
|
+
simpleLogger.info('Quick debug message');
|
|
459
|
+
|
|
460
|
+
// With Morgan HTTP logger
|
|
461
|
+
import morgan from 'morgan';
|
|
462
|
+
app.use(morgan('combined', { stream }));
|
|
82
463
|
```
|
|
83
464
|
|
|
84
|
-
|
|
465
|
+
#### Database (`@exyconn/common/server/db`)
|
|
85
466
|
|
|
86
467
|
```typescript
|
|
87
|
-
import
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
468
|
+
import {
|
|
469
|
+
connectDB,
|
|
470
|
+
disconnectDB,
|
|
471
|
+
getConnectionStatus,
|
|
472
|
+
type DbConnectionOptions,
|
|
473
|
+
} from '@exyconn/common/server/db';
|
|
474
|
+
|
|
475
|
+
// Connect with default options
|
|
476
|
+
await connectDB(process.env.MONGODB_URI!, {}, logger);
|
|
477
|
+
|
|
478
|
+
// Connect with custom options
|
|
479
|
+
await connectDB(process.env.MONGODB_URI!, {
|
|
480
|
+
maxPoolSize: 50,
|
|
481
|
+
minPoolSize: 10,
|
|
482
|
+
socketTimeoutMS: 45000,
|
|
483
|
+
retryWrites: true,
|
|
484
|
+
}, logger);
|
|
485
|
+
|
|
486
|
+
// Check status
|
|
487
|
+
const status = getConnectionStatus(); // 'connected' | 'disconnected' | ...
|
|
488
|
+
|
|
489
|
+
// Disconnect gracefully
|
|
490
|
+
await disconnectDB(logger);
|
|
94
491
|
```
|
|
95
492
|
|
|
96
|
-
|
|
493
|
+
#### Middleware (`@exyconn/common/server/middleware`)
|
|
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
|
+
|
|
541
|
+
```typescript
|
|
542
|
+
import {
|
|
543
|
+
authenticateJWT,
|
|
544
|
+
optionalAuthenticateJWT,
|
|
545
|
+
authenticateApiKey,
|
|
546
|
+
extractOrganization,
|
|
547
|
+
type AuthRequest,
|
|
548
|
+
} from '@exyconn/common/server/middleware';
|
|
549
|
+
|
|
550
|
+
// JWT Authentication (required)
|
|
551
|
+
app.use('/api/protected', authenticateJWT(process.env.JWT_SECRET!));
|
|
552
|
+
|
|
553
|
+
// JWT Authentication (optional)
|
|
554
|
+
app.use('/api/public', optionalAuthenticateJWT(process.env.JWT_SECRET!));
|
|
555
|
+
|
|
556
|
+
// API Key Authentication
|
|
557
|
+
app.use('/api/external', authenticateApiKey(async (key) => {
|
|
558
|
+
const apiKey = await ApiKey.findOne({ key, isActive: true });
|
|
559
|
+
return { valid: !!apiKey, organizationId: apiKey?.organizationId };
|
|
560
|
+
}));
|
|
561
|
+
|
|
562
|
+
// Access auth data in routes
|
|
563
|
+
app.get('/api/profile', authenticateJWT(secret), (req: AuthRequest, res) => {
|
|
564
|
+
const userId = req.userId;
|
|
565
|
+
const orgId = req.organizationId;
|
|
566
|
+
});
|
|
567
|
+
```
|
|
568
|
+
|
|
569
|
+
#### Server Utils (`@exyconn/common/server/utils`)
|
|
570
|
+
|
|
571
|
+
```typescript
|
|
572
|
+
import {
|
|
573
|
+
buildFilter,
|
|
574
|
+
buildPagination,
|
|
575
|
+
buildPaginationMeta,
|
|
576
|
+
} from '@exyconn/common/server/utils';
|
|
577
|
+
|
|
578
|
+
// Build MongoDB filter dynamically
|
|
579
|
+
const filter = buildFilter({
|
|
580
|
+
organizationId: req.organizationId,
|
|
581
|
+
search: req.query.q,
|
|
582
|
+
searchFields: ['name', 'email', 'phone'],
|
|
583
|
+
status: req.query.status,
|
|
584
|
+
startDate: req.query.from,
|
|
585
|
+
endDate: req.query.to,
|
|
586
|
+
});
|
|
587
|
+
|
|
588
|
+
// Calculate pagination
|
|
589
|
+
const pagination = buildPagination({
|
|
590
|
+
page: parseInt(req.query.page) || 1,
|
|
591
|
+
limit: parseInt(req.query.limit),
|
|
592
|
+
defaultLimit: 10,
|
|
593
|
+
maxLimit: 100,
|
|
594
|
+
});
|
|
595
|
+
|
|
596
|
+
// Build pagination meta
|
|
597
|
+
const meta = buildPaginationMeta(totalCount, pagination.page, pagination.limit);
|
|
598
|
+
```
|
|
599
|
+
|
|
600
|
+
---
|
|
601
|
+
|
|
602
|
+
### Client Module
|
|
603
|
+
|
|
604
|
+
**Import:** `@exyconn/common/client` or specific submodules
|
|
605
|
+
|
|
606
|
+
#### React Hooks (`@exyconn/common/client/hooks`)
|
|
607
|
+
|
|
608
|
+
50+ production-ready React hooks organized by category.
|
|
609
|
+
|
|
610
|
+
##### State & Logic Hooks
|
|
611
|
+
|
|
612
|
+
| Hook | Description | Example |
|
|
613
|
+
|------|-------------|---------|
|
|
614
|
+
| `useToggle` | Boolean toggle | `const [isOpen, toggle] = useToggle(false)` |
|
|
615
|
+
| `useCounter` | Numeric counter | `const { count, increment, decrement } = useCounter(0)` |
|
|
616
|
+
| `usePrevious` | Track previous value | `const prevValue = usePrevious(value)` |
|
|
617
|
+
| `useObjectState` | Partial state updates | `const [state, setState] = useObjectState({ a: 1 })` |
|
|
618
|
+
| `useHistoryState` | State with undo/redo | `const [value, setValue, { undo, redo }] = useHistoryState('')` |
|
|
619
|
+
| `useList` | Array operations | `const [list, { push, removeAt }] = useList([])` |
|
|
620
|
+
| `useMap` | Map operations | `const [map, { set, remove }] = useMap()` |
|
|
621
|
+
| `useSet` | Set operations | `const [set, { add, toggle }] = useSet()` |
|
|
622
|
+
|
|
623
|
+
##### Side Effects & Timing Hooks
|
|
624
|
+
|
|
625
|
+
| Hook | Description | Example |
|
|
626
|
+
|------|-------------|---------|
|
|
627
|
+
| `useDebounce` | Debounce value | `const debouncedSearch = useDebounce(search, 500)` |
|
|
628
|
+
| `useThrottle` | Throttle value | `const throttled = useThrottle(value, 100)` |
|
|
629
|
+
| `useTimeout` | Execute after delay | `const { reset, clear } = useTimeout(fn, 3000)` |
|
|
630
|
+
| `useInterval` | Repeated execution | `const { start, stop } = useInterval(fn, 1000)` |
|
|
631
|
+
| `useCountdown` | Countdown timer | `const { count, start, stop } = useCountdown({ seconds: 60 })` |
|
|
632
|
+
|
|
633
|
+
##### Browser & Document Hooks
|
|
634
|
+
|
|
635
|
+
| Hook | Description | Example |
|
|
636
|
+
|------|-------------|---------|
|
|
637
|
+
| `useWindowSize` | Track dimensions | `const { width, height } = useWindowSize()` |
|
|
638
|
+
| `useWindowScroll` | Track scroll | `const { y, scrollToTop } = useWindowScroll()` |
|
|
639
|
+
| `useDocumentTitle` | Set title | `useDocumentTitle('My Page')` |
|
|
640
|
+
| `useVisibilityChange` | Tab visibility | `const isVisible = useVisibilityChange()` |
|
|
641
|
+
| `useLockBodyScroll` | Prevent scroll | `useLockBodyScroll(isModalOpen)` |
|
|
642
|
+
| `useIsClient` | Client check | `const isClient = useIsClient()` |
|
|
643
|
+
|
|
644
|
+
##### Events & Interaction Hooks
|
|
645
|
+
|
|
646
|
+
| Hook | Description | Example |
|
|
647
|
+
|------|-------------|---------|
|
|
648
|
+
| `useEventListener` | Attach events | `useEventListener('keydown', handler)` |
|
|
649
|
+
| `useKeyPress` | Detect key | `const isEnter = useKeyPress('Enter')` |
|
|
650
|
+
| `useHover` | Track hover | `const [ref, isHovered] = useHover()` |
|
|
651
|
+
| `useClickAway` | Click outside | `useClickAway(ref, closeHandler)` |
|
|
652
|
+
| `useLongPress` | Long press | `const props = useLongPress(handler, { delay: 500 })` |
|
|
653
|
+
| `useCopyToClipboard` | Copy text | `const [copied, copy] = useCopyToClipboard()` |
|
|
654
|
+
|
|
655
|
+
##### Media & Device Hooks
|
|
656
|
+
|
|
657
|
+
| Hook | Description | Example |
|
|
658
|
+
|------|-------------|---------|
|
|
659
|
+
| `useMediaQuery` | Responsive | `const isMobile = useMediaQuery('(max-width: 768px)')` |
|
|
660
|
+
| `useBattery` | Battery status | `const { level, charging } = useBattery()` |
|
|
661
|
+
| `useNetworkState` | Network info | `const { online, effectiveType } = useNetworkState()` |
|
|
662
|
+
| `useIdle` | User inactivity | `const isIdle = useIdle(30000)` |
|
|
663
|
+
| `useGeolocation` | User location | `const { latitude, longitude } = useGeolocation()` |
|
|
664
|
+
| `useThemeDetector` | System theme | `const isDark = useThemeDetector()` |
|
|
665
|
+
|
|
666
|
+
##### Storage Hooks
|
|
667
|
+
|
|
668
|
+
| Hook | Description | Example |
|
|
669
|
+
|------|-------------|---------|
|
|
670
|
+
| `useLocalStorage` | Persist in localStorage | `const [value, setValue] = useLocalStorage('key', initial)` |
|
|
671
|
+
| `useSessionStorage` | Session storage | `const [value, setValue] = useSessionStorage('key', initial)` |
|
|
672
|
+
|
|
673
|
+
##### Data Fetching Hooks
|
|
674
|
+
|
|
675
|
+
| Hook | Description | Example |
|
|
676
|
+
|------|-------------|---------|
|
|
677
|
+
| `useFetch` | Data fetching | `const { data, loading, error } = useFetch('/api/data')` |
|
|
678
|
+
| `useScript` | Load scripts | `const { loaded, error } = useScript('https://...')` |
|
|
679
|
+
|
|
680
|
+
##### Performance Hooks
|
|
681
|
+
|
|
682
|
+
| Hook | Description | Example |
|
|
683
|
+
|------|-------------|---------|
|
|
684
|
+
| `useRenderCount` | Count renders | `const count = useRenderCount()` |
|
|
685
|
+
| `useMeasure` | Element dimensions | `const [ref, { width, height }] = useMeasure()` |
|
|
686
|
+
| `useIntersectionObserver` | Visibility detection | `const [ref, entry] = useIntersectionObserver()` |
|
|
687
|
+
|
|
688
|
+
#### HTTP Client (`@exyconn/common/client/http`)
|
|
689
|
+
|
|
690
|
+
```typescript
|
|
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
|
+
}
|
|
713
|
+
|
|
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';
|
|
758
|
+
|
|
759
|
+
generateSlug('Hello World'); // 'helloWorld'
|
|
760
|
+
generateUrlSlug('Hello World'); // 'hello-world'
|
|
761
|
+
generateSnakeSlug('Hello World'); // 'hello_world'
|
|
762
|
+
```
|
|
763
|
+
|
|
764
|
+
---
|
|
765
|
+
|
|
766
|
+
### Shared Module
|
|
767
|
+
|
|
768
|
+
**Import:** `@exyconn/common/shared` or specific submodules
|
|
769
|
+
|
|
770
|
+
#### Validation (`@exyconn/common/shared/validation`)
|
|
97
771
|
|
|
98
772
|
```typescript
|
|
99
773
|
import {
|
|
100
774
|
VALIDATION_PATTERNS,
|
|
101
|
-
VALIDATION_MESSAGES,
|
|
102
775
|
isValidEmail,
|
|
103
776
|
isValidPassword,
|
|
104
777
|
isValidPhone,
|
|
105
|
-
isValidUrl
|
|
778
|
+
isValidUrl,
|
|
106
779
|
} from '@exyconn/common/shared/validation';
|
|
780
|
+
|
|
781
|
+
isValidEmail('test@example.com'); // true
|
|
782
|
+
isValidPassword('Password1!', 'strong'); // true
|
|
783
|
+
isValidPhone('+1 234 567 8901'); // true
|
|
784
|
+
|
|
785
|
+
// Use patterns directly
|
|
786
|
+
VALIDATION_PATTERNS.EMAIL.test('test@example.com');
|
|
787
|
+
VALIDATION_PATTERNS.SLUG.test('my-blog-post');
|
|
107
788
|
```
|
|
108
789
|
|
|
109
|
-
|
|
790
|
+
**Available Patterns:**
|
|
791
|
+
|
|
792
|
+
| Pattern | Description |
|
|
793
|
+
|---------|-------------|
|
|
794
|
+
| `EMAIL` | Email validation |
|
|
795
|
+
| `PASSWORD_STRONG` | Strong password (8+ chars, upper, lower, number, special) |
|
|
796
|
+
| `PASSWORD_MEDIUM` | Medium password (8+ chars, upper, lower, number) |
|
|
797
|
+
| `PHONE_INTERNATIONAL` | International phone |
|
|
798
|
+
| `URL` / `URL_STRICT` | URL validation |
|
|
799
|
+
| `SLUG` | Kebab-case slug |
|
|
800
|
+
| `USERNAME` | Username (3-30 chars, alphanumeric, _, -) |
|
|
801
|
+
| `IPV4` | IPv4 address |
|
|
802
|
+
| `HEX_COLOR` | Hex color code |
|
|
803
|
+
| `DATE_ISO` | ISO date format |
|
|
804
|
+
|
|
805
|
+
#### Environment Config (`@exyconn/common/shared/config`)
|
|
110
806
|
|
|
111
807
|
```typescript
|
|
112
|
-
import {
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
subtractTime,
|
|
123
|
-
getDayBoundaries,
|
|
124
|
-
getAge,
|
|
125
|
-
isWeekend,
|
|
126
|
-
DATE_FORMATS
|
|
127
|
-
} from '@exyconn/common/shared';
|
|
808
|
+
import { getEnv, getEnvOptional, isProd, isDev, validateEnv } from '@exyconn/common/shared/config';
|
|
809
|
+
|
|
810
|
+
const port = getEnv('PORT', 3000); // Returns number
|
|
811
|
+
const secret = getEnv('JWT_SECRET'); // Required, throws if missing
|
|
812
|
+
const optional = getEnvOptional('OPTIONAL'); // Returns undefined if missing
|
|
813
|
+
|
|
814
|
+
if (isProd()) { /* Production only */ }
|
|
815
|
+
if (isDev()) { /* Development only */ }
|
|
816
|
+
|
|
817
|
+
validateEnv(['DATABASE_URL', 'JWT_SECRET']); // Throws with missing vars
|
|
128
818
|
```
|
|
129
819
|
|
|
130
|
-
|
|
820
|
+
---
|
|
821
|
+
|
|
822
|
+
### Data Module
|
|
823
|
+
|
|
824
|
+
**Import:** `@exyconn/common/data` or specific files
|
|
825
|
+
|
|
826
|
+
#### Countries (`@exyconn/common/data/countries`)
|
|
131
827
|
|
|
132
828
|
```typescript
|
|
133
|
-
// Countries with nested states/cities + flags
|
|
134
829
|
import {
|
|
135
|
-
countries,
|
|
136
|
-
getCountryByCode,
|
|
137
|
-
getStatesByCountry,
|
|
138
|
-
getCitiesByState,
|
|
830
|
+
default as countries,
|
|
831
|
+
getCountryByCode,
|
|
832
|
+
getStatesByCountry,
|
|
139
833
|
searchCountries,
|
|
140
834
|
getFlag,
|
|
141
|
-
getAllCountriesWithFlags,
|
|
142
|
-
codeToFlag
|
|
143
835
|
} from '@exyconn/common/data/countries';
|
|
144
836
|
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
formatCurrencyNative
|
|
151
|
-
} from '@exyconn/common/data/currencies';
|
|
837
|
+
const usa = getCountryByCode('US');
|
|
838
|
+
const states = getStatesByCountry('US');
|
|
839
|
+
const matches = searchCountries('united');
|
|
840
|
+
const flag = getFlag('US'); // '🇺🇸'
|
|
841
|
+
```
|
|
152
842
|
|
|
153
|
-
|
|
154
|
-
import { phoneCodes, getPhoneCodeByCountry } from '@exyconn/common/data/phone-codes';
|
|
843
|
+
#### Currencies (`@exyconn/common/data/currencies`)
|
|
155
844
|
|
|
156
|
-
|
|
157
|
-
import {
|
|
158
|
-
timezones,
|
|
159
|
-
getTimezoneByCode,
|
|
160
|
-
searchTimezones,
|
|
161
|
-
getCommonTimezones
|
|
162
|
-
} from '@exyconn/common/data/timezones';
|
|
845
|
+
```typescript
|
|
846
|
+
import { getCurrencyByCode, formatCurrency } from '@exyconn/common/data/currencies';
|
|
163
847
|
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
REGEX,
|
|
167
|
-
EMAIL,
|
|
168
|
-
PASSWORD_STRONG,
|
|
169
|
-
PHONE_INTERNATIONAL,
|
|
170
|
-
UUID,
|
|
171
|
-
URL_STRICT
|
|
172
|
-
} from '@exyconn/common/data/regex';
|
|
173
|
-
|
|
174
|
-
// Brand Identity
|
|
175
|
-
import {
|
|
176
|
-
BRANDS,
|
|
177
|
-
APPS,
|
|
178
|
-
getBrandById,
|
|
179
|
-
getBrandByDomain,
|
|
180
|
-
getThemedLogo,
|
|
181
|
-
createAppConfig
|
|
182
|
-
} from '@exyconn/common/data/brand-identity';
|
|
848
|
+
const usd = getCurrencyByCode('USD');
|
|
849
|
+
formatCurrency(1234.56, 'USD'); // '$1,234.56'
|
|
183
850
|
```
|
|
184
851
|
|
|
185
|
-
|
|
852
|
+
#### Phone Codes (`@exyconn/common/data/phone-codes`)
|
|
186
853
|
|
|
187
854
|
```typescript
|
|
188
|
-
import {
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
ORDER_STATUS,
|
|
193
|
-
PAYMENT_STATUS,
|
|
194
|
-
TASK_STATUS,
|
|
195
|
-
|
|
196
|
-
// Sort options
|
|
197
|
-
SORT,
|
|
198
|
-
SORT_DIRECTION,
|
|
199
|
-
|
|
200
|
-
// Roles & permissions
|
|
201
|
-
ROLE,
|
|
202
|
-
PERMISSION_LEVEL,
|
|
203
|
-
|
|
204
|
-
// UI constants
|
|
205
|
-
BREAKPOINTS,
|
|
206
|
-
MEDIA_QUERIES,
|
|
207
|
-
THEME,
|
|
208
|
-
|
|
209
|
-
// Other
|
|
210
|
-
PRIORITY,
|
|
211
|
-
VISIBILITY,
|
|
212
|
-
NOTIFICATION_TYPE
|
|
213
|
-
} from '@exyconn/common/shared/constants/enums';
|
|
855
|
+
import { getPhoneCodeByCountry } from '@exyconn/common/data/phone-codes';
|
|
856
|
+
|
|
857
|
+
const usCode = getPhoneCodeByCountry('US');
|
|
858
|
+
// { country: 'United States', dial_code: '+1', flag: '🇺🇸' }
|
|
214
859
|
```
|
|
215
860
|
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
### Countries
|
|
219
|
-
- 50+ countries with full details
|
|
220
|
-
- Nested states/cities for major countries
|
|
221
|
-
- Includes phone codes, currencies, timezones
|
|
222
|
-
- **NEW:** Emoji flags with `getFlag()` and `codeToFlag()` helpers
|
|
223
|
-
|
|
224
|
-
### Currencies
|
|
225
|
-
- 100+ world currencies
|
|
226
|
-
- Symbol, name, and native formatting
|
|
227
|
-
- formatCurrency(amount, code) helper
|
|
228
|
-
|
|
229
|
-
### Phone Codes
|
|
230
|
-
- 190+ international phone codes
|
|
231
|
-
- Country flags (emoji)
|
|
232
|
-
|
|
233
|
-
### Timezones
|
|
234
|
-
- 65+ timezones with UTC offsets
|
|
235
|
-
- Common timezone presets
|
|
236
|
-
- Search and filter functions
|
|
237
|
-
|
|
238
|
-
### Regex Patterns
|
|
239
|
-
100+ common regex patterns organized by category:
|
|
240
|
-
- **Email** - Basic, RFC5322, common domains
|
|
241
|
-
- **Password** - Weak to very strong
|
|
242
|
-
- **Phone** - International, US, India, UK
|
|
243
|
-
- **Postal** - ZIP, PIN, UK, Canada, Germany
|
|
244
|
-
- **IDs** - UUID, MongoDB ObjectId, Aadhaar, PAN, SSN
|
|
245
|
-
- **URL** - Basic, strict, domain, localhost
|
|
246
|
-
- **Credit Card** - Visa, Mastercard, Amex, etc.
|
|
247
|
-
- **Date/Time** - ISO, various formats
|
|
248
|
-
- **Colors** - Hex, RGB, RGBA, HSL
|
|
249
|
-
- **And more!**
|
|
250
|
-
|
|
251
|
-
### Brand Identity
|
|
252
|
-
Complete brand configuration for all 5 Exyconn projects:
|
|
253
|
-
- **Logo variants:** light, dark, logoOnly, darkLogoOnly
|
|
254
|
-
- **Colors:** primary, secondary, accent
|
|
255
|
-
- **Contact:** support email, sales email
|
|
256
|
-
- **Social links:** Twitter, LinkedIn, GitHub, etc.
|
|
257
|
-
- **SEO config:** title, description, keywords
|
|
258
|
-
|
|
259
|
-
Projects:
|
|
260
|
-
- **botify.life** - AI/Bot platform
|
|
261
|
-
- **exyconn.com** - Main Exyconn brand
|
|
262
|
-
- **partywings.fun** - Event platform
|
|
263
|
-
- **sibera.work** - Work management
|
|
264
|
-
- **spentiva.com** - Finance platform
|
|
265
|
-
|
|
266
|
-
### Enums & Breakpoints
|
|
267
|
-
Comprehensive enums for consistent data handling:
|
|
268
|
-
- **STATUS** - active, pending, approved, archived...
|
|
269
|
-
- **USER_STATUS** - active, suspended, banned...
|
|
270
|
-
- **ORDER_STATUS** - pending, confirmed, shipped...
|
|
271
|
-
- **PAYMENT_STATUS** - pending, completed, refunded...
|
|
272
|
-
- **TASK_STATUS** - todo, in_progress, done...
|
|
273
|
-
- **SORT** - newest, oldest, alphabetical, popular...
|
|
274
|
-
- **ROLE** - super_admin, admin, user, viewer...
|
|
275
|
-
- **PRIORITY** - low, medium, high, critical
|
|
276
|
-
- **BREAKPOINTS** - xs, sm, md, lg, xl, 2xl (Tailwind-compatible)
|
|
277
|
-
|
|
278
|
-
## React Hooks
|
|
279
|
-
|
|
280
|
-
| Hook | Description |
|
|
281
|
-
|------|-------------|
|
|
282
|
-
| useLocalStorage | Persist state in localStorage with cross-tab sync |
|
|
283
|
-
| useDebounce | Debounce a value with configurable delay |
|
|
284
|
-
| useCopyToClipboard | Copy text to clipboard with status |
|
|
285
|
-
| usePageTitle | Set document title with suffix |
|
|
286
|
-
| useSnackbar | Toast/snackbar notification state |
|
|
287
|
-
| useMediaQuery | Track media query matches |
|
|
288
|
-
| useIsMobile / useIsDesktop | Responsive breakpoint hooks |
|
|
289
|
-
| useThemeDetector | Detect system light/dark mode |
|
|
290
|
-
| useInterval | Safely run intervals with cleanup |
|
|
291
|
-
| useOnClickOutside | Detect clicks outside element |
|
|
292
|
-
| useWindowSize | Track window dimensions |
|
|
861
|
+
#### Timezones (`@exyconn/common/data/timezones`)
|
|
293
862
|
|
|
294
|
-
|
|
863
|
+
```typescript
|
|
864
|
+
import { getTimezoneByCode, searchTimezones } from '@exyconn/common/data/timezones';
|
|
295
865
|
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
```
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
}
|
|
866
|
+
const pst = getTimezoneByCode('America/Los_Angeles');
|
|
867
|
+
const matches = searchTimezones('india');
|
|
868
|
+
```
|
|
869
|
+
|
|
870
|
+
#### Regex Patterns (`@exyconn/common/data/regex`)
|
|
871
|
+
|
|
872
|
+
```typescript
|
|
873
|
+
import REGEX from '@exyconn/common/data/regex';
|
|
874
|
+
|
|
875
|
+
REGEX.EMAIL.BASIC.test('test@example.com');
|
|
876
|
+
REGEX.PASSWORD.STRONG.test('Test@123');
|
|
877
|
+
REGEX.CREDIT_CARD.VISA.test('4111111111111111');
|
|
309
878
|
```
|
|
310
879
|
|
|
311
|
-
|
|
880
|
+
#### Brand Identity (`@exyconn/common/data/brand-identity`)
|
|
881
|
+
|
|
882
|
+
```typescript
|
|
883
|
+
import { BRANDS, getBrandById, getBrandByDomain, getThemedLogo } from '@exyconn/common/data/brand-identity';
|
|
884
|
+
|
|
885
|
+
const exyconn = getBrandById('exyconn');
|
|
886
|
+
const brand = getBrandByDomain('botify.life');
|
|
887
|
+
const logo = getThemedLogo('exyconn', 'dark');
|
|
888
|
+
```
|
|
889
|
+
|
|
890
|
+
---
|
|
891
|
+
|
|
892
|
+
## Package Exports Map
|
|
312
893
|
|
|
313
894
|
```
|
|
314
895
|
@exyconn/common
|
|
315
|
-
├── /server
|
|
316
|
-
│ ├── /response
|
|
317
|
-
│ ├── /enums
|
|
318
|
-
│ ├── /logger
|
|
319
|
-
│ ├── /db
|
|
320
|
-
│ ├── /middleware
|
|
321
|
-
│
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
│ ├── /
|
|
325
|
-
│ ├── /
|
|
326
|
-
│
|
|
327
|
-
├── /
|
|
328
|
-
│
|
|
329
|
-
|
|
330
|
-
│ ├── /
|
|
331
|
-
│
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
├── /
|
|
337
|
-
├── /
|
|
338
|
-
├── /
|
|
339
|
-
|
|
896
|
+
├── /server # Server utilities
|
|
897
|
+
│ ├── /response # Response helpers
|
|
898
|
+
│ ├── /enums # Status codes/messages
|
|
899
|
+
│ ├── /logger # Winston logger
|
|
900
|
+
│ ├── /db # Database connections
|
|
901
|
+
│ ├── /middleware # Auth middleware
|
|
902
|
+
│ ├── /utils # Server utilities
|
|
903
|
+
│ └── /configs # Dynamic configurations
|
|
904
|
+
├── /client # Client utilities
|
|
905
|
+
│ ├── /http # Axios HTTP client
|
|
906
|
+
│ ├── /hooks # React hooks (50+)
|
|
907
|
+
│ ├── /logger # Browser logger
|
|
908
|
+
│ ├── /utils # Client utilities
|
|
909
|
+
│ └── /web # Web utilities
|
|
910
|
+
├── /shared # Shared between server/client
|
|
911
|
+
│ ├── /types # TypeScript types
|
|
912
|
+
│ ├── /validation # Validation patterns
|
|
913
|
+
│ ├── /constants # Enums, breakpoints
|
|
914
|
+
│ ├── /config # Environment config
|
|
915
|
+
│ └── /utils # Date-time utilities
|
|
916
|
+
└── /data # Static data modules
|
|
917
|
+
├── /countries # Countries with states/cities
|
|
918
|
+
├── /currencies # World currencies
|
|
919
|
+
├── /phone-codes # Phone codes
|
|
920
|
+
├── /timezones # Timezones
|
|
921
|
+
├── /regex # Regex patterns
|
|
922
|
+
└── /brand-identity # Brand configs
|
|
923
|
+
```
|
|
924
|
+
|
|
925
|
+
---
|
|
926
|
+
|
|
927
|
+
## Peer Dependencies
|
|
928
|
+
|
|
929
|
+
All peer dependencies are **optional**. Install only what you need:
|
|
930
|
+
|
|
931
|
+
| Package | Version | Required For |
|
|
932
|
+
|---------|---------|--------------|
|
|
933
|
+
| `react` | ^17.0.0 \|\| ^18.0.0 \|\| ^19.0.0 | Client hooks |
|
|
934
|
+
| `express` | ^4.18.0 \|\| ^5.0.0 | Server utilities |
|
|
935
|
+
| `express-rate-limit` | ^7.0.0 | Rate limiter configs |
|
|
936
|
+
| `cors` | ^2.8.5 | CORS configs |
|
|
937
|
+
| `mongoose` | ^8.0.0 | Database utilities |
|
|
938
|
+
| `winston` | ^3.11.0 | Server logger |
|
|
939
|
+
| `jsonwebtoken` | ^9.0.0 | Auth middleware |
|
|
940
|
+
| `axios` | ^1.6.0 | HTTP client |
|
|
941
|
+
| `date-fns` | ^3.0.0 | Date utilities |
|
|
942
|
+
|
|
943
|
+
---
|
|
944
|
+
|
|
945
|
+
## TypeScript Support
|
|
946
|
+
|
|
947
|
+
Full TypeScript support with exported types:
|
|
948
|
+
|
|
949
|
+
```typescript
|
|
950
|
+
import type {
|
|
951
|
+
ApiResponse,
|
|
952
|
+
CorsConfig,
|
|
953
|
+
RateLimitTierConfig,
|
|
954
|
+
AuthRequest,
|
|
955
|
+
} from '@exyconn/common/server';
|
|
340
956
|
```
|
|
341
957
|
|
|
958
|
+
---
|
|
959
|
+
|
|
960
|
+
## Node.js Version
|
|
961
|
+
|
|
962
|
+
Requires Node.js >= 18.0.0
|
|
963
|
+
|
|
964
|
+
---
|
|
965
|
+
|
|
966
|
+
## Development
|
|
967
|
+
|
|
968
|
+
### Setup
|
|
969
|
+
|
|
970
|
+
```bash
|
|
971
|
+
# Clone the repository
|
|
972
|
+
git clone https://github.com/exyconn/common.git
|
|
973
|
+
cd common
|
|
974
|
+
|
|
975
|
+
# Install dependencies
|
|
976
|
+
npm install
|
|
977
|
+
|
|
978
|
+
# Build the package
|
|
979
|
+
npm run build
|
|
980
|
+
|
|
981
|
+
# Run tests
|
|
982
|
+
npm run test
|
|
983
|
+
|
|
984
|
+
# Run tests with coverage
|
|
985
|
+
npm run test:coverage
|
|
986
|
+
```
|
|
987
|
+
|
|
988
|
+
### Available Scripts
|
|
989
|
+
|
|
990
|
+
| Command | Description |
|
|
991
|
+
|---------|-------------|
|
|
992
|
+
| `npm run build` | Build the package with tsup |
|
|
993
|
+
| `npm run dev` | Watch mode for development |
|
|
994
|
+
| `npm run test` | Run tests with Vitest |
|
|
995
|
+
| `npm run test:coverage` | Run tests with coverage report |
|
|
996
|
+
| `npm run lint` | Lint the codebase |
|
|
997
|
+
| `npm run type-check` | TypeScript type checking |
|
|
998
|
+
| `npm run docs:dev` | Start docs dev server (port 4006) |
|
|
999
|
+
| `npm run docs:build` | Build documentation |
|
|
1000
|
+
| `npm run docs:start` | Start production docs server |
|
|
1001
|
+
|
|
1002
|
+
### Documentation Development
|
|
1003
|
+
|
|
1004
|
+
```bash
|
|
1005
|
+
# Install docs dependencies
|
|
1006
|
+
npm run docs:install
|
|
1007
|
+
|
|
1008
|
+
# Start documentation site locally
|
|
1009
|
+
npm run docs:dev
|
|
1010
|
+
|
|
1011
|
+
# Open http://localhost:4006
|
|
1012
|
+
```
|
|
1013
|
+
|
|
1014
|
+
### Running with Docker
|
|
1015
|
+
|
|
1016
|
+
```bash
|
|
1017
|
+
# Build the docs Docker image
|
|
1018
|
+
cd docs
|
|
1019
|
+
docker build -t common-docs .
|
|
1020
|
+
|
|
1021
|
+
# Run the container
|
|
1022
|
+
docker run -p 4006:4006 common-docs
|
|
1023
|
+
```
|
|
1024
|
+
|
|
1025
|
+
---
|
|
1026
|
+
|
|
1027
|
+
## Contributing
|
|
1028
|
+
|
|
1029
|
+
Contributions are welcome! Please read our [Contributing Guide](CONTRIBUTING.md) for details.
|
|
1030
|
+
|
|
1031
|
+
1. Fork the repository
|
|
1032
|
+
2. Create your feature branch (`git checkout -b feature/amazing-feature`)
|
|
1033
|
+
3. Commit your changes (`git commit -m 'feat: add amazing feature'`)
|
|
1034
|
+
4. Push to the branch (`git push origin feature/amazing-feature`)
|
|
1035
|
+
5. Open a Pull Request
|
|
1036
|
+
|
|
1037
|
+
### Commit Convention
|
|
1038
|
+
|
|
1039
|
+
We use [Conventional Commits](https://www.conventionalcommits.org/):
|
|
1040
|
+
|
|
1041
|
+
- `feat:` - New features
|
|
1042
|
+
- `fix:` - Bug fixes
|
|
1043
|
+
- `docs:` - Documentation changes
|
|
1044
|
+
- `test:` - Adding or updating tests
|
|
1045
|
+
- `refactor:` - Code refactoring
|
|
1046
|
+
- `chore:` - Maintenance tasks
|
|
1047
|
+
|
|
1048
|
+
---
|
|
1049
|
+
|
|
342
1050
|
## License
|
|
343
1051
|
|
|
344
1052
|
MIT © Exyconn
|