@jabraf/app-config
v0.0.1-4113486.2
Published
Application configuration and feature flags management for web projects
Maintainers
Readme
@jabraf/app-config
Application configuration and feature flags management for web projects.
Installation
npm install @jabraf/app-configreact is an optional peer dependency. Install it alongside @jabraf/app-config only if you intend to use the useFeature hook.
Overview
@jabraf/app-config is a lightweight configuration and feature-flag layer for Node.js and React applications. It provides:
- A universal runtime API (
setConfig/getConfig) that works identically in browser and Node environments — the consumer statically imports the generatedconfig/app-config.jsonand hands it to the library at bootstrap. - A Node-only
buildConfigentrypoint (under the@jabraf/app-config/buildsubpath) that delegates to a project-ownedconfig/make-config.tsso apps can produce their own configuration artifacts. - A React hook (
useFeature) plus cookie helpers (setFeatureCookie,removeFeatureCookie) for toggling features at runtime with cookie-based overrides. - A
jabraf-configCLI (scaffolded — the command set is not yet implemented).
Entry points
| Import | Environment | Exports |
| ----------------------------------------- | --------------- | ------------------------------------------------------- |
| @jabraf/app-config | browser + Node | setConfig, getConfig, resetConfig, types |
| @jabraf/app-config/hooks/use-feature.js | browser (+ SSR) | useFeature, setFeatureCookie, removeFeatureCookie |
| @jabraf/app-config/build | Node only | buildConfig, appDirectory, fromRoot |
The root entry point and the React hooks subpath never import node:* modules and are safe to bundle for the browser.
Project Layout
The package resolves paths relative to the nearest package.json of the consuming project and expects the following files under a config/ directory:
your-app/
└── config/
├── make-config.ts # Authored: build-time script invoked by buildConfig
└── app-config.json # Generated: application configuration consumed by getConfigmake-config.ts must export an async buildAppConfig function that produces and persists the configuration. buildConfig simply imports this module and invokes that function:
// config/make-config.ts
export async function buildAppConfig(): Promise<void> {
// Read environment, source files, etc., and write `config/app-config.json`
// using your preferred strategy.
}Because config/app-config.json is a build artifact rather than source, add it to your .gitignore:
# config build output from @jabraf/app-config
config/app-config.jsonFeature Type
Features are tracked through a global FeatureMap interface. The Feature string-literal type is derived as keyof FeatureMap. Register your app's features by augmenting FeatureMap from a declaration file in your project — do not redeclare Feature directly, as that would clash with the type shipped by this package and produce a Duplicate identifier 'Feature' error.
// app/types/features.d.ts
declare global {
interface FeatureMap {
beta: true;
'experimental-search': true;
'new-checkout': true;
}
}
export {};After augmenting, Feature resolves to 'beta' | 'experimental-search' | 'new-checkout' everywhere the package is consumed. If you don't augment FeatureMap, Feature is never and calls like isFeatureEnabled(...) won't type-check.
Usage
Bootstrap: register the config once
Statically import the generated config/app-config.json and hand it to setConfig at app startup. The same snippet works in a browser entry, a Node script, or an SSR server — there is no node:fs involved.
// app/setup.ts
import appConfig from '../config/app-config.json' with { type: 'json' };
import { setConfig } from '@jabraf/app-config';
setConfig(appConfig);TypeScript: make sure
"resolveJsonModule": trueis set in yourtsconfig.json(it is by default in@jabraf/dev/ Vite / Next.js).CommonJS bundlers / older Node: drop the
with { type: 'json' }assertion —import appConfig from '../config/app-config.json'is enough.First-time checkouts: since
config/app-config.jsonis git-ignored and generated bybuildConfig, the import may not resolve on a fresh clone. Either commit a placeholder{}, runbuildConfigbefore the type-checker, or annotate the import with// @ts-expect-error generated by buildConfig.
Read configuration
import { getConfig } from '@jabraf/app-config';
const config = getConfig();
console.log(config.env);
console.log(config.hostname);
console.log(config.features);getConfig returns whatever object was last registered with setConfig. It throws Error: app-config not set. Call setConfig(config) with your imported config/app-config.json at app startup. when called before bootstrap.
resetConfig() clears the registered config — primarily useful in tests.
Build configuration (Node only)
import { buildConfig } from '@jabraf/app-config/build';
await buildConfig();buildConfig dynamically imports your config/make-config.ts and invokes its buildAppConfig export. If that export is missing, buildConfig throws Error: buildAppConfig not found in config/make-config.ts.
React: useFeature
Once setConfig has run, the useFeature hook reads features directly from the registered config:
// app/components/beta-banner.tsx
import { useFeature } from '@jabraf/app-config/hooks/use-feature.js';
function BetaBanner() {
const { isFeatureEnabled } = useFeature();
if (!isFeatureEnabled('beta')) return null;
return <div>Welcome to the beta!</div>;
}Cookies named feature__{feature} take precedence over the registered features, which makes runtime overrides (e.g., for QA or beta testers) easy.
Toggling features via cookies
import { setFeatureCookie, removeFeatureCookie } from '@jabraf/app-config';
setFeatureCookie('beta', true);
setFeatureCookie('beta', false);
removeFeatureCookie('beta');Both setFeatureCookie and removeFeatureCookie are no-ops in SSR environments where document is undefined.
API Reference
setConfig(config: AppConfig): void
Registers the application configuration. Call once at app bootstrap with the statically-imported config/app-config.json. Subsequent calls replace the registered config.
getConfig(): AppConfig
Returns the registered AppConfig reference. Throws Error: app-config not set. Call setConfig(config) with your imported config/app-config.json at app startup. when called before setConfig.
resetConfig(): void
Clears the registered config so that the next getConfig call throws again. Intended for tests.
buildConfig(): Promise<void> — @jabraf/app-config/build
Node only. Dynamically imports config/make-config.ts from the project root and invokes its buildAppConfig export. Throws if the export is missing.
useFeature() — @jabraf/app-config/hooks/use-feature.js
React hook. Returns { isFeatureEnabled: (feature: Feature) => boolean }. Resolves features from getConfig().features. The returned function checks a feature__{name} cookie first and falls back to the registered features. The function is memoized against the registered features reference.
setFeatureCookie(feature: Feature, enabled: boolean): void — @jabraf/app-config/hooks/use-feature.js
Writes feature__{feature}={enabled} with a one-year expiry and path=/. No-op when document is undefined.
removeFeatureCookie(feature: Feature): void — @jabraf/app-config/hooks/use-feature.js
Clears the cookie for feature by setting it with an expiry in the past. No-op when document is undefined.
Utilities — @jabraf/app-config/build (Node only)
appDirectory(): string— Resolves the directory of the nearestpackage.jsonto the current working directory. Memoized after the first call.fromRoot(...segments: string[]): string— Joins path segments againstappDirectory().
Types
type Environment = 'dev' | 'test' | 'staging' | 'production';
type EnvironmentConfig<T> = {
[key in Environment]: T;
};
type FeatureConfig<T extends string = Feature> = {
[key in T]: boolean;
};
type AppConfig = {
env: Environment;
hostname: string;
features: FeatureConfig;
};
type BuildAppConfig = {
buildAppConfig: () => Promise<void>;
};CLI
The package ships a jabraf-config binary that is scaffolded but not yet implemented. Running it currently logs NOT IMPLEMENTED YET!!. Command support will land in a future release.
npx jabraf-config --versionLicense
MIT © Jabran Rafique
