Skip to content

Tutorial: Typed chain

Wires are not only “text.” A node can declare input / output as text, int, float, bool, or json. threadle checks that the payload parses before the next node runs, so a bad number fails loudly instead of poisoning the rest of the graph.

This tutorial builds two small demos from the repo examples.


Text stats emits a JSON object. Sum numbers walks that JSON and returns a float total.

~/.config/threadle/nodes/char-stats/node.json
{
"id": "char-stats",
"label": "Text stats",
"glyph": "∑",
"description": "chars, words and lines of the input, as JSON",
"entry": "node.ts",
"input": "text",
"output": "json"
}
node.ts
export default class TextStats {
async run(input: string): Promise<string> {
return JSON.stringify(
{
chars: input.length,
words: (input.trim().match(/\S+/g) ?? []).length,
lines: input === "" ? 0 : input.split("\n").length,
},
null,
2,
);
}
}
~/.config/threadle/nodes/sum-numbers/node.json
{
"id": "sum-numbers",
"label": "Sum numbers",
"glyph": "+",
"description": "sums every number found in a JSON value",
"entry": "node.ts",
"input": "json",
"output": "float"
}
node.ts
export default class SumNumbers {
async run(input: string): Promise<string> {
let total = 0;
const walk = (v: unknown): void => {
if (typeof v === "number") total += v;
else if (Array.isArray(v)) v.forEach(walk);
else if (v && typeof v === "object") Object.values(v).forEach(walk);
};
walk(JSON.parse(input));
return String(total);
}
}
prompt → Text stats → ◇ Data(json) → Sum numbers → output

(You can omit ◇ Data when Text stats already declares output: "json". The type gate accepts a direct wire. Data is useful when an upstream agent emits prose JSON you still want to coerce.)

Prompt:

one two
three

Expected JSON from Text stats (shape):

{ "chars": 14, "words": 3, "lines": 2 }

Sum numbers walks every number → output roughly 19 (exact chars depend on newlines).

If you wire a plain text prompt directly into Sum numbers, the run fails at the type gate. json expected. Instead of throwing inside JSON.parse mid-pipeline.


~/.config/threadle/nodes/fibonacci/node.json
{
"id": "fibonacci",
"label": "Fibonacci",
"glyph": "φ",
"description": "first N Fibonacci numbers",
"entry": "node.ts",
"input": "int",
"output": "text"
}
node.ts
export default class Fibonacci {
async run(input: string): Promise<string> {
const n = Math.min(Number.parseInt(input.trim(), 10), 90);
const seq: bigint[] = [];
let [a, b] = [0n, 1n];
for (let i = 0; i < n; i++) {
seq.push(a);
[a, b] = [b, a + b];
}
return seq.join(", ");
}
}

Wire an int-typed prompt (inspector → value type int). Or ◇ Data(int). Into Fibonacci:

prompt(int: 8) → Fibonacci → output
◇ Data(int: 8) → Fibonacci → output

→ 0, 1, 1, 2, 3, 5, 8, 13

A text prompt containing eight will not satisfy int and the edge or run will refuse.


From → ToAllowed?
same typeyes
anything → textyes
int → floatyes
float → intno
text → jsononly if the string parses as JSON

Named ports use the same types per port. See Params & named ports.


If you have the threadle source tree:

Settings → custom nodes → ⇣ Import → pick examples/nodes/char-stats (and siblings), or copy the folders into ~/.config/threadle/nodes/.