Vibecode DB

Vibecode DB is the database layer for AppLighter, providing a unified API for mock and Supabase adapters with authentication, CRUD operations, and TanStack Query integration.

What is Vibecode DB?

Vibecode DB is a flexible database client that provides a unified API across different backend adapters. AppLighter uses Vibecode DB as its data layer, allowing you to switch between mock data and Supabase without changing your application code.

Why Vibecode DB?

  • Adapter Pattern - Same API whether using mock data or Supabase
  • Built-in Auth - Sign up, sign in, session management across all adapters
  • Mock Mode - Start building immediately with seeded data, no backend needed
  • Supabase Compatible - Familiar query builder API if you're coming from Supabase
  • Type Safe - Full TypeScript support with schema types

Adapters

Mock Adapter (Default)

No setup required. Uses in-memory storage with seeded data from src/db/seeds/.

EXPO_PUBLIC_DB_ADAPTER=mock

Auto-creates a demo user (demo@example.com) and seeds all mock data on app start.

Supabase Adapter

Connect to Supabase for cloud PostgreSQL with auth, storage, and realtime.

EXPO_PUBLIC_DB_ADAPTER=supabase
EXPO_PUBLIC_SUPABASE_URL=https://your-project.supabase.co
EXPO_PUBLIC_SUPABASE_ANON_KEY=your-anon-key

PocketBase Scripts

AppLighter also includes scripts for pushing schema and seeding data to a PocketBase backend:

# Push schema collections to PocketBase
yarn pocketbase-db-push
 
# Seed data to PocketBase
yarn pocketbase-db-seed

Client Setup

The Vibecode client is initialized in src/db/client.ts:

import { createClient } from '@vibecode-db/client'
 
const DB_ADAPTER = process.env.EXPO_PUBLIC_DB_ADAPTER ?? 'mock'
 
// Adapter is selected based on DB_ADAPTER value
// All adapters expose the same vibecode API

Import and use in your screens:

import { vibecode } from '@/src/db/client'

API Chain Order

Important: Vibecode DB uses a specific chain order for queries:

from → filters → order → limit → select

CRUD Operations

Select

// Select all rows
const { data, error } = await vibecode.from('posts').select('*')
 
// With filters, ordering, and limit
const { data, error } = await vibecode
  .from('posts')
  .eq('user_id', userId)
  .order('created_at', { ascending: false })
  .limit(50)
  .select('*')
 
// Single record
const { data, error } = await vibecode
  .from('posts')
  .eq('id', postId)
  .limit(1)
  .select('*')
const post = data?.[0] ?? null

Insert

const { data, error } = await vibecode.from('posts').insert({
  id: crypto.randomUUID(),
  caption: 'Hello world',
  user_id: user.id,
  created_at: new Date().toISOString(),
  updated_at: new Date().toISOString(),
})
 
if (error) throw error

Update

const { error } = await vibecode
  .from('posts')
  .eq('id', postId)
  .update({
    caption: 'Updated caption',
    updated_at: new Date().toISOString(),
  })

Delete

const { error } = await vibecode
  .from('posts')
  .eq('id', postId)
  .delete()

Filter Methods

MethodDescriptionExample
.eq(col, val)Equal.eq('published', true)
.ne(col, val)Not equal.ne('status', 'draft')
.gt(col, val)Greater than.gt('views', 100)
.gte(col, val)Greater than or equal.gte('created_at', date)
.lt(col, val)Less than.lt('price', 50)
.lte(col, val)Less than or equal.lte('updated_at', date)
.in(col, arr)In array.in('status', ['active', 'pending'])
.like(col, pattern)Pattern match.like('title', '%search%')

Authentication

Vibecode DB provides built-in auth methods:

Sign Up

const { data, error } = await vibecode.auth.signUp({
  email: 'user@example.com',
  password: 'securepassword',
  name: 'User Name',
})

Sign In

const { data, error } = await vibecode.auth.signIn({
  email: 'user@example.com',
  password: 'securepassword',
})
 
if (data) {
  // User is authenticated
  (globalThis as any).__setAuthUser?.(data.user)
}

Sign Out

await vibecode.auth.signOut()

Get Session

const { data } = await vibecode.auth.getSession()
 
if (data?.session?.user) {
  // User is authenticated
  console.log('Current user:', data.session.user.email)
}

With TanStack Query

Use Vibecode DB with TanStack Query for caching, background refetching, and optimistic updates:

import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'
import { vibecode } from '../src/db/client'
 
export default function HomeScreen() {
  const queryClient = useQueryClient()
 
  const query = useQuery({
    queryKey: ['posts'],
    queryFn: async () => {
      const { data, error } = await vibecode
        .from('posts')
        .order('created_at', { ascending: false })
        .limit(50)
        .select('*')
      if (error) throw error
      return data ?? []
    },
  })
 
  const createPost = useMutation({
    mutationFn: async (post: { caption: string; user_id: string }) => {
      const { error } = await vibecode.from('posts').insert({
        id: crypto.randomUUID(),
        ...post,
        created_at: new Date().toISOString(),
        updated_at: new Date().toISOString(),
      })
      if (error) throw error
    },
    onSuccess: () => {
      queryClient.invalidateQueries({ queryKey: ['posts'] })
    },
  })
 
  return { posts: query.data, isLoading: query.isLoading, createPost }
}

Mock Data Seeds

When using the mock adapter, data is seeded from src/db/seeds/. The starter template includes seeds for users, posts, and stories. Each seed file follows this pattern:

// src/db/seeds/posts.ts
import type { Post } from '../schema'
 
type SeedEntry = { table: string; rows: Post[] }
 
const seed: SeedEntry = {
  table: 'posts',
  rows: [
    {
      id: '1',
      caption: 'Hello world',
      // ... other fields
    },
  ],
}
 
export default seed

Register seeds in src/db/seeds/index.ts:

import users from './users'
import posts from './posts'
import stories from './stories'
 
export const seeds: SeedEntry[] = [users, posts, stories]
export { DEMO_USER_ID } from './users'

Schema Sync Rule

Important: When adding or updating database entities, always keep these three files in sync:

  1. src/db/schema.ts - TypeScript type definitions (source of truth)
  2. src/db/seeds/<table>.ts - Mock data with matching shape
  3. src/db/seeds/index.ts - Register in the seeds array

Error Handling

Always check for errors from Vibecode DB operations:

const { data, error } = await vibecode.from('posts').select('*')
 
if (error) {
  console.error('Database error:', error.message)
  Alert.alert('Error', 'Failed to load posts')
  return
}
 
// Safe to use data

Next Steps