@honua/react 0.1.1-beta.0 → 0.1.4-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 (281) hide show
  1. package/contract/discovery.d.ts +34 -0
  2. package/contract/discovery.d.ts.map +1 -1
  3. package/contract/index.d.ts +7 -5
  4. package/contract/index.d.ts.map +1 -1
  5. package/contract/index.js +3 -2
  6. package/contract/pmtiles.d.ts +10 -1
  7. package/contract/pmtiles.d.ts.map +1 -1
  8. package/contract/pmtiles.js +10 -2
  9. package/contract/protocol-module.d.ts +84 -9
  10. package/contract/protocol-module.d.ts.map +1 -1
  11. package/contract/query-filter-fes.d.ts +18 -0
  12. package/contract/query-filter-fes.d.ts.map +1 -0
  13. package/contract/query-filter-fes.js +122 -0
  14. package/contract/query-filter.d.ts +300 -0
  15. package/contract/query-filter.d.ts.map +1 -0
  16. package/contract/query-filter.js +883 -0
  17. package/contract/schema.d.ts +12 -0
  18. package/contract/schema.d.ts.map +1 -1
  19. package/contract/schema.js +34 -0
  20. package/contract/source-capability-support.d.ts.map +1 -1
  21. package/contract/source-capability-support.js +15 -3
  22. package/contract/source.d.ts +45 -25
  23. package/contract/source.d.ts.map +1 -1
  24. package/contract/source.js +719 -479
  25. package/contract/types.d.ts +87 -18
  26. package/contract/types.d.ts.map +1 -1
  27. package/contract/widget-source.d.ts.map +1 -1
  28. package/core/client.d.ts +45 -21
  29. package/core/client.d.ts.map +1 -1
  30. package/core/client.js +224 -46
  31. package/core/error-classifications.d.ts +6 -0
  32. package/core/error-classifications.d.ts.map +1 -1
  33. package/core/error-classifications.js +6 -0
  34. package/core/error-code-registry.d.ts +6 -0
  35. package/core/error-code-registry.d.ts.map +1 -1
  36. package/core/error-code-registry.js +6 -0
  37. package/core/error-envelope.js +2 -0
  38. package/core/errors.d.ts +1 -1
  39. package/core/errors.js +1 -1
  40. package/core/geoparquet-sql.d.ts +68 -4
  41. package/core/geoparquet-sql.d.ts.map +1 -1
  42. package/core/geoparquet-sql.js +325 -8
  43. package/core/odata-write-codec.d.ts.map +1 -1
  44. package/core/odata-write-codec.js +0 -1
  45. package/core/odata.d.ts +6 -0
  46. package/core/odata.d.ts.map +1 -1
  47. package/core/odata.js +6 -1
  48. package/core/ogc-processes.d.ts +182 -25
  49. package/core/ogc-processes.d.ts.map +1 -1
  50. package/core/ogc-processes.js +602 -30
  51. package/core/process-runner.d.ts +8 -1
  52. package/core/process-runner.d.ts.map +1 -1
  53. package/core/process-runner.js +1 -0
  54. package/core/protocol-transport.d.ts +2 -0
  55. package/core/protocol-transport.d.ts.map +1 -1
  56. package/core/stac.d.ts +31 -0
  57. package/core/stac.d.ts.map +1 -1
  58. package/core/stac.js +136 -19
  59. package/core/surfaces.d.ts.map +1 -1
  60. package/core/surfaces.js +273 -68
  61. package/core/types.d.ts +123 -9
  62. package/core/types.d.ts.map +1 -1
  63. package/core/wfs-axis-order.d.ts +18 -0
  64. package/core/wfs-axis-order.d.ts.map +1 -0
  65. package/core/wfs-axis-order.js +108 -0
  66. package/core/wfs-capabilities.d.ts +65 -24
  67. package/core/wfs-capabilities.d.ts.map +1 -1
  68. package/core/wfs-capabilities.js +237 -27
  69. package/core/wfs-filter.d.ts +26 -3
  70. package/core/wfs-filter.d.ts.map +1 -1
  71. package/core/wfs-filter.js +48 -53
  72. package/core/wfs-protocol-error.d.ts +12 -0
  73. package/core/wfs-protocol-error.d.ts.map +1 -0
  74. package/core/wfs-protocol-error.js +13 -0
  75. package/core/wfs.d.ts +53 -1
  76. package/core/wfs.d.ts.map +1 -1
  77. package/core/wfs.js +251 -29
  78. package/core/wms-feature-info.d.ts +92 -0
  79. package/core/wms-feature-info.d.ts.map +1 -0
  80. package/core/wms-feature-info.js +285 -0
  81. package/core/wms.d.ts +9 -0
  82. package/core/wms.d.ts.map +1 -1
  83. package/core/wms.js +17 -5
  84. package/esri-compat/event-bus.d.ts +20 -0
  85. package/esri-compat/event-bus.d.ts.map +1 -1
  86. package/esri-compat/feature-filter.d.ts +46 -1
  87. package/esri-compat/feature-filter.d.ts.map +1 -1
  88. package/esri-compat/feature-filter.js +49 -0
  89. package/esri-compat/geojson-layer.d.ts +4 -2
  90. package/esri-compat/geojson-layer.d.ts.map +1 -1
  91. package/esri-compat/geojson-layer.js +4 -2
  92. package/esri-compat/identity-manager.js +2 -2
  93. package/esri-compat/imagery-layer.d.ts +2 -1
  94. package/esri-compat/imagery-layer.d.ts.map +1 -1
  95. package/esri-compat/imagery-layer.js +2 -1
  96. package/esri-compat/layer-list.d.ts +8 -1
  97. package/esri-compat/layer-list.d.ts.map +1 -1
  98. package/esri-compat/layer-list.js +11 -4
  99. package/esri-compat/legend.d.ts.map +1 -1
  100. package/esri-compat/legend.js +1 -1
  101. package/esri-compat/locator.d.ts +297 -0
  102. package/esri-compat/locator.d.ts.map +1 -0
  103. package/esri-compat/locator.js +579 -0
  104. package/esri-compat/map-image-layer.d.ts.map +1 -1
  105. package/esri-compat/map-view.d.ts.map +1 -1
  106. package/esri-compat/map-view.js +1 -1
  107. package/esri-compat/scene-view.d.ts.map +1 -1
  108. package/esri-compat/time-slider.d.ts +29 -0
  109. package/esri-compat/time-slider.d.ts.map +1 -1
  110. package/esri-compat/time-slider.js +117 -0
  111. package/esri-compat/wfs-layer.d.ts +2 -1
  112. package/esri-compat/wfs-layer.d.ts.map +1 -1
  113. package/esri-compat/wfs-layer.js +2 -1
  114. package/esri-compat/widget-host.d.ts +42 -6
  115. package/esri-compat/widget-host.d.ts.map +1 -1
  116. package/esri-compat/widget-host.js +99 -10
  117. package/generated-app/projection.d.ts.map +1 -1
  118. package/generated-app/runtime.d.ts.map +1 -1
  119. package/map/data-to-map-bridge.d.ts +1 -1
  120. package/map/data-to-map-bridge.d.ts.map +1 -1
  121. package/map/data-to-map-bridge.js +1 -2
  122. package/map/honua-map.d.ts.map +1 -1
  123. package/map/honua-map.js +1 -1
  124. package/map/index.d.ts +2 -0
  125. package/map/index.d.ts.map +1 -1
  126. package/map/index.js +2 -0
  127. package/map/realtime-reconciliation-adapter.d.ts +98 -0
  128. package/map/realtime-reconciliation-adapter.d.ts.map +1 -0
  129. package/map/realtime-reconciliation-adapter.js +84 -0
  130. package/map/temporal-playback.d.ts +25 -0
  131. package/map/temporal-playback.d.ts.map +1 -1
  132. package/map/temporal-playback.js +29 -1
  133. package/package.json +4 -3
  134. package/query-planner/canonical-filter.d.ts +30 -0
  135. package/query-planner/canonical-filter.d.ts.map +1 -0
  136. package/query-planner/canonical-filter.js +30 -0
  137. package/query-planner/columnar-plan.d.ts +63 -0
  138. package/query-planner/columnar-plan.d.ts.map +1 -0
  139. package/query-planner/columnar-plan.js +94 -0
  140. package/query-planner/diagnostics.d.ts +3 -3
  141. package/query-planner/diagnostics.d.ts.map +1 -1
  142. package/query-planner/diagnostics.js +17 -4
  143. package/query-planner/duckdb.d.ts.map +1 -1
  144. package/query-planner/duckdb.js +16 -2
  145. package/query-planner/execution-context.d.ts.map +1 -1
  146. package/query-planner/execution-context.js +3 -1
  147. package/query-planner/executor.d.ts.map +1 -1
  148. package/query-planner/executor.js +7 -1
  149. package/query-planner/geoservices.d.ts.map +1 -1
  150. package/query-planner/geoservices.js +24 -5
  151. package/query-planner/grpc.d.ts.map +1 -1
  152. package/query-planner/grpc.js +29 -5
  153. package/query-planner/index.d.ts +20 -5
  154. package/query-planner/index.d.ts.map +1 -1
  155. package/query-planner/index.js +16 -3
  156. package/query-planner/ir.d.ts +6 -1
  157. package/query-planner/ir.d.ts.map +1 -1
  158. package/query-planner/ir.js +63 -1
  159. package/query-planner/odata-protocol-module.d.ts +54 -0
  160. package/query-planner/odata-protocol-module.d.ts.map +1 -0
  161. package/query-planner/odata-protocol-module.js +199 -0
  162. package/query-planner/odata-v1.d.ts +23 -0
  163. package/query-planner/odata-v1.d.ts.map +1 -0
  164. package/query-planner/odata-v1.js +202 -0
  165. package/query-planner/odata.d.ts +0 -3
  166. package/query-planner/odata.d.ts.map +1 -1
  167. package/query-planner/odata.js +0 -143
  168. package/query-planner/ogc-features.d.ts.map +1 -1
  169. package/query-planner/ogc-features.js +23 -1
  170. package/query-planner/planner.d.ts +2 -2
  171. package/query-planner/planner.d.ts.map +1 -1
  172. package/query-planner/planner.js +146 -27
  173. package/query-planner/representation.d.ts +59 -0
  174. package/query-planner/representation.d.ts.map +1 -0
  175. package/query-planner/representation.js +140 -0
  176. package/query-planner/types.d.ts +153 -3
  177. package/query-planner/types.d.ts.map +1 -1
  178. package/query-planner/wfs-protocol-module.d.ts +57 -0
  179. package/query-planner/wfs-protocol-module.d.ts.map +1 -0
  180. package/query-planner/wfs-protocol-module.js +731 -0
  181. package/query-planner/wfs-v1.d.ts +41 -0
  182. package/query-planner/wfs-v1.d.ts.map +1 -0
  183. package/query-planner/wfs-v1.js +366 -0
  184. package/query-planner/wfs.d.ts +1 -3
  185. package/query-planner/wfs.d.ts.map +1 -1
  186. package/query-planner/wfs.js +1 -70
  187. package/react/honua-map.d.ts +3 -1
  188. package/react/honua-map.d.ts.map +1 -1
  189. package/react/honua-map.js +11 -1
  190. package/react/hooks.d.ts +1 -1
  191. package/react/hooks.js +1 -1
  192. package/react/selection.d.ts.map +1 -1
  193. package/react/selection.js +0 -0
  194. package/react/use-mounted-source.d.ts +1 -1
  195. package/react/use-mounted-source.js +1 -1
  196. package/realtime/checkpoint-store.d.ts +206 -0
  197. package/realtime/checkpoint-store.d.ts.map +1 -0
  198. package/realtime/checkpoint-store.js +504 -0
  199. package/realtime/contract.d.ts +142 -0
  200. package/realtime/contract.d.ts.map +1 -0
  201. package/realtime/contract.js +154 -0
  202. package/realtime/exploration.d.ts +16 -0
  203. package/realtime/exploration.d.ts.map +1 -0
  204. package/realtime/exploration.js +32 -0
  205. package/realtime/honua-server.d.ts +131 -0
  206. package/realtime/honua-server.d.ts.map +1 -0
  207. package/realtime/honua-server.js +302 -0
  208. package/realtime/index.d.ts +39 -0
  209. package/realtime/index.d.ts.map +1 -0
  210. package/realtime/index.js +26 -0
  211. package/realtime/odata-delta.d.ts +114 -0
  212. package/realtime/odata-delta.d.ts.map +1 -0
  213. package/realtime/odata-delta.js +483 -0
  214. package/realtime/projections.d.ts +30 -0
  215. package/realtime/projections.d.ts.map +1 -0
  216. package/realtime/projections.js +51 -0
  217. package/realtime/reconciliation.d.ts +245 -0
  218. package/realtime/reconciliation.d.ts.map +1 -0
  219. package/realtime/reconciliation.js +377 -0
  220. package/realtime/reducer.d.ts +14 -0
  221. package/realtime/reducer.d.ts.map +1 -0
  222. package/realtime/reducer.js +351 -0
  223. package/realtime/resumable-transport.d.ts +111 -0
  224. package/realtime/resumable-transport.d.ts.map +1 -0
  225. package/realtime/resumable-transport.js +406 -0
  226. package/realtime/resumable.d.ts +104 -0
  227. package/realtime/resumable.d.ts.map +1 -0
  228. package/realtime/resumable.js +632 -0
  229. package/realtime/sse.d.ts +23 -0
  230. package/realtime/sse.d.ts.map +1 -0
  231. package/realtime/sse.js +147 -0
  232. package/realtime/store.d.ts +18 -0
  233. package/realtime/store.d.ts.map +1 -0
  234. package/realtime/store.js +63 -0
  235. package/realtime/types.d.ts +190 -0
  236. package/realtime/types.d.ts.map +1 -0
  237. package/realtime/types.js +10 -0
  238. package/realtime/websocket.d.ts +60 -0
  239. package/realtime/websocket.d.ts.map +1 -0
  240. package/realtime/websocket.js +157 -0
  241. package/realtime/wire.d.ts +28 -0
  242. package/realtime/wire.d.ts.map +1 -0
  243. package/realtime/wire.js +73 -0
  244. package/runtime/index.d.ts +6 -2
  245. package/runtime/index.d.ts.map +1 -1
  246. package/runtime/index.js +3 -1
  247. package/runtime/load-package.d.ts +24 -0
  248. package/runtime/load-package.d.ts.map +1 -1
  249. package/runtime/load-package.js +45 -1
  250. package/runtime/maplibre-renderer.d.ts +11 -0
  251. package/runtime/maplibre-renderer.d.ts.map +1 -1
  252. package/runtime/maplibre-renderer.js +32 -7
  253. package/runtime/offline-region-protocol.d.ts +73 -0
  254. package/runtime/offline-region-protocol.d.ts.map +1 -0
  255. package/runtime/offline-region-protocol.js +63 -0
  256. package/runtime/offline-region-style.d.ts +78 -0
  257. package/runtime/offline-region-style.d.ts.map +1 -0
  258. package/runtime/offline-region-style.js +147 -0
  259. package/runtime/pmtiles-protocol.d.ts +7 -8
  260. package/runtime/pmtiles-protocol.d.ts.map +1 -1
  261. package/runtime/pmtiles-protocol.js +19 -66
  262. package/runtime/protocol-registry.d.ts +55 -0
  263. package/runtime/protocol-registry.d.ts.map +1 -0
  264. package/runtime/protocol-registry.js +103 -0
  265. package/runtime/runtime.d.ts +2 -0
  266. package/runtime/runtime.d.ts.map +1 -1
  267. package/runtime/runtime.js +6 -1
  268. package/studio/index.d.ts +24 -7
  269. package/studio/index.d.ts.map +1 -1
  270. package/studio/index.js +21 -7
  271. package/studio/lifecycle-client.d.ts +164 -0
  272. package/studio/lifecycle-client.d.ts.map +1 -0
  273. package/studio/lifecycle-client.js +299 -0
  274. package/studio/lifecycle-errors.d.ts +91 -0
  275. package/studio/lifecycle-errors.d.ts.map +1 -0
  276. package/studio/lifecycle-errors.js +77 -0
  277. package/studio/lifecycle-types.d.ts +321 -0
  278. package/studio/lifecycle-types.d.ts.map +1 -0
  279. package/studio/lifecycle-types.js +22 -0
  280. package/webmap/convert-label.js +0 -1
  281. package/webmap/convert-symbol.d.ts.map +1 -1
@@ -5,11 +5,12 @@ import { compileDuckDbQuery, compileDuckDbQueryV2 } from "./duckdb.js";
5
5
  import { compileGeoServicesQuery } from "./geoservices.js";
6
6
  import { compileGrpcQuery } from "./grpc.js";
7
7
  import { createGeoParquetQueryIr, createQueryIr, deepFreeze, queryFromCanonical } from "./ir.js";
8
- import { compileOdataQuery } from "./odata.js";
8
+ import { compileOdataQuery, odataProtocolQueryCompiler } from "./odata-v1.js";
9
9
  import { compileOgcApiFeaturesQuery } from "./ogc-features.js";
10
+ import { createQueryPlanRepresentationDecision, normalizeRepresentationRequest } from "./representation.js";
10
11
  import { parseGeoParquetResourceHandle } from "./resource.js";
11
12
  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
+ import { compileWfsQuery, wfsProtocolQueryCompiler } from "./wfs-v1.js";
13
14
  export function explainQuery(options) {
14
15
  const resource = ownGeoParquetResource(options);
15
16
  if (!resource && options.descriptor.protocol === "geoparquet")
@@ -19,6 +20,9 @@ export function explainQuery(options) {
19
20
  const fallback = normalizeFallback(options.fallback);
20
21
  const estimates = normalizeEstimates(options.estimates);
21
22
  const executionMode = normalizeExecutionMode(options.executionMode);
23
+ // Representation is chosen before any step is compiled so an unsatisfiable
24
+ // explicit pin fails closed before the plan takes shape.
25
+ const representation = createQueryPlanRepresentationDecision(ir, estimates, normalizeRepresentationRequest(options.representation));
22
26
  let baseSteps;
23
27
  let pushdown;
24
28
  if (ir.query.aggregation && !ir.source.capabilities.includes("queryAggregate")) {
@@ -30,7 +34,8 @@ export function explainQuery(options) {
30
34
  }
31
35
  rejectUnsafeEstimate(fallback, estimates.rows, estimates.bytes);
32
36
  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 });
37
+ const adapterOwnsLookahead = ogcFallback || ir.source.protocol === "odata" || ir.source.protocol === "wfs";
38
+ const inputQuery = localAggregateInputQuery(ir.query, ir.query.aggregation, fallback.maxRows, options.descriptor.schema?.primaryKey, { preserveGeometry: ogcFallback, adapterOwnsLookahead });
34
39
  baseSteps = [
35
40
  {
36
41
  id: "remote-input",
@@ -86,8 +91,8 @@ export function explainQuery(options) {
86
91
  const { fidelity, losses } = summarizeFidelity(steps);
87
92
  const bounds = summarizePlanBounds(steps);
88
93
  const cache = createCacheDecision(options.cache);
89
- const validity = createPlanValidity(ir, provenance, capabilityPolicy, fallback, executionMode);
90
- const warnings = createPlanWarnings(baseSteps, ir.source);
94
+ const validity = createPlanValidity(ir, provenance, capabilityPolicy, fallback, executionMode, representation.selected);
95
+ const warnings = createPlanWarnings(baseSteps, ir.source, representation);
91
96
  const unsigned = {
92
97
  kind: QUERY_PLAN_KIND,
93
98
  version: ir.version === "2.0" ? QUERY_PLAN_V2_VERSION : QUERY_PLAN_VERSION,
@@ -102,6 +107,7 @@ export function explainQuery(options) {
102
107
  cache,
103
108
  provenance,
104
109
  validity,
110
+ representation,
105
111
  estimates,
106
112
  steps,
107
113
  warnings,
@@ -120,9 +126,9 @@ function compileRemoteQuery(source, query, operation = "query") {
120
126
  case "ogc-features":
121
127
  return ogcQueryAllWireRequest(compileOgcApiFeaturesQuery(source, query), operation);
122
128
  case "wfs":
123
- return compileWfsQuery(source, query);
129
+ return wfsProtocolQueryCompiler.compile({ source, query, operation });
124
130
  case "odata":
125
- return compileOdataQuery(source, query);
131
+ return odataProtocolQueryCompiler.compile({ source, query, operation });
126
132
  case "geoparquet":
127
133
  return isGeoParquetV2Source(source)
128
134
  ? compileDuckDbQueryV2(source, query)
@@ -191,14 +197,14 @@ export function validateQueryPlanSnapshot(plan) {
191
197
  throw invalidPersistedPlan();
192
198
  return snapshot.plan;
193
199
  }
194
- /** Canonical persistence boundary for accepted v1/v2 query plans. */
200
+ /** Canonical persistence boundary; validated legacy protocol artifacts are upgraded before serialization. */
195
201
  export function serializeQueryPlan(plan) {
196
202
  const snapshot = persistedPlanSnapshot(plan);
197
203
  if (snapshot.hash !== snapshot.plan.fingerprint)
198
204
  throw invalidPersistedPlan();
199
205
  return canonicalStringify(toJsonValue(snapshot.plan));
200
206
  }
201
- /** Parse a persisted plan without retaining invalid JSON or credential-bearing v1 locators. */
207
+ /** Parse a persisted plan, upgrading validated legacy protocol artifacts without retaining unsafe input. */
202
208
  export function parseQueryPlan(serialized) {
203
209
  if (typeof serialized !== "string")
204
210
  throw invalidPersistedPlan();
@@ -255,6 +261,7 @@ export function migrateGeoParquetQueryPlanV1(legacyPlan, resourceValue) {
255
261
  ...(parsed.ir.source.sourceVersion !== undefined ? { sourceVersion: parsed.ir.source.sourceVersion } : {}),
256
262
  authorizationScope: parsed.ir.source.authorizationScope,
257
263
  estimates: parsed.estimates,
264
+ representation: normalizeRepresentationRequest(persistedRepresentationRequest(parsed)),
258
265
  });
259
266
  }
260
267
  function ownGeoParquetResource(options) {
@@ -294,15 +301,11 @@ function assertPlanPersistenceSafe(plan) {
294
301
  throw invalidPersistedPlan();
295
302
  }
296
303
  }
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);
304
+ const expected = isLegacyOdataQueryPlanV1(plan)
305
+ ? rebuildLegacyOdataQueryPlanV1(plan)
306
+ : isLegacyWfsQueryPlanV1(plan)
307
+ ? rebuildLegacyWfsQueryPlanV1(plan)
308
+ : rebuildQueryPlanV1(plan);
306
309
  if (canonicalStringify(toJsonValue(plan)) !== canonicalStringify(toJsonValue(expected))) {
307
310
  throw invalidPersistedPlan();
308
311
  }
@@ -324,6 +327,10 @@ function assertStructuredDiagnostics(plan) {
324
327
  const fallback = normalizeFallback(plan.fallback);
325
328
  const estimates = normalizeEstimates(plan.estimates);
326
329
  const executionMode = normalizeExecutionMode(plan.validity.executionMode);
330
+ // Re-derive the representation decision from the persisted request plus the
331
+ // persisted IR/estimates, so a tampered `selected`, `available`, or `reason`
332
+ // cannot survive the canonical comparison below.
333
+ const representation = createQueryPlanRepresentationDecision(plan.ir, estimates, normalizeRepresentationRequest(persistedRepresentationRequest(plan)));
327
334
  if (!sameCanonicalValue(plan.fallback, fallback) || !sameCanonicalValue(plan.estimates, estimates)) {
328
335
  throw invalidPersistedPlan();
329
336
  }
@@ -363,8 +370,9 @@ function assertStructuredDiagnostics(plan) {
363
370
  bounds: summarizePlanBounds(expectedSteps),
364
371
  cache: createCacheDecision(persistedCacheOptions(plan.cache)),
365
372
  provenance: expectedProvenance,
366
- validity: createPlanValidity(plan.ir, expectedProvenance, plan.capabilityPolicy, fallback, executionMode),
367
- warnings: createPlanWarnings(plan.steps, plan.ir.source),
373
+ validity: createPlanValidity(plan.ir, expectedProvenance, plan.capabilityPolicy, fallback, executionMode, representation.selected),
374
+ representation,
375
+ warnings: createPlanWarnings(plan.steps, plan.ir.source, representation),
368
376
  };
369
377
  const actual = {
370
378
  diagnosticsVersion: plan.diagnosticsVersion,
@@ -375,11 +383,19 @@ function assertStructuredDiagnostics(plan) {
375
383
  cache: plan.cache,
376
384
  provenance: plan.provenance,
377
385
  validity: plan.validity,
386
+ representation: plan.representation,
378
387
  warnings: plan.warnings,
379
388
  };
380
389
  if (!sameCanonicalValue(actual, expected))
381
390
  throw invalidPersistedPlan();
382
391
  }
392
+ /** Read the persisted representation request without trusting any other member of the decision. */
393
+ function persistedRepresentationRequest(plan) {
394
+ const decision = plan.representation;
395
+ if (decision === null || typeof decision !== "object" || Array.isArray(decision))
396
+ throw invalidPersistedPlan();
397
+ return decision.requested;
398
+ }
383
399
  function persistedStepPushdown(step) {
384
400
  if (step.engine === "client") {
385
401
  if (step.operation !== "aggregate")
@@ -415,8 +431,9 @@ function rebuildPersistedProvenance(plan) {
415
431
  });
416
432
  }
417
433
  /** Rebuild legacy plans through the same pure planner to bind every persisted step to the IR. */
418
- function rebuildQueryPlanV1(plan) {
434
+ function rebuildQueryPlanV1(plan, options = {}) {
419
435
  const source = plan.ir.source;
436
+ const sourceAuthorityFingerprint = parseSourceAuthorityFingerprint(source.authorityFingerprint);
420
437
  const capabilitiesValue = parsePlanCapabilities(source.capabilities);
421
438
  const geometryProperty = parseOptionalPlanMetadata(source.geometryProperty);
422
439
  const primaryKey = persistedV1FallbackPrimaryKey(plan);
@@ -479,10 +496,110 @@ function rebuildQueryPlanV1(plan) {
479
496
  ...(source.schemaVersion !== undefined ? { schemaVersion: source.schemaVersion } : {}),
480
497
  ...(source.sourceVersion !== undefined ? { sourceVersion: source.sourceVersion } : {}),
481
498
  authorizationScope: source.authorizationScope,
499
+ ...(sourceAuthorityFingerprint
500
+ ? { sourceAuthorityFingerprint }
501
+ : options.legacyWfsAuthority
502
+ ? { sourceAuthorityFingerprint: null }
503
+ : {}),
482
504
  estimates: plan.estimates,
483
505
  cache: persistedCacheOptions(plan.cache),
484
506
  discovery: persistedDiscoveryContext(plan.provenance.discovery),
485
507
  executionMode: plan.validity.executionMode,
508
+ representation: normalizeRepresentationRequest(persistedRepresentationRequest(plan)),
509
+ });
510
+ }
511
+ function parseSourceAuthorityFingerprint(value) {
512
+ if (value === undefined)
513
+ return undefined;
514
+ if (typeof value !== "string" || !/^sha256:[0-9a-f]{64}$/.test(value)) {
515
+ throw invalidPersistedPlan();
516
+ }
517
+ return value;
518
+ }
519
+ /**
520
+ * Rebuild the exact OData plan representation emitted before operation-bound
521
+ * protocol artifacts were introduced. The persisted plan version remains
522
+ * 1.0, so those integrity-checked snapshots must be validated against their
523
+ * original compiler semantics before they can be migrated.
524
+ */
525
+ function rebuildLegacyOdataQueryPlanV1(plan) {
526
+ return rebuildLegacyProtocolQueryPlanV1(plan, "odata", compileOdataQuery);
527
+ }
528
+ function isLegacyOdataQueryPlanV1(plan) {
529
+ if (plan.version !== QUERY_PLAN_VERSION || plan.ir.version !== "1.0" || plan.ir.source.protocol !== "odata") {
530
+ return false;
531
+ }
532
+ const remoteSteps = plan.steps.filter((step) => step.engine === "remote");
533
+ return remoteSteps.length > 0 && remoteSteps.every((step) => step.compiled.compiler === "odata-v4-query-v1");
534
+ }
535
+ /**
536
+ * Rebuild the exact WFS plan representation emitted before operation-bound
537
+ * protocol artifacts were introduced. This validates the old artifact against
538
+ * the old compiler before the same-version plan is migrated.
539
+ */
540
+ function rebuildLegacyWfsQueryPlanV1(plan) {
541
+ return rebuildLegacyProtocolQueryPlanV1(plan, "wfs", compileWfsQuery);
542
+ }
543
+ function isLegacyWfsQueryPlanV1(plan) {
544
+ if (plan.version !== QUERY_PLAN_VERSION || plan.ir.version !== "1.0" || plan.ir.source.protocol !== "wfs") {
545
+ return false;
546
+ }
547
+ const remoteSteps = plan.steps.filter((step) => step.engine === "remote");
548
+ return remoteSteps.length > 0 && remoteSteps.every((step) => step.compiled.compiler === "wfs-2.0-get-feature-v1");
549
+ }
550
+ function rebuildLegacyProtocolQueryPlanV1(plan, protocol, compile) {
551
+ const modern = rebuildQueryPlanV1(plan, {
552
+ legacyWfsAuthority: protocol === "wfs" && plan.ir.source.authorityFingerprint === undefined,
553
+ });
554
+ if (modern.ir.source.protocol !== protocol)
555
+ throw invalidPersistedPlan();
556
+ const baseSteps = modern.steps.map((step) => {
557
+ const { fidelity: _fidelity, losses: _losses, bounds: _bounds, provenance: _provenance, ...base } = step;
558
+ if (base.engine !== "remote")
559
+ return base;
560
+ const query = base.operation === "queryAll" ? legacyProtocolQueryAllInput(base.query, modern.fallback) : base.query;
561
+ return {
562
+ ...base,
563
+ query,
564
+ compiled: compile(modern.ir.source, query),
565
+ };
566
+ });
567
+ const steps = baseSteps.map((step) => {
568
+ const diagnostics = createStepDiagnostics(step, modern.estimates, modern.provenance);
569
+ return step.engine === "client" ? { ...step, ...diagnostics, fidelity: "equivalent" } : { ...step, ...diagnostics };
570
+ });
571
+ const { fidelity, losses } = summarizeFidelity(steps);
572
+ const bounds = summarizePlanBounds(steps);
573
+ const warnings = createPlanWarnings(baseSteps, modern.ir.source, modern.representation);
574
+ const { id: _id, fingerprint: _fingerprint, fidelity: _modernFidelity, losses: _modernLosses, bounds: _modernBounds, steps: _modernSteps, warnings: _modernWarnings, ...shared } = modern;
575
+ const unsigned = {
576
+ ...shared,
577
+ fidelity,
578
+ losses,
579
+ bounds,
580
+ steps,
581
+ warnings,
582
+ };
583
+ const fingerprint = sha256(canonicalStringify(toJsonValue(unsigned)));
584
+ return deepFreeze({
585
+ ...unsigned,
586
+ id: `plan_${fingerprint.slice("sha256:".length, "sha256:".length + 16)}`,
587
+ fingerprint,
588
+ });
589
+ }
590
+ function legacyProtocolQueryAllInput(query, fallback) {
591
+ if (fallback.mode !== "bounded-local")
592
+ throw invalidPersistedPlan();
593
+ const pagination = query.pagination;
594
+ if (!pagination ||
595
+ pagination.offset !== 0 ||
596
+ pagination.limit !== fallback.maxRows ||
597
+ fallback.maxRows >= Number.MAX_SAFE_INTEGER) {
598
+ throw invalidPersistedPlan();
599
+ }
600
+ return deepFreeze({
601
+ ...query,
602
+ pagination: { offset: 0, limit: fallback.maxRows + 1 },
486
603
  });
487
604
  }
488
605
  function persistedV1FallbackPrimaryKey(plan) {
@@ -576,6 +693,7 @@ function rebuildGeoParquetQueryPlanV2(plan) {
576
693
  cache: persistedCacheOptions(plan.cache),
577
694
  discovery: persistedDiscoveryContext(plan.provenance.discovery),
578
695
  executionMode: plan.validity.executionMode,
696
+ representation: normalizeRepresentationRequest(persistedRepresentationRequest(plan)),
579
697
  });
580
698
  }
581
699
  function parsePersistedGeoParquetGeometryEncoding(value) {
@@ -670,8 +788,9 @@ function persistedPlanSnapshot(value) {
670
788
  assertPlanPersistenceSafe(plan);
671
789
  if (containsCredentialBearingPlanMaterial(plan))
672
790
  throw invalidPersistedPlan();
673
- const { id: _id, fingerprint: _fingerprint, ...unsigned } = plan;
674
- return { plan, hash: sha256(canonicalStringify(toJsonValue(unsigned))) };
791
+ const acceptedPlan = isLegacyOdataQueryPlanV1(plan) || isLegacyWfsQueryPlanV1(plan) ? rebuildQueryPlanV1(plan) : plan;
792
+ const { id: _id, fingerprint: _fingerprint, ...unsigned } = acceptedPlan;
793
+ return { plan: acceptedPlan, hash: sha256(canonicalStringify(toJsonValue(unsigned))) };
675
794
  }
676
795
  catch {
677
796
  throw invalidPersistedPlan();
@@ -809,10 +928,10 @@ function localAggregateInputQuery(query, aggregation, maxRows, primaryKey, optio
809
928
  ...base,
810
929
  ...(requiredFields.size > 0 ? { outFields: [...requiredFields].sort() } : {}),
811
930
  ...(!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.
931
+ // The OGC, OData, and WFS queryAll adapters own the overflow sentinel: a
932
+ // logical N-row ceiling becomes an N + 1 wire request and is reported
933
+ // through exceededTransferLimit. GeoServices retains its established
934
+ // explicit sentinel in the canonical step for backward-compatible output.
816
935
  pagination: { offset: 0, limit: maxRows + (options.adapterOwnsLookahead ? 0 : 1) },
817
936
  });
818
937
  }
@@ -0,0 +1,59 @@
1
+ /**
2
+ * Deterministic result-representation selection for the query planner.
3
+ *
4
+ * The planner's {@link QueryPlanExecutionMode} axis answers *when* rows are
5
+ * delivered (`snapshot` vs `delta`). This module adds the orthogonal axis that
6
+ * answers *what shape* they arrive in: protocol-neutral `Result` feature
7
+ * objects, or a `ColumnarBatchV1`.
8
+ *
9
+ * Selection is metadata-only and reproducible by inspection. It reads the
10
+ * declared source identity (protocol plus physical geometry encoding) and the
11
+ * caller's {@link QueryPlanningEstimates}; it never reads result data, never
12
+ * constructs a worker, and never performs I/O. Two callers who explain the same
13
+ * query against the same declared source get the same decision, and therefore
14
+ * the same plan fingerprint.
15
+ *
16
+ * Capability honesty is the governing rule: a source that cannot serve columnar
17
+ * plans `object` and records why, and an explicit unsatisfiable pin throws
18
+ * {@link HonuaCapabilityNotSupportedError}. There is no silent fallback that
19
+ * would report a representation execution cannot deliver.
20
+ *
21
+ * @module
22
+ */
23
+ import { type DuckDbGeometryEncoding, type QueryIrV1, type QueryIrV2, type QueryPlanRepresentationDecisionV1, type QueryPlanRepresentationRequest, type QueryPlanningEstimates } from "./types.js";
24
+ /**
25
+ * Estimated rows at or above which `representation: "auto"` selects columnar.
26
+ *
27
+ * Set at the SDK's bounded-local materialization ceiling
28
+ * (`MAX_LOCAL_MATERIALIZATION_ROWS`): below it a result is small enough that
29
+ * feature objects stay affordable, at or above it the per-row object cost is
30
+ * exactly the problem a columnar batch exists to remove.
31
+ */
32
+ export declare const COLUMNAR_REPRESENTATION_MIN_ROWS = 100000;
33
+ /** Estimated payload bytes at or above which `representation: "auto"` selects columnar. */
34
+ export declare const COLUMNAR_REPRESENTATION_MIN_BYTES: number;
35
+ /**
36
+ * Physical geometry encodings with an executable columnar producer today.
37
+ *
38
+ * These are the GeoParquet 1.1 native encodings that map 1:1 onto a Honua
39
+ * GeoArrow geometry kind, so a batch can be produced without inventing a
40
+ * geometry type. The multi-part encodings (`geoarrow-multipoint`,
41
+ * `geoarrow-multilinestring`, `geoarrow-multipolygon`) are deliberately absent:
42
+ * this SDK's dependency-free GeoArrow mapping has no multi-part kind, and
43
+ * re-labelling their parts as a single-part column would misreport the
44
+ * geometry.
45
+ */
46
+ export declare const COLUMNAR_REPRESENTATION_ENCODINGS: readonly DuckDbGeometryEncoding[];
47
+ /** True for a declared geometry encoding that has an executable columnar producer. */
48
+ export declare function isColumnarRepresentationEncoding(encoding: DuckDbGeometryEncoding | undefined): encoding is DuckDbGeometryEncoding;
49
+ /** Validate a caller-supplied representation request; `undefined` means `auto`. */
50
+ export declare function normalizeRepresentationRequest(value: unknown): QueryPlanRepresentationRequest;
51
+ /**
52
+ * Select the plan's result representation.
53
+ *
54
+ * @throws HonuaCapabilityNotSupportedError when `requested` is `"columnar"` and
55
+ * the declared source or query cannot serve it. Failing closed here is the
56
+ * point: an unsatisfiable pin must never quietly become object execution.
57
+ */
58
+ export declare function createQueryPlanRepresentationDecision(ir: QueryIrV1 | QueryIrV2, estimates: QueryPlanningEstimates, requested: QueryPlanRepresentationRequest): QueryPlanRepresentationDecisionV1;
59
+ //# sourceMappingURL=representation.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"representation.d.ts","sourceRoot":"","sources":["../../../src/query-planner/representation.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAGH,OAAO,EAEL,KAAK,sBAAsB,EAK3B,KAAK,SAAS,EACd,KAAK,SAAS,EAEd,KAAK,iCAAiC,EAGtC,KAAK,8BAA8B,EACnC,KAAK,sBAAsB,EAC5B,MAAM,YAAY,CAAC;AAEpB;;;;;;;GAOG;AACH,eAAO,MAAM,gCAAgC,SAAU,CAAC;AAExD,2FAA2F;AAC3F,eAAO,MAAM,iCAAiC,QAAkB,CAAC;AAEjE;;;;;;;;;;GAUG;AACH,eAAO,MAAM,iCAAiC,EAAE,SAAS,sBAAsB,EAI7E,CAAC;AAEH,sFAAsF;AACtF,wBAAgB,gCAAgC,CAC9C,QAAQ,EAAE,sBAAsB,GAAG,SAAS,GAC3C,QAAQ,IAAI,sBAAsB,CAEpC;AAED,mFAAmF;AACnF,wBAAgB,8BAA8B,CAAC,KAAK,EAAE,OAAO,GAAG,8BAA8B,CAM7F;AA2CD;;;;;;GAMG;AACH,wBAAgB,qCAAqC,CACnD,EAAE,EAAE,SAAS,GAAG,SAAS,EACzB,SAAS,EAAE,sBAAsB,EACjC,SAAS,EAAE,8BAA8B,GACxC,iCAAiC,CA0BnC"}
@@ -0,0 +1,140 @@
1
+ /**
2
+ * Deterministic result-representation selection for the query planner.
3
+ *
4
+ * The planner's {@link QueryPlanExecutionMode} axis answers *when* rows are
5
+ * delivered (`snapshot` vs `delta`). This module adds the orthogonal axis that
6
+ * answers *what shape* they arrive in: protocol-neutral `Result` feature
7
+ * objects, or a `ColumnarBatchV1`.
8
+ *
9
+ * Selection is metadata-only and reproducible by inspection. It reads the
10
+ * declared source identity (protocol plus physical geometry encoding) and the
11
+ * caller's {@link QueryPlanningEstimates}; it never reads result data, never
12
+ * constructs a worker, and never performs I/O. Two callers who explain the same
13
+ * query against the same declared source get the same decision, and therefore
14
+ * the same plan fingerprint.
15
+ *
16
+ * Capability honesty is the governing rule: a source that cannot serve columnar
17
+ * plans `object` and records why, and an explicit unsatisfiable pin throws
18
+ * {@link HonuaCapabilityNotSupportedError}. There is no silent fallback that
19
+ * would report a representation execution cannot deliver.
20
+ *
21
+ * @module
22
+ */
23
+ import { HonuaCapabilityNotSupportedError } from "../core/errors.js";
24
+ import { HonuaQueryPlanningError, QUERY_PLAN_DIAGNOSTICS_VERSION, } from "./types.js";
25
+ /**
26
+ * Estimated rows at or above which `representation: "auto"` selects columnar.
27
+ *
28
+ * Set at the SDK's bounded-local materialization ceiling
29
+ * (`MAX_LOCAL_MATERIALIZATION_ROWS`): below it a result is small enough that
30
+ * feature objects stay affordable, at or above it the per-row object cost is
31
+ * exactly the problem a columnar batch exists to remove.
32
+ */
33
+ export const COLUMNAR_REPRESENTATION_MIN_ROWS = 100_000;
34
+ /** Estimated payload bytes at or above which `representation: "auto"` selects columnar. */
35
+ export const COLUMNAR_REPRESENTATION_MIN_BYTES = 8 * 1024 * 1024;
36
+ /**
37
+ * Physical geometry encodings with an executable columnar producer today.
38
+ *
39
+ * These are the GeoParquet 1.1 native encodings that map 1:1 onto a Honua
40
+ * GeoArrow geometry kind, so a batch can be produced without inventing a
41
+ * geometry type. The multi-part encodings (`geoarrow-multipoint`,
42
+ * `geoarrow-multilinestring`, `geoarrow-multipolygon`) are deliberately absent:
43
+ * this SDK's dependency-free GeoArrow mapping has no multi-part kind, and
44
+ * re-labelling their parts as a single-part column would misreport the
45
+ * geometry.
46
+ */
47
+ export const COLUMNAR_REPRESENTATION_ENCODINGS = Object.freeze([
48
+ "geoarrow-linestring",
49
+ "geoarrow-point",
50
+ "geoarrow-polygon",
51
+ ]);
52
+ /** True for a declared geometry encoding that has an executable columnar producer. */
53
+ export function isColumnarRepresentationEncoding(encoding) {
54
+ return encoding !== undefined && COLUMNAR_REPRESENTATION_ENCODINGS.includes(encoding);
55
+ }
56
+ /** Validate a caller-supplied representation request; `undefined` means `auto`. */
57
+ export function normalizeRepresentationRequest(value) {
58
+ if (value === undefined)
59
+ return "auto";
60
+ if (value !== "auto" && value !== "object" && value !== "columnar") {
61
+ throw new HonuaQueryPlanningError("invalid-query", "representation is invalid");
62
+ }
63
+ return value;
64
+ }
65
+ function declaredGeometryEncoding(source) {
66
+ return source.protocol === "geoparquet" ? source.geoparquet?.geometryEncoding : undefined;
67
+ }
68
+ /**
69
+ * Decide whether this source and query can be served columnar, from declared
70
+ * metadata alone. Reasons are ordered most-fundamental first so the recorded
71
+ * reason names the constraint a caller would have to change first.
72
+ */
73
+ function columnarCapability(source, query) {
74
+ const geometryEncoding = declaredGeometryEncoding(source);
75
+ const encoded = geometryEncoding === undefined ? {} : { geometryEncoding };
76
+ if (source.protocol !== "geoparquet") {
77
+ return { supported: false, reason: "protocol-not-columnar", ...encoded };
78
+ }
79
+ if (!isColumnarRepresentationEncoding(geometryEncoding)) {
80
+ return { supported: false, reason: "encoding-not-columnar", ...encoded };
81
+ }
82
+ if (query.aggregation !== undefined) {
83
+ return { supported: false, reason: "aggregation-not-columnar", ...encoded };
84
+ }
85
+ if (query.returnGeometry === false) {
86
+ return { supported: false, reason: "geometry-not-requested", ...encoded };
87
+ }
88
+ return { supported: true, reason: "workload-above-threshold", ...encoded };
89
+ }
90
+ function meetsColumnarThreshold(estimates) {
91
+ if (estimates.rows !== undefined && estimates.rows >= COLUMNAR_REPRESENTATION_MIN_ROWS)
92
+ return true;
93
+ return estimates.bytes !== undefined && estimates.bytes >= COLUMNAR_REPRESENTATION_MIN_BYTES;
94
+ }
95
+ /**
96
+ * Select the plan's result representation.
97
+ *
98
+ * @throws HonuaCapabilityNotSupportedError when `requested` is `"columnar"` and
99
+ * the declared source or query cannot serve it. Failing closed here is the
100
+ * point: an unsatisfiable pin must never quietly become object execution.
101
+ */
102
+ export function createQueryPlanRepresentationDecision(ir, estimates, requested) {
103
+ const capability = columnarCapability(ir.source, ir.query);
104
+ if (requested === "columnar" && !capability.supported) {
105
+ throw new HonuaCapabilityNotSupportedError("columnar-execution", ir.source.protocol, ir.source.id, {
106
+ context: { representation: "columnar", reason: capability.reason },
107
+ });
108
+ }
109
+ const inputs = {
110
+ ...(capability.geometryEncoding === undefined ? {} : { geometryEncoding: capability.geometryEncoding }),
111
+ rowThreshold: COLUMNAR_REPRESENTATION_MIN_ROWS,
112
+ byteThreshold: COLUMNAR_REPRESENTATION_MIN_BYTES,
113
+ ...(estimates.rows === undefined ? {} : { estimatedRows: estimates.rows }),
114
+ ...(estimates.bytes === undefined ? {} : { estimatedBytes: estimates.bytes }),
115
+ };
116
+ const available = capability.supported
117
+ ? Object.freeze(["object", "columnar"])
118
+ : Object.freeze(["object"]);
119
+ const { selected, reason } = selectRepresentation(capability, estimates, requested);
120
+ return Object.freeze({
121
+ version: QUERY_PLAN_DIAGNOSTICS_VERSION,
122
+ requested,
123
+ selected,
124
+ available,
125
+ reason,
126
+ inputs: Object.freeze(inputs),
127
+ });
128
+ }
129
+ function selectRepresentation(capability, estimates, requested) {
130
+ if (requested !== "auto")
131
+ return { selected: requested, reason: "explicit-pin" };
132
+ if (!capability.supported)
133
+ return { selected: "object", reason: capability.reason };
134
+ if (estimates.rows === undefined && estimates.bytes === undefined) {
135
+ return { selected: "object", reason: "estimate-unavailable" };
136
+ }
137
+ return meetsColumnarThreshold(estimates)
138
+ ? { selected: "columnar", reason: "workload-above-threshold" }
139
+ : { selected: "object", reason: "workload-below-threshold" };
140
+ }