Skip to content

Getting Started

Get from zero to generated events in five minutes.

Install

The fastest way to start is the interactive scaffolder:

bash
npm install -g @synode/cli
synode init

This creates a project with config, dependencies, and an optional example journey. See the CLI guide for details.

Or install the core library directly:

bash
npm install @synode/core

Requires Node.js 18+.

Core Concepts

Synode organizes generation around three pillars:

PillarPurposeRequired?
UsersSynthetic identities with personasYes
DatasetsPre-generated entity tables (products, locations)No
JourneysBehavioral flows that emit eventsNo

Events are produced through a four-level hierarchy: Journey (user goal) -> Adventure (session) -> Action (behavior) -> Event[] (output).

Your First Journey

1. Define an action

The fields shorthand generates a single event with the given payload:

typescript
import { defineAction } from '@synode/core';

const pageView = defineAction({
  id: 'page-view',
  name: 'page_viewed',
  fields: {
    url: '/home',
    title: 'Home Page',
    referrer: (ctx) => ctx.faker.internet.url(),
  },
});

2. Define an adventure

Group actions into a session with timing and optional bounce behavior:

typescript
import { defineAdventure } from '@synode/core';

const browsing = defineAdventure({
  id: 'browse',
  name: 'Browse Website',
  actions: [pageView],
  timeSpan: { min: 1000, max: 3000 },
  bounceChance: 0.2,
});

3. Define a journey

Tie adventures into a complete user flow:

typescript
import { defineJourney } from '@synode/core';

const visit = defineJourney({
  id: 'website-visit',
  name: 'Website Visit',
  adventures: [browsing],
});

4. Generate

Collect events with InMemoryAdapter:

typescript
import { generate, InMemoryAdapter } from '@synode/core';

const adapter = new InMemoryAdapter();
await generate(visit, { users: 50, adapter });

console.log(`Generated ${adapter.events.length} events`);

Adding a Dataset

Datasets are pre-generated entity tables you can reference inside actions. Define a catalog, then use ctx.dataset() in a handler:

typescript
import { defineDataset, defineAction, oneOf } from '@synode/core';

const products = defineDataset({
  id: 'products',
  name: 'Product Catalog',
  count: 50,
  fields: {
    productId: (_ctx, row) => `prod-${row.index}`,
    name: (ctx) => ctx.faker.commerce.productName(),
    price: (ctx) => ctx.faker.number.float({ min: 9.99, max: 499.99 }),
    category: oneOf(['electronics', 'clothing', 'home', 'sports']),
  },
});

const viewProduct = defineAction({
  id: 'view-product',
  name: 'product_viewed',
  handler: (ctx) => {
    const product = ctx.dataset('products').randomRow();
    return [
      {
        id: ctx.generateId(),
        userId: ctx.userId,
        sessionId: ctx.sessionId,
        name: 'product_viewed',
        timestamp: ctx.now(),
        payload: { productId: product.productId, price: product.price },
      },
    ];
  },
});

await generate(visit, { users: 50, datasets: [products], adapter });

Adding a Persona

Personas assign weighted attributes to each user. The locale attribute configures Faker.js automatically:

typescript
import { definePersona, weighted } from '@synode/core';

const shopper = definePersona({
  id: 'shopper',
  name: 'Online Shopper',
  attributes: {
    locale: weighted({ en: 50, es: 20, fr: 15, de: 15 }),
    deviceType: weighted({ mobile: 60, desktop: 30, tablet: 10 }),
  },
});

await generate(visit, { users: 50, persona: shopper, datasets: [products], adapter });

Each user's ctx.locale and ctx.faker reflect their assigned locale. Access persona attributes with ctx.get('deviceType').

Next Steps