@honua/react 0.1.12 → 0.1.13

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 (190) hide show
  1. package/columnar/apache-arrow.d.ts +17 -0
  2. package/columnar/apache-arrow.d.ts.map +1 -0
  3. package/columnar/apache-arrow.js +1036 -0
  4. package/columnar/batch-cache-conformance.d.ts +66 -0
  5. package/columnar/batch-cache-conformance.d.ts.map +1 -0
  6. package/columnar/batch-cache-conformance.js +317 -0
  7. package/columnar/batch-cache-indexeddb.d.ts +31 -0
  8. package/columnar/batch-cache-indexeddb.d.ts.map +1 -0
  9. package/columnar/batch-cache-indexeddb.js +148 -0
  10. package/columnar/batch-cache.d.ts +298 -0
  11. package/columnar/batch-cache.d.ts.map +1 -0
  12. package/columnar/batch-cache.js +757 -0
  13. package/columnar/geoarrow-aggregate.d.ts +178 -0
  14. package/columnar/geoarrow-aggregate.d.ts.map +1 -0
  15. package/columnar/geoarrow-aggregate.js +976 -0
  16. package/columnar/geoarrow-patch.d.ts +382 -0
  17. package/columnar/geoarrow-patch.d.ts.map +1 -0
  18. package/columnar/geoarrow-patch.js +1656 -0
  19. package/columnar/geoarrow-reproject.d.ts +22 -0
  20. package/columnar/geoarrow-reproject.d.ts.map +1 -0
  21. package/columnar/geoarrow-reproject.js +119 -0
  22. package/columnar/geoarrow-serialization.d.ts +68 -0
  23. package/columnar/geoarrow-serialization.d.ts.map +1 -0
  24. package/columnar/geoarrow-serialization.js +331 -0
  25. package/columnar/geoarrow-types.d.ts +271 -0
  26. package/columnar/geoarrow-types.d.ts.map +1 -0
  27. package/columnar/geoarrow-types.js +15 -0
  28. package/columnar/geoarrow.d.ts +52 -0
  29. package/columnar/geoarrow.d.ts.map +1 -0
  30. package/columnar/geoarrow.js +1469 -0
  31. package/columnar/index.d.ts +43 -0
  32. package/columnar/index.d.ts.map +1 -0
  33. package/columnar/index.js +29 -0
  34. package/columnar/operators.d.ts +70 -0
  35. package/columnar/operators.d.ts.map +1 -0
  36. package/columnar/operators.js +307 -0
  37. package/columnar/result.d.ts +369 -0
  38. package/columnar/result.d.ts.map +1 -0
  39. package/columnar/result.js +815 -0
  40. package/columnar/telemetry.d.ts +152 -0
  41. package/columnar/telemetry.d.ts.map +1 -0
  42. package/columnar/telemetry.js +265 -0
  43. package/columnar/transfer.d.ts +28 -0
  44. package/columnar/transfer.d.ts.map +1 -0
  45. package/columnar/transfer.js +669 -0
  46. package/columnar/types.d.ts +175 -0
  47. package/columnar/types.d.ts.map +1 -0
  48. package/columnar/types.js +22 -0
  49. package/columnar/worker.d.ts +157 -0
  50. package/columnar/worker.d.ts.map +1 -0
  51. package/columnar/worker.js +1059 -0
  52. package/connect-url-safety.d.ts +53 -0
  53. package/connect-url-safety.js +189 -0
  54. package/contract/pmtiles.d.ts +6 -5
  55. package/contract/pmtiles.d.ts.map +1 -1
  56. package/contract/pmtiles.js +30 -5
  57. package/core/client.d.ts.map +1 -1
  58. package/core/client.js +87 -19
  59. package/core/deprecation-notice.d.ts +28 -0
  60. package/core/deprecation-notice.d.ts.map +1 -0
  61. package/core/deprecation-notice.js +22 -0
  62. package/core/esri-geojson.d.ts.map +1 -1
  63. package/core/esri-geojson.js +10 -6
  64. package/core/optional-grpc-peer.d.ts +15 -0
  65. package/core/optional-grpc-peer.d.ts.map +1 -0
  66. package/core/optional-grpc-peer.js +60 -0
  67. package/esri-compat/basemap-gallery.d.ts +2 -0
  68. package/esri-compat/basemap-gallery.d.ts.map +1 -1
  69. package/esri-compat/basemap-gallery.js +12 -0
  70. package/esri-compat/basemap.d.ts +4 -0
  71. package/esri-compat/basemap.d.ts.map +1 -1
  72. package/esri-compat/basemap.js +70 -0
  73. package/esri-compat/bookmarks.d.ts +2 -0
  74. package/esri-compat/bookmarks.d.ts.map +1 -1
  75. package/esri-compat/bookmarks.js +12 -0
  76. package/esri-compat/controls.d.ts +18 -0
  77. package/esri-compat/controls.d.ts.map +1 -1
  78. package/esri-compat/controls.js +120 -57
  79. package/esri-compat/directions.d.ts +4 -0
  80. package/esri-compat/directions.d.ts.map +1 -1
  81. package/esri-compat/directions.js +30 -20
  82. package/esri-compat/editor.d.ts +4 -0
  83. package/esri-compat/editor.d.ts.map +1 -1
  84. package/esri-compat/editor.js +16 -0
  85. package/esri-compat/esri-config.d.ts +2 -1
  86. package/esri-compat/esri-config.d.ts.map +1 -1
  87. package/esri-compat/feature-effect.d.ts +23 -0
  88. package/esri-compat/feature-effect.d.ts.map +1 -0
  89. package/esri-compat/feature-effect.js +12 -0
  90. package/esri-compat/feature-form.d.ts +6 -0
  91. package/esri-compat/feature-form.d.ts.map +1 -1
  92. package/esri-compat/feature-form.js +63 -0
  93. package/esri-compat/feature-layer.d.ts +21 -2
  94. package/esri-compat/feature-layer.d.ts.map +1 -1
  95. package/esri-compat/feature-layer.js +172 -1
  96. package/esri-compat/feature-table.d.ts +5 -0
  97. package/esri-compat/feature-table.d.ts.map +1 -1
  98. package/esri-compat/feature-table.js +23 -0
  99. package/esri-compat/identity-manager.d.ts +1 -1
  100. package/esri-compat/identity-manager.js +1 -1
  101. package/esri-compat/legend.d.ts +2 -0
  102. package/esri-compat/legend.d.ts.map +1 -1
  103. package/esri-compat/legend.js +98 -0
  104. package/esri-compat/locator.d.ts +1 -1
  105. package/esri-compat/locator.js +1 -1
  106. package/esri-compat/map-view-mount.d.ts +1 -0
  107. package/esri-compat/map-view-mount.d.ts.map +1 -1
  108. package/esri-compat/map-view-mount.js +46 -25
  109. package/esri-compat/map-view.d.ts +13 -0
  110. package/esri-compat/map-view.d.ts.map +1 -1
  111. package/esri-compat/map-view.js +94 -8
  112. package/esri-compat/map.d.ts +5 -1
  113. package/esri-compat/map.d.ts.map +1 -1
  114. package/esri-compat/map.js +10 -0
  115. package/esri-compat/measurement.d.ts +2 -0
  116. package/esri-compat/measurement.d.ts.map +1 -1
  117. package/esri-compat/measurement.js +14 -0
  118. package/esri-compat/point.d.ts +7 -0
  119. package/esri-compat/point.d.ts.map +1 -1
  120. package/esri-compat/point.js +33 -3
  121. package/esri-compat/popup.d.ts +2 -0
  122. package/esri-compat/popup.d.ts.map +1 -1
  123. package/esri-compat/popup.js +12 -0
  124. package/esri-compat/portal.d.ts +1 -1
  125. package/esri-compat/portal.js +1 -1
  126. package/esri-compat/print.d.ts +3 -0
  127. package/esri-compat/print.d.ts.map +1 -1
  128. package/esri-compat/print.js +15 -0
  129. package/esri-compat/search.d.ts +4 -0
  130. package/esri-compat/search.d.ts.map +1 -1
  131. package/esri-compat/search.js +25 -28
  132. package/esri-compat/sketch.d.ts +2 -0
  133. package/esri-compat/sketch.d.ts.map +1 -1
  134. package/esri-compat/sketch.js +12 -0
  135. package/esri-compat/widget-host.d.ts +46 -17
  136. package/esri-compat/widget-host.d.ts.map +1 -1
  137. package/esri-compat/widget-host.js +84 -20
  138. package/esri-compat/widget-shell-hosts.d.ts +61 -0
  139. package/esri-compat/widget-shell-hosts.d.ts.map +1 -0
  140. package/esri-compat/widget-shell-hosts.js +76 -0
  141. package/geocoding/index.d.ts +114 -0
  142. package/geocoding/index.d.ts.map +1 -0
  143. package/geocoding/index.js +293 -0
  144. package/geocoding/provider-http.d.ts +37 -0
  145. package/geocoding/provider-http.d.ts.map +1 -0
  146. package/geocoding/provider-http.js +93 -0
  147. package/geocoding/provider.d.ts +145 -0
  148. package/geocoding/provider.d.ts.map +1 -0
  149. package/geocoding/provider.js +41 -0
  150. package/geocoding/providers/honua.d.ts +28 -0
  151. package/geocoding/providers/honua.d.ts.map +1 -0
  152. package/geocoding/providers/honua.js +68 -0
  153. package/geocoding/providers/nominatim.d.ts +47 -0
  154. package/geocoding/providers/nominatim.d.ts.map +1 -0
  155. package/geocoding/providers/nominatim.js +105 -0
  156. package/geocoding/providers/pelias.d.ts +40 -0
  157. package/geocoding/providers/pelias.d.ts.map +1 -0
  158. package/geocoding/providers/pelias.js +115 -0
  159. package/geocoding/providers/photon.d.ts +39 -0
  160. package/geocoding/providers/photon.d.ts.map +1 -0
  161. package/geocoding/providers/photon.js +107 -0
  162. package/geometry/convert.d.ts +13 -0
  163. package/geometry/convert.d.ts.map +1 -0
  164. package/geometry/convert.js +12 -0
  165. package/geometry/index.d.ts +17 -0
  166. package/geometry/index.d.ts.map +1 -0
  167. package/geometry/index.js +15 -0
  168. package/geometry/internal.d.ts +25 -0
  169. package/geometry/internal.d.ts.map +1 -0
  170. package/geometry/internal.js +40 -0
  171. package/geometry/ops.d.ts +71 -0
  172. package/geometry/ops.d.ts.map +1 -0
  173. package/geometry/ops.js +147 -0
  174. package/geometry/project.d.ts +40 -0
  175. package/geometry/project.d.ts.map +1 -0
  176. package/geometry/project.js +88 -0
  177. package/geometry/types.d.ts +36 -0
  178. package/geometry/types.d.ts.map +1 -0
  179. package/geometry/types.js +12 -0
  180. package/kernel/renderer.d.ts +82 -0
  181. package/kernel/renderer.js +1 -0
  182. package/offline/digest.d.ts +15 -0
  183. package/offline/digest.js +58 -0
  184. package/offline/quota.d.ts +161 -0
  185. package/offline/quota.js +370 -0
  186. package/offline/types.d.ts +349 -0
  187. package/offline/types.js +75 -0
  188. package/package.json +14 -2
  189. package/widget-capabilities.d.ts +67 -0
  190. package/widget-capabilities.js +152 -0
@@ -0,0 +1,1059 @@
1
+ import { beginColumnarSpan } from "./telemetry.js";
2
+ import { createColumnarBatch, inspectColumnarBatch, leaseColumnarBatch } from "./transfer.js";
3
+ import { COLUMNAR_BATCH_KIND, COLUMNAR_BATCH_VERSION, HonuaColumnarTransferError, } from "./types.js";
4
+ export const COLUMNAR_WORKER_PROTOCOL_VERSION = "1.0";
5
+ export const COLUMNAR_WORKER_REQUEST_KIND = "honua.columnar-worker.request";
6
+ export const COLUMNAR_WORKER_CANCEL_KIND = "honua.columnar-worker.cancel";
7
+ export const COLUMNAR_WORKER_PROGRESS_KIND = "honua.columnar-worker.progress";
8
+ export const COLUMNAR_WORKER_RESULT_KIND = "honua.columnar-worker.result";
9
+ export const COLUMNAR_WORKER_ERROR_KIND = "honua.columnar-worker.error";
10
+ const DEFAULT_MAX_PENDING_REQUESTS = 16;
11
+ const DEFAULT_CANCEL_ACKNOWLEDGEMENT_MS = 50;
12
+ const MAX_IDENTIFIER_LENGTH = 256;
13
+ export class HonuaColumnarWorkerError extends Error {
14
+ code;
15
+ constructor(code, message, options = {}) {
16
+ super(message, options.cause === undefined ? undefined : { cause: options.cause });
17
+ this.code = code;
18
+ this.name = "HonuaColumnarWorkerError";
19
+ this.requestId = options.requestId;
20
+ this.operation = options.operation;
21
+ }
22
+ requestId;
23
+ operation;
24
+ }
25
+ /**
26
+ * Create a lazy, serial worker session. Only one transferred request is active;
27
+ * queued batches stay caller-owned until dispatch. Cancelling active work
28
+ * settles the caller immediately and gives the worker a bounded window to
29
+ * acknowledge the cancellation; a worker that misses the window is retired so a
30
+ * non-cooperative operation cannot escape bounds.
31
+ */
32
+ export function createColumnarWorkerSession(options) {
33
+ if (typeof options !== "object" || options === null) {
34
+ throw new HonuaColumnarWorkerError("invalid-request", "Worker session options must be an object");
35
+ }
36
+ let createWorker;
37
+ let rawMaxPendingRequests;
38
+ let rawStreamOrdering;
39
+ let rawCancelAcknowledgementMs;
40
+ let telemetry;
41
+ try {
42
+ createWorker = options.createWorker;
43
+ rawMaxPendingRequests = options.maxPendingRequests;
44
+ rawStreamOrdering = options.streamOrdering;
45
+ rawCancelAcknowledgementMs = options.cancelAcknowledgementMs;
46
+ telemetry = options.telemetry;
47
+ }
48
+ catch (cause) {
49
+ throw new HonuaColumnarWorkerError("invalid-request", "Worker session options could not be read", { cause });
50
+ }
51
+ if (typeof createWorker !== "function") {
52
+ throw new HonuaColumnarWorkerError("invalid-request", "createWorker must be a function");
53
+ }
54
+ if (telemetry !== undefined && (typeof telemetry !== "object" || telemetry === null)) {
55
+ throw new HonuaColumnarWorkerError("invalid-request", "telemetry must be an object");
56
+ }
57
+ const maxPendingRequests = positiveInteger(rawMaxPendingRequests ?? DEFAULT_MAX_PENDING_REQUESTS, "maxPendingRequests");
58
+ const streamOrdering = normalizeStreamOrdering(rawStreamOrdering);
59
+ const cancelAcknowledgementMs = nonNegativeInteger(rawCancelAcknowledgementMs ?? DEFAULT_CANCEL_ACKNOWLEDGEMENT_MS, "cancelAcknowledgementMs");
60
+ const queue = [];
61
+ let active;
62
+ let pendingCancel;
63
+ let streamCursor;
64
+ let transport;
65
+ let workerPromise;
66
+ let workerGeneration = 0;
67
+ let disposed = false;
68
+ let nextRequest = 0;
69
+ const onMessage = (event) => {
70
+ if (pendingCancel && !active) {
71
+ acknowledgeCancellation(event);
72
+ return;
73
+ }
74
+ if (!active || active.settled)
75
+ return;
76
+ let message;
77
+ try {
78
+ message = normalizeWorkerResponse(event.data, active.requestId);
79
+ }
80
+ catch (cause) {
81
+ failActive(failure("invalid-response", "Worker returned an invalid protocol message", active, cause), true);
82
+ return;
83
+ }
84
+ if (message.requestId !== active.requestId)
85
+ return;
86
+ if (message.kind === COLUMNAR_WORKER_PROGRESS_KIND) {
87
+ if (message.fraction < active.lastProgress) {
88
+ failActive(failure("invalid-response", "Worker progress must be monotonic", active), true);
89
+ return;
90
+ }
91
+ active.lastProgress = message.fraction;
92
+ try {
93
+ active.options.onProgress?.(Object.freeze({
94
+ requestId: active.requestId,
95
+ fraction: message.fraction,
96
+ ...(message.stage === undefined ? {} : { stage: message.stage }),
97
+ }));
98
+ }
99
+ catch {
100
+ // Progress observers cannot corrupt worker ownership or settlement.
101
+ }
102
+ return;
103
+ }
104
+ if (message.kind === COLUMNAR_WORKER_ERROR_KIND) {
105
+ failActive(failure(message.code, message.message, active));
106
+ return;
107
+ }
108
+ const current = active;
109
+ try {
110
+ const batch = normalizeBatchEnvelope(message.batch, "worker result batch", current.options);
111
+ const outputMetrics = inspectColumnarBatch(batch, current.options);
112
+ assertMetrics(message.metrics, outputMetrics, "result");
113
+ settle(current);
114
+ current.resolve(Object.freeze({
115
+ requestId: current.requestId,
116
+ operation: current.operation,
117
+ batch,
118
+ inputMetrics: current.inputMetrics,
119
+ outputMetrics,
120
+ }));
121
+ active = undefined;
122
+ void dispatch();
123
+ }
124
+ catch (cause) {
125
+ failActive(failure("invalid-response", "Worker returned an invalid columnar result", current, cause), true);
126
+ }
127
+ };
128
+ const onError = (event) => {
129
+ if (!active) {
130
+ // A fault during the cancellation window ends the quarantine, so queued
131
+ // work must resume on a replacement transport.
132
+ retireTransport();
133
+ void dispatch();
134
+ return;
135
+ }
136
+ let message = "Columnar worker failed";
137
+ let cause;
138
+ try {
139
+ message = event.message?.trim() || message;
140
+ cause = event.error;
141
+ }
142
+ catch {
143
+ // A hostile fault event cannot prevent deterministic settlement.
144
+ }
145
+ failActive(failure("worker-failed", message, active, cause), true);
146
+ };
147
+ function clearPendingCancel() {
148
+ if (!pendingCancel)
149
+ return;
150
+ clearTimeout(pendingCancel.timer);
151
+ pendingCancel = undefined;
152
+ }
153
+ /**
154
+ * A cancelled request keeps the transport quarantined until the worker
155
+ * reports a terminal outcome for it. Any terminal message proves the worker
156
+ * is back under session control, so the warm transport is reused. A result
157
+ * that raced the cancellation is discarded together with the buffers it
158
+ * transferred: the caller has already been settled with `aborted`.
159
+ */
160
+ function acknowledgeCancellation(event) {
161
+ const expected = pendingCancel;
162
+ if (!expected)
163
+ return;
164
+ let message;
165
+ try {
166
+ message = normalizeWorkerResponse(event.data, expected.requestId);
167
+ }
168
+ catch {
169
+ retireTransport();
170
+ void dispatch();
171
+ return;
172
+ }
173
+ if (message.requestId !== expected.requestId)
174
+ return;
175
+ if (message.kind === COLUMNAR_WORKER_PROGRESS_KIND)
176
+ return;
177
+ clearPendingCancel();
178
+ void dispatch();
179
+ }
180
+ function retireTransport() {
181
+ clearPendingCancel();
182
+ workerGeneration += 1;
183
+ const current = transport;
184
+ transport = undefined;
185
+ workerPromise = undefined;
186
+ if (!current)
187
+ return;
188
+ try {
189
+ current.removeEventListener("message", onMessage);
190
+ }
191
+ catch {
192
+ // Continue best-effort retirement.
193
+ }
194
+ try {
195
+ current.removeEventListener("error", onError);
196
+ }
197
+ catch {
198
+ // Continue best-effort retirement.
199
+ }
200
+ try {
201
+ current.dispose();
202
+ }
203
+ catch {
204
+ // A transport fault is already represented by the failed request.
205
+ }
206
+ }
207
+ function settle(item) {
208
+ if (item.settled)
209
+ return;
210
+ item.settled = true;
211
+ item.abortListener?.();
212
+ }
213
+ function failActive(error, retire = false) {
214
+ const current = active;
215
+ if (!current)
216
+ return;
217
+ settle(current);
218
+ current.reject(error);
219
+ active = undefined;
220
+ if (retire)
221
+ retireTransport();
222
+ void dispatch();
223
+ }
224
+ async function getTransport() {
225
+ if (transport)
226
+ return transport;
227
+ if (!workerPromise) {
228
+ const generation = workerGeneration;
229
+ workerPromise = Promise.resolve()
230
+ .then(createWorker)
231
+ .then((created) => {
232
+ const createdTransport = snapshotTransport(created);
233
+ if (disposed || generation !== workerGeneration) {
234
+ createdTransport.dispose();
235
+ throw new HonuaColumnarWorkerError(disposed ? "disposed" : "worker-failed", disposed
236
+ ? "Columnar worker session is disposed"
237
+ : "Columnar worker was retired before creation completed");
238
+ }
239
+ transport = createdTransport;
240
+ createdTransport.addEventListener("message", onMessage);
241
+ createdTransport.addEventListener("error", onError);
242
+ return createdTransport;
243
+ })
244
+ .catch((cause) => {
245
+ if (generation === workerGeneration)
246
+ workerPromise = undefined;
247
+ if (cause instanceof HonuaColumnarWorkerError)
248
+ throw cause;
249
+ throw new HonuaColumnarWorkerError("worker-failed", "Columnar worker creation failed", { cause });
250
+ });
251
+ }
252
+ return workerPromise;
253
+ }
254
+ async function dispatch() {
255
+ if (disposed || active || pendingCancel || queue.length === 0)
256
+ return;
257
+ const item = queue.shift();
258
+ if (!item || item.settled)
259
+ return void dispatch();
260
+ try {
261
+ if (item.options.signal?.isAborted()) {
262
+ settle(item);
263
+ item.reject(failure("aborted", "Columnar worker request was aborted before dispatch", item));
264
+ return void dispatch();
265
+ }
266
+ }
267
+ catch (cause) {
268
+ settle(item);
269
+ item.reject(failure("invalid-request", "Columnar worker signal failed before dispatch", item, cause));
270
+ return void dispatch();
271
+ }
272
+ active = item;
273
+ try {
274
+ const currentTransport = await getTransport();
275
+ if (disposed || active !== item || item.settled)
276
+ return;
277
+ const lease = leaseColumnarBatch(item.batch, item.options);
278
+ item.inputMetrics = inspectColumnarBatch(item.batch, item.options);
279
+ await lease.transfer((transferMessage, transfer) => {
280
+ const request = Object.freeze({
281
+ kind: COLUMNAR_WORKER_REQUEST_KIND,
282
+ version: COLUMNAR_WORKER_PROTOCOL_VERSION,
283
+ requestId: item.requestId,
284
+ operation: item.operation,
285
+ batch: transferMessage.batch,
286
+ metrics: transferMessage.metrics,
287
+ });
288
+ currentTransport.postMessage(request, transfer);
289
+ item.posted = true;
290
+ }, transferOptions(item.options));
291
+ lease.dispose();
292
+ }
293
+ catch (cause) {
294
+ if (active === item) {
295
+ const inputFailure = cause instanceof HonuaColumnarTransferError && cause.code !== "transport-failed";
296
+ failActive(failure(inputFailure ? "invalid-request" : "worker-failed", inputFailure ? "Columnar worker input batch is invalid" : "Columnar worker dispatch failed", item, cause), !inputFailure);
297
+ }
298
+ }
299
+ }
300
+ /** Limits for the dispatch handoff, plus this session's observer when it has one. */
301
+ function transferOptions(execution) {
302
+ const limits = columnarLimits(execution);
303
+ return telemetry === undefined ? limits : Object.freeze({ ...limits, telemetry });
304
+ }
305
+ function cancel(item) {
306
+ if (item.settled)
307
+ return;
308
+ const queuedIndex = queue.indexOf(item);
309
+ if (queuedIndex >= 0) {
310
+ queue.splice(queuedIndex, 1);
311
+ settle(item);
312
+ item.reject(failure("aborted", "Columnar worker request was aborted before dispatch", item));
313
+ return;
314
+ }
315
+ if (active !== item)
316
+ return;
317
+ const currentTransport = transport;
318
+ settle(item);
319
+ active = undefined;
320
+ item.reject(failure("aborted", "Columnar worker request was aborted", item));
321
+ if (!currentTransport || !item.posted) {
322
+ // The worker never received this request, so its transport is unaffected
323
+ // and the in-flight dispatch cannot post a cancelled request.
324
+ void dispatch();
325
+ return;
326
+ }
327
+ try {
328
+ currentTransport.postMessage(Object.freeze({
329
+ kind: COLUMNAR_WORKER_CANCEL_KIND,
330
+ version: COLUMNAR_WORKER_PROTOCOL_VERSION,
331
+ requestId: item.requestId,
332
+ }), []);
333
+ }
334
+ catch {
335
+ // A transport that cannot carry the cancel message cannot be trusted to
336
+ // stop the operation, so retirement remains the cancellation boundary.
337
+ retireTransport();
338
+ void dispatch();
339
+ return;
340
+ }
341
+ const timer = setTimeout(() => {
342
+ pendingCancel = undefined;
343
+ retireTransport();
344
+ void dispatch();
345
+ }, cancelAcknowledgementMs);
346
+ timer.unref?.();
347
+ pendingCancel = Object.freeze({ requestId: item.requestId, timer });
348
+ }
349
+ return {
350
+ get state() {
351
+ if (disposed)
352
+ return "disposed";
353
+ return active ? "running" : "idle";
354
+ },
355
+ get pendingRequests() {
356
+ return queue.length + (active ? 1 : 0);
357
+ },
358
+ execute(operation, batch, executeOptions = {}) {
359
+ if (disposed)
360
+ return Promise.reject(new HonuaColumnarWorkerError("disposed", "Columnar worker session is disposed"));
361
+ const normalizedOperation = identifier(operation, "operation");
362
+ let normalizedOptions;
363
+ try {
364
+ normalizedOptions = normalizeExecutionOptions(executeOptions);
365
+ }
366
+ catch (cause) {
367
+ return Promise.reject(cause instanceof HonuaColumnarWorkerError
368
+ ? cause
369
+ : new HonuaColumnarWorkerError("invalid-request", "Columnar worker options are invalid", { cause }));
370
+ }
371
+ if (queue.length + (active ? 1 : 0) >= maxPendingRequests) {
372
+ return Promise.reject(new HonuaColumnarWorkerError("queue-full", `Columnar worker queue is limited to ${maxPendingRequests} requests`, { operation: normalizedOperation }));
373
+ }
374
+ try {
375
+ if (normalizedOptions.signal?.isAborted()) {
376
+ return Promise.reject(new HonuaColumnarWorkerError("aborted", "Columnar worker request was aborted before enqueue", {
377
+ operation: normalizedOperation,
378
+ }));
379
+ }
380
+ }
381
+ catch (cause) {
382
+ return Promise.reject(new HonuaColumnarWorkerError("invalid-request", "Columnar worker signal could not be read", {
383
+ cause,
384
+ operation: normalizedOperation,
385
+ }));
386
+ }
387
+ let normalizedBatch;
388
+ try {
389
+ normalizedBatch = normalizeBatchEnvelope(batch, "worker input batch", normalizedOptions);
390
+ }
391
+ catch (cause) {
392
+ return Promise.reject(new HonuaColumnarWorkerError("invalid-request", "Columnar worker input batch is invalid", {
393
+ cause,
394
+ operation: normalizedOperation,
395
+ }));
396
+ }
397
+ if (streamOrdering === "strict") {
398
+ const drift = streamDrift(streamCursor, normalizedBatch);
399
+ if (drift !== undefined) {
400
+ return Promise.reject(new HonuaColumnarWorkerError("invalid-request", drift, { operation: normalizedOperation }));
401
+ }
402
+ }
403
+ const requestId = `columnar-${++nextRequest}`;
404
+ // Started here rather than at the top of `execute`: everything above
405
+ // refuses the request before it becomes work, and only a normalized batch
406
+ // can bind the span to an identity.
407
+ const span = telemetry
408
+ ? beginColumnarSpan(telemetry, "columnar-worker-operation", normalizedBatch.identity, {
409
+ requestId,
410
+ operation: normalizedOperation,
411
+ batchId: normalizedBatch.id,
412
+ })
413
+ : undefined;
414
+ const settlement = new Promise((resolve, reject) => {
415
+ const item = {
416
+ requestId,
417
+ operation: normalizedOperation,
418
+ batch: normalizedBatch,
419
+ options: normalizedOptions,
420
+ resolve,
421
+ reject,
422
+ lastProgress: 0,
423
+ settled: false,
424
+ posted: false,
425
+ };
426
+ if (normalizedOptions.signal) {
427
+ let ready = false;
428
+ let abortedDuringSetup = false;
429
+ const listener = () => {
430
+ if (ready)
431
+ cancel(item);
432
+ else
433
+ abortedDuringSetup = true;
434
+ };
435
+ try {
436
+ item.abortListener = normalizedOptions.signal.subscribe(listener);
437
+ abortedDuringSetup ||= normalizedOptions.signal.isAborted();
438
+ }
439
+ catch (cause) {
440
+ settle(item);
441
+ reject(failure("invalid-request", "Columnar worker signal subscription failed", item, cause));
442
+ return;
443
+ }
444
+ if (abortedDuringSetup) {
445
+ settle(item);
446
+ reject(failure("aborted", "Columnar worker request was aborted before enqueue", item));
447
+ return;
448
+ }
449
+ ready = true;
450
+ }
451
+ // The stream cursor advances on acceptance, not completion, because a
452
+ // queue holds several batches before any of them settle.
453
+ if (streamOrdering === "strict") {
454
+ streamCursor = Object.freeze({
455
+ sequence: normalizedBatch.sequence,
456
+ rowOffset: normalizedBatch.rowOffset,
457
+ rowCount: normalizedBatch.rowCount,
458
+ });
459
+ }
460
+ queue.push(item);
461
+ try {
462
+ if (normalizedOptions.signal?.isAborted())
463
+ cancel(item);
464
+ }
465
+ catch (cause) {
466
+ const queuedIndex = queue.indexOf(item);
467
+ if (queuedIndex >= 0)
468
+ queue.splice(queuedIndex, 1);
469
+ settle(item);
470
+ reject(failure("invalid-request", "Columnar worker signal failed after enqueue", item, cause));
471
+ return;
472
+ }
473
+ void dispatch();
474
+ });
475
+ if (span === undefined)
476
+ return settlement;
477
+ return settlement.then((result) => {
478
+ span.finish({ inputMetrics: result.inputMetrics, outputMetrics: result.outputMetrics });
479
+ return result;
480
+ }, (error) => {
481
+ span.fail(error, error instanceof HonuaColumnarWorkerError ? { code: error.code } : undefined);
482
+ throw error;
483
+ });
484
+ },
485
+ dispose() {
486
+ if (disposed)
487
+ return;
488
+ disposed = true;
489
+ if (active) {
490
+ const current = active;
491
+ settle(current);
492
+ current.reject(failure("disposed", "Columnar worker session was disposed", current));
493
+ active = undefined;
494
+ }
495
+ for (const item of queue.splice(0)) {
496
+ settle(item);
497
+ item.reject(failure("disposed", "Columnar worker session was disposed", item));
498
+ }
499
+ retireTransport();
500
+ },
501
+ };
502
+ }
503
+ /** Register worker-local operations on a transport without importing a worker runtime. */
504
+ export function startColumnarWorkerHost(options) {
505
+ if (typeof options !== "object" || options === null) {
506
+ throw new HonuaColumnarWorkerError("invalid-request", "Worker host options are required");
507
+ }
508
+ let foreignTransport;
509
+ let foreignOperations;
510
+ let foreignBatchLimits;
511
+ let foreignMaxActiveRequests;
512
+ try {
513
+ foreignTransport = options.transport;
514
+ foreignOperations = options.operations;
515
+ foreignBatchLimits = options.batchLimits;
516
+ foreignMaxActiveRequests = options.maxActiveRequests;
517
+ }
518
+ catch (cause) {
519
+ throw new HonuaColumnarWorkerError("invalid-request", "Worker host options could not be read", { cause });
520
+ }
521
+ const transport = snapshotTransport(foreignTransport);
522
+ const batchLimits = snapshotColumnarLimits(foreignBatchLimits);
523
+ let operations;
524
+ try {
525
+ operations = normalizeOperations(foreignOperations);
526
+ }
527
+ catch (cause) {
528
+ if (cause instanceof HonuaColumnarWorkerError)
529
+ throw cause;
530
+ throw new HonuaColumnarWorkerError("invalid-request", "Worker operations are invalid", { cause });
531
+ }
532
+ const maxActiveRequests = positiveInteger(foreignMaxActiveRequests ?? 1, "maxActiveRequests");
533
+ const active = new Map();
534
+ let disposed = false;
535
+ const sendError = (requestId, code, message) => {
536
+ if (disposed)
537
+ return;
538
+ try {
539
+ transport.postMessage(Object.freeze({
540
+ kind: COLUMNAR_WORKER_ERROR_KIND,
541
+ version: COLUMNAR_WORKER_PROTOCOL_VERSION,
542
+ requestId,
543
+ code,
544
+ message,
545
+ }), []);
546
+ }
547
+ catch {
548
+ // Delivery failure is terminal for this response but never unhandled.
549
+ }
550
+ };
551
+ const run = async (foreign) => {
552
+ let request;
553
+ try {
554
+ request = normalizeWorkerRequest(foreign, batchLimits);
555
+ }
556
+ catch (cause) {
557
+ const requestId = safeRequestId(foreign) ?? "invalid-request";
558
+ sendError(requestId, "invalid-request", messageFrom(cause, "Invalid columnar worker request"));
559
+ return;
560
+ }
561
+ if (active.has(request.requestId)) {
562
+ sendError(request.requestId, "invalid-request", "Duplicate active request id");
563
+ return;
564
+ }
565
+ if (active.size >= maxActiveRequests) {
566
+ sendError(request.requestId, "invalid-request", `Worker host is limited to ${maxActiveRequests} active requests`);
567
+ return;
568
+ }
569
+ const operation = operations[request.operation];
570
+ if (!operation) {
571
+ sendError(request.requestId, "unknown-operation", `Unknown columnar worker operation: ${request.operation}`);
572
+ return;
573
+ }
574
+ const controller = new AbortController();
575
+ active.set(request.requestId, controller);
576
+ let progress = 0;
577
+ try {
578
+ const output = await operation(request.batch, {
579
+ requestId: request.requestId,
580
+ signal: controller.signal,
581
+ reportProgress(fraction, stage) {
582
+ if (disposed || controller.signal.aborted)
583
+ return;
584
+ const normalized = progressFraction(fraction);
585
+ if (normalized < progress) {
586
+ throw new HonuaColumnarWorkerError("operation-failed", "Operation progress must be monotonic");
587
+ }
588
+ progress = normalized;
589
+ transport.postMessage(Object.freeze({
590
+ kind: COLUMNAR_WORKER_PROGRESS_KIND,
591
+ version: COLUMNAR_WORKER_PROTOCOL_VERSION,
592
+ requestId: request.requestId,
593
+ fraction: normalized,
594
+ ...(stage === undefined ? {} : { stage: identifier(stage, "progress stage") }),
595
+ }), []);
596
+ },
597
+ });
598
+ if (controller.signal.aborted || disposed) {
599
+ sendError(request.requestId, "aborted", "Columnar worker operation was aborted");
600
+ return;
601
+ }
602
+ const batch = normalizeBatchEnvelope(output, "operation result batch", batchLimits);
603
+ const lease = leaseColumnarBatch(batch, batchLimits);
604
+ await lease.transfer((transferMessage, transfer) => {
605
+ transport.postMessage(Object.freeze({
606
+ kind: COLUMNAR_WORKER_RESULT_KIND,
607
+ version: COLUMNAR_WORKER_PROTOCOL_VERSION,
608
+ requestId: request.requestId,
609
+ batch: transferMessage.batch,
610
+ metrics: transferMessage.metrics,
611
+ }), transfer);
612
+ });
613
+ lease.dispose();
614
+ }
615
+ catch {
616
+ if (controller.signal.aborted)
617
+ sendError(request.requestId, "aborted", "Columnar worker operation was aborted");
618
+ else
619
+ sendError(request.requestId, "operation-failed", "Columnar worker operation failed");
620
+ }
621
+ finally {
622
+ active.delete(request.requestId);
623
+ }
624
+ };
625
+ const onMessage = (event) => {
626
+ if (disposed)
627
+ return;
628
+ let data;
629
+ try {
630
+ data = event.data;
631
+ }
632
+ catch {
633
+ return;
634
+ }
635
+ if (isCancel(data)) {
636
+ active.get(data.requestId)?.abort();
637
+ return;
638
+ }
639
+ void run(data).catch(() => {
640
+ // The host contains unexpected protocol-handler failures.
641
+ });
642
+ };
643
+ const onError = () => {
644
+ for (const controller of active.values())
645
+ controller.abort();
646
+ };
647
+ try {
648
+ transport.addEventListener("message", onMessage);
649
+ transport.addEventListener("error", onError);
650
+ }
651
+ catch (cause) {
652
+ try {
653
+ transport.removeEventListener("message", onMessage);
654
+ }
655
+ catch {
656
+ // Continue registration rollback.
657
+ }
658
+ try {
659
+ transport.removeEventListener("error", onError);
660
+ }
661
+ catch {
662
+ // Continue registration rollback.
663
+ }
664
+ try {
665
+ transport.dispose();
666
+ }
667
+ catch {
668
+ // The registration failure remains the primary cause.
669
+ }
670
+ throw new HonuaColumnarWorkerError("worker-failed", "Worker host listener registration failed", { cause });
671
+ }
672
+ return {
673
+ get activeRequests() {
674
+ return active.size;
675
+ },
676
+ get disposed() {
677
+ return disposed;
678
+ },
679
+ dispose() {
680
+ if (disposed)
681
+ return;
682
+ disposed = true;
683
+ const failures = [];
684
+ try {
685
+ transport.removeEventListener("message", onMessage);
686
+ }
687
+ catch (cause) {
688
+ failures.push(cause);
689
+ }
690
+ try {
691
+ transport.removeEventListener("error", onError);
692
+ }
693
+ catch (cause) {
694
+ failures.push(cause);
695
+ }
696
+ for (const controller of active.values())
697
+ controller.abort();
698
+ active.clear();
699
+ try {
700
+ transport.dispose();
701
+ }
702
+ catch (cause) {
703
+ failures.push(cause);
704
+ }
705
+ if (failures.length > 0) {
706
+ throw new HonuaColumnarWorkerError("worker-failed", "Worker host disposal completed with transport failures", {
707
+ cause: new AggregateError(failures),
708
+ });
709
+ }
710
+ },
711
+ };
712
+ }
713
+ function normalizeWorkerRequest(foreign, limits = {}) {
714
+ const value = record(foreign, "worker request");
715
+ if (value.kind !== COLUMNAR_WORKER_REQUEST_KIND || value.version !== COLUMNAR_WORKER_PROTOCOL_VERSION) {
716
+ throw new HonuaColumnarWorkerError("invalid-request", `Worker request must be ${COLUMNAR_WORKER_REQUEST_KIND}@${COLUMNAR_WORKER_PROTOCOL_VERSION}`);
717
+ }
718
+ const requestId = identifier(value.requestId, "requestId");
719
+ const operation = identifier(value.operation, "operation");
720
+ const batch = normalizeBatchEnvelope(value.batch, "worker request batch", limits);
721
+ const metrics = inspectColumnarBatch(batch, limits);
722
+ assertMetrics(value.metrics, metrics, "request");
723
+ return Object.freeze({
724
+ kind: COLUMNAR_WORKER_REQUEST_KIND,
725
+ version: COLUMNAR_WORKER_PROTOCOL_VERSION,
726
+ requestId,
727
+ operation,
728
+ batch,
729
+ metrics,
730
+ });
731
+ }
732
+ function normalizeWorkerResponse(foreign, expectedRequestId) {
733
+ const value = record(foreign, "worker response");
734
+ if (value.version !== COLUMNAR_WORKER_PROTOCOL_VERSION) {
735
+ throw new HonuaColumnarWorkerError("invalid-response", "Worker response protocol version is not supported");
736
+ }
737
+ const requestId = identifier(value.requestId, "requestId");
738
+ if (requestId !== expectedRequestId) {
739
+ // Valid late/duplicate messages are ignored by the caller; malformed ones fail closed.
740
+ if (![COLUMNAR_WORKER_PROGRESS_KIND, COLUMNAR_WORKER_RESULT_KIND, COLUMNAR_WORKER_ERROR_KIND].includes(value.kind)) {
741
+ throw new HonuaColumnarWorkerError("invalid-response", "Worker response kind is not supported");
742
+ }
743
+ return Object.freeze({
744
+ kind: COLUMNAR_WORKER_PROGRESS_KIND,
745
+ version: COLUMNAR_WORKER_PROTOCOL_VERSION,
746
+ requestId,
747
+ fraction: 0,
748
+ });
749
+ }
750
+ if (value.kind === COLUMNAR_WORKER_PROGRESS_KIND) {
751
+ const fraction = progressFraction(value.fraction);
752
+ const stage = value.stage === undefined ? undefined : identifier(value.stage, "progress stage");
753
+ return Object.freeze({
754
+ kind: value.kind,
755
+ version: COLUMNAR_WORKER_PROTOCOL_VERSION,
756
+ requestId,
757
+ fraction,
758
+ ...(stage === undefined ? {} : { stage }),
759
+ });
760
+ }
761
+ if (value.kind === COLUMNAR_WORKER_ERROR_KIND) {
762
+ const code = value.code;
763
+ if (!["invalid-request", "unknown-operation", "aborted", "operation-failed"].includes(code)) {
764
+ throw new HonuaColumnarWorkerError("invalid-response", "Worker error code is not supported");
765
+ }
766
+ return Object.freeze({
767
+ kind: value.kind,
768
+ version: COLUMNAR_WORKER_PROTOCOL_VERSION,
769
+ requestId,
770
+ code: code,
771
+ message: messageText(value.message, "error message"),
772
+ });
773
+ }
774
+ if (value.kind === COLUMNAR_WORKER_RESULT_KIND) {
775
+ return Object.freeze({
776
+ kind: value.kind,
777
+ version: COLUMNAR_WORKER_PROTOCOL_VERSION,
778
+ requestId,
779
+ batch: value.batch,
780
+ metrics: value.metrics,
781
+ });
782
+ }
783
+ throw new HonuaColumnarWorkerError("invalid-response", "Worker response kind is not supported");
784
+ }
785
+ function isCancel(foreign) {
786
+ try {
787
+ if (typeof foreign !== "object" || foreign === null)
788
+ return false;
789
+ const value = foreign;
790
+ return (value.kind === COLUMNAR_WORKER_CANCEL_KIND &&
791
+ value.version === COLUMNAR_WORKER_PROTOCOL_VERSION &&
792
+ typeof value.requestId === "string");
793
+ }
794
+ catch {
795
+ return false;
796
+ }
797
+ }
798
+ function normalizeOperations(foreign) {
799
+ const value = record(foreign, "operations");
800
+ const result = Object.create(null);
801
+ const keys = Object.keys(value);
802
+ if (keys.length === 0 || keys.length > 64)
803
+ throw new HonuaColumnarWorkerError("invalid-request", "operations must contain between 1 and 64 entries");
804
+ for (const key of keys) {
805
+ const name = identifier(key, "operation name");
806
+ const operation = value[key];
807
+ if (typeof operation !== "function")
808
+ throw new HonuaColumnarWorkerError("invalid-request", `Operation ${name} must be a function`);
809
+ result[name] = operation;
810
+ }
811
+ return Object.freeze(result);
812
+ }
813
+ function normalizeBatchEnvelope(foreign, label, limits = {}) {
814
+ const value = record(foreign, label);
815
+ if (value.kind !== COLUMNAR_BATCH_KIND || value.version !== COLUMNAR_BATCH_VERSION) {
816
+ throw new HonuaColumnarWorkerError("invalid-request", `${label} must be ${COLUMNAR_BATCH_KIND}@${COLUMNAR_BATCH_VERSION}`);
817
+ }
818
+ return createColumnarBatch(value, limits);
819
+ }
820
+ function normalizeExecutionOptions(foreign) {
821
+ if (typeof foreign !== "object" || foreign === null) {
822
+ throw new HonuaColumnarWorkerError("invalid-request", "Worker execution options must be an object");
823
+ }
824
+ const foreignSignal = foreign.signal;
825
+ const onProgress = foreign.onProgress;
826
+ const maxRows = foreign.maxRows;
827
+ const maxBackingBytes = foreign.maxBackingBytes;
828
+ const maxSchemaNodes = foreign.maxSchemaNodes;
829
+ const maxMetadataEntries = foreign.maxMetadataEntries;
830
+ const maxBufferViews = foreign.maxBufferViews;
831
+ const maxStringBytes = foreign.maxStringBytes;
832
+ if (onProgress !== undefined && typeof onProgress !== "function") {
833
+ throw new HonuaColumnarWorkerError("invalid-request", "onProgress must be a function");
834
+ }
835
+ const signal = foreignSignal === undefined ? undefined : normalizeAbortSignal(foreignSignal);
836
+ return Object.freeze({
837
+ ...(signal === undefined ? {} : { signal }),
838
+ ...(onProgress === undefined ? {} : { onProgress }),
839
+ ...(maxRows === undefined ? {} : { maxRows }),
840
+ ...(maxBackingBytes === undefined ? {} : { maxBackingBytes }),
841
+ ...(maxSchemaNodes === undefined ? {} : { maxSchemaNodes }),
842
+ ...(maxMetadataEntries === undefined ? {} : { maxMetadataEntries }),
843
+ ...(maxBufferViews === undefined ? {} : { maxBufferViews }),
844
+ ...(maxStringBytes === undefined ? {} : { maxStringBytes }),
845
+ });
846
+ }
847
+ function normalizeAbortSignal(foreign) {
848
+ if (typeof foreign !== "object" || foreign === null) {
849
+ throw new HonuaColumnarWorkerError("invalid-request", "signal must be an AbortSignal-like object");
850
+ }
851
+ let add;
852
+ let remove;
853
+ try {
854
+ add = foreign.addEventListener;
855
+ remove = foreign.removeEventListener;
856
+ }
857
+ catch (cause) {
858
+ throw new HonuaColumnarWorkerError("invalid-request", "signal methods could not be read", { cause });
859
+ }
860
+ if (typeof add !== "function" || typeof remove !== "function") {
861
+ throw new HonuaColumnarWorkerError("invalid-request", "signal must provide event listener methods");
862
+ }
863
+ return Object.freeze({
864
+ isAborted() {
865
+ let aborted;
866
+ try {
867
+ aborted = foreign.aborted;
868
+ }
869
+ catch (cause) {
870
+ throw new HonuaColumnarWorkerError("invalid-request", "signal.aborted could not be read", { cause });
871
+ }
872
+ if (typeof aborted !== "boolean") {
873
+ throw new HonuaColumnarWorkerError("invalid-request", "signal.aborted must be a boolean");
874
+ }
875
+ return aborted;
876
+ },
877
+ subscribe(listener) {
878
+ try {
879
+ add.call(foreign, "abort", listener, { once: true });
880
+ }
881
+ catch (cause) {
882
+ throw new HonuaColumnarWorkerError("invalid-request", "signal abort listener could not be installed", {
883
+ cause,
884
+ });
885
+ }
886
+ return () => {
887
+ try {
888
+ remove.call(foreign, "abort", listener);
889
+ }
890
+ catch {
891
+ // A foreign signal cannot prevent request settlement.
892
+ }
893
+ };
894
+ },
895
+ });
896
+ }
897
+ function columnarLimits(options) {
898
+ return Object.freeze({
899
+ ...(options.maxRows === undefined ? {} : { maxRows: options.maxRows }),
900
+ ...(options.maxBackingBytes === undefined ? {} : { maxBackingBytes: options.maxBackingBytes }),
901
+ ...(options.maxSchemaNodes === undefined ? {} : { maxSchemaNodes: options.maxSchemaNodes }),
902
+ ...(options.maxMetadataEntries === undefined ? {} : { maxMetadataEntries: options.maxMetadataEntries }),
903
+ ...(options.maxBufferViews === undefined ? {} : { maxBufferViews: options.maxBufferViews }),
904
+ ...(options.maxStringBytes === undefined ? {} : { maxStringBytes: options.maxStringBytes }),
905
+ });
906
+ }
907
+ function snapshotColumnarLimits(foreign) {
908
+ if (foreign === undefined)
909
+ return Object.freeze({});
910
+ if (typeof foreign !== "object" || foreign === null) {
911
+ throw new HonuaColumnarWorkerError("invalid-request", "batchLimits must be an object");
912
+ }
913
+ try {
914
+ return columnarLimits({
915
+ maxRows: foreign.maxRows,
916
+ maxBackingBytes: foreign.maxBackingBytes,
917
+ maxSchemaNodes: foreign.maxSchemaNodes,
918
+ maxMetadataEntries: foreign.maxMetadataEntries,
919
+ maxBufferViews: foreign.maxBufferViews,
920
+ maxStringBytes: foreign.maxStringBytes,
921
+ });
922
+ }
923
+ catch (cause) {
924
+ throw new HonuaColumnarWorkerError("invalid-request", "batchLimits could not be read", { cause });
925
+ }
926
+ }
927
+ function snapshotTransport(value) {
928
+ const transport = record(value, "worker transport");
929
+ let postMessage;
930
+ let addEventListener;
931
+ let removeEventListener;
932
+ let dispose;
933
+ try {
934
+ postMessage = transport.postMessage;
935
+ addEventListener = transport.addEventListener;
936
+ removeEventListener = transport.removeEventListener;
937
+ dispose = transport.dispose;
938
+ }
939
+ catch (cause) {
940
+ throw new HonuaColumnarWorkerError("worker-failed", "Worker transport methods could not be read", { cause });
941
+ }
942
+ if (typeof postMessage !== "function" ||
943
+ typeof addEventListener !== "function" ||
944
+ typeof removeEventListener !== "function" ||
945
+ typeof dispose !== "function") {
946
+ throw new HonuaColumnarWorkerError("worker-failed", "Worker transport methods must be functions");
947
+ }
948
+ return Object.freeze({
949
+ postMessage(message, transfer) {
950
+ Reflect.apply(postMessage, value, [message, transfer]);
951
+ },
952
+ addEventListener(type, listener) {
953
+ Reflect.apply(addEventListener, value, [type, listener]);
954
+ },
955
+ removeEventListener(type, listener) {
956
+ Reflect.apply(removeEventListener, value, [type, listener]);
957
+ },
958
+ dispose() {
959
+ Reflect.apply(dispose, value, []);
960
+ },
961
+ });
962
+ }
963
+ function assertMetrics(foreign, expected, label) {
964
+ const value = record(foreign, `${label} metrics`);
965
+ for (const key of [
966
+ "rows",
967
+ "logicalBytes",
968
+ "backingBytes",
969
+ "transferBytes",
970
+ "copiedBytes",
971
+ "bufferViews",
972
+ "backingBuffers",
973
+ ]) {
974
+ if (value[key] !== expected[key])
975
+ throw new HonuaColumnarWorkerError("invalid-response", `${label} metrics.${key} does not match the batch`);
976
+ }
977
+ }
978
+ function record(value, label) {
979
+ if (typeof value !== "object" || value === null || Array.isArray(value))
980
+ throw new HonuaColumnarWorkerError("invalid-request", `${label} must be an object`);
981
+ return value;
982
+ }
983
+ function identifier(value, label) {
984
+ if (typeof value !== "string" ||
985
+ value.length === 0 ||
986
+ value.length > MAX_IDENTIFIER_LENGTH ||
987
+ value !== value.trim()) {
988
+ throw new HonuaColumnarWorkerError("invalid-request", `${label} must be a non-empty trimmed string of at most ${MAX_IDENTIFIER_LENGTH} characters`);
989
+ }
990
+ return value;
991
+ }
992
+ function messageText(value, label) {
993
+ if (typeof value !== "string" || value.length === 0 || value.length > 512 || value !== value.trim()) {
994
+ throw new HonuaColumnarWorkerError("invalid-request", `${label} must be a non-empty trimmed string of at most 512 characters`);
995
+ }
996
+ return value;
997
+ }
998
+ function positiveInteger(value, label) {
999
+ if (!Number.isSafeInteger(value) || value <= 0)
1000
+ throw new HonuaColumnarWorkerError("invalid-request", `${label} must be a positive safe integer`);
1001
+ return value;
1002
+ }
1003
+ function nonNegativeInteger(value, label) {
1004
+ if (!Number.isSafeInteger(value) || value < 0)
1005
+ throw new HonuaColumnarWorkerError("invalid-request", `${label} must be a non-negative safe integer`);
1006
+ return value;
1007
+ }
1008
+ function normalizeStreamOrdering(value) {
1009
+ if (value === undefined)
1010
+ return "none";
1011
+ if (value !== "none" && value !== "strict")
1012
+ throw new HonuaColumnarWorkerError("invalid-request", "streamOrdering must be none or strict");
1013
+ return value;
1014
+ }
1015
+ /**
1016
+ * Describe how a batch drifts from a `strict` ordered stream, or `undefined`
1017
+ * when it continues the stream. Drift is a caller contract violation, so it is
1018
+ * reported before the batch is transferred and the caller keeps its buffers.
1019
+ */
1020
+ function streamDrift(cursor, batch) {
1021
+ if (!cursor)
1022
+ return undefined;
1023
+ if (batch.sequence <= cursor.sequence) {
1024
+ return `Columnar stream sequence must increase; received ${batch.sequence} after ${cursor.sequence}`;
1025
+ }
1026
+ const previous = cursor.rowOffset;
1027
+ const next = batch.rowOffset;
1028
+ if ((previous === undefined) !== (next === undefined)) {
1029
+ return "Columnar stream rowOffset must be declared by every batch or by none";
1030
+ }
1031
+ if (previous !== undefined && next !== undefined && next !== previous + cursor.rowCount) {
1032
+ return `Columnar stream rowOffset must be contiguous; expected ${previous + cursor.rowCount} but received ${next}`;
1033
+ }
1034
+ return undefined;
1035
+ }
1036
+ function progressFraction(value) {
1037
+ if (typeof value !== "number" || !Number.isFinite(value) || value < 0 || value > 1)
1038
+ throw new HonuaColumnarWorkerError("invalid-request", "progress fraction must be between 0 and 1");
1039
+ return value;
1040
+ }
1041
+ function safeRequestId(foreign) {
1042
+ if (typeof foreign !== "object" || foreign === null)
1043
+ return undefined;
1044
+ try {
1045
+ const value = foreign.requestId;
1046
+ return typeof value === "string" && value.length > 0 && value.length <= MAX_IDENTIFIER_LENGTH ? value : undefined;
1047
+ }
1048
+ catch {
1049
+ return undefined;
1050
+ }
1051
+ }
1052
+ function messageFrom(cause, fallback) {
1053
+ if (cause instanceof Error && cause.message.trim())
1054
+ return cause.message.slice(0, 512);
1055
+ return fallback;
1056
+ }
1057
+ function failure(code, message, item, cause) {
1058
+ return new HonuaColumnarWorkerError(code, message, { cause, requestId: item.requestId, operation: item.operation });
1059
+ }