Integrations
Where duck-iam plugs into the rest of your stack - adapters, server middleware, client SDKs, cross-instance invalidation, and metrics
Everything outside the policy engine lives here. @gentleduck/iam ships five families of integration, each on its own npm subpath and each replaceable: the adapter decides where policies live, the server helpers turn HTTP requests into decisions, the client SDKs render a pre-computed snapshot, the invalidator keeps a fleet's caches in step, and the metrics aggregator turns decision events into percentiles.
The five families
Integrations are sliced by direction: which way the data moves relative to the engine.
adapters/* is the only family the engine cannot run without. server/* sits in front of the engine and translates a framework's request object into a can / authorize call; client/* sits behind it and consumes a snapshot the server already computed. observability/metrics is a pure sink on the onMetrics hook. invalidators/redis is the only one that flows both ways: it publishes local admin writes and applies remote ones.
| Subpath | Direction | What it does | Pages |
|---|---|---|---|
@gentleduck/iam/adapters/* | inbound | Where policies, roles, assignments, and subject attributes are stored | Adapters overview |
@gentleduck/iam/server/* | request edge | Express / Hono / Next / Nest middleware and admin routers | Server overview |
@gentleduck/iam/client/* | outbound | React / Vue / vanilla SDKs over a serialised permission map | Client overview |
@gentleduck/iam/invalidators/redis | sideways | Signed pub/sub cache invalidation across instances | Redis invalidator |
@gentleduck/iam/observability/metrics | outbound | Rolling p50 / p95 / p99 and allow / deny / fail-open counters | Metrics aggregator |
Every integration and its page
Adapters
| Export | Subpath | Page |
|---|---|---|
IamMemoryAdapter, iamMemoryAdapter | adapters/memory | Memory adapter |
IamFileAdapter, iamFileAdapter | adapters/file | File adapter |
IamPrismaAdapter, iamPrismaAdapter | adapters/prisma | Prisma adapter |
IamDrizzleAdapter, iamDrizzleAdapter | adapters/drizzle, adapters/drizzle/pg, adapters/drizzle/mysql, adapters/drizzle/sqlite | Drizzle adapter |
IamRedisAdapter, iamRedisAdapter | adapters/redis | Redis adapter |
IamHttpAdapter, iamHttpAdapter | adapters/http | HTTP adapter |
IamAdapter.IAdapter (the interface) | root entry, type-only | Custom adapter |
Not sure which one? Choosing an adapter has the feature matrix and the decision tree.
Server
| Framework | Subpath | Page |
|---|---|---|
| Framework-agnostic helpers | server | Generic helpers |
| Next.js app router | server/next | Next.js |
| Express | server/express | Express |
| Hono | server/hono | Hono |
| NestJS | server/nest | NestJS |
Client
| Target | Subpath | Page |
|---|---|---|
| The wire shape itself | type-only | PermissionMap reference |
| Vanilla JS | client | Vanilla JS |
| React | client/react | React |
| Vue | client/vue | Vue |
Invalidation and observability
| Export | Subpath | Page |
|---|---|---|
createIamRedisInvalidator | invalidators/redis | Redis invalidator |
iamCreateMetricsAggregator | observability/metrics | Metrics aggregator |
Wiring all five at once
A production deployment that uses one of each. Every symbol below is a real export; check each linked page for its full option table.
import { and, eq, isNull, or } from 'drizzle-orm'
import { drizzle } from 'drizzle-orm/node-postgres'
import { IamEngine } from '@gentleduck/iam/core'
import { IamDrizzleAdapter } from '@gentleduck/iam/adapters/drizzle'
import { iamAssignments, iamPolicies, iamRoles, iamSubjectAttrs } from '@gentleduck/iam/adapters/drizzle/pg'
import { iamAdminRouter, iamGuard } from '@gentleduck/iam/server/express'
import { createIamRedisInvalidator } from '@gentleduck/iam/invalidators/redis'
import { iamCreateMetricsAggregator } from '@gentleduck/iam/observability/metrics'
const db = drizzle(pool)
// 1. inbound: where the authorization data lives
const adapter = new IamDrizzleAdapter({
db,
tables: { policies: iamPolicies, roles: iamRoles, assignments: iamAssignments, attrs: iamSubjectAttrs },
ops: { and, eq, isNull, or }, // isNull and or are optional; omitting either warns and takes a slower path
})
// 2. sideways: every node drops its caches when any node writes
const invalidator = createIamRedisInvalidator({
client: redisPubSub,
secret: process.env.IAM_INVALIDATE_SECRET,
})
// 3. outbound: rolling latency + allow/deny counters
const metrics = iamCreateMetricsAggregator({ sampleSize: 2000 })
const engine = new IamEngine({
adapter,
invalidator,
defaultEffect: 'deny',
mode: 'production',
hooks: { onMetrics: metrics.record },
})
await engine.preload() // warm the policy and role caches before first request
// 4. request edge
app.delete('/posts/:id', iamGuard(engine, 'delete', 'post'), handler)
app.use('/admin/iam', iamAdminRouter(engine, { authorize: (req) => req.user?.role === 'admin' })(Router))
app.get('/healthz', async (_, res) => res.json(await engine.healthCheck()))
app.get('/metrics', (_, res) => res.json(metrics.snapshot()))
createIamRedisInvalidator accepts unsigned envelopes when secret is omitted, and warns once per process. In that mode anyone with PUBLISH rights on the channel can wipe every node's cache. Set secret in production - see Redis invalidator.How the pieces sit in a fleet
Two app instances sharing one database and one Redis. The adapter is the source of truth; Redis carries only invalidation traffic.
Only the cache-miss edges reach the adapter: with cacheTTL at its default of 60 seconds, a warm node answers from its own LRU. The publish edge exists so Node B does not serve a stale decision for up to a full TTL after Node A revokes a role. Each node aggregates its own metrics, so scrape both.
What is not here
- The policy engine itself - see Core and Engine.
- The fluent policy and role builder - see Policies and Roles.
- The validator and JSON schema - see Validation and JSON schema.
createIam()typed config - see Config.
See also
- Adapters overview - the storage layer, method by method
- Choosing an adapter - feature matrix and decision tree
- Production checklist - what to turn on before you ship