Skip to Content
URNEncoding and comparing

Unreleased changes

These pages document main, which has changes 2.1.1 does not. npm install @evanion/urn gives you 2.1.1, documented here. Values on these pages are produced by running the current source on every build.

Encoding and comparing URNs

encodeNss in @evanion/urn turns a human-readable title, such as Brass: Birmingham, into a valid NSS, and equals compares two URNs according to the RFC 8141 §3.1 standard.

The Problem: “Messy” Titles and inconsistent Spelling

A URN’s Namespace Specific String (NSS) has a strict grammar. If you try to put a title like Brass: Birmingham directly into a URN, you run into two problems:

  1. Grammar Violations: Characters like spaces are prohibited in the NSS.
  2. Equivalence Drift: Different services might represent the same title differently (e.g., one uses %3A for a colon, another uses a literal :), making simple string equality (===) fail.

Solution: Percent-Encoding with encodeNss

To include characters that aren’t allowed (or should be consistent) in your identifiers, you must percent-encode them.

encodeNss leaves letters, digits and -._~ alone and percent-encodes every other character, writing each of its UTF-8 bytes as a % and two hex digits. decodeNss reverses this process.

Note that stringify and parse do not perform encoding automatically:

import { , , } from '@evanion/urn'; .('urn:game:Brass: Birmingham'); // -> false const = ('Brass: Birmingham'); ; // -> 'Brass%3A%20Birmingham' const = .(, 'game'); ; // -> 'urn:game:Brass%3A%20Birmingham' (.()); // -> 'Brass: Birmingham'

encodeNss also encodes the :, even though the NSS grammar allows it unencoded. This ensures that two services encoding the same title always produce identical NSS strings.

Comparing URNs Across Services

When URNs arrive from multiple services, you cannot rely on simple string equality. equals implements RFC 8141 §3.1 equivalence.

It treats URNs as equal if:

  • The scheme and NID match (case-insensitively).
  • The NSS matches exactly (case-sensitively).
  • The hex digits in percent-encoded characters match (case-insensitively).
.('URN:GAME:azul', 'urn:game:azul'); // -> true .('urn:game:Brass%3a%20Birmingham', 'urn:game:Brass%3A%20Birmingham'); // -> true .('urn:game:Brass%3A%20Birmingham', 'urn:game:Brass:%20Birmingham'); // -> false .('urn:game:Azul', 'urn:game:azul'); // -> false

Use equals wherever you need to verify that two differently spelled URNs refer to the same resource.

Carrying a URN in a URL Path

To use a URN as a segment in a URL path (e.g., /resources/urn:game:brass-birmingham), you must handle two layers of encoding.

The NSS must be encoded first, and then the entire URN must be encoded to ensure that characters like : and % do not interfere with the browser or the router.

import { , } from '@evanion/urn'; const = .(('Brass: Birmingham'), 'game'); const = `/games/${()}`; ; // -> '/games/urn%3Agame%3ABrass%253A%2520Birmingham' const = (.('/games/'.)); .(, ); // -> true

The %3A inside the NSS becomes %253A in the path. A single decodeURIComponent restores the URN. A second decode would turn %3A into :, potentially introducing characters that cause parse to throw.

Mint URNs outside RFC 8141

While the library defaults to the RFC standard, you aren’t limited by it. Schemes and separators shows how to give an @evanion/urn namespace class a custom scheme and separator, allowing you to create identifiers that read as dotted paths.

Last updated on