@multiplier-labs/stepflow 0.3.4 → 0.3.8

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.
Files changed (109) hide show
  1. package/README.md +9 -0
  2. package/dist/{chunk-5XJBAPXF.js → chunk-4IDPWWGS.js} +15 -4
  3. package/dist/chunk-4IDPWWGS.js.map +1 -0
  4. package/dist/{chunk-U3PCVK6O.js → chunk-T6GAZBTQ.js} +70 -38
  5. package/dist/chunk-T6GAZBTQ.js.map +1 -0
  6. package/dist/{chunk-FMPJBK2N.js → chunk-TVSADUYU.js} +3 -5
  7. package/dist/{chunk-FMPJBK2N.js.map → chunk-TVSADUYU.js.map} +1 -1
  8. package/dist/events/index.d.ts +43 -3
  9. package/dist/events/index.js +7 -3
  10. package/dist/{index-Cd0IZ40L.d.ts → index-CdXDv30T.d.ts} +10 -2
  11. package/dist/index.d.ts +5 -4
  12. package/dist/index.js +159 -46
  13. package/dist/index.js.map +1 -1
  14. package/dist/scheduler/index.d.ts +2 -2
  15. package/dist/scheduler/index.js +1 -1
  16. package/dist/storage/index.d.ts +6 -4
  17. package/dist/storage/index.js +1 -1
  18. package/dist/{types-DSEjiLMj.d.ts → types-BfWe5yP6.d.ts} +2 -0
  19. package/package.json +26 -14
  20. package/.codekin/reports/code-review/2026-04-11_code-review-daily.md +0 -146
  21. package/.codekin/reports/code-review/2026-04-13_code-review-daily.md +0 -74
  22. package/.codekin/reports/comments/2026-04-04_comment-assessment.md +0 -179
  23. package/.codekin/reports/comments/2026-04-11_comment-assessment.md +0 -161
  24. package/.codekin/reports/comments/2026-04-14_comment-assessment.md +0 -34
  25. package/.codekin/reports/complexity/2026-04-03_complexity-report.md +0 -152
  26. package/.codekin/reports/complexity/2026-04-11_complexity-report.md +0 -152
  27. package/.codekin/reports/complexity/2026-04-14_complexity-report.md +0 -34
  28. package/.codekin/reports/dependencies/2026-04-04_dependency-health.md +0 -94
  29. package/.codekin/reports/dependencies/2026-04-11_dependency-health.md +0 -93
  30. package/.codekin/reports/docs-audit/2026-04-04_docs-audit.md +0 -10
  31. package/.codekin/reports/docs-audit/2026-04-11_docs-audit.md +0 -138
  32. package/.codekin/reports/docs-audit/2026-04-13_docs-audit.md +0 -121
  33. package/.codekin/reports/security/2026-04-03_security-audit.md +0 -208
  34. package/.codekin/reports/security/2026-04-11_security-audit.md +0 -297
  35. package/.github/CODEOWNERS +0 -15
  36. package/.github/dependabot.yml +0 -23
  37. package/.github/workflows/ci.yml +0 -38
  38. package/.github/workflows/publish.yml +0 -42
  39. package/.github/workflows/security.yml +0 -29
  40. package/CHANGELOG.md +0 -34
  41. package/SECURITY.md +0 -40
  42. package/dist/chunk-5XJBAPXF.js.map +0 -1
  43. package/dist/chunk-U3PCVK6O.js.map +0 -1
  44. package/docs/github-packages-setup.md +0 -97
  45. package/docs/stepflow-api-reference.md +0 -1663
  46. package/src/core/engine/concurrency.ts +0 -229
  47. package/src/core/engine/lifecycle.ts +0 -176
  48. package/src/core/engine.test.ts +0 -1435
  49. package/src/core/engine.ts +0 -598
  50. package/src/core/index.ts +0 -7
  51. package/src/core/orchestrator/persistence.ts +0 -165
  52. package/src/core/orchestrator/plan-resolution.ts +0 -140
  53. package/src/core/orchestrator/state-transitions.ts +0 -109
  54. package/src/core/orchestrator/step-runner.ts +0 -427
  55. package/src/core/orchestrator.ts +0 -252
  56. package/src/core/types.ts +0 -220
  57. package/src/events/index.ts +0 -8
  58. package/src/events/memory.test.ts +0 -107
  59. package/src/events/memory.ts +0 -92
  60. package/src/events/socketio.test.ts +0 -378
  61. package/src/events/socketio.ts +0 -262
  62. package/src/events/types.ts +0 -118
  63. package/src/events/webhook.test.ts +0 -949
  64. package/src/events/webhook.ts +0 -807
  65. package/src/index.ts +0 -270
  66. package/src/planning/evaluate-condition.test.ts +0 -153
  67. package/src/planning/evaluate-condition.ts +0 -105
  68. package/src/planning/index.ts +0 -55
  69. package/src/planning/planner.ts +0 -494
  70. package/src/planning/planning.test.ts +0 -727
  71. package/src/planning/registry.test.ts +0 -398
  72. package/src/planning/registry.ts +0 -283
  73. package/src/planning/types.ts +0 -566
  74. package/src/scheduler/cron.test.ts +0 -479
  75. package/src/scheduler/cron.ts +0 -438
  76. package/src/scheduler/index.ts +0 -8
  77. package/src/scheduler/postgres-persistence.ts +0 -530
  78. package/src/scheduler/sqlite-persistence.test.ts +0 -235
  79. package/src/scheduler/sqlite-persistence.ts +0 -286
  80. package/src/scheduler/types.ts +0 -73
  81. package/src/storage/index.ts +0 -27
  82. package/src/storage/memory.test.ts +0 -349
  83. package/src/storage/memory.ts +0 -227
  84. package/src/storage/postgres-core.ts +0 -550
  85. package/src/storage/postgres-migration.test.ts +0 -69
  86. package/src/storage/postgres-transaction-pagination.test.ts +0 -107
  87. package/src/storage/postgres.ts +0 -835
  88. package/src/storage/sqlite-core.ts +0 -151
  89. package/src/storage/sqlite.test.ts +0 -642
  90. package/src/storage/sqlite.ts +0 -717
  91. package/src/storage/types.ts +0 -343
  92. package/src/utils/errors.test.ts +0 -150
  93. package/src/utils/errors.ts +0 -161
  94. package/src/utils/id.ts +0 -21
  95. package/src/utils/index.ts +0 -10
  96. package/src/utils/logger.test.ts +0 -121
  97. package/src/utils/logger.ts +0 -96
  98. package/src/utils/pinned-fetch.test.ts +0 -84
  99. package/src/utils/pinned-fetch.ts +0 -118
  100. package/src/utils/postgres-deps.ts +0 -79
  101. package/src/utils/retry.test.ts +0 -177
  102. package/src/utils/retry.ts +0 -121
  103. package/src/utils/safe-json.test.ts +0 -166
  104. package/src/utils/safe-json.ts +0 -107
  105. package/src/utils/webhook-signature.test.ts +0 -111
  106. package/src/utils/webhook-signature.ts +0 -74
  107. package/tsconfig.json +0 -21
  108. package/tsup.config.ts +0 -14
  109. package/vitest.config.ts +0 -9
@@ -1,6 +1,6 @@
1
- export { C as CronScheduler, a as CronSchedulerConfig, P as PostgresSchedulePersistence, b as PostgresSchedulePersistenceConfig, S as SQLiteSchedulePersistence, c as SQLiteSchedulePersistenceConfig, d as SchedulePersistence, e as Scheduler, T as TriggerType, h as WorkflowSchedule } from '../index-Cd0IZ40L.js';
1
+ export { C as CronScheduler, a as CronSchedulerConfig, P as PostgresSchedulePersistence, b as PostgresSchedulePersistenceConfig, S as SQLiteSchedulePersistence, c as SQLiteSchedulePersistenceConfig, d as SchedulePersistence, e as Scheduler, T as TriggerType, h as WorkflowSchedule } from '../index-CdXDv30T.js';
2
2
  import '../types-K5Gjk3H_.js';
3
- import '../types-DSEjiLMj.js';
3
+ import '../types-BfWe5yP6.js';
4
4
  import '../types-DZUdWmCc.js';
5
5
  import 'better-sqlite3';
6
6
  import 'pg';
@@ -2,7 +2,7 @@ import {
2
2
  CronScheduler,
3
3
  PostgresSchedulePersistence,
4
4
  SQLiteSchedulePersistence
5
- } from "../chunk-FMPJBK2N.js";
5
+ } from "../chunk-TVSADUYU.js";
6
6
  import "../chunk-JPXPB7U5.js";
7
7
  export {
8
8
  CronScheduler,
@@ -1,7 +1,7 @@
1
- import { i as StorageAdapter, j as WorkflowRunRecord, d as ListRunsOptions, P as PaginatedResult, k as WorkflowRunStepRecord, W as WorkflowEventRecord, L as ListEventsOptions, C as CreateRunInput, U as UpdateRunInput, e as StepResult } from '../types-DSEjiLMj.js';
2
- export { E as ExtendedListRunsOptions, a as ExtendedRunStatus, b as ExtendedStepStatus, c as ExtendedWorkflowRunRecord, S as StepRecord, f as StepflowDatabase, g as StepflowRunsTable, h as StepflowStepResultsTable, l as WorkflowStorage } from '../types-DSEjiLMj.js';
1
+ import { i as StorageAdapter, j as WorkflowRunRecord, d as ListRunsOptions, P as PaginatedResult, k as WorkflowRunStepRecord, W as WorkflowEventRecord, L as ListEventsOptions, C as CreateRunInput, U as UpdateRunInput, e as StepResult } from '../types-BfWe5yP6.js';
2
+ export { E as ExtendedListRunsOptions, a as ExtendedRunStatus, b as ExtendedStepStatus, c as ExtendedWorkflowRunRecord, S as StepRecord, f as StepflowDatabase, g as StepflowRunsTable, h as StepflowStepResultsTable, l as WorkflowStorage } from '../types-BfWe5yP6.js';
3
3
  import Database from 'better-sqlite3';
4
- import { L as Logger } from '../types-K5Gjk3H_.js';
4
+ import { Logger } from '../index.js';
5
5
  import { Kysely } from 'kysely';
6
6
  import { Pool, PoolConfig } from 'pg';
7
7
 
@@ -312,6 +312,7 @@ interface WorkflowRunsTable {
312
312
  input_json: string;
313
313
  metadata_json: string;
314
314
  context_json: string;
315
+ completed_steps_json: string | null;
315
316
  output_json: string | null;
316
317
  error_json: string | null;
317
318
  priority: number;
@@ -665,4 +666,5 @@ declare class PostgresTransactionAdapter extends PostgresStorageCore {
665
666
  protected get warnLabel(): string;
666
667
  }
667
668
 
668
- export { CreateRunInput, ListEventsOptions, ListRunsOptions, MemoryStorageAdapter, PaginatedResult, PostgresStorageAdapter as PostgresStorage, PostgresStorageAdapter, type PostgresStorageConfig, type PostgresStorageConfig as PostgresStorageOptions, PostgresTransactionAdapter, SQLiteStorageAdapter, type SQLiteStorageConfig, StepResult, StorageAdapter, UpdateRunInput, WorkflowEventRecord, WorkflowRunRecord, WorkflowRunStepRecord };
669
+ export { CreateRunInput, ListEventsOptions, ListRunsOptions, MemoryStorageAdapter, PaginatedResult, PostgresStorageAdapter as PostgresStorage, PostgresStorageAdapter, PostgresTransactionAdapter, SQLiteStorageAdapter, StepResult, StorageAdapter, UpdateRunInput, WorkflowEventRecord, WorkflowRunRecord, WorkflowRunStepRecord };
670
+ export type { PostgresStorageConfig, PostgresStorageConfig as PostgresStorageOptions, SQLiteStorageConfig };
@@ -3,7 +3,7 @@ import {
3
3
  PostgresStorageAdapter,
4
4
  PostgresTransactionAdapter,
5
5
  SQLiteStorageAdapter
6
- } from "../chunk-5XJBAPXF.js";
6
+ } from "../chunk-4IDPWWGS.js";
7
7
  import "../chunk-JPXPB7U5.js";
8
8
  export {
9
9
  MemoryStorageAdapter,
@@ -124,6 +124,7 @@ interface CreateRunInput {
124
124
  parentRunId?: string;
125
125
  input: Record<string, unknown>;
126
126
  context?: Record<string, unknown>;
127
+ completedSteps?: string[];
127
128
  metadata?: Record<string, unknown>;
128
129
  priority?: number;
129
130
  timeoutMs?: number;
@@ -134,6 +135,7 @@ interface CreateRunInput {
134
135
  interface UpdateRunInput {
135
136
  status?: ExtendedRunStatus;
136
137
  context?: Record<string, unknown>;
138
+ completedSteps?: string[];
137
139
  output?: Record<string, unknown>;
138
140
  error?: {
139
141
  code: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@multiplier-labs/stepflow",
3
- "version": "0.3.4",
3
+ "version": "0.3.8",
4
4
  "description": "A durable, type-safe workflow orchestration engine for Node.js with SQLite persistence, scheduling, and real-time events",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -43,21 +43,25 @@
43
43
  "import": "./dist/scheduler/index.js"
44
44
  }
45
45
  },
46
+ "engines": {
47
+ "node": "^22.22.2 || ^24.15.0 || >=26.0.0"
48
+ },
46
49
  "scripts": {
47
50
  "build": "tsup",
48
51
  "dev": "tsup --watch",
49
52
  "typecheck": "tsc --noEmit",
50
53
  "test": "vitest run",
54
+ "test:package": "node scripts/check-package.mjs",
51
55
  "test:watch": "vitest",
52
56
  "test:coverage": "vitest run --coverage"
53
57
  },
54
58
  "dependencies": {
55
- "cron-parser": "^5.5.0",
56
- "re2": "^1.24.0"
59
+ "cron-parser": "^5.10.1",
60
+ "re2": "^1.27.0"
57
61
  },
58
62
  "peerDependencies": {
59
63
  "better-sqlite3": ">=11.0.0",
60
- "kysely": ">=0.28.16",
64
+ "kysely": ">=0.28.17",
61
65
  "pg": ">=8.13.0"
62
66
  },
63
67
  "peerDependenciesMeta": {
@@ -73,17 +77,25 @@
73
77
  },
74
78
  "devDependencies": {
75
79
  "@types/better-sqlite3": "^7.6.11",
76
- "@types/node": "^22.0.0",
80
+ "@types/node": "^25.6.0",
77
81
  "@types/pg": "^8.18.0",
78
- "@vitest/coverage-v8": "^4.1.4",
79
- "better-sqlite3": "^12.8.0",
80
- "kysely": "^0.28.16",
81
- "pg": "^8.20.0",
82
- "tsup": "^8.5.1",
83
- "typescript": "^5.7.2",
84
- "vitest": "^4.1.4"
82
+ "@vitest/coverage-v8": "^4.1.12",
83
+ "better-sqlite3": "^13.0.3",
84
+ "kysely": "^0.29.4",
85
+ "pg": "^8.23.0",
86
+ "tsup": "^9.0.0",
87
+ "typescript": "^6.0.3",
88
+ "vitest": "^4.1.12"
85
89
  },
86
90
  "overrides": {
87
- "postcss": ">=8.5.10"
88
- }
91
+ "postcss": ">=8.5.18",
92
+ "brace-expansion": ">=5.0.8",
93
+ "esbuild": ">=0.28.1",
94
+ "tar": ">=7.5.21",
95
+ "vite": ">=8.3.3",
96
+ "nanoid": "^3.3.17"
97
+ },
98
+ "files": [
99
+ "dist"
100
+ ]
89
101
  }
@@ -1,146 +0,0 @@
1
- # Daily Code Review: stepflow
2
-
3
- **Date**: 2026-04-11T02:46:56.903Z
4
- **Repository**: /srv/repos/stepflow
5
- **Branch**: main
6
- **Workflow Run**: 592ed81f-2d62-4608-bf34-98470c42c55b
7
- **Session**: 99646d28-b2e3-4d3a-bcdf-1478c0bc9eff
8
-
9
- ---
10
-
11
- Let me verify the most critical findings before writing the final report.Now I have enough detail to write the full report.
12
-
13
- ---
14
-
15
- ## Daily Automated Code Review — `@multiplier-labs/stepflow` v0.2.6
16
-
17
- **Date**: 2026-04-11 | **Commit**: `bd16961` (Initial commit — only commit in repo)
18
-
19
- ---
20
-
21
- ### Critical
22
-
23
- **None found.**
24
-
25
- ---
26
-
27
- ### Warnings
28
-
29
- **[W1] Checkpoint resume derives completedStepKeys from context keys — fragile assumption**
30
- `src/core/engine.ts:569`
31
- ```typescript
32
- const completedStepKeys = new Set(Object.keys(run.context));
33
- ```
34
- `run.context` stores step results keyed by step key. If a step writes to context before throwing, its key will appear in context but the step didn't complete. On resume, that step would be silently skipped. There is no separate `completedSteps` log to distinguish "wrote result" from "finished cleanly."
35
- **Recommendation**: Track a dedicated `completedSteps: string[]` field in the persisted run record, written atomically with the step result, and use that set during resume.
36
-
37
- ---
38
-
39
- **[W2] `isBlockedIp` does not cover IPv6 reserved ranges beyond loopback and link-local**
40
- `src/events/webhook.ts:533–557`
41
- The resolved-IP check (`isBlockedIp`) handles IPv4 private ranges well, and blocks `::1` and `fe80:` for IPv6. However it does not block:
42
- - `fc00::/7` — Unique Local Addresses (ULA, the IPv6 equivalent of RFC 1918)
43
- - `ff00::/8` — Multicast
44
- - `2001:db8::/32` — Documentation range
45
- - `::ffff:0:0/96` — IPv4-mapped IPv6 addresses (could bypass IPv4 private range checks)
46
-
47
- `isBlockedHost` is more aggressive (blocks all bracket-wrapped IPv6 literals), so the static check is safe. But if `validateResolvedHost` receives a resolved IPv6 address in any format other than `[addr]`, the gap applies.
48
- **Recommendation**: In `isBlockedIp`, add IPv4-mapped check (`::ffff:` prefix) and ULA check (`/^f[cd]/i`).
49
-
50
- ---
51
-
52
- **[W3] `enqueueRequest` doesn't guard against `maxConcurrentRequests <= 0`**
53
- `src/events/webhook.ts:475–479`
54
- ```typescript
55
- if (this.activeRequests < this.maxConcurrentRequests) {
56
- execute();
57
- } else {
58
- this.requestQueue.push(execute);
59
- }
60
- ```
61
- If `maxConcurrentRequests` is set to `0` (or negative), the condition is never true and all requests queue forever — effectively a deadlock. There is no constructor validation.
62
- **Recommendation**: Add `if (maxConcurrentRequests <= 0) throw new Error('maxConcurrentRequests must be > 0')` in the constructor.
63
-
64
- ---
65
-
66
- **[W4] `withRetry` abort throws a generic `Error('Aborted')` instead of `WorkflowCanceledError`**
67
- `src/utils/retry.ts:83–85`
68
- ```typescript
69
- if (opts.signal?.aborted) {
70
- throw new Error('Aborted');
71
- }
72
- ```
73
- The `sleep()` helper (line 58) correctly throws `WorkflowCanceledError` on abort, but the pre-attempt abort check throws a plain `Error`. Downstream error classification (e.g. `instanceof WorkflowCanceledError`) will fail for aborts caught before the first sleep.
74
- **Recommendation**: Change to `throw new WorkflowCanceledError('run')` to match the `sleep()` path.
75
-
76
- ---
77
-
78
- **[W5] Hook errors log without run context**
79
- `src/core/orchestrator.ts:258`
80
- ```typescript
81
- logger.error('afterRun hook failed:', hookError);
82
- ```
83
- The error is intentionally swallowed (correct behavior), but the log line has no `runId` or `kind`, making production debugging unnecessarily hard.
84
- **Recommendation**: `logger.error(\`afterRun hook failed for run ${runId}:\`, hookError)`
85
-
86
- ---
87
-
88
- **[W6] Integration and edge-case test coverage gaps**
89
-
90
- No integration tests exercise the full lifecycle across multiple adapters. Specific gaps:
91
- - Resume of a partially-completed run (directly relevant to W1 above)
92
- - `maxConcurrency` queuing with priority ordering
93
- - Webhook retries under network failure simulations
94
- - Scheduler DST-boundary cron behavior
95
- - Hook error recovery scenarios (`beforeRun`, `afterRun`, `onStepError` throwing)
96
- - SQLite transaction rollback verification
97
-
98
- The current test suite is unit-level and covers the happy path well, but these edge cases are where durable-execution bugs hide.
99
-
100
- ---
101
-
102
- ### Info
103
-
104
- **[I1] CI workflow publishes without running tests**
105
- `.github/workflows/publish.yml:23–29`
106
- The publish job runs `npm ci` → `npm run build` → `npm publish`. There is no `npm test` or `npm run typecheck` step. A broken release could be published if tests are skipped.
107
- **Recommendation**: Add `- run: npm test` and `- run: npm run typecheck` between Install and Publish steps.
108
-
109
- ---
110
-
111
- **[I2] `calculateRetryDelay` is defined but never called internally**
112
- `src/utils/retry.ts:115–121`
113
- `withRetry` computes delay inline (`currentDelay * opts.backoff`) rather than calling `calculateRetryDelay`. The exported helper is a public API, but the internal inconsistency means the two can drift.
114
- **Recommendation**: Refactor `withRetry` to call `calculateRetryDelay` for the delay computation, or document that they are intentionally separate.
115
-
116
- ---
117
-
118
- **[I3] Peer dependency lower bounds are not tested**
119
- `package.json:57–71`
120
- `better-sqlite3>=11`, `kysely>=0.27`, `pg>=8.13` are wide ranges. Only the devDependency pinned versions are tested. If a consumer uses the minimum allowed version and it has a breaking API difference, the error surface is silent.
121
- **Recommendation**: Add a CI matrix job that installs minimum peer dependency versions and runs the test suite.
122
-
123
- ---
124
-
125
- **[I4] Short webhook secrets not warned against**
126
- `src/events/webhook.ts:24`
127
- The `secret` field accepts any string. A very short secret (< 16 bytes) produces a weak HMAC. The JSDoc example (`process.env.WEBHOOK_SECRET`) is good, but there is no runtime warning or minimum-length enforcement.
128
- **Recommendation**: Add a `secret.length < 16` warning log in `signPayload` at minimum, or throw in the constructor.
129
-
130
- ---
131
-
132
- **[I5] `retryDelay` in webhook config is not applied with backoff**
133
- `src/events/webhook.ts` — webhook retries use a fixed `retryDelay` with no backoff, while the core `withRetry` utility supports exponential backoff. This inconsistency means repeated webhook failures hammer the endpoint at a fixed rate.
134
- **Recommendation**: Either apply backoff to webhook retries (using the existing `withRetry` utility) or document the fixed-delay intent.
135
-
136
- ---
137
-
138
- ### Summary
139
-
140
- | Severity | Count |
141
- |---|---|
142
- | Critical | 0 |
143
- | Warning | 6 |
144
- | Info | 5 |
145
-
146
- The codebase is well-structured with good TypeScript discipline, solid SSRF protection, and thoughtful SQLite query design. The top priorities are **W1** (checkpoint resume correctness) and **W4** (abort error type mismatch), both of which are correctness bugs rather than just style issues. **W2** is a security hardening gap. The CI publish-without-tests gap (**I1**) is a process risk worth closing quickly.
@@ -1,74 +0,0 @@
1
- # Daily Code Review: stepflow
2
-
3
- **Date**: 2026-04-13T01:32:56.537Z
4
- **Repository**: /srv/repos/stepflow
5
- **Branch**: main
6
- **Workflow Run**: 12fb929b-3da3-4e1b-a8fc-e9b497a0eb42
7
- **Session**: c3af596a-a1f0-43c3-922e-8ed0cd0562f0
8
-
9
- ---
10
-
11
- ---
12
-
13
- ## Daily Code Review — 2026-04-13
14
-
15
- **Project:** `@multiplier-labs/stepflow` — TypeScript workflow orchestration library
16
- **Coverage:** 93.88% | **Recent changes:** Initial commit only (no incremental changes in 7 days)
17
-
18
- ---
19
-
20
- ### CRITICAL (4)
21
-
22
- **C1 — Unhandled `JSON.parse()` in all storage adapters**
23
- `src/storage/sqlite.ts`, `src/storage/postgres.ts`, `src/scheduler/*-persistence.ts`
24
- Every row deserialization call is bare `JSON.parse()`. Corrupted DB rows will throw unhandled `SyntaxError` and crash the process. Wrap all calls in try-catch with safe fallbacks.
25
-
26
- **C2 — DNS lookup blocks event loop on every webhook send**
27
- `src/events/webhook.ts` ~line 417
28
- `validateResolvedHost()` calls `dns.lookup()` with no timeout, no caching. A stalling DNS resolver can hang the entire event loop for 30+ seconds. Add a per-call timeout (via `AbortController`) and a short-TTL cache.
29
-
30
- **C3 — No `shutdown()` method; timers are untracked**
31
- `src/core/engine.ts` ~lines 369–374
32
- `setTimeout` / `setImmediate` handles in `launchRun()` are never stored or cleared. The process cannot exit cleanly while delayed runs are pending. Implement `WorkflowEngine.shutdown()` that cancels all tracked handles.
33
-
34
- **C4 — Race condition in `waitForRun`**
35
- `src/core/engine.ts` ~lines 480–527
36
- Storage is polled _before_ subscribing to events. A run completing in that window emits an event that is never received, causing an infinite wait. Fix: subscribe first, then check storage; unsubscribe immediately if terminal.
37
-
38
- ---
39
-
40
- ### WARNING (10)
41
-
42
- | # | File | Issue |
43
- |---|------|-------|
44
- | W1 | `package.json` deps | `npm audit`: `picomatch` (HIGH ReDoS) + `brace-expansion` (MODERATE hang). Run `npm audit fix`. |
45
- | W2 | `src/storage/postgres.ts:13-16` | 48 `any` casts — concentrated in lazy Kysely imports. Use conditional types or `unknown` + guards. |
46
- | W3 | `src/events/webhook.ts` | Webhook secret length not enforced despite docs recommending ≥ 32 bytes. Validate in `addEndpoint()`. |
47
- | W4 | `src/events/webhook.ts` | DNS validation is outside the retry loop — DNS failures skip all retry logic. Move inside loop. |
48
- | W5 | `src/scheduler/cron.ts:96-97` | `start()` after `stop()` can create duplicate subscriptions. Unconditionally unsubscribe at top of `start()`. |
49
- | W6 | `src/storage/postgres.ts` | PG connection pool not closed if adapter goes out of scope without explicit `close()`. Document + warn. |
50
- | W7 | `src/core/engine.ts:405-412` | No depth/count limit on `spawnChild()` — recursive workflows can spawn unboundedly. Add `maxChildDepth`. |
51
- | W8 | `src/core/orchestrator.ts:87-93` | `emitEvent()` called without error handling — transport failures can abort workflow execution. Wrap in try-catch. |
52
- | W9 | `src/scheduler/cron.ts` | Invalid persisted cron strings crash the scheduler on startup. Catch `CronExpressionParser.parse()` errors. |
53
- | W10 | `src/storage/sqlite.ts` | SQLite single-writer limitation not documented. Add prominent warning; recommend Postgres for multi-worker. |
54
-
55
- ---
56
-
57
- ### INFO (5)
58
-
59
- - **I1** — Test files are very large (engine.test.ts: 1364 lines). Split by feature for maintainability.
60
- - **I2** — Resume/checkpoint semantics undocumented for distributed use (single-process only).
61
- - **I3** — `registerWorkflow()` doesn't validate `kind` format or duplicate step keys.
62
- - **I4** — Error stacks sanitized on write but not on read — defense-in-depth gap.
63
- - **I5** — No circuit breaker for consistently failing webhook endpoints.
64
-
65
- ---
66
-
67
- **Top 5 priorities:**
68
- 1. `JSON.parse()` error handling in all storage adapters (C1)
69
- 2. `WorkflowEngine.shutdown()` with timer cleanup (C3)
70
- 3. Fix subscribe-then-check ordering in `waitForRun` (C4)
71
- 4. `npm audit fix` for dep vulnerabilities (W1)
72
- 5. DNS caching + timeout in webhook transport (C2/W4)
73
-
74
- Report saved to `.codekin/reports/code-review/2026-04-13_code-review-daily.md`.
@@ -1,179 +0,0 @@
1
- # Comment Assessment: stepflow
2
-
3
- **Date**: 2026-04-04T06:59:29.464Z
4
- **Repository**: /srv/repos/stepflow
5
- **Branch**: main
6
- **Workflow Run**: c4721f8a-9287-4e91-b3a7-17151c71b861
7
- **Session**: 0bf68b75-f778-4c55-9c9d-a011d10130be
8
-
9
- ---
10
-
11
- Now I have a thorough picture of the codebase. Let me produce the final report.
12
-
13
- ## Summary
14
-
15
- **Language**: TypeScript (strict mode) | **Files**: 43 source files (~14,000 LoC) | **Modules**: core, storage, events, scheduler, planning, utils
16
-
17
- Overall comment coverage is **high** — approximately **90%** of public API surface is documented, and ~70% of complex logic has inline explanation. The codebase demonstrates professional-grade JSDoc discipline: every exported type, interface, config object, and error class carries a doc comment. File-level module comments are present on nearly every file. The main gaps are a duplicated/misplaced JSDoc block in `postgres.ts`, thin coverage on a handful of `StorageAdapter` implementation methods, and the absence of `@param`/`@returns` tags on several private helpers. Test files are undocumented, which is standard practice but leaves intent implicit for complex scenarios.
18
-
19
- **Overall rating: 8.5 / 10**
20
-
21
- ---
22
-
23
- ## Well-Documented Areas
24
-
25
- ### `src/core/orchestrator.ts`
26
- Exemplary documentation. The two private helpers `executeWithTimeout` and `raceWithAbort` carry unusually thorough comments explaining *why* the `finally` cleanup blocks exist (memory-leak prevention, listener accumulation). The module-level JSDoc accurately describes the file's role. Inline comments mark every logical phase of `executeWorkflow` and `executeStep` (checkpoint resume, cancellation checks, error strategy branching).
27
-
28
- ```typescript
29
- /**
30
- * Execute a function with a timeout.
31
- *
32
- * Uses Promise.race to resolve as soon as either the function completes, the
33
- * timeout fires, or the abort signal triggers. The `finally` block is critical:
34
- * without it, the losing promise's timer/listener would remain active, leaking
35
- * memory — especially problematic in long-running engines processing many steps.
36
- */
37
- ```
38
-
39
- ### `src/utils/retry.ts`
40
- Every exported symbol has full JSDoc with `@param`, `@returns`, and `@throws` tags. The `sleep` function documents its cancellation contract. `DEFAULT_RETRY_OPTIONS` is explicitly exported and described. A closing comment on the unreachable branch (`// This should never be reached, but TypeScript needs it`) is an honest and useful annotation.
41
-
42
- ### `src/core/types.ts`
43
- All foundational types are documented. `StepErrorStrategy` includes inline prose describing the effect of each union member:
44
- ```typescript
45
- /**
46
- * - 'fail': Stop the workflow immediately
47
- * - 'retry': Retry the step up to maxRetries times
48
- * - 'skip': Mark as skipped and continue to next step
49
- */
50
- ```
51
-
52
- ### `src/utils/errors.ts`
53
- Each of the eight custom error classes has a one-line JSDoc that clearly states the triggering condition. `WorkflowEngineError.fromError` documents its fallback behavior.
54
-
55
- ### `src/storage/postgres.ts` — configuration and schema sections
56
- `PostgresStorageConfig` has `@example` blocks for both the basic and shared-pool cases. Kysely row types (`WorkflowRunsTable`, `WorkflowRunStepsTable`, etc.) document every column inline, including semantic meaning, units, and null semantics. The `qb` getter explains the schema-scoping invariant.
57
-
58
- ### `src/utils/postgres-deps.ts`
59
- The lazy-loader pattern is precisely explained in the file-level comment, and the `@throws` tags on `loadPostgresDeps` enumerate both failure modes. The `// any is intentional here` comment pre-empts reviewer questions about the `PostgresDeps` interface.
60
-
61
- ### `src/events/socketio.ts`
62
- `setupClientHandlers` includes a full `@example` block showing correct usage including the authorization callback pattern. The `close()` method clarifies that the underlying Socket.IO socket lifecycle is the caller's responsibility.
63
-
64
- ### `src/planning/planner.ts` — `scoreConditions`
65
- The scoring formula comment is unusually transparent:
66
- ```typescript
67
- // Base score 50 ensures condition-matched recipes beat unconditional defaults (score 10).
68
- // Each additional condition adds 10 points, capped at 100 so forced recipes (100) still win.
69
- ```
70
-
71
- ### `src/utils/id.ts`
72
- Despite being a single exported function, it documents the ID format, design rationale (time-ordering, collision resistance, URL safety), and the approximate output length.
73
-
74
- ---
75
-
76
- ## Underdocumented Areas
77
-
78
- | File | Issue | Severity |
79
- |---|---|---|
80
- | `src/storage/postgres.ts:563–565` | Two JSDoc blocks stacked immediately on top of each other (`listRuns` doc + `applyRunsFilters` doc) with no actual method between them; the `listRuns` method itself gets the private helper's comment, and `listRuns` is effectively undocumented | High |
81
- | `src/storage/postgres.ts:516–524` | `getRun`, `createStep`, `getStep`, `updateStep`, `getSteps`, `createEvent`, `listEvents` — none have JSDoc; only `createRun`, `updateRun`, and `listRuns` are documented | High |
82
- | `src/core/engine.ts:109–119` | `QueuedRun` interface is documented but the `WorkflowEngine` class declaration at line 121 has no JSDoc block (the preceding `@example` block belongs to a dangling doc comment from the closing `*/` of the example at line 107, making it visually orphaned) | High |
83
- | `src/storage/types.ts:80–200` | `WorkflowEventRecord`, `StepResult`, `StepRecord`, `CreateRunInput`, `UpdateRunInput`, `StorageAdapter` interface and all its method signatures lack individual JSDoc; only the top-level record types are documented | Medium |
84
- | `src/scheduler/cron.ts` (internal methods) | Private methods `checkAndTriggerSchedules`, `handleWorkflowCompletionTriggers`, `calculateNextRun` have no JSDoc; the polling and event-subscription architecture is not explained | Medium |
85
- | `src/core/engine.ts:324–332` | `executeRun` is documented as "Execute a run (internal method)" with no detail; it is a thin wrapper that calls `launchRun` — the indirection is unexplained | Medium |
86
- | `src/storage/sqlite.ts` | `createStep`, `updateStep`, `getSteps`, `createEvent`, `listEvents` methods lack JSDoc; only configuration types are documented | Medium |
87
- | `src/planning/registry.ts` | `MemoryStepHandlerRegistry` and `MemoryRecipeRegistry` class-level JSDoc is absent; method comments are single-line but several complex query methods (e.g., `getByKind`, `getVariant`) have no parameter documentation | Medium |
88
- | `src/events/webhook.ts` | `WebhookEventTransport` class body is not visible in sampling; retry/backoff logic for failed webhook deliveries is expected to lack inline explanation based on surrounding pattern | Medium |
89
- | `src/storage/memory.ts:67–200` | `listRuns` filter/sort/pagination logic has inline `// Filter by X` headers but no explanation of sort stability guarantees or edge-case behaviour (e.g., null `startedAt`) | Low |
90
- | `src/scheduler/sqlite-persistence.ts` | Prepared-statement initialization pattern (using `stmts` nullable object) is not commented; the reason for lazy initialization vs. constructor setup is implicit | Low |
91
- | `src/utils/logger.ts` | `sanitizeErrorForStorage` is exported but has no JSDoc — its role (stripping sensitive fields before DB writes) is non-obvious from the name alone | Low |
92
- | `src/core/engine.ts` (test files) | `src/core/engine.test.ts` has no describe-level comments explaining what scenario each test group targets; complex concurrency and resume tests lack rationale comments | Low |
93
- | `src/storage/types.ts` | `ExtendedRunStatus` and `ExtendedStepStatus` comment says "New" but those are likely legacy designations now; no explanation of when to use extended vs. core status types | Low |
94
- | `src/planning/types.ts` | `PlanModificationType` union values (`'add_step'`, `'remove_step'`, `'modify_step'`, `'set_default'`) are not individually annotated with their intended semantics | Low |
95
-
96
- ---
97
-
98
- ## Comment Quality Issues
99
-
100
- **1. Stacked / orphaned JSDoc block — `src/storage/postgres.ts:563–568`**
101
-
102
- ```typescript
103
- /**
104
- * List workflow runs with filtering and pagination.
105
- */
106
- /**
107
- * Apply common run filters to a Kysely query builder.
108
- * Used by both the data query and count query in listRuns to avoid duplication.
109
- */
110
- private applyRunsFilters<T ...>(
111
- ```
112
-
113
- Two `/** */` blocks appear consecutively. TypeScript/JSDoc tooling attaches the *last* block to the declaration, meaning `listRuns` (a public method) loses its documentation entirely and only the private helper `applyRunsFilters` gets a comment. The public method's JSDoc is silently discarded. This is a bug in the comment structure.
114
-
115
- **2. `WorkflowEngine` class body starts without a JSDoc — `src/core/engine.ts:109–121`**
116
-
117
- The `@example` closing marker at line 107 (`*/`) is followed by the undocumented `QueuedRun` interface, then the `export class WorkflowEngine` at line 121 with no doc comment of its own. The intended class-level JSDoc (lines 83–108) is rendered as a freestanding comment, not as attached JSDoc for `WorkflowEngine`. IDEs will not show hover documentation for the class constructor.
118
-
119
- **3. `StorageAdapter.listRuns` — `src/storage/types.ts` (approximately line 130–160)**
120
-
121
- The `StorageAdapter` interface lists all method signatures but provides no per-method JSDoc. A consumer implementing the interface receives no guidance on whether `listRuns` should be transactional, whether filters are AND- or OR-combined, or what an empty result looks like.
122
-
123
- **4. `cancelRun` comment inaccuracy — `src/core/engine.ts:444`**
124
-
125
- ```typescript
126
- // Update status in storage (the orchestrator will also update on completion)
127
- await this.storage.updateRun(runId, { status: 'canceled', finishedAt: new Date() });
128
- ```
129
-
130
- The comment says "the orchestrator will also update on completion" but when `cancelRun` is called, the orchestrator may not be running (if the run was still queued). The comment implies a double-write that is only sometimes true, which could mislead a reader investigating status inconsistencies.
131
-
132
- **5. `ensureInitialized` — `src/storage/postgres.ts:246`**
133
-
134
- This private guard method has no JSDoc. Since it throws on un-initialized state, documenting the throw condition would aid callers implementing `StorageAdapter`.
135
-
136
- **6. `ExtendedRunStatus` comment — `src/storage/types.ts:12–23`**
137
-
138
- ```typescript
139
- /**
140
- * Extended status of a workflow run.
141
- * Adds 'pending' and 'timeout' to the core statuses.
142
- */
143
- ```
144
-
145
- `RunStatus` (core) already includes `'queued'` and `'running'`; `ExtendedRunStatus` adds `'pending'` and `'timeout'`. The comment correctly notes the additions, but gives no guidance on *when* a consumer should use `ExtendedRunStatus` vs. `RunStatus` — the distinction between the two parallel type hierarchies is undocumented.
146
-
147
- ---
148
-
149
- ## Recommendations
150
-
151
- **1. Fix the stacked JSDoc block in `src/storage/postgres.ts:563–568`**
152
- Remove or merge the orphaned `listRuns` JSDoc into a single block placed directly above the `async listRuns(...)` method declaration. The `applyRunsFilters` private helper should retain its own comment, but it should immediately precede that private method, not `listRuns`. This is a correctness issue: as written, `listRuns` is invisible to IDE hover and documentation generators.
153
-
154
- **2. Attach the `WorkflowEngine` class-level JSDoc in `src/core/engine.ts`**
155
- The `@example` block (lines 83–107) and the `QueuedRun` interface (lines 109–119) currently sit between the intended class doc and the class declaration. Move `QueuedRun` above the class-level JSDoc, or place the class JSDoc immediately before `export class WorkflowEngine` at line 121. Without this fix, TypeScript language servers do not display hover documentation for the class or its constructor.
156
-
157
- **3. Add JSDoc to `StorageAdapter` method signatures in `src/storage/types.ts`**
158
- The `StorageAdapter` interface is the primary extension point for library consumers implementing custom backends. Document at minimum: `createRun`, `updateRun`, `listRuns` (note AND semantics for filters), `createStep`, `updateStep`, and `createEvent`. Include `@param` descriptions and `@returns` contracts. This directly impacts third-party adapter authors.
159
-
160
- **4. Document `sanitizeErrorForStorage` in `src/utils/logger.ts`**
161
- This exported utility performs security-sensitive work (scrubbing error objects before persistence), but it has no JSDoc. A doc comment should state what fields are removed (e.g., `stack`), why (storage size / privacy), and what the return type contract is. Its current name gives no indication it performs sanitization beyond what the type system shows.
162
-
163
- **5. Document the `ExtendedRunStatus` / `RunStatus` dual hierarchy in `src/storage/types.ts`**
164
- Add a comment block before `ExtendedRunStatus` explaining when each type should be used (e.g., "Use `RunStatus` for the core engine; use `ExtendedRunStatus` when working with the extended storage schema which includes `pending` and `timeout`"). This prevents consumers from accidentally mixing the two.
165
-
166
- **6. Add inline comments to `CronScheduler`'s internal scheduling loop in `src/scheduler/cron.ts`**
167
- The private methods responsible for polling (`checkAndTriggerSchedules`), event-based triggering (`handleWorkflowCompletionTriggers`), and next-run calculation (`calculateNextRun`) contain the most nuanced logic in the scheduler module (timezone handling, cron expression parsing, status matching). Each should have at minimum a one-paragraph JSDoc explaining its role and any non-obvious invariants (e.g., why the poll interval is 1 second by default, how `lastRunAt` prevents double-firing).
168
-
169
- **7. Fix the `cancelRun` comment inaccuracy in `src/core/engine.ts:444`**
170
- Replace `// Update status in storage (the orchestrator will also update on completion)` with a comment that accurately reflects the dual-write scenario: the orchestrator updates status when it catches `WorkflowCanceledError`, but this pre-emptive write ensures the status is visible immediately and handles the case where the run was queued (never started). This removes a misleading implication about guaranteed orchestrator involvement.
171
-
172
- **8. Add JSDoc to `getRun`, `createStep`, `updateStep`, and `listEvents` in `src/storage/postgres.ts`**
173
- These `StorageAdapter` implementation methods (~lines 516–750) have no doc comments, unlike `createRun`, `updateRun`, and `listRuns`. At minimum, note any PostgreSQL-specific behaviour that differs from the `MemoryStorageAdapter` (e.g., JSON serialization round-trips, `null` vs. `undefined` handling for optional fields).
174
-
175
- **9. Annotate `PlanModificationType` union values in `src/planning/types.ts`**
176
- The four modification types (`'add_step'`, `'remove_step'`, `'modify_step'`, `'set_default'`) are used in the planner output to describe what was changed, but their intended semantics are not documented. Add inline comments (or expand the type into a documented discriminated union) so that consumers of `Plan.modifications` know how to interpret and render each modification type.
177
-
178
- **10. Add a brief rationale comment in `src/core/engine.ts:568–574` (`resumeRun` checkpoint logic)**
179
- The line `const completedStepKeys = new Set(Object.keys(run.context))` silently encodes a convention: that the `context` object's keys are exactly the completed step keys. This is a non-obvious coupling between the orchestrator's checkpoint-save strategy (`storage.updateRun(runId, { context: { ...context.results } })`) and the resume logic. A one-line comment (`// context keys == completed step keys by orchestrator checkpoint convention`) would make this invariant explicit and prevent it from being accidentally broken.