@pineliner/common-client
v1.0.2
Published
Shared client-side utilities for frontend applications in the fulfilment system
Downloads
3
Maintainers
Readme
@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
bun add @fulfilment/common-clientFor React-specific features:
bun add @fulfilment/common-client reactFeatures
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
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
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
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
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
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:
const response = await fetch('/api/users', {
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json'
}
});
const data = await response.json();After:
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
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
// 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: '[email protected]', 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:
// 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
// Manual token management
client.setAuth('jwt-token-here');
// Built-in auth methods
const { user, error } = await client.auth.signIn({
email: '[email protected]',
password: 'password'
});
// Auto token injection in all requests
const { data } = await client.from('protected_table').select();Service Class Pattern
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:
// 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' }
});