Skip to content
/ kernel Public

Zern Kernel is a strongly-typed, extensible plugin runtime for TypeScript/Node. It provides deterministic plugin ordering, lifecycle phases, hooks, events, errors, and alerts, plus safe augmentations to compose features. Focused on DX and type inference, it delivers clean APIs, powerful tooling, and robust error routing.

License

zernjs/kernel

Repository files navigation

πŸ”₯ Zern Kernel

Strongly-Typed Plugin Kernel

Ultra-lightweight plugin orchestration with exceptional developer experience

TypeScript Node.js MIT License Coverage

CI CodeQL OpenSSF Scorecard

Features β€’ Quick Start β€’ Documentation β€’ Examples β€’ API Reference


🌟 Overview

Zern Kernel is a next-generation plugin system designed for exceptional developer experience. It provides a minimal, type-safe core that enables plugins to work naturally like independent libraries, with automatic dependency resolution, transparent API augmentation, and powerful method interception.

Why Zern Kernel?

  • 🎯 Natural API Design - Plugins feel like native libraries, not framework components
  • πŸ”’ Complete Type Safety - Full TypeScript support with autocomplete everywhere
  • πŸš€ Zero Boilerplate - Fluent API eliminates ceremonial code
  • πŸ”„ Intelligent Resolution - Automatic dependency ordering with version validation
  • ⚑ Runtime Flexibility - Extend, intercept, and modify plugin behavior dynamically

✨ Features

Core Capabilities

Feature Description
πŸͺΆ Minimal Core Only essential functionality - register, initialize, shutdown
πŸ”„ Fluent API Clean, chainable interface for plugin and kernel configuration
πŸ€– Auto Dependency Resolution Topological sorting with intelligent cycle detection
πŸ”§ API Extensions Plugins can seamlessly extend other plugins' APIs
🎭 Method Proxying Intercept and modify behavior with before/after/around hooks
⏱️ Lifecycle Hooks onInit, onReady, onShutdown, onError for resource management
πŸ—„οΈ Reactive Store Automatic reactive state with watchers, computed values, and transactions
🏷️ Complete Plugin Access kernel.get() returns API + $meta + $store for full plugin inspection
πŸ“¦ Direct Exports Import plugin methods directly like a normal library
πŸ›‘οΈ Error Handling Hierarchical typed errors with stack traces, solutions, and severities
πŸ” Version Control Semantic versioning with flexible constraint matching

Advanced Features

  • βœ… 4 Proxy Modes: Self-proxy, single plugin, dependencies, and global
  • βœ… Kernel-Level Proxies: Application-level interception via createKernel().proxy()
  • βœ… Type-Safe Context: Plugin dependencies and metadata are fully typed
  • βœ… Priority-Based Execution: Control proxy execution order with priorities
  • βœ… Conditional Proxies: Apply interceptors based on runtime conditions
  • βœ… Method Selectors: Fine-grained control with include/exclude patterns

πŸš€ Quick Start

Installation

npm install @zern/kernel

Basic Example

import { createKernel, plugin } from '@zern/kernel';

// 1️⃣ Create a database plugin
const databasePlugin = plugin('database', '1.0.0')
  .metadata({
    author: 'Zern Team',
    category: 'data',
  })
  .setup(() => ({
    async connect(url: string) {
      console.log(`Connected to: ${url}`);
      return { connected: true };
    },
    users: {
      async create(userData: { name: string; email: string }) {
        const id = Math.random().toString(36).slice(2);
        console.log(`User created: ${id}`);
        return { id, ...userData };
      },
    },
  }));

// 2️⃣ Create auth plugin with dependency
const authPlugin = plugin('auth', '1.0.0')
  .depends(databasePlugin, '^1.0.0')
  .onInit(({ plugins }) => {
    console.log('Auth initializing...');
    console.log('Database author:', plugins.database.$meta.author);
  })
  .setup(({ plugins }) => ({
    async validateToken(token: string) {
      console.log(`Validating token: ${token}`);
      return token === 'valid-token';
    },
  }));

// 3️⃣ Initialize kernel and use plugins
const kernel = await createKernel().use(databasePlugin).use(authPlugin).start();

// βœ… Type-safe plugin access with $meta and $store
const db = kernel.get('database');
await db.connect('postgresql://localhost:5432/mydb');

// Access metadata
console.log(db.$meta.author); // "Zern Team"
console.log(db.$meta.category); // "data"

const user = await db.users.create({
  name: 'John Doe',
  email: 'john@example.com',
});

const auth = kernel.get('auth');
const isValid = await auth.validateToken('valid-token');

// Cleanup
await kernel.shutdown();

πŸ”§ Core Concepts

1. Plugin Creation

const mathPlugin = plugin('math', '1.0.0')
  .metadata({ author: 'Zern Team' })
  .setup(() => ({
    add: (a: number, b: number) => a + b,
    multiply: (a: number, b: number) => a * b,
  }));

2. Dependencies

const calculatorPlugin = plugin('calculator', '1.0.0')
  .depends(mathPlugin, '^1.0.0') // Semantic versioning
  .setup(({ plugins }) => ({
    calculate: (expr: string) => {
      // Access math plugin with full type safety
      return plugins.math.add(1, 2);
    },
  }));

3. Reactive Store

Create automatically reactive type-safe state accessible across all plugin stages:

const databasePlugin = plugin('database', '1.0.0')
  .store(() => ({
    connection: null as Connection | null,
    queryCount: 0,
    startTime: Date.now(),
  }))
  .onInit(async ({ store }) => {
    // Initialize connection in store
    store.connection = await createConnection();

    // πŸ”₯ Watch for changes automatically
    store.watch('queryCount', change => {
      console.log(`Queries: ${change.oldValue} β†’ ${change.newValue}`);
    });
  })
  .proxy({
    include: ['*'],
    before: ctx => {
      // Track queries in store (triggers watchers automatically)
      ctx.store.queryCount++;
    },
  })
  .setup(({ store }) => ({
    query: async (sql: string) => {
      // Access store in methods
      if (!store.connection) throw new Error('Not connected');
      return await store.connection.execute(sql);
    },
    getStats: () => ({
      queries: store.queryCount,
      uptime: Date.now() - store.startTime,
    }),
  }))
  .onReady(({ store, api }) => {
    // Access both store and api in hooks
    console.log(`Database ready. Stats:`, api.getStats());
  });

Reactive Features:

  • βœ… Automatic reactivity - No manual setup required
  • βœ… Watch changes - watch(), watchAll(), watchBatch()
  • βœ… Computed values - Memoized derived state with computed()
  • βœ… Batch updates - Group changes with batch()
  • βœ… Transactions - Atomic updates with automatic rollback
  • βœ… Performance - ~10x faster with optimized cloning and indexed watchers
  • βœ… Type inference - No generics needed, full autocomplete
  • βœ… Isolated - Each plugin has its own store

πŸ“š See Store System for complete documentation

4. API Extensions

Extend another plugin's API transparently:

const advancedMathPlugin = plugin('advancedMath', '1.0.0')
  .depends(mathPlugin, '^1.0.0')
  .extend(mathPlugin, api => ({
    // Add new methods to math plugin
    power: (base: number, exp: number) => Math.pow(base, exp),
    sqrt: (x: number) => Math.sqrt(x),
  }))
  .setup(() => ({}));

// After kernel initialization:
const math = kernel.get('math');
math.power(2, 3); // βœ… Extended method available!
math.sqrt(16); // βœ… All extensions are merged

5. Method Proxying

Intercept and modify plugin behavior:

const loggingPlugin = plugin('logging', '1.0.0')
  .depends(mathPlugin, '^1.0.0')
  .proxy(mathPlugin, {
    include: ['add'], // Intercept specific method
    before: ctx => {
      console.log(`[LOG] Calling ${ctx.method} with:`, ctx.args);
    },
    after: (result, ctx) => {
      console.log(`[LOG] ${ctx.method} returned:`, result);
      return result;
    },
  })
  .setup(() => ({}));

Proxy Modes:

// 1. Self-proxy: Intercept own methods
.proxy({ include: ['add'], before: ctx => console.log('self') })

// 2. Single plugin proxy: Intercept specific plugin
.depends(mathPlugin, '^1.0.0')
.proxy(mathPlugin, { before: ctx => console.log('single') })

// 3. Dependencies proxy: Intercept all dependencies
.proxy('*', { before: ctx => console.log('all deps') })

// 4. Global proxy: Intercept ALL plugins
.proxy('**', { before: ctx => console.log('global') })

6. Kernel-Level Proxies

Apply proxies at the application level:

const kernel = await createKernel()
  .use(mathPlugin)
  .use(apiPlugin)
  // Global logging for all plugins
  .proxy('**', {
    priority: 80,
    before: ctx => {
      console.log(`[LOG] ${ctx.plugin}.${ctx.method}() called`);
    },
  })
  // Specific auth for API plugin
  .proxy(apiPlugin, {
    priority: 100,
    include: ['create*', 'update*', 'delete*'],
    before: ctx => checkPermissions(ctx.method),
  })
  .start();

7. Lifecycle Hooks

Manage plugin initialization, readiness, and cleanup:

const databasePlugin = plugin('database', '1.0.0')
  .metadata({ connectionString: 'postgresql://localhost:5432/db' })
  .onInit(({ plugins }) => {
    console.log('Initializing database...');
  })
  .onReady(({ plugins }) => {
    console.log('Database ready!');
  })
  .onShutdown(({ plugins }) => {
    console.log('Closing connections...');
  })
  .onError(({ error, plugins }) => {
    console.error('Database error:', error);
  })
  .setup(() => ({
    query: async (sql: string) => {
      /* ... */
    },
  }));

8. Accessing Plugin Metadata and Store

kernel.get() returns complete plugin access - API methods, metadata, and reactive store:

const mathPlugin = plugin('math', '1.0.0')
  .metadata({
    author: 'Zern Team',
    category: 'utilities',
    precision: 'high',
  })
  .store(() => ({
    operationCount: 0,
    lastResult: 0,
  }))
  .setup(({ store }) => ({
    add: (a: number, b: number) => {
      store.operationCount++;
      store.lastResult = a + b;
      return a + b;
    },
  }));

const kernel = await createKernel().use(mathPlugin).start();
const math = kernel.get('math');

// βœ… Use API methods
const result = math.add(10, 5); // 15

// βœ… Access metadata (read-only)
console.log(math.$meta.name); // "math"
console.log(math.$meta.version); // "1.0.0"
console.log(math.$meta.author); // "Zern Team"
console.log(math.$meta.precision); // "high"

// βœ… Access and modify reactive store
console.log(math.$store.operationCount); // 1
console.log(math.$store.lastResult); // 15

// βœ… Watch store changes
math.$store.watch('operationCount', change => {
  console.log(`Operations: ${change.oldValue} β†’ ${change.newValue}`);
});

// βœ… Use all Store methods
math.$store.batch(() => {
  math.$store.operationCount++;
  math.$store.lastResult = 100;
});

const doubled = math.$store.computed(s => s.operationCount * 2);
console.log(doubled.value); // 4

// Next operation triggers watcher
math.add(20, 30); // Logs: "Operations: 2 β†’ 3"

Available on every plugin:

Property Description Example
API methods All plugin methods with full type safety math.add(1, 2)
$meta.name Plugin name "math"
$meta.version Plugin version "1.0.0"
$meta.* Custom metadata math.$meta.author
$store.* Reactive state properties math.$store.count
$store.watch() Watch specific property math.$store.watch('count', fn)
$store.watchAll() Watch all changes math.$store.watchAll(fn)
$store.batch() Batch multiple updates math.$store.batch(() => {...})
$store.computed() Create computed values math.$store.computed(s => s.x * 2)
$store.transaction() Atomic updates with rollback await math.$store.transaction(async () => {...})

πŸ“š See Kernel Layer and Store System for complete documentation

9. Direct Method Exports

Use plugins like normal libraries:

// In your plugin file:
export const mathPlugin = plugin('math', '1.0.0').setup(() => ({
  add: (a: number, b: number) => a + b,
  multiply: (a: number, b: number) => a * b,
}));

// Export direct methods
export const { add, multiply } = createDirectExports('math', {
  add: (a: number, b: number): number => 0,
  multiply: (a: number, b: number): number => 0,
});

// Usage in other files:
import { add, multiply } from './math-plugin';
console.log(add(2, 3)); // βœ… Full type safety!
console.log(multiply(4, 5)); // βœ… Autocomplete works!

10. Error Handling

Professional error handling with typed errors, stack traces, and actionable solutions:

import { ValidationError, ErrorSeverity, solution } from '@zern/kernel';

const apiPlugin = plugin('api', '1.0.0')
  .config({
    errors: {
      showStackTrace: true,
      stackTraceLimit: 10,
      formatErrors: true,
      severity: ErrorSeverity.ERROR,
    },
  })
  .onError(async ({ error, phase, method }) => {
    // Global error handler for all plugin phases
    console.error(`[${phase}] Error in ${method}:`, error);

    // Send to monitoring service
    if (error.severity === ErrorSeverity.FATAL) {
      await alertTeam(error);
    }
  })
  .setup(() => ({
    async fetchUser(userId: string) {
      if (!userId) {
        throw new ValidationError(
          { userId },
          {
            severity: ErrorSeverity.ERROR,
            solutions: [
              solution(
                'Provide a valid user ID',
                'The userId parameter cannot be empty',
                'api.fetchUser("user-123")'
              ),
            ],
          }
        );
      }
      return { id: userId, name: 'John' };
    },
  }));

Error Features:

  • βœ… Typed Errors - Hierarchical error classes with full type safety
  • βœ… Stack Traces - Automatic parsing with file, line, and column
  • βœ… Solutions - Actionable suggestions for resolving errors
  • βœ… Severities - INFO, WARN, ERROR, FATAL levels
  • βœ… Context - Rich metadata about where and why errors occurred
  • βœ… Global Hooks - Capture errors from any phase (init, setup, runtime, shutdown)
  • βœ… Formatting - Beautiful console output with colors and structure

πŸ“š See Error Handling for complete documentation


πŸ“š Documentation

Comprehensive documentation is available in the docs/ directory:

Document Description
Architecture Overview System design and layer architecture
Getting Started Installation and first steps
Plugin System Creating and managing plugins
Kernel Layer Kernel initialization and lifecycle
Extension System Extending plugin APIs
Direct Exports Library-like method exports
Lifecycle Hooks Plugin lifecycle management
Metadata System Custom metadata with type safety
API Reference Complete API documentation
Best Practices Patterns and guidelines
Proxy System Method interception and proxying
Store System Reactive state with watchers, computed values, and transactions
Error Handling Typed errors with stack traces, solutions, and severities

πŸ’‘ Examples

Explore complete examples in the examples/ directory:


🎯 Use Cases

Cross-Cutting Concerns

// Global logging
const loggingPlugin = plugin('logging', '1.0.0')
  .proxy('**', {
    before: ctx => console.log(`[LOG] ${ctx.plugin}.${ctx.method}()`),
  })
  .setup(() => ({}));

// Performance monitoring (using plugin store)
const timingPlugin = plugin('timing', '1.0.0')
  .store(() => new Map<string, number>()) // Map to store start times by method
  .proxy('**', {
    before: ctx => {
      const key = `${ctx.plugin}.${ctx.method}`;
      ctx.store.set(key, Date.now());
    },
    after: (result, ctx) => {
      const key = `${ctx.plugin}.${ctx.method}`;
      const startTime = ctx.store.get(key);
      if (startTime) {
        console.log(`⏱️ ${key} took ${Date.now() - startTime}ms`);
        ctx.store.delete(key);
      }
      return result;
    },
  })
  .setup(() => ({}));

Authentication & Authorization

const authPlugin = plugin('auth', '1.0.0')
  .depends(apiPlugin, '^1.0.0')
  .proxy(apiPlugin, {
    include: ['create*', 'update*', 'delete*'],
    priority: 100, // Execute first
    before: ctx => {
      if (!isAuthenticated()) {
        ctx.skip();
        throw new Error('Unauthorized');
      }
    },
  })
  .setup(() => ({
    /* ... */
  }));

Caching

const cachePlugin = plugin('cache', '1.0.0')
  .depends(apiPlugin, '^1.0.0')
  .proxy(apiPlugin, {
    include: ['get*', 'find*'],
    priority: 90,
    around: async (ctx, next) => {
      const key = `${ctx.method}:${JSON.stringify(ctx.args)}`;
      const cached = cache.get(key);
      if (cached) return cached;

      const result = await next();
      cache.set(key, result);
      return result;
    },
  })
  .setup(() => ({}));

Error Monitoring & Resilience

See examples/plugins/ for complete implementations:

// Telemetry: Track all errors across plugins
const telemetryPlugin = plugin('telemetry', '1.0.0')
  .proxy('**', {
    onError: async (error, ctx) => {
      // Capture and send to monitoring service
      console.log(`[TELEMETRY] Error in ${ctx.plugin}.${ctx.method}:`, error);
      await sendToDatadog({ error, context: ctx });
      throw error; // Re-throw to allow other handlers
    },
  })
  .setup(() => ({
    /* ... */
  }));

// Retry: Automatic retry with exponential backoff
const retryPlugin = plugin('retry', '1.0.0')
  .proxy('**', {
    around: async (ctx, next) => {
      const maxRetries = 3;
      for (let i = 0; i < maxRetries; i++) {
        try {
          return await next();
        } catch (error) {
          if (i === maxRetries - 1) throw error;
          await sleep(Math.pow(2, i) * 1000);
        }
      }
    },
  })
  .setup(() => ({
    /* ... */
  }));

// Error Boundary: Graceful fallbacks
const errorBoundaryPlugin = plugin('errorBoundary', '1.0.0')
  .proxy('**', {
    onError: async (error, ctx) => {
      console.error(`[BOUNDARY] Caught error in ${ctx.plugin}.${ctx.method}`);
      return getFallbackValue(ctx.method); // Return fallback instead of throwing
    },
  })
  .setup(() => ({
    /* ... */
  }));

πŸ’‘ Pro Tip: These patterns demonstrate how Zern's proxy system enables powerful cross-cutting concerns without modifying plugin code!


πŸ” Advanced Configuration

Kernel Configuration

const kernel = await createKernel()
  .use(myPlugin)
  .config({
    autoGlobal: true, // Auto-register as global kernel
    strictVersioning: true, // Enforce strict version matching
    circularDependencies: false, // Disallow circular dependencies
    initializationTimeout: 30000, // Timeout in milliseconds
    extensionsEnabled: true, // Enable plugin extensions
    logLevel: 'info', // Log level: debug | info | warn | error
    errors: {
      showStackTrace: true, // Show stack traces in errors
      stackTraceLimit: 10, // Limit stack trace depth
      formatErrors: true, // Format errors for console output
    },
  })
  .start();

Version Constraints

const authPlugin = plugin('auth', '1.0.0')
  .depends(databasePlugin, '^1.0.0') // Compatible with 1.x.x
  .depends(cachePlugin, '>=2.0.0') // Requires 2.0.0 or higher
  .depends(utilsPlugin, '~1.2.3') // Compatible with 1.2.x
  .setup(({ plugins }) => ({
    /* ... */
  }));

πŸ› οΈ API Quick Reference

Creating Plugins

plugin(name: string, version: string)
  .metadata(data: Record<string, unknown>)
  .store(factory: () => state)                 // Reactive store (watch, computed, batch, transaction)
  .config(options: Partial<PluginConfig>)      // Plugin-level configuration (errors, etc)
  .depends(plugin: BuiltPlugin, versionRange?: string)
  .extend(target: BuiltPlugin, fn: (api) => extensions)
  .proxy(config: ProxyConfig)                  // Self-proxy
  .proxy(target: BuiltPlugin, config: ProxyConfig)  // Single plugin
  .proxy('*', config: ProxyConfig)             // All dependencies
  .proxy('**', config: ProxyConfig)            // All plugins
  .onInit(hook: (ctx) => void)
  .onReady(hook: (ctx) => void)
  .onShutdown(hook: (ctx) => void)
  .onError(hook: (ctx) => void)                // Captures errors from all phases
  .setup(fn: (ctx) => api)

Creating Kernel

createKernel()
  .use(plugin: BuiltPlugin)
  .config(config: Partial<KernelConfig>)       // Kernel-level configuration
  .proxy(target: BuiltPlugin, config: ProxyConfig)
  .proxy('**', config: ProxyConfig)
  .build()
  .start()

Using Kernel

kernel.get(name: string)      // Get plugin API + $meta + $store
kernel.shutdown()             // Shutdown all plugins

What kernel.get() returns:

const plugin = kernel.get('myPlugin');

// βœ… API methods (fully typed)
plugin.myMethod();

// βœ… Metadata access
plugin.$meta.name; // Plugin name
plugin.$meta.version; // Plugin version
plugin.$meta.author; // Custom metadata

// βœ… Reactive store access
plugin.$store.count; // Read state
plugin.$store.count++; // Update state
plugin.$store.watch('count', change => {
  console.log(`Changed: ${change.newValue}`);
});

πŸ§ͺ Testing

import { createKernel, plugin } from '@zern/kernel';

describe('MyPlugin', () => {
  it('should work correctly', async () => {
    const kernel = await createKernel().use(myPlugin).start();

    const api = kernel.get('myPlugin');
    expect(api.myMethod()).toBe('expected');

    await kernel.shutdown();
  });
});

🀝 Contributing

Contributions are welcome! Please read our contributing guidelines before submitting pull requests.


πŸ“„ License

MIT Β© ZernJS


⬆ Back to Top

Made with ❀️ by the Zern Team

About

Zern Kernel is a strongly-typed, extensible plugin runtime for TypeScript/Node. It provides deterministic plugin ordering, lifecycle phases, hooks, events, errors, and alerts, plus safe augmentations to compose features. Focused on DX and type inference, it delivers clean APIs, powerful tooling, and robust error routing.

Resources

License

Security policy

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Contributors 3

  •  
  •  
  •