csv2geo-sdk 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,208 @@
1
+ # CSV2GEO Node.js SDK
2
+
3
+ [![npm version](https://img.shields.io/npm/v/csv2geo-sdk.svg)](https://www.npmjs.com/package/csv2geo-sdk)
4
+ [![Node.js versions](https://img.shields.io/node/v/csv2geo-sdk.svg)](https://www.npmjs.com/package/csv2geo-sdk)
5
+ [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT)
6
+
7
+ Official Node.js SDK for the [CSV2GEO Geocoding API](https://csv2geo.com) - fast, accurate geocoding powered by 446M+ addresses worldwide.
8
+
9
+ ## Installation
10
+
11
+ ```bash
12
+ npm install csv2geo-sdk
13
+ ```
14
+
15
+ ## Quick Start
16
+
17
+ ```javascript
18
+ const { Client } = require('csv2geo-sdk');
19
+
20
+ // Initialize with your API key
21
+ const client = new Client('your_api_key');
22
+
23
+ // Forward geocoding (address → coordinates)
24
+ const result = await client.geocode('1600 Pennsylvania Ave, Washington DC');
25
+ if (result) {
26
+ console.log(`Lat: ${result.lat}, Lng: ${result.lng}`);
27
+ console.log(`Address: ${result.formattedAddress}`);
28
+ }
29
+
30
+ // Reverse geocoding (coordinates → address)
31
+ const result = await client.reverse(38.8977, -77.0365);
32
+ if (result) {
33
+ console.log(`Address: ${result.formattedAddress}`);
34
+ }
35
+ ```
36
+
37
+ ## Features
38
+
39
+ - **Forward geocoding** - Convert addresses to coordinates
40
+ - **Reverse geocoding** - Convert coordinates to addresses
41
+ - **Batch processing** - Geocode up to 10,000 addresses per request
42
+ - **Auto-retry** - Automatic retry on rate limits
43
+ - **TypeScript support** - Full type definitions included
44
+ - **Zero dependencies** - Uses native `fetch` (Node.js 18+)
45
+
46
+ ## API Reference
47
+
48
+ ### Initialize Client
49
+
50
+ ```javascript
51
+ const { Client } = require('csv2geo-sdk');
52
+
53
+ const client = new Client('your_api_key', {
54
+ baseUrl: 'https://api.csv2geo.com/v1', // optional
55
+ timeout: 30000, // optional, milliseconds
56
+ autoRetry: true, // optional, retry on rate limit
57
+ });
58
+ ```
59
+
60
+ ### Forward Geocoding
61
+
62
+ ```javascript
63
+ // Simple - returns best match or null
64
+ const result = await client.geocode('1600 Pennsylvania Ave, Washington DC');
65
+
66
+ // With country filter
67
+ const result = await client.geocode('123 Main St', { country: 'US' });
68
+
69
+ // Full response with all matches
70
+ const response = await client.geocodeFull('1600 Pennsylvania Ave');
71
+ for (const result of response.results) {
72
+ console.log(`${result.formattedAddress}: ${result.accuracyScore}`);
73
+ }
74
+ ```
75
+
76
+ ### Reverse Geocoding
77
+
78
+ ```javascript
79
+ // Simple - returns best match or null
80
+ const result = await client.reverse(38.8977, -77.0365);
81
+
82
+ // Full response with all matches
83
+ const response = await client.reverseFull(38.8977, -77.0365);
84
+ ```
85
+
86
+ ### Batch Geocoding
87
+
88
+ ```javascript
89
+ // Geocode multiple addresses (up to 10,000)
90
+ const addresses = [
91
+ '1600 Pennsylvania Ave, Washington DC',
92
+ '350 Fifth Avenue, New York, NY',
93
+ '1 Infinite Loop, Cupertino, CA',
94
+ ];
95
+
96
+ const results = await client.geocodeBatch(addresses);
97
+ for (const response of results) {
98
+ const best = response.results[0];
99
+ if (best) {
100
+ console.log(`${response.query}: ${best.lat}, ${best.lng}`);
101
+ } else {
102
+ console.log(`${response.query}: Not found`);
103
+ }
104
+ }
105
+ ```
106
+
107
+ ### Batch Reverse Geocoding
108
+
109
+ ```javascript
110
+ // Reverse geocode multiple coordinates
111
+ const coordinates = [
112
+ { lat: 38.8977, lng: -77.0365 },
113
+ { lat: 40.7484, lng: -73.9857 },
114
+ ];
115
+
116
+ const results = await client.reverseBatch(coordinates);
117
+ for (const response of results) {
118
+ const best = response.results[0];
119
+ if (best) {
120
+ console.log(best.formattedAddress);
121
+ }
122
+ }
123
+ ```
124
+
125
+ ### GeocodeResult Object
126
+
127
+ ```javascript
128
+ const result = await client.geocode('1600 Pennsylvania Ave, Washington DC');
129
+
130
+ // Coordinates
131
+ result.lat // 38.8977
132
+ result.lng // -77.0365
133
+
134
+ // Address
135
+ result.formattedAddress // "1600 PENNSYLVANIA AVE NW, WASHINGTON, DC 20500, US"
136
+ result.accuracy // "rooftop"
137
+ result.accuracyScore // 1.0
138
+
139
+ // Components
140
+ result.components.houseNumber // "1600"
141
+ result.components.street // "PENNSYLVANIA AVE NW"
142
+ result.components.city // "WASHINGTON"
143
+ result.components.state // "DC"
144
+ result.components.postcode // "20500"
145
+ result.components.country // "US"
146
+ ```
147
+
148
+ ## Error Handling
149
+
150
+ ```javascript
151
+ const { Client, AuthenticationError, RateLimitError, InvalidRequestError } = require('csv2geo-sdk');
152
+
153
+ const client = new Client('your_api_key');
154
+
155
+ try {
156
+ const result = await client.geocode('123 Main St');
157
+ } catch (err) {
158
+ if (err instanceof AuthenticationError) {
159
+ console.log(`Invalid API key: ${err.message}`);
160
+ } else if (err instanceof RateLimitError) {
161
+ console.log(`Rate limited. Retry after ${err.retryAfter} seconds`);
162
+ } else if (err instanceof InvalidRequestError) {
163
+ console.log(`Invalid request: ${err.message}`);
164
+ }
165
+ }
166
+ ```
167
+
168
+ ## Rate Limits
169
+
170
+ The client tracks rate limit headers automatically:
171
+
172
+ ```javascript
173
+ await client.geocode('123 Main St');
174
+
175
+ console.log(client.rateLimit); // Max requests per minute
176
+ console.log(client.rateLimitRemaining); // Requests remaining
177
+ console.log(client.rateLimitReset); // Unix timestamp when limit resets
178
+ ```
179
+
180
+ With `autoRetry: true` (default), the client automatically waits and retries when rate limited.
181
+
182
+ ## TypeScript
183
+
184
+ Full TypeScript support is included:
185
+
186
+ ```typescript
187
+ import { Client, GeocodeResult, GeocodeResponse } from 'csv2geo-sdk';
188
+
189
+ const client = new Client('your_api_key');
190
+ const result: GeocodeResult | null = await client.geocode('123 Main St');
191
+ ```
192
+
193
+ ## Requirements
194
+
195
+ - Node.js 16+ (uses native `fetch`)
196
+
197
+ ## Get Your API Key
198
+
199
+ Sign up at [csv2geo.com](https://csv2geo.com) to get your API key.
200
+
201
+ ## Documentation
202
+
203
+ - [API Documentation](https://acenji.github.io/csv2geo-api/docs/)
204
+ - [OpenAPI Specification](https://github.com/acenji/csv2geo-api/blob/main/openapi.yaml)
205
+
206
+ ## License
207
+
208
+ MIT License - see [LICENSE](https://github.com/acenji/csv2geo-api/blob/main/LICENSE) for details.
package/package.json ADDED
@@ -0,0 +1,40 @@
1
+ {
2
+ "name": "csv2geo-sdk",
3
+ "version": "1.0.0",
4
+ "description": "Node.js SDK for CSV2GEO Geocoding API",
5
+ "main": "src/index.js",
6
+ "types": "src/index.d.ts",
7
+ "files": [
8
+ "src/**/*"
9
+ ],
10
+ "scripts": {
11
+ "test": "node --test test/*.test.js",
12
+ "lint": "eslint src/"
13
+ },
14
+ "keywords": [
15
+ "geocoding",
16
+ "geocode",
17
+ "address",
18
+ "latitude",
19
+ "longitude",
20
+ "maps",
21
+ "csv2geo",
22
+ "geolocation"
23
+ ],
24
+ "author": "Scale Campaign <admin@csv2geo.com>",
25
+ "license": "MIT",
26
+ "repository": {
27
+ "type": "git",
28
+ "url": "git+https://github.com/acenji/csv2geo-api.git"
29
+ },
30
+ "bugs": {
31
+ "url": "https://github.com/acenji/csv2geo-api/issues"
32
+ },
33
+ "homepage": "https://csv2geo.com",
34
+ "engines": {
35
+ "node": ">=16.0.0"
36
+ },
37
+ "devDependencies": {
38
+ "eslint": "^8.0.0"
39
+ }
40
+ }
package/src/errors.js ADDED
@@ -0,0 +1,75 @@
1
+ /**
2
+ * Custom errors for CSV2GEO SDK
3
+ */
4
+
5
+ /**
6
+ * Base error class for CSV2GEO SDK
7
+ */
8
+ class CSV2GEOError extends Error {
9
+ constructor(message, code = null, status = null) {
10
+ super(message);
11
+ this.name = 'CSV2GEOError';
12
+ this.code = code;
13
+ this.status = status;
14
+ }
15
+ }
16
+
17
+ /**
18
+ * Raised when API key is missing, invalid, or revoked
19
+ */
20
+ class AuthenticationError extends CSV2GEOError {
21
+ constructor(message, code = null, status = 401) {
22
+ super(message, code, status);
23
+ this.name = 'AuthenticationError';
24
+ }
25
+ }
26
+
27
+ /**
28
+ * Raised when rate limit is exceeded
29
+ */
30
+ class RateLimitError extends CSV2GEOError {
31
+ constructor(message, code = null, status = 429, retryAfter = null) {
32
+ super(message, code, status);
33
+ this.name = 'RateLimitError';
34
+ this.retryAfter = retryAfter;
35
+ }
36
+ }
37
+
38
+ /**
39
+ * Raised when request parameters are invalid
40
+ */
41
+ class InvalidRequestError extends CSV2GEOError {
42
+ constructor(message, code = null, status = 400) {
43
+ super(message, code, status);
44
+ this.name = 'InvalidRequestError';
45
+ }
46
+ }
47
+
48
+ /**
49
+ * Raised when API key lacks required permission
50
+ */
51
+ class PermissionError extends CSV2GEOError {
52
+ constructor(message, code = null, status = 403) {
53
+ super(message, code, status);
54
+ this.name = 'PermissionError';
55
+ }
56
+ }
57
+
58
+ /**
59
+ * Raised for general API errors
60
+ */
61
+ class APIError extends CSV2GEOError {
62
+ constructor(message, code = null, status = 500) {
63
+ super(message, code, status);
64
+ this.name = 'APIError';
65
+ }
66
+ }
67
+
68
+ module.exports = {
69
+ CSV2GEOError,
70
+ AuthenticationError,
71
+ RateLimitError,
72
+ InvalidRequestError,
73
+ PermissionError,
74
+ APIError,
75
+ };
package/src/index.d.ts ADDED
@@ -0,0 +1,126 @@
1
+ /**
2
+ * CSV2GEO Node.js SDK Type Definitions
3
+ */
4
+
5
+ export interface ClientOptions {
6
+ /** API base URL (default: https://api.csv2geo.com/v1) */
7
+ baseUrl?: string;
8
+ /** Request timeout in milliseconds (default: 30000) */
9
+ timeout?: number;
10
+ /** Auto-retry on rate limit (default: true) */
11
+ autoRetry?: boolean;
12
+ }
13
+
14
+ export interface GeocodeOptions {
15
+ /** Limit to specific country (ISO 3166-1 alpha-2) */
16
+ country?: string;
17
+ }
18
+
19
+ export interface AddressComponents {
20
+ houseNumber?: string;
21
+ street?: string;
22
+ unit?: string;
23
+ city?: string;
24
+ state?: string;
25
+ postcode?: string;
26
+ country?: string;
27
+ }
28
+
29
+ export interface GeocodeResult {
30
+ formattedAddress: string;
31
+ lat: number;
32
+ lng: number;
33
+ accuracy: string;
34
+ accuracyScore: number;
35
+ components: AddressComponents;
36
+ }
37
+
38
+ export interface GeocodeResponse {
39
+ query: string | { lat: number; lng: number };
40
+ results: GeocodeResult[];
41
+ }
42
+
43
+ export interface Coordinate {
44
+ lat: number;
45
+ lng: number;
46
+ }
47
+
48
+ export class Client {
49
+ /** Max requests per minute */
50
+ rateLimit: string | null;
51
+ /** Requests remaining in current window */
52
+ rateLimitRemaining: string | null;
53
+ /** Unix timestamp when limit resets */
54
+ rateLimitReset: string | null;
55
+
56
+ /**
57
+ * Create a new CSV2GEO client
58
+ * @param apiKey Your CSV2GEO API key
59
+ * @param options Configuration options
60
+ */
61
+ constructor(apiKey: string, options?: ClientOptions);
62
+
63
+ /**
64
+ * Geocode a single address
65
+ * @param address The address to geocode
66
+ * @param options Options
67
+ * @returns Best result or null if not found
68
+ */
69
+ geocode(address: string, options?: GeocodeOptions): Promise<GeocodeResult | null>;
70
+
71
+ /**
72
+ * Geocode with full response
73
+ * @param address The address to geocode
74
+ * @param options Options
75
+ * @returns Full response with all results
76
+ */
77
+ geocodeFull(address: string, options?: GeocodeOptions): Promise<GeocodeResponse>;
78
+
79
+ /**
80
+ * Reverse geocode coordinates
81
+ * @param lat Latitude
82
+ * @param lng Longitude
83
+ * @returns Best result or null if not found
84
+ */
85
+ reverse(lat: number, lng: number): Promise<GeocodeResult | null>;
86
+
87
+ /**
88
+ * Reverse geocode with full response
89
+ * @param lat Latitude
90
+ * @param lng Longitude
91
+ * @returns Full response with all results
92
+ */
93
+ reverseFull(lat: number, lng: number): Promise<GeocodeResponse>;
94
+
95
+ /**
96
+ * Batch geocode multiple addresses
97
+ * @param addresses Array of addresses (max 10,000)
98
+ * @returns Array of responses
99
+ */
100
+ geocodeBatch(addresses: string[]): Promise<GeocodeResponse[]>;
101
+
102
+ /**
103
+ * Batch reverse geocode multiple coordinates
104
+ * @param coordinates Array of coordinates (max 10,000)
105
+ * @returns Array of responses
106
+ */
107
+ reverseBatch(coordinates: Coordinate[]): Promise<GeocodeResponse[]>;
108
+ }
109
+
110
+ export class CSV2GEOError extends Error {
111
+ code: string | null;
112
+ status: number | null;
113
+ constructor(message: string, code?: string | null, status?: number | null);
114
+ }
115
+
116
+ export class AuthenticationError extends CSV2GEOError {}
117
+
118
+ export class RateLimitError extends CSV2GEOError {
119
+ retryAfter: number | null;
120
+ }
121
+
122
+ export class InvalidRequestError extends CSV2GEOError {}
123
+
124
+ export class PermissionError extends CSV2GEOError {}
125
+
126
+ export class APIError extends CSV2GEOError {}
package/src/index.js ADDED
@@ -0,0 +1,296 @@
1
+ /**
2
+ * CSV2GEO Node.js SDK
3
+ *
4
+ * Fast, accurate geocoding powered by 446M+ addresses worldwide.
5
+ *
6
+ * @example
7
+ * const { Client } = require('csv2geo');
8
+ *
9
+ * const client = new Client('your_api_key');
10
+ * const result = await client.geocode('1600 Pennsylvania Ave, Washington DC');
11
+ * console.log(result.lat, result.lng);
12
+ */
13
+
14
+ const {
15
+ CSV2GEOError,
16
+ AuthenticationError,
17
+ RateLimitError,
18
+ InvalidRequestError,
19
+ PermissionError,
20
+ APIError,
21
+ } = require('./errors');
22
+
23
+ const DEFAULT_BASE_URL = 'https://api.csv2geo.com/v1';
24
+ const DEFAULT_TIMEOUT = 30000;
25
+ const MAX_RETRIES = 3;
26
+
27
+ /**
28
+ * CSV2GEO API Client
29
+ */
30
+ class Client {
31
+ /**
32
+ * Create a new CSV2GEO client
33
+ * @param {string} apiKey - Your CSV2GEO API key
34
+ * @param {Object} [options] - Configuration options
35
+ * @param {string} [options.baseUrl] - API base URL
36
+ * @param {number} [options.timeout] - Request timeout in milliseconds
37
+ * @param {boolean} [options.autoRetry] - Auto-retry on rate limit (default: true)
38
+ */
39
+ constructor(apiKey, options = {}) {
40
+ if (!apiKey) {
41
+ throw new Error('API key is required');
42
+ }
43
+
44
+ this.apiKey = apiKey;
45
+ this.baseUrl = (options.baseUrl || DEFAULT_BASE_URL).replace(/\/$/, '');
46
+ this.timeout = options.timeout || DEFAULT_TIMEOUT;
47
+ this.autoRetry = options.autoRetry !== false;
48
+
49
+ // Rate limit tracking
50
+ this.rateLimit = null;
51
+ this.rateLimitRemaining = null;
52
+ this.rateLimitReset = null;
53
+ }
54
+
55
+ /**
56
+ * Make an API request
57
+ * @private
58
+ */
59
+ async _request(method, endpoint, params = {}, body = null, retryCount = 0) {
60
+ const url = new URL(`${this.baseUrl}${endpoint}`);
61
+
62
+ // Add query parameters
63
+ Object.entries(params).forEach(([key, value]) => {
64
+ if (value !== undefined && value !== null) {
65
+ url.searchParams.append(key, value);
66
+ }
67
+ });
68
+
69
+ const controller = new AbortController();
70
+ const timeoutId = setTimeout(() => controller.abort(), this.timeout);
71
+
72
+ try {
73
+ const fetchOptions = {
74
+ method,
75
+ headers: {
76
+ 'Authorization': `Bearer ${this.apiKey}`,
77
+ 'Content-Type': 'application/json',
78
+ 'User-Agent': 'csv2geo-node/1.0.0',
79
+ },
80
+ signal: controller.signal,
81
+ };
82
+
83
+ if (body) {
84
+ fetchOptions.body = JSON.stringify(body);
85
+ }
86
+
87
+ const response = await fetch(url.toString(), fetchOptions);
88
+ clearTimeout(timeoutId);
89
+
90
+ // Update rate limit info
91
+ this.rateLimit = response.headers.get('X-RateLimit-Limit');
92
+ this.rateLimitRemaining = response.headers.get('X-RateLimit-Remaining');
93
+ this.rateLimitReset = response.headers.get('X-RateLimit-Reset');
94
+
95
+ const data = await response.json();
96
+
97
+ if (response.ok) {
98
+ return data;
99
+ }
100
+
101
+ // Handle errors
102
+ const error = data.error || {};
103
+ const code = error.code || 'unknown';
104
+ const message = error.message || 'Unknown error';
105
+ const status = error.status || response.status;
106
+
107
+ if (response.status === 401) {
108
+ throw new AuthenticationError(message, code, status);
109
+ } else if (response.status === 403) {
110
+ throw new PermissionError(message, code, status);
111
+ } else if (response.status === 429) {
112
+ const retryAfter = parseInt(response.headers.get('Retry-After') || '60', 10);
113
+ const rateLimitError = new RateLimitError(message, code, status, retryAfter);
114
+
115
+ if (this.autoRetry && retryCount < MAX_RETRIES) {
116
+ await this._sleep(Math.min(retryAfter * 1000, 60000));
117
+ return this._request(method, endpoint, params, body, retryCount + 1);
118
+ }
119
+ throw rateLimitError;
120
+ } else if (response.status === 400) {
121
+ throw new InvalidRequestError(message, code, status);
122
+ } else {
123
+ throw new APIError(message, code, status);
124
+ }
125
+ } catch (err) {
126
+ clearTimeout(timeoutId);
127
+
128
+ if (err.name === 'AbortError') {
129
+ throw new APIError('Request timed out', 'timeout');
130
+ }
131
+ if (err instanceof CSV2GEOError) {
132
+ throw err;
133
+ }
134
+ throw new APIError(err.message, 'network_error');
135
+ }
136
+ }
137
+
138
+ /**
139
+ * Sleep helper
140
+ * @private
141
+ */
142
+ _sleep(ms) {
143
+ return new Promise(resolve => setTimeout(resolve, ms));
144
+ }
145
+
146
+ /**
147
+ * Geocode a single address
148
+ * @param {string} address - The address to geocode
149
+ * @param {Object} [options] - Options
150
+ * @param {string} [options.country] - Limit to specific country (ISO 3166-1 alpha-2)
151
+ * @returns {Promise<GeocodeResult|null>} Best result or null if not found
152
+ *
153
+ * @example
154
+ * const result = await client.geocode('1600 Pennsylvania Ave, Washington DC');
155
+ * if (result) {
156
+ * console.log(result.lat, result.lng);
157
+ * }
158
+ */
159
+ async geocode(address, options = {}) {
160
+ const params = { q: address };
161
+ if (options.country) params.country = options.country;
162
+
163
+ const data = await this._request('GET', '/geocode', params);
164
+ const results = data.results || [];
165
+ return results.length > 0 ? this._parseResult(results[0]) : null;
166
+ }
167
+
168
+ /**
169
+ * Geocode with full response
170
+ * @param {string} address - The address to geocode
171
+ * @param {Object} [options] - Options
172
+ * @returns {Promise<GeocodeResponse>} Full response with all results
173
+ */
174
+ async geocodeFull(address, options = {}) {
175
+ const params = { q: address };
176
+ if (options.country) params.country = options.country;
177
+
178
+ const data = await this._request('GET', '/geocode', params);
179
+ return {
180
+ query: data.query,
181
+ results: (data.results || []).map(r => this._parseResult(r)),
182
+ };
183
+ }
184
+
185
+ /**
186
+ * Reverse geocode coordinates
187
+ * @param {number} lat - Latitude
188
+ * @param {number} lng - Longitude
189
+ * @returns {Promise<GeocodeResult|null>} Best result or null if not found
190
+ *
191
+ * @example
192
+ * const result = await client.reverse(38.8977, -77.0365);
193
+ * if (result) {
194
+ * console.log(result.formattedAddress);
195
+ * }
196
+ */
197
+ async reverse(lat, lng) {
198
+ const data = await this._request('GET', '/reverse', { lat, lng });
199
+ const results = data.results || [];
200
+ return results.length > 0 ? this._parseResult(results[0]) : null;
201
+ }
202
+
203
+ /**
204
+ * Reverse geocode with full response
205
+ * @param {number} lat - Latitude
206
+ * @param {number} lng - Longitude
207
+ * @returns {Promise<GeocodeResponse>} Full response with all results
208
+ */
209
+ async reverseFull(lat, lng) {
210
+ const data = await this._request('GET', '/reverse', { lat, lng });
211
+ return {
212
+ query: data.query,
213
+ results: (data.results || []).map(r => this._parseResult(r)),
214
+ };
215
+ }
216
+
217
+ /**
218
+ * Batch geocode multiple addresses
219
+ * @param {string[]} addresses - Array of addresses (max 10,000)
220
+ * @returns {Promise<GeocodeResponse[]>} Array of responses
221
+ *
222
+ * @example
223
+ * const results = await client.geocodeBatch([
224
+ * '1600 Pennsylvania Ave, Washington DC',
225
+ * '350 Fifth Avenue, New York, NY',
226
+ * ]);
227
+ */
228
+ async geocodeBatch(addresses) {
229
+ if (addresses.length > 10000) {
230
+ throw new InvalidRequestError('Maximum 10,000 addresses per batch request');
231
+ }
232
+
233
+ const data = await this._request('POST', '/geocode', {}, { addresses });
234
+ return (data.results || []).map(r => ({
235
+ query: r.query,
236
+ results: (r.results || []).map(res => this._parseResult(res)),
237
+ }));
238
+ }
239
+
240
+ /**
241
+ * Batch reverse geocode multiple coordinates
242
+ * @param {Array<{lat: number, lng: number}>} coordinates - Array of coordinates (max 10,000)
243
+ * @returns {Promise<GeocodeResponse[]>} Array of responses
244
+ *
245
+ * @example
246
+ * const results = await client.reverseBatch([
247
+ * { lat: 38.8977, lng: -77.0365 },
248
+ * { lat: 40.7484, lng: -73.9857 },
249
+ * ]);
250
+ */
251
+ async reverseBatch(coordinates) {
252
+ if (coordinates.length > 10000) {
253
+ throw new InvalidRequestError('Maximum 10,000 coordinates per batch request');
254
+ }
255
+
256
+ const data = await this._request('POST', '/reverse', {}, { coordinates });
257
+ return (data.results || []).map(r => ({
258
+ query: r.query,
259
+ results: (r.results || []).map(res => this._parseResult(res)),
260
+ }));
261
+ }
262
+
263
+ /**
264
+ * Parse API result into GeocodeResult
265
+ * @private
266
+ */
267
+ _parseResult(data) {
268
+ const location = data.location || {};
269
+ return {
270
+ formattedAddress: data.formatted_address,
271
+ lat: location.lat,
272
+ lng: location.lng,
273
+ accuracy: data.accuracy,
274
+ accuracyScore: data.accuracy_score,
275
+ components: {
276
+ houseNumber: data.components?.house_number,
277
+ street: data.components?.street,
278
+ unit: data.components?.unit,
279
+ city: data.components?.city,
280
+ state: data.components?.state,
281
+ postcode: data.components?.postcode,
282
+ country: data.components?.country,
283
+ },
284
+ };
285
+ }
286
+ }
287
+
288
+ module.exports = {
289
+ Client,
290
+ CSV2GEOError,
291
+ AuthenticationError,
292
+ RateLimitError,
293
+ InvalidRequestError,
294
+ PermissionError,
295
+ APIError,
296
+ };