@multiplier-labs/stepflow 0.3.1 → 0.3.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.codekin/reports/code-review/2026-04-11_code-review-daily.md +146 -0
- package/.codekin/reports/code-review/2026-04-13_code-review-daily.md +74 -0
- package/.codekin/reports/comments/2026-04-04_comment-assessment.md +179 -0
- package/.codekin/reports/comments/2026-04-11_comment-assessment.md +161 -0
- package/.codekin/reports/comments/2026-04-14_comment-assessment.md +34 -0
- package/.codekin/reports/complexity/2026-04-03_complexity-report.md +152 -0
- package/.codekin/reports/complexity/2026-04-11_complexity-report.md +152 -0
- package/.codekin/reports/complexity/2026-04-14_complexity-report.md +34 -0
- package/.codekin/reports/dependencies/2026-04-04_dependency-health.md +94 -0
- package/.codekin/reports/dependencies/2026-04-11_dependency-health.md +93 -0
- package/.codekin/reports/docs-audit/2026-04-04_docs-audit.md +10 -0
- package/.codekin/reports/docs-audit/2026-04-11_docs-audit.md +138 -0
- package/.codekin/reports/docs-audit/2026-04-13_docs-audit.md +121 -0
- package/.codekin/reports/security/2026-04-03_security-audit.md +208 -0
- package/.codekin/reports/security/2026-04-11_security-audit.md +297 -0
- package/.github/CODEOWNERS +15 -0
- package/.github/dependabot.yml +23 -0
- package/.github/workflows/ci.yml +38 -0
- package/.github/workflows/publish.yml +17 -5
- package/.github/workflows/security.yml +29 -0
- package/CHANGELOG.md +34 -0
- package/README.md +24 -0
- package/SECURITY.md +40 -0
- package/dist/{chunk-FQCT3I2A.js → chunk-5XJBAPXF.js} +527 -551
- package/dist/chunk-5XJBAPXF.js.map +1 -0
- package/dist/{chunk-U5QGN3RT.js → chunk-FMPJBK2N.js} +67 -55
- package/dist/chunk-FMPJBK2N.js.map +1 -0
- package/dist/{chunk-GXY66V7Q.js → chunk-JPXPB7U5.js} +52 -2
- package/dist/chunk-JPXPB7U5.js.map +1 -0
- package/dist/{chunk-XCF4PLN7.js → chunk-U3PCVK6O.js} +276 -61
- package/dist/chunk-U3PCVK6O.js.map +1 -0
- package/dist/events/index.d.ts +60 -7
- package/dist/events/index.js +1 -1
- package/dist/{index-CPfGsmrp.d.ts → index-Cd0IZ40L.d.ts} +56 -17
- package/dist/index.d.ts +15 -23
- package/dist/index.js +784 -547
- package/dist/index.js.map +1 -1
- package/dist/scheduler/index.d.ts +4 -4
- package/dist/scheduler/index.js +2 -2
- package/dist/storage/index.d.ts +322 -56
- package/dist/storage/index.js +4 -2
- package/dist/{types-BlwFEGzj.d.ts → types-DSEjiLMj.d.ts} +16 -15
- package/dist/{types-DzkEqnQJ.d.ts → types-DZUdWmCc.d.ts} +2 -2
- package/dist/{types-BVShq7zT.d.ts → types-K5Gjk3H_.d.ts} +4 -4
- package/docs/stepflow-api-reference.md +5 -4
- package/package.json +12 -8
- package/src/core/engine/concurrency.ts +229 -0
- package/src/core/engine/lifecycle.ts +176 -0
- package/src/core/engine.test.ts +536 -522
- package/src/core/engine.ts +116 -235
- package/src/core/index.ts +3 -3
- package/src/core/orchestrator/persistence.ts +165 -0
- package/src/core/orchestrator/plan-resolution.ts +140 -0
- package/src/core/orchestrator/state-transitions.ts +109 -0
- package/src/core/orchestrator/step-runner.ts +427 -0
- package/src/core/orchestrator.ts +57 -395
- package/src/core/types.ts +7 -29
- package/src/events/index.ts +4 -4
- package/src/events/memory.test.ts +39 -41
- package/src/events/memory.ts +7 -10
- package/src/events/socketio.test.ts +112 -162
- package/src/events/socketio.ts +20 -27
- package/src/events/types.ts +16 -19
- package/src/events/webhook.test.ts +489 -235
- package/src/events/webhook.ts +355 -139
- package/src/index.ts +19 -19
- package/src/planning/evaluate-condition.test.ts +153 -0
- package/src/planning/evaluate-condition.ts +105 -0
- package/src/planning/index.ts +6 -3
- package/src/planning/planner.ts +45 -152
- package/src/planning/planning.test.ts +303 -366
- package/src/planning/registry.test.ts +204 -251
- package/src/planning/registry.ts +20 -106
- package/src/planning/types.ts +23 -27
- package/src/scheduler/cron.test.ts +149 -150
- package/src/scheduler/cron.ts +58 -69
- package/src/scheduler/index.ts +4 -14
- package/src/scheduler/postgres-persistence.ts +89 -105
- package/src/scheduler/sqlite-persistence.test.ts +96 -96
- package/src/scheduler/sqlite-persistence.ts +40 -22
- package/src/scheduler/types.ts +4 -9
- package/src/storage/index.ts +5 -5
- package/src/storage/memory.test.ts +133 -183
- package/src/storage/memory.ts +21 -42
- package/src/storage/postgres-core.ts +550 -0
- package/src/storage/postgres-migration.test.ts +69 -0
- package/src/storage/postgres-transaction-pagination.test.ts +107 -0
- package/src/storage/postgres.ts +222 -937
- package/src/storage/sqlite-core.ts +151 -0
- package/src/storage/sqlite.test.ts +330 -281
- package/src/storage/sqlite.ts +225 -170
- package/src/storage/types.ts +33 -58
- package/src/utils/errors.test.ts +78 -80
- package/src/utils/errors.ts +27 -57
- package/src/utils/id.ts +3 -3
- package/src/utils/index.ts +6 -4
- package/src/utils/logger.test.ts +52 -58
- package/src/utils/logger.ts +4 -4
- package/src/utils/pinned-fetch.test.ts +84 -0
- package/src/utils/pinned-fetch.ts +118 -0
- package/src/utils/postgres-deps.ts +20 -4
- package/src/utils/retry.test.ts +47 -67
- package/src/utils/retry.ts +9 -9
- package/src/utils/safe-json.test.ts +166 -0
- package/src/utils/safe-json.ts +107 -0
- package/src/utils/webhook-signature.test.ts +111 -0
- package/src/utils/webhook-signature.ts +74 -0
- package/dist/chunk-FQCT3I2A.js.map +0 -1
- package/dist/chunk-GXY66V7Q.js.map +0 -1
- package/dist/chunk-U5QGN3RT.js.map +0 -1
- package/dist/chunk-XCF4PLN7.js.map +0 -1
- package/src/scheduler/postgres-persistence.test.ts +0 -906
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
# Complexity Report: stepflow
|
|
2
|
+
|
|
3
|
+
**Date**: 2026-04-03T20:01:36.000Z
|
|
4
|
+
**Repository**: /srv/repos/stepflow
|
|
5
|
+
**Branch**: main
|
|
6
|
+
**Workflow Run**: ba52b549-8f86-4537-b95d-af7bbe803812
|
|
7
|
+
**Session**: 456e9325-f315-4a53-8b5f-e02d09d44a08
|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
Now I have sufficient data to produce the full report.
|
|
12
|
+
|
|
13
|
+
## Summary
|
|
14
|
+
|
|
15
|
+
**Overall Complexity Rating: Medium**
|
|
16
|
+
|
|
17
|
+
The codebase is a TypeScript workflow orchestration library (~8,700 source lines across 19 source files, 14,600 including tests). It is well-structured overall, with clear module boundaries and good separation between execution, storage, scheduling, and events. The main complexity hotspots are: (1) `postgres.ts` at 1,354 lines — a large god class with dual interface implementations, (2) `orchestrator.ts:executeStep` — deeply nested retry/cancellation logic, and (3) an unresolved migration from a legacy `StorageAdapter` interface to a newer `WorkflowStorage` interface that introduces duplicated type hierarchies and dead code paths.
|
|
18
|
+
|
|
19
|
+
- **Largest file**: `src/storage/postgres.ts` — 1,354 lines
|
|
20
|
+
- **Deepest nesting**: `orchestrator.ts:executeStep` — ~4 levels in the retry/cancellation catch block
|
|
21
|
+
- **Most complex function**: `executeStep` in `orchestrator.ts` — ~185 lines, retry loop, 4 error paths, 5+ storage/event calls per iteration
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
## Largest Files
|
|
26
|
+
|
|
27
|
+
| File | Lines | Primary Responsibility | Refactor Priority |
|
|
28
|
+
|------|-------|------------------------|-------------------|
|
|
29
|
+
| `src/storage/postgres.ts` | 1,354 | PostgreSQL storage adapter (DDL + CRUD + extended API) | **High** |
|
|
30
|
+
| `src/core/engine.ts` | 692 | WorkflowEngine class (registry, run lifecycle, queue) | Medium |
|
|
31
|
+
| `src/storage/sqlite.ts` | 621 | SQLite storage adapter | Medium |
|
|
32
|
+
| `src/planning/planner.ts` | 576 | Rule-based recipe selection and plan generation | Low |
|
|
33
|
+
| `src/planning/types.ts` | 566 | Planning type definitions | Low |
|
|
34
|
+
| `src/core/orchestrator.ts` | 564 | Workflow + step execution logic | **High** |
|
|
35
|
+
| `src/events/webhook.ts` | 557 | Webhook event transport + SSRF protection | Medium |
|
|
36
|
+
| `src/scheduler/postgres-persistence.ts` | 512 | PostgreSQL schedule persistence | Medium |
|
|
37
|
+
| `src/scheduler/cron.ts` | 414 | CronScheduler (polling + event triggers) | Low |
|
|
38
|
+
| `src/planning/registry.ts` | 365 | Recipe and step handler registries | Low |
|
|
39
|
+
| `src/storage/types.ts` | 342 | Storage interface definitions (legacy + new) | **High** |
|
|
40
|
+
| `src/index.ts` | 270 | Public API re-exports | Low |
|
|
41
|
+
| `src/events/socketio.ts` | 262 | Socket.IO event transport | Low |
|
|
42
|
+
| `src/scheduler/sqlite-persistence.ts` | 261 | SQLite schedule persistence | Low |
|
|
43
|
+
| `src/storage/memory.ts` | 227 | In-memory storage adapter | Low |
|
|
44
|
+
|
|
45
|
+
---
|
|
46
|
+
|
|
47
|
+
## Most Complex Functions
|
|
48
|
+
|
|
49
|
+
| File:Function | Estimated Complexity | Issue Description | Refactor Suggestion |
|
|
50
|
+
|---------------|---------------------|-------------------|---------------------|
|
|
51
|
+
| `orchestrator.ts:executeStep` | High | ~185 lines; infinite `while(true)` retry loop; 4 distinct exit paths (success, skip, retry, fail); 5+ storage/event calls per iteration; 4-level nesting in catch block | Extract `handleStepError()` and `executeStepWithTimeout()` helper; separate retry orchestration from event emission |
|
|
52
|
+
| `orchestrator.ts:executeWorkflow` | High | ~220 lines; manages hooks, checkpointing, timeouts, cancellation, success/failure divergence; complex error classification logic (lines 237–244) | Split into `runSteps()` + `finalizeRun()` helpers; extract error classification into a pure function |
|
|
53
|
+
| `postgres.ts:createTables` | High | ~155 lines; 20+ sequential `sql\`...\`.execute(this.db)` calls; mixes initial DDL with alter-column migration; silent `.catch(() => {})` suppresses errors | Split into `createSchemaTables()` and `runMigrations()`; use a proper migration runner; avoid silent catch |
|
|
54
|
+
| `postgres.ts:mapRunRow` | Medium | Repeated `typeof row.X === 'string' ? JSON.parse(row.X) : row.X` guard on every JSON column — 8 repetitions in one function | Extract a `parseJsonColumn(col: unknown): unknown` helper shared across all map methods |
|
|
55
|
+
| `postgres.ts:createRun` | Medium | Accepts a union type `CreateRunInput \| Omit<WorkflowRunRecord, …>` — forces `'id' in run`, `'parentRunId' in run`, etc. inline discriminators throughout the method body | Consolidate to a single input type; bridge at the call site rather than inside the method |
|
|
56
|
+
| `sqlite.ts:prepareStatements` | Medium | Bulk-prepares 15 SQL statements in one method; `listRuns` uses double-binding pattern (`kind, kind`, `statusJson, statusJson`) which is fragile and easily mis-ordered | Each statement can be lazily prepared on first use; document or encapsulate the double-bind idiom |
|
|
57
|
+
| `planner.ts:generatePlan` | Medium | Applies three independent mutation passes (hints, constraints, config) over the steps array using nested loops; emits `PlanModification` records as a side effect | Extract `applyHints()`, `applyAdditionalConfig()` as pure functions returning `{ steps, modifications }` |
|
|
58
|
+
| `engine.ts:startRun` | Medium | Handles both the immediate-start and queue paths, emits multiple events, performs queue insertion — 60 lines with branching and inline event building | Extract `enqueueOrExecute()` helper to isolate the concurrency decision; move event building to a factory |
|
|
59
|
+
| `webhook.ts:sendWebhook` | Medium | Retry loop, timeout via AbortController, SSRF re-validation on every send, payload signing, concurrency queue interaction — all in one method | SSRF validation at `addEndpoint` time only (URL is static); extract `buildHeaders()` |
|
|
60
|
+
| `planner.ts:evaluateCondition` | Low–Medium | 10-case switch statement; regex instantiated on every call via `new RE2(conditionValue)` inside the `matches` branch | Cache compiled RE2 instances per pattern; no structural issue but the hot-path instantiation is a performance concern |
|
|
61
|
+
|
|
62
|
+
---
|
|
63
|
+
|
|
64
|
+
## Coupling & Cohesion Issues
|
|
65
|
+
|
|
66
|
+
1. **Dual storage interface (`StorageAdapter` vs. `WorkflowStorage`) in `storage/types.ts`**
|
|
67
|
+
- **Problem**: The file defines two nearly-identical storage interfaces: the legacy `StorageAdapter` (used by the engine) and a new `WorkflowStorage` (used by `PostgresStorageAdapter` internally). `PostgresStorageAdapter` implements both simultaneously. This doubles the number of types for runs/steps (`WorkflowRunRecord` vs. `ExtendedWorkflowRunRecord`, `RunStatus` vs. `ExtendedRunStatus`, `StepStatus` vs. `ExtendedStepStatus`, `ListRunsOptions` vs. `ExtendedListRunsOptions`).
|
|
68
|
+
- **Suggested Fix**: Pick one interface as canonical and migrate all adapters to it; keep the other as a shim alias during transition. The duplicated status union types should be unified.
|
|
69
|
+
|
|
70
|
+
2. **`postgres.ts` is a god class**
|
|
71
|
+
- **Problem**: `PostgresStorageAdapter` (1,354 lines) is responsible for: connection pool management, lazy dependency loading, DDL/migration, all CRUD operations for three tables, the extended `WorkflowStorage` API (7 additional methods), row-mapping helpers, atomic dequeue, and transaction wrapping. Unrelated responsibilities are co-located.
|
|
72
|
+
- **Suggested Fix**: Extract `PostgresMigrationRunner` (DDL), `PostgresRunRepository`, and `PostgresStepRepository` as internal collaborators.
|
|
73
|
+
|
|
74
|
+
3. **Lazy-loaded module globals in `postgres.ts` and `postgres-persistence.ts`**
|
|
75
|
+
- **Problem**: `let Kysely: any; let PostgresDialect: any; let sql: any; let pgModule: any;` are module-level mutable variables populated by `loadPostgresDeps()` during `initialize()`. These are effectively global singletons shared across all adapter instances. Concurrent `initialize()` calls could create a race condition. All type safety is lost (`any`).
|
|
76
|
+
- **Suggested Fix**: Store loaded dependencies as instance properties populated in `initialize()`, or use a module-level `Promise` cache so the load happens exactly once and the result is strongly typed.
|
|
77
|
+
|
|
78
|
+
4. **`executeStep` is tightly coupled to storage, events, logger, and abort controller**
|
|
79
|
+
- **Problem**: The function signature passes a 5-field `StepExecutionOptions` object, and inside it makes direct calls to `storage.createStep`, `storage.updateStep`, `events.emit` (×5–6 per iteration), `definition.hooks.*` (×3), and `abortController.signal`. It is essentially untestable in isolation without mocking all infrastructure.
|
|
80
|
+
- **Suggested Fix**: Extract an `StepExecutionContext` interface that provides `onStarted`, `onSucceeded`, `onFailed`, `onCanceled` callbacks; the orchestrator wires these to storage/events; the step function only calls the context.
|
|
81
|
+
|
|
82
|
+
5. **`executeRun` is a no-op pass-through in `engine.ts`**
|
|
83
|
+
- **Problem**: `executeRun` (lines 324–332) calls `launchRun` with identical arguments. It exists only as an indirection, adding a layer of abstraction with no logic.
|
|
84
|
+
- **Suggested Fix**: Remove `executeRun`; call `launchRun` directly from `startRun` and `processQueue`.
|
|
85
|
+
|
|
86
|
+
6. **`SQLiteStorageAdapter.transaction()` misrepresents its async contract**
|
|
87
|
+
- **Problem**: The `transaction()` method (marked `@deprecated`) has an `async` signature but actually runs synchronously via `better-sqlite3`. The JSDoc warns "only works for synchronous operations," but callers expecting async-safe semantics will be misled.
|
|
88
|
+
- **Suggested Fix**: Remove the method (it is already deprecated); expose only `transactionSync()`.
|
|
89
|
+
|
|
90
|
+
---
|
|
91
|
+
|
|
92
|
+
## Refactoring Candidates
|
|
93
|
+
|
|
94
|
+
1. **Unify the dual storage interface hierarchy**
|
|
95
|
+
- **Location**: `src/storage/types.ts`, `src/storage/postgres.ts`, `src/storage/sqlite.ts`, `src/storage/memory.ts`
|
|
96
|
+
- **Problem**: Two parallel type families (`StorageAdapter`/`WorkflowStorage`, `WorkflowRunRecord`/`ExtendedWorkflowRunRecord`, `RunStatus`/`ExtendedRunStatus`) exist as an in-progress migration that was never completed. All consumers must understand both, and `PostgresStorageAdapter` implements both simultaneously.
|
|
97
|
+
- **Suggested Approach**: Merge the two interfaces; remove the `Extended*` duplicates by upgrading `RunStatus` to include `'pending'` and `'timeout'` and adding `output` to the base record. Adapters can implement a single interface.
|
|
98
|
+
- **Effort**: Large
|
|
99
|
+
|
|
100
|
+
2. **Extract `PostgresMigrationRunner` from `PostgresStorageAdapter`**
|
|
101
|
+
- **Location**: `src/storage/postgres.ts:314–465` (`createTables`)
|
|
102
|
+
- **Problem**: 155 lines of sequential DDL mixed with the adapter. It also silently swallows `ALTER TABLE` errors, making it hard to reason about migration state.
|
|
103
|
+
- **Suggested Approach**: Move all DDL into a standalone `PostgresMigrationRunner` class with versioned migrations and explicit error handling. `PostgresStorageAdapter` calls `runner.migrate()` during `initialize()`.
|
|
104
|
+
- **Effort**: Medium
|
|
105
|
+
|
|
106
|
+
3. **Decompose `executeStep` in the orchestrator**
|
|
107
|
+
- **Location**: `src/core/orchestrator.ts:296–479`
|
|
108
|
+
- **Problem**: ~185-line function with 4 exit paths, 4-level nesting, and direct coupling to all infrastructure. The retry delay sleep path duplicates cancellation handling from the outer path.
|
|
109
|
+
- **Suggested Approach**: Extract `handleStepSuccess()`, `handleStepCancellation()`, and `handleStepRetry()` — each taking the step record and emitting the right events/storage calls. The main function becomes a loop calling these.
|
|
110
|
+
- **Effort**: Medium
|
|
111
|
+
|
|
112
|
+
4. **Fix lazy-loaded dependency globals in postgres adapters**
|
|
113
|
+
- **Location**: `src/storage/postgres.ts:13–16`, `src/scheduler/postgres-persistence.ts:11–15`
|
|
114
|
+
- **Problem**: `let Kysely: any` etc. as module-level mutable globals. Both files duplicate this pattern. Type safety lost, race condition risk on concurrent initialization.
|
|
115
|
+
- **Suggested Approach**: Create a shared `src/utils/postgres-deps.ts` module (it may partially exist as `loadPostgresDeps`) that returns a typed, promise-cached result; store the resolved deps as instance properties.
|
|
116
|
+
- **Effort**: Small
|
|
117
|
+
|
|
118
|
+
5. **Cache RE2 instances in `evaluateCondition`**
|
|
119
|
+
- **Location**: `src/planning/planner.ts:88–93`
|
|
120
|
+
- **Problem**: `new RE2(conditionValue)` is called on every condition evaluation. In a hot planning path with many recipes and conditions, this allocates a new compiled regex object on each call.
|
|
121
|
+
- **Suggested Approach**: Add a `Map<string, RE2>` cache at module scope or on the `RuleBasedPlanner` instance; look up or create before matching.
|
|
122
|
+
- **Effort**: Small
|
|
123
|
+
|
|
124
|
+
6. **Remove `executeRun` pass-through in `WorkflowEngine`**
|
|
125
|
+
- **Location**: `src/core/engine.ts:323–332`
|
|
126
|
+
- **Problem**: `executeRun` adds a layer of indirection with zero logic, making the call chain harder to follow (`startRun → executeRun → launchRun`).
|
|
127
|
+
- **Suggested Approach**: Delete `executeRun`; call `launchRun` directly from `startRun` (line 270) and `processQueue` (line 392).
|
|
128
|
+
- **Effort**: Small
|
|
129
|
+
|
|
130
|
+
7. **Extract a `parseJsonColumn` helper in `postgres.ts` row mappers**
|
|
131
|
+
- **Location**: `src/storage/postgres.ts:861–940` (four `map*Row` methods)
|
|
132
|
+
- **Problem**: The guard `typeof row.X === 'string' ? JSON.parse(row.X) : row.X` is repeated ~10 times across four mapper methods, indicating that Kysely returns JSONB as either a string or a parsed object depending on configuration.
|
|
133
|
+
- **Suggested Approach**: Extract `parseJsonColumn(col: unknown): unknown` and call it from all mappers. Alternatively, configure Kysely with a `pg.types.setTypeParser` for the JSONB OID to always return parsed objects, eliminating the guard entirely.
|
|
134
|
+
- **Effort**: Small
|
|
135
|
+
|
|
136
|
+
8. **Encapsulate the SQLite double-binding pattern**
|
|
137
|
+
- **Location**: `src/storage/sqlite.ts:329–341` (`listRuns`)
|
|
138
|
+
- **Problem**: Prepared statements use `? IS NULL OR field = ?` which requires each parameter to be passed twice: `kind, kind` / `statusJson, statusJson` / `parentRunId, parentRunId`. This is fragile — if parameter order changes, bugs are silent.
|
|
139
|
+
- **Suggested Approach**: Add a small helper `nullableParam(val)` returning `[val, val]` to make the doubling explicit and documented, or switch to Kysely for SQLite queries to get type-safe query building.
|
|
140
|
+
- **Effort**: Small
|
|
141
|
+
|
|
142
|
+
9. **Remove or complete the deprecated `SQLiteStorageAdapter.transaction()` method**
|
|
143
|
+
- **Location**: `src/storage/sqlite.ts:440–446`
|
|
144
|
+
- **Problem**: The `async transaction()` method has a misleading contract — it wraps a synchronous transaction with an async signature, only works if the callback does no real async I/O, and is deprecated. It remains in the public API creating a footgun.
|
|
145
|
+
- **Suggested Approach**: Remove the method in the next minor version (or immediately if semver allows). Consumers should use `transactionSync()`. Add a clear deprecation notice in the changelog.
|
|
146
|
+
- **Effort**: Small
|
|
147
|
+
|
|
148
|
+
10. **Consolidate magic timeout/retry defaults into named constants**
|
|
149
|
+
- **Location**: `src/core/orchestrator.ts:304–307`, `src/core/engine.ts:484`, `src/events/webhook.ts:145–151`
|
|
150
|
+
- **Problem**: Default values like `maxRetries: 3`, `retryDelay: 1000`, `retryBackoff: 2`, `timeout: 60000`, `defaultTimeout: 5000` are magic numbers scattered across files. They are inconsistent (`orchestrator.ts` defaults `maxRetries` to 3 inline; `webhook.ts` defaults `defaultRetries` to 3 separately).
|
|
151
|
+
- **Suggested Approach**: Collect all engine defaults into a `src/core/defaults.ts` constants file (`DEFAULT_MAX_RETRIES`, `DEFAULT_RETRY_DELAY_MS`, `DEFAULT_WAIT_TIMEOUT_MS`, etc.) and reference them everywhere.
|
|
152
|
+
- **Effort**: Small
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
# Complexity Report: stepflow
|
|
2
|
+
|
|
3
|
+
**Date**: 2026-04-11T02:46:40.781Z
|
|
4
|
+
**Repository**: /srv/repos/stepflow
|
|
5
|
+
**Branch**: main
|
|
6
|
+
**Workflow Run**: 7c4174ae-8cba-4ad8-9a30-b2f56cf4c98b
|
|
7
|
+
**Session**: 474ba458-5157-4ff3-8ef4-802b1bc389e5
|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
Now I have enough information to produce the report.
|
|
12
|
+
|
|
13
|
+
## Summary
|
|
14
|
+
|
|
15
|
+
**Overall Complexity Rating: Medium**
|
|
16
|
+
|
|
17
|
+
The codebase is a TypeScript workflow orchestration library (~14,300 lines across source + tests). The implementation is generally clean and well-structured, but several files are oversized, the storage layer has two parallel interface lineages with significant duplication, and the orchestrator's `executeStep` function carries the highest cyclomatic complexity. No deeply pathological nesting exists, but coupling between the `postgres.ts` adapter and two different storage interfaces is the most acute maintenance risk.
|
|
18
|
+
|
|
19
|
+
Key metrics:
|
|
20
|
+
- **Largest file:** `src/storage/postgres.ts` — 1,354 lines
|
|
21
|
+
- **Deepest nesting:** `executeStep` in `orchestrator.ts` — 4–5 levels (`while → try → if → if → try`)
|
|
22
|
+
- **Most complex function:** `executeStep` — retry loop, cancellation guards, hooks, event emission, 4 error-handling branches
|
|
23
|
+
- **Duplicate interface footprint:** `StorageAdapter` vs. `WorkflowStorage` — two complete CRUD contracts implemented in the same `postgres.ts` class
|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
## Largest Files
|
|
28
|
+
|
|
29
|
+
| File | Lines | Primary Responsibility | Refactor Priority |
|
|
30
|
+
|------|-------|------------------------|-------------------|
|
|
31
|
+
| `src/storage/postgres.ts` | 1,354 | PostgreSQL adapter (implements both `StorageAdapter` and `WorkflowStorage`) | **High** |
|
|
32
|
+
| `src/core/engine.test.ts` | 1,364 | Engine integration tests | Low (test file) |
|
|
33
|
+
| `src/planning/planning.test.ts` | 727 | Planner integration tests | Low (test file) |
|
|
34
|
+
| `src/events/webhook.test.ts` | 657 | Webhook transport tests | Low (test file) |
|
|
35
|
+
| `src/core/engine.ts` | 692 | `WorkflowEngine` class — run lifecycle, queue, resume | Medium |
|
|
36
|
+
| `src/storage/sqlite.ts` | 621 | SQLite adapter | Medium |
|
|
37
|
+
| `src/planning/planner.ts` | 576 | `RuleBasedPlanner` — recipe selection, plan generation | Low |
|
|
38
|
+
| `src/planning/types.ts` | 566 | Planning domain types (Recipe, Plan, Planner interfaces) | Low |
|
|
39
|
+
| `src/core/orchestrator.ts` | 564 | Step execution loop, retry logic, hooks | **High** |
|
|
40
|
+
| `src/events/webhook.ts` | 557 | WebhookEventTransport — SSRF guards, signing, concurrency | Medium |
|
|
41
|
+
| `src/scheduler/postgres-persistence.ts` | 512 | PostgreSQL schedule persistence | Low |
|
|
42
|
+
| `src/scheduler/cron.ts` | 414 | CronScheduler — polling + event-driven triggers | Low |
|
|
43
|
+
| `src/storage/types.ts` | 342 | Storage type definitions (two parallel interface families) | **High** |
|
|
44
|
+
| `src/events/socketio.ts` | 262 | Socket.IO event transport | Low |
|
|
45
|
+
| `src/index.ts` | 270 | Public API barrel file | Low |
|
|
46
|
+
|
|
47
|
+
---
|
|
48
|
+
|
|
49
|
+
## Most Complex Functions
|
|
50
|
+
|
|
51
|
+
| File:Function | Estimated Complexity | Issue Description | Refactor Suggestion |
|
|
52
|
+
|---------------|---------------------|-------------------|---------------------|
|
|
53
|
+
| `orchestrator.ts:executeStep` | Very High (CC ~12) | Single `while(true)` loop wraps: `AbortSignal` check, step record creation, `beforeStep` hook, conditional timeout vs. `raceWithAbort`, `afterStep` hook, cancellation detection, `onStepError` hook, three error branches (`skip`/`retry`/`fail`), sleep-with-abort, and re-throw. 4–5 nesting levels. | Extract `handleStepError(error, attempt, strategy)` and `executeStepOnce(step, context, options)` as separate functions |
|
|
54
|
+
| `orchestrator.ts:executeWorkflow` | High (CC ~8) | Combines run status update, event emission, context construction, timeout setup, `beforeRun` hook, step loop with skip checks, `afterRun` hook, success/failure/timeout/cancel branching in one try/catch | Split success path and failure classification into `finalizeSuccess()` / `finalizeFailure()` helpers |
|
|
55
|
+
| `storage/postgres.ts:createTables` | High (CC ~7) | 150-line method issues 15+ raw SQL statements sequentially with ad-hoc `catch(() => {})` for ALTER TABLE idempotency. No migration versioning. | Replace with a proper migration runner (numbered migration files or Kysely-migrate) |
|
|
56
|
+
| `storage/postgres.ts:mapRunRow` | Medium (CC ~6) | Repeated `typeof x === 'string' ? JSON.parse(x) : x` ternary for every JSONB column (6 occurrences in `mapRunRow` alone, duplicated in `mapExtendedRunRow`) | Extract `parseJsonColumn<T>(col: string | T): T` helper; used in all `mapXRow` methods |
|
|
57
|
+
| `events/webhook.ts:sendWebhook` | Medium (CC ~6) | Retry loop with DNS validation re-run on every attempt, payload size check, dynamic header assembly, HMAC signing, and `AbortController` per attempt | Extract `deliverOnce(url, headers, body, timeout)` and call from `sendWebhookWithRetry` |
|
|
58
|
+
| `events/webhook.ts:isBlockedHost` | Medium (CC ~9) | Flat function with 9 early-return branches testing IPv4 ranges, IPv6 literals, loopback, link-local — logic duplicated almost identically in `isBlockedIp` | Merge `isBlockedHost` and `isBlockedIp` into a single `isBlockedAddress(address: string)` that handles both hostname and resolved-IP cases |
|
|
59
|
+
| `planner.ts:selectRecipe` | Medium (CC ~6) | Three sequential priority checks (forced, preferred, scored) each with early returns, plus a fallback chain of two more returns | Already reasonably structured; document the scoring ladder with inline comments |
|
|
60
|
+
| `storage/postgres.ts:updateRun` | Medium (CC ~5) | Builds `updateData` object via 6 independent `if` guards covering two different input shapes (`UpdateRunInput` and `Partial<WorkflowRunRecord>`) simultaneously | Accept only `UpdateRunInput`; deprecate the `Partial<WorkflowRunRecord>` overload |
|
|
61
|
+
| `engine.ts:waitForRun` | Medium (CC ~5) | Interleaves early-return for already-completed runs with Promise-based event subscription, dual cleanup via `cleanup()` closure, nested `try/catch` inside the event callback | Extract the Promise setup into a `awaitRunEvent(runId, timeout)` utility |
|
|
62
|
+
| `planner.ts:evaluateCondition` | Low-Medium (CC ~11) | Large `switch` on 9 operators; each case is self-contained but the function is called in a hot loop for every condition on every recipe | Acceptable as-is; consider a `conditionEvaluators: Record<ConditionOperator, Evaluator>` map if operators are user-extensible |
|
|
63
|
+
|
|
64
|
+
---
|
|
65
|
+
|
|
66
|
+
## Coupling & Cohesion Issues
|
|
67
|
+
|
|
68
|
+
**1. `storage/postgres.ts` implements two unrelated storage interfaces**
|
|
69
|
+
|
|
70
|
+
`PostgresStorageAdapter` simultaneously implements `StorageAdapter` (the legacy interface with `WorkflowRunRecord`, `WorkflowRunStepRecord`) and `WorkflowStorage` (the newer interface with `ExtendedWorkflowRunRecord`, `StepResult`). This means `postgres.ts` contains two nearly identical sets of CRUD methods (`createRun`/`getRun`/`updateRun` in both shapes), two `mapRunRow` functions (`mapRunRow` and `mapExtendedRunRow`) that differ only in the target type, and two separate status type hierarchies (`RunStatus` vs. `ExtendedRunStatus`). Any schema change must be applied in multiple places.
|
|
71
|
+
|
|
72
|
+
*Suggested fix:* Decide on one canonical interface. If `WorkflowStorage` is the future, mark `StorageAdapter` as deprecated and migrate callers. Provide an adapter shim for backward compatibility during the transition rather than carrying both implementations indefinitely.
|
|
73
|
+
|
|
74
|
+
**2. `storage/types.ts` defines two parallel type families**
|
|
75
|
+
|
|
76
|
+
`StorageAdapter` + `WorkflowRunRecord`/`WorkflowRunStepRecord` and `WorkflowStorage` + `ExtendedWorkflowRunRecord`/`StepResult` overlap heavily. `ExtendedRunStatus` adds `'pending'` and `'timeout'` to `RunStatus`; `ExtendedStepStatus` renames `'succeeded'` to `'completed'`. These differences are not documented as deliberate — they appear to be the result of incremental additions without consolidation.
|
|
77
|
+
|
|
78
|
+
*Suggested fix:* Unify the status enums (`RunStatus` should include `'timeout'`; step status should use consistent terminology). Remove the `Extended*` types once the single interface is adopted.
|
|
79
|
+
|
|
80
|
+
**3. `engine.ts` holds both scheduling concerns and run lifecycle concerns**
|
|
81
|
+
|
|
82
|
+
`WorkflowEngine` manages the in-memory run queue with priority insertion (`queueRun`, `processQueue`), active-run tracking (`activeRuns` map), child spawning delegation, event subscription forwarding, and shutdown. This is reasonable but the queue and concurrency control logic (`hasCapacity`, `queueRun`, `processQueue`) could become an independent `RunQueue` class, making `WorkflowEngine` a thinner coordinator.
|
|
83
|
+
|
|
84
|
+
**4. `events/webhook.ts` duplicates IP-range logic across two functions**
|
|
85
|
+
|
|
86
|
+
`isBlockedHost` and `isBlockedIp` both check the same IPv4 private ranges (10.x, 172.16-31.x, 192.168.x, 127.x, 169.254.x, 0.0.0.0) with near-identical code. A future range addition or bug fix must be applied twice.
|
|
87
|
+
|
|
88
|
+
**5. `scheduler/postgres-persistence.ts` repeats the Kysely lazy-load pattern from `storage/postgres.ts`**
|
|
89
|
+
|
|
90
|
+
Both files declare module-level `let Kysely`, `let PostgresDialect`, `let sql`, `let pgModule` and both call `loadPostgresDeps()` in `initialize()`. This cross-file duplication of boilerplate means any change to the lazy-loading pattern (e.g., adding a new exported symbol) must be applied in two places.
|
|
91
|
+
|
|
92
|
+
*Suggested fix:* Centralize the Kysely instantiation in `utils/postgres-deps.ts` and return a ready-to-use `Kysely` instance, rather than returning the constructor for callers to instantiate manually.
|
|
93
|
+
|
|
94
|
+
---
|
|
95
|
+
|
|
96
|
+
## Refactoring Candidates
|
|
97
|
+
|
|
98
|
+
**1. Consolidate dual storage interfaces (`StorageAdapter` / `WorkflowStorage`)**
|
|
99
|
+
|
|
100
|
+
- **Location:** `src/storage/types.ts`, `src/storage/postgres.ts`
|
|
101
|
+
- **Problem:** Two overlapping interface families with near-duplicate type definitions, status enums that differ by one or two values, and two sets of CRUD methods in `PostgresStorageAdapter`. Every schema or status change requires dual edits.
|
|
102
|
+
- **Suggested approach:** Choose `WorkflowStorage` as the single target. Introduce a `LegacyStorageAdapterBridge` shim that wraps a `WorkflowStorage` into the `StorageAdapter` shape for backward compatibility. Unify `RunStatus` to include `'timeout'`; unify step status terminology to `'succeeded'`. Remove `Extended*` type aliases.
|
|
103
|
+
- **Effort:** Large
|
|
104
|
+
|
|
105
|
+
**2. Extract step execution helpers from `orchestrator.ts:executeStep`**
|
|
106
|
+
|
|
107
|
+
- **Location:** `src/core/orchestrator.ts:296–479`
|
|
108
|
+
- **Problem:** The `executeStep` function is a 183-line `while(true)` loop combining timeout handling, cancellation, hook invocation, event emission, and three error recovery strategies. High cyclomatic complexity makes it difficult to unit-test individual error paths.
|
|
109
|
+
- **Suggested approach:** Extract `executeStepHandler(step, context, abortController)` (pure execution + timeout), `handleStepSuccess(stepRecord, ...)`, and `handleStepError(step, error, attempt, strategy, ...)` (retry/skip/fail branching). `executeStep` becomes a thin coordinator.
|
|
110
|
+
- **Effort:** Medium
|
|
111
|
+
|
|
112
|
+
**3. Merge `isBlockedHost` and `isBlockedIp` into one utility**
|
|
113
|
+
|
|
114
|
+
- **Location:** `src/events/webhook.ts:487–555`
|
|
115
|
+
- **Problem:** Two functions with near-identical IPv4-range checks. A missed range in one is a security regression.
|
|
116
|
+
- **Suggested approach:** Consolidate into `isBlockedAddress(address: string): boolean` operating on dotted-decimal IPv4 or hostname strings. Call it from both `validateWebhookUrl` and `validateResolvedHost`.
|
|
117
|
+
- **Effort:** Small
|
|
118
|
+
|
|
119
|
+
**4. Extract a `parseJsonColumn` helper for storage mappers**
|
|
120
|
+
|
|
121
|
+
- **Location:** `src/storage/postgres.ts:861–940`, `src/storage/sqlite.ts`
|
|
122
|
+
- **Problem:** The pattern `typeof col === 'string' ? JSON.parse(col) : col` appears 10+ times across `mapRunRow`, `mapExtendedRunRow`, `mapStepRow`, `mapStepResultRow`, and `mapEventRow` in postgres.ts, and again in sqlite.ts's mapper.
|
|
123
|
+
- **Suggested approach:** `function parseJsonColumn<T>(col: string | T | null | undefined, fallback?: T): T | undefined` — extracted to a shared `src/storage/utils.ts`. Eliminates all inline ternaries.
|
|
124
|
+
- **Effort:** Small
|
|
125
|
+
|
|
126
|
+
**5. Replace `createTables` ad-hoc migration with a versioned migration runner**
|
|
127
|
+
|
|
128
|
+
- **Location:** `src/storage/postgres.ts:314–465`
|
|
129
|
+
- **Problem:** The 150-line `createTables` method issues 15+ raw SQL statements and silently swallows `ALTER TABLE` errors as idempotency. There is no migration version tracking, so it is impossible to determine the current schema version or safely evolve the schema.
|
|
130
|
+
- **Suggested approach:** Introduce a `schema_migrations` table tracking applied versions. Replace the `ALTER TABLE ... ADD COLUMN IF NOT EXISTS` hack with numbered migration files applied sequentially. This also enables rollback logic.
|
|
131
|
+
- **Effort:** Large
|
|
132
|
+
|
|
133
|
+
**6. Refactor `WorkflowEngine` queue management into a `RunQueue` class**
|
|
134
|
+
|
|
135
|
+
- **Location:** `src/core/engine.ts:112–399`
|
|
136
|
+
- **Problem:** `WorkflowEngine` mixes run-lifecycle concerns (start, cancel, resume, wait) with an in-memory priority queue implementation (`queueRun`, `processQueue`, `hasCapacity`, `runQueue` array, `activeRuns` map). As concurrency features grow, this class will accumulate more queue-specific logic.
|
|
137
|
+
- **Suggested approach:** Extract a `RunQueue` class that owns `runQueue`, `activeRuns`, `hasCapacity`, `queueRun`, `processQueue`, and the `QueuedRun` interface. `WorkflowEngine` delegates to it.
|
|
138
|
+
- **Effort:** Medium
|
|
139
|
+
|
|
140
|
+
**7. Centralize Kysely lazy-loading in `utils/postgres-deps.ts`**
|
|
141
|
+
|
|
142
|
+
- **Location:** `src/utils/postgres-deps.ts`, `src/storage/postgres.ts:13–16`, `src/scheduler/postgres-persistence.ts:12–15`
|
|
143
|
+
- **Problem:** Module-level `let Kysely`, `let PostgresDialect`, `let sql`, `let pgModule` declarations and `loadPostgresDeps()` calls are copy-pasted into two different files, and the callers then call `new Kysely(...)` themselves.
|
|
144
|
+
- **Suggested approach:** Have `loadPostgresDeps` return an already-constructed `Kysely<any>` factory or a `buildKysely(pool)` helper. Callers receive a usable query builder, not raw constructors.
|
|
145
|
+
- **Effort:** Small
|
|
146
|
+
|
|
147
|
+
**8. Add a `retryUntilAbort` helper to eliminate duplicated sleep-abort patterns**
|
|
148
|
+
|
|
149
|
+
- **Location:** `src/core/orchestrator.ts:462–473`, `src/utils/retry.ts`
|
|
150
|
+
- **Problem:** The `sleep(delay, abortController.signal)` call inside the retry loop is followed by a catch that checks `WorkflowCanceledError` and updates the step record — a pattern that will be repeated if a second retry site is ever added.
|
|
151
|
+
- **Suggested approach:** Provide a `sleepWithAbort(ms, signal, onAbort?)` utility in `src/utils/retry.ts` that handles the cancellation check and throws consistently, removing the need for a catch at the call site.
|
|
152
|
+
- **Effort:** Small
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# Complexity Report: stepflow
|
|
2
|
+
|
|
3
|
+
**Date**: 2026-04-14T01:04:23.188Z
|
|
4
|
+
**Repository**: /srv/repos/stepflow
|
|
5
|
+
**Branch**: main
|
|
6
|
+
**Workflow Run**: 21addb61-3850-4a9f-9504-0c0627b6a8d5
|
|
7
|
+
**Session**: 01abcef0-2ca7-4ee5-b46f-a7959609ab9a
|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
## Code Complexity Report — Stepflow
|
|
12
|
+
|
|
13
|
+
The report has been saved to `.codekin/reports/complexity/2026-04-14_complexity-report.md`.
|
|
14
|
+
|
|
15
|
+
**Overall rating: Medium-High**
|
|
16
|
+
|
|
17
|
+
Key findings:
|
|
18
|
+
|
|
19
|
+
**Largest files** (top 3):
|
|
20
|
+
- `src/storage/postgres.ts` — 1,354 lines (4.5× the 300-line guideline), a God object with 20+ methods across 8 distinct responsibilities
|
|
21
|
+
- `src/core/engine.ts` — 692 lines, mixes queue management with lifecycle orchestration
|
|
22
|
+
- `src/core/orchestrator.ts` — 564 lines, hosts the two most complex functions in the codebase
|
|
23
|
+
|
|
24
|
+
**Hottest complexity spots:**
|
|
25
|
+
- `orchestrator.ts: executeStep()` — CC ≈ 20, 7 levels of nesting in the retry loop
|
|
26
|
+
- `orchestrator.ts: executeWorkflow()` — 217-line function with inline timeout scheduling, checkpointing, and lifecycle hooks all interleaved
|
|
27
|
+
- `postgres.ts: createTables()` — 152 lines of undifferentiated SQL blocks
|
|
28
|
+
|
|
29
|
+
**Highest-impact refactoring opportunities (quick wins):**
|
|
30
|
+
1. Extract a `parseJsonColumn<T>()` utility to eliminate the 8+ identical JSON parsing idioms duplicated across postgres and sqlite adapters (Small effort)
|
|
31
|
+
2. Replace `evaluateCondition()` switch with a strategy map (Small effort)
|
|
32
|
+
3. Extract event/status string literals into a `src/core/constants.ts` file (Small effort)
|
|
33
|
+
4. Replace the O(n) array-splice run queue with a priority heap (Small effort)
|
|
34
|
+
5. Introduce a shared `Semaphore` utility — both `engine.ts` and `webhook.ts` have hand-rolled concurrency controllers (Small effort)
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
# Dependency Health Report: stepflow
|
|
2
|
+
|
|
3
|
+
**Date**: 2026-04-04T06:58:47.515Z
|
|
4
|
+
**Repository**: /srv/repos/stepflow
|
|
5
|
+
**Branch**: main
|
|
6
|
+
**Workflow Run**: caf445eb-aa6a-4887-a75d-0068f9ea47ac
|
|
7
|
+
**Session**: 40a62583-ab64-444b-be60-b6caef82eab5
|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
Now I have all the data needed. Here is the full dependency health assessment:
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## Summary
|
|
16
|
+
|
|
17
|
+
| Package Manager | Total Deps | Outdated | Vulnerabilities | Risk Level |
|
|
18
|
+
|---|---|---|---|---|
|
|
19
|
+
| npm | 296 | 7 | 3 (2 high, 1 moderate) | **High** |
|
|
20
|
+
|
|
21
|
+
**Notes:** 296 total installed packages (64 prod, 231 dev, 87 optional). Direct production dependencies: `cron-parser`, `re2`. Direct devDependencies: 10 packages. Peer dependencies: `better-sqlite3`, `kysely`, `pg`.
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
## Security Vulnerabilities
|
|
26
|
+
|
|
27
|
+
| Package | Severity | Advisory | Description | Fixed In |
|
|
28
|
+
|---|---|---|---|---|
|
|
29
|
+
| `kysely` | **HIGH** (CVSS 8.1) | [GHSA-8cpq-38p9-67gx](https://github.com/advisories/GHSA-8cpq-38p9-67gx) | MySQL SQL Injection via insufficient backslash escaping in `sql.lit(string)` and similar string-literal methods | `>=0.28.14` |
|
|
30
|
+
| `kysely` | **HIGH** (CVSS 8.1) | [GHSA-fr9j-6mvq-frcv](https://github.com/advisories/GHSA-fr9j-6mvq-frcv) | MySQL SQL Injection via backslash escape bypass in non-type-safe JSON path key usage | `>=0.28.14` |
|
|
31
|
+
| `picomatch` | **HIGH** (CVSS 7.5) | [GHSA-c2c7-rcm5-vvqj](https://github.com/advisories/GHSA-c2c7-rcm5-vvqj) | ReDoS vulnerability via extglob quantifiers — denial-of-service via malicious glob patterns (indirect dep of `chokidar`/`tsup`) | `>=4.0.4` |
|
|
32
|
+
| `brace-expansion` | **MODERATE** (CVSS 6.5) | [GHSA-f886-m6hf-6m8v](https://github.com/advisories/GHSA-f886-m6hf-6m8v) | Zero-step sequence (e.g. `{1..0}`) causes process hang and memory exhaustion (indirect dep) | `>=5.0.5` |
|
|
33
|
+
| `picomatch` | MODERATE (CVSS 5.3) | [GHSA-3v7f-55p6-f55p](https://github.com/advisories/GHSA-3v7f-55p6-f55p) | Method injection via POSIX character classes causing incorrect glob matching (indirect dep of `chokidar`/`tsup`) | `>=4.0.4` |
|
|
34
|
+
|
|
35
|
+
**Scope assessment:** The `kysely` vulnerabilities are in a peer dependency used for the PostgreSQL/MySQL adapter path. They affect MySQL dialect users who pass unsanitised input via `sql.lit()` or JSON path keys. SQLite users are not affected. The `picomatch` and `brace-expansion` issues are in dev toolchain transitive deps (`tsup` → `chokidar`) and do not affect the published package.
|
|
36
|
+
|
|
37
|
+
---
|
|
38
|
+
|
|
39
|
+
## Outdated Dependencies
|
|
40
|
+
|
|
41
|
+
| Package | Current | Latest | Age of Current | Type |
|
|
42
|
+
|---|---|---|---|---|
|
|
43
|
+
| `@types/node` | 22.19.5 | 25.5.2 | ~84 days (3 major versions behind) | devDependency |
|
|
44
|
+
| `typescript` | 5.9.3 | 6.0.2 | — (1 major version behind; v6 released 2026-03-23) | devDependency |
|
|
45
|
+
| `re2` | 1.23.3 | 1.24.0 | ~54 days (released 2026-02-09) | dependency (prod) |
|
|
46
|
+
| `kysely` | 0.28.12 | 0.28.15 | ~22 days (released 2026-03-13) | peerDependency / devDependency |
|
|
47
|
+
| `vitest` | 4.1.0 | 4.1.2 | ~23 days (released 2026-03-12) | devDependency |
|
|
48
|
+
| `@vitest/coverage-v8` | 4.1.0 | 4.1.2 | ~23 days | devDependency |
|
|
49
|
+
| `@types/pg` | 8.18.0 | 8.20.0 | — (2 minor versions behind) | devDependency |
|
|
50
|
+
|
|
51
|
+
---
|
|
52
|
+
|
|
53
|
+
## Abandoned / Unmaintained Packages
|
|
54
|
+
|
|
55
|
+
All items below are **transitive/indirect** dependencies — none are direct. Most trace back to the `prebuild-install` → `node-pre-gyp` chain used by `better-sqlite3` and `re2` for native addon installation.
|
|
56
|
+
|
|
57
|
+
| Package | Installed Version | Last npm Publish | Age |
|
|
58
|
+
|---|---|---|---|
|
|
59
|
+
| `any-promise` | 1.3.0 | 2022-06-13 | ~3.8 years |
|
|
60
|
+
| `tunnel-agent` | 0.6.0 | 2022-06-27 | ~3.8 years |
|
|
61
|
+
| `util-deprecate` | 1.0.2 | 2022-06-28 | ~3.8 years |
|
|
62
|
+
| `thenify` | 3.3.1 | 2022-06-27 | ~3.8 years |
|
|
63
|
+
| `github-from-package` | 0.1.1 | 2022-11-11 | ~3.4 years |
|
|
64
|
+
| `deep-extend` | 0.6.0 | 2023-07-10 | ~2.7 years |
|
|
65
|
+
| `safe-buffer` | 5.2.1 | 2023-07-10 | ~2.7 years |
|
|
66
|
+
| `bindings` | 1.5.0 | 2023-07-10 | ~2.7 years |
|
|
67
|
+
| `file-uri-to-path` | 2.0.0 | 2023-07-10 | ~2.7 years |
|
|
68
|
+
| `mz` | 2.7.0 | 2023-11-07 | ~2.4 years |
|
|
69
|
+
|
|
70
|
+
These packages are effectively stable and in maintenance-only mode (no API surface changes expected). Their abandonment is low operational risk, but they represent supply-chain exposure if they are ever compromised on npm.
|
|
71
|
+
|
|
72
|
+
---
|
|
73
|
+
|
|
74
|
+
## Recommendations
|
|
75
|
+
|
|
76
|
+
1. **[CRITICAL] Update `kysely` to `>=0.28.14` immediately.** Two high-severity SQL injection CVEs (GHSA-8cpq-38p9-67gx, GHSA-fr9j-6mvq-frcv) affect all versions `<=0.28.13`. The fix is available in `0.28.15` (released 2026-03-31). Since `kysely` is a peer dependency, consumers must update their own `kysely` version; update the peer/dev dep range floor in `package.json` to `>=0.28.14` and run `npm install` to update the lockfile.
|
|
77
|
+
|
|
78
|
+
2. **[HIGH] Update `picomatch` to `>=4.0.4`.** Two CVEs (ReDoS GHSA-c2c7-rcm5-vvqj at CVSS 7.5, method injection GHSA-3v7f-55p6-f55p) are present in the installed `4.0.x` range. This is pulled in transitively by `tsup`/`chokidar`. Run `npm update picomatch` or update `chokidar` when a version that pins `picomatch>=4.0.4` is released.
|
|
79
|
+
|
|
80
|
+
3. **[MODERATE] Update `brace-expansion` to `>=5.0.5`.** The installed range `4.0.0–5.0.4` is vulnerable to a process-hanging ReDoS (GHSA-f886-m6hf-6m8v, CVSS 6.5). Run `npm update brace-expansion` to pull in the patched version.
|
|
81
|
+
|
|
82
|
+
4. **[HIGH] Plan migration to TypeScript 6.** TypeScript 6.0.2 is now available and includes breaking changes (stricter inference, removed deprecated emit options). Schedule an upgrade cycle: run `tsc` against the new compiler on a branch, address any new type errors, then merge. TypeScript 5.9 will not receive bug fixes once support ends.
|
|
83
|
+
|
|
84
|
+
5. **[MEDIUM] Update `@types/node` from `22.x` to `22.x` latest (semver-wanted), and plan a roadmap to `25.x`.** The installed version (`22.19.5`) is 3 major type-definition versions behind `25.5.2`. While types don't affect runtime, using stale `@types/node` means missing Node.js API types and incorrect/missing signatures for newer APIs. Update to the wanted `22.19.17` now, then evaluate a bump to `24.x` or `25.x` after the TypeScript 6 migration.
|
|
85
|
+
|
|
86
|
+
6. **[MEDIUM] Update `re2` to `1.24.0`.** The production `re2` package (a direct dependency) has a new minor release. As a native addon that is loaded at runtime, staying current ensures compatibility with newer Node.js ABI versions and any security fixes in the C++ RE2 library.
|
|
87
|
+
|
|
88
|
+
7. **[LOW] Update `vitest` and `@vitest/coverage-v8` to `4.1.2`.** These are patch releases that likely include bug fixes. They are low-risk to update (`npm update vitest @vitest/coverage-v8`) and should be kept in sync since mismatched versions can cause coverage reporting failures.
|
|
89
|
+
|
|
90
|
+
8. **[LOW] Audit the `prebuild-install` dependency chain.** Ten transitive packages in the native addon prebuilt-install chain (`tunnel-agent`, `any-promise`, `github-from-package`, etc.) have not been published in 2–4 years. Consider whether `better-sqlite3` or `re2` have moved to alternative install mechanisms that shed these deps, and upgrade those packages to their latest versions to reduce supply-chain surface area.
|
|
91
|
+
|
|
92
|
+
9. **[INFO] Verify `kysely` peer dependency minimum version in `package.json`.** The current `peerDependencies` floor is `>=0.27.0`, which includes the now-vulnerable range `<=0.28.13`. Update the peer dep constraint to `>=0.28.14` to prevent downstream consumers from inadvertently installing a vulnerable version of `kysely` when using this library.
|
|
93
|
+
|
|
94
|
+
10. **[INFO] Consider replacing `tunnel-agent` and `safe-buffer` transitive deps long-term.** Both are effectively superseded by Node.js built-ins (`https.Agent` and `Buffer` respectively). They persist only as transitive deps of `prebuild-install`. File upstream issues or switch to alternative prebuilt strategies (e.g., `node-gyp-build` with bundled binaries) if those deps cannot be shed through normal upgrades.
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
# Dependency Health Report: stepflow
|
|
2
|
+
|
|
3
|
+
**Date**: 2026-04-11T02:46:13.072Z
|
|
4
|
+
**Repository**: /srv/repos/stepflow
|
|
5
|
+
**Branch**: main
|
|
6
|
+
**Workflow Run**: 672a3272-e7d7-4127-b865-4e02a47eac94
|
|
7
|
+
**Session**: 0b582a44-067e-4ef0-b863-b0cd566a81fd
|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
Now I have all the data needed to compile the report.
|
|
12
|
+
|
|
13
|
+
## Dependency Health Assessment — `@multiplier-labs/stepflow` v0.2.6
|
|
14
|
+
|
|
15
|
+
_Assessment date: 2026-04-11 | Package manager: **npm** | Lock file: `package-lock.json`_
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## Summary
|
|
20
|
+
|
|
21
|
+
| Package Manager | Total Deps | Outdated (direct) | Vulnerabilities | Risk Level |
|
|
22
|
+
|---|---|---|---|---|
|
|
23
|
+
| npm | 296 (12 direct) | 7 | 4 (3 high, 1 moderate) | **HIGH** |
|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
## Security Vulnerabilities
|
|
28
|
+
|
|
29
|
+
| Package | Severity | Advisory | Description | Fixed In |
|
|
30
|
+
|---|---|---|---|---|
|
|
31
|
+
| `kysely` | **HIGH** (CVSS 8.1) | GHSA-8cpq-38p9-67gx | SQL Injection via insufficient backslash escaping in `sql.lit()` and similar string-literal methods | `0.28.14` |
|
|
32
|
+
| `kysely` | **HIGH** (CVSS 8.1) | GHSA-fr9j-6mvq-frcv | SQL Injection via backslash escape bypass in non-type-safe JSON path key usage | `0.28.14` |
|
|
33
|
+
| `picomatch` | **HIGH** (CVSS 7.5) | GHSA-c2c7-rcm5-vvqj | ReDoS (Denial of Service) via extglob quantifiers — affects transitive dep from `vitest`/`chokidar` chain | `4.0.4` |
|
|
34
|
+
| `vite` | **HIGH** | GHSA-p9ff-h696-f583 | Arbitrary file read via Vite Dev Server WebSocket — transitive dep via `vitest` | `8.0.5` |
|
|
35
|
+
| `vite` | **HIGH** | GHSA-v2wj-q39q-566r | `server.fs.deny` access control bypass with query strings — transitive dep via `vitest` | `8.0.5` |
|
|
36
|
+
| `vite` | Moderate | GHSA-4w7w-66w2-5vf9 | Path traversal in optimized deps `.map` handling — transitive dep via `vitest` | `8.0.5` |
|
|
37
|
+
| `brace-expansion` | Moderate (CVSS 6.5) | GHSA-f886-m6hf-6m8v | Zero-step sequence causes process hang and memory exhaustion — transitive dep | `5.0.5` |
|
|
38
|
+
| `picomatch` | Moderate (CVSS 5.3) | GHSA-3v7f-55p6-f55p | Method injection in POSIX character classes causes incorrect glob matching | `4.0.4` |
|
|
39
|
+
|
|
40
|
+
> **Note:** `kysely` appears as both a `devDependency` and `peerDependency` in this project. The SQL injection issues affect MySQL dialect users specifically; SQLite and PostgreSQL dialects have lower exposure, but upgrading is still critical.
|
|
41
|
+
|
|
42
|
+
---
|
|
43
|
+
|
|
44
|
+
## Outdated Dependencies
|
|
45
|
+
|
|
46
|
+
| Package | Current | Latest | Gap | Type |
|
|
47
|
+
|---|---|---|---|---|
|
|
48
|
+
| `@types/node` | `22.19.5` | `25.6.0` | 3 major versions | devDependency |
|
|
49
|
+
| `typescript` | `5.9.3` | `6.0.2` | 1 major version | devDependency |
|
|
50
|
+
| `kysely` | `0.28.12` | `0.28.16` | 4 patches (**security**) | devDependency / peerDependency |
|
|
51
|
+
| `re2` | `1.23.3` | `1.24.0` | 1 minor | production dependency |
|
|
52
|
+
| `vitest` | `4.1.0` | `4.1.4` | 4 patches (transitive vuln fix) | devDependency |
|
|
53
|
+
| `@vitest/coverage-v8` | `4.1.0` | `4.1.4` | 4 patches | devDependency |
|
|
54
|
+
| `@types/pg` | `8.18.0` | `8.20.0` | 2 patches | devDependency |
|
|
55
|
+
|
|
56
|
+
---
|
|
57
|
+
|
|
58
|
+
## Abandoned / Unmaintained Packages
|
|
59
|
+
|
|
60
|
+
These are all **transitive dependencies** pulled in via the build/test toolchain (`tsup`, `prebuild-install`, `vitest`). Direct replacement is not possible without upstream action, but they represent long-term risk.
|
|
61
|
+
|
|
62
|
+
| Package | Installed Version | Est. Last Release | Dependency Path | Notes |
|
|
63
|
+
|---|---|---|---|---|
|
|
64
|
+
| `any-promise` | `1.3.0` | ~2016 (~10 yrs) | `tsup` → `mz` → `any-promise` | Effectively abandoned; superseded by native Promises |
|
|
65
|
+
| `object-assign` | `4.1.1` | ~2017 (~9 yrs) | `tsup` → build chain | Superseded by `Object.assign()` natively in Node.js |
|
|
66
|
+
| `file-uri-to-path` | `1.0.0` | ~2017 (~9 yrs) | `bindings` → `file-uri-to-path` | No longer needed for modern Node.js |
|
|
67
|
+
| `tunnel-agent` | `0.6.0` | ~2018 (~8 yrs) | `prebuild-install` → `request` chain | `request` itself is deprecated |
|
|
68
|
+
| `safe-buffer` | `5.2.1` | ~2020 (~6 yrs) | `readable-stream` → `safe-buffer` | Superseded by native `Buffer` APIs |
|
|
69
|
+
| `source-map` | `0.7.6` | ~2021 (~5 yrs) | `sucrase` → `source-map` | Minimal maintenance; `sucrase` is a dev-only tool |
|
|
70
|
+
|
|
71
|
+
---
|
|
72
|
+
|
|
73
|
+
## Recommendations
|
|
74
|
+
|
|
75
|
+
1. **[Critical] Upgrade `kysely` to `≥0.28.14` immediately.** Two HIGH-severity SQL injection vulnerabilities (GHSA-8cpq-38p9-67gx, GHSA-fr9j-6mvq-frcv) affect all versions ≤0.28.13. Update both `devDependencies` and adjust `peerDependencies` range (`"kysely": ">=0.27.0"`) to document the minimum safe version. The fix is available at `0.28.16` (current latest).
|
|
76
|
+
|
|
77
|
+
2. **[High] Upgrade `vitest` to `≥4.1.4` to pull in patched `vite` (≥8.0.5) and `picomatch` (≥4.0.4).** Three HIGH/moderate vulnerabilities in `vite` (GHSA-p9ff-h696-f583, GHSA-v2wj-q39q-566r, GHSA-4w7w-66w2-5vf9) and two in `picomatch` are fixed by bumping vitest. Although these only run in the dev server context, they pose a real risk in CI or development environments.
|
|
78
|
+
|
|
79
|
+
3. **[High] Run `npm audit fix` to resolve `brace-expansion` (GHSA-f886-m6hf-6m8v).** This moderate ReDoS/memory-exhaustion vulnerability is in a transitive dep with a straightforward fix available. Running `npm audit fix` should resolve all 4 outstanding advisories once `kysely` and `vitest` are updated first.
|
|
80
|
+
|
|
81
|
+
4. **[Medium] Upgrade `@types/node` from `22.x` to `24.x` (or `25.x`) in a planned sprint.** The installed version is 3 major versions behind. While `@types/node` bumps rarely break code, staying far behind obscures real type errors and misses new Node.js API typings. Align with the Node.js LTS version in use.
|
|
82
|
+
|
|
83
|
+
5. **[Medium] Evaluate `TypeScript 6.x` migration.** The project uses `typescript ^5.7.2` while `6.0.2` is now the latest stable release. TypeScript 6 ships breaking changes (notably around module resolution and `erasableSyntaxOnly`). Plan a migration branch to assess impact on the codebase and tsup build config.
|
|
84
|
+
|
|
85
|
+
6. **[Medium] Update `re2` to `1.24.0`.** `re2` is a **production dependency** — it directly affects the package published to consumers. Patch updates should be applied promptly; `1.24.0` is a minor bump with bug fixes.
|
|
86
|
+
|
|
87
|
+
7. **[Low] Update peer dependency minimum for `kysely` to `>=0.28.14` in `package.json`.** Even if downstream users manage their own `kysely` install, advertising a range that includes vulnerable versions is irresponsible. Tightening the peer constraint signals the minimum safe version to consumers.
|
|
88
|
+
|
|
89
|
+
8. **[Low] Audit and potentially remove `prebuild-install` → `tunnel-agent` chain.** `tunnel-agent` is pulled in via `re2`'s native build tooling (`prebuild-install`). Check whether `re2` 1.24.0 still ships prebuilt binaries for all target platforms; if so, `prebuild-install` (and its abandoned transitive deps) may not run at all in production.
|
|
90
|
+
|
|
91
|
+
9. **[Low] Pin `vitest` and `@vitest/coverage-v8` to matching patch versions** (`4.1.4`). The `^4.1.0` range means CI will auto-install the latest patch, but local developer environments may lag. Lock to a specific version to ensure consistent test environments across machines.
|
|
92
|
+
|
|
93
|
+
10. **[Informational] No production bundle contamination detected.** All dev dependencies (`vitest`, `tsup`, `typescript`, `@types/*`) are correctly scoped to `devDependencies` and excluded from the published package. The only two production dependencies are `cron-parser` and `re2`, which is appropriately lean for a library.
|