@honua/react 0.0.20-alpha.0 → 0.1.1-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 +231 -0
- package/contract/discovery.d.ts.map +1 -0
- package/contract/discovery.js +554 -0
- package/contract/edit-sketch.d.ts +8 -0
- package/contract/edit-sketch.d.ts.map +1 -1
- package/contract/edit-sketch.js +15 -0
- package/contract/edit-snapping.d.ts +160 -0
- package/contract/edit-snapping.d.ts.map +1 -0
- package/contract/edit-snapping.js +675 -0
- package/contract/generated/projjson-v0.7-crs-validator.d.ts +35 -0
- package/contract/generated/projjson-v0.7-crs-validator.d.ts.map +1 -0
- package/contract/generated/projjson-v0.7-crs-validator.js +3941 -0
- package/contract/index.d.ts +12 -1
- package/contract/index.d.ts.map +1 -1
- package/contract/index.js +4 -0
- package/contract/pmtiles.d.ts +193 -0
- package/contract/pmtiles.d.ts.map +1 -0
- package/contract/pmtiles.js +195 -0
- package/contract/protocol-module.d.ts +70 -0
- package/contract/protocol-module.d.ts.map +1 -0
- package/contract/protocol-module.js +1 -0
- package/contract/schema-envelope.d.ts +26 -0
- package/contract/schema-envelope.d.ts.map +1 -0
- package/contract/schema-envelope.js +25 -0
- package/contract/schema.d.ts +582 -0
- package/contract/schema.d.ts.map +1 -0
- package/contract/schema.js +2483 -0
- package/contract/source-capability-support.d.ts +6 -0
- package/contract/source-capability-support.d.ts.map +1 -0
- package/contract/source-capability-support.js +154 -0
- package/contract/source.d.ts +57 -15
- package/contract/source.d.ts.map +1 -1
- package/contract/source.js +368 -65
- package/contract/types.d.ts +171 -6
- package/contract/types.d.ts.map +1 -1
- package/contract/types.js +14 -14
- package/core/auth/client-credentials-provider.d.ts +51 -0
- package/core/auth/client-credentials-provider.d.ts.map +1 -0
- package/core/auth/client-credentials-provider.js +115 -0
- package/core/auth/credential-store.d.ts +52 -0
- package/core/auth/credential-store.d.ts.map +1 -0
- package/core/auth/credential-store.js +82 -0
- package/core/auth/index.d.ts +23 -0
- package/core/auth/index.d.ts.map +1 -0
- package/core/auth/index.js +20 -0
- package/core/auth/oauth2-provider.d.ts +114 -0
- package/core/auth/oauth2-provider.d.ts.map +1 -0
- package/core/auth/oauth2-provider.js +408 -0
- package/core/auth/pkce.d.ts +25 -0
- package/core/auth/pkce.d.ts.map +1 -0
- package/core/auth/pkce.js +46 -0
- package/core/auth/static-providers.d.ts +20 -0
- package/core/auth/static-providers.d.ts.map +1 -0
- package/core/auth/static-providers.js +26 -0
- package/core/auth/token-endpoint.d.ts +40 -0
- package/core/auth/token-endpoint.d.ts.map +1 -0
- package/core/auth/token-endpoint.js +133 -0
- package/core/auth/types.d.ts +144 -0
- package/core/auth/types.d.ts.map +1 -0
- package/core/auth/types.js +8 -0
- package/core/bounded-json.d.ts +4 -0
- package/core/bounded-json.d.ts.map +1 -0
- package/core/bounded-json.js +38 -0
- package/core/cache-state.d.ts +9 -0
- package/core/cache-state.d.ts.map +1 -1
- package/core/cache-state.js +10 -0
- package/core/capabilities-xml.d.ts +33 -0
- package/core/capabilities-xml.d.ts.map +1 -0
- package/core/capabilities-xml.js +331 -0
- package/core/client.d.ts +35 -2
- package/core/client.d.ts.map +1 -1
- package/core/client.js +167 -29
- package/core/error-classifications.d.ts +153 -0
- package/core/error-classifications.d.ts.map +1 -0
- package/core/error-classifications.js +140 -0
- package/core/error-code-registry.d.ts +158 -0
- package/core/error-code-registry.d.ts.map +1 -0
- package/core/error-code-registry.js +143 -0
- package/core/error-envelope.d.ts +68 -0
- package/core/error-envelope.d.ts.map +1 -0
- package/core/error-envelope.js +357 -0
- package/core/errors.d.ts +73 -25
- package/core/errors.d.ts.map +1 -1
- package/core/errors.js +113 -46
- package/core/geoparquet-sql.d.ts +190 -0
- package/core/geoparquet-sql.d.ts.map +1 -0
- package/core/geoparquet-sql.js +368 -0
- package/core/odata-key-path.d.ts +12 -0
- package/core/odata-key-path.d.ts.map +1 -0
- package/core/odata-key-path.js +28 -0
- package/core/odata-write-codec.d.ts +66 -0
- package/core/odata-write-codec.d.ts.map +1 -0
- package/core/odata-write-codec.js +1083 -0
- package/core/odata.d.ts +59 -7
- package/core/odata.d.ts.map +1 -1
- package/core/odata.js +431 -99
- package/core/ogc-endpoint-layout.d.ts +70 -0
- package/core/ogc-endpoint-layout.d.ts.map +1 -0
- package/core/ogc-endpoint-layout.js +173 -0
- package/core/ogc-features.d.ts.map +1 -1
- package/core/ogc-features.js +35 -17
- package/core/ogc-maps.d.ts +7 -0
- package/core/ogc-maps.d.ts.map +1 -1
- package/core/ogc-maps.js +46 -7
- package/core/ogc-processes.d.ts +2 -1
- package/core/ogc-processes.d.ts.map +1 -1
- package/core/ogc-processes.js +33 -5
- package/core/ogc-records.d.ts +9 -0
- package/core/ogc-records.d.ts.map +1 -1
- package/core/ogc-records.js +78 -29
- package/core/ogc-tiles.d.ts +10 -1
- package/core/ogc-tiles.d.ts.map +1 -1
- package/core/ogc-tiles.js +56 -17
- package/core/path-utils.d.ts +9 -0
- package/core/path-utils.d.ts.map +1 -1
- package/core/path-utils.js +19 -0
- package/core/protocol-transport.d.ts +1 -0
- package/core/protocol-transport.d.ts.map +1 -1
- package/core/request-pipeline.d.ts.map +1 -1
- package/core/request-pipeline.js +1 -1
- package/core/spatial-filter.d.ts +8 -4
- package/core/spatial-filter.d.ts.map +1 -1
- package/core/spatial-filter.js +206 -16
- package/core/stac-static.d.ts +55 -0
- package/core/stac-static.d.ts.map +1 -0
- package/core/stac-static.js +386 -0
- package/core/stac.d.ts +12 -0
- package/core/stac.d.ts.map +1 -1
- package/core/stac.js +45 -15
- package/core/surfaces.d.ts +13 -0
- package/core/surfaces.d.ts.map +1 -1
- package/core/surfaces.js +127 -16
- package/core/types.d.ts +174 -7
- package/core/types.d.ts.map +1 -1
- package/core/wfs-capabilities.d.ts +12 -0
- package/core/wfs-capabilities.d.ts.map +1 -1
- package/core/wfs-capabilities.js +22 -3
- package/core/wfs.d.ts +13 -0
- package/core/wfs.d.ts.map +1 -1
- package/core/wfs.js +35 -5
- package/core/wms-capabilities.d.ts +56 -84
- package/core/wms-capabilities.d.ts.map +1 -1
- package/core/wms-capabilities.js +212 -509
- package/core/wmts-capabilities.d.ts +62 -62
- package/core/wmts-capabilities.d.ts.map +1 -1
- package/core/wmts-capabilities.js +224 -371
- package/esri-compat/editor.d.ts +8 -0
- package/esri-compat/editor.d.ts.map +1 -1
- package/esri-compat/editor.js +13 -0
- package/esri-compat/feature-layer.d.ts +2 -0
- package/esri-compat/feature-layer.d.ts.map +1 -1
- package/esri-compat/feature-layer.js +1 -0
- package/esri-compat/identity-manager.d.ts +43 -0
- package/esri-compat/identity-manager.d.ts.map +1 -1
- package/esri-compat/identity-manager.js +131 -1
- package/esri-compat/layer-list.d.ts +11 -0
- package/esri-compat/layer-list.d.ts.map +1 -1
- package/esri-compat/layer-list.js +49 -0
- package/esri-compat/legend.d.ts +10 -0
- package/esri-compat/legend.d.ts.map +1 -1
- package/esri-compat/legend.js +27 -0
- package/esri-compat/oauth-info.d.ts +15 -0
- package/esri-compat/oauth-info.d.ts.map +1 -1
- package/esri-compat/oauth-info.js +27 -0
- package/esri-compat/portal.d.ts +197 -0
- package/esri-compat/portal.d.ts.map +1 -0
- package/esri-compat/portal.js +251 -0
- package/esri-compat/renderer-objects.d.ts +40 -0
- package/esri-compat/renderer-objects.d.ts.map +1 -0
- package/esri-compat/renderer-objects.js +62 -0
- package/esri-compat/sketch.d.ts +43 -0
- package/esri-compat/sketch.d.ts.map +1 -1
- package/esri-compat/sketch.js +46 -0
- package/esri-compat/snapping.d.ts +43 -0
- package/esri-compat/snapping.d.ts.map +1 -0
- package/esri-compat/snapping.js +45 -0
- package/esri-compat/url.d.ts.map +1 -1
- package/esri-compat/url.js +1 -1
- package/esri-compat/widget-host.d.ts +84 -0
- package/esri-compat/widget-host.d.ts.map +1 -0
- package/esri-compat/widget-host.js +159 -0
- package/filter-registry/index.d.ts +145 -0
- package/filter-registry/index.d.ts.map +1 -0
- package/filter-registry/index.js +531 -0
- package/generated-app/errors.d.ts +18 -2
- package/generated-app/errors.d.ts.map +1 -1
- package/generated-app/errors.js +44 -11
- package/map/automatic-mount-integration.d.ts +134 -0
- package/map/automatic-mount-integration.d.ts.map +1 -0
- package/map/automatic-mount-integration.js +305 -0
- package/map/automatic-source-strategy.d.ts +125 -0
- package/map/automatic-source-strategy.d.ts.map +1 -0
- package/map/automatic-source-strategy.js +494 -0
- package/map/data-to-map-bridge.d.ts +293 -0
- package/map/data-to-map-bridge.d.ts.map +1 -0
- package/map/data-to-map-bridge.js +817 -0
- package/map/geojson-projection.d.ts +33 -0
- package/map/geojson-projection.d.ts.map +1 -0
- package/map/geojson-projection.js +118 -0
- package/map/index.d.ts +14 -0
- package/map/index.d.ts.map +1 -1
- package/map/index.js +8 -0
- package/map/raster-source-spec.d.ts +30 -0
- package/map/raster-source-spec.d.ts.map +1 -0
- package/map/raster-source-spec.js +190 -0
- package/map/raster-source-strategy.d.ts +65 -0
- package/map/raster-source-strategy.d.ts.map +1 -0
- package/map/raster-source-strategy.js +232 -0
- package/map/source-to-maplibre.d.ts +91 -0
- package/map/source-to-maplibre.d.ts.map +1 -0
- package/map/source-to-maplibre.js +454 -0
- package/map/temporal-playback.d.ts +139 -0
- package/map/temporal-playback.d.ts.map +1 -0
- package/map/temporal-playback.js +247 -0
- package/package.json +19 -1
- package/query-planner/canonical.d.ts +7 -0
- package/query-planner/canonical.d.ts.map +1 -0
- package/query-planner/canonical.js +145 -0
- package/query-planner/cql2-json.d.ts +17 -0
- package/query-planner/cql2-json.d.ts.map +1 -0
- package/query-planner/cql2-json.js +646 -0
- package/query-planner/diagnostics.d.ts +35 -0
- package/query-planner/diagnostics.d.ts.map +1 -0
- package/query-planner/diagnostics.js +373 -0
- package/query-planner/duckdb.d.ts +79 -0
- package/query-planner/duckdb.d.ts.map +1 -0
- package/query-planner/duckdb.js +758 -0
- package/query-planner/execution-context.d.ts +5 -0
- package/query-planner/execution-context.d.ts.map +1 -0
- package/query-planner/execution-context.js +50 -0
- package/query-planner/executor.d.ts +5 -0
- package/query-planner/executor.d.ts.map +1 -0
- package/query-planner/executor.js +223 -0
- package/query-planner/geoservices.d.ts +91 -0
- package/query-planner/geoservices.d.ts.map +1 -0
- package/query-planner/geoservices.js +667 -0
- package/query-planner/grpc.d.ts +100 -0
- package/query-planner/grpc.d.ts.map +1 -0
- package/query-planner/grpc.js +736 -0
- package/query-planner/index.d.ts +44 -0
- package/query-planner/index.d.ts.map +1 -0
- package/query-planner/index.js +30 -0
- package/query-planner/ir.d.ts +20 -0
- package/query-planner/ir.d.ts.map +1 -0
- package/query-planner/ir.js +417 -0
- package/query-planner/local-aggregate.d.ts +5 -0
- package/query-planner/local-aggregate.d.ts.map +1 -0
- package/query-planner/local-aggregate.js +126 -0
- package/query-planner/odata.d.ts +48 -0
- package/query-planner/odata.d.ts.map +1 -0
- package/query-planner/odata.js +662 -0
- package/query-planner/ogc-compiler.d.ts +55 -0
- package/query-planner/ogc-compiler.d.ts.map +1 -0
- package/query-planner/ogc-compiler.js +480 -0
- package/query-planner/ogc-features.d.ts +53 -0
- package/query-planner/ogc-features.d.ts.map +1 -0
- package/query-planner/ogc-features.js +617 -0
- package/query-planner/planner.d.ts +17 -0
- package/query-planner/planner.d.ts.map +1 -0
- package/query-planner/planner.js +818 -0
- package/query-planner/resource.d.ts +94 -0
- package/query-planner/resource.d.ts.map +1 -0
- package/query-planner/resource.js +526 -0
- package/query-planner/semantic-canonical.d.ts +15 -0
- package/query-planner/semantic-canonical.d.ts.map +1 -0
- package/query-planner/semantic-canonical.js +62 -0
- package/query-planner/semantic-compat.d.ts +10 -0
- package/query-planner/semantic-compat.d.ts.map +1 -0
- package/query-planner/semantic-compat.js +35 -0
- package/query-planner/semantic-compiler.d.ts +64 -0
- package/query-planner/semantic-compiler.d.ts.map +1 -0
- package/query-planner/semantic-compiler.js +81 -0
- package/query-planner/semantic-literals.d.ts +30 -0
- package/query-planner/semantic-literals.d.ts.map +1 -0
- package/query-planner/semantic-literals.js +133 -0
- package/query-planner/semantic-types.d.ts +265 -0
- package/query-planner/semantic-types.d.ts.map +1 -0
- package/query-planner/semantic-types.js +1 -0
- package/query-planner/semantic.d.ts +54 -0
- package/query-planner/semantic.d.ts.map +1 -0
- package/query-planner/semantic.js +1421 -0
- package/query-planner/types.d.ts +560 -0
- package/query-planner/types.d.ts.map +1 -0
- package/query-planner/types.js +49 -0
- package/query-planner/wfs-semantic.d.ts +58 -0
- package/query-planner/wfs-semantic.d.ts.map +1 -0
- package/query-planner/wfs-semantic.js +547 -0
- package/query-planner/wfs.d.ts +6 -0
- package/query-planner/wfs.d.ts.map +1 -0
- package/query-planner/wfs.js +71 -0
- package/react/external-map.d.ts +67 -0
- package/react/external-map.d.ts.map +1 -0
- package/react/external-map.js +63 -0
- package/react/honua-map.d.ts.map +1 -1
- package/react/honua-map.js +3 -2
- package/react/honua-source-layer.d.ts +110 -0
- package/react/honua-source-layer.d.ts.map +1 -0
- package/react/honua-source-layer.js +101 -0
- package/react/hooks.d.ts +28 -0
- package/react/hooks.d.ts.map +1 -1
- package/react/hooks.js +26 -4
- package/react/index.d.ts +8 -0
- package/react/index.d.ts.map +1 -1
- package/react/index.js +5 -0
- package/react/query-cache.d.ts +26 -4
- package/react/query-cache.d.ts.map +1 -1
- package/react/query-cache.js +51 -8
- package/react/selection.d.ts +177 -0
- package/react/selection.d.ts.map +1 -0
- package/react/selection.js +0 -0
- package/react/use-mounted-source.d.ts +69 -0
- package/react/use-mounted-source.d.ts.map +1 -0
- package/react/use-mounted-source.js +346 -0
- package/runtime/edit-snapping.d.ts +107 -0
- package/runtime/edit-snapping.d.ts.map +1 -0
- package/runtime/edit-snapping.js +190 -0
- package/runtime/errors.d.ts +3 -2
- package/runtime/errors.d.ts.map +1 -1
- package/runtime/errors.js +19 -2
- package/runtime/index.d.ts +8 -2
- package/runtime/index.d.ts.map +1 -1
- package/runtime/index.js +4 -4
- package/runtime/load-package.d.ts.map +1 -1
- package/runtime/load-package.js +8 -0
- package/runtime/map-package-validation.d.ts +0 -13
- package/runtime/map-package-validation.d.ts.map +1 -1
- package/runtime/map-package-validation.js +0 -12
- package/runtime/map-package.d.ts +1 -1
- package/runtime/map-package.d.ts.map +1 -1
- package/runtime/maplibre-renderer.d.ts +41 -0
- package/runtime/maplibre-renderer.d.ts.map +1 -0
- package/runtime/maplibre-renderer.js +322 -0
- package/runtime/pmtiles-protocol.d.ts +77 -0
- package/runtime/pmtiles-protocol.d.ts.map +1 -0
- package/runtime/pmtiles-protocol.js +120 -0
- package/runtime/query-tiles.d.ts +2 -1
- package/runtime/query-tiles.d.ts.map +1 -1
- package/runtime/query-tiles.js +6 -2
- package/runtime/source-bridge.d.ts +4 -4
- package/runtime/source-bridge.d.ts.map +1 -1
- package/runtime/source-bridge.js +36 -56
- package/runtime/style-interactions.d.ts +3 -2
- package/runtime/style-interactions.d.ts.map +1 -1
- package/runtime/style-interactions.js +11 -3
- package/runtime/terra-draw-sketch.d.ts +275 -0
- package/runtime/terra-draw-sketch.d.ts.map +1 -0
- package/runtime/terra-draw-sketch.js +404 -0
- package/source-capability-registry.d.ts +1 -0
- package/source-capability-registry.js +1 -0
- package/source-capability-types.d.ts +186 -0
- package/source-capability-types.js +7 -0
- package/style/index.d.ts +2 -0
- package/style/index.d.ts.map +1 -1
- package/style/index.js +2 -0
- package/style/renderers.d.ts +326 -0
- package/style/renderers.d.ts.map +1 -0
- package/style/renderers.js +517 -0
- package/webmap/convert-renderer.d.ts +24 -3
- package/webmap/convert-renderer.d.ts.map +1 -1
- package/webmap/convert-renderer.js +93 -112
- package/webmap/index.d.ts +1 -1
- package/webmap/index.d.ts.map +1 -1
- package/webmap/index.js +1 -1
package/contract/source.js
CHANGED
|
@@ -18,12 +18,15 @@ import { HonuaOgcCollectionMap, HonuaOgcMaps } from "../core/ogc-maps.js";
|
|
|
18
18
|
import { HonuaOgcRecordCollection } from "../core/ogc-records.js";
|
|
19
19
|
import { HonuaOgcTiles, HonuaOgcTileset } from "../core/ogc-tiles.js";
|
|
20
20
|
import { trimTrailingSlashes } from "../core/path-utils.js";
|
|
21
|
+
import { HonuaStacStaticCatalog } from "../core/stac-static.js";
|
|
21
22
|
import { HonuaStacSearch } from "../core/stac.js";
|
|
22
23
|
import { HonuaFeatureLayer, HonuaGeometryService, HonuaGeoprocessingService, HonuaImageService, HonuaMapLayer, HonuaMapService, HonuaOgcFeatureCollection, HonuaOgcFeatures, } from "../core/surfaces.js";
|
|
23
24
|
import { UNSUPPORTED_FES, compileSpatialFilter, compileWhere, geoJsonGeometryToGml, serializeFes, } from "../core/wfs-filter.js";
|
|
24
|
-
import {
|
|
25
|
+
import { HonuaWfsFeatureType } from "../core/wfs.js";
|
|
25
26
|
import { HonuaWms, HonuaWmsLayer, parseWmsLayerNames } from "../core/wms.js";
|
|
26
27
|
import { HonuaWmts, HonuaWmtsLayer, HonuaWmtsTileset } from "../core/wmts.js";
|
|
28
|
+
import { pmtilesProtocolModule } from "./pmtiles.js";
|
|
29
|
+
import { addCapabilitySupport, normalizeCapabilityDescriptor } from "./source-capability-support.js";
|
|
27
30
|
import { CAPABILITIES, PROTOCOL_DEFAULT_CAPABILITIES, } from "./types.js";
|
|
28
31
|
/**
|
|
29
32
|
* Construct a protocol-neutral `Dataset` over a `HonuaClient`.
|
|
@@ -83,7 +86,8 @@ export function createDataset(options) {
|
|
|
83
86
|
const descriptors = new Map();
|
|
84
87
|
const handles = new Map();
|
|
85
88
|
let compatibilityPromise;
|
|
86
|
-
for (const
|
|
89
|
+
for (const input of sources) {
|
|
90
|
+
const descriptor = normalizeCapabilityDescriptor(input);
|
|
87
91
|
if (descriptors.has(descriptor.id)) {
|
|
88
92
|
throw new Error(`createDataset: duplicate source id "${descriptor.id}"`);
|
|
89
93
|
}
|
|
@@ -93,13 +97,14 @@ export function createDataset(options) {
|
|
|
93
97
|
const cached = handles.get(descriptor.id);
|
|
94
98
|
if (cached)
|
|
95
99
|
return cached;
|
|
96
|
-
const
|
|
97
|
-
|
|
100
|
+
const builtIn = buildBuiltInSource(descriptor, client, policy);
|
|
101
|
+
const built = builtIn ?? resolveSource?.(descriptor, { client, capabilityPolicy: policy });
|
|
98
102
|
if (!built) {
|
|
99
103
|
throw new HonuaCapabilityNotSupportedError("query", descriptor.protocol, descriptor.id);
|
|
100
104
|
}
|
|
101
|
-
|
|
102
|
-
|
|
105
|
+
const source = builtIn ?? addCapabilitySupport(built, descriptor);
|
|
106
|
+
handles.set(descriptor.id, source);
|
|
107
|
+
return source;
|
|
103
108
|
}
|
|
104
109
|
return {
|
|
105
110
|
id,
|
|
@@ -133,6 +138,15 @@ export function createDataset(options) {
|
|
|
133
138
|
// ── Built-in resolver ─────────────────────────────────────────
|
|
134
139
|
function buildBuiltInSource(descriptor, client, policy) {
|
|
135
140
|
switch (descriptor.protocol) {
|
|
141
|
+
case "grpc":
|
|
142
|
+
// Honua gRPC is a transport-selectable fast path over the same
|
|
143
|
+
// canonical FeatureServer semantics, not a distinct adapter: the
|
|
144
|
+
// returned `HonuaFeatureLayer` transparently routes `queryFeatures`
|
|
145
|
+
// over gRPC-Web when `client.transport === "grpc-web"` (see
|
|
146
|
+
// `docs/protocol-capability-matrix.md`, "gRPC FeatureService"). The
|
|
147
|
+
// discovered descriptor's narrower `capabilities` (verified through a
|
|
148
|
+
// live gRPC parity probe in `connect-grpc.ts`) still gate execution.
|
|
149
|
+
return geoServicesFeatureSource(descriptor, client, policy);
|
|
136
150
|
case "geoservices-feature-service":
|
|
137
151
|
return geoServicesFeatureSource(descriptor, client, policy);
|
|
138
152
|
case "geoservices-map-service":
|
|
@@ -161,6 +175,8 @@ function buildBuiltInSource(descriptor, client, policy) {
|
|
|
161
175
|
return wfsSource(descriptor, client, policy);
|
|
162
176
|
case "odata":
|
|
163
177
|
return odataSource(descriptor, client, policy);
|
|
178
|
+
case "pmtiles":
|
|
179
|
+
return pmtilesSource(descriptor, client, policy);
|
|
164
180
|
default:
|
|
165
181
|
return undefined;
|
|
166
182
|
}
|
|
@@ -192,9 +208,16 @@ function buildBuiltInSource(descriptor, client, policy) {
|
|
|
192
208
|
* ```
|
|
193
209
|
*/
|
|
194
210
|
export function geoServicesFeatureSource(descriptor, client, policy) {
|
|
211
|
+
descriptor = normalizeCapabilityDescriptor(descriptor);
|
|
195
212
|
const { serviceId, layerId } = requireFeatureServiceLocator(descriptor);
|
|
196
213
|
const layer = new HonuaFeatureLayer({ client, serviceId, layerId });
|
|
197
|
-
|
|
214
|
+
// Indexed by the descriptor's own protocol (not hardcoded to
|
|
215
|
+
// "geoservices-feature-service") so a `protocol: "grpc"` descriptor built
|
|
216
|
+
// without explicit capabilities falls back to the narrower gRPC default
|
|
217
|
+
// set rather than silently inheriting REST-only capabilities (`pbf`,
|
|
218
|
+
// `sql`, `attachments`, `queryRelated`) the gRPC FeatureService RPC
|
|
219
|
+
// surface does not support.
|
|
220
|
+
const caps = descriptor.capabilities ?? PROTOCOL_DEFAULT_CAPABILITIES[descriptor.protocol];
|
|
198
221
|
const adapterRegistry = {
|
|
199
222
|
"geoservices-feature-service": layer,
|
|
200
223
|
};
|
|
@@ -283,6 +306,7 @@ export function geoServicesFeatureSource(descriptor, client, policy) {
|
|
|
283
306
|
* ```
|
|
284
307
|
*/
|
|
285
308
|
export function geoServicesMapServiceSource(descriptor, client, policy) {
|
|
309
|
+
descriptor = normalizeCapabilityDescriptor(descriptor);
|
|
286
310
|
const { serviceId, layerId } = requireMapServiceLocator(descriptor);
|
|
287
311
|
const service = new HonuaMapService({ client, serviceId });
|
|
288
312
|
const layer = new HonuaMapLayer({ client, serviceId, layerId });
|
|
@@ -377,9 +401,15 @@ export function geoServicesMapServiceSource(descriptor, client, policy) {
|
|
|
377
401
|
* ```
|
|
378
402
|
*/
|
|
379
403
|
export function ogcFeaturesSource(descriptor, client, policy) {
|
|
404
|
+
descriptor = normalizeCapabilityDescriptor(descriptor);
|
|
380
405
|
const { collectionId } = requireOgcLocator(descriptor);
|
|
381
|
-
const
|
|
382
|
-
const
|
|
406
|
+
const layoutMode = ogcFeaturesLayoutMode(descriptor.locator.layout);
|
|
407
|
+
const collection = new HonuaOgcFeatureCollection({
|
|
408
|
+
client,
|
|
409
|
+
collectionId,
|
|
410
|
+
...(layoutMode ? { layout: layoutMode } : {}),
|
|
411
|
+
});
|
|
412
|
+
const root = new HonuaOgcFeatures({ client, ...(layoutMode ? { layout: layoutMode } : {}) });
|
|
383
413
|
const caps = descriptor.capabilities ?? PROTOCOL_DEFAULT_CAPABILITIES["ogc-features"];
|
|
384
414
|
const adapterRegistry = {
|
|
385
415
|
"ogc-features": collection,
|
|
@@ -591,6 +621,7 @@ export function ogcFeaturesSource(descriptor, client, policy) {
|
|
|
591
621
|
* ```
|
|
592
622
|
*/
|
|
593
623
|
export function geoServicesImageSource(descriptor, client, policy) {
|
|
624
|
+
descriptor = normalizeCapabilityDescriptor(descriptor);
|
|
594
625
|
const { serviceId } = requireImageServiceLocator(descriptor);
|
|
595
626
|
const service = new HonuaImageService({ client, serviceId });
|
|
596
627
|
const caps = descriptor.capabilities ?? PROTOCOL_DEFAULT_CAPABILITIES["geoservices-image-service"];
|
|
@@ -700,6 +731,7 @@ export function geoServicesImageSource(descriptor, client, policy) {
|
|
|
700
731
|
* ```
|
|
701
732
|
*/
|
|
702
733
|
export function geoServicesGeometryServiceSource(descriptor, client, policy) {
|
|
734
|
+
descriptor = normalizeCapabilityDescriptor(descriptor);
|
|
703
735
|
const service = new HonuaGeometryService({ client });
|
|
704
736
|
const caps = descriptor.capabilities ?? PROTOCOL_DEFAULT_CAPABILITIES["geoservices-geometry-service"];
|
|
705
737
|
const adapterRegistry = {
|
|
@@ -722,6 +754,7 @@ export function geoServicesGeometryServiceSource(descriptor, client, policy) {
|
|
|
722
754
|
* ```
|
|
723
755
|
*/
|
|
724
756
|
export function geoServicesGPServiceSource(descriptor, client, policy) {
|
|
757
|
+
descriptor = normalizeCapabilityDescriptor(descriptor);
|
|
725
758
|
const caps = descriptor.capabilities ?? PROTOCOL_DEFAULT_CAPABILITIES["geoservices-gp-service"];
|
|
726
759
|
const { serviceId, taskName } = requireGPServiceLocator(descriptor, caps);
|
|
727
760
|
const service = new HonuaGeoprocessingService({
|
|
@@ -749,20 +782,66 @@ export function geoServicesGPServiceSource(descriptor, client, policy) {
|
|
|
749
782
|
* ```
|
|
750
783
|
*/
|
|
751
784
|
export function ogcTilesSource(descriptor, client, policy) {
|
|
785
|
+
descriptor = normalizeCapabilityDescriptor(descriptor);
|
|
752
786
|
const { collectionId, tileMatrixSetId } = requireOgcTilesLocator(descriptor);
|
|
753
787
|
const caps = descriptor.capabilities ?? PROTOCOL_DEFAULT_CAPABILITIES["ogc-tiles"];
|
|
788
|
+
const basePath = descriptor.locator.basePath;
|
|
754
789
|
// Descriptors without a tileMatrixSetId cannot construct a usable
|
|
755
790
|
// HonuaOgcTileset (every tile route requires `tileMatrixSetId`). Expose
|
|
756
791
|
// the root HonuaOgcTiles adapter instead so callers can discover the
|
|
757
792
|
// tilesets the server advertises for the collection before binding one.
|
|
793
|
+
// A `connect()`-discovered third-party root threads its basePath so tile
|
|
794
|
+
// routes resolve against the advertised layout, not the `/ogc/tiles` facade.
|
|
758
795
|
const adapter = tileMatrixSetId !== undefined && tileMatrixSetId !== ""
|
|
759
|
-
? new HonuaOgcTileset({ client, collectionId, tileMatrixSetId })
|
|
760
|
-
: new HonuaOgcTiles({ client });
|
|
796
|
+
? new HonuaOgcTileset({ client, collectionId, tileMatrixSetId, ...(basePath !== undefined ? { basePath } : {}) })
|
|
797
|
+
: new HonuaOgcTiles({ client, ...(basePath !== undefined ? { basePath } : {}) });
|
|
761
798
|
const adapterRegistry = {
|
|
762
799
|
"ogc-tiles": adapter,
|
|
763
800
|
};
|
|
764
801
|
return makeSource(descriptor, caps, policy, adapterRegistry, unsupportedFeatureSurface(descriptor));
|
|
765
802
|
}
|
|
803
|
+
// ── PMTiles archive ───────────────────────────────────────────
|
|
804
|
+
/**
|
|
805
|
+
* Tiles-only Source adapter for a PMTiles archive (`protocol: "pmtiles"`).
|
|
806
|
+
*
|
|
807
|
+
* PMTiles archives are immutable single-file tile stores, so the canonical
|
|
808
|
+
* query family throws `HonuaCapabilityNotSupportedError` (there is no
|
|
809
|
+
* feature-query surface on an archive). Archive metadata — bounds, min/max
|
|
810
|
+
* zoom, and vector layer names — is inspected through the typed escape hatch:
|
|
811
|
+
* `Source.protocol("pmtiles").describe()`. Rendering integrations register the
|
|
812
|
+
* `pmtiles://` protocol on the map (see `@honua/sdk-js/runtime`) and reference
|
|
813
|
+
* the archive as a MapLibre source `url`.
|
|
814
|
+
*
|
|
815
|
+
* `locator.url` may carry a leading `pmtiles://` scheme (the MapLibre form) or
|
|
816
|
+
* be a bare archive URL; both resolve to the same archive.
|
|
817
|
+
*
|
|
818
|
+
* This builds the escape-hatch adapter through {@link pmtilesProtocolModule}
|
|
819
|
+
* (issue #538) rather than constructing `HonuaPmtilesArchive` directly, so
|
|
820
|
+
* the built-in wiring runs the same public `ProtocolModule` seam a module
|
|
821
|
+
* registered through `HonuaPluginRegistry` uses
|
|
822
|
+
* (`pmtilesProtocolPlugin`, `@honua/sdk-js/plugin`).
|
|
823
|
+
*
|
|
824
|
+
* @example
|
|
825
|
+
* ```ts
|
|
826
|
+
* const archive = dataset.source("basemap")!.protocol("pmtiles");
|
|
827
|
+
* const info = await archive!.describe();
|
|
828
|
+
* console.log(info.bounds, info.vectorLayers.map((layer) => layer.id));
|
|
829
|
+
* ```
|
|
830
|
+
*/
|
|
831
|
+
export function pmtilesSource(descriptor, _client, policy) {
|
|
832
|
+
descriptor = normalizeCapabilityDescriptor(descriptor);
|
|
833
|
+
const discovered = pmtilesProtocolModule().discover(descriptor);
|
|
834
|
+
if (discovered instanceof Promise) {
|
|
835
|
+
// PMTiles discovery is always synchronous (the reader opens lazily on the
|
|
836
|
+
// handle's own first `describe()` call); a module that started returning
|
|
837
|
+
// a promise would be a breaking change to this built-in's own contract.
|
|
838
|
+
throw new Error("pmtiles: built-in source construction requires synchronous protocol-module discovery");
|
|
839
|
+
}
|
|
840
|
+
const adapterRegistry = {
|
|
841
|
+
pmtiles: discovered.adapter,
|
|
842
|
+
};
|
|
843
|
+
return makeSource(descriptor, discovered.capabilities, policy, adapterRegistry, unsupportedFeatureSurface(descriptor));
|
|
844
|
+
}
|
|
766
845
|
// ── OGC API Maps ──────────────────────────────────────────────
|
|
767
846
|
/**
|
|
768
847
|
* Render-only Source adapter for OGC API Maps. Same shape as the Tiles
|
|
@@ -776,13 +855,18 @@ export function ogcTilesSource(descriptor, client, policy) {
|
|
|
776
855
|
* ```
|
|
777
856
|
*/
|
|
778
857
|
export function ogcMapsSource(descriptor, client, policy) {
|
|
779
|
-
|
|
858
|
+
descriptor = normalizeCapabilityDescriptor(descriptor);
|
|
859
|
+
const basePath = descriptor.locator.basePath;
|
|
860
|
+
// A `connect()`-discovered third-party root threads its basePath so render
|
|
861
|
+
// routes resolve against the advertised layout, not the `/ogc/maps` facade.
|
|
862
|
+
const root = new HonuaOgcMaps({ client, ...(basePath !== undefined ? { basePath } : {}) });
|
|
780
863
|
const adapterRegistry = {};
|
|
781
864
|
if (descriptor.locator.collectionId !== undefined) {
|
|
782
865
|
adapterRegistry["ogc-maps"] = new HonuaOgcCollectionMap({
|
|
783
866
|
client,
|
|
784
867
|
collectionId: descriptor.locator.collectionId,
|
|
785
868
|
styleId: descriptor.locator.styleId,
|
|
869
|
+
...(basePath !== undefined ? { basePath } : {}),
|
|
786
870
|
});
|
|
787
871
|
}
|
|
788
872
|
else {
|
|
@@ -806,8 +890,14 @@ export function ogcMapsSource(descriptor, client, policy) {
|
|
|
806
890
|
* ```
|
|
807
891
|
*/
|
|
808
892
|
export function ogcRecordsSource(descriptor, client, policy) {
|
|
893
|
+
descriptor = normalizeCapabilityDescriptor(descriptor);
|
|
809
894
|
const { collectionId } = requireOgcRecordsLocator(descriptor);
|
|
810
|
-
const
|
|
895
|
+
const basePath = descriptor.locator.basePath;
|
|
896
|
+
const collection = new HonuaOgcRecordCollection({
|
|
897
|
+
client,
|
|
898
|
+
collectionId,
|
|
899
|
+
...(basePath !== undefined ? { basePath } : {}),
|
|
900
|
+
});
|
|
811
901
|
const caps = descriptor.capabilities ?? PROTOCOL_DEFAULT_CAPABILITIES["ogc-records"];
|
|
812
902
|
const adapterRegistry = {
|
|
813
903
|
"ogc-records": collection,
|
|
@@ -919,6 +1009,18 @@ export function ogcRecordsSource(descriptor, client, policy) {
|
|
|
919
1009
|
* ```
|
|
920
1010
|
*/
|
|
921
1011
|
export function wmsSource(descriptor, client, policy) {
|
|
1012
|
+
descriptor = normalizeCapabilityDescriptor(descriptor);
|
|
1013
|
+
const advertisedCaps = descriptor.capabilities ?? PROTOCOL_DEFAULT_CAPABILITIES.wms;
|
|
1014
|
+
if (typeof descriptor.locator.serviceId !== "string" || descriptor.locator.serviceId.length === 0) {
|
|
1015
|
+
if (descriptor.locator.raster?.kind !== "wms-kvp")
|
|
1016
|
+
requireWmsLocator(descriptor);
|
|
1017
|
+
// Raw third-party discovery is renderable through the existing descriptor
|
|
1018
|
+
// → MapLibre raster projection. The existing canonical FeatureInfo adapter
|
|
1019
|
+
// is Honua-service-id based, so keep that surface fail-closed here.
|
|
1020
|
+
const caps = new Set([...advertisedCaps].filter((capability) => capability !== "query"));
|
|
1021
|
+
descriptor = { ...descriptor, capabilities: caps };
|
|
1022
|
+
return makeSource(descriptor, caps, policy, {}, unsupportedFeatureSurface(descriptor));
|
|
1023
|
+
}
|
|
922
1024
|
const { serviceId } = requireWmsLocator(descriptor);
|
|
923
1025
|
const layerName = descriptor.locator.typeName;
|
|
924
1026
|
const styleId = descriptor.locator.styleId;
|
|
@@ -941,7 +1043,7 @@ export function wmsSource(descriptor, client, policy) {
|
|
|
941
1043
|
layerOpts.defaultStyleId = styleId;
|
|
942
1044
|
adapterRegistry["wms-layer"] = new HonuaWmsLayer(layerOpts);
|
|
943
1045
|
}
|
|
944
|
-
const caps =
|
|
1046
|
+
const caps = advertisedCaps;
|
|
945
1047
|
return makeSource(descriptor, caps, policy, adapterRegistry, {
|
|
946
1048
|
async query(request) {
|
|
947
1049
|
ensureCapability(descriptor, caps, "query");
|
|
@@ -1019,6 +1121,16 @@ export function wmsSource(descriptor, client, policy) {
|
|
|
1019
1121
|
* ```
|
|
1020
1122
|
*/
|
|
1021
1123
|
export function wmtsSource(descriptor, client, policy) {
|
|
1124
|
+
descriptor = normalizeCapabilityDescriptor(descriptor);
|
|
1125
|
+
const caps = descriptor.capabilities ?? PROTOCOL_DEFAULT_CAPABILITIES.wmts;
|
|
1126
|
+
if (typeof descriptor.locator.serviceId !== "string" || descriptor.locator.serviceId.length === 0) {
|
|
1127
|
+
if (descriptor.locator.raster?.kind !== "wmts-kvp" && descriptor.locator.raster?.kind !== "wmts-template") {
|
|
1128
|
+
requireWmtsLocator(descriptor);
|
|
1129
|
+
}
|
|
1130
|
+
// Raw WMTS sources use the validated descriptor binding in the existing
|
|
1131
|
+
// MapLibre projection; no second protocol wire adapter is introduced.
|
|
1132
|
+
return makeSource(descriptor, caps, policy, {}, unsupportedFeatureSurface(descriptor));
|
|
1133
|
+
}
|
|
1022
1134
|
const { serviceId } = requireWmtsLocator(descriptor);
|
|
1023
1135
|
const layerName = descriptor.locator.typeName;
|
|
1024
1136
|
const tileMatrixSetId = descriptor.locator.tileMatrixSetId ?? "WebMercatorQuad";
|
|
@@ -1041,7 +1153,6 @@ export function wmtsSource(descriptor, client, policy) {
|
|
|
1041
1153
|
tileMatrixSetId,
|
|
1042
1154
|
});
|
|
1043
1155
|
}
|
|
1044
|
-
const caps = descriptor.capabilities ?? PROTOCOL_DEFAULT_CAPABILITIES.wmts;
|
|
1045
1156
|
return makeSource(descriptor, caps, policy, adapterRegistry, unsupportedFeatureSurface(descriptor));
|
|
1046
1157
|
}
|
|
1047
1158
|
// ── STAC API ──────────────────────────────────────────────────
|
|
@@ -1074,7 +1185,17 @@ export function wmtsSource(descriptor, client, policy) {
|
|
|
1074
1185
|
* ```
|
|
1075
1186
|
*/
|
|
1076
1187
|
export function stacSearchSource(descriptor, client, policy) {
|
|
1077
|
-
|
|
1188
|
+
descriptor = normalizeCapabilityDescriptor(descriptor);
|
|
1189
|
+
// Endpoint-layout selection. `stac-api` treats `locator.url` as a raw STAC
|
|
1190
|
+
// API root (search / collections mounted directly under it) rather than the
|
|
1191
|
+
// Honua `/stac` facade. `stac-static` reads a static catalog.json tree with
|
|
1192
|
+
// no search endpoint. Omitted / `honua-facade` keeps the `/stac` facade.
|
|
1193
|
+
const layout = descriptor.locator.layout;
|
|
1194
|
+
const stacBasePath = layout === "stac-api" ? "" : undefined;
|
|
1195
|
+
const stac = new HonuaStacSearch({ client, ...(stacBasePath !== undefined ? { basePath: stacBasePath } : {}) });
|
|
1196
|
+
const staticCatalog = layout === "stac-static"
|
|
1197
|
+
? new HonuaStacStaticCatalog(client, descriptor.locator.url, descriptor.locator.stacStatic)
|
|
1198
|
+
: undefined;
|
|
1078
1199
|
const caps = descriptor.capabilities ?? PROTOCOL_DEFAULT_CAPABILITIES.stac;
|
|
1079
1200
|
const collectionScope = descriptor.locator.collectionId;
|
|
1080
1201
|
const adapterRegistry = {
|
|
@@ -1087,7 +1208,9 @@ export function stacSearchSource(descriptor, client, policy) {
|
|
|
1087
1208
|
throw new HonuaCapabilityNotSupportedError("queryAggregate", descriptor.protocol, descriptor.id);
|
|
1088
1209
|
}
|
|
1089
1210
|
const stacRequest = toStacRequest(request, collectionScope);
|
|
1090
|
-
const response =
|
|
1211
|
+
const response = staticCatalog
|
|
1212
|
+
? await staticCatalog.search(toStacStaticParams(stacRequest))
|
|
1213
|
+
: await stac.search(stacRequest);
|
|
1091
1214
|
const features = (response.features ?? []).map((toTypedFeatureFromStac));
|
|
1092
1215
|
const totalCount = response.numberMatched ?? response.context?.matched;
|
|
1093
1216
|
const exceededTransferLimit = totalCount !== undefined && features.length < totalCount;
|
|
@@ -1100,6 +1223,12 @@ export function stacSearchSource(descriptor, client, policy) {
|
|
|
1100
1223
|
async queryAll(request) {
|
|
1101
1224
|
ensureCapability(descriptor, caps, "query");
|
|
1102
1225
|
const limit = request?.pagination?.limit;
|
|
1226
|
+
if (staticCatalog) {
|
|
1227
|
+
const response = await staticCatalog.search(toStacStaticParams(toStacRequest(request, collectionScope)));
|
|
1228
|
+
const typed = (response.features ?? []).map((toTypedFeatureFromStac));
|
|
1229
|
+
const { features, exceededTransferLimit } = applyQueryAllLimit(typed, limit);
|
|
1230
|
+
return { features, exceededTransferLimit, totalCount: features.length };
|
|
1231
|
+
}
|
|
1103
1232
|
const items = await stac.searchAll({
|
|
1104
1233
|
...toStacRequest(request, collectionScope),
|
|
1105
1234
|
...withPagingBounds({}, limit),
|
|
@@ -1121,6 +1250,16 @@ export function stacSearchSource(descriptor, client, policy) {
|
|
|
1121
1250
|
async *stream(request) {
|
|
1122
1251
|
ensureCapability(descriptor, caps, "stream");
|
|
1123
1252
|
const limit = request?.pagination?.limit;
|
|
1253
|
+
if (staticCatalog) {
|
|
1254
|
+
// A static catalog has no server-side paging; yield the whole
|
|
1255
|
+
// filtered set as one page.
|
|
1256
|
+
const response = await staticCatalog.search(toStacStaticParams(toStacRequest(request, collectionScope)));
|
|
1257
|
+
yield {
|
|
1258
|
+
features: (response.features ?? []).map((toTypedFeatureFromStac)),
|
|
1259
|
+
exceededTransferLimit: false,
|
|
1260
|
+
};
|
|
1261
|
+
return;
|
|
1262
|
+
}
|
|
1124
1263
|
const stream = stac.searchStream({
|
|
1125
1264
|
...toStacRequest(request, collectionScope),
|
|
1126
1265
|
pageSize: limit !== undefined ? Math.max(1, limit) : undefined,
|
|
@@ -1140,10 +1279,12 @@ export function stacSearchSource(descriptor, client, policy) {
|
|
|
1140
1279
|
// routes through `withPagingBounds` so the underlying searchAll
|
|
1141
1280
|
// fetches at most `limit + 1` rows, never a full-catalog scan.
|
|
1142
1281
|
const limit = request?.pagination?.limit;
|
|
1143
|
-
const items =
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1282
|
+
const items = staticCatalog
|
|
1283
|
+
? ((await staticCatalog.search(toStacStaticParams(toStacRequest(request, collectionScope)))).features ?? [])
|
|
1284
|
+
: await stac.searchAll({
|
|
1285
|
+
...toStacRequest(request, collectionScope),
|
|
1286
|
+
...withPagingBounds({}, limit),
|
|
1287
|
+
});
|
|
1147
1288
|
const ids = [];
|
|
1148
1289
|
for (const item of items) {
|
|
1149
1290
|
if (item.id !== undefined && item.id !== null) {
|
|
@@ -1201,8 +1342,9 @@ const DEFAULT_WFS_GEOMETRY_PROPERTY = "the_geom";
|
|
|
1201
1342
|
* ```
|
|
1202
1343
|
*/
|
|
1203
1344
|
export function wfsSource(descriptor, client, policy) {
|
|
1345
|
+
descriptor = normalizeCapabilityDescriptor(descriptor);
|
|
1204
1346
|
const { url, typeName, featureNamespace, srsName: wfsSrsName } = requireWfsLocator(descriptor);
|
|
1205
|
-
const root =
|
|
1347
|
+
const root = client.wfs(url);
|
|
1206
1348
|
const featureType = new HonuaWfsFeatureType({ root, typeName });
|
|
1207
1349
|
const caps = descriptor.capabilities ?? PROTOCOL_DEFAULT_CAPABILITIES.wfs;
|
|
1208
1350
|
void policy;
|
|
@@ -1435,6 +1577,10 @@ export function wfsSource(descriptor, client, policy) {
|
|
|
1435
1577
|
summary = { totalInserted: 0, totalUpdated: 0, totalDeleted: 0, insertResults: [] };
|
|
1436
1578
|
}
|
|
1437
1579
|
else {
|
|
1580
|
+
// Ensure a caller-cache hit still resolves the advertised Transaction
|
|
1581
|
+
// POST URL before sending edits. Fresh connect discovery reuses this
|
|
1582
|
+
// same root, so the capabilities promise is already settled there.
|
|
1583
|
+
await root.capabilities(envelope.signal ? { signal: envelope.signal } : undefined);
|
|
1438
1584
|
const filtered = { ...envelope, updates: validUpdates };
|
|
1439
1585
|
const body = buildTransactionBody(typeName, filtered, featureNamespace, wfsSrsName);
|
|
1440
1586
|
const transactionOptions = { body };
|
|
@@ -1891,21 +2037,12 @@ function canonicalEditResultFromTransaction(envelope, summary, malformedUpdateIn
|
|
|
1891
2037
|
}));
|
|
1892
2038
|
return { added, updated, deleted };
|
|
1893
2039
|
}
|
|
1894
|
-
|
|
1895
|
-
/**
|
|
1896
|
-
|
|
1897
|
-
|
|
1898
|
-
|
|
1899
|
-
|
|
1900
|
-
* `Source.protocol("odata")` on the returned `HonuaOdataEntitySet`.
|
|
1901
|
-
*
|
|
1902
|
-
* `$metadata` is fetched lazily on the first call that needs it and
|
|
1903
|
-
* cached on the entity-set instance. The fetched capability annotations
|
|
1904
|
-
* are intersected with the descriptor's declared `Capabilities` set the
|
|
1905
|
-
* first time a capability-gated method is invoked — this is the
|
|
1906
|
-
* precedent for the metadata-driven downgrade pattern referenced in
|
|
1907
|
-
* `docs/shared-client-contract.md`.
|
|
1908
|
-
*/
|
|
2040
|
+
let odataWriteCodecPromise;
|
|
2041
|
+
/** Load the opt-in codec once; legacy sources never evaluate this import. */
|
|
2042
|
+
function loadOdataWriteCodec() {
|
|
2043
|
+
odataWriteCodecPromise ??= import("../core/odata-write-codec.js");
|
|
2044
|
+
return odataWriteCodecPromise;
|
|
2045
|
+
}
|
|
1909
2046
|
/**
|
|
1910
2047
|
* Adapter factory for an OData v4 entity set.
|
|
1911
2048
|
*
|
|
@@ -1933,11 +2070,16 @@ function canonicalEditResultFromTransaction(envelope, summary, malformedUpdateIn
|
|
|
1933
2070
|
* });
|
|
1934
2071
|
* ```
|
|
1935
2072
|
*/
|
|
1936
|
-
export function odataSource(descriptor, client, policy) {
|
|
2073
|
+
export function odataSource(descriptor, client, policy, options = {}) {
|
|
2074
|
+
descriptor = normalizeCapabilityDescriptor(descriptor);
|
|
1937
2075
|
const { entitySet, basePath } = requireOdataLocator(descriptor);
|
|
1938
2076
|
const entity = new HonuaOdataEntitySet({ client, entitySet, basePath });
|
|
1939
2077
|
const declaredCaps = descriptor.capabilities ?? PROTOCOL_DEFAULT_CAPABILITIES.odata;
|
|
1940
2078
|
const negotiation = new OdataCapabilityNegotiator(entity, declaredCaps);
|
|
2079
|
+
const writeEncoding = options.writeEncoding ?? "legacy";
|
|
2080
|
+
if (writeEncoding !== "legacy" && writeEncoding !== "lossless-json") {
|
|
2081
|
+
throw new TypeError('odata: writeEncoding must be "legacy" or "lossless-json"');
|
|
2082
|
+
}
|
|
1941
2083
|
const adapterRegistry = {
|
|
1942
2084
|
odata: entity,
|
|
1943
2085
|
};
|
|
@@ -2040,6 +2182,9 @@ export function odataSource(descriptor, client, policy) {
|
|
|
2040
2182
|
ensureCapability(descriptor, declaredCaps, "applyEdits");
|
|
2041
2183
|
await negotiation.ensureAdvertised(descriptor, "applyEdits");
|
|
2042
2184
|
const keyFields = await negotiation.keyFields(descriptor.id);
|
|
2185
|
+
const prepared = writeEncoding === "lossless-json"
|
|
2186
|
+
? prepareLosslessOdataEdits(entity, envelope, keyFields, await negotiation.metadata(), await loadOdataWriteCodec())
|
|
2187
|
+
: undefined;
|
|
2043
2188
|
// Atomic path: when the caller asks for rollback-on-failure AND the
|
|
2044
2189
|
// service advertises `$batch`, collapse the envelope into a single
|
|
2045
2190
|
// OData batch with a shared `atomicityGroup` so a later failure
|
|
@@ -2050,9 +2195,9 @@ export function odataSource(descriptor, client, policy) {
|
|
|
2050
2195
|
if (envelope.rollbackOnFailure === true) {
|
|
2051
2196
|
const batchAdvertised = await negotiation.batchAdvertised();
|
|
2052
2197
|
if (batchAdvertised) {
|
|
2053
|
-
return atomicOdataApplyEdits(entity, envelope, keyFields, descriptor);
|
|
2198
|
+
return atomicOdataApplyEdits(entity, envelope, keyFields, descriptor, prepared);
|
|
2054
2199
|
}
|
|
2055
|
-
const result = await perCallOdataApplyEdits(entity, envelope, keyFields);
|
|
2200
|
+
const result = await perCallOdataApplyEdits(entity, envelope, keyFields, prepared);
|
|
2056
2201
|
return {
|
|
2057
2202
|
...result,
|
|
2058
2203
|
degraded: [
|
|
@@ -2066,7 +2211,7 @@ export function odataSource(descriptor, client, policy) {
|
|
|
2066
2211
|
],
|
|
2067
2212
|
};
|
|
2068
2213
|
}
|
|
2069
|
-
return perCallOdataApplyEdits(entity, envelope, keyFields);
|
|
2214
|
+
return perCallOdataApplyEdits(entity, envelope, keyFields, prepared);
|
|
2070
2215
|
},
|
|
2071
2216
|
async queryRelated() {
|
|
2072
2217
|
// OData has no canonical related-records surface; navigation
|
|
@@ -2077,21 +2222,114 @@ export function odataSource(descriptor, client, policy) {
|
|
|
2077
2222
|
attachments: unsupportedAttachmentApi(descriptor),
|
|
2078
2223
|
});
|
|
2079
2224
|
}
|
|
2225
|
+
/**
|
|
2226
|
+
* Preflight the complete opted-in envelope against one cached metadata
|
|
2227
|
+
* snapshot. Both direct and atomic execution consume this projection, so a
|
|
2228
|
+
* local encoding failure cannot leak a partial request or take a different
|
|
2229
|
+
* branch-specific coercion path.
|
|
2230
|
+
*/
|
|
2231
|
+
function prepareLosslessOdataEdits(entity, envelope, keyFields, metadata, codec) {
|
|
2232
|
+
const urlKeys = urlKeyFields(entity.entitySet, keyFields);
|
|
2233
|
+
const adds = (envelope.adds ?? []).map((add) => preparedOdataBody(metadata, entity.entitySet, add, codec));
|
|
2234
|
+
const updates = (envelope.updates ?? []).map((update) => {
|
|
2235
|
+
const prepared = preparedOdataBody(metadata, entity.entitySet, update, codec);
|
|
2236
|
+
const key = preparedOdataUpdateKey(metadata, entity.entitySet, update.id, prepared.body, urlKeys, codec);
|
|
2237
|
+
return { ...prepared, key };
|
|
2238
|
+
});
|
|
2239
|
+
const deletes = (envelope.deletes ?? []).map((id) => {
|
|
2240
|
+
if (urlKeys.length > 1) {
|
|
2241
|
+
// The generic contract carries only a scalar FeatureId. Lossless
|
|
2242
|
+
// composite addressing requires named components, so do not interpret
|
|
2243
|
+
// an ad-hoc string expression or risk targeting a different entity.
|
|
2244
|
+
throw new codec.HonuaOdataEdmEncodingError("missing-key", "$.key");
|
|
2245
|
+
}
|
|
2246
|
+
return {
|
|
2247
|
+
key: codec.encodeOdataEntityKey(metadata, entity.entitySet, id, {
|
|
2248
|
+
keyFields: urlKeys,
|
|
2249
|
+
}),
|
|
2250
|
+
};
|
|
2251
|
+
});
|
|
2252
|
+
return { adds, updates, deletes };
|
|
2253
|
+
}
|
|
2254
|
+
function preparedOdataBody(metadata, entitySet, feature, codec) {
|
|
2255
|
+
const attributes = codec.encodeOdataWriteBody(metadata, entitySet, feature.attributes);
|
|
2256
|
+
let encoded = attributes;
|
|
2257
|
+
if (feature.geometry !== undefined &&
|
|
2258
|
+
feature.geometry !== null &&
|
|
2259
|
+
!Object.hasOwn(attributes.body, "Geometry") &&
|
|
2260
|
+
!Object.hasOwn(attributes.body, "geometry")) {
|
|
2261
|
+
const geometry = codec.encodeOdataWriteBody(metadata, entitySet, { Geometry: feature.geometry });
|
|
2262
|
+
encoded = {
|
|
2263
|
+
body: { ...attributes.body, ...geometry.body },
|
|
2264
|
+
requiresIeee754Compatible: attributes.requiresIeee754Compatible || geometry.requiresIeee754Compatible,
|
|
2265
|
+
};
|
|
2266
|
+
}
|
|
2267
|
+
return {
|
|
2268
|
+
body: encoded.body,
|
|
2269
|
+
contentType: encoded.requiresIeee754Compatible ? "application/json;IEEE754Compatible=true" : "application/json",
|
|
2270
|
+
};
|
|
2271
|
+
}
|
|
2272
|
+
function preparedOdataUpdateKey(metadata, entitySet, id, attributes, keyFields, codec) {
|
|
2273
|
+
if (keyFields.length === 1) {
|
|
2274
|
+
const field = keyFields[0];
|
|
2275
|
+
const attributeValue = ownPreparedValue(attributes, field);
|
|
2276
|
+
const attributeKey = attributeValue === undefined || attributeValue === null
|
|
2277
|
+
? undefined
|
|
2278
|
+
: codec.encodeOdataEntityKey(metadata, entitySet, attributeValue, { keyFields });
|
|
2279
|
+
const idKey = id === undefined || id === null ? undefined : codec.encodeOdataEntityKey(metadata, entitySet, id, { keyFields });
|
|
2280
|
+
if (attributeKey &&
|
|
2281
|
+
idKey &&
|
|
2282
|
+
(attributeKey.literal !== idKey.literal || attributeKey.pathSegment !== idKey.pathSegment)) {
|
|
2283
|
+
throw new codec.HonuaOdataEdmEncodingError("invalid-value", "$.key");
|
|
2284
|
+
}
|
|
2285
|
+
return attributeKey ?? idKey ?? codec.encodeOdataEntityKey(metadata, entitySet, undefined, { keyFields });
|
|
2286
|
+
}
|
|
2287
|
+
if (keyFields.length === 0) {
|
|
2288
|
+
return codec.encodeOdataEntityKey(metadata, entitySet, id, { keyFields });
|
|
2289
|
+
}
|
|
2290
|
+
if (id !== undefined && id !== null) {
|
|
2291
|
+
// A scalar generic id cannot prove equivalence to a named composite key.
|
|
2292
|
+
// Reject it even when the body carries all components so the adapter never
|
|
2293
|
+
// targets one identity while reporting another.
|
|
2294
|
+
throw new codec.HonuaOdataEdmEncodingError("invalid-value", "$.key");
|
|
2295
|
+
}
|
|
2296
|
+
const key = Object.create(null);
|
|
2297
|
+
for (const field of keyFields) {
|
|
2298
|
+
const value = ownPreparedValue(attributes, field);
|
|
2299
|
+
if (value === undefined || value === null)
|
|
2300
|
+
continue;
|
|
2301
|
+
Object.defineProperty(key, field, {
|
|
2302
|
+
configurable: true,
|
|
2303
|
+
enumerable: true,
|
|
2304
|
+
writable: true,
|
|
2305
|
+
value,
|
|
2306
|
+
});
|
|
2307
|
+
}
|
|
2308
|
+
return codec.encodeOdataEntityKey(metadata, entitySet, key, { keyFields });
|
|
2309
|
+
}
|
|
2310
|
+
function ownPreparedValue(attributes, field) {
|
|
2311
|
+
const descriptor = Object.getOwnPropertyDescriptor(attributes, field);
|
|
2312
|
+
return descriptor && "value" in descriptor ? descriptor.value : undefined;
|
|
2313
|
+
}
|
|
2080
2314
|
/**
|
|
2081
2315
|
* Per-call OData edits — issues independent POST/PATCH/DELETE requests
|
|
2082
2316
|
* and collects per-row outcomes. This is the non-atomic fallback used
|
|
2083
2317
|
* when `rollbackOnFailure` is unset or the server does not advertise
|
|
2084
2318
|
* `$batch`.
|
|
2085
2319
|
*/
|
|
2086
|
-
async function perCallOdataApplyEdits(entity, envelope, keyFields) {
|
|
2320
|
+
async function perCallOdataApplyEdits(entity, envelope, keyFields, prepared) {
|
|
2087
2321
|
const added = [];
|
|
2088
2322
|
const updated = [];
|
|
2089
2323
|
const deleted = [];
|
|
2090
2324
|
const { signal } = envelope;
|
|
2091
|
-
|
|
2325
|
+
const adds = envelope.adds ?? [];
|
|
2326
|
+
for (let index = 0; index < adds.length; index += 1) {
|
|
2327
|
+
const add = adds[index];
|
|
2328
|
+
const encoded = prepared?.adds[index];
|
|
2092
2329
|
try {
|
|
2093
|
-
const created = await entity.add(featureToOdataBody(add), {
|
|
2330
|
+
const created = await entity.add(encoded?.body ?? featureToOdataBody(add), {
|
|
2094
2331
|
...(signal ? { signal } : {}),
|
|
2332
|
+
...(encoded ? { contentType: encoded.contentType } : {}),
|
|
2095
2333
|
});
|
|
2096
2334
|
const id = readKey(created, keyFields, add.id);
|
|
2097
2335
|
added.push(id !== undefined ? { id, success: true } : { success: true });
|
|
@@ -2106,22 +2344,30 @@ async function perCallOdataApplyEdits(entity, envelope, keyFields) {
|
|
|
2106
2344
|
// using the full `keyFields` because the response body still carries
|
|
2107
2345
|
// every key.
|
|
2108
2346
|
const urlKeys = urlKeyFields(entity.entitySet, keyFields);
|
|
2109
|
-
|
|
2110
|
-
|
|
2347
|
+
const updates = envelope.updates ?? [];
|
|
2348
|
+
for (let index = 0; index < updates.length; index += 1) {
|
|
2349
|
+
const update = updates[index];
|
|
2350
|
+
const encoded = prepared?.updates[index];
|
|
2351
|
+
const key = encoded?.key.literal ?? canonicalKeyToOdata(update, urlKeys);
|
|
2111
2352
|
if (key === undefined) {
|
|
2112
2353
|
updated.push({ success: false, error: { code: 400, description: "update.id is required" } });
|
|
2113
2354
|
continue;
|
|
2114
2355
|
}
|
|
2115
2356
|
try {
|
|
2116
|
-
await entity.update(key, featureToOdataBody(update), {
|
|
2357
|
+
await entity.update(key, encoded?.body ?? featureToOdataBody(update), {
|
|
2358
|
+
...(signal ? { signal } : {}),
|
|
2359
|
+
...(encoded ? { contentType: encoded.contentType } : {}),
|
|
2360
|
+
});
|
|
2117
2361
|
updated.push({ id: update.id ?? readKeyFromBody(update.attributes, keyFields), success: true });
|
|
2118
2362
|
}
|
|
2119
2363
|
catch (err) {
|
|
2120
2364
|
updated.push({ id: update.id, success: false, error: editErrorFromCatch(err) });
|
|
2121
2365
|
}
|
|
2122
2366
|
}
|
|
2123
|
-
|
|
2124
|
-
|
|
2367
|
+
const deletes = envelope.deletes ?? [];
|
|
2368
|
+
for (let index = 0; index < deletes.length; index += 1) {
|
|
2369
|
+
const id = deletes[index];
|
|
2370
|
+
const key = prepared?.deletes[index]?.key.literal ?? canonicalKeyToOdata({ id, attributes: {} }, urlKeys);
|
|
2125
2371
|
if (key === undefined) {
|
|
2126
2372
|
deleted.push({ success: false, error: { code: 400, description: "delete id is required" } });
|
|
2127
2373
|
continue;
|
|
@@ -2144,7 +2390,7 @@ async function perCallOdataApplyEdits(entity, envelope, keyFields) {
|
|
|
2144
2390
|
* threaded back into per-row `EditOutcome` entries in the original
|
|
2145
2391
|
* order so callers see the same shape regardless of transport.
|
|
2146
2392
|
*/
|
|
2147
|
-
async function atomicOdataApplyEdits(entity, envelope, keyFields, descriptor) {
|
|
2393
|
+
async function atomicOdataApplyEdits(entity, envelope, keyFields, descriptor, prepared) {
|
|
2148
2394
|
const operations = [];
|
|
2149
2395
|
const plan = [];
|
|
2150
2396
|
const bucketIndices = { add: [], update: [], delete: [] };
|
|
@@ -2156,21 +2402,26 @@ async function atomicOdataApplyEdits(entity, envelope, keyFields, descriptor) {
|
|
|
2156
2402
|
// and the test surface stays small.
|
|
2157
2403
|
let seq = 1;
|
|
2158
2404
|
const adds = envelope.adds ?? [];
|
|
2159
|
-
for (
|
|
2405
|
+
for (let index = 0; index < adds.length; index += 1) {
|
|
2406
|
+
const add = adds[index];
|
|
2407
|
+
const encoded = prepared?.adds[index];
|
|
2160
2408
|
const id = String(seq++);
|
|
2161
2409
|
operations.push({
|
|
2162
2410
|
id,
|
|
2163
2411
|
method: "POST",
|
|
2164
2412
|
url: stripLeadingSlashLocal(entity.entitySet),
|
|
2165
|
-
body: featureToOdataBody(add),
|
|
2413
|
+
body: encoded?.body ?? featureToOdataBody(add),
|
|
2414
|
+
...(encoded ? { headers: { "Content-Type": encoded.contentType } } : {}),
|
|
2166
2415
|
});
|
|
2167
2416
|
bucketIndices.add.push(plan.length);
|
|
2168
2417
|
plan.push({ kind: "add", id: add.id });
|
|
2169
2418
|
}
|
|
2170
2419
|
const updates = envelope.updates ?? [];
|
|
2171
|
-
for (
|
|
2420
|
+
for (let index = 0; index < updates.length; index += 1) {
|
|
2421
|
+
const update = updates[index];
|
|
2422
|
+
const encoded = prepared?.updates[index];
|
|
2172
2423
|
const id = String(seq++);
|
|
2173
|
-
const key = canonicalKeyToOdata(update, urlKeys);
|
|
2424
|
+
const key = encoded?.key.literal ?? canonicalKeyToOdata(update, urlKeys);
|
|
2174
2425
|
if (key === undefined) {
|
|
2175
2426
|
bucketIndices.update.push(plan.length);
|
|
2176
2427
|
plan.push({ kind: "update", id: update.id });
|
|
@@ -2179,22 +2430,29 @@ async function atomicOdataApplyEdits(entity, envelope, keyFields, descriptor) {
|
|
|
2179
2430
|
operations.push({
|
|
2180
2431
|
id,
|
|
2181
2432
|
method: "PATCH",
|
|
2182
|
-
url: `${entity.entitySet}(${key})`,
|
|
2183
|
-
body: featureToOdataBody(update),
|
|
2433
|
+
url: `${entity.entitySet}(${encoded?.key.pathSegment ?? key})`,
|
|
2434
|
+
body: encoded?.body ?? featureToOdataBody(update),
|
|
2435
|
+
...(encoded ? { headers: { "Content-Type": encoded.contentType } } : {}),
|
|
2184
2436
|
});
|
|
2185
2437
|
bucketIndices.update.push(plan.length);
|
|
2186
2438
|
plan.push({ kind: "update", id: update.id, key });
|
|
2187
2439
|
}
|
|
2188
2440
|
const deletes = envelope.deletes ?? [];
|
|
2189
|
-
for (
|
|
2441
|
+
for (let index = 0; index < deletes.length; index += 1) {
|
|
2442
|
+
const rawId = deletes[index];
|
|
2443
|
+
const encoded = prepared?.deletes[index];
|
|
2190
2444
|
const id = String(seq++);
|
|
2191
|
-
const key = canonicalKeyToOdata({ id: rawId, attributes: {} }, urlKeys);
|
|
2445
|
+
const key = encoded?.key.literal ?? canonicalKeyToOdata({ id: rawId, attributes: {} }, urlKeys);
|
|
2192
2446
|
if (key === undefined) {
|
|
2193
2447
|
bucketIndices.delete.push(plan.length);
|
|
2194
2448
|
plan.push({ kind: "delete", id: rawId });
|
|
2195
2449
|
continue;
|
|
2196
2450
|
}
|
|
2197
|
-
operations.push({
|
|
2451
|
+
operations.push({
|
|
2452
|
+
id,
|
|
2453
|
+
method: "DELETE",
|
|
2454
|
+
url: `${entity.entitySet}(${encoded?.key.pathSegment ?? key})`,
|
|
2455
|
+
});
|
|
2198
2456
|
bucketIndices.delete.push(plan.length);
|
|
2199
2457
|
plan.push({ kind: "delete", id: rawId, key });
|
|
2200
2458
|
}
|
|
@@ -2485,6 +2743,13 @@ async function buildOdataParams(entity, descriptor, request, options) {
|
|
|
2485
2743
|
if (request.returnGeometry === false) {
|
|
2486
2744
|
rootSelect = rootSelect.filter((f) => !isOdataGeometryFieldName(f, options.geomColumn));
|
|
2487
2745
|
}
|
|
2746
|
+
else if (options.geomColumn && !rootSelect.includes(options.geomColumn)) {
|
|
2747
|
+
// OData `$select` is exclusive: once callers project attributes the
|
|
2748
|
+
// service omits every unlisted property. Preserve canonical
|
|
2749
|
+
// `returnGeometry` (default true) by explicitly retaining the resolved
|
|
2750
|
+
// geometry column.
|
|
2751
|
+
rootSelect = [...rootSelect, options.geomColumn];
|
|
2752
|
+
}
|
|
2488
2753
|
if (rootSelect.length > 0)
|
|
2489
2754
|
out.select = rootSelect;
|
|
2490
2755
|
if (split.expand.length > 0)
|
|
@@ -2723,6 +2988,10 @@ class OdataCapabilityNegotiator {
|
|
|
2723
2988
|
return [];
|
|
2724
2989
|
return meta.keys[typeName] ?? [];
|
|
2725
2990
|
}
|
|
2991
|
+
/** Return the same single-flight snapshot used by every capability probe. */
|
|
2992
|
+
metadata() {
|
|
2993
|
+
return this.materialize();
|
|
2994
|
+
}
|
|
2726
2995
|
async keyField(sourceId) {
|
|
2727
2996
|
const keys = await this.keyFields(sourceId);
|
|
2728
2997
|
if (keys.length === 1)
|
|
@@ -2779,7 +3048,7 @@ function makeSource(descriptor, caps, _policy, adapters, impl) {
|
|
|
2779
3048
|
function lookupAdapter(kind) {
|
|
2780
3049
|
return adapters[kind];
|
|
2781
3050
|
}
|
|
2782
|
-
|
|
3051
|
+
const source = {
|
|
2783
3052
|
descriptor: { ...descriptor, capabilities: caps },
|
|
2784
3053
|
capabilities: caps,
|
|
2785
3054
|
query: impl.query.bind(impl),
|
|
@@ -2794,6 +3063,7 @@ function makeSource(descriptor, caps, _policy, adapters, impl) {
|
|
|
2794
3063
|
protocol: lookupAdapter,
|
|
2795
3064
|
adapter: lookupAdapter,
|
|
2796
3065
|
};
|
|
3066
|
+
return addCapabilitySupport(source, source.descriptor);
|
|
2797
3067
|
}
|
|
2798
3068
|
/**
|
|
2799
3069
|
* Helper: build a no-attachment-support `AttachmentApi` whose every method
|
|
@@ -2901,6 +3171,20 @@ function requireOgcLocator(descriptor) {
|
|
|
2901
3171
|
}
|
|
2902
3172
|
return { collectionId };
|
|
2903
3173
|
}
|
|
3174
|
+
/**
|
|
3175
|
+
* Map a `SourceLocator.layout` hint onto the OGC API Features endpoint
|
|
3176
|
+
* discovery mode. `undefined` / `honua-facade` keep the fixed facade fast
|
|
3177
|
+
* path; `ogc-api` and `auto` enable spec-driven landing-page discovery.
|
|
3178
|
+
* The STAC-specific modes are not applicable to Features and fall back to
|
|
3179
|
+
* the facade.
|
|
3180
|
+
*/
|
|
3181
|
+
function ogcFeaturesLayoutMode(layout) {
|
|
3182
|
+
if (layout === "ogc-api")
|
|
3183
|
+
return "ogc-api";
|
|
3184
|
+
if (layout === "auto")
|
|
3185
|
+
return "auto";
|
|
3186
|
+
return undefined;
|
|
3187
|
+
}
|
|
2904
3188
|
function requireOgcRecordsLocator(descriptor) {
|
|
2905
3189
|
const { collectionId } = descriptor.locator;
|
|
2906
3190
|
if (collectionId === undefined || collectionId === null || collectionId === "") {
|
|
@@ -2947,8 +3231,6 @@ function requireGPServiceLocator(descriptor, caps) {
|
|
|
2947
3231
|
// /rest/services/<serviceId>/GPServer/<taskName>/..., so descriptors that
|
|
2948
3232
|
// advertise the `geoprocess` capability must carry a task name. Without
|
|
2949
3233
|
// one the lifecycle routes resolve to non-existent paths on the server.
|
|
2950
|
-
// Descriptors with only `connect` (service-root metadata probe) may omit
|
|
2951
|
-
// taskName.
|
|
2952
3234
|
if (caps.has("geoprocess") && (typeof taskName !== "string" || taskName.length === 0)) {
|
|
2953
3235
|
throw new Error(`createDataset: source "${descriptor.id}" (geoservices-gp-service) advertises "geoprocess" but locator.taskName is missing; GP lifecycle routes require /GPServer/<taskName>/...`);
|
|
2954
3236
|
}
|
|
@@ -3110,6 +3392,24 @@ function toStacRequest(request, collectionScope) {
|
|
|
3110
3392
|
out.signal = request.signal;
|
|
3111
3393
|
return out;
|
|
3112
3394
|
}
|
|
3395
|
+
/**
|
|
3396
|
+
* Project a STAC API search request onto the client-side filter params a
|
|
3397
|
+
* static catalog traversal applies (it has no server-side query grammar).
|
|
3398
|
+
*/
|
|
3399
|
+
function toStacStaticParams(request) {
|
|
3400
|
+
const out = {};
|
|
3401
|
+
if (request.collections && request.collections.length > 0)
|
|
3402
|
+
out.collections = request.collections;
|
|
3403
|
+
if (request.bbox)
|
|
3404
|
+
out.bbox = request.bbox;
|
|
3405
|
+
if (request.datetime !== undefined)
|
|
3406
|
+
out.datetime = request.datetime;
|
|
3407
|
+
if (request.limit !== undefined)
|
|
3408
|
+
out.limit = request.limit;
|
|
3409
|
+
if (request.signal)
|
|
3410
|
+
out.signal = request.signal;
|
|
3411
|
+
return out;
|
|
3412
|
+
}
|
|
3113
3413
|
function toTypedFeatureFromStac(feature) {
|
|
3114
3414
|
return {
|
|
3115
3415
|
attributes: (feature.properties ?? {}),
|
|
@@ -3345,8 +3645,10 @@ function toOgcRequest(request) {
|
|
|
3345
3645
|
if (!request)
|
|
3346
3646
|
return {};
|
|
3347
3647
|
const out = {};
|
|
3348
|
-
if (request.where !== undefined)
|
|
3648
|
+
if (request.where !== undefined) {
|
|
3349
3649
|
out.filter = request.where;
|
|
3650
|
+
out.filterLang = "cql2-text";
|
|
3651
|
+
}
|
|
3350
3652
|
if (request.outFields && request.outFields.length > 0)
|
|
3351
3653
|
out.properties = [...request.outFields];
|
|
3352
3654
|
if (request.outSr !== undefined)
|
|
@@ -3551,10 +3853,10 @@ function readField(attributes, field) {
|
|
|
3551
3853
|
function toApplyEditsRequest(envelope) {
|
|
3552
3854
|
const out = {};
|
|
3553
3855
|
if (envelope.adds && envelope.adds.length > 0) {
|
|
3554
|
-
out.adds = envelope.adds.map(
|
|
3856
|
+
out.adds = envelope.adds.map(canonicalToHonuaFeature);
|
|
3555
3857
|
}
|
|
3556
3858
|
if (envelope.updates && envelope.updates.length > 0) {
|
|
3557
|
-
out.updates = envelope.updates.map(
|
|
3859
|
+
out.updates = envelope.updates.map(canonicalToHonuaFeature);
|
|
3558
3860
|
}
|
|
3559
3861
|
if (envelope.deletes && envelope.deletes.length > 0) {
|
|
3560
3862
|
const numericIds = [];
|
|
@@ -3799,4 +4101,5 @@ export const FIRST_PARTY_PROTOCOLS = new Set([
|
|
|
3799
4101
|
"wfs",
|
|
3800
4102
|
"wms",
|
|
3801
4103
|
"wmts",
|
|
4104
|
+
"pmtiles",
|
|
3802
4105
|
]);
|