@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 +206 -206
- package/dist/lib/opContext/spanContextTypes.d.ts +4 -4
- package/dist/lib/opContext/spanContextTypes.d.ts.map +1 -1
- package/dist/lib/schema/typeGuards.d.ts +7 -47
- package/dist/lib/schema/typeGuards.d.ts.map +1 -1
- package/dist/lib/schema/typeGuards.js +7 -123
- package/dist/lib/traceContext.d.ts.map +1 -1
- package/dist/lib/traceContext.js +1 -4
- package/package.json +1 -1
- package/src/lib/opContext/spanContextTypes.ts +24 -7
- package/src/lib/schema/typeGuards.ts +7 -151
- package/src/lib/traceContext.ts +1 -6
- package/src/lib/tracers/__tests__/CompositeTracer.test.ts +150 -0
package/README.md
CHANGED
|
@@ -1,269 +1,269 @@
|
|
|
1
|
-
# lmao
|
|
1
|
+
# @smoothbricks/lmao
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
A high-performance, type-safe structured tracing and observability library for TypeScript.
|
|
4
4
|
|
|
5
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
12
|
-
directly, you execute code that emits trace facts and assert on WHAT happened.
|
|
19
|
+
## Install
|
|
13
20
|
|
|
14
|
-
|
|
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
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
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
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
```
|
|
45
|
+
// 2. Bundle the schema into an op context.
|
|
46
|
+
const opContext = defineOpContext({ logSchema: schema });
|
|
47
|
+
const { defineOp } = opContext;
|
|
38
48
|
|
|
39
|
-
3.
|
|
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
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
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
|
-
|
|
51
|
-
|
|
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
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
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
|
-
|
|
67
|
-
|
|
73
|
+
## Core API
|
|
74
|
+
|
|
75
|
+
### Schema (`S` + `defineLogSchema`)
|
|
68
76
|
|
|
69
|
-
|
|
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
|
-
|
|
85
|
+
### `defineOpContext`
|
|
72
86
|
|
|
73
|
-
```
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
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
|
-
|
|
99
|
+
`defineOp(name, fn)` creates a single op; `defineOps({ ... })` batches ops into a reusable group.
|
|
81
100
|
|
|
82
|
-
|
|
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
|
-
|
|
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
|
-
|
|
97
|
-
|
|
98
|
-
|
|
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
|
-
|
|
118
|
+
### Results & typed errors
|
|
103
119
|
|
|
104
|
-
|
|
105
|
-
|
|
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
|
-
|
|
110
|
-
|
|
111
|
-
const ctx = useTestSpan();
|
|
123
|
+
```ts
|
|
124
|
+
import { defineCodeError } from '@smoothbricks/lmao';
|
|
112
125
|
|
|
113
|
-
|
|
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
|
-
|
|
120
|
-
|
|
121
|
-
|
|
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
|
-
|
|
127
|
-
|
|
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
|
-
|
|
162
|
+
## Feature flags
|
|
130
163
|
|
|
131
|
-
|
|
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
|
-
```
|
|
143
|
-
|
|
166
|
+
```ts
|
|
167
|
+
import { defineFeatureFlags, InMemoryFlagEvaluator, S } from '@smoothbricks/lmao';
|
|
144
168
|
|
|
145
|
-
|
|
169
|
+
const flags = defineFeatureFlags({
|
|
170
|
+
advancedValidation: S.boolean().default(false).sync(),
|
|
171
|
+
maxRetries: S.number().default(3).sync(),
|
|
172
|
+
});
|
|
146
173
|
|
|
147
|
-
|
|
174
|
+
const opContext = defineOpContext({ logSchema: schema, flags: flags.schema });
|
|
148
175
|
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
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
|
-
|
|
156
|
-
|
|
182
|
+
// Inside an op — sync flags are read as `ctx.ff.<name>?.value`:
|
|
183
|
+
if (ctx.ff.advancedValidation?.value) { /* ... */ }
|
|
157
184
|
```
|
|
158
185
|
|
|
159
|
-
|
|
186
|
+
## Persist & analyze
|
|
160
187
|
|
|
161
|
-
|
|
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
|
-
|
|
190
|
+
```ts
|
|
191
|
+
import { convertSpanTreeToArrowTable } from '@smoothbricks/lmao';
|
|
167
192
|
|
|
168
|
-
|
|
193
|
+
const table = convertSpanTreeToArrowTable(tracer.rootBuffers[0]);
|
|
194
|
+
console.log(table.numRows, table.names);
|
|
195
|
+
```
|
|
169
196
|
|
|
170
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
211
|
+
describe('order processing', () => {
|
|
212
|
+
it('validates and saves', async () => {
|
|
213
|
+
const ctx = useTestSpan();
|
|
181
214
|
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
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
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
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
|
-
|
|
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
|
-
|
|
235
|
+
## Package exports
|
|
242
236
|
|
|
243
|
-
|
|
244
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
258
|
-
import { findSpan, extractFactsFor, spanNames } from '@smoothbricks/lmao/testing';
|
|
253
|
+
## Examples
|
|
259
254
|
|
|
260
|
-
|
|
261
|
-
const facts = extractFactsFor(rootBuffer, 'save');
|
|
262
|
-
const names = spanNames(rootBuffer);
|
|
263
|
-
```
|
|
255
|
+
Runnable with `bun run examples/<name>.ts`:
|
|
264
256
|
|
|
265
|
-
|
|
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
|
-
|
|
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
|
|
30
|
-
*
|
|
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
|
|
35
|
-
*
|
|
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":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,OAAO,KAAK,EAAE,cAAc,IAAI,2BAA2B,EAAE,MAAM,mCAAmC,CAAC;AACvG,OAAO,KAAK,EAAE,GAAG,EAAE,EAAE,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AACpD,OAAO,KAAK,EAAE,oBAAoB,EAAE,4BAA4B,EAAE,MAAM,wBAAwB,CAAC;AACjG,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,wBAAwB,CAAC;AACxD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AACtD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAC9C,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,cAAc,CAAC;AACvC,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;
|
|
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
|
-
*
|
|
2
|
+
* Schema type-guard re-exports.
|
|
3
3
|
*
|
|
4
|
-
* Re-exports schema
|
|
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
|
|
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
|
-
*
|
|
2
|
+
* Schema type-guard re-exports.
|
|
3
3
|
*
|
|
4
|
-
* Re-exports schema
|
|
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":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAC;AACtD,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAC1C,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,gCAAgC,CAAC;AACxE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAC3D,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAC;AACnD,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAM5C;;;;GAIG;AACH,MAAM,MAAM,wBAAwB,GAAG,SAAS,GAAG,mBAAmB,GAAG,eAAe,GAAG,UAAU,GAAG,IAAI,GAAG,MAAM,CAAC;AAMtH;;;;;GAKG;AACH,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,SAAS,CAAC;AAElC,YAAY,EAAE,EAAE,EAAE,MAAM,SAAS,CAAC;AAMlC;;;;;;;GAOG;AACH,MAAM,MAAM,UAAU,GAAG;IAEvB,CAAC,GAAG,SAAS,SAAS,EAAE,IAAI,SAAS,OAAO,EAAE,EAAE,CAAC,EAAE,CAAC,EAClD,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,EACZ,EAAE,EAAE,EAAE,CAAC,GAAG,EAAE,IAAI,EAAE,CAAC,EAAE,CAAC,CAAC,EACvB,GAAG,IAAI,EAAE,IAAI,GACZ,OAAO,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;IAEzB,CAAC,GAAG,SAAS,SAAS,EAAE,IAAI,SAAS,OAAO,EAAE,EAAE,CAAC,EAAE,CAAC,EAClD,IAAI,EAAE,MAAM,EACZ,EAAE,EAAE,EAAE,CAAC,GAAG,EAAE,IAAI,EAAE,CAAC,EAAE,CAAC,CAAC,EACvB,GAAG,IAAI,EAAE,IAAI,GACZ,OAAO,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;CAC1B,CAAC;AAMF;;;;;;;;;GASG;AACH,MAAM,WAAW,kBAAkB,CACjC,EAAE,SAAS,iBAAiB,EAC5B,CAAC,SAAS,SAAS,GAAG,SAAS,EAC/B,GAAG,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;IAG7D,QAAQ,CAAC,iBAAiB,EAAE,MAAM,CAAC;IACnC,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B,gFAAgF;IAChF,QAAQ,CAAC,EAAE,EAAE,aAAa,CAAC,SAAS,CAAC,CAAC,EAAE,EAAE,EAAE,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC;IACzE,QAAQ,CAAC,IAAI,EAAE,UAAU,CAAC;CAC3B;AAED;;;;;;;;;;;;GAYG;AACH,MAAM,MAAM,YAAY,CAAC,EAAE,SAAS,iBAAiB,EAAE,KAAK,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,IAAI,kBAAkB,CAAC,EAAE,CAAC,GACpH,KAAK,CAAC;AAMR;;;GAGG;AACH,eAAO,MAAM,oBAAoB,eAAkC,CAAC;AAEpE;;;;;;;;;GASG;AACH,MAAM,WAAW,gBAAgB;IAC/B,2CAA2C;IAC3C,QAAQ,CAAC,CAAC,oBAAoB,CAAC,EAAE,IAAI,CAAC;IACtC,gCAAgC;IAChC,QAAQ,EAAE,OAAO,GAAG,SAAS,CAAC;IAC9B,4CAA4C;IAC5C,iBAAiB,EAAE,MAAM,GAAG,SAAS,CAAC;IACtC,2CAA2C;IAC3C,aAAa,EAAE,MAAM,GAAG,SAAS,CAAC;IAClC,6BAA6B;IAC7B,EAAE,EAAE,OAAO,CAAC;IACZ,yBAAyB;IACzB,IAAI,EAAE,OAAO,CAAC;IACd,kFAAkF;IAClF,UAAU,CAAC,EAAE,MAAM,EAAE,CAAC;CACvB;AAED;;;;;;;GAOG;AACH,eAAO,MAAM,iBAAiB,EAAE,gBAQ/B,CAAC;AAMF;;GAEG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,YAAY,CAAC,iBAAiB,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAEhH"}
|
package/dist/lib/traceContext.js
CHANGED
|
@@ -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
|
|
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
|
@@ -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 (
|
|
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
|
|
36
|
-
*
|
|
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:
|
|
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
|
|
43
|
-
*
|
|
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:
|
|
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
|
-
*
|
|
2
|
+
* Schema type-guard re-exports.
|
|
3
3
|
*
|
|
4
|
-
* Re-exports schema
|
|
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
|
-
}
|
package/src/lib/traceContext.ts
CHANGED
|
@@ -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
|
+
});
|