field-guard

📁 mohhh-ok/field-guard 📅 3 days ago
4
总安装量
4
周安装量
#51991
全站排名
安装命令
npx skills add https://github.com/mohhh-ok/field-guard --skill field-guard

Agent 安装分布

opencode 4
gemini-cli 4
github-copilot 4
codex 4
kimi-cli 4
cursor 4

Skill 文档

field-guard skill

field-guard is a lightweight, fully type-safe, field-level access control library for TypeScript with zero runtime dependencies.

Import

import { defineGuard, combineGuards, mergeFieldVerdicts } from "field-guard";

Define a 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
  },
});

Policy Modes

Value Behavior
true Allow all fields for this level
false Deny all fields for this level
{ id: true, name: true } Whitelist — only listed fields allowed
{ secret: false } Blacklist — all fields except listed ones

fields and policy are both optional. You can omit either or both.

.withCheck<Target>()

Resolves the access level based on context and a target object. Use verdictMap[level] to return the correct FieldVerdict.

const 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()

Computes extra properties from context. Can also produce row-level filter conditions.

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-level

combineGuards

Bundles multiple guards for different resources and binds them all at once with a single .for() call.

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" });

FieldVerdict Helpers

verdict.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 fields

mergeFieldVerdicts

Merges multiple verdicts with "union" (any-of) or "intersection" (all-of) strategy.

// 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);

Also available as mergeVerdicts on every guard instance:

const verdict = guard.mergeVerdicts("union", { owner: true, admin: false });

Type Safety

All fields, levels, and verdicts are inferred from your definitions. The TypeScript compiler immediately flags:

  • Unknown field names in policy
  • Unknown levels in verdictMap lookups
  • Incorrect return types from .withCheck()

Type errors from AI-generated code become instant feedback — no runtime surprises.