@voyantjs/workflows-cloud-adapter 0.66.0 → 0.68.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/auto-publish.d.ts +45 -0
- package/dist/auto-publish.d.ts.map +1 -0
- package/dist/auto-publish.js +107 -0
- package/dist/index.d.ts +32 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +49 -7
- package/package.json +4 -4
- package/src/auto-publish.ts +145 -0
- package/src/index.ts +96 -8
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { buildManifest } from "@voyantjs/workflows/events";
|
|
2
|
+
import type { WorkflowManifest } from "@voyantjs/workflows/protocol";
|
|
3
|
+
import type { CfManifestStore } from "@voyantjs/workflows-orchestrator-cloudflare";
|
|
4
|
+
export type WorkflowEnvironment = "production" | "preview" | "development";
|
|
5
|
+
export interface AutoPublishContext {
|
|
6
|
+
manifestStore: CfManifestStore;
|
|
7
|
+
environment?: WorkflowEnvironment | string;
|
|
8
|
+
projectId?: string;
|
|
9
|
+
/**
|
|
10
|
+
* Hook to schedule the publish so it doesn't block the hot path.
|
|
11
|
+
* In a CF Worker, pass `ctx.waitUntil`. Defaults to fire-and-forget
|
|
12
|
+
* (the returned promise is unhandled).
|
|
13
|
+
*/
|
|
14
|
+
waitUntil?: (promise: Promise<unknown>) => void;
|
|
15
|
+
logger?: (level: "info" | "warn" | "error", msg: string, data?: object) => void;
|
|
16
|
+
/**
|
|
17
|
+
* Internal seam — defaults to the global `__listRegisteredWorkflows()`.
|
|
18
|
+
* Tests inject a fixture registry without polluting the process-wide
|
|
19
|
+
* registry.
|
|
20
|
+
*/
|
|
21
|
+
listWorkflows?: () => ReadonlyArray<{
|
|
22
|
+
id: string;
|
|
23
|
+
config?: Parameters<typeof buildManifest>[0]["workflows"][number]["config"];
|
|
24
|
+
}>;
|
|
25
|
+
/**
|
|
26
|
+
* Internal seam — defaults to the global event-filter registry.
|
|
27
|
+
*/
|
|
28
|
+
listEventFilters?: () => Parameters<typeof buildManifest>[0]["eventFilters"];
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Schedule an auto-publish of the in-process registry. Idempotent —
|
|
32
|
+
* the latch prevents repeated KV reads per isolate, and the publish
|
|
33
|
+
* itself short-circuits when the current envelope already matches the
|
|
34
|
+
* registry's versionId.
|
|
35
|
+
*/
|
|
36
|
+
export declare function scheduleAutoPublishManifest(ctx: AutoPublishContext): void;
|
|
37
|
+
/**
|
|
38
|
+
* Build the registry-derived manifest and write it to KV when needed.
|
|
39
|
+
* Exported so tests (and the rare caller that wants synchronous
|
|
40
|
+
* semantics) can await the result. Returns the published manifest, or
|
|
41
|
+
* `null` when the registry is empty or KV already has a matching
|
|
42
|
+
* envelope.
|
|
43
|
+
*/
|
|
44
|
+
export declare function publishManifest(ctx: AutoPublishContext): Promise<WorkflowManifest | null>;
|
|
45
|
+
//# sourceMappingURL=auto-publish.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auto-publish.d.ts","sourceRoot":"","sources":["../src/auto-publish.ts"],"names":[],"mappings":"AAkBA,OAAO,EAAE,aAAa,EAA0B,MAAM,4BAA4B,CAAA;AAClF,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,8BAA8B,CAAA;AACpE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,6CAA6C,CAAA;AAIlF,MAAM,MAAM,mBAAmB,GAAG,YAAY,GAAG,SAAS,GAAG,aAAa,CAAA;AAE1E,MAAM,WAAW,kBAAkB;IACjC,aAAa,EAAE,eAAe,CAAA;IAC9B,WAAW,CAAC,EAAE,mBAAmB,GAAG,MAAM,CAAA;IAC1C,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB;;;;OAIG;IACH,SAAS,CAAC,EAAE,CAAC,OAAO,EAAE,OAAO,CAAC,OAAO,CAAC,KAAK,IAAI,CAAA;IAC/C,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;;;;OAIG;IACH,aAAa,CAAC,EAAE,MAAM,aAAa,CAAC;QAClC,EAAE,EAAE,MAAM,CAAA;QACV,MAAM,CAAC,EAAE,UAAU,CAAC,OAAO,aAAa,CAAC,CAAC,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,MAAM,CAAC,CAAC,QAAQ,CAAC,CAAA;KAC5E,CAAC,CAAA;IACF;;OAEG;IACH,gBAAgB,CAAC,EAAE,MAAM,UAAU,CAAC,OAAO,aAAa,CAAC,CAAC,CAAC,CAAC,CAAC,cAAc,CAAC,CAAA;CAC7E;AASD;;;;;GAKG;AACH,wBAAgB,2BAA2B,CAAC,GAAG,EAAE,kBAAkB,GAAG,IAAI,CA0BzE;AAED;;;;;;GAMG;AACH,wBAAsB,eAAe,CAAC,GAAG,EAAE,kBAAkB,GAAG,OAAO,CAAC,gBAAgB,GAAG,IAAI,CAAC,CAqC/F"}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
// Cold-start auto-publish of the in-process workflow registry to the
|
|
2
|
+
// `WORKFLOW_MANIFESTS` KV namespace.
|
|
3
|
+
//
|
|
4
|
+
// Background (issue #1070): the KV manifest is only populated when a
|
|
5
|
+
// tenant explicitly calls `registerManifest` (driver path) or POSTs
|
|
6
|
+
// `/api/manifests` (HTTP path). Tenants that compose workflows from
|
|
7
|
+
// many packages and never wire either path end up with an empty KV,
|
|
8
|
+
// which means voyant-cloud's scheduler can't pull the runtime manifest
|
|
9
|
+
// to seed `workflow_schedules`. The cron tick then has nothing to fire.
|
|
10
|
+
//
|
|
11
|
+
// This module bridges the gap: when the cloud adapter sees its first
|
|
12
|
+
// request, it builds the manifest from the in-process registry and
|
|
13
|
+
// writes it to KV if the current envelope is missing or its versionId
|
|
14
|
+
// differs. The manifest is content-addressed, so concurrent cold
|
|
15
|
+
// starts converge on the same versionId — repeated publishes are
|
|
16
|
+
// no-ops after the first.
|
|
17
|
+
import { __listRegisteredWorkflows } from "@voyantjs/workflows";
|
|
18
|
+
import { buildManifest, getEventFilterRegistry } from "@voyantjs/workflows/events";
|
|
19
|
+
const ALLOWED_ENVS = new Set(["production", "preview", "development"]);
|
|
20
|
+
/**
|
|
21
|
+
* Per-store latch so we only check KV once per cold start. The store
|
|
22
|
+
* object is created from the KV binding, which is stable across
|
|
23
|
+
* requests on the same isolate.
|
|
24
|
+
*/
|
|
25
|
+
const PUBLISHED = new WeakSet();
|
|
26
|
+
/**
|
|
27
|
+
* Schedule an auto-publish of the in-process registry. Idempotent —
|
|
28
|
+
* the latch prevents repeated KV reads per isolate, and the publish
|
|
29
|
+
* itself short-circuits when the current envelope already matches the
|
|
30
|
+
* registry's versionId.
|
|
31
|
+
*/
|
|
32
|
+
export function scheduleAutoPublishManifest(ctx) {
|
|
33
|
+
if (PUBLISHED.has(ctx.manifestStore))
|
|
34
|
+
return;
|
|
35
|
+
PUBLISHED.add(ctx.manifestStore);
|
|
36
|
+
const env = normalizeEnvironment(ctx.environment);
|
|
37
|
+
const work = (async () => {
|
|
38
|
+
try {
|
|
39
|
+
await publishManifest({ ...ctx, environment: env });
|
|
40
|
+
}
|
|
41
|
+
catch (err) {
|
|
42
|
+
// Cold-start publish is best-effort — never let a KV hiccup take
|
|
43
|
+
// down the request that triggered it. Clear the latch so a
|
|
44
|
+
// subsequent request re-tries.
|
|
45
|
+
PUBLISHED.delete(ctx.manifestStore);
|
|
46
|
+
ctx.logger?.("warn", "workflows: auto-publish manifest failed", {
|
|
47
|
+
error: err instanceof Error ? err.message : String(err),
|
|
48
|
+
environment: env,
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
})();
|
|
52
|
+
if (ctx.waitUntil) {
|
|
53
|
+
ctx.waitUntil(work);
|
|
54
|
+
}
|
|
55
|
+
else {
|
|
56
|
+
// No waitUntil — let it run; we already swallowed errors inside.
|
|
57
|
+
void work;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Build the registry-derived manifest and write it to KV when needed.
|
|
62
|
+
* Exported so tests (and the rare caller that wants synchronous
|
|
63
|
+
* semantics) can await the result. Returns the published manifest, or
|
|
64
|
+
* `null` when the registry is empty or KV already has a matching
|
|
65
|
+
* envelope.
|
|
66
|
+
*/
|
|
67
|
+
export async function publishManifest(ctx) {
|
|
68
|
+
const workflows = (ctx.listWorkflows ?? __listRegisteredWorkflows)();
|
|
69
|
+
if (workflows.length === 0)
|
|
70
|
+
return null;
|
|
71
|
+
const eventFilters = ctx.listEventFilters
|
|
72
|
+
? ctx.listEventFilters()
|
|
73
|
+
: getEventFilterRegistry().list();
|
|
74
|
+
const environment = normalizeEnvironment(ctx.environment);
|
|
75
|
+
const manifest = await buildManifest({
|
|
76
|
+
projectId: ctx.projectId,
|
|
77
|
+
environment,
|
|
78
|
+
workflows: workflows.map((wf) => ({ id: wf.id, config: wf.config })),
|
|
79
|
+
eventFilters,
|
|
80
|
+
});
|
|
81
|
+
const current = await ctx.manifestStore.getCurrent(environment);
|
|
82
|
+
if (current && current.versionId === manifest.versionId) {
|
|
83
|
+
ctx.logger?.("info", "workflows: auto-publish manifest is a no-op", {
|
|
84
|
+
environment,
|
|
85
|
+
versionId: manifest.versionId,
|
|
86
|
+
});
|
|
87
|
+
return null;
|
|
88
|
+
}
|
|
89
|
+
await ctx.manifestStore.registerManifest({
|
|
90
|
+
environment,
|
|
91
|
+
versionId: manifest.versionId,
|
|
92
|
+
manifest: manifest,
|
|
93
|
+
});
|
|
94
|
+
ctx.logger?.("info", "workflows: auto-published manifest", {
|
|
95
|
+
environment,
|
|
96
|
+
versionId: manifest.versionId,
|
|
97
|
+
workflowCount: workflows.length,
|
|
98
|
+
eventFilterCount: eventFilters.length,
|
|
99
|
+
});
|
|
100
|
+
return manifest;
|
|
101
|
+
}
|
|
102
|
+
function normalizeEnvironment(value) {
|
|
103
|
+
if (value && ALLOWED_ENVS.has(value)) {
|
|
104
|
+
return value;
|
|
105
|
+
}
|
|
106
|
+
return "production";
|
|
107
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { type ContainerNamespaceLike, type DurableObjectNamespaceLike, type DurableObjectStorageLike, type KvNamespaceLike, type StepDispatcher, type WorkerFetchDeps } from "@voyantjs/workflows-orchestrator-cloudflare";
|
|
2
|
+
export { type AutoPublishContext, publishManifest, scheduleAutoPublishManifest, type WorkflowEnvironment, } from "./auto-publish.js";
|
|
2
3
|
export interface CloudWorkflowsEnv {
|
|
3
4
|
/** Per-run Durable Object namespace declared by the tenant Worker. */
|
|
4
5
|
WORKFLOW_RUN_DO: DurableObjectNamespaceLike;
|
|
@@ -18,6 +19,12 @@ export interface CloudWorkflowsEnv {
|
|
|
18
19
|
* development only.
|
|
19
20
|
*/
|
|
20
21
|
VOYANT_API_TOKENS?: string;
|
|
22
|
+
/**
|
|
23
|
+
* Deployment environment label used when the cloud adapter auto-publishes
|
|
24
|
+
* the in-process workflow registry to `WORKFLOW_MANIFESTS`. Voyant Cloud
|
|
25
|
+
* injects this on tenant workers; defaults to `"production"` when unset.
|
|
26
|
+
*/
|
|
27
|
+
VOYANT_WORKFLOWS_ENVIRONMENT?: "production" | "preview" | "development" | string;
|
|
21
28
|
/**
|
|
22
29
|
* Prefix for the R2 S3 API URL that hosts the container bundle.
|
|
23
30
|
* Expected form: `https://<account>.r2.cloudflarestorage.com/<bucket>`.
|
|
@@ -53,9 +60,33 @@ export interface CloudOrchestratorOptions<Env extends CloudWorkflowsEnv = CloudW
|
|
|
53
60
|
tenantMeta?: WorkerFetchDeps["tenantMeta"];
|
|
54
61
|
services?: import("@voyantjs/workflows/driver").ServiceResolver;
|
|
55
62
|
resolveEnv?: (env: Env) => Env;
|
|
63
|
+
/**
|
|
64
|
+
* Opt out of the cold-start auto-publish of the in-process workflow
|
|
65
|
+
* registry to `WORKFLOW_MANIFESTS` KV. Default: auto-publish is on
|
|
66
|
+
* whenever the KV binding is present. Set to `false` if your deploy
|
|
67
|
+
* pipeline already POSTs `/api/manifests` and you want a single
|
|
68
|
+
* source of truth.
|
|
69
|
+
*/
|
|
70
|
+
autoPublishManifest?: boolean;
|
|
71
|
+
/**
|
|
72
|
+
* Cloudflare `ctx.waitUntil` — when provided, the auto-publish
|
|
73
|
+
* background task is registered with it so the response isn't held
|
|
74
|
+
* back by the KV write. Pass `(p) => ctx.waitUntil(p)` from the
|
|
75
|
+
* outer `fetch(request, env, ctx)` handler.
|
|
76
|
+
*/
|
|
77
|
+
waitUntil?: (promise: Promise<unknown>) => void;
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Cloudflare ExecutionContext-like shape consumed by the cloud
|
|
81
|
+
* orchestrator. Declared structurally so this package doesn't pull in
|
|
82
|
+
* `@cloudflare/workers-types` — pass the real `ctx` from your worker's
|
|
83
|
+
* `fetch(request, env, ctx)` and the structural shape will match.
|
|
84
|
+
*/
|
|
85
|
+
export interface CloudExecutionCtx {
|
|
86
|
+
waitUntil?: (promise: Promise<unknown>) => void;
|
|
56
87
|
}
|
|
57
88
|
export interface CloudOrchestrator<Env extends CloudWorkflowsEnv = CloudWorkflowsEnv> {
|
|
58
|
-
fetch: (request: Request, env?: Env) => Promise<Response>;
|
|
89
|
+
fetch: (request: Request, env?: Env, ctx?: CloudExecutionCtx) => Promise<Response>;
|
|
59
90
|
WorkflowRunDO: WorkflowRunDOClass<Env>;
|
|
60
91
|
}
|
|
61
92
|
export type WorkflowRunDOClass<Env extends CloudWorkflowsEnv = CloudWorkflowsEnv> = new (state: DurableObjectStateLike, env: Env) => WorkflowRunDO<Env>;
|
|
@@ -77,5 +108,4 @@ export declare class WorkflowRunDO<Env extends CloudWorkflowsEnv = CloudWorkflow
|
|
|
77
108
|
protected executionOptions(): CloudExecutionOptions<Env>;
|
|
78
109
|
}
|
|
79
110
|
export declare function createCloudStepDispatcher<Env extends CloudWorkflowsEnv = CloudWorkflowsEnv>(env: Env, options?: CloudExecutionOptions<Env>): StepDispatcher;
|
|
80
|
-
export {};
|
|
81
111
|
//# 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":"AAcA,OAAO,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAcA,OAAO,EAEL,KAAK,sBAAsB,EAK3B,KAAK,0BAA0B,EAC/B,KAAK,wBAAwB,EAI7B,KAAK,eAAe,EACpB,KAAK,cAAc,EACnB,KAAK,eAAe,EACrB,MAAM,6CAA6C,CAAA;AAIpD,OAAO,EACL,KAAK,kBAAkB,EACvB,eAAe,EACf,2BAA2B,EAC3B,KAAK,mBAAmB,GACzB,MAAM,mBAAmB,CAAA;AAE1B,MAAM,WAAW,iBAAiB;IAChC,sEAAsE;IACtE,eAAe,EAAE,0BAA0B,CAAA;IAC3C;;;;OAIG;IACH,WAAW,CAAC,EAAE,sBAAsB,CAAA;IACpC;;;OAGG;IACH,kBAAkB,CAAC,EAAE,eAAe,CAAA;IACpC;;;OAGG;IACH,iBAAiB,CAAC,EAAE,MAAM,CAAA;IAC1B;;;;OAIG;IACH,4BAA4B,CAAC,EAAE,YAAY,GAAG,SAAS,GAAG,aAAa,GAAG,MAAM,CAAA;IAChF;;;OAGG;IACH,iCAAiC,CAAC,EAAE,MAAM,CAAA;IAC1C,sEAAsE;IACtE,0BAA0B,CAAC,EAAE,MAAM,CAAA;IACnC,sEAAsE;IACtE,2BAA2B,CAAC,EAAE,MAAM,CAAA;IACpC,8EAA8E;IAC9E,uCAAuC,CAAC,EAAE,MAAM,CAAA;IAChD,kFAAkF;IAClF,2CAA2C,CAAC,EAAE,MAAM,CAAA;IACpD,uEAAuE;IACvE,oCAAoC,CAAC,EAAE,MAAM,CAAA;IAC7C,mEAAmE;IACnE,gCAAgC,CAAC,EAAE,MAAM,CAAA;IACzC,2DAA2D;IAC3D,sCAAsC,CAAC,EAAE,MAAM,CAAA;IAC/C,4DAA4D;IAC5D,+BAA+B,CAAC,EAAE,MAAM,CAAA;IACxC,yEAAyE;IACzE,gCAAgC,CAAC,EAAE,MAAM,CAAA;CAC1C;AAED,MAAM,WAAW,sBAAsB;IACrC,OAAO,EAAE,wBAAwB,CAAA;CAClC;AAED,MAAM,WAAW,wBAAwB,CAAC,GAAG,SAAS,iBAAiB,GAAG,iBAAiB;IACzF,aAAa,CAAC,EAAE,eAAe,CAAC,eAAe,CAAC,CAAA;IAChD,MAAM,CAAC,EAAE,eAAe,CAAC,QAAQ,CAAC,CAAA;IAClC,WAAW,CAAC,EAAE,eAAe,CAAC,aAAa,CAAC,CAAA;IAC5C,GAAG,CAAC,EAAE,MAAM,MAAM,CAAA;IAClB,UAAU,CAAC,EAAE,eAAe,CAAC,YAAY,CAAC,CAAA;IAC1C,QAAQ,CAAC,EAAE,OAAO,4BAA4B,EAAE,eAAe,CAAA;IAC/D,UAAU,CAAC,EAAE,CAAC,GAAG,EAAE,GAAG,KAAK,GAAG,CAAA;IAC9B;;;;;;OAMG;IACH,mBAAmB,CAAC,EAAE,OAAO,CAAA;IAC7B;;;;;OAKG;IACH,SAAS,CAAC,EAAE,CAAC,OAAO,EAAE,OAAO,CAAC,OAAO,CAAC,KAAK,IAAI,CAAA;CAChD;AAED;;;;;GAKG;AACH,MAAM,WAAW,iBAAiB;IAChC,SAAS,CAAC,EAAE,CAAC,OAAO,EAAE,OAAO,CAAC,OAAO,CAAC,KAAK,IAAI,CAAA;CAChD;AAED,MAAM,WAAW,iBAAiB,CAAC,GAAG,SAAS,iBAAiB,GAAG,iBAAiB;IAClF,KAAK,EAAE,CAAC,OAAO,EAAE,OAAO,EAAE,GAAG,CAAC,EAAE,GAAG,EAAE,GAAG,CAAC,EAAE,iBAAiB,KAAK,OAAO,CAAC,QAAQ,CAAC,CAAA;IAClF,aAAa,EAAE,kBAAkB,CAAC,GAAG,CAAC,CAAA;CACvC;AAcD,MAAM,MAAM,kBAAkB,CAAC,GAAG,SAAS,iBAAiB,GAAG,iBAAiB,IAAI,KAClF,KAAK,EAAE,sBAAsB,EAC7B,GAAG,EAAE,GAAG,KACL,aAAa,CAAC,GAAG,CAAC,CAAA;AAEvB,KAAK,qBAAqB,CAAC,GAAG,SAAS,iBAAiB,GAAG,iBAAiB,IAAI,IAAI,CAClF,wBAAwB,CAAC,GAAG,CAAC,EAC7B,UAAU,GAAG,KAAK,GAAG,QAAQ,CAC9B,CAAA;AAED,wBAAgB,uBAAuB,CAAC,GAAG,SAAS,iBAAiB,GAAG,iBAAiB,EACvF,SAAS,CAAC,EAAE,OAAO,EACnB,QAAQ,CAAC,EAAE,GAAG,EACd,OAAO,GAAE,wBAAwB,CAAC,GAAG,CAAM,GAC1C,iBAAiB,CAAC,GAAG,CAAC,CAcxB;AAED,wBAAgB,cAAc,CAC5B,GAAG,SAAS;IACV,GAAG,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,QAAQ,GAAG,OAAO,CAAC,QAAQ,CAAC,KAAK,OAAO,CAAA;IAC9F,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,OAAO,EAAE,GAAG,CAAC,EAAE,OAAO,EAAE,GAAG,CAAC,EAAE,OAAO,KAAK,QAAQ,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAA;CACzF,EACD,GAAG,SAAS,iBAAiB,GAAG,iBAAiB,EACjD,GAAG,EAAE,GAAG,EAAE,GAAG,CAAC,EAAE,GAAG,EAAE,OAAO,GAAE,wBAAwB,CAAC,GAAG,CAAC,GAAG;IAAE,UAAU,CAAC,EAAE,MAAM,CAAA;CAAO,GAAG,GAAG,CAgCjG;AAED,wBAAsB,gBAAgB,CAAC,GAAG,SAAS,iBAAiB,GAAG,iBAAiB,EACtF,OAAO,EAAE,OAAO,EAChB,GAAG,EAAE,GAAG,EACR,OAAO,GAAE,wBAAwB,CAAC,GAAG,CAAM,GAC1C,OAAO,CAAC,QAAQ,CAAC,CA6BnB;AAED,qBAAa,aAAa,CAAC,GAAG,SAAS,iBAAiB,GAAG,iBAAiB;IAC1E,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAwB;IAC9C,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAK;gBAEb,KAAK,EAAE,sBAAsB,EAAE,GAAG,EAAE,GAAG;IAK7C,KAAK,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,QAAQ,CAAC;IAQ1C,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAQ5B,SAAS,CAAC,gBAAgB,IAAI,qBAAqB,CAAC,GAAG,CAAC;CAGzD;AAED,wBAAgB,yBAAyB,CAAC,GAAG,SAAS,iBAAiB,GAAG,iBAAiB,EACzF,GAAG,EAAE,GAAG,EACR,OAAO,GAAE,qBAAqB,CAAC,GAAG,CAAyD,GAC1F,cAAc,CAEhB"}
|
package/dist/index.js
CHANGED
|
@@ -7,15 +7,20 @@ import { createBearerVerifier, createHmacSigner } from "@voyantjs/workflows/auth
|
|
|
7
7
|
import { handleStepRequest, } from "@voyantjs/workflows/handler";
|
|
8
8
|
import { createInMemoryRateLimiter } from "@voyantjs/workflows/rate-limit";
|
|
9
9
|
import { createCfContainerStepRunner, createInlineDispatcher, createKvManifestStore, createR2Presigner, handleDurableObjectAlarm, handleDurableObjectRequest, handleWorkerRequest, } from "@voyantjs/workflows-orchestrator-cloudflare";
|
|
10
|
+
import { scheduleAutoPublishManifest } from "./auto-publish.js";
|
|
11
|
+
export { publishManifest, scheduleAutoPublishManifest, } from "./auto-publish.js";
|
|
10
12
|
const envCache = new WeakMap();
|
|
11
13
|
const defaultExecutionOptions = {};
|
|
12
14
|
export function createCloudOrchestrator(workflows, boundEnv, options = {}) {
|
|
13
15
|
void workflows;
|
|
14
16
|
const WorkflowRunDOWithOptions = createWorkflowRunDOClass(options);
|
|
15
17
|
return {
|
|
16
|
-
fetch(request, requestEnv) {
|
|
18
|
+
fetch(request, requestEnv, ctx) {
|
|
17
19
|
const env = resolveBoundEnv(boundEnv, requestEnv, options);
|
|
18
|
-
|
|
20
|
+
const callOptions = ctx?.waitUntil
|
|
21
|
+
? { ...options, waitUntil: options.waitUntil ?? ctx.waitUntil.bind(ctx) }
|
|
22
|
+
: options;
|
|
23
|
+
return handleCloudFetch(request, env, callOptions);
|
|
19
24
|
},
|
|
20
25
|
WorkflowRunDO: WorkflowRunDOWithOptions,
|
|
21
26
|
};
|
|
@@ -27,7 +32,8 @@ export function mountWorkflows(app, env, options = {}) {
|
|
|
27
32
|
app.all(`${pathPrefix}/*`, (...args) => {
|
|
28
33
|
const request = extractRequest(args);
|
|
29
34
|
const requestEnv = extractEnv(args, env);
|
|
30
|
-
|
|
35
|
+
const ctx = extractCtx(args);
|
|
36
|
+
return orchestrator.fetch(request, requestEnv, ctx);
|
|
31
37
|
});
|
|
32
38
|
return app;
|
|
33
39
|
}
|
|
@@ -35,7 +41,7 @@ export function mountWorkflows(app, env, options = {}) {
|
|
|
35
41
|
const originalFetch = app.fetch.bind(app);
|
|
36
42
|
app.fetch = (request, requestEnv, ctx) => {
|
|
37
43
|
if (isMountedPath(new URL(request.url).pathname, pathPrefix)) {
|
|
38
|
-
return orchestrator.fetch(request, requestEnv ?? env);
|
|
44
|
+
return orchestrator.fetch(request, requestEnv ?? env, ctx);
|
|
39
45
|
}
|
|
40
46
|
return originalFetch(request, requestEnv, ctx);
|
|
41
47
|
};
|
|
@@ -49,6 +55,16 @@ export async function handleCloudFetch(request, env, options = {}) {
|
|
|
49
55
|
.split(",")
|
|
50
56
|
.map((s) => s.trim())
|
|
51
57
|
.filter((s) => s.length > 0);
|
|
58
|
+
const manifestStore = resolveManifestStore(resolvedEnv);
|
|
59
|
+
if (manifestStore && options.autoPublishManifest !== false) {
|
|
60
|
+
const autoPublishCtx = {
|
|
61
|
+
manifestStore,
|
|
62
|
+
environment: resolvedEnv.VOYANT_WORKFLOWS_ENVIRONMENT,
|
|
63
|
+
logger: options.logger,
|
|
64
|
+
...(options.waitUntil ? { waitUntil: options.waitUntil } : {}),
|
|
65
|
+
};
|
|
66
|
+
scheduleAutoPublishManifest(autoPublishCtx);
|
|
67
|
+
}
|
|
52
68
|
return handleWorkerRequest(request, {
|
|
53
69
|
runDO: resolvedEnv.WORKFLOW_RUN_DO,
|
|
54
70
|
verifyRequest: options.verifyRequest ?? (tokens.length > 0 ? createBearerVerifier(tokens) : undefined),
|
|
@@ -56,9 +72,7 @@ export async function handleCloudFetch(request, env, options = {}) {
|
|
|
56
72
|
idGenerator: options.idGenerator,
|
|
57
73
|
now: options.now,
|
|
58
74
|
tenantMeta: options.tenantMeta,
|
|
59
|
-
manifestStore
|
|
60
|
-
? createKvManifestStore({ kv: resolvedEnv.WORKFLOW_MANIFESTS })
|
|
61
|
-
: undefined,
|
|
75
|
+
manifestStore,
|
|
62
76
|
});
|
|
63
77
|
}
|
|
64
78
|
export class WorkflowRunDO {
|
|
@@ -96,6 +110,22 @@ function createWorkflowRunDOClass(options) {
|
|
|
96
110
|
}
|
|
97
111
|
};
|
|
98
112
|
}
|
|
113
|
+
function resolveManifestStore(env) {
|
|
114
|
+
const kv = env.WORKFLOW_MANIFESTS;
|
|
115
|
+
if (!kv)
|
|
116
|
+
return undefined;
|
|
117
|
+
const cache = cacheFor(env);
|
|
118
|
+
// Re-create when the KV binding identity changes (e.g. a test that
|
|
119
|
+
// swaps namespaces on the same env reference). In production the
|
|
120
|
+
// binding is stable across requests so this path is a cache hit and
|
|
121
|
+
// the WeakSet-based latch in scheduleAutoPublishManifest works as
|
|
122
|
+
// intended.
|
|
123
|
+
if (!cache.manifestStore || cache.manifestStoreKv !== kv) {
|
|
124
|
+
cache.manifestStoreKv = kv;
|
|
125
|
+
cache.manifestStore = createKvManifestStore({ kv });
|
|
126
|
+
}
|
|
127
|
+
return cache.manifestStore;
|
|
128
|
+
}
|
|
99
129
|
function resolveDispatcher(env, options = defaultExecutionOptions) {
|
|
100
130
|
const cache = cacheFor(env);
|
|
101
131
|
if (!cache.dispatcher || cache.dispatcherOptions !== options) {
|
|
@@ -378,3 +408,15 @@ function extractEnv(args, boundEnv) {
|
|
|
378
408
|
const firstEnv = args[0]?.env;
|
|
379
409
|
return firstEnv ?? args[1];
|
|
380
410
|
}
|
|
411
|
+
function extractCtx(args) {
|
|
412
|
+
// Hono passes (c, next) with executionCtx on the context; raw workers
|
|
413
|
+
// pass (request, env, ctx) directly. Probe both shapes so the auto-publish
|
|
414
|
+
// background task can register with the right waitUntil.
|
|
415
|
+
const honoCtx = args[0];
|
|
416
|
+
if (honoCtx?.executionCtx?.waitUntil)
|
|
417
|
+
return honoCtx.executionCtx;
|
|
418
|
+
const rawCtx = args[2];
|
|
419
|
+
if (rawCtx && typeof rawCtx.waitUntil === "function")
|
|
420
|
+
return rawCtx;
|
|
421
|
+
return undefined;
|
|
422
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@voyantjs/workflows-cloud-adapter",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.68.0",
|
|
4
4
|
"description": "Tenant Worker adapter for Voyant Cloud Workflows deployments. Wires WorkflowRunDO, inline local dispatch, and platform step-runner bindings from env.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"repository": {
|
|
@@ -26,9 +26,9 @@
|
|
|
26
26
|
"NOTICE"
|
|
27
27
|
],
|
|
28
28
|
"dependencies": {
|
|
29
|
-
"@voyantjs/workflows": "0.
|
|
30
|
-
"@voyantjs/workflows-orchestrator": "0.
|
|
31
|
-
"@voyantjs/workflows-orchestrator-cloudflare": "0.
|
|
29
|
+
"@voyantjs/workflows": "0.68.0",
|
|
30
|
+
"@voyantjs/workflows-orchestrator": "0.68.0",
|
|
31
|
+
"@voyantjs/workflows-orchestrator-cloudflare": "0.68.0"
|
|
32
32
|
},
|
|
33
33
|
"devDependencies": {
|
|
34
34
|
"@types/node": "^20.12.0",
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
// Cold-start auto-publish of the in-process workflow registry to the
|
|
2
|
+
// `WORKFLOW_MANIFESTS` KV namespace.
|
|
3
|
+
//
|
|
4
|
+
// Background (issue #1070): the KV manifest is only populated when a
|
|
5
|
+
// tenant explicitly calls `registerManifest` (driver path) or POSTs
|
|
6
|
+
// `/api/manifests` (HTTP path). Tenants that compose workflows from
|
|
7
|
+
// many packages and never wire either path end up with an empty KV,
|
|
8
|
+
// which means voyant-cloud's scheduler can't pull the runtime manifest
|
|
9
|
+
// to seed `workflow_schedules`. The cron tick then has nothing to fire.
|
|
10
|
+
//
|
|
11
|
+
// This module bridges the gap: when the cloud adapter sees its first
|
|
12
|
+
// request, it builds the manifest from the in-process registry and
|
|
13
|
+
// writes it to KV if the current envelope is missing or its versionId
|
|
14
|
+
// differs. The manifest is content-addressed, so concurrent cold
|
|
15
|
+
// starts converge on the same versionId — repeated publishes are
|
|
16
|
+
// no-ops after the first.
|
|
17
|
+
|
|
18
|
+
import { __listRegisteredWorkflows } from "@voyantjs/workflows"
|
|
19
|
+
import { buildManifest, getEventFilterRegistry } from "@voyantjs/workflows/events"
|
|
20
|
+
import type { WorkflowManifest } from "@voyantjs/workflows/protocol"
|
|
21
|
+
import type { CfManifestStore } from "@voyantjs/workflows-orchestrator-cloudflare"
|
|
22
|
+
|
|
23
|
+
const ALLOWED_ENVS = new Set<WorkflowEnvironment>(["production", "preview", "development"])
|
|
24
|
+
|
|
25
|
+
export type WorkflowEnvironment = "production" | "preview" | "development"
|
|
26
|
+
|
|
27
|
+
export interface AutoPublishContext {
|
|
28
|
+
manifestStore: CfManifestStore
|
|
29
|
+
environment?: WorkflowEnvironment | string
|
|
30
|
+
projectId?: string
|
|
31
|
+
/**
|
|
32
|
+
* Hook to schedule the publish so it doesn't block the hot path.
|
|
33
|
+
* In a CF Worker, pass `ctx.waitUntil`. Defaults to fire-and-forget
|
|
34
|
+
* (the returned promise is unhandled).
|
|
35
|
+
*/
|
|
36
|
+
waitUntil?: (promise: Promise<unknown>) => void
|
|
37
|
+
logger?: (level: "info" | "warn" | "error", msg: string, data?: object) => void
|
|
38
|
+
/**
|
|
39
|
+
* Internal seam — defaults to the global `__listRegisteredWorkflows()`.
|
|
40
|
+
* Tests inject a fixture registry without polluting the process-wide
|
|
41
|
+
* registry.
|
|
42
|
+
*/
|
|
43
|
+
listWorkflows?: () => ReadonlyArray<{
|
|
44
|
+
id: string
|
|
45
|
+
config?: Parameters<typeof buildManifest>[0]["workflows"][number]["config"]
|
|
46
|
+
}>
|
|
47
|
+
/**
|
|
48
|
+
* Internal seam — defaults to the global event-filter registry.
|
|
49
|
+
*/
|
|
50
|
+
listEventFilters?: () => Parameters<typeof buildManifest>[0]["eventFilters"]
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Per-store latch so we only check KV once per cold start. The store
|
|
55
|
+
* object is created from the KV binding, which is stable across
|
|
56
|
+
* requests on the same isolate.
|
|
57
|
+
*/
|
|
58
|
+
const PUBLISHED = new WeakSet<CfManifestStore>()
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Schedule an auto-publish of the in-process registry. Idempotent —
|
|
62
|
+
* the latch prevents repeated KV reads per isolate, and the publish
|
|
63
|
+
* itself short-circuits when the current envelope already matches the
|
|
64
|
+
* registry's versionId.
|
|
65
|
+
*/
|
|
66
|
+
export function scheduleAutoPublishManifest(ctx: AutoPublishContext): void {
|
|
67
|
+
if (PUBLISHED.has(ctx.manifestStore)) return
|
|
68
|
+
PUBLISHED.add(ctx.manifestStore)
|
|
69
|
+
|
|
70
|
+
const env = normalizeEnvironment(ctx.environment)
|
|
71
|
+
const work = (async () => {
|
|
72
|
+
try {
|
|
73
|
+
await publishManifest({ ...ctx, environment: env })
|
|
74
|
+
} catch (err) {
|
|
75
|
+
// Cold-start publish is best-effort — never let a KV hiccup take
|
|
76
|
+
// down the request that triggered it. Clear the latch so a
|
|
77
|
+
// subsequent request re-tries.
|
|
78
|
+
PUBLISHED.delete(ctx.manifestStore)
|
|
79
|
+
ctx.logger?.("warn", "workflows: auto-publish manifest failed", {
|
|
80
|
+
error: err instanceof Error ? err.message : String(err),
|
|
81
|
+
environment: env,
|
|
82
|
+
})
|
|
83
|
+
}
|
|
84
|
+
})()
|
|
85
|
+
|
|
86
|
+
if (ctx.waitUntil) {
|
|
87
|
+
ctx.waitUntil(work)
|
|
88
|
+
} else {
|
|
89
|
+
// No waitUntil — let it run; we already swallowed errors inside.
|
|
90
|
+
void work
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Build the registry-derived manifest and write it to KV when needed.
|
|
96
|
+
* Exported so tests (and the rare caller that wants synchronous
|
|
97
|
+
* semantics) can await the result. Returns the published manifest, or
|
|
98
|
+
* `null` when the registry is empty or KV already has a matching
|
|
99
|
+
* envelope.
|
|
100
|
+
*/
|
|
101
|
+
export async function publishManifest(ctx: AutoPublishContext): Promise<WorkflowManifest | null> {
|
|
102
|
+
const workflows = (ctx.listWorkflows ?? __listRegisteredWorkflows)()
|
|
103
|
+
if (workflows.length === 0) return null
|
|
104
|
+
|
|
105
|
+
const eventFilters = ctx.listEventFilters
|
|
106
|
+
? ctx.listEventFilters()
|
|
107
|
+
: getEventFilterRegistry().list()
|
|
108
|
+
|
|
109
|
+
const environment = normalizeEnvironment(ctx.environment)
|
|
110
|
+
const manifest = await buildManifest({
|
|
111
|
+
projectId: ctx.projectId,
|
|
112
|
+
environment,
|
|
113
|
+
workflows: workflows.map((wf) => ({ id: wf.id, config: wf.config })),
|
|
114
|
+
eventFilters,
|
|
115
|
+
})
|
|
116
|
+
|
|
117
|
+
const current = await ctx.manifestStore.getCurrent(environment)
|
|
118
|
+
if (current && current.versionId === manifest.versionId) {
|
|
119
|
+
ctx.logger?.("info", "workflows: auto-publish manifest is a no-op", {
|
|
120
|
+
environment,
|
|
121
|
+
versionId: manifest.versionId,
|
|
122
|
+
})
|
|
123
|
+
return null
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
await ctx.manifestStore.registerManifest({
|
|
127
|
+
environment,
|
|
128
|
+
versionId: manifest.versionId,
|
|
129
|
+
manifest: manifest as unknown as Record<string, unknown>,
|
|
130
|
+
})
|
|
131
|
+
ctx.logger?.("info", "workflows: auto-published manifest", {
|
|
132
|
+
environment,
|
|
133
|
+
versionId: manifest.versionId,
|
|
134
|
+
workflowCount: workflows.length,
|
|
135
|
+
eventFilterCount: eventFilters.length,
|
|
136
|
+
})
|
|
137
|
+
return manifest
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function normalizeEnvironment(value: string | undefined): WorkflowEnvironment {
|
|
141
|
+
if (value && ALLOWED_ENVS.has(value as WorkflowEnvironment)) {
|
|
142
|
+
return value as WorkflowEnvironment
|
|
143
|
+
}
|
|
144
|
+
return "production"
|
|
145
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
import { createInMemoryRateLimiter } from "@voyantjs/workflows/rate-limit"
|
|
14
14
|
import type { StepHandler } from "@voyantjs/workflows-orchestrator"
|
|
15
15
|
import {
|
|
16
|
+
type CfManifestStore,
|
|
16
17
|
type ContainerNamespaceLike,
|
|
17
18
|
createCfContainerStepRunner,
|
|
18
19
|
createInlineDispatcher,
|
|
@@ -28,6 +29,15 @@ import {
|
|
|
28
29
|
type WorkerFetchDeps,
|
|
29
30
|
} from "@voyantjs/workflows-orchestrator-cloudflare"
|
|
30
31
|
|
|
32
|
+
import { type AutoPublishContext, scheduleAutoPublishManifest } from "./auto-publish.js"
|
|
33
|
+
|
|
34
|
+
export {
|
|
35
|
+
type AutoPublishContext,
|
|
36
|
+
publishManifest,
|
|
37
|
+
scheduleAutoPublishManifest,
|
|
38
|
+
type WorkflowEnvironment,
|
|
39
|
+
} from "./auto-publish.js"
|
|
40
|
+
|
|
31
41
|
export interface CloudWorkflowsEnv {
|
|
32
42
|
/** Per-run Durable Object namespace declared by the tenant Worker. */
|
|
33
43
|
WORKFLOW_RUN_DO: DurableObjectNamespaceLike
|
|
@@ -47,6 +57,12 @@ export interface CloudWorkflowsEnv {
|
|
|
47
57
|
* development only.
|
|
48
58
|
*/
|
|
49
59
|
VOYANT_API_TOKENS?: string
|
|
60
|
+
/**
|
|
61
|
+
* Deployment environment label used when the cloud adapter auto-publishes
|
|
62
|
+
* the in-process workflow registry to `WORKFLOW_MANIFESTS`. Voyant Cloud
|
|
63
|
+
* injects this on tenant workers; defaults to `"production"` when unset.
|
|
64
|
+
*/
|
|
65
|
+
VOYANT_WORKFLOWS_ENVIRONMENT?: "production" | "preview" | "development" | string
|
|
50
66
|
/**
|
|
51
67
|
* Prefix for the R2 S3 API URL that hosts the container bundle.
|
|
52
68
|
* Expected form: `https://<account>.r2.cloudflarestorage.com/<bucket>`.
|
|
@@ -84,10 +100,35 @@ export interface CloudOrchestratorOptions<Env extends CloudWorkflowsEnv = CloudW
|
|
|
84
100
|
tenantMeta?: WorkerFetchDeps["tenantMeta"]
|
|
85
101
|
services?: import("@voyantjs/workflows/driver").ServiceResolver
|
|
86
102
|
resolveEnv?: (env: Env) => Env
|
|
103
|
+
/**
|
|
104
|
+
* Opt out of the cold-start auto-publish of the in-process workflow
|
|
105
|
+
* registry to `WORKFLOW_MANIFESTS` KV. Default: auto-publish is on
|
|
106
|
+
* whenever the KV binding is present. Set to `false` if your deploy
|
|
107
|
+
* pipeline already POSTs `/api/manifests` and you want a single
|
|
108
|
+
* source of truth.
|
|
109
|
+
*/
|
|
110
|
+
autoPublishManifest?: boolean
|
|
111
|
+
/**
|
|
112
|
+
* Cloudflare `ctx.waitUntil` — when provided, the auto-publish
|
|
113
|
+
* background task is registered with it so the response isn't held
|
|
114
|
+
* back by the KV write. Pass `(p) => ctx.waitUntil(p)` from the
|
|
115
|
+
* outer `fetch(request, env, ctx)` handler.
|
|
116
|
+
*/
|
|
117
|
+
waitUntil?: (promise: Promise<unknown>) => void
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Cloudflare ExecutionContext-like shape consumed by the cloud
|
|
122
|
+
* orchestrator. Declared structurally so this package doesn't pull in
|
|
123
|
+
* `@cloudflare/workers-types` — pass the real `ctx` from your worker's
|
|
124
|
+
* `fetch(request, env, ctx)` and the structural shape will match.
|
|
125
|
+
*/
|
|
126
|
+
export interface CloudExecutionCtx {
|
|
127
|
+
waitUntil?: (promise: Promise<unknown>) => void
|
|
87
128
|
}
|
|
88
129
|
|
|
89
130
|
export interface CloudOrchestrator<Env extends CloudWorkflowsEnv = CloudWorkflowsEnv> {
|
|
90
|
-
fetch: (request: Request, env?: Env) => Promise<Response>
|
|
131
|
+
fetch: (request: Request, env?: Env, ctx?: CloudExecutionCtx) => Promise<Response>
|
|
91
132
|
WorkflowRunDO: WorkflowRunDOClass<Env>
|
|
92
133
|
}
|
|
93
134
|
|
|
@@ -96,6 +137,8 @@ type EnvCache = {
|
|
|
96
137
|
dispatcherOptions?: CloudExecutionOptions<CloudWorkflowsEnv>
|
|
97
138
|
stepHandler?: StepHandler
|
|
98
139
|
stepHandlerOptions?: CloudExecutionOptions<CloudWorkflowsEnv>
|
|
140
|
+
manifestStore?: CfManifestStore
|
|
141
|
+
manifestStoreKv?: KvNamespaceLike
|
|
99
142
|
}
|
|
100
143
|
|
|
101
144
|
const envCache = new WeakMap<object, EnvCache>()
|
|
@@ -120,9 +163,12 @@ export function createCloudOrchestrator<Env extends CloudWorkflowsEnv = CloudWor
|
|
|
120
163
|
const WorkflowRunDOWithOptions = createWorkflowRunDOClass<Env>(options)
|
|
121
164
|
|
|
122
165
|
return {
|
|
123
|
-
fetch(request, requestEnv) {
|
|
166
|
+
fetch(request, requestEnv, ctx) {
|
|
124
167
|
const env = resolveBoundEnv(boundEnv, requestEnv, options)
|
|
125
|
-
|
|
168
|
+
const callOptions = ctx?.waitUntil
|
|
169
|
+
? { ...options, waitUntil: options.waitUntil ?? ctx.waitUntil.bind(ctx) }
|
|
170
|
+
: options
|
|
171
|
+
return handleCloudFetch(request, env, callOptions)
|
|
126
172
|
},
|
|
127
173
|
WorkflowRunDO: WorkflowRunDOWithOptions,
|
|
128
174
|
}
|
|
@@ -142,7 +188,8 @@ export function mountWorkflows<
|
|
|
142
188
|
app.all(`${pathPrefix}/*`, (...args) => {
|
|
143
189
|
const request = extractRequest(args)
|
|
144
190
|
const requestEnv = extractEnv<Env>(args, env)
|
|
145
|
-
|
|
191
|
+
const ctx = extractCtx(args)
|
|
192
|
+
return orchestrator.fetch(request, requestEnv, ctx)
|
|
146
193
|
})
|
|
147
194
|
return app
|
|
148
195
|
}
|
|
@@ -151,7 +198,11 @@ export function mountWorkflows<
|
|
|
151
198
|
const originalFetch = app.fetch.bind(app)
|
|
152
199
|
;(app as { fetch: typeof app.fetch }).fetch = (request, requestEnv, ctx) => {
|
|
153
200
|
if (isMountedPath(new URL(request.url).pathname, pathPrefix)) {
|
|
154
|
-
return orchestrator.fetch(
|
|
201
|
+
return orchestrator.fetch(
|
|
202
|
+
request,
|
|
203
|
+
(requestEnv as Env | undefined) ?? env,
|
|
204
|
+
ctx as CloudExecutionCtx | undefined,
|
|
205
|
+
)
|
|
155
206
|
}
|
|
156
207
|
return originalFetch(request, requestEnv, ctx)
|
|
157
208
|
}
|
|
@@ -174,6 +225,18 @@ export async function handleCloudFetch<Env extends CloudWorkflowsEnv = CloudWork
|
|
|
174
225
|
.map((s) => s.trim())
|
|
175
226
|
.filter((s) => s.length > 0)
|
|
176
227
|
|
|
228
|
+
const manifestStore = resolveManifestStore(resolvedEnv)
|
|
229
|
+
|
|
230
|
+
if (manifestStore && options.autoPublishManifest !== false) {
|
|
231
|
+
const autoPublishCtx: AutoPublishContext = {
|
|
232
|
+
manifestStore,
|
|
233
|
+
environment: resolvedEnv.VOYANT_WORKFLOWS_ENVIRONMENT,
|
|
234
|
+
logger: options.logger,
|
|
235
|
+
...(options.waitUntil ? { waitUntil: options.waitUntil } : {}),
|
|
236
|
+
}
|
|
237
|
+
scheduleAutoPublishManifest(autoPublishCtx)
|
|
238
|
+
}
|
|
239
|
+
|
|
177
240
|
return handleWorkerRequest(request, {
|
|
178
241
|
runDO: resolvedEnv.WORKFLOW_RUN_DO,
|
|
179
242
|
verifyRequest:
|
|
@@ -182,9 +245,7 @@ export async function handleCloudFetch<Env extends CloudWorkflowsEnv = CloudWork
|
|
|
182
245
|
idGenerator: options.idGenerator,
|
|
183
246
|
now: options.now,
|
|
184
247
|
tenantMeta: options.tenantMeta,
|
|
185
|
-
manifestStore
|
|
186
|
-
? createKvManifestStore({ kv: resolvedEnv.WORKFLOW_MANIFESTS })
|
|
187
|
-
: undefined,
|
|
248
|
+
manifestStore,
|
|
188
249
|
})
|
|
189
250
|
}
|
|
190
251
|
|
|
@@ -235,6 +296,22 @@ function createWorkflowRunDOClass<Env extends CloudWorkflowsEnv>(
|
|
|
235
296
|
}
|
|
236
297
|
}
|
|
237
298
|
|
|
299
|
+
function resolveManifestStore(env: CloudWorkflowsEnv): CfManifestStore | undefined {
|
|
300
|
+
const kv = env.WORKFLOW_MANIFESTS
|
|
301
|
+
if (!kv) return undefined
|
|
302
|
+
const cache = cacheFor(env)
|
|
303
|
+
// Re-create when the KV binding identity changes (e.g. a test that
|
|
304
|
+
// swaps namespaces on the same env reference). In production the
|
|
305
|
+
// binding is stable across requests so this path is a cache hit and
|
|
306
|
+
// the WeakSet-based latch in scheduleAutoPublishManifest works as
|
|
307
|
+
// intended.
|
|
308
|
+
if (!cache.manifestStore || cache.manifestStoreKv !== kv) {
|
|
309
|
+
cache.manifestStoreKv = kv
|
|
310
|
+
cache.manifestStore = createKvManifestStore({ kv })
|
|
311
|
+
}
|
|
312
|
+
return cache.manifestStore
|
|
313
|
+
}
|
|
314
|
+
|
|
238
315
|
function resolveDispatcher<Env extends CloudWorkflowsEnv>(
|
|
239
316
|
env: Env,
|
|
240
317
|
options: CloudExecutionOptions<Env> = defaultExecutionOptions as CloudExecutionOptions<Env>,
|
|
@@ -608,3 +685,14 @@ function extractEnv<Env extends CloudWorkflowsEnv>(
|
|
|
608
685
|
const firstEnv = (args[0] as { env?: unknown } | undefined)?.env
|
|
609
686
|
return (firstEnv as Env | undefined) ?? (args[1] as Env | undefined)
|
|
610
687
|
}
|
|
688
|
+
|
|
689
|
+
function extractCtx(args: readonly unknown[]): CloudExecutionCtx | undefined {
|
|
690
|
+
// Hono passes (c, next) with executionCtx on the context; raw workers
|
|
691
|
+
// pass (request, env, ctx) directly. Probe both shapes so the auto-publish
|
|
692
|
+
// background task can register with the right waitUntil.
|
|
693
|
+
const honoCtx = args[0] as { executionCtx?: CloudExecutionCtx } | undefined
|
|
694
|
+
if (honoCtx?.executionCtx?.waitUntil) return honoCtx.executionCtx
|
|
695
|
+
const rawCtx = args[2] as CloudExecutionCtx | undefined
|
|
696
|
+
if (rawCtx && typeof rawCtx.waitUntil === "function") return rawCtx
|
|
697
|
+
return undefined
|
|
698
|
+
}
|