@gnldev/scheduler 0.1.0
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/LICENSE +201 -0
- package/README.md +110 -0
- package/dist/cron.d.ts +7 -0
- package/dist/cron.js +66 -0
- package/dist/cron.js.map +1 -0
- package/dist/index.d.ts +147 -0
- package/dist/index.js +441 -0
- package/dist/index.js.map +1 -0
- package/dist/workflow-waker.d.ts +60 -0
- package/dist/workflow-waker.js +101 -0
- package/dist/workflow-waker.js.map +1 -0
- package/package.json +60 -0
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
// @gnldev/scheduler/workflow-waker — P2-waker closes the "suspend and hope
|
|
2
|
+
// someone polls" gap in @gnldev/workflow. `sleep(id, untilMs)`/`waitFor` suspend a run and rely on
|
|
3
|
+
// someone re-calling `runResumable` to wake it up — this builds a real waker on top of the P0.4
|
|
4
|
+
// suspended-run registry (`listWorkflowRuns`) the same way `pollScheduler` drives cron/interval/at
|
|
5
|
+
// triggers: a self-rescheduling poll loop (`createPollLoop`, the SAME shared core `createScheduler`
|
|
6
|
+
// uses — see index.ts) that scans `wfrun:` for suspended runs and calls the host-supplied `resume`
|
|
7
|
+
// for the ones that are actually due.
|
|
8
|
+
//
|
|
9
|
+
// The waker does NOT know how to rebuild a workflow instance — it has no `workflows: {}` registry.
|
|
10
|
+
// `resume(runId, status)` is host-supplied and closes over whatever registry (e.g. `createGnl` /
|
|
11
|
+
// a plain `Workflow` map) actually knows how to call `runResumable` again for that run.
|
|
12
|
+
import { claim, createPollLoop } from '@gnldev/durable';
|
|
13
|
+
import { listWorkflowRuns } from '@gnldev/workflow';
|
|
14
|
+
/**
|
|
15
|
+
* Per-run wake ticket key. Includes `updatedAt` so a run that later re-suspends (sleep/waitFor
|
|
16
|
+
* rewrite the `wfrun:` record with a fresh `updatedAt` on every suspend) gets a FRESH ticket —
|
|
17
|
+
* the same run at a NEW suspend point is a different wake opportunity, not a repeat of the old one.
|
|
18
|
+
* Bound (honest, low-stakes): `updatedAt` is ms-epoch — two re-suspends of the SAME run within the
|
|
19
|
+
* SAME millisecond share a ticket (one is silently skipped that tick, picked up the next). This only
|
|
20
|
+
* matters for a run that suspends multiple times faster than 1ms apart, which a real `resume()`
|
|
21
|
+
* round-trip never does; it's a cost/politeness bound anyway (see the correctness note below).
|
|
22
|
+
*/
|
|
23
|
+
const WAKE_TICKET = (runId, updatedAt) => `wfwake:${runId}:${updatedAt}`;
|
|
24
|
+
/** Best-effort narrowing of `WorkflowRunStatus.reason` — see `sleep`/`waitFor`/`waitForResume` in @gnldev/workflow. */
|
|
25
|
+
function reasonKind(reason) {
|
|
26
|
+
return reason && typeof reason === 'object' ? reason : {};
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Creates a durable sleep/event waker for @gnldev/workflow suspended runs. `start()`/`stop()` mirror
|
|
30
|
+
* `createScheduler`'s lifecycle (a self-rescheduling `setTimeout` chain via `createPollLoop` — no
|
|
31
|
+
* dangling timer after `stop()`).
|
|
32
|
+
*/
|
|
33
|
+
export function createWorkflowWaker(opts) {
|
|
34
|
+
const { journal, resume } = opts;
|
|
35
|
+
const intervalMs = opts.intervalMs ?? 5000;
|
|
36
|
+
const jitterMs = opts.jitterMs ?? 0;
|
|
37
|
+
const wakeEvented = opts.wakeEvented ?? false;
|
|
38
|
+
const onError = opts.onError;
|
|
39
|
+
// Warn-once-per-failure-streak bookkeeping (default onError path only) — a run that starts
|
|
40
|
+
// succeeding (or stops showing up as suspended) drops out of the map, so a LATER failure streak
|
|
41
|
+
// warns again.
|
|
42
|
+
const failStreak = new Map();
|
|
43
|
+
async function tick(now = Date.now()) {
|
|
44
|
+
const runs = await listWorkflowRuns(journal, { status: 'suspended' });
|
|
45
|
+
const out = { resumed: 0, skipped: 0, errored: 0 };
|
|
46
|
+
for (const run of runs) {
|
|
47
|
+
const reason = reasonKind(run.reason);
|
|
48
|
+
const due = reason.kind === 'time'
|
|
49
|
+
? reason.untilMs !== undefined && now >= reason.untilMs
|
|
50
|
+
: reason.kind === 'event' || reason.kind === 'resume'
|
|
51
|
+
? wakeEvented
|
|
52
|
+
: false; // unknown reason shape — conservatively don't wake it
|
|
53
|
+
if (!due) {
|
|
54
|
+
out.skipped++;
|
|
55
|
+
continue;
|
|
56
|
+
}
|
|
57
|
+
// Cost/politeness optimization ONLY — correctness does NOT depend on this CAS. `runResumable`
|
|
58
|
+
// is idempotent by construction (completed steps replay from the journal), so even if two
|
|
59
|
+
// waker instances both win this race (e.g. the journal lacks `putIfAbsent` and falls back to
|
|
60
|
+
// the documented get+put race window), both `resume()` calls are harmless — at most one of
|
|
61
|
+
// them actually advances the run.
|
|
62
|
+
const ticketKey = WAKE_TICKET(run.runId, run.updatedAt);
|
|
63
|
+
const gotTicket = await claim(journal, ticketKey, { at: now });
|
|
64
|
+
if (!gotTicket) {
|
|
65
|
+
out.skipped++;
|
|
66
|
+
continue;
|
|
67
|
+
}
|
|
68
|
+
out.resumed++;
|
|
69
|
+
try {
|
|
70
|
+
await resume(run.runId, run);
|
|
71
|
+
failStreak.delete(run.runId);
|
|
72
|
+
}
|
|
73
|
+
catch (err) {
|
|
74
|
+
out.errored++;
|
|
75
|
+
if (onError) {
|
|
76
|
+
onError(run.runId, err);
|
|
77
|
+
}
|
|
78
|
+
else {
|
|
79
|
+
const streak = (failStreak.get(run.runId) ?? 0) + 1;
|
|
80
|
+
failStreak.set(run.runId, streak);
|
|
81
|
+
if (streak === 1) {
|
|
82
|
+
console.warn(`@gnldev/scheduler: workflow-waker resume('${run.runId}') failed (chain continues):`, err);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
return out;
|
|
88
|
+
}
|
|
89
|
+
const loop = createPollLoop(async () => {
|
|
90
|
+
if (jitterMs > 0)
|
|
91
|
+
await new Promise((r) => setTimeout(r, Math.floor(Math.random() * jitterMs)));
|
|
92
|
+
const r = await tick();
|
|
93
|
+
return r.resumed > 0;
|
|
94
|
+
}, { pollMs: intervalMs, backoff: false });
|
|
95
|
+
return {
|
|
96
|
+
tick,
|
|
97
|
+
start: loop.start,
|
|
98
|
+
stop: loop.stop,
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
//# sourceMappingURL=workflow-waker.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"workflow-waker.js","sourceRoot":"","sources":["../src/workflow-waker.ts"],"names":[],"mappings":"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","sourcesContent":["// @gnldev/scheduler/workflow-waker — P2-waker closes the \"suspend and hope\n// someone polls\" gap in @gnldev/workflow. `sleep(id, untilMs)`/`waitFor` suspend a run and rely on\n// someone re-calling `runResumable` to wake it up — this builds a real waker on top of the P0.4\n// suspended-run registry (`listWorkflowRuns`) the same way `pollScheduler` drives cron/interval/at\n// triggers: a self-rescheduling poll loop (`createPollLoop`, the SAME shared core `createScheduler`\n// uses — see index.ts) that scans `wfrun:` for suspended runs and calls the host-supplied `resume`\n// for the ones that are actually due.\n//\n// The waker does NOT know how to rebuild a workflow instance — it has no `workflows: {}` registry.\n// `resume(runId, status)` is host-supplied and closes over whatever registry (e.g. `createGnl` /\n// a plain `Workflow` map) actually knows how to call `runResumable` again for that run.\nimport { claim, createPollLoop } from '@gnldev/durable';\nimport type { PollLoop } from '@gnldev/durable';\nimport { listWorkflowRuns } from '@gnldev/workflow';\nimport type { JournalLike, WorkflowRunStatus } from '@gnldev/workflow';\n\n/**\n * Per-run wake ticket key. Includes `updatedAt` so a run that later re-suspends (sleep/waitFor\n * rewrite the `wfrun:` record with a fresh `updatedAt` on every suspend) gets a FRESH ticket —\n * the same run at a NEW suspend point is a different wake opportunity, not a repeat of the old one.\n * Bound (honest, low-stakes): `updatedAt` is ms-epoch — two re-suspends of the SAME run within the\n * SAME millisecond share a ticket (one is silently skipped that tick, picked up the next). This only\n * matters for a run that suspends multiple times faster than 1ms apart, which a real `resume()`\n * round-trip never does; it's a cost/politeness bound anyway (see the correctness note below).\n */\nconst WAKE_TICKET = (runId: string, updatedAt: number) => `wfwake:${runId}:${updatedAt}`;\n\n/** Best-effort narrowing of `WorkflowRunStatus.reason` — see `sleep`/`waitFor`/`waitForResume` in @gnldev/workflow. */\nfunction reasonKind(reason: unknown): { kind?: string; untilMs?: number } {\n return reason && typeof reason === 'object' ? (reason as { kind?: string; untilMs?: number }) : {};\n}\n\nexport interface WorkflowWakerOptions {\n /** Structurally compatible with @gnldev/workflow's JournalLike (a superset — @gnldev/durable's Journal — also works). */\n journal: JournalLike;\n /**\n * Host-supplied resume: the waker does not know how to rebuild a workflow instance, so it hands\n * the runId + registry record back to the host, which typically calls the matching workflow's\n * `runResumable(input, { runId, journal }, opts)` again (completed steps replay from the journal;\n * the suspended step re-evaluates and either continues or suspends again).\n */\n resume: (runId: string, status: WorkflowRunStatus) => Promise<unknown>;\n /** Poll interval (ms). Default 5000. */\n intervalMs?: number;\n /**\n * Random delay (0..jitterMs) added BEFORE each tick's scan — spreads multiple waker instances'\n * polls apart so they don't all hit the journal in lockstep. Does NOT push a time-based sleep\n * PAST its `untilMs`: the due-check reads `Date.now()` AFTER the jitter delay, so a run only\n * ever wakes at-or-after its scheduled time (may be noticed up to `jitterMs` later, never earlier\n * than what a poll interval would already allow). Default 0 (off).\n */\n jitterMs?: number;\n /**\n * Evented (`waitFor`) and HITL (`waitForResume`) suspends have NO time signal — the waker cannot\n * tell whether the event/payload has arrived. Default (false): SKIP them entirely (only time-based\n * sleeps are woken). Opt-in `true`: resume them too, on every tick they remain suspended. Honest\n * cost: since a still-not-ready evented run gets a FRESH `wfrun:` `updatedAt` (and thus a fresh\n * wake ticket) every time `resume()` re-suspends it, this means one `resume()` round-trip (and\n * whatever `check()`/host work it triggers) PER TICK PER still-suspended evented run, for as long\n * as it stays unresolved — a busy-poll, not a push. Fine for a handful of runs; expensive at scale\n * (prefer delivering the event directly, e.g. `runResumable({ resume })`, when you can).\n */\n wakeEvented?: boolean;\n /**\n * Per-run error hook. Default (omitted): swallowed — `console.warn` ONCE per runId per\n * failure-streak (a success, or the run moving on, resets the streak) so a stuck run doesn't spam\n * logs every tick. The tick loop never dies from a `resume()` throw either way (matches\n * `createPollLoop`'s \"the chain doesn't die\" contract).\n */\n onError?: (runId: string, error: unknown) => void;\n}\n\nexport interface WorkflowWakerTickResult {\n /** Runs whose `resume()` was called AND won the wake ticket (or the CAS-less fallback let through). */\n resumed: number;\n /** Runs not due yet, evented/HITL runs skipped (wakeEvented off), or runs that lost the wake-ticket race. */\n skipped: number;\n /** `resume()` calls that threw (swallowed — see `onError`). Included in `resumed` (the call WAS made). */\n errored: number;\n}\n\nexport interface WorkflowWaker {\n /** One scan+wake pass. Exposed directly for tests/manual driving (mirrors pollScheduler/poll). */\n tick(now?: number): Promise<WorkflowWakerTickResult>;\n start(): void;\n stop(): void;\n}\n\n/**\n * Creates a durable sleep/event waker for @gnldev/workflow suspended runs. `start()`/`stop()` mirror\n * `createScheduler`'s lifecycle (a self-rescheduling `setTimeout` chain via `createPollLoop` — no\n * dangling timer after `stop()`).\n */\nexport function createWorkflowWaker(opts: WorkflowWakerOptions): WorkflowWaker {\n const { journal, resume } = opts;\n const intervalMs = opts.intervalMs ?? 5000;\n const jitterMs = opts.jitterMs ?? 0;\n const wakeEvented = opts.wakeEvented ?? false;\n const onError = opts.onError;\n // Warn-once-per-failure-streak bookkeeping (default onError path only) — a run that starts\n // succeeding (or stops showing up as suspended) drops out of the map, so a LATER failure streak\n // warns again.\n const failStreak = new Map<string, number>();\n\n async function tick(now: number = Date.now()): Promise<WorkflowWakerTickResult> {\n const runs = await listWorkflowRuns(journal, { status: 'suspended' });\n const out: WorkflowWakerTickResult = { resumed: 0, skipped: 0, errored: 0 };\n\n for (const run of runs) {\n const reason = reasonKind(run.reason);\n const due =\n reason.kind === 'time'\n ? reason.untilMs !== undefined && now >= reason.untilMs\n : reason.kind === 'event' || reason.kind === 'resume'\n ? wakeEvented\n : false; // unknown reason shape — conservatively don't wake it\n if (!due) {\n out.skipped++;\n continue;\n }\n\n // Cost/politeness optimization ONLY — correctness does NOT depend on this CAS. `runResumable`\n // is idempotent by construction (completed steps replay from the journal), so even if two\n // waker instances both win this race (e.g. the journal lacks `putIfAbsent` and falls back to\n // the documented get+put race window), both `resume()` calls are harmless — at most one of\n // them actually advances the run.\n const ticketKey = WAKE_TICKET(run.runId, run.updatedAt);\n const gotTicket = await claim(journal, ticketKey, { at: now });\n if (!gotTicket) {\n out.skipped++;\n continue;\n }\n\n out.resumed++;\n try {\n await resume(run.runId, run);\n failStreak.delete(run.runId);\n } catch (err) {\n out.errored++;\n if (onError) {\n onError(run.runId, err);\n } else {\n const streak = (failStreak.get(run.runId) ?? 0) + 1;\n failStreak.set(run.runId, streak);\n if (streak === 1) {\n console.warn(`@gnldev/scheduler: workflow-waker resume('${run.runId}') failed (chain continues):`, err);\n }\n }\n }\n }\n return out;\n }\n\n const loop: PollLoop = createPollLoop(\n async () => {\n if (jitterMs > 0) await new Promise((r) => setTimeout(r, Math.floor(Math.random() * jitterMs)));\n const r = await tick();\n return r.resumed > 0;\n },\n { pollMs: intervalMs, backoff: false }, // sleep waking is timing-critical, same rationale as createScheduler's default\n );\n\n return {\n tick,\n start: loop.start,\n stop: loop.stop,\n };\n}\n"]}
|
package/package.json
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@gnldev/scheduler",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"license": "Apache-2.0",
|
|
5
|
+
"engines": {
|
|
6
|
+
"node": ">=22.13.0"
|
|
7
|
+
},
|
|
8
|
+
"description": "Durable workflow scheduler on @gnldev/durable: cron/interval/at triggers, exactly-once fire (run-lock), backoff/retry. Time is data: the next fire time is resolved and frozen into the journal.",
|
|
9
|
+
"keywords": [
|
|
10
|
+
"ai",
|
|
11
|
+
"agent",
|
|
12
|
+
"llm",
|
|
13
|
+
"typescript",
|
|
14
|
+
"ai-sdk",
|
|
15
|
+
"durable",
|
|
16
|
+
"exactly-once",
|
|
17
|
+
"cron",
|
|
18
|
+
"triggers"
|
|
19
|
+
],
|
|
20
|
+
"type": "module",
|
|
21
|
+
"main": "./dist/index.js",
|
|
22
|
+
"types": "./dist/index.d.ts",
|
|
23
|
+
"exports": {
|
|
24
|
+
".": {
|
|
25
|
+
"types": "./dist/index.d.ts",
|
|
26
|
+
"default": "./dist/index.js"
|
|
27
|
+
},
|
|
28
|
+
"./package.json": "./package.json"
|
|
29
|
+
},
|
|
30
|
+
"files": [
|
|
31
|
+
"dist"
|
|
32
|
+
],
|
|
33
|
+
"dependencies": {
|
|
34
|
+
"@gnldev/workflow": "^0.1.0"
|
|
35
|
+
},
|
|
36
|
+
"peerDependencies": {
|
|
37
|
+
"@gnldev/durable": "^0.1.0"
|
|
38
|
+
},
|
|
39
|
+
"devDependencies": {
|
|
40
|
+
"@gnldev/durable": "0.1.0"
|
|
41
|
+
},
|
|
42
|
+
"author": "Karaca Yılmaz (https://gnl.dev)",
|
|
43
|
+
"homepage": "https://gnl.dev",
|
|
44
|
+
"bugs": {
|
|
45
|
+
"url": "https://github.com/Karaca7/gnldev/issues"
|
|
46
|
+
},
|
|
47
|
+
"repository": {
|
|
48
|
+
"type": "git",
|
|
49
|
+
"url": "git+https://github.com/Karaca7/gnldev.git",
|
|
50
|
+
"directory": "packages/scheduler"
|
|
51
|
+
},
|
|
52
|
+
"publishConfig": {
|
|
53
|
+
"access": "public"
|
|
54
|
+
},
|
|
55
|
+
"scripts": {
|
|
56
|
+
"build": "tsc -p tsconfig.json",
|
|
57
|
+
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
58
|
+
"test": "vitest run"
|
|
59
|
+
}
|
|
60
|
+
}
|