| title | The Stack - ObjectQL + ObjectUI + ObjectOS |
|---|---|
| description | How the three protocols work together as one cohesive system |
import { Database, Layout, Cpu, ArrowRight, CheckCircle, Workflow } from 'lucide-react';
ObjectStack is built on three foundational protocols that work together as a unified system:
} title="ObjectQL" description="Data Protocol - Structure and queries" /> } title="ObjectOS" description="Control Protocol - Runtime and governance" /> } title="ObjectUI" description="View Protocol - Presentation and interaction" />Traditional applications tightly couple data, business logic, and presentation. This creates Implementation Coupling — changing one layer forces changes across the entire stack.
ObjectStack enforces Separation of Concerns through protocol boundaries:
┌─────────────────────────────────────────┐
│ ObjectUI (View Layer) │
│ Apps, Views, Dashboards, Reports │
│ "How do users interact?" │
└──────────────┬──────────────────────────┘
│ API Protocol
┌──────────────┴──────────────────────────┐
│ ObjectOS (Control Layer) │
│ Auth, Permissions, Workflows, Events │
│ "Who can do what, when?" │
└──────────────┬──────────────────────────┘
│ Query Protocol
┌──────────────┴──────────────────────────┐
│ ObjectQL (Data Layer) │
│ Objects, Fields, Queries, Drivers │
│ "What is the data structure?" │
└─────────────────────────────────────────┘
Role: Define the Structure and Intent of data.
Responsibilities:
- Object schema definitions (what is a "Customer"?)
- Field types and validation rules
- Query language (filtering, sorting, aggregation)
- Database drivers (Postgres, MongoDB, SQLite)
Key Principle: ObjectQL knows nothing about users, permissions, or UI. It only cares about data structure and queries.
// packages/crm/src/objects/customer.object.ts
import { Object, Field } from '@objectstack/spec';
export const Customer = Object({
name: 'customer',
label: 'Customer',
fields: {
name: Field.text({
label: 'Company Name',
required: true,
maxLength: 120,
}),
industry: Field.select({
label: 'Industry',
options: ['technology', 'finance', 'healthcare', 'retail'],
}),
annual_revenue: Field.currency({
label: 'Annual Revenue',
}),
primary_contact: Field.lookup({
label: 'Primary Contact',
object: 'contact',
}),
},
});This definition is pure metadata. It doesn't know:
- Who can see this data
- How to render a form
- When to trigger workflows
That's the job of the other layers.
Role: Manage the Lifecycle and Governance of requests.
Responsibilities:
- Authentication (who is this user?)
- Authorization (can they access this field?)
- Workflows and automations (what happens after save?)
- Event processing (audit logs, notifications)
- Multi-tenancy and data isolation
Key Principle: ObjectOS acts as the Gateway. No layer can directly access the database; all requests must pass through the OS Kernel.
// packages/crm/src/permissions/customer.permission.ts
import { Permission } from '@objectstack/spec';
export const CustomerPermission = Permission({
object: 'customer',
rules: [
{
profile: 'sales_rep',
crud: {
create: true,
read: true,
update: true,
delete: false, // Only managers can delete
},
fieldPermissions: {
annual_revenue: { read: true, edit: false }, // Read-only
},
},
{
profile: 'sales_manager',
crud: {
create: true,
read: true,
update: true,
delete: true,
},
},
],
});// packages/crm/src/workflows/customer.workflow.ts
import { Workflow } from '@objectstack/spec';
export const CustomerWorkflow = Workflow({
object: 'customer',
trigger: 'after_create',
conditions: [
{ field: 'annual_revenue', operator: 'greaterThan', value: 1000000 },
],
actions: [
{
type: 'assign_owner',
params: { owner: 'enterprise_sales_team' },
},
{
type: 'send_email',
params: {
template: 'high_value_customer_alert',
to: 'sales-leadership@company.com',
},
},
],
});ObjectOS orchestrates these rules at runtime, independent of the data structure or UI.
Role: Render the Presentation and handle User Interaction.
Responsibilities:
- App navigation and branding
- List views (grid, kanban, calendar)
- Form layouts (simple, tabbed, wizard)
- Dashboards and reports
- Actions and buttons
Key Principle: ObjectUI is a Rendering Engine, not a hardcoded interface. It asks ObjectQL "What is the schema?" and dynamically generates the UI.
// packages/crm/src/views/customer_list.view.ts
import { ListView } from '@objectstack/spec';
export const CustomerListView = ListView({
object: 'customer',
label: 'All Customers',
type: 'grid',
columns: [
{ field: 'name', width: 200 },
{ field: 'industry', width: 150 },
{ field: 'annual_revenue', width: 150 },
{ field: 'primary_contact', width: 180 },
],
filters: [
{ field: 'industry', operator: 'equals' },
{ field: 'annual_revenue', operator: 'greaterThan' },
],
defaultSort: { field: 'name', direction: 'asc' },
});// packages/crm/src/views/customer_form.view.ts
import { FormView } from '@objectstack/spec';
export const CustomerFormView = FormView({
object: 'customer',
label: 'Customer Details',
type: 'tabbed',
tabs: [
{
label: 'Overview',
sections: [
{
label: 'Company Information',
fields: ['name', 'industry', 'annual_revenue'],
},
{
label: 'Contact',
fields: ['primary_contact'],
},
],
},
{
label: 'Related Records',
sections: [
{
label: 'Opportunities',
component: 'related_list',
object: 'opportunity',
filter: { customer: '$recordId' },
},
],
},
],
});The UI doesn't "know" the field types. It asks ObjectQL for the schema and renders accordingly:
Field.text→ Text inputField.select→ DropdownField.lookup→ Autocomplete lookupField.currency→ Number input with currency formatting
Let's trace a real-world scenario: A sales rep creates a new high-value customer.
User fills out the "Create Customer" form:
- Name: "Acme Corp"
- Industry: "Technology"
- Annual Revenue: $5,000,000
- Primary Contact: "John Doe"
User clicks "Save"
// ObjectUI dispatches an action to ObjectOS
const request = {
action: 'create',
object: 'customer',
data: {
name: 'Acme Corp',
industry: 'technology',
annual_revenue: 5000000,
primary_contact: 'contact_12345',
},
};// Kernel checks: Does this user have permission?
const user = await Auth.getCurrentUser();
const canCreate = await Permission.check({
user,
object: 'customer',
operation: 'create',
});
if (!canCreate) {
throw new Error('Permission denied');
}// Kernel asks ObjectQL: Is this data valid?
const schema = ObjectQL.getSchema('customer');
const validation = schema.validate(request.data);
if (!validation.success) {
throw new ValidationError(validation.errors);
}// ObjectQL compiles the request into a database operation
const driver = ObjectQL.getDriver(); // Postgres, MongoDB, etc.
const result = await driver.insert('customer', {
name: 'Acme Corp',
industry: 'technology',
annual_revenue: 5000000,
primary_contact_id: 'contact_12345',
});// Kernel checks: Are there any workflows for this event?
const workflows = Workflow.getTriggersFor('customer', 'after_create');
for (const workflow of workflows) {
if (workflow.conditionsMet(result)) {
await workflow.execute(result);
}
}
// In this case:
// ✅ Annual revenue > $1M
// → Assign to enterprise sales team
// → Send alert email to leadership// Kernel returns success response
// UI optimistically updates the screen
// UI shows toast notification: "Customer created successfully"
// UI navigates to the new customer detail pageHere's how all three protocols collaborate for a Kanban Board feature:
export const Opportunity = Object({
name: 'opportunity',
fields: {
title: Field.text({ required: true }),
stage: Field.select({
options: ['prospecting', 'qualification', 'proposal', 'closed_won'],
}),
amount: Field.currency(),
customer: Field.lookup({ object: 'customer' }),
},
});export const OpportunityWorkflow = Workflow({
object: 'opportunity',
trigger: 'field_update',
conditions: [
{ field: 'stage', operator: 'equals', value: 'closed_won' },
],
actions: [
{ type: 'create_invoice', params: { object: 'invoice' } },
{ type: 'send_notification', params: { to: 'sales_team' } },
],
});export const OpportunityKanban = ListView({
object: 'opportunity',
type: 'kanban',
groupBy: 'stage',
columns: [
{ field: 'title' },
{ field: 'amount' },
{ field: 'customer' },
],
enableDragDrop: true,
});When a user drags an opportunity card from "Proposal" to "Closed Won":
- ObjectUI captures the drag-drop event
- ObjectOS checks if the user has permission to update the
stagefield - ObjectQL validates that
"closed_won"is a valid option - ObjectQL writes the update to the database
- ObjectOS triggers the workflow (create invoice, send notification)
- ObjectUI updates the kanban board to reflect the new state
All from metadata. Zero hardcoded logic.
Swap implementations without breaking the system:
Same Metadata Definitions
↓
┌─────────┼─────────┐
ObjectQL: │ │
Postgres │ MongoDB
│
ObjectOS: │
Node.js │ Python
│
ObjectUI: │
React │ Flutter
Teams can work independently on each layer:
- Data Team: Define objects in ObjectQL
- Backend Team: Build workflows in ObjectOS
- Frontend Team: Create views in ObjectUI
All communicate through protocol contracts, not code dependencies.
Adopt ObjectStack gradually:
- Phase 1: Use ObjectQL as an ORM replacement
- Phase 2: Add ObjectOS for permissions and workflows
- Phase 3: Build ObjectUI views to replace custom forms
Each layer is independently useful.
Mock any layer for testing:
// Test ObjectOS workflows without a real database
const mockObjectQL = {
getSchema: () => CustomerSchema,
insert: jest.fn(),
};
// Test ObjectUI rendering without a real backend
const mockObjectOS = {
checkPermission: () => true,
executeQuery: () => mockData,
};| Layer | Role | Knows About | Doesn't Know About |
|---|---|---|---|
| ObjectQL | Data structure & queries | Schema, fields, drivers | Users, permissions, UI |
| ObjectOS | Runtime & governance | Auth, workflows, events | Data structure, UI layout |
| ObjectUI | Presentation & interaction | Layout, navigation, actions | Business logic, data storage |
The three protocols are loosely coupled but tightly integrated:
- They communicate through standard contracts (Zod schemas)
- They can be swapped or upgraded independently
- They form a complete system when combined
- Object Model - Deep dive into ObjectQL's universal object model
- ObjectQL Protocol - Full data protocol specification
- ObjectOS Protocol - Full control protocol specification
- ObjectUI Protocol - Full view protocol specification