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

@ynm/location-resolver

v1.2.4

Published

Resolve Vietnam province from location strings (local lookup + optional Google Geocoding)

Readme

Location Resolver

A Node.js library that resolves country codes and Vietnamese provinces/cities from free-form address strings or Facebook-like identity objects. It prefers local lookup (provinces + districts), and falls back to a Geo API — by default the internal YNM Geo Resolver, with optional Google Geocoding API.

npm (npmjs.com): scoped package @ynm/location-resolver — published under the private npm org @ynm. Only members / tokens with permissions on the org can npm install it (this is a private package and requires authentication).

Requirements

  • Node.js ≥ 18
  • One of the following geocoding backends for the network fallback:
    • YNM Geo Resolver (default) — reachable HTTP endpoint, e.g. the in-cluster service http://geo-resolver.sl-production.svc.cluster.local:3030 or a custom URL via ynmGeoEndpoint.
    • Google Maps Geocoding — requires a valid API key, enabled by setting geoAPI: 'googleGeoAPI'.

Installation

Login to npm using an account added to the @ynm org (or use an automation token with read access to private packages):

npm login
npm install @ynm/location-resolver
# or
yarn npm login   # if using Yarn 2+
yarn add @ynm/location-resolver

For CI / non-interactive machines: create an Access Token (Granular or Classic, with read access to private packages) on npmjs, set the NPM_TOKEN environment variable, and run for example:

echo "//registry.npmjs.org/:_authToken=$NPM_TOKEN" > .npmrc
npm install @ynm/location-resolver

(Do not commit .npmrc with your token; in CI environments, usually you create a temporary file or use npm config set, and then remove it after the install step.)

From a local clone (development):

npm install /path/to/location-resolver

Bundled data

The data/ directory:

| File | Purpose | |------|---------| | province.json | Province/city name → internal province_id | | districts.js | District/ward list → parent province | | countries.json | Country names (English / aliases) → ISO alpha-2 code |

You can edit these files if your internal IDs or place names differ from your upstream system.

Quick start

const { LocationResolver } = require('@ynm/location-resolver');

// Default: local lookup, fallback to YNM Geo Resolver
const resolver = new LocationResolver({
  // ynmGeoEndpoint: 'http://geo-resolver.sl-production.svc.cluster.local:3030', // optional override
});

// Free-form address string
const info = await resolver.getProvinceInfo('Quận 1, Hồ Chí Minh, Vietnam');
// { country_code: 'VN', province_name: 'Hồ Chí Minh', province_id: 5 }

// Facebook-like location / hometown object
const fb = await resolver.getProvinceInfoForFBIdentity({
  location: { country: 'Vietnam', city: 'Đà Nẵng', name: '' },
});

Switching to Google Geocoding instead of the YNM endpoint:

const resolver = new LocationResolver({
  geoAPI: 'googleGeoAPI',
  googleApiKey: process.env.GOOGLE_MAPS_API_KEY,
  hooks: {
    onGoogleRequest() { /* e.g. quota accounting */ },
    onGoogleSuccess() { /* optional */ },
  },
});

API

new LocationResolver(options?)

| Option | Type | Default | Description | |--------|------|---------|-------------| | geoAPI | 'YNMGeoAPI' \| 'googleGeoAPI' | 'YNMGeoAPI' | Selects which network backend is invoked when local lookup fails. | | ynmGeoEndpoint | string | http://geo-resolver.sl-production.svc.cluster.local:3030 | Base URL of the YNM Geo Resolver. The library calls GET {ynmGeoEndpoint}/detect?location=.... | | googleApiKey | string | '' | Google Maps Geocoding API key (only used when geoAPI === 'googleGeoAPI'). | | hooks | object | {} | onGoogleRequest() / onGoogleSuccess() — invoked before / after a Google call. |

The selected backend is bound to this.getProvinceByAPI at construction time, so resolveLocation always calls the right one.

getProvinceInfo(str)

Parses a string:

  • If the entire string is a known country name → { country_code, province_id: null }.
  • If the string contains a country other than Vietnam → { country_code, province_id: null }.
  • If Vietnam or country is unknown but Vietnamese place resolution is needed → calls resolveLocation (local, then Geo API).

getProvinceInfoForFBIdentity(identity)

Reads identity.location or identity.hometown as { country, city, name } (address often in name). Non-Vietnam countries return province_id: 0; Vietnam builds a string and runs resolveLocation.

resolveLocation(str)

Use when you need Vietnamese province inference (or geocoding to another country): local first, then the configured Geo API. Result includes country_code, province_name, province_id (0 if the API returns Vietnam but the province cannot be mapped).

resolveProvinceByLocal(str) (synchronous)

Splits on commas and tries to match province name or district; no network I/O.

getProvinceByYNMGeoAPI(location)

Calls GET {ynmGeoEndpoint}/detect?location=<encoded> and expects:

{ "status": "OK", "results": { "country_code": "VN", "province_name": "Hồ Chí Minh" } }

Returns { country_code, province_name } or null on HTTP error / NOT_FOUND.

getProvinceByGoogleMap(location)

Calls the Google Geocoding JSON API and normalizes the result to the same shape as the YNM backend: { country_code, province_name } (province_name may be null if Google does not return administrative_area_level_1).

Breaking change vs. v1.0.x — this method previously returned { countryCode, province }. The keys are now country_code and province_name for parity with the YNM Geo API.

Additional exports

The module also exports PROVINCE_MAPPING and COUNTRIES_MAPPING (loaded from data/) for direct use.

Resolution flow (summary)

  1. Normalize Vietnamese text (strip diacritics, lowercase, remove spaces; tokens like city, thanhpho are stripped for matching).
  2. For comma-separated addresses: try the last segment as a province name; if no match, try district on the last or second-to-last segment.
  3. If local resolution fails → call the configured Geo API (YNMGeoAPI by default, googleGeoAPI when explicitly enabled) → if the result is in Vietnam, map the returned province_name back to province_id via province.json.

Production logging

When process.env.NODE_ENV === 'production', the library disables console.debug to keep production logs clean. console.log and console.error are unaffected.

Testing

This repository includes a comprehensive, data-driven automation test suite built with Jest that exercises:

  • Local lookup by province and district.
  • Foreign address handling.
  • Both fallback backends — Google (geoAPI: 'googleGeoAPI') and YNM (ynmGeoEndpoint) — including HTTP errors and NOT_FOUND responses.
  • Edge cases (null, empty, whitespace-only inputs) and constructor wiring.

Most cases are centralized in __tests__/test-cases.json, making it easy to add or remove records without modifying the test runner.

To run the tests:

npm run test

To run the tests and generate a graphical code coverage report:

npm run test:coverage
# Open coverage/lcov-report/index.html to view the report

License

UNLICENSED — see package.json. Change the license if you open-source or distribute the package.