@honua/react 0.1.0-beta.0 → 0.1.2-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 (315) 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 +11 -2
  14. package/contract/index.d.ts.map +1 -1
  15. package/contract/index.js +4 -1
  16. package/contract/pmtiles.d.ts +31 -0
  17. package/contract/pmtiles.d.ts.map +1 -1
  18. package/contract/pmtiles.js +60 -20
  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 +35 -17
  32. package/contract/source.d.ts.map +1 -1
  33. package/contract/source.js +267 -71
  34. package/contract/types.d.ts +140 -4
  35. package/contract/types.d.ts.map +1 -1
  36. package/contract/types.js +6 -14
  37. package/core/bounded-json.d.ts +4 -0
  38. package/core/bounded-json.d.ts.map +1 -0
  39. package/core/bounded-json.js +38 -0
  40. package/core/cache-state.d.ts +9 -0
  41. package/core/cache-state.d.ts.map +1 -1
  42. package/core/cache-state.js +10 -0
  43. package/core/capabilities-xml.d.ts +33 -0
  44. package/core/capabilities-xml.d.ts.map +1 -0
  45. package/core/capabilities-xml.js +331 -0
  46. package/core/client.d.ts +4 -1
  47. package/core/client.d.ts.map +1 -1
  48. package/core/client.js +115 -29
  49. package/core/error-classifications.d.ts +153 -0
  50. package/core/error-classifications.d.ts.map +1 -0
  51. package/core/error-classifications.js +140 -0
  52. package/core/error-code-registry.d.ts +158 -0
  53. package/core/error-code-registry.d.ts.map +1 -0
  54. package/core/error-code-registry.js +143 -0
  55. package/core/error-envelope.d.ts +68 -0
  56. package/core/error-envelope.d.ts.map +1 -0
  57. package/core/error-envelope.js +357 -0
  58. package/core/errors.d.ts +45 -29
  59. package/core/errors.d.ts.map +1 -1
  60. package/core/errors.js +97 -50
  61. package/core/geoparquet-sql.d.ts +30 -2
  62. package/core/geoparquet-sql.d.ts.map +1 -1
  63. package/core/geoparquet-sql.js +38 -3
  64. package/core/odata-key-path.d.ts +12 -0
  65. package/core/odata-key-path.d.ts.map +1 -0
  66. package/core/odata-key-path.js +28 -0
  67. package/core/odata-write-codec.d.ts +66 -0
  68. package/core/odata-write-codec.d.ts.map +1 -0
  69. package/core/odata-write-codec.js +1083 -0
  70. package/core/odata.d.ts +59 -7
  71. package/core/odata.d.ts.map +1 -1
  72. package/core/odata.js +431 -99
  73. package/core/ogc-features.d.ts.map +1 -1
  74. package/core/ogc-features.js +3 -0
  75. package/core/ogc-maps.d.ts +7 -0
  76. package/core/ogc-maps.d.ts.map +1 -1
  77. package/core/ogc-maps.js +46 -7
  78. package/core/ogc-processes.d.ts +2 -1
  79. package/core/ogc-processes.d.ts.map +1 -1
  80. package/core/ogc-processes.js +33 -5
  81. package/core/ogc-records.d.ts +9 -0
  82. package/core/ogc-records.d.ts.map +1 -1
  83. package/core/ogc-records.js +78 -29
  84. package/core/ogc-tiles.d.ts +10 -1
  85. package/core/ogc-tiles.d.ts.map +1 -1
  86. package/core/ogc-tiles.js +56 -17
  87. package/core/path-utils.d.ts +9 -0
  88. package/core/path-utils.d.ts.map +1 -1
  89. package/core/path-utils.js +19 -0
  90. package/core/protocol-transport.d.ts +1 -0
  91. package/core/protocol-transport.d.ts.map +1 -1
  92. package/core/request-pipeline.d.ts.map +1 -1
  93. package/core/request-pipeline.js +1 -1
  94. package/core/spatial-filter.d.ts +8 -4
  95. package/core/spatial-filter.d.ts.map +1 -1
  96. package/core/spatial-filter.js +206 -16
  97. package/core/stac-static.d.ts +6 -2
  98. package/core/stac-static.d.ts.map +1 -1
  99. package/core/stac-static.js +260 -69
  100. package/core/surfaces.d.ts.map +1 -1
  101. package/core/surfaces.js +69 -5
  102. package/core/types.d.ts +125 -7
  103. package/core/types.d.ts.map +1 -1
  104. package/core/wfs-capabilities.d.ts +2 -0
  105. package/core/wfs-capabilities.d.ts.map +1 -1
  106. package/core/wfs-capabilities.js +8 -1
  107. package/core/wfs.d.ts.map +1 -1
  108. package/core/wfs.js +10 -0
  109. package/core/wms-capabilities.d.ts +56 -84
  110. package/core/wms-capabilities.d.ts.map +1 -1
  111. package/core/wms-capabilities.js +212 -509
  112. package/core/wmts-capabilities.d.ts +62 -62
  113. package/core/wmts-capabilities.d.ts.map +1 -1
  114. package/core/wmts-capabilities.js +224 -371
  115. package/esri-compat/editor.d.ts +8 -0
  116. package/esri-compat/editor.d.ts.map +1 -1
  117. package/esri-compat/editor.js +13 -0
  118. package/esri-compat/feature-layer.d.ts +2 -0
  119. package/esri-compat/feature-layer.d.ts.map +1 -1
  120. package/esri-compat/feature-layer.js +1 -0
  121. package/esri-compat/layer-list.d.ts +11 -0
  122. package/esri-compat/layer-list.d.ts.map +1 -1
  123. package/esri-compat/layer-list.js +49 -0
  124. package/esri-compat/legend.d.ts +10 -0
  125. package/esri-compat/legend.d.ts.map +1 -1
  126. package/esri-compat/legend.js +27 -0
  127. package/esri-compat/portal.d.ts +197 -0
  128. package/esri-compat/portal.d.ts.map +1 -0
  129. package/esri-compat/portal.js +251 -0
  130. package/esri-compat/renderer-objects.d.ts +40 -0
  131. package/esri-compat/renderer-objects.d.ts.map +1 -0
  132. package/esri-compat/renderer-objects.js +62 -0
  133. package/esri-compat/sketch.d.ts +43 -0
  134. package/esri-compat/sketch.d.ts.map +1 -1
  135. package/esri-compat/sketch.js +46 -0
  136. package/esri-compat/snapping.d.ts +43 -0
  137. package/esri-compat/snapping.d.ts.map +1 -0
  138. package/esri-compat/snapping.js +45 -0
  139. package/esri-compat/url.d.ts.map +1 -1
  140. package/esri-compat/url.js +1 -1
  141. package/esri-compat/widget-host.d.ts +84 -0
  142. package/esri-compat/widget-host.d.ts.map +1 -0
  143. package/esri-compat/widget-host.js +159 -0
  144. package/filter-registry/index.d.ts +145 -0
  145. package/filter-registry/index.d.ts.map +1 -0
  146. package/filter-registry/index.js +531 -0
  147. package/generated-app/errors.d.ts +18 -2
  148. package/generated-app/errors.d.ts.map +1 -1
  149. package/generated-app/errors.js +44 -11
  150. package/map/automatic-mount-integration.d.ts +134 -0
  151. package/map/automatic-mount-integration.d.ts.map +1 -0
  152. package/map/automatic-mount-integration.js +305 -0
  153. package/map/automatic-source-strategy.d.ts +125 -0
  154. package/map/automatic-source-strategy.d.ts.map +1 -0
  155. package/map/automatic-source-strategy.js +494 -0
  156. package/map/data-to-map-bridge.d.ts +293 -0
  157. package/map/data-to-map-bridge.d.ts.map +1 -0
  158. package/map/data-to-map-bridge.js +817 -0
  159. package/map/geojson-projection.d.ts +33 -0
  160. package/map/geojson-projection.d.ts.map +1 -0
  161. package/map/geojson-projection.js +118 -0
  162. package/map/index.d.ts +14 -0
  163. package/map/index.d.ts.map +1 -1
  164. package/map/index.js +8 -0
  165. package/map/raster-source-spec.d.ts +30 -0
  166. package/map/raster-source-spec.d.ts.map +1 -0
  167. package/map/raster-source-spec.js +190 -0
  168. package/map/raster-source-strategy.d.ts +65 -0
  169. package/map/raster-source-strategy.d.ts.map +1 -0
  170. package/map/raster-source-strategy.js +232 -0
  171. package/map/source-to-maplibre.d.ts +91 -0
  172. package/map/source-to-maplibre.d.ts.map +1 -0
  173. package/map/source-to-maplibre.js +454 -0
  174. package/map/temporal-playback.d.ts +139 -0
  175. package/map/temporal-playback.d.ts.map +1 -0
  176. package/map/temporal-playback.js +247 -0
  177. package/package.json +14 -1
  178. package/query-planner/canonical.d.ts +7 -0
  179. package/query-planner/canonical.d.ts.map +1 -0
  180. package/query-planner/canonical.js +145 -0
  181. package/query-planner/cql2-json.d.ts +17 -0
  182. package/query-planner/cql2-json.d.ts.map +1 -0
  183. package/query-planner/cql2-json.js +646 -0
  184. package/query-planner/diagnostics.d.ts +35 -0
  185. package/query-planner/diagnostics.d.ts.map +1 -0
  186. package/query-planner/diagnostics.js +373 -0
  187. package/query-planner/duckdb.d.ts +79 -0
  188. package/query-planner/duckdb.d.ts.map +1 -0
  189. package/query-planner/duckdb.js +758 -0
  190. package/query-planner/execution-context.d.ts +5 -0
  191. package/query-planner/execution-context.d.ts.map +1 -0
  192. package/query-planner/execution-context.js +50 -0
  193. package/query-planner/executor.d.ts +5 -0
  194. package/query-planner/executor.d.ts.map +1 -0
  195. package/query-planner/executor.js +223 -0
  196. package/query-planner/geoservices.d.ts +91 -0
  197. package/query-planner/geoservices.d.ts.map +1 -0
  198. package/query-planner/geoservices.js +667 -0
  199. package/query-planner/grpc.d.ts +100 -0
  200. package/query-planner/grpc.d.ts.map +1 -0
  201. package/query-planner/grpc.js +736 -0
  202. package/query-planner/index.d.ts +44 -0
  203. package/query-planner/index.d.ts.map +1 -0
  204. package/query-planner/index.js +30 -0
  205. package/query-planner/ir.d.ts +20 -0
  206. package/query-planner/ir.d.ts.map +1 -0
  207. package/query-planner/ir.js +417 -0
  208. package/query-planner/local-aggregate.d.ts +5 -0
  209. package/query-planner/local-aggregate.d.ts.map +1 -0
  210. package/query-planner/local-aggregate.js +126 -0
  211. package/query-planner/odata.d.ts +48 -0
  212. package/query-planner/odata.d.ts.map +1 -0
  213. package/query-planner/odata.js +662 -0
  214. package/query-planner/ogc-compiler.d.ts +55 -0
  215. package/query-planner/ogc-compiler.d.ts.map +1 -0
  216. package/query-planner/ogc-compiler.js +480 -0
  217. package/query-planner/ogc-features.d.ts +53 -0
  218. package/query-planner/ogc-features.d.ts.map +1 -0
  219. package/query-planner/ogc-features.js +617 -0
  220. package/query-planner/planner.d.ts +17 -0
  221. package/query-planner/planner.d.ts.map +1 -0
  222. package/query-planner/planner.js +818 -0
  223. package/query-planner/resource.d.ts +94 -0
  224. package/query-planner/resource.d.ts.map +1 -0
  225. package/query-planner/resource.js +526 -0
  226. package/query-planner/semantic-canonical.d.ts +15 -0
  227. package/query-planner/semantic-canonical.d.ts.map +1 -0
  228. package/query-planner/semantic-canonical.js +62 -0
  229. package/query-planner/semantic-compat.d.ts +10 -0
  230. package/query-planner/semantic-compat.d.ts.map +1 -0
  231. package/query-planner/semantic-compat.js +35 -0
  232. package/query-planner/semantic-compiler.d.ts +64 -0
  233. package/query-planner/semantic-compiler.d.ts.map +1 -0
  234. package/query-planner/semantic-compiler.js +81 -0
  235. package/query-planner/semantic-literals.d.ts +30 -0
  236. package/query-planner/semantic-literals.d.ts.map +1 -0
  237. package/query-planner/semantic-literals.js +133 -0
  238. package/query-planner/semantic-types.d.ts +265 -0
  239. package/query-planner/semantic-types.d.ts.map +1 -0
  240. package/query-planner/semantic-types.js +1 -0
  241. package/query-planner/semantic.d.ts +54 -0
  242. package/query-planner/semantic.d.ts.map +1 -0
  243. package/query-planner/semantic.js +1421 -0
  244. package/query-planner/types.d.ts +560 -0
  245. package/query-planner/types.d.ts.map +1 -0
  246. package/query-planner/types.js +49 -0
  247. package/query-planner/wfs-semantic.d.ts +58 -0
  248. package/query-planner/wfs-semantic.d.ts.map +1 -0
  249. package/query-planner/wfs-semantic.js +547 -0
  250. package/query-planner/wfs.d.ts +6 -0
  251. package/query-planner/wfs.d.ts.map +1 -0
  252. package/query-planner/wfs.js +71 -0
  253. package/react/external-map.d.ts +67 -0
  254. package/react/external-map.d.ts.map +1 -0
  255. package/react/external-map.js +63 -0
  256. package/react/honua-map.d.ts.map +1 -1
  257. package/react/honua-map.js +3 -2
  258. package/react/honua-source-layer.d.ts +110 -0
  259. package/react/honua-source-layer.d.ts.map +1 -0
  260. package/react/honua-source-layer.js +101 -0
  261. package/react/hooks.d.ts +28 -0
  262. package/react/hooks.d.ts.map +1 -1
  263. package/react/hooks.js +26 -4
  264. package/react/index.d.ts +8 -0
  265. package/react/index.d.ts.map +1 -1
  266. package/react/index.js +5 -0
  267. package/react/query-cache.d.ts +26 -4
  268. package/react/query-cache.d.ts.map +1 -1
  269. package/react/query-cache.js +51 -8
  270. package/react/selection.d.ts +177 -0
  271. package/react/selection.d.ts.map +1 -0
  272. package/react/selection.js +0 -0
  273. package/react/use-mounted-source.d.ts +69 -0
  274. package/react/use-mounted-source.d.ts.map +1 -0
  275. package/react/use-mounted-source.js +346 -0
  276. package/runtime/edit-snapping.d.ts +107 -0
  277. package/runtime/edit-snapping.d.ts.map +1 -0
  278. package/runtime/edit-snapping.js +190 -0
  279. package/runtime/errors.d.ts +3 -2
  280. package/runtime/errors.d.ts.map +1 -1
  281. package/runtime/errors.js +19 -2
  282. package/runtime/index.d.ts +6 -0
  283. package/runtime/index.d.ts.map +1 -1
  284. package/runtime/index.js +3 -0
  285. package/runtime/maplibre-renderer.d.ts +41 -0
  286. package/runtime/maplibre-renderer.d.ts.map +1 -0
  287. package/runtime/maplibre-renderer.js +322 -0
  288. package/runtime/query-tiles.d.ts +2 -1
  289. package/runtime/query-tiles.d.ts.map +1 -1
  290. package/runtime/query-tiles.js +6 -2
  291. package/runtime/source-bridge.d.ts +4 -4
  292. package/runtime/source-bridge.d.ts.map +1 -1
  293. package/runtime/source-bridge.js +9 -56
  294. package/runtime/style-interactions.d.ts +3 -2
  295. package/runtime/style-interactions.d.ts.map +1 -1
  296. package/runtime/style-interactions.js +11 -3
  297. package/runtime/terra-draw-sketch.d.ts +275 -0
  298. package/runtime/terra-draw-sketch.d.ts.map +1 -0
  299. package/runtime/terra-draw-sketch.js +404 -0
  300. package/source-capability-registry.d.ts +1 -0
  301. package/source-capability-registry.js +1 -0
  302. package/source-capability-types.d.ts +186 -0
  303. package/source-capability-types.js +7 -0
  304. package/style/index.d.ts +2 -0
  305. package/style/index.d.ts.map +1 -1
  306. package/style/index.js +2 -0
  307. package/style/renderers.d.ts +326 -0
  308. package/style/renderers.d.ts.map +1 -0
  309. package/style/renderers.js +517 -0
  310. package/webmap/convert-renderer.d.ts +24 -3
  311. package/webmap/convert-renderer.d.ts.map +1 -1
  312. package/webmap/convert-renderer.js +93 -112
  313. package/webmap/index.d.ts +1 -1
  314. package/webmap/index.d.ts.map +1 -1
  315. package/webmap/index.js +1 -1
@@ -0,0 +1,818 @@
1
+ import { CAPABILITIES, capabilities, } from "../contract/types.js";
2
+ import { canonicalStringify, sha256, toJsonValue } from "./canonical.js";
3
+ import { createCacheDecision, createPlanValidity, createPlanWarnings, createQueryPlanProvenance, createStepDiagnostics, summarizeFidelity, summarizePlanBounds, } from "./diagnostics.js";
4
+ import { compileDuckDbQuery, compileDuckDbQueryV2 } from "./duckdb.js";
5
+ import { compileGeoServicesQuery } from "./geoservices.js";
6
+ import { compileGrpcQuery } from "./grpc.js";
7
+ import { createGeoParquetQueryIr, createQueryIr, deepFreeze, queryFromCanonical } from "./ir.js";
8
+ import { compileOdataQuery } from "./odata.js";
9
+ import { compileOgcApiFeaturesQuery } from "./ogc-features.js";
10
+ import { parseGeoParquetResourceHandle } from "./resource.js";
11
+ import { HonuaQueryPlanExecutionError, HonuaQueryPlanningError, MAX_LOCAL_MATERIALIZATION_ROWS, QUERY_PLAN_DIAGNOSTICS_VERSION, QUERY_PLAN_KIND, QUERY_PLAN_V2_VERSION, QUERY_PLAN_VERSION, } from "./types.js";
12
+ import { compileWfsQuery } from "./wfs.js";
13
+ export function explainQuery(options) {
14
+ const resource = ownGeoParquetResource(options);
15
+ if (!resource && options.descriptor.protocol === "geoparquet")
16
+ assertCredentialFreeLegacyDescriptor(options.descriptor);
17
+ const ir = resource ? createGeoParquetQueryIr({ ...options, geoparquetResource: resource }) : createQueryIr(options);
18
+ const capabilityPolicy = options.capabilityPolicy ?? "strict";
19
+ const fallback = normalizeFallback(options.fallback);
20
+ const estimates = normalizeEstimates(options.estimates);
21
+ const executionMode = normalizeExecutionMode(options.executionMode);
22
+ let baseSteps;
23
+ let pushdown;
24
+ if (ir.query.aggregation && !ir.source.capabilities.includes("queryAggregate")) {
25
+ if (capabilityPolicy !== "degraded" || fallback.mode === "disabled") {
26
+ throw new HonuaQueryPlanningError(capabilityPolicy === "degraded" ? "fallback-disabled" : "capability-not-supported", `Source "${ir.source.id}" does not support queryAggregate; select degraded policy with an explicit bounded-local fallback to proceed`);
27
+ }
28
+ if (!ir.source.capabilities.includes("query")) {
29
+ throw new HonuaQueryPlanningError("capability-not-supported", `Source "${ir.source.id}" cannot run the bounded fallback because it does not support query`);
30
+ }
31
+ rejectUnsafeEstimate(fallback, estimates.rows, estimates.bytes);
32
+ const ogcFallback = ir.source.protocol === "ogc-features";
33
+ const inputQuery = localAggregateInputQuery(ir.query, ir.query.aggregation, fallback.maxRows, options.descriptor.schema?.primaryKey, { preserveGeometry: ogcFallback, adapterOwnsLookahead: ogcFallback });
34
+ baseSteps = [
35
+ {
36
+ id: "remote-input",
37
+ engine: "remote",
38
+ operation: "queryAll",
39
+ pushdown: "partial",
40
+ reason: `Push filters and projection to ${remoteEngineName(ir.source.protocol)}, fetching at most ${fallback.maxRows + 1} rows as an overflow sentinel.`,
41
+ requests: estimates.requests ?? 1,
42
+ query: inputQuery,
43
+ compiled: compileRemoteQuery(ir.source, inputQuery, "queryAll"),
44
+ },
45
+ {
46
+ id: "local-aggregate",
47
+ engine: "client",
48
+ operation: "aggregate",
49
+ pushdown: "none",
50
+ reason: `queryAggregate is unavailable; compute only after enforcing the ${fallback.maxRows}-row local ceiling.`,
51
+ inputStepId: "remote-input",
52
+ aggregation: ir.query.aggregation,
53
+ maxRows: fallback.maxRows,
54
+ ...(fallback.maxBytes !== undefined ? { maxBytes: fallback.maxBytes } : {}),
55
+ },
56
+ ];
57
+ pushdown = "partial";
58
+ }
59
+ else {
60
+ const capability = ir.query.aggregation ? "queryAggregate" : "query";
61
+ if (!ir.source.capabilities.includes(capability)) {
62
+ throw new HonuaQueryPlanningError("capability-not-supported", `Source "${ir.source.id}" does not support ${capability}`);
63
+ }
64
+ if (estimates.requests !== undefined && estimates.requests !== 1) {
65
+ throw new HonuaQueryPlanningError("invalid-query", `estimates.requests must be 1 for the exact single-request ${capability} operation`);
66
+ }
67
+ const spatialAggregation = ir.query.aggregation !== undefined && ir.query.spatialFilter !== undefined;
68
+ baseSteps = [
69
+ {
70
+ id: "remote-query",
71
+ engine: "remote",
72
+ operation: ir.query.aggregation ? "queryAggregate" : "query",
73
+ pushdown: "full",
74
+ reason: spatialAggregation
75
+ ? `${remoteEngineName(ir.source.protocol)} executes the spatial aggregation server-side: the spatial filter constrains the grouped statistics without materializing features.`
76
+ : `${remoteEngineName(ir.source.protocol)} can execute the complete canonical query remotely.`,
77
+ requests: 1,
78
+ query: ir.query,
79
+ compiled: compileRemoteQuery(ir.source, ir.query),
80
+ },
81
+ ];
82
+ pushdown = "full";
83
+ }
84
+ const provenance = createQueryPlanProvenance(options.descriptor, ir.source, options);
85
+ const steps = baseSteps.map((step) => ({ ...step, ...createStepDiagnostics(step, estimates, provenance) }));
86
+ const { fidelity, losses } = summarizeFidelity(steps);
87
+ const bounds = summarizePlanBounds(steps);
88
+ const cache = createCacheDecision(options.cache);
89
+ const validity = createPlanValidity(ir, provenance, capabilityPolicy, fallback, executionMode);
90
+ const warnings = createPlanWarnings(baseSteps, ir.source);
91
+ const unsigned = {
92
+ kind: QUERY_PLAN_KIND,
93
+ version: ir.version === "2.0" ? QUERY_PLAN_V2_VERSION : QUERY_PLAN_VERSION,
94
+ ir,
95
+ capabilityPolicy,
96
+ fallback,
97
+ pushdown,
98
+ diagnosticsVersion: QUERY_PLAN_DIAGNOSTICS_VERSION,
99
+ fidelity,
100
+ losses,
101
+ bounds,
102
+ cache,
103
+ provenance,
104
+ validity,
105
+ estimates,
106
+ steps,
107
+ warnings,
108
+ };
109
+ const fingerprint = sha256(canonicalStringify(toJsonValue(unsigned)));
110
+ return deepFreeze({
111
+ ...unsigned,
112
+ id: `plan_${fingerprint.slice("sha256:".length, "sha256:".length + 16)}`,
113
+ fingerprint,
114
+ });
115
+ }
116
+ function compileRemoteQuery(source, query, operation = "query") {
117
+ switch (source.protocol) {
118
+ case "geoservices-feature-service":
119
+ return compileGeoServicesQuery(source, query);
120
+ case "ogc-features":
121
+ return ogcQueryAllWireRequest(compileOgcApiFeaturesQuery(source, query), operation);
122
+ case "wfs":
123
+ return compileWfsQuery(source, query);
124
+ case "odata":
125
+ return compileOdataQuery(source, query);
126
+ case "geoparquet":
127
+ return isGeoParquetV2Source(source)
128
+ ? compileDuckDbQueryV2(source, query)
129
+ : compileDuckDbQuery(source, query);
130
+ case "grpc":
131
+ return compileGrpcQuery(source, query);
132
+ default:
133
+ throw new HonuaQueryPlanningError("unsupported-compiler", `No deterministic query compiler is registered for protocol "${source.protocol}"`);
134
+ }
135
+ }
136
+ function isGeoParquetV2Source(source) {
137
+ return source.protocol === "geoparquet" && source.geoparquet !== undefined && "resource" in source.geoparquet;
138
+ }
139
+ function ogcQueryAllWireRequest(compiled, operation) {
140
+ if (operation !== "queryAll" || compiled.limit === undefined)
141
+ return compiled;
142
+ return deepFreeze({ ...compiled, limit: compiled.limit + 1 });
143
+ }
144
+ function remoteEngineName(protocol) {
145
+ switch (protocol) {
146
+ case "ogc-features":
147
+ return "OGC API Features";
148
+ case "geoservices-feature-service":
149
+ return "GeoServices";
150
+ case "wfs":
151
+ return "WFS 2.0";
152
+ case "odata":
153
+ return "OData v4";
154
+ case "geoparquet":
155
+ return "DuckDB SQL (columnar)";
156
+ case "grpc":
157
+ return "Honua gRPC FeatureService";
158
+ default:
159
+ return protocol;
160
+ }
161
+ }
162
+ export function hashQueryPlan(plan) {
163
+ return persistedPlanSnapshot(plan).hash;
164
+ }
165
+ /** @internal Lightweight v1 integrity path for stable consumers that cannot execute v2 resources. */
166
+ export function hashQueryPlanV1(plan) {
167
+ try {
168
+ const detached = toJsonValue(plan);
169
+ if (!isPlanEnvelope(detached) || detached.version !== QUERY_PLAN_VERSION || detached.ir.version !== "1.0") {
170
+ return undefined;
171
+ }
172
+ if (detached.ir.source.protocol === "geoparquet") {
173
+ const sources = detached.ir.source.geoparquet?.sources;
174
+ if (!sources || sources.length === 0 || sources.some((source) => !isCredentialFreeLegacySource(source))) {
175
+ return undefined;
176
+ }
177
+ }
178
+ if (containsCredentialBearingPlanMaterial(detached))
179
+ return undefined;
180
+ const { id: _id, fingerprint: _fingerprint, ...unsigned } = detached;
181
+ return sha256(canonicalStringify(toJsonValue(unsigned)));
182
+ }
183
+ catch {
184
+ return undefined;
185
+ }
186
+ }
187
+ /** Internal executor boundary: validate once and continue with an immutable detached snapshot. */
188
+ export function validateQueryPlanSnapshot(plan) {
189
+ const snapshot = persistedPlanSnapshot(plan);
190
+ if (snapshot.hash !== snapshot.plan.fingerprint)
191
+ throw invalidPersistedPlan();
192
+ return snapshot.plan;
193
+ }
194
+ /** Canonical persistence boundary for accepted v1/v2 query plans. */
195
+ export function serializeQueryPlan(plan) {
196
+ const snapshot = persistedPlanSnapshot(plan);
197
+ if (snapshot.hash !== snapshot.plan.fingerprint)
198
+ throw invalidPersistedPlan();
199
+ return canonicalStringify(toJsonValue(snapshot.plan));
200
+ }
201
+ /** Parse a persisted plan without retaining invalid JSON or credential-bearing v1 locators. */
202
+ export function parseQueryPlan(serialized) {
203
+ if (typeof serialized !== "string")
204
+ throw invalidPersistedPlan();
205
+ let value;
206
+ try {
207
+ value = JSON.parse(serialized);
208
+ }
209
+ catch {
210
+ throw invalidPersistedPlan();
211
+ }
212
+ const snapshot = persistedPlanSnapshot(value);
213
+ if (snapshot.hash !== snapshot.plan.fingerprint)
214
+ throw invalidPersistedPlan();
215
+ return snapshot.plan;
216
+ }
217
+ /** Stable cache identity derived only after the plan passes the persistence guard. */
218
+ export function queryPlanCacheKey(plan) {
219
+ const snapshot = persistedPlanSnapshot(plan);
220
+ if (snapshot.hash !== snapshot.plan.fingerprint)
221
+ throw invalidPersistedPlan();
222
+ return `honua-query-plan:${snapshot.plan.version}:${snapshot.plan.fingerprint}`;
223
+ }
224
+ /** Explicitly migrate a credential-free v1 GeoParquet plan to opaque v2 identity. */
225
+ export function migrateGeoParquetQueryPlanV1(legacyPlan, resourceValue) {
226
+ const parsed = parseQueryPlan(serializeQueryPlan(legacyPlan));
227
+ if (parsed.version !== QUERY_PLAN_VERSION || parsed.ir.source.protocol !== "geoparquet") {
228
+ throw invalidPersistedPlan();
229
+ }
230
+ const resource = parseGeoParquetResourceHandle(resourceValue);
231
+ const legacyGeoParquet = parsed.ir.source.geoparquet;
232
+ if (!legacyGeoParquet)
233
+ throw invalidPersistedPlan();
234
+ const descriptor = {
235
+ id: resource.resource.id,
236
+ protocol: "geoparquet",
237
+ locator: {
238
+ url: "honua-resource://opaque",
239
+ geoparquet: {
240
+ ...(legacyGeoParquet.geometryColumn ? { geometryColumn: legacyGeoParquet.geometryColumn } : {}),
241
+ ...(legacyGeoParquet.geometryEncoding ? { geometryEncoding: legacyGeoParquet.geometryEncoding } : {}),
242
+ ...(legacyGeoParquet.bboxColumn ? { bboxColumn: legacyGeoParquet.bboxColumn } : {}),
243
+ ...(legacyGeoParquet.nativeDimensions ? { nativeDimensions: legacyGeoParquet.nativeDimensions } : {}),
244
+ },
245
+ },
246
+ capabilities: capabilities(parsed.ir.source.capabilities),
247
+ };
248
+ return explainQuery({
249
+ descriptor,
250
+ query: queryFromCanonical(parsed.ir.query),
251
+ geoparquetResource: resource,
252
+ capabilityPolicy: parsed.capabilityPolicy,
253
+ fallback: parsed.fallback,
254
+ ...(parsed.ir.source.schemaVersion !== undefined ? { schemaVersion: parsed.ir.source.schemaVersion } : {}),
255
+ ...(parsed.ir.source.sourceVersion !== undefined ? { sourceVersion: parsed.ir.source.sourceVersion } : {}),
256
+ authorizationScope: parsed.ir.source.authorizationScope,
257
+ estimates: parsed.estimates,
258
+ });
259
+ }
260
+ function ownGeoParquetResource(options) {
261
+ let descriptor;
262
+ try {
263
+ descriptor = Object.getOwnPropertyDescriptor(options, "geoparquetResource");
264
+ }
265
+ catch {
266
+ throw new HonuaQueryPlanningError("invalid-query", "GeoParquet resource handle is invalid");
267
+ }
268
+ if (!descriptor)
269
+ return undefined;
270
+ if (!("value" in descriptor)) {
271
+ throw new HonuaQueryPlanningError("invalid-query", "GeoParquet resource handle is invalid");
272
+ }
273
+ try {
274
+ return parseGeoParquetResourceHandle(descriptor.value);
275
+ }
276
+ catch {
277
+ throw new HonuaQueryPlanningError("invalid-query", "GeoParquet resource handle is invalid");
278
+ }
279
+ }
280
+ function assertCredentialFreeLegacyDescriptor(descriptor) {
281
+ const sources = [descriptor.locator.url, ...(descriptor.locator.geoparquet?.urls ?? [])];
282
+ if (sources.length === 0 || sources.some((source) => !isCredentialFreeLegacySource(source))) {
283
+ throw new HonuaQueryPlanningError("invalid-query", "Credential-bearing GeoParquet locators require an opaque resource handle");
284
+ }
285
+ }
286
+ function assertPlanPersistenceSafe(plan) {
287
+ assertStructuredDiagnostics(plan);
288
+ if (plan.version === QUERY_PLAN_VERSION) {
289
+ if (plan.ir.version !== "1.0")
290
+ throw invalidPersistedPlan();
291
+ if (plan.ir.source.protocol === "geoparquet") {
292
+ const sources = plan.ir.source.geoparquet?.sources;
293
+ if (!sources || sources.length === 0 || sources.some((source) => !isCredentialFreeLegacySource(source))) {
294
+ throw invalidPersistedPlan();
295
+ }
296
+ }
297
+ for (const step of plan.steps) {
298
+ if (step.engine !== "remote")
299
+ continue;
300
+ const expected = compileRemoteQuery(plan.ir.source, step.query, step.operation);
301
+ if (!sameCanonicalValue(step.compiled, expected)) {
302
+ throw invalidPersistedPlan();
303
+ }
304
+ }
305
+ const expected = rebuildQueryPlanV1(plan);
306
+ if (canonicalStringify(toJsonValue(plan)) !== canonicalStringify(toJsonValue(expected))) {
307
+ throw invalidPersistedPlan();
308
+ }
309
+ return;
310
+ }
311
+ if (plan.version !== QUERY_PLAN_V2_VERSION ||
312
+ plan.ir.version !== "2.0" ||
313
+ plan.ir.source.protocol !== "geoparquet" ||
314
+ plan.ir.source.endpoint !== "[opaque-resource]") {
315
+ throw invalidPersistedPlan();
316
+ }
317
+ const expected = rebuildGeoParquetQueryPlanV2(plan);
318
+ if (canonicalStringify(toJsonValue(plan)) !== canonicalStringify(toJsonValue(expected)))
319
+ throw invalidPersistedPlan();
320
+ }
321
+ function assertStructuredDiagnostics(plan) {
322
+ if (plan.capabilityPolicy !== "strict" && plan.capabilityPolicy !== "degraded")
323
+ throw invalidPersistedPlan();
324
+ const fallback = normalizeFallback(plan.fallback);
325
+ const estimates = normalizeEstimates(plan.estimates);
326
+ const executionMode = normalizeExecutionMode(plan.validity.executionMode);
327
+ if (!sameCanonicalValue(plan.fallback, fallback) || !sameCanonicalValue(plan.estimates, estimates)) {
328
+ throw invalidPersistedPlan();
329
+ }
330
+ const expectedProvenance = rebuildPersistedProvenance(plan);
331
+ if (!sameCanonicalValue(plan.provenance, expectedProvenance))
332
+ throw invalidPersistedPlan();
333
+ const expectedSteps = plan.steps.map((step) => ({
334
+ pushdown: persistedStepPushdown(step),
335
+ ...createStepDiagnostics(step, estimates, expectedProvenance),
336
+ }));
337
+ for (let index = 0; index < plan.steps.length; index += 1) {
338
+ const step = plan.steps[index];
339
+ const expected = expectedSteps[index];
340
+ if (!step || !expected)
341
+ throw invalidPersistedPlan();
342
+ const actual = {
343
+ pushdown: step.pushdown,
344
+ fidelity: step.fidelity,
345
+ losses: step.losses,
346
+ bounds: step.bounds,
347
+ provenance: step.provenance,
348
+ };
349
+ if (!sameCanonicalValue(actual, expected))
350
+ throw invalidPersistedPlan();
351
+ if (step.engine === "remote") {
352
+ const expectedRequests = step.operation === "queryAll" ? (estimates.requests ?? 1) : 1;
353
+ if (step.requests !== expectedRequests)
354
+ throw invalidPersistedPlan();
355
+ }
356
+ }
357
+ const expectedFidelity = summarizeFidelity(expectedSteps);
358
+ const expected = {
359
+ diagnosticsVersion: QUERY_PLAN_DIAGNOSTICS_VERSION,
360
+ pushdown: summarizePersistedPushdown(expectedSteps),
361
+ fidelity: expectedFidelity.fidelity,
362
+ losses: expectedFidelity.losses,
363
+ bounds: summarizePlanBounds(expectedSteps),
364
+ cache: createCacheDecision(persistedCacheOptions(plan.cache)),
365
+ provenance: expectedProvenance,
366
+ validity: createPlanValidity(plan.ir, expectedProvenance, plan.capabilityPolicy, fallback, executionMode),
367
+ warnings: createPlanWarnings(plan.steps, plan.ir.source),
368
+ };
369
+ const actual = {
370
+ diagnosticsVersion: plan.diagnosticsVersion,
371
+ pushdown: plan.pushdown,
372
+ fidelity: plan.fidelity,
373
+ losses: plan.losses,
374
+ bounds: plan.bounds,
375
+ cache: plan.cache,
376
+ provenance: plan.provenance,
377
+ validity: plan.validity,
378
+ warnings: plan.warnings,
379
+ };
380
+ if (!sameCanonicalValue(actual, expected))
381
+ throw invalidPersistedPlan();
382
+ }
383
+ function persistedStepPushdown(step) {
384
+ if (step.engine === "client") {
385
+ if (step.operation !== "aggregate")
386
+ throw invalidPersistedPlan();
387
+ return "none";
388
+ }
389
+ if (step.engine !== "remote")
390
+ throw invalidPersistedPlan();
391
+ if (step.operation === "queryAll")
392
+ return "partial";
393
+ if (step.operation === "query" || step.operation === "queryAggregate")
394
+ return "full";
395
+ throw invalidPersistedPlan();
396
+ }
397
+ function summarizePersistedPushdown(steps) {
398
+ return steps.every((step) => step.pushdown === "full") ? "full" : "partial";
399
+ }
400
+ function rebuildPersistedProvenance(plan) {
401
+ const source = plan.ir.source;
402
+ const schema = plan.provenance.schema;
403
+ const descriptor = {
404
+ id: source.id,
405
+ protocol: source.protocol,
406
+ locator: { url: source.endpoint },
407
+ capabilities: capabilities(parsePlanCapabilities(source.capabilities)),
408
+ ...(schema.state === "known" && schema.basis === "schema-v2"
409
+ ? { schemaV2: { kind: "honua.source-schema", version: "2.0", fingerprint: schema.fingerprint } }
410
+ : {}),
411
+ };
412
+ return createQueryPlanProvenance(descriptor, source, {
413
+ ...(source.schemaVersion ? { schemaVersion: source.schemaVersion } : {}),
414
+ discovery: persistedDiscoveryContext(plan.provenance.discovery),
415
+ });
416
+ }
417
+ /** Rebuild legacy plans through the same pure planner to bind every persisted step to the IR. */
418
+ function rebuildQueryPlanV1(plan) {
419
+ const source = plan.ir.source;
420
+ const capabilitiesValue = parsePlanCapabilities(source.capabilities);
421
+ const geometryProperty = parseOptionalPlanMetadata(source.geometryProperty);
422
+ const primaryKey = persistedV1FallbackPrimaryKey(plan);
423
+ const schema = geometryProperty || primaryKey
424
+ ? {
425
+ ...(geometryProperty ? { fields: [{ name: geometryProperty, type: "esriFieldTypeGeometry" }] } : {}),
426
+ ...(primaryKey ? { primaryKey } : {}),
427
+ }
428
+ : undefined;
429
+ const geoparquet = source.geoparquet;
430
+ const url = source.protocol === "geoparquet" ? geoparquet?.sources[0] : source.endpoint;
431
+ if (typeof url !== "string" || (source.protocol === "geoparquet" && url.length === 0)) {
432
+ throw invalidPersistedPlan();
433
+ }
434
+ const geometryEncoding = parsePersistedGeoParquetGeometryEncoding(geoparquet?.geometryEncoding);
435
+ const nativeDimensions = parsePersistedNativeDimensions(geoparquet?.nativeDimensions);
436
+ const locator = {
437
+ url,
438
+ ...(source.serviceId !== undefined ? { serviceId: parseOptionalPlanMetadata(source.serviceId) } : {}),
439
+ ...(source.layerId !== undefined ? { layerId: parseOptionalLocatorNumber(source.layerId) } : {}),
440
+ ...(source.collectionId !== undefined ? { collectionId: parseOptionalLocatorId(source.collectionId) } : {}),
441
+ ...(source.typeName !== undefined ? { typeName: parseOptionalPlanMetadata(source.typeName) } : {}),
442
+ ...(source.entitySet !== undefined ? { entitySet: parseOptionalPlanMetadata(source.entitySet) } : {}),
443
+ ...(source.srsName !== undefined ? { srsName: parseOptionalPlanMetadata(source.srsName) } : {}),
444
+ ...(geoparquet
445
+ ? {
446
+ geoparquet: {
447
+ ...(geoparquet.sources.length > 1 ? { urls: geoparquet.sources.slice(1) } : {}),
448
+ ...(geoparquet.geometryColumn
449
+ ? { geometryColumn: parseOptionalPlanMetadata(geoparquet.geometryColumn) }
450
+ : {}),
451
+ ...(geometryEncoding ? { geometryEncoding } : {}),
452
+ ...(geoparquet.bboxColumn ? { bboxColumn: parseOptionalPlanMetadata(geoparquet.bboxColumn) } : {}),
453
+ ...(nativeDimensions ? { nativeDimensions } : {}),
454
+ },
455
+ }
456
+ : {}),
457
+ };
458
+ const descriptor = {
459
+ id: requirePlanMetadata(source.id),
460
+ protocol: source.protocol,
461
+ locator,
462
+ capabilities: capabilities(capabilitiesValue),
463
+ ...(schema ? { schema } : {}),
464
+ ...(plan.provenance.schema.state === "known" && plan.provenance.schema.basis === "schema-v2"
465
+ ? {
466
+ schemaV2: {
467
+ kind: "honua.source-schema",
468
+ version: "2.0",
469
+ fingerprint: plan.provenance.schema.fingerprint,
470
+ },
471
+ }
472
+ : {}),
473
+ };
474
+ return explainQuery({
475
+ descriptor,
476
+ query: queryFromCanonical(plan.ir.query),
477
+ capabilityPolicy: plan.capabilityPolicy,
478
+ fallback: plan.fallback,
479
+ ...(source.schemaVersion !== undefined ? { schemaVersion: source.schemaVersion } : {}),
480
+ ...(source.sourceVersion !== undefined ? { sourceVersion: source.sourceVersion } : {}),
481
+ authorizationScope: source.authorizationScope,
482
+ estimates: plan.estimates,
483
+ cache: persistedCacheOptions(plan.cache),
484
+ discovery: persistedDiscoveryContext(plan.provenance.discovery),
485
+ executionMode: plan.validity.executionMode,
486
+ });
487
+ }
488
+ function persistedV1FallbackPrimaryKey(plan) {
489
+ const aggregation = plan.ir.query.aggregation;
490
+ if (!aggregation)
491
+ return undefined;
492
+ const requiresProjectedField = (aggregation.groupBy?.length ?? 0) > 0 || aggregation.metrics.some((metric) => metric.field !== "*");
493
+ if (requiresProjectedField)
494
+ return undefined;
495
+ const remote = plan.steps[0];
496
+ if (remote?.engine !== "remote" || remote.operation !== "queryAll")
497
+ return undefined;
498
+ const outFields = remote.query.outFields;
499
+ if (outFields === undefined)
500
+ return undefined;
501
+ if (outFields.length !== 1)
502
+ throw invalidPersistedPlan();
503
+ return parseOptionalPlanMetadata(outFields[0]);
504
+ }
505
+ function parseOptionalLocatorNumber(value) {
506
+ if (!Number.isSafeInteger(value) || value < 0)
507
+ throw invalidPersistedPlan();
508
+ return value;
509
+ }
510
+ function parseOptionalLocatorId(value) {
511
+ if (typeof value === "number")
512
+ return parseOptionalLocatorNumber(value);
513
+ return requirePlanMetadata(value);
514
+ }
515
+ function requirePlanMetadata(value) {
516
+ const parsed = parseOptionalPlanMetadata(value);
517
+ if (parsed === undefined)
518
+ throw invalidPersistedPlan();
519
+ return parsed;
520
+ }
521
+ function sameCanonicalValue(left, right) {
522
+ return canonicalStringify(toJsonValue(left)) === canonicalStringify(toJsonValue(right));
523
+ }
524
+ /**
525
+ * Rebuild a v2 plan through the pure planner so persistence accepts only one
526
+ * complete canonical projection. This performs no resolution or network I/O.
527
+ */
528
+ function rebuildGeoParquetQueryPlanV2(plan) {
529
+ const source = plan.ir.source;
530
+ const resource = parseGeoParquetResourceHandle(source.geoparquet.resource);
531
+ const sourceCapabilities = parsePlanCapabilities(source.capabilities);
532
+ const primaryKey = parseOptionalPlanMetadata(source.primaryKey);
533
+ const schemaVersion = parseOptionalPlanMetadata(source.schemaVersion);
534
+ const sourceVersion = parseOptionalPlanMetadata(source.sourceVersion);
535
+ const geometryColumn = parseOptionalPlanMetadata(source.geoparquet.geometryColumn);
536
+ const bboxColumn = parseOptionalPlanMetadata(source.geoparquet.bboxColumn);
537
+ const geometryEncoding = parsePersistedGeoParquetGeometryEncoding(source.geoparquet.geometryEncoding);
538
+ const nativeDimensions = parsePersistedNativeDimensions(source.geoparquet.nativeDimensions);
539
+ if (plan.capabilityPolicy !== "strict" && plan.capabilityPolicy !== "degraded")
540
+ throw invalidPersistedPlan();
541
+ const descriptor = {
542
+ id: resource.resource.id,
543
+ protocol: "geoparquet",
544
+ locator: {
545
+ url: "honua-resource://opaque",
546
+ geoparquet: {
547
+ ...(geometryColumn ? { geometryColumn } : {}),
548
+ ...(geometryEncoding ? { geometryEncoding } : {}),
549
+ ...(bboxColumn ? { bboxColumn } : {}),
550
+ ...(nativeDimensions ? { nativeDimensions } : {}),
551
+ },
552
+ },
553
+ capabilities: capabilities(sourceCapabilities),
554
+ ...(primaryKey ? { schema: { primaryKey } } : {}),
555
+ ...(plan.provenance.schema.state === "known" && plan.provenance.schema.basis === "schema-v2"
556
+ ? {
557
+ schemaV2: {
558
+ kind: "honua.source-schema",
559
+ version: "2.0",
560
+ fingerprint: plan.provenance.schema.fingerprint,
561
+ },
562
+ }
563
+ : {}),
564
+ };
565
+ return explainQuery({
566
+ descriptor,
567
+ geoparquetResource: resource,
568
+ query: queryFromCanonical(plan.ir.query),
569
+ capabilityPolicy: plan.capabilityPolicy,
570
+ fallback: plan.fallback,
571
+ ...(schemaVersion ? { schemaVersion } : {}),
572
+ ...(sourceVersion ? { sourceVersion } : {}),
573
+ ...(source.geoparquet.effectiveSchema ? { effectiveSchema: source.geoparquet.effectiveSchema } : {}),
574
+ authorizationScope: source.authorizationScope,
575
+ estimates: plan.estimates,
576
+ cache: persistedCacheOptions(plan.cache),
577
+ discovery: persistedDiscoveryContext(plan.provenance.discovery),
578
+ executionMode: plan.validity.executionMode,
579
+ });
580
+ }
581
+ function parsePersistedGeoParquetGeometryEncoding(value) {
582
+ if (value === undefined)
583
+ return undefined;
584
+ switch (value) {
585
+ case "wkb":
586
+ case "native":
587
+ case "geojson":
588
+ case "geoarrow-point":
589
+ case "geoarrow-linestring":
590
+ case "geoarrow-polygon":
591
+ case "geoarrow-multipoint":
592
+ case "geoarrow-multilinestring":
593
+ case "geoarrow-multipolygon":
594
+ return value;
595
+ default:
596
+ throw invalidPersistedPlan();
597
+ }
598
+ }
599
+ function parsePersistedNativeDimensions(value) {
600
+ if (value === undefined)
601
+ return undefined;
602
+ if (value !== "xy" && value !== "xyz")
603
+ throw invalidPersistedPlan();
604
+ return value;
605
+ }
606
+ function persistedCacheOptions(cache) {
607
+ return {
608
+ policy: cache.policy,
609
+ freshness: cache.freshness,
610
+ ...(cache.validator ? { validator: { kind: cache.validator.kind, fingerprint: cache.validator.fingerprint } } : {}),
611
+ };
612
+ }
613
+ function persistedDiscoveryContext(discovery) {
614
+ return {
615
+ state: discovery.state,
616
+ ...(discovery.sourceFingerprint ? { sourceFingerprint: discovery.sourceFingerprint } : {}),
617
+ ...(discovery.validator
618
+ ? { validator: { kind: discovery.validator.kind, fingerprint: discovery.validator.fingerprint } }
619
+ : {}),
620
+ };
621
+ }
622
+ function parsePlanCapabilities(value) {
623
+ if (!Array.isArray(value) ||
624
+ value.some((capability) => typeof capability !== "string" || !CAPABILITIES.includes(capability))) {
625
+ throw invalidPersistedPlan();
626
+ }
627
+ return value;
628
+ }
629
+ function parseOptionalPlanMetadata(value) {
630
+ if (value === undefined)
631
+ return undefined;
632
+ if (typeof value !== "string" || value.length === 0 || value.length > 1_024 || containsControlCharacter(value)) {
633
+ throw invalidPersistedPlan();
634
+ }
635
+ return value;
636
+ }
637
+ const CREDENTIAL_BEARING_PLAN_TEXT = /(?:\bBearer\s+[A-Za-z0-9._~+/=-]{8,}|\bBasic\s+[A-Za-z0-9+/=]{8,}|\bAKIA[0-9A-Z]{16}\b|[?&;](?:access[-_]?token|id[-_]?token|refresh[-_]?token|x-amz-signature|x-goog-credential|signature|sig|token|api[-_]?key|password|secret)=[^\s&#;]*|\b(?:authorization|proxy[-_]?authorization|x-api-key|password|secret|token|api[-_]?key|account[-_]?key|shared[-_]?access[-_]?signature)\s*(?:=|:)\s*[^\s,;]+|[a-z][a-z0-9+.-]*:\/\/[^/\s"'<>]*@|\beyJ[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}\b)/i;
638
+ const CREDENTIAL_BEARING_PLAN_KEYS = new Set([
639
+ "authorization",
640
+ "proxyauthorization",
641
+ "cookie",
642
+ "setcookie",
643
+ "username",
644
+ "password",
645
+ "passwd",
646
+ "secret",
647
+ "token",
648
+ "accesstoken",
649
+ "refreshtoken",
650
+ "idtoken",
651
+ "apikey",
652
+ "xapikey",
653
+ "accesskey",
654
+ "secretkey",
655
+ "clientsecret",
656
+ "credential",
657
+ "credentials",
658
+ "signature",
659
+ "xamzsignature",
660
+ "xgoogcredential",
661
+ "accountkey",
662
+ "sharedaccesssignature",
663
+ ]);
664
+ /** Detach once so hostile accessors/proxies cannot change between validation and hashing. */
665
+ function persistedPlanSnapshot(value) {
666
+ try {
667
+ const plan = deepFreeze(toJsonValue(value));
668
+ if (!isPlanEnvelope(plan))
669
+ throw invalidPersistedPlan();
670
+ assertPlanPersistenceSafe(plan);
671
+ if (containsCredentialBearingPlanMaterial(plan))
672
+ throw invalidPersistedPlan();
673
+ const { id: _id, fingerprint: _fingerprint, ...unsigned } = plan;
674
+ return { plan, hash: sha256(canonicalStringify(toJsonValue(unsigned))) };
675
+ }
676
+ catch {
677
+ throw invalidPersistedPlan();
678
+ }
679
+ }
680
+ function containsCredentialBearingPlanMaterial(value, path = []) {
681
+ if (typeof value === "string")
682
+ return CREDENTIAL_BEARING_PLAN_TEXT.test(value);
683
+ if (Array.isArray(value)) {
684
+ return value.some((child, index) => containsCredentialBearingPlanMaterial(child, [...path, String(index)]));
685
+ }
686
+ if (value === null || typeof value !== "object")
687
+ return false;
688
+ return Object.entries(value).some(([key, child]) => {
689
+ const normalizedKey = key.replace(/[^A-Za-z0-9]/g, "").toLowerCase();
690
+ const parent = path
691
+ .at(-1)
692
+ ?.replace(/[^A-Za-z0-9]/g, "")
693
+ .toLowerCase();
694
+ const sensitiveHeader = parent === "headers" &&
695
+ (normalizedKey === "xauth" ||
696
+ normalizedKey === "xauthtoken" ||
697
+ normalizedKey === "xaccesskey" ||
698
+ normalizedKey === "xsessiontoken");
699
+ const sensitiveLocator = parent === "locator" && (normalizedKey === "user" || normalizedKey === "userinfo");
700
+ const sensitiveQueryParameter = parent === "query" && (normalizedKey === "auth" || normalizedKey === "key" || normalizedKey === "session");
701
+ return (CREDENTIAL_BEARING_PLAN_KEYS.has(normalizedKey) ||
702
+ sensitiveHeader ||
703
+ sensitiveLocator ||
704
+ sensitiveQueryParameter ||
705
+ containsCredentialBearingPlanMaterial(child, [...path, key]));
706
+ });
707
+ }
708
+ function isCredentialFreeLegacySource(value) {
709
+ if (typeof value !== "string" || value.length === 0 || value.trim() !== value || containsControlCharacter(value)) {
710
+ return false;
711
+ }
712
+ try {
713
+ const url = new URL(value);
714
+ return !url.username && !url.password && !url.search && !url.hash;
715
+ }
716
+ catch {
717
+ return !value.includes("?") && !value.includes("#") && !value.includes("@");
718
+ }
719
+ }
720
+ function containsControlCharacter(value) {
721
+ for (let index = 0; index < value.length; index += 1) {
722
+ const code = value.charCodeAt(index);
723
+ if (code <= 0x1f || code === 0x7f)
724
+ return true;
725
+ }
726
+ return false;
727
+ }
728
+ function isPlanEnvelope(value) {
729
+ if (value === null || typeof value !== "object" || Array.isArray(value))
730
+ return false;
731
+ const plan = value;
732
+ return (plan.kind === QUERY_PLAN_KIND &&
733
+ (plan.version === QUERY_PLAN_VERSION || plan.version === QUERY_PLAN_V2_VERSION) &&
734
+ plan.diagnosticsVersion === QUERY_PLAN_DIAGNOSTICS_VERSION &&
735
+ typeof plan.id === "string" &&
736
+ typeof plan.fingerprint === "string" &&
737
+ /^sha256:[0-9a-f]{64}$/.test(plan.fingerprint) &&
738
+ plan.ir !== null &&
739
+ typeof plan.ir === "object" &&
740
+ Array.isArray(plan.steps) &&
741
+ Array.isArray(plan.warnings));
742
+ }
743
+ function normalizeExecutionMode(value) {
744
+ if (value === undefined)
745
+ return "snapshot";
746
+ if (value !== "snapshot" && value !== "delta") {
747
+ throw new HonuaQueryPlanningError("invalid-query", "executionMode is invalid");
748
+ }
749
+ return value;
750
+ }
751
+ function invalidPersistedPlan() {
752
+ return new HonuaQueryPlanExecutionError("invalid-plan", "Query plan is invalid or unsafe to persist");
753
+ }
754
+ function normalizeFallback(fallback) {
755
+ if (!fallback || fallback.mode === "disabled")
756
+ return deepFreeze({ mode: "disabled" });
757
+ if (!Number.isSafeInteger(fallback.maxRows) || fallback.maxRows < 1) {
758
+ throw new HonuaQueryPlanningError("unsafe-materialization", "bounded-local maxRows must be a positive safe integer");
759
+ }
760
+ if (fallback.maxRows > MAX_LOCAL_MATERIALIZATION_ROWS) {
761
+ throw new HonuaQueryPlanningError("unsafe-materialization", `bounded-local maxRows ${fallback.maxRows} exceeds the SDK safety ceiling ${MAX_LOCAL_MATERIALIZATION_ROWS}`);
762
+ }
763
+ if (fallback.maxBytes !== undefined && (!Number.isSafeInteger(fallback.maxBytes) || fallback.maxBytes < 1)) {
764
+ throw new HonuaQueryPlanningError("unsafe-materialization", "bounded-local maxBytes must be a positive safe integer");
765
+ }
766
+ return deepFreeze({
767
+ mode: "bounded-local",
768
+ maxRows: fallback.maxRows,
769
+ ...(fallback.maxBytes !== undefined ? { maxBytes: fallback.maxBytes } : {}),
770
+ });
771
+ }
772
+ function normalizeEstimates(estimates) {
773
+ const out = {};
774
+ for (const key of ["rows", "bytes", "requests"]) {
775
+ const value = estimates?.[key];
776
+ if (value === undefined)
777
+ continue;
778
+ if (key === "requests" && (!Number.isSafeInteger(value) || value < 1)) {
779
+ throw new HonuaQueryPlanningError("invalid-query", "estimates.requests must be a safe integer >= 1");
780
+ }
781
+ if (!Number.isFinite(value) || value < 0) {
782
+ throw new HonuaQueryPlanningError("invalid-query", `estimates.${key} must be a finite non-negative number`);
783
+ }
784
+ out[key] = value;
785
+ }
786
+ return deepFreeze(out);
787
+ }
788
+ function rejectUnsafeEstimate(fallback, rows, bytes) {
789
+ if (rows !== undefined && rows > fallback.maxRows) {
790
+ throw new HonuaQueryPlanningError("unsafe-materialization", `Estimated input ${rows} rows exceeds bounded-local maxRows ${fallback.maxRows}`);
791
+ }
792
+ if (fallback.maxBytes !== undefined && bytes !== undefined && bytes > fallback.maxBytes) {
793
+ throw new HonuaQueryPlanningError("unsafe-materialization", `Estimated input ${bytes} bytes exceeds bounded-local maxBytes ${fallback.maxBytes}`);
794
+ }
795
+ }
796
+ function localAggregateInputQuery(query, aggregation, maxRows, primaryKey, options = {}) {
797
+ if (aggregation.histogram || aggregation.timeSeries) {
798
+ throw new HonuaQueryPlanningError("unsupported-query", "The first bounded-local compiler supports metrics and groupBy; histogram/timeSeries remain follow-on work");
799
+ }
800
+ const requiredFields = new Set(aggregation.groupBy ?? []);
801
+ for (const metric of aggregation.metrics) {
802
+ if (metric.field !== "*")
803
+ requiredFields.add(metric.field);
804
+ }
805
+ if (requiredFields.size === 0 && primaryKey)
806
+ requiredFields.add(primaryKey);
807
+ const { aggregation: _aggregation, orderBy: _orderBy, pagination: _pagination, outFields: _outFields, ...base } = query;
808
+ return deepFreeze({
809
+ ...base,
810
+ ...(requiredFields.size > 0 ? { outFields: [...requiredFields].sort() } : {}),
811
+ ...(!options.preserveGeometry ? { returnGeometry: false } : {}),
812
+ // The OGC Source.queryAll adapter owns the overflow sentinel: a logical
813
+ // N-row ceiling becomes an N + 1 wire request and is reported through
814
+ // exceededTransferLimit. GeoServices retains its established explicit
815
+ // sentinel in the canonical step for backward-compatible plan output.
816
+ pagination: { offset: 0, limit: maxRows + (options.adapterOwnsLookahead ? 0 : 1) },
817
+ });
818
+ }