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

@mapmap/react-native

v0.3.2

Published

MapMap React Native SDK — Expo module for fully offline turn-by-turn navigation: signed territory install/lifecycle, on-device routing, a typed guidance event stream and voice guidance.

Readme

@mapmap/react-native

The official MapMap React Native SDK — an Expo module for fully offline turn-by-turn navigation:

  • Territory lifecycle — list, install (signed + verified), remove, and style offline territory packages; progress events throughout.
  • Routing — on-device route computation with typed geometry + maneuvers (no Valhalla JSON re-parsing in app code).
  • Guidance — a typed event stream (navigating / rerouting / arrived, banner instructions, ETA, off-route) driven by live GPS or a .drive.jsonl replay corpus.
  • Voice — spoken guidance with mute/volume control and a speaking state event.
  • Mock modegetMapmapNav(true) returns a pure-TS simulator with the identical surface, for Expo Go, Storybook-style UI work and unit tests.

Status: TypeScript surface stable; both native bridges compile in CI against the shipped SDKs. This package is the bridge (~2,000 lines across TS/Swift/Kotlin) extracted from a real customer app and promoted to the official module. The TypeScript surface, including the mock, is stable, tested and usable today.

The iOS bridge is built in CI against the real published MapMapKit (see iOS status): every file that calls the SDK compiles against the actual distribution package, not a stub.

The Android bridge was rewritten against the real ai.mapmap:core API and is compiled against the artefact on Maven Central by the react-native-android CI workflow on every change; parts of the contract the shipped core cannot serve reject loudly rather than fake a result (see Android status).

Requirements

| | | |---|---| | iOS | 16.4+, MapMapKit 0.6.x via the public Mapmapai/mapmap-ios distribution repo (SwiftPM, or the binary MapMapKit.podspec). 0.6.0 is the release the bridge is compiled against in CI; older tags are not supported (0.2.x does not build at all) | | Android | minSdk 26 (above Expo's default — see below), ai.mapmap:core 0.3.0 from Maven Central — no credentials needed | | React Native | Expo SDK 52+ (config plugin), or bare React Native 0.74+ with expo-modules-core installed |

Install — Expo

npx expo install @mapmap/react-native

Register the config plugin in app.json / app.config.js:

{
  "expo": {
    "plugins": [
      [
        "@mapmap/react-native",
        {
          "locationWhenInUsePermission": "Shown to the user when asking for foreground location.",
          "locationAlwaysPermission": "Shown when asking for background location."
        }
      ]
    ],
    "android": {
      // minSdk 26: the MapMap core requires it; Expo's default is lower.
      // With expo-build-properties:
      //   ["expo-build-properties", { "android": { "minSdkVersion": 26 } }]
    }
  }
}

The plugin writes the NSLocation*UsageDescription Info.plist keys the SDK's CoreLocationProvider needs (the SDK deliberately declares none of its own) and enables the location + audio background modes so guidance and voice keep running with the screen off.

Then build a dev client (npx expo run:ios / run:android). The native module is not available in Expo Go — use the mock there (getMapmapNav(true)). Importing this package never requires the native module to be present: it is resolved lazily, on first use of the native handle, so the mock path works anywhere (Expo Go, web, unit tests).

Android repositories — nothing to configure

ai.mapmap:core is on Maven Central from 0.3.0, so the mavenCentral() that every Expo/RN template already declares resolves the core and its dependencies anonymously. No GitHub account, no read:packages PAT.

To pin a different core:

./gradlew ... -Pmapmap.coreVersion=0.3.1

Versions before 0.3.0 were only ever published to GitHub Packages. If you deliberately pin one, supply credentials in ~/.gradle/gradle.properties (or as GPR_USER / GPR_TOKEN env vars) and the module will register that repository for you, scoped to ai.mapmap artefacts only:

gpr.user=<github username>
gpr.token=<PAT with read:packages>

Without those properties the repository is not registered at all — which is what keeps a default build credential-free, and keeps it working under the RepositoriesMode.FAIL_ON_PROJECT_REPOS set by current RN/Expo templates.

iOS binary SDK

The pod depends on MapMapKit (~> 0.6.0), the prebuilt binary of the MapMap iOS SDK (no vendoring, no Valhalla/boost recompiles). Until it is on the CocoaPods CDN, pin it in your Podfile:

pod 'MapMapKit', podspec: 'https://raw.githubusercontent.com/Mapmapai/mapmap-ios/main/MapMapKit.podspec'

On-device routing needs one more package. computeRoute runs Valhalla on the device through the SDK's MapMapValhalla product, which ships only via SwiftPM — there is no binary pod for it yet. Add the SwiftPM package to your app target (File → Add Package Dependencies…https://github.com/Mapmapai/mapmap-ios, link MapMapValhalla). Without it the bridge still builds and everything else works, but computeRoute rejects with E_INTERNAL rather than silently pretending.

Install — bare React Native

  1. npm install @mapmap/react-native expo-modules-coreexpo-modules-core is the only required peer; the expo package itself is optional and is not imported at runtime.
  2. Follow the Expo modules install guide (adds use_expo_modules! to the Podfile and the Gradle settings plugin).
  3. iOS: cd ios && pod install (with the MapMapKit Podfile line above).
  4. Android: set minSdkVersion 26 in android/build.gradle. No repository configuration is needed beyond the template's mavenCentral().
  5. Apply the Info.plist keys / background modes manually (the config plugin only runs under Expo prebuild): NSLocationWhenInUseUsageDescription, NSLocationAlwaysAndWhenInUseUsageDescription, and UIBackgroundModes location + audio.

Usage

import {
  getMapmapNav,
  addGuidanceListener,
  addTerritoryProgressListener,
} from "@mapmap/react-native";

const nav = getMapmapNav(false); // true → pure-TS mock (Expo Go, tests)

await nav.init("YOUR_MAPMAP_API_KEY");

// Territories: list, install (signed + verified), style offline
const available = await nav.listAvailableTerritories();
const sub = addTerritoryProgressListener(nav, (e) =>
  console.log(e.territoryId, e.phase, e.bytesDone, e.bytesTotal)
);
await nav.installTerritory("uk");
const styleJson = await nav.getTerritoryStyle("uk", "dark"); // MapLibre style, all-local refs

// Routing — typed geometry + maneuvers
const { routes } = await nav.computeRoute({
  profile: "car",
  origin: { latitude: 51.5074, longitude: -0.1278 },
  destination: { latitude: 51.5081, longitude: -0.0759 },
});

// Guidance — typed event stream
addGuidanceListener(nav, (e) => {
  console.log(e.state, e.banner?.primary, e.remainingDistanceMeters, e.etaEpochMs);
});
await nav.startGuidance({ routeIndex: 0, source: { kind: "live" } });

// Voice
await nav.setVoiceMuted(false);
await nav.setVoiceVolume(0.8);

The full surface (all types, error codes, events) is defined in src/MapmapNav.types.ts — that file is the bridge contract, implemented exactly on both platforms.

Errors

Every rejected promise carries a stable machine-readable code (E_NOT_INITIALISED, E_AUTH, E_TERRITORY_VERIFY, E_NO_ROUTE, …) plus a human message; the same shape is emitted on the bridgeError event. See BridgeErrorCode in the types file.

Development

npm install
npm run typecheck   # tsc --noEmit
npm test            # vitest (mock + event helpers; no native runtime needed)
npm pack --dry-run  # what ships

iOS status

The Swift bridge in ios/ compiles against the real published MapMapKit. That is a build result, not an assertion: ios/Package.swift is a compile-check manifest that resolves Mapmapai/mapmap-ios at the pinned release and builds these sources against it, and the ios-bridge-compile job in .github/workflows/publish-react-native.yml runs it on every PR. Locally:

cd react-native/ios
xcodebuild -scheme MapmapNavBridge -destination 'generic/platform=iOS Simulator' build

What is compiled: every file that calls MapMapKit — MapmapNavCore.swift, GuidanceMapping.swift, RouteMapping.swift, GatewayLayerFetcher.swift, BridgeError.swift, Support.swift. What is not: MapmapNavModule.swift, which imports ExpoModulesCore (no SwiftPM distribution — compiling it needs a full CocoaPods + React Native pod install). That file contains no MapMapKit symbols; it is a pure Expo Module shim forwarding to MapmapNavCore.

Before this was checked, the bridge did not compile. It was written against MapMapKit 0.2.0, and 0.2.x is not a buildable package at all — those tags ship Swift sources referencing UniFFI-generated types (GuidanceUpdate, CostingModel, LayerFetcher, RoutePoint, …) that are absent from the tag; the generated bindings first shipped in 0.3.0, and no MapMapKit.podspec existed in the distribution repo before then either. Against 0.6.0 the divergences were:

| Bridge had | Shipped SDK | |---|---| | GatewayLayerFetcher: LayerFetcher with no import MapMapKit | LayerFetcher is a MapMapKit type — hard compile error | | case .navigating matched with 10 associated values | 11 (durationRemainingS was missed) | | ETA estimated as distance ÷ speed, falling back to "50 km/h" | durationRemainingS is reported by the core | | raw GPS fix used for the puck | SnappedFix (route-snapped position + course) | | banner arrow hardcoded to straight | VisualBanner.primary.maneuverType/maneuverModifier | | search() rejected: "no on-device geocoder exists" | TerritoryStore.openSearchTerritorySearch.search | | hand-written MapLibre style, glyphs on a public HTTPS URL | TerritoryStore.territoryStyle(territoryId:theme:), all-local | | Valhalla JSON re-parsed by hand (own polyline decoder, length × 1000) | RouteResult.summary/.geometry/.maneuvers, typed and in SI | | errors classified by substring-matching String(describing:) | NavCoreError / FetchError cases, switched on by type |

Known gaps on iOS, stated rather than faked:

  • On-device routing needs the SwiftPM MapMapValhalla product (see above). The import sits behind #if canImport, so the pod builds without it; computeRoute then rejects with E_INTERNAL.
  • BannerInstruction.roundaboutExit and the "Then ↰" chip (thenPrimary / thenManeuverType) are never set during guidance. The core's banner carries roundaboutExitDegrees (degrees travelled, not an exit ordinal) and a sub line holding the lane diagram, not the next manoeuvre. Both contract fields are optional, so they are left absent. (roundaboutExit is populated on computeRoute manoeuvres, where the SDK reports roundaboutExitCount.)
  • listAvailableTerritories and installTerritory talk to gateway paths (/v1/territories…) that the compile check cannot exercise; they are verified as Swift, not as an integration.

Android status

The Android bridge in android/src/main compiles against the published ai.mapmap:core (issue #448). It was previously written against an assumed API and did not compile against 0.2.0 or 0.3.0; the only check that ran was -PskipMapmap, which substituted a hand-written android/src/stub/java whose own header admitted it encoded the assumed surface. Those stubs are deleted and -PskipMapmap is gone. In their place, the react-native-android CI workflow packs this npm package, installs it into a freshly generated Expo app and compiles the module with Gradle against ai.mapmap:core from Maven Central, on every change to android/.

Kotlin metadata

ai.mapmap:core 0.3.0 is published with Kotlin 2.3 metadata, which the Kotlin plugin current Expo/RN templates pin (2.1.x on SDK 54/57) refuses to read. android/build.gradle therefore compiles this module — and only this module — with -Xskip-metadata-version-check. Remove that once the core is published with metadata a stock template reads.

What the shipped core cannot do

These are contract gaps, not bridge shortcuts. Nothing below is faked: each one rejects with a contract error code and a message naming the reason.

| Contract | Android behaviour on core 0.3.0 | |---|---| | installTerritory(id) for a territory that is not yet installed | Rejects E_INTERNAL. The core installs only from a local .snpkg or package directory (installSnpkg / installDir); checkForUpdate errors outright when the territory is not installed, and the hosted channel serves an index, manifests and layer blobs but no package archive. Updating an already-installed territory works fully (signed differential update, verify-then-promote). | | RouteRequest.alternatives (0–3, default 2) | Ignored. OfflineRouting.route() returns exactly one RouteResult and the core exposes no alternates API, so RouteResult.routes always has length 1. | | GuidanceEvent.state === "rerouting" | Reported when the core emits OffRoute, but the bridge does not recompute: an engine is bound to one route, so the app must call computeRoute + startGuidance again. | | BannerInstruction.roundaboutExit / thenPrimary / thenManeuverType | Emitted only when the route's maneuver list is index-aligned with the core's guidance steps (checked against NavigationEngine.totalSteps()), because the core's sub-banner is the lane diagram, not the next instruction. Omitted otherwise rather than guessed. | | stopGuidance() silencing speech immediately | VoiceGuidance has no pause — close() shuts the TTS engine down permanently — so the module holds one instance for its lifetime and stops feeding it. An utterance already handed to the platform engine finishes. | | TerritorySummary.name / sizeBytes | Not in the signed channel index (ids and versions only), so listAvailableTerritories() fetches each territory's package manifest for the display name and layer byte total: one extra GET per territory. |

Everything else in the contract is implemented against real core APIs: territory listing/removal/activation, offline style JSON (TerritoryStore.territoryStyle), on-device search (TerritoryStore.openSearchTerritorySearch.search), on-device routing (OfflineRouting.route over ValhallaMobileRouter.fromTileDir), guidance (NavigationEngine.navigate) and voice, including the live speaking state.

android/build.gradle needs the territory channel's ed25519 verifying key baked in at build time via the mapmap.factoryPubkeyHex Gradle property or MAPMAP_FACTORY_PUBKEY_HEX env; init() rejects E_INIT_FAILED when it is missing rather than failing later inside the core.

Native follow-ups tracked for this package:

  • Give the core a channel-driven first install (or serve a .snpkg) so installTerritory works end to end.
  • Ship MapMapValhalla as a binary pod so on-device Valhalla routing links without SwiftPM (today MapmapNavCore.swift imports it from the SwiftPM package, behind #if canImport).
  • Run the iOS bridge against a real device/simulator with an installed territory: the CI job proves it compiles, not that a drive works.

Licence

Commercial — see LICENSE. Requires a MapMap API key (mapmap.ai).