β

ObjectQL v4.0 is currently in Beta.

ObjectStack LogoObjectQL
Business Logic

Business Logic

Add dynamic behavior with Formulas, Hooks, and Actions

ObjectQL provides three powerful mechanisms to implement business logic in your applications. All three features are fully implemented and production-ready.

📊 Quick Comparison

FeatureUse CaseWhen It RunsImplementation Status
FormulasComputed fieldsOn read/write✅ 100% Complete
HooksEvent-driven logicBefore/after CRUD operations✅ 100% Complete
ActionsCustom RPC operationsOn explicit invocation✅ 100% Complete

✅ Formulas (Computed Fields)

Status: Fully Implemented ✅

Formulas allow you to create computed fields that automatically calculate their values based on other fields or expressions.

Key Features

  • JavaScript expression evaluation with sandbox
  • Field references and lookup chains
  • System variables ($today, $now, $current_user)
  • Built-in Math, String, Date functions
  • Type coercion and validation
  • Execution timeout protection

Example

# order.object.yml
fields:
  price:
    type: currency
  quantity:
    type: number
  total:
    type: formula
    expression: "price * quantity"
    data_type: currency

Learn More →


✅ Hooks (Event-Driven Logic)

Status: Fully Implemented ✅

Hooks (also called "triggers") allow you to intercept database operations to inject custom logic. They are transaction-aware and run automatically.

Available Hooks

  • beforeCreate / afterCreate
  • beforeUpdate / afterUpdate
  • beforeDelete / afterDelete
  • beforeFind / afterFind
  • beforeCount / afterCount

Common Use Cases

  • Validation - Additional business rule checks
  • Default Values - Set computed defaults
  • Audit Logging - Track all changes
  • Security - Row-level access control
  • Side Effects - Send notifications, update related records
  • Data Transformation - Sanitize or format data

Example

// project.hook.ts
import { ObjectHookDefinition } from '@objectql/types';

const hooks: ObjectHookDefinition = {
  beforeCreate: async (ctx) => {
    // Set default owner
    if (!ctx.data.owner) {
      ctx.data.owner = ctx.user.userId;
    }
  },

  afterUpdate: async (ctx) => {
    // Log changes
    console.log(`Project ${ctx.id} updated by ${ctx.user.userId}`);
  }
};

export default hooks;

Learn More →


✅ Actions (Custom RPC Operations)

Status: Fully Implemented ✅

Actions allow you to define custom backend functions that go beyond simple CRUD. They are integrated into the metadata, making them discoverable and type-safe.

Action Types

  • Record Actions - Operate on a specific record (e.g., "Approve Invoice")
  • Global Actions - Operate on the collection (e.g., "Import CSV")

Common Use Cases

  • Workflow Operations - Approve, reject, submit
  • Batch Operations - Bulk update, mass delete
  • External Integration - Sync with third-party systems
  • Complex Business Logic - Multi-step processes
  • Report Generation - Generate PDFs, exports

Example

# invoice.object.yml
actions:
  mark_paid:
    type: record
    label: Mark as Paid
    icon: standard:money
    params:
      payment_method:
        type: select
        options: [cash, card, transfer]
// invoice.action.ts
import { ActionDefinition } from '@objectql/types';

export const mark_paid: ActionDefinition = {
  handler: async ({ id, input, api, user }) => {
    await api.update('invoice', id, {
      status: 'Paid',
      payment_method: input.payment_method,
      paid_by: user.id,
      paid_at: new Date()
    });

    return { 
      success: true, 
      message: 'Invoice marked as paid' 
    };
  }
};

Learn More →


🔄 Combining Logic Types

You can combine formulas, hooks, and actions to create sophisticated business logic:

Example: Order Management System

# order.object.yml
fields:
  subtotal:
    type: currency
  tax_rate:
    type: percent
    defaultValue: 0.08
  
  # Formula: Calculate tax
  tax:
    type: formula
    expression: "subtotal * tax_rate"
    data_type: currency
  
  # Formula: Calculate total
  total:
    type: formula
    expression: "subtotal + tax"
    data_type: currency
  
  status:
    type: select
    options: [draft, submitted, approved, paid]

actions:
  approve:
    type: record
    label: Approve Order
// order.hook.ts
const hooks: ObjectHookDefinition = {
  beforeCreate: async (ctx) => {
    // Default status
    ctx.data.status = 'draft';
    ctx.data.created_by = ctx.user.userId;
  },

  afterUpdate: async (ctx) => {
    // Send notification when status changes
    if (ctx.previousData.status !== ctx.data.status) {
      await sendNotification({
        user: ctx.data.created_by,
        message: `Order ${ctx.id} status changed to ${ctx.data.status}`
      });
    }
  }
};
// order.action.ts
export const approve: ActionDefinition = {
  handler: async ({ id, api, user }) => {
    const order = await api.findOne('order', id);
    
    if (order.status !== 'submitted') {
      throw new Error('Only submitted orders can be approved');
    }

    await api.update('order', id, {
      status: 'approved',
      approved_by: user.id,
      approved_at: new Date()
    });

    return { success: true };
  }
};

🎯 Best Practices

When to Use Each

ScenarioUse
Calculate derived valuesFormula
Validate business rulesHook (beforeCreate/Update)
Set default valuesHook (beforeCreate)
Track changes (audit)Hook (after*)
Enforce permissionsHook (before*)
Custom workflow operationsAction
Multi-step processesAction
External integrationsAction

Performance Tips

  1. Formulas - Keep expressions simple; they run on every read/write
  2. Hooks - Avoid heavy computations in before* hooks (they block the operation)
  3. Actions - Use for expensive operations that shouldn't block CRUD
  4. Async Operations - Use after* hooks for sending emails, external calls
  5. Database Queries - Use the api parameter in hooks/actions to avoid circular dependencies

📚 Next Steps

  • Formulas - Complete formula syntax guide
  • Hooks - Hook lifecycle and patterns
  • Actions - Action definition and implementation
  • Security - Implement permissions using hooks
  • Data Access - Querying data from hooks and actions

🚀 Implementation Status

All three logic mechanisms are production-ready:

  • Formula Engine - 100% Complete

    • JavaScript expression evaluation
    • Sandbox security
    • Type coercion
    • Built-in functions
    • Execution monitoring
  • Hook System - 100% Complete

    • All lifecycle events
    • Transaction-aware
    • Wildcard listeners
    • Hook API for inter-object operations
    • Error handling
  • Action System - 100% Complete

    • Record and global actions
    • Parameter validation
    • Full context access
    • Type-safe definitions
    • Execution framework

📋 See Implementation Status for complete feature matrix

On this page