@wherabouts/sdk 0.2.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.js ADDED
@@ -0,0 +1,419 @@
1
+ // src/errors.ts
2
+ var WheraboutsApiError = class extends Error {
3
+ code;
4
+ payload;
5
+ status;
6
+ /** Correlation id from the `X-Request-Id` header or error body, if present. */
7
+ requestId;
8
+ /** Documentation link for this error, if the API provided one. */
9
+ docUrl;
10
+ /** Field-level validation detail, if the API provided any. */
11
+ fields;
12
+ constructor(options) {
13
+ super(options.message);
14
+ this.name = "WheraboutsApiError";
15
+ this.status = options.status;
16
+ this.code = options.code ?? "unknown_error";
17
+ this.payload = options.payload ?? null;
18
+ this.requestId = options.requestId ?? null;
19
+ this.docUrl = options.docUrl ?? null;
20
+ this.fields = options.fields ?? null;
21
+ }
22
+ };
23
+
24
+ // src/shared-types.ts
25
+ var WHERABOUTS_API_VERSION = "v1";
26
+ var WHERABOUTS_SDK_VERSION = "0.2.0";
27
+
28
+ // src/http.ts
29
+ var DEFAULT_BASE_URL = "https://api.wherabouts.com";
30
+ var DEFAULT_MAX_RETRIES = 2;
31
+ var DEFAULT_TIMEOUT_MS = 3e4;
32
+ var BACKOFF_BASE_MS = 200;
33
+ var BACKOFF_CAP_MS = 5e3;
34
+ var RETRYABLE_STATUSES = /* @__PURE__ */ new Set([408, 425, 429, 500, 502, 503, 504]);
35
+ var WRITE_METHODS = /* @__PURE__ */ new Set(["POST", "PUT"]);
36
+ var createBaseHeaders = (config) => {
37
+ const headers = new Headers(config.headers);
38
+ headers.set("accept", "application/json");
39
+ headers.set("authorization", `Bearer ${config.apiKey}`);
40
+ headers.set(
41
+ "x-wherabouts-sdk",
42
+ `js-ts/${WHERABOUTS_SDK_VERSION} api/${WHERABOUTS_API_VERSION}`
43
+ );
44
+ return headers;
45
+ };
46
+ var generateIdempotencyKey = () => {
47
+ const cryptoObj = globalThis.crypto;
48
+ if (cryptoObj?.randomUUID) {
49
+ return cryptoObj.randomUUID();
50
+ }
51
+ return `${Date.now().toString(16)}-${Math.random().toString(16).slice(2)}`;
52
+ };
53
+ var buildRequestHeaders = (baseHeaders, opts) => {
54
+ const headers = new Headers(baseHeaders);
55
+ if (opts.headers) {
56
+ for (const [key, value] of Object.entries(opts.headers)) {
57
+ headers.set(key, value);
58
+ }
59
+ }
60
+ if (opts.body !== void 0) {
61
+ headers.set("content-type", "application/json");
62
+ }
63
+ if (WRITE_METHODS.has(opts.method)) {
64
+ headers.set(
65
+ "idempotency-key",
66
+ opts.idempotencyKey ?? generateIdempotencyKey()
67
+ );
68
+ } else if (opts.idempotencyKey) {
69
+ headers.set("idempotency-key", opts.idempotencyKey);
70
+ }
71
+ return headers;
72
+ };
73
+ var readRequestId = (response) => response.headers.get("x-request-id") ?? response.headers.get("x-wherabouts-request-id");
74
+ var parseApiError = async (response) => {
75
+ let payload = null;
76
+ try {
77
+ payload = await response.json();
78
+ } catch {
79
+ payload = null;
80
+ }
81
+ const message = payload?.error.message ?? `Wherabouts request failed with status ${response.status}`;
82
+ return new WheraboutsApiError({
83
+ status: response.status,
84
+ message,
85
+ code: payload?.error.code ?? "unknown_error",
86
+ payload,
87
+ requestId: payload?.error.request_id ?? readRequestId(response),
88
+ docUrl: payload?.error.doc_url ?? null,
89
+ fields: payload?.error.fields ?? null
90
+ });
91
+ };
92
+ var parseRetryAfter = (value) => {
93
+ if (!value) {
94
+ return null;
95
+ }
96
+ const seconds = Number(value);
97
+ if (Number.isFinite(seconds)) {
98
+ return Math.max(0, seconds * 1e3);
99
+ }
100
+ const dateMs = Date.parse(value);
101
+ if (Number.isNaN(dateMs)) {
102
+ return null;
103
+ }
104
+ return Math.max(0, dateMs - Date.now());
105
+ };
106
+ var computeBackoff = (attempt) => {
107
+ const exponential = Math.min(BACKOFF_CAP_MS, BACKOFF_BASE_MS * 2 ** attempt);
108
+ return Math.random() * exponential;
109
+ };
110
+ var sleep = (ms, signal) => new Promise((resolve, reject) => {
111
+ if (signal?.aborted) {
112
+ reject(signal.reason ?? new Error("Aborted"));
113
+ return;
114
+ }
115
+ const timer = setTimeout(() => {
116
+ signal?.removeEventListener("abort", onAbort);
117
+ resolve();
118
+ }, ms);
119
+ const onAbort = () => {
120
+ clearTimeout(timer);
121
+ reject(signal?.reason ?? new Error("Aborted"));
122
+ };
123
+ signal?.addEventListener("abort", onAbort, { once: true });
124
+ });
125
+ var createRequester = (config) => {
126
+ const fetchImpl = config.fetch ?? globalThis.fetch;
127
+ if (!fetchImpl) {
128
+ throw new Error(
129
+ "A fetch implementation is required to create the SDK client."
130
+ );
131
+ }
132
+ const baseUrl = new URL(config.baseUrl ?? DEFAULT_BASE_URL);
133
+ const baseHeaders = createBaseHeaders(config);
134
+ return async (opts) => {
135
+ const url = new URL(opts.path, baseUrl);
136
+ if (opts.query) {
137
+ for (const [key, value] of Object.entries(opts.query)) {
138
+ if (value !== void 0) {
139
+ url.searchParams.set(key, String(value));
140
+ }
141
+ }
142
+ }
143
+ const headers = buildRequestHeaders(baseHeaders, opts);
144
+ const hasBody = opts.body !== void 0;
145
+ const body = hasBody ? JSON.stringify(opts.body) : void 0;
146
+ const maxRetries = opts.maxRetries ?? config.maxRetries ?? DEFAULT_MAX_RETRIES;
147
+ const timeoutMs = opts.timeoutMs ?? config.timeoutMs ?? DEFAULT_TIMEOUT_MS;
148
+ const callerSignal = opts.signal;
149
+ let attempt = 0;
150
+ while (true) {
151
+ const controller = new AbortController();
152
+ let timedOut = false;
153
+ const timeoutId = setTimeout(() => {
154
+ timedOut = true;
155
+ controller.abort();
156
+ }, timeoutMs);
157
+ const onCallerAbort = () => controller.abort();
158
+ if (callerSignal) {
159
+ if (callerSignal.aborted) {
160
+ controller.abort();
161
+ } else {
162
+ callerSignal.addEventListener("abort", onCallerAbort, { once: true });
163
+ }
164
+ }
165
+ try {
166
+ const response = await fetchImpl(url, {
167
+ method: opts.method,
168
+ headers,
169
+ body,
170
+ signal: controller.signal
171
+ });
172
+ if (!response.ok) {
173
+ if (RETRYABLE_STATUSES.has(response.status) && attempt < maxRetries) {
174
+ const wait = parseRetryAfter(response.headers.get("retry-after")) ?? computeBackoff(attempt);
175
+ attempt++;
176
+ await sleep(Math.min(wait, BACKOFF_CAP_MS), callerSignal);
177
+ continue;
178
+ }
179
+ throw await parseApiError(response);
180
+ }
181
+ if (response.status === 204) {
182
+ return void 0;
183
+ }
184
+ const text = await response.text();
185
+ return text ? JSON.parse(text) : void 0;
186
+ } catch (error) {
187
+ if (callerSignal?.aborted) {
188
+ throw error;
189
+ }
190
+ if (timedOut) {
191
+ if (attempt < maxRetries) {
192
+ attempt++;
193
+ await sleep(computeBackoff(attempt - 1), callerSignal);
194
+ continue;
195
+ }
196
+ throw new WheraboutsApiError({
197
+ status: 0,
198
+ code: "timeout",
199
+ message: `Request timed out after ${timeoutMs}ms.`
200
+ });
201
+ }
202
+ if (error instanceof WheraboutsApiError) {
203
+ throw error;
204
+ }
205
+ if (attempt < maxRetries) {
206
+ attempt++;
207
+ await sleep(computeBackoff(attempt - 1), callerSignal);
208
+ continue;
209
+ }
210
+ throw error;
211
+ } finally {
212
+ clearTimeout(timeoutId);
213
+ callerSignal?.removeEventListener("abort", onCallerAbort);
214
+ }
215
+ }
216
+ };
217
+ };
218
+
219
+ // src/resources/addresses.ts
220
+ var createAddresses = (request) => ({
221
+ autocomplete: (params, options) => request({
222
+ method: "GET",
223
+ path: "/api/v1/addresses/autocomplete",
224
+ query: {
225
+ q: params.q,
226
+ country: params.country,
227
+ state: params.state,
228
+ limit: params.limit
229
+ },
230
+ ...options
231
+ }),
232
+ getById: (id, options) => request({
233
+ method: "GET",
234
+ path: `/api/v1/addresses/${id}`,
235
+ ...options
236
+ }),
237
+ nearby: (params, options) => request({
238
+ method: "GET",
239
+ path: "/api/v1/addresses/nearby",
240
+ query: {
241
+ lat: params.lat,
242
+ lng: params.lng,
243
+ radius: params.radius,
244
+ limit: params.limit,
245
+ country: params.country
246
+ },
247
+ ...options
248
+ }),
249
+ reverse: (params, options) => request({
250
+ method: "GET",
251
+ path: "/api/v1/addresses/reverse",
252
+ query: { lat: params.lat, lng: params.lng },
253
+ ...options
254
+ })
255
+ });
256
+
257
+ // src/resources/devices.ts
258
+ var createDevices = (request) => ({
259
+ pushLocation: (deviceId, body, options) => request({
260
+ method: "POST",
261
+ path: `/api/v1/devices/${deviceId}/location`,
262
+ body,
263
+ ...options
264
+ }),
265
+ zones: (deviceId, options) => request({
266
+ method: "GET",
267
+ path: `/api/v1/devices/${deviceId}/zones`,
268
+ ...options
269
+ })
270
+ });
271
+
272
+ // src/resources/geocode.ts
273
+ var createGeocode = (request) => ({
274
+ forward: (params, options) => request({
275
+ method: "GET",
276
+ path: "/api/v1/addresses/geocode",
277
+ query: {
278
+ q: params.q,
279
+ structured: params.structured,
280
+ street: params.street,
281
+ locality: params.locality,
282
+ state: params.state,
283
+ postcode: params.postcode,
284
+ country: params.country
285
+ },
286
+ ...options
287
+ }),
288
+ batch: {
289
+ submit: (body, options) => request({
290
+ method: "POST",
291
+ path: "/api/v1/geocode/batch",
292
+ body,
293
+ ...options
294
+ }),
295
+ poll: (jobId, options) => request({
296
+ method: "GET",
297
+ path: `/api/v1/geocode/batch/${jobId}`,
298
+ ...options
299
+ }),
300
+ results: (jobId, options) => request({
301
+ method: "GET",
302
+ path: `/api/v1/geocode/batch/${jobId}/results`,
303
+ ...options
304
+ })
305
+ }
306
+ });
307
+
308
+ // src/resources/regions.ts
309
+ var createRegions = (request) => ({
310
+ classify: (params, options) => request({
311
+ method: "GET",
312
+ path: "/api/v1/regions",
313
+ query: { lat: params.lat, lng: params.lng, layers: params.layers },
314
+ ...options
315
+ })
316
+ });
317
+
318
+ // src/resources/routing.ts
319
+ var createRouting = (request) => ({
320
+ directions: (params, options) => request({
321
+ method: "GET",
322
+ path: "/api/v1/routing/directions",
323
+ query: {
324
+ from: params.from,
325
+ to: params.to,
326
+ fromAddressId: params.fromAddressId,
327
+ toAddressId: params.toAddressId,
328
+ profile: params.profile
329
+ },
330
+ ...options
331
+ })
332
+ });
333
+
334
+ // src/resources/webhooks.ts
335
+ var createWebhooks = (request) => ({
336
+ create: (body, options) => request({
337
+ method: "POST",
338
+ path: "/api/v1/webhooks",
339
+ body,
340
+ ...options
341
+ }),
342
+ list: (options) => request({
343
+ method: "GET",
344
+ path: "/api/v1/webhooks",
345
+ ...options
346
+ }),
347
+ delete: (id, options) => request({
348
+ method: "DELETE",
349
+ path: `/api/v1/webhooks/${id}`,
350
+ ...options
351
+ }),
352
+ reactivate: (id, options) => request({
353
+ method: "POST",
354
+ path: `/api/v1/webhooks/${id}/reactivate`,
355
+ ...options
356
+ })
357
+ });
358
+
359
+ // src/resources/zones.ts
360
+ var createZones = (request) => ({
361
+ create: (body, options) => request({
362
+ method: "POST",
363
+ path: "/api/v1/zones",
364
+ body,
365
+ ...options
366
+ }),
367
+ list: (params, options) => request({
368
+ method: "GET",
369
+ path: "/api/v1/zones",
370
+ query: { page: params?.page, limit: params?.limit },
371
+ ...options
372
+ }),
373
+ get: (id, options) => request({
374
+ method: "GET",
375
+ path: `/api/v1/zones/${id}`,
376
+ ...options
377
+ }),
378
+ update: (id, body, options) => request({
379
+ method: "PUT",
380
+ path: `/api/v1/zones/${id}`,
381
+ body,
382
+ ...options
383
+ }),
384
+ delete: (id, options) => request({
385
+ method: "DELETE",
386
+ path: `/api/v1/zones/${id}`,
387
+ ...options
388
+ }),
389
+ contains: (params, options) => request({
390
+ method: "GET",
391
+ path: "/api/v1/zones/contains",
392
+ query: { lat: params.lat, lng: params.lng },
393
+ ...options
394
+ }),
395
+ addresses: (id, params, options) => request({
396
+ method: "GET",
397
+ path: `/api/v1/zones/${id}/addresses`,
398
+ query: { page: params?.page, limit: params?.limit },
399
+ ...options
400
+ })
401
+ });
402
+
403
+ // src/client.ts
404
+ var createWheraboutsClient = (config) => {
405
+ const request = createRequester(config);
406
+ return {
407
+ addresses: createAddresses(request),
408
+ geocode: createGeocode(request),
409
+ zones: createZones(request),
410
+ devices: createDevices(request),
411
+ webhooks: createWebhooks(request),
412
+ regions: createRegions(request),
413
+ routing: createRouting(request)
414
+ };
415
+ };
416
+
417
+ export { WHERABOUTS_API_VERSION, WHERABOUTS_SDK_VERSION, WheraboutsApiError, createAddresses, createDevices, createGeocode, createRegions, createRouting, createWebhooks, createWheraboutsClient, createZones };
418
+ //# sourceMappingURL=index.js.map
419
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/errors.ts","../src/shared-types.ts","../src/http.ts","../src/resources/addresses.ts","../src/resources/devices.ts","../src/resources/geocode.ts","../src/resources/regions.ts","../src/resources/routing.ts","../src/resources/webhooks.ts","../src/resources/zones.ts","../src/client.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["import type {\n\tWheraboutsApiErrorPayload,\n\tWheraboutsErrorCode,\n\tWheraboutsFieldError,\n} from \"./shared-types.ts\";\n\nexport class WheraboutsApiError extends Error {\n\treadonly code: WheraboutsErrorCode | \"unknown_error\";\n\treadonly payload: WheraboutsApiErrorPayload | null;\n\treadonly status: number;\n\t/** Correlation id from the `X-Request-Id` header or error body, if present. */\n\treadonly requestId: string | null;\n\t/** Documentation link for this error, if the API provided one. */\n\treadonly docUrl: string | null;\n\t/** Field-level validation detail, if the API provided any. */\n\treadonly fields: WheraboutsFieldError[] | null;\n\n\tconstructor(options: {\n\t\tcode?: WheraboutsApiError[\"code\"];\n\t\tdocUrl?: string | null;\n\t\tfields?: WheraboutsFieldError[] | null;\n\t\tmessage: string;\n\t\tpayload?: WheraboutsApiErrorPayload | null;\n\t\trequestId?: string | null;\n\t\tstatus: number;\n\t}) {\n\t\tsuper(options.message);\n\t\tthis.name = \"WheraboutsApiError\";\n\t\tthis.status = options.status;\n\t\tthis.code = options.code ?? \"unknown_error\";\n\t\tthis.payload = options.payload ?? null;\n\t\tthis.requestId = options.requestId ?? null;\n\t\tthis.docUrl = options.docUrl ?? null;\n\t\tthis.fields = options.fields ?? null;\n\t}\n}\n","export const WHERABOUTS_API_VERSION = \"v1\" as const;\nexport const WHERABOUTS_SDK_VERSION = \"0.2.0\" as const;\n\n/**\n * Error codes the API may return. The server currently emits a subset; the full\n * union is declared for forward-compatibility with the Phase 2 error envelope\n * (see docs/CONTRACT.md §4). Unknown codes fall back to `unknown_error`.\n */\nexport type WheraboutsErrorCode =\n\t| \"bad_request\"\n\t| \"conflict\"\n\t| \"forbidden\"\n\t| \"internal_error\"\n\t| \"not_found\"\n\t| \"rate_limited\"\n\t| \"timeout\"\n\t| \"unauthorized\"\n\t| \"unprocessable\";\n\nexport interface WheraboutsFieldError {\n\tmessage: string;\n\tpath: string;\n}\n\nexport interface WheraboutsApiErrorPayload {\n\terror: {\n\t\tcode: WheraboutsErrorCode;\n\t\tmessage: string;\n\t\t/** Correlation id; also sent as the `X-Request-Id` response header. */\n\t\trequest_id?: string;\n\t\t/** Link to documentation for this error code. */\n\t\tdoc_url?: string;\n\t\t/** Field-level validation detail (validation errors only). */\n\t\tfields?: WheraboutsFieldError[];\n\t};\n}\n\nexport interface WheraboutsClientConfig {\n\tapiKey: string;\n\tbaseUrl?: string;\n\tfetch?: typeof fetch;\n\theaders?: Record<string, string>;\n\t/** Max automatic retries for transient failures. Default 2. */\n\tmaxRetries?: number;\n\t/** Per-request timeout in milliseconds. Default 30000. */\n\ttimeoutMs?: number;\n}\n\n/**\n * Per-call overrides. Every resource method accepts an optional trailing\n * `options` argument of this shape.\n */\nexport interface CallOptions {\n\t/** Extra headers merged over (not replacing) the client headers. */\n\theaders?: Record<string, string>;\n\t/** Idempotency key for write requests. Auto-generated on writes if omitted. */\n\tidempotencyKey?: string;\n\t/** Override the client's `maxRetries` for this call. */\n\tmaxRetries?: number;\n\t/** Abort signal — aborting rejects the call (and is not retried). */\n\tsignal?: AbortSignal;\n\t/** Override the client's `timeoutMs` for this call. */\n\ttimeoutMs?: number;\n}\n\nexport type HttpMethod = \"GET\" | \"POST\" | \"PUT\" | \"DELETE\";\n\nexport interface RequestOptions extends CallOptions {\n\tbody?: unknown;\n\tmethod: HttpMethod;\n\tpath: string;\n\tquery?: Record<string, number | string | undefined>;\n}\n\nexport type Requester = <T>(opts: RequestOptions) => Promise<T>;\n","import { WheraboutsApiError } from \"./errors.ts\";\nimport {\n\ttype HttpMethod,\n\ttype Requester,\n\ttype RequestOptions,\n\tWHERABOUTS_API_VERSION,\n\tWHERABOUTS_SDK_VERSION,\n\ttype WheraboutsApiErrorPayload,\n\ttype WheraboutsClientConfig,\n} from \"./shared-types.ts\";\n\nconst DEFAULT_BASE_URL = \"https://api.wherabouts.com\";\nconst DEFAULT_MAX_RETRIES = 2;\nconst DEFAULT_TIMEOUT_MS = 30_000;\nconst BACKOFF_BASE_MS = 200;\nconst BACKOFF_CAP_MS = 5000;\nconst RETRYABLE_STATUSES = new Set([408, 425, 429, 500, 502, 503, 504]);\nconst WRITE_METHODS = new Set<HttpMethod>([\"POST\", \"PUT\"]);\n\nconst createBaseHeaders = (config: WheraboutsClientConfig): Headers => {\n\tconst headers = new Headers(config.headers);\n\theaders.set(\"accept\", \"application/json\");\n\theaders.set(\"authorization\", `Bearer ${config.apiKey}`);\n\theaders.set(\n\t\t\"x-wherabouts-sdk\",\n\t\t`js-ts/${WHERABOUTS_SDK_VERSION} api/${WHERABOUTS_API_VERSION}`\n\t);\n\treturn headers;\n};\n\nconst generateIdempotencyKey = (): string => {\n\tconst cryptoObj = globalThis.crypto;\n\tif (cryptoObj?.randomUUID) {\n\t\treturn cryptoObj.randomUUID();\n\t}\n\t// Fallback for runtimes without crypto.randomUUID.\n\treturn `${Date.now().toString(16)}-${Math.random().toString(16).slice(2)}`;\n};\n\nconst buildRequestHeaders = (\n\tbaseHeaders: Headers,\n\topts: RequestOptions\n): Headers => {\n\tconst headers = new Headers(baseHeaders);\n\tif (opts.headers) {\n\t\tfor (const [key, value] of Object.entries(opts.headers)) {\n\t\t\theaders.set(key, value);\n\t\t}\n\t}\n\tif (opts.body !== undefined) {\n\t\theaders.set(\"content-type\", \"application/json\");\n\t}\n\t// Writes carry an Idempotency-Key so retries are safe (docs/CONTRACT.md §6).\n\tif (WRITE_METHODS.has(opts.method)) {\n\t\theaders.set(\n\t\t\t\"idempotency-key\",\n\t\t\topts.idempotencyKey ?? generateIdempotencyKey()\n\t\t);\n\t} else if (opts.idempotencyKey) {\n\t\theaders.set(\"idempotency-key\", opts.idempotencyKey);\n\t}\n\treturn headers;\n};\n\nconst readRequestId = (response: Response): string | null =>\n\tresponse.headers.get(\"x-request-id\") ??\n\tresponse.headers.get(\"x-wherabouts-request-id\");\n\nconst parseApiError = async (\n\tresponse: Response\n): Promise<WheraboutsApiError> => {\n\tlet payload: WheraboutsApiErrorPayload | null = null;\n\ttry {\n\t\tpayload = (await response.json()) as WheraboutsApiErrorPayload;\n\t} catch {\n\t\tpayload = null;\n\t}\n\tconst message =\n\t\tpayload?.error.message ??\n\t\t`Wherabouts request failed with status ${response.status}`;\n\treturn new WheraboutsApiError({\n\t\tstatus: response.status,\n\t\tmessage,\n\t\tcode: payload?.error.code ?? \"unknown_error\",\n\t\tpayload,\n\t\trequestId: payload?.error.request_id ?? readRequestId(response),\n\t\tdocUrl: payload?.error.doc_url ?? null,\n\t\tfields: payload?.error.fields ?? null,\n\t});\n};\n\n/** Parse a `Retry-After` header (delta-seconds or HTTP-date) to milliseconds. */\nconst parseRetryAfter = (value: string | null): number | null => {\n\tif (!value) {\n\t\treturn null;\n\t}\n\tconst seconds = Number(value);\n\tif (Number.isFinite(seconds)) {\n\t\treturn Math.max(0, seconds * 1000);\n\t}\n\tconst dateMs = Date.parse(value);\n\tif (Number.isNaN(dateMs)) {\n\t\treturn null;\n\t}\n\treturn Math.max(0, dateMs - Date.now());\n};\n\n/** Exponential backoff with full jitter, capped. */\nconst computeBackoff = (attempt: number): number => {\n\tconst exponential = Math.min(BACKOFF_CAP_MS, BACKOFF_BASE_MS * 2 ** attempt);\n\treturn Math.random() * exponential;\n};\n\nconst sleep = (ms: number, signal?: AbortSignal): Promise<void> =>\n\tnew Promise((resolve, reject) => {\n\t\tif (signal?.aborted) {\n\t\t\treject(signal.reason ?? new Error(\"Aborted\"));\n\t\t\treturn;\n\t\t}\n\t\tconst timer = setTimeout(() => {\n\t\t\tsignal?.removeEventListener(\"abort\", onAbort);\n\t\t\tresolve();\n\t\t}, ms);\n\t\tconst onAbort = () => {\n\t\t\tclearTimeout(timer);\n\t\t\treject(signal?.reason ?? new Error(\"Aborted\"));\n\t\t};\n\t\tsignal?.addEventListener(\"abort\", onAbort, { once: true });\n\t});\n\nexport const createRequester = (config: WheraboutsClientConfig): Requester => {\n\tconst fetchImpl = config.fetch ?? globalThis.fetch;\n\tif (!fetchImpl) {\n\t\tthrow new Error(\n\t\t\t\"A fetch implementation is required to create the SDK client.\"\n\t\t);\n\t}\n\tconst baseUrl = new URL(config.baseUrl ?? DEFAULT_BASE_URL);\n\tconst baseHeaders = createBaseHeaders(config);\n\n\treturn async <T>(opts: RequestOptions): Promise<T> => {\n\t\tconst url = new URL(opts.path, baseUrl);\n\t\tif (opts.query) {\n\t\t\tfor (const [key, value] of Object.entries(opts.query)) {\n\t\t\t\tif (value !== undefined) {\n\t\t\t\t\turl.searchParams.set(key, String(value));\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tconst headers = buildRequestHeaders(baseHeaders, opts);\n\t\tconst hasBody = opts.body !== undefined;\n\t\tconst body = hasBody ? JSON.stringify(opts.body) : undefined;\n\t\tconst maxRetries =\n\t\t\topts.maxRetries ?? config.maxRetries ?? DEFAULT_MAX_RETRIES;\n\t\tconst timeoutMs = opts.timeoutMs ?? config.timeoutMs ?? DEFAULT_TIMEOUT_MS;\n\t\t// GET/PUT/DELETE are idempotent; POST is only retry-safe with a key (always\n\t\t// set for writes above), so every request here is retryable by method.\n\t\tconst callerSignal = opts.signal;\n\n\t\tlet attempt = 0;\n\t\t// Retry loop: returns on success or non-retryable failure; otherwise waits\n\t\t// and continues until `maxRetries` is exhausted.\n\t\twhile (true) {\n\t\t\tconst controller = new AbortController();\n\t\t\tlet timedOut = false;\n\t\t\tconst timeoutId = setTimeout(() => {\n\t\t\t\ttimedOut = true;\n\t\t\t\tcontroller.abort();\n\t\t\t}, timeoutMs);\n\t\t\tconst onCallerAbort = () => controller.abort();\n\t\t\tif (callerSignal) {\n\t\t\t\tif (callerSignal.aborted) {\n\t\t\t\t\tcontroller.abort();\n\t\t\t\t} else {\n\t\t\t\t\tcallerSignal.addEventListener(\"abort\", onCallerAbort, { once: true });\n\t\t\t\t}\n\t\t\t}\n\n\t\t\ttry {\n\t\t\t\tconst response = await fetchImpl(url, {\n\t\t\t\t\tmethod: opts.method,\n\t\t\t\t\theaders,\n\t\t\t\t\tbody,\n\t\t\t\t\tsignal: controller.signal,\n\t\t\t\t});\n\n\t\t\t\tif (!response.ok) {\n\t\t\t\t\tif (RETRYABLE_STATUSES.has(response.status) && attempt < maxRetries) {\n\t\t\t\t\t\tconst wait =\n\t\t\t\t\t\t\tparseRetryAfter(response.headers.get(\"retry-after\")) ??\n\t\t\t\t\t\t\tcomputeBackoff(attempt);\n\t\t\t\t\t\tattempt++;\n\t\t\t\t\t\tawait sleep(Math.min(wait, BACKOFF_CAP_MS), callerSignal);\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\t}\n\t\t\t\t\tthrow await parseApiError(response);\n\t\t\t\t}\n\n\t\t\t\tif (response.status === 204) {\n\t\t\t\t\treturn undefined as T;\n\t\t\t\t}\n\t\t\t\tconst text = await response.text();\n\t\t\t\treturn (text ? JSON.parse(text) : undefined) as T;\n\t\t\t} catch (error) {\n\t\t\t\t// Caller aborted — never retry; propagate their intent.\n\t\t\t\tif (callerSignal?.aborted) {\n\t\t\t\t\tthrow error;\n\t\t\t\t}\n\t\t\t\t// Our timeout fired — treat like a transient failure.\n\t\t\t\tif (timedOut) {\n\t\t\t\t\tif (attempt < maxRetries) {\n\t\t\t\t\t\tattempt++;\n\t\t\t\t\t\tawait sleep(computeBackoff(attempt - 1), callerSignal);\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\t}\n\t\t\t\t\tthrow new WheraboutsApiError({\n\t\t\t\t\t\tstatus: 0,\n\t\t\t\t\t\tcode: \"timeout\",\n\t\t\t\t\t\tmessage: `Request timed out after ${timeoutMs}ms.`,\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t\t// A parsed API error (non-retryable status) — surface as-is.\n\t\t\t\tif (error instanceof WheraboutsApiError) {\n\t\t\t\t\tthrow error;\n\t\t\t\t}\n\t\t\t\t// Network/transport error — retry if budget remains.\n\t\t\t\tif (attempt < maxRetries) {\n\t\t\t\t\tattempt++;\n\t\t\t\t\tawait sleep(computeBackoff(attempt - 1), callerSignal);\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\tthrow error;\n\t\t\t} finally {\n\t\t\t\tclearTimeout(timeoutId);\n\t\t\t\tcallerSignal?.removeEventListener(\"abort\", onCallerAbort);\n\t\t\t}\n\t\t}\n\t};\n};\n","import type { CallOptions, Requester } from \"../shared-types.ts\";\n\nexport interface AddressSuggestion {\n\tcountry: string;\n\tformattedAddress: string;\n\tid: number;\n\tlatitude: number;\n\tlocality: string;\n\tlongitude: number;\n\tpostcode: string;\n\tstate: string;\n\tstreetAddress: string;\n}\n\nexport interface AddressRecord {\n\tbuildingName: string | null;\n\tconfidence: number | null;\n\tcountry: string;\n\tflatNumber: string | null;\n\tflatType: string | null;\n\tgnafPid: string | null;\n\tid: number;\n\tlatitude: number;\n\tlevelNumber: string | null;\n\tlevelType: string | null;\n\tlocality: string;\n\tlongitude: number;\n\tnumberFirst: string | null;\n\tnumberLast: string | null;\n\tpostcode: string;\n\tstate: string;\n\tstreetName: string;\n\tstreetSuffix: string | null;\n\tstreetType: string | null;\n}\n\nexport interface NearbyAddress {\n\tbuildingName: string | null;\n\tcountry: string;\n\tdistance: number;\n\tflatNumber: string | null;\n\tflatType: string | null;\n\tid: number;\n\tlatitude: number;\n\tlocality: string;\n\tlongitude: number;\n\tnumberFirst: string | null;\n\tnumberLast: string | null;\n\tpostcode: string;\n\tstate: string;\n\tstreetName: string;\n\tstreetType: string | null;\n}\n\nexport interface ReverseGeocodeAddress {\n\tconfidence: number | null;\n\tcountry: string;\n\tformattedAddress: string;\n\tid: number;\n\tlatitude: number;\n\tlocality: string;\n\tlongitude: number;\n\tpostcode: string;\n\tstate: string;\n\tstreetAddress: string;\n}\n\nexport interface AutocompleteParams {\n\tcountry?: string;\n\tlimit?: number;\n\tq: string;\n\tstate?: string;\n}\n\nexport interface NearbyParams {\n\tcountry?: string;\n\tlat: number;\n\tlimit?: number;\n\tlng: number;\n\tradius?: number;\n}\n\nexport interface ReverseParams {\n\tlat: number;\n\tlng: number;\n}\n\nexport interface AutocompleteResponse {\n\tcount: number;\n\tresults: AddressSuggestion[];\n}\n\nexport interface NearbyResponse {\n\tcount: number;\n\tquery: {\n\t\tlat: number;\n\t\tlng: number;\n\t\tradius: number;\n\t};\n\tresults: NearbyAddress[];\n}\n\nexport interface ReverseResponse {\n\taddress: ReverseGeocodeAddress;\n\tdistance: number;\n\tquery: {\n\t\tlat: number;\n\t\tlng: number;\n\t};\n}\n\nexport interface AddressesResource {\n\tautocomplete(\n\t\tparams: AutocompleteParams,\n\t\toptions?: CallOptions\n\t): Promise<AutocompleteResponse>;\n\tgetById(id: number, options?: CallOptions): Promise<AddressRecord>;\n\tnearby(params: NearbyParams, options?: CallOptions): Promise<NearbyResponse>;\n\treverse(\n\t\tparams: ReverseParams,\n\t\toptions?: CallOptions\n\t): Promise<ReverseResponse>;\n}\n\nexport const createAddresses = (request: Requester): AddressesResource => ({\n\tautocomplete: (params, options) =>\n\t\trequest<AutocompleteResponse>({\n\t\t\tmethod: \"GET\",\n\t\t\tpath: \"/api/v1/addresses/autocomplete\",\n\t\t\tquery: {\n\t\t\t\tq: params.q,\n\t\t\t\tcountry: params.country,\n\t\t\t\tstate: params.state,\n\t\t\t\tlimit: params.limit,\n\t\t\t},\n\t\t\t...options,\n\t\t}),\n\tgetById: (id, options) =>\n\t\trequest<AddressRecord>({\n\t\t\tmethod: \"GET\",\n\t\t\tpath: `/api/v1/addresses/${id}`,\n\t\t\t...options,\n\t\t}),\n\tnearby: (params, options) =>\n\t\trequest<NearbyResponse>({\n\t\t\tmethod: \"GET\",\n\t\t\tpath: \"/api/v1/addresses/nearby\",\n\t\t\tquery: {\n\t\t\t\tlat: params.lat,\n\t\t\t\tlng: params.lng,\n\t\t\t\tradius: params.radius,\n\t\t\t\tlimit: params.limit,\n\t\t\t\tcountry: params.country,\n\t\t\t},\n\t\t\t...options,\n\t\t}),\n\treverse: (params, options) =>\n\t\trequest<ReverseResponse>({\n\t\t\tmethod: \"GET\",\n\t\t\tpath: \"/api/v1/addresses/reverse\",\n\t\t\tquery: { lat: params.lat, lng: params.lng },\n\t\t\t...options,\n\t\t}),\n});\n","import type { CallOptions, Requester } from \"../shared-types.ts\";\n\nexport interface BoundaryCrossing {\n\tevent: \"entry\" | \"exit\";\n\tzoneId: number;\n\tzoneName: string;\n}\n\nexport interface PushLocationBody {\n\tlat: number;\n\tlng: number;\n}\n\nexport interface PushLocationResponse {\n\tcrossings: BoundaryCrossing[];\n\tzones: number[];\n}\n\nexport interface DeviceZonesResponse {\n\tdeviceId: string;\n\tlatitude: number | null;\n\tlongitude: number | null;\n\tupdatedAt: string | null;\n\tzoneIds: number[];\n}\n\nexport interface DevicesResource {\n\tpushLocation(\n\t\tdeviceId: string,\n\t\tbody: PushLocationBody,\n\t\toptions?: CallOptions\n\t): Promise<PushLocationResponse>;\n\tzones(deviceId: string, options?: CallOptions): Promise<DeviceZonesResponse>;\n}\n\nexport const createDevices = (request: Requester): DevicesResource => ({\n\tpushLocation: (deviceId, body, options) =>\n\t\trequest<PushLocationResponse>({\n\t\t\tmethod: \"POST\",\n\t\t\tpath: `/api/v1/devices/${deviceId}/location`,\n\t\t\tbody,\n\t\t\t...options,\n\t\t}),\n\tzones: (deviceId, options) =>\n\t\trequest<DeviceZonesResponse>({\n\t\t\tmethod: \"GET\",\n\t\t\tpath: `/api/v1/devices/${deviceId}/zones`,\n\t\t\t...options,\n\t\t}),\n});\n","import type { CallOptions, Requester } from \"../shared-types.ts\";\n\n// ---------------------------------------------------------------------------\n// Forward geocode types\n// ---------------------------------------------------------------------------\n\n/**\n * Params for forward geocoding. Provide `q` for unstructured free-text search,\n * OR `street` + `locality` (plus optional `state`, `postcode`, `country`) for\n * structured mode. Set `structured: \"true\"` when using structured fields.\n */\nexport interface ForwardGeocodeParams {\n\t/** ISO 3166-1 alpha-2 country code, e.g. \"AU\". */\n\tcountry?: string;\n\t/** Suburb / locality (structured mode). */\n\tlocality?: string;\n\t/** Postcode / ZIP. */\n\tpostcode?: string;\n\t/** Free-text address query (unstructured mode). */\n\tq?: string;\n\t/** State abbreviation, e.g. \"NSW\". */\n\tstate?: string;\n\t/** Street address (structured mode). */\n\tstreet?: string;\n\t/** Pass `\"true\"` to enable structured mode. */\n\tstructured?: \"true\" | \"false\";\n}\n\nexport interface GeocodeAddress {\n\tcountry: string;\n\tformattedAddress: string;\n\tid: number;\n\tlatitude: number;\n\tlocality: string;\n\tlongitude: number;\n\tpostcode: string;\n\tstate: string;\n\tstreetAddress: string;\n}\n\nexport interface ForwardGeocodeResponse {\n\taddress: GeocodeAddress;\n\tmatchType: \"structured\" | \"fuzzy\";\n}\n\n// ---------------------------------------------------------------------------\n// Batch geocode types\n// ---------------------------------------------------------------------------\n\nexport interface BatchSubmitBody {\n\taddresses: string[];\n}\n\nexport interface BatchSubmitResponse {\n\tinputCount: number;\n\tjobId: string;\n\tstatus: \"pending\" | \"processing\";\n}\n\nexport interface BatchJobStatus {\n\tcompletedAt: string | null;\n\tdownloadUrl: string | null;\n\terror: string | null;\n\tinputCount: number;\n\tjobId: string;\n\tprocessedCount: number | null;\n\tstatus: \"pending\" | \"processing\" | \"completed\" | \"failed\";\n}\n\nexport interface BatchResultsResponse {\n\tcount: number;\n\tresults: unknown[];\n}\n\n// ---------------------------------------------------------------------------\n// Resource factory\n// ---------------------------------------------------------------------------\n\nexport interface GeocodeResource {\n\tbatch: {\n\t\tsubmit(\n\t\t\tbody: BatchSubmitBody,\n\t\t\toptions?: CallOptions\n\t\t): Promise<BatchSubmitResponse>;\n\t\tpoll(jobId: string, options?: CallOptions): Promise<BatchJobStatus>;\n\t\tresults(\n\t\t\tjobId: string,\n\t\t\toptions?: CallOptions\n\t\t): Promise<BatchResultsResponse>;\n\t};\n\tforward(\n\t\tparams: ForwardGeocodeParams,\n\t\toptions?: CallOptions\n\t): Promise<ForwardGeocodeResponse>;\n}\n\nexport const createGeocode = (request: Requester): GeocodeResource => ({\n\tforward: (params, options) =>\n\t\trequest<ForwardGeocodeResponse>({\n\t\t\tmethod: \"GET\",\n\t\t\tpath: \"/api/v1/addresses/geocode\",\n\t\t\tquery: {\n\t\t\t\tq: params.q,\n\t\t\t\tstructured: params.structured,\n\t\t\t\tstreet: params.street,\n\t\t\t\tlocality: params.locality,\n\t\t\t\tstate: params.state,\n\t\t\t\tpostcode: params.postcode,\n\t\t\t\tcountry: params.country,\n\t\t\t},\n\t\t\t...options,\n\t\t}),\n\tbatch: {\n\t\tsubmit: (body, options) =>\n\t\t\trequest<BatchSubmitResponse>({\n\t\t\t\tmethod: \"POST\",\n\t\t\t\tpath: \"/api/v1/geocode/batch\",\n\t\t\t\tbody,\n\t\t\t\t...options,\n\t\t\t}),\n\t\tpoll: (jobId, options) =>\n\t\t\trequest<BatchJobStatus>({\n\t\t\t\tmethod: \"GET\",\n\t\t\t\tpath: `/api/v1/geocode/batch/${jobId}`,\n\t\t\t\t...options,\n\t\t\t}),\n\t\tresults: (jobId, options) =>\n\t\t\trequest<BatchResultsResponse>({\n\t\t\t\tmethod: \"GET\",\n\t\t\t\tpath: `/api/v1/geocode/batch/${jobId}/results`,\n\t\t\t\t...options,\n\t\t\t}),\n\t},\n});\n","import type { CallOptions, Requester } from \"../shared-types.ts\";\n\nexport interface RegionsClassifyParams {\n\tlat: number;\n\tlayers?: string;\n\tlng: number;\n}\n\nexport interface RegionMatch {\n\tcode: string;\n\tname: string;\n}\n\nexport interface RegionsClassifyResponse {\n\tquery: { lat: number; lng: number };\n\tregions: Record<string, RegionMatch>;\n}\n\nexport interface RegionsResource {\n\tclassify(\n\t\tparams: RegionsClassifyParams,\n\t\toptions?: CallOptions\n\t): Promise<RegionsClassifyResponse>;\n}\n\nexport const createRegions = (request: Requester): RegionsResource => ({\n\tclassify: (params, options) =>\n\t\trequest<RegionsClassifyResponse>({\n\t\t\tmethod: \"GET\",\n\t\t\tpath: \"/api/v1/regions\",\n\t\t\tquery: { lat: params.lat, lng: params.lng, layers: params.layers },\n\t\t\t...options,\n\t\t}),\n});\n","import type { CallOptions, Requester } from \"../shared-types.ts\";\n\nexport interface DirectionsParams {\n\tfrom?: string;\n\tfromAddressId?: number;\n\tprofile?: \"driving\";\n\tto?: string;\n\ttoAddressId?: number;\n}\n\nexport interface DirectionsGeometry {\n\tcoordinates: [number, number][];\n\ttype: \"LineString\";\n}\n\nexport interface DirectionsResponse {\n\tdistance_m: number;\n\tduration_s: number;\n\tgeometry: DirectionsGeometry;\n\tquery: {\n\t\tfrom: { lat: number; lng: number };\n\t\tprofile: string;\n\t\tto: { lat: number; lng: number };\n\t};\n}\n\nexport interface RoutingResource {\n\tdirections(\n\t\tparams: DirectionsParams,\n\t\toptions?: CallOptions\n\t): Promise<DirectionsResponse>;\n}\n\nexport const createRouting = (request: Requester): RoutingResource => ({\n\tdirections: (params, options) =>\n\t\trequest<DirectionsResponse>({\n\t\t\tmethod: \"GET\",\n\t\t\tpath: \"/api/v1/routing/directions\",\n\t\t\tquery: {\n\t\t\t\tfrom: params.from,\n\t\t\t\tto: params.to,\n\t\t\t\tfromAddressId: params.fromAddressId,\n\t\t\t\ttoAddressId: params.toAddressId,\n\t\t\t\tprofile: params.profile,\n\t\t\t},\n\t\t\t...options,\n\t\t}),\n});\n","import type { CallOptions, Requester } from \"../shared-types.ts\";\n\nexport type WebhookEvent = \"entry\" | \"exit\";\n\nexport interface CreateWebhookBody {\n\tevents?: WebhookEvent[];\n\turl: string;\n\tzoneId?: number;\n}\n\nexport interface WebhookSubscription {\n\tactive: boolean;\n\tcreatedAt: string;\n\tevents: WebhookEvent[];\n\tid: number;\n\turl: string;\n\tzoneId: number | null;\n}\n\nexport interface CreateWebhookResponse extends WebhookSubscription {\n\tsecret: string;\n}\n\nexport interface WebhookListItem extends WebhookSubscription {\n\tfailing: boolean;\n}\n\nexport interface ListWebhooksResponse {\n\tcount: number;\n\tresults: WebhookListItem[];\n}\n\nexport interface DeleteWebhookResponse {\n\tdeleted: true;\n\tid: number;\n}\n\nexport interface ReactivateWebhookResponse {\n\tid: number;\n\treactivated: true;\n}\n\nexport interface WebhooksResource {\n\tcreate(\n\t\tbody: CreateWebhookBody,\n\t\toptions?: CallOptions\n\t): Promise<CreateWebhookResponse>;\n\tdelete(id: number, options?: CallOptions): Promise<DeleteWebhookResponse>;\n\tlist(options?: CallOptions): Promise<ListWebhooksResponse>;\n\treactivate(\n\t\tid: number,\n\t\toptions?: CallOptions\n\t): Promise<ReactivateWebhookResponse>;\n}\n\nexport const createWebhooks = (request: Requester): WebhooksResource => ({\n\tcreate: (body, options) =>\n\t\trequest<CreateWebhookResponse>({\n\t\t\tmethod: \"POST\",\n\t\t\tpath: \"/api/v1/webhooks\",\n\t\t\tbody,\n\t\t\t...options,\n\t\t}),\n\tlist: (options) =>\n\t\trequest<ListWebhooksResponse>({\n\t\t\tmethod: \"GET\",\n\t\t\tpath: \"/api/v1/webhooks\",\n\t\t\t...options,\n\t\t}),\n\tdelete: (id, options) =>\n\t\trequest<DeleteWebhookResponse>({\n\t\t\tmethod: \"DELETE\",\n\t\t\tpath: `/api/v1/webhooks/${id}`,\n\t\t\t...options,\n\t\t}),\n\treactivate: (id, options) =>\n\t\trequest<ReactivateWebhookResponse>({\n\t\t\tmethod: \"POST\",\n\t\t\tpath: `/api/v1/webhooks/${id}/reactivate`,\n\t\t\t...options,\n\t\t}),\n});\n","import type { CallOptions, Requester } from \"../shared-types.ts\";\n\n// --- Types ---\n\nexport interface ZoneRecord {\n\tcreatedAt: string;\n\tdescription: string | null;\n\tid: number;\n\tmetadata: Record<string, unknown> | null;\n\tname: string;\n\tprojectId: string;\n\tupdatedAt: string;\n}\n\nexport interface ZoneWithGeometry extends ZoneRecord {\n\tgeometry: {\n\t\ttype: string;\n\t\tcoordinates: number[][][];\n\t};\n}\n\n// --- GeoJSON Polygon geometry ---\n\nexport interface GeoJsonPolygon {\n\tcoordinates: number[][][];\n\ttype: \"Polygon\";\n}\n\n// --- Param interfaces ---\n\nexport interface ZoneListParams {\n\tlimit?: number;\n\tpage?: number;\n}\n\nexport interface ZoneContainsParams {\n\tlat: number;\n\tlng: number;\n}\n\nexport interface ZoneAddressesParams {\n\tlimit?: number;\n\tpage?: number;\n}\n\nexport interface ZoneCreateBody {\n\tdescription?: string;\n\tgeometry: GeoJsonPolygon;\n\tmetadata?: Record<string, unknown>;\n\tname: string;\n}\n\nexport interface ZoneUpdateBody {\n\tdescription?: string;\n\tgeometry?: GeoJsonPolygon;\n\tmetadata?: Record<string, unknown>;\n\tname?: string;\n}\n\n// --- Response interfaces ---\n\nexport interface ZoneListResponse {\n\tcount: number;\n\tpage: number;\n\tzones: ZoneRecord[];\n}\n\nexport interface ZoneDeleteResponse {\n\tsuccess: true;\n}\n\nexport interface ZoneContainsResponse {\n\tcount: number;\n\tquery: { lat: number; lng: number };\n\tzones: ZoneRecord[];\n}\n\nexport interface ZoneAddressesResponse {\n\tcount: number;\n\tquery: { id: number; limit: number; page: number };\n\tresults: Array<{\n\t\tbuildingName: string | null;\n\t\tcountry: string;\n\t\tflatNumber: string | null;\n\t\tflatType: string | null;\n\t\tid: number;\n\t\tlatitude: number;\n\t\tlocality: string;\n\t\tlongitude: number;\n\t\tnumberFirst: string | null;\n\t\tnumberLast: string | null;\n\t\tpostcode: string;\n\t\tstate: string;\n\t\tstreetName: string;\n\t\tstreetType: string | null;\n\t}>;\n\ttruncated: boolean;\n}\n\n// --- Resource interface ---\n\nexport interface ZonesResource {\n\taddresses(\n\t\tid: number,\n\t\tparams?: ZoneAddressesParams,\n\t\toptions?: CallOptions\n\t): Promise<ZoneAddressesResponse>;\n\tcontains(\n\t\tparams: ZoneContainsParams,\n\t\toptions?: CallOptions\n\t): Promise<ZoneContainsResponse>;\n\tcreate(body: ZoneCreateBody, options?: CallOptions): Promise<ZoneRecord>;\n\tdelete(id: number, options?: CallOptions): Promise<ZoneDeleteResponse>;\n\tget(id: number, options?: CallOptions): Promise<ZoneWithGeometry>;\n\tlist(\n\t\tparams?: ZoneListParams,\n\t\toptions?: CallOptions\n\t): Promise<ZoneListResponse>;\n\tupdate(\n\t\tid: number,\n\t\tbody: ZoneUpdateBody,\n\t\toptions?: CallOptions\n\t): Promise<ZoneRecord>;\n}\n\n// --- Factory ---\n\nexport const createZones = (request: Requester): ZonesResource => ({\n\tcreate: (body, options) =>\n\t\trequest<ZoneRecord>({\n\t\t\tmethod: \"POST\",\n\t\t\tpath: \"/api/v1/zones\",\n\t\t\tbody,\n\t\t\t...options,\n\t\t}),\n\n\tlist: (params, options) =>\n\t\trequest<ZoneListResponse>({\n\t\t\tmethod: \"GET\",\n\t\t\tpath: \"/api/v1/zones\",\n\t\t\tquery: { page: params?.page, limit: params?.limit },\n\t\t\t...options,\n\t\t}),\n\n\tget: (id, options) =>\n\t\trequest<ZoneWithGeometry>({\n\t\t\tmethod: \"GET\",\n\t\t\tpath: `/api/v1/zones/${id}`,\n\t\t\t...options,\n\t\t}),\n\n\tupdate: (id, body, options) =>\n\t\trequest<ZoneRecord>({\n\t\t\tmethod: \"PUT\",\n\t\t\tpath: `/api/v1/zones/${id}`,\n\t\t\tbody,\n\t\t\t...options,\n\t\t}),\n\n\tdelete: (id, options) =>\n\t\trequest<ZoneDeleteResponse>({\n\t\t\tmethod: \"DELETE\",\n\t\t\tpath: `/api/v1/zones/${id}`,\n\t\t\t...options,\n\t\t}),\n\n\tcontains: (params, options) =>\n\t\trequest<ZoneContainsResponse>({\n\t\t\tmethod: \"GET\",\n\t\t\tpath: \"/api/v1/zones/contains\",\n\t\t\tquery: { lat: params.lat, lng: params.lng },\n\t\t\t...options,\n\t\t}),\n\n\taddresses: (id, params, options) =>\n\t\trequest<ZoneAddressesResponse>({\n\t\t\tmethod: \"GET\",\n\t\t\tpath: `/api/v1/zones/${id}/addresses`,\n\t\t\tquery: { page: params?.page, limit: params?.limit },\n\t\t\t...options,\n\t\t}),\n});\n","import { createRequester } from \"./http.ts\";\nimport {\n\ttype AddressesResource,\n\tcreateAddresses,\n} from \"./resources/addresses.ts\";\nimport { createDevices, type DevicesResource } from \"./resources/devices.ts\";\nimport { createGeocode, type GeocodeResource } from \"./resources/geocode.ts\";\nimport { createRegions, type RegionsResource } from \"./resources/regions.ts\";\nimport { createRouting, type RoutingResource } from \"./resources/routing.ts\";\nimport { createWebhooks, type WebhooksResource } from \"./resources/webhooks.ts\";\nimport { createZones, type ZonesResource } from \"./resources/zones.ts\";\nimport type { WheraboutsClientConfig } from \"./shared-types.ts\";\n\nexport interface WheraboutsClient {\n\taddresses: AddressesResource;\n\tdevices: DevicesResource;\n\tgeocode: GeocodeResource;\n\tregions: RegionsResource;\n\trouting: RoutingResource;\n\twebhooks: WebhooksResource;\n\tzones: ZonesResource;\n}\n\nexport const createWheraboutsClient = (\n\tconfig: WheraboutsClientConfig\n): WheraboutsClient => {\n\tconst request = createRequester(config);\n\treturn {\n\t\taddresses: createAddresses(request),\n\t\tgeocode: createGeocode(request),\n\t\tzones: createZones(request),\n\t\tdevices: createDevices(request),\n\t\twebhooks: createWebhooks(request),\n\t\tregions: createRegions(request),\n\t\trouting: createRouting(request),\n\t};\n};\n"]}
package/package.json ADDED
@@ -0,0 +1,68 @@
1
+ {
2
+ "name": "@wherabouts/sdk",
3
+ "version": "0.2.0",
4
+ "description": "Official TypeScript SDK for the Wherabouts location API — Australian geocoding, geofencing zones, device tracking, and webhooks over authoritative G-NAF/ABS data.",
5
+ "type": "module",
6
+ "license": "UNLICENSED",
7
+ "homepage": "https://wherabouts.com",
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "git+https://github.com/amani-joseph/wherabouts.com.git",
11
+ "directory": "packages/sdk"
12
+ },
13
+ "keywords": [
14
+ "geocoding",
15
+ "reverse-geocoding",
16
+ "geofencing",
17
+ "australia",
18
+ "g-naf",
19
+ "abs",
20
+ "location",
21
+ "webhooks",
22
+ "wherabouts"
23
+ ],
24
+ "publishConfig": {
25
+ "access": "public"
26
+ },
27
+ "sideEffects": false,
28
+ "engines": {
29
+ "node": ">=18"
30
+ },
31
+ "main": "./dist/index.cjs",
32
+ "module": "./dist/index.js",
33
+ "types": "./dist/index.d.ts",
34
+ "exports": {
35
+ ".": {
36
+ "import": {
37
+ "types": "./dist/index.d.ts",
38
+ "default": "./dist/index.js"
39
+ },
40
+ "require": {
41
+ "types": "./dist/index.d.cts",
42
+ "default": "./dist/index.cjs"
43
+ }
44
+ }
45
+ },
46
+ "files": [
47
+ "dist",
48
+ "README.md",
49
+ "CHANGELOG.md"
50
+ ],
51
+ "scripts": {
52
+ "build": "tsup",
53
+ "check-types": "tsc --noEmit",
54
+ "test": "vitest run",
55
+ "lint:pkg": "publint && attw --pack",
56
+ "smoke": "node scripts/smoke-dist.mjs",
57
+ "prepublishOnly": "pnpm build && pnpm lint:pkg"
58
+ },
59
+ "devDependencies": {
60
+ "@arethetypeswrong/cli": "^0.18.3",
61
+ "@types/node": "^20",
62
+ "@wherabouts.com/config": "workspace:*",
63
+ "publint": "^0.3.21",
64
+ "tsup": "^8.5.1",
65
+ "typescript": "^5",
66
+ "vitest": "^4.1.4"
67
+ }
68
+ }