first commit

This commit is contained in:
2026-05-29 08:20:56 +01:00
commit 949299f58d
6368 changed files with 527176 additions and 0 deletions

View File

@@ -0,0 +1,99 @@
# `@suaveplan/api-host/empty-secrets`
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs -->
## Purpose
`EmptySecrets` is the no-op default `SecretsLike` implementation the
host injects into every `PluginContext` / `RequestContext` when an
operator constructs a `HostAdapters` bag without a `secrets` slot. It
preserves the foundation-fix-gap-3 design intent that plugin authors
can always destructure `ctx.secrets` without a defensive null guard —
the failure mode is an explicit, debuggable `SECRETS.SECRET_NOT_FOUND`
throw rather than a `TypeError` from a missing field.
In production a real adapter (e.g. `createEnvSecrets` from
`@suaveplan/api-secrets-env`, a Vault-backed adapter, or an HSM bridge)
replaces this default. `EmptySecrets` is exclusively the
"nothing configured" fall-through.
## Features
- `get(name)` resolves to `undefined` for every key.
- `require(name)` throws `SECRETS.SECRET_NOT_FOUND` carrying the
requested key on `context.name` and a sentinel `context.source =
"host.default.empty-secrets"` for correlation.
- `list()` returns the frozen empty `readonly string[]`.
- Zero state — `EmptySecrets` instances are interchangeable; the host
constructs one per `createHost` call so OTel attribute attachment
remains traceable per host instance.
- Stable error identity — every throw is a `SecretsError` so callers
can `instanceof`-narrow without importing the abstract base.
## Usage: Basic
```ts
import { EmptySecrets } from "@suaveplan/api-host/empty-secrets";
const secrets = new EmptySecrets();
const maybe = await secrets.get("STRIPE_SECRET_KEY");
// maybe === undefined — `EmptySecrets` never resolves a value.
```
## Usage: Advanced
A custom host that injects `EmptySecrets` only in `development` mode so
production deploys fail fast when the real adapter wiring is missing:
```ts
import { createHost } from "@suaveplan/api-host/host";
import type { HostAdapters } from "@suaveplan/api-host/host";
import { EmptySecrets } from "@suaveplan/api-host/empty-secrets";
import { createEnvSecrets } from "@suaveplan/api-secrets-env";
function buildSecrets(mode: "production" | "development") {
if (mode === "production") {
return createEnvSecrets({ allowedKeys: ["STRIPE_SECRET_KEY"] });
}
return new EmptySecrets();
}
const adapters: HostAdapters = {
/* ...other slots... */
secrets: buildSecrets(process.env["NODE_ENV"] as never),
} as never;
const host = await createHost({ adapters, plugins: [], mode: "development" });
```
## API Reference
`class EmptySecrets implements SecretsLike`
- `get(name: string): Promise<string | undefined>` — always
`undefined`.
- `require(name: string): Promise<string>` — always throws
`SecretsError` with code `SECRETS.SECRET_NOT_FOUND` and `context: {
name, source: "host.default.empty-secrets" }`.
- `list(): readonly string[]` — frozen empty array, stable across
calls.
## Implementation Notes
`EmptySecrets` is deliberately the simplest possible `SecretsLike`
implementation — every method is constant-time and allocation-free
(the frozen empty array is module-scoped and shared). The class lives
inside `@suaveplan/api-host` rather than `@suaveplan/api-secrets`
because the policy decision "what does the host inject when no adapter
is provided" is a host-orchestration concern, not a secrets-domain
concern. Sibling adapters (`api-secrets-env`, hypothetical
`api-secrets-vault`) stay focused on concrete sources.
Error codes are sourced from `@suaveplan/api-secrets`'s
`SECRETS_CODES` registry so the throw uses the canonical
namespace string (`SECRETS.SECRET_NOT_FOUND`) every other secrets
source uses. This keeps audit / OTel log dashboards consistent
regardless of which source surfaced the failure.