ZModel Language
The first thing that ZenStack may surprise you is that, unlike other Prisma tools, we created a new schema language called ZModel. It's a superset of the Prisma Schema Language (PSL) with syntax elements to support additional features. The zenstack
CLI takes a ZModel file as input and generates a Prisma Schema file out of it - which in turn can be fed to the standard prisma
CLI for generating a Prisma Client or migrating the database.
Why did we invent a new schema language?
Why ZModel?
Custom Attributes
While Prisma Schema Language provides a terse and intuitive way to define data models, it has a major extensibility limitation: you can't add custom attributes. Prisma provides a set of pre-defined attributes to control detailed aspects of your tables and fields, but you're stuck when you need custom ones for your special modeling purposes. Traditionally, Prisma community tools have been hacking around this limitation by smuggling custom information in code comments, like the following example with TypeGraphQL Prisma:
model User {
id Int @default(autoincrement()) @id
email String @unique
/// @TypeGraphQL.omit(output: true, input: true)
password String
}
It works, but it's ugly and gets no protection from the compiler. The model can become messy if it's littered with such hacks everywhere. One of the biggest reasons for introducing the ZModel language is to systematically remove this obstacle so that we have a solid foundation for adding custom semantics into the schema down the road.
Here's a quick example of the custom @password
and @omit
attributes ZModel added for automatically hashing passwords upon saving and omitting them from query results:
model User {
id Int @default(autoincrement()) @id
email String @unique
password String @omit @password
}
The access policies and data validation rules are also implemented with custom attributes. Don't worry. We'll cover them in detail in the following chapters.
Other Language Features
A custom schema language also allows us to add new language features besides custom attributes. For example:
- The
import
syntax for breaking down a large schema into multiple files - The
extends
syntax for inheriting fields from a base model
Here's an example of how to use them to manage large schemas more effectively:
abstract model Base {
id Int @id @default(autoincrement())
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt()
published Boolean @default(true)
// author has full access
@@allow('all', auth() != null && published)
}
import "base"
model User extends Base {
name String
}
model Post extends Base {
title String
content String?
viewCount Int @default(0)
comment Comment[]
}
model Comment extends Base {
content String
post Post @relation(fields: [postId], references: [id])
postId Int
}
A Better Plugin System
Prisma allows you to write custom generators. However, the generator development API is undocumented and difficult to understand. ZenStack provides a plugin system that enables you to generate custom artifacts with a simple API and object model. In fact, almost all the features of ZenStack itself are implemented as plugins. Part II of this guide is dedicated to covering the plugin system.
ZModel Structure
The ZModel language is a superset of Prisma Schema Language (PSL). All Prisma schema syntaxes are valid in ZModel. A ZModel can contain the following declarations.
Imports
The import
syntax is an extension to PSL. You can use it to break down a large schema into multiple files.
import "user"
import "post"
model User {
id Int @id @default(autoincrement())
email String @unique
posts Post[]
}
model Post {
id Int @id @default(autoincrement())
title String
author User @relation(fields: [authorId], references: [id])
authorId Int
}
When the zenstack
CLI compiles an input schema, it merges the content of all imported files into a single schema before further processing.
Data Source
The datasource
declaration of ZModel is exactly the same as PSL. ZenStack passes it to the generated Prisma schema without modification.
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
Prisma Generators
The generator
declarations of ZModel are exactly the same as PSL. ZenStack passes it to the generated Prisma schema without modification.
generator client {
provider = "prisma-client-js"
}
Plugins
Plugins are the new extensibility mechanism provided by ZModel. Its syntax is similar to generators but with the plugin
declaration keyword. Here's an example for generating tRPC CRUD routers:
plugin trpc {
provider = "@zenstackhq/trpc"
output = "src/generated"
}
Part II of this guide will cover the plugin system in detail.
Models
The model
declaration of ZModel is exactly the same as PSL, except for the new set of attributes ZenStack added.
model User {
id Int @id @default(autoincrement())
// during create and update, ZenStack validates the field is a valid email address
email String @unique @email
// the field is automatically hashed upon saving, and omitted from query results
password String @omit @password
// access policy: open to sign up
@@allow('create', true)
// access policy: the user has full access to self
@@allow('all', auth() == this)
}
IDE Support
ZenStack comes with a VSCode extension and a JetBrains IDE plugin. You can find more information about IDE support here.
ZModel supports both line comments (//
) and block comments (/* ... */
). Comments starting with triple slashes (///
) are treated as documentation: they show up as hover tooltip in IDEs, and they are passed along to the generated Prisma schema.
/// A user model
model User {
id String @id
/// The user's email
email String @unique
}
Full Documentation
Check out the ZModel Language reference documentation for a complete language description.