KKiotaPay UI
Browse documentation

Storage adapter

S3 File Uploader, S3 File Viewer and View S3 Attachment all need three calls: sign an upload, resolve a key into a readable URL, and delete an object. They do not know how your back end spells those, so you supply them once through a StorageAdapter.

Installation

pnpm dlx shadcn@latest add https://kiotapay-ui-staging.staging.kiotapay.com/r/kiota-storage.json

Installed automatically with any of the three components — you rarely add it by hand.

Wiring it up

'use client'

import { StorageProvider } from '@/lib/kiota-storage'
import { SignFile, GetFile, DeleteFile } from '@/lib/api/uploads-endpoints'

export function Providers({ children }: { children: React.ReactNode }) {
  return (
    <StorageProvider adapter={{ signFile: SignFile, getFile: GetFile, deleteFile: DeleteFile }}>
      {children}
    </StorageProvider>
  )
}

That is usually the whole integration. Existing API clients drop in unchanged because the adapter methods are typed to return unknown and the payload is unwrapped for you.

The contract

interface StorageAdapter {
  signFile(request: { fileName: string; contentType?: string; filePath?: string }): Promise<unknown>
  getFile(fileKey: string): Promise<unknown>
  deleteFile(fileKey: string): Promise<unknown>
  proxyUploadEndpoint?: string
}

signFile should resolve to something containing a presigned upload URL and the object key. getFile should resolve to something containing a readable URL, optionally with a content type.

Why unknown

Back ends wrap payloads differently, and pinning one shape would mean writing a shim per project. The resolvers peel { data } envelopes until they reach a level carrying a url or key, and read the first of several conventional names:

ResolverReads
resolveSignedUploadurl, putUrl, presignedUrl, uploadUrl — and key, fileKey, keyName, name
resolveFileurl, getUrl, signedUrl, signed_url, downloadUrl — and contentType, mimeType, content_type

A bare string is treated as the URL. If none of that fits, wrap your client in four lines and return whatever shape you like — the resolvers only need one of the names above.

Direct versus proxy uploads

S3FileUploader takes an uploadMode:

Start with direct. Reach for proxy when uploads fail with a CORS error and the bucket policy is not yours to change.

Per-instance overrides

Every storage component takes an adapter prop that wins over the provider. Useful when one screen reads from a different bucket, or in tests.

<S3FileViewer url={key} adapter={publicBucketAdapter} />

Missing adapter

With neither a provider nor a prop, these components throw at mount with a message naming both fixes. That is deliberate: a silently dead uploader looks like a network fault and costs far more to track down than an error that says what is missing.