@honua/app-platform 0.1.2-beta.0 → 0.1.7-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/app/index.d.ts.map +1 -1
- package/app/index.js +4 -0
- package/collaboration/client.d.ts.map +1 -1
- package/connect-url-safety.d.ts +53 -0
- package/connect-url-safety.js +189 -0
- package/contract/discovery.d.ts +34 -0
- package/contract/discovery.d.ts.map +1 -1
- package/contract/index.d.ts +7 -5
- package/contract/index.d.ts.map +1 -1
- package/contract/index.js +3 -2
- package/contract/pmtiles.d.ts +10 -1
- package/contract/pmtiles.d.ts.map +1 -1
- package/contract/pmtiles.js +10 -2
- package/contract/protocol-module.d.ts +84 -9
- package/contract/protocol-module.d.ts.map +1 -1
- package/contract/query-filter-fes.d.ts +18 -0
- package/contract/query-filter-fes.d.ts.map +1 -0
- package/contract/query-filter-fes.js +122 -0
- package/contract/query-filter.d.ts +300 -0
- package/contract/query-filter.d.ts.map +1 -0
- package/contract/query-filter.js +883 -0
- package/contract/schema.d.ts +12 -0
- package/contract/schema.d.ts.map +1 -1
- package/contract/schema.js +34 -0
- package/contract/source-capability-support.d.ts.map +1 -1
- package/contract/source-capability-support.js +15 -3
- package/contract/source.d.ts +45 -25
- package/contract/source.d.ts.map +1 -1
- package/contract/source.js +719 -479
- package/contract/types.d.ts +88 -19
- package/contract/types.d.ts.map +1 -1
- package/contract/types.js +4 -0
- package/contract/widget-source.d.ts.map +1 -1
- package/controls/basemap-switcher.d.ts +19 -11
- package/controls/basemap-switcher.d.ts.map +1 -1
- package/controls/basemap-switcher.js +58 -25
- package/controls/catalog.d.ts +105 -0
- package/controls/catalog.d.ts.map +1 -0
- package/controls/catalog.js +341 -0
- package/controls/element-utils.d.ts +9 -0
- package/controls/element-utils.d.ts.map +1 -1
- package/controls/element-utils.js +17 -0
- package/controls/index.d.ts +29 -8
- package/controls/index.d.ts.map +1 -1
- package/controls/index.js +26 -8
- package/controls/layer-list.d.ts +13 -5
- package/controls/layer-list.d.ts.map +1 -1
- package/controls/layer-list.js +72 -15
- package/controls/legend.d.ts +32 -5
- package/controls/legend.d.ts.map +1 -1
- package/controls/legend.js +95 -25
- package/controls/qualification.d.ts +152 -0
- package/controls/qualification.d.ts.map +1 -0
- package/controls/qualification.js +1265 -0
- package/controls/registry.d.ts +104 -0
- package/controls/registry.d.ts.map +1 -0
- package/controls/registry.js +150 -0
- package/controls/swipe-control.d.ts +6 -1
- package/controls/swipe-control.d.ts.map +1 -1
- package/controls/swipe-control.js +82 -20
- package/core/client.d.ts +51 -21
- package/core/client.d.ts.map +1 -1
- package/core/client.js +234 -54
- package/core/error-classifications.d.ts +23 -0
- package/core/error-classifications.d.ts.map +1 -1
- package/core/error-classifications.js +23 -0
- package/core/error-code-registry.d.ts +24 -1
- package/core/error-code-registry.d.ts.map +1 -1
- package/core/error-code-registry.js +23 -0
- package/core/error-envelope.js +2 -0
- package/core/errors.d.ts +2 -2
- package/core/errors.d.ts.map +1 -1
- package/core/errors.js +1 -1
- package/core/geoparquet-sql.d.ts +69 -5
- package/core/geoparquet-sql.d.ts.map +1 -1
- package/core/geoparquet-sql.js +327 -10
- package/core/odata-write-codec.d.ts.map +1 -1
- package/core/odata-write-codec.js +0 -1
- package/core/odata.d.ts +6 -0
- package/core/odata.d.ts.map +1 -1
- package/core/odata.js +6 -1
- package/core/ogc-processes.d.ts +182 -25
- package/core/ogc-processes.d.ts.map +1 -1
- package/core/ogc-processes.js +602 -30
- package/core/process-runner.d.ts +8 -1
- package/core/process-runner.d.ts.map +1 -1
- package/core/process-runner.js +1 -0
- package/core/protocol-transport.d.ts +2 -0
- package/core/protocol-transport.d.ts.map +1 -1
- package/core/stac.d.ts +32 -0
- package/core/stac.d.ts.map +1 -1
- package/core/stac.js +158 -23
- package/core/surfaces.d.ts.map +1 -1
- package/core/surfaces.js +273 -68
- package/core/types.d.ts +138 -11
- package/core/types.d.ts.map +1 -1
- package/core/wfs-axis-order.d.ts +18 -0
- package/core/wfs-axis-order.d.ts.map +1 -0
- package/core/wfs-axis-order.js +108 -0
- package/core/wfs-capabilities.d.ts +65 -24
- package/core/wfs-capabilities.d.ts.map +1 -1
- package/core/wfs-capabilities.js +237 -27
- package/core/wfs-filter.d.ts +26 -3
- package/core/wfs-filter.d.ts.map +1 -1
- package/core/wfs-filter.js +48 -53
- package/core/wfs-protocol-error.d.ts +12 -0
- package/core/wfs-protocol-error.d.ts.map +1 -0
- package/core/wfs-protocol-error.js +13 -0
- package/core/wfs.d.ts +53 -1
- package/core/wfs.d.ts.map +1 -1
- package/core/wfs.js +251 -29
- package/core/wms-feature-info.d.ts +92 -0
- package/core/wms-feature-info.d.ts.map +1 -0
- package/core/wms-feature-info.js +285 -0
- package/core/wms.d.ts +9 -0
- package/core/wms.d.ts.map +1 -1
- package/core/wms.js +17 -5
- package/esri-compat/event-bus.d.ts +20 -0
- package/esri-compat/event-bus.d.ts.map +1 -1
- package/esri-compat/feature-filter.d.ts +46 -1
- package/esri-compat/feature-filter.d.ts.map +1 -1
- package/esri-compat/feature-filter.js +49 -0
- package/esri-compat/geojson-layer.d.ts +4 -2
- package/esri-compat/geojson-layer.d.ts.map +1 -1
- package/esri-compat/geojson-layer.js +4 -2
- package/esri-compat/identity-manager.js +2 -2
- package/esri-compat/imagery-layer.d.ts +2 -1
- package/esri-compat/imagery-layer.d.ts.map +1 -1
- package/esri-compat/imagery-layer.js +2 -1
- package/esri-compat/layer-list.d.ts +8 -1
- package/esri-compat/layer-list.d.ts.map +1 -1
- package/esri-compat/layer-list.js +11 -4
- package/esri-compat/legend.d.ts.map +1 -1
- package/esri-compat/legend.js +1 -1
- package/esri-compat/locator.d.ts +297 -0
- package/esri-compat/locator.d.ts.map +1 -0
- package/esri-compat/locator.js +579 -0
- package/esri-compat/map-image-layer.d.ts.map +1 -1
- package/esri-compat/map-view.d.ts.map +1 -1
- package/esri-compat/map-view.js +1 -1
- package/esri-compat/scene-view.d.ts.map +1 -1
- package/esri-compat/time-slider.d.ts +29 -0
- package/esri-compat/time-slider.d.ts.map +1 -1
- package/esri-compat/time-slider.js +117 -0
- package/esri-compat/wfs-layer.d.ts +2 -1
- package/esri-compat/wfs-layer.d.ts.map +1 -1
- package/esri-compat/wfs-layer.js +2 -1
- package/esri-compat/widget-host.d.ts +42 -6
- package/esri-compat/widget-host.d.ts.map +1 -1
- package/esri-compat/widget-host.js +99 -10
- package/generated-app/projection.d.ts.map +1 -1
- package/generated-app/runtime.d.ts.map +1 -1
- package/interactions/declarative-contract.d.ts +149 -0
- package/interactions/declarative-contract.d.ts.map +1 -0
- package/interactions/declarative-contract.js +293 -0
- package/interactions/declarative.d.ts +198 -0
- package/interactions/declarative.d.ts.map +1 -0
- package/interactions/declarative.js +470 -0
- package/interactions/feature-state.d.ts +7 -0
- package/interactions/feature-state.d.ts.map +1 -1
- package/interactions/feature-state.js +10 -2
- package/map/data-to-map-bridge.d.ts +1 -1
- package/map/data-to-map-bridge.d.ts.map +1 -1
- package/map/data-to-map-bridge.js +1 -2
- package/map/honua-map.d.ts.map +1 -1
- package/map/honua-map.js +1 -1
- package/map/index.d.ts +2 -0
- package/map/index.d.ts.map +1 -1
- package/map/index.js +2 -0
- package/map/maplibre-target.js +1 -1
- package/map/realtime-reconciliation-adapter.d.ts +98 -0
- package/map/realtime-reconciliation-adapter.d.ts.map +1 -0
- package/map/realtime-reconciliation-adapter.js +84 -0
- package/map/temporal-playback.d.ts +25 -0
- package/map/temporal-playback.d.ts.map +1 -1
- package/map/temporal-playback.js +29 -1
- package/operator/workspace/workspace.d.ts.map +1 -1
- package/operator/workspace/workspace.js +0 -2
- package/package.json +4 -3
- package/query-planner/canonical-filter.d.ts +30 -0
- package/query-planner/canonical-filter.d.ts.map +1 -0
- package/query-planner/canonical-filter.js +30 -0
- package/query-planner/columnar-plan.d.ts +63 -0
- package/query-planner/columnar-plan.d.ts.map +1 -0
- package/query-planner/columnar-plan.js +94 -0
- package/query-planner/diagnostics.d.ts +3 -3
- package/query-planner/diagnostics.d.ts.map +1 -1
- package/query-planner/diagnostics.js +17 -4
- package/query-planner/duckdb.d.ts.map +1 -1
- package/query-planner/duckdb.js +16 -2
- package/query-planner/execution-context.d.ts.map +1 -1
- package/query-planner/execution-context.js +3 -1
- package/query-planner/executor.d.ts.map +1 -1
- package/query-planner/executor.js +7 -1
- package/query-planner/geoservices.d.ts.map +1 -1
- package/query-planner/geoservices.js +24 -5
- package/query-planner/grpc.d.ts.map +1 -1
- package/query-planner/grpc.js +29 -5
- package/query-planner/index.d.ts +20 -5
- package/query-planner/index.d.ts.map +1 -1
- package/query-planner/index.js +16 -3
- package/query-planner/ir.d.ts +6 -1
- package/query-planner/ir.d.ts.map +1 -1
- package/query-planner/ir.js +63 -1
- package/query-planner/odata-protocol-module.d.ts +54 -0
- package/query-planner/odata-protocol-module.d.ts.map +1 -0
- package/query-planner/odata-protocol-module.js +199 -0
- package/query-planner/odata-v1.d.ts +23 -0
- package/query-planner/odata-v1.d.ts.map +1 -0
- package/query-planner/odata-v1.js +202 -0
- package/query-planner/odata.d.ts +0 -3
- package/query-planner/odata.d.ts.map +1 -1
- package/query-planner/odata.js +0 -143
- package/query-planner/ogc-features.d.ts.map +1 -1
- package/query-planner/ogc-features.js +23 -1
- package/query-planner/planner.d.ts +2 -2
- package/query-planner/planner.d.ts.map +1 -1
- package/query-planner/planner.js +146 -27
- package/query-planner/representation.d.ts +59 -0
- package/query-planner/representation.d.ts.map +1 -0
- package/query-planner/representation.js +140 -0
- package/query-planner/types.d.ts +153 -3
- package/query-planner/types.d.ts.map +1 -1
- package/query-planner/wfs-protocol-module.d.ts +57 -0
- package/query-planner/wfs-protocol-module.d.ts.map +1 -0
- package/query-planner/wfs-protocol-module.js +731 -0
- package/query-planner/wfs-v1.d.ts +41 -0
- package/query-planner/wfs-v1.d.ts.map +1 -0
- package/query-planner/wfs-v1.js +366 -0
- package/query-planner/wfs.d.ts +1 -3
- package/query-planner/wfs.d.ts.map +1 -1
- package/query-planner/wfs.js +1 -70
- package/realtime/checkpoint-store.d.ts +206 -0
- package/realtime/checkpoint-store.d.ts.map +1 -0
- package/realtime/checkpoint-store.js +504 -0
- package/realtime/honua-server.d.ts +8 -0
- package/realtime/honua-server.d.ts.map +1 -1
- package/realtime/honua-server.js +123 -14
- package/realtime/index.d.ts +4 -0
- package/realtime/index.d.ts.map +1 -1
- package/realtime/index.js +2 -0
- package/realtime/reconciliation.d.ts +245 -0
- package/realtime/reconciliation.d.ts.map +1 -0
- package/realtime/reconciliation.js +377 -0
- package/realtime/resumable-transport.d.ts.map +1 -1
- package/realtime/sse.d.ts.map +1 -1
- package/realtime/sse.js +7 -1
- package/realtime/websocket.d.ts.map +1 -1
- package/realtime/wire.d.ts +2 -1
- package/realtime/wire.d.ts.map +1 -1
- package/realtime/wire.js +24 -8
- package/replica-sync/errors.d.ts +19 -1
- package/replica-sync/errors.d.ts.map +1 -1
- package/replica-sync/errors.js +16 -0
- package/replica-sync/geoservices.d.ts +260 -0
- package/replica-sync/geoservices.d.ts.map +1 -0
- package/replica-sync/geoservices.js +1093 -0
- package/replica-sync/index.d.ts +5 -1
- package/replica-sync/index.d.ts.map +1 -1
- package/replica-sync/index.js +3 -1
- package/replica-sync/transport-conformance.d.ts +58 -0
- package/replica-sync/transport-conformance.d.ts.map +1 -0
- package/replica-sync/transport-conformance.js +265 -0
- package/replica-sync/types.d.ts +22 -0
- package/replica-sync/types.d.ts.map +1 -1
- package/runtime/index.d.ts +6 -2
- package/runtime/index.d.ts.map +1 -1
- package/runtime/index.js +3 -1
- package/runtime/load-package.d.ts +24 -0
- package/runtime/load-package.d.ts.map +1 -1
- package/runtime/load-package.js +45 -1
- package/runtime/maplibre-renderer.d.ts +11 -0
- package/runtime/maplibre-renderer.d.ts.map +1 -1
- package/runtime/maplibre-renderer.js +32 -7
- package/runtime/offline-region-protocol.d.ts +73 -0
- package/runtime/offline-region-protocol.d.ts.map +1 -0
- package/runtime/offline-region-protocol.js +63 -0
- package/runtime/offline-region-style.d.ts +78 -0
- package/runtime/offline-region-style.d.ts.map +1 -0
- package/runtime/offline-region-style.js +147 -0
- package/runtime/pmtiles-protocol.d.ts +7 -8
- package/runtime/pmtiles-protocol.d.ts.map +1 -1
- package/runtime/pmtiles-protocol.js +19 -66
- package/runtime/protocol-registry.d.ts +55 -0
- package/runtime/protocol-registry.d.ts.map +1 -0
- package/runtime/protocol-registry.js +103 -0
- package/runtime/runtime.d.ts +2 -0
- package/runtime/runtime.d.ts.map +1 -1
- package/runtime/runtime.js +6 -1
- package/runtime/style-compose.d.ts.map +1 -1
- package/runtime/style-compose.js +78 -9
- package/scene-workspace/analysis-widgets.d.ts +4 -2
- package/scene-workspace/analysis-widgets.d.ts.map +1 -1
- package/scene-workspace/analysis-widgets.js +4 -2
- package/scene-workspace/camera-correspondence.d.ts +151 -0
- package/scene-workspace/camera-correspondence.d.ts.map +1 -0
- package/scene-workspace/camera-correspondence.js +220 -0
- package/scene-workspace/cesium-adapter.d.ts +230 -29
- package/scene-workspace/cesium-adapter.d.ts.map +1 -1
- package/scene-workspace/cesium-adapter.js +741 -56
- package/scene-workspace/cesium-mount.d.ts +205 -0
- package/scene-workspace/cesium-mount.d.ts.map +1 -0
- package/scene-workspace/cesium-mount.js +392 -0
- package/scene-workspace/cesium-scene-owner.d.ts +160 -0
- package/scene-workspace/cesium-scene-owner.d.ts.map +1 -0
- package/scene-workspace/cesium-scene-owner.js +205 -0
- package/scene-workspace/cesium-state-sync.d.ts +101 -0
- package/scene-workspace/cesium-state-sync.d.ts.map +1 -0
- package/scene-workspace/cesium-state-sync.js +432 -0
- package/scene-workspace/cesium-time.d.ts +346 -0
- package/scene-workspace/cesium-time.d.ts.map +1 -0
- package/scene-workspace/cesium-time.js +403 -0
- package/scene-workspace/index.d.ts +42 -10
- package/scene-workspace/index.d.ts.map +1 -1
- package/scene-workspace/index.js +32 -7
- package/scene-workspace/maplibre-state-sync.d.ts +112 -0
- package/scene-workspace/maplibre-state-sync.d.ts.map +1 -0
- package/scene-workspace/maplibre-state-sync.js +400 -0
- package/scene-workspace/primitives.d.ts +237 -3
- package/scene-workspace/primitives.d.ts.map +1 -1
- package/scene-workspace/primitives.js +1087 -17
- package/scene-workspace/scene-discovery.d.ts +5 -2
- package/scene-workspace/scene-discovery.d.ts.map +1 -1
- package/scene-workspace/scene-discovery.js +5 -2
- package/scene-workspace/scene-view.d.ts +5 -2
- package/scene-workspace/scene-view.d.ts.map +1 -1
- package/scene-workspace/scene-view.js +5 -2
- package/scene-workspace/source-to-cesium.d.ts +141 -8
- package/scene-workspace/source-to-cesium.d.ts.map +1 -1
- package/scene-workspace/source-to-cesium.js +497 -72
- package/scene-workspace/state-sync-port-kit.d.ts +156 -0
- package/scene-workspace/state-sync-port-kit.d.ts.map +1 -0
- package/scene-workspace/state-sync-port-kit.js +319 -0
- package/scene-workspace/state-sync.d.ts +34 -1
- package/scene-workspace/state-sync.d.ts.map +1 -1
- package/scene-workspace/state-sync.js +48 -0
- package/scene-workspace/types.d.ts +8 -0
- package/scene-workspace/types.d.ts.map +1 -1
- package/scene-workspace/types.js +2 -0
- package/scene-workspace/workspace.d.ts +12 -1
- package/scene-workspace/workspace.d.ts.map +1 -1
- package/scene-workspace/workspace.js +13 -0
- package/studio/index.d.ts +24 -7
- package/studio/index.d.ts.map +1 -1
- package/studio/index.js +21 -7
- package/studio/lifecycle-client.d.ts +164 -0
- package/studio/lifecycle-client.d.ts.map +1 -0
- package/studio/lifecycle-client.js +299 -0
- package/studio/lifecycle-errors.d.ts +91 -0
- package/studio/lifecycle-errors.d.ts.map +1 -0
- package/studio/lifecycle-errors.js +77 -0
- package/studio/lifecycle-types.d.ts +321 -0
- package/studio/lifecycle-types.d.ts.map +1 -0
- package/studio/lifecycle-types.js +22 -0
- package/web-components/controller.d.ts.map +1 -1
- package/web-components/csp-styles.d.ts +6 -0
- package/web-components/csp-styles.d.ts.map +1 -0
- package/web-components/csp-styles.js +53 -0
- package/web-components/elements.d.ts +158 -2
- package/web-components/elements.d.ts.map +1 -1
- package/web-components/elements.js +1302 -215
- package/web-components/export-redaction.d.ts +252 -0
- package/web-components/export-redaction.d.ts.map +1 -0
- package/web-components/export-redaction.js +588 -0
- package/web-components/export.d.ts +417 -0
- package/web-components/export.d.ts.map +1 -0
- package/web-components/export.js +719 -0
- package/web-components/feature-editor-model.d.ts +265 -0
- package/web-components/feature-editor-model.d.ts.map +1 -0
- package/web-components/feature-editor-model.js +438 -0
- package/web-components/feature-editor-sketch.d.ts +125 -0
- package/web-components/feature-editor-sketch.d.ts.map +1 -0
- package/web-components/feature-editor-sketch.js +194 -0
- package/web-components/feature-editor-workflow.d.ts +286 -0
- package/web-components/feature-editor-workflow.d.ts.map +1 -0
- package/web-components/feature-editor-workflow.js +889 -0
- package/web-components/feature-editor.d.ts +83 -0
- package/web-components/feature-editor.d.ts.map +1 -0
- package/web-components/feature-editor.js +829 -0
- package/web-components/feature-table-engine.d.ts +465 -0
- package/web-components/feature-table-engine.d.ts.map +1 -0
- package/web-components/feature-table-engine.js +1360 -0
- package/web-components/feature-table-view.d.ts +95 -0
- package/web-components/feature-table-view.d.ts.map +1 -0
- package/web-components/feature-table-view.js +343 -0
- package/web-components/index.d.ts +34 -3
- package/web-components/index.d.ts.map +1 -1
- package/web-components/index.js +35 -2
- package/web-components/maplibre-renderer.d.ts.map +1 -1
- package/web-components/maplibre-renderer.js +13 -4
- package/web-components/measurement.d.ts +3 -1
- package/web-components/measurement.d.ts.map +1 -1
- package/web-components/measurement.js +62 -18
- package/web-components/time-slider.d.ts +122 -0
- package/web-components/time-slider.d.ts.map +1 -0
- package/web-components/time-slider.js +740 -0
- package/web-components/types.d.ts +310 -0
- package/web-components/types.d.ts.map +1 -1
- package/webmap/convert-label.js +0 -1
- package/webmap/convert-symbol.d.ts.map +1 -1
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared machinery for the shipped renderer state-sync ports.
|
|
3
|
+
*
|
|
4
|
+
* `createSceneStateSynchronizer` is a transport: it revisions, coalesces, and
|
|
5
|
+
* delivers, but it never touches a renderer. Everything a *real* port has to get
|
|
6
|
+
* right — sequence discipline, echo suppression, bounded convergence, listener
|
|
7
|
+
* lifecycles, and honest degradation reporting — lives here once so the
|
|
8
|
+
* MapLibre and Cesium ports differ only in the renderer they drive.
|
|
9
|
+
*
|
|
10
|
+
* ## Echo suppression and convergence
|
|
11
|
+
*
|
|
12
|
+
* Every renderer change loop has the same hazard: state applied *from* the
|
|
13
|
+
* shared envelope makes the renderer fire its own change event, which would be
|
|
14
|
+
* re-published as a local change and bounce back forever. Two mechanisms close
|
|
15
|
+
* the loop, and both are needed:
|
|
16
|
+
*
|
|
17
|
+
* 1. **Applied-signature matching.** Immediately after applying a delivery the
|
|
18
|
+
* port reads the renderer back and records a quantized signature of what the
|
|
19
|
+
* renderer actually holds. The next renderer event whose signature matches is
|
|
20
|
+
* an echo: it is published once with `causeRevision` set to the applied
|
|
21
|
+
* revision, which the synchronizer classifies as `loop-suppressed` and drops.
|
|
22
|
+
* Publishing the acknowledgement rather than swallowing it keeps the loop
|
|
23
|
+
* closure observable instead of invisible.
|
|
24
|
+
* 2. **Lossy sides do not write back.** When a renderer cannot hold what it was
|
|
25
|
+
* given (a globe pitch a 2D map clamps, a pose outside the zoom range) the
|
|
26
|
+
* port applies its best effort, records a {@link SceneStateSyncPortDegradation},
|
|
27
|
+
* and leaves the shared value alone. Writing the clamped value back would
|
|
28
|
+
* drag every other view down to the least capable renderer's limits *and*
|
|
29
|
+
* would be the one shape that can oscillate. With this rule a change
|
|
30
|
+
* converges in one round trip: origin publishes, target applies, target
|
|
31
|
+
* acknowledges, done.
|
|
32
|
+
*
|
|
33
|
+
* @beta Part of the beta `@honua/app-platform/scene-workspace` surface.
|
|
34
|
+
* @module
|
|
35
|
+
*/
|
|
36
|
+
import type { SceneCameraDegradationCode } from "./camera-correspondence.js";
|
|
37
|
+
import { type SceneStateSyncAttributionValue, type SceneStateSyncIdentity, type SceneStateSyncInput, type SceneStateSyncPort, type SceneStateSyncRenderer, type SceneStateSyncSlice, type SceneStateSyncValueMap } from "./state-sync.js";
|
|
38
|
+
/**
|
|
39
|
+
* Named ways a shipped port could not fully honour a slice. Camera codes are
|
|
40
|
+
* shared with the pure projection helpers so one vocabulary covers both.
|
|
41
|
+
*/
|
|
42
|
+
export type SceneStateSyncPortDegradationCode = SceneCameraDegradationCode | "selection-source-missing" | "selection-target-unresolved" | "selection-not-fully-expressible" | "filters-no-target-layers" | "filters-layer-rejected" | "time-field-unconfigured" | "time-clock-unavailable" | "time-clock-host-owned" | "time-plan-rejected" | "detail-target-unresolved" | "attribution-id-rejected";
|
|
43
|
+
/** One reported shortfall, attributed to the port and revision that caused it. */
|
|
44
|
+
export interface SceneStateSyncPortDegradation {
|
|
45
|
+
readonly portId: string;
|
|
46
|
+
readonly slice: SceneStateSyncSlice;
|
|
47
|
+
readonly code: SceneStateSyncPortDegradationCode;
|
|
48
|
+
readonly message: string;
|
|
49
|
+
readonly at: string;
|
|
50
|
+
readonly revision?: number;
|
|
51
|
+
/** What the shared state asked for, when the shortfall is numeric. */
|
|
52
|
+
readonly requested?: number;
|
|
53
|
+
/** What the renderer was given instead. */
|
|
54
|
+
readonly applied?: number;
|
|
55
|
+
}
|
|
56
|
+
/** Optional quantitative context for {@link PortCore.degrade}. */
|
|
57
|
+
export interface SceneStateSyncPortDegradationDetail {
|
|
58
|
+
readonly revision?: number;
|
|
59
|
+
readonly requested?: number;
|
|
60
|
+
readonly applied?: number;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* A {@link SceneStateSyncPort} bound to a live renderer.
|
|
64
|
+
*
|
|
65
|
+
* Beyond the transport contract it adds the two things a real binding needs: a
|
|
66
|
+
* way for the host application to publish renderer-origin state the renderer
|
|
67
|
+
* has no event for (selection, filters, time), and an owned teardown.
|
|
68
|
+
*/
|
|
69
|
+
export interface SceneStateSyncRendererPort extends SceneStateSyncPort {
|
|
70
|
+
/**
|
|
71
|
+
* Publish a renderer-origin change. Returns what the port did with it:
|
|
72
|
+
* `published` for a genuine local change, `acknowledged` for an echo of a
|
|
73
|
+
* delivery this port applied, `duplicate` when nothing changed, and `disposed`
|
|
74
|
+
* after teardown.
|
|
75
|
+
*/
|
|
76
|
+
publish<Slice extends SceneStateSyncSlice>(slice: Slice, value: SceneStateSyncValueMap[Slice]): SceneStateSyncPublishOutcome;
|
|
77
|
+
/**
|
|
78
|
+
* Read a slice back out of the live renderer and publish it if it changed.
|
|
79
|
+
*
|
|
80
|
+
* The push path (renderer events) is wired automatically where the renderer
|
|
81
|
+
* has one; this is the pull complement for slices whose change notification is
|
|
82
|
+
* frame-driven or absent. `unsupported` means this renderer holds no readable
|
|
83
|
+
* state for the slice.
|
|
84
|
+
*/
|
|
85
|
+
readFromRenderer(slice: SceneStateSyncSlice): SceneStateSyncPublishOutcome | "unsupported";
|
|
86
|
+
/** Everything this port could not fully honour, oldest first and bounded. */
|
|
87
|
+
readonly degradations: readonly SceneStateSyncPortDegradation[];
|
|
88
|
+
readonly disposed: boolean;
|
|
89
|
+
/** Release every renderer listener this port owns. Idempotent. */
|
|
90
|
+
dispose(): void;
|
|
91
|
+
}
|
|
92
|
+
/** What a port did with a renderer-origin value. */
|
|
93
|
+
export type SceneStateSyncPublishOutcome = "published" | "acknowledged" | "duplicate" | "disposed";
|
|
94
|
+
/**
|
|
95
|
+
* Reduce free-form attribution text to the credential-free identifier charset
|
|
96
|
+
* the `attribution` slice accepts.
|
|
97
|
+
*
|
|
98
|
+
* The slice carries identifiers, not markup: `"County orthophotography"` becomes
|
|
99
|
+
* `county-orthophotography`, and `<a href="...">OpenStreetMap</a> contributors`
|
|
100
|
+
* becomes `openstreetmap-contributors`. Anything with no identifier characters
|
|
101
|
+
* at all — or anything carrying a URL with credentials — yields `undefined`
|
|
102
|
+
* rather than a lossy guess, and the caller reports it as a degradation. The
|
|
103
|
+
* safe-id rule is a boundary to satisfy, not to relax.
|
|
104
|
+
*
|
|
105
|
+
* Reduction is a single bounded character pass rather than a chain of regular
|
|
106
|
+
* expressions: attribution text is attacker-influenceable (it arrives from a
|
|
107
|
+
* style document or a provider), and markup stripping with a pattern is a
|
|
108
|
+
* polynomial-backtracking shape.
|
|
109
|
+
*/
|
|
110
|
+
export declare function sceneAttributionId(value: unknown): string | undefined;
|
|
111
|
+
/**
|
|
112
|
+
* Build an `attribution` slice value from live credit strings, dropping what
|
|
113
|
+
* cannot be reduced to a safe identifier and de-duplicating the rest.
|
|
114
|
+
*/
|
|
115
|
+
export declare function sceneAttributionValue(values: Iterable<unknown>): SceneStateSyncAttributionValue;
|
|
116
|
+
export interface PortCoreOptions {
|
|
117
|
+
readonly id: string;
|
|
118
|
+
readonly renderer: SceneStateSyncRenderer;
|
|
119
|
+
readonly identity: SceneStateSyncIdentity;
|
|
120
|
+
readonly now?: () => string;
|
|
121
|
+
readonly onDegraded?: (degradation: SceneStateSyncPortDegradation) => void;
|
|
122
|
+
/** Attach renderer listeners; returns the detach. Called on first subscriber. */
|
|
123
|
+
readonly bind?: () => () => void;
|
|
124
|
+
}
|
|
125
|
+
export interface PortCore {
|
|
126
|
+
readonly id: string;
|
|
127
|
+
readonly degradations: readonly SceneStateSyncPortDegradation[];
|
|
128
|
+
readonly disposed: boolean;
|
|
129
|
+
subscribe(listener: (event: SceneStateSyncInput) => void, signal?: AbortSignal): () => void;
|
|
130
|
+
publish<Slice extends SceneStateSyncSlice>(slice: Slice, value: SceneStateSyncValueMap[Slice]): SceneStateSyncPublishOutcome;
|
|
131
|
+
/**
|
|
132
|
+
* Register what counts as an echo of `revision`.
|
|
133
|
+
*
|
|
134
|
+
* Pass the renderer read-back first and the delivered value second when they
|
|
135
|
+
* differ: a renderer that clamped what it was given must acknowledge its own
|
|
136
|
+
* clamped read-back instead of publishing it as a new local change.
|
|
137
|
+
*/
|
|
138
|
+
markApplied(slice: SceneStateSyncSlice, revision: number, ...values: readonly unknown[]): void;
|
|
139
|
+
degrade(slice: SceneStateSyncSlice, code: SceneStateSyncPortDegradationCode, message: string, detail?: SceneStateSyncPortDegradationDetail): void;
|
|
140
|
+
dispose(): void;
|
|
141
|
+
}
|
|
142
|
+
/** Build the shared listener/sequence/echo core a shipped port is wrapped around. */
|
|
143
|
+
export declare function createPortCore(options: PortCoreOptions): PortCore;
|
|
144
|
+
/**
|
|
145
|
+
* Quantized equality signature for a slice value.
|
|
146
|
+
*
|
|
147
|
+
* Camera values are quantized before comparison because a renderer round-trip
|
|
148
|
+
* is never bit-exact: Cesium re-derives longitude/latitude/height from a
|
|
149
|
+
* Cartesian position, and a 2D map re-derives a zoom from a scale. Without a
|
|
150
|
+
* tolerance every applied camera would look like a fresh local change and the
|
|
151
|
+
* loop would never converge. The quantization is ~1 cm horizontally, nine
|
|
152
|
+
* significant digits of height, and 1/10000 of a degree of orientation — far
|
|
153
|
+
* below what any renderer displays, and far above float round-trip noise.
|
|
154
|
+
*/
|
|
155
|
+
export declare function sliceSignature(slice: SceneStateSyncSlice, value: unknown): string;
|
|
156
|
+
//# sourceMappingURL=state-sync-port-kit.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"state-sync-port-kit.d.ts","sourceRoot":"","sources":["../../../src/scene-workspace/state-sync-port-kit.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,319 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared machinery for the shipped renderer state-sync ports.
|
|
3
|
+
*
|
|
4
|
+
* `createSceneStateSynchronizer` is a transport: it revisions, coalesces, and
|
|
5
|
+
* delivers, but it never touches a renderer. Everything a *real* port has to get
|
|
6
|
+
* right — sequence discipline, echo suppression, bounded convergence, listener
|
|
7
|
+
* lifecycles, and honest degradation reporting — lives here once so the
|
|
8
|
+
* MapLibre and Cesium ports differ only in the renderer they drive.
|
|
9
|
+
*
|
|
10
|
+
* ## Echo suppression and convergence
|
|
11
|
+
*
|
|
12
|
+
* Every renderer change loop has the same hazard: state applied *from* the
|
|
13
|
+
* shared envelope makes the renderer fire its own change event, which would be
|
|
14
|
+
* re-published as a local change and bounce back forever. Two mechanisms close
|
|
15
|
+
* the loop, and both are needed:
|
|
16
|
+
*
|
|
17
|
+
* 1. **Applied-signature matching.** Immediately after applying a delivery the
|
|
18
|
+
* port reads the renderer back and records a quantized signature of what the
|
|
19
|
+
* renderer actually holds. The next renderer event whose signature matches is
|
|
20
|
+
* an echo: it is published once with `causeRevision` set to the applied
|
|
21
|
+
* revision, which the synchronizer classifies as `loop-suppressed` and drops.
|
|
22
|
+
* Publishing the acknowledgement rather than swallowing it keeps the loop
|
|
23
|
+
* closure observable instead of invisible.
|
|
24
|
+
* 2. **Lossy sides do not write back.** When a renderer cannot hold what it was
|
|
25
|
+
* given (a globe pitch a 2D map clamps, a pose outside the zoom range) the
|
|
26
|
+
* port applies its best effort, records a {@link SceneStateSyncPortDegradation},
|
|
27
|
+
* and leaves the shared value alone. Writing the clamped value back would
|
|
28
|
+
* drag every other view down to the least capable renderer's limits *and*
|
|
29
|
+
* would be the one shape that can oscillate. With this rule a change
|
|
30
|
+
* converges in one round trip: origin publishes, target applies, target
|
|
31
|
+
* acknowledges, done.
|
|
32
|
+
*
|
|
33
|
+
* @beta Part of the beta `@honua/app-platform/scene-workspace` surface.
|
|
34
|
+
* @module
|
|
35
|
+
*/
|
|
36
|
+
import { HonuaSceneStateSyncError, SCENE_STATE_SYNC_KIND, SCENE_STATE_SYNC_VERSION, } from "./state-sync.js";
|
|
37
|
+
const MAX_DEGRADATIONS = 256;
|
|
38
|
+
const MAX_ATTRIBUTION_ID_LENGTH = 256;
|
|
39
|
+
const MAX_ATTRIBUTION_SOURCE_LENGTH = 4_096;
|
|
40
|
+
/**
|
|
41
|
+
* Reduce free-form attribution text to the credential-free identifier charset
|
|
42
|
+
* the `attribution` slice accepts.
|
|
43
|
+
*
|
|
44
|
+
* The slice carries identifiers, not markup: `"County orthophotography"` becomes
|
|
45
|
+
* `county-orthophotography`, and `<a href="...">OpenStreetMap</a> contributors`
|
|
46
|
+
* becomes `openstreetmap-contributors`. Anything with no identifier characters
|
|
47
|
+
* at all — or anything carrying a URL with credentials — yields `undefined`
|
|
48
|
+
* rather than a lossy guess, and the caller reports it as a degradation. The
|
|
49
|
+
* safe-id rule is a boundary to satisfy, not to relax.
|
|
50
|
+
*
|
|
51
|
+
* Reduction is a single bounded character pass rather than a chain of regular
|
|
52
|
+
* expressions: attribution text is attacker-influenceable (it arrives from a
|
|
53
|
+
* style document or a provider), and markup stripping with a pattern is a
|
|
54
|
+
* polynomial-backtracking shape.
|
|
55
|
+
*/
|
|
56
|
+
export function sceneAttributionId(value) {
|
|
57
|
+
if (typeof value !== "string" || value.length > MAX_ATTRIBUTION_SOURCE_LENGTH)
|
|
58
|
+
return undefined;
|
|
59
|
+
if (containsCredentialBearingUrl(value))
|
|
60
|
+
return undefined;
|
|
61
|
+
let slug = "";
|
|
62
|
+
let inTag = false;
|
|
63
|
+
let separatorPending = false;
|
|
64
|
+
for (const character of value) {
|
|
65
|
+
if (character === "<") {
|
|
66
|
+
inTag = true;
|
|
67
|
+
separatorPending = true;
|
|
68
|
+
continue;
|
|
69
|
+
}
|
|
70
|
+
if (character === ">") {
|
|
71
|
+
inTag = false;
|
|
72
|
+
separatorPending = true;
|
|
73
|
+
continue;
|
|
74
|
+
}
|
|
75
|
+
if (inTag)
|
|
76
|
+
continue;
|
|
77
|
+
const lowered = character.toLowerCase();
|
|
78
|
+
if (!isAttributionCharacter(lowered)) {
|
|
79
|
+
separatorPending = true;
|
|
80
|
+
continue;
|
|
81
|
+
}
|
|
82
|
+
// The charset allows `.`, `_`, and `:` inside an identifier but not as its
|
|
83
|
+
// first character, so a leading run of them is dropped rather than emitted.
|
|
84
|
+
if (slug.length === 0 && !isAlphanumeric(lowered))
|
|
85
|
+
continue;
|
|
86
|
+
if (separatorPending && slug.length > 0)
|
|
87
|
+
slug += "-";
|
|
88
|
+
separatorPending = false;
|
|
89
|
+
slug += lowered;
|
|
90
|
+
if (slug.length > MAX_ATTRIBUTION_ID_LENGTH)
|
|
91
|
+
return undefined;
|
|
92
|
+
}
|
|
93
|
+
while (slug.length > 0 && !isAlphanumeric(slug.charAt(slug.length - 1)))
|
|
94
|
+
slug = slug.slice(0, -1);
|
|
95
|
+
return slug.length === 0 ? undefined : slug;
|
|
96
|
+
}
|
|
97
|
+
function isAlphanumeric(character) {
|
|
98
|
+
return (character >= "a" && character <= "z") || (character >= "0" && character <= "9");
|
|
99
|
+
}
|
|
100
|
+
/** The identifier charset minus `-`, which the pass emits as a separator. */
|
|
101
|
+
function isAttributionCharacter(character) {
|
|
102
|
+
return isAlphanumeric(character) || character === "." || character === "_" || character === ":";
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Whether the text carries a URL with userinfo (`scheme://user:secret@host`).
|
|
106
|
+
*
|
|
107
|
+
* The slug reduction below keeps `:` and `.`, so a credential pair would
|
|
108
|
+
* survive it as a readable token. Scanned character by character rather than
|
|
109
|
+
* with a regular expression so a hostile string cannot drive backtracking.
|
|
110
|
+
*/
|
|
111
|
+
function containsCredentialBearingUrl(value) {
|
|
112
|
+
const AUTHORITY_TERMINATORS = "/?# \t\n";
|
|
113
|
+
let index = value.indexOf("://");
|
|
114
|
+
while (index !== -1) {
|
|
115
|
+
const authorityStart = index + 3;
|
|
116
|
+
let end = authorityStart;
|
|
117
|
+
while (end < value.length && !AUTHORITY_TERMINATORS.includes(value[end]))
|
|
118
|
+
end += 1;
|
|
119
|
+
if (value.slice(authorityStart, end).includes("@"))
|
|
120
|
+
return true;
|
|
121
|
+
index = value.indexOf("://", authorityStart);
|
|
122
|
+
}
|
|
123
|
+
return false;
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Build an `attribution` slice value from live credit strings, dropping what
|
|
127
|
+
* cannot be reduced to a safe identifier and de-duplicating the rest.
|
|
128
|
+
*/
|
|
129
|
+
export function sceneAttributionValue(values) {
|
|
130
|
+
const ids = new Set();
|
|
131
|
+
for (const value of values) {
|
|
132
|
+
const id = sceneAttributionId(value);
|
|
133
|
+
if (id !== undefined)
|
|
134
|
+
ids.add(id);
|
|
135
|
+
}
|
|
136
|
+
return Object.freeze({ ids: Object.freeze([...ids].sort()) });
|
|
137
|
+
}
|
|
138
|
+
/** Build the shared listener/sequence/echo core a shipped port is wrapped around. */
|
|
139
|
+
export function createPortCore(options) {
|
|
140
|
+
const now = options.now ?? (() => new Date().toISOString());
|
|
141
|
+
const listeners = new Set();
|
|
142
|
+
const abortReleases = new Map();
|
|
143
|
+
const degradations = [];
|
|
144
|
+
const appliedSignatures = new Map();
|
|
145
|
+
const lastSignatures = new Map();
|
|
146
|
+
let unbind;
|
|
147
|
+
let sequence = 0;
|
|
148
|
+
let disposed = false;
|
|
149
|
+
function ensureBound() {
|
|
150
|
+
if (unbind === undefined && listeners.size > 0 && options.bind)
|
|
151
|
+
unbind = options.bind();
|
|
152
|
+
}
|
|
153
|
+
function releaseIfIdle() {
|
|
154
|
+
if (listeners.size === 0 && unbind) {
|
|
155
|
+
const release = unbind;
|
|
156
|
+
unbind = undefined;
|
|
157
|
+
release();
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
function emit(slice, value, causeRevision) {
|
|
161
|
+
sequence += 1;
|
|
162
|
+
const event = Object.freeze({
|
|
163
|
+
kind: SCENE_STATE_SYNC_KIND,
|
|
164
|
+
version: SCENE_STATE_SYNC_VERSION,
|
|
165
|
+
sequence,
|
|
166
|
+
emittedAt: now(),
|
|
167
|
+
slice,
|
|
168
|
+
value,
|
|
169
|
+
identity: options.identity,
|
|
170
|
+
...(causeRevision === undefined ? {} : { causeRevision }),
|
|
171
|
+
});
|
|
172
|
+
for (const listener of [...listeners]) {
|
|
173
|
+
try {
|
|
174
|
+
listener(event);
|
|
175
|
+
}
|
|
176
|
+
catch {
|
|
177
|
+
// A synchronizer that rejects one event must not break the renderer.
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
return {
|
|
182
|
+
id: options.id,
|
|
183
|
+
get degradations() {
|
|
184
|
+
return Object.freeze([...degradations]);
|
|
185
|
+
},
|
|
186
|
+
get disposed() {
|
|
187
|
+
return disposed;
|
|
188
|
+
},
|
|
189
|
+
subscribe(listener, signal) {
|
|
190
|
+
if (typeof listener !== "function")
|
|
191
|
+
throw new HonuaSceneStateSyncError("invalid-input", "listener must be a function");
|
|
192
|
+
if (disposed)
|
|
193
|
+
return () => undefined;
|
|
194
|
+
listeners.add(listener);
|
|
195
|
+
ensureBound();
|
|
196
|
+
const release = () => {
|
|
197
|
+
if (!listeners.delete(listener))
|
|
198
|
+
return;
|
|
199
|
+
abortReleases.get(listener)?.();
|
|
200
|
+
abortReleases.delete(listener);
|
|
201
|
+
releaseIfIdle();
|
|
202
|
+
};
|
|
203
|
+
if (signal) {
|
|
204
|
+
if (signal.aborted) {
|
|
205
|
+
release();
|
|
206
|
+
return () => undefined;
|
|
207
|
+
}
|
|
208
|
+
const onAbort = () => release();
|
|
209
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
210
|
+
abortReleases.set(listener, () => signal.removeEventListener("abort", onAbort));
|
|
211
|
+
}
|
|
212
|
+
return release;
|
|
213
|
+
},
|
|
214
|
+
publish(slice, value) {
|
|
215
|
+
if (disposed)
|
|
216
|
+
return "disposed";
|
|
217
|
+
const signature = sliceSignature(slice, value);
|
|
218
|
+
const applied = appliedSignatures.get(slice);
|
|
219
|
+
if (applied?.signatures.has(signature)) {
|
|
220
|
+
appliedSignatures.delete(slice);
|
|
221
|
+
lastSignatures.set(slice, signature);
|
|
222
|
+
emit(slice, value, applied.revision);
|
|
223
|
+
return "acknowledged";
|
|
224
|
+
}
|
|
225
|
+
if (lastSignatures.get(slice) === signature)
|
|
226
|
+
return "duplicate";
|
|
227
|
+
appliedSignatures.delete(slice);
|
|
228
|
+
lastSignatures.set(slice, signature);
|
|
229
|
+
emit(slice, value);
|
|
230
|
+
return "published";
|
|
231
|
+
},
|
|
232
|
+
markApplied(slice, revision, ...values) {
|
|
233
|
+
const signatures = new Set(values.map((value) => sliceSignature(slice, value)));
|
|
234
|
+
appliedSignatures.set(slice, { revision, signatures });
|
|
235
|
+
const first = values[0];
|
|
236
|
+
lastSignatures.set(slice, sliceSignature(slice, first));
|
|
237
|
+
},
|
|
238
|
+
degrade(slice, code, message, detail = {}) {
|
|
239
|
+
const degradation = Object.freeze({
|
|
240
|
+
portId: options.id,
|
|
241
|
+
slice,
|
|
242
|
+
code,
|
|
243
|
+
message,
|
|
244
|
+
at: now(),
|
|
245
|
+
...(detail.revision === undefined ? {} : { revision: detail.revision }),
|
|
246
|
+
...(detail.requested === undefined ? {} : { requested: detail.requested }),
|
|
247
|
+
...(detail.applied === undefined ? {} : { applied: detail.applied }),
|
|
248
|
+
});
|
|
249
|
+
degradations.push(degradation);
|
|
250
|
+
if (degradations.length > MAX_DEGRADATIONS)
|
|
251
|
+
degradations.splice(0, degradations.length - MAX_DEGRADATIONS);
|
|
252
|
+
try {
|
|
253
|
+
options.onDegraded?.(degradation);
|
|
254
|
+
}
|
|
255
|
+
catch {
|
|
256
|
+
// Observers cannot break renderer application.
|
|
257
|
+
}
|
|
258
|
+
},
|
|
259
|
+
dispose() {
|
|
260
|
+
if (disposed)
|
|
261
|
+
return;
|
|
262
|
+
disposed = true;
|
|
263
|
+
for (const release of abortReleases.values())
|
|
264
|
+
release();
|
|
265
|
+
abortReleases.clear();
|
|
266
|
+
listeners.clear();
|
|
267
|
+
appliedSignatures.clear();
|
|
268
|
+
lastSignatures.clear();
|
|
269
|
+
releaseIfIdle();
|
|
270
|
+
},
|
|
271
|
+
};
|
|
272
|
+
}
|
|
273
|
+
/**
|
|
274
|
+
* Quantized equality signature for a slice value.
|
|
275
|
+
*
|
|
276
|
+
* Camera values are quantized before comparison because a renderer round-trip
|
|
277
|
+
* is never bit-exact: Cesium re-derives longitude/latitude/height from a
|
|
278
|
+
* Cartesian position, and a 2D map re-derives a zoom from a scale. Without a
|
|
279
|
+
* tolerance every applied camera would look like a fresh local change and the
|
|
280
|
+
* loop would never converge. The quantization is ~1 cm horizontally, nine
|
|
281
|
+
* significant digits of height, and 1/10000 of a degree of orientation — far
|
|
282
|
+
* below what any renderer displays, and far above float round-trip noise.
|
|
283
|
+
*/
|
|
284
|
+
export function sliceSignature(slice, value) {
|
|
285
|
+
if (slice !== "camera")
|
|
286
|
+
return stableStringify(value);
|
|
287
|
+
if (value === null || typeof value !== "object")
|
|
288
|
+
return stableStringify(value);
|
|
289
|
+
const camera = value;
|
|
290
|
+
return [
|
|
291
|
+
quantize(camera.longitude, 7),
|
|
292
|
+
quantize(camera.latitude, 7),
|
|
293
|
+
quantizeSignificant(camera.height),
|
|
294
|
+
quantize(camera.heading, 4),
|
|
295
|
+
quantize(camera.pitch, 4),
|
|
296
|
+
quantize(camera.roll, 4),
|
|
297
|
+
].join("|");
|
|
298
|
+
}
|
|
299
|
+
function quantize(value, decimals) {
|
|
300
|
+
if (typeof value !== "number" || !Number.isFinite(value))
|
|
301
|
+
return "-";
|
|
302
|
+
const fixed = value.toFixed(decimals);
|
|
303
|
+
return fixed === `-${(0).toFixed(decimals)}` ? (0).toFixed(decimals) : fixed;
|
|
304
|
+
}
|
|
305
|
+
function quantizeSignificant(value) {
|
|
306
|
+
if (typeof value !== "number" || !Number.isFinite(value))
|
|
307
|
+
return "-";
|
|
308
|
+
return Number(value.toPrecision(9)).toString();
|
|
309
|
+
}
|
|
310
|
+
function stableStringify(value) {
|
|
311
|
+
if (value === null || typeof value !== "object")
|
|
312
|
+
return JSON.stringify(value) ?? "null";
|
|
313
|
+
if (Array.isArray(value))
|
|
314
|
+
return `[${value.map(stableStringify).join(",")}]`;
|
|
315
|
+
return `{${Object.keys(value)
|
|
316
|
+
.sort()
|
|
317
|
+
.map((key) => `${JSON.stringify(key)}:${stableStringify(value[key])}`)
|
|
318
|
+
.join(",")}}`;
|
|
319
|
+
}
|
|
@@ -1,9 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Versioned envelope that keeps two renderers — typically a Cesium scene and a
|
|
3
|
+
* MapLibre map — on the same camera, selection, filter, time, and detail state.
|
|
4
|
+
*
|
|
5
|
+
* The synchronizer is transport-neutral: it maps slices onto a caller-supplied
|
|
6
|
+
* port and reports per-slice fidelity instead of silently degrading.
|
|
7
|
+
*
|
|
8
|
+
* @beta Part of the beta `@honua/app-platform/scene-workspace` surface; the
|
|
9
|
+
* envelope kind, version, and slice vocabulary are stable through 0.1.x, and
|
|
10
|
+
* diagnostic codes grow additively.
|
|
11
|
+
* @module
|
|
12
|
+
*/
|
|
1
13
|
import type { FeatureSelectionTarget, FilterClause } from "../exploration/index.js";
|
|
2
|
-
import type { SceneCameraState, SceneDetailState, SceneTimelineState } from "./types.js";
|
|
14
|
+
import type { SceneCameraState, SceneDetailState, SceneTimelineState, SceneWorkspaceSlice } from "./types.js";
|
|
3
15
|
export declare const SCENE_STATE_SYNC_KIND: "honua.scene-state-sync";
|
|
4
16
|
export declare const SCENE_STATE_SYNC_VERSION: "1.0";
|
|
5
17
|
export declare const SCENE_STATE_SYNC_SLICES: readonly ["camera", "selection", "filters", "time", "detail", "attribution", "realtime"];
|
|
6
18
|
export type SceneStateSyncSlice = (typeof SCENE_STATE_SYNC_SLICES)[number];
|
|
19
|
+
/**
|
|
20
|
+
* How the two slice vocabularies relate (issue #1049, REQ-006).
|
|
21
|
+
*
|
|
22
|
+
* They are deliberately different sizes because they answer different
|
|
23
|
+
* questions. `SCENE_STATE_SYNC_SLICES` is a *wire* vocabulary: the seven pieces
|
|
24
|
+
* of state that two renderers in the same application have to agree on.
|
|
25
|
+
* `SCENE_WORKSPACE_SLICES` is a *store* vocabulary: the thirteen change
|
|
26
|
+
* notifications one workspace publishes to its own subscribers.
|
|
27
|
+
*
|
|
28
|
+
* The overlap is exactly what crosses renderers, and this table names it. The
|
|
29
|
+
* workspace-only members are single-application concerns with nothing to
|
|
30
|
+
* synchronize — `all` is a subscription wildcard, and `scene`, `layers`,
|
|
31
|
+
* `primitives`, `diagnostics`, `evidence`, and `history` describe what one
|
|
32
|
+
* renderer was asked to draw and what it reported back, which is per-renderer by
|
|
33
|
+
* construction. `attribution` maps to `null` in the other direction: it is
|
|
34
|
+
* derived from what each renderer actually credits rather than stored as
|
|
35
|
+
* workspace state.
|
|
36
|
+
*
|
|
37
|
+
* The only rename across the boundary is `time` (wire) / `timeline` (store).
|
|
38
|
+
*/
|
|
39
|
+
export declare const SCENE_STATE_SYNC_SLICE_WORKSPACE_CROSSWALK: Readonly<Record<SceneStateSyncSlice, SceneWorkspaceSlice | null>>;
|
|
7
40
|
export type SceneStateSyncRenderer = "maplibre" | "cesium" | "custom";
|
|
8
41
|
export type SceneStateSyncFidelity = "exact" | "equivalent" | "unsupported";
|
|
9
42
|
export interface SceneStateSyncIdentity {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"state-sync.d.ts","sourceRoot":"","sources":["../../../src/scene-workspace/state-sync.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,sBAAsB,EAAE,YAAY,EAAE,MAAM,yBAAyB,CAAC;AACpF,OAAO,KAAK,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"state-sync.d.ts","sourceRoot":"","sources":["../../../src/scene-workspace/state-sync.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,3 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Versioned envelope that keeps two renderers — typically a Cesium scene and a
|
|
3
|
+
* MapLibre map — on the same camera, selection, filter, time, and detail state.
|
|
4
|
+
*
|
|
5
|
+
* The synchronizer is transport-neutral: it maps slices onto a caller-supplied
|
|
6
|
+
* port and reports per-slice fidelity instead of silently degrading.
|
|
7
|
+
*
|
|
8
|
+
* @beta Part of the beta `@honua/app-platform/scene-workspace` surface; the
|
|
9
|
+
* envelope kind, version, and slice vocabulary are stable through 0.1.x, and
|
|
10
|
+
* diagnostic codes grow additively.
|
|
11
|
+
* @module
|
|
12
|
+
*/
|
|
1
13
|
export const SCENE_STATE_SYNC_KIND = "honua.scene-state-sync";
|
|
2
14
|
export const SCENE_STATE_SYNC_VERSION = "1.0";
|
|
3
15
|
export const SCENE_STATE_SYNC_SLICES = [
|
|
@@ -9,6 +21,35 @@ export const SCENE_STATE_SYNC_SLICES = [
|
|
|
9
21
|
"attribution",
|
|
10
22
|
"realtime",
|
|
11
23
|
];
|
|
24
|
+
/**
|
|
25
|
+
* How the two slice vocabularies relate (issue #1049, REQ-006).
|
|
26
|
+
*
|
|
27
|
+
* They are deliberately different sizes because they answer different
|
|
28
|
+
* questions. `SCENE_STATE_SYNC_SLICES` is a *wire* vocabulary: the seven pieces
|
|
29
|
+
* of state that two renderers in the same application have to agree on.
|
|
30
|
+
* `SCENE_WORKSPACE_SLICES` is a *store* vocabulary: the thirteen change
|
|
31
|
+
* notifications one workspace publishes to its own subscribers.
|
|
32
|
+
*
|
|
33
|
+
* The overlap is exactly what crosses renderers, and this table names it. The
|
|
34
|
+
* workspace-only members are single-application concerns with nothing to
|
|
35
|
+
* synchronize — `all` is a subscription wildcard, and `scene`, `layers`,
|
|
36
|
+
* `primitives`, `diagnostics`, `evidence`, and `history` describe what one
|
|
37
|
+
* renderer was asked to draw and what it reported back, which is per-renderer by
|
|
38
|
+
* construction. `attribution` maps to `null` in the other direction: it is
|
|
39
|
+
* derived from what each renderer actually credits rather than stored as
|
|
40
|
+
* workspace state.
|
|
41
|
+
*
|
|
42
|
+
* The only rename across the boundary is `time` (wire) / `timeline` (store).
|
|
43
|
+
*/
|
|
44
|
+
export const SCENE_STATE_SYNC_SLICE_WORKSPACE_CROSSWALK = Object.freeze({
|
|
45
|
+
camera: "camera",
|
|
46
|
+
selection: "selection",
|
|
47
|
+
filters: "filters",
|
|
48
|
+
time: "timeline",
|
|
49
|
+
detail: "detail",
|
|
50
|
+
attribution: null,
|
|
51
|
+
realtime: "realtime",
|
|
52
|
+
});
|
|
12
53
|
export class HonuaSceneStateSyncError extends Error {
|
|
13
54
|
code;
|
|
14
55
|
constructor(code, message, options = {}) {
|
|
@@ -25,6 +66,7 @@ const MAX_SYNCHRONOUS_SUBSCRIBE_EVENTS = 32;
|
|
|
25
66
|
const MAX_SELECTION = 2_048;
|
|
26
67
|
const MAX_FILTERS = 128;
|
|
27
68
|
const MAX_ATTRIBUTIONS = 128;
|
|
69
|
+
const MAX_TIME_SPEED = 1_000_000;
|
|
28
70
|
const MAX_JSON_NODES = 4_096;
|
|
29
71
|
const MAX_JSON_DEPTH = 24;
|
|
30
72
|
const MAX_JSON_PROPERTIES = 256;
|
|
@@ -742,11 +784,16 @@ function normalizeTime(foreign) {
|
|
|
742
784
|
};
|
|
743
785
|
const playing = optionalDataProperty(foreign, "playing");
|
|
744
786
|
const progress = optionalDataProperty(foreign, "progress");
|
|
787
|
+
// Clock rate is signed (a globe can run time backwards) and bounded, so a
|
|
788
|
+
// renderer cannot put an unusable multiplier on the wire.
|
|
789
|
+
const speed = optionalDataProperty(foreign, "speed");
|
|
745
790
|
if (playing !== undefined && typeof playing !== "boolean")
|
|
746
791
|
throw new HonuaSceneStateSyncError("invalid-input", "time.playing is invalid");
|
|
747
792
|
if (progress !== undefined &&
|
|
748
793
|
(typeof progress !== "number" || !Number.isFinite(progress) || progress < 0 || progress > 1))
|
|
749
794
|
throw new HonuaSceneStateSyncError("invalid-input", "time.progress is invalid");
|
|
795
|
+
if (speed !== undefined && (typeof speed !== "number" || !Number.isFinite(speed) || Math.abs(speed) > MAX_TIME_SPEED))
|
|
796
|
+
throw new HonuaSceneStateSyncError("invalid-input", "time.speed is invalid");
|
|
750
797
|
const currentTime = iso("currentTime");
|
|
751
798
|
const startTime = iso("startTime");
|
|
752
799
|
const endTime = iso("endTime");
|
|
@@ -756,6 +803,7 @@ function normalizeTime(foreign) {
|
|
|
756
803
|
...(endTime === undefined ? {} : { endTime }),
|
|
757
804
|
...(playing === undefined ? {} : { playing }),
|
|
758
805
|
...(progress === undefined ? {} : { progress }),
|
|
806
|
+
...(speed === undefined ? {} : { speed: Object.is(speed, -0) ? 0 : speed }),
|
|
759
807
|
});
|
|
760
808
|
}
|
|
761
809
|
function normalizeDetail(foreign) {
|
|
@@ -4,6 +4,8 @@
|
|
|
4
4
|
* These types intentionally describe state and adapter events without
|
|
5
5
|
* importing Cesium, MapLibre, or any rendering package.
|
|
6
6
|
*
|
|
7
|
+
* @beta Part of the beta `@honua/app-platform/scene-workspace` surface; these
|
|
8
|
+
* state shapes are not renamed or removed through 0.1.x and grow additively.
|
|
7
9
|
* @module
|
|
8
10
|
*/
|
|
9
11
|
import type { FeatureId, SourceId } from "../contract/types.js";
|
|
@@ -39,6 +41,12 @@ export interface SceneTimelineState {
|
|
|
39
41
|
readonly endTime?: string;
|
|
40
42
|
readonly playing?: boolean;
|
|
41
43
|
readonly progress?: number;
|
|
44
|
+
/**
|
|
45
|
+
* Clock rate: simulated seconds advanced per real second. Renderer-neutral,
|
|
46
|
+
* and bound to Cesium's `Clock.multiplier` by the scene adapter's time
|
|
47
|
+
* binding. Omit it and the renderer's own rate is left alone.
|
|
48
|
+
*/
|
|
49
|
+
readonly speed?: number;
|
|
42
50
|
}
|
|
43
51
|
export interface SceneEvidenceReference {
|
|
44
52
|
readonly id: string;
|