first commit
This commit is contained in:
@@ -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.
|
||||
Reference in New Issue
Block a user