Skip to main content

VAULT PROVIDER

A vault provider is a TypeScript module that implements secret storage and retrieval for a custom backend. The file lives in extensions/vaults/ and exports a vault object that swamp loads at runtime.

For vault configuration and usage (creating vaults, storing secrets, CEL integration), see the Vaults reference.

Export Shape

import { z } from "npm:zod@4";

export const vault = {
  type: "@mycollective/my-vault",
  name: "My Vault",
  description: "Store secrets in my backend",
  configSchema: z.object({
    region: z.string().describe("Backend region"),
  }),
  createProvider(
    name: string,
    config: Record<string, unknown>,
  ): VaultProvider {
    // validate config, return provider instance
  },
};

Fields

Field Type Description
type string Unique identifier in @collective/name format. Reserved collectives: swamp, si.
name string Human-readable display name.
description string Short description shown in swamp vault type search.
configSchema z.ZodTypeAny Zod schema that validates the config object passed at vault creation time.
createProvider (name: string, config: Record<string, unknown>) => VaultProvider Factory that returns a provider instance. name is the vault's user-assigned name.

VaultProvider Interface

The object returned by createProvider must implement four methods.

Method Signature Description
get (secretKey: string) => Promise<string> Retrieve a secret value by key. Throw if the key does not exist.
put (secretKey: string, secretValue: string) => Promise<void> Store a secret value, creating or overwriting the key.
list () => Promise<string[]> List all secret key names. Values are never returned.
getName () => string Return the vault's user-assigned name.

VaultAnnotationProvider Interface

Vault providers can optionally support secret annotations — metadata (URL, notes, labels) attached to individual secrets. A provider opts in by implementing the VaultAnnotationProvider interface alongside VaultProvider.

Swamp detects annotation support at runtime via duck typing: if the object returned by createProvider has getAnnotation, putAnnotation, deleteAnnotation, and listAnnotations methods, the provider supports annotations. No schema or adapter changes are needed.

Method Signature Description
getAnnotation (secretKey: string) => Promise<VaultAnnotation | null> Retrieve the annotation for a secret. Returns null if unset.
putAnnotation (secretKey: string, annotation: VaultAnnotation) => Promise<void> Store an annotation for a secret, creating or overwriting it.
deleteAnnotation (secretKey: string) => Promise<void> Remove the annotation for a secret.
listAnnotations () => Promise<Map<string, VaultAnnotation>> List all annotations keyed by secret name.

VaultAnnotation

The annotation value object passed to and returned from the provider methods.

Field Type Required Description
url string No URL associated with this secret
notes string No Free-text notes
labels Record<string, string> No Key=value metadata labels
updatedAt Date Yes Timestamp of the last update

Minimal Example

import { z } from "npm:zod@4";

interface VaultProvider {
  get(secretKey: string): Promise<string>;
  put(secretKey: string, secretValue: string): Promise<void>;
  list(): Promise<string[]>;
  getName(): string;
}

const configSchema = z.object({
  endpoint: z.string().url().describe("Backend API endpoint"),
  token: z.string().describe("Authentication token"),
});

export const vault = {
  type: "@mycollective/kv-vault",
  name: "KV Vault",
  description: "Store secrets in a key-value API",
  configSchema,

  createProvider(
    name: string,
    config: Record<string, unknown>,
  ): VaultProvider {
    const { endpoint, token } = configSchema.parse(config);
    const headers = { Authorization: `Bearer ${token}` };

    return {
      async get(secretKey: string): Promise<string> {
        const res = await fetch(`${endpoint}/secrets/${secretKey}`, {
          headers,
        });
        if (!res.ok) throw new Error(`Secret "${secretKey}" not found`);
        const data = await res.json();
        return data.value;
      },

      async put(secretKey: string, secretValue: string): Promise<void> {
        await fetch(`${endpoint}/secrets/${secretKey}`, {
          method: "PUT",
          headers: { ...headers, "Content-Type": "application/json" },
          body: JSON.stringify({ value: secretValue }),
        });
      },

      async list(): Promise<string[]> {
        const res = await fetch(`${endpoint}/secrets`, { headers });
        const data = await res.json();
        return data.keys;
      },

      getName(): string {
        return name;
      },
    };
  },
};

Adding Annotation Support

A provider that implements all four VaultAnnotationProvider methods alongside VaultProvider is recognized as annotation-capable. Detection is via duck typing — no registration or schema change is required.

import { z } from "npm:zod@4";

interface VaultAnnotation {
  url?: string;
  notes?: string;
  labels: Record<string, string>;
  updatedAt: Date;
}

interface VaultProvider {
  get(secretKey: string): Promise<string>;
  put(secretKey: string, secretValue: string): Promise<void>;
  list(): Promise<string[]>;
  getName(): string;
}

interface VaultAnnotationProvider {
  getAnnotation(secretKey: string): Promise<VaultAnnotation | null>;
  putAnnotation(secretKey: string, annotation: VaultAnnotation): Promise<void>;
  deleteAnnotation(secretKey: string): Promise<void>;
  listAnnotations(): Promise<Map<string, VaultAnnotation>>;
}

const configSchema = z.object({
  endpoint: z.string().url().describe("Backend API endpoint"),
  token: z.string().describe("Authentication token"),
});

export const vault = {
  type: "@mycollective/kv-vault",
  name: "KV Vault",
  description: "Store secrets in a key-value API",
  configSchema,

  createProvider(
    name: string,
    config: Record<string, unknown>,
  ): VaultProvider & VaultAnnotationProvider {
    const { endpoint, token } = configSchema.parse(config);
    const headers = { Authorization: `Bearer ${token}` };

    return {
      async get(secretKey: string): Promise<string> {
        const res = await fetch(`${endpoint}/secrets/${secretKey}`, {
          headers,
        });
        if (!res.ok) throw new Error(`Secret "${secretKey}" not found`);
        const data = await res.json();
        return data.value;
      },

      async put(secretKey: string, secretValue: string): Promise<void> {
        await fetch(`${endpoint}/secrets/${secretKey}`, {
          method: "PUT",
          headers: { ...headers, "Content-Type": "application/json" },
          body: JSON.stringify({ value: secretValue }),
        });
      },

      async list(): Promise<string[]> {
        const res = await fetch(`${endpoint}/secrets`, { headers });
        const data = await res.json();
        return data.keys;
      },

      getName(): string {
        return name;
      },

      async getAnnotation(
        secretKey: string,
      ): Promise<VaultAnnotation | null> {
        const res = await fetch(
          `${endpoint}/secrets/${secretKey}/annotation`,
          { headers },
        );
        if (res.status === 404) return null;
        const data = await res.json();
        return { ...data, updatedAt: new Date(data.updatedAt) };
      },

      async putAnnotation(
        secretKey: string,
        annotation: VaultAnnotation,
      ): Promise<void> {
        await fetch(`${endpoint}/secrets/${secretKey}/annotation`, {
          method: "PUT",
          headers: { ...headers, "Content-Type": "application/json" },
          body: JSON.stringify({
            ...annotation,
            updatedAt: annotation.updatedAt.toISOString(),
          }),
        });
      },

      async deleteAnnotation(secretKey: string): Promise<void> {
        await fetch(`${endpoint}/secrets/${secretKey}/annotation`, {
          method: "DELETE",
          headers,
        });
      },

      async listAnnotations(): Promise<Map<string, VaultAnnotation>> {
        const res = await fetch(`${endpoint}/annotations`, { headers });
        const data = await res.json();
        const map = new Map<string, VaultAnnotation>();
        for (const [key, ann] of Object.entries(data)) {
          const a = ann as Record<string, unknown>;
          map.set(key, {
            url: a.url as string | undefined,
            notes: a.notes as string | undefined,
            labels: (a.labels as Record<string, string>) ?? {},
            updatedAt: new Date(a.updatedAt as string),
          });
        }
        return map;
      },
    };
  },
};

Packaging

Declare the vault in your extension's manifest:

vaults:
  - kv_vault.ts

The file path is relative to the extensions/vaults/ directory within the extension root.