@seta-labs/geo 0.0.0-stage → 0.1.0-next.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,3 +1,79 @@
1
- # Temporary Holding Version
1
+ # @seta-labs/geo
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Typed ESM client for Seta Geo's HTTP API. Requires Node.js 22+ or a browser with
4
+ standard Fetch, Request, Response and AbortController support. The server's
5
+ Node.js 26 development requirement does not apply to SDK consumers.
6
+
7
+ ```ts
8
+ import { ApiError, createGeoClient } from '@seta-labs/geo';
9
+
10
+ const geo = createGeoClient({
11
+ baseUrl: 'https://geo.example.com/api',
12
+ apiKey: serverConfig.geoApiKey,
13
+ });
14
+
15
+ try {
16
+ const result = await geo.geocode(
17
+ { query: 'Via Roma 1, Torino', include: 'full' },
18
+ { signal: AbortSignal.timeout(20_000) },
19
+ );
20
+ // result is GeocodeResult, inferred from the generated HTTP contract.
21
+ } catch (error) {
22
+ if (error instanceof ApiError) {
23
+ // error.status, error.body (unknown), error.headers and error.requestId.
24
+ // Retry-After and any retryAfterMs in the body remain available.
25
+ }
26
+ throw error;
27
+ }
28
+ ```
29
+
30
+ `baseUrl` is the complete API root, including `/api` when the deployment uses
31
+ that prefix. The SDK never appends `/api`. Browser-relative roots such as `/api`
32
+ resolve against the current page. SSR requires an absolute API root and a new
33
+ client for each request when credentials are request-scoped.
34
+
35
+ Use API keys only on a trusted backend. A browser may omit `apiKey` when calling
36
+ its own backend, which supplies the key to Geo. The bundled Geo panel follows
37
+ this arrangement through its existing Nitro pass-through. It adapts Nitro's
38
+ request-scoped Fetch to real `Response` objects and preserves cookies locally.
39
+ Do not use `$fetch` as `fetch`: it returns the parsed body instead of a Response.
40
+
41
+ All calls return `Promise<T>` and accept optional `{ signal }` as the last
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.
46
+
47
+ | Method | Input |
48
+ | ---------------------------------------- | -------------------------------------- |
49
+ | `geocode` | `GeocodeRequest` |
50
+ | `geocodeBatch` | `GeocodeBatchRequest` |
51
+ | `reverseGeocode` | `ReverseGeocodeRequest` |
52
+ | `territory.status`, `territory.datasets` | options only |
53
+ | `territory.comuni` | optional `TerritoryComuniQuery` |
54
+ | `territory.quotazioni` | optional `TerritoryQuotazioniQuery` |
55
+ | `territory.compravendite` | optional `TerritoryCompravenditeQuery` |
56
+ | `territory.comune` | municipality code |
57
+ | `territory.resolve` | `TerritoryResolveRequest` |
58
+
59
+ The package exports `createGeoClient`, `ApiError`, client/options interfaces,
60
+ query types and the generated geocode/territory request and response aliases.
61
+ The three service health routes are intentionally outside this SDK surface.
62
+ OpenAPI remains authoritative: TypeScript provides static types, not runtime
63
+ validation of received data. `include` may be omitted and defaults to `best`
64
+ on the service.
65
+
66
+ HTTP errors are `ApiError`; their body is JSON when parseable, otherwise text,
67
+ or `undefined` for an empty response. Network failures, aborts and JSON parsing
68
+ errors retain their native types. Every Geo success operation requires a JSON
69
+ result: an unexpected empty/204 success raises `SyntaxError`. The SDK logs no
70
+ body, URL or credential automatically.
71
+
72
+ Versioning is independent from the service. The initial `0.1.0-next.0` candidate
73
+ uses the HTTP contract of Geo service `0.4.10`. Declarations are bundled from the
74
+ repository's generated types; installing the SDK needs no private workspace
75
+ package. Distribution is public npm; the retained license is `UNLICENSED`.
76
+
77
+ The SDK remains on `0.x` until an explicit decision to graduate to `1.0`.
78
+ Prerelease candidates use the `next` channel; completing prerelease testing
79
+ does not itself authorize a `1.0` version.
@@ -0,0 +1,1002 @@
1
+ /** HTTP failures only. Network, abort and JSON parsing errors keep their native types. */
2
+ declare class ApiError extends Error {
3
+ readonly body: unknown;
4
+ readonly status: number;
5
+ readonly headers: Headers;
6
+ readonly requestId: string | undefined;
7
+ constructor(response: Response, body: unknown);
8
+ }
9
+
10
+ /**
11
+ * This file was auto-generated by openapi-typescript.
12
+ * Do not make direct changes to the file.
13
+ */
14
+ interface paths {
15
+ '/health': {
16
+ parameters: {
17
+ query?: never;
18
+ header?: never;
19
+ path?: never;
20
+ cookie?: never;
21
+ };
22
+ /** Liveness: the process answers; no dependency is checked */
23
+ get: operations['HealthController_live'];
24
+ put?: never;
25
+ post?: never;
26
+ delete?: never;
27
+ options?: never;
28
+ head?: never;
29
+ patch?: never;
30
+ trace?: never;
31
+ };
32
+ '/health/ready': {
33
+ parameters: {
34
+ query?: never;
35
+ header?: never;
36
+ path?: never;
37
+ cookie?: never;
38
+ };
39
+ /** Readiness: memory pressure and critical dependencies */
40
+ get: operations['HealthController_ready'];
41
+ put?: never;
42
+ post?: never;
43
+ delete?: never;
44
+ options?: never;
45
+ head?: never;
46
+ patch?: never;
47
+ trace?: never;
48
+ };
49
+ '/health/status': {
50
+ parameters: {
51
+ query?: never;
52
+ header?: never;
53
+ path?: never;
54
+ cookie?: never;
55
+ };
56
+ /** Diagnostics: memory, event loop delay, every check with its error */
57
+ get: operations['HealthController_status'];
58
+ put?: never;
59
+ post?: never;
60
+ delete?: never;
61
+ options?: never;
62
+ head?: never;
63
+ patch?: never;
64
+ trace?: never;
65
+ };
66
+ '/geocode': {
67
+ parameters: {
68
+ query?: never;
69
+ header?: never;
70
+ path?: never;
71
+ cookie?: never;
72
+ };
73
+ get?: never;
74
+ put?: never;
75
+ post: operations['GeocodeController_geocode'];
76
+ delete?: never;
77
+ options?: never;
78
+ head?: never;
79
+ patch?: never;
80
+ trace?: never;
81
+ };
82
+ '/geocode/batch': {
83
+ parameters: {
84
+ query?: never;
85
+ header?: never;
86
+ path?: never;
87
+ cookie?: never;
88
+ };
89
+ get?: never;
90
+ put?: never;
91
+ post: operations['GeocodeController_geocodeBatch'];
92
+ delete?: never;
93
+ options?: never;
94
+ head?: never;
95
+ patch?: never;
96
+ trace?: never;
97
+ };
98
+ '/geocode/reverse': {
99
+ parameters: {
100
+ query?: never;
101
+ header?: never;
102
+ path?: never;
103
+ cookie?: never;
104
+ };
105
+ get?: never;
106
+ put?: never;
107
+ post: operations['GeocodeController_reverseGeocode'];
108
+ delete?: never;
109
+ options?: never;
110
+ head?: never;
111
+ patch?: never;
112
+ trace?: never;
113
+ };
114
+ '/territory/status': {
115
+ parameters: {
116
+ query?: never;
117
+ header?: never;
118
+ path?: never;
119
+ cookie?: never;
120
+ };
121
+ get: operations['TerritoryController_status'];
122
+ put?: never;
123
+ post?: never;
124
+ delete?: never;
125
+ options?: never;
126
+ head?: never;
127
+ patch?: never;
128
+ trace?: never;
129
+ };
130
+ '/territory/datasets': {
131
+ parameters: {
132
+ query?: never;
133
+ header?: never;
134
+ path?: never;
135
+ cookie?: never;
136
+ };
137
+ get: operations['TerritoryController_datasets'];
138
+ put?: never;
139
+ post?: never;
140
+ delete?: never;
141
+ options?: never;
142
+ head?: never;
143
+ patch?: never;
144
+ trace?: never;
145
+ };
146
+ '/territory/comuni': {
147
+ parameters: {
148
+ query?: never;
149
+ header?: never;
150
+ path?: never;
151
+ cookie?: never;
152
+ };
153
+ get: operations['TerritoryController_listComuni'];
154
+ put?: never;
155
+ post?: never;
156
+ delete?: never;
157
+ options?: never;
158
+ head?: never;
159
+ patch?: never;
160
+ trace?: never;
161
+ };
162
+ '/territory/quotazioni': {
163
+ parameters: {
164
+ query?: never;
165
+ header?: never;
166
+ path?: never;
167
+ cookie?: never;
168
+ };
169
+ get: operations['TerritoryController_listQuotazioni'];
170
+ put?: never;
171
+ post?: never;
172
+ delete?: never;
173
+ options?: never;
174
+ head?: never;
175
+ patch?: never;
176
+ trace?: never;
177
+ };
178
+ '/territory/compravendite': {
179
+ parameters: {
180
+ query?: never;
181
+ header?: never;
182
+ path?: never;
183
+ cookie?: never;
184
+ };
185
+ get: operations['TerritoryController_listCompravendite'];
186
+ put?: never;
187
+ post?: never;
188
+ delete?: never;
189
+ options?: never;
190
+ head?: never;
191
+ patch?: never;
192
+ trace?: never;
193
+ };
194
+ '/territory/comuni/{codComune}': {
195
+ parameters: {
196
+ query?: never;
197
+ header?: never;
198
+ path?: never;
199
+ cookie?: never;
200
+ };
201
+ get: operations['TerritoryController_comune'];
202
+ put?: never;
203
+ post?: never;
204
+ delete?: never;
205
+ options?: never;
206
+ head?: never;
207
+ patch?: never;
208
+ trace?: never;
209
+ };
210
+ '/territory/resolve': {
211
+ parameters: {
212
+ query?: never;
213
+ header?: never;
214
+ path?: never;
215
+ cookie?: never;
216
+ };
217
+ get?: never;
218
+ put?: never;
219
+ post: operations['TerritoryController_resolve'];
220
+ delete?: never;
221
+ options?: never;
222
+ head?: never;
223
+ patch?: never;
224
+ trace?: never;
225
+ };
226
+ }
227
+ interface components {
228
+ schemas: {
229
+ /** @enum {string} */
230
+ HealthStatus: 'ok' | 'degraded' | 'down';
231
+ HealthCheckResultDto: {
232
+ /** @example database */
233
+ name: string;
234
+ /**
235
+ * @description A check is `ok` or `down`.
236
+ * @example ok
237
+ */
238
+ status: components['schemas']['HealthStatus'];
239
+ /** @description A failing critical check fails readiness. */
240
+ critical: boolean;
241
+ /** @example 3.2 */
242
+ durationMs: number;
243
+ /**
244
+ * @description Failure reason, only in `/health/status`: probes are public.
245
+ * @example timed out after 750 ms
246
+ */
247
+ error?: string;
248
+ };
249
+ HealthCheckResponseDto: {
250
+ /** @example ok */
251
+ status: components['schemas']['HealthStatus'];
252
+ /** @example api */
253
+ service: string;
254
+ /**
255
+ * Format: date-time
256
+ * @example 2026-07-03T10:00:00.000Z
257
+ */
258
+ timestamp: string;
259
+ /** @description Readiness checks; absent on liveness. */
260
+ checks?: components['schemas']['HealthCheckResultDto'][];
261
+ };
262
+ MemoryUsageDto: {
263
+ /** @example 182.4 */
264
+ rssMiB: number;
265
+ /** @example 96.1 */
266
+ heapUsedMiB: number;
267
+ /** @example 120.5 */
268
+ heapTotalMiB: number;
269
+ /** @example 4.2 */
270
+ externalMiB: number;
271
+ /** @example 1.3 */
272
+ arrayBuffersMiB: number;
273
+ /**
274
+ * @description V8 `heap_size_limit`.
275
+ * @example 259
276
+ */
277
+ heapLimitMiB: number;
278
+ /**
279
+ * @description cgroup memory limit, `null` when unconstrained.
280
+ * @example 512
281
+ */
282
+ containerLimitMiB: number | null;
283
+ /**
284
+ * @description `heapUsed / heapLimit`.
285
+ * @example 0.371
286
+ */
287
+ heapRatio: number;
288
+ /**
289
+ * @description `rss / containerLimit`, `null` when unconstrained.
290
+ * @example 0.356
291
+ */
292
+ rssRatio: number | null;
293
+ };
294
+ EventLoopDelayDto: {
295
+ /** @example 10.1 */
296
+ p50Ms: number;
297
+ /** @example 21.4 */
298
+ p99Ms: number;
299
+ /** @example 48.7 */
300
+ maxMs: number;
301
+ /**
302
+ * @description Seconds covered: since the previous status report, or since startup.
303
+ * @example 60
304
+ */
305
+ windowSec: number;
306
+ };
307
+ HealthReportResponseDto: {
308
+ /** @example ok */
309
+ status: components['schemas']['HealthStatus'];
310
+ /** @example api */
311
+ service: string;
312
+ /** @example v0.4.2 */
313
+ version?: string;
314
+ /** @example v22.23.2 */
315
+ node: string;
316
+ /** @example 3600 */
317
+ uptimeSec: number;
318
+ /**
319
+ * Format: date-time
320
+ * @example 2026-07-03T10:00:00.000Z
321
+ */
322
+ timestamp: string;
323
+ memory: components['schemas']['MemoryUsageDto'];
324
+ eventLoop: components['schemas']['EventLoopDelayDto'];
325
+ checks: components['schemas']['HealthCheckResultDto'][];
326
+ };
327
+ GeocodeRequestDto: {
328
+ /** @description Free-text query, used verbatim when present. */
329
+ query?: string;
330
+ indirizzo?: string;
331
+ cap?: string;
332
+ citta?: string;
333
+ provincia?: string;
334
+ /**
335
+ * @description 'full' adds `results`: the provider's untouched result list, for callers that pick themselves.
336
+ * @default best
337
+ * @enum {string}
338
+ */
339
+ include?: 'best' | 'full';
340
+ };
341
+ GeocodeResultDto: {
342
+ /** @enum {string} */
343
+ status: 'OK' | 'NO_RESULT' | 'INVALID_QUERY';
344
+ /** @description The normalized query actually used as cache key and provider input. */
345
+ query: string | null;
346
+ lat: number | null;
347
+ lng: number | null;
348
+ /** @description True when the answer came from the cache, no provider call. */
349
+ cached: boolean;
350
+ /** @description True when the coordinates fall inside the loose Italy bounding box. */
351
+ withinItaly: boolean;
352
+ /** @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. */
353
+ results?: {
354
+ [key: string]: unknown;
355
+ }[] | null;
356
+ };
357
+ GeocodeBatchRequestDto: {
358
+ items: components['schemas']['GeocodeRequestDto'][];
359
+ /**
360
+ * @description Applied to every item; a per-item include overrides it.
361
+ * @default best
362
+ * @enum {string}
363
+ */
364
+ include?: 'best' | 'full';
365
+ };
366
+ GeocodeBatchResponseDto: {
367
+ /** @description Same order as the request items. */
368
+ items: components['schemas']['GeocodeResultDto'][];
369
+ };
370
+ ReverseGeocodeRequestDto: {
371
+ lat: number;
372
+ lng: number;
373
+ /**
374
+ * @default best
375
+ * @enum {string}
376
+ */
377
+ include?: 'best' | 'full';
378
+ };
379
+ ReverseGeocodeResultDto: {
380
+ /** @enum {string} */
381
+ status: 'OK' | 'NO_RESULT';
382
+ /** @description Canonical cache key: "lat,lng" rounded to 5 decimals (~1 m). */
383
+ query: string;
384
+ /** @description Formatted address of the best-tier result. */
385
+ address: string | null;
386
+ /** @description True when the answer came from the cache, no provider call. */
387
+ cached: boolean;
388
+ /** @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. */
389
+ results?: {
390
+ [key: string]: unknown;
391
+ }[] | null;
392
+ };
393
+ TerritoryStatusDto: {
394
+ /** @description Latest OMI publication loaded (`YYYY-S`), null while no supply is loaded. */
395
+ semestre: string | null;
396
+ };
397
+ TerritoryDatasetsDto: {
398
+ comuni: number;
399
+ zone: number;
400
+ quotazioni: number;
401
+ compravendite: number;
402
+ /** @description Newest first. */
403
+ semestri: string[];
404
+ tipologie: string[];
405
+ stati: string[];
406
+ /** @description Newest first. */
407
+ anni: number[];
408
+ province: string[];
409
+ regioni: string[];
410
+ };
411
+ TerritoryComuneRigaDto: {
412
+ codComune: string;
413
+ codIstat: string;
414
+ denominazione: string;
415
+ provincia: string;
416
+ regione: string;
417
+ popolazione: number | null;
418
+ redditoMedio: number | null;
419
+ };
420
+ TerritoryComuniPageDto: {
421
+ page: number;
422
+ pageSize: number;
423
+ total: number;
424
+ items: components['schemas']['TerritoryComuneRigaDto'][];
425
+ };
426
+ TerritoryQuotazioneDto: {
427
+ semestre: string;
428
+ codComune: string;
429
+ comune: string | null;
430
+ provincia: string | null;
431
+ codZona: string;
432
+ zona: string | null;
433
+ tipologia: string;
434
+ stato: string;
435
+ comprMin: number | null;
436
+ comprMax: number | null;
437
+ locMin: number | null;
438
+ locMax: number | null;
439
+ };
440
+ TerritoryQuotazioniPageDto: {
441
+ page: number;
442
+ pageSize: number;
443
+ total: number;
444
+ /** @description Publication listed, null while none is loaded. */
445
+ semestre: string | null;
446
+ items: components['schemas']['TerritoryQuotazioneDto'][];
447
+ };
448
+ TerritoryCompravenditaDto: {
449
+ codComune: string;
450
+ comune: string | null;
451
+ provincia: string | null;
452
+ anno: number;
453
+ segmento: string;
454
+ ntn: number;
455
+ };
456
+ TerritoryCompravenditePageDto: {
457
+ page: number;
458
+ pageSize: number;
459
+ total: number;
460
+ items: components['schemas']['TerritoryCompravenditaDto'][];
461
+ };
462
+ TerritoryComuneDto: {
463
+ codComune: string;
464
+ codIstat: string;
465
+ denominazione: string;
466
+ provincia: string;
467
+ regione: string;
468
+ };
469
+ TerritoryIstatDto: {
470
+ denominazione: string;
471
+ provincia: string;
472
+ popolazione: number | null;
473
+ popolazioneAnno: number | null;
474
+ redditoMedio: number | null;
475
+ redditoAnno: number | null;
476
+ };
477
+ TerritoryOmiDto: {
478
+ semestre: string;
479
+ tipologia: string;
480
+ stato: string;
481
+ comprMin: number | null;
482
+ comprMax: number | null;
483
+ locMin: number | null;
484
+ locMax: number | null;
485
+ };
486
+ TerritoryZonaQuotazioneDto: {
487
+ codZona: string;
488
+ nome: string | null;
489
+ omi: components['schemas']['TerritoryOmiDto'] | null;
490
+ };
491
+ TerritoryCompravenditaAnnoDto: {
492
+ anno: number;
493
+ segmento: string;
494
+ ntn: number;
495
+ };
496
+ TerritoryComuneDetailDto: {
497
+ comune: components['schemas']['TerritoryComuneDto'];
498
+ istat: components['schemas']['TerritoryIstatDto'];
499
+ /** @description Publication the zone quotations come from, null while none is loaded. */
500
+ semestre: string | null;
501
+ zone: components['schemas']['TerritoryZonaQuotazioneDto'][];
502
+ compravendite: components['schemas']['TerritoryCompravenditaAnnoDto'][];
503
+ };
504
+ TerritoryPointDto: {
505
+ id: string;
506
+ lat: number;
507
+ lng: number;
508
+ };
509
+ TerritoryResolveRequestDto: {
510
+ /** @description OMI publication to quote from (`YYYY-S`). Callers pin it once per run — from `GET /territory/status` — so a publication switch mid-run cannot shear the batch. */
511
+ semestre: string;
512
+ points: components['schemas']['TerritoryPointDto'][];
513
+ };
514
+ TerritoryZonaDto: {
515
+ codComune: string;
516
+ codZona: string;
517
+ nome: string | null;
518
+ };
519
+ TerritoryCompravenditeDto: {
520
+ anno: number;
521
+ ntn: number;
522
+ };
523
+ TerritoryPointResultDto: {
524
+ id: string;
525
+ zona: components['schemas']['TerritoryZonaDto'] | null;
526
+ omi: components['schemas']['TerritoryOmiDto'] | null;
527
+ istat: components['schemas']['TerritoryIstatDto'] | null;
528
+ compravendite: components['schemas']['TerritoryCompravenditeDto'] | null;
529
+ };
530
+ TerritoryResolveResponseDto: {
531
+ items: components['schemas']['TerritoryPointResultDto'][];
532
+ };
533
+ };
534
+ responses: never;
535
+ parameters: never;
536
+ requestBodies: never;
537
+ headers: never;
538
+ pathItems: never;
539
+ }
540
+ interface operations {
541
+ HealthController_live: {
542
+ parameters: {
543
+ query?: never;
544
+ header?: never;
545
+ path?: never;
546
+ cookie?: never;
547
+ };
548
+ requestBody?: never;
549
+ responses: {
550
+ 200: {
551
+ headers: {
552
+ [name: string]: unknown;
553
+ };
554
+ content: {
555
+ 'application/json': components['schemas']['HealthCheckResponseDto'];
556
+ };
557
+ };
558
+ };
559
+ };
560
+ HealthController_ready: {
561
+ parameters: {
562
+ query?: never;
563
+ header?: never;
564
+ path?: never;
565
+ cookie?: never;
566
+ };
567
+ requestBody?: never;
568
+ responses: {
569
+ 200: {
570
+ headers: {
571
+ [name: string]: unknown;
572
+ };
573
+ content: {
574
+ 'application/json': components['schemas']['HealthCheckResponseDto'];
575
+ };
576
+ };
577
+ 503: {
578
+ headers: {
579
+ [name: string]: unknown;
580
+ };
581
+ content: {
582
+ 'application/json': components['schemas']['HealthCheckResponseDto'];
583
+ };
584
+ };
585
+ };
586
+ };
587
+ HealthController_status: {
588
+ parameters: {
589
+ query?: never;
590
+ header?: never;
591
+ path?: never;
592
+ cookie?: never;
593
+ };
594
+ requestBody?: never;
595
+ responses: {
596
+ 200: {
597
+ headers: {
598
+ [name: string]: unknown;
599
+ };
600
+ content: {
601
+ 'application/json': components['schemas']['HealthReportResponseDto'];
602
+ };
603
+ };
604
+ };
605
+ };
606
+ GeocodeController_geocode: {
607
+ parameters: {
608
+ query?: never;
609
+ header?: never;
610
+ path?: never;
611
+ cookie?: never;
612
+ };
613
+ requestBody: {
614
+ content: {
615
+ 'application/json': components['schemas']['GeocodeRequestDto'];
616
+ };
617
+ };
618
+ responses: {
619
+ 200: {
620
+ headers: {
621
+ [name: string]: unknown;
622
+ };
623
+ content: {
624
+ 'application/json': components['schemas']['GeocodeResultDto'];
625
+ };
626
+ };
627
+ /** @description Missing or invalid API key. */
628
+ 401: {
629
+ headers: {
630
+ [name: string]: unknown;
631
+ };
632
+ content?: never;
633
+ };
634
+ /** @description Provider not configured, cache miss unservable. */
635
+ 503: {
636
+ headers: {
637
+ [name: string]: unknown;
638
+ };
639
+ content?: never;
640
+ };
641
+ };
642
+ };
643
+ GeocodeController_geocodeBatch: {
644
+ parameters: {
645
+ query?: never;
646
+ header?: never;
647
+ path?: never;
648
+ cookie?: never;
649
+ };
650
+ requestBody: {
651
+ content: {
652
+ 'application/json': components['schemas']['GeocodeBatchRequestDto'];
653
+ };
654
+ };
655
+ responses: {
656
+ 200: {
657
+ headers: {
658
+ [name: string]: unknown;
659
+ };
660
+ content: {
661
+ 'application/json': components['schemas']['GeocodeBatchResponseDto'];
662
+ };
663
+ };
664
+ /** @description Missing or invalid API key. */
665
+ 401: {
666
+ headers: {
667
+ [name: string]: unknown;
668
+ };
669
+ content?: never;
670
+ };
671
+ /** @description Provider not configured, cache miss unservable. */
672
+ 503: {
673
+ headers: {
674
+ [name: string]: unknown;
675
+ };
676
+ content?: never;
677
+ };
678
+ };
679
+ };
680
+ GeocodeController_reverseGeocode: {
681
+ parameters: {
682
+ query?: never;
683
+ header?: never;
684
+ path?: never;
685
+ cookie?: never;
686
+ };
687
+ requestBody: {
688
+ content: {
689
+ 'application/json': components['schemas']['ReverseGeocodeRequestDto'];
690
+ };
691
+ };
692
+ responses: {
693
+ 200: {
694
+ headers: {
695
+ [name: string]: unknown;
696
+ };
697
+ content: {
698
+ 'application/json': components['schemas']['ReverseGeocodeResultDto'];
699
+ };
700
+ };
701
+ /** @description Missing or invalid API key. */
702
+ 401: {
703
+ headers: {
704
+ [name: string]: unknown;
705
+ };
706
+ content?: never;
707
+ };
708
+ /** @description Provider not configured, cache miss unservable. */
709
+ 503: {
710
+ headers: {
711
+ [name: string]: unknown;
712
+ };
713
+ content?: never;
714
+ };
715
+ };
716
+ };
717
+ TerritoryController_status: {
718
+ parameters: {
719
+ query?: never;
720
+ header?: never;
721
+ path?: never;
722
+ cookie?: never;
723
+ };
724
+ requestBody?: never;
725
+ responses: {
726
+ 200: {
727
+ headers: {
728
+ [name: string]: unknown;
729
+ };
730
+ content: {
731
+ 'application/json': components['schemas']['TerritoryStatusDto'];
732
+ };
733
+ };
734
+ /** @description Missing or invalid API key. */
735
+ 401: {
736
+ headers: {
737
+ [name: string]: unknown;
738
+ };
739
+ content?: never;
740
+ };
741
+ };
742
+ };
743
+ TerritoryController_datasets: {
744
+ parameters: {
745
+ query?: never;
746
+ header?: never;
747
+ path?: never;
748
+ cookie?: never;
749
+ };
750
+ requestBody?: never;
751
+ responses: {
752
+ 200: {
753
+ headers: {
754
+ [name: string]: unknown;
755
+ };
756
+ content: {
757
+ 'application/json': components['schemas']['TerritoryDatasetsDto'];
758
+ };
759
+ };
760
+ /** @description Missing or invalid API key. */
761
+ 401: {
762
+ headers: {
763
+ [name: string]: unknown;
764
+ };
765
+ content?: never;
766
+ };
767
+ };
768
+ };
769
+ TerritoryController_listComuni: {
770
+ parameters: {
771
+ query?: {
772
+ page?: number;
773
+ pageSize?: number;
774
+ /** @description Part of the municipality name, case-insensitive. */
775
+ q?: string;
776
+ /** @description Province code, e.g. `GE`. */
777
+ provincia?: string;
778
+ regione?: string;
779
+ ordina?: 'denominazione' | 'popolazione';
780
+ };
781
+ header?: never;
782
+ path?: never;
783
+ cookie?: never;
784
+ };
785
+ requestBody?: never;
786
+ responses: {
787
+ 200: {
788
+ headers: {
789
+ [name: string]: unknown;
790
+ };
791
+ content: {
792
+ 'application/json': components['schemas']['TerritoryComuniPageDto'];
793
+ };
794
+ };
795
+ /** @description Missing or invalid API key. */
796
+ 401: {
797
+ headers: {
798
+ [name: string]: unknown;
799
+ };
800
+ content?: never;
801
+ };
802
+ };
803
+ };
804
+ TerritoryController_listQuotazioni: {
805
+ parameters: {
806
+ query?: {
807
+ page?: number;
808
+ pageSize?: number;
809
+ /** @description Publication `YYYY-S`; defaults to the latest loaded. */
810
+ semestre?: string;
811
+ /** @description Belfiore code of the municipality. */
812
+ codComune?: string;
813
+ /** @description Province code, e.g. `GE`. */
814
+ provincia?: string;
815
+ /** @description Property type as published, e.g. `Abitazioni civili`. */
816
+ tipologia?: string;
817
+ /** @description Condition as published, e.g. `NORMALE`. */
818
+ stato?: string;
819
+ };
820
+ header?: never;
821
+ path?: never;
822
+ cookie?: never;
823
+ };
824
+ requestBody?: never;
825
+ responses: {
826
+ 200: {
827
+ headers: {
828
+ [name: string]: unknown;
829
+ };
830
+ content: {
831
+ 'application/json': components['schemas']['TerritoryQuotazioniPageDto'];
832
+ };
833
+ };
834
+ /** @description Missing or invalid API key. */
835
+ 401: {
836
+ headers: {
837
+ [name: string]: unknown;
838
+ };
839
+ content?: never;
840
+ };
841
+ };
842
+ };
843
+ TerritoryController_listCompravendite: {
844
+ parameters: {
845
+ query?: {
846
+ page?: number;
847
+ pageSize?: number;
848
+ anno?: number;
849
+ /** @description Belfiore code of the municipality. */
850
+ codComune?: string;
851
+ /** @description Province code, e.g. `GE`. */
852
+ provincia?: string;
853
+ segmento?: 'RESIDENZIALE' | 'RESIDENZIALE_FINO_50MQ' | 'RESIDENZIALE_50_85MQ' | 'RESIDENZIALE_85_115MQ' | 'RESIDENZIALE_115_145MQ' | 'RESIDENZIALE_OLTRE_145MQ';
854
+ };
855
+ header?: never;
856
+ path?: never;
857
+ cookie?: never;
858
+ };
859
+ requestBody?: never;
860
+ responses: {
861
+ 200: {
862
+ headers: {
863
+ [name: string]: unknown;
864
+ };
865
+ content: {
866
+ 'application/json': components['schemas']['TerritoryCompravenditePageDto'];
867
+ };
868
+ };
869
+ /** @description Missing or invalid API key. */
870
+ 401: {
871
+ headers: {
872
+ [name: string]: unknown;
873
+ };
874
+ content?: never;
875
+ };
876
+ };
877
+ };
878
+ TerritoryController_comune: {
879
+ parameters: {
880
+ query?: never;
881
+ header?: never;
882
+ path: {
883
+ /** @description Belfiore code of the municipality (e.g. `D969`). */
884
+ codComune: string;
885
+ };
886
+ cookie?: never;
887
+ };
888
+ requestBody?: never;
889
+ responses: {
890
+ 200: {
891
+ headers: {
892
+ [name: string]: unknown;
893
+ };
894
+ content: {
895
+ 'application/json': components['schemas']['TerritoryComuneDetailDto'];
896
+ };
897
+ };
898
+ /** @description Missing or invalid API key. */
899
+ 401: {
900
+ headers: {
901
+ [name: string]: unknown;
902
+ };
903
+ content?: never;
904
+ };
905
+ /** @description The municipality is not in the registry. */
906
+ 404: {
907
+ headers: {
908
+ [name: string]: unknown;
909
+ };
910
+ content?: never;
911
+ };
912
+ };
913
+ };
914
+ TerritoryController_resolve: {
915
+ parameters: {
916
+ query?: never;
917
+ header?: never;
918
+ path?: never;
919
+ cookie?: never;
920
+ };
921
+ requestBody: {
922
+ content: {
923
+ 'application/json': components['schemas']['TerritoryResolveRequestDto'];
924
+ };
925
+ };
926
+ responses: {
927
+ 200: {
928
+ headers: {
929
+ [name: string]: unknown;
930
+ };
931
+ content: {
932
+ 'application/json': components['schemas']['TerritoryResolveResponseDto'];
933
+ };
934
+ };
935
+ /** @description Missing or invalid API key. */
936
+ 401: {
937
+ headers: {
938
+ [name: string]: unknown;
939
+ };
940
+ content?: never;
941
+ };
942
+ };
943
+ };
944
+ }
945
+
946
+ type ApiPaths = paths;
947
+ type ApiSchemas = components['schemas'];
948
+ type GeocodeRequest = ApiSchemas['GeocodeRequestDto'];
949
+ type GeocodeBatchRequest = ApiSchemas['GeocodeBatchRequestDto'];
950
+ type GeocodeResult = ApiSchemas['GeocodeResultDto'];
951
+ type GeocodeBatchResponse = ApiSchemas['GeocodeBatchResponseDto'];
952
+ type ReverseGeocodeRequest = ApiSchemas['ReverseGeocodeRequestDto'];
953
+ type ReverseGeocodeResult = ApiSchemas['ReverseGeocodeResultDto'];
954
+ type TerritoryStatus = ApiSchemas['TerritoryStatusDto'];
955
+ type TerritoryResolveRequest = ApiSchemas['TerritoryResolveRequestDto'];
956
+ type TerritoryResolveResponse = ApiSchemas['TerritoryResolveResponseDto'];
957
+ type TerritoryPointResult = ApiSchemas['TerritoryPointResultDto'];
958
+ type TerritoryOmi = ApiSchemas['TerritoryOmiDto'];
959
+ type TerritoryComune = ApiSchemas['TerritoryComuneDto'];
960
+ type TerritoryComuneDetail = ApiSchemas['TerritoryComuneDetailDto'];
961
+ type TerritoryDatasets = ApiSchemas['TerritoryDatasetsDto'];
962
+ type TerritoryComuniPage = ApiSchemas['TerritoryComuniPageDto'];
963
+ type TerritoryComuneRiga = ApiSchemas['TerritoryComuneRigaDto'];
964
+ type TerritoryQuotazioniPage = ApiSchemas['TerritoryQuotazioniPageDto'];
965
+ type TerritoryQuotazione = ApiSchemas['TerritoryQuotazioneDto'];
966
+ type TerritoryCompravenditePage = ApiSchemas['TerritoryCompravenditePageDto'];
967
+ type TerritoryCompravendita = ApiSchemas['TerritoryCompravenditaDto'];
968
+
969
+ type TerritoryComuniQuery = NonNullable<ApiPaths['/territory/comuni']['get']['parameters']['query']>;
970
+ type TerritoryQuotazioniQuery = NonNullable<ApiPaths['/territory/quotazioni']['get']['parameters']['query']>;
971
+ type TerritoryCompravenditeQuery = NonNullable<ApiPaths['/territory/compravendite']['get']['parameters']['query']>;
972
+ interface RequestOptions {
973
+ signal?: AbortSignal;
974
+ }
975
+ interface GeoClientOptions {
976
+ /** Complete API root, including /api. Relative roots are supported in browsers. */
977
+ baseUrl: string;
978
+ /** Omit only when a trusted same-origin backend supplies the API key. */
979
+ apiKey?: string;
980
+ /** A real Fetch implementation returning a Response, not a parsed-body $fetch. */
981
+ fetch?: (request: Request) => Promise<Response>;
982
+ }
983
+ interface TerritoryClient {
984
+ status(options?: RequestOptions): Promise<TerritoryStatus>;
985
+ datasets(options?: RequestOptions): Promise<TerritoryDatasets>;
986
+ comuni(query?: TerritoryComuniQuery, options?: RequestOptions): Promise<TerritoryComuniPage>;
987
+ quotazioni(query?: TerritoryQuotazioniQuery, options?: RequestOptions): Promise<TerritoryQuotazioniPage>;
988
+ compravendite(query?: TerritoryCompravenditeQuery, options?: RequestOptions): Promise<TerritoryCompravenditePage>;
989
+ comune(codComune: string, options?: RequestOptions): Promise<TerritoryComuneDetail>;
990
+ resolve(input: TerritoryResolveRequest, options?: RequestOptions): Promise<TerritoryResolveResponse>;
991
+ }
992
+ interface GeoClient {
993
+ geocode(input: GeocodeRequest, options?: RequestOptions): Promise<GeocodeResult>;
994
+ geocodeBatch(input: GeocodeBatchRequest, options?: RequestOptions): Promise<GeocodeBatchResponse>;
995
+ reverseGeocode(input: ReverseGeocodeRequest, options?: RequestOptions): Promise<ReverseGeocodeResult>;
996
+ territory: TerritoryClient;
997
+ }
998
+
999
+ declare function createGeoClient(options: GeoClientOptions): GeoClient;
1000
+
1001
+ export { ApiError, createGeoClient };
1002
+ export type { GeoClient, GeoClientOptions, GeocodeBatchRequest, GeocodeBatchResponse, GeocodeRequest, GeocodeResult, RequestOptions, ReverseGeocodeRequest, ReverseGeocodeResult, TerritoryClient, TerritoryCompravendita, TerritoryCompravenditePage, TerritoryCompravenditeQuery, TerritoryComune, TerritoryComuneDetail, TerritoryComuneRiga, TerritoryComuniPage, TerritoryComuniQuery, TerritoryDatasets, TerritoryOmi, TerritoryPointResult, TerritoryQuotazione, TerritoryQuotazioniPage, TerritoryQuotazioniQuery, TerritoryResolveRequest, TerritoryResolveResponse, TerritoryStatus };
package/dist/index.js ADDED
@@ -0,0 +1,79 @@
1
+ // src/api-error.ts
2
+ var ApiError = class extends Error {
3
+ constructor(response, body) {
4
+ super(`Geo API request failed with status ${response.status}`);
5
+ this.body = body;
6
+ this.name = "ApiError";
7
+ this.status = response.status;
8
+ this.headers = new Headers(response.headers);
9
+ this.requestId = response.headers.get("x-request-id") ?? void 0;
10
+ }
11
+ body;
12
+ status;
13
+ headers;
14
+ requestId;
15
+ };
16
+ async function readErrorBody(response) {
17
+ const text = await response.text();
18
+ if (!text) return void 0;
19
+ try {
20
+ return JSON.parse(text);
21
+ } catch {
22
+ return text;
23
+ }
24
+ }
25
+
26
+ // src/client.ts
27
+ import createClient from "openapi-fetch";
28
+ function apiBaseUrl(baseUrl) {
29
+ const url = new URL(baseUrl, globalThis.location?.href);
30
+ if (!["http:", "https:"].includes(url.protocol) || url.username || url.password || url.search || url.hash) {
31
+ throw new TypeError(
32
+ "baseUrl must be an HTTP(S) API root without credentials, query or fragment"
33
+ );
34
+ }
35
+ return url.href.replace(/\/+$/, "");
36
+ }
37
+ async function data(request) {
38
+ const result = await request;
39
+ if (result.data === void 0) throw new SyntaxError("Geo API returned an empty JSON response");
40
+ return result.data;
41
+ }
42
+ function territory(client) {
43
+ return {
44
+ status: (options) => data(client.GET("/territory/status", { ...options })),
45
+ datasets: (options) => data(client.GET("/territory/datasets", { ...options })),
46
+ comuni: (query, options) => data(client.GET("/territory/comuni", { ...options, params: { query } })),
47
+ quotazioni: (query, options) => data(client.GET("/territory/quotazioni", { ...options, params: { query } })),
48
+ compravendite: (query, options) => data(client.GET("/territory/compravendite", { ...options, params: { query } })),
49
+ comune: (codComune, options) => data(
50
+ client.GET("/territory/comuni/{codComune}", {
51
+ ...options,
52
+ params: { path: { codComune } }
53
+ })
54
+ ),
55
+ resolve: (input, options) => data(client.POST("/territory/resolve", { ...options, body: input }))
56
+ };
57
+ }
58
+ function createGeoClient(options) {
59
+ const client = createClient({
60
+ baseUrl: apiBaseUrl(options.baseUrl),
61
+ fetch: options.fetch,
62
+ headers: options.apiKey ? { "x-api-key": options.apiKey } : void 0
63
+ });
64
+ client.use({
65
+ async onResponse({ response }) {
66
+ if (!response.ok) throw new ApiError(response, await readErrorBody(response));
67
+ }
68
+ });
69
+ return {
70
+ geocode: (input, request) => data(client.POST("/geocode", { ...request, body: input })),
71
+ geocodeBatch: (input, request) => data(client.POST("/geocode/batch", { ...request, body: input })),
72
+ reverseGeocode: (input, request) => data(client.POST("/geocode/reverse", { ...request, body: input })),
73
+ territory: territory(client)
74
+ };
75
+ }
76
+ export {
77
+ ApiError,
78
+ createGeoClient
79
+ };
package/package.json CHANGED
@@ -1,6 +1,50 @@
1
1
  {
2
- "name": "@seta-labs/geo",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
2
+ "name": "@seta-labs/geo",
3
+ "version": "0.1.0-next.0",
4
+ "description": "Typed Fetch client for the Seta Geo HTTP API",
5
+ "license": "UNLICENSED",
6
+ "type": "module",
7
+ "sideEffects": false,
8
+ "main": "./dist/index.js",
9
+ "types": "./dist/index.d.ts",
10
+ "exports": {
11
+ ".": {
12
+ "types": "./dist/index.d.ts",
13
+ "import": "./dist/index.js"
14
+ }
15
+ },
16
+ "files": [
17
+ "dist",
18
+ "README.md"
19
+ ],
20
+ "engines": {
21
+ "node": ">=22"
22
+ },
23
+ "repository": {
24
+ "type": "git",
25
+ "url": "git+https://github.com/Seta-Labs/seta-geo.git",
26
+ "directory": "packages/sdk"
27
+ },
28
+ "publishConfig": {
29
+ "access": "public",
30
+ "tag": "next",
31
+ "provenance": false
32
+ },
33
+ "scripts": {
34
+ "build": "tsup",
35
+ "lint": "eslint src tsup.config.ts",
36
+ "lint:fix": "eslint --fix src tsup.config.ts",
37
+ "typecheck": "tsc --noEmit",
38
+ "test:unit": "vitest run src"
39
+ },
40
+ "dependencies": {
41
+ "openapi-fetch": "^0.17.0"
42
+ },
43
+ "devDependencies": {
44
+ "@seta-geo/types": "workspace:*",
45
+ "@seta-labs/config": "^0.16.1",
46
+ "tsup": "^9.0.0",
47
+ "typescript": "^6.0.3",
48
+ "vitest": "^5.0.3"
49
+ }
50
+ }