Skip to content

Latest commit

 

History

History
1260 lines (1043 loc) · 22.5 KB

File metadata and controls

1260 lines (1043 loc) · 22.5 KB

ObjectQL API Reference

Version: 1.0.0

This document provides a comprehensive reference for all ObjectQL API interfaces. Given the extensive metadata capabilities of ObjectQL, we provide multiple API styles to suit different use cases.

Table of Contents

  1. API Overview
  2. JSON-RPC Style API
  3. REST-Style API
  4. Metadata API
  5. WebSocket API
  6. Authentication & Authorization
  7. Error Handling
  8. Rate Limiting
  9. Examples

API Overview

ObjectQL provides a unified query protocol that can be exposed through multiple API styles:

API Style Use Case Endpoint Pattern
JSON-RPC Universal client, AI agents, microservices POST /api/objectql
REST Traditional web apps, mobile apps GET/POST/PUT/DELETE /api/data/:object
Metadata Admin interfaces, schema discovery, runtime config GET /api/metadata/*
GraphQL Modern frontends with complex data requirements POST /api/graphql (Planned)
WebSocket Real-time apps, live updates ws://host/api/realtime (Planned)

Design Principles

  1. Protocol-First: All APIs accept/return structured JSON, never raw SQL
  2. Type-Safe: Full TypeScript definitions for all requests/responses
  3. AI-Friendly: Queries include optional ai_context for explainability
  4. Secure: Built-in validation, permission checks, SQL injection prevention
  5. Universal: Same query works across MongoDB, PostgreSQL, SQLite

Unified ID Field

ObjectQL uses a unified id field as the primary key across all database drivers:

  • Consistent Naming: Always use id in API requests and responses
  • Database Agnostic: Works seamlessly with both MongoDB (which uses _id internally) and SQL databases
  • Automatic Mapping: MongoDB driver transparently converts between id (API) and _id (database)

Example:

// Create with custom ID - works with any driver
{
  "op": "create",
  "object": "users",
  "args": {
    "id": "user-123",
    "name": "Alice"
  }
}

// Query by ID - works with any driver
{
  "op": "find",
  "object": "users",
  "args": {
    "filters": [["id", "=", "user-123"]]
  }
}

// Response always uses 'id'
{
  "data": [
    {
      "id": "user-123",
      "name": "Alice"
    }
  ]
}

See the Driver Documentation for more details.


JSON-RPC Style API

The primary ObjectQL API is a JSON-RPC style protocol where all operations are sent to a single endpoint.

Base Endpoint

POST /api/objectql
Content-Type: application/json

Request Format

interface ObjectQLRequest {
  // Authentication context (optional, can also come from headers)
  user?: {
    id: string;
    roles: string[];
    [key: string]: any;
  };
  
  // The operation to perform
  op: 'find' | 'findOne' | 'create' | 'update' | 'delete' | 'count' | 'action';
  
  // The target object/table
  object: string;
  
  // Operation-specific arguments
  args: any;
}

Response Format

interface ObjectQLResponse {
  data?: any;
  error?: {
    code: string;
    message: string;
  }
}

Operations

1. find - Query Records

Retrieve multiple records with filtering, sorting, pagination, and joins.

Request:

{
  "op": "find",
  "object": "orders",
  "args": {
    "fields": ["order_no", "amount", "status", "created_at"],
    "filters": [
      ["status", "=", "paid"],
      "and",
      ["amount", ">", 1000]
    ],
    "sort": [["created_at", "desc"]],
    "top": 20,
    "skip": 0,
    "expand": {
      "customer": {
        "fields": ["name", "email"]
      }
    }
  }
}

Response:

{
  "data": [
    {
      "order_no": "ORD-001",
      "amount": 1500,
      "status": "paid",
      "created_at": "2024-01-15T10:30:00Z",
      "customer": {
        "name": "Acme Corp",
        "email": "contact@acme.com"
      }
    }
  ]
}

2. findOne - Get Single Record

Retrieve a single record by ID or query.

Request (by ID):

{
  "op": "findOne",
  "object": "users",
  "args": "user_123"
}

Request (by query):

{
  "op": "findOne",
  "object": "users",
  "args": {
    "filters": [["email", "=", "alice@example.com"]]
  }
}

Response:

{
  "data": {
    "id": "user_123",
    "name": "Alice",
    "email": "alice@example.com"
  }
}

3. create - Create Record

Insert a new record.

Request:

{
  "op": "create",
  "object": "tasks",
  "args": {
    "name": "Review PR",
    "priority": "high",
    "assignee_id": "user_123",
    "due_date": "2024-01-20"
  }
}

Response:

{
  "data": {
    "id": "task_456",
    "name": "Review PR",
    "priority": "high",
    "assignee_id": "user_123",
    "due_date": "2024-01-20",
    "created_at": "2024-01-15T10:30:00Z"
  }
}

4. update - Update Record

Modify an existing record.

Request:

{
  "op": "update",
  "object": "tasks",
  "args": {
    "id": "task_456",
    "data": {
      "status": "completed",
      "completed_at": "2024-01-16T14:00:00Z"
    }
  }
}

Response:

{
  "data": {
    "id": "task_456",
    "status": "completed",
    "completed_at": "2024-01-16T14:00:00Z"
  }
}

5. delete - Delete Record

Remove a record by ID.

Request:

{
  "op": "delete",
  "object": "tasks",
  "args": {
    "id": "task_456"
  }
}

Response:

{
  "data": {
    "id": "task_456",
    "deleted": true
  }
}

6. count - Count Records

Get the count of records matching a filter.

Request:

{
  "op": "count",
  "object": "orders",
  "args": {
    "filters": [
      ["status", "=", "pending"]
    ]
  }
}

Response:

{
  "data": 42
}

7. action - Execute Custom Action

Execute a custom server-side action (RPC-style operation).

Request:

{
  "op": "action",
  "object": "orders",
  "args": {
    "action": "approve",
    "id": "order_789",
    "input": {
      "approved_by": "manager_123",
      "notes": "Approved for expedited shipping"
    }
  }
}

Response:

{
  "data": {
    "success": true,
    "message": "Order approved successfully",
    "order": {
      "id": "order_789",
      "status": "approved",
      "approved_at": "2024-01-15T10:30:00Z"
    }
  }
}

Advanced Query Features

AI Context (Optional)

Add semantic information to queries for better logging, debugging, and AI processing:

{
  "op": "find",
  "object": "projects",
  "ai_context": {
    "intent": "Find at-risk projects requiring immediate attention",
    "natural_language": "Show active projects that are overdue or over budget",
    "use_case": "Project manager dashboard"
  },
  "args": {
    "filters": [
      ["status", "=", "active"],
      "and",
      [
        ["end_date", "<", "$today"],
        "or",
        ["actual_cost", ">", "budget"]
      ]
    ]
  }
}

Aggregation Queries

Perform GROUP BY operations:

{
  "op": "find",
  "object": "orders",
  "args": {
    "groupBy": ["category"],
    "aggregate": [
      {
        "func": "sum",
        "field": "amount",
        "alias": "total_sales"
      },
      {
        "func": "count",
        "field": "id",
        "alias": "order_count"
      }
    ],
    "filters": [["status", "=", "paid"]],
    "sort": [["total_sales", "desc"]]
  }
}

REST-Style API

For traditional REST clients, ObjectQL can expose a REST-style interface.

Endpoints

Method Path Description
GET /api/data/:object List records
GET /api/data/:object/:id Get single record
POST /api/data/:object Create record
PUT /api/data/:object/:id Update record
DELETE /api/data/:object/:id Delete record

List Records

GET /api/data/users?filter={"status":"active"}&sort=created_at&limit=20

Response:

{
  "data": [...],
  "meta": {
    "total": 150,
    "page": 1,
    "per_page": 20
  }
}

Get Single Record

GET /api/data/users/user_123

Response:

{
  "data": {
    "id": "user_123",
    "name": "Alice",
    "email": "alice@example.com"
  }
}

Create Record

POST /api/data/users
Content-Type: application/json

{
  "name": "Bob",
  "email": "bob@example.com",
  "role": "admin"
}

Response:

{
  "data": {
    "id": "user_456",
    "name": "Bob",
    "email": "bob@example.com",
    "role": "admin",
    "created_at": "2024-01-15T10:30:00Z"
  }
}

Update Record

PUT /api/data/users/user_456
Content-Type: application/json

{
  "role": "user"
}

Response:

{
  "data": {
    "id": "user_456",
    "role": "user",
    "updated_at": "2024-01-15T11:00:00Z"
  }
}

Delete Record

DELETE /api/data/users/user_456

Response:

{
  "data": {
    "id": "user_456",
    "deleted": true
  }
}

Metadata API

The Metadata API provides runtime access to schema information, object definitions, and configuration.

Base Endpoint

/api/metadata

Endpoints

1. List All Objects

Get a list of all registered objects/tables.

GET /api/metadata/objects

Response:

{
  "objects": [
    {
      "name": "users",
      "label": "Users",
      "icon": "user",
      "description": "System users and authentication",
      "fields": {...}
    },
    {
      "name": "orders",
      "label": "Orders",
      "icon": "shopping-cart",
      "description": "Customer orders",
      "fields": {...}
    }
  ]
}

2. Get Object Schema

Get detailed schema for a specific object.

GET /api/metadata/objects/users

Response:

{
  "name": "users",
  "label": "Users",
  "icon": "user",
  "description": "System users and authentication",
  "fields": [
    {
      "name": "email",
      "type": "email",
      "label": "Email Address",
      "required": true,
      "unique": true
    },
    {
      "name": "role",
      "type": "select",
      "label": "Role",
      "options": ["admin", "user", "guest"],
      "defaultValue": "user"
    }
  ],
  "actions": [
    {
      "name": "reset_password",
      "type": "record",
      "label": "Reset Password"
    }
  ],
  "hooks": [
    {
      "event": "afterCreate",
      "description": "Send welcome email"
    }
  ]
}

3. Update Metadata (Admin)

Dynamically update object configuration at runtime.

PUT /api/metadata/object/users
Content-Type: application/json
Authorization: Bearer <admin_token>

{
  "label": "System Users",
  "description": "Updated description"
}

Response:

{
  "success": true
}

4. Get Field Metadata

Get detailed information about a specific field.

GET /api/metadata/objects/users/fields/email

Response:

{
  "name": "email",
  "type": "email",
  "label": "Email Address",
  "required": true,
  "unique": true,
  "validations": [
    {
      "type": "email_format",
      "message": "Must be a valid email address"
    }
  ]
}

5. List Actions

Get all custom actions for an object.

GET /api/metadata/objects/orders/actions

Response:

{
  "actions": [
    {
      "name": "approve",
      "type": "record",
      "label": "Approve Order",
      "params": {
        "notes": {
          "type": "textarea",
          "label": "Approval Notes"
        }
      }
    },
    {
      "name": "bulk_import",
      "type": "global",
      "label": "Bulk Import Orders"
    }
  ]
}

WebSocket API

(Planned Feature)

For real-time updates and live data synchronization.

Connection

const ws = new WebSocket('ws://localhost:3000/api/realtime');

ws.onopen = () => {
  // Authenticate
  ws.send(JSON.stringify({
    type: 'auth',
    token: 'your_jwt_token'
  }));
  
  // Subscribe to changes
  ws.send(JSON.stringify({
    type: 'subscribe',
    object: 'orders',
    filters: [["status", "=", "pending"]]
  }));
};

ws.onmessage = (event) => {
  const data = JSON.parse(event.data);
  
  if (data.type === 'change') {
    console.log('Record changed:', data.record);
  }
};

Authentication & Authorization

Authentication Methods

ObjectQL supports multiple authentication strategies:

1. JWT Tokens (Recommended)

POST /api/objectql
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
Content-Type: application/json

2. API Keys

POST /api/objectql
X-API-Key: your_api_key_here
Content-Type: application/json

3. Session Cookies

POST /api/objectql
Cookie: session_id=abc123...
Content-Type: application/json

4. User Context in Request (Development Only)

For testing and development, you can pass user context directly in the request:

{
  "user": {
    "id": "user_123",
    "roles": ["admin"]
  },
  "op": "find",
  "object": "users",
  "args": {}
}

⚠️ Warning: In production, always authenticate via headers, not request body.

Permission System

ObjectQL enforces permissions at multiple levels:

  1. Object-Level: Can the user access this object at all?
  2. Operation-Level: Can they perform this operation (read/create/update/delete)?
  3. Field-Level: Which fields can they see/edit?
  4. Record-Level: Which specific records can they access?

Permission Check Flow:

Request → Authentication → Object Permission → Field Permission → Record Permission → Execute

Example Permission Config:

# user.object.yml
permissions:
  - profile: admin
    allow_read: true
    allow_create: true
    allow_edit: true
    allow_delete: true
    
  - profile: user
    allow_read: true
    allow_create: false
    allow_edit: true
    allow_delete: false
    record_filters:
      - ["owner", "=", "$current_user"]

Error Handling

Error Response Format

All errors follow a consistent format:

{
  "error": {
    "code": "ERROR_CODE",
    "message": "Human-readable error message",
    "details": {
      "field": "email",
      "reason": "Email already exists"
    }
  }
}

Error Codes

Code HTTP Status Description
INVALID_REQUEST 400 Malformed request body
VALIDATION_ERROR 400 Data validation failed
UNAUTHORIZED 401 Authentication required
FORBIDDEN 403 Insufficient permissions
NOT_FOUND 404 Object or record not found
CONFLICT 409 Unique constraint violation
INTERNAL_ERROR 500 Server error
DATABASE_ERROR 500 Database operation failed

Example Error Responses

Validation Error:

{
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "Validation failed",
    "details": {
      "fields": {
        "email": "Invalid email format",
        "age": "Must be greater than 0"
      }
    }
  }
}

Permission Error:

{
  "error": {
    "code": "FORBIDDEN",
    "message": "You do not have permission to access this resource",
    "details": {
      "required_permission": "users:delete",
      "user_roles": ["user"]
    }
  }
}

Not Found:

{
  "error": {
    "code": "NOT_FOUND",
    "message": "Object 'xyz' not found"
  }
}

Rate Limiting

ObjectQL supports configurable rate limiting to prevent abuse.

Default Limits

Tier Requests/Minute Requests/Hour
Anonymous 20 100
Authenticated 100 1000
Premium 500 10000

Rate Limit Headers

All responses include rate limit information:

X-RateLimit-Limit: 100
X-RateLimit-Remaining: 95
X-RateLimit-Reset: 1642258800

Rate Limit Exceeded

When rate limit is exceeded:

{
  "error": {
    "code": "RATE_LIMIT_EXCEEDED",
    "message": "Too many requests. Please try again later.",
    "details": {
      "retry_after": 60
    }
  }
}

HTTP Status: 429 Too Many Requests


Examples

Example 1: User Registration Flow

// 1. Create user
const response = await fetch('/api/objectql', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({
    op: 'create',
    object: 'users',
    args: {
      email: 'alice@example.com',
      name: 'Alice',
      password_hash: 'hashed_password'
    }
  })
});

const { data: user } = await response.json();
// { id: 'user_123', email: 'alice@example.com', ... }

// 2. Send verification email (triggered by hook)
// 3. User verifies email via action
await fetch('/api/objectql', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({
    op: 'action',
    object: 'users',
    args: {
      action: 'verify_email',
      id: user.id,
      input: {
        token: 'verification_token_xyz'
      }
    }
  })
});

Example 2: Dashboard Analytics

// Get sales metrics for dashboard
const response = await fetch('/api/objectql', {
  method: 'POST',
  headers: { 
    'Content-Type': 'application/json',
    'Authorization': 'Bearer ' + jwt_token
  },
  body: JSON.stringify({
    op: 'find',
    object: 'orders',
    ai_context: {
      intent: "Calculate monthly sales by category",
      use_case: "Executive dashboard"
    },
    args: {
      groupBy: ['category', 'month'],
      aggregate: [
        { func: 'sum', field: 'amount', alias: 'revenue' },
        { func: 'count', field: 'id', alias: 'order_count' },
        { func: 'avg', field: 'amount', alias: 'avg_order_value' }
      ],
      filters: [
        ['status', '=', 'paid'],
        'and',
        ['created_at', '>=', '2024-01-01']
      ],
      sort: [['month', 'asc'], ['revenue', 'desc']]
    }
  })
});

const { data } = await response.json();
// [
//   { category: 'Electronics', month: '2024-01', revenue: 50000, order_count: 120, avg_order_value: 416.67 },
//   { category: 'Clothing', month: '2024-01', revenue: 30000, order_count: 250, avg_order_value: 120.00 },
//   ...
// ]

Example 3: Complex Search with Relations

// Find customers with high-value recent orders
const response = await fetch('/api/objectql', {
  method: 'POST',
  headers: { 
    'Content-Type': 'application/json',
    'Authorization': 'Bearer ' + jwt_token
  },
  body: JSON.stringify({
    op: 'find',
    object: 'customers',
    args: {
      fields: ['name', 'email', 'vip_level', 'total_spent'],
      filters: [
        ['vip_level', '>=', 'gold'],
        'and',
        ['is_active', '=', true]
      ],
      expand: {
        orders: {
          fields: ['order_no', 'amount', 'status'],
          filters: [
            ['created_at', '>', '2024-01-01'],
            'and',
            ['amount', '>', 1000]
          ],
          sort: [['created_at', 'desc']],
          top: 5
        }
      },
      sort: [['total_spent', 'desc']],
      top: 20
    }
  })
});

Example 4: Bulk Operations

// Create multiple records in one request
const response = await fetch('/api/objectql', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({
    op: 'action',
    object: 'tasks',
    args: {
      action: 'bulk_create',
      input: {
        items: [
          { name: 'Task 1', priority: 'high' },
          { name: 'Task 2', priority: 'medium' },
          { name: 'Task 3', priority: 'low' }
        ]
      }
    }
  })
});

Example 5: Metadata-Driven Form Generation

// 1. Fetch object schema
const schemaResponse = await fetch('/api/metadata/objects/contacts');
const schema = await schemaResponse.json();

// 2. Generate form based on field metadata
const form = generateForm(schema.fields);

// 3. Submit form data
const submitResponse = await fetch('/api/objectql', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({
    op: 'create',
    object: 'contacts',
    args: formData
  })
});

OpenAPI/Swagger Specification

ObjectQL automatically generates OpenAPI 3.0 specifications from your metadata.

Access the Spec

GET /api/objectql/openapi.json

This can be imported into:

  • Swagger UI for interactive API documentation
  • Postman for testing
  • Code Generators for client SDKs

Example Integration

<!-- Swagger UI -->
<!DOCTYPE html>
<html>
<head>
  <link rel="stylesheet" href="https://unpkg.com/swagger-ui-dist/swagger-ui.css" />
</head>
<body>
  <div id="swagger-ui"></div>
  <script src="https://unpkg.com/swagger-ui-dist/swagger-ui-bundle.js"></script>
  <script>
    SwaggerUIBundle({
      url: '/api/objectql/openapi.json',
      dom_id: '#swagger-ui'
    });
  </script>
</body>
</html>

Best Practices

1. Use Specific Field Projections

Don't fetch all fields:

{
  "op": "find",
  "object": "users"
}

Do specify only needed fields:

{
  "op": "find",
  "object": "users",
  "args": {
    "fields": ["id", "name", "email"]
  }
}

2. Use Pagination for Large Datasets

{
  "op": "find",
  "object": "orders",
  "args": {
    "top": 50,
    "skip": 0
  }
}

3. Add Indexes for Filtered Fields

If you frequently filter by a field, add an index:

# order.object.yml
indexes:
  - fields: [status, created_at]

4. Use AI Context for Complex Queries

{
  "op": "find",
  "object": "projects",
  "ai_context": {
    "intent": "Find at-risk projects",
    "natural_language": "Active projects that are overdue or over budget"
  },
  "args": {...}
}

5. Batch Related Requests

Use expand instead of multiple requests:

Don't:

// Multiple requests
const orders = await getOrders();
for (const order of orders) {
  order.customer = await getCustomer(order.customer_id);
}

Do:

{
  "op": "find",
  "object": "orders",
  "args": {
    "expand": {
      "customer": {
        "fields": ["name", "email"]
      }
    }
  }
}

Next Steps


Support


Last Updated: January 2024
API Version: 1.0.0