← stitchkitDocumentation

GeoIP

stitchkit/geo is a server-only, evolving entrypoint for reading local GeoIP databases without making a database vendor part of the framework contract. It exposes a managed resolver, a peer-neutral reader boundary and an optional MaxMind adapter.

MaxMind

Install maxmind only in applications that use the adapter:

bun add maxmind

Create one resolver and place it in the application resource graph:

import { createApplication } from 'stitchkit/application';
import { createGeoIpResolver, createMaxMindGeoIpLoader } from 'stitchkit/geo';

const geo = createGeoIpResolver({
  paths: {
    city: '/srv/geo/GeoLite2-City.mmdb',
    asn: '/srv/geo/GeoLite2-ASN.mmdb',
  },
  loader: createMaxMindGeoIpLoader(),
  reload: { intervalMs: 60_000 },
  onError(error) {
    logger.warn('GeoIP reader unavailable', { error });
  },
});

const application = createApplication({ resources: [geo] });

The loader imports the optional peer lazily. City and optional ASN files open as one revision: if either changes during the open, the incomplete generation is discarded.

Three states

geo.snapshot() distinguishes facts that must not be collapsed:

  • uninitialized — the managed resource has not started, or has closed;
  • unavailable — no usable database generation could be opened;
  • ready — lookups use a complete generation.

A refresh failure while a reader is already ready preserves that reader and sets reloadError. This is last-known-good service, not a successful refresh. Applications decide whether that fact degrades their own readiness.

Lookups

const attribution = await geo.resolve(requestIp);

resolve returns null for invalid, loopback, private, link-local and otherwise non-public addresses without querying the reader. A database miss or lookup failure also returns null; onError receives operational failures. A result may include country, region, city, postal, coordinates, timezone and ASN fields.

When a reload succeeds, new lookups move to the new generation immediately. The previous reader closes only after its in-flight lookups finish.

A custom reader

Use GeoIpReaderLoader when the database is not MaxMind-compatible. revision must identify the complete input generation and return null when it cannot be read. open returns a reader for exactly that revision. Stitchkit serializes reloads, swaps generations and owns their close lifecycle.

Download schedules, licenses, database paths, attribution persistence and analytics policy belong to the application.