A database-agnostic, type-safe engine for executing and managing production-grade decision tree based surveys, diagnostics, and guided user journeys.
ivy-flow provides a predictable state machine for graph traversal and a strict versioning contract for managing survey lifecycles. It is designed to be the pure domain layer of your application—completely independent of any specific framework, UI library, or database.
- ivy-flow-studio: The official visual editor and admin UI for constructing
ivy-flowdecision graphs.
- Deterministic Traversal: Explicit state transitions for navigating complex decision graphs.
- Framework & Database Agnostic: Bring your own database (Prisma, Drizzle, etc.) via simple repository contracts.
- Draft & Publish Lifecycle: Built-in versioning service to safely manage survey revisions.
- Strict Validation: Schema-level and reference-level integrity checks prevent broken graphs from being published.
- Type-Safe: First-class TypeScript support for graph definitions and runtime state.
- Built-in Adapters: Includes JSON and Prisma repository adapters that implement the same domain contract.
ivy-flow requires a repository adapter to manage graph persistence. Out of the box, it includes a read-only JSON string adapter for rapid prototyping.
import {
createSurveyState,
createSurveyDefinitionService,
createJsonSurveyDefinitionRepository,
validateGraph,
} from "ivy-flow";
// 1. Initialize the storage adapter
const repository = createJsonSurveyDefinitionRepository({
graphJson: JSON.stringify({
metadata: {
title: "Main Survey",
version: "1.0.0",
description: "Survey loaded from JSON string",
locale: "en",
},
startNode: "start",
nodes: {
start: { type: "start", label: "Start", next: "stop" },
stop: { type: "stop", title: "Done", description: "Survey complete." },
},
}),
sourceLabel: "bootstrap-graph",
definitionKey: "main",
definitionName: "Main Survey",
});
// 2. Initialize the versioning service
const service = createSurveyDefinitionService({
repository,
defaultSurveyKey: "main",
defaultSurveyName: "Main Survey",
systemEditorIdentifier: "system@local",
validateGraph,
});
// 3. Fetch the active graph and initialize the runtime state
const activeGraph = await service.getActiveSurveyGraph();
const initialState = createSurveyState(activeGraph);createJsonSurveyDefinitionRepository(...)is read-only by design.- It validates schema and references before exposing graph data.
- Admin write operations (
createDraftRevision,publishDraftAndArchiveOthers, etc.) intentionally throw unsupported-operation errors. - Use Prisma adapter for full draft and publish workflows.
ivy-flow includes a Prisma repository adapter via createPrismaSurveyDefinitionRepository(...).
The adapter expects mapped Prisma-like delegates, so you can use:
- direct Prisma client,
- wrapped data-layer objects,
- custom delegate mappings.
import { SurveyRevisionStatus } from "@prisma/client";
import {
createPrismaSurveyDefinitionRepository,
createSurveyDefinitionService,
validateGraph,
} from "ivy-flow";
const repository = createPrismaSurveyDefinitionRepository({
db: prisma,
statusMap: {
draft: SurveyRevisionStatus.DRAFT,
published: SurveyRevisionStatus.PUBLISHED,
archived: SurveyRevisionStatus.ARCHIVED,
},
});
const service = createSurveyDefinitionService({
repository,
defaultSurveyKey: "main",
defaultSurveyName: "Main Survey",
systemEditorIdentifier: "system@local",
validateGraph,
});import { createPrismaSurveyDefinitionRepository } from "ivy-flow";
const repository = createPrismaSurveyDefinitionRepository({
db: {
surveyDefinition: dataLayer.surveys.definition,
surveyDefinitionRevision: dataLayer.surveys.revision,
$transaction: dataLayer.$transaction,
},
statusMap: {
draft: "DRAFT",
published: "PUBLISHED",
archived: "ARCHIVED",
},
});db.surveyDefinitiondelegate withfindUnique,update,create.db.surveyDefinitionRevisiondelegate withfindFirst,findMany,create,update,updateMany.db.$transactionfunction.- Revision status mapping via
statusMap.
The adapter returns the standard SurveyDefinitionRepository, so it plugs directly into createSurveyDefinitionService(...).
The runtime engine handles active user progression through a published graph.
| Method | Description |
|---|---|
createSurveyState(graph) |
Initializes state at graph.startNode. |
getCurrentNode(state, graph) |
Resolves the active node object from the current state. |
selectOption(state, graph, index) |
Applies a selection on a decision node and advances history. |
advanceNode(state, graph) |
Moves forward from start or info nodes via the next reference. |
goBack(state) |
Reverts one history step. |
isTerminal(state, graph) |
Returns true if the current node is a result or stop node. |
getResult(state, graph) |
Returns the normalized result payload on terminal nodes. |
getProgress(state, graph) |
Estimates progress as a percentage [0, 100]. |
validateGraph(graph) |
Performs structural and referential validation, returning an array of errors. |
The versioning service manages the administrative lifecycle (drafting, validating, and publishing) of survey definitions.
| Method | Description |
|---|---|
getActiveSurveyGraph() |
Returns the current active, published revision. |
getSurveyAdminData() |
Returns the admin payload, including draft-preferred graph and revision history. |
upsertDraftSurveyRevision(input) |
Updates the latest draft or creates the next sequential draft version. |
publishDraftSurveyRevision(input) |
Validates and promotes a draft to active, archiving previous revisions. |
Graphs must strictly adhere to the DecisionGraph type. validateGraph() enforces reference integrity to ensure no orphaned nodes or dead ends exist.
Supported node types:
start: Entry point. Requireslabelandnext.decision: Branching point. Requiresquestionand an array ofoptions(each withlabelandnext).info: Interstitial content. Requirescontent.result: Terminal node yielding an outcome. Requirestitle,description, andcategory.stop: Terminal node for dead-ends. Requirestitleanddescription.helper: Supplementary content nodes attached to decisions.
MIT
