Validators
Validators are custom functions that run after permission checks but before the actual database operation. They give you more programmatic control over create and update operations.
Create validator
We have a Asset model that belongs to a Project. We want to allow users to create assets, but only if they are a contributor on or the owner of the parent project.
import { type AccessDefinition } from "@mongalayer/server";
import type { Asset } from "../model/asset.js";
const contributorAssetAccess: AccessDefinition<Asset> = {
role: "contributor",
validators: {
create: async (context, document) => {
const database = context.client.db(context.database);
const project = await database.collection<Project>("projects").findOne({ _id: { $eq: document.projectID }, $or: [
{"owner": {$eq: context.accessData.user.id}},
{"contributors": {$in: [context.accessData.user.id]}}
]});
return project !== null;
}
},
document: AccessPermissions.ReadWrite
}
import z from "zod";
export const assetSchema = z.strictObject({
_id: z.string(),
projectID: z.string(),
name: z.string(),
supply: z.number()
});
export type Asset = z.infer<typeof assetSchema>;
The create validator function receives the same context object, and the document that the user is trying to create. You can return false or throw an Error or AccessValidatorError to reject the operation.
The context object looks like this:
type AccessValidatorContext<TAccessPayload extends AccessPayload> = {
database: string;
collection: string;
action: "create" | "update";
accessData: TAccessPayload;
client: MongoClient;
}
Update validator
Let say the Project schema has a lockedBy field that indicates if a project is locked for editing by a specific user. We want to prevent updates to the document if it's locked by another user.
import { defineUpdateAccessValidator, type AccessDefinition } from "@mongalayer/server";
const updateValidator = defineUpdateAccessValidator<Project>()(
[ "lockedBy" ], // Only the specified fields (+ _id) are fetched for validation
async (
context,
document, // _id and lockedBy fields only
update
) => {
return update.$set === void 0
|| update.$set.lockedBy === void 0
|| (
update.$set.lockedBy === context.accessData.user.id &&
document.lockedBy === context.accessData.user.id
);
}
);
const editorProjectAccess: AccessDefinition<Project> = {
role: "editor",
validators: {
update: updateValidator
},
document: AccessPermissions.ReadUpdate
};
The defineUpdateAccessValidator helper lets you specify which fields to fetch from the existing document to use in the validator. This avoids loading the entire document when you only need a few fields.
The update validator receives the same context object as the create validator, the existing document (with only the specified fields), and the update object that the user is trying to apply.