@maimaps/js
v0.1.1
Published
Maimaps core JavaScript/TypeScript SDK: search, routing, reverse geocoding, point details, loccode, and map style helpers over the keyed /sdk/v1 API.
Readme
@maimaps/js
Core Maimaps JavaScript/TypeScript SDK. Pure TypeScript, zero runtime dependencies (uses the
global fetch), shipped as dual ESM/CJS with full type declarations.
It talks to the keyed Maimaps SDK API (/sdk/v1/* REST + /sdk/* map-display routes) and is the
foundation for @maimaps/react and @maimaps/react-native.
Install
pnpm add @maimaps/js
# or: npm install @maimaps/js / yarn add @maimaps/jsRequires an environment with the Fetch API (modern browsers, Node 18+, React Native). You can also
inject your own fetch implementation.
Quickstart
import { MaimapsClient, decodePolyline } from '@maimaps/js';
const client = new MaimapsClient({
apiKey: 'mk_test_…', // minted in the Maiaddy Developers portal (MAIMAPS product)
// environment: 'staging' is the default and bakes in the staging hosts.
});
// Search
const results = await client.search({ q: 'garki market', latitude: 9.05, longitude: 7.49 });
// Route A → B (OSRM-shaped result, precision-5 polyline geometry)
const trip = await client.route({
originLat: 9.05,
originLng: 7.49,
destLat: 6.45,
destLng: 3.39,
mode: 'drive',
});
const coordinates = decodePolyline(trip.routes[0]!.geometry); // [lat, lng][]
// Reverse geocode, point details, loccode, categories
const place = await client.reverseGeocode({ latitude: 9.06, longitude: 7.48 });
const details = await client.pointDetails({ latitude: 9.06, longitude: 7.48, osmId: 42 });
const loc = await client.loccode.resolve('ABCD1234');
const nearest = await client.loccode.nearest({ latitude: 9.06, longitude: 7.48 });
const categories = await client.placeCategories.list();Routing with waypoints or loccodes
const trip = await client.routeAdvanced({
originLoccode: 'AAAA0000',
destLat: 6.45,
destLng: 3.39,
waypoints: [{ order: 1, latitude: 8.0, longitude: 5.0 }],
mode: 'drive',
});Map display (MapLibre)
import maplibregl from 'maplibre-gl';
const map = new maplibregl.Map({
container: 'map',
style: client.styleUrl({ mode: 'dark' }), // keyed style.json — one map load per fetch
transformRequest: (url) => ({ url: client.transformMapResource(url) }),
});styleUrl() builds {mapBaseUrl}/sdk/styles/{family}/style.json?mode={mode}&key={apiKey}.
transformMapResource(url) appends ?key= to map-host /sdk/ tile/sprite/font requests that do
not already carry one, and leaves every other URL untouched.
Configuration
new MaimapsClient({
apiKey: string; // required, non-empty (mk_live_… or mk_test_…)
environment?: 'staging' | 'production'; // default 'staging'
apiBaseUrl?: string; // gateway host for /sdk/v1 REST
mapBaseUrl?: string; // engine host for /sdk/* map assets
fetch?: FetchLike; // custom fetch (tests, polyfills)
timeoutMs?: number; // per-attempt timeout, default 10000
});environment: 'staging'(default) targetshttps://maps-staging-api.maiaddy.com(REST) andhttps://maimaps-staging-api.maiaddy.com(map assets).environment: 'production'requires explicitapiBaseUrlandmapBaseUrl— production hostnames are not yet published, so the client throws if either is missing.- API keys are not secrets (they ship in client apps) but are quota-bearing; usage restrictions are managed in the Maiaddy Developers portal.
Errors
All failures throw a typed subclass of MaimapsError (message, httpStatus, code):
| Error | When |
|---|---|
| InvalidApiKeyError | 401 invalid_api_key — missing/malformed/revoked/wrong-product key |
| RateLimitError | 429 rate_limited — carries retryAfterSeconds from Retry-After |
| QuotaExceededError | 429 quota_exceeded — monthly cap reached |
| ServerError | any 5xx, including 503 auth_unavailable |
| NetworkError | request never produced a response (connection failure, timeout) |
import { RateLimitError, QuotaExceededError } from '@maimaps/js';
try {
await client.search({ q: 'wuse' });
} catch (error) {
if (error instanceof RateLimitError) {
console.log(`retry after ${error.retryAfterSeconds ?? 1}s`);
} else if (error instanceof QuotaExceededError) {
// monthly cap reached — do not retry
}
}Retries & timeout
- Idempotent GETs are retried at most twice: on 429
rate_limited(honoringRetry-After, capped at 10 s) and on 5xx/network errors (250 ms then 1 s backoff). - POST requests (e.g.
routeAdvanced) are never retried. - 401 and
quota_exceededare never retried. - Each attempt is aborted after
timeoutMs(default 10 s) viaAbortController.
Development
pnpm install # from the monorepo root
pnpm --filter @maimaps/js build # tsup → dist (ESM + CJS + d.ts)
pnpm --filter @maimaps/js test # vitest
pnpm --filter @maimaps/js typecheck