SonusLab StorageSonusLab Storage

Errors

Three classes cover every failure path in sonuslab-storage.

Error classes

ParameterTypeDescription
StorageApiErrorserver SDKNon-2xx response from storage-api. Exposes .status (number) and .body (parsed JSON or string).
UploadErrorbrowserNetwork or PUT failure in the browser helper. Exposes .cause with the underlying error.
WebhookVerificationErrorwebhookThrown by verifyWebhook on bad signature, expired timestamp, or malformed payload.

StorageApiError

Every server SDK call rejects with this class on a non-2xx response. Switch on .status to map to your own HTTP responses.

import { StorageApiError } from 'sonuslab-storage/server'

try {
  await storage.upload({ name, contentType, data })
} catch (err) {
  if (err instanceof StorageApiError) {
    if (err.status === 401) // invalid or revoked API key
    if (err.status === 413) // file exceeds maxFileSize
    if (err.status === 404) // file id not found
    if (err.status === 429) // rate limited (see below)
    console.error(err.status, err.body)
  }
  throw err
}

Common statuses

ParameterTypeDescription
400Bad RequestRequest body failed validation. .body has the field-level error.
401UnauthorizedAPI key missing, invalid, or revoked.
404Not FoundFile or upload id does not exist (or belongs to a different app).
413Payload Too Largesize exceeds the app maxFileSize. Use multipart instead.
429Too Many RequestsRate limit hit. Honour the Retry-After header.
5xxServer ErrorTransient storage-api error. Retry with exponential backoff.

WebhookVerificationError

Thrown by verifyWebhook when:

  • The signature header is missing or malformed.
  • The HMAC doesn't match (wrong secret, modified body).
  • The timestamp is older than the tolerance window (default 300s).

Always respond with 401 — do not leak which check failed.

import { WebhookVerificationError } from 'sonuslab-storage/webhook'

try {
  verifyWebhook({ body, signature, secret })
} catch (err) {
  if (err instanceof WebhookVerificationError) {
    throw createError({ statusCode: 401, statusMessage: 'Invalid signature' })
  }
  throw err
}

UploadError

Wraps any failure during the browser upload flow — presign request, the direct PUT, abort, or finalize. The original error is preserved in .cause.

import { UploadError } from 'sonuslab-storage/client'

try {
  await uploadFile({ file, presignEndpoint, completeEndpoint })
} catch (err) {
  if (err instanceof UploadError) {
    console.error('upload failed:', err.message, err.cause)
    toast.error('Could not upload — please try again')
    return
  }
  throw err
}

Patterns

Map server errors to HTTP responses

export default defineEventHandler(async (event) => {
  try {
    return await storage.presign(await readBody(event))
  } catch (err) {
    if (err instanceof StorageApiError) {
      throw createError({ statusCode: err.status, statusMessage: err.body?.message ?? 'Upload error' })
    }
    throw err
  }
})

Retry on 5xx / 429

async function withRetry<T>(fn: () => Promise<T>, attempts = 3): Promise<T> {
  for (let i = 0; i < attempts; i++) {
    try {
      return await fn()
    } catch (err) {
      const retryable = err instanceof StorageApiError && (err.status === 429 || err.status >= 500)
      if (!retryable || i === attempts - 1) throw err
      await new Promise((r) => setTimeout(r, 250 * 2 ** i))
    }
  }
  throw new Error('unreachable')
}

const file = await withRetry(() => storage.upload({ name, contentType, data }))