Files
flowgraph/docs/architecture/build-distribution.md
glm-5.1 d2253099ee 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.
2026-05-19 09:36:22 +00:00

11 KiB

status, last_updated
status last_updated
draft 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

{
  "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

{
  "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:

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

{
  "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

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