omg/docs
SDK

SDK overview

Building DB-backed apps with @omg-dev/schema, @omg-dev/sdk, and @omg-dev/server.

The Vibes SDK is three packages cooperating across one project:

PackageWhere it runsWhat it does
@omg-dev/schemaBuild timeDeclarative schema; emits SQL DDL, TS types, Zod, and the build manifest's hasDb flag.
@omg-dev/sdkBrowseruseQuery<T> — WebSocket snapshot + live row deltas, optional where scope, and mutations (useCollection is the deprecated alias).
@omg-dev/serverBun runtimeMounts /api/<collection> against sqlite, dispatches functions/*.ts, broadcasts invalidations.

The pipeline keys off one file: schema.ts at project root. Find default.collections and the deploy classifies as runtime — a Firecracker VM serves /api/*. Otherwise it's static, the VM is skipped, and mutations return method not allowed. Getting that file shape right is the single load-bearing step; see Schema reference for the contract.

Minimum viable DB-backed app

The fresh sandbox at /home/user/project already has Vite, React, Tailwind, shadcn, and @omg-dev/* installed. To turn it into a DB app, write two files.

1. schema.ts (project root, default-exported)

import { defineSchema, collection, fields } from "@omg-dev/schema"

export default defineSchema({
  collections: {
    tasks: collection({
      fields: {
        title: fields.string(),
        done: fields.boolean(),
      },
    }),
  },
})

This file MUST be at the project root, not under src/ — the build does await import("./schema.ts") from process.cwd(). Anywhere else silently ships a static deploy.

2. src/App.tsx

import { useState } from "react"
import { useQuery } from "@omg-dev/sdk"

interface Task { id: string; title: string; done: boolean }

export default function App() {
  const { data, create, update, remove } = useQuery<Task>({
    api: "/api/tasks",
    collection: "tasks",
  })
  const [draft, setDraft] = useState("")

  return (
    <main>
      <h1>Tasks</h1>
      <form
        onSubmit={async (e) => {
          e.preventDefault()
          if (!draft.trim()) return
          await create({ title: draft, done: false })
          setDraft("")
        }}
      >
        <input value={draft} onChange={(e) => setDraft(e.target.value)} />
        <button type="submit">Add</button>
      </form>
      <ul>
        {data.map((t) => (
          <li key={t.id}>
            <input
              type="checkbox"
              checked={t.done}
              onChange={() => update(t.id, { done: !t.done })}
            />
            <span>{t.title}</span>
            <button onClick={() => remove(t.id)}>x</button>
          </li>
        ))}
      </ul>
    </main>
  )
}

That is the whole DB app. No useEffect, no fetch, no DB client, no auth wiring. data is always an array (defaults to [] before the first fetch resolves) so .map is safe from the first render.

What happens at deploy time

  1. bun run build runs the user-facing client build (Vite).
  2. The orchestrator chains vibes-build (@omg-dev/vite-plugin's bin), which:
    • Imports schema.ts, reads default.collections, decides hasDb.
    • Scans functions/*.ts, generates .vibes/routes.generated.ts.
    • Bundles everything into a single .vibes/server.mjs via bun build --target=bun.
    • Writes .vibes/artifacts.json with { static, hasDb, functions, ... }.
  3. The orchestrator (apps/infra/internal/orchestrator/manager.go) reads the manifest. If !hasDb && functions === 0 it takes the static path (no VM). Otherwise it forks a runtime VM that runs bun .vibes/server.mjs.
  4. At runtime, @omg-dev/server:
    • Opens .vibes/data.db (litestream-restored from deploys-db/<slug>/v:N-1/, replicates to v:N/).
    • Auto-mounts /api/<collection> for every collection in the schema.
    • Dispatches functions/*.ts exports as additional routes.
    • Streams invalidations on /__vibes_events so every connected client refreshes when any mutation lands.

Custom routes (functions)

useCollection covers the common CRUD shape. For anything else, drop a file in functions/:

// functions/search.ts
export default {
  POST: async (req: Request) => {
    const { q } = await req.json()
    return Response.json({ results: [] })
  },
}

Builds at /api/search. The build scanner detects every file in functions/, codegens preloaded module imports, and ships them inside .vibes/server.mjs — no node_modules/ lookup at runtime.

Common errors

Browser consoleReal causeFix
TypeError: r.map is not a functionuseCollection was destructured wrong (it returns an object, not an array).const { data } = useCollection(...), then data.map(...).
Unexpected token 'm', "method not allowed" ...Static-only deploy. schema.ts didn't default-export collections, so the orchestrator dropped the runtime VM.Verify schema.ts is at root and default-exports defineSchema(...). See Schema reference.

Reference