
Security News
AI Agent Submits PR to Matplotlib, Publishes Angry Blog Post After Rejection
After Matplotlib rejected an AI-written PR, the agent fired back with a blog post, igniting debate over AI contributions and maintainer burden.
zen-stack-ts
Advanced tools
Generate ZenStack from TypeScript, based on Refract.
npm i -D zen-stack-ts
yarn add -D zen-stack-ts
See here for a full demo.
Use the ZenStackTs default export of this package to generate a Prisma file.
// schema.ts
// Import the entry-point
import ZenStackTs from 'zen-stack-ts';
// Import your custom Models
import { Roles, User, Posts } from './models';
ZenStackTs({
// Supply models/enums for generation
schema: [Roles, User, Posts],
// https://www.prisma.io/docs/reference/api-reference/prisma-schema-reference#datasource
datasource: {
provider: 'postgresql',
url: 'env("DATABASE_URL")',
shadowDatabaseUrl: 'env("DATABASE_SHADOW_URL")',
referentialIntegrity: 'prisma',
},
// https://www.prisma.io/docs/reference/api-reference/prisma-schema-reference#generator
generators: [
{
provider: 'prisma-client-js',
previewFeatures: ['referentialIntegrity'],
engineType: 'library',
binaryTargets: ['native'],
},
],
// Define output path for generated Prisma file
output: path.join(process.cwd(), 'schema.prisma'),
});
A command like npx ts-node schema.ts will run this TypeScript code & generate
the resulting Prisma file at the output path.
const User = Model('User', 'This is an optional comment');
User.Field('id', Int(Id, Default('autoincrement()')), 'The primary key');
// // This is an optional comment
// model User {
// // The primary key
// id Int @id @default(autoincrement())
// }
Model uses a fluid interface, so you can chain the following methods:
.Field(name, scalar): Add a scalar column to a Model.Relation(name, relation): Add a relationship to a Model.Block(compound): Add a block field, e.g. @@id, @@unique, @@map.Mixin(mixin): Inherit columns from a Mixin for compositional Models.Raw(value): Escape hatch into writing raw PrismaScalars are the types of data that the column contains, Int, String
etc. You can define & re-use Scalars wherever in your models
const PrimaryKey = Int(Id, Default('autoincrement()'));
// id Int @id @default("autoincrement()")
m.Field('id', PrimaryKey);
Modifiers are functions/objects that append attributes to a column e.g.
// String? @default("Hello World")
String(Default('Hello World'), Nullable);
// Int @id @unique @default(autoincrement())
Int(Id, Unique, Default('autoincrement()'));
// DateTime @default(now()) @updatedAt
DateTime(Default('now()'), UpdatedAt);
Certain modifiers are constrained to certain scalars, the mapping is:
String: Unique, Id, Default(string | 'auto()'), Limit(number)Int: Unique, Id, Default('cuid' | 'autoincrement()' | 'uuid()' | number)Float: Unique, Default(number)BigInt: Unique, Default(BigInt)Bytes: UniqueDecimal: UniqueBoolean: UniqueDateTime: Default('now()'), UpdatedAtUnsupportedAdditionally all scalars can use: Nullable, Map, Ignore, Raw & Array modifiers.
The Raw() modifier can be used as an escape hatch:
// String @db.ObjectId
String(Raw('@db.ObjectId'));
@db attributesCurrently there's support for mysql, postgresql, cockroachdb & mongodb @db
attributes, and can be used like all the other modifiers.
import { MySql as db } from 'zen-stack-ts';
// email String @db.VarChar(255)
m.Field('email', String(db.VarChar(255)));
Check src/public/db/mysql.ts (mongo.ts/postgresql.ts/cockroach.ts) for list of mappings between scalar types &
attributes.
OneToMany (model, name?, ...modifiers)
OneToOne (model, name?, fields, references, ...modifiers)OneToOne (model, name?, ...modifiers)
ManyToOne (model, name?, fields, references, ...modifiers)
Where Action is one of: Cascade, Restrict, NoAction, SetNull, SetDefault
const User = Model('User');
const Something = Model('Something');
Something
.Field('id', PrimaryKey)
// Holds foreign key
.Field('userId', Int())
.Relation('user', OneToOne(User, Fields('userId'), References('id')));
// Alternatively you can do Fields('userId', Int()) to avoid the extra
// .Field() call, this'll add the column to the model for you
User
.Field('id', PrimaryKey)
.Relation('thingy', OneToOne(Something));
const Post = Model('Post');
const Category = Model('Category');
Post
.Field('id', Int(Id, Default('autoincrement()')))
.Relation('categories', OneToMany(Category));
Category
.Field('id', Int(Id, Default('autoincrement()')))
.Relation('posts', OneToMany(Post));
The 2nd parameter of the Relation can be a string & explicitly denote the name of the relation.
// pinnedBy User? @relation(name: "PinnedPost", fields: [pinnedById], references: [id])
m.Relation(
'pinnedBy',
OneToOne(
User,
'PinnedPost',
Fields('pinnedById'),
References('id'),
Nullable,
),
);
OnUpdate & OnDelete modifiers can be used as follows:
// tag Tag? @relation(fields: [tagId], references: [id], onUpdate: Cascade, onDelete: Cascade)
m.Relation(
'tag',
ManyToOne(
Fields('tagId'),
References('id'),
OnUpdate('Cascade'),
OnDelete('Cascade'),
Nullable,
),
);
Composed of two parts:
Enum(name, comment?, ...Key)Key(value, ...modifiers, comment?)
const Animal = Enum(
'Animal',
Key('Seacow'),
Key('Capybara'),
Key('Otter', Map('otter')),
);
// fave Animal @default(Seacow)
// null Animal?
model
.Field('fave', Animal('Seacow'))
.Field('null', Animal());
const WithComment = Enum(
"Foo", "This is with a comment",
Key("Bar", "Another comment")
);
// // This is with a comment
// enum Foo {
// // Another comment
// Bar
// }
Used for adding fields like @@map, @@id, @@fulltext etc.
import { Compound, Mongo as db } from 'zen-stack-ts';
// Creating a compound index
model
.Field('id', Int(Id, Default('autoincrement()')))
.Field('authorId', Int())
.Relation('author', ManyToOne(User, Fields('authorId'), References('id')))
.Block(Compound.Id('id', 'authorId'));
// e.g. in MongoDB schemas
Model('User')
.Field('id', String(Id, db.ObjectId, Map('_id')))
.Block(Compound.Map('users'));
Allows you to re-use groups of fields, compositional models.
const Timestamps = Mixin()
.Field('createdAt', DateTime(Default('now()')))
.Field('updatedAt', DateTime(Nullable, UpdatedAt));
const User = Model('User').Field('id', PrimaryKey).Mixin(Timestamps);
// User will now have `createdAt` & `updatedAt` columns
const prisma = ZenStackTs.generate({
datasource: {...},
generators: [...],
schema
})
console.log(prisma); // schema.prisma contents
At some point you'll want to split the schema across files, which introduces issues with circular relationships when you're importing for .Relation()s in Node
One way to get around this is to have a file with all the models/enums defined, and have files import those & apply the fields, e.g.
// models.ts ------------------------------
const User = Model("User");
const Post = Model("Posts");
// ... and all the other Models
// users.ts ------------------------------
import { User, Post } from './models'
User
.Field("id", Int(Id, Default("autoincrement()")))
.Relation("posts", OneToMany(Post))
// posts.ts ------------------------------
import { User, Post } from './models'
Post
.Field("id", Int(Id, Default("autoincrement()")))
.Field("authorId", Int())
.Relation("author", ManyToOne(User, Fields("authorId"), References("id")))
// zen-stack.ts ------------------------------
import * as schema from './models'
// IMPORTANT: import the model files which performs the `.Field()`, `.Relation()`
// etc. calls, thereby adding the columns to the models
import "./posts";
import "./users";
ZenStackTs({
datasource: {...},
generators: [...],
schema
})
FAQs
Generate ZenStack from TypeScript
We found that zen-stack-ts demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Security News
After Matplotlib rejected an AI-written PR, the agent fired back with a blog post, igniting debate over AI contributions and maintainer burden.

Security News
HashiCorp disclosed a high-severity RCE in next-mdx-remote affecting versions 4.3.0 to 5.x when compiling untrusted MDX on the server.

Security News
Security researchers report widespread abuse of OpenClaw skills to deliver info-stealing malware, exposing a new supply chain risk as agent ecosystems scale.