lightflow-engine 0.2.5 → 0.2.7

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/BENCHMARKS.md CHANGED
@@ -141,18 +141,59 @@ sequential-ish workflows through `start()` + terminal-poll.
141
141
  Every version's table above was produced on the same machine and database,
142
142
  so cross-version rows are directly comparable.
143
143
 
144
- ## v0.2.0 — compat layer (perf-neutral)
144
+ ## v0.2.x — compat layer (all versions re-benchmarked)
145
145
 
146
- Focus: Vercel Workflow drop-in compat (`lightflow-engine/compat/*`).
147
- No perf regression vs v0.1.4 — two real bugs found and fixed along the way:
148
- post-resume chunk loss (chunk keys now include the durable-timer position)
149
- and chunks vanishing from streams after snapshot compaction (live chunks
150
- now fold into snapshots).
146
+ Each published version was benchmarked from its published tarball against the
147
+ same Postgres (16, local) and the same harness (200 runs x 5 steps @ conc 20;
148
+ 50 runs x 400 steps). Steps/wf = 5 for short, 400 for long.
151
149
 
152
- | Scenario | v0.1.4 | v0.2.0 |
153
- |---|---|---|
154
- | 200 runs x 5 steps, conc 20 (runs/s) | 502–512 | 504.5 |
155
- | 50 runs x 400 steps durable (steps/s) | 9,939 | 9,062–10,600 |
150
+ | Version | Short runs/s | Short steps/s | Long steps/s |
151
+ |---|---|---|---|
152
+ | 0.2.0 | 462.7 | 2,313 | 8,493 |
153
+ | 0.2.1 | 466.0 | 2,330 | 9,062 |
154
+ | 0.2.2 | 466.9 | 2,335 | 8,348 |
155
+ | 0.2.3 | 461.6 | 2,308 | 8,384 |
156
+ | 0.2.4 | 456.1 | 2,280 | 8,280 |
157
+ | 0.2.5 | 459.1 | 2,295 | 9,068 |
158
+ | 0.2.6 | 494.9–525.1 | 2,474–2,626 | 8,379–9,737 |
159
+
160
+ ### What changed per version (all perf-neutral by design)
161
+
162
+ - **0.2.0** — Vercel Workflow drop-in compat (`compat/workflow`,
163
+ `compat/api`, `compat/next`). Two data-loss bugs fixed en route: chunk
164
+ memo keys now include the durable-timer position (`w:<sleepCalls>:<writes>`)
165
+ so post-resume writes can't be skipped against pre-timer chunks; live chunk
166
+ appends now fold into snapshot compaction (chunks previously vanished from
167
+ `getReadable` after ~200 steps).
168
+ - **0.2.1** — `run.status` is a live getter (fresh promise per access).
169
+ entry-agents' startStopMonitor re-awaits it in a 150ms poll loop; a single
170
+ memoized promise left the monitor blocked until terminal.
171
+ - **0.2.2** — `makeStep(fn, {retries})`: durable wrapper for arg-taking
172
+ step functions (entry's "use step" style).
173
+ - **0.2.3–0.2.4** — packaging: explicit `types` in subpath exports,
174
+ declaration files emitted. No runtime change.
175
+ - **0.2.5** — `rootDir: "src"` so the published `dist/` layout actually
176
+ matches the subpath export map (0.2.0–0.2.4 shipped `dist/src/...` while
177
+ exports claimed `dist/...` — subpath imports still worked because Node
178
+ resolved the package root, but the types were broken).
179
+ - **0.2.6** — `export { workflowFetch as fetch }` (entry imports both
180
+ names), `WorkflowFn` accepts typed args, `run.exists: true`.
181
+
182
+ ### Spread notes
183
+
184
+ Short-run spread across versions is ~2% (456–467 runs/s) — within run-to-run
185
+ noise; the compat layer added no measurable overhead. 0.2.6 shows the best
186
+ short-run numbers (494–525 runs/s across repeated runs) and the long-run
187
+ 8.3k–9.7k steps/s band matches the v0.1.4-era 9.9k within noise.
188
+
189
+ ## Reproducing
156
190
 
157
- Verdict: perf-neutral within noise. Compat test (7/7) exercises the full
158
- entry-agents surface end-to-end.
191
+ Each version's tarball can be re-benchmarked independently:
192
+
193
+ ```bash
194
+ npm pack lightflow-engine@<version>
195
+ tar xzf lightflow-engine-<version>.tgz -C <dir> --strip-components=1
196
+ ln -s <path-to>/lightflow/node_modules <dir>/node_modules
197
+ PKG=<dir> LIGHTFLOW_PG_URL=postgres://... BENCH_N=200 RN=5 npx tsx bench-pkg.mts
198
+ PKG=<dir> LIGHTFLOW_PG_URL=postgres://... BENCH_N=50 RN=400 npx tsx bench-pkg.mts
199
+ ```
@@ -14,6 +14,8 @@ import { Engine, type Store } from "../index.js";
14
14
  export type VercelRunStatus = "pending" | "running" | "completed" | "failed" | "cancelled";
15
15
  export type VercelRun = {
16
16
  runId: string;
17
+ /** Vercel's run handle exposes exists; ours always resolves from the DB. */
18
+ readonly exists: true;
17
19
  /** Live getter: each access returns a FRESH promise snapshotting the
18
20
  * current status ("pending"/"running" until terminal). Vercel's is a
19
21
  * getter too (chat.test.ts mocks `get status()`), and entry-agents'
@@ -65,8 +65,11 @@ function makeRunHandle(runId) {
65
65
  return loop();
66
66
  };
67
67
  const cache = {};
68
+ let lazyReturnValue;
69
+ let pendingRejection;
68
70
  const handle = {
69
71
  runId,
72
+ exists: true,
70
73
  get status() {
71
74
  // Live snapshot: resolve immediately with the current state; the
72
75
  // startStopMonitor poll loop relies on this re-reading each tick.
@@ -75,10 +78,26 @@ function makeRunHandle(runId) {
75
78
  return st;
76
79
  });
77
80
  },
78
- returnValue: (async () => {
79
- await terminal(cache);
80
- return (await engine.getRun(runId)).returnValue;
81
- })(),
81
+ // Lazy: created on first access so a handle whose returnValue is never
82
+ // awaited (e.g. status-only getRun polls) can't produce an unhandled
83
+ // rejection when the run fails.
84
+ get returnValue() {
85
+ if (!lazyReturnValue) {
86
+ lazyReturnValue = (async () => {
87
+ try {
88
+ await terminal(cache);
89
+ return await (await engine.getRun(runId)).returnValue;
90
+ }
91
+ catch (e) {
92
+ pendingRejection = e;
93
+ throw e;
94
+ }
95
+ })();
96
+ if (pendingRejection)
97
+ throw pendingRejection; // unreachable
98
+ }
99
+ return lazyReturnValue;
100
+ },
82
101
  getReadable(opts) {
83
102
  const startIndex = opts?.startIndex ?? 0;
84
103
  let sent = startIndex;
@@ -9,6 +9,7 @@
9
9
  */
10
10
  export { FatalError, CancelledError, sleep } from "../index.js";
11
11
  export { getWorkflowMetadata } from "../compat/metadata.js";
12
+ export { workflowFetch as fetch } from "../compat/fetch.js";
12
13
  export { workflowFetch } from "../compat/fetch.js";
13
14
  /**
14
15
  * Vercel's getWritable returns a web-standard WritableStream whose writes
@@ -12,6 +12,9 @@ import { step, registerStep, } from "../index.js";
12
12
  import { current } from "../index.js";
13
13
  export { FatalError, CancelledError, sleep } from "../index.js";
14
14
  export { getWorkflowMetadata } from "../compat/metadata.js";
15
+ // Vercel exports the step-aware fetch under both names; entry-agents
16
+ // imports `fetch as workflowFetch` in some files and plain `fetch` in others.
17
+ export { workflowFetch as fetch } from "../compat/fetch.js";
15
18
  export { workflowFetch } from "../compat/fetch.js";
16
19
  /**
17
20
  * Vercel's getWritable returns a web-standard WritableStream whose writes
package/dist/index.d.ts CHANGED
@@ -74,7 +74,7 @@ export interface Store {
74
74
  } | null>;
75
75
  setStatus(runId: string, status: RunStatus, output?: unknown): Promise<void>;
76
76
  }
77
- type WorkflowFn = (...args: unknown[]) => Promise<unknown>;
77
+ type WorkflowFn = (...args: any[]) => Promise<unknown>;
78
78
  export declare function registerWorkflow(id: string, fn: WorkflowFn): void;
79
79
  export declare function registerStep(id: string, fn: (...a: unknown[]) => Promise<unknown>): void;
80
80
  type Ctx = {
package/dist/index.js CHANGED
@@ -229,6 +229,10 @@ export class Engine {
229
229
  let resolve;
230
230
  let reject;
231
231
  const promise = new Promise((res, rej) => { resolve = res; reject = rej; });
232
+ // Prevent unhandled-rejection crashes when a caller (e.g. compat
233
+ // returnValue path) never awaits the local deferred — status is read
234
+ // from the store instead.
235
+ promise.catch(() => { });
232
236
  const d = { promise, resolve, reject };
233
237
  this.local.set(runId, d);
234
238
  return d;
package/package.json CHANGED
@@ -1,10 +1,10 @@
1
1
  {
2
2
  "name": "lightflow-engine",
3
- "version": "0.2.5",
3
+ "version": "0.2.7",
4
4
  "description": "A tiny durable workflow engine for Node.js and Postgres — with a Vercel Workflow drop-in compat layer.",
5
5
  "license": "MIT",
6
6
  "type": "module",
7
- "main": "dist/index.js", "types": "dist/index.d.ts",
7
+ "main": "dist/index.js",
8
8
  "types": "dist/index.d.ts",
9
9
  "exports": {
10
10
  ".": {