npm package: @gofranz/formshive-submit
Description: JavaScript library for submitting forms to Formshive with retry logic, file support, and flexible HTTP client options
Author: Franz Geffke
Latest version: 1.0.4

# Formshive Submit

A robust JavaScript/TypeScript library for submitting forms to Formshive with advanced features including retry logic, file uploads, progress tracking, and flexible HTTP client support.

## Features

- 🚀 **Easy to use** - Simple API for form submissions
- 🔄 **Smart retry logic** - Exponential backoff with configurable settings
- 📁 **File upload support** - Handle single and multiple file uploads with progress tracking
- 🔌 **Flexible HTTP clients** - Works with both fetch and axios
- 💪 **TypeScript support** - Fully typed for better development experience
- 🎯 **Callback system** - Success, error, retry, and progress callbacks
- 🔧 **Configurable** - Extensive configuration options for different use cases
- 🌐 **Universal** - Works in browser and Node.js environments
- ⚡ **Lightweight** - Minimal dependencies, tree-shakeable

## Installation

```bash
npm install @gofranz/formshive-submit
# or
yarn add @gofranz/formshive-submit
# or
pnpm add @gofranz/formshive-submit
```

For browser usage via CDN:

```html
<script src="https://unpkg.com/@gofranz/formshive-submit/dist/formshive-submit.min.js"></script>
```

## Quick Start

### Basic Form Submission

```javascript
import { submitFormSimple } from '@gofranz/formshive-submit';

// Simple form submission
const response = await submitFormSimple('your-form-id', {
    name: 'John Doe',
    email: 'john@example.com',
    message: 'Hello from Formshive Submit!'
});

console.log('Form submitted successfully:', response);
```

### With Error Handling

```javascript
import { submitForm } from '@gofranz/formshive-submit';

try {
    const response = await submitForm({
        formId: 'your-form-id',
        data: {
            name: 'Jane Smith',
            email: 'jane@example.com',
            subject: 'Contact Form',
            message: 'This is a test message.'
        },
        callbacks: {
            onSuccess: (response) => {
                console.log('Success!', response);
            },
            onError: (error) => {
                console.error('Submission failed:', error);
            },
            onRetry: (attempt, maxAttempts) => {
                console.log(`Retrying... ${attempt}/${maxAttempts}`);
            }
        }
    });
} catch (error) {
    console.error('Final error:', error);
}
```

## File Upload

### Single File Upload

```javascript
import { submitForm } from '@gofranz/formshive-submit';

const fileInput = document.getElementById('fileInput');
const formData = new FormData();

formData.append('name', 'John Doe');
formData.append('file', fileInput.files[0]);

const response = await submitForm({
    formId: 'file-upload-form',
    data: formData,
    callbacks: {
        onProgress: (percent, loaded, total) => {
            console.log(`Upload progress: ${percent}%`);
            document.getElementById('progress').style.width = percent + '%';
        }
    }
});
```

### Multiple Files with Validation

```javascript
import { submitForm } from '@gofranz/formshive-submit';

const files = document.getElementById('multipleFiles').files;
const formData = new FormData();

formData.append('title', 'Multiple File Upload');
for (let i = 0; i < files.length; i++) {
    formData.append(`file_${i}`, files[i]);
}

const response = await submitForm({
    formId: 'multi-file-form',
    data: formData,
    files: {
        maxFileSize: 10 * 1024 * 1024, // 10MB per file
        allowedTypes: ['image/jpeg', 'image/png', 'application/pdf'],
        trackProgress: true
    },
    callbacks: {
        onProgress: (percent) => updateProgressBar(percent)
    }
});
```

## Configuration Options

### Retry Configuration

```javascript
import { submitForm, RetryPresets } from '@gofranz/formshive-submit';

const response = await submitForm({
    formId: 'your-form-id',
    data: { message: 'Hello' },
    retry: {
        maxAttempts: 5,
        baseDelay: 2000,      // 2 seconds
        maxDelay: 30000,      // 30 seconds max
        enableJitter: true,   // Add randomness to delays
        backoffMultiplier: 2  // Exponential backoff factor
    }
});

// Or use presets
const response2 = await submitForm({
    formId: 'your-form-id',
    data: { message: 'Hello' },
    retry: RetryPresets.patient() // More retries, longer delays
});
```

### Using Axios Instead of Fetch

```javascript
import axios from 'axios';
import { submitForm } from '@gofranz/formshive-submit';

// Option 1: Use string identifier
const response = await submitForm({
    formId: 'your-form-id',
    data: { message: 'Hello' },
    httpClient: 'axios' // Requires axios to be installed
});

// Option 2: Use custom axios instance
const customAxios = axios.create({
    timeout: 10000,
    headers: {
        'Custom-Header': 'value'
    }
});

const response2 = await submitForm({
    formId: 'your-form-id',
    data: { message: 'Hello' },
    httpClient: customAxios
});
```

### Custom Endpoint and Headers

```javascript
const response = await submitForm({
    formId: 'your-form-id',
    data: { message: 'Hello' },
    endpoint: 'https://your-custom-domain.com/api/v1',
    headers: {
        'Authorization': 'Bearer your-token',
        'Custom-Header': 'custom-value'
    },
    timeout: 15000 // 15 seconds
});
```

## Advanced Usage

### Form Validation

```javascript
import { validateFormData, submitForm } from '@gofranz/formshive-submit';

const data = {
    name: '',
    email: 'invalid-email',
    message: 'Hello'
};

const validation = validateFormData(data);
if (!validation.isValid) {
    console.log('Validation errors:', validation.errors);
    console.log('Warnings:', validation.warnings);
    return;
}

const response = await submitForm({
    formId: 'validated-form',
    data: data
});
```

### Reusable Form Submitter

```javascript
import { FormSubmitter } from '@gofranz/formshive-submit';

// Create a submitter with default configuration
const submitter = new FormSubmitter({
    endpoint: 'https://api.myapp.com/v1',
    retry: {
        maxAttempts: 5,
        baseDelay: 1000
    },
    debug: true
});

// Submit multiple forms using the same configuration
const response1 = await submitter.submit('contact-form', {
    name: 'John',
    email: 'john@example.com'
});

const response2 = await submitter.submit('feedback-form', {
    rating: 5,
    comment: 'Great service!'
});

// Test connection
const isConnected = await submitter.testConnection();
console.log('API is reachable:', isConnected);
```

### Progress Tracking

```javascript
import { submitForm, formatFileSize } from '@gofranz/formshive-submit';

const response = await submitForm({
    formId: 'upload-form',
    data: formData,
    callbacks: {
        onStart: () => {
            console.log('Upload starting...');
            showProgressBar(true);
        },
        onProgress: (percent, loaded, total) => {
            const loadedSize = formatFileSize(loaded);
            const totalSize = formatFileSize(total);
            console.log(`${percent}% - ${loadedSize}/${totalSize}`);
            updateProgress(percent);
        },
        onSuccess: (response) => {
            console.log('Upload complete!');
            hideProgressBar();
        },
        onError: (error) => {
            console.error('Upload failed:', error);
            showError(error.message);
        }
    }
});
```

## Browser Usage (CDN)

```html
<!DOCTYPE html>
<html>
<head>
    <title>Formshive Submit Example</title>
</head>
<body>
    <form id="myForm">
        <input type="text" name="name" placeholder="Name" required>
        <input type="email" name="email" placeholder="Email" required>
        <textarea name="message" placeholder="Message"></textarea>
        <input type="file" name="file" multiple>
        <button type="submit">Submit</button>
    </form>

    <script src="https://unpkg.com/@gofranz/formshive-submit/dist/formshive-submit.min.js"></script>
    <script>
        document.getElementById('myForm').addEventListener('submit', async (e) => {
            e.preventDefault();
            
            const formData = new FormData(e.target);
            
            try {
                const response = await FormshiveSubmit.submitForm({
                    formId: 'my-form-id',
                    data: formData,
                    callbacks: {
                        onSuccess: (response) => {
                            alert('Form submitted successfully!');
                        },
                        onError: (error) => {
                            alert('Error: ' + error.message);
                        }
                    }
                });
            } catch (error) {
                console.error('Submission error:', error);
            }
        });
    </script>
</body>
</html>
```

## API Reference

### Functions

#### `submitForm(options: FormshiveSubmitOptions): Promise<SubmitResponse>`

Main form submission function with full configuration options.

#### `submitFormSimple(formId: string, data: Record<string, any> | FormData, options?: Partial<FormshiveSubmitOptions>): Promise<SubmitResponse>`

Simplified submission function for basic use cases.

#### `validateFormData(data: Record<string, any> | FormData): ValidationResult`

Validates form data before submission.

### Classes

#### `FormSubmitter`

Reusable form submitter with configurable defaults.

```javascript
const submitter = new FormSubmitter(defaultOptions);
await submitter.submit(formId, data, overrideOptions);
```

### Types

#### `FormshiveSubmitOptions`

```typescript
interface FormshiveSubmitOptions {
    formId: string;                    // Required: Formshive form ID
    data: Record<string, any> | FormData; // Required: Form data
    endpoint?: string;                 // API endpoint (default: api.formshive.com)
    httpClient?: HttpClient;           // 'fetch', 'axios', or axios instance
    retry?: RetryConfig;               // Retry configuration
    files?: FileConfig;                // File upload configuration  
    callbacks?: FormshiveCallbacks;    // Success/error/progress callbacks
    headers?: Record<string, string>;  // Additional HTTP headers
    timeout?: number;                  // Request timeout in milliseconds
    debug?: boolean;                   // Enable debug logging
}
```

#### `SubmitResponse`

```typescript
interface SubmitResponse {
    success: boolean;
    data?: any;
    statusCode: number;
    headers?: Record<string, string>;
    redirectUrl?: string;
    attempt: number;
    duration: number;
}
```

#### `SubmitError`

```typescript
interface SubmitError extends Error {
    code: string;
    statusCode?: number;
    response?: any;
    attempt: number;
    isRetryable: boolean;
    originalError?: Error;
    fieldErrors?: FieldValidationError[];
    validationResponse?: FieldValidationErrorResponse;
}
```

## Error Handling

The library provides detailed error information with specific error codes:

- `NETWORK_ERROR` - Network connectivity issues
- `TIMEOUT_ERROR` - Request timeout
- `VALIDATION_ERROR` - Form validation failed
- `FILE_TOO_LARGE` - File exceeds size limit
- `INVALID_FILE_TYPE` - File type not allowed
- `FORM_NOT_FOUND` - Form ID not found
- `SERVER_ERROR` - Server-side error (5xx)
- `RATE_LIMITED` - Too many requests (429)

```javascript
try {
    const response = await submitForm(options);
} catch (error) {
    switch (error.code) {
        case 'FORM_NOT_FOUND':
            console.error('Form not found. Check your form ID.');
            break;
        case 'FILE_TOO_LARGE':
            console.error('File is too large. Please choose a smaller file.');
            break;
        case 'NETWORK_ERROR':
            console.error('Network error. Please check your connection.');
            break;
        default:
            console.error('Unexpected error:', error.message);
    }
}
```

### Field Validation Errors

When form validation fails (400 Bad Request), Formshive returns structured field-level validation errors that you can use to display specific error messages for each form field.

#### Checking for Field Validation Errors

```javascript
import { submitForm, isFieldValidationError, getFieldErrors } from '@gofranz/formshive-submit';

try {
    const response = await submitForm({
        formId: 'contact-form',
        data: {
            name: '',  // Missing required field
            email: 'invalid-email',  // Invalid format
            message: 'Hello'
        }
    });
} catch (error) {
    if (isFieldValidationError(error)) {
        console.log('Form has validation errors');
        const fieldErrors = getFieldErrors(error);
        
        fieldErrors.forEach(fieldError => {
            console.log(`Field: ${fieldError.field}`);
            console.log(`Error: ${fieldError.message}`);
            console.log(`Code: ${fieldError.code}`);
        });
    } else {
        console.error('Non-validation error:', error.message);
    }
}
```

#### Using Field Error Helpers for UI

The library provides helper functions to make it easy to display field errors in your forms:

```javascript
import { createFieldErrorHelpers, submitForm } from '@gofranz/formshive-submit';

let currentError = null;

// Create helpers for easy UI integration
const errorHelpers = createFieldErrorHelpers(currentError);

// Check if a field has an error
if (errorHelpers.hasError('email')) {
    document.getElementById('email').classList.add('input-error');
}

// Get error message for a field
const emailErrorMsg = errorHelpers.getMessage('email');
if (emailErrorMsg) {
    document.getElementById('email-error').textContent = emailErrorMsg;
}

// Get CSS class for field error state
const fieldClass = errorHelpers.getFieldClass('email', 'has-error');
document.getElementById('email').className = fieldClass;
```

#### Complete Field Validation Example

```javascript
import { 
    submitForm, 
    isFieldValidationError, 
    createFieldErrorHelpers, 
    getValidationErrorSummary 
} from '@gofranz/formshive-submit';

async function handleFormSubmit(formData) {
    let currentError = null;
    
    try {
        const response = await submitForm({
            formId: 'registration-form',
            data: formData
        });
        
        // Success - clear any previous errors
        clearFieldErrors();
        showSuccessMessage();
        
    } catch (error) {
        currentError = error;
        
        if (isFieldValidationError(error)) {
            // Handle field-specific validation errors
            displayFieldErrors(error);
            
            // Show general validation summary
            const summary = getValidationErrorSummary(error);
            showErrorMessage(summary);
            
        } else {
            // Handle other types of errors
            showErrorMessage(error.message || 'An unexpected error occurred');
        }
    }
}

function displayFieldErrors(error) {
    const helpers = createFieldErrorHelpers(error, 'field-error');
    const fieldNames = ['name', 'email', 'phone', 'message'];
    
    fieldNames.forEach(fieldName => {
        const input = document.getElementById(fieldName);
        const errorDiv = document.getElementById(`${fieldName}-error`);
        
        if (helpers.hasError(fieldName)) {
            // Add error styling
            input.classList.add('field-error');
            
            // Show error message
            errorDiv.textContent = helpers.getMessage(fieldName);
            errorDiv.style.display = 'block';
        } else {
            // Clear error state
            input.classList.remove('field-error');
            errorDiv.style.display = 'none';
        }
    });
}
```

#### Field Validation Error Types

```typescript
interface FieldValidationError {
    field: string;                    // Field name (e.g., 'email')
    code: string;                     // Error code (e.g., 'required', 'invalid_format')
    message: string;                  // User-friendly error message
    params: Record<string, any>;      // Additional error parameters
}

interface FieldValidationErrorResponse {
    error: string;                    // Always 'validation_error'
    action?: string;                  // Optional action hint
    message: string;                  // General validation error message
    errors: FieldValidationError[];   // Array of field-specific errors
}
```

#### Available Field Error Utility Functions

The library exports many utility functions for working with field validation errors:

- `isFieldValidationError(error)` - Check if error contains field validation errors
- `getFieldErrors(error)` - Get array of all field validation errors
- `getFieldError(error, fieldName)` - Get validation error for specific field
- `hasFieldError(error, fieldName)` - Check if specific field has error
- `getErrorFieldNames(error)` - Get names of all fields with errors
- `formatFieldErrors(error)` - Get simple object mapping field names to error messages
- `createFieldErrorHelpers(error)` - Create helper object for UI integration
- `getValidationErrorSummary(error)` - Get human-readable error summary
- `hasErrorCode(error, code)` - Check if any field has specific error code
- `getErrorsByCode(error, code)` - Get all errors with specific code

## Contributing

Contributions are welcome! Please read our contributing guidelines and submit pull requests for any improvements.

## License

MIT License - see LICENSE file for details.

## Support

For issues and questions, please visit our GitHub repository or contact support at support@formshive.com.

---

**Formshive Submit** - Making form submissions robust and reliable. ✨