关于 zenstack-plugin-dev
zenstack-plugin-dev 是 ZenStack V3 插件开发技能,介绍如何扩展 ZModel、`zen generate` 代码生成和 ORM 运行时。适合需要编写自定义属性、生成器或查询钩子的开发者;插件功能目前为预览版。
主要功能
- 01扩展 ZModel 属性与函数
- 02为 CLI 生成代码制品
- 03拦截 ORM 操作与 SQL
- 04添加实体变更生命周期钩子
- 05扩展客户端方法与查询参数
- 06计算查询结果虚拟字段
适用场景
- 01为模型字段添加自定义属性
- 02从 schema 生成报告或文件
- 03在写入数据库前转换 SQL 或数据
- 04为实体变更添加审计逻辑
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ zenstackhq/ skills --skill zenstack-plugin-dev安装后在 Claude Code 中输入/zenstack-plugin-dev确认已加载。
详细介绍
ZenStack V3 — 插件开发
插件目前是预览版功能,未来版本中可能会发生变化。
ZenStack 插件可以单独或组合扩展工具包的三个层面:
- 模式(ZModel) — 为模式语言添加自定义属性(例如
@password)和函数。 - CLI(代码生成) — 参与
zen generate,根据模式生成产物。 - 运行时(ORM) — 安装到客户端,以拦截查询、挂接 CRUD 生命周期事件、转换 SQL、添加自定义方法或计算字段。
ZenStack 的 plugin 取代了 Prisma 的 generator。要了解如何在模式中声明和使用插件,请参阅
zenstack-schema-modeling;本指南介绍如何编写插件。
软件包:@zenstackhq/orm(运行时插件类型)、@zenstackhq/sdk(CLI 插件类型 + ZModel
AST)。将 SDK 安装为开发依赖:npm install -D @zenstackhq/sdk。
在 ZModel 中声明插件
plugin myPlugin {
provider = './my-plugin' // local .ts/.js/.mts/.mjs file, a folder with an index, or an npm package
output = './generated' // arbitrary key=value options, readable by the plugin
}
provider 的解析顺序:本地文件路径 → 含有 index 的文件夹 → npm 软件包。CLI 插件必须是
ESM;TypeScript provider 通过 jiti 加载。内置的
@core/typescript 插件会自动运行;@core/prisma 会生成 Prisma 模式。
运行时插件
运行时插件是一个实现了 @zenstackhq/orm 中 RuntimePlugin 的对象。可以通过三种方式定义;
definePlugin 能提供最佳的类型推断:
import { definePlugin, type RuntimePlugin } from '@zenstackhq/orm';
// plain object / definePlugin (recommended)
const plugin = definePlugin({ id: 'my-plugin', /* hooks… */ });
// or a class
class MyPlugin implements RuntimePlugin<typeof schema> {
id = 'my-plugin';
/* hooks… */
}
在客户端上安装(不可变 — 返回一个新客户端,原客户端保持不变);使用
$unuse(id) / $unuseAll() 移除。也可以在 ZenStackClient 构造函数中传入 plugins: [...]。
const db = new ZenStackClient(schema, { dialect });
const withPlugin = db.$use(plugin); // new client
const plain = withPlugin.$unuse('my-plugin'); // remove by id
// or: new ZenStackClient(schema, { dialect, plugins: [plugin] });
每个插件都需要一个唯一的 id。可以组合使用以下任意钩子。
onQuery — 拦截 ORM 操作
仅在调用 ORM API(findMany、create 等)时触发,不会在查询构建器上触发。可以通过
proceed 包装操作、修改参数、提前返回或后处理结果。
definePlugin({
id: 'timing',
onQuery: async ({ model, operation, args, proceed }) => {
const start = Date.now();
const result = await proceed(args);
console.log(`${model}.${operation} took ${Date.now() - start}ms`);
return result;
},
});
onKyselyQuery — 拦截/转换 SQL
这是最低层级的钩子:每次数据库访问(包括 ORM 和查询构建器)都会经由 Kysely,因此你可以在
调用 proceed 前检查或重写 SQL AST(OperationNode)。适用于软删除、注入过滤条件和列转换。
import type { OnKyselyQueryCallback } from '@zenstackhq/orm';
const hook: OnKyselyQueryCallback<typeof schema> = async ({ query, proceed }) => {
// inspect/transform `query` (a Kysely OperationNode), e.g. with an OperationNodeTransformer
return proceed(query);
};
definePlugin({ id: 'sql-rewrite', onKyselyQuery: hook });
onEntityMutation — 创建·更新·删除前后
围绕实体变更触发的生命周期钩子,并提供用于延迟加载受影响行的加载器。
definePlugin({
id: 'audit',
onEntityMutation: {
runAfterMutationWithinTransaction: false, // default: run after the tx commits
async beforeEntityMutation({ model, action, loadBeforeMutationEntities }) {
// action: 'create' | 'update' | 'delete'
if (model === 'Post' && action === 'update') {
const before = await loadBeforeMutationEntities();
}
},
async afterEntityMutation({ model, action, loadAfterMutationEntities }) {
const after = await loadAfterMutationEntities();
},
},
});
将 runAfterMutationWithinTransaction: true 设置为在变更事务中原子地运行 after-hook。注意:
数据库级联触发的变更不会被捕获,且在批量变更时,加载器可能开销较大。
扩展客户端(client)
在 client 下添加自定义方法/属性。名称必须以 $ 开头,以免与模型访问器冲突;建议归入同一个
$namespace 对象。
const cacheDb = db.$use(definePlugin({
id: 'cache',
client: {
$cache: {
get stats() { return { hits: 0, misses: 0 }; },
async invalidate() { /* … */ },
},
},
}));
await cacheDb.$cache.invalidate();
自定义查询参数(queryArgs)
在 ORM 调用中接受额外选项,并使用 Zod 模式进行验证。可针对 $all、$create/$read/$update/$delete 组,或特定操作。
import { z } from 'zod';
definePlugin({
id: 'cache',
queryArgs: {
$read: z.object({ cache: z.strictObject({ ttl: z.number().positive().optional() }).optional() }),
},
onQuery: ({ args, proceed }) => proceed(args), // read (args as any).cache
});
// await db.user.findMany({ where: {...}, cache: { ttl: 60 } });
计算结果字段(result,v3.5.0+)
添加虚拟字段,这些字段会在查询后根据声明的 needs 字段计算。
definePlugin({
id: 'full-name',
result: {
user: {
fullName: {
needs: { firstName: true, lastName: true },
compute: (user) => `${user.firstName} ${user.lastName}`,
},
},
},
});
CLI 插件(代码生成)
CLI 插件是一个 ESM 模块,其默认导出为带有 generate 回调的 CliPlugin。
该插件在 zen generate 期间运行,并接收解析后的 ZModel AST。
import type { CliPlugin } from '@zenstackhq/sdk';
import { isDataModel, type InvocationExpr } from '@zenstackhq/sdk/ast';
import fs from 'node:fs';
const plugin: CliPlugin = {
name: 'Password Report',
// statusText is optional
generate: ({ model, defaultOutputPath, pluginOptions }) => {
if (pluginOptions['report'] !== true) return; // options come from the ZModel plugin block
let out = '# Password Fields\n\n';
for (const dm of model.declarations.filter(isDataModel)) {
for (const field of dm.fields) {
const attr = field.attributes.find((a) => a.decl.$refText === '@password');
if (attr) out += `- ${dm.name}.${field.name}\n`;
}
}
fs.writeFileSync(`${defaultOutputPath}/password-fields.md`, out, 'utf-8');
},
};
export default plugin;
generate 会接收 { model, defaultOutputPath, pluginOptions } — 分别对应 ZModel AST、解析后的
输出目录(output 选项或默认目录),以及 plugin 代码块中的选项。
模式插件(自定义属性和函数)
在插件提供的 .zmodel 文件中声明新的 ZModel 属性/函数,然后在应用模式中使用。
@@@targetField 用于限制属性的适用位置。
// my-plugin/plugin.zmodel
attribute @password(hasher: Any) @@@targetField([StringField])
function bcryptHasher(rounds: Int): Any {}
// app schema
model User {
id Int @id @default(autoincrement())
password String @password(hasher: bcryptHasher(10))
}
端到端模式:密码哈希
文档中的示例将三个层级组合成一个插件包:
- Schema —
plugin.zmodel定义@password+bcryptHasher();应用 Schema 将@password(hasher: bcryptHasher(10))应用到某个字段。 - CLI —
CliPlugin.generate遍历 AST,并在设置report选项时生成“密码字段”报告。 - 运行时 — 一个带有
onKyselyQuery的RuntimePlugin使用 KyselyOperationNodeTransformer检测@password字段(通过schema.models[...].fields[...].attributes),并在INSERT/UPDATE时、写入数据库之前对值进行哈希处理。
使用 db.$use(new PasswordHasherPlugin())(或 plugins 构造函数选项)进行安装,然后运行
npm install -D @zenstackhq/sdk 和 zen generate,执行 CLI 部分。
queryArgsSchema 请使用 Zod v4,以匹配 ZenStack 捆绑的版本。
参考文档
此主题的完整 ZenStack 文档已打包在 references/ 下:
- plugin-dev.md — 端到端插件开发指南
- runtime-plugins-overview.md — 运行时插件模型
- extending-orm-client.md — 客户端方法、查询参数、计算字段
- entity-mutation-hooks.md — 变更前/后钩子
- kysely-query-hooks.md —
onKyselyQuerySQL 转换 - query-api-hooks.md —
onQueryORM 拦截 - zmodel-plugin.md — 在 ZModel 中声明插件
- zmodel-plugin-reference.md —
plugin代码块参考