@digiphilo/opencage-typescript
v1.0.0
Published
TypeScript SDK for OpenCage Geocoding API - Type-safe frontend geocoding with zero dependencies
Maintainers
Readme
OpenCage TypeScript SDK
A type-safe, zero-dependency TypeScript SDK for the OpenCage Geocoding API. Built from the ground up with TypeScript for maximum type safety, developer experience, and frontend optimization.
✨ Features
- 🔷 100% TypeScript - Built with TypeScript for TypeScript developers
- 🛡️ Complete Type Safety - Full type coverage with strict TypeScript definitions
- 🚀 Zero Dependencies - Pure TypeScript with no external dependencies
- 🎯 Frontend Optimized - Designed specifically for browser environments
- 💾 Smart Caching - Type-safe memory cache with LRU eviction and TTL
- ⚡ Debounced Requests - Built-in rate limiting with configurable debouncing
- 🔄 Retry Logic - Automatic retry with exponential backoff and error handling
- 📊 Statistics Tracking - Comprehensive usage analytics with type safety
- 🛡️ Input Validation - Runtime validation with detailed TypeScript error messages
- 🌐 Multiple Formats - ES modules, CommonJS with full type definitions
- 📱 Mobile Friendly - Optimized for mobile and progressive web apps
- 🧩 Tree Shakable - Import only what you need for smaller bundles
🚀 Quick Start
Installation
npm install @geonot/typescript-sdkBasic Usage
import { OpenCageClient } from '@geonot/typescript-sdk'
import type { OpenCageResponse, GeocodingResult } from '@geonot/typescript-sdk'
// Initialize client with full type safety
const client = new OpenCageClient({
apiKey: 'YOUR_API_KEY'
})
// Forward geocoding with automatic type inference
const forwardResult: OpenCageResponse = await client.geocode('1600 Pennsylvania Avenue, Washington, DC')
const coordinates = forwardResult.results[0]?.geometry // { lat: number, lng: number } | undefined
console.log(`Coordinates: ${coordinates?.lat}, ${coordinates?.lng}`)
// Reverse geocoding with type safety
const reverseResult: OpenCageResponse = await client.reverseGeocode(38.8976633, -77.0365739)
const address = reverseResult.results[0]?.formatted // string | undefined
console.log(`Address: ${address}`)
// Get rate limit info with full typing
const rateInfo = client.getRateLimitInfo()
if (rateInfo) {
console.log(`${rateInfo.remaining}/${rateInfo.limit} requests remaining`)
}Advanced Type-Safe Usage
import {
OpenCageClient,
COUNTRY_CODES,
LANGUAGE_CODES,
type GeocodingQuery,
type ReverseGeocodingOptions
} from '@geonot/typescript-sdk'
const client = new OpenCageClient({
apiKey: 'YOUR_API_KEY',
cache: true,
timeout: 10000
})
// Type-safe query object
const query: GeocodingQuery = {
query: '10 Downing Street',
country: COUNTRY_CODES.UNITED_KINGDOM,
language: LANGUAGE_CODES.ENGLISH,
limit: 5,
bounds: [51.3, -0.5, 51.7, 0.2], // Type: [number, number, number, number]
minConfidence: 7
}
const result = await client.geocode(query)
// Type-safe options for reverse geocoding
const options: ReverseGeocodingOptions = {
language: LANGUAGE_CODES.SPANISH,
limit: 1,
noAnnotations: true
}
const reverseResult = await client.reverseGeocode(40.7128, -74.0060, options)📚 Complete API Documentation
Client Configuration
import type { OpenCageClientOptions } from '@geonot/typescript-sdk'
const options: OpenCageClientOptions = {
apiKey: 'YOUR_API_KEY', // Required: Your OpenCage API key
timeout: 10000, // Request timeout in milliseconds
retryAttempts: 3, // Number of retry attempts for failed requests
retryDelay: 1000, // Base retry delay in milliseconds
cache: true, // Enable/disable caching
cacheSize: 100, // Maximum cache entries
cacheTtl: 300000, // Cache TTL in milliseconds (5 minutes)
debounce: 300, // Debounce delay for rapid requests
baseUrl: 'https://api.opencagedata.com/geocode/v1', // API base URL
userAgent: 'MyApp/1.0.0' // Custom User-Agent string
}
const client = new OpenCageClient(options)Forward Geocoding with Full Type Safety
import type { Query, GeocodingQuery, OpenCageResponse } from '@geonot/typescript-sdk'
// String queries are automatically typed
const stringQuery: string = 'Paris, France'
const result1: OpenCageResponse = await client.geocode(stringQuery)
// Object queries with full IntelliSense support
const objectQuery: GeocodingQuery = {
query: '123 Main Street',
country: 'us', // Type: string
language: 'en', // Type: string
limit: 5, // Type: number
bounds: [40, -75, 41, -74], // Type: [number, number, number, number]
proximity: [40.7, -74.0], // Type: [number, number]
minConfidence: 7, // Type: number
abbrv: true, // Type: boolean
noAnnotations: true // Type: boolean
}
const result2: OpenCageResponse = await client.geocode(objectQuery)
// Type-safe access to results
const firstResult = result2.results[0]
if (firstResult) {
const lat: number = firstResult.geometry.lat
const lng: number = firstResult.geometry.lng
const confidence: number = firstResult.confidence
const formatted: string = firstResult.formatted
const country: string | undefined = firstResult.components.country
}
// Debounced geocoding with the same type safety
const debouncedResult: OpenCageResponse = await client.debouncedGeocode(objectQuery)Reverse Geocoding with Type Safety
import type { ReverseGeocodingOptions } from '@geonot/typescript-sdk'
// Basic reverse geocoding - coordinates are strongly typed as numbers
const lat: number = 40.7128
const lng: number = -74.0060
const basicResult: OpenCageResponse = await client.reverseGeocode(lat, lng)
// With typed options
const options: ReverseGeocodingOptions = {
language: 'es', // Type: string
limit: 1, // Type: number
minConfidence: 5, // Type: number
noAnnotations: true, // Type: boolean
abbrv: false, // Type: boolean
addRequest: true, // Type: boolean
pretty: false // Type: boolean
}
const advancedResult: OpenCageResponse = await client.reverseGeocode(lat, lng, options)
// Type-safe access to reverse geocoding results
const address = advancedResult.results[0]?.formatted
const components = advancedResult.results[0]?.components
if (components) {
const city: string | undefined = components.city
const country: string | undefined = components.country
const postcode: string | undefined = components.postcode
}Batch Geocoding with Progress Tracking
import type {
BatchQueries,
BatchGeocodingResult,
BatchGeocodingProgress,
ProgressCallback
} from '@geonot/typescript-sdk'
const queries: BatchQueries = [
'London, UK',
'Paris, France',
{ query: 'Tokyo, Japan', language: 'ja' },
{ query: 'New York, USA', limit: 1 }
]
// Type-safe progress callback
const progressCallback: ProgressCallback = (progress: BatchGeocodingProgress) => {
console.log(`Progress: ${progress.percentage.toFixed(1)}%`)
console.log(`Completed: ${progress.completed}/${progress.total}`)
if (progress.errors.length > 0) {
progress.errors.forEach(error => {
console.error(`Error at index ${error.index}:`, error.error.message)
})
}
}
// Execute batch with full type safety
const batchResult: BatchGeocodingResult = await client.batchGeocode(queries, progressCallback)
// Process results with type safety
batchResult.results.forEach((result, index) => {
if (result) {
const bestMatch = result.results[0]
if (bestMatch) {
console.log(`Query ${index}: ${bestMatch.formatted}`)
console.log(`Coordinates: ${bestMatch.geometry.lat}, ${bestMatch.geometry.lng}`)
}
} else {
console.log(`Query ${index} failed`)
}
})
// Handle errors with full type information
if (batchResult.errors.length > 0) {
batchResult.errors.forEach(({ index, query, error }) => {
console.error(`Failed query at index ${index}:`, query)
console.error(`Error: ${error.message} (Code: ${error.code})`)
})
}Cache Management with Type Safety
import type { CacheStats } from '@geonot/typescript-sdk'
// Clear cache
client.clearCache()
// Get typed cache statistics
const stats: CacheStats | null = client.getCacheStats()
if (stats) {
console.log(`Cache size: ${stats.size}/${stats.maxSize}`)
console.log(`Hit rate: ${(stats.hitRate * 100).toFixed(1)}%`)
console.log(`Hits: ${stats.hits}, Misses: ${stats.misses}`)
}
// Update cache settings with validation
client.updateOptions({
cache: true,
cacheSize: 200,
cacheTtl: 600000 // 10 minutes
})Statistics and Monitoring
import type { ClientStats, RateLimit } from '@geonot/typescript-sdk'
// Get comprehensive statistics with full typing
const stats: ClientStats = client.getStats()
console.log({
totalRequests: stats.totalRequests, // number
errors: stats.errors, // number
cacheHits: stats.cacheHits, // number
cacheMisses: stats.cacheMisses, // number
lastRequest: stats.lastRequest, // string | null
rateLimit: stats.rateLimit, // RateLimit | null
cache: stats.cache // CacheStats | null
})
// Get rate limit information with null safety
const rateLimit: RateLimit | null = client.getRateLimitInfo()
if (rateLimit) {
const remaining: number = rateLimit.remaining
const limit: number = rateLimit.limit
const reset: number = rateLimit.reset
if (remaining < 100) {
console.warn(`Low quota: ${remaining}/${limit} requests remaining`)
console.log(`Resets at: ${new Date(reset).toLocaleString()}`)
}
}
// Health check with comprehensive typing
const health = client.getHealth()
console.log({
configured: health.configured, // boolean
cacheEnabled: health.cacheEnabled, // boolean
rateLimit: health.rateLimit, // RateLimit | null
lastError: health.lastError, // string | null
uptime: health.uptime // number
})Error Handling with Type Safety
import type { OpenCageError } from '@geonot/typescript-sdk'
import { isValidationError, ValidationError } from '@geonot/typescript-sdk'
try {
const result = await client.geocode('invalid query')
} catch (error) {
// Type-safe error handling
if (error instanceof Error) {
const opencageError = error as OpenCageError
console.error('Geocoding failed:', {
message: opencageError.message, // string
code: opencageError.code, // number
status: opencageError.status, // { code: number; message: string }
operation: opencageError.operation, // 'forward' | 'reverse' | 'batch' | undefined
context: opencageError.context, // unknown
timestamp: opencageError.timestamp, // string | undefined
rateLimitRemaining: opencageError.rateLimitRemaining // number | undefined
})
// Handle specific error types with type safety
switch (opencageError.code) {
case 402:
console.error('Quota exceeded - upgrade your plan')
break
case 401:
console.error('Invalid API key')
break
case 408:
console.error('Request timeout - try again')
break
case 429:
console.error('Rate limit exceeded')
break
default:
console.error(`API error: ${opencageError.message}`)
}
}
// Handle validation errors specifically
if (isValidationError(error)) {
const validationError = error as ValidationError
console.error(`Validation error in field ${validationError.field}: ${validationError.message}`)
}
}API Key Validation
// Validate API key with type safety
try {
const isValid: boolean = await client.validateApiKey()
if (isValid) {
console.log('✅ API key is valid')
} else {
console.error('❌ Invalid API key')
}
} catch (error) {
console.error('Could not validate API key:', (error as Error).message)
}
// Check configuration state
const isConfigured: boolean = client.isConfigured()
if (!isConfigured) {
console.error('Client is not properly configured')
}🛠️ Type-Safe Utilities
The SDK includes comprehensive utilities with full TypeScript support:
import {
formatCoordinates,
calculateDistance,
getBestResult,
isWithinBounds,
parseCoordinates,
createBounds,
COUNTRY_CODES,
LANGUAGE_CODES,
type CountryCode,
type LanguageCode
} from '@geonot/typescript-sdk'
// Format coordinates with type safety
const formatted: string = formatCoordinates(40.7128, -74.0060, 4) // "40.7128, -74.0060"
// Calculate distance with automatic type checking
const distance: number = calculateDistance(40.7128, -74.0060, 34.0522, -118.2437)
console.log(`Distance: ${distance.toFixed(0)} km`)
// Get best result with generic type support
interface ResultWithConfidence { confidence: number; formatted: string }
const results: ResultWithConfidence[] = [
{ confidence: 5, formatted: 'Result 1' },
{ confidence: 9, formatted: 'Result 2' }
]
const best: ResultWithConfidence | null = getBestResult(results)
// Bounds checking with tuple types
const bounds: [number, number, number, number] = [40, -75, 41, -73]
const inBounds: boolean = isWithinBounds(40.7128, -74.0060, bounds)
// Parse coordinates from strings
const coords = parseCoordinates('40.7128, -74.0060')
if (coords) {
const { lat, lng } = coords // Both typed as number
}
// Create bounds with type safety
const boundingBox: [number, number, number, number] = createBounds(40.7128, -74.0060, 10)
// Use predefined constants with type safety
const countryCode: CountryCode = COUNTRY_CODES.UNITED_STATES
const languageCode: LanguageCode = LANGUAGE_CODES.ENGLISH
const query = {
query: 'Berlin',
country: countryCode, // Typed as CountryCode
language: languageCode // Typed as LanguageCode
}🎛️ Advanced TypeScript Configuration
Custom Type Extensions
// Extend types for your specific use case
interface ExtendedGeocodingQuery extends GeocodingQuery {
customField?: string
}
interface MyAppResponse extends OpenCageResponse {
appMetadata?: {
timestamp: number
userId: string
}
}
// Create typed client wrapper
class MyAppGeocodingService {
private client: OpenCageClient
constructor(apiKey: string) {
this.client = new OpenCageClient({ apiKey })
}
async geocodeWithMetadata(query: ExtendedGeocodingQuery): Promise<MyAppResponse> {
const result = await this.client.geocode(query)
return {
...result,
appMetadata: {
timestamp: Date.now(),
userId: 'current-user-id'
}
} as MyAppResponse
}
}Generic Utilities
import type { GeocodingResult } from '@geonot/typescript-sdk'
// Create type-safe result processors
function processResults<T extends GeocodingResult>(
results: T[],
processor: (result: T) => string
): string[] {
return results.map(processor)
}
// Use with full type safety
const addresses = processResults(response.results, (result) => {
return `${result.formatted} (${result.confidence}/10)`
})Strict Type Configuration
// tsconfig.json for maximum type safety
{
"compilerOptions": {
"strict": true,
"noImplicitAny": true,
"noImplicitReturns": true,
"noImplicitThis": true,
"noUncheckedIndexedAccess": true,
"exactOptionalPropertyTypes": true
}
}🔧 Browser and Runtime Compatibility
- TypeScript: 4.7+
- Node.js: 16+
- Browsers:
- Chrome 90+
- Firefox 88+
- Safari 14+
- Edge 90+
📊 Bundle Analysis
- Core SDK: ~12KB gzipped with full TypeScript definitions
- Tree-shakable: Import only the functions you need
- Zero dependencies: No external runtime dependencies
- Type definitions: ~8KB for complete type coverage
🧪 Development with TypeScript
# Install with TypeScript support
npm install @geonot/typescript-sdk
# Development dependencies (optional)
npm install -D typescript @types/node
# Build your TypeScript project
npx tsc
# Lint with TypeScript-aware rules
npx eslint src --ext .ts🤝 Contributing
We welcome contributions! Please ensure all code follows TypeScript best practices:
- Type Safety: All code must be fully typed with no
anytypes - Strict Mode: Must pass with
strict: truein TypeScript - Tests: Include comprehensive type tests
- Documentation: Update type definitions and examples
📄 License
This project is licensed under the MIT License - see the LICENSE file for details.
🙏 Acknowledgments
- OpenCage Data for the excellent geocoding API
- TypeScript community for the amazing tooling and ecosystem
- Built with enterprise-grade TypeScript patterns and best practices
📞 Support
- 📧 Email: [email protected]
- 🐛 Issues: GitHub Issues
- 📚 TypeScript Docs: Full API Documentation
- 🔍 Type Definitions: Included with package for full IntelliSense support
Made with 💙 and TypeScript by Rome Stone
"Type safety isn't just about preventing errors—it's about enabling fearless refactoring, better developer experience, and building software that scales."
