adapters overview
Storage layer for duck-iam — pick a built-in adapter (Memory, Prisma, Drizzle, Redis, HTTP) or implement the Adapter interface for custom backends.
What is an adapter?
Adapters are duck-iam's storage layer. They implement the Adapter interface, which combines three stores:
- PolicyStore — CRUD for access policies (rules, conditions, combining algorithms)
- RoleStore — CRUD for roles (permissions, inheritance, scopes)
- SubjectStore — role assignments, scoped roles, and subject attributes
Every adapter is fully typed against your app's action, resource, role, and scope unions.
import { Engine } from '@gentleduck/iam'
const engine = new Engine({
adapter: yourAdapter,
defaultEffect: 'deny',
cacheTTL: 60,
})import { Engine } from '@gentleduck/iam'
const engine = new Engine({
adapter: yourAdapter,
defaultEffect: 'deny',
cacheTTL: 60,
})Built-in adapters
| Adapter | Use case | Persistence | Peer dep |
|---|---|---|---|
| Memory | Dev, testing, prototyping | None (in-process) | None |
| Prisma | Production w/ Prisma | Any Prisma-supported DB | @prisma/client |
| Drizzle | Production w/ Drizzle | PG, MySQL, SQLite | drizzle-orm |
| Redis | Distributed deploys | Redis-compatible KV | ioredis or redis |
| HTTP | Microservices split | Remote API | None (uses fetch) |
| Custom | Any other backend | Your choice | Your choice |
All adapters are interchangeable. Start with MemoryAdapter during development and switch to a database adapter in production without changing engine, builder, or middleware code.
How adapters fit in
import { Engine } from '@gentleduck/iam'
import { MemoryAdapter } from '@gentleduck/iam/adapters/memory'
// 1. Pick a storage backend
const adapter = new MemoryAdapter({ /* seed */ })
// 2. Wrap it in an engine
const engine = new Engine({ adapter, defaultEffect: 'deny' })
// 3. Use the engine — same API regardless of adapter
const allowed = await engine.can('user-1', 'read', { type: 'post', attributes: {} })import { Engine } from '@gentleduck/iam'
import { MemoryAdapter } from '@gentleduck/iam/adapters/memory'
// 1. Pick a storage backend
const adapter = new MemoryAdapter({ /* seed */ })
// 2. Wrap it in an engine
const engine = new Engine({ adapter, defaultEffect: 'deny' })
// 3. Use the engine — same API regardless of adapter
const allowed = await engine.can('user-1', 'read', { type: 'post', attributes: {} })The engine reads from the adapter on cache miss, then caches policies, roles, and resolved subjects in an LRU with the configured cacheTTL. Adapter writes via engine.admin.* automatically invalidate the relevant caches.
See Comparison & FAQ for selection guidance and answers to common adapter questions.