ObjectStackObjectStack Protocol

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 TypeBusiness MeaningExample
lookupRelationshipaccount_id references account
formulaCalculated valuetotal = quantity * price
summaryAggregate childrentotal_opportunities counts child records
currencyMoney with exchange rates{amount: 1000, currency: 'USD'}
addressGeocoding & 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 attributes

Result: 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 --migrate

What happens:

  1. ObjectQL analyzes schema diff
  2. Generates database-specific migration
  3. Applies changes online (no downtime)
  4. Backfills data in background
  5. 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 support

Runtime:

  • 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 jobs

Performance 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:

  1. Schema Definition - Learn to define objects and fields
  2. 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-*

Next Steps

On this page