Files
skills/docs/api-stack/api/api-host/src/empty-secrets/empty-secrets.md
2026-05-29 08:20:56 +01:00

3.7 KiB

@suaveplan/api-host/empty-secrets

(no submodules)

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

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:

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.