Generators
Generators transform your opensaas.config.ts into Prisma schemas and TypeScript types.
Overview
The generator system reads your declarative config and creates:
- Prisma Schema (
prisma/schema.prisma) - TypeScript Types (
.opensaas/types.ts) - Context Factory (
.opensaas/context.ts)
Running the Generator
pnpm opensaas generate
Or in a specific example:
cd examples/blog
pnpm generate
What Gets Generated
1. Prisma Schema
From your config:
Post: list({
fields: {
title: text({ validation: { isRequired: true } }),
author: relationship({ ref: 'User.posts' }),
},
})
The generator creates:
model Post {
id String @id @default(cuid())
title String
authorId String
author User @relation(fields: [authorId], references: [id])
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
2. TypeScript Types
Type-safe types for all your lists:
export type Lists = {
Post: {
fields: {
title: string
author: User
}
}
// ... more lists
}
3. Context Factory
Auto-generated context creation function:
import { getContext } from '@/.opensaas/context'
const context = await getContext({ userId: '123' })
Generator Architecture
Generators delegate to field builder methods rather than using switch statements. Each field type provides its own generation logic:
text({
getPrismaType: (fieldName) => {
return { type: 'String', modifiers: '?' }
},
getTypeScriptType: () => {
return { type: 'string', optional: true }
},
})
This allows field types to be fully self-contained and extensible.
Custom Prisma Client Constructor
To use custom database drivers (e.g., Neon, Turso, PlanetScale), provide a prismaClientConstructor:
export default config({
db: {
provider: 'postgresql',
url: process.env.DATABASE_URL,
prismaClientConstructor: (PrismaClient) => {
const adapter = new PrismaNeon({
connectionString: process.env.DATABASE_URL,
})
return new PrismaClient({ adapter })
},
},
// ... rest of config
})
Extending the Generated Prisma Schema
The extendPrismaSchema function allows you to modify the generated Prisma schema before it's written to disk. This is useful for advanced Prisma features not directly supported by the config API.
export default config({
db: {
provider: 'postgresql',
prismaClientConstructor: (PrismaClient) => {
// ... adapter setup
},
extendPrismaSchema: (schema) => {
// Modify the schema as needed
let modifiedSchema = schema
// Example: Add multi-schema support for PostgreSQL
modifiedSchema = modifiedSchema.replace(
/(datasource db \{[^}]+provider\s*=\s*"postgresql")/,
'$1\n schemas = ["public", "auth"]',
)
// Example: Add @@schema attribute to all models
modifiedSchema = modifiedSchema.replace(
/^(model \w+\s*\{[\s\S]*?)(^}$)/gm,
(match, modelContent) => {
if (!modelContent.includes('@@schema')) {
return `${modelContent}\n @@schema("public")\n}`
}
return match
},
)
return modifiedSchema
},
},
// ... rest of config
})
Common Use Cases
- Multi-schema support: Add Prisma's multi-schema support for PostgreSQL
- Custom attributes: Add model-level or field-level attributes not exposed in the config API
- Preview features: Enable Prisma preview features via datasource or generator configuration
- Output path modifications: Adjust the Prisma Client output path
Generator Limitations
Current generators are basic:
- ❌ No migration support (use
prisma db push) - ❌ No introspection support
- ❌ Limited Prisma features (no raw queries, advanced transactions)
Best Practices
1. Regenerate After Config Changes
Always run the generator after modifying your config:
pnpm generate
2. Commit Generated Files
Commit the generated files to version control for consistency:
git add prisma/schema.prisma
git add .opensaas/
git commit -m "Regenerate schema"
3. Use Type-Safe Operations
Use the generated types for type safety:
import type { Lists } from '@/.opensaas/types'
const post: Lists['Post'] = await context.db.post.findUnique({
where: { id: '123' },
})
Next Steps
- Config System - Learn about config options
- Field Types - Available field types