manifest-core
v1.0.1
Published
Smart JSON composition with inheritance, immutable rules, and intelligent merging. Build complex configurations from simple manifests.
Downloads
12
Maintainers
Readme
manifest-core
Smart JSON composition with inheritance, immutable rules, and intelligent merging
Transform complex configuration hierarchies into a single, validated JSON structure. Built for production systems that need composability, auditability, and cryptographic integrity.
What is manifest-core?
A TypeScript library that solves one problem extremely well: compose JSON manifests with inheritance.
// base.json
{ "patch": { "database": "postgres", "cache": "redis" } }
// app.json
{ "extends": "./base.json", "patch": { "port": 3000 } }
// Result
{ "database": "postgres", "cache": "redis", "port": 3000 }That's it. No schema validation, no data fetching, no YAML parsing. Just pure composition.
Why manifest-core?
✅ What it does
- Inheritance (
extends) - Hierarchical configuration composition - Immutable rules (
tail) - Enforce base policies that can't be overridden - Intelligent merging - Myers algorithm for smart array operations
- Integrity verification - SHA256 checksum for every resolution
- Pluggable loaders - Load from files, databases, S3, or memory
❌ What it doesn't do
- Schema validation - Use your own validation layer
- Data fetching - Implement in your loaders
- File parsing - Parse JSON/YAML before passing in
- External references - Handle in your application layer
Core principle: Do one thing, do it well, make it pluggable.
Installation
npm install manifest-core
# or
pnpm add manifest-coreSingle dependency: json-myers (intelligent diff/patch)
Quick Start
Basic Inheritance
base.json
{
"name": "base",
"patch": {
"database": "postgresql",
"cache": "redis",
"features": ["auth", "logging"]
}
}production.json
{
"name": "production",
"extends": "./base.json",
"patch": {
"features": ["auth", "logging", "analytics"],
"environment": "production"
}
}Usage:
import { ManifestCore } from 'manifest-core';
const core = new ManifestCore();
const config = await core.resolveFile('./production.json');
console.log(config);
// {
// database: "postgresql",
// cache: "redis",
// features: ["auth", "logging", "analytics"],
// environment: "production"
// }Core Concepts
1. Inheritance with extends
Build configurations hierarchically. Present overwrites past.
{
"extends": "./base.json",
"patch": { "newFeature": true }
}Chain inheritance by making each file extend the previous one:
// app.json
{ "extends": "./with-security.json" }
// with-security.json
{ "extends": "./base.json", "patch": { ... } }2. Immutable Rules with tail
Enforce base policies that cannot be overridden. Past overwrites present.
security-policy.json (immutable foundation)
{
"name": "security-policy",
"patch": {
"security": {
"encryption": "AES-256",
"mfa": "required"
}
}
}app.json
{
"name": "app",
"tail": "./security-policy.json",
"patch": {
"security": {
"encryption": "AES-128" // ❌ Will be OVERRIDDEN!
},
"customFeature": true // ✅ Preserved
}
}Result:
{
security: {
encryption: "AES-256", // ← Enforced by tail!
mfa: "required" // ← From tail
},
customFeature: true // ← Your data
}Use cases:
- Compliance policies (SOX, HIPAA, GDPR)
- Security baselines
- Organizational standards
- Immutable contracts
3. Intelligent Merging (Myers Algorithm)
Arrays aren't replaced - they're intelligently merged using json-myers.
Why json-myers? Standard object merging always replaces arrays completely. json-myers enables array versioning - tracking additions, removals, updates, and moves - something traditional merge strategies cannot do.
Base:
{
"patch": {
"permissions": [
{ "role": "admin", "access": "full" },
{ "role": "user", "access": "read" }
]
}
}Child (using $__arrayOps):
{
"extends": "./base.json",
"patch": {
"permissions": {
"$__arrayOps": [
{
"type": "update",
"index": 1,
"item": { "role": "user", "access": "write" }
},
{
"type": "add",
"index": 2,
"item": { "role": "guest", "access": "readonly" }
}
]
}
}
}Result:
{
"permissions": [
{ "role": "admin", "access": "full" },
{ "role": "user", "access": "write" }, // ✅ Updated
{ "role": "guest", "access": "readonly" } // ✅ Added
]
}⚠️ Important: Putting arrays directly in patch will replace them, not merge! Use $__arrayOps for intelligent merging, or use merge field if you want full replacement.
Learn more: json-myers documentation
4. Raw Mode (Bypass Processing)
Load JSON without processing extends or tail in that file.
{
"extends": {
"path": "./legacy-config.json",
"raw": true
}
}Use case: Extending configs that use reserved field names as data.
API Reference
ManifestCore (High-Level API)
import { ManifestCore } from 'manifest-core';
const core = new ManifestCore({
// Custom manifest loader
manifestProvider?: {
load(source: string): Promise<Manifest>
},
// Cache settings
cache?: {
enabled?: boolean;
directory?: string;
},
// Debug mode
debug?: boolean
});
// Resolve from file/URL
await core.resolveFile(path: string): Promise<ResolvedManifest>
// Resolve from object
await core.resolve(manifest: Manifest): Promise<ResolvedManifest>ManifestResolver (Low-Level API)
import { ManifestResolver } from 'manifest-core';
const resolver = new ManifestResolver({
// Custom loaders (file, http, s3, etc.)
loaders?: {
file: Loader,
http: Loader,
s3: Loader,
// ... any protocol
},
cache?: { enabled?: boolean; directory?: string },
debug?: boolean,
logger?: (message: string) => void
});
await resolver.resolve(source: string): Promise<ResolvedManifest>Types
import type {
Manifest,
ResolvedManifest,
ManifestProvider,
Loader,
LoaderRegistry,
MergeStrategy,
ExtendsSpec,
TailSpec
} from 'manifest-core';Custom Loaders (Dependency Injection)
Load manifests from any source - just implement the Loader interface.
Loader Interface
interface Loader {
load(source: string): Promise<string>;
}Database Loader (PostgreSQL)
import { ManifestCore } from 'manifest-core';
import { Pool } from 'pg';
const pool = new Pool({ connectionString: process.env.DATABASE_URL });
const core = new ManifestCore({
manifestProvider: {
async load(source: string) {
const result = await pool.query(
'SELECT data FROM manifests WHERE id = $1',
[source]
);
return result.rows[0].data;
}
}
});
// Load from database by ID
const config = await core.resolveFile('tenant-123');S3 Loader
import { ManifestResolver } from 'manifest-core';
import { S3Client, GetObjectCommand } from '@aws-sdk/client-s3';
const s3 = new S3Client({ region: 'us-east-1' });
const resolver = new ManifestResolver({
loaders: {
s3: {
async load(source: string) {
// s3://bucket/path/to/manifest.json
const [, bucket, ...keyParts] = source.replace('s3://', '').split('/');
const key = keyParts.join('/');
const command = new GetObjectCommand({ Bucket: bucket, Key: key });
const result = await s3.send(command);
return await result.Body!.transformToString();
}
}
}
});
const config = await resolver.resolve('s3://my-bucket/config.json');Redis Cache + HTTP
import Redis from 'ioredis';
const redis = new Redis();
const core = new ManifestCore({
manifestProvider: {
async load(source: string) {
// Try cache first
const cached = await redis.get(`manifest:${source}`);
if (cached) return JSON.parse(cached);
// Fetch from HTTP
const response = await fetch(source);
const manifest = await response.json();
// Cache for 1 hour
await redis.setex(`manifest:${source}`, 3600, JSON.stringify(manifest));
return manifest;
}
}
});In-Memory Loader (Testing)
const manifests = new Map([
['base', { patch: { debug: true } }],
['app', { extends: 'base', patch: { port: 3000 } }]
]);
const core = new ManifestCore({
manifestProvider: {
async load(source: string) {
const manifest = manifests.get(source);
if (!manifest) throw new Error(`Manifest not found: ${source}`);
return manifest;
}
}
});
// Instant, zero I/O
const config = await core.resolveFile('app');Architecture
Resolution Pipeline (5 Steps)
┌─────────────┐
│ Input │ manifest.json
└──────┬──────┘
↓
┌─────────────┐
│ 1. Load │ Load manifest via loaders
└──────┬──────┘
↓
┌─────────────┐
│ 2. Tail │ Resolve tail chain → Store immutable rules
└──────┬──────┘
↓
┌─────────────┐
│ 3. Extends │ Resolve extends chain
└──────┬──────┘
↓
┌─────────────┐
│ 4. Merge │ Myers intelligent merge
└──────┬──────┘
↓
┌─────────────┐
│ 5. Apply │ Enforce tail rules (FINAL)
└──────┬──────┘
↓
┌─────────────┐
│ Output │ Resolved manifest + SHA256 checksum
└─────────────┘Design Principles
- Single Responsibility - Only composition and merging
- Dependency Injection - All I/O is pluggable
- Immutability - Original manifests never modified
- Transparency - Full resolution metadata
- Zero Dependencies* - Only
json-myersfor merging
Real-World Examples
Multi-Environment Config
config/
├── base.json # Common settings
├── development.json # extends base
├── staging.json # extends base
└── production.json # extends base + tail security-policy.jsonconst env = process.env.NODE_ENV || 'development';
const config = await core.resolveFile(`./config/${env}.json`);Multi-Tenant SaaS
// Each tenant extends their tier
const config = await core.resolveFile(`tenants/${tenantId}/config.json`);
// tenant-123/config.json
{
"extends": "../../tiers/premium.json",
"patch": {
"customDomain": "acme.app",
"features": { "sso": true, "analytics": true }
}
}Infrastructure as Code
{
"name": "infrastructure-prod",
"extends": "./infra-base.json",
"tail": "./security-policy.json",
"patch": {
"database": {
"type": "postgresql",
"instances": 3,
"storage": "1TB"
},
"cache": {
"type": "redis",
"instances": 2
}
}
}Feature Flags
{
"extends": "./base-features.json",
"patch": {
"features": [
{ "name": "darkMode", "enabled": true, "rollout": 100 },
{ "name": "newUI", "enabled": true, "rollout": 50 },
{ "name": "aiChat", "enabled": false, "rollout": 0 }
]
}
}Integrity Verification
Every resolution includes a SHA256 checksum.
const config = await core.resolveFile('./production.json');
console.log(config.resolution.checksum);
// "sha256-abc123def456..."
// Store checksum for immutable deployments
const expectedChecksum = config.resolution.checksum;
// Later, verify integrity
const newConfig = await core.resolveFile('./production.json');
if (newConfig.resolution.checksum !== expectedChecksum) {
throw new Error('Configuration has been modified!');
}Use cases:
- Immutable deployments
- Audit trails
- Configuration drift detection
- Compliance verification
Manifest Format
Minimal Manifest
{
"patch": {
"database": "postgres"
}
}Full Manifest
{
// Metadata (optional)
"name": "my-app",
"version": "1.0.0",
"description": "Application configuration",
// Inheritance (optional)
"extends": "./base.json",
// or with options
"extends": {
"path": "./base.json",
"hash": "sha256-abc123...", // Verify integrity
"raw": true // Bypass processing
},
// Immutable rules (optional)
"tail": "./security-policy.json",
// or
"tail": {
"path": "./security-policy.json",
"hash": "sha256-def456..."
},
// Merge strategy (optional)
"mergeStrategy": {
"features": "myers", // Smart array merge
"config": "deep", // Deep merge (arrays replace)
"routes": "replace" // Complete replacement
},
// Payload (ONE of these)
"patch": { ... }, // Myers merge (intelligent)
"merge": { ... }, // Deep merge (arrays replace)
// Or raw payload (when no extends/tail)
"database": "postgres",
"cache": "redis"
}Testing
Built for testability with in-memory providers.
import { describe, it, expect } from 'vitest';
import { ManifestCore } from 'manifest-core';
describe('Configuration Resolution', () => {
it('should merge tenant config with tier defaults', async () => {
const manifests = new Map([
['tier-basic', {
patch: { maxUsers: 10, features: ['core'] }
}],
['tenant-acme', {
extends: 'tier-basic',
patch: { customDomain: 'acme.app' }
}]
]);
const core = new ManifestCore({
manifestProvider: {
async load(source) {
return manifests.get(source.replace('.json', ''))!;
}
}
});
const config = await core.resolveFile('tenant-acme');
expect(config.maxUsers).toBe(10);
expect(config.features).toEqual(['core']);
expect(config.customDomain).toBe('acme.app');
});
});Zero I/O. Instant. Deterministic.
Advanced: Custom Post-Processing
Extend ManifestCore for custom validation or transformation.
import { ManifestCore } from 'manifest-core';
class ValidatedManifestCore extends ManifestCore {
async resolve(manifest: Manifest) {
// 1. Core composition and merging
const resolved = await super.resolve(manifest);
// 2. Your custom validation
this.validate(resolved);
// 3. Your custom transformation
const transformed = this.transform(resolved);
return transformed;
}
private validate(manifest: any): void {
// Add your validation logic
if (!manifest.database) {
throw new Error('Database configuration required');
}
}
private transform(manifest: any): any {
// Add your transformation logic
return manifest;
}
}Contributing
Contributions welcome!
# Install dependencies
pnpm install
# Run tests
pnpm test
# Run tests with coverage
pnpm test:coverage
# Type check
pnpm typecheck
# Build
pnpm buildGuidelines:
- Add tests for new features
- Ensure all tests pass
- Maintain TypeScript strict mode
- Update documentation
Documentation
- Architecture Guide - Deep dive into internals
- Payload Rules - Merge strategies explained
- JSON Myers Guide - How intelligent merging works
License
MIT © 2025 Anderson D. Rosa
Links
manifest-core - Smart JSON composition for production systems
Simple. Powerful. Pluggable.
