@smoothbricks/lmao 0.1.4 → 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/cloudflare.d.ts +15 -0
- package/dist/cloudflare.d.ts.map +1 -0
- package/dist/cloudflare.js +14 -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.js +1 -1
- 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 +17 -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 +4 -0
- package/dist/lib/traceRoot.d.ts +5 -5
- package/dist/lib/traceRoot.es.js +1 -1
- package/dist/lib/traceRoot.js +1 -1
- package/dist/lib/traceRoot.node.d.ts +2 -2
- package/dist/lib/traceRoot.node.js +3 -3
- package/package.json +7 -1
- package/src/cloudflare.ts +35 -0
- package/src/lib/bufferStrategy.ts +2 -2
- 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 -2
- package/src/lib/codegen/fixedPositionWriterGenerator.ts +26 -1
- package/src/lib/schema/defineFeatureFlags.ts +2 -2
- package/src/lib/schema/evaluator.ts +2 -2
- package/src/lib/sqlite/sqlite-common.ts +5 -0
- package/src/lib/testing/__tests__/facts.test.ts +6 -6
- package/src/lib/traceRoot.es.ts +2 -2
- package/src/lib/traceRoot.node.ts +3 -3
- package/src/lib/traceRoot.ts +5 -5
- package/src/lib/tracers/ArrayQueueTracer.ts +2 -2
- package/src/lib/codegen/__tests__/__snapshots__/spanLoggerGenerator.test.ts.snap +0 -1965
|
@@ -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,4 +1,4 @@
|
|
|
1
|
-
//#region
|
|
1
|
+
//#region smoo/lmao!n/flag-evaluator-generator
|
|
2
2
|
/**
|
|
3
3
|
* Worker-safe feature-flag evaluator generation.
|
|
4
4
|
*
|
|
@@ -308,4 +308,4 @@ export function createEvaluatorClass<Ctx extends OpContext>(
|
|
|
308
308
|
|
|
309
309
|
return generatedEvaluatorClass;
|
|
310
310
|
}
|
|
311
|
-
//#endregion
|
|
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.
|
|
@@ -296,12 +304,24 @@ const resultWriterClassCache = new WeakMap<LogSchema, unknown>();
|
|
|
296
304
|
// TagWriter API
|
|
297
305
|
// ============================================================================
|
|
298
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
|
+
|
|
299
319
|
/**
|
|
300
320
|
* Generate TagWriter class code for a schema.
|
|
301
321
|
* TagWriter writes to position 0 (span-start row).
|
|
302
322
|
*/
|
|
303
323
|
export function generateTagWriterClass(schema: LogSchema): string {
|
|
304
|
-
return generateFixedPositionWriterClass(schema, 0, 'GeneratedTagWriter');
|
|
324
|
+
return generateFixedPositionWriterClass(schema, 0, 'GeneratedTagWriter', tagWriterExtension);
|
|
305
325
|
}
|
|
306
326
|
|
|
307
327
|
/**
|
|
@@ -378,6 +398,11 @@ line(lineNumber) {
|
|
|
378
398
|
this._buffer.line(this._pos, lineNumber);
|
|
379
399
|
return this;
|
|
380
400
|
}
|
|
401
|
+
|
|
402
|
+
uint64_value(value) {
|
|
403
|
+
this._buffer.uint64_value(this._pos, value);
|
|
404
|
+
return this;
|
|
405
|
+
}
|
|
381
406
|
`,
|
|
382
407
|
};
|
|
383
408
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
//#region
|
|
1
|
+
//#region smoo/lmao!n/flag-flag-schema
|
|
2
2
|
import type { Output } from '@smoothbricks/arrow-builder';
|
|
3
3
|
import type { FeatureFlagDefinition } from './types.js';
|
|
4
4
|
|
|
@@ -90,4 +90,4 @@ export interface UsageContext {
|
|
|
90
90
|
value?: number;
|
|
91
91
|
metadata?: Record<string, string | number | boolean>;
|
|
92
92
|
}
|
|
93
|
-
//#endregion
|
|
93
|
+
//#endregion smoo/lmao!n/flag-flag-schema
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
//#region
|
|
1
|
+
//#region smoo/lmao!n/flag-evaluator
|
|
2
2
|
import type { Schema, SchemaWithMetadata } from '@smoothbricks/arrow-builder';
|
|
3
3
|
import { createEvaluatorClass, type GeneratedEvaluatorInstance } from '../codegen/evaluatorGenerator.js';
|
|
4
4
|
import type { FluentLogEntry } from '../codegen/spanLoggerGenerator.js';
|
|
@@ -403,4 +403,4 @@ export class InMemoryFlagEvaluator<Ctx extends OpContext = OpContext> implements
|
|
|
403
403
|
return createFeatureFlagEvaluator(this.ffSchema, ctx, this);
|
|
404
404
|
}
|
|
405
405
|
}
|
|
406
|
-
//#endregion
|
|
406
|
+
//#endregion smoo/lmao!n/flag-evaluator
|
|
@@ -294,6 +294,11 @@ export function buildInsertSql(activeUserFields: readonly string[]): string {
|
|
|
294
294
|
VALUES (?, ?, ?, ?, ?, ?, ?${userPlaceholders})`;
|
|
295
295
|
}
|
|
296
296
|
|
|
297
|
+
/** Read a user schema field for one buffer row, falling back to the buffer's scope values. */
|
|
298
|
+
export function readSpanFieldValue(buffer: AnySpanBuffer, fieldName: string, row: number): unknown {
|
|
299
|
+
return readUserValue(buffer, fieldName, row) ?? readScopeValue(buffer, fieldName);
|
|
300
|
+
}
|
|
301
|
+
|
|
297
302
|
export function buildInsertParams(segment: SpanSegment, row: number, activeUserFields: readonly string[]): unknown[] {
|
|
298
303
|
const { buffer, traceId, spanId, parentSpanId, rowOffset } = segment;
|
|
299
304
|
const entryType = buffer.entry_type[row];
|
|
@@ -286,10 +286,10 @@ describe('trace-testing example', () => {
|
|
|
286
286
|
// Simulate facts collected from a trace
|
|
287
287
|
const facts = createFactArray([
|
|
288
288
|
spanStarted('execute-loop'),
|
|
289
|
-
spanStarted('
|
|
289
|
+
spanStarted('prepare'),
|
|
290
290
|
tagFact('event_count', 5),
|
|
291
|
-
spanOk('
|
|
292
|
-
spanStarted('
|
|
291
|
+
spanOk('prepare'),
|
|
292
|
+
spanStarted('plan'),
|
|
293
293
|
spanStarted('op:reserveInventory'),
|
|
294
294
|
tagFact('sku', 'SKU-A'),
|
|
295
295
|
tagFact('quantity', 2),
|
|
@@ -297,7 +297,7 @@ describe('trace-testing example', () => {
|
|
|
297
297
|
spanStarted('op:chargePayment'),
|
|
298
298
|
logInfo('Charging $49.99'),
|
|
299
299
|
spanOk('op:chargePayment'),
|
|
300
|
-
spanOk('
|
|
300
|
+
spanOk('plan'),
|
|
301
301
|
spanOk('execute-loop'),
|
|
302
302
|
]);
|
|
303
303
|
|
|
@@ -307,8 +307,8 @@ describe('trace-testing example', () => {
|
|
|
307
307
|
// The execution completed successfully
|
|
308
308
|
expect(facts.has(spanOk('execute-loop'))).toBe(true);
|
|
309
309
|
|
|
310
|
-
//
|
|
311
|
-
expect(facts.hasInOrder([spanOk('
|
|
310
|
+
// Preparation happened before planning
|
|
311
|
+
expect(facts.hasInOrder([spanOk('prepare'), spanStarted('plan')])).toBe(true);
|
|
312
312
|
|
|
313
313
|
// Inventory was reserved before payment was charged
|
|
314
314
|
expect(facts.hasInOrder([spanOk('op:reserveInventory'), spanStarted('op:chargePayment')])).toBe(true);
|
package/src/lib/traceRoot.es.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
//#region
|
|
1
|
+
//#region smoo/lmao!n/es-trace-root
|
|
2
2
|
/**
|
|
3
3
|
* TraceRoot - Browser/ES implementation.
|
|
4
4
|
*
|
|
@@ -158,4 +158,4 @@ export function createTraceRoot(trace_id: string, tracer: TracerLifecycleHooks):
|
|
|
158
158
|
const anchorPerfNow = performance.now();
|
|
159
159
|
return new TraceRoot(createTraceId(trace_id), anchorEpochNanos, anchorPerfNow, tracer);
|
|
160
160
|
}
|
|
161
|
-
//#endregion
|
|
161
|
+
//#endregion smoo/lmao!n/es-trace-root
|
|
@@ -52,8 +52,8 @@ export class TraceRoot implements ITraceRoot {
|
|
|
52
52
|
private readonly _perfView: Float64Array;
|
|
53
53
|
|
|
54
54
|
/**
|
|
55
|
-
* Cached anchor hrtime as BigInt
|
|
56
|
-
*
|
|
55
|
+
* Cached anchor hrtime as the exact BigInt, so the JS timestamp delta stays
|
|
56
|
+
* correct after the f64 `anchorPerfNow` loses integer precision (~104 days uptime).
|
|
57
57
|
*/
|
|
58
58
|
private readonly _anchorHrtimeBigInt: bigint;
|
|
59
59
|
|
|
@@ -165,7 +165,7 @@ export class TraceRoot implements ITraceRoot {
|
|
|
165
165
|
export function createTraceRoot(trace_id: string, tracer: TracerLifecycleHooks): TraceRoot {
|
|
166
166
|
const anchorEpochNanos = BigInt(Date.now()) * 1_000_000n;
|
|
167
167
|
const anchorHrtimeBigInt = process.hrtime.bigint();
|
|
168
|
-
//
|
|
168
|
+
// Also store as f64 in the shared _system layout (WASM reads it without BigInt extraction)
|
|
169
169
|
const anchorPerfNow = Number(anchorHrtimeBigInt);
|
|
170
170
|
return new TraceRoot(createTraceId(trace_id), anchorEpochNanos, anchorPerfNow, anchorHrtimeBigInt, tracer);
|
|
171
171
|
}
|
package/src/lib/traceRoot.ts
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
*
|
|
9
9
|
* Platform-specific implementations:
|
|
10
10
|
* - TraceRoot.es.ts: Browser - uses performance.now() for timing
|
|
11
|
-
* - TraceRoot.node.ts: Node.js -
|
|
11
|
+
* - TraceRoot.node.ts: Node.js - process.hrtime.bigint(), pure-JS writes to SpanBuffer state
|
|
12
12
|
*
|
|
13
13
|
* @module traceRoot
|
|
14
14
|
*/
|
|
@@ -61,7 +61,7 @@ export interface TracerLifecycleHooks {
|
|
|
61
61
|
*
|
|
62
62
|
* Total: 17 + trace_id.length bytes
|
|
63
63
|
*
|
|
64
|
-
* This layout enables
|
|
64
|
+
* This layout enables the WASM path to read anchors directly without BigInt extraction.
|
|
65
65
|
*/
|
|
66
66
|
//#region smoo/lmao!n/trace-root-timestamps #contract
|
|
67
67
|
export const TRACE_ROOT_ANCHOR_EPOCH_OFFSET = 0;
|
|
@@ -74,7 +74,7 @@ export const TRACE_ROOT_TRACE_ID_OFFSET = 17;
|
|
|
74
74
|
*
|
|
75
75
|
* Each implementation handles timestamp calculation and span writes differently:
|
|
76
76
|
* - Browser: Pure JS with performance.now()
|
|
77
|
-
* - Node.js: NAPI addon
|
|
77
|
+
* - Node.js: process.hrtime.bigint() (pure JS; a NAPI addon was benchmarked slower than WASM and removed)
|
|
78
78
|
*/
|
|
79
79
|
/**
|
|
80
80
|
* Factory function type for creating platform-specific TraceRoot instances.
|
|
@@ -87,7 +87,7 @@ export type TraceRootFactory = (trace_id: string, tracer: TracerLifecycleHooks)
|
|
|
87
87
|
export interface ITraceRoot {
|
|
88
88
|
/**
|
|
89
89
|
* Raw backing buffer containing anchor timestamps and trace_id.
|
|
90
|
-
*
|
|
90
|
+
* The WASM path can read this directly without BigInt extraction.
|
|
91
91
|
*/
|
|
92
92
|
readonly _system: ArrayBuffer;
|
|
93
93
|
|
|
@@ -110,7 +110,7 @@ export interface ITraceRoot {
|
|
|
110
110
|
/**
|
|
111
111
|
* High-resolution timer anchor when trace was created.
|
|
112
112
|
* Browser: performance.now() value (number)
|
|
113
|
-
* Node.js: stored as number
|
|
113
|
+
* Node.js: stored as number in the shared layout (WASM-readable), but represents hrtime
|
|
114
114
|
*/
|
|
115
115
|
readonly anchorPerfNow: number;
|
|
116
116
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
//#region
|
|
1
|
+
//#region smoo/lmao!n/array-queue-tracer
|
|
2
2
|
import type { OpContextBinding } from '../opContext/types.js';
|
|
3
3
|
import { Tracer } from '../tracer.js';
|
|
4
4
|
import type { SpanBuffer } from '../types.js';
|
|
@@ -75,4 +75,4 @@ export class ArrayQueueTracer<B extends OpContextBinding = OpContextBinding> ext
|
|
|
75
75
|
return buffers;
|
|
76
76
|
}
|
|
77
77
|
}
|
|
78
|
-
//#endregion
|
|
78
|
+
//#endregion smoo/lmao!n/array-queue-tracer
|