npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@smooai/config

v4.5.3

Published

Type-safe multi-language configuration management with schema validation, three-tier config (public, secret, feature flags), and runtime client support for TypeScript, Python, Rust, and Go.

Downloads

2,782

Readme

About SmooAI

SmooAI is an AI-powered platform for helping businesses multiply their customer, employee, and developer experience.

Learn more on smoo.ai

SmooAI Packages

Check out other SmooAI packages at smoo.ai/open-source

About @smooai/config

Type-safe config, secrets, and feature flags for every layer of your stack -- One schema, one API, every language. Rename a key and every call site is a compile error, not a 3 AM page.

NPM Version NPM Downloads NPM Last Update

GitHub License GitHub Actions Workflow Status GitHub Repo stars


What you get

  • Three tiers, one schema -- public config, secrets, and feature flags defined once with Zod/Valibot/ArkType/Effect, validated everywhere they're read.
  • Strongly-typed keys -- defineConfig() gives you PublicConfigKeys, SecretConfigKeys, and FeatureFlagKeys with full inference. Mis-typed keys fail at compile time, not at runtime.
  • Any environment, any key -- same API for development, staging, production. Override per-stage without touching code.
  • Zero-latency cold starts -- values are baked into the bundle as env vars (Next.js, Vite) or resolved in-memory from a local runtime (server). No network round-trip on the hot path.
  • Browser, server, framework-native -- the same typed keys read cleanly from React client components, Server Components, Next.js, Vite, or plain Node.
  • Live feature flags -- toggled from the dashboard without a redeploy, but still typed.
  • Native clients in every language -- TypeScript, Python, Rust, Go, .NET (C#) all read from the same source of truth.

Install

pnpm add @smooai/config

Quick Start (TypeScript)

1. Define your configuration schema

Use defineConfig() with any StandardSchema-compliant library (Zod, Valibot, ArkType, Effect Schema) or the built-in StringSchema, BooleanSchema, and NumberSchema helpers:

// .smooai-config/config.ts
import { defineConfig, StringSchema, BooleanSchema, NumberSchema } from '@smooai/config';
import { z } from 'zod';

const config = defineConfig({
    publicConfigSchema: {
        apiBaseUrl: z.string().url(),
        maxRetries: NumberSchema,
        enableDebug: BooleanSchema,
    },
    secretConfigSchema: {
        databaseUrl: z.string().url(),
        apiKey: StringSchema,
    },
    featureFlagSchema: {
        enableNewUi: BooleanSchema,
        betaFeatures: BooleanSchema,
    },
});

export default config;

// Extract typed key objects for use throughout your app
export const { FeatureFlagKeys, PublicConfigKeys, SecretConfigKeys } = config;

defineConfig() automatically maps camelCase keys to UPPER_SNAKE_CASE:

FeatureFlagKeys.ENABLE_NEW_UI; // "ENABLE_NEW_UI"
PublicConfigKeys.API_BASE_URL; // "API_BASE_URL"
SecretConfigKeys.DATABASE_URL; // "DATABASE_URL"

2. Add to tsconfig.json

{
    "compilerOptions": { ... },
    "include": ["src/**/*", ".smooai-config/**/*.ts"]
}

Next.js Integration

Inject config into next.config.ts

Use withSmooConfig() to inject feature flags and public config as NEXT_PUBLIC_ environment variables, with per-stage overrides:

// next.config.ts
import { withSmooConfig } from '@smooai/config/nextjs/withSmooConfig';

const nextConfig = withSmooConfig({
    default: {
        featureFlags: { enableNewUi: false, betaFeatures: false },
        publicConfig: { apiBaseUrl: 'https://api.smooai.com', maxRetries: 3 },
    },
    development: {
        featureFlags: { enableNewUi: true },
        publicConfig: { apiBaseUrl: 'http://localhost:3000' },
    },
});

export default nextConfig;

This sets environment variables like NEXT_PUBLIC_FEATURE_FLAG_ENABLE_NEW_UI=true and NEXT_PUBLIC_CONFIG_API_BASE_URL=http://localhost:3000 based on the current stage.

Read config in React client components

import { getClientFeatureFlag, getClientPublicConfig } from '@smooai/config/client';

function MyComponent() {
    const isNewUi = getClientFeatureFlag('enableNewUi');
    const apiUrl = getClientPublicConfig('apiBaseUrl');

    if (!isNewUi) return <LegacyUI />;
    return <NewUI apiUrl={apiUrl} />;
}

These functions check NEXT_PUBLIC_FEATURE_FLAG_* and NEXT_PUBLIC_CONFIG_* env vars automatically -- no provider needed, no loading state.

Server Components + Client hydration (zero loading flash)

For apps that need runtime config from a config server, use getConfig on the server and SmooConfigProvider to hydrate client components:

// app/layout.tsx (Server Component)
import { getConfig, SmooConfigProvider } from '@smooai/config/nextjs';

export default async function RootLayout({ children }: { children: React.ReactNode }) {
    const config = await getConfig({
        environment: 'production',
        fetchOptions: { next: { revalidate: 60 } },
    });

    return (
        <html>
            <body>
                <SmooConfigProvider
                    initialValues={config}
                    baseUrl={process.env.SMOOAI_CONFIG_API_URL}
                    apiKey={process.env.SMOOAI_CONFIG_API_KEY}
                    orgId={process.env.SMOOAI_CONFIG_ORG_ID}
                    environment="production"
                >
                    {children}
                </SmooConfigProvider>
            </body>
        </html>
    );
}
// Any client component -- values available synchronously (pre-seeded from SSR)
import { usePublicConfig, useFeatureFlag } from '@smooai/config/nextjs';

function Dashboard() {
    const { value: apiUrl } = usePublicConfig<string>('API_BASE_URL');
    const { value: enableNewUi } = useFeatureFlag<boolean>('ENABLE_NEW_UI');
    return (
        <div>
            API: {apiUrl}, New UI: {String(enableNewUi)}
        </div>
    );
}

Vite Integration

Vite plugin

// vite.config.ts
import { defineConfig } from 'vite';
import { smooConfigPlugin } from '@smooai/config/vite/smooConfigPlugin';

export default defineConfig({
    plugins: [
        smooConfigPlugin({
            featureFlags: { enableNewUi: true, betaFeatures: false },
            publicConfig: { apiBaseUrl: 'http://localhost:3000' },
        }),
    ],
});

Then read values the same way as Next.js -- getClientFeatureFlag and getClientPublicConfig from @smooai/config/client check VITE_FEATURE_FLAG_* and VITE_CONFIG_* automatically.

Preload config (optional)

For runtime config from a config server, start fetching before React mounts:

// main.tsx
import { preloadConfig, ConfigProvider } from '@smooai/config/vite';
import { createRoot } from 'react-dom/client';

preloadConfig({ environment: 'production' });

createRoot(document.getElementById('root')!).render(
    <ConfigProvider baseUrl="https://config.smooai.dev" apiKey="your-public-key" orgId="your-org-id" environment="production">
        <App />
    </ConfigProvider>,
);

Server-Side Config Access

For Node.js server code, use buildConfigObject to get sync and async accessors with full type safety:

import buildConfigObject from '@smooai/config/platform/server';
import config, { PublicConfigKeys, SecretConfigKeys, FeatureFlagKeys } from './.smooai-config/config';

const configObj = buildConfigObject(config);

// Sync access (uses worker threads)
const dbUrl = configObj.secretConfig.getSync(SecretConfigKeys.DATABASE_URL);
const apiUrl = configObj.publicConfig.getSync(PublicConfigKeys.API_BASE_URL);
const isNewUi = configObj.featureFlag.getSync(FeatureFlagKeys.ENABLE_NEW_UI);

// Async access
const apiKey = await configObj.secretConfig.getAsync(SecretConfigKeys.API_KEY);

How .getSync() works (and how to ship it in any bundled compute)

Sync accessors run an async config read to completion on the caller thread via synckit — a Node Worker pool + Atomics.wait on a SharedArrayBuffer. createSyncFn only accepts a file:// URL, so the worker body has to live on disk. The SDK resolves it in two stages:

  1. Sidecar filesync-worker.mjs sitting next to the compiled SDK entry (i.e. resolved via new URL('./sync-worker.mjs', import.meta.url) from dist/server/index.mjs). This is the normal case for plain Node resolution with no bundling — node_modules/@smooai/config/dist/server/sync-worker.mjs is already there. It's also the preferred case when bundlers copy the sidecar into the deploy output. Zero /tmp writes.

  2. Extract-to-/tmp fallback — if the sidecar isn't on disk at that path (e.g. a bundler inlined the SDK entry into a single file and didn't copy the sidecar), the SDK writes an embedded copy of the worker source to mkdtempSync()/sync-worker.mjs once per process and hands that path to synckit. One ~1-2 MiB write at cold start, amortised across every sync read for the lifetime of the process. Works anywhere with a writable temp dir.

Both paths are transparent — your code is identical either way. Which path you land on depends on how your compute is packaged.

Plain Node (no bundling)

Zero config. The SDK resolves node_modules/@smooai/config/dist/server/sync-worker.mjs directly — path (1) every time.

Any bundled compute (Lambda, Cloud Run, ECS, container, Worker, etc.)

The rule is universal: if your build inlines the SDK entry into a single output file, you need to ship sync-worker.mjs next to that output (or accept path (2)'s /tmp write once per cold start).

The source path is always:

node_modules/@smooai/config/dist/server/sync-worker.mjs

The destination is alongside whichever file ends up being your runtime's import.meta.url anchor — typically the bundled handler .mjs / .js.

Recipes for common setups:

esbuild — explicit copy plugin

// build.ts
import { build } from 'esbuild';
import { copy } from 'esbuild-plugin-copy';

await build({
    entryPoints: ['src/handler.ts'],
    outdir: 'dist',
    bundle: true,
    format: 'esm',
    platform: 'node',
    plugins: [
        copy({
            assets: {
                from: 'node_modules/@smooai/config/dist/server/sync-worker.mjs',
                to: 'dist/sync-worker.mjs',
            },
        }),
    ],
});

tsup — onSuccess hook

// tsup.config.ts
export default defineConfig({
    entry: ['src/handler.ts'],
    format: ['esm'],
    onSuccess: 'cp node_modules/@smooai/config/dist/server/sync-worker.mjs dist/sync-worker.mjs',
});

Serverless Framework — package.include

package:
    patterns:
        - 'node_modules/@smooai/config/dist/server/sync-worker.mjs'

Or copy into the handler dir as a build step and include from there.

AWS SAM — CodeUri + build script

Add a Makefile / build script that copies sync-worker.mjs into the BuildArtifactPath alongside your handler.

SST (AWS) — per-function or via $transform

// sst.config.ts — per function
new sst.aws.Function('Api', {
    handler: 'src/api.handler',
    copyFiles: [{ from: 'node_modules/@smooai/config/dist/server/sync-worker.mjs' }],
});

// Or at the stack level via $transform (every Function gets it automatically)
$transform(sst.aws.Function, (fn) => {
    fn.copyFiles = [...(fn.copyFiles ?? []), { from: 'node_modules/@smooai/config/dist/server/sync-worker.mjs' }];
});

Docker container (ECS, Cloud Run, anywhere)

# After your main build step, ensure the sidecar is next to the bundled entry.
COPY --from=build /app/dist/server.mjs /app/
COPY --from=build /app/node_modules/@smooai/config/dist/server/sync-worker.mjs /app/
CMD ["node", "server.mjs"]

If your build step keeps node_modules in the final image, no extra copy is needed — the SDK resolves the sidecar from node_modules/ path (1) directly.

When the sidecar truly can't be shipped

Path (2) — the /tmp extraction — is the safety net. One ~1-2 MiB write at cold start, then synckit re-uses the file for the rest of the process lifetime. Lambda's 512 MiB–10 GiB /tmp easily absorbs this; containers with an ephemeral /tmp work the same way. You can ignore this whole section and .getSync() will still work — you're just paying one filesystem write per cold start.

Edge runtimes (Vercel Edge, Cloudflare Workers)

These runtimes don't expose Node's worker_threads at all, so .getSync() is a no-go there by design. Use .get() (async) everywhere that needs to run on the edge. The error surface makes this explicit if you try.


React Hooks (framework-agnostic)

For any React app using the runtime config client:

import { ConfigProvider, usePublicConfig, useFeatureFlag } from '@smooai/config/react';

function App() {
    return (
        <ConfigProvider baseUrl="https://config.smooai.dev" apiKey="your-api-key" orgId="your-org-id" environment="production">
            <MyComponent />
        </ConfigProvider>
    );
}

function MyComponent() {
    const { value: apiUrl, isLoading, error } = usePublicConfig<string>('API_BASE_URL');
    const { value: enableNewUi } = useFeatureFlag<boolean>('ENABLE_NEW_UI');

    if (isLoading) return <div>Loading...</div>;
    if (error) return <div>Error: {error.message}</div>;

    return (
        <div>
            API URL: {apiUrl}, New UI: {String(enableNewUi)}
        </div>
    );
}

SDK Runtime Client

All language implementations include a runtime client for fetching configuration values from the Smoo AI config server with local caching.

Environment Variables

| Variable | Description | Required | | ----------------------- | ------------------------------------------------------ | -------- | | SMOOAI_CONFIG_API_URL | Base URL of the config API | Yes | | SMOOAI_CONFIG_API_KEY | Bearer token for authentication | Yes | | SMOOAI_CONFIG_ORG_ID | Organization ID | Yes | | SMOOAI_CONFIG_ENV | Default environment name (defaults to "development") | No |

TypeScript Client

import { ConfigClient } from '@smooai/config/platform/client';

// Zero-config (reads from env vars)
const client = new ConfigClient();

// Or explicit
const client = new ConfigClient({
    baseUrl: 'https://config.smooai.dev',
    apiKey: 'your-api-key',
    orgId: 'your-org-id',
    environment: 'production',
});

const apiUrl = await client.getValue('API_BASE_URL');
const allValues = await client.getAllValues();
client.invalidateCache();

Configuration Tiers

| Tier | Purpose | Examples | | ----------------- | ----------------------- | ---------------------------------------- | | Public | Client-visible settings | API URLs, feature toggles, UI config | | Secret | Server-side only | Database URLs, API keys, JWT secrets | | Feature Flags | Runtime toggles | A/B tests, gradual rollouts, beta access |

Security: B2M Key Restrictions

| Operation | B2M (Public Key) | M2M (Secret Key) | | -------------------- | ----------------- | ---------------- | | Read public values | Yes | Yes | | Read feature flags | Yes | Yes | | Read secret values | No (filtered) | Yes | | Write config values | No (403) | Yes | | Delete config values | No (403) | Yes |

Browser-to-Machine (B2M) keys are designed for browser clients. Secret-tier values are automatically filtered. B2M keys are read-only for public and feature flag tiers.

Machine-to-Machine (M2M) keys have full access to all tiers and write operations.


Multi-Language Support

@smooai/config has native implementations in Python, Rust, and Go alongside the primary TypeScript package.

Python

pip install smooai-config
# or: uv add smooai-config
from pydantic import BaseModel
from smooai_config import define_config
from smooai_config.client import ConfigClient

class PublicConfig(BaseModel):
    api_url: str = "https://api.example.com"
    max_retries: int = 3

class SecretConfig(BaseModel):
    database_url: str
    api_key: str

config = define_config(public=PublicConfig, secret=SecretConfig)

with ConfigClient() as client:  # reads from env vars
    value = client.get_value("API_URL", environment="production")
    all_values = client.get_all_values()

Rust

[dependencies]
smooai-config = { git = "https://github.com/SmooAI/config", package = "smooai-config" }
use smooai_config::client::ConfigClient;

let mut client = ConfigClient::from_env();
let value = client.get_value("API_URL", None).await?;
let all = client.get_all_values(Some("production")).await?;

Go

go get github.com/SmooAI/config/go/config
import "github.com/SmooAI/config/go/config"

client := config.NewConfigClientFromEnv()
defer client.Close()

value, err := client.GetValue("API_URL", "production")
allValues, err := client.GetAllValues("")

Development

Prerequisites

  • Node.js 22+, pnpm 10+
  • Python 3.13+ with uv (for Python package)
  • Rust toolchain (for Rust package)
  • Go 1.22+ (for Go package)

Commands

pnpm install               # Install dependencies
pnpm build                 # Build all packages (TS, Python, Rust, Go)
pnpm test                  # Run all tests (Vitest, pytest, cargo test, go test)
pnpm lint                  # Lint all code (oxlint, ruff, clippy, go vet)
pnpm format                # Format all code (oxfmt, ruff, cargo fmt, gofmt)
pnpm typecheck             # Type check (tsc, basedpyright, cargo check)
pnpm check-all             # Full CI parity check

Schema Libraries

Supports Zod, Valibot, ArkType, Effect Schema, and built-in schema types. See SCHEMA_USAGE.md for examples with each library.


Contributing

Contributions are welcome! This project uses changesets to manage versions and releases.

  1. Fork the repository
  2. Create your branch (git checkout -b amazing-feature)
  3. Make your changes
  4. Add a changeset: pnpm changeset
  5. Commit and push
  6. Open a Pull Request

Contact

Brent Rager

Smoo Github: https://github.com/SmooAI