@spendgraph/graph 0.6.0 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -0
- package/dist/compile/ceiling.d.ts +3 -0
- package/dist/compile/ceiling.js +1 -0
- package/dist/compile/compile.d.ts +5 -14
- package/dist/compile/compile.js +1 -1
- package/dist/compile/covered.d.ts +3 -0
- package/dist/compile/covered.js +1 -0
- package/dist/compile/edges.d.ts +5 -0
- package/dist/compile/edges.js +1 -0
- package/dist/compile/errors.d.ts +14 -0
- package/dist/compile/errors.js +1 -0
- package/dist/compile/index.d.ts +2 -0
- package/dist/compile/index.js +1 -1
- package/dist/compile/nodes.d.ts +4 -0
- package/dist/compile/nodes.js +1 -0
- package/dist/compile/reachable.d.ts +9 -0
- package/dist/compile/reachable.js +1 -0
- package/dist/execute/context.d.ts +4 -0
- package/dist/execute/context.js +1 -0
- package/dist/execute/errors.d.ts +6 -0
- package/dist/execute/errors.js +1 -0
- package/dist/execute/index.d.ts +3 -3
- package/dist/execute/index.js +1 -1
- package/dist/execute/outcome.d.ts +4 -25
- package/dist/execute/outcome.js +1 -1
- package/dist/execute/resume.d.ts +16 -0
- package/dist/execute/resume.js +1 -0
- package/dist/execute/run.d.ts +33 -0
- package/dist/execute/run.js +1 -0
- package/dist/execute/steps.d.ts +7 -0
- package/dist/execute/steps.js +1 -0
- package/dist/execute/visit.d.ts +10 -0
- package/dist/execute/visit.js +1 -0
- package/dist/execute/walk.d.ts +4 -0
- package/dist/execute/walk.js +1 -0
- package/dist/index.d.ts +4 -3
- package/dist/index.js +1 -1
- package/dist/node/edge.d.ts +1 -6
- package/dist/node/node.d.ts +1 -7
- package/dist/node/node.js +1 -1
- package/dist/stream/events.d.ts +22 -0
- package/dist/stream/events.js +1 -0
- package/dist/stream/index.d.ts +2 -0
- package/dist/stream/index.js +1 -0
- package/dist/stream/stream.d.ts +9 -0
- package/dist/stream/stream.js +1 -0
- package/dist/types/args.d.ts +2 -15
- package/dist/types/context.d.ts +39 -14
- package/dist/types/edge.d.ts +1 -4
- package/dist/types/event.d.ts +9 -11
- package/dist/types/index.d.ts +1 -1
- package/dist/types/node.d.ts +2 -10
- package/dist/types/result.d.ts +6 -46
- package/dist/types/spec.d.ts +13 -4
- package/dist/types/wait.d.ts +9 -17
- package/dist/types/wait.js +1 -1
- package/docs/nodes.mdx +59 -0
- package/docs/overview.mdx +96 -0
- package/docs/pausing.mdx +109 -0
- package/docs/running.mdx +71 -0
- package/docs/streaming.mdx +62 -0
- package/docs/wiring.mdx +76 -0
- package/package.json +10 -8
- package/dist/compile/validate.d.ts +0 -49
- package/dist/compile/validate.js +0 -1
- package/dist/execute/execute.d.ts +0 -16
- package/dist/execute/execute.js +0 -1
- package/dist/execute/step.d.ts +0 -37
- package/dist/execute/step.js +0 -1
- package/dist/execute/stream.d.ts +0 -25
- package/dist/execute/stream.js +0 -1
package/README.md
CHANGED
|
@@ -26,6 +26,8 @@ const result = await flow.execute({ expression: "12 / 4" });
|
|
|
26
26
|
- **Handlers typed from their args**, the same inference `tools` uses.
|
|
27
27
|
- **A node can stop to ask a person**, and the graph reports the question rather
|
|
28
28
|
than walking past it.
|
|
29
|
+
- **A failed node can route somewhere** — `onFailure` names the node that runs
|
|
30
|
+
instead of the run ending, for a workflow whose earlier steps have to be undone.
|
|
29
31
|
|
|
30
32
|
## Docs
|
|
31
33
|
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{notACeiling as n}from"./errors.js";const r=25;function t(i){if(i===void 0)return r;if(!Number.isFinite(i))throw n(i,r);return Math.max(1,Math.floor(i))}export{r as DEFAULT_MAX_STEPS,t as ceilingFor};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type GraphStream } from "../
|
|
1
|
+
import { type GraphStream } from "../stream/index.js";
|
|
2
2
|
import type { Edge, GraphResult, GraphRunOptions, GraphSpec, Node } from "../types/index.js";
|
|
3
3
|
export interface GraphOptions {
|
|
4
4
|
/** Injectable clock, so tests do not measure real time. */
|
|
@@ -12,18 +12,9 @@ export declare function graph(spec: GraphSpec, opts?: GraphOptions): {
|
|
|
12
12
|
nodes: () => Node<never>[];
|
|
13
13
|
/** Edges leaving a node, in the order they will be tried. */
|
|
14
14
|
edgesFrom: (name: string) => Edge[];
|
|
15
|
-
/**
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
execute: (values?: Record<string, unknown>, opts?: GraphRunOptions) => Promise<GraphResult>;
|
|
20
|
-
/**
|
|
21
|
-
* The same run, narrated. Yields a node's start and end, whatever its nodes
|
|
22
|
-
* emit while running, and the finished result last.
|
|
23
|
-
*
|
|
24
|
-
* The run begins on the call, not on the first read, so `result` is there
|
|
25
|
-
* for the caller who wants the rollout and not the commentary.
|
|
26
|
-
*/
|
|
27
|
-
stream: (values?: Record<string, unknown>, opts?: GraphRunOptions) => GraphStream;
|
|
15
|
+
/** Runs it once. Never throws — the result carries every step taken. */
|
|
16
|
+
execute: (values?: Record<string, unknown>, runOpts?: GraphRunOptions) => Promise<GraphResult>;
|
|
17
|
+
/** The same run, narrated as it goes; `result` is there whether or not anyone reads. */
|
|
18
|
+
stream: (values?: Record<string, unknown>, runOpts?: GraphRunOptions) => GraphStream;
|
|
28
19
|
};
|
|
29
20
|
export type Graph = ReturnType<typeof graph>;
|
package/dist/compile/compile.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{executeGraph as
|
|
1
|
+
import{executeGraph as i}from"../execute/index.js";import{streamGraph as l}from"../stream/index.js";import{ceilingFor as s}from"./ceiling.js";import{assertEveryBranchIsCovered as u}from"./covered.js";import{indexEdges as d}from"./edges.js";import{unknownFailureNode as F}from"./errors.js";import{indexNodes as f}from"./nodes.js";import{assertAllReachable as x}from"./reachable.js";function E(r,m={}){const e=f(r),t=d(r.edges??[],e);if(r.onFailure&&!e.has(r.onFailure))throw F(r.onFailure,[...e.keys()]);x(r.entry,e,t,r.onFailure),u(t);const n={entry:r.entry,maxSteps:s(r.maxSteps),nodes:e,edgesFrom:t,...r.onFailure?{onFailure:r.onFailure}:{},now:m.now??(()=>Date.now())};return{entry:n.entry,maxSteps:n.maxSteps,nodes:()=>[...e.values()],edgesFrom:o=>[...t.get(o)??[]],execute:(o={},a={})=>i(n,o,a),stream:(o={},a={})=>l(n,o,a)}}export{E as graph};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{onlyConditionalEdges as o}from"./errors.js";function f(r){const n=[...r.entries()].filter(([,e])=>e.length>0&&e.every(t=>t.when)).map(([e])=>e);if(n.length>0)throw o(n)}export{f as assertEveryBranchIsCovered};
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { Edge } from "../types/index.js";
|
|
2
|
+
import { type NodesByName } from "./nodes.js";
|
|
3
|
+
export type EdgesFrom = Map<string, Edge[]>;
|
|
4
|
+
/** Edges grouped by the node they leave, in the order they will be tried. */
|
|
5
|
+
export declare function indexEdges(edges: Edge[], nodes: NodesByName): EdgesFrom;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{edgeAfterDefault as i,edgeFromUnknown as a,edgeToUnknown as g}from"./errors.js";import{namesOf as m}from"./nodes.js";function h(s,e){const t=new Map,f=new Map;for(const o of s){if(!e.has(o.from))throw a(o.from,m(e));if(o.to!==null&&!e.has(o.to))throw g(o.from,o.to,m(e));const r=f.get(o.from);if(r)throw i(o.from,r.to,o.to);o.when||f.set(o.from,o);const n=t.get(o.from);n?n.push(o):t.set(o.from,[o])}return t}export{h as indexEdges};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/** A graph refused at compile time: something about its shape cannot run as written. */
|
|
2
|
+
export declare class GraphSpecError extends Error {
|
|
3
|
+
constructor(message: string);
|
|
4
|
+
}
|
|
5
|
+
export declare const noNodes: () => GraphSpecError;
|
|
6
|
+
export declare const duplicateNode: (name: string) => GraphSpecError;
|
|
7
|
+
export declare const unknownEntry: (entry: string, known: string[]) => GraphSpecError;
|
|
8
|
+
export declare const edgeFromUnknown: (from: string, known: string[]) => GraphSpecError;
|
|
9
|
+
export declare const edgeToUnknown: (from: string, to: string, known: string[]) => GraphSpecError;
|
|
10
|
+
export declare const edgeAfterDefault: (from: string, defaultTo: string | null, to: string | null) => GraphSpecError;
|
|
11
|
+
export declare const notACeiling: (maxSteps: number, fallback: number) => GraphSpecError;
|
|
12
|
+
export declare const onlyConditionalEdges: (names: string[]) => GraphSpecError;
|
|
13
|
+
export declare const unreachable: (orphans: string[], entry: string) => GraphSpecError;
|
|
14
|
+
export declare const unknownFailureNode: (name: string, known: string[]) => GraphSpecError;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
class o extends Error{constructor(n){super(n),this.name="GraphSpecError"}}const r=e=>e.map(n=>`"${n}"`).join(", "),a=()=>new o("A graph needs at least one node."),s=e=>new o(`Two nodes are called "${e}".`),i=(e,n)=>new o(`The entry node "${e}" is not in this graph. Known: ${n.join(", ")}.`),d=(e,n)=>new o(`An edge starts at "${e}", which is not a node in this graph. Known: ${n.join(", ")}.`),h=(e,n,t)=>new o(`An edge from "${e}" points at "${n}", which is not a node in this graph. Known: ${t.join(", ")}.`),c=(e,n,t)=>new o(`The edge from "${e}" to "${n??"the end"}" has no condition, so the edge to "${t??"the end"}" declared after it can never be taken. Put the unconditional edge last.`),p=(e,n)=>new o(`maxSteps is ${e}, which is not a ceiling \u2014 a run would never stop at it. Give it a finite number, or leave it out for the default of ${n}.`),w=e=>new o(`${r(e)} ${e.length===1?"has":"have"} only conditional edges, so a run where none of them match stops there and reports success. Add an unconditional edge as the default, or end("${e[0]}") to stop there on purpose.`),l=(e,n)=>new o(`Nothing reaches ${r(e)} from "${n}". Add an edge, or take the node out.`),u=(e,n)=>new o(`onFailure names "${e}", which is not a node in this graph. Known: ${n.join(", ")}.`);export{o as GraphSpecError,s as duplicateNode,c as edgeAfterDefault,d as edgeFromUnknown,h as edgeToUnknown,a as noNodes,p as notACeiling,w as onlyConditionalEdges,i as unknownEntry,u as unknownFailureNode,l as unreachable};
|
package/dist/compile/index.d.ts
CHANGED
package/dist/compile/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{
|
|
1
|
+
import{ceilingFor as p,DEFAULT_MAX_STEPS as e}from"./ceiling.js";import{graph as m}from"./compile.js";import{GraphSpecError as x}from"./errors.js";export{e as DEFAULT_MAX_STEPS,x as GraphSpecError,p as ceilingFor,m as graph};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{duplicateNode as t,noNodes as r,unknownEntry as s}from"./errors.js";const d=n=>[...n.keys()];function f(n){if(n.nodes.length===0)throw r();const o=new Map;for(const e of n.nodes){if(o.has(e.name))throw t(e.name);o.set(e.name,e)}if(!o.has(n.entry))throw s(n.entry,d(o));return o}export{f as indexNodes,d as namesOf};
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { EdgesFrom } from "./edges.js";
|
|
2
|
+
import { type NodesByName } from "./nodes.js";
|
|
3
|
+
/**
|
|
4
|
+
* Refuses a node nothing reaches from the entry, since the usual cause is a typo in an edge.
|
|
5
|
+
*
|
|
6
|
+
* A failure node is a second root: nothing points an edge at it, and the walk
|
|
7
|
+
* still reaches it from anywhere that falls over.
|
|
8
|
+
*/
|
|
9
|
+
export declare function assertAllReachable(entry: string, nodes: NodesByName, edgesFrom: EdgesFrom, onFailure?: string): void;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{unreachable as l}from"./errors.js";import{namesOf as i}from"./nodes.js";function d(e,h,c,a){const f=a?[e,a]:[e],r=new Set(f),s=[...f];for(let o=0;o<s.length;o++)for(const t of c.get(s[o])??[])t.to&&!r.has(t.to)&&(r.add(t.to),s.push(t.to));const n=i(h).filter(o=>!r.has(o));if(n.length)throw l(n,e)}export{d as assertAllReachable};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
function a(t,o){return{values:t.values,outputs:t.outputs,from:t.cameFrom,failure:t.failure,steps:[...t.steps],emit:e=>t.narrate({type:"token",node:o,text:e}),note:e=>t.narrate({type:"note",node:o,data:e})}}export{a as contextFor};
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export declare const nowhereToStart: (start: string, known: string[]) => string;
|
|
2
|
+
export declare const hitTheCeiling: (maxSteps: number, at: string) => string;
|
|
3
|
+
export declare const inputThrew: (node: string, why: string) => string;
|
|
4
|
+
export declare const givenBadArgs: (node: string, why: string) => string;
|
|
5
|
+
export declare const nodeFailed: (node: string, why: string) => string;
|
|
6
|
+
export declare const choosingThrew: (node: string, why: string) => string;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
const n=(e,t)=>`No node is called "${e}", so there is nowhere to start. \`entry\` names where a resumed run picks up. Known: ${t.join(", ")}.`,o=(e,t)=>`Ran ${e} nodes without reaching an end, stopping at "${t}". Raising the ceiling is the last thing to try: a cycle that never exits is what it is here to stop. Otherwise a conditional edge never turns false, or the graph is genuinely longer than ${e} nodes.`,i=(e,t)=>`Building the input for "${e}" threw: ${t}`,r=(e,t)=>`"${e}" was given ${t}`,s=(e,t)=>`"${e}" failed: ${t}`,h=(e,t)=>`Choosing what runs after "${e}" threw: ${t}`;export{h as choosingThrew,r as givenBadArgs,o as hitTheCeiling,i as inputThrew,s as nodeFailed,n as nowhereToStart};
|
package/dist/execute/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { executeGraph } from "./execute.js";
|
|
2
1
|
export type { NodeOutcome } from "./outcome.js";
|
|
3
|
-
export
|
|
4
|
-
export {
|
|
2
|
+
export { resumingAt } from "./resume.js";
|
|
3
|
+
export type { Compiled, Narrator } from "./run.js";
|
|
4
|
+
export { executeGraph } from "./walk.js";
|
package/dist/execute/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{
|
|
1
|
+
import{resumingAt as o}from"./resume.js";import{executeGraph as m}from"./walk.js";export{m as executeGraph,o as resumingAt};
|
|
@@ -1,13 +1,4 @@
|
|
|
1
1
|
import type { RolloutStep } from "@spendgraph/sdk";
|
|
2
|
-
/**
|
|
3
|
-
* What a node returned, when it returned the shape a model call produces.
|
|
4
|
-
*
|
|
5
|
-
* Recognised structurally rather than declared: a node may return anything, and
|
|
6
|
-
* a plain object is the common case. But a node that called a model, ran a
|
|
7
|
-
* prompt or nested another workflow returns tokens and sometimes its own steps,
|
|
8
|
-
* and both used to be flattened into the output string and lost — which made a
|
|
9
|
-
* graph run report as costing nothing.
|
|
10
|
-
*/
|
|
11
2
|
export interface NodeOutcome {
|
|
12
3
|
output?: string;
|
|
13
4
|
model?: string;
|
|
@@ -17,26 +8,14 @@ export interface NodeOutcome {
|
|
|
17
8
|
outputTokens?: number;
|
|
18
9
|
cacheReadTokens?: number;
|
|
19
10
|
cacheWriteTokens?: number;
|
|
20
|
-
|
|
11
|
+
citationTokens?: number;
|
|
12
|
+
reasoningTokens?: number;
|
|
21
13
|
steps?: RolloutStep[];
|
|
22
14
|
}
|
|
23
|
-
/** The text a step should carry: an outcome's own output, or the whole value. */
|
|
24
15
|
export declare function outputOf(value: unknown): string;
|
|
25
|
-
|
|
26
|
-
* The token counts a node reported, or nothing when it reported none.
|
|
27
|
-
*
|
|
28
|
-
* A node that also returns nested steps reports none here: its own totals are
|
|
29
|
-
* the sum of those steps, and counting both makes a graph inside a graph cost
|
|
30
|
-
* exactly twice what it did.
|
|
31
|
-
*/
|
|
32
|
-
export declare function tokensOf(value: unknown): Pick<RolloutStep, "inputTokens" | "outputTokens"> & {
|
|
16
|
+
export declare function tokensOf(value: unknown): Pick<RolloutStep, "inputTokens" | "outputTokens" | "cacheReadTokens" | "cacheWriteTokens" | "citationTokens" | "reasoningTokens"> & {
|
|
33
17
|
model?: string;
|
|
34
18
|
};
|
|
35
|
-
/**
|
|
36
|
-
* A nested run's steps, renumbered to sit in this run's sequence.
|
|
37
|
-
*
|
|
38
|
-
* Prefixed with the node that produced them, so a step from a refine loop
|
|
39
|
-
* inside a router still says which branch it came from.
|
|
40
|
-
*/
|
|
19
|
+
/** A nested run's steps, renumbered into this run and named for the node they came from. */
|
|
41
20
|
export declare function nestedSteps(value: unknown, from: number, source: string): RolloutStep[];
|
|
42
21
|
export { totalTokens } from "@spendgraph/sdk";
|
package/dist/execute/outcome.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{stringify as
|
|
1
|
+
import{sealed as u,stringify as f}from"./steps.js";function r(t){if(!t||typeof t!="object"||Array.isArray(t))return!1;const n=t;return typeof n.output=="string"||typeof n.inputTokens=="number"||typeof n.outputTokens=="number"||Array.isArray(n.steps)}function k(t){return r(t)&&typeof t.output=="string"?t.output:f(t)}function s(t){if(!(typeof t!="number"||!Number.isFinite(t)||t<0))return Math.trunc(t)}const p=["cacheReadTokens","cacheWriteTokens","citationTokens","reasoningTokens"];function c(t){const n={};for(const o of p)t[o]!==void 0&&(n[o]=s(t[o])??0);return n}function m(t){if(!r(t))return{};const n=Array.isArray(t.steps)&&t.steps.length>0,o=n?void 0:s(t.inputTokens),e=n?void 0:s(t.outputTokens);return{...o===void 0?{}:{inputTokens:o},...e===void 0?{}:{outputTokens:e},...n?{}:c(t),...t.model?{model:t.model}:{}}}const d=t=>!!t&&typeof t=="object"&&!Array.isArray(t);function A(t,n,o){return!r(t)||!Array.isArray(t.steps)?[]:t.steps.filter(d).map((e,i)=>u({...e,index:n+i,source:`${o}.${e.source??"step"}`}))}import{totalTokens as g}from"@spendgraph/sdk";export{A as nestedSteps,k as outputOf,m as tokensOf,g as totalTokens};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { GraphResult, GraphRunOptions } from "../types/index.js";
|
|
2
|
+
/**
|
|
3
|
+
* How to re-enter a graph at the node that stopped to ask.
|
|
4
|
+
*
|
|
5
|
+
* The steps it hands back stop short of that node: a nested run resumed by its
|
|
6
|
+
* own id comes back carrying its whole history, the half before the pause
|
|
7
|
+
* included, so seeding that half again would record it twice. What the outer
|
|
8
|
+
* run owns is everything before the node; what the node did is the node's to
|
|
9
|
+
* report.
|
|
10
|
+
*
|
|
11
|
+
* The **last** time the node ran, not the first. A cycle visits it more than
|
|
12
|
+
* once, and a run that went round three times before stopping paused on the
|
|
13
|
+
* third — cutting at the first would throw away every round it had already
|
|
14
|
+
* paid for.
|
|
15
|
+
*/
|
|
16
|
+
export declare function resumingAt(result: GraphResult, node: string): GraphRunOptions;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
function n(t,e){for(let s=t.steps.length-1;s>=0;s--)if(t.steps[s].source===e)return s;return-1}function p(t,e){const s=n(t,e);return{entry:e,steps:s<0?t.steps:t.steps.slice(0,s),outputs:t.outputs}}export{p as resumingAt};
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { RolloutStep } from "@spendgraph/sdk";
|
|
2
|
+
import type { Edge, GraphEvent, GraphResult, GraphRunOptions, Node, Paused } from "../types/index.js";
|
|
3
|
+
export type Narrator = (event: GraphEvent) => void;
|
|
4
|
+
/** A graph as compiled: what `execute` and `stream` walk. */
|
|
5
|
+
export interface Compiled {
|
|
6
|
+
entry: string;
|
|
7
|
+
maxSteps: number;
|
|
8
|
+
nodes: Map<string, Node<never>>;
|
|
9
|
+
edgesFrom: Map<string, Edge[]>;
|
|
10
|
+
onFailure?: string;
|
|
11
|
+
now: () => number;
|
|
12
|
+
}
|
|
13
|
+
export interface Run {
|
|
14
|
+
compiled: Compiled;
|
|
15
|
+
values: Record<string, unknown>;
|
|
16
|
+
outputs: Record<string, unknown>;
|
|
17
|
+
steps: RolloutStep[];
|
|
18
|
+
visited: number;
|
|
19
|
+
/** The node the walk arrived from, kept on the run so every context reads the same one. */
|
|
20
|
+
cameFrom: string | null;
|
|
21
|
+
/** What sent the walk to the failure node, or null while nothing has fallen over. */
|
|
22
|
+
failure: {
|
|
23
|
+
node: string;
|
|
24
|
+
error: string;
|
|
25
|
+
} | null;
|
|
26
|
+
lastOutput: string;
|
|
27
|
+
startedAt: number;
|
|
28
|
+
narrate: Narrator;
|
|
29
|
+
}
|
|
30
|
+
export declare function beginRun(compiled: Compiled, values: Record<string, unknown>, opts: GraphRunOptions, narrate: Narrator): Run;
|
|
31
|
+
export declare function finished(run: Run, status: "completed" | "failed", error?: string): GraphResult;
|
|
32
|
+
/** A run stopped at a node that asked something: said once for the stream, once on the result. */
|
|
33
|
+
export declare function parked(run: Run, asked: Paused, node: string, index: number): GraphResult;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{totalTokens as p}from"./outcome.js";import{sealed as i}from"./steps.js";function l(t,n,e,o){const u=(e.steps??[]).map(i);return{compiled:t,values:n,outputs:{...e.outputs},steps:u,visited:0,cameFrom:null,failure:null,lastOutput:"",startedAt:t.now(),narrate:o}}function r(t,n,e){return{status:n,output:n==="completed"?t.lastOutput:"",error:e,steps:t.steps,outputs:t.outputs,latencyMs:t.compiled.now()-t.startedAt,...p(t.steps)}}function d(t,n,e,o){return t.narrate({type:"paused",node:e,index:o,waitingOn:n.waitingOn,...n.runId?{runId:n.runId}:{}}),{...r(t,"completed"),waitingOn:n.waitingOn,...n.runId?{waitingFor:n.runId}:{}}}export{l as beginRun,r as finished,d as parked};
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { RolloutStep } from "@spendgraph/sdk";
|
|
2
|
+
/** A string is itself, anything else is JSON; what cannot be written says so rather than reading as nothing. */
|
|
3
|
+
export declare function stringify(value: unknown): string;
|
|
4
|
+
export declare function reasonOf(err: unknown): string;
|
|
5
|
+
export declare function failedStep(index: number, source: string, input: Record<string, unknown>, latencyMs: number, error: string): RolloutStep;
|
|
6
|
+
/** Frozen, so a node handed the record cannot rewrite an earlier step's output or cost. */
|
|
7
|
+
export declare function sealed(step: RolloutStep): RolloutStep;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
function f(t){if(t==null)return"";if(typeof t=="string")return t;try{const n=JSON.stringify(t);return n===void 0?`[not recordable: ${typeof t}]`:n}catch(n){return`[not recordable: ${i(n)}]`}}function i(t){return t instanceof Error?t.message:String(t)}function u(t,n,r,e,o){return{index:t,source:n,input:r,output:"",status:"failed",error:o,latencyMs:e}}function c(t){return Object.freeze(t)}export{u as failedStep,i as reasonOf,c as sealed,f as stringify};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { Node } from "../types/index.js";
|
|
2
|
+
import type { Run } from "./run.js";
|
|
3
|
+
export type Visited = {
|
|
4
|
+
value: unknown;
|
|
5
|
+
} | {
|
|
6
|
+
error: string;
|
|
7
|
+
why: string;
|
|
8
|
+
};
|
|
9
|
+
/** Runs one node: builds its input, checks its args, runs it, and records a step whichever way it went. */
|
|
10
|
+
export declare function visit(run: Run, node: Node<never>): Promise<Visited>;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{FieldValidationError as l,validateFields as m}from"@spendgraph/sdk";import{contextFor as f}from"./context.js";import{givenBadArgs as g,inputThrew as x,nodeFailed as h}from"./errors.js";import{nestedSteps as y,outputOf as O,tokensOf as A}from"./outcome.js";import{failedStep as F,reasonOf as u,sealed as d}from"./steps.js";function M(e,r){if(!e.args?.length)return null;const t=m(r,e.args);return t.length>0?new l(t).message:null}const c=e=>e.run.compiled.now()-e.startedAt;function p(e,r,t,s){const{run:n,node:o,index:a}=e,i=c(e);return n.steps.push(d(F(a,o.name,r,i,t))),n.narrate({type:"node_end",node:o.name,index:a,status:"failed",error:t,latencyMs:i}),{error:s(o.name,t),why:t}}function _(e,r,t){const{run:s,node:n,index:o}=e,a=c(e);s.outputs[n.name]=t,s.lastOutput=O(t),s.steps.push(d({index:o,source:n.name,input:r,output:s.lastOutput,status:"completed",latencyMs:a,...A(t)}));for(const i of y(t,s.steps.length,n.name))s.steps.push(i);return s.narrate({type:"node_end",node:n.name,index:o,status:"completed",output:t,latencyMs:a}),{value:t}}async function B(e,r){e.visited+=1;const t={run:e,node:r,index:e.steps.length,startedAt:e.compiled.now()},s=f(e,r.name);let n;try{n=r.input?r.input(s):e.values}catch(a){return p(t,{},u(a),x)}e.narrate({type:"node_start",node:r.name,index:t.index,input:n});const o=M(r,n);if(o)return p(t,n,o,g);try{return _(t,n,await r.run(n,s))}catch(a){return p(t,n,u(a),h)}}export{B as visit};
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { GraphResult, GraphRunOptions } from "../types/index.js";
|
|
2
|
+
import { type Compiled, type Narrator } from "./run.js";
|
|
3
|
+
/** Walks a compiled graph. Never throws: every exit is a result carrying the steps taken. */
|
|
4
|
+
export declare function executeGraph(compiled: Compiled, values: Record<string, unknown>, opts?: GraphRunOptions, narrate?: Narrator): Promise<GraphResult>;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{pausedInside as x}from"../types/index.js";import{contextFor as g}from"./context.js";import{choosingThrew as p,hitTheCeiling as w,nowhereToStart as y}from"./errors.js";import{beginRun as F,finished as a,parked as v}from"./run.js";import{reasonOf as k}from"./steps.js";import{visit as S}from"./visit.js";function T(e,t){let n;for(const o of e)if(!o.when||(n??=t(),o.when(n)))return o.to;return null}function A(e,t){try{const n=e.compiled.edgesFrom.get(t.name)??[];return{next:T(n,()=>g(e,t.name))}}catch(n){return{error:p(t.name,k(n))}}}async function j(e,t,n={},o=()=>{}){const r=F(e,t,n,o),u=n.entry??e.entry;if(!e.nodes.has(u))return a(r,"failed",y(u,[...e.nodes.keys()]));let i=u,c=null;for(;i;){if(r.visited>=e.maxSteps)return a(r,"failed",w(e.maxSteps,i));const s=e.nodes.get(i),l=r.steps.length;r.cameFrom=c;const f=await S(r,s);if("error"in f){const h=e.onFailure;if(!h||s.name===h)return a(r,"failed",f.error);r.failure={node:s.name,error:f.why},c=s.name,i=h;continue}const d=x(f.value);if(d)return v(r,d,s.name,l);const m=A(r,s);if("error"in m)return a(r,"failed",m.error);c=i,i=m.next}return a(r,"completed")}export{j as executeGraph};
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
export { type Graph, type GraphOptions, graph } from "./compile/index.js";
|
|
2
|
-
export
|
|
1
|
+
export { type Graph, type GraphOptions, GraphSpecError, graph } from "./compile/index.js";
|
|
2
|
+
export { resumingAt } from "./execute/index.js";
|
|
3
3
|
export type { NodeSpec } from "./node/index.js";
|
|
4
4
|
export { edge, end, node } from "./node/index.js";
|
|
5
|
-
export type {
|
|
5
|
+
export type { GraphStream } from "./stream/index.js";
|
|
6
|
+
export type { ArgSpec, ArgsOf, ArgValue, Edge, Emit, GraphContext, GraphEvent, GraphResult, GraphRunOptions, GraphSpec, Node, Note, Paused, Wait, } from "./types/index.js";
|
|
6
7
|
export { pausedInside } from "./types/index.js";
|
package/dist/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{graph as
|
|
1
|
+
import{GraphSpecError as o,graph as p}from"./compile/index.js";import{resumingAt as m}from"./execute/index.js";import{edge as f,end as n,node as x}from"./node/index.js";import{pausedInside as g}from"./types/index.js";export{o as GraphSpecError,f as edge,n as end,p as graph,x as node,g as pausedInside,m as resumingAt};
|
package/dist/node/edge.d.ts
CHANGED
|
@@ -1,10 +1,5 @@
|
|
|
1
1
|
import type { Edge } from "../types/index.js";
|
|
2
|
-
/**
|
|
3
|
-
* An edge from one node to the next, or to the end of the run.
|
|
4
|
-
*
|
|
5
|
-
* A helper so the argument order is fixed by something other than memory:
|
|
6
|
-
* `edge("a", "b")` reads in the direction the run travels.
|
|
7
|
-
*/
|
|
2
|
+
/** An edge from one node to the next, or to the end of the run. */
|
|
8
3
|
export declare function edge(from: string, to: string | null, when?: Edge["when"]): Edge;
|
|
9
4
|
/** Ends the run after `from`. The same as `edge(from, null)`, said out loud. */
|
|
10
5
|
export declare function end(from: string, when?: Edge["when"]): Edge;
|
package/dist/node/node.d.ts
CHANGED
|
@@ -5,11 +5,5 @@ export interface NodeSpec<T extends readonly ArgSpec[]> {
|
|
|
5
5
|
input?: (ctx: GraphContext) => Record<string, unknown>;
|
|
6
6
|
run(input: ArgsOf<T>, ctx: GraphContext): Promise<unknown> | unknown;
|
|
7
7
|
}
|
|
8
|
-
/**
|
|
9
|
-
* Declares a node, checking what a graph cannot check later.
|
|
10
|
-
*
|
|
11
|
-
* The same argument as `tool()`: a duplicated argument silently loses one, and a
|
|
12
|
-
* hyphenated name reads badly in the step record that is the only trace of a
|
|
13
|
-
* failed run.
|
|
14
|
-
*/
|
|
8
|
+
/** Declares a node, checking what a graph cannot check later. */
|
|
15
9
|
export declare function node<const T extends readonly ArgSpec[]>(spec: NodeSpec<T>): Node<ArgsOf<T>>;
|
package/dist/node/node.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
const o=/^[A-Za-z_][A-Za-z0-9_]*$/;function
|
|
1
|
+
const o=/^[A-Za-z_][A-Za-z0-9_]*$/;function a(e){if(!o.test(e.name))throw new Error(`Node name "${e.name}" must be letters, digits and underscores, starting with a letter.`);const t=new Set;for(const n of e.args??[]){if(!o.test(n.name))throw new Error(`Node "${e.name}" argument "${n.name}" is not a usable name.`);if(t.has(n.name))throw new Error(`Node "${e.name}" declares "${n.name}" twice.`);if(t.add(n.name),n.type==="enum"&&!n.options?.length)throw new Error(`Node "${e.name}" argument "${n.name}" is an enum with no options, so nothing can satisfy it.`)}return{name:e.name,args:e.args?.map(n=>({...n,options:n.options?[...n.options]:void 0})),input:e.input,run:e.run}}export{a as node};
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { GraphEvent } from "../types/index.js";
|
|
2
|
+
/** Events queued between the run and its one reader; a run never waits on a slow reader. */
|
|
3
|
+
export declare class Events {
|
|
4
|
+
private queued;
|
|
5
|
+
private head;
|
|
6
|
+
private wake;
|
|
7
|
+
private closed;
|
|
8
|
+
private draining;
|
|
9
|
+
push(event: GraphEvent): void;
|
|
10
|
+
close(): void;
|
|
11
|
+
private release;
|
|
12
|
+
private takeNext;
|
|
13
|
+
/**
|
|
14
|
+
* The events, once.
|
|
15
|
+
*
|
|
16
|
+
* A second reader is refused rather than served: both take from the same
|
|
17
|
+
* queue, so each would see roughly half the run — which reads as a graph that
|
|
18
|
+
* dropped tokens rather than as the mistake it is. Two watchers want one
|
|
19
|
+
* reader writing into something they can both read, not two readers.
|
|
20
|
+
*/
|
|
21
|
+
drain(): AsyncGenerator<GraphEvent>;
|
|
22
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
class s{queued=[];head=0;wake=null;closed=!1;draining=!1;push(e){this.queued.push(e),this.release()}close(){this.closed=!0,this.release()}release(){const e=this.wake;this.wake=null,e?.()}takeNext(){const e=this.queued[this.head++];return this.head===this.queued.length&&(this.queued=[],this.head=0),e}async*drain(){if(this.draining)throw new Error("This stream is already being read. Iterate it once, or await `result` instead \u2014 to show a run to more than one watcher, keep the events somewhere they can both read.");for(this.draining=!0;;){for(;this.head<this.queued.length;)yield this.takeNext();if(this.closed)return;await new Promise(e=>{this.wake=e})}}}export{s as Events};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{streamGraph as e}from"./stream.js";export{e as streamGraph};
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { type Compiled } from "../execute/index.js";
|
|
2
|
+
import type { GraphEvent, GraphResult, GraphRunOptions } from "../types/index.js";
|
|
3
|
+
/** A run as it happens, and the same run once it is over. Iterable once. */
|
|
4
|
+
export interface GraphStream extends AsyncIterable<GraphEvent> {
|
|
5
|
+
/** Resolves whether or not anyone iterated. */
|
|
6
|
+
result: Promise<GraphResult>;
|
|
7
|
+
}
|
|
8
|
+
/** Runs a compiled graph, narrating it; the run starts on the call, not on the first read. */
|
|
9
|
+
export declare function streamGraph(compiled: Compiled, values: Record<string, unknown>, opts?: GraphRunOptions): GraphStream;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{executeGraph as c}from"../execute/index.js";import{Events as u}from"./events.js";function l(n,o,s={}){const t=new u,r=(async()=>{try{const e=await c(n,o,s,a=>t.push(a));return t.push({type:"result",result:e}),e}finally{t.close()}})();return r.catch(()=>{}),{result:r,[Symbol.asyncIterator]:()=>t.drain()}}export{l as streamGraph};
|
package/dist/types/args.d.ts
CHANGED
|
@@ -1,12 +1,5 @@
|
|
|
1
1
|
import type { FieldType } from "@spendgraph/sdk";
|
|
2
|
-
/**
|
|
3
|
-
* A declared argument, as written.
|
|
4
|
-
*
|
|
5
|
-
* Structurally a `FieldSpec` with every field readonly, because that is what an
|
|
6
|
-
* `as const` array produces — and `FieldSpec.options` being a mutable `string[]`
|
|
7
|
-
* is enough to make the whole tuple fail the constraint and inference fall back
|
|
8
|
-
* to nothing, silently.
|
|
9
|
-
*/
|
|
2
|
+
/** A declared argument as written: a readonly `FieldSpec`, which is what an `as const` array produces. */
|
|
10
3
|
export interface ArgSpec {
|
|
11
4
|
readonly name: string;
|
|
12
5
|
readonly type: FieldType;
|
|
@@ -41,13 +34,7 @@ type Required<T> = T extends {
|
|
|
41
34
|
type Optional<T> = T extends {
|
|
42
35
|
required: true;
|
|
43
36
|
} ? never : T;
|
|
44
|
-
/**
|
|
45
|
-
* The object `run` is handed, derived from the args you declared.
|
|
46
|
-
*
|
|
47
|
-
* Declare `args` with `as const` and the handler's parameter types follow, so a
|
|
48
|
-
* renamed argument is a compile error rather than an `undefined` discovered
|
|
49
|
-
* halfway through a graph.
|
|
50
|
-
*/
|
|
37
|
+
/** The object `run` is handed, derived from the args declared `as const`. */
|
|
51
38
|
export type ArgsOf<T extends readonly ArgSpec[]> = {
|
|
52
39
|
[F in Required<T[number]> as F["name"]]: ArgValue<F>;
|
|
53
40
|
} & {
|
package/dist/types/context.d.ts
CHANGED
|
@@ -1,25 +1,50 @@
|
|
|
1
1
|
import type { RolloutStep } from "@spendgraph/sdk";
|
|
2
|
-
import type { Emit } from "./event.js";
|
|
3
|
-
/**
|
|
4
|
-
* What a node can see while it decides and while it runs.
|
|
5
|
-
*
|
|
6
|
-
* `outputs` is the only channel between nodes. A shared mutable bag would let
|
|
7
|
-
* node four depend on a key node two happens to set — a dependency the graph
|
|
8
|
-
* does not declare and compilation cannot check.
|
|
9
|
-
*/
|
|
2
|
+
import type { Emit, Note } from "./event.js";
|
|
3
|
+
/** What a node can see while it decides and while it runs; `outputs` is the only channel between nodes. */
|
|
10
4
|
export interface GraphContext {
|
|
11
5
|
/** The object `execute` was called with. */
|
|
12
6
|
values: Record<string, unknown>;
|
|
13
|
-
/**
|
|
7
|
+
/**
|
|
8
|
+
* What each node that has finished returned, by name.
|
|
9
|
+
*
|
|
10
|
+
* By name, so a node a cycle comes back to overwrites what it returned last
|
|
11
|
+
* time — a retry reads the attempt it just made, not the first one. Keeping
|
|
12
|
+
* every pass would make `outputs` a different shape for a cyclic graph than a
|
|
13
|
+
* straight one, and every reader would owe a check it usually does not need.
|
|
14
|
+
*/
|
|
14
15
|
outputs: Record<string, unknown>;
|
|
16
|
+
/**
|
|
17
|
+
* The node that routed here, or null on the entry node.
|
|
18
|
+
*
|
|
19
|
+
* What a node reached from more than one place otherwise has to guess at, by
|
|
20
|
+
* probing `outputs` for whichever branch's node happens to have run. That
|
|
21
|
+
* guess is wrong in a graph with a cycle: `outputs` keeps the last value a
|
|
22
|
+
* node returned for the rest of the run, so a join reached a second time
|
|
23
|
+
* reads a branch it did not come through this pass.
|
|
24
|
+
*/
|
|
25
|
+
from: string | null;
|
|
26
|
+
/**
|
|
27
|
+
* What failed on the way here, for a node reached by `onFailure`.
|
|
28
|
+
*
|
|
29
|
+
* Null everywhere else, and on the failure node itself when the run reached
|
|
30
|
+
* it through an ordinary edge — which is how a compensating node tells "I am
|
|
31
|
+
* cleaning up after a crash" from "I was walked through in the normal way".
|
|
32
|
+
*/
|
|
33
|
+
failure: {
|
|
34
|
+
node: string;
|
|
35
|
+
error: string;
|
|
36
|
+
} | null;
|
|
15
37
|
/** The steps recorded so far, in order. A copy; writing to it does nothing. */
|
|
16
38
|
steps: RolloutStep[];
|
|
39
|
+
/** Sends a token out of the run, tagged with this node. A no-op unless someone is streaming. */
|
|
40
|
+
emit: Emit;
|
|
17
41
|
/**
|
|
18
|
-
* Sends
|
|
42
|
+
* Sends structured progress out of the run, tagged with this node.
|
|
19
43
|
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
44
|
+
* `emit` is for what the model is saying; this is for what the run is doing —
|
|
45
|
+
* which step of how many, what it found, how far along. A node reaches it
|
|
46
|
+
* through the context rather than closing over the caller's, so a step defined
|
|
47
|
+
* in another module can report progress without being handed a channel.
|
|
23
48
|
*/
|
|
24
|
-
|
|
49
|
+
note: Note;
|
|
25
50
|
}
|
package/dist/types/edge.d.ts
CHANGED
|
@@ -4,9 +4,6 @@ export interface Edge {
|
|
|
4
4
|
from: string;
|
|
5
5
|
/** The next node, or null to end the run here. */
|
|
6
6
|
to: string | null;
|
|
7
|
-
/**
|
|
8
|
-
* Taken only when true. Tried in declaration order, first match wins — so an
|
|
9
|
-
* unconditional edge is the default branch and belongs last.
|
|
10
|
-
*/
|
|
7
|
+
/** Taken only when true. */
|
|
11
8
|
when?: (ctx: GraphContext) => boolean;
|
|
12
9
|
}
|
package/dist/types/event.d.ts
CHANGED
|
@@ -1,12 +1,6 @@
|
|
|
1
1
|
import type { GraphResult } from "./result.js";
|
|
2
2
|
import type { Wait } from "./wait.js";
|
|
3
|
-
/**
|
|
4
|
-
* What a run says about itself while it is still running.
|
|
5
|
-
*
|
|
6
|
-
* Every event names the node it came from, because a stream carrying tokens
|
|
7
|
-
* from four nodes is unreadable without it — and which nodes are worth showing
|
|
8
|
-
* is the consumer's decision, not the graph's.
|
|
9
|
-
*/
|
|
3
|
+
/** What a run says about itself while it is still running; every event names its node. */
|
|
10
4
|
export type GraphEvent = {
|
|
11
5
|
type: "node_start";
|
|
12
6
|
node: string;
|
|
@@ -17,6 +11,11 @@ export type GraphEvent = {
|
|
|
17
11
|
type: "token";
|
|
18
12
|
node: string;
|
|
19
13
|
text: string;
|
|
14
|
+
} | {
|
|
15
|
+
/** Whatever a node chose to say about its own progress, as data. */
|
|
16
|
+
type: "note";
|
|
17
|
+
node: string;
|
|
18
|
+
data: unknown;
|
|
20
19
|
} | {
|
|
21
20
|
type: "node_end";
|
|
22
21
|
node: string;
|
|
@@ -26,10 +25,7 @@ export type GraphEvent = {
|
|
|
26
25
|
error?: string;
|
|
27
26
|
latencyMs: number;
|
|
28
27
|
} | {
|
|
29
|
-
/**
|
|
30
|
-
* A node returned a run that stopped to ask. The walk stops here, so
|
|
31
|
-
* nothing downstream runs against an answer nobody has given yet.
|
|
32
|
-
*/
|
|
28
|
+
/** A node returned a run that stopped to ask; the walk stops here. */
|
|
33
29
|
type: "paused";
|
|
34
30
|
node: string;
|
|
35
31
|
index: number;
|
|
@@ -42,3 +38,5 @@ export type GraphEvent = {
|
|
|
42
38
|
};
|
|
43
39
|
/** Where a node's tokens go. A run that nobody is streaming drops them. */
|
|
44
40
|
export type Emit = (text: string) => void;
|
|
41
|
+
/** Where a node's structured progress goes. Dropped the same way when nobody is listening. */
|
|
42
|
+
export type Note = (data: unknown) => void;
|
package/dist/types/index.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
export type { ArgSpec, ArgsOf, ArgValue } from "./args.js";
|
|
2
2
|
export type { GraphContext } from "./context.js";
|
|
3
3
|
export type { Edge } from "./edge.js";
|
|
4
|
-
export type { Emit, GraphEvent } from "./event.js";
|
|
4
|
+
export type { Emit, GraphEvent, Note } from "./event.js";
|
|
5
5
|
export type { Node } from "./node.js";
|
|
6
6
|
export type { GraphResult, GraphRunOptions } from "./result.js";
|
|
7
7
|
export type { GraphSpec } from "./spec.js";
|
package/dist/types/node.d.ts
CHANGED
|
@@ -1,18 +1,10 @@
|
|
|
1
1
|
import type { FieldSpec } from "@spendgraph/sdk";
|
|
2
2
|
import type { GraphContext } from "./context.js";
|
|
3
|
-
/**
|
|
4
|
-
* One unit of work in a graph.
|
|
5
|
-
*
|
|
6
|
-
* `input` is what makes a node reusable: the node says what it needs, the graph
|
|
7
|
-
* says where it comes from. Without it every node has to know the whole run.
|
|
8
|
-
*/
|
|
3
|
+
/** One unit of work in a graph. */
|
|
9
4
|
export interface Node<Args = Record<string, unknown>> {
|
|
10
5
|
/** Unique in the graph, and the key its output is stored under. */
|
|
11
6
|
name: string;
|
|
12
|
-
/**
|
|
13
|
-
* What this node needs, validated before `run` does any work. Halfway through
|
|
14
|
-
* a graph is the most expensive place to discover a typo.
|
|
15
|
-
*/
|
|
7
|
+
/** What this node needs, validated before `run` does any work. */
|
|
16
8
|
args?: FieldSpec[];
|
|
17
9
|
/** The edge's data half: an edge says what runs next, this says on what. */
|
|
18
10
|
input?: (ctx: GraphContext) => Record<string, unknown>;
|