npm package: @liftitinc/geocoding
Description: JavaScript/TypeScript SDK for Liftit Geocoding API
Author: Liftit
Latest version: 0.3.0

# @liftit/geocoding

![npm version](https://img.shields.io/npm/v/@liftit/geocoding.svg)
![License](https://img.shields.io/npm/l/@liftit/geocoding.svg)
![Node version](https://img.shields.io/node/v/@liftit/geocoding.svg)

JavaScript/TypeScript SDK for the Liftit Geocoding API.

## Installation

```bash
npm install @liftit/geocoding
# or
pnpm add @liftit/geocoding
# or
yarn add @liftit/geocoding
```

## Quick Start

```typescript
import { GeocodingClient } from '@liftit/geocoding';

const client = new GeocodingClient({
  apiKey: 'your-api-key',
  // Optional configuration:
  // baseUrl: 'https://api.codepin.co',
  // timeout: 10000,
  // maxRetries: 3,
});

// Forward geocoding - convert address to coordinates
const result = await client.geocode({
  country: 'co',
  city: 'Bogota',
  address: 'Calle 100 # 19-61',
  // Optional parameters:
  // region: 'Cundinamarca',
  // custom_zone: true,
});

console.log(result.location);        // { lat: 4.6097, lng: -74.0817 }
console.log(result.geocode.formatted_address);

// Reverse geocoding - convert coordinates to address
const address = await client.reverseGeocode({
  lat: 4.6097,
  lng: -74.0817,
});

console.log(address.geocode.formatted_address);
console.log(address.address_components?.city);
```

## Features

- **TypeScript Support**: Full type definitions with comprehensive type safety
- **Zero Dependencies**: Uses native `fetch` API (Node.js 18+ required)
- **Automatic Retry**: Exponential backoff with full jitter for transient errors
- **Rate Limit Handling**: Respects `Retry-After` headers automatically
- **Error Hierarchy**: Typed error classes for precise error handling
- **Zod Schemas**: Exported schemas for request/response validation

## Configuration

| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `apiKey` | `string` | Required | Your Liftit Geocoding API key |
| `baseUrl` | `string` | `"https://api.codepin.co"` | API base URL |
| `timeout` | `number` | `10000` | Request timeout in milliseconds |
| `maxRetries` | `number` | `3` | Maximum retry attempts for transient errors |
| `baseDelay` | `number` | `1000` | Initial retry delay in milliseconds |
| `maxDelay` | `number` | `30000` | Maximum retry delay cap in milliseconds |

**Example:**

```typescript
const client = new GeocodingClient({
  apiKey: 'your-api-key',
  timeout: 15000,
  maxRetries: 5,
  baseDelay: 2000,
  maxDelay: 60000,
});
```

## Retry Behavior

The SDK automatically retries requests for transient errors:

| Error Type | Retryable | Strategy |
|------------|-----------|----------|
| Rate limit (429) | Yes | Uses `Retry-After` header if present |
| Server error (500, 502, 503, 504) | Yes | Exponential backoff with full jitter |
| Timeout | Yes | Exponential backoff with full jitter |
| Network errors | Yes | Exponential backoff with full jitter |
| Validation (400) | No | Fails immediately |
| Authentication (401) | No | Fails immediately |
| User abort | No | Fails immediately |

### Retry Timing

- **Initial delay**: 1 second (configurable via `baseDelay`)
- **Maximum delay**: 30 seconds (configurable via `maxDelay`)
- **Multiplier**: 2x exponential growth
- **Jitter**: Full random jitter (0 to calculated delay)
- **Default attempts**: 3 retries (configurable via `maxRetries`)

**Formula:** `delay = min(baseDelay * 2^attempt * random(), maxDelay)`

### Disabling Retries

```typescript
// Disable retries for testing or special cases
const client = new GeocodingClient({
  apiKey: 'your-api-key',
  maxRetries: 0,
});
```

## Error Handling

The SDK provides a comprehensive error hierarchy:

```typescript
import {
  GeocodingClient,
  GeocodeError,
  ValidationError,
  AuthenticationError,
  RateLimitError,
  UpstreamError,
} from '@liftit/geocoding';

const client = new GeocodingClient({ apiKey: 'your-api-key' });

try {
  const result = await client.geocode({
    country: 'co',
    city: 'Bogota',
    address: 'Calle 100 # 19-61',
  });
} catch (error) {
  if (error instanceof ValidationError) {
    // Invalid request parameters (400)
    console.error(`Validation error: ${error.message}`);
    console.error(`Request ID: ${error.requestId}`);
  } else if (error instanceof AuthenticationError) {
    // Invalid API key (401)
    console.error(`Authentication failed: ${error.message}`);
  } else if (error instanceof RateLimitError) {
    // Rate limit exceeded (429)
    console.error(`Rate limited. Retry after ${error.retryAfter} seconds`);
    console.error(`Limit: ${error.limit}, Remaining: ${error.remaining}`);

    // Calculate optimal retry delay
    const delayMs = error.getRetryDelay();
    await new Promise(resolve => setTimeout(resolve, delayMs));
    // Retry request...
  } else if (error instanceof UpstreamError) {
    // API server error (500-599)
    console.error(`Upstream error: ${error.message}`);
    console.error(`Status code: ${error.statusCode}`);
  } else if (error instanceof GeocodeError) {
    // Base class catches all SDK errors
    console.error(`Geocoding error [${error.code}]: ${error.message}`);
  }
}
```

**Error Properties:**

| Error Class | Properties |
|-------------|------------|
| `GeocodeError` | `message`, `code`, `requestId` |
| `ValidationError` | Inherits from `GeocodeError` |
| `AuthenticationError` | Inherits from `GeocodeError` |
| `RateLimitError` | Inherits + `retryAfter`, `limit`, `remaining`, `getRetryDelay()` |
| `UpstreamError` | Inherits + `statusCode` |

## API Reference

### Forward Geocoding

Convert an address to geographic coordinates.

**Method:** `client.geocode(request, options?)`

**Parameters:**

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `request.country` | `string` | Yes | ISO 3166-1 alpha-2 country code (ar, br, cl, co, ec, mx, pe) |
| `request.address` | `string` | Yes | Street address to geocode |
| `request.city` | `string` | No | City name (recommended for accuracy) |
| `request.region` | `string` | No | State/department name |
| `request.custom_zone` | `boolean` | No | Include zone information (default: false) |
| `options.signal` | `AbortSignal` | No | AbortSignal for request cancellation |

**Response:**

```typescript
{
  success: true,
  data: {
    location: { lat: number, lng: number },
    geocode: { formatted_address: string },
    cleaned_address: boolean,
    zone?: { id: number, name: string, type: string }
  },
  requestId: string
}
```

**Example:**

```typescript
const result = await client.geocode({
  country: 'co',
  city: 'Bogota',
  address: 'Calle 100 # 19-61',
  custom_zone: true,
});

console.log(result.location);  // { lat: 4.6097, lng: -74.0817 }
console.log(result.zone?.name); // "Zone 1"
```

### Reverse Geocoding

Convert geographic coordinates to an address.

**Method:** `client.reverseGeocode(request, options?)`

**Parameters:**

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `request.lat` | `number` | Yes | Latitude (-90 to 90) |
| `request.lng` | `number` | Yes | Longitude (-180 to 180) |
| `options.signal` | `AbortSignal` | No | AbortSignal for request cancellation |

**Response:**

```typescript
{
  success: true,
  data: {
    geocode: { formatted_address: string },
    location: { lat: number, lng: number },
    address_components: {
      city: string,
      country: string
    }
  },
  requestId: string
}
```

**Example:**

```typescript
const address = await client.reverseGeocode({
  lat: 4.6097,
  lng: -74.0817,
});

console.log(address.geocode.formatted_address);
console.log(address.address_components?.city); // "Bogota"
```

## Supported Countries

The SDK supports geocoding in the following countries:

| Country | Code | Example City |
|---------|------|--------------|
| Argentina | `ar` | Buenos Aires |
| Brazil | `br` | Sao Paulo |
| Chile | `cl` | Santiago |
| Colombia | `co` | Bogota |
| Ecuador | `ec` | Quito |
| Mexico | `mx` | Mexico City |
| Peru | `pe` | Lima |

## Requirements

- **Node.js**: >= 18.0.0 (required for native `fetch` API)
- **Runtime Dependencies**: None (zero dependencies)
- **TypeScript**: >= 5.0 (for TypeScript projects)

## Development

```bash
# Clone the repository
git clone https://github.com/Liftitapp/geocoding-enterprise.git
cd geocoding-enterprise

# Install dependencies
pnpm install

# Build the SDK
pnpm --filter @liftit/geocoding build

# Run tests
pnpm --filter @liftit/geocoding test

# Type checking
pnpm --filter @liftit/geocoding typecheck

# Lint
pnpm --filter @liftit/geocoding lint
```

## License

MIT - see [LICENSE](./LICENSE) for details.

## Support

- **GitHub Issues**: [Report bugs and request features](https://github.com/Liftitapp/geocoding-enterprise/issues)
- **API Reference**: [View API documentation](https://codepin.co/docs)
- **Homepage**: [https://codepin.co](https://codepin.co)