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

@console-one/address

v0.1.1

Published

Multi-dimensional addresses for objects: each entity carries a stable anchor plus optional secondary coordinates (tenant, environment, version, parent). Parses and serializes a compact textual form. Zero dependencies.

Downloads

238

Readme

@console-one/address

Multi-dimensional addresses for objects.

A single object often has more than one identity at the same time: a path inside a tenant, a path inside an environment, a version on a release axis, a parent pointer for orchestration. Address collapses those identities into one ordered, serializable container so they travel together and survive a round-trip through string form.

user:/u/42@2;org:/o/acme;env:/prod@1

That string is one ObjectAddress. The first segment is the anchor — immutable, the thing the object "really is." The rest are secondary coordinates, each in its own dimension (user, org, env). Anything that knows about a dimension can ask the address for its path, version, and parent payload in that dimension; anything that doesn't can ignore them and just use the anchor.

Zero dependencies. Pure TypeScript. ESM.

Install

npm install @console-one/address

When to reach for this

Plain string IDs work fine until one of these starts to hurt:

  • The same logical entity needs to be located in several namespaces at once (tenant + environment + version axis), and you keep building parallel maps to keep them in sync.
  • You need a stable, parseable wire form for that compound identity (logging, caching keys, message envelopes).
  • Some callers care about the version, others don't — and you want both reads to be one-liners against the same value.
  • You want to "fork" an entity into a sibling location: same anchor, different env or version, without manually stitching the new identity together.

If a string is enough, use a string. If you've reached for a Map<TenantId, Map<EnvId, ...>>, you're in this package's territory.

Quick start

import { ObjectAddressImpl, Address, Addressed } from '@console-one/address';

// Construct directly
const a = new ObjectAddressImpl({ dim: 'user', path: '/u/42', version: 3 });
a.set({ dim: 'org', path: '/o/acme' });
a.set({ dim: 'env', path: '/prod', order: 1 }); // place env between user and org

a.toString();             // "user:/u/42@3;env:/prod;org:/o/acme"
a.getPath('org');         // "/o/acme"
a.isVersioned('user');    // true
a.dimensions();           // ["user", "env", "org"]

// Round-trip through a string
const b = ObjectAddressImpl.parse('user:/u/42@3;org:/o/acme');
a.equals(b, 'user');      // true (same dim+path+version on user)

// Tag a domain object with its address
type User = Addressed<'User', { username: string }>;
const ada: User = Addressed.create('User', { username: 'ada' }, [
  { dim: 'user', path: '/u/ada', version: 1, parent: { id: 'u1' } },
  { dim: 'org', path: '/o/eng' },
]);
ada.address.toString();          // "user:/u/ada@1;org:/o/eng"
ada.address.getParent('user');   // { id: "u1" }

Wire format

<dim>:<path>[@<version>][;<dim>:<path>[@<version>]]*
  • The first segment is the anchor and cannot be removed or reordered.
  • Segments are separated by ;. Order is preserved across parse / stringify.
  • @<version> is optional. The version slot accepts any number; non-numeric tails parse as "no version."
  • Empty input yields an addressless container (an anchor is materialised on first read at the default dimension).

Address.parse('user:/a/b@3') and Address.stringify(addr) are exact inverses on well-formed input.

Public surface

Single coordinates

  • Address — one {type, dim, path, version?, parent?, order?} record. Address.create, Address.parse, Address.stringify, Address.clone, Address.compare, Address.describes.
  • AddressLike — the loose "shape" used as input. AddressLike.parse(str) does the textual decode without building a full Address. AddressLike.describes(x) is the structural type guard.
  • AddressBuilder — fluent builder for an Address (.addDimension, .addPath, .addVersion, .addParent, .addOrder, .build).
  • AddressPath / RelativeAddressPath / AbsoluteAddressPath — string aliases with type guards. They're nominally distinct but structurally identical (both are strings); the split exists to document author intent at call sites.

Multi-dimensional containers

  • ObjectAddress / ObjectAddressImpl — the multi-dimensional container. The interface is read-side; the impl carries the mutators (set, remove, fork, setParentPayload, [Symbol.iterator]).
  • ObjectAddressImpl.parse(s) / .fromJSON(...) — accept a stringified form, a single Address, an array of addresses, or a {dim: {path,...}} map.
  • AddressSet — the toJSON() shape: {type: 'addressset', addresses: {[dim]: AddressLike}}. Useful as a wire form when you want a JSON object instead of the colon/semicolon string.
  • dim(name) / dims(...names) — small helpers for building dimension selectors used by fork().

Relative / pending coordinates

  • RelativeAddressSet — wire-form list of relative coordinates.
  • RelativeObjectAddress / RelativeObjectAddressImpl — a builder-style container of relative coordinates, indexable by dimension, used as input to fork.

Tagged objects

  • Addressable{...item, location | address} mixin for "give this domain object an identity later." Addressable.getAddress(item) resolves either the eagerly-parsed address or a lazily-generated one.
  • Addressed{type, address, ...child} for "give this domain object an identity right now." Addressed.create(type, child, refs) builds the full record.

Forking

fork() produces a new ObjectAddress derived from an existing one. The argument selects which dimensions to carry over and (optionally) overrides their paths or versions:

const base = ObjectAddressImpl.parse('user:/u/42@2;org:/o/acme;env:/prod@1');

base.fork();                              // deep copy of all three dims
base.fork([dim('env'), dim('user')]);     // subset, in the order given
base.fork('/elsewhere');                  // single new default-dim address
base.fork({ env: { path: '/staging' } }); // override env's path on the fork
base.fork(RelativeObjectAddressImpl.create({ dim: 'env', path: 'ignored' }));
                                          // pick dims via a relative address container

Override paths win; missing fields fall back to the corresponding dim on the base.

Layout

src/
├── address.ts               # Address, AddressLike, AddressBuilder
├── addresspath.ts           # path string aliases + guards
├── addressset.ts            # AddressSet — JSON wire form
├── addressable.ts           # Addressable mixin + getAddress helper
├── addressed.ts             # Addressed<Type, Child> tag
├── dimension.ts             # DefaultDimension, dim()
├── objectaddress.ts         # ObjectAddress, ObjectAddressImpl, fork, dims()
├── relativeaddressset.ts    # RelativeAddressLike, RelativeAddressSet
├── relativeobjectaddress.ts # RelativeObjectAddress, RelativeObjectAddressImpl
├── util.ts                  # uuid, splitFirst, splitLast (vendored)
├── smoke.ts                 # end-to-end test
└── index.ts                 # public surface

Notes on behavior

  • The anchor is immutable. Setting a new dimension with order: 0 throws; calling remove(anchorDim) returns false. Fork the address and re-anchor instead.
  • set is upsert. Calling set({dim, path}) on an existing dim merges field-by-field — only fields you provide overwrite the prior value. Calling it on a new dim requires path (we don't materialise pathless secondary refs).
  • get(dim) falls back to the anchor. If the requested dim isn't present, get returns the anchor instead of undefined. This keeps the read path branchless on the caller side; if you need a presence check, use has(dim).
  • fork(termArray) honours overrides. A term's path and version win over what's on the base. Dimensions you don't list aren't carried over.
  • getRoots() returns defensive clones. Mutating the returned array's entries does not affect the source.
  • Address.parse is total. Malformed-but-string input yields a best-effort Address; non-numeric versions become undefined. There is no throwing parser.
  • The string form is order-stable. parse then stringify is a no-op on a well-formed input.

Smoke test

npm run smoke exercises the package's full surface against node:assert. Anchor immutability, parse/stringify round-trips, fork variants, defensive copies, address-set JSON shape, the Addressable and Addressed mixins, and every code path on RelativeObjectAddressImpl. The build aborts publish if any assertion fails.

Known limitations

  • RelativeAddressPath and AbsoluteAddressPath are both string and structurally indistinguishable — the type guards return the same answer. The split is documentation, not enforcement.
  • fork accepts a {[dim]: {path, version, ...}} map, but the typed signature treats values as Omit<RelativeAddressLike, 'dim'>. In practice you'll often as any the value object until the input type is narrowed.
  • Versions are bare numbers. There is no opinion about monotonicity, semver, or vector clocks; comparison is ===.

License

MIT.