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

Names, addresses and countries

On this page

These types hold people's names, postal addresses and email addresses, and the closed lists every app needs: countries and their subdivisions, languages, scripts, continents, nationalities. Each list is a built-in enum, read by its code and shown by its name in the reader's language.

Here a shop sends parcels abroad and prints each label for the person who packs it, greets its customers by name, and reads the countries a supplier's file gives by their three-letter codes:

example
type FileText = Text {}type UnknownCountry = Failure {    kind: FailureKind.invalid_value}/// A parcel's label, as its packer reads an address: in the order of the/// parcel's country for the packer's language.function parcel_label(parcel_to: PostalAddress, packer: Locale) -> PresentationText {    return text.format(parcel_to, locale: packer)}/// A customer's name as a greeting writes it in the reader's language.function greeting_name(customer: PersonName, viewer: Locale) -> PresentationText {    return text.format(customer, locale: viewer)}/// The country a supplier's file gives by its three-letter code.function supplier_country(raw: FileText) fails UnknownCountry -> Country {    var made_in: Country = attempt text.parse(raw, as: Country, form: CodeForm.alpha3) {        malformed: UnknownCountry    }    return made_in}
example
test "an address is in its country's order for the reader" {    var jp_parcel: PostalAddress = PostalAddress {        lines: ["丸の内2-7-2"]        locality: "千代田区"        region: "東京都"        postal_code: "100-8994"        country: Country.jp    }    assert parcel_label(jp_parcel, Locale.ja_jp) == "〒100-8994\n東京都千代田区\n丸の内2-7-2"    assert parcel_label(jp_parcel, Locale.en_gb) == "丸の内2-7-2\n千代田区, 東京都\n100-8994\nJapan"    var de_parcel: PostalAddress = PostalAddress {        lines: ["Platz der Republik 1"]        locality: "Berlin"        region: none        postal_code: "11011"        country: Country.de    }    assert parcel_label(de_parcel, Locale.de_de) == "Platz der Republik 1\n11011 Berlin"    assert parcel_label(de_parcel, Locale.en_us) == "Platz der Republik 1\n11011 Berlin\nGermany"}test "a name is in the order the reader's language gives" {    var countess: PersonName = PersonName {        given: "Ada"        family: "Lovelace"    }    assert greeting_name(countess, Locale.en_gb) == "Ada Lovelace"    assert greeting_name(countess, Locale.hu_hu) == "Lovelace Ada"    var yamada: PersonName = PersonName {        given: "太郎"        family: "山田"    }    assert greeting_name(yamada, Locale.en_gb) == "山田太郎"}test "a closed list reads its codes and its other code systems" {    var made_in: Country = attempt supplier_country("gbr")    assert made_in == Country.gb    assert supplier_country("UK") fails UnknownCountry    assert text.from(Country.in_) == "IN"    assert text.from(Country.gb, form: CodeForm.numeric) == "826"    assert text.format(Country.gb, locale: Locale.fr_fr) == "Royaume-Uni"    assert text.format(Language.ja, locale: Locale.en_gb) == "Japanese"    assert list.count(Country.all) == 249}

A name and an address are stored as a column per part, so a query can read a town or a country as it reads any column. They come back as they were written:

example
/// Someone the mailroom sends parcels to.entity Recipient {    id: Uuid    name: PersonName  // columns `name_given` and `name_family`    ship_to: PostalAddress  // a column per part, its lines a `text[]`    citizen_of: Nationality?  // its code, `IN`}
example
use mailroom/entities { Recipient }use mailroom/actions { readdress }test "a name and an address are kept part by part" {    var enrolled: Recipient = enrol(        PersonName {            given: "Mary"            family: none        },        PostalAddress {            lines: ["Flat 2", "14 Rue de Rivoli"]            locality: "Paris"            region: none            postal_code: "75004"            country: Country.fr        },        Nationality.in_    )    var kept_record: Recipient = attempt Recipient.get(enrolled.id)    assert kept_record.name == enrolled.name    assert kept_record.ship_to == enrolled.ship_to    assert kept_record.citizen_of == Nationality.in_    var readdressed: Recipient = readdress(        kept_record,        PostalAddress {            lines: ["2-7-2 Marunouchi"]            locality: "Chiyoda-ku"            region: "Tokyo"            postal_code: "100-8994"            country: Country.jp        }    )    var kept_after: Recipient = attempt Recipient.get(enrolled.id)    assert kept_after.ship_to == readdressed.ship_to}

The lists, and where they come from

Each list is shipped with the release, generated once into jadpo_std::standard::places_data:

  • Country is ISO 3166-1's 249 assigned codes as CLDR 48.2 carries them: its regular regions, less the exceptionally reserved AC, CP, CQ, DG, EA, IC and TA and the user-assigned XK. Its alpha-3 and numeric codes come from CLDR's code mappings. Its retired codes are the eleven ISO 3166-3 codes CLDR marks deprecated (AN, BU, CS, DD, FX, NT, SU, TP, YD, YU, ZR), shown by ISO 3166-3's English names, because CLDR names none of them.
  • CountrySubdivision is ISO 3166-2 as CLDR 48.2 carries it: 5,027 codes, with the 626 that CLDR marks deprecated retired.
  • Language is ISO 639-1's 183 codes, from the Library of Congress's ISO 639-2 list (2026-10-18). Its alpha-3 form is ISO 639-2/T. in, iw, ji, jw and mo are retired. A language with only a three-letter code isn't in the list.
  • WritingScript is ISO 15924's 224 codes (2026-10-18), less the private-use range. Its numeric form is ISO 15924's number.
  • Continent is UN M49's five regions, and Antarctica, which M49 lists beside them. It follows the authority's list, so the Americas are one region. Its codes are digits, so each variant's code is its own name, as a Weekday's is, and M49's number is its numeric form.
  • TextDirection is the enum locale.direction gives. Its codes are ltr and rtl, as HTML's dir attribute writes them.
  • Nationality takes Country's codes and names, as a passport names a nationality by its country. CLDR has no names for nationalities, and ICAO's codes for travel documents belong to IdentityDocument.
  • Currency also holds the six codes ISO 4217 has withdrawn since 2021 (ANG, BGN, CUC, HRK, SLL, ZWL), retired.

Closed lists are enums

A standard type is a built-in enum when its list is closed, published by an authority and rarely changes, as Locale, Zone and Weekday are. A huge or fast-changing list stays text with a pattern:, and is checked against its list where this page says so: IataAirport, PostalCode, HsCode, UnLocode, Icd10Code, SpdxLicence, Emoji.

  • HttpStatus stays a number, because its registry is open by design: an unknown code is still a status.
  • CallingCode stays text, because its codes are digits.
  • CountrySubdivision, about 5,000 codes with a few changes a year, is an enum. Validating an address needs the list anyway, CLDR names each subdivision, and its changes arrive with releases, as the time-zone database's do.

Jadpo doesn't match names such as "England" or "UK" to codes, in text.parse or in any operation. Which name means which code is a judgement, and it differs by locale: England is CountrySubdivision.gb_eng, not a country. A dropdown built from .all already asks a person to pick. A package can offer name matching if an import needs it.

Variants and .all

A variant is its code in lower case with _ for -: CountrySubdivision.gb_eng, as Locale writes its variants. A code that is a reserved word, or is all, takes a trailing _: Country.in_, Language.or_.

.all lists an enum's variants in its order, a closed list's by code, as a List of the enum. Retired codes are left out, because a dropdown never offers Yugoslavia. On an enum whose variants carry data, .all is type.unknown_enum_variant, and a variant called all is type.reserved_variant_name.

Retired codes

A retired code is refused where code writes it as a value, comparisons included: type.variant_retired. You can decline it, and the decline's reason says why the code still needs the old value. A match arm on a retired code isn't refused, because it only reads, and a match lists every variant anyway (Match).

Reading codes: form:

form: picks which of a list's code systems to read or write, as a CodeForm written out: CodeForm.alpha3, CodeForm.numeric. A value held in a variable is type.mismatch. A form the type has no codes in is type.temporal_option_unused, as an option that means nothing for a type is.

text.parse reads a form's code ignoring case. Where a retired variant shares a code with a current one, the code reads as the current one: ind is Language.id, not Language.in_.

At a boundary, such as a request, JSON, a CSV cell or a setting, a standard enum's code is read ignoring case and written back in its canonical case. No two codes in these lists differ only by case, so that is safe. An unknown code is invalid_value naming the field, with the rule variant. Spaces and other spellings are still refused.

Stored and shown

Stored: a closed list's column holds its code, as Currency's does: GB, and IN rather than in_. An object is stored as a column per part, named <field>_<part>. A part that may be none is a column that may be NULL. A PostalAddress's lines are a text[] column, and its country is its code.

Shown: a country, a continent, a language and a script by CLDR's name in the reader's locale, through ICU4X's display names, and a withdrawn code by its list's English name. A subdivision is shown by CLDR's English name in every locale, because CLDR names few in other languages and ICU4X doesn't include them. A continent is shown by CLDR's name for its M49 region, and Antarctica by its territory's name.

People's names

PersonName is a given name and, where the person has one, a family name. A single name is a given name, as CLDR holds a mononym, so no rule across the fields is needed. It has no single text form, and its JSON is its parts.

text.format applies CLDR's medium, formal pattern for referring to a person, from CLDR 48.2's person-name data:

  • the name's locale is the reader's when the name is in the reader's script; otherwise it is the likeliest locale for the name's script, which then formats it;
  • the reader's locale's lists give the order of the parts;
  • the name's locale gives the space between them, none in Japanese.

Jadpo applies the patterns itself. ICU4X's formatter is experimental, leaves a pattern's literals behind ("Ada Lovelace ,") and doesn't work out the order.

Addresses

PostalAddress is its lines as written, then a locality, a region and a postal code, each one line that may be none, and its Country. The region is text as the envelope writes it, not a CountrySubdivision. A dependent locality or a sorting code goes in the lines, and the recipient's name is your application's own field.

CLDR has no address formats, so text.format uses libaddressinput's, Google's formats from UPU data (Apache-2.0, commit 81eb962 of 2026-05-15):

  • a reader of one of the country's languages gets the country's own format, and anyone else its Latin-script format;
  • the text between two fields appears only where the fields beside it are there;
  • the country's CLDR name comes last for a reader in another country.

Nothing is put in capitals. UPU's capitals are for the envelope, not for people reading.

EmailWithName is a name that may be none and an Email. Its canonical text is RFC 5322's mailbox with RFC 6532's UTF-8: Ada Lovelace <ada@example.com>. The name is quoted only where it must be, and an address with no name is written alone. Only that text is read. Its JSON is its parts.

Lines and other text

A line, an object's part of text, is 1 to 200 characters with no control character and no space at either end, as an invoice number is. A literal is checked while compiling.

These are held as text:

  • GivenName and FamilyName are a line;
  • Username is [a-z0-9_.-]{1,64};
  • PhoneExtension is up to 20 digits, the longest libphonenumber reads after ext.;
  • Uprn, a UK property's unique reference number, is 1 to 12 digits with no leading zero, held as text so that no digit is lost.

Personal data and shapes

Personal data: a name, a given or family name and a username are identifying; a mailbox, an address, an extension and a UPRN are contact; a nationality is demographic. The lists of places aren't personal data.

No shapes: type.standard_exists matches none of the objects, because { name, email } and { first_name, last_name } are too common in applications' own types to be a sign that a type is one.

Markdown for agents: /docs/names-addresses-and-countries.md.Built from Jadpo main at a707efb.