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 +53 -12
- package/dist/compat/api.d.ts +2 -0
- package/dist/compat/api.js +23 -4
- package/dist/compat/workflow.d.ts +1 -0
- package/dist/compat/workflow.js +3 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +4 -0
- package/package.json +2 -2
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.
|
|
144
|
+
## v0.2.x — compat layer (all versions re-benchmarked)
|
|
145
145
|
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
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
|
-
|
|
|
153
|
-
|
|
154
|
-
|
|
|
155
|
-
|
|
|
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
|
-
|
|
158
|
-
|
|
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
|
+
```
|
package/dist/compat/api.d.ts
CHANGED
|
@@ -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'
|
package/dist/compat/api.js
CHANGED
|
@@ -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
|
-
|
|
79
|
-
|
|
80
|
-
|
|
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
|
package/dist/compat/workflow.js
CHANGED
|
@@ -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:
|
|
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.
|
|
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",
|
|
7
|
+
"main": "dist/index.js",
|
|
8
8
|
"types": "dist/index.d.ts",
|
|
9
9
|
"exports": {
|
|
10
10
|
".": {
|