ObjectQL Overview
The database-agnostic data abstraction layer - Define once, run anywhere
ObjectQL: The Data Protocol
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.
The Core Problem
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
The ObjectQL Solution
Write Once, Run Anywhere
Define objects in .object.yml. ObjectStack compiles to PostgreSQL, MongoDB, Redis, or Excelβno code changes.
Schema as Code
Version control your data model. Automatic migrations. Rollback schema changes like Git commits.
Type Safety
Runtime validation with Zod schemas. TypeScript types auto-generated. Catch errors before production.
Business Semantics
Fields encode intent: 'lookup' means relationship, 'formula' means calculated. Drivers optimize accordingly.
Architecture Overview
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β APPLICATION LAYER β
β βββββββββββββββ βββββββββββββββ βββββββββββββββ β
β β REST API β β GraphQL β β UI Forms β β
β ββββββββ¬βββββββ ββββββββ¬βββββββ ββββββββ¬βββββββ β
βββββββββββΌββββββββββββββββββΌββββββββββββββββββΌββββββββββββββββ
β β β
βββββββββββββββββββΌββββββββββββββββββ
β
βββββββββββββββββββΌββββββββββββββββββ
β ObjectQL Runtime β
β ββββββββββββββββββββββββββββββββ β
β β Query Planner & Optimizer β β
β ββββββββββββ¬ββββββββββββββββββββ β
β β β
β ββββββββββββΌββββββββββββββββββββ β
β β Driver Layer (Adapters) β β
β ββββ¬βββββββββ¬βββββββββ¬ββββββββββ β
βββββββΌβββββββββΌβββββββββΌβββββββββββββ
β β β
ββββββββββΌβββ ββββΌββββββ β ββββββββββββ
β PostgreSQLβ βMongoDB β β β Redis β
βββββββββββββ ββββββββββ β ββββββββββββ
β
ββββββββΌββββ
β Excel β
ββββββββββββKey Concepts
1. Objects: Business Entities
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
2. Fields: Rich Type System
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.
3. Query Language: Database-Agnostic AST
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 }
]);4. Validation: Business Rules as Data
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)
Real-World Use Cases
Multi-Database Architecture
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' });Schema Evolution
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
Offline-First Mobile
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]]
});Integration with Other Protocols
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 jobsPerformance Characteristics
Query Optimization
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';Caching Strategy
- Schema Cache: Object definitions cached in memory
- Query Plan Cache: Compiled queries reused
- Result Cache: Redis/Memcached for frequently accessed data
Security Model
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.
Learning Path
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
Technical References
- Zod Schemas:
packages/spec/src/data/*.zod.ts - TypeScript Types:
packages/spec/src/data/*.ts - Driver Implementations:
packages/driver-*