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

placr

v0.0.5

Published

A lightweight library for navigating hierarchical location data (countries, states, cities, postal codes) with persistent state management. Works with Node.js and Bun.

Readme

placr

A lightweight, strongly-typed library for navigating hierarchical location data with persistent state management.

npm version License: MIT

Features

  • Navigate through location hierarchies (countries, states, cities, postal codes)
  • 17 navigation formats with strongly-typed returns
  • Conditional navigation methods - loadNext() and loadPrevious() only appear when available
  • Persistent state with SQLite (works offline)
  • Built-in data for 250+ countries, 5000+ states, 150000+ cities
  • Postal code support for US, CA, GB, DE, JP, FR, IN, AU, NL, IE
  • Works with Node.js (v22+) and Bun
  • TypeScript support out of the box

Installation

npm install placr
bun add placr

Quick Start

import { Placr } from 'placr';

// Create a new instance - returns strongly typed Placr<'city-state-country'>
const nav = await Placr.create('city-state-country', 'US');

// Get the current location - nav.nav is typed as CityStateCountryNav
const current = nav.getNav();
console.log(current.nav);
// { city: 'New York', state: 'New York', stateShort: 'NY', country: 'US', countryShort: 'US' }

// Quick access via placeholder (address format)
console.log(current.placeholder);
// "New York, NY, US"

// Conditional navigation - loadNext() only exists when hasNext is true
if (current.hasNext) {
  const next = current.loadNext();
}

// Mark current as complete
nav.markComplete();

Type-Safe Navigation

Each format returns a specifically typed Nav object with only the fields for that format:

// 'zip' format returns ZipNav
const zipNav = await Placr.create('zip', 'US');
const result = zipNav.getNav();
result.nav.zip;     // ✓ string
result.nav.country; // ✓ string
result.nav.city;    // ✗ TypeScript error - doesn't exist on ZipNav

// 'city-state-country' format returns CityStateCountryNav
const cityNav = await Placr.create('city-state-country', 'US');
const cityResult = cityNav.getNav();
cityResult.nav.city;         // ✓ string
cityResult.nav.state;        // ✓ string
cityResult.nav.stateShort;   // ✓ string
cityResult.nav.country;      // ✓ string
cityResult.nav.countryShort; // ✓ string

Conditional Navigation Methods

Navigation methods are only available when they can be used:

const nav = await Placr.create('city-state', 'US');
const current = nav.getNav();

// TypeScript knows exactly what's available based on hasNext/hasPrevious
if (current.hasNext && current.hasPrevious) {
  // Both methods available
  current.loadNext();     // ✓
  current.loadPrevious(); // ✓
} else if (current.hasNext) {
  // Only loadNext available
  current.loadNext();     // ✓
  current.loadPrevious(); // ✗ TypeScript error
} else if (current.hasPrevious) {
  // Only loadPrevious available
  current.loadNext();     // ✗ TypeScript error
  current.loadPrevious(); // ✓
}

Navigation Formats

Location Formats

| Format | Nav Type | Example Output | |--------|----------|----------------| | zip | ZipNav | { zip: '10001', country: 'US' } | | zip-country | ZipCountryNav | { zip: '10001', country: 'US', countryShort: 'US' } | | city | CityNav | { city: 'New York', country: 'US' } | | city-state | CityStateNav | { city: 'New York', state: 'New York', stateShort: 'NY', country: 'US' } | | city-state-country | CityStateCountryNav | { city: 'New York', state: 'New York', stateShort: 'NY', country: 'US', countryShort: 'US' } | | state | StateNav | { state: 'New York', stateShort: 'NY', country: 'US' } | | state-country | StateCountryNav | { state: 'New York', stateShort: 'NY', country: 'US', countryShort: 'US' } | | county | CountyNav | { county: 'Kings County', country: 'US' } |

Query-based Formats

Combine locations with custom search queries:

| Format | Nav Type | Example Output | |--------|----------|----------------| | query | QueryNav | { query: 'restaurants', country: 'US' } | | query-zip | QueryZipNav | { query: 'restaurants', zip: '10001', country: 'US' } | | query-zip-country | QueryZipCountryNav | { query: 'restaurants', zip: '10001', country: 'US', countryShort: 'US' } | | query-city | QueryCityNav | { query: 'restaurants', city: 'New York', country: 'US' } | | query-city-state | QueryCityStateNav | { query: 'restaurants', city: 'New York', state: 'New York', stateShort: 'NY', country: 'US' } | | query-city-state-country | QueryCityStateCountryNav | { query: 'restaurants', city: 'New York', state: 'New York', stateShort: 'NY', country: 'US', countryShort: 'US' } | | query-state | QueryStateNav | { query: 'restaurants', state: 'New York', stateShort: 'NY', country: 'US' } | | query-state-country | QueryStateCountryNav | { query: 'restaurants', state: 'New York', stateShort: 'NY', country: 'US', countryShort: 'US' } | | query-county | QueryCountyNav | { query: 'restaurants', county: 'Kings County', country: 'US' } |

Placeholder Format

The placeholder field provides a quick, human-readable address string:

// Location formats
const nav = await Placr.create('city-state-country', 'US');
nav.getNav().placeholder; // "New York, NY, US"

const zipNav = await Placr.create('zip-country', 'US');
zipNav.getNav().placeholder; // "10001, US"

// Query formats include the query
const queryNav = await Placr.create('query-city-state', 'US');
queryNav.addSearchQuery('restaurants');
queryNav.getNav().placeholder; // "restaurants in New York, NY"

API Reference

Placr.create(format?, targetCountry?, dbPath?)

Creates a new Placr instance with strongly-typed returns.

// Each format returns a specifically typed Placr instance
const nav = await Placr.create('city-state-country', 'US');
// Type: Placr<'city-state-country'>

// With custom database path
const nav2 = await Placr.create('zip-country', 'all', './custom.db');
// Type: Placr<'zip-country'>

Parameters:

  • format (NavFormat): Navigation format. Default: 'zip-country'
  • targetCountry (ICountryShort | 'all'): ISO country code or 'all'. Default: 'US'
  • dbPath (string): Path to SQLite database. Default: .nav.db

Navigation Methods

getNav(): NavResponse<T> | null

Returns the current navigation item without advancing.

const current = nav.getNav();
// current.nav is typed based on the format

getNextNav(): NavResponse<T> | null

Advances to and returns the next navigation item.

const next = nav.getNextNav();

getPreviousNav(): NavResponse<T> | null

Goes back to and returns the previous navigation item.

const previous = nav.getPreviousNav();

markComplete(): void

Marks the current navigation item as completed.

nav.markComplete();

resetNav(): void

Resets navigation to the beginning.

nav.resetNav();

Query Methods

addSearchQueries(queries: string[]): void

Adds search queries for query-based navigation formats.

nav.addSearchQueries(['restaurants', 'hotels', 'attractions']);

addSearchQuery(query: string): void

Adds a single search query.

nav.addSearchQuery('coffee shops');

clearSearchQueries(): void

Removes all search queries.

nav.clearSearchQueries();

Data Methods

addCities(cities): void

Adds custom cities to the database.

nav.addCities([
  { city: 'Custom City', state: 'State Name', stateShort: 'ST', countryShort: 'US' }
]);

addStates(states): void

Adds custom states to the database.

nav.addStates([
  { state: 'Custom State', stateShort: 'CS', countryShort: 'US' }
]);

addCountry(countries): void

Adds custom countries to the database.

nav.addCountry([
  { country: 'Custom Country', countryShort: 'CC' }
]);

Pagination Methods

For navigation items with multiple pages:

setPageNav(totalPages: number, pages: Set<number>): void

Sets pagination info for the current item.

nav.setPageNav(10, new Set([1])); // 10 total pages, starting at page 1

markPageAsDone(page: number): void

Marks a specific page as completed.

nav.markPageAsDone(1);
nav.markPageAsDone(2);
// Auto-completes when all pages are done

Types

NavResponse

The response type is conditional based on navigation availability:

// When hasNext and hasPrevious are both true
interface NavResponseBoth<T> {
  nav: T;
  placeholder: string;
  page: PageNav | 'completed' | null;
  hasNext: true;
  hasPrevious: true;
  loadNext: () => NavResponse<T>;
  loadPrevious: () => NavResponse<T>;
}

// When only hasNext is true
interface NavResponseNextOnly<T> {
  nav: T;
  placeholder: string;
  page: PageNav | 'completed' | null;
  hasNext: true;
  hasPrevious: false;
  loadNext: () => NavResponse<T>;
}

// When only hasPrevious is true
interface NavResponsePreviousOnly<T> {
  nav: T;
  placeholder: string;
  page: PageNav | 'completed' | null;
  hasNext: false;
  hasPrevious: true;
  loadPrevious: () => NavResponse<T>;
}

// When neither are available
interface NavResponseNone<T> {
  nav: T;
  placeholder: string;
  page: PageNav | 'completed' | null;
  hasNext: false;
  hasPrevious: false;
}

Format-Specific Nav Types

// Location types
interface ZipNav { zip: string; country: string }
interface ZipCountryNav { zip: string; country: string; countryShort: string }
interface CityNav { city: string; country: string }
interface CityStateNav { city: string; state: string; stateShort: string; country: string }
interface CityStateCountryNav { city: string; state: string; stateShort: string; country: string; countryShort: string }
interface StateNav { state: string; stateShort: string; country: string }
interface StateCountryNav { state: string; stateShort: string; country: string; countryShort: string }
interface CountyNav { county: string; country: string }

// Query types
interface QueryNav { query: string; country: string }
interface QueryZipNav { query: string; zip: string; country: string }
interface QueryZipCountryNav { query: string; zip: string; country: string; countryShort: string }
interface QueryCityNav { query: string; city: string; country: string }
interface QueryCityStateNav { query: string; city: string; state: string; stateShort: string; country: string }
interface QueryCityStateCountryNav { query: string; city: string; state: string; stateShort: string; country: string; countryShort: string }
interface QueryStateNav { query: string; state: string; stateShort: string; country: string }
interface QueryStateCountryNav { query: string; state: string; stateShort: string; country: string; countryShort: string }
interface QueryCountyNav { query: string; county: string; country: string }

NavTypeMap

Maps format strings to their Nav types:

type NavTypeMap = {
  'zip': ZipNav;
  'zip-country': ZipCountryNav;
  'city': CityNav;
  'city-state': CityStateNav;
  'city-state-country': CityStateCountryNav;
  'state': StateNav;
  'state-country': StateCountryNav;
  'county': CountyNav;
  'query': QueryNav;
  'query-zip': QueryZipNav;
  'query-zip-country': QueryZipCountryNav;
  'query-city': QueryCityNav;
  'query-city-state': QueryCityStateNav;
  'query-city-state-country': QueryCityStateCountryNav;
  'query-state': QueryStateNav;
  'query-state-country': QueryStateCountryNav;
  'query-county': QueryCountyNav;
}

Data Sources

Location data is sourced from:

Data is automatically downloaded on first use if not available.

Runtime Support

| Runtime | Version | Status | |---------|---------|--------| | Node.js | 22+ | Supported | | Bun | 1.0+ | Supported |

License

MIT

Contributing

Contributions are welcome. Please open an issue or submit a pull request on GitHub.