Validation
Validation protocol schemas
ObjectStack Validation Protocol
This module defines the validation schema protocol for ObjectStack, providing a comprehensive
type-safe validation system similar to Salesforce's validation rules but with enhanced capabilities.
Overview
Validation rules are applied at the data layer to ensure data integrity and enforce business logic.
A validation rule is a **deterministic, synchronous, side-effect-free predicate over a single
record** — it must be decidable from the incoming write (and, on update, the prior record) with
no I/O. Everything advertised here runs on the write path (see
objectql/src/validation/rule-validator.ts); nothing is a silent no-op.
The system supports these validation types:
-
Script Validation: Formula-based validation using a CEL predicate
-
State Machine Validation: Control allowed state transitions
-
Format Validation: Validate a field's value (email, URL, phone, JSON, regex)
-
Cross-Field Validation: Validate relationships between multiple fields
-
JSON Schema Validation: Validate a JSON field against a JSON Schema
-
Conditional Validation: Apply a nested rule based on a CEL condition
Deliberately NOT validation rules
These were once declared here but never enforced. Because the contract above rules them out
(they need I/O or are client-side concerns), they were removed rather than left as silent
no-ops. Use the layer that already does each one correctly:
- Uniqueness → a unique index (
ObjectSchema.indexes,\{ fields, unique: true \},
with partial for a scoped/conditional constraint), or field-level unique: true. A
SELECT-then-INSERT "rule" is inherently racy (TOCTOU); a DB unique constraint is not.
- Async / remote validation → a client-form concern (
debounce/validatorUrlonly mean
anything against keystrokes) and an SSRF/latency hazard on the server write path. Keep it in
the form layer, or enforce the underlying invariant with a unique index / lifecycle hook.
- Custom handler → a
beforeInsert/beforeUpdatelifecycle hook, the typed, supported
extension point for arbitrary validation code.
Salesforce Comparison
ObjectStack validation rules are inspired by Salesforce validation rules but enhanced:
-
Salesforce: Formula-based validation with
Error Condition Formula -
ObjectStack: Multiple validation types with composable rules
Example Salesforce validation rule:
Rule Name: Discount_Cannot_Exceed_40_Percent
Error Condition Formula: Discount_Percent__c > 0.40
Error Message: Discount cannot exceed 40%.
Equivalent ObjectStack rule:
\{
type: 'script',
name: 'discount_cannot_exceed_40_percent',
condition: 'discount_percent > 0.40',
message: 'Discount cannot exceed 40%',
severity: 'error'
\}
Source: packages/spec/src/data/validation.zod.ts
TypeScript Usage
import { ConditionalValidation, CrossFieldValidation, FormatValidation, JSONValidation, ScriptValidation, StateMachineValidation, ValidationRule } from '@objectstack/spec/data';
import type { ConditionalValidation, CrossFieldValidation, FormatValidation, JSONValidation, ScriptValidation, StateMachineValidation, ValidationRule } from '@objectstack/spec/data';
// Validate data
const result = ConditionalValidation.parse(data);ConditionalValidation
Properties
| Property | Type | Required | Description |
|---|---|---|---|
| name | string | ✅ | Unique rule name (snake_case) |
| label | string | optional | Human-readable label for the rule listing |
| description | string | optional | Administrative notes explaining the business reason |
| active | boolean | optional | |
| events | Enum<'insert' | 'update' | 'delete'>[] | optional | Validation contexts |
| priority | integer | optional | Execution priority (lower runs first, default: 100) |
| tags | string[] | optional | Categorization tags (e.g., "compliance", "billing") |
| severity | Enum<'error' | 'warning' | 'info'> | optional | |
| message | string | ✅ | Error message to display to the user |
| type | string | ✅ | |
| when | string | Object | ✅ | Predicate (CEL). e.g. Precord.type == 'enterprise' |
| then | Object | Object | Object | Object | Object | [#](./#) | ✅ | Validation rule to apply when condition is true |
| otherwise | Object | Object | Object | Object | Object | [#](./#) | optional | Validation rule to apply when condition is false |
CrossFieldValidation
Properties
| Property | Type | Required | Description |
|---|---|---|---|
| name | string | ✅ | Unique rule name (snake_case) |
| label | string | optional | Human-readable label for the rule listing |
| description | string | optional | Administrative notes explaining the business reason |
| active | boolean | optional | |
| events | Enum<'insert' | 'update' | 'delete'>[] | optional | Validation contexts |
| priority | integer | optional | Execution priority (lower runs first, default: 100) |
| tags | string[] | optional | Categorization tags (e.g., "compliance", "billing") |
| severity | Enum<'error' | 'warning' | 'info'> | optional | |
| message | string | ✅ | Error message to display to the user |
| type | string | ✅ | |
| condition | string | Object | ✅ | Predicate (CEL) comparing fields. e.g. Precord.end_date > record.start_date |
| fields | string[] | ✅ | Fields involved in the validation |
FormatValidation
Properties
| Property | Type | Required | Description |
|---|---|---|---|
| name | string | ✅ | Unique rule name (snake_case) |
| label | string | optional | Human-readable label for the rule listing |
| description | string | optional | Administrative notes explaining the business reason |
| active | boolean | ✅ | |
| events | Enum<'insert' | 'update' | 'delete'>[] | ✅ | Validation contexts |
| priority | integer | ✅ | Execution priority (lower runs first, default: 100) |
| tags | string[] | optional | Categorization tags (e.g., "compliance", "billing") |
| severity | Enum<'error' | 'warning' | 'info'> | ✅ | |
| message | string | ✅ | Error message to display to the user |
| type | string | ✅ | |
| field | string | ✅ | |
| regex | string | optional | |
| format | Enum<'email' | 'url' | 'phone' | 'json'> | optional |
JSONValidation
Properties
| Property | Type | Required | Description |
|---|---|---|---|
| name | string | ✅ | Unique rule name (snake_case) |
| label | string | optional | Human-readable label for the rule listing |
| description | string | optional | Administrative notes explaining the business reason |
| active | boolean | ✅ | |
| events | Enum<'insert' | 'update' | 'delete'>[] | ✅ | Validation contexts |
| priority | integer | ✅ | Execution priority (lower runs first, default: 100) |
| tags | string[] | optional | Categorization tags (e.g., "compliance", "billing") |
| severity | Enum<'error' | 'warning' | 'info'> | ✅ | |
| message | string | ✅ | Error message to display to the user |
| type | string | ✅ | |
| field | string | ✅ | JSON field to validate |
| schema | Record<string, any> | ✅ | JSON Schema object definition |
ScriptValidation
Properties
| Property | Type | Required | Description |
|---|---|---|---|
| name | string | ✅ | Unique rule name (snake_case) |
| label | string | optional | Human-readable label for the rule listing |
| description | string | optional | Administrative notes explaining the business reason |
| active | boolean | optional | |
| events | Enum<'insert' | 'update' | 'delete'>[] | optional | Validation contexts |
| priority | integer | optional | Execution priority (lower runs first, default: 100) |
| tags | string[] | optional | Categorization tags (e.g., "compliance", "billing") |
| severity | Enum<'error' | 'warning' | 'info'> | optional | |
| message | string | ✅ | Error message to display to the user |
| type | string | ✅ | |
| condition | string | Object | ✅ | Predicate (CEL). If TRUE, validation fails. e.g. Precord.amount < 0 |
StateMachineValidation
Properties
| Property | Type | Required | Description |
|---|---|---|---|
| name | string | ✅ | Unique rule name (snake_case) |
| label | string | optional | Human-readable label for the rule listing |
| description | string | optional | Administrative notes explaining the business reason |
| active | boolean | ✅ | |
| events | Enum<'insert' | 'update' | 'delete'>[] | ✅ | Validation contexts |
| priority | integer | ✅ | Execution priority (lower runs first, default: 100) |
| tags | string[] | optional | Categorization tags (e.g., "compliance", "billing") |
| severity | Enum<'error' | 'warning' | 'info'> | ✅ | |
| message | string | ✅ | Error message to display to the user |
| type | string | ✅ | |
| field | string | ✅ | State field (e.g. status) |
| transitions | Record<string, string[]> | ✅ | Map of { OldState: [AllowedNewStates] } |
ValidationRule
Union Options
This schema accepts one of the following structures:
Option 1
Type: script
Properties
| Property | Type | Required | Description |
|---|---|---|---|
| name | string | ✅ | Unique rule name (snake_case) |
| label | string | optional | Human-readable label for the rule listing |
| description | string | optional | Administrative notes explaining the business reason |
| active | boolean | optional | |
| events | Enum<'insert' | 'update' | 'delete'>[] | optional | Validation contexts |
| priority | integer | optional | Execution priority (lower runs first, default: 100) |
| tags | string[] | optional | Categorization tags (e.g., "compliance", "billing") |
| severity | Enum<'error' | 'warning' | 'info'> | optional | |
| message | string | ✅ | Error message to display to the user |
| type | string | ✅ | |
| condition | string | Object | ✅ | Predicate (CEL). If TRUE, validation fails. e.g. Precord.amount < 0 |
Option 2
Type: state_machine
Properties
| Property | Type | Required | Description |
|---|---|---|---|
| name | string | ✅ | Unique rule name (snake_case) |
| label | string | optional | Human-readable label for the rule listing |
| description | string | optional | Administrative notes explaining the business reason |
| active | boolean | optional | |
| events | Enum<'insert' | 'update' | 'delete'>[] | optional | Validation contexts |
| priority | integer | optional | Execution priority (lower runs first, default: 100) |
| tags | string[] | optional | Categorization tags (e.g., "compliance", "billing") |
| severity | Enum<'error' | 'warning' | 'info'> | optional | |
| message | string | ✅ | Error message to display to the user |
| type | string | ✅ | |
| field | string | ✅ | State field (e.g. status) |
| transitions | Record<string, string[]> | ✅ | Map of { OldState: [AllowedNewStates] } |
Option 3
Type: format
Properties
| Property | Type | Required | Description |
|---|---|---|---|
| name | string | ✅ | Unique rule name (snake_case) |
| label | string | optional | Human-readable label for the rule listing |
| description | string | optional | Administrative notes explaining the business reason |
| active | boolean | optional | |
| events | Enum<'insert' | 'update' | 'delete'>[] | optional | Validation contexts |
| priority | integer | optional | Execution priority (lower runs first, default: 100) |
| tags | string[] | optional | Categorization tags (e.g., "compliance", "billing") |
| severity | Enum<'error' | 'warning' | 'info'> | optional | |
| message | string | ✅ | Error message to display to the user |
| type | string | ✅ | |
| field | string | ✅ | |
| regex | string | optional | |
| format | Enum<'email' | 'url' | 'phone' | 'json'> | optional |
Option 4
Type: cross_field
Properties
| Property | Type | Required | Description |
|---|---|---|---|
| name | string | ✅ | Unique rule name (snake_case) |
| label | string | optional | Human-readable label for the rule listing |
| description | string | optional | Administrative notes explaining the business reason |
| active | boolean | optional | |
| events | Enum<'insert' | 'update' | 'delete'>[] | optional | Validation contexts |
| priority | integer | optional | Execution priority (lower runs first, default: 100) |
| tags | string[] | optional | Categorization tags (e.g., "compliance", "billing") |
| severity | Enum<'error' | 'warning' | 'info'> | optional | |
| message | string | ✅ | Error message to display to the user |
| type | string | ✅ | |
| condition | string | Object | ✅ | Predicate (CEL) comparing fields. e.g. Precord.end_date > record.start_date |
| fields | string[] | ✅ | Fields involved in the validation |
Option 5
Type: json_schema
Properties
| Property | Type | Required | Description |
|---|---|---|---|
| name | string | ✅ | Unique rule name (snake_case) |
| label | string | optional | Human-readable label for the rule listing |
| description | string | optional | Administrative notes explaining the business reason |
| active | boolean | optional | |
| events | Enum<'insert' | 'update' | 'delete'>[] | optional | Validation contexts |
| priority | integer | optional | Execution priority (lower runs first, default: 100) |
| tags | string[] | optional | Categorization tags (e.g., "compliance", "billing") |
| severity | Enum<'error' | 'warning' | 'info'> | optional | |
| message | string | ✅ | Error message to display to the user |
| type | string | ✅ | |
| field | string | ✅ | JSON field to validate |
| schema | Record<string, any> | ✅ | JSON Schema object definition |
Option 6
Type: conditional
Properties
| Property | Type | Required | Description |
|---|---|---|---|
| name | string | ✅ | Unique rule name (snake_case) |
| label | string | optional | Human-readable label for the rule listing |
| description | string | optional | Administrative notes explaining the business reason |
| active | boolean | optional | |
| events | Enum<'insert' | 'update' | 'delete'>[] | optional | Validation contexts |
| priority | integer | optional | Execution priority (lower runs first, default: 100) |
| tags | string[] | optional | Categorization tags (e.g., "compliance", "billing") |
| severity | Enum<'error' | 'warning' | 'info'> | optional | |
| message | string | ✅ | Error message to display to the user |
| type | string | ✅ | |
| when | string | Object | ✅ | Predicate (CEL). e.g. Precord.type == 'enterprise' |
| then | [#](./#) | ✅ | Validation rule to apply when condition is true |
| otherwise | [#](./#) | optional | Validation rule to apply when condition is false |