Multi-Journey Prerequisites
Journeys can depend on other journeys via requires. The engine checks prerequisites before starting each journey and skips journeys whose dependencies have not been completed. Combined with bounce and suppression, this creates realistic funnel drop-off behavior.
Basic Prerequisites
Set requires to an array of journey IDs that must complete before this journey starts.
import { defineJourney, defineAdventure, defineAction, fake } from '@synode/core';
const signupJourney = defineJourney({
id: 'signup',
name: 'Signup Flow',
adventures: [
defineAdventure({
id: 'registration',
name: 'Registration',
timeSpan: { min: 2000, max: 5000 },
actions: [
defineAction({
id: 'signup-form',
name: 'sign_up',
fields: {
method: 'email',
source: fake((f) => f.helpers.arrayElement(['organic', 'paid', 'referral'])),
},
}),
],
}),
],
});
const browseJourney = defineJourney({
id: 'browse',
name: 'Browse Products',
requires: ['signup'], // must complete signup first
adventures: [
/* ... */
],
});
const purchaseJourney = defineJourney({
id: 'purchase',
name: 'Purchase Flow',
requires: ['signup', 'browse'], // must complete both
adventures: [
/* ... */
],
});How Prerequisites Work
- Journeys are passed to
generateas an array and executed in order for each user - Before starting a journey, the engine checks
ctx.hasCompletedJourney(id)for each required ID - If any prerequisite is not met, the journey is silently skipped
- When a journey completes,
ctx.markJourneyComplete(id)is called automatically
The order you pass journeys to generate matters -- place prerequisites earlier in the array.
import { generate } from '@synode/core';
// Order matters: signup -> browse -> purchase
await generate([signupJourney, browseJourney, purchaseJourney], {
users: 1000,
lanes: 4,
});Shared Context Across Journeys
Context persists across all journeys for a single user. Data set in one journey is available in later journeys.
// In signup journey: store data for downstream journeys
const signupAction = defineAction({
id: 'signup-complete',
name: 'sign_up_complete',
handler: (ctx) => {
ctx.set('accountType', 'premium');
return [
{
id: ctx.generateId('event'),
userId: ctx.userId,
sessionId: ctx.sessionId,
name: 'sign_up_complete',
timestamp: ctx.now(),
payload: { accountType: 'premium' },
},
];
},
});
// In purchase journey: read data set by signup journey
const purchaseAction = defineAction({
id: 'checkout',
name: 'checkout',
handler: (ctx) => {
const accountType = ctx.get<string>('accountType');
const discount = accountType === 'premium' ? 0.1 : 0;
return [
{
id: ctx.generateId('event'),
userId: ctx.userId,
sessionId: ctx.sessionId,
name: 'checkout',
timestamp: ctx.now(),
payload: { discount, accountType },
},
];
},
});Context Scoping
Fields can be scoped to automatically clean up when a scope ends. Global fields (no scope) persist across all journeys.
// Persists across all journeys (default)
ctx.set('accountId', 'acc-123');
// Cleared after the current journey completes
ctx.set('cartItems', [], { scope: 'journey' });
// Cleared after the current adventure completes
ctx.set('searchQuery', 'shoes', { scope: 'adventure' });
// Cleared after the current action completes
ctx.set('tempCalc', 42, { scope: 'action' });Bounce Behavior
Bounce creates realistic funnel drop-off. Users who bounce on a journey never complete it, so downstream journeys with requires are also skipped.
Bounce applies at three levels:
| Level | bounceChance | Effect |
|---|---|---|
| Journey | Journey.bounceChance | Journey never starts, no events generated |
| Adventure | Adventure.bounceChance | onBounce: 'stop' ends journey, 'skip' continues to next adventure |
| Action | Action.bounceChance | Stops the current adventure |
onBounce options: 'stop' (default) ends the entire journey (NOT marked complete), 'skip' skips to the next adventure.
Suppression Periods
After a journey completes or bounces, a suppression period advances the user's clock. This creates realistic gaps between journeys.
const signupJourney = defineJourney({
id: 'signup',
name: 'Signup',
suppressionPeriod: { min: 3600000, max: 86400000 }, // 1 hour to 1 day
adventures: [
/* ... */
],
});Suppression is applied in both cases:
- Journey completes normally: user waits before starting the next journey
- Journey bounces at the journey level: user waits before the next journey is attempted
