npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@mailwoman/codex

v10.5.0

Published

Per-address-system postal reference data + branded types (USPS street suffixes, US ZIP codes). Pure, zero-runtime-dep — the shared canonical home for postal-system primitives the parser, resolver, and synthesis layers all reach for.

Readme

Address formatting takes more than string concatenation. Germany puts the house number after the street and the postcode before the city. Spain separates the street from the number with a comma. Japan writes the largest unit first, opens with a postal mark, and joins the whole admin run without spaces. A formatter that gets any of these wrong produces a plausible address from another country.

import { formatAddress } from "@mailwoman/codex/address/format"

formatAddress(
	{ house_number: "1600", street: "Pennsylvania Ave NW", locality: "Washington", region: "DC", postcode: "20500" },
	"US"
)
// → "1600 Pennsylvania Ave NW\nWashington, DC 20500"

formatAddress({ street: "Willy-Brandt-Straße", house_number: "1", locality: "Berlin", postcode: "10557" }, "DE", {
	singleLine: true,
})
// → "Willy-Brandt-Straße 1, 10557 Berlin"

formatAddress({ street: "Calle de Alcalá", house_number: "3", locality: "Madrid", postcode: "28014" }, "ES", {
	singleLine: true,
})
// → "Calle de Alcalá, 3, 28014 Madrid"

formatAddress(
	{
		postcode: "100-0005",
		region: "東京都",
		subregion: "千代田区",
		dependent_locality: "丸の内",
		house_number: "1-9-1",
	},
	"JP",
	{ singleLine: true }
)
// → "〒100-0005 東京都千代田区丸の内1-9-1"

The package covers 197 countries and needs no dependencies, network, or database. It runs in Node, the browser, and on an edge worker.

Installation

npm install @mailwoman/codex
# or
yarn add @mailwoman/codex

[!IMPORTANT] Requires Node.js ≥ 24.18.0. Pure ESM.

Format addresses

formatAddress(components, countryCode, options?)

formatAddress takes a partial map of component tags and a country code, and returns the envelope form by default. singleLine: true joins the lines the way that country joins them: ", " for most countries, " " for Japan and Korea, and no separator for the Chinese-script systems.

A missing value never leaves a dangling separator, because a connector renders only when something rendered on both sides of it:

formatAddress({ locality: "New York", postcode: "10118" }, "US", { singleLine: true })
// → "New York, 10118"   — not "New York, , 10118"

An unknown country returns "" instead of a guessed order. 55 of the 252 shipped country records have no usable layout, and the empty string reports that absence.

formatAddressRow(components, countryCode, options?)

formatAddressRow performs the same render and also reports which tags it printed and which it could not place. The second part is the reason this function exists. A country's layout can legitimately drop components, and searching the output string for each value cannot distinguish a dropped component from one whose text happens to appear inside another (Paris inside Rue de Paris).

formatAddressRow({ locality: "Paris", region: "Île-de-France", postcode: "75008" }, "FR", { singleLine: true })
// → {
//     raw: "75008 Paris",
//     components: { postcode: "75008", locality: "Paris" },
//     unplaced: ["region"],            // France absorbs the region — named rather than silently dropped
//   }

It returns null when the render produced no output.

canonicalKey(components, options?)

canonicalKey builds a deterministic match key for record linkage. The key is lowercased, stripped of diacritics and punctuation, and lists fields in a fixed order. Two records for the same address that differ only in spelling produce the same key.

import { canonicalKey } from "@mailwoman/codex/address/key"

canonicalKey({
	house_number: "123",
	street: "Main",
	street_suffix: "St",
	locality: "Portland",
	region: "OR",
	postcode: "97201",
})
// → "123|main|st|portland|or|97201"

The key excludes venue and attention, because those fields identify an organization and not an address.

renderAddress(layout, components)

renderAddress is the layer under formatAddress. It serves callers that need the pieces instead of a string, such as a syntax highlighter, a template that wraps each component in its own element, or an aligner that turns a render into labeled spans. It returns AddressPiece[], and each piece records the tag that produced it (or null for a separator).

Where the order comes from

Each country's layout is data, written as a tagged template that reads in the order it prints:

// %N%n%O%n%A%n%C, %S %Z
US: addr`${attention}
${venue}
${numberFirstStreet}
${locality}, ${region} ${postcode}
${country}`,

To check a country, a reviewer reads the shape of an address from that country instead of a nested call. The line skeletons are derived from libaddressinput, Google's address metadata (Apache-2.0). 186 countries are generated from it. The 11 locales that this project publishes models for are hand-authored and checked against real addresses on a committed board.

One rule governs rendering:

A node that renders no output removes itself, and its connector goes with it.

A connector between two slots needs a rendered slot on each side. A connector at a line's edge has only one side, so it binds to the single slot it touches. That is how Japan's 〒 disappears with an absent postcode while an interior space does not. When several connectors survive in a row, the strongest one wins. Punctuation outranks whitespace, so Calle Mayor, 12 keeps its comma when the street suffix is absent.

This package authors four things that libaddressinput does not model, each based on a measurement: the street line's spelling (183 countries space-join the number and the name, and 22 comma-join them), the post-office box line, the country line, and the sub-locality line.

Reference data

The formatter sits on top of the per-address-system tables. Callers can also use those tables directly.

import { us, fr } from "@mailwoman/codex"

us.lookupStreetSuffix("PKWY") // → { primary: "Parkway", standard: "Parkway", … }
fr.postcodePattern // → /^\d{5}$/

const zip: us.ZipCode = "94043" // branded rather than string alone

| System | Scope | | -------- | ------------------------------------------------------------------------------------ | | us | USPS street suffixes, directional abbreviations, ZIP code types, state abbreviations | | fr | La Poste postcode format, CEDEX conventions, département codes | | gb | Royal Mail postcode format, post town conventions | | de | Deutsche Post postcode format, Bundesland abbreviations | | ca | Canada Post postcode format, province abbreviations, the urban/rural FSA split | | au | Australia Post postcode format, state abbreviations |

Which country is this postcode from?

import { candidateSystemsForPostcode } from "@mailwoman/codex"

candidateSystemsForPostcode("94043") // → ["us", "de", "fr"]
candidateSystemsForPostcode("SW1A 1AA") // → ["gb"]

The first call returns three systems. This function tests the code's shape and does not check gazetteer membership, so returning all three is correct. The caller's country scope narrows the list. The code returns every locale because it has no evidence for choosing one.

Postcode granularity: three tiers, each earned by measurement

One postalcode placetype covers systems that are not comparable. An Irish Eircode identifies a single address, and an Australian postcode identifies a locality. Most of the world sits between them, and the question that changes an answer is narrower: is this code finer than the locality that contains it?

The answer depends on a country's administrative geography and not its postal system, and code length does not predict it. France and Germany both use five digits and land on opposite sides.

import { isUnitGradePostcodeHit, areaPostcodeLeadsLocality } from "@mailwoman/codex"

isUnitGradePostcodeHit("N7 0BT", "n70bt") // → true  (GB unit, and the resolver hit the FULL code)
isUnitGradePostcodeHit("N7 0BT", "n7") // → false (the resolver answered with the outward district)
areaPostcodeLeadsLocality("DE") // → true  (a Gemeinde can be the size of Berlin)
areaPostcodeLeadsLocality("FR") // → false (one code postal often spans several communes)

| Tier | Members | What earned it | | --------------------------------------------------------------------------------- | --------------------------------- | ------------------------------------------------------------------------- | | unit-grade (UNIT_GRADE_POSTCODE) | NL PC6, GB unit, CA urban LDU | measured against rooftop truth — GB 38 m median, CA urban 78 m | | area, but still finer than the locality (AREA_POSTCODE_FINER_THAN_LOCALITY) | DE | full-panel measurement: 5.84 km → 1.24 km p50, better on every percentile | | area (the default) | everything else | the locality-first convention |

A system joins a tier only after a measurement, never because its codes look precise. Canada shows why. Its urban LDU is unit-grade. Its rural LDU measures 2.08 km against the locality's 929 m and is excluded. Canada Post already marks the difference with a 0 in the second character, so the code itself identifies its type before any lookup runs. The pooled Canadian number reads 0.10 km and looks like a uniform improvement, but it mixes two populations. These tables exist to prevent a tier claim that averages two granularities.

Layout and convention coverage

The layouts and the parsing conventions cover different numbers of places. The difference is intentional and does not represent a backlog.

| Table | Keyed by | Coverage | | ------------------------------------------------------------- | ------------ | ---------------------------------------------- | | ADDRESS_LAYOUTS (./address-layouts) | country code | 197 countries, 55 of 252 records lack a layout | | ADDRESS_SYSTEM_CONVENTIONS (./address-system-conventions) | SystemCode | two rows: fr, gb | | SystemCode (./postcode-systems) | — | ten: us de fr es it ca gb jp au nz |

A layout defines how to print an address, so a country needs one before it can be served. A conventions row defines what is ungrammatical in that system, and the decoder applies it as a hard mask before Viterbi. A wrong row therefore breaks parses that currently work. Each row records the measurement that justified it. An absent row means "no constraints known" and never "no constraints exist". Contributors can add a country to the layouts directly. A conventions row requires a measured receipt.

Where a convention's provenance comes from

A UPU S42 template is a country-approved crosswalk of national addressing semantics into shared international elements. It is stronger than a recommendation, because the country approved it, and weaker than the native authority, because USPS Pub 28, Royal Mail and NF Z 10-011 exist independently and can drift after the approval. Provenance for a convention is therefore a claim with one observation per source kind: national-postal-authority (authoritative), upu-pas (description), upu-s42 (approved crosswalk), libaddressinput (implementation), opencage-address-formatting (rendering) and mailwoman-board (observed). A disagreement between sources is kept as it stands rather than reconciled into one basis field, and ObservationStance distinguishes silent, unread, contradicts and the absence of an observation.

S42 templates are restricted to registered users, so every upu-s42 observation reads unread until someone with a UPU account reads the template. The eight jurisdictions with a template and no layout (BW, DJ, KM, LC, QA, TT, UG and ZW) have a null libaddressinput fmt, so the template is their only source of order.

Postal regimes the country code does not name

Three families of addresses need a unit that is finer than, or different from, the ISO country code. codex cannot express any of them today (#2323):

  • One code, several postal regimes. SH covers Saint Helena, Ascension Island and Tristan da Cunha. Those three territories format addresses differently.
  • BFPO identifiers. BFPO identifiers are routing instructions and do not represent a GB locality plus postcode. The American half of this family is modeled in lib/us/military-address.ts, which covers APO/FPO/DPO and the AA/AE/AP pseudo-states. The British half is not modeled.
  • Narrative addresses. Costa Rica, Nicaragua and parts of Panama and the Caribbean build an address from a landmark, a direction and a distance. That is a separate grammar and not a variant of the street grammar the layouts assume.

Out of scope

Research state, source URLs and license terms for address data belong elsewhere. Whether this project has verified an open national address register for Chile is a fact about the data backlog and not about how Chileans write an address. That information lives in @mailwoman/corpus.

What this package does not do

This package does not parse. The mailwoman parser uses sequence labeling to turn "1600 Amphitheatre Pkwy, Mountain View CA" into components. A separately installed small transformer encoder solves that task. This package does not geocode either. It covers the inverse direction and the reference tables, and it stays dependency-free so that a consumer who only wants to print an address does not download a model.

Design

  • Zero runtime dependencies. The package is pure TypeScript data and a small evaluator, with no database, I/O, or network access. It is suitable for bundling into browser and edge environments.
  • Branded types. ZIP codes, postcodes and abbreviations have nominal types, so the type system catches locale mismatches at compile time.
  • An absence is reported, never invented. A country with no layout returns "", and a component that the layout has no slot for is listed in unplaced.
  • One definition. The parser, the resolver, the corpus synthesis layer and the matcher all import these tables instead of each keeping a copy.

The normative tier (codex vs the libpostal dictionaries)

Mailwoman maintains two closed-class vocabularies that overlap intentionally and must stay separate:

  • Codex is normative. It reproduces USPS Pub-28 (and each system's equivalent) verbatim: the canonical word, every recognized variant, and the one preferred abbreviation. Its consumers need precision. They are corpus synthesis recipes, the eval harness's invariance transforms, and formatting, since rendering N or North requires knowing which form the authority prints.
  • The libpostal dictionaries (core/data/libpostal/dictionaries/, Pelias lineage) are descriptive. They list everything people write, including forms that no authority recognizes (en/directionals.txt lists lower/upper/central). Their consumers need recall. They are evidence-lexicon curation laws, street decomposition for training gold, and the street-morphology FST.

Descriptive forms in codex would corrupt formatting. A descriptive list limited to normative forms would weaken the evidence guards. The two vocabularies answer different questions, so they stay in different tables.

Related

License

AGPL-3.0-only