@honua/sdk-esri-compat 0.1.12 → 0.1.14

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 (250) hide show
  1. package/README.md +2 -0
  2. package/columnar/apache-arrow.d.ts +17 -0
  3. package/columnar/apache-arrow.d.ts.map +1 -0
  4. package/columnar/apache-arrow.js +1036 -0
  5. package/columnar/batch-cache-conformance.d.ts +66 -0
  6. package/columnar/batch-cache-conformance.d.ts.map +1 -0
  7. package/columnar/batch-cache-conformance.js +317 -0
  8. package/columnar/batch-cache-indexeddb.d.ts +31 -0
  9. package/columnar/batch-cache-indexeddb.d.ts.map +1 -0
  10. package/columnar/batch-cache-indexeddb.js +148 -0
  11. package/columnar/batch-cache.d.ts +298 -0
  12. package/columnar/batch-cache.d.ts.map +1 -0
  13. package/columnar/batch-cache.js +757 -0
  14. package/columnar/geoarrow-aggregate.d.ts +178 -0
  15. package/columnar/geoarrow-aggregate.d.ts.map +1 -0
  16. package/columnar/geoarrow-aggregate.js +976 -0
  17. package/columnar/geoarrow-patch.d.ts +382 -0
  18. package/columnar/geoarrow-patch.d.ts.map +1 -0
  19. package/columnar/geoarrow-patch.js +1656 -0
  20. package/columnar/geoarrow-reproject.d.ts +22 -0
  21. package/columnar/geoarrow-reproject.d.ts.map +1 -0
  22. package/columnar/geoarrow-reproject.js +119 -0
  23. package/columnar/geoarrow-serialization.d.ts +68 -0
  24. package/columnar/geoarrow-serialization.d.ts.map +1 -0
  25. package/columnar/geoarrow-serialization.js +331 -0
  26. package/columnar/geoarrow-types.d.ts +271 -0
  27. package/columnar/geoarrow-types.d.ts.map +1 -0
  28. package/columnar/geoarrow-types.js +15 -0
  29. package/columnar/geoarrow.d.ts +52 -0
  30. package/columnar/geoarrow.d.ts.map +1 -0
  31. package/columnar/geoarrow.js +1469 -0
  32. package/columnar/index.d.ts +43 -0
  33. package/columnar/index.d.ts.map +1 -0
  34. package/columnar/index.js +29 -0
  35. package/columnar/operators.d.ts +70 -0
  36. package/columnar/operators.d.ts.map +1 -0
  37. package/columnar/operators.js +307 -0
  38. package/columnar/result.d.ts +369 -0
  39. package/columnar/result.d.ts.map +1 -0
  40. package/columnar/result.js +815 -0
  41. package/columnar/telemetry.d.ts +152 -0
  42. package/columnar/telemetry.d.ts.map +1 -0
  43. package/columnar/telemetry.js +265 -0
  44. package/columnar/transfer.d.ts +28 -0
  45. package/columnar/transfer.d.ts.map +1 -0
  46. package/columnar/transfer.js +669 -0
  47. package/columnar/types.d.ts +175 -0
  48. package/columnar/types.d.ts.map +1 -0
  49. package/columnar/types.js +22 -0
  50. package/columnar/worker.d.ts +157 -0
  51. package/columnar/worker.d.ts.map +1 -0
  52. package/columnar/worker.js +1059 -0
  53. package/connect-url-safety.d.ts +53 -0
  54. package/connect-url-safety.js +189 -0
  55. package/contract/pmtiles.d.ts +6 -5
  56. package/contract/pmtiles.d.ts.map +1 -1
  57. package/contract/pmtiles.js +30 -5
  58. package/contract/source.d.ts.map +1 -1
  59. package/contract/source.js +11 -8
  60. package/core/client.d.ts.map +1 -1
  61. package/core/client.js +87 -19
  62. package/core/deprecation-notice.d.ts +28 -0
  63. package/core/deprecation-notice.d.ts.map +1 -0
  64. package/core/deprecation-notice.js +22 -0
  65. package/core/esri-geojson.d.ts.map +1 -1
  66. package/core/esri-geojson.js +14 -8
  67. package/core/optional-grpc-peer.d.ts +15 -0
  68. package/core/optional-grpc-peer.d.ts.map +1 -0
  69. package/core/optional-grpc-peer.js +60 -0
  70. package/esri-compat/basemap-gallery.d.ts +2 -0
  71. package/esri-compat/basemap-gallery.d.ts.map +1 -1
  72. package/esri-compat/basemap-gallery.js +12 -0
  73. package/esri-compat/basemap.d.ts +4 -0
  74. package/esri-compat/basemap.d.ts.map +1 -1
  75. package/esri-compat/basemap.js +70 -0
  76. package/esri-compat/bookmarks.d.ts +2 -0
  77. package/esri-compat/bookmarks.d.ts.map +1 -1
  78. package/esri-compat/bookmarks.js +12 -0
  79. package/esri-compat/controls.d.ts +18 -0
  80. package/esri-compat/controls.d.ts.map +1 -1
  81. package/esri-compat/controls.js +120 -57
  82. package/esri-compat/directions.d.ts +4 -0
  83. package/esri-compat/directions.d.ts.map +1 -1
  84. package/esri-compat/directions.js +30 -20
  85. package/esri-compat/editor.d.ts +4 -0
  86. package/esri-compat/editor.d.ts.map +1 -1
  87. package/esri-compat/editor.js +16 -0
  88. package/esri-compat/esri-config.d.ts +2 -1
  89. package/esri-compat/esri-config.d.ts.map +1 -1
  90. package/esri-compat/feature-effect.d.ts +23 -0
  91. package/esri-compat/feature-effect.d.ts.map +1 -0
  92. package/esri-compat/feature-effect.js +12 -0
  93. package/esri-compat/feature-form.d.ts +6 -0
  94. package/esri-compat/feature-form.d.ts.map +1 -1
  95. package/esri-compat/feature-form.js +63 -0
  96. package/esri-compat/feature-layer.d.ts +21 -2
  97. package/esri-compat/feature-layer.d.ts.map +1 -1
  98. package/esri-compat/feature-layer.js +172 -1
  99. package/esri-compat/feature-table.d.ts +5 -0
  100. package/esri-compat/feature-table.d.ts.map +1 -1
  101. package/esri-compat/feature-table.js +23 -0
  102. package/esri-compat/identity-manager.d.ts +1 -1
  103. package/esri-compat/identity-manager.js +1 -1
  104. package/esri-compat/legend.d.ts +2 -0
  105. package/esri-compat/legend.d.ts.map +1 -1
  106. package/esri-compat/legend.js +98 -0
  107. package/esri-compat/locator.d.ts +1 -1
  108. package/esri-compat/locator.js +1 -1
  109. package/esri-compat/map-view-mount.d.ts +1 -0
  110. package/esri-compat/map-view-mount.d.ts.map +1 -1
  111. package/esri-compat/map-view-mount.js +46 -25
  112. package/esri-compat/map-view.d.ts +13 -0
  113. package/esri-compat/map-view.d.ts.map +1 -1
  114. package/esri-compat/map-view.js +94 -8
  115. package/esri-compat/map.d.ts +5 -1
  116. package/esri-compat/map.d.ts.map +1 -1
  117. package/esri-compat/map.js +10 -0
  118. package/esri-compat/measurement.d.ts +2 -0
  119. package/esri-compat/measurement.d.ts.map +1 -1
  120. package/esri-compat/measurement.js +14 -0
  121. package/esri-compat/point.d.ts +7 -0
  122. package/esri-compat/point.d.ts.map +1 -1
  123. package/esri-compat/point.js +33 -3
  124. package/esri-compat/popup.d.ts +2 -0
  125. package/esri-compat/popup.d.ts.map +1 -1
  126. package/esri-compat/popup.js +12 -0
  127. package/esri-compat/portal.d.ts +1 -1
  128. package/esri-compat/portal.js +1 -1
  129. package/esri-compat/print.d.ts +3 -0
  130. package/esri-compat/print.d.ts.map +1 -1
  131. package/esri-compat/print.js +15 -0
  132. package/esri-compat/search.d.ts +4 -0
  133. package/esri-compat/search.d.ts.map +1 -1
  134. package/esri-compat/search.js +25 -28
  135. package/esri-compat/sketch.d.ts +2 -0
  136. package/esri-compat/sketch.d.ts.map +1 -1
  137. package/esri-compat/sketch.js +12 -0
  138. package/esri-compat/widget-host.d.ts +46 -17
  139. package/esri-compat/widget-host.d.ts.map +1 -1
  140. package/esri-compat/widget-host.js +84 -20
  141. package/esri-compat/widget-shell-hosts.d.ts +61 -0
  142. package/esri-compat/widget-shell-hosts.d.ts.map +1 -0
  143. package/esri-compat/widget-shell-hosts.js +76 -0
  144. package/index.d.ts +3 -1
  145. package/index.js +1 -0
  146. package/offline/digest.d.ts +15 -0
  147. package/offline/digest.js +58 -0
  148. package/offline/quota.d.ts +161 -0
  149. package/offline/quota.js +370 -0
  150. package/offline/types.d.ts +349 -0
  151. package/offline/types.js +75 -0
  152. package/package.json +21 -2
  153. package/query-planner/canonical-filter.d.ts +30 -0
  154. package/query-planner/canonical-filter.d.ts.map +1 -0
  155. package/query-planner/canonical-filter.js +30 -0
  156. package/query-planner/canonical.d.ts +7 -0
  157. package/query-planner/canonical.d.ts.map +1 -0
  158. package/query-planner/canonical.js +145 -0
  159. package/query-planner/columnar-plan.d.ts +63 -0
  160. package/query-planner/columnar-plan.d.ts.map +1 -0
  161. package/query-planner/columnar-plan.js +94 -0
  162. package/query-planner/cql2-json.d.ts +17 -0
  163. package/query-planner/cql2-json.d.ts.map +1 -0
  164. package/query-planner/cql2-json.js +646 -0
  165. package/query-planner/diagnostics.d.ts +35 -0
  166. package/query-planner/diagnostics.d.ts.map +1 -0
  167. package/query-planner/diagnostics.js +386 -0
  168. package/query-planner/duckdb.d.ts +79 -0
  169. package/query-planner/duckdb.d.ts.map +1 -0
  170. package/query-planner/duckdb.js +772 -0
  171. package/query-planner/execution-context.d.ts +5 -0
  172. package/query-planner/execution-context.d.ts.map +1 -0
  173. package/query-planner/execution-context.js +52 -0
  174. package/query-planner/executor.d.ts +5 -0
  175. package/query-planner/executor.d.ts.map +1 -0
  176. package/query-planner/executor.js +229 -0
  177. package/query-planner/geoservices.d.ts +91 -0
  178. package/query-planner/geoservices.d.ts.map +1 -0
  179. package/query-planner/geoservices.js +688 -0
  180. package/query-planner/grpc.d.ts +100 -0
  181. package/query-planner/grpc.d.ts.map +1 -0
  182. package/query-planner/grpc.js +760 -0
  183. package/query-planner/index.d.ts +59 -0
  184. package/query-planner/index.d.ts.map +1 -0
  185. package/query-planner/index.js +43 -0
  186. package/query-planner/ir.d.ts +25 -0
  187. package/query-planner/ir.d.ts.map +1 -0
  188. package/query-planner/ir.js +479 -0
  189. package/query-planner/local-aggregate.d.ts +5 -0
  190. package/query-planner/local-aggregate.d.ts.map +1 -0
  191. package/query-planner/local-aggregate.js +126 -0
  192. package/query-planner/odata-protocol-module.d.ts +54 -0
  193. package/query-planner/odata-protocol-module.d.ts.map +1 -0
  194. package/query-planner/odata-protocol-module.js +199 -0
  195. package/query-planner/odata-v1.d.ts +23 -0
  196. package/query-planner/odata-v1.d.ts.map +1 -0
  197. package/query-planner/odata-v1.js +202 -0
  198. package/query-planner/odata.d.ts +45 -0
  199. package/query-planner/odata.d.ts.map +1 -0
  200. package/query-planner/odata.js +519 -0
  201. package/query-planner/ogc-compiler.d.ts +55 -0
  202. package/query-planner/ogc-compiler.d.ts.map +1 -0
  203. package/query-planner/ogc-compiler.js +480 -0
  204. package/query-planner/ogc-features.d.ts +53 -0
  205. package/query-planner/ogc-features.d.ts.map +1 -0
  206. package/query-planner/ogc-features.js +639 -0
  207. package/query-planner/planner.d.ts +17 -0
  208. package/query-planner/planner.d.ts.map +1 -0
  209. package/query-planner/planner.js +937 -0
  210. package/query-planner/representation.d.ts +59 -0
  211. package/query-planner/representation.d.ts.map +1 -0
  212. package/query-planner/representation.js +140 -0
  213. package/query-planner/resource.d.ts +94 -0
  214. package/query-planner/resource.d.ts.map +1 -0
  215. package/query-planner/resource.js +526 -0
  216. package/query-planner/semantic-canonical.d.ts +15 -0
  217. package/query-planner/semantic-canonical.d.ts.map +1 -0
  218. package/query-planner/semantic-canonical.js +62 -0
  219. package/query-planner/semantic-compat.d.ts +10 -0
  220. package/query-planner/semantic-compat.d.ts.map +1 -0
  221. package/query-planner/semantic-compat.js +35 -0
  222. package/query-planner/semantic-compiler.d.ts +64 -0
  223. package/query-planner/semantic-compiler.d.ts.map +1 -0
  224. package/query-planner/semantic-compiler.js +81 -0
  225. package/query-planner/semantic-literals.d.ts +30 -0
  226. package/query-planner/semantic-literals.d.ts.map +1 -0
  227. package/query-planner/semantic-literals.js +133 -0
  228. package/query-planner/semantic-types.d.ts +265 -0
  229. package/query-planner/semantic-types.d.ts.map +1 -0
  230. package/query-planner/semantic-types.js +1 -0
  231. package/query-planner/semantic.d.ts +54 -0
  232. package/query-planner/semantic.d.ts.map +1 -0
  233. package/query-planner/semantic.js +1421 -0
  234. package/query-planner/types.d.ts +712 -0
  235. package/query-planner/types.d.ts.map +1 -0
  236. package/query-planner/types.js +49 -0
  237. package/query-planner/wfs-protocol-module.d.ts +57 -0
  238. package/query-planner/wfs-protocol-module.d.ts.map +1 -0
  239. package/query-planner/wfs-protocol-module.js +731 -0
  240. package/query-planner/wfs-semantic.d.ts +58 -0
  241. package/query-planner/wfs-semantic.d.ts.map +1 -0
  242. package/query-planner/wfs-semantic.js +547 -0
  243. package/query-planner/wfs-v1.d.ts +41 -0
  244. package/query-planner/wfs-v1.d.ts.map +1 -0
  245. package/query-planner/wfs-v1.js +366 -0
  246. package/query-planner/wfs.d.ts +4 -0
  247. package/query-planner/wfs.d.ts.map +1 -0
  248. package/query-planner/wfs.js +2 -0
  249. package/widget-capabilities.d.ts +67 -0
  250. package/widget-capabilities.js +152 -0
@@ -0,0 +1,152 @@
1
+ /**
2
+ * Columnar-plane telemetry (issue #1043).
3
+ *
4
+ * Epic #394 asks the SDK to integrate planner, GeoParquet, GeoArrow, deck.gl,
5
+ * caching, and telemetry "around one batch identity". Everything the columnar
6
+ * plane measured was previously a per-call return value — `ColumnarBatchMetrics`
7
+ * on a transfer receipt, a cache diagnostic on one module's callback, nothing at
8
+ * all for patch or conversion — so two observations of the same batch could not
9
+ * be correlated. This module is the seam that correlates them.
10
+ *
11
+ * Three properties decide the shape:
12
+ *
13
+ * 1. **It is the SDK's telemetry shape, not a new one.** `ColumnarTelemetry`
14
+ * is the same before/after/error collector as `HonuaRuntimeTelemetry`
15
+ * (`src/runtime/runtime.ts`), mirrored exactly the way
16
+ * `src/operator/telemetry.ts` mirrors it, so one embedder-supplied observer
17
+ * satisfies every surface. The span's `identity` slot is this plane's
18
+ * correlation key, exactly as `packageId` is the runtime's and `intentId`
19
+ * the operator's — and it is `undefined` for a batch that declares no
20
+ * identity rather than being invented.
21
+ * 2. **It is structural and dependency-light.** The declaration is an interface
22
+ * the caller satisfies; nothing here imports a telemetry implementation, and
23
+ * this module imports no module outside `src/columnar/`. That matters because
24
+ * `transfer.ts`, `result.ts`, and `geoarrow-patch.ts` are on the data plane's
25
+ * hot path and are reachable from the `/deckgl` and `/geoparquet` bundles.
26
+ * 3. **A span never carries a raw authorization scope.** The identity is
27
+ * reported as source id, source version, schema version, plan id, and the
28
+ * *digest* of the authorization scope — the same digest rule the cache key
29
+ * uses, which lives here (see {@link columnarAuthorizationScopeDigest}) so
30
+ * the hot-path modules can bind identity without importing the cache.
31
+ *
32
+ * **Zero cost when absent.** Every emitter entry point is reached through a
33
+ * caller-side `telemetry ? … : undefined` guard and every terminal call through
34
+ * `span?.finish(…)`, both of which short-circuit argument evaluation. With no
35
+ * sink configured the columnar path allocates nothing here, performs no digest,
36
+ * and reads no clock.
37
+ *
38
+ * **A hostile sink cannot reach the operation.** Every hook invocation, every
39
+ * read of a caller-supplied property, and the digest itself are contained: a
40
+ * sink that throws, or an identity with a throwing getter, changes neither the
41
+ * result nor the typed error of the operation being observed. Sink calls are
42
+ * never awaited.
43
+ *
44
+ * **Delivery ordering.** Binding a span costs one SHA-256 of the authorization
45
+ * scope, which Web Crypto only offers asynchronously. The digest is memoized per
46
+ * scope, so the *first* span for a previously unseen scope is delivered once its
47
+ * digest resolves and every later span for that scope is delivered
48
+ * synchronously. Within one span `before` always precedes its terminal event,
49
+ * and cold spans sharing a scope stay in order because they chain on the same
50
+ * digest promise. The observed operation never waits for either.
51
+ *
52
+ * @module
53
+ */
54
+ import type { ColumnarBatchIdentityV1 } from "./types.js";
55
+ /**
56
+ * The columnar operations that emit spans. Each value names one measurable
57
+ * unit of work, not one function: `columnar-transfer` covers the ownership
58
+ * handoff whether an application or the worker session performs it.
59
+ */
60
+ export type ColumnarTelemetryKind = "columnar-transfer" | "columnar-worker-operation" | "columnar-cache-read" | "columnar-cache-write" | "columnar-patch-apply" | "columnar-result-conversion";
61
+ /**
62
+ * The batch identity a span is bound to.
63
+ *
64
+ * This is {@link ColumnarBatchIdentityV1} with the authorization scope replaced
65
+ * by its digest, which is the whole point: telemetry must never become a side
66
+ * channel for the scope value the cache key deliberately excludes. Ordering and
67
+ * freshness are omitted because a span reports what an operation cost, not what
68
+ * the batch is; the four keyed identifiers plus the scope digest are what make
69
+ * two observations of the same batch correlatable.
70
+ */
71
+ export interface ColumnarTelemetryIdentityV1 {
72
+ readonly sourceId: string;
73
+ readonly sourceVersion: string;
74
+ readonly schemaVersion: string;
75
+ readonly planId: string;
76
+ /** {@link columnarAuthorizationScopeDigest} of the identity's scope. */
77
+ readonly authorizationScopeDigest: `sha256:${string}`;
78
+ }
79
+ export interface ColumnarTelemetrySpan {
80
+ readonly kind: ColumnarTelemetryKind;
81
+ /** `undefined` when the batch declares no identity, or when no digest is available. */
82
+ readonly identity: ColumnarTelemetryIdentityV1 | undefined;
83
+ readonly startedAt: number;
84
+ readonly detail?: Record<string, unknown>;
85
+ }
86
+ export interface ColumnarTelemetrySpanResult extends ColumnarTelemetrySpan {
87
+ readonly finishedAt: number;
88
+ readonly durationMs: number;
89
+ readonly error?: unknown;
90
+ }
91
+ /**
92
+ * Before/after/error collector for columnar execution. Mirrors
93
+ * {@link HonuaRuntimeTelemetry}; supply one object and wire it to every
94
+ * columnar surface. Hooks must not be relied on to run synchronously (see the
95
+ * module's delivery-ordering note) and are never awaited.
96
+ */
97
+ export interface ColumnarTelemetry {
98
+ before?: (span: ColumnarTelemetrySpan) => void;
99
+ after?: (span: ColumnarTelemetrySpanResult) => void;
100
+ error?: (span: ColumnarTelemetrySpanResult) => void;
101
+ }
102
+ /** Injection point shared by every columnar surface that can be observed. */
103
+ export interface ColumnarTelemetryOptions {
104
+ /**
105
+ * Optional observer. Off by default: no surface constructs one, and with no
106
+ * sink the observed path costs exactly what it costs without this module.
107
+ */
108
+ readonly telemetry?: ColumnarTelemetry;
109
+ }
110
+ /**
111
+ * Opaque digest of an authorization scope.
112
+ *
113
+ * The scope is documented as a non-secret opaque fingerprint; digesting it is
114
+ * what keeps that promise enforceable rather than assumed, and keeps the raw
115
+ * value out of the persistent cache key (`columnarBatchCacheKey`) and out of
116
+ * every telemetry span.
117
+ *
118
+ * It lives in this module rather than in `batch-cache.ts` so the hot-path
119
+ * columnar modules can bind span identity without importing the cache — the
120
+ * rule has exactly one implementation and it is this one.
121
+ *
122
+ * @throws HonuaGeoArrowError `missing-peer` when the host exposes no Web Crypto
123
+ * SHA-256. The digest is never approximated and the raw scope is never
124
+ * substituted for it.
125
+ */
126
+ export declare function columnarAuthorizationScopeDigest(scope: string): Promise<`sha256:${string}`>;
127
+ /**
128
+ * Resolve once every span deferred behind a cold scope digest has been
129
+ * delivered.
130
+ *
131
+ * Deliberately not part of the package surface: it exists so this repo's tests
132
+ * can assert delivery deterministically instead of racing a digest that Node
133
+ * completes on its thread pool. Nothing on a columnar path awaits it.
134
+ */
135
+ export declare function columnarTelemetryDelivered(): Promise<void>;
136
+ /** One in-flight observation. Terminal calls are idempotent. */
137
+ export interface ColumnarSpanHandle {
138
+ /** Record success. `detail` is merged over the detail the span started with. */
139
+ finish(detail?: Record<string, unknown>): void;
140
+ /** Record failure with the value the operation is about to throw or reject with. */
141
+ fail(error: unknown, detail?: Record<string, unknown>): void;
142
+ }
143
+ /**
144
+ * Begin one observation.
145
+ *
146
+ * Callers guard the call itself — `telemetry ? beginColumnarSpan(telemetry, …) : undefined` —
147
+ * so that with no sink neither this function nor its `detail` argument is ever
148
+ * evaluated. Returns `undefined` when a span could not be started, which the
149
+ * `span?.finish(…)` call sites treat exactly like "no sink".
150
+ */
151
+ export declare function beginColumnarSpan(telemetry: ColumnarTelemetry, kind: ColumnarTelemetryKind, identity: ColumnarBatchIdentityV1 | undefined, detail?: Record<string, unknown>): ColumnarSpanHandle | undefined;
152
+ //# sourceMappingURL=telemetry.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"telemetry.d.ts","sourceRoot":"","sources":["../../../src/columnar/telemetry.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoDG;AAGH,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,YAAY,CAAC;AAE1D;;;;GAIG;AACH,MAAM,MAAM,qBAAqB,GAC7B,mBAAmB,GACnB,2BAA2B,GAC3B,qBAAqB,GACrB,sBAAsB,GACtB,sBAAsB,GACtB,4BAA4B,CAAC;AAEjC;;;;;;;;;GASG;AACH,MAAM,WAAW,2BAA2B;IAC1C,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,wEAAwE;IACxE,QAAQ,CAAC,wBAAwB,EAAE,UAAU,MAAM,EAAE,CAAC;CACvD;AAED,MAAM,WAAW,qBAAqB;IACpC,QAAQ,CAAC,IAAI,EAAE,qBAAqB,CAAC;IACrC,uFAAuF;IACvF,QAAQ,CAAC,QAAQ,EAAE,2BAA2B,GAAG,SAAS,CAAC;IAC3D,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAC3C;AAED,MAAM,WAAW,2BAA4B,SAAQ,qBAAqB;IACxE,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,KAAK,CAAC,EAAE,OAAO,CAAC;CAC1B;AAED;;;;;GAKG;AACH,MAAM,WAAW,iBAAiB;IAChC,MAAM,CAAC,EAAE,CAAC,IAAI,EAAE,qBAAqB,KAAK,IAAI,CAAC;IAC/C,KAAK,CAAC,EAAE,CAAC,IAAI,EAAE,2BAA2B,KAAK,IAAI,CAAC;IACpD,KAAK,CAAC,EAAE,CAAC,IAAI,EAAE,2BAA2B,KAAK,IAAI,CAAC;CACrD;AAED,6EAA6E;AAC7E,MAAM,WAAW,wBAAwB;IACvC;;;OAGG;IACH,QAAQ,CAAC,SAAS,CAAC,EAAE,iBAAiB,CAAC;CACxC;AAID;;;;;;;;;;;;;;;GAeG;AACH,wBAAsB,gCAAgC,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,MAAM,EAAE,CAAC,CAWjG;AA2CD;;;;;;;GAOG;AACH,wBAAsB,0BAA0B,IAAI,OAAO,CAAC,IAAI,CAAC,CAOhE;AA6BD,gEAAgE;AAChE,MAAM,WAAW,kBAAkB;IACjC,gFAAgF;IAChF,MAAM,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC;IAC/C,oFAAoF;IACpF,IAAI,CAAC,KAAK,EAAE,OAAO,EAAE,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC;CAC9D;AAyGD;;;;;;;GAOG;AACH,wBAAgB,iBAAiB,CAC/B,SAAS,EAAE,iBAAiB,EAC5B,IAAI,EAAE,qBAAqB,EAC3B,QAAQ,EAAE,uBAAuB,GAAG,SAAS,EAC7C,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC/B,kBAAkB,GAAG,SAAS,CAOhC"}
@@ -0,0 +1,265 @@
1
+ /**
2
+ * Columnar-plane telemetry (issue #1043).
3
+ *
4
+ * Epic #394 asks the SDK to integrate planner, GeoParquet, GeoArrow, deck.gl,
5
+ * caching, and telemetry "around one batch identity". Everything the columnar
6
+ * plane measured was previously a per-call return value — `ColumnarBatchMetrics`
7
+ * on a transfer receipt, a cache diagnostic on one module's callback, nothing at
8
+ * all for patch or conversion — so two observations of the same batch could not
9
+ * be correlated. This module is the seam that correlates them.
10
+ *
11
+ * Three properties decide the shape:
12
+ *
13
+ * 1. **It is the SDK's telemetry shape, not a new one.** `ColumnarTelemetry`
14
+ * is the same before/after/error collector as `HonuaRuntimeTelemetry`
15
+ * (`src/runtime/runtime.ts`), mirrored exactly the way
16
+ * `src/operator/telemetry.ts` mirrors it, so one embedder-supplied observer
17
+ * satisfies every surface. The span's `identity` slot is this plane's
18
+ * correlation key, exactly as `packageId` is the runtime's and `intentId`
19
+ * the operator's — and it is `undefined` for a batch that declares no
20
+ * identity rather than being invented.
21
+ * 2. **It is structural and dependency-light.** The declaration is an interface
22
+ * the caller satisfies; nothing here imports a telemetry implementation, and
23
+ * this module imports no module outside `src/columnar/`. That matters because
24
+ * `transfer.ts`, `result.ts`, and `geoarrow-patch.ts` are on the data plane's
25
+ * hot path and are reachable from the `/deckgl` and `/geoparquet` bundles.
26
+ * 3. **A span never carries a raw authorization scope.** The identity is
27
+ * reported as source id, source version, schema version, plan id, and the
28
+ * *digest* of the authorization scope — the same digest rule the cache key
29
+ * uses, which lives here (see {@link columnarAuthorizationScopeDigest}) so
30
+ * the hot-path modules can bind identity without importing the cache.
31
+ *
32
+ * **Zero cost when absent.** Every emitter entry point is reached through a
33
+ * caller-side `telemetry ? … : undefined` guard and every terminal call through
34
+ * `span?.finish(…)`, both of which short-circuit argument evaluation. With no
35
+ * sink configured the columnar path allocates nothing here, performs no digest,
36
+ * and reads no clock.
37
+ *
38
+ * **A hostile sink cannot reach the operation.** Every hook invocation, every
39
+ * read of a caller-supplied property, and the digest itself are contained: a
40
+ * sink that throws, or an identity with a throwing getter, changes neither the
41
+ * result nor the typed error of the operation being observed. Sink calls are
42
+ * never awaited.
43
+ *
44
+ * **Delivery ordering.** Binding a span costs one SHA-256 of the authorization
45
+ * scope, which Web Crypto only offers asynchronously. The digest is memoized per
46
+ * scope, so the *first* span for a previously unseen scope is delivered once its
47
+ * digest resolves and every later span for that scope is delivered
48
+ * synchronously. Within one span `before` always precedes its terminal event,
49
+ * and cold spans sharing a scope stay in order because they chain on the same
50
+ * digest promise. The observed operation never waits for either.
51
+ *
52
+ * @module
53
+ */
54
+ import { HonuaGeoArrowError } from "./geoarrow-types.js";
55
+ const SCOPE_DIGEST_PREFIX = "honua-columnar-batch-cache-scope:v1:";
56
+ /**
57
+ * Opaque digest of an authorization scope.
58
+ *
59
+ * The scope is documented as a non-secret opaque fingerprint; digesting it is
60
+ * what keeps that promise enforceable rather than assumed, and keeps the raw
61
+ * value out of the persistent cache key (`columnarBatchCacheKey`) and out of
62
+ * every telemetry span.
63
+ *
64
+ * It lives in this module rather than in `batch-cache.ts` so the hot-path
65
+ * columnar modules can bind span identity without importing the cache — the
66
+ * rule has exactly one implementation and it is this one.
67
+ *
68
+ * @throws HonuaGeoArrowError `missing-peer` when the host exposes no Web Crypto
69
+ * SHA-256. The digest is never approximated and the raw scope is never
70
+ * substituted for it.
71
+ */
72
+ export async function columnarAuthorizationScopeDigest(scope) {
73
+ const subtle = globalThis.crypto?.subtle;
74
+ if (!subtle) {
75
+ throw new HonuaGeoArrowError("missing-peer", "Columnar authorization-scope digests require Web Crypto SHA-256.", {
76
+ resource: "crypto.subtle",
77
+ });
78
+ }
79
+ const digest = await subtle.digest("SHA-256", new TextEncoder().encode(`${SCOPE_DIGEST_PREFIX}${scope}`));
80
+ let hex = "";
81
+ for (const byte of new Uint8Array(digest))
82
+ hex += byte.toString(16).padStart(2, "0");
83
+ return `sha256:${hex}`;
84
+ }
85
+ /**
86
+ * Bounded scope→digest memo.
87
+ *
88
+ * Scopes are non-secret fingerprints and an application holds a handful of
89
+ * them, so this is small by nature; it is bounded anyway (oldest-first) because
90
+ * an unbounded module-level map keyed by caller data is a retention bug waiting
91
+ * to happen. Spans are derived observations and nothing here is persisted.
92
+ */
93
+ const MAX_MEMOIZED_SCOPES = 64;
94
+ const resolvedScopeDigests = new Map();
95
+ const pendingScopeDigests = new Map();
96
+ function memoize(scope, digest) {
97
+ if (resolvedScopeDigests.size >= MAX_MEMOIZED_SCOPES) {
98
+ const oldest = resolvedScopeDigests.keys().next();
99
+ if (!oldest.done)
100
+ resolvedScopeDigests.delete(oldest.value);
101
+ }
102
+ resolvedScopeDigests.set(scope, digest);
103
+ }
104
+ /** Resolve and memoize one scope digest. Never rejects: a failure is `undefined`. */
105
+ function scopeDigest(scope) {
106
+ const pending = pendingScopeDigests.get(scope);
107
+ if (pending)
108
+ return pending;
109
+ const resolving = columnarAuthorizationScopeDigest(scope).then((digest) => {
110
+ pendingScopeDigests.delete(scope);
111
+ memoize(scope, digest);
112
+ return digest;
113
+ }, () => {
114
+ // Without a digest an identity cannot be reported at all, because the raw
115
+ // scope is never an acceptable substitute for it.
116
+ pendingScopeDigests.delete(scope);
117
+ return undefined;
118
+ });
119
+ pendingScopeDigests.set(scope, resolving);
120
+ return resolving;
121
+ }
122
+ /**
123
+ * Resolve once every span deferred behind a cold scope digest has been
124
+ * delivered.
125
+ *
126
+ * Deliberately not part of the package surface: it exists so this repo's tests
127
+ * can assert delivery deterministically instead of racing a digest that Node
128
+ * completes on its thread pool. Nothing on a columnar path awaits it.
129
+ */
130
+ export async function columnarTelemetryDelivered() {
131
+ for (let turn = 0; turn < 8; turn += 1) {
132
+ if (pendingScopeDigests.size > 0)
133
+ await Promise.allSettled([...pendingScopeDigests.values()]);
134
+ // Each deferred span chains `before` and then its terminal event on that
135
+ // digest, so the queue needs a turn per link.
136
+ await Promise.resolve();
137
+ }
138
+ }
139
+ /** Read the four keyed identifiers, tolerating a hostile or malformed identity. */
140
+ function keyedIdentity(identity) {
141
+ try {
142
+ const sourceId = identity.sourceId;
143
+ const sourceVersion = identity.sourceVersion;
144
+ const schemaVersion = identity.schemaVersion;
145
+ const planId = identity.planId;
146
+ const scope = identity.authorizationScope;
147
+ if (typeof sourceId !== "string" ||
148
+ typeof sourceVersion !== "string" ||
149
+ typeof schemaVersion !== "string" ||
150
+ typeof planId !== "string" ||
151
+ typeof scope !== "string") {
152
+ return undefined;
153
+ }
154
+ return { scope, fields: { sourceId, sourceVersion, schemaVersion, planId } };
155
+ }
156
+ catch {
157
+ return undefined;
158
+ }
159
+ }
160
+ class ColumnarSpan {
161
+ #telemetry;
162
+ #kind;
163
+ #startedAt;
164
+ #detail;
165
+ #identity;
166
+ /** Set only while this span's scope digest is still cold. */
167
+ #gate;
168
+ #settled = false;
169
+ constructor(telemetry, kind, identity, detail) {
170
+ this.#telemetry = telemetry;
171
+ this.#kind = kind;
172
+ this.#startedAt = Date.now();
173
+ this.#detail = detail;
174
+ const keyed = identity === undefined || identity === null ? undefined : keyedIdentity(identity);
175
+ if (keyed === undefined) {
176
+ this.#emitBefore();
177
+ return;
178
+ }
179
+ const memoized = resolvedScopeDigests.get(keyed.scope);
180
+ if (memoized !== undefined) {
181
+ this.#identity = Object.freeze({ ...keyed.fields, authorizationScopeDigest: memoized });
182
+ this.#emitBefore();
183
+ return;
184
+ }
185
+ this.#gate = scopeDigest(keyed.scope).then((digest) => {
186
+ if (digest !== undefined) {
187
+ this.#identity = Object.freeze({ ...keyed.fields, authorizationScopeDigest: digest });
188
+ }
189
+ this.#emitBefore();
190
+ });
191
+ }
192
+ finish(detail) {
193
+ this.#settle(detail, undefined);
194
+ }
195
+ fail(error, detail) {
196
+ this.#settle(detail, { error });
197
+ }
198
+ #settle(detail, failure) {
199
+ if (this.#settled)
200
+ return;
201
+ this.#settled = true;
202
+ if (this.#gate === undefined) {
203
+ this.#emitTerminal(detail, failure);
204
+ return;
205
+ }
206
+ // Ordered, never awaited by the operation: the terminal event follows its
207
+ // own `before` even when the digest was still resolving.
208
+ void this.#gate.then(() => {
209
+ this.#emitTerminal(detail, failure);
210
+ });
211
+ }
212
+ #emitBefore() {
213
+ const span = Object.freeze({
214
+ kind: this.#kind,
215
+ identity: this.#identity,
216
+ startedAt: this.#startedAt,
217
+ ...(this.#detail === undefined ? {} : { detail: this.#detail }),
218
+ });
219
+ deliver(this.#telemetry, "before", span);
220
+ }
221
+ #emitTerminal(detail, failure) {
222
+ const finishedAt = Date.now();
223
+ const merged = this.#detail === undefined ? detail : detail === undefined ? this.#detail : { ...this.#detail, ...detail };
224
+ const result = Object.freeze({
225
+ kind: this.#kind,
226
+ identity: this.#identity,
227
+ startedAt: this.#startedAt,
228
+ ...(merged === undefined ? {} : { detail: merged }),
229
+ finishedAt,
230
+ durationMs: finishedAt - this.#startedAt,
231
+ ...(failure === undefined ? {} : { error: failure.error }),
232
+ });
233
+ deliver(this.#telemetry, failure === undefined ? "after" : "error", result);
234
+ }
235
+ }
236
+ /** Invoke one hook. A missing, unreadable, or throwing hook is inert. */
237
+ function deliver(telemetry, hook, span) {
238
+ try {
239
+ const listener = telemetry[hook];
240
+ if (typeof listener !== "function")
241
+ return;
242
+ listener(span);
243
+ }
244
+ catch {
245
+ // An observer cannot fail, stall, or alter the operation it observes.
246
+ }
247
+ }
248
+ /**
249
+ * Begin one observation.
250
+ *
251
+ * Callers guard the call itself — `telemetry ? beginColumnarSpan(telemetry, …) : undefined` —
252
+ * so that with no sink neither this function nor its `detail` argument is ever
253
+ * evaluated. Returns `undefined` when a span could not be started, which the
254
+ * `span?.finish(…)` call sites treat exactly like "no sink".
255
+ */
256
+ export function beginColumnarSpan(telemetry, kind, identity, detail) {
257
+ try {
258
+ if (typeof telemetry !== "object" || telemetry === null)
259
+ return undefined;
260
+ return new ColumnarSpan(telemetry, kind, identity, detail);
261
+ }
262
+ catch {
263
+ return undefined;
264
+ }
265
+ }
@@ -0,0 +1,28 @@
1
+ import { type ColumnarBatchLeaseState, type ColumnarBatchLimits, type ColumnarBatchMetrics, type ColumnarBatchV1, type ColumnarTransferOptions, type ColumnarTransferReceipt, type ColumnarTransferTarget, type CreateColumnarBatchInput } from "./types.js";
2
+ /**
3
+ * Validate and normalize one batch without copying its payload buffers.
4
+ * Schema metadata is cloned/frozen so later caller mutation cannot change the
5
+ * transport contract; every `data` reference remains exactly caller-owned.
6
+ */
7
+ export declare function createColumnarBatch(input: CreateColumnarBatchInput, limits?: ColumnarBatchLimits): ColumnarBatchV1;
8
+ /** Inspect an owned batch and return exact backing-allocation/copy accounting. */
9
+ export declare function inspectColumnarBatch(batch: ColumnarBatchV1, limits?: ColumnarBatchLimits): ColumnarBatchMetrics;
10
+ /**
11
+ * Owns the right to transfer a batch once. The SDK first uses structured clone
12
+ * to detach the lease's buffers, then invokes the consumer with the owned clone
13
+ * and its exact deduplicated transfer list. The optional consumer promise is an
14
+ * acknowledgement/backpressure boundary; rejection never restores ownership.
15
+ */
16
+ export declare class ColumnarBatchLease {
17
+ #private;
18
+ constructor(batch: ColumnarBatchV1, limits?: ColumnarBatchLimits);
19
+ get state(): ColumnarBatchLeaseState;
20
+ get batch(): ColumnarBatchV1;
21
+ transfer(target: ColumnarTransferTarget, options?: ColumnarTransferOptions): Promise<ColumnarTransferReceipt>;
22
+ /** Release this lease's owned references. Idempotent after disposal. */
23
+ dispose(): void;
24
+ private releaseReservations;
25
+ }
26
+ /** Validate and wrap a batch in a one-owner transfer lease. */
27
+ export declare function leaseColumnarBatch(batch: ColumnarBatchV1, limits?: ColumnarBatchLimits): ColumnarBatchLease;
28
+ //# sourceMappingURL=transfer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"transfer.d.ts","sourceRoot":"","sources":["../../../src/columnar/transfer.ts"],"names":[],"mappings":"AACA,OAAO,EAKL,KAAK,uBAAuB,EAC5B,KAAK,mBAAmB,EACxB,KAAK,oBAAoB,EACzB,KAAK,eAAe,EAMpB,KAAK,uBAAuB,EAC5B,KAAK,uBAAuB,EAC5B,KAAK,sBAAsB,EAE3B,KAAK,wBAAwB,EAQ9B,MAAM,YAAY,CAAC;AAkmBpB;;;;GAIG;AACH,wBAAgB,mBAAmB,CACjC,KAAK,EAAE,wBAAwB,EAC/B,MAAM,GAAE,mBAAwB,GAC/B,eAAe,CAOjB;AAED,kFAAkF;AAClF,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,eAAe,EAAE,MAAM,GAAE,mBAAwB,GAAG,oBAAoB,CAEnH;AAED;;;;;GAKG;AACH,qBAAa,kBAAkB;;gBAMV,KAAK,EAAE,eAAe,EAAE,MAAM,GAAE,mBAAwB;IA8B3E,IAAW,KAAK,IAAI,uBAAuB,CAE1C;IAED,IAAW,KAAK,IAAI,eAAe,CAOlC;IAEY,QAAQ,CACnB,MAAM,EAAE,sBAAsB,EAC9B,OAAO,GAAE,uBAA4B,GACpC,OAAO,CAAC,uBAAuB,CAAC;IAmEnC,wEAAwE;IACjE,OAAO,IAAI,IAAI;IAUtB,OAAO,CAAC,mBAAmB;CAK5B;AAED,+DAA+D;AAC/D,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,eAAe,EAAE,MAAM,GAAE,mBAAwB,GAAG,kBAAkB,CAE/G"}