Storage
Public storage service contracts, result semantics, atomic operations, SQLite adapters, and migrations.
Effect Auth exposes focused Effect services, not one Storage service. Programs require only tags used by enabled features. Exported *StoreService, record, row, and input types are the authoritative signatures; construct implementations with each tag's make function.
For deployment choices and ownership, see Storage Overview. For implementation and concurrency guidance, see Custom Database.
Base services
The base contracts are exported from @effect-auth/core/Storage and fail with StorageError.
| Service | Rows and responsibility | Result-sensitive mutations |
|---|---|---|
UserStore | Identity-neutral NewUserRow / UserRow; lookup by ID and insert | User lifecycle stays separate from identifiers and credentials |
IdentityStore | Identity lookup and lifecycle for global email and global/tenant username or custom kinds | Verification, primary selection, replacement, and revocation are identity-scoped CAS mutations; active-value uniqueness permits immediate reuse after revoke/replace |
RegistrationStore | Atomic user + identity + password credential creation | The transaction either commits all three records or none |
CredentialStore | NewPasswordCredentialRow / PasswordCredentialRow; password lookup, insert, update | updatePassword returns the updated row as Option |
SessionStore | NewSessionRow / SessionRow; server-only authentication events, derived summaries, lookup, refresh, rotation, claims, revocation | assureAndRotate compare-and-swaps expected evidence/secret state, writes derived summaries, rotates the secret, and invalidates the old token atomically; omitted claim fields are preserved |
VerificationStore | NewVerificationRow / VerificationRow; one-time challenges and cleanup | consume returns Option and is atomic; deleteExpired returns the deleted count |
SessionStore.findById returns stored rows even when expired or revoked; policy interprets lifecycle fields. VerificationStore.consume succeeds only when a row exists, is unconsumed, matches an optional type, and has expiresAt > consumedAt. Concurrent consumers have at most one winner.
StorageError carries entity, operation, message, and optional cause. StorageError.fromUnknown(entity, operation, cause) maps backend failures without exposing row contents or secrets.
Feature-owned services
Feature contracts and errors live in the named public module. The maintained SQLite bundle implements these services. * requires eligibility and mutation in one atomic statement or transaction; CAS also requires the expected prior state.
| Module | Public service | Contract summary and atomic operations |
|---|---|---|
AuditLog | AuditLogStore | append/query audit events |
LoginApproval | LoginApprovalReviewStore | create and review approval records; review transition * |
LoginRisk | LoginRiskHistory | record/query risk history |
TrustedDevice | TrustedDeviceStore | device lookup, touch, revoke, cleanup |
Passkey | PasskeyCredentialStore | credential lookup/list; sign-count update CAS; revoke |
Totp | TotpFactorStore | factor lifecycle; confirm and last-used update * |
RecoveryCode | RecoveryCodeStore | issue/list/revoke codes; one-time use * |
ApiKey | ApiKeyStore | ID/prefix lookup, list, use update, revoke |
RefreshToken | RefreshTokenStore | ID/hash lookup and family lifecycle; rotation inserts the replacement and retires the source atomically *; reuse marking |
Jwt | JwtRevocationStore | store and find JWT revocations; failures use JwtError |
OAuth | OAuthAccountStore, OAuthIdentityBridgeStore | provider-account links and unlinking; atomic user + optional email identity + provider-account creation for OAuth JIT |
OAuth | OAuthClientStore, OAuthConsentStore, OAuthClientSecretStore | clients, consent, and hashed client secrets |
OAuth | OAuthAuthorizationCodeStore | authorization-code lookup and one-time markConsumed * |
OAuth | OAuthProviderModeTokenStore, OAuthProviderTokenVault | provider-token and encrypted-vault lifecycle; rotation * |
DomainVerification | DomainVerificationStore | owner/domain lookup and status transitions * |
SecurityTimeline | SecurityTimelineStore | append and query security events |
Webhook | WebhookOutboxStore | enqueue, lease due work *, mark outcomes, delete old rows |
Webhook | WebhookReplayStore | idempotent replay claim * and expiry cleanup |
Permission | PermissionStore | CAS-protected definition lifecycle; direct and role grants; role mappings; deterministic active/inactive/exact-scope queries; exact/global authorization checks |
RateLimitStore is public but is not in the maintained SQLite bundle. Supply a feature-specific implementation when required.
IdentityManagement relies on storage, not a preflight availability query, for correctness. Replace, revoke, and primary mutations match owner, active state, and expectedUpdatedAt; revoke additionally prevents removing the final login-eligible identity. Custom adapters must make those checks and writes atomic. The OAuth bridge likewise commits its new user, optional trusted verified-email identity, and provider account together. A provider-account race resolves to the winner; an email uniqueness race becomes an explicit-linking result rather than a partial user or silent merge.
Results, time, and encoding
| Shape | Meaning |
|---|---|
Option.some(row) | Lookup found a row, or a conditional mutation won and returns its resulting row |
Option.none() | Missing or ineligible row; successful operation, not storage failure |
readonly Row[] | Successful list; no matches is an empty array |
boolean | Claim/admission result, as defined by the owning service |
number | Affected-row count for cleanup or deletion |
void | Success; missing rows may be an intentional idempotent no-op |
Driver, timeout, uniqueness, serialization, and decode failures belong in the Effect error channel: StorageError for base stores and the owning module's error elsewhere. Never convert backend failures to Option.none().
Identifiers are branded runtime strings. UnixMillis values are finite epoch-millisecond numbers; use exact boundaries and caller-supplied mutation times, not database time. Preserve optional fields. Metadata, claims, scopes, canonical AMR, transports, request context, and versioned authentication events must round-trip losslessly; SQLite encodes structures as JSON text and booleans as compatible values. Materialized AAL/AMR/timestamps must match central derivation on every read and write. Raw events are security provenance: keep them server-side and never include them in ordinary session HTTP responses. Store hashes and ciphertext unchanged.
Maintained SQLite entry points
| Public module | Entry point |
|---|---|
EffectQbSqliteStorage | makeEffectQbSqliteStorage(executor, options?), EffectQbSqliteAuthStorageLive(executor, options?), D1EffectQbSqliteAuthStorageLive(...) |
DrizzleEffectSqliteStorage | makeDrizzleEffectSqliteStorage(database, options?), DrizzleEffectSqliteAuthStorageLive(database, options?) |
DrizzleD1SqliteStorage | makeD1DrizzleEffectSqliteStorage(...), DrizzleD1SqliteAuthStorageLive(...) |
DrizzleNodeSqliteStorage / DrizzleBunSqliteStorage | makeNodeDrizzleEffectSqliteStorage(...) / makeBunDrizzleEffectSqliteStorage(...) and matching *AuthStorageLive layers |
DrizzleDurableObjectSqliteStorage | makeDurableObjectDrizzleEffectSqliteStorage(...), DrizzleDurableObjectSqliteAuthStorageLive(...) |
DrizzleWasmSqliteStorage | persistent and memory makeWasm*Storage(...) functions and matching DrizzleWasm*AuthStorageLive layers |
All return the same service set; options can replace AuthSqliteTables and tune login-risk history. Constructors acquiring a client are scoped. Provided executors and databases remain application-owned.
Migrations
authStorageMigrations from @effect-auth/core/StorageMigrations is the authoritative ordered array of AuthStorageMigration { id, sql }. It contains 0001_auth_storage through 0026_auth_user_identity, covering the maintained bundle. Apply each entry once, in order, before traffic and record its id. Migration 0026_auth_user_identity upgrades the published email-first user schema to identity-first storage and backfills each existing email as the user's primary global identity. Adapters never migrate automatically.
Assurance v2 alpha reset
Assurance v2 rewrote the baseline 0001_auth_storage and 0006_auth_totp_factor migrations to add session evidence and the TOTP replay floor. There is no compatibility read, backfill, or upgrade migration. Reset development databases created by an earlier alpha and apply the current baseline from empty storage. Do not apply the rewritten SQL over a database that already recorded those migration IDs.