@smoothbricks/lmao 0.1.3 → 0.2.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.
- package/dist/allocator.wasm +0 -0
- package/dist/cloudflare.d.ts +15 -0
- package/dist/cloudflare.d.ts.map +1 -0
- package/dist/cloudflare.js +14 -0
- package/dist/es.d.ts.map +1 -1
- package/dist/es.js +1 -0
- package/dist/lib/archive/chunkCompaction.d.ts.map +1 -1
- package/dist/lib/archive/chunkCompaction.js +2 -0
- package/dist/lib/archive/chunkEnvelope.d.ts.map +1 -1
- package/dist/lib/archive/chunkRouting.d.ts.map +1 -1
- package/dist/lib/archive/chunkRouting.js +3 -0
- package/dist/lib/archive/chunkStats.d.ts.map +1 -1
- package/dist/lib/archive/chunkStats.js +1 -0
- package/dist/lib/arrow/capacityStats.d.ts.map +1 -1
- package/dist/lib/arrow/capacityStats.js +1 -0
- package/dist/lib/bufferStrategy.d.ts.map +1 -1
- package/dist/lib/capacityTuning.d.ts.map +1 -1
- package/dist/lib/capacityTuning.js +1 -0
- package/dist/lib/cloudflare/classSplit.d.ts +73 -0
- package/dist/lib/cloudflare/classSplit.d.ts.map +1 -0
- package/dist/lib/cloudflare/classSplit.js +82 -0
- package/dist/lib/cloudflare/collectorClient.d.ts +69 -0
- package/dist/lib/cloudflare/collectorClient.d.ts.map +1 -0
- package/dist/lib/cloudflare/collectorClient.js +82 -0
- package/dist/lib/cloudflare/diagnosticDrain.d.ts +78 -0
- package/dist/lib/cloudflare/diagnosticDrain.d.ts.map +1 -0
- package/dist/lib/cloudflare/diagnosticDrain.js +120 -0
- package/dist/lib/cloudflare/traceRows.d.ts +19 -0
- package/dist/lib/cloudflare/traceRows.d.ts.map +1 -0
- package/dist/lib/cloudflare/traceRows.js +51 -0
- package/dist/lib/codegen/evaluatorGenerator.d.ts.map +1 -1
- package/dist/lib/codegen/evaluatorGenerator.js +1 -0
- package/dist/lib/codegen/fixedPositionWriterGenerator.d.ts +8 -0
- package/dist/lib/codegen/fixedPositionWriterGenerator.d.ts.map +1 -1
- package/dist/lib/codegen/fixedPositionWriterGenerator.js +18 -1
- package/dist/lib/codegen/spanLoggerGenerator.d.ts.map +1 -1
- package/dist/lib/codegen/spanLoggerGenerator.js +3 -0
- package/dist/lib/convertToArrow.d.ts.map +1 -1
- package/dist/lib/convertToArrow.js +6 -0
- package/dist/lib/defineOpContext.d.ts +5 -0
- package/dist/lib/defineOpContext.d.ts.map +1 -1
- package/dist/lib/defineOpContext.js +11 -1
- package/dist/lib/errors/Blocked.d.ts.map +1 -1
- package/dist/lib/errors/Transient.d.ts.map +1 -1
- package/dist/lib/errors/retry-policy.d.ts.map +1 -1
- package/dist/lib/flushScheduler.d.ts.map +1 -1
- package/dist/lib/flushScheduler.js +2 -0
- package/dist/lib/library.d.ts +5 -0
- package/dist/lib/library.d.ts.map +1 -1
- package/dist/lib/library.js +6 -0
- package/dist/lib/op.d.ts.map +1 -1
- package/dist/lib/op.js +2 -0
- package/dist/lib/opContext/createOpGroup.d.ts.map +1 -1
- package/dist/lib/opContext/createOpGroup.js +5 -0
- package/dist/lib/opContext/defineOp.d.ts.map +1 -1
- package/dist/lib/opContext/defineOp.js +5 -0
- package/dist/lib/result.d.ts.map +1 -1
- package/dist/lib/result.js +3 -0
- package/dist/lib/schema/builder.d.ts.map +1 -1
- package/dist/lib/schema/builder.js +1 -0
- package/dist/lib/schema/defineFeatureFlags.d.ts.map +1 -1
- package/dist/lib/schema/defineLogSchema.d.ts.map +1 -1
- package/dist/lib/schema/defineLogSchema.js +1 -0
- package/dist/lib/schema/evaluator.d.ts.map +1 -1
- package/dist/lib/schema/systemSchema.d.ts.map +1 -1
- package/dist/lib/schema/systemSchema.js +1 -0
- package/dist/lib/spanBuffer.d.ts.map +1 -1
- package/dist/lib/spanBuffer.js +8 -0
- package/dist/lib/spanBufferHelpers.d.ts.map +1 -1
- package/dist/lib/spanBufferHelpers.js +1 -0
- package/dist/lib/spanBufferStats.d.ts.map +1 -1
- package/dist/lib/spanContext.d.ts +8 -0
- package/dist/lib/spanContext.d.ts.map +1 -1
- package/dist/lib/spanContext.js +35 -0
- package/dist/lib/sqlite/index.d.ts +1 -0
- package/dist/lib/sqlite/index.d.ts.map +1 -1
- package/dist/lib/sqlite/sqlite-common.d.ts +2 -0
- package/dist/lib/sqlite/sqlite-common.d.ts.map +1 -1
- package/dist/lib/sqlite/sqlite-common.js +9 -1
- package/dist/lib/threadId.d.ts.map +1 -1
- package/dist/lib/threadId.js +2 -0
- package/dist/lib/traceId.d.ts.map +1 -1
- package/dist/lib/traceId.js +1 -0
- package/dist/lib/traceRoot.d.ts +5 -5
- package/dist/lib/traceRoot.d.ts.map +1 -1
- package/dist/lib/traceRoot.es.d.ts.map +1 -1
- package/dist/lib/traceRoot.es.js +3 -0
- package/dist/lib/traceRoot.js +3 -1
- package/dist/lib/traceRoot.node.d.ts +2 -2
- package/dist/lib/traceRoot.node.d.ts.map +1 -1
- package/dist/lib/traceRoot.node.js +4 -3
- package/dist/lib/tracer.d.ts.map +1 -1
- package/dist/lib/tracer.js +2 -0
- package/dist/lib/tracers/ArrayQueueTracer.d.ts.map +1 -1
- package/dist/lib/tracers/CompositeTracer.d.ts.map +1 -1
- package/dist/lib/tracers/CompositeTracer.js +1 -0
- package/dist/lib/tracers/NoOpTracer.d.ts.map +1 -1
- package/dist/lib/tracers/NoOpTracer.js +1 -0
- package/dist/lib/tracers/SQLiteTracer.d.ts.map +1 -1
- package/dist/lib/tracers/SQLiteTracer.js +2 -0
- package/dist/lib/tracers/StdioTracer.d.ts.map +1 -1
- package/dist/lib/tracers/StdioTracer.js +1 -0
- package/dist/lib/tracers/TestTracer.d.ts.map +1 -1
- package/dist/lib/tracers/TestTracer.js +1 -0
- package/dist/lib/types.d.ts.map +1 -1
- package/dist/lib/utf8Cache.d.ts.map +1 -1
- package/dist/lib/utf8Cache.js +1 -0
- package/dist/lib/wasm/WasmBufferStrategy.d.ts.map +1 -1
- package/dist/lib/wasm/WasmBufferStrategy.js +2 -0
- package/dist/lib/wasm/wasmAllocator.d.ts +2 -0
- package/dist/lib/wasm/wasmAllocator.d.ts.map +1 -1
- package/dist/lib/wasm/wasmSpanBuffer.d.ts.map +1 -1
- package/dist/lib/wasm/wasmTraceRoot.d.ts.map +1 -1
- package/dist/node.d.ts.map +1 -1
- package/dist/node.js +1 -0
- package/package.json +10 -4
- package/src/cloudflare.ts +35 -0
- package/src/es.ts +2 -0
- package/src/lib/archive/chunkCompaction.ts +2 -0
- package/src/lib/archive/chunkEnvelope.ts +2 -0
- package/src/lib/archive/chunkRouting.ts +4 -0
- package/src/lib/archive/chunkStats.ts +2 -0
- package/src/lib/arrow/capacityStats.ts +2 -0
- package/src/lib/bufferStrategy.ts +2 -0
- package/src/lib/capacityTuning.ts +2 -0
- package/src/lib/cloudflare/__tests__/classSplit.test.ts +171 -0
- package/src/lib/cloudflare/__tests__/collectorClient.test.ts +104 -0
- package/src/lib/cloudflare/__tests__/diagnosticDrain.test.ts +165 -0
- package/src/lib/cloudflare/classSplit.ts +139 -0
- package/src/lib/cloudflare/collectorClient.ts +117 -0
- package/src/lib/cloudflare/diagnosticDrain.ts +152 -0
- package/src/lib/cloudflare/traceRows.ts +60 -0
- package/src/lib/codegen/__tests__/fixedPositionWriter-uint64.test.ts +304 -0
- package/src/lib/codegen/evaluatorGenerator.ts +2 -0
- package/src/lib/codegen/fixedPositionWriterGenerator.ts +28 -1
- package/src/lib/codegen/spanLoggerGenerator.ts +4 -0
- package/src/lib/convertToArrow.ts +6 -0
- package/src/lib/defineOpContext.ts +17 -1
- package/src/lib/errors/Blocked.ts +2 -0
- package/src/lib/errors/Transient.ts +2 -0
- package/src/lib/errors/retry-policy.ts +2 -0
- package/src/lib/flushScheduler.ts +2 -0
- package/src/lib/library.ts +7 -0
- package/src/lib/op.ts +8 -0
- package/src/lib/opContext/createOpGroup.ts +6 -0
- package/src/lib/opContext/defineOp.ts +6 -0
- package/src/lib/result.ts +4 -0
- package/src/lib/schema/builder.ts +2 -0
- package/src/lib/schema/defineFeatureFlags.ts +2 -0
- package/src/lib/schema/defineLogSchema.ts +2 -0
- package/src/lib/schema/evaluator.ts +2 -0
- package/src/lib/schema/systemSchema.ts +2 -0
- package/src/lib/spanBuffer.ts +10 -0
- package/src/lib/spanBufferHelpers.ts +2 -0
- package/src/lib/spanBufferStats.ts +2 -0
- package/src/lib/spanContext.ts +48 -1
- package/src/lib/sqlite/__tests__/sqlite-common.test.ts +40 -1
- package/src/lib/sqlite/index.ts +1 -0
- package/src/lib/sqlite/sqlite-common.ts +10 -1
- package/src/lib/testing/__tests__/facts.test.ts +6 -6
- package/src/lib/threadId.ts +2 -0
- package/src/lib/traceId.ts +4 -0
- package/src/lib/traceRoot.es.ts +4 -0
- package/src/lib/traceRoot.node.ts +5 -3
- package/src/lib/traceRoot.ts +9 -5
- package/src/lib/tracer.ts +4 -0
- package/src/lib/tracers/ArrayQueueTracer.ts +2 -0
- package/src/lib/tracers/CompositeTracer.ts +2 -0
- package/src/lib/tracers/NoOpTracer.ts +2 -0
- package/src/lib/tracers/SQLiteTracer.ts +2 -0
- package/src/lib/tracers/StdioTracer.ts +2 -0
- package/src/lib/tracers/TestTracer.ts +2 -0
- package/src/lib/types.ts +2 -0
- package/src/lib/utf8Cache.ts +2 -0
- package/src/lib/wasm/WasmBufferStrategy.ts +4 -0
- package/src/lib/wasm/allocator.zig +4 -1
- package/src/lib/wasm/wasmAllocator.ts +5 -0
- package/src/lib/wasm/wasmSpanBuffer.ts +4 -0
- package/src/lib/wasm/wasmTraceRoot.ts +4 -0
- package/src/node.ts +2 -0
- package/src/lib/codegen/__tests__/__snapshots__/spanLoggerGenerator.test.ts.snap +0 -1965
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Diagnostic-class drain adapter.
|
|
3
|
+
*
|
|
4
|
+
* Per specs/lmao/01u_cloudflare_trace_segments.md §LMAO Sink Fit: no new abstraction — the base
|
|
5
|
+
* Tracer lifecycle (onTraceEnd queueing + flush) already models this. The drain tracer extends
|
|
6
|
+
* ArrayQueueTracer; flush() drains completed root buffers, flattens them to rows, and hands them
|
|
7
|
+
* to a constructor-injected transport.
|
|
8
|
+
*
|
|
9
|
+
* Transports (per 01u §Collection Lanes and Fallbacks):
|
|
10
|
+
* - primary: a Pipelines-stream-binding-shaped send fn (`env.TRACE_STREAM.send(rows)`) — rows,
|
|
11
|
+
* because Pipelines owns batching and Parquet encoding for the raw tier
|
|
12
|
+
* - fallback: Cloudflare Queues — batched TraceChunkEnvelope messages built from the existing
|
|
13
|
+
* lib/archive primitives (chunk_id idempotency); NEVER per-span messages (128 KB messages,
|
|
14
|
+
* per-64 KB op billing make per-span ~160x more expensive)
|
|
15
|
+
*
|
|
16
|
+
* Delivery is fire-and-forget: flush() is scheduled via ctx.waitUntil and never rejects; enqueue
|
|
17
|
+
* failures surface through the onSendError hook (01s §Failure Handling — emit metrics, rely on
|
|
18
|
+
* queue policy/backoff), they never block.
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
import { buildTraceChunkEnvelope, type TraceChunkEnvelope } from '../archive/chunkEnvelope.js';
|
|
22
|
+
import type { OpContextBinding } from '../opContext/types.js';
|
|
23
|
+
import type { TracerOptions } from '../tracer.js';
|
|
24
|
+
import { ArrayQueueTracer } from '../tracers/ArrayQueueTracer.js';
|
|
25
|
+
import { spanBufferToTraceRows, type TraceRow } from './traceRows.js';
|
|
26
|
+
|
|
27
|
+
/** Shape of a Cloudflare Pipelines stream binding's send — promise resolves on confirmed ingest. */
|
|
28
|
+
export type PipelinesStreamSend = (rows: TraceRow[]) => Promise<void>;
|
|
29
|
+
|
|
30
|
+
/** Diagnostic-lane transport seam: delivers one drained batch of rows, at most best-effort. */
|
|
31
|
+
export interface DiagnosticTransport {
|
|
32
|
+
send(rows: readonly TraceRow[]): Promise<void>;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** Primary diagnostic transport: rows straight into the injected Pipelines stream binding. */
|
|
36
|
+
export class PipelinesStreamTransport implements DiagnosticTransport {
|
|
37
|
+
constructor(private readonly sendToStream: PipelinesStreamSend) {}
|
|
38
|
+
|
|
39
|
+
send(rows: readonly TraceRow[]): Promise<void> {
|
|
40
|
+
// The binding signature takes a mutable array of records.
|
|
41
|
+
return this.sendToStream([...rows]);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** One Queues-fallback message: chunk envelope (idempotency + routing metadata) plus its rows. */
|
|
46
|
+
export interface TraceChunkQueueMessage {
|
|
47
|
+
readonly envelope: TraceChunkEnvelope;
|
|
48
|
+
readonly rows: readonly TraceRow[];
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export interface QueuesFallbackOptions {
|
|
52
|
+
/** Logical file reference recorded in the envelopes, e.g. 'queue://worker-name'. */
|
|
53
|
+
fileRef: string;
|
|
54
|
+
/** Max rows per queue message — bounds message size under the 128 KB Queues limit. */
|
|
55
|
+
maxRowsPerMessage: number;
|
|
56
|
+
/** Injected queue producer, e.g. (msg) => env.TRACE_QUEUE.send(msg). */
|
|
57
|
+
sendMessage: (message: TraceChunkQueueMessage) => Promise<void>;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function fnv1a32Hex(input: string): string {
|
|
61
|
+
let hash = 0x811c9dc5;
|
|
62
|
+
for (let i = 0; i < input.length; i++) {
|
|
63
|
+
hash ^= input.charCodeAt(i);
|
|
64
|
+
hash = Math.imul(hash, 0x01000193) >>> 0;
|
|
65
|
+
}
|
|
66
|
+
return hash.toString(16).padStart(8, '0');
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function timestampBoundsMs(rows: readonly TraceRow[]): { startedAtMs: number; endedAtMs: number } {
|
|
70
|
+
let minNs = Number.POSITIVE_INFINITY;
|
|
71
|
+
let maxNs = Number.NEGATIVE_INFINITY;
|
|
72
|
+
for (const row of rows) {
|
|
73
|
+
const ts = row.timestamp_ns;
|
|
74
|
+
if (typeof ts !== 'number') continue;
|
|
75
|
+
if (ts < minNs) minNs = ts;
|
|
76
|
+
if (ts > maxNs) maxNs = ts;
|
|
77
|
+
}
|
|
78
|
+
if (minNs === Number.POSITIVE_INFINITY) return { startedAtMs: 0, endedAtMs: 0 };
|
|
79
|
+
return { startedAtMs: Math.floor(minNs / 1_000_000), endedAtMs: Math.floor(maxNs / 1_000_000) };
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Queues-fallback transport: rows are batched into chunk-envelope messages.
|
|
84
|
+
*
|
|
85
|
+
* The chunk_ref is a deterministic content hash of the batch, so a retried send of the same
|
|
86
|
+
* batch produces the same chunk_id and the consumer's chunk_id dedup makes redelivery a no-op.
|
|
87
|
+
*/
|
|
88
|
+
export class QueuesFallbackTransport implements DiagnosticTransport {
|
|
89
|
+
constructor(private readonly options: QueuesFallbackOptions) {
|
|
90
|
+
if (options.maxRowsPerMessage < 1) {
|
|
91
|
+
throw new Error('QueuesFallbackTransport: maxRowsPerMessage must be >= 1');
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
async send(rows: readonly TraceRow[]): Promise<void> {
|
|
96
|
+
const { fileRef, maxRowsPerMessage, sendMessage } = this.options;
|
|
97
|
+
for (let offset = 0; offset < rows.length; offset += maxRowsPerMessage) {
|
|
98
|
+
const batch = rows.slice(offset, offset + maxRowsPerMessage);
|
|
99
|
+
const { startedAtMs, endedAtMs } = timestampBoundsMs(batch);
|
|
100
|
+
const envelope = buildTraceChunkEnvelope({
|
|
101
|
+
file_ref: fileRef,
|
|
102
|
+
chunk_ref: `rows_${fnv1a32Hex(JSON.stringify(batch))}`,
|
|
103
|
+
row_count: batch.length,
|
|
104
|
+
started_at_ms: startedAtMs,
|
|
105
|
+
ended_at_ms: endedAtMs,
|
|
106
|
+
});
|
|
107
|
+
await sendMessage({ envelope, rows: batch });
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export interface DiagnosticDrainOptions<
|
|
113
|
+
T extends import('../schema/LogSchema.js').LogSchema = import('../schema/LogSchema.js').LogSchema,
|
|
114
|
+
> extends TracerOptions<T> {
|
|
115
|
+
transport: DiagnosticTransport;
|
|
116
|
+
/** Failure hook (01s §Failure Handling): emit runtime metrics/alerts here; never rethrows. */
|
|
117
|
+
onSendError?: (error: unknown, rows: readonly TraceRow[]) => void;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Diagnostic-lane sink tracer: queue completed roots (ArrayQueueTracer), drain + send on flush().
|
|
122
|
+
*
|
|
123
|
+
* flush() is waitUntil-schedulable: it never rejects, and buffers are always released back to
|
|
124
|
+
* the strategy regardless of transport outcome.
|
|
125
|
+
*/
|
|
126
|
+
export class DiagnosticDrainTracer<B extends OpContextBinding = OpContextBinding> extends ArrayQueueTracer<B> {
|
|
127
|
+
private readonly transport: DiagnosticTransport;
|
|
128
|
+
private readonly onSendError: ((error: unknown, rows: readonly TraceRow[]) => void) | undefined;
|
|
129
|
+
|
|
130
|
+
constructor(binding: B, options: DiagnosticDrainOptions<B['logBinding']['logSchema']>) {
|
|
131
|
+
super(binding, options);
|
|
132
|
+
this.transport = options.transport;
|
|
133
|
+
this.onSendError = options.onSendError;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
override async flush(): Promise<void> {
|
|
137
|
+
const buffers = this.drain();
|
|
138
|
+
for (const buffer of buffers) {
|
|
139
|
+
let rows: TraceRow[];
|
|
140
|
+
try {
|
|
141
|
+
rows = spanBufferToTraceRows(buffer);
|
|
142
|
+
} finally {
|
|
143
|
+
this.bufferStrategy.releaseBuffer(buffer);
|
|
144
|
+
}
|
|
145
|
+
try {
|
|
146
|
+
await this.transport.send(rows);
|
|
147
|
+
} catch (error) {
|
|
148
|
+
this.onSendError?.(error, rows);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Trace-row conversion for the Cloudflare sink lanes.
|
|
3
|
+
*
|
|
4
|
+
* Per specs/lmao/01u_cloudflare_trace_segments.md §Collection Lanes: spans are sent as ROWS
|
|
5
|
+
* (JSON-serializable records), not pre-built Arrow chunks — Pipelines owns batching and Parquet
|
|
6
|
+
* encoding for the raw tier, and the collector DO reduces the same flat row shape.
|
|
7
|
+
*
|
|
8
|
+
* The row shape mirrors the SQLite `spans` table (01a schema system): system columns
|
|
9
|
+
* (trace_id, span_id, parent_span_id, row_index, entry_type, timestamp_ns, message) plus the
|
|
10
|
+
* buffer's user schema columns, flattened so downstream SQL transforms need no unnesting.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { readSpanFieldValue, walkSpanSegments } from '../sqlite/sqlite-common.js';
|
|
14
|
+
import type { AnySpanBuffer } from '../types.js';
|
|
15
|
+
|
|
16
|
+
/** JSON-serializable scalar — the only value kinds a trace row may carry across the sink boundary. */
|
|
17
|
+
export type TraceRowValue = string | number | boolean | null;
|
|
18
|
+
|
|
19
|
+
/** One flat, JSON-serializable trace record — the payload unit of both delivery lanes. */
|
|
20
|
+
export type TraceRow = Record<string, TraceRowValue>;
|
|
21
|
+
|
|
22
|
+
function toTraceRowValue(value: unknown): TraceRowValue {
|
|
23
|
+
if (value === undefined || value === null) return null;
|
|
24
|
+
switch (typeof value) {
|
|
25
|
+
case 'string':
|
|
26
|
+
case 'number':
|
|
27
|
+
case 'boolean':
|
|
28
|
+
return value;
|
|
29
|
+
case 'bigint':
|
|
30
|
+
return Number(value);
|
|
31
|
+
default:
|
|
32
|
+
// Non-scalar buffer values (binary payloads etc.) have no JSON row representation.
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** Flatten a completed root span-buffer tree into JSON-serializable trace rows. */
|
|
38
|
+
export function spanBufferToTraceRows(rootBuffer: AnySpanBuffer): TraceRow[] {
|
|
39
|
+
const rows: TraceRow[] = [];
|
|
40
|
+
for (const segment of walkSpanSegments(rootBuffer)) {
|
|
41
|
+
const { buffer, traceId, spanId, parentSpanId, rowOffset } = segment;
|
|
42
|
+
const fieldNames = buffer._logSchema._columnNames;
|
|
43
|
+
for (let row = 0; row < buffer._writeIndex; row++) {
|
|
44
|
+
const record: TraceRow = {
|
|
45
|
+
trace_id: traceId,
|
|
46
|
+
span_id: spanId,
|
|
47
|
+
parent_span_id: parentSpanId,
|
|
48
|
+
row_index: rowOffset + row,
|
|
49
|
+
entry_type: buffer.entry_type[row],
|
|
50
|
+
timestamp_ns: Number(buffer.timestamp[row]),
|
|
51
|
+
message: buffer.message_values[row] ?? null,
|
|
52
|
+
};
|
|
53
|
+
for (const fieldName of fieldNames) {
|
|
54
|
+
record[fieldName] = toTraceRowValue(readSpanFieldValue(buffer, fieldName, row));
|
|
55
|
+
}
|
|
56
|
+
rows.push(record);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
return rows;
|
|
60
|
+
}
|
|
@@ -0,0 +1,304 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Behavioral tests for the fluent `uint64_value(value)` writer on TagWriter (row 0)
|
|
3
|
+
* and ResultWriter (row 1).
|
|
4
|
+
*
|
|
5
|
+
* `uint64_value` is a RESERVED system column (systemSchema), stored as a LAZY
|
|
6
|
+
* BigUint64Array multiplexed by row. Both writers route through the buffer's own
|
|
7
|
+
* lazy setter `buffer.uint64_value(pos, val)`, which allocates-then-writes and
|
|
8
|
+
* flips the Arrow null bit (1 = valid, 0 = null).
|
|
9
|
+
*
|
|
10
|
+
* These tests defend the observable contract:
|
|
11
|
+
* - the value lands at the writer's fixed row (0 for tag, 1 for result),
|
|
12
|
+
* - the null bit is set for a written value and cleared for `null`,
|
|
13
|
+
* - rows 0 and 1 hold INDEPENDENT values (the load-bearing no-collision invariant,
|
|
14
|
+
* since span lifecycle writes never touch `uint64_value`),
|
|
15
|
+
* - allocation is lazy, chaining returns the writer, call order is irrelevant,
|
|
16
|
+
* - and large near-2^64 bigints round-trip without precision loss while `0n`
|
|
17
|
+
* is a distinct non-null zero (not "absent").
|
|
18
|
+
*
|
|
19
|
+
* `packages/lmao` is exempt from the traced-suite requirement, so this is a plain
|
|
20
|
+
* bun:test file (matches ../../__tests__/arrow-builder-integration/lazy-columns.test.ts).
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
import { describe, expect, it } from 'bun:test';
|
|
24
|
+
import { createResultWriter, createTagWriter, ENTRY_TYPE_SPAN_OK, ENTRY_TYPE_SPAN_START, S } from '@smoothbricks/lmao';
|
|
25
|
+
import { createTestOpMetadata, createTestSchema, createTestTraceRoot } from '../../__tests__/test-helpers.js';
|
|
26
|
+
import { createSpanBuffer } from '../../spanBuffer.js';
|
|
27
|
+
import type { AnySpanBuffer } from '../../types.js';
|
|
28
|
+
|
|
29
|
+
// ---------------------------------------------------------------------------
|
|
30
|
+
// Fixtures & readers
|
|
31
|
+
// ---------------------------------------------------------------------------
|
|
32
|
+
|
|
33
|
+
// Schema carries sibling user columns (batchId/count) so tests can prove that
|
|
34
|
+
// `uint64_value` neither interferes with nor is clobbered by other columns.
|
|
35
|
+
// One schema instance is shared between the buffer and its writers so their
|
|
36
|
+
// column sets are guaranteed identical.
|
|
37
|
+
function setup() {
|
|
38
|
+
const schema = createTestSchema({ batchId: S.category(), count: S.number() });
|
|
39
|
+
const buffer = createSpanBuffer(schema, createTestTraceRoot('t'), createTestOpMetadata(), 8);
|
|
40
|
+
return { schema, buffer };
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Read the allocated `uint64_value` values, narrowed to BigUint64Array.
|
|
44
|
+
// Uses getColumnIfAllocated so it never triggers allocation as a side effect.
|
|
45
|
+
function uint64Column(buffer: AnySpanBuffer): BigUint64Array {
|
|
46
|
+
const col = buffer.getColumnIfAllocated('uint64_value');
|
|
47
|
+
if (!(col instanceof BigUint64Array)) {
|
|
48
|
+
throw new Error('uint64_value column was not allocated as a BigUint64Array');
|
|
49
|
+
}
|
|
50
|
+
return col;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function uint64Nulls(buffer: AnySpanBuffer): Uint8Array {
|
|
54
|
+
const nulls = buffer.getNullsIfAllocated('uint64_value');
|
|
55
|
+
if (!(nulls instanceof Uint8Array)) {
|
|
56
|
+
throw new Error('uint64_value null bitmap was not allocated');
|
|
57
|
+
}
|
|
58
|
+
return nulls;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Arrow null bitmap: bit set (1) => valid/non-null; bit clear (0) => null/absent.
|
|
62
|
+
// Named because the bit-extraction formula is not self-explanatory inline and is
|
|
63
|
+
// asserted at many call sites in lockstep.
|
|
64
|
+
function isNonNull(nulls: Uint8Array, pos: number): boolean {
|
|
65
|
+
return (nulls[pos >>> 3] & (1 << (pos & 7))) !== 0;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Invoke a writer's uint64_value at runtime with a value TypeScript would reject
|
|
69
|
+
// (null), to exercise the setter's null-clearing branch without an unsafe `as` cast
|
|
70
|
+
// (the repo's eslint bans no-unsafe-type-assertion). Mirrors the Reflect pattern in
|
|
71
|
+
// arrow-builder's columnBufferSetters.test.ts.
|
|
72
|
+
function writeUint64Runtime(writer: object, value: bigint | null): void {
|
|
73
|
+
const method = Reflect.get(writer, 'uint64_value');
|
|
74
|
+
if (typeof method !== 'function') {
|
|
75
|
+
throw new Error('writer is missing a uint64_value method');
|
|
76
|
+
}
|
|
77
|
+
Reflect.apply(method, writer, [value]);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// ---------------------------------------------------------------------------
|
|
81
|
+
// Row placement
|
|
82
|
+
// ---------------------------------------------------------------------------
|
|
83
|
+
|
|
84
|
+
describe('uint64_value row placement', () => {
|
|
85
|
+
it('TagWriter writes to row 0 (span-start)', () => {
|
|
86
|
+
const { schema, buffer } = setup();
|
|
87
|
+
|
|
88
|
+
createTagWriter(schema, buffer).uint64_value(123n);
|
|
89
|
+
|
|
90
|
+
const values = uint64Column(buffer);
|
|
91
|
+
const nulls = uint64Nulls(buffer);
|
|
92
|
+
expect(values[0]).toBe(123n);
|
|
93
|
+
expect(isNonNull(nulls, 0)).toBe(true);
|
|
94
|
+
// Row 1 must be untouched by a tag write.
|
|
95
|
+
expect(isNonNull(nulls, 1)).toBe(false);
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
it('ResultWriter writes to row 1 (span-completion)', () => {
|
|
99
|
+
const { schema, buffer } = setup();
|
|
100
|
+
|
|
101
|
+
createResultWriter(schema, buffer, { ok: true }, false).uint64_value(456n);
|
|
102
|
+
|
|
103
|
+
const values = uint64Column(buffer);
|
|
104
|
+
const nulls = uint64Nulls(buffer);
|
|
105
|
+
expect(values[1]).toBe(456n);
|
|
106
|
+
expect(isNonNull(nulls, 1)).toBe(true);
|
|
107
|
+
// Row 0 must be untouched by a result write.
|
|
108
|
+
expect(isNonNull(nulls, 0)).toBe(false);
|
|
109
|
+
});
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
// ---------------------------------------------------------------------------
|
|
113
|
+
// The load-bearing invariant: rows 0 and 1 never collide
|
|
114
|
+
// ---------------------------------------------------------------------------
|
|
115
|
+
|
|
116
|
+
describe('uint64_value cross-row independence (no collision)', () => {
|
|
117
|
+
it('tag row 0 and result row 1 hold distinct values on one buffer', () => {
|
|
118
|
+
const { schema, buffer } = setup();
|
|
119
|
+
const rowZero = 111n;
|
|
120
|
+
const rowOne = 222n;
|
|
121
|
+
|
|
122
|
+
createTagWriter(schema, buffer).uint64_value(rowZero);
|
|
123
|
+
createResultWriter(schema, buffer, { ok: true }, false).uint64_value(rowOne);
|
|
124
|
+
|
|
125
|
+
const values = uint64Column(buffer);
|
|
126
|
+
const nulls = uint64Nulls(buffer);
|
|
127
|
+
// Neither write overwrote the other.
|
|
128
|
+
expect(values[0]).toBe(rowZero);
|
|
129
|
+
expect(values[1]).toBe(rowOne);
|
|
130
|
+
expect(isNonNull(nulls, 0)).toBe(true);
|
|
131
|
+
expect(isNonNull(nulls, 1)).toBe(true);
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
it('is order-independent: result-first then tag yields the same layout', () => {
|
|
135
|
+
const { schema, buffer } = setup();
|
|
136
|
+
const rowZero = 333n;
|
|
137
|
+
const rowOne = 444n;
|
|
138
|
+
|
|
139
|
+
// Reverse the write order relative to the previous case.
|
|
140
|
+
createResultWriter(schema, buffer, { ok: true }, false).uint64_value(rowOne);
|
|
141
|
+
createTagWriter(schema, buffer).uint64_value(rowZero);
|
|
142
|
+
|
|
143
|
+
const values = uint64Column(buffer);
|
|
144
|
+
expect(values[0]).toBe(rowZero);
|
|
145
|
+
expect(values[1]).toBe(rowOne);
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
it('survives the span lifecycle: writeSpanStart/writeSpanEnd never touch uint64_value', () => {
|
|
149
|
+
const { schema, buffer } = setup();
|
|
150
|
+
const rowZero = 900n;
|
|
151
|
+
const rowOne = 901n;
|
|
152
|
+
|
|
153
|
+
const tag = createTagWriter(schema, buffer);
|
|
154
|
+
const result = createResultWriter(schema, buffer, { ok: true }, false);
|
|
155
|
+
|
|
156
|
+
// Reproduce the production sequence: lifecycle start, user tags, user result, lifecycle end.
|
|
157
|
+
buffer._traceRoot.writeSpanStart(buffer, 'lifecycle-span');
|
|
158
|
+
tag.uint64_value(rowZero);
|
|
159
|
+
result.uint64_value(rowOne);
|
|
160
|
+
buffer._traceRoot.writeSpanEnd(buffer, ENTRY_TYPE_SPAN_OK);
|
|
161
|
+
|
|
162
|
+
// The lifecycle actually ran and wrote its OWN columns (entry_type)...
|
|
163
|
+
expect(buffer.entry_type[0]).toBe(ENTRY_TYPE_SPAN_START);
|
|
164
|
+
expect(buffer.entry_type[1]).toBe(ENTRY_TYPE_SPAN_OK);
|
|
165
|
+
// ...without clobbering the user uint64 values in either row.
|
|
166
|
+
const values = uint64Column(buffer);
|
|
167
|
+
const nulls = uint64Nulls(buffer);
|
|
168
|
+
expect(values[0]).toBe(rowZero);
|
|
169
|
+
expect(values[1]).toBe(rowOne);
|
|
170
|
+
expect(isNonNull(nulls, 0)).toBe(true);
|
|
171
|
+
expect(isNonNull(nulls, 1)).toBe(true);
|
|
172
|
+
});
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
// ---------------------------------------------------------------------------
|
|
176
|
+
// Lazy allocation
|
|
177
|
+
// ---------------------------------------------------------------------------
|
|
178
|
+
|
|
179
|
+
describe('uint64_value lazy allocation', () => {
|
|
180
|
+
it('is unallocated until first write, then allocated', () => {
|
|
181
|
+
const { schema, buffer } = setup();
|
|
182
|
+
|
|
183
|
+
expect(buffer.getColumnIfAllocated('uint64_value')).toBeUndefined();
|
|
184
|
+
expect(buffer.getNullsIfAllocated('uint64_value')).toBeUndefined();
|
|
185
|
+
|
|
186
|
+
createTagWriter(schema, buffer).uint64_value(7n);
|
|
187
|
+
|
|
188
|
+
expect(buffer.getColumnIfAllocated('uint64_value')).toBeDefined();
|
|
189
|
+
expect(uint64Column(buffer)[0]).toBe(7n);
|
|
190
|
+
});
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
// ---------------------------------------------------------------------------
|
|
194
|
+
// Chaining & call-order independence with a sibling column
|
|
195
|
+
// ---------------------------------------------------------------------------
|
|
196
|
+
|
|
197
|
+
describe('uint64_value chaining and sibling non-interference', () => {
|
|
198
|
+
it('returns the writer for chaining', () => {
|
|
199
|
+
const { schema, buffer } = setup();
|
|
200
|
+
const tag = createTagWriter(schema, buffer);
|
|
201
|
+
expect(tag.uint64_value(1n)).toBe(tag);
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
it('batchId(...).uint64_value(...) lands both values', () => {
|
|
205
|
+
const { schema, buffer } = setup();
|
|
206
|
+
|
|
207
|
+
createTagWriter(schema, buffer).batchId('batch-A').uint64_value(10n);
|
|
208
|
+
|
|
209
|
+
expect(uint64Column(buffer)[0]).toBe(10n);
|
|
210
|
+
const batch = buffer.getColumnIfAllocated('batchId');
|
|
211
|
+
if (!Array.isArray(batch)) {
|
|
212
|
+
throw new Error('batchId column was not allocated as an array');
|
|
213
|
+
}
|
|
214
|
+
expect(batch[0]).toBe('batch-A');
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
it('uint64_value(...).batchId(...) lands both values (reverse order)', () => {
|
|
218
|
+
const { schema, buffer } = setup();
|
|
219
|
+
|
|
220
|
+
createTagWriter(schema, buffer).uint64_value(20n).batchId('batch-B');
|
|
221
|
+
|
|
222
|
+
expect(uint64Column(buffer)[0]).toBe(20n);
|
|
223
|
+
const batch = buffer.getColumnIfAllocated('batchId');
|
|
224
|
+
if (!Array.isArray(batch)) {
|
|
225
|
+
throw new Error('batchId column was not allocated as an array');
|
|
226
|
+
}
|
|
227
|
+
expect(batch[0]).toBe('batch-B');
|
|
228
|
+
});
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
// ---------------------------------------------------------------------------
|
|
232
|
+
// null clears the null bit
|
|
233
|
+
// ---------------------------------------------------------------------------
|
|
234
|
+
|
|
235
|
+
describe('uint64_value null handling', () => {
|
|
236
|
+
it('writing null clears the null bit (value marked absent), not sets it', () => {
|
|
237
|
+
const { schema, buffer } = setup();
|
|
238
|
+
const tag = createTagWriter(schema, buffer);
|
|
239
|
+
|
|
240
|
+
// First set a real value so the null bit is on...
|
|
241
|
+
tag.uint64_value(99n);
|
|
242
|
+
expect(isNonNull(uint64Nulls(buffer), 0)).toBe(true);
|
|
243
|
+
|
|
244
|
+
// ...then clear it via the runtime null path.
|
|
245
|
+
writeUint64Runtime(tag, null);
|
|
246
|
+
expect(isNonNull(uint64Nulls(buffer), 0)).toBe(false);
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
it('null at row 1 clears row 1 only, leaving a non-null row 0 intact', () => {
|
|
250
|
+
const { schema, buffer } = setup();
|
|
251
|
+
const tag = createTagWriter(schema, buffer);
|
|
252
|
+
const result = createResultWriter(schema, buffer, { ok: true }, false);
|
|
253
|
+
|
|
254
|
+
tag.uint64_value(5n);
|
|
255
|
+
result.uint64_value(6n);
|
|
256
|
+
expect(isNonNull(uint64Nulls(buffer), 0)).toBe(true);
|
|
257
|
+
expect(isNonNull(uint64Nulls(buffer), 1)).toBe(true);
|
|
258
|
+
|
|
259
|
+
writeUint64Runtime(result, null);
|
|
260
|
+
|
|
261
|
+
const nulls = uint64Nulls(buffer);
|
|
262
|
+
expect(isNonNull(nulls, 1)).toBe(false); // row 1 cleared
|
|
263
|
+
expect(isNonNull(nulls, 0)).toBe(true); // row 0 untouched
|
|
264
|
+
expect(uint64Column(buffer)[0]).toBe(5n);
|
|
265
|
+
});
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
// ---------------------------------------------------------------------------
|
|
269
|
+
// Boundary values
|
|
270
|
+
// ---------------------------------------------------------------------------
|
|
271
|
+
|
|
272
|
+
describe('uint64_value boundary values', () => {
|
|
273
|
+
const cases: ReadonlyArray<{ name: string; value: bigint }> = [
|
|
274
|
+
{ name: 'max uint64 (2^64 - 1)', value: 2n ** 64n - 1n },
|
|
275
|
+
{ name: 'one below max', value: 2n ** 64n - 2n },
|
|
276
|
+
{ name: '2^63 (above JS safe-integer precision)', value: 2n ** 63n },
|
|
277
|
+
{ name: '2^53 + 1 (first value not exactly representable as a JS number)', value: 2n ** 53n + 1n },
|
|
278
|
+
{ name: 'one', value: 1n },
|
|
279
|
+
];
|
|
280
|
+
|
|
281
|
+
for (const { name, value } of cases) {
|
|
282
|
+
it(`round-trips ${name} exactly`, () => {
|
|
283
|
+
const { schema, buffer } = setup();
|
|
284
|
+
createTagWriter(schema, buffer).uint64_value(value);
|
|
285
|
+
|
|
286
|
+
const values = uint64Column(buffer);
|
|
287
|
+
expect(values[0]).toBe(value);
|
|
288
|
+
expect(isNonNull(uint64Nulls(buffer), 0)).toBe(true);
|
|
289
|
+
});
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
it('stores 0n as a non-null zero, distinct from absent', () => {
|
|
293
|
+
const { schema, buffer } = setup();
|
|
294
|
+
createTagWriter(schema, buffer).uint64_value(0n);
|
|
295
|
+
|
|
296
|
+
const values = uint64Column(buffer);
|
|
297
|
+
const nulls = uint64Nulls(buffer);
|
|
298
|
+
expect(values[0]).toBe(0n);
|
|
299
|
+
// The crux: 0n is a written value, so the null bit MUST be set (non-null),
|
|
300
|
+
// distinguishing an explicit zero from an unwritten/absent row (row 1 here).
|
|
301
|
+
expect(isNonNull(nulls, 0)).toBe(true);
|
|
302
|
+
expect(isNonNull(nulls, 1)).toBe(false);
|
|
303
|
+
});
|
|
304
|
+
});
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
//#region smoo/lmao!n/flag-evaluator-generator
|
|
1
2
|
/**
|
|
2
3
|
* Worker-safe feature-flag evaluator generation.
|
|
3
4
|
*
|
|
@@ -307,3 +308,4 @@ export function createEvaluatorClass<Ctx extends OpContext>(
|
|
|
307
308
|
|
|
308
309
|
return generatedEvaluatorClass;
|
|
309
310
|
}
|
|
311
|
+
//#endregion smoo/lmao!n/flag-evaluator-generator
|
|
@@ -55,6 +55,10 @@ export type TagWriter<T extends LogSchema> = {
|
|
|
55
55
|
* Bulk set multiple attributes at once.
|
|
56
56
|
*/
|
|
57
57
|
with(attributes: Partial<InferSchema<T>>): TagWriter<T>;
|
|
58
|
+
/**
|
|
59
|
+
* Set the reserved `uint64_value` column for this span-start row.
|
|
60
|
+
*/
|
|
61
|
+
uint64_value(value: bigint): TagWriter<T>;
|
|
58
62
|
} & {
|
|
59
63
|
/**
|
|
60
64
|
* Individual attribute setters - each returns `this` for chaining.
|
|
@@ -97,6 +101,10 @@ export type ResultWriter<T extends LogSchema, R = unknown, E = unknown> = {
|
|
|
97
101
|
* Set the result-row source line (row 1).
|
|
98
102
|
*/
|
|
99
103
|
line(lineNumber: number): ResultWriter<T, R, E>;
|
|
104
|
+
/**
|
|
105
|
+
* Set the reserved `uint64_value` column for this result row.
|
|
106
|
+
*/
|
|
107
|
+
uint64_value(value: bigint): ResultWriter<T, R, E>;
|
|
100
108
|
} & {
|
|
101
109
|
/**
|
|
102
110
|
* Individual attribute setters - each returns `this` for chaining.
|
|
@@ -183,6 +191,7 @@ function generateWithMethod(schemaFields: readonly ColumnEntry[], enumFieldNames
|
|
|
183
191
|
}`;
|
|
184
192
|
}
|
|
185
193
|
|
|
194
|
+
//#region smoo/lmao!n/codegen-architecture
|
|
186
195
|
/**
|
|
187
196
|
* Generate the complete fixed-position writer class code.
|
|
188
197
|
*
|
|
@@ -295,12 +304,24 @@ const resultWriterClassCache = new WeakMap<LogSchema, unknown>();
|
|
|
295
304
|
// TagWriter API
|
|
296
305
|
// ============================================================================
|
|
297
306
|
|
|
307
|
+
/**
|
|
308
|
+
* TagWriter extension - adds the reserved system `uint64_value` writer (row 0).
|
|
309
|
+
*/
|
|
310
|
+
const tagWriterExtension: FixedPositionWriterExtension = {
|
|
311
|
+
methods: `
|
|
312
|
+
uint64_value(value) {
|
|
313
|
+
this._buffer.uint64_value(this._pos, value);
|
|
314
|
+
return this;
|
|
315
|
+
}
|
|
316
|
+
`,
|
|
317
|
+
};
|
|
318
|
+
|
|
298
319
|
/**
|
|
299
320
|
* Generate TagWriter class code for a schema.
|
|
300
321
|
* TagWriter writes to position 0 (span-start row).
|
|
301
322
|
*/
|
|
302
323
|
export function generateTagWriterClass(schema: LogSchema): string {
|
|
303
|
-
return generateFixedPositionWriterClass(schema, 0, 'GeneratedTagWriter');
|
|
324
|
+
return generateFixedPositionWriterClass(schema, 0, 'GeneratedTagWriter', tagWriterExtension);
|
|
304
325
|
}
|
|
305
326
|
|
|
306
327
|
/**
|
|
@@ -377,6 +398,11 @@ line(lineNumber) {
|
|
|
377
398
|
this._buffer.line(this._pos, lineNumber);
|
|
378
399
|
return this;
|
|
379
400
|
}
|
|
401
|
+
|
|
402
|
+
uint64_value(value) {
|
|
403
|
+
this._buffer.uint64_value(this._pos, value);
|
|
404
|
+
return this;
|
|
405
|
+
}
|
|
380
406
|
`,
|
|
381
407
|
};
|
|
382
408
|
|
|
@@ -442,3 +468,4 @@ export function createResultWriter<T extends LogSchema, R = unknown, E = unknown
|
|
|
442
468
|
const WriterClass = getResultWriterClass(schema);
|
|
443
469
|
return new WriterClass(buffer, resultOrError, isError);
|
|
444
470
|
}
|
|
471
|
+
//#endregion smoo/lmao!n/codegen-architecture
|
|
@@ -261,6 +261,7 @@ function generateEnumFluentSetters(enumFieldNames: Set<string>): string {
|
|
|
261
261
|
return setters.join('\n');
|
|
262
262
|
}
|
|
263
263
|
|
|
264
|
+
//#region smoo/lmao!n/codegen-spanlogger.scope
|
|
264
265
|
/**
|
|
265
266
|
* Generate _setScope() method code - IMMUTABLE scope semantics
|
|
266
267
|
*
|
|
@@ -365,7 +366,9 @@ function generatePrefillScopedAttributesMethod(
|
|
|
365
366
|
${columnFills.join('\n')}
|
|
366
367
|
}`;
|
|
367
368
|
}
|
|
369
|
+
//#endregion smoo/lmao!n/codegen-spanlogger.scope
|
|
368
370
|
|
|
371
|
+
//#region smoo/lmao!n/codegen-spanlogger
|
|
369
372
|
/**
|
|
370
373
|
* Build the extension for SpanLogger that extends ColumnWriter
|
|
371
374
|
*/
|
|
@@ -713,3 +716,4 @@ export function createSpanLogger<T extends LogSchema>(schema: T, buffer: SpanBuf
|
|
|
713
716
|
const SpanLoggerClass = createSpanLoggerClass(schema);
|
|
714
717
|
return new SpanLoggerClass(buffer);
|
|
715
718
|
}
|
|
719
|
+
//#endregion smoo/lmao!n/codegen-spanlogger
|
|
@@ -186,6 +186,11 @@ function getAllocatedBinaryColumn(buffer: AnySpanBuffer, columnName: string): un
|
|
|
186
186
|
return Array.isArray(column) ? column : undefined;
|
|
187
187
|
}
|
|
188
188
|
|
|
189
|
+
//#region smoo/lmao!n/scope-attributes.arrow-fill
|
|
190
|
+
// Cold-path scope materialization (01i): scope values fill NULL cells (no direct write) at
|
|
191
|
+
// Arrow conversion. These typed readers pull from the buffer's immutable _scopeValues; the
|
|
192
|
+
// per-column fill loops below call them so a row with a direct write keeps its value and scope
|
|
193
|
+
// only fills the gaps.
|
|
189
194
|
function getStringScopeValue(buffer: AnySpanBuffer, fieldName: string): string | undefined {
|
|
190
195
|
const value = buffer._scopeValues?.[fieldName];
|
|
191
196
|
return typeof value === 'string' ? value : undefined;
|
|
@@ -200,6 +205,7 @@ function getBooleanScopeValue(buffer: AnySpanBuffer, fieldName: string): boolean
|
|
|
200
205
|
const value = buffer._scopeValues?.[fieldName];
|
|
201
206
|
return typeof value === 'boolean' ? value : undefined;
|
|
202
207
|
}
|
|
208
|
+
//#endregion smoo/lmao!n/scope-attributes.arrow-fill
|
|
203
209
|
|
|
204
210
|
function requireUint8Array(value: unknown, message: string): Uint8Array {
|
|
205
211
|
if (!(value instanceof Uint8Array)) {
|