@voyantjs/workflows-orchestrator-cloudflare 0.66.6 → 0.69.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/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/schedule-handler.d.ts +43 -0
- package/dist/schedule-handler.d.ts.map +1 -0
- package/dist/schedule-handler.js +90 -0
- package/dist/worker.d.ts +11 -2
- package/dist/worker.d.ts.map +1 -1
- package/dist/worker.js +15 -0
- package/package.json +3 -3
- package/src/index.ts +6 -0
- package/src/schedule-handler.ts +144 -0
- package/src/worker.ts +27 -2
package/dist/index.d.ts
CHANGED
|
@@ -6,6 +6,7 @@ export { createDurableObjectRunStore } from "./do-store.js";
|
|
|
6
6
|
export { type DurableObjectDeps, handleDurableObjectAlarm, handleDurableObjectRequest, } from "./durable-object.js";
|
|
7
7
|
export { type CfManifestEnvelope, type CfManifestStore, type CreateKvManifestStoreOptions, createInMemoryKv, createKvManifestStore, type KvNamespaceLike, } from "./manifest-kv-store.js";
|
|
8
8
|
export { createR2Presigner, type PresignArgs, type R2PresignerOptions, } from "./r2-sign.js";
|
|
9
|
+
export { handleGetSchedules, type ScheduleHandlerDeps, type ScheduleListResponse, type ScheduleSummary, } from "./schedule-handler.js";
|
|
9
10
|
export * from "./types.js";
|
|
10
11
|
export { type DurableObjectNamespaceLike, handleWorkerRequest, type WorkerFetchDeps, } from "./worker.js";
|
|
11
12
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAkCA,OAAO,EACL,KAAK,cAAc,EACnB,KAAK,qBAAqB,EAC1B,KAAK,sBAAsB,EAC3B,2BAA2B,GAC5B,MAAM,0BAA0B,CAAA;AACjC,OAAO,EACL,KAAK,2BAA2B,EAChC,0BAA0B,GAC3B,MAAM,6BAA6B,CAAA;AACpC,OAAO,EACL,KAAK,sBAAsB,EAC3B,KAAK,0BAA0B,EAC/B,4BAA4B,EAC5B,mCAAmC,GACpC,MAAM,8BAA8B,CAAA;AACrC,OAAO,EACL,oBAAoB,EACpB,sBAAsB,EACtB,8BAA8B,EAC9B,KAAK,qBAAqB,EAC1B,KAAK,+BAA+B,EACpC,KAAK,kBAAkB,EACvB,KAAK,cAAc,EACnB,KAAK,qBAAqB,GAC3B,MAAM,kBAAkB,CAAA;AACzB,OAAO,EAAE,2BAA2B,EAAE,MAAM,eAAe,CAAA;AAC3D,OAAO,EACL,KAAK,iBAAiB,EACtB,wBAAwB,EACxB,0BAA0B,GAC3B,MAAM,qBAAqB,CAAA;AAC5B,OAAO,EACL,KAAK,kBAAkB,EACvB,KAAK,eAAe,EACpB,KAAK,4BAA4B,EACjC,gBAAgB,EAChB,qBAAqB,EACrB,KAAK,eAAe,GACrB,MAAM,wBAAwB,CAAA;AAC/B,OAAO,EACL,iBAAiB,EACjB,KAAK,WAAW,EAChB,KAAK,kBAAkB,GACxB,MAAM,cAAc,CAAA;AACrB,cAAc,YAAY,CAAA;AAC1B,OAAO,EACL,KAAK,0BAA0B,EAC/B,mBAAmB,EACnB,KAAK,eAAe,GACrB,MAAM,aAAa,CAAA"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAkCA,OAAO,EACL,KAAK,cAAc,EACnB,KAAK,qBAAqB,EAC1B,KAAK,sBAAsB,EAC3B,2BAA2B,GAC5B,MAAM,0BAA0B,CAAA;AACjC,OAAO,EACL,KAAK,2BAA2B,EAChC,0BAA0B,GAC3B,MAAM,6BAA6B,CAAA;AACpC,OAAO,EACL,KAAK,sBAAsB,EAC3B,KAAK,0BAA0B,EAC/B,4BAA4B,EAC5B,mCAAmC,GACpC,MAAM,8BAA8B,CAAA;AACrC,OAAO,EACL,oBAAoB,EACpB,sBAAsB,EACtB,8BAA8B,EAC9B,KAAK,qBAAqB,EAC1B,KAAK,+BAA+B,EACpC,KAAK,kBAAkB,EACvB,KAAK,cAAc,EACnB,KAAK,qBAAqB,GAC3B,MAAM,kBAAkB,CAAA;AACzB,OAAO,EAAE,2BAA2B,EAAE,MAAM,eAAe,CAAA;AAC3D,OAAO,EACL,KAAK,iBAAiB,EACtB,wBAAwB,EACxB,0BAA0B,GAC3B,MAAM,qBAAqB,CAAA;AAC5B,OAAO,EACL,KAAK,kBAAkB,EACvB,KAAK,eAAe,EACpB,KAAK,4BAA4B,EACjC,gBAAgB,EAChB,qBAAqB,EACrB,KAAK,eAAe,GACrB,MAAM,wBAAwB,CAAA;AAC/B,OAAO,EACL,iBAAiB,EACjB,KAAK,WAAW,EAChB,KAAK,kBAAkB,GACxB,MAAM,cAAc,CAAA;AACrB,OAAO,EACL,kBAAkB,EAClB,KAAK,mBAAmB,EACxB,KAAK,oBAAoB,EACzB,KAAK,eAAe,GACrB,MAAM,uBAAuB,CAAA;AAC9B,cAAc,YAAY,CAAA;AAC1B,OAAO,EACL,KAAK,0BAA0B,EAC/B,mBAAmB,EACnB,KAAK,eAAe,GACrB,MAAM,aAAa,CAAA"}
|
package/dist/index.js
CHANGED
|
@@ -39,5 +39,6 @@ export { createDurableObjectRunStore } from "./do-store.js";
|
|
|
39
39
|
export { handleDurableObjectAlarm, handleDurableObjectRequest, } from "./durable-object.js";
|
|
40
40
|
export { createInMemoryKv, createKvManifestStore, } from "./manifest-kv-store.js";
|
|
41
41
|
export { createR2Presigner, } from "./r2-sign.js";
|
|
42
|
+
export { handleGetSchedules, } from "./schedule-handler.js";
|
|
42
43
|
export * from "./types.js";
|
|
43
44
|
export { handleWorkerRequest, } from "./worker.js";
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import type { ManifestSchedule } from "@voyantjs/workflows/protocol";
|
|
2
|
+
import type { CfManifestStore } from "./manifest-kv-store.js";
|
|
3
|
+
export interface ScheduleHandlerDeps {
|
|
4
|
+
manifestStore: CfManifestStore;
|
|
5
|
+
/**
|
|
6
|
+
* Process-wide schedules toggle. The Voyant Cloud orchestrator gates
|
|
7
|
+
* scheduler ticks behind `VOYANT_WORKFLOWS_ENABLE_SCHEDULES`; the UI
|
|
8
|
+
* surfaces that flag so operators can tell at a glance whether any
|
|
9
|
+
* schedule will fire even if `enabled: true` on the registration.
|
|
10
|
+
*
|
|
11
|
+
* When omitted, the response omits `schedulesEnabledByEnv` so older
|
|
12
|
+
* UIs can ignore the field.
|
|
13
|
+
*/
|
|
14
|
+
schedulesEnabledByEnv?: boolean;
|
|
15
|
+
now?: () => number;
|
|
16
|
+
logger?: (level: "info" | "warn" | "error", msg: string, data?: object) => void;
|
|
17
|
+
}
|
|
18
|
+
export interface ScheduleSummary {
|
|
19
|
+
workflowId: string;
|
|
20
|
+
scheduleId: string;
|
|
21
|
+
schedule: ManifestSchedule;
|
|
22
|
+
/** Epoch millis of the next computed fire, or null when undecidable. */
|
|
23
|
+
nextRunAt: number | null;
|
|
24
|
+
/**
|
|
25
|
+
* False when the registration explicitly sets `enabled: false`, or
|
|
26
|
+
* when the registration's `environments` list excludes the requested
|
|
27
|
+
* environment.
|
|
28
|
+
*/
|
|
29
|
+
enabled: boolean;
|
|
30
|
+
disabledReason?: "registration_disabled" | "env_filtered";
|
|
31
|
+
}
|
|
32
|
+
export interface ScheduleListResponse {
|
|
33
|
+
environment: string;
|
|
34
|
+
versionId: string;
|
|
35
|
+
schedulesEnabledByEnv?: boolean;
|
|
36
|
+
data: ScheduleSummary[];
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Handle `GET /api/schedules/:env`. Returns the projected schedule list
|
|
40
|
+
* for the current manifest, or 404 when no manifest is registered.
|
|
41
|
+
*/
|
|
42
|
+
export declare function handleGetSchedules(environment: string, deps: ScheduleHandlerDeps): Promise<Response>;
|
|
43
|
+
//# sourceMappingURL=schedule-handler.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"schedule-handler.d.ts","sourceRoot":"","sources":["../src/schedule-handler.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,EAAE,gBAAgB,EAAoB,MAAM,8BAA8B,CAAA;AAGtF,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAA;AAI7D,MAAM,WAAW,mBAAmB;IAClC,aAAa,EAAE,eAAe,CAAA;IAC9B;;;;;;;;OAQG;IACH,qBAAqB,CAAC,EAAE,OAAO,CAAA;IAC/B,GAAG,CAAC,EAAE,MAAM,MAAM,CAAA;IAClB,MAAM,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,EAAE,GAAG,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,KAAK,IAAI,CAAA;CAChF;AAED,MAAM,WAAW,eAAe;IAC9B,UAAU,EAAE,MAAM,CAAA;IAClB,UAAU,EAAE,MAAM,CAAA;IAClB,QAAQ,EAAE,gBAAgB,CAAA;IAC1B,wEAAwE;IACxE,SAAS,EAAE,MAAM,GAAG,IAAI,CAAA;IACxB;;;;OAIG;IACH,OAAO,EAAE,OAAO,CAAA;IAChB,cAAc,CAAC,EAAE,uBAAuB,GAAG,cAAc,CAAA;CAC1D;AAED,MAAM,WAAW,oBAAoB;IACnC,WAAW,EAAE,MAAM,CAAA;IACnB,SAAS,EAAE,MAAM,CAAA;IACjB,qBAAqB,CAAC,EAAE,OAAO,CAAA;IAC/B,IAAI,EAAE,eAAe,EAAE,CAAA;CACxB;AAED;;;GAGG;AACH,wBAAsB,kBAAkB,CACtC,WAAW,EAAE,MAAM,EACnB,IAAI,EAAE,mBAAmB,GACxB,OAAO,CAAC,QAAQ,CAAC,CAoEnB"}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
// HTTP handler for `/api/schedules/:env`. Reads the registered manifest
|
|
2
|
+
// for the requested environment, projects each workflow's schedule blocks
|
|
3
|
+
// into a flat list, and computes `nextRun` per entry via the same cron /
|
|
4
|
+
// every / at logic the live scheduler uses.
|
|
5
|
+
//
|
|
6
|
+
// Aggregate "lastRun" is intentionally out of scope here — runs in the
|
|
7
|
+
// Cloudflare orchestrator live in per-run Durable Objects, indexed by
|
|
8
|
+
// runId, with no list-by-workflow path. The UI is expected to fetch
|
|
9
|
+
// last-run state separately from the template-side `workflow-runs`
|
|
10
|
+
// admin API (`/v1/admin/workflow-runs?workflowName=…&limit=1`).
|
|
11
|
+
import { computeNextFire } from "@voyantjs/workflows-orchestrator";
|
|
12
|
+
const ALLOWED_ENVS = new Set(["production", "preview", "development"]);
|
|
13
|
+
/**
|
|
14
|
+
* Handle `GET /api/schedules/:env`. Returns the projected schedule list
|
|
15
|
+
* for the current manifest, or 404 when no manifest is registered.
|
|
16
|
+
*/
|
|
17
|
+
export async function handleGetSchedules(environment, deps) {
|
|
18
|
+
if (!ALLOWED_ENVS.has(environment)) {
|
|
19
|
+
return json(400, {
|
|
20
|
+
error: "invalid_environment",
|
|
21
|
+
message: `environment must be one of ${[...ALLOWED_ENVS].join(", ")}`,
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
const envelope = await deps.manifestStore.getCurrent(environment);
|
|
25
|
+
if (!envelope) {
|
|
26
|
+
return json(404, { error: "not_found", environment });
|
|
27
|
+
}
|
|
28
|
+
const now = deps.now ? deps.now() : Date.now();
|
|
29
|
+
const manifest = envelope.manifest;
|
|
30
|
+
const data = [];
|
|
31
|
+
for (const workflow of manifest.workflows ?? []) {
|
|
32
|
+
const schedules = Array.isArray(workflow.schedules) ? workflow.schedules : [];
|
|
33
|
+
schedules.forEach((schedule, index) => {
|
|
34
|
+
const scheduleId = `${envelope.versionId}:${workflow.id}:${schedule.name ?? index}`;
|
|
35
|
+
const registrationDisabled = schedule.enabled === false;
|
|
36
|
+
const envFiltered = Array.isArray(schedule.environments)
|
|
37
|
+
? !schedule.environments.includes(environment)
|
|
38
|
+
: false;
|
|
39
|
+
let nextRunAt = null;
|
|
40
|
+
if (!registrationDisabled && !envFiltered) {
|
|
41
|
+
try {
|
|
42
|
+
const fire = computeNextFire(schedule, now);
|
|
43
|
+
nextRunAt = Number.isFinite(fire) ? fire : null;
|
|
44
|
+
}
|
|
45
|
+
catch (err) {
|
|
46
|
+
deps.logger?.("warn", "schedules: cannot compute next fire", {
|
|
47
|
+
workflowId: workflow.id,
|
|
48
|
+
scheduleId,
|
|
49
|
+
error: err instanceof Error ? err.message : String(err),
|
|
50
|
+
});
|
|
51
|
+
nextRunAt = null;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
const enabled = !registrationDisabled && !envFiltered;
|
|
55
|
+
const disabledReason = registrationDisabled
|
|
56
|
+
? "registration_disabled"
|
|
57
|
+
: envFiltered
|
|
58
|
+
? "env_filtered"
|
|
59
|
+
: undefined;
|
|
60
|
+
data.push({
|
|
61
|
+
workflowId: workflow.id,
|
|
62
|
+
scheduleId,
|
|
63
|
+
schedule,
|
|
64
|
+
nextRunAt,
|
|
65
|
+
enabled,
|
|
66
|
+
...(disabledReason ? { disabledReason } : {}),
|
|
67
|
+
});
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
const response = {
|
|
71
|
+
environment,
|
|
72
|
+
versionId: envelope.versionId,
|
|
73
|
+
data,
|
|
74
|
+
...(deps.schedulesEnabledByEnv !== undefined
|
|
75
|
+
? { schedulesEnabledByEnv: deps.schedulesEnabledByEnv }
|
|
76
|
+
: {}),
|
|
77
|
+
};
|
|
78
|
+
return json(200, response);
|
|
79
|
+
}
|
|
80
|
+
function json(status, body) {
|
|
81
|
+
return new Response(JSON.stringify(body), {
|
|
82
|
+
status,
|
|
83
|
+
headers: {
|
|
84
|
+
"content-type": "application/json; charset=utf-8",
|
|
85
|
+
"access-control-allow-origin": "*",
|
|
86
|
+
"access-control-allow-methods": "GET,POST,OPTIONS",
|
|
87
|
+
"access-control-allow-headers": "content-type, x-voyant-protocol",
|
|
88
|
+
},
|
|
89
|
+
});
|
|
90
|
+
}
|
package/dist/worker.d.ts
CHANGED
|
@@ -25,10 +25,19 @@ export interface WorkerFetchDeps<Id = unknown> {
|
|
|
25
25
|
now?: () => number;
|
|
26
26
|
/**
|
|
27
27
|
* Optional KV-backed manifest store. When set, the worker also serves
|
|
28
|
-
* `/api/manifests
|
|
29
|
-
* useful for orchestrators that only expose the
|
|
28
|
+
* `/api/manifests`, `/api/schedules`, and `/api/events`. When unset,
|
|
29
|
+
* those routes 404 — useful for orchestrators that only expose the
|
|
30
|
+
* run surface.
|
|
30
31
|
*/
|
|
31
32
|
manifestStore?: CfManifestStore;
|
|
33
|
+
/**
|
|
34
|
+
* Optional process-wide schedules toggle reported by
|
|
35
|
+
* `/api/schedules/:env`. Used by the workflow-schedules UI to show
|
|
36
|
+
* "schedules disabled by env flag" when the orchestrator is running
|
|
37
|
+
* with `VOYANT_WORKFLOWS_ENABLE_SCHEDULES` (or equivalent) turned off.
|
|
38
|
+
* When omitted, the schedules response leaves the flag out.
|
|
39
|
+
*/
|
|
40
|
+
schedulesEnabledByEnv?: boolean;
|
|
32
41
|
/**
|
|
33
42
|
* Tenant metadata stamped on event-triggered runs. Defaults to
|
|
34
43
|
* `{ tenantId: "default", projectId: "default", organizationId: "default" }`.
|
package/dist/worker.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"worker.d.ts","sourceRoot":"","sources":["../src/worker.ts"],"names":[],"mappings":"AAuBA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAA;
|
|
1
|
+
{"version":3,"file":"worker.d.ts","sourceRoot":"","sources":["../src/worker.ts"],"names":[],"mappings":"AAuBA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAA;AAS7D;;;;GAIG;AACH,MAAM,WAAW,0BAA0B,CAAC,EAAE,GAAG,OAAO;IACtD,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,EAAE,CAAA;IAC5B,GAAG,CAAC,EAAE,EAAE,EAAE,GAAG;QAAE,KAAK,CAAC,GAAG,EAAE,OAAO,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAA;KAAE,CAAA;CACxD;AAED,MAAM,WAAW,eAAe,CAAC,EAAE,GAAG,OAAO;IAC3C,KAAK,EAAE,0BAA0B,CAAC,EAAE,CAAC,CAAA;IACrC;;;OAGG;IACH,aAAa,CAAC,EAAE,CAAC,GAAG,EAAE,OAAO,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IACtD,uBAAuB;IACvB,MAAM,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,EAAE,GAAG,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,KAAK,IAAI,CAAA;IAC/E,iEAAiE;IACjE,WAAW,CAAC,EAAE,MAAM,MAAM,CAAA;IAC1B,0CAA0C;IAC1C,GAAG,CAAC,EAAE,MAAM,MAAM,CAAA;IAClB;;;;;OAKG;IACH,aAAa,CAAC,EAAE,eAAe,CAAA;IAC/B;;;;;;OAMG;IACH,qBAAqB,CAAC,EAAE,OAAO,CAAA;IAC/B;;;;;OAKG;IACH,UAAU,CAAC,EAAE;QACX,QAAQ,EAAE,MAAM,CAAA;QAChB,SAAS,EAAE,MAAM,CAAA;QACjB,cAAc,EAAE,MAAM,CAAA;QACtB,YAAY,CAAC,EAAE,MAAM,CAAA;KACtB,CAAA;CACF;AAED,wBAAsB,mBAAmB,CAAC,EAAE,EAC1C,GAAG,EAAE,OAAO,EACZ,IAAI,EAAE,eAAe,CAAC,EAAE,CAAC,GACxB,OAAO,CAAC,QAAQ,CAAC,CAmNnB"}
|
package/dist/worker.js
CHANGED
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
import { buildResumeJournal, buildSeededResumeJournal, } from "@voyantjs/workflows-orchestrator";
|
|
15
15
|
import { handleIngestEvent } from "./event-handler.js";
|
|
16
16
|
import { handleGetManifest, handleRegisterManifest } from "./manifest-handler.js";
|
|
17
|
+
import { handleGetSchedules } from "./schedule-handler.js";
|
|
17
18
|
const DEFAULT_TENANT_META = {
|
|
18
19
|
tenantId: "default",
|
|
19
20
|
projectId: "default",
|
|
@@ -60,6 +61,20 @@ export async function handleWorkerRequest(req, deps) {
|
|
|
60
61
|
logger: deps.logger,
|
|
61
62
|
});
|
|
62
63
|
}
|
|
64
|
+
// GET /api/schedules/:env — aggregate scheduled-workflow view.
|
|
65
|
+
const schedulesMatch = url.pathname.match(/^\/api\/schedules\/([^/]+)$/);
|
|
66
|
+
if (schedulesMatch) {
|
|
67
|
+
if (!deps.manifestStore) {
|
|
68
|
+
return json(404, { error: "schedules_not_configured" });
|
|
69
|
+
}
|
|
70
|
+
const env = decodeURIComponent(schedulesMatch[1] ?? "");
|
|
71
|
+
return handleGetSchedules(env, {
|
|
72
|
+
manifestStore: deps.manifestStore,
|
|
73
|
+
schedulesEnabledByEnv: deps.schedulesEnabledByEnv,
|
|
74
|
+
now: deps.now,
|
|
75
|
+
logger: deps.logger,
|
|
76
|
+
});
|
|
77
|
+
}
|
|
63
78
|
}
|
|
64
79
|
// POST /api/events — synchronous event ingest. Loads the manifest,
|
|
65
80
|
// routes filters, forwards each match to the run-DO trigger flow.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@voyantjs/workflows-orchestrator-cloudflare",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.69.0",
|
|
4
4
|
"description": "Cloudflare Worker + Durable Object adapter for @voyantjs/workflows-orchestrator. Dispatches workflow-step requests to tenant Workers via a Workers-for-Platforms dispatch namespace.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"repository": {
|
|
@@ -26,8 +26,8 @@
|
|
|
26
26
|
"NOTICE"
|
|
27
27
|
],
|
|
28
28
|
"dependencies": {
|
|
29
|
-
"@voyantjs/workflows-orchestrator": "0.
|
|
30
|
-
"@voyantjs/workflows": "0.
|
|
29
|
+
"@voyantjs/workflows-orchestrator": "0.69.0",
|
|
30
|
+
"@voyantjs/workflows": "0.69.0"
|
|
31
31
|
},
|
|
32
32
|
"devDependencies": {
|
|
33
33
|
"@cloudflare/vitest-pool-workers": "^0.15.1",
|
package/src/index.ts
CHANGED
|
@@ -77,6 +77,12 @@ export {
|
|
|
77
77
|
type PresignArgs,
|
|
78
78
|
type R2PresignerOptions,
|
|
79
79
|
} from "./r2-sign.js"
|
|
80
|
+
export {
|
|
81
|
+
handleGetSchedules,
|
|
82
|
+
type ScheduleHandlerDeps,
|
|
83
|
+
type ScheduleListResponse,
|
|
84
|
+
type ScheduleSummary,
|
|
85
|
+
} from "./schedule-handler.js"
|
|
80
86
|
export * from "./types.js"
|
|
81
87
|
export {
|
|
82
88
|
type DurableObjectNamespaceLike,
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
// HTTP handler for `/api/schedules/:env`. Reads the registered manifest
|
|
2
|
+
// for the requested environment, projects each workflow's schedule blocks
|
|
3
|
+
// into a flat list, and computes `nextRun` per entry via the same cron /
|
|
4
|
+
// every / at logic the live scheduler uses.
|
|
5
|
+
//
|
|
6
|
+
// Aggregate "lastRun" is intentionally out of scope here — runs in the
|
|
7
|
+
// Cloudflare orchestrator live in per-run Durable Objects, indexed by
|
|
8
|
+
// runId, with no list-by-workflow path. The UI is expected to fetch
|
|
9
|
+
// last-run state separately from the template-side `workflow-runs`
|
|
10
|
+
// admin API (`/v1/admin/workflow-runs?workflowName=…&limit=1`).
|
|
11
|
+
|
|
12
|
+
import type { ManifestSchedule, WorkflowManifest } from "@voyantjs/workflows/protocol"
|
|
13
|
+
import { computeNextFire } from "@voyantjs/workflows-orchestrator"
|
|
14
|
+
|
|
15
|
+
import type { CfManifestStore } from "./manifest-kv-store.js"
|
|
16
|
+
|
|
17
|
+
const ALLOWED_ENVS = new Set(["production", "preview", "development"])
|
|
18
|
+
|
|
19
|
+
export interface ScheduleHandlerDeps {
|
|
20
|
+
manifestStore: CfManifestStore
|
|
21
|
+
/**
|
|
22
|
+
* Process-wide schedules toggle. The Voyant Cloud orchestrator gates
|
|
23
|
+
* scheduler ticks behind `VOYANT_WORKFLOWS_ENABLE_SCHEDULES`; the UI
|
|
24
|
+
* surfaces that flag so operators can tell at a glance whether any
|
|
25
|
+
* schedule will fire even if `enabled: true` on the registration.
|
|
26
|
+
*
|
|
27
|
+
* When omitted, the response omits `schedulesEnabledByEnv` so older
|
|
28
|
+
* UIs can ignore the field.
|
|
29
|
+
*/
|
|
30
|
+
schedulesEnabledByEnv?: boolean
|
|
31
|
+
now?: () => number
|
|
32
|
+
logger?: (level: "info" | "warn" | "error", msg: string, data?: object) => void
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export interface ScheduleSummary {
|
|
36
|
+
workflowId: string
|
|
37
|
+
scheduleId: string
|
|
38
|
+
schedule: ManifestSchedule
|
|
39
|
+
/** Epoch millis of the next computed fire, or null when undecidable. */
|
|
40
|
+
nextRunAt: number | null
|
|
41
|
+
/**
|
|
42
|
+
* False when the registration explicitly sets `enabled: false`, or
|
|
43
|
+
* when the registration's `environments` list excludes the requested
|
|
44
|
+
* environment.
|
|
45
|
+
*/
|
|
46
|
+
enabled: boolean
|
|
47
|
+
disabledReason?: "registration_disabled" | "env_filtered"
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export interface ScheduleListResponse {
|
|
51
|
+
environment: string
|
|
52
|
+
versionId: string
|
|
53
|
+
schedulesEnabledByEnv?: boolean
|
|
54
|
+
data: ScheduleSummary[]
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Handle `GET /api/schedules/:env`. Returns the projected schedule list
|
|
59
|
+
* for the current manifest, or 404 when no manifest is registered.
|
|
60
|
+
*/
|
|
61
|
+
export async function handleGetSchedules(
|
|
62
|
+
environment: string,
|
|
63
|
+
deps: ScheduleHandlerDeps,
|
|
64
|
+
): Promise<Response> {
|
|
65
|
+
if (!ALLOWED_ENVS.has(environment)) {
|
|
66
|
+
return json(400, {
|
|
67
|
+
error: "invalid_environment",
|
|
68
|
+
message: `environment must be one of ${[...ALLOWED_ENVS].join(", ")}`,
|
|
69
|
+
})
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const envelope = await deps.manifestStore.getCurrent(environment)
|
|
73
|
+
if (!envelope) {
|
|
74
|
+
return json(404, { error: "not_found", environment })
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const now = deps.now ? deps.now() : Date.now()
|
|
78
|
+
const manifest = envelope.manifest as unknown as WorkflowManifest
|
|
79
|
+
const data: ScheduleSummary[] = []
|
|
80
|
+
|
|
81
|
+
for (const workflow of manifest.workflows ?? []) {
|
|
82
|
+
const schedules = Array.isArray(workflow.schedules) ? workflow.schedules : []
|
|
83
|
+
schedules.forEach((schedule, index) => {
|
|
84
|
+
const scheduleId = `${envelope.versionId}:${workflow.id}:${schedule.name ?? index}`
|
|
85
|
+
const registrationDisabled = schedule.enabled === false
|
|
86
|
+
const envFiltered = Array.isArray(schedule.environments)
|
|
87
|
+
? !schedule.environments.includes(environment as never)
|
|
88
|
+
: false
|
|
89
|
+
|
|
90
|
+
let nextRunAt: number | null = null
|
|
91
|
+
if (!registrationDisabled && !envFiltered) {
|
|
92
|
+
try {
|
|
93
|
+
const fire = computeNextFire(schedule, now)
|
|
94
|
+
nextRunAt = Number.isFinite(fire) ? fire : null
|
|
95
|
+
} catch (err) {
|
|
96
|
+
deps.logger?.("warn", "schedules: cannot compute next fire", {
|
|
97
|
+
workflowId: workflow.id,
|
|
98
|
+
scheduleId,
|
|
99
|
+
error: err instanceof Error ? err.message : String(err),
|
|
100
|
+
})
|
|
101
|
+
nextRunAt = null
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const enabled = !registrationDisabled && !envFiltered
|
|
106
|
+
const disabledReason = registrationDisabled
|
|
107
|
+
? "registration_disabled"
|
|
108
|
+
: envFiltered
|
|
109
|
+
? "env_filtered"
|
|
110
|
+
: undefined
|
|
111
|
+
|
|
112
|
+
data.push({
|
|
113
|
+
workflowId: workflow.id,
|
|
114
|
+
scheduleId,
|
|
115
|
+
schedule,
|
|
116
|
+
nextRunAt,
|
|
117
|
+
enabled,
|
|
118
|
+
...(disabledReason ? { disabledReason } : {}),
|
|
119
|
+
})
|
|
120
|
+
})
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const response: ScheduleListResponse = {
|
|
124
|
+
environment,
|
|
125
|
+
versionId: envelope.versionId,
|
|
126
|
+
data,
|
|
127
|
+
...(deps.schedulesEnabledByEnv !== undefined
|
|
128
|
+
? { schedulesEnabledByEnv: deps.schedulesEnabledByEnv }
|
|
129
|
+
: {}),
|
|
130
|
+
}
|
|
131
|
+
return json(200, response)
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function json(status: number, body: unknown): Response {
|
|
135
|
+
return new Response(JSON.stringify(body), {
|
|
136
|
+
status,
|
|
137
|
+
headers: {
|
|
138
|
+
"content-type": "application/json; charset=utf-8",
|
|
139
|
+
"access-control-allow-origin": "*",
|
|
140
|
+
"access-control-allow-methods": "GET,POST,OPTIONS",
|
|
141
|
+
"access-control-allow-headers": "content-type, x-voyant-protocol",
|
|
142
|
+
},
|
|
143
|
+
})
|
|
144
|
+
}
|
package/src/worker.ts
CHANGED
|
@@ -22,6 +22,7 @@ import {
|
|
|
22
22
|
import { handleIngestEvent } from "./event-handler.js"
|
|
23
23
|
import { handleGetManifest, handleRegisterManifest } from "./manifest-handler.js"
|
|
24
24
|
import type { CfManifestStore } from "./manifest-kv-store.js"
|
|
25
|
+
import { handleGetSchedules } from "./schedule-handler.js"
|
|
25
26
|
|
|
26
27
|
const DEFAULT_TENANT_META = {
|
|
27
28
|
tenantId: "default",
|
|
@@ -54,10 +55,19 @@ export interface WorkerFetchDeps<Id = unknown> {
|
|
|
54
55
|
now?: () => number
|
|
55
56
|
/**
|
|
56
57
|
* Optional KV-backed manifest store. When set, the worker also serves
|
|
57
|
-
* `/api/manifests
|
|
58
|
-
* useful for orchestrators that only expose the
|
|
58
|
+
* `/api/manifests`, `/api/schedules`, and `/api/events`. When unset,
|
|
59
|
+
* those routes 404 — useful for orchestrators that only expose the
|
|
60
|
+
* run surface.
|
|
59
61
|
*/
|
|
60
62
|
manifestStore?: CfManifestStore
|
|
63
|
+
/**
|
|
64
|
+
* Optional process-wide schedules toggle reported by
|
|
65
|
+
* `/api/schedules/:env`. Used by the workflow-schedules UI to show
|
|
66
|
+
* "schedules disabled by env flag" when the orchestrator is running
|
|
67
|
+
* with `VOYANT_WORKFLOWS_ENABLE_SCHEDULES` (or equivalent) turned off.
|
|
68
|
+
* When omitted, the schedules response leaves the flag out.
|
|
69
|
+
*/
|
|
70
|
+
schedulesEnabledByEnv?: boolean
|
|
61
71
|
/**
|
|
62
72
|
* Tenant metadata stamped on event-triggered runs. Defaults to
|
|
63
73
|
* `{ tenantId: "default", projectId: "default", organizationId: "default" }`.
|
|
@@ -118,6 +128,21 @@ export async function handleWorkerRequest<Id>(
|
|
|
118
128
|
logger: deps.logger,
|
|
119
129
|
})
|
|
120
130
|
}
|
|
131
|
+
|
|
132
|
+
// GET /api/schedules/:env — aggregate scheduled-workflow view.
|
|
133
|
+
const schedulesMatch = url.pathname.match(/^\/api\/schedules\/([^/]+)$/)
|
|
134
|
+
if (schedulesMatch) {
|
|
135
|
+
if (!deps.manifestStore) {
|
|
136
|
+
return json(404, { error: "schedules_not_configured" })
|
|
137
|
+
}
|
|
138
|
+
const env = decodeURIComponent(schedulesMatch[1] ?? "")
|
|
139
|
+
return handleGetSchedules(env, {
|
|
140
|
+
manifestStore: deps.manifestStore,
|
|
141
|
+
schedulesEnabledByEnv: deps.schedulesEnabledByEnv,
|
|
142
|
+
now: deps.now,
|
|
143
|
+
logger: deps.logger,
|
|
144
|
+
})
|
|
145
|
+
}
|
|
121
146
|
}
|
|
122
147
|
|
|
123
148
|
// POST /api/events — synchronous event ingest. Loads the manifest,
|