@1matrix/config-loader
v1.0.0
Published
Hot-reloadable public configuration management for Apps with GitHub webhook integration
Readme
@1matrix/config-loader
Hot-reloadable public configuration management for Applications with GitHub webhook integration.
Features
- ✅ Hot Reloading: Automatically update configurations without restarting your application
- ✅ GitHub Integration: Fetch configurations from a dedicated GitHub repository
- ✅ Webhook Support: Real-time updates via GitHub webhooks
- ✅ Polling Backup: Periodic polling as a fallback mechanism
- ✅ Schema Validation: JSON Schema validation for all configurations
- ✅ Environment Support: Map NODE_ENV to different configuration directories
- ✅ Fail-safe Caching: Local cache fallback when GitHub is unavailable
- ✅ TypeScript: Full TypeScript support with type definitions
- ✅ Event-Driven: Listen to configuration changes with EventEmitter
- ✅ Framework Adapters: Built-in Express and Fastify support
- ✅ API Key Auth: Ready-to-use API key authentication middleware
Installation
pnpm add @1matrix/config-loaderQuick Start
1. Initialize ConfigLoader
import { ConfigLoader } from "@1matrix/config-loader";
// Set NODE_ENV (required)
process.env.NODE_ENV = "production";
const config = new ConfigLoader({
repository: "OneMatrixL1/public-configs",
branch: "main",
githubToken: process.env.GITHUB_TOKEN,
webhookSecret: process.env.GITHUB_WEBHOOK_SECRET,
// Optional settings
pollingInterval: 5 * 60 * 1000, // Optional: 5 minutes (disabled by default)
envMappings: { production: "prod", development: "dev" }, // Optional: map NODE_ENV values
cacheFile: "/tmp/config-loader-cache.json", // Optional: default location
requireInitialConfig: false, // Optional: false = permissive mode (default)
});
await config.initialize();2. Access Configuration
// Get specific value
const apiKeyName = config.get("api-keys.a129f786...");
// Get with type inference
const handlers = config.get<string[]>("intent-handlers", []);
// Check if exists
if (config.has("api-keys.some-hash")) {
// ...
}
// Get all configs
const allConfigs = config.getAll();3. Listen to Updates
// Listen to all updates
config.on("update", (newConfigs, changedKeys) => {
console.log("Updated configs:", changedKeys);
});
// Listen to specific config updates
config.on("update:api-keys", (newApiKeys) => {
console.log("API keys changed");
});
// Listen to validation errors
config.on("validation-error", (configName, errors) => {
console.error("Validation failed:", configName, errors);
});
// Listen to error events (for monitoring)
config.on("error", (errorEvent) => {
console.error("Config error:", {
source: errorEvent.source, // 'github' | 'cache' | 'validation'
phase: errorEvent.phase, // 'initialize' | 'refresh' | 'polling'
error: errorEvent.error,
fallbackUsed: errorEvent.fallbackUsed,
timestamp: errorEvent.timestamp,
});
});4. Set Up Webhook
Express:
import express from "express";
import { createExpressWebhook } from "@1matrix/config-loader";
const app = express();
app.post(
"/webhook/config",
express.json(),
createExpressWebhook(config, {
onUpdate: (configs) => console.log("Updated via webhook"),
onError: (err) => console.error(err),
})
);Fastify:
import Fastify from "fastify";
import { createFastifyWebhook } from "@1matrix/config-loader";
const fastify = Fastify();
fastify.post("/webhook/config", createFastifyWebhook(config, {
onUpdate: (configs) => console.log("Updated via webhook"),
onError: (err) => console.error(err),
}));5. Protect Routes with API Key Authentication
Express:
import { createExpressApiKeyAuth } from "@1matrix/config-loader";
// Default: uses Keccak256 hashing (most common)
app.use("/api", createExpressApiKeyAuth(config));
// If clients send pre-hashed keys (disable hashing)
app.use("/api", createExpressApiKeyAuth(config, { hashFn: null }));
app.get("/api/protected", (req, res) => {
res.json({ authenticatedAs: req.apiKeyName });
});Fastify:
import { createFastifyApiKeyAuth } from "@1matrix/config-loader";
// Default: uses Keccak256 hashing (most common)
fastify.addHook("preHandler", createFastifyApiKeyAuth(config));
// If clients send pre-hashed keys (disable hashing)
fastify.addHook("preHandler", createFastifyApiKeyAuth(config, { hashFn: null }));
// Or apply to specific routes
fastify.get("/api/protected", {
preHandler: createFastifyApiKeyAuth(config)
}, async (request) => {
return { authenticatedAs: request.apiKeyName };
});Configuration Repository Structure
Your GitHub configuration repository should follow this structure:
public-configs/
├── schemas/
│ ├── api-keys.schema.json
│ ├── roles.schema.json
│ └── intent-handlers.schema.json
├── dev/
│ ├── api-keys.json
│ ├── roles.json
│ └── intent-handlers.json
├── staging/
│ └── ...
└── prod/
└── ...Example Configurations
api/keys-dev.json
{
"a129f7860d47c0630e6d06c153fe36711f25a31bfb4304dc6d2a79e609da0e96": "VNIDC",
"b234c8971e58d1741f7e17d264gf47822g36b42cgc5415ed7e3b8a710eb1fa07": "SERVICE_2"
}rabc/roles-dev.json
{
"explorer.viewer": [
"0xE61383556642AF1Bd7c5756b13f19A63Dc8601df",
"0x7d5538fEe2CE89dA936ec29cC48386b6E7548FaB"
],
"admin": ["0x194f5b1755562966302Ef0BbF4349c842c60FC42"]
}intent/handlers-dev.json
[
"0x84f915BcbD5C1134BCb93a0f50D9D36E6D3b508c",
"0x626b1E2458A9307E73A570c291bCd467216cc1D7"
]Example Schemas
schemas/api-keys.schema.json
{
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"patternProperties": {
"^[a-f0-9]{64}$": {
"type": "string",
"minLength": 1
}
},
"additionalProperties": false
}Environment Variables
NODE_ENV (Required)
The NODE_ENV environment variable determines which configuration directory to load.
NODE_ENV=production node app.jsUse envMappings to map NODE_ENV values to configuration directories:
const config = new ConfigLoader({
repository: "owner/repo",
branch: "main",
envMappings: {
production: "prod",
development: "dev",
staging: "stg",
},
});SKIP_AUTH (Optional)
Set SKIP_AUTH=true to bypass authentication middleware (for testing only).
SKIP_AUTH=true NODE_ENV=test npm test⚠️ Warning: If SKIP_AUTH=true in production, a warning will be logged to the console.
Configuration Options
ConfigLoaderOptions
| Option | Type | Default | Description |
| ----------------------- | ------------------------- | --------------------------------- | ------------------------------------------------ |
| repository | string | Required | GitHub repository in owner/repo format |
| branch | string | Required | Git branch to fetch from |
| githubToken | string | undefined | GitHub personal access token |
| webhookSecret | string | undefined | GitHub webhook secret for signature verification |
| envMappings | Record<string, string> | {} | Map NODE_ENV values to config directories |
| pollingInterval | number | undefined (disabled) | Polling interval in milliseconds |
| cacheFile | string | /tmp/config-loader-cache.json | Local cache file path |
| usePackageSchemas | boolean | true | Use embedded default schemas |
| requireInitialConfig | boolean | false | Throw error if no configs available on startup |
Error Handling Modes
Permissive Mode (requireInitialConfig: false, default):
- Starts with empty configs if GitHub and cache both fail
- Emits error events for monitoring
- Application continues running
Strict Mode (requireInitialConfig: true):
- Throws error if no configs available on startup
- Ensures application always has configuration data
- Better for production deployments with critical config dependencies
API Reference
Framework Adapters
createExpressWebhook(configLoader, options?)
Create Express middleware for handling GitHub webhook requests.
Parameters:
configLoader: ConfigLoader- ConfigLoader instanceoptions?: WebhookCoreOptions- Optional webhook handler optionsonUpdate?: (configs) => void- Called when configs are updatedonError?: (error) => void- Called on errors
Returns: Express middleware function
createFastifyWebhook(configLoader, options?)
Create Fastify route handler for GitHub webhook requests.
Parameters: Same as createExpressWebhook
Returns: Fastify route handler function
createExpressApiKeyAuth(configLoader, options?)
Create Express middleware for API key authentication.
Parameters:
configLoader: ConfigLoader- ConfigLoader instanceoptions?: ExpressApiKeyAuthOptionsheaderName?: string- Header to extract API key from (default:x-api-key)hashFn?: (raw: string) => string- Function to hash raw API keysonError?: (req, res, error) => void- Custom error handleronSuccess?: (req, res, name) => void- Custom success handler
Returns: Express middleware function
Note: Attaches apiKeyName to req object when authentication succeeds.
createFastifyApiKeyAuth(configLoader, options?)
Create Fastify preHandler hook for API key authentication.
Parameters:
configLoader: ConfigLoader- ConfigLoader instanceoptions?: FastifyApiKeyAuthOptionsheaderName?: string- Header to extract API key from (default:x-api-key)hashFn?: (raw: string) => string- Function to hash raw API keysonError?: (request, reply, error) => void- Custom error handleronSuccess?: (request, reply, name) => void- Custom success handler
Returns: Fastify preHandler hook function
Note: Attaches apiKeyName to request object when authentication succeeds.
verifyApiKey(configLoader, hashedKey)
Low-level API key verification utility (used internally by auth adapters).
Parameters:
configLoader: ConfigLoader- ConfigLoader instancehashedKey: string | undefined- Pre-hashed API key
Returns: ApiKeyVerificationResult
{
valid: boolean;
name?: string; // API key name if valid
error?: string; // Error message if invalid
}ConfigLoader Options
interface ConfigLoaderOptions {
repository: string; // GitHub repository (owner/repo)
branch: string; // Branch to fetch from
githubToken?: string; // GitHub personal access token
webhookSecret?: string; // GitHub webhook secret
envMappings?: Record<string, string>; // NODE_ENV to directory mappings
defaultEnv?: string; // Default environment (default: 'dev')
pollingInterval?: number; // Polling interval in ms (default: 300000)
cacheFile?: string; // Cache file path (default: './config-cache.json')
usePackageSchemas?: boolean; // Use built-in schemas (default: true)
}Methods
async initialize(): Promise<void>
Initialize the config loader. Attempts to load from GitHub, falls back to cache if unavailable.
async refresh(): Promise<void>
Manually refresh configurations from GitHub.
get<T>(path: string, defaultValue?: T): T | undefined
Get a configuration value by dot-notation path.
has(path: string): boolean
Check if a configuration path exists.
getAll(): Record<string, any>
Get all configurations.
getCurrentEnvironment(): string
Get the current environment name.
getConfigNames(): string[]
Get names of all loaded configurations.
createWebhookHandler(options?: WebhookHandlerOptions): ExpressMiddleware
Create an Express middleware for handling GitHub webhooks.
destroy(): void
Clean up resources (stop polling, remove listeners).
Events
'update'
Emitted when configurations are updated.
config.on(
"update",
(newConfigs: Record<string, any>, changedKeys: string[]) => {
// Handle update
}
);'update:${configName}'
Emitted when a specific configuration is updated.
config.on("update:api-keys", (newApiKeys: any) => {
// Handle API keys update
});'validation-error'
Emitted when configuration validation fails.
config.on("validation-error", (configName: string, errors: any[]) => {
// Handle validation error
});'error'
Emitted when an error occurs.
config.on("error", (error: Error) => {
// Handle error
});GitHub Webhook Setup
1. Create Webhook in GitHub Repository
- Go to your config repository → Settings → Webhooks → Add webhook
- Payload URL:
https://your-app.com/webhook/config-update - Content type:
application/json - Secret: Generate a strong random secret
- Events: Select "Just the push event"
- Active: ✅
2. Configure Branch Protection
Ensure only authorized users can update configs:
- Go to Settings → Branches → Add rule
- Branch name pattern:
main - Enable:
- Require pull request reviews (at least 1 approval)
- Require status checks before merging
- Restrict push access to admins
3. Set Environment Variables
GITHUB_TOKEN=ghp_your_personal_access_token
GITHUB_WEBHOOK_SECRET=your_webhook_secretEnvironment Mapping
Map NODE_ENV values to configuration directories:
const config = new ConfigLoader({
// ...
envMappings: {
dev: "dev",
development: "dev",
develop: "develop",
stg: "stg",
staging: "staging",
prod: "prod",
production: "production",
},
defaultEnv: "dev",
});Security Best Practices
✅ DO
- Store only hashed/public data in configurations (e.g., Keccak256 hashes)
- Use environment variables for webhook secrets and GitHub tokens
- Enable branch protection on configuration repository
- Monitor
validation-errorevents for malicious payloads - Set up alerts for repeated webhook validation failures
❌ DON'T
- Store raw API keys or passwords in configuration files
- Expose webhook endpoints without signature validation
- Allow direct pushes to main branch (require PRs)
- Disable schema validation
- Use weak webhook secrets
Testing
# Run tests
pnpm test
# Run tests in watch mode
pnpm test:watch
# Run tests with coverage
pnpm test:coverageBuilding
# Build TypeScript to JavaScript
pnpm build
# Build and watch for changes
pnpm build:watchExamples
See the examples/ directory for complete examples:
basic-usage.ts: Simple configuration loadingexpress-integration.ts: Full Express.js integration with webhooks
License
MIT
Contributing
Contributions are welcome! Please open an issue or submit a pull request.
Support
For issues and questions, please open an issue on GitHub.
