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

@archont561/pathway

v0.1.2

Published

One Path value for native, virtual and URL paths in TypeScript

Readme

@archont561/pathway

One Path value for native filesystem paths, virtual paths and URL paths — lexical, cross-platform and inspired by Python's pathlib.

Every operation is a pure, synchronous function of its arguments: no filesystem access, no working directory, no environment variables, no clock, no randomness. The same input produces the same result on every host for the same explicit path style, and every value is canonical — construction collapses duplicate and trailing separators rather than keeping the input byte for byte.

v0.1.0 contains the lexical core only. Filesystem I/O, traversal, storage adapters and text codecs are not part of this release; see Scope.

Install

bun add @archont561/pathway

The package is ESM-only and runs on any ES2022 runtime: Bun ≥ 1.4.2 and Node ≥ 18 on Windows, macOS and Linux, and modern browsers. See Runtimes and portability.

Quick start

import { Path } from '@archont561/pathway';

const path = Path.posix('/srv/app/logs/2026-10-17.log');

path.kind; // 'native'
path.style; // 'posix'
path.parts; // ['srv', 'app', 'logs', '2026-10-17.log']
path.name; // '2026-10-17.log'
path.stem; // '2026-10-17'
path.suffix; // '.log'
path.parent.toString(); // '/srv/app/logs'
path.isAbsolute; // true

Path.posix('/srv/app').join('logs', 'app.log').toString();
// '/srv/app/logs/app.log'

Path.posix('/srv/app/./logs/../config').resolve().toString();
// '/srv/app/config'

Path.posix('/srv/app/logs').relativeTo(Path.posix('/srv/app')).toString();
// 'logs'

Path.windows(String.raw`C:\Users\me\file.txt`).parts;
// ['Users', 'me', 'file.txt']

One Path, three domains

The domain is explicit at construction and fixed for the life of the value. path.kind reports it, and path.style reports the native style.

| Factory | kind | style | Root forms | | :-- | :-- | :-- | :-- | | Path.native(input, style?) | 'native' | 'posix' or 'windows', host platform by default | POSIX /; Windows C:\ drive roots, \\server\share\ UNC roots, plus drive-relative C: and root-relative \ | | Path.posix(input) | 'native' | 'posix' | / | | Path.windows(input) | 'native' | 'windows' | C:\, \\server\share\, \, C: | | Path.virtual(input) | 'virtual' | undefined | / | | Path.url(input) | 'url' | undefined | / |

Path.posix('/a/b').kind; // 'native'
Path.posix('/a/b').style; // 'posix'
Path.windows('C:/a/b').style; // 'windows'
Path.virtual('/a/b').kind; // 'virtual'
Path.url('/a/b').kind; // 'url'

Path.virtual is POSIX-like on every host, with the backslash as an ordinary character. Path.url accepts an RFC 3986 path reference only and preserves percent-encoding verbatim. Because the style is explicit, Windows rules can be created and tested on Linux and vice versa.

Domains never mix: a Path keeps its kind and style through every operation, equals() is false across domains, and relativeTo() across domains — or across native styles — raises PathError.

Operations

| Member | Result | | :-- | :-- | | native, posix, windows, virtual, url | Construct a value in canonical form; toString() and toJSON() return that string | | kind / style | The domain, and the native style (undefined for virtual and URL paths) | | parts | The segments, without the root and without . segments. The url domain keeps empty segments, because /a//b and /a/b are different URLs | | name | The last segment, or '' | | stem / suffix | The name split at its last dot; a leading dot does not start a suffix (.bashrc has none) | | parent | The path without its last segment. A path with no segments is its own parent; a has parent . | | isAbsolute / isRelative | Whether the value starts at a root. A Windows drive-relative C:a and a root-relative \a are not absolute | | join(...segments) | Appends segments; an absolute segment replaces the accumulated path. Does not resolve dot segments | | resolve(...segments) | join(...segments), then . and .. removal. .. at a root is dropped; leading .. of a relative path are kept | | relativeTo(base) | The relative path from base, or . when the two are equal. Both values must share a domain and style | | equals(other) | True when both values share a domain and style and resolve to the same string |

Construction is canonical but keeps . and .. segments: removing .. lexically is not the same as resolving it on a filesystem, because a parent directory may be a symlink, so resolve() is an explicit call. resolve() is lexical: it never follows symlinks and never checks that a path exists. Equality is byte-exact — nothing is case-folded.

relativeTo() resolves both operands before comparing them, so 'a/./b'.relativeTo('a/b') is '.' and 'a/b/../c'.relativeTo('a/x') is '../c'.

Errors

  • A non-string path, an empty segment, or an argument that is not a Path raises a TypeError.
  • An invalid value or an impossible operation raises the exported PathError: an empty path, a malformed Windows UNC root, a URL path that carries a scheme, authority, query or fragment, relativizing across domains or native styles, and relativizing paths that do not share a root.
import { Path, PathError } from '@archont561/pathway';

try {
  Path.posix('/a').relativeTo(Path.virtual('/a'));
} catch (error) {
  error instanceof PathError; // true
}

Runtimes and portability

The library is plain ES2022 TypeScript: it imports no Node built-in, reads no ambient state, and ships one ESM entry with no runtime-specific builds.

| Runtime | Default style from Path.native(input) | | :-- | :-- | | Bun ≥ 1.4.2 on Windows | 'windows' | | Bun ≥ 1.4.2 on macOS or Linux | 'posix' | | Node ≥ 18 on Windows | 'windows' | | Node ≥ 18 on macOS or Linux | 'posix' | | Browser, or any runtime without process | 'posix' (never throws) |

Only Path.native(input) without a style consults the host. Path.posix, Path.windows, Path.virtual and Path.url are host-independent, so a Windows path built on Linux is a Windows path, and every explicit style gives a byte-identical result on every host. That is what makes the Windows matrix testable on a Linux runner, and it is the recommended way to write portable code: pass the style.

Two deliberate limits, both documented rather than hidden:

  • Case is not folded. Comparison is byte-exact on every host. Native Windows filesystems and the default macOS filesystem are case-insensitive, so two values differing only in case can name the same file.
  • Unicode is not normalized. Values are code-unit exact, so NFC and NFD spellings of the same name are different values. macOS stores names decomposed.

Both belong to the future filesystem layer, where the encoding and the filesystem identity are observable. A browser has no native filesystem; virtual and url values are the natural fit there, and native values stay lexical and usable.

Scope

| Concern | v0.1.0 | | :-- | :-- | | Lexical path operations, one Path value with three domains | In scope | | Any ES2022 runtime (Bun, Node, browsers), TypeScript-only | In scope | | Filesystem I/O, traversal, storage adapters, codecs | Deferred to a later release | | Sandboxing, locks, signals and cancellation | Out of scope: nothing is read or written, and lexical operations are synchronous |

Development

pixi install --locked
pixi run test
pixi run typecheck
pixi run build
pixi run lint

Pre-push checks use Bun's change-aware test selection against origin/main; if that base ref is not available yet, the hook runs the full test suite.