@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,368 @@
1
+ /**
2
+ * Pure SQL compiler for the GeoParquet / DuckDB-WASM `Source`
3
+ * (`@honua/sdk-js/geoparquet`). Translates the protocol-neutral
4
+ * {@link Query} envelope into DuckDB SQL over `read_parquet(...)` with spatial
5
+ * predicate pushdown.
6
+ *
7
+ * This module has **no runtime dependencies** (not even `@duckdb/duckdb-wasm`)
8
+ * so it can be unit- and snapshot-tested in isolation and so the SQL text is a
9
+ * pure function of its inputs. The heavy DuckDB orchestration lives in
10
+ * `src/geoparquet/`.
11
+ *
12
+ * ## SQL-injection safety
13
+ *
14
+ * Every value this compiler interpolates is either (a) a numeric literal that
15
+ * is validated to be a finite JS number and rendered with {@link numberLiteral},
16
+ * (b) a string literal escaped by {@link stringLiteral} (single quotes doubled,
17
+ * embedded NULs rejected), or (c) a SQL identifier escaped by
18
+ * {@link quoteIdentifier} (double quotes doubled, embedded NULs / control chars
19
+ * rejected). The one exception is {@link Query.where}: like the GeoServices and
20
+ * OGC adapters, `where` is caller-authored filter SQL and is passed through
21
+ * verbatim (wrapped in parentheses). Callers are responsible for the trust
22
+ * boundary on `where`, exactly as they are for a GeoServices `where` clause.
23
+ *
24
+ * @module
25
+ */
26
+ // ── Identifier / literal escaping ─────────────────────────────
27
+ /**
28
+ * Quote a SQL identifier for DuckDB: wrap in double quotes and double any
29
+ * embedded double quote. Rejects NUL and other control characters, which
30
+ * cannot appear in a legitimate column name and are a classic smuggling
31
+ * vector. Overture column names (`id`, `names`, `geometry`, dotted struct
32
+ * paths accessed as whole columns) are all safe under this rule.
33
+ */
34
+ export function quoteIdentifier(name) {
35
+ if (typeof name !== "string" || name.length === 0) {
36
+ throw new Error(`geoparquet: invalid SQL identifier ${JSON.stringify(name)}`);
37
+ }
38
+ // biome-ignore lint/suspicious/noControlCharactersInRegex: rejecting control chars in identifiers is the point
39
+ if (/[\u0000-\u001f\u007f]/.test(name)) {
40
+ throw new Error(`geoparquet: SQL identifier contains control characters: ${JSON.stringify(name)}`);
41
+ }
42
+ return `"${name.replace(/"/g, '""')}"`;
43
+ }
44
+ /**
45
+ * Render a string SQL literal (single quotes doubled). Rejects embedded NUL.
46
+ */
47
+ export function stringLiteral(value) {
48
+ if (typeof value !== "string") {
49
+ throw new Error(`geoparquet: expected a string literal, received ${typeof value}`);
50
+ }
51
+ if (value.includes("\u0000")) {
52
+ throw new Error("geoparquet: string literal contains a NUL byte");
53
+ }
54
+ return `'${value.replace(/'/g, "''")}'`;
55
+ }
56
+ /**
57
+ * Render a numeric SQL literal, validating the input is a finite JS number.
58
+ * Non-finite values (NaN/Infinity) throw rather than emit `'NaN'` — a spatial
59
+ * predicate must never silently widen to an infinite envelope.
60
+ */
61
+ export function numberLiteral(value) {
62
+ if (typeof value !== "number" || !Number.isFinite(value)) {
63
+ throw new Error(`geoparquet: expected a finite number, received ${String(value)}`);
64
+ }
65
+ return String(value);
66
+ }
67
+ /**
68
+ * Render an integer SQL literal (offset / limit). Coerces via `Math.trunc` and
69
+ * validates non-negative finiteness.
70
+ */
71
+ export function integerLiteral(value) {
72
+ if (typeof value !== "number" || !Number.isFinite(value) || value < 0) {
73
+ throw new Error(`geoparquet: expected a non-negative integer, received ${String(value)}`);
74
+ }
75
+ return String(Math.trunc(value));
76
+ }
77
+ // ── read_parquet(...) source expression ───────────────────────
78
+ /**
79
+ * Build the `read_parquet(...)` table expression from one or more file URLs /
80
+ * globs. A single source renders `read_parquet('url')`; multiple sources
81
+ * render `read_parquet(['a', 'b'])`. Every URL is escaped as a string literal.
82
+ * `union_by_name := true` and `hive_partitioning := true` are set so mixed
83
+ * schemas across a hive-partitioned Overture theme line up by column name.
84
+ */
85
+ export function parquetSourceExpr(urls) {
86
+ if (!Array.isArray(urls) || urls.length === 0) {
87
+ throw new Error("geoparquet: at least one parquet URL is required");
88
+ }
89
+ const list = urls.map(stringLiteral);
90
+ const arg = list.length === 1 ? list[0] : `[${list.join(", ")}]`;
91
+ return `read_parquet(${arg}, union_by_name = true, hive_partitioning = true)`;
92
+ }
93
+ const GEOARROW_NATIVE_ENCODINGS = new Set([
94
+ "geoarrow-point",
95
+ "geoarrow-linestring",
96
+ "geoarrow-polygon",
97
+ "geoarrow-multipoint",
98
+ "geoarrow-multilinestring",
99
+ "geoarrow-multipolygon",
100
+ ]);
101
+ /** True for a GeoParquet 1.1 native single-geometry encoding (see {@link GeometryEncoding}). */
102
+ export function isGeoArrowNativeEncoding(encoding) {
103
+ return GEOARROW_NATIVE_ENCODINGS.has(encoding);
104
+ }
105
+ /**
106
+ * SQL expression that yields a DuckDB `GEOMETRY` from the stored geometry
107
+ * column, adapting to the physical encoding.
108
+ *
109
+ * @throws for a `geoarrow-*` encoding — GeoParquet 1.1 native columns are
110
+ * nested `STRUCT`/`LIST` values with no DuckDB spatial-predicate
111
+ * expression. Callers must fail closed before constructing SQL rather than
112
+ * let a malformed `ST_Intersects(...)` reach DuckDB; use a bbox-covering
113
+ * column for spatial pushdown and the dedicated native decoder for
114
+ * geometry projection instead.
115
+ */
116
+ export function geometryExpr(plan) {
117
+ const col = quoteIdentifier(plan.column);
118
+ switch (plan.encoding) {
119
+ case "wkb":
120
+ return `ST_GeomFromWKB(${col})`;
121
+ case "geojson":
122
+ return `ST_GeomFromGeoJSON(${col})`;
123
+ case "native":
124
+ return col;
125
+ case "geoarrow-point":
126
+ case "geoarrow-linestring":
127
+ case "geoarrow-polygon":
128
+ case "geoarrow-multipoint":
129
+ case "geoarrow-multilinestring":
130
+ case "geoarrow-multipolygon":
131
+ throw new Error(`geoparquet: column ${JSON.stringify(plan.column)} uses the GeoParquet 1.1 native "${plan.encoding.slice("geoarrow-".length)}" encoding, which DuckDB stores as a nested struct/list value with no spatial-predicate expression. Supply a bbox-covering column for spatialFilter pushdown; geometry projection is decoded separately from the raw column.`);
132
+ }
133
+ }
134
+ /**
135
+ * Reduce any Esri-shaped {@link SpatialFilter} geometry to its axis-aligned
136
+ * bounding box. Envelopes are exact; points, polylines (`paths`), polygons
137
+ * (`rings`), and multipoints (`points`) are reduced to their coordinate
138
+ * bounds. Returns `undefined` when the geometry carries no usable coordinates.
139
+ */
140
+ export function bboxFromSpatialFilter(filter) {
141
+ const g = filter.geometry;
142
+ if (typeof g.xmin === "number" &&
143
+ typeof g.ymin === "number" &&
144
+ typeof g.xmax === "number" &&
145
+ typeof g.ymax === "number") {
146
+ return { xmin: g.xmin, ymin: g.ymin, xmax: g.xmax, ymax: g.ymax };
147
+ }
148
+ if (typeof g.x === "number" && typeof g.y === "number") {
149
+ return { xmin: g.x, ymin: g.y, xmax: g.x, ymax: g.y };
150
+ }
151
+ const coordSets = [];
152
+ if (Array.isArray(g.rings))
153
+ coordSets.push(...g.rings);
154
+ if (Array.isArray(g.paths))
155
+ coordSets.push(...g.paths);
156
+ if (Array.isArray(g.points))
157
+ coordSets.push(g.points);
158
+ let xmin = Number.POSITIVE_INFINITY;
159
+ let ymin = Number.POSITIVE_INFINITY;
160
+ let xmax = Number.NEGATIVE_INFINITY;
161
+ let ymax = Number.NEGATIVE_INFINITY;
162
+ let seen = false;
163
+ for (const set of coordSets) {
164
+ if (!Array.isArray(set))
165
+ continue;
166
+ for (const pt of set) {
167
+ if (!Array.isArray(pt) || typeof pt[0] !== "number" || typeof pt[1] !== "number")
168
+ continue;
169
+ seen = true;
170
+ xmin = Math.min(xmin, pt[0]);
171
+ ymin = Math.min(ymin, pt[1]);
172
+ xmax = Math.max(xmax, pt[0]);
173
+ ymax = Math.max(ymax, pt[1]);
174
+ }
175
+ }
176
+ return seen ? { xmin, ymin, xmax, ymax } : undefined;
177
+ }
178
+ /**
179
+ * True when the spatial filter is a plain envelope (exact bbox intersection).
180
+ * Anything else is reduced to its bbox and is therefore an approximation the
181
+ * caller should be told about via a `degraded` reason.
182
+ */
183
+ export function isEnvelopeFilter(filter) {
184
+ return filter.geometryType === "esriGeometryEnvelope";
185
+ }
186
+ /**
187
+ * Compile the spatial predicate for a bbox against a geometry plan. Prefers the
188
+ * GeoParquet 1.1 bbox-covering column (row-group-skippable) when present, then
189
+ * falls back to `ST_Intersects(<geom>, ST_MakeEnvelope(...))`.
190
+ */
191
+ export function spatialPredicate(bbox, plan) {
192
+ const xmin = numberLiteral(bbox.xmin);
193
+ const ymin = numberLiteral(bbox.ymin);
194
+ const xmax = numberLiteral(bbox.xmax);
195
+ const ymax = numberLiteral(bbox.ymax);
196
+ if (plan.bboxColumn) {
197
+ const b = quoteIdentifier(plan.bboxColumn);
198
+ // Two envelopes intersect iff neither is strictly to one side of the other.
199
+ return `${b}.xmin <= ${xmax} AND ${b}.xmax >= ${xmin} AND ` + `${b}.ymin <= ${ymax} AND ${b}.ymax >= ${ymin}`;
200
+ }
201
+ return `ST_Intersects(${geometryExpr(plan)}, ST_MakeEnvelope(${xmin}, ${ymin}, ${xmax}, ${ymax}))`;
202
+ }
203
+ const DEFAULT_GEOMETRY_ALIAS = "geometry";
204
+ // ── WHERE + ORDER BY + LIMIT fragments ────────────────────────
205
+ function whereClause(query, spatial) {
206
+ const clauses = [];
207
+ if (typeof query.where === "string" && query.where.trim().length > 0) {
208
+ clauses.push(`(${query.where})`);
209
+ }
210
+ if (spatial)
211
+ clauses.push(`(${spatial})`);
212
+ return clauses.length > 0 ? ` WHERE ${clauses.join(" AND ")}` : "";
213
+ }
214
+ function orderByClause(orderBy) {
215
+ if (!orderBy || orderBy.length === 0)
216
+ return "";
217
+ const parts = orderBy.map((s) => {
218
+ const dir = s.direction === "desc" ? " DESC" : " ASC";
219
+ return `${quoteIdentifier(s.field)}${dir}`;
220
+ });
221
+ return ` ORDER BY ${parts.join(", ")}`;
222
+ }
223
+ function limitOffsetClause(query) {
224
+ let out = "";
225
+ const limit = query.pagination?.limit;
226
+ const offset = query.pagination?.offset;
227
+ if (typeof limit === "number")
228
+ out += ` LIMIT ${integerLiteral(limit)}`;
229
+ if (typeof offset === "number" && offset > 0)
230
+ out += ` OFFSET ${integerLiteral(offset)}`;
231
+ return out;
232
+ }
233
+ // ── Projection ────────────────────────────────────────────────
234
+ function projection(query, options) {
235
+ const geom = options.geometry;
236
+ const geomAlias = quoteIdentifier(options.geometryAlias ?? DEFAULT_GEOMETRY_ALIAS);
237
+ const wantGeometry = query.returnGeometry !== false && geom !== undefined;
238
+ // `geoarrow-*` columns are raw nested struct/list values, not a DuckDB
239
+ // `GEOMETRY` — project them as-is and let the caller's native decoder
240
+ // (built on src/columnar/geoarrow.ts) turn the materialized rows into
241
+ // GeoJSON. Every other encoding keeps projecting `ST_AsGeoJSON(...)` text.
242
+ const geomSelect = wantGeometry
243
+ ? isGeoArrowNativeEncoding(geom.encoding)
244
+ ? `${quoteIdentifier(geom.column)} AS ${geomAlias}`
245
+ : `ST_AsGeoJSON(${geometryExpr(geom)}) AS ${geomAlias}`
246
+ : undefined;
247
+ const outFields = query.outFields;
248
+ if (outFields && outFields.length > 0) {
249
+ const cols = outFields.filter((f) => !geom || f !== geom.column).map((f) => quoteIdentifier(f));
250
+ if (geomSelect)
251
+ cols.push(geomSelect);
252
+ return cols.length > 0 ? cols.join(", ") : "*";
253
+ }
254
+ // No explicit projection: select every non-geometry column, plus the
255
+ // GeoJSON-projected geometry when requested. When we do not know the column
256
+ // list, fall back to `* EXCLUDE (geom)` so raw WKB never rides along.
257
+ const nonGeom = options.columns?.filter((c) => !geom || c !== geom.column);
258
+ if (nonGeom && nonGeom.length > 0) {
259
+ const cols = nonGeom.map((c) => quoteIdentifier(c));
260
+ if (geomSelect)
261
+ cols.push(geomSelect);
262
+ return cols.join(", ");
263
+ }
264
+ if (geom) {
265
+ const base = `* EXCLUDE (${quoteIdentifier(geom.column)})`;
266
+ return geomSelect ? `${base}, ${geomSelect}` : base;
267
+ }
268
+ return "*";
269
+ }
270
+ /**
271
+ * Compile a feature {@link Query} into a DuckDB `SELECT`. The geometry column,
272
+ * when present and `returnGeometry !== false`, is projected as GeoJSON text via
273
+ * `ST_AsGeoJSON` under the alias `geometry` (overridable).
274
+ */
275
+ export function compileQuery(query, options) {
276
+ const from = parquetSourceExpr(options.sources);
277
+ let spatial;
278
+ let bboxApproximated = false;
279
+ if (query.spatialFilter) {
280
+ if (!options.geometry) {
281
+ throw new Error("geoparquet: spatialFilter supplied but the source has no detected geometry column");
282
+ }
283
+ const bbox = bboxFromSpatialFilter(query.spatialFilter);
284
+ if (bbox) {
285
+ spatial = spatialPredicate(bbox, options.geometry);
286
+ bboxApproximated = !isEnvelopeFilter(query.spatialFilter);
287
+ }
288
+ }
289
+ const sql = `SELECT ${projection(query, options)} FROM ${from}${whereClause(query, spatial)}${orderByClause(query.orderBy)}${limitOffsetClause(query)}`;
290
+ return { sql, bboxApproximated };
291
+ }
292
+ function aggregateFn(fn, field) {
293
+ const col = field === "*" ? "*" : quoteIdentifier(field);
294
+ switch (fn) {
295
+ case "count":
296
+ return `count(${col})`;
297
+ case "sum":
298
+ return `sum(${col})`;
299
+ case "avg":
300
+ return `avg(${col})`;
301
+ case "min":
302
+ return `min(${col})`;
303
+ case "max":
304
+ return `max(${col})`;
305
+ case "stddev":
306
+ return `stddev_samp(${col})`;
307
+ case "var":
308
+ return `var_samp(${col})`;
309
+ }
310
+ }
311
+ /**
312
+ * Compile an {@link AggregationSpec} into a GROUP BY `SELECT`. Group-by fields
313
+ * are echoed in the projection (so the returned rows carry the group key),
314
+ * followed by each metric expression aliased to its `alias` (or `fn_field`).
315
+ */
316
+ export function compileAggregate(query, options) {
317
+ const from = parquetSourceExpr(options.sources);
318
+ const agg = query.aggregation;
319
+ if (!agg.metrics || agg.metrics.length === 0) {
320
+ throw new Error("geoparquet: aggregation requires at least one metric");
321
+ }
322
+ const groupBy = agg.groupBy ?? [];
323
+ const selectParts = [];
324
+ for (const field of groupBy)
325
+ selectParts.push(quoteIdentifier(field));
326
+ for (const m of agg.metrics) {
327
+ const alias = quoteIdentifier(m.alias ?? `${m.fn}_${m.field === "*" ? "all" : m.field}`);
328
+ selectParts.push(`${aggregateFn(m.fn, m.field)} AS ${alias}`);
329
+ }
330
+ let spatial;
331
+ let bboxApproximated = false;
332
+ if (query.spatialFilter && options.geometry) {
333
+ const bbox = bboxFromSpatialFilter(query.spatialFilter);
334
+ if (bbox) {
335
+ spatial = spatialPredicate(bbox, options.geometry);
336
+ bboxApproximated = !isEnvelopeFilter(query.spatialFilter);
337
+ }
338
+ }
339
+ const groupByClause = groupBy.length > 0 ? ` GROUP BY ${groupBy.map((f) => quoteIdentifier(f)).join(", ")}` : "";
340
+ const sql = `SELECT ${selectParts.join(", ")} FROM ${from}${whereClause(query, spatial)}${groupByClause}${orderByClause(query.orderBy)}${limitOffsetClause(query)}`;
341
+ return { sql, bboxApproximated };
342
+ }
343
+ /**
344
+ * SQL that reads the parquet footer schema without scanning rows.
345
+ */
346
+ export function describeSql(sources) {
347
+ return `DESCRIBE SELECT * FROM ${parquetSourceExpr(sources)}`;
348
+ }
349
+ /**
350
+ * Cheap row-count estimate from the parquet footer (`num_rows` per row group),
351
+ * summed across all files. Avoids a full `count(*)` table scan on large
352
+ * Overture extracts.
353
+ */
354
+ export function rowEstimateSql(sources) {
355
+ const list = sources.map(stringLiteral);
356
+ const arg = list.length === 1 ? list[0] : `[${list.join(", ")}]`;
357
+ return `SELECT CAST(COALESCE(sum(num_rows), 0) AS BIGINT) AS row_estimate FROM parquet_file_metadata(${arg})`;
358
+ }
359
+ /**
360
+ * SQL that reads the GeoParquet `geo` key-value metadata (the JSON blob that
361
+ * carries `primary_column`, per-column `encoding`, and `crs`). Returns zero
362
+ * rows for Parquet-native geometry files, which carry no `geo` metadata.
363
+ */
364
+ export function geoMetadataSql(sources) {
365
+ const list = sources.map(stringLiteral);
366
+ const arg = list.length === 1 ? list[0] : `[${list.join(", ")}]`;
367
+ return `SELECT files.file_name, geo.value FROM parquet_file_metadata(${arg}) AS files LEFT JOIN parquet_kv_metadata(${arg}) AS geo ON geo.file_name = files.file_name AND decode(geo.key) = 'geo' ORDER BY files.file_name`;
368
+ }
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Encode an OData key predicate without hiding its structural grammar.
3
+ *
4
+ * Quotes plus the `,` / `=` delimiters outside quoted literals remain
5
+ * visible to direct and JSON-batch OData parsers. Unsafe value characters
6
+ * are percent-encoded by Unicode code point so non-BMP string keys remain
7
+ * valid path data rather than splitting or truncating the target URL.
8
+ *
9
+ * @internal
10
+ */
11
+ export declare function encodeOdataKeyPredicatePath(key: string | number): string;
12
+ //# sourceMappingURL=odata-key-path.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"odata-key-path.d.ts","sourceRoot":"","sources":["../../../src/core/odata-key-path.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,wBAAgB,2BAA2B,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,MAAM,CAiBxE"}
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Encode an OData key predicate without hiding its structural grammar.
3
+ *
4
+ * Quotes plus the `,` / `=` delimiters outside quoted literals remain
5
+ * visible to direct and JSON-batch OData parsers. Unsafe value characters
6
+ * are percent-encoded by Unicode code point so non-BMP string keys remain
7
+ * valid path data rather than splitting or truncating the target URL.
8
+ *
9
+ * @internal
10
+ */
11
+ export function encodeOdataKeyPredicatePath(key) {
12
+ const raw = String(key);
13
+ let output = "";
14
+ let inQuote = false;
15
+ for (const character of raw) {
16
+ if (character === "'") {
17
+ inQuote = !inQuote;
18
+ output += "'";
19
+ continue;
20
+ }
21
+ if (!inQuote && (character === "," || character === "=")) {
22
+ output += character;
23
+ continue;
24
+ }
25
+ output += /[A-Za-z0-9\-._~]/.test(character) ? character : encodeURIComponent(character);
26
+ }
27
+ return output;
28
+ }
@@ -0,0 +1,66 @@
1
+ /**
2
+ * Metadata-driven OData write encoding primitives.
3
+ *
4
+ * This module deliberately has no transport dependencies. Callers supply the
5
+ * already-cached CSDL projection and decide how to attach the encoded body,
6
+ * content type, and key to an HTTP request.
7
+ */
8
+ import { type HonuaOdataMetadata } from "./odata.js";
9
+ /** Stable failure categories for local, pre-transport encoding errors. */
10
+ export type HonuaOdataEdmEncodingErrorCode = "cyclic-value" | "invalid-metadata" | "invalid-options" | "invalid-value" | "max-depth-exceeded" | "missing-key" | "resource-limit-exceeded" | "unknown-entity-set" | "unsupported-type";
11
+ /**
12
+ * A redacted local encoding failure.
13
+ *
14
+ * Messages contain a fixed explanation and a bounded, escaped property path.
15
+ * The rejected value and request body are intentionally never retained.
16
+ */
17
+ export declare class HonuaOdataEdmEncodingError extends TypeError {
18
+ readonly code: HonuaOdataEdmEncodingErrorCode;
19
+ readonly path: string;
20
+ constructor(code: HonuaOdataEdmEncodingErrorCode, path: string);
21
+ }
22
+ /**
23
+ * Controls the hard recursion bound for complex, collection, and untyped
24
+ * values. Fixed breadth and aggregate-node budgets also protect every call.
25
+ */
26
+ export interface HonuaOdataEdmEncodingOptions {
27
+ /** Maximum nested container depth. Defaults to and cannot exceed 32. */
28
+ readonly maxDepth?: number;
29
+ }
30
+ /** Controls metadata-typed entity-key encoding. */
31
+ export interface HonuaOdataEntityKeyEncodingOptions extends HonuaOdataEdmEncodingOptions {
32
+ /**
33
+ * Ordered metadata key fields to place in this URL predicate. Omit this for
34
+ * a direct entity set. Navigation paths may provide the child-key subset
35
+ * because the parent key is already carried by the path.
36
+ */
37
+ readonly keyFields?: readonly string[];
38
+ }
39
+ /** Body projection consumed by a later HTTP/edit integration layer. */
40
+ export interface HonuaOdataEncodedWriteBody {
41
+ /** JSON-compatible body with metadata-declared values encoded losslessly. */
42
+ readonly body: Record<string, unknown>;
43
+ /** Whether the body requires `IEEE754Compatible=true` on its media type. */
44
+ readonly requiresIeee754Compatible: boolean;
45
+ }
46
+ /** Key projection consumed by direct and batch URL builders. */
47
+ export interface HonuaOdataEncodedEntityKey {
48
+ /** OData key predicate content, without surrounding parentheses. */
49
+ readonly literal: string;
50
+ /** The same predicate encoded as one RFC 3986-safe URL path segment. */
51
+ readonly pathSegment: string;
52
+ }
53
+ /**
54
+ * Encode one entity body using the entity and complex property declarations in
55
+ * an existing OData metadata snapshot.
56
+ */
57
+ export declare function encodeOdataWriteBody(metadata: HonuaOdataMetadata, entitySet: string, body: Readonly<Record<string, unknown>>, options?: HonuaOdataEdmEncodingOptions): HonuaOdataEncodedWriteBody;
58
+ /**
59
+ * Encode a single or composite primary key in metadata declaration order.
60
+ *
61
+ * A single key accepts either its scalar value or an object carrying the key
62
+ * property. Composite keys require an object so component names cannot be
63
+ * confused or reordered by the caller.
64
+ */
65
+ export declare function encodeOdataEntityKey(metadata: HonuaOdataMetadata, entitySet: string, key: unknown, options?: HonuaOdataEntityKeyEncodingOptions): HonuaOdataEncodedEntityKey;
66
+ //# sourceMappingURL=odata-write-codec.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"odata-write-codec.d.ts","sourceRoot":"","sources":["../../../src/core/odata-write-codec.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAGH,OAAO,EAGL,KAAK,kBAAkB,EAGxB,MAAM,YAAY,CAAC;AAwCpB,0EAA0E;AAC1E,MAAM,MAAM,8BAA8B,GACtC,cAAc,GACd,kBAAkB,GAClB,iBAAiB,GACjB,eAAe,GACf,oBAAoB,GACpB,aAAa,GACb,yBAAyB,GACzB,oBAAoB,GACpB,kBAAkB,CAAC;AAEvB;;;;;GAKG;AACH,qBAAa,0BAA2B,SAAQ,SAAS;IACvD,SAAgB,IAAI,EAAE,8BAA8B,CAAC;IACrD,SAAgB,IAAI,EAAE,MAAM,CAAC;gBAEV,IAAI,EAAE,8BAA8B,EAAE,IAAI,EAAE,MAAM;CAMtE;AAED;;;GAGG;AACH,MAAM,WAAW,4BAA4B;IAC3C,wEAAwE;IACxE,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;CAC5B;AAED,mDAAmD;AACnD,MAAM,WAAW,kCAAmC,SAAQ,4BAA4B;IACtF;;;;OAIG;IACH,QAAQ,CAAC,SAAS,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;CACxC;AAED,uEAAuE;AACvE,MAAM,WAAW,0BAA0B;IACzC,6EAA6E;IAC7E,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACvC,4EAA4E;IAC5E,QAAQ,CAAC,yBAAyB,EAAE,OAAO,CAAC;CAC7C;AAED,gEAAgE;AAChE,MAAM,WAAW,0BAA0B;IACzC,oEAAoE;IACpE,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,wEAAwE;IACxE,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;CAC9B;AAkBD;;;GAGG;AACH,wBAAgB,oBAAoB,CAClC,QAAQ,EAAE,kBAAkB,EAC5B,SAAS,EAAE,MAAM,EACjB,IAAI,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,EACvC,OAAO,GAAE,4BAAiC,GACzC,0BAA0B,CAS5B;AAED;;;;;;GAMG;AACH,wBAAgB,oBAAoB,CAClC,QAAQ,EAAE,kBAAkB,EAC5B,SAAS,EAAE,MAAM,EACjB,GAAG,EAAE,OAAO,EACZ,OAAO,GAAE,kCAAuC,GAC/C,0BAA0B,CAyC5B"}