Jadpo is experimental and not ready for production. These docs follow the compiler's main branch as of 30 Oct 2026. What's built · Install 0.1.0
jadpo
Reference: Standard library

Standard library

Every standard type meets one contract

On this page

Every standard type comes with the same baseline of operations, so once you know how one type is built, read, sent, stored and shown, you know them all. Some types add operations of their own on top (Extras beyond the baseline).

Baseline What it is Where a type has none
Construct and validate From its parts (PostalCode(raw), Money from money.from_minor), and from text with text.parse(raw, as: T); each failure named by the rule it breaks, malformed for text that isn't the type at all Never: every type has both
format for people text.format(value, locale:), giving PresentationText, through CLDR where the locale matters (an address's line order, a name's order, a quantity's unit) Every type has one
Canonical text text.from(value): E.164, ISO codes, #rrggbb, ISO 8601, pkg: URLs; the form text.parse reads back Types with no single text form (PostalAddress, PersonName); asking is a compile error
JSON One shape, read and written by json.read, json.write and every route Never
Storage One shape: a column type, or named columns for an object (Money's two) Never
==, and order == on every type; < and sort_by where the type is ordered (quantities of one kind, Money of one currency, times, SemanticVersion, Percentage) Order only where it means something
Conversions at named edges geo.from_lng_lat, units.in, Isbn 10 to 13, MaskedCardNumber from a PaymentCardNumber Only where the type has another form

Each of text.parse, text.format and text.from is one operation over every standard type, as json.read is one over every input type, so the contract adds 2 names, not 700.

A standard type may take a type argument (Range<T>), be a choice of other types (Discount, an enum with data), be built on a built-in type with bounds of its own (TaxRate), or be a named collection (RichText a list, LocalisedText a map). A named collection is built, sent and read as one your app declares is. Every standard object and choice is stored in named columns laid out from its parts (Every boundary through the contract).

jadpo suggest uses each standard type's shape and its signals, such as conversion constants, the words of names, format literals and validation texts, to find code that hand-rolls the type (Suggestions).

In this example a form sends a quantity as text, an order's time goes into a log line as its canonical text, and a day, a duration and a zone are shown to people in their own words:

example
type OrderedAt = Instant {}type LogText = Text {}type FormText = Text {}type Quantity = Int {    min: 1    max: 12}type QuantityUnreadable = Failure {    kind: FailureKind.invalid_value}type QuantityTooLarge = Failure {    kind: FailureKind.invalid_value}/// The quantity a form sent: text that may not be a number at all/// (`malformed`), or a number outside `Quantity`'s rules.function quantity_from(raw: FormText) fails QuantityUnreadable, QuantityTooLarge -> Quantity {    var quantity: Quantity = attempt text.parse(raw, as: Quantity) {        malformed: QuantityUnreadable        invalid: QuantityTooLarge    }    return quantity}/// When an order was placed, as a log line writes it.function placed_text(at: OrderedAt) -> LogText {    return LogText(text.from(at))}
example
test "standard values are read, written and shown one way" {    var ordered_quantity: Quantity = attempt quantity_from("4")    assert ordered_quantity == 4    assert quantity_from("13") fails QuantityTooLarge    assert quantity_from("four") fails QuantityUnreadable    assert placed_text("2026-10-25T09:30:00Z") == "2026-10-25T09:30:00Z"    assert text.from(Zone.europe_london) == "Europe/London"    assert text.format(Weekday.monday, locale: Locale.fr_fr) == "lundi"    assert text.format(90m, locale: Locale.en_gb) == "1 hour, 30 minutes"    assert text.format(Locale.fr_fr, locale: Locale.en_gb) == "French (France)"}

Extras beyond the baseline

Many groups of types have operations beyond the baseline. Each has its full table under Operations:

Group Extras Count
Money and payments money.from_minor, of, minor, currency, sum, allocate, split, convert (with a given ExchangeRate, never fetched), mask (a MaskedCardNumber from a PaymentCardNumber), apply_discount (a Discount off a Price) 10; arithmetic by the operator table
Physical quantities, DataSize, Bitrate units.of, in, sum; operators within a kind and the defined products and quotients 3
Time and calendar The 21 from Configuration and time, plus temporal.occurrences, temporal.open_at and temporal.period_between; CalendarPeriod arithmetic by the operator table 3 new
Ranges (Range<T>, DateRange, InstantRange) range.contains, range.overlaps, range.intersection 3
Colour colour.from_hsl, from_oklch, mix, with_lightness, contrast (WCAG 2) 5
Security crypto as tabled below, with sign, verify_signature, verify_jwt and verify_totp 10
Web web.with_query(url, entries), web.resolve(base, relative), web.cidr_contains(range, address) 3
Software semver.satisfies(version, range) 1
Media media.fit(dimensions, within:), keeping the aspect ratio 1
Text formats markup.to_html, rich_to_html, to_plain_text; text.pick with fallback. Html is always safe: its construction sanitises 4
Geometry geo.distance, length, bearing, area, intersects, contains, within, centroid, bounding_box, the constructions and WKT's two Geospatial
AI (Vector, Embedding, ChatMessage) ai.generate, stream, embed, embed_all, distance AI
People, places, identity numbers, commerce codes, health, travel, other Baseline only: each identifier's check digit or pattern is its construction

Rules across a type's parts

Some standard types have parts that constrain each other: a range's start can't be after its end. Each such constraint is a named rule, as on an object type you declare (Rules across fields). So a value you build in code is proved or attempted exactly as a value of your own type is. Here are each type's rules, in the order they are checked, which is also the order OpenAPI's description lists them in:

Type Rule What it holds
InstantRange, DateRange, Range<T> start_not_after_end start <= end; an open end of a Range<T> keeps it
IsoWeek week_in_year week 53 only in a year with 53 ISO weeks
CalendarEvent days_or_times both days and no start, end or zone, or all three and no days
last_day_not_before_first last_day >= first_day
end_after_start end > start
recurrence_from_start the recurrence starts on first_day, or at start in zone
PostalAddress postal_code_of_country postal_code, where it has one, in its country's form
IdentityDocument number_of_kind_and_country number in the form of its kind from its country
PassportNumber, DrivingLicenceNumber, TaxId, VatNumber number_of_country number in its country's form
ContentRating rating_of_scheme rating one of its scheme's categories
Timecode frames_within_rate frames below the rate's whole frames a second
drop_frame_rate drop-frame only at 30000/1001 and 60000/1001
frame_not_dropped never a label drop-frame counting skips
SubtitleCue start_not_negative start >= 0ms
end_after_start end > start
text_without_arrow text holds no -->
Rating best_above_worst best > worst
value_on_scale worst <= value and value <= best
BloodPressure diastolic_below_systolic diastolic < systolic
MapTile within_zoom x and y each below 2 to the zoom
TokenUsage cached_within_input cached <= input, where it is given
PasswordPolicy min_not_above_max min_length <= max_length
BoundingBox south_not_above_north south <= north
Polygon rings_valid each ring closed and simple, no rings touching, holes inside the outer ring, every edge under half the globe (Polygons are valid when built)
altitudes_agree every position with an altitude, or none (Altitudes are all or nothing)
LineString, MultiPoint, MultiLineString, MultiPolygon altitudes_agree every position with an altitude, or none, a multi-geometry's parts with each other (Altitudes are all or nothing)

Here a slot built from two bookings' instants is attempted by its rule, and one whose order a condition has already checked needs no attempt:

example
type SlotBackwards = Failure {    kind: FailureKind.invalid_value}/// A slot from one booking's start to another's end.function combined_slot(    earlier_booking: InstantRange,    later_booking: InstantRange) fails SlotBackwards -> InstantRange {    return attempt InstantRange {        start: earlier_booking.start        end: later_booking.end    } {        start_not_after_end: SlotBackwards    }}/// The same, refused before it is built.function combined_slot_checked(    earlier_booking: InstantRange,    later_booking: InstantRange) fails SlotBackwards -> InstantRange {    if (later_booking.end < earlier_booking.start) {        reject SlotBackwards    }    return InstantRange {  // the condition proves `start_not_after_end`        start: earlier_booking.start        end: later_booking.end    }}
example
test "a slot never ends before it starts" {    var morning_slot: InstantRange = InstantRange {        start: "2026-10-26T09:00:00Z"        end: "2026-10-26T12:00:00Z"    }    var afternoon_slot: InstantRange = InstantRange {        start: "2026-10-26T13:00:00Z"        end: "2026-10-26T17:00:00Z"    }    var whole_day: InstantRange = attempt combined_slot(morning_slot, afternoon_slot)    assert whole_day.end == afternoon_slot.end    assert combined_slot(afternoon_slot, morning_slot) fails SlotBackwards    assert combined_slot_checked(afternoon_slot, morning_slot) fails SlotBackwards}
  • Each rule behaves like a condition you write. It is named, proved, attempted and reported exactly as a condition is. It runs on every value made, however it is made: an object literal, text.parse, a request's JSON or json.read, in compiled code as in the interpreter. The checker reads a literal by the same check. Every check across a standard type's parts is one of these rules, so there is no second mechanism.
  • How the checker proves a rule. It reads a literal by what it shows of each part. A part written out is known, a part written none is absent, and a part built from a value of a type that can't be none is given. A rule its parts decide either holds or is the error type.invalid_literal, naming the rule.
  • A rule ordering two parts (a range's ends, a rating's, a password policy's lengths) can otherwise be proved as a condition comparing two fields is: by a comparison known where the value is built, or by the declared ranges. A password policy's minimum length of 8, for example, is never above its maximum, which is at least 64. Any other rule on a value built from other values is attempted.
  • A rule may read one part. A subtitle cue's start at 0 or later, and its text without -->, are named rules checked in the cue's order, not bounds the part's value would be built against on its own. Where one statement covers it, it is one rule: a rating's value from its worst to its best is value_on_scale, and a map tile's column and row below 2 to the power of its zoom is within_zoom.
  • Text and JSON break rules by name. Text in the type's form that breaks a rule (week 53 of 2027, a cue ending before it starts, PEGI 12A) is read and then breaks the rule; it isn't malformed. Only text that isn't the type at all is malformed. So attempt text.parse(raw, as: T) maps each of T's rules, or invalid:. JSON reports the rule at the object's path, or at the field's path for a type sent as its text.
  • Text written where such a type goes is refused. That text would be the type's canonical text, which is read only when the program runs. The checker doesn't have the readers the program uses for it, so it couldn't check the rules. The literal is type.invalid_literal: write the value out as an object literal, whose rules are checked, or read the text with text.parse and attempt. A type with one text form and no such rules (SemanticVersion, CardExpiry, Colour) still takes its text.
  • Any other literal where a standard object type goes is type.mismatch: a number, a Bool, or text where a type with no single text form goes (PostalAddress, or WKT where a geometry goes, mixed altitudes included).
  • A value read from the store is checked against its rules, as a value made anywhere else is. Jadpo never writes a value that breaks one, so a row that does was written by something else. Reading it is a defect naming the rule, never the values. A rule the running program can't decide, such as one on a part of the wrong kind, counts as broken.
  • OpenAPI describes a standard object's schema with "Rules across its parts:" and each rule. Range<T> is an object of start and end, each its item or null, with its rule in its description.

A sale's dates written as a DateRange's text can't be checked while compiling:

example
/// When the shop's winter sale runs.function winter_sale() -> DateRange {    return "2026-12-27/2027-01-03"}
text
error: `"2026-12-27/2027-01-03"` can't be checked as a `DateRange` while compiling --> main.jadpo:3:12  |3 |     return "2026-12-27/2027-01-03"  |            ^^^^^^^^^^^^^^^^^^^^^^^ canonical text  reason: A `DateRange` has rules across its parts, which every literal is checked against while compiling; text written where one goes is its canonical text, which isn't read until the program runs, so its rules can't be checked.  next: Write it out as `DateRange { ... }`, or read the text with `text.parse(..., as: DateRange)` and `attempt` (agent)  rule: type.invalid_literal  help: https://jadpo.dev/docs/errors/type.invalid_literalcheck failed: 1 error, 1 file

Written out, the sale's rules are checked while compiling:

example
/// When the shop's winter sale runs.function winter_sale() -> DateRange {    return DateRange {        start: "2026-12-27"        end: "2027-01-03"    }}

Markdown for agents: /docs/every-standard-type-meets-one-contract.md.Built from Jadpo main at a707efb.