@honua/react 0.1.1-beta.0 → 0.1.4-beta.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/contract/discovery.d.ts +34 -0
- package/contract/discovery.d.ts.map +1 -1
- package/contract/index.d.ts +7 -5
- package/contract/index.d.ts.map +1 -1
- package/contract/index.js +3 -2
- package/contract/pmtiles.d.ts +10 -1
- package/contract/pmtiles.d.ts.map +1 -1
- package/contract/pmtiles.js +10 -2
- package/contract/protocol-module.d.ts +84 -9
- package/contract/protocol-module.d.ts.map +1 -1
- package/contract/query-filter-fes.d.ts +18 -0
- package/contract/query-filter-fes.d.ts.map +1 -0
- package/contract/query-filter-fes.js +122 -0
- package/contract/query-filter.d.ts +300 -0
- package/contract/query-filter.d.ts.map +1 -0
- package/contract/query-filter.js +883 -0
- package/contract/schema.d.ts +12 -0
- package/contract/schema.d.ts.map +1 -1
- package/contract/schema.js +34 -0
- package/contract/source-capability-support.d.ts.map +1 -1
- package/contract/source-capability-support.js +15 -3
- package/contract/source.d.ts +45 -25
- package/contract/source.d.ts.map +1 -1
- package/contract/source.js +719 -479
- package/contract/types.d.ts +87 -18
- package/contract/types.d.ts.map +1 -1
- package/contract/widget-source.d.ts.map +1 -1
- package/core/client.d.ts +45 -21
- package/core/client.d.ts.map +1 -1
- package/core/client.js +224 -46
- package/core/error-classifications.d.ts +6 -0
- package/core/error-classifications.d.ts.map +1 -1
- package/core/error-classifications.js +6 -0
- package/core/error-code-registry.d.ts +6 -0
- package/core/error-code-registry.d.ts.map +1 -1
- package/core/error-code-registry.js +6 -0
- package/core/error-envelope.js +2 -0
- package/core/errors.d.ts +1 -1
- package/core/errors.js +1 -1
- package/core/geoparquet-sql.d.ts +68 -4
- package/core/geoparquet-sql.d.ts.map +1 -1
- package/core/geoparquet-sql.js +325 -8
- package/core/odata-write-codec.d.ts.map +1 -1
- package/core/odata-write-codec.js +0 -1
- package/core/odata.d.ts +6 -0
- package/core/odata.d.ts.map +1 -1
- package/core/odata.js +6 -1
- package/core/ogc-processes.d.ts +182 -25
- package/core/ogc-processes.d.ts.map +1 -1
- package/core/ogc-processes.js +602 -30
- package/core/process-runner.d.ts +8 -1
- package/core/process-runner.d.ts.map +1 -1
- package/core/process-runner.js +1 -0
- package/core/protocol-transport.d.ts +2 -0
- package/core/protocol-transport.d.ts.map +1 -1
- package/core/stac.d.ts +31 -0
- package/core/stac.d.ts.map +1 -1
- package/core/stac.js +136 -19
- package/core/surfaces.d.ts.map +1 -1
- package/core/surfaces.js +273 -68
- package/core/types.d.ts +123 -9
- package/core/types.d.ts.map +1 -1
- package/core/wfs-axis-order.d.ts +18 -0
- package/core/wfs-axis-order.d.ts.map +1 -0
- package/core/wfs-axis-order.js +108 -0
- package/core/wfs-capabilities.d.ts +65 -24
- package/core/wfs-capabilities.d.ts.map +1 -1
- package/core/wfs-capabilities.js +237 -27
- package/core/wfs-filter.d.ts +26 -3
- package/core/wfs-filter.d.ts.map +1 -1
- package/core/wfs-filter.js +48 -53
- package/core/wfs-protocol-error.d.ts +12 -0
- package/core/wfs-protocol-error.d.ts.map +1 -0
- package/core/wfs-protocol-error.js +13 -0
- package/core/wfs.d.ts +53 -1
- package/core/wfs.d.ts.map +1 -1
- package/core/wfs.js +251 -29
- package/core/wms-feature-info.d.ts +92 -0
- package/core/wms-feature-info.d.ts.map +1 -0
- package/core/wms-feature-info.js +285 -0
- package/core/wms.d.ts +9 -0
- package/core/wms.d.ts.map +1 -1
- package/core/wms.js +17 -5
- package/esri-compat/event-bus.d.ts +20 -0
- package/esri-compat/event-bus.d.ts.map +1 -1
- package/esri-compat/feature-filter.d.ts +46 -1
- package/esri-compat/feature-filter.d.ts.map +1 -1
- package/esri-compat/feature-filter.js +49 -0
- package/esri-compat/geojson-layer.d.ts +4 -2
- package/esri-compat/geojson-layer.d.ts.map +1 -1
- package/esri-compat/geojson-layer.js +4 -2
- package/esri-compat/identity-manager.js +2 -2
- package/esri-compat/imagery-layer.d.ts +2 -1
- package/esri-compat/imagery-layer.d.ts.map +1 -1
- package/esri-compat/imagery-layer.js +2 -1
- package/esri-compat/layer-list.d.ts +8 -1
- package/esri-compat/layer-list.d.ts.map +1 -1
- package/esri-compat/layer-list.js +11 -4
- package/esri-compat/legend.d.ts.map +1 -1
- package/esri-compat/legend.js +1 -1
- package/esri-compat/locator.d.ts +297 -0
- package/esri-compat/locator.d.ts.map +1 -0
- package/esri-compat/locator.js +579 -0
- package/esri-compat/map-image-layer.d.ts.map +1 -1
- package/esri-compat/map-view.d.ts.map +1 -1
- package/esri-compat/map-view.js +1 -1
- package/esri-compat/scene-view.d.ts.map +1 -1
- package/esri-compat/time-slider.d.ts +29 -0
- package/esri-compat/time-slider.d.ts.map +1 -1
- package/esri-compat/time-slider.js +117 -0
- package/esri-compat/wfs-layer.d.ts +2 -1
- package/esri-compat/wfs-layer.d.ts.map +1 -1
- package/esri-compat/wfs-layer.js +2 -1
- package/esri-compat/widget-host.d.ts +42 -6
- package/esri-compat/widget-host.d.ts.map +1 -1
- package/esri-compat/widget-host.js +99 -10
- package/generated-app/projection.d.ts.map +1 -1
- package/generated-app/runtime.d.ts.map +1 -1
- package/map/data-to-map-bridge.d.ts +1 -1
- package/map/data-to-map-bridge.d.ts.map +1 -1
- package/map/data-to-map-bridge.js +1 -2
- package/map/honua-map.d.ts.map +1 -1
- package/map/honua-map.js +1 -1
- package/map/index.d.ts +2 -0
- package/map/index.d.ts.map +1 -1
- package/map/index.js +2 -0
- package/map/realtime-reconciliation-adapter.d.ts +98 -0
- package/map/realtime-reconciliation-adapter.d.ts.map +1 -0
- package/map/realtime-reconciliation-adapter.js +84 -0
- package/map/temporal-playback.d.ts +25 -0
- package/map/temporal-playback.d.ts.map +1 -1
- package/map/temporal-playback.js +29 -1
- package/package.json +4 -3
- package/query-planner/canonical-filter.d.ts +30 -0
- package/query-planner/canonical-filter.d.ts.map +1 -0
- package/query-planner/canonical-filter.js +30 -0
- package/query-planner/columnar-plan.d.ts +63 -0
- package/query-planner/columnar-plan.d.ts.map +1 -0
- package/query-planner/columnar-plan.js +94 -0
- package/query-planner/diagnostics.d.ts +3 -3
- package/query-planner/diagnostics.d.ts.map +1 -1
- package/query-planner/diagnostics.js +17 -4
- package/query-planner/duckdb.d.ts.map +1 -1
- package/query-planner/duckdb.js +16 -2
- package/query-planner/execution-context.d.ts.map +1 -1
- package/query-planner/execution-context.js +3 -1
- package/query-planner/executor.d.ts.map +1 -1
- package/query-planner/executor.js +7 -1
- package/query-planner/geoservices.d.ts.map +1 -1
- package/query-planner/geoservices.js +24 -5
- package/query-planner/grpc.d.ts.map +1 -1
- package/query-planner/grpc.js +29 -5
- package/query-planner/index.d.ts +20 -5
- package/query-planner/index.d.ts.map +1 -1
- package/query-planner/index.js +16 -3
- package/query-planner/ir.d.ts +6 -1
- package/query-planner/ir.d.ts.map +1 -1
- package/query-planner/ir.js +63 -1
- package/query-planner/odata-protocol-module.d.ts +54 -0
- package/query-planner/odata-protocol-module.d.ts.map +1 -0
- package/query-planner/odata-protocol-module.js +199 -0
- package/query-planner/odata-v1.d.ts +23 -0
- package/query-planner/odata-v1.d.ts.map +1 -0
- package/query-planner/odata-v1.js +202 -0
- package/query-planner/odata.d.ts +0 -3
- package/query-planner/odata.d.ts.map +1 -1
- package/query-planner/odata.js +0 -143
- package/query-planner/ogc-features.d.ts.map +1 -1
- package/query-planner/ogc-features.js +23 -1
- package/query-planner/planner.d.ts +2 -2
- package/query-planner/planner.d.ts.map +1 -1
- package/query-planner/planner.js +146 -27
- package/query-planner/representation.d.ts +59 -0
- package/query-planner/representation.d.ts.map +1 -0
- package/query-planner/representation.js +140 -0
- package/query-planner/types.d.ts +153 -3
- package/query-planner/types.d.ts.map +1 -1
- package/query-planner/wfs-protocol-module.d.ts +57 -0
- package/query-planner/wfs-protocol-module.d.ts.map +1 -0
- package/query-planner/wfs-protocol-module.js +731 -0
- package/query-planner/wfs-v1.d.ts +41 -0
- package/query-planner/wfs-v1.d.ts.map +1 -0
- package/query-planner/wfs-v1.js +366 -0
- package/query-planner/wfs.d.ts +1 -3
- package/query-planner/wfs.d.ts.map +1 -1
- package/query-planner/wfs.js +1 -70
- package/react/honua-map.d.ts +3 -1
- package/react/honua-map.d.ts.map +1 -1
- package/react/honua-map.js +11 -1
- package/react/hooks.d.ts +1 -1
- package/react/hooks.js +1 -1
- package/react/selection.d.ts.map +1 -1
- package/react/selection.js +0 -0
- package/react/use-mounted-source.d.ts +1 -1
- package/react/use-mounted-source.js +1 -1
- package/realtime/checkpoint-store.d.ts +206 -0
- package/realtime/checkpoint-store.d.ts.map +1 -0
- package/realtime/checkpoint-store.js +504 -0
- package/realtime/contract.d.ts +142 -0
- package/realtime/contract.d.ts.map +1 -0
- package/realtime/contract.js +154 -0
- package/realtime/exploration.d.ts +16 -0
- package/realtime/exploration.d.ts.map +1 -0
- package/realtime/exploration.js +32 -0
- package/realtime/honua-server.d.ts +131 -0
- package/realtime/honua-server.d.ts.map +1 -0
- package/realtime/honua-server.js +302 -0
- package/realtime/index.d.ts +39 -0
- package/realtime/index.d.ts.map +1 -0
- package/realtime/index.js +26 -0
- package/realtime/odata-delta.d.ts +114 -0
- package/realtime/odata-delta.d.ts.map +1 -0
- package/realtime/odata-delta.js +483 -0
- package/realtime/projections.d.ts +30 -0
- package/realtime/projections.d.ts.map +1 -0
- package/realtime/projections.js +51 -0
- package/realtime/reconciliation.d.ts +245 -0
- package/realtime/reconciliation.d.ts.map +1 -0
- package/realtime/reconciliation.js +377 -0
- package/realtime/reducer.d.ts +14 -0
- package/realtime/reducer.d.ts.map +1 -0
- package/realtime/reducer.js +351 -0
- package/realtime/resumable-transport.d.ts +111 -0
- package/realtime/resumable-transport.d.ts.map +1 -0
- package/realtime/resumable-transport.js +406 -0
- package/realtime/resumable.d.ts +104 -0
- package/realtime/resumable.d.ts.map +1 -0
- package/realtime/resumable.js +632 -0
- package/realtime/sse.d.ts +23 -0
- package/realtime/sse.d.ts.map +1 -0
- package/realtime/sse.js +147 -0
- package/realtime/store.d.ts +18 -0
- package/realtime/store.d.ts.map +1 -0
- package/realtime/store.js +63 -0
- package/realtime/types.d.ts +190 -0
- package/realtime/types.d.ts.map +1 -0
- package/realtime/types.js +10 -0
- package/realtime/websocket.d.ts +60 -0
- package/realtime/websocket.d.ts.map +1 -0
- package/realtime/websocket.js +157 -0
- package/realtime/wire.d.ts +28 -0
- package/realtime/wire.d.ts.map +1 -0
- package/realtime/wire.js +73 -0
- package/runtime/index.d.ts +6 -2
- package/runtime/index.d.ts.map +1 -1
- package/runtime/index.js +3 -1
- package/runtime/load-package.d.ts +24 -0
- package/runtime/load-package.d.ts.map +1 -1
- package/runtime/load-package.js +45 -1
- package/runtime/maplibre-renderer.d.ts +11 -0
- package/runtime/maplibre-renderer.d.ts.map +1 -1
- package/runtime/maplibre-renderer.js +32 -7
- package/runtime/offline-region-protocol.d.ts +73 -0
- package/runtime/offline-region-protocol.d.ts.map +1 -0
- package/runtime/offline-region-protocol.js +63 -0
- package/runtime/offline-region-style.d.ts +78 -0
- package/runtime/offline-region-style.d.ts.map +1 -0
- package/runtime/offline-region-style.js +147 -0
- package/runtime/pmtiles-protocol.d.ts +7 -8
- package/runtime/pmtiles-protocol.d.ts.map +1 -1
- package/runtime/pmtiles-protocol.js +19 -66
- package/runtime/protocol-registry.d.ts +55 -0
- package/runtime/protocol-registry.d.ts.map +1 -0
- package/runtime/protocol-registry.js +103 -0
- package/runtime/runtime.d.ts +2 -0
- package/runtime/runtime.d.ts.map +1 -1
- package/runtime/runtime.js +6 -1
- package/studio/index.d.ts +24 -7
- package/studio/index.d.ts.map +1 -1
- package/studio/index.js +21 -7
- package/studio/lifecycle-client.d.ts +164 -0
- package/studio/lifecycle-client.d.ts.map +1 -0
- package/studio/lifecycle-client.js +299 -0
- package/studio/lifecycle-errors.d.ts +91 -0
- package/studio/lifecycle-errors.d.ts.map +1 -0
- package/studio/lifecycle-errors.js +77 -0
- package/studio/lifecycle-types.d.ts +321 -0
- package/studio/lifecycle-types.d.ts.map +1 -0
- package/studio/lifecycle-types.js +22 -0
- package/webmap/convert-label.js +0 -1
- package/webmap/convert-symbol.d.ts.map +1 -1
package/core/surfaces.js
CHANGED
|
@@ -774,33 +774,15 @@ export class HonuaOgcFeatures {
|
|
|
774
774
|
return this.client.listOgcItems({ ...request, ...(layout ? { layout } : {}) });
|
|
775
775
|
}
|
|
776
776
|
async itemsAll(request) {
|
|
777
|
-
const pageSize = normalizePageSize(request.pageSize, request.limit);
|
|
778
|
-
const maxPages = normalizeMaxPages(request.maxPages);
|
|
779
|
-
const offset = normalizeOffset(request.offset);
|
|
780
777
|
const totalLimit = normalizeTotalLimit(request.limit);
|
|
781
778
|
const features = [];
|
|
782
|
-
for (
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
}
|
|
790
|
-
const limit = Math.min(pageSize, remainingLimit);
|
|
791
|
-
const response = await this.items({
|
|
792
|
-
...request,
|
|
793
|
-
limit,
|
|
794
|
-
offset: offset + page * pageSize,
|
|
795
|
-
});
|
|
796
|
-
const pageFeatures = extractOgcFeatures(response);
|
|
797
|
-
if (pageFeatures.length === 0) {
|
|
798
|
-
break;
|
|
799
|
-
}
|
|
800
|
-
features.push(...pageFeatures);
|
|
801
|
-
if (pageFeatures.length < limit) {
|
|
802
|
-
break;
|
|
803
|
-
}
|
|
779
|
+
for await (const page of drainOgcItemPages((pageRequest) => this.items(withOgcItemsPage(request, pageRequest)), {
|
|
780
|
+
pageSize: normalizePageSize(request.pageSize, request.limit),
|
|
781
|
+
maxPages: normalizeMaxPages(request.maxPages),
|
|
782
|
+
startOffset: normalizeOffset(request.offset),
|
|
783
|
+
...(totalLimit !== undefined ? { totalLimit } : {}),
|
|
784
|
+
})) {
|
|
785
|
+
features.push(...page);
|
|
804
786
|
}
|
|
805
787
|
if (totalLimit !== undefined && features.length > totalLimit) {
|
|
806
788
|
return features.slice(0, totalLimit);
|
|
@@ -865,33 +847,15 @@ export class HonuaOgcFeatureCollection {
|
|
|
865
847
|
});
|
|
866
848
|
}
|
|
867
849
|
async itemsAll(request = {}) {
|
|
868
|
-
const pageSize = normalizePageSize(request.pageSize, request.limit);
|
|
869
|
-
const maxPages = normalizeMaxPages(request.maxPages);
|
|
870
|
-
const offset = normalizeOffset(request.offset);
|
|
871
850
|
const totalLimit = normalizeTotalLimit(request.limit);
|
|
872
851
|
const features = [];
|
|
873
|
-
for (
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
}
|
|
881
|
-
const limit = Math.min(pageSize, remainingLimit);
|
|
882
|
-
const response = await this.items({
|
|
883
|
-
...request,
|
|
884
|
-
limit,
|
|
885
|
-
offset: offset + page * pageSize,
|
|
886
|
-
});
|
|
887
|
-
const pageFeatures = extractOgcFeatures(response);
|
|
888
|
-
if (pageFeatures.length === 0) {
|
|
889
|
-
break;
|
|
890
|
-
}
|
|
891
|
-
features.push(...pageFeatures);
|
|
892
|
-
if (pageFeatures.length < limit) {
|
|
893
|
-
break;
|
|
894
|
-
}
|
|
852
|
+
for await (const page of drainOgcItemPages((pageRequest) => this.items(withOgcItemsPage(request, pageRequest)), {
|
|
853
|
+
pageSize: normalizePageSize(request.pageSize, request.limit),
|
|
854
|
+
maxPages: normalizeMaxPages(request.maxPages),
|
|
855
|
+
startOffset: normalizeOffset(request.offset),
|
|
856
|
+
...(totalLimit !== undefined ? { totalLimit } : {}),
|
|
857
|
+
})) {
|
|
858
|
+
features.push(...page);
|
|
895
859
|
}
|
|
896
860
|
if (totalLimit !== undefined && features.length > totalLimit) {
|
|
897
861
|
return features.slice(0, totalLimit);
|
|
@@ -899,24 +863,13 @@ export class HonuaOgcFeatureCollection {
|
|
|
899
863
|
return features;
|
|
900
864
|
}
|
|
901
865
|
async *itemsStream(request = {}) {
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
offset: offset + page * pageSize,
|
|
910
|
-
});
|
|
911
|
-
const pageFeatures = extractOgcFeatures(response);
|
|
912
|
-
if (pageFeatures.length === 0) {
|
|
913
|
-
break;
|
|
914
|
-
}
|
|
915
|
-
yield pageFeatures;
|
|
916
|
-
if (pageFeatures.length < pageSize) {
|
|
917
|
-
break;
|
|
918
|
-
}
|
|
919
|
-
}
|
|
866
|
+
// The stream yields every page the drain produces; `limit` selects the
|
|
867
|
+
// page size here (as it always has) rather than capping the total.
|
|
868
|
+
yield* drainOgcItemPages((pageRequest) => this.items(withOgcItemsPage(request, pageRequest)), {
|
|
869
|
+
pageSize: normalizePageSize(request.pageSize, request.limit),
|
|
870
|
+
maxPages: normalizeMaxPages(request.maxPages),
|
|
871
|
+
startOffset: normalizeOffset(request.offset),
|
|
872
|
+
});
|
|
920
873
|
}
|
|
921
874
|
async item(request) {
|
|
922
875
|
const layout = await this.layout();
|
|
@@ -1974,6 +1927,258 @@ function normalizeTotalLimit(limit) {
|
|
|
1974
1927
|
}
|
|
1975
1928
|
return limit;
|
|
1976
1929
|
}
|
|
1930
|
+
/**
|
|
1931
|
+
* Query-string keys on an OGC API Features `rel="next"` link that must not be
|
|
1932
|
+
* replayed on the follow-up request: the drain owns the page size (`limit`)
|
|
1933
|
+
* and the caller owns the response format (`f`). Every other param on the
|
|
1934
|
+
* link — `offset` / `startindex` on an offset server, an opaque
|
|
1935
|
+
* `token` / `cursor` / `next` on a cursor server — is the server's own paging
|
|
1936
|
+
* state and is echoed back verbatim, subject to the stale-offset rules in
|
|
1937
|
+
* {@link ogcCursorFromHref}.
|
|
1938
|
+
*/
|
|
1939
|
+
const OGC_NEXT_LINK_RESERVED_PARAMS = new Set(["f", "limit"]);
|
|
1940
|
+
/**
|
|
1941
|
+
* Params that express a numeric start position. Compared case-insensitively:
|
|
1942
|
+
* `offset` is Honua Server's spelling, `startindex` / `startIndex` the OGC API
|
|
1943
|
+
* spelling servers vary the casing of.
|
|
1944
|
+
*/
|
|
1945
|
+
const OGC_OFFSET_PARAM_NAMES = new Set(["offset", "startindex"]);
|
|
1946
|
+
/**
|
|
1947
|
+
* Params that express an opaque paging cursor. Matched case-insensitively with
|
|
1948
|
+
* `-` / `_` stripped, so `page_token` and `pageToken` both hit. When one of
|
|
1949
|
+
* these drives the next link, the token owns the read position and any
|
|
1950
|
+
* `offset` / `startindex` on the same link is a stale echo of the request the
|
|
1951
|
+
* server just answered.
|
|
1952
|
+
*/
|
|
1953
|
+
const OGC_CURSOR_PARAM_NAMES = new Set([
|
|
1954
|
+
"token",
|
|
1955
|
+
"nexttoken",
|
|
1956
|
+
"pagetoken",
|
|
1957
|
+
"continuationtoken",
|
|
1958
|
+
"resumptiontoken",
|
|
1959
|
+
"cursor",
|
|
1960
|
+
"next",
|
|
1961
|
+
"page",
|
|
1962
|
+
"searchafter",
|
|
1963
|
+
"scroll",
|
|
1964
|
+
"scrollid",
|
|
1965
|
+
]);
|
|
1966
|
+
/** Base used only to parse relative `rel="next"` hrefs; never requested. */
|
|
1967
|
+
const OGC_NEXT_LINK_PARSE_BASE = "https://placeholder.test";
|
|
1968
|
+
/**
|
|
1969
|
+
* Page `/items` the way OGC API Features Core specifies: follow the
|
|
1970
|
+
* `rel="next"` link the server returns, and only fall back to `limit`/`offset`
|
|
1971
|
+
* arithmetic when the response advertises no usable next link. Offset
|
|
1972
|
+
* arithmetic alone silently mis-pages every server that pages by opaque token
|
|
1973
|
+
* or cursor (pgstac-backed APIs, several pygeoapi deployments): the cursor is
|
|
1974
|
+
* dropped, so pages repeat or skip with no error.
|
|
1975
|
+
*
|
|
1976
|
+
* Anti-corruption invariants, preserved on both paths:
|
|
1977
|
+
* - an empty page ends the drain;
|
|
1978
|
+
* - a page whose feature identity repeats an already-yielded page is dropped
|
|
1979
|
+
* and ends the drain, so a looping server can never duplicate features;
|
|
1980
|
+
* - a `rel="next"` link that repeats a cursor already followed is treated as
|
|
1981
|
+
* a stalled pager and ends the drain;
|
|
1982
|
+
* - a stale `offset` / `startindex` echoed on a cursor link never reaches the
|
|
1983
|
+
* wire beside the cursor (see {@link ogcCursorFromHref});
|
|
1984
|
+
* - `maxPages` still caps the total number of requests.
|
|
1985
|
+
*
|
|
1986
|
+
* On the link-driven path a short page is deliberately *not* a stop signal:
|
|
1987
|
+
* OGC API Features permits a server to return fewer than `limit` items on a
|
|
1988
|
+
* non-final page while still advertising `rel="next"` (the same tolerance the
|
|
1989
|
+
* Records drain applies). On the offset path a short page still ends the
|
|
1990
|
+
* drain, as it always has.
|
|
1991
|
+
*/
|
|
1992
|
+
async function* drainOgcItemPages(fetchPage, options) {
|
|
1993
|
+
const { pageSize, maxPages, startOffset, totalLimit } = options;
|
|
1994
|
+
const seenCursorKeys = new Set();
|
|
1995
|
+
const seenPageKeys = new Set();
|
|
1996
|
+
let cursor;
|
|
1997
|
+
let emitted = 0;
|
|
1998
|
+
for (let page = 0; page < maxPages; page += 1) {
|
|
1999
|
+
const remaining = totalLimit === undefined ? pageSize : Math.max(0, totalLimit - emitted);
|
|
2000
|
+
if (remaining < 1) {
|
|
2001
|
+
break;
|
|
2002
|
+
}
|
|
2003
|
+
const limit = Math.min(pageSize, remaining);
|
|
2004
|
+
// A server cursor carries its own position; re-sending the computed offset
|
|
2005
|
+
// alongside it is what makes token pagers skip rows.
|
|
2006
|
+
const sentOffset = cursor === undefined ? startOffset + page * pageSize : undefined;
|
|
2007
|
+
const response = await fetchPage({
|
|
2008
|
+
limit,
|
|
2009
|
+
offset: sentOffset,
|
|
2010
|
+
cursorParams: cursor?.params,
|
|
2011
|
+
});
|
|
2012
|
+
const pageFeatures = extractOgcFeatures(response);
|
|
2013
|
+
if (pageFeatures.length === 0) {
|
|
2014
|
+
break;
|
|
2015
|
+
}
|
|
2016
|
+
const pageKey = ogcPageIdentityKey(pageFeatures);
|
|
2017
|
+
if (pageKey !== undefined) {
|
|
2018
|
+
if (seenPageKeys.has(pageKey)) {
|
|
2019
|
+
break;
|
|
2020
|
+
}
|
|
2021
|
+
seenPageKeys.add(pageKey);
|
|
2022
|
+
}
|
|
2023
|
+
yield pageFeatures;
|
|
2024
|
+
emitted += pageFeatures.length;
|
|
2025
|
+
const next = ogcNextItemsCursor(response.links, sentOffset);
|
|
2026
|
+
if (next !== undefined) {
|
|
2027
|
+
if (seenCursorKeys.has(next.key)) {
|
|
2028
|
+
break;
|
|
2029
|
+
}
|
|
2030
|
+
seenCursorKeys.add(next.key);
|
|
2031
|
+
cursor = next;
|
|
2032
|
+
continue;
|
|
2033
|
+
}
|
|
2034
|
+
if (cursor !== undefined) {
|
|
2035
|
+
// Link-driven drain and the server stopped advertising a next link:
|
|
2036
|
+
// that is the spec's end-of-results signal.
|
|
2037
|
+
break;
|
|
2038
|
+
}
|
|
2039
|
+
if (pageFeatures.length < limit) {
|
|
2040
|
+
break;
|
|
2041
|
+
}
|
|
2042
|
+
}
|
|
2043
|
+
}
|
|
2044
|
+
/**
|
|
2045
|
+
* Merge one {@link drainOgcItemPages} page request onto the caller's request.
|
|
2046
|
+
* Cursor params ride on `extraParams`, which the OGC wire layer serializes
|
|
2047
|
+
* before the typed fields, so the caller's `filter` / `bbox` / `crs` still win
|
|
2048
|
+
* and only the paging state comes from the server.
|
|
2049
|
+
*/
|
|
2050
|
+
function withOgcItemsPage(request, page) {
|
|
2051
|
+
return {
|
|
2052
|
+
...request,
|
|
2053
|
+
limit: page.limit,
|
|
2054
|
+
offset: page.offset,
|
|
2055
|
+
...(page.cursorParams ? { extraParams: { ...request.extraParams, ...page.cursorParams } } : {}),
|
|
2056
|
+
};
|
|
2057
|
+
}
|
|
2058
|
+
/**
|
|
2059
|
+
* Resolve the server's next-page cursor from an `/items` response. Only the
|
|
2060
|
+
* link's query params are replayed (not its host or path), so a next link
|
|
2061
|
+
* rewritten by a proxy — or emitted with the server's internal hostname —
|
|
2062
|
+
* still pages correctly. Returns `undefined` when no `rel="next"` link carries
|
|
2063
|
+
* usable paging state, which is what selects the offset fallback.
|
|
2064
|
+
*
|
|
2065
|
+
* @param sentOffset the `offset` sent for the page that produced these links,
|
|
2066
|
+
* or `undefined` when a cursor already drove that request. Used to spot an
|
|
2067
|
+
* `offset` on the link that merely echoes the request just answered.
|
|
2068
|
+
*/
|
|
2069
|
+
function ogcNextItemsCursor(links, sentOffset) {
|
|
2070
|
+
if (!links) {
|
|
2071
|
+
return undefined;
|
|
2072
|
+
}
|
|
2073
|
+
for (const link of links) {
|
|
2074
|
+
if (link.rel !== "next" || typeof link.href !== "string") {
|
|
2075
|
+
continue;
|
|
2076
|
+
}
|
|
2077
|
+
// An HTML alternate of the next page carries the same cursor but is not
|
|
2078
|
+
// the representation we are draining; prefer a data link when both exist.
|
|
2079
|
+
if (typeof link.type === "string" && link.type.includes("html")) {
|
|
2080
|
+
continue;
|
|
2081
|
+
}
|
|
2082
|
+
const cursor = ogcCursorFromHref(link.href, sentOffset);
|
|
2083
|
+
if (cursor !== undefined) {
|
|
2084
|
+
return cursor;
|
|
2085
|
+
}
|
|
2086
|
+
}
|
|
2087
|
+
return undefined;
|
|
2088
|
+
}
|
|
2089
|
+
/** Fold a query-param name to its comparison form (case / `-` / `_` insensitive). */
|
|
2090
|
+
function ogcParamKey(name) {
|
|
2091
|
+
let folded = "";
|
|
2092
|
+
for (const char of name) {
|
|
2093
|
+
if (char === "-" || char === "_")
|
|
2094
|
+
continue;
|
|
2095
|
+
folded += char.toLowerCase();
|
|
2096
|
+
}
|
|
2097
|
+
return folded;
|
|
2098
|
+
}
|
|
2099
|
+
/**
|
|
2100
|
+
* Lift the paging state off one `rel="next"` href.
|
|
2101
|
+
*
|
|
2102
|
+
* Servers routinely build the next link by preserving the query string of the
|
|
2103
|
+
* request they just answered and appending their cursor, so the link can carry
|
|
2104
|
+
* both a stale `offset=0` and a live `token=…`. Replaying both is exactly the
|
|
2105
|
+
* mis-paging this drain exists to prevent — a server that honors `offset`
|
|
2106
|
+
* alongside its own token re-reads or skips a page. Two rules keep the two
|
|
2107
|
+
* paging styles apart:
|
|
2108
|
+
*
|
|
2109
|
+
* 1. an opaque cursor on the link (`token`, `cursor`, `next`, `page`, …) owns
|
|
2110
|
+
* the read position, so every `offset` / `startindex` on that link is
|
|
2111
|
+
* dropped;
|
|
2112
|
+
* 2. otherwise an `offset` / `startindex` that merely repeats the offset just
|
|
2113
|
+
* sent cannot advance the drain, so it is dropped too — and if that leaves
|
|
2114
|
+
* the link with nothing, the drain falls back to offset arithmetic.
|
|
2115
|
+
*
|
|
2116
|
+
* A genuine offset link (`offset=10` after requesting `offset=0`) is kept and
|
|
2117
|
+
* drives the next page, which is what pygeoapi / ldproxy deployments emit.
|
|
2118
|
+
*/
|
|
2119
|
+
function ogcCursorFromHref(href, sentOffset) {
|
|
2120
|
+
let url;
|
|
2121
|
+
try {
|
|
2122
|
+
url = new URL(href, OGC_NEXT_LINK_PARSE_BASE);
|
|
2123
|
+
}
|
|
2124
|
+
catch {
|
|
2125
|
+
return undefined;
|
|
2126
|
+
}
|
|
2127
|
+
const candidates = [];
|
|
2128
|
+
let hasOpaqueCursor = false;
|
|
2129
|
+
for (const [key, value] of url.searchParams) {
|
|
2130
|
+
if (OGC_NEXT_LINK_RESERVED_PARAMS.has(key)) {
|
|
2131
|
+
continue;
|
|
2132
|
+
}
|
|
2133
|
+
if (OGC_CURSOR_PARAM_NAMES.has(ogcParamKey(key))) {
|
|
2134
|
+
hasOpaqueCursor = true;
|
|
2135
|
+
}
|
|
2136
|
+
candidates.push([key, value]);
|
|
2137
|
+
}
|
|
2138
|
+
const params = {};
|
|
2139
|
+
let hasPagingState = false;
|
|
2140
|
+
for (const [key, value] of candidates) {
|
|
2141
|
+
const folded = ogcParamKey(key);
|
|
2142
|
+
if (OGC_OFFSET_PARAM_NAMES.has(folded)) {
|
|
2143
|
+
if (hasOpaqueCursor) {
|
|
2144
|
+
continue;
|
|
2145
|
+
}
|
|
2146
|
+
if (sentOffset !== undefined && Number(value) === sentOffset) {
|
|
2147
|
+
continue;
|
|
2148
|
+
}
|
|
2149
|
+
hasPagingState = true;
|
|
2150
|
+
}
|
|
2151
|
+
else if (OGC_CURSOR_PARAM_NAMES.has(folded)) {
|
|
2152
|
+
hasPagingState = true;
|
|
2153
|
+
}
|
|
2154
|
+
params[key] = value;
|
|
2155
|
+
}
|
|
2156
|
+
// Everything that survived is an echo of the query we just sent (`bbox`,
|
|
2157
|
+
// `filter`, `sortby`, …): the link advertises no forward progress, so the
|
|
2158
|
+
// offset arithmetic below stays in charge rather than re-reading this page.
|
|
2159
|
+
if (!hasPagingState) {
|
|
2160
|
+
return undefined;
|
|
2161
|
+
}
|
|
2162
|
+
const entries = Object.entries(params).sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0));
|
|
2163
|
+
return { key: entries.map(([key, value]) => `${key}=${value}`).join("&"), params };
|
|
2164
|
+
}
|
|
2165
|
+
/**
|
|
2166
|
+
* Cheap identity signature for one `/items` page, used to detect a server that
|
|
2167
|
+
* replays a page it already returned. Returns `undefined` when any feature
|
|
2168
|
+
* lacks a GeoJSON `id`, in which case the drain simply skips the check rather
|
|
2169
|
+
* than serializing whole geometries.
|
|
2170
|
+
*/
|
|
2171
|
+
function ogcPageIdentityKey(features) {
|
|
2172
|
+
const ids = [];
|
|
2173
|
+
for (const feature of features) {
|
|
2174
|
+
const id = feature?.id;
|
|
2175
|
+
if (typeof id !== "string" && typeof id !== "number") {
|
|
2176
|
+
return undefined;
|
|
2177
|
+
}
|
|
2178
|
+
ids.push(String(id));
|
|
2179
|
+
}
|
|
2180
|
+
return JSON.stringify(ids);
|
|
2181
|
+
}
|
|
1977
2182
|
function isFinitePositiveInteger(value) {
|
|
1978
2183
|
return typeof value === "number" && Number.isFinite(value) && Math.trunc(value) > 0;
|
|
1979
2184
|
}
|
package/core/types.d.ts
CHANGED
|
@@ -540,9 +540,33 @@ export interface HonuaClientOptions {
|
|
|
540
540
|
transport?: HonuaTransport;
|
|
541
541
|
}
|
|
542
542
|
export interface HonuaRetryOptions {
|
|
543
|
+
/**
|
|
544
|
+
* Maximum number of transient-failure retry attempts after the initial
|
|
545
|
+
* request. Retry is entirely opt-in: values below `1` (including the default
|
|
546
|
+
* `0`) disable the retry loop, so no status, network, or timeout failure is
|
|
547
|
+
* replayed.
|
|
548
|
+
*
|
|
549
|
+
* This option does not govern the authentication replay. When an
|
|
550
|
+
* {@link HonuaClientOptions.auth} provider is configured, a replay-safe
|
|
551
|
+
* request (`GET` / `HEAD` / `PUT` / `DELETE`) answered with `401` or `403` is
|
|
552
|
+
* still reissued once after a forced credential refresh, even with retries
|
|
553
|
+
* disabled. Instrumentation that counts requests should expect that one extra
|
|
554
|
+
* attempt.
|
|
555
|
+
*/
|
|
543
556
|
maxRetries?: number;
|
|
557
|
+
/** First backoff delay in milliseconds; doubles per attempt. Default: `100`. */
|
|
544
558
|
baseDelayMs?: number;
|
|
559
|
+
/** Upper bound on any single backoff delay, including a server `Retry-After`. Default: `2000`. */
|
|
545
560
|
maxDelayMs?: number;
|
|
561
|
+
/**
|
|
562
|
+
* HTTP statuses the loop replays on replay-safe methods
|
|
563
|
+
* (`GET` / `HEAD` / `PUT` / `DELETE`). Default: `[429, 502, 503, 504]` — the
|
|
564
|
+
* statuses that explicitly invite the same request again. This is
|
|
565
|
+
* intentionally narrower than the broader `408, 429, 500, 502, 503, 504` set
|
|
566
|
+
* that {@link HonuaHttpError} classifies as transient `retryable` metadata:
|
|
567
|
+
* that classification is descriptive and never initiates a retry. Pass the
|
|
568
|
+
* broader set here to opt into replaying `408` / `500`.
|
|
569
|
+
*/
|
|
546
570
|
retryStatuses?: readonly number[];
|
|
547
571
|
}
|
|
548
572
|
export interface HonuaCompatibilityRequest {
|
|
@@ -1118,6 +1142,17 @@ export interface HonuaOgcLink {
|
|
|
1118
1142
|
rel?: string;
|
|
1119
1143
|
type?: string;
|
|
1120
1144
|
title?: string;
|
|
1145
|
+
/**
|
|
1146
|
+
* HTTP method the link target expects. STAC API uses it on the landing
|
|
1147
|
+
* page (`rel="search"` advertised for `GET` and/or `POST`) and on
|
|
1148
|
+
* pagination links (a `POST` `rel="next"` carries its cursor in `body`
|
|
1149
|
+
* rather than on the href query string).
|
|
1150
|
+
*/
|
|
1151
|
+
method?: string;
|
|
1152
|
+
/** Request-body members a `POST` link contributes to the next request. */
|
|
1153
|
+
body?: Record<string, unknown>;
|
|
1154
|
+
/** When true, `body` merges over the originating request body. */
|
|
1155
|
+
merge?: boolean;
|
|
1121
1156
|
}
|
|
1122
1157
|
/** Response from the OGC API landing page (`/ogc/features`). */
|
|
1123
1158
|
export interface HonuaOgcLandingResponse {
|
|
@@ -1363,12 +1398,21 @@ export interface HonuaOgcProcessJobStatus {
|
|
|
1363
1398
|
};
|
|
1364
1399
|
}
|
|
1365
1400
|
/**
|
|
1366
|
-
* Process-execution request envelope
|
|
1367
|
-
*
|
|
1368
|
-
*
|
|
1369
|
-
*
|
|
1370
|
-
*
|
|
1371
|
-
*
|
|
1401
|
+
* Process-execution request envelope for `POST /processes/{id}/execution`
|
|
1402
|
+
* (OGC API — Processes Part 1: Core §7.11).
|
|
1403
|
+
*
|
|
1404
|
+
* `mode` selects the RFC 7240 preference the SDK sends:
|
|
1405
|
+
*
|
|
1406
|
+
* - `"async"` → `Prefer: respond-async`. A conformant server answers `201` with
|
|
1407
|
+
* a `Location` header and a `statusInfo` body.
|
|
1408
|
+
* - `"sync"` → no `Prefer` header, and the caller asserts the process declares
|
|
1409
|
+
* `sync-execute`. A conformant server answers `200` with the results document.
|
|
1410
|
+
* - `"auto"` (default) → no `Prefer` header; whichever shape the server returns
|
|
1411
|
+
* is adapted onto the same `IJobRun` surface.
|
|
1412
|
+
*
|
|
1413
|
+
* Core defines no header that *forces* synchronous execution, so `"sync"` is a
|
|
1414
|
+
* preference plus a capability assertion, never a guarantee: a server that
|
|
1415
|
+
* answers a `"sync"` request with a job still yields a pollable `IJobRun`.
|
|
1372
1416
|
*/
|
|
1373
1417
|
export interface OgcProcessExecuteRequest {
|
|
1374
1418
|
processId: string;
|
|
@@ -1379,19 +1423,76 @@ export interface OgcProcessExecuteRequest {
|
|
|
1379
1423
|
mediaType?: string;
|
|
1380
1424
|
};
|
|
1381
1425
|
}>;
|
|
1382
|
-
/**
|
|
1383
|
-
mode?: "async" | "auto";
|
|
1426
|
+
/** Execution preference; see the interface docs. Defaults to `"auto"`. */
|
|
1427
|
+
mode?: "sync" | "async" | "auto";
|
|
1428
|
+
/**
|
|
1429
|
+
* The process's advertised `jobControlOptions` (from `describe()` or a
|
|
1430
|
+
* discovery snapshot). Supplied — or already memoized from a `describe()` on
|
|
1431
|
+
* the same handle — it lets `mode` be gated against what the process actually
|
|
1432
|
+
* declares, so an undeclared `sync-execute` / `async-execute` fails closed with
|
|
1433
|
+
* `HonuaCapabilityNotSupportedError` instead of being posted blindly.
|
|
1434
|
+
*/
|
|
1435
|
+
jobControlOptions?: readonly string[];
|
|
1384
1436
|
headers?: HeadersInit;
|
|
1385
1437
|
signal?: AbortSignal;
|
|
1438
|
+
/**
|
|
1439
|
+
* Raw endpoint path prefix (defaults to the Honua facade `/ogc/processes`). A
|
|
1440
|
+
* raw third-party Processes service root discovered by `discoverOgcProcesses()`
|
|
1441
|
+
* threads its service-root path here so the execution route resolves against
|
|
1442
|
+
* the advertised layout instead of the facade.
|
|
1443
|
+
*/
|
|
1444
|
+
basePath?: string;
|
|
1445
|
+
}
|
|
1446
|
+
/**
|
|
1447
|
+
* Request envelope for the OGC API Processes job routes (status, results,
|
|
1448
|
+
* dismiss). Carries the same `basePath` seam as the metadata requests so a job
|
|
1449
|
+
* created against a raw service root keeps polling that root.
|
|
1450
|
+
*/
|
|
1451
|
+
export interface OgcProcessJobRequest {
|
|
1452
|
+
jobId: string;
|
|
1453
|
+
signal?: AbortSignal;
|
|
1454
|
+
responseFormat?: string;
|
|
1455
|
+
extraParams?: Record<string, string | number | boolean>;
|
|
1456
|
+
/** Raw endpoint path prefix (defaults to the Honua facade `/ogc/processes`). */
|
|
1457
|
+
basePath?: string;
|
|
1458
|
+
/**
|
|
1459
|
+
* Server-advertised route for this job resource, already reduced to a
|
|
1460
|
+
* same-origin request path. When present it replaces the Core-templated
|
|
1461
|
+
* `{basePath}/jobs/{jobId}[/results]` route so the lifecycle follows the
|
|
1462
|
+
* server's own `Location` header and `links[]` rather than a guessed path.
|
|
1463
|
+
*/
|
|
1464
|
+
routePath?: string;
|
|
1386
1465
|
}
|
|
1387
|
-
/**
|
|
1466
|
+
/**
|
|
1467
|
+
* Outcome of `POST /processes/{id}/execution`.
|
|
1468
|
+
*
|
|
1469
|
+
* The same envelope carries both Core response shapes so one typed surface
|
|
1470
|
+
* covers a raw third-party server and the Honua facade:
|
|
1471
|
+
*
|
|
1472
|
+
* - asynchronous — `jobID` names the created job and `synchronous` is `false`.
|
|
1473
|
+
* - synchronous — the server answered `200` with the results document inline.
|
|
1474
|
+
* `jobID` is `""` (Core assigns no job identifier to a synchronous
|
|
1475
|
+
* execution), `status` is `"successful"`, `synchronous` is `true`, and
|
|
1476
|
+
* `results` holds the document.
|
|
1477
|
+
*/
|
|
1388
1478
|
export interface HonuaOgcProcessJobAccepted {
|
|
1479
|
+
/** Server-assigned job id; `""` for a synchronous execution (no job exists). */
|
|
1389
1480
|
jobID: string;
|
|
1390
1481
|
status: OgcProcessStatus;
|
|
1391
1482
|
processID?: string;
|
|
1392
1483
|
links?: HonuaOgcLink[];
|
|
1393
1484
|
/** Initial (often `accepted` or `running`) status payload, when reported. */
|
|
1394
1485
|
statusInfo?: HonuaOgcProcessJobStatus;
|
|
1486
|
+
/**
|
|
1487
|
+
* Same-origin request path derived from the async `Location` header (or the
|
|
1488
|
+
* accepted document's `self` link). Polling follows it instead of templating
|
|
1489
|
+
* `{basePath}/jobs/{jobId}`.
|
|
1490
|
+
*/
|
|
1491
|
+
statusPath?: string;
|
|
1492
|
+
/** Results document, populated only when `synchronous` is `true`. */
|
|
1493
|
+
results?: HonuaOgcProcessJobResults;
|
|
1494
|
+
/** `true` when the server answered inline with the results document. */
|
|
1495
|
+
synchronous?: boolean;
|
|
1395
1496
|
}
|
|
1396
1497
|
/**
|
|
1397
1498
|
* Result document returned from `/jobs/{jobId}/results`. OGC API Processes
|
|
@@ -1422,6 +1523,19 @@ export interface StacSearchRequest {
|
|
|
1422
1523
|
* advertise an opaque `?next=…` token instead of `offset`.
|
|
1423
1524
|
*/
|
|
1424
1525
|
next?: string;
|
|
1526
|
+
/**
|
|
1527
|
+
* Opaque continuation query params captured verbatim from a server
|
|
1528
|
+
* `rel="next"` link whose cursor is neither `offset` nor `next` (pgstac /
|
|
1529
|
+
* stac-fastapi emit `?token=next:…`). Merged over the serialized search
|
|
1530
|
+
* params so the next page is requested exactly the way the server asked.
|
|
1531
|
+
*/
|
|
1532
|
+
nextParams?: Readonly<Record<string, string>>;
|
|
1533
|
+
/**
|
|
1534
|
+
* Opaque continuation body members captured from a `POST` `rel="next"`
|
|
1535
|
+
* link (`{ "method": "POST", "body": { "token": "next:…" }, "merge": true }`).
|
|
1536
|
+
* Merged over the serialized `POST /search` body.
|
|
1537
|
+
*/
|
|
1538
|
+
nextBody?: Readonly<Record<string, unknown>>;
|
|
1425
1539
|
/** Subset of asset / item properties to return. */
|
|
1426
1540
|
fields?: {
|
|
1427
1541
|
include?: readonly string[];
|