{"_id":"@alkdev/flowgraph","name":"@alkdev/flowgraph","dist-tags":{"latest":"0.1.0"},"versions":{"0.1.0":{"name":"@alkdev/flowgraph","version":"0.1.0","description":"Workflow graph library — DAG-based operation orchestration over graphology, with ujsx template composition and reactive execution","type":"module","main":"./dist/index.cjs","module":"./dist/index.js","types":"./dist/index.d.ts","exports":{".":{"import":{"types":"./dist/index.d.ts","default":"./dist/index.js"},"require":{"types":"./dist/index.d.cts","default":"./dist/index.cjs"}},"./component":{"import":{"types":"./dist/component/index.d.ts","default":"./dist/component/index.js"},"require":{"types":"./dist/component/index.d.cts","default":"./dist/component/index.cjs"}},"./host":{"import":{"types":"./dist/host/index.d.ts","default":"./dist/host/index.js"},"require":{"types":"./dist/host/index.d.cts","default":"./dist/host/index.cjs"}},"./schema":{"import":{"types":"./dist/schema/index.d.ts","default":"./dist/schema/index.js"},"require":{"types":"./dist/schema/index.d.cts","default":"./dist/schema/index.cjs"}},"./graph":{"import":{"types":"./dist/graph/index.d.ts","default":"./dist/graph/index.js"},"require":{"types":"./dist/graph/index.d.cts","default":"./dist/graph/index.cjs"}},"./reactive":{"import":{"types":"./dist/reactive/index.d.ts","default":"./dist/reactive/index.js"},"require":{"types":"./dist/reactive/index.d.cts","default":"./dist/reactive/index.cjs"}},"./analysis":{"import":{"types":"./dist/analysis/index.d.ts","default":"./dist/analysis/index.js"},"require":{"types":"./dist/analysis/index.d.cts","default":"./dist/analysis/index.cjs"}},"./error":{"import":{"types":"./dist/error/index.d.ts","default":"./dist/error/index.js"},"require":{"types":"./dist/error/index.d.cts","default":"./dist/error/index.cjs"}}},"publishConfig":{"access":"public"},"scripts":{"build":"tsup","build:tsc":"tsc","test":"vitest run","test:watch":"vitest","test:coverage":"vitest run --coverage","lint":"tsc --noEmit","prepublishOnly":"npm run build"},"keywords":["flowgraph","dag","workflow","graphology","ujsx","operations"],"license":"MIT OR Apache-2.0","repository":{"type":"git","url":"git+https://git.alk.dev/alkdev/flowgraph.git"},"homepage":"https://git.alk.dev/alkdev/flowgraph","bugs":{"url":"https://git.alk.dev/alkdev/flowgraph/issues"},"sideEffects":false,"dependencies":{"@alkdev/typebox":"^0.34.49","@alkdev/ujsx":"^0.1.0","@preact/signals-core":"^1.14.1","graphology":"^0.26.0","graphology-dag":"^0.4.1"},"peerDependencies":{"@alkdev/operations":"^0.1.0"},"devDependencies":{"@types/node":"^22.0.0","@vitest/coverage-v8":"^3.2.4","tsup":"^8.5.1","typescript":"^5.7.0","vitest":"^3.1.0"},"engines":{"node":">=18.0.0"},"gitHead":"058b437c3f024beae5f4ca93b4e9edcc4b0dadae","_id":"@alkdev/flowgraph@0.1.0","_nodeVersion":"25.8.1","_npmVersion":"11.11.0","dist":{"integrity":"sha512-J1k5bEJguGh4NL/GrcIxPhDSFyvStHGrEJCOacIr3Q+3nRn8xMuuCLIz5ayoD/Q/YJB6A/qY99hbwPkPRUcVUg==","shasum":"197ca61e46ca4d20e5f483007254beebd4855d6c","tarball":"https://registry.npmjs.org/@alkdev/flowgraph/-/flowgraph-0.1.0.tgz","fileCount":77,"unpackedSize":866384,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEQCICMwJa7IS7VXbw49YNXjIpOjlSEvzZm6HsXsUH4BIB1NAiBICwvb0TO+WmI37Ko9HEWJKZ0Gq5FYvFfN+AcOCe137g=="}]},"_npmUser":{"name":"alkdev","email":"admin@alk.dev"},"directories":{},"maintainers":[{"name":"alkdev","email":"admin@alk.dev"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/flowgraph_0.1.0_1779431604532_0.2467750551178285"},"_hasShrinkwrap":false}},"time":{"created":"2026-05-22T06:33:24.361Z","0.1.0":"2026-05-22T06:33:24.770Z","modified":"2026-05-22T06:33:24.937Z"},"maintainers":[{"name":"alkdev","email":"admin@alk.dev"}],"description":"Workflow graph library — DAG-based operation orchestration over graphology, with ujsx template composition and reactive execution","homepage":"https://git.alk.dev/alkdev/flowgraph","keywords":["flowgraph","dag","workflow","graphology","ujsx","operations"],"repository":{"type":"git","url":"git+https://git.alk.dev/alkdev/flowgraph.git"},"bugs":{"url":"https://git.alk.dev/alkdev/flowgraph/issues"},"license":"MIT OR Apache-2.0","readme":"# @alkdev/flowgraph\n\nDAG-based workflow orchestration over graphology, with ujsx template composition and reactive signal-driven execution.\n\n## What This Does\n\nFlowgraph sits between `@alkdev/operations` (which defines *what can be called*) and `@alkdev/alkhub` (which records *what was called*). Flowgraph defines **how calls are orchestrated** — the structure, validation, and execution of workflows.\n\nThree conceptual graphs, each for a different purpose:\n\n1. **Operation Graph** — static graph built from `OperationSpec`s at startup. Nodes are operations, edges are type-compatibility relationships. Enables cycle detection, topological ordering, and validation.\n2. **Call Graph** — dynamic graph built from call protocol events at runtime. Nodes are call invocations with status/timestamps, edges are parent-child relationships. Enables abort cascading and observability.\n3. **Workflow Template** — declarative ujsx tree defining a reusable workflow structure. A validated path through the operation graph, instantiated as a call graph at runtime.\n\n**The graph is the specification. The template is the authoring surface. The call graph is the execution record.**\n\n## Installation\n\n```bash\nnpm install @alkdev/flowgraph\n```\n\nPeer dependency: `@alkdev/operations ^0.1.0`\n\n## Quick Start\n\n### Build an Operation Graph\n\n```typescript\nimport { FlowGraph } from \"@alkdev/flowgraph/graph\";\nimport type { OperationSpec } from \"@alkdev/flowgraph/graph\";\n\nconst specs: OperationSpec[] = [\n  { namespace: \"task\", name: \"classify\", type: \"query\", version: \"1.0.0\", inputSchema: {...}, outputSchema: {...} },\n  { namespace: \"task\", name: \"enrich\", type: \"query\", version: \"1.0.0\", inputSchema: {...}, outputSchema: {...} },\n  { namespace: \"task\", name: \"summarize\", type: \"mutation\", version: \"1.0.0\", inputSchema: {...}, outputSchema: {...} },\n];\n\nconst graph = FlowGraph.fromSpecs(specs);\n// Type-compatibility edges added automatically\ngraph.hasEdge(\"task.classify\", \"task.enrich\");\n```\n\n### Define a Workflow Template\n\n```typescript\nimport { h } from \"@alkdev/ujsx\";\nimport { Operation, Sequential, Parallel, Conditional } from \"@alkdev/flowgraph/component\";\n\nconst template = h(Sequential, {},\n  h(Operation, { name: \"task.classify\" }),\n  h(Conditional, {\n    test: (results) => results[\"task.classify\"].output.confidence > 0.8,\n  },\n    h(Parallel, {},\n      h(Operation, { name: \"task.enrich\" }),\n      h(Operation, { name: \"task.summarize\" }),\n    ),\n    h(Operation, { name: \"task.classify\" }),\n  ),\n);\n```\n\n### Validate the Template\n\n```typescript\nimport { validateTemplate } from \"@alkdev/flowgraph/analysis\";\n\nconst errors = validateTemplate(template, graph);\nif (errors.length > 0) {\n  for (const error of errors) {\n    console.error(`[${error.type}]`, error);\n  }\n}\n```\n\n### Populate a Call Graph from Events\n\n```typescript\nimport { FlowGraph } from \"@alkdev/flowgraph/graph\";\n\nconst callGraph = FlowGraph.fromCallEvents(eventArray);\ncallGraph.filterByStatus(\"running\");\ncallGraph.children(\"req_abc123\");\ncallGraph.lineage(\"req_xyz789\");\ncallGraph.duration(\"req_abc123\");\n```\n\n### Drive Reactive Execution\n\n```typescript\nimport { WorkflowReactiveRoot } from \"@alkdev/flowgraph/reactive\";\n\nconst workflow = new WorkflowReactiveRoot(dag, {\n  failurePolicy: \"abort-dependents\",\n});\n\n// Append call protocol events — status derives reactively\nworkflow.append({ type: \"call.requested\", requestId: \"req_1\", operationId: \"task.classify\", input: {}, timestamp: \"...\" });\nworkflow.append({ type: \"call.responded\", requestId: \"req_1\", output: { confidence: 0.95 }, timestamp: \"...\" });\n\n// Read reactive state\nworkflow.getStatus(\"task.enrich\");\nworkflow.getResult(\"task.classify\");\n\n// Abort cascading\nworkflow.abortAll();\nworkflow.dispose();\n```\n\n## Subpath Exports\n\n| Subpath | Purpose | Key Exports |\n|---------|---------|-------------|\n| `@alkdev/flowgraph` | Root — re-exports everything | All public types and functions |\n| `@alkdev/flowgraph/graph` | Core DAG class | `FlowGraph`, `FlowGraphOptions`, `OperationSpec`, `CallEventMapValue` |\n| `@alkdev/flowgraph/schema` | TypeBox schemas and types | `CallStatus`, `NodeStatus`, `EdgeType`, `OperationType`, `CallNodeAttrs`, `OperationNodeAttrs`, `OperationEdgeAttrs`, `CallEdgeAttrs`, `TemplateEdgeAttrs`, `CallResult`, `FlowGraphSerialized` |\n| `@alkdev/flowgraph/component` | ujsx workflow components | `Operation`, `Sequential`, `Parallel`, `Conditional`, `Map` |\n| `@alkdev/flowgraph/host` | Rendering backends | `GraphologyHostConfig`, `ReactiveHostConfig` |\n| `@alkdev/flowgraph/analysis` | Validation and analysis functions | `typeCompat`, `buildTypeEdges`, `validateGraph`, `validateSchema`, `validate`, `validateTemplate`, `validatePreconditions`, `topologicalOrder`, `parallelGroups`, `criticalPath`, `reachableFrom` |\n| `@alkdev/flowgraph/reactive` | Reactive execution engine | `WorkflowReactiveRoot`, `EventLogProjection`, `WorkflowNode`, `ReactiveContext`, `FailurePolicy`, `AggregateStatus` |\n| `@alkdev/flowgraph/error` | Error hierarchy | `FlowgraphError`, `ConstructionError`, `DuplicateNodeError`, `DuplicateEdgeError`, `NodeNotFoundError`, `CycleError`, `InvalidInputError`, `InvalidTransitionError` |\n\n## Core API: FlowGraph Class\n\n`FlowGraph<NodeAttrs, EdgeAttrs>` wraps a graphology `DirectedGraph` and enforces DAG invariants. It delegates graph operations to graphology while adding flowgraph-specific construction, mutation, and query methods.\n\n### Factory Methods\n\n```typescript\nFlowGraph.fromSpecs(specs: OperationSpec[]): OperationGraph\nFlowGraph.fromCallEvents(events: CallEventMapValue[]): CallGraph\nFlowGraph.fromJSON(data: FlowGraphSerialized): FlowGraph\n```\n\n### Node Operations\n\n```typescript\ngraph.addNode(key, attrs)            // throws DuplicateNodeError\ngraph.removeNode(key)                // throws NodeNotFoundError\ngraph.updateNode(key, partialAttrs)  // throws NodeNotFoundError\ngraph.hasNode(key): boolean\ngraph.getNodeAttributes(key): NodeAttrs\ngraph.forEachNode(callback): void\n```\n\n### Edge Operations\n\n```typescript\ngraph.addEdge(source, target, attrs?) // throws NodeNotFoundError, DuplicateEdgeError, CycleError\ngraph.removeEdge(source, target)      // no-op if not found\ngraph.hasEdge(source, target): boolean\ngraph.getEdgeAttributes(source, target): EdgeAttrs\ngraph.forEachEdge(callback): void\n```\n\n### Traversal\n\n```typescript\ngraph.topologicalOrder(): string[]\ngraph.ancestors(nodeId): string[]\ngraph.descendants(nodeId): string[]\ngraph.predecessors(nodeId): string[]\ngraph.successors(nodeId): string[]\ngraph.reachableFrom(nodeIds): Set<string>\ngraph.hasCycles(): boolean\ngraph.findCycles(): string[][]\n```\n\n### Call Graph Convenience\n\n```typescript\ngraph.addCall(attrs: CallNodeAttrs): void\ngraph.addDependency(source, target): void\ngraph.updateStatus(requestId, status, extra?): void  // throws InvalidTransitionError\ngraph.updateCall(requestId, partialAttrs): void\ngraph.removeCall(requestId): void\ngraph.updateFromEvent(event: CallEventMapValue): void\ngraph.filterByStatus(status: CallStatus): string[]\ngraph.getRoots(): string[]\ngraph.children(requestId): string[]\ngraph.duration(requestId): number\ngraph.lineage(requestId): string[]\n```\n\n### Serialization\n\n```typescript\ngraph.export(): FlowGraphSerialized\ngraph.toJSON(): FlowGraphSerialized\ngraph.toString(): string\n```\n\n### Escape Hatch\n\n```typescript\ngraph.graph  // → DirectedGraph (raw graphology instance)\n```\n\nDirect mutation via `graph.graph` bypasses flowgraph validation. Use with caution.\n\n## Schema Enums\n\n| Enum | Values |\n|------|--------|\n| `CallStatus` | `pending`, `running`, `completed`, `failed`, `aborted` |\n| `NodeStatus` | `idle`, `waiting`, `ready`, `running`, `completed`, `failed`, `skipped`, `aborted` |\n| `EdgeType` | `triggered`, `depends_on`, `typed`, `sequential`, `conditional` |\n| `OperationType` | `query`, `mutation`, `subscription` |\n\nCall status transitions: `pending → running → completed|failed|aborted`. Terminal states are immutable. `InvalidTransitionError` is thrown on invalid transitions.\n\n## Workflow Components\n\n| Component | Props | Behavior |\n|-----------|-------|----------|\n| `<Operation>` | `name`, `input?`, `retries?`, `timeout?` | Declares an operation node in the workflow |\n| `<Sequential>` | `id?` | Children execute in order; edges are `sequential` |\n| `<Parallel>` | `id?`, `maxConcurrency?` | Children execute concurrently |\n| `<Conditional>` | `test`, `else?` | Branches on `test(results)`. Children = then-branch, `else` prop = else-branch |\n| `<Map>` | `over`, `as` | Iterates over `over` collection, binding each item as `as` variable |\n\n## Analysis Functions\n\n```typescript\nimport { typeCompat, validateTemplate, topologicalOrder, parallelGroups, criticalPath } from \"@alkdev/flowgraph/analysis\";\n\ntypeCompat(outputSchema, inputSchema): TypeCompatResult | undefined\nvalidateTemplate(template, operationGraph): AnyValidationError[]\ntopologicalOrder(graph): string[]\nparallelGroups(graph): string[][]  // topological generations\ncriticalPath(graph): string[]     // longest path\nvalidateGraph(graph): GraphValidationError[]\nvalidateSchema(graph, schema): ValidationError[]\nvalidate(graph, schema): AnyValidationError[]  // combined\n```\n\n## Reactive Execution\n\n`WorkflowReactiveRoot` implements `EventLogProjection` — call protocol events are the source of truth, status/results are derived projections.\n\n```typescript\nconst workflow = new WorkflowReactiveRoot(dag, {\n  failurePolicy: \"abort-dependents\",  // or \"continue-running\"\n  parallelGroups: { group1: { siblings: [\"a\", \"b\"], maxConcurrency: 2 } },\n});\n\n// Per-node reactive signals\nworkflow.statusMap      // Map<string, Signal<NodeStatus>>\nworkflow.preconditions  // Map<string, ReadonlySignal<boolean>> — all predecessors completed/skipped\nworkflow.canStart       // Map<string, ReadonlySignal<boolean>> — preconditions + concurrency\nworkflow.blockedByFailure // Map<string, ReadonlySignal<boolean>> — any predecessor failed/aborted\nworkflow.resultMap      // Map<string, ReadonlySignal<CallResult | undefined>>\n\n// Event-driven updates\nworkflow.append(event: CallEventMapValue): void\n\n// Queries\nworkflow.getStatus(nodeId): NodeStatus\nworkflow.getResult(nodeId): CallResult | undefined\nworkflow.isComplete(): boolean\nworkflow.getAggregateStatus(): AggregateStatus\n\n// Lifecycle\nworkflow.abortAll(): void\nworkflow.abortNode(nodeId): void\nworkflow.dispose(): void  // mandatory cleanup — releases signal subscriptions\n```\n\n## Error Hierarchy\n\n```\nFlowgraphError (base)\n├── ConstructionError\n│   ├── DuplicateNodeError    (readonly key)\n│   ├── DuplicateEdgeError    (readonly source, target)\n│   ├── NodeNotFoundError     (readonly key)\n│   ├── CycleError           (readonly cycles: string[][])\n│   └── InvalidInputError    (readonly errors: ValidationError[])\n└── InvalidTransitionError   (readonly requestId, from, to)\n```\n\n## Design Principles\n\n1. **DAG-only, no cycles** — `addEdge()` rejects cycle-creating edges at mutation time (ADR-002). This differs from taskgraph, which allows cycles and detects them after the fact.\n\n2. **Storage is decoupled** — flowgraph handles in-memory graph construction, validation, and analysis. Persistence is the caller's concern. `export()`/`fromJSON()` provides the serialization boundary.\n\n3. **Template → DAG → Execution is a pipeline** — each representation serves a different phase and can exist independently. Validate a template without executing it. Build a call graph from events without a template. Run reactive execution directly from a DAG.\n\n4. **Event log as source of truth** — call protocol events (`call.requested`, `call.responded`, `call.error`, `call.aborted`, `call.completed`) are the ground truth. Status, results, and the call graph are projections derived from the event log (ADR-005).\n\n5. **Delegation, not inheritance** — `FlowGraph` wraps a graphology `DirectedGraph`, exposing a curated API. The raw graphology instance is available via the `.graph` escape hatch.\n\n## For AI Agents\n\nWhen working with this library programmatically:\n\n- **Use subpath imports** — `@alkdev/flowgraph/graph`, `@alkdev/flowgraph/analysis`, etc. The root export re-exports everything but subpath imports make dependencies explicit.\n- **Always call `dispose()` on `WorkflowReactiveRoot`** — signal subscriptions leak without it.\n- **Function-valued props don't survive JSON serialization** — `Conditional.test` and `Map.over` with function values need runtime resolution. Use string references for stored templates.\n- **`fromSpecs()` graphs are conventionally immutable** — don't mutate operation graphs after construction. If the registry changes, rebuild via `fromSpecs()`.\n- **Call graph mutation uses event protocol** — use `updateFromEvent()` or `addCall()`/`updateStatus()`, not direct node mutation.\n- **`typeCompat()` returns `undefined` for `unknown`/`any` schemas** — this means \"no meaningful check possible\", not \"incompatible\".\n- **Architecture specs are in `docs/architecture/`** — detailed design decisions, ADRs, and open questions live there. This README is a surface-level guide. Consult the architecture docs for anything non-trivial.\n\n## Dependencies\n\n| Package | Relationship |\n|---------|-------------|\n| `graphology` | Direct — the underlying directed graph data structure |\n| `graphology-dag` | Direct — topological sort, cycle detection, DAG traversal |\n| `@alkdev/ujsx` | Direct — `UNode` trees and `HostConfig` for workflow template rendering |\n| `@alkdev/typebox` | Direct — all schemas are TypeBox Modules |\n| `@preact/signals-core` | Direct — reactive state management for `WorkflowReactiveRoot` |\n| `@alkdev/operations` | **Peer** — provides `OperationSpec`, `OperationRegistry`, call event types |\n\n## License\n\nDual-licensed under [MIT](LICENSE-MIT) or [Apache-2.0](LICENSE-APACHE) at your option.","readmeFilename":"README.md","_rev":"1-eab19a27bad97b90a18d553629d467ae"}