Stories Behind ZenStack V2
After polishing ZenStack V2 in the future branch for more than two months, we are happy to make it official now. I would like to take this opportunity to briefly show you the main features of V2 and the stories behind it.
Polymorphic Relations
This feature is actually the reason we created V2. It's one of the most desired features of Prisma, which you can see from the reactions to the two related GitHub issues:
Some folks even think that an ORM is incomplete without polymorphism support.
As the believer and enhancer of Prisma, one of our strategies is to pick up where Prisma has left. So, it did come to our radar at the early stage:
Support for Polymorphic Associations #430
It quickly became one of the most desired features of ZenStack too. Not only for the reactions themselves but also because more issues actually depend on it:
- ZenStack does not let me define multiple fields that are referencing the same model #653
- Support implicit inversed relationship #613
However, we know it’s a non-trivial task. Therefore, instead of rushing into the implementation, we wrote a blog post first to explain our approach and send it to the communities of both Prisma and ZenStack to get their feedback:
Tackling Polymorphism in Prisma
The feedback has exceeded our expectations. One guy even refers to it as Christmas magic:
Gaining enough confidence, we began working on the implementation. Our confidence was further boosted when Prisma mentioned it as a third-party solution in its official blog, even though the feature was still in the alpha stage:
Table inheritance | Prisma Documentation
Now, you can fully enjoy it with the latest version of ZenStack:
Why should you use it? Check out the below post to illustrate the benefit with a real example:
End-To-End Polymorphism: From Database to UI, Achieving SOLID Design
Edge Support
This is a surprising gift we received from Prisma. Previously, when a user asked if ZenStack could run on Edge, we would direct them to the Prisma Accelerate proxy, as it was the only way to do it. However, not all users are willing to use another service. This situation is frustrating for us because, unlike resolving polymorphism, we can't fully address it from ZenStack unless reimplementing the entire Prisma engine.
Fortunately, Prisma sent us a fantastic gift at the start of 2024. Without hesitation, we began tweaking ZenStack to ensure its compatibility with the edge runtime. Even though the workload is minimal compared to polymorphism, we gained substantial knowledge throughout the process. If you're also planning to adapt to the Edge runtime, we hope our experience can save you some time:
Adapting ZenStack to the Edge: Our Struggles and Learnings
By the way, Edge support is in preview for both Prisma and ZenStack, so if you run into any issues, feel free to contact us via Twitter, Discord, or GitHub.
Auth()
in Default()
This is a very special feature. The brilliant idea came from one of our users at a very early time:
Support for auth() in @default annotation #310
The best part is that right before we started to consider whether to implement it in V2, another user sent out a PR for it:
Support for auth() in @default attribute #958
This is the first feature of ZenStack that is fully implemented by the community. We would like to express our gratitude by sharing the author’s GitHub profile link here.
It once again shows the benefit of using declarative schema to reduce the duplication of imperative code:
- before
//schema.zmodel
model Post {
...
owner User @relation(fields: [ownerId], references: [id])
ownerId Int
}
//xxx.ts
const db = enhance(prisma, { user });
await db.post.create({
data: {
owner: { connect: { id: user.id } },
title: 'Post1'
}
}) - after
//schema.zmodel
model Post {
...
owner User @relation(fields: [ownerId], references: [id])
ownerId Int @default(auth().id) // <- assign ownerId automatically
}
//xxx.ts
const db = enhance(prisma, { user });
await db.post.create({ data: { title: 'Post1' } });
VSCode Auto-Formatting
This is the feature I regret the most. In fact, I would call it a fix instead of a feature because I did implement it in V1:
I mentioned “minimize disruptions for prisma users”, but you know what? It was actually a lie. Although I tried to make ZModel look as same as Prisma, it used a different indentation style:
Which one do you think looks better? 😉 I convinced myself that the left one was better due to its consistency with TS style. Why need consistency with TS? Because it includes the access policy code.
It turns out that all of that was just an excuse to avoid making changes. Even when I realized it soon, I found another excuse that the change would disturb existing users. You can see how skilled our mind is at finding excuses to avoid work. However, it struggles to estimate the actual workload accurately because it attempts to avoid it.
The total time I spent to implement it is just a couple of hours which even includes flags both in VSCode extension and CLI to give the user the option to preserve the old style if he likes:
Usage: zenstack format [options]
Format a ZenStack schema file.
Options:
--no-prisma-style do not use prisma style
Don't let inertia obscure the truth; use your heart to find the right path and pursue it.
Final Words
You can find the complete change detail in the below post:
In the Game of Thrones series finale, Tyrion Lannister said the following words:
What unites people? Armies? Gold? Flags? Stories. There's nothing in the world more powerful than a good story.
These features will probably be forgotten as time passes or even not used at all by most people. But I hope the stories behind them could still have some meaning for you.