Expression schema — Shared Protocol reference
Canonical wire format for all "expression"-shaped metadata across ObjectStack.
Expression Protocol
Canonical wire format for all "expression"-shaped metadata across ObjectStack (formula fields, predicates, conditions, criteria, visibility rules, seed dynamic values, …).
The persisted form is { dialect, source } (and, after objectstack compile normalization, { dialect, ast }). String-only shorthand is
accepted at input time for developer ergonomics; build emits the canonical
envelope.
Dialects
| dialect | engine | use |
|---|---|---|
cel | @objectstack/formula (cel-js + ObjectStack stdlib) | formulas, predicates, seed dynamic values |
cron | none at parse time — croner fires it at schedule time, on the one wired slot | job schedules |
template | placeholder interpolation at evaluate time (same variable scope as CEL); canonically {{var}}, but the accepted spelling belongs to the renderer, not to this schema — see TemplateExpressionInputSchema | notification subjects/bodies, titleFormat, prompt templates |
No cron syntax is judged at parse time: croner evaluates a cron slot only
when CronSchedule.expression is scheduled (toBoundaryJobSchedule →
CronJobAdapter, where an invalid pattern is refused); every other
cron-typed slot is parsed and reaches no engine, and @objectstack/formula's
registered cron engine has no caller outside that package.
A TYPED slot — one declared with CronExpressionInputSchema or
TemplateExpressionInputSchema, or with the same input carrying its own
refusal sentences (a notify node's title / message) — takes a bare,
non-blank string (shorthand for its own dialect) or an envelope declaring
that one dialect. An envelope naming any other dialect, and a blank string,
are refused at the slot with one issue whose message names the dialect and
the fix; a template slot's fix is written in the placeholder spelling its
renderer reads. Only the untyped
ExpressionInputSchema takes every declared dialect in envelope form.
Those three are the whole list — it is exactly the ExpressionDialect enum
below. Procedural JavaScript is not a dialect: it is the L2 authoring
surface, the sandboxed, capability-gated ScriptBody { language: 'js' } in
hook/action bodies. A js row stood in this table long after the dialect was
retired in #3278 (ADR-0058 addendum); ExpressionSchema rejects
dialect: 'js'.
SQL fragments (analytics joins, partial indexes) are intentionally not routed through this schema — they stay driver-native because their security posture and portability story differ.
See also: content/docs/concepts/north-star.mdx §8 "No private expression DSL"
Source: packages/spec/src/shared/expression.zod.ts
TypeScript Usage
import { CronExpressionInputSchema, EvaluatedExpressionSchema, EvaluatedExpressionInputSchema, ExpressionSchema, ExpressionDialect, ExpressionInputSchema, ExpressionMetaSchema, PredicateSchema, PredicateInputSchema, TemplateExpressionInputSchema } from '@objectstack/spec/shared';
import type { CronExpressionInput, EvaluatedExpression, EvaluatedExpressionInput, Expression, ExpressionDialect, ExpressionInput, ExpressionMeta, Predicate, PredicateInput, TemplateExpressionInput } from '@objectstack/spec/shared';
// Validate data
const result = CronExpressionInputSchema.parse(data);CronExpressionInput
Union Options
This schema accepts one of the following structures:
Option 1
Type: string
Option 2
Properties
| Property | Type | Required | Description |
|---|---|---|---|
| dialect | 'cron' | ✅ | |
| source | string | optional | |
| ast | any | optional | |
| meta | { rationale?: string; generatedBy?: string } | optional |
EvaluatedExpression
Properties
| Property | Type | Required | Description |
|---|---|---|---|
| dialect | Enum<'cel' | 'cron' | 'template'> | ✅ | |
| source | string | ✅ | |
| ast | any | optional | |
| meta | { rationale?: string; generatedBy?: string } | optional |
EvaluatedExpressionInput
Union Options
This schema accepts one of the following structures:
Option 1
Type: string
Option 2
Properties
| Property | Type | Required | Description |
|---|---|---|---|
| dialect | Enum<'cel' | 'cron' | 'template'> | ✅ | |
| source | string | ✅ | |
| ast | any | optional | |
| meta | { rationale?: string; generatedBy?: string } | optional |
Expression
Properties
| Property | Type | Required | Description |
|---|---|---|---|
| dialect | Enum<'cel' | 'cron' | 'template'> | ✅ | |
| source | string | optional | |
| ast | any | optional | |
| meta | { rationale?: string; generatedBy?: string } | optional |
ExpressionDialect
Allowed Values
celcrontemplate
ExpressionInput
Union Options
This schema accepts one of the following structures:
Option 1
Type: string
Option 2
Properties
| Property | Type | Required | Description |
|---|---|---|---|
| dialect | Enum<'cel' | 'cron' | 'template'> | ✅ | |
| source | string | optional | |
| ast | any | optional | |
| meta | { rationale?: string; generatedBy?: string } | optional |
ExpressionMeta
Properties
| Property | Type | Required | Description |
|---|---|---|---|
| rationale | string | optional | |
| generatedBy | string | optional |
Predicate
Properties
| Property | Type | Required | Description |
|---|---|---|---|
| dialect | Enum<'cel' | 'cron' | 'template'> | ✅ | |
| source | string | optional | |
| ast | any | optional | |
| meta | { rationale?: string; generatedBy?: string } | optional |
PredicateInput
Union Options
This schema accepts one of the following structures:
Option 1
Type: string
Option 2
Properties
| Property | Type | Required | Description |
|---|---|---|---|
| dialect | Enum<'cel' | 'cron' | 'template'> | ✅ | |
| source | string | optional | |
| ast | any | optional | |
| meta | { rationale?: string; generatedBy?: string } | optional |
TemplateExpressionInput
Union Options
This schema accepts one of the following structures:
Option 1
Type: string
Option 2
Properties
| Property | Type | Required | Description |
|---|---|---|---|
| dialect | 'template' | ✅ | |
| source | string | optional | |
| ast | any | optional | |
| meta | { rationale?: string; generatedBy?: string } | optional |