@seta-labs/geo 0.2.0 → 0.4.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.4.0
4
+
5
+ - New `cadastreParcels(query?)` on `GET /cadastre/parcels`: the stored
6
+ cadastral answers, newest first, filtered by `codComune`, `foglio`,
7
+ `particella` and `tipologia` (exact match), paginated with `page` and
8
+ `pageSize`. A read of the store: it never asks the cadastral map.
9
+ - New types `CadastreParcelsQuery`, `CadastreParcelsPage` and `CadastreParcelRow`.
10
+
11
+ ## 0.3.0
12
+
13
+ - `geocode`, `geocodeBatch`, `reverseGeocode` and `cadastreParcel` take
14
+ `cache: { refresh, noStore, maxAge, onlyIfCached }`, sent as
15
+ `Cache-Control` (`no-cache`, `no-store`, `max-age`, `only-if-cached`).
16
+ Without it nothing changes. New types `LookupCache` and `LookupOptions`.
17
+ - A lookup refused by `onlyIfCached` rejects with `ApiError` 504
18
+ (`code: 'NOT_CACHED'`); a batch item has `status: 'NOT_CACHED'`.
19
+
3
20
  ## 0.2.0
4
21
 
5
22
  - New `cadastreParcel({ lat, lng })` on `POST /cadastre/parcel`: what the AdE
package/README.md CHANGED
@@ -50,6 +50,7 @@ retain their own concurrency and retry policy.
50
50
  | `geocodeBatch` | `GeocodeBatchRequest` |
51
51
  | `reverseGeocode` | `ReverseGeocodeRequest` |
52
52
  | `cadastreParcel` | `CadastreParcelRequest` |
53
+ | `cadastreParcels` | optional `CadastreParcelsQuery` |
53
54
  | `territory.status`, `territory.datasets` | options only |
54
55
  | `territory.comuni` | optional `TerritoryComuniQuery` |
55
56
  | `territory.quotazioni` | optional `TerritoryQuotazioniQuery` |
@@ -64,6 +65,13 @@ OpenAPI remains authoritative: TypeScript provides static types, not runtime
64
65
  validation of received data. `include` may be omitted and defaults to `best`
65
66
  on the service.
66
67
 
68
+ The four lookups (`geocode`, `geocodeBatch`, `reverseGeocode`,
69
+ `cadastreParcel`) take `cache` in their options, sent as `Cache-Control`:
70
+ `{ refresh: true }` asks the provider again and stores the new answer,
71
+ `{ refresh: true, noStore: true }` bypasses the store entirely, `{ maxAge }`
72
+ asks again past that many seconds, `{ onlyIfCached: true }` never asks the
73
+ provider (504 `NOT_CACHED`, or `status: 'NOT_CACHED'` for a batch item).
74
+
67
75
  HTTP errors are `ApiError`; their body is JSON when parseable, otherwise text,
68
76
  or `undefined` for an empty response. A `503` from `cadastreParcel` with
69
77
  `code: 'CADASTRE_BLOCKED'` means the cadastral map is refusing calls: its
package/dist/index.d.ts CHANGED
@@ -127,6 +127,22 @@ interface paths {
127
127
  patch?: never;
128
128
  trace?: never;
129
129
  };
130
+ '/cadastre/parcels': {
131
+ parameters: {
132
+ query?: never;
133
+ header?: never;
134
+ path?: never;
135
+ cookie?: never;
136
+ };
137
+ get: operations['CadastreController_listParcels'];
138
+ put?: never;
139
+ post?: never;
140
+ delete?: never;
141
+ options?: never;
142
+ head?: never;
143
+ patch?: never;
144
+ trace?: never;
145
+ };
130
146
  '/territory/status': {
131
147
  parameters: {
132
148
  query?: never;
@@ -355,8 +371,11 @@ interface components {
355
371
  include?: 'best' | 'full';
356
372
  };
357
373
  GeocodeResultDto: {
358
- /** @enum {string} */
359
- status: 'OK' | 'NO_RESULT' | 'INVALID_QUERY';
374
+ /**
375
+ * @description NOT_CACHED: a batch item refused by Cache-Control only-if-cached.
376
+ * @enum {string}
377
+ */
378
+ status: 'OK' | 'NO_RESULT' | 'INVALID_QUERY' | 'NOT_CACHED';
360
379
  /** @description The normalized query actually used as cache key and provider input. */
361
380
  query: string | null;
362
381
  lat: number | null;
@@ -436,6 +455,39 @@ interface components {
436
455
  /** @description True when the answer came from the cache, no map call. */
437
456
  cached: boolean;
438
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;
479
+ /**
480
+ * Format: date-time
481
+ * @description When the map last gave this answer.
482
+ */
483
+ updatedAt: string;
484
+ };
485
+ CadastreParcelsPageDto: {
486
+ page: number;
487
+ pageSize: number;
488
+ total: number;
489
+ items: components['schemas']['CadastreParcelRowDto'][];
490
+ };
439
491
  TerritoryStatusDto: {
440
492
  /** @description Latest OMI publication loaded (`YYYY-S`), null while no supply is loaded. */
441
493
  semestre: string | null;
@@ -652,7 +704,10 @@ interface operations {
652
704
  GeocodeController_geocode: {
653
705
  parameters: {
654
706
  query?: never;
655
- header?: never;
707
+ header?: {
708
+ /** @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. */
709
+ 'Cache-Control'?: string;
710
+ };
656
711
  path?: never;
657
712
  cookie?: never;
658
713
  };
@@ -684,12 +739,22 @@ interface operations {
684
739
  };
685
740
  content?: never;
686
741
  };
742
+ /** @description NOT_CACHED: only-if-cached and no usable stored answer. */
743
+ 504: {
744
+ headers: {
745
+ [name: string]: unknown;
746
+ };
747
+ content?: never;
748
+ };
687
749
  };
688
750
  };
689
751
  GeocodeController_geocodeBatch: {
690
752
  parameters: {
691
753
  query?: never;
692
- header?: never;
754
+ header?: {
755
+ /** @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. */
756
+ 'Cache-Control'?: string;
757
+ };
693
758
  path?: never;
694
759
  cookie?: never;
695
760
  };
@@ -726,7 +791,10 @@ interface operations {
726
791
  GeocodeController_reverseGeocode: {
727
792
  parameters: {
728
793
  query?: never;
729
- header?: never;
794
+ header?: {
795
+ /** @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. */
796
+ 'Cache-Control'?: string;
797
+ };
730
798
  path?: never;
731
799
  cookie?: never;
732
800
  };
@@ -758,12 +826,22 @@ interface operations {
758
826
  };
759
827
  content?: never;
760
828
  };
829
+ /** @description NOT_CACHED: only-if-cached and no usable stored answer. */
830
+ 504: {
831
+ headers: {
832
+ [name: string]: unknown;
833
+ };
834
+ content?: never;
835
+ };
761
836
  };
762
837
  };
763
838
  CadastreController_parcel: {
764
839
  parameters: {
765
840
  query?: never;
766
- header?: never;
841
+ header?: {
842
+ /** @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. */
843
+ 'Cache-Control'?: string;
844
+ };
767
845
  path?: never;
768
846
  cookie?: never;
769
847
  };
@@ -803,6 +881,49 @@ interface operations {
803
881
  };
804
882
  content?: never;
805
883
  };
884
+ /** @description NOT_CACHED: only-if-cached and no usable stored answer. */
885
+ 504: {
886
+ headers: {
887
+ [name: string]: unknown;
888
+ };
889
+ content?: never;
890
+ };
891
+ };
892
+ };
893
+ CadastreController_listParcels: {
894
+ parameters: {
895
+ query?: {
896
+ page?: number;
897
+ pageSize?: number;
898
+ /** @description Cadastral municipality code. */
899
+ codComune?: string;
900
+ foglio?: string;
901
+ particella?: string;
902
+ /** @description What lies under the point. */
903
+ tipologia?: string;
904
+ };
905
+ header?: never;
906
+ path?: never;
907
+ cookie?: never;
908
+ };
909
+ requestBody?: never;
910
+ responses: {
911
+ /** @description The stored map answers, newest first; never a map call. */
912
+ 200: {
913
+ headers: {
914
+ [name: string]: unknown;
915
+ };
916
+ content: {
917
+ 'application/json': components['schemas']['CadastreParcelsPageDto'];
918
+ };
919
+ };
920
+ /** @description Missing or invalid API key. */
921
+ 401: {
922
+ headers: {
923
+ [name: string]: unknown;
924
+ };
925
+ content?: never;
926
+ };
806
927
  };
807
928
  };
808
929
  TerritoryController_status: {
@@ -1044,6 +1165,8 @@ type ReverseGeocodeRequest = ApiSchemas['ReverseGeocodeRequestDto'];
1044
1165
  type ReverseGeocodeResult = ApiSchemas['ReverseGeocodeResultDto'];
1045
1166
  type CadastreParcelRequest = ApiSchemas['CadastreParcelRequestDto'];
1046
1167
  type CadastreParcelResult = ApiSchemas['CadastreParcelResultDto'];
1168
+ type CadastreParcelsPage = ApiSchemas['CadastreParcelsPageDto'];
1169
+ type CadastreParcelRow = ApiSchemas['CadastreParcelRowDto'];
1047
1170
  type TerritoryStatus = ApiSchemas['TerritoryStatusDto'];
1048
1171
  type TerritoryResolveRequest = ApiSchemas['TerritoryResolveRequestDto'];
1049
1172
  type TerritoryResolveResponse = ApiSchemas['TerritoryResolveResponseDto'];
@@ -1062,9 +1185,27 @@ type TerritoryCompravendita = ApiSchemas['TerritoryCompravenditaDto'];
1062
1185
  type TerritoryComuniQuery = NonNullable<ApiPaths['/territory/comuni']['get']['parameters']['query']>;
1063
1186
  type TerritoryQuotazioniQuery = NonNullable<ApiPaths['/territory/quotazioni']['get']['parameters']['query']>;
1064
1187
  type TerritoryCompravenditeQuery = NonNullable<ApiPaths['/territory/compravendite']['get']['parameters']['query']>;
1188
+ type CadastreParcelsQuery = NonNullable<ApiPaths['/cadastre/parcels']['get']['parameters']['query']>;
1065
1189
  interface RequestOptions {
1066
1190
  signal?: AbortSignal;
1067
1191
  }
1192
+ /**
1193
+ * How a lookup may use seta-geo's store, sent as `Cache-Control` (RFC 9111).
1194
+ * Without it the store answers when it can and the provider otherwise.
1195
+ */
1196
+ interface LookupCache {
1197
+ /** `no-cache`: ask the provider even with a stored answer, and store the new one. */
1198
+ refresh?: boolean;
1199
+ /** `no-store`: never write the provider's answer (with `refresh`: a pure bypass). */
1200
+ noStore?: boolean;
1201
+ /** `max-age`: a stored answer older than this many seconds is asked again. */
1202
+ maxAge?: number;
1203
+ /** `only-if-cached`: never ask the provider; 504 `NOT_CACHED` without a stored answer. */
1204
+ onlyIfCached?: boolean;
1205
+ }
1206
+ interface LookupOptions extends RequestOptions {
1207
+ cache?: LookupCache;
1208
+ }
1068
1209
  interface GeoClientOptions {
1069
1210
  /** Complete API root, including /api. Relative roots are supported in browsers. */
1070
1211
  baseUrl: string;
@@ -1083,15 +1224,18 @@ interface TerritoryClient {
1083
1224
  resolve(input: TerritoryResolveRequest, options?: RequestOptions): Promise<TerritoryResolveResponse>;
1084
1225
  }
1085
1226
  interface GeoClient {
1086
- geocode(input: GeocodeRequest, options?: RequestOptions): Promise<GeocodeResult>;
1087
- geocodeBatch(input: GeocodeBatchRequest, options?: RequestOptions): Promise<GeocodeBatchResponse>;
1088
- reverseGeocode(input: ReverseGeocodeRequest, options?: RequestOptions): Promise<ReverseGeocodeResult>;
1227
+ geocode(input: GeocodeRequest, options?: LookupOptions): Promise<GeocodeResult>;
1228
+ /** `cache` applies to every item; an item only-if-cached refuses has status NOT_CACHED. */
1229
+ geocodeBatch(input: GeocodeBatchRequest, options?: LookupOptions): Promise<GeocodeBatchResponse>;
1230
+ reverseGeocode(input: ReverseGeocodeRequest, options?: LookupOptions): Promise<ReverseGeocodeResult>;
1089
1231
  /** What the AdE cadastral map finds under a point; 503 `CADASTRE_BLOCKED` carries Retry-After. */
1090
- cadastreParcel(input: CadastreParcelRequest, options?: RequestOptions): Promise<CadastreParcelResult>;
1232
+ cadastreParcel(input: CadastreParcelRequest, options?: LookupOptions): Promise<CadastreParcelResult>;
1233
+ /** The stored cadastral answers, newest first: a read of the store, never a map call. */
1234
+ cadastreParcels(query?: CadastreParcelsQuery, options?: RequestOptions): Promise<CadastreParcelsPage>;
1091
1235
  territory: TerritoryClient;
1092
1236
  }
1093
1237
 
1094
1238
  declare function createGeoClient(options: GeoClientOptions): GeoClient;
1095
1239
 
1096
1240
  export { ApiError, createGeoClient };
1097
- export type { CadastreParcelRequest, CadastreParcelResult, 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 };
1241
+ export type { CadastreParcelRequest, CadastreParcelResult, CadastreParcelRow, CadastreParcelsPage, CadastreParcelsQuery, 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,10 +82,11 @@ 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 })),
73
- cadastreParcel: (input, request) => data(client.POST("/cadastre/parcel", { ...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 })),
89
+ cadastreParcels: (query, options2) => data(client.GET("/cadastre/parcels", { ...options2, params: { query } })),
74
90
  territory: territory(client)
75
91
  };
76
92
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@seta-labs/geo",
3
- "version": "0.2.0",
3
+ "version": "0.4.0",
4
4
  "description": "Typed Fetch client for the Seta Geo HTTP API",
5
5
  "license": "UNLICENSED",
6
6
  "type": "module",