lightflow-engine 0.2.6 → 0.2.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.
- package/BENCHMARKS.md +53 -12
- package/dist/compat/api.js +30 -11
- package/dist/index.js +11 -2
- package/package.json +1 -1
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.js
CHANGED
|
@@ -12,18 +12,19 @@
|
|
|
12
12
|
*/
|
|
13
13
|
import { Engine, registerWorkflow } from "../index.js";
|
|
14
14
|
import { workflowIdFor } from "./workflow.js";
|
|
15
|
-
|
|
16
|
-
|
|
15
|
+
const g = globalThis;
|
|
16
|
+
if (!g.__lightflowCompat)
|
|
17
|
+
g.__lightflowCompat = { engine: null, store: null };
|
|
17
18
|
/** Configure the compat layer with a store/engine (call once at boot). */
|
|
18
19
|
export function initWorkflowApi(store, engine) {
|
|
19
|
-
|
|
20
|
-
|
|
20
|
+
g.__lightflowCompat.store = store;
|
|
21
|
+
g.__lightflowCompat.engine = engine ?? new Engine(store);
|
|
21
22
|
}
|
|
22
23
|
export function getEngine() {
|
|
23
|
-
if (!
|
|
24
|
+
if (!g.__lightflowCompat.engine) {
|
|
24
25
|
throw new Error("workflow/api not initialized — call initWorkflowApi(store) at startup");
|
|
25
26
|
}
|
|
26
|
-
return
|
|
27
|
+
return g.__lightflowCompat.engine;
|
|
27
28
|
}
|
|
28
29
|
/**
|
|
29
30
|
* Register a workflow function under a stable id and start it. Mirrors
|
|
@@ -31,7 +32,7 @@ export function getEngine() {
|
|
|
31
32
|
*/
|
|
32
33
|
function makeRunHandle(runId) {
|
|
33
34
|
const engine = getEngine();
|
|
34
|
-
const store =
|
|
35
|
+
const store = g.__lightflowCompat.store;
|
|
35
36
|
/** One DB read -> Vercel status vocabulary. */
|
|
36
37
|
const readStatus = async () => {
|
|
37
38
|
const row = await store.getRun(runId);
|
|
@@ -65,6 +66,8 @@ function makeRunHandle(runId) {
|
|
|
65
66
|
return loop();
|
|
66
67
|
};
|
|
67
68
|
const cache = {};
|
|
69
|
+
let lazyReturnValue;
|
|
70
|
+
let pendingRejection;
|
|
68
71
|
const handle = {
|
|
69
72
|
runId,
|
|
70
73
|
exists: true,
|
|
@@ -76,10 +79,26 @@ function makeRunHandle(runId) {
|
|
|
76
79
|
return st;
|
|
77
80
|
});
|
|
78
81
|
},
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
82
|
+
// Lazy: created on first access so a handle whose returnValue is never
|
|
83
|
+
// awaited (e.g. status-only getRun polls) can't produce an unhandled
|
|
84
|
+
// rejection when the run fails.
|
|
85
|
+
get returnValue() {
|
|
86
|
+
if (!lazyReturnValue) {
|
|
87
|
+
lazyReturnValue = (async () => {
|
|
88
|
+
try {
|
|
89
|
+
await terminal(cache);
|
|
90
|
+
return await (await engine.getRun(runId)).returnValue;
|
|
91
|
+
}
|
|
92
|
+
catch (e) {
|
|
93
|
+
pendingRejection = e;
|
|
94
|
+
throw e;
|
|
95
|
+
}
|
|
96
|
+
})();
|
|
97
|
+
if (pendingRejection)
|
|
98
|
+
throw pendingRejection; // unreachable
|
|
99
|
+
}
|
|
100
|
+
return lazyReturnValue;
|
|
101
|
+
},
|
|
83
102
|
getReadable(opts) {
|
|
84
103
|
const startIndex = opts?.startIndex ?? 0;
|
|
85
104
|
let sent = startIndex;
|
package/dist/index.js
CHANGED
|
@@ -26,8 +26,13 @@ export class FatalError extends Error {
|
|
|
26
26
|
this.name = "FatalError";
|
|
27
27
|
}
|
|
28
28
|
}
|
|
29
|
-
const
|
|
30
|
-
|
|
29
|
+
const reg = globalThis;
|
|
30
|
+
if (!reg.__lightflowWorkflows)
|
|
31
|
+
reg.__lightflowWorkflows = new Map();
|
|
32
|
+
if (!reg.__lightflowSteps)
|
|
33
|
+
reg.__lightflowSteps = new Map();
|
|
34
|
+
const workflows = reg.__lightflowWorkflows;
|
|
35
|
+
const steps = reg.__lightflowSteps;
|
|
31
36
|
export function registerWorkflow(id, fn) {
|
|
32
37
|
workflows.set(id, fn);
|
|
33
38
|
}
|
|
@@ -229,6 +234,10 @@ export class Engine {
|
|
|
229
234
|
let resolve;
|
|
230
235
|
let reject;
|
|
231
236
|
const promise = new Promise((res, rej) => { resolve = res; reject = rej; });
|
|
237
|
+
// Prevent unhandled-rejection crashes when a caller (e.g. compat
|
|
238
|
+
// returnValue path) never awaits the local deferred — status is read
|
|
239
|
+
// from the store instead.
|
|
240
|
+
promise.catch(() => { });
|
|
232
241
|
const d = { promise, resolve, reject };
|
|
233
242
|
this.local.set(runId, d);
|
|
234
243
|
return d;
|