npm package: @hnndes-ou/forms-easy-sdk
Description: FormsEasy SDK is a powerful, type-safe HTTP client library for interacting with the FormsEasy API. It provides a clean, intuitive interface for making HTTP requests with built-in state management, error handling, request/response interceptors, and query b
Author: oudisho
Latest version: 1.2.0

I'll create comprehensive documentation for the entire SDK. Here's the complete documentation:

---

# **FormsEasy SDK - Complete Documentation**

## Table of Contents
1. [Installation](#installation)
2. [Core SDK](#core-sdk)
3. [Authentication](#authentication)
4. [CRUD Operations](#crud-operations)
5. [Query Builder & Filtering](#query-builder--filtering)
6. [File Upload (Gallery)](#file-upload-gallery)
7. [State Management](#state-management)
8. [Error Handling](#error-handling)
9. [Interceptors](#interceptors)
10. [React Integration](#react-integration)
11. [TypeScript Types](#typescript-types)
12. [API Reference](#api-reference)

---

## Installation

```bash
npm install @hnndes-ou/forms-easy-sdk
# or
yarn add @hnndes-ou/forms-easy-sdk
```

```typescript
// ES Modules
import FormsEasySDK from '@hnndes-ou/forms-easy-sdk';

// React Hook
import { useFormsEasy } from '@hnndes-ou/forms-easy-sdk/react';
```

---

## Core SDK

### Initialization

```typescript
import FormsEasySDK from '@hnndes-ou/forms-easy-sdk';

const client = new FormsEasySDK({
  authToken: 'your-api-key',
  modelName: 'posts',
  timeout: 30000,
  headers: {
    'X-Custom-Header': 'value'
  }
});
```

### Configuration Options

| Option | Type | Default | Description |
|--------|------|---------|-------------|
| `authToken` | `string \| null` | `null` | Authentication token |
| `modelName` | `string` | `''` | Default model for operations |
| `timeout` | `number` | `30000` | Request timeout (ms) |
| `headers` | `Record<string, string>` | `{}` | Default headers |

---

## Authentication

```typescript
// Set/update auth token
client.setAuthToken('new-token', 'Bearer');

// Check if authenticated
if (client.authToken) {
  console.log('Authenticated');
}

// Clear authentication
client.clearAuth();
```

---

## CRUD Operations

### Create
```typescript
const newItem = await client.create<Post>({
  title: 'My Post',
  content: 'Hello World',
  status: 'draft'
});
// Returns: { data: Post, status: 201, ... }
```

### Read All (with filtering)
```typescript
// Basic
const allPosts = await client.getAll<Post>();

// With options
const posts = await client.getAll<Post>({
  limit: 10,
  page: 1,
  filter: { status: 'active' },
  order: { createdAt: -1 },
  projection: ['title', 'createdAt']
});
```

### Read One
```typescript
// By ID
const post = await client.getById<Post>('123');

// Using query builder
const post = await client.query<Post>()
  .where('_id', '123')
  .first();
```

### Update (Full Replace)
```typescript
const updated = await client.update<Post>('123', {
  title: 'New Title',
  content: 'New Content',
  status: 'published'
});
```

### Patch (Partial Update)
```typescript
const patched = await client.patch<Post>('123', {
  title: 'Updated Title Only'
});
```

### Delete
```typescript
await client.delete('123');
```

---

## Query Builder & Filtering

### Basic Query Builder
```typescript
const results = await client.query<Post>()
  .where('status', 'active')
  .where('authorId', 'user123')
  .sortBy('createdAt', 'desc')
  .limit(10)
  .page(1)
  .project(['title', 'content'])
  .get();
```

### Filter Operators

| Operator | Description | Example |
|----------|-------------|---------|
| `$eq` | Equal | `{ status: { $eq: 'active' } }` or `{ status: 'active' }` |
| `$ne` | Not equal | `{ status: { $ne: 'deleted' } }` |
| `$gt` | Greater than | `{ age: { $gt: 18 } }` |
| `$gte` | Greater than or equal | `{ age: { $gte: 18 } }` |
| `$lt` | Less than | `{ price: { $lt: 100 } }` |
| `$lte` | Less than or equal | `{ price: { $lte: 100 } }` |
| `$in` | Match any in array | `{ status: { $in: ['active', 'pending'] } }` |
| `$nin` | Match none in array | `{ status: { $nin: ['deleted', 'archived'] } }` |
| `$regex` | Regex pattern | `{ title: { $regex: 'hello', $options: 'i' } }` |
| `$options` | Regex flags | `'i'` (case-insensitive), `'m'` (multiline), etc. |

### Complex Filter Examples

```typescript
// Range query
const adults = await client.query<User>()
  .where('age', { $gte: 18, $lte: 65 })
  .get();

// Array contains
const specificPosts = await client.query<Post>()
  .where('tags', { $in: ['tech', 'coding'] })
  .get();

// Regex search (case-insensitive)
const searchResults = await client.query<Post>()
  .where('title', { $regex: 'keyword', $options: 'i' })
  .get();

// Combined filters
const complex = await client.query<Post>()
  .where('status', 'active')
  .where('views', { $gt: 100 })
  .where('category', { $in: ['tech', 'news'] })
  .where('title', { $regex: 'tutorial', $options: 'i' })
  .sortBy('createdAt', 'desc')
  .limit(20)
  .get();
```

### Utility Methods

```typescript
// Get first match only
const first = await client.query<Post>()
  .where('status', 'featured')
  .first();

// Count items
const count = await client.count({ status: 'active' });

// Check existence
const exists = await client.exists({ slug: 'my-post' });

// Get distinct values
const statuses = await client.distinct('status');
// Returns: ['active', 'draft', 'archived']

// Pagination with metadata
const paginated = await client.paginate<Post>({
  page: 2,
  limit: 10,
  filter: { status: 'active' }
});
// Returns: { data: Post[], pagination: { page, limit, total, pages, hasNext, hasPrev } }
```

---

## File Upload (Gallery)

### Simple Upload
```typescript
const fileInput = document.getElementById('file');
const file = fileInput.files[0];

// Auto-extracts name, size, type from File object
const response = await client.uploadFile(file, (percent) => {
  console.log(`Upload progress: ${percent}%`);
});

console.log('File uploaded:', response.data);
// response.data contains:
// - id: string
// - name: string
// - size: number
// - mimeType: string
// - url: string (public URL)
// - downloadUrl: string
// - createDate: string
```

### Upload with Custom Name
```typescript
const response = await client.uploadFile(
  file,
  (percent) => updateProgress(percent),
  'custom-filename.pdf'  // Optional custom name
);
```

### List Files
```typescript
// List all files
const files = await client.listFiles();

// With filtering
const files = await client.listFiles({
  limit: 20,
  page: 1,
  orderBy: 'createDate',  // or 'name'
  order: 'DESC',          // or 'ASC'
  search: 'document',     // search in filename
  since: '2024-01-01T00:00:00Z'  // ISO date string
});

console.log(files.data.items);  // GalleryFile[]
console.log(files.data.meta);   // Pagination metadata
```

### Delete File
```typescript
await client.deleteFile('file-id-123');
```

### Manual Upload Flow (Advanced)
```typescript
// Step 1: Get presigned URL
const presigned = await client.requestPresignedUrl({
  name: file.name,
  size: file.size,
  mimeType: file.type
});

// Step 2: Upload to S3 directly
await client.upload(
  presigned.data,
  file,
  (percent) => console.log(`${percent}%`)
);

// presigned.data contains uploadUrl, downloadUrl, etc.
```

---

## State Management

The SDK has built-in reactive state management.

```typescript
// Subscribe to all state changes
const unsubscribe = client.subscribeToState((state) => {
  console.log('Status:', state.status);   // 'idle' | 'loading' | 'success' | 'error'
  console.log('Loading:', state.loading); // boolean
  console.log('Data:', state.data);       // T | null
  console.log('Error:', state.error);     // IApiError | null
});

// Get current state snapshot
const currentState = client.getRequestState();
if (currentState.loading) {
  showSpinner();
}

// Check if loading
if (client.isLoading()) {
  // Show loading UI
}

// Reset state
client.resetState();

// Unsubscribe
unsubscribe();
```

---

## Error Handling

### Global Error Handler
```typescript
const removeHandler = client.onError((error) => {
  switch (error.type) {
    case 'unauthorized':
      redirectToLogin();
      break;
    case 'validation':
      showValidationErrors(error.errors);
      break;
    case 'network':
      showOfflineMessage();
      break;
    case 'timeout':
      showTimeoutMessage();
      break;
    case 'not_found':
      showNotFound();
      break;
    default:
      console.error('Error:', error.message);
  }
});

// Remove handler
removeHandler();
```

### Try-Catch with Types
```typescript
try {
  await client.create(data);
} catch (error) {
  // error is typed as IApiError
  console.log(error.type);    // 'validation' | 'unauthorized' | etc.
  console.log(error.message); // Human-readable message
  console.log(error.status);  // HTTP status code
  console.log(error.errors);  // Validation errors object
}
```

### Error Types
| Type | Description | HTTP Status |
|------|-------------|-------------|
| `validation` | Validation failed | 422 |
| `unauthorized` | Not authenticated | 401 |
| `forbidden` | No permission | 403 |
| `not_found` | Resource not found | 404 |
| `unprocessable` | Cannot process | 422 |
| `server` | Server error | 500+ |
| `network` | Network failure | - |
| `timeout` | Request timeout | - |
| `canceled` | Request aborted | - |

---

## Interceptors

Modify requests/responses globally.

### Request Interceptor
```typescript
const removeReq = client.interceptors.addRequestInterceptor((config) => {
  // Add auth header dynamically
  config.headers['X-Timestamp'] = Date.now().toString();
  
  // Or cancel request
  if (shouldCancel(config)) {
    return false;
  }
  
  return config;
});
```

### Response Interceptor
```typescript
const removeRes = client.interceptors.addResponseInterceptor((response) => {
  console.log(`${response.status} ${response.config.url}`);
  
  // Transform data
  if (response.data?.items) {
    response.data = response.data.items;
  }
  
  return response;
});
```

### Clear Interceptors
```typescript
client.interceptors.clearRequestInterceptors();
client.interceptors.clearResponseInterceptors();
```

---

## React Integration

### useFormsEasy Hook

```typescript
import { useFormsEasy } from '@hnndes-ou/forms-easy-sdk/react';

function MyComponent() {
  const {
    // State
    client,
    state,
    isLoading,
    error,
    data,
    
    // CRUD
    getAll,
    getById,
    create,
    update,
    patch,
    delete: deleteItem,
    query,
    reset,
    
    // File Upload
    uploadFile,
    listFiles,
    deleteFile,
    requestPresignedUrl,
    uploadToS3,
    uploadProgress,
    isUploading,
    files
  } = useFormsEasy<Post>({
    authToken: 'your-token',
    modelName: 'posts',
    autoInit: true
  });

  // Use methods...
}
```

### React Example: CRUD + File Upload

```tsx
import React, { useState, useEffect } from 'react';
import { useFormsEasy } from '@hnndes-ou/forms-easy-sdk/react';

function PostManager() {
  const {
    data: posts,
    isLoading,
    error,
    getAll,
    create,
    delete: deletePost,
    uploadFile,
    listFiles,
    deleteFile,
    uploadProgress,
    isUploading,
    files
  } = useFormsEasy<Post>({
    modelName: 'posts',
    authToken: 'your-api-key'
  });

  const [newPostTitle, setNewPostTitle] = useState('');

  useEffect(() => {
    getAll();
    listFiles(); // Load files on mount
  }, []);

  const handleCreate = async () => {
    await create({ title: newPostTitle });
    setNewPostTitle('');
  };

  const handleFileUpload = async (e: React.ChangeEvent<HTMLInputElement>) => {
    const file = e.target.files?.[0];
    if (!file) return;
    
    try {
      await uploadFile(file, (percent) => {
        console.log(`Upload: ${percent}%`);
      });
      alert('Upload successful!');
    } catch (err) {
      alert('Upload failed');
    }
  };

  if (isLoading) return <div>Loading...</div>;
  if (error) return <div>Error: {error.message}</div>;

  return (
    <div>
      <h1>Posts</h1>
      
      {/* Create Post */}
      <input 
        value={newPostTitle} 
        onChange={e => setNewPostTitle(e.target.value)} 
        placeholder="Post title"
      />
      <button onClick={handleCreate}>Create</button>

      {/* Upload File */}
      <input 
        type="file" 
        onChange={handleFileUpload} 
        disabled={isUploading} 
      />
      {isUploading && <progress value={uploadProgress} max={100} />}

      {/* Posts List */}
      <ul>
        {posts?.map(post => (
          <li key={post._id}>
            {post.title}
            <button onClick={() => deletePost(post._id)}>Delete</button>
          </li>
        ))}
      </ul>

      {/* Files List */}
      <h2>Uploaded Files ({files.length})</h2>
      <ul>
        {files.map(file => (
          <li key={file.id}>
            <a href={file.url} target="_blank">{file.name}</a>
            <button onClick={() => deleteFile(file.id)}>Delete</button>
          </li>
        ))}
      </ul>
    </div>
  );
}
```

---

## TypeScript Types

### Core Types

```typescript
// Configuration
interface RequestConfig {
  baseURL?: string;
  headers?: Record<string, string>;
  timeout?: number;
  authToken?: string | null;
  authType?: string;
  modelName?: string;
}

// API Response
interface ApiResponse<T> {
  status: number;
  statusText: string;
  headers: Headers;
  data: T;
  config: RequestConfig;
}

// Request State
interface IRequestState<T> {
  loading: boolean;
  data: T | null;
  error: IApiError | null;
  status: 'idle' | 'loading' | 'success' | 'error';
}

// API Error
interface IApiError {
  type: 'validation' | 'unauthorized' | 'forbidden' | 'not_found' |
        'unprocessable' | 'server' | 'network' | 'client' | 'http' |
        'timeout' | 'canceled';
  message: string;
  status?: number;
  details?: any;
  errors?: Record<string, string[]>;
  original?: any;
}

// Query Options
interface QueryOptions {
  limit?: number;
  page?: number;
  since?: string;
  filter?: string | Record<string, any>;
  order?: string | Record<string, 1 | -1>;
  projection?: string[];
  distinct?: string;
}

// Gallery Types
interface GalleryFile {
  id: string;
  name: string;
  size: number;
  mimeType: string;
  key: string;
  bucket: string;
  url: string;
  downloadUrl: string;
  createDate: string;
}

interface GalleryListOptions {
  limit?: number;
  page?: number;
  since?: string;
  orderBy?: 'name' | 'createDate';
  order?: 'ASC' | 'DESC';
  search?: string;
}

interface PresignedUrlResponse {
  uploadUrl: string;
  downloadUrl: string;
  key: string;
  bucket: string;
  expiresIn: number;
  id: string;
  size: number;
  name: string;
  mimeType: string;
  url: string;
  createDate: string;
}

interface RequestUploadParams {
  name: string;
  size: number;
  mimeType: string;
}
```

---

## API Reference

### FormsEasySDK Methods

| Method | Signature | Description |
|--------|-----------|-------------|
| `setAuthToken` | `(token: string, type?: string) => void` | Set auth token |
| `clearAuth` | `() => void` | Clear auth |
| `getAll` | `<T>(options?: QueryOptions) => Promise<ApiResponse<T[]>>` | Get all items |
| `getById` | `<T>(id: string \| number) => Promise<ApiResponse<T \| null>>` | Get by ID |
| `create` | `<T, D>(data: D) => Promise<ApiResponse<T>>` | Create item |
| `update` | `<T, D>(id: string \| number, data: D) => Promise<ApiResponse<T>>` | Full update |
| `patch` | `<T, D>(id: string \| number, data: Partial<D>) => Promise<ApiResponse<T>>` | Partial update |
| `delete` | `<T>(id: string \| number) => Promise<ApiResponse<T>>` | Delete item |
| `query` | `<T>() => QueryBuilder` | Get query builder |
| `first` | `<T>(options?: QueryOptions) => Promise<ApiResponse<T \| null>>` | Get first match |
| `count` | `(filter?: object) => Promise<number>` | Count items |
| `exists` | `(filter: object) => Promise<boolean>` | Check existence |
| `distinct` | `<T>(field: string, filter?: object) => Promise<ApiResponse<T[]>>` | Get distinct values |
| `paginate` | `<T>(options?: QueryOptions) => Promise<ApiResponse<{data: T[], pagination}>>` | Paginated results |
| `uploadFile` | `(file: File \| Blob, onProgress?: (p: number) => void, customName?: string) => Promise<ApiResponse<PresignedUrlResponse>>` | Upload file |
| `listFiles` | `(options?: GalleryListOptions) => Promise<ApiResponse<GalleryListResponse>>` | List files |
| `deleteFile` | `(id: string \| number) => Promise<ApiResponse<void>>` | Delete file |
| `requestPresignedUrl` | `(params: RequestUploadParams) => Promise<ApiResponse<PresignedUrlResponse>>` | Get upload URL |
| `upload` | `(presignedData: PresignedUrlResponse, file: File \| Blob, onProgress?: (p: number) => void) => Promise<void>` | Upload to S3 |
| `subscribeToState` | `<T>(callback: StateObserver<T>) => () => void` | Subscribe to state |
| `getRequestState` | `<T>() => IRequestState<T>` | Get current state |
| `resetState` | `() => void` | Reset state |
| `isLoading` | `() => boolean` | Check loading |
| `onError` | `(callback: ErrorInterceptor) => () => void` | Error handler |
| `setBaseURL` | `(url: string) => void` | Update base URL |
| `setDefaultHeaders` | `(headers: object) => void` | Set headers |
| `setTimeout` | `(timeout: number) => void` | Set timeout |
| `addResource` | `<T>(name: string, ResourceClass: T) => InstanceType<T>` | Add custom resource |

### React Hook Return

| Property | Type | Description |
|----------|------|-------------|
| `client` | `FormsEasySDK \| null` | SDK instance |
| `state` | `IRequestState<T>` | Full state |
| `isLoading` | `boolean` | Loading state |
| `error` | `IApiError \| null` | Error object |
| `data` | `T \| null` | Response data |
| `uploadProgress` | `number` | Upload % (0-100) |
| `isUploading` | `boolean` | Uploading state |
| `files` | `GalleryFile[]` | Uploaded files |

---

## Quick Start Examples

### Vanilla JS
```javascript
import FormsEasySDK from '@hnndes-ou/forms-easy-sdk';

const client = new FormsEasySDK({
  authToken: 'your-key',
  modelName: 'posts'
});

// CRUD
const posts = await client.getAll();
const post = await client.create({ title: 'Hello' });
await client.update(post.data._id, { title: 'World' });
await client.delete(post.data._id);

// Upload
const file = document.getElementById('file').files[0];
await client.uploadFile(file, (p) => console.log(`${p}%`));
```

### React
```tsx
import { useFormsEasy } from '@hnndes-ou/forms-easy-sdk/react';

function App() {
  const { data, isLoading, create, uploadFile, uploadProgress } = useFormsEasy({
    modelName: 'posts',
    authToken: 'your-key'
  });

  if (isLoading) return <div>Loading...</div>;
  
  return (
    <div>
      {data?.map(p => <div key={p._id}>{p.title}</div>)}
      <input type="file" onChange={e => uploadFile(e.target.files[0])} />
      {uploadProgress > 0 && <progress value={uploadProgress} max={100} />}
    </div>
  );
}
```