@honua/app-platform 0.0.20-alpha.0 → 0.1.1-beta.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/contract/discovery.d.ts +231 -0
- package/contract/discovery.d.ts.map +1 -0
- package/contract/discovery.js +554 -0
- package/contract/edit-sketch.d.ts +8 -0
- package/contract/edit-sketch.d.ts.map +1 -1
- package/contract/edit-sketch.js +15 -0
- package/contract/edit-snapping.d.ts +160 -0
- package/contract/edit-snapping.d.ts.map +1 -0
- package/contract/edit-snapping.js +675 -0
- package/contract/generated/projjson-v0.7-crs-validator.d.ts +35 -0
- package/contract/generated/projjson-v0.7-crs-validator.d.ts.map +1 -0
- package/contract/generated/projjson-v0.7-crs-validator.js +3941 -0
- package/contract/index.d.ts +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/generated-app/errors.d.ts +18 -2
- package/generated-app/errors.d.ts.map +1 -1
- package/generated-app/errors.js +44 -11
- package/geocoding/index.d.ts +7 -0
- package/geocoding/index.d.ts.map +1 -1
- package/geocoding/index.js +5 -0
- package/geocoding/provider-http.d.ts +37 -0
- package/geocoding/provider-http.d.ts.map +1 -0
- package/geocoding/provider-http.js +91 -0
- package/geocoding/provider.d.ts +145 -0
- package/geocoding/provider.d.ts.map +1 -0
- package/geocoding/provider.js +41 -0
- package/geocoding/providers/honua.d.ts +28 -0
- package/geocoding/providers/honua.d.ts.map +1 -0
- package/geocoding/providers/honua.js +68 -0
- package/geocoding/providers/nominatim.d.ts +47 -0
- package/geocoding/providers/nominatim.d.ts.map +1 -0
- package/geocoding/providers/nominatim.js +105 -0
- package/geocoding/providers/pelias.d.ts +40 -0
- package/geocoding/providers/pelias.d.ts.map +1 -0
- package/geocoding/providers/pelias.js +115 -0
- package/geocoding/providers/photon.d.ts +39 -0
- package/geocoding/providers/photon.d.ts.map +1 -0
- package/geocoding/providers/photon.js +107 -0
- package/geometry/convert.d.ts +13 -0
- package/geometry/convert.d.ts.map +1 -0
- package/geometry/convert.js +12 -0
- package/geometry/index.d.ts +17 -0
- package/geometry/index.d.ts.map +1 -0
- package/geometry/index.js +15 -0
- package/geometry/internal.d.ts +25 -0
- package/geometry/internal.d.ts.map +1 -0
- package/geometry/internal.js +40 -0
- package/geometry/ops.d.ts +71 -0
- package/geometry/ops.d.ts.map +1 -0
- package/geometry/ops.js +147 -0
- package/geometry/project.d.ts +40 -0
- package/geometry/project.d.ts.map +1 -0
- package/geometry/project.js +88 -0
- package/geometry/types.d.ts +36 -0
- package/geometry/types.d.ts.map +1 -0
- package/geometry/types.js +12 -0
- 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 +33 -2
- 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/realtime/contract.d.ts +142 -0
- package/realtime/contract.d.ts.map +1 -0
- package/realtime/contract.js +154 -0
- package/realtime/honua-server.d.ts.map +1 -1
- package/realtime/honua-server.js +13 -8
- package/realtime/index.d.ts +19 -4
- package/realtime/index.d.ts.map +1 -1
- package/realtime/index.js +14 -4
- package/realtime/odata-delta.d.ts +114 -0
- package/realtime/odata-delta.d.ts.map +1 -0
- package/realtime/odata-delta.js +483 -0
- package/realtime/resumable-transport.d.ts +111 -0
- package/realtime/resumable-transport.d.ts.map +1 -0
- package/realtime/resumable-transport.js +406 -0
- package/realtime/resumable.d.ts +104 -0
- package/realtime/resumable.d.ts.map +1 -0
- package/realtime/resumable.js +632 -0
- package/realtime/sse.d.ts.map +1 -1
- package/realtime/sse.js +27 -23
- package/realtime/websocket.d.ts +60 -0
- package/realtime/websocket.d.ts.map +1 -0
- package/realtime/websocket.js +157 -0
- package/realtime/wire.d.ts +27 -0
- package/realtime/wire.d.ts.map +1 -0
- package/realtime/wire.js +57 -0
- package/replica-sync/errors.d.ts +2 -1
- package/replica-sync/errors.d.ts.map +1 -1
- package/replica-sync/errors.js +33 -2
- 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/scene-workspace/index.d.ts +4 -0
- package/scene-workspace/index.d.ts.map +1 -1
- package/scene-workspace/index.js +2 -0
- package/scene-workspace/source-to-cesium.d.ts +116 -0
- package/scene-workspace/source-to-cesium.d.ts.map +1 -0
- package/scene-workspace/source-to-cesium.js +791 -0
- package/scene-workspace/state-sync.d.ts +147 -0
- package/scene-workspace/state-sync.d.ts.map +1 -0
- package/scene-workspace/state-sync.js +1123 -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/web-components/controller.d.ts +2 -0
- package/web-components/controller.d.ts.map +1 -1
- package/web-components/controller.js +49 -8
- package/web-components/elements.d.ts +95 -1
- package/web-components/elements.d.ts.map +1 -1
- package/web-components/elements.js +458 -19
- package/web-components/index.d.ts +8 -1
- package/web-components/index.d.ts.map +1 -1
- package/web-components/index.js +7 -0
- package/web-components/maplibre-renderer.d.ts.map +1 -1
- package/web-components/maplibre-renderer.js +87 -0
- package/web-components/measurement.d.ts +69 -0
- package/web-components/measurement.d.ts.map +1 -0
- package/web-components/measurement.js +523 -0
- package/web-components/types.d.ts +109 -0
- package/web-components/types.d.ts.map +1 -1
- 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
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { UNSUPPORTED_FES, compileSpatialFilter, compileWhere, serializeFes } from "../core/wfs-filter.js";
|
|
2
|
+
import { HonuaQueryPlanningError } from "./types.js";
|
|
3
|
+
export { compileSemanticWfsQuery } from "./wfs-semantic.js";
|
|
4
|
+
/** Compile canonical query IR to a deterministic WFS 2.0 GetFeature request. */
|
|
5
|
+
export function compileWfsQuery(source, query) {
|
|
6
|
+
if (source.protocol !== "wfs") {
|
|
7
|
+
throw new HonuaQueryPlanningError("unsupported-compiler", `wfs-2.0-get-feature-v1 does not compile protocol "${source.protocol}"`);
|
|
8
|
+
}
|
|
9
|
+
if (!source.typeName) {
|
|
10
|
+
throw new HonuaQueryPlanningError("invalid-query", `Source "${source.id}" requires locator.typeName for WFS planning`);
|
|
11
|
+
}
|
|
12
|
+
if (query.aggregation) {
|
|
13
|
+
throw new HonuaQueryPlanningError("unsupported-query", "WFS 2.0 has no portable remote aggregation; use degraded policy with an explicit bounded-local fallback");
|
|
14
|
+
}
|
|
15
|
+
if (query.spatialFilter && query.outSr !== undefined) {
|
|
16
|
+
throw new HonuaQueryPlanningError("unsupported-query", "WFS planning cannot use outSr to label input filter coordinates; stamp the input geometry through the typed WFS escape hatch");
|
|
17
|
+
}
|
|
18
|
+
// Keep this aligned with the current WFS adapter's reviewed default. A
|
|
19
|
+
// vendor-specific geometry property requires the typed WFS escape hatch
|
|
20
|
+
// until discovery carries that binding into both planner and executor.
|
|
21
|
+
const geometryProperty = "the_geom";
|
|
22
|
+
const filters = [];
|
|
23
|
+
if (query.where) {
|
|
24
|
+
const where = compileWhere(query.where.expression);
|
|
25
|
+
if (where === UNSUPPORTED_FES) {
|
|
26
|
+
throw new HonuaQueryPlanningError("unsupported-query", "The where clause is not expressible as FES 2.0");
|
|
27
|
+
}
|
|
28
|
+
if (where.kind !== "and" || where.operands.length > 0)
|
|
29
|
+
filters.push(where);
|
|
30
|
+
}
|
|
31
|
+
if (query.spatialFilter) {
|
|
32
|
+
const spatial = compileSpatialFilter({
|
|
33
|
+
geometry: query.spatialFilter.geometry,
|
|
34
|
+
geometryType: query.spatialFilter.geometryType,
|
|
35
|
+
...(query.spatialFilter.spatialRel ? { spatialRel: query.spatialFilter.spatialRel } : {}),
|
|
36
|
+
}, {
|
|
37
|
+
geometryProperty,
|
|
38
|
+
});
|
|
39
|
+
if (spatial === UNSUPPORTED_FES) {
|
|
40
|
+
throw new HonuaQueryPlanningError("unsupported-query", "The spatial predicate is not expressible as FES 2.0");
|
|
41
|
+
}
|
|
42
|
+
filters.push(spatial);
|
|
43
|
+
}
|
|
44
|
+
let propertyName;
|
|
45
|
+
if (query.outFields && query.outFields.length > 0) {
|
|
46
|
+
const fields = [...query.outFields];
|
|
47
|
+
if (query.returnGeometry === false && fields.includes(geometryProperty)) {
|
|
48
|
+
throw new HonuaQueryPlanningError("unsupported-query", `WFS returnGeometry=false conflicts with outFields containing the geometry property "${geometryProperty}"`);
|
|
49
|
+
}
|
|
50
|
+
if (query.returnGeometry !== false && !fields.includes(geometryProperty))
|
|
51
|
+
fields.push(geometryProperty);
|
|
52
|
+
propertyName = fields;
|
|
53
|
+
}
|
|
54
|
+
else if (query.returnGeometry === false) {
|
|
55
|
+
throw new HonuaQueryPlanningError("unsupported-query", "WFS returnGeometry=false requires explicit outFields because propertyName cannot otherwise suppress geometry exactly");
|
|
56
|
+
}
|
|
57
|
+
return {
|
|
58
|
+
compiler: "wfs-2.0-get-feature-v1",
|
|
59
|
+
typeName: source.typeName,
|
|
60
|
+
...(filters.length > 0 ? { filter: serializeFes(filters, { typeName: source.typeName }) } : {}),
|
|
61
|
+
...(propertyName ? { propertyName } : {}),
|
|
62
|
+
...(query.orderBy && query.orderBy.length > 0
|
|
63
|
+
? {
|
|
64
|
+
sortBy: query.orderBy.map((sort) => `${sort.field} ${sort.direction === "desc" ? "DESC" : "ASC"}`).join(","),
|
|
65
|
+
}
|
|
66
|
+
: {}),
|
|
67
|
+
...(query.pagination?.offset !== undefined ? { startIndex: query.pagination.offset } : {}),
|
|
68
|
+
...(query.pagination?.limit !== undefined ? { count: query.pagination.limit } : {}),
|
|
69
|
+
...(query.outSr !== undefined ? { srsName: String(query.outSr) } : {}),
|
|
70
|
+
};
|
|
71
|
+
}
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Snapshot, delta, cursor, resume, and plan-identity contract surface for
|
|
3
|
+
* `@honua/sdk-js/realtime` (issue #556).
|
|
4
|
+
*
|
|
5
|
+
* The resumable delivery gate in `resumable.ts` already implements the
|
|
6
|
+
* durable-checkpoint state machine: versioned snapshot/delta/cursor
|
|
7
|
+
* envelopes, monotonic-sequence ordering and deduplication, delete
|
|
8
|
+
* tombstones (`reducer.ts`), and mandatory resnapshot on gap, expiry, or
|
|
9
|
+
* scope change. This module closes the three remaining contract gaps that
|
|
10
|
+
* keep that machinery a *design* rather than an ad hoc convention:
|
|
11
|
+
*
|
|
12
|
+
* 1. **Plan identity** — {@link RealtimeResumeContextV1.queryFingerprint} was
|
|
13
|
+
* previously any caller-supplied string. {@link realtimePlanFingerprint}
|
|
14
|
+
* and {@link assertRealtimePlanIdentity} bind it to the query planner's
|
|
15
|
+
* canonical, credential-free `QueryExecutionPlan.fingerprint` — the same
|
|
16
|
+
* content hash `hashQueryPlan`/`persistedPlanSnapshot` establish once,
|
|
17
|
+
* with full protocol-compiler re-verification, when a plan is first
|
|
18
|
+
* constructed (`explainQuery`) or reloaded (`parseQueryPlan`). That
|
|
19
|
+
* fingerprint already folds in `capabilityPolicy` and `fallback`, so
|
|
20
|
+
* binding to it also carries policy identity per REQ-003 without a
|
|
21
|
+
* second fingerprint field. This module reads the already-trusted field
|
|
22
|
+
* rather than importing `hashQueryPlan` itself: plan *integrity* is the
|
|
23
|
+
* query planner's trust boundary, established once at construction time,
|
|
24
|
+
* and re-deriving it here would pull every protocol compiler
|
|
25
|
+
* (DuckDB/gRPC/GeoServices/OGC/OData/WFS — several hundred KB) into the
|
|
26
|
+
* `@honua/sdk-js/realtime` bundle for a check that is already done.
|
|
27
|
+
* 2. **Authority** — {@link deriveRealtimeContractAuthority} projects the
|
|
28
|
+
* resumable gate's `phase` into one explicit
|
|
29
|
+
* `replaying | live | stale | terminal` state plus an `authoritative`
|
|
30
|
+
* flag, so consumers stop inferring trust from reason codes.
|
|
31
|
+
* 3. **Deterministic, redacted serialization** — {@link serializeRealtimeCheckpoint}
|
|
32
|
+
* and {@link redactRealtimeCheckpoint} give a canonical (sorted-key) JSON
|
|
33
|
+
* form for durable storage and a form safe for logs/telemetry that never
|
|
34
|
+
* carries a raw cursor, watermark, or delta-token value.
|
|
35
|
+
*
|
|
36
|
+
* @module
|
|
37
|
+
*/
|
|
38
|
+
import type { QueryExecutionPlan } from "../query-planner/types.js";
|
|
39
|
+
import { type RealtimeDurableCheckpointV1, type RealtimeResumeContextV1, type ResumableRealtimeState } from "./resumable.js";
|
|
40
|
+
/**
|
|
41
|
+
* Canonical plan-identity fingerprint for
|
|
42
|
+
* {@link RealtimeResumeContextV1.queryFingerprint}: the accepted plan's own
|
|
43
|
+
* trusted `fingerprint` field (`hashQueryPlan(plan) === plan.fingerprint`
|
|
44
|
+
* for every plan produced by `explainQuery`/`parseQueryPlan`). Named and
|
|
45
|
+
* exported so call sites bind resume identity to the plan by construction
|
|
46
|
+
* instead of by convention, without importing the query planner's
|
|
47
|
+
* protocol-compiler graph into `@honua/sdk-js/realtime`. Only
|
|
48
|
+
* `QueryExecutionPlan["fingerprint"]` is type-imported; nothing from
|
|
49
|
+
* `query-planner/planner.js` is bundled here.
|
|
50
|
+
*/
|
|
51
|
+
export declare function realtimePlanFingerprint(plan: Pick<QueryExecutionPlan, "fingerprint">): `sha256:${string}`;
|
|
52
|
+
/**
|
|
53
|
+
* Assert that a resume context's `queryFingerprint` matches the accepted
|
|
54
|
+
* plan's fingerprint. Throws a `"query-changed"`
|
|
55
|
+
* {@link HonuaRealtimeResumeError} — the same taxonomy
|
|
56
|
+
* {@link evaluateRealtimeCheckpoint} uses for a checkpoint whose bound query
|
|
57
|
+
* identity no longer matches — when they diverge, so a subscription can
|
|
58
|
+
* never resume against a plan other than the one it was accepted for. This
|
|
59
|
+
* checks identity, not plan authenticity: trust that `plan.fingerprint`
|
|
60
|
+
* matches the plan's actual content is established once, by the query
|
|
61
|
+
* planner, when the plan is constructed or parsed.
|
|
62
|
+
*/
|
|
63
|
+
export declare function assertRealtimePlanIdentity(context: RealtimeResumeContextV1, plan: Pick<QueryExecutionPlan, "fingerprint">): void;
|
|
64
|
+
/**
|
|
65
|
+
* Explicit realtime contract authority states.
|
|
66
|
+
*
|
|
67
|
+
* - `replaying`: no confirmed authoritative baseline yet — awaiting an
|
|
68
|
+
* initial or replacement snapshot, or resuming from a durable checkpoint
|
|
69
|
+
* whose continuity has not been reconfirmed by a live event.
|
|
70
|
+
* - `live`: an accepted snapshot establishes ground truth and the most
|
|
71
|
+
* recent contiguous event arrived within the caller's staleness window
|
|
72
|
+
* (or no window was supplied).
|
|
73
|
+
* - `stale`: the baseline is still authoritative but no event has arrived
|
|
74
|
+
* within `staleAfterMs`; render the last known state but surface
|
|
75
|
+
* liveness doubt.
|
|
76
|
+
* - `terminal`: delivery stopped on an unrecoverable error or an explicit
|
|
77
|
+
* close. The last accepted checkpoint, if any, remains available for
|
|
78
|
+
* read-only rendering but will never advance.
|
|
79
|
+
*/
|
|
80
|
+
export type RealtimeContractAuthorityState = "replaying" | "live" | "stale" | "terminal";
|
|
81
|
+
export interface RealtimeContractAuthority {
|
|
82
|
+
readonly state: RealtimeContractAuthorityState;
|
|
83
|
+
/** True once an accepted snapshot has established ground truth for the current checkpoint identity. */
|
|
84
|
+
readonly authoritative: boolean;
|
|
85
|
+
/** Milliseconds since the checkpoint was accepted, when a checkpoint exists and `now` was supplied. */
|
|
86
|
+
readonly ageMs?: number;
|
|
87
|
+
}
|
|
88
|
+
export interface DeriveRealtimeContractAuthorityOptions {
|
|
89
|
+
/** No event within this window while `phase === "live"` demotes authority to `"stale"`. */
|
|
90
|
+
readonly staleAfterMs?: number;
|
|
91
|
+
readonly now?: number;
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Derive one explicit {@link RealtimeContractAuthority} from a resumable
|
|
95
|
+
* gate's state. Pure and side-effect free: call it after every
|
|
96
|
+
* `enqueue`/`requireResnapshot`/`close` transition to render connection
|
|
97
|
+
* chrome without re-deriving trust from `phase`/`reason` combinations.
|
|
98
|
+
*/
|
|
99
|
+
export declare function deriveRealtimeContractAuthority(resumable: Pick<ResumableRealtimeState, "phase" | "checkpoint">, options?: DeriveRealtimeContractAuthorityOptions): RealtimeContractAuthority;
|
|
100
|
+
/** Opaque, deterministic digest of a raw resume-position value; never the raw value itself. */
|
|
101
|
+
export type RedactedResumePosition = `sha256:${string}`;
|
|
102
|
+
export interface RedactedRealtimeResumePosition {
|
|
103
|
+
readonly sequence: number;
|
|
104
|
+
readonly cursor?: RedactedResumePosition;
|
|
105
|
+
readonly watermark?: RedactedResumePosition;
|
|
106
|
+
readonly timestamp?: string;
|
|
107
|
+
readonly deltaToken?: RedactedResumePosition;
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Log/telemetry-safe projection of a durable checkpoint. `context` is kept
|
|
111
|
+
* as-is because every one of its fields is already an opaque identity or
|
|
112
|
+
* fingerprint (never a credential). `resume.cursor`, `resume.watermark`, and
|
|
113
|
+
* `resume.deltaToken` are replaced with a deterministic SHA-256 digest so
|
|
114
|
+
* repeated observations of the same position stay correlatable without
|
|
115
|
+
* exposing the raw, potentially sensitive token. `timestamp` is kept in the
|
|
116
|
+
* clear because it is not an opaque credential. `recentEventIds` is
|
|
117
|
+
* collapsed to a count: individual event ids are dedup identity, not
|
|
118
|
+
* something a log line needs verbatim.
|
|
119
|
+
*/
|
|
120
|
+
export interface RedactedRealtimeCheckpointV1 {
|
|
121
|
+
readonly kind: "honua.realtime-checkpoint";
|
|
122
|
+
readonly version: RealtimeDurableCheckpointV1["version"];
|
|
123
|
+
readonly context: RealtimeResumeContextV1;
|
|
124
|
+
readonly resume: RedactedRealtimeResumePosition;
|
|
125
|
+
readonly recentEventIdCount: number;
|
|
126
|
+
readonly savedAt: string;
|
|
127
|
+
}
|
|
128
|
+
export declare function redactRealtimeCheckpoint(checkpoint: RealtimeDurableCheckpointV1): RedactedRealtimeCheckpointV1;
|
|
129
|
+
/**
|
|
130
|
+
* Canonical (sorted-key) JSON serialization of the full durable checkpoint,
|
|
131
|
+
* for persistence where the caller owns access control. Two checkpoints
|
|
132
|
+
* with the same accepted content always serialize identically, regardless
|
|
133
|
+
* of property insertion order — required for content-addressed storage and
|
|
134
|
+
* for stable diffing across processes.
|
|
135
|
+
*/
|
|
136
|
+
export declare function serializeRealtimeCheckpoint(checkpoint: RealtimeDurableCheckpointV1): string;
|
|
137
|
+
/**
|
|
138
|
+
* Canonical, redacted JSON serialization safe for logs and telemetry
|
|
139
|
+
* sinks — never a raw cursor, watermark, or delta-token value.
|
|
140
|
+
*/
|
|
141
|
+
export declare function serializeRedactedRealtimeCheckpoint(checkpoint: RealtimeDurableCheckpointV1): string;
|
|
142
|
+
//# sourceMappingURL=contract.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"contract.d.ts","sourceRoot":"","sources":["../../../src/realtime/contract.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoCG;AAGH,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,2BAA2B,CAAC;AACpE,OAAO,EAEL,KAAK,2BAA2B,EAChC,KAAK,uBAAuB,EAC5B,KAAK,sBAAsB,EAC5B,MAAM,gBAAgB,CAAC;AAExB;;;;;;;;;;GAUG;AACH,wBAAgB,uBAAuB,CAAC,IAAI,EAAE,IAAI,CAAC,kBAAkB,EAAE,aAAa,CAAC,GAAG,UAAU,MAAM,EAAE,CAEzG;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,0BAA0B,CACxC,OAAO,EAAE,uBAAuB,EAChC,IAAI,EAAE,IAAI,CAAC,kBAAkB,EAAE,aAAa,CAAC,GAC5C,IAAI,CAQN;AAED;;;;;;;;;;;;;;;GAeG;AACH,MAAM,MAAM,8BAA8B,GAAG,WAAW,GAAG,MAAM,GAAG,OAAO,GAAG,UAAU,CAAC;AAEzF,MAAM,WAAW,yBAAyB;IACxC,QAAQ,CAAC,KAAK,EAAE,8BAA8B,CAAC;IAC/C,uGAAuG;IACvG,QAAQ,CAAC,aAAa,EAAE,OAAO,CAAC;IAChC,uGAAuG;IACvG,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;CACzB;AAED,MAAM,WAAW,sCAAsC;IACrD,2FAA2F;IAC3F,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,CAAC;CACvB;AAED;;;;;GAKG;AACH,wBAAgB,+BAA+B,CAC7C,SAAS,EAAE,IAAI,CAAC,sBAAsB,EAAE,OAAO,GAAG,YAAY,CAAC,EAC/D,OAAO,GAAE,sCAA2C,GACnD,yBAAyB,CA2B3B;AAYD,+FAA+F;AAC/F,MAAM,MAAM,sBAAsB,GAAG,UAAU,MAAM,EAAE,CAAC;AAExD,MAAM,WAAW,8BAA8B;IAC7C,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,MAAM,CAAC,EAAE,sBAAsB,CAAC;IACzC,QAAQ,CAAC,SAAS,CAAC,EAAE,sBAAsB,CAAC;IAC5C,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,UAAU,CAAC,EAAE,sBAAsB,CAAC;CAC9C;AAED;;;;;;;;;;GAUG;AACH,MAAM,WAAW,4BAA4B;IAC3C,QAAQ,CAAC,IAAI,EAAE,2BAA2B,CAAC;IAC3C,QAAQ,CAAC,OAAO,EAAE,2BAA2B,CAAC,SAAS,CAAC,CAAC;IACzD,QAAQ,CAAC,OAAO,EAAE,uBAAuB,CAAC;IAC1C,QAAQ,CAAC,MAAM,EAAE,8BAA8B,CAAC;IAChD,QAAQ,CAAC,kBAAkB,EAAE,MAAM,CAAC;IACpC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;CAC1B;AAED,wBAAgB,wBAAwB,CAAC,UAAU,EAAE,2BAA2B,GAAG,4BAA4B,CAmB9G;AAMD;;;;;;GAMG;AACH,wBAAgB,2BAA2B,CAAC,UAAU,EAAE,2BAA2B,GAAG,MAAM,CAE3F;AAED;;;GAGG;AACH,wBAAgB,mCAAmC,CAAC,UAAU,EAAE,2BAA2B,GAAG,MAAM,CAEnG"}
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Snapshot, delta, cursor, resume, and plan-identity contract surface for
|
|
3
|
+
* `@honua/sdk-js/realtime` (issue #556).
|
|
4
|
+
*
|
|
5
|
+
* The resumable delivery gate in `resumable.ts` already implements the
|
|
6
|
+
* durable-checkpoint state machine: versioned snapshot/delta/cursor
|
|
7
|
+
* envelopes, monotonic-sequence ordering and deduplication, delete
|
|
8
|
+
* tombstones (`reducer.ts`), and mandatory resnapshot on gap, expiry, or
|
|
9
|
+
* scope change. This module closes the three remaining contract gaps that
|
|
10
|
+
* keep that machinery a *design* rather than an ad hoc convention:
|
|
11
|
+
*
|
|
12
|
+
* 1. **Plan identity** — {@link RealtimeResumeContextV1.queryFingerprint} was
|
|
13
|
+
* previously any caller-supplied string. {@link realtimePlanFingerprint}
|
|
14
|
+
* and {@link assertRealtimePlanIdentity} bind it to the query planner's
|
|
15
|
+
* canonical, credential-free `QueryExecutionPlan.fingerprint` — the same
|
|
16
|
+
* content hash `hashQueryPlan`/`persistedPlanSnapshot` establish once,
|
|
17
|
+
* with full protocol-compiler re-verification, when a plan is first
|
|
18
|
+
* constructed (`explainQuery`) or reloaded (`parseQueryPlan`). That
|
|
19
|
+
* fingerprint already folds in `capabilityPolicy` and `fallback`, so
|
|
20
|
+
* binding to it also carries policy identity per REQ-003 without a
|
|
21
|
+
* second fingerprint field. This module reads the already-trusted field
|
|
22
|
+
* rather than importing `hashQueryPlan` itself: plan *integrity* is the
|
|
23
|
+
* query planner's trust boundary, established once at construction time,
|
|
24
|
+
* and re-deriving it here would pull every protocol compiler
|
|
25
|
+
* (DuckDB/gRPC/GeoServices/OGC/OData/WFS — several hundred KB) into the
|
|
26
|
+
* `@honua/sdk-js/realtime` bundle for a check that is already done.
|
|
27
|
+
* 2. **Authority** — {@link deriveRealtimeContractAuthority} projects the
|
|
28
|
+
* resumable gate's `phase` into one explicit
|
|
29
|
+
* `replaying | live | stale | terminal` state plus an `authoritative`
|
|
30
|
+
* flag, so consumers stop inferring trust from reason codes.
|
|
31
|
+
* 3. **Deterministic, redacted serialization** — {@link serializeRealtimeCheckpoint}
|
|
32
|
+
* and {@link redactRealtimeCheckpoint} give a canonical (sorted-key) JSON
|
|
33
|
+
* form for durable storage and a form safe for logs/telemetry that never
|
|
34
|
+
* carries a raw cursor, watermark, or delta-token value.
|
|
35
|
+
*
|
|
36
|
+
* @module
|
|
37
|
+
*/
|
|
38
|
+
import { canonicalStringify, sha256, toJsonValue } from "../query-planner/canonical.js";
|
|
39
|
+
import { HonuaRealtimeResumeError, } from "./resumable.js";
|
|
40
|
+
/**
|
|
41
|
+
* Canonical plan-identity fingerprint for
|
|
42
|
+
* {@link RealtimeResumeContextV1.queryFingerprint}: the accepted plan's own
|
|
43
|
+
* trusted `fingerprint` field (`hashQueryPlan(plan) === plan.fingerprint`
|
|
44
|
+
* for every plan produced by `explainQuery`/`parseQueryPlan`). Named and
|
|
45
|
+
* exported so call sites bind resume identity to the plan by construction
|
|
46
|
+
* instead of by convention, without importing the query planner's
|
|
47
|
+
* protocol-compiler graph into `@honua/sdk-js/realtime`. Only
|
|
48
|
+
* `QueryExecutionPlan["fingerprint"]` is type-imported; nothing from
|
|
49
|
+
* `query-planner/planner.js` is bundled here.
|
|
50
|
+
*/
|
|
51
|
+
export function realtimePlanFingerprint(plan) {
|
|
52
|
+
return plan.fingerprint;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Assert that a resume context's `queryFingerprint` matches the accepted
|
|
56
|
+
* plan's fingerprint. Throws a `"query-changed"`
|
|
57
|
+
* {@link HonuaRealtimeResumeError} — the same taxonomy
|
|
58
|
+
* {@link evaluateRealtimeCheckpoint} uses for a checkpoint whose bound query
|
|
59
|
+
* identity no longer matches — when they diverge, so a subscription can
|
|
60
|
+
* never resume against a plan other than the one it was accepted for. This
|
|
61
|
+
* checks identity, not plan authenticity: trust that `plan.fingerprint`
|
|
62
|
+
* matches the plan's actual content is established once, by the query
|
|
63
|
+
* planner, when the plan is constructed or parsed.
|
|
64
|
+
*/
|
|
65
|
+
export function assertRealtimePlanIdentity(context, plan) {
|
|
66
|
+
const expected = realtimePlanFingerprint(plan);
|
|
67
|
+
if (context.queryFingerprint !== expected) {
|
|
68
|
+
throw new HonuaRealtimeResumeError("query-changed", `Realtime resume context queryFingerprint does not match the accepted plan fingerprint (expected "${expected}", received "${context.queryFingerprint}").`);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Derive one explicit {@link RealtimeContractAuthority} from a resumable
|
|
73
|
+
* gate's state. Pure and side-effect free: call it after every
|
|
74
|
+
* `enqueue`/`requireResnapshot`/`close` transition to render connection
|
|
75
|
+
* chrome without re-deriving trust from `phase`/`reason` combinations.
|
|
76
|
+
*/
|
|
77
|
+
export function deriveRealtimeContractAuthority(resumable, options = {}) {
|
|
78
|
+
const ageMs = checkpointAgeMs(resumable.checkpoint, options.now);
|
|
79
|
+
switch (resumable.phase) {
|
|
80
|
+
case "awaiting-snapshot":
|
|
81
|
+
case "resuming":
|
|
82
|
+
case "resnapshot-required":
|
|
83
|
+
return freeze({ state: "replaying", authoritative: false, ...(ageMs !== undefined ? { ageMs } : {}) });
|
|
84
|
+
case "error":
|
|
85
|
+
case "closed":
|
|
86
|
+
return freeze({
|
|
87
|
+
state: "terminal",
|
|
88
|
+
authoritative: resumable.checkpoint !== undefined,
|
|
89
|
+
...(ageMs !== undefined ? { ageMs } : {}),
|
|
90
|
+
});
|
|
91
|
+
case "live": {
|
|
92
|
+
const stale = ageMs !== undefined &&
|
|
93
|
+
typeof options.staleAfterMs === "number" &&
|
|
94
|
+
Number.isFinite(options.staleAfterMs) &&
|
|
95
|
+
ageMs > options.staleAfterMs;
|
|
96
|
+
return freeze({
|
|
97
|
+
state: stale ? "stale" : "live",
|
|
98
|
+
authoritative: true,
|
|
99
|
+
...(ageMs !== undefined ? { ageMs } : {}),
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
function checkpointAgeMs(checkpoint, now) {
|
|
105
|
+
if (!checkpoint || typeof now !== "number" || !Number.isFinite(now))
|
|
106
|
+
return undefined;
|
|
107
|
+
const savedAt = Date.parse(checkpoint.savedAt);
|
|
108
|
+
if (!Number.isFinite(savedAt))
|
|
109
|
+
return undefined;
|
|
110
|
+
return Math.max(0, now - savedAt);
|
|
111
|
+
}
|
|
112
|
+
export function redactRealtimeCheckpoint(checkpoint) {
|
|
113
|
+
return freeze({
|
|
114
|
+
kind: checkpoint.kind,
|
|
115
|
+
version: checkpoint.version,
|
|
116
|
+
context: checkpoint.context,
|
|
117
|
+
resume: freeze({
|
|
118
|
+
sequence: checkpoint.resume.sequence,
|
|
119
|
+
...(checkpoint.resume.cursor !== undefined ? { cursor: redactPosition("cursor", checkpoint.resume.cursor) } : {}),
|
|
120
|
+
...(checkpoint.resume.watermark !== undefined
|
|
121
|
+
? { watermark: redactPosition("watermark", checkpoint.resume.watermark) }
|
|
122
|
+
: {}),
|
|
123
|
+
...(checkpoint.resume.timestamp !== undefined ? { timestamp: checkpoint.resume.timestamp } : {}),
|
|
124
|
+
...(checkpoint.resume.deltaToken !== undefined
|
|
125
|
+
? { deltaToken: redactPosition("deltaToken", checkpoint.resume.deltaToken) }
|
|
126
|
+
: {}),
|
|
127
|
+
}),
|
|
128
|
+
recentEventIdCount: checkpoint.recentEventIds.length,
|
|
129
|
+
savedAt: checkpoint.savedAt,
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
function redactPosition(field, value) {
|
|
133
|
+
return sha256(`honua-realtime-checkpoint-redaction:v1:${field}:${value}`);
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Canonical (sorted-key) JSON serialization of the full durable checkpoint,
|
|
137
|
+
* for persistence where the caller owns access control. Two checkpoints
|
|
138
|
+
* with the same accepted content always serialize identically, regardless
|
|
139
|
+
* of property insertion order — required for content-addressed storage and
|
|
140
|
+
* for stable diffing across processes.
|
|
141
|
+
*/
|
|
142
|
+
export function serializeRealtimeCheckpoint(checkpoint) {
|
|
143
|
+
return canonicalStringify(toJsonValue(checkpoint));
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* Canonical, redacted JSON serialization safe for logs and telemetry
|
|
147
|
+
* sinks — never a raw cursor, watermark, or delta-token value.
|
|
148
|
+
*/
|
|
149
|
+
export function serializeRedactedRealtimeCheckpoint(checkpoint) {
|
|
150
|
+
return canonicalStringify(toJsonValue(redactRealtimeCheckpoint(checkpoint)));
|
|
151
|
+
}
|
|
152
|
+
function freeze(value) {
|
|
153
|
+
return Object.freeze(value);
|
|
154
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"honua-server.d.ts","sourceRoot":"","sources":["../../../src/realtime/honua-server.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAC;
|
|
1
|
+
{"version":3,"file":"honua-server.d.ts","sourceRoot":"","sources":["../../../src/realtime/honua-server.ts"],"names":[],"mappings":"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"}
|
package/realtime/honua-server.js
CHANGED
|
@@ -34,7 +34,8 @@
|
|
|
34
34
|
* @module
|
|
35
35
|
*/
|
|
36
36
|
import { trimTrailingSlashes } from "../core/path-utils.js";
|
|
37
|
-
import {
|
|
37
|
+
import { HonuaRealtimeResumeError } from "./resumable.js";
|
|
38
|
+
import { createRealtimeServerSentEventsTransport, decodeRealtimeServerSentEvent } from "./sse.js";
|
|
38
39
|
/** Default streaming-features path served by honua-server. */
|
|
39
40
|
export const HONUA_SERVER_STREAMING_FEATURES_PATH = "/api/v1/streaming/features";
|
|
40
41
|
/**
|
|
@@ -83,16 +84,17 @@ export function encodeHonuaServerRealtimeRequest(url, request) {
|
|
|
83
84
|
* already speak the SDK vocabulary pass through unchanged.
|
|
84
85
|
*/
|
|
85
86
|
export function decodeHonuaServerRealtimeEvent(payload) {
|
|
86
|
-
if (!isRecord(payload))
|
|
87
|
-
throw new
|
|
87
|
+
if (!isRecord(payload)) {
|
|
88
|
+
throw new HonuaRealtimeResumeError("invalid-event", "honua-server streaming payload must be a JSON object.");
|
|
89
|
+
}
|
|
88
90
|
const envelope = payload;
|
|
89
91
|
// Status / heartbeat / error envelopes already use the SDK vocabulary.
|
|
90
92
|
if (typeof envelope.type === "string" && envelope.type !== "change" && envelope.type !== "feature-change") {
|
|
91
|
-
return payload;
|
|
93
|
+
return decodeRealtimeServerSentEvent(payload);
|
|
92
94
|
}
|
|
93
95
|
const changes = collectChanges(envelope);
|
|
94
96
|
if (changes.length === 0) {
|
|
95
|
-
throw new
|
|
97
|
+
throw new HonuaRealtimeResumeError("invalid-event", "honua-server feature-change envelope is missing changes.");
|
|
96
98
|
}
|
|
97
99
|
const base = {
|
|
98
100
|
eventId: envelope.eventId,
|
|
@@ -105,15 +107,18 @@ export function decodeHonuaServerRealtimeEvent(payload) {
|
|
|
105
107
|
const upserts = [];
|
|
106
108
|
const deletes = [];
|
|
107
109
|
for (const change of changes) {
|
|
110
|
+
if (!isRecord(change)) {
|
|
111
|
+
throw new HonuaRealtimeResumeError("invalid-event", "honua-server feature change must be an object.");
|
|
112
|
+
}
|
|
108
113
|
if (change.featureId === undefined) {
|
|
109
|
-
throw new
|
|
114
|
+
throw new HonuaRealtimeResumeError("invalid-event", "honua-server feature change is missing featureId.");
|
|
110
115
|
}
|
|
111
116
|
if (change.op === "delete") {
|
|
112
117
|
deletes.push({ id: change.featureId, version: change.version, updatedAt: change.updatedAt });
|
|
113
118
|
continue;
|
|
114
119
|
}
|
|
115
120
|
if (change.feature === undefined) {
|
|
116
|
-
throw new
|
|
121
|
+
throw new HonuaRealtimeResumeError("invalid-event", `honua-server ${change.op} change is missing feature payload.`);
|
|
117
122
|
}
|
|
118
123
|
upserts.push({
|
|
119
124
|
id: change.featureId,
|
|
@@ -161,7 +166,7 @@ export function createHonuaServerRealtimeSubscription(options) {
|
|
|
161
166
|
}
|
|
162
167
|
function resolveStreamingUrl(baseUrl) {
|
|
163
168
|
if (!baseUrl) {
|
|
164
|
-
throw new
|
|
169
|
+
throw new HonuaRealtimeResumeError("invalid-event", "createHonuaServerRealtimeSubscription requires either `url` or `baseUrl`.");
|
|
165
170
|
}
|
|
166
171
|
// Linear trim (no anchored `\/+$` regex) to avoid polynomial backtracking on
|
|
167
172
|
// adversarial input — mirrors the rest of the SDK's path handling.
|
package/realtime/index.d.ts
CHANGED
|
@@ -1,9 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* `@honua/sdk-js/realtime` — realtime transport adapters
|
|
3
|
-
* WebSocket
|
|
2
|
+
* `@honua/sdk-js/realtime` — realtime transport adapters. Raw Server-Sent
|
|
3
|
+
* Events (`sse.ts`) and WebSocket (`websocket.ts`) wire adapters pair with
|
|
4
|
+
* the bounded, resumable wrapper in `resumable-transport.ts` (issue #557),
|
|
5
|
+
* which adds the #556 delivery gate, reconnect/backoff, heartbeat timeout,
|
|
6
|
+
* and redacted telemetry on top of either one. `odata-delta.ts` (issue #558)
|
|
7
|
+
* adds a pull-based OData v4 delta-link adapter with its own bounded poll
|
|
8
|
+
* loop and honest, non-live freshness reporting — it satisfies the same
|
|
9
|
+
* `RealtimeFeatureTransport` surface but is not a socket transport, so it
|
|
10
|
+
* does not compose with `resumable-transport.ts`'s reconnect wrapper.
|
|
4
11
|
*
|
|
5
|
-
* @experimental This entrypoint is not yet covered by the SDK's semver contract
|
|
6
|
-
* — the surface may change in any minor release prior to `1.0.0`.
|
|
7
12
|
* @module
|
|
8
13
|
*/
|
|
9
14
|
export { createRealtimeServerSentEventsTransport, decodeRealtimeServerSentEvent, encodeDefaultRealtimeRequest, } from "./sse.js";
|
|
@@ -13,6 +18,16 @@ export type { CreateHonuaServerRealtimeSubscriptionOptions, HonuaServerFeatureCh
|
|
|
13
18
|
export { emptyRealtimeFeatureState, reconcileRealtimeStaleness, reduceRealtimeFeatureState, realtimeFeatureKey, realtimeResumeCheckpoint, realtimeSubscriptionKey, } from "./reducer.js";
|
|
14
19
|
export { createRealtimeFeatureStore } from "./store.js";
|
|
15
20
|
export type { RealtimeFeatureStore } from "./store.js";
|
|
21
|
+
export { REALTIME_DURABLE_CHECKPOINT_VERSION, HonuaRealtimeResumeError, createResumableRealtimeSubscription, evaluateRealtimeCheckpoint, } from "./resumable.js";
|
|
22
|
+
export type { CreateResumableRealtimeSubscriptionOptions, RealtimeCheckpointCompatibility, RealtimeCheckpointCompatibilityCode, RealtimeCheckpointStore, RealtimeDurableCheckpointV1, RealtimeExternalResnapshotReason, RealtimeResumeContextV1, RealtimeSequencedEvent, ResumableRealtimeDelivery, ResumableRealtimeDeliveryStatus, ResumableRealtimePhase, ResumableRealtimeReasonCode, ResumableRealtimeState, ResumableRealtimeSubscription, } from "./resumable.js";
|
|
23
|
+
export { assertRealtimePlanIdentity, deriveRealtimeContractAuthority, realtimePlanFingerprint, redactRealtimeCheckpoint, serializeRealtimeCheckpoint, serializeRedactedRealtimeCheckpoint, } from "./contract.js";
|
|
24
|
+
export type { DeriveRealtimeContractAuthorityOptions, RealtimeContractAuthority, RealtimeContractAuthorityState, RedactedRealtimeCheckpointV1, RedactedRealtimeResumePosition, RedactedResumePosition, } from "./contract.js";
|
|
25
|
+
export { createRealtimeWebSocketTransport } from "./websocket.js";
|
|
26
|
+
export type { RealtimeWebSocket, RealtimeWebSocketCloseEvent, RealtimeWebSocketFactory, RealtimeWebSocketTransportOptions, } from "./websocket.js";
|
|
27
|
+
export { createOdataDeltaTransport } from "./odata-delta.js";
|
|
28
|
+
export type { OdataDeltaEntity, OdataDeltaInitialQuery, OdataDeltaPollTelemetry, OdataDeltaTransportOptions, } from "./odata-delta.js";
|
|
29
|
+
export { computeReconnectDelayMs, createResumableRealtimeTransport, createResumableServerSentEventsTransport, createResumableWebSocketTransport, } from "./resumable-transport.js";
|
|
30
|
+
export type { RealtimeReconnectPolicy, RealtimeResumableTransportOptions, RealtimeResumableTransportTelemetry, } from "./resumable-transport.js";
|
|
16
31
|
export { filterRealtimeSelection, reconcileRealtimeSelection } from "./exploration.js";
|
|
17
32
|
export { selectRealtimeDetail, selectRealtimeFeatureRecordMap, selectRealtimeFeatureRecords, selectRealtimeFeatureTombstones, selectRealtimeFeatures, } from "./projections.js";
|
|
18
33
|
export type { RealtimeDetailSelectorOptions, RealtimeDetailState, RealtimeFeatureRecordSelectorOptions, RealtimeFeatureReference, } from "./projections.js";
|
package/realtime/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/realtime/index.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/realtime/index.ts"],"names":[],"mappings":"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"}
|
package/realtime/index.js
CHANGED
|
@@ -1,14 +1,24 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* `@honua/sdk-js/realtime` — realtime transport adapters
|
|
3
|
-
* WebSocket
|
|
2
|
+
* `@honua/sdk-js/realtime` — realtime transport adapters. Raw Server-Sent
|
|
3
|
+
* Events (`sse.ts`) and WebSocket (`websocket.ts`) wire adapters pair with
|
|
4
|
+
* the bounded, resumable wrapper in `resumable-transport.ts` (issue #557),
|
|
5
|
+
* which adds the #556 delivery gate, reconnect/backoff, heartbeat timeout,
|
|
6
|
+
* and redacted telemetry on top of either one. `odata-delta.ts` (issue #558)
|
|
7
|
+
* adds a pull-based OData v4 delta-link adapter with its own bounded poll
|
|
8
|
+
* loop and honest, non-live freshness reporting — it satisfies the same
|
|
9
|
+
* `RealtimeFeatureTransport` surface but is not a socket transport, so it
|
|
10
|
+
* does not compose with `resumable-transport.ts`'s reconnect wrapper.
|
|
4
11
|
*
|
|
5
|
-
* @experimental This entrypoint is not yet covered by the SDK's semver contract
|
|
6
|
-
* — the surface may change in any minor release prior to `1.0.0`.
|
|
7
12
|
* @module
|
|
8
13
|
*/
|
|
9
14
|
export { createRealtimeServerSentEventsTransport, decodeRealtimeServerSentEvent, encodeDefaultRealtimeRequest, } from "./sse.js";
|
|
10
15
|
export { createHonuaServerRealtimeSubscription, decodeHonuaServerRealtimeEvent, encodeHonuaServerRealtimeRequest, honuaServerRealtimePreset, HONUA_SERVER_STREAMING_FEATURES_PATH, } from "./honua-server.js";
|
|
11
16
|
export { emptyRealtimeFeatureState, reconcileRealtimeStaleness, reduceRealtimeFeatureState, realtimeFeatureKey, realtimeResumeCheckpoint, realtimeSubscriptionKey, } from "./reducer.js";
|
|
12
17
|
export { createRealtimeFeatureStore } from "./store.js";
|
|
18
|
+
export { REALTIME_DURABLE_CHECKPOINT_VERSION, HonuaRealtimeResumeError, createResumableRealtimeSubscription, evaluateRealtimeCheckpoint, } from "./resumable.js";
|
|
19
|
+
export { assertRealtimePlanIdentity, deriveRealtimeContractAuthority, realtimePlanFingerprint, redactRealtimeCheckpoint, serializeRealtimeCheckpoint, serializeRedactedRealtimeCheckpoint, } from "./contract.js";
|
|
20
|
+
export { createRealtimeWebSocketTransport } from "./websocket.js";
|
|
21
|
+
export { createOdataDeltaTransport } from "./odata-delta.js";
|
|
22
|
+
export { computeReconnectDelayMs, createResumableRealtimeTransport, createResumableServerSentEventsTransport, createResumableWebSocketTransport, } from "./resumable-transport.js";
|
|
13
23
|
export { filterRealtimeSelection, reconcileRealtimeSelection } from "./exploration.js";
|
|
14
24
|
export { selectRealtimeDetail, selectRealtimeFeatureRecordMap, selectRealtimeFeatureRecords, selectRealtimeFeatureTombstones, selectRealtimeFeatures, } from "./projections.js";
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OData v4 delta-link pull adapter for `@honua/sdk-js/realtime` (issue #558).
|
|
3
|
+
*
|
|
4
|
+
* OData delta links (`@odata.deltaLink`) are a *pull* change feed, not a
|
|
5
|
+
* socket transport: a client requests a scoped delta-link URL and the
|
|
6
|
+
* server returns only what changed since that link was minted, plus a fresh
|
|
7
|
+
* delta link for the next request. This module maps that request/response
|
|
8
|
+
* paging protocol onto the transport-neutral `RealtimeFeatureTransport`
|
|
9
|
+
* surface (`types.ts`, #556) so it composes with `createRealtimeFeatureStore`
|
|
10
|
+
* / `createResumableRealtimeSubscription` exactly like the SSE and WebSocket
|
|
11
|
+
* adapters (`sse.ts`, `websocket.ts`, #557) — with one deliberate
|
|
12
|
+
* difference dictated by REQ-004: this transport never claims to be live.
|
|
13
|
+
* `capabilities.kind` is `"polling"`, it never emits a `"heartbeat"`, and
|
|
14
|
+
* `onPoll` reports an explicit cadence/freshness snapshot on every request
|
|
15
|
+
* cycle so a consumer can render "checked N seconds ago, next check in M"
|
|
16
|
+
* rather than a live badge that misrepresents a poll as a stream.
|
|
17
|
+
*
|
|
18
|
+
* Ownership split from the SSE/WebSocket adapters: those open exactly one
|
|
19
|
+
* connection per `subscribe()` and never reconnect on their own, deferring
|
|
20
|
+
* reconnect entirely to `resumable-transport.ts`. This transport has no
|
|
21
|
+
* connection to reconnect — every cycle is an independent request/response —
|
|
22
|
+
* so it owns its own bounded poll loop, including the on-expiry resnapshot
|
|
23
|
+
* recovery a socket transport cannot perform for itself: an expired or
|
|
24
|
+
* rejected delta link (REQ per the User Workflow, "Expired/invalid delta
|
|
25
|
+
* state requires an explicit resnapshot") is recovered by re-running a full
|
|
26
|
+
* snapshot cycle and emitting a fresh `type: "snapshot", replace: true`
|
|
27
|
+
* event, bounded by `maxConsecutiveResnapshots` so a server that keeps
|
|
28
|
+
* rejecting the token cannot loop forever.
|
|
29
|
+
*
|
|
30
|
+
* Security posture (REQ-002): every `@odata.nextLink` / `@odata.deltaLink`
|
|
31
|
+
* this transport follows, and any caller-supplied `resumeFrom.deltaToken`,
|
|
32
|
+
* must resolve to the exact same origin and collection path as the
|
|
33
|
+
* configured `url`. A link that does not is rejected rather than followed —
|
|
34
|
+
* a delta token is an opaque, potentially attacker-influenced value once it
|
|
35
|
+
* has round-tripped through a checkpoint store, and this transport never
|
|
36
|
+
* trusts it to redirect a request elsewhere.
|
|
37
|
+
*
|
|
38
|
+
* @module
|
|
39
|
+
*/
|
|
40
|
+
import type { FeatureId } from "../contract/types.js";
|
|
41
|
+
import type { RealtimeFeatureTransport } from "./types.js";
|
|
42
|
+
/** Raw OData entity as delivered in a `value[]` array entry, annotations included. */
|
|
43
|
+
export interface OdataDeltaEntity {
|
|
44
|
+
readonly [key: string]: unknown;
|
|
45
|
+
}
|
|
46
|
+
/** `$`-shaped query options applied only to the initial full-collection snapshot request. */
|
|
47
|
+
export interface OdataDeltaInitialQuery {
|
|
48
|
+
readonly filter?: string;
|
|
49
|
+
readonly select?: readonly string[];
|
|
50
|
+
readonly orderBy?: readonly string[];
|
|
51
|
+
readonly top?: number;
|
|
52
|
+
}
|
|
53
|
+
/** Honest pull-cadence/freshness telemetry emitted after every request cycle. Never implies push liveness. */
|
|
54
|
+
export interface OdataDeltaPollTelemetry {
|
|
55
|
+
/** When this cycle's response was accepted, per `now()`. */
|
|
56
|
+
readonly polledAt: number;
|
|
57
|
+
/** When the next poll is scheduled, per `now()`. Absent while a resnapshot recovery is in progress. */
|
|
58
|
+
readonly nextPollAt?: number;
|
|
59
|
+
readonly intervalMs: number;
|
|
60
|
+
readonly sequence: number;
|
|
61
|
+
readonly changed: boolean;
|
|
62
|
+
readonly upsertCount: number;
|
|
63
|
+
readonly deleteCount: number;
|
|
64
|
+
}
|
|
65
|
+
export interface OdataDeltaTransportOptions<TFeature = unknown> {
|
|
66
|
+
/**
|
|
67
|
+
* Absolute OData entity-set collection URL, e.g.
|
|
68
|
+
* `"https://host/odata/Incidents"`. Every `@odata.nextLink` /
|
|
69
|
+
* `@odata.deltaLink` this transport follows, and any resumed
|
|
70
|
+
* `resumeFrom.deltaToken`, must resolve to this exact origin and path
|
|
71
|
+
* (REQ-002).
|
|
72
|
+
*/
|
|
73
|
+
readonly url: string;
|
|
74
|
+
/** Delay between the end of one accepted cycle and the start of the next. */
|
|
75
|
+
readonly pollIntervalMs: number;
|
|
76
|
+
/** Extract a stable entity id from a raw OData entity for `RealtimeFeaturePatch.id`. */
|
|
77
|
+
readonly entityId: (entity: OdataDeltaEntity) => FeatureId;
|
|
78
|
+
/** Project a raw OData entity into the feature payload. @default the entity with `@`-prefixed annotations stripped */
|
|
79
|
+
readonly toFeature?: (entity: OdataDeltaEntity) => TFeature;
|
|
80
|
+
/** `$filter` / `$select` / `$orderby` / `$top` applied only to the initial snapshot request. */
|
|
81
|
+
readonly initialQuery?: OdataDeltaInitialQuery;
|
|
82
|
+
/** Request headers, recomputed for every request (e.g. a fresh bearer token). */
|
|
83
|
+
readonly headers?: () => Record<string, string>;
|
|
84
|
+
/** Injectable fetch; defaults to `globalThis.fetch`. */
|
|
85
|
+
readonly fetchImpl?: typeof fetch;
|
|
86
|
+
/**
|
|
87
|
+
* Classify a non-ok response as an expired/rejected delta link that should
|
|
88
|
+
* trigger an explicit resnapshot rather than a generic transport failure.
|
|
89
|
+
* @default status => status === 410
|
|
90
|
+
*/
|
|
91
|
+
readonly isDeltaLinkExpiredResponse?: (status: number, body: unknown) => boolean;
|
|
92
|
+
/**
|
|
93
|
+
* Identify an OData delta-payload entry as an unsupported relationship
|
|
94
|
+
* (link) change rather than an entity change, per REQ-003. Relationship
|
|
95
|
+
* deltas fail explicitly instead of being silently dropped or
|
|
96
|
+
* misinterpreted as an entity upsert.
|
|
97
|
+
* @default entry with string `source`, `relationship`, and `target` properties
|
|
98
|
+
*/
|
|
99
|
+
readonly isRelationshipDeltaEntry?: (entry: OdataDeltaEntity) => boolean;
|
|
100
|
+
/** Safety bound on `@odata.nextLink` pages followed within one cycle. @default 500 */
|
|
101
|
+
readonly maxPagesPerCycle?: number;
|
|
102
|
+
/** Safety bound on rows collected by one snapshot cycle. @default 50_000 */
|
|
103
|
+
readonly maxSnapshotRows?: number;
|
|
104
|
+
/** Consecutive expired-delta-link resnapshots tolerated before failing closed. @default 3 */
|
|
105
|
+
readonly maxConsecutiveResnapshots?: number;
|
|
106
|
+
readonly onPoll?: (telemetry: OdataDeltaPollTelemetry) => void;
|
|
107
|
+
readonly now?: () => number;
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Build a {@link RealtimeFeatureTransport} that polls an OData v4 entity
|
|
111
|
+
* set's delta link. See the module doc for the full recovery/security model.
|
|
112
|
+
*/
|
|
113
|
+
export declare function createOdataDeltaTransport<TFeature = unknown>(options: OdataDeltaTransportOptions<TFeature>): RealtimeFeatureTransport<TFeature>;
|
|
114
|
+
//# sourceMappingURL=odata-delta.d.ts.map
|