installation
Install duck-iam, configure a storage adapter, and run your first permission check in under five minutes.
Prerequisites
- Node.js 18+
- TypeScript 5.0+. duck-iam uses const type parameters and the
satisfiesoperator. - npm, bun, or pnpm.
Install duck-iam
The core engine, memory adapter, type builders, and all integration entry points are in a single package.
Install the package
# npm
npm install @gentleduck/iam
# bun
bun add @gentleduck/iam
# pnpm
pnpm add @gentleduck/iam# npm
npm install @gentleduck/iam
# bun
bun add @gentleduck/iam
# pnpm
pnpm add @gentleduck/iamBasic setup
import { createAccessConfig, MemoryAdapter } from "@gentleduck/iam";
// 1. Define your application's actions, resources, and scopes
const access = createAccessConfig({
actions: ["create", "read", "update", "delete", "manage"],
resources: ["post", "comment", "user", "team"],
scopes: ["org"],
} as const);
// 2. Define roles using typed builders
const viewer = access
.defineRole("viewer")
.grantRead("post", "comment")
.build();
const editor = access
.defineRole("editor")
.inherits("viewer")
.grant("create", "post")
.grant("update", "post")
.grant("create", "comment")
.build();
const admin = access
.defineRole("admin")
.inherits("editor")
.grant("delete", "post")
.grant("delete", "comment")
.grantCRUD("user")
.grant("manage", "team")
.build();
// 3. Create an adapter
const adapter = new MemoryAdapter({
roles: [viewer, editor, admin],
assignments: {
"user-1": ["admin"],
"user-2": ["editor"],
"user-3": ["viewer"],
},
});
// 4. Create the engine
export const engine = access.createEngine({ adapter });import { createAccessConfig, MemoryAdapter } from "@gentleduck/iam";
// 1. Define your application's actions, resources, and scopes
const access = createAccessConfig({
actions: ["create", "read", "update", "delete", "manage"],
resources: ["post", "comment", "user", "team"],
scopes: ["org"],
} as const);
// 2. Define roles using typed builders
const viewer = access
.defineRole("viewer")
.grantRead("post", "comment")
.build();
const editor = access
.defineRole("editor")
.inherits("viewer")
.grant("create", "post")
.grant("update", "post")
.grant("create", "comment")
.build();
const admin = access
.defineRole("admin")
.inherits("editor")
.grant("delete", "post")
.grant("delete", "comment")
.grantCRUD("user")
.grant("manage", "team")
.build();
// 3. Create an adapter
const adapter = new MemoryAdapter({
roles: [viewer, editor, admin],
assignments: {
"user-1": ["admin"],
"user-2": ["editor"],
"user-3": ["viewer"],
},
});
// 4. Create the engine
export const engine = access.createEngine({ adapter });Check permissions
// Simple boolean check
const canCreate = await engine.can(
"user-2",
"create",
{ type: "post", attributes: {} }
);
// -> true (editor can create posts)
const canDelete = await engine.can(
"user-2",
"delete",
{ type: "post", attributes: {} }
);
// -> false (editor cannot delete posts)
// Full decision with metadata
const decision = await engine.check(
"user-2",
"delete",
{ type: "post", attributes: {} }
);
// -> { allowed: false, effect: "deny", reason: "...", duration: 0.12, ... }
// Debug why a permission was granted or denied
const trace = await engine.explain(
"user-2",
"delete",
{ type: "post", attributes: {} }
);
// -> detailed trace of every policy, rule, and condition evaluated// Simple boolean check
const canCreate = await engine.can(
"user-2",
"create",
{ type: "post", attributes: {} }
);
// -> true (editor can create posts)
const canDelete = await engine.can(
"user-2",
"delete",
{ type: "post", attributes: {} }
);
// -> false (editor cannot delete posts)
// Full decision with metadata
const decision = await engine.check(
"user-2",
"delete",
{ type: "post", attributes: {} }
);
// -> { allowed: false, effect: "deny", reason: "...", duration: 0.12, ... }
// Debug why a permission was granted or denied
const trace = await engine.explain(
"user-2",
"delete",
{ type: "post", attributes: {} }
);
// -> detailed trace of every policy, rule, and condition evaluatedBatch permission checks
Batch-load permissions for a user, useful for hydrating client-side UI:
const permissions = await engine.permissions("user-2", [
{ action: "create", resource: "post" },
{ action: "update", resource: "post" },
{ action: "delete", resource: "post" },
{ action: "manage", resource: "team" },
]);
// -> { "create:post": true, "update:post": true, "delete:post": false, "manage:team": false }const permissions = await engine.permissions("user-2", [
{ action: "create", resource: "post" },
{ action: "update", resource: "post" },
{ action: "delete", resource: "post" },
{ action: "manage", resource: "team" },
]);
// -> { "create:post": true, "update:post": true, "delete:post": false, "manage:team": false }Optional Peer Dependencies
| Integration | Peer dependency | Install |
|---|---|---|
| React client | react >= 18.0.0 | bun add react |
| Vue client | vue >= 3.3.0 | bun add vue |
| Prisma adapter | @prisma/client >= 5.0.0 | bun add @prisma/client |
| Drizzle adapter | drizzle-orm >= 0.30.0 | bun add drizzle-orm |
| Redis adapter | ioredis >= 5.0.0 or redis >= 4.0.0 | bun add ioredis |
The core engine, memory adapter, HTTP adapter, and all server integrations (Express, Hono, NestJS, Next.js) have zero peer dependencies. The Drizzle schemas (/adapters/drizzle/schema/{pg,mysql,sqlite}) require drizzle-orm since they re-export Drizzle table builders.
Import Paths
duck-iam uses subpath exports:
// Core engine, builders, memory adapter, types
import { Engine, defineRole, policy, when, MemoryAdapter } from "@gentleduck/iam";
// Server integrations
import { accessMiddleware, guard } from "@gentleduck/iam/server/express";
import { accessMiddleware, guard } from "@gentleduck/iam/server/hono";
import { Authorize, nestAccessGuard } from "@gentleduck/iam/server/nest";
import { withAccess, checkAccess, getPermissions } from "@gentleduck/iam/server/next";
// Client libraries
import { createAccessControl } from "@gentleduck/iam/client/react";
import { createVueAccess } from "@gentleduck/iam/client/vue";
import { AccessClient } from "@gentleduck/iam/client/vanilla";
// Storage adapters
import { MemoryAdapter } from "@gentleduck/iam/adapters/memory";
import { PrismaAdapter } from "@gentleduck/iam/adapters/prisma";
import { DrizzleAdapter } from "@gentleduck/iam/adapters/drizzle";
import { RedisAdapter } from "@gentleduck/iam/adapters/redis";
import { HttpAdapter } from "@gentleduck/iam/adapters/http";
// Drizzle pre-built schemas (pick one dialect)
import * as schema from "@gentleduck/iam/adapters/drizzle/schema/pg";
// or: @gentleduck/iam/adapters/drizzle/schema/mysql
// or: @gentleduck/iam/adapters/drizzle/schema/sqlite// Core engine, builders, memory adapter, types
import { Engine, defineRole, policy, when, MemoryAdapter } from "@gentleduck/iam";
// Server integrations
import { accessMiddleware, guard } from "@gentleduck/iam/server/express";
import { accessMiddleware, guard } from "@gentleduck/iam/server/hono";
import { Authorize, nestAccessGuard } from "@gentleduck/iam/server/nest";
import { withAccess, checkAccess, getPermissions } from "@gentleduck/iam/server/next";
// Client libraries
import { createAccessControl } from "@gentleduck/iam/client/react";
import { createVueAccess } from "@gentleduck/iam/client/vue";
import { AccessClient } from "@gentleduck/iam/client/vanilla";
// Storage adapters
import { MemoryAdapter } from "@gentleduck/iam/adapters/memory";
import { PrismaAdapter } from "@gentleduck/iam/adapters/prisma";
import { DrizzleAdapter } from "@gentleduck/iam/adapters/drizzle";
import { RedisAdapter } from "@gentleduck/iam/adapters/redis";
import { HttpAdapter } from "@gentleduck/iam/adapters/http";
// Drizzle pre-built schemas (pick one dialect)
import * as schema from "@gentleduck/iam/adapters/drizzle/schema/pg";
// or: @gentleduck/iam/adapters/drizzle/schema/mysql
// or: @gentleduck/iam/adapters/drizzle/schema/sqliteTypeScript Configuration
{
"compilerOptions": {
"strict": true,
"moduleResolution": "bundler"
}
}{
"compilerOptions": {
"strict": true,
"moduleResolution": "bundler"
}
}strict enables const type parameter inference for type-safe role and policy builders. bundler module resolution is required for subpath exports; "moduleResolution": "nodenext" also works.
Next Steps
- Quick Start: end-to-end walkthrough with roles, policies, server middleware, and client hooks.
- Core Concepts: deep dive into the RBAC + ABAC evaluation model.
- Integrations: set up Express, Hono, NestJS, or Next.js middleware.