@honua/app-platform 0.1.1-beta.0 → 0.1.4-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/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 +87 -18
- package/contract/types.d.ts.map +1 -1
- 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 +45 -21
- package/core/client.d.ts.map +1 -1
- package/core/client.js +224 -46
- package/core/error-classifications.d.ts +6 -0
- package/core/error-classifications.d.ts.map +1 -1
- package/core/error-classifications.js +6 -0
- package/core/error-code-registry.d.ts +6 -0
- package/core/error-code-registry.d.ts.map +1 -1
- package/core/error-code-registry.js +6 -0
- package/core/error-envelope.js +2 -0
- package/core/errors.d.ts +1 -1
- package/core/errors.js +1 -1
- package/core/geoparquet-sql.d.ts +68 -4
- package/core/geoparquet-sql.d.ts.map +1 -1
- package/core/geoparquet-sql.js +325 -8
- 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 +31 -0
- package/core/stac.d.ts.map +1 -1
- package/core/stac.js +136 -19
- package/core/surfaces.d.ts.map +1 -1
- package/core/surfaces.js +273 -68
- package/core/types.d.ts +123 -9
- 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/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/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/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/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,504 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Durable {@link RealtimeCheckpointStore} implementations for
|
|
3
|
+
* `@honua/sdk-js/realtime` (issue #937).
|
|
4
|
+
*
|
|
5
|
+
* `resumable.ts` already owns the resume state machine: it loads a checkpoint,
|
|
6
|
+
* validates it with {@link evaluateRealtimeCheckpoint}, and requires an explicit
|
|
7
|
+
* replacement snapshot whenever the checkpoint no longer applies. What it never
|
|
8
|
+
* had was a shipped store, so a reload always resnapshotted even when a valid
|
|
9
|
+
* cursor was in hand a second earlier. This module supplies that durability
|
|
10
|
+
* without redefining any cursor semantics:
|
|
11
|
+
*
|
|
12
|
+
* - {@link createIndexedDbRealtimeCheckpointStore} persists one record per
|
|
13
|
+
* resume scope in the browser, keyed by the authorization-scope digest,
|
|
14
|
+
* source identity, and query identity that already define checkpoint scope.
|
|
15
|
+
* - {@link createMemoryRealtimeCheckpointStore} runs the identical
|
|
16
|
+
* normalization, screening, expiry, and eviction logic without IndexedDB, for
|
|
17
|
+
* tests and non-persistent hosts.
|
|
18
|
+
* - {@link createRealtimeCheckpointStore} binds that same logic to any
|
|
19
|
+
* {@link RealtimeCheckpointRecordStorage}, for hosts this module ships no
|
|
20
|
+
* adapter for.
|
|
21
|
+
*
|
|
22
|
+
* Four properties are deliberate:
|
|
23
|
+
*
|
|
24
|
+
* 1. **Cursors only.** A record carries the resume position (sequence, cursor,
|
|
25
|
+
* watermark, timestamp, delta token), the scope identity it belongs to, and
|
|
26
|
+
* two observation times. It never carries features, snapshots, request URLs,
|
|
27
|
+
* headers, or credentials. The authorization scope is reduced to a SHA-256
|
|
28
|
+
* digest before it reaches storage, and every remaining persisted string is
|
|
29
|
+
* screened with the shared persisted-string screen in
|
|
30
|
+
* `src/connect-url-safety.ts` — the same discipline the offline region store
|
|
31
|
+
* applies through `assertCredentialFreeManifest`.
|
|
32
|
+
* 2. **Fail-closed resume.** Load revalidates through
|
|
33
|
+
* {@link evaluateRealtimeCheckpoint} and discards the record outright when
|
|
34
|
+
* the source, query, source version, schema version, or authorization scope
|
|
35
|
+
* changed, when the record is corrupt, or when the checkpoint is older than
|
|
36
|
+
* the configured maximum age. A discarded checkpoint means the subscription
|
|
37
|
+
* resnapshots; it never means a partial delta stream is delivered as though
|
|
38
|
+
* it were complete.
|
|
39
|
+
* 3. **Bounded and atomic.** Each scope holds exactly one record (last write
|
|
40
|
+
* wins), the store holds at most `maxRecords` scope records, and the
|
|
41
|
+
* IndexedDB write plus its eviction sweep share a single readwrite
|
|
42
|
+
* transaction.
|
|
43
|
+
* 4. **Never throws into a subscription.** A storage failure, a refused
|
|
44
|
+
* credential-shaped value, or a corrupt record resolves and reports a
|
|
45
|
+
* {@link RealtimeCheckpointStoreDiagnosticV1} carrying a
|
|
46
|
+
* `HonuaRealtimeResumeError`, so the subscription degrades to a resnapshot
|
|
47
|
+
* instead of entering the gate's terminal `error` phase.
|
|
48
|
+
*
|
|
49
|
+
* The store stays opt-in: nothing here is wired into a subscription unless a
|
|
50
|
+
* caller passes it as `checkpointStore`, so no existing subscription changes
|
|
51
|
+
* behaviour by default.
|
|
52
|
+
*
|
|
53
|
+
* @module
|
|
54
|
+
*/
|
|
55
|
+
import { credentialScreenMessage, screenPersistedString } from "../connect-url-safety.js";
|
|
56
|
+
import { canonicalStringify, sha256 } from "../query-planner/canonical.js";
|
|
57
|
+
import { HonuaRealtimeResumeError, REALTIME_DURABLE_CHECKPOINT_VERSION, evaluateRealtimeCheckpoint, } from "./resumable.js";
|
|
58
|
+
/**
|
|
59
|
+
* Versioned format string stamped on every persisted record. A record whose
|
|
60
|
+
* format string differs is treated as corrupt and discarded on load, the same
|
|
61
|
+
* posture the offline stores take, so a future format can never be
|
|
62
|
+
* misinterpreted as this one.
|
|
63
|
+
*/
|
|
64
|
+
export const HONUA_REALTIME_CHECKPOINT_STORE_FORMAT = "honua.realtime-checkpoint-store/1.0";
|
|
65
|
+
/**
|
|
66
|
+
* Default maximum checkpoint age. A cursor older than this forces a resnapshot
|
|
67
|
+
* even when its scope still matches, because a server may retire a cursor
|
|
68
|
+
* window without telling the client; a silent gap is worse than an honest
|
|
69
|
+
* resnapshot.
|
|
70
|
+
*/
|
|
71
|
+
export const DEFAULT_REALTIME_CHECKPOINT_MAX_AGE_MS = 15 * 60 * 1000;
|
|
72
|
+
/** Absolute ceiling on `maxAgeMs`. Callers may tighten it, never raise it. */
|
|
73
|
+
export const MAX_REALTIME_CHECKPOINT_MAX_AGE_MS = 24 * 60 * 60 * 1000;
|
|
74
|
+
/**
|
|
75
|
+
* Default cap on distinct resume scopes retained per store. Each scope holds at
|
|
76
|
+
* most one record, so this is also the record cap; the least recently written
|
|
77
|
+
* record is evicted first.
|
|
78
|
+
*/
|
|
79
|
+
export const DEFAULT_REALTIME_CHECKPOINT_MAX_RECORDS = 64;
|
|
80
|
+
/** Absolute ceiling on `maxRecords`. Callers may tighten it, never raise it. */
|
|
81
|
+
export const MAX_REALTIME_CHECKPOINT_RECORDS = 512;
|
|
82
|
+
const DATABASE_VERSION = 1;
|
|
83
|
+
const CHECKPOINT_STORE = "checkpoints";
|
|
84
|
+
const OBSERVED_AT_INDEX = "observedAt";
|
|
85
|
+
/** Reason codes mapped onto the delivery gate's existing failure taxonomy. */
|
|
86
|
+
const DIAGNOSTIC_ERROR_CODES = {
|
|
87
|
+
"invalid-checkpoint": "invalid-checkpoint",
|
|
88
|
+
"source-changed": "source-changed",
|
|
89
|
+
"query-changed": "query-changed",
|
|
90
|
+
"source-version-changed": "source-version-changed",
|
|
91
|
+
"schema-version-changed": "schema-version-changed",
|
|
92
|
+
"authorization-scope-changed": "authorization-scope-changed",
|
|
93
|
+
"checkpoint-expired": "cursor-expired",
|
|
94
|
+
"credential-screened": "checkpoint-save-failed",
|
|
95
|
+
"storage-failed": "checkpoint-load-failed",
|
|
96
|
+
"unsupported-format": "invalid-checkpoint",
|
|
97
|
+
};
|
|
98
|
+
/**
|
|
99
|
+
* Deterministic scope key for a resume context: a digest over the
|
|
100
|
+
* authorization-scope digest, source identity, and query identity — exactly the
|
|
101
|
+
* identities checkpoint scope validation is defined against. Source and schema
|
|
102
|
+
* versions are intentionally excluded so a version bump replaces the record in
|
|
103
|
+
* place and is discarded explicitly on load, instead of orphaning it under a
|
|
104
|
+
* key nothing will read again.
|
|
105
|
+
*/
|
|
106
|
+
export function realtimeCheckpointScopeKey(context) {
|
|
107
|
+
return sha256(`honua-realtime-checkpoint-scope:v1:${canonicalStringify({
|
|
108
|
+
authorizationScopeDigest: realtimeAuthorizationScopeDigest(context.authorizationScopeFingerprint),
|
|
109
|
+
queryFingerprint: String(context.queryFingerprint),
|
|
110
|
+
sourceId: String(context.sourceId),
|
|
111
|
+
})}`);
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Opaque digest of an authorization-scope fingerprint. The fingerprint is
|
|
115
|
+
* already documented as a non-secret opaque identity; persisting only its
|
|
116
|
+
* digest keeps that promise enforceable rather than assumed.
|
|
117
|
+
*/
|
|
118
|
+
export function realtimeAuthorizationScopeDigest(fingerprint) {
|
|
119
|
+
return sha256(`honua-realtime-checkpoint-scope-digest:v1:${fingerprint}`);
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Bind the shared scope, screening, expiry, and eviction logic to any
|
|
123
|
+
* {@link RealtimeCheckpointRecordStorage}. The returned store never throws into
|
|
124
|
+
* a subscription: a rejected storage call, a refused value, or a corrupt record
|
|
125
|
+
* resolves and reports a diagnostic, so the subscription resnapshots.
|
|
126
|
+
*/
|
|
127
|
+
export function createRealtimeCheckpointStore(storage, options = {}) {
|
|
128
|
+
const normalized = normalizeOptions(options);
|
|
129
|
+
return {
|
|
130
|
+
load: (context, signal) => loadCheckpoint(storage, normalized, context, signal),
|
|
131
|
+
save: (checkpoint, signal) => saveCheckpoint(storage, normalized, checkpoint, signal),
|
|
132
|
+
records: async () => (await storage.list()).filter((record) => isCheckpointRecord(record)),
|
|
133
|
+
clear: () => storage.clear(),
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
/** In-memory store with the persistent store's exact semantics, for tests and non-persistent hosts. */
|
|
137
|
+
export function createMemoryRealtimeCheckpointStore(options = {}) {
|
|
138
|
+
return createRealtimeCheckpointStore(createMemoryCheckpointStorage(), options);
|
|
139
|
+
}
|
|
140
|
+
/** Map-backed storage with the same one-record-per-scope, oldest-first eviction contract. */
|
|
141
|
+
export function createMemoryCheckpointStorage() {
|
|
142
|
+
const records = new Map();
|
|
143
|
+
return {
|
|
144
|
+
read: async (key) => records.get(key),
|
|
145
|
+
write: async (record, maxRecords) => {
|
|
146
|
+
// Delete first so the re-inserted key moves to the tail of the insertion
|
|
147
|
+
// order, which is the eviction order.
|
|
148
|
+
records.delete(record.key);
|
|
149
|
+
records.set(record.key, record);
|
|
150
|
+
for (const key of [...records.keys()].slice(0, Math.max(0, records.size - maxRecords)))
|
|
151
|
+
records.delete(key);
|
|
152
|
+
},
|
|
153
|
+
remove: async (key) => {
|
|
154
|
+
records.delete(key);
|
|
155
|
+
},
|
|
156
|
+
list: async () => [...records.values()],
|
|
157
|
+
clear: async () => records.clear(),
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
/** Persistent browser store: one bounded, atomically written record per resume scope. */
|
|
161
|
+
export function createIndexedDbRealtimeCheckpointStore(options = {}) {
|
|
162
|
+
return createRealtimeCheckpointStore(createIndexedDbCheckpointStorage(options), options);
|
|
163
|
+
}
|
|
164
|
+
/** IndexedDB storage; each method runs in exactly one transaction. */
|
|
165
|
+
export function createIndexedDbCheckpointStorage(options = {}) {
|
|
166
|
+
const factory = options.indexedDB ?? globalThis.indexedDB;
|
|
167
|
+
if (!factory)
|
|
168
|
+
throw new Error("IndexedDB is not available in this runtime.");
|
|
169
|
+
const name = options.name ?? "honua-realtime-checkpoints";
|
|
170
|
+
if (name.trim().length === 0)
|
|
171
|
+
throw new Error("IndexedDB database name must be non-empty.");
|
|
172
|
+
const database = openDatabase(factory, name);
|
|
173
|
+
return {
|
|
174
|
+
read: async (key) => runTransaction(await database, "readonly", async (transaction) => request(transaction.objectStore(CHECKPOINT_STORE).get(key))),
|
|
175
|
+
write: async (record, maxRecords) => runTransaction(await database, "readwrite", async (transaction) => {
|
|
176
|
+
const store = transaction.objectStore(CHECKPOINT_STORE);
|
|
177
|
+
store.put(record);
|
|
178
|
+
const total = await request(store.count());
|
|
179
|
+
if (total > maxRecords)
|
|
180
|
+
await evictOldest(store, total - maxRecords, record.key);
|
|
181
|
+
}),
|
|
182
|
+
remove: async (key) => runTransaction(await database, "readwrite", async (transaction) => {
|
|
183
|
+
transaction.objectStore(CHECKPOINT_STORE).delete(key);
|
|
184
|
+
}),
|
|
185
|
+
list: async () => runTransaction(await database, "readonly", async (transaction) => request(transaction.objectStore(CHECKPOINT_STORE).getAll())),
|
|
186
|
+
clear: async () => runTransaction(await database, "readwrite", async (transaction) => {
|
|
187
|
+
transaction.objectStore(CHECKPOINT_STORE).clear();
|
|
188
|
+
}),
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
async function loadCheckpoint(backend, options, context, signal) {
|
|
192
|
+
if (signal?.aborted)
|
|
193
|
+
return undefined;
|
|
194
|
+
let scopeKey;
|
|
195
|
+
try {
|
|
196
|
+
scopeKey = realtimeCheckpointScopeKey(context);
|
|
197
|
+
}
|
|
198
|
+
catch (cause) {
|
|
199
|
+
// A context this store cannot even key is not a resumable context.
|
|
200
|
+
report(options, "load", "invalid-checkpoint", INVALID_SCOPE_KEY, "Resume context could not be keyed.", cause);
|
|
201
|
+
return undefined;
|
|
202
|
+
}
|
|
203
|
+
let stored;
|
|
204
|
+
try {
|
|
205
|
+
stored = await backend.read(scopeKey);
|
|
206
|
+
}
|
|
207
|
+
catch (cause) {
|
|
208
|
+
report(options, "load", "storage-failed", scopeKey, "Unable to read the stored realtime checkpoint.", cause);
|
|
209
|
+
return undefined;
|
|
210
|
+
}
|
|
211
|
+
if (stored === undefined || signal?.aborted)
|
|
212
|
+
return undefined;
|
|
213
|
+
const projection = projectStoredCheckpoint(stored, context, options);
|
|
214
|
+
if ("reason" in projection) {
|
|
215
|
+
await discard(backend, options, scopeKey, projection.reason, projection.detail);
|
|
216
|
+
return undefined;
|
|
217
|
+
}
|
|
218
|
+
return projection.checkpoint;
|
|
219
|
+
}
|
|
220
|
+
async function saveCheckpoint(backend, options, checkpoint, signal) {
|
|
221
|
+
if (signal?.aborted)
|
|
222
|
+
return;
|
|
223
|
+
let scopeKey = INVALID_SCOPE_KEY;
|
|
224
|
+
try {
|
|
225
|
+
const compatibility = evaluateRealtimeCheckpoint(checkpoint?.context, checkpoint);
|
|
226
|
+
if (!compatibility.compatible) {
|
|
227
|
+
report(options, "save", "invalid-checkpoint", scopeKey, compatibility.reason);
|
|
228
|
+
return;
|
|
229
|
+
}
|
|
230
|
+
scopeKey = realtimeCheckpointScopeKey(checkpoint.context);
|
|
231
|
+
}
|
|
232
|
+
catch (cause) {
|
|
233
|
+
report(options, "save", "invalid-checkpoint", scopeKey, "Checkpoint could not be projected for storage.", cause);
|
|
234
|
+
return;
|
|
235
|
+
}
|
|
236
|
+
const record = projectRecord(checkpoint, scopeKey, options.now());
|
|
237
|
+
if ("path" in record) {
|
|
238
|
+
// Refusing to persist is deliberate: the in-memory checkpoint still drives
|
|
239
|
+
// this connection, and the next load simply resnapshots.
|
|
240
|
+
report(options, "save", "credential-screened", scopeKey, credentialScreenMessage(record.path, record.reason));
|
|
241
|
+
return;
|
|
242
|
+
}
|
|
243
|
+
try {
|
|
244
|
+
await backend.write(record.record, options.maxRecords);
|
|
245
|
+
}
|
|
246
|
+
catch (cause) {
|
|
247
|
+
report(options, "save", "storage-failed", scopeKey, "Unable to persist the realtime checkpoint.", cause);
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
async function discard(backend, options, scopeKey, reason, detail) {
|
|
251
|
+
try {
|
|
252
|
+
await backend.remove(scopeKey);
|
|
253
|
+
}
|
|
254
|
+
catch {
|
|
255
|
+
// The record is already unusable; failing to delete it must not change the
|
|
256
|
+
// outcome, which is an explicit resnapshot either way.
|
|
257
|
+
}
|
|
258
|
+
report(options, "load", reason, scopeKey, detail);
|
|
259
|
+
}
|
|
260
|
+
function projectStoredCheckpoint(stored, context, options) {
|
|
261
|
+
// The format stamp is read before anything else, so a record from another
|
|
262
|
+
// store layout is reported as the version fact it is instead of being
|
|
263
|
+
// absorbed into the generic corrupt bucket. The offending value is never
|
|
264
|
+
// echoed: a hostile record could put anything in that field.
|
|
265
|
+
if (typeof stored === "object" && stored !== null && !isStoreFormat(stored.format)) {
|
|
266
|
+
return {
|
|
267
|
+
reason: "unsupported-format",
|
|
268
|
+
detail: `Stored realtime checkpoint was written in a different store format; this build reads "${HONUA_REALTIME_CHECKPOINT_STORE_FORMAT}".`,
|
|
269
|
+
};
|
|
270
|
+
}
|
|
271
|
+
if (!isCheckpointRecord(stored)) {
|
|
272
|
+
return { reason: "invalid-checkpoint", detail: "Stored realtime checkpoint record is corrupt or unreadable." };
|
|
273
|
+
}
|
|
274
|
+
if (stored.authorizationScopeDigest !== realtimeAuthorizationScopeDigest(context.authorizationScopeFingerprint)) {
|
|
275
|
+
return {
|
|
276
|
+
reason: "authorization-scope-changed",
|
|
277
|
+
detail: "Stored checkpoint authorization scope does not match the current grants.",
|
|
278
|
+
};
|
|
279
|
+
}
|
|
280
|
+
const ageMs = options.now() - Date.parse(stored.savedAt);
|
|
281
|
+
if (!Number.isFinite(ageMs) || ageMs < 0 || ageMs > options.maxAgeMs) {
|
|
282
|
+
return {
|
|
283
|
+
reason: "checkpoint-expired",
|
|
284
|
+
detail: `Stored checkpoint is outside the ${options.maxAgeMs}ms maximum resume age.`,
|
|
285
|
+
};
|
|
286
|
+
}
|
|
287
|
+
const checkpoint = Object.freeze({
|
|
288
|
+
kind: "honua.realtime-checkpoint",
|
|
289
|
+
version: REALTIME_DURABLE_CHECKPOINT_VERSION,
|
|
290
|
+
context: Object.freeze({
|
|
291
|
+
kind: "honua.realtime-resume-context",
|
|
292
|
+
version: 1,
|
|
293
|
+
sourceId: stored.sourceId,
|
|
294
|
+
queryFingerprint: stored.queryFingerprint,
|
|
295
|
+
sourceVersion: stored.sourceVersion,
|
|
296
|
+
schemaVersion: stored.schemaVersion,
|
|
297
|
+
// Safe only because the digest above already matched this exact scope.
|
|
298
|
+
authorizationScopeFingerprint: context.authorizationScopeFingerprint,
|
|
299
|
+
}),
|
|
300
|
+
resume: Object.freeze({ ...stored.resume }),
|
|
301
|
+
recentEventIds: Object.freeze([]),
|
|
302
|
+
savedAt: stored.savedAt,
|
|
303
|
+
});
|
|
304
|
+
const compatibility = evaluateRealtimeCheckpoint(context, checkpoint);
|
|
305
|
+
if (!compatibility.compatible) {
|
|
306
|
+
// `compatible` is only reported alongside `compatible: true`; an
|
|
307
|
+
// incompatible result that still carried it would be a contract break, so
|
|
308
|
+
// it degrades to the shape-level rejection rather than resuming.
|
|
309
|
+
const reason = compatibility.code === "compatible" ? "invalid-checkpoint" : compatibility.code;
|
|
310
|
+
return { reason, detail: compatibility.reason };
|
|
311
|
+
}
|
|
312
|
+
return { checkpoint };
|
|
313
|
+
}
|
|
314
|
+
function projectRecord(checkpoint, scopeKey, now) {
|
|
315
|
+
const context = checkpoint.context;
|
|
316
|
+
const resume = {
|
|
317
|
+
sequence: checkpoint.resume.sequence,
|
|
318
|
+
...optionalText("cursor", checkpoint.resume),
|
|
319
|
+
...optionalText("watermark", checkpoint.resume),
|
|
320
|
+
...optionalText("timestamp", checkpoint.resume),
|
|
321
|
+
...optionalText("deltaToken", checkpoint.resume),
|
|
322
|
+
};
|
|
323
|
+
const screened = [
|
|
324
|
+
["sourceId", String(context.sourceId)],
|
|
325
|
+
["queryFingerprint", context.queryFingerprint],
|
|
326
|
+
["sourceVersion", context.sourceVersion],
|
|
327
|
+
["schemaVersion", context.schemaVersion],
|
|
328
|
+
...(resume.cursor === undefined ? [] : [["resume.cursor", resume.cursor]]),
|
|
329
|
+
...(resume.watermark === undefined ? [] : [["resume.watermark", resume.watermark]]),
|
|
330
|
+
...(resume.timestamp === undefined ? [] : [["resume.timestamp", resume.timestamp]]),
|
|
331
|
+
...(resume.deltaToken === undefined ? [] : [["resume.deltaToken", resume.deltaToken]]),
|
|
332
|
+
];
|
|
333
|
+
for (const [path, value] of screened) {
|
|
334
|
+
const reason = screenPersistedString(value, "identity");
|
|
335
|
+
if (reason)
|
|
336
|
+
return { path, reason };
|
|
337
|
+
}
|
|
338
|
+
const observedAt = new Date(Number.isFinite(now) ? now : Date.now()).toISOString();
|
|
339
|
+
return {
|
|
340
|
+
record: {
|
|
341
|
+
key: scopeKey,
|
|
342
|
+
format: HONUA_REALTIME_CHECKPOINT_STORE_FORMAT,
|
|
343
|
+
sourceId: String(context.sourceId),
|
|
344
|
+
queryFingerprint: context.queryFingerprint,
|
|
345
|
+
sourceVersion: context.sourceVersion,
|
|
346
|
+
schemaVersion: context.schemaVersion,
|
|
347
|
+
authorizationScopeDigest: realtimeAuthorizationScopeDigest(context.authorizationScopeFingerprint),
|
|
348
|
+
resume,
|
|
349
|
+
savedAt: checkpoint.savedAt,
|
|
350
|
+
observedAt,
|
|
351
|
+
},
|
|
352
|
+
};
|
|
353
|
+
}
|
|
354
|
+
function optionalText(name, resume) {
|
|
355
|
+
const value = resume[name];
|
|
356
|
+
return typeof value === "string" ? { [name]: value } : {};
|
|
357
|
+
}
|
|
358
|
+
function report(options, operation, reason, scopeKey, detail, cause) {
|
|
359
|
+
if (!options.onDiagnostic)
|
|
360
|
+
return;
|
|
361
|
+
const error = new HonuaRealtimeResumeError(DIAGNOSTIC_ERROR_CODES[reason], detail, cause === undefined ? undefined : { cause });
|
|
362
|
+
options.onDiagnostic(Object.freeze({
|
|
363
|
+
kind: "honua.realtime-checkpoint-store-diagnostic",
|
|
364
|
+
version: 1,
|
|
365
|
+
operation,
|
|
366
|
+
reason,
|
|
367
|
+
scopeKey,
|
|
368
|
+
error,
|
|
369
|
+
}));
|
|
370
|
+
}
|
|
371
|
+
function normalizeOptions(options) {
|
|
372
|
+
return {
|
|
373
|
+
maxAgeMs: tightenedPositive(options.maxAgeMs, DEFAULT_REALTIME_CHECKPOINT_MAX_AGE_MS, MAX_REALTIME_CHECKPOINT_MAX_AGE_MS, "maxAgeMs"),
|
|
374
|
+
maxRecords: tightenedPositive(options.maxRecords, DEFAULT_REALTIME_CHECKPOINT_MAX_RECORDS, MAX_REALTIME_CHECKPOINT_RECORDS, "maxRecords"),
|
|
375
|
+
now: options.now ?? Date.now,
|
|
376
|
+
...(options.onDiagnostic ? { onDiagnostic: options.onDiagnostic } : {}),
|
|
377
|
+
};
|
|
378
|
+
}
|
|
379
|
+
function tightenedPositive(value, fallback, ceiling, name) {
|
|
380
|
+
if (value === undefined)
|
|
381
|
+
return fallback;
|
|
382
|
+
if (!Number.isSafeInteger(value) || value < 1) {
|
|
383
|
+
throw new HonuaRealtimeResumeError("invalid-checkpoint", `${name} must be a safe integer greater than zero.`);
|
|
384
|
+
}
|
|
385
|
+
if (value > ceiling) {
|
|
386
|
+
throw new HonuaRealtimeResumeError("invalid-checkpoint", `${name} cannot exceed the ${ceiling} safety ceiling.`);
|
|
387
|
+
}
|
|
388
|
+
return value;
|
|
389
|
+
}
|
|
390
|
+
const INVALID_SCOPE_KEY = `sha256:${"0".repeat(64)}`;
|
|
391
|
+
function isStoreFormat(value) {
|
|
392
|
+
return value === HONUA_REALTIME_CHECKPOINT_STORE_FORMAT;
|
|
393
|
+
}
|
|
394
|
+
function isCheckpointRecord(value) {
|
|
395
|
+
if (typeof value !== "object" || value === null)
|
|
396
|
+
return false;
|
|
397
|
+
const record = value;
|
|
398
|
+
if (!isStoreFormat(record.format))
|
|
399
|
+
return false;
|
|
400
|
+
for (const name of ["key", "sourceId", "queryFingerprint", "sourceVersion", "schemaVersion"]) {
|
|
401
|
+
if (typeof record[name] !== "string" || record[name].length === 0)
|
|
402
|
+
return false;
|
|
403
|
+
}
|
|
404
|
+
if (typeof record.authorizationScopeDigest !== "string" || !record.authorizationScopeDigest.startsWith("sha256:")) {
|
|
405
|
+
return false;
|
|
406
|
+
}
|
|
407
|
+
if (typeof record.savedAt !== "string" || !Number.isFinite(Date.parse(record.savedAt)))
|
|
408
|
+
return false;
|
|
409
|
+
if (typeof record.observedAt !== "string" || !Number.isFinite(Date.parse(record.observedAt)))
|
|
410
|
+
return false;
|
|
411
|
+
if (typeof record.resume !== "object" || record.resume === null)
|
|
412
|
+
return false;
|
|
413
|
+
const resume = record.resume;
|
|
414
|
+
if (!Number.isSafeInteger(resume.sequence) || resume.sequence < 0)
|
|
415
|
+
return false;
|
|
416
|
+
for (const name of ["cursor", "watermark", "timestamp", "deltaToken"]) {
|
|
417
|
+
if (resume[name] !== undefined && typeof resume[name] !== "string")
|
|
418
|
+
return false;
|
|
419
|
+
}
|
|
420
|
+
return true;
|
|
421
|
+
}
|
|
422
|
+
function openDatabase(factory, name) {
|
|
423
|
+
return new Promise((resolve, reject) => {
|
|
424
|
+
const open = factory.open(name, DATABASE_VERSION);
|
|
425
|
+
open.onupgradeneeded = () => {
|
|
426
|
+
const database = open.result;
|
|
427
|
+
const store = database.objectStoreNames.contains(CHECKPOINT_STORE)
|
|
428
|
+
? open.transaction?.objectStore(CHECKPOINT_STORE)
|
|
429
|
+
: database.createObjectStore(CHECKPOINT_STORE, { keyPath: "key" });
|
|
430
|
+
if (store && !store.indexNames.contains(OBSERVED_AT_INDEX))
|
|
431
|
+
store.createIndex(OBSERVED_AT_INDEX, "observedAt");
|
|
432
|
+
};
|
|
433
|
+
open.onsuccess = () => resolve(open.result);
|
|
434
|
+
open.onerror = () => reject(open.error ?? new Error("Failed to open the realtime checkpoint database."));
|
|
435
|
+
open.onblocked = () => reject(new Error("Realtime checkpoint database upgrade is blocked by another connection."));
|
|
436
|
+
});
|
|
437
|
+
}
|
|
438
|
+
function runTransaction(database, mode, body) {
|
|
439
|
+
return new Promise((resolve, reject) => {
|
|
440
|
+
const transaction = database.transaction([CHECKPOINT_STORE], mode);
|
|
441
|
+
let result;
|
|
442
|
+
let settled = false;
|
|
443
|
+
void body(transaction).then((value) => {
|
|
444
|
+
result = value;
|
|
445
|
+
}, (error) => {
|
|
446
|
+
settled = true;
|
|
447
|
+
try {
|
|
448
|
+
transaction.abort();
|
|
449
|
+
}
|
|
450
|
+
catch {
|
|
451
|
+
// The transaction may already have completed or aborted.
|
|
452
|
+
}
|
|
453
|
+
reject(error);
|
|
454
|
+
});
|
|
455
|
+
transaction.oncomplete = () => {
|
|
456
|
+
if (settled)
|
|
457
|
+
return;
|
|
458
|
+
settled = true;
|
|
459
|
+
resolve(result);
|
|
460
|
+
};
|
|
461
|
+
transaction.onerror = () => {
|
|
462
|
+
if (settled)
|
|
463
|
+
return;
|
|
464
|
+
settled = true;
|
|
465
|
+
reject(transaction.error ?? new Error("Realtime checkpoint transaction failed."));
|
|
466
|
+
};
|
|
467
|
+
transaction.onabort = () => {
|
|
468
|
+
if (settled)
|
|
469
|
+
return;
|
|
470
|
+
settled = true;
|
|
471
|
+
reject(transaction.error ?? new Error("Realtime checkpoint transaction aborted."));
|
|
472
|
+
};
|
|
473
|
+
});
|
|
474
|
+
}
|
|
475
|
+
function request(source) {
|
|
476
|
+
return new Promise((resolve, reject) => {
|
|
477
|
+
source.onsuccess = () => resolve(source.result);
|
|
478
|
+
source.onerror = () => reject(source.error ?? new Error("Realtime checkpoint request failed."));
|
|
479
|
+
});
|
|
480
|
+
}
|
|
481
|
+
/** Delete `count` least recently written records inside the caller's transaction. */
|
|
482
|
+
function evictOldest(store, count, protectedKey) {
|
|
483
|
+
return new Promise((resolve, reject) => {
|
|
484
|
+
let remaining = count;
|
|
485
|
+
if (remaining <= 0) {
|
|
486
|
+
resolve();
|
|
487
|
+
return;
|
|
488
|
+
}
|
|
489
|
+
const cursorRequest = store.index(OBSERVED_AT_INDEX).openCursor();
|
|
490
|
+
cursorRequest.onerror = () => reject(cursorRequest.error ?? new Error("Failed to enumerate realtime checkpoint records."));
|
|
491
|
+
cursorRequest.onsuccess = () => {
|
|
492
|
+
const cursor = cursorRequest.result;
|
|
493
|
+
if (!cursor || remaining <= 0) {
|
|
494
|
+
resolve();
|
|
495
|
+
return;
|
|
496
|
+
}
|
|
497
|
+
if (cursor.primaryKey !== protectedKey) {
|
|
498
|
+
cursor.delete();
|
|
499
|
+
remaining -= 1;
|
|
500
|
+
}
|
|
501
|
+
cursor.continue();
|
|
502
|
+
};
|
|
503
|
+
});
|
|
504
|
+
}
|
|
@@ -64,6 +64,7 @@ export interface HonuaServerFeatureChange<TFeature = unknown> {
|
|
|
64
64
|
*/
|
|
65
65
|
export interface HonuaServerFeatureChangeEnvelope<TFeature = unknown> {
|
|
66
66
|
readonly serviceId?: string;
|
|
67
|
+
readonly sourceId?: string;
|
|
67
68
|
readonly layerId?: string | number;
|
|
68
69
|
readonly eventId?: string;
|
|
69
70
|
readonly sequence?: number;
|
|
@@ -77,7 +78,14 @@ export interface HonuaServerFeatureChangeEnvelope<TFeature = unknown> {
|
|
|
77
78
|
/** Single-change envelopes inline the change instead of a `changes` array. */
|
|
78
79
|
readonly op?: HonuaServerFeatureChange<TFeature>["op"];
|
|
79
80
|
readonly featureId?: FeatureId;
|
|
81
|
+
readonly objectId?: FeatureId;
|
|
80
82
|
readonly feature?: TFeature;
|
|
83
|
+
/** Current honua-server wire field; normalized to {@link HonuaServerFeatureChange.op}. */
|
|
84
|
+
readonly operation?: Exclude<HonuaServerFeatureChange<TFeature>["op"], "snapshot">;
|
|
85
|
+
/** Current honua-server GeoJSON geometry field. */
|
|
86
|
+
readonly geometry?: unknown;
|
|
87
|
+
/** Current honua-server full attribute snapshot for insert/update events. */
|
|
88
|
+
readonly attributes?: Readonly<Record<string, unknown>>;
|
|
81
89
|
readonly version?: number;
|
|
82
90
|
readonly updatedAt?: string;
|
|
83
91
|
/** Pass-through status / heartbeat / error envelopes. */
|
|
@@ -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;AAItD,OAAO,KAAK,EAAE,wCAAwC,EAAE,MAAM,UAAU,CAAC;AACzE,OAAO,KAAK,EACV,oBAAoB,EAEpB,wBAAwB,EACxB,2BAA2B,EAC5B,MAAM,YAAY,CAAC;AAEpB,8DAA8D;AAC9D,eAAO,MAAM,oCAAoC,+BAA+B,CAAC;AAEjF;;;;;GAKG;AACH,wBAAgB,gCAAgC,CAAC,GAAG,EAAE,GAAG,EAAE,OAAO,EAAE,2BAA2B,GAAG,GAAG,CAyBpG;AAED;;;;GAIG;AACH,MAAM,WAAW,wBAAwB,CAAC,QAAQ,GAAG,OAAO;IAC1D,QAAQ,CAAC,EAAE,EAAE,QAAQ,GAAG,QAAQ,GAAG,QAAQ,GAAG,UAAU,CAAC;IACzD,QAAQ,CAAC,SAAS,CAAC,EAAE,SAAS,CAAC;IAC/B,QAAQ,CAAC,OAAO,CAAC,EAAE,QAAQ,CAAC;IAC5B,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;CAC7B;AAED;;;;GAIG;AACH,MAAM,WAAW,gCAAgC,CAAC,QAAQ,GAAG,OAAO;IAClE,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IACnC,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;IAC7B,mEAAmE;IACnE,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,OAAO,CAAC,EAAE,aAAa,CAAC,wBAAwB,CAAC,QAAQ,CAAC,CAAC,CAAC;IACrE,8EAA8E;IAC9E,QAAQ,CAAC,EAAE,CAAC,EAAE,wBAAwB,CAAC,QAAQ,CAAC,CAAC,IAAI,CAAC,CAAC;IACvD,QAAQ,CAAC,SAAS,CAAC,EAAE,SAAS,CAAC;IAC/B,QAAQ,CAAC,OAAO,CAAC,EAAE,QAAQ,CAAC;IAC5B,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAC5B,yDAAyD;IACzD,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;CACxB;AAED;;;;;GAKG;AACH,wBAAgB,8BAA8B,CAAC,QAAQ,GAAG,OAAO,EAAE,OAAO,EAAE,OAAO,GAAG,oBAAoB,CAAC,QAAQ,CAAC,
|
|
1
|
+
{"version":3,"file":"honua-server.d.ts","sourceRoot":"","sources":["../../../src/realtime/honua-server.ts"],"names":[],"mappings":"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"}
|