Aliases & Typed JSON — ASL
Named scalar aliases & Extended Types
Section titled “Named scalar aliases & Extended Types”Create a named alias or extended scalar type over a built-in scalar or another scalar type using extends.
scalar type EmailStr extends str;scalar type Score extends float32;Deprecation Notice: The
extendingkeyword is deprecated in favor ofextends. Runningaxel fmtwill automatically migrateextendingtoextends.
Extended Scalars with Field Descriptors
Section titled “Extended Scalars with Field Descriptors”Scalar types can define field descriptors (constraint, default, rewrite) inside their body block { ... }, just like properties in object type models.
scalar type Code extends str { constraint min_length(6); constraint max_length(6); default := random_hex(6);}
scalar type AutoTimestamp extends datetime { rewrite update := datetime_current();}Any object type property declared with an extended scalar automatically inherits all of its field descriptors:
type Product { required id: uuid { constraint pk; }; # Inherits min_length(6), max_length(6), and default random_hex(6): code: Code; # Properties can override the default and add additional constraints: custom_code: Code { default := '999999'; constraint exclusive; }; # Inherits the BEFORE UPDATE trigger rewrite: updated_at: AutoTimestamp;}Chained Scalar Inheritance
Section titled “Chained Scalar Inheritance”Scalar types can extend other user-defined scalar types, inheriting constraints, defaults, and rewrites in a chain:
scalar type ShortStr extends str { constraint max_length(10);}
scalar type ExactCode extends ShortStr { constraint min_length(6); default := '000000';}Custom SQL Extension Scalars
Section titled “Custom SQL Extension Scalars”For PostgreSQL extension types (such as PostGIS geography, geometry, pgvector vector, citext, ltree), declare custom SQL scalars with extends sql "<type>". You can optionally supply client-side representation typing using as:
# Record representation: generates interfaces/structs in codegen, enables AQL dot-access (.location.latitude)scalar type Point extends sql "geography(Point, 4326)" as { latitude: float32; longitude: float32;};
# Codec functions for reading and writing:function (p Point) deserialize() Point { return Point{ latitude: ST_Y(p::geometry), longitude: ST_X(p::geometry) };};
function (p Point) serialize() { return ST_SetSRID(ST_MakePoint(p.longitude, p.latitude), 4326);};
# Multi-dimensional array representation: generates number[] / []float32 in codegenscalar type Embedding extends sql "vector(1536)" as multi float32;
# Primitive scalar mapping: generates string in codegenscalar type Citext extends sql "citext" as str;
# Opaque type: defaults to string in codegenscalar type Geometry extends sql "geometry";How deserialize and serialize work:
Section titled “How deserialize and serialize work:”- Zero-Overhead Read Inlining: In
selectqueries,deserialize()inlines PostGIS extraction viajson_build_object(...)directly on the database side. - SQL-Side Write Serialization: In
insertandupdatequeries, passing a{ latitude, longitude }object or parameter is inlined usingserialize()(ST_SetSRID(ST_MakePoint(...), 4326)) directly on the database side. - Backwards Compatibility: Legacy inline extraction
latitude: float32 := ST_Y(__self__::geometry);is fully supported and auto-migrated byaxel fmt.
Typed JSON Scalars
Section titled “Typed JSON Scalars”You can define structured, typed JSON and JSONB scalars that give type safety, autocomplete, and query capabilities to document data in PostgreSQL.
scalar type Coordinate extends json { lat: str; lng: str;}
scalar type ItemStats extends jsonb { score: float64; views: int32; multi tags: str;}
scalar type VendorAvailability extends jsonb { required day: str; opening: time; closing: time; effective_from: date; updated_at: datetime;}Allowed Field Types
Section titled “Allowed Field Types”To ensure reliable extraction and type coercion in PostgreSQL, field types within typed JSON scalars are strictly restricted to:
- Strings:
str - Numbers:
int16,int32,int64,float32,float64,decimal - Temporal:
date,time,datetime - Arrays: Provided via the
multimodifier (e.g.multi tags: str;,multi scores: float64;)
Nested JSON objects and non-primitive types are not permitted within typed JSON scalars.
Temporal fields
Section titled “Temporal fields”JSON has no native date/time type, so temporal fields are stored in the document as
strings ("09:00:00", "2026-01-31", "2026-01-31T08:30:00Z") and cast on
extraction, exactly like numeric fields:
# ((availability->>'opening')::TIME)select Vendor { id, name } filter .availability.opening <= $now;
# ((availability->>'updated_at')::TIMESTAMPTZ)select Vendor { id, name } filter .availability.updated_at >= $since;Because the underlying document value is a string, generated clients type these
fields as strings (string in TypeScript, string in Go) rather than Date /
time.Time — the same treatment decimal already gets. Query parameters
compared against them keep their real temporal type (Date in TypeScript,
time.Time in Go), since those are bound as SQL values.
Using Typed JSON Scalars in Types
Section titled “Using Typed JSON Scalars in Types”Declare properties with your typed JSON scalar:
type Place { required id: uuid; name: str; coord: Coordinate; stats: ItemStats;}Querying in AQL
Section titled “Querying in AQL”You can query and filter on individual fields of a typed JSON scalar directly using dot-notation:
# String comparison automatically extracts as text (coord->>'lat')select Place { id, name } filter .coord.lat = $lat;
# Numeric comparisons automatically apply PostgreSQL type casting ((stats->>'score')::DOUBLE PRECISION)select Place { id, name } filter .stats.score > $min_score;
# Integer comparisonsselect Place { id, name } filter .stats.views >= 100;Generated Client Types
Section titled “Generated Client Types”Axel generates idiomatic types for all target languages:
TypeScript
Section titled “TypeScript”export interface Coordinate { lat?: string | null; lng?: string | null;}
export interface ItemStats { score?: number | null; views?: number | null; tags?: string[] | null;}
export interface VendorAvailability { closing?: string | null; day: string; effectiveFrom?: string | null; opening?: string | null; updatedAt?: string | null;}
export interface Place { id: string; name?: string | null; coord?: Coordinate | null; stats?: ItemStats | null;}type Coordinate struct { Lat *string `json:"lat"` Lng *string `json:"lng"`}
type ItemStats struct { Score *float64 `json:"score"` Views *int32 `json:"views"` Tags []string `json:"tags"`}
type VendorAvailability struct { Closing *string `json:"closing"` Day string `json:"day"` EffectiveFrom *string `json:"effective_from"` Opening *string `json:"opening"` UpdatedAt *string `json:"updated_at"`}
type Place struct { ID string `json:"id" db:"id"` Name *string `json:"name" db:"name"` Coord *Coordinate `json:"coord" db:"coord"` Stats *ItemStats `json:"stats" db:"stats"`}