| title | ObjectQL: The Data Protocol |
|---|---|
| description | The database-agnostic data abstraction layer - Define once, run anywhere |
import { Database, Zap, Shield, GitBranch, Target, CheckCircle } from 'lucide-react';
ObjectQL (Object Query Language) is ObjectStack's database-agnostic data abstraction layer. It provides a single, declarative way to define business data models, validation rules, and queries that work consistently across SQL, NoSQL, Graph, and Time-series databases.
Traditional software development chains you to specific database technologies:
- PostgreSQL → MongoDB Migration: Rewrite thousands of SQL queries
- Schema Evolution: Coordinate complex migration scripts across environments
- Multi-Database Systems: Maintain separate codebases for each database
- Business Logic Scatter: Validation rules duplicated in frontend, backend, and database triggers
- Vendor Lock-in: Switching databases requires months of rewrites
┌─────────────────────────────────────────────────────────────┐
│ APPLICATION LAYER │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ REST API │ │ GraphQL │ │ UI Forms │ │
│ └──────┬──────┘ └──────┬──────┘ └──────┬──────┘ │
└─────────┼─────────────────┼─────────────────┼───────────────┘
│ │ │
└─────────────────┼─────────────────┘
│
┌─────────────────▼─────────────────┐
│ ObjectQL Runtime │
│ ┌──────────────────────────────┐ │
│ │ Query Planner & Optimizer │ │
│ └──────────┬───────────────────┘ │
│ │ │
│ ┌──────────▼───────────────────┐ │
│ │ Driver Layer (Adapters) │ │
│ └──┬────────┬────────┬─────────┘ │
└─────┼────────┼────────┼────────────┘
│ │ │
┌────────▼──┐ ┌──▼─────┐ │ ┌──────────┐
│ PostgreSQL│ │MongoDB │ │ │ Redis │
└───────────┘ └────────┘ │ └──────────┘
│
┌──────▼───┐
│ Excel │
└──────────┘
Objects are your business entities—not just database tables. They represent real-world concepts like Customer, Order, Project.
# customer.object.yml
name: customer
label: Customer
icon: standard:account
enable:
audit: true # Track field history
full_text_search: true # Global search
api: true # REST/GraphQL endpoints
fields:
company_name:
type: text
label: Company Name
required: true
industry:
type: select
options:
- { value: tech, label: Technology }
- { value: finance, label: Finance }What you get automatically:
- Database table/collection created
- REST API endpoints:
GET/POST/PUT/DELETE /api/customer - Admin UI with list view and form
- Full-text search index
- Field history tracking
- TypeScript types
ObjectQL provides 20+ field types that encode business semantics:
| Field Type | Business Meaning | Example |
|---|---|---|
lookup |
Relationship | account_id references account |
formula |
Calculated value | total = quantity * price |
summary |
Aggregate children | total_opportunities counts child records |
currency |
Money with exchange rates | {amount: 1000, currency: 'USD'} |
address |
Geocoding & distance | {street, city, lat, lng} |
See Types Reference for complete list.
Queries are expressed as Abstract Syntax Trees (AST), not SQL strings:
// TypeScript Query
const query: Query = {
object: 'customer',
filters: [
['industry', '=', 'tech'],
['annual_revenue', '>', 1000000]
],
fields: ['company_name', 'industry', 'owner.name'],
sort: [{ field: 'created_at', order: 'desc' }],
limit: 10
};Runtime compilation to different databases:
-- PostgreSQL (with JOIN)
SELECT c.company_name, c.industry, u.name AS "owner.name"
FROM customer c
LEFT JOIN user u ON c.owner_id = u.id
WHERE c.industry = 'tech' AND c.annual_revenue > 1000000
ORDER BY c.created_at DESC
LIMIT 10;// MongoDB
db.customer.aggregate([
{
$match: {
industry: 'tech',
annual_revenue: { $gt: 1000000 }
}
},
{
$lookup: {
from: 'user',
localField: 'owner_id',
foreignField: '_id',
as: 'owner'
}
},
{ $sort: { created_at: -1 } },
{ $limit: 10 }
]);Validation rules are declared alongside the schema:
validation_rules:
- name: end_after_start
condition: "end_date < start_date"
message: "End date must be after start date"
- name: enterprise_requires_contract
condition: "account_type = 'Enterprise' AND contract_value = null"
message: "Enterprise accounts require contract value"Validation executes:
- Before database write (server-side)
- In UI forms (compiled to JavaScript)
- In API requests (REST/GraphQL)
- In bulk imports (CSV, Excel)
Scenario: E-commerce platform with:
- PostgreSQL: Transactional data (orders, payments)
- MongoDB: Product catalog (flexible schemas)
- Redis: Session cache
- Elasticsearch: Product search
ObjectQL Solution:
# order.object.yml
name: order
datasource: postgres_main
fields:
customer_id:
type: lookup
reference_to: customer # Also in PostgreSQL
# product.object.yml
name: product
datasource: mongodb_catalog
fields:
attributes:
type: json # Flexible product attributesResult: One query API works across all databases:
// Same code, different databases
const orders = await ObjectQL.query({ object: 'order' });
const products = await ObjectQL.query({ object: 'product' });Challenge: Add a new field to customer object with 10M records.
Traditional Approach:
-- Manual migration, downtime required
ALTER TABLE customer ADD COLUMN credit_score INTEGER;
-- Backfill historical data
UPDATE customer SET credit_score = 0 WHERE credit_score IS NULL;ObjectQL Approach:
# Edit customer.object.yml
fields:
credit_score:
type: number
default_value: 0
migration:
backfill: true # Automatically backfill existing records# Deploy (zero downtime)
objectstack deploy --migrateWhat happens:
- ObjectQL analyzes schema diff
- Generates database-specific migration
- Applies changes online (no downtime)
- Backfills data in background
- Updates API/UI automatically
Challenge: Field sales app needs to work offline.
ObjectQL Solution:
# Same object definition
name: opportunity
enable:
offline_sync: true # Enable offline supportRuntime:
- Server: PostgreSQL (production database)
- Mobile: SQLite (on-device database)
- Sync: Conflict resolution built into protocol
Code is identical:
// Works online or offline
const opportunities = await ObjectQL.query({
object: 'opportunity',
filters: [['owner_id', '=', currentUser.id]]
});ObjectQL is the foundation for other protocols:
ObjectQL (Data Layer)
↓
├→ ObjectUI (UI Protocol)
│ └→ Auto-generate forms, tables, dashboards
│
├→ Permission Protocol
│ └→ Row-level security, field-level access
│
├→ API Protocol
│ └→ REST/GraphQL endpoints
│
└→ Automation Protocol
└→ Triggers, workflows, scheduled jobs
ObjectQL analyzes queries and applies database-specific optimizations:
- Index Selection: Chooses best index based on filters
- Join Strategy: Hash join vs nested loop based on data size
- Projection Pushdown: Only fetches requested fields
- Filter Pushdown: Applies filters at database level
Example:
// This query
const query = {
object: 'customer',
filters: [['industry', '=', 'tech']],
fields: ['company_name']
};
// Optimizes to (PostgreSQL)
SELECT company_name FROM customer WHERE industry = 'tech';
// NOT: SELECT * FROM customer WHERE industry = 'tech';- Schema Cache: Object definitions cached in memory
- Query Plan Cache: Compiled queries reused
- Result Cache: Redis/Memcached for frequently accessed data
ObjectQL enforces security at the data layer, before queries execute:
// User requests data
const query = { object: 'account' };
// ObjectQL applies permissions
const securedQuery = {
object: 'account',
filters: [
...query.filters,
['owner_id', '=', currentUser.id] // Row-level security
],
fields: query.fields.filter(f =>
currentUser.hasFieldAccess('account', f) // Field-level security
)
};See Security Protocol for details.
Start Here:
- Schema Definition - Learn to define objects and fields
- Type System - Understand field types and relationships
Intermediate: 3. Query Syntax - Master the query language 4. Security - Implement access control
Advanced: 5. Driver Protocol - Build custom database drivers 6. Analytics Protocol - OLAP queries
- Zod Schemas:
packages/spec/src/data/*.zod.ts - TypeScript Types:
packages/spec/src/data/*.ts - Driver Implementations:
packages/driver-*