types & namespaces
I-prefixed interfaces under per-module namespaces - the type-import contract for duck-iam consumers.
Convention
duck-iam ships TypeScript types under per-module namespaces. Two rules:
- Interfaces use the
Iprefix -IPolicy,IRule,IDecision,IAccessRequest, etc. - Type aliases stay bare -
Effect,Operator,Mode,PolicyCombine,CombiningAlgorithm.
Every type lives inside one namespace export per module. Consumers import the namespace once and access members through it:
import type { AccessControl, IamRequest } from '@gentleduck/iam'
function check(policy: AccessControl.IPolicy, req: IamRequest.IAccessRequest): AccessControl.IDecision {
// ...
}
The pattern matches the rest of the duck-* monorepo (calendar, upload, etc.). Type aliases stay bare because adding I to Effect reads awkwardly and conveys no information.
Namespace map
Core namespaces
Exported from @gentleduck/iam:
| Namespace | Members |
|---|---|
AccessControl | IPolicy, IRule, IPermission, IRole, IDecision, ICondition, IConditionGroup, Effect, Operator, CombiningAlgorithm, PolicyCombine, Mode, ModeResult, ModePermissionMap, OpFn |
Request | IAccessRequest, ISubject, IResource, IEnvironment, IScopedRole |
Adapter | IAdapter, IPolicyStore, IRoleStore, ISubjectStore, IReadOptions |
Primitives | Attributes, AttributeValue, Scalar |
Client | PermissionKey, PermissionMap, IPermissionCheck |
DotPath | DotPaths, FlexibleDotPaths, DollarPaths, FlexibleDollarPaths, PathValue, FieldValue, ConditionValue, AttrValue, AttrValueAt, SubjectAttrs, SubjectAttrShape, ResourceAttrs, ResourceAttrShape, EnvAttrs, EnvAttrShape, ResourceAttrMap, ResolvedResourceAttrs, ResolvedResourceAttrPaths, IDefaultContext, IAnyAttributes |
IamEngineTypes | IConfig, IHooks, IAdmin, IMetricsEvent, IInvalidator, IInvalidateEvent, ISnapshot, IImportOptions, IImportResult, IHealth |
Evaluate | Combiner, IIndexedRule, IPolicyRuleIndex |
Explain | IResult, ISubjectInfo, IPolicyTrace, IRuleTrace, ILeafTrace, IGroupTrace, Trace |
Validate | IIssue, IResult, ValidationCode |
Config | IAccessConfig, IAccessConfigInput, InferAction, InferResource, InferScope, InferRole |
Module-local namespaces
Each integration module exports its own option-bag namespace (added in 2.0):
| Namespace | Source | Members |
|---|---|---|
Memory | @gentleduck/iam/adapters/memory | IInit |
File | @gentleduck/iam/adapters/file | IInit, IFS, IState |
Http | @gentleduck/iam/adapters/http | IConfig (baseUrl, fetch, headers, retry, timeout, circuit-breaker) |
Redis | @gentleduck/iam/adapters/redis | IConfig, ILike (minimal client surface) |
Drizzle | @gentleduck/iam/adapters/drizzle | IConfig (db, tables, ops) |
Express | @gentleduck/iam/server/express | IOptions, IAdminRouterOptions, IAdminAuthorize |
Hono | @gentleduck/iam/server/hono | IOptions, IAdminOptions, IAdminAuthorize, IRouterLike |
Nest | @gentleduck/iam/server/nest | IAuthorizeMeta, IGuardOptions, IAdminOptions, IAdminAuthorize |
Next | @gentleduck/iam/server/next | IWithAccessOptions, IMiddlewareOptions, IAdminOptions, IAdminAuthorize |
ReactClient | @gentleduck/iam/client/react | IContextValue |
RedisInvalidator | @gentleduck/iam/invalidators/redis | IConfig, IPubSubLike |
Metrics | @gentleduck/iam/observability/metrics | IAggregator, ISnapshot, IConfig |
The class IamEngine lives next to the IamEngineTypes namespace - same name avoided by suffixing the type-only namespace. IamMemoryAdapter / IamFileAdapter / etc. are classes; their config namespaces (Memory, File, Http, Redis, Drizzle) hold the option bags. ReactClient is named so to avoid clashing with the React package namespace at the import site.
Why namespaces
Three reasons.
1. Avoid name collisions across modules. IResult exists in both Validate and Explain. Without namespaces, one of them needs a longer name. With namespaces, both stay short and the context is explicit at the call site (IamValidate.IResult vs Explain.IResult).
2. One import per module. Instead of:
import type {
IPolicy, IRule, IDecision, ICondition, IConditionGroup,
Effect, CombiningAlgorithm, PolicyCombine,
} from '@gentleduck/iam'
You write:
import type { AccessControl } from '@gentleduck/iam'
...and access everything through AccessControl.X. Reading the call site you know which module each type belongs to.
3. Tree-shake friendliness. import type { AccessControl } is a single TypeScript type import - the bundler doesn't trace 14 distinct re-exports, and sideEffects: false in package.json lets it drop unused namespaces entirely.
Migrating from flat imports
If you have older code using the flat names:
// Before
import type { Policy, Rule, Decision } from '@gentleduck/iam'
function check(p: Policy): Decision { /* ... */ }
Two mechanical changes:
// After
import type { AccessControl } from '@gentleduck/iam'
function check(p: AccessControl.IPolicy): AccessControl.IDecision { /* ... */ }
- Drop the flat names from the import; pull in the namespace instead.
- Prefix references:
Policy->AccessControl.IPolicy,Rule->AccessControl.IRule, etc.
The runtime values (IamEngine, IamMemoryAdapter, policy, defineRole, builders, etc.) are unchanged - only type imports moved.
See also
- Engine -
IamEngineTypes.IConfigandIamEngineTypes.IHooksshapes. - Adapters -
IamAdapter.IAdaptercontract. - Conditions -
AccessControl.IConditionandAccessControl.IConditionGroup.