Use this page when you know an API name and want its contract. Each method has a permanent heading link, a readable signature, inputs, return value, and usage. Start with Flows from scratch if flows are new to you.
These are working-tree docs. Replay, fixture comparison, and display masking are marked unreleased relative to npm 0.4.1. Check the changelog and your installed version. Signatures below use Expression<T> as shorthand for FlowExpression<T> and Adapter for ReturnType<typeof createJevDevtools>; these are reading aids, not additional exports. Readable signatures omit some generic detail; expand the exact declarations below when you need it. SDKClientOptions means the TypeSafe SDK constructor options, and JoinedReferences means an object of lane-name references. These labels are not package exports. SDK request/client types belong to @typesafe-ai/sdk.
Find an API#
| Area | APIs |
|---|---|
| React | JevDevtools, styles.css |
| Server | createJevDevtools, getJevDevtools, createClient, handle, handleNodeRoute, middleware, run, dispose, startFlow |
| Flow | defineJevFlow, run, definition, input, task, jev, answer / answers, if, branch, parallel, map, loop, output |
| Expressions | FlowExpression, get, eq, lt, gt, gte, all, any, not, FlowLoopLimitError |
| Fixtures | flowFixture, replayClient, compareWithFixture, formatComparison |
| Recorder | JevRecorder, transport, instrument, snapshot, changes, subscribe, clear, withFlow |
| Observer hooks | startFlow, update, request |
| Data contracts | Public types |
Code fragments use inspector for an adapter, recorder for a recorder, flow for a builder, and program for a defined flow. Fragments explain individual calls; the linked guides supply complete setup. Every builder method accepts optional final display: FlowDisplay metadata. See flow limits for shared ID, nesting, and execution limits.
React#
JevDevtools#
JevDevtools(props: JevDevtoolsProps): ReactElement | nullImport: jev-devtools. Render once in your React app. It returns nothing during SSR or while disabled; once enabled and mounted it loads the connected drawer.
| Input prop | Default | Meaning |
|---|---|---|
enabled?: boolean |
false |
Opt in to connecting and showing the inspector. |
endpoint?: string |
/api/jev-devtools |
Same-origin inspector path; match the server. |
defaultOpen?: boolean |
true |
Open the drawer on its initial mount. |
redactAdditionalSensitiveData?: boolean |
true |
Unreleased. Mask supported personal fields in the display. Captured/streamed values remain unchanged. |
Unmounting or disabling closes its subscription. Closing the drawer alone does not stop capture. React setup explains mounting and styling.
Return interface
| Value / member | Type | Description |
|---|---|---|
| Return | React element or null |
Nothing during SSR/disabled state; the mounted inspector when enabled. |
import { JevDevtools } from "jev-devtools";
<JevDevtools enabled={import.meta.env.DEV} />;Mount the inspector once near your application root. It connects after mounting when enabled; it renders nothing during SSR.
<JevDevtools enabled defaultOpen={false} />- enabled
- Load and connect the browser inspector. Defaults to false; enable the server adapter separately.
- defaultOpen
- Whether the drawer starts expanded. Defaults to true; false starts at the launcher.
- endpoint
- Same-origin inspector path. Defaults to /api/jev-devtools; must match the server adapter.
- redactAdditionalSensitiveData
- Mask supported personal fields in the display. Does not remove captured data. Unreleased; defaults to true.
Load and connect the browser inspector. Defaults to false; enable the server adapter separately.
enabledAPI reference: JevDevtools
Example walkthrough: The component enables the inspector only in Vite development builds through import.meta.env.DEV. Mounting it once gives the application one drawer and one connection.
styles.css#
import "jev-devtools/styles.css" loads the public stylesheet export. The built-in drawer includes its styles automatically, so ordinary installations do not need this import. This export is a stylesheet, not a function.
Server adapter#
Import factories from jev-devtools/server. All server adapter and recorder runtime code belongs on Node.
createJevDevtools#
createJevDevtools(options: JevDevtoolsServerOptions): AdapterCreates an independent adapter and its in-memory histories. Returns: an object with all the adapter methods below.
| Input | Default | Meaning |
|---|---|---|
origin: string |
Required | Exact browser origin, including port. |
enabled?: boolean |
false |
Enable recording and HTTP endpoints. |
endpoint?: string |
/api/jev-devtools |
Absolute path without query or fragment. |
limit?: number |
50 |
Global trace retention; clamped to 1–200. |
redact?: (path: string[], value: unknown) => unknown |
None | Transform the captured copy before storage/delivery. |
| `authorize?: (request: Request) => boolean | Promise |
None |
| `getSession?: (request: Request) => string | Promise |
"local" |
Invalid origin/path configuration throws; enabled non-loopback origins without authorization throw. Share this instance between inference and inspector routes. Access guide.
Return interface
| Value / member | Type | Description |
|---|---|---|
| Return | Adapter |
Independent adapter with client, routing, session, observer, and disposal methods. |
import { createJevDevtools } from "jev-devtools/server";
const inspector = createJevDevtools({ enabled: true, origin: "http://localhost:3000" });Create an independent Node server adapter. Share it between your HTTP endpoints and instrumented clients.
createJevDevtools(options)- enabled
- Opt into server capture and inspector endpoints. Defaults to false; disabling capture does not stop inference.
- origin
- The exact browser origin, including scheme, hostname, and port.
- endpoint
- The inspector URL path. Defaults to /api/jev-devtools; must match the browser component.
- limit
- Maximum retained request traces across sessions. Defaults to 50; clamped to 1–200.
- redact
- Transform the captured copy before storage or streaming. Does not change SDK inputs or application values.
- authorize
- Your application access check. Required for an enabled non-loopback origin.
- getSession
- Derive a trusted recording scope from the request. Defaults to the shared local scope.
Opt into server capture and inspector endpoints. Defaults to false; disabling capture does not stop inference.
enabledThe exact browser origin, including scheme, hostname, and port.
originAPI reference: createJevDevtools()
Example walkthrough: The example enables capture and binds inspector access to the browser origin http://localhost:3000. Keep this adapter instance shared between the HTTP routes and instrumented clients.
getJevDevtools#
getJevDevtools(key: string, createOptions: () => JevDevtoolsServerOptions): AdapterInput interface
| Parameter / option | Type |
|---|---|
key |
string |
createOptions |
() => JevDevtoolsServerOptions |
Inputs: a nonblank app-specific key and synchronous options factory. Returns: the same adapter for that key in the same Node realm; calls the factory only when creating it. The first options win until disposal or process restart. Empty keys throw. Histories are not shared across processes. Use this for module reloads in Next.js.
Return interface
| Value / member | Type | Description |
|---|---|---|
| Return | Adapter |
Existing adapter for the key, or a newly created adapter. |
import { getJevDevtools } from "jev-devtools/server";
const inspector = getJevDevtools("my-app", () => ({
enabled: true, origin: "http://localhost:3000",
}));Reuse an adapter for a stable key in the same Node realm. The options factory runs once per key; restart after configuration changes. Different processes do not share history.
getJevDevtools(key, createOptions)- key
- A stable, nonempty application key.
- createOptions
- Return the server options, including enabled and origin.
API reference: getJevDevtools() · createJevDevtools()
Example walkthrough: The stable "my-app" key reuses the adapter during module reloads. The callback supplies configuration only when an adapter for that key does not already exist.
adapter.createClient#
inspector.createClient(options: SDKClientOptions, metadata?: ClientMetadata): TypeSafeClientInput interface
| Parameter / option | Type |
|---|---|
options |
SDKClientOptions |
metadata? |
ClientMetadata |
Inputs: TypeSafe client constructor options (including your server-side key or custom fetch); optional { label?, source? } defaults to "Jev request" and "live". Returns: a client that records systemOne calls when enabled, otherwise an ordinary SDK client. Other SDK methods are not instrumented. Setting source: "sample" changes a label, not the transport or billing behavior.
Return interface
| Value / member | Type | Description |
|---|---|---|
| Return | TypeSafeClient |
SDK client; instrumented when the adapter is enabled. |
const client = inspector.createClient(
{ apiKey: process.env.TYPESAFE_API_KEY },
{ label: "Support", source: "live" },
);Create an instrumented TypeSafe client. Captures systemOne calls when recording is enabled; otherwise returns an ordinary SDK client. Keep credentials on the server.
adapter.createClient(options, metadata?)- label
- Optional capture label. Defaults to Jev request.
- source
- Capture label: sample or live. Defaults to live. This does not switch transports or prevent inference.
Optional capture label. Defaults to Jev request.
labelCapture label: sample or live. Defaults to live. This does not switch transports or prevent inference.
sourceAPI reference: adapter.createClient()
Example walkthrough: The first argument configures the SDK with a server-side key. The second adds a human-readable capture label and marks the capture as live; it does not change the SDK request behavior.
adapter.handle#
inspector.handle(request: Request): Promise<Response>Input interface
| Parameter / option | Type |
|---|---|
request |
Request |
Input: a Web Request with the actual public URL. Returns: configuration JSON, an event stream, a clear response, or an HTTP error. Forward the base path and all subpaths. Disabled/unmatched routes return 404; origin/auth failures return 403. Authorizer/session callback errors can reject, so let your framework handle errors. A Web Request provides no socket address: bind to loopback or configure authorization.
Return interface
| Value / member | Type | Description |
|---|---|---|
| Return | Promise<Response> |
Resolves to an HTTP response; includes HTTP error responses. |
const response = await inspector.handle(request);Handle a Web Request with its actual public URL and return a Web Response. Applies the adapter's access checks.
adapter.handle(request)API reference: adapter.handle()
Example walkthrough: The application passes its incoming Web Request to the adapter and awaits a Web Response. Return that response from your framework’s inspector route.
adapter.handleNodeRoute#
inspector.handleNodeRoute(request: Request): Promise<Response>Input interface
| Parameter / option | Type |
|---|---|
request |
Request |
Input: a Node framework's Web Request, including its actual Host header. Returns: the same response contract as handle, after checking Host against the configured authority and normalizing internal URLs. Forwarded host headers are not trusted. See the Next.js route example.
Return interface
| Value / member | Type | Description |
|---|---|---|
| Return | Promise<Response> |
Response after Host validation and URL normalization. |
export const GET = (request: Request) => inspector.handleNodeRoute(request);
export const DELETE = GET;Check Host, normalize a Node framework's internal request URL, and apply the adapter's access checks. Route the base endpoint and all subpaths here.
adapter.handleNodeRoute(request)API reference: adapter.handleNodeRoute()
Example walkthrough: The route exposes the same handler for GET and DELETE. The adapter checks Host before serving configuration, event streams, or a clear request.
adapter.middleware#
inspector.middleware(req: IncomingMessage, res: ServerResponse, next: () => void): Promise<void>Input interface
| Parameter / option | Type |
|---|---|
req |
IncomingMessage |
res |
ServerResponse |
next |
() => void |
Inputs: Node HTTP request/response and continuation. Returns: a promise completing response handling, or calls next() for unrelated paths. Mount at the root without stripping the inspector prefix. Without authorize, enabled endpoints require a loopback socket. Route errors are contained as HTTP 500 or a destroyed response.
Return interface
| Value / member | Type | Description |
|---|---|---|
| Return | Promise<void> |
Completes after response handling or calls next for an unrelated path. |
app.use(inspector.middleware); // app is your Express/Connect serverMount at the application root to serve inspector endpoints and their subpaths. Unrelated routes call next().
app.use(adapter.middleware)API reference: adapter.middleware
Example walkthrough: Mounting the middleware on app preserves the full inspector path. Requests outside that path continue through next() to the application’s other handlers.
adapter.run#
inspector.run<T>(request: Request, callback: () => T | Promise<T>): Promise<T>Input interface
| Parameter / option | Type |
|---|---|
request |
Request |
callback |
() => T | Promise<T> |
Inputs: the application's authenticated request and work callback. Returns: the callback's resolved result; failures propagate. Chooses the recording scope through getSession for the callback's asynchronous work. It does not authorize the application action. Needed when using custom sessions.
Return interface
| Value / member | Type | Description |
|---|---|---|
| Return | Promise<T> |
Resolves to the application callback’s result; callback errors propagate. |
return inspector.run(request, () => client.systemOne(input));Run application code in the request's server-derived recording scope. Needed when getSession is customized. Authenticate the application action separately.
adapter.run(request, callback)API reference: adapter.run()
Example walkthrough: The request determines the recording session. The callback performs the SDK call inside that session, and its result becomes the resolved value of inspector.run().
adapter.dispose#
inspector.dispose(): voidInput interface: no parameters.
Takes no arguments. Disables capture/endpoints and closes active streams. Shared adapters also release their key so a later getJevDevtools can create a new instance. Returns: nothing. Call on shutdown or deliberate reinitialization, not per request.
Return interface
| Value / member | Type | Description |
|---|---|---|
| Return | void |
No data is returned. |
inspector.dispose();Disable the adapter and close active streams. A shared adapter also releases its key. Use at shutdown, not after each request.
adapter.dispose()API reference: adapter.dispose()
Example walkthrough: This shutdown call disables recording and closes streams owned by the adapter. A shared adapter also releases its registry key.
Flow definitions and steps#
Import defineJevFlow from jev-devtools/flow on Node. Builder methods describe work synchronously; they do not execute it. See Flows from scratch for a complete, small example of every operation.
defineJevFlow#
defineJevFlow<Input>(name: string, build: (flow: FlowBuilder<Input>) => Expression<FlowOutcome>): { definition: FlowDefinition; run: /* see below */ }Input interface
| Parameter / option | Type |
|---|---|
name |
string |
build |
(flow: FlowBuilder<Input>) => FlowExpression<FlowOutcome> |
Inputs: a nonblank flow name of at most 100 characters, and a synchronous builder callback. Return the terminal output or branch. Returns: { definition, run }, a reusable recipe. No SDK request is sent during definition. Invalid IDs, scopes, nesting, or terminal structure throw immediately.
Return interface
| Value / member | Type | Description |
|---|---|---|
| definition | FlowDefinition |
Static graph. |
| run | (input: Input, options: FlowRunOptions) => Promise<FlowOutcome & { runId: string }> |
Executes a fresh run. |
import { defineJevFlow } from "jev-devtools/flow";
const program = defineJevFlow<{ name: string }>("hello", (flow) =>
flow.output("greeted", flow.input.get("name")),
);Describe a flow once, then run it with different inputs. Defining it does not send requests.
defineJevFlow<Input>(name, build)- name
- The flow name shown in DevTools.
- build
- Receives a builder. Return an output or a terminal branch.
Finish this path with a named outcome and, optionally, a value for your application.
flow.output(name, value?, display?)- name
- The outcome string returned by run().
- value?
- Optional result data. Narrow its type in your application before using it.
- display?
- Optional labels and display metadata for DevTools.
A reference to the data passed to run(). Inside map/loop programs, it refers to the item or iteration input instead.
flow.input: FlowExpression<Input>Select a field from a referenced value. Keys come from your data shape. A missing field is an execution error.
expression.get(key)API reference: defineJevFlow() · flow.output() · flow.input · expression.get()
Example walkthrough: The input type requires a name. get("name") selects it without reading a runtime value yet; output("greeted", ...) describes the terminal result. Calling program.run() later performs the execution.
flow.run#
program.run(input: Input, options: FlowRunOptions): Promise<{ runId: string; outcome: string; value?: unknown }>Inputs: the typed input promised by defineJevFlow and these options:
| Option | Meaning |
|---|---|
client |
Required client with SDK-compatible systemOne, even for task-only flows. |
devtools? |
Optional FlowObserver, usually your server adapter. |
signal? |
Optional AbortSignal to cancel the run. |
Returns: a fresh run ID, selected outcome name, and optional resolved value. value is unknown, not an inferred discriminated union. Errors and cancellation reject the promise. Tasks must cooperate with their signal; there is no rollback or automatic retry.
Return interface
| Value / member | Type | Description |
|---|---|---|
| Return | Promise<FlowOutcome & { runId: string }> |
Await to receive the fields below. |
| runId | string |
Unique execution ID. |
| outcome | string |
Selected terminal outcome name. |
| value? | unknown |
Resolved output value; narrow its type in application code. |
const result = await program.run({ name: "Ada" }, { client, devtools: inspector });
console.log(result.outcome, result.value);Example walkthrough: The input object supplies Ada’s name. client enables SDK steps and devtools: inspector enables recording. Awaiting the promise yields the chosen outcome and resolved output value.
flow.definition#
program.definition: FlowDefinitionInput interface: no parameters.
A description of the recipe, to treat as read-only: version, ID, name, and nodes. Usage: inspect it or pair it with a captured FlowRun when building a fixture. Reading it does not execute a flow. Do not mutate the definition to change running behavior.
Return interface
| Value / member | Type | Description |
|---|---|---|
| Property value | FlowDefinition |
Version, identity, name, and graph nodes. No work is executed. |
console.log(program.definition.name, program.definition.nodes.length);Example walkthrough: Reading name and the node count inspects the recipe’s static structure. It does not execute tasks or send requests.
flow.input#
flow.input: FlowExpression<Input>Input interface: no parameters.
A reference to the current recipe input, not its actual runtime value. Returns/shape: Input in ordinary flow/path/lane builders; { item, index, context } inside map; { state, index, context } inside loop. Select a field with get, or pass the whole reference into a task/request/output.
Return interface
| Value / member | Type | Description |
|---|---|---|
| Property value | FlowExpression<Input> |
Reference to the current flow, item, or iteration input. |
const name = flow.input.get("name");API reference: flow.input · expression.get()
Example walkthrough: The get("name") call selects a typed field reference. Pass that reference to a task or output to resolve it during execution.
flow.task#
flow.task<I, O>(id: string, task: FlowTask<I, O>, display?: FlowDisplay): Expression<Awaited<O>>Input interface
| Parameter / option | Type |
|---|---|
id |
string |
task.input |
I |
task.run |
(input: Resolved<I>, context: TaskContext) => O | Promise<O> |
task.timeoutMs? |
number |
display? |
FlowDisplay |
Inputs: unique step ID; input containing values/references; run(resolvedInput, context) returning a value or promise; optional timeoutMs integer from 1 to 2,147,483,647. Context has signal, runId, stepId, executionId. Returns: a reference to the awaited callback result. Failure rejects the flow. Timeouts cancel the wait and signal work; they cannot forcibly stop non-cooperating side effects. Beginner example.
Return interface
| Value / member | Type | Description |
|---|---|---|
| Builder return | FlowExpression<Awaited<O>> |
Reference to the callback result. |
| Resolved value | Awaited<O> |
Actual callback result after awaiting it, if needed. |
const greeting = flow.task("greet", {
input: flow.input.get("name"),
run: (name) => `Hello, ${name}!`,
});Resolve input references, run application code, and return a reference to its result. Tasks do not retry automatically.
flow.task(id, { input, run, timeoutMs? }, display?)- input
- Values and nested flow references resolved before run is called.
- run
- Your callback receives the resolved input and task context, including a cancellation signal.
- timeoutMs?
- Optional time limit. The callback must cooperate with cancellation; a timeout cannot undo side effects.
Values and nested flow references resolved before run is called.
inputYour callback receives the resolved input and task context, including a cancellation signal.
runAPI reference: flow.task() · flow.input · expression.get()
Example walkthrough: The task resolves the input name before invoking the callback. The template literal builds a greeting, and greeting holds a reference to that string for subsequent steps.
flow.jev#
flow.jev(id, request, display?): AnswerRefs
flow.jev(id, { input, request: (resolvedInput) => sdkRequest }, display?): AnswerRefsInput interface
| Parameter / option | Type |
|---|---|
id |
string |
request |
SDK request with expression-capable state, or FlowRequestFactory<I, Q> |
display? |
FlowDisplay |
Inputs: unique ID and either a normal SDK request whose state may contain nested expressions, or a factory configuration. The factory's input is resolved first, then its synchronous request callback returns an SDK request. Use tasks for async preparation. The request's questions define named SDK question types; other SDK request options are passed through.
Returns: .answer(name) field references and .answers for all answers, typed from the questions. Runtime factories may generate question names; use .answers for dynamic keys. SDK failures reject the run. A request factory returning a promise is invalid. Both forms with complete examples.
Return interface
| Value / member | Type | Description |
|---|---|---|
| answer(name) | Object of typed answer-field expressions |
Selects one question’s fields, such as choice or noul. |
| answers | FlowExpression<{ [K in keyof Q]: ResultFor<Q[K]> }> |
Reference to all answers. |
| Resolved answer fields | SDK ResultFor<Q[K]> fields |
Actual field values for the selected SDK question type. |
const result = flow.jev("classify", {
state: flow.input,
questions: { team: choice("Which team?", { billing: null, support: null }) },
}); // choice is imported from @typesafe-ai/sdkAdd a Jev request step. It returns references to answers you can use in later steps.
flow.jev(id, { state, questions }, display?)- id
- A unique step ID within this flow.
- state
- Context sent to Jev for this request. Flow references are resolved when the step runs.
- questions
- Named questions to ask. Their keys identify the returned answers.
- display?
- Optional labels and display metadata for DevTools.
Context sent to Jev for this request. Flow references are resolved when the step runs.
stateNamed questions to ask. Their keys identify the returned answers.
questionsAPI reference: flow.jev() · flow.input
Example walkthrough: The SDK’s choice() helper defines two allowed team labels. The flow supplies its input as request context and exposes the answer through result for later steps.
request.answer#
result.answer<Name extends keyof Questions>(name: Name): AnswerFieldReferencesInput interface
| Parameter / option | Type |
|---|---|
name |
A key of the request’s Questions type |
Input: a known question name. Returns: an object whose answer fields are typed expressions: e.g. .choice for Choice, .noul for Noul, .score for Score, or .box for Box; other SDK result fields are available according to the question type. Calling answer does not send another request. Static unknown questions fail during definition; generated questions must exist when executed. A Noul is a probability of true, not a confidence score.
Return interface
| Value / member | Type | Description |
|---|---|---|
| Return | Object of FlowExpression fields |
Each result field is an expression with its SDK-defined value type. |
const team = result.answer("team").choice; // expression for a Choice resultExample walkthrough: answer("team") selects the named question and .choice selects its chosen label. The assignment stores a reference, not an immediate model response.
request.answers#
result.answers: Expression<{ [Name in keyof Questions]: ResultFor<Questions[Name]> }>Input interface: no parameters.
The full resolved SDK answer object, useful when a factory generates question names at runtime. Usage: pass it to a task or output. It is still a future-value reference during definition.
Return interface
| Value / member | Type | Description |
|---|---|---|
| Property value | FlowExpression<{ [K in keyof Q]: ResultFor<Q[K]> }> |
Reference to the complete answer object. |
return flow.output("answers", result.answers);API reference: flow.output()
Example walkthrough: Passing the full answer reference to output() returns every answer and its SDK metadata in the flow value, rather than extracting one field.
flow.if#
flow.if<A, B>(id, { when, then, else }, display?): Expression<A | B>Input interface
| Parameter / option | Type |
|---|---|
id |
string |
paths.when |
FlowExpression<boolean> |
paths.then |
(path: FlowBuilder<Input>) => FlowExpression<A> |
paths.else |
(path: FlowBuilder<Input>) => FlowExpression<B> |
display? |
FlowDisplay |
Inputs: a Boolean expression when; then(path) and else(path) callbacks each returning a value reference. Returns: the chosen value reference so later shared steps can continue. Both callbacks build synchronously, only the selected path executes. Use the provided path builder; do not return terminal outputs here. Beginner example.
Return interface
| Value / member | Type | Description |
|---|---|---|
| Builder return | FlowExpression<A | B> |
Reference to the selected path’s result. |
| Resolved value | A | B |
Value from the executed path, available to later steps. |
const days = flow.if("speed", {
when: flow.input.get("express").eq(true),
then: (path) => path.task("fast", { input: 1, run: (n) => n }),
else: (path) => path.task("regular", { input: 5, run: (n) => n }),
});Choose one value-returning path, then continue with its result. Unlike branch(), this does not finish the flow.
flow.if(id, { when, then, else }, display?)- when
- A flow condition evaluated during the run.
- then
- Build the path selected when the condition is true. Return an expression for its value.
- else
- Build the path selected when the condition is false. Return an expression for its value.
A flow condition evaluated during the run.
whenBuild an equality condition. It compares matching scalar values when the flow runs; it does not compare values during definition.
expression.eq(value)Build the path selected when the condition is true. Return an expression for its value.
thenBuild the path selected when the condition is false. Return an expression for its value.
elseAPI reference: flow.if() · flow.input · expression.get() · expression.eq() · flow.task()
Example walkthrough: The condition compares the express flag with true. The selected task returns either 1 or 5; days makes that value available to shared steps after the decision.
flow.branch#
flow.branch(id, { when, then, else }, display?): Expression<FlowOutcome>Input interface
| Parameter / option | Type |
|---|---|
id |
string |
paths.when |
FlowExpression<boolean> |
paths.then, paths.else |
(path: FlowBuilder<Input>) => FlowExpression<FlowOutcome> |
display? |
FlowDisplay |
Inputs: Boolean when; two callbacks returning output or another terminal branch. Returns: the terminal result reference. Return it from the containing flow/path. No steps may follow this terminal decision. Use if to rejoin instead. Beginner example.
Return interface
| Value / member | Type | Description |
|---|---|---|
| Builder return | FlowExpression<FlowOutcome> |
Terminal result reference. |
| Resolved outcome | string |
Name returned by the selected terminal path. |
| Resolved value? | unknown |
Optional resolved value returned by that path. |
return flow.branch("route", {
when: flow.input.get("needsReview").eq(true),
then: (path) => path.output("review"),
else: (path) => path.output("ready"),
});Finish the flow by choosing one of two paths. Each path must return an output or another branch.
flow.branch(id, { when, then, else }, display?)- id
- A unique step ID within this flow.
- when
- A flow condition evaluated when the branch runs.
- then
- The path used when the condition is true.
- else
- The path used when the condition is false.
- display?
- Optional labels and display metadata for DevTools.
A flow condition evaluated when the branch runs.
whenThe path used when the condition is true.
thenThe path used when the condition is false.
elseAPI reference: flow.branch() · flow.input · expression.get() · expression.eq() · flow.output()
Example walkthrough: The condition selects one terminal path. Each path returns a distinct outcome, so returning the branch completes the flow instead of joining into shared work.
flow.parallel#
flow.parallel(id, lanes: { [name: string]: (lane) => Expression<unknown> }, display?): JoinedReferencesInput interface
| Parameter / option | Type |
|---|---|
id |
string |
lanes |
Named builder callbacks, each returning a FlowExpression |
display? |
FlowDisplay |
Inputs: a nonempty object of named lane callbacks, each returning a value expression. Lane names follow step ID naming rules. Returns: an object preserving lane names and their expression types. Lanes run concurrently; steps within a lane are sequential. Sibling intermediate results are private. A failure cancels sibling work cooperatively. Beginner example.
Return interface
| Value / member | Type | Description |
|---|---|---|
| Builder return | Object with the lane keys |
Each member preserves its lane’s expression type. |
| Resolved lane values | Each lane’s result type |
Available through the named references after the join. |
const results = flow.parallel("prices", {
regular: (lane) => lane.task("full", { input: 100, run: (n) => n }),
sale: (lane) => lane.task("discount", { input: 80, run: (n) => n }),
});
return flow.output("priced", { regular: results.regular, sale: results.sale });Run named lanes concurrently and join their returned expressions. Each lane's work stays sequential. Lane names belong to your application.
flow.parallel(id, lanes, display?)- lanes
- An object of builder callbacks, each returning an expression. Private sibling intermediates are not available across lanes.
API reference: flow.parallel() · flow.task() · flow.output()
Example walkthrough: The named lanes produce independent prices. The output object resolves results.regular and results.sale after the lanes join, yielding { regular: 100, sale: 80 }.
flow.map#
flow.map<Item, Context, Output>(id, { items, input, maxItems, concurrency? }, build, display?): Expression<Output[]>Input interface
| Parameter / option | Type |
|---|---|
id |
string |
options.items |
readonly Item[] | FlowExpression<readonly Item[]> |
options.input |
Context |
options.maxItems |
number |
options.concurrency? |
number |
build |
(item: FlowBuilder<{ item: Item; index: number; context: Resolved<Context> }>) => FlowExpression<Output> |
display? |
FlowDisplay |
Inputs: array/reference items; shared context input; required integer maxItems from 1–64; concurrency from 1–16 (default 4). build(item) receives FlowBuilder<{ item: Item; index: number; context: Resolved<Context> }> and returns a value reference.
Returns: results in original item order; empty input returns an empty array. Exceeding maxItems rejects instead of truncating. Use explicit context rather than outer references inside the item program. No nested maps or loops inside maps. Complete beginner example.
Return interface
| Value / member | Type | Description |
|---|---|---|
| Builder return | FlowExpression<Output[]> |
Reference to the result array. |
| Resolved value | Output[] |
One result per item, in original input order. |
const doubled = flow.map("double", { items: [2, 4], input: {}, maxItems: 10 },
(item) => item.task("multiply", {
input: item.input.get("item"), run: (n) => n * 2,
}),
);Run a bounded item program for each item. Results preserve input order even when items execute concurrently.
flow.map(id, options, build, display?)- items
- An array or a reference to one. Each item is passed to the item program.
- input
- Explicit shared context for each item program, available as input.context.
- maxItems
- Required bound on the number of items.
- concurrency?
- Maximum number of item programs allowed to run concurrently.
- build
- Receives a builder with input { item, index, context }. Return an expression for the item result.
An array or a reference to one. Each item is passed to the item program.
itemsExplicit shared context for each item program, available as input.context.
inputRequired bound on the number of items.
maxItemsAPI reference: flow.map() · flow.task() · flow.input · expression.get()
Example walkthrough: Each item program selects its own number and multiplies it by two in a task. For [2, 4], the resolved array is [4, 8] in the same order. The bound rejects inputs above ten items.
flow.loop#
flow.loop<State, Context>(id, { initial, input, while, maxIterations }, build, display?): Expression<State>Input interface
| Parameter / option | Type |
|---|---|
id |
string |
options.initial |
State | FlowExpression<State> |
options.input |
Context |
options.while |
(state: FlowExpression<State>) => FlowExpression<boolean> |
options.maxIterations |
number |
build |
(iteration: FlowBuilder<{ state: State; index: number; context: Resolved<Context> }>) => FlowExpression<State> |
display? |
FlowDisplay |
Inputs: JSON state/reference initial; explicit shared context input; while(state) returning a Boolean expression of that state; required maxIterations integer 1–64. build(iteration) gets { state, index, context } through its builder input and returns the next state reference of the same type.
Returns: final state when the pre-iteration condition becomes false (possibly the initial state). If still true at the limit, rejects with FlowLoopLimitError. Nested loops are unsupported; a loop can contain a map. Beginner example.
Return interface
| Value / member | Type | Description |
|---|---|---|
| Builder return | FlowExpression<State> |
Reference to the final state. |
| Resolved value | State |
State when the condition becomes false; initial state if no iterations run. |
const final = flow.loop("count", {
initial: 0, input: {}, while: (state) => state.lt(3), maxIterations: 3,
}, (iteration) => iteration.task("increment", {
input: iteration.input.get("state"), run: (n) => n + 1,
}));Test a condition before each iteration and return the final state. Rejects if the condition still holds at the iteration limit.
flow.loop(id, options, build, display?)- initial
- The starting JSON state, or a reference to it.
- input
- Explicit shared context, available to the iteration program as input.context.
- while
- Build a Boolean flow condition from the current state. Tested before each iteration.
- maxIterations
- Required bound. Exceeding it while the condition is still true rejects with FlowLoopLimitError.
- build
- Receives a builder with input { state, index, context }. Return an expression for the next state.
The starting JSON state, or a reference to it.
initialExplicit shared context, available to the iteration program as input.context.
inputBuild a Boolean flow condition from the current state. Tested before each iteration.
whileBuild a strictly-less-than condition. Compares finite numeric values when the flow runs.
expression.lt(value)Required bound. Exceeding it while the condition is still true rejects with FlowLoopLimitError.
maxIterationsAPI reference: flow.loop() · expression.lt() · flow.task() · flow.input · expression.get()
Example walkthrough: The loop starts at zero and checks .lt(3) before each iteration. The task increments the state; after it reaches three, the condition is false and the loop resolves to 3.
flow.output#
flow.output(name: string, value?: unknown, display?: FlowDisplay): Expression<FlowOutcome>Input interface
| Parameter / option | Type |
|---|---|
name |
string |
value? |
unknown |
display? |
FlowDisplay |
Inputs: outcome name (also a unique node ID), optional literal/reference or nested value, optional display metadata. Returns: a terminal expression. Return it from your flow or branch path. Resolves nested references when run; no business action happens just because an outcome is named. No steps may follow it.
Return interface
| Value / member | Type | Description |
|---|---|---|
| Builder return | FlowExpression<FlowOutcome> |
Terminal result reference. |
| Resolved outcome | string |
The supplied name. |
| Resolved value? | unknown |
The supplied value with nested references resolved. |
return flow.output("done", { message, count });API reference: flow.output()
Example walkthrough: The object groups two previously defined references, message and count. Both are resolved into ordinary values under outcome "done"; naming the outcome performs no external action.
Expressions and conditions#
All these runtime exports come from jev-devtools/flow. References must belong to the same flow and be available in the current execution scope. See expression scopes.
FlowExpression#
new FlowExpression<T>(ast, owner?: object)
expression.ast: AstInput interface
| Parameter / option | Type |
|---|---|
ast |
Expression AST; see supporting structural types |
owner? |
object; ownership is managed by the builder |
A typed representation of a future value. The constructor accepts an expression AST (input, ref, get, literal, or supported operator nodes) and optional ownership token; it freezes the AST. Returns: an expression with the methods below. Its ast property describes the expression, not its resolved value.
Ordinary consumers should obtain owned expressions from builders. Hand-constructed expressions are generally unowned and cannot be inserted into a flow; the optional owner is not an application integration mechanism. The structural AST is visible in installed declarations, but not exported as a named public Ast type.
Return interface
| Value / member | Type | Description |
|---|---|---|
| Return | FlowExpression<T> |
Immutable expression object. |
| ast | Ast |
Frozen expression structure, not a resolved application value. |
const message = flow.input.get("message"); // obtain an owned expressionAPI reference: flow.input · expression.get()
Example walkthrough: Use the builder’s owned input reference and select its message field. This avoids manually constructing an AST or ownership token.
expression.get#
expression.get<K extends keyof T & (string | number)>(key: K): Expression<T[K]>Input interface
| Parameter / option | Type |
|---|---|
key |
keyof T & (string | number) |
Input: a valid string/number property key. Returns: a reference to that field; chain for nested fields. Missing values fail when execution reaches them. This is field selection, not a general optional chaining mechanism.
Return interface
| Value / member | Type | Description |
|---|---|---|
| Return | FlowExpression<T[K]> |
Reference to the selected field. |
const city = flow.input.get("address").get("city");API reference: flow.input · expression.get()
Example walkthrough: The first get() selects the nested address object; the second selects its city field. Both selections remain references until the flow evaluates them.
expression.eq#
expression.eq(value: T | Expression<T>): Expression<boolean>Input interface
| Parameter / option | Type |
|---|---|
value |
T | FlowExpression<T> |
Input: a literal or compatible expression. Equality for matching scalar types (string, number, Boolean, or null); not deep object equality. Returns: a Boolean expression, not a JavaScript Boolean. Invalid scalar comparisons reject during execution.
Return interface
| Value / member | Type | Description |
|---|---|---|
| Return | FlowExpression<boolean> |
Reference to the comparison/condition result. |
| Resolved value | boolean |
True or false when the flow evaluates it. |
const condition = flow.input.get("count").eq(3);API reference: flow.input · expression.get() · expression.eq()
Example walkthrough: The comparison checks whether the resolved count equals 3. Pass the resulting Boolean expression to when or another condition helper.
expression.lt#
expression.lt(value: number | Expression<number>): Expression<boolean>Input interface
| Parameter / option | Type |
|---|---|
value |
number | FlowExpression<number> |
Input: a literal or compatible expression. Strictly less than. Returns: a Boolean expression, not a JavaScript Boolean. Both resolved operands must be finite numbers; missing, nullable, or nonnumeric operands reject during execution. There is no lte; use not(value.gt(limit)) for a valid numeric inclusive upper bound.
Return interface
| Value / member | Type | Description |
|---|---|---|
| Return | FlowExpression<boolean> |
Reference to the comparison/condition result. |
| Resolved value | boolean |
True or false when the flow evaluates it. |
const condition = flow.input.get("count").lt(3);API reference: flow.input · expression.get() · expression.lt()
Example walkthrough: The comparison is true when the resolved count is below 3. It describes a condition now and evaluates it when the flow runs.
expression.gt#
expression.gt(value: number | Expression<number>): Expression<boolean>Input interface
| Parameter / option | Type |
|---|---|
value |
number | FlowExpression<number> |
Input: a literal or compatible expression. Strictly greater than. Returns: a Boolean expression, not a JavaScript Boolean. Both resolved operands must be finite numbers; missing, nullable, or nonnumeric operands reject during execution. There is no lte; use not(value.gt(limit)) for a valid numeric inclusive upper bound.
Return interface
| Value / member | Type | Description |
|---|---|---|
| Return | FlowExpression<boolean> |
Reference to the comparison/condition result. |
| Resolved value | boolean |
True or false when the flow evaluates it. |
const condition = flow.input.get("count").gt(3);Build a strictly-greater-than condition. Compares finite numeric values when the flow runs.
expression.gt(value)API reference: flow.input · expression.get() · expression.gt()
Example walkthrough: The comparison is true when the resolved count is above 3. Both operands must resolve to finite numbers.
expression.gte#
expression.gte(value: number | Expression<number>): Expression<boolean>Input interface
| Parameter / option | Type |
|---|---|
value |
number | FlowExpression<number> |
Input: a literal or compatible expression. Greater than or equal. Returns: a Boolean expression, not a JavaScript Boolean. Both resolved operands must be finite numbers; missing, nullable, or nonnumeric operands reject during execution. There is no lte; use not(value.gt(limit)) for a valid numeric inclusive upper bound.
Return interface
| Value / member | Type | Description |
|---|---|---|
| Return | FlowExpression<boolean> |
Reference to the comparison/condition result. |
| Resolved value | boolean |
True or false when the flow evaluates it. |
const condition = flow.input.get("count").gte(3);Build a greater-than-or-equal condition. Compares finite numeric values when the flow runs.
expression.gte(value)API reference: flow.input · expression.get() · expression.gte()
Example walkthrough: The comparison includes the boundary: a count of 3 satisfies it. It returns a condition reference for a later decision.
all#
all(...conditions: Expression<boolean>[]): Expression<boolean>Input interface
| Parameter / option | Type |
|---|---|
...conditions |
FlowExpression<boolean>[] |
Inputs: Boolean expressions. Returns: a Boolean expression. True only when every condition is true. Supply at least one owned condition; a zero-operand expression has no flow owner and cannot be used in a definition. All operands are evaluated; these helpers do not short-circuit missing-reference errors like JavaScript &&/||.
Return interface
| Value / member | Type | Description |
|---|---|---|
| Return | FlowExpression<boolean> |
Reference to the comparison/condition result. |
| Resolved value | boolean |
True or false when the flow evaluates it. |
const ready = all(flow.input.get("count").gte(1), flow.input.get("count").lt(10));Combine Boolean flow conditions: every condition must be true. All operands are evaluated; this does not short-circuit missing references.
all(...conditions)API reference: all() · flow.input · expression.get() · expression.gte() · expression.lt()
Example walkthrough: The two comparisons describe an inclusive lower bound of 1 and an exclusive upper bound of 10. all() combines them so both must hold.
any#
any(...conditions: Expression<boolean>[]): Expression<boolean>Input interface
| Parameter / option | Type |
|---|---|
...conditions |
FlowExpression<boolean>[] |
Inputs: Boolean expressions. Returns: a Boolean expression. True when at least one condition is true. Supply at least one owned condition; a zero-operand expression has no flow owner and cannot be used in a definition. All operands are evaluated; these helpers do not short-circuit missing-reference errors like JavaScript &&/||.
Return interface
| Value / member | Type | Description |
|---|---|---|
| Return | FlowExpression<boolean> |
Reference to the comparison/condition result. |
| Resolved value | boolean |
True or false when the flow evaluates it. |
const boundary = any(flow.input.get("count").eq(0), flow.input.get("count").eq(10));Combine Boolean flow conditions: at least one condition must be true. All operands are evaluated.
any(...conditions)API reference: any() · flow.input · expression.get() · expression.eq()
Example walkthrough: The two comparisons test the endpoints zero and ten. any() is true when either endpoint matches.
not#
not(condition: Expression<boolean>): Expression<boolean>Input interface
| Parameter / option | Type |
|---|---|
condition |
FlowExpression<boolean> |
Inputs: Boolean expression. Returns: a Boolean expression. Inverts one Boolean expression. All operands are evaluated; these helpers do not short-circuit missing-reference errors like JavaScript &&/||.
Return interface
| Value / member | Type | Description |
|---|---|---|
| Return | FlowExpression<boolean> |
Reference to the comparison/condition result. |
| Resolved value | boolean |
True or false when the flow evaluates it. |
const remaining = not(flow.input.get("count").eq(0));Invert a Boolean flow condition, evaluated when the flow runs.
not(condition)API reference: not() · flow.input · expression.get() · expression.eq()
Example walkthrough: The equality expression checks for zero; not() inverts it so a nonzero count satisfies the condition.
FlowLoopLimitError#
new FlowLoopLimitError(): Error
error.name: "FlowLoopLimitError"Input interface: no parameters.
Exported error class thrown when a loop condition remains true after its iteration limit. Takes no constructor arguments. Usage: catch around the awaited run; other errors should propagate. A limit error is not a successful partial result.
Return interface
| Value / member | Type | Description |
|---|---|---|
| Return | FlowLoopLimitError |
Error instance. |
| name | "FlowLoopLimitError" |
Identifies a still-true condition at the loop limit. |
import { FlowLoopLimitError } from "jev-devtools/flow";
try {
await program.run(input, { client });
} catch (error) {
if (!(error instanceof FlowLoopLimitError)) throw error;
console.error("The flow needs a different stopping rule or iteration limit.");
}Example walkthrough: The catch block handles only loop exhaustion and rethrows other failures. The flow did not complete successfully, so the handler reports the limit instead of treating a partial state as a result.
Fixtures and comparison#
Unreleased relative to npm 0.4.1. Use the working-tree package. Fixtures contain captured response bodies, not the original input or executable recipe.
flowFixture#
flowFixture(definition: FlowDefinition, run: FlowRun, traces: ReadonlyMap<string, Trace>): BuiltFlowFixtureInput interface
| Parameter / option | Type |
|---|---|
definition |
FlowDefinition |
run |
FlowRun |
traces |
ReadonlyMap<string, Trace> |
Import: jev-devtools (browser-safe) or jev-devtools/flow (Node). Inputs: recorded definition, recorded run, and map indexed by trace ID. Returns: { fixture, missing, warnings }. Completed Jev steps without available response captures appear in missing. Capture truncation/redaction warnings appear in warnings. Task results are not fixture responses.
Check both arrays before saving. Server redaction remains; this helper does not apply the drawer's current display masking. The drawer's Copy fixture action does. Failed/cancelled runs can yield partial fixtures.
Return interface
| Value / member | Type | Description |
|---|---|---|
| Return | BuiltFlowFixture |
Export result with fields below. |
| fixture | FlowFixture |
Saved response bodies by step ID. |
| missing | string[] |
Completed Jev steps with missing response captures. |
| warnings | string[] |
Capture limitations such as redaction or truncation. |
const built = flowFixture(definition, capturedRun, new Map(traces.map((trace) => [trace.id, trace])));
if (built.missing.length || built.warnings.length) console.warn(built);
const json = JSON.stringify(built.fixture, null, 2);Example walkthrough: The map indexes captures by trace ID. flowFixture() finds responses linked to completed Jev steps; the example checks limitations before serializing only the fixture payload.
replayClient#
replayClient(fixture: FlowFixture): TypeSafeClientInput interface
| Parameter / option | Type |
|---|---|
fixture |
FlowFixture |
Import: jev-devtools/flow. Input: { version: 1, flow, steps }. Returns: a client for a flow's run that supplies saved Jev responses without a key/network. Checks flow name, step IDs, and question names, not prompts/state. Invalid fixtures throw; missing steps/answers reject rather than falling back to inference. Item/iteration step keys include their indices.
Tasks and request factories run again and can have side effects. Pass controlled tasks in tests. Complete replay example.
Return interface
| Value / member | Type | Description |
|---|---|---|
| Return | TypeSafeClient |
Supplies fixture answers when used as a flow run’s client. |
const result = await program.run(input, { client: replayClient(fixture) });Example walkthrough: The client supplies stored answers for Jev steps while the same recipe executes with the supplied input. Tasks and request factories still execute, so control side effects in replay tests.
compareWithFixture#
compareWithFixture<Input>(flow: ComparableFlow<Input>, input: Input, fixture: FlowFixture, options: FlowRunOptions & { tolerance?: number }): Promise<FixtureComparison>Input interface
| Parameter / option | Type |
|---|---|
flow |
ComparableFlow<Input> |
input |
Input |
fixture |
FlowFixture |
options |
FlowRunOptions & { tolerance?: number } |
Import: jev-devtools/flow. Inputs: the executable recipe, the same input for both runs, saved fixture, live client, optional observer/signal, and numeric answer tolerance (default 0; finite and nonnegative). Replays the recorded side first, then runs the live side; both execute tasks. Only the live side reaches your observer.
Returns: outcome/value comparisons, branch/if decisions, scalar answer-field differences, step IDs unique to either side, and a new liveFixture. Top-level changed means outcome, value, decision, or executed Jev-step differences; answer drift alone does not set it. Tolerance applies to numeric answer-field differences, not outcome/value comparisons. Invalid tolerance or unreplayable fixtures reject; live errors propagate.
Unlike offline replay, this makes real SDK calls with the supplied client. liveFixture contains unredacted, untruncated live response bodies; review before storing or sharing.
Return interface
| Value / member | Type | Description |
|---|---|---|
| Return | Promise<FixtureComparison> |
Resolves to the comparison fields below. |
| changed | boolean |
Outcome, value, decision, or executed Jev steps differ; answer drift alone does not set it. |
| outcome | { recorded: string; live: string; changed: boolean } |
Terminal outcome comparison. |
| value | { recorded?: unknown; live?: unknown; changed: boolean } |
Resolved output comparison. |
| decisions | Array<{ stepId: string; recorded?: boolean; live?: boolean; changed: boolean }> |
Branch and if decisions. |
| answers | AnswerChange[] |
Scalar answer-field comparisons, including numeric deltas when finite. |
| steps | { onlyRecorded: string[]; onlyLive: string[] } |
Jev step IDs executed on only one side. |
| liveFixture | FlowFixture |
Unredacted, untruncated live response bodies. |
import { compareWithFixture, formatComparison } from "jev-devtools/flow";
const comparison = await compareWithFixture(program, input, fixture, {
client, devtools: inspector, tolerance: 0.01,
});
console.log(formatComparison(comparison));Example walkthrough: The helper first replays the saved fixture, then executes with the supplied client. The tolerance permits small numeric answer differences. formatComparison() prints the differences without changing either run’s result.
formatComparison#
formatComparison(comparison: FixtureComparison): stringInput interface
| Parameter / option | Type |
|---|---|
comparison |
FixtureComparison |
Import: jev-devtools/flow. Input: a comparison result. Returns: a plain-text summary with one line per change, or "no changes from fixture". No I/O is performed; print or store it yourself. It may report answer drift even when comparison.changed is false.
Return interface
| Value / member | Type | Description |
|---|---|---|
| Return | string |
One line per reported change, or no changes from fixture. |
const summary = formatComparison(comparison);
console.log(summary);Example walkthrough: Formatting produces text only. console.log() is the application-owned output operation; the formatter itself performs no logging.
Recorder#
Import JevRecorder from jev-devtools/server. This is for custom integrations; most apps can use the server adapter. Complete recorder setup.
JevRecorder#
new JevRecorder(options?: CaptureOptions): JevRecorderInput interface
| Parameter / option | Type |
|---|---|
options? |
CaptureOptions |
Inputs: optional limit (default 50, clamped 1–200 globally) and redact(path, value) callback. Returns: a recorder with the following methods. History is bounded process memory, not durable storage. Pair instrument and transport to capture logical requests and HTTP attempts.
Return interface
| Value / member | Type | Description |
|---|---|---|
| Return | JevRecorder |
Recorder instance with the methods below. |
import { JevRecorder } from "jev-devtools/server";
const recorder = new JevRecorder({ limit: 50 });Create a lower-level in-memory request recorder for a custom integration.
new JevRecorder(options?)- limit
- Maximum retained request traces across sessions. Defaults to 50; clamped to 1–200.
- redact
- Transform the captured copy before storage or streaming. Does not change SDK inputs or application values.
Maximum retained request traces across sessions. Defaults to 50; clamped to 1–200.
limitAPI reference: JevRecorder
Example walkthrough: Constructing the recorder sets a global retention bound of 50 traces. The instance can then wrap an SDK client and expose history to a custom viewer.
recorder.transport#
recorder.transport(fetcher: Fetch): FetchInput interface
| Parameter / option | Type |
|---|---|
fetcher |
SDK-compatible Fetch |
Input: SDK-compatible fetch implementation, e.g. globalThis.fetch. Returns: a compatible wrapper that records HTTP attempts associated with instrumented calls. Give it to the SDK's fetch option; it is not a standalone recording server.
Return interface
| Value / member | Type | Description |
|---|---|---|
| Return | Fetch |
SDK-compatible fetch wrapper; request/response contract is retained. |
const sdk = new TypeSafeClient({ apiKey, fetch: recorder.transport(globalThis.fetch) });Example walkthrough: The SDK receives a wrapped fetch, so HTTP attempts can be associated with instrumented logical calls. Pair this client with recorder.instrument() to record both levels.
recorder.instrument#
recorder.instrument<T extends TypeSafeClient>(client: T, metadata: { session: string; label: string; source: "sample" | "live" } | (() => Metadata)): TInput interface
| Parameter / option | Type |
|---|---|
client |
T extends TypeSafeClient |
metadata |
{ session: string; label: string; source: "sample" | "live" }, or a factory returning it |
Inputs: an SDK client and complete metadata or a synchronous metadata factory. session partitions history; label identifies a call; source labels sample/live captures. Returns: an instrumented proxy preserving the client's type, SDK promise, response helpers, and application result. Only systemOne is captured; pair with transport for attempts. A sample label does not prevent network calls.
Return interface
| Value / member | Type | Description |
|---|---|---|
| Return | T |
Instrumented client proxy preserving its original SDK type. |
const client = recorder.instrument(sdk, { session: "local", label: "Support", source: "live" });Example walkthrough: The proxy records systemOne calls under the local session with the Support label. The original SDK’s response helpers remain available on the returned client.
recorder.snapshot#
recorder.snapshot(session: string): Trace[]Input interface
| Parameter / option | Type |
|---|---|
session |
string |
Input: session to read. Returns: detached retained traces, newest first. These are bounded/redacted inspection copies, not original SDK objects. Reading does not clear history.
Return interface
| Value / member | Type | Description |
|---|---|---|
| Return | Trace[] |
Detached retained traces for the session, newest first. |
const traces = recorder.snapshot("local");Example walkthrough: The call retrieves a detached copy of the local session’s current history. Reading the array does not clear or mutate recorder storage.
recorder.changes#
recorder.changes(session: string, after?: number): { revision: number; order: string[]; upserts: Trace[] }Input interface
| Parameter / option | Type |
|---|---|
session |
string |
after? |
number |
Inputs: session and optional revision cursor. Returns: current revision, authoritative newest-first retained IDs, and detached changed traces. Omit after for an initial full view. Remove locally cached IDs absent from order, even when upserts is empty, and save the returned cursor.
Return interface
| Value / member | Type | Description |
|---|---|---|
| revision | number |
Cursor for the next call. |
| order | string[] |
Authoritative newest-first retained trace IDs. |
| upserts | Trace[] |
Detached traces changed since the supplied cursor. |
const initial = recorder.changes("local");
const next = recorder.changes("local", initial.revision);Example walkthrough: The initial call requests a full view. Passing its revision into the second call requests changed traces; use the returned order to remove traces no longer retained.
recorder.subscribe#
recorder.subscribe(listener: (sessions: ReadonlySet<string>) => void): () => voidInput interface
| Parameter / option | Type |
|---|---|
listener |
(sessions: ReadonlySet<string>) => void |
Input: listener notified of affected sessions. Returns: a function to unsubscribe. Read a snapshot/changes for your session when notified. Notifications carry session names, not trace payloads.
Return interface
| Value / member | Type | Description |
|---|---|---|
| Return | () => void |
Unsubscribe function; call when the listener is no longer needed. |
const stop = recorder.subscribe((sessions) => {
if (sessions.has("local")) console.log(recorder.snapshot("local"));
});
// On shutdown:
stop();Example walkthrough: The listener filters notifications to the local session before reading its snapshot. Calling stop() removes that listener during cleanup.
recorder.clear#
recorder.clear(session: string): voidInput interface
| Parameter / option | Type |
|---|---|
session |
string |
Input: session to clear. Returns: nothing. Removes that session's retained traces and notifies listeners; other session traces remain. Does not cancel your application's in-flight SDK work.
Return interface
| Value / member | Type | Description |
|---|---|---|
| Return | void |
No data; affected listeners are notified. |
recorder.clear("local");Example walkthrough: Only retained traces for the local session are removed. Other session histories and application inference continue.
recorder.withFlow#
recorder.withFlow<T>(link: { runId: string; stepId: string; executionId: string }, onTrace: (id: string) => void, callback: () => T): TInput interface
| Parameter / option | Type |
|---|---|
link |
{ runId: string; stepId: string; executionId: string } |
onTrace |
(id: string) => void |
callback |
() => T |
Inputs: flow linkage, trace-ID callback, and request callback. Returns: the callback's result unchanged, including a promise if it returns one. Establishes async recording context and calls through immediately. Normally invoked by the adapter's observer; standalone custom integrations should keep the call-through synchronous.
Return interface
| Value / member | Type | Description |
|---|---|---|
| Return | T |
The callback result unchanged, including a promise when applicable. |
return recorder.withFlow(link, (traceId) => { latestTraceId = traceId; }, callback);Example walkthrough: The wrapper establishes flow linkage around the request callback. onTrace stores the captured trace ID, while the callback’s result is returned unchanged.
Observer hooks#
FlowObserver is exported as a type from jev-devtools/flow. Pass an implementation as devtools to a run; the server adapter already implements it. Observers record execution and must not control application work. Current working-tree observer failure containment is unreleased relative to npm 0.4.1.
observer.startFlow#
observer.startFlow(definition: FlowDefinition, run: FlowRun): { update; request } | undefinedInput interface
| Parameter / option | Type |
|---|---|
definition |
FlowDefinition |
run |
FlowRun |
Inputs: the graph and initial captured run. Returns: per-run update/request hooks, or undefined to skip observation. The adapter's startFlow records to the current session and returns undefined when disabled. Usually you never call it directly.
Return interface
| Value / member | Type | Description |
|---|---|---|
| Return | { update, request } | undefined |
Per-run hooks, or undefined to skip recording. |
| update | (run: FlowRun) => void |
Receives execution updates. |
| request | Generic callback wrapper |
Links a request to its flow step; signature below. |
await program.run(input, { client, devtools: inspector }); // runner calls startFlowExample walkthrough: Passing the inspector as devtools lets the runner call startFlow and manage the returned hooks. Application code does not need to invoke those hooks manually.
observer.update#
hooks.update(run: FlowRun): voidInput interface
| Parameter / option | Type |
|---|---|
run |
FlowRun |
Input: latest run snapshot with step status, outcome, or error. Returns: nothing. Store/send the snapshot in a custom viewer integration; do not use it to execute business actions. The runner calls this as execution changes.
Return interface
| Value / member | Type | Description |
|---|---|---|
| Return | void |
No application result is produced. |
const hooks = {
update(run: FlowRun) { console.log(run.id, run.status); },
request<T>(_link: unknown, _onTrace: (id: string) => void, callback: () => T): T {
return callback();
},
};Example walkthrough: The update hook logs the run ID and status. Its companion request hook calls through immediately; these hooks observe execution rather than implement application steps.
observer.request#
hooks.request<T>(link: { runId: string; stepId: string; executionId: string }, onTrace: (id: string) => void, callback: () => T): TInput interface
| Parameter / option | Type |
|---|---|
link |
{ runId: string; stepId: string; executionId: string } |
onTrace |
(id: string) => void |
callback |
() => T |
Inputs: execution linkage, a callback to report the captured trace ID, and the request callback. Returns: the callback's original result. Call through synchronously so recording context is established; the runner owns the result. Custom observers can delegate to a recorder.
Return interface
| Value / member | Type | Description |
|---|---|---|
| Return | T |
The original request callback result. |
request<T>(link: Link, onTrace: (id: string) => void, callback: () => T): T {
return recorder.withFlow(link, onTrace, callback);
} // method inside a custom hook object; Link is the shape aboveExample walkthrough: The custom observer delegates to withFlow() so the recorder can associate the SDK request with its run, step, and execution ID. Returning the callback result preserves the runner’s request behavior.
Public types#
Use import type for these contracts. The expandable declarations below are read from the actual public package exports at docs build time, including their supporting structural types. They supplement the method explanations above and make exact fields and unions available without a browser TypeScript runtime. A type's declaration is not a promise that the same API exists in an older installed package.
Type: Trace#
One captured request, its attempts, answers, timing, status, and capture warnings.
Import type from: jev-devtools.
Exact TypeScript declaration
export type Trace = {
version: 1;
id: string;
session: string;
flow?: {
runId: string;
stepId: string;
executionId: string;
};
label: string;
source: "sample" | "live";
startedAt: number;
durationMs?: number;
status: "pending" | "success" | "error" | "cancelled";
request: {
model: string;
state: Json;
questions: Record<string, QuestionView>;
assets?: Json;
};
result?: {
model: string;
answers: Record<string, AnswerView>;
usage?: {
input_tokens: number;
output_tokens: number;
};
};
requestId?: string;
error?: {
name: string;
message: string;
status?: number;
};
attempts: {
number: number;
durationMs?: number;
status?: number;
error?: string;
}[];
captureWarnings: string[];
};Type: Snapshot#
Version 1 full trace history message.
Import type from: jev-devtools.
Exact TypeScript declaration
export type Snapshot = {
version: 1;
traces: Trace[];
};Type: StreamSnapshot#
Version 2 initial trace history with a revision cursor.
Import type from: jev-devtools.
Exact TypeScript declaration
export type StreamSnapshot = {
version: 2;
revision: number;
traces: Trace[];
};Type: TraceDelta#
Version 2 incremental update. order is authoritative: remove retained IDs absent from it.
Import type from: jev-devtools.
Exact TypeScript declaration
export type TraceDelta = {
version: 2;
base: number;
revision: number;
upserts: Trace[];
/** Authoritative newest-first retained IDs. Omitted IDs are removed. */
order: string[];
};Type: Json#
The JSON-safe shape used for captured data, which may be redacted or truncated.
Import type from: jev-devtools.
Exact TypeScript declaration
export type Json = null | boolean | number | string | Json[] | {
[key: string]: Json;
};Type: AnswerView#
Captured SDK answer fields; available fields depend on the question type.
Import type from: jev-devtools.
Exact TypeScript declaration
export type AnswerView = {
type: string;
choice?: string;
score?: number;
noul?: number;
confidence?: number;
probabilities?: Record<string, number>;
legend?: Record<string, Json>;
box?: {
left: number;
top: number;
right: number;
bottom: number;
};
};Type: QuestionView#
Captured question type, instructions, and criteria.
Import type from: jev-devtools.
Exact TypeScript declaration
export type QuestionView = {
type: string;
instructions?: Json;
criteria?: Json;
};Type: FlowFixture#
Unreleased. Saved Jev response bodies indexed by step ID; no original input or implementation.
Import type from: jev-devtools or jev-devtools/flow.
Exact TypeScript declaration
export type FlowFixture = {
version: 1;
flow: string;
steps: Record<string, unknown>;
};Type: BuiltFlowFixture#
Unreleased. Exported fixture plus missing capture IDs and warnings to inspect before saving.
Import type from: jev-devtools or jev-devtools/flow.
Exact TypeScript declaration
export type BuiltFlowFixture = {
fixture: FlowFixture;
/** Completed jev steps whose capture was evicted, unlinked, or has no result. */
missing: string[];
/** Capture warnings from the traces used; redaction or truncation may show in the answers. */
warnings: string[];
};Type: JevDevtoolsProps#
Props for the React drawer: enablement, endpoint, initial open state, and display masking.
Import type from: jev-devtools.
Exact TypeScript declaration
export type JevDevtoolsProps = {
endpoint?: string;
/** Opt in explicitly, normally with import.meta.env.DEV. */
enabled?: boolean;
defaultOpen?: boolean;
/** Mask supported personal fields in DevTools views only. Defaults to true.
* Captures still contain these values; credentials remain server-redacted. */
redactAdditionalSensitiveData?: boolean;
};Type: CaptureOptions#
Trace retention and captured-value redaction shared by the adapter and recorder.
Import type from: jev-devtools/server.
Exact TypeScript declaration
export type CaptureOptions = {
limit?: number;
redact?: (path: string[], value: unknown) => unknown;
};Type: JevDevtoolsServerOptions#
Server adapter configuration, including CaptureOptions, access checks, and session selection.
Import type from: jev-devtools/server.
Exact TypeScript declaration
export type JevDevtoolsServerOptions = CaptureOptions & {
/** Explicit opt-in; disabled by default. */
enabled?: boolean;
origin: string;
endpoint?: string;
/**
* Required for non-loopback origins. Without it, Node middleware only accepts
* loopback connections. Apply your application's authentication.
*/
authorize?: (request: Request) => boolean | Promise<boolean>;
/** Server-derived scope. Defaults to a shared local-development history. */
getSession?: (request: Request) => string | Promise<string>;
};Type: ClientMetadata#
Optional capture label and sample/live source label; neither changes the SDK transport.
Import type from: jev-devtools/server.
Exact TypeScript declaration
export type ClientMetadata = {
label?: string;
source?: "sample" | "live";
};Type: FlowDisplay#
Optional title and description for the inspector, not model instructions or business behavior.
Import type from: jev-devtools/flow.
Exact TypeScript declaration
export type FlowDisplay = {
title?: string;
description?: string;
};Type: FlowDefinition#
Static recipe graph, including its identity, name, and node metadata.
Import type from: jev-devtools/flow.
Exact TypeScript declaration
export type FlowDefinition = {
version: 1;
id: string;
name: string;
nodes: FlowNode[];
};Type: FlowRun#
Captured execution state, with per-step progress and optional outcome, value, or error.
Import type from: jev-devtools/flow.
Exact TypeScript declaration
export type FlowRun = {
version: 1;
id: string;
definitionId: string;
name: string;
startedAt: number;
durationMs?: number;
status: "pending" | "success" | "error" | "cancelled";
steps: Record<string, FlowStep>;
outcome?: string;
value?: Json;
error?: string;
};Type: FlowStep#
Captured step state, timing, decisions, trace linkage, and collection/loop progress.
Import type from: jev-devtools/flow.
Exact TypeScript declaration
export type FlowStep = {
id: string;
executionId: string;
status: "waiting" | "running" | "completed" | "untaken" | "blocked" | "error" | "cancelled";
startedAt?: number;
durationMs?: number;
decision?: boolean;
traceId?: string;
error?: string;
/** Optional bounded, redacted task input/output or selected merge value; never executable code. */
data?: Json;
captureWarnings?: string[];
loop?: {
maxIterations: number;
stopReason?: "condition" | "limit";
};
collection?: {
total: number;
concurrency: number;
items: Array<{
index: number;
status: FlowStep["status"];
run?: FlowRun;
}>;
};
};Type: AnswerChange#
Unreleased. One scalar answer-field comparison; delta exists only for finite numeric differences.
Import type from: jev-devtools/flow.
Exact TypeScript declaration
export type AnswerChange = {
stepId: string;
question: string;
field: string;
recorded?: AnswerValue;
live?: AnswerValue;
delta?: number;
changed: boolean;
};Type: ComparableFlow#
Unreleased. A definition and executable run method accepted by compareWithFixture.
Import type from: jev-devtools/flow.
Exact TypeScript declaration
export type ComparableFlow<T> = {
definition: FlowDefinition;
run(input: T, options: FlowRunOptions): Promise<FlowOutcome & {
runId: string;
}>;
};Type: FixtureComparison#
Unreleased. Outcome/value/decision/step changes, answer drift, and unredacted live fixture.
Import type from: jev-devtools/flow.
Exact TypeScript declaration
export type FixtureComparison = {
/** Outcome, value or a decision differs, or a jev step ran on only one side. Answer drift alone does not set it. */
changed: boolean;
outcome: {
recorded: string;
live: string;
changed: boolean;
};
value: {
recorded?: unknown;
live?: unknown;
changed: boolean;
};
decisions: {
stepId: string;
recorded?: boolean;
live?: boolean;
changed: boolean;
}[];
answers: AnswerChange[];
/** Jev step ids; map and loop items use `${parentId}/${index}/${innerId}`. */
steps: {
onlyRecorded: string[];
onlyLive: string[];
};
/** The live run's response bodies, unredacted and untruncated. */
liveFixture: FlowFixture;
};Type: FlowOutcome#
A named terminal outcome and optional value. The value is unknown; narrow it in your app.
Import type from: jev-devtools/flow.
Exact TypeScript declaration
export type FlowOutcome = {
outcome: string;
value?: unknown;
};Type: Resolved#
Recursively replaces expression references in an input shape with their value types.
Import type from: jev-devtools/flow.
Exact TypeScript declaration
export type Resolved<T> = T extends FlowExpression<infer V> ? V : T extends readonly unknown[] ? {
[K in keyof T]: Resolved<T[K]>;
} : T extends object ? {
[K in keyof T]: Resolved<T[K]>;
} : T;Type: TaskContext#
Cancellation signal and identifiers passed as a task callback's second argument.
Import type from: jev-devtools/flow.
Exact TypeScript declaration
export type TaskContext = {
signal: AbortSignal;
runId: string;
stepId: string;
executionId: string;
};Type: FlowTask#
Resolved task input, application callback, and optional timeout.
Import type from: jev-devtools/flow.
Exact TypeScript declaration
export type FlowTask<I, O> = {
input: I;
run: (input: Resolved<I>, context: TaskContext) => O | Promise<O>;
timeoutMs?: number;
};Type: FlowRequestFactory#
Input to resolve before synchronously constructing an SDK request.
Import type from: jev-devtools/flow.
Exact TypeScript declaration
export type FlowRequestFactory<I, Q extends Questions> = {
input: I;
/** Synchronous construction only; the runner executes the SDK request. */
request: (input: Resolved<I>) => SystemOneRequest<Q>;
};Type: FlowObserver#
Per-run capture hooks. Return undefined to skip recording; execution belongs to the runner.
Import type from: jev-devtools/flow.
Exact TypeScript declaration
export type FlowObserver = {
startFlow(definition: FlowDefinition, run: FlowRun): {
update(run: FlowRun): void;
/** Call through synchronously to establish recording context. The runner owns the result. */
request<T>(link: {
runId: string;
stepId: string;
executionId: string;
}, onTrace: (id: string) => void, callback: () => T): T;
} | undefined;
};Type: FlowRunOptions#
Required SDK-compatible client, optional observer, and cancellation signal.
Import type from: jev-devtools/flow.
Exact TypeScript declaration
export type FlowRunOptions = {
client: Pick<TypeSafeClient, "systemOne">;
devtools?: FlowObserver;
signal?: AbortSignal;
};Type: FlowBuilder#
The synchronous recipe-building interface. Its methods return references, not runtime results.
Import type from: jev-devtools/flow.
Exact TypeScript declaration
export interface FlowBuilder<T> {
readonly input: FlowExpression<T>;
jev<const Q extends Questions>(id: string, request: Request<Q>, display?: FlowDisplay): AnswerRefs<Q>;
jev<I, const Q extends Questions>(id: string, request: FlowRequestFactory<I, Q>, display?: FlowDisplay): AnswerRefs<Q>;
task<I, O>(id: string, task: FlowTask<I, O>, display?: FlowDisplay): FlowExpression<Awaited<O>>;
/** Execute a reusable item program with bounded concurrency; return results in input order. */
map<I, C, O>(id: string, options: {
items: FlowExpression<readonly I[]> | readonly I[];
input: C;
concurrency?: number;
maxItems: number;
}, build: (item: FlowBuilder<{
item: I;
index: number;
context: Resolved<C>;
}>) => FlowExpression<O>, display?: FlowDisplay): FlowExpression<O[]>;
/** Pre-test loop with JSON state, explicit context and a required iteration limit. */
loop<S, C>(id: string, options: {
initial: S | FlowExpression<S>;
input: C;
while: (state: FlowExpression<NoInfer<S>>) => FlowExpression<boolean>;
maxIterations: number;
}, build: (iteration: FlowBuilder<{
state: S;
index: number;
context: Resolved<C>;
}>) => FlowExpression<NoInfer<S>>, display?: FlowDisplay): FlowExpression<S>;
/** Execute one value-returning path, then continue with its typed result. */
if<A, B>(id: string, paths: {
when: FlowExpression<boolean>;
then: (path: FlowBuilder<T>) => FlowExpression<A>;
else: (path: FlowBuilder<T>) => FlowExpression<B>;
}, display?: FlowDisplay): FlowExpression<A | B>;
/** Terminal decision: each callback must return output() or another branch(). */
branch(id: string, paths: {
when: FlowExpression<boolean>;
then: (path: FlowBuilder<T>) => FlowExpression<FlowOutcome>;
else: (path: FlowBuilder<T>) => FlowExpression<FlowOutcome>;
}, display?: FlowDisplay): FlowExpression<FlowOutcome>;
parallel<const B extends Record<string, (lane: FlowBuilder<T>) => FlowExpression<unknown>>>(id: string, lanes: B, display?: FlowDisplay): {
[K in keyof B]: ReturnType<B[K]>;
};
output(name: string, value?: unknown, display?: FlowDisplay): FlowExpression<FlowOutcome>;
}Exact callable declarations#
These expandable declarations preserve the actual generic signatures behind the readable method summaries.
JevDevtools declaration#
Import from jev-devtools.
Exact TypeScript declaration
export declare function JevDevtools({ enabled, ...props }: JevDevtoolsProps): import("react").JSX.Element | null;flowFixture declaration#
Import from jev-devtools.
Exact TypeScript declaration
export declare function flowFixture(definition: FlowDefinition, run: FlowRun, traces: ReadonlyMap<string, Trace>): BuiltFlowFixture;createJevDevtools declaration#
Import from jev-devtools/server.
Exact TypeScript declaration
export declare function createJevDevtools(options: JevDevtoolsServerOptions): {
startFlow: (definition: import("../src/flow-protocol.js").FlowDefinition, run: import("../src/flow-protocol.js").FlowRun) => {
update(run: import("../src/flow-protocol.js").FlowRun): void;
request<T>(link: {
runId: string;
stepId: string;
executionId: string;
}, onTrace: (id: string) => void, callback: () => T): T;
} | undefined;
handle: (request: Request) => Promise<Response>;
/**
* For Node framework routes whose Request URL uses an internal hostname.
* Require the actual Host header to match the configured public authority;
* never trust forwarded host/protocol headers. Origin checks still apply.
* A Request does not expose the socket peer and any non-browser client can
* set Host, so bind the server to loopback or set authorize.
*/
handleNodeRoute(request: Request): Promise<Response>;
middleware: (req: IncomingMessage, res: ServerResponse, next: () => void) => Promise<void>;
createClient(clientOptions: ConstructorParameters<typeof TypeSafeClient>[0], meta?: ClientMetadata): TypeSafeClient;
/** Needed only when getSession is customized. Wrap the application's request handler. */
run<T>(request: Request, callback: () => T | Promise<T>): Promise<T>;
dispose(): void;
};getJevDevtools declaration#
Import from jev-devtools/server.
Exact TypeScript declaration
export declare function getJevDevtools(key: string, createOptions: () => JevDevtoolsServerOptions): Adapter;JevRecorder declaration#
Import from jev-devtools/server.
Exact TypeScript declaration
export declare class JevRecorder {
private options;
private traces;
private listeners;
private revision;
private revisions;
private context;
private flowContext;
withFlow<T>(link: NonNullable<Trace["flow"]>, onTrace: (id: string) => void, callback: () => T): T;
private limit;
constructor(options?: CaptureOptions);
snapshot(session: string): Trace[];
changes(session: string, after?: number): {
revision: number;
order: string[];
upserts: Trace[];
};
subscribe(listener: (sessions: ReadonlySet<string>) => void): () => void;
clear(session: string): void;
private notify;
private update;
transport(fetcher: Fetch): Fetch;
instrument<T extends TypeSafeClient>(client: T, meta: {
session: string;
label: string;
source: "sample" | "live";
} | (() => {
session: string;
label: string;
source: "sample" | "live";
})): T;
}defineJevFlow declaration#
Import from jev-devtools/flow.
Exact TypeScript declaration
export declare function defineJevFlow<T>(name: string, build: (flow: FlowBuilder<T>) => FlowExpression<FlowOutcome>): {
definition: FlowDefinition;
run(data: T, options: FlowRunOptions): Promise<{
runId: string;
outcome: string;
value?: unknown;
}>;
};replayClient declaration#
Import from jev-devtools/flow.
Exact TypeScript declaration
export declare function replayClient(fixture: FlowFixture): TypeSafeClient;compareWithFixture declaration#
Import from jev-devtools/flow.
Exact TypeScript declaration
export declare function compareWithFixture<T>(flow: ComparableFlow<T>, input: T, fixture: FlowFixture, options: FlowRunOptions & {
tolerance?: number;
}): Promise<FixtureComparison>;formatComparison declaration#
Import from jev-devtools/flow.
Exact TypeScript declaration
export declare function formatComparison(comparison: FixtureComparison): string;FlowExpression declaration#
Import from jev-devtools/flow.
Exact TypeScript declaration
export declare class FlowExpression<T> {
readonly ast: Ast;
protected readonly [valueTypeBrand]: T;
constructor(ast: Ast, owner?: object);
get<K extends keyof T & (string | number)>(key: K): FlowExpression<T[K]>;
eq(value: T | FlowExpression<T>): FlowExpression<boolean>;
lt(this: FlowExpression<number>, value: number | FlowExpression<number>): FlowExpression<boolean>;
gt(this: FlowExpression<number>, value: number | FlowExpression<number>): FlowExpression<boolean>;
gte(this: FlowExpression<number>, value: number | FlowExpression<number>): FlowExpression<boolean>;
}all declaration#
Import from jev-devtools/flow.
Exact TypeScript declaration
declare const all: (...conditions: FlowExpression<boolean>[]) => FlowExpression<boolean>;any declaration#
Import from jev-devtools/flow.
Exact TypeScript declaration
declare const any: (...conditions: FlowExpression<boolean>[]) => FlowExpression<boolean>;not declaration#
Import from jev-devtools/flow.
Exact TypeScript declaration
declare const not: (condition: FlowExpression<boolean>) => FlowExpression<boolean>;FlowLoopLimitError declaration#
Import from jev-devtools/flow.
Exact TypeScript declaration
export declare class FlowLoopLimitError extends Error {
readonly name = "FlowLoopLimitError";
constructor();
}Supporting structural types#
These shapes appear inside the public contracts above. They are not named public exports; do not import them from package internals. SDK and platform types are defined by their respective packages.
FlowNode shape#
Exact TypeScript declaration
export type FlowNode = FlowDisplay & {
id: string;
kind: "jev" | "task" | "map" | "loop" | "if" | "merge" | "branch" | "parallel" | "output";
dependencies: string[];
path: string[];
condition?: string;
/** Conditional merge edges; only the selected arm supplies a value. */
merge?: {
condition: string;
then: string;
else: string;
};
/** Optional for older captures; Json allows redacted/truncated subtrees. */
conditionRule?: Json | FlowCondition;
/** Named concurrent lanes; optional for older captures. */
lanes?: string[];
/** Reusable item program; map nesting is not supported in v1. */
itemDefinition?: FlowDefinition;
};FlowCondition shape#
Exact TypeScript declaration
export type FlowCondition = {
op: "reference";
label: string;
probability: boolean;
name?: string;
stepId?: string;
path?: string[];
source?: "task";
} | {
op: "literal";
value: string | number | boolean | null;
} | {
op: "eq" | "lt" | "gt" | "gte" | "all" | "any" | "not";
args: FlowCondition[];
};AnswerValue shape#
Exact TypeScript declaration
type AnswerValue = string | number | boolean | null;Request shape#
Exact TypeScript declaration
type Request<Q extends Questions> = Omit<SystemOneRequest<Q>, "state"> & {
state: unknown;
};AnswerRefs shape#
Exact TypeScript declaration
type AnswerRefs<Q extends Questions> = {
/** All answers, including question names created by a runtime request factory. */
readonly answers: FlowExpression<{
[K in keyof Q]: ResultFor<Q[K]>;
}>;
answer<K extends keyof Q & string>(name: K): {
[F in keyof ResultFor<Q[K]>]: FlowExpression<ResultFor<Q[K]>[F]>;
};
};Adapter shape#
Exact TypeScript declaration
type Adapter = ReturnType<typeof createJevDevtools>;Ast shape#
Exact TypeScript declaration
type Ast = {
op: "input";
} | {
op: "ref";
id: string;
} | {
op: "get";
from: Ast;
key: string;
} | {
op: "literal";
value: unknown;
} | {
op: "eq" | "lt" | "gt" | "gte" | "all" | "any" | "not";
args: Ast[];
};