Back to Blog

Architecture Internals: The Plugin Lifecycle & Dependency Graph

• By ObjectOS Engineering

Architecture Internals: The Plugin Lifecycle

ObjectOS is designed as a Modular Monolith. It runs as a single process (Node.js), but it is structured as a collection of strictly decoupled plugins managed by a micro-kernel.

This article explores the Lifecycle Manager—the part of the kernel responsible for booting the system.

The Plugin Manifest

Every unit of functionality in ObjectOS is a plugin. A plugin is defined by its manifest.ts.

// plugins/inventory/manifest.ts
export const InventoryPlugin = definePlugin({
  id: 'com.objectos.inventory',
  version: '1.2.0',
  dependencies: [
    'com.objectos.auth', 
    'com.objectos.products'
  ],
  provides: ['inventory.service']
});

The Boot Sequence (Topological Sort)

When ObjectOS.boot() is called, the kernel performs the following steps:

  1. Discovery: Scans the plugins/ directory (or node_modules) to find manifests.
  2. Graph Construction: Builds a Directed Acyclic Graph (DAG) based on the dependencies array.
  3. Cycle Detection: If Plugin A depends on Plugin B, and Plugin B depends on Plugin A, the kernel throws a CircularDependencyError and halts boot.
  4. Ordering: Performs a Topological Sort to determine the linear load order.
    • Result: Auth -> Products -> Inventory.

Code Snapshot: The Resolver

// @objectos/kernel/src/loader.ts

function resolveLoadOrder(plugins: Map<string, Plugin>): Plugin[] {
  const sorted: Plugin[] = [];
  const visited = new Set<string>();
  const tempStack = new Set<string>();

  function visit(pluginId: string) {
    if (tempStack.has(pluginId)) {
      throw new Error(`Circular dependency detected involving ${pluginId}`);
    }
    if (visited.has(pluginId)) return;

    tempStack.add(pluginId);
    
    const plugin = plugins.get(pluginId);
    for (const depId of plugin.manifest.dependencies) {
      visit(depId);
    }

    tempStack.delete(pluginId);
    visited.add(pluginId);
    sorted.push(plugin);
  }

  for (const id of plugins.keys()) visit(id);
  
  return sorted;
}

The Sandbox and Service Container

Once loaded, plugins do not access global variables. They interact with the system via the Context.

The Kernel creates a Sandbox for each plugin.

  • Service Registry: A plugin can provide() services.
  • Service Injection: A plugin inject()s services from dependencies.
// Inside Inventory Plugin
async function onLoad(ctx: PluginContext) {
  // Safe Injection: guaranteed to be available because of the DAG sort
  const authService = ctx.inject('auth.service');
  
  // Registering own service
  ctx.provide('inventory.reserve', async (itemId, qty) => {
    // Implementation
  });
}

Error Boundaries and Crash Protection

What happens if a plugin crashes? If the Inventory plugin throws an unhandled exception during an HTTP request, we don't want the entire OS to crash.

ObjectOS wraps every plugin hook in a try/catch block that acts as an Error Boundary.

  1. Isolation: The faulty request is terminated with a 500 error.
  2. Quarantine: If a plugin throws too many errors in a short window (Circuit Breaker pattern), the kernel can "pause" the plugin, rejecting traffic to it while keeping the rest of the OS (e.g., Auth, CRM) alive.

Hot Module Replacement (HMR)

In development, the Kernel supports HMR. Because the dependency graph is known, when you edit InventoryPlugin, the kernel can:

  1. Dispose InventoryPlugin.
  2. Dispose any plugins that depend on InventoryPlugin (reverse topological walk).
  3. Reload the code.
  4. Re-initialize them in order.

This provides a blazing fast developer experience without restarting the Node.js process.

Summary

The ObjectOS Kernel is not just a express.use() chain. It is a sophisticated dependency injection container and lifecycle manager that ensures strict architectural boundaries. This discipline is what allows ObjectOS projects to scale to millions of lines of code without becoming a "Distributed Monolith" nightmare.