geogridapi 0.0.0-stage → 0.1.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/dist/index.cjs ADDED
@@ -0,0 +1,540 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ APIConnectionError: () => APIConnectionError,
24
+ APIError: () => APIError,
25
+ AuthenticationError: () => AuthenticationError,
26
+ ConflictError: () => ConflictError,
27
+ DEFAULT_BASE_URL: () => DEFAULT_BASE_URL,
28
+ DEFAULT_TOLERANCE_SECONDS: () => DEFAULT_TOLERANCE_SECONDS,
29
+ GeoGridAPI: () => GeoGridAPI,
30
+ GeoGridAPIError: () => GeoGridAPIError,
31
+ InsufficientCreditsError: () => InsufficientCreditsError,
32
+ InvalidRequestError: () => InvalidRequestError,
33
+ NotFoundError: () => NotFoundError,
34
+ RateLimitError: () => RateLimitError,
35
+ SIGNATURE_HEADER: () => SIGNATURE_HEADER,
36
+ UnprocessableEntityError: () => UnprocessableEntityError,
37
+ VERSION: () => VERSION,
38
+ WaitTimeoutError: () => WaitTimeoutError,
39
+ WebhookSignatureError: () => WebhookSignatureError,
40
+ constructEvent: () => constructEvent,
41
+ default: () => index_default,
42
+ verifyWebhookSignature: () => verifyWebhookSignature
43
+ });
44
+ module.exports = __toCommonJS(index_exports);
45
+
46
+ // src/errors.ts
47
+ var GeoGridAPIError = class extends Error {
48
+ /** HTTP status, `null` when no response was received. */
49
+ status;
50
+ /** API error code (`invalid_body`, `insufficient_points`, `rate_limited`…). */
51
+ code;
52
+ /** Seconds to wait before retrying, from the `Retry-After` header. */
53
+ retryAfter;
54
+ /** Parsed error body, when there is one. */
55
+ body;
56
+ /** Set when the error happened after a grid was created (while waiting for it): read it later with `grids.get`. */
57
+ gridId;
58
+ /** Set when the error happened after a batch was created (while waiting for it): read it later with `batches.get`. */
59
+ batchId;
60
+ constructor(message, options = {}) {
61
+ super(message, options.cause === void 0 ? void 0 : { cause: options.cause });
62
+ this.name = new.target.name;
63
+ this.status = options.status ?? null;
64
+ this.code = options.code ?? "unknown_error";
65
+ this.retryAfter = options.retryAfter ?? null;
66
+ this.body = options.body;
67
+ if (options.gridId) this.gridId = options.gridId;
68
+ if (options.batchId) this.batchId = options.batchId;
69
+ }
70
+ };
71
+ var InvalidRequestError = class extends GeoGridAPIError {
72
+ };
73
+ var AuthenticationError = class extends GeoGridAPIError {
74
+ };
75
+ var InsufficientCreditsError = class extends GeoGridAPIError {
76
+ };
77
+ var NotFoundError = class extends GeoGridAPIError {
78
+ };
79
+ var ConflictError = class extends GeoGridAPIError {
80
+ };
81
+ var UnprocessableEntityError = class extends GeoGridAPIError {
82
+ };
83
+ var RateLimitError = class extends GeoGridAPIError {
84
+ };
85
+ var APIError = class extends GeoGridAPIError {
86
+ };
87
+ var APIConnectionError = class extends GeoGridAPIError {
88
+ };
89
+ var WaitTimeoutError = class extends GeoGridAPIError {
90
+ };
91
+ function errorFromResponse(status, body, retryAfter) {
92
+ const envelope = body?.error;
93
+ const code = typeof envelope?.code === "string" ? envelope.code : `http_${status}`;
94
+ const message = typeof envelope?.message === "string" ? envelope.message : `GeoGridAPI request failed with status ${status}`;
95
+ const options = { status, code, retryAfter, body };
96
+ if (status === 400) return new InvalidRequestError(message, options);
97
+ if (status === 401) return new AuthenticationError(message, options);
98
+ if (status === 402) return new InsufficientCreditsError(message, options);
99
+ if (status === 404) return new NotFoundError(message, options);
100
+ if (status === 409) return new ConflictError(message, options);
101
+ if (status === 422) return new UnprocessableEntityError(message, options);
102
+ if (status === 429) return new RateLimitError(message, options);
103
+ if (status >= 500) return new APIError(message, options);
104
+ return new GeoGridAPIError(message, options);
105
+ }
106
+ function isTransient(error) {
107
+ return error.status === null || error.status === 408 || error.status === 429 || error.status >= 500;
108
+ }
109
+
110
+ // src/version.ts
111
+ var VERSION = "0.1.0";
112
+
113
+ // src/core.ts
114
+ var DEFAULT_BASE_URL = "https://api.geogridapi.io";
115
+ var DEFAULT_TIMEOUT_MS = 6e4;
116
+ var DEFAULT_MAX_RETRIES = 2;
117
+ var MAX_RETRY_DELAY_MS = 6e4;
118
+ function readEnv(name) {
119
+ const env = globalThis.process?.env;
120
+ return env?.[name]?.trim() || void 0;
121
+ }
122
+ function sleep(ms, signal) {
123
+ return new Promise((resolve, reject) => {
124
+ if (signal?.aborted) return reject(signal.reason);
125
+ const timer = setTimeout(() => {
126
+ signal?.removeEventListener("abort", onAbort);
127
+ resolve();
128
+ }, ms);
129
+ const onAbort = () => {
130
+ clearTimeout(timer);
131
+ reject(signal?.reason);
132
+ };
133
+ signal?.addEventListener("abort", onAbort, { once: true });
134
+ });
135
+ }
136
+ function parseRetryAfter(value) {
137
+ if (!value) return null;
138
+ const seconds = Number(value);
139
+ if (Number.isFinite(seconds) && seconds >= 0) return seconds;
140
+ const date = Date.parse(value);
141
+ return Number.isNaN(date) ? null : Math.max(0, (date - Date.now()) / 1e3);
142
+ }
143
+ function retryDelayMs(attempt, retryAfter) {
144
+ if (retryAfter !== null) return Math.min(retryAfter * 1e3, MAX_RETRY_DELAY_MS);
145
+ const base = Math.min(500 * 2 ** attempt, 8e3);
146
+ return base / 2 + Math.random() * (base / 2);
147
+ }
148
+ function combineSignals(timeoutMs, signal) {
149
+ const controller = new AbortController();
150
+ const timer = setTimeout(() => controller.abort(new DOMException("Request timed out", "TimeoutError")), timeoutMs);
151
+ const onAbort = () => controller.abort(signal?.reason);
152
+ if (signal?.aborted) controller.abort(signal.reason);
153
+ else signal?.addEventListener("abort", onAbort, { once: true });
154
+ return {
155
+ signal: controller.signal,
156
+ cleanup: () => {
157
+ clearTimeout(timer);
158
+ signal?.removeEventListener("abort", onAbort);
159
+ }
160
+ };
161
+ }
162
+ async function readBody(response) {
163
+ const text = await response.text();
164
+ if (!text) return null;
165
+ try {
166
+ return JSON.parse(text);
167
+ } catch {
168
+ return text;
169
+ }
170
+ }
171
+ var Transport = class {
172
+ // Native private field: the key never shows in console.log, util.inspect or JSON.stringify.
173
+ #apiKey;
174
+ baseURL;
175
+ timeoutMs;
176
+ maxRetries;
177
+ fetchImpl;
178
+ constructor(options = {}) {
179
+ const apiKey = options.apiKey?.trim() || readEnv("GEOGRIDAPI_API_KEY");
180
+ if (!apiKey) {
181
+ throw new GeoGridAPIError(
182
+ "Missing GeoGridAPI API key: pass { apiKey } or set GEOGRIDAPI_API_KEY. Create one at https://geogridapi.io/en/console.",
183
+ { code: "missing_api_key" }
184
+ );
185
+ }
186
+ const fetchImpl = options.fetch ?? globalThis.fetch;
187
+ if (typeof fetchImpl !== "function") {
188
+ throw new GeoGridAPIError("No fetch implementation found: use Node.js 20+ or pass { fetch }.", { code: "missing_fetch" });
189
+ }
190
+ this.#apiKey = apiKey;
191
+ this.baseURL = (options.baseURL ?? DEFAULT_BASE_URL).replace(/\/+$/, "");
192
+ this.timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
193
+ this.maxRetries = options.maxRetries ?? DEFAULT_MAX_RETRIES;
194
+ this.fetchImpl = fetchImpl;
195
+ }
196
+ /** Whether the key is a test key (free mock grids). */
197
+ get isTestKey() {
198
+ return this.#apiKey.startsWith("gga_test_");
199
+ }
200
+ buildURL(path, query) {
201
+ const url = new URL(`${this.baseURL}${path}`);
202
+ for (const [name, value] of Object.entries(query ?? {})) {
203
+ if (value !== void 0) url.searchParams.set(name, String(value));
204
+ }
205
+ return url.toString();
206
+ }
207
+ headers(spec) {
208
+ const headers = {
209
+ Authorization: `Bearer ${this.#apiKey}`,
210
+ Accept: spec.binary ? "image/png" : "application/json",
211
+ "User-Agent": `geogridapi-node/${VERSION}`
212
+ };
213
+ if (spec.body !== void 0) headers["Content-Type"] = "application/json";
214
+ if (spec.idempotencyKey) headers["Idempotency-Key"] = spec.idempotencyKey;
215
+ return headers;
216
+ }
217
+ async request(spec, options = {}) {
218
+ const maxRetries = options.maxRetries ?? this.maxRetries;
219
+ const url = this.buildURL(spec.path, spec.query);
220
+ const init = {
221
+ method: spec.method,
222
+ headers: this.headers(spec),
223
+ ...spec.body !== void 0 ? { body: JSON.stringify(spec.body) } : {}
224
+ };
225
+ for (let attempt = 0; ; attempt++) {
226
+ if (options.signal?.aborted) throw options.signal.reason;
227
+ const outcome = await this.attempt(url, init, spec, options);
228
+ if (outcome.ok) return outcome.value;
229
+ const { error } = outcome;
230
+ const transient = error.status === 429 || spec.retryable && isTransient(error);
231
+ if (!transient || attempt >= maxRetries) throw error;
232
+ await sleep(retryDelayMs(attempt, error.retryAfter), options.signal);
233
+ }
234
+ }
235
+ /** One HTTP exchange. A caller abort is rethrown as is; everything else becomes a GeoGridAPIError. */
236
+ async attempt(url, init, spec, options) {
237
+ const { signal, cleanup } = combineSignals(options.timeoutMs ?? this.timeoutMs, options.signal);
238
+ try {
239
+ const response = await this.fetchImpl(url, { ...init, signal });
240
+ if (response.ok) {
241
+ const value = spec.binary ? new Uint8Array(await response.arrayBuffer()) : await readBody(response);
242
+ return { ok: true, value };
243
+ }
244
+ const body = await readBody(response);
245
+ return { ok: false, error: errorFromResponse(response.status, body, parseRetryAfter(response.headers.get("retry-after"))) };
246
+ } catch (cause) {
247
+ if (options.signal?.aborted) throw options.signal.reason;
248
+ const timedOut = signal.aborted;
249
+ const message = timedOut ? "GeoGridAPI request timed out." : "Could not reach GeoGridAPI.";
250
+ return { ok: false, error: new APIConnectionError(message, { code: timedOut ? "timeout" : "connection_error", cause }) };
251
+ } finally {
252
+ cleanup();
253
+ }
254
+ }
255
+ };
256
+
257
+ // src/resources.ts
258
+ var GRID_WAIT = { pollIntervalMs: 3e3, timeoutMs: 3e5 };
259
+ var BATCH_WAIT = { pollIntervalMs: 5e3, timeoutMs: 9e5 };
260
+ var MAX_POLL_INTERVAL_MS = 1e4;
261
+ var POLL_BACKOFF = 1.5;
262
+ var newIdempotencyKey = () => `gga-node-${crypto.randomUUID()}`;
263
+ var segment = (id) => encodeURIComponent(id);
264
+ function nextInterval(current) {
265
+ return Math.min(current * POLL_BACKOFF, Math.max(MAX_POLL_INTERVAL_MS, current));
266
+ }
267
+ var withJitter = (ms) => ms * (0.9 + Math.random() * 0.2);
268
+ async function pollUntil(id, read, isDone, describe, defaults, options) {
269
+ let interval = options.pollIntervalMs ?? defaults.pollIntervalMs;
270
+ const deadline = Date.now() + (options.timeoutMs ?? defaults.timeoutMs);
271
+ let last = null;
272
+ for (; ; ) {
273
+ let delay = interval;
274
+ try {
275
+ last = await read();
276
+ if (isDone(last)) return last;
277
+ } catch (error) {
278
+ if (!(error instanceof GeoGridAPIError)) throw error;
279
+ if (!isTransient(error)) throw Object.assign(error, id);
280
+ if (error.retryAfter !== null) delay = Math.max(delay, error.retryAfter * 1e3);
281
+ }
282
+ if (Date.now() + delay > deadline) {
283
+ throw new WaitTimeoutError(`${describe(last)} Read it again later or use a webhook_url.`, { code: "wait_timeout", ...id });
284
+ }
285
+ await sleep(withJitter(delay), options.signal);
286
+ interval = nextInterval(interval);
287
+ }
288
+ }
289
+ var gridDone = (grid) => grid.status === "completed" || grid.status === "failed";
290
+ var Places = class {
291
+ constructor(transport) {
292
+ this.transport = transport;
293
+ }
294
+ transport;
295
+ /** Finds a Google Business Profile by name and city, with its `place_id`. Free (a live key needs a positive balance). */
296
+ search(params, options) {
297
+ const query = typeof params === "string" ? { q: params } : params;
298
+ return this.transport.request({ method: "GET", path: "/v1/places/search", query, retryable: true }, options);
299
+ }
300
+ };
301
+ var Grids = class {
302
+ constructor(transport) {
303
+ this.transport = transport;
304
+ }
305
+ transport;
306
+ /**
307
+ * Starts a grid (status `pending`); it completes in 20 seconds to 2 minutes.
308
+ * Costs 1 credit per point, refunded if the grid fails. Use `run` to wait for the result.
309
+ * Retries inside this call reuse one idempotency key; pass your own `idempotencyKey` to make your own retries safe too.
310
+ */
311
+ create(params, options = {}) {
312
+ const { idempotencyKey = newIdempotencyKey(), ...rest } = options;
313
+ return this.transport.request({ method: "POST", path: "/v1/grids", body: params, idempotencyKey, retryable: true }, rest);
314
+ }
315
+ /** Exact number of points and credits a grid would cost. Free, nothing is reserved. */
316
+ estimate(params, options) {
317
+ return this.transport.request({ method: "POST", path: "/v1/grids/estimate", body: params, retryable: true }, options);
318
+ }
319
+ /** Status and result of a grid: ranks, summary, competitors ahead and change since the previous grid. */
320
+ get(id, options) {
321
+ return this.transport.request({ method: "GET", path: `/v1/grids/${segment(id)}`, retryable: true }, options);
322
+ }
323
+ /** One page of grids, newest first, without point details. */
324
+ list(params = {}, options) {
325
+ return this.transport.request({ method: "GET", path: "/v1/grids", query: { ...params }, retryable: true }, options);
326
+ }
327
+ /** Every grid, newest first, reading the pages as you iterate. */
328
+ async *listAll(params = {}, options) {
329
+ let before;
330
+ for (; ; ) {
331
+ const page = await this.list({ ...params, before }, options);
332
+ yield* page.data;
333
+ if (!page.has_more || !page.next_before) return;
334
+ before = page.next_before;
335
+ }
336
+ }
337
+ /** PNG heatmap (1200 × 1092) of a completed grid. Free. */
338
+ image(id, params = {}, options) {
339
+ return this.transport.request(
340
+ { method: "GET", path: `/v1/grids/${segment(id)}/image`, query: { ...params }, retryable: true, binary: true },
341
+ options
342
+ );
343
+ }
344
+ /** Reads the grid until it is `completed` or `failed`. */
345
+ wait(id, options = {}) {
346
+ return pollUntil(
347
+ { gridId: id },
348
+ () => this.get(id, { signal: options.signal, maxRetries: 0 }),
349
+ gridDone,
350
+ (grid) => `Grid ${id} is still ${grid?.status ?? "running"}.`,
351
+ GRID_WAIT,
352
+ options
353
+ );
354
+ }
355
+ /** Starts a grid and waits for its result (`completed` or `failed`; a failed grid is refunded). */
356
+ async run(params, options = {}) {
357
+ const { wait = {}, ...requestOptions } = options;
358
+ const grid = await this.create(params, requestOptions);
359
+ return gridDone(grid) ? grid : this.wait(grid.id, { signal: requestOptions.signal, ...wait });
360
+ }
361
+ };
362
+ var Batches = class {
363
+ constructor(transport) {
364
+ this.transport = transport;
365
+ }
366
+ transport;
367
+ /**
368
+ * Up to 50 grids with the same format: one listing with several keywords, or several listings with one keyword.
369
+ * Each grid is charged when it starts and refunded if it fails.
370
+ */
371
+ create(params, options = {}) {
372
+ const { idempotencyKey = newIdempotencyKey(), ...rest } = options;
373
+ return this.transport.request({ method: "POST", path: "/v1/batches", body: params, idempotencyKey, retryable: true }, rest);
374
+ }
375
+ /** A batch and the status of each of its grids. */
376
+ get(id, options) {
377
+ return this.transport.request({ method: "GET", path: `/v1/batches/${segment(id)}`, retryable: true }, options);
378
+ }
379
+ /** Reads the batch until every grid is finished. */
380
+ wait(id, options = {}) {
381
+ return pollUntil(
382
+ { batchId: id },
383
+ () => this.get(id, { signal: options.signal, maxRetries: 0 }),
384
+ (batch) => batch.status === "completed",
385
+ (batch) => batch ? `Batch ${id} is still running (${batch.counts.completed + batch.counts.failed}/${batch.items_count} grids finished).` : `Batch ${id} is still running.`,
386
+ BATCH_WAIT,
387
+ options
388
+ );
389
+ }
390
+ /** Starts a batch and waits until every grid is finished. */
391
+ async run(params, options = {}) {
392
+ const { wait = {}, ...requestOptions } = options;
393
+ const batch = await this.create(params, requestOptions);
394
+ return batch.status === "completed" ? batch : this.wait(batch.id, { signal: requestOptions.signal, ...wait });
395
+ }
396
+ };
397
+ var HistoryResource = class {
398
+ constructor(transport) {
399
+ this.transport = transport;
400
+ }
401
+ transport;
402
+ /** Every completed grid of the same map, oldest first, and the change between the first and the last. Free. */
403
+ get(params, options) {
404
+ return this.transport.request({ method: "GET", path: "/v1/history", query: { ...params }, retryable: true }, options);
405
+ }
406
+ };
407
+ var Schedules = class {
408
+ constructor(transport) {
409
+ this.transport = transport;
410
+ }
411
+ transport;
412
+ /** Repeats a completed grid every week or every month. Each run costs the grid's credits. */
413
+ create(params, options) {
414
+ return this.transport.request({ method: "POST", path: "/v1/schedules", body: params, retryable: false }, options);
415
+ }
416
+ list(options) {
417
+ return this.transport.request({ method: "GET", path: "/v1/schedules", retryable: true }, options);
418
+ }
419
+ /** Pauses (`paused`) or resumes (`active`) a schedule. */
420
+ update(id, params, options) {
421
+ return this.transport.request({ method: "PATCH", path: `/v1/schedules/${segment(id)}`, body: params, retryable: true }, options);
422
+ }
423
+ /** Stops repeating a grid for good. Grids already run are kept. Not retried: a lost response would turn into a 404. */
424
+ delete(id, options) {
425
+ return this.transport.request({ method: "DELETE", path: `/v1/schedules/${segment(id)}`, retryable: false }, options);
426
+ }
427
+ };
428
+ var BalanceResource = class {
429
+ constructor(transport) {
430
+ this.transport = transport;
431
+ }
432
+ transport;
433
+ /** Credits left on the account (1 credit = 1 grid point). */
434
+ get(options) {
435
+ return this.transport.request({ method: "GET", path: "/v1/balance", retryable: true }, options);
436
+ }
437
+ };
438
+
439
+ // src/webhooks.ts
440
+ var SIGNATURE_HEADER = "GeoGridAPI-Signature";
441
+ var DEFAULT_TOLERANCE_SECONDS = 300;
442
+ var WebhookSignatureError = class extends GeoGridAPIError {
443
+ };
444
+ var encoder = new TextEncoder();
445
+ function parseHeader(header) {
446
+ let timestamp = Number.NaN;
447
+ const signatures = [];
448
+ for (const part of header.split(",")) {
449
+ const separator = part.indexOf("=");
450
+ if (separator === -1) continue;
451
+ const key = part.slice(0, separator).trim();
452
+ const value = part.slice(separator + 1).trim();
453
+ if (key === "t") timestamp = Number(value);
454
+ if (key === "v1") signatures.push(value);
455
+ }
456
+ return { timestamp, signatures };
457
+ }
458
+ async function hmacHex(secret, message) {
459
+ const key = await crypto.subtle.importKey("raw", encoder.encode(secret), { name: "HMAC", hash: "SHA-256" }, false, ["sign"]);
460
+ const signature = new Uint8Array(await crypto.subtle.sign("HMAC", key, message));
461
+ return Array.from(signature, (byte) => byte.toString(16).padStart(2, "0")).join("");
462
+ }
463
+ function constantTimeEqual(a, b) {
464
+ if (a.length !== b.length) return false;
465
+ let diff = 0;
466
+ for (let i = 0; i < a.length; i++) diff |= a.charCodeAt(i) ^ b.charCodeAt(i);
467
+ return diff === 0;
468
+ }
469
+ function toBytes(payload) {
470
+ return typeof payload === "string" ? encoder.encode(payload) : payload;
471
+ }
472
+ async function verifyWebhookSignature(payload, header, secret, options = {}) {
473
+ if (!header || !secret) return false;
474
+ const { timestamp, signatures } = parseHeader(header);
475
+ if (!Number.isFinite(timestamp) || timestamp <= 0 || signatures.length === 0) return false;
476
+ const now = options.now ?? Math.floor(Date.now() / 1e3);
477
+ if (Math.abs(now - timestamp) > (options.toleranceSeconds ?? DEFAULT_TOLERANCE_SECONDS)) return false;
478
+ const prefix = encoder.encode(`${timestamp}.`);
479
+ const body = toBytes(payload);
480
+ const message = new Uint8Array(prefix.length + body.length);
481
+ message.set(prefix, 0);
482
+ message.set(body, prefix.length);
483
+ const expected = await hmacHex(secret, message);
484
+ return signatures.some((signature) => constantTimeEqual(expected, signature.toLowerCase()));
485
+ }
486
+ async function constructEvent(payload, header, secret, options = {}) {
487
+ if (!await verifyWebhookSignature(payload, header, secret, options)) {
488
+ throw new WebhookSignatureError("Invalid or expired GeoGridAPI webhook signature.", { code: "invalid_signature" });
489
+ }
490
+ const text = typeof payload === "string" ? payload : new TextDecoder().decode(payload);
491
+ return JSON.parse(text);
492
+ }
493
+
494
+ // src/index.ts
495
+ var GeoGridAPI = class {
496
+ places;
497
+ grids;
498
+ batches;
499
+ history;
500
+ schedules;
501
+ balance;
502
+ transport;
503
+ constructor(options = {}) {
504
+ this.transport = new Transport(options);
505
+ this.places = new Places(this.transport);
506
+ this.grids = new Grids(this.transport);
507
+ this.batches = new Batches(this.transport);
508
+ this.history = new HistoryResource(this.transport);
509
+ this.schedules = new Schedules(this.transport);
510
+ this.balance = new BalanceResource(this.transport);
511
+ }
512
+ /** Whether the client uses a test key (`gga_test_`: free mock grids). */
513
+ get isTestKey() {
514
+ return this.transport.isTestKey;
515
+ }
516
+ };
517
+ var index_default = GeoGridAPI;
518
+ // Annotate the CommonJS export names for ESM import in node:
519
+ 0 && (module.exports = {
520
+ APIConnectionError,
521
+ APIError,
522
+ AuthenticationError,
523
+ ConflictError,
524
+ DEFAULT_BASE_URL,
525
+ DEFAULT_TOLERANCE_SECONDS,
526
+ GeoGridAPI,
527
+ GeoGridAPIError,
528
+ InsufficientCreditsError,
529
+ InvalidRequestError,
530
+ NotFoundError,
531
+ RateLimitError,
532
+ SIGNATURE_HEADER,
533
+ UnprocessableEntityError,
534
+ VERSION,
535
+ WaitTimeoutError,
536
+ WebhookSignatureError,
537
+ constructEvent,
538
+ verifyWebhookSignature
539
+ });
540
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts","../src/errors.ts","../src/version.ts","../src/core.ts","../src/resources.ts","../src/webhooks.ts"],"sourcesContent":["import { Transport, type ClientOptions } from \"./core.js\";\nimport { BalanceResource, Batches, Grids, HistoryResource, Places, Schedules } from \"./resources.js\";\n\n/**\n * GeoGridAPI client.\n *\n * ```ts\n * import { GeoGridAPI } from \"geogridapi\";\n * const gga = new GeoGridAPI(); // reads GEOGRIDAPI_API_KEY\n * const grid = await gga.grids.run({ query: \"Joe's Pizza, New York\", keyword: \"pizza\", size: 7 });\n * console.log(grid.summary?.average_rank, grid.ranks);\n * ```\n */\nexport class GeoGridAPI {\n readonly places: Places;\n readonly grids: Grids;\n readonly batches: Batches;\n readonly history: HistoryResource;\n readonly schedules: Schedules;\n readonly balance: BalanceResource;\n private readonly transport: Transport;\n\n constructor(options: ClientOptions = {}) {\n this.transport = new Transport(options);\n this.places = new Places(this.transport);\n this.grids = new Grids(this.transport);\n this.batches = new Batches(this.transport);\n this.history = new HistoryResource(this.transport);\n this.schedules = new Schedules(this.transport);\n this.balance = new BalanceResource(this.transport);\n }\n\n /** Whether the client uses a test key (`gga_test_`: free mock grids). */\n get isTestKey(): boolean {\n return this.transport.isTestKey;\n }\n}\n\nexport default GeoGridAPI;\n\nexport { DEFAULT_BASE_URL, type ClientOptions, type IdempotentRequestOptions, type RequestOptions } from \"./core.js\";\nexport type { RunOptions, WaitOptions } from \"./resources.js\";\nexport {\n APIConnectionError,\n APIError,\n AuthenticationError,\n ConflictError,\n GeoGridAPIError,\n InsufficientCreditsError,\n InvalidRequestError,\n NotFoundError,\n RateLimitError,\n UnprocessableEntityError,\n WaitTimeoutError,\n type GeoGridAPIErrorOptions,\n} from \"./errors.js\";\nexport {\n DEFAULT_TOLERANCE_SECONDS,\n SIGNATURE_HEADER,\n WebhookSignatureError,\n constructEvent,\n verifyWebhookSignature,\n type VerifyOptions,\n} from \"./webhooks.js\";\nexport { VERSION } from \"./version.js\";\nexport type * from \"./types.js\";\n","// Errors thrown by the client. Every API error carries the `code` and\n// `message` of the `{ error: { code, message } }` envelope.\n\nexport interface GeoGridAPIErrorOptions {\n status?: number | null;\n code?: string;\n retryAfter?: number | null;\n body?: unknown;\n cause?: unknown;\n gridId?: string;\n batchId?: string;\n}\n\nexport class GeoGridAPIError extends Error {\n /** HTTP status, `null` when no response was received. */\n readonly status: number | null;\n /** API error code (`invalid_body`, `insufficient_points`, `rate_limited`…). */\n readonly code: string;\n /** Seconds to wait before retrying, from the `Retry-After` header. */\n readonly retryAfter: number | null;\n /** Parsed error body, when there is one. */\n readonly body: unknown;\n /** Set when the error happened after a grid was created (while waiting for it): read it later with `grids.get`. */\n gridId?: string;\n /** Set when the error happened after a batch was created (while waiting for it): read it later with `batches.get`. */\n batchId?: string;\n\n constructor(message: string, options: GeoGridAPIErrorOptions = {}) {\n super(message, options.cause === undefined ? undefined : { cause: options.cause });\n this.name = new.target.name;\n this.status = options.status ?? null;\n this.code = options.code ?? \"unknown_error\";\n this.retryAfter = options.retryAfter ?? null;\n this.body = options.body;\n if (options.gridId) this.gridId = options.gridId;\n if (options.batchId) this.batchId = options.batchId;\n }\n}\n\n/** 400: invalid body or parameters. */\nexport class InvalidRequestError extends GeoGridAPIError {}\n/** 401: missing, malformed, unknown or revoked API key. */\nexport class AuthenticationError extends GeoGridAPIError {}\n/** 402: not enough credits for this grid or batch. */\nexport class InsufficientCreditsError extends GeoGridAPIError {}\n/** 404: unknown grid, batch or schedule, or no listing matches. */\nexport class NotFoundError extends GeoGridAPIError {}\n/** 409: conflicting state (grid not ready, already scheduled, idempotency key reused…). */\nexport class ConflictError extends GeoGridAPIError {}\n/** 422: an account limit is reached (`schedule_limit`). */\nexport class UnprocessableEntityError extends GeoGridAPIError {}\n/** 429: too many requests or too many grids still running. */\nexport class RateLimitError extends GeoGridAPIError {}\n/** 5xx: the API or the ranking engine is temporarily unavailable. */\nexport class APIError extends GeoGridAPIError {}\n/** No response: network failure or request timeout. */\nexport class APIConnectionError extends GeoGridAPIError {}\n/** A grid or batch did not finish within the wait timeout. It keeps running server side. */\nexport class WaitTimeoutError extends GeoGridAPIError {}\n\nexport function errorFromResponse(status: number, body: unknown, retryAfter: number | null): GeoGridAPIError {\n const envelope = (body as { error?: { code?: unknown; message?: unknown } } | null)?.error;\n const code = typeof envelope?.code === \"string\" ? envelope.code : `http_${status}`;\n const message = typeof envelope?.message === \"string\" ? envelope.message : `GeoGridAPI request failed with status ${status}`;\n const options = { status, code, retryAfter, body };\n if (status === 400) return new InvalidRequestError(message, options);\n if (status === 401) return new AuthenticationError(message, options);\n if (status === 402) return new InsufficientCreditsError(message, options);\n if (status === 404) return new NotFoundError(message, options);\n if (status === 409) return new ConflictError(message, options);\n if (status === 422) return new UnprocessableEntityError(message, options);\n if (status === 429) return new RateLimitError(message, options);\n if (status >= 500) return new APIError(message, options);\n return new GeoGridAPIError(message, options);\n}\n\n/** Errors worth waiting out: no response, timeout, 429 or 5xx. */\nexport function isTransient(error: GeoGridAPIError): boolean {\n return error.status === null || error.status === 408 || error.status === 429 || error.status >= 500;\n}\n","export const VERSION = \"0.1.0\";\n","// HTTP transport: auth, timeouts, retries and error mapping.\n\nimport { APIConnectionError, GeoGridAPIError, errorFromResponse, isTransient } from \"./errors.js\";\nimport { VERSION } from \"./version.js\";\n\nexport const DEFAULT_BASE_URL = \"https://api.geogridapi.io\";\nexport const DEFAULT_TIMEOUT_MS = 60_000;\nexport const DEFAULT_MAX_RETRIES = 2;\nconst MAX_RETRY_DELAY_MS = 60_000;\n\nexport interface ClientOptions {\n /** API key (`gga_live_…` or `gga_test_…`). Defaults to `process.env.GEOGRIDAPI_API_KEY`. */\n apiKey?: string;\n /** Defaults to `https://api.geogridapi.io`. */\n baseURL?: string;\n /** Timeout of each HTTP request, in milliseconds (default 60 000). */\n timeoutMs?: number;\n /** Retries on network errors, 429 and 5xx (default 2). Grid and batch creations are retried with the same idempotency key. */\n maxRetries?: number;\n /** Custom fetch implementation. Defaults to the global `fetch`. */\n fetch?: typeof fetch;\n}\n\nexport interface RequestOptions {\n signal?: AbortSignal;\n timeoutMs?: number;\n maxRetries?: number;\n}\n\nexport interface IdempotentRequestOptions extends RequestOptions {\n /** Same key = same grid or batch, never charged twice. Generated when omitted. */\n idempotencyKey?: string;\n}\n\nexport type QueryValue = string | number | boolean | undefined;\n\nexport interface RequestSpec {\n method: \"GET\" | \"POST\" | \"PATCH\" | \"DELETE\";\n path: string;\n query?: Record<string, QueryValue>;\n body?: unknown;\n idempotencyKey?: string;\n /** Safe to send again after a network error or a 5xx. */\n retryable: boolean;\n binary?: boolean;\n}\n\nfunction readEnv(name: string): string | undefined {\n const env = (globalThis as { process?: { env?: Record<string, string | undefined> } }).process?.env;\n return env?.[name]?.trim() || undefined;\n}\n\nexport function sleep(ms: number, signal?: AbortSignal): Promise<void> {\n return new Promise((resolve, reject) => {\n if (signal?.aborted) return reject(signal.reason);\n const timer = setTimeout(() => {\n signal?.removeEventListener(\"abort\", onAbort);\n resolve();\n }, ms);\n const onAbort = () => {\n clearTimeout(timer);\n reject(signal?.reason);\n };\n signal?.addEventListener(\"abort\", onAbort, { once: true });\n });\n}\n\nfunction parseRetryAfter(value: string | null): number | null {\n if (!value) return null;\n const seconds = Number(value);\n if (Number.isFinite(seconds) && seconds >= 0) return seconds;\n const date = Date.parse(value);\n return Number.isNaN(date) ? null : Math.max(0, (date - Date.now()) / 1000);\n}\n\nfunction retryDelayMs(attempt: number, retryAfter: number | null): number {\n if (retryAfter !== null) return Math.min(retryAfter * 1000, MAX_RETRY_DELAY_MS);\n const base = Math.min(500 * 2 ** attempt, 8_000);\n return base / 2 + Math.random() * (base / 2);\n}\n\nfunction combineSignals(timeoutMs: number, signal?: AbortSignal): { signal: AbortSignal; cleanup: () => void } {\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(new DOMException(\"Request timed out\", \"TimeoutError\")), timeoutMs);\n const onAbort = () => controller.abort(signal?.reason);\n if (signal?.aborted) controller.abort(signal.reason);\n else signal?.addEventListener(\"abort\", onAbort, { once: true });\n return {\n signal: controller.signal,\n cleanup: () => {\n clearTimeout(timer);\n signal?.removeEventListener(\"abort\", onAbort);\n },\n };\n}\n\nasync function readBody(response: Response): Promise<unknown> {\n const text = await response.text();\n if (!text) return null;\n try {\n return JSON.parse(text);\n } catch {\n return text;\n }\n}\n\nexport class Transport {\n // Native private field: the key never shows in console.log, util.inspect or JSON.stringify.\n readonly #apiKey: string;\n private readonly baseURL: string;\n private readonly timeoutMs: number;\n private readonly maxRetries: number;\n private readonly fetchImpl: typeof fetch;\n\n constructor(options: ClientOptions = {}) {\n const apiKey = options.apiKey?.trim() || readEnv(\"GEOGRIDAPI_API_KEY\");\n if (!apiKey) {\n throw new GeoGridAPIError(\n \"Missing GeoGridAPI API key: pass { apiKey } or set GEOGRIDAPI_API_KEY. Create one at https://geogridapi.io/en/console.\",\n { code: \"missing_api_key\" },\n );\n }\n const fetchImpl = options.fetch ?? globalThis.fetch;\n if (typeof fetchImpl !== \"function\") {\n throw new GeoGridAPIError(\"No fetch implementation found: use Node.js 20+ or pass { fetch }.\", { code: \"missing_fetch\" });\n }\n this.#apiKey = apiKey;\n this.baseURL = (options.baseURL ?? DEFAULT_BASE_URL).replace(/\\/+$/, \"\");\n this.timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;\n this.maxRetries = options.maxRetries ?? DEFAULT_MAX_RETRIES;\n this.fetchImpl = fetchImpl;\n }\n\n /** Whether the key is a test key (free mock grids). */\n get isTestKey(): boolean {\n return this.#apiKey.startsWith(\"gga_test_\");\n }\n\n private buildURL(path: string, query?: Record<string, QueryValue>): string {\n const url = new URL(`${this.baseURL}${path}`);\n for (const [name, value] of Object.entries(query ?? {})) {\n if (value !== undefined) url.searchParams.set(name, String(value));\n }\n return url.toString();\n }\n\n private headers(spec: RequestSpec): Record<string, string> {\n const headers: Record<string, string> = {\n Authorization: `Bearer ${this.#apiKey}`,\n Accept: spec.binary ? \"image/png\" : \"application/json\",\n \"User-Agent\": `geogridapi-node/${VERSION}`,\n };\n if (spec.body !== undefined) headers[\"Content-Type\"] = \"application/json\";\n if (spec.idempotencyKey) headers[\"Idempotency-Key\"] = spec.idempotencyKey;\n return headers;\n }\n\n async request<T>(spec: RequestSpec, options: RequestOptions = {}): Promise<T> {\n const maxRetries = options.maxRetries ?? this.maxRetries;\n const url = this.buildURL(spec.path, spec.query);\n const init: RequestInit = {\n method: spec.method,\n headers: this.headers(spec),\n ...(spec.body !== undefined ? { body: JSON.stringify(spec.body) } : {}),\n };\n\n for (let attempt = 0; ; attempt++) {\n if (options.signal?.aborted) throw options.signal.reason;\n const outcome = await this.attempt<T>(url, init, spec, options);\n if (outcome.ok) return outcome.value;\n const { error } = outcome;\n // A 429 is refused before any processing: always safe to send again.\n const transient = error.status === 429 || (spec.retryable && isTransient(error));\n if (!transient || attempt >= maxRetries) throw error;\n await sleep(retryDelayMs(attempt, error.retryAfter), options.signal);\n }\n }\n\n /** One HTTP exchange. A caller abort is rethrown as is; everything else becomes a GeoGridAPIError. */\n private async attempt<T>(\n url: string,\n init: RequestInit,\n spec: RequestSpec,\n options: RequestOptions,\n ): Promise<{ ok: true; value: T } | { ok: false; error: GeoGridAPIError }> {\n const { signal, cleanup } = combineSignals(options.timeoutMs ?? this.timeoutMs, options.signal);\n try {\n const response = await this.fetchImpl(url, { ...init, signal });\n if (response.ok) {\n const value = spec.binary ? new Uint8Array(await response.arrayBuffer()) : await readBody(response);\n return { ok: true, value: value as T };\n }\n const body = await readBody(response);\n return { ok: false, error: errorFromResponse(response.status, body, parseRetryAfter(response.headers.get(\"retry-after\"))) };\n } catch (cause) {\n if (options.signal?.aborted) throw options.signal.reason;\n const timedOut = signal.aborted;\n const message = timedOut ? \"GeoGridAPI request timed out.\" : \"Could not reach GeoGridAPI.\";\n return { ok: false, error: new APIConnectionError(message, { code: timedOut ? \"timeout\" : \"connection_error\", cause }) };\n } finally {\n cleanup();\n }\n }\n}\n","// API resources: places, grids, batches, history, schedules and balance.\n\nimport { sleep, type IdempotentRequestOptions, type RequestOptions, type Transport } from \"./core.js\";\nimport { GeoGridAPIError, WaitTimeoutError, isTransient } from \"./errors.js\";\nimport type {\n Balance,\n Batch,\n CreateBatchParams,\n CreateGridParams,\n CreateScheduleParams,\n Estimate,\n Grid,\n GridList,\n History,\n HistoryParams,\n ListGridsParams,\n PlaceList,\n Schedule,\n ScheduleList,\n} from \"./types.js\";\n\nexport interface WaitOptions {\n /** First delay between two status reads, in milliseconds (grids: 3 000, batches: 5 000). It then grows up to 10 s. */\n pollIntervalMs?: number;\n /** Give up after this many milliseconds (grids: 300 000, batches: 900 000). The work keeps running server side. */\n timeoutMs?: number;\n signal?: AbortSignal;\n}\n\nexport type RunOptions = IdempotentRequestOptions & { wait?: WaitOptions };\n\nconst GRID_WAIT = { pollIntervalMs: 3_000, timeoutMs: 300_000 };\nconst BATCH_WAIT = { pollIntervalMs: 5_000, timeoutMs: 900_000 };\nconst MAX_POLL_INTERVAL_MS = 10_000;\nconst POLL_BACKOFF = 1.5;\n\nconst newIdempotencyKey = (): string => `gga-node-${crypto.randomUUID()}`;\nconst segment = (id: string): string => encodeURIComponent(id);\n\n/** Next polling delay: grows by half up to 10 s (keeps several waits under the 60 requests/minute of a key), ±10 % jitter. */\nfunction nextInterval(current: number): number {\n return Math.min(current * POLL_BACKOFF, Math.max(MAX_POLL_INTERVAL_MS, current));\n}\nconst withJitter = (ms: number): number => ms * (0.9 + Math.random() * 0.2);\n\n/**\n * Reads `id` until `isDone`. Transient errors (network, 429, 5xx) are waited out until the deadline;\n * any error thrown carries the id (`gridId` or `batchId`) so the work can be read again later.\n */\nasync function pollUntil<T>(\n id: { gridId: string } | { batchId: string },\n read: () => Promise<T>,\n isDone: (value: T) => boolean,\n describe: (value: T | null) => string,\n defaults: { pollIntervalMs: number; timeoutMs: number },\n options: WaitOptions,\n): Promise<T> {\n let interval = options.pollIntervalMs ?? defaults.pollIntervalMs;\n const deadline = Date.now() + (options.timeoutMs ?? defaults.timeoutMs);\n let last: T | null = null;\n for (;;) {\n let delay = interval;\n try {\n last = await read();\n if (isDone(last)) return last;\n } catch (error) {\n if (!(error instanceof GeoGridAPIError)) throw error;\n if (!isTransient(error)) throw Object.assign(error, id);\n if (error.retryAfter !== null) delay = Math.max(delay, error.retryAfter * 1000);\n }\n if (Date.now() + delay > deadline) {\n throw new WaitTimeoutError(`${describe(last)} Read it again later or use a webhook_url.`, { code: \"wait_timeout\", ...id });\n }\n await sleep(withJitter(delay), options.signal);\n interval = nextInterval(interval);\n }\n}\n\nconst gridDone = (grid: Grid): boolean => grid.status === \"completed\" || grid.status === \"failed\";\n\nexport class Places {\n constructor(private readonly transport: Transport) {}\n\n /** Finds a Google Business Profile by name and city, with its `place_id`. Free (a live key needs a positive balance). */\n search(params: string | { q: string; language?: string }, options?: RequestOptions): Promise<PlaceList> {\n const query = typeof params === \"string\" ? { q: params } : params;\n return this.transport.request({ method: \"GET\", path: \"/v1/places/search\", query, retryable: true }, options);\n }\n}\n\nexport class Grids {\n constructor(private readonly transport: Transport) {}\n\n /**\n * Starts a grid (status `pending`); it completes in 20 seconds to 2 minutes.\n * Costs 1 credit per point, refunded if the grid fails. Use `run` to wait for the result.\n * Retries inside this call reuse one idempotency key; pass your own `idempotencyKey` to make your own retries safe too.\n */\n create(params: CreateGridParams, options: IdempotentRequestOptions = {}): Promise<Grid> {\n const { idempotencyKey = newIdempotencyKey(), ...rest } = options;\n return this.transport.request({ method: \"POST\", path: \"/v1/grids\", body: params, idempotencyKey, retryable: true }, rest);\n }\n\n /** Exact number of points and credits a grid would cost. Free, nothing is reserved. */\n estimate(params: CreateGridParams, options?: RequestOptions): Promise<Estimate> {\n return this.transport.request({ method: \"POST\", path: \"/v1/grids/estimate\", body: params, retryable: true }, options);\n }\n\n /** Status and result of a grid: ranks, summary, competitors ahead and change since the previous grid. */\n get(id: string, options?: RequestOptions): Promise<Grid> {\n return this.transport.request({ method: \"GET\", path: `/v1/grids/${segment(id)}`, retryable: true }, options);\n }\n\n /** One page of grids, newest first, without point details. */\n list(params: ListGridsParams = {}, options?: RequestOptions): Promise<GridList> {\n return this.transport.request({ method: \"GET\", path: \"/v1/grids\", query: { ...params }, retryable: true }, options);\n }\n\n /** Every grid, newest first, reading the pages as you iterate. */\n async *listAll(params: Omit<ListGridsParams, \"before\"> = {}, options?: RequestOptions): AsyncGenerator<Grid> {\n let before: string | undefined;\n for (;;) {\n const page = await this.list({ ...params, before }, options);\n yield* page.data;\n if (!page.has_more || !page.next_before) return;\n before = page.next_before;\n }\n }\n\n /** PNG heatmap (1200 × 1092) of a completed grid. Free. */\n image(id: string, params: { lang?: \"en\" | \"fr\" } = {}, options?: RequestOptions): Promise<Uint8Array> {\n return this.transport.request(\n { method: \"GET\", path: `/v1/grids/${segment(id)}/image`, query: { ...params }, retryable: true, binary: true },\n options,\n );\n }\n\n /** Reads the grid until it is `completed` or `failed`. */\n wait(id: string, options: WaitOptions = {}): Promise<Grid> {\n return pollUntil(\n { gridId: id },\n () => this.get(id, { signal: options.signal, maxRetries: 0 }),\n gridDone,\n (grid) => `Grid ${id} is still ${grid?.status ?? \"running\"}.`,\n GRID_WAIT,\n options,\n );\n }\n\n /** Starts a grid and waits for its result (`completed` or `failed`; a failed grid is refunded). */\n async run(params: CreateGridParams, options: RunOptions = {}): Promise<Grid> {\n const { wait = {}, ...requestOptions } = options;\n const grid = await this.create(params, requestOptions);\n return gridDone(grid) ? grid : this.wait(grid.id, { signal: requestOptions.signal, ...wait });\n }\n}\n\nexport class Batches {\n constructor(private readonly transport: Transport) {}\n\n /**\n * Up to 50 grids with the same format: one listing with several keywords, or several listings with one keyword.\n * Each grid is charged when it starts and refunded if it fails.\n */\n create(params: CreateBatchParams, options: IdempotentRequestOptions = {}): Promise<Batch> {\n const { idempotencyKey = newIdempotencyKey(), ...rest } = options;\n return this.transport.request({ method: \"POST\", path: \"/v1/batches\", body: params, idempotencyKey, retryable: true }, rest);\n }\n\n /** A batch and the status of each of its grids. */\n get(id: string, options?: RequestOptions): Promise<Batch> {\n return this.transport.request({ method: \"GET\", path: `/v1/batches/${segment(id)}`, retryable: true }, options);\n }\n\n /** Reads the batch until every grid is finished. */\n wait(id: string, options: WaitOptions = {}): Promise<Batch> {\n return pollUntil(\n { batchId: id },\n () => this.get(id, { signal: options.signal, maxRetries: 0 }),\n (batch) => batch.status === \"completed\",\n (batch) =>\n batch\n ? `Batch ${id} is still running (${batch.counts.completed + batch.counts.failed}/${batch.items_count} grids finished).`\n : `Batch ${id} is still running.`,\n BATCH_WAIT,\n options,\n );\n }\n\n /** Starts a batch and waits until every grid is finished. */\n async run(params: CreateBatchParams, options: RunOptions = {}): Promise<Batch> {\n const { wait = {}, ...requestOptions } = options;\n const batch = await this.create(params, requestOptions);\n return batch.status === \"completed\" ? batch : this.wait(batch.id, { signal: requestOptions.signal, ...wait });\n }\n}\n\nexport class HistoryResource {\n constructor(private readonly transport: Transport) {}\n\n /** Every completed grid of the same map, oldest first, and the change between the first and the last. Free. */\n get(params: HistoryParams, options?: RequestOptions): Promise<History> {\n return this.transport.request({ method: \"GET\", path: \"/v1/history\", query: { ...params }, retryable: true }, options);\n }\n}\n\nexport class Schedules {\n constructor(private readonly transport: Transport) {}\n\n /** Repeats a completed grid every week or every month. Each run costs the grid's credits. */\n create(params: CreateScheduleParams, options?: RequestOptions): Promise<Schedule> {\n return this.transport.request({ method: \"POST\", path: \"/v1/schedules\", body: params, retryable: false }, options);\n }\n\n list(options?: RequestOptions): Promise<ScheduleList> {\n return this.transport.request({ method: \"GET\", path: \"/v1/schedules\", retryable: true }, options);\n }\n\n /** Pauses (`paused`) or resumes (`active`) a schedule. */\n update(id: string, params: { status: \"active\" | \"paused\" }, options?: RequestOptions): Promise<Schedule> {\n return this.transport.request({ method: \"PATCH\", path: `/v1/schedules/${segment(id)}`, body: params, retryable: true }, options);\n }\n\n /** Stops repeating a grid for good. Grids already run are kept. Not retried: a lost response would turn into a 404. */\n delete(id: string, options?: RequestOptions): Promise<Record<string, unknown>> {\n return this.transport.request({ method: \"DELETE\", path: `/v1/schedules/${segment(id)}`, retryable: false }, options);\n }\n}\n\nexport class BalanceResource {\n constructor(private readonly transport: Transport) {}\n\n /** Credits left on the account (1 credit = 1 grid point). */\n get(options?: RequestOptions): Promise<Balance> {\n return this.transport.request({ method: \"GET\", path: \"/v1/balance\", retryable: true }, options);\n }\n}\n","// Webhook signatures: header `GeoGridAPI-Signature: t=<unix seconds>,v1=<hex>`\n// where v1 = HMAC-SHA256(secret, `${t}.${raw body}`). Uses Web Crypto, so it\n// runs on Node 20+, Deno, Bun, Cloudflare Workers and edge runtimes.\n\nimport { GeoGridAPIError } from \"./errors.js\";\nimport type { WebhookEvent } from \"./types.js\";\n\nexport const SIGNATURE_HEADER = \"GeoGridAPI-Signature\";\nexport const DEFAULT_TOLERANCE_SECONDS = 300;\n\nexport interface VerifyOptions {\n /** Maximum age of the signature, in seconds (default 300). */\n toleranceSeconds?: number;\n /** Current time in unix seconds, for tests. */\n now?: number;\n}\n\n/** Thrown by `constructEvent` when the signature is missing, invalid or too old. */\nexport class WebhookSignatureError extends GeoGridAPIError {}\n\nconst encoder = new TextEncoder();\n\nfunction parseHeader(header: string): { timestamp: number; signatures: string[] } {\n let timestamp = Number.NaN;\n const signatures: string[] = [];\n for (const part of header.split(\",\")) {\n const separator = part.indexOf(\"=\");\n if (separator === -1) continue;\n const key = part.slice(0, separator).trim();\n const value = part.slice(separator + 1).trim();\n if (key === \"t\") timestamp = Number(value);\n if (key === \"v1\") signatures.push(value);\n }\n return { timestamp, signatures };\n}\n\nasync function hmacHex(secret: string, message: Uint8Array<ArrayBuffer>): Promise<string> {\n const key = await crypto.subtle.importKey(\"raw\", encoder.encode(secret), { name: \"HMAC\", hash: \"SHA-256\" }, false, [\"sign\"]);\n const signature = new Uint8Array(await crypto.subtle.sign(\"HMAC\", key, message));\n return Array.from(signature, (byte) => byte.toString(16).padStart(2, \"0\")).join(\"\");\n}\n\nfunction constantTimeEqual(a: string, b: string): boolean {\n if (a.length !== b.length) return false;\n let diff = 0;\n for (let i = 0; i < a.length; i++) diff |= a.charCodeAt(i) ^ b.charCodeAt(i);\n return diff === 0;\n}\n\nfunction toBytes(payload: string | Uint8Array): Uint8Array {\n return typeof payload === \"string\" ? encoder.encode(payload) : payload;\n}\n\n/**\n * Checks the `GeoGridAPI-Signature` header of a webhook call.\n * `payload` must be the raw request body, exactly as received (not re-serialized JSON).\n */\nexport async function verifyWebhookSignature(\n payload: string | Uint8Array,\n header: string | null | undefined,\n secret: string,\n options: VerifyOptions = {},\n): Promise<boolean> {\n if (!header || !secret) return false;\n const { timestamp, signatures } = parseHeader(header);\n if (!Number.isFinite(timestamp) || timestamp <= 0 || signatures.length === 0) return false;\n const now = options.now ?? Math.floor(Date.now() / 1000);\n if (Math.abs(now - timestamp) > (options.toleranceSeconds ?? DEFAULT_TOLERANCE_SECONDS)) return false;\n\n const prefix = encoder.encode(`${timestamp}.`);\n const body = toBytes(payload);\n const message = new Uint8Array(prefix.length + body.length);\n message.set(prefix, 0);\n message.set(body, prefix.length);\n const expected = await hmacHex(secret, message);\n return signatures.some((signature) => constantTimeEqual(expected, signature.toLowerCase()));\n}\n\n/** Verifies the signature, then parses the event. Throws `WebhookSignatureError` when it does not match. */\nexport async function constructEvent(\n payload: string | Uint8Array,\n header: string | null | undefined,\n secret: string,\n options: VerifyOptions = {},\n): Promise<WebhookEvent> {\n if (!(await verifyWebhookSignature(payload, header, secret, options))) {\n throw new WebhookSignatureError(\"Invalid or expired GeoGridAPI webhook signature.\", { code: \"invalid_signature\" });\n }\n const text = typeof payload === \"string\" ? payload : new TextDecoder().decode(payload);\n return JSON.parse(text) as WebhookEvent;\n}\n"],"mappings":"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","names":[]}