npm package: @pineliner/common-services
Description: Shared server-side Express components and utilities for fulfilment system APIs
Latest version: 1.0.5

# @fulfilment/common-services

Shared components and utilities for the fulfilment system, supporting both **server-side** (Express APIs) and **client-side** (frontend applications) usage.

## Installation

```bash
bun add @fulfilment/common-services
```

## Features

### Server-Side
- **Authentication Middleware** - JWT-based authentication with configurable options
- **CORS Middleware** - Standardized CORS configuration with presets
- **Error Handling** - Centralized error handling middleware  
- **Health Checks** - Standardized health check endpoints
- **Express App Factory** - Pre-configured Express app setup

### Client-Side
- **API Client** - Type-safe HTTP client with authentication support
- **Auth Client** - Token management and user profile handling
- **Utilities** - URL building, retry logic, pagination helpers
- **Local Storage** - Browser storage and cookie utilities

### Common
- **TypeScript Support** - Full type definitions for both server and client
- **Shared Types** - Common interfaces and types across server/client

## Usage

## Server-Side Usage

### Quick Start - Full App Factory

```typescript
import { createFullApp } from '@fulfilment/common-services';

const app = createFullApp({
  cors: {
    origins: ['http://localhost:3000', 'http://localhost:3001'],
    credentials: true
  },
  auth: {
    jwtSecret: process.env.JWT_SECRET,
    skipPaths: ['/health', '/docs']
  },
  health: {
    serviceName: 'my-api',
    version: '1.0.0'
  }
});

// Your protected routes
app.use('/api/users', (app as any).auth, userRoutes);

app.listen(3000);
```

### Individual Middleware Usage

#### Authentication

```typescript
import { createAuthMiddleware, AuthMiddleware } from '@fulfilment/common-services/middleware';

// Simple usage
const auth = createAuthMiddleware({
  jwtSecret: process.env.JWT_SECRET,
  expiresIn: '24h',
  skipPaths: ['/health', '/docs']
});

app.use('/api', auth, apiRoutes);

// Class-based usage for token generation
const authManager = new AuthMiddleware({
  jwtSecret: process.env.JWT_SECRET
});

const token = authManager.generateToken({
  userId: '123',
  email: 'user@example.com'
});
```

#### CORS

```typescript
import { createCorsMiddleware, corsConfigs } from '@fulfilment/common-services/middleware';

// Development preset
app.use(createCorsMiddleware(corsConfigs.development));

// Custom configuration  
app.use(createCorsMiddleware({
  origins: ['https://myapp.com'],
  methods: ['GET', 'POST'],
  credentials: false
}));
```

#### Error Handling

```typescript
import { createErrorHandler, notFoundHandler } from '@fulfilment/common-services/middleware';

// 404 handler
app.use(notFoundHandler);

// Error handler
app.use(createErrorHandler({
  includeStack: process.env.NODE_ENV === 'development',
  logErrors: true,
  customLogger: (error, req) => {
    console.error('API Error:', error.message, req.path);
  }
}));
```

#### Health Checks

```typescript
import { createHealthEndpoint } from '@fulfilment/common-services/utils';

const healthCheck = createHealthEndpoint({
  serviceName: 'pipeline-api',
  version: '1.0.0',
  customChecks: [
    async () => ({
      name: 'database',
      status: 'ok', // or 'error'
      details: { connectionCount: 5 }
    })
  ]
});

app.get('/health', healthCheck);
```

### Manual App Setup

```typescript
import express from 'express';
import { 
  createCorsMiddleware, 
  createAuthMiddleware,
  createErrorHandler,
  notFoundHandler 
} from '@fulfilment/common-services';

const app = express();

// Basic middleware
app.use(express.json());
app.use(createCorsMiddleware());

// Health check (before auth)
app.get('/health', simpleHealthCheck);

// Authentication for API routes
const auth = createAuthMiddleware();
app.use('/api', auth, apiRoutes);

// Error handling
app.use(notFoundHandler);
app.use(createErrorHandler());
```

## Type Definitions

```typescript
import type { 
  JWTPayload, 
  AuthenticatedRequest, 
  HealthCheckResponse,
  CORSConfig 
} from '@fulfilment/common-services';

// Extend Express Request with auth info
declare global {
  namespace Express {
    interface Request {
      user?: JWTPayload;
      tenantId?: string;
    }
  }
}
```

## Migration from Existing Code

To migrate existing API packages:

1. **Install the common-services package**
2. **Replace auth middleware** - Remove local auth.ts files and use the shared version
3. **Replace CORS setup** - Use the shared CORS configuration  
4. **Replace error handlers** - Use the shared error handling middleware
5. **Replace health checks** - Use the shared health check utilities
6. **Update imports** - Replace local imports with package imports

## Configuration

All middleware accepts configuration objects to customize behavior:

- **AuthConfig** - JWT secret, expiration, skip paths
- **CORSConfig** - Origins, headers, methods, credentials  
- **ErrorHandlerConfig** - Stack traces, logging, custom logger
- **HealthCheckConfig** - Service name, version, custom checks

## Client-Side Usage

### Import Options

```typescript
// Option 1: Import from specific client module
import { ApiClient, AuthClient, createApiClient } from '@fulfilment/common-services/client';

// Option 2: Import from main package with namespace
import { client } from '@fulfilment/common-services';
const apiClient = new client.ApiClient({ baseUrl: 'http://localhost:3000' });

// Option 3: Import from server module explicitly  
import { authMiddleware } from '@fulfilment/common-services/server';
```

### API Client

```typescript
import { createApiClient, createAuthClient } from '@fulfilment/common-services/client';

// Create API client
const apiClient = createApiClient({
  baseUrl: 'http://localhost:3000',
  timeout: 10000,
  onError: (error) => {
    console.error('API Error:', error.message);
  }
});

// Basic usage
const response = await apiClient.get('/api/users');
const users = response.data;

// With authentication token
apiClient.setAuthToken('your-jwt-token');
const protectedData = await apiClient.get('/api/protected');

// POST request
const newUser = await apiClient.post('/api/users', {
  name: 'John Doe',
  email: 'john@example.com'
});

// Paginated requests
const paginatedUsers = await apiClient.getPaginated('/api/users', {
  page: 1,
  limit: 20,
  sort: 'created_at',
  order: 'desc'
});
```

### Authentication Client

```typescript
import { createAuthClient } from '@fulfilment/common-services/client';

const authClient = createAuthClient({
  storage: 'localStorage', // or 'sessionStorage' or 'memory'
  tokenKey: 'auth_token',
  onTokenExpired: () => {
    // Redirect to login or refresh token
    window.location.href = '/login';
  }
});

// Login and store tokens
const loginResponse = await apiClient.post('/auth/login', {
  email: 'user@example.com',
  password: 'password'
});

authClient.setTokens({
  accessToken: loginResponse.data.token,
  refreshToken: loginResponse.data.refreshToken,
  expiresAt: Date.now() + 3600000 // 1 hour
});

// Check authentication status
if (authClient.isAuthenticated()) {
  const userProfile = authClient.getUserProfile();
  console.log('User:', userProfile);
}

// Get current token for API requests
const token = authClient.getAccessToken();
apiClient.setAuthToken(token);
```

### Client Utilities

```typescript
import { 
  buildApiUrl, 
  withRetry, 
  createPaginationInfo, 
  storage,
  cookies 
} from '@fulfilment/common-services/client';

// URL building
const url = buildApiUrl('http://localhost:3000', '/api/users', { 
  page: 1, 
  limit: 20 
});
// Result: "http://localhost:3000/api/users?page=1&limit=20"

// Retry logic
const dataWithRetry = await withRetry(
  () => apiClient.get('/api/unreliable-endpoint'),
  {
    maxRetries: 3,
    delay: 1000,
    backoff: 'exponential',
    retryCondition: (error) => error.status >= 500
  }
);

// Pagination helpers
const paginationInfo = createPaginationInfo(paginatedResponse);
console.log(`Showing ${paginationInfo.startIndex}-${paginationInfo.endIndex} of ${paginationInfo.totalItems}`);

// Local storage
storage.set('user-preferences', { theme: 'dark', language: 'en' });
const preferences = storage.get('user-preferences');

// Cookies
cookies.set('session-id', 'abc123', { 
  expires: new Date(Date.now() + 86400000), // 1 day
  secure: true,
  sameSite: 'strict'
});
```

### React Hook Examples

```typescript
// Custom hook for API calls
import { useEffect, useState } from 'react';
import { createApiClient } from '@fulfilment/common-services/client';

const apiClient = createApiClient({ baseUrl: process.env.REACT_APP_API_URL });

export function useApi<T>(endpoint: string) {
  const [data, setData] = useState<T | null>(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState<string | null>(null);

  useEffect(() => {
    apiClient.get<T>(endpoint)
      .then(response => setData(response.data))
      .catch(err => setError(err.message))
      .finally(() => setLoading(false));
  }, [endpoint]);

  return { data, loading, error };
}

// Usage in component
function UserList() {
  const { data: users, loading, error } = useApi<User[]>('/api/users');
  
  if (loading) return <div>Loading...</div>;
  if (error) return <div>Error: {error}</div>;
  
  return (
    <ul>
      {users?.map(user => <li key={user.id}>{user.name}</li>)}
    </ul>
  );
}
```

## Migration Examples

### Server-Side (Express API)

**Before:**
```typescript
// Old tracking-api/src/index.ts
import express from 'express';
import cors from 'cors';
import { authMiddleware } from './middleware/auth';
// ... lots of boilerplate
```

**After:**
```typescript
// New tracking-api/src/index.ts  
import { createFullApp } from '@fulfilment/common-services/server';
// or: import { createFullApp } from '@fulfilment/common-services';

const app = createFullApp({
  cors: { origins: ['http://localhost:3000'] },
  auth: { jwtSecret: process.env.JWT_SECRET },
  health: { serviceName: 'tracking-api' }
});
```

### Client-Side (React/Vue/etc)

**Before:**
```typescript
// Old frontend code
const response = await fetch('/api/users', {
  headers: {
    'Authorization': `Bearer ${token}`,
    'Content-Type': 'application/json'
  }
});
const data = await response.json();
```

**After:**
```typescript
// New frontend code
import { createApiClient } from '@fulfilment/common-services/client';

const apiClient = createApiClient({ baseUrl: '/api' });
apiClient.setAuthToken(token);
const { data } = await apiClient.get('/users');
```

## Development

```bash
# Install dependencies
bun install

# Build the library  
bun run build

# Type check
bun run typecheck

# Watch mode for development
bun run dev
```