Draft architecture specification for @alkdev/flowgraph — a workflow graph library providing DAG-based orchestration over operations. Covers two graph types (operation graph, call graph), ujsx workflow templates, GraphologyHost and ReactiveHost configs, signal-driven execution, type-compatibility analysis, error hierarchy, and build/distribution. Includes 3 ADRs: ujsx as template IR, DAG-only enforcement, decoupled storage.
289 lines
11 KiB
Markdown
289 lines
11 KiB
Markdown
---
|
|
status: draft
|
|
last_updated: 2026-05-19
|
|
---
|
|
|
|
# Build & Distribution
|
|
|
|
Package structure, exports map, dependencies, and platform targets.
|
|
|
|
## Package Structure
|
|
|
|
```
|
|
@alkdev/flowgraph/
|
|
├── src/
|
|
│ ├── component/ # ujsx workflow components
|
|
│ │ ├── operation.ts # <Operation> component
|
|
│ │ ├── sequential.ts # <Sequential> component
|
|
│ │ ├── parallel.ts # <Parallel> component
|
|
│ │ ├── conditional.ts # <Conditional> component
|
|
│ │ └── index.ts
|
|
│ ├── host/
|
|
│ │ ├── graphology.ts # GraphologyHostConfig
|
|
│ │ ├── reactive.ts # ReactiveHostConfig
|
|
│ │ └── index.ts
|
|
│ ├── schema/
|
|
│ │ ├── enums.ts # CallStatus, EdgeType, NodeCategory, NodeStatus
|
|
│ │ ├── node.ts # OperationNodeAttrs, CallNodeAttrs
|
|
│ │ ├── edge.ts # TypedEdgeAttrs, CallEdgeAttrs, TemplateEdgeAttrs
|
|
│ │ ├── graph.ts # SerializedGraph, FlowGraphSerialized
|
|
│ │ └── index.ts
|
|
│ ├── graph/
|
|
│ │ ├── construction.ts # FlowGraph class (fromSpecs, fromCallEvents, fromJSON, etc.)
|
|
│ │ ├── validation.ts # validateSchema, validateGraph, validate
|
|
│ │ ├── queries.ts # topologicalOrder, hasCycles, ancestors, descendants, etc.
|
|
│ │ ├── mutation.ts # addNode, addEdge, updateNodeStatus, removeNode, etc.
|
|
│ │ └── index.ts
|
|
│ ├── reactive/
|
|
│ │ ├── workflow.ts # WorkflowReactiveRoot (signal-backed execution)
|
|
│ │ ├── node-status.ts # Signal<NodeStatus>, computed preconditions
|
|
│ │ └── index.ts
|
|
│ ├── analysis/
|
|
│ │ ├── type-compat.ts # typeCompat, buildTypeEdges, analyzeTypeCompat
|
|
│ │ ├── workflow.ts # validateTemplate, validatePreconditions
|
|
│ │ ├── defaults.ts # resolveDefaults for CallStatus, EdgeType, etc.
|
|
│ │ └── index.ts
|
|
│ ├── error/
|
|
│ │ └── index.ts # FlowgraphError hierarchy
|
|
│ └── index.ts # Barrel export
|
|
├── test/
|
|
│ ├── graph/
|
|
│ │ ├── construction.test.ts
|
|
│ │ ├── validation.test.ts
|
|
│ │ ├── queries.test.ts
|
|
│ │ └── mutation.test.ts
|
|
│ ├── schema/
|
|
│ │ └── enums.test.ts
|
|
│ ├── analysis/
|
|
│ │ ├── type-compat.test.ts
|
|
│ │ └── workflow.test.ts
|
|
│ ├── component/
|
|
│ │ └── components.test.ts
|
|
│ ├── host/
|
|
│ │ ├── graphology.test.ts
|
|
│ │ └── reactive.test.ts
|
|
│ └── error/
|
|
│ └── errors.test.ts
|
|
├── package.json
|
|
├── tsconfig.json
|
|
├── tsup.config.ts
|
|
├── vitest.config.ts
|
|
└── AGENTS.md
|
|
```
|
|
|
|
## Package JSON
|
|
|
|
```json
|
|
{
|
|
"name": "@alkdev/flowgraph",
|
|
"version": "0.1.0",
|
|
"type": "module",
|
|
"exports": {
|
|
".": {
|
|
"import": "./dist/index.js",
|
|
"require": "./dist/index.cjs"
|
|
},
|
|
"./component": {
|
|
"import": "./dist/component/index.js",
|
|
"require": "./dist/component/index.cjs"
|
|
},
|
|
"./host": {
|
|
"import": "./dist/host/index.js",
|
|
"require": "./dist/host/index.cjs"
|
|
},
|
|
"./schema": {
|
|
"import": "./dist/schema/index.js",
|
|
"require": "./dist/schema/index.cjs"
|
|
},
|
|
"./graph": {
|
|
"import": "./dist/graph/index.js",
|
|
"require": "./dist/graph/index.cjs"
|
|
},
|
|
"./reactive": {
|
|
"import": "./dist/reactive/index.js",
|
|
"require": "./dist/reactive/index.cjs"
|
|
},
|
|
"./analysis": {
|
|
"import": "./dist/analysis/index.js",
|
|
"require": "./dist/analysis/index.cjs"
|
|
},
|
|
"./error": {
|
|
"import": "./dist/error/index.js",
|
|
"require": "./dist/error/index.cjs"
|
|
}
|
|
},
|
|
"typesVersions": {
|
|
"*": {
|
|
"component": ["./dist/component/index.d.ts"],
|
|
"host": ["./dist/host/index.d.ts"],
|
|
"schema": ["./dist/schema/index.d.ts"],
|
|
"graph": ["./dist/graph/index.d.ts"],
|
|
"reactive": ["./dist/reactive/index.d.ts"],
|
|
"analysis": ["./dist/analysis/index.d.ts"],
|
|
"error": ["./dist/error/index.d.ts"]
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
## Exports Map
|
|
|
|
Following the taskgraph pattern, each module has a sub-path export:
|
|
|
|
| Sub-path | Content | Use case |
|
|
|----------|---------|----------|
|
|
| `@alkdev/flowgraph` | Barrel export (everything) | Full import |
|
|
| `@alkdev/flowgraph/component` | `<Operation>`, `<Sequential>`, `<Parallel>`, `<Conditional>` | Template authoring |
|
|
| `@alkdev/flowgraph/host` | `GraphologyHostConfig`, `ReactiveHostConfig` | ujsx HostConfig implementations |
|
|
| `@alkdev/flowgraph/schema` | TypeBox schemas, enums, types | Schema-only import (no graph dependency) |
|
|
| `@alkdev/flowgraph/graph` | `FlowGraph` class, construction, mutation, queries | Core graph operations |
|
|
| `@alkdev/flowgraph/reactive` | `WorkflowReactiveRoot`, signal-based execution | Runtime execution |
|
|
| `@alkdev/flowgraph/analysis` | `typeCompat`, `validateTemplate`, ordering functions | Analysis and validation |
|
|
| `@alkdev/flowgraph/error` | Error classes | Error handling |
|
|
|
|
## Dependencies
|
|
|
|
### Production Dependencies
|
|
|
|
```json
|
|
{
|
|
"dependencies": {
|
|
"@alkdev/typebox": "workspace:*",
|
|
"@alkdev/ujsx": "workspace:*",
|
|
"@preact/signals-core": "^1.x",
|
|
"graphology": "^0.25",
|
|
"graphology-dag": "^0.4"
|
|
},
|
|
"peerDependencies": {
|
|
"@alkdev/operations": "workspace:*"
|
|
}
|
|
}
|
|
```
|
|
|
|
| Package | Role | Why |
|
|
|---------|------|-----|
|
|
| `@alkdev/typebox` | Schema definitions, validation, `Value.Check`, `Value.Errors` | Direct dependency — all schemas are TypeBox |
|
|
| `@alkdev/ujsx` | UNode, HostConfig, createRoot, h(), ReactiveRoot | Direct dependency — workflow templates are ujsx trees |
|
|
| `@preact/signals-core` | `signal`, `computed`, `effect`, `batch` | Transitive via ujsx, re-exported for flowgraph's reactive layer |
|
|
| `graphology` | `DirectedGraph` data structure | Core graph engine — same as taskgraph |
|
|
| `graphology-dag` | `topologicalSort`, `hasCycle`, `parallelGroups` | DAG-specific algorithms |
|
|
| `@alkdev/operations` | `OperationSpec`, `CallEventMap`, `CallStatus` | Peer dependency — type imports only, no runtime dependency |
|
|
|
|
### Why `@alkdev/operations` is a Peer Dependency
|
|
|
|
Flowgraph imports `OperationSpec`, `CallEventMap`, and `CallStatus` types from `@alkdev/operations`, but does not depend on the runtime (registry, call handler, pending request map). Making it a peer dependency:
|
|
|
|
1. Avoids circular dependency concerns (operations doesn't depend on flowgraph)
|
|
2. Allows flowgraph to work with any version of operations that provides the right types
|
|
3. Reduces bundle size for consumers that don't use operations
|
|
|
|
### Why `@preact/signals-core` via `@alkdev/ujsx`
|
|
|
|
Flowgraph's reactive layer uses `signal()`, `computed()`, and `effect()` from `@preact/signals-core`. These are re-exported from `@alkdev/ujsx/reactive` so consumers don't need to import directly from Preact. If ujsx ever changes its reactive primitive library, only ujsx's re-export needs updating.
|
|
|
|
## Build Configuration
|
|
|
|
### tsup.config.ts
|
|
|
|
Following taskgraph's build pattern:
|
|
|
|
```typescript
|
|
import { defineConfig } from "tsup";
|
|
|
|
export default defineConfig({
|
|
entry: {
|
|
index: "src/index.ts",
|
|
component: "src/component/index.ts",
|
|
host: "src/host/index.ts",
|
|
schema: "src/schema/index.ts",
|
|
graph: "src/graph/index.ts",
|
|
reactive: "src/reactive/index.ts",
|
|
analysis: "src/analysis/index.ts",
|
|
error: "src/error/index.ts",
|
|
},
|
|
format: ["esm", "cjs"],
|
|
dts: true,
|
|
clean: true,
|
|
splitting: true,
|
|
sourcemap: true,
|
|
});
|
|
```
|
|
|
|
- **ESM + CJS dual output** — matches all sibling packages
|
|
- **Code splitting** — enables tree-shaking for sub-path imports
|
|
- **Source maps** — for debugging
|
|
- **Type declarations** — `.d.ts` files for all exports
|
|
|
|
### tsconfig.json
|
|
|
|
```json
|
|
{
|
|
"compilerOptions": {
|
|
"target": "ES2022",
|
|
"module": "Node16",
|
|
"moduleResolution": "Node16",
|
|
"strict": true,
|
|
"declaration": true,
|
|
"declarationMap": true,
|
|
"sourceMap": true,
|
|
"outDir": "./dist",
|
|
"rootDir": "./src",
|
|
"types": ["vitest/globals"]
|
|
},
|
|
"include": ["src/**/*.ts"],
|
|
"exclude": ["node_modules", "dist", "test"]
|
|
}
|
|
```
|
|
|
|
Matches the tsconfig pattern of all `@alkdev` packages: ES2022 target, Node16 module resolution, strict mode.
|
|
|
|
### vitest.config.ts
|
|
|
|
```typescript
|
|
import { defineConfig } from "vitest/config";
|
|
|
|
export default defineConfig({
|
|
test: {
|
|
globals: true,
|
|
},
|
|
});
|
|
```
|
|
|
|
## Platform Targets
|
|
|
|
Following taskgraph's philosophy: **pure JavaScript, no native addons**.
|
|
|
|
| Platform | Support Level | Notes |
|
|
|----------|---------------|-------|
|
|
| Node.js | Primary | All dependencies are pure JS |
|
|
| Deno | Compatible | ESM-first, no Node-specific APIs used |
|
|
| Bun | Compatible | All dependencies are Bun-compatible |
|
|
| Browser | Compatible | graphology and signals-core work in browsers |
|
|
|
|
The library has no native dependencies, no filesystem access, and no Node-specific APIs (no `fs`, `path`, `child_process`, etc.). This makes it platform-agnostic.
|
|
|
|
## Tree-Shaking
|
|
|
|
The sub-path export structure enables effective tree-shaking:
|
|
|
|
- Consumers using only `@alkdev/flowgraph/schema` don't pull in the graph engine or ujsx
|
|
- Consumers using only `@alkdev/flowgraph/analysis` don't pull in the reactive layer
|
|
- Consumers using `@alkdev/flowgraph/component` get ujsx but not graphology (templates can be defined without importing the graph engine)
|
|
|
|
The barrel export (`@alkdev/flowgraph`) re-exports everything for convenience, but consumers concerned about bundle size should use sub-path imports.
|
|
|
|
## Constraints
|
|
|
|
- **No filesystem access** — flowgraph is a pure computation library. Persistence is the hub's concern.
|
|
- **No network access** — no HTTP clients, WebSocket connections, or Redis clients. All data comes in through constructor arguments.
|
|
- **`@alkdev/operations` is a peer dependency** — type imports only, no runtime dependency on the operations registry or call protocol.
|
|
- **ESM-first** — the package is authored in ESM with CJS output generated by tsup. All internal imports use `.js` extensions for Node16 module resolution.
|
|
- **Code splitting enabled** — tsup's `splitting: true` enables optimal code splitting for sub-path imports.
|
|
- **Vitest for testing** — following the monorepo convention.
|
|
|
|
## References
|
|
|
|
- Taskgraph build configuration: `@alkdev/taskgraph_ts/tsup.config.ts`, `@alkdev/taskgraph_ts/tsconfig.json`
|
|
- ujsx build configuration: `@alkdev/ujsx/tsup.config.ts`
|
|
- graphology: https://github.com/graphology/graphology
|
|
- graphology-dag: https://github.com/graphology/graphology-dag |