@honua/react 0.0.20-alpha.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 (597) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +9 -0
  3. package/contract/analytics-sources.d.ts +149 -0
  4. package/contract/analytics-sources.d.ts.map +1 -0
  5. package/contract/analytics-sources.js +235 -0
  6. package/contract/edit-session.d.ts +203 -0
  7. package/contract/edit-session.d.ts.map +1 -0
  8. package/contract/edit-session.js +760 -0
  9. package/contract/edit-sketch.d.ts +91 -0
  10. package/contract/edit-sketch.d.ts.map +1 -0
  11. package/contract/edit-sketch.js +281 -0
  12. package/contract/index.d.ts +61 -0
  13. package/contract/index.d.ts.map +1 -0
  14. package/contract/index.js +51 -0
  15. package/contract/jobs.d.ts +149 -0
  16. package/contract/jobs.d.ts.map +1 -0
  17. package/contract/jobs.js +40 -0
  18. package/contract/source.d.ts +423 -0
  19. package/contract/source.d.ts.map +1 -0
  20. package/contract/source.js +3802 -0
  21. package/contract/spatial-aggregation.d.ts +336 -0
  22. package/contract/spatial-aggregation.d.ts.map +1 -0
  23. package/contract/spatial-aggregation.js +298 -0
  24. package/contract/temporal.d.ts +455 -0
  25. package/contract/temporal.d.ts.map +1 -0
  26. package/contract/temporal.js +146 -0
  27. package/contract/tiles.d.ts +370 -0
  28. package/contract/tiles.d.ts.map +1 -0
  29. package/contract/tiles.js +519 -0
  30. package/contract/types.d.ts +665 -0
  31. package/contract/types.d.ts.map +1 -0
  32. package/contract/types.js +175 -0
  33. package/contract/widget-source.d.ts +205 -0
  34. package/contract/widget-source.d.ts.map +1 -0
  35. package/contract/widget-source.js +1473 -0
  36. package/control-plane/client.d.ts +95 -0
  37. package/control-plane/client.d.ts.map +1 -0
  38. package/control-plane/client.js +374 -0
  39. package/control-plane/index.d.ts +18 -0
  40. package/control-plane/index.d.ts.map +1 -0
  41. package/control-plane/index.js +14 -0
  42. package/control-plane/types.d.ts +233 -0
  43. package/control-plane/types.d.ts.map +1 -0
  44. package/control-plane/types.js +7 -0
  45. package/core/batch.d.ts +37 -0
  46. package/core/batch.d.ts.map +1 -0
  47. package/core/batch.js +79 -0
  48. package/core/cache-state.d.ts +79 -0
  49. package/core/cache-state.d.ts.map +1 -0
  50. package/core/cache-state.js +87 -0
  51. package/core/client.d.ts +577 -0
  52. package/core/client.d.ts.map +1 -0
  53. package/core/client.js +1664 -0
  54. package/core/compatibility.d.ts +18 -0
  55. package/core/compatibility.d.ts.map +1 -0
  56. package/core/compatibility.js +245 -0
  57. package/core/errors.d.ts +107 -0
  58. package/core/errors.d.ts.map +1 -0
  59. package/core/errors.js +156 -0
  60. package/core/esri-geojson.d.ts +66 -0
  61. package/core/esri-geojson.d.ts.map +1 -0
  62. package/core/esri-geojson.js +363 -0
  63. package/core/geoservices.d.ts +34 -0
  64. package/core/geoservices.d.ts.map +1 -0
  65. package/core/geoservices.js +434 -0
  66. package/core/grpc-adapter.d.ts +23 -0
  67. package/core/grpc-adapter.d.ts.map +1 -0
  68. package/core/grpc-adapter.js +688 -0
  69. package/core/odata.d.ts +347 -0
  70. package/core/odata.d.ts.map +1 -0
  71. package/core/odata.js +913 -0
  72. package/core/ogc-conformance.d.ts +35 -0
  73. package/core/ogc-conformance.d.ts.map +1 -0
  74. package/core/ogc-conformance.js +134 -0
  75. package/core/ogc-features.d.ts +23 -0
  76. package/core/ogc-features.d.ts.map +1 -0
  77. package/core/ogc-features.js +114 -0
  78. package/core/ogc-maps.d.ts +49 -0
  79. package/core/ogc-maps.d.ts.map +1 -0
  80. package/core/ogc-maps.js +126 -0
  81. package/core/ogc-processes.d.ts +118 -0
  82. package/core/ogc-processes.d.ts.map +1 -0
  83. package/core/ogc-processes.js +423 -0
  84. package/core/ogc-records.d.ts +75 -0
  85. package/core/ogc-records.d.ts.map +1 -0
  86. package/core/ogc-records.js +251 -0
  87. package/core/ogc-tiles.d.ts +125 -0
  88. package/core/ogc-tiles.d.ts.map +1 -0
  89. package/core/ogc-tiles.js +183 -0
  90. package/core/path-utils.d.ts +69 -0
  91. package/core/path-utils.d.ts.map +1 -0
  92. package/core/path-utils.js +123 -0
  93. package/core/pbf-decoder.d.ts +39 -0
  94. package/core/pbf-decoder.d.ts.map +1 -0
  95. package/core/pbf-decoder.js +702 -0
  96. package/core/process-runner.d.ts +104 -0
  97. package/core/process-runner.d.ts.map +1 -0
  98. package/core/process-runner.js +315 -0
  99. package/core/protocol-transport.d.ts +76 -0
  100. package/core/protocol-transport.d.ts.map +1 -0
  101. package/core/protocol-transport.js +15 -0
  102. package/core/query-builder.d.ts +261 -0
  103. package/core/query-builder.d.ts.map +1 -0
  104. package/core/query-builder.js +538 -0
  105. package/core/request-pipeline.d.ts +88 -0
  106. package/core/request-pipeline.d.ts.map +1 -0
  107. package/core/request-pipeline.js +273 -0
  108. package/core/spatial-filter.d.ts +93 -0
  109. package/core/spatial-filter.d.ts.map +1 -0
  110. package/core/spatial-filter.js +153 -0
  111. package/core/stac.d.ts +55 -0
  112. package/core/stac.d.ts.map +1 -0
  113. package/core/stac.js +229 -0
  114. package/core/surfaces.d.ts +527 -0
  115. package/core/surfaces.d.ts.map +1 -0
  116. package/core/surfaces.js +1917 -0
  117. package/core/terrain-elevation.d.ts +114 -0
  118. package/core/terrain-elevation.d.ts.map +1 -0
  119. package/core/terrain-elevation.js +150 -0
  120. package/core/types.d.ts +1355 -0
  121. package/core/types.d.ts.map +1 -0
  122. package/core/types.js +13 -0
  123. package/core/wfs-capabilities.d.ts +143 -0
  124. package/core/wfs-capabilities.d.ts.map +1 -0
  125. package/core/wfs-capabilities.js +478 -0
  126. package/core/wfs-filter.d.ts +118 -0
  127. package/core/wfs-filter.d.ts.map +1 -0
  128. package/core/wfs-filter.js +634 -0
  129. package/core/wfs.d.ts +219 -0
  130. package/core/wfs.d.ts.map +1 -0
  131. package/core/wfs.js +406 -0
  132. package/core/wire-shared.d.ts +32 -0
  133. package/core/wire-shared.d.ts.map +1 -0
  134. package/core/wire-shared.js +76 -0
  135. package/core/wms-axis.d.ts +36 -0
  136. package/core/wms-axis.d.ts.map +1 -0
  137. package/core/wms-axis.js +73 -0
  138. package/core/wms-capabilities.d.ts +110 -0
  139. package/core/wms-capabilities.d.ts.map +1 -0
  140. package/core/wms-capabilities.js +559 -0
  141. package/core/wms-types.d.ts +128 -0
  142. package/core/wms-types.d.ts.map +1 -0
  143. package/core/wms-types.js +32 -0
  144. package/core/wms.d.ts +144 -0
  145. package/core/wms.d.ts.map +1 -0
  146. package/core/wms.js +338 -0
  147. package/core/wmts-capabilities.d.ts +82 -0
  148. package/core/wmts-capabilities.d.ts.map +1 -0
  149. package/core/wmts-capabilities.js +402 -0
  150. package/core/wmts.d.ts +103 -0
  151. package/core/wmts.d.ts.map +1 -0
  152. package/core/wmts.js +332 -0
  153. package/core/xml-text.d.ts +20 -0
  154. package/core/xml-text.d.ts.map +1 -0
  155. package/core/xml-text.js +85 -0
  156. package/esri-compat/basemap-gallery.d.ts +38 -0
  157. package/esri-compat/basemap-gallery.d.ts.map +1 -0
  158. package/esri-compat/basemap-gallery.js +159 -0
  159. package/esri-compat/basemap-layer-list.d.ts +35 -0
  160. package/esri-compat/basemap-layer-list.d.ts.map +1 -0
  161. package/esri-compat/basemap-layer-list.js +139 -0
  162. package/esri-compat/basemap.d.ts +32 -0
  163. package/esri-compat/basemap.d.ts.map +1 -0
  164. package/esri-compat/basemap.js +84 -0
  165. package/esri-compat/bookmarks.d.ts +36 -0
  166. package/esri-compat/bookmarks.d.ts.map +1 -0
  167. package/esri-compat/bookmarks.js +108 -0
  168. package/esri-compat/class-breaks-renderer.d.ts +50 -0
  169. package/esri-compat/class-breaks-renderer.d.ts.map +1 -0
  170. package/esri-compat/class-breaks-renderer.js +164 -0
  171. package/esri-compat/color.d.ts +22 -0
  172. package/esri-compat/color.d.ts.map +1 -0
  173. package/esri-compat/color.js +106 -0
  174. package/esri-compat/controls.d.ts +174 -0
  175. package/esri-compat/controls.d.ts.map +1 -0
  176. package/esri-compat/controls.js +479 -0
  177. package/esri-compat/coordinate-conversion.d.ts +41 -0
  178. package/esri-compat/coordinate-conversion.d.ts.map +1 -0
  179. package/esri-compat/coordinate-conversion.js +125 -0
  180. package/esri-compat/directions.d.ts +45 -0
  181. package/esri-compat/directions.d.ts.map +1 -0
  182. package/esri-compat/directions.js +123 -0
  183. package/esri-compat/editor.d.ts +47 -0
  184. package/esri-compat/editor.d.ts.map +1 -0
  185. package/esri-compat/editor.js +170 -0
  186. package/esri-compat/esri-config.d.ts +16 -0
  187. package/esri-compat/esri-config.d.ts.map +1 -0
  188. package/esri-compat/esri-config.js +23 -0
  189. package/esri-compat/esri-request.d.ts +17 -0
  190. package/esri-compat/esri-request.d.ts.map +1 -0
  191. package/esri-compat/esri-request.js +127 -0
  192. package/esri-compat/event-bus.d.ts +106 -0
  193. package/esri-compat/event-bus.d.ts.map +1 -0
  194. package/esri-compat/event-bus.js +90 -0
  195. package/esri-compat/expand.d.ts +36 -0
  196. package/esri-compat/expand.d.ts.map +1 -0
  197. package/esri-compat/expand.js +97 -0
  198. package/esri-compat/extent.d.ts +43 -0
  199. package/esri-compat/extent.d.ts.map +1 -0
  200. package/esri-compat/extent.js +140 -0
  201. package/esri-compat/feature-filter.d.ts +55 -0
  202. package/esri-compat/feature-filter.d.ts.map +1 -0
  203. package/esri-compat/feature-filter.js +109 -0
  204. package/esri-compat/feature-form.d.ts +64 -0
  205. package/esri-compat/feature-form.d.ts.map +1 -0
  206. package/esri-compat/feature-form.js +137 -0
  207. package/esri-compat/feature-layer.d.ts +173 -0
  208. package/esri-compat/feature-layer.d.ts.map +1 -0
  209. package/esri-compat/feature-layer.js +661 -0
  210. package/esri-compat/feature-set.d.ts +32 -0
  211. package/esri-compat/feature-set.d.ts.map +1 -0
  212. package/esri-compat/feature-set.js +80 -0
  213. package/esri-compat/feature-table.d.ts +141 -0
  214. package/esri-compat/feature-table.d.ts.map +1 -0
  215. package/esri-compat/feature-table.js +500 -0
  216. package/esri-compat/feature-templates.d.ts +41 -0
  217. package/esri-compat/feature-templates.d.ts.map +1 -0
  218. package/esri-compat/feature-templates.js +97 -0
  219. package/esri-compat/feature.d.ts +33 -0
  220. package/esri-compat/feature.d.ts.map +1 -0
  221. package/esri-compat/feature.js +83 -0
  222. package/esri-compat/geojson-layer.d.ts +86 -0
  223. package/esri-compat/geojson-layer.d.ts.map +1 -0
  224. package/esri-compat/geojson-layer.js +191 -0
  225. package/esri-compat/geometry-engine.d.ts +107 -0
  226. package/esri-compat/geometry-engine.d.ts.map +1 -0
  227. package/esri-compat/geometry-engine.js +266 -0
  228. package/esri-compat/graphic.d.ts +46 -0
  229. package/esri-compat/graphic.d.ts.map +1 -0
  230. package/esri-compat/graphic.js +103 -0
  231. package/esri-compat/graphics-layer.d.ts +49 -0
  232. package/esri-compat/graphics-layer.d.ts.map +1 -0
  233. package/esri-compat/graphics-layer.js +185 -0
  234. package/esri-compat/group-layer.d.ts +48 -0
  235. package/esri-compat/group-layer.d.ts.map +1 -0
  236. package/esri-compat/group-layer.js +212 -0
  237. package/esri-compat/identify.d.ts +71 -0
  238. package/esri-compat/identify.d.ts.map +1 -0
  239. package/esri-compat/identify.js +299 -0
  240. package/esri-compat/identity-manager.d.ts +31 -0
  241. package/esri-compat/identity-manager.d.ts.map +1 -0
  242. package/esri-compat/identity-manager.js +115 -0
  243. package/esri-compat/imagery-layer.d.ts +78 -0
  244. package/esri-compat/imagery-layer.d.ts.map +1 -0
  245. package/esri-compat/imagery-layer.js +167 -0
  246. package/esri-compat/label-class.d.ts +31 -0
  247. package/esri-compat/label-class.d.ts.map +1 -0
  248. package/esri-compat/label-class.js +102 -0
  249. package/esri-compat/layer-list.d.ts +77 -0
  250. package/esri-compat/layer-list.d.ts.map +1 -0
  251. package/esri-compat/layer-list.js +342 -0
  252. package/esri-compat/legend.d.ts +53 -0
  253. package/esri-compat/legend.d.ts.map +1 -0
  254. package/esri-compat/legend.js +254 -0
  255. package/esri-compat/map-image-layer.d.ts +162 -0
  256. package/esri-compat/map-image-layer.d.ts.map +1 -0
  257. package/esri-compat/map-image-layer.js +709 -0
  258. package/esri-compat/map-view.d.ts +323 -0
  259. package/esri-compat/map-view.d.ts.map +1 -0
  260. package/esri-compat/map-view.js +1175 -0
  261. package/esri-compat/map.d.ts +47 -0
  262. package/esri-compat/map.d.ts.map +1 -0
  263. package/esri-compat/map.js +209 -0
  264. package/esri-compat/measurement-2d.d.ts +61 -0
  265. package/esri-compat/measurement-2d.d.ts.map +1 -0
  266. package/esri-compat/measurement-2d.js +184 -0
  267. package/esri-compat/measurement.d.ts +45 -0
  268. package/esri-compat/measurement.d.ts.map +1 -0
  269. package/esri-compat/measurement.js +193 -0
  270. package/esri-compat/oauth-info.d.ts +35 -0
  271. package/esri-compat/oauth-info.d.ts.map +1 -0
  272. package/esri-compat/oauth-info.js +115 -0
  273. package/esri-compat/picture-marker-symbol.d.ts +35 -0
  274. package/esri-compat/picture-marker-symbol.d.ts.map +1 -0
  275. package/esri-compat/picture-marker-symbol.js +126 -0
  276. package/esri-compat/point.d.ts +31 -0
  277. package/esri-compat/point.d.ts.map +1 -0
  278. package/esri-compat/point.js +102 -0
  279. package/esri-compat/polygon.d.ts +31 -0
  280. package/esri-compat/polygon.d.ts.map +1 -0
  281. package/esri-compat/polygon.js +107 -0
  282. package/esri-compat/polyline.d.ts +31 -0
  283. package/esri-compat/polyline.d.ts.map +1 -0
  284. package/esri-compat/polyline.js +107 -0
  285. package/esri-compat/popup-template.d.ts +55 -0
  286. package/esri-compat/popup-template.d.ts.map +1 -0
  287. package/esri-compat/popup-template.js +128 -0
  288. package/esri-compat/popup.d.ts +54 -0
  289. package/esri-compat/popup.d.ts.map +1 -0
  290. package/esri-compat/popup.js +312 -0
  291. package/esri-compat/print.d.ts +47 -0
  292. package/esri-compat/print.d.ts.map +1 -0
  293. package/esri-compat/print.js +105 -0
  294. package/esri-compat/query.d.ts +58 -0
  295. package/esri-compat/query.d.ts.map +1 -0
  296. package/esri-compat/query.js +128 -0
  297. package/esri-compat/reactive-utils.d.ts +24 -0
  298. package/esri-compat/reactive-utils.d.ts.map +1 -0
  299. package/esri-compat/reactive-utils.js +193 -0
  300. package/esri-compat/request.d.ts +41 -0
  301. package/esri-compat/request.d.ts.map +1 -0
  302. package/esri-compat/request.js +253 -0
  303. package/esri-compat/route-layer.d.ts +59 -0
  304. package/esri-compat/route-layer.d.ts.map +1 -0
  305. package/esri-compat/route-layer.js +223 -0
  306. package/esri-compat/route-task.d.ts +88 -0
  307. package/esri-compat/route-task.d.ts.map +1 -0
  308. package/esri-compat/route-task.js +225 -0
  309. package/esri-compat/scene-view.d.ts +41 -0
  310. package/esri-compat/scene-view.d.ts.map +1 -0
  311. package/esri-compat/scene-view.js +68 -0
  312. package/esri-compat/search.d.ts +112 -0
  313. package/esri-compat/search.d.ts.map +1 -0
  314. package/esri-compat/search.js +820 -0
  315. package/esri-compat/simple-fill-symbol.d.ts +27 -0
  316. package/esri-compat/simple-fill-symbol.d.ts.map +1 -0
  317. package/esri-compat/simple-fill-symbol.js +85 -0
  318. package/esri-compat/simple-line-symbol.d.ts +27 -0
  319. package/esri-compat/simple-line-symbol.d.ts.map +1 -0
  320. package/esri-compat/simple-line-symbol.js +85 -0
  321. package/esri-compat/simple-marker-symbol.d.ts +29 -0
  322. package/esri-compat/simple-marker-symbol.d.ts.map +1 -0
  323. package/esri-compat/simple-marker-symbol.js +92 -0
  324. package/esri-compat/simple-renderer.d.ts +29 -0
  325. package/esri-compat/simple-renderer.d.ts.map +1 -0
  326. package/esri-compat/simple-renderer.js +92 -0
  327. package/esri-compat/sketch.d.ts +64 -0
  328. package/esri-compat/sketch.d.ts.map +1 -0
  329. package/esri-compat/sketch.js +227 -0
  330. package/esri-compat/spatial-reference.d.ts +31 -0
  331. package/esri-compat/spatial-reference.d.ts.map +1 -0
  332. package/esri-compat/spatial-reference.js +102 -0
  333. package/esri-compat/swipe.d.ts +34 -0
  334. package/esri-compat/swipe.d.ts.map +1 -0
  335. package/esri-compat/swipe.js +93 -0
  336. package/esri-compat/table-list.d.ts +36 -0
  337. package/esri-compat/table-list.d.ts.map +1 -0
  338. package/esri-compat/table-list.js +126 -0
  339. package/esri-compat/text-symbol.d.ts +37 -0
  340. package/esri-compat/text-symbol.d.ts.map +1 -0
  341. package/esri-compat/text-symbol.js +123 -0
  342. package/esri-compat/tile-layer.d.ts +52 -0
  343. package/esri-compat/tile-layer.d.ts.map +1 -0
  344. package/esri-compat/tile-layer.js +168 -0
  345. package/esri-compat/time-slider.d.ts +70 -0
  346. package/esri-compat/time-slider.d.ts.map +1 -0
  347. package/esri-compat/time-slider.js +208 -0
  348. package/esri-compat/track.d.ts +51 -0
  349. package/esri-compat/track.d.ts.map +1 -0
  350. package/esri-compat/track.js +174 -0
  351. package/esri-compat/unique-value-renderer.d.ts +41 -0
  352. package/esri-compat/unique-value-renderer.d.ts.map +1 -0
  353. package/esri-compat/unique-value-renderer.js +127 -0
  354. package/esri-compat/url.d.ts +13 -0
  355. package/esri-compat/url.d.ts.map +1 -0
  356. package/esri-compat/url.js +68 -0
  357. package/esri-compat/vector-tile-layer.d.ts +47 -0
  358. package/esri-compat/vector-tile-layer.d.ts.map +1 -0
  359. package/esri-compat/vector-tile-layer.js +130 -0
  360. package/esri-compat/web-map.d.ts +14 -0
  361. package/esri-compat/web-map.d.ts.map +1 -0
  362. package/esri-compat/web-map.js +24 -0
  363. package/esri-compat/wfs-layer.d.ts +64 -0
  364. package/esri-compat/wfs-layer.d.ts.map +1 -0
  365. package/esri-compat/wfs-layer.js +145 -0
  366. package/esri-compat/wms-layer.d.ts +76 -0
  367. package/esri-compat/wms-layer.d.ts.map +1 -0
  368. package/esri-compat/wms-layer.js +157 -0
  369. package/exploration/context.d.ts +25 -0
  370. package/exploration/context.d.ts.map +1 -0
  371. package/exploration/context.js +396 -0
  372. package/exploration/index.d.ts +27 -0
  373. package/exploration/index.d.ts.map +1 -0
  374. package/exploration/index.js +24 -0
  375. package/exploration/presets.d.ts +27 -0
  376. package/exploration/presets.d.ts.map +1 -0
  377. package/exploration/presets.js +114 -0
  378. package/exploration/reducer.d.ts +23 -0
  379. package/exploration/reducer.d.ts.map +1 -0
  380. package/exploration/reducer.js +332 -0
  381. package/exploration/selection.d.ts +25 -0
  382. package/exploration/selection.d.ts.map +1 -0
  383. package/exploration/selection.js +34 -0
  384. package/exploration/selectors.d.ts +56 -0
  385. package/exploration/selectors.d.ts.map +1 -0
  386. package/exploration/selectors.js +110 -0
  387. package/exploration/types.d.ts +294 -0
  388. package/exploration/types.d.ts.map +1 -0
  389. package/exploration/types.js +33 -0
  390. package/expr/expression.d.ts +457 -0
  391. package/expr/expression.d.ts.map +1 -0
  392. package/expr/expression.js +663 -0
  393. package/expr/index.d.ts +32 -0
  394. package/expr/index.d.ts.map +1 -0
  395. package/expr/index.js +30 -0
  396. package/gen/honua/v1/feature_service_pb.d.ts +918 -0
  397. package/gen/honua/v1/feature_service_pb.d.ts.map +1 -0
  398. package/gen/honua/v1/feature_service_pb.js +375 -0
  399. package/generated-app/errors.d.ts +41 -0
  400. package/generated-app/errors.d.ts.map +1 -0
  401. package/generated-app/errors.js +41 -0
  402. package/generated-app/index.d.ts +17 -0
  403. package/generated-app/index.d.ts.map +1 -0
  404. package/generated-app/index.js +12 -0
  405. package/generated-app/manifest.d.ts +183 -0
  406. package/generated-app/manifest.d.ts.map +1 -0
  407. package/generated-app/manifest.js +14 -0
  408. package/generated-app/projection.d.ts +44 -0
  409. package/generated-app/projection.d.ts.map +1 -0
  410. package/generated-app/projection.js +234 -0
  411. package/generated-app/runtime.d.ts +215 -0
  412. package/generated-app/runtime.d.ts.map +1 -0
  413. package/generated-app/runtime.js +1196 -0
  414. package/interactions/detail-model.d.ts +54 -0
  415. package/interactions/detail-model.d.ts.map +1 -0
  416. package/interactions/detail-model.js +105 -0
  417. package/interactions/exploration-bindings.d.ts +120 -0
  418. package/interactions/exploration-bindings.d.ts.map +1 -0
  419. package/interactions/exploration-bindings.js +218 -0
  420. package/interactions/feature-state.d.ts +144 -0
  421. package/interactions/feature-state.d.ts.map +1 -0
  422. package/interactions/feature-state.js +169 -0
  423. package/interactions/hit-test.d.ts +112 -0
  424. package/interactions/hit-test.d.ts.map +1 -0
  425. package/interactions/hit-test.js +291 -0
  426. package/interactions/index.d.ts +31 -0
  427. package/interactions/index.d.ts.map +1 -0
  428. package/interactions/index.js +24 -0
  429. package/interactions/share-state.d.ts +36 -0
  430. package/interactions/share-state.d.ts.map +1 -0
  431. package/interactions/share-state.js +123 -0
  432. package/map/feature-service-adapter.d.ts +138 -0
  433. package/map/feature-service-adapter.d.ts.map +1 -0
  434. package/map/feature-service-adapter.js +279 -0
  435. package/map/honua-map.d.ts +203 -0
  436. package/map/honua-map.d.ts.map +1 -0
  437. package/map/honua-map.js +347 -0
  438. package/map/index.d.ts +27 -0
  439. package/map/index.d.ts.map +1 -0
  440. package/map/index.js +22 -0
  441. package/map/maplibre-target.d.ts +103 -0
  442. package/map/maplibre-target.d.ts.map +1 -0
  443. package/map/maplibre-target.js +164 -0
  444. package/map/webmap-maplibre.d.ts +60 -0
  445. package/map/webmap-maplibre.d.ts.map +1 -0
  446. package/map/webmap-maplibre.js +212 -0
  447. package/package.json +37 -0
  448. package/react/context.d.ts +32 -0
  449. package/react/context.d.ts.map +1 -0
  450. package/react/context.js +19 -0
  451. package/react/honua-layer.d.ts +35 -0
  452. package/react/honua-layer.d.ts.map +1 -0
  453. package/react/honua-layer.js +47 -0
  454. package/react/honua-map.d.ts +83 -0
  455. package/react/honua-map.d.ts.map +1 -0
  456. package/react/honua-map.js +109 -0
  457. package/react/honua-popup.d.ts +26 -0
  458. package/react/honua-popup.d.ts.map +1 -0
  459. package/react/honua-popup.js +34 -0
  460. package/react/hooks.d.ts +127 -0
  461. package/react/hooks.d.ts.map +1 -0
  462. package/react/hooks.js +206 -0
  463. package/react/index.d.ts +42 -0
  464. package/react/index.d.ts.map +1 -0
  465. package/react/index.js +34 -0
  466. package/react/provider.d.ts +36 -0
  467. package/react/provider.d.ts.map +1 -0
  468. package/react/provider.js +35 -0
  469. package/react/query-cache.d.ts +76 -0
  470. package/react/query-cache.d.ts.map +1 -0
  471. package/react/query-cache.js +188 -0
  472. package/runtime/diff.d.ts +31 -0
  473. package/runtime/diff.d.ts.map +1 -0
  474. package/runtime/diff.js +143 -0
  475. package/runtime/errors.d.ts +40 -0
  476. package/runtime/errors.d.ts.map +1 -0
  477. package/runtime/errors.js +35 -0
  478. package/runtime/index.d.ts +59 -0
  479. package/runtime/index.d.ts.map +1 -0
  480. package/runtime/index.js +43 -0
  481. package/runtime/legend.d.ts +23 -0
  482. package/runtime/legend.d.ts.map +1 -0
  483. package/runtime/legend.js +38 -0
  484. package/runtime/load-package.d.ts +125 -0
  485. package/runtime/load-package.d.ts.map +1 -0
  486. package/runtime/load-package.js +264 -0
  487. package/runtime/map-package-fetch.d.ts +107 -0
  488. package/runtime/map-package-fetch.d.ts.map +1 -0
  489. package/runtime/map-package-fetch.js +336 -0
  490. package/runtime/map-package-realtime.d.ts +169 -0
  491. package/runtime/map-package-realtime.d.ts.map +1 -0
  492. package/runtime/map-package-realtime.js +350 -0
  493. package/runtime/map-package-validation.d.ts +43 -0
  494. package/runtime/map-package-validation.d.ts.map +1 -0
  495. package/runtime/map-package-validation.js +269 -0
  496. package/runtime/map-package-watch.d.ts +78 -0
  497. package/runtime/map-package-watch.d.ts.map +1 -0
  498. package/runtime/map-package-watch.js +695 -0
  499. package/runtime/map-package.d.ts +167 -0
  500. package/runtime/map-package.d.ts.map +1 -0
  501. package/runtime/map-package.js +15 -0
  502. package/runtime/ogc-styles.d.ts +110 -0
  503. package/runtime/ogc-styles.d.ts.map +1 -0
  504. package/runtime/ogc-styles.js +195 -0
  505. package/runtime/popups.d.ts +62 -0
  506. package/runtime/popups.d.ts.map +1 -0
  507. package/runtime/popups.js +95 -0
  508. package/runtime/query-tiles.d.ts +217 -0
  509. package/runtime/query-tiles.d.ts.map +1 -0
  510. package/runtime/query-tiles.js +801 -0
  511. package/runtime/runtime.d.ts +243 -0
  512. package/runtime/runtime.d.ts.map +1 -0
  513. package/runtime/runtime.js +1147 -0
  514. package/runtime/source-bridge.d.ts +99 -0
  515. package/runtime/source-bridge.d.ts.map +1 -0
  516. package/runtime/source-bridge.js +401 -0
  517. package/runtime/style-compose.d.ts +46 -0
  518. package/runtime/style-compose.d.ts.map +1 -0
  519. package/runtime/style-compose.js +144 -0
  520. package/runtime/style-interactions.d.ts +141 -0
  521. package/runtime/style-interactions.d.ts.map +1 -0
  522. package/runtime/style-interactions.js +574 -0
  523. package/runtime/style-spec-validation.d.ts +23 -0
  524. package/runtime/style-spec-validation.d.ts.map +1 -0
  525. package/runtime/style-spec-validation.js +357 -0
  526. package/studio/capability-manifest.d.ts +285 -0
  527. package/studio/capability-manifest.d.ts.map +1 -0
  528. package/studio/capability-manifest.js +59 -0
  529. package/studio/chart-spec.d.ts +85 -0
  530. package/studio/chart-spec.d.ts.map +1 -0
  531. package/studio/chart-spec.js +106 -0
  532. package/studio/index.d.ts +34 -0
  533. package/studio/index.d.ts.map +1 -0
  534. package/studio/index.js +27 -0
  535. package/studio/provenance.d.ts +131 -0
  536. package/studio/provenance.d.ts.map +1 -0
  537. package/studio/provenance.js +39 -0
  538. package/studio/publish.d.ts +64 -0
  539. package/studio/publish.d.ts.map +1 -0
  540. package/studio/publish.js +13 -0
  541. package/studio/types.d.ts +278 -0
  542. package/studio/types.d.ts.map +1 -0
  543. package/studio/types.js +71 -0
  544. package/studio/validate.d.ts +45 -0
  545. package/studio/validate.d.ts.map +1 -0
  546. package/studio/validate.js +255 -0
  547. package/studio/validation.d.ts +112 -0
  548. package/studio/validation.d.ts.map +1 -0
  549. package/studio/validation.js +42 -0
  550. package/style/factory.d.ts +30 -0
  551. package/style/factory.d.ts.map +1 -0
  552. package/style/factory.js +61 -0
  553. package/style/index.d.ts +25 -0
  554. package/style/index.d.ts.map +1 -0
  555. package/style/index.js +24 -0
  556. package/style/specification.d.ts +203 -0
  557. package/style/specification.d.ts.map +1 -0
  558. package/style/specification.js +178 -0
  559. package/style/wms-wmts-resolvers.d.ts +38 -0
  560. package/style/wms-wmts-resolvers.d.ts.map +1 -0
  561. package/style/wms-wmts-resolvers.js +92 -0
  562. package/webmap/contract.d.ts +28 -0
  563. package/webmap/contract.d.ts.map +1 -0
  564. package/webmap/contract.js +130 -0
  565. package/webmap/convert-basemap.d.ts +17 -0
  566. package/webmap/convert-basemap.d.ts.map +1 -0
  567. package/webmap/convert-basemap.js +78 -0
  568. package/webmap/convert-extent.d.ts +17 -0
  569. package/webmap/convert-extent.d.ts.map +1 -0
  570. package/webmap/convert-extent.js +114 -0
  571. package/webmap/convert-label.d.ts +13 -0
  572. package/webmap/convert-label.d.ts.map +1 -0
  573. package/webmap/convert-label.js +125 -0
  574. package/webmap/convert-layer.d.ts +22 -0
  575. package/webmap/convert-layer.d.ts.map +1 -0
  576. package/webmap/convert-layer.js +181 -0
  577. package/webmap/convert-popup.d.ts +34 -0
  578. package/webmap/convert-popup.d.ts.map +1 -0
  579. package/webmap/convert-popup.js +68 -0
  580. package/webmap/convert-renderer.d.ts +17 -0
  581. package/webmap/convert-renderer.d.ts.map +1 -0
  582. package/webmap/convert-renderer.js +161 -0
  583. package/webmap/convert-symbol.d.ts +17 -0
  584. package/webmap/convert-symbol.d.ts.map +1 -0
  585. package/webmap/convert-symbol.js +122 -0
  586. package/webmap/index.d.ts +44 -0
  587. package/webmap/index.d.ts.map +1 -0
  588. package/webmap/index.js +33 -0
  589. package/webmap/parse.d.ts +25 -0
  590. package/webmap/parse.d.ts.map +1 -0
  591. package/webmap/parse.js +83 -0
  592. package/webmap/types.d.ts +264 -0
  593. package/webmap/types.d.ts.map +1 -0
  594. package/webmap/types.js +13 -0
  595. package/webmap/warnings.d.ts +30 -0
  596. package/webmap/warnings.d.ts.map +1 -0
  597. package/webmap/warnings.js +37 -0
package/core/odata.js ADDED
@@ -0,0 +1,913 @@
1
+ /**
2
+ * OData v4 entity-set adapter. Wraps an OData entity set behind the shared
3
+ * `Source<T>` contract surface in `src/contract/source.ts`.
4
+ *
5
+ * Library posture is recorded in
6
+ * `docs/decisions/odata-library-selection.md`: the canonical surface is a
7
+ * thin URL/JSON serializer over `HonuaClient.pipelineFetch` /
8
+ * `pipelineRequestJson` (rather than the GeoServices-shaped
9
+ * `request()` helper, which would inject `f=json` and trip Honua
10
+ * Server's OData validators) so that the auth / retry / telemetry
11
+ * pipeline stays centralized. The dialect-specific `$batch` / `$apply`
12
+ * / `$search` / `$deltatoken` operations live behind the typed escape
13
+ * hatch returned by `Source.protocol("odata")`.
14
+ *
15
+ * @module
16
+ */
17
+ import { createHonuaCacheState, honuaCacheValidatorFromHeaders, honuaMetadataRequestHeaders, isHonuaCacheEntryFresh, normalizeHonuaMetadataRequestOptions, withHonuaCacheState, withoutHonuaCacheState, } from "./cache-state.js";
18
+ import { trimTrailingSlashes } from "./path-utils.js";
19
+ // ── Runtime class ─────────────────────────────────────────────
20
+ /**
21
+ * Runtime handle for one OData entity set. Returned by
22
+ * `Source.protocol("odata")` so callers can reach the dialect-specific
23
+ * surface (`$batch`, `$apply`, `$search`, `$deltatoken`) without leaking
24
+ * OData-shaped types into the canonical `Source` API.
25
+ */
26
+ export class HonuaOdataEntitySet {
27
+ client;
28
+ entitySet;
29
+ /**
30
+ * Unqualified entity-set name used as the CSDL metadata-lookup key.
31
+ * Equals `entitySet` for direct paths like `"Parcels"`. For
32
+ * layer-scoped navigation paths like `"Layers(1)/Features"` it is the
33
+ * navigation suffix `"Features"` so capability and schema lookups
34
+ * still find the entry the server emits in `<EntitySet Name="…">`.
35
+ */
36
+ entitySetName;
37
+ basePath;
38
+ cachedMetadata;
39
+ inflightMetadata;
40
+ constructor(options) {
41
+ this.client = options.client;
42
+ this.entitySet = options.entitySet;
43
+ this.entitySetName = extractEntitySetName(options.entitySet);
44
+ this.basePath = normalizeBasePath(options.basePath);
45
+ }
46
+ /**
47
+ * Fetch (and cache) the `$metadata` document. The cache is per
48
+ * `HonuaOdataEntitySet` instance so callers can pin a metadata snapshot
49
+ * to a long-lived `Source` without re-parsing on every request.
50
+ */
51
+ async metadata(options = {}) {
52
+ const metadataOptions = normalizeHonuaMetadataRequestOptions(options);
53
+ const bypass = metadataOptions.cache === "bypass";
54
+ const now = Date.now();
55
+ const freshCachedMetadata = this.cachedMetadata
56
+ ? isHonuaCacheEntryFresh(this.cachedMetadata.cachedAtMs, now, metadataOptions.ttlMs)
57
+ : false;
58
+ if (!bypass && !metadataOptions.refresh && this.cachedMetadata && freshCachedMetadata) {
59
+ return withOdataMetadataCacheState(this.cachedMetadata, "hit", {
60
+ now,
61
+ ttlMs: metadataOptions.ttlMs,
62
+ staleIfErrorMs: metadataOptions.staleIfErrorMs,
63
+ });
64
+ }
65
+ if (!bypass && !metadataOptions.refresh && this.inflightMetadata)
66
+ return this.inflightMetadata;
67
+ const previous = this.cachedMetadata;
68
+ const promise = this.fetchMetadata(metadataOptions, previous).then((entry) => {
69
+ if (!bypass) {
70
+ this.cachedMetadata = entry;
71
+ }
72
+ const status = bypass ? "bypass" : entry.status;
73
+ return withOdataMetadataCacheState(entry, status, {
74
+ now: Date.now(),
75
+ ttlMs: metadataOptions.ttlMs,
76
+ staleIfErrorMs: metadataOptions.staleIfErrorMs,
77
+ ...(status === "refreshed" ? { revalidatedAt: new Date().toISOString() } : {}),
78
+ });
79
+ });
80
+ const staleFallback = (error) => {
81
+ if (!bypass && metadataOptions.staleIfError && previous) {
82
+ return withOdataMetadataCacheState(previous, "stale", {
83
+ now: Date.now(),
84
+ ttlMs: metadataOptions.ttlMs,
85
+ staleIfErrorMs: metadataOptions.staleIfErrorMs,
86
+ refreshErrorId: odataMetadataRefreshErrorId(error),
87
+ });
88
+ }
89
+ throw error;
90
+ };
91
+ if (!bypass && !metadataOptions.refresh) {
92
+ const trackedPromise = promise.catch(staleFallback).finally(() => {
93
+ if (this.inflightMetadata === trackedPromise)
94
+ this.inflightMetadata = undefined;
95
+ });
96
+ this.inflightMetadata = trackedPromise;
97
+ return trackedPromise;
98
+ }
99
+ try {
100
+ return await promise;
101
+ }
102
+ catch (error) {
103
+ return staleFallback(error);
104
+ }
105
+ }
106
+ /** Fetch one page of the entity set. */
107
+ async query(params = {}) {
108
+ const path = this.entitySetPath();
109
+ const query = serializeQueryParams(params);
110
+ const body = await this.requestJson("GET", path, query, params.signal);
111
+ return {
112
+ rows: body.value ?? [],
113
+ ...(typeof body["@odata.count"] === "number" ? { totalCount: body["@odata.count"] } : {}),
114
+ ...(typeof body["@odata.nextLink"] === "string" ? { nextLink: body["@odata.nextLink"] } : {}),
115
+ ...(typeof body["@odata.deltaLink"] === "string" ? { deltaLink: body["@odata.deltaLink"] } : {}),
116
+ };
117
+ }
118
+ /**
119
+ * Drain server-driven `@odata.nextLink` pages. When `params.top` is
120
+ * supplied it is treated as a hard cap on collected rows. Callers that
121
+ * want a lookahead row to detect truncation must add it themselves; the
122
+ * canonical `Source.queryAll` path does this with `top = limit + 1`,
123
+ * while ids-only projections enforce their own result cap.
124
+ * Returning `top + 1` here would double-count for callers that already
125
+ * added the lookahead, so this helper respects `top` exactly.
126
+ */
127
+ async queryAll(params = {}) {
128
+ const cap = typeof params.top === "number" && params.top >= 0 ? params.top : undefined;
129
+ const collected = [];
130
+ let totalCount;
131
+ let next;
132
+ let firstPageParams = params;
133
+ while (true) {
134
+ const page = next
135
+ ? await this.followNextLink(next, params.signal)
136
+ : await this.query(firstPageParams ?? {});
137
+ firstPageParams = undefined;
138
+ if (totalCount === undefined && typeof page.totalCount === "number") {
139
+ totalCount = page.totalCount;
140
+ }
141
+ for (const row of page.rows) {
142
+ if (cap !== undefined && collected.length >= cap)
143
+ break;
144
+ collected.push(row);
145
+ if (cap !== undefined && collected.length >= cap)
146
+ break;
147
+ }
148
+ if (cap !== undefined && collected.length >= cap)
149
+ break;
150
+ if (!page.nextLink)
151
+ break;
152
+ next = page.nextLink;
153
+ }
154
+ return totalCount === undefined ? { rows: collected } : { rows: collected, totalCount };
155
+ }
156
+ /** Yield one page per server response. Used by `Source.stream()`. */
157
+ async *queryStream(params = {}) {
158
+ let page = await this.query(params);
159
+ yield page;
160
+ while (page.nextLink) {
161
+ page = await this.followNextLink(page.nextLink, params.signal);
162
+ yield page;
163
+ }
164
+ }
165
+ /** Insert a row via `POST /<entitySet>`. */
166
+ async add(body, options = {}) {
167
+ const path = this.entitySetPath();
168
+ return this.requestJson("POST", path, undefined, options.signal, JSON.stringify(body), {
169
+ "Content-Type": "application/json",
170
+ });
171
+ }
172
+ /**
173
+ * Update a row via `PATCH /<entitySet>(<key>)`. The OData server in
174
+ * Honua Server intentionally does not implement `PUT`; the SDK issues
175
+ * `PATCH` with the full canonical body to match a `PUT` replacement on
176
+ * the canonical surface (documented in
177
+ * `docs/protocol-capability-matrix.md` under the OData section).
178
+ */
179
+ async update(key, body, options = {}) {
180
+ const path = `${this.entitySetPath()}(${encodeOdataKey(key)})`;
181
+ return this.requestJson("PATCH", path, undefined, options.signal, JSON.stringify(body), {
182
+ "Content-Type": "application/json",
183
+ });
184
+ }
185
+ /** Delete a row via `DELETE /<entitySet>(<key>)`. */
186
+ async delete(key, options = {}) {
187
+ const path = `${this.entitySetPath()}(${encodeOdataKey(key)})`;
188
+ await this.requestJson("DELETE", path, undefined, options.signal);
189
+ }
190
+ /**
191
+ * Submit a `$batch` request using the OData JSON batch format.
192
+ * Multipart/mixed fallback is intentionally not implemented; Honua
193
+ * Server emits both formats but the JSON envelope is sufficient for
194
+ * the canonical surface and avoids the multipart parser.
195
+ *
196
+ * `atomicity: "all"` stamps the same `atomicityGroup` token on every
197
+ * request so the server runs them in one change-set with rollback on
198
+ * any failure (per OData v4 §11.7.7.3 and Honua Server's
199
+ * `ODataBatchHandler`, which groups by `request.AtomicityGroup`).
200
+ * `atomicity: "none"` (default) leaves the field unset so each request
201
+ * runs independently.
202
+ */
203
+ async batch(operations, options = {}) {
204
+ if (operations.length === 0)
205
+ return { responses: [] };
206
+ const groupId = options.atomicity === "all" ? "g1" : undefined;
207
+ const requests = operations.map((op, index) => ({
208
+ id: op.id ?? String(index + 1),
209
+ method: op.method,
210
+ url: stripLeadingSlash(op.url),
211
+ ...(op.headers
212
+ ? { headers: { "Content-Type": "application/json", ...op.headers } }
213
+ : {
214
+ headers: { "Content-Type": "application/json" },
215
+ }),
216
+ ...(op.body !== undefined ? { body: op.body } : {}),
217
+ ...(groupId !== undefined ? { atomicityGroup: groupId } : {}),
218
+ }));
219
+ const response = await this.requestJson("POST", `${this.basePath}/$batch`, undefined, options.signal, JSON.stringify({ requests }), { "Content-Type": "application/json" });
220
+ return { responses: response.responses ?? [] };
221
+ }
222
+ /**
223
+ * Run an `$apply` aggregation. The OData server applies the
224
+ * transformation pipeline (`aggregate`, `groupby`, `filter`,
225
+ * `compute`); the SDK returns the rows unchanged so callers can shape
226
+ * them onto the canonical `Result.aggregateRows`.
227
+ */
228
+ async apply(transformations, params = {}) {
229
+ const path = this.entitySetPath();
230
+ const query = serializeQueryParams({ apply: transformations, filter: params.filter });
231
+ const body = await this.requestJson("GET", path, query, params.signal);
232
+ return {
233
+ rows: body.value ?? [],
234
+ ...(typeof body["@odata.count"] === "number" ? { totalCount: body["@odata.count"] } : {}),
235
+ };
236
+ }
237
+ /**
238
+ * Run a `$search` full-text query. Returns an `HonuaOdataPage` so the
239
+ * canonical `Result<T>` projection is symmetric with `query()`.
240
+ */
241
+ async search(text, params = {}) {
242
+ return this.query({ ...params, search: text });
243
+ }
244
+ /**
245
+ * `$deltatoken`-driven change feed. Yields `HonuaOdataDeltaPage` per
246
+ * server response. The final page carries `deltaLink`; callers persist
247
+ * the encoded token for the next call to `delta({ since })`.
248
+ */
249
+ async *delta(options = {}) {
250
+ const params = options.since ? { deltatoken: options.since } : {};
251
+ if (options.signal)
252
+ params.signal = options.signal;
253
+ let page = await this.query(params);
254
+ yield {
255
+ rows: page.rows,
256
+ ...(page.nextLink ? { nextLink: page.nextLink } : {}),
257
+ ...(page.deltaLink ? { deltaLink: page.deltaLink } : {}),
258
+ };
259
+ while (page.nextLink) {
260
+ page = await this.followNextLink(page.nextLink, options.signal);
261
+ yield {
262
+ rows: page.rows,
263
+ ...(page.nextLink ? { nextLink: page.nextLink } : {}),
264
+ ...(page.deltaLink ? { deltaLink: page.deltaLink } : {}),
265
+ };
266
+ }
267
+ }
268
+ /**
269
+ * Last-resort raw passthrough — flows through `HonuaClient.pipelineFetch`
270
+ * so auth headers, retry, timeout, interceptors, telemetry, and
271
+ * normalized error mapping all apply. The returned `Response` is
272
+ * unconsumed; callers pick `.json()`, `.text()`, or `.arrayBuffer()`.
273
+ */
274
+ async raw(method, path, init = {}) {
275
+ const normalized = method.toUpperCase();
276
+ return this.client.pipelineFetch(normalized, ensureLeadingSlash(path), init);
277
+ }
278
+ // ── Internal ────────────────────────────────────────────────
279
+ entitySetPath() {
280
+ return `${this.basePath}/${this.entitySet}`;
281
+ }
282
+ async fetchMetadata(options, cached) {
283
+ // Route through pipelineFetch so auth headers, interceptors, retry,
284
+ // timeout, and normalized error handling all apply. The default
285
+ // pipeline does not impose an `Accept` header — we ask for XML
286
+ // explicitly because the OData `$metadata` route emits CSDL.
287
+ const response = await this.client.pipelineFetch("GET", `${this.basePath}/$metadata`, {
288
+ headers: honuaMetadataRequestHeaders({
289
+ accept: "application/xml",
290
+ refresh: options.refresh || Boolean(cached),
291
+ bypass: options.cache === "bypass",
292
+ validator: cached?.validator,
293
+ }),
294
+ }, options.signal, { okStatuses: [304] });
295
+ if (response.status === 304 && cached) {
296
+ const validator = honuaCacheValidatorFromHeaders(response.headers) ?? cached.validator;
297
+ return {
298
+ ...cached,
299
+ cachedAtMs: Date.now(),
300
+ ...(validator ? { validator } : {}),
301
+ status: "refreshed",
302
+ };
303
+ }
304
+ const xml = await response.text();
305
+ const validator = honuaCacheValidatorFromHeaders(response.headers);
306
+ const sourceUpdatedAt = response.headers.get("last-modified") ?? undefined;
307
+ return {
308
+ metadata: withoutHonuaCacheState(parseOdataMetadata(xml)),
309
+ cachedAtMs: Date.now(),
310
+ keyFingerprint: `metadata:odata:${this.basePath}:$metadata`,
311
+ status: options.cache === "bypass" ? "bypass" : cached ? "refreshed" : "miss",
312
+ ...(validator ? { validator } : {}),
313
+ ...(sourceUpdatedAt ? { sourceUpdatedAt } : {}),
314
+ };
315
+ }
316
+ async followNextLink(nextLink, signal) {
317
+ // OData servers may emit either an absolute URL or a relative one.
318
+ // Normalize through the same fetch path used by `query()` so retry,
319
+ // auth interceptors, and telemetry still apply.
320
+ const { path, query } = parseNextLink(nextLink, this.client.serverBaseUrl, this.basePath);
321
+ const body = await this.requestJson("GET", path, query, signal);
322
+ return {
323
+ rows: body.value ?? [],
324
+ ...(typeof body["@odata.count"] === "number" ? { totalCount: body["@odata.count"] } : {}),
325
+ ...(typeof body["@odata.nextLink"] === "string" ? { nextLink: body["@odata.nextLink"] } : {}),
326
+ ...(typeof body["@odata.deltaLink"] === "string" ? { deltaLink: body["@odata.deltaLink"] } : {}),
327
+ };
328
+ }
329
+ /**
330
+ * Wrap `HonuaClient.pipelineRequestJson` with OData-shaped query
331
+ * serialization so callers can pass parsed query bags. Critically this
332
+ * does **not** go through `HonuaClient.request` — that helper injects
333
+ * `f=json`, which Honua Server's OData validators reject as
334
+ * `InvalidQueryOption`. The OData server defaults to JSON when the
335
+ * request advertises `Accept: application/json` (the pipeline default).
336
+ */
337
+ async requestJson(method, path, query, signal, body, headers) {
338
+ const params = new URLSearchParams();
339
+ if (query) {
340
+ for (const [key, value] of Object.entries(query)) {
341
+ params.set(key, String(value));
342
+ }
343
+ }
344
+ const pathWithQuery = params.size > 0 ? `${path}?${params.toString()}` : path;
345
+ return this.client.pipelineRequestJson(method, pathWithQuery, {
346
+ ...(headers ? { headers } : {}),
347
+ ...(body !== undefined ? { body } : {}),
348
+ }, signal);
349
+ }
350
+ }
351
+ // ── Helpers ───────────────────────────────────────────────────
352
+ function withOdataMetadataCacheState(entry, status, options) {
353
+ return withHonuaCacheState(entry.metadata, createHonuaCacheState({
354
+ scope: "metadata",
355
+ status,
356
+ keyFingerprint: entry.keyFingerprint,
357
+ ageMs: Math.max(0, options.now - entry.cachedAtMs),
358
+ ...(options.ttlMs !== undefined ? { ttlMs: options.ttlMs } : {}),
359
+ ...(options.staleIfErrorMs !== undefined ? { staleIfErrorMs: options.staleIfErrorMs } : {}),
360
+ ...(options.revalidatedAt ? { revalidatedAt: options.revalidatedAt } : {}),
361
+ ...(entry.sourceUpdatedAt ? { sourceUpdatedAt: entry.sourceUpdatedAt } : {}),
362
+ ...(entry.validator ? { validator: entry.validator } : {}),
363
+ ...(options.refreshErrorId ? { refreshErrorId: options.refreshErrorId } : {}),
364
+ }));
365
+ }
366
+ function odataMetadataRefreshErrorId(error) {
367
+ if (error instanceof Error && error.name) {
368
+ return error.name;
369
+ }
370
+ return "unknown";
371
+ }
372
+ function normalizeBasePath(path) {
373
+ if (path === undefined || path === "")
374
+ return "/odata";
375
+ return path.startsWith("/") ? trimTrailingSlashes(path) || "/" : `/${trimTrailingSlashes(path)}`;
376
+ }
377
+ /**
378
+ * Extract the unqualified entity-set name from a path-shaped token.
379
+ * Direct identifiers (`"Parcels"`) round-trip unchanged; navigation paths
380
+ * (`"Layers(1)/Features"`) collapse to the trailing segment so CSDL
381
+ * metadata lookups (which key by `<EntitySet Name="…">`) still hit.
382
+ */
383
+ function extractEntitySetName(path) {
384
+ const slash = path.lastIndexOf("/");
385
+ return slash === -1 ? path : path.slice(slash + 1);
386
+ }
387
+ function ensureLeadingSlash(path) {
388
+ return path.startsWith("/") ? path : `/${path}`;
389
+ }
390
+ function stripLeadingSlash(path) {
391
+ return path.startsWith("/") ? path.slice(1) : path;
392
+ }
393
+ /**
394
+ * Make an OData entity key safe to interpolate into the URL path segment
395
+ * `<entitySet>(<key>)`. A raw key containing characters such as `&`, `?`,
396
+ * `#`, `/`, space, or `'` would otherwise split or mis-target the path
397
+ * (e.g. `Customers(A&B)`).
398
+ *
399
+ * The key is treated as an OData literal: single-quoted spans are kept as
400
+ * quoted string literals (with embedded `'` doubled per OData v4 §5.1.1.6.1),
401
+ * and structural characters that delimit composite keys (`,`, `=`) and
402
+ * literal quotes are preserved. Everything else is percent-encoded so the
403
+ * resulting segment is a single, correctly-targeted URL path component.
404
+ */
405
+ function encodeOdataKey(key) {
406
+ const raw = String(key);
407
+ let out = "";
408
+ let inQuote = false;
409
+ for (let i = 0; i < raw.length; i += 1) {
410
+ const ch = raw[i];
411
+ if (ch === "'") {
412
+ // Toggle quoted-literal state and keep the (URL-safe) quote verbatim.
413
+ inQuote = !inQuote;
414
+ out += "'";
415
+ continue;
416
+ }
417
+ if (!inQuote && (ch === "," || ch === "=")) {
418
+ // Composite-key delimiters stay structural outside quoted spans.
419
+ out += ch;
420
+ continue;
421
+ }
422
+ // Percent-encode anything that is unsafe in a path segment. Unreserved
423
+ // characters (RFC 3986) pass through untouched.
424
+ if (/[A-Za-z0-9\-._~]/.test(ch)) {
425
+ out += ch;
426
+ }
427
+ else {
428
+ out += encodeURIComponent(ch);
429
+ }
430
+ }
431
+ return out;
432
+ }
433
+ /**
434
+ * Translate canonical query params onto the OData `$`-prefixed query
435
+ * string. Empty strings, undefined values, and zero-length lists are
436
+ * dropped so the URL stays minimal.
437
+ */
438
+ function serializeQueryParams(params) {
439
+ const out = {};
440
+ if (params.filter !== undefined && params.filter !== "")
441
+ out.$filter = params.filter;
442
+ if (params.select && params.select.length > 0)
443
+ out.$select = params.select.join(",");
444
+ if (params.orderBy && params.orderBy.length > 0)
445
+ out.$orderby = params.orderBy.join(",");
446
+ if (typeof params.top === "number" && Number.isFinite(params.top))
447
+ out.$top = params.top;
448
+ if (typeof params.skip === "number" && Number.isFinite(params.skip) && params.skip > 0)
449
+ out.$skip = params.skip;
450
+ if (params.expand && params.expand.length > 0)
451
+ out.$expand = params.expand.join(",");
452
+ if (params.count)
453
+ out.$count = "true";
454
+ if (params.skiptoken !== undefined && params.skiptoken !== "")
455
+ out.$skiptoken = params.skiptoken;
456
+ if (params.search !== undefined && params.search !== "")
457
+ out.$search = params.search;
458
+ if (params.apply !== undefined && params.apply !== "")
459
+ out.$apply = params.apply;
460
+ if (params.deltatoken !== undefined && params.deltatoken !== "")
461
+ out.$deltatoken = params.deltatoken;
462
+ if (params.extra)
463
+ Object.assign(out, params.extra);
464
+ return out;
465
+ }
466
+ /**
467
+ * `@odata.nextLink` is either an absolute URL (preferred by spec) or a
468
+ * service-relative URL. Reduce it to the path + query bag the same fetch
469
+ * pipeline expects.
470
+ */
471
+ function parseNextLink(link, serverBaseUrl, basePath) {
472
+ const absolute = link.startsWith("http://") || link.startsWith("https://")
473
+ ? new URL(link)
474
+ : new URL(link, `${ensureTrailingSlash(serverBaseUrl)}${stripLeadingSlash(basePath)}/`);
475
+ const path = absolute.pathname;
476
+ const query = {};
477
+ for (const [key, value] of absolute.searchParams)
478
+ query[key] = value;
479
+ return { path, query };
480
+ }
481
+ function ensureTrailingSlash(url) {
482
+ return url.endsWith("/") ? url : `${url}/`;
483
+ }
484
+ // ── EDMX (CSDL) parsing ───────────────────────────────────────
485
+ /**
486
+ * Parse the subset of CSDL we need to power capability negotiation and
487
+ * key resolution: entity sets, entity-type keys, property names + types,
488
+ * `Edm.Geography` / `Edm.Geometry` typing, and `Capabilities.*`
489
+ * annotations. The XML is read via small regular expressions because we
490
+ * intentionally do not pull a DOM dep — the surface we read is
491
+ * well-defined by OData CSDL §6 and the Honua Server spec page.
492
+ */
493
+ export function parseOdataMetadata(xml) {
494
+ const entitySets = {};
495
+ const keys = {};
496
+ const fields = {};
497
+ const capabilities = {};
498
+ // Sibling annotations: <Annotations Target="<schema>.<container>/<entitySet>">
499
+ // …</Annotations>. Honua Server emits Capabilities.* this way (next to
500
+ // the EntityContainer) rather than nesting them inside <EntitySet>, so
501
+ // capability negotiation must merge both shapes.
502
+ const siblingByEntitySet = new Map();
503
+ const siblingAnnotationsRe = /<Annotations\s+([^>]*?)>([\s\S]*?)<\/Annotations>/g;
504
+ for (const match of xml.matchAll(siblingAnnotationsRe)) {
505
+ const attrs = match[1] ?? "";
506
+ const target = readAttr(attrs, "Target");
507
+ if (!target)
508
+ continue;
509
+ const slash = target.lastIndexOf("/");
510
+ if (slash === -1)
511
+ continue;
512
+ const setName = target.slice(slash + 1);
513
+ if (!setName)
514
+ continue;
515
+ const body = match[2] ?? "";
516
+ const existing = siblingByEntitySet.get(setName);
517
+ siblingByEntitySet.set(setName, existing === undefined ? body : `${existing}\n${body}`);
518
+ }
519
+ // Entity sets: <EntitySet Name="Foo" EntityType="ns.FooEntity"/>
520
+ const entitySetRe = /<EntitySet\s+([^>]*?)\/>|<EntitySet\s+([^>]*?)>([\s\S]*?)<\/EntitySet>/g;
521
+ for (const match of xml.matchAll(entitySetRe)) {
522
+ const attrs = match[1] ?? match[2] ?? "";
523
+ const name = readAttr(attrs, "Name");
524
+ const type = readAttr(attrs, "EntityType");
525
+ if (!name || !type)
526
+ continue;
527
+ entitySets[name] = stripNamespace(type);
528
+ const inlineBody = match[3] ?? "";
529
+ const siblingBody = siblingByEntitySet.get(name) ?? "";
530
+ capabilities[name] = parseEntitySetAnnotations(`${inlineBody}\n${siblingBody}`);
531
+ siblingByEntitySet.delete(name);
532
+ }
533
+ // Capture annotations for entity sets that were not declared inline (e.g.
534
+ // when a server emits only the sibling block). The simple-name suffix
535
+ // from the Target still keys a capability entry so callers can find it.
536
+ for (const [setName, body] of siblingByEntitySet) {
537
+ capabilities[setName] = parseEntitySetAnnotations(body);
538
+ }
539
+ // Entity types: <EntityType Name="FooEntity">…<Key><PropertyRef Name="Id"/></Key><Property…/>…</EntityType>
540
+ const entityTypeRe = /<EntityType\s+([^>]*?)>([\s\S]*?)<\/EntityType>/g;
541
+ for (const match of xml.matchAll(entityTypeRe)) {
542
+ const attrs = match[1] ?? "";
543
+ const body = match[2] ?? "";
544
+ const name = readAttr(attrs, "Name");
545
+ if (!name)
546
+ continue;
547
+ const keyNames = [];
548
+ const keyBlock = /<Key>([\s\S]*?)<\/Key>/.exec(body);
549
+ if (keyBlock) {
550
+ for (const ref of keyBlock[1].matchAll(/<PropertyRef\s+([^>]*?)\/>/g)) {
551
+ const refName = readAttr(ref[1], "Name");
552
+ if (refName)
553
+ keyNames.push(refName);
554
+ }
555
+ }
556
+ keys[name] = keyNames;
557
+ const props = [];
558
+ for (const propMatch of body.matchAll(/<Property\s+([^>]*?)\/>|<Property\s+([^>]*?)>/g)) {
559
+ const propAttrs = propMatch[1] ?? propMatch[2] ?? "";
560
+ const propName = readAttr(propAttrs, "Name");
561
+ const propType = readAttr(propAttrs, "Type");
562
+ if (!propName || !propType)
563
+ continue;
564
+ const nullable = readAttr(propAttrs, "Nullable");
565
+ const sridAttr = readAttr(propAttrs, "SRID");
566
+ const isSpatial = propType.startsWith("Edm.Geography") || propType.startsWith("Edm.Geometry");
567
+ const info = { name: propName, type: propType };
568
+ if (nullable !== undefined)
569
+ info.nullable = nullable !== "false";
570
+ if (isSpatial)
571
+ info.isSpatial = true;
572
+ if (sridAttr !== undefined) {
573
+ const srid = Number(sridAttr);
574
+ if (Number.isFinite(srid))
575
+ info.srid = srid;
576
+ }
577
+ props.push(info);
578
+ }
579
+ fields[name] = props;
580
+ }
581
+ return { entitySets, keys, fields, capabilities };
582
+ }
583
+ /**
584
+ * Convenience: project the field schema for one entity set into the
585
+ * canonical `HonuaFieldInfo[]` shape reused by `Result.fields`.
586
+ */
587
+ export function odataFieldSchema(metadata, entitySet) {
588
+ const typeName = metadata.entitySets[entitySet];
589
+ if (!typeName)
590
+ return [];
591
+ const props = metadata.fields[typeName];
592
+ if (!props)
593
+ return [];
594
+ return props.map((p) => ({ name: p.name, type: edmToHonuaFieldType(p.type) }));
595
+ }
596
+ function edmToHonuaFieldType(edm) {
597
+ if (edm.startsWith("Edm.Geography") || edm.startsWith("Edm.Geometry"))
598
+ return "esriFieldTypeGeometry";
599
+ if (edm === "Edm.String")
600
+ return "esriFieldTypeString";
601
+ if (edm === "Edm.Int32")
602
+ return "esriFieldTypeInteger";
603
+ if (edm === "Edm.Int64")
604
+ return "esriFieldTypeOID";
605
+ if (edm === "Edm.Double")
606
+ return "esriFieldTypeDouble";
607
+ if (edm === "Edm.Single")
608
+ return "esriFieldTypeSingle";
609
+ if (edm === "Edm.Boolean")
610
+ return "esriFieldTypeSmallInteger";
611
+ if (edm === "Edm.DateTimeOffset" || edm === "Edm.Date")
612
+ return "esriFieldTypeDate";
613
+ return "esriFieldTypeString";
614
+ }
615
+ function parseEntitySetAnnotations(body) {
616
+ const out = {};
617
+ for (const match of body.matchAll(/<Annotation\s+([^>]*?)(?:\/>|>([\s\S]*?)<\/Annotation>)/g)) {
618
+ const attrs = match[1] ?? "";
619
+ const term = readAttr(attrs, "Term");
620
+ if (!term || !term.includes("Capabilities."))
621
+ continue;
622
+ const inner = (match[2] ?? "").trim();
623
+ const boolValue = readAttr(attrs, "Bool") ??
624
+ matchBool(inner) ??
625
+ matchPropertyValueBool(inner, "Insertable") ??
626
+ matchPropertyValueBool(inner, "Updatable") ??
627
+ matchPropertyValueBool(inner, "Deletable") ??
628
+ matchPropertyValueBool(inner, "Searchable") ??
629
+ matchPropertyValueBool(inner, "Filterable") ??
630
+ matchPropertyValueBool(inner, "Expandable") ??
631
+ matchPropertyValueBool(inner, "Selectable") ??
632
+ matchPropertyValueBool(inner, "Countable") ??
633
+ // Honua Server uses `Supported` for ChangeTracking and other
634
+ // capability records; without this fallback, `<PropertyValue
635
+ // Property="Supported" Bool="false"/>` would be silently treated
636
+ // as supported.
637
+ matchPropertyValueBool(inner, "Supported");
638
+ if (term.endsWith("InsertRestrictions")) {
639
+ out.insert = matchPropertyValueBool(inner, "Insertable") !== "false";
640
+ }
641
+ else if (term.endsWith("UpdateRestrictions")) {
642
+ out.update = matchPropertyValueBool(inner, "Updatable") !== "false";
643
+ }
644
+ else if (term.endsWith("DeleteRestrictions")) {
645
+ out.delete = matchPropertyValueBool(inner, "Deletable") !== "false";
646
+ }
647
+ else if (term.endsWith("BatchSupported") || term.endsWith("BatchSupport")) {
648
+ out.batch = boolValue !== "false";
649
+ }
650
+ else if (term.endsWith("FilterRestrictions") || term.endsWith("FilterFunctions")) {
651
+ out.filter = boolValue !== "false";
652
+ }
653
+ else if (term.endsWith("ExpandRestrictions")) {
654
+ out.expand = boolValue !== "false";
655
+ }
656
+ else if (term.endsWith("SelectSupport")) {
657
+ out.select = boolValue !== "false";
658
+ }
659
+ else if (term.endsWith("SearchRestrictions")) {
660
+ out.search = boolValue !== "false";
661
+ }
662
+ else if (term.endsWith("CountRestrictions")) {
663
+ out.count = boolValue !== "false";
664
+ }
665
+ else if (term.endsWith("ChangeTracking")) {
666
+ out.delta = boolValue !== "false";
667
+ }
668
+ else if (term.endsWith("ApplySupported") || term.endsWith("ApplyRestrictions")) {
669
+ out.apply = boolValue !== "false";
670
+ }
671
+ }
672
+ return out;
673
+ }
674
+ function readAttr(attrs, name) {
675
+ const re = new RegExp(`${name}\\s*=\\s*"([^"]*)"`);
676
+ const match = re.exec(attrs);
677
+ return match ? match[1] : undefined;
678
+ }
679
+ function stripNamespace(name) {
680
+ const idx = name.lastIndexOf(".");
681
+ return idx === -1 ? name : name.slice(idx + 1);
682
+ }
683
+ function matchBool(inner) {
684
+ const m = /<Bool>(true|false)<\/Bool>/.exec(inner);
685
+ return m ? m[1] : undefined;
686
+ }
687
+ function matchPropertyValueBool(inner, property) {
688
+ const re = new RegExp(`<PropertyValue\\s+Property="${property}"\\s+Bool="(true|false)"`);
689
+ const match = re.exec(inner);
690
+ return match ? match[1] : undefined;
691
+ }
692
+ const UNSUPPORTED_OPERATOR_RES = [
693
+ { token: "has", re: /\bhas\b/i },
694
+ { token: "in", re: /\bin\s*\(/i },
695
+ { token: "any", re: /\bany\s*\(/i },
696
+ { token: "all", re: /\ball\s*\(/i },
697
+ { token: "cast", re: /\bcast\s*\(/i },
698
+ { token: "isof", re: /\bisof\s*\(/i },
699
+ ];
700
+ // ── SQL → OData filter rewrite ────────────────────────────────
701
+ /**
702
+ * `Query.where` is documented as SQL-92 / CQL2 on the canonical surface.
703
+ * The OData `$filter` grammar is close to but not identical to SQL-92;
704
+ * this minimal rewriter handles the cases that would otherwise produce a
705
+ * server-side parse error rather than a translated query. Full SQL→OData
706
+ * translation is tracked as follow-up work in
707
+ * `docs/decisions/odata-library-selection.md`.
708
+ *
709
+ * Rewrites applied:
710
+ * - `IS NULL` / `IS NOT NULL` → `eq null` / `ne null`
711
+ * - `<col> = '...'`, `<col> = N` → `<col> eq '...'` / `<col> eq N`
712
+ * - SQL-style `<>` → `ne`
713
+ * - SQL comparison operators `>=` / `<=` / `>` / `<` → `ge` / `le` /
714
+ * `gt` / `lt` (Honua Server's OData lexer rejects bare punctuation
715
+ * comparisons as `InvalidQueryOption`).
716
+ *
717
+ * Predicates whose syntax already matches OData (e.g. `STATE eq 'CA'`)
718
+ * pass through unchanged. Unsupported operators (`HAS`, `IN`, `ANY`,
719
+ * `ALL`, `CAST`, `ISOF` from the parity matrix) are detected and
720
+ * rejected with a typed error rather than left to the server.
721
+ */
722
+ export function rewriteWhereToOdataFilter(where) {
723
+ if (where === "" || where === "1=1")
724
+ return "";
725
+ // Tokenize into alternating non-literal / literal spans so the rewriter
726
+ // and unsupported-operator detector skip the contents of quoted string
727
+ // literals. Without this split, a value like `NAME = 'A=B'` would have
728
+ // its embedded `=` rewritten to ` eq `, corrupting the literal before it
729
+ // reaches `$filter`. OData uses single-quoted strings with `''` for
730
+ // embedded quotes (per OData v4 §5.1.1.6.1).
731
+ const spans = splitOdataLiteralSpans(where);
732
+ for (let idx = 0; idx < spans.length; idx += 2) {
733
+ const segment = spans[idx];
734
+ if (segment === "")
735
+ continue;
736
+ for (const banned of UNSUPPORTED_OPERATOR_RES) {
737
+ if (banned.re.test(segment)) {
738
+ throw new Error(`odata: $filter does not support "${banned.token}"; the parity matrix lists this operator as unsupported on Honua Server.`);
739
+ }
740
+ }
741
+ let out = segment;
742
+ out = out.replace(/\bIS\s+NOT\s+NULL\b/gi, "ne null");
743
+ out = out.replace(/\bIS\s+NULL\b/gi, "eq null");
744
+ // `<>` must be rewritten before bare `<` / `>` so the not-equal
745
+ // operator does not get split into `lt` / `gt`. Two-char operators
746
+ // (`>=` / `<=`) likewise come before bare `>` / `<`.
747
+ out = out.replace(/<>/g, "ne");
748
+ out = out.replace(/(\b\w+\b)\s*>=\s*/g, (_match, lhs) => `${lhs} ge `);
749
+ out = out.replace(/(\b\w+\b)\s*<=\s*/g, (_match, lhs) => `${lhs} le `);
750
+ out = out.replace(/(\b\w+\b)\s*>\s*/g, (_match, lhs) => `${lhs} gt `);
751
+ out = out.replace(/(\b\w+\b)\s*<\s*/g, (_match, lhs) => `${lhs} lt `);
752
+ out = out.replace(/(\b\w+\b)\s*=\s*/g, (_match, lhs) => `${lhs} eq `);
753
+ out = out.replace(/\bAND\b/gi, "and");
754
+ out = out.replace(/\bOR\b/gi, "or");
755
+ out = out.replace(/\bNOT\b/gi, "not");
756
+ spans[idx] = out;
757
+ }
758
+ return spans.join("").trim();
759
+ }
760
+ /**
761
+ * Split an OData `$filter`-style expression into alternating non-literal
762
+ * and literal spans. Even indices contain code; odd indices contain
763
+ * single-quoted string literals (with quotes preserved). Embedded `''`
764
+ * escape sequences inside a literal are kept verbatim. An unterminated
765
+ * literal is treated as if it ran to the end of the input.
766
+ */
767
+ function splitOdataLiteralSpans(input) {
768
+ const spans = [];
769
+ let buf = "";
770
+ let i = 0;
771
+ while (i < input.length) {
772
+ const ch = input[i];
773
+ if (ch !== "'") {
774
+ buf += ch;
775
+ i += 1;
776
+ continue;
777
+ }
778
+ spans.push(buf);
779
+ let literal = "'";
780
+ i += 1;
781
+ while (i < input.length) {
782
+ const c = input[i];
783
+ if (c === "'") {
784
+ // OData string literals double the quote to embed it.
785
+ if (input[i + 1] === "'") {
786
+ literal += "''";
787
+ i += 2;
788
+ continue;
789
+ }
790
+ literal += "'";
791
+ i += 1;
792
+ break;
793
+ }
794
+ literal += c;
795
+ i += 1;
796
+ }
797
+ spans.push(literal);
798
+ buf = "";
799
+ }
800
+ spans.push(buf);
801
+ return spans;
802
+ }
803
+ export function buildOdataSpatialFilter(spatialFilter, context = {}) {
804
+ const rel = spatialFilter.spatialRel ?? "esriSpatialRelIntersects";
805
+ if (rel !== "esriSpatialRelIntersects" &&
806
+ rel !== "esriSpatialRelEnvelopeIntersects" &&
807
+ rel !== "esriSpatialRelDistance") {
808
+ throw new Error(`odata: spatialRel "${rel}" is not supported; OData parity matrix only implements geo.intersects (intersects/envelope-intersects) and geo.distance.`);
809
+ }
810
+ const column = resolveGeometryColumn(context);
811
+ const wkt = geometryToWkt(spatialFilter.geometry, spatialFilter.geometryType);
812
+ const inputSrid = resolveInputSrid(spatialFilter.geometry, context);
813
+ const literal = wktLiteral(wkt, inputSrid);
814
+ if (rel === "esriSpatialRelDistance") {
815
+ if (typeof spatialFilter.distance !== "number" || !Number.isFinite(spatialFilter.distance)) {
816
+ throw new Error('odata: spatialRel "esriSpatialRelDistance" requires a numeric distance (meters for geography).');
817
+ }
818
+ return `geo.distance(${column},${literal}) le ${spatialFilter.distance}`;
819
+ }
820
+ return `geo.intersects(${column},${literal})`;
821
+ }
822
+ function resolveGeometryColumn(context) {
823
+ if (context.geometryColumn)
824
+ return context.geometryColumn;
825
+ const spatial = context.geometryFields?.find((f) => f.isSpatial);
826
+ if (!spatial) {
827
+ throw new Error("odata: cannot translate spatialFilter — no geometry column was supplied and metadata exposes no Edm.Geography field.");
828
+ }
829
+ return spatial.name;
830
+ }
831
+ function resolveInputSrid(geometry, context) {
832
+ if (context.inputSrid !== undefined && context.inputSrid !== null && context.inputSrid !== "") {
833
+ return context.inputSrid;
834
+ }
835
+ const sr = geometry.spatialReference;
836
+ if (sr?.wkid !== undefined && Number.isFinite(sr.wkid))
837
+ return sr.wkid;
838
+ if (sr?.latestWkid !== undefined && Number.isFinite(sr.latestWkid))
839
+ return sr.latestWkid;
840
+ // Prefer the SRID of the *selected* geometry column when a name was
841
+ // pinned by the caller — entity types with multiple spatial fields
842
+ // can declare different SRIDs per column, and the WKT must be stamped
843
+ // with the SRID of the column the predicate is being evaluated
844
+ // against. Falls back to the first spatial field only when no column
845
+ // was pinned (or the pinned column is absent from the supplied
846
+ // metadata fields).
847
+ const selected = context.geometryColumn !== undefined
848
+ ? context.geometryFields?.find((f) => f.name === context.geometryColumn)
849
+ : undefined;
850
+ if (selected?.srid !== undefined && Number.isFinite(selected.srid))
851
+ return selected.srid;
852
+ if (context.geometryColumn !== undefined && selected)
853
+ return undefined;
854
+ const spatial = context.geometryFields?.find((f) => f.isSpatial);
855
+ if (spatial?.srid !== undefined && Number.isFinite(spatial.srid))
856
+ return spatial.srid;
857
+ return undefined;
858
+ }
859
+ function wktLiteral(wkt, srid) {
860
+ if (srid === undefined || srid === null || srid === "") {
861
+ return `geography'${wkt}'`;
862
+ }
863
+ return `geography'SRID=${srid};${wkt}'`;
864
+ }
865
+ // Coordinates are interpolated verbatim into a WKT string that is wrapped in an
866
+ // OData `geography'...'` literal and passed straight into `$filter`. Any value
867
+ // that is not a finite number must be rejected here: a non-numeric string (e.g.
868
+ // one containing a single quote) would otherwise terminate the OData string
869
+ // literal and inject attacker-controlled tokens into the server-side filter, and
870
+ // NaN/Infinity/undefined would produce malformed WKT.
871
+ function wktCoordinate(value, context) {
872
+ const num = typeof value === "number" ? value : Number(value);
873
+ if (!Number.isFinite(num)) {
874
+ throw new Error(`odata: spatialFilter ${context} must be a finite number.`);
875
+ }
876
+ return num;
877
+ }
878
+ function wktPoint(pt, context) {
879
+ return `${wktCoordinate(pt[0], `${context}[0]`)} ${wktCoordinate(pt[1], `${context}[1]`)}`;
880
+ }
881
+ function geometryToWkt(geometry, geometryType) {
882
+ if (geometryType === "esriGeometryEnvelope") {
883
+ const xmin = wktCoordinate(geometry.xmin, "envelope.xmin");
884
+ const ymin = wktCoordinate(geometry.ymin, "envelope.ymin");
885
+ const xmax = wktCoordinate(geometry.xmax, "envelope.xmax");
886
+ const ymax = wktCoordinate(geometry.ymax, "envelope.ymax");
887
+ return `POLYGON((${xmin} ${ymin}, ${xmax} ${ymin}, ${xmax} ${ymax}, ${xmin} ${ymax}, ${xmin} ${ymin}))`;
888
+ }
889
+ if (geometryType === "esriGeometryPoint") {
890
+ const x = wktCoordinate(geometry.x, "point.x");
891
+ const y = wktCoordinate(geometry.y, "point.y");
892
+ return `POINT(${x} ${y})`;
893
+ }
894
+ if (geometryType === "esriGeometryPolygon") {
895
+ const rings = geometry.rings;
896
+ if (!Array.isArray(rings) || rings.length === 0) {
897
+ throw new Error("odata: polygon spatialFilter has no rings");
898
+ }
899
+ const inner = rings
900
+ .map((ring) => `(${ring.map((pt) => wktPoint(pt, "polygon.ring")).join(", ")})`)
901
+ .join(", ");
902
+ return `POLYGON(${inner})`;
903
+ }
904
+ if (geometryType === "esriGeometryPolyline") {
905
+ const paths = geometry.paths;
906
+ if (!Array.isArray(paths) || paths.length === 0) {
907
+ throw new Error("odata: polyline spatialFilter has no paths");
908
+ }
909
+ const path = paths[0];
910
+ return `LINESTRING(${path.map((pt) => wktPoint(pt, "polyline.path")).join(", ")})`;
911
+ }
912
+ throw new Error(`odata: spatialFilter.geometryType "${geometryType}" is not supported.`);
913
+ }