Web (multi-package)

@sankofa/config

Typed remote config for the web. A single generic `get` that infers the return type from the default, plus decision envelopes, cohort-targeted overrides, and onChange subscriptions.

@sankofa/config exposes typed remote-config reads to your web code. It's a plugin to @sankofa/browser that consumes the config slice of the decision handshake and returns values synchronously.

If you've never wired remote config before, start with Your first remote config. This page is the reference.

Install

bash
npm install @sankofa/config

Register the plugin

TypeScript
import { Sankofa } from "@sankofa/browser";
import { configPlugin, getConfig } from "@sankofa/config";

await Sankofa.init({
apiKey: "sk_live_...",
endpoint: "https://api.sankofa.dev",
plugins: [
  configPlugin({
    defaults: {
      max_upload_mb: 25,
      support_email: "help@example.com",
      feature_modal_copy: "Welcome!",
    },
  }),
],
});

const config = getConfig()!;

getConfig() returns the singleton config client. It's null until Sankofa.init resolves; after that it's stable.

config.get<T>(key, default) — single generic getter

Reads are typed at the call site — pass the expected return type and a default. The default runs when the key is missing or the engine hasn't responded yet.

TypeScript
const supportEmail = config.get<string>("support_email", "help@example.com");
const showBanner = config.get<boolean>("show_promotion_banner", false);
const maxRetries = config.get<number>("api_max_retries", 3);

// Generic JSON values
type HomePageHero = { title: string; subtitle: string; ctaLabel: string };

const hero = config.get<HomePageHero>("home_hero", {
title: "Welcome",
subtitle: "Your default subtitle",
ctaLabel: "Get started",
});

A single generic get<T> covers strings, booleans, numbers, and JSON-shaped values. The SDK validates the engine's payload against the type the default suggests and falls back to the default if there's a type mismatch.

config.getDecision(key) — full envelope

Sometimes you need more than the value — for example to log the rollout reason for analytics:

TypeScript
const decision = config.getDecision("max_upload_mb");
//   { value: 200, version: 7, reason: "cohort:pro" }

config.onChange(key, listener)

Subscribe to mid-session value changes. Use this when an admin publishes a new override and you want the UI to refresh:

TypeScript
const unsubscribe = config.onChange("home_hero", (decision) => {
rerenderHomeHero(decision.value);
});

// Later:
unsubscribe();

The listener fires:

  • on the first decision handshake (with the initial value);
  • on every subsequent handshake that returns a different value;
  • never on the bundled default itself.

config.getAll() / config.getAllKeys()

Inspect every config key the engine returned. Useful for debugging, admin UIs, and dev-tools panels.

TypeScript
const allValues = config.getAll();
// Record<string, unknown>

const allKeys = config.getAllKeys();
// string[]

Don't drive logic off these — they're not stable across versions. Use get<T> with explicit defaults for any consumer.

Cohort-targeted overrides

Config items are inherently cohort-aware on the engine. From the SDK perspective you don't think about cohorts — the engine resolves the right value for the user before sending it. Make sure you've called Sankofa.identify(userId) for the user-targeted case to work; for anonymous targeting the SDK forwards anon_id automatically.

Bundled defaults

Bundled defaults serve three purposes:

  1. Synchronous startup behavior

    Your code calls config.get<number>("max_upload_mb", 25) immediately after init. The engine hasn't responded yet, so the SDK returns 25. As soon as the handshake lands (typically < 200ms), subsequent reads return the engine value.

  2. Offline fallback

    On a fresh install with no cached snapshot and no network, all reads return defaults until connectivity returns.

  3. Type contract

    The default establishes the type the SDK expects. If the engine ever returns a different type, the default is preserved.

API summary

SymbolDescription
configPlugin({ defaults? })Plugin to register at Sankofa.init.
getConfig()Returns the singleton config client.
config.get<T>(key, default)Generic typed read — the primary API.
config.getDecision(key)Full decision envelope (value + version + reason).
config.onChange(key, listener)Subscribe to mid-session changes.
config.getAll()Map of every key → current value.
config.getAllKeys()Array of every key.

What's next

Edit this page on GitHub