@honua/react 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 (363) 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 +12 -1
  14. package/contract/index.d.ts.map +1 -1
  15. package/contract/index.js +4 -0
  16. package/contract/pmtiles.d.ts +193 -0
  17. package/contract/pmtiles.d.ts.map +1 -0
  18. package/contract/pmtiles.js +195 -0
  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 +57 -15
  32. package/contract/source.d.ts.map +1 -1
  33. package/contract/source.js +368 -65
  34. package/contract/types.d.ts +171 -6
  35. package/contract/types.d.ts.map +1 -1
  36. package/contract/types.js +14 -14
  37. package/core/auth/client-credentials-provider.d.ts +51 -0
  38. package/core/auth/client-credentials-provider.d.ts.map +1 -0
  39. package/core/auth/client-credentials-provider.js +115 -0
  40. package/core/auth/credential-store.d.ts +52 -0
  41. package/core/auth/credential-store.d.ts.map +1 -0
  42. package/core/auth/credential-store.js +82 -0
  43. package/core/auth/index.d.ts +23 -0
  44. package/core/auth/index.d.ts.map +1 -0
  45. package/core/auth/index.js +20 -0
  46. package/core/auth/oauth2-provider.d.ts +114 -0
  47. package/core/auth/oauth2-provider.d.ts.map +1 -0
  48. package/core/auth/oauth2-provider.js +408 -0
  49. package/core/auth/pkce.d.ts +25 -0
  50. package/core/auth/pkce.d.ts.map +1 -0
  51. package/core/auth/pkce.js +46 -0
  52. package/core/auth/static-providers.d.ts +20 -0
  53. package/core/auth/static-providers.d.ts.map +1 -0
  54. package/core/auth/static-providers.js +26 -0
  55. package/core/auth/token-endpoint.d.ts +40 -0
  56. package/core/auth/token-endpoint.d.ts.map +1 -0
  57. package/core/auth/token-endpoint.js +133 -0
  58. package/core/auth/types.d.ts +144 -0
  59. package/core/auth/types.d.ts.map +1 -0
  60. package/core/auth/types.js +8 -0
  61. package/core/bounded-json.d.ts +4 -0
  62. package/core/bounded-json.d.ts.map +1 -0
  63. package/core/bounded-json.js +38 -0
  64. package/core/cache-state.d.ts +9 -0
  65. package/core/cache-state.d.ts.map +1 -1
  66. package/core/cache-state.js +10 -0
  67. package/core/capabilities-xml.d.ts +33 -0
  68. package/core/capabilities-xml.d.ts.map +1 -0
  69. package/core/capabilities-xml.js +331 -0
  70. package/core/client.d.ts +35 -2
  71. package/core/client.d.ts.map +1 -1
  72. package/core/client.js +167 -29
  73. package/core/error-classifications.d.ts +153 -0
  74. package/core/error-classifications.d.ts.map +1 -0
  75. package/core/error-classifications.js +140 -0
  76. package/core/error-code-registry.d.ts +158 -0
  77. package/core/error-code-registry.d.ts.map +1 -0
  78. package/core/error-code-registry.js +143 -0
  79. package/core/error-envelope.d.ts +68 -0
  80. package/core/error-envelope.d.ts.map +1 -0
  81. package/core/error-envelope.js +357 -0
  82. package/core/errors.d.ts +73 -25
  83. package/core/errors.d.ts.map +1 -1
  84. package/core/errors.js +113 -46
  85. package/core/geoparquet-sql.d.ts +190 -0
  86. package/core/geoparquet-sql.d.ts.map +1 -0
  87. package/core/geoparquet-sql.js +368 -0
  88. package/core/odata-key-path.d.ts +12 -0
  89. package/core/odata-key-path.d.ts.map +1 -0
  90. package/core/odata-key-path.js +28 -0
  91. package/core/odata-write-codec.d.ts +66 -0
  92. package/core/odata-write-codec.d.ts.map +1 -0
  93. package/core/odata-write-codec.js +1083 -0
  94. package/core/odata.d.ts +59 -7
  95. package/core/odata.d.ts.map +1 -1
  96. package/core/odata.js +431 -99
  97. package/core/ogc-endpoint-layout.d.ts +70 -0
  98. package/core/ogc-endpoint-layout.d.ts.map +1 -0
  99. package/core/ogc-endpoint-layout.js +173 -0
  100. package/core/ogc-features.d.ts.map +1 -1
  101. package/core/ogc-features.js +35 -17
  102. package/core/ogc-maps.d.ts +7 -0
  103. package/core/ogc-maps.d.ts.map +1 -1
  104. package/core/ogc-maps.js +46 -7
  105. package/core/ogc-processes.d.ts +2 -1
  106. package/core/ogc-processes.d.ts.map +1 -1
  107. package/core/ogc-processes.js +33 -5
  108. package/core/ogc-records.d.ts +9 -0
  109. package/core/ogc-records.d.ts.map +1 -1
  110. package/core/ogc-records.js +78 -29
  111. package/core/ogc-tiles.d.ts +10 -1
  112. package/core/ogc-tiles.d.ts.map +1 -1
  113. package/core/ogc-tiles.js +56 -17
  114. package/core/path-utils.d.ts +9 -0
  115. package/core/path-utils.d.ts.map +1 -1
  116. package/core/path-utils.js +19 -0
  117. package/core/protocol-transport.d.ts +1 -0
  118. package/core/protocol-transport.d.ts.map +1 -1
  119. package/core/request-pipeline.d.ts.map +1 -1
  120. package/core/request-pipeline.js +1 -1
  121. package/core/spatial-filter.d.ts +8 -4
  122. package/core/spatial-filter.d.ts.map +1 -1
  123. package/core/spatial-filter.js +206 -16
  124. package/core/stac-static.d.ts +55 -0
  125. package/core/stac-static.d.ts.map +1 -0
  126. package/core/stac-static.js +386 -0
  127. package/core/stac.d.ts +12 -0
  128. package/core/stac.d.ts.map +1 -1
  129. package/core/stac.js +45 -15
  130. package/core/surfaces.d.ts +13 -0
  131. package/core/surfaces.d.ts.map +1 -1
  132. package/core/surfaces.js +127 -16
  133. package/core/types.d.ts +174 -7
  134. package/core/types.d.ts.map +1 -1
  135. package/core/wfs-capabilities.d.ts +12 -0
  136. package/core/wfs-capabilities.d.ts.map +1 -1
  137. package/core/wfs-capabilities.js +22 -3
  138. package/core/wfs.d.ts +13 -0
  139. package/core/wfs.d.ts.map +1 -1
  140. package/core/wfs.js +35 -5
  141. package/core/wms-capabilities.d.ts +56 -84
  142. package/core/wms-capabilities.d.ts.map +1 -1
  143. package/core/wms-capabilities.js +212 -509
  144. package/core/wmts-capabilities.d.ts +62 -62
  145. package/core/wmts-capabilities.d.ts.map +1 -1
  146. package/core/wmts-capabilities.js +224 -371
  147. package/esri-compat/editor.d.ts +8 -0
  148. package/esri-compat/editor.d.ts.map +1 -1
  149. package/esri-compat/editor.js +13 -0
  150. package/esri-compat/feature-layer.d.ts +2 -0
  151. package/esri-compat/feature-layer.d.ts.map +1 -1
  152. package/esri-compat/feature-layer.js +1 -0
  153. package/esri-compat/identity-manager.d.ts +43 -0
  154. package/esri-compat/identity-manager.d.ts.map +1 -1
  155. package/esri-compat/identity-manager.js +131 -1
  156. package/esri-compat/layer-list.d.ts +11 -0
  157. package/esri-compat/layer-list.d.ts.map +1 -1
  158. package/esri-compat/layer-list.js +49 -0
  159. package/esri-compat/legend.d.ts +10 -0
  160. package/esri-compat/legend.d.ts.map +1 -1
  161. package/esri-compat/legend.js +27 -0
  162. package/esri-compat/oauth-info.d.ts +15 -0
  163. package/esri-compat/oauth-info.d.ts.map +1 -1
  164. package/esri-compat/oauth-info.js +27 -0
  165. package/esri-compat/portal.d.ts +197 -0
  166. package/esri-compat/portal.d.ts.map +1 -0
  167. package/esri-compat/portal.js +251 -0
  168. package/esri-compat/renderer-objects.d.ts +40 -0
  169. package/esri-compat/renderer-objects.d.ts.map +1 -0
  170. package/esri-compat/renderer-objects.js +62 -0
  171. package/esri-compat/sketch.d.ts +43 -0
  172. package/esri-compat/sketch.d.ts.map +1 -1
  173. package/esri-compat/sketch.js +46 -0
  174. package/esri-compat/snapping.d.ts +43 -0
  175. package/esri-compat/snapping.d.ts.map +1 -0
  176. package/esri-compat/snapping.js +45 -0
  177. package/esri-compat/url.d.ts.map +1 -1
  178. package/esri-compat/url.js +1 -1
  179. package/esri-compat/widget-host.d.ts +84 -0
  180. package/esri-compat/widget-host.d.ts.map +1 -0
  181. package/esri-compat/widget-host.js +159 -0
  182. package/filter-registry/index.d.ts +145 -0
  183. package/filter-registry/index.d.ts.map +1 -0
  184. package/filter-registry/index.js +531 -0
  185. package/generated-app/errors.d.ts +18 -2
  186. package/generated-app/errors.d.ts.map +1 -1
  187. package/generated-app/errors.js +44 -11
  188. package/map/automatic-mount-integration.d.ts +134 -0
  189. package/map/automatic-mount-integration.d.ts.map +1 -0
  190. package/map/automatic-mount-integration.js +305 -0
  191. package/map/automatic-source-strategy.d.ts +125 -0
  192. package/map/automatic-source-strategy.d.ts.map +1 -0
  193. package/map/automatic-source-strategy.js +494 -0
  194. package/map/data-to-map-bridge.d.ts +293 -0
  195. package/map/data-to-map-bridge.d.ts.map +1 -0
  196. package/map/data-to-map-bridge.js +817 -0
  197. package/map/geojson-projection.d.ts +33 -0
  198. package/map/geojson-projection.d.ts.map +1 -0
  199. package/map/geojson-projection.js +118 -0
  200. package/map/index.d.ts +14 -0
  201. package/map/index.d.ts.map +1 -1
  202. package/map/index.js +8 -0
  203. package/map/raster-source-spec.d.ts +30 -0
  204. package/map/raster-source-spec.d.ts.map +1 -0
  205. package/map/raster-source-spec.js +190 -0
  206. package/map/raster-source-strategy.d.ts +65 -0
  207. package/map/raster-source-strategy.d.ts.map +1 -0
  208. package/map/raster-source-strategy.js +232 -0
  209. package/map/source-to-maplibre.d.ts +91 -0
  210. package/map/source-to-maplibre.d.ts.map +1 -0
  211. package/map/source-to-maplibre.js +454 -0
  212. package/map/temporal-playback.d.ts +139 -0
  213. package/map/temporal-playback.d.ts.map +1 -0
  214. package/map/temporal-playback.js +247 -0
  215. package/package.json +19 -1
  216. package/query-planner/canonical.d.ts +7 -0
  217. package/query-planner/canonical.d.ts.map +1 -0
  218. package/query-planner/canonical.js +145 -0
  219. package/query-planner/cql2-json.d.ts +17 -0
  220. package/query-planner/cql2-json.d.ts.map +1 -0
  221. package/query-planner/cql2-json.js +646 -0
  222. package/query-planner/diagnostics.d.ts +35 -0
  223. package/query-planner/diagnostics.d.ts.map +1 -0
  224. package/query-planner/diagnostics.js +373 -0
  225. package/query-planner/duckdb.d.ts +79 -0
  226. package/query-planner/duckdb.d.ts.map +1 -0
  227. package/query-planner/duckdb.js +758 -0
  228. package/query-planner/execution-context.d.ts +5 -0
  229. package/query-planner/execution-context.d.ts.map +1 -0
  230. package/query-planner/execution-context.js +50 -0
  231. package/query-planner/executor.d.ts +5 -0
  232. package/query-planner/executor.d.ts.map +1 -0
  233. package/query-planner/executor.js +223 -0
  234. package/query-planner/geoservices.d.ts +91 -0
  235. package/query-planner/geoservices.d.ts.map +1 -0
  236. package/query-planner/geoservices.js +667 -0
  237. package/query-planner/grpc.d.ts +100 -0
  238. package/query-planner/grpc.d.ts.map +1 -0
  239. package/query-planner/grpc.js +736 -0
  240. package/query-planner/index.d.ts +44 -0
  241. package/query-planner/index.d.ts.map +1 -0
  242. package/query-planner/index.js +30 -0
  243. package/query-planner/ir.d.ts +20 -0
  244. package/query-planner/ir.d.ts.map +1 -0
  245. package/query-planner/ir.js +417 -0
  246. package/query-planner/local-aggregate.d.ts +5 -0
  247. package/query-planner/local-aggregate.d.ts.map +1 -0
  248. package/query-planner/local-aggregate.js +126 -0
  249. package/query-planner/odata.d.ts +48 -0
  250. package/query-planner/odata.d.ts.map +1 -0
  251. package/query-planner/odata.js +662 -0
  252. package/query-planner/ogc-compiler.d.ts +55 -0
  253. package/query-planner/ogc-compiler.d.ts.map +1 -0
  254. package/query-planner/ogc-compiler.js +480 -0
  255. package/query-planner/ogc-features.d.ts +53 -0
  256. package/query-planner/ogc-features.d.ts.map +1 -0
  257. package/query-planner/ogc-features.js +617 -0
  258. package/query-planner/planner.d.ts +17 -0
  259. package/query-planner/planner.d.ts.map +1 -0
  260. package/query-planner/planner.js +818 -0
  261. package/query-planner/resource.d.ts +94 -0
  262. package/query-planner/resource.d.ts.map +1 -0
  263. package/query-planner/resource.js +526 -0
  264. package/query-planner/semantic-canonical.d.ts +15 -0
  265. package/query-planner/semantic-canonical.d.ts.map +1 -0
  266. package/query-planner/semantic-canonical.js +62 -0
  267. package/query-planner/semantic-compat.d.ts +10 -0
  268. package/query-planner/semantic-compat.d.ts.map +1 -0
  269. package/query-planner/semantic-compat.js +35 -0
  270. package/query-planner/semantic-compiler.d.ts +64 -0
  271. package/query-planner/semantic-compiler.d.ts.map +1 -0
  272. package/query-planner/semantic-compiler.js +81 -0
  273. package/query-planner/semantic-literals.d.ts +30 -0
  274. package/query-planner/semantic-literals.d.ts.map +1 -0
  275. package/query-planner/semantic-literals.js +133 -0
  276. package/query-planner/semantic-types.d.ts +265 -0
  277. package/query-planner/semantic-types.d.ts.map +1 -0
  278. package/query-planner/semantic-types.js +1 -0
  279. package/query-planner/semantic.d.ts +54 -0
  280. package/query-planner/semantic.d.ts.map +1 -0
  281. package/query-planner/semantic.js +1421 -0
  282. package/query-planner/types.d.ts +560 -0
  283. package/query-planner/types.d.ts.map +1 -0
  284. package/query-planner/types.js +49 -0
  285. package/query-planner/wfs-semantic.d.ts +58 -0
  286. package/query-planner/wfs-semantic.d.ts.map +1 -0
  287. package/query-planner/wfs-semantic.js +547 -0
  288. package/query-planner/wfs.d.ts +6 -0
  289. package/query-planner/wfs.d.ts.map +1 -0
  290. package/query-planner/wfs.js +71 -0
  291. package/react/external-map.d.ts +67 -0
  292. package/react/external-map.d.ts.map +1 -0
  293. package/react/external-map.js +63 -0
  294. package/react/honua-map.d.ts.map +1 -1
  295. package/react/honua-map.js +3 -2
  296. package/react/honua-source-layer.d.ts +110 -0
  297. package/react/honua-source-layer.d.ts.map +1 -0
  298. package/react/honua-source-layer.js +101 -0
  299. package/react/hooks.d.ts +28 -0
  300. package/react/hooks.d.ts.map +1 -1
  301. package/react/hooks.js +26 -4
  302. package/react/index.d.ts +8 -0
  303. package/react/index.d.ts.map +1 -1
  304. package/react/index.js +5 -0
  305. package/react/query-cache.d.ts +26 -4
  306. package/react/query-cache.d.ts.map +1 -1
  307. package/react/query-cache.js +51 -8
  308. package/react/selection.d.ts +177 -0
  309. package/react/selection.d.ts.map +1 -0
  310. package/react/selection.js +0 -0
  311. package/react/use-mounted-source.d.ts +69 -0
  312. package/react/use-mounted-source.d.ts.map +1 -0
  313. package/react/use-mounted-source.js +346 -0
  314. package/runtime/edit-snapping.d.ts +107 -0
  315. package/runtime/edit-snapping.d.ts.map +1 -0
  316. package/runtime/edit-snapping.js +190 -0
  317. package/runtime/errors.d.ts +3 -2
  318. package/runtime/errors.d.ts.map +1 -1
  319. package/runtime/errors.js +19 -2
  320. package/runtime/index.d.ts +8 -2
  321. package/runtime/index.d.ts.map +1 -1
  322. package/runtime/index.js +4 -4
  323. package/runtime/load-package.d.ts.map +1 -1
  324. package/runtime/load-package.js +8 -0
  325. package/runtime/map-package-validation.d.ts +0 -13
  326. package/runtime/map-package-validation.d.ts.map +1 -1
  327. package/runtime/map-package-validation.js +0 -12
  328. package/runtime/map-package.d.ts +1 -1
  329. package/runtime/map-package.d.ts.map +1 -1
  330. package/runtime/maplibre-renderer.d.ts +41 -0
  331. package/runtime/maplibre-renderer.d.ts.map +1 -0
  332. package/runtime/maplibre-renderer.js +322 -0
  333. package/runtime/pmtiles-protocol.d.ts +77 -0
  334. package/runtime/pmtiles-protocol.d.ts.map +1 -0
  335. package/runtime/pmtiles-protocol.js +120 -0
  336. package/runtime/query-tiles.d.ts +2 -1
  337. package/runtime/query-tiles.d.ts.map +1 -1
  338. package/runtime/query-tiles.js +6 -2
  339. package/runtime/source-bridge.d.ts +4 -4
  340. package/runtime/source-bridge.d.ts.map +1 -1
  341. package/runtime/source-bridge.js +36 -56
  342. package/runtime/style-interactions.d.ts +3 -2
  343. package/runtime/style-interactions.d.ts.map +1 -1
  344. package/runtime/style-interactions.js +11 -3
  345. package/runtime/terra-draw-sketch.d.ts +275 -0
  346. package/runtime/terra-draw-sketch.d.ts.map +1 -0
  347. package/runtime/terra-draw-sketch.js +404 -0
  348. package/source-capability-registry.d.ts +1 -0
  349. package/source-capability-registry.js +1 -0
  350. package/source-capability-types.d.ts +186 -0
  351. package/source-capability-types.js +7 -0
  352. package/style/index.d.ts +2 -0
  353. package/style/index.d.ts.map +1 -1
  354. package/style/index.js +2 -0
  355. package/style/renderers.d.ts +326 -0
  356. package/style/renderers.d.ts.map +1 -0
  357. package/style/renderers.js +517 -0
  358. package/webmap/convert-renderer.d.ts +24 -3
  359. package/webmap/convert-renderer.d.ts.map +1 -1
  360. package/webmap/convert-renderer.js +93 -112
  361. package/webmap/index.d.ts +1 -1
  362. package/webmap/index.d.ts.map +1 -1
  363. package/webmap/index.js +1 -1
@@ -0,0 +1,386 @@
1
+ /**
2
+ * Static STAC catalog reader.
3
+ *
4
+ * A large share of public STAC catalogs are not API servers — they are
5
+ * static `catalog.json` / `collection.json` / item trees served from object
6
+ * storage, with no `/search` endpoint. This reader walks such a tree the
7
+ * spec-driven way: it follows `rel="child"` links to descend catalogs /
8
+ * collections and `rel="item"` links to reach STAC Items, resolving every
9
+ * href against the document it was found in (STAC best-practice relative
10
+ * links). Filtering (`collections` / `bbox` / `datetime` / `limit`) is
11
+ * applied client-side and the result is projected onto the same
12
+ * {@link HonuaStacItemCollectionResponse} envelope the STAC API search path
13
+ * returns, so the canonical `Source` surface is identical across both
14
+ * layouts.
15
+ *
16
+ * @module
17
+ */
18
+ import { boundedJsonStructureViolation } from "./bounded-json.js";
19
+ import { HonuaAbortError, HonuaCapabilityNotSupportedError } from "./errors.js";
20
+ /** Guardrails match `connect()` static-STAC admission defaults and hard caps. */
21
+ const DEFAULT_MAX_DOCUMENTS = 32;
22
+ const DEFAULT_MAX_DEPTH = 4;
23
+ const DEFAULT_MAX_LINKS_PER_DOCUMENT = 64;
24
+ const DEFAULT_MAX_DOCUMENT_BYTES = 1024 * 1024;
25
+ const HARD_MAX_DOCUMENTS = 64;
26
+ const HARD_MAX_DEPTH = 8;
27
+ const HARD_MAX_LINKS_PER_DOCUMENT = 128;
28
+ const HARD_MAX_DOCUMENT_BYTES = 4 * 1024 * 1024;
29
+ const MAX_STATIC_JSON_DEPTH = 64;
30
+ const MAX_STATIC_JSON_NODES = 100_000;
31
+ const JSON_MEDIA_TYPES = new Set(["application/json", "application/geo+json"]);
32
+ /**
33
+ * Reader bound to the URL of a static STAC catalog / collection root. The
34
+ * URL is the client baseUrl (the transport resolves same-origin absolute
35
+ * child/item hrefs directly), so `rootUrl` is normally `""` (the baseUrl
36
+ * itself) but may be an absolute `catalog.json` URL.
37
+ */
38
+ export class HonuaStacStaticCatalog {
39
+ client;
40
+ rootUrl;
41
+ policy;
42
+ constructor(client, rootUrl = "", policy) {
43
+ this.client = client;
44
+ this.rootUrl = canonicalRuntimeUrl(rootUrl || client.serverBaseUrl, client.serverBaseUrl);
45
+ this.policy = normalizeRuntimePolicy(policy);
46
+ }
47
+ /**
48
+ * Traverse the catalog tree, collect matching items, and return them in
49
+ * the STAC search envelope shape.
50
+ */
51
+ async search(params = {}) {
52
+ const items = await this.collectItems(params);
53
+ const filtered = items.filter((item) => matchesFilters(item, params));
54
+ const cap = typeof params.limit === "number" && params.limit >= 0 ? params.limit : filtered.length;
55
+ const sliced = filtered.slice(0, cap);
56
+ return {
57
+ type: "FeatureCollection",
58
+ features: sliced,
59
+ numberReturned: sliced.length,
60
+ numberMatched: filtered.length,
61
+ links: [],
62
+ };
63
+ }
64
+ async fetchDoc(url, signal) {
65
+ const response = await this.client.pipelineFetch("GET", url, { headers: { Accept: "application/json, application/geo+json;q=0.9" } }, signal, { redirect: "error" });
66
+ const contentType = mediaEssence(response.headers.get("content-type") ?? "");
67
+ if (contentType && !JSON_MEDIA_TYPES.has(contentType)) {
68
+ await response.body?.cancel().catch(() => undefined);
69
+ throw new Error("static STAC document did not return JSON");
70
+ }
71
+ const finalUrl = canonicalRuntimeUrl(response.url || url, url);
72
+ if (new URL(finalUrl).origin !== new URL(this.rootUrl).origin) {
73
+ await response.body?.cancel().catch(() => undefined);
74
+ throw new Error("static STAC redirect left the admitted origin");
75
+ }
76
+ const value = await readBoundedJson(response, this.policy.maxDocumentBytes);
77
+ if (!isPlainObject(value))
78
+ throw new Error("static STAC document must be a JSON object");
79
+ if (value.links !== undefined && !Array.isArray(value.links)) {
80
+ throw new Error("static STAC links must be an array");
81
+ }
82
+ return { value: value, url: finalUrl };
83
+ }
84
+ /**
85
+ * Breadth-first walk collecting item documents. Descends `rel="child"`
86
+ * links; a `collections` filter prunes Collection subtrees whose `id`
87
+ * is not requested. Every attempted document, traversal depth, per-document
88
+ * link list, response body, redirect, and origin is bounded by the locator's
89
+ * admitted policy.
90
+ */
91
+ async collectItems(params) {
92
+ const wantedCollections = params.collections ? new Set(params.collections) : undefined;
93
+ const visitedRequests = new Set();
94
+ const completedUrls = new Set();
95
+ const queue = [{ url: this.rootUrl, depth: 0 }];
96
+ const items = [];
97
+ let attempts = 0;
98
+ while (queue.length > 0 && attempts < this.policy.maxDocuments) {
99
+ throwIfRuntimeAborted(params.signal);
100
+ const next = queue.shift();
101
+ if (!next || visitedRequests.has(next.url))
102
+ continue;
103
+ visitedRequests.add(next.url);
104
+ attempts += 1;
105
+ let fetched;
106
+ try {
107
+ fetched = await this.fetchDoc(next.url, params.signal);
108
+ }
109
+ catch (err) {
110
+ if (params.signal?.aborted || err instanceof HonuaAbortError)
111
+ throw new HonuaAbortError();
112
+ if (next.depth === 0) {
113
+ throw new HonuaCapabilityNotSupportedError("query", "stac", `static catalog root ${this.rootUrl} could not be read: ${err instanceof Error ? err.message : String(err)}`);
114
+ }
115
+ continue;
116
+ }
117
+ if (completedUrls.has(fetched.url))
118
+ continue;
119
+ completedUrls.add(fetched.url);
120
+ visitedRequests.add(fetched.url);
121
+ const doc = fetched.value;
122
+ if (doc.type === "Feature") {
123
+ if (isMinimallyValidStacItem(doc)) {
124
+ items.push(doc);
125
+ }
126
+ else if (next.depth === 0) {
127
+ throw new HonuaCapabilityNotSupportedError("query", "stac", "static catalog root Feature is not a minimally valid STAC Item");
128
+ }
129
+ continue;
130
+ }
131
+ // A Collection subtree not in the requested set is pruned entirely.
132
+ if (wantedCollections &&
133
+ doc.type === "Collection" &&
134
+ typeof doc.id === "string" &&
135
+ !wantedCollections.has(doc.id)) {
136
+ continue;
137
+ }
138
+ if (next.depth >= this.policy.maxDepth)
139
+ continue;
140
+ for (const link of (doc.links ?? []).slice(0, this.policy.maxLinksPerDocument)) {
141
+ if (!isPlainObject(link))
142
+ continue;
143
+ if (typeof link.href !== "string")
144
+ continue;
145
+ const rel = typeof link.rel === "string" ? link.rel.toLowerCase() : "";
146
+ if (rel !== "child" && rel !== "item")
147
+ continue;
148
+ if (typeof link.type === "string" && !JSON_MEDIA_TYPES.has(mediaEssence(link.type)))
149
+ continue;
150
+ const resolved = safeTraversalUrl(link.href, fetched.url, this.rootUrl);
151
+ if (resolved && !visitedRequests.has(resolved))
152
+ queue.push({ url: resolved, depth: next.depth + 1 });
153
+ }
154
+ }
155
+ return items;
156
+ }
157
+ }
158
+ function safeTraversalUrl(href, base, root) {
159
+ let url;
160
+ try {
161
+ url = new URL(href, base);
162
+ }
163
+ catch {
164
+ return undefined;
165
+ }
166
+ if ((url.protocol !== "http:" && url.protocol !== "https:") ||
167
+ url.username ||
168
+ url.password ||
169
+ url.search ||
170
+ url.hash ||
171
+ url.origin !== new URL(root).origin) {
172
+ return undefined;
173
+ }
174
+ return url.toString();
175
+ }
176
+ function canonicalRuntimeUrl(value, base) {
177
+ const url = new URL(value, base);
178
+ if ((url.protocol !== "http:" && url.protocol !== "https:") ||
179
+ url.username ||
180
+ url.password ||
181
+ url.search ||
182
+ url.hash) {
183
+ throw new HonuaCapabilityNotSupportedError("query", "stac", "static STAC URLs must be credential-free HTTP(S) URLs without query parameters or fragments");
184
+ }
185
+ return url.toString();
186
+ }
187
+ function normalizeRuntimePolicy(value) {
188
+ if (value === undefined) {
189
+ return Object.freeze({
190
+ maxDocuments: DEFAULT_MAX_DOCUMENTS,
191
+ maxDepth: DEFAULT_MAX_DEPTH,
192
+ maxLinksPerDocument: DEFAULT_MAX_LINKS_PER_DOCUMENT,
193
+ maxDocumentBytes: DEFAULT_MAX_DOCUMENT_BYTES,
194
+ });
195
+ }
196
+ const maxDocuments = runtimeBound(value.maxDocuments, 1, HARD_MAX_DOCUMENTS, "maxDocuments");
197
+ const maxDepth = runtimeBound(value.maxDepth, 1, HARD_MAX_DEPTH, "maxDepth");
198
+ const maxLinksPerDocument = runtimeBound(value.maxLinksPerDocument, 1, HARD_MAX_LINKS_PER_DOCUMENT, "maxLinksPerDocument");
199
+ const maxDocumentBytes = runtimeBound(value.maxDocumentBytes, 1, HARD_MAX_DOCUMENT_BYTES, "maxDocumentBytes");
200
+ // Validate the complete admitted locator even though asset inspection and
201
+ // HEAD-probe budgets are used only by connect-time discovery.
202
+ runtimeBound(value.maxAssets, 1, 1024, "maxAssets");
203
+ runtimeBound(value.maxAssetProbes, 0, 16, "maxAssetProbes");
204
+ return Object.freeze({ maxDocuments, maxDepth, maxLinksPerDocument, maxDocumentBytes });
205
+ }
206
+ function runtimeBound(value, minimum, maximum, name) {
207
+ if (!Number.isSafeInteger(value) || value < minimum || value > maximum) {
208
+ throw new HonuaCapabilityNotSupportedError("query", "stac", `static STAC ${name} must be an integer between ${minimum} and ${maximum}`);
209
+ }
210
+ return value;
211
+ }
212
+ async function readBoundedJson(response, maximum) {
213
+ const advertised = response.headers.get("content-length");
214
+ if (advertised !== null && Number(advertised) > maximum) {
215
+ await response.body?.cancel().catch(() => undefined);
216
+ throw new Error(`static STAC response exceeds the ${maximum}-byte limit`);
217
+ }
218
+ if (!response.body)
219
+ throw new Error("static STAC response body is empty");
220
+ const reader = response.body.getReader();
221
+ const chunks = [];
222
+ let length = 0;
223
+ try {
224
+ for (;;) {
225
+ const { done, value } = await reader.read();
226
+ if (done)
227
+ break;
228
+ length += value.byteLength;
229
+ if (length > maximum) {
230
+ await reader.cancel().catch(() => undefined);
231
+ throw new Error(`static STAC response exceeds the ${maximum}-byte limit`);
232
+ }
233
+ chunks.push(value);
234
+ }
235
+ }
236
+ finally {
237
+ reader.releaseLock();
238
+ }
239
+ const bytes = new Uint8Array(length);
240
+ let offset = 0;
241
+ for (const chunk of chunks) {
242
+ bytes.set(chunk, offset);
243
+ offset += chunk.byteLength;
244
+ }
245
+ let value;
246
+ try {
247
+ value = JSON.parse(new TextDecoder().decode(bytes));
248
+ }
249
+ catch {
250
+ throw new Error("static STAC response contained invalid JSON");
251
+ }
252
+ const violation = boundedJsonStructureViolation(value, MAX_STATIC_JSON_DEPTH, MAX_STATIC_JSON_NODES);
253
+ if (violation === "max-depth") {
254
+ throw new Error(`static STAC response exceeds the ${MAX_STATIC_JSON_DEPTH}-level nesting limit`);
255
+ }
256
+ if (violation === "max-nodes") {
257
+ throw new Error(`static STAC response exceeds the ${MAX_STATIC_JSON_NODES}-node structural limit`);
258
+ }
259
+ if (violation === "non-json")
260
+ throw new Error("static STAC response contains non-finite JSON data");
261
+ return value;
262
+ }
263
+ function mediaEssence(value) {
264
+ return value.split(";", 1)[0]?.trim().toLowerCase() ?? "";
265
+ }
266
+ function isPlainObject(value) {
267
+ if (typeof value !== "object" || value === null || Array.isArray(value))
268
+ return false;
269
+ const prototype = Object.getPrototypeOf(value);
270
+ return prototype === Object.prototype || prototype === null;
271
+ }
272
+ function throwIfRuntimeAborted(signal) {
273
+ if (signal?.aborted)
274
+ throw new HonuaAbortError();
275
+ }
276
+ function isMinimallyValidStacItem(value) {
277
+ if (value.type !== "Feature" ||
278
+ typeof value.stac_version !== "string" ||
279
+ !value.stac_version.trim() ||
280
+ value.stac_version.length > 64 ||
281
+ typeof value.id !== "string" ||
282
+ !value.id.trim() ||
283
+ value.id.length > 512 ||
284
+ !isPlainObject(value.properties) ||
285
+ !Array.isArray(value.links) ||
286
+ !isPlainObject(value.assets)) {
287
+ return false;
288
+ }
289
+ if (value.stac_extensions !== undefined &&
290
+ (!Array.isArray(value.stac_extensions) || value.stac_extensions.some((entry) => typeof entry !== "string"))) {
291
+ return false;
292
+ }
293
+ if (value.collection !== undefined && (typeof value.collection !== "string" || !value.collection.trim())) {
294
+ return false;
295
+ }
296
+ if (!Object.hasOwn(value, "geometry") || !isRuntimeGeometry(value.geometry))
297
+ return false;
298
+ if (value.bbox !== undefined && !isRuntimeBbox(value.bbox))
299
+ return false;
300
+ if (value.links.some((link) => !isPlainObject(link) ||
301
+ typeof link.rel !== "string" ||
302
+ !link.rel.trim() ||
303
+ typeof link.href !== "string" ||
304
+ !link.href.trim())) {
305
+ return false;
306
+ }
307
+ if (Object.values(value.assets).some((asset) => !isPlainObject(asset) || typeof asset.href !== "string" || !asset.href.trim())) {
308
+ return false;
309
+ }
310
+ const datetime = value.properties.datetime;
311
+ if (datetime === null) {
312
+ return validRuntimeDatetime(value.properties.start_datetime) && validRuntimeDatetime(value.properties.end_datetime);
313
+ }
314
+ return validRuntimeDatetime(datetime);
315
+ }
316
+ function isRuntimeGeometry(value) {
317
+ if (value === null)
318
+ return true;
319
+ if (!isPlainObject(value) || typeof value.type !== "string")
320
+ return false;
321
+ if (value.type === "GeometryCollection")
322
+ return Array.isArray(value.geometries);
323
+ return ((value.type === "Point" ||
324
+ value.type === "MultiPoint" ||
325
+ value.type === "LineString" ||
326
+ value.type === "MultiLineString" ||
327
+ value.type === "Polygon" ||
328
+ value.type === "MultiPolygon") &&
329
+ Array.isArray(value.coordinates));
330
+ }
331
+ function isRuntimeBbox(value) {
332
+ return (Array.isArray(value) &&
333
+ (value.length === 4 || value.length === 6) &&
334
+ value.every((entry) => typeof entry === "number" && Number.isFinite(entry)) &&
335
+ value[0] <= value[value.length / 2] &&
336
+ value[1] <= value[value.length / 2 + 1] &&
337
+ (value.length === 4 || value[2] <= value[5]));
338
+ }
339
+ function validRuntimeDatetime(value) {
340
+ return typeof value === "string" && value.length <= 128 && !Number.isNaN(Date.parse(value));
341
+ }
342
+ function matchesFilters(item, params) {
343
+ const doc = item;
344
+ if (params.collections && params.collections.length > 0) {
345
+ if (typeof doc.collection !== "string" || !params.collections.includes(doc.collection))
346
+ return false;
347
+ }
348
+ if (params.bbox && Array.isArray(doc.bbox) && doc.bbox.length >= 4) {
349
+ if (!bboxIntersects(params.bbox, doc.bbox))
350
+ return false;
351
+ }
352
+ if (params.datetime) {
353
+ if (!datetimeMatches(params.datetime, doc))
354
+ return false;
355
+ }
356
+ return true;
357
+ }
358
+ function bboxIntersects(a, b) {
359
+ // 2D envelope intersection; ignores any z components.
360
+ const [axmin, aymin, axmax, aymax] = a;
361
+ const bxmin = b[0];
362
+ const bymin = b[1];
363
+ // A 4- or 6-tuple bbox: max x/y are the second half.
364
+ const bxmax = b.length >= 6 ? b[3] : b[2];
365
+ const bymax = b.length >= 6 ? b[4] : b[3];
366
+ return axmin <= bxmax && axmax >= bxmin && aymin <= bymax && aymax >= bymin;
367
+ }
368
+ function datetimeMatches(query, doc) {
369
+ const dt = typeof doc.properties?.datetime === "string" ? doc.properties.datetime : undefined;
370
+ // Closed / open interval query per STAC (`start/end`, `../end`, `start/..`).
371
+ if (query.includes("/")) {
372
+ const [rawStart, rawEnd] = query.split("/");
373
+ const start = rawStart && rawStart !== ".." ? Date.parse(rawStart) : Number.NEGATIVE_INFINITY;
374
+ const end = rawEnd && rawEnd !== ".." ? Date.parse(rawEnd) : Number.POSITIVE_INFINITY;
375
+ const startProp = typeof doc.properties?.start_datetime === "string" ? doc.properties.start_datetime : dt;
376
+ const endProp = typeof doc.properties?.end_datetime === "string" ? doc.properties.end_datetime : dt;
377
+ if (!startProp && !endProp)
378
+ return false;
379
+ const itemStart = startProp ? Date.parse(startProp) : Number.NEGATIVE_INFINITY;
380
+ const itemEnd = endProp ? Date.parse(endProp) : Number.POSITIVE_INFINITY;
381
+ return itemStart <= end && itemEnd >= start;
382
+ }
383
+ if (!dt)
384
+ return false;
385
+ return Date.parse(dt) === Date.parse(query);
386
+ }
package/core/stac.d.ts CHANGED
@@ -11,6 +11,13 @@ import type { HonuaProtocolTransport } from "./protocol-transport.js";
11
11
  import type { HonuaOgcCollectionMetadata, HonuaOgcCollectionsResponse, HonuaStacItemCollectionResponse, HonuaStacItemResponse, HonuaStacLandingResponse, OgcCollectionRequest, OgcMetadataRequest, StacSearchRequest } from "./types.js";
12
12
  export interface HonuaStacSearchOptions {
13
13
  client: HonuaClient;
14
+ /**
15
+ * Path prefix the STAC endpoints are mounted under. Defaults to `/stac`
16
+ * (Honua facade). Pass `""` for a raw STAC API root (e.g. Earth Search
17
+ * served at `.../v1`), so the search / collection paths resolve directly
18
+ * under the client baseUrl.
19
+ */
20
+ basePath?: string;
14
21
  }
15
22
  export interface HonuaStacSearchAllRequest extends StacSearchRequest {
16
23
  /** Maximum number of items to materialize across pages. */
@@ -25,7 +32,10 @@ export interface HonuaStacSearchAllRequest extends StacSearchRequest {
25
32
  */
26
33
  export declare class HonuaStacSearch {
27
34
  readonly client: HonuaClient;
35
+ private readonly basePath;
28
36
  constructor(options: HonuaStacSearchOptions);
37
+ /** Inject the configured STAC base path unless the caller already set one. */
38
+ private withBase;
29
39
  landing(request?: OgcMetadataRequest): Promise<HonuaStacLandingResponse>;
30
40
  collections(request?: OgcMetadataRequest): Promise<HonuaOgcCollectionsResponse>;
31
41
  collection(request: OgcCollectionRequest): Promise<HonuaOgcCollectionMetadata>;
@@ -35,6 +45,7 @@ export declare class HonuaStacSearch {
35
45
  signal?: AbortSignal;
36
46
  responseFormat?: string;
37
47
  extraParams?: Record<string, string | number | boolean>;
48
+ stacBasePath?: string;
38
49
  }): Promise<HonuaStacItemResponse>;
39
50
  search(request?: StacSearchRequest): Promise<HonuaStacItemCollectionResponse>;
40
51
  searchAll(request?: HonuaStacSearchAllRequest): Promise<HonuaStacItemResponse[]>;
@@ -50,6 +61,7 @@ export declare function getStacItem(transport: HonuaProtocolTransport, request:
50
61
  signal?: AbortSignal;
51
62
  responseFormat?: string;
52
63
  extraParams?: Record<string, string | number | boolean>;
64
+ stacBasePath?: string;
53
65
  }): Promise<HonuaStacItemResponse>;
54
66
  export declare function searchStac(transport: HonuaProtocolTransport, request?: StacSearchRequest): Promise<HonuaStacItemCollectionResponse>;
55
67
  //# sourceMappingURL=stac.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"stac.d.ts","sourceRoot":"","sources":["../../../src/core/stac.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAC/C,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,yBAAyB,CAAC;AACtE,OAAO,KAAK,EACV,0BAA0B,EAC1B,2BAA2B,EAC3B,+BAA+B,EAC/B,qBAAqB,EACrB,wBAAwB,EACxB,oBAAoB,EACpB,kBAAkB,EAClB,iBAAiB,EAClB,MAAM,YAAY,CAAC;AAGpB,MAAM,WAAW,sBAAsB;IACrC,MAAM,EAAE,WAAW,CAAC;CACrB;AAED,MAAM,WAAW,yBAA0B,SAAQ,iBAAiB;IAClE,2DAA2D;IAC3D,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,kEAAkE;IAClE,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAKD;;;;GAIG;AACH,qBAAa,eAAe;IAC1B,SAAgB,MAAM,EAAE,WAAW,CAAC;gBAEjB,OAAO,EAAE,sBAAsB;IAIrC,OAAO,CAAC,OAAO,GAAE,kBAAuB,GAAG,OAAO,CAAC,wBAAwB,CAAC;IAI5E,WAAW,CAAC,OAAO,GAAE,kBAAuB,GAAG,OAAO,CAAC,2BAA2B,CAAC;IAInF,UAAU,CAAC,OAAO,EAAE,oBAAoB,GAAG,OAAO,CAAC,0BAA0B,CAAC;IAI9E,IAAI,CAAC,OAAO,EAAE;QACzB,YAAY,EAAE,MAAM,GAAG,MAAM,CAAC;QAC9B,MAAM,EAAE,MAAM,GAAG,MAAM,CAAC;QACxB,MAAM,CAAC,EAAE,WAAW,CAAC;QACrB,cAAc,CAAC,EAAE,MAAM,CAAC;QACxB,WAAW,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC,CAAC;KACzD,GAAG,OAAO,CAAC,qBAAqB,CAAC;IAIrB,MAAM,CAAC,OAAO,GAAE,iBAAsB,GAAG,OAAO,CAAC,+BAA+B,CAAC;IAIjF,SAAS,CAAC,OAAO,GAAE,yBAA8B,GAAG,OAAO,CAAC,qBAAqB,EAAE,CAAC;IAyBnF,YAAY,CACxB,OAAO,GAAE,yBAA8B,GACtC,cAAc,CAAC,qBAAqB,EAAE,EAAE,IAAI,EAAE,SAAS,CAAC;CAoB5D;AAkCD,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,WAAW,GAAG,eAAe,CAE1E;AAID,wBAAsB,cAAc,CAClC,SAAS,EAAE,sBAAsB,EACjC,OAAO,GAAE,kBAAuB,GAC/B,OAAO,CAAC,wBAAwB,CAAC,CAOnC;AAED,wBAAsB,mBAAmB,CACvC,SAAS,EAAE,sBAAsB,EACjC,OAAO,GAAE,kBAAuB,GAC/B,OAAO,CAAC,2BAA2B,CAAC,CAOtC;AAED,wBAAsB,iBAAiB,CACrC,SAAS,EAAE,sBAAsB,EACjC,OAAO,EAAE,oBAAoB,GAC5B,OAAO,CAAC,0BAA0B,CAAC,CAOrC;AAED,wBAAsB,WAAW,CAC/B,SAAS,EAAE,sBAAsB,EACjC,OAAO,EAAE;IACP,YAAY,EAAE,MAAM,GAAG,MAAM,CAAC;IAC9B,MAAM,EAAE,MAAM,GAAG,MAAM,CAAC;IACxB,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,WAAW,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC,CAAC;CACzD,GACA,OAAO,CAAC,qBAAqB,CAAC,CAMhC;AAED,wBAAsB,UAAU,CAC9B,SAAS,EAAE,sBAAsB,EACjC,OAAO,GAAE,iBAAsB,GAC9B,OAAO,CAAC,+BAA+B,CAAC,CAmB1C"}
1
+ {"version":3,"file":"stac.d.ts","sourceRoot":"","sources":["../../../src/core/stac.ts"],"names":[],"mappings":"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"}
package/core/stac.js CHANGED
@@ -16,23 +16,31 @@ const DEFAULT_STAC_MAX_PAGES = 100;
16
16
  */
17
17
  export class HonuaStacSearch {
18
18
  client;
19
+ basePath;
19
20
  constructor(options) {
20
21
  this.client = options.client;
22
+ this.basePath = options.basePath;
23
+ }
24
+ /** Inject the configured STAC base path unless the caller already set one. */
25
+ withBase(request) {
26
+ if (this.basePath === undefined || request.stacBasePath !== undefined)
27
+ return request;
28
+ return { ...request, stacBasePath: this.basePath };
21
29
  }
22
30
  async landing(request = {}) {
23
- return this.client.getStacLanding(request);
31
+ return this.client.getStacLanding(this.withBase(request));
24
32
  }
25
33
  async collections(request = {}) {
26
- return this.client.listStacCollections(request);
34
+ return this.client.listStacCollections(this.withBase(request));
27
35
  }
28
36
  async collection(request) {
29
- return this.client.getStacCollection(request);
37
+ return this.client.getStacCollection(this.withBase(request));
30
38
  }
31
39
  async item(request) {
32
- return this.client.getStacItem(request);
40
+ return this.client.getStacItem(this.withBase(request));
33
41
  }
34
42
  async search(request = {}) {
35
- return this.client.searchStac(request);
43
+ return this.client.searchStac(this.withBase(request));
36
44
  }
37
45
  async searchAll(request = {}) {
38
46
  const pageSize = request.pageSize ?? request.limit ?? DEFAULT_STAC_PAGE_SIZE;
@@ -40,12 +48,12 @@ export class HonuaStacSearch {
40
48
  const items = [];
41
49
  let cursor = { offset: request.offset, next: request.next };
42
50
  for (let page = 0; page < maxPages; page += 1) {
43
- const response = await this.client.searchStac({
51
+ const response = await this.client.searchStac(this.withBase({
44
52
  ...request,
45
53
  limit: pageSize,
46
54
  offset: cursor.offset,
47
55
  next: cursor.next,
48
- });
56
+ }));
49
57
  const pageItems = response.features ?? [];
50
58
  if (pageItems.length === 0)
51
59
  break;
@@ -65,12 +73,12 @@ export class HonuaStacSearch {
65
73
  const maxPages = request.maxPages ?? DEFAULT_STAC_MAX_PAGES;
66
74
  let cursor = { offset: request.offset, next: request.next };
67
75
  for (let page = 0; page < maxPages; page += 1) {
68
- const response = await this.client.searchStac({
76
+ const response = await this.client.searchStac(this.withBase({
69
77
  ...request,
70
78
  limit: pageSize,
71
79
  offset: cursor.offset,
72
80
  next: cursor.next,
73
- });
81
+ }));
74
82
  const pageItems = response.features ?? [];
75
83
  if (pageItems.length === 0)
76
84
  break;
@@ -118,33 +126,55 @@ export function createHonuaStacSearch(client) {
118
126
  return new HonuaStacSearch({ client });
119
127
  }
120
128
  // ── STAC API wire methods ───────────────────────────────────────
129
+ /**
130
+ * Path prefix the STAC endpoints are mounted under. Defaults to `/stac`
131
+ * (the Honua Server facade). Backend-agnostic callers pointing at a raw
132
+ * STAC API root pass `""` so the paths resolve directly under the client
133
+ * baseUrl (e.g. Earth Search served at `.../v1`).
134
+ */
135
+ function stacBase(request) {
136
+ const base = request.stacBasePath;
137
+ if (base === undefined)
138
+ return "/stac";
139
+ // Trim trailing slashes; an explicit "" means "the client baseUrl is the
140
+ // STAC API root".
141
+ let end = base.length;
142
+ while (end > 0 && base.charCodeAt(end - 1) === 0x2f)
143
+ end--;
144
+ return base.slice(0, end);
145
+ }
121
146
  export async function getStacLanding(transport, request = {}) {
122
147
  const params = createOgcMetadataParams(request);
123
- return transport.requestCachedMetadataJson(`stac:landing:${params.toString()}`, `/stac?${params.toString()}`, request);
148
+ const base = stacBase(request);
149
+ return transport.requestCachedMetadataJson(`stac:landing:${base}:${params.toString()}`, `${base}?${params.toString()}`, request);
124
150
  }
125
151
  export async function listStacCollections(transport, request = {}) {
126
152
  const params = createOgcMetadataParams(request);
127
- return transport.requestCachedMetadataJson(`stac:collections:${params.toString()}`, `/stac/collections?${params.toString()}`, request);
153
+ const base = stacBase(request);
154
+ return transport.requestCachedMetadataJson(`stac:collections:${base}:${params.toString()}`, `${base}/collections?${params.toString()}`, request);
128
155
  }
129
156
  export async function getStacCollection(transport, request) {
130
157
  const params = createOgcMetadataParams(request);
131
- return transport.requestCachedMetadataJson(`stac:collection:${request.collectionId}:${params.toString()}`, `/stac/collections/${encodeURIComponent(String(request.collectionId))}?${params.toString()}`, request);
158
+ const base = stacBase(request);
159
+ return transport.requestCachedMetadataJson(`stac:collection:${base}:${request.collectionId}:${params.toString()}`, `${base}/collections/${encodeURIComponent(String(request.collectionId))}?${params.toString()}`, request);
132
160
  }
133
161
  export async function getStacItem(transport, request) {
134
162
  const params = createOgcMetadataParams(request);
135
- const path = `/stac/collections/${encodeURIComponent(String(request.collectionId))}` +
163
+ const base = stacBase(request);
164
+ const path = `${base}/collections/${encodeURIComponent(String(request.collectionId))}` +
136
165
  `/items/${encodeURIComponent(String(request.itemId))}`;
137
166
  return transport.requestJson("GET", `${path}?${params.toString()}`, undefined, request.signal);
138
167
  }
139
168
  export async function searchStac(transport, request = {}) {
169
+ const base = stacBase(request);
140
170
  if (request.usePost) {
141
- return transport.requestJson("POST", "/stac/search", {
171
+ return transport.requestJson("POST", `${base}/search`, {
142
172
  headers: mergeHeaders({ "Content-Type": "application/json", Accept: "application/json" }),
143
173
  body: JSON.stringify(stacSearchBody(request)),
144
174
  }, request.signal);
145
175
  }
146
176
  const params = serializeStacSearchParams(request);
147
- return transport.requestJson("GET", `/stac/search?${params.toString()}`, undefined, request.signal);
177
+ return transport.requestJson("GET", `${base}/search?${params.toString()}`, undefined, request.signal);
148
178
  }
149
179
  function serializeStacSearchParams(request) {
150
180
  const params = new URLSearchParams();
@@ -3,6 +3,7 @@ import type { SpatialAggregationRequest, SpatialAggregationResult } from "../con
3
3
  import type { SourceId } from "../contract/types.js";
4
4
  import type { HonuaMetadataRequestOptions } from "./cache-state.js";
5
5
  import type { HonuaClient } from "./client.js";
6
+ import type { OgcApiLayoutMode } from "./ogc-endpoint-layout.js";
6
7
  import type { ApplyEditsRequest, ExportMapRequest, HonuaAddAttachmentResponse, HonuaApplyEditsResponse, HonuaAttachmentListResponse, HonuaDeleteAttachmentsResponse, HonuaExportMapResponse, HonuaExtent, HonuaFeature, HonuaFindResponse, HonuaIdentifyResponse, HonuaLayerMetadata, HonuaLegendResponse, HonuaOgcCollectionMetadata, HonuaOgcCollectionsResponse, HonuaOgcConformanceResponse, HonuaOgcFeatureCollectionResponse, HonuaOgcFeatureResponse, HonuaOgcLandingResponse, HonuaOgcQueryablesResponse, HonuaQueryAttachmentsResponse, HonuaQueryResponse, HonuaRawRequest, HonuaRelatedRecordsResponse, HonuaServiceMetadata, HonuaTypedFeature, HonuaTypedQueryResponse, HonuaUpdateAttachmentResponse, MapFindRequest, MapIdentifyRequest, MapLayerQueryRequest, MapLegendRequest, MapRelatedRecordsRequest, OgcCollectionRequest, OgcCreateItemRequest, OgcDeleteItemRequest, OgcItemRequest, OgcItemsRequest, OgcMetadataRequest, OgcPatchItemRequest, OgcReplaceItemRequest, QueryFeaturesRequest, QueryMethod, QueryRelatedRecordsRequest } from "./types.js";
7
8
  export interface HonuaServiceOptions {
8
9
  client: HonuaClient;
@@ -231,14 +232,24 @@ export declare class HonuaMapLayer {
231
232
  }
232
233
  export interface HonuaOgcFeaturesOptions {
233
234
  client: HonuaClient;
235
+ /**
236
+ * Endpoint-layout discovery mode. `honua-facade` (default) uses the fixed
237
+ * `/ogc/features/...` fast path; `ogc-api` / `auto` discover the layout
238
+ * from the server landing page so the surface works against raw pygeoapi
239
+ * / ldproxy / GeoServer OGC API endpoints.
240
+ */
241
+ layout?: OgcApiLayoutMode;
234
242
  }
235
243
  export interface HonuaOgcFeatureCollectionOptions {
236
244
  client: HonuaClient;
237
245
  collectionId: string | number;
246
+ layout?: OgcApiLayoutMode;
238
247
  }
239
248
  export declare class HonuaOgcFeatures {
240
249
  readonly client: HonuaClient;
250
+ private readonly layoutMode;
241
251
  constructor(options: HonuaOgcFeaturesOptions);
252
+ private layout;
242
253
  collection(collectionId: string | number): HonuaOgcFeatureCollection;
243
254
  landing(request?: HonuaOgcMetadataRequest): Promise<HonuaOgcLandingResponse>;
244
255
  conformance(request?: HonuaOgcMetadataRequest): Promise<HonuaOgcConformanceResponse>;
@@ -256,7 +267,9 @@ export declare class HonuaOgcFeatures {
256
267
  export declare class HonuaOgcFeatureCollection {
257
268
  readonly client: HonuaClient;
258
269
  readonly collectionId: string | number;
270
+ private readonly layoutMode;
259
271
  constructor(options: HonuaOgcFeatureCollectionOptions);
272
+ private layout;
260
273
  metadata(request?: HonuaOgcMetadataRequest): Promise<HonuaOgcCollectionMetadata>;
261
274
  queryables(request?: HonuaOgcMetadataRequest): Promise<HonuaOgcQueryablesResponse>;
262
275
  items(request?: HonuaOgcCollectionItemsRequest): Promise<HonuaOgcFeatureCollectionResponse>;