Vibe Storage
GenKit includes a pluggable storage system for persisting user-generated components. Use the built-in Prisma adapter, or create your own for any database.Quick Start
import { createVibeApi, createPrismaStorage } from 'genkit'
import { prisma } from '@/lib/prisma'
const vibe = createVibeApi({
storage: createPrismaStorage(prisma)
})
// Save a component
const saved = await vibe.save({
userId: 'user-123',
name: 'Sales Dashboard',
description: 'Pipeline visualization',
code: generatedCode
})
// Load a component
const component = await vibe.load(saved.id)
// List user's components
const components = await vibe.list({ userId: 'user-123' })
// Delete a component
await vibe.delete(saved.id)
Prisma Setup
1. Add the Model
Add this to yourschema.prisma:
model VibeComponent {
id String @id @default(cuid())
userId String
name String
description String?
code String @db.Text
compiled String? @db.Text
version Int @default(1)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@index([userId])
}
2. Run Migration
npx prisma migrate dev --name add-vibe-components
3. Create the API
// lib/vibe.ts
import { createVibeApi, createPrismaStorage } from 'genkit'
import { prisma } from '@/lib/prisma'
export const vibe = createVibeApi({
storage: createPrismaStorage(prisma)
})
API Reference
vibe.save(input)
Save a new component.
const component = await vibe.save({
userId: 'user-123',
name: 'Stats Card',
description: 'Displays key metrics',
code: `export default function StatsCard({ data }) { ... }`
})
// Returns:
{
id: 'cuid123...',
userId: 'user-123',
name: 'Stats Card',
description: 'Displays key metrics',
code: '...',
version: 1,
createdAt: Date,
updatedAt: Date
}
vibe.load(id)
Load a component by ID.
const component = await vibe.load('cuid123...')
if (!component) {
throw new Error('Component not found')
}
vibe.list(options)
List components for a user.
const components = await vibe.list({
userId: 'user-123',
limit: 20, // default: 50
offset: 0 // for pagination
})
vibe.delete(id)
Delete a component.
await vibe.delete('cuid123...')
Memory Storage (Testing)
For development and testing, use in-memory storage:import { createVibeApi, createMemoryStorage } from 'genkit'
const vibe = createVibeApi({
storage: createMemoryStorage()
})
// Works the same as Prisma storage
await vibe.save({ ... })
Memory storage is cleared when the server restarts. Only use for development.
Custom Storage Adapter
Implement theVibeStorage interface for custom backends:
import type { VibeStorage, VibeComponent, VibeSaveInput, VibeListOptions } from 'genkit'
export function createCustomStorage(db: YourDatabase): VibeStorage {
return {
async save(input: VibeSaveInput): Promise<VibeComponent> {
const id = generateId()
const now = new Date()
const component = {
id,
...input,
version: 1,
createdAt: now,
updatedAt: now
}
await db.components.insert(component)
return component
},
async load(id: string): Promise<VibeComponent | null> {
return db.components.findById(id)
},
async list(options: VibeListOptions): Promise<VibeComponent[]> {
return db.components.find({
where: { userId: options.userId },
orderBy: { updatedAt: 'desc' },
limit: options.limit ?? 50,
offset: options.offset ?? 0
})
},
async delete(id: string): Promise<void> {
await db.components.deleteById(id)
},
async update(id: string, data: Partial<VibeSaveInput>): Promise<VibeComponent> {
const existing = await this.load(id)
if (!existing) throw new Error('Not found')
const updated = {
...existing,
...data,
version: existing.version + 1,
updatedAt: new Date()
}
await db.components.update(id, updated)
return updated
}
}
}
Drizzle Example
import { eq, desc } from 'drizzle-orm'
import { vibeComponents } from './schema'
export function createDrizzleStorage(db: DrizzleDB): VibeStorage {
return {
async save(input) {
const [component] = await db
.insert(vibeComponents)
.values({
userId: input.userId,
name: input.name,
description: input.description,
code: input.code
})
.returning()
return component
},
async load(id) {
const [component] = await db
.select()
.from(vibeComponents)
.where(eq(vibeComponents.id, id))
.limit(1)
return component ?? null
},
async list(options) {
return db
.select()
.from(vibeComponents)
.where(eq(vibeComponents.userId, options.userId))
.orderBy(desc(vibeComponents.updatedAt))
.limit(options.limit ?? 50)
.offset(options.offset ?? 0)
},
async delete(id) {
await db
.delete(vibeComponents)
.where(eq(vibeComponents.id, id))
},
async update(id, data) {
const [updated] = await db
.update(vibeComponents)
.set({
name: data.name,
description: data.description,
code: data.code,
updatedAt: new Date()
})
.where(eq(vibeComponents.id, id))
.returning()
return updated
}
}
}
API Routes
Save Component
// app/api/vibe/save/route.ts
import { vibe } from '@/lib/vibe'
import { auth } from '@/lib/auth'
export async function POST(request: Request) {
const session = await auth()
if (!session) {
return Response.json({ error: 'Unauthorized' }, { status: 401 })
}
const { name, description, code } = await request.json()
const component = await vibe.save({
userId: session.user.id,
name,
description,
code
})
return Response.json({ component })
}
List Components
// app/api/vibe/list/route.ts
import { vibe } from '@/lib/vibe'
import { auth } from '@/lib/auth'
export async function GET(request: Request) {
const session = await auth()
if (!session) {
return Response.json({ error: 'Unauthorized' }, { status: 401 })
}
const { searchParams } = new URL(request.url)
const limit = parseInt(searchParams.get('limit') ?? '50')
const offset = parseInt(searchParams.get('offset') ?? '0')
const components = await vibe.list({
userId: session.user.id,
limit,
offset
})
return Response.json({ components })
}
Load Component
// app/api/vibe/[id]/route.ts
import { vibe } from '@/lib/vibe'
import { auth } from '@/lib/auth'
export async function GET(
request: Request,
{ params }: { params: { id: string } }
) {
const session = await auth()
if (!session) {
return Response.json({ error: 'Unauthorized' }, { status: 401 })
}
const component = await vibe.load(params.id)
if (!component) {
return Response.json({ error: 'Not found' }, { status: 404 })
}
// Check ownership
if (component.userId !== session.user.id) {
return Response.json({ error: 'Forbidden' }, { status: 403 })
}
return Response.json({ component })
}
Component Types
interface VibeComponent {
id: string
userId: string
name: string
description?: string
code: string
compiled?: string
version: number
createdAt: Date
updatedAt: Date
}
interface VibeSaveInput {
userId: string
name: string
description?: string
code: string
}
interface VibeListOptions {
userId: string
limit?: number
offset?: number
}
Next Steps
VibeFrame
Render saved components securely
Compiler
Validate code before saving