@honua/app-platform 0.0.20-alpha.0 → 0.1.1-beta.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (381) hide show
  1. package/contract/discovery.d.ts +231 -0
  2. package/contract/discovery.d.ts.map +1 -0
  3. package/contract/discovery.js +554 -0
  4. package/contract/edit-sketch.d.ts +8 -0
  5. package/contract/edit-sketch.d.ts.map +1 -1
  6. package/contract/edit-sketch.js +15 -0
  7. package/contract/edit-snapping.d.ts +160 -0
  8. package/contract/edit-snapping.d.ts.map +1 -0
  9. package/contract/edit-snapping.js +675 -0
  10. package/contract/generated/projjson-v0.7-crs-validator.d.ts +35 -0
  11. package/contract/generated/projjson-v0.7-crs-validator.d.ts.map +1 -0
  12. package/contract/generated/projjson-v0.7-crs-validator.js +3941 -0
  13. package/contract/index.d.ts +11 -2
  14. package/contract/index.d.ts.map +1 -1
  15. package/contract/index.js +4 -1
  16. package/contract/pmtiles.d.ts +31 -0
  17. package/contract/pmtiles.d.ts.map +1 -1
  18. package/contract/pmtiles.js +60 -20
  19. package/contract/protocol-module.d.ts +70 -0
  20. package/contract/protocol-module.d.ts.map +1 -0
  21. package/contract/protocol-module.js +1 -0
  22. package/contract/schema-envelope.d.ts +26 -0
  23. package/contract/schema-envelope.d.ts.map +1 -0
  24. package/contract/schema-envelope.js +25 -0
  25. package/contract/schema.d.ts +582 -0
  26. package/contract/schema.d.ts.map +1 -0
  27. package/contract/schema.js +2483 -0
  28. package/contract/source-capability-support.d.ts +6 -0
  29. package/contract/source-capability-support.d.ts.map +1 -0
  30. package/contract/source-capability-support.js +154 -0
  31. package/contract/source.d.ts +35 -17
  32. package/contract/source.d.ts.map +1 -1
  33. package/contract/source.js +267 -71
  34. package/contract/types.d.ts +140 -4
  35. package/contract/types.d.ts.map +1 -1
  36. package/contract/types.js +6 -14
  37. package/core/bounded-json.d.ts +4 -0
  38. package/core/bounded-json.d.ts.map +1 -0
  39. package/core/bounded-json.js +38 -0
  40. package/core/cache-state.d.ts +9 -0
  41. package/core/cache-state.d.ts.map +1 -1
  42. package/core/cache-state.js +10 -0
  43. package/core/capabilities-xml.d.ts +33 -0
  44. package/core/capabilities-xml.d.ts.map +1 -0
  45. package/core/capabilities-xml.js +331 -0
  46. package/core/client.d.ts +4 -1
  47. package/core/client.d.ts.map +1 -1
  48. package/core/client.js +115 -29
  49. package/core/error-classifications.d.ts +153 -0
  50. package/core/error-classifications.d.ts.map +1 -0
  51. package/core/error-classifications.js +140 -0
  52. package/core/error-code-registry.d.ts +158 -0
  53. package/core/error-code-registry.d.ts.map +1 -0
  54. package/core/error-code-registry.js +143 -0
  55. package/core/error-envelope.d.ts +68 -0
  56. package/core/error-envelope.d.ts.map +1 -0
  57. package/core/error-envelope.js +357 -0
  58. package/core/errors.d.ts +45 -29
  59. package/core/errors.d.ts.map +1 -1
  60. package/core/errors.js +97 -50
  61. package/core/geoparquet-sql.d.ts +30 -2
  62. package/core/geoparquet-sql.d.ts.map +1 -1
  63. package/core/geoparquet-sql.js +38 -3
  64. package/core/odata-key-path.d.ts +12 -0
  65. package/core/odata-key-path.d.ts.map +1 -0
  66. package/core/odata-key-path.js +28 -0
  67. package/core/odata-write-codec.d.ts +66 -0
  68. package/core/odata-write-codec.d.ts.map +1 -0
  69. package/core/odata-write-codec.js +1083 -0
  70. package/core/odata.d.ts +59 -7
  71. package/core/odata.d.ts.map +1 -1
  72. package/core/odata.js +431 -99
  73. package/core/ogc-features.d.ts.map +1 -1
  74. package/core/ogc-features.js +3 -0
  75. package/core/ogc-maps.d.ts +7 -0
  76. package/core/ogc-maps.d.ts.map +1 -1
  77. package/core/ogc-maps.js +46 -7
  78. package/core/ogc-processes.d.ts +2 -1
  79. package/core/ogc-processes.d.ts.map +1 -1
  80. package/core/ogc-processes.js +33 -5
  81. package/core/ogc-records.d.ts +9 -0
  82. package/core/ogc-records.d.ts.map +1 -1
  83. package/core/ogc-records.js +78 -29
  84. package/core/ogc-tiles.d.ts +10 -1
  85. package/core/ogc-tiles.d.ts.map +1 -1
  86. package/core/ogc-tiles.js +56 -17
  87. package/core/path-utils.d.ts +9 -0
  88. package/core/path-utils.d.ts.map +1 -1
  89. package/core/path-utils.js +19 -0
  90. package/core/protocol-transport.d.ts +1 -0
  91. package/core/protocol-transport.d.ts.map +1 -1
  92. package/core/request-pipeline.d.ts.map +1 -1
  93. package/core/request-pipeline.js +1 -1
  94. package/core/spatial-filter.d.ts +8 -4
  95. package/core/spatial-filter.d.ts.map +1 -1
  96. package/core/spatial-filter.js +206 -16
  97. package/core/stac-static.d.ts +6 -2
  98. package/core/stac-static.d.ts.map +1 -1
  99. package/core/stac-static.js +260 -69
  100. package/core/surfaces.d.ts.map +1 -1
  101. package/core/surfaces.js +69 -5
  102. package/core/types.d.ts +125 -7
  103. package/core/types.d.ts.map +1 -1
  104. package/core/wfs-capabilities.d.ts +2 -0
  105. package/core/wfs-capabilities.d.ts.map +1 -1
  106. package/core/wfs-capabilities.js +8 -1
  107. package/core/wfs.d.ts.map +1 -1
  108. package/core/wfs.js +10 -0
  109. package/core/wms-capabilities.d.ts +56 -84
  110. package/core/wms-capabilities.d.ts.map +1 -1
  111. package/core/wms-capabilities.js +212 -509
  112. package/core/wmts-capabilities.d.ts +62 -62
  113. package/core/wmts-capabilities.d.ts.map +1 -1
  114. package/core/wmts-capabilities.js +224 -371
  115. package/esri-compat/editor.d.ts +8 -0
  116. package/esri-compat/editor.d.ts.map +1 -1
  117. package/esri-compat/editor.js +13 -0
  118. package/esri-compat/feature-layer.d.ts +2 -0
  119. package/esri-compat/feature-layer.d.ts.map +1 -1
  120. package/esri-compat/feature-layer.js +1 -0
  121. package/esri-compat/layer-list.d.ts +11 -0
  122. package/esri-compat/layer-list.d.ts.map +1 -1
  123. package/esri-compat/layer-list.js +49 -0
  124. package/esri-compat/legend.d.ts +10 -0
  125. package/esri-compat/legend.d.ts.map +1 -1
  126. package/esri-compat/legend.js +27 -0
  127. package/esri-compat/portal.d.ts +197 -0
  128. package/esri-compat/portal.d.ts.map +1 -0
  129. package/esri-compat/portal.js +251 -0
  130. package/esri-compat/renderer-objects.d.ts +40 -0
  131. package/esri-compat/renderer-objects.d.ts.map +1 -0
  132. package/esri-compat/renderer-objects.js +62 -0
  133. package/esri-compat/sketch.d.ts +43 -0
  134. package/esri-compat/sketch.d.ts.map +1 -1
  135. package/esri-compat/sketch.js +46 -0
  136. package/esri-compat/snapping.d.ts +43 -0
  137. package/esri-compat/snapping.d.ts.map +1 -0
  138. package/esri-compat/snapping.js +45 -0
  139. package/esri-compat/url.d.ts.map +1 -1
  140. package/esri-compat/url.js +1 -1
  141. package/esri-compat/widget-host.d.ts +84 -0
  142. package/esri-compat/widget-host.d.ts.map +1 -0
  143. package/esri-compat/widget-host.js +159 -0
  144. package/generated-app/errors.d.ts +18 -2
  145. package/generated-app/errors.d.ts.map +1 -1
  146. package/generated-app/errors.js +44 -11
  147. package/geocoding/index.d.ts +7 -0
  148. package/geocoding/index.d.ts.map +1 -1
  149. package/geocoding/index.js +5 -0
  150. package/geocoding/provider-http.d.ts +37 -0
  151. package/geocoding/provider-http.d.ts.map +1 -0
  152. package/geocoding/provider-http.js +91 -0
  153. package/geocoding/provider.d.ts +145 -0
  154. package/geocoding/provider.d.ts.map +1 -0
  155. package/geocoding/provider.js +41 -0
  156. package/geocoding/providers/honua.d.ts +28 -0
  157. package/geocoding/providers/honua.d.ts.map +1 -0
  158. package/geocoding/providers/honua.js +68 -0
  159. package/geocoding/providers/nominatim.d.ts +47 -0
  160. package/geocoding/providers/nominatim.d.ts.map +1 -0
  161. package/geocoding/providers/nominatim.js +105 -0
  162. package/geocoding/providers/pelias.d.ts +40 -0
  163. package/geocoding/providers/pelias.d.ts.map +1 -0
  164. package/geocoding/providers/pelias.js +115 -0
  165. package/geocoding/providers/photon.d.ts +39 -0
  166. package/geocoding/providers/photon.d.ts.map +1 -0
  167. package/geocoding/providers/photon.js +107 -0
  168. package/geometry/convert.d.ts +13 -0
  169. package/geometry/convert.d.ts.map +1 -0
  170. package/geometry/convert.js +12 -0
  171. package/geometry/index.d.ts +17 -0
  172. package/geometry/index.d.ts.map +1 -0
  173. package/geometry/index.js +15 -0
  174. package/geometry/internal.d.ts +25 -0
  175. package/geometry/internal.d.ts.map +1 -0
  176. package/geometry/internal.js +40 -0
  177. package/geometry/ops.d.ts +71 -0
  178. package/geometry/ops.d.ts.map +1 -0
  179. package/geometry/ops.js +147 -0
  180. package/geometry/project.d.ts +40 -0
  181. package/geometry/project.d.ts.map +1 -0
  182. package/geometry/project.js +88 -0
  183. package/geometry/types.d.ts +36 -0
  184. package/geometry/types.d.ts.map +1 -0
  185. package/geometry/types.js +12 -0
  186. package/map/automatic-mount-integration.d.ts +134 -0
  187. package/map/automatic-mount-integration.d.ts.map +1 -0
  188. package/map/automatic-mount-integration.js +305 -0
  189. package/map/automatic-source-strategy.d.ts +125 -0
  190. package/map/automatic-source-strategy.d.ts.map +1 -0
  191. package/map/automatic-source-strategy.js +494 -0
  192. package/map/data-to-map-bridge.d.ts +293 -0
  193. package/map/data-to-map-bridge.d.ts.map +1 -0
  194. package/map/data-to-map-bridge.js +817 -0
  195. package/map/geojson-projection.d.ts +33 -0
  196. package/map/geojson-projection.d.ts.map +1 -0
  197. package/map/geojson-projection.js +118 -0
  198. package/map/index.d.ts +14 -0
  199. package/map/index.d.ts.map +1 -1
  200. package/map/index.js +8 -0
  201. package/map/raster-source-spec.d.ts +30 -0
  202. package/map/raster-source-spec.d.ts.map +1 -0
  203. package/map/raster-source-spec.js +190 -0
  204. package/map/raster-source-strategy.d.ts +65 -0
  205. package/map/raster-source-strategy.d.ts.map +1 -0
  206. package/map/raster-source-strategy.js +232 -0
  207. package/map/source-to-maplibre.d.ts +91 -0
  208. package/map/source-to-maplibre.d.ts.map +1 -0
  209. package/map/source-to-maplibre.js +454 -0
  210. package/map/temporal-playback.d.ts +139 -0
  211. package/map/temporal-playback.d.ts.map +1 -0
  212. package/map/temporal-playback.js +247 -0
  213. package/package.json +33 -2
  214. package/query-planner/canonical.d.ts +7 -0
  215. package/query-planner/canonical.d.ts.map +1 -0
  216. package/query-planner/canonical.js +145 -0
  217. package/query-planner/cql2-json.d.ts +17 -0
  218. package/query-planner/cql2-json.d.ts.map +1 -0
  219. package/query-planner/cql2-json.js +646 -0
  220. package/query-planner/diagnostics.d.ts +35 -0
  221. package/query-planner/diagnostics.d.ts.map +1 -0
  222. package/query-planner/diagnostics.js +373 -0
  223. package/query-planner/duckdb.d.ts +79 -0
  224. package/query-planner/duckdb.d.ts.map +1 -0
  225. package/query-planner/duckdb.js +758 -0
  226. package/query-planner/execution-context.d.ts +5 -0
  227. package/query-planner/execution-context.d.ts.map +1 -0
  228. package/query-planner/execution-context.js +50 -0
  229. package/query-planner/executor.d.ts +5 -0
  230. package/query-planner/executor.d.ts.map +1 -0
  231. package/query-planner/executor.js +223 -0
  232. package/query-planner/geoservices.d.ts +91 -0
  233. package/query-planner/geoservices.d.ts.map +1 -0
  234. package/query-planner/geoservices.js +667 -0
  235. package/query-planner/grpc.d.ts +100 -0
  236. package/query-planner/grpc.d.ts.map +1 -0
  237. package/query-planner/grpc.js +736 -0
  238. package/query-planner/index.d.ts +44 -0
  239. package/query-planner/index.d.ts.map +1 -0
  240. package/query-planner/index.js +30 -0
  241. package/query-planner/ir.d.ts +20 -0
  242. package/query-planner/ir.d.ts.map +1 -0
  243. package/query-planner/ir.js +417 -0
  244. package/query-planner/local-aggregate.d.ts +5 -0
  245. package/query-planner/local-aggregate.d.ts.map +1 -0
  246. package/query-planner/local-aggregate.js +126 -0
  247. package/query-planner/odata.d.ts +48 -0
  248. package/query-planner/odata.d.ts.map +1 -0
  249. package/query-planner/odata.js +662 -0
  250. package/query-planner/ogc-compiler.d.ts +55 -0
  251. package/query-planner/ogc-compiler.d.ts.map +1 -0
  252. package/query-planner/ogc-compiler.js +480 -0
  253. package/query-planner/ogc-features.d.ts +53 -0
  254. package/query-planner/ogc-features.d.ts.map +1 -0
  255. package/query-planner/ogc-features.js +617 -0
  256. package/query-planner/planner.d.ts +17 -0
  257. package/query-planner/planner.d.ts.map +1 -0
  258. package/query-planner/planner.js +818 -0
  259. package/query-planner/resource.d.ts +94 -0
  260. package/query-planner/resource.d.ts.map +1 -0
  261. package/query-planner/resource.js +526 -0
  262. package/query-planner/semantic-canonical.d.ts +15 -0
  263. package/query-planner/semantic-canonical.d.ts.map +1 -0
  264. package/query-planner/semantic-canonical.js +62 -0
  265. package/query-planner/semantic-compat.d.ts +10 -0
  266. package/query-planner/semantic-compat.d.ts.map +1 -0
  267. package/query-planner/semantic-compat.js +35 -0
  268. package/query-planner/semantic-compiler.d.ts +64 -0
  269. package/query-planner/semantic-compiler.d.ts.map +1 -0
  270. package/query-planner/semantic-compiler.js +81 -0
  271. package/query-planner/semantic-literals.d.ts +30 -0
  272. package/query-planner/semantic-literals.d.ts.map +1 -0
  273. package/query-planner/semantic-literals.js +133 -0
  274. package/query-planner/semantic-types.d.ts +265 -0
  275. package/query-planner/semantic-types.d.ts.map +1 -0
  276. package/query-planner/semantic-types.js +1 -0
  277. package/query-planner/semantic.d.ts +54 -0
  278. package/query-planner/semantic.d.ts.map +1 -0
  279. package/query-planner/semantic.js +1421 -0
  280. package/query-planner/types.d.ts +560 -0
  281. package/query-planner/types.d.ts.map +1 -0
  282. package/query-planner/types.js +49 -0
  283. package/query-planner/wfs-semantic.d.ts +58 -0
  284. package/query-planner/wfs-semantic.d.ts.map +1 -0
  285. package/query-planner/wfs-semantic.js +547 -0
  286. package/query-planner/wfs.d.ts +6 -0
  287. package/query-planner/wfs.d.ts.map +1 -0
  288. package/query-planner/wfs.js +71 -0
  289. package/realtime/contract.d.ts +142 -0
  290. package/realtime/contract.d.ts.map +1 -0
  291. package/realtime/contract.js +154 -0
  292. package/realtime/honua-server.d.ts.map +1 -1
  293. package/realtime/honua-server.js +13 -8
  294. package/realtime/index.d.ts +19 -4
  295. package/realtime/index.d.ts.map +1 -1
  296. package/realtime/index.js +14 -4
  297. package/realtime/odata-delta.d.ts +114 -0
  298. package/realtime/odata-delta.d.ts.map +1 -0
  299. package/realtime/odata-delta.js +483 -0
  300. package/realtime/resumable-transport.d.ts +111 -0
  301. package/realtime/resumable-transport.d.ts.map +1 -0
  302. package/realtime/resumable-transport.js +406 -0
  303. package/realtime/resumable.d.ts +104 -0
  304. package/realtime/resumable.d.ts.map +1 -0
  305. package/realtime/resumable.js +632 -0
  306. package/realtime/sse.d.ts.map +1 -1
  307. package/realtime/sse.js +27 -23
  308. package/realtime/websocket.d.ts +60 -0
  309. package/realtime/websocket.d.ts.map +1 -0
  310. package/realtime/websocket.js +157 -0
  311. package/realtime/wire.d.ts +27 -0
  312. package/realtime/wire.d.ts.map +1 -0
  313. package/realtime/wire.js +57 -0
  314. package/replica-sync/errors.d.ts +2 -1
  315. package/replica-sync/errors.d.ts.map +1 -1
  316. package/replica-sync/errors.js +33 -2
  317. package/runtime/edit-snapping.d.ts +107 -0
  318. package/runtime/edit-snapping.d.ts.map +1 -0
  319. package/runtime/edit-snapping.js +190 -0
  320. package/runtime/errors.d.ts +3 -2
  321. package/runtime/errors.d.ts.map +1 -1
  322. package/runtime/errors.js +19 -2
  323. package/runtime/index.d.ts +6 -0
  324. package/runtime/index.d.ts.map +1 -1
  325. package/runtime/index.js +3 -0
  326. package/runtime/maplibre-renderer.d.ts +41 -0
  327. package/runtime/maplibre-renderer.d.ts.map +1 -0
  328. package/runtime/maplibre-renderer.js +322 -0
  329. package/runtime/query-tiles.d.ts +2 -1
  330. package/runtime/query-tiles.d.ts.map +1 -1
  331. package/runtime/query-tiles.js +6 -2
  332. package/runtime/source-bridge.d.ts +4 -4
  333. package/runtime/source-bridge.d.ts.map +1 -1
  334. package/runtime/source-bridge.js +9 -56
  335. package/runtime/style-interactions.d.ts +3 -2
  336. package/runtime/style-interactions.d.ts.map +1 -1
  337. package/runtime/style-interactions.js +11 -3
  338. package/runtime/terra-draw-sketch.d.ts +275 -0
  339. package/runtime/terra-draw-sketch.d.ts.map +1 -0
  340. package/runtime/terra-draw-sketch.js +404 -0
  341. package/scene-workspace/index.d.ts +4 -0
  342. package/scene-workspace/index.d.ts.map +1 -1
  343. package/scene-workspace/index.js +2 -0
  344. package/scene-workspace/source-to-cesium.d.ts +116 -0
  345. package/scene-workspace/source-to-cesium.d.ts.map +1 -0
  346. package/scene-workspace/source-to-cesium.js +791 -0
  347. package/scene-workspace/state-sync.d.ts +147 -0
  348. package/scene-workspace/state-sync.d.ts.map +1 -0
  349. package/scene-workspace/state-sync.js +1123 -0
  350. package/source-capability-registry.d.ts +1 -0
  351. package/source-capability-registry.js +1 -0
  352. package/source-capability-types.d.ts +186 -0
  353. package/source-capability-types.js +7 -0
  354. package/style/index.d.ts +2 -0
  355. package/style/index.d.ts.map +1 -1
  356. package/style/index.js +2 -0
  357. package/style/renderers.d.ts +326 -0
  358. package/style/renderers.d.ts.map +1 -0
  359. package/style/renderers.js +517 -0
  360. package/web-components/controller.d.ts +2 -0
  361. package/web-components/controller.d.ts.map +1 -1
  362. package/web-components/controller.js +49 -8
  363. package/web-components/elements.d.ts +95 -1
  364. package/web-components/elements.d.ts.map +1 -1
  365. package/web-components/elements.js +458 -19
  366. package/web-components/index.d.ts +8 -1
  367. package/web-components/index.d.ts.map +1 -1
  368. package/web-components/index.js +7 -0
  369. package/web-components/maplibre-renderer.d.ts.map +1 -1
  370. package/web-components/maplibre-renderer.js +87 -0
  371. package/web-components/measurement.d.ts +69 -0
  372. package/web-components/measurement.d.ts.map +1 -0
  373. package/web-components/measurement.js +523 -0
  374. package/web-components/types.d.ts +109 -0
  375. package/web-components/types.d.ts.map +1 -1
  376. package/webmap/convert-renderer.d.ts +24 -3
  377. package/webmap/convert-renderer.d.ts.map +1 -1
  378. package/webmap/convert-renderer.js +93 -112
  379. package/webmap/index.d.ts +1 -1
  380. package/webmap/index.d.ts.map +1 -1
  381. package/webmap/index.js +1 -1
@@ -0,0 +1,71 @@
1
+ import { UNSUPPORTED_FES, compileSpatialFilter, compileWhere, serializeFes } from "../core/wfs-filter.js";
2
+ import { HonuaQueryPlanningError } from "./types.js";
3
+ export { compileSemanticWfsQuery } from "./wfs-semantic.js";
4
+ /** Compile canonical query IR to a deterministic WFS 2.0 GetFeature request. */
5
+ export function compileWfsQuery(source, query) {
6
+ if (source.protocol !== "wfs") {
7
+ throw new HonuaQueryPlanningError("unsupported-compiler", `wfs-2.0-get-feature-v1 does not compile protocol "${source.protocol}"`);
8
+ }
9
+ if (!source.typeName) {
10
+ throw new HonuaQueryPlanningError("invalid-query", `Source "${source.id}" requires locator.typeName for WFS planning`);
11
+ }
12
+ if (query.aggregation) {
13
+ throw new HonuaQueryPlanningError("unsupported-query", "WFS 2.0 has no portable remote aggregation; use degraded policy with an explicit bounded-local fallback");
14
+ }
15
+ if (query.spatialFilter && query.outSr !== undefined) {
16
+ throw new HonuaQueryPlanningError("unsupported-query", "WFS planning cannot use outSr to label input filter coordinates; stamp the input geometry through the typed WFS escape hatch");
17
+ }
18
+ // Keep this aligned with the current WFS adapter's reviewed default. A
19
+ // vendor-specific geometry property requires the typed WFS escape hatch
20
+ // until discovery carries that binding into both planner and executor.
21
+ const geometryProperty = "the_geom";
22
+ const filters = [];
23
+ if (query.where) {
24
+ const where = compileWhere(query.where.expression);
25
+ if (where === UNSUPPORTED_FES) {
26
+ throw new HonuaQueryPlanningError("unsupported-query", "The where clause is not expressible as FES 2.0");
27
+ }
28
+ if (where.kind !== "and" || where.operands.length > 0)
29
+ filters.push(where);
30
+ }
31
+ if (query.spatialFilter) {
32
+ const spatial = compileSpatialFilter({
33
+ geometry: query.spatialFilter.geometry,
34
+ geometryType: query.spatialFilter.geometryType,
35
+ ...(query.spatialFilter.spatialRel ? { spatialRel: query.spatialFilter.spatialRel } : {}),
36
+ }, {
37
+ geometryProperty,
38
+ });
39
+ if (spatial === UNSUPPORTED_FES) {
40
+ throw new HonuaQueryPlanningError("unsupported-query", "The spatial predicate is not expressible as FES 2.0");
41
+ }
42
+ filters.push(spatial);
43
+ }
44
+ let propertyName;
45
+ if (query.outFields && query.outFields.length > 0) {
46
+ const fields = [...query.outFields];
47
+ if (query.returnGeometry === false && fields.includes(geometryProperty)) {
48
+ throw new HonuaQueryPlanningError("unsupported-query", `WFS returnGeometry=false conflicts with outFields containing the geometry property "${geometryProperty}"`);
49
+ }
50
+ if (query.returnGeometry !== false && !fields.includes(geometryProperty))
51
+ fields.push(geometryProperty);
52
+ propertyName = fields;
53
+ }
54
+ else if (query.returnGeometry === false) {
55
+ throw new HonuaQueryPlanningError("unsupported-query", "WFS returnGeometry=false requires explicit outFields because propertyName cannot otherwise suppress geometry exactly");
56
+ }
57
+ return {
58
+ compiler: "wfs-2.0-get-feature-v1",
59
+ typeName: source.typeName,
60
+ ...(filters.length > 0 ? { filter: serializeFes(filters, { typeName: source.typeName }) } : {}),
61
+ ...(propertyName ? { propertyName } : {}),
62
+ ...(query.orderBy && query.orderBy.length > 0
63
+ ? {
64
+ sortBy: query.orderBy.map((sort) => `${sort.field} ${sort.direction === "desc" ? "DESC" : "ASC"}`).join(","),
65
+ }
66
+ : {}),
67
+ ...(query.pagination?.offset !== undefined ? { startIndex: query.pagination.offset } : {}),
68
+ ...(query.pagination?.limit !== undefined ? { count: query.pagination.limit } : {}),
69
+ ...(query.outSr !== undefined ? { srsName: String(query.outSr) } : {}),
70
+ };
71
+ }
@@ -0,0 +1,142 @@
1
+ /**
2
+ * Snapshot, delta, cursor, resume, and plan-identity contract surface for
3
+ * `@honua/sdk-js/realtime` (issue #556).
4
+ *
5
+ * The resumable delivery gate in `resumable.ts` already implements the
6
+ * durable-checkpoint state machine: versioned snapshot/delta/cursor
7
+ * envelopes, monotonic-sequence ordering and deduplication, delete
8
+ * tombstones (`reducer.ts`), and mandatory resnapshot on gap, expiry, or
9
+ * scope change. This module closes the three remaining contract gaps that
10
+ * keep that machinery a *design* rather than an ad hoc convention:
11
+ *
12
+ * 1. **Plan identity** — {@link RealtimeResumeContextV1.queryFingerprint} was
13
+ * previously any caller-supplied string. {@link realtimePlanFingerprint}
14
+ * and {@link assertRealtimePlanIdentity} bind it to the query planner's
15
+ * canonical, credential-free `QueryExecutionPlan.fingerprint` — the same
16
+ * content hash `hashQueryPlan`/`persistedPlanSnapshot` establish once,
17
+ * with full protocol-compiler re-verification, when a plan is first
18
+ * constructed (`explainQuery`) or reloaded (`parseQueryPlan`). That
19
+ * fingerprint already folds in `capabilityPolicy` and `fallback`, so
20
+ * binding to it also carries policy identity per REQ-003 without a
21
+ * second fingerprint field. This module reads the already-trusted field
22
+ * rather than importing `hashQueryPlan` itself: plan *integrity* is the
23
+ * query planner's trust boundary, established once at construction time,
24
+ * and re-deriving it here would pull every protocol compiler
25
+ * (DuckDB/gRPC/GeoServices/OGC/OData/WFS — several hundred KB) into the
26
+ * `@honua/sdk-js/realtime` bundle for a check that is already done.
27
+ * 2. **Authority** — {@link deriveRealtimeContractAuthority} projects the
28
+ * resumable gate's `phase` into one explicit
29
+ * `replaying | live | stale | terminal` state plus an `authoritative`
30
+ * flag, so consumers stop inferring trust from reason codes.
31
+ * 3. **Deterministic, redacted serialization** — {@link serializeRealtimeCheckpoint}
32
+ * and {@link redactRealtimeCheckpoint} give a canonical (sorted-key) JSON
33
+ * form for durable storage and a form safe for logs/telemetry that never
34
+ * carries a raw cursor, watermark, or delta-token value.
35
+ *
36
+ * @module
37
+ */
38
+ import type { QueryExecutionPlan } from "../query-planner/types.js";
39
+ import { type RealtimeDurableCheckpointV1, type RealtimeResumeContextV1, type ResumableRealtimeState } from "./resumable.js";
40
+ /**
41
+ * Canonical plan-identity fingerprint for
42
+ * {@link RealtimeResumeContextV1.queryFingerprint}: the accepted plan's own
43
+ * trusted `fingerprint` field (`hashQueryPlan(plan) === plan.fingerprint`
44
+ * for every plan produced by `explainQuery`/`parseQueryPlan`). Named and
45
+ * exported so call sites bind resume identity to the plan by construction
46
+ * instead of by convention, without importing the query planner's
47
+ * protocol-compiler graph into `@honua/sdk-js/realtime`. Only
48
+ * `QueryExecutionPlan["fingerprint"]` is type-imported; nothing from
49
+ * `query-planner/planner.js` is bundled here.
50
+ */
51
+ export declare function realtimePlanFingerprint(plan: Pick<QueryExecutionPlan, "fingerprint">): `sha256:${string}`;
52
+ /**
53
+ * Assert that a resume context's `queryFingerprint` matches the accepted
54
+ * plan's fingerprint. Throws a `"query-changed"`
55
+ * {@link HonuaRealtimeResumeError} — the same taxonomy
56
+ * {@link evaluateRealtimeCheckpoint} uses for a checkpoint whose bound query
57
+ * identity no longer matches — when they diverge, so a subscription can
58
+ * never resume against a plan other than the one it was accepted for. This
59
+ * checks identity, not plan authenticity: trust that `plan.fingerprint`
60
+ * matches the plan's actual content is established once, by the query
61
+ * planner, when the plan is constructed or parsed.
62
+ */
63
+ export declare function assertRealtimePlanIdentity(context: RealtimeResumeContextV1, plan: Pick<QueryExecutionPlan, "fingerprint">): void;
64
+ /**
65
+ * Explicit realtime contract authority states.
66
+ *
67
+ * - `replaying`: no confirmed authoritative baseline yet — awaiting an
68
+ * initial or replacement snapshot, or resuming from a durable checkpoint
69
+ * whose continuity has not been reconfirmed by a live event.
70
+ * - `live`: an accepted snapshot establishes ground truth and the most
71
+ * recent contiguous event arrived within the caller's staleness window
72
+ * (or no window was supplied).
73
+ * - `stale`: the baseline is still authoritative but no event has arrived
74
+ * within `staleAfterMs`; render the last known state but surface
75
+ * liveness doubt.
76
+ * - `terminal`: delivery stopped on an unrecoverable error or an explicit
77
+ * close. The last accepted checkpoint, if any, remains available for
78
+ * read-only rendering but will never advance.
79
+ */
80
+ export type RealtimeContractAuthorityState = "replaying" | "live" | "stale" | "terminal";
81
+ export interface RealtimeContractAuthority {
82
+ readonly state: RealtimeContractAuthorityState;
83
+ /** True once an accepted snapshot has established ground truth for the current checkpoint identity. */
84
+ readonly authoritative: boolean;
85
+ /** Milliseconds since the checkpoint was accepted, when a checkpoint exists and `now` was supplied. */
86
+ readonly ageMs?: number;
87
+ }
88
+ export interface DeriveRealtimeContractAuthorityOptions {
89
+ /** No event within this window while `phase === "live"` demotes authority to `"stale"`. */
90
+ readonly staleAfterMs?: number;
91
+ readonly now?: number;
92
+ }
93
+ /**
94
+ * Derive one explicit {@link RealtimeContractAuthority} from a resumable
95
+ * gate's state. Pure and side-effect free: call it after every
96
+ * `enqueue`/`requireResnapshot`/`close` transition to render connection
97
+ * chrome without re-deriving trust from `phase`/`reason` combinations.
98
+ */
99
+ export declare function deriveRealtimeContractAuthority(resumable: Pick<ResumableRealtimeState, "phase" | "checkpoint">, options?: DeriveRealtimeContractAuthorityOptions): RealtimeContractAuthority;
100
+ /** Opaque, deterministic digest of a raw resume-position value; never the raw value itself. */
101
+ export type RedactedResumePosition = `sha256:${string}`;
102
+ export interface RedactedRealtimeResumePosition {
103
+ readonly sequence: number;
104
+ readonly cursor?: RedactedResumePosition;
105
+ readonly watermark?: RedactedResumePosition;
106
+ readonly timestamp?: string;
107
+ readonly deltaToken?: RedactedResumePosition;
108
+ }
109
+ /**
110
+ * Log/telemetry-safe projection of a durable checkpoint. `context` is kept
111
+ * as-is because every one of its fields is already an opaque identity or
112
+ * fingerprint (never a credential). `resume.cursor`, `resume.watermark`, and
113
+ * `resume.deltaToken` are replaced with a deterministic SHA-256 digest so
114
+ * repeated observations of the same position stay correlatable without
115
+ * exposing the raw, potentially sensitive token. `timestamp` is kept in the
116
+ * clear because it is not an opaque credential. `recentEventIds` is
117
+ * collapsed to a count: individual event ids are dedup identity, not
118
+ * something a log line needs verbatim.
119
+ */
120
+ export interface RedactedRealtimeCheckpointV1 {
121
+ readonly kind: "honua.realtime-checkpoint";
122
+ readonly version: RealtimeDurableCheckpointV1["version"];
123
+ readonly context: RealtimeResumeContextV1;
124
+ readonly resume: RedactedRealtimeResumePosition;
125
+ readonly recentEventIdCount: number;
126
+ readonly savedAt: string;
127
+ }
128
+ export declare function redactRealtimeCheckpoint(checkpoint: RealtimeDurableCheckpointV1): RedactedRealtimeCheckpointV1;
129
+ /**
130
+ * Canonical (sorted-key) JSON serialization of the full durable checkpoint,
131
+ * for persistence where the caller owns access control. Two checkpoints
132
+ * with the same accepted content always serialize identically, regardless
133
+ * of property insertion order — required for content-addressed storage and
134
+ * for stable diffing across processes.
135
+ */
136
+ export declare function serializeRealtimeCheckpoint(checkpoint: RealtimeDurableCheckpointV1): string;
137
+ /**
138
+ * Canonical, redacted JSON serialization safe for logs and telemetry
139
+ * sinks — never a raw cursor, watermark, or delta-token value.
140
+ */
141
+ export declare function serializeRedactedRealtimeCheckpoint(checkpoint: RealtimeDurableCheckpointV1): string;
142
+ //# sourceMappingURL=contract.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"contract.d.ts","sourceRoot":"","sources":["../../../src/realtime/contract.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoCG;AAGH,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,2BAA2B,CAAC;AACpE,OAAO,EAEL,KAAK,2BAA2B,EAChC,KAAK,uBAAuB,EAC5B,KAAK,sBAAsB,EAC5B,MAAM,gBAAgB,CAAC;AAExB;;;;;;;;;;GAUG;AACH,wBAAgB,uBAAuB,CAAC,IAAI,EAAE,IAAI,CAAC,kBAAkB,EAAE,aAAa,CAAC,GAAG,UAAU,MAAM,EAAE,CAEzG;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,0BAA0B,CACxC,OAAO,EAAE,uBAAuB,EAChC,IAAI,EAAE,IAAI,CAAC,kBAAkB,EAAE,aAAa,CAAC,GAC5C,IAAI,CAQN;AAED;;;;;;;;;;;;;;;GAeG;AACH,MAAM,MAAM,8BAA8B,GAAG,WAAW,GAAG,MAAM,GAAG,OAAO,GAAG,UAAU,CAAC;AAEzF,MAAM,WAAW,yBAAyB;IACxC,QAAQ,CAAC,KAAK,EAAE,8BAA8B,CAAC;IAC/C,uGAAuG;IACvG,QAAQ,CAAC,aAAa,EAAE,OAAO,CAAC;IAChC,uGAAuG;IACvG,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;CACzB;AAED,MAAM,WAAW,sCAAsC;IACrD,2FAA2F;IAC3F,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,CAAC;CACvB;AAED;;;;;GAKG;AACH,wBAAgB,+BAA+B,CAC7C,SAAS,EAAE,IAAI,CAAC,sBAAsB,EAAE,OAAO,GAAG,YAAY,CAAC,EAC/D,OAAO,GAAE,sCAA2C,GACnD,yBAAyB,CA2B3B;AAYD,+FAA+F;AAC/F,MAAM,MAAM,sBAAsB,GAAG,UAAU,MAAM,EAAE,CAAC;AAExD,MAAM,WAAW,8BAA8B;IAC7C,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,MAAM,CAAC,EAAE,sBAAsB,CAAC;IACzC,QAAQ,CAAC,SAAS,CAAC,EAAE,sBAAsB,CAAC;IAC5C,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,UAAU,CAAC,EAAE,sBAAsB,CAAC;CAC9C;AAED;;;;;;;;;;GAUG;AACH,MAAM,WAAW,4BAA4B;IAC3C,QAAQ,CAAC,IAAI,EAAE,2BAA2B,CAAC;IAC3C,QAAQ,CAAC,OAAO,EAAE,2BAA2B,CAAC,SAAS,CAAC,CAAC;IACzD,QAAQ,CAAC,OAAO,EAAE,uBAAuB,CAAC;IAC1C,QAAQ,CAAC,MAAM,EAAE,8BAA8B,CAAC;IAChD,QAAQ,CAAC,kBAAkB,EAAE,MAAM,CAAC;IACpC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;CAC1B;AAED,wBAAgB,wBAAwB,CAAC,UAAU,EAAE,2BAA2B,GAAG,4BAA4B,CAmB9G;AAMD;;;;;;GAMG;AACH,wBAAgB,2BAA2B,CAAC,UAAU,EAAE,2BAA2B,GAAG,MAAM,CAE3F;AAED;;;GAGG;AACH,wBAAgB,mCAAmC,CAAC,UAAU,EAAE,2BAA2B,GAAG,MAAM,CAEnG"}
@@ -0,0 +1,154 @@
1
+ /**
2
+ * Snapshot, delta, cursor, resume, and plan-identity contract surface for
3
+ * `@honua/sdk-js/realtime` (issue #556).
4
+ *
5
+ * The resumable delivery gate in `resumable.ts` already implements the
6
+ * durable-checkpoint state machine: versioned snapshot/delta/cursor
7
+ * envelopes, monotonic-sequence ordering and deduplication, delete
8
+ * tombstones (`reducer.ts`), and mandatory resnapshot on gap, expiry, or
9
+ * scope change. This module closes the three remaining contract gaps that
10
+ * keep that machinery a *design* rather than an ad hoc convention:
11
+ *
12
+ * 1. **Plan identity** — {@link RealtimeResumeContextV1.queryFingerprint} was
13
+ * previously any caller-supplied string. {@link realtimePlanFingerprint}
14
+ * and {@link assertRealtimePlanIdentity} bind it to the query planner's
15
+ * canonical, credential-free `QueryExecutionPlan.fingerprint` — the same
16
+ * content hash `hashQueryPlan`/`persistedPlanSnapshot` establish once,
17
+ * with full protocol-compiler re-verification, when a plan is first
18
+ * constructed (`explainQuery`) or reloaded (`parseQueryPlan`). That
19
+ * fingerprint already folds in `capabilityPolicy` and `fallback`, so
20
+ * binding to it also carries policy identity per REQ-003 without a
21
+ * second fingerprint field. This module reads the already-trusted field
22
+ * rather than importing `hashQueryPlan` itself: plan *integrity* is the
23
+ * query planner's trust boundary, established once at construction time,
24
+ * and re-deriving it here would pull every protocol compiler
25
+ * (DuckDB/gRPC/GeoServices/OGC/OData/WFS — several hundred KB) into the
26
+ * `@honua/sdk-js/realtime` bundle for a check that is already done.
27
+ * 2. **Authority** — {@link deriveRealtimeContractAuthority} projects the
28
+ * resumable gate's `phase` into one explicit
29
+ * `replaying | live | stale | terminal` state plus an `authoritative`
30
+ * flag, so consumers stop inferring trust from reason codes.
31
+ * 3. **Deterministic, redacted serialization** — {@link serializeRealtimeCheckpoint}
32
+ * and {@link redactRealtimeCheckpoint} give a canonical (sorted-key) JSON
33
+ * form for durable storage and a form safe for logs/telemetry that never
34
+ * carries a raw cursor, watermark, or delta-token value.
35
+ *
36
+ * @module
37
+ */
38
+ import { canonicalStringify, sha256, toJsonValue } from "../query-planner/canonical.js";
39
+ import { HonuaRealtimeResumeError, } from "./resumable.js";
40
+ /**
41
+ * Canonical plan-identity fingerprint for
42
+ * {@link RealtimeResumeContextV1.queryFingerprint}: the accepted plan's own
43
+ * trusted `fingerprint` field (`hashQueryPlan(plan) === plan.fingerprint`
44
+ * for every plan produced by `explainQuery`/`parseQueryPlan`). Named and
45
+ * exported so call sites bind resume identity to the plan by construction
46
+ * instead of by convention, without importing the query planner's
47
+ * protocol-compiler graph into `@honua/sdk-js/realtime`. Only
48
+ * `QueryExecutionPlan["fingerprint"]` is type-imported; nothing from
49
+ * `query-planner/planner.js` is bundled here.
50
+ */
51
+ export function realtimePlanFingerprint(plan) {
52
+ return plan.fingerprint;
53
+ }
54
+ /**
55
+ * Assert that a resume context's `queryFingerprint` matches the accepted
56
+ * plan's fingerprint. Throws a `"query-changed"`
57
+ * {@link HonuaRealtimeResumeError} — the same taxonomy
58
+ * {@link evaluateRealtimeCheckpoint} uses for a checkpoint whose bound query
59
+ * identity no longer matches — when they diverge, so a subscription can
60
+ * never resume against a plan other than the one it was accepted for. This
61
+ * checks identity, not plan authenticity: trust that `plan.fingerprint`
62
+ * matches the plan's actual content is established once, by the query
63
+ * planner, when the plan is constructed or parsed.
64
+ */
65
+ export function assertRealtimePlanIdentity(context, plan) {
66
+ const expected = realtimePlanFingerprint(plan);
67
+ if (context.queryFingerprint !== expected) {
68
+ throw new HonuaRealtimeResumeError("query-changed", `Realtime resume context queryFingerprint does not match the accepted plan fingerprint (expected "${expected}", received "${context.queryFingerprint}").`);
69
+ }
70
+ }
71
+ /**
72
+ * Derive one explicit {@link RealtimeContractAuthority} from a resumable
73
+ * gate's state. Pure and side-effect free: call it after every
74
+ * `enqueue`/`requireResnapshot`/`close` transition to render connection
75
+ * chrome without re-deriving trust from `phase`/`reason` combinations.
76
+ */
77
+ export function deriveRealtimeContractAuthority(resumable, options = {}) {
78
+ const ageMs = checkpointAgeMs(resumable.checkpoint, options.now);
79
+ switch (resumable.phase) {
80
+ case "awaiting-snapshot":
81
+ case "resuming":
82
+ case "resnapshot-required":
83
+ return freeze({ state: "replaying", authoritative: false, ...(ageMs !== undefined ? { ageMs } : {}) });
84
+ case "error":
85
+ case "closed":
86
+ return freeze({
87
+ state: "terminal",
88
+ authoritative: resumable.checkpoint !== undefined,
89
+ ...(ageMs !== undefined ? { ageMs } : {}),
90
+ });
91
+ case "live": {
92
+ const stale = ageMs !== undefined &&
93
+ typeof options.staleAfterMs === "number" &&
94
+ Number.isFinite(options.staleAfterMs) &&
95
+ ageMs > options.staleAfterMs;
96
+ return freeze({
97
+ state: stale ? "stale" : "live",
98
+ authoritative: true,
99
+ ...(ageMs !== undefined ? { ageMs } : {}),
100
+ });
101
+ }
102
+ }
103
+ }
104
+ function checkpointAgeMs(checkpoint, now) {
105
+ if (!checkpoint || typeof now !== "number" || !Number.isFinite(now))
106
+ return undefined;
107
+ const savedAt = Date.parse(checkpoint.savedAt);
108
+ if (!Number.isFinite(savedAt))
109
+ return undefined;
110
+ return Math.max(0, now - savedAt);
111
+ }
112
+ export function redactRealtimeCheckpoint(checkpoint) {
113
+ return freeze({
114
+ kind: checkpoint.kind,
115
+ version: checkpoint.version,
116
+ context: checkpoint.context,
117
+ resume: freeze({
118
+ sequence: checkpoint.resume.sequence,
119
+ ...(checkpoint.resume.cursor !== undefined ? { cursor: redactPosition("cursor", checkpoint.resume.cursor) } : {}),
120
+ ...(checkpoint.resume.watermark !== undefined
121
+ ? { watermark: redactPosition("watermark", checkpoint.resume.watermark) }
122
+ : {}),
123
+ ...(checkpoint.resume.timestamp !== undefined ? { timestamp: checkpoint.resume.timestamp } : {}),
124
+ ...(checkpoint.resume.deltaToken !== undefined
125
+ ? { deltaToken: redactPosition("deltaToken", checkpoint.resume.deltaToken) }
126
+ : {}),
127
+ }),
128
+ recentEventIdCount: checkpoint.recentEventIds.length,
129
+ savedAt: checkpoint.savedAt,
130
+ });
131
+ }
132
+ function redactPosition(field, value) {
133
+ return sha256(`honua-realtime-checkpoint-redaction:v1:${field}:${value}`);
134
+ }
135
+ /**
136
+ * Canonical (sorted-key) JSON serialization of the full durable checkpoint,
137
+ * for persistence where the caller owns access control. Two checkpoints
138
+ * with the same accepted content always serialize identically, regardless
139
+ * of property insertion order — required for content-addressed storage and
140
+ * for stable diffing across processes.
141
+ */
142
+ export function serializeRealtimeCheckpoint(checkpoint) {
143
+ return canonicalStringify(toJsonValue(checkpoint));
144
+ }
145
+ /**
146
+ * Canonical, redacted JSON serialization safe for logs and telemetry
147
+ * sinks — never a raw cursor, watermark, or delta-token value.
148
+ */
149
+ export function serializeRedactedRealtimeCheckpoint(checkpoint) {
150
+ return canonicalStringify(toJsonValue(redactRealtimeCheckpoint(checkpoint)));
151
+ }
152
+ function freeze(value) {
153
+ return Object.freeze(value);
154
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"honua-server.d.ts","sourceRoot":"","sources":["../../../src/realtime/honua-server.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"honua-server.d.ts","sourceRoot":"","sources":["../../../src/realtime/honua-server.ts"],"names":[],"mappings":"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"}
@@ -34,7 +34,8 @@
34
34
  * @module
35
35
  */
36
36
  import { trimTrailingSlashes } from "../core/path-utils.js";
37
- import { createRealtimeServerSentEventsTransport } from "./sse.js";
37
+ import { HonuaRealtimeResumeError } from "./resumable.js";
38
+ import { createRealtimeServerSentEventsTransport, decodeRealtimeServerSentEvent } from "./sse.js";
38
39
  /** Default streaming-features path served by honua-server. */
39
40
  export const HONUA_SERVER_STREAMING_FEATURES_PATH = "/api/v1/streaming/features";
40
41
  /**
@@ -83,16 +84,17 @@ export function encodeHonuaServerRealtimeRequest(url, request) {
83
84
  * already speak the SDK vocabulary pass through unchanged.
84
85
  */
85
86
  export function decodeHonuaServerRealtimeEvent(payload) {
86
- if (!isRecord(payload))
87
- throw new Error("honua-server streaming payload must be a JSON object.");
87
+ if (!isRecord(payload)) {
88
+ throw new HonuaRealtimeResumeError("invalid-event", "honua-server streaming payload must be a JSON object.");
89
+ }
88
90
  const envelope = payload;
89
91
  // Status / heartbeat / error envelopes already use the SDK vocabulary.
90
92
  if (typeof envelope.type === "string" && envelope.type !== "change" && envelope.type !== "feature-change") {
91
- return payload;
93
+ return decodeRealtimeServerSentEvent(payload);
92
94
  }
93
95
  const changes = collectChanges(envelope);
94
96
  if (changes.length === 0) {
95
- throw new Error("honua-server feature-change envelope is missing changes.");
97
+ throw new HonuaRealtimeResumeError("invalid-event", "honua-server feature-change envelope is missing changes.");
96
98
  }
97
99
  const base = {
98
100
  eventId: envelope.eventId,
@@ -105,15 +107,18 @@ export function decodeHonuaServerRealtimeEvent(payload) {
105
107
  const upserts = [];
106
108
  const deletes = [];
107
109
  for (const change of changes) {
110
+ if (!isRecord(change)) {
111
+ throw new HonuaRealtimeResumeError("invalid-event", "honua-server feature change must be an object.");
112
+ }
108
113
  if (change.featureId === undefined) {
109
- throw new Error("honua-server feature change is missing featureId.");
114
+ throw new HonuaRealtimeResumeError("invalid-event", "honua-server feature change is missing featureId.");
110
115
  }
111
116
  if (change.op === "delete") {
112
117
  deletes.push({ id: change.featureId, version: change.version, updatedAt: change.updatedAt });
113
118
  continue;
114
119
  }
115
120
  if (change.feature === undefined) {
116
- throw new Error(`honua-server ${change.op} change is missing feature payload.`);
121
+ throw new HonuaRealtimeResumeError("invalid-event", `honua-server ${change.op} change is missing feature payload.`);
117
122
  }
118
123
  upserts.push({
119
124
  id: change.featureId,
@@ -161,7 +166,7 @@ export function createHonuaServerRealtimeSubscription(options) {
161
166
  }
162
167
  function resolveStreamingUrl(baseUrl) {
163
168
  if (!baseUrl) {
164
- throw new Error("createHonuaServerRealtimeSubscription requires either `url` or `baseUrl`.");
169
+ throw new HonuaRealtimeResumeError("invalid-event", "createHonuaServerRealtimeSubscription requires either `url` or `baseUrl`.");
165
170
  }
166
171
  // Linear trim (no anchored `\/+$` regex) to avoid polynomial backtracking on
167
172
  // adversarial input — mirrors the rest of the SDK's path handling.
@@ -1,9 +1,14 @@
1
1
  /**
2
- * `@honua/sdk-js/realtime` — realtime transport adapters (Server-Sent Events today,
3
- * WebSocket/WebTransport adapters land alongside future server endpoints).
2
+ * `@honua/sdk-js/realtime` — realtime transport adapters. Raw Server-Sent
3
+ * Events (`sse.ts`) and WebSocket (`websocket.ts`) wire adapters pair with
4
+ * the bounded, resumable wrapper in `resumable-transport.ts` (issue #557),
5
+ * which adds the #556 delivery gate, reconnect/backoff, heartbeat timeout,
6
+ * and redacted telemetry on top of either one. `odata-delta.ts` (issue #558)
7
+ * adds a pull-based OData v4 delta-link adapter with its own bounded poll
8
+ * loop and honest, non-live freshness reporting — it satisfies the same
9
+ * `RealtimeFeatureTransport` surface but is not a socket transport, so it
10
+ * does not compose with `resumable-transport.ts`'s reconnect wrapper.
4
11
  *
5
- * @experimental This entrypoint is not yet covered by the SDK's semver contract
6
- * — the surface may change in any minor release prior to `1.0.0`.
7
12
  * @module
8
13
  */
9
14
  export { createRealtimeServerSentEventsTransport, decodeRealtimeServerSentEvent, encodeDefaultRealtimeRequest, } from "./sse.js";
@@ -13,6 +18,16 @@ export type { CreateHonuaServerRealtimeSubscriptionOptions, HonuaServerFeatureCh
13
18
  export { emptyRealtimeFeatureState, reconcileRealtimeStaleness, reduceRealtimeFeatureState, realtimeFeatureKey, realtimeResumeCheckpoint, realtimeSubscriptionKey, } from "./reducer.js";
14
19
  export { createRealtimeFeatureStore } from "./store.js";
15
20
  export type { RealtimeFeatureStore } from "./store.js";
21
+ export { REALTIME_DURABLE_CHECKPOINT_VERSION, HonuaRealtimeResumeError, createResumableRealtimeSubscription, evaluateRealtimeCheckpoint, } from "./resumable.js";
22
+ export type { CreateResumableRealtimeSubscriptionOptions, RealtimeCheckpointCompatibility, RealtimeCheckpointCompatibilityCode, RealtimeCheckpointStore, RealtimeDurableCheckpointV1, RealtimeExternalResnapshotReason, RealtimeResumeContextV1, RealtimeSequencedEvent, ResumableRealtimeDelivery, ResumableRealtimeDeliveryStatus, ResumableRealtimePhase, ResumableRealtimeReasonCode, ResumableRealtimeState, ResumableRealtimeSubscription, } from "./resumable.js";
23
+ export { assertRealtimePlanIdentity, deriveRealtimeContractAuthority, realtimePlanFingerprint, redactRealtimeCheckpoint, serializeRealtimeCheckpoint, serializeRedactedRealtimeCheckpoint, } from "./contract.js";
24
+ export type { DeriveRealtimeContractAuthorityOptions, RealtimeContractAuthority, RealtimeContractAuthorityState, RedactedRealtimeCheckpointV1, RedactedRealtimeResumePosition, RedactedResumePosition, } from "./contract.js";
25
+ export { createRealtimeWebSocketTransport } from "./websocket.js";
26
+ export type { RealtimeWebSocket, RealtimeWebSocketCloseEvent, RealtimeWebSocketFactory, RealtimeWebSocketTransportOptions, } from "./websocket.js";
27
+ export { createOdataDeltaTransport } from "./odata-delta.js";
28
+ export type { OdataDeltaEntity, OdataDeltaInitialQuery, OdataDeltaPollTelemetry, OdataDeltaTransportOptions, } from "./odata-delta.js";
29
+ export { computeReconnectDelayMs, createResumableRealtimeTransport, createResumableServerSentEventsTransport, createResumableWebSocketTransport, } from "./resumable-transport.js";
30
+ export type { RealtimeReconnectPolicy, RealtimeResumableTransportOptions, RealtimeResumableTransportTelemetry, } from "./resumable-transport.js";
16
31
  export { filterRealtimeSelection, reconcileRealtimeSelection } from "./exploration.js";
17
32
  export { selectRealtimeDetail, selectRealtimeFeatureRecordMap, selectRealtimeFeatureRecords, selectRealtimeFeatureTombstones, selectRealtimeFeatures, } from "./projections.js";
18
33
  export type { RealtimeDetailSelectorOptions, RealtimeDetailState, RealtimeFeatureRecordSelectorOptions, RealtimeFeatureReference, } from "./projections.js";
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/realtime/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,EACL,uCAAuC,EACvC,6BAA6B,EAC7B,4BAA4B,GAC7B,MAAM,UAAU,CAAC;AAClB,YAAY,EACV,6BAA6B,EAC7B,oCAAoC,EACpC,wCAAwC,GACzC,MAAM,UAAU,CAAC;AAClB,OAAO,EACL,qCAAqC,EACrC,8BAA8B,EAC9B,gCAAgC,EAChC,yBAAyB,EACzB,oCAAoC,GACrC,MAAM,mBAAmB,CAAC;AAC3B,YAAY,EACV,4CAA4C,EAC5C,wBAAwB,EACxB,gCAAgC,GACjC,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EACL,yBAAyB,EACzB,0BAA0B,EAC1B,0BAA0B,EAC1B,kBAAkB,EAClB,wBAAwB,EACxB,uBAAuB,GACxB,MAAM,cAAc,CAAC;AACtB,OAAO,EAAE,0BAA0B,EAAE,MAAM,YAAY,CAAC;AACxD,YAAY,EAAE,oBAAoB,EAAE,MAAM,YAAY,CAAC;AACvD,OAAO,EAAE,uBAAuB,EAAE,0BAA0B,EAAE,MAAM,kBAAkB,CAAC;AACvF,OAAO,EACL,oBAAoB,EACpB,8BAA8B,EAC9B,4BAA4B,EAC5B,+BAA+B,EAC/B,sBAAsB,GACvB,MAAM,kBAAkB,CAAC;AAC1B,YAAY,EACV,6BAA6B,EAC7B,mBAAmB,EACnB,oCAAoC,EACpC,wBAAwB,GACzB,MAAM,kBAAkB,CAAC;AAC1B,YAAY,EACV,wBAAwB,EACxB,mBAAmB,EACnB,mBAAmB,EACnB,kBAAkB,EAClB,kBAAkB,EAClB,oBAAoB,EACpB,wBAAwB,EACxB,uBAAuB,EACvB,oBAAoB,EACpB,qBAAqB,EACrB,oBAAoB,EACpB,wBAAwB,EACxB,wBAAwB,EACxB,sBAAsB,EACtB,sBAAsB,EACtB,wBAAwB,EACxB,kBAAkB,EAClB,qBAAqB,EACrB,qBAAqB,EACrB,mBAAmB,EACnB,wBAAwB,EACxB,4BAA4B,EAC5B,0BAA0B,EAC1B,wBAAwB,EACxB,2BAA2B,EAC3B,6BAA6B,EAC7B,qBAAqB,EACrB,mBAAmB,GACpB,MAAM,YAAY,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/realtime/index.ts"],"names":[],"mappings":"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"}
package/realtime/index.js CHANGED
@@ -1,14 +1,24 @@
1
1
  /**
2
- * `@honua/sdk-js/realtime` — realtime transport adapters (Server-Sent Events today,
3
- * WebSocket/WebTransport adapters land alongside future server endpoints).
2
+ * `@honua/sdk-js/realtime` — realtime transport adapters. Raw Server-Sent
3
+ * Events (`sse.ts`) and WebSocket (`websocket.ts`) wire adapters pair with
4
+ * the bounded, resumable wrapper in `resumable-transport.ts` (issue #557),
5
+ * which adds the #556 delivery gate, reconnect/backoff, heartbeat timeout,
6
+ * and redacted telemetry on top of either one. `odata-delta.ts` (issue #558)
7
+ * adds a pull-based OData v4 delta-link adapter with its own bounded poll
8
+ * loop and honest, non-live freshness reporting — it satisfies the same
9
+ * `RealtimeFeatureTransport` surface but is not a socket transport, so it
10
+ * does not compose with `resumable-transport.ts`'s reconnect wrapper.
4
11
  *
5
- * @experimental This entrypoint is not yet covered by the SDK's semver contract
6
- * — the surface may change in any minor release prior to `1.0.0`.
7
12
  * @module
8
13
  */
9
14
  export { createRealtimeServerSentEventsTransport, decodeRealtimeServerSentEvent, encodeDefaultRealtimeRequest, } from "./sse.js";
10
15
  export { createHonuaServerRealtimeSubscription, decodeHonuaServerRealtimeEvent, encodeHonuaServerRealtimeRequest, honuaServerRealtimePreset, HONUA_SERVER_STREAMING_FEATURES_PATH, } from "./honua-server.js";
11
16
  export { emptyRealtimeFeatureState, reconcileRealtimeStaleness, reduceRealtimeFeatureState, realtimeFeatureKey, realtimeResumeCheckpoint, realtimeSubscriptionKey, } from "./reducer.js";
12
17
  export { createRealtimeFeatureStore } from "./store.js";
18
+ export { REALTIME_DURABLE_CHECKPOINT_VERSION, HonuaRealtimeResumeError, createResumableRealtimeSubscription, evaluateRealtimeCheckpoint, } from "./resumable.js";
19
+ export { assertRealtimePlanIdentity, deriveRealtimeContractAuthority, realtimePlanFingerprint, redactRealtimeCheckpoint, serializeRealtimeCheckpoint, serializeRedactedRealtimeCheckpoint, } from "./contract.js";
20
+ export { createRealtimeWebSocketTransport } from "./websocket.js";
21
+ export { createOdataDeltaTransport } from "./odata-delta.js";
22
+ export { computeReconnectDelayMs, createResumableRealtimeTransport, createResumableServerSentEventsTransport, createResumableWebSocketTransport, } from "./resumable-transport.js";
13
23
  export { filterRealtimeSelection, reconcileRealtimeSelection } from "./exploration.js";
14
24
  export { selectRealtimeDetail, selectRealtimeFeatureRecordMap, selectRealtimeFeatureRecords, selectRealtimeFeatureTombstones, selectRealtimeFeatures, } from "./projections.js";
@@ -0,0 +1,114 @@
1
+ /**
2
+ * OData v4 delta-link pull adapter for `@honua/sdk-js/realtime` (issue #558).
3
+ *
4
+ * OData delta links (`@odata.deltaLink`) are a *pull* change feed, not a
5
+ * socket transport: a client requests a scoped delta-link URL and the
6
+ * server returns only what changed since that link was minted, plus a fresh
7
+ * delta link for the next request. This module maps that request/response
8
+ * paging protocol onto the transport-neutral `RealtimeFeatureTransport`
9
+ * surface (`types.ts`, #556) so it composes with `createRealtimeFeatureStore`
10
+ * / `createResumableRealtimeSubscription` exactly like the SSE and WebSocket
11
+ * adapters (`sse.ts`, `websocket.ts`, #557) — with one deliberate
12
+ * difference dictated by REQ-004: this transport never claims to be live.
13
+ * `capabilities.kind` is `"polling"`, it never emits a `"heartbeat"`, and
14
+ * `onPoll` reports an explicit cadence/freshness snapshot on every request
15
+ * cycle so a consumer can render "checked N seconds ago, next check in M"
16
+ * rather than a live badge that misrepresents a poll as a stream.
17
+ *
18
+ * Ownership split from the SSE/WebSocket adapters: those open exactly one
19
+ * connection per `subscribe()` and never reconnect on their own, deferring
20
+ * reconnect entirely to `resumable-transport.ts`. This transport has no
21
+ * connection to reconnect — every cycle is an independent request/response —
22
+ * so it owns its own bounded poll loop, including the on-expiry resnapshot
23
+ * recovery a socket transport cannot perform for itself: an expired or
24
+ * rejected delta link (REQ per the User Workflow, "Expired/invalid delta
25
+ * state requires an explicit resnapshot") is recovered by re-running a full
26
+ * snapshot cycle and emitting a fresh `type: "snapshot", replace: true`
27
+ * event, bounded by `maxConsecutiveResnapshots` so a server that keeps
28
+ * rejecting the token cannot loop forever.
29
+ *
30
+ * Security posture (REQ-002): every `@odata.nextLink` / `@odata.deltaLink`
31
+ * this transport follows, and any caller-supplied `resumeFrom.deltaToken`,
32
+ * must resolve to the exact same origin and collection path as the
33
+ * configured `url`. A link that does not is rejected rather than followed —
34
+ * a delta token is an opaque, potentially attacker-influenced value once it
35
+ * has round-tripped through a checkpoint store, and this transport never
36
+ * trusts it to redirect a request elsewhere.
37
+ *
38
+ * @module
39
+ */
40
+ import type { FeatureId } from "../contract/types.js";
41
+ import type { RealtimeFeatureTransport } from "./types.js";
42
+ /** Raw OData entity as delivered in a `value[]` array entry, annotations included. */
43
+ export interface OdataDeltaEntity {
44
+ readonly [key: string]: unknown;
45
+ }
46
+ /** `$`-shaped query options applied only to the initial full-collection snapshot request. */
47
+ export interface OdataDeltaInitialQuery {
48
+ readonly filter?: string;
49
+ readonly select?: readonly string[];
50
+ readonly orderBy?: readonly string[];
51
+ readonly top?: number;
52
+ }
53
+ /** Honest pull-cadence/freshness telemetry emitted after every request cycle. Never implies push liveness. */
54
+ export interface OdataDeltaPollTelemetry {
55
+ /** When this cycle's response was accepted, per `now()`. */
56
+ readonly polledAt: number;
57
+ /** When the next poll is scheduled, per `now()`. Absent while a resnapshot recovery is in progress. */
58
+ readonly nextPollAt?: number;
59
+ readonly intervalMs: number;
60
+ readonly sequence: number;
61
+ readonly changed: boolean;
62
+ readonly upsertCount: number;
63
+ readonly deleteCount: number;
64
+ }
65
+ export interface OdataDeltaTransportOptions<TFeature = unknown> {
66
+ /**
67
+ * Absolute OData entity-set collection URL, e.g.
68
+ * `"https://host/odata/Incidents"`. Every `@odata.nextLink` /
69
+ * `@odata.deltaLink` this transport follows, and any resumed
70
+ * `resumeFrom.deltaToken`, must resolve to this exact origin and path
71
+ * (REQ-002).
72
+ */
73
+ readonly url: string;
74
+ /** Delay between the end of one accepted cycle and the start of the next. */
75
+ readonly pollIntervalMs: number;
76
+ /** Extract a stable entity id from a raw OData entity for `RealtimeFeaturePatch.id`. */
77
+ readonly entityId: (entity: OdataDeltaEntity) => FeatureId;
78
+ /** Project a raw OData entity into the feature payload. @default the entity with `@`-prefixed annotations stripped */
79
+ readonly toFeature?: (entity: OdataDeltaEntity) => TFeature;
80
+ /** `$filter` / `$select` / `$orderby` / `$top` applied only to the initial snapshot request. */
81
+ readonly initialQuery?: OdataDeltaInitialQuery;
82
+ /** Request headers, recomputed for every request (e.g. a fresh bearer token). */
83
+ readonly headers?: () => Record<string, string>;
84
+ /** Injectable fetch; defaults to `globalThis.fetch`. */
85
+ readonly fetchImpl?: typeof fetch;
86
+ /**
87
+ * Classify a non-ok response as an expired/rejected delta link that should
88
+ * trigger an explicit resnapshot rather than a generic transport failure.
89
+ * @default status => status === 410
90
+ */
91
+ readonly isDeltaLinkExpiredResponse?: (status: number, body: unknown) => boolean;
92
+ /**
93
+ * Identify an OData delta-payload entry as an unsupported relationship
94
+ * (link) change rather than an entity change, per REQ-003. Relationship
95
+ * deltas fail explicitly instead of being silently dropped or
96
+ * misinterpreted as an entity upsert.
97
+ * @default entry with string `source`, `relationship`, and `target` properties
98
+ */
99
+ readonly isRelationshipDeltaEntry?: (entry: OdataDeltaEntity) => boolean;
100
+ /** Safety bound on `@odata.nextLink` pages followed within one cycle. @default 500 */
101
+ readonly maxPagesPerCycle?: number;
102
+ /** Safety bound on rows collected by one snapshot cycle. @default 50_000 */
103
+ readonly maxSnapshotRows?: number;
104
+ /** Consecutive expired-delta-link resnapshots tolerated before failing closed. @default 3 */
105
+ readonly maxConsecutiveResnapshots?: number;
106
+ readonly onPoll?: (telemetry: OdataDeltaPollTelemetry) => void;
107
+ readonly now?: () => number;
108
+ }
109
+ /**
110
+ * Build a {@link RealtimeFeatureTransport} that polls an OData v4 entity
111
+ * set's delta link. See the module doc for the full recovery/security model.
112
+ */
113
+ export declare function createOdataDeltaTransport<TFeature = unknown>(options: OdataDeltaTransportOptions<TFeature>): RealtimeFeatureTransport<TFeature>;
114
+ //# sourceMappingURL=odata-delta.d.ts.map