edge-config

star 2.6k

Understand and create S3-backed edge configs with poll-based caching. Use when adding new runtime configs (feature rollouts, request blocking, operational toggles), debugging edge config polling, or working with the EdgeConfigStore factory.

useautumn By useautumn schedule Updated 4/1/2026

name: edge-config description: Understand and create S3-backed edge configs with poll-based caching. Use when adding new runtime configs (feature rollouts, request blocking, operational toggles), debugging edge config polling, or working with the EdgeConfigStore factory.

Edge Configs

Edge configs are JSON files stored in S3 that are polled into server memory at a regular cadence. They let us change server behavior at runtime without deploys -- load shedding, feature rollouts, operational toggles, etc.

Architecture

Each edge config has its own S3 file under the admin/ prefix in the admin S3 bucket (autumn-dev-server / autumn-prod-server). Configs are independent -- a parse error in one does not affect another.

Key files

File Purpose
server/src/internal/misc/edgeConfig/edgeConfigStore.ts createEdgeConfigStore<T> factory -- generic S3 read/write, polling, status tracking
server/src/internal/misc/edgeConfig/edgeConfigRegistry.ts Registry -- registerEdgeConfig, startAllEdgeConfigPolling, stopAllEdgeConfigPolling
server/src/init.ts Calls startAllEdgeConfigPolling on boot, stopAllEdgeConfigPolling on shutdown
server/src/external/aws/s3/adminS3Config.ts Bucket/region resolution (dev vs prod)

Existing configs

Config S3 Key Module
Request blocks admin/request-block-config.json server/src/internal/misc/requestBlocks/requestBlockStore.ts

Lifecycle

Boot:
  1. Config module is imported (top-level side effect) -> calls registerEdgeConfig()
  2. init.ts calls startAllEdgeConfigPolling({ logger })
  3. Each store: await refresh() (initial S3 fetch, blocks until done)
  4. Each store: setInterval(refresh, pollIntervalMs)
  5. Server starts accepting traffic

Runtime:
  - get() reads from in-memory cache (zero I/O, sync)
  - refresh() runs every pollIntervalMs (fire-and-forget)
  - writeToSource() writes to S3 + updates local cache immediately

Shutdown:
  - stopAllEdgeConfigPolling() clears all intervals

Fail-Open Guarantee

On any S3 failure (network error, auth error, bad JSON, schema mismatch), runtimeConfig resets to defaultValue(). This means:

  • No request is ever blocked due to infrastructure failure
  • If S3 goes down, configs degrade to their default (empty/safe) state
  • The store does NOT hold onto stale data -- it resets to default

NoSuchKey (file doesn't exist yet) is treated as a normal empty state, not an error.

How to Add a New Edge Config

1. Define the schema

Create a schemas file with a Zod schema:

// server/src/internal/misc/featureRollouts/featureRolloutSchemas.ts
import { z } from "zod/v4";

export const FeatureRolloutConfigSchema = z.object({
  features: z.record(z.string(), z.object({
    percentage: z.number().min(0).max(100).default(0),
    enabled: z.boolean().default(false),
  })).default({}),
});

export type FeatureRolloutConfig = z.infer<typeof FeatureRolloutConfigSchema>;

2. Create the store module

// server/src/internal/misc/featureRollouts/featureRolloutStore.ts
import { createEdgeConfigStore } from "@/internal/misc/edgeConfig/edgeConfigStore.js";
import { registerEdgeConfig } from "@/internal/misc/edgeConfig/edgeConfigRegistry.js";
import {
  type FeatureRolloutConfig,
  FeatureRolloutConfigSchema,
} from "./featureRolloutSchemas.js";

const store = createEdgeConfigStore<FeatureRolloutConfig>({
  s3Key: "admin/feature-rollout-config.json",
  schema: FeatureRolloutConfigSchema,
  defaultValue: () => ({ features: {} }),
  pollIntervalMs: 300_000, // 5 minutes
});

registerEdgeConfig({ store });

export const getFeatureRolloutConfig = () => store.get();

export const isFeatureEnabled = ({
  featureId,
}: {
  featureId: string;
}): boolean => {
  const feature = store.get().features[featureId];
  return feature?.enabled ?? false;
};

3. Register the import in init.ts

Add a side-effect import so the module runs registerEdgeConfig at boot:

// server/src/init.ts
import "./internal/misc/featureRollouts/featureRolloutStore.js";

4. (Optional) Add the S3 key constant

// server/src/external/aws/s3/adminS3Config.ts
export const ADMIN_FEATURE_ROLLOUT_CONFIG_KEY = "admin/feature-rollout-config.json";

5. Use it

import { isFeatureEnabled } from "@/internal/misc/featureRollouts/featureRolloutStore.js";

if (isFeatureEnabled({ featureId: "new-billing-engine" })) {
  // new path
}

Factory API Reference

const store = createEdgeConfigStore<T>({
  s3Key: string,              // S3 object key under admin bucket
  schema: z.ZodType<T>,       // Zod schema for validation
  defaultValue: () => T,      // Factory for empty/safe default
  pollIntervalMs?: number,    // Poll interval (default: 60_000)
  s3Client?: S3Client,        // Optional DI for testing
});

store.get()                   // T -- in-memory cached config (sync, no I/O)
store.getStatus()             // EdgeConfigStatus -- health/timing info
store.refresh({ logger? })    // Re-fetch from S3, update cache
store.startPolling({ logger? })  // Initial fetch + start interval
store.stopPolling()           // Clear interval
store.readFromSource()        // Direct S3 read (bypasses cache)
store.writeToSource({ config })  // S3 write + update cache immediately

Testing

Unit tests use the s3Client DI parameter to inject a mock:

const mockClient = { send: jest.fn(async () => ({ Body: ... })) } as unknown as S3Client;

const store = createEdgeConfigStore<MyConfig>({
  s3Key: "admin/test.json",
  schema: MyConfigSchema,
  defaultValue: () => ({ ... }),
  s3Client: mockClient,
});

Existing tests: server/tests/unit/edge-config/edge-config-store.test.ts

Install via CLI
npx skills add https://github.com/useautumn/autumn --skill edge-config
Repository Details
star Stars 2,586
call_split Forks 223
navigation Branch main
article Path SKILL.md
More from Creator