@honua/app-platform 0.1.2-beta.0 → 0.1.7-beta.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (400) hide show
  1. package/app/index.d.ts.map +1 -1
  2. package/app/index.js +4 -0
  3. package/collaboration/client.d.ts.map +1 -1
  4. package/connect-url-safety.d.ts +53 -0
  5. package/connect-url-safety.js +189 -0
  6. package/contract/discovery.d.ts +34 -0
  7. package/contract/discovery.d.ts.map +1 -1
  8. package/contract/index.d.ts +7 -5
  9. package/contract/index.d.ts.map +1 -1
  10. package/contract/index.js +3 -2
  11. package/contract/pmtiles.d.ts +10 -1
  12. package/contract/pmtiles.d.ts.map +1 -1
  13. package/contract/pmtiles.js +10 -2
  14. package/contract/protocol-module.d.ts +84 -9
  15. package/contract/protocol-module.d.ts.map +1 -1
  16. package/contract/query-filter-fes.d.ts +18 -0
  17. package/contract/query-filter-fes.d.ts.map +1 -0
  18. package/contract/query-filter-fes.js +122 -0
  19. package/contract/query-filter.d.ts +300 -0
  20. package/contract/query-filter.d.ts.map +1 -0
  21. package/contract/query-filter.js +883 -0
  22. package/contract/schema.d.ts +12 -0
  23. package/contract/schema.d.ts.map +1 -1
  24. package/contract/schema.js +34 -0
  25. package/contract/source-capability-support.d.ts.map +1 -1
  26. package/contract/source-capability-support.js +15 -3
  27. package/contract/source.d.ts +45 -25
  28. package/contract/source.d.ts.map +1 -1
  29. package/contract/source.js +719 -479
  30. package/contract/types.d.ts +88 -19
  31. package/contract/types.d.ts.map +1 -1
  32. package/contract/types.js +4 -0
  33. package/contract/widget-source.d.ts.map +1 -1
  34. package/controls/basemap-switcher.d.ts +19 -11
  35. package/controls/basemap-switcher.d.ts.map +1 -1
  36. package/controls/basemap-switcher.js +58 -25
  37. package/controls/catalog.d.ts +105 -0
  38. package/controls/catalog.d.ts.map +1 -0
  39. package/controls/catalog.js +341 -0
  40. package/controls/element-utils.d.ts +9 -0
  41. package/controls/element-utils.d.ts.map +1 -1
  42. package/controls/element-utils.js +17 -0
  43. package/controls/index.d.ts +29 -8
  44. package/controls/index.d.ts.map +1 -1
  45. package/controls/index.js +26 -8
  46. package/controls/layer-list.d.ts +13 -5
  47. package/controls/layer-list.d.ts.map +1 -1
  48. package/controls/layer-list.js +72 -15
  49. package/controls/legend.d.ts +32 -5
  50. package/controls/legend.d.ts.map +1 -1
  51. package/controls/legend.js +95 -25
  52. package/controls/qualification.d.ts +152 -0
  53. package/controls/qualification.d.ts.map +1 -0
  54. package/controls/qualification.js +1265 -0
  55. package/controls/registry.d.ts +104 -0
  56. package/controls/registry.d.ts.map +1 -0
  57. package/controls/registry.js +150 -0
  58. package/controls/swipe-control.d.ts +6 -1
  59. package/controls/swipe-control.d.ts.map +1 -1
  60. package/controls/swipe-control.js +82 -20
  61. package/core/client.d.ts +51 -21
  62. package/core/client.d.ts.map +1 -1
  63. package/core/client.js +234 -54
  64. package/core/error-classifications.d.ts +23 -0
  65. package/core/error-classifications.d.ts.map +1 -1
  66. package/core/error-classifications.js +23 -0
  67. package/core/error-code-registry.d.ts +24 -1
  68. package/core/error-code-registry.d.ts.map +1 -1
  69. package/core/error-code-registry.js +23 -0
  70. package/core/error-envelope.js +2 -0
  71. package/core/errors.d.ts +2 -2
  72. package/core/errors.d.ts.map +1 -1
  73. package/core/errors.js +1 -1
  74. package/core/geoparquet-sql.d.ts +69 -5
  75. package/core/geoparquet-sql.d.ts.map +1 -1
  76. package/core/geoparquet-sql.js +327 -10
  77. package/core/odata-write-codec.d.ts.map +1 -1
  78. package/core/odata-write-codec.js +0 -1
  79. package/core/odata.d.ts +6 -0
  80. package/core/odata.d.ts.map +1 -1
  81. package/core/odata.js +6 -1
  82. package/core/ogc-processes.d.ts +182 -25
  83. package/core/ogc-processes.d.ts.map +1 -1
  84. package/core/ogc-processes.js +602 -30
  85. package/core/process-runner.d.ts +8 -1
  86. package/core/process-runner.d.ts.map +1 -1
  87. package/core/process-runner.js +1 -0
  88. package/core/protocol-transport.d.ts +2 -0
  89. package/core/protocol-transport.d.ts.map +1 -1
  90. package/core/stac.d.ts +32 -0
  91. package/core/stac.d.ts.map +1 -1
  92. package/core/stac.js +158 -23
  93. package/core/surfaces.d.ts.map +1 -1
  94. package/core/surfaces.js +273 -68
  95. package/core/types.d.ts +138 -11
  96. package/core/types.d.ts.map +1 -1
  97. package/core/wfs-axis-order.d.ts +18 -0
  98. package/core/wfs-axis-order.d.ts.map +1 -0
  99. package/core/wfs-axis-order.js +108 -0
  100. package/core/wfs-capabilities.d.ts +65 -24
  101. package/core/wfs-capabilities.d.ts.map +1 -1
  102. package/core/wfs-capabilities.js +237 -27
  103. package/core/wfs-filter.d.ts +26 -3
  104. package/core/wfs-filter.d.ts.map +1 -1
  105. package/core/wfs-filter.js +48 -53
  106. package/core/wfs-protocol-error.d.ts +12 -0
  107. package/core/wfs-protocol-error.d.ts.map +1 -0
  108. package/core/wfs-protocol-error.js +13 -0
  109. package/core/wfs.d.ts +53 -1
  110. package/core/wfs.d.ts.map +1 -1
  111. package/core/wfs.js +251 -29
  112. package/core/wms-feature-info.d.ts +92 -0
  113. package/core/wms-feature-info.d.ts.map +1 -0
  114. package/core/wms-feature-info.js +285 -0
  115. package/core/wms.d.ts +9 -0
  116. package/core/wms.d.ts.map +1 -1
  117. package/core/wms.js +17 -5
  118. package/esri-compat/event-bus.d.ts +20 -0
  119. package/esri-compat/event-bus.d.ts.map +1 -1
  120. package/esri-compat/feature-filter.d.ts +46 -1
  121. package/esri-compat/feature-filter.d.ts.map +1 -1
  122. package/esri-compat/feature-filter.js +49 -0
  123. package/esri-compat/geojson-layer.d.ts +4 -2
  124. package/esri-compat/geojson-layer.d.ts.map +1 -1
  125. package/esri-compat/geojson-layer.js +4 -2
  126. package/esri-compat/identity-manager.js +2 -2
  127. package/esri-compat/imagery-layer.d.ts +2 -1
  128. package/esri-compat/imagery-layer.d.ts.map +1 -1
  129. package/esri-compat/imagery-layer.js +2 -1
  130. package/esri-compat/layer-list.d.ts +8 -1
  131. package/esri-compat/layer-list.d.ts.map +1 -1
  132. package/esri-compat/layer-list.js +11 -4
  133. package/esri-compat/legend.d.ts.map +1 -1
  134. package/esri-compat/legend.js +1 -1
  135. package/esri-compat/locator.d.ts +297 -0
  136. package/esri-compat/locator.d.ts.map +1 -0
  137. package/esri-compat/locator.js +579 -0
  138. package/esri-compat/map-image-layer.d.ts.map +1 -1
  139. package/esri-compat/map-view.d.ts.map +1 -1
  140. package/esri-compat/map-view.js +1 -1
  141. package/esri-compat/scene-view.d.ts.map +1 -1
  142. package/esri-compat/time-slider.d.ts +29 -0
  143. package/esri-compat/time-slider.d.ts.map +1 -1
  144. package/esri-compat/time-slider.js +117 -0
  145. package/esri-compat/wfs-layer.d.ts +2 -1
  146. package/esri-compat/wfs-layer.d.ts.map +1 -1
  147. package/esri-compat/wfs-layer.js +2 -1
  148. package/esri-compat/widget-host.d.ts +42 -6
  149. package/esri-compat/widget-host.d.ts.map +1 -1
  150. package/esri-compat/widget-host.js +99 -10
  151. package/generated-app/projection.d.ts.map +1 -1
  152. package/generated-app/runtime.d.ts.map +1 -1
  153. package/interactions/declarative-contract.d.ts +149 -0
  154. package/interactions/declarative-contract.d.ts.map +1 -0
  155. package/interactions/declarative-contract.js +293 -0
  156. package/interactions/declarative.d.ts +198 -0
  157. package/interactions/declarative.d.ts.map +1 -0
  158. package/interactions/declarative.js +470 -0
  159. package/interactions/feature-state.d.ts +7 -0
  160. package/interactions/feature-state.d.ts.map +1 -1
  161. package/interactions/feature-state.js +10 -2
  162. package/map/data-to-map-bridge.d.ts +1 -1
  163. package/map/data-to-map-bridge.d.ts.map +1 -1
  164. package/map/data-to-map-bridge.js +1 -2
  165. package/map/honua-map.d.ts.map +1 -1
  166. package/map/honua-map.js +1 -1
  167. package/map/index.d.ts +2 -0
  168. package/map/index.d.ts.map +1 -1
  169. package/map/index.js +2 -0
  170. package/map/maplibre-target.js +1 -1
  171. package/map/realtime-reconciliation-adapter.d.ts +98 -0
  172. package/map/realtime-reconciliation-adapter.d.ts.map +1 -0
  173. package/map/realtime-reconciliation-adapter.js +84 -0
  174. package/map/temporal-playback.d.ts +25 -0
  175. package/map/temporal-playback.d.ts.map +1 -1
  176. package/map/temporal-playback.js +29 -1
  177. package/operator/workspace/workspace.d.ts.map +1 -1
  178. package/operator/workspace/workspace.js +0 -2
  179. package/package.json +4 -3
  180. package/query-planner/canonical-filter.d.ts +30 -0
  181. package/query-planner/canonical-filter.d.ts.map +1 -0
  182. package/query-planner/canonical-filter.js +30 -0
  183. package/query-planner/columnar-plan.d.ts +63 -0
  184. package/query-planner/columnar-plan.d.ts.map +1 -0
  185. package/query-planner/columnar-plan.js +94 -0
  186. package/query-planner/diagnostics.d.ts +3 -3
  187. package/query-planner/diagnostics.d.ts.map +1 -1
  188. package/query-planner/diagnostics.js +17 -4
  189. package/query-planner/duckdb.d.ts.map +1 -1
  190. package/query-planner/duckdb.js +16 -2
  191. package/query-planner/execution-context.d.ts.map +1 -1
  192. package/query-planner/execution-context.js +3 -1
  193. package/query-planner/executor.d.ts.map +1 -1
  194. package/query-planner/executor.js +7 -1
  195. package/query-planner/geoservices.d.ts.map +1 -1
  196. package/query-planner/geoservices.js +24 -5
  197. package/query-planner/grpc.d.ts.map +1 -1
  198. package/query-planner/grpc.js +29 -5
  199. package/query-planner/index.d.ts +20 -5
  200. package/query-planner/index.d.ts.map +1 -1
  201. package/query-planner/index.js +16 -3
  202. package/query-planner/ir.d.ts +6 -1
  203. package/query-planner/ir.d.ts.map +1 -1
  204. package/query-planner/ir.js +63 -1
  205. package/query-planner/odata-protocol-module.d.ts +54 -0
  206. package/query-planner/odata-protocol-module.d.ts.map +1 -0
  207. package/query-planner/odata-protocol-module.js +199 -0
  208. package/query-planner/odata-v1.d.ts +23 -0
  209. package/query-planner/odata-v1.d.ts.map +1 -0
  210. package/query-planner/odata-v1.js +202 -0
  211. package/query-planner/odata.d.ts +0 -3
  212. package/query-planner/odata.d.ts.map +1 -1
  213. package/query-planner/odata.js +0 -143
  214. package/query-planner/ogc-features.d.ts.map +1 -1
  215. package/query-planner/ogc-features.js +23 -1
  216. package/query-planner/planner.d.ts +2 -2
  217. package/query-planner/planner.d.ts.map +1 -1
  218. package/query-planner/planner.js +146 -27
  219. package/query-planner/representation.d.ts +59 -0
  220. package/query-planner/representation.d.ts.map +1 -0
  221. package/query-planner/representation.js +140 -0
  222. package/query-planner/types.d.ts +153 -3
  223. package/query-planner/types.d.ts.map +1 -1
  224. package/query-planner/wfs-protocol-module.d.ts +57 -0
  225. package/query-planner/wfs-protocol-module.d.ts.map +1 -0
  226. package/query-planner/wfs-protocol-module.js +731 -0
  227. package/query-planner/wfs-v1.d.ts +41 -0
  228. package/query-planner/wfs-v1.d.ts.map +1 -0
  229. package/query-planner/wfs-v1.js +366 -0
  230. package/query-planner/wfs.d.ts +1 -3
  231. package/query-planner/wfs.d.ts.map +1 -1
  232. package/query-planner/wfs.js +1 -70
  233. package/realtime/checkpoint-store.d.ts +206 -0
  234. package/realtime/checkpoint-store.d.ts.map +1 -0
  235. package/realtime/checkpoint-store.js +504 -0
  236. package/realtime/honua-server.d.ts +8 -0
  237. package/realtime/honua-server.d.ts.map +1 -1
  238. package/realtime/honua-server.js +123 -14
  239. package/realtime/index.d.ts +4 -0
  240. package/realtime/index.d.ts.map +1 -1
  241. package/realtime/index.js +2 -0
  242. package/realtime/reconciliation.d.ts +245 -0
  243. package/realtime/reconciliation.d.ts.map +1 -0
  244. package/realtime/reconciliation.js +377 -0
  245. package/realtime/resumable-transport.d.ts.map +1 -1
  246. package/realtime/sse.d.ts.map +1 -1
  247. package/realtime/sse.js +7 -1
  248. package/realtime/websocket.d.ts.map +1 -1
  249. package/realtime/wire.d.ts +2 -1
  250. package/realtime/wire.d.ts.map +1 -1
  251. package/realtime/wire.js +24 -8
  252. package/replica-sync/errors.d.ts +19 -1
  253. package/replica-sync/errors.d.ts.map +1 -1
  254. package/replica-sync/errors.js +16 -0
  255. package/replica-sync/geoservices.d.ts +260 -0
  256. package/replica-sync/geoservices.d.ts.map +1 -0
  257. package/replica-sync/geoservices.js +1093 -0
  258. package/replica-sync/index.d.ts +5 -1
  259. package/replica-sync/index.d.ts.map +1 -1
  260. package/replica-sync/index.js +3 -1
  261. package/replica-sync/transport-conformance.d.ts +58 -0
  262. package/replica-sync/transport-conformance.d.ts.map +1 -0
  263. package/replica-sync/transport-conformance.js +265 -0
  264. package/replica-sync/types.d.ts +22 -0
  265. package/replica-sync/types.d.ts.map +1 -1
  266. package/runtime/index.d.ts +6 -2
  267. package/runtime/index.d.ts.map +1 -1
  268. package/runtime/index.js +3 -1
  269. package/runtime/load-package.d.ts +24 -0
  270. package/runtime/load-package.d.ts.map +1 -1
  271. package/runtime/load-package.js +45 -1
  272. package/runtime/maplibre-renderer.d.ts +11 -0
  273. package/runtime/maplibre-renderer.d.ts.map +1 -1
  274. package/runtime/maplibre-renderer.js +32 -7
  275. package/runtime/offline-region-protocol.d.ts +73 -0
  276. package/runtime/offline-region-protocol.d.ts.map +1 -0
  277. package/runtime/offline-region-protocol.js +63 -0
  278. package/runtime/offline-region-style.d.ts +78 -0
  279. package/runtime/offline-region-style.d.ts.map +1 -0
  280. package/runtime/offline-region-style.js +147 -0
  281. package/runtime/pmtiles-protocol.d.ts +7 -8
  282. package/runtime/pmtiles-protocol.d.ts.map +1 -1
  283. package/runtime/pmtiles-protocol.js +19 -66
  284. package/runtime/protocol-registry.d.ts +55 -0
  285. package/runtime/protocol-registry.d.ts.map +1 -0
  286. package/runtime/protocol-registry.js +103 -0
  287. package/runtime/runtime.d.ts +2 -0
  288. package/runtime/runtime.d.ts.map +1 -1
  289. package/runtime/runtime.js +6 -1
  290. package/runtime/style-compose.d.ts.map +1 -1
  291. package/runtime/style-compose.js +78 -9
  292. package/scene-workspace/analysis-widgets.d.ts +4 -2
  293. package/scene-workspace/analysis-widgets.d.ts.map +1 -1
  294. package/scene-workspace/analysis-widgets.js +4 -2
  295. package/scene-workspace/camera-correspondence.d.ts +151 -0
  296. package/scene-workspace/camera-correspondence.d.ts.map +1 -0
  297. package/scene-workspace/camera-correspondence.js +220 -0
  298. package/scene-workspace/cesium-adapter.d.ts +230 -29
  299. package/scene-workspace/cesium-adapter.d.ts.map +1 -1
  300. package/scene-workspace/cesium-adapter.js +741 -56
  301. package/scene-workspace/cesium-mount.d.ts +205 -0
  302. package/scene-workspace/cesium-mount.d.ts.map +1 -0
  303. package/scene-workspace/cesium-mount.js +392 -0
  304. package/scene-workspace/cesium-scene-owner.d.ts +160 -0
  305. package/scene-workspace/cesium-scene-owner.d.ts.map +1 -0
  306. package/scene-workspace/cesium-scene-owner.js +205 -0
  307. package/scene-workspace/cesium-state-sync.d.ts +101 -0
  308. package/scene-workspace/cesium-state-sync.d.ts.map +1 -0
  309. package/scene-workspace/cesium-state-sync.js +432 -0
  310. package/scene-workspace/cesium-time.d.ts +346 -0
  311. package/scene-workspace/cesium-time.d.ts.map +1 -0
  312. package/scene-workspace/cesium-time.js +403 -0
  313. package/scene-workspace/index.d.ts +42 -10
  314. package/scene-workspace/index.d.ts.map +1 -1
  315. package/scene-workspace/index.js +32 -7
  316. package/scene-workspace/maplibre-state-sync.d.ts +112 -0
  317. package/scene-workspace/maplibre-state-sync.d.ts.map +1 -0
  318. package/scene-workspace/maplibre-state-sync.js +400 -0
  319. package/scene-workspace/primitives.d.ts +237 -3
  320. package/scene-workspace/primitives.d.ts.map +1 -1
  321. package/scene-workspace/primitives.js +1087 -17
  322. package/scene-workspace/scene-discovery.d.ts +5 -2
  323. package/scene-workspace/scene-discovery.d.ts.map +1 -1
  324. package/scene-workspace/scene-discovery.js +5 -2
  325. package/scene-workspace/scene-view.d.ts +5 -2
  326. package/scene-workspace/scene-view.d.ts.map +1 -1
  327. package/scene-workspace/scene-view.js +5 -2
  328. package/scene-workspace/source-to-cesium.d.ts +141 -8
  329. package/scene-workspace/source-to-cesium.d.ts.map +1 -1
  330. package/scene-workspace/source-to-cesium.js +497 -72
  331. package/scene-workspace/state-sync-port-kit.d.ts +156 -0
  332. package/scene-workspace/state-sync-port-kit.d.ts.map +1 -0
  333. package/scene-workspace/state-sync-port-kit.js +319 -0
  334. package/scene-workspace/state-sync.d.ts +34 -1
  335. package/scene-workspace/state-sync.d.ts.map +1 -1
  336. package/scene-workspace/state-sync.js +48 -0
  337. package/scene-workspace/types.d.ts +8 -0
  338. package/scene-workspace/types.d.ts.map +1 -1
  339. package/scene-workspace/types.js +2 -0
  340. package/scene-workspace/workspace.d.ts +12 -1
  341. package/scene-workspace/workspace.d.ts.map +1 -1
  342. package/scene-workspace/workspace.js +13 -0
  343. package/studio/index.d.ts +24 -7
  344. package/studio/index.d.ts.map +1 -1
  345. package/studio/index.js +21 -7
  346. package/studio/lifecycle-client.d.ts +164 -0
  347. package/studio/lifecycle-client.d.ts.map +1 -0
  348. package/studio/lifecycle-client.js +299 -0
  349. package/studio/lifecycle-errors.d.ts +91 -0
  350. package/studio/lifecycle-errors.d.ts.map +1 -0
  351. package/studio/lifecycle-errors.js +77 -0
  352. package/studio/lifecycle-types.d.ts +321 -0
  353. package/studio/lifecycle-types.d.ts.map +1 -0
  354. package/studio/lifecycle-types.js +22 -0
  355. package/web-components/controller.d.ts.map +1 -1
  356. package/web-components/csp-styles.d.ts +6 -0
  357. package/web-components/csp-styles.d.ts.map +1 -0
  358. package/web-components/csp-styles.js +53 -0
  359. package/web-components/elements.d.ts +158 -2
  360. package/web-components/elements.d.ts.map +1 -1
  361. package/web-components/elements.js +1302 -215
  362. package/web-components/export-redaction.d.ts +252 -0
  363. package/web-components/export-redaction.d.ts.map +1 -0
  364. package/web-components/export-redaction.js +588 -0
  365. package/web-components/export.d.ts +417 -0
  366. package/web-components/export.d.ts.map +1 -0
  367. package/web-components/export.js +719 -0
  368. package/web-components/feature-editor-model.d.ts +265 -0
  369. package/web-components/feature-editor-model.d.ts.map +1 -0
  370. package/web-components/feature-editor-model.js +438 -0
  371. package/web-components/feature-editor-sketch.d.ts +125 -0
  372. package/web-components/feature-editor-sketch.d.ts.map +1 -0
  373. package/web-components/feature-editor-sketch.js +194 -0
  374. package/web-components/feature-editor-workflow.d.ts +286 -0
  375. package/web-components/feature-editor-workflow.d.ts.map +1 -0
  376. package/web-components/feature-editor-workflow.js +889 -0
  377. package/web-components/feature-editor.d.ts +83 -0
  378. package/web-components/feature-editor.d.ts.map +1 -0
  379. package/web-components/feature-editor.js +829 -0
  380. package/web-components/feature-table-engine.d.ts +465 -0
  381. package/web-components/feature-table-engine.d.ts.map +1 -0
  382. package/web-components/feature-table-engine.js +1360 -0
  383. package/web-components/feature-table-view.d.ts +95 -0
  384. package/web-components/feature-table-view.d.ts.map +1 -0
  385. package/web-components/feature-table-view.js +343 -0
  386. package/web-components/index.d.ts +34 -3
  387. package/web-components/index.d.ts.map +1 -1
  388. package/web-components/index.js +35 -2
  389. package/web-components/maplibre-renderer.d.ts.map +1 -1
  390. package/web-components/maplibre-renderer.js +13 -4
  391. package/web-components/measurement.d.ts +3 -1
  392. package/web-components/measurement.d.ts.map +1 -1
  393. package/web-components/measurement.js +62 -18
  394. package/web-components/time-slider.d.ts +122 -0
  395. package/web-components/time-slider.d.ts.map +1 -0
  396. package/web-components/time-slider.js +740 -0
  397. package/web-components/types.d.ts +310 -0
  398. package/web-components/types.d.ts.map +1 -1
  399. package/webmap/convert-label.js +0 -1
  400. package/webmap/convert-symbol.d.ts.map +1 -1
@@ -0,0 +1,719 @@
1
+ /**
2
+ * The Honua component kit's **export-adapter contract** (issue #683, parent
3
+ * epic #678; REQ-001/REQ-002/REQ-003).
4
+ *
5
+ * ## Why an adapter at all
6
+ *
7
+ * `<honua-print-export>` has always offered three actions — print layout,
8
+ * renderer snapshot, sanitized state JSON — but only browser print was ever
9
+ * implemented. The other two were *correctly* reported as unsupported rather
10
+ * than being faked, because producing them safely needs something the
11
+ * component kit does not have and must not guess at:
12
+ *
13
+ * - A **renderer** it is allowed to read pixels from. `canvas.toDataURL()` on a
14
+ * WebGL map silently returns a blank image unless the map was created with
15
+ * `canvasContextAttributes: { preserveDrawingBuffer: true }` (MapLibre 5 and
16
+ * 6; earlier majors took a top-level `preserveDrawingBuffer`), and it throws on a canvas tainted by
17
+ * cross-origin tiles. Only the application that constructed the map knows
18
+ * which of those is true, and only it can decide whether reading those
19
+ * pixels is authorized.
20
+ * - An **authorization boundary**. A map's live state transitively references
21
+ * the credentials that made it work: signed tile URLs, `Authorization`
22
+ * headers, SAS query strings, OAuth scope detail. Serializing "the current
23
+ * state" naively hands all of that to whoever receives the file.
24
+ *
25
+ * So export is modelled as an explicit, injected capability. There is no
26
+ * ambient fallback that "does its best": with no adapter, snapshot and state
27
+ * export **fail closed** with {@link HonuaCapabilityNotSupportedError} and
28
+ * produce no bytes at all. That is the whole point — a missing adapter must
29
+ * never degrade into a silent, partially-credentialed artifact.
30
+ *
31
+ * ## The four things the contract makes explicit (REQ-001)
32
+ *
33
+ * 1. **Capability** — {@link HonuaExportAdapter.describeCapabilities} declares
34
+ * exactly which kinds the adapter implements. A kind it does not declare is
35
+ * refused before the adapter is called; a kind it declares but does not
36
+ * implement is a *reported* contract violation, never a silent no-op.
37
+ * 2. **Ownership** — every result carries {@link HonuaExportResult.ownership}
38
+ * and an always-present {@link HonuaExportResult.release}. `bytes` handed to
39
+ * the caller are the caller's to keep and remain valid after `release()`;
40
+ * `release()` frees only adapter-held resources (object URLs, offscreen
41
+ * canvases, server-side render jobs). Callers can therefore always call it
42
+ * exactly once, unconditionally, in a `finally`.
43
+ * 3. **Cancellation** — an {@link AbortSignal} is checked before the adapter
44
+ * runs, forwarded to it, and re-checked after it returns; a cancelled export
45
+ * releases whatever the adapter produced and yields `status: "cancelled"`
46
+ * with no bytes.
47
+ * 4. **Error reporting** — the runner never throws. It returns a discriminated
48
+ * {@link HonuaExportResult} whose `status` is one of
49
+ * `ready`/`unsupported`/`cancelled`/`error`, with a redacted `message` and
50
+ * the structured `error` for programmatic handling. Elements can render the
51
+ * envelope directly; callers who prefer exceptions use
52
+ * {@link assertHonuaExportReady}.
53
+ *
54
+ * ## Redaction is not the adapter's job (REQ-002)
55
+ *
56
+ * The runner sanitizes state **before** the adapter is invoked, so an adapter
57
+ * — including a third-party or server-side one — never receives credential
58
+ * material in the first place. It then re-scans everything the adapter returned
59
+ * (bytes, text, filename hint, fidelity warnings) and refuses to emit an
60
+ * artifact that fails the scan. Both layers live in
61
+ * {@link "./export-redaction.js"}; see that module for the two-layer model.
62
+ *
63
+ * ## Provenance travels with the artifact (REQ-003)
64
+ *
65
+ * Attribution, license notices, scale, export timestamp, data-freshness, and
66
+ * fidelity warnings are assembled by the runner from the map package and
67
+ * controller state, handed to the adapter, and echoed on the result. Where a
68
+ * source declares attribution, the export **cannot** drop it: an artifact
69
+ * missing required attribution fails closed exactly like a credential leak,
70
+ * because shipping an unattributed map is a licence violation, not a cosmetic
71
+ * defect.
72
+ *
73
+ * ## Bundle posture (NFR-001)
74
+ *
75
+ * This module and its redaction sibling import nothing but the SDK's own error
76
+ * base and types. No renderer, PDF writer, image encoder, localization
77
+ * framework, or test-only peer is referenced, statically or dynamically —
78
+ * every one of those lives on the application side of the adapter seam. The
79
+ * built-in adapters here are thin structural shims over objects the caller
80
+ * already owns.
81
+ *
82
+ * @module
83
+ */
84
+ import { HonuaSdkError } from "../core/error-envelope.js";
85
+ import { HonuaAbortError, HonuaCapabilityNotSupportedError } from "../core/errors.js";
86
+ import { HonuaExportSafetyError, assertCredentialFreeExportBytes, assertCredentialFreeExportText, redactHonuaExportText, sanitizeHonuaExportFilename, sanitizeHonuaExportState, } from "./export-redaction.js";
87
+ /** Protocol label used in capability errors raised by the export pipeline. */
88
+ const EXPORT_PROTOCOL = "honua-app-platform-export";
89
+ /** Every export kind, in a stable order. */
90
+ export const HONUA_EXPORT_KINDS = Object.freeze(["print", "snapshot", "state"]);
91
+ /** Thrown by {@link assertHonuaExportReady} and used to wrap adapter faults. */
92
+ export class HonuaExportError extends HonuaSdkError {
93
+ status;
94
+ constructor(status, message, options = {}) {
95
+ super("app.export-failed", message, options);
96
+ this.name = "HonuaExportError";
97
+ this.status = status;
98
+ }
99
+ }
100
+ // ── provenance ───────────────────────────────────────────────────────────
101
+ /** `1:24,000`-style approximate scale for a web-mercator viewport at 96 DPI. */
102
+ export function approximateHonuaScaleLabel(viewport) {
103
+ const zoom = viewport?.zoom;
104
+ if (!Number.isFinite(zoom))
105
+ return undefined;
106
+ const latitude = viewport?.center?.[1] ?? 0;
107
+ const metersPerPixel = (156543.03392 * Math.cos((latitude * Math.PI) / 180)) / 2 ** Number(zoom);
108
+ const scale = Math.max(1, Math.round(metersPerPixel * 96 * 39.37));
109
+ return `1:${scale.toLocaleString("en-US")}`;
110
+ }
111
+ /** Attribution strings a map package's own bindings and style sources declare. */
112
+ function collectDeclaredAttribution(mapPackage) {
113
+ const attribution = new Set();
114
+ for (const binding of mapPackage?.sourceBindings ?? []) {
115
+ if (typeof binding.attribution === "string" && binding.attribution.trim().length > 0) {
116
+ attribution.add(redactHonuaExportText(binding.attribution.trim()).slice(0, 512));
117
+ }
118
+ }
119
+ const styleSources = mapPackage?.mapSpec?.sources;
120
+ if (styleSources && typeof styleSources === "object") {
121
+ for (const source of Object.values(styleSources)) {
122
+ const value = source?.attribution;
123
+ if (typeof value === "string" && value.trim().length > 0) {
124
+ attribution.add(redactHonuaExportText(value.trim()).slice(0, 512));
125
+ }
126
+ }
127
+ }
128
+ return [...attribution];
129
+ }
130
+ /** Licence notices declared on the map package (`license`, `licence`, or `metadata.license`). */
131
+ function collectDeclaredLicenseNotices(mapPackage) {
132
+ const notices = new Set();
133
+ const candidates = [
134
+ mapPackage?.license,
135
+ mapPackage?.licence,
136
+ mapPackage?.metadata?.license,
137
+ ];
138
+ for (const candidate of candidates) {
139
+ if (typeof candidate === "string" && candidate.trim().length > 0) {
140
+ notices.add(redactHonuaExportText(candidate.trim()).slice(0, 512));
141
+ }
142
+ }
143
+ return [...notices];
144
+ }
145
+ /**
146
+ * Assembles the provenance block for an export (REQ-003) from the map package
147
+ * and controller state, unioned with anything the accepted plan additionally
148
+ * requires. Every string is redacted, because attribution text is
149
+ * caller-supplied and has appeared with a signed URL in it in the wild.
150
+ */
151
+ export function buildHonuaExportProvenance(state, sanitized, options = {}) {
152
+ const attribution = new Set(collectDeclaredAttribution(state?.mapPackage));
153
+ for (const entry of options.requiredAttribution ?? []) {
154
+ const trimmed = redactHonuaExportText(entry.trim()).slice(0, 512);
155
+ if (trimmed.length > 0)
156
+ attribution.add(trimmed);
157
+ }
158
+ const licenseNotices = new Set(collectDeclaredLicenseNotices(state?.mapPackage));
159
+ for (const entry of options.requiredLicenseNotices ?? []) {
160
+ const trimmed = redactHonuaExportText(entry.trim()).slice(0, 512);
161
+ if (trimmed.length > 0)
162
+ licenseNotices.add(trimmed);
163
+ }
164
+ const fidelityWarnings = new Set((options.fidelityWarnings ?? []).map((warning) => redactHonuaExportText(warning).slice(0, 512)));
165
+ // A withheld non-exportable source is a fidelity loss the artifact must
166
+ // admit to; silently shipping a map that is missing a layer is worse than
167
+ // shipping one that says so.
168
+ const omitted = sanitized.sources.filter((source) => source.omitted).map((source) => source.sourceId);
169
+ if (omitted.length > 0) {
170
+ fidelityWarnings.add(`${omitted.length} source(s) marked non-exportable were omitted from this export: ${omitted.join(", ")}.`);
171
+ }
172
+ const provenance = {
173
+ exportedAt: options.exportedAt ?? new Date().toISOString(),
174
+ attribution: [...attribution],
175
+ licenseNotices: [...licenseNotices],
176
+ fidelityWarnings: [...fidelityWarnings],
177
+ generator: "@honua/sdk-js app-platform component kit",
178
+ };
179
+ const scaleLabel = approximateHonuaScaleLabel(sanitized.viewport ?? state?.viewport);
180
+ if (scaleLabel)
181
+ provenance.scaleLabel = scaleLabel;
182
+ if (sanitized.refreshedAt)
183
+ provenance.dataFreshnessAt = sanitized.refreshedAt;
184
+ if (typeof sanitized.stale === "boolean")
185
+ provenance.stale = sanitized.stale;
186
+ if (sanitized.packageId)
187
+ provenance.packageId = sanitized.packageId;
188
+ return provenance;
189
+ }
190
+ /**
191
+ * Fail-closed provenance check (REQ-003): every attribution string and licence
192
+ * notice a source or the accepted plan declares must be present on the
193
+ * provenance that ships with the artifact.
194
+ *
195
+ * This exists because provenance is the one part of an export a well-meaning
196
+ * adapter or caller is most likely to "simplify away". Dropping attribution is
197
+ * a licence breach, so it is enforced with the same severity as a credential
198
+ * leak rather than logged as a warning.
199
+ */
200
+ export function assertHonuaExportProvenanceComplete(provenance, required) {
201
+ const missingAttribution = required.attribution.filter((entry) => !provenance.attribution.includes(entry));
202
+ const missingLicenses = required.licenseNotices.filter((entry) => !provenance.licenseNotices.includes(entry));
203
+ if (missingAttribution.length === 0 && missingLicenses.length === 0)
204
+ return;
205
+ throw new HonuaExportSafetyError(`Export is missing required provenance: ${[...missingAttribution, ...missingLicenses].join("; ")}.`, "unsupported-value");
206
+ }
207
+ // ── the runner ───────────────────────────────────────────────────────────
208
+ const ADAPTER_METHODS = {
209
+ print: "print",
210
+ snapshot: "snapshot",
211
+ state: "exportState",
212
+ };
213
+ function noopRelease() {
214
+ return Promise.resolve();
215
+ }
216
+ /** Wraps a payload's `release` so it runs at most once and never rejects. */
217
+ function onceRelease(release) {
218
+ if (!release)
219
+ return noopRelease;
220
+ let released = false;
221
+ return async () => {
222
+ if (released)
223
+ return;
224
+ released = true;
225
+ try {
226
+ await release();
227
+ }
228
+ catch {
229
+ // A failing release is not worth surfacing over the export's own
230
+ // outcome; the resource is the adapter's to leak.
231
+ }
232
+ };
233
+ }
234
+ function unsupported(kind, adapterId, provenance, redactions, capability, message) {
235
+ const redactedAdapterId = adapterId === undefined ? undefined : redactHonuaExportText(adapterId);
236
+ const redactedMessage = redactHonuaExportText(redactedAdapterId && adapterId ? message.replaceAll(adapterId, redactedAdapterId) : message);
237
+ return {
238
+ kind,
239
+ status: "unsupported",
240
+ adapterId,
241
+ provenance,
242
+ redactions,
243
+ ownership: "none",
244
+ release: noopRelease,
245
+ message: redactedMessage,
246
+ error: new HonuaCapabilityNotSupportedError(capability, EXPORT_PROTOCOL, adapterId),
247
+ };
248
+ }
249
+ function failed(kind, adapterId, provenance, redactions, error, ownership) {
250
+ return {
251
+ kind,
252
+ status: "error",
253
+ adapterId,
254
+ provenance,
255
+ redactions,
256
+ ownership,
257
+ release: noopRelease,
258
+ message: redactHonuaExportText(error.message),
259
+ error,
260
+ };
261
+ }
262
+ /**
263
+ * Copies adapter bytes into a buffer the caller owns outright.
264
+ *
265
+ * Adapters routinely hand back a view onto a canvas-backed or pooled buffer
266
+ * they will reuse or free. Copying is what makes the ownership contract
267
+ * ("`bytes` stay valid after `release()`") true rather than aspirational.
268
+ */
269
+ function adoptBytes(bytes) {
270
+ const copy = new Uint8Array(bytes.byteLength);
271
+ copy.set(bytes);
272
+ return copy;
273
+ }
274
+ /**
275
+ * Runs one export end to end. Never throws: every failure mode — no adapter,
276
+ * undeclared kind, missing method, adapter exception, cancellation, unsafe
277
+ * artifact, missing provenance — comes back as a {@link HonuaExportResult}.
278
+ *
279
+ * @example Fail closed with no adapter
280
+ * ```ts doc-test=skip reason="illustrative excerpt over an ambient controller state"
281
+ * const result = await runHonuaExport({ kind: "snapshot", state });
282
+ * result.status; // "unsupported"
283
+ * result.bytes; // undefined — no partial artifact is ever produced
284
+ * result.error?.sdkCode // "core.capability-not-supported"
285
+ * ```
286
+ */
287
+ export async function runHonuaExport(request) {
288
+ const { kind, adapter } = request;
289
+ const sanitization = sanitizeHonuaExportState(request.state, request.nonExportableSourceIds ? { nonExportableSourceIds: request.nonExportableSourceIds } : {});
290
+ const requiredAttribution = [
291
+ ...collectDeclaredAttribution(request.state?.mapPackage),
292
+ ...(request.requiredAttribution ?? []).map((entry) => redactHonuaExportText(entry.trim()).slice(0, 512)),
293
+ ].filter((entry) => entry.length > 0);
294
+ const requiredLicenseNotices = [
295
+ ...collectDeclaredLicenseNotices(request.state?.mapPackage),
296
+ ...(request.requiredLicenseNotices ?? []).map((entry) => redactHonuaExportText(entry.trim()).slice(0, 512)),
297
+ ].filter((entry) => entry.length > 0);
298
+ const baseProvenance = buildHonuaExportProvenance(request.state, sanitization.state, {
299
+ ...(request.requiredAttribution ? { requiredAttribution: request.requiredAttribution } : {}),
300
+ ...(request.requiredLicenseNotices ? { requiredLicenseNotices: request.requiredLicenseNotices } : {}),
301
+ ...(request.exportedAt ? { exportedAt: request.exportedAt } : {}),
302
+ });
303
+ const redactions = sanitization.redactions;
304
+ const title = request.title === undefined ? undefined : redactHonuaExportText(request.title).slice(0, 256);
305
+ if (!adapter) {
306
+ return unsupported(kind, undefined, baseProvenance, redactions, `export:${kind}`, `${describeKind(kind)} requires an explicit export adapter. Without one the component kit cannot read renderer pixels or authorize a state export, so it refuses rather than emitting a partial or credentialed artifact.`);
307
+ }
308
+ let capabilities;
309
+ try {
310
+ capabilities = adapter.describeCapabilities();
311
+ }
312
+ catch (cause) {
313
+ return failed(kind, typeof adapter.id === "string" ? adapter.id : undefined, baseProvenance, redactions, new HonuaExportError("error", "Export adapter failed to describe its capabilities.", { cause }), "none");
314
+ }
315
+ // Validated rather than trusted, and validated *inside* the guarded region:
316
+ // an adapter that returns undefined, null, or an object without a `kinds`
317
+ // array used to be dereferenced here and reject the whole call with a
318
+ // TypeError, breaking this function's one guarantee — that it always resolves
319
+ // to a structured result. A malformed capability declaration is a contract
320
+ // violation by the adapter and is reported as one.
321
+ const declaredId = capabilities?.adapterId;
322
+ const adapterId = (typeof declaredId === "string" && declaredId) || adapter.id || "unknown-adapter";
323
+ const declaredKinds = capabilities?.kinds;
324
+ if (!capabilities || typeof capabilities !== "object" || !Array.isArray(declaredKinds)) {
325
+ return failed(kind, typeof adapterId === "string" ? adapterId : undefined, baseProvenance, redactions, new HonuaExportError("error", `Export adapter "${String(adapterId)}" returned a malformed capability declaration; describeCapabilities() must return an object with a kinds array.`), "none");
326
+ }
327
+ if (!declaredKinds.includes(kind)) {
328
+ return unsupported(kind, adapterId, baseProvenance, redactions, `export:${kind}`, `Export adapter "${adapterId}" does not declare the "${kind}" export kind.`);
329
+ }
330
+ const method = adapter[ADAPTER_METHODS[kind]];
331
+ if (typeof method !== "function") {
332
+ return unsupported(kind, adapterId, baseProvenance, redactions, `export:${kind}`, `Export adapter "${adapterId}" declares the "${kind}" kind but does not implement ${String(ADAPTER_METHODS[kind])}().`);
333
+ }
334
+ if (request.signal?.aborted) {
335
+ return {
336
+ kind,
337
+ status: "cancelled",
338
+ adapterId,
339
+ provenance: baseProvenance,
340
+ redactions,
341
+ ownership: "released",
342
+ release: noopRelease,
343
+ message: "Export was cancelled before the adapter ran.",
344
+ error: new HonuaAbortError("Export was cancelled before the adapter ran."),
345
+ };
346
+ }
347
+ const context = {
348
+ kind,
349
+ title,
350
+ provenance: baseProvenance,
351
+ state: sanitization.state,
352
+ signal: request.signal,
353
+ mediaType: request.mediaType,
354
+ };
355
+ let payload;
356
+ try {
357
+ payload = await method.call(adapter, context);
358
+ }
359
+ catch (cause) {
360
+ if (request.signal?.aborted || isAbortLike(cause)) {
361
+ return {
362
+ kind,
363
+ status: "cancelled",
364
+ adapterId,
365
+ provenance: baseProvenance,
366
+ redactions,
367
+ ownership: "released",
368
+ release: noopRelease,
369
+ message: "Export was cancelled.",
370
+ error: new HonuaAbortError("Export was cancelled."),
371
+ };
372
+ }
373
+ // An adapter that already speaks the SDK's error envelope has said
374
+ // something specific and actionable ("the canvas is not readable because
375
+ // the map lacks preserveDrawingBuffer"); wrapping that in a generic message
376
+ // would throw away the only useful part. Its message is redacted by
377
+ // `failed()` like any other.
378
+ return failed(kind, adapterId, baseProvenance, redactions, cause instanceof HonuaSdkError
379
+ ? cause
380
+ : new HonuaExportError("error", `Export adapter "${adapterId}" failed to produce a ${kind} artifact.`, {
381
+ cause,
382
+ }), "none");
383
+ }
384
+ const release = onceRelease(payload?.release);
385
+ if (!payload || typeof payload !== "object") {
386
+ await release();
387
+ return failed(kind, adapterId, baseProvenance, redactions, new HonuaExportError("error", `Export adapter "${adapterId}" returned no payload for the ${kind} export.`), "released");
388
+ }
389
+ if (request.signal?.aborted) {
390
+ await release();
391
+ return {
392
+ kind,
393
+ status: "cancelled",
394
+ adapterId,
395
+ provenance: baseProvenance,
396
+ redactions,
397
+ ownership: "released",
398
+ release: noopRelease,
399
+ message: "Export was cancelled; the adapter's resources were released.",
400
+ error: new HonuaAbortError("Export was cancelled after the adapter produced an artifact."),
401
+ };
402
+ }
403
+ try {
404
+ const result = finalize(kind, adapterId, payload, baseProvenance, redactions, release, { attribution: requiredAttribution, licenseNotices: requiredLicenseNotices }, request.requireEmbeddedProvenance === true);
405
+ return result;
406
+ }
407
+ catch (cause) {
408
+ await release();
409
+ const error = cause instanceof HonuaExportSafetyError
410
+ ? cause
411
+ : new HonuaExportError("error", `Export adapter "${adapterId}" produced an unusable ${kind} artifact.`, {
412
+ cause,
413
+ });
414
+ return failed(kind, adapterId, baseProvenance, redactions, error, "released");
415
+ }
416
+ }
417
+ function describeKind(kind) {
418
+ if (kind === "snapshot")
419
+ return "Snapshot export";
420
+ if (kind === "state")
421
+ return "State export";
422
+ return "Print export";
423
+ }
424
+ function isAbortLike(cause) {
425
+ const name = cause?.name;
426
+ return name === "AbortError" || cause instanceof HonuaAbortError;
427
+ }
428
+ /**
429
+ * Validates and adopts an adapter payload. Throws on anything unsafe — the
430
+ * caller turns that into a `status: "error"` result after releasing the
431
+ * adapter's resources.
432
+ */
433
+ function finalize(kind, adapterId, payload, baseProvenance, redactions, release, required, requireEmbeddedProvenance) {
434
+ const projectedMediaType = projectMediaType(payload.mediaType);
435
+ const adapterWarnings = (payload.fidelityWarnings ?? []).map((warning) => redactHonuaExportText(String(warning)).slice(0, 512));
436
+ const warnings = new Set([...baseProvenance.fidelityWarnings, ...adapterWarnings]);
437
+ if (projectedMediaType.violation && projectedMediaType.mediaType.length > 0) {
438
+ warnings.add(`Export adapter "${adapterId}": ${projectedMediaType.violation}.`);
439
+ }
440
+ // REQ-003: whether the artifact's *bytes* carry the required attribution is a
441
+ // separate fact from whether the export knows the attribution, and conflating
442
+ // them is how an unattributed image ships. An adapter must say which it did;
443
+ // the safe default for an artifact that carries a body is "did not".
444
+ const requiresProvenance = required.attribution.length > 0 || required.licenseNotices.length > 0;
445
+ const provenanceEmbedded = payload.sideEffectOnly === true || payload.provenanceEmbedded === true;
446
+ if (requiresProvenance && !provenanceEmbedded) {
447
+ warnings.add(`This artifact's bytes do not carry the attribution and licence notices its sources require (${[
448
+ ...required.attribution,
449
+ ...required.licenseNotices,
450
+ ].join("; ")}). The caller must present them alongside the artifact wherever it is published.`);
451
+ }
452
+ const provenance = { ...baseProvenance, fidelityWarnings: [...warnings] };
453
+ assertHonuaExportProvenanceComplete(provenance, required);
454
+ // The strict mode, for callers who cannot present provenance out of band.
455
+ if (requireEmbeddedProvenance && requiresProvenance && !provenanceEmbedded) {
456
+ throw new HonuaExportSafetyError(`Export adapter "${adapterId}" cannot embed the required attribution into the ${kind} artifact, and requireEmbeddedProvenance was set. Refusing to emit an artifact whose bytes would travel without the attribution its sources require.`, "unsupported-value");
457
+ }
458
+ if (payload.sideEffectOnly === true) {
459
+ return {
460
+ kind,
461
+ status: "ready",
462
+ adapterId,
463
+ sideEffectOnly: true,
464
+ provenance,
465
+ provenanceEmbedded,
466
+ redactions,
467
+ // A side-effect export can still hold resources (a print stylesheet
468
+ // injected into the document, an off-screen render frame). Reporting
469
+ // "none" here told documented callers to skip cleanup and leaked it.
470
+ ownership: payload.release ? "caller-releases" : "none",
471
+ release,
472
+ };
473
+ }
474
+ if (projectedMediaType.mediaType.length === 0) {
475
+ throw new HonuaExportError("error", `Export adapter "${adapterId}" returned an artifact with no media type.`);
476
+ }
477
+ const mediaType = projectedMediaType.mediaType;
478
+ const result = {
479
+ kind,
480
+ status: "ready",
481
+ adapterId,
482
+ filename: sanitizeHonuaExportFilename({
483
+ ...(payload.filenameHint === undefined ? {} : { title: payload.filenameHint }),
484
+ mediaType,
485
+ }),
486
+ mediaType,
487
+ provenance,
488
+ provenanceEmbedded,
489
+ redactions,
490
+ ownership: payload.release ? "caller-releases" : "none",
491
+ release,
492
+ };
493
+ if (payload.bytes instanceof Uint8Array) {
494
+ // Scanned two ways, because "binary" is not a security boundary. A buffer
495
+ // that decodes cleanly as UTF-8 (SVG, JSON, HTML) is scanned as text; every
496
+ // buffer, including a genuine PNG or PDF that will never decode, also has
497
+ // its printable runs extracted and scanned, so a token inside a PNG tEXt
498
+ // chunk, a JPEG comment, or PDF/XMP metadata cannot ride out under cover of
499
+ // the surrounding binary.
500
+ const decoded = decodeUtf8Strict(payload.bytes);
501
+ if (decoded !== undefined)
502
+ assertCredentialFreeExportText(decoded, `${kind} export bytes`);
503
+ assertCredentialFreeExportBytes(payload.bytes, `${kind} export bytes`);
504
+ result.bytes = adoptBytes(payload.bytes);
505
+ }
506
+ else if (typeof payload.text === "string") {
507
+ assertCredentialFreeExportText(payload.text, `${kind} export text`);
508
+ result.text = payload.text;
509
+ }
510
+ else {
511
+ throw new HonuaExportError("error", `Export adapter "${adapterId}" returned neither bytes nor text for the ${kind} export.`);
512
+ }
513
+ for (const warning of provenance.fidelityWarnings) {
514
+ assertCredentialFreeExportText(warning, "export fidelity warning");
515
+ }
516
+ return result;
517
+ }
518
+ /** Fallback for a media type the adapter got wrong; deliberately inert. */
519
+ const SAFE_FALLBACK_MEDIA_TYPE = "application/octet-stream";
520
+ /** RFC 9110 token characters, the only thing allowed in a type, subtype, or parameter name. */
521
+ const MEDIA_TYPE_TOKEN = /^[A-Za-z0-9!#$%&'*+.^_`|~-]{1,64}$/;
522
+ /**
523
+ * Parameters an export media type may carry. Everything else is dropped: a
524
+ * media type is echoed onto the result, onto the `honua-export` event, and into
525
+ * a `Content-Type` header by most callers, so an adapter-controlled parameter
526
+ * bag is a credential channel (`text/plain; token=...`) and a header-injection
527
+ * channel. `charset` is the only parameter an export artifact legitimately
528
+ * needs, and its value is checked against the same token grammar.
529
+ */
530
+ const MEDIA_TYPE_PARAMETER_ALLOWLIST = new Set(["charset"]);
531
+ /**
532
+ * Validates an adapter-supplied media type against a strict `type/subtype`
533
+ * grammar with an allowlisted parameter set, replacing anything else with
534
+ * {@link SAFE_FALLBACK_MEDIA_TYPE} and reporting the substitution.
535
+ *
536
+ * Truncation alone (the previous behavior) is not validation: it bounds length
537
+ * and nothing else, so a value carrying a token, a newline, or arbitrary
538
+ * parameters passed straight through to the result and the event detail.
539
+ */
540
+ function projectMediaType(raw) {
541
+ if (typeof raw !== "string" || raw.trim().length === 0) {
542
+ return { mediaType: "", violation: "no media type" };
543
+ }
544
+ const value = raw.slice(0, 255);
545
+ const [essence, ...parameters] = value.split(";");
546
+ const [type, subtype, ...extra] = essence.trim().toLowerCase().split("/");
547
+ if (extra.length > 0 || !MEDIA_TYPE_TOKEN.test(type ?? "") || !MEDIA_TYPE_TOKEN.test(subtype ?? "")) {
548
+ return {
549
+ mediaType: SAFE_FALLBACK_MEDIA_TYPE,
550
+ violation: `media type is not a valid type/subtype; substituted ${SAFE_FALLBACK_MEDIA_TYPE}`,
551
+ };
552
+ }
553
+ const kept = [];
554
+ let dropped = 0;
555
+ for (const parameter of parameters) {
556
+ const separator = parameter.indexOf("=");
557
+ if (separator < 0) {
558
+ dropped += 1;
559
+ continue;
560
+ }
561
+ const name = parameter.slice(0, separator).trim().toLowerCase();
562
+ const parameterValue = parameter
563
+ .slice(separator + 1)
564
+ .trim()
565
+ .replace(/^"|"$/g, "");
566
+ if (!MEDIA_TYPE_PARAMETER_ALLOWLIST.has(name) || !MEDIA_TYPE_TOKEN.test(parameterValue)) {
567
+ dropped += 1;
568
+ continue;
569
+ }
570
+ kept.push(`${name}=${parameterValue}`);
571
+ }
572
+ const mediaType = [`${type}/${subtype}`, ...kept].join("; ");
573
+ return dropped === 0
574
+ ? { mediaType }
575
+ : { mediaType, violation: `${dropped} disallowed media-type parameter(s) were dropped` };
576
+ }
577
+ function decodeUtf8Strict(bytes) {
578
+ if (typeof TextDecoder === "undefined")
579
+ return undefined;
580
+ try {
581
+ return new TextDecoder("utf-8", { fatal: true }).decode(bytes);
582
+ }
583
+ catch {
584
+ return undefined;
585
+ }
586
+ }
587
+ /** Throws `result.error` unless the export succeeded; returns the result otherwise. */
588
+ export function assertHonuaExportReady(result) {
589
+ if (result.status === "ready")
590
+ return result;
591
+ throw result.error ?? new HonuaExportError(result.status, result.message ?? "Export did not complete.");
592
+ }
593
+ const DATA_URL_PREFIX = /^data:([^;,]{1,128})?(;base64)?,/i;
594
+ function decodeDataUrl(dataUrl) {
595
+ const match = DATA_URL_PREFIX.exec(dataUrl);
596
+ if (!match)
597
+ throw new HonuaExportError("error", "Renderer returned a value that is not a data URL.");
598
+ const mediaType = match[1] || "image/png";
599
+ const body = dataUrl.slice(match[0].length);
600
+ if (match[2]) {
601
+ const binary = typeof atob === "function" ? atob(body) : Buffer.from(body, "base64").toString("binary");
602
+ const bytes = new Uint8Array(binary.length);
603
+ for (let index = 0; index < binary.length; index += 1)
604
+ bytes[index] = binary.charCodeAt(index);
605
+ return { mediaType, bytes };
606
+ }
607
+ return { mediaType, bytes: new TextEncoder().encode(decodeURIComponent(body)) };
608
+ }
609
+ /**
610
+ * Builds an export adapter from capabilities the application explicitly grants.
611
+ *
612
+ * Everything here is structural: no `maplibre-gl`, `cesium`, PDF writer, or
613
+ * image encoder is imported, so wiring an adapter costs a consumer bundle
614
+ * nothing beyond the object it passes in (NFR-001).
615
+ *
616
+ * @example
617
+ * ```ts doc-test=skip reason="requires a live MapLibre map instance"
618
+ * const adapter = createHonuaExportAdapter({
619
+ * id: "my-app",
620
+ * // map created with canvasContextAttributes: { preserveDrawingBuffer: true }
621
+ * snapshot: { getCanvas: () => map.getCanvas() },
622
+ * state: true,
623
+ * });
624
+ * printExportElement.exportAdapter = adapter;
625
+ * ```
626
+ */
627
+ export function createHonuaExportAdapter(options) {
628
+ const kinds = [];
629
+ if (options.print)
630
+ kinds.push("print");
631
+ if (options.snapshot)
632
+ kinds.push("snapshot");
633
+ if (options.state)
634
+ kinds.push("state");
635
+ const snapshotMediaTypes = options.snapshot ? [options.snapshot.mediaType ?? "image/png"] : undefined;
636
+ const adapter = {
637
+ id: options.id,
638
+ describeCapabilities: () => ({
639
+ adapterId: options.id,
640
+ kinds: [...kinds],
641
+ cancellable: true,
642
+ ...(snapshotMediaTypes ? { snapshotMediaTypes } : {}),
643
+ }),
644
+ };
645
+ if (options.print)
646
+ adapter.print = options.print;
647
+ if (options.snapshot) {
648
+ const source = options.snapshot;
649
+ adapter.snapshot = (context) => {
650
+ const canvas = source.getCanvas();
651
+ if (!canvas) {
652
+ throw new HonuaExportError("error", "The renderer canvas is not currently readable, so no snapshot was produced. A map must be created with canvasContextAttributes: { preserveDrawingBuffer: true } for its pixels to be readable.");
653
+ }
654
+ const mediaType = context.mediaType ?? source.mediaType ?? "image/png";
655
+ if (typeof canvas.toDataURL !== "function") {
656
+ throw new HonuaExportError("error", "The supplied renderer canvas does not implement toDataURL().");
657
+ }
658
+ const decoded = decodeDataUrl(canvas.toDataURL(mediaType));
659
+ return {
660
+ mediaType: decoded.mediaType,
661
+ bytes: decoded.bytes,
662
+ filenameHint: context.title ?? context.provenance.packageId ?? "snapshot",
663
+ fidelityWarnings: source.fidelityWarnings ?? [],
664
+ // Reading a canvas captures the map's pixels and nothing else: MapLibre
665
+ // renders attribution as DOM siblings of the canvas, so it is provably
666
+ // absent from these bytes. Declaring `true` here would make the result
667
+ // claim an attribution guarantee the image does not carry, which is the
668
+ // licence-breaking failure REQ-003 exists to prevent. The runner turns
669
+ // this into an explicit fidelity warning naming what the caller must
670
+ // present, or a hard failure under `requireEmbeddedProvenance`.
671
+ // An adapter that composites a watermark or footer should declare true.
672
+ provenanceEmbedded: source.provenanceEmbedded === true,
673
+ };
674
+ };
675
+ }
676
+ if (options.state) {
677
+ const custom = typeof options.state === "function" ? options.state : undefined;
678
+ adapter.exportState =
679
+ custom ??
680
+ ((context) => ({
681
+ mediaType: "application/json",
682
+ text: JSON.stringify({ ...context.state, provenance: context.provenance }, null, 2),
683
+ filenameHint: context.title ?? context.provenance.packageId ?? "state",
684
+ }));
685
+ }
686
+ return adapter;
687
+ }
688
+ /**
689
+ * The browser-print adapter: the one export the component kit can serve without
690
+ * application help, because `window.print()` reads no pixels and serializes no
691
+ * state — the browser prints what it already renders, under the user's own
692
+ * session.
693
+ *
694
+ * It is a *default* for `print` only. Snapshot and state export deliberately
695
+ * have no equivalent built-in, because both require reading privileged data.
696
+ */
697
+ export function createBrowserPrintExportAdapter(windowLike = typeof window === "undefined" ? undefined : window) {
698
+ return {
699
+ id: "honua-browser-print",
700
+ describeCapabilities: () => ({
701
+ adapterId: "honua-browser-print",
702
+ kinds: windowLike && typeof windowLike.print === "function" ? ["print"] : [],
703
+ cancellable: false,
704
+ }),
705
+ print: () => {
706
+ if (!windowLike || typeof windowLike.print !== "function") {
707
+ throw new HonuaExportError("error", "Window printing is unavailable in this environment.");
708
+ }
709
+ windowLike.print();
710
+ return {
711
+ mediaType: "application/vnd.honua.print-dialog",
712
+ sideEffectOnly: true,
713
+ fidelityWarnings: [
714
+ "Browser print reproduces the on-screen layout only; page breaks, scale, and layer legibility are controlled by the print stylesheet, not by the map.",
715
+ ],
716
+ };
717
+ },
718
+ };
719
+ }