@seta-labs/geo 0.1.0 → 0.3.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,22 @@
1
1
  # @seta-labs/geo
2
2
 
3
+ ## 0.3.0
4
+
5
+ - `geocode`, `geocodeBatch`, `reverseGeocode` and `cadastreParcel` take
6
+ `cache: { refresh, noStore, maxAge, onlyIfCached }`, sent as
7
+ `Cache-Control` (`no-cache`, `no-store`, `max-age`, `only-if-cached`).
8
+ Without it nothing changes. New types `LookupCache` and `LookupOptions`.
9
+ - A lookup refused by `onlyIfCached` rejects with `ApiError` 504
10
+ (`code: 'NOT_CACHED'`); a batch item has `status: 'NOT_CACHED'`.
11
+
12
+ ## 0.2.0
13
+
14
+ - New `cadastreParcel({ lat, lng })` on `POST /cadastre/parcel`: what the AdE
15
+ cadastral map finds under a point (`tipologia`, the cadastral fields with
16
+ blanks cleared, the `raw` answer), served from the shared store when known.
17
+ A `503` with `code: 'CADASTRE_BLOCKED'` carries `Retry-After`.
18
+ - New type aliases `CadastreParcelRequest` and `CadastreParcelResult`.
19
+
3
20
  ## 0.1.0
4
21
 
5
22
  - Promote the SDK from `0.1.0-next.1` to the ordinary `latest` npm channel.
package/README.md CHANGED
@@ -49,6 +49,7 @@ retain their own concurrency and retry policy.
49
49
  | `geocode` | `GeocodeRequest` |
50
50
  | `geocodeBatch` | `GeocodeBatchRequest` |
51
51
  | `reverseGeocode` | `ReverseGeocodeRequest` |
52
+ | `cadastreParcel` | `CadastreParcelRequest` |
52
53
  | `territory.status`, `territory.datasets` | options only |
53
54
  | `territory.comuni` | optional `TerritoryComuniQuery` |
54
55
  | `territory.quotazioni` | optional `TerritoryQuotazioniQuery` |
@@ -57,14 +58,24 @@ retain their own concurrency and retry policy.
57
58
  | `territory.resolve` | `TerritoryResolveRequest` |
58
59
 
59
60
  The package exports `createGeoClient`, `ApiError`, client/options interfaces,
60
- query types and the generated geocode/territory request and response aliases.
61
+ query types and the generated geocode/cadastre/territory request and response aliases.
61
62
  The three service health routes are intentionally outside this SDK surface.
62
63
  OpenAPI remains authoritative: TypeScript provides static types, not runtime
63
64
  validation of received data. `include` may be omitted and defaults to `best`
64
65
  on the service.
65
66
 
67
+ The four lookups (`geocode`, `geocodeBatch`, `reverseGeocode`,
68
+ `cadastreParcel`) take `cache` in their options, sent as `Cache-Control`:
69
+ `{ refresh: true }` asks the provider again and stores the new answer,
70
+ `{ refresh: true, noStore: true }` bypasses the store entirely, `{ maxAge }`
71
+ asks again past that many seconds, `{ onlyIfCached: true }` never asks the
72
+ provider (504 `NOT_CACHED`, or `status: 'NOT_CACHED'` for a batch item).
73
+
66
74
  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
75
+ or `undefined` for an empty response. A `503` from `cadastreParcel` with
76
+ `code: 'CADASTRE_BLOCKED'` means the cadastral map is refusing calls: its
77
+ `Retry-After` header (and `retryAfterSeconds` in the body) says when to ask
78
+ again. Network failures, aborts and JSON parsing
68
79
  errors retain their native types. Every Geo success operation requires a JSON
69
80
  result: an unexpected empty/204 success raises `SyntaxError`. The SDK logs no
70
81
  body, URL or credential automatically.
package/dist/index.d.ts CHANGED
@@ -111,6 +111,22 @@ interface paths {
111
111
  patch?: never;
112
112
  trace?: never;
113
113
  };
114
+ '/cadastre/parcel': {
115
+ parameters: {
116
+ query?: never;
117
+ header?: never;
118
+ path?: never;
119
+ cookie?: never;
120
+ };
121
+ get?: never;
122
+ put?: never;
123
+ post: operations['CadastreController_parcel'];
124
+ delete?: never;
125
+ options?: never;
126
+ head?: never;
127
+ patch?: never;
128
+ trace?: never;
129
+ };
114
130
  '/territory/status': {
115
131
  parameters: {
116
132
  query?: never;
@@ -339,8 +355,11 @@ interface components {
339
355
  include?: 'best' | 'full';
340
356
  };
341
357
  GeocodeResultDto: {
342
- /** @enum {string} */
343
- status: 'OK' | 'NO_RESULT' | 'INVALID_QUERY';
358
+ /**
359
+ * @description NOT_CACHED: a batch item refused by Cache-Control only-if-cached.
360
+ * @enum {string}
361
+ */
362
+ status: 'OK' | 'NO_RESULT' | 'INVALID_QUERY' | 'NOT_CACHED';
344
363
  /** @description The normalized query actually used as cache key and provider input. */
345
364
  query: string | null;
346
365
  lat: number | null;
@@ -390,6 +409,36 @@ interface components {
390
409
  [key: string]: unknown;
391
410
  }[] | null;
392
411
  };
412
+ CadastreParcelRequestDto: {
413
+ lat: number;
414
+ lng: number;
415
+ };
416
+ CadastreParcelResultDto: {
417
+ /** @description Canonical cache key: "lat,lng" rounded to 5 decimals (~1 m). */
418
+ query: string;
419
+ /**
420
+ * @description What lies under the point, verbatim from the map: PARTICELLA, STRADA, …
421
+ * @example PARTICELLA
422
+ */
423
+ tipologia: string;
424
+ /** @example B885 */
425
+ codComune: string | null;
426
+ /** @description Null when the map gives none: blanks and "null" strings are cleared. */
427
+ sezione: string | null;
428
+ /** @example 36 */
429
+ foglio: string | null;
430
+ /** @example G */
431
+ allegato: string | null;
432
+ sviluppo: string | null;
433
+ /** @example 5101 */
434
+ particella: string | null;
435
+ /** @description The map answer exactly as returned (blank SEZIONE and "null" strings included). */
436
+ raw: {
437
+ [key: string]: unknown;
438
+ };
439
+ /** @description True when the answer came from the cache, no map call. */
440
+ cached: boolean;
441
+ };
393
442
  TerritoryStatusDto: {
394
443
  /** @description Latest OMI publication loaded (`YYYY-S`), null while no supply is loaded. */
395
444
  semestre: string | null;
@@ -606,7 +655,10 @@ interface operations {
606
655
  GeocodeController_geocode: {
607
656
  parameters: {
608
657
  query?: never;
609
- header?: never;
658
+ header?: {
659
+ /** @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. */
660
+ 'Cache-Control'?: string;
661
+ };
610
662
  path?: never;
611
663
  cookie?: never;
612
664
  };
@@ -638,12 +690,22 @@ interface operations {
638
690
  };
639
691
  content?: never;
640
692
  };
693
+ /** @description NOT_CACHED: only-if-cached and no usable stored answer. */
694
+ 504: {
695
+ headers: {
696
+ [name: string]: unknown;
697
+ };
698
+ content?: never;
699
+ };
641
700
  };
642
701
  };
643
702
  GeocodeController_geocodeBatch: {
644
703
  parameters: {
645
704
  query?: never;
646
- header?: never;
705
+ header?: {
706
+ /** @description Applies to every item: no-cache, no-store, max-age=N, only-if-cached. An item only-if-cached refuses has status NOT_CACHED; the batch still answers 200. */
707
+ 'Cache-Control'?: string;
708
+ };
647
709
  path?: never;
648
710
  cookie?: never;
649
711
  };
@@ -680,7 +742,10 @@ interface operations {
680
742
  GeocodeController_reverseGeocode: {
681
743
  parameters: {
682
744
  query?: never;
683
- header?: never;
745
+ header?: {
746
+ /** @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. */
747
+ 'Cache-Control'?: string;
748
+ };
684
749
  path?: never;
685
750
  cookie?: never;
686
751
  };
@@ -712,6 +777,68 @@ interface operations {
712
777
  };
713
778
  content?: never;
714
779
  };
780
+ /** @description NOT_CACHED: only-if-cached and no usable stored answer. */
781
+ 504: {
782
+ headers: {
783
+ [name: string]: unknown;
784
+ };
785
+ content?: never;
786
+ };
787
+ };
788
+ };
789
+ CadastreController_parcel: {
790
+ parameters: {
791
+ query?: never;
792
+ header?: {
793
+ /** @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. */
794
+ 'Cache-Control'?: string;
795
+ };
796
+ path?: never;
797
+ cookie?: never;
798
+ };
799
+ requestBody: {
800
+ content: {
801
+ 'application/json': components['schemas']['CadastreParcelRequestDto'];
802
+ };
803
+ };
804
+ responses: {
805
+ /** @description What the AdE cadastral map finds under the point: a parcel, a street, … */
806
+ 200: {
807
+ headers: {
808
+ [name: string]: unknown;
809
+ };
810
+ content: {
811
+ 'application/json': components['schemas']['CadastreParcelResultDto'];
812
+ };
813
+ };
814
+ /** @description Missing or invalid API key. */
815
+ 401: {
816
+ headers: {
817
+ [name: string]: unknown;
818
+ };
819
+ content?: never;
820
+ };
821
+ /** @description CADASTRE_UNAVAILABLE or CADASTRE_INVALID_REPLY: the map gave no answer. */
822
+ 502: {
823
+ headers: {
824
+ [name: string]: unknown;
825
+ };
826
+ content?: never;
827
+ };
828
+ /** @description CADASTRE_BLOCKED: the map is refusing calls; Retry-After says when to try again. */
829
+ 503: {
830
+ headers: {
831
+ [name: string]: unknown;
832
+ };
833
+ content?: never;
834
+ };
835
+ /** @description NOT_CACHED: only-if-cached and no usable stored answer. */
836
+ 504: {
837
+ headers: {
838
+ [name: string]: unknown;
839
+ };
840
+ content?: never;
841
+ };
715
842
  };
716
843
  };
717
844
  TerritoryController_status: {
@@ -951,6 +1078,8 @@ type GeocodeResult = ApiSchemas['GeocodeResultDto'];
951
1078
  type GeocodeBatchResponse = ApiSchemas['GeocodeBatchResponseDto'];
952
1079
  type ReverseGeocodeRequest = ApiSchemas['ReverseGeocodeRequestDto'];
953
1080
  type ReverseGeocodeResult = ApiSchemas['ReverseGeocodeResultDto'];
1081
+ type CadastreParcelRequest = ApiSchemas['CadastreParcelRequestDto'];
1082
+ type CadastreParcelResult = ApiSchemas['CadastreParcelResultDto'];
954
1083
  type TerritoryStatus = ApiSchemas['TerritoryStatusDto'];
955
1084
  type TerritoryResolveRequest = ApiSchemas['TerritoryResolveRequestDto'];
956
1085
  type TerritoryResolveResponse = ApiSchemas['TerritoryResolveResponseDto'];
@@ -972,6 +1101,23 @@ type TerritoryCompravenditeQuery = NonNullable<ApiPaths['/territory/compravendit
972
1101
  interface RequestOptions {
973
1102
  signal?: AbortSignal;
974
1103
  }
1104
+ /**
1105
+ * How a lookup may use seta-geo's store, sent as `Cache-Control` (RFC 9111).
1106
+ * Without it the store answers when it can and the provider otherwise.
1107
+ */
1108
+ interface LookupCache {
1109
+ /** `no-cache`: ask the provider even with a stored answer, and store the new one. */
1110
+ refresh?: boolean;
1111
+ /** `no-store`: never write the provider's answer (with `refresh`: a pure bypass). */
1112
+ noStore?: boolean;
1113
+ /** `max-age`: a stored answer older than this many seconds is asked again. */
1114
+ maxAge?: number;
1115
+ /** `only-if-cached`: never ask the provider; 504 `NOT_CACHED` without a stored answer. */
1116
+ onlyIfCached?: boolean;
1117
+ }
1118
+ interface LookupOptions extends RequestOptions {
1119
+ cache?: LookupCache;
1120
+ }
975
1121
  interface GeoClientOptions {
976
1122
  /** Complete API root, including /api. Relative roots are supported in browsers. */
977
1123
  baseUrl: string;
@@ -990,13 +1136,16 @@ interface TerritoryClient {
990
1136
  resolve(input: TerritoryResolveRequest, options?: RequestOptions): Promise<TerritoryResolveResponse>;
991
1137
  }
992
1138
  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>;
1139
+ geocode(input: GeocodeRequest, options?: LookupOptions): Promise<GeocodeResult>;
1140
+ /** `cache` applies to every item; an item only-if-cached refuses has status NOT_CACHED. */
1141
+ geocodeBatch(input: GeocodeBatchRequest, options?: LookupOptions): Promise<GeocodeBatchResponse>;
1142
+ reverseGeocode(input: ReverseGeocodeRequest, options?: LookupOptions): Promise<ReverseGeocodeResult>;
1143
+ /** What the AdE cadastral map finds under a point; 503 `CADASTRE_BLOCKED` carries Retry-After. */
1144
+ cadastreParcel(input: CadastreParcelRequest, options?: LookupOptions): Promise<CadastreParcelResult>;
996
1145
  territory: TerritoryClient;
997
1146
  }
998
1147
 
999
1148
  declare function createGeoClient(options: GeoClientOptions): GeoClient;
1000
1149
 
1001
1150
  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 };
1151
+ export type { CadastreParcelRequest, CadastreParcelResult, GeoClient, GeoClientOptions, 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 };
package/dist/index.js CHANGED
@@ -39,6 +39,21 @@ async function data(request) {
39
39
  if (result.data === void 0) throw new SyntaxError("Geo API returned an empty JSON response");
40
40
  return result.data;
41
41
  }
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
+ function lookupInit(options) {
53
+ const { cache, ...request } = options ?? {};
54
+ const header = cacheControl(cache);
55
+ return header ? { ...request, headers: { "cache-control": header } } : request;
56
+ }
42
57
  function territory(client) {
43
58
  return {
44
59
  status: (options) => data(client.GET("/territory/status", { ...options })),
@@ -67,9 +82,10 @@ function createGeoClient(options) {
67
82
  }
68
83
  });
69
84
  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 })),
85
+ geocode: (input, request) => data(client.POST("/geocode", { ...lookupInit(request), body: input })),
86
+ geocodeBatch: (input, request) => data(client.POST("/geocode/batch", { ...lookupInit(request), body: input })),
87
+ reverseGeocode: (input, request) => data(client.POST("/geocode/reverse", { ...lookupInit(request), body: input })),
88
+ cadastreParcel: (input, request) => data(client.POST("/cadastre/parcel", { ...lookupInit(request), body: input })),
73
89
  territory: territory(client)
74
90
  };
75
91
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@seta-labs/geo",
3
- "version": "0.1.0",
3
+ "version": "0.3.0",
4
4
  "description": "Typed Fetch client for the Seta Geo HTTP API",
5
5
  "license": "UNLICENSED",
6
6
  "type": "module",