API wrapper infrastructure for
@study-lensestracer packages. ValidatesTracerModuleobjects and provides four pre-bound API wrappers:trace,tracify,embody,embodify.
Neutral infrastructure. This package makes no pedagogical decisions — those belong in tracer implementations and the tools that consume them.
The same infrastructure pattern appears across technology stacks:
@study-lenses/tracing provides execution-tracing API infrastructure →
@study-lenses/trace-* packages build language-specific tracing logic →
educational tools build learning experiences on topPedagogical approaches vary: constructivist vs. instructivist, individual vs. collaborative, formative vs. summative, visual vs. textual. Staying neutral at this layer enables multiple approaches to coexist. Domain experts (CS educators, learning scientists) can specialize their tools without reinventing the tracing infrastructure.
Config structure mirrors trace output structure. The limits you configure correspond to the events you observe:
Config (meta.max.*) |
What it limits | Trace output |
|---|---|---|
steps |
Execution steps | step field per StepCore |
iterations |
Loop iterations | Tracer-specific step fields |
callstack |
Stack depth | Tracer-specific step fields |
time |
Execution time (ms) | timestamps option |
Tracer implementations should log what a professional debugger shows — explicit operations
that a learner would see and reason about. Implicit operations (e.g., implicit toString
coercions, internal VM bookkeeping) are not logged. This keeps traces meaningful.
Primary — Tracer package developers building @study-lenses/trace-* packages:
This package gives you the four standard API wrappers so you only write the record()
function. Everything else is handled.
Secondary — Educational tool developers consuming tracer packages:
Tertiary — CER researchers using tracer packages for data collection or measurement.
npm install @study-lenses/tracing
// In a tracer package (typical use)
import tracing from '@study-lenses/tracing';
import tracerModule from './tracer-module.js';
// Validates tracerModule once upfront; returns 4 pre-bound wrappers
const { trace, tracify, embody, embodify } = tracing(tracerModule);
// trace: positional args, throws on error
const steps = await trace(code, config);
// embody: chainable, throws on error
const steps = await embody.code(code).config(config).steps;
// tracify: keyed args, returns { ok, steps } or { ok: false, error }
const result = await tracify({ code, config });
if (result.ok) console.log(result.steps);
// embodify: keyed + chainable, .trace() is async
const chain = await embodify({ code }).set({ config }).trace();
if (chain.ok) console.log(chain.steps);
| Simple | Chainable | |
|---|---|---|
| Throws | trace |
embody |
| Safe | tracify |
embodify |
Throws = errors propagate as exceptions. Use in scripts and REPLs.
Safe = errors returned as { ok: false, error }. Use in production UIs.
Simple = all args at once. Chainable = build state across multiple calls.
Full API reference: run npm run docs locally, or see the hosted API docs.
TracerModule ContractEvery tracer package exports one object matching this contract:
| Field | Type | Required | Purpose |
|---|---|---|---|
id |
string |
Yes | Unique tracer ID, e.g. 'js:klve' — used for cache invalidation |
langs |
readonly string[] |
Yes | Supported file extensions; [] = universal tracer |
record |
RecordFunction |
Yes | The tracing function |
optionsSchema |
JSON Schema object | No | Tracer-specific options schema |
verifyOptions |
(opts) => void |
No | Cross-field semantic validation |
import type { TracerModule } from '@study-lenses/tracing';
const tracerModule: TracerModule = {
id: 'js:klve',
langs: Object.freeze(['js', 'mjs', 'cjs']),
record: async (code, { meta, options }) => {
// ... language-specific tracing logic
return steps;
},
optionsSchema: {
/* JSON Schema */
},
verifyOptions: (options) => {
/* cross-field checks */
},
};
export default tracerModule;
langs: [] = universal tracer — accepts any language. The chainable APIs use langs to
decide whether to keep or clear code when switching tracers.
We provide:
TracerModule validation (TracerInvalidError with aggregate violations)instanceof discriminationTracerModule, RecordFunction, StepCore, MetaConfig, ResolvedConfigYour tracer does:
options.schema.json defining tracer-specific optionsverifyOptions for cross-field constraintsYour tool does:
The wrapper pipeline has three phases: VALIDATION (synchronous) → EXECUTION (asynchronous) → POST-PROCESSING (synchronous). The diagram shows trace — the other wrappers (tracify, embody, embodify) follow the same pipeline with different access patterns and error handling.
flowchart TB
consumer1["CONSUMER PROGRAM
(educational tool)
calls trace(tracer, code, config?)"]
consumer1 -- "code + config" --> validation
subgraph wrapper ["@study-lenses/tracing — API WRAPPER"]
direction TB
validation["VALIDATE CONFIG · sync
expand shorthand, fill defaults,
schema + semantic validation"]
execution["EXECUTE TRACER · async
call record() with code +
fully resolved frozen config"]
postprocessing["VALIDATE + FREEZE STEPS · sync
check StepCore conformity,
deep-freeze for consumer"]
validation --> execution --> postprocessing
end
postprocessing -- "frozen steps" --> consumer2
consumer2["CONSUMER PROGRAM
(receives frozen steps)"]
subgraph tracermod ["YOUR TRACER MODULE ★ = you implement this"]
direction TB
fields["★ id · unique identifier
★ langs · supported extensions
★ optionsSchema · JSON Schema (optional)
★ verifyOptions · semantic checks (optional)
★ record · instruments + executes code"]
end
tracermod -. "schema + verify" .-> validation
tracermod -. "record()" .-> execution
style wrapper fill:none,stroke:#333,stroke-width:3px
style tracermod fill:#fff8e1,stroke:#f9a825,stroke-width:2px
style consumer1 fill:#e3f2fd,stroke:#1565c0
style consumer2 fill:#e3f2fd,stroke:#1565c0
Layer stack (bottom → top, each layer imports only from below):
src/utils/ ← deep-clone, deep-freeze, deep-freeze-in-place, deep-merge, deep-equal
src/errors/ ← TracingError base + 9 specific error classes
src/configuring/ ← pure config pipeline (schema-agnostic, tracer-agnostic)
src/api/ ← trace, tracify, embody, embodify + validate-steps, validate-tracer-module
src/tracing.ts ← tracing() sugar (validates TracerModule, returns pre-bound wrappers)
src/index.ts ← entry point (re-exports everything public)
See DEV.md for full architecture, conventions, and TDD workflow. See src/DOCS.md for cross-cutting architectural decisions.
See CONTRIBUTING.md and DEV.md.
MIT © 2026 CodeSchool in a Box