@smoothbricks/lmao 0.2.3 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,269 +1,269 @@
1
- # lmao
1
+ # @smoothbricks/lmao
2
2
 
3
- This library was generated with [Nx](https://nx.dev).
3
+ A high-performance, type-safe structured tracing and observability library for TypeScript.
4
4
 
5
- ## Building
5
+ Instrumented code writes spans directly into columnar [Apache Arrow](https://arrow.apache.org/) buffers — near-zero
6
+ hot-path overhead — and emits queryable Arrow tables that persist to SQLite (a local file, Node, or Cloudflare D1). A
7
+ headline feature is **trace-testing**: assert on the span tree your code emits instead of on return values.
6
8
 
7
- Run `nx build lmao` to build the library.
9
+ - **Type-safe by construction.** Your log schema drives the types of `ctx.tag`, `ctx.log`, feature flags, and results —
10
+ autocomplete everywhere, no casts.
11
+ - **Columnar & cheap.** Attributes are written to fixed buffer positions; string columns are dictionary-encoded. No
12
+ per-event object allocation on the hot path.
13
+ - **Arrow-native.** Convert a trace to an Arrow table and persist it to SQLite/D1, or analyze it with the companion
14
+ inspector.
8
15
 
9
- ## Trace Testing
16
+ > Part of the `smoothbricks` monorepo. Full documentation lives in [`targets/lmao-docs/`](../../targets/lmao-docs) (an Astro Starlight
17
+ > site). Runnable examples are in [`examples/`](./examples).
10
18
 
11
- LMAO provides a trace-testing system where each test creates queryable trace spans. Instead of testing return values
12
- directly, you execute code that emits trace facts and assert on WHAT happened.
19
+ ## Install
13
20
 
14
- ### Quick Start (bun:test)
15
-
16
- 1. Create a preload file:
17
-
18
- ```typescript
19
- // test-trace-setup.ts (preload)
20
- import * as bunTest from 'bun:test';
21
- import { mock } from 'bun:test';
22
- import { createBunTestMock, initTraceTestRun } from '@smoothbricks/lmao/testing/bun';
23
- import { myOpContext } from './src/opContext.js';
24
-
25
- initTraceTestRun(myOpContext, { sqlite: { dbPath: '.trace-results.db' } });
26
- mock.module('bun:test', () => createBunTestMock(bunTest));
21
+ ```bash
22
+ bun add @smoothbricks/lmao
27
23
  ```
28
24
 
29
- `mock.module` MUST be called from the preload file itself — bun only intercepts subsequent imports when the mock is
30
- registered from the entry module context.
31
-
32
- 2. Add to `bunfig.toml`:
25
+ ## Quick start
26
+
27
+ ```ts
28
+ import {
29
+ defineLogSchema,
30
+ defineOpContext,
31
+ JsBufferStrategy,
32
+ S,
33
+ StdioTracer,
34
+ } from '@smoothbricks/lmao';
35
+ import { createTraceRoot } from '@smoothbricks/lmao/node'; // or '/es' for browsers/Workers
36
+
37
+ // 1. Describe your columns. Pick a string strategy per field:
38
+ // S.enum (known set, 1 byte) · S.category (repeating, dictionary) · S.text (mostly unique)
39
+ const schema = defineLogSchema({
40
+ userId: S.category(),
41
+ operation: S.enum(['SELECT', 'INSERT', 'UPDATE', 'DELETE']),
42
+ duration: S.number(),
43
+ });
33
44
 
34
- ```toml
35
- [test]
36
- preload = ["./test-trace-setup.ts"]
37
- ```
45
+ // 2. Bundle the schema into an op context.
46
+ const opContext = defineOpContext({ logSchema: schema });
47
+ const { defineOp } = opContext;
38
48
 
39
- 3. Write tests import from `bun:test` as normal, `mock.module` intercepts transparently:
49
+ // 3. Define an op. Its body receives a typed SpanContext (`ctx`).
50
+ const createUser = defineOp('create-user', async (ctx, email: string) => {
51
+ ctx.tag.userId(email).operation('INSERT');
52
+ ctx.log.info('creating {{userId}}').userId(email); // {{field}} = template, value set via the chain
40
53
 
41
- ```typescript
42
- import { describe, it, expect } from 'bun:test';
43
- import { useTestSpan } from '@smoothbricks/lmao/testing/bun';
44
- import { querySpan, findSpan } from '@smoothbricks/lmao/testing';
45
-
46
- describe('Order Processing', () => {
47
- it('validates and saves order', async () => {
48
- const ctx = useTestSpan();
54
+ const validated = await ctx.span('validate', async (child) => {
55
+ child.tag.operation('SELECT');
56
+ return child.ok({ valid: true });
57
+ });
58
+ if (!validated.success) return ctx.err(new Error('validation failed'));
49
59
 
50
- await ctx.span('processOrder', async (child) => {
51
- child.tag.orderId('123');
52
- await child.span('validate', async (v) => v.ok(true));
53
- await child.span('save', async (s) => s.ok({ id: 'order-123' }));
54
- return child.ok({ status: 'saved' });
55
- });
60
+ return ctx.ok({ id: 'user-1', email }).with({ duration: 12 });
61
+ });
56
62
 
57
- // Query the trace tree
58
- const q = querySpan(ctx.buffer);
59
- expect(q.names()).toEqual(['processOrder', 'validate', 'save']);
60
- expect(q.find('validate')).toBeDefined();
61
- expect(findSpan(ctx.buffer, 'save')).toBeDefined();
62
- });
63
+ // 4. Build a tracer and run at the request boundary.
64
+ const { trace } = new StdioTracer(opContext, {
65
+ bufferStrategy: new JsBufferStrategy(),
66
+ createTraceRoot,
63
67
  });
68
+
69
+ const result = await trace('create-user', createUser, 'ada@example.com');
70
+ console.log(result.success ? result.value : result.error);
64
71
  ```
65
72
 
66
- Note: `describe`/`it`/`expect` are imported from `bun:test` as normal — `mock.module` intercepts transparently. Only
67
- `useTestSpan` comes from `@smoothbricks/lmao/testing/bun`.
73
+ ## Core API
74
+
75
+ ### Schema (`S` + `defineLogSchema`)
68
76
 
69
- ### Quick Start (vitest)
77
+ | Builder | Use |
78
+ |---|---|
79
+ | `S.enum([...])` | A fixed set of known values (stored as 1 byte). |
80
+ | `S.category()` | Repeating strings (dictionary-encoded). |
81
+ | `S.text()` | Mostly-unique strings (no dictionary). |
82
+ | `S.number()` | Numeric column. |
83
+ | `S.boolean()` | Boolean column (bit-packed). |
70
84
 
71
- 1. Configure vitest:
85
+ ### `defineOpContext`
72
86
 
73
- ```typescript
74
- // vitest.config.ts
75
- export default defineConfig({
76
- test: { setupFiles: ['./test-setup.ts'] },
87
+ ```ts
88
+ const opContext = defineOpContext({
89
+ logSchema, // required
90
+ flags, // optional: defineFeatureFlags(...).schema
91
+ deps, // optional: other op groups, wired with .prefix()/.mapColumns()
92
+ ctx: { // optional: user context carried on every span
93
+ requestId: undefined as string | undefined,
94
+ },
77
95
  });
96
+ const { defineOp, defineOps } = opContext;
78
97
  ```
79
98
 
80
- 2. Create the setup file with `vi.mock` for transparent interception:
99
+ `defineOp(name, fn)` creates a single op; `defineOps({ ... })` batches ops into a reusable group.
81
100
 
82
- ```typescript
83
- // test-setup.ts
84
- import { vi } from 'vitest';
85
- import { createNodeSQLiteDatabase } from '@smoothbricks/lmao/sqlite/node';
86
- import { myOpContext } from './src/opContext.js';
101
+ ### `SpanContext` (`ctx`)
87
102
 
88
- vi.mock('vitest', async (importOriginal) => {
89
- const [mod, { createVitestMock }] = await Promise.all([
90
- importOriginal(),
91
- import('@smoothbricks/lmao/testing/vitest'),
92
- ]);
93
- return createVitestMock(mod as Record<string, unknown>);
94
- });
103
+ The object every op body receives:
95
104
 
96
- import { initTraceTestRun } from '@smoothbricks/lmao/testing/vitest';
97
- initTraceTestRun(myOpContext, {
98
- sqlite: { dbPath: '.trace-results.db', createDatabase: createNodeSQLiteDatabase },
99
- });
100
- ```
105
+ | Member | What it does |
106
+ |---|---|
107
+ | `ctx.tag.field(value)` | Set span-start attributes (row 0). Chainable; `ctx.tag.with({...})` sets several. |
108
+ | `ctx.log.info/debug/warn/error(msg)` | Append a log event. `msg` is a `{{field}}` template; attach values via the chain. |
109
+ | `ctx.span(name, opOrFn, ...args)` | Open a child span; returns `Promise<Result>`. |
110
+ | `ctx.spanSync(name, fn)` | Open a **synchronous** child span; `fn` returns a `Result` directly (no `await`). |
111
+ | `ctx.ok(value)` / `ctx.err(error)` | Complete the span. Both chain `.with({...})` and `.message('...')`. |
112
+ | `ctx.setScope({...})` / `ctx.scope` | Set/read attributes inherited by all subsequent logs and child spans. |
113
+ | `ctx.ff` | Feature-flag access (present when `flags` is declared). |
114
+ | `ctx.deps` | Declared dependency op groups. |
115
+ | `ctx.buffer` | The underlying span buffer (used in tests and for Arrow conversion). |
116
+ | user context | Anything declared in `ctx: {...}` (e.g. `ctx.requestId`). |
101
117
 
102
- 3. Write tests import from `vitest` as normal, `vi.mock` intercepts transparently:
118
+ ### Results & typed errors
103
119
 
104
- ```typescript
105
- import { describe, it, expect } from 'vitest';
106
- import { useTestSpan } from '@smoothbricks/lmao/testing/vitest';
107
- import { querySpan, findSpan } from '@smoothbricks/lmao/testing';
120
+ Ops return a `Result` — check `result.success`, then read `result.value` or `result.error`. Error codes are typed
121
+ factories:
108
122
 
109
- describe('Order Processing', () => {
110
- it('validates and saves order', async () => {
111
- const ctx = useTestSpan();
123
+ ```ts
124
+ import { defineCodeError } from '@smoothbricks/lmao';
112
125
 
113
- await ctx.span('processOrder', async (child) => {
114
- child.tag.orderId('123');
115
- await child.span('validate', async (v) => v.ok(true));
116
- return child.ok({ status: 'saved' });
117
- });
126
+ const NOT_FOUND = defineCodeError('NOT_FOUND')<{ userId: string }>();
118
127
 
119
- const q = querySpan(ctx.buffer);
120
- expect(q.names()).toContain('validate');
121
- expect(findSpan(ctx.buffer, 'processOrder')).toBeDefined();
122
- });
128
+ const getUser = defineOp('get-user', async (ctx, id: string) => {
129
+ const user = await lookup(id);
130
+ return user ? ctx.ok(user) : ctx.err(NOT_FOUND({ userId: id }));
123
131
  });
132
+
133
+ const r = await trace('get-user', getUser, 'u1');
134
+ if (r.success) {
135
+ console.log(r.value);
136
+ } else {
137
+ console.log(r.error.code, r.error.userId); // 'NOT_FOUND' + the typed payload field
138
+ }
124
139
  ```
125
140
 
126
- Both bun:test and vitest use the same transparent interception pattern — tests import from their native test module as
127
- normal. Only `useTestSpan` (for accessing the it-local trace root) comes from the lmao testing module.
141
+ ## Tracers
142
+
143
+ Construct a tracer with `new SomeTracer(opContext, options)`, then destructure `trace`. Every tracer needs a
144
+ `bufferStrategy` (`new JsBufferStrategy()`) and a `createTraceRoot` (from `@smoothbricks/lmao/node` for Node, or `/es`
145
+ for browsers/Deno/Workers).
146
+
147
+ | Tracer | Use |
148
+ |---|---|
149
+ | `StdioTracer` | Print the span tree to the console. |
150
+ | `TestTracer` | Keep completed traces in memory (`tracer.rootBuffers`) for inspection/tests. |
151
+ | `ArrayQueueTracer` | Queue completed traces for batch processing (`tracer.drain()`). |
152
+ | `SQLiteTracer` / `SQLiteAsyncTracer` | Persist to a synchronous SQLite DB, or an async one (e.g. D1). |
153
+ | `CompositeTracer` | Fan out to several tracers. |
154
+ | `NoOpTracer` | Execute ops without emitting. |
155
+
156
+ ```ts
157
+ // trace() runs an op (or an inline fn) as the root span; overrides carry user-context values.
158
+ await trace('greet', greet);
159
+ await trace('greet', { requestId: 'req-1' }, greet, ...args);
160
+ ```
128
161
 
129
- #### Cloudflare Worker + Vitest notes
162
+ ## Feature flags
130
163
 
131
- - Keep the setup file wiring-only: call `setupVitestTestSuiteTracing(...)` and keep the `vi.mock('vitest', ...)` bridge
132
- with `createVitestMock(...)`.
133
- - Configure SQLite sink in the package-local vitest tracer module (not in test files) so worker suites flush to a
134
- worker-appropriate SQLite target (for example `d1://TRACE_RESULTS`).
135
- - Debug logging is harness-owned and toggled by `LMAO_VITEST_DEBUG`.
136
- - Verbose trace replay is toggled by `LMAO_TEST_TRACE_VERBOSE` (prints spans to stdout and still flushes SQLite).
137
- - In Worker tests, forward the debug env in `vitest.config.ts` via `define`, for example
138
- `globalThis.__LMAO_VITEST_DEBUG_ENV__`, and do the same for `globalThis.__LMAO_TEST_TRACE_VERBOSE_ENV__` when verbose
139
- replay is needed.
140
- - Enable debug for Cloudflare tests with:
164
+ Declare flags, pass the schema to `defineOpContext`, and supply an evaluator **to the tracer**:
141
165
 
142
- ```bash
143
- ```
166
+ ```ts
167
+ import { defineFeatureFlags, InMemoryFlagEvaluator, S } from '@smoothbricks/lmao';
144
168
 
145
- ### Setting Up Trace Testing for a New Package
169
+ const flags = defineFeatureFlags({
170
+ advancedValidation: S.boolean().default(false).sync(),
171
+ maxRetries: S.number().default(3).sync(),
172
+ });
146
173
 
147
- 1. Create the preload file (`test-trace-setup.ts`):
174
+ const opContext = defineOpContext({ logSchema: schema, flags: flags.schema });
148
175
 
149
- ```typescript
150
- import * as bunTest from 'bun:test';
151
- import { mock } from 'bun:test';
152
- import { createBunTestMock, initTraceTestRun } from '@smoothbricks/lmao/testing/bun';
153
- import { myOpContext } from './src/opContext.js';
176
+ const { trace } = new StdioTracer(opContext, {
177
+ bufferStrategy: new JsBufferStrategy(),
178
+ createTraceRoot,
179
+ flagEvaluator: new InMemoryFlagEvaluator(flags.schema, { advancedValidation: true, maxRetries: 5 }),
180
+ });
154
181
 
155
- initTraceTestRun(myOpContext, { sqlite: { dbPath: '.trace-results.db' } });
156
- mock.module('bun:test', () => createBunTestMock(bunTest));
182
+ // Inside an op sync flags are read as `ctx.ff.<name>?.value`:
183
+ if (ctx.ff.advancedValidation?.value) { /* ... */ }
157
184
  ```
158
185
 
159
- 2. Add `bunfig.toml`:
186
+ ## Persist & analyze
160
187
 
161
- ```toml
162
- [test]
163
- preload = ["./test-trace-setup.ts"]
164
- ```
188
+ Convert a completed trace to an Arrow table, then persist or query it:
165
189
 
166
- 3. Tests import `describe`/`it`/`expect` from `bun:test` as normal — the mock intercepts transparently.
190
+ ```ts
191
+ import { convertSpanTreeToArrowTable } from '@smoothbricks/lmao';
167
192
 
168
- 4. Add `.trace-results.db` to `.gitignore`.
193
+ const table = convertSpanTreeToArrowTable(tracer.rootBuffers[0]);
194
+ console.log(table.numRows, table.names);
195
+ ```
169
196
 
170
- ### Querying Trace Results
197
+ - **SQLite / D1** — use `SQLiteTracer`/`SQLiteAsyncTracer` with `createNodeSQLiteDatabase` (`@smoothbricks/lmao/sqlite/node`)
198
+ or `createD1SQLiteDatabase` (`@smoothbricks/lmao/sqlite`).
199
+ - **Query engine** — the companion package [`@smoothbricks/lmao-inspector`](../lmao-inspector) runs SQL over exported
200
+ Arrow data in the browser.
171
201
 
172
- After a test run, the trace database is written to the configured path. The `trace_id` is printed at the end:
202
+ ## Trace-testing
173
203
 
174
- ```
175
- [trace] trace_id: 550e8400-e29b-41d4-a716-446655440000 → .trace-results.db
176
- ```
204
+ Assert on **what your code did**, not just what it returned. Each test's ops emit a queryable span tree.
177
205
 
178
- The `trace_id` IS the run identifier — one root span per test run, with each `it()` as a child span.
206
+ ```ts
207
+ import { describe, it, expect } from 'bun:test';
208
+ import { useTestSpan } from '@smoothbricks/lmao/testing/bun';
209
+ import { querySpan, findSpan } from '@smoothbricks/lmao/testing';
179
210
 
180
- **SQLite CLI queries:**
211
+ describe('order processing', () => {
212
+ it('validates and saves', async () => {
213
+ const ctx = useTestSpan();
181
214
 
182
- ```bash
183
- # All spans for the latest trace (root span name = 'test-run')
184
- sqlite3 .trace-results.db "SELECT s0.message, s0.describe FROM spans s0 WHERE s0.row_index = 0 ORDER BY s0.timestamp_ns"
185
-
186
- # Find root span_id, then query it-level spans
187
- sqlite3 .trace-results.db "
188
- SELECT s0.message AS test_name, s0.describe,
189
- CASE WHEN s1.entry_type = 2 THEN 'ok'
190
- WHEN s1.entry_type = 3 THEN 'err'
191
- WHEN s1.entry_type = 4 THEN 'exception'
192
- ELSE 'running' END AS status,
193
- s1.timestamp_ns - s0.timestamp_ns AS duration_ns
194
- FROM spans s0
195
- LEFT JOIN spans s1 ON s1.trace_id = s0.trace_id AND s1.span_id = s0.span_id AND s1.row_index = 1
196
- WHERE s0.trace_id = (SELECT trace_id FROM spans WHERE parent_span_id = 0 AND row_index = 0 ORDER BY timestamp_ns DESC LIMIT 1)
197
- AND s0.parent_span_id = (SELECT span_id FROM spans WHERE parent_span_id = 0 AND row_index = 0 ORDER BY timestamp_ns DESC LIMIT 1)
198
- AND s0.row_index = 0
199
- ORDER BY s0.timestamp_ns"
200
-
201
- # All tests under a specific describe group
202
- sqlite3 .trace-results.db "SELECT message FROM spans WHERE describe = 'Order Processing > validation' AND row_index = 0"
203
-
204
- # Nested describe paths use ' > ' separator
205
- sqlite3 .trace-results.db "SELECT DISTINCT describe FROM spans WHERE describe IS NOT NULL AND row_index = 0"
206
- ```
215
+ await ctx.span('processOrder', async (child) => {
216
+ child.tag.orderId('123');
217
+ await child.span('validate', async (v) => v.ok(true));
218
+ await child.span('save', async (s) => s.ok({ id: 'order-123' }));
219
+ return child.ok({ status: 'saved' });
220
+ });
207
221
 
208
- **Schema:**
209
-
210
- | Column | Description |
211
- | ---------------- | ---------------------------------------------------------------- |
212
- | `trace_id` | Run identifier (= root span's trace_id) |
213
- | `span_id` | Unique span counter within trace |
214
- | `parent_span_id` | Parent span (0 = root, root's span_id = it-level) |
215
- | `row_index` | Row within span (0 = span-start, 1 = span-end, 2+ = log entries) |
216
- | `entry_type` | 1=span-start, 2=span-ok, 3=span-err, 4=span-exception |
217
- | `timestamp_ns` | Nanosecond timestamp |
218
- | `message` | Span name (row 0), log message (rows 2+) |
219
- | `describe` | `' > '`-separated describe path (user schema column) |
220
- | `...` | Additional user schema columns added dynamically via ALTER TABLE |
221
-
222
- Tree structure is encoded via `span_id` / `parent_span_id`. The root span (`parent_span_id = 0`) represents the entire
223
- test run. Each `it()` is a direct child of the root. User operations create deeper children.
224
-
225
- **TraceQuery API (programmatic access):**
226
-
227
- ```typescript
228
- import { Database } from 'bun:sqlite';
229
- import { TraceQuery } from '@smoothbricks/lmao/testing';
230
-
231
- const query = new TraceQuery(new Database('.trace-results.db'));
232
- query.failures(); // all failed tests
233
- query.slowest(undefined, 10); // 10 slowest tests
234
- query.findSpans('%validate%'); // spans matching pattern
235
- query.testTree('my-test'); // full span tree for a test
236
- query.close();
222
+ const q = querySpan(ctx.buffer);
223
+ expect(q.names()).toEqual(['processOrder', 'validate', 'save']);
224
+ expect(findSpan(ctx.buffer, 'save')).toBeDefined();
225
+ });
226
+ });
237
227
  ```
238
228
 
239
- ### Querying Span Results (in-test)
229
+ Setup is wiring-only: a preload/setup file calls `initTraceTestRun(opContext, { sqlite: { dbPath: '.trace-results.db' } })`
230
+ and installs a transparent mock so tests import `describe`/`it`/`expect` from their native runner as usual. Bun and
231
+ Vitest are both supported (`@smoothbricks/lmao/testing/bun` · `@smoothbricks/lmao/testing/vitest`). Traces flush to a
232
+ SQLite sink you can query with the `TraceQuery` API or the `sqlite3` CLI. See the docs for the full harness setup, the
233
+ SQLite schema, and query recipes.
240
234
 
241
- **QueryableSpan** wraps a SpanBuffer with ergonomic helpers:
235
+ ## Package exports
242
236
 
243
- ```typescript
244
- import { querySpan } from '@smoothbricks/lmao/testing';
237
+ | Import | Provides |
238
+ |---|---|
239
+ | `@smoothbricks/lmao` | Core API: `defineOpContext`, `defineLogSchema`, `defineFeatureFlags`, `S`, `JsBufferStrategy`, all tracers, Arrow conversion, results (`Ok`/`Err`/`defineCodeError`), `InMemoryFlagEvaluator`, entry-type constants. |
240
+ | `@smoothbricks/lmao/node` | `createTraceRoot` using `process.hrtime.bigint()`. |
241
+ | `@smoothbricks/lmao/es` | `createTraceRoot` using `performance.now()` (browser/Deno/Workers). |
242
+ | `@smoothbricks/lmao/sqlite`, `/sqlite/node` | SQLite/D1 tracers and database factories. |
243
+ | `@smoothbricks/lmao/cloudflare` | Cloudflare trace-sink adapters (`DiagnosticDrainTracer`, `ClassSplitTracer`, transports). *Partially implemented.* |
244
+ | `@smoothbricks/lmao/errors*` | `Transient`, `Blocked`, `defineCodeError`, backoff/retry helpers. |
245
+ | `@smoothbricks/lmao/testing`, `/testing/bun`, `/testing/vitest` | Trace-testing query API and runner harnesses. |
245
246
 
246
- const q = querySpan(tracer.rootBuffers[0]);
247
- q.name; // span name
248
- q.facts(); // all facts from this span tree
249
- q.find('validate'); // first child span by name
250
- q.findAll('db-query'); // all matching descendants
251
- q.children; // direct child QueryableSpans
252
- q.names(); // all descendant span names
253
- ```
247
+ ### Companion packages
254
248
 
255
- **Standalone functions** (tree-shakable):
249
+ - [`@smoothbricks/lmao-inspector`](../lmao-inspector) — client-side Arrow query engine and trace sources.
250
+ - [`@smoothbricks/lmao-transformer`](../lmao-transformer) — optional build-time TypeScript transformer (source-line
251
+ injection and `ctx.tag` inlining).
256
252
 
257
- ```typescript
258
- import { findSpan, extractFactsFor, spanNames } from '@smoothbricks/lmao/testing';
253
+ ## Examples
259
254
 
260
- const span = findSpan(rootBuffer, 'validate');
261
- const facts = extractFactsFor(rootBuffer, 'save');
262
- const names = spanNames(rootBuffer);
263
- ```
255
+ Runnable with `bun run examples/<name>.ts`:
264
256
 
265
- ### SQLite Persistence
257
+ | Example | Shows |
258
+ |---|---|
259
+ | `basic-usage.ts` | Schema, feature flags, user context, fluent tags, child spans. |
260
+ | `fluent-result-api.ts` | `ok`/`err` with `.with()`/`.message()`, typed error codes, exceptions. |
261
+ | `chaining-showcase.ts` | Tag/log fluent chaining patterns. |
262
+ | `middleware-pattern.ts` | Wrapping ops with cross-cutting behavior. |
263
+ | `library-integration.ts` | Composing op groups across packages with `.prefix()`/`.mapColumns()`. |
264
+ | `complete-example.ts` | An end-to-end request flow. |
265
+ | `arrow-export.ts` | Converting a trace to an Arrow table. |
266
266
 
267
- When configured, the trace database is written after all tests complete. Schema columns evolve automatically based on
268
- your LogSchema fields.
267
+ ## License
269
268
 
269
+ MIT
@@ -26,13 +26,13 @@ import type { SpanBuffer } from '../types.js';
26
26
  import type { Op } from './opTypes.js';
27
27
  import type { OpContext } from './types.js';
28
28
  /**
29
- * DepsConfig - structural type to avoid importing from opGroupTypes.
30
- * Represents a record of dependency groups.
29
+ * DepsConfig - structural placeholder for a record of dependency groups.
30
+ * The authoritative type is `DepsConfig` in opGroupTypes; see the WHY above.
31
31
  */
32
32
  export type DepsConfig = Record<string, any>;
33
33
  /**
34
- * ResolvedDeps - structural type to avoid importing from opGroupTypes.
35
- * At runtime, deps are resolved OpGroups with their ops accessible.
34
+ * ResolvedDeps - structural placeholder for resolved dependency OpGroups.
35
+ * The authoritative type is `ResolvedDeps` in opGroupTypes; see the WHY above.
36
36
  */
37
37
  export type ResolvedDeps<_D extends DepsConfig> = Record<string, any>;
38
38
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"spanContextTypes.d.ts","sourceRoot":"","sources":["../../../src/lib/opContext/spanContextTypes.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"spanContextTypes.d.ts","sourceRoot":"","sources":["../../../src/lib/opContext/spanContextTypes.ts"],"names":[],"mappings":"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"}
@@ -1,52 +1,12 @@
1
1
  /**
2
- * Type guard functions for runtime type checking
2
+ * Schema type-guard re-exports.
3
3
  *
4
- * Re-exports schema type guards from arrow-builder and adds lmao-specific guards.
4
+ * Re-exports the schema introspection guards from arrow-builder so the rest of
5
+ * the package imports them from a single place.
6
+ *
7
+ * NOTE: Do not hand-write `isRecord`/`typeof` guards here. For runtime
8
+ * validation at trust boundaries use Typia (`typia.is<T>()`) or
9
+ * `@smoothbricks/validation`'s shared helpers (e.g. `isRecord`).
5
10
  */
6
11
  export { getBinaryEncoder, getEnumUtf8, getEnumValues, getSchemaType, isEnumSchema, isSchemaWithMetadata, } from '@smoothbricks/arrow-builder';
7
- import type { EvaluationContext, UsageContext } from './defineFeatureFlags.js';
8
- import type { FeatureFlagDefinition } from './types.js';
9
- /** Narrow unknown to Record<string, unknown> — non-null object with string-keyed access. */
10
- export declare function isRecord(value: unknown): value is Record<string, unknown>;
11
- /**
12
- * Type guard to check if a value is a FeatureFlagDefinition
13
- */
14
- export declare function isFeatureFlagDefinition(value: unknown): value is FeatureFlagDefinition<string | number | boolean>;
15
- /**
16
- * Type guard to check if a value is a valid EvaluationContext
17
- */
18
- export declare function isEvaluationContext(value: unknown): value is EvaluationContext;
19
- /**
20
- * Type guard to check if a value is a valid UsageContext
21
- */
22
- export declare function isUsageContext(value: unknown): value is UsageContext;
23
- /**
24
- * Type guard to check if a value is a string
25
- * Useful for narrowing unknown types in safe contexts
26
- */
27
- export declare function isString(value: unknown): value is string;
28
- /**
29
- * Type guard to check if a value is a number
30
- * Useful for narrowing unknown types in safe contexts
31
- */
32
- export declare function isNumber(value: unknown): value is number;
33
- /**
34
- * Type guard to check if a value is a boolean
35
- * Useful for narrowing unknown types in safe contexts
36
- */
37
- export declare function isBoolean(value: unknown): value is boolean;
38
- /**
39
- * Type guard to check if a value is a primitive type (string | number | boolean)
40
- * Useful for validating feature flag values and tag attributes
41
- */
42
- export declare function isPrimitive(value: unknown): value is string | number | boolean;
43
- /**
44
- * Type guard to check if a value is a plain object (not array, not null)
45
- */
46
- export declare function isPlainObject(value: unknown): value is Record<string, unknown>;
47
- /**
48
- * Type guard to check if a value is a valid record with primitive values
49
- * Useful for validating tag attribute objects
50
- */
51
- export declare function isRecordOfPrimitives(value: unknown): value is Record<string, string | number | boolean>;
52
12
  //# sourceMappingURL=typeGuards.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"typeGuards.d.ts","sourceRoot":"","sources":["../../../src/lib/schema/typeGuards.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAGH,OAAO,EACL,gBAAgB,EAChB,WAAW,EACX,aAAa,EACb,aAAa,EACb,YAAY,EACZ,oBAAoB,GACrB,MAAM,6BAA6B,CAAC;AAErC,OAAO,KAAK,EAAE,iBAAiB,EAAE,YAAY,EAAE,MAAM,yBAAyB,CAAC;AAC/E,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,YAAY,CAAC;AAExD,4FAA4F;AAC5F,wBAAgB,QAAQ,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAEzE;AAED;;GAEG;AACH,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,qBAAqB,CAAC,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC,CAWjH;AAED;;GAEG;AACH,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,iBAAiB,CAe9E;AAED;;GAEG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,YAAY,CAqCpE;AAED;;;GAGG;AACH,wBAAgB,QAAQ,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,MAAM,CAExD;AAED;;;GAGG;AACH,wBAAgB,QAAQ,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,MAAM,CAExD;AAED;;;GAGG;AACH,wBAAgB,SAAS,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,OAAO,CAE1D;AAED;;;GAGG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,MAAM,GAAG,MAAM,GAAG,OAAO,CAE9E;AAED;;GAEG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAO9E;AAED;;;GAGG;AACH,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC,CAYvG"}
1
+ {"version":3,"file":"typeGuards.d.ts","sourceRoot":"","sources":["../../../src/lib/schema/typeGuards.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,EACL,gBAAgB,EAChB,WAAW,EACX,aAAa,EACb,aAAa,EACb,YAAY,EACZ,oBAAoB,GACrB,MAAM,6BAA6B,CAAC"}
@@ -1,127 +1,11 @@
1
1
  /**
2
- * Type guard functions for runtime type checking
2
+ * Schema type-guard re-exports.
3
3
  *
4
- * Re-exports schema type guards from arrow-builder and adds lmao-specific guards.
4
+ * Re-exports the schema introspection guards from arrow-builder so the rest of
5
+ * the package imports them from a single place.
6
+ *
7
+ * NOTE: Do not hand-write `isRecord`/`typeof` guards here. For runtime
8
+ * validation at trust boundaries use Typia (`typia.is<T>()`) or
9
+ * `@smoothbricks/validation`'s shared helpers (e.g. `isRecord`).
5
10
  */
6
- // Re-export schema type guards from arrow-builder
7
11
  export { getBinaryEncoder, getEnumUtf8, getEnumValues, getSchemaType, isEnumSchema, isSchemaWithMetadata, } from '@smoothbricks/arrow-builder';
8
- /** Narrow unknown to Record<string, unknown> — non-null object with string-keyed access. */
9
- export function isRecord(value) {
10
- return typeof value === 'object' && value !== null && !Array.isArray(value);
11
- }
12
- /**
13
- * Type guard to check if a value is a FeatureFlagDefinition
14
- */
15
- export function isFeatureFlagDefinition(value) {
16
- if (!isRecord(value)) {
17
- return false;
18
- }
19
- return ('schema' in value &&
20
- 'defaultValue' in value &&
21
- 'evaluationType' in value &&
22
- (value.evaluationType === 'sync' || value.evaluationType === 'async'));
23
- }
24
- /**
25
- * Type guard to check if a value is a valid EvaluationContext
26
- */
27
- export function isEvaluationContext(value) {
28
- if (!isRecord(value)) {
29
- return false;
30
- }
31
- // EvaluationContext can have various string/number/boolean properties
32
- // Check that all values are of valid types
33
- for (const key in value) {
34
- const val = value[key];
35
- if (val !== undefined && typeof val !== 'string' && typeof val !== 'number' && typeof val !== 'boolean') {
36
- return false;
37
- }
38
- }
39
- return true;
40
- }
41
- /**
42
- * Type guard to check if a value is a valid UsageContext
43
- */
44
- export function isUsageContext(value) {
45
- if (!isRecord(value)) {
46
- return false;
47
- }
48
- // Check action property if present
49
- if ('action' in value && typeof value.action !== 'string' && value.action !== undefined) {
50
- return false;
51
- }
52
- // Check outcome property if present
53
- if ('outcome' in value && value.outcome !== undefined && value.outcome !== 'success' && value.outcome !== 'failure') {
54
- return false;
55
- }
56
- // Check value property if present
57
- if ('value' in value && typeof value.value !== 'number' && value.value !== undefined) {
58
- return false;
59
- }
60
- // Check metadata property if present
61
- if ('metadata' in value && value.metadata !== undefined) {
62
- if (!isRecord(value.metadata)) {
63
- return false;
64
- }
65
- // Validate metadata values
66
- const metadata = value.metadata;
67
- for (const key in metadata) {
68
- const val = metadata[key];
69
- if (typeof val !== 'string' && typeof val !== 'number' && typeof val !== 'boolean') {
70
- return false;
71
- }
72
- }
73
- }
74
- return true;
75
- }
76
- /**
77
- * Type guard to check if a value is a string
78
- * Useful for narrowing unknown types in safe contexts
79
- */
80
- export function isString(value) {
81
- return typeof value === 'string';
82
- }
83
- /**
84
- * Type guard to check if a value is a number
85
- * Useful for narrowing unknown types in safe contexts
86
- */
87
- export function isNumber(value) {
88
- return typeof value === 'number' && !Number.isNaN(value);
89
- }
90
- /**
91
- * Type guard to check if a value is a boolean
92
- * Useful for narrowing unknown types in safe contexts
93
- */
94
- export function isBoolean(value) {
95
- return typeof value === 'boolean';
96
- }
97
- /**
98
- * Type guard to check if a value is a primitive type (string | number | boolean)
99
- * Useful for validating feature flag values and tag attributes
100
- */
101
- export function isPrimitive(value) {
102
- return isString(value) || isNumber(value) || isBoolean(value);
103
- }
104
- /**
105
- * Type guard to check if a value is a plain object (not array, not null)
106
- */
107
- export function isPlainObject(value) {
108
- return (typeof value === 'object' &&
109
- value !== null &&
110
- !Array.isArray(value) &&
111
- Object.getPrototypeOf(value) === Object.prototype);
112
- }
113
- /**
114
- * Type guard to check if a value is a valid record with primitive values
115
- * Useful for validating tag attribute objects
116
- */
117
- export function isRecordOfPrimitives(value) {
118
- if (!isPlainObject(value)) {
119
- return false;
120
- }
121
- for (const key in value) {
122
- if (!isPrimitive(value[key])) {
123
- return false;
124
- }
125
- }
126
- return true;
127
- }
@@ -1 +1 @@
1
- {"version":3,"file":"traceContext.d.ts","sourceRoot":"","sources":["../../src/lib/traceContext.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"traceContext.d.ts","sourceRoot":"","sources":["../../src/lib/traceContext.ts"],"names":[],"mappings":"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"}
@@ -38,8 +38,5 @@ export const TraceContextProto = {
38
38
  * Type guard to check if a value is a TraceContext
39
39
  */
40
40
  export function isTraceContext(value) {
41
- return (typeof value === 'object' &&
42
- value !== null &&
43
- TRACE_CONTEXT_MARKER in value &&
44
- value[TRACE_CONTEXT_MARKER] === true);
41
+ return typeof value === 'object' && value !== null && Reflect.get(value, TRACE_CONTEXT_MARKER) === true;
45
42
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@smoothbricks/lmao",
3
- "version": "0.2.3",
3
+ "version": "0.3.0",
4
4
  "type": "module",
5
5
  "sideEffects": [
6
6
  "./dist/vocabulary/register/v1.js",
@@ -28,21 +28,38 @@ import type { Op } from './opTypes.js';
28
28
  import type { OpContext } from './types.js';
29
29
 
30
30
  // =============================================================================
31
- // DEPENDENCY TYPES (structural to avoid circular imports)
31
+ // DEPENDENCY TYPES (deliberate `any` placeholders see rationale below)
32
32
  // =============================================================================
33
33
 
34
+ // WHY these are `any` and not the real `DepsConfig`/`ResolvedDeps` from opGroupTypes:
35
+ //
36
+ // `ResolvedDeps<D>` maps each dep to `OpGroup<Ctx, Ops>`, and an OpGroup's ops carry
37
+ // `SpanContext<DepCtx>` in their signatures. If `SpanContext` referenced the *real*
38
+ // `ResolvedDeps<Ctx['deps']>` for its `deps` member, `SpanContext` would become
39
+ // structurally recursive: deps -> OpGroup -> op -> SpanContext -> deps -> ...
40
+ //
41
+ // That recursion breaks the structural assignability TypeScript performs when a
42
+ // dependency's op is passed to `ctx.span(name, ctx.deps.x.op)` across libraries
43
+ // (verified: it regresses the nested-library-tasks composition tests) and makes
44
+ // type comparison cost explode. The `any` keeps `ctx.deps.x.op` ergonomic for op
45
+ // authors while keeping `SpanContext` comparisons tractable and non-recursive.
46
+ //
47
+ // A real fix (fully-typed `ctx.deps`) requires reworking deps resolution so the
48
+ // resolved-deps view does not pull `SpanContext` back into its own definition.
49
+ // Until then these placeholders are load-bearing, not laziness.
50
+
34
51
  /**
35
- * DepsConfig - structural type to avoid importing from opGroupTypes.
36
- * Represents a record of dependency groups.
52
+ * DepsConfig - structural placeholder for a record of dependency groups.
53
+ * The authoritative type is `DepsConfig` in opGroupTypes; see the WHY above.
37
54
  */
38
- // biome-ignore lint/suspicious/noExplicitAny: Structural placeholder - actual type in opGroupTypes
55
+ // biome-ignore lint/suspicious/noExplicitAny: load-bearing placeholder real type makes SpanContext recursive (see WHY above)
39
56
  export type DepsConfig = Record<string, any>;
40
57
 
41
58
  /**
42
- * ResolvedDeps - structural type to avoid importing from opGroupTypes.
43
- * At runtime, deps are resolved OpGroups with their ops accessible.
59
+ * ResolvedDeps - structural placeholder for resolved dependency OpGroups.
60
+ * The authoritative type is `ResolvedDeps` in opGroupTypes; see the WHY above.
44
61
  */
45
- // biome-ignore lint/suspicious/noExplicitAny: Structural placeholder - actual type in opGroupTypes
62
+ // biome-ignore lint/suspicious/noExplicitAny: load-bearing placeholder real type makes SpanContext recursive (see WHY above)
46
63
  export type ResolvedDeps<_D extends DepsConfig> = Record<string, any>;
47
64
 
48
65
  // =============================================================================
@@ -1,10 +1,14 @@
1
1
  /**
2
- * Type guard functions for runtime type checking
2
+ * Schema type-guard re-exports.
3
3
  *
4
- * Re-exports schema type guards from arrow-builder and adds lmao-specific guards.
4
+ * Re-exports the schema introspection guards from arrow-builder so the rest of
5
+ * the package imports them from a single place.
6
+ *
7
+ * NOTE: Do not hand-write `isRecord`/`typeof` guards here. For runtime
8
+ * validation at trust boundaries use Typia (`typia.is<T>()`) or
9
+ * `@smoothbricks/validation`'s shared helpers (e.g. `isRecord`).
5
10
  */
6
11
 
7
- // Re-export schema type guards from arrow-builder
8
12
  export {
9
13
  getBinaryEncoder,
10
14
  getEnumUtf8,
@@ -13,151 +17,3 @@ export {
13
17
  isEnumSchema,
14
18
  isSchemaWithMetadata,
15
19
  } from '@smoothbricks/arrow-builder';
16
-
17
- import type { EvaluationContext, UsageContext } from './defineFeatureFlags.js';
18
- import type { FeatureFlagDefinition } from './types.js';
19
-
20
- /** Narrow unknown to Record<string, unknown> — non-null object with string-keyed access. */
21
- export function isRecord(value: unknown): value is Record<string, unknown> {
22
- return typeof value === 'object' && value !== null && !Array.isArray(value);
23
- }
24
-
25
- /**
26
- * Type guard to check if a value is a FeatureFlagDefinition
27
- */
28
- export function isFeatureFlagDefinition(value: unknown): value is FeatureFlagDefinition<string | number | boolean> {
29
- if (!isRecord(value)) {
30
- return false;
31
- }
32
-
33
- return (
34
- 'schema' in value &&
35
- 'defaultValue' in value &&
36
- 'evaluationType' in value &&
37
- (value.evaluationType === 'sync' || value.evaluationType === 'async')
38
- );
39
- }
40
-
41
- /**
42
- * Type guard to check if a value is a valid EvaluationContext
43
- */
44
- export function isEvaluationContext(value: unknown): value is EvaluationContext {
45
- if (!isRecord(value)) {
46
- return false;
47
- }
48
-
49
- // EvaluationContext can have various string/number/boolean properties
50
- // Check that all values are of valid types
51
- for (const key in value) {
52
- const val = value[key];
53
- if (val !== undefined && typeof val !== 'string' && typeof val !== 'number' && typeof val !== 'boolean') {
54
- return false;
55
- }
56
- }
57
-
58
- return true;
59
- }
60
-
61
- /**
62
- * Type guard to check if a value is a valid UsageContext
63
- */
64
- export function isUsageContext(value: unknown): value is UsageContext {
65
- if (!isRecord(value)) {
66
- return false;
67
- }
68
-
69
- // Check action property if present
70
- if ('action' in value && typeof value.action !== 'string' && value.action !== undefined) {
71
- return false;
72
- }
73
-
74
- // Check outcome property if present
75
- if ('outcome' in value && value.outcome !== undefined && value.outcome !== 'success' && value.outcome !== 'failure') {
76
- return false;
77
- }
78
-
79
- // Check value property if present
80
- if ('value' in value && typeof value.value !== 'number' && value.value !== undefined) {
81
- return false;
82
- }
83
-
84
- // Check metadata property if present
85
- if ('metadata' in value && value.metadata !== undefined) {
86
- if (!isRecord(value.metadata)) {
87
- return false;
88
- }
89
-
90
- // Validate metadata values
91
- const metadata = value.metadata;
92
- for (const key in metadata) {
93
- const val = metadata[key];
94
- if (typeof val !== 'string' && typeof val !== 'number' && typeof val !== 'boolean') {
95
- return false;
96
- }
97
- }
98
- }
99
-
100
- return true;
101
- }
102
-
103
- /**
104
- * Type guard to check if a value is a string
105
- * Useful for narrowing unknown types in safe contexts
106
- */
107
- export function isString(value: unknown): value is string {
108
- return typeof value === 'string';
109
- }
110
-
111
- /**
112
- * Type guard to check if a value is a number
113
- * Useful for narrowing unknown types in safe contexts
114
- */
115
- export function isNumber(value: unknown): value is number {
116
- return typeof value === 'number' && !Number.isNaN(value);
117
- }
118
-
119
- /**
120
- * Type guard to check if a value is a boolean
121
- * Useful for narrowing unknown types in safe contexts
122
- */
123
- export function isBoolean(value: unknown): value is boolean {
124
- return typeof value === 'boolean';
125
- }
126
-
127
- /**
128
- * Type guard to check if a value is a primitive type (string | number | boolean)
129
- * Useful for validating feature flag values and tag attributes
130
- */
131
- export function isPrimitive(value: unknown): value is string | number | boolean {
132
- return isString(value) || isNumber(value) || isBoolean(value);
133
- }
134
-
135
- /**
136
- * Type guard to check if a value is a plain object (not array, not null)
137
- */
138
- export function isPlainObject(value: unknown): value is Record<string, unknown> {
139
- return (
140
- typeof value === 'object' &&
141
- value !== null &&
142
- !Array.isArray(value) &&
143
- Object.getPrototypeOf(value) === Object.prototype
144
- );
145
- }
146
-
147
- /**
148
- * Type guard to check if a value is a valid record with primitive values
149
- * Useful for validating tag attribute objects
150
- */
151
- export function isRecordOfPrimitives(value: unknown): value is Record<string, string | number | boolean> {
152
- if (!isPlainObject(value)) {
153
- return false;
154
- }
155
-
156
- for (const key in value) {
157
- if (!isPrimitive(value[key])) {
158
- return false;
159
- }
160
- }
161
-
162
- return true;
163
- }
@@ -173,10 +173,5 @@ export const TraceContextProto: TraceContextBase = {
173
173
  * Type guard to check if a value is a TraceContext
174
174
  */
175
175
  export function isTraceContext(value: unknown): value is TraceContext<FeatureFlagSchema, Record<string, unknown>> {
176
- return (
177
- typeof value === 'object' &&
178
- value !== null &&
179
- TRACE_CONTEXT_MARKER in value &&
180
- (value as Record<symbol, unknown>)[TRACE_CONTEXT_MARKER] === true
181
- );
176
+ return typeof value === 'object' && value !== null && Reflect.get(value, TRACE_CONTEXT_MARKER) === true;
182
177
  }
@@ -0,0 +1,150 @@
1
+ /**
2
+ * CompositeTracer tests
3
+ *
4
+ * CompositeTracer fans one trace run out to several delegate tracers. The case
5
+ * that matters in practice — and the one the examples and the convert-to-Arrow
6
+ * guide document — is stacking StdioTracer (human-readable output) with
7
+ * ArrayQueueTracer (retains completed root buffers for Arrow conversion), so a
8
+ * single run both prints and exports.
9
+ */
10
+
11
+ // Configure Node.js timestamp implementation - MUST be first import
12
+ import '../../__tests__/test-helpers.js';
13
+
14
+ import { describe, expect, it } from 'bun:test';
15
+ import { Writable } from 'node:stream';
16
+ import { createTestTracerOptions } from '../../__tests__/test-helpers.js';
17
+ import { convertSpanTreeToArrowTable } from '../../convertToArrow.js';
18
+ import { defineLogSchema, defineOpContext, S } from '../../defineOpContext.js';
19
+ import { resolveMessage } from '../../resolveMessage.js';
20
+ import { ArrayQueueTracer } from '../ArrayQueueTracer.js';
21
+ import { CompositeTracer } from '../CompositeTracer.js';
22
+ import { StdioTracer, type StdioWritable } from '../StdioTracer.js';
23
+
24
+ type MockStream = { stream: StdioWritable; output: string[] };
25
+
26
+ function createMockStream(): MockStream {
27
+ const output: string[] = [];
28
+ const writable = new Writable({
29
+ write(chunk, _encoding, callback) {
30
+ output.push(chunk.toString());
31
+ callback();
32
+ },
33
+ });
34
+ const stream: StdioWritable = {
35
+ write(chunk: string): boolean {
36
+ return writable.write(chunk);
37
+ },
38
+ };
39
+ return { stream, output };
40
+ }
41
+
42
+ describe('CompositeTracer', () => {
43
+ const testSchema = defineLogSchema({
44
+ userId: S.category(),
45
+ });
46
+
47
+ const ctx = defineOpContext({
48
+ logSchema: testSchema,
49
+ });
50
+ const { defineOp } = ctx;
51
+
52
+ // `CompositeTracerOptions` carries `delegates: Tracer<B>[]`, which pins B to this
53
+ // op context — so the shared tracer options must be built for the same concrete
54
+ // log schema rather than the loose `LogSchema` default.
55
+ type TestLogSchema = (typeof ctx)['logBinding']['logSchema'];
56
+
57
+ /** Stdio + ArrayQueue behind one composite, sharing the same tracer options. */
58
+ function createStackedTracer() {
59
+ const { stream: out, output } = createMockStream();
60
+ const { stream: err } = createMockStream();
61
+ const options = createTestTracerOptions<TestLogSchema>();
62
+
63
+ const stdio = new StdioTracer(ctx, { ...options, out, err, colorEnabled: false });
64
+ const queued = new ArrayQueueTracer(ctx, { ...options });
65
+ const tracer = new CompositeTracer(ctx, { ...options, delegates: [stdio, queued] });
66
+
67
+ return { tracer, queued, output };
68
+ }
69
+
70
+ describe('stdio + ArrayQueue stacking', () => {
71
+ it('should print to stdout and retain the root buffer from one trace run', async () => {
72
+ const { tracer, queued, output } = createStackedTracer();
73
+
74
+ const testOp = defineOp('test', (ctx) => ctx.ok('done'));
75
+ await tracer.trace('stacked-trace', testOp);
76
+
77
+ // StdioTracer delegate printed the span tree...
78
+ expect(output.some((line) => line.includes('stacked-trace'))).toBe(true);
79
+
80
+ // ...and the ArrayQueueTracer delegate kept the completed root buffer.
81
+ expect(queued.queue).toHaveLength(1);
82
+ expect(resolveMessage(queued.queue[0], 0)).toBe('stacked-trace');
83
+ });
84
+
85
+ it('should convert the retained buffer to an Arrow table', async () => {
86
+ const { tracer, queued } = createStackedTracer();
87
+
88
+ const testOp = defineOp('test', (ctx) => ctx.ok('done'));
89
+ await tracer.trace('exported-trace', testOp);
90
+
91
+ const tables = queued.drain().map((rootBuffer) => convertSpanTreeToArrowTable(rootBuffer));
92
+
93
+ expect(tables).toHaveLength(1);
94
+ // span-start + span-ok
95
+ expect(tables[0]?.numRows).toBe(2);
96
+ expect(tables[0]?.names).toContain('entry_type');
97
+ expect(tables[0]?.names).toContain('userId');
98
+ });
99
+
100
+ it('should leave the queue empty after draining so the next batch is clean', async () => {
101
+ const { tracer, queued } = createStackedTracer();
102
+
103
+ const testOp = defineOp('test', (ctx) => ctx.ok('done'));
104
+ await tracer.trace('first', testOp);
105
+
106
+ expect(queued.drain()).toHaveLength(1);
107
+ expect(queued.queue).toHaveLength(0);
108
+
109
+ await tracer.trace('second', testOp);
110
+
111
+ const batch = queued.drain();
112
+ expect(batch).toHaveLength(1);
113
+ expect(resolveMessage(batch[0], 0)).toBe('second');
114
+ });
115
+
116
+ it('should capture child spans in both delegates', async () => {
117
+ const { tracer, queued, output } = createStackedTracer();
118
+
119
+ const childOp = defineOp('child', (ctx) => ctx.ok('child-done'));
120
+ const parentOp = defineOp('parent', async (ctx) => {
121
+ await ctx.span('child-span', childOp);
122
+ return ctx.ok('parent-done');
123
+ });
124
+
125
+ await tracer.trace('with-children', parentOp);
126
+
127
+ expect(output.some((line) => line.includes('child-span'))).toBe(true);
128
+
129
+ const table = convertSpanTreeToArrowTable(queued.drain()[0]);
130
+ // parent span-start/ok + child span-start/ok
131
+ expect(table.numRows).toBe(4);
132
+ });
133
+ });
134
+
135
+ describe('delegate fan-out', () => {
136
+ it('should keep delegates independent — draining one does not affect the other', async () => {
137
+ const { tracer, queued, output } = createStackedTracer();
138
+
139
+ const testOp = defineOp('test', (ctx) => ctx.ok('done'));
140
+ await tracer.trace('independent', testOp);
141
+
142
+ const printedBefore = output.length;
143
+ queued.drain();
144
+
145
+ // Draining the queue must not retroactively change what stdio already wrote.
146
+ expect(output).toHaveLength(printedBefore);
147
+ expect(output.some((line) => line.includes('independent'))).toBe(true);
148
+ });
149
+ });
150
+ });