v5.0 (2026):
project→environment. The runtime concept formerly called Project (per-tenant business workspace; Org/Project/Branch hierarchy) is now called Environment throughout the codebase: CLI flags (--environment/-e), HTTP paths (/api/v1/environments/:environmentId/...), headers (X-Environment-Id), env vars (OS_ENVIRONMENT_ID), exported symbols (createSystemEnvironmentPlugin,SYSTEM_ENVIRONMENT_ID), DB columns (environment_id), and JSON schemas (EnvironmentArtifact) all use the new term with no aliases or deprecation shims. The word "project" elsewhere in this document still refers to the npm/monorepo sense (i.e., the framework as a software project). See.changeset/v5-project-to-environment-rename.mdfor the full breaking-change list and ADR-0006 for the rationale.
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, Kernel/System, 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/ # System 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
Drivers (IDataDriver implementations) live in packages/drivers/*; every other
official plugin lives in packages/plugins/*.
Location: packages/drivers/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/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: Kernel (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 |
| Kernel | 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/kernel/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/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/concepts/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/.
Metadata service architecture. This section used to link
docs/adr/0001-metadata-service-architecture.md, deleted on 2026-02-11 in 9da8e3e72
(together with ADR-0002 and the ADR README.md). The link is deliberately not repointed
at another record, because the decision it carried — both ObjectQL and MetadataPlugin can
provide the metadata service, MetadataPlugin taking precedence and ObjectQL acting as
fallback — is no longer what the code does. Today:
- MetadataPlugin is the sole provider of the
metadataservice (packages/metadata/src/plugin.ts).ObjectQLPluginregistersobjectql,data,manifestandlifecycle— nevermetadata. - ObjectQL is a consumer. In
start()it reads themetadataservice, syncs definitions into its own registry and subscribes to metadata events, and degrades to that internal registry when no such service is present (packages/objectql/src/plugin.ts). - The shared-interface principle survived, as a spec contract:
IMetadataServiceinpackages/spec/src/contracts/metadata-service.ts, with the slot declared asCoreServiceName 'metadata'inpackages/spec/src/system/core-services.zod.ts. - Repository, change-log and subscription mechanics are recorded in ADR-0008: Metadata Repository, Change Log & Subscription.
The single-provider decision itself has no ADR record today — ADR-0001 was deleted and nothing replaced it. Re-homing it is a maintainer call; until then the contract and the two plugin files above are the live source of truth.
docs/METADATA_FLOW.md no longer exists in this repository and no replacement was ever
written; the closest live description of how metadata flows from definition to runtime is
the "Metadata service architecture" walkthrough immediately above.
-
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
- Development Roadmap - Next-phase optimization & improvement plan
- Three-Layer Stack - How the Data, System, and UI protocols work together as one cohesive system
- Design Principles
Six more links used to live in this list — a Quick Reference Guide, a Package Dependency
Graph, a Studio Roadmap, and three content/docs/developers/* guides (MicroKernel
Architecture, Plugin Ecosystem, Writing Plugins). None of those files exist anywhere in
this repository today (Studio itself now lives in the separate objectui repository, so
no relative link from here can reach a Studio roadmap), and no replacement was ever
written, so the entries are removed rather than repointed at a guess.
Last Updated: February 2026
Maintainers: ObjectStack Core Team
Status: Living Document