Skip to main content
Version: 3.x

Extending ORM Client API

Preview Feature
Plugin feature is in preview and may be subject to breaking changes in future releases.

Introduction​

ORM plugins can contribute new methods and properties to the ORM client, as well as introduce new properties to the query arguments of existing query APIs. These capabilities, combined with the lifecycle hooks introduced previously, allow you to implement powerful use cases like caching, soft delete, etc.

Adding new members to the ORM client​

You can add arbitrary methods and properties to the ORM client by including them under the client key of the plugin object. Extended member names must be prefixed with $ so that they don't accidentally shadow model names.

definePlugin({
...
client: {
$myNewMethod() { ... },
}
});

If your plugin adds multiple members, it's recommended to group them under one top-level property:

definePlugin({
...
client: {
$myPlugin: {
myMethod() { ... },
get myProperty() { ... }
}
}
});

Extending query args​

Extending query args involve providing the following two pieces of information:

1. What query APIs to add the new property to

You have the following granularity options to choose from:

  • Use $all to denote all query APIs.
  • Use $create, $read, $update, $delete to denote groups of query APIs.
  • Use specific API names like findUnique, deleteMany, etc.

2. Compile-time typing and runtime validation for the new property

Provide zod schemas (must use zod v4 to avoid version mismatch) that serve both purposes. The schema must be an ZodObject and will be merged with ZenStack's built-in query args validation schemas when checking the query args at runtime.

A simple configuration looks like this:

import { z } from 'zod';

definePlugin({
queryArgs: {
// inferred type is `{ myArg?: boolean }`
$read: z.object({
myArg: z.boolean().optional(),
}),
},
});

Samples​

Open in StackBlitz
plugins/extend-orm-client.ts
import { definePlugin } from '@zenstackhq/orm';
import { createClient } from '../db';
import z from 'zod';

async function main() {
const db = await createClient();

const cacheDb = db.$use(
definePlugin({
id: 'cache',

// add a $cache property to contain custom methods and properties
client: {
$cache: {
get stats() {
return { hits: 10, misses: 5 };
},
async invalidate() {
console.log('Cache invalidated');
}
}
},

queryArgs: {
// use "$read" to extend query args of all read operations
$read: z.object({
cache: z
.strictObject({
ttl: z.number().positive().optional()
})
.optional()
})
},

onQuery: ({ args, proceed }) => {
console.log('Intercepted cache args:', (args as any).cache);
return proceed(args);
}
})
);

await cacheDb.user.findMany({
where: { email: { contains: 'zenstack' } },
cache: { ttl: 60 }
});

console.log('Cache stats:', cacheDb.$cache.stats);
await cacheDb.$cache.invalidate();
}

main();
Comments
Feel free to ask questions, give feedback, or report issues.

Don't Spam


You can edit/delete your comments by going directly to the discussion, clicking on the 'comments' link below