Pushduck
Pushduck// S3 uploads for any framework

createUploadClient

Create a type-safe upload client with property-based access and optional per-route configuration

createUploadClient Function

Create a type-safe upload client with property-based access and optional per-route configuration. This is the recommended approach for most projects.

Enhanced in v2.0: Now supports per-route callbacks, progress tracking, and error handling while maintaining superior type safety.

Why Use This Approach?

  • 🏆 Superior Type Safety - Route names validated at compile time

  • 🎯 Property-Based Access - No string literals, full IntelliSense

  • Per-Route Configuration - Callbacks, endpoints, and options per route

  • 🔄 Centralized Setup - Single configuration for all routes

  • 🛡️ Refactoring Safety - Rename routes safely across codebase

    This utility function provides property-based access to your upload routes. You can also use the useUploadRoute<AppRouter>() hook if you prefer traditional React patterns.

Basic Setup

Create the upload client

lib/upload-client.ts
import { createUploadClient } from 'pushduck/client'
import type { AppRouter } from './upload'

export const upload = createUploadClient<AppRouter>({
  endpoint: '/api/upload'
})

Use in components

components/upload-form.tsx
import { upload } from '@/lib/upload-client'

export function UploadForm() {
  const { uploadFiles, files, isUploading } = upload.imageUpload() 
  
  return (
    <input 
      type="file" 
      onChange={(e) => uploadFiles(Array.from(e.target.files || []))} 
      disabled={isUploading}
    />
  )
}

Configuration Options

Prop

Type

Per-Route Configuration

Each route method now accepts optional configuration:

Prop

Type

Examples

import { upload } from '@/lib/upload-client'

export function BasicUpload() {
  // Simple usage - no configuration needed
  const { uploadFiles, files, isUploading, reset } = upload.imageUpload()

  return (
    <div>
      <input 
        type="file"
        accept="image/*"
        onChange={(e) => uploadFiles(Array.from(e.target.files || []))}
        disabled={isUploading}
      />
      
      {files.map(file => (
        <div key={file.id}>
          <span>{file.name}</span>
          <progress value={file.progress} max={100} />
          {file.status === 'success' && <span>✅</span>}
        </div>
      ))}
      
      <button onClick={reset}>Reset</button>
    </div>
  )
}

Type Safety Benefits

The structured client provides superior TypeScript integration:

const upload = createUploadClient<AppRouter>({ endpoint: '/api/upload' })

// ✅ IntelliSense shows available routes
upload.imageUpload()      // Autocomplete suggests this
upload.documentUpload()   // And this
upload.videoUpload()      // And this

// ❌ TypeScript error for non-existent routes
upload.invalidRoute()     // Error: Property 'invalidRoute' does not exist

// ✅ Route rename safety
// If you rename 'imageUpload' to 'photoUpload' in your router,
// TypeScript will show errors everywhere it's used, making refactoring safe

// ✅ Callback type inference
upload.imageUpload({
  onSuccess: (results) => {
    // `results` is fully typed based on your router configuration
    results.forEach(result => {
      console.log(result.url)  // TypeScript knows this exists
      console.log(result.key)  // And this
    })
  }
})

Comparison with Hooks

FeatureEnhanced Structured ClientHook-Based
Type SafetySuperior - Property-based✅ Good - Generic types
IntelliSenseFull route autocomplete⚠️ String-based routes
RefactoringSafe rename across codebase⚠️ Manual find/replace
CallbacksFull support✅ Full support
Per-route ConfigFull support✅ Full support
Bundle SizeSame✅ Same
PerformanceIdentical✅ Identical

Migration from Hooks

Easy migration from hook-based approach:

// Before: Hook-based
import { useUploadRoute } from 'pushduck/client'

const { uploadFiles, files } = useUploadRoute<AppRouter>('imageUpload', {
  onSuccess: handleSuccess,
  onError: handleError
})

// After: Enhanced structured client
import { upload } from '@/lib/upload-client'

const { uploadFiles, files } = upload.imageUpload({
  onSuccess: handleSuccess,
  onError: handleError
})

Benefits of migration:

  • 🎯 Better type safety - Route names validated at compile time
  • 🔍 Enhanced IntelliSense - Autocomplete for all routes
  • 🏗️ Centralized config - Single place for endpoint and defaults
  • 🛡️ Refactoring safety - Rename routes safely
  • Same performance - Zero runtime overhead

Recommended Approach: Use createUploadClient for the best developer experience with full flexibility and type safety.