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

@dromney/mapthis

v0.2.0

Published

Open-source location intelligence: parse freeform text, HTML, and URLs into geocoded locations, with types and React components.

Readme

@dromney/mapthis

Open-source location intelligence toolkit. Parse freeform text, HTML, and URLs into geocoded locations; ship with types and React components for rendering maps.

npm install @dromney/mapthis

You'll also want one or more optional peer deps (see Subpath exports):

# pick what you use:
npm install openai js-tiktoken                       # @dromney/mapthis/ai (OpenAI)
npm install @anthropic-ai/sdk js-tiktoken            # @dromney/mapthis/ai (Anthropic)
npm install @googlemaps/google-maps-services-js      # @dromney/mapthis/geocoding
npm install html-to-text                             # @dromney/mapthis/scrape
npm install react react-dom @vis.gl/react-google-maps  # @dromney/mapthis/react

Quick start

import { createLocationParser } from "@dromney/mapthis/ai"
import { createGeocoder } from "@dromney/mapthis/geocoding"
import { createMapGenerator } from "@dromney/mapthis/generate"

const generator = createMapGenerator({
    parser: createLocationParser({ apiKey: process.env.OPENAI_API_KEY! }),
    geocoder: createGeocoder({ provider: "google", apiKey: process.env.GOOGLE_MAPS_KEY! }),
})

const result = await generator.generateFromSource({
    sourceType: "url",
    source: "https://example.com/best-restaurants-tokyo",
})

console.log(result.title)
for (const p of result.places) {
    if (p.data) console.log(p.data.name, p.data.lat, p.data.lng)
    else console.warn("could not geocode", p.input.address, "—", p.error)
}

sourceType accepts "url", "text", or "list". For "list", each newline-separated entry is geocoded directly with no LLM call (and no LLM cost).

Subpath exports

@dromney/mapthis ships a root entry plus focused subpaths so consumers only load what they use.

| Subpath | What it contains | Optional peer deps | |---|---|---| | @dromney/mapthis | Re-exports types + utils (lightweight only) | — | | @dromney/mapthis/types | Zod schemas + TS domain types (PlaceMap, Place, PlaceGroup, Partner, ParsedLocation, Coordinates, …) | — | | @dromney/mapthis/utils | Pure helpers: text, color, numbers, geo, stopwatch | — | | @dromney/mapthis/scrape | getHtmlFromUrl, htmlToText, getTextFromUrl | html-to-text | | @dromney/mapthis/search | createSearchClient (Google Custom Search) | — | | @dromney/mapthis/ai | createLocationParser, createOpenAiBackend, createAnthropicBackend, summarizeText, prompts | openai or @anthropic-ai/sdk, plus js-tiktoken | | @dromney/mapthis/geocoding | createGeocoder (Google Maps) with pluggable GeocodingProvider | @googlemaps/google-maps-services-js | | @dromney/mapthis/generate | createMapGenerator — composes parser + geocoder into a pure orchestrator | — (transitively whatever parser/geocoder use) | | @dromney/mapthis/react | MapProvider, GoogleMapsViewer, PlaceMarker, autofit, browserAutocomplete | react, react-dom, @vis.gl/react-google-maps |

Choosing an LLM backend

createLocationParser accepts either an OpenAI config, an Anthropic config, or a bring-your-own backend implementing the LlmBackend interface.

// OpenAI (default)
createLocationParser({ apiKey: process.env.OPENAI_API_KEY! })
createLocationParser({ provider: "openai", apiKey: ..., model: "gpt-4o-mini" })

// Anthropic
createLocationParser({
    provider: "anthropic",
    apiKey: process.env.ANTHROPIC_API_KEY!,
    model: "claude-haiku-4-5-20251001",
})

// Bring-your-own (e.g. an Azure/Bedrock adapter or a deterministic test fake)
createLocationParser({ backend: myCustomBackend })

Both bundled backends implement the same two-stage pipeline: a chunked summarization pass to fit the model's context window, followed by a structured-output call (OpenAI function calling, Anthropic tool use) to extract the location list.

Persisting results

createMapGenerator is intentionally pure — it returns a plain object and never touches a database. Wire the result into your own persistence layer:

const result = await generator.generateFromSource({ sourceType: "url", source: url })

await db.$transaction(async (tx) => {
    const map = await tx.map.create({ data: { title: result.title /* … */ } })
    const group = await tx.placeGroup.create({ data: { mapId: map.id /* … */ } })
    await tx.place.createMany({
        data: result.places.map((p, i) => ({
            mapId: map.id,
            groupId: group.id,
            position: i,
            query: p.input.address,
            description: p.input.description,
            name: p.data?.name,
            address: p.data?.address,
            lat: p.data?.lat,
            lon: p.data?.lng,
            provider: p.data?.provider,
            providerId: p.data?.providerId,
            error: p.error,
            locationPromptVersion: result.locationPromptVersion,
            summaryPromptVersion: result.summaryPromptVersion,
        })),
    })
})

Error handling

Every error thrown by the package extends MapthisError (@dromney/mapthis/types). Domain-specific subclasses let callers handle the failure modes that matter to their UX:

| Class | Subpath | When | |---|---|---| | InvalidUrlError, ScrapeError | /scrape | URL was unreachable or returned non-HTML | | NoSearchResultsError, SearchError | /search | Google CSE returned zero usable results | | NoLocationsFoundError, AiInputLengthError, AiOutputLengthError, AiResponseJsonError, InvalidJsonSchemaError, SummarizeTextError, AiError | /ai | LLM stage failed | | GeocodingError | /geocoding | Geocoder threw (per-input failures resolve, not throw) |

Per-input geocoder failures are not thrown — geocodeMany returns a PlaceQueryResult with data: null and error: string so one bad address can't fail an entire batch.

Design principles

  • No process.env reads. All secrets are passed to factory functions by the consumer. Keeps the package portable and testable.
  • Framework-agnostic. Core modules are pure TypeScript. React components live behind the @dromney/mapthis/react subpath so server-only consumers skip them entirely.
  • Plain domain types. Hand-rolled TS with matching Zod schemas. Consumers aren't locked to a particular ORM — write adapter functions between your DB models and these types in your app.
  • Optional peer deps for heavy things. Every SDK is an optional peer dependency. Install only what the subpaths you use need.

License

Apache 2.0 — see LICENSE.