@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,454 @@
1
+ /**
2
+ * Plan-bound canonical Source → MapLibre GeoJSON workflow.
3
+ *
4
+ * This module is peer-injected and safe to import in Node/SSR/worker code: it
5
+ * never imports `maplibre-gl`. The first production slice deliberately covers
6
+ * exact feature-query plans; tile and realtime strategies remain separate.
7
+ */
8
+ import { HonuaSdkError, mergeHonuaErrorContext } from "../core/error-envelope.js";
9
+ import { HonuaCapabilityNotSupportedError } from "../core/errors.js";
10
+ import { canonicalStringify, toJsonValue } from "../query-planner/canonical.js";
11
+ import { assertQueryPlanExecutionContextV1 } from "../query-planner/execution-context.js";
12
+ import { queryFromCanonical, queryIrSourceIdentity } from "../query-planner/ir.js";
13
+ import { hashQueryPlanV1 } from "../query-planner/planner.js";
14
+ import { HonuaQueryPlanExecutionError, } from "../query-planner/types.js";
15
+ import { canonicalFeaturesToGeoJson, defaultPaint, geometryKind, geometryKinds, layerType, mapLibreGeometryType, removeUndefined, safeId, } from "./geojson-projection.js";
16
+ /** A stable, machine-readable adapter failure. */
17
+ export class HonuaMapLibreSourceAdapterError extends HonuaSdkError {
18
+ code;
19
+ detail;
20
+ constructor(code, message, detail, options = {}) {
21
+ super(MAPLIBRE_SOURCE_ADAPTER_CODES[code], message, {
22
+ ...options,
23
+ context: mergeHonuaErrorContext(detail, options.context),
24
+ });
25
+ this.code = code;
26
+ this.detail = detail;
27
+ this.name = "HonuaMapLibreSourceAdapterError";
28
+ }
29
+ }
30
+ const MAPLIBRE_SOURCE_ADAPTER_CODES = {
31
+ disposed: "map.source-adapter.disposed",
32
+ "source-conflict": "map.source-adapter.source-conflict",
33
+ "layer-conflict": "map.source-adapter.layer-conflict",
34
+ "unsupported-plan": "map.source-adapter.unsupported-plan",
35
+ "invalid-option": "map.source-adapter.invalid-option",
36
+ "map-mutation-failed": "map.source-adapter.map-mutation-failed",
37
+ };
38
+ /**
39
+ * Project an already-executed plan result into native MapLibre source/layers.
40
+ * This function is deterministic and does not mutate a map.
41
+ */
42
+ export function projectSourceToMapLibre(source, plan, result, options = {}) {
43
+ assertProjectionPlanContext(source, plan);
44
+ assertFeaturePlan(plan, result);
45
+ assertQueryable(source);
46
+ validateStaticOptions(options);
47
+ const sourceId = options.sourceId ?? `honua-${safeId(source.descriptor.id)}`;
48
+ const layerId = options.layerId ?? `${sourceId}-features`;
49
+ const converted = canonicalFeaturesToGeoJson(result.features, source.descriptor.schema?.primaryKey);
50
+ const presentKinds = geometryKinds(converted.data.features);
51
+ const requestedKind = options.geometry === "auto" || options.geometry === undefined ? undefined : options.geometry;
52
+ // Auto mode installs a stable three-layer geometry matrix. Refresh can then
53
+ // move between geometry kinds through setData without structural layer churn.
54
+ const renderKinds = requestedKind
55
+ ? [requestedKind]
56
+ : options.cluster
57
+ ? ["point"]
58
+ : ["point", "line", "polygon"];
59
+ if (options.cluster &&
60
+ (requestedKind !== undefined ? requestedKind !== "point" : presentKinds.some((kind) => kind !== "point"))) {
61
+ throw new HonuaMapLibreSourceAdapterError("invalid-option", "MapLibre clustering is supported only for point GeoJSON sources.", { sourceId, geometryKinds: renderKinds });
62
+ }
63
+ const diagnostics = [
64
+ diagnostic(source, plan, "strategy-selected", "info", "project", "exact", "Selected exact bounded GeoJSON query strategy."),
65
+ ];
66
+ if (result.features.length === 0) {
67
+ diagnostics.push(diagnostic(source, plan, "empty-result", "info", "execute", "exact", "The accepted plan returned no features.", {
68
+ rowCount: 0,
69
+ }));
70
+ }
71
+ if (result.exceededTransferLimit) {
72
+ diagnostics.push(diagnostic(source, plan, "transfer-limit", "warning", "execute", "equivalent", "The accepted bounded query returned a page while additional source rows exist.", { renderedRowCount: result.features.length }));
73
+ }
74
+ if (result.degraded && result.degraded.length > 0) {
75
+ diagnostics.push(diagnostic(source, plan, "source-degraded", "warning", "execute", "equivalent", "The source reported degraded execution semantics.", { reasons: result.degraded.map((entry) => entry.reason) }));
76
+ }
77
+ if (converted.unsupported > 0) {
78
+ diagnostics.push(diagnostic(source, plan, "geometry-unsupported", "warning", "project", "unsupported", `${converted.unsupported} feature geometry value(s) could not be projected and remain attribute-only.`, { unsupportedGeometryCount: converted.unsupported }));
79
+ }
80
+ const mismatchedGeometryCount = requestedKind
81
+ ? converted.data.features.filter((feature) => {
82
+ const kind = geometryKind(feature.geometry);
83
+ return kind !== undefined && kind !== requestedKind;
84
+ }).length
85
+ : 0;
86
+ if (requestedKind && mismatchedGeometryCount > 0) {
87
+ diagnostics.push(diagnostic(source, plan, "geometry-mismatch", "warning", "project", "unsupported", `${mismatchedGeometryCount} feature(s) do not match the explicit ${requestedKind} geometry layer and will not render.`, {
88
+ requestedGeometry: requestedKind,
89
+ mismatchedGeometryCount,
90
+ renderedGeometryCount: result.features.length - mismatchedGeometryCount - converted.unsupported,
91
+ }));
92
+ }
93
+ if (presentKinds.length > 1 && !requestedKind) {
94
+ diagnostics.push(diagnostic(source, plan, "mixed-geometry", "warning", "project", "equivalent", "Mixed geometry output uses one filtered layer per geometry kind.", {
95
+ geometryKinds: presentKinds,
96
+ }));
97
+ }
98
+ const mapSource = {
99
+ type: "geojson",
100
+ data: converted.data,
101
+ promoteId: source.descriptor.schema?.primaryKey,
102
+ attribution: options.attribution ?? source.descriptor.attribution,
103
+ ...(options.cluster
104
+ ? { cluster: true, clusterRadius: positiveInteger(options.clusterRadius, 50, "clusterRadius") }
105
+ : {}),
106
+ };
107
+ removeUndefined(mapSource);
108
+ return Object.freeze({
109
+ strategy: "geojson-query",
110
+ sourceId,
111
+ layerId,
112
+ planId: plan.id,
113
+ planFingerprint: plan.fingerprint,
114
+ sourceVersion: plan.ir.source.sourceVersion,
115
+ schemaVersion: plan.ir.source.schemaVersion,
116
+ authorizationScope: plan.ir.source.authorizationScope,
117
+ source: Object.freeze(mapSource),
118
+ layers: Object.freeze(renderKinds.map((kind, index) => Object.freeze({
119
+ id: renderKinds.length === 1 ? layerId : `${layerId}-${kind}`,
120
+ type: layerType(kind),
121
+ source: sourceId,
122
+ filter: ["==", ["geometry-type"], mapLibreGeometryType(kind)],
123
+ paint: { ...defaultPaint(kind), ...(options.paint ?? {}) },
124
+ ...(options.layout ? { layout: { ...options.layout } } : {}),
125
+ metadata: {
126
+ "honua:strategy": "geojson-query",
127
+ "honua:planId": plan.id,
128
+ "honua:planFingerprint": plan.fingerprint,
129
+ "honua:sourceVersion": plan.ir.source.sourceVersion,
130
+ "honua:geometryOrder": index,
131
+ },
132
+ }))),
133
+ diagnostics: Object.freeze(diagnostics),
134
+ state: diagnostics.some((entry) => entry.severity === "warning")
135
+ ? "degraded"
136
+ : result.features.length === 0
137
+ ? "empty"
138
+ : "ready",
139
+ });
140
+ }
141
+ /**
142
+ * Execute an accepted plan, mount its projection, and return one lifecycle.
143
+ * Omitted execution bindings inherit the accepted plan; explicit overrides
144
+ * remain fail-closed when they describe another context.
145
+ */
146
+ export async function mountSourceToMapLibre(map, source, plan, options = {}) {
147
+ assertProjectionPlanContext(source, plan);
148
+ assertFeaturePlan(plan);
149
+ assertQueryable(source);
150
+ validateStaticOptions(options);
151
+ const intendedSourceId = options.sourceId ?? `honua-${safeId(source.descriptor.id)}`;
152
+ if (map.getSource(intendedSourceId)) {
153
+ throw new HonuaMapLibreSourceAdapterError("source-conflict", `MapLibre source "${intendedSourceId}" already exists.`, {
154
+ sourceId: intendedSourceId,
155
+ });
156
+ }
157
+ for (const layerId of projectedLayerIds(intendedSourceId, options)) {
158
+ if (map.getLayer(layerId)) {
159
+ throw new HonuaMapLibreSourceAdapterError("layer-conflict", `MapLibre layer "${layerId}" already exists.`, {
160
+ layerId,
161
+ });
162
+ }
163
+ }
164
+ const lifecycleController = new AbortController();
165
+ const executeOptions = {
166
+ ...executionOptions(plan, options),
167
+ signal: combineSignals([lifecycleController.signal, options.signal]),
168
+ };
169
+ const result = await executeAcceptedFeaturePlan(plan, source, executeOptions);
170
+ throwIfAborted(executeOptions.signal);
171
+ let projection = projectSourceToMapLibre(source, plan, result, options);
172
+ throwIfAborted(executeOptions.signal);
173
+ assertMapIdsAvailable(map, projection);
174
+ const attemptedLayerIds = [];
175
+ try {
176
+ map.addSource(projection.sourceId, projection.source);
177
+ for (const layer of projection.layers) {
178
+ attemptedLayerIds.push(String(layer.id));
179
+ map.addLayer(layer, options.beforeId);
180
+ }
181
+ }
182
+ catch (cause) {
183
+ const rollbackFailures = rollbackMapMutation(map, projection.sourceId, attemptedLayerIds);
184
+ throw new HonuaMapLibreSourceAdapterError("map-mutation-failed", `Failed to mount source "${projection.sourceId}" transactionally.`, {
185
+ sourceId: projection.sourceId,
186
+ attemptedLayerIds,
187
+ rollbackFailureCount: rollbackFailures.length,
188
+ rollbackFailures: rollbackFailures.map(errorMessage),
189
+ }, { cause });
190
+ }
191
+ let state = projection.state;
192
+ let diagnostics = [...projection.diagnostics];
193
+ const layerIds = projection.layers.map((layer) => String(layer.id));
194
+ const isDisposed = () => state === "disposed";
195
+ let refreshTail = Promise.resolve();
196
+ const runRefresh = async (refreshOptions) => {
197
+ if (isDisposed())
198
+ throw new HonuaMapLibreSourceAdapterError("disposed", "Cannot refresh a disposed MapLibre source mount.");
199
+ const effectiveSignal = combineSignals([lifecycleController.signal, refreshOptions.signal]);
200
+ throwIfAborted(effectiveSignal);
201
+ const nextExecutionOptions = {
202
+ ...executeOptions,
203
+ ...refreshOptions,
204
+ signal: effectiveSignal,
205
+ };
206
+ const nextResult = await executeAcceptedFeaturePlan(plan, source, nextExecutionOptions);
207
+ throwIfAborted(effectiveSignal);
208
+ if (isDisposed())
209
+ throw new HonuaMapLibreSourceAdapterError("disposed", "MapLibre source mount was disposed during refresh.");
210
+ const next = projectSourceToMapLibre(source, plan, nextResult, options);
211
+ throwIfAborted(effectiveSignal);
212
+ const handle = map.getSource(projection.sourceId);
213
+ if (!handle || typeof handle.setData !== "function") {
214
+ throw new HonuaMapLibreSourceAdapterError("map-mutation-failed", `Mounted source "${projection.sourceId}" no longer exposes setData().`);
215
+ }
216
+ throwIfAborted(effectiveSignal);
217
+ const previousData = projection.source.data;
218
+ try {
219
+ handle.setData(next.source.data);
220
+ }
221
+ catch (cause) {
222
+ let rollbackFailure;
223
+ try {
224
+ handle.setData(previousData);
225
+ }
226
+ catch (error) {
227
+ rollbackFailure = error;
228
+ }
229
+ state = "degraded";
230
+ diagnostics = [
231
+ ...diagnostics,
232
+ diagnostic(source, plan, "incremental-update-failed", "warning", "update", "unsupported", rollbackFailure
233
+ ? "MapLibre setData failed and the previous projection could not be restored."
234
+ : "MapLibre setData failed; the previous projection was restored.", { rollbackSucceeded: rollbackFailure === undefined }),
235
+ ];
236
+ throw new HonuaMapLibreSourceAdapterError("map-mutation-failed", `Failed to refresh mounted source "${projection.sourceId}" through setData().`, {
237
+ sourceId: projection.sourceId,
238
+ planId: plan.id,
239
+ rollbackSucceeded: rollbackFailure === undefined,
240
+ ...(rollbackFailure ? { rollbackFailure: errorMessage(rollbackFailure) } : {}),
241
+ }, { cause });
242
+ }
243
+ diagnostics = [
244
+ ...next.diagnostics,
245
+ diagnostic(source, plan, "incremental-update", "info", "update", "exact", "Updated GeoJSON data in place with setData()."),
246
+ ];
247
+ projection = { ...next, diagnostics };
248
+ state = next.state;
249
+ return projection;
250
+ };
251
+ const enqueueRefresh = (refreshOptions) => {
252
+ const result = refreshTail.then(() => runRefresh(refreshOptions));
253
+ refreshTail = result.then(() => undefined, () => undefined);
254
+ return result;
255
+ };
256
+ return {
257
+ strategy: "geojson-query",
258
+ sourceId: projection.sourceId,
259
+ layerIds,
260
+ planId: plan.id,
261
+ planFingerprint: plan.fingerprint,
262
+ get state() {
263
+ return state;
264
+ },
265
+ get diagnostics() {
266
+ return diagnostics;
267
+ },
268
+ refresh(refreshOptions = {}) {
269
+ return enqueueRefresh(refreshOptions);
270
+ },
271
+ dispose() {
272
+ if (state === "disposed")
273
+ return;
274
+ lifecycleController.abort(new DOMException("MapLibre source mount disposed", "AbortError"));
275
+ const failures = [];
276
+ for (const layerId of [...layerIds].reverse()) {
277
+ try {
278
+ if (map.getLayer(layerId))
279
+ map.removeLayer(layerId);
280
+ }
281
+ catch (error) {
282
+ failures.push(error);
283
+ }
284
+ }
285
+ try {
286
+ if (map.getSource(projection.sourceId))
287
+ map.removeSource(projection.sourceId);
288
+ }
289
+ catch (error) {
290
+ failures.push(error);
291
+ }
292
+ state = "disposed";
293
+ if (failures.length > 0) {
294
+ throw new HonuaMapLibreSourceAdapterError("map-mutation-failed", `MapLibre source "${projection.sourceId}" disposal completed with ${failures.length} renderer failure(s).`, { sourceId: projection.sourceId, failureCount: failures.length }, { cause: failures[0] });
295
+ }
296
+ },
297
+ };
298
+ }
299
+ function assertQueryable(source) {
300
+ if (!source.capabilities.has("query")) {
301
+ throw new HonuaCapabilityNotSupportedError("query", source.descriptor.protocol, source.descriptor.id);
302
+ }
303
+ }
304
+ function assertProjectionPlanContext(source, plan) {
305
+ if (hashQueryPlanV1(plan) !== plan.fingerprint) {
306
+ throw new HonuaQueryPlanExecutionError("invalid-plan", "Plan content does not match its fingerprint; project only the accepted immutable plan.");
307
+ }
308
+ const identity = queryIrSourceIdentity(source.descriptor, {
309
+ schemaVersion: plan.ir.source.schemaVersion,
310
+ sourceVersion: plan.ir.source.sourceVersion,
311
+ authorizationScope: plan.ir.source.authorizationScope,
312
+ });
313
+ const runtimeCapabilities = [...source.capabilities].sort();
314
+ const descriptorCapabilities = [...source.descriptor.capabilities].sort();
315
+ const planCapabilities = [...plan.ir.source.capabilities].sort();
316
+ if (canonicalStringify(toJsonValue(identity)) !== canonicalStringify(toJsonValue(plan.ir.source)) ||
317
+ canonicalStringify(toJsonValue(runtimeCapabilities)) !== canonicalStringify(toJsonValue(descriptorCapabilities)) ||
318
+ canonicalStringify(toJsonValue(runtimeCapabilities)) !== canonicalStringify(toJsonValue(planCapabilities))) {
319
+ throw new HonuaQueryPlanExecutionError("plan-context-mismatch", "Source identity, descriptor capabilities, or runtime capabilities do not match the accepted plan projection context.");
320
+ }
321
+ }
322
+ function assertFeaturePlan(plan, result) {
323
+ const first = plan.steps[0];
324
+ const featureShape = plan.ir.query.aggregation === undefined &&
325
+ plan.ir.query.returnGeometry !== false &&
326
+ plan.steps.length === 1 &&
327
+ first?.engine === "remote" &&
328
+ (first.operation === "query" || first.operation === "queryAll");
329
+ if (!featureShape || result?.aggregateRows !== undefined) {
330
+ throw new HonuaMapLibreSourceAdapterError("unsupported-plan", "MapLibre GeoJSON mounting requires a feature-query plan; aggregate or client-materialization plans are not renderable by this strategy.", { planId: plan.id, operations: plan.steps.map((step) => step.operation) });
331
+ }
332
+ }
333
+ async function executeAcceptedFeaturePlan(plan, source, options) {
334
+ try {
335
+ assertQueryPlanExecutionContextV1(plan, source, options);
336
+ const step = plan.steps[0];
337
+ if (!step || step.engine !== "remote" || (step.operation !== "query" && step.operation !== "queryAll")) {
338
+ throw new HonuaMapLibreSourceAdapterError("unsupported-plan", "MapLibre GeoJSON mounting requires one remote feature-query step.", { planId: plan.id });
339
+ }
340
+ const query = queryFromCanonical(step.query, options.signal);
341
+ return step.operation === "queryAll" ? await source.queryAll(query) : await source.query(query);
342
+ }
343
+ catch (error) {
344
+ // Preserve this bridge's established AbortSignal reason while the shared
345
+ // canonical execution-context boundary supplies plan validation.
346
+ if (options.signal?.aborted)
347
+ options.signal.throwIfAborted();
348
+ throw error;
349
+ }
350
+ }
351
+ function assertMapIdsAvailable(map, projection) {
352
+ if (map.getSource(projection.sourceId)) {
353
+ throw new HonuaMapLibreSourceAdapterError("source-conflict", `MapLibre source "${projection.sourceId}" already exists.`, {
354
+ sourceId: projection.sourceId,
355
+ });
356
+ }
357
+ for (const layer of projection.layers) {
358
+ if (map.getLayer(String(layer.id))) {
359
+ throw new HonuaMapLibreSourceAdapterError("layer-conflict", `MapLibre layer "${String(layer.id)}" already exists.`, {
360
+ layerId: layer.id,
361
+ });
362
+ }
363
+ }
364
+ }
365
+ function rollbackMapMutation(map, sourceId, layerIds) {
366
+ const failures = [];
367
+ for (const layerId of [...layerIds].reverse()) {
368
+ try {
369
+ if (map.getLayer(layerId))
370
+ map.removeLayer(layerId);
371
+ }
372
+ catch (error) {
373
+ failures.push(error);
374
+ }
375
+ }
376
+ try {
377
+ if (map.getSource(sourceId))
378
+ map.removeSource(sourceId);
379
+ }
380
+ catch (error) {
381
+ failures.push(error);
382
+ }
383
+ return failures;
384
+ }
385
+ function diagnostic(source, plan, code, severity, stage, fidelity, message, detail) {
386
+ return {
387
+ code,
388
+ severity,
389
+ stage,
390
+ fidelity,
391
+ sourceId: source.descriptor.id,
392
+ planId: plan.id,
393
+ message,
394
+ ...(detail ? { detail } : {}),
395
+ };
396
+ }
397
+ function executionOptions(plan, options) {
398
+ const schemaVersion = options.schemaVersion ?? plan.ir.source.schemaVersion;
399
+ const sourceVersion = options.sourceVersion ?? plan.ir.source.sourceVersion;
400
+ const authorizationScope = options.authorizationScope ?? plan.ir.source.authorizationScope;
401
+ const discovery = options.discovery ?? plan.provenance.discovery;
402
+ const executionMode = options.executionMode ?? plan.validity.executionMode;
403
+ return {
404
+ ...(options.signal ? { signal: options.signal } : {}),
405
+ ...(schemaVersion !== undefined ? { schemaVersion } : {}),
406
+ ...(sourceVersion !== undefined ? { sourceVersion } : {}),
407
+ ...(authorizationScope !== undefined ? { authorizationScope } : {}),
408
+ ...(discovery !== undefined ? { discovery } : {}),
409
+ ...(executionMode !== undefined ? { executionMode } : {}),
410
+ };
411
+ }
412
+ function combineSignals(signals) {
413
+ const available = signals.filter((signal) => signal !== undefined);
414
+ return available.length === 1 ? available[0] : AbortSignal.any(available);
415
+ }
416
+ function throwIfAborted(signal) {
417
+ signal.throwIfAborted();
418
+ }
419
+ function errorMessage(error) {
420
+ return error instanceof Error ? error.message : String(error);
421
+ }
422
+ function positiveInteger(value, fallback, name) {
423
+ if (value === undefined)
424
+ return fallback;
425
+ if (!Number.isInteger(value) || value <= 0) {
426
+ throw new HonuaMapLibreSourceAdapterError("invalid-option", `${name} must be a positive integer.`, {
427
+ [name]: value,
428
+ });
429
+ }
430
+ return value;
431
+ }
432
+ function validateStaticOptions(options) {
433
+ if (options.clusterRadius !== undefined && options.cluster !== true) {
434
+ throw new HonuaMapLibreSourceAdapterError("invalid-option", "clusterRadius requires cluster: true.", {
435
+ clusterRadius: options.clusterRadius,
436
+ });
437
+ }
438
+ if (options.cluster) {
439
+ positiveInteger(options.clusterRadius, 50, "clusterRadius");
440
+ if (options.geometry !== undefined && options.geometry !== "auto" && options.geometry !== "point") {
441
+ throw new HonuaMapLibreSourceAdapterError("invalid-option", "MapLibre clustering is supported only for point GeoJSON sources.", { geometry: options.geometry });
442
+ }
443
+ }
444
+ }
445
+ function projectedLayerIds(sourceId, options) {
446
+ const base = options.layerId ?? `${sourceId}-features`;
447
+ const kind = options.geometry === "auto" ? undefined : options.geometry;
448
+ const kinds = kind
449
+ ? [kind]
450
+ : options.cluster
451
+ ? ["point"]
452
+ : ["point", "line", "polygon"];
453
+ return kinds.length === 1 ? [base] : kinds.map((entry) => `${base}-${entry}`);
454
+ }
@@ -0,0 +1,139 @@
1
+ /**
2
+ * Temporal playback (issue #497 REQ-004): a renderer-neutral play/pause/
3
+ * scrub controller over a time extent, plus thin bindings that drive time
4
+ * filters through the data-to-map bridge (`MountedSource.setFilter`) or
5
+ * directly through MapLibre layer filters.
6
+ *
7
+ * Memory is bounded by construction: the controller keeps only the current
8
+ * window (two numbers). Filter application is coalesced — at most one
9
+ * request in flight and at most one pending window; intermediate windows
10
+ * are dropped, never queued. No window data is pre-fetched or cached by the
11
+ * controller itself (the issue's snapshot-plus-delta realtime vocabulary
12
+ * from #393 applies only when playback is layered over a realtime feed;
13
+ * playback over static data has no realtime dependency).
14
+ *
15
+ * @module
16
+ */
17
+ import { HonuaSdkError } from "../core/error-envelope.js";
18
+ /** A half-open time window `[start, end)` in epoch milliseconds. @experimental */
19
+ export interface TemporalWindow {
20
+ readonly start: number;
21
+ readonly end: number;
22
+ }
23
+ /** Accepted time inputs: epoch ms, `Date`, or an ISO-8601 string. @experimental */
24
+ export type TemporalInstant = number | Date | string;
25
+ /** Payload for `"tick"` listeners. @experimental */
26
+ export interface TemporalPlaybackTick {
27
+ /** The window that was just applied. */
28
+ readonly window: TemporalWindow;
29
+ /** Window-start progress through the extent, 0..1. */
30
+ readonly progress: number;
31
+ readonly playing: boolean;
32
+ }
33
+ /** Event names emitted by the controller. @experimental */
34
+ export type TemporalPlaybackEventType = "tick" | "play" | "pause" | "end" | "error";
35
+ /** Listener registration handle. @experimental */
36
+ export interface TemporalPlaybackSubscription {
37
+ remove(): void;
38
+ }
39
+ /**
40
+ * Bridge-shaped filter sink: the subset of `MountedSource` the playback
41
+ * binding needs. Any object with a Query-shaped `setFilter` works.
42
+ *
43
+ * @experimental
44
+ */
45
+ export interface TemporalFilterHandle {
46
+ setFilter(query: {
47
+ where?: string;
48
+ } | undefined): Promise<unknown>;
49
+ }
50
+ /** MapLibre-shaped filter sink for client-side (already materialized) data. @experimental */
51
+ export interface TemporalFilterMap {
52
+ setFilter(layerId: string, filter: unknown): void;
53
+ getFilter?(layerId: string): unknown;
54
+ }
55
+ /** Options for {@link createTemporalPlayback}. @experimental */
56
+ export interface TemporalPlaybackOptions {
57
+ /** Full playable time extent as `[start, end]`. */
58
+ readonly extent: readonly [TemporalInstant, TemporalInstant];
59
+ /** Window length in milliseconds. */
60
+ readonly windowMs: number;
61
+ /** Wall-clock delay between frames. @default 500 */
62
+ readonly frameIntervalMs?: number;
63
+ /** How far the window advances per frame. @default `windowMs` (non-overlapping windows) */
64
+ readonly stepMs?: number;
65
+ /** Restart from the extent start after the last window. @default false */
66
+ readonly loop?: boolean;
67
+ /**
68
+ * Bridge binding: a `MountedSource` (or anything with a Query-shaped
69
+ * `setFilter`). Each window becomes a `where` clause on `timeField`.
70
+ */
71
+ readonly handle?: TemporalFilterHandle;
72
+ /**
73
+ * MapLibre binding: client-side `setFilter` over already-materialized
74
+ * layers. Each window becomes a numeric range filter on `timeField`
75
+ * composed (`all`) with the layer's filter at bind time, so bridge
76
+ * geometry filters survive. Requires numeric (epoch ms) properties.
77
+ */
78
+ readonly layer?: {
79
+ readonly map: TemporalFilterMap;
80
+ readonly layerIds: readonly string[];
81
+ };
82
+ /** Custom sink; use instead of (or in addition to) `handle`/`layer`. */
83
+ readonly apply?: (window: TemporalWindow) => void | Promise<void>;
84
+ /** Feature property holding the feature's time. Required with `handle`/`layer`. */
85
+ readonly timeField?: string;
86
+ /** Extra query properties merged under the generated `where` (handle binding). */
87
+ readonly baseQuery?: Readonly<Record<string, unknown>> & {
88
+ readonly where?: string;
89
+ };
90
+ /**
91
+ * Override the generated `where` clause (handle binding). The default is
92
+ * `<timeField> >= <startMs> AND <timeField> < <endMs>`.
93
+ */
94
+ readonly formatWhere?: (window: TemporalWindow, timeField: string) => string;
95
+ }
96
+ /** Playback controller returned by {@link createTemporalPlayback}. @experimental */
97
+ export interface TemporalPlayback {
98
+ readonly playing: boolean;
99
+ /** The current window (epoch ms, half-open). */
100
+ readonly window: TemporalWindow;
101
+ /** The normalized extent (epoch ms). */
102
+ readonly extent: TemporalWindow;
103
+ /** Start advancing one window per frame. Applies the current window immediately. */
104
+ play(): void;
105
+ pause(): void;
106
+ /** Jump the window start to a time (clamped to the extent) and apply it. */
107
+ scrub(time: TemporalInstant): void;
108
+ /** Change the window length, keeping the current start, and apply it. */
109
+ setWindow(windowMs: number): void;
110
+ on(type: "tick", listener: (event: TemporalPlaybackTick) => void): TemporalPlaybackSubscription;
111
+ on(type: "play" | "pause" | "end", listener: () => void): TemporalPlaybackSubscription;
112
+ on(type: "error", listener: (error: unknown) => void): TemporalPlaybackSubscription;
113
+ /** Stop the timer and drop all listeners. Idempotent. */
114
+ dispose(): void;
115
+ }
116
+ /** Raised for invalid playback options. @experimental */
117
+ export declare class HonuaTemporalPlaybackError extends HonuaSdkError {
118
+ constructor(message: string);
119
+ }
120
+ /**
121
+ * Create a temporal playback controller over a time extent.
122
+ *
123
+ * @example
124
+ * ```ts
125
+ * const playback = createTemporalPlayback({
126
+ * handle: mounted, // from mountSource()
127
+ * timeField: "event_time",
128
+ * extent: ["2026-01-01T00:00:00Z", "2026-02-01T00:00:00Z"],
129
+ * windowMs: 24 * 60 * 60 * 1000,
130
+ * frameIntervalMs: 400,
131
+ * });
132
+ * playback.on("tick", ({ window, progress }) => updateSlider(progress));
133
+ * playback.play();
134
+ * ```
135
+ *
136
+ * @experimental
137
+ */
138
+ export declare function createTemporalPlayback(options: TemporalPlaybackOptions): TemporalPlayback;
139
+ //# sourceMappingURL=temporal-playback.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"temporal-playback.d.ts","sourceRoot":"","sources":["../../../src/map/temporal-playback.ts"],"names":[],"mappings":"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"}