@bernierllc/nevar-graph-executor
v0.1.1
Published
Topological execution engine for Nevar typed-DAG graphs — dispatches nodes, resolves edge mappings, enforces idempotency
Readme
@bernierllc/nevar-graph-executor
Topological execution engine for Nevar typed-DAG graphs. Dispatches nodes level-by-level, resolves edge mappings between upstream outputs and downstream config, enforces idempotency, and records per-node step history.
Overview
createGraphExecutor returns a GraphExecutor that runs GraphDefinition instances loaded from a GraphStorageAdapter. It does not subscribe to triggers — the calling suite (e.g. @bernierllc/nevar) owns that wiring and invokes runGraph() directly.
Execution semantics:
- Idempotency check before run creation (configurable TTL, default 60s).
- Topological level walk with bounded concurrency (default 4).
- Edge mappings resolved per node; root nodes receive the trigger payload merged over static config.
- Skip propagation: nodes whose upstreams all failed/skipped or whose edge conditions evaluate false are recorded as
skipped. - Failure mode:
fail-fast(graph stops on first error) orbest-effort(continues, final status becomespartialorfailed).
Installation
pnpm add @bernierllc/nevar-graph-executorYou will also need the peer-style workspace packages: @bernierllc/nevar-graph-storage, @bernierllc/nevar-action-executor, and a condition evaluator from @bernierllc/nevar-evaluator.
Usage
import { createGraphExecutor } from '@bernierllc/nevar-graph-executor';
import { InMemoryGraphStorageAdapter } from '@bernierllc/nevar-graph-storage';
const storage = new InMemoryGraphStorageAdapter();
const executor = createGraphExecutor({
storage,
actionRegistry, // ActionHandlerLookup
actionExecutor, // ActionExecutor (single-intent dispatch)
conditionEvaluator, // (tree, payload) => boolean
});
const run = await executor.runGraph(
'graph-123',
{ postId: 'post-456', authorId: 'user-789' },
{ idempotencyTtlMs: 60_000 }
);
console.log(run.status); // 'succeeded' | 'failed' | 'partial'Helpers
import {
computeIdempotencyKey,
computeTopoLevels,
resolveEdgeMappings,
} from '@bernierllc/nevar-graph-executor';
const key = computeIdempotencyKey('graph-123', 'event:post.published', { postId: 'p1' });
const levels = computeTopoLevels(graph); // TopoLevel[]
const { resolvedInput, errors } = resolveEdgeMappings(node.config, incomingEdges, nodeOutputs);API
createGraphExecutor(deps): GraphExecutor
| Dep | Type | Purpose |
|-----|------|---------|
| storage | GraphStorageAdapter | Persists definitions, runs, step records |
| actionRegistry | ActionHandlerLookup | Looks up handlers by actionType |
| actionExecutor | ActionExecutor | Dispatches one node as one ActionIntent |
| conditionEvaluator | (tree, payload) => boolean | Evaluates edge/activation conditions |
| loopDetector? | { check, record } | Optional loop guard, keyed |
| circuitBreaker? | { check, record } | Optional circuit breaker, keyed |
| lifecycleEvents? | { emit(event, payload) } | Forwards deferred triggers |
GraphExecutor.runGraph(graphId, triggerPayload, options?)
| Param | Type | Default | Description |
|-------|------|---------|-------------|
| graphId | string | — | Definition to execute |
| triggerPayload | Record<string, unknown> | — | Payload that triggered this graph |
| options.idempotencyTtlMs | number | 60_000 | Dedup window in ms |
| options.parentRunId | string | null | Set when replaying a prior failed run |
Returns: Promise<GraphRun> — either a newly created run or the existing deduplicated run.
Errors
| Class | Code | When |
|-------|------|------|
| NevarGraphExecutionError | NEVAR_GRAPH_EXECUTION_ERROR (default), NEVAR_GRAPH_NOT_FOUND | Graph missing or run-time failure during execution |
| NevarGraphIdempotencyError | NEVAR_GRAPH_IDEMPOTENCY_ERROR | Idempotency-key computation failure |
All errors carry ES2022 cause chains and a context object.
Integrations
Logger integration
Logger integration is consumer-driven: the executor never logs directly. It surfaces NevarGraphExecutionError instances with cause chains and per-step errorMessage strings via the storage adapter's appendStep. Consumers wire @bernierllc/logger at the call site:
import { formatErrorChain } from '@bernierllc/logger';
try {
await executor.runGraph(graphId, payload);
} catch (err) {
logger.error('graph execution failed', { chain: formatErrorChain(err) });
throw err;
}NeverHub integration
NeverHub integration is optional and is owned by the suite layer (@bernierllc/nevar). The executor itself does not register with NeverHub — it is a pure orchestrator that operates on injected dependencies. When the consuming suite has NeverHub available it registers the executor as part of the service surface; when it is not available, the executor continues to dispatch graphs normally via the injected actionExecutor and storage.
Graceful degradation
Graceful degradation is built into the executor's optional dependencies:
loopDetector,circuitBreaker, andlifecycleEventsare all optional — omit them and the corresponding feature simply no-ops.- When NeverHub is unavailable, the executor functions fully via its injected core dependencies.
- Idempotency failures fall back to creating a new run; mapping errors in
best-effortmode are recorded as step errors rather than aborting the whole run.
Related Packages
@bernierllc/nevar-graph-types— shared type definitions (GraphDefinition, GraphRun, etc.)@bernierllc/nevar-graph-validator— static validation prior to execution@bernierllc/nevar-graph-storage— storage contract (in-memory reference)@bernierllc/nevar-graph-adapter-prisma— Prisma/PostgreSQL storage backend@bernierllc/nevar-action-executor— single-action dispatcher used per node@bernierllc/nevar— suite re-export (Graph namespace)
License
Copyright (c) 2025 Bernier LLC. See LICENSE for details.
