Skip to content

@grest-ts/config

Type-safe configuration management for Grest Framework.

Features

  • Type-safe configuration keys with validation
  • Multiple key types (Settings, Secrets, Resources)
  • Pluggable storage backends (file, environment, remote)
  • Runtime configuration updates with watchers
  • Centralized configuration definitions
  • Config values are validated!

Pattern

  • Keys are defined statically, but values are resolved at runtime from async context
  • Different key types (Setting, Secret, Resource) can use different storage backends (Strategy pattern)
  • Supports watching for runtime configuration changes (Observer pattern)

Getting Started

1. Define your configuration

typescript
import {GGConfig, GGSetting, GGSecret, GGResource} from "@grest-ts/config"
import {IsString, IsPosInt, IsBoolean} from "@grest-ts/schema"

export const AppConfig = GGConfig.define('/app/', () => ({
    server: {
        port: new GGSetting('server/port', IsPosInt, 3000, 'HTTP server port'),
        debug: new GGSetting('debug', IsBoolean, false, 'Enable debug mode'),
    },
    database: {
        host: new GGResource('db/host', IsString, 'Database host'),
        password: new GGSecret('db/password', IsString, 'Database password'),
    }
}));

2. Provide local values for development

typescript
import {createLocalConfig} from "@grest-ts/config"

export const localConfig = createLocalConfig(AppConfig, {
    database: {
        host: "localhost:5432",
        password: "root"
    }
});

createLocalConfig is a compile-time helper — it type-checks the values against your config definition.

3. Wire it up in your runtime

typescript
import {GGConfigLocator, GGConfigStoreFile} from "@grest-ts/config"

new GGConfigLocator(AppConfig, localConfig)
    .add(GGSetting, new GGConfigStoreFile('./config/settings.json', import.meta.url))
    .add([GGSecret, GGResource], new GGConfigStoreAwsSecretsManager({ ... }))

When localConfig is passed to the constructor, stores are automatically swapped for GGConfigStoreLocal outside of production — so your real stores (AWS, files, etc.) are only used when NODE_ENV=production.

4. Use it

typescript
// Settings - .get()
const port = AppConfig.server.port.get();
const debug = AppConfig.server.debug.get();

// Secrets - .reveal() to make intent explicit
const dbPassword = AppConfig.database.password.reveal();

// Resources - .get()
const dbHost = AppConfig.database.host.get();

// Watch for runtime changes
const unwatch = AppConfig.server.port.watch((newPort) => {
    console.log('Port changed to:', newPort);
});
unwatch(); // stop watching

Key Types

TypeConstructorRead methodUse Case
GGSetting(name, schema, default, description).get()General configuration (ports, flags)
GGSecret(name, schema, description).reveal()Sensitive data (API keys, passwords)
GGResource(name, schema, description).get()Infrastructure (URLs, hosts, paths)

Settings have a default value. Secrets and resources do not — their values must come from a store.

Validation

All keys require a @grest-ts/schema validator. Values are validated when loaded from a store. Invalid config at startup prevents the service from starting. Invalid config during a runtime reload is rejected and an error is raised — the previous valid value remains in effect.

typescript
import {IsString, IsPosInt, IsBoolean, IsLiteral} from "@grest-ts/schema"

port: new GGSetting('port', IsPosInt, 8080, 'Server port')
name: new GGSetting('name', IsString, 'default', 'App name')
env:  new GGSetting('env', IsLiteral('dev', 'staging', 'prod'), 'dev', 'Environment')

Storage Backends

GGConfigLocator maps key types to stores. Each key type must have a store registered before start.

typescript
new GGConfigLocator(AppConfig)
    .add(GGSetting, new GGConfigStoreFile("./config/settings.json", import.meta.url))
    .add(GGResource, new GGConfigStoreFile("./config/resources.json", import.meta.url))

Multiple key types can share a store:

typescript
new GGConfigLocator(AppConfig)
    .add([GGSetting, GGResource], new GGConfigStoreFile("./config/config.json", import.meta.url))

GGConfigStoreFile

Reads values from a JSON file. Watches the file for changes and automatically reloads (100ms debounce).

The key path /app/server/port maps to app.server.port in JSON:

json
{
    "app": {
        "server": {
            "port": 8080
        },
        "debug": true
    }
}

GGConfigStoreLocal

Type-safe development store. Refuses to start when NODE_ENV=production.

typescript
import {createLocalConfig, GGConfigStoreLocal} from "@grest-ts/config"

const localConfig = createLocalConfig(AppConfig, {
    database: {
        host: "localhost:5432",
        password: "root"
    }
});

new GGConfigLocator(AppConfig)
    .add([GGResource, GGSecret], new GGConfigStoreLocal(AppConfig, localConfig))
    .add(GGSetting, new GGConfigStoreFile('./config/settings.json', import.meta.url))

GGConfigStoreAwsSecretsManager

See @grest-ts/config-aws.

Further Reading

  • Extending - How to add custom key types and stores