bunderstackbunderstack/ docs

API Reference

Current public surface of Bunderstack and its client packages

This page summarizes the stable public concepts. TypeScript remains the exact reference for generic details and adapter-specific types.

bunderstack(options)

function bunderstack<
  TSchema,
  TAccess,
  TStorage,
  TEnv,
  TJobs,
  TApi,
>(options: BunderstackConfig<...>): BunderstackBackend<BunderstackApp<...>>

Required options:

  • schema: a record containing the application's Drizzle tables;
  • database.adapter: one statically imported database adapter.

Major optional groups:

  • access: generated CRUD exposure, ownership, filters, sorting, and guards;
  • auth / authResolver: Better Auth and custom session resolution;
  • storage, email, env, and jobs: application facilities;
  • api: your oRPC router, as an object or as (o) => router;
  • middleware: oRPC middleware applied to every procedure in the graph;
  • realtime: memory or Redis Publisher configuration;
  • rateLimit, idempotency, background, and openapi.

See Configuration for examples and defaults.

bunderstack() only declares the backend. backend.manifest is available synchronously for Blueprint generation. Call await backend.start({ env }) to materialize a production runtime, or await backend.test() to create an isolated test fixture owned by the current lexical scope.

BunderstackApp

type BunderstackApp = {
  handler(request: Request): Promise<Response>
  db: DbFor<TSchema>
  auth: AuthInstance
  storage: StorageFacade
  email: EmailFacade
  env: ValidatedEnv<TEnv>
  jobs: JobsFacade<TJobs>
  realtime: RealtimeFacade<TSchema>
  startWorker(options?): Promise<WorkerHandle>
  runWorker(options?): Promise<void>
  close(): Promise<void>
  readonly status: LifecycleStatus
  readonly signal: AbortSignal
  readonly backgroundRunning: boolean
  readonly $inferClient?: ClientTypeCarrier
}

$inferClient is type-only and does not exist at runtime. Export type App = typeof app for client inference.

API builder

const o = defineApi({ schema, env: envSchema })

o.public // no session resolution
o.protected // resolves the session, narrows context.user
o.webhook // public, preserves the exact raw body
o.middleware(fn) // a standalone middleware over ApiContext

defineApi infers TSchema and TEnv from the values it receives, so an application never writes BunderstackApiBuilder<…> by hand. It reads nothing at runtime and can be called at module scope. createApiBuilder<TSchema, TEnv>() remains available when you want to pass the generics explicitly.

The three bases are oRPC procedure builders with the shared Bunderstack error contract and ApiContext:

type ApiContext = {
  db: DbFor<TSchema>
  env: ValidatedEnv<TEnv>
  storage: StorageFacade
  email: EmailFacade
  jobs: JobsRuntimeFacade
  realtime: RealtimeFacade<TSchema>
  auth: AuthInstance
  request: Request
  resHeaders: Headers
  getRawBody(): Promise<string>
  getSession(): Promise<{
    user: AccessUser | null
    activeOrganizationId: string | null
  }>
  /** The already-resolved session, or undefined. Never starts a resolution. */
  peekSession():
    | { user: AccessUser | null; activeOrganizationId: string | null }
    | undefined
}

o.protected adds non-null context.user and the active organization session to handlers. Inputs and optional outputs accept Standard Schema.

peekSession() exists for graph-wide middleware, which runs before authentication. Use it for observability only, never for authorization — see Middleware.

Helper exports

ExportPurpose
defineApi({ schema, env })The procedure builder, with generics inferred from values
listSpec(table, options)Input schema and handler for a list endpoint outside CRUD
BunderstackErrorTyped error for code outside a handler
BunderstackDb<TSchema>The database type for a helper parameter
BunderstackTx<TSchema>The transaction handle inside db.transaction

Database and provisioning

Import one of libsql(), pglite(), bunSql(), or postgresJs() from its bunderstack/* subpath. The schema dialect and adapter dialect must match.

import { provision } from 'bunderstack/provision'

await provision(app, { force: false })

Without a migration journal, provisioning uses the development schema push. With committed migrations, it applies pending migrations.

bunderstack/client

import { createClient, createLiveView } from 'bunderstack/client'
import { createRestClient } from 'bunderstack/client-rest'
import { useLiveView as useReactLiveView } from 'bunderstack/client-react'
import { createLiveStore } from 'bunderstack/client-solid'
import { liveStore } from 'bunderstack/client-svelte'
import { useLiveView as useVueLiveView } from 'bunderstack/client-vue'

A zero-dependency typed RPC client and confirmed realtime LiveView with reactive store adapters for Solid, React, Svelte, and Vue. Use it for lightweight clients, mobile/native applications, or when you don't need TanStack Query.

bunderstack/query

function createClient<TApp>(options?: {
  baseUrl?: string
  fetch?: TransportFetch
  queryClient?: QueryClient
}): BunderstackClient<TApp>

The result contains the complete oRPC router utilities and file helpers:

api.posts.list.call(input)
api.posts.list.queryOptions({ input })
api.posts.create.mutationOptions(options)
api.customProcedure.call(input)
api.files.images.upload(file)
api.files.images.url(id, transforms)

Other exports include createApiClient, syncRealtime, InferSchema, InferSelect, InferInsert, and the list input helpers.

bunderstack/sync

function createSyncClient<TApp>(options: {
  queryClient: QueryClient
  baseUrl?: string
  fetch?: TransportFetch
  realtime?: boolean
}): BunderstackSyncClient<TApp>

Each generated table exposes collection, table, scopedCollection(options), and collectionByIds(ids). The client starts its typed realtime iterator in the browser by default and keeps it disabled during SSR.

bunderstack/start

createApiHandlers(app)
createIsomorphicFetch(options?)
getSessionUser(app, request)
createStartAuthClient(options?)
bunderstackStart<TApp>(options?)

These adapters mount app.handler, resolve relative API URLs during SSR, and create app-inferred clients without changing the server graph.

RealtimeFacade

interface RealtimeFacade<TSchema> {
  readonly enabled: boolean
  readonly transport: 'disabled' | 'memory' | 'redis'
  publish<TTable extends SchemaTable<TSchema>>(
    table: TTable,
    action: 'create' | 'update' | 'delete',
    record: InferSelectModel<TTable>,
  ): Promise<void>
}

Generated writes publish automatically. Call publish() only for custom writes performed through context.db or background jobs.

Storage

StorageFacade provides server-side upload, getUrl, and bucket-aware operations. Browser clients receive upload, url, and delete helpers per declared bucket. Upload and image-transform rules are documented in Storage and Thumbnails.

Email

interface EmailFacade {
  send(message: EmailMessage): Promise<SentEmail>
}

EmailMessage supports to, subject, HTML or text bodies, sender override, reply-to, CC, and BCC. Configure the console, Resend, SMTP, or a custom adapter.

Testing (bunderstack/testing)

backend.test(options) produces an isolated test fixture with lexical async disposal (await using). For a suite, backend.test.configure() removes repeated environment, database, and seed boilerplate:

import { backend } from './bunderstack'

const createFixture = backend.test.configure({
  database: { mode: 'temporary', schema: 'migrations' },
  setup: async (fixture) => {
    const identity = fixture.auth.mockSession({
      id: 'alice',
      email: '[email protected]',
      name: 'Alice',
    })
    return { identity, client: fixture.client(identity) }
  },
})

test('posts procedure works', async () => {
  await using fixture = await createFixture()
  const { client } = fixture.context

  const result = await client.posts.create({ title: 'First post' })
  expect(result.title).toBe('First post')

  await fixture.jobs.runUntilIdle()
  expect(fixture.email.sent).toHaveLength(1)
})

TestFixture provides:

  • fixture.app: the materialized application runtime;
  • fixture.context: the typed value returned by configured setup;
  • fixture.defer(cleanup): LIFO async cleanup before application shutdown;
  • fixture.auth: real signUpEmail(), signInEmail(), getSession(), signOut(), and verifyEmail() flows, plus header-scoped mockSession() identities;
  • fixture.client(identity?): inferred typed oRPC client calling app.handler directly in-process;
  • fixture.jobs: deterministic runNext() / runUntilIdle() execution and inspect() / pending() / failed() queue assertions;
  • fixture.logs: captured internal entries, errors, and warnings with clear();
  • fixture.email: in-memory capture of sent emails at fixture.email.sent;
  • fixture.storage: isolated test storage with fixture.storage.read(key).

configure() defaults and per-test options deep-merge env and database. Runtime logs are captured by default; configure logs: 'inherit' to capture and forward to the console, or logs: 'silent' to discard them.

On this page