Skip to content

Svelte Integration

Torrin works beautifully with Svelte's reactive system. This guide shows you how to build efficient upload components with progress tracking, pause/resume, and error handling.

Installation

bash
npm install @torrin-kit/client
bash
yarn add @torrin-kit/client
bash
pnpm add @torrin-kit/client
bash
bun add @torrin-kit/client

Basic Example

A simple file uploader with progress tracking using Svelte's reactivity:

svelte
<script lang="ts">
  import { createTorrinClient } from '@torrin-kit/client';
  
  const torrin = createTorrinClient({
    endpoint: 'http://localhost:3000/api/uploads',
  });

  let progress = 0;
  let status = 'idle';

  async function handleFileChange(event: Event) {
    const input = event.target as HTMLInputElement;
    const file = input.files?.[0];
    if (!file) return;

    const upload = torrin.createUpload({ file });

    upload.on('progress', (p) => {
      progress = p.percentage;
    });

    upload.on('status', (s) => {
      status = s;
    });

    try {
      const result = await upload.start();
      console.log('Upload complete:', result.location);
    } catch (error) {
      console.error('Upload failed:', error);
    }
  }
</script>

<div class="space-y-4">
  <input 
    type="file" 
    on:change={handleFileChange}
    class="block w-full text-sm"
  />

  {#if status !== 'idle'}
    <div class="space-y-2">
      <div class="w-full bg-gray-200 rounded-full h-2.5">
        <div 
          class="bg-orange-600 h-2.5 rounded-full transition-all" 
          style="width: {progress}%"
        />
      </div>
      <div class="flex justify-between text-sm">
        <span>{status}</span>
        <span>{progress.toFixed(1)}%</span>
      </div>
    </div>
  {/if}
</div>

Advanced Example

A complete uploader with pause, resume, and cancel functionality:

svelte
<script lang="ts">
  import { createTorrinClient, type TorrinUpload } from '@torrin-kit/client';
  
  const torrin = createTorrinClient({
    endpoint: 'http://localhost:3000/api/uploads',
  });

  let progress = 0;
  let status: string = 'idle';
  let error: string | null = null;
  let upload: TorrinUpload | null = null;

  async function handleFileChange(event: Event) {
    const input = event.target as HTMLInputElement;
    const file = input.files?.[0];
    if (!file) return;

    error = null;
    progress = 0;

    upload = torrin.createUpload({ 
      file,
      metadata: { 
        userId: 'user123',
        timestamp: new Date().toISOString() 
      }
    });

    upload.on('progress', (p) => {
      progress = p.percentage;
      console.log(`${p.chunksCompleted}/${p.totalChunks} chunks uploaded`);
    });

    upload.on('status', (s) => {
      status = s;
    });

    upload.on('error', (err) => {
      error = err.message;
    });

    try {
      const result = await upload.start();
      console.log('Upload complete:', result);
      upload = null;
    } catch (err: any) {
      error = err.message;
    }
  }

  function handlePause() {
    upload?.pause();
  }

  function handleResume() {
    upload?.resume();
  }

  async function handleCancel() {
    await upload?.cancel();
    upload = null;
    progress = 0;
    status = 'idle';
  }
</script>

<div class="max-w-md p-6 bg-white rounded-lg shadow">
  <h2 class="text-xl font-bold mb-4">Upload File</h2>
  
  <input 
    type="file" 
    on:change={handleFileChange}
    disabled={status === 'uploading'}
    class="block w-full mb-4"
  />

  {#if error}
    <div class="mb-4 p-3 bg-red-50 border border-red-200 rounded">
      <p class="text-sm text-red-600">{error}</p>
    </div>
  {/if}

  {#if status !== 'idle'}
    <div class="space-y-4">
      <div>
        <div class="flex justify-between text-sm mb-1">
          <span class="font-medium">Status: {status}</span>
          <span>{progress.toFixed(1)}%</span>
        </div>
        <div class="w-full bg-gray-200 rounded-full h-3">
          <div 
            class="bg-orange-600 h-3 rounded-full transition-all duration-300" 
            style="width: {progress}%"
          />
        </div>
      </div>

      <div class="flex gap-2">
        {#if status === 'uploading'}
          <button
            on:click={handlePause}
            class="px-4 py-2 bg-yellow-500 text-white rounded hover:bg-yellow-600"
          >
            Pause
          </button>
        {/if}
        
        {#if status === 'paused'}
          <button
            on:click={handleResume}
            class="px-4 py-2 bg-orange-500 text-white rounded hover:bg-orange-600"
          >
            Resume
          </button>
        {/if}
        
        {#if status === 'uploading' || status === 'paused'}
          <button
            on:click={handleCancel}
            class="px-4 py-2 bg-red-500 text-white rounded hover:bg-red-600"
          >
            Cancel
          </button>
        {/if}
      </div>
    </div>
  {/if}
</div>

Svelte Store

Create a reusable store for upload management:

typescript
// stores/torrinStore.ts
import { writable, get } from 'svelte/store';
import { createTorrinClient, type TorrinUpload } from '@torrin-kit/client';

const torrin = createTorrinClient({
  endpoint: 'http://localhost:3000/api/uploads',
});

interface UploadState {
  progress: number;
  status: string;
  error: string | null;
  upload: TorrinUpload | null;
}

function createTorrinStore() {
  const { subscribe, set, update } = writable<UploadState>({
    progress: 0,
    status: 'idle',
    error: null,
    upload: null,
  });

  return {
    subscribe,
    
    async startUpload(file: File, metadata?: Record<string, any>) {
      const upload = torrin.createUpload({ file, metadata });
      
      update(state => ({
        ...state,
        error: null,
        progress: 0,
        upload,
      }));

      upload.on('progress', (p) => {
        update(state => ({ ...state, progress: p.percentage }));
      });

      upload.on('status', (s) => {
        update(state => ({ ...state, status: s }));
      });

      upload.on('error', (err) => {
        update(state => ({ ...state, error: err.message }));
      });

      try {
        return await upload.start();
      } catch (err: any) {
        update(state => ({ ...state, error: err.message }));
        throw err;
      }
    },

    pause() {
      const state = get({ subscribe });
      state.upload?.pause();
    },

    resume() {
      const state = get({ subscribe });
      state.upload?.resume();
    },

    async cancel() {
      const state = get({ subscribe });
      await state.upload?.cancel();
      set({
        progress: 0,
        status: 'idle',
        error: null,
        upload: null,
      });
    },
  };
}

export const torrinUpload = createTorrinStore();

Usage:

svelte
<script lang="ts">
  import { torrinUpload } from './stores/torrinStore';

  async function handleFile(e: Event) {
    const input = e.target as HTMLInputElement;
    const file = input.files?.[0];
    if (file) {
      try {
        const result = await torrinUpload.startUpload(file, { userId: '123' });
        console.log('Done:', result);
      } catch (err) {
        console.error('Failed:', err);
      }
    }
  }
</script>

<div>
  <input type="file" on:change={handleFile} />
  <p>Progress: {$torrinUpload.progress}%</p>
  <p>Status: {$torrinUpload.status}</p>
  {#if $torrinUpload.error}
    <p>Error: {$torrinUpload.error}</p>
  {/if}
  <button on:click={() => torrinUpload.pause()}>Pause</button>
  <button on:click={() => torrinUpload.resume()}>Resume</button>
  <button on:click={() => torrinUpload.cancel()}>Cancel</button>
</div>

With Resume Support

Enable automatic resume after page refresh:

typescript
import { createTorrinClient, createLocalStorageResumeStore } from '@torrin-kit/client';

const torrin = createTorrinClient({
  endpoint: 'http://localhost:3000/api/uploads',
  resumeStore: createLocalStorageResumeStore(), // Enable auto-resume
});

Now if the user refreshes the page and selects the same file, the upload will automatically resume from where it left off.

SvelteKit Example

For SvelteKit applications with SSR:

svelte
<script lang="ts">
  import { browser } from '$app/environment';
  import { onMount } from 'svelte';
  import type { TorrinClient } from '@torrin-kit/client';

  let torrin: TorrinClient;
  let progress = 0;
  let status = 'idle';

  onMount(async () => {
    if (browser) {
      const { createTorrinClient } = await import('@torrin-kit/client');
      torrin = createTorrinClient({
        endpoint: '/api/uploads',
      });
    }
  });

  async function handleFileChange(event: Event) {
    if (!torrin) return;
    
    const input = event.target as HTMLInputElement;
    const file = input.files?.[0];
    if (!file) return;

    const upload = torrin.createUpload({ file });

    upload.on('progress', (p) => {
      progress = p.percentage;
    });

    upload.on('status', (s) => {
      status = s;
    });

    try {
      await upload.start();
      console.log('Upload complete');
    } catch (error) {
      console.error(error);
    }
  }
</script>

{#if browser}
  <div>
    <input type="file" on:change={handleFileChange} />
    {#if status !== 'idle'}
      <progress value={progress} max="100" />
      <p>{progress}% - {status}</p>
    {/if}
  </div>
{/if}

TypeScript Support

Define proper types for your upload state:

typescript
import type { TorrinUpload, TorrinProgress } from '@torrin-kit/client';

interface UploadState {
  progress: TorrinProgress | null;
  status: 'idle' | 'uploading' | 'paused' | 'completed' | 'failed';
  error: Error | null;
  upload: TorrinUpload | null;
}

Next Steps