Skip to content

Feature flags and storage

Feature flags

Composable backends: memory (overrides/tests), env (FLAG_<NAME>) and composite (first to decide wins).

import {
  CompositeFeatureFlagBackend,
  EnvFeatureFlagBackend,
  FeatureFlags,
  MemoryFeatureFlagBackend,
  makeFlagGuard,
} from "tempest-express-sdk";

const flags = new FeatureFlags(
  new CompositeFeatureFlagBackend([
    new MemoryFeatureFlagBackend({ "new-ui": true }),
    new EnvFeatureFlagBackend(), // reads FLAG_NEW_UI, FLAG_BETA, …
  ]),
);

await flags.isEnabled("new-ui"); // true

// Hide a route (404) when the flag is off:
app.get("/api/beta", makeFlagGuard(flags, "beta"), (_req, res) => res.json({ ok: true }));

Env convention

The flag new-ui maps to FLAG_NEW_UI. Values 1/true/on/yes/enabled (case-insensitive) count as enabled.

Storage / uploads

UploadStorage is the interface; LocalUploadStorage writes to the filesystem and S3UploadStorage writes to S3/MinIO — same contract, swap without touching call sites.

import { LocalUploadStorage, buildContentDisposition } from "tempest-express-sdk";

const storage = new LocalUploadStorage({
  root: "./var/uploads",
  baseUrl: "https://cdn.example.com",
});

const result = await storage.save(
  "avatars/123.png",
  new Uint8Array(bytes),
  { contentType: "image/png" },
);
result.url; // "https://cdn.example.com/avatars/123.png"

// Download with a filename (accent-safe via RFC 5987):
res.setHeader("Content-Disposition", buildContentDisposition("report.pdf"));
res.send(await storage.read("docs/report.pdf"));

S3 / MinIO

S3UploadStorage implements the same interface over the minio client (an optional peer — npm i minio), lazy-loaded on first use. Swap the backend without changing the rest of the code:

import { S3UploadStorage } from "tempest-express-sdk";
import { Client } from "minio";

const storage = new S3UploadStorage({
  bucket: "uploads",
  publicBaseUrl: "https://cdn.example.com",
  client: new Client({
    endPoint: "s3.amazonaws.com",
    useSSL: true,
    accessKey: process.env.MINIO_ACCESS_KEY ?? "",
    secretKey: process.env.MINIO_SECRET_KEY ?? "",
    region: "us-east-1",
  }),
});

await storage.save("avatars/123.png", bytes, { contentType: "image/png" });
storage.url("avatars/123.png"); // "https://cdn.example.com/uploads/avatars/123.png"

Without a client, pass endPoint/accessKey/secretKey/… and the SDK builds the minio client on first use. The fields match minioSettingsShape.

Recap

Flags with swappable backends + a route guard; storage behind a single interface (local now, S3/MinIO by implementing the same contract).