@smoothbricks/lmao 0.2.2 → 0.3.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 (285) hide show
  1. package/README.md +206 -206
  2. package/dist/allocator.wasm +0 -0
  3. package/dist/index.d.ts +1 -0
  4. package/dist/index.d.ts.map +1 -1
  5. package/dist/index.js +1 -0
  6. package/dist/lib/JsBufferStrategy.d.ts +4 -3
  7. package/dist/lib/JsBufferStrategy.d.ts.map +1 -1
  8. package/dist/lib/JsBufferStrategy.js +18 -11
  9. package/dist/lib/arrow/lease.d.ts +10 -0
  10. package/dist/lib/arrow/lease.d.ts.map +1 -0
  11. package/dist/lib/arrow/lease.js +33 -0
  12. package/dist/lib/arrow/utils.d.ts +3 -4
  13. package/dist/lib/arrow/utils.d.ts.map +1 -1
  14. package/dist/lib/arrow/utils.js +12 -13
  15. package/dist/lib/arrow/vocabularyDictionary.d.ts +21 -0
  16. package/dist/lib/arrow/vocabularyDictionary.d.ts.map +1 -0
  17. package/dist/lib/arrow/vocabularyDictionary.js +95 -0
  18. package/dist/lib/bufferStrategy.d.ts +3 -2
  19. package/dist/lib/bufferStrategy.d.ts.map +1 -1
  20. package/dist/lib/cloudflare/traceRows.d.ts.map +1 -1
  21. package/dist/lib/cloudflare/traceRows.js +3 -2
  22. package/dist/lib/codegen/evaluatorGenerator.d.ts.map +1 -1
  23. package/dist/lib/codegen/evaluatorGenerator.js +12 -11
  24. package/dist/lib/codegen/fixedPositionWriterGenerator.d.ts +44 -82
  25. package/dist/lib/codegen/fixedPositionWriterGenerator.d.ts.map +1 -1
  26. package/dist/lib/codegen/fixedPositionWriterGenerator.js +223 -162
  27. package/dist/lib/codegen/spanLoggerClosureMaterializer.d.ts +63 -0
  28. package/dist/lib/codegen/spanLoggerClosureMaterializer.d.ts.map +1 -0
  29. package/dist/lib/codegen/spanLoggerClosureMaterializer.js +447 -0
  30. package/dist/lib/codegen/spanLoggerGenerator.d.ts +36 -19
  31. package/dist/lib/codegen/spanLoggerGenerator.d.ts.map +1 -1
  32. package/dist/lib/codegen/spanLoggerGenerator.js +164 -158
  33. package/dist/lib/convertToArrow.d.ts +7 -2
  34. package/dist/lib/convertToArrow.d.ts.map +1 -1
  35. package/dist/lib/convertToArrow.js +499 -285
  36. package/dist/lib/defineOpContext.js +1 -1
  37. package/dist/lib/enumMetadata.d.ts +19 -0
  38. package/dist/lib/enumMetadata.d.ts.map +1 -0
  39. package/dist/lib/enumMetadata.js +86 -0
  40. package/dist/lib/flushScheduler.d.ts.map +1 -1
  41. package/dist/lib/flushScheduler.js +13 -4
  42. package/dist/lib/library.d.ts +16 -98
  43. package/dist/lib/library.d.ts.map +1 -1
  44. package/dist/lib/library.js +32 -525
  45. package/dist/lib/logBinding.d.ts +21 -27
  46. package/dist/lib/logBinding.d.ts.map +1 -1
  47. package/dist/lib/op.d.ts +30 -30
  48. package/dist/lib/op.d.ts.map +1 -1
  49. package/dist/lib/op.js +15 -22
  50. package/dist/lib/opContext/createOpGroup.d.ts +1 -1
  51. package/dist/lib/opContext/createOpGroup.d.ts.map +1 -1
  52. package/dist/lib/opContext/createOpGroup.js +22 -29
  53. package/dist/lib/opContext/defineOp.d.ts +4 -4
  54. package/dist/lib/opContext/defineOp.d.ts.map +1 -1
  55. package/dist/lib/opContext/defineOp.js +53 -10
  56. package/dist/lib/opContext/opGroupTypes.d.ts +2 -2
  57. package/dist/lib/opContext/opTypes.d.ts +1 -1
  58. package/dist/lib/opContext/opTypes.d.ts.map +1 -1
  59. package/dist/lib/opContext/spanContextTypes.d.ts +33 -18
  60. package/dist/lib/opContext/spanContextTypes.d.ts.map +1 -1
  61. package/dist/lib/opContext/types.d.ts +4 -4
  62. package/dist/lib/opContext/types.d.ts.map +1 -1
  63. package/dist/lib/physicalLayoutPlan.d.ts +89 -0
  64. package/dist/lib/physicalLayoutPlan.d.ts.map +1 -0
  65. package/dist/lib/physicalLayoutPlan.js +442 -0
  66. package/dist/lib/resolveMessage.d.ts +9 -0
  67. package/dist/lib/resolveMessage.d.ts.map +1 -0
  68. package/dist/lib/resolveMessage.js +63 -0
  69. package/dist/lib/result.d.ts +5 -7
  70. package/dist/lib/result.d.ts.map +1 -1
  71. package/dist/lib/result.js +19 -26
  72. package/dist/lib/runtimeHint.d.ts +35 -0
  73. package/dist/lib/runtimeHint.d.ts.map +1 -0
  74. package/dist/lib/runtimeHint.js +79 -0
  75. package/dist/lib/schema/typeGuards.d.ts +7 -47
  76. package/dist/lib/schema/typeGuards.d.ts.map +1 -1
  77. package/dist/lib/schema/typeGuards.js +7 -123
  78. package/dist/lib/schema/types.d.ts +1 -1
  79. package/dist/lib/schema/types.d.ts.map +1 -1
  80. package/dist/lib/span-buffer/aot/v1.d.ts +2 -0
  81. package/dist/lib/span-buffer/aot/v1.d.ts.map +1 -0
  82. package/dist/lib/span-buffer/aot/v1.js +24 -0
  83. package/dist/lib/spanBuffer.d.ts +33 -4
  84. package/dist/lib/spanBuffer.d.ts.map +1 -1
  85. package/dist/lib/spanBuffer.js +583 -91
  86. package/dist/lib/spanBufferStats.d.ts +1 -1
  87. package/dist/lib/spanBufferStats.d.ts.map +1 -1
  88. package/dist/lib/spanContext.d.ts +27 -51
  89. package/dist/lib/spanContext.d.ts.map +1 -1
  90. package/dist/lib/spanContext.js +822 -344
  91. package/dist/lib/sqlite/sqlite-common.d.ts.map +1 -1
  92. package/dist/lib/sqlite/sqlite-common.js +20 -21
  93. package/dist/lib/testing/bun-harness.d.ts +8 -3
  94. package/dist/lib/testing/bun-harness.d.ts.map +1 -1
  95. package/dist/lib/testing/bun-harness.js +30 -18
  96. package/dist/lib/testing/extractFacts.d.ts +1 -1
  97. package/dist/lib/testing/extractFacts.d.ts.map +1 -1
  98. package/dist/lib/testing/extractFacts.js +55 -63
  99. package/dist/lib/testing/facts.d.ts +1 -1
  100. package/dist/lib/testing/facts.d.ts.map +1 -1
  101. package/dist/lib/testing/queryable-span.d.ts +1 -1
  102. package/dist/lib/testing/queryable-span.d.ts.map +1 -1
  103. package/dist/lib/testing/queryable-span.js +26 -28
  104. package/dist/lib/testing/span-query.d.ts.map +1 -1
  105. package/dist/lib/testing/span-query.js +19 -22
  106. package/dist/lib/testing/stdio-replay.d.ts.map +1 -1
  107. package/dist/lib/testing/stdio-replay.js +19 -8
  108. package/dist/lib/threadId.d.ts.map +1 -1
  109. package/dist/lib/threadId.js +6 -3
  110. package/dist/lib/traceContext.d.ts.map +1 -1
  111. package/dist/lib/traceContext.js +1 -4
  112. package/dist/lib/traceRoot.d.ts +30 -8
  113. package/dist/lib/traceRoot.d.ts.map +1 -1
  114. package/dist/lib/traceRoot.es.d.ts +17 -15
  115. package/dist/lib/traceRoot.es.d.ts.map +1 -1
  116. package/dist/lib/traceRoot.es.js +68 -34
  117. package/dist/lib/traceRoot.js +11 -0
  118. package/dist/lib/traceRoot.node.d.ts +17 -19
  119. package/dist/lib/traceRoot.node.d.ts.map +1 -1
  120. package/dist/lib/traceRoot.node.js +66 -38
  121. package/dist/lib/traceTopology.d.ts +42 -0
  122. package/dist/lib/traceTopology.d.ts.map +1 -0
  123. package/dist/lib/traceTopology.js +260 -0
  124. package/dist/lib/tracer.d.ts +9 -5
  125. package/dist/lib/tracer.d.ts.map +1 -1
  126. package/dist/lib/tracer.js +65 -71
  127. package/dist/lib/tracers/ArrayQueueTracer.d.ts +1 -1
  128. package/dist/lib/tracers/ArrayQueueTracer.js +1 -1
  129. package/dist/lib/tracers/StdioTracer.d.ts.map +1 -1
  130. package/dist/lib/tracers/StdioTracer.js +15 -13
  131. package/dist/lib/tracers/TestTracer.d.ts +4 -6
  132. package/dist/lib/tracers/TestTracer.d.ts.map +1 -1
  133. package/dist/lib/tracers/TestTracer.js +6 -8
  134. package/dist/lib/types.d.ts +49 -32
  135. package/dist/lib/types.d.ts.map +1 -1
  136. package/dist/lib/vocabularyRegistry.d.ts +25 -0
  137. package/dist/lib/vocabularyRegistry.d.ts.map +1 -0
  138. package/dist/lib/vocabularyRegistry.js +372 -0
  139. package/dist/lib/wasm/WasmBufferStrategy.d.ts +6 -10
  140. package/dist/lib/wasm/WasmBufferStrategy.d.ts.map +1 -1
  141. package/dist/lib/wasm/WasmBufferStrategy.js +44 -37
  142. package/dist/lib/wasm/wasmAllocator.d.ts +27 -22
  143. package/dist/lib/wasm/wasmAllocator.d.ts.map +1 -1
  144. package/dist/lib/wasm/wasmAllocator.js +66 -44
  145. package/dist/lib/wasm/wasmPhysicalLayout.d.ts +67 -0
  146. package/dist/lib/wasm/wasmPhysicalLayout.d.ts.map +1 -0
  147. package/dist/lib/wasm/wasmPhysicalLayout.js +179 -0
  148. package/dist/lib/wasm/wasmSpanBuffer.d.ts +79 -31
  149. package/dist/lib/wasm/wasmSpanBuffer.d.ts.map +1 -1
  150. package/dist/lib/wasm/wasmSpanBuffer.js +442 -272
  151. package/dist/lib/wasm/wasmTraceRoot.d.ts +24 -22
  152. package/dist/lib/wasm/wasmTraceRoot.d.ts.map +1 -1
  153. package/dist/lib/wasm/wasmTraceRoot.js +110 -71
  154. package/dist/vocabulary/register/v1.d.ts +2 -0
  155. package/dist/vocabulary/register/v1.d.ts.map +1 -0
  156. package/dist/vocabulary/register/v1.js +1 -0
  157. package/dist/wasm.d.ts +5 -0
  158. package/dist/wasm.d.ts.map +1 -0
  159. package/dist/wasm.js +4 -0
  160. package/package.json +54 -6
  161. package/src/index.ts +1 -0
  162. package/src/lib/JsBufferStrategy.ts +27 -9
  163. package/src/lib/__tests__/arrow-builder-integration/arrow-conversion.test.ts +335 -37
  164. package/src/lib/__tests__/arrow-builder-integration/binary-arrow-conversion.test.ts +97 -26
  165. package/src/lib/__tests__/arrow-builder-integration/binary-comparison.test.ts +75 -15
  166. package/src/lib/__tests__/arrow-builder-integration/binary-format-compliance.test.ts +30 -6
  167. package/src/lib/__tests__/arrow-builder-integration/buffer-chaining.test.ts +18 -21
  168. package/src/lib/__tests__/arrow-builder-integration/foundation.test.ts +5 -2
  169. package/src/lib/__tests__/arrow-builder-integration/integration.test.ts +25 -7
  170. package/src/lib/__tests__/arrow-builder-integration/lazy-columns.test.ts +3 -2
  171. package/src/lib/__tests__/buffer-overflow.test.ts +24 -18
  172. package/src/lib/__tests__/capability-span-context.test.ts +505 -0
  173. package/src/lib/__tests__/capacity-tuning.test.ts +5 -3
  174. package/src/lib/__tests__/eager-columns.test.ts +345 -0
  175. package/src/lib/__tests__/enum-metadata-aot.test.ts +153 -0
  176. package/src/lib/__tests__/enum-metadata.test.ts +177 -0
  177. package/src/lib/__tests__/fixtures/empty-vocabulary-runtime.fixture.ts +15 -0
  178. package/src/lib/__tests__/flushScheduler.test.ts +5 -1
  179. package/src/lib/__tests__/identity-view.property.test.ts +179 -0
  180. package/src/lib/__tests__/library.test.ts +19 -235
  181. package/src/lib/__tests__/message-buffer-families.test.ts +878 -0
  182. package/src/lib/__tests__/nested-library-tasks.test.ts +14 -15
  183. package/src/lib/__tests__/op-metadata.test.ts +1 -1
  184. package/src/lib/__tests__/op-type-safety.test.ts +5 -16
  185. package/src/lib/__tests__/physical-layout-plan.test.ts +333 -0
  186. package/src/lib/__tests__/remap-descriptor-integration.test.ts +381 -0
  187. package/src/lib/__tests__/root-context-construction.test.ts +414 -0
  188. package/src/lib/__tests__/span-auto-runtime.test.ts +386 -0
  189. package/src/lib/__tests__/span-buffer-segment-stats.test.ts +221 -0
  190. package/src/lib/__tests__/span-lifecycle.test.ts +53 -25
  191. package/src/lib/__tests__/spanbuffer-materializer-parity.test.ts +266 -0
  192. package/src/lib/__tests__/sync-async-trace.test.ts +24 -23
  193. package/src/lib/__tests__/tag-writer-aot.test.ts +214 -0
  194. package/src/lib/__tests__/test-helpers.ts +67 -12
  195. package/src/lib/__tests__/threadId.test.ts +62 -0
  196. package/src/lib/__tests__/timestamp.test.ts +221 -93
  197. package/src/lib/__tests__/traceTopology.property.test.ts +383 -0
  198. package/src/lib/__tests__/type-narrowing.test.ts +63 -0
  199. package/src/lib/__tests__/vocabulary-dense-decoding.test.ts +405 -0
  200. package/src/lib/__tests__/vocabularyRegistry.test.ts +313 -0
  201. package/src/lib/arrow/lease.ts +43 -0
  202. package/src/lib/arrow/utils.ts +10 -14
  203. package/src/lib/arrow/vocabularyDictionary.ts +123 -0
  204. package/src/lib/bufferStrategy.ts +9 -1
  205. package/src/lib/cloudflare/traceRows.ts +3 -2
  206. package/src/lib/codegen/__tests__/evaluatorGenerator.test.ts +21 -141
  207. package/src/lib/codegen/__tests__/fixedPositionWriter-parity.test.ts +273 -0
  208. package/src/lib/codegen/__tests__/fixedPositionWriter-uint64.test.ts +61 -34
  209. package/src/lib/codegen/__tests__/nullBitmap.test.ts +33 -37
  210. package/src/lib/codegen/__tests__/spanLoggerGenerator.test.ts +34 -18
  211. package/src/lib/codegen/__tests__/spanLoggerMaterializerParity.test.ts +503 -0
  212. package/src/lib/codegen/evaluatorGenerator.ts +18 -13
  213. package/src/lib/codegen/fixedPositionWriterGenerator.ts +329 -245
  214. package/src/lib/codegen/spanLoggerClosureMaterializer.ts +598 -0
  215. package/src/lib/codegen/spanLoggerGenerator.ts +241 -177
  216. package/src/lib/convertToArrow.ts +598 -311
  217. package/src/lib/defineOpContext.ts +1 -1
  218. package/src/lib/enumMetadata.ts +121 -0
  219. package/src/lib/flushScheduler.ts +11 -5
  220. package/src/lib/library.ts +37 -595
  221. package/src/lib/logBinding.ts +21 -27
  222. package/src/lib/op.ts +75 -18
  223. package/src/lib/opContext/__tests__/retry.test.ts +27 -21
  224. package/src/lib/opContext/createOpGroup.ts +33 -31
  225. package/src/lib/opContext/defineOp.ts +88 -11
  226. package/src/lib/opContext/opGroupTypes.ts +2 -2
  227. package/src/lib/opContext/opTypes.ts +1 -1
  228. package/src/lib/opContext/spanContextTypes.ts +122 -24
  229. package/src/lib/opContext/types.ts +7 -3
  230. package/src/lib/physicalLayoutPlan.ts +598 -0
  231. package/src/lib/resolveMessage.ts +67 -0
  232. package/src/lib/result.ts +20 -33
  233. package/src/lib/runtimeHint.ts +98 -0
  234. package/src/lib/schema/__tests__/featureFlags.test.ts +2 -1
  235. package/src/lib/schema/builder.ts +1 -1
  236. package/src/lib/schema/typeGuards.ts +7 -151
  237. package/src/lib/schema/types.ts +1 -1
  238. package/src/lib/span-buffer/aot/v1.ts +26 -0
  239. package/src/lib/spanBuffer.ts +658 -92
  240. package/src/lib/spanBufferStats.ts +1 -1
  241. package/src/lib/spanContext.ts +1350 -861
  242. package/src/lib/sqlite/sqlite-common.ts +20 -31
  243. package/src/lib/testing/__tests__/topology-traversal.test.ts +174 -0
  244. package/src/lib/testing/__tests__/vitest-harness.test.ts +2 -1
  245. package/src/lib/testing/bun-harness.ts +94 -43
  246. package/src/lib/testing/extractFacts.ts +64 -78
  247. package/src/lib/testing/facts.ts +1 -1
  248. package/src/lib/testing/queryable-span.ts +23 -30
  249. package/src/lib/testing/span-query.ts +16 -20
  250. package/src/lib/testing/stdio-replay.ts +25 -8
  251. package/src/lib/testing/vitest-harness.ts +1 -1
  252. package/src/lib/threadId.ts +6 -3
  253. package/src/lib/traceContext.ts +1 -6
  254. package/src/lib/traceRoot.es.ts +77 -42
  255. package/src/lib/traceRoot.node.ts +75 -46
  256. package/src/lib/traceRoot.ts +41 -14
  257. package/src/lib/traceTopology.ts +279 -0
  258. package/src/lib/tracer.ts +109 -98
  259. package/src/lib/tracers/ArrayQueueTracer.ts +1 -1
  260. package/src/lib/tracers/StdioTracer.ts +15 -13
  261. package/src/lib/tracers/TestTracer.ts +6 -8
  262. package/src/lib/tracers/__tests__/ArrayQueueTracer.test.ts +15 -10
  263. package/src/lib/tracers/__tests__/CompositeTracer.test.ts +150 -0
  264. package/src/lib/tracers/__tests__/TestTracer.test.ts +23 -13
  265. package/src/lib/types.ts +64 -33
  266. package/src/lib/vocabularyRegistry.ts +439 -0
  267. package/src/lib/wasm/WasmBufferStrategy.ts +56 -48
  268. package/src/lib/wasm/__tests__/WasmBufferStrategy.test.ts +215 -11
  269. package/src/lib/wasm/__tests__/allocatorFreeBlockClamp.test.ts +86 -0
  270. package/src/lib/wasm/__tests__/wasm-identity-view.test.ts +311 -0
  271. package/src/lib/wasm/__tests__/wasm-integration.test.ts +417 -44
  272. package/src/lib/wasm/__tests__/wasm-message-buffer-families.test.ts +588 -0
  273. package/src/lib/wasm/__tests__/wasmAllocator.test.ts +100 -668
  274. package/src/lib/wasm/__tests__/wasmSpanBuffer.test.ts +419 -63
  275. package/src/lib/wasm/__tests__/wasmTraceRoot.test.ts +24 -29
  276. package/src/lib/wasm/__tests__/wasmTracer.test.ts.disabled +2 -2
  277. package/src/lib/wasm/wasmAllocator.ts +131 -96
  278. package/src/lib/wasm/wasmPhysicalLayout.ts +309 -0
  279. package/src/lib/wasm/wasmSpanBuffer.ts +597 -341
  280. package/src/lib/wasm/wasmTraceRoot.ts +140 -78
  281. package/src/vocabulary/register/v1.ts +1 -0
  282. package/src/wasm.ts +19 -0
  283. package/src/lib/__tests__/remapped-buffer-view-integration.test.ts +0 -319
  284. package/src/lib/__tests__/remapped-buffer-view.test.ts +0 -635
  285. package/src/lib/wasm/allocator.zig +0 -948
@@ -11,6 +11,7 @@
11
11
  import {
12
12
  clearBitRange,
13
13
  compareStrings,
14
+ countNulls,
14
15
  createSortedDictionary,
15
16
  DictionaryBuilder,
16
17
  type FinalizedDictionary,
@@ -43,31 +44,47 @@ import { buildSortedCategoryDictionary, buildTextDictionary } from './arrow/dict
43
44
  import {
44
45
  type DictionaryColumnData,
45
46
  type GenericColumnData,
46
- getArrowIndexArrayConstructorOr,
47
- getArrowIndexTypeOr,
48
47
  makeArrowColumn,
49
48
  type Utf8ColumnData,
50
49
  } from './arrow/flechette.js';
50
+ import { type ArrowLease, createArrowLease } from './arrow/lease.js';
51
51
  import {
52
52
  calculateUtf8Offsets,
53
+ combineColumnChunks,
53
54
  concatenateFloat64Arrays,
54
55
  concatenateNullBitmaps,
55
56
  concatenateUint8Arrays,
56
57
  encodeUtf8Strings,
57
58
  getArrowFieldName,
58
- walkSpanTree,
59
59
  } from './arrow/utils.js';
60
- import { ENTRY_TYPE_NAMES, SYSTEM_SCHEMA_FIELD_NAMES } from './schema/systemSchema.js';
61
- import { getBinaryEncoder, getEnumUtf8, getEnumValues, getSchemaType } from './schema/typeGuards.js';
60
+ import {
61
+ appendVocabularyDictionarySuffix,
62
+ getVocabularyDictionaryPrefix,
63
+ PREBUILT_ENTRY_TYPE_DICTIONARY,
64
+ } from './arrow/vocabularyDictionary.js';
65
+ import { type EnumLookupDescriptor, resolveEnumLookupDescriptor } from './enumMetadata.js';
66
+ import type { RemapDescriptor } from './logBinding.js';
67
+ import { resolveEntryType } from './resolveMessage.js';
68
+ import {
69
+ ENTRY_TYPE_ERROR,
70
+ ENTRY_TYPE_SPAN_START,
71
+ ENTRY_TYPE_TRACE,
72
+ SYSTEM_SCHEMA_FIELD_NAMES,
73
+ } from './schema/systemSchema.js';
74
+ import { getBinaryEncoder, getEnumUtf8, getSchemaType } from './schema/typeGuards.js';
62
75
  import type { LogSchema } from './schema/types.js';
76
+ import { walkSpanTree } from './traceTopology.js';
63
77
  import type { AnySpanBuffer, OpMetadata } from './types.js';
64
78
  import { globalUtf8Cache } from './utf8Cache.js';
79
+ import type { VocabularyGeneration } from './vocabularyRegistry.js';
65
80
 
66
81
  const DictBuilder = DictionaryBuilder;
67
82
  const F64Array = Float64Array;
68
83
 
69
84
  type BuiltArrowColumn = { column: Column<unknown>; nullCount: number };
70
85
  type BuiltMetadataColumns = { fields: string[]; vectors: Column<unknown>[] };
86
+ type BuiltMessageColumn = { column: Column<unknown>; entryTypeIndices?: Int8Array };
87
+ type RemapsByBuffer = WeakMap<AnySpanBuffer, RemapDescriptor>;
71
88
 
72
89
  export type SystemColumnBuilder = (
73
90
  buffer: AnySpanBuffer,
@@ -173,16 +190,30 @@ function appendTables(base: Table, extra: Table): Table {
173
190
  return tableFromColumns(columns);
174
191
  }
175
192
 
176
- function getAllocatedStringColumn(buffer: AnySpanBuffer, columnName: string): string[] | undefined {
177
- const column = buffer.getColumnIfAllocated(columnName);
193
+ function resolveColumnName(buffer: AnySpanBuffer, outputName: string, remaps: RemapsByBuffer): string {
194
+ return remaps.get(buffer)?.sourceNames[outputName] ?? outputName;
195
+ }
196
+
197
+ function getAllocatedStringColumn(
198
+ buffer: AnySpanBuffer,
199
+ columnName: string,
200
+ remaps?: RemapsByBuffer,
201
+ ): string[] | undefined {
202
+ const sourceName = remaps ? resolveColumnName(buffer, columnName, remaps) : columnName;
203
+ const column = buffer.getColumnIfAllocated(sourceName);
178
204
  if (!Array.isArray(column)) {
179
205
  return undefined;
180
206
  }
181
207
  return isStringArray(column) ? column : undefined;
182
208
  }
183
209
 
184
- function getAllocatedBinaryColumn(buffer: AnySpanBuffer, columnName: string): unknown[] | undefined {
185
- const column = buffer.getColumnIfAllocated(columnName);
210
+ function getAllocatedBinaryColumn(
211
+ buffer: AnySpanBuffer,
212
+ columnName: string,
213
+ remaps?: RemapsByBuffer,
214
+ ): unknown[] | undefined {
215
+ const sourceName = remaps ? resolveColumnName(buffer, columnName, remaps) : columnName;
216
+ const column = buffer.getColumnIfAllocated(sourceName);
186
217
  return Array.isArray(column) ? column : undefined;
187
218
  }
188
219
 
@@ -191,18 +222,21 @@ function getAllocatedBinaryColumn(buffer: AnySpanBuffer, columnName: string): un
191
222
  // Arrow conversion. These typed readers pull from the buffer's immutable _scopeValues; the
192
223
  // per-column fill loops below call them so a row with a direct write keeps its value and scope
193
224
  // only fills the gaps.
194
- function getStringScopeValue(buffer: AnySpanBuffer, fieldName: string): string | undefined {
195
- const value = buffer._scopeValues?.[fieldName];
225
+ function getStringScopeValue(buffer: AnySpanBuffer, fieldName: string, remaps?: RemapsByBuffer): string | undefined {
226
+ const sourceName = remaps ? resolveColumnName(buffer, fieldName, remaps) : fieldName;
227
+ const value = buffer._scopeValues?.[sourceName];
196
228
  return typeof value === 'string' ? value : undefined;
197
229
  }
198
230
 
199
- function getNumberScopeValue(buffer: AnySpanBuffer, fieldName: string): number | undefined {
200
- const value = buffer._scopeValues?.[fieldName];
231
+ function getNumberScopeValue(buffer: AnySpanBuffer, fieldName: string, remaps?: RemapsByBuffer): number | undefined {
232
+ const sourceName = remaps ? resolveColumnName(buffer, fieldName, remaps) : fieldName;
233
+ const value = buffer._scopeValues?.[sourceName];
201
234
  return typeof value === 'number' ? value : undefined;
202
235
  }
203
236
 
204
- function getBooleanScopeValue(buffer: AnySpanBuffer, fieldName: string): boolean | undefined {
205
- const value = buffer._scopeValues?.[fieldName];
237
+ function getBooleanScopeValue(buffer: AnySpanBuffer, fieldName: string, remaps?: RemapsByBuffer): boolean | undefined {
238
+ const sourceName = remaps ? resolveColumnName(buffer, fieldName, remaps) : fieldName;
239
+ const value = buffer._scopeValues?.[sourceName];
206
240
  return typeof value === 'boolean' ? value : undefined;
207
241
  }
208
242
  //#endregion smoo/lmao!n/scope-attributes.arrow-fill
@@ -219,11 +253,179 @@ function isStringArray(value: unknown): value is string[] {
219
253
  }
220
254
 
221
255
  function indexTypeForCount(count: number): IntType {
222
- if (count <= 255) return uint8();
223
- if (count <= 65535) return uint16();
256
+ if (count <= 0x100) return uint8();
257
+ if (count <= 0x1_0000) return uint16();
224
258
  return uint32();
225
259
  }
226
260
 
261
+ function assertStaticMessageKind(buffer: AnySpanBuffer, row: number, entryType: number, denseIndex: number): void {
262
+ const generation = buffer._vocabularyGeneration;
263
+ if (denseIndex >= generation.ids.length) {
264
+ throw new Error(`Invalid vocabulary dense index ${denseIndex} for generation ${generation.generation}`);
265
+ }
266
+ const expectedKind =
267
+ entryType === ENTRY_TYPE_SPAN_START ? 2 : entryType >= ENTRY_TYPE_TRACE && entryType <= ENTRY_TYPE_ERROR ? 1 : 0;
268
+ if (expectedKind === 0) {
269
+ throw new Error(`Entry type ${entryType} at row ${row} cannot carry a static vocabulary identity`);
270
+ }
271
+ const actualKind = generation.kindTags[denseIndex];
272
+ if (actualKind !== expectedKind) {
273
+ throw new Error(
274
+ `Vocabulary kind ${actualKind} at dense index ${denseIndex} does not match entry type ${entryType} at row ${row}`,
275
+ );
276
+ }
277
+ }
278
+
279
+ function buildMessageColumn(
280
+ buffers: AnySpanBuffer[],
281
+ totalRows: number,
282
+ dictionaryId?: number,
283
+ materializeEntryTypes = true,
284
+ ): BuiltMessageColumn {
285
+ let generation = buffers[0]._vocabularyGeneration;
286
+ for (let index = 1; index < buffers.length; index++) {
287
+ const candidate = buffers[index]._vocabularyGeneration;
288
+ if (candidate.generation > generation.generation) generation = candidate;
289
+ }
290
+ const prefix = getVocabularyDictionaryPrefix(generation);
291
+ const indices = new Uint32Array(totalRows);
292
+ const entryTypeIndices = materializeEntryTypes ? new Int8Array(totalRows) : undefined;
293
+ const nullBitmap = new Uint8Array(Math.ceil(totalRows / 8));
294
+ let nullCount = 0;
295
+ let rowOffset = 0;
296
+ let suffixValues: string[] | undefined;
297
+ let suffixIndices: Map<string, number> | undefined;
298
+
299
+ for (const buffer of buffers) {
300
+ const plannedStorage = buffer._opMetadata._physicalLayoutPlan?.arrowExposure.messageIdentityStorage;
301
+ const messageIdentityStorage =
302
+ plannedStorage ??
303
+ (buffer._messagePhysicalLayout === 'current'
304
+ ? 'local-u16'
305
+ : buffer._messagePhysicalLayout === 'specialized'
306
+ ? 'global-u32'
307
+ : 'packed-row-headers');
308
+ for (let row = 0; row < buffer._writeIndex; row++) {
309
+ const outputRow = rowOffset + row;
310
+ const hasDedicatedMessage =
311
+ (row === 0 && buffer._spanName !== undefined) || (row === 1 && buffer._terminalMessage !== undefined);
312
+ let dictionaryIndex: number | undefined;
313
+ let entryType: number;
314
+
315
+ if (messageIdentityStorage === 'local-u16') {
316
+ entryType = resolveEntryType(buffer, row);
317
+ const localId = buffer._messageIds?.[row];
318
+ const rawMessage = buffer.message_values?.[row];
319
+ if (row === 0 && typeof buffer._spanName === 'number') {
320
+ if ((localId !== undefined && localId > 0) || rawMessage !== undefined) {
321
+ throw new Error(`Local row ${row} has conflicting static message identities`);
322
+ }
323
+ dictionaryIndex = buffer._spanName;
324
+ } else if (localId !== undefined && localId > 0) {
325
+ if (hasDedicatedMessage || rawMessage !== undefined) {
326
+ throw new Error(`Local row ${row} has conflicting static and raw message storage`);
327
+ }
328
+ dictionaryIndex = buffer._opMetadata._physicalLayoutPlan?.localMessageDictionary[localId - 1];
329
+ if (dictionaryIndex === undefined) throw new Error(`Missing local message dictionary entry ${localId}`);
330
+ }
331
+ } else if (messageIdentityStorage === 'global-u32') {
332
+ entryType = resolveEntryType(buffer, row);
333
+ const encodedDenseIndex = buffer._logHeaders?.[row];
334
+ if (encodedDenseIndex !== undefined && encodedDenseIndex > 0) {
335
+ if (hasDedicatedMessage || buffer.message_values?.[row] !== undefined) {
336
+ throw new Error(`Global row ${row} has conflicting static and raw message storage`);
337
+ }
338
+ dictionaryIndex = encodedDenseIndex - 1;
339
+ }
340
+ } else {
341
+ const rowHeaders = buffer._rowHeaders;
342
+ if (rowHeaders === undefined) throw new TypeError('Packed message layout is missing row headers');
343
+ const header = rowHeaders[row];
344
+ entryType = header & 0xff;
345
+ const encodedDenseIndex = header >>> 8;
346
+ if (encodedDenseIndex !== 0) {
347
+ if (hasDedicatedMessage) {
348
+ throw new Error(`Packed row ${row} has conflicting static and dedicated message storage`);
349
+ }
350
+ dictionaryIndex = encodedDenseIndex - 1;
351
+ }
352
+ }
353
+ const dedicatedDenseIndex = row === 0 && typeof buffer._spanName === 'number' ? buffer._spanName : undefined;
354
+ if (dedicatedDenseIndex !== undefined) {
355
+ if (buffer.message_values?.[row] !== undefined) {
356
+ throw new Error(`Row ${row} has conflicting dedicated static and raw message storage`);
357
+ }
358
+ dictionaryIndex ??= dedicatedDenseIndex;
359
+ }
360
+
361
+ if (entryTypeIndices !== undefined) entryTypeIndices[outputRow] = entryType;
362
+ if (dictionaryIndex !== undefined) {
363
+ assertStaticMessageKind(buffer, row, entryType, dictionaryIndex);
364
+ } else {
365
+ const message =
366
+ row === 0 && typeof buffer._spanName === 'string'
367
+ ? buffer._spanName
368
+ : row === 1 && buffer._terminalMessage !== undefined
369
+ ? buffer._terminalMessage
370
+ : buffer.message_values?.[row];
371
+ if (message === undefined) {
372
+ nullCount++;
373
+ continue;
374
+ }
375
+ const staticIndex = prefix.valueToDenseIndex.get(message);
376
+ if (staticIndex !== undefined) {
377
+ dictionaryIndex = staticIndex;
378
+ } else {
379
+ let dynamicIndex = suffixIndices?.get(message);
380
+ if (dynamicIndex === undefined) {
381
+ suffixValues ??= [];
382
+ suffixIndices ??= new Map<string, number>();
383
+ dynamicIndex = suffixValues.length;
384
+ suffixValues.push(message);
385
+ suffixIndices.set(message, dynamicIndex);
386
+ }
387
+ dictionaryIndex = prefix.length + dynamicIndex;
388
+ }
389
+ }
390
+ indices[outputRow] = dictionaryIndex;
391
+ nullBitmap[outputRow >>> 3] |= 1 << (outputRow & 7);
392
+ }
393
+ rowOffset += buffer._writeIndex;
394
+ }
395
+
396
+ let messageDictionary = prefix.column;
397
+ if (suffixValues !== undefined) {
398
+ const encoded = globalUtf8Cache.encodeMany(suffixValues);
399
+ const suffix = buildColumn(
400
+ buildData({
401
+ type: utf8(),
402
+ offset: 0,
403
+ length: suffixValues.length,
404
+ nullCount: 0,
405
+ valueOffsets: encoded.offsets,
406
+ data: encoded.data,
407
+ }),
408
+ );
409
+ messageDictionary = appendVocabularyDictionarySuffix(prefix, suffix, suffixValues);
410
+ }
411
+
412
+ return {
413
+ column: buildColumn(
414
+ buildData({
415
+ type:
416
+ dictionaryId === undefined ? dictionary(utf8(), uint32()) : dictionary(utf8(), uint32(), false, dictionaryId),
417
+ offset: 0,
418
+ length: totalRows,
419
+ nullCount,
420
+ data: indices,
421
+ nullBitmap: nullCount > 0 ? nullBitmap : undefined,
422
+ dictionary: messageDictionary,
423
+ }),
424
+ ),
425
+ entryTypeIndices,
426
+ };
427
+ }
428
+
227
429
  // ═══════════════════════════════════════════════════════════════════════════════
228
430
  // Shared metadata column helpers (used by both buildMetadataColumnsWithFields
229
431
  // and convertBuffersWithSharedDicts to avoid duplication)
@@ -321,75 +523,100 @@ function buildNonDictIdentityColumns(
321
523
  );
322
524
  }
323
525
 
324
- /**
325
- * Build timestamp column from buffers.
326
- */
327
526
  function buildTimestampColumn(
328
527
  buffers: AnySpanBuffer[],
329
528
  totalRows: number,
330
529
  fields: string[],
331
530
  vectors: Column<unknown>[],
531
+ borrowChunks = false,
332
532
  ): void {
533
+ const timestampType = timestamp(TimeUnit.NANOSECOND);
534
+ fields.push('timestamp');
535
+ if (borrowChunks) {
536
+ const chunks = buffers.map((buffer) =>
537
+ buildColumn(
538
+ buildData({
539
+ type: timestampType,
540
+ offset: 0,
541
+ length: buffer._writeIndex,
542
+ nullCount: 0,
543
+ data: buffer.timestamp.subarray(0, buffer._writeIndex),
544
+ }),
545
+ ),
546
+ );
547
+ vectors.push(combineColumnChunks(chunks));
548
+ return;
549
+ }
333
550
  const allTimestamps = new BigInt64Array(totalRows);
334
551
  let offset = 0;
335
- for (const buf of buffers) {
336
- if (!buf.timestamp) {
337
- throw new Error(`Buffer missing timestamps property (_writeIndex: ${buf._writeIndex})`);
338
- }
339
- allTimestamps.set(buf.timestamp.subarray(0, buf._writeIndex), offset);
340
- offset += buf._writeIndex;
552
+ for (const buffer of buffers) {
553
+ allTimestamps.set(buffer.timestamp.subarray(0, buffer._writeIndex), offset);
554
+ offset += buffer._writeIndex;
341
555
  }
342
- fields.push('timestamp');
343
556
  vectors.push(
344
- buildColumn(
345
- buildData({
346
- type: timestamp(TimeUnit.NANOSECOND),
347
- offset: 0,
348
- length: totalRows,
349
- nullCount: 0,
350
- data: allTimestamps,
351
- }),
352
- ),
557
+ buildColumn(buildData({ type: timestampType, offset: 0, length: totalRows, nullCount: 0, data: allTimestamps })),
558
+ );
559
+ }
560
+
561
+ function canBorrowEntryTypeChunks(buffers: AnySpanBuffer[], borrowChunks: boolean): boolean {
562
+ return (
563
+ borrowChunks &&
564
+ buffers.every((buffer) => {
565
+ const declared = buffer._opMetadata._physicalLayoutPlan?.arrowExposure.entryTypeStorage;
566
+ return declared === 'borrowed-u8' || (declared === undefined && buffer.entry_type !== undefined);
567
+ })
353
568
  );
354
569
  }
355
570
 
356
- /**
357
- * Build entry_type dictionary column.
358
- */
359
571
  function buildEntryTypeColumn(
360
572
  buffers: AnySpanBuffer[],
361
573
  totalRows: number,
362
574
  fields: string[],
363
575
  vectors: Column<unknown>[],
364
576
  dictType?: ReturnType<typeof dictionary>,
577
+ borrowChunks = false,
578
+ classifiedEntryTypes?: Int8Array,
365
579
  ): void {
366
- const entryTypeIndices = new Int8Array(totalRows);
367
- let offset = 0;
368
- for (const buf of buffers) {
369
- if (!buf.entry_type) {
370
- throw new Error(`Buffer missing operations property (_writeIndex: ${buf._writeIndex})`);
580
+ const entryTypeDictionary = PREBUILT_ENTRY_TYPE_DICTIONARY;
581
+ const type = dictType ?? dictionary(utf8(), int8());
582
+ fields.push('entry_type');
583
+ if (canBorrowEntryTypeChunks(buffers, borrowChunks)) {
584
+ const chunks = buffers.map((buffer) => {
585
+ const source = buffer.entry_type;
586
+ if (source === undefined) throw new TypeError('Current/specialized buffer is missing entry types');
587
+ const values = new Int8Array(source.buffer, source.byteOffset, buffer._writeIndex);
588
+ return buildColumn(
589
+ buildData({
590
+ type,
591
+ offset: 0,
592
+ length: buffer._writeIndex,
593
+ nullCount: 0,
594
+ data: values,
595
+ dictionary: entryTypeDictionary,
596
+ }),
597
+ );
598
+ });
599
+ vectors.push(combineColumnChunks(chunks));
600
+ return;
601
+ }
602
+ const entryTypeIndices = classifiedEntryTypes ?? new Int8Array(totalRows);
603
+ if (classifiedEntryTypes === undefined) {
604
+ let offset = 0;
605
+ for (const buffer of buffers) {
606
+ for (let row = 0; row < buffer._writeIndex; row++) {
607
+ entryTypeIndices[offset++] = resolveEntryType(buffer, row);
608
+ }
371
609
  }
372
- entryTypeIndices.set(buf.entry_type.subarray(0, buf._writeIndex), offset);
373
- offset += buf._writeIndex;
374
610
  }
375
- const entryTypeDictData = buildData({
376
- type: utf8(),
377
- offset: 0,
378
- length: ENTRY_TYPE_NAMES.length,
379
- nullCount: 0,
380
- valueOffsets: calculateUtf8Offsets(ENTRY_TYPE_NAMES),
381
- data: encodeUtf8Strings(ENTRY_TYPE_NAMES),
382
- });
383
- fields.push('entry_type');
384
611
  vectors.push(
385
612
  buildColumn(
386
613
  buildData({
387
- type: dictType ?? dictionary(utf8(), int8()),
614
+ type,
388
615
  offset: 0,
389
616
  length: totalRows,
390
617
  nullCount: 0,
391
618
  data: entryTypeIndices,
392
- dictionary: buildColumn(entryTypeDictData),
619
+ dictionary: entryTypeDictionary,
393
620
  }),
394
621
  ),
395
622
  );
@@ -462,14 +689,16 @@ function buildBinaryColumnFromBuffers(
462
689
  columnName: string,
463
690
  totalRows: number,
464
691
  fieldSchema: unknown,
692
+ remaps?: RemapsByBuffer,
465
693
  ): BuiltArrowColumn {
466
694
  const encoder = getBinaryEncoder(fieldSchema);
467
695
 
468
696
  // Collect all values from buffers, encode each if encoder is present
469
697
  const allValues: (Uint8Array | null)[] = [];
470
698
  for (const buf of buffers) {
471
- const col = getAllocatedBinaryColumn(buf, columnName);
472
- const srcNulls = buf.getNullsIfAllocated(columnName);
699
+ const col = getAllocatedBinaryColumn(buf, columnName, remaps);
700
+ const sourceName = remaps ? resolveColumnName(buf, columnName, remaps) : columnName;
701
+ const srcNulls = buf.getNullsIfAllocated(sourceName);
473
702
  for (let i = 0; i < buf._writeIndex; i++) {
474
703
  if (srcNulls) {
475
704
  const isValid = (srcNulls[i >>> 3] & (1 << (i & 7))) !== 0;
@@ -549,16 +778,18 @@ function buildBinaryColumnFromBuffers(
549
778
  * Dictionaries are built from the data in this batch only and are not shared
550
779
  * with other batches, even when combined into a Table.
551
780
  */
552
- export function convertToTable(buffer: AnySpanBuffer, systemColumnBuilder?: SystemColumnBuilder): Table {
781
+ export function convertToTable(
782
+ buffer: AnySpanBuffer,
783
+ systemColumnBuilder?: SystemColumnBuilder,
784
+ borrowChunks = false,
785
+ ): Table {
553
786
  const buffers: AnySpanBuffer[] = [];
554
787
  let currentBuffer: AnySpanBuffer | undefined = buffer;
555
-
556
788
  while (currentBuffer) {
557
789
  buffers.push(currentBuffer);
558
790
  currentBuffer = currentBuffer._overflow;
559
791
  }
560
-
561
- return convertBuffersToTable(buffers, systemColumnBuilder);
792
+ return convertBuffersToTable(buffers, systemColumnBuilder, borrowChunks);
562
793
  }
563
794
 
564
795
  /**
@@ -568,13 +799,18 @@ export function convertToTable(buffer: AnySpanBuffer, systemColumnBuilder?: Syst
568
799
  * Dictionaries are built from the data in this batch only and are not shared
569
800
  * with other batches, even when combined into a Table.
570
801
  */
571
- function convertBuffersToTable(buffers: AnySpanBuffer[], systemColumnBuilder?: SystemColumnBuilder): Table {
802
+ function convertBuffersToTable(
803
+ buffers: AnySpanBuffer[],
804
+ systemColumnBuilder?: SystemColumnBuilder,
805
+ borrowChunks = false,
806
+ ): Table {
572
807
  if (buffers.length === 0) return tableFromColumns({});
573
808
 
574
809
  const totalRows = buffers.reduce((sum, buf) => sum + buf._writeIndex, 0);
575
810
  if (totalRows === 0) return tableFromColumns({});
576
811
 
577
812
  const schema: LogSchema = buffers[0]._logSchema;
813
+ const enumLookup = resolveEnumLookupDescriptor(schema);
578
814
 
579
815
  // Build vectors first, then derive schema from them
580
816
  // This ensures Field types and vector data types are identical (Arrow IPC requirement)
@@ -586,57 +822,22 @@ function convertBuffersToTable(buffers: AnySpanBuffer[], systemColumnBuilder?: S
586
822
  fields.push(...systemColumns.fields);
587
823
  vectors.push(...systemColumns.vectors);
588
824
  } else {
589
- // Build metadata columns - returns both fields and vectors with matching types
590
- const metadataResult = buildMetadataColumnsWithFields(buffers, totalRows);
825
+ const borrowEntryTypes = canBorrowEntryTypeChunks(buffers, borrowChunks);
826
+ const messageResult = buildMessageColumn(buffers, totalRows, undefined, !borrowEntryTypes);
827
+ // Build metadata columns - returns both fields and vectors with matching types.
828
+ const metadataResult = buildMetadataColumnsWithFields(
829
+ buffers,
830
+ totalRows,
831
+ borrowChunks,
832
+ messageResult.entryTypeIndices,
833
+ );
591
834
  fields.push(...metadataResult.fields);
592
835
  vectors.push(...metadataResult.vectors);
593
836
 
594
- // System attribute column: message (eager category)
595
- const maskTransform = undefined; // message has no masking
596
- let {
597
- dictionary: dictValues,
598
- indices,
599
- arrowIndexType,
600
- nullBitmap,
601
- } = buildSortedCategoryDictionary(buffers, 'message', maskTransform);
602
- // Ensure dictionary has at least one entry (empty string) if no messages were written
603
- if (dictValues.length === 0) {
604
- dictValues = [''];
605
- }
606
- // Ensure nullBitmap is all 1s (all valid) for eager column
607
- if (!nullBitmap) {
608
- const bitmapBytes = Math.ceil(totalRows / 8);
609
- nullBitmap = new Uint8Array(bitmapBytes);
610
- nullBitmap.fill(0xff);
611
- }
612
-
613
- // CRITICAL: Create ONE Dictionary type instance for field and batch data
614
- // Arrow Schema validates that fields with same dictionary ID have identical type references
615
- const messageDictType = dictionary(utf8(), arrowIndexType);
837
+ // Static vocabulary values form an immutable prefix. Only raw messages
838
+ // missing from that prefix are interned into a first-seen suffix.
616
839
  fields.push('message');
617
-
618
- const { data: messageUtf8Data, offsets: messageUtf8Offsets } = globalUtf8Cache.encodeMany(dictValues);
619
-
620
- const messageDictData = buildData({
621
- type: utf8(),
622
- offset: 0,
623
- length: dictValues.length,
624
- nullCount: 0,
625
- valueOffsets: messageUtf8Offsets,
626
- data: messageUtf8Data,
627
- });
628
-
629
- const messageData = buildData({
630
- type: messageDictType,
631
- offset: 0,
632
- length: totalRows,
633
- nullCount: 0, // message is eager, never null
634
- data: indices,
635
- nullBitmap, // Always include nullBitmap for message (even though nullCount is 0)
636
- dictionary: buildColumn(messageDictData),
637
- });
638
-
639
- vectors.push(buildColumn(messageData));
840
+ vectors.push(messageResult.column);
640
841
  }
641
842
 
642
843
  // Build user attribute vectors
@@ -649,17 +850,18 @@ function convertBuffersToTable(buffers: AnySpanBuffer[], systemColumnBuilder?: S
649
850
  const columnName = fieldName; // User columns have no prefix
650
851
 
651
852
  if (lmaoType === 'enum') {
652
- const enumValues = getEnumValues(fieldSchema) || [];
853
+ const descriptor = enumLookup.byField[fieldName];
854
+ if (!descriptor) throw new TypeError(`Enum lookup missing for field '${fieldName}'`);
855
+ const enumValues = descriptor.values;
653
856
  const enumUtf8 = getEnumUtf8(fieldSchema);
654
- // Get constructors from schema metadata
655
- const indexArrayCtor = getArrowIndexArrayConstructorOr(fieldSchema);
656
- const arrowIndexType = getArrowIndexTypeOr(fieldSchema, uint8());
857
+ const indexArrayCtor = descriptor.indexArrayConstructor;
858
+ const arrowIndexType = indexTypeForCount(enumValues.length);
657
859
  // Collect value arrays - need to handle different TypedArray types
658
860
  const valueArrays: (Uint8Array | Uint16Array | Uint32Array)[] = [];
659
861
  for (const buf of buffers) {
660
862
  const column = buf.getColumnIfAllocated(columnName);
661
863
  if (column && column instanceof indexArrayCtor) {
662
- valueArrays.push(column.subarray(0, buf._writeIndex));
864
+ valueArrays.push(column.subarray(0, buf._writeIndex).slice());
663
865
  } else {
664
866
  valueArrays.push(new indexArrayCtor(buf._writeIndex));
665
867
  }
@@ -784,33 +986,52 @@ function convertBuffersToTable(buffers: AnySpanBuffer[], systemColumnBuilder?: S
784
986
  vectors.push(buildColumn(textData));
785
987
  }
786
988
  } else if (lmaoType === 'number') {
787
- const valueArrays: Float64Array[] = [];
788
-
789
- for (const buf of buffers) {
790
- const column = buf.getColumnIfAllocated(columnName);
791
- if (column && column instanceof Float64Array) {
792
- valueArrays.push(column.subarray(0, buf._writeIndex));
793
- } else {
794
- valueArrays.push(new F64Array(buf._writeIndex));
795
- }
796
- }
797
-
798
- const allValues = concatenateFloat64Arrays(valueArrays);
799
- const { nullBitmap, nullCount } = concatenateNullBitmaps(buffers, columnName);
800
-
801
989
  const numberType = float64();
802
990
  fields.push(arrowFieldName);
803
-
804
- const numberData = buildData({
805
- type: numberType,
806
- offset: 0,
807
- length: totalRows,
808
- nullCount,
809
- data: allValues,
810
- nullBitmap,
811
- });
812
-
813
- vectors.push(buildColumn(numberData));
991
+ const borrowed = borrowChunks
992
+ ? buffers.map((buffer) => {
993
+ const column = buffer.getColumnIfAllocated(columnName);
994
+ const validity = buffer.getNullsIfAllocated(columnName);
995
+ if (!(column instanceof Float64Array) || !(validity instanceof Uint8Array)) return undefined;
996
+ return buildColumn(
997
+ buildData({
998
+ type: numberType,
999
+ offset: 0,
1000
+ length: buffer._writeIndex,
1001
+ nullCount: countNulls(validity, buffer._writeIndex),
1002
+ data: column.subarray(0, buffer._writeIndex),
1003
+ nullBitmap: validity.subarray(0, Math.ceil(buffer._writeIndex / 8)),
1004
+ }),
1005
+ );
1006
+ })
1007
+ : undefined;
1008
+ if (borrowed?.every((chunk): chunk is Column<unknown> => chunk !== undefined)) {
1009
+ vectors.push(combineColumnChunks(borrowed));
1010
+ } else {
1011
+ const valueArrays: Float64Array[] = [];
1012
+ for (const buffer of buffers) {
1013
+ const column = buffer.getColumnIfAllocated(columnName);
1014
+ valueArrays.push(
1015
+ column instanceof Float64Array
1016
+ ? column.subarray(0, buffer._writeIndex).slice()
1017
+ : new F64Array(buffer._writeIndex),
1018
+ );
1019
+ }
1020
+ const allValues = concatenateFloat64Arrays(valueArrays);
1021
+ const { nullBitmap, nullCount } = concatenateNullBitmaps(buffers, columnName);
1022
+ vectors.push(
1023
+ buildColumn(
1024
+ buildData({
1025
+ type: numberType,
1026
+ offset: 0,
1027
+ length: totalRows,
1028
+ nullCount,
1029
+ data: allValues,
1030
+ nullBitmap,
1031
+ }),
1032
+ ),
1033
+ );
1034
+ }
814
1035
  } else if (lmaoType === 'boolean') {
815
1036
  const valueArrays: Uint8Array[] = [];
816
1037
 
@@ -818,7 +1039,7 @@ function convertBuffersToTable(buffers: AnySpanBuffer[], systemColumnBuilder?: S
818
1039
  const column = buf.getColumnIfAllocated(columnName);
819
1040
  if (column && column instanceof Uint8Array) {
820
1041
  const requiredBytes = Math.ceil(buf._writeIndex / 8);
821
- valueArrays.push(column.subarray(0, requiredBytes));
1042
+ valueArrays.push(column.subarray(0, requiredBytes).slice());
822
1043
  } else {
823
1044
  const requiredBytes = Math.ceil(buf._writeIndex / 8);
824
1045
  valueArrays.push(new Uint8Array(requiredBytes));
@@ -858,7 +1079,12 @@ function convertBuffersToTable(buffers: AnySpanBuffer[], systemColumnBuilder?: S
858
1079
  * This is required for Arrow IPC serialization - Schema validates that
859
1080
  * fields with same dictionary ID have identical type object references.
860
1081
  */
861
- function buildMetadataColumnsWithFields(buffers: AnySpanBuffer[], totalRows: number): BuiltMetadataColumns {
1082
+ function buildMetadataColumnsWithFields(
1083
+ buffers: AnySpanBuffer[],
1084
+ totalRows: number,
1085
+ borrowChunks = false,
1086
+ classifiedEntryTypes?: Int8Array,
1087
+ ): BuiltMetadataColumns {
862
1088
  const fields: string[] = [];
863
1089
  const vectors: Column<unknown>[] = [];
864
1090
 
@@ -866,7 +1092,7 @@ function buildMetadataColumnsWithFields(buffers: AnySpanBuffer[], totalRows: num
866
1092
  // entry_type, package_name, package_file, git_sha
867
1093
 
868
1094
  // 1. Timestamp
869
- buildTimestampColumn(buffers, totalRows, fields, vectors);
1095
+ buildTimestampColumn(buffers, totalRows, fields, vectors, borrowChunks);
870
1096
 
871
1097
  // 2. Trace ID dictionary (built inline from buffers)
872
1098
  const traceIdSet = new Set<string>();
@@ -915,7 +1141,7 @@ function buildMetadataColumnsWithFields(buffers: AnySpanBuffer[], totalRows: num
915
1141
  buildNonDictIdentityColumns(buffers, totalRows, fields, vectors);
916
1142
 
917
1143
  // 7. Entry type
918
- buildEntryTypeColumn(buffers, totalRows, fields, vectors);
1144
+ buildEntryTypeColumn(buffers, totalRows, fields, vectors, undefined, borrowChunks, classifiedEntryTypes);
919
1145
 
920
1146
  // 8-10. Per-buffer metadata columns (package_name, package_file, git_sha)
921
1147
  for (const [fieldName, metadataGetter] of [
@@ -951,6 +1177,51 @@ function buildMetadataColumnsWithFields(buffers: AnySpanBuffer[], totalRows: num
951
1177
  return { fields, vectors };
952
1178
  }
953
1179
 
1180
+ type MemoryEpochPin = { readonly buffer: ArrayBuffer; release(): void };
1181
+ type EpochPinnableAllocator = { pinMemoryEpoch(): MemoryEpochPin };
1182
+ type EpochPinnableBuffer = AnySpanBuffer & { readonly _allocator: EpochPinnableAllocator };
1183
+
1184
+ function isEpochPinnableBuffer(buffer: AnySpanBuffer): buffer is EpochPinnableBuffer {
1185
+ if (!('_allocator' in buffer)) return false;
1186
+ const allocator = Reflect.get(buffer, '_allocator');
1187
+ return (
1188
+ typeof allocator === 'object' &&
1189
+ allocator !== null &&
1190
+ typeof Reflect.get(allocator, 'pinMemoryEpoch') === 'function'
1191
+ );
1192
+ }
1193
+
1194
+ function createLeasedConversion(
1195
+ root: AnySpanBuffer,
1196
+ buffers: readonly AnySpanBuffer[],
1197
+ build: (borrowChunks: boolean) => Table,
1198
+ ): ArrowLease {
1199
+ const topology = root._traceRoot._topology;
1200
+ topology.assertLive(root);
1201
+ const releaseTopology = topology.acquireLease();
1202
+ const generations = new Set<VocabularyGeneration>();
1203
+ const allocators = new Set<EpochPinnableAllocator>();
1204
+ for (const buffer of buffers) {
1205
+ generations.add(buffer._vocabularyGeneration);
1206
+ if (isEpochPinnableBuffer(buffer)) allocators.add(buffer._allocator);
1207
+ }
1208
+
1209
+ const epochPins = Array.from(allocators, (allocator) => allocator.pinMemoryEpoch());
1210
+ // WASM linear-memory growth cannot preserve borrowed JS TypedArray views, so
1211
+ // its leased result uses the existing Arrow-owned copy path. JS heap buffers
1212
+ // expose their physical chunks directly under the topology lease.
1213
+ const borrowChunks = epochPins.length === 0;
1214
+ try {
1215
+ const table = build(borrowChunks);
1216
+ for (const pin of epochPins) pin.release();
1217
+ return createArrowLease(table, [...buffers, ...generations], [releaseTopology]);
1218
+ } catch (error) {
1219
+ for (const pin of epochPins) pin.release();
1220
+ releaseTopology();
1221
+ throw error;
1222
+ }
1223
+ }
1224
+
954
1225
  /**
955
1226
  * Convert SpanBuffer (and its overflow chain) to Arrow Table
956
1227
  */
@@ -963,6 +1234,23 @@ export function convertToArrowTable<_T extends LogSchema = LogSchema>(
963
1234
  return batch;
964
1235
  }
965
1236
 
1237
+ /** Borrow JS physical Arrow chunks until the returned lease is released. */
1238
+ export function convertToLeasedArrowTable(
1239
+ buffer: AnySpanBuffer,
1240
+ systemColumnBuilder?: SystemColumnBuilder,
1241
+ ): ArrowLease {
1242
+ const buffers: AnySpanBuffer[] = [];
1243
+ let segment: AnySpanBuffer | undefined = buffer;
1244
+ while (segment) {
1245
+ buffers.push(segment);
1246
+ segment = segment._overflow;
1247
+ }
1248
+ return createLeasedConversion(buffer, buffers, (borrowChunks) => {
1249
+ const table = convertToTable(buffer, systemColumnBuilder, borrowChunks);
1250
+ return table.numRows === 0 ? tableFromColumns({}) : table;
1251
+ });
1252
+ }
1253
+
966
1254
  // ═══════════════════════════════════════════════════════════════════════════════
967
1255
  // Tree Conversion with Shared Dictionaries
968
1256
  // ═══════════════════════════════════════════════════════════════════════════════
@@ -979,6 +1267,7 @@ export function convertSpanTreeToArrowTable(
979
1267
  _systemColumnBuilder?: SystemColumnBuilder,
980
1268
  modulesToLogStats?: CapacityStatsEntry[],
981
1269
  periodStartNs?: bigint,
1270
+ borrowChunks = false,
982
1271
  ): Table {
983
1272
  // ═══════════════════════════════════════════════════════════════════════════
984
1273
  // PASS 0: Collect ALL unique schema fields from ALL buffers in the tree
@@ -986,19 +1275,27 @@ export function convertSpanTreeToArrowTable(
986
1275
  // (e.g., library prefixed schemas like db_query, http_status, etc.)
987
1276
  // ═══════════════════════════════════════════════════════════════════════════
988
1277
  const mergedSchemaFields = new Map<string, unknown>();
989
-
990
- walkSpanTree(rootBuffer, (buffer) => {
991
- // Use buffer._columns directly - works for both SpanBuffer and RemappedBufferView
992
- const fields = buffer._columns;
993
- for (const [fieldName, fieldSchema] of fields) {
994
- // Skip system schema fields - they are handled separately as system columns
995
- // (message, line, error_code, exception_stack, ff_value, uint64_value)
996
- if (SYSTEM_SCHEMA_FIELD_NAMES.has(fieldName)) {
997
- continue;
1278
+ const remaps: RemapsByBuffer = new WeakMap();
1279
+ const enumLookupsByField = new Map<string, EnumLookupDescriptor>();
1280
+
1281
+ walkSpanTree(rootBuffer, (buffer, remapDescriptor) => {
1282
+ const enumLookup = resolveEnumLookupDescriptor(buffer._logSchema);
1283
+ if (remapDescriptor) {
1284
+ remaps.set(buffer, remapDescriptor);
1285
+ for (const [fieldName, sourceName, fieldSchema] of remapDescriptor.columns) {
1286
+ if (!SYSTEM_SCHEMA_FIELD_NAMES.has(fieldName) && !mergedSchemaFields.has(fieldName)) {
1287
+ mergedSchemaFields.set(fieldName, fieldSchema);
1288
+ const descriptor = enumLookup.byField[sourceName];
1289
+ if (descriptor) enumLookupsByField.set(fieldName, descriptor);
1290
+ }
998
1291
  }
999
- // Only add if not already present (first buffer with this field wins)
1000
- if (!mergedSchemaFields.has(fieldName)) {
1292
+ return;
1293
+ }
1294
+ for (const [fieldName, fieldSchema] of buffer._columns) {
1295
+ if (!SYSTEM_SCHEMA_FIELD_NAMES.has(fieldName) && !mergedSchemaFields.has(fieldName)) {
1001
1296
  mergedSchemaFields.set(fieldName, fieldSchema);
1297
+ const descriptor = enumLookup.byField[fieldName];
1298
+ if (descriptor) enumLookupsByField.set(fieldName, descriptor);
1002
1299
  }
1003
1300
  }
1004
1301
  });
@@ -1035,12 +1332,6 @@ export function convertSpanTreeToArrowTable(
1035
1332
  }
1036
1333
  }
1037
1334
 
1038
- // System attribute column: message (eager category, not in user schema)
1039
- // message is always present (eager allocation), so we need to handle it explicitly
1040
- categoryBuilders.set('message', new DictBuilder(globalUtf8Cache));
1041
- categoryMaskTransforms.set('message', undefined); // message has no masking
1042
- categoryOriginalToMasked.set('message', new Map());
1043
-
1044
1335
  // Metadata column dictionaries
1045
1336
  // For traceId (metadata), use DictionaryBuilder (values not pre-encoded)
1046
1337
  const traceIdBuilder = new DictBuilder(globalUtf8Cache);
@@ -1062,12 +1353,12 @@ export function convertSpanTreeToArrowTable(
1062
1353
  }
1063
1354
 
1064
1355
  for (const [fieldName, builder] of categoryBuilders) {
1065
- const col = getAllocatedStringColumn(buffer, fieldName);
1066
1356
  const maskTransform = categoryMaskTransforms.get(fieldName);
1067
1357
  const originalToMasked = categoryOriginalToMasked.get(fieldName);
1068
1358
  if (!originalToMasked) {
1069
1359
  throw new Error(`Category originalToMasked map not found for field: ${fieldName}`);
1070
1360
  }
1361
+ const col = getAllocatedStringColumn(buffer, fieldName, remaps);
1071
1362
  if (col) {
1072
1363
  for (let i = 0; i < buffer._writeIndex; i++) {
1073
1364
  const originalValue = col[i];
@@ -1083,7 +1374,7 @@ export function convertSpanTreeToArrowTable(
1083
1374
  }
1084
1375
  // Also add scope values to dictionary (they may not be in columns)
1085
1376
  // Per specs/lmao/01i_span_scope_attributes.md: scope values fill NULL cells at Arrow conversion
1086
- const scopeValue = getStringScopeValue(buffer, fieldName);
1377
+ const scopeValue = getStringScopeValue(buffer, fieldName, remaps);
1087
1378
  if (scopeValue !== undefined) {
1088
1379
  let maskedScopeValue = originalToMasked.get(scopeValue);
1089
1380
  if (maskedScopeValue === undefined) {
@@ -1095,7 +1386,7 @@ export function convertSpanTreeToArrowTable(
1095
1386
  }
1096
1387
 
1097
1388
  for (const [fieldName, builder] of textBuilders) {
1098
- const col = getAllocatedStringColumn(buffer, fieldName);
1389
+ const col = getAllocatedStringColumn(buffer, fieldName, remaps);
1099
1390
  const maskTransform = textMaskTransforms.get(fieldName);
1100
1391
  const originalToMasked = textOriginalToMasked.get(fieldName);
1101
1392
  if (!originalToMasked) {
@@ -1116,7 +1407,7 @@ export function convertSpanTreeToArrowTable(
1116
1407
  }
1117
1408
  // Also add scope values to dictionary (they may not be in columns)
1118
1409
  // Per specs/lmao/01i_span_scope_attributes.md: scope values fill NULL cells at Arrow conversion
1119
- const scopeValue = getStringScopeValue(buffer, fieldName);
1410
+ const scopeValue = getStringScopeValue(buffer, fieldName, remaps);
1120
1411
  if (scopeValue !== undefined) {
1121
1412
  let maskedScopeValue = originalToMasked.get(scopeValue);
1122
1413
  if (maskedScopeValue === undefined) {
@@ -1166,15 +1457,6 @@ export function convertSpanTreeToArrowTable(
1166
1457
  textDicts.set(name, builder.finalize(false)); // not sorted
1167
1458
  }
1168
1459
 
1169
- // Get messageDict from categoryDicts (message is handled as a category column)
1170
- // If no messages were written, create an empty dictionary
1171
- let messageDict = categoryDicts.get('message');
1172
- if (!messageDict) {
1173
- const messageBuilder = new DictBuilder(globalUtf8Cache);
1174
- messageBuilder.add(''); // Single empty string entry
1175
- messageDict = messageBuilder.finalize(true); // sorted
1176
- }
1177
-
1178
1460
  // ═══════════════════════════════════════════════════════════════════════════
1179
1461
  // PASS 2: Collect all buffers with non-zero rows
1180
1462
  // ═══════════════════════════════════════════════════════════════════════════
@@ -1198,8 +1480,11 @@ export function convertSpanTreeToArrowTable(
1198
1480
  categoryDicts,
1199
1481
  textDicts,
1200
1482
  schemaFields,
1483
+ enumLookupsByField,
1201
1484
  categoryOriginalToMasked,
1202
1485
  textOriginalToMasked,
1486
+ remaps,
1487
+ borrowChunks,
1203
1488
  )
1204
1489
  : undefined;
1205
1490
 
@@ -1221,6 +1506,22 @@ export function convertSpanTreeToArrowTable(
1221
1506
  return tableFromColumns({});
1222
1507
  }
1223
1508
 
1509
+ /** Convert a logical trace tree while pinning every borrowed physical chunk. */
1510
+ export function convertSpanTreeToLeasedArrowTable(
1511
+ rootBuffer: AnySpanBuffer,
1512
+ systemColumnBuilder?: SystemColumnBuilder,
1513
+ modulesToLogStats?: CapacityStatsEntry[],
1514
+ periodStartNs?: bigint,
1515
+ ): ArrowLease {
1516
+ const buffers: AnySpanBuffer[] = [];
1517
+ walkSpanTree(rootBuffer, (buffer) => {
1518
+ if (buffer._writeIndex > 0) buffers.push(buffer);
1519
+ });
1520
+ return createLeasedConversion(rootBuffer, buffers, (borrowChunks) =>
1521
+ convertSpanTreeToArrowTable(rootBuffer, systemColumnBuilder, modulesToLogStats, periodStartNs, borrowChunks),
1522
+ );
1523
+ }
1524
+
1224
1525
  /**
1225
1526
  * Convert multiple buffers to a single table using pre-built dictionaries.
1226
1527
  *
@@ -1238,12 +1539,17 @@ function convertBuffersWithSharedDicts(
1238
1539
  categoryDicts: Map<string, FinalizedDictionary>,
1239
1540
  textDicts: Map<string, FinalizedDictionary>,
1240
1541
  schemaFields: Array<[string, unknown]>,
1542
+ enumLookupsByField: ReadonlyMap<string, EnumLookupDescriptor>,
1241
1543
  categoryOriginalToMasked: Map<string, Map<string, string>>,
1242
1544
  textOriginalToMasked: Map<string, Map<string, string>>,
1545
+ remaps: RemapsByBuffer,
1546
+ borrowChunks = false,
1243
1547
  ): Table {
1244
1548
  const totalRows = buffers.reduce((sum, buf) => sum + buf._writeIndex, 0);
1245
1549
  const fields: string[] = [];
1246
1550
  const vectors: Column<unknown>[] = [];
1551
+ const borrowEntryTypes = canBorrowEntryTypeChunks(buffers, borrowChunks);
1552
+ const messageResult = buildMessageColumn(buffers, totalRows, 5, !borrowEntryTypes);
1247
1553
 
1248
1554
  // Dictionary types with explicit IDs for IPC serialization
1249
1555
  const traceIdType = dictionary(utf8(), traceIdDict.arrowIndexType, false, 0);
@@ -1256,7 +1562,7 @@ function convertBuffersWithSharedDicts(
1256
1562
  // entry_type, package_name, package_file, git_sha
1257
1563
 
1258
1564
  // 1. Timestamp
1259
- buildTimestampColumn(buffers, totalRows, fields, vectors);
1565
+ buildTimestampColumn(buffers, totalRows, fields, vectors, borrowChunks);
1260
1566
 
1261
1567
  // 2. Trace ID (using shared pre-built dictionary)
1262
1568
  const traceIdIndices = new traceIdDict.indexArrayCtor(totalRows);
@@ -1293,7 +1599,15 @@ function convertBuffersWithSharedDicts(
1293
1599
  buildNonDictIdentityColumns(buffers, totalRows, fields, vectors);
1294
1600
 
1295
1601
  // 7. Entry type (with explicit dictionary ID for IPC)
1296
- buildEntryTypeColumn(buffers, totalRows, fields, vectors, entryTypeDictType);
1602
+ buildEntryTypeColumn(
1603
+ buffers,
1604
+ totalRows,
1605
+ fields,
1606
+ vectors,
1607
+ entryTypeDictType,
1608
+ borrowChunks,
1609
+ messageResult.entryTypeIndices,
1610
+ );
1297
1611
 
1298
1612
  // 8-10. Per-buffer metadata columns using shared pre-built dictionaries
1299
1613
  // Per specs/lmao/01c_context_flow_and_op_wrappers.md:
@@ -1325,52 +1639,7 @@ function convertBuffersWithSharedDicts(
1325
1639
  // ═══════════════════════════════════════════════════════════════════════════
1326
1640
 
1327
1641
  // System attribute column: message (at fixed position 10, before user attributes)
1328
- const messageDict = categoryDicts.get('message');
1329
- const messageOriginalToMasked = categoryOriginalToMasked.get('message');
1330
- if (!messageDict || !messageOriginalToMasked) {
1331
- throw new Error('Message dictionary or mapping not found');
1332
- }
1333
- const messageFieldType = dictionary(utf8(), messageDict.arrowIndexType, false, 5);
1334
- const messageIndices = new messageDict.indexArrayCtor(totalRows);
1335
- const messageNullBitmap = new Uint8Array(Math.ceil(totalRows / 8));
1336
- let messageRowOffset = 0;
1337
- for (const buf of buffers) {
1338
- const col = getAllocatedStringColumn(buf, 'message');
1339
- if (col) {
1340
- for (let i = 0; i < buf._writeIndex; i++) {
1341
- const v = col[i];
1342
- const rowIdx = messageRowOffset + i;
1343
- if (v != null) {
1344
- const maskedValue = messageOriginalToMasked.get(v) ?? v;
1345
- messageIndices[rowIdx] = messageDict.indexMap.get(maskedValue) ?? 0;
1346
- messageNullBitmap[rowIdx >>> 3] |= 1 << (rowIdx & 7);
1347
- }
1348
- }
1349
- }
1350
- messageRowOffset += buf._writeIndex;
1351
- }
1352
- vectors.push(
1353
- buildColumn(
1354
- buildData({
1355
- type: messageFieldType,
1356
- offset: 0,
1357
- length: totalRows,
1358
- nullCount: 0, // message is eager, never null
1359
- data: messageIndices,
1360
- nullBitmap: messageNullBitmap,
1361
- dictionary: buildColumn(
1362
- buildData({
1363
- type: utf8(),
1364
- offset: 0,
1365
- length: messageDict.indexMap.size,
1366
- nullCount: 0,
1367
- valueOffsets: messageDict.offsets,
1368
- data: messageDict.data,
1369
- }),
1370
- ),
1371
- }),
1372
- ),
1373
- );
1642
+ vectors.push(messageResult.column);
1374
1643
  fields.push('message');
1375
1644
 
1376
1645
  // System attribute column: uint64_value (for buffer metrics - op durations, counts, etc.)
@@ -1409,9 +1678,9 @@ function convertBuffersWithSharedDicts(
1409
1678
 
1410
1679
  // Per specs/lmao/01i_span_scope_attributes.md: scope values fill NULL cells at Arrow conversion
1411
1680
  for (const buf of buffers) {
1412
- const col = getAllocatedStringColumn(buf, columnName);
1681
+ const col = getAllocatedStringColumn(buf, columnName, remaps);
1413
1682
  // Check if this buffer has a scope value for this field
1414
- const scopeValue = getStringScopeValue(buf, fieldName);
1683
+ const scopeValue = getStringScopeValue(buf, fieldName, remaps);
1415
1684
  let scopeEncodedValue: number | undefined;
1416
1685
  if (scopeValue !== undefined) {
1417
1686
  const maskedScopeValue = originalToMasked.get(scopeValue) ?? scopeValue;
@@ -1484,9 +1753,9 @@ function convertBuffersWithSharedDicts(
1484
1753
  let rowOffset = 0;
1485
1754
 
1486
1755
  for (const buf of buffers) {
1487
- const col = getAllocatedStringColumn(buf, columnName);
1756
+ const col = getAllocatedStringColumn(buf, columnName, remaps);
1488
1757
  // Check if this buffer has a scope value for this field
1489
- const scopeValue = getStringScopeValue(buf, fieldName);
1758
+ const scopeValue = getStringScopeValue(buf, fieldName, remaps);
1490
1759
  let scopeEncodedValue: number | undefined;
1491
1760
  if (scopeValue !== undefined) {
1492
1761
  const maskedScopeValue = originalToMasked.get(scopeValue) ?? scopeValue;
@@ -1548,77 +1817,101 @@ function convertBuffersWithSharedDicts(
1548
1817
  );
1549
1818
  fields.push(arrowFieldName);
1550
1819
  } else if (lmaoType === 'number') {
1551
- const allValues = new F64Array(totalRows);
1552
- const nullBitmap = new Uint8Array(Math.ceil(totalRows / 8));
1553
- // Start with all nulls (bits cleared) - we'll set bits as we find values
1554
- let nullCount = 0;
1555
- let rowOffset = 0;
1556
-
1557
- for (const buf of buffers) {
1558
- const col = buf.getColumnIfAllocated(columnName);
1559
- const srcNulls = buf.getNullsIfAllocated(columnName);
1560
- // Check if this buffer has a scope value for this field
1561
- const scopeValue = getNumberScopeValue(buf, fieldName);
1562
-
1563
- if (col instanceof Float64Array) {
1564
- allValues.set(col.subarray(0, buf._writeIndex), rowOffset);
1565
- if (srcNulls) {
1566
- // Process each row - direct write wins, then scope, then null
1567
- for (let i = 0; i < buf._writeIndex; i++) {
1568
- const srcByte = i >>> 3;
1569
- const srcBit = i & 7;
1570
- const isValid = (srcNulls[srcByte] & (1 << srcBit)) !== 0;
1571
- const rowIdx = rowOffset + i;
1572
- if (isValid) {
1573
- // Direct write - mark as valid
1574
- nullBitmap[rowIdx >>> 3] |= 1 << (rowIdx & 7);
1575
- } else if (scopeValue !== undefined) {
1576
- // No direct write, but have scope value - use it
1577
- allValues[rowIdx] = scopeValue;
1578
- nullBitmap[rowIdx >>> 3] |= 1 << (rowIdx & 7);
1579
- } else {
1580
- // No direct write and no scope - null
1581
- nullCount++;
1820
+ const borrowed = borrowChunks
1821
+ ? buffers.map((buffer) => {
1822
+ const sourceName = resolveColumnName(buffer, columnName, remaps);
1823
+ if (getNumberScopeValue(buffer, fieldName, remaps) !== undefined) return undefined;
1824
+ const column = buffer.getColumnIfAllocated(sourceName);
1825
+ const validity = buffer.getNullsIfAllocated(sourceName);
1826
+ if (!(column instanceof Float64Array) || !(validity instanceof Uint8Array)) return undefined;
1827
+ return buildColumn(
1828
+ buildData({
1829
+ type: float64(),
1830
+ offset: 0,
1831
+ length: buffer._writeIndex,
1832
+ nullCount: countNulls(validity, buffer._writeIndex),
1833
+ data: column.subarray(0, buffer._writeIndex),
1834
+ nullBitmap: validity.subarray(0, Math.ceil(buffer._writeIndex / 8)),
1835
+ }),
1836
+ );
1837
+ })
1838
+ : undefined;
1839
+ if (borrowed?.every((chunk): chunk is Column<unknown> => chunk !== undefined)) {
1840
+ vectors.push(combineColumnChunks(borrowed));
1841
+ } else {
1842
+ const allValues = new F64Array(totalRows);
1843
+ const nullBitmap = new Uint8Array(Math.ceil(totalRows / 8));
1844
+ // Start with all nulls (bits cleared) - we'll set bits as we find values
1845
+ let nullCount = 0;
1846
+ let rowOffset = 0;
1847
+
1848
+ for (const buf of buffers) {
1849
+ const sourceName = resolveColumnName(buf, columnName, remaps);
1850
+ const col = buf.getColumnIfAllocated(sourceName);
1851
+ const srcNulls = buf.getNullsIfAllocated(sourceName);
1852
+ // Check if this buffer has a scope value for this field
1853
+ const scopeValue = getNumberScopeValue(buf, fieldName, remaps);
1854
+
1855
+ if (col instanceof Float64Array) {
1856
+ allValues.set(col.subarray(0, buf._writeIndex), rowOffset);
1857
+ if (srcNulls) {
1858
+ // Process each row - direct write wins, then scope, then null
1859
+ for (let i = 0; i < buf._writeIndex; i++) {
1860
+ const srcByte = i >>> 3;
1861
+ const srcBit = i & 7;
1862
+ const isValid = (srcNulls[srcByte] & (1 << srcBit)) !== 0;
1863
+ const rowIdx = rowOffset + i;
1864
+ if (isValid) {
1865
+ // Direct write - mark as valid
1866
+ nullBitmap[rowIdx >>> 3] |= 1 << (rowIdx & 7);
1867
+ } else if (scopeValue !== undefined) {
1868
+ // No direct write, but have scope value - use it
1869
+ allValues[rowIdx] = scopeValue;
1870
+ nullBitmap[rowIdx >>> 3] |= 1 << (rowIdx & 7);
1871
+ } else {
1872
+ // No direct write and no scope - null
1873
+ nullCount++;
1874
+ }
1875
+ }
1876
+ } else {
1877
+ // No null bitmap means column was allocated but no writes - check scope
1878
+ for (let i = 0; i < buf._writeIndex; i++) {
1879
+ const rowIdx = rowOffset + i;
1880
+ if (scopeValue !== undefined) {
1881
+ allValues[rowIdx] = scopeValue;
1882
+ nullBitmap[rowIdx >>> 3] |= 1 << (rowIdx & 7);
1883
+ } else {
1884
+ nullCount++;
1885
+ }
1582
1886
  }
1583
1887
  }
1584
- } else {
1585
- // No null bitmap means column was allocated but no writes - check scope
1888
+ } else if (scopeValue !== undefined) {
1889
+ // Column not allocated but we have scope - fill all rows with scope value
1586
1890
  for (let i = 0; i < buf._writeIndex; i++) {
1587
1891
  const rowIdx = rowOffset + i;
1588
- if (scopeValue !== undefined) {
1589
- allValues[rowIdx] = scopeValue;
1590
- nullBitmap[rowIdx >>> 3] |= 1 << (rowIdx & 7);
1591
- } else {
1592
- nullCount++;
1593
- }
1892
+ allValues[rowIdx] = scopeValue;
1893
+ nullBitmap[rowIdx >>> 3] |= 1 << (rowIdx & 7);
1594
1894
  }
1895
+ } else {
1896
+ // Column doesn't exist for this buffer and no scope - all nulls
1897
+ nullCount += buf._writeIndex;
1595
1898
  }
1596
- } else if (scopeValue !== undefined) {
1597
- // Column not allocated but we have scope - fill all rows with scope value
1598
- for (let i = 0; i < buf._writeIndex; i++) {
1599
- const rowIdx = rowOffset + i;
1600
- allValues[rowIdx] = scopeValue;
1601
- nullBitmap[rowIdx >>> 3] |= 1 << (rowIdx & 7);
1602
- }
1603
- } else {
1604
- // Column doesn't exist for this buffer and no scope - all nulls
1605
- nullCount += buf._writeIndex;
1899
+ rowOffset += buf._writeIndex;
1606
1900
  }
1607
- rowOffset += buf._writeIndex;
1608
- }
1609
1901
 
1610
- vectors.push(
1611
- buildColumn(
1612
- buildData({
1613
- type: float64(),
1614
- offset: 0,
1615
- length: totalRows,
1616
- nullCount,
1617
- data: allValues,
1618
- nullBitmap: nullCount > 0 ? nullBitmap : undefined,
1619
- }),
1620
- ),
1621
- );
1902
+ vectors.push(
1903
+ buildColumn(
1904
+ buildData({
1905
+ type: float64(),
1906
+ offset: 0,
1907
+ length: totalRows,
1908
+ nullCount,
1909
+ data: allValues,
1910
+ nullBitmap: nullCount > 0 ? nullBitmap : undefined,
1911
+ }),
1912
+ ),
1913
+ );
1914
+ }
1622
1915
  fields.push(arrowFieldName);
1623
1916
  } else if (lmaoType === 'boolean') {
1624
1917
  const requiredBytes = Math.ceil(totalRows / 8);
@@ -1629,10 +1922,11 @@ function convertBuffersWithSharedDicts(
1629
1922
  let rowOffset = 0;
1630
1923
 
1631
1924
  for (const buf of buffers) {
1632
- const col = buf.getColumnIfAllocated(columnName);
1633
- const srcNulls = buf.getNullsIfAllocated(columnName);
1925
+ const sourceName = resolveColumnName(buf, columnName, remaps);
1926
+ const col = buf.getColumnIfAllocated(sourceName);
1927
+ const srcNulls = buf.getNullsIfAllocated(sourceName);
1634
1928
  // Check if this buffer has a scope value for this field
1635
- const scopeValue = getBooleanScopeValue(buf, fieldName);
1929
+ const scopeValue = getBooleanScopeValue(buf, fieldName, remaps);
1636
1930
 
1637
1931
  if (col instanceof Uint8Array) {
1638
1932
  // Copy boolean values bit by bit - can't avoid loop for bit-level operations
@@ -1702,32 +1996,25 @@ function convertBuffersWithSharedDicts(
1702
1996
  );
1703
1997
  fields.push(arrowFieldName);
1704
1998
  } else if (lmaoType === 'enum') {
1705
- const enumValues = getEnumValues(fieldSchema) || [];
1999
+ const descriptor = enumLookupsByField.get(fieldName);
2000
+ if (!descriptor) throw new TypeError(`Enum lookup missing for field '${fieldName}'`);
2001
+ const enumValues = descriptor.values;
1706
2002
  const enumUtf8 = getEnumUtf8(fieldSchema);
1707
- // Get constructors from schema metadata
1708
- const indexArrayCtor = getArrowIndexArrayConstructorOr(fieldSchema);
1709
- const arrowIndexType = getArrowIndexTypeOr(fieldSchema, uint8());
2003
+ const indexArrayCtor = descriptor.indexArrayConstructor;
2004
+ const arrowIndexType = indexTypeForCount(enumValues.length);
1710
2005
  const allIndices = new indexArrayCtor(totalRows);
1711
2006
  const nullBitmap = new Uint8Array(Math.ceil(totalRows / 8));
1712
2007
  // Start with all nulls (bits cleared) - we'll set bits as we find values
1713
2008
  let nullCount = 0;
1714
2009
  let rowOffset = 0;
1715
2010
 
1716
- // Build enum value to index mapping for scope lookup
1717
- const enumValueToIndex = new Map<string, number>();
1718
- for (let i = 0; i < enumValues.length; i++) {
1719
- enumValueToIndex.set(enumValues[i], i);
1720
- }
1721
-
1722
2011
  for (const buf of buffers) {
1723
- const col = buf.getColumnIfAllocated(columnName);
1724
- const srcNulls = buf.getNullsIfAllocated(columnName);
2012
+ const sourceName = resolveColumnName(buf, columnName, remaps);
2013
+ const col = buf.getColumnIfAllocated(sourceName);
2014
+ const srcNulls = buf.getNullsIfAllocated(sourceName);
1725
2015
  // Check if this buffer has a scope value for this field
1726
- const scopeValue = getStringScopeValue(buf, fieldName);
1727
- let scopeEncodedValue: number | undefined;
1728
- if (scopeValue !== undefined) {
1729
- scopeEncodedValue = enumValueToIndex.get(scopeValue);
1730
- }
2016
+ const scopeValue = getStringScopeValue(buf, fieldName, remaps);
2017
+ const scopeEncodedValue = scopeValue === undefined ? undefined : descriptor.encode(scopeValue);
1731
2018
 
1732
2019
  if (col instanceof indexArrayCtor) {
1733
2020
  allIndices.set(col.subarray(0, buf._writeIndex), rowOffset);
@@ -1801,7 +2088,7 @@ function convertBuffersWithSharedDicts(
1801
2088
  } else if (lmaoType === 'binary') {
1802
2089
  // Binary columns: raw Uint8Array or encoder-wrapped values (e.g. msgpack)
1803
2090
  // Binary columns don't use dictionaries and are not scope-fillable
1804
- const { column } = buildBinaryColumnFromBuffers(buffers, columnName, totalRows, fieldSchema);
2091
+ const { column } = buildBinaryColumnFromBuffers(buffers, columnName, totalRows, fieldSchema, remaps);
1805
2092
  fields.push(arrowFieldName);
1806
2093
  vectors.push(column);
1807
2094
  }