lightflow-engine 0.2.8 → 0.2.10

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.
@@ -1,9 +1,10 @@
1
1
  /**
2
2
  * Vercel Workflow compat: getWorkflowMetadata() returns { workflowRunId }.
3
3
  */
4
- import { current } from "../index.js";
4
+ import { getCurrent } from "../index.js";
5
5
  export function getWorkflowMetadata() {
6
- if (!current)
6
+ const ctx = getCurrent();
7
+ if (!ctx)
7
8
  throw new Error("getWorkflowMetadata() outside a workflow");
8
- return { workflowRunId: current.runId };
9
+ return { workflowRunId: ctx.runId };
9
10
  }
@@ -9,7 +9,7 @@
9
9
  */
10
10
  import { createHash } from "node:crypto";
11
11
  import { step, registerStep, } from "../index.js";
12
- import { current } from "../index.js";
12
+ import { getCurrent } from "../index.js";
13
13
  export { FatalError, CancelledError, sleep } from "../index.js";
14
14
  export { getWorkflowMetadata } from "../compat/metadata.js";
15
15
  // Vercel exports the step-aware fetch under both names; entry-agents
@@ -23,7 +23,7 @@ export { workflowFetch } from "../compat/fetch.js";
23
23
  * terminal done marker.
24
24
  */
25
25
  export function getWritable() {
26
- const ctx = current;
26
+ const ctx = getCurrent();
27
27
  if (!ctx)
28
28
  throw new Error("getWritable() outside a workflow");
29
29
  const underlying = {
package/dist/index.d.ts CHANGED
@@ -102,7 +102,7 @@ type Ctx = {
102
102
  chunks: string[];
103
103
  now: () => number;
104
104
  };
105
- export declare let current: Ctx | null;
105
+ export declare function getCurrent(): Ctx | null;
106
106
  export declare function getWorkflowMetadata(): {
107
107
  runId: string;
108
108
  };
package/dist/index.js CHANGED
@@ -39,19 +39,28 @@ export function registerWorkflow(id, fn) {
39
39
  export function registerStep(id, fn) {
40
40
  steps.set(id, fn);
41
41
  }
42
- export let current = null;
42
+ // Cross-bundle execution context: Next.js may execute the engine and workflow
43
+ // code from different module instances of this file. `current` must be visible
44
+ // across all of them, so it lives on globalThis and is read via getCurrent().
45
+ export function getCurrent() {
46
+ return globalThis.__lightflowCurrent ?? null;
47
+ }
48
+ function setCurrent(v) {
49
+ globalThis.__lightflowCurrent = v;
50
+ }
51
+ // Rewrite plain assignments on `current` below to setCurrent(...)
43
52
  export function getWorkflowMetadata() {
44
- if (!current)
53
+ if (!getCurrent())
45
54
  throw new Error("getWorkflowMetadata() outside a workflow");
46
- return { runId: current.runId };
55
+ return { runId: getCurrent().runId };
47
56
  }
48
57
  /* ------------------------------------------------------------------ */
49
58
  /* The step id: derived from call order, so replay is deterministic */
50
59
  /* ------------------------------------------------------------------ */
51
60
  function nextStepKey() {
52
- if (!current)
61
+ if (!getCurrent())
53
62
  throw new Error("steps can only be called inside a workflow");
54
- return `step:${current.stepCalls}`;
63
+ return `step:${getCurrent().stepCalls}`;
55
64
  }
56
65
  /* ------------------------------------------------------------------ */
57
66
  /* Public API inside a workflow */
@@ -61,9 +70,9 @@ function nextStepKey() {
61
70
  * the function body is NOT re-executed.
62
71
  */
63
72
  export async function step(fn) {
64
- if (!current)
73
+ if (!getCurrent())
65
74
  return fn(); // plain call outside a workflow
66
- const ctx = current;
75
+ const ctx = getCurrent();
67
76
  const key = nextStepKey();
68
77
  ctx.stepCalls += 1;
69
78
  // Replay: memoized result for this call position? (O(1) map lookup)
@@ -154,11 +163,11 @@ async function maybeSnapshot(ctx) {
154
163
  }
155
164
  /** Durable sleep. Accepts milliseconds or an absolute Date. */
156
165
  export async function sleep(until) {
157
- if (!current) {
166
+ if (!getCurrent()) {
158
167
  const ms = until instanceof Date ? until.getTime() - Date.now() : until;
159
168
  return new Promise((r) => setTimeout(r, Math.max(0, ms)));
160
169
  }
161
- const ctx = current;
170
+ const ctx = getCurrent();
162
171
  const wakeAt = until instanceof Date ? until.getTime() : ctx.now() + until;
163
172
  const key = `sleep:${ctx.sleepCalls}`;
164
173
  ctx.sleepCalls += 1;
@@ -183,8 +192,8 @@ export class SuspendSignal {
183
192
  }
184
193
  /** Ordered output stream for a run. Chunks are persisted and replayable. */
185
194
  export function getWritable() {
186
- const ctx = current;
187
- if (!current)
195
+ const ctx = getCurrent();
196
+ if (!getCurrent())
188
197
  throw new Error("getWritable() outside a workflow");
189
198
  return {
190
199
  async write(chunk) {
@@ -368,8 +377,8 @@ export class Engine {
368
377
  return p;
369
378
  },
370
379
  };
371
- const prev = current;
372
- current = ctx;
380
+ const prev = getCurrent();
381
+ setCurrent(ctx);
373
382
  try {
374
383
  const output = await fn(...args);
375
384
  await Promise.all(ctx.inflight);
@@ -397,7 +406,7 @@ export class Engine {
397
406
  return fail(err instanceof Error ? err.message : String(err));
398
407
  }
399
408
  finally {
400
- current = prev;
409
+ setCurrent(prev);
401
410
  await claimer.releaseRun?.(runId);
402
411
  }
403
412
  }
@@ -524,21 +533,21 @@ export class CancelledError extends Error {
524
533
  export function defineHook() {
525
534
  return {
526
535
  async create() {
527
- if (!current)
536
+ if (!getCurrent())
528
537
  throw new Error("hooks only inside a workflow");
529
538
  const token = `hook_${randomUUID().replace(/-/g, "").slice(0, 20)}`;
530
- const key = `hook:${current.hookCalls}`;
531
- current.hookCalls += 1;
532
- await current.store.createHook?.(current.runId, token, key);
539
+ const key = `hook:${getCurrent().hookCalls}`;
540
+ getCurrent().hookCalls += 1;
541
+ await getCurrent().store.createHook?.(getCurrent().runId, token, key);
533
542
  return { token };
534
543
  },
535
544
  };
536
545
  }
537
546
  /** Await a previously created hook until an external caller resolves it. */
538
547
  export async function hookResult(token) {
539
- if (!current)
548
+ if (!getCurrent())
540
549
  throw new Error("hooks only inside a workflow");
541
- const existing = await current.store.getHook?.(token);
550
+ const existing = await getCurrent().store.getHook?.(token);
542
551
  if (existing && existing.payload?.resolved) {
543
552
  return existing.payload.value;
544
553
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lightflow-engine",
3
- "version": "0.2.8",
3
+ "version": "0.2.10",
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",