@smoothbricks/lmao 0.2.2 → 0.2.3

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 (276) hide show
  1. package/dist/allocator.wasm +0 -0
  2. package/dist/index.d.ts +1 -0
  3. package/dist/index.d.ts.map +1 -1
  4. package/dist/index.js +1 -0
  5. package/dist/lib/JsBufferStrategy.d.ts +4 -3
  6. package/dist/lib/JsBufferStrategy.d.ts.map +1 -1
  7. package/dist/lib/JsBufferStrategy.js +18 -11
  8. package/dist/lib/arrow/lease.d.ts +10 -0
  9. package/dist/lib/arrow/lease.d.ts.map +1 -0
  10. package/dist/lib/arrow/lease.js +33 -0
  11. package/dist/lib/arrow/utils.d.ts +3 -4
  12. package/dist/lib/arrow/utils.d.ts.map +1 -1
  13. package/dist/lib/arrow/utils.js +12 -13
  14. package/dist/lib/arrow/vocabularyDictionary.d.ts +21 -0
  15. package/dist/lib/arrow/vocabularyDictionary.d.ts.map +1 -0
  16. package/dist/lib/arrow/vocabularyDictionary.js +95 -0
  17. package/dist/lib/bufferStrategy.d.ts +3 -2
  18. package/dist/lib/bufferStrategy.d.ts.map +1 -1
  19. package/dist/lib/cloudflare/traceRows.d.ts.map +1 -1
  20. package/dist/lib/cloudflare/traceRows.js +3 -2
  21. package/dist/lib/codegen/evaluatorGenerator.d.ts.map +1 -1
  22. package/dist/lib/codegen/evaluatorGenerator.js +12 -11
  23. package/dist/lib/codegen/fixedPositionWriterGenerator.d.ts +44 -82
  24. package/dist/lib/codegen/fixedPositionWriterGenerator.d.ts.map +1 -1
  25. package/dist/lib/codegen/fixedPositionWriterGenerator.js +223 -162
  26. package/dist/lib/codegen/spanLoggerClosureMaterializer.d.ts +63 -0
  27. package/dist/lib/codegen/spanLoggerClosureMaterializer.d.ts.map +1 -0
  28. package/dist/lib/codegen/spanLoggerClosureMaterializer.js +447 -0
  29. package/dist/lib/codegen/spanLoggerGenerator.d.ts +36 -19
  30. package/dist/lib/codegen/spanLoggerGenerator.d.ts.map +1 -1
  31. package/dist/lib/codegen/spanLoggerGenerator.js +164 -158
  32. package/dist/lib/convertToArrow.d.ts +7 -2
  33. package/dist/lib/convertToArrow.d.ts.map +1 -1
  34. package/dist/lib/convertToArrow.js +499 -285
  35. package/dist/lib/defineOpContext.js +1 -1
  36. package/dist/lib/enumMetadata.d.ts +19 -0
  37. package/dist/lib/enumMetadata.d.ts.map +1 -0
  38. package/dist/lib/enumMetadata.js +86 -0
  39. package/dist/lib/flushScheduler.d.ts.map +1 -1
  40. package/dist/lib/flushScheduler.js +13 -4
  41. package/dist/lib/library.d.ts +16 -98
  42. package/dist/lib/library.d.ts.map +1 -1
  43. package/dist/lib/library.js +32 -525
  44. package/dist/lib/logBinding.d.ts +21 -27
  45. package/dist/lib/logBinding.d.ts.map +1 -1
  46. package/dist/lib/op.d.ts +30 -30
  47. package/dist/lib/op.d.ts.map +1 -1
  48. package/dist/lib/op.js +15 -22
  49. package/dist/lib/opContext/createOpGroup.d.ts +1 -1
  50. package/dist/lib/opContext/createOpGroup.d.ts.map +1 -1
  51. package/dist/lib/opContext/createOpGroup.js +22 -29
  52. package/dist/lib/opContext/defineOp.d.ts +4 -4
  53. package/dist/lib/opContext/defineOp.d.ts.map +1 -1
  54. package/dist/lib/opContext/defineOp.js +53 -10
  55. package/dist/lib/opContext/opGroupTypes.d.ts +2 -2
  56. package/dist/lib/opContext/opTypes.d.ts +1 -1
  57. package/dist/lib/opContext/opTypes.d.ts.map +1 -1
  58. package/dist/lib/opContext/spanContextTypes.d.ts +29 -14
  59. package/dist/lib/opContext/spanContextTypes.d.ts.map +1 -1
  60. package/dist/lib/opContext/types.d.ts +4 -4
  61. package/dist/lib/opContext/types.d.ts.map +1 -1
  62. package/dist/lib/physicalLayoutPlan.d.ts +89 -0
  63. package/dist/lib/physicalLayoutPlan.d.ts.map +1 -0
  64. package/dist/lib/physicalLayoutPlan.js +442 -0
  65. package/dist/lib/resolveMessage.d.ts +9 -0
  66. package/dist/lib/resolveMessage.d.ts.map +1 -0
  67. package/dist/lib/resolveMessage.js +63 -0
  68. package/dist/lib/result.d.ts +5 -7
  69. package/dist/lib/result.d.ts.map +1 -1
  70. package/dist/lib/result.js +19 -26
  71. package/dist/lib/runtimeHint.d.ts +35 -0
  72. package/dist/lib/runtimeHint.d.ts.map +1 -0
  73. package/dist/lib/runtimeHint.js +79 -0
  74. package/dist/lib/schema/types.d.ts +1 -1
  75. package/dist/lib/schema/types.d.ts.map +1 -1
  76. package/dist/lib/span-buffer/aot/v1.d.ts +2 -0
  77. package/dist/lib/span-buffer/aot/v1.d.ts.map +1 -0
  78. package/dist/lib/span-buffer/aot/v1.js +24 -0
  79. package/dist/lib/spanBuffer.d.ts +33 -4
  80. package/dist/lib/spanBuffer.d.ts.map +1 -1
  81. package/dist/lib/spanBuffer.js +583 -91
  82. package/dist/lib/spanBufferStats.d.ts +1 -1
  83. package/dist/lib/spanBufferStats.d.ts.map +1 -1
  84. package/dist/lib/spanContext.d.ts +27 -51
  85. package/dist/lib/spanContext.d.ts.map +1 -1
  86. package/dist/lib/spanContext.js +822 -344
  87. package/dist/lib/sqlite/sqlite-common.d.ts.map +1 -1
  88. package/dist/lib/sqlite/sqlite-common.js +20 -21
  89. package/dist/lib/testing/bun-harness.d.ts +8 -3
  90. package/dist/lib/testing/bun-harness.d.ts.map +1 -1
  91. package/dist/lib/testing/bun-harness.js +30 -18
  92. package/dist/lib/testing/extractFacts.d.ts +1 -1
  93. package/dist/lib/testing/extractFacts.d.ts.map +1 -1
  94. package/dist/lib/testing/extractFacts.js +55 -63
  95. package/dist/lib/testing/facts.d.ts +1 -1
  96. package/dist/lib/testing/facts.d.ts.map +1 -1
  97. package/dist/lib/testing/queryable-span.d.ts +1 -1
  98. package/dist/lib/testing/queryable-span.d.ts.map +1 -1
  99. package/dist/lib/testing/queryable-span.js +26 -28
  100. package/dist/lib/testing/span-query.d.ts.map +1 -1
  101. package/dist/lib/testing/span-query.js +19 -22
  102. package/dist/lib/testing/stdio-replay.d.ts.map +1 -1
  103. package/dist/lib/testing/stdio-replay.js +19 -8
  104. package/dist/lib/threadId.d.ts.map +1 -1
  105. package/dist/lib/threadId.js +6 -3
  106. package/dist/lib/traceRoot.d.ts +30 -8
  107. package/dist/lib/traceRoot.d.ts.map +1 -1
  108. package/dist/lib/traceRoot.es.d.ts +17 -15
  109. package/dist/lib/traceRoot.es.d.ts.map +1 -1
  110. package/dist/lib/traceRoot.es.js +68 -34
  111. package/dist/lib/traceRoot.js +11 -0
  112. package/dist/lib/traceRoot.node.d.ts +17 -19
  113. package/dist/lib/traceRoot.node.d.ts.map +1 -1
  114. package/dist/lib/traceRoot.node.js +66 -38
  115. package/dist/lib/traceTopology.d.ts +42 -0
  116. package/dist/lib/traceTopology.d.ts.map +1 -0
  117. package/dist/lib/traceTopology.js +260 -0
  118. package/dist/lib/tracer.d.ts +9 -5
  119. package/dist/lib/tracer.d.ts.map +1 -1
  120. package/dist/lib/tracer.js +65 -71
  121. package/dist/lib/tracers/ArrayQueueTracer.d.ts +1 -1
  122. package/dist/lib/tracers/ArrayQueueTracer.js +1 -1
  123. package/dist/lib/tracers/StdioTracer.d.ts.map +1 -1
  124. package/dist/lib/tracers/StdioTracer.js +15 -13
  125. package/dist/lib/tracers/TestTracer.d.ts +4 -6
  126. package/dist/lib/tracers/TestTracer.d.ts.map +1 -1
  127. package/dist/lib/tracers/TestTracer.js +6 -8
  128. package/dist/lib/types.d.ts +49 -32
  129. package/dist/lib/types.d.ts.map +1 -1
  130. package/dist/lib/vocabularyRegistry.d.ts +25 -0
  131. package/dist/lib/vocabularyRegistry.d.ts.map +1 -0
  132. package/dist/lib/vocabularyRegistry.js +372 -0
  133. package/dist/lib/wasm/WasmBufferStrategy.d.ts +6 -10
  134. package/dist/lib/wasm/WasmBufferStrategy.d.ts.map +1 -1
  135. package/dist/lib/wasm/WasmBufferStrategy.js +44 -37
  136. package/dist/lib/wasm/wasmAllocator.d.ts +27 -22
  137. package/dist/lib/wasm/wasmAllocator.d.ts.map +1 -1
  138. package/dist/lib/wasm/wasmAllocator.js +66 -44
  139. package/dist/lib/wasm/wasmPhysicalLayout.d.ts +67 -0
  140. package/dist/lib/wasm/wasmPhysicalLayout.d.ts.map +1 -0
  141. package/dist/lib/wasm/wasmPhysicalLayout.js +179 -0
  142. package/dist/lib/wasm/wasmSpanBuffer.d.ts +79 -31
  143. package/dist/lib/wasm/wasmSpanBuffer.d.ts.map +1 -1
  144. package/dist/lib/wasm/wasmSpanBuffer.js +442 -272
  145. package/dist/lib/wasm/wasmTraceRoot.d.ts +24 -22
  146. package/dist/lib/wasm/wasmTraceRoot.d.ts.map +1 -1
  147. package/dist/lib/wasm/wasmTraceRoot.js +110 -71
  148. package/dist/vocabulary/register/v1.d.ts +2 -0
  149. package/dist/vocabulary/register/v1.d.ts.map +1 -0
  150. package/dist/vocabulary/register/v1.js +1 -0
  151. package/dist/wasm.d.ts +5 -0
  152. package/dist/wasm.d.ts.map +1 -0
  153. package/dist/wasm.js +4 -0
  154. package/package.json +54 -6
  155. package/src/index.ts +1 -0
  156. package/src/lib/JsBufferStrategy.ts +27 -9
  157. package/src/lib/__tests__/arrow-builder-integration/arrow-conversion.test.ts +335 -37
  158. package/src/lib/__tests__/arrow-builder-integration/binary-arrow-conversion.test.ts +97 -26
  159. package/src/lib/__tests__/arrow-builder-integration/binary-comparison.test.ts +75 -15
  160. package/src/lib/__tests__/arrow-builder-integration/binary-format-compliance.test.ts +30 -6
  161. package/src/lib/__tests__/arrow-builder-integration/buffer-chaining.test.ts +18 -21
  162. package/src/lib/__tests__/arrow-builder-integration/foundation.test.ts +5 -2
  163. package/src/lib/__tests__/arrow-builder-integration/integration.test.ts +25 -7
  164. package/src/lib/__tests__/arrow-builder-integration/lazy-columns.test.ts +3 -2
  165. package/src/lib/__tests__/buffer-overflow.test.ts +24 -18
  166. package/src/lib/__tests__/capability-span-context.test.ts +505 -0
  167. package/src/lib/__tests__/capacity-tuning.test.ts +5 -3
  168. package/src/lib/__tests__/eager-columns.test.ts +345 -0
  169. package/src/lib/__tests__/enum-metadata-aot.test.ts +153 -0
  170. package/src/lib/__tests__/enum-metadata.test.ts +177 -0
  171. package/src/lib/__tests__/fixtures/empty-vocabulary-runtime.fixture.ts +15 -0
  172. package/src/lib/__tests__/flushScheduler.test.ts +5 -1
  173. package/src/lib/__tests__/identity-view.property.test.ts +179 -0
  174. package/src/lib/__tests__/library.test.ts +19 -235
  175. package/src/lib/__tests__/message-buffer-families.test.ts +878 -0
  176. package/src/lib/__tests__/nested-library-tasks.test.ts +14 -15
  177. package/src/lib/__tests__/op-metadata.test.ts +1 -1
  178. package/src/lib/__tests__/op-type-safety.test.ts +5 -16
  179. package/src/lib/__tests__/physical-layout-plan.test.ts +333 -0
  180. package/src/lib/__tests__/remap-descriptor-integration.test.ts +381 -0
  181. package/src/lib/__tests__/root-context-construction.test.ts +414 -0
  182. package/src/lib/__tests__/span-auto-runtime.test.ts +386 -0
  183. package/src/lib/__tests__/span-buffer-segment-stats.test.ts +221 -0
  184. package/src/lib/__tests__/span-lifecycle.test.ts +53 -25
  185. package/src/lib/__tests__/spanbuffer-materializer-parity.test.ts +266 -0
  186. package/src/lib/__tests__/sync-async-trace.test.ts +24 -23
  187. package/src/lib/__tests__/tag-writer-aot.test.ts +214 -0
  188. package/src/lib/__tests__/test-helpers.ts +67 -12
  189. package/src/lib/__tests__/threadId.test.ts +62 -0
  190. package/src/lib/__tests__/timestamp.test.ts +221 -93
  191. package/src/lib/__tests__/traceTopology.property.test.ts +383 -0
  192. package/src/lib/__tests__/type-narrowing.test.ts +63 -0
  193. package/src/lib/__tests__/vocabulary-dense-decoding.test.ts +405 -0
  194. package/src/lib/__tests__/vocabularyRegistry.test.ts +313 -0
  195. package/src/lib/arrow/lease.ts +43 -0
  196. package/src/lib/arrow/utils.ts +10 -14
  197. package/src/lib/arrow/vocabularyDictionary.ts +123 -0
  198. package/src/lib/bufferStrategy.ts +9 -1
  199. package/src/lib/cloudflare/traceRows.ts +3 -2
  200. package/src/lib/codegen/__tests__/evaluatorGenerator.test.ts +21 -141
  201. package/src/lib/codegen/__tests__/fixedPositionWriter-parity.test.ts +273 -0
  202. package/src/lib/codegen/__tests__/fixedPositionWriter-uint64.test.ts +61 -34
  203. package/src/lib/codegen/__tests__/nullBitmap.test.ts +33 -37
  204. package/src/lib/codegen/__tests__/spanLoggerGenerator.test.ts +34 -18
  205. package/src/lib/codegen/__tests__/spanLoggerMaterializerParity.test.ts +503 -0
  206. package/src/lib/codegen/evaluatorGenerator.ts +18 -13
  207. package/src/lib/codegen/fixedPositionWriterGenerator.ts +329 -245
  208. package/src/lib/codegen/spanLoggerClosureMaterializer.ts +598 -0
  209. package/src/lib/codegen/spanLoggerGenerator.ts +241 -177
  210. package/src/lib/convertToArrow.ts +598 -311
  211. package/src/lib/defineOpContext.ts +1 -1
  212. package/src/lib/enumMetadata.ts +121 -0
  213. package/src/lib/flushScheduler.ts +11 -5
  214. package/src/lib/library.ts +37 -595
  215. package/src/lib/logBinding.ts +21 -27
  216. package/src/lib/op.ts +75 -18
  217. package/src/lib/opContext/__tests__/retry.test.ts +27 -21
  218. package/src/lib/opContext/createOpGroup.ts +33 -31
  219. package/src/lib/opContext/defineOp.ts +88 -11
  220. package/src/lib/opContext/opGroupTypes.ts +2 -2
  221. package/src/lib/opContext/opTypes.ts +1 -1
  222. package/src/lib/opContext/spanContextTypes.ts +98 -17
  223. package/src/lib/opContext/types.ts +7 -3
  224. package/src/lib/physicalLayoutPlan.ts +598 -0
  225. package/src/lib/resolveMessage.ts +67 -0
  226. package/src/lib/result.ts +20 -33
  227. package/src/lib/runtimeHint.ts +98 -0
  228. package/src/lib/schema/__tests__/featureFlags.test.ts +2 -1
  229. package/src/lib/schema/builder.ts +1 -1
  230. package/src/lib/schema/types.ts +1 -1
  231. package/src/lib/span-buffer/aot/v1.ts +26 -0
  232. package/src/lib/spanBuffer.ts +658 -92
  233. package/src/lib/spanBufferStats.ts +1 -1
  234. package/src/lib/spanContext.ts +1350 -861
  235. package/src/lib/sqlite/sqlite-common.ts +20 -31
  236. package/src/lib/testing/__tests__/topology-traversal.test.ts +174 -0
  237. package/src/lib/testing/__tests__/vitest-harness.test.ts +2 -1
  238. package/src/lib/testing/bun-harness.ts +94 -43
  239. package/src/lib/testing/extractFacts.ts +64 -78
  240. package/src/lib/testing/facts.ts +1 -1
  241. package/src/lib/testing/queryable-span.ts +23 -30
  242. package/src/lib/testing/span-query.ts +16 -20
  243. package/src/lib/testing/stdio-replay.ts +25 -8
  244. package/src/lib/testing/vitest-harness.ts +1 -1
  245. package/src/lib/threadId.ts +6 -3
  246. package/src/lib/traceRoot.es.ts +77 -42
  247. package/src/lib/traceRoot.node.ts +75 -46
  248. package/src/lib/traceRoot.ts +41 -14
  249. package/src/lib/traceTopology.ts +279 -0
  250. package/src/lib/tracer.ts +109 -98
  251. package/src/lib/tracers/ArrayQueueTracer.ts +1 -1
  252. package/src/lib/tracers/StdioTracer.ts +15 -13
  253. package/src/lib/tracers/TestTracer.ts +6 -8
  254. package/src/lib/tracers/__tests__/ArrayQueueTracer.test.ts +15 -10
  255. package/src/lib/tracers/__tests__/TestTracer.test.ts +23 -13
  256. package/src/lib/types.ts +64 -33
  257. package/src/lib/vocabularyRegistry.ts +439 -0
  258. package/src/lib/wasm/WasmBufferStrategy.ts +56 -48
  259. package/src/lib/wasm/__tests__/WasmBufferStrategy.test.ts +215 -11
  260. package/src/lib/wasm/__tests__/allocatorFreeBlockClamp.test.ts +86 -0
  261. package/src/lib/wasm/__tests__/wasm-identity-view.test.ts +311 -0
  262. package/src/lib/wasm/__tests__/wasm-integration.test.ts +417 -44
  263. package/src/lib/wasm/__tests__/wasm-message-buffer-families.test.ts +588 -0
  264. package/src/lib/wasm/__tests__/wasmAllocator.test.ts +100 -668
  265. package/src/lib/wasm/__tests__/wasmSpanBuffer.test.ts +419 -63
  266. package/src/lib/wasm/__tests__/wasmTraceRoot.test.ts +24 -29
  267. package/src/lib/wasm/__tests__/wasmTracer.test.ts.disabled +2 -2
  268. package/src/lib/wasm/wasmAllocator.ts +131 -96
  269. package/src/lib/wasm/wasmPhysicalLayout.ts +309 -0
  270. package/src/lib/wasm/wasmSpanBuffer.ts +597 -341
  271. package/src/lib/wasm/wasmTraceRoot.ts +140 -78
  272. package/src/vocabulary/register/v1.ts +1 -0
  273. package/src/wasm.ts +19 -0
  274. package/src/lib/__tests__/remapped-buffer-view-integration.test.ts +0 -319
  275. package/src/lib/__tests__/remapped-buffer-view.test.ts +0 -635
  276. package/src/lib/wasm/allocator.zig +0 -948
@@ -0,0 +1,313 @@
1
+ import { describe, expect, it } from 'bun:test';
2
+ import { createHash } from 'node:crypto';
3
+ import {
4
+ getVocabularyGeneration,
5
+ registerVocabularyFragment,
6
+ type VocabularyFragment,
7
+ type VocabularyGeneration,
8
+ VocabularyRegistrationError,
9
+ } from '../vocabularyRegistry.js';
10
+
11
+ const encoder = new TextEncoder();
12
+ const decoder = new TextDecoder();
13
+
14
+ type RecordField = { name: string; column: string };
15
+ type RecordInput = { text: string; fields?: readonly RecordField[] };
16
+
17
+ function encodeRecord({ text, fields = [] }: RecordInput): Uint8Array {
18
+ const textBytes = encoder.encode(text);
19
+ const encodedFields = fields.map(({ name, column }) => ({
20
+ name: encoder.encode(name),
21
+ column: encoder.encode(column),
22
+ }));
23
+ let byteLength = 4 + textBytes.length + 2;
24
+ for (const field of encodedFields) byteLength += 2 + field.name.length + 2 + field.column.length;
25
+ const record = new Uint8Array(byteLength);
26
+ const view = new DataView(record.buffer);
27
+ let offset = 0;
28
+ view.setUint32(offset, textBytes.length, true);
29
+ offset += 4;
30
+ record.set(textBytes, offset);
31
+ offset += textBytes.length;
32
+ view.setUint16(offset, encodedFields.length, true);
33
+ offset += 2;
34
+ for (const field of encodedFields) {
35
+ view.setUint16(offset, field.name.length, true);
36
+ offset += 2;
37
+ record.set(field.name, offset);
38
+ offset += field.name.length;
39
+ view.setUint16(offset, field.column.length, true);
40
+ offset += 2;
41
+ record.set(field.column, offset);
42
+ offset += field.column.length;
43
+ }
44
+ return record;
45
+ }
46
+
47
+ function stableId(kindTag: number, record: Uint8Array): number {
48
+ const digest = createHash('sha256').update(Uint8Array.of(kindTag)).update(record).digest();
49
+ return (digest[0] << 16) | (digest[1] << 8) | digest[2];
50
+ }
51
+
52
+ function fragmentHash(fragment: Omit<VocabularyFragment, 'contentHash'>): string {
53
+ const algorithm = encoder.encode(fragment.idAlgorithm);
54
+ const byteLength =
55
+ 1 +
56
+ 2 +
57
+ algorithm.length +
58
+ 4 +
59
+ fragment.ids.length * 4 +
60
+ 4 +
61
+ fragment.kindTags.length +
62
+ 4 +
63
+ fragment.utf8.length +
64
+ 4 +
65
+ fragment.offsets.length * 4;
66
+ const bytes = new Uint8Array(byteLength);
67
+ const view = new DataView(bytes.buffer);
68
+ let offset = 0;
69
+ bytes[offset++] = fragment.schemaVersion;
70
+ view.setUint16(offset, algorithm.length, true);
71
+ offset += 2;
72
+ bytes.set(algorithm, offset);
73
+ offset += algorithm.length;
74
+ view.setUint32(offset, fragment.ids.length, true);
75
+ offset += 4;
76
+ for (const id of fragment.ids) {
77
+ view.setUint32(offset, id, true);
78
+ offset += 4;
79
+ }
80
+ view.setUint32(offset, fragment.kindTags.length, true);
81
+ offset += 4;
82
+ bytes.set(fragment.kindTags, offset);
83
+ offset += fragment.kindTags.length;
84
+ view.setUint32(offset, fragment.utf8.length, true);
85
+ offset += 4;
86
+ bytes.set(fragment.utf8, offset);
87
+ offset += fragment.utf8.length;
88
+ view.setUint32(offset, fragment.offsets.length, true);
89
+ offset += 4;
90
+ for (const boundary of fragment.offsets) {
91
+ view.setInt32(offset, boundary, true);
92
+ offset += 4;
93
+ }
94
+ return createHash('sha256').update(bytes).digest('hex');
95
+ }
96
+
97
+ function makeFragment(
98
+ inputs: readonly RecordInput[],
99
+ kindTags: readonly number[] = inputs.map(() => 1),
100
+ ): VocabularyFragment {
101
+ const records = inputs.map(encodeRecord);
102
+ const offsets = new Int32Array(records.length + 1);
103
+ let byteLength = 0;
104
+ for (let ordinal = 0; ordinal < records.length; ordinal++) {
105
+ byteLength += records[ordinal].length;
106
+ offsets[ordinal + 1] = byteLength;
107
+ }
108
+ const utf8 = new Uint8Array(byteLength);
109
+ let offset = 0;
110
+ for (const record of records) {
111
+ utf8.set(record, offset);
112
+ offset += record.length;
113
+ }
114
+ const withoutHash: Omit<VocabularyFragment, 'contentHash'> = {
115
+ schemaVersion: 1,
116
+ idAlgorithm: 'sha256-24-v1',
117
+ ids: Uint32Array.from(records, (record, ordinal) => stableId(kindTags[ordinal], record)),
118
+ kindTags: Uint8Array.from(kindTags),
119
+ utf8,
120
+ offsets,
121
+ };
122
+ return { ...withoutHash, contentHash: fragmentHash(withoutHash) };
123
+ }
124
+
125
+ function textAt(generation: VocabularyGeneration, denseIndex: number): string {
126
+ const record = generation.records.subarray(generation.offsets[denseIndex], generation.offsets[denseIndex + 1]);
127
+ const textLength = new DataView(record.buffer, record.byteOffset, record.byteLength).getUint32(0, true);
128
+ return decoder.decode(record.subarray(4, 4 + textLength));
129
+ }
130
+
131
+ function decodedBinding(binding: Uint32Array): string[] {
132
+ const generation = getVocabularyGeneration();
133
+ return Array.from(binding, (denseIndex) => textAt(generation, denseIndex));
134
+ }
135
+
136
+ function expectRegistrationError(fragment: unknown, code: string): void {
137
+ let thrown: unknown;
138
+ try {
139
+ Reflect.apply(registerVocabularyFragment, undefined, [fragment]);
140
+ } catch (error) {
141
+ thrown = error;
142
+ }
143
+ expect(thrown).toBeInstanceOf(VocabularyRegistrationError);
144
+ if (!(thrown instanceof VocabularyRegistrationError)) throw new Error(`expected ${code}`);
145
+ expect(thrown.message.startsWith(`${code}:`)).toBe(true);
146
+ }
147
+
148
+ describe('vocabulary fragment registration', () => {
149
+ it('binds fragment ordinals directly while duplicate and overlapping registration preserve append-only generations', () => {
150
+ const alpha = { text: 'registry overlap alpha', fields: [{ name: 'request.id', column: 'request_id' }] };
151
+ const beta = { text: 'registry overlap beta' };
152
+ const gamma = { text: 'registry overlap gamma', fields: [{ name: 'user', column: 'user_name' }] };
153
+ const firstFragment = makeFragment([alpha, beta], [1, 2]);
154
+ const overlappingFragment = makeFragment([beta, gamma], [2, 1]);
155
+
156
+ const before = getVocabularyGeneration();
157
+ const firstBinding = registerVocabularyFragment(firstFragment);
158
+ const firstGeneration = getVocabularyGeneration();
159
+ expect(firstGeneration.generation).toBe(before.generation + 1);
160
+ expect(decodedBinding(firstBinding)).toEqual([alpha.text, beta.text]);
161
+
162
+ const firstSnapshot = {
163
+ ids: Array.from(firstGeneration.ids),
164
+ kinds: Array.from(firstGeneration.kindTags),
165
+ records: Array.from(firstGeneration.records),
166
+ offsets: Array.from(firstGeneration.offsets),
167
+ };
168
+ const overlapBinding = registerVocabularyFragment(overlappingFragment);
169
+ const secondGeneration = getVocabularyGeneration();
170
+
171
+ expect(overlapBinding[0]).toBe(firstBinding[1]);
172
+ expect(decodedBinding(overlapBinding)).toEqual([beta.text, gamma.text]);
173
+ expect(secondGeneration.generation).toBe(firstGeneration.generation + 1);
174
+ expect(Array.from(secondGeneration.ids.subarray(0, firstGeneration.ids.length))).toEqual(firstSnapshot.ids);
175
+ expect(Array.from(secondGeneration.kindTags.subarray(0, firstGeneration.kindTags.length))).toEqual(
176
+ firstSnapshot.kinds,
177
+ );
178
+ expect(Array.from(secondGeneration.records.subarray(0, firstGeneration.records.length))).toEqual(
179
+ firstSnapshot.records,
180
+ );
181
+ expect(Array.from(secondGeneration.offsets.subarray(0, firstGeneration.offsets.length))).toEqual(
182
+ firstSnapshot.offsets,
183
+ );
184
+ expect(Array.from(firstGeneration.ids)).toEqual(firstSnapshot.ids);
185
+ expect(Array.from(firstGeneration.kindTags)).toEqual(firstSnapshot.kinds);
186
+ expect(Array.from(firstGeneration.records)).toEqual(firstSnapshot.records);
187
+ expect(Array.from(firstGeneration.offsets)).toEqual(firstSnapshot.offsets);
188
+
189
+ const duplicateBinding = registerVocabularyFragment(firstFragment);
190
+ expect(duplicateBinding).toBe(firstBinding);
191
+ expect(getVocabularyGeneration()).toBe(secondGeneration);
192
+ });
193
+
194
+ it('decodes each fragment in ordinal order regardless of the records registration order', () => {
195
+ const left = { text: 'registry ordinal left' };
196
+ const right = { text: 'registry ordinal right' };
197
+ const forward = makeFragment([left, right]);
198
+ const reverse = makeFragment([right, left]);
199
+
200
+ const forwardBinding = registerVocabularyFragment(forward);
201
+ const generationAfterForward = getVocabularyGeneration();
202
+ const reverseBinding = registerVocabularyFragment(reverse);
203
+
204
+ expect(decodedBinding(forwardBinding)).toEqual([left.text, right.text]);
205
+ expect(decodedBinding(reverseBinding)).toEqual([right.text, left.text]);
206
+ expect(Array.from(reverseBinding)).toEqual([forwardBinding[1], forwardBinding[0]]);
207
+ expect(getVocabularyGeneration()).toBe(generationAfterForward);
208
+ });
209
+
210
+ it('rejects malformed ABI, offsets, kind tags, record-derived IDs, UTF-8, and payload hashes before mutation', () => {
211
+ const valid = makeFragment([{ text: 'registry validation sentinel' }]);
212
+ const generation = getVocabularyGeneration();
213
+ const malformedCases: Array<{ name: string; fragment: unknown; code: string }> = [
214
+ {
215
+ name: 'schema version',
216
+ fragment: { ...valid, schemaVersion: 2 },
217
+ code: 'LMAO_VOCABULARY_ABI_UNAVAILABLE',
218
+ },
219
+ {
220
+ name: 'ID algorithm',
221
+ fragment: { ...valid, idAlgorithm: 'sha256-24-v2' },
222
+ code: 'LMAO_VOCABULARY_ABI_UNAVAILABLE',
223
+ },
224
+ {
225
+ name: 'offset coverage',
226
+ fragment: { ...valid, offsets: new Int32Array([1, valid.utf8.length]) },
227
+ code: 'LMAO_VOCABULARY_FRAGMENT_INVALID',
228
+ },
229
+ {
230
+ name: 'nonmonotonic offsets',
231
+ fragment: (() => {
232
+ const malformed = makeFragment([
233
+ { text: 'registry offset first' },
234
+ { text: 'registry offset second' },
235
+ { text: 'registry offset third' },
236
+ ]);
237
+ return {
238
+ ...malformed,
239
+ offsets: new Int32Array([0, malformed.offsets[2], malformed.offsets[1], malformed.utf8.length]),
240
+ };
241
+ })(),
242
+ code: 'LMAO_VOCABULARY_FRAGMENT_INVALID',
243
+ },
244
+ {
245
+ name: 'unknown kind',
246
+ fragment: makeFragment([{ text: 'registry unknown kind' }], [3]),
247
+ code: 'LMAO_VOCABULARY_FRAGMENT_INVALID',
248
+ },
249
+ {
250
+ name: 'record-derived ID mismatch',
251
+ fragment: (() => {
252
+ const mismatch = makeFragment([{ text: 'registry wrong stable ID' }]);
253
+ const withoutHash = { ...mismatch, ids: Uint32Array.of(mismatch.ids[0] ^ 1) };
254
+ return { ...withoutHash, contentHash: fragmentHash(withoutHash) };
255
+ })(),
256
+ code: 'LMAO_VOCABULARY_FRAGMENT_INVALID',
257
+ },
258
+ {
259
+ name: 'invalid UTF-8 text',
260
+ fragment: (() => {
261
+ const malformed = makeFragment([{ text: 'registry malformed UTF-8' }]);
262
+ const utf8 = malformed.utf8.slice();
263
+ utf8[4] = 0xff;
264
+ return { ...malformed, utf8 };
265
+ })(),
266
+ code: 'LMAO_VOCABULARY_FRAGMENT_INVALID',
267
+ },
268
+ {
269
+ name: 'payload hash mismatch',
270
+ fragment: { ...valid, contentHash: '0'.repeat(64) },
271
+ code: 'LMAO_VOCABULARY_CONTENT_HASH_MISMATCH',
272
+ },
273
+ ];
274
+
275
+ for (const testCase of malformedCases) {
276
+ try {
277
+ expectRegistrationError(testCase.fragment, testCase.code);
278
+ } catch (error) {
279
+ throw new Error(`case ${testCase.name} failed`, { cause: error });
280
+ }
281
+ expect(getVocabularyGeneration()).toBe(generation);
282
+ }
283
+ });
284
+
285
+ it('rejects reuse of a registered content hash with a different payload before mutation', () => {
286
+ const registered = makeFragment([{ text: 'registry content hash owner' }]);
287
+ registerVocabularyFragment(registered);
288
+ const generation = getVocabularyGeneration();
289
+ const conflictingPayload = makeFragment([{ text: 'registry content hash impostor' }]);
290
+
291
+ expectRegistrationError(
292
+ { ...conflictingPayload, contentHash: registered.contentHash },
293
+ 'LMAO_VOCABULARY_CONTENT_HASH_COLLISION',
294
+ );
295
+ expect(getVocabularyGeneration()).toBe(generation);
296
+ expect(decodedBinding(registerVocabularyFragment(registered))).toEqual(['registry content hash owner']);
297
+ });
298
+
299
+ it('rejects a stable-ID collision without partially registering the fragment', () => {
300
+ const firstRecord = { text: 'stable collision fixture 585' };
301
+ const secondRecord = { text: 'stable collision fixture 4248' };
302
+ const first = makeFragment([firstRecord]);
303
+ const second = makeFragment([secondRecord]);
304
+
305
+ expect(first.ids[0]).toBe(15_813_490);
306
+ expect(second.ids[0]).toBe(first.ids[0]);
307
+ registerVocabularyFragment(first);
308
+ const generation = getVocabularyGeneration();
309
+ expectRegistrationError(second, 'LMAO_VOCABULARY_ID_COLLISION');
310
+ expect(getVocabularyGeneration()).toBe(generation);
311
+ expect(decodedBinding(registerVocabularyFragment(first))).toEqual([firstRecord.text]);
312
+ });
313
+ });
@@ -0,0 +1,43 @@
1
+ import type { Table } from '@uwdata/flechette';
2
+
3
+ export interface ArrowLease {
4
+ readonly table: Table;
5
+ readonly released: boolean;
6
+ release(): void;
7
+ [Symbol.dispose](): void;
8
+ }
9
+
10
+ /** Internal constructor for a lease over borrowed Arrow source chunks. */
11
+ export function createArrowLease(
12
+ table: Table,
13
+ pinned: readonly unknown[],
14
+ releases: readonly (() => void)[],
15
+ ): ArrowLease {
16
+ let released = false;
17
+ // Retained by the closures for the full lease lifetime. Clearing this array on
18
+ // release makes the backing epochs and dictionary generation collectible.
19
+ const retained = [...pinned];
20
+ return Object.freeze({
21
+ table,
22
+ get released() {
23
+ return released;
24
+ },
25
+ release(): void {
26
+ if (released) return;
27
+ released = true;
28
+ let failure: unknown;
29
+ for (let index = releases.length - 1; index >= 0; index--) {
30
+ try {
31
+ releases[index]();
32
+ } catch (error) {
33
+ failure ??= error;
34
+ }
35
+ }
36
+ retained.length = 0;
37
+ if (failure !== undefined) throw failure;
38
+ },
39
+ [Symbol.dispose](): void {
40
+ this.release();
41
+ },
42
+ });
43
+ }
@@ -3,6 +3,7 @@
3
3
  */
4
4
 
5
5
  import { copyBits, countNulls } from '@smoothbricks/arrow-builder';
6
+ import { Column } from '@uwdata/flechette';
6
7
  import type { AnySpanBuffer } from '../types.js';
7
8
 
8
9
  const UTF8_ENCODER = new TextEncoder();
@@ -104,23 +105,18 @@ export function concatenateNullBitmaps(buffers: AnySpanBuffer[], columnName: str
104
105
  return { nullBitmap, nullCount };
105
106
  }
106
107
 
108
+ /** Combine Arrow-native batches by copying only batch references, never values. */
109
+ export function combineColumnChunks(chunks: readonly Column<unknown>[]): Column<unknown> {
110
+ if (chunks.length === 0) throw new Error('Cannot combine an empty Arrow chunk list');
111
+ if (chunks.length === 1) return chunks[0];
112
+ const batches = [];
113
+ for (const chunk of chunks) batches.push(...chunk.data);
114
+ return new Column(batches);
115
+ }
116
+
107
117
  /**
108
118
  * Get Arrow field name from LMAO field name
109
119
  */
110
120
  export function getArrowFieldName(fieldName: string): string {
111
121
  return fieldName;
112
122
  }
113
-
114
- /**
115
- * Walk a SpanBuffer tree (including overflow chains and children)
116
- */
117
- export function walkSpanTree(root: AnySpanBuffer, visitor: (buffer: AnySpanBuffer) => void): void {
118
- let current: AnySpanBuffer | undefined = root;
119
- while (current) {
120
- visitor(current);
121
- current = current._overflow;
122
- }
123
- for (const child of root._children) {
124
- walkSpanTree(child, visitor);
125
- }
126
- }
@@ -0,0 +1,123 @@
1
+ import { Column, utf8 } from '@uwdata/flechette';
2
+ import { ENTRY_TYPE_NAMES } from '../schema/systemSchema.js';
3
+ import type { VocabularyGeneration } from '../vocabularyRegistry.js';
4
+ import { makeArrowColumn } from './flechette.js';
5
+
6
+ const ENTRY_TYPE_UTF8 = new TextEncoder().encode(ENTRY_TYPE_NAMES.join(''));
7
+ const ENTRY_TYPE_OFFSETS = new Int32Array(ENTRY_TYPE_NAMES.length + 1);
8
+ for (let index = 0, offset = 0; index < ENTRY_TYPE_NAMES.length; index++) {
9
+ ENTRY_TYPE_OFFSETS[index] = offset;
10
+ offset += ENTRY_TYPE_NAMES[index].length;
11
+ ENTRY_TYPE_OFFSETS[index + 1] = offset;
12
+ }
13
+
14
+ function makeUtf8Dictionary(length: number, offsets: Int32Array, values: Uint8Array): Column<unknown> {
15
+ return makeArrowColumn({
16
+ type: utf8(),
17
+ length,
18
+ nullCount: 0,
19
+ offsets,
20
+ values,
21
+ });
22
+ }
23
+
24
+ const EMPTY_DICTIONARY_BYTES = new Uint8Array(0);
25
+ const EMPTY_DICTIONARY_OFFSETS = new Int32Array([0, 0]);
26
+ const decoder = new TextDecoder('utf-8', { fatal: true });
27
+
28
+ export interface VocabularyDictionaryPrefix {
29
+ readonly column: Column<unknown>;
30
+ readonly length: number;
31
+ readonly valueToDenseIndex: ReadonlyMap<string, number>;
32
+ }
33
+
34
+ const vocabularyDictionaries = new WeakMap<VocabularyGeneration, VocabularyDictionaryPrefix>();
35
+
36
+ /**
37
+ * Return the immutable Arrow dictionary prefix for one pinned vocabulary generation.
38
+ * Record text is decoded and copied into Arrow UTF-8 buffers once per generation;
39
+ * every flush thereafter reuses the same column and backing buffers.
40
+ */
41
+ export function getVocabularyDictionaryPrefix(generation: VocabularyGeneration): VocabularyDictionaryPrefix {
42
+ const cached = vocabularyDictionaries.get(generation);
43
+ if (cached !== undefined) return cached;
44
+
45
+ if (generation.ids.length === 0) {
46
+ const emptyPrefix = Object.freeze({
47
+ column: makeUtf8Dictionary(1, EMPTY_DICTIONARY_OFFSETS, EMPTY_DICTIONARY_BYTES),
48
+ length: 1,
49
+ valueToDenseIndex: new Map<string, number>([['', 0]]),
50
+ });
51
+ emptyPrefix.column.cache();
52
+ vocabularyDictionaries.set(generation, emptyPrefix);
53
+ return emptyPrefix;
54
+ }
55
+
56
+ const count = generation.ids.length;
57
+ const offsets = new Int32Array(count + 1);
58
+ const textLengths = new Uint32Array(count);
59
+ let byteLength = 0;
60
+ for (let denseIndex = 0; denseIndex < count; denseIndex++) {
61
+ const recordOffset = generation.offsets[denseIndex];
62
+ const textLength = new DataView(
63
+ generation.records.buffer,
64
+ generation.records.byteOffset + recordOffset,
65
+ 4,
66
+ ).getUint32(0, true);
67
+ textLengths[denseIndex] = textLength;
68
+ byteLength += textLength;
69
+ offsets[denseIndex + 1] = byteLength;
70
+ }
71
+
72
+ const values = new Uint8Array(byteLength);
73
+ const valueToDenseIndex = new Map<string, number>();
74
+ for (let denseIndex = 0; denseIndex < count; denseIndex++) {
75
+ const textStart = generation.offsets[denseIndex] + 4;
76
+ const text = generation.records.subarray(textStart, textStart + textLengths[denseIndex]);
77
+ values.set(text, offsets[denseIndex]);
78
+ const value = decoder.decode(text);
79
+ if (!valueToDenseIndex.has(value)) valueToDenseIndex.set(value, denseIndex);
80
+ }
81
+
82
+ const prefix = Object.freeze({
83
+ column: makeUtf8Dictionary(count, offsets, values),
84
+ length: count,
85
+ valueToDenseIndex,
86
+ });
87
+ prefix.column.cache();
88
+ vocabularyDictionaries.set(generation, prefix);
89
+ return prefix;
90
+ }
91
+
92
+ /**
93
+ * Append one UTF-8 dictionary batch without copying the prefix buffers or
94
+ * scanning its cached strings. Flechette serializes later dictionary batches
95
+ * as Arrow dictionary deltas.
96
+ */
97
+ export function appendVocabularyDictionarySuffix(
98
+ prefix: VocabularyDictionaryPrefix,
99
+ suffix: Column<unknown>,
100
+ suffixValues: readonly string[],
101
+ ): Column<unknown> {
102
+ const dictionary = new Column([...prefix.column.data, ...suffix.data]);
103
+ const prefixCache = prefix.column.cache();
104
+ const cacheTarget = new Array<unknown>(prefix.length + suffixValues.length);
105
+ dictionary._cache = new Proxy(cacheTarget, {
106
+ get(target, property, receiver) {
107
+ if (typeof property === 'string') {
108
+ const index = Number(property);
109
+ if (Number.isInteger(index) && index >= 0 && index < target.length && String(index) === property) {
110
+ return index < prefix.length ? prefixCache[index] : suffixValues[index - prefix.length];
111
+ }
112
+ }
113
+ return Reflect.get(target, property, receiver);
114
+ },
115
+ });
116
+ return dictionary;
117
+ }
118
+
119
+ export const PREBUILT_ENTRY_TYPE_DICTIONARY = makeUtf8Dictionary(
120
+ ENTRY_TYPE_NAMES.length,
121
+ ENTRY_TYPE_OFFSETS,
122
+ ENTRY_TYPE_UTF8,
123
+ );
@@ -22,6 +22,7 @@
22
22
  import type { Table } from '@uwdata/flechette';
23
23
  import type { OpMetadata } from './opContext/opTypes.js';
24
24
  import type { LogSchema } from './schema/LogSchema.js';
25
+ import type { SpanBufferConstructor } from './spanBuffer.js';
25
26
  import type { ITraceRoot } from './traceRoot.js';
26
27
  import type { AnySpanBuffer, SpanBuffer } from './types.js';
27
28
 
@@ -45,7 +46,13 @@ export interface BufferStrategy<T extends LogSchema = LogSchema> {
45
46
  * @param capacity - Optional capacity override
46
47
  * @returns New SpanBuffer for the root span
47
48
  */
48
- createSpanBuffer(schema: T, traceRoot: ITraceRoot, opMetadata: OpMetadata, capacity?: number): SpanBuffer<T>;
49
+ createSpanBuffer(
50
+ schema: T,
51
+ traceRoot: ITraceRoot,
52
+ opMetadata: OpMetadata,
53
+ capacity?: number,
54
+ SpanBufferClass?: SpanBufferConstructor<T>,
55
+ ): SpanBuffer<T>;
49
56
 
50
57
  /**
51
58
  * Create a child span buffer linked to a parent.
@@ -65,6 +72,7 @@ export interface BufferStrategy<T extends LogSchema = LogSchema> {
65
72
  opMetadata: OpMetadata,
66
73
  capacity?: number,
67
74
  schema?: T,
75
+ SpanBufferClass?: SpanBufferConstructor<T>,
68
76
  ): SpanBuffer<T>;
69
77
 
70
78
  /**
@@ -10,6 +10,7 @@
10
10
  * buffer's user schema columns, flattened so downstream SQL transforms need no unnesting.
11
11
  */
12
12
 
13
+ import { resolveEntryType, resolveMessage } from '../resolveMessage.js';
13
14
  import { readSpanFieldValue, walkSpanSegments } from '../sqlite/sqlite-common.js';
14
15
  import type { AnySpanBuffer } from '../types.js';
15
16
 
@@ -46,9 +47,9 @@ export function spanBufferToTraceRows(rootBuffer: AnySpanBuffer): TraceRow[] {
46
47
  span_id: spanId,
47
48
  parent_span_id: parentSpanId,
48
49
  row_index: rowOffset + row,
49
- entry_type: buffer.entry_type[row],
50
+ entry_type: resolveEntryType(buffer, row),
50
51
  timestamp_ns: Number(buffer.timestamp[row]),
51
- message: buffer.message_values[row] ?? null,
52
+ message: toTraceRowValue(resolveMessage(buffer, row)),
52
53
  };
53
54
  for (const fieldName of fieldNames) {
54
55
  record[fieldName] = toTraceRowValue(readSpanFieldValue(buffer, fieldName, row));