@honua/react 0.1.7-beta.0 → 0.1.10-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/control-plane/admin-client.d.ts +89 -0
- package/control-plane/admin-client.d.ts.map +1 -0
- package/control-plane/admin-client.js +200 -0
- package/control-plane/client.d.ts +1 -1
- package/control-plane/client.d.ts.map +1 -1
- package/control-plane/client.js +145 -11
- package/control-plane/commands/catalog.d.ts +133 -0
- package/control-plane/commands/catalog.d.ts.map +1 -0
- package/control-plane/commands/catalog.js +365 -0
- package/control-plane/commands/errors.d.ts +96 -0
- package/control-plane/commands/errors.d.ts.map +1 -0
- package/control-plane/commands/errors.js +130 -0
- package/control-plane/commands/identity.d.ts +71 -0
- package/control-plane/commands/identity.d.ts.map +1 -0
- package/control-plane/commands/identity.js +0 -0
- package/control-plane/commands/index.d.ts +39 -0
- package/control-plane/commands/index.d.ts.map +1 -0
- package/control-plane/commands/index.js +34 -0
- package/control-plane/commands/runtime.d.ts +146 -0
- package/control-plane/commands/runtime.d.ts.map +1 -0
- package/control-plane/commands/runtime.js +330 -0
- package/control-plane/commands/types.d.ts +228 -0
- package/control-plane/commands/types.d.ts.map +1 -0
- package/control-plane/commands/types.js +30 -0
- package/control-plane/commands/validate.d.ts +27 -0
- package/control-plane/commands/validate.d.ts.map +1 -0
- package/control-plane/commands/validate.js +132 -0
- package/control-plane/generated/admin-api.d.ts +30265 -0
- package/control-plane/generated/admin-api.d.ts.map +1 -0
- package/control-plane/generated/admin-api.js +5 -0
- package/control-plane/generated/admin-operations.d.ts +4053 -0
- package/control-plane/generated/admin-operations.d.ts.map +1 -0
- package/control-plane/generated/admin-operations.js +820 -0
- package/control-plane/index.d.ts +11 -0
- package/control-plane/index.d.ts.map +1 -1
- package/control-plane/index.js +8 -0
- package/control-plane/types.d.ts +14 -0
- package/control-plane/types.d.ts.map +1 -1
- package/core/client.d.ts.map +1 -1
- package/core/client.js +29 -31
- package/core/credential-redaction.d.ts +163 -0
- package/core/credential-redaction.d.ts.map +1 -0
- package/core/credential-redaction.js +389 -0
- package/core/error-classifications.d.ts +15 -0
- package/core/error-classifications.d.ts.map +1 -1
- package/core/error-classifications.js +15 -0
- package/core/error-code-registry.d.ts +15 -0
- package/core/error-code-registry.d.ts.map +1 -1
- package/core/error-code-registry.js +15 -0
- package/core/error-envelope.d.ts +31 -0
- package/core/error-envelope.d.ts.map +1 -1
- package/core/error-envelope.js +11 -0
- package/core/errors.d.ts +15 -3
- package/core/errors.d.ts.map +1 -1
- package/core/errors.js +254 -2
- package/core/geometry-input-sr.d.ts +3 -0
- package/core/geometry-input-sr.d.ts.map +1 -0
- package/core/geometry-input-sr.js +28 -0
- package/core/geoservices.d.ts.map +1 -1
- package/core/geoservices.js +33 -6
- package/core/grpc-adapter.d.ts +2 -2
- package/core/grpc-adapter.d.ts.map +1 -1
- package/core/grpc-adapter.js +41 -9
- package/core/job-run-errors.d.ts +10 -0
- package/core/job-run-errors.d.ts.map +1 -0
- package/core/job-run-errors.js +14 -0
- package/core/job-run-lifecycle.d.ts +34 -0
- package/core/job-run-lifecycle.d.ts.map +1 -0
- package/core/job-run-lifecycle.js +193 -0
- package/core/ogc-processes.d.ts +38 -31
- package/core/ogc-processes.d.ts.map +1 -1
- package/core/ogc-processes.js +123 -173
- package/core/pbf-decoder.d.ts.map +1 -1
- package/core/pbf-decoder.js +16 -4
- package/core/process-runner.js +15 -100
- package/core/protocol-transport.d.ts +6 -1
- package/core/protocol-transport.d.ts.map +1 -1
- package/core/request-pipeline.d.ts +6 -3
- package/core/request-pipeline.d.ts.map +1 -1
- package/core/request-pipeline.js +28 -11
- package/core/spatial-filter.d.ts +4 -4
- package/core/spatial-filter.d.ts.map +1 -1
- package/core/spatial-filter.js +7 -7
- package/core/surfaces.d.ts +2 -8
- package/core/surfaces.d.ts.map +1 -1
- package/core/surfaces.js +21 -112
- package/core/types.d.ts +21 -9
- package/core/types.d.ts.map +1 -1
- package/esri-compat/class-breaks-renderer.d.ts +2 -0
- package/esri-compat/class-breaks-renderer.d.ts.map +1 -1
- package/esri-compat/class-breaks-renderer.js +7 -0
- package/esri-compat/controls.d.ts +10 -1
- package/esri-compat/controls.d.ts.map +1 -1
- package/esri-compat/controls.js +86 -2
- package/esri-compat/directions.d.ts +35 -0
- package/esri-compat/directions.d.ts.map +1 -1
- package/esri-compat/directions.js +153 -2
- package/esri-compat/expand.d.ts +1 -0
- package/esri-compat/expand.d.ts.map +1 -1
- package/esri-compat/expand.js +61 -0
- package/esri-compat/feature-layer.d.ts +29 -4
- package/esri-compat/feature-layer.d.ts.map +1 -1
- package/esri-compat/feature-layer.js +335 -13
- package/esri-compat/geometry-engine.d.ts +4 -0
- package/esri-compat/geometry-engine.d.ts.map +1 -1
- package/esri-compat/geometry-engine.js +8 -1
- package/esri-compat/graphic.d.ts +7 -4
- package/esri-compat/graphic.d.ts.map +1 -1
- package/esri-compat/legend.d.ts +2 -1
- package/esri-compat/legend.d.ts.map +1 -1
- package/esri-compat/legend.js +15 -0
- package/esri-compat/map-view-mount.d.ts +48 -0
- package/esri-compat/map-view-mount.d.ts.map +1 -0
- package/esri-compat/map-view-mount.js +344 -0
- package/esri-compat/map-view.d.ts +38 -6
- package/esri-compat/map-view.d.ts.map +1 -1
- package/esri-compat/map-view.js +467 -33
- package/esri-compat/portal.d.ts +2 -0
- package/esri-compat/portal.d.ts.map +1 -1
- package/esri-compat/portal.js +7 -2
- package/esri-compat/renderer-objects.d.ts +7 -2
- package/esri-compat/renderer-objects.d.ts.map +1 -1
- package/esri-compat/renderer-objects.js +16 -5
- package/esri-compat/route-layer.d.ts +8 -0
- package/esri-compat/route-layer.d.ts.map +1 -1
- package/esri-compat/route-layer.js +24 -4
- package/esri-compat/route-task.d.ts +5 -1
- package/esri-compat/route-task.d.ts.map +1 -1
- package/esri-compat/route-task.js +119 -7
- package/esri-compat/scene-view.d.ts.map +1 -1
- package/esri-compat/scene-view.js +1 -0
- package/esri-compat/search.d.ts +2 -1
- package/esri-compat/search.d.ts.map +1 -1
- package/esri-compat/search.js +52 -2
- package/esri-compat/unique-value-renderer.d.ts +2 -0
- package/esri-compat/unique-value-renderer.d.ts.map +1 -1
- package/esri-compat/unique-value-renderer.js +7 -0
- package/esri-compat/web-map.d.ts +10 -0
- package/esri-compat/web-map.d.ts.map +1 -1
- package/esri-compat/web-map.js +102 -0
- package/gen/geospatial/v1/common_pb.d.ts +514 -0
- package/gen/geospatial/v1/common_pb.d.ts.map +1 -0
- package/gen/geospatial/v1/common_pb.js +335 -0
- package/gen/geospatial/v1/execution_types_pb.d.ts +1186 -0
- package/gen/geospatial/v1/execution_types_pb.d.ts.map +1 -0
- package/gen/geospatial/v1/execution_types_pb.js +396 -0
- package/gen/geospatial/v1/feature_service_pb.d.ts +403 -0
- package/gen/geospatial/v1/feature_service_pb.d.ts.map +1 -0
- package/gen/geospatial/v1/feature_service_pb.js +47 -0
- package/gen/geospatial/v1/index.d.ts +5 -0
- package/gen/geospatial/v1/index.d.ts.map +1 -0
- package/gen/geospatial/v1/index.js +9 -0
- package/gen/geospatial/v1/spatial_types_pb.d.ts +285 -0
- package/gen/geospatial/v1/spatial_types_pb.d.ts.map +1 -0
- package/gen/geospatial/v1/spatial_types_pb.js +58 -0
- package/gen/geospatial/v1/workspace_artifact_types_pb.d.ts +379 -0
- package/gen/geospatial/v1/workspace_artifact_types_pb.d.ts.map +1 -0
- package/gen/geospatial/v1/workspace_artifact_types_pb.js +151 -0
- package/generated-app/projection.d.ts.map +1 -1
- package/generated-app/projection.js +10 -1
- package/map/webmap-maplibre.d.ts +2 -1
- package/map/webmap-maplibre.d.ts.map +1 -1
- package/map/webmap-maplibre.js +3 -0
- package/package.json +3 -3
- package/query-planner/geoservices.d.ts.map +1 -1
- package/query-planner/geoservices.js +2 -0
- package/query-planner/grpc.d.ts +4 -4
- package/query-planner/grpc.d.ts.map +1 -1
- package/query-planner/grpc.js +11 -11
- package/query-planner/types.d.ts +6 -4
- package/query-planner/types.d.ts.map +1 -1
- package/realtime/honua-server.d.ts +4 -0
- package/realtime/honua-server.d.ts.map +1 -1
- package/realtime/honua-server.js +10 -0
- package/realtime/resumable.js +9 -0
- package/realtime/types.d.ts +8 -0
- package/realtime/types.d.ts.map +1 -1
- package/runtime/errors.d.ts +1 -1
- package/runtime/errors.d.ts.map +1 -1
- package/runtime/errors.js +2 -0
- package/runtime/generated/map-package-schema-meta.d.ts +11 -0
- package/runtime/generated/map-package-schema-meta.d.ts.map +1 -0
- package/runtime/generated/map-package-schema-meta.js +12 -0
- package/runtime/generated/map-package-schema-validator.d.ts +16 -0
- package/runtime/generated/map-package-schema-validator.d.ts.map +1 -0
- package/runtime/generated/map-package-schema-validator.js +1422 -0
- package/runtime/index.d.ts +5 -1
- package/runtime/index.d.ts.map +1 -1
- package/runtime/index.js +6 -0
- package/runtime/map-package-export.d.ts +151 -0
- package/runtime/map-package-export.d.ts.map +1 -0
- package/runtime/map-package-export.js +636 -0
- package/runtime/map-package-validation.d.ts +21 -1
- package/runtime/map-package-validation.d.ts.map +1 -1
- package/runtime/map-package-validation.js +132 -95
- package/runtime/map-package.d.ts +80 -0
- package/runtime/map-package.d.ts.map +1 -1
- package/studio/capability-manifest.d.ts +10 -0
- package/studio/capability-manifest.d.ts.map +1 -1
- package/studio/capability-manifest.js +38 -0
- package/studio/chart-spec.d.ts +6 -6
- package/studio/chart-spec.d.ts.map +1 -1
- package/studio/chart-spec.js +6 -6
- package/studio/command-adapter.d.ts +35 -0
- package/studio/command-adapter.d.ts.map +1 -0
- package/studio/command-adapter.js +36 -0
- package/studio/generated/studio-api.d.ts +1062 -0
- package/studio/generated/studio-api.d.ts.map +1 -0
- package/studio/generated/studio-api.js +4 -0
- package/studio/index.d.ts +25 -13
- package/studio/index.d.ts.map +1 -1
- package/studio/index.js +21 -10
- package/studio/lifecycle-client.d.ts +262 -22
- package/studio/lifecycle-client.d.ts.map +1 -1
- package/studio/lifecycle-client.js +543 -24
- package/studio/lifecycle-errors.d.ts +8 -2
- package/studio/lifecycle-errors.d.ts.map +1 -1
- package/studio/lifecycle-errors.js +6 -1
- package/studio/lifecycle-types.d.ts +237 -38
- package/studio/lifecycle-types.d.ts.map +1 -1
- package/studio/lifecycle-types.js +5 -12
- package/studio/publication-status.d.ts +55 -0
- package/studio/publication-status.d.ts.map +1 -0
- package/studio/publication-status.js +103 -0
- package/studio/types.d.ts +37 -7
- package/studio/types.d.ts.map +1 -1
- package/studio/types.js +5 -3
- package/studio/validate.d.ts.map +1 -1
- package/studio/validate.js +104 -7
- package/style/renderers.d.ts +6 -1
- package/style/renderers.d.ts.map +1 -1
- package/style/renderers.js +11 -6
- package/style/visual-variables.d.ts +43 -0
- package/style/visual-variables.d.ts.map +1 -0
- package/style/visual-variables.js +183 -0
- package/webmap/convert-layer.d.ts +2 -1
- package/webmap/convert-layer.d.ts.map +1 -1
- package/webmap/convert-layer.js +20 -3
- package/webmap/convert-renderer.d.ts +10 -3
- package/webmap/convert-renderer.d.ts.map +1 -1
- package/webmap/convert-renderer.js +190 -36
- package/webmap/index.d.ts +1 -0
- package/webmap/index.d.ts.map +1 -1
- package/webmap/parse.d.ts +2 -1
- package/webmap/parse.d.ts.map +1 -1
- package/webmap/parse.js +1 -1
- package/gen/honua/v1/feature_service_pb.d.ts +0 -918
- package/gen/honua/v1/feature_service_pb.d.ts.map +0 -1
- package/gen/honua/v1/feature_service_pb.js +0 -375
package/core/ogc-processes.js
CHANGED
|
@@ -23,10 +23,9 @@
|
|
|
23
23
|
*
|
|
24
24
|
* @module
|
|
25
25
|
*/
|
|
26
|
-
import {
|
|
27
|
-
import { HonuaSdkError } from "./error-envelope.js";
|
|
26
|
+
import { isJobTerminal } from "../contract/jobs.js";
|
|
28
27
|
import { HonuaCapabilityNotSupportedError } from "./errors.js";
|
|
29
|
-
import {
|
|
28
|
+
import { JobRunLifecycle } from "./job-run-lifecycle.js";
|
|
30
29
|
import { createOgcMetadataParams, mergeHeaders } from "./wire-shared.js";
|
|
31
30
|
const DEFAULT_POLL_INTERVAL_MS = 1_000;
|
|
32
31
|
/**
|
|
@@ -35,7 +34,6 @@ const DEFAULT_POLL_INTERVAL_MS = 1_000;
|
|
|
35
34
|
* never reaches a terminal state must not poll forever. Callers who genuinely
|
|
36
35
|
* want a longer wait pass their own `deadlineMs`.
|
|
37
36
|
*/
|
|
38
|
-
const DEFAULT_POLL_DEADLINE_MS = 600_000;
|
|
39
37
|
/** Honua facade path prefix for OGC API Processes. */
|
|
40
38
|
const PROCESSES_FACADE_BASE = "/ogc/processes";
|
|
41
39
|
/**
|
|
@@ -49,7 +47,11 @@ const PROCESSES_CONFORMANCE = {
|
|
|
49
47
|
core: "http://www.opengis.net/spec/ogcapi-processes-1/1.0/conf/core",
|
|
50
48
|
dismiss: "http://www.opengis.net/spec/ogcapi-processes-1/1.0/conf/dismiss",
|
|
51
49
|
};
|
|
52
|
-
/**
|
|
50
|
+
/**
|
|
51
|
+
* Suffix matchers tolerant of a server publishing the class under a different
|
|
52
|
+
* host or path prefix, but *not* of a different class that merely starts with
|
|
53
|
+
* one of these. See {@link declaresConformanceClass}.
|
|
54
|
+
*/
|
|
53
55
|
const PROCESSES_CONFORMANCE_MATCH = {
|
|
54
56
|
core: "processes-1/1.0/conf/core",
|
|
55
57
|
dismiss: "processes-1/1.0/conf/dismiss",
|
|
@@ -77,6 +79,36 @@ const JOB_CONTROL = {
|
|
|
77
79
|
function capabilityRefusal(capability, sourceId, context) {
|
|
78
80
|
return new HonuaCapabilityNotSupportedError(capability, "ogc-processes", sourceId, { context });
|
|
79
81
|
}
|
|
82
|
+
/**
|
|
83
|
+
* `true` when the server declares exactly the named conformance class.
|
|
84
|
+
*
|
|
85
|
+
* The public `hasOgcConformanceClass()` is a documented *substring* test, which
|
|
86
|
+
* is the right tool for a caller probing an extension they already recognise.
|
|
87
|
+
* It is the wrong tool for a fail-closed gate: an unrecognised class that
|
|
88
|
+
* happens to extend one of ours — `…/conf/dismiss-disabled`, `…/conf/core-lite`,
|
|
89
|
+
* or a vendor class carrying the URI in a query string — would widen what the
|
|
90
|
+
* client is willing to send. So the gate anchors the match at the end of the
|
|
91
|
+
* class URI (ignoring a trailing slash, query, or fragment): a prefix the
|
|
92
|
+
* server chose is still tolerated, an unknown longer class is not.
|
|
93
|
+
*/
|
|
94
|
+
function declaresConformanceClass(conformance, classSuffix) {
|
|
95
|
+
for (const uri of conformance?.conformsTo ?? []) {
|
|
96
|
+
if (typeof uri !== "string")
|
|
97
|
+
continue;
|
|
98
|
+
const withoutFragment = uri.split("#")[0] ?? "";
|
|
99
|
+
let path = withoutFragment.split("?")[0] ?? "";
|
|
100
|
+
// Trailing slashes are trimmed by scanning, not by a `/+$/` regex: this
|
|
101
|
+
// string is server input, and that pattern backtracks on a URI made of
|
|
102
|
+
// many slashes.
|
|
103
|
+
let end = path.length;
|
|
104
|
+
while (end > 0 && path.charCodeAt(end - 1) === 47)
|
|
105
|
+
end -= 1;
|
|
106
|
+
path = path.slice(0, end);
|
|
107
|
+
if (path.endsWith(classSuffix))
|
|
108
|
+
return true;
|
|
109
|
+
}
|
|
110
|
+
return false;
|
|
111
|
+
}
|
|
80
112
|
/** `true` when the declaration list contains `option` (case-insensitive). */
|
|
81
113
|
function declares(options, option) {
|
|
82
114
|
if (!options)
|
|
@@ -226,9 +258,12 @@ export class HonuaOgcProcesses {
|
|
|
226
258
|
declaredConformance;
|
|
227
259
|
conformancePromise;
|
|
228
260
|
/**
|
|
229
|
-
* `jobControlOptions` learned from `describe()` calls made through
|
|
230
|
-
* handle. Callers that follow the natural discover → describe → execute
|
|
231
|
-
* therefore get process-level gating for free, with no extra round trip.
|
|
261
|
+
* `jobControlOptions` learned from `describe()` / `list()` calls made through
|
|
262
|
+
* this handle. Callers that follow the natural discover → describe → execute
|
|
263
|
+
* flow therefore get process-level gating for free, with no extra round trip.
|
|
264
|
+
*
|
|
265
|
+
* Each entry remembers which source taught it, because a later listing must
|
|
266
|
+
* not be able to downgrade what a description already established.
|
|
232
267
|
*/
|
|
233
268
|
jobControlByProcess = new Map();
|
|
234
269
|
constructor(options) {
|
|
@@ -253,19 +288,36 @@ export class HonuaOgcProcesses {
|
|
|
253
288
|
async list(request = {}) {
|
|
254
289
|
const response = await this.client.listOgcProcesses(this.withBase(request));
|
|
255
290
|
for (const process of response?.processes ?? []) {
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
291
|
+
// A summary that omits `jobControlOptions` advertises no execution mode;
|
|
292
|
+
// recorded as an empty declaration so the mode gate refuses rather than
|
|
293
|
+
// reading silence as consent — but only where nothing better is known.
|
|
294
|
+
if (process?.id)
|
|
295
|
+
this.rememberJobControl(process.id, process.jobControlOptions, "summary");
|
|
259
296
|
}
|
|
260
297
|
return response;
|
|
261
298
|
}
|
|
262
299
|
async describe(processId, request = {}) {
|
|
263
300
|
const description = await this.client.getOgcProcess(this.withBase({ ...request, processId }));
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
301
|
+
// As in `list()`: an omitted `jobControlOptions` is an empty declaration.
|
|
302
|
+
// A description is the authoritative source, so this always wins.
|
|
303
|
+
this.rememberJobControl(processId, description?.jobControlOptions, "description");
|
|
267
304
|
return description;
|
|
268
305
|
}
|
|
306
|
+
/**
|
|
307
|
+
* Record what a process advertises, keeping the more authoritative source.
|
|
308
|
+
*
|
|
309
|
+
* Silence is still not consent — an omitted `jobControlOptions` is stored as
|
|
310
|
+
* an empty declaration, and an explicit mode against it is refused. But a
|
|
311
|
+
* process summary may legally omit what the full description declares, so a
|
|
312
|
+
* `list()` refresh after a `describe()` must not overwrite the description's
|
|
313
|
+
* answer with the listing's silence. Refusing an execution the server did
|
|
314
|
+
* declare is as wrong as permitting one it did not.
|
|
315
|
+
*/
|
|
316
|
+
rememberJobControl(processId, options, source) {
|
|
317
|
+
if (source === "summary" && this.jobControlByProcess.get(processId)?.source === "description")
|
|
318
|
+
return;
|
|
319
|
+
this.jobControlByProcess.set(processId, { options: [...(options ?? [])], source });
|
|
320
|
+
}
|
|
269
321
|
/**
|
|
270
322
|
* Submit a process for execution and return an `IJobRun` — always, so the
|
|
271
323
|
* caller reads results through one surface whichever Core response shape the
|
|
@@ -289,7 +341,7 @@ export class HonuaOgcProcesses {
|
|
|
289
341
|
const conformance = await this.assertExecutionDeclared(request);
|
|
290
342
|
const accepted = await this.client.executeOgcProcess(executeRequest);
|
|
291
343
|
const processId = accepted.processID ?? request.processId;
|
|
292
|
-
const jobControlOptions = request.jobControlOptions ?? this.jobControlByProcess.get(request.processId);
|
|
344
|
+
const jobControlOptions = request.jobControlOptions ?? this.jobControlByProcess.get(request.processId)?.options;
|
|
293
345
|
if (accepted.synchronous === true) {
|
|
294
346
|
// Core assigns no job identifier to a synchronous execution; the results
|
|
295
347
|
// document is already in hand, so the run starts terminal.
|
|
@@ -317,7 +369,7 @@ export class HonuaOgcProcesses {
|
|
|
317
369
|
/** Adopt an existing job by id (useful when reconnecting after navigation). */
|
|
318
370
|
job(jobId, options = {}) {
|
|
319
371
|
const { basePath } = this.withBase(options);
|
|
320
|
-
const jobControlOptions = options.processId ? this.jobControlByProcess.get(options.processId) : undefined;
|
|
372
|
+
const jobControlOptions = options.processId ? this.jobControlByProcess.get(options.processId)?.options : undefined;
|
|
321
373
|
return new HonuaOgcProcessJobRun({
|
|
322
374
|
client: this.client,
|
|
323
375
|
jobId,
|
|
@@ -364,7 +416,11 @@ export class HonuaOgcProcesses {
|
|
|
364
416
|
*/
|
|
365
417
|
async assertExecutionDeclared(request) {
|
|
366
418
|
const conformance = await this.resolveConformance(request.signal);
|
|
367
|
-
|
|
419
|
+
// Under `"strict"` the declaration was fetched, so silence is an answer:
|
|
420
|
+
// a document that declares no Core class — or none at all — refuses. Under
|
|
421
|
+
// `"advertised"` only a declaration actually in hand can refuse.
|
|
422
|
+
const declarationInHand = conformance?.conformsTo !== undefined || this.capabilityPolicy === "strict";
|
|
423
|
+
if (declarationInHand && !declaresConformanceClass(conformance, PROCESSES_CONFORMANCE_MATCH.core)) {
|
|
368
424
|
throw capabilityRefusal("processes.execute", request.processId, {
|
|
369
425
|
missingClass: PROCESSES_CONFORMANCE.core,
|
|
370
426
|
construct: "execute",
|
|
@@ -375,11 +431,15 @@ export class HonuaOgcProcesses {
|
|
|
375
431
|
// shapes land on the same `IJobRun`. Only an explicit preference is gated.
|
|
376
432
|
if (mode === "auto")
|
|
377
433
|
return conformance;
|
|
378
|
-
let jobControlOptions = request.jobControlOptions ?? this.jobControlByProcess.get(request.processId);
|
|
434
|
+
let jobControlOptions = request.jobControlOptions ?? this.jobControlByProcess.get(request.processId)?.options;
|
|
379
435
|
if (!jobControlOptions && this.capabilityPolicy === "strict") {
|
|
380
436
|
const description = await this.describe(request.processId, request.signal ? { signal: request.signal } : {});
|
|
381
|
-
jobControlOptions
|
|
437
|
+
// An omitted `jobControlOptions` is an empty declaration, never a wildcard:
|
|
438
|
+
// the server was asked and advertised no execution mode at all.
|
|
439
|
+
jobControlOptions = description.jobControlOptions ?? [];
|
|
382
440
|
}
|
|
441
|
+
// Only a process this handle has never seen described is unknown; every
|
|
442
|
+
// observed description contributes a declaration, empty or otherwise.
|
|
383
443
|
if (!jobControlOptions)
|
|
384
444
|
return conformance;
|
|
385
445
|
const required = mode === "sync" ? JOB_CONTROL.sync : JOB_CONTROL.async;
|
|
@@ -413,11 +473,7 @@ export class HonuaOgcProcessJobRun {
|
|
|
413
473
|
/** Server-advertised routes, refreshed from every `links[]` the job reports. */
|
|
414
474
|
statusPath;
|
|
415
475
|
resultsPath;
|
|
416
|
-
|
|
417
|
-
currentProgress;
|
|
418
|
-
terminalSnapshot;
|
|
419
|
-
terminalPromise;
|
|
420
|
-
listeners = new Set();
|
|
476
|
+
lifecycle;
|
|
421
477
|
constructor(options) {
|
|
422
478
|
this.client = options.client;
|
|
423
479
|
this.id = options.jobId;
|
|
@@ -433,8 +489,14 @@ export class HonuaOgcProcessJobRun {
|
|
|
433
489
|
this.pollFn = options.pollFn ?? ((jobId, signal) => this.fetchStatus(jobId, signal));
|
|
434
490
|
const initial = options.initialStatus;
|
|
435
491
|
this.absorbLinks(initial?.links);
|
|
436
|
-
this.
|
|
437
|
-
|
|
492
|
+
this.lifecycle = new JobRunLifecycle({
|
|
493
|
+
id: this.id,
|
|
494
|
+
initialStatus: initial?.status ?? "accepted",
|
|
495
|
+
initialProgress: progressFromOgcStatus(initial),
|
|
496
|
+
pollIntervalMs: () => this.pollIntervalMs,
|
|
497
|
+
pollBudget: this.pollBudget,
|
|
498
|
+
poll: async (signal) => this.translateOgcStatus(await this.pollFn(this.id, signal)),
|
|
499
|
+
});
|
|
438
500
|
}
|
|
439
501
|
async fetchStatus(jobId, signal) {
|
|
440
502
|
if (this.capabilityPolicy === "strict" && this.statusPath === undefined) {
|
|
@@ -470,57 +532,32 @@ export class HonuaOgcProcessJobRun {
|
|
|
470
532
|
}
|
|
471
533
|
}
|
|
472
534
|
get status() {
|
|
473
|
-
return this.
|
|
535
|
+
return this.lifecycle.status;
|
|
474
536
|
}
|
|
475
537
|
get progress() {
|
|
476
|
-
return this.
|
|
538
|
+
return this.lifecycle.progress;
|
|
477
539
|
}
|
|
478
540
|
async poll() {
|
|
479
|
-
|
|
480
|
-
return this.terminalSnapshot;
|
|
481
|
-
}
|
|
482
|
-
const ogcStatus = await this.pollFn(this.id);
|
|
483
|
-
return this.handleOgcStatus(ogcStatus);
|
|
541
|
+
return this.lifecycle.poll();
|
|
484
542
|
}
|
|
485
543
|
watch(listener) {
|
|
486
|
-
this.
|
|
487
|
-
return () => this.listeners.delete(listener);
|
|
544
|
+
return this.lifecycle.watch(listener);
|
|
488
545
|
}
|
|
489
546
|
async results(options = {}) {
|
|
490
|
-
|
|
491
|
-
// Reset the cached promise if the poll loop rejects (abort / deadline /
|
|
492
|
-
// attempt cap) so a later results() call can retry rather than being
|
|
493
|
-
// permanently poisoned by a transient cancellation.
|
|
494
|
-
this.terminalPromise = this.runUntilTerminal(this.resolveBudget(options)).catch((error) => {
|
|
495
|
-
this.terminalPromise = undefined;
|
|
496
|
-
throw error;
|
|
497
|
-
});
|
|
498
|
-
}
|
|
499
|
-
return this.terminalPromise;
|
|
500
|
-
}
|
|
501
|
-
/**
|
|
502
|
-
* Layer the caller's bounds over the handle's default budget, then guarantee
|
|
503
|
-
* a bound exists. NFR-001: `results()` is never an unbounded retry loop, so a
|
|
504
|
-
* caller that names neither a deadline nor an attempt cap still inherits
|
|
505
|
-
* {@link DEFAULT_POLL_DEADLINE_MS}.
|
|
506
|
-
*/
|
|
507
|
-
resolveBudget(options) {
|
|
508
|
-
const merged = { ...this.pollBudget, ...options };
|
|
509
|
-
if (merged.deadlineMs !== undefined || merged.maxAttempts !== undefined)
|
|
510
|
-
return merged;
|
|
511
|
-
return { ...merged, deadlineMs: DEFAULT_POLL_DEADLINE_MS };
|
|
547
|
+
return this.lifecycle.results(options);
|
|
512
548
|
}
|
|
513
549
|
async cancel() {
|
|
514
|
-
if (this.
|
|
515
|
-
return this.
|
|
516
|
-
}
|
|
550
|
+
if (this.lifecycle.terminal)
|
|
551
|
+
return this.lifecycle.status;
|
|
517
552
|
this.assertDismissDeclared();
|
|
553
|
+
return this.lifecycle.cancel(() => this.cancelSnapshot());
|
|
554
|
+
}
|
|
555
|
+
async cancelSnapshot() {
|
|
518
556
|
try {
|
|
519
557
|
// DELETE targets the job resource itself, so it follows the same
|
|
520
558
|
// advertised route the status polls use rather than re-templating it.
|
|
521
559
|
const cancelled = await this.client.cancelOgcProcessJob(this.jobRequest(this.statusPath));
|
|
522
|
-
|
|
523
|
-
return snapshot.status;
|
|
560
|
+
return this.translateOgcStatus(cancelled);
|
|
524
561
|
}
|
|
525
562
|
catch (error) {
|
|
526
563
|
const statusCode = error?.statusCode;
|
|
@@ -540,7 +577,7 @@ export class HonuaOgcProcessJobRun {
|
|
|
540
577
|
// "completed job" claim cannot be confirmed and the original 409
|
|
541
578
|
// is the most honest signal.
|
|
542
579
|
if (statusCode === 404) {
|
|
543
|
-
return this.
|
|
580
|
+
return { status: this.lifecycle.status, progress: this.lifecycle.progress };
|
|
544
581
|
}
|
|
545
582
|
if (statusCode === 409 && isCompletedJobConflict(error)) {
|
|
546
583
|
let fresh;
|
|
@@ -553,67 +590,24 @@ export class HonuaOgcProcessJobRun {
|
|
|
553
590
|
// 409 instead of letting the poll-side error swallow it.
|
|
554
591
|
throw error;
|
|
555
592
|
}
|
|
556
|
-
const snapshot = await this.
|
|
593
|
+
const snapshot = await this.translateOgcStatus(fresh);
|
|
557
594
|
if (!isJobTerminal(snapshot.status)) {
|
|
558
595
|
throw error;
|
|
559
596
|
}
|
|
560
|
-
return snapshot
|
|
597
|
+
return snapshot;
|
|
561
598
|
}
|
|
562
599
|
throw error;
|
|
563
600
|
}
|
|
564
601
|
}
|
|
565
|
-
async runUntilTerminal(options = {}) {
|
|
566
|
-
const { signal } = options;
|
|
567
|
-
const baseIntervalMs = options.pollIntervalMs ?? this.pollIntervalMs;
|
|
568
|
-
const maxIntervalMs = options.maxPollIntervalMs ?? Math.max(baseIntervalMs, 30_000);
|
|
569
|
-
const startedAt = Date.now();
|
|
570
|
-
let attempts = 0;
|
|
571
|
-
while (!this.terminalSnapshot) {
|
|
572
|
-
if (signal?.aborted) {
|
|
573
|
-
throw new HonuaJobPollTimeoutError(`Job ${this.id} poll aborted`, "aborted", this.id, this.currentStatus);
|
|
574
|
-
}
|
|
575
|
-
if (options.maxAttempts !== undefined && attempts >= options.maxAttempts) {
|
|
576
|
-
throw new HonuaJobPollTimeoutError(`Job ${this.id} did not reach a terminal state within ${options.maxAttempts} poll attempt(s)`, "max-attempts", this.id, this.currentStatus);
|
|
577
|
-
}
|
|
578
|
-
let ogcStatus;
|
|
579
|
-
try {
|
|
580
|
-
ogcStatus = await this.pollFn(this.id, signal);
|
|
581
|
-
}
|
|
582
|
-
catch (error) {
|
|
583
|
-
if (signal?.aborted) {
|
|
584
|
-
throw new HonuaJobPollTimeoutError(`Job ${this.id} poll aborted`, "aborted", this.id, this.currentStatus);
|
|
585
|
-
}
|
|
586
|
-
throw error;
|
|
587
|
-
}
|
|
588
|
-
attempts += 1;
|
|
589
|
-
await this.handleOgcStatus(ogcStatus);
|
|
590
|
-
if (this.terminalSnapshot)
|
|
591
|
-
break;
|
|
592
|
-
if (options.deadlineMs !== undefined && Date.now() - startedAt >= options.deadlineMs) {
|
|
593
|
-
throw new HonuaJobPollTimeoutError(`Job ${this.id} did not reach a terminal state within ${options.deadlineMs}ms`, "deadline", this.id, this.currentStatus);
|
|
594
|
-
}
|
|
595
|
-
// Capped exponential backoff instead of a fixed interval.
|
|
596
|
-
const intervalMs = Math.min(maxIntervalMs, baseIntervalMs * 2 ** (attempts - 1));
|
|
597
|
-
if (intervalMs > 0) {
|
|
598
|
-
await delay(intervalMs, signal);
|
|
599
|
-
}
|
|
600
|
-
}
|
|
601
|
-
if (this.terminalSnapshot.status === "successful" && this.terminalSnapshot.result) {
|
|
602
|
-
return this.terminalSnapshot.result;
|
|
603
|
-
}
|
|
604
|
-
throw makeJobFailedError(this.terminalSnapshot);
|
|
605
|
-
}
|
|
606
602
|
/**
|
|
607
603
|
* Translate an OGC `statusInfo` payload onto the canonical snapshot
|
|
608
604
|
* surface, fire watchers, and (for `successful` terminals) fetch the
|
|
609
605
|
* result document inline so the snapshot's `result.outputs` is
|
|
610
606
|
* populated by the time `runUntilTerminal` / `poll` resolves.
|
|
611
607
|
*/
|
|
612
|
-
async
|
|
608
|
+
async translateOgcStatus(ogcStatus) {
|
|
613
609
|
const status = ogcStatus.status ?? "accepted";
|
|
614
610
|
const progress = progressFromOgcStatus(ogcStatus);
|
|
615
|
-
this.currentStatus = status;
|
|
616
|
-
this.currentProgress = progress;
|
|
617
611
|
// Routes the server publishes on this document win over the Core template.
|
|
618
612
|
this.absorbLinks(ogcStatus.links);
|
|
619
613
|
if (status === "successful") {
|
|
@@ -634,8 +628,6 @@ export class HonuaOgcProcessJobRun {
|
|
|
634
628
|
progress,
|
|
635
629
|
result: { outputs: results },
|
|
636
630
|
};
|
|
637
|
-
this.terminalSnapshot = snapshot;
|
|
638
|
-
this.notify(snapshot);
|
|
639
631
|
return snapshot;
|
|
640
632
|
}
|
|
641
633
|
catch (error) {
|
|
@@ -653,9 +645,6 @@ export class HonuaOgcProcessJobRun {
|
|
|
653
645
|
message: error instanceof Error ? error.message : String(error),
|
|
654
646
|
},
|
|
655
647
|
};
|
|
656
|
-
this.currentStatus = "failed";
|
|
657
|
-
this.terminalSnapshot = failure;
|
|
658
|
-
this.notify(failure);
|
|
659
648
|
return failure;
|
|
660
649
|
}
|
|
661
650
|
}
|
|
@@ -666,12 +655,9 @@ export class HonuaOgcProcessJobRun {
|
|
|
666
655
|
progress,
|
|
667
656
|
...(error ? { error } : {}),
|
|
668
657
|
};
|
|
669
|
-
this.terminalSnapshot = snapshot;
|
|
670
|
-
this.notify(snapshot);
|
|
671
658
|
return snapshot;
|
|
672
659
|
}
|
|
673
660
|
const snapshot = { status, progress };
|
|
674
|
-
this.notify(snapshot);
|
|
675
661
|
return snapshot;
|
|
676
662
|
}
|
|
677
663
|
/**
|
|
@@ -690,6 +676,10 @@ export class HonuaOgcProcessJobRun {
|
|
|
690
676
|
* in hand and neither the Dismiss conformance class nor the process's
|
|
691
677
|
* `jobControlOptions` includes `dismiss`, refuse before issuing a DELETE the
|
|
692
678
|
* server never advertised.
|
|
679
|
+
*
|
|
680
|
+
* Under `"strict"` the absence of any declaration refuses too: a handle that
|
|
681
|
+
* resolves declarations for itself has no honest reason to DELETE a job on a
|
|
682
|
+
* server that never said it could be dismissed.
|
|
693
683
|
*/
|
|
694
684
|
assertDismissDeclared() {
|
|
695
685
|
// Under `"strict"` the DELETE route must have been advertised, exactly as
|
|
@@ -703,25 +693,15 @@ export class HonuaOgcProcessJobRun {
|
|
|
703
693
|
if (declares(this.jobControlOptions, JOB_CONTROL.dismiss))
|
|
704
694
|
return;
|
|
705
695
|
const conformsTo = this.conformance?.conformsTo;
|
|
706
|
-
if (!conformsTo)
|
|
696
|
+
if (!conformsTo && this.capabilityPolicy !== "strict")
|
|
707
697
|
return;
|
|
708
|
-
if (
|
|
698
|
+
if (declaresConformanceClass(this.conformance, PROCESSES_CONFORMANCE_MATCH.dismiss))
|
|
709
699
|
return;
|
|
710
700
|
throw capabilityRefusal("processes.dismiss", this.type, {
|
|
711
701
|
missingClass: PROCESSES_CONFORMANCE.dismiss,
|
|
712
702
|
construct: JOB_CONTROL.dismiss,
|
|
713
703
|
});
|
|
714
704
|
}
|
|
715
|
-
notify(snapshot) {
|
|
716
|
-
for (const listener of this.listeners) {
|
|
717
|
-
try {
|
|
718
|
-
listener(snapshot);
|
|
719
|
-
}
|
|
720
|
-
catch {
|
|
721
|
-
// Listener exceptions must not break the runner; log silently.
|
|
722
|
-
}
|
|
723
|
-
}
|
|
724
|
-
}
|
|
725
705
|
}
|
|
726
706
|
/**
|
|
727
707
|
* `IJobRun` for a synchronous execution (Core §7.11: `200` with the results
|
|
@@ -760,18 +740,7 @@ export class HonuaOgcProcessSyncRun {
|
|
|
760
740
|
}
|
|
761
741
|
}
|
|
762
742
|
/** Error thrown when `IJobRun.results()` resolves a non-success terminal. */
|
|
763
|
-
export
|
|
764
|
-
status;
|
|
765
|
-
errorCode;
|
|
766
|
-
details;
|
|
767
|
-
constructor(message, status, errorCode, details) {
|
|
768
|
-
super("core.job-failed", message, { context: { status, errorCode } });
|
|
769
|
-
this.name = "HonuaJobFailedError";
|
|
770
|
-
this.status = status;
|
|
771
|
-
this.errorCode = errorCode;
|
|
772
|
-
this.details = details;
|
|
773
|
-
}
|
|
774
|
-
}
|
|
743
|
+
export { HonuaJobFailedError } from "./job-run-errors.js";
|
|
775
744
|
/**
|
|
776
745
|
* Honua-server emits problem-details JSON for DELETE /jobs/{id} 409s. The
|
|
777
746
|
* `title` distinguishes the benign terminal race ("Cannot dismiss
|
|
@@ -832,28 +801,6 @@ function clampPercent(value) {
|
|
|
832
801
|
return 100;
|
|
833
802
|
return value;
|
|
834
803
|
}
|
|
835
|
-
function makeJobFailedError(snapshot) {
|
|
836
|
-
const error = snapshot.error;
|
|
837
|
-
const message = error?.message ?? `Job ended in non-success terminal state: ${snapshot.status}`;
|
|
838
|
-
return new HonuaJobFailedError(message, snapshot.status, error?.code, error?.details);
|
|
839
|
-
}
|
|
840
|
-
function delay(ms, signal) {
|
|
841
|
-
return new Promise((resolve) => {
|
|
842
|
-
if (signal?.aborted) {
|
|
843
|
-
resolve();
|
|
844
|
-
return;
|
|
845
|
-
}
|
|
846
|
-
const onAbort = () => {
|
|
847
|
-
clearTimeout(timer);
|
|
848
|
-
resolve();
|
|
849
|
-
};
|
|
850
|
-
const timer = setTimeout(() => {
|
|
851
|
-
signal?.removeEventListener("abort", onAbort);
|
|
852
|
-
resolve();
|
|
853
|
-
}, ms);
|
|
854
|
-
signal?.addEventListener("abort", onAbort, { once: true });
|
|
855
|
-
});
|
|
856
|
-
}
|
|
857
804
|
export function createHonuaOgcProcesses(client) {
|
|
858
805
|
return new HonuaOgcProcesses({ client });
|
|
859
806
|
}
|
|
@@ -891,7 +838,7 @@ export async function getOgcProcess(transport, request) {
|
|
|
891
838
|
* against the async-only Honua facade, and reports which shape came back.
|
|
892
839
|
*/
|
|
893
840
|
export async function executeOgcProcess(transport, request) {
|
|
894
|
-
const headers =
|
|
841
|
+
const headers = executionHeaders(request);
|
|
895
842
|
const path = `${processesBase(request)}/processes/${encodeURIComponent(request.processId)}/execution`;
|
|
896
843
|
// `document` is the only response mode the SDK decodes: its body is the
|
|
897
844
|
// output-id-keyed map that `JobResult.outputs` is defined over. (honua-server
|
|
@@ -1008,16 +955,19 @@ export async function cancelOgcProcessJob(transport, request) {
|
|
|
1008
955
|
return transport.requestJson("DELETE", withParams(route, params), undefined, request.signal);
|
|
1009
956
|
}
|
|
1010
957
|
/**
|
|
1011
|
-
* Build the
|
|
1012
|
-
*
|
|
1013
|
-
*
|
|
1014
|
-
*
|
|
1015
|
-
* forces synchronous execution, so `"sync"` is expressed by *not* preferring
|
|
1016
|
-
* async plus the `sync-execute` capability assertion made before the post.
|
|
958
|
+
* Build the execution preference header defined by OGC API Processes 1.0.
|
|
959
|
+
* Requirement 25 selects synchronous execution by omitting `Prefer`; only an
|
|
960
|
+
* explicit asynchronous request sends the standard `respond-async` token.
|
|
961
|
+
* Clients remain prepared for either response shape.
|
|
1017
962
|
*/
|
|
1018
|
-
function
|
|
963
|
+
function executionHeaders(request) {
|
|
964
|
+
const headers = new Headers(mergeHeaders({ "Content-Type": "application/json", Accept: "application/json" }, request.headers));
|
|
965
|
+
// `mode` is the sole authority for this protocol semantic. A shared caller
|
|
966
|
+
// header bag must not silently turn `auto` / `sync` into an async request or
|
|
967
|
+
// smuggle a different preference into the explicit async mode.
|
|
968
|
+
headers.delete("Prefer");
|
|
1019
969
|
if (request.mode === "async") {
|
|
1020
|
-
|
|
970
|
+
headers.set("Prefer", "respond-async");
|
|
1021
971
|
}
|
|
1022
|
-
return
|
|
972
|
+
return headers;
|
|
1023
973
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pbf-decoder.d.ts","sourceRoot":"","sources":["../../../src/core/pbf-decoder.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AASH;;;;;;;GAOG;AACH,qBAAa,cAAe,SAAQ,KAAK;gBACpB,OAAO,EAAE,MAAM;CAInC;
|
|
1
|
+
{"version":3,"file":"pbf-decoder.d.ts","sourceRoot":"","sources":["../../../src/core/pbf-decoder.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AASH;;;;;;;GAOG;AACH,qBAAa,cAAe,SAAQ,KAAK;gBACpB,OAAO,EAAE,MAAM;CAInC;AAyoBD;;;;;;;;;GASG;AACH,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,WAAW,GAAG,UAAU,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAiChG;AAED;;GAEG;AACH,wBAAgB,aAAa,CAAC,QAAQ,EAAE,QAAQ,GAAG,OAAO,CAGzD"}
|
package/core/pbf-decoder.js
CHANGED
|
@@ -359,6 +359,14 @@ function decodeValue(reader) {
|
|
|
359
359
|
else
|
|
360
360
|
reader.skip(wire);
|
|
361
361
|
break;
|
|
362
|
+
case 8: // sint64_value (zigzag, used by ArcGIS for timestamp epochs)
|
|
363
|
+
if (wire === WIRE_VARINT) {
|
|
364
|
+
const encoded = reader.readVarint64Bigint();
|
|
365
|
+
value = safeNumberOrString((encoded >> 1n) ^ -(encoded & 1n));
|
|
366
|
+
}
|
|
367
|
+
else
|
|
368
|
+
reader.skip(wire);
|
|
369
|
+
break;
|
|
362
370
|
case 9: // bool_value
|
|
363
371
|
if (wire === WIRE_VARINT)
|
|
364
372
|
value = reader.readBool();
|
|
@@ -484,10 +492,12 @@ function decodeFeature(reader, fields, transform, geometryType) {
|
|
|
484
492
|
reader.skip(wire);
|
|
485
493
|
}
|
|
486
494
|
}
|
|
487
|
-
//
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
495
|
+
// FeatureCollection attributes follow field order. The optional Value.index
|
|
496
|
+
// is also supported when a producer explicitly supplies a field mapping.
|
|
497
|
+
for (const [position, entry] of valueEntries.entries()) {
|
|
498
|
+
const fieldIndex = entry.fieldIndex >= 0 ? entry.fieldIndex : position;
|
|
499
|
+
if (fieldIndex < fields.length) {
|
|
500
|
+
attributes[fields[fieldIndex].name] = entry.value;
|
|
491
501
|
}
|
|
492
502
|
}
|
|
493
503
|
const result = { attributes };
|
|
@@ -644,6 +654,8 @@ function mapPbfFieldTypeToGeoServices(fieldType) {
|
|
|
644
654
|
return "esriFieldTypeXML";
|
|
645
655
|
case 13:
|
|
646
656
|
return "esriFieldTypeBigInteger";
|
|
657
|
+
case 14:
|
|
658
|
+
return "esriFieldTypeDateOnly";
|
|
647
659
|
default:
|
|
648
660
|
return "esriFieldTypeString";
|
|
649
661
|
}
|