@honua/react 0.1.0-beta.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 +11 -2
- package/contract/index.d.ts.map +1 -1
- package/contract/index.js +4 -1
- package/contract/pmtiles.d.ts +31 -0
- package/contract/pmtiles.d.ts.map +1 -1
- package/contract/pmtiles.js +60 -20
- 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 +35 -17
- package/contract/source.d.ts.map +1 -1
- package/contract/source.js +267 -71
- package/contract/types.d.ts +140 -4
- package/contract/types.d.ts.map +1 -1
- package/contract/types.js +6 -14
- 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 +4 -1
- package/core/client.d.ts.map +1 -1
- package/core/client.js +115 -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 +45 -29
- package/core/errors.d.ts.map +1 -1
- package/core/errors.js +97 -50
- package/core/geoparquet-sql.d.ts +30 -2
- package/core/geoparquet-sql.d.ts.map +1 -1
- package/core/geoparquet-sql.js +38 -3
- 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-features.d.ts.map +1 -1
- package/core/ogc-features.js +3 -0
- 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 +6 -2
- package/core/stac-static.d.ts.map +1 -1
- package/core/stac-static.js +260 -69
- package/core/surfaces.d.ts.map +1 -1
- package/core/surfaces.js +69 -5
- package/core/types.d.ts +125 -7
- package/core/types.d.ts.map +1 -1
- package/core/wfs-capabilities.d.ts +2 -0
- package/core/wfs-capabilities.d.ts.map +1 -1
- package/core/wfs-capabilities.js +8 -1
- package/core/wfs.d.ts.map +1 -1
- package/core/wfs.js +10 -0
- 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/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/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 +14 -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 +6 -0
- package/runtime/index.d.ts.map +1 -1
- package/runtime/index.js +3 -0
- 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/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 +9 -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
|
@@ -22,10 +22,11 @@ import { HonuaStacStaticCatalog } from "../core/stac-static.js";
|
|
|
22
22
|
import { HonuaStacSearch } from "../core/stac.js";
|
|
23
23
|
import { HonuaFeatureLayer, HonuaGeometryService, HonuaGeoprocessingService, HonuaImageService, HonuaMapLayer, HonuaMapService, HonuaOgcFeatureCollection, HonuaOgcFeatures, } from "../core/surfaces.js";
|
|
24
24
|
import { UNSUPPORTED_FES, compileSpatialFilter, compileWhere, geoJsonGeometryToGml, serializeFes, } from "../core/wfs-filter.js";
|
|
25
|
-
import {
|
|
25
|
+
import { HonuaWfsFeatureType } from "../core/wfs.js";
|
|
26
26
|
import { HonuaWms, HonuaWmsLayer, parseWmsLayerNames } from "../core/wms.js";
|
|
27
27
|
import { HonuaWmts, HonuaWmtsLayer, HonuaWmtsTileset } from "../core/wmts.js";
|
|
28
|
-
import {
|
|
28
|
+
import { pmtilesProtocolModule } from "./pmtiles.js";
|
|
29
|
+
import { addCapabilitySupport, normalizeCapabilityDescriptor } from "./source-capability-support.js";
|
|
29
30
|
import { CAPABILITIES, PROTOCOL_DEFAULT_CAPABILITIES, } from "./types.js";
|
|
30
31
|
/**
|
|
31
32
|
* Construct a protocol-neutral `Dataset` over a `HonuaClient`.
|
|
@@ -85,7 +86,8 @@ export function createDataset(options) {
|
|
|
85
86
|
const descriptors = new Map();
|
|
86
87
|
const handles = new Map();
|
|
87
88
|
let compatibilityPromise;
|
|
88
|
-
for (const
|
|
89
|
+
for (const input of sources) {
|
|
90
|
+
const descriptor = normalizeCapabilityDescriptor(input);
|
|
89
91
|
if (descriptors.has(descriptor.id)) {
|
|
90
92
|
throw new Error(`createDataset: duplicate source id "${descriptor.id}"`);
|
|
91
93
|
}
|
|
@@ -95,13 +97,14 @@ export function createDataset(options) {
|
|
|
95
97
|
const cached = handles.get(descriptor.id);
|
|
96
98
|
if (cached)
|
|
97
99
|
return cached;
|
|
98
|
-
const
|
|
99
|
-
|
|
100
|
+
const builtIn = buildBuiltInSource(descriptor, client, policy);
|
|
101
|
+
const built = builtIn ?? resolveSource?.(descriptor, { client, capabilityPolicy: policy });
|
|
100
102
|
if (!built) {
|
|
101
103
|
throw new HonuaCapabilityNotSupportedError("query", descriptor.protocol, descriptor.id);
|
|
102
104
|
}
|
|
103
|
-
|
|
104
|
-
|
|
105
|
+
const source = builtIn ?? addCapabilitySupport(built, descriptor);
|
|
106
|
+
handles.set(descriptor.id, source);
|
|
107
|
+
return source;
|
|
105
108
|
}
|
|
106
109
|
return {
|
|
107
110
|
id,
|
|
@@ -135,6 +138,15 @@ export function createDataset(options) {
|
|
|
135
138
|
// ── Built-in resolver ─────────────────────────────────────────
|
|
136
139
|
function buildBuiltInSource(descriptor, client, policy) {
|
|
137
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);
|
|
138
150
|
case "geoservices-feature-service":
|
|
139
151
|
return geoServicesFeatureSource(descriptor, client, policy);
|
|
140
152
|
case "geoservices-map-service":
|
|
@@ -196,9 +208,16 @@ function buildBuiltInSource(descriptor, client, policy) {
|
|
|
196
208
|
* ```
|
|
197
209
|
*/
|
|
198
210
|
export function geoServicesFeatureSource(descriptor, client, policy) {
|
|
211
|
+
descriptor = normalizeCapabilityDescriptor(descriptor);
|
|
199
212
|
const { serviceId, layerId } = requireFeatureServiceLocator(descriptor);
|
|
200
213
|
const layer = new HonuaFeatureLayer({ client, serviceId, layerId });
|
|
201
|
-
|
|
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];
|
|
202
221
|
const adapterRegistry = {
|
|
203
222
|
"geoservices-feature-service": layer,
|
|
204
223
|
};
|
|
@@ -287,6 +306,7 @@ export function geoServicesFeatureSource(descriptor, client, policy) {
|
|
|
287
306
|
* ```
|
|
288
307
|
*/
|
|
289
308
|
export function geoServicesMapServiceSource(descriptor, client, policy) {
|
|
309
|
+
descriptor = normalizeCapabilityDescriptor(descriptor);
|
|
290
310
|
const { serviceId, layerId } = requireMapServiceLocator(descriptor);
|
|
291
311
|
const service = new HonuaMapService({ client, serviceId });
|
|
292
312
|
const layer = new HonuaMapLayer({ client, serviceId, layerId });
|
|
@@ -381,6 +401,7 @@ export function geoServicesMapServiceSource(descriptor, client, policy) {
|
|
|
381
401
|
* ```
|
|
382
402
|
*/
|
|
383
403
|
export function ogcFeaturesSource(descriptor, client, policy) {
|
|
404
|
+
descriptor = normalizeCapabilityDescriptor(descriptor);
|
|
384
405
|
const { collectionId } = requireOgcLocator(descriptor);
|
|
385
406
|
const layoutMode = ogcFeaturesLayoutMode(descriptor.locator.layout);
|
|
386
407
|
const collection = new HonuaOgcFeatureCollection({
|
|
@@ -600,6 +621,7 @@ export function ogcFeaturesSource(descriptor, client, policy) {
|
|
|
600
621
|
* ```
|
|
601
622
|
*/
|
|
602
623
|
export function geoServicesImageSource(descriptor, client, policy) {
|
|
624
|
+
descriptor = normalizeCapabilityDescriptor(descriptor);
|
|
603
625
|
const { serviceId } = requireImageServiceLocator(descriptor);
|
|
604
626
|
const service = new HonuaImageService({ client, serviceId });
|
|
605
627
|
const caps = descriptor.capabilities ?? PROTOCOL_DEFAULT_CAPABILITIES["geoservices-image-service"];
|
|
@@ -709,6 +731,7 @@ export function geoServicesImageSource(descriptor, client, policy) {
|
|
|
709
731
|
* ```
|
|
710
732
|
*/
|
|
711
733
|
export function geoServicesGeometryServiceSource(descriptor, client, policy) {
|
|
734
|
+
descriptor = normalizeCapabilityDescriptor(descriptor);
|
|
712
735
|
const service = new HonuaGeometryService({ client });
|
|
713
736
|
const caps = descriptor.capabilities ?? PROTOCOL_DEFAULT_CAPABILITIES["geoservices-geometry-service"];
|
|
714
737
|
const adapterRegistry = {
|
|
@@ -731,6 +754,7 @@ export function geoServicesGeometryServiceSource(descriptor, client, policy) {
|
|
|
731
754
|
* ```
|
|
732
755
|
*/
|
|
733
756
|
export function geoServicesGPServiceSource(descriptor, client, policy) {
|
|
757
|
+
descriptor = normalizeCapabilityDescriptor(descriptor);
|
|
734
758
|
const caps = descriptor.capabilities ?? PROTOCOL_DEFAULT_CAPABILITIES["geoservices-gp-service"];
|
|
735
759
|
const { serviceId, taskName } = requireGPServiceLocator(descriptor, caps);
|
|
736
760
|
const service = new HonuaGeoprocessingService({
|
|
@@ -758,15 +782,19 @@ export function geoServicesGPServiceSource(descriptor, client, policy) {
|
|
|
758
782
|
* ```
|
|
759
783
|
*/
|
|
760
784
|
export function ogcTilesSource(descriptor, client, policy) {
|
|
785
|
+
descriptor = normalizeCapabilityDescriptor(descriptor);
|
|
761
786
|
const { collectionId, tileMatrixSetId } = requireOgcTilesLocator(descriptor);
|
|
762
787
|
const caps = descriptor.capabilities ?? PROTOCOL_DEFAULT_CAPABILITIES["ogc-tiles"];
|
|
788
|
+
const basePath = descriptor.locator.basePath;
|
|
763
789
|
// Descriptors without a tileMatrixSetId cannot construct a usable
|
|
764
790
|
// HonuaOgcTileset (every tile route requires `tileMatrixSetId`). Expose
|
|
765
791
|
// the root HonuaOgcTiles adapter instead so callers can discover the
|
|
766
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.
|
|
767
795
|
const adapter = tileMatrixSetId !== undefined && tileMatrixSetId !== ""
|
|
768
|
-
? new HonuaOgcTileset({ client, collectionId, tileMatrixSetId })
|
|
769
|
-
: new HonuaOgcTiles({ client });
|
|
796
|
+
? new HonuaOgcTileset({ client, collectionId, tileMatrixSetId, ...(basePath !== undefined ? { basePath } : {}) })
|
|
797
|
+
: new HonuaOgcTiles({ client, ...(basePath !== undefined ? { basePath } : {}) });
|
|
770
798
|
const adapterRegistry = {
|
|
771
799
|
"ogc-tiles": adapter,
|
|
772
800
|
};
|
|
@@ -787,6 +815,12 @@ export function ogcTilesSource(descriptor, client, policy) {
|
|
|
787
815
|
* `locator.url` may carry a leading `pmtiles://` scheme (the MapLibre form) or
|
|
788
816
|
* be a bare archive URL; both resolve to the same archive.
|
|
789
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
|
+
*
|
|
790
824
|
* @example
|
|
791
825
|
* ```ts
|
|
792
826
|
* const archive = dataset.source("basemap")!.protocol("pmtiles");
|
|
@@ -795,13 +829,18 @@ export function ogcTilesSource(descriptor, client, policy) {
|
|
|
795
829
|
* ```
|
|
796
830
|
*/
|
|
797
831
|
export function pmtilesSource(descriptor, _client, policy) {
|
|
798
|
-
|
|
799
|
-
const
|
|
800
|
-
|
|
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
|
+
}
|
|
801
840
|
const adapterRegistry = {
|
|
802
|
-
pmtiles:
|
|
841
|
+
pmtiles: discovered.adapter,
|
|
803
842
|
};
|
|
804
|
-
return makeSource(descriptor,
|
|
843
|
+
return makeSource(descriptor, discovered.capabilities, policy, adapterRegistry, unsupportedFeatureSurface(descriptor));
|
|
805
844
|
}
|
|
806
845
|
// ── OGC API Maps ──────────────────────────────────────────────
|
|
807
846
|
/**
|
|
@@ -816,13 +855,18 @@ export function pmtilesSource(descriptor, _client, policy) {
|
|
|
816
855
|
* ```
|
|
817
856
|
*/
|
|
818
857
|
export function ogcMapsSource(descriptor, client, policy) {
|
|
819
|
-
|
|
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 } : {}) });
|
|
820
863
|
const adapterRegistry = {};
|
|
821
864
|
if (descriptor.locator.collectionId !== undefined) {
|
|
822
865
|
adapterRegistry["ogc-maps"] = new HonuaOgcCollectionMap({
|
|
823
866
|
client,
|
|
824
867
|
collectionId: descriptor.locator.collectionId,
|
|
825
868
|
styleId: descriptor.locator.styleId,
|
|
869
|
+
...(basePath !== undefined ? { basePath } : {}),
|
|
826
870
|
});
|
|
827
871
|
}
|
|
828
872
|
else {
|
|
@@ -846,8 +890,14 @@ export function ogcMapsSource(descriptor, client, policy) {
|
|
|
846
890
|
* ```
|
|
847
891
|
*/
|
|
848
892
|
export function ogcRecordsSource(descriptor, client, policy) {
|
|
893
|
+
descriptor = normalizeCapabilityDescriptor(descriptor);
|
|
849
894
|
const { collectionId } = requireOgcRecordsLocator(descriptor);
|
|
850
|
-
const
|
|
895
|
+
const basePath = descriptor.locator.basePath;
|
|
896
|
+
const collection = new HonuaOgcRecordCollection({
|
|
897
|
+
client,
|
|
898
|
+
collectionId,
|
|
899
|
+
...(basePath !== undefined ? { basePath } : {}),
|
|
900
|
+
});
|
|
851
901
|
const caps = descriptor.capabilities ?? PROTOCOL_DEFAULT_CAPABILITIES["ogc-records"];
|
|
852
902
|
const adapterRegistry = {
|
|
853
903
|
"ogc-records": collection,
|
|
@@ -959,6 +1009,18 @@ export function ogcRecordsSource(descriptor, client, policy) {
|
|
|
959
1009
|
* ```
|
|
960
1010
|
*/
|
|
961
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
|
+
}
|
|
962
1024
|
const { serviceId } = requireWmsLocator(descriptor);
|
|
963
1025
|
const layerName = descriptor.locator.typeName;
|
|
964
1026
|
const styleId = descriptor.locator.styleId;
|
|
@@ -981,7 +1043,7 @@ export function wmsSource(descriptor, client, policy) {
|
|
|
981
1043
|
layerOpts.defaultStyleId = styleId;
|
|
982
1044
|
adapterRegistry["wms-layer"] = new HonuaWmsLayer(layerOpts);
|
|
983
1045
|
}
|
|
984
|
-
const caps =
|
|
1046
|
+
const caps = advertisedCaps;
|
|
985
1047
|
return makeSource(descriptor, caps, policy, adapterRegistry, {
|
|
986
1048
|
async query(request) {
|
|
987
1049
|
ensureCapability(descriptor, caps, "query");
|
|
@@ -1059,6 +1121,16 @@ export function wmsSource(descriptor, client, policy) {
|
|
|
1059
1121
|
* ```
|
|
1060
1122
|
*/
|
|
1061
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
|
+
}
|
|
1062
1134
|
const { serviceId } = requireWmtsLocator(descriptor);
|
|
1063
1135
|
const layerName = descriptor.locator.typeName;
|
|
1064
1136
|
const tileMatrixSetId = descriptor.locator.tileMatrixSetId ?? "WebMercatorQuad";
|
|
@@ -1081,7 +1153,6 @@ export function wmtsSource(descriptor, client, policy) {
|
|
|
1081
1153
|
tileMatrixSetId,
|
|
1082
1154
|
});
|
|
1083
1155
|
}
|
|
1084
|
-
const caps = descriptor.capabilities ?? PROTOCOL_DEFAULT_CAPABILITIES.wmts;
|
|
1085
1156
|
return makeSource(descriptor, caps, policy, adapterRegistry, unsupportedFeatureSurface(descriptor));
|
|
1086
1157
|
}
|
|
1087
1158
|
// ── STAC API ──────────────────────────────────────────────────
|
|
@@ -1114,6 +1185,7 @@ export function wmtsSource(descriptor, client, policy) {
|
|
|
1114
1185
|
* ```
|
|
1115
1186
|
*/
|
|
1116
1187
|
export function stacSearchSource(descriptor, client, policy) {
|
|
1188
|
+
descriptor = normalizeCapabilityDescriptor(descriptor);
|
|
1117
1189
|
// Endpoint-layout selection. `stac-api` treats `locator.url` as a raw STAC
|
|
1118
1190
|
// API root (search / collections mounted directly under it) rather than the
|
|
1119
1191
|
// Honua `/stac` facade. `stac-static` reads a static catalog.json tree with
|
|
@@ -1121,7 +1193,9 @@ export function stacSearchSource(descriptor, client, policy) {
|
|
|
1121
1193
|
const layout = descriptor.locator.layout;
|
|
1122
1194
|
const stacBasePath = layout === "stac-api" ? "" : undefined;
|
|
1123
1195
|
const stac = new HonuaStacSearch({ client, ...(stacBasePath !== undefined ? { basePath: stacBasePath } : {}) });
|
|
1124
|
-
const staticCatalog = layout === "stac-static"
|
|
1196
|
+
const staticCatalog = layout === "stac-static"
|
|
1197
|
+
? new HonuaStacStaticCatalog(client, descriptor.locator.url, descriptor.locator.stacStatic)
|
|
1198
|
+
: undefined;
|
|
1125
1199
|
const caps = descriptor.capabilities ?? PROTOCOL_DEFAULT_CAPABILITIES.stac;
|
|
1126
1200
|
const collectionScope = descriptor.locator.collectionId;
|
|
1127
1201
|
const adapterRegistry = {
|
|
@@ -1268,8 +1342,9 @@ const DEFAULT_WFS_GEOMETRY_PROPERTY = "the_geom";
|
|
|
1268
1342
|
* ```
|
|
1269
1343
|
*/
|
|
1270
1344
|
export function wfsSource(descriptor, client, policy) {
|
|
1345
|
+
descriptor = normalizeCapabilityDescriptor(descriptor);
|
|
1271
1346
|
const { url, typeName, featureNamespace, srsName: wfsSrsName } = requireWfsLocator(descriptor);
|
|
1272
|
-
const root =
|
|
1347
|
+
const root = client.wfs(url);
|
|
1273
1348
|
const featureType = new HonuaWfsFeatureType({ root, typeName });
|
|
1274
1349
|
const caps = descriptor.capabilities ?? PROTOCOL_DEFAULT_CAPABILITIES.wfs;
|
|
1275
1350
|
void policy;
|
|
@@ -1502,6 +1577,10 @@ export function wfsSource(descriptor, client, policy) {
|
|
|
1502
1577
|
summary = { totalInserted: 0, totalUpdated: 0, totalDeleted: 0, insertResults: [] };
|
|
1503
1578
|
}
|
|
1504
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);
|
|
1505
1584
|
const filtered = { ...envelope, updates: validUpdates };
|
|
1506
1585
|
const body = buildTransactionBody(typeName, filtered, featureNamespace, wfsSrsName);
|
|
1507
1586
|
const transactionOptions = { body };
|
|
@@ -1958,21 +2037,12 @@ function canonicalEditResultFromTransaction(envelope, summary, malformedUpdateIn
|
|
|
1958
2037
|
}));
|
|
1959
2038
|
return { added, updated, deleted };
|
|
1960
2039
|
}
|
|
1961
|
-
|
|
1962
|
-
/**
|
|
1963
|
-
|
|
1964
|
-
|
|
1965
|
-
|
|
1966
|
-
|
|
1967
|
-
* `Source.protocol("odata")` on the returned `HonuaOdataEntitySet`.
|
|
1968
|
-
*
|
|
1969
|
-
* `$metadata` is fetched lazily on the first call that needs it and
|
|
1970
|
-
* cached on the entity-set instance. The fetched capability annotations
|
|
1971
|
-
* are intersected with the descriptor's declared `Capabilities` set the
|
|
1972
|
-
* first time a capability-gated method is invoked — this is the
|
|
1973
|
-
* precedent for the metadata-driven downgrade pattern referenced in
|
|
1974
|
-
* `docs/shared-client-contract.md`.
|
|
1975
|
-
*/
|
|
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
|
+
}
|
|
1976
2046
|
/**
|
|
1977
2047
|
* Adapter factory for an OData v4 entity set.
|
|
1978
2048
|
*
|
|
@@ -2000,11 +2070,16 @@ function canonicalEditResultFromTransaction(envelope, summary, malformedUpdateIn
|
|
|
2000
2070
|
* });
|
|
2001
2071
|
* ```
|
|
2002
2072
|
*/
|
|
2003
|
-
export function odataSource(descriptor, client, policy) {
|
|
2073
|
+
export function odataSource(descriptor, client, policy, options = {}) {
|
|
2074
|
+
descriptor = normalizeCapabilityDescriptor(descriptor);
|
|
2004
2075
|
const { entitySet, basePath } = requireOdataLocator(descriptor);
|
|
2005
2076
|
const entity = new HonuaOdataEntitySet({ client, entitySet, basePath });
|
|
2006
2077
|
const declaredCaps = descriptor.capabilities ?? PROTOCOL_DEFAULT_CAPABILITIES.odata;
|
|
2007
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
|
+
}
|
|
2008
2083
|
const adapterRegistry = {
|
|
2009
2084
|
odata: entity,
|
|
2010
2085
|
};
|
|
@@ -2107,6 +2182,9 @@ export function odataSource(descriptor, client, policy) {
|
|
|
2107
2182
|
ensureCapability(descriptor, declaredCaps, "applyEdits");
|
|
2108
2183
|
await negotiation.ensureAdvertised(descriptor, "applyEdits");
|
|
2109
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;
|
|
2110
2188
|
// Atomic path: when the caller asks for rollback-on-failure AND the
|
|
2111
2189
|
// service advertises `$batch`, collapse the envelope into a single
|
|
2112
2190
|
// OData batch with a shared `atomicityGroup` so a later failure
|
|
@@ -2117,9 +2195,9 @@ export function odataSource(descriptor, client, policy) {
|
|
|
2117
2195
|
if (envelope.rollbackOnFailure === true) {
|
|
2118
2196
|
const batchAdvertised = await negotiation.batchAdvertised();
|
|
2119
2197
|
if (batchAdvertised) {
|
|
2120
|
-
return atomicOdataApplyEdits(entity, envelope, keyFields, descriptor);
|
|
2198
|
+
return atomicOdataApplyEdits(entity, envelope, keyFields, descriptor, prepared);
|
|
2121
2199
|
}
|
|
2122
|
-
const result = await perCallOdataApplyEdits(entity, envelope, keyFields);
|
|
2200
|
+
const result = await perCallOdataApplyEdits(entity, envelope, keyFields, prepared);
|
|
2123
2201
|
return {
|
|
2124
2202
|
...result,
|
|
2125
2203
|
degraded: [
|
|
@@ -2133,7 +2211,7 @@ export function odataSource(descriptor, client, policy) {
|
|
|
2133
2211
|
],
|
|
2134
2212
|
};
|
|
2135
2213
|
}
|
|
2136
|
-
return perCallOdataApplyEdits(entity, envelope, keyFields);
|
|
2214
|
+
return perCallOdataApplyEdits(entity, envelope, keyFields, prepared);
|
|
2137
2215
|
},
|
|
2138
2216
|
async queryRelated() {
|
|
2139
2217
|
// OData has no canonical related-records surface; navigation
|
|
@@ -2144,21 +2222,114 @@ export function odataSource(descriptor, client, policy) {
|
|
|
2144
2222
|
attachments: unsupportedAttachmentApi(descriptor),
|
|
2145
2223
|
});
|
|
2146
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
|
+
}
|
|
2147
2314
|
/**
|
|
2148
2315
|
* Per-call OData edits — issues independent POST/PATCH/DELETE requests
|
|
2149
2316
|
* and collects per-row outcomes. This is the non-atomic fallback used
|
|
2150
2317
|
* when `rollbackOnFailure` is unset or the server does not advertise
|
|
2151
2318
|
* `$batch`.
|
|
2152
2319
|
*/
|
|
2153
|
-
async function perCallOdataApplyEdits(entity, envelope, keyFields) {
|
|
2320
|
+
async function perCallOdataApplyEdits(entity, envelope, keyFields, prepared) {
|
|
2154
2321
|
const added = [];
|
|
2155
2322
|
const updated = [];
|
|
2156
2323
|
const deleted = [];
|
|
2157
2324
|
const { signal } = envelope;
|
|
2158
|
-
|
|
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];
|
|
2159
2329
|
try {
|
|
2160
|
-
const created = await entity.add(featureToOdataBody(add), {
|
|
2330
|
+
const created = await entity.add(encoded?.body ?? featureToOdataBody(add), {
|
|
2161
2331
|
...(signal ? { signal } : {}),
|
|
2332
|
+
...(encoded ? { contentType: encoded.contentType } : {}),
|
|
2162
2333
|
});
|
|
2163
2334
|
const id = readKey(created, keyFields, add.id);
|
|
2164
2335
|
added.push(id !== undefined ? { id, success: true } : { success: true });
|
|
@@ -2173,22 +2344,30 @@ async function perCallOdataApplyEdits(entity, envelope, keyFields) {
|
|
|
2173
2344
|
// using the full `keyFields` because the response body still carries
|
|
2174
2345
|
// every key.
|
|
2175
2346
|
const urlKeys = urlKeyFields(entity.entitySet, keyFields);
|
|
2176
|
-
|
|
2177
|
-
|
|
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);
|
|
2178
2352
|
if (key === undefined) {
|
|
2179
2353
|
updated.push({ success: false, error: { code: 400, description: "update.id is required" } });
|
|
2180
2354
|
continue;
|
|
2181
2355
|
}
|
|
2182
2356
|
try {
|
|
2183
|
-
await entity.update(key, featureToOdataBody(update), {
|
|
2357
|
+
await entity.update(key, encoded?.body ?? featureToOdataBody(update), {
|
|
2358
|
+
...(signal ? { signal } : {}),
|
|
2359
|
+
...(encoded ? { contentType: encoded.contentType } : {}),
|
|
2360
|
+
});
|
|
2184
2361
|
updated.push({ id: update.id ?? readKeyFromBody(update.attributes, keyFields), success: true });
|
|
2185
2362
|
}
|
|
2186
2363
|
catch (err) {
|
|
2187
2364
|
updated.push({ id: update.id, success: false, error: editErrorFromCatch(err) });
|
|
2188
2365
|
}
|
|
2189
2366
|
}
|
|
2190
|
-
|
|
2191
|
-
|
|
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);
|
|
2192
2371
|
if (key === undefined) {
|
|
2193
2372
|
deleted.push({ success: false, error: { code: 400, description: "delete id is required" } });
|
|
2194
2373
|
continue;
|
|
@@ -2211,7 +2390,7 @@ async function perCallOdataApplyEdits(entity, envelope, keyFields) {
|
|
|
2211
2390
|
* threaded back into per-row `EditOutcome` entries in the original
|
|
2212
2391
|
* order so callers see the same shape regardless of transport.
|
|
2213
2392
|
*/
|
|
2214
|
-
async function atomicOdataApplyEdits(entity, envelope, keyFields, descriptor) {
|
|
2393
|
+
async function atomicOdataApplyEdits(entity, envelope, keyFields, descriptor, prepared) {
|
|
2215
2394
|
const operations = [];
|
|
2216
2395
|
const plan = [];
|
|
2217
2396
|
const bucketIndices = { add: [], update: [], delete: [] };
|
|
@@ -2223,21 +2402,26 @@ async function atomicOdataApplyEdits(entity, envelope, keyFields, descriptor) {
|
|
|
2223
2402
|
// and the test surface stays small.
|
|
2224
2403
|
let seq = 1;
|
|
2225
2404
|
const adds = envelope.adds ?? [];
|
|
2226
|
-
for (
|
|
2405
|
+
for (let index = 0; index < adds.length; index += 1) {
|
|
2406
|
+
const add = adds[index];
|
|
2407
|
+
const encoded = prepared?.adds[index];
|
|
2227
2408
|
const id = String(seq++);
|
|
2228
2409
|
operations.push({
|
|
2229
2410
|
id,
|
|
2230
2411
|
method: "POST",
|
|
2231
2412
|
url: stripLeadingSlashLocal(entity.entitySet),
|
|
2232
|
-
body: featureToOdataBody(add),
|
|
2413
|
+
body: encoded?.body ?? featureToOdataBody(add),
|
|
2414
|
+
...(encoded ? { headers: { "Content-Type": encoded.contentType } } : {}),
|
|
2233
2415
|
});
|
|
2234
2416
|
bucketIndices.add.push(plan.length);
|
|
2235
2417
|
plan.push({ kind: "add", id: add.id });
|
|
2236
2418
|
}
|
|
2237
2419
|
const updates = envelope.updates ?? [];
|
|
2238
|
-
for (
|
|
2420
|
+
for (let index = 0; index < updates.length; index += 1) {
|
|
2421
|
+
const update = updates[index];
|
|
2422
|
+
const encoded = prepared?.updates[index];
|
|
2239
2423
|
const id = String(seq++);
|
|
2240
|
-
const key = canonicalKeyToOdata(update, urlKeys);
|
|
2424
|
+
const key = encoded?.key.literal ?? canonicalKeyToOdata(update, urlKeys);
|
|
2241
2425
|
if (key === undefined) {
|
|
2242
2426
|
bucketIndices.update.push(plan.length);
|
|
2243
2427
|
plan.push({ kind: "update", id: update.id });
|
|
@@ -2246,22 +2430,29 @@ async function atomicOdataApplyEdits(entity, envelope, keyFields, descriptor) {
|
|
|
2246
2430
|
operations.push({
|
|
2247
2431
|
id,
|
|
2248
2432
|
method: "PATCH",
|
|
2249
|
-
url: `${entity.entitySet}(${key})`,
|
|
2250
|
-
body: featureToOdataBody(update),
|
|
2433
|
+
url: `${entity.entitySet}(${encoded?.key.pathSegment ?? key})`,
|
|
2434
|
+
body: encoded?.body ?? featureToOdataBody(update),
|
|
2435
|
+
...(encoded ? { headers: { "Content-Type": encoded.contentType } } : {}),
|
|
2251
2436
|
});
|
|
2252
2437
|
bucketIndices.update.push(plan.length);
|
|
2253
2438
|
plan.push({ kind: "update", id: update.id, key });
|
|
2254
2439
|
}
|
|
2255
2440
|
const deletes = envelope.deletes ?? [];
|
|
2256
|
-
for (
|
|
2441
|
+
for (let index = 0; index < deletes.length; index += 1) {
|
|
2442
|
+
const rawId = deletes[index];
|
|
2443
|
+
const encoded = prepared?.deletes[index];
|
|
2257
2444
|
const id = String(seq++);
|
|
2258
|
-
const key = canonicalKeyToOdata({ id: rawId, attributes: {} }, urlKeys);
|
|
2445
|
+
const key = encoded?.key.literal ?? canonicalKeyToOdata({ id: rawId, attributes: {} }, urlKeys);
|
|
2259
2446
|
if (key === undefined) {
|
|
2260
2447
|
bucketIndices.delete.push(plan.length);
|
|
2261
2448
|
plan.push({ kind: "delete", id: rawId });
|
|
2262
2449
|
continue;
|
|
2263
2450
|
}
|
|
2264
|
-
operations.push({
|
|
2451
|
+
operations.push({
|
|
2452
|
+
id,
|
|
2453
|
+
method: "DELETE",
|
|
2454
|
+
url: `${entity.entitySet}(${encoded?.key.pathSegment ?? key})`,
|
|
2455
|
+
});
|
|
2265
2456
|
bucketIndices.delete.push(plan.length);
|
|
2266
2457
|
plan.push({ kind: "delete", id: rawId, key });
|
|
2267
2458
|
}
|
|
@@ -2552,6 +2743,13 @@ async function buildOdataParams(entity, descriptor, request, options) {
|
|
|
2552
2743
|
if (request.returnGeometry === false) {
|
|
2553
2744
|
rootSelect = rootSelect.filter((f) => !isOdataGeometryFieldName(f, options.geomColumn));
|
|
2554
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
|
+
}
|
|
2555
2753
|
if (rootSelect.length > 0)
|
|
2556
2754
|
out.select = rootSelect;
|
|
2557
2755
|
if (split.expand.length > 0)
|
|
@@ -2790,6 +2988,10 @@ class OdataCapabilityNegotiator {
|
|
|
2790
2988
|
return [];
|
|
2791
2989
|
return meta.keys[typeName] ?? [];
|
|
2792
2990
|
}
|
|
2991
|
+
/** Return the same single-flight snapshot used by every capability probe. */
|
|
2992
|
+
metadata() {
|
|
2993
|
+
return this.materialize();
|
|
2994
|
+
}
|
|
2793
2995
|
async keyField(sourceId) {
|
|
2794
2996
|
const keys = await this.keyFields(sourceId);
|
|
2795
2997
|
if (keys.length === 1)
|
|
@@ -2846,7 +3048,7 @@ function makeSource(descriptor, caps, _policy, adapters, impl) {
|
|
|
2846
3048
|
function lookupAdapter(kind) {
|
|
2847
3049
|
return adapters[kind];
|
|
2848
3050
|
}
|
|
2849
|
-
|
|
3051
|
+
const source = {
|
|
2850
3052
|
descriptor: { ...descriptor, capabilities: caps },
|
|
2851
3053
|
capabilities: caps,
|
|
2852
3054
|
query: impl.query.bind(impl),
|
|
@@ -2861,6 +3063,7 @@ function makeSource(descriptor, caps, _policy, adapters, impl) {
|
|
|
2861
3063
|
protocol: lookupAdapter,
|
|
2862
3064
|
adapter: lookupAdapter,
|
|
2863
3065
|
};
|
|
3066
|
+
return addCapabilitySupport(source, source.descriptor);
|
|
2864
3067
|
}
|
|
2865
3068
|
/**
|
|
2866
3069
|
* Helper: build a no-attachment-support `AttachmentApi` whose every method
|
|
@@ -2989,13 +3192,6 @@ function requireOgcRecordsLocator(descriptor) {
|
|
|
2989
3192
|
}
|
|
2990
3193
|
return { collectionId };
|
|
2991
3194
|
}
|
|
2992
|
-
function requirePmtilesLocator(descriptor) {
|
|
2993
|
-
const { url } = descriptor.locator;
|
|
2994
|
-
if (typeof url !== "string" || url === "") {
|
|
2995
|
-
throw new Error(`createDataset: source "${descriptor.id}" (pmtiles) requires locator.url`);
|
|
2996
|
-
}
|
|
2997
|
-
return url;
|
|
2998
|
-
}
|
|
2999
3195
|
function requireImageServiceLocator(descriptor) {
|
|
3000
3196
|
const { serviceId } = descriptor.locator;
|
|
3001
3197
|
if (typeof serviceId !== "string") {
|
|
@@ -3035,8 +3231,6 @@ function requireGPServiceLocator(descriptor, caps) {
|
|
|
3035
3231
|
// /rest/services/<serviceId>/GPServer/<taskName>/..., so descriptors that
|
|
3036
3232
|
// advertise the `geoprocess` capability must carry a task name. Without
|
|
3037
3233
|
// one the lifecycle routes resolve to non-existent paths on the server.
|
|
3038
|
-
// Descriptors with only `connect` (service-root metadata probe) may omit
|
|
3039
|
-
// taskName.
|
|
3040
3234
|
if (caps.has("geoprocess") && (typeof taskName !== "string" || taskName.length === 0)) {
|
|
3041
3235
|
throw new Error(`createDataset: source "${descriptor.id}" (geoservices-gp-service) advertises "geoprocess" but locator.taskName is missing; GP lifecycle routes require /GPServer/<taskName>/...`);
|
|
3042
3236
|
}
|
|
@@ -3451,8 +3645,10 @@ function toOgcRequest(request) {
|
|
|
3451
3645
|
if (!request)
|
|
3452
3646
|
return {};
|
|
3453
3647
|
const out = {};
|
|
3454
|
-
if (request.where !== undefined)
|
|
3648
|
+
if (request.where !== undefined) {
|
|
3455
3649
|
out.filter = request.where;
|
|
3650
|
+
out.filterLang = "cql2-text";
|
|
3651
|
+
}
|
|
3456
3652
|
if (request.outFields && request.outFields.length > 0)
|
|
3457
3653
|
out.properties = [...request.outFields];
|
|
3458
3654
|
if (request.outSr !== undefined)
|
|
@@ -3657,10 +3853,10 @@ function readField(attributes, field) {
|
|
|
3657
3853
|
function toApplyEditsRequest(envelope) {
|
|
3658
3854
|
const out = {};
|
|
3659
3855
|
if (envelope.adds && envelope.adds.length > 0) {
|
|
3660
|
-
out.adds = envelope.adds.map(
|
|
3856
|
+
out.adds = envelope.adds.map(canonicalToHonuaFeature);
|
|
3661
3857
|
}
|
|
3662
3858
|
if (envelope.updates && envelope.updates.length > 0) {
|
|
3663
|
-
out.updates = envelope.updates.map(
|
|
3859
|
+
out.updates = envelope.updates.map(canonicalToHonuaFeature);
|
|
3664
3860
|
}
|
|
3665
3861
|
if (envelope.deletes && envelope.deletes.length > 0) {
|
|
3666
3862
|
const numericIds = [];
|