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

@twinfinity/entity-reference

v6.2.0-beta

Published

Parser and builder for Twinfinity entity references

Readme

@twinfinity/entity-reference

Parser and builder for Twinfinity entity references — the one string spelling every service uses to name an entity, such as twin~twin:<uuid>.file:<uuid>/entities/<gid>. It is the TypeScript twin of the server's ScopedEntityReference and agrees with it character for character; both are held to the same conformance corpus.

The package has no dependencies. Everything is a free function over frozen plain objects, so values stay compatible even when an application ends up with two copies of the package.

Installation

bun add @twinfinity/entity-reference

Usage

Parsing and formatting

import { tryParse, parse, format, leaf, stem } from '@twinfinity/entity-reference';

const reference = tryParse(
  'twin~twin:550e8400-e29b-41d4-a716-446655440000.file:6fa459ea-ee8a-4ca4-894e-db77e160355e/entities/3WoDmit2L9H8xguu5dNQPk'
);
if (reference !== undefined) {
  reference.domain; // 'twin'
  reference.components; // [{ type: 'twin', id: '550e…' }, { type: 'file', id: '6fa4…' }]
  reference.path; // ['entities', '3WoDmit2L9H8xguu5dNQPk']
  leaf(reference); // { type: 'file', id: '6fa4…' }
  stem(reference); // everything before the first '/'
  format(reference); // the original string; nothing is rewritten
}

parse('not a reference'); // throws a plain Error

tryParse returns undefined for input that is not well formed, and is the form to use for input you don't control. parse throws instead. There is no error class to catch.

Always decompose a reference with the parser, never with string operations. Splitting the whole string breaks across a path, because a path segment may contain :, ., ~ and spaces. Even on the stem alone, a split skips the character policy the parser enforces (text must be NFC, with no invisible or control characters), so use the parser. The grammar also bounds length at 512 code points.

Building

import { root, child, withPath, format } from '@twinfinity/entity-reference';

const twin = root('twin', 'twin', '550e8400-e29b-41d4-a716-446655440000');
const file = child(twin, 'file', '6fa459ea-ee8a-4ca4-894e-db77e160355e');
format(file); // 'twin~twin:550e8400-….file:6fa459ea-…'

const gid = withPath(file, 'entities', '3WoDmit2L9H8xguu5dNQPk');
format(gid); // 'twin~twin:550e8400-….file:6fa459ea-…/entities/3WoDmit2L9H8xguu5dNQPk'

root, child and withPath throw when the grammar refuses a part, such as an id containing . or /, rather than splicing it in, and when the result would exceed 512 code points. child also throws on a reference that has a path. Build references with these instead of interpolating strings: add a path with withPath, never by formatting a reference, appending to the string and parsing it again. withPath also extends a path that is already there, and parent undoes one appended segment.

For ids you don't control, tryRoot, tryChild and tryWithPath return undefined where the others throw. They take no version: build a reference with a version qualifier through root and child. tryChild and tryWithPath also accept undefined and null, returning undefined for either, so they chain without a check in between:

import { tryRoot, tryChild, tryWithPath } from '@twinfinity/entity-reference';

tryWithPath(tryChild(tryRoot('twin', 'twin', twinId), 'file', fileId), 'entities', gid); // EntityReference | undefined

Containment and cardinality

import { parse, isUnder, parent, prefixes, cardinalityOf } from '@twinfinity/entity-reference';

const twin = parse('twin~twin:550e8400-e29b-41d4-a716-446655440000');
const gid = parse(
  'twin~twin:550e8400-e29b-41d4-a716-446655440000.file:6fa459ea-ee8a-4ca4-894e-db77e160355e/entities/3WoDmit2L9H8xguu5dNQPk'
);

isUnder(gid, twin); // true
isUnder(twin, twin); // true; a reference is under itself
isUnder(parse('twin~twin:XY'), parse('twin~twin:X')); // false, where startsWith would say true

parent(gid); // drops the last path segment first, then components
prefixes(gid); // every ancestor, outermost first

cardinalityOf(gid); // 'many': a reference with a path names a collection
cardinalityOf(twin); // 'one'

Use isUnder for containment, not startsWith. A version (@5) counts as the same entity, so twin~twin:X@5 is under twin~twin:X. To compare regardless of version, use withoutVersions.

Bare domains

A reference can also name a whole domain, e.g. twin, which is how a customer-wide grant is spelled. This arm has its own type and its own parser, and the two parsers are disjoint: tryParse('twin') and tryParseDomain('twin~twin:X') both return undefined.

import { tryParse, tryParseDomain, forKnownDomain } from '@twinfinity/entity-reference';

const input = 'twin';
const parsed = tryParse(input) ?? tryParseDomain(input); // EntityReference | EntityDomainReference | undefined

forKnownDomain('twin'); // ok
forKnownDomain('folder'); // throws: well formed, but no such domain

tryParseDomain checks the shape only and accepts any lowercase word of three or more letters. forKnownDomain also checks the word against DOMAINS, the closed allowlist (twin, documents, activity, forum, permission).

Shapes

Parsing is layer 1 and knows no domain. Layer 2 checks a reference against the platform's shape table. It matches the whole chain and the id spellings, never the leaf alone:

import { parse, matchShape, ALL_SHAPES } from '@twinfinity/entity-reference';

matchShape(parse('twin~twin:550e8400-e29b-41d4-a716-446655440000'), ALL_SHAPES); // the twin shape
matchShape(parse('twin~twin:not-a-uuid'), ALL_SHAPES); // undefined: parses, but no shape claims it

addressable(reference, shapes) returns the reference itself when a shape claims it and undefined otherwise. It accepts undefined and null, so it closes a chain of try* builders: addressable(tryRoot('twin', 'twin', twinId), ALL_SHAPES). Like matchShape, it refuses a reference carrying a version qualifier the shape does not support, unless passed the 'ignore' version policy.

URLs

References travel as query parameters. toUriComponent percent-encodes one, and fromUriComponent decodes and parses it, throwing if the result is not well formed.

Queries

parseQuery / tryParseQuery read the entity query language: the stem grammar plus the wildcards type:* and * and the ... gap, e.g. forum~forum:<uuid>.... parse never accepts a wildcard, so a query can never be mistaken for a reference. See ADR 0005.

Specification

The grammar and the character policy are defined in Entity References. The shape table is in Entity Reference Spellings; ALL_SHAPES mirrors PS.BIM.Core.Model.EntityShapes. The rationale is in ADR 0004 (addressing) and ADR 0005 (queries).

Exports

The package has two export paths:

  • @twinfinity/entity-reference - Public API: parsing, formatting, building, containment, bare domains, shapes and queries
  • @twinfinity/entity-reference/internal/* - Internal modules (no stability guarantees)