Loading...
Loading...
Use this skill when working with the field-guard library (TypeScript field-level access control). Provides patterns for defineGuard, withCheck, withDerive, combineGuards, and mergeFieldVerdicts.
npx skill4agent add mohhh-ok/field-guard field-guardimport { defineGuard, combineGuards, mergeFieldVerdicts } from "field-guard";type Ctx = { userId: string; role: "admin" | "user" };
type User = { id: string; email: string; name: string };
const userGuard = defineGuard<Ctx>()({
fields: ["id", "email", "name"],
policy: {
owner: true, // all fields allowed
admin: true, // all fields allowed
other: { id: true, name: true }, // whitelist — only id and name
banned: false, // no fields allowed
},
});| Value | Behavior |
|---|---|
| Allow all fields for this level |
| Deny all fields for this level |
| Whitelist — only listed fields allowed |
| Blacklist — all fields except listed ones |
fieldspolicy.withCheck<Target>()verdictMap[level]FieldVerdictconst userGuard = defineGuard<Ctx>()({
fields: ["id", "email", "name"],
policy: {
owner: true,
other: { id: true, name: true },
},
}).withCheck<User>()(({ ctx, target, verdictMap }) => {
const level = ctx.userId === target.id ? "owner" : "other";
return verdictMap[level];
});
// Evaluate
const g = userGuard.for({ userId: "1", role: "user" });
const verdict = g.check({ id: "1", email: "me@example.com", name: "Me" });
verdict.allowedFields; // ["id", "email", "name"].withDerive()import { eq } from "drizzle-orm";
type Post = { id: string; content: string; authorId: string };
const postGuard = defineGuard<Ctx>()({
fields: ["id", "content", "authorId"],
policy: {
owner: true,
other: { id: true, content: true },
},
})
.withDerive(({ ctx }) => ({
// Row-level filter (e.g. for Drizzle ORM WHERE clause)
where: ctx.role === "admin"
? undefined
: eq(posts.authorId, ctx.userId),
}))
.withCheck<Post>()(({ ctx, target, verdictMap }) => {
const level = ctx.userId === target.authorId ? "owner" : "other";
return verdictMap[level];
});
// Usage
const g = postGuard.for({ userId: "1", role: "user" });
const rows = await db.select().from(posts).where(g.where); // row-level
const results = rows.map((row) => g.check(row).pick(row)); // field-levelcombineGuards.for()const guards = combineGuards<Ctx>()({
users: userGuard,
posts: postGuard,
});
const g = guards.for({ userId: "1", role: "user" });
g.users.check({ id: "1", email: "a@b.com", name: "A" });
g.posts.check({ id: "p1", content: "hello", authorId: "1" });FieldVerdictverdict.allowedFields; // string[] of allowed field names
verdict.coversAll(["id", "name"]); // true if all given fields are allowed
verdict.coversSome(["email"]); // true if any given field is allowed
verdict.pick(obj); // returns object with only allowed fieldsmergeFieldVerdicts"union""intersection"// Union: field allowed if ANY verdict allows it
mergeFieldVerdicts("union", [verdictA, verdictB], fields);
// Intersection: field allowed only if ALL verdicts allow it
mergeFieldVerdicts("intersection", [verdictA, verdictB], fields);mergeVerdictsconst verdict = guard.mergeVerdicts("union", { owner: true, admin: false });policyverdictMap.withCheck()