@seta-labs/geo 0.6.0 → 0.8.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 +36 -0
- package/README.md +30 -9
- package/dist/index.d.ts +267 -195
- package/dist/index.js +127 -12
- package/package.json +2 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,41 @@
|
|
|
1
1
|
# @seta-labs/geo
|
|
2
2
|
|
|
3
|
+
## 0.8.0
|
|
4
|
+
|
|
5
|
+
- **Breaking:** `LookupCache` becomes `CachePolicy`, the cache options every
|
|
6
|
+
Seta service and SDK shares: `refresh` is now `noCache`, like the header
|
|
7
|
+
directive; `noStore`, `maxAge` and `onlyIfCached` keep their names. The type
|
|
8
|
+
and its header writer come from `@seta-labs/nestjs-cache-control/policy`,
|
|
9
|
+
bundled into the SDK: nothing more to install.
|
|
10
|
+
- Each client paces its calls (`pacing`): at most 10 in flight, a `429` or
|
|
11
|
+
`503` sent again up to 4 times after its `Retry-After` (up to a minute;
|
|
12
|
+
1-8 s backoff without it), the whole queue waiting meanwhile. `pacing:
|
|
13
|
+
{ concurrency, retries }` tunes it, `pacing: false` turns it off; two queues
|
|
14
|
+
are two clients.
|
|
15
|
+
- `timeoutMs` (default 30 s) bounds each attempt, not the wait in the queue.
|
|
16
|
+
- `ApiError.code`: Geo's error code (`CADASTRE_BLOCKED`, `NOT_CACHED`, ...)
|
|
17
|
+
read from the body.
|
|
18
|
+
|
|
19
|
+
## 0.7.0
|
|
20
|
+
|
|
21
|
+
- `reverseGeocode(input)` answers from a chain of sources: the ANNCSU register
|
|
22
|
+
and the OpenStreetMap house numbers loaded in seta-geo first (within 25 m),
|
|
23
|
+
TrueWay last (an exact street address within 50 m). Failing a number, the
|
|
24
|
+
named road TrueWay places the point on, as `match.level: 'VIA'` with
|
|
25
|
+
`streetNumber`, `location` and `distanceM` null. Never an approximate
|
|
26
|
+
number: with neither, `match` is null and `status` is `NO_RESULT`.
|
|
27
|
+
- The result gains `admin` (new type `GeoAdminArea`: `codComune`, `comune`,
|
|
28
|
+
`provincia`, `regione`), the municipality of the point from the ISTAT
|
|
29
|
+
boundaries, present even without a match; and `match` (new type
|
|
30
|
+
`GeoStreetMatch`): `level`, `provider` (`ANNCSU`, `OSM`, `TRUEWAY`),
|
|
31
|
+
`precision`, `street`, `streetNumber`, `postalCode`, `locality`,
|
|
32
|
+
`location`, `distanceM`, `sourceRef`.
|
|
33
|
+
- `address` is now composed from the match: `Via Ponte Schiavo 13, Roma (RM)`,
|
|
34
|
+
or `Via Ponte Schiavo, Roma (RM)` for a street alone.
|
|
35
|
+
- With `include: 'full'`, `results` is the raw answer of `match.provider`:
|
|
36
|
+
the TrueWay result list, or the ANNCSU/OSM record as one item.
|
|
37
|
+
- The request takes `maxDistanceM` (1-50), which can only tighten the limits.
|
|
38
|
+
|
|
3
39
|
## 0.6.0
|
|
4
40
|
|
|
5
41
|
- `comune(codComune)` gains the `territorio` block (new type
|
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.
|
|
44
|
-
|
|
45
|
-
|
|
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
|
|
70
|
-
|
|
71
|
-
`{
|
|
72
|
-
|
|
73
|
-
|
|
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
|
|
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
|
|
|
@@ -111,30 +113,30 @@ interface paths {
|
|
|
111
113
|
patch?: never;
|
|
112
114
|
trace?: never;
|
|
113
115
|
};
|
|
114
|
-
'/
|
|
116
|
+
'/territory/status': {
|
|
115
117
|
parameters: {
|
|
116
118
|
query?: never;
|
|
117
119
|
header?: never;
|
|
118
120
|
path?: never;
|
|
119
121
|
cookie?: never;
|
|
120
122
|
};
|
|
121
|
-
get
|
|
123
|
+
get: operations['TerritoryController_status'];
|
|
122
124
|
put?: never;
|
|
123
|
-
post
|
|
125
|
+
post?: never;
|
|
124
126
|
delete?: never;
|
|
125
127
|
options?: never;
|
|
126
128
|
head?: never;
|
|
127
129
|
patch?: never;
|
|
128
130
|
trace?: never;
|
|
129
131
|
};
|
|
130
|
-
'/
|
|
132
|
+
'/territory/datasets': {
|
|
131
133
|
parameters: {
|
|
132
134
|
query?: never;
|
|
133
135
|
header?: never;
|
|
134
136
|
path?: never;
|
|
135
137
|
cookie?: never;
|
|
136
138
|
};
|
|
137
|
-
get: operations['
|
|
139
|
+
get: operations['TerritoryController_datasets'];
|
|
138
140
|
put?: never;
|
|
139
141
|
post?: never;
|
|
140
142
|
delete?: never;
|
|
@@ -143,14 +145,14 @@ interface paths {
|
|
|
143
145
|
patch?: never;
|
|
144
146
|
trace?: never;
|
|
145
147
|
};
|
|
146
|
-
'/territory/
|
|
148
|
+
'/territory/comuni': {
|
|
147
149
|
parameters: {
|
|
148
150
|
query?: never;
|
|
149
151
|
header?: never;
|
|
150
152
|
path?: never;
|
|
151
153
|
cookie?: never;
|
|
152
154
|
};
|
|
153
|
-
get: operations['
|
|
155
|
+
get: operations['TerritoryController_listComuni'];
|
|
154
156
|
put?: never;
|
|
155
157
|
post?: never;
|
|
156
158
|
delete?: never;
|
|
@@ -159,14 +161,14 @@ interface paths {
|
|
|
159
161
|
patch?: never;
|
|
160
162
|
trace?: never;
|
|
161
163
|
};
|
|
162
|
-
'/territory/
|
|
164
|
+
'/territory/quotazioni': {
|
|
163
165
|
parameters: {
|
|
164
166
|
query?: never;
|
|
165
167
|
header?: never;
|
|
166
168
|
path?: never;
|
|
167
169
|
cookie?: never;
|
|
168
170
|
};
|
|
169
|
-
get: operations['
|
|
171
|
+
get: operations['TerritoryController_listQuotazioni'];
|
|
170
172
|
put?: never;
|
|
171
173
|
post?: never;
|
|
172
174
|
delete?: never;
|
|
@@ -175,14 +177,14 @@ interface paths {
|
|
|
175
177
|
patch?: never;
|
|
176
178
|
trace?: never;
|
|
177
179
|
};
|
|
178
|
-
'/territory/
|
|
180
|
+
'/territory/compravendite': {
|
|
179
181
|
parameters: {
|
|
180
182
|
query?: never;
|
|
181
183
|
header?: never;
|
|
182
184
|
path?: never;
|
|
183
185
|
cookie?: never;
|
|
184
186
|
};
|
|
185
|
-
get: operations['
|
|
187
|
+
get: operations['TerritoryController_listCompravendite'];
|
|
186
188
|
put?: never;
|
|
187
189
|
post?: never;
|
|
188
190
|
delete?: never;
|
|
@@ -191,14 +193,14 @@ interface paths {
|
|
|
191
193
|
patch?: never;
|
|
192
194
|
trace?: never;
|
|
193
195
|
};
|
|
194
|
-
'/territory/
|
|
196
|
+
'/territory/comuni/{codComune}': {
|
|
195
197
|
parameters: {
|
|
196
198
|
query?: never;
|
|
197
199
|
header?: never;
|
|
198
200
|
path?: never;
|
|
199
201
|
cookie?: never;
|
|
200
202
|
};
|
|
201
|
-
get: operations['
|
|
203
|
+
get: operations['TerritoryController_comune'];
|
|
202
204
|
put?: never;
|
|
203
205
|
post?: never;
|
|
204
206
|
delete?: never;
|
|
@@ -207,48 +209,48 @@ interface paths {
|
|
|
207
209
|
patch?: never;
|
|
208
210
|
trace?: never;
|
|
209
211
|
};
|
|
210
|
-
'/territory/
|
|
212
|
+
'/territory/resolve': {
|
|
211
213
|
parameters: {
|
|
212
214
|
query?: never;
|
|
213
215
|
header?: never;
|
|
214
216
|
path?: never;
|
|
215
217
|
cookie?: never;
|
|
216
218
|
};
|
|
217
|
-
get
|
|
219
|
+
get?: never;
|
|
218
220
|
put?: never;
|
|
219
|
-
post
|
|
221
|
+
post: operations['TerritoryController_resolve'];
|
|
220
222
|
delete?: never;
|
|
221
223
|
options?: never;
|
|
222
224
|
head?: never;
|
|
223
225
|
patch?: never;
|
|
224
226
|
trace?: never;
|
|
225
227
|
};
|
|
226
|
-
'/
|
|
228
|
+
'/cadastre/parcel': {
|
|
227
229
|
parameters: {
|
|
228
230
|
query?: never;
|
|
229
231
|
header?: never;
|
|
230
232
|
path?: never;
|
|
231
233
|
cookie?: never;
|
|
232
234
|
};
|
|
233
|
-
get
|
|
235
|
+
get?: never;
|
|
234
236
|
put?: never;
|
|
235
|
-
post
|
|
237
|
+
post: operations['CadastreController_parcel'];
|
|
236
238
|
delete?: never;
|
|
237
239
|
options?: never;
|
|
238
240
|
head?: never;
|
|
239
241
|
patch?: never;
|
|
240
242
|
trace?: never;
|
|
241
243
|
};
|
|
242
|
-
'/
|
|
244
|
+
'/cadastre/parcels': {
|
|
243
245
|
parameters: {
|
|
244
246
|
query?: never;
|
|
245
247
|
header?: never;
|
|
246
248
|
path?: never;
|
|
247
249
|
cookie?: never;
|
|
248
250
|
};
|
|
249
|
-
get
|
|
251
|
+
get: operations['CadastreController_listParcels'];
|
|
250
252
|
put?: never;
|
|
251
|
-
post
|
|
253
|
+
post?: never;
|
|
252
254
|
delete?: never;
|
|
253
255
|
options?: never;
|
|
254
256
|
head?: never;
|
|
@@ -410,83 +412,68 @@ interface components {
|
|
|
410
412
|
* @enum {string}
|
|
411
413
|
*/
|
|
412
414
|
include?: 'best' | 'full';
|
|
415
|
+
/** @description Tightens the distance a house number may lie from the point (25 m for ANNCSU and OSM, 50 m for TrueWay); it never widens them. */
|
|
416
|
+
maxDistanceM?: number;
|
|
413
417
|
};
|
|
414
|
-
|
|
415
|
-
/** @
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
/** @description True when the answer came from the cache, no provider call. */
|
|
422
|
-
cached: boolean;
|
|
423
|
-
/** @description Only with include=full: the provider's result list exactly as returned (all tiers, all fields). Empty array = the provider answered with no results. Null = the raw response is not available for this cached row. */
|
|
424
|
-
results?: {
|
|
425
|
-
[key: string]: unknown;
|
|
426
|
-
}[] | null;
|
|
418
|
+
GeoAdminAreaDto: {
|
|
419
|
+
/** @description Belfiore code, `H501`. */
|
|
420
|
+
codComune: string;
|
|
421
|
+
comune: string;
|
|
422
|
+
/** @description Province code, `RM`. */
|
|
423
|
+
provincia: string;
|
|
424
|
+
regione: string;
|
|
427
425
|
};
|
|
428
|
-
|
|
426
|
+
GeoLocationDto: {
|
|
429
427
|
lat: number;
|
|
430
428
|
lng: number;
|
|
431
429
|
};
|
|
432
|
-
|
|
433
|
-
/** @description Canonical cache key: "lat,lng" rounded to 5 decimals (~1 m). */
|
|
434
|
-
query: string;
|
|
430
|
+
GeoStreetMatchDto: {
|
|
435
431
|
/**
|
|
436
|
-
* @description
|
|
437
|
-
* @
|
|
432
|
+
* @description How far down the address the match reaches: `CIVICO` a house number close enough, `VIA` only the street, when no source has a number close enough.
|
|
433
|
+
* @enum {string}
|
|
438
434
|
*/
|
|
439
|
-
|
|
440
|
-
/** @example B885 */
|
|
441
|
-
codComune: string | null;
|
|
442
|
-
/** @description Null when the map gives none: blanks and "null" strings are cleared. */
|
|
443
|
-
sezione: string | null;
|
|
444
|
-
/** @example 36 */
|
|
445
|
-
foglio: string | null;
|
|
446
|
-
/** @example G */
|
|
447
|
-
allegato: string | null;
|
|
448
|
-
sviluppo: string | null;
|
|
449
|
-
/** @example 5101 */
|
|
450
|
-
particella: string | null;
|
|
451
|
-
/** @description The map answer exactly as returned (blank SEZIONE and "null" strings included). */
|
|
452
|
-
raw: {
|
|
453
|
-
[key: string]: unknown;
|
|
454
|
-
};
|
|
455
|
-
/** @description True when the answer came from the cache, no map call. */
|
|
456
|
-
cached: boolean;
|
|
457
|
-
};
|
|
458
|
-
CadastreParcelRowDto: {
|
|
459
|
-
/** @description Canonical cache key: "lat,lng" rounded to 5 decimals. */
|
|
460
|
-
query: string;
|
|
461
|
-
lat: number;
|
|
462
|
-
lng: number;
|
|
463
|
-
/** @example PARTICELLA */
|
|
464
|
-
tipologia: string;
|
|
465
|
-
/** @example B885 */
|
|
466
|
-
codComune: string | null;
|
|
467
|
-
/** @description Municipality name; null when the code is not in the registry. */
|
|
468
|
-
comune: string | null;
|
|
469
|
-
/** @example AL */
|
|
470
|
-
provincia: string | null;
|
|
471
|
-
sezione: string | null;
|
|
472
|
-
/** @example 36 */
|
|
473
|
-
foglio: string | null;
|
|
474
|
-
/** @example G */
|
|
475
|
-
allegato: string | null;
|
|
476
|
-
sviluppo: string | null;
|
|
477
|
-
/** @example 5101 */
|
|
478
|
-
particella: string | null;
|
|
435
|
+
level: 'CIVICO' | 'VIA';
|
|
479
436
|
/**
|
|
480
|
-
*
|
|
481
|
-
* @
|
|
437
|
+
* @description The source that answered: ANNCSU and OSM are loaded locally, TrueWay is paid.
|
|
438
|
+
* @enum {string}
|
|
482
439
|
*/
|
|
483
|
-
|
|
440
|
+
provider: 'ANNCSU' | 'OSM' | 'TRUEWAY';
|
|
441
|
+
/** @description How the source rates the position: `metodo-1`…`metodo-5` (ANNCSU), `nodo` or `edificio` (OSM), `exact` (TrueWay), `centroid` (a TrueWay street). */
|
|
442
|
+
precision: string;
|
|
443
|
+
street: string;
|
|
444
|
+
/** @description `12`, `12/A`, `12 rosso`; null at level `VIA`. */
|
|
445
|
+
streetNumber: string | null;
|
|
446
|
+
/** @description Null from ANNCSU, which has none. */
|
|
447
|
+
postalCode: string | null;
|
|
448
|
+
/** @description Hamlet or locality as the source names it; the municipality is in `admin`. */
|
|
449
|
+
locality: string | null;
|
|
450
|
+
/** @description Where the house number is; null for a street the source places at its centroid. */
|
|
451
|
+
location: components['schemas']['GeoLocationDto'] | null;
|
|
452
|
+
/** @description Metres from the requested point to `location`; null without a location. */
|
|
453
|
+
distanceM: number | null;
|
|
454
|
+
/** @description Id of the record in its source: ANNCSU access, OSM `n123`/`w456`. */
|
|
455
|
+
sourceRef: string | null;
|
|
484
456
|
};
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
457
|
+
ReverseGeocodeResultDto: {
|
|
458
|
+
/**
|
|
459
|
+
* @description OK when `match` is present.
|
|
460
|
+
* @enum {string}
|
|
461
|
+
*/
|
|
462
|
+
status: 'OK' | 'NO_RESULT';
|
|
463
|
+
/** @description Canonical cache key: "lat,lng" rounded to 5 decimals (~1 m). */
|
|
464
|
+
query: string;
|
|
465
|
+
/** @description The match as one line: `Via Ponte Schiavo 13, Roma (RM)`, or `Via Ponte Schiavo, Roma (RM)` at level `VIA`. */
|
|
466
|
+
address: string | null;
|
|
467
|
+
/** @description Municipality of the point from the ISTAT boundaries, whatever the match; null at sea, abroad or while no boundary edition is loaded. */
|
|
468
|
+
admin: components['schemas']['GeoAdminAreaDto'] | null;
|
|
469
|
+
/** @description An exact house number close enough, else the street a source names (level `VIA`); null when neither: never an approximate number. */
|
|
470
|
+
match: components['schemas']['GeoStreetMatchDto'] | null;
|
|
471
|
+
/** @description True when the answer came from stored rows: no source was asked again. */
|
|
472
|
+
cached: boolean;
|
|
473
|
+
/** @description Only with include=full: the raw answer of the source in `match.provider` (the TrueWay result list, or the ANNCSU/OSM record as one item); without a match, the raw answer of the last source asked. */
|
|
474
|
+
results?: {
|
|
475
|
+
[key: string]: unknown;
|
|
476
|
+
}[] | null;
|
|
490
477
|
};
|
|
491
478
|
TerritoryStatusDto: {
|
|
492
479
|
/** @description Latest OMI publication loaded (`YYYY-S`), null while no supply is loaded. */
|
|
@@ -651,7 +638,7 @@ interface components {
|
|
|
651
638
|
TerritoryComuneDetailDto: {
|
|
652
639
|
comune: components['schemas']['TerritoryComuneDto'];
|
|
653
640
|
istat: components['schemas']['TerritoryIstatDto'];
|
|
654
|
-
/** @description
|
|
641
|
+
/** @description Latest SITUAS edition that describes the municipality; null when none does, as for one suppressed before the first edition loaded. */
|
|
655
642
|
territorio: components['schemas']['TerritoryTerritorioDto'] | null;
|
|
656
643
|
/** @description Publication the zone quotations come from, null while none is loaded. */
|
|
657
644
|
semestre: string | null;
|
|
@@ -687,6 +674,69 @@ interface components {
|
|
|
687
674
|
TerritoryResolveResponseDto: {
|
|
688
675
|
items: components['schemas']['TerritoryPointResultDto'][];
|
|
689
676
|
};
|
|
677
|
+
CadastreParcelRequestDto: {
|
|
678
|
+
lat: number;
|
|
679
|
+
lng: number;
|
|
680
|
+
};
|
|
681
|
+
CadastreParcelResultDto: {
|
|
682
|
+
/** @description Canonical cache key: "lat,lng" rounded to 5 decimals (~1 m). */
|
|
683
|
+
query: string;
|
|
684
|
+
/**
|
|
685
|
+
* @description What lies under the point, verbatim from the map: PARTICELLA, STRADA, …
|
|
686
|
+
* @example PARTICELLA
|
|
687
|
+
*/
|
|
688
|
+
tipologia: string;
|
|
689
|
+
/** @example B885 */
|
|
690
|
+
codComune: string | null;
|
|
691
|
+
/** @description Null when the map gives none: blanks and "null" strings are cleared. */
|
|
692
|
+
sezione: string | null;
|
|
693
|
+
/** @example 36 */
|
|
694
|
+
foglio: string | null;
|
|
695
|
+
/** @example G */
|
|
696
|
+
allegato: string | null;
|
|
697
|
+
sviluppo: string | null;
|
|
698
|
+
/** @example 5101 */
|
|
699
|
+
particella: string | null;
|
|
700
|
+
/** @description The map answer exactly as returned (blank SEZIONE and "null" strings included). */
|
|
701
|
+
raw: {
|
|
702
|
+
[key: string]: unknown;
|
|
703
|
+
};
|
|
704
|
+
/** @description True when the answer came from the cache, no map call. */
|
|
705
|
+
cached: boolean;
|
|
706
|
+
};
|
|
707
|
+
CadastreParcelRowDto: {
|
|
708
|
+
/** @description Canonical cache key: "lat,lng" rounded to 5 decimals. */
|
|
709
|
+
query: string;
|
|
710
|
+
lat: number;
|
|
711
|
+
lng: number;
|
|
712
|
+
/** @example PARTICELLA */
|
|
713
|
+
tipologia: string;
|
|
714
|
+
/** @example B885 */
|
|
715
|
+
codComune: string | null;
|
|
716
|
+
/** @description Municipality name; null when the code is not in the registry. */
|
|
717
|
+
comune: string | null;
|
|
718
|
+
/** @example AL */
|
|
719
|
+
provincia: string | null;
|
|
720
|
+
sezione: string | null;
|
|
721
|
+
/** @example 36 */
|
|
722
|
+
foglio: string | null;
|
|
723
|
+
/** @example G */
|
|
724
|
+
allegato: string | null;
|
|
725
|
+
sviluppo: string | null;
|
|
726
|
+
/** @example 5101 */
|
|
727
|
+
particella: string | null;
|
|
728
|
+
/**
|
|
729
|
+
* Format: date-time
|
|
730
|
+
* @description When the map last gave this answer.
|
|
731
|
+
*/
|
|
732
|
+
updatedAt: string;
|
|
733
|
+
};
|
|
734
|
+
CadastreParcelsPageDto: {
|
|
735
|
+
page: number;
|
|
736
|
+
pageSize: number;
|
|
737
|
+
total: number;
|
|
738
|
+
items: components['schemas']['CadastreParcelRowDto'][];
|
|
739
|
+
};
|
|
690
740
|
};
|
|
691
741
|
responses: never;
|
|
692
742
|
parameters: never;
|
|
@@ -894,97 +944,6 @@ interface operations {
|
|
|
894
944
|
};
|
|
895
945
|
};
|
|
896
946
|
};
|
|
897
|
-
CadastreController_parcel: {
|
|
898
|
-
parameters: {
|
|
899
|
-
query?: never;
|
|
900
|
-
header?: {
|
|
901
|
-
/** @description no-cache: ask the provider and store its answer; no-store: never write the store; max-age=N: a stored answer older than N seconds is asked again; only-if-cached: never ask the provider. The response says what happened in Cache-Status (hit, fwd=miss|request|stale, stored) and, on a hit, Age. */
|
|
902
|
-
'Cache-Control'?: string;
|
|
903
|
-
};
|
|
904
|
-
path?: never;
|
|
905
|
-
cookie?: never;
|
|
906
|
-
};
|
|
907
|
-
requestBody: {
|
|
908
|
-
content: {
|
|
909
|
-
'application/json': components['schemas']['CadastreParcelRequestDto'];
|
|
910
|
-
};
|
|
911
|
-
};
|
|
912
|
-
responses: {
|
|
913
|
-
/** @description What the AdE cadastral map finds under the point: a parcel, a street, … */
|
|
914
|
-
200: {
|
|
915
|
-
headers: {
|
|
916
|
-
[name: string]: unknown;
|
|
917
|
-
};
|
|
918
|
-
content: {
|
|
919
|
-
'application/json': components['schemas']['CadastreParcelResultDto'];
|
|
920
|
-
};
|
|
921
|
-
};
|
|
922
|
-
/** @description Missing or invalid API key. */
|
|
923
|
-
401: {
|
|
924
|
-
headers: {
|
|
925
|
-
[name: string]: unknown;
|
|
926
|
-
};
|
|
927
|
-
content?: never;
|
|
928
|
-
};
|
|
929
|
-
/** @description CADASTRE_UNAVAILABLE or CADASTRE_INVALID_REPLY: the map gave no answer. */
|
|
930
|
-
502: {
|
|
931
|
-
headers: {
|
|
932
|
-
[name: string]: unknown;
|
|
933
|
-
};
|
|
934
|
-
content?: never;
|
|
935
|
-
};
|
|
936
|
-
/** @description CADASTRE_BLOCKED: the map is refusing calls; Retry-After says when to try again. */
|
|
937
|
-
503: {
|
|
938
|
-
headers: {
|
|
939
|
-
[name: string]: unknown;
|
|
940
|
-
};
|
|
941
|
-
content?: never;
|
|
942
|
-
};
|
|
943
|
-
/** @description NOT_CACHED: only-if-cached and no usable stored answer. */
|
|
944
|
-
504: {
|
|
945
|
-
headers: {
|
|
946
|
-
[name: string]: unknown;
|
|
947
|
-
};
|
|
948
|
-
content?: never;
|
|
949
|
-
};
|
|
950
|
-
};
|
|
951
|
-
};
|
|
952
|
-
CadastreController_listParcels: {
|
|
953
|
-
parameters: {
|
|
954
|
-
query?: {
|
|
955
|
-
page?: number;
|
|
956
|
-
pageSize?: number;
|
|
957
|
-
/** @description Cadastral municipality code. */
|
|
958
|
-
codComune?: string;
|
|
959
|
-
foglio?: string;
|
|
960
|
-
particella?: string;
|
|
961
|
-
/** @description What lies under the point. */
|
|
962
|
-
tipologia?: string;
|
|
963
|
-
};
|
|
964
|
-
header?: never;
|
|
965
|
-
path?: never;
|
|
966
|
-
cookie?: never;
|
|
967
|
-
};
|
|
968
|
-
requestBody?: never;
|
|
969
|
-
responses: {
|
|
970
|
-
/** @description The stored map answers, newest first; never a map call. */
|
|
971
|
-
200: {
|
|
972
|
-
headers: {
|
|
973
|
-
[name: string]: unknown;
|
|
974
|
-
};
|
|
975
|
-
content: {
|
|
976
|
-
'application/json': components['schemas']['CadastreParcelsPageDto'];
|
|
977
|
-
};
|
|
978
|
-
};
|
|
979
|
-
/** @description Missing or invalid API key. */
|
|
980
|
-
401: {
|
|
981
|
-
headers: {
|
|
982
|
-
[name: string]: unknown;
|
|
983
|
-
};
|
|
984
|
-
content?: never;
|
|
985
|
-
};
|
|
986
|
-
};
|
|
987
|
-
};
|
|
988
947
|
TerritoryController_status: {
|
|
989
948
|
parameters: {
|
|
990
949
|
query?: never;
|
|
@@ -1214,6 +1173,97 @@ interface operations {
|
|
|
1214
1173
|
};
|
|
1215
1174
|
};
|
|
1216
1175
|
};
|
|
1176
|
+
CadastreController_parcel: {
|
|
1177
|
+
parameters: {
|
|
1178
|
+
query?: never;
|
|
1179
|
+
header?: {
|
|
1180
|
+
/** @description no-cache: ask the provider and store its answer; no-store: never write the store; max-age=N: a stored answer older than N seconds is asked again; only-if-cached: never ask the provider. The response says what happened in Cache-Status (hit, fwd=miss|request|stale, stored) and, on a hit, Age. */
|
|
1181
|
+
'Cache-Control'?: string;
|
|
1182
|
+
};
|
|
1183
|
+
path?: never;
|
|
1184
|
+
cookie?: never;
|
|
1185
|
+
};
|
|
1186
|
+
requestBody: {
|
|
1187
|
+
content: {
|
|
1188
|
+
'application/json': components['schemas']['CadastreParcelRequestDto'];
|
|
1189
|
+
};
|
|
1190
|
+
};
|
|
1191
|
+
responses: {
|
|
1192
|
+
/** @description What the AdE cadastral map finds under the point: a parcel, a street, … */
|
|
1193
|
+
200: {
|
|
1194
|
+
headers: {
|
|
1195
|
+
[name: string]: unknown;
|
|
1196
|
+
};
|
|
1197
|
+
content: {
|
|
1198
|
+
'application/json': components['schemas']['CadastreParcelResultDto'];
|
|
1199
|
+
};
|
|
1200
|
+
};
|
|
1201
|
+
/** @description Missing or invalid API key. */
|
|
1202
|
+
401: {
|
|
1203
|
+
headers: {
|
|
1204
|
+
[name: string]: unknown;
|
|
1205
|
+
};
|
|
1206
|
+
content?: never;
|
|
1207
|
+
};
|
|
1208
|
+
/** @description CADASTRE_UNAVAILABLE or CADASTRE_INVALID_REPLY: the map gave no answer. */
|
|
1209
|
+
502: {
|
|
1210
|
+
headers: {
|
|
1211
|
+
[name: string]: unknown;
|
|
1212
|
+
};
|
|
1213
|
+
content?: never;
|
|
1214
|
+
};
|
|
1215
|
+
/** @description CADASTRE_BLOCKED: the map is refusing calls; Retry-After says when to try again. */
|
|
1216
|
+
503: {
|
|
1217
|
+
headers: {
|
|
1218
|
+
[name: string]: unknown;
|
|
1219
|
+
};
|
|
1220
|
+
content?: never;
|
|
1221
|
+
};
|
|
1222
|
+
/** @description NOT_CACHED: only-if-cached and no usable stored answer. */
|
|
1223
|
+
504: {
|
|
1224
|
+
headers: {
|
|
1225
|
+
[name: string]: unknown;
|
|
1226
|
+
};
|
|
1227
|
+
content?: never;
|
|
1228
|
+
};
|
|
1229
|
+
};
|
|
1230
|
+
};
|
|
1231
|
+
CadastreController_listParcels: {
|
|
1232
|
+
parameters: {
|
|
1233
|
+
query?: {
|
|
1234
|
+
page?: number;
|
|
1235
|
+
pageSize?: number;
|
|
1236
|
+
/** @description Cadastral municipality code. */
|
|
1237
|
+
codComune?: string;
|
|
1238
|
+
foglio?: string;
|
|
1239
|
+
particella?: string;
|
|
1240
|
+
/** @description What lies under the point. */
|
|
1241
|
+
tipologia?: string;
|
|
1242
|
+
};
|
|
1243
|
+
header?: never;
|
|
1244
|
+
path?: never;
|
|
1245
|
+
cookie?: never;
|
|
1246
|
+
};
|
|
1247
|
+
requestBody?: never;
|
|
1248
|
+
responses: {
|
|
1249
|
+
/** @description The stored map answers, newest first; never a map call. */
|
|
1250
|
+
200: {
|
|
1251
|
+
headers: {
|
|
1252
|
+
[name: string]: unknown;
|
|
1253
|
+
};
|
|
1254
|
+
content: {
|
|
1255
|
+
'application/json': components['schemas']['CadastreParcelsPageDto'];
|
|
1256
|
+
};
|
|
1257
|
+
};
|
|
1258
|
+
/** @description Missing or invalid API key. */
|
|
1259
|
+
401: {
|
|
1260
|
+
headers: {
|
|
1261
|
+
[name: string]: unknown;
|
|
1262
|
+
};
|
|
1263
|
+
content?: never;
|
|
1264
|
+
};
|
|
1265
|
+
};
|
|
1266
|
+
};
|
|
1217
1267
|
}
|
|
1218
1268
|
|
|
1219
1269
|
type ApiPaths = paths;
|
|
@@ -1224,6 +1274,8 @@ type GeocodeResult = ApiSchemas['GeocodeResultDto'];
|
|
|
1224
1274
|
type GeocodeBatchResponse = ApiSchemas['GeocodeBatchResponseDto'];
|
|
1225
1275
|
type ReverseGeocodeRequest = ApiSchemas['ReverseGeocodeRequestDto'];
|
|
1226
1276
|
type ReverseGeocodeResult = ApiSchemas['ReverseGeocodeResultDto'];
|
|
1277
|
+
type GeoStreetMatch = ApiSchemas['GeoStreetMatchDto'];
|
|
1278
|
+
type GeoAdminArea = ApiSchemas['GeoAdminAreaDto'];
|
|
1227
1279
|
type CadastreParcelRequest = ApiSchemas['CadastreParcelRequestDto'];
|
|
1228
1280
|
type CadastreParcelResult = ApiSchemas['CadastreParcelResultDto'];
|
|
1229
1281
|
type CadastreParcelsPage = ApiSchemas['CadastreParcelsPageDto'];
|
|
@@ -1244,6 +1296,31 @@ type TerritoryQuotazione = ApiSchemas['TerritoryQuotazioneDto'];
|
|
|
1244
1296
|
type TerritoryCompravenditePage = ApiSchemas['TerritoryCompravenditePageDto'];
|
|
1245
1297
|
type TerritoryCompravendita = ApiSchemas['TerritoryCompravenditaDto'];
|
|
1246
1298
|
|
|
1299
|
+
/**
|
|
1300
|
+
* The request directives of RFC 9111 a lookup honours; any other directive is
|
|
1301
|
+
* ignored. One shape for both sides: the server reads it from the header
|
|
1302
|
+
* (`parseCacheControl`, every flag set), a client writes it
|
|
1303
|
+
* (`formatCacheControl`, an absent flag is false).
|
|
1304
|
+
*/
|
|
1305
|
+
interface CachePolicy {
|
|
1306
|
+
/** `no-cache`: never answer from the store without asking the provider. */
|
|
1307
|
+
noCache?: boolean;
|
|
1308
|
+
/** `no-store`: never write the provider's answer to the store. */
|
|
1309
|
+
noStore?: boolean;
|
|
1310
|
+
/** `only-if-cached`: never ask the provider; without a usable row, refuse. */
|
|
1311
|
+
onlyIfCached?: boolean;
|
|
1312
|
+
/** `max-age=N`: a stored row older than N seconds is not usable. */
|
|
1313
|
+
maxAge?: number;
|
|
1314
|
+
}
|
|
1315
|
+
|
|
1316
|
+
/** How a client paces its calls to Geo; each client has its own queue. */
|
|
1317
|
+
interface PacingOptions {
|
|
1318
|
+
/** Requests in flight at once; default 10. */
|
|
1319
|
+
concurrency?: number;
|
|
1320
|
+
/** New attempts after a 429 or 503 that says when to come back; default 4 (5 attempts in all). */
|
|
1321
|
+
retries?: number;
|
|
1322
|
+
}
|
|
1323
|
+
|
|
1247
1324
|
type TerritoryComuniQuery = NonNullable<ApiPaths['/territory/comuni']['get']['parameters']['query']>;
|
|
1248
1325
|
type TerritoryQuotazioniQuery = NonNullable<ApiPaths['/territory/quotazioni']['get']['parameters']['query']>;
|
|
1249
1326
|
type TerritoryCompravenditeQuery = NonNullable<ApiPaths['/territory/compravendite']['get']['parameters']['query']>;
|
|
@@ -1252,21 +1329,12 @@ interface RequestOptions {
|
|
|
1252
1329
|
signal?: AbortSignal;
|
|
1253
1330
|
}
|
|
1254
1331
|
/**
|
|
1255
|
-
*
|
|
1256
|
-
*
|
|
1332
|
+
* The options of a lookup, the four calls Geo answers from its store:
|
|
1333
|
+
* `cache` is sent as `Cache-Control` (RFC 9111). Without it Geo answers
|
|
1334
|
+
* from the store when it can and asks the provider otherwise.
|
|
1257
1335
|
*/
|
|
1258
|
-
interface LookupCache {
|
|
1259
|
-
/** `no-cache`: ask the provider even with a stored answer, and store the new one. */
|
|
1260
|
-
refresh?: boolean;
|
|
1261
|
-
/** `no-store`: never write the provider's answer (with `refresh`: a pure bypass). */
|
|
1262
|
-
noStore?: boolean;
|
|
1263
|
-
/** `max-age`: a stored answer older than this many seconds is asked again. */
|
|
1264
|
-
maxAge?: number;
|
|
1265
|
-
/** `only-if-cached`: never ask the provider; 504 `NOT_CACHED` without a stored answer. */
|
|
1266
|
-
onlyIfCached?: boolean;
|
|
1267
|
-
}
|
|
1268
1336
|
interface LookupOptions extends RequestOptions {
|
|
1269
|
-
cache?:
|
|
1337
|
+
cache?: CachePolicy;
|
|
1270
1338
|
}
|
|
1271
1339
|
interface GeoClientOptions {
|
|
1272
1340
|
/** Complete API root, including /api. Relative roots are supported in browsers. */
|
|
@@ -1275,6 +1343,10 @@ interface GeoClientOptions {
|
|
|
1275
1343
|
apiKey?: string;
|
|
1276
1344
|
/** A real Fetch implementation returning a Response, not a parsed-body $fetch. */
|
|
1277
1345
|
fetch?: (request: Request) => Promise<Response>;
|
|
1346
|
+
/** Each attempt's deadline, not counting the wait in the queue; default 30 s. */
|
|
1347
|
+
timeoutMs?: number;
|
|
1348
|
+
/** The client's queue and its retries on 429/503; `false` sends every call at once, once. */
|
|
1349
|
+
pacing?: false | PacingOptions;
|
|
1278
1350
|
}
|
|
1279
1351
|
interface TerritoryClient {
|
|
1280
1352
|
status(options?: RequestOptions): Promise<TerritoryStatus>;
|
|
@@ -1300,4 +1372,4 @@ interface GeoClient {
|
|
|
1300
1372
|
declare function createGeoClient(options: GeoClientOptions): GeoClient;
|
|
1301
1373
|
|
|
1302
1374
|
export { ApiError, createGeoClient };
|
|
1303
|
-
export type { CadastreParcelRequest, CadastreParcelResult, CadastreParcelRow, CadastreParcelsPage, CadastreParcelsQuery, GeoClient, GeoClientOptions, GeocodeBatchRequest, GeocodeBatchResponse, GeocodeRequest, GeocodeResult,
|
|
1375
|
+
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, 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.1.2_class-transformer@0.5.1_cla_d70e55208c987ac6b22f0eff0ce5869e/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 =
|
|
164
|
+
const header = formatCacheControl(cache);
|
|
55
165
|
return header ? { ...request, headers: { "cache-control": header } } : request;
|
|
56
166
|
}
|
|
57
167
|
function territory(client) {
|
|
@@ -73,7 +183,7 @@ function territory(client) {
|
|
|
73
183
|
function createGeoClient(options) {
|
|
74
184
|
const client = createClient({
|
|
75
185
|
baseUrl: apiBaseUrl(options.baseUrl),
|
|
76
|
-
fetch: options
|
|
186
|
+
fetch: transport(options),
|
|
77
187
|
headers: options.apiKey ? { "x-api-key": options.apiKey } : void 0
|
|
78
188
|
});
|
|
79
189
|
client.use({
|
|
@@ -90,6 +200,11 @@ function createGeoClient(options) {
|
|
|
90
200
|
territory: territory(client)
|
|
91
201
|
};
|
|
92
202
|
}
|
|
203
|
+
function transport(options) {
|
|
204
|
+
const send = options.fetch ?? ((request) => globalThis.fetch(request));
|
|
205
|
+
const timed = timedFetch(send, options.timeoutMs ?? DEFAULT_TIMEOUT_MS);
|
|
206
|
+
return options.pacing === false ? timed : pacedFetch(timed, options.pacing);
|
|
207
|
+
}
|
|
93
208
|
export {
|
|
94
209
|
ApiError,
|
|
95
210
|
createGeoClient
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@seta-labs/geo",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.0",
|
|
4
4
|
"description": "Typed Fetch client for the Seta Geo HTTP API",
|
|
5
5
|
"license": "UNLICENSED",
|
|
6
6
|
"type": "module",
|
|
@@ -44,6 +44,7 @@
|
|
|
44
44
|
"devDependencies": {
|
|
45
45
|
"@seta-geo/types": "workspace:*",
|
|
46
46
|
"@seta-labs/config": "^0.16.1",
|
|
47
|
+
"@seta-labs/nestjs-cache-control": "0.2.1",
|
|
47
48
|
"tsup": "^9.0.0",
|
|
48
49
|
"typescript": "^6.0.3",
|
|
49
50
|
"vitest": "^5.0.3"
|