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

dot.paths

v0.1.1

Published

Fast, type-safe dot-notation path types for TypeScript: enumerate every path into a type (Paths) and resolve the value at any path (Get), tuned for compiler performance.

Downloads

60

Readme

dot.paths

Fast, type-safe dot-notation path types for TypeScript.

  • Paths<T> — a union of every valid dot-notation path into T.
  • Get<T, P> — the value type at a path (loose P, maximally fast).
  • GetStrict<T, P>Get with P constrained to valid paths (autocomplete + invalid-path errors).

Zero runtime. Zero dependencies. Pure type-level utilities, tuned for compiler performance — the resolver is ~40% faster to type-check than a conventional keyof-cascade, and the gap widens as your types grow.

import type { Paths, Get, GetStrict } from 'dot.paths'

interface State {
  user: { name: string; age: number }
  todos: { id: string; done: boolean }[]
}

type P = Paths<State>
//   ^? 'user' | 'user.name' | 'user.age' | 'todos' | `todos.${number}`
//      | `todos.${number}.id` | `todos.${number}.done`

type Name = Get<State, 'user.name'> //          string
type Todo = Get<State, `todos.${number}`> //    { id: string; done: boolean }
type Done = Get<State, `todos.${number}.done`> // boolean

// Strict: invalid paths are a compile error, valid ones autocomplete.
type Bad = GetStrict<State, 'user.xyz'>
//                          ~~~~~~~~~~~  Type '"user.xyz"' does not satisfy …

Install

npm i -D dot.paths

(Type-only — install as a dev dependency.)

API

Paths<T, Opts?>

Union of every dot-notation path into T.

  • Walks objects, arrays (`${number}` segments), tuples, optionals, nullable unions, and recursive types.
  • Stops at "leaf" values (primitives, Date, RegExp, Map, Set, Promise, functions).
  • Depth defaults to 8. Raise it for deeply nested/recursive types:
type Deep = Paths<MyTree, { depth: 12 }> // up to 16

The depth cap is what keeps recursive types (linked lists, trees, JSON) from exploding the compiler — it bounds path length rather than failing.

Get<T, P extends string>

Value type at path P. P is loosely typed (string) — this is the fast primitive; use it internally and for one-off lookups. Tail-recursive, so arbitrarily deep paths are fine.

GetStrict<T, P extends Paths<T>>

Same resolution, but P is constrained to Paths<T> — you get path autocomplete and invalid paths become compile errors. The constraint forces a Paths<T> computation, so prefer it at API boundaries that want the DX rather than in hot inner code. (If your paths already come from Paths<T>, this is effectively free.)

Note: a naive Get<T, P extends Paths<T>> does not compile — the recursive tail can't be proven a sub-path. GetStrict is the wrapper that makes it work (strict boundary, loose recursion).

Performance

These types are built to be cheap for tsc. Highlights from the included benchmark (vs a conventional implementation, same fixtures, fair same-session A/B):

| Workload | Instantiations | Check time | | --------------------- | -------------: | ------------: | | Get (resolution) | −5% | −50% | | Paths (enumeration) | −15% | −6% | | realistic round-trip | −10% | −40% | | recursive types | −2% | no regression |

The Get check-time win grows with type size (−50% at the benchmark's 2× scale, −60% at 3×), because the core trick — resolving a segment by testing the key intersection K & keyof T against never — is far cheaper for the checker to relate than a K extends keyof T conditional, and never materializes the indexed-access value type on a miss.

Correctness is locked: every optimization is proven type-identical to a frozen reference implementation across objects, arrays, tuples, optionals, nullable/discriminated unions, exotic leaves, and recursive types (including self-referential JSON maps) — see test/equivalence.ts.

Running the benchmarks

npm run typeperf:gen        # generate the pathological fixtures
npm run typeperf            # measure Paths / Get / combined / recursive
npm run typeperf -- --save  # save current numbers as the baseline
npm run typeperf:ab         # A/B the optimized impl vs a frozen original

typeperf runs tsc --extendedDiagnostics against isolated scenarios and reports the deterministic counters (Types, Instantiations) plus min-over-N timers. typeperf:ab interleaves the optimized and original implementations in one process for a drift-free timing comparison.

How it works (the short version)

  • Paths gates on T extends _Leaf first (primitives — the common case — terminate in one cheap check), drops a redundant T extends object guard, uses an O(1) decrement for the depth counter, and keeps NonNullable per key (dropping it is faster on flat types but slower on recursive nullable ones).
  • Get is two branches: split on the first ., then resolve the segment with _Index. _Index is the fused T[K & keyof T], with a fallback to the array element type that only fires on a key miss — which is exactly what keeps recursive unions like JSON resolving correctly while normal lookups stay fast.

License

MIT