add flowgraph architecture docs (Phase 1 SDD)
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.
This commit is contained in:
289
docs/architecture/build-distribution.md
Normal file
289
docs/architecture/build-distribution.md
Normal file
@@ -0,0 +1,289 @@
|
||||
---
|
||||
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
|
||||
Reference in New Issue
Block a user