storage-imagekit
| Package | @theoven/storage-imagekit |
| Adds to context | ctx.storage — it is a driver for storage |
| Endpoints | none |
| Creates files | whatever you upload, in your media library |
| Creates tables | none |
| Status | shipped |
Install
Section titled “Install”bun add @theoven/storage @theoven/storage-imagekitimport { storage } from '@theoven/storage'import { imagekitStorage } from '@theoven/storage-imagekit'
const media = imagekitStorage({ privateKey: env.string('IMAGEKIT_PRIVATE_KEY'), urlEndpoint: env.string('IMAGEKIT_URL_ENDPOINT'),})
export const app = createApp().use(storage(media))What it does
Section titled “What it does”Puts ImageKit behind the storage contract, so ctx.storage works exactly as it does on S3 or
disk. On top of that the driver exposes url(), which builds ImageKit’s transformation URLs —
resize, crop, reformat on delivery.
Endpoints
Section titled “Endpoints”This brick adds no endpoints.
Uploading is ctx.storage.upload() — the same call as every other driver:
import { z } from 'zod'import { media } from '../app'
export const auth = trueexport const body = z.object({ file: z.file() })
export default async ({ body, storage, user }) => { const key = `avatars/${user.id}.png` await storage.upload(key, body.file)
return { url: media.url(key, { transform: { width: 200, height: 200, format: 'auto' } }) }}Upload the original once, then serve whatever size you need from the URL — that is the pattern ImageKit exists for, and why you do not store thumbnails yourself.
await storage.upload('photos/a.png', bytes, { type: 'image/png' })
const blob = storage.download('photos/a.png') // lazy — no request until readawait storage.exists('photos/a.png')await storage.stat('photos/a.png')await storage.list({ prefix: 'photos/', limit: 50 })await storage.delete('photos/a.png')Full detail on each is on the storage page; they behave the same here.
What it creates
Section titled “What it creates”Nothing in your repository. Files go to your ImageKit media library.
Transformations
Section titled “Transformations”The reason to use ImageKit rather than plain object storage:
media.url('avatars/1.png', { transform: { width: 200, height: 200, format: 'auto' } })// https://ik.imagekit.io/demo/avatars/1.png?tr=w-200,h-200,f-autoformat: 'auto' lets ImageKit serve webp or avif per browser, which is most of the win.
Deliberately on the driver, not on ctx.storage: resizing an image is not something a storage
contract should have an opinion about, and inventing one would mean every other driver pretending
it could crop.
Signed URLs
Section titled “Signed URLs”media.url('private/report.png', { transform: { width: 800 }, expiresIn: 300 })The signature covers the transformation as well as the path — so a client cannot edit tr= to
request a 10 000px render off a signed URL, which matters on a product billed per transformation.
Configuration
Section titled “Configuration”| Option | Default | |
|---|---|---|
privateKey |
— | required; never send it to a browser |
urlEndpoint |
— | required, e.g. https://ik.imagekit.io/your_id |
Uploads set useUniqueFileName: false, because ImageKit otherwise appends a random suffix — and
then the key you asked for is not the key you get, which silently breaks every other method in the
contract.
Capabilities
Section titled “Capabilities”upload, download, delete, exists, stat, list |
✓ |
media.url() with transformations and signing |
✓ |
presignDownload / presignUpload |
✗ — use media.url({ expiresIn }) for signed delivery |
directUpload |
✗ — depends on presignUpload |
Multiple buckets via storage.bucket() |
✗ — one media library per driver |
Limitations
Section titled “Limitations”- No browser-direct uploads. ImageKit’s browser upload uses a token/signature triple posted to
their endpoint, not a
PUT-able URL, sopresignUploadis unavailable rather than misleading. Server-sideupload()is unaffected — send the file to a route, as in Usage. - An extra lookup on
remove,existsandstat— see above. - Paging is offset-based, so
nextis a count rather than a key. - Uploads are buffered, since the upload API is multipart.
How it is verified
Section titled “How it is verified”URL building, transformation syntax and signing are deterministic and fully tested — including that changing a transformation changes the signature. Request shapes and the fileId lookup are tested against an injected fetcher.
That ImageKit accepts those requests is not verified: there is no account in CI.