IR Schema
Full IR reference — Schema, TypeSchema, Predicate types, Transform types
The wellformed Intermediate Representation (IR) is the portable JSON format emitted by the TypeScript builder and read by the runtimes.
The TypeScript types match the Rust wellformed crate's serde serialization format exactly.
You usually do not write this by hand
Author schemas with the builder API. Inspect IR when you need storage, auditing, Rust validation, codegen, or advanced tooling.
Schema (Top-Level)
interface Schema {
version: string;
id?: string;
title?: string;
description?: string;
irs_form?: IrsFormMetadata;
pdf_template?: PdfTemplate;
import?: ImportConfig;
definitions?: Record<string, TypeSchema>;
sections?: Record<string, SectionDefinition>;
root: TypeSchema;
}TypeSchema
A discriminated union on the type field. The Rust crate uses #[serde(tag = "type")]:
Primitive Types
{ type: "string", transforms?: Transform[], constraints?: Constraint[] }
{ type: "number", transforms?: Transform[], constraints?: Constraint[] }
{ type: "integer", transforms?: Transform[], constraints?: Constraint[] }
{ type: "boolean" }Fixed-Width Integers
{ type: "int32", transforms?: Transform[], constraints?: Constraint[] }
{ type: "int64", transforms?: Transform[], constraints?: Constraint[] }
{ type: "uint32", transforms?: Transform[], constraints?: Constraint[] }
{ type: "uint64", transforms?: Transform[], constraints?: Constraint[] }Domain-Specific Numeric Types
{ type: "money", scale?: number, transforms?: Transform[], constraints?: Constraint[] }
{ type: "currency", code?: string, scale?: number, transforms?: Transform[], constraints?: Constraint[] }
{ type: "decimal", precision?: number, scale?: number, transforms?: Transform[], constraints?: Constraint[] }
{ type: "percentage", format?: "decimal" | "whole", allow_over_100?: boolean, scale?: number, transforms?: Transform[], constraints?: Constraint[] }Date
{ type: "date", format?: string, transforms?: Transform[], constraints?: Constraint[] }Object
{
type: "object",
properties?: Record<string, PropertySchema>,
additional_properties?: boolean,
unknown_keys?: "strict" | "passthrough" | "strip",
catchall?: TypeSchema,
rules?: Constraint[]
}PropertySchema is a TypeSchema flattened with an optional required field (defaults to true):
type PropertySchema = TypeSchema & { required?: boolean }Array
{
type: "array",
items: TypeSchema,
min_items?: number,
max_items?: number,
transforms?: Transform[],
constraints?: Constraint[]
}Enum
{ type: "enum", values: unknown[] }Literal and Never
{ type: "literal", value: unknown }
{ type: "never" }Tuple
{ type: "tuple", items: TypeSchema[] }Union
{ type: "union", oneOf: TypeSchema[], discriminator?: string }Intersection
{ type: "intersection", allOf: TypeSchema[] }Record
{ type: "record", value: TypeSchema, key?: TypeSchema, partial?: boolean }Preprocess and Catch
{ type: "preprocess", transforms?: Transform[], schema: TypeSchema }
{ type: "catch", schema: TypeSchema, value: unknown }Reference and Any
{ type: "ref", $ref: string }
{ type: "any" }Constraint
Every validation rule is a Constraint:
interface Constraint {
id?: string;
pred: Predicate;
error: ErrorMeta;
}
interface ErrorMeta {
code: string;
message: string;
path?: string;
severity?: "error" | "warning";
help?: string;
source?: string;
}error.message is the custom message returned when pred fails. code should
stay stable for application logic and localization; message can be changed as
display copy.
{
"pred": { "type": "min_len", "len": 1 },
"error": {
"code": "NAME_REQUIRED",
"message": "Name is required"
}
}Predicate
A discriminated union on the type field:
Constants
{ type: "true" }
{ type: "false" }String/Array Predicates
{ type: "regex", pattern: string, flags?: string }
{ type: "template_literal", parts: TemplateLiteralPart[] }
{ type: "min_len", len: number }
{ type: "max_len", len: number }TemplateLiteralPart supports these part shapes:
{ kind: "literal", value: string }
{ kind: "digits", min?: number, max?: number }
{ kind: "ascii_letters", min?: number, max?: number }
{ kind: "ascii_alphanumeric", min?: number, max?: number }
{ kind: "uppercase", min?: number, max?: number }
{ kind: "lowercase", min?: number, max?: number }
{ kind: "hex", min?: number, max?: number }Numeric Predicate
{ type: "range", min?: number, max?: number }Path-Based Predicates
{ type: "exists", path: string } // JSON Pointer
{ type: "eq", path: string, value: any }
{ type: "in", path: string, values: any[] }
{ type: "required_with", field: string, with: string }
{ type: "required_without", field: string, without: string }
{ type: "exactly_one_of", paths: string[] }Cross-Field Comparisons
{ type: "eq_fields", left: string, right: string }
{ type: "gt_field", left: string, right: string }
{ type: "gte_field", left: string, right: string }
{ type: "lt_field", left: string, right: string }
{ type: "lte_field", left: string, right: string }Computed Predicates
{ type: "sum_equals", paths: string[], target: string }
{ type: "sum_equals_value", paths: string[], value: number }Boolean Combinators
{ type: "and", predicates: Predicate[] }
{ type: "or", predicates: Predicate[] }
{ type: "not", predicate: Predicate }
{ type: "implies", if: Predicate, then: Predicate }Named Predicates
{ type: "call", name: string, args?: any }Named predicates are resolved through the PredicateRegistry at evaluation time.
Transform
Transforms are tagged on the fn field (Rust uses #[serde(tag = "fn")]):
{ fn: "trim" }
{ fn: "collapse_whitespace" }
{ fn: "digits_only" }
{ fn: "upper" }
{ fn: "lower" }
{ fn: "money_to_cents", scale?: number }
{ fn: "date_parse", format: string }
{ fn: "replace", pattern: string, replacement: string }
{ fn: "normalize_flight_number" }
{ fn: "normalize_icd10" }
{ fn: "normalize_cpt" }
{ fn: "normalize_hcpcs" }
{ fn: "normalize_ndc11" }
{ fn: "default", value: any }
{ fn: "phone_us" }
{ fn: "phone_e164" }
{ fn: "card_mask_last4" }
{ fn: "format_ssn" }
{ fn: "format_ein" }
{ fn: "mask_ssn" }
{ fn: "mask_ein" }
{ fn: "format_iban" }
{ fn: "format_credit_card" }
{ fn: "format_thousands", separator?: string }
{ fn: "format_decimal", places: number }Relationship to Rust Crate
The TypeScript IR types are a direct mirror of the Rust wellformed crate's types:
TypeSchemacorresponds to Rust'sTypeSchemaenum with#[serde(tag = "type")]PropertySchemauses flattened serialization matching Rust's#[serde(flatten)]Predicatecorresponds to Rust'sPredicateenum with#[serde(tag = "type")]Transformcorresponds to Rust'sTransformenum with#[serde(tag = "fn")]- Object fields use
snake_caseto match Rust's#[serde(rename_all = "snake_case")] - Union variants use
oneOfto match Rust's#[serde(rename = "oneOf")] - Intersection variants use
allOfto match Rust's#[serde(rename = "allOf")]
A schema serialized from TypeScript can be deserialized and evaluated by the Rust crate, and vice versa:
// TypeScript → JSON → Rust
const json = schemaToJSON(schema);
// Send to Rust backend, which deserializes with serde_jsonRuntime support can lag behind the type surface for extension points. For ref schemas, both the TypeScript and Rust runtimes resolve names through top-level definitions; missing references and cycles should be treated as schema authoring errors and covered by compatibility tests.