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

3.3 KiB

secrets-like — SecretsLike re-export and required-secret enforcement

(no submodules)

Purpose

This module is the runtime entry point for the abstract Secrets contract. The canonical SecretsLike interface — get(name), require(name), list() — lives in @suaveplan/api-types/secrets so type-only consumers (the manifest validator, the plugin host signature) can depend on it without pulling api-secrets's runtime. We re-export it here so plugin authors importing from @suaveplan/api-secrets get one canonical entry. The runtime in this file is the host-side helper pair checkRequiredSecrets and enforceRequiredSecrets — both walk a manifest's secrets[] to assert every required: true descriptor resolves before the plugin transitions from installing → installed.

The helpers are split into a returning form and a throwing form so callers can integrate the check into whatever lifecycle plumbing they prefer — discriminated-result for hosts that aggregate failures, exception for hosts that bail fast.

Features

  • Re-export of the canonical SecretsLike from @suaveplan/api-types.
  • SecretsListLike — minimal structural shape (list(): readonly string[]) accepted by the helpers.
  • checkRequiredSecrets — pure, returns { ok: true } | { ok: false; missing }.
  • enforceRequiredSecrets — throws SecretsError(SECRETS.SECRET_REQUIRED_MISSING_AT_START) listing every missing name on context.missing.

Usage: Basic

import { enforceRequiredSecrets } from "@suaveplan/api-secrets/secrets-like";

await enforceRequiredSecrets(ctx.secrets, manifest.secrets);
// throws SECRETS.SECRET_REQUIRED_MISSING_AT_START if any required name absent

Usage: Advanced

Aggregate missing secrets across multiple plugins during a batch install:

import { checkRequiredSecrets } from "@suaveplan/api-secrets/secrets-like";

interface InstallPlan { plugin: string; missing: readonly string[] }

const plans: InstallPlan[] = [];
for (const { name, secretsView, descriptors } of installCandidates) {
    const result = checkRequiredSecrets(secretsView, descriptors);
    if (!result.ok) plans.push({ plugin: name, missing: result.missing });
}
if (plans.length > 0) {
    // present an operator-facing report rather than failing the first plugin
    reportToAdminConsole(plans);
}

API Reference

  • type SecretsLike — re-exported from @suaveplan/api-types/secrets.
  • interface SecretsListLike — { list(): readonly string[] }.
  • function checkRequiredSecrets(secrets, descriptors): RequiredSecretsCheck.
  • function enforceRequiredSecrets(secrets, descriptors): void — throws on failure.

Implementation Notes

The helpers accept SecretsListLike rather than the full SecretsLike so tests can pass a { list: () => [...] } literal without implementing every method. Required-secret enforcement deliberately uses list() rather than calling require() for every entry — list() is documented as cheap (often O(1) over an in-memory map), while require may hit a network or a secret-manager daemon. The throwing form attaches the full missing-names array to error.context.missing, which the @suaveplan/error wire-format adapter renders into the JSON envelope so admin UIs can show every missing name in one shot rather than playing whack-a-mole.