Prisma模式是直观的,可以让您以人为可读的方式声明数据库表。我最喜欢的事情是能够将其用作我应用程序数据模型的真实源。但是,为了获得更多情况,应该有一个良好的扩展机制。否则,Prisma本身必须涵盖所有不同的案件才能普遍实现。
扩展它的最直接方法是使用自定义属性。不幸的是,它尚未得到支持,似乎在可预见的将来。查看问题的悠久历史:
Allow custom field attributes #3102
假设我想确保,在创建电子邮件之前,电子邮件始终是有效的。
如果我能添加这样的@email
指令,那就太棒了:
model user { {
在 @id
在在
}
虽然有道理,但现在不允许使用@email
,如果我们允许的话,用户现在可以添加电子邮件验证中间件。
允许顶级@email
可能没有意义,以保持验证相当严格,以便我们也可以帮助您进行错别字。
但是,可以从可以从X-
或html属性开始的HTTP标头中的相同,该属性可以从data-
开始,我们可以为自定义指令添加一个名称空间,例如@custom.X
。
这将允许Prisma的整个扩展世界,可以通过中间warre进行处理。
当然,这只是一个疯狂的主意,它也可能只是垃圾,也许我们不应该这样做。 因此,让我们讨论是否有意义:)
另外,与以下相关问题:
- Support SQL Check constraints #3388
- Add exclude in the query arguments #5042
- Support adding prefix to an @id field #3391
在以下内容中,我将向您展示添加自定义属性的两种方法:
- Prisma的评论解决方法(如果您已经知道,可以跳过此部分)
- Zenstack的自定义属性。
评论Prisma的解决方法
开源社区的美丽就是只要有必要,总会有一个解决方案。请参阅社区提供的解决方案:
model Post {
id String @id @default(uuid()) /// @zod.uuid()
/// @zod.max(255, { message: "The title must be shorter than 256 characters" })
title String
contents String /// @zod.max(10240)
}
model User {
id String @id @default(cuid())
/// @HideField()
password String
/// @HideField({ output: true, input: true })
secret String
/// @HideField({ match: '@(User|Comment)Create*Input' })
createdAt DateTime @default(now())
}
他们都使用Triple Slash注释来实现技巧,因为这些注释将在架构文件的抽象语法树(AST)中显示为AST节点的描述。然后,他们可以通过自己的发电机访问它。我将简要介绍如何做。
如何访问发电机中的属性
在简而言之,运行prisma generate
命令时将调用发电机的代码。
实施生成器
您需要做的就是实现generatorHandler
函数,以创建您自己的新生成器,如下所示:
import { DMMF, generatorHandler, GeneratorOptions } from '@prisma/generator-helper';
import { getDMMF } from '@prisma/internals';
generatorHandler({
onManifest: () => ({
prettyName: 'My Generator',
}),
onGenerate: async (options: GeneratorOptions) => {
const prismaClientDmmf = await getDMMF({
datamodel: options.datamodel,
});
const models: DMMF.Model[] = prismaClientDmmf.datamodel.models;
console.log(models);
},
});
在模式中使用发电机
然后,您可以在定义的Prisma模式中使用它:
generator myGenerator {
// provider is the command to run the generatorHandler function defined above
provider = "node index.js"
// the output folder for the generated code
output = "./generated"
}
运行发电机
之后,运行prisma,您应该看到我的发电机的输出:
Prisma schema loaded from prisma/schema.prisma
{
enums: [],
models: [
{
name: 'User',
dbName: null,
fields: [Array],
primaryKey: null,
uniqueFields: [],
uniqueIndexes: [],
isGenerated: false
},
{
name: 'Post',
dbName: null,
fields: [Array],
primaryKey: null,
uniqueFields: [],
uniqueIndexes: [],
isGenerated: false
}
],
types: []
}
✔ Generated Prisma Client (4.8.0 | library) to ./node_modules/@prisma/client in 76ms
✔ Generated My Generator to ./prisma/generated in 2.32s
访问属性
假设我们想拥有一些自定义属性:
- 属性@@允许在模型级别控制访问策略。
- @email @email进行电子邮件验证。
- 属性@password和@omit在密码字段上加密并通过正常读取忽略它。
// everyone can signup, and user profile is also publicly readable
/// @@allow('create,read', true)
model User {
id String @id()
name String?
/// @email
email String? @unique()
/// @password
/// @omit
password String?
}
三重注释将设置为AST节点的documentation
属性,因此您可以如下访问它:
const userModel = datamodel.models.find((model) => model.name === 'User');
console.log(`user model attribute: ${userModel?.documentation}`);
const emailField = userModel?.fields.find((field) => field.name === 'email');
console.log(`email field attribute: ${emailField?.documentation}`);
const passwordField = userModel?.fields.find((field) => field.name === 'password');
console.log(`password field attribute: ${passwordField?.documentation}`);
运行后的输出是:
user model attribute: @@allow('create,read', true)
email field attribute: @email
password field attribute: @password
@omit
问题
尽管起作用,但使用注释有明显的缺陷:
- 没有语法检查。这意味着您不知道您是否在实际运行之前编写正确的语法。我个人对此有一种非常难过的感觉,因为类型安全是我从Typeorm切换到Prisma的主要原因。
- 您必须将一系列注释分析给属性,还需要在上面的示例中考虑多个属性案例。 。
- 存在两种类型的属性,这些属性不适合阅读。
- 您需要非常谨慎,不要错过使用双重斜杠//评论,这在AST中不存在。
因此,如果不存在完美的解决方案,那么我将构建它。
Zenstack中的属性
ZenStack是一种工具包,可以用强大的访问控制层增强Prisma,并释放其充分的全堆栈开发潜力。它的模式文件ZModel是Prisma架构的超集。
不管Zenstack如何,上面的Prisma文件都应该在下面看起来像:
model User {
id String @id
email String @email
password String @password @omit
// everyone can signup, and user profile is also publicly readable
@@allow('create,read', true)
}
实际上,这正是Zmodel所在。您可以在我们的示例代码中找到它:
https://zenstack.dev/docs/get-started/nextjs#3-preparing-the-user-model-for-authentication
上述属性已经在我们的标准库中预定义,如下:
attribute @email()
attribute @password(saltLength: Int?, salt: String?)
attribute @omit()
attribute @@allow(_ operation: String, _ condition: Boolean)
您可以使用相同的方法来定义自己的属性。
Zenstack插件中的访问属性
插件是Zenstack的可扩展性机制。它的用法类似于Prisma中的发电机,但以以下方式有所不同:
- 它们具有一个清洁界面,没有JSON-RPC的复杂性。
- 他们使用比发电机更易于编程的AST表示。
- 他们可以访问Zenstack添加到Prisma的语言功能,例如自定义属性和函数。
插件的实现类似于发电机,但更简单。您只需要创建一个节点模块,该模块以下面的签名导出功能:
import { DMMF } from "@prisma/generator-helper";
import { PluginOptions } from "@zenstackhq/sdk";
import { Model } from "@zenstackhq/sdk/ast";
export default async function run(
model: Model,
options: PluginOptions,
dmmf: DMMF.Document
) {
....
}
然后您可以评估以下属性:
const userModel = models.find((m) => m.name === 'User');
const userModelAttributes = userModel?.attributes;
const emailField = userModel?.fields.find((f) => f.name === 'email');
const emailFieldAttributes = emailField?.attributes;
const passwordField = userModel?.fields.find((f) => f.name === 'password');
const passwordFieldAttributes = passwordField?.attributes;
属性具有强大的类型,如下所示:
export interface DataModelFieldAttribute extends AstNode {
readonly $container: DataModelField;
readonly $type: 'DataModelFieldAttribute';
args: Array<AttributeArg>;
decl: Reference<Attribute>;
}
在内部,使用出色的开源语言工具Langium定义了Zmodel。因此该界面实际上是由langium生成的。
总的来说,您可以使用与Prisma Generator相似的逻辑编写Zenstack插件,该逻辑在您的代码和用户架构文件中使用类型安全。
奖金
您是否注意到在Zenstack的输入功能中,还有一个看起来很熟悉的dmmf
参数?是的,这是Prisma的AST模型,与自定义生成器中使用的模型相同。我想这是Zmodel是Prisma模式的超集。但是,如果您在插件中添加代码:
const userModel = dmmf.datamodel.models.find((m) => m.name === 'User');
console.log(`user model attribute: ${userModel?.documentation}`);
const emailField = userModel?.fields.find((f) => f.name === 'email');
console.log(`email field attribute: ${emailField?.documentation}`);
const passwordField = userModel?.fields.find((f) => f.name === 'password');
console.log(`password field attribute: ${passwordField?.documentation}`);
运行后,您将看到与Prisma Generator中完全相同的结果:
user model attribute: @@allow('create,read', true)
password field attribute: @password
@omit
实际上,如果您查看从zmodel生成的schema.prisma
文件,您将得到它:
/// @@allow('create,read', true)
model User {
id String @id() @default(cuid())
name String?
/// @email
email String? @unique()
/// @password
/// @omit
password String?
zenstack_guard Boolean @default(true)
zenstack_transaction String?
@@index([zenstack_transaction])
}
基于的原理,从来没有重新发明轮子 - 我们确实对社区贡献的现有发电机构成了价值,例如Zod Prisma和Prisma-nestjs -graphql。因此,为了使其仍然有效,对于任何非Prisma属性,我们将在schema.prisma
文件中作为三重注释生成。
因此,实际的好处是您可以将Zmodel简单地用作纯Prisma +句法自定义属性。,尽管Zenstack拥有的不仅仅是此,但我们仍然很高兴看到任何帮助可以给您! /p>