# Providers and adapters

How small interfaces, self-registering adapters, and the container make backend swaps a config change.

Every backend concern is one small interface with several adapters, resolved only by the container. Changing `sqlite` to `postgres` or `memory` to `redis` stays a config edit, never a handler rewrite.

## The mental model

| Role | Rule |
|---|---|
| Interface | Stays small. Never add methods (it breaks implementers); add a new interface instead. |
| Adapter | Self-registers; one subdirectory per backend. |
| Container | Sole resolver; owns construction, caching, retry, and close. |
| Config | Selects the adapter (`DB__PRIMARY__ADAPTER=postgres`) plus options. |

Application code speaks the interface; the container speaks the registry:

```go
conn, err := db.Open(db.SQLite, db.Options{Path: ":memory:"})
```

Each package follows one pattern:

```go
// Package db: one small interface.
type DB interface {
	Query(ctx context.Context, query string, args ...any) (Rows, error)
	Exec(ctx context.Context, query string, args ...any) (int64, error)
	Ping(ctx context.Context) error
	Close(ctx context.Context) error
	Dialect() string
}

// Adapter sqlite registers itself; postgres registers itself elsewhere.
// Call Register once from main (no init magic); app code holds no globals.
func Register() { _ = db.Register(db.SQLite, New) }
```

## Swap an adapter with config

Development uses zero infrastructure; production changes names only:

```sh
DB__PRIMARY__ADAPTER=sqlite
DB__PRIMARY__PATH=data/app.db
CACHE__DEFAULT__ADAPTER=memory
QUEUE__DEFAULT__ADAPTER=memory
```

```sh
DB__PRIMARY__ADAPTER=postgres
DB__PRIMARY__DSN=postgres://app:secret@db:5432/app
DB__PRIMARY__MAX_CONNS=20
CACHE__DEFAULT__ADAPTER=redis
CACHE__DEFAULT__ADDR=redis:6379
```

Env equivalents use `____` with no prefix: `DB__PRIMARY__ADAPTER=postgres`, `DB__PRIMARY__MAXCONNS=20`, `CACHE__DEFAULT__ADAPTER=redis`. Handlers keep calling `c.DB()` and `c.Cache()`; register the adapter module for the name you choose. `zever doctor` reports per-service health. Under zero-infra defaults, the bookings example documents expected FAILs (missing API key, JWT secret, cities file, embed FS): config gaps, not framework bugs.

Most swaps are invisible to handlers: placeholders stay `?` on both `db` drivers (postgres rewrites to `$n` before pgx) and route params read through `router.Param`. Where backends differ, the adapter fails fast or documents the gap. Two known gaps:

- `MinConns` is a pool minimum on postgres but `SetMaxIdleConns` on sqlite.
- Regex route constraints exist on fiber but not stdhttp.

## Pick from the adapter matrix

An excerpt follows. The full table covers all 37 services from `ai` to `workflow`, each with a default adapter (zero-infra except `ai`): see the [adapters matrix](https://zenta-dev.github.io/zever/reference/adapters-matrix/).

| Service | Adapters | Default |
|---|---|---|
| `db` | `sqlite`, `postgres` | `sqlite` |
| `cache` | `memory`, `redis`, `db` | `memory` |
| `queue` | `memory`, `redis`, `db` | `memory` |
| `router` | `fiber`, `stdhttp` | `stdhttp` |
| `auth` | `jwt`, `session`, `oidc` | `jwt` |
| `session` | `memory`, `redis`, `db` | `memory` |
| `storage` | `local`, `s3`, `r2` | `local` |
| `search` | `db`, `postgres`, `meilisearch`, `sqlite` | `db` |
| `vectorstore` | `db`, `sqlite`, `pgvector`, `qdrant` | `db` |
| `payment` / `billing` | `stub`, `stripe`, `paddle` | `stub` |
| `log` | `noop`, `zerolog`, `slog`, `pretty` | `slog` |
| `secrets` | `env`, `vault` | `env` with `ZEVER` prefix |

## Add a new adapter

1. Implement the existing interface exactly: no extra exported methods on the facade.
2. Parse options with typed readers and return `InvalidOptionsError` on unparsable values.
3. Never log raw option maps; expose redaction through `Redact` / `RedactedServices`.
4. Make shutdown cheap and idempotent: implement `Close(ctx)`, `Close()`, or `Stop()` so the container's probing finds it.
5. Keep tests hermetic (sqlite `:memory:`, in-memory fakes) and gate live tests behind env such as `POSTGRES_DSN`.

Unexported adapter structs are named `driver` with receiver `d`; timeouts are `time.Duration` with `Default*Timeout` constants.

## Design decisions and trade-offs

- **Close shape is a provider contract.** See [Container](https://zenta-dev.github.io/zever/concepts/container/#close-services-in-order) for which signature each adapter takes.
- **Fail closed.** Unsupported ORM features return `dialect.ErrUnsupportedByDialect` (prefixed `orm:`) instead of wrong SQL, strict config rejects unknown keys at load, and redaction masks secrets everywhere.

## Troubleshooting

<details>
<summary>The swap changes nothing at runtime</summary>

Cause: an env override (`CACHE__DEFAULT__ADAPTER`) beats the file. Fix: unset or correct the env variable.
</details>

<details>
<summary>An accessor fails with `UnknownAdapterError`</summary>

Cause: a typo in the adapter name (config accepts any non-empty name), or its `Register()` was never called. Fix: match the matrix spelling (`postgres`, not `postgresql`) and register the adapter module before resolving.
</details>

<details>
<summary>A handler sees different SQL on postgres</summary>

Cause: a misunderstanding of the placeholder rewrite. Fix: keep writing `?`; the postgres driver rewrites to `$n` before pgx.
</details>

<details>
<summary>A fiber-only route constraint breaks stdhttp</summary>

Cause: regex constraints exist on fiber only. Fix: restructure the route using constructs both drivers share, or pin `fiber`.
</details>

<details>
<summary>A live adapter test fails in CI</summary>

Cause: the test needs a real backend but no DSN is set. Fix: gate it behind env (`POSTGRES_DSN='postgres://user:pass@localhost:5432/test?sslmode=disable'`).
</details>

## Related

- [Container](https://zenta-dev.github.io/zever/concepts/container/): Sole-resolver mechanics and accessor table.
- [Lifecycle](https://zenta-dev.github.io/zever/concepts/lifecycle/): When resolution and close run.
- [Compiler boundary](https://zenta-dev.github.io/zever/concepts/compiler-boundary/): Why providers never import the compiler.
- [Query builder](https://zenta-dev.github.io/zever/guides/data/query-builder/): Dialect gating over db.DB.
