Node
A node is the building block of the graph. It starts with a message to be sent to the user (action), then either it auto-advances to the next node, or waits for user input which can be validated and used to update the state before advancing to the next node.
Structure
Every node has four key properties:
id: A unique identifier for the nodeaction: What the node does (sends a message, updates state) — see Actionvalidate(optional): How to handle user input — see ValidateautoAdvance(optional): Whether to skip waiting for user input
Two Types of Nodes
1. Nodes That Wait for User Input
These nodes send a message and wait for the user to respond. The response is validated before advancing.
typescript
{
id: 'ask_name',
action: { message: "What's your name?" },
validate: {
rules: [{ regex: '\\w+', errorMessage: 'Please enter a valid name' }],
answerKey: 'name'
}
}2. Nodes That Auto-Advance
These nodes execute their action and immediately move to the next node without waiting for input.
typescript
{
id: 'welcome',
action: (state) => ({
messages: [`Welcome to our service, ${state.name}!`]
}),
autoAdvance: true
}Complete Examples
Example 1: Simple Q&A Node
typescript
{
id: 'ask_email',
action: { message: 'What is your email address?' },
validate: {
rules: [
{
regex: '\\S+@\\S+\\.\\S+',
errorMessage: 'Please enter a valid email address'
}
],
answerKey: 'email'
}
}Example 2: Dynamic Action with Function
typescript
{
id: 'gold_price',
action: async (state) => {
const price = await getCurrentGoldPrice(); // could be an API call or some logic to fetch the price
return {
messages: [`Hello, ${state.name}! The current gold price is $${price}.`]
};
},
autoAdvance: true
}Example 3: Custom Function Validation
typescript
{
id: 'ask_age',
action: { message: 'How old are you?' },
validate: (state, event) => {
const age = Number(event.userMessage);
if (!Number.isFinite(age) || age < 0) {
return {
isValid: false,
errorMessage: 'Please enter a valid age'
};
}
if (age < 18) {
return {
isValid: false,
errorMessage: 'You must be 18 or older'
};
}
return {
isValid: true,
state: { age, isAdult: true }
};
}
}Example 4: Multi-Step Flow
typescript
const flow = new ChatGraph({
id: 'user_onboarding',
schema: UserSchema,
nodes: [
{
id: 'greet',
action: { message: "Hi! What's your name?" },
validate: {
rules: [{ regex: '\\w+', errorMessage: 'Enter a valid name' }],
answerKey: 'name',
},
},
{
id: 'personalized_message',
action: { message: 'Great to meet you, {{name}}!' },
autoAdvance: true,
},
{
id: 'ask_email',
action: { message: 'What is your email?' },
validate: {
rules: [
{
regex: '\\S+@\\S+\\.\\S+',
errorMessage: 'Enter a valid email',
},
],
answerKey: 'email',
},
},
{
id: 'confirmation',
action: async (state) => {
await sendEmail(state.email); // some async operation to send an email
return {
messages: [
`Perfect! We have sent a confirmation email to ${state.email}.`,
],
};
},
autoAdvance: true,
},
],
edges: [
{ from: START, to: 'greet' },
{ from: 'greet', to: 'personalized_message' },
{ from: 'personalized_message', to: 'ask_email' },
{ from: 'ask_email', to: 'confirmation' },
{ from: 'confirmation', to: END },
],
});Example 5: Complex State Updates
typescript
{
id: 'process_order',
action: (state, event) => {
const items = state.cartItems || [];
const total = items.reduce((sum, item) => sum + item.price, 0);
return {
messages: [
`Your order total is $${total.toFixed(2)}`,
'Confirm with "yes" or "no"'
],
orderTotal: total,
orderDate: new Date().toISOString()
};
},
validate: (state, event) => {
const answer = event.userMessage.toLowerCase();
if (answer === 'yes') {
return {
isValid: true,
state: { orderConfirmed: true }
};
} else if (answer === 'no') {
return {
isValid: true,
state: { orderConfirmed: false }
};
}
return {
isValid: false,
errorMessage: 'Please answer "yes" or "no"'
};
}
}Best Practices
- Use descriptive IDs: Choose clear, meaningful node identifiers
- Keep actions focused: Each node should do one thing well
- Validate thoroughly: Don't assume user input will be in the expected format
- Provide clear error messages: Help users understand what went wrong
- Use autoAdvance for info messages: If no input is needed, set
autoAdvance: true - Store validated data: Use
answerKeyor return state updates to persist user input