Loading...
Loading...
Develop ZenStack V3 plugins. Use when building or declaring a plugin — custom ZModel attributes/functions, CLI code generators (CliPlugin with a generate callback for `zen generate`), or runtime plugins installed via $use/definePlugin (onQuery, onKyselyQuery, onEntityMutation hooks, extending the client, custom query args, computed result fields).
npx skill4agent add zenstackhq/skills zenstack-plugin-devPlugins are a preview feature and may change in future releases.
@passwordzen generateplugingeneratorzenstack-schema-modeling@zenstackhq/orm@zenstackhq/sdknpm install -D @zenstackhq/sdkplugin 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@core/typescript@core/prismaRuntimePlugin@zenstackhq/ormdefinePluginimport { 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()plugins: [...]ZenStackClientconst 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] });idonQueryfindManycreateproceeddefinePlugin({
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;
},
});onKyselyQueryOperationNodeproceedimport 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 });onEntityMutationdefinePlugin({
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: trueclientclient$$namespaceconst cacheDb = db.$use(definePlugin({
id: 'cache',
client: {
$cache: {
get stats() { return { hits: 0, misses: 0 }; },
async invalidate() { /* … */ },
},
},
}));
await cacheDb.$cache.invalidate();queryArgs$all$create$read$update$deleteimport { 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 } });resultneedsdefinePlugin({
id: 'full-name',
result: {
user: {
fullName: {
needs: { firstName: true, lastName: true },
compute: (user) => `${user.firstName} ${user.lastName}`,
},
},
},
});CliPlugingeneratezen generateimport 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 }outputplugin.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))
}plugin.zmodel@passwordbcryptHasher()@password(hasher: bcryptHasher(10))CliPlugin.generatereportRuntimePluginonKyselyQueryOperationNodeTransformer@passwordschema.models[...].fields[...].attributesINSERTUPDATEdb.$use(new PasswordHasherPlugin())pluginsnpm install -D @zenstackhq/sdkzen generateUse Zod v4 forschemas to match ZenStack's bundled version.queryArgs
references/onKyselyQueryonQueryplugin