npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@exyconn/common

v2.3.6

Published

Production-ready common utilities shared across all Exyconn projects - includes server utilities, client hooks, shared types, data modules, validation helpers, and dynamic configurations

Readme

@exyconn/common

npm version CI Coverage License: MIT TypeScript Bundle Size Documentation

Production-ready common utilities, hooks, types, and data shared across all Exyconn projects (botify.life, exyconn.com, partywings.fun, sibera.work, spentiva.com).

📚 Documentation

Full documentation available at: common-docs.exyconn.com


Table of Contents


Installation

npm install @exyconn/common
# or
yarn add @exyconn/common
# or
pnpm add @exyconn/common

Requirements

  • Node.js >= 18.0.0
  • React >= 18.0.0 (for hooks)
  • TypeScript >= 5.0.0 (recommended)

Features

| Category | Description | |----------|-------------| | 🖥️ Server utilities | Response helpers, middleware, logging, database connections, dynamic configs | | 🌐 Client utilities | HTTP clients, React hooks (54+), response parsing | | 📝 Shared types | TypeScript interfaces for API responses, users, etc. | | 🗃️ Data modules | Countries, currencies, phone codes, timezones, brand logos | | ✅ Validation | Common patterns, regex, and validation helpers (Zod integration) | | 📅 Date utilities | Enhanced date-fns with timezone support | | 🎨 Brand Identity | Complete brand configs with logos, colors, and SEO | | 🔢 Enums & Constants | STATUS, SORT, ROLE, BREAKPOINTS, and more | | ⚙️ Dynamic Configs | Production-ready CORS, Rate Limiting, Server configs | | 🧪 Fully Tested | Comprehensive test coverage with Vitest |


Quick Start

Import by Module (Recommended)

// Server utilities
import { successResponse, errorResponse } from '@exyconn/common/server/response';
import { createCorsOptions, createRateLimiter } from '@exyconn/common/server/configs';
import { createLogger } from '@exyconn/common/server/logger';
import { connectDB } from '@exyconn/common/server/db';
import { createCrudControllers, queryParser, queryPagination } from '@exyconn/common/server/middleware';

// Client utilities
import { useLocalStorage, useDebounce } from '@exyconn/common/client/hooks';
import { getRequest, postRequest, extractData, isSuccess } from '@exyconn/common/client/http';

// Shared utilities
import { isValidEmail, VALIDATION_PATTERNS } from '@exyconn/common/shared/validation';
import { getEnv, isProd } from '@exyconn/common/shared/config';

// Data modules
import { countries, getCountryByCode } from '@exyconn/common/data/countries';
import { BRANDS, getBrandById } from '@exyconn/common/data/brand-identity';

Import Everything (Not Recommended for Bundle Size)

import { server, client, shared, data } from '@exyconn/common';

Module Reference

Server Module

Import: @exyconn/common/server or specific submodules

Server Configs (@exyconn/common/server/configs)

Dynamic configuration system for production applications.

ConfigBuilder
import { 
  createConfig, 
  buildConfig, 
  ConfigBuilder,
  // Types
  type AppConfig,
  type ServerConfig,
  type DatabaseConfig,
  type AuthConfig,
  type LoggingConfig,
} from '@exyconn/common/server/configs';

// Method 1: Using ConfigBuilder (fluent API)
const config = createConfig()
  .setServer({
    name: 'my-api-server',
    port: 4000,
    environment: 'production',
  })
  .setDatabase({
    uri: process.env.MONGODB_URI!,
    maxPoolSize: 50,
  })
  .setAuth({
    jwtSecret: process.env.JWT_SECRET!,
    jwtExpiresIn: '7d',
  })
  .setLogging({
    level: 'info',
    file: true,
  })
  .addProductionOrigin('https://myapp.com')
  .addProductionOrigin('https://api.myapp.com')
  .addCorsPattern('.myapp.com')
  .addRateLimitTier('upload', {
    windowMs: 60000,
    maxRequests: 5,
    message: 'Upload limit exceeded',
  })
  .setCustom('featureFlags', { newFeature: true })
  .loadFromEnv()
  .build();

// Method 2: Quick build from partial config
const quickConfig = buildConfig({
  server: { name: 'quick-server', port: 3000 },
  database: { uri: 'mongodb://localhost/mydb' },
});

// Validate configuration
const validation = createConfig().setServer({ name: '' }).validate();
if (!validation.valid) {
  console.error('Config errors:', validation.errors);
}

ConfigBuilder Methods:

| Method | Parameters | Description | |--------|------------|-------------| | setServer(config) | Partial<ServerConfig> | Set server configuration | | setDatabase(config) | Partial<DatabaseConfig> | Set database configuration | | setAuth(config) | Partial<AuthConfig> | Set auth configuration | | setLogging(config) | Partial<LoggingConfig> | Set logging configuration | | setCorsOrigins(config) | Partial<CorsOriginsConfig> | Set CORS origins | | addProductionOrigin(origin) | string | Add a production CORS origin | | addDevelopmentOrigin(origin) | string | Add a development CORS origin | | addCorsPattern(pattern) | string | Add subdomain pattern | | setRateLimit(config) | Partial<RateLimitConfig> | Set rate limit config | | addRateLimitTier(name, tier) | string, RateLimitTier | Add custom rate limit tier | | setCustom(key, value) | string, unknown | Set custom config value | | loadFromEnv() | - | Load config from environment variables | | validate() | - | Validate configuration, returns { valid, errors } | | build() | - | Build final configuration |

CORS Configuration
import { 
  createCorsOptions,
  createBrandCorsOptions,
  createMultiBrandCorsOptions,
  // Presets
  DEFAULT_CORS_CONFIG,
  EXYCONN_CORS_CONFIG,
  STRICT_CORS_CONFIG,
  PERMISSIVE_CORS_CONFIG,
  // Types
  type CorsConfig,
} from '@exyconn/common/server/configs';

// Custom CORS configuration
const corsOptions = createCorsOptions({
  productionOrigins: ['https://myapp.com', 'https://api.myapp.com'],
  developmentOrigins: ['http://localhost:3000', 'http://localhost:5173'],
  allowedSubdomains: ['.myapp.com'],
  originPatterns: [/\.myapp\.(com|io)$/],
  allowNoOrigin: true,       // Allow mobile apps, curl
  allowAllInDev: true,       // Allow all in development
  credentials: true,
  methods: ['GET', 'POST', 'PUT', 'DELETE', 'PATCH'],
  allowedHeaders: ['Content-Type', 'Authorization', 'X-API-Key'],
  exposedHeaders: ['X-Total-Count'],
  maxAge: 86400,             // 24 hours preflight cache
  customValidator: (origin) => origin.includes('trusted'), // Custom validation
});

// Single brand CORS (auto-includes www and subdomains)
const brandCors = createBrandCorsOptions('myapp.com', {
  credentials: true,
});

// Multi-brand CORS
const multiBrandCors = createMultiBrandCorsOptions(
  ['myapp.com', 'myapp.io', 'api.myapp.com'],
  { allowNoOrigin: false }
);

// Usage with Express
import cors from 'cors';
app.use(cors(corsOptions));

CorsConfig Options:

| Option | Type | Default | Required | Description | |--------|------|---------|----------|-------------| | productionOrigins | string[] | [] | No | Allowed production origins | | developmentOrigins | string[] | Common localhost ports | No | Allowed development origins | | allowedSubdomains | string[] | [] | No | Subdomain patterns (e.g., .myapp.com) | | originPatterns | RegExp[] | [] | No | Regex patterns for origin matching | | allowNoOrigin | boolean | true | No | Allow requests with no origin | | allowAllInDev | boolean | true | No | Allow all origins in development | | customValidator | (origin: string) => boolean | - | No | Custom origin validator | | credentials | boolean | true | No | Include credentials | | methods | string[] | All standard methods | No | Allowed HTTP methods | | allowedHeaders | string[] | Common headers | No | Allowed request headers | | exposedHeaders | string[] | Common response headers | No | Exposed response headers | | maxAge | number | 86400 | No | Preflight cache duration (seconds) |

Rate Limiter Configuration
import { 
  // Factory functions
  createRateLimiter,
  createStandardRateLimiter,
  createStrictRateLimiter,
  createDdosRateLimiter,
  createApiRateLimiter,
  // Builder
  rateLimiter,
  RateLimiterBuilder,
  // Key generators
  defaultKeyGenerator,
  createPrefixedKeyGenerator,
  createUserKeyGenerator,
  createApiKeyGenerator,
  // Constants
  DEFAULT_RATE_LIMIT_TIERS,
  // Types
  type RateLimitTierConfig,
} from '@exyconn/common/server/configs';

// Method 1: Factory functions with defaults
const standardLimiter = createStandardRateLimiter(); // 100 req/15min
const strictLimiter = createStrictRateLimiter();     // 20 req/15min
const ddosLimiter = createDdosRateLimiter();         // 60 req/1min
const apiLimiter = createApiRateLimiter();           // 30 req/1min (by API key)

// Method 2: Custom configuration
const customLimiter = createRateLimiter({
  windowMs: 5 * 60 * 1000,    // 5 minutes
  maxRequests: 50,
  message: 'Too many requests, please slow down.',
  skipSuccessfulRequests: false,
  skipFailedRequests: false,
}, {
  standardHeaders: true,
  legacyHeaders: false,
  keyGenerator: defaultKeyGenerator,
  skip: (req) => req.headers['x-skip-rate-limit'] === 'true',
});

// Method 3: Builder pattern (most flexible)
const uploadLimiter = rateLimiter('STRICT')
  .windowMinutes(5)
  .max(10)
  .message('Upload limit exceeded. Please wait.')
  .keyByIp()
  .skipWhen((req) => req.user?.isPremium)
  .build();

// User-based rate limiting
const userLimiter = rateLimiter()
  .windowHours(1)
  .max(1000)
  .keyBy((req) => req.userId || defaultKeyGenerator(req))
  .build();

// API key based rate limiting
const apiKeyLimiter = rateLimiter('API')
  .keyByApiKey('x-api-key')
  .build();

// Usage with Express
app.use('/api/', ddosLimiter);
app.use('/api/', standardLimiter);
app.post('/api/auth/login', strictLimiter);
app.post('/api/upload', uploadLimiter);

RateLimiterBuilder Methods:

| Method | Parameters | Description | |--------|------------|-------------| | windowMs(ms) | number | Set window in milliseconds | | windowMinutes(minutes) | number | Set window in minutes | | windowHours(hours) | number | Set window in hours | | max(requests) | number | Set max requests in window | | message(msg) | string | Set error message | | skipSuccessful(skip?) | boolean | Skip successful requests (default: true) | | skipFailed(skip?) | boolean | Skip failed requests (default: true) | | keyBy(generator) | (req) => string | Custom key generator | | keyByIp() | - | Key by IP address (default) | | keyByApiKey(headerName?) | string | Key by API key header | | skipWhen(predicate) | (req) => boolean | Skip when condition is true | | build() | - | Build rate limiter middleware |

Available Rate Limit Tiers:

| Tier | Window | Max Requests | Use Case | |------|--------|--------------|----------| | STANDARD | 15 min | 100 | General API endpoints | | STRICT | 15 min | 20 | Auth endpoints (login, signup) | | DDOS | 1 min | 60 | Global DDoS protection | | VERY_STRICT | 1 hour | 5 | Password reset, sensitive actions | | RELAXED | 15 min | 500 | High-traffic endpoints | | API | 1 min | 30 | Third-party API access |

Response Helpers (@exyconn/common/server/response)

import { 
  // Success responses
  successResponse,      // 200 OK
  successResponseArr,   // 200 OK with pagination
  createdResponse,      // 201 Created
  noContentResponse,    // 204 No Content
  
  // Error responses
  badRequestResponse,   // 400 Bad Request
  unauthorizedResponse, // 401 Unauthorized
  forbiddenResponse,    // 403 Forbidden
  notFoundResponse,     // 404 Not Found
  conflictResponse,     // 409 Conflict
  serverErrorResponse,  // 500 Internal Server Error
  
  // Utilities
  extractColumns,
  
  // Types
  type ApiResponse,
  type PaginationData,
  type ColumnMetadata,
} from '@exyconn/common/server/response';

// Basic success response
app.get('/api/user/:id', async (req, res) => {
  const user = await User.findById(req.params.id);
  return successResponse(res, user, 'User retrieved successfully');
});
// Response: { status: 'success', statusCode: 200, message: '...', data: {...} }

// Paginated array response (auto-extracts columns for table rendering)
app.get('/api/users', async (req, res) => {
  const { page = 1, limit = 10 } = req.query;
  const users = await User.find().skip((page - 1) * limit).limit(limit);
  const total = await User.countDocuments();
  
  return successResponseArr(res, users, {
    total,
    page,
    limit,
    totalPages: Math.ceil(total / limit),
    hasNextPage: page < Math.ceil(total / limit),
    hasPrevPage: page > 1,
  }, 'Users retrieved');
});

// Error responses
return badRequestResponse(res, 'Invalid email format', { field: 'email' });
return unauthorizedResponse(res, 'Token expired');
return forbiddenResponse(res, 'Insufficient permissions');
return notFoundResponse(res, 'User not found');
return serverErrorResponse(res, 'Database connection failed');

Response Functions:

| Function | Status | Parameters | Description | |----------|--------|------------|-------------| | successResponse | 200 | (res, data, message?) | Standard success | | successResponseArr | 200 | (res, data[], pagination?, message?) | Paginated array | | createdResponse | 201 | (res, data, message?) | Resource created | | noContentResponse | 200* | (res, data?, message?) | No data found | | badRequestResponse | 400 | (res, message?, errors?) | Invalid request | | unauthorizedResponse | 401 | (res, message?) | Authentication required | | forbiddenResponse | 403 | (res, message?) | Access denied | | notFoundResponse | 404 | (res, message?) | Resource not found | | conflictResponse | 409 | (res, message?) | Resource conflict | | serverErrorResponse | 500 | (res, message?, error?) | Server error |

Logger (@exyconn/common/server/logger)

import { 
  createLogger, 
  logger,            // Default instance
  simpleLogger,      // Console-only logger
  createMorganStream,
  stream,            // Morgan stream for default logger
  type LoggerConfig,
} from '@exyconn/common/server/logger';

// Default logger (writes to console + files)
logger.info('Server started', { port: 3000 });
logger.error('Database error', { error: err.message, stack: err.stack });
logger.warn('Rate limit approaching');
logger.debug('Request received', { method: 'GET', path: '/api/users' });

// Custom logger
const customLogger = createLogger({
  level: 'debug',              // 'error' | 'warn' | 'info' | 'http' | 'debug'
  logsDir: 'logs',             // Directory for log files
  maxSize: '20m',              // Max file size before rotation
  maxFiles: '14d',             // Keep logs for 14 days
  errorMaxFiles: '30d',        // Keep error logs longer
});

// Simple logger (no files, just console)
simpleLogger.info('Quick debug message');

// With Morgan HTTP logger
import morgan from 'morgan';
app.use(morgan('combined', { stream }));

Database (@exyconn/common/server/db)

import { 
  connectDB, 
  disconnectDB, 
  getConnectionStatus,
  type DbConnectionOptions,
} from '@exyconn/common/server/db';

// Connect with default options
await connectDB(process.env.MONGODB_URI!, {}, logger);

// Connect with custom options
await connectDB(process.env.MONGODB_URI!, {
  maxPoolSize: 50,
  minPoolSize: 10,
  socketTimeoutMS: 45000,
  retryWrites: true,
}, logger);

// Check status
const status = getConnectionStatus(); // 'connected' | 'disconnected' | ...

// Disconnect gracefully
await disconnectDB(logger);

Middleware (@exyconn/common/server/middleware)

CRUD Controller Factory
import { createCrudControllers } from '@exyconn/common/server/middleware';
import { z } from 'zod';
import { User } from './models/User';

const userController = createCrudControllers({
  model: User,
  resourceName: 'User',
  createSchema: z.object({ name: z.string(), email: z.string().email() }),
  updateSchema: z.object({ name: z.string().optional() }),
  searchFields: ['name', 'email'],
  withOrganization: true,
});

// Auto-generated routes
router.get('/users', userController.getAll);
router.get('/users/:id', userController.getById);
router.post('/users', userController.create);
router.put('/users/:id', userController.update);
router.delete('/users/:id', userController.deleteOne);
router.delete('/users', userController.bulkDelete);
Query Parser & Pagination
import { queryParser, queryPagination } from '@exyconn/common/server/middleware';

router.get('/users', queryParser, async (req, res) => {
  const { page, limit, sort, search, filter } = req.parsedQuery;
  // page: 1, limit: 10, sort: { createdAt: 'desc' }, search: 'john'
  
  await queryPagination(User, {
    searchFields: ['name', 'email'],
    populate: ['department'],
  }, true)(req, res, () => {});
  
  res.json(res.paginatedResult);
  // { data: [...], meta: { total, page, limit, totalPages }, columns: [...] }
});
Authentication Middleware
import { 
  authenticateJWT,
  optionalAuthenticateJWT,
  authenticateApiKey,
  extractOrganization,
  type AuthRequest,
} from '@exyconn/common/server/middleware';

// JWT Authentication (required)
app.use('/api/protected', authenticateJWT(process.env.JWT_SECRET!));

// JWT Authentication (optional)
app.use('/api/public', optionalAuthenticateJWT(process.env.JWT_SECRET!));

// API Key Authentication
app.use('/api/external', authenticateApiKey(async (key) => {
  const apiKey = await ApiKey.findOne({ key, isActive: true });
  return { valid: !!apiKey, organizationId: apiKey?.organizationId };
}));

// Access auth data in routes
app.get('/api/profile', authenticateJWT(secret), (req: AuthRequest, res) => {
  const userId = req.userId;
  const orgId = req.organizationId;
});

Server Utils (@exyconn/common/server/utils)

import { 
  buildFilter, 
  buildPagination, 
  buildPaginationMeta,
} from '@exyconn/common/server/utils';

// Build MongoDB filter dynamically
const filter = buildFilter({
  organizationId: req.organizationId,
  search: req.query.q,
  searchFields: ['name', 'email', 'phone'],
  status: req.query.status,
  startDate: req.query.from,
  endDate: req.query.to,
});

// Calculate pagination
const pagination = buildPagination({
  page: parseInt(req.query.page) || 1,
  limit: parseInt(req.query.limit),
  defaultLimit: 10,
  maxLimit: 100,
});

// Build pagination meta
const meta = buildPaginationMeta(totalCount, pagination.page, pagination.limit);

Client Module

Import: @exyconn/common/client or specific submodules

React Hooks (@exyconn/common/client/hooks)

50+ production-ready React hooks organized by category.

State & Logic Hooks

| Hook | Description | Example | |------|-------------|---------| | useToggle | Boolean toggle | const [isOpen, toggle] = useToggle(false) | | useCounter | Numeric counter | const { count, increment, decrement } = useCounter(0) | | usePrevious | Track previous value | const prevValue = usePrevious(value) | | useObjectState | Partial state updates | const [state, setState] = useObjectState({ a: 1 }) | | useHistoryState | State with undo/redo | const [value, setValue, { undo, redo }] = useHistoryState('') | | useList | Array operations | const [list, { push, removeAt }] = useList([]) | | useMap | Map operations | const [map, { set, remove }] = useMap() | | useSet | Set operations | const [set, { add, toggle }] = useSet() |

Side Effects & Timing Hooks

| Hook | Description | Example | |------|-------------|---------| | useDebounce | Debounce value | const debouncedSearch = useDebounce(search, 500) | | useThrottle | Throttle value | const throttled = useThrottle(value, 100) | | useTimeout | Execute after delay | const { reset, clear } = useTimeout(fn, 3000) | | useInterval | Repeated execution | const { start, stop } = useInterval(fn, 1000) | | useCountdown | Countdown timer | const { count, start, stop } = useCountdown({ seconds: 60 }) |

Browser & Document Hooks

| Hook | Description | Example | |------|-------------|---------| | useWindowSize | Track dimensions | const { width, height } = useWindowSize() | | useWindowScroll | Track scroll | const { y, scrollToTop } = useWindowScroll() | | useDocumentTitle | Set title | useDocumentTitle('My Page') | | useVisibilityChange | Tab visibility | const isVisible = useVisibilityChange() | | useLockBodyScroll | Prevent scroll | useLockBodyScroll(isModalOpen) | | useIsClient | Client check | const isClient = useIsClient() |

Events & Interaction Hooks

| Hook | Description | Example | |------|-------------|---------| | useEventListener | Attach events | useEventListener('keydown', handler) | | useKeyPress | Detect key | const isEnter = useKeyPress('Enter') | | useHover | Track hover | const [ref, isHovered] = useHover() | | useClickAway | Click outside | useClickAway(ref, closeHandler) | | useLongPress | Long press | const props = useLongPress(handler, { delay: 500 }) | | useCopyToClipboard | Copy text | const [copied, copy] = useCopyToClipboard() |

Media & Device Hooks

| Hook | Description | Example | |------|-------------|---------| | useMediaQuery | Responsive | const isMobile = useMediaQuery('(max-width: 768px)') | | useBattery | Battery status | const { level, charging } = useBattery() | | useNetworkState | Network info | const { online, effectiveType } = useNetworkState() | | useIdle | User inactivity | const isIdle = useIdle(30000) | | useGeolocation | User location | const { latitude, longitude } = useGeolocation() | | useThemeDetector | System theme | const isDark = useThemeDetector() |

Storage Hooks

| Hook | Description | Example | |------|-------------|---------| | useLocalStorage | Persist in localStorage | const [value, setValue] = useLocalStorage('key', initial) | | useSessionStorage | Session storage | const [value, setValue] = useSessionStorage('key', initial) |

Data Fetching Hooks

| Hook | Description | Example | |------|-------------|---------| | useFetch | Data fetching | const { data, loading, error } = useFetch('/api/data') | | useScript | Load scripts | const { loaded, error } = useScript('https://...') |

Performance Hooks

| Hook | Description | Example | |------|-------------|---------| | useRenderCount | Count renders | const count = useRenderCount() | | useMeasure | Element dimensions | const [ref, { width, height }] = useMeasure() | | useIntersectionObserver | Visibility detection | const [ref, entry] = useIntersectionObserver() |

HTTP Client (@exyconn/common/client/http)

import {
  getRequest,
  postRequest,
  putRequest,
  patchRequest,
  deleteRequest,
  uploadFile,
  extractData,
  extractPaginatedData,
  isSuccess,
  parseError,
} from '@exyconn/common/client/http';

// GET request
const response = await getRequest('/api/users');
const users = extractData(response);

// POST request  
const createResponse = await postRequest('/api/users', { name: 'John' });
if (isSuccess(createResponse)) {
  console.log('User created!');
}

// Paginated response
const paginatedResponse = await getRequest('/api/users', { page: 1, limit: 10 });
const { items, total, page, totalPages } = extractPaginatedData(paginatedResponse);

// File upload
const uploadResponse = await uploadFile('/api/upload', file, { folder: 'avatars' });

// Error handling
try {
  await postRequest('/api/users', invalidData);
} catch (error) {
  const { message, statusCode } = parseError(error);
}

HTTP Functions:

| Function | Parameters | Returns | Description | |----------|------------|---------|-------------| | getRequest | url, params?, headers? | Promise<AxiosResponse> | GET request | | postRequest | url, data?, headers? | Promise<AxiosResponse> | POST request | | putRequest | url, data?, headers? | Promise<AxiosResponse> | PUT request | | patchRequest | url, data?, headers? | Promise<AxiosResponse> | PATCH request | | deleteRequest | url, params?, headers? | Promise<AxiosResponse> | DELETE request | | uploadFile | url, file, data? | Promise<AxiosResponse> | File upload |

Response Utilities:

| Function | Description | |----------|-------------| | extractData | Extract data from axios response | | extractMessage | Extract message from response | | extractPaginatedData | Extract paginated data with meta | | isSuccess | Check if response status is 2xx | | isSuccessResponse | Check response data for success | | parseError | Parse error to standardized format | | parseResponseData | Parse data from raw response | | parsePaginatedResponse | Parse paginated response data | | safeJsonParse | Safe JSON parsing with fallback |

Slug Utilities:

import { generateSlug, generateUrlSlug, generateSnakeSlug } from '@exyconn/common/client/http';

generateSlug('Hello World');      // 'helloWorld'
generateUrlSlug('Hello World');   // 'hello-world'
generateSnakeSlug('Hello World'); // 'hello_world'

Shared Module

Import: @exyconn/common/shared or specific submodules

Validation (@exyconn/common/shared/validation)

import { 
  VALIDATION_PATTERNS,
  isValidEmail,
  isValidPassword,
  isValidPhone,
  isValidUrl,
} from '@exyconn/common/shared/validation';

isValidEmail('[email protected]'); // true
isValidPassword('Password1!', 'strong'); // true
isValidPhone('+1 234 567 8901'); // true

// Use patterns directly
VALIDATION_PATTERNS.EMAIL.test('[email protected]');
VALIDATION_PATTERNS.SLUG.test('my-blog-post');

Available Patterns:

| Pattern | Description | |---------|-------------| | EMAIL | Email validation | | PASSWORD_STRONG | Strong password (8+ chars, upper, lower, number, special) | | PASSWORD_MEDIUM | Medium password (8+ chars, upper, lower, number) | | PHONE_INTERNATIONAL | International phone | | URL / URL_STRICT | URL validation | | SLUG | Kebab-case slug | | USERNAME | Username (3-30 chars, alphanumeric, _, -) | | IPV4 | IPv4 address | | HEX_COLOR | Hex color code | | DATE_ISO | ISO date format |

Environment Config (@exyconn/common/shared/config)

import { getEnv, getEnvOptional, isProd, isDev, validateEnv } from '@exyconn/common/shared/config';

const port = getEnv('PORT', 3000);           // Returns number
const secret = getEnv('JWT_SECRET');         // Required, throws if missing
const optional = getEnvOptional('OPTIONAL'); // Returns undefined if missing

if (isProd()) { /* Production only */ }
if (isDev()) { /* Development only */ }

validateEnv(['DATABASE_URL', 'JWT_SECRET']); // Throws with missing vars

Data Module

Import: @exyconn/common/data or specific files

Countries (@exyconn/common/data/countries)

import { 
  default as countries,
  getCountryByCode,
  getStatesByCountry,
  searchCountries,
  getFlag,
} from '@exyconn/common/data/countries';

const usa = getCountryByCode('US');
const states = getStatesByCountry('US');
const matches = searchCountries('united');
const flag = getFlag('US'); // '🇺🇸'

Currencies (@exyconn/common/data/currencies)

import { getCurrencyByCode, formatCurrency } from '@exyconn/common/data/currencies';

const usd = getCurrencyByCode('USD');
formatCurrency(1234.56, 'USD'); // '$1,234.56'

Phone Codes (@exyconn/common/data/phone-codes)

import { getPhoneCodeByCountry } from '@exyconn/common/data/phone-codes';

const usCode = getPhoneCodeByCountry('US');
// { country: 'United States', dial_code: '+1', flag: '🇺🇸' }

Timezones (@exyconn/common/data/timezones)

import { getTimezoneByCode, searchTimezones } from '@exyconn/common/data/timezones';

const pst = getTimezoneByCode('America/Los_Angeles');
const matches = searchTimezones('india');

Regex Patterns (@exyconn/common/data/regex)

import REGEX from '@exyconn/common/data/regex';

REGEX.EMAIL.BASIC.test('[email protected]');
REGEX.PASSWORD.STRONG.test('Test@123');
REGEX.CREDIT_CARD.VISA.test('4111111111111111');

Brand Identity (@exyconn/common/data/brand-identity)

import { BRANDS, getBrandById, getBrandByDomain, getThemedLogo } from '@exyconn/common/data/brand-identity';

const exyconn = getBrandById('exyconn');
const brand = getBrandByDomain('botify.life');
const logo = getThemedLogo('exyconn', 'dark');

Package Exports Map

@exyconn/common
├── /server                    # Server utilities
│   ├── /response              # Response helpers
│   ├── /enums                 # Status codes/messages
│   ├── /logger                # Winston logger
│   ├── /db                    # Database connections
│   ├── /middleware            # Auth middleware
│   ├── /utils                 # Server utilities
│   └── /configs               # Dynamic configurations
├── /client                    # Client utilities
│   ├── /http                  # Axios HTTP client
│   ├── /hooks                 # React hooks (50+)
│   ├── /logger                # Browser logger
│   ├── /utils                 # Client utilities
│   └── /web                   # Web utilities
├── /shared                    # Shared between server/client
│   ├── /types                 # TypeScript types
│   ├── /validation            # Validation patterns
│   ├── /constants             # Enums, breakpoints
│   ├── /config                # Environment config
│   └── /utils                 # Date-time utilities
└── /data                      # Static data modules
    ├── /countries             # Countries with states/cities
    ├── /currencies            # World currencies
    ├── /phone-codes           # Phone codes
    ├── /timezones             # Timezones
    ├── /regex                 # Regex patterns
    └── /brand-identity        # Brand configs

Peer Dependencies

All peer dependencies are optional. Install only what you need:

| Package | Version | Required For | |---------|---------|--------------| | react | ^17.0.0 || ^18.0.0 || ^19.0.0 | Client hooks | | express | ^4.18.0 || ^5.0.0 | Server utilities | | express-rate-limit | ^7.0.0 | Rate limiter configs | | cors | ^2.8.5 | CORS configs | | mongoose | ^8.0.0 | Database utilities | | winston | ^3.11.0 | Server logger | | jsonwebtoken | ^9.0.0 | Auth middleware | | axios | ^1.6.0 | HTTP client | | date-fns | ^3.0.0 | Date utilities |


TypeScript Support

Full TypeScript support with exported types:

import type { 
  ApiResponse, 
  CorsConfig, 
  RateLimitTierConfig,
  AuthRequest,
} from '@exyconn/common/server';

Node.js Version

Requires Node.js >= 18.0.0


Development

Setup

# Clone the repository
git clone https://github.com/exyconn/common.git
cd common

# Install dependencies
npm install

# Build the package
npm run build

# Run tests
npm run test

# Run tests with coverage
npm run test:coverage

Available Scripts

| Command | Description | |---------|-------------| | npm run build | Build the package with tsup | | npm run dev | Watch mode for development | | npm run test | Run tests with Vitest | | npm run test:coverage | Run tests with coverage report | | npm run lint | Lint the codebase | | npm run type-check | TypeScript type checking | | npm run docs:dev | Start docs dev server (port 4006) | | npm run docs:build | Build documentation | | npm run docs:start | Start production docs server |

Documentation Development

# Install docs dependencies
npm run docs:install

# Start documentation site locally
npm run docs:dev

# Open http://localhost:4006

Running with Docker

# Build the docs Docker image
cd docs
docker build -t common-docs .

# Run the container
docker run -p 4006:4006 common-docs

Contributing

Contributions are welcome! Please read our Contributing Guide for details.

  1. Fork the repository
  2. Create your feature branch (git checkout -b feature/amazing-feature)
  3. Commit your changes (git commit -m 'feat: add amazing feature')
  4. Push to the branch (git push origin feature/amazing-feature)
  5. Open a Pull Request

Commit Convention

We use Conventional Commits:

  • feat: - New features
  • fix: - Bug fixes
  • docs: - Documentation changes
  • test: - Adding or updating tests
  • refactor: - Code refactoring
  • chore: - Maintenance tasks

License

MIT © Exyconn