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

@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-loader

Quick 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.js

Use 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 instance
  • options?: WebhookCoreOptions - Optional webhook handler options
    • onUpdate?: (configs) => void - Called when configs are updated
    • onError?: (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 instance
  • options?: ExpressApiKeyAuthOptions
    • headerName?: string - Header to extract API key from (default: x-api-key)
    • hashFn?: (raw: string) => string - Function to hash raw API keys
    • onError?: (req, res, error) => void - Custom error handler
    • onSuccess?: (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 instance
  • options?: FastifyApiKeyAuthOptions
    • headerName?: string - Header to extract API key from (default: x-api-key)
    • hashFn?: (raw: string) => string - Function to hash raw API keys
    • onError?: (request, reply, error) => void - Custom error handler
    • onSuccess?: (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 instance
  • hashedKey: 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

  1. Go to your config repository → Settings → Webhooks → Add webhook
  2. Payload URL: https://your-app.com/webhook/config-update
  3. Content type: application/json
  4. Secret: Generate a strong random secret
  5. Events: Select "Just the push event"
  6. Active: ✅

2. Configure Branch Protection

Ensure only authorized users can update configs:

  1. Go to Settings → Branches → Add rule
  2. Branch name pattern: main
  3. 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_secret

Environment 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-error events 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:coverage

Building

# Build TypeScript to JavaScript
pnpm build

# Build and watch for changes
pnpm build:watch

Examples

See the examples/ directory for complete examples:

  • basic-usage.ts: Simple configuration loading
  • express-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.