Skip to content

Typed Datasets

Datasets are pre-generated entity tables (products, locations, campaigns) that journeys reference during execution. They are generated once before any journey runs and are immutable during execution.

Defining a Dataset

Use defineDataset with field generators. Each field can be a static value or a function receiving the context and row metadata.

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

const productsDef = defineDataset({
  id: 'products',
  name: 'Product Catalog',
  count: 500,
  fields: {
    id: (ctx, row) => `prod-${row.index}`,
    name: (ctx) => ctx.faker.commerce.productName(),
    price: (ctx) => ctx.faker.number.float({ min: 9.99, max: 499.99, fractionDigits: 2 }),
    category: oneOf(['electronics', 'clothing', 'home', 'sports', 'books']),
    inStock: (ctx) => ctx.faker.datatype.boolean({ probability: 0.85 }),
  },
});

Field generators receive (context, { index, data }) where index is the row number and data is the partially-built row (for referencing earlier fields).

Type Inference with InferDatasetRow

Extract the row type from a dataset definition for type-safe access downstream.

ts
import { type InferDatasetRow } from '@synode/core';

type Product = InferDatasetRow<typeof productsDef>;
// Result: { id: string; name: string; price: number; category: string; inStock: boolean }

This works by unwrapping generator functions and Promises -- if a field is (ctx) => string, the inferred type is string.

Accessing Datasets in Journeys

Untyped Access

Use ctx.dataset('id') for a DatasetHandle with unknown row types.

ts
const product = ctx.dataset('products').randomRow();
const allProducts = ctx.dataset('products').getAllRows();
const count = ctx.dataset('products').size();

Typed Access

Use ctx.typedDataset<T>('id') with the inferred type for full type safety.

ts
import { defineAction, type InferDatasetRow } from '@synode/core';

type Product = InferDatasetRow<typeof productsDef>;

const addToCart = defineAction({
  id: 'add-to-cart',
  name: 'add_to_cart',
  handler: (ctx) => {
    const product = ctx.typedDataset<Product>('products').randomRow();
    return [
      {
        id: ctx.generateId('event'),
        userId: ctx.userId,
        sessionId: ctx.sessionId,
        name: 'add_to_cart',
        timestamp: ctx.now(),
        payload: { productId: product.id, productName: product.name, price: product.price },
      },
    ];
  },
});

DatasetHandle Methods

MethodReturnDescription
randomRow()TRowRandom row from the dataset
getRowById(id)TRow | undefinedLookup by id field value
getRowByIndex(i)TRow | undefinedLookup by zero-based index
getAllRows()TRow[]All rows
size()numberRow count

Registering Datasets with generate

Pass dataset definitions to generate. They are hydrated before any journey runs.

ts
import { generate } from '@synode/core';

await generate(purchaseJourney, {
  users: 1000,
  datasets: [productsDef, locationsDef],
  lanes: 4,
});

Import and Export

Use importDataset and exportDataset for file-based dataset I/O. Supported formats: csv, json, jsonl.

ts
import { exportDataset, importDataset } from '@synode/core';

// Export to file (formats: 'csv', 'json', 'jsonl')
await exportDataset(dataset, './data/products.csv', 'csv');

// Import from file
const products = await importDataset('products', 'Product Catalog', './data/products.csv', 'csv');

Pass imported datasets via preloadedDatasets -- these skip generation and are injected directly.

ts
const products = await importDataset('products', 'Products', './products.json', 'json');
await generate(journey, { users: 500, preloadedDatasets: [products] });

For in-memory operations, use exportDatasetToString and importDatasetFromString.

Cross-Field References

Later fields can reference earlier fields in the same row via the data parameter.

ts
const orders = defineDataset({
  id: 'orders',
  name: 'Orders',
  count: 200,
  fields: {
    quantity: (ctx) => ctx.faker.number.int({ min: 1, max: 10 }),
    unitPrice: (ctx) => ctx.faker.number.float({ min: 5, max: 100, fractionDigits: 2 }),
    total: (_ctx, row) => (row.data.quantity as number) * (row.data.unitPrice as number),
  },
});