Skip to Content
URNSeveral namespaces

URNs from several namespaces

@evanion/urn 2.1.0 and its patch releases up to 2.1.1, the version on npm. Values on this page were produced by running the source at f9b1d93, a later commit than the 2.1.0 tag, and are not re-executed.

In a large system, you often deal with multiple types of identifiers. For example, an e-commerce shop needs to handle both Game URNs (identifying board games) and Order URNs (identifying customer orders).

While both are URNs, they belong to different namespaces. If you try to read an order URN as a game URN, your application should be able to identify it as “foreign” rather than simply failing or, worse, treating it as a game with a very strange ID.

The Problem: Namespace Collision and Confusion

When your system handles multiple resource types, using a single generic parser can be dangerous. If you pass urn:order:123 into a function that expects a GameURN, a naive implementation might just strip urn: and treat order:123 as the game’s ID.

This leads to:

  • Database Pollutants: Accidentally searching for a game with the ID order:123.
  • Silent Failures: Logic that assumes any valid URN is of the “correct” type.
  • Brittle Logic: Manual string slicing to check if an ID “looks like” an order.

Solution: Namespace-Specific Classes

The safest way to handle different identifier types is to declare a separate namespace class for each one. This gives each namespace its own class in your application.

By creating GameURN and OrderURN, you can use the belongsToNamespace helper to filter a mixed list of identifiers instantly:

class extends { static override readonly = 'order'; } .('spirit-island'); // -> 'urn:game:spirit-island' .('order-2026-0042'); // -> 'urn:order:order-2026-0042' const = ['urn:game:spirit-island', 'urn:order:order-2026-0042', 'hive']; .(() => .(, 'game')); // -> ['urn:game:spirit-island']

belongsToNamespace compares the scheme and NID of the URN without regard to case. This means URN:GAME:azul is correctly recognized as a game URN. If a string is not a valid URN at all (like 'hive'), the function simply returns false without throwing an error.

Handling “Foreign” URNs Safely

What happens when you use a specific namespace class—like GameURN—to parse a URN that belongs to a different namespace?

GameURN.parse is designed to be “safe.” It will strip its own NID if it finds it, but if it encounters a foreign NID, it simply leaves it at the front of the Namespace Specific String (NSS).

.('urn:game:azul'); // -> { urn: 'urn', nid: 'game', nss: 'azul' } .('URN:GAME:azul'); // -> { urn: 'URN', nid: 'GAME', nss: 'azul' } .('urn:order:order-2026-0042'); // -> { urn: 'urn', nid: 'order', nss: 'order:order-2026-0042' } .('baize:game:azul'); // -> { urn: 'baize', nid: 'game', nss: 'baize:game:azul' }

The foreign NID (e.g., order:) remains part of the nss, and nid reports order. An NSS may itself contain a :, so check nid, or call belongsToNamespace, before you look up the nss as a game slug.

Dynamic Namespaces with the Base URN Class

If your application supports a dynamic or unknown number of namespaces (e.g., a gateway service), you can use the base URN class instead of creating subclasses.

The base URN class provides a generic stringify method that takes the NID as an argument. This is ideal for services that act as a pass-through for many different types of URNs.

.('azul'); // -> 'urn:game:azul' .('azul', 'game'); // -> 'urn:game:azul' .('azul', 'game', 'baize'); // -> 'baize:game:azul' .({ : 'azul', : 'game' }); // -> 'urn:game:azul' .(.('urn:game:azul')); // -> 'urn:game:azul'

To extract the actual ID from a URN regardless of its namespace, use extractId. This helper strips the NID and returns only the unique identifier:

.('urn:game:azul').; // -> 'game:azul' .('urn:game:azul').; // -> 'game' .('urn:game:azul'); // -> 'azul'

Comparing Namespaces Across URNs

Sometimes you just need to know if two URNs belong to the same category, regardless of what that category is. sameNamespace compares the scheme and NID of two URNs. If they match (case-insensitively), the URNs are in the same namespace.

.('urn:game:azul', 'URN:Game:hive'); // -> true .('urn:game:azul', 'urn:order:order-2026-0042'); // -> false .('azul', 'azul'); // -> false

Next steps: Validating input

Knowing how to separate namespaces is the first step. The next is ensuring that the URNs you receive from outside your system are well-formed before you attempt to parse them.

Checking URN input covers how to use isValidFormat to screen input and distinguish between a malformed URN and one that simply has a bad part.

Last updated on