@seta-labs/geo 0.7.0 → 0.9.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/CHANGELOG.md CHANGED
@@ -1,5 +1,30 @@
1
1
  # @seta-labs/geo
2
2
 
3
+ ## 0.9.0
4
+
5
+ - `territory.raggi(input)`: for each point and municipality (`codComune`), the
6
+ radius that covers the whole municipality from the point: the distance to
7
+ the farthest vertex of its ISTAT boundary, in metres rounded up, from the
8
+ latest edition that has it (a suppressed municipality keeps its last).
9
+ `edizione` and `raggioM` are null when no edition has the municipality. Up
10
+ to 100 points per call; `id` is echoed back.
11
+
12
+ ## 0.8.0
13
+
14
+ - **Breaking:** `LookupCache` becomes `CachePolicy`, the cache options every
15
+ Seta service and SDK shares: `refresh` is now `noCache`, like the header
16
+ directive; `noStore`, `maxAge` and `onlyIfCached` keep their names. The type
17
+ and its header writer come from `@seta-labs/nestjs-cache-control/policy`,
18
+ bundled into the SDK: nothing more to install.
19
+ - Each client paces its calls (`pacing`): at most 10 in flight, a `429` or
20
+ `503` sent again up to 4 times after its `Retry-After` (up to a minute;
21
+ 1-8 s backoff without it), the whole queue waiting meanwhile. `pacing:
22
+ { concurrency, retries }` tunes it, `pacing: false` turns it off; two queues
23
+ are two clients.
24
+ - `timeoutMs` (default 30 s) bounds each attempt, not the wait in the queue.
25
+ - `ApiError.code`: Geo's error code (`CADASTRE_BLOCKED`, `NOT_CACHED`, ...)
26
+ read from the body.
27
+
3
28
  ## 0.7.0
4
29
 
5
30
  - `reverseGeocode(input)` answers from a chain of sources: the ANNCSU register
package/README.md CHANGED
@@ -40,9 +40,20 @@ Do not use `$fetch` as `fetch`: it returns the parsed body instead of a Response
40
40
 
41
41
  All calls return `Promise<T>` and accept optional `{ signal }` as the last
42
42
  argument. An optional `fetch: (request: Request) => Promise<Response>` allows
43
- consumer-owned instrumentation or transport adapters. There is no scheduler,
44
- queue, rate limiter or automatic retry, including for failed writes. Consumers
45
- retain their own concurrency and retry policy.
43
+ consumer-owned instrumentation or transport adapters.
44
+
45
+ Each client paces its calls: at most 10 in flight, and a `429` or `503` that
46
+ says when to come back (`Retry-After` up to a minute; 1 s, 2 s, 4 s, 8 s
47
+ without it) is sent again up to 4 times while the client's whole queue waits.
48
+ A refusal asking for a longer wait, every other status and network errors
49
+ reach the caller after one attempt. Each attempt has `timeoutMs` (default
50
+ 30 s), not counting the wait in the queue. Two queues are two clients:
51
+
52
+ ```ts
53
+ const geo = createGeoClient({ baseUrl, apiKey }); // lookups, territory
54
+ const cadastre = createGeoClient({ baseUrl, apiKey, pacing: { concurrency: 4 } }); // the map, apart
55
+ const panel = createGeoClient({ baseUrl: '/api', pacing: false }); // every call at once, once
56
+ ```
46
57
 
47
58
  | Method | Input |
48
59
  | ---------------------------------------- | -------------------------------------- |
@@ -66,14 +77,24 @@ validation of received data. `include` may be omitted and defaults to `best`
66
77
  on the service.
67
78
 
68
79
  The four lookups (`geocode`, `geocodeBatch`, `reverseGeocode`,
69
- `cadastreParcel`) take `cache` in their options, sent as `Cache-Control`:
70
- `{ refresh: true }` asks the provider again and stores the new answer,
71
- `{ refresh: true, noStore: true }` bypasses the store entirely, `{ maxAge }`
72
- asks again past that many seconds, `{ onlyIfCached: true }` never asks the
73
- provider (504 `NOT_CACHED`, or `status: 'NOT_CACHED'` for a batch item).
80
+ `cadastreParcel`) take `cache` in their options: a `CachePolicy`, the shape
81
+ every Seta service and SDK shares (`@seta-labs/nestjs-cache-control/policy`,
82
+ bundled here), sent as `Cache-Control`. `{ noCache: true }` asks the provider
83
+ again and stores the new answer, `{ noCache: true, noStore: true }` bypasses
84
+ the store entirely, `{ maxAge }` asks again past that many seconds,
85
+ `{ onlyIfCached: true }` never asks the provider (504 `NOT_CACHED`, or
86
+ `status: 'NOT_CACHED'` for a batch item). Without `cache` the store answers
87
+ when it can and the provider otherwise. The other calls read Geo's own data
88
+ and take only `signal`.
89
+
90
+ ```ts
91
+ await geo.reverseGeocode({ lat, lng }, { cache: { onlyIfCached: true } });
92
+ await geo.reverseGeocode({ lat, lng }, { cache: jobCachePolicy() }); // the job's, at each call
93
+ ```
74
94
 
75
95
  HTTP errors are `ApiError`; their body is JSON when parseable, otherwise text,
76
- or `undefined` for an empty response. A `503` from `cadastreParcel` with
96
+ or `undefined` for an empty response, and `code` is Geo's error code when the
97
+ body has one. A `503` from `cadastreParcel` with
77
98
  `code: 'CADASTRE_BLOCKED'` means the cadastral map is refusing calls: its
78
99
  `Retry-After` header (and `retryAfterSeconds` in the body) says when to ask
79
100
  again. Network failures, aborts and JSON parsing
package/dist/index.d.ts CHANGED
@@ -4,6 +4,8 @@ declare class ApiError extends Error {
4
4
  readonly status: number;
5
5
  readonly headers: Headers;
6
6
  readonly requestId: string | undefined;
7
+ /** Geo's error `code` (`CADASTRE_BLOCKED`, `NOT_CACHED`, ...), when the body has one. */
8
+ readonly code: string | undefined;
7
9
  constructor(response: Response, body: unknown);
8
10
  }
9
11
 
@@ -223,6 +225,22 @@ interface paths {
223
225
  patch?: never;
224
226
  trace?: never;
225
227
  };
228
+ '/territory/raggi': {
229
+ parameters: {
230
+ query?: never;
231
+ header?: never;
232
+ path?: never;
233
+ cookie?: never;
234
+ };
235
+ get?: never;
236
+ put?: never;
237
+ post: operations['TerritoryController_raggi'];
238
+ delete?: never;
239
+ options?: never;
240
+ head?: never;
241
+ patch?: never;
242
+ trace?: never;
243
+ };
226
244
  '/cadastre/parcel': {
227
245
  parameters: {
228
246
  query?: never;
@@ -672,6 +690,30 @@ interface components {
672
690
  TerritoryResolveResponseDto: {
673
691
  items: components['schemas']['TerritoryPointResultDto'][];
674
692
  };
693
+ TerritoryRaggioPointDto: {
694
+ id: string;
695
+ /** @description Belfiore code of the municipality (e.g. `D969`). */
696
+ codComune: string;
697
+ lat: number;
698
+ lng: number;
699
+ };
700
+ TerritoryRaggiRequestDto: {
701
+ points: components['schemas']['TerritoryRaggioPointDto'][];
702
+ };
703
+ TerritoryRaggioDto: {
704
+ id: string;
705
+ codComune: string;
706
+ /**
707
+ * Format: date
708
+ * @description Boundary edition.
709
+ */
710
+ edizione: string | null;
711
+ /** @description Metres, rounded up. */
712
+ raggioM: number | null;
713
+ };
714
+ TerritoryRaggiResponseDto: {
715
+ items: components['schemas']['TerritoryRaggioDto'][];
716
+ };
675
717
  CadastreParcelRequestDto: {
676
718
  lat: number;
677
719
  lng: number;
@@ -1171,6 +1213,36 @@ interface operations {
1171
1213
  };
1172
1214
  };
1173
1215
  };
1216
+ TerritoryController_raggi: {
1217
+ parameters: {
1218
+ query?: never;
1219
+ header?: never;
1220
+ path?: never;
1221
+ cookie?: never;
1222
+ };
1223
+ requestBody: {
1224
+ content: {
1225
+ 'application/json': components['schemas']['TerritoryRaggiRequestDto'];
1226
+ };
1227
+ };
1228
+ responses: {
1229
+ 200: {
1230
+ headers: {
1231
+ [name: string]: unknown;
1232
+ };
1233
+ content: {
1234
+ 'application/json': components['schemas']['TerritoryRaggiResponseDto'];
1235
+ };
1236
+ };
1237
+ /** @description Missing or invalid API key. */
1238
+ 401: {
1239
+ headers: {
1240
+ [name: string]: unknown;
1241
+ };
1242
+ content?: never;
1243
+ };
1244
+ };
1245
+ };
1174
1246
  CadastreController_parcel: {
1175
1247
  parameters: {
1176
1248
  query?: never;
@@ -1282,6 +1354,9 @@ type TerritoryStatus = ApiSchemas['TerritoryStatusDto'];
1282
1354
  type TerritoryResolveRequest = ApiSchemas['TerritoryResolveRequestDto'];
1283
1355
  type TerritoryResolveResponse = ApiSchemas['TerritoryResolveResponseDto'];
1284
1356
  type TerritoryPointResult = ApiSchemas['TerritoryPointResultDto'];
1357
+ type TerritoryRaggiRequest = ApiSchemas['TerritoryRaggiRequestDto'];
1358
+ type TerritoryRaggiResponse = ApiSchemas['TerritoryRaggiResponseDto'];
1359
+ type TerritoryRaggio = ApiSchemas['TerritoryRaggioDto'];
1285
1360
  type TerritoryOmi = ApiSchemas['TerritoryOmiDto'];
1286
1361
  type TerritoryComune = ApiSchemas['TerritoryComuneDto'];
1287
1362
  type TerritoryComuneDetail = ApiSchemas['TerritoryComuneDetailDto'];
@@ -1294,6 +1369,31 @@ type TerritoryQuotazione = ApiSchemas['TerritoryQuotazioneDto'];
1294
1369
  type TerritoryCompravenditePage = ApiSchemas['TerritoryCompravenditePageDto'];
1295
1370
  type TerritoryCompravendita = ApiSchemas['TerritoryCompravenditaDto'];
1296
1371
 
1372
+ /**
1373
+ * The request directives of RFC 9111 a lookup honours; any other directive is
1374
+ * ignored. One shape for both sides: the server reads it from the header
1375
+ * (`parseCacheControl`, every flag set), a client writes it
1376
+ * (`formatCacheControl`, an absent flag is false).
1377
+ */
1378
+ interface CachePolicy {
1379
+ /** `no-cache`: never answer from the store without asking the provider. */
1380
+ noCache?: boolean;
1381
+ /** `no-store`: never write the provider's answer to the store. */
1382
+ noStore?: boolean;
1383
+ /** `only-if-cached`: never ask the provider; without a usable row, refuse. */
1384
+ onlyIfCached?: boolean;
1385
+ /** `max-age=N`: a stored row older than N seconds is not usable. */
1386
+ maxAge?: number;
1387
+ }
1388
+
1389
+ /** How a client paces its calls to Geo; each client has its own queue. */
1390
+ interface PacingOptions {
1391
+ /** Requests in flight at once; default 10. */
1392
+ concurrency?: number;
1393
+ /** New attempts after a 429 or 503 that says when to come back; default 4 (5 attempts in all). */
1394
+ retries?: number;
1395
+ }
1396
+
1297
1397
  type TerritoryComuniQuery = NonNullable<ApiPaths['/territory/comuni']['get']['parameters']['query']>;
1298
1398
  type TerritoryQuotazioniQuery = NonNullable<ApiPaths['/territory/quotazioni']['get']['parameters']['query']>;
1299
1399
  type TerritoryCompravenditeQuery = NonNullable<ApiPaths['/territory/compravendite']['get']['parameters']['query']>;
@@ -1302,21 +1402,12 @@ interface RequestOptions {
1302
1402
  signal?: AbortSignal;
1303
1403
  }
1304
1404
  /**
1305
- * How a lookup may use seta-geo's store, sent as `Cache-Control` (RFC 9111).
1306
- * Without it the store answers when it can and the provider otherwise.
1405
+ * The options of a lookup, the four calls Geo answers from its store:
1406
+ * `cache` is sent as `Cache-Control` (RFC 9111). Without it Geo answers
1407
+ * from the store when it can and asks the provider otherwise.
1307
1408
  */
1308
- interface LookupCache {
1309
- /** `no-cache`: ask the provider even with a stored answer, and store the new one. */
1310
- refresh?: boolean;
1311
- /** `no-store`: never write the provider's answer (with `refresh`: a pure bypass). */
1312
- noStore?: boolean;
1313
- /** `max-age`: a stored answer older than this many seconds is asked again. */
1314
- maxAge?: number;
1315
- /** `only-if-cached`: never ask the provider; 504 `NOT_CACHED` without a stored answer. */
1316
- onlyIfCached?: boolean;
1317
- }
1318
1409
  interface LookupOptions extends RequestOptions {
1319
- cache?: LookupCache;
1410
+ cache?: CachePolicy;
1320
1411
  }
1321
1412
  interface GeoClientOptions {
1322
1413
  /** Complete API root, including /api. Relative roots are supported in browsers. */
@@ -1325,6 +1416,10 @@ interface GeoClientOptions {
1325
1416
  apiKey?: string;
1326
1417
  /** A real Fetch implementation returning a Response, not a parsed-body $fetch. */
1327
1418
  fetch?: (request: Request) => Promise<Response>;
1419
+ /** Each attempt's deadline, not counting the wait in the queue; default 30 s. */
1420
+ timeoutMs?: number;
1421
+ /** The client's queue and its retries on 429/503; `false` sends every call at once, once. */
1422
+ pacing?: false | PacingOptions;
1328
1423
  }
1329
1424
  interface TerritoryClient {
1330
1425
  status(options?: RequestOptions): Promise<TerritoryStatus>;
@@ -1334,6 +1429,7 @@ interface TerritoryClient {
1334
1429
  compravendite(query?: TerritoryCompravenditeQuery, options?: RequestOptions): Promise<TerritoryCompravenditePage>;
1335
1430
  comune(codComune: string, options?: RequestOptions): Promise<TerritoryComuneDetail>;
1336
1431
  resolve(input: TerritoryResolveRequest, options?: RequestOptions): Promise<TerritoryResolveResponse>;
1432
+ raggi(input: TerritoryRaggiRequest, options?: RequestOptions): Promise<TerritoryRaggiResponse>;
1337
1433
  }
1338
1434
  interface GeoClient {
1339
1435
  geocode(input: GeocodeRequest, options?: LookupOptions): Promise<GeocodeResult>;
@@ -1350,4 +1446,4 @@ interface GeoClient {
1350
1446
  declare function createGeoClient(options: GeoClientOptions): GeoClient;
1351
1447
 
1352
1448
  export { ApiError, createGeoClient };
1353
- export type { CadastreParcelRequest, CadastreParcelResult, CadastreParcelRow, CadastreParcelsPage, CadastreParcelsQuery, GeoAdminArea, GeoClient, GeoClientOptions, GeoStreetMatch, GeocodeBatchRequest, GeocodeBatchResponse, GeocodeRequest, GeocodeResult, LookupCache, LookupOptions, RequestOptions, ReverseGeocodeRequest, ReverseGeocodeResult, TerritoryClient, TerritoryCompravendita, TerritoryCompravenditePage, TerritoryCompravenditeQuery, TerritoryComune, TerritoryComuneDetail, TerritoryComuneRiga, TerritoryComuniPage, TerritoryComuniQuery, TerritoryDatasets, TerritoryOmi, TerritoryPointResult, TerritoryQuotazione, TerritoryQuotazioniPage, TerritoryQuotazioniQuery, TerritoryResolveRequest, TerritoryResolveResponse, TerritoryStatus, TerritoryTerritorio };
1449
+ export type { CachePolicy, CadastreParcelRequest, CadastreParcelResult, CadastreParcelRow, CadastreParcelsPage, CadastreParcelsQuery, GeoAdminArea, GeoClient, GeoClientOptions, GeoStreetMatch, GeocodeBatchRequest, GeocodeBatchResponse, GeocodeRequest, GeocodeResult, LookupOptions, PacingOptions, RequestOptions, ReverseGeocodeRequest, ReverseGeocodeResult, TerritoryClient, TerritoryCompravendita, TerritoryCompravenditePage, TerritoryCompravenditeQuery, TerritoryComune, TerritoryComuneDetail, TerritoryComuneRiga, TerritoryComuniPage, TerritoryComuniQuery, TerritoryDatasets, TerritoryOmi, TerritoryPointResult, TerritoryQuotazione, TerritoryQuotazioniPage, TerritoryQuotazioniQuery, TerritoryRaggiRequest, TerritoryRaggiResponse, TerritoryRaggio, TerritoryResolveRequest, TerritoryResolveResponse, TerritoryStatus, TerritoryTerritorio };
package/dist/index.js CHANGED
@@ -7,11 +7,14 @@ var ApiError = class extends Error {
7
7
  this.status = response.status;
8
8
  this.headers = new Headers(response.headers);
9
9
  this.requestId = response.headers.get("x-request-id") ?? void 0;
10
+ this.code = codeOf(body);
10
11
  }
11
12
  body;
12
13
  status;
13
14
  headers;
14
15
  requestId;
16
+ /** Geo's error `code` (`CADASTRE_BLOCKED`, `NOT_CACHED`, ...), when the body has one. */
17
+ code;
15
18
  };
16
19
  async function readErrorBody(response) {
17
20
  const text = await response.text();
@@ -22,9 +25,126 @@ async function readErrorBody(response) {
22
25
  return text;
23
26
  }
24
27
  }
28
+ function codeOf(body) {
29
+ if (typeof body !== "object" || body === null || !("code" in body)) return void 0;
30
+ return typeof body.code === "string" ? body.code : void 0;
31
+ }
32
+
33
+ // ../../node_modules/.pnpm/@seta-labs+nestjs-cache-control@0.2.1_@nestjs+common@12.3.2_class-transformer@0.5.1_cla_e752cf18c930d1bfb41f37f56b7e15c5/node_modules/@seta-labs/nestjs-cache-control/dist/cache-policy.util.js
34
+ var MAX_DELTA_SECONDS = 2147483648;
35
+ function formatCacheControl(policy) {
36
+ if (!policy)
37
+ return void 0;
38
+ const maxAge = policy.maxAge;
39
+ const directives = [
40
+ policy.noCache ? "no-cache" : void 0,
41
+ policy.noStore ? "no-store" : void 0,
42
+ maxAge !== void 0 && Number.isFinite(maxAge) ? `max-age=${Math.min(Math.max(0, Math.floor(maxAge)), MAX_DELTA_SECONDS)}` : void 0,
43
+ policy.onlyIfCached ? "only-if-cached" : void 0
44
+ ].filter((directive) => directive !== void 0);
45
+ return directives.length > 0 ? directives.join(", ") : void 0;
46
+ }
25
47
 
26
48
  // src/client.ts
27
49
  import createClient from "openapi-fetch";
50
+
51
+ // src/pacing.ts
52
+ var DEFAULT_CONCURRENCY = 10;
53
+ var DEFAULT_RETRIES = 4;
54
+ var RETRY_STATUSES = /* @__PURE__ */ new Set([429, 503]);
55
+ var BACKOFF_MS = 1e3;
56
+ var MAX_WAIT_MS = 6e4;
57
+ function pacedFetch(fetch, options = {}) {
58
+ const run = limiter(options.concurrency ?? DEFAULT_CONCURRENCY);
59
+ const retries = options.retries ?? DEFAULT_RETRIES;
60
+ let pausedUntil = 0;
61
+ const send = async (request, attempt) => {
62
+ while (pausedUntil > Date.now()) await sleepUntil(pausedUntil, request.signal);
63
+ const response = await fetch(request.clone());
64
+ const waitMs = attempt < retries ? retryDelay(response, attempt) : void 0;
65
+ if (waitMs !== void 0) pausedUntil = Math.max(pausedUntil, Date.now() + waitMs);
66
+ return { response, waitMs };
67
+ };
68
+ return async (request) => {
69
+ for (let attempt = 0; ; attempt++) {
70
+ const { response, waitMs } = await run(request.signal, () => send(request, attempt));
71
+ if (waitMs === void 0) return response;
72
+ await response.body?.cancel();
73
+ }
74
+ };
75
+ }
76
+ function timedFetch(fetch, timeoutMs) {
77
+ return (request) => fetch(
78
+ new Request(request, {
79
+ signal: AbortSignal.any([request.signal, AbortSignal.timeout(timeoutMs)])
80
+ })
81
+ );
82
+ }
83
+ function retryDelay(response, attempt) {
84
+ if (!RETRY_STATUSES.has(response.status)) return void 0;
85
+ const header = response.headers.get("retry-after");
86
+ const waitMs = header === null ? BACKOFF_MS * 2 ** attempt : retryAfterMs(header);
87
+ return waitMs !== void 0 && waitMs <= MAX_WAIT_MS ? waitMs : void 0;
88
+ }
89
+ function retryAfterMs(header) {
90
+ const value = header.trim();
91
+ if (/^\d+$/.test(value)) return Number(value) * 1e3;
92
+ const date = Date.parse(value);
93
+ return Number.isNaN(date) ? void 0 : Math.max(0, date - Date.now());
94
+ }
95
+ function limiter(concurrency) {
96
+ let active = 0;
97
+ const waiting = [];
98
+ const acquire = (signal) => {
99
+ signal.throwIfAborted();
100
+ if (active < concurrency) {
101
+ active++;
102
+ return Promise.resolve();
103
+ }
104
+ return new Promise((resolve, reject) => {
105
+ const start = () => {
106
+ signal.removeEventListener("abort", abort);
107
+ active++;
108
+ resolve();
109
+ };
110
+ const abort = () => {
111
+ waiting.splice(waiting.indexOf(start), 1);
112
+ reject(signal.reason);
113
+ };
114
+ waiting.push(start);
115
+ signal.addEventListener("abort", abort, { once: true });
116
+ });
117
+ };
118
+ return async (signal, task) => {
119
+ await acquire(signal);
120
+ try {
121
+ return await task();
122
+ } finally {
123
+ active--;
124
+ waiting.shift()?.();
125
+ }
126
+ };
127
+ }
128
+ function sleepUntil(time, signal) {
129
+ const delay = time - Date.now();
130
+ if (delay <= 0) return Promise.resolve();
131
+ return new Promise((resolve, reject) => {
132
+ signal.throwIfAborted();
133
+ const timer = setTimeout(done, delay);
134
+ signal.addEventListener("abort", abort, { once: true });
135
+ function done() {
136
+ signal.removeEventListener("abort", abort);
137
+ resolve();
138
+ }
139
+ function abort() {
140
+ clearTimeout(timer);
141
+ reject(signal.reason);
142
+ }
143
+ });
144
+ }
145
+
146
+ // src/client.ts
147
+ var DEFAULT_TIMEOUT_MS = 3e4;
28
148
  function apiBaseUrl(baseUrl) {
29
149
  const url = new URL(baseUrl, globalThis.location?.href);
30
150
  if (!["http:", "https:"].includes(url.protocol) || url.username || url.password || url.search || url.hash) {
@@ -39,19 +159,9 @@ async function data(request) {
39
159
  if (result.data === void 0) throw new SyntaxError("Geo API returned an empty JSON response");
40
160
  return result.data;
41
161
  }
42
- function cacheControl(cache) {
43
- if (!cache) return void 0;
44
- const directives = [
45
- cache.refresh ? "no-cache" : void 0,
46
- cache.noStore ? "no-store" : void 0,
47
- cache.maxAge === void 0 ? void 0 : `max-age=${Math.max(0, Math.floor(cache.maxAge))}`,
48
- cache.onlyIfCached ? "only-if-cached" : void 0
49
- ].filter((directive) => directive !== void 0);
50
- return directives.length > 0 ? directives.join(", ") : void 0;
51
- }
52
162
  function lookupInit(options) {
53
163
  const { cache, ...request } = options ?? {};
54
- const header = cacheControl(cache);
164
+ const header = formatCacheControl(cache);
55
165
  return header ? { ...request, headers: { "cache-control": header } } : request;
56
166
  }
57
167
  function territory(client) {
@@ -67,13 +177,14 @@ function territory(client) {
67
177
  params: { path: { codComune } }
68
178
  })
69
179
  ),
70
- resolve: (input, options) => data(client.POST("/territory/resolve", { ...options, body: input }))
180
+ resolve: (input, options) => data(client.POST("/territory/resolve", { ...options, body: input })),
181
+ raggi: (input, options) => data(client.POST("/territory/raggi", { ...options, body: input }))
71
182
  };
72
183
  }
73
184
  function createGeoClient(options) {
74
185
  const client = createClient({
75
186
  baseUrl: apiBaseUrl(options.baseUrl),
76
- fetch: options.fetch,
187
+ fetch: transport(options),
77
188
  headers: options.apiKey ? { "x-api-key": options.apiKey } : void 0
78
189
  });
79
190
  client.use({
@@ -90,6 +201,11 @@ function createGeoClient(options) {
90
201
  territory: territory(client)
91
202
  };
92
203
  }
204
+ function transport(options) {
205
+ const send = options.fetch ?? ((request) => globalThis.fetch(request));
206
+ const timed = timedFetch(send, options.timeoutMs ?? DEFAULT_TIMEOUT_MS);
207
+ return options.pacing === false ? timed : pacedFetch(timed, options.pacing);
208
+ }
93
209
  export {
94
210
  ApiError,
95
211
  createGeoClient
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@seta-labs/geo",
3
- "version": "0.7.0",
3
+ "version": "0.9.0",
4
4
  "description": "Typed Fetch client for the Seta Geo HTTP API",
5
5
  "license": "UNLICENSED",
6
6
  "type": "module",
@@ -43,9 +43,10 @@
43
43
  },
44
44
  "devDependencies": {
45
45
  "@seta-geo/types": "workspace:*",
46
- "@seta-labs/config": "^0.16.1",
46
+ "@seta-labs/config": "^0.16.2",
47
+ "@seta-labs/nestjs-cache-control": "0.2.1",
47
48
  "tsup": "^9.0.0",
48
49
  "typescript": "^6.0.3",
49
- "vitest": "^5.0.3"
50
+ "vitest": "^5.0.5"
50
51
  }
51
52
  }