fix build/distribution spec: npm deps not workspace, align configs with sibling projects, resolve review issues

- Replace workspace:* deps with published npm semver ranges (^0.34.49, ^0.1.0)
- Expand package.json: add description, publishConfig, scripts, engines,
  devDependencies, conditional exports with types/default for import+require
- Fix tsup entry names (path-prefixed like ujsx), add target: es2022,
  remove splitting:true (not used by sibling projects)
- Align tsconfig with sibling projects: add lib, noUncheckedIndexedAccess,
  noUnusedLocals, noUnusedParameters, erasableSyntaxOnly, etc.
- Expand vitest.config.ts with include, coverage, and path alias
- Clarify @preact/signals-core as direct dep (not just transitive via ujsx)
- Clarify @alkdev/pubsub is a consumer dependency, not flowgraph's dep
- Fix edge key convention: document composite key format for call graph's
  multi-edge-type scenario (triggered + depends_on between same pair)
- Align OperationEdgeAttrs field naming: use detail+mismatches consistently
  instead of compatibilityDetail
- Add InvalidInputError to error hierarchy (referenced in flowgraph-api but
  was missing)
- Fix undefined attrs.category reference in reactive-execution.md
- Remove internal drafting note from host-configs.md
- Fix ReactiveHostConfig constructor signature inconsistency across docs
- Constrain TemplateEdgeAttrs.edgeType to sequential|conditional only
This commit is contained in:
2026-05-20 03:09:57 +00:00
parent eaeba38e71
commit da2973e2a6
9 changed files with 251 additions and 92 deletions

View File

@@ -74,59 +74,159 @@ Package structure, exports map, dependencies, and platform targets.
## Package JSON
Following the same pattern as `@alkdev/ujsx` and `@alkdev/operations` — conditional exports with explicit `types` and `default` for both import and require:
```json
{
"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": "./dist/index.js",
"require": "./dist/index.cjs"
"import": {
"types": "./dist/index.d.ts",
"default": "./dist/index.js"
},
"require": {
"types": "./dist/index.d.cts",
"default": "./dist/index.cjs"
}
},
"./component": {
"import": "./dist/component/index.js",
"require": "./dist/component/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": "./dist/host/index.js",
"require": "./dist/host/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": "./dist/schema/index.js",
"require": "./dist/schema/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": "./dist/graph/index.js",
"require": "./dist/graph/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": "./dist/reactive/index.js",
"require": "./dist/reactive/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": "./dist/analysis/index.js",
"require": "./dist/analysis/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": "./dist/error/index.js",
"require": "./dist/error/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"
}
}
},
"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"]
}
"publishConfig": {
"access": "public"
},
"files": [
"dist"
],
"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",
"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"
}
}
```
### Exports Map: Sub-path Imports
The exports map uses the `"/subpath"` format (with leading slash) to match the pattern used by `@alkdev/ujsx`. This is the Node.js subpath exports convention — consumers import as:
```typescript
import { FlowGraph } from "@alkdev/flowgraph/graph";
import { typeCompat } from "@alkdev/flowgraph/analysis";
import { h } from "@alkdev/ujsx";
import { createRoot } from "@alkdev/ujsx/reactive";
```
Each sub-path has conditional exports for both `import` (ESM) and `require` (CJS), with separate `types` files (`.d.ts` and `.d.cts`). The `typesVersions` field is NOT used — conditional exports with `types` entries replace it for modern Node.js resolution.
## Exports Map
Following the taskgraph pattern, each module has a sub-path export:
@@ -149,14 +249,14 @@ Following the taskgraph pattern, each module has a sub-path export:
```json
{
"dependencies": {
"@alkdev/typebox": "workspace:*",
"@alkdev/ujsx": "workspace:*",
"@preact/signals-core": "^1.x",
"graphology": "^0.25",
"graphology-dag": "^0.4"
"@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": "workspace:*"
"@alkdev/operations": "^0.1.0"
}
}
```
@@ -165,7 +265,7 @@ Following the taskgraph pattern, each module has a sub-path export:
|---------|------|-----|
| `@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 |
| `@preact/signals-core` | `signal`, `computed`, `effect`, `batch` | Direct dependency — flowgraph's reactive layer uses signals directly for WorkflowReactiveRoot |
| `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 |
@@ -178,42 +278,49 @@ Flowgraph imports `OperationSpec`, `CallEventMap`, and `CallStatus` types from `
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`
### Why `@preact/signals-core` is a Direct Dependency
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.
While `@preact/signals-core` is also a dependency of `@alkdev/ujsx`, flowgraph lists it as a direct dependency because `WorkflowReactiveRoot` creates `signal()` and `computed()` instances directly. This makes the dependency explicit and ensures version alignment. Consumers import signals from flowgraph's reactive exports, not directly from Preact — the `@alkdev/ujsx/reactive` module re-exports the same signal primitives for consumers who need them, but flowgraph's own reactive module depends on `@preact/signals-core` directly for its internal signal graph construction.
### No `workspace:*` — Published npm Packages
All `@alkdev` packages are independently published to npm. The dependency versions use semver ranges (`^0.34.49`, `^0.1.0`) matching the pattern used by all sibling packages. During local development, the packages exist as local repos on disk, but they are not linked via a monorepo workspace protocol. Each package is built and published independently.
## Build Configuration
### tsup.config.ts
Following taskgraph's build pattern:
Following the same pattern as `@alkdev/taskgraph` and `@alkdev/ujsx` — named entry points matching source file paths, no `splitting` flag (ujsx doesn't use it either):
```typescript
import { defineConfig } from "tsup";
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",
index: 'src/index.ts',
'component/index': 'src/component/index.ts',
'host/index': 'src/host/index.ts',
'schema/index': 'src/schema/index.ts',
'graph/index': 'src/graph/index.ts',
'reactive/index': 'src/reactive/index.ts',
'analysis/index': 'src/analysis/index.ts',
'error/index': 'src/error/index.ts',
},
format: ["esm", "cjs"],
format: ['esm', 'cjs'],
dts: true,
clean: true,
splitting: true,
sourcemap: true,
clean: true,
target: 'es2022',
});
```
- **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
- **`target: 'es2022'`** — matches tsconfig target, ensures consistent output
- **Named entry points with path prefixes** — matches ujsx pattern (e.g., `'core/schema': 'src/core/schema.ts'`), producing output like `dist/component/index.js` consistent with the exports map
Note: The `splitting: true` option was in an earlier draft but has been removed. The sibling projects (taskgraph, ujsx, operations) don't use splitting, and the named entry points with sub-paths already provide natural code boundaries for tree-shaking.
### tsconfig.json
@@ -221,31 +328,55 @@ export default defineConfig({
{
"compilerOptions": {
"target": "ES2022",
"lib": ["ES2022"],
"module": "Node16",
"moduleResolution": "Node16",
"outDir": "./dist",
"rootDir": "./src",
"strict": true,
"declaration": true,
"declarationMap": true,
"sourceMap": true,
"outDir": "./dist",
"rootDir": "./src",
"types": ["vitest/globals"]
"noUncheckedIndexedAccess": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"resolveJsonModule": true,
"isolatedModules": true,
"erasableSyntaxOnly": true
},
"include": ["src/**/*.ts"],
"exclude": ["node_modules", "dist", "test"]
}
```
Matches the tsconfig pattern of all `@alkdev` packages: ES2022 target, Node16 module resolution, strict mode.
Matches the tsconfig patterns of all `@alkdev` packages: ES2022 target, Node16 module resolution, strict mode with additional safety checks (`noUncheckedIndexedAccess`, `noUnusedLocals`, `noUnusedParameters`, `noFallthroughCasesInSwitch`, `erasableSyntaxOnly`).
### vitest.config.ts
Following the same pattern as `@alkdev/taskgraph` and `@alkdev/ujsx`:
```typescript
import { defineConfig } from "vitest/config";
import { defineConfig } from 'vitest/config';
import path from 'node:path';
export default defineConfig({
resolve: {
alias: {
'@': path.resolve(__dirname, 'src'),
},
},
test: {
globals: true,
include: ['test/**/*.test.ts'],
coverage: {
provider: 'v8',
include: ['src/**/*.ts'],
exclude: ['src/**/index.ts'],
},
},
});
```
@@ -354,8 +485,8 @@ All error paths should be tested:
- **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.
- **Sub-path exports provide natural code boundaries** — named entry points in tsup match source file paths, enabling effective tree-shaking without `splitting: true`.
- **Vitest for testing** — following the convention of all `@alkdev` packages.
## References