Skip to content

Latest commit

 

History

History
416 lines (289 loc) · 20.7 KB

File metadata and controls

416 lines (289 loc) · 20.7 KB
title Validation
description Validation protocol schemas

{/* ⚠️ AUTO-GENERATED — DO NOT EDIT. Run build-docs.ts to regenerate. Hand-written docs live in the module folders under content/docs/. */}

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) — insert, single-id update, and multi-row

(multi: true) update, where the evaluator runs once per matched row (#3106); nothing is a

silent no-op. The events enum admits only insert/update for this reason — see the

delete note under "Deliberately NOT validation rules" below.

The system supports these validation types:

  1. Script Validation: Formula-based validation using a CEL predicate

  2. State Machine Validation: Control allowed state transitions

  3. Format Validation: Validate a field's value (email, URL, phone, JSON, regex)

  4. Cross-Field Validation: Validate relationships between multiple fields

  5. JSON Schema Validation: Validate a JSON field against a JSON Schema

  6. 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/validatorUrl only 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 / beforeUpdate lifecycle hook, the typed, supported

extension point for arbitrary validation code.

  • Delete-time guards (events: ['delete']) → a beforeDelete lifecycle hook. The evaluator

only runs on the insert/update write path (a delete carries no record payload to validate), so

a delete event was a proven silent no-op — the enum value was removed rather than left

advertised-but-unenforced (#3184; see docs/audits/2026-06-validationschema-property-liveness.md).

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'>[] optional Write contexts the rule runs on. delete is intentionally absent — the evaluator only runs on the insert/update write path; guard deletions with a beforeDelete lifecycle hook
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 'conditional'
when string | { dialect: Enum<'cel' | 'js' | 'cron' | 'template'>; source?: string; ast?: any; meta?: object } Predicate (CEL). e.g. Precord.type == 'enterprise'
then { name: string; label?: string; description?: string; active?: boolean; … } | { name: string; label?: string; description?: string; active?: boolean; … } | { name: string; label?: string; description?: string; active?: boolean; … } | { name: string; label?: string; description?: string; active?: boolean; … } | { name: string; label?: string; description?: string; active?: boolean; … } | [ConditionalValidation](#conditionalvalidation) Validation rule to apply when condition is true
otherwise { name: string; label?: string; description?: string; active?: boolean; … } | { name: string; label?: string; description?: string; active?: boolean; … } | { name: string; label?: string; description?: string; active?: boolean; … } | { name: string; label?: string; description?: string; active?: boolean; … } | { name: string; label?: string; description?: string; active?: boolean; … } | [ConditionalValidation](#conditionalvalidation) 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'>[] optional Write contexts the rule runs on. delete is intentionally absent — the evaluator only runs on the insert/update write path; guard deletions with a beforeDelete lifecycle hook
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 'cross_field'
condition string | { dialect: Enum<'cel' | 'js' | 'cron' | 'template'>; source?: string; ast?: any; meta?: object } Predicate (CEL) comparing fields. e.g. Precord.end_date > record.start_date
fields string[] Fields involved. Only fields[0] is read (labels which field the violation attaches to); the rest are advisory. Shares script’s evaluation path.

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'>[] Write contexts the rule runs on. delete is intentionally absent — the evaluator only runs on the insert/update write path; guard deletions with a beforeDelete lifecycle hook
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 'format'
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'>[] Write contexts the rule runs on. delete is intentionally absent — the evaluator only runs on the insert/update write path; guard deletions with a beforeDelete lifecycle hook
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 'json_schema'
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'>[] optional Write contexts the rule runs on. delete is intentionally absent — the evaluator only runs on the insert/update write path; guard deletions with a beforeDelete lifecycle hook
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 'script'
condition string | { dialect: Enum<'cel' | 'js' | 'cron' | 'template'>; source?: string; ast?: any; meta?: 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'>[] Write contexts the rule runs on. delete is intentionally absent — the evaluator only runs on the insert/update write path; guard deletions with a beforeDelete lifecycle hook
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 'state_machine'
field string State field (e.g. status)
transitions Record<string, string[]> Map of { OldState: [AllowedNewStates] }
initialStates string[] optional States a record may be CREATED in. When set, an INSERT whose state field carries a value outside this list is rejected (server-enforced) — the FSM entry point. transitions only governs UPDATE, and a select field permits ANY declared option as an initial value, so without this a record could be born mid-flow (e.g. created already approved). Omit to keep the legacy behavior (no initial-state check on insert). #3165.

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'>[] optional Write contexts the rule runs on. delete is intentionally absent — the evaluator only runs on the insert/update write path; guard deletions with a beforeDelete lifecycle hook
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 'script'
condition string | { dialect: Enum<'cel' | 'js' | 'cron' | 'template'>; source?: string; ast?: any; meta?: 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'>[] optional Write contexts the rule runs on. delete is intentionally absent — the evaluator only runs on the insert/update write path; guard deletions with a beforeDelete lifecycle hook
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 'state_machine'
field string State field (e.g. status)
transitions Record<string, string[]> Map of { OldState: [AllowedNewStates] }
initialStates string[] optional States a record may be CREATED in. When set, an INSERT whose state field carries a value outside this list is rejected (server-enforced) — the FSM entry point. transitions only governs UPDATE, and a select field permits ANY declared option as an initial value, so without this a record could be born mid-flow (e.g. created already approved). Omit to keep the legacy behavior (no initial-state check on insert). #3165.

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'>[] optional Write contexts the rule runs on. delete is intentionally absent — the evaluator only runs on the insert/update write path; guard deletions with a beforeDelete lifecycle hook
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 'format'
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'>[] optional Write contexts the rule runs on. delete is intentionally absent — the evaluator only runs on the insert/update write path; guard deletions with a beforeDelete lifecycle hook
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 'cross_field'
condition string | { dialect: Enum<'cel' | 'js' | 'cron' | 'template'>; source?: string; ast?: any; meta?: object } Predicate (CEL) comparing fields. e.g. Precord.end_date > record.start_date
fields string[] Fields involved. Only fields[0] is read (labels which field the violation attaches to); the rest are advisory. Shares script’s evaluation path.

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'>[] optional Write contexts the rule runs on. delete is intentionally absent — the evaluator only runs on the insert/update write path; guard deletions with a beforeDelete lifecycle hook
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 'json_schema'
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'>[] optional Write contexts the rule runs on. delete is intentionally absent — the evaluator only runs on the insert/update write path; guard deletions with a beforeDelete lifecycle hook
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 'conditional'
when string | { dialect: Enum<'cel' | 'js' | 'cron' | 'template'>; source?: string; ast?: any; meta?: object } Predicate (CEL). e.g. Precord.type == 'enterprise'
then [ValidationRule](#validationrule) Validation rule to apply when condition is true
otherwise [ValidationRule](#validationrule) optional Validation rule to apply when condition is false