Architecture Document - Updated January 2026
This document provides a comprehensive overview of ObjectStack's microkernel architecture, package distribution, and design principles.
- Architecture Overview
- Microkernel Design
- Package Structure
- Layer Architecture
- Plugin System
- Dependency Graph
- Design Decisions
- Additional Resources
ObjectStack is built on a microkernel architecture inspired by operating system design principles. The system separates core functionality (the kernel) from business logic (plugins), enabling:
- Modularity: Each component is independently developed, tested, and deployed
- Extensibility: New features added through plugins without modifying core
- Testability: Components can be mocked and tested in isolation
- Technology Independence: Swap implementations without breaking contracts
- Protocol-First Design: All interactions defined through Zod schemas
- Separation of Concerns: Three distinct layers (QL, OS, UI)
- Dependency Injection: Services registered and consumed through DI container
- Event-Driven Communication: Plugins communicate via hooks/events
- Reverse Domain Naming: Global uniqueness through domain notation
Location: packages/core/src/kernel.ts
The ObjectKernel is the heart of the system, providing:
┌─────────────────────────────────────────────────────┐
│ ObjectKernel (Core) │
│ ┌───────────────────────────────────────────────┐ │
│ │ Plugin Lifecycle Manager │ │
│ │ • Dependency Resolution (Topological Sort) │ │
│ │ • Init → Start → Destroy Phases │ │
│ └───────────────────────────────────────────────┘ │
│ ┌───────────────────────────────────────────────┐ │
│ │ Service Registry (DI Container) │ │
│ │ • registerService(name, service) │ │
│ │ • getService<T>(name): T │ │
│ └───────────────────────────────────────────────┘ │
│ ┌───────────────────────────────────────────────┐ │
│ │ Event Bus (Hook System) │ │
│ │ • hook(name, handler) │ │
│ │ • trigger(name, ...args) │ │
│ └───────────────────────────────────────────────┘ │
│ ┌───────────────────────────────────────────────┐ │
│ │ Logger (Pino-based) │ │
│ │ • Server: JSON logs to stdout │ │
│ │ • Browser: Console with pretty formatting │ │
│ └───────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────┘
│
┌─────────┴─────────┬──────────┬──────────┐
│ │ │ │
┌───▼────┐ ┌───────▼──┐ ┌──▼───┐ ┌───▼────┐
│ObjectQL│ │ Driver │ │ Hono │ │ App │
│ Plugin │ │ Plugin │ │Server│ │ Plugin │
└────────┘ └──────────┘ └──────┘ └────────┘
State Machine:
┌────────┐
│ idle │ ← Initial state
└───┬────┘
│ kernel.use(plugin)
▼
┌────────┐
│ init │ ← Register services, subscribe to hooks
└───┬────┘
│ kernel.bootstrap()
▼
┌────────┐
│ start │ ← Connect to databases, start servers
└───┬────┘
│
▼
┌─────────┐
│ running │
└───┬─────┘
│ kernel.shutdown()
▼
┌─────────┐
│ destroy │ ← Clean up resources
└─────────┘
ObjectStack is organized as a monorepo with distinct package layers:
Location: packages/spec/
Role: Protocol Definitions (The "Constitution")
- Zod schemas for all protocols (ObjectQL, ObjectOS, ObjectUI)
- TypeScript type definitions
- JSON Schema generation
- Constants and enums
- Zero runtime dependencies (except Zod)
// packages/spec/src/
├── data/ # ObjectQL schemas (Object, Field, Query, etc.)
├── ui/ # ObjectUI schemas (App, View, Dashboard, etc.)
├── system/ # ObjectOS schemas (Manifest, Driver, Plugin, etc.)
├── automation/ # Workflow, Flow, Trigger schemas
├── ai/ # Agent, RAG, Model schemas
├── api/ # API contract schemas
├── auth/ # Authentication & Authorization schemas
└── shared/ # Common utilitiesLocation: packages/core/
Role: Microkernel Runtime
ObjectKernel- Plugin lifecycle managerPluginContext- Runtime context for pluginsLogger- Universal logging (Pino-based)- Service Registry (DI Container)
- Event Bus (Hook System)
Dependencies: @objectstack/spec, pino, zod
Location: packages/types/
Role: Shared Runtime Interfaces
- Type definitions used across packages
- Runtime environment types
- Minimal, focused on shared contracts
Dependencies: @objectstack/spec
Location: packages/objectql/
Role: ObjectQL Query Engine
- ObjectQL core engine
- Schema registry
- Query compilation
- Driver management
- Protocol implementation
Dependencies: @objectstack/core, @objectstack/spec, @objectstack/types
Location: packages/metadata/
Role: Metadata Loading & Persistence
- Metadata serialization/deserialization (JSON, YAML, TypeScript)
- File system operations (load, save, watch)
- Validation using Zod schemas
- ETag-based caching
- Import/export tools
- SchemaRegistry persistence bridge
Dependencies: @objectstack/spec, @objectstack/core, @objectstack/types, glob, js-yaml, chokidar
Location: packages/runtime/
Role: Runtime Utilities & Plugins
DriverPlugin- Generic driver wrapperAppPlugin- Application loader- HTTP Server utilities
- REST API server
- Middleware system
Dependencies: @objectstack/core, @objectstack/spec, @objectstack/types
Location: packages/client/
Role: Official TypeScript Client SDK for ObjectStack Protocol
Features:
- Auto-Discovery: Connects to ObjectStack server and auto-configures API endpoints
- Typed Metadata: Retrieve Object and View definitions with full TypeScript support
- Metadata Caching: ETag-based conditional requests for efficient metadata caching
- Data Operations:
- Advanced queries with filtering, sorting, and field selection
- CRUD operations (create, read, update, delete)
- Batch operations with transaction support (batch create/update/upsert/delete)
- Query builder with type-safe filters
- View Storage: Save, load, share, and manage custom UI view configurations
- Error Handling: Standardized error codes with retry guidance
- HTTP Caching: ETag support for optimized metadata requests
API Surface:
client.connect(): Initialize client with server discoveryclient.meta.*: Metadata operations (getObject, getCached, getView)client.data.*: Data operations (find, get, query, create, batch, update, delete)client.views.*: View storage operations (create, get, list, update, delete, share)
Dependencies: @objectstack/core, @objectstack/spec
Location: packages/client-react/
Role: React Hooks for ObjectStack Client SDK
Features:
- Data Hooks:
useQuery,useMutation,usePagination,useInfiniteQuery - Metadata Hooks:
useObject,useView,useFields,useMetadata - Context Management:
ObjectStackProvider,useClient - Type Safety: Full TypeScript generics support for type-safe data
- Auto-refetch: Automatic data refetching and caching
- Loading States: Built-in loading and error state management
Dependencies: @objectstack/client, @objectstack/core, @objectstack/spec
Peer Dependencies: react
Located in packages/plugins/*:
Location: packages/plugins/driver-memory/
Role: In-Memory Driver (Reference Implementation)
- Complete ObjectQL driver implementation
- Used for testing and prototyping
- Demonstrates driver contract
Dependencies: @objectstack/core, @objectstack/spec
Location: packages/plugins/plugin-hono-server/
Role: HTTP Server Plugin
- Hono-based HTTP server
- REST API endpoints
- Middleware support
Dependencies: @objectstack/core, @objectstack/spec, @objectstack/types, hono
Location: packages/plugins/plugin-msw/
Role: Mock Service Worker Plugin
- Browser-based API mocking
- E2E testing support
- Development mode
Dependencies: @objectstack/objectql, @objectstack/spec, @objectstack/types, msw
Location: packages/cli/
Role: Command Line Interface
- Project scaffolding
- Code generation
- Development tools
- Build & deployment utilities
Dependencies: @objectstack/spec, commander, chalk, tsx, zod
ObjectStack follows a Three-Layer Protocol Stack:
┌──────────────────────────────────────────────────┐
│ Layer 3: ObjectUI (View) │
│ ┌────────────────────────────────────────────┐ │
│ │ Apps, Views, Dashboards, Reports │ │
│ │ "How do users interact?" │ │
│ └────────────────────────────────────────────┘ │
│ Packages: @objectstack/client-react │
└────────────────┬─────────────────────────────────┘
│ REST API / GraphQL
┌────────────────▼─────────────────────────────────┐
│ Layer 2: ObjectOS (Control) │
│ ┌────────────────────────────────────────────┐ │
│ │ Auth, Permissions, Workflows, Events │ │
│ │ "Who can do what, when?" │ │
│ └────────────────────────────────────────────┘ │
│ Packages: @objectstack/runtime, │
│ @objectstack/plugin-hono-server │
└────────────────┬─────────────────────────────────┘
│ ObjectQL Protocol
┌────────────────▼─────────────────────────────────┐
│ Layer 1: ObjectQL (Data) │
│ ┌────────────────────────────────────────────┐ │
│ │ Objects, Fields, Queries, Drivers │ │
│ │ "What is the data structure?" │ │
│ └────────────────────────────────────────────┘ │
│ Packages: @objectstack/objectql, │
│ @objectstack/driver-* │
└──────────────────────────────────────────────────┘
Foundation:
┌──────────────────────────────────────────────────┐
│ @objectstack/core (Microkernel) │
│ @objectstack/spec (Protocols) │
│ @objectstack/types (Shared Types) │
└──────────────────────────────────────────────────┘
| Layer | Knows About | Doesn't Know About |
|---|---|---|
| ObjectQL | Schema, fields, queries, drivers | Users, permissions, UI |
| ObjectOS | Auth, workflows, events | Data structure, UI layout |
| ObjectUI | Layout, navigation, actions | Business logic, storage |
interface Plugin {
name: string; // Reverse domain notation
version?: string; // Semantic version
dependencies?: string[]; // Plugin names this depends on
init(ctx: PluginContext): Promise<void> | void;
start?(ctx: PluginContext): Promise<void> | void;
destroy?(): Promise<void> | void;
}Name: com.objectstack.engine.objectql
Location: packages/objectql/src/plugin.ts
Services Registered:
- 'objectql': ObjectQL engine instance
- 'protocol': Protocol implementation shim
Lifecycle:
- init: Register ObjectQL as service
- start: Discover and register drivers and appsName: com.objectstack.driver.{name}
Location: packages/runtime/src/driver-plugin.ts
Services Registered:
- 'driver.{name}': Driver instance
Lifecycle:
- init: Register driver service
- start: Log driver startedName: com.objectstack.app.{name}
Location: packages/runtime/src/app-plugin.ts
Services Registered:
- 'app.{name}': Manifest instance
Lifecycle:
- init: Register app manifest as serviceLocation: packages/spec/src/system/plugin-capability.zod.ts
Plugins declare capabilities through structured manifests:
interface PluginCapabilityManifest {
implements?: PluginCapability[]; // Protocols implemented
provides?: PluginInterface[]; // Services exposed
requires?: PluginDependency[]; // Required plugins
extensionPoints?: ExtensionPoint[]; // Extension hooks
extensions?: Extension[]; // Extensions contributed
}@objectstack/spec (Zod schemas, zero runtime deps except zod)
↑
├── @objectstack/types
│ ↑
│ └── (Shared by many packages)
│
├── @objectstack/core (Kernel + Logger)
│ ↑
│ ├── @objectstack/objectql (Query Engine)
│ │ ↑
│ │ └── @objectstack/plugin-msw
│ │
│ ├── @objectstack/metadata (Metadata I/O)
│ │ ↑
│ │ ├── @objectstack/runtime (Uses for manifest loading)
│ │ ├── @objectstack/cli (Uses for code generation)
│ │ └── @objectstack/objectql (Uses for registry persistence)
│ │
│ ├── @objectstack/runtime (Plugins & HTTP)
│ │ ↑
│ │ └── (Used by server plugins)
│ │
│ ├── @objectstack/client
│ │ ↑
│ │ └── @objectstack/client-react
│ │
│ └── @objectstack/driver-memory
│
├── @objectstack/cli (Standalone tool)
-
@objectstack/spechas ZERO dependencies (except Zod)- Pure protocol definitions
- Can be used standalone for type-checking
-
@objectstack/coredepends only on@objectstack/spec- Minimal kernel
- No knowledge of ObjectQL or UI
-
@objectstack/objectqldepends oncoreandspec- Pluggable into kernel
- Independent of UI layer
-
Plugins depend on
coreand optionallyobjectql- Never depend on other plugins directly
- Use service registry for runtime coupling
-
No circular dependencies
- Enforced through topological sort
- Build-time validation
Decision: Use microkernel architecture
Rationale:
- Business logic isolated in plugins
- Core remains stable
- Easy to swap implementations
- Better testability
Tradeoffs:
- More complex setup
- Plugin discovery overhead
- Learning curve for plugin API
Decision: All schemas defined with Zod
Rationale:
- Runtime validation
- TypeScript inference
- JSON Schema generation
- Single source of truth
Implementation:
// Define once with Zod
export const FieldSchema = z.object({ ... });
// TypeScript types inferred
export type Field = z.infer<typeof FieldSchema>;
// JSON Schema generated at build timeDecision: Use reverse domain notation for plugins
Rationale:
- Global uniqueness
- Industry standard (OSGi, Eclipse, Android)
- Clear ownership
- Hierarchical organization
Examples:
com.objectstack.engine.objectqlcom.objectstack.driver.postgrescom.acme.crm.customer-management
Decision: Use DI container for service discovery
Rationale:
- Loose coupling
- Runtime flexibility
- Easy mocking for tests
- No hard dependencies
Alternative Considered: Direct imports Rejected Because: Creates tight coupling
Decision: Use hook/event system for inter-plugin communication
Rationale:
- Decoupled communication
- Multiple subscribers
- Async handling
- Extensibility
Standard Events:
// Kernel lifecycle
'kernel:init'
'kernel:ready'
'kernel:shutdown'
// Data lifecycle
'data:record:beforeCreate'
'data:record:afterCreate'
'data:record:beforeUpdate'
'data:record:afterUpdate'Decision: Separate QL, OS, UI into distinct layers
Rationale:
- Separation of concerns
- Technology independence
- Parallel development
- Incremental migration
See Also: content/docs/introduction/architecture.mdx
Decision: Use pnpm workspaces
Rationale:
- Shared dependencies
- Atomic commits
- Easier refactoring
- Consistent versioning
Build Order:
@objectstack/spec@objectstack/types,@objectstack/core@objectstack/objectql,@objectstack/runtime- Plugins and clients
Decision: Not all packages are plugins
Rationale:
@objectstack/spec- Pure data, no runtime@objectstack/cli- Standalone tool@objectstack/core- Kernel, not a plugin
Plugin Packages: Implement Plugin interface and can be registered with kernel
Format: @objectstack/{category}-{name}
Style: kebab-case
Examples:
@objectstack/driver-memory@objectstack/plugin-hono-server@objectstack/client-react
Format: {domain}.{category}.{name}
Style: kebab-case
Examples:
com.objectstack.engine.objectqlcom.objectstack.driver.postgrescom.acme.crm.customer-management
Configuration Keys: camelCase
{ maxLength: 120, defaultValue: 'hello' }Data Values: snake_case
{ name: 'first_name', object: 'project_task' }Important architectural decisions are documented as ADRs in docs/adr/:
- ADR-0001: Metadata Service Architecture - Explains why both ObjectQL and MetadataPlugin can provide metadata service and how they work together
- Metadata Flow Documentation - Detailed explanation of how metadata flows from definition to runtime, including configuration examples and troubleshooting
-
Plugin Versioning & Compatibility
- SemVer range matching
- Breaking change detection
- Deprecation warnings
-
Plugin Registry & Hub
- Centralized plugin discovery
- Quality scoring
- Security scanning
-
Hot Reload
- Runtime plugin reload
- State preservation
- Development mode
-
Multi-Tenancy
- Kernel per tenant
- Resource isolation
- Shared plugin instances
-
Enhanced Logging
- Structured logging
- Distributed tracing
- Performance metrics
- Quick Reference Guide - Fast lookup for common tasks
- Package Dependency Graph - Complete dependency visualization
- Development Roadmap - Next-phase optimization & improvement plan
- Studio Roadmap - Studio IDE development plan
- MicroKernel Architecture Guide
- Plugin Ecosystem Architecture
- Writing Plugins
- Three-Layer Stack
- Design Principles
Last Updated: February 2026
Maintainers: ObjectStack Core Team
Status: Living Document