npm package: @pineliner/common-client
Description: Shared client-side utilities for frontend applications in the fulfilment system
Latest version: 1.0.2

# @fulfilment/common-client

Client-side utilities and components for frontend applications in the fulfilment system. This package provides reusable API clients, authentication utilities, and React integration.

## Installation

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

For React-specific features:
```bash
bun add @fulfilment/common-client react
```

## Features

### Core Client Utilities
- **API Client** - Type-safe HTTP client with built-in authentication
- **Auth Client** - Token management and user profile handling
- **Storage Utilities** - Browser storage and cookie helpers
- **Retry Logic** - Configurable retry mechanisms with backoff
- **Pagination Helpers** - Utilities for handling paginated responses

### React Integration
- **Hooks** - `useApi`, `usePaginatedApi`, `useMutation`, `useAuth`
- **Providers** - Context providers for API and auth clients
- **Components** - Loading spinners, error boundaries, pagination

## Usage

### Basic API Client

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

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

// Create auth client
const authClient = createAuthClient({
  storage: 'localStorage',
  tokenKey: 'auth_token'
});

// Make API calls
const users = await apiClient.get('/api/users');
const newUser = await apiClient.post('/api/users', { name: 'John' });

// Handle authentication
authClient.setTokens({ 
  accessToken: 'jwt-token',
  expiresAt: Date.now() + 3600000 
});
apiClient.setAuthToken(authClient.getAccessToken());
```

### React Usage

```typescript
import { useApi, usePaginatedApi, useAuth } from '@fulfilment/common-client/react';
import { AuthProvider, useApiClient } from '@fulfilment/common-client/react';

// API Hook
function UserList() {
  const apiClient = useApiClient();
  const { data, loading, error } = useApi(apiClient, '/api/users');
  
  if (loading) return <div>Loading...</div>;
  if (error) return <div>Error: {error}</div>;
  
  return (
    <ul>
      {data?.map(user => <li key={user.id}>{user.name}</li>)}
    </ul>
  );
}

// Pagination Hook
function PaginatedUsers() {
  const apiClient = useApiClient();
  const { data, loading, pagination, nextPage, prevPage } = usePaginatedApi(
    apiClient, 
    '/api/users',
    { page: 1, limit: 10 }
  );
  
  return (
    <div>
      <ul>{data?.map(user => <li key={user.id}>{user.name}</li>)}</ul>
      <button onClick={prevPage} disabled={!pagination?.hasPrev}>
        Previous
      </button>
      <button onClick={nextPage} disabled={!pagination?.hasNext}>
        Next
      </button>
    </div>
  );
}

// App with providers
function App() {
  const apiClient = createApiClient({ baseUrl: '/api' });
  const authClient = createAuthClient();

  return (
    <AuthProvider apiClient={apiClient} authClient={authClient}>
      <UserList />
    </AuthProvider>
  );
}
```


### Utilities

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

// URL building
const url = buildApiUrl('/api', '/users', { page: 1, limit: 20 });

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

// Storage
storage.set('preferences', { theme: 'dark' });
const prefs = storage.get('preferences');

// Cookies
cookies.set('session', 'abc123', { 
  expires: new Date(Date.now() + 86400000) 
});
```

## API Reference

### ApiClient

```typescript
class ApiClient {
  constructor(config: ApiClientConfig)
  setAuthToken(token: string | null): void
  get<T>(url: string, params?: Record<string, any>): Promise<ApiResponse<T>>
  post<T>(url: string, body?: any): Promise<ApiResponse<T>>
  put<T>(url: string, body?: any): Promise<ApiResponse<T>>
  delete<T>(url: string): Promise<ApiResponse<T>>
  getPaginated<T>(url: string, pagination?: PaginationParams): Promise<ApiResponse<PaginatedResponse<T>>>
}
```

### AuthClient

```typescript
class AuthClient {
  constructor(config?: AuthClientConfig)
  setTokens(tokens: AuthTokens): void
  getTokens(): AuthTokens | null
  getAccessToken(): string | null
  clearTokens(): void
  isAuthenticated(): boolean
  isTokenExpired(): boolean
  getUserProfile(): UserProfile | null
}
```

## Migration from Fetch

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

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

## Supabase-like Client (NEW)

For PostgREST-compatible APIs, we now provide a Supabase-style query builder:

### Basic Setup

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

const client = createClient('https://api.example.com', 'your-anon-key', {
  db: { schema: 'public' },
  auth: { autoRefreshToken: true },
  global: { headers: { 'X-Custom': 'value' } }
});
```

### Query Examples

```typescript
// Type-safe queries
interface User {
  id: number;
  email: string;
  name: string;
}

// SELECT with filters
const { data } = await client.from<User>('users')
  .select('id, email, name')
  .eq('active', true)
  .in('role', ['admin', 'user'])
  .order('created_at', { ascending: false })
  .limit(10);

// INSERT
const { data: newUser } = await client.from<User>('users')
  .insert({ email: 'john@example.com', name: 'John' });

// UPDATE
const { data: updated } = await client.from<User>('users')
  .update({ name: 'John Updated' })
  .eq('id', 123);

// DELETE
await client.from('users').delete().eq('id', 123);
```

### PostgREST Operators

All standard PostgREST operators are supported:

```typescript
// Comparison
.eq('status', 'active')
.neq('status', 'deleted')
.gt('age', 18)
.gte('age', 18)
.lt('price', 100)
.lte('price', 100)

// Text search
.like('name', '%john%')
.ilike('name', '%JOHN%') // case insensitive

// Arrays and JSON
.in('status_id', [1, 2, 3])
.contains('tags', ['urgent'])

// Null checks
.is('deleted_at', null)
```

### Authentication Integration

```typescript
// Manual token management
client.setAuth('jwt-token-here');

// Built-in auth methods
const { user, error } = await client.auth.signIn({
  email: 'user@example.com',
  password: 'password'
});

// Auto token injection in all requests
const { data } = await client.from('protected_table').select();
```

### Service Class Pattern

```typescript
export class OrderService {
  private client = createClient(process.env.API_URL!, process.env.ANON_KEY!);

  async getOrders(statusIds?: number[]) {
    let query = this.client.from('orders').select();
    
    if (statusIds?.length) {
      query = query.in('status_id', statusIds);
    }
    
    return query.order('created_at', { ascending: false });
  }

  async updateStatus(orderId: number, statusId: number) {
    return this.client.from('orders')
      .update({ status_id: statusId })
      .eq('id', orderId);
  }
}
```

## Integration with Server Package

This client package works seamlessly with `@fulfilment/common-services`:

```typescript
// Client-side
import { createApiClient, createClient } from '@fulfilment/common-client';
import type { JWTPayload } from '@fulfilment/common-services';

// Server-side  
import { createFullApp } from '@fulfilment/common-services';

// Traditional API client
const apiClient = createApiClient({ baseUrl: '/api' });

// Supabase-like client for PostgREST endpoints
const supabaseClient = createClient('/rest/v1', 'anon-key');

// Server app
const app = createFullApp({ 
  auth: { jwtSecret: 'secret' },
  health: { serviceName: 'my-api' } 
});
```