@shware/workflow 2.0.1 → 3.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/README.md +106 -2
- package/dist/apns-relay.cjs +4 -17
- package/dist/apns-relay.cjs.map +1 -1
- package/dist/apns-relay.mjs +4 -17
- package/dist/apns-relay.mjs.map +1 -1
- package/dist/aws/engine-step.cjs +104 -0
- package/dist/aws/engine-step.cjs.map +1 -0
- package/dist/aws/engine-step.d.cts +28 -0
- package/dist/aws/engine-step.d.cts.map +1 -0
- package/dist/aws/engine-step.d.mts +28 -0
- package/dist/aws/engine-step.d.mts.map +1 -0
- package/dist/aws/engine-step.mjs +102 -0
- package/dist/aws/engine-step.mjs.map +1 -0
- package/dist/aws/function-url.cjs +62 -0
- package/dist/aws/function-url.cjs.map +1 -0
- package/dist/aws/function-url.d.cts +38 -0
- package/dist/aws/function-url.d.cts.map +1 -0
- package/dist/aws/function-url.d.mts +38 -0
- package/dist/aws/function-url.d.mts.map +1 -0
- package/dist/aws/function-url.mjs +59 -0
- package/dist/aws/function-url.mjs.map +1 -0
- package/dist/aws/index.cjs +160 -0
- package/dist/aws/index.cjs.map +1 -0
- package/dist/aws/index.d.cts +68 -0
- package/dist/aws/index.d.cts.map +1 -0
- package/dist/aws/index.d.mts +68 -0
- package/dist/aws/index.d.mts.map +1 -0
- package/dist/aws/index.mjs +148 -0
- package/dist/aws/index.mjs.map +1 -0
- package/dist/aws/instances.cjs +196 -0
- package/dist/aws/instances.cjs.map +1 -0
- package/dist/aws/instances.d.cts +94 -0
- package/dist/aws/instances.d.cts.map +1 -0
- package/dist/aws/instances.d.mts +94 -0
- package/dist/aws/instances.d.mts.map +1 -0
- package/dist/aws/instances.mjs +192 -0
- package/dist/aws/instances.mjs.map +1 -0
- package/dist/aws/lambda-api.cjs +89 -0
- package/dist/aws/lambda-api.cjs.map +1 -0
- package/dist/aws/lambda-api.d.cts +43 -0
- package/dist/aws/lambda-api.d.cts.map +1 -0
- package/dist/aws/lambda-api.d.mts +43 -0
- package/dist/aws/lambda-api.d.mts.map +1 -0
- package/dist/aws/lambda-api.mjs +88 -0
- package/dist/aws/lambda-api.mjs.map +1 -0
- package/dist/client.d.cts +1 -1
- package/dist/client.d.mts +1 -1
- package/dist/cloudflare/bindings.cjs +19 -0
- package/dist/cloudflare/bindings.cjs.map +1 -1
- package/dist/cloudflare/bindings.d.cts +39 -0
- package/dist/cloudflare/bindings.d.cts.map +1 -1
- package/dist/cloudflare/bindings.d.mts +39 -0
- package/dist/cloudflare/bindings.d.mts.map +1 -1
- package/dist/cloudflare/bindings.mjs +18 -1
- package/dist/cloudflare/bindings.mjs.map +1 -1
- package/dist/cloudflare/index.cjs +32 -28
- package/dist/cloudflare/index.d.cts +8 -6
- package/dist/cloudflare/index.d.mts +8 -6
- package/dist/cloudflare/index.mjs +12 -10
- package/dist/cloudflare/runner.cjs +27 -76
- package/dist/cloudflare/runner.cjs.map +1 -1
- package/dist/cloudflare/runner.d.cts +7 -2
- package/dist/cloudflare/runner.d.cts.map +1 -1
- package/dist/cloudflare/runner.d.mts +7 -2
- package/dist/cloudflare/runner.d.mts.map +1 -1
- package/dist/cloudflare/runner.mjs +26 -75
- package/dist/cloudflare/runner.mjs.map +1 -1
- package/dist/cloudflare/worker.cjs +22 -203
- package/dist/cloudflare/worker.cjs.map +1 -1
- package/dist/cloudflare/worker.d.cts +10 -56
- package/dist/cloudflare/worker.d.cts.map +1 -1
- package/dist/cloudflare/worker.d.mts +10 -56
- package/dist/cloudflare/worker.d.mts.map +1 -1
- package/dist/cloudflare/worker.mjs +20 -199
- package/dist/cloudflare/worker.mjs.map +1 -1
- package/dist/email-sender.cjs +62 -0
- package/dist/email-sender.cjs.map +1 -0
- package/dist/email-sender.d.cts +70 -0
- package/dist/email-sender.d.cts.map +1 -0
- package/dist/email-sender.d.mts +70 -0
- package/dist/email-sender.d.mts.map +1 -0
- package/dist/email-sender.mjs +60 -0
- package/dist/email-sender.mjs.map +1 -0
- package/dist/email.cjs +21 -0
- package/dist/email.cjs.map +1 -1
- package/dist/email.d.cts +6 -0
- package/dist/email.d.cts.map +1 -1
- package/dist/email.d.mts +6 -0
- package/dist/email.d.mts.map +1 -1
- package/dist/email.mjs +21 -1
- package/dist/email.mjs.map +1 -1
- package/dist/engine/index.cjs +1 -1
- package/dist/engine/index.mjs +1 -1
- package/dist/engine/interpreter.cjs +1 -1
- package/dist/engine/interpreter.cjs.map +1 -1
- package/dist/engine/interpreter.mjs +1 -1
- package/dist/engine/interpreter.mjs.map +1 -1
- package/dist/engine/ports.d.cts +6 -1
- package/dist/engine/ports.d.cts.map +1 -1
- package/dist/engine/ports.d.mts +6 -1
- package/dist/engine/ports.d.mts.map +1 -1
- package/dist/{cloudflare → host}/channels.cjs +13 -11
- package/dist/host/channels.cjs.map +1 -0
- package/dist/{cloudflare → host}/channels.d.cts +8 -3
- package/dist/host/channels.d.cts.map +1 -0
- package/dist/{cloudflare → host}/channels.d.mts +8 -3
- package/dist/host/channels.d.mts.map +1 -0
- package/dist/{cloudflare → host}/channels.mjs +9 -7
- package/dist/host/channels.mjs.map +1 -0
- package/dist/host/execute.cjs +81 -0
- package/dist/host/execute.cjs.map +1 -0
- package/dist/host/execute.d.cts +37 -0
- package/dist/host/execute.d.cts.map +1 -0
- package/dist/host/execute.d.mts +37 -0
- package/dist/host/execute.d.mts.map +1 -0
- package/dist/host/execute.mjs +80 -0
- package/dist/host/execute.mjs.map +1 -0
- package/dist/host/journey-host.cjs +214 -0
- package/dist/host/journey-host.cjs.map +1 -0
- package/dist/host/journey-host.d.cts +105 -0
- package/dist/host/journey-host.d.cts.map +1 -0
- package/dist/host/journey-host.d.mts +105 -0
- package/dist/host/journey-host.d.mts.map +1 -0
- package/dist/host/journey-host.mjs +210 -0
- package/dist/host/journey-host.mjs.map +1 -0
- package/dist/{cloudflare → host}/router.cjs +34 -13
- package/dist/host/router.cjs.map +1 -0
- package/dist/{cloudflare → host}/router.d.cts +2 -2
- package/dist/host/router.d.cts.map +1 -0
- package/dist/{cloudflare → host}/router.d.mts +2 -2
- package/dist/host/router.d.mts.map +1 -0
- package/dist/{cloudflare → host}/router.mjs +34 -13
- package/dist/host/router.mjs.map +1 -0
- package/dist/host/senders.cjs +63 -0
- package/dist/host/senders.cjs.map +1 -0
- package/dist/host/senders.d.cts +31 -0
- package/dist/host/senders.d.cts.map +1 -0
- package/dist/host/senders.d.mts +31 -0
- package/dist/host/senders.d.mts.map +1 -0
- package/dist/host/senders.mjs +59 -0
- package/dist/host/senders.mjs.map +1 -0
- package/dist/http2-client.cjs +90 -0
- package/dist/http2-client.cjs.map +1 -0
- package/dist/http2-client.d.cts +47 -0
- package/dist/http2-client.d.cts.map +1 -0
- package/dist/http2-client.d.mts +47 -0
- package/dist/http2-client.d.mts.map +1 -0
- package/dist/http2-client.mjs +88 -0
- package/dist/http2-client.mjs.map +1 -0
- package/dist/index.cjs +1 -0
- package/dist/index.d.cts +2 -2
- package/dist/index.d.mts +2 -2
- package/dist/index.mjs +2 -2
- package/dist/react-email.cjs.map +1 -1
- package/dist/react-email.d.cts +1 -2
- package/dist/react-email.d.cts.map +1 -1
- package/dist/react-email.d.mts +1 -2
- package/dist/react-email.d.mts.map +1 -1
- package/dist/react-email.mjs.map +1 -1
- package/dist/store/d1.cjs +1 -0
- package/dist/store/d1.cjs.map +1 -1
- package/dist/store/d1.d.cts.map +1 -1
- package/dist/store/d1.d.mts.map +1 -1
- package/dist/store/d1.mjs +1 -0
- package/dist/store/d1.mjs.map +1 -1
- package/dist/store/index.cjs.map +1 -1
- package/dist/store/index.d.cts +74 -0
- package/dist/store/index.d.cts.map +1 -1
- package/dist/store/index.d.mts +74 -0
- package/dist/store/index.d.mts.map +1 -1
- package/dist/store/index.mjs.map +1 -1
- package/dist/store/postgres.cjs +177 -12
- package/dist/store/postgres.cjs.map +1 -1
- package/dist/store/postgres.d.cts +10 -1
- package/dist/store/postgres.d.cts.map +1 -1
- package/dist/store/postgres.d.mts +10 -1
- package/dist/store/postgres.d.mts.map +1 -1
- package/dist/store/postgres.mjs +177 -12
- package/dist/store/postgres.mjs.map +1 -1
- package/dist/store/tables.cjs +31 -2
- package/dist/store/tables.cjs.map +1 -1
- package/dist/store/tables.d.cts +1 -0
- package/dist/store/tables.d.cts.map +1 -1
- package/dist/store/tables.d.mts +1 -0
- package/dist/store/tables.d.mts.map +1 -1
- package/dist/store/tables.mjs +31 -2
- package/dist/store/tables.mjs.map +1 -1
- package/dist/transports/cloudflare-email.cjs +59 -0
- package/dist/transports/cloudflare-email.cjs.map +1 -0
- package/dist/transports/cloudflare-email.d.cts +38 -0
- package/dist/transports/cloudflare-email.d.cts.map +1 -0
- package/dist/transports/cloudflare-email.d.mts +38 -0
- package/dist/transports/cloudflare-email.d.mts.map +1 -0
- package/dist/transports/cloudflare-email.mjs +57 -0
- package/dist/transports/cloudflare-email.mjs.map +1 -0
- package/dist/transports/ses.cjs +91 -0
- package/dist/transports/ses.cjs.map +1 -0
- package/dist/transports/ses.d.cts +39 -0
- package/dist/transports/ses.d.cts.map +1 -0
- package/dist/transports/ses.d.mts +39 -0
- package/dist/transports/ses.d.mts.map +1 -0
- package/dist/transports/ses.mjs +89 -0
- package/dist/transports/ses.mjs.map +1 -0
- package/dist/transports/sigv4.cjs +83 -0
- package/dist/transports/sigv4.cjs.map +1 -0
- package/dist/transports/sigv4.d.cts +27 -0
- package/dist/transports/sigv4.d.cts.map +1 -0
- package/dist/transports/sigv4.d.mts +27 -0
- package/dist/transports/sigv4.d.mts.map +1 -0
- package/dist/transports/sigv4.mjs +82 -0
- package/dist/transports/sigv4.mjs.map +1 -0
- package/package.json +53 -1
- package/dist/cloudflare/channels.cjs.map +0 -1
- package/dist/cloudflare/channels.d.cts.map +0 -1
- package/dist/cloudflare/channels.d.mts.map +0 -1
- package/dist/cloudflare/channels.mjs.map +0 -1
- package/dist/cloudflare/router.cjs.map +0 -1
- package/dist/cloudflare/router.d.cts.map +0 -1
- package/dist/cloudflare/router.d.mts.map +0 -1
- package/dist/cloudflare/router.mjs.map +0 -1
- package/dist/cloudflare/senders.cjs +0 -121
- package/dist/cloudflare/senders.cjs.map +0 -1
- package/dist/cloudflare/senders.d.cts +0 -72
- package/dist/cloudflare/senders.d.cts.map +0 -1
- package/dist/cloudflare/senders.d.mts +0 -72
- package/dist/cloudflare/senders.d.mts.map +0 -1
- package/dist/cloudflare/senders.mjs +0 -115
- package/dist/cloudflare/senders.mjs.map +0 -1
|
@@ -4,6 +4,7 @@ import { JourneyEnv, JourneyParams } from "./bindings.cjs";
|
|
|
4
4
|
import { JourneyStore } from "../store/index.cjs";
|
|
5
5
|
import { Contract } from "../contract.cjs";
|
|
6
6
|
import { JourneyRuntimeOptions } from "../runtime.cjs";
|
|
7
|
+
import { JourneyRunParts } from "../host/execute.cjs";
|
|
7
8
|
import { WorkflowEntrypoint, WorkflowEvent, WorkflowStep } from "cloudflare:workers";
|
|
8
9
|
//#region src/cloudflare/runner.d.ts
|
|
9
10
|
/**
|
|
@@ -36,7 +37,7 @@ export declare class JourneyRunner extends WorkflowEntrypoint<JourneyEnv, Journe
|
|
|
36
37
|
protected runtime(_params: JourneyParams): JourneyRuntimeOptions;
|
|
37
38
|
/**
|
|
38
39
|
* Message outlet; an app overrides this to plug in real channels (say
|
|
39
|
-
*
|
|
40
|
+
* an EmailSender over a transport with a react-email renderer). By default it picks the webhook
|
|
40
41
|
* or logging sender based on the environment.
|
|
41
42
|
*/
|
|
42
43
|
protected createMessageSender(): MessageSender;
|
|
@@ -66,8 +67,12 @@ export declare class JourneyRunner extends WorkflowEntrypoint<JourneyEnv, Journe
|
|
|
66
67
|
protected createObserver(_store: JourneyStore): JourneyObserver | undefined;
|
|
67
68
|
/** Override for a different version policy ('warn' by default) or a fully custom invoker (e.g. webhook degradation for hosted tenants). */
|
|
68
69
|
protected createActionInvoker(): ActionInvoker;
|
|
70
|
+
/**
|
|
71
|
+
* Everything one run is wired to. By default it is composed from the methods above; a host
|
|
72
|
+
* that assembles the whole set itself (journeyWorker) overrides this one instead.
|
|
73
|
+
*/
|
|
74
|
+
protected createRunParts(store: JourneyStore, params: JourneyParams): JourneyRunParts;
|
|
69
75
|
run(event: WorkflowEvent<JourneyParams>, step: WorkflowStep): Promise<JourneyOutcome>;
|
|
70
|
-
private execute;
|
|
71
76
|
}
|
|
72
77
|
//#endregion
|
|
73
78
|
//# sourceMappingURL=runner.d.cts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"runner.d.cts","names":[],"sources":["../../src/cloudflare/runner.ts"],"mappings":"
|
|
1
|
+
{"version":3,"file":"runner.d.cts","names":[],"sources":["../../src/cloudflare/runner.ts"],"mappings":";;;;;;;;;;;;;;;qBAuGa,sBAAsB,mBAAmB,YAAY;;;;;;;YAOtD,eAAe;;YAKT,WAAW,QAAQ,eAAe;;;;;;;;;;;;YAaxC,QAAQ,SAAS,gBAAgB;;;;;;YASjC,uBAAuB;;;;;;;;YAavB,oBAAoB;;;;;YAQpB,YAAY;;;;;;YASZ,0BAA0B;;;;;YAQ1B,eAAe,QAAQ,eAAe;;YAKtC,uBAAuB;;;;;YAQvB,eAAe,OAAO,cAAc,QAAQ,gBAAgB;EAahE,IAAI,OAAO,cAAc,gBAAgB,MAAM,eAAe,QAAQ"}
|
|
@@ -4,6 +4,7 @@ import { JourneyEnv, JourneyParams } from "./bindings.mjs";
|
|
|
4
4
|
import { JourneyStore } from "../store/index.mjs";
|
|
5
5
|
import { Contract } from "../contract.mjs";
|
|
6
6
|
import { JourneyRuntimeOptions } from "../runtime.mjs";
|
|
7
|
+
import { JourneyRunParts } from "../host/execute.mjs";
|
|
7
8
|
import { WorkflowEntrypoint, WorkflowEvent, WorkflowStep } from "cloudflare:workers";
|
|
8
9
|
//#region src/cloudflare/runner.d.ts
|
|
9
10
|
/**
|
|
@@ -36,7 +37,7 @@ export declare class JourneyRunner extends WorkflowEntrypoint<JourneyEnv, Journe
|
|
|
36
37
|
protected runtime(_params: JourneyParams): JourneyRuntimeOptions;
|
|
37
38
|
/**
|
|
38
39
|
* Message outlet; an app overrides this to plug in real channels (say
|
|
39
|
-
*
|
|
40
|
+
* an EmailSender over a transport with a react-email renderer). By default it picks the webhook
|
|
40
41
|
* or logging sender based on the environment.
|
|
41
42
|
*/
|
|
42
43
|
protected createMessageSender(): MessageSender;
|
|
@@ -66,8 +67,12 @@ export declare class JourneyRunner extends WorkflowEntrypoint<JourneyEnv, Journe
|
|
|
66
67
|
protected createObserver(_store: JourneyStore): JourneyObserver | undefined;
|
|
67
68
|
/** Override for a different version policy ('warn' by default) or a fully custom invoker (e.g. webhook degradation for hosted tenants). */
|
|
68
69
|
protected createActionInvoker(): ActionInvoker;
|
|
70
|
+
/**
|
|
71
|
+
* Everything one run is wired to. By default it is composed from the methods above; a host
|
|
72
|
+
* that assembles the whole set itself (journeyWorker) overrides this one instead.
|
|
73
|
+
*/
|
|
74
|
+
protected createRunParts(store: JourneyStore, params: JourneyParams): JourneyRunParts;
|
|
69
75
|
run(event: WorkflowEvent<JourneyParams>, step: WorkflowStep): Promise<JourneyOutcome>;
|
|
70
|
-
private execute;
|
|
71
76
|
}
|
|
72
77
|
//#endregion
|
|
73
78
|
//# sourceMappingURL=runner.d.mts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"runner.d.mts","names":[],"sources":["../../src/cloudflare/runner.ts"],"mappings":"
|
|
1
|
+
{"version":3,"file":"runner.d.mts","names":[],"sources":["../../src/cloudflare/runner.ts"],"mappings":";;;;;;;;;;;;;;;qBAuGa,sBAAsB,mBAAmB,YAAY;;;;;;;YAOtD,eAAe;;YAKT,WAAW,QAAQ,eAAe;;;;;;;;;;;;YAaxC,QAAQ,SAAS,gBAAgB;;;;;;YASjC,uBAAuB;;;;;;;;YAavB,oBAAoB;;;;;YAQpB,YAAY;;;;;;YASZ,0BAA0B;;;;;YAQ1B,eAAe,QAAQ,eAAe;;YAKtC,uBAAuB;;;;;YAQvB,eAAe,OAAO,cAAc,QAAQ,gBAAgB;EAahE,IAAI,OAAO,cAAc,gBAAgB,MAAM,eAAe,QAAQ"}
|
|
@@ -1,13 +1,9 @@
|
|
|
1
|
-
import { ContractValidationError, checkEvent } from "../contract.mjs";
|
|
2
1
|
import { resolveRuntime } from "../runtime.mjs";
|
|
3
2
|
import { WAKE_EVENT_TYPE, wakeExpired } from "./bindings.mjs";
|
|
4
|
-
import { JourneyFactSource } from "../store/facts.mjs";
|
|
5
3
|
import { d1Store } from "./d1-env.mjs";
|
|
6
|
-
import {
|
|
7
|
-
import { LogMessageSender, WebhookMessageSender } from "./senders.mjs";
|
|
4
|
+
import { executeJourney } from "../host/execute.mjs";
|
|
8
5
|
import { RegistryActionInvoker } from "../engine/actions.mjs";
|
|
9
|
-
import {
|
|
10
|
-
import { scaleDurations } from "../engine/time-scale.mjs";
|
|
6
|
+
import { LogMessageSender, WebhookMessageSender } from "../host/senders.mjs";
|
|
11
7
|
import { WorkflowEntrypoint } from "cloudflare:workers";
|
|
12
8
|
import { NonRetryableError } from "cloudflare:workflows";
|
|
13
9
|
//#region src/cloudflare/runner.ts
|
|
@@ -72,13 +68,6 @@ var CfEngineStep = class {
|
|
|
72
68
|
}
|
|
73
69
|
}
|
|
74
70
|
};
|
|
75
|
-
/** Development aid: echo each outbound message before handing it to the real sender. */
|
|
76
|
-
function logging(sender) {
|
|
77
|
-
return { async send(message) {
|
|
78
|
-
console.log(`[journey] ${message.channel} ${message.template} → ${message.recipient ?? message.userId} ${JSON.stringify(message.props)}`);
|
|
79
|
-
return sender.send(message);
|
|
80
|
-
} };
|
|
81
|
-
}
|
|
82
71
|
/**
|
|
83
72
|
* The generic journey executor: one class runs every workflow, because IR is
|
|
84
73
|
* data. It is loaded from the store by the contentHash pinned at entry —
|
|
@@ -113,7 +102,7 @@ var JourneyRunner = class extends WorkflowEntrypoint {
|
|
|
113
102
|
}
|
|
114
103
|
/**
|
|
115
104
|
* Message outlet; an app overrides this to plug in real channels (say
|
|
116
|
-
*
|
|
105
|
+
* an EmailSender over a transport with a react-email renderer). By default it picks the webhook
|
|
117
106
|
* or logging sender based on the environment.
|
|
118
107
|
*/
|
|
119
108
|
createMessageSender() {
|
|
@@ -149,73 +138,35 @@ var JourneyRunner = class extends WorkflowEntrypoint {
|
|
|
149
138
|
createActionInvoker() {
|
|
150
139
|
return new RegistryActionInvoker(this.actions());
|
|
151
140
|
}
|
|
141
|
+
/**
|
|
142
|
+
* Everything one run is wired to. By default it is composed from the methods above; a host
|
|
143
|
+
* that assembles the whole set itself (journeyWorker) overrides this one instead.
|
|
144
|
+
*/
|
|
145
|
+
createRunParts(store, params) {
|
|
146
|
+
return {
|
|
147
|
+
store,
|
|
148
|
+
env: this.env,
|
|
149
|
+
runtime: this.runtime(params),
|
|
150
|
+
messages: this.createMessageSender(),
|
|
151
|
+
actions: this.createActionInvoker(),
|
|
152
|
+
contract: this.contract(),
|
|
153
|
+
validator: this.createMessageValidator(),
|
|
154
|
+
observer: this.createObserver(store)
|
|
155
|
+
};
|
|
156
|
+
}
|
|
152
157
|
async run(event, step) {
|
|
153
158
|
const store = this.createStore();
|
|
154
159
|
try {
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
await this.closeStore(store);
|
|
158
|
-
}
|
|
159
|
-
}
|
|
160
|
-
async execute(event, step, store) {
|
|
161
|
-
const { workflowId, contentHash, userId } = event.payload;
|
|
162
|
-
const env = this.env;
|
|
163
|
-
const stored = await store.getWorkflowIR(contentHash);
|
|
164
|
-
if (stored === void 0) throw new Error(`WorkflowIR not found: ${workflowId}@${contentHash}`);
|
|
165
|
-
const runtime = resolveRuntime({ current: this.runtime(event.payload) }, "current");
|
|
166
|
-
const ir = scaleDurations(stored, runtime.timeScale);
|
|
167
|
-
const observer = this.createObserver(store);
|
|
168
|
-
const finished = (status, convertedAt) => observer?.finished?.({
|
|
169
|
-
workflow: workflowId,
|
|
170
|
-
instanceId: event.instanceId,
|
|
171
|
-
userId,
|
|
172
|
-
status,
|
|
173
|
-
at: Date.now(),
|
|
174
|
-
...convertedAt === void 0 ? {} : { convertedAt }
|
|
175
|
-
});
|
|
176
|
-
const messages = runtime.logMessages ? logging(this.createMessageSender()) : this.createMessageSender();
|
|
177
|
-
const contract = this.contract();
|
|
178
|
-
const events = { emit: async (name, payload, id) => {
|
|
179
|
-
let accepted = payload;
|
|
180
|
-
if (contract !== void 0) {
|
|
181
|
-
const verdict = checkEvent(contract, name, payload);
|
|
182
|
-
if (!verdict.ok) throw new ContractValidationError(name, verdict.issues);
|
|
183
|
-
accepted = verdict.data;
|
|
184
|
-
}
|
|
185
|
-
await ingestEvent(env, {
|
|
186
|
-
id,
|
|
187
|
-
userId,
|
|
188
|
-
event: name,
|
|
189
|
-
payload: accepted
|
|
190
|
-
}, store);
|
|
191
|
-
} };
|
|
192
|
-
let outcome;
|
|
193
|
-
try {
|
|
194
|
-
outcome = await runJourney(ir, {
|
|
195
|
-
userId,
|
|
160
|
+
const { userId } = event.payload;
|
|
161
|
+
return await executeJourney({
|
|
196
162
|
instanceId: event.instanceId,
|
|
163
|
+
params: event.payload,
|
|
197
164
|
enteredAtMs: event.timestamp.getTime(),
|
|
198
|
-
step: new CfEngineStep(step, store, userId, event.instanceId)
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
actions: this.createActionInvoker(),
|
|
203
|
-
validator: this.createMessageValidator(),
|
|
204
|
-
observer
|
|
205
|
-
});
|
|
206
|
-
} catch (error) {
|
|
207
|
-
await step.do("finalize:failed", async () => {
|
|
208
|
-
await store.setEntryStatus(event.instanceId, "failed");
|
|
209
|
-
await finished("failed");
|
|
210
|
-
});
|
|
211
|
-
throw error;
|
|
165
|
+
step: new CfEngineStep(step, store, userId, event.instanceId)
|
|
166
|
+
}, this.createRunParts(store, event.payload));
|
|
167
|
+
} finally {
|
|
168
|
+
await this.closeStore(store);
|
|
212
169
|
}
|
|
213
|
-
await step.do("finalize", async () => {
|
|
214
|
-
await store.setEntryStatus(event.instanceId, outcome.status);
|
|
215
|
-
await store.unsubscribe(event.instanceId);
|
|
216
|
-
await finished(outcome.status, outcome.convertedAt);
|
|
217
|
-
});
|
|
218
|
-
return outcome;
|
|
219
170
|
}
|
|
220
171
|
};
|
|
221
172
|
//#endregion
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"runner.mjs","names":[],"sources":["../../src/cloudflare/runner.ts"],"sourcesContent":["import { WorkflowEntrypoint, type WorkflowEvent, type WorkflowStep } from 'cloudflare:workers';\nimport { NonRetryableError } from 'cloudflare:workflows';\nimport { type Contract, ContractValidationError, checkEvent } from '../contract';\nimport { type RegisteredAction, RegistryActionInvoker } from '../engine/actions';\nimport { runJourney } from '../engine/interpreter';\nimport type {\n ActionInvoker,\n EngineStep,\n EventSink,\n JourneyObserver,\n JourneyOutcome,\n MessageSender,\n MessageValidator,\n} from '../engine/ports';\nimport { scaleDurations } from '../engine/time-scale';\nimport { type JourneyRuntimeOptions, resolveRuntime } from '../runtime';\nimport { JourneyFactSource } from '../store/facts';\nimport type { JourneyStore } from '../store/index';\nimport { type JourneyEnv, type JourneyParams, WAKE_EVENT_TYPE, wakeExpired } from './bindings';\nimport { d1Store } from './d1-env';\nimport { ingestEvent } from './router';\nimport { LogMessageSender, WebhookMessageSender } from './senders';\n\n/**\n * Adapts CF's WorkflowStep to EngineStep.\n * subscribe/unsubscribe write the subscription table inside durable steps (the\n * router wakes instances from that table); waitForWake parks on CF's\n * waitForEvent, which signals a timeout by throwing — and so does every other\n * failure in there, so only the clock can tell them apart (see wakeExpired).\n */\n/** Refusals a retry would only repeat: a template's props, an emitted event's payload. */\nconst NON_RETRYABLE = new Set(['TemplateValidationError', 'ContractValidationError']);\n\nclass CfEngineStep implements EngineStep {\n constructor(\n private readonly step: WorkflowStep,\n private readonly store: JourneyStore,\n private readonly userId: string,\n private readonly instanceId: string\n ) {}\n\n /**\n * A template refusing its props, or the contract refusing an emitted event,\n * is deterministic — a retry hits the same refusal — so it ends the step at\n * once instead of burning the retry budget.\n */\n do<T>(name: string, fn: () => Promise<T>): Promise<T> {\n return this.step.do(name, async () => {\n try {\n return await fn();\n } catch (error) {\n if (error instanceof Error && NON_RETRYABLE.has(error.name)) {\n throw new NonRetryableError(error.message, error.name);\n }\n throw error;\n }\n });\n }\n\n sleep(name: string, ms: number): Promise<void> {\n return this.step.sleep(name, ms);\n }\n\n sleepUntil(name: string, timestampMs: number): Promise<void> {\n return this.step.sleepUntil(name, timestampMs);\n }\n\n subscribe(name: string, events: readonly string[]): Promise<void> {\n // Idempotent in the store: the interpreter re-subscribes on every wait\n // attempt (one-shot-callback contract) and registrations persist here.\n return this.step.do(name, () =>\n this.store.subscribe(this.instanceId, this.userId, events, Date.now())\n );\n }\n\n unsubscribe(name: string): Promise<void> {\n return this.step.do(name, () => this.store.unsubscribe(this.instanceId));\n }\n\n async waitForWake(name: string, timeoutMs: number): Promise<'event' | 'timeout'> {\n const startedAt = Date.now();\n try {\n await this.step.waitForEvent(name, { type: WAKE_EVENT_TYPE, timeout: timeoutMs });\n return 'event';\n } catch (error) {\n /*\n * Only the deadline can tell a real timeout from a failure (see\n * wakeExpired). `startedAt` is not replay-stable, but it is read only\n * inside the step body below, and a step body runs on the live attempt\n * alone — a replay returns the persisted verdict without touching the\n * clock.\n */\n const timedOut = await this.step.do(`${name}:timed-out`, async () =>\n wakeExpired(startedAt, timeoutMs, Date.now())\n );\n if (timedOut) return 'timeout';\n throw error;\n }\n }\n}\n\n/** Development aid: echo each outbound message before handing it to the real sender. */\nfunction logging(sender: MessageSender): MessageSender {\n return {\n async send(message) {\n console.log(\n `[journey] ${message.channel} ${message.template} → ${message.recipient ?? message.userId} ${JSON.stringify(message.props)}`\n );\n return sender.send(message);\n },\n };\n}\n\n/**\n * The generic journey executor: one class runs every workflow, because IR is\n * data. It is loaded from the store by the contentHash pinned at entry —\n * content addressing makes it immutable, so reading it outside a step is\n * replay-safe.\n */\nexport class JourneyRunner extends WorkflowEntrypoint<JourneyEnv, JourneyParams> {\n /**\n * The data plane. D1 + KV from the env by default; an app on Postgres\n * overrides this (and `closeStore` if its client needs closing):\n *\n * protected override createStore() { return new PostgresJourneyStore(client(this.env)); }\n */\n protected createStore(): JourneyStore {\n return d1Store(this.env);\n }\n\n /** Called once `run` is over, success or not — release whatever `createStore` opened. */\n protected async closeStore(_store: JourneyStore): Promise<void> {}\n\n /**\n * Runtime options for this instance — typed configuration, resolved by the app (typically\n * `resolveRuntime(config.runtime, mode)` over its workflow.config.ts, with the mode coming from\n * an ENVIRONMENT var). The instance params are passed so the decision can also be per user:\n * a tester account in production can run in fast-forward while everyone else runs in real\n * time. Defaults: real time, quiet.\n *\n * protected override runtime({ userId }) {\n * return resolveRuntime(workflowConfig.runtime, isTester(userId) ? 'test' : this.env.ENVIRONMENT);\n * }\n */\n protected runtime(_params: JourneyParams): JourneyRuntimeOptions {\n return resolveRuntime(undefined, 'production');\n }\n\n /**\n * Message outlet; an app overrides this to plug in real channels (say\n * CfEmailSender with a react-email renderer). By default it picks the webhook\n * or logging sender based on the environment.\n */\n protected createMessageSender(): MessageSender {\n return this.env.MESSAGE_WEBHOOK_URL\n ? new WebhookMessageSender(this.env.MESSAGE_WEBHOOK_URL)\n : new LogMessageSender();\n }\n\n /**\n * Custom-action registry; an app overrides this returning the same\n * `action(...)` objects its workflows reference (an ActionRef is a\n * RegisteredAction structurally) — the code plane of the dual-plane model:\n *\n * protected override actions() { return [syncCrm, issueCoupon]; }\n */\n protected actions(): readonly RegisteredAction[] {\n return [];\n }\n\n /**\n * The contract events this journey emits (`.sendEvent()`) are checked\n * against, exactly like ingest; journeyWorker passes its config's.\n */\n protected contract(): Contract | undefined {\n return undefined;\n }\n\n /**\n * Template props check before every send; an app overrides this with\n * `registryValidator({ emails, pushes })` (journeyWorker does it from its\n * registries). By default props go out as resolved.\n */\n protected createMessageValidator(): MessageValidator | undefined {\n return undefined;\n }\n\n /**\n * Reporting hooks: node arrivals, every copy sent, how each instance ended. The host stores\n * what it needs for its dashboards; by default nothing is reported.\n */\n protected createObserver(_store: JourneyStore): JourneyObserver | undefined {\n return undefined;\n }\n\n /** Override for a different version policy ('warn' by default) or a fully custom invoker (e.g. webhook degradation for hosted tenants). */\n protected createActionInvoker(): ActionInvoker {\n return new RegistryActionInvoker(this.actions());\n }\n\n async run(event: WorkflowEvent<JourneyParams>, step: WorkflowStep): Promise<JourneyOutcome> {\n const store = this.createStore();\n try {\n return await this.execute(event, step, store);\n } finally {\n await this.closeStore(store);\n }\n }\n\n private async execute(\n event: WorkflowEvent<JourneyParams>,\n step: WorkflowStep,\n store: JourneyStore\n ): Promise<JourneyOutcome> {\n const { workflowId, contentHash, userId } = event.payload;\n const env = this.env;\n\n const stored = await store.getWorkflowIR(contentHash);\n if (stored === undefined) {\n throw new Error(`WorkflowIR not found: ${workflowId}@${contentHash}`);\n }\n const runtime = resolveRuntime({ current: this.runtime(event.payload) }, 'current');\n // Development runs the journey in fast-forward; the pinned hash still names the version.\n const ir = scaleDurations(stored, runtime.timeScale);\n\n const observer = this.createObserver(store);\n const finished = (status: JourneyOutcome['status'] | 'failed', convertedAt?: number) =>\n observer?.finished?.({\n workflow: workflowId,\n instanceId: event.instanceId,\n userId,\n status,\n at: Date.now(),\n ...(convertedAt === undefined ? {} : { convertedAt }),\n });\n\n const messages = runtime.logMessages\n ? logging(this.createMessageSender())\n : this.createMessageSender();\n\n // send_event feeds straight back into the router logic — an in-Worker call\n // forming the event edge between workflows. The step's identity travels as\n // the event id, so a retried emit records the occurrence once.\n // An emitted event passes the same contract check as one arriving over HTTP.\n const contract = this.contract();\n const events: EventSink = {\n emit: async (name, payload, id) => {\n let accepted: Record<string, unknown> = payload;\n if (contract !== undefined) {\n const verdict = checkEvent(contract, name, payload);\n if (!verdict.ok) throw new ContractValidationError(name, verdict.issues);\n accepted = verdict.data;\n }\n await ingestEvent(env, { id, userId, event: name, payload: accepted }, store);\n },\n };\n\n let outcome: JourneyOutcome;\n try {\n outcome = await runJourney(ir, {\n userId,\n instanceId: event.instanceId,\n // Trigger-event creation time: replay-stable, unlike Date.now() here\n enteredAtMs: event.timestamp.getTime(),\n step: new CfEngineStep(step, store, userId, event.instanceId),\n facts: new JourneyFactSource(store, userId),\n messages,\n events,\n actions: this.createActionInvoker(),\n validator: this.createMessageValidator(),\n observer,\n });\n } catch (error) {\n /*\n * An error escaping run() ends the instance, and the ledger is the only\n * place anyone looks afterwards: leaving it at 'running' meant a dead\n * journey stayed indistinguishable from a waiting one, in the dashboard\n * and in the once-policy, forever.\n *\n * Subscriptions are deliberately left in place. They are cleaned up on\n * every ordinary ending, but here the wake rows are the only evidence of\n * what the instance was waiting for, and dropping them would also break\n * the instance if the platform does re-run it.\n */\n await step.do('finalize:failed', async () => {\n await store.setEntryStatus(event.instanceId, 'failed');\n await finished('failed');\n });\n throw error;\n }\n\n await step.do('finalize', async () => {\n await store.setEntryStatus(event.instanceId, outcome.status);\n await store.unsubscribe(event.instanceId);\n await finished(outcome.status, outcome.convertedAt);\n });\n\n return outcome;\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"runner.mjs","names":[],"sources":["../../src/cloudflare/runner.ts"],"sourcesContent":["import { WorkflowEntrypoint, type WorkflowEvent, type WorkflowStep } from 'cloudflare:workers';\nimport { NonRetryableError } from 'cloudflare:workflows';\nimport type { Contract } from '../contract';\nimport { type RegisteredAction, RegistryActionInvoker } from '../engine/actions';\nimport type {\n ActionInvoker,\n EngineStep,\n JourneyObserver,\n JourneyOutcome,\n MessageSender,\n MessageValidator,\n} from '../engine/ports';\nimport { type JourneyRunParts, executeJourney } from '../host/execute';\nimport { LogMessageSender, WebhookMessageSender } from '../host/senders';\nimport { type JourneyRuntimeOptions, resolveRuntime } from '../runtime';\nimport type { JourneyStore } from '../store/index';\nimport { type JourneyEnv, type JourneyParams, WAKE_EVENT_TYPE, wakeExpired } from './bindings';\nimport { d1Store } from './d1-env';\n\n/**\n * Adapts CF's WorkflowStep to EngineStep.\n * subscribe/unsubscribe write the subscription table inside durable steps (the\n * router wakes instances from that table); waitForWake parks on CF's\n * waitForEvent, which signals a timeout by throwing — and so does every other\n * failure in there, so only the clock can tell them apart (see wakeExpired).\n */\n/** Refusals a retry would only repeat: a template's props, an emitted event's payload. */\nconst NON_RETRYABLE = new Set(['TemplateValidationError', 'ContractValidationError']);\n\nclass CfEngineStep implements EngineStep {\n constructor(\n private readonly step: WorkflowStep,\n private readonly store: JourneyStore,\n private readonly userId: string,\n private readonly instanceId: string\n ) {}\n\n /**\n * A template refusing its props, or the contract refusing an emitted event,\n * is deterministic — a retry hits the same refusal — so it ends the step at\n * once instead of burning the retry budget.\n */\n do<T>(name: string, fn: () => Promise<T>): Promise<T> {\n return this.step.do(name, async () => {\n try {\n return await fn();\n } catch (error) {\n if (error instanceof Error && NON_RETRYABLE.has(error.name)) {\n throw new NonRetryableError(error.message, error.name);\n }\n throw error;\n }\n });\n }\n\n sleep(name: string, ms: number): Promise<void> {\n return this.step.sleep(name, ms);\n }\n\n sleepUntil(name: string, timestampMs: number): Promise<void> {\n return this.step.sleepUntil(name, timestampMs);\n }\n\n subscribe(name: string, events: readonly string[]): Promise<void> {\n // Idempotent in the store: the interpreter re-subscribes on every wait\n // attempt (one-shot-callback contract) and registrations persist here.\n return this.step.do(name, () =>\n this.store.subscribe(this.instanceId, this.userId, events, Date.now())\n );\n }\n\n unsubscribe(name: string): Promise<void> {\n return this.step.do(name, () => this.store.unsubscribe(this.instanceId));\n }\n\n async waitForWake(name: string, timeoutMs: number): Promise<'event' | 'timeout'> {\n const startedAt = Date.now();\n try {\n await this.step.waitForEvent(name, { type: WAKE_EVENT_TYPE, timeout: timeoutMs });\n return 'event';\n } catch (error) {\n /*\n * Only the deadline can tell a real timeout from a failure (see\n * wakeExpired). `startedAt` is not replay-stable, but it is read only\n * inside the step body below, and a step body runs on the live attempt\n * alone — a replay returns the persisted verdict without touching the\n * clock.\n */\n const timedOut = await this.step.do(`${name}:timed-out`, async () =>\n wakeExpired(startedAt, timeoutMs, Date.now())\n );\n if (timedOut) return 'timeout';\n throw error;\n }\n }\n}\n\n/**\n * The generic journey executor: one class runs every workflow, because IR is\n * data. It is loaded from the store by the contentHash pinned at entry —\n * content addressing makes it immutable, so reading it outside a step is\n * replay-safe.\n */\nexport class JourneyRunner extends WorkflowEntrypoint<JourneyEnv, JourneyParams> {\n /**\n * The data plane. D1 + KV from the env by default; an app on Postgres\n * overrides this (and `closeStore` if its client needs closing):\n *\n * protected override createStore() { return new PostgresJourneyStore(client(this.env)); }\n */\n protected createStore(): JourneyStore {\n return d1Store(this.env);\n }\n\n /** Called once `run` is over, success or not — release whatever `createStore` opened. */\n protected async closeStore(_store: JourneyStore): Promise<void> {}\n\n /**\n * Runtime options for this instance — typed configuration, resolved by the app (typically\n * `resolveRuntime(config.runtime, mode)` over its workflow.config.ts, with the mode coming from\n * an ENVIRONMENT var). The instance params are passed so the decision can also be per user:\n * a tester account in production can run in fast-forward while everyone else runs in real\n * time. Defaults: real time, quiet.\n *\n * protected override runtime({ userId }) {\n * return resolveRuntime(workflowConfig.runtime, isTester(userId) ? 'test' : this.env.ENVIRONMENT);\n * }\n */\n protected runtime(_params: JourneyParams): JourneyRuntimeOptions {\n return resolveRuntime(undefined, 'production');\n }\n\n /**\n * Message outlet; an app overrides this to plug in real channels (say\n * an EmailSender over a transport with a react-email renderer). By default it picks the webhook\n * or logging sender based on the environment.\n */\n protected createMessageSender(): MessageSender {\n return this.env.MESSAGE_WEBHOOK_URL\n ? new WebhookMessageSender(this.env.MESSAGE_WEBHOOK_URL)\n : new LogMessageSender();\n }\n\n /**\n * Custom-action registry; an app overrides this returning the same\n * `action(...)` objects its workflows reference (an ActionRef is a\n * RegisteredAction structurally) — the code plane of the dual-plane model:\n *\n * protected override actions() { return [syncCrm, issueCoupon]; }\n */\n protected actions(): readonly RegisteredAction[] {\n return [];\n }\n\n /**\n * The contract events this journey emits (`.sendEvent()`) are checked\n * against, exactly like ingest; journeyWorker passes its config's.\n */\n protected contract(): Contract | undefined {\n return undefined;\n }\n\n /**\n * Template props check before every send; an app overrides this with\n * `registryValidator({ emails, pushes })` (journeyWorker does it from its\n * registries). By default props go out as resolved.\n */\n protected createMessageValidator(): MessageValidator | undefined {\n return undefined;\n }\n\n /**\n * Reporting hooks: node arrivals, every copy sent, how each instance ended. The host stores\n * what it needs for its dashboards; by default nothing is reported.\n */\n protected createObserver(_store: JourneyStore): JourneyObserver | undefined {\n return undefined;\n }\n\n /** Override for a different version policy ('warn' by default) or a fully custom invoker (e.g. webhook degradation for hosted tenants). */\n protected createActionInvoker(): ActionInvoker {\n return new RegistryActionInvoker(this.actions());\n }\n\n /**\n * Everything one run is wired to. By default it is composed from the methods above; a host\n * that assembles the whole set itself (journeyWorker) overrides this one instead.\n */\n protected createRunParts(store: JourneyStore, params: JourneyParams): JourneyRunParts {\n return {\n store,\n env: this.env,\n runtime: this.runtime(params),\n messages: this.createMessageSender(),\n actions: this.createActionInvoker(),\n contract: this.contract(),\n validator: this.createMessageValidator(),\n observer: this.createObserver(store),\n };\n }\n\n async run(event: WorkflowEvent<JourneyParams>, step: WorkflowStep): Promise<JourneyOutcome> {\n const store = this.createStore();\n try {\n const { userId } = event.payload;\n return await executeJourney(\n {\n instanceId: event.instanceId,\n params: event.payload,\n // Trigger-event creation time: replay-stable, unlike Date.now() here\n enteredAtMs: event.timestamp.getTime(),\n step: new CfEngineStep(step, store, userId, event.instanceId),\n },\n this.createRunParts(store, event.payload)\n );\n } finally {\n await this.closeStore(store);\n }\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;AA2BA,MAAM,gCAAgB,IAAI,IAAI,CAAC,2BAA2B,yBAAyB,CAAC;AAEpF,IAAM,eAAN,MAAyC;CAEpB;CACA;CACA;CACA;CAJnB,YACE,MACA,OACA,QACA,YACA;EAJiB,KAAA,OAAA;EACA,KAAA,QAAA;EACA,KAAA,SAAA;EACA,KAAA,aAAA;CAChB;;;;;;CAOH,GAAM,MAAc,IAAkC;EACpD,OAAO,KAAK,KAAK,GAAG,MAAM,YAAY;GACpC,IAAI;IACF,OAAO,MAAM,GAAG;GAClB,SAAS,OAAO;IACd,IAAI,iBAAiB,SAAS,cAAc,IAAI,MAAM,IAAI,GACxD,MAAM,IAAI,kBAAkB,MAAM,SAAS,MAAM,IAAI;IAEvD,MAAM;GACR;EACF,CAAC;CACH;CAEA,MAAM,MAAc,IAA2B;EAC7C,OAAO,KAAK,KAAK,MAAM,MAAM,EAAE;CACjC;CAEA,WAAW,MAAc,aAAoC;EAC3D,OAAO,KAAK,KAAK,WAAW,MAAM,WAAW;CAC/C;CAEA,UAAU,MAAc,QAA0C;EAGhE,OAAO,KAAK,KAAK,GAAG,YAClB,KAAK,MAAM,UAAU,KAAK,YAAY,KAAK,QAAQ,QAAQ,KAAK,IAAI,CAAC,CACvE;CACF;CAEA,YAAY,MAA6B;EACvC,OAAO,KAAK,KAAK,GAAG,YAAY,KAAK,MAAM,YAAY,KAAK,UAAU,CAAC;CACzE;CAEA,MAAM,YAAY,MAAc,WAAiD;EAC/E,MAAM,YAAY,KAAK,IAAI;EAC3B,IAAI;GACF,MAAM,KAAK,KAAK,aAAa,MAAM;IAAE,MAAM;IAAiB,SAAS;GAAU,CAAC;GAChF,OAAO;EACT,SAAS,OAAO;GAWd,IAAI,MAHmB,KAAK,KAAK,GAAG,GAAG,KAAK,aAAa,YACvD,YAAY,WAAW,WAAW,KAAK,IAAI,CAAC,CAC9C,GACc,OAAO;GACrB,MAAM;EACR;CACF;AACF;;;;;;;AAQA,IAAa,gBAAb,cAAmC,mBAA8C;;;;;;;CAO/E,cAAsC;EACpC,OAAO,QAAQ,KAAK,GAAG;CACzB;;CAGA,MAAgB,WAAW,QAAqC,CAAC;;;;;;;;;;;;CAajE,QAAkB,SAA+C;EAC/D,OAAO,eAAe,KAAA,GAAW,YAAY;CAC/C;;;;;;CAOA,sBAA+C;EAC7C,OAAO,KAAK,IAAI,sBACZ,IAAI,qBAAqB,KAAK,IAAI,mBAAmB,IACrD,IAAI,iBAAiB;CAC3B;;;;;;;;CASA,UAAiD;EAC/C,OAAO,CAAC;CACV;;;;;CAMA,WAA2C,CAE3C;;;;;;CAOA,yBAAiE,CAEjE;;;;;CAMA,eAAyB,QAAmD,CAE5E;;CAGA,sBAA+C;EAC7C,OAAO,IAAI,sBAAsB,KAAK,QAAQ,CAAC;CACjD;;;;;CAMA,eAAyB,OAAqB,QAAwC;EACpF,OAAO;GACL;GACA,KAAK,KAAK;GACV,SAAS,KAAK,QAAQ,MAAM;GAC5B,UAAU,KAAK,oBAAoB;GACnC,SAAS,KAAK,oBAAoB;GAClC,UAAU,KAAK,SAAS;GACxB,WAAW,KAAK,uBAAuB;GACvC,UAAU,KAAK,eAAe,KAAK;EACrC;CACF;CAEA,MAAM,IAAI,OAAqC,MAA6C;EAC1F,MAAM,QAAQ,KAAK,YAAY;EAC/B,IAAI;GACF,MAAM,EAAE,WAAW,MAAM;GACzB,OAAO,MAAM,eACX;IACE,YAAY,MAAM;IAClB,QAAQ,MAAM;IAEd,aAAa,MAAM,UAAU,QAAQ;IACrC,MAAM,IAAI,aAAa,MAAM,OAAO,QAAQ,MAAM,UAAU;GAC9D,GACA,KAAK,eAAe,OAAO,MAAM,OAAO,CAC1C;EACF,UAAU;GACR,MAAM,KAAK,WAAW,KAAK;EAC7B;CACF;AACF"}
|
|
@@ -1,21 +1,6 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
-
const
|
|
3
|
-
const require_email = require("../email.cjs");
|
|
4
|
-
const require_templates = require("../templates.cjs");
|
|
5
|
-
const require_config = require("../config.cjs");
|
|
6
|
-
const require_contract = require("../contract.cjs");
|
|
7
|
-
require("../store/tables.cjs");
|
|
8
|
-
const require_react_email = require("../react-email.cjs");
|
|
9
|
-
const require_reporting_tracking = require("../reporting/tracking.cjs");
|
|
10
|
-
const require_store_index = require("../store/index.cjs");
|
|
11
|
-
const require_cloudflare_d1_env = require("./d1-env.cjs");
|
|
12
|
-
const require_cloudflare_router = require("./router.cjs");
|
|
13
|
-
const require_cloudflare_channels = require("./channels.cjs");
|
|
14
|
-
const require_engine_time_scale = require("../engine/time-scale.cjs");
|
|
2
|
+
const require_host_journey_host = require("../host/journey-host.cjs");
|
|
15
3
|
const require_cloudflare_runner = require("./runner.cjs");
|
|
16
|
-
const require_reporting_observer = require("../reporting/observer.cjs");
|
|
17
|
-
let zod_mini = require("zod/mini");
|
|
18
|
-
zod_mini = require_runtime.__toESM(zod_mini, 1);
|
|
19
4
|
//#region src/cloudflare/worker.ts
|
|
20
5
|
/**
|
|
21
6
|
* A complete journey host in one call: the runner class with the app's store,
|
|
@@ -29,227 +14,61 @@ zod_mini = require_runtime.__toESM(zod_mini, 1);
|
|
|
29
14
|
* export const MyRunner = journeys.Runner;
|
|
30
15
|
* export default { fetch: journeys.fetch };
|
|
31
16
|
*/
|
|
32
|
-
/** The Worker's environment failed its schema (`journeyWorker({ env })`). */
|
|
33
|
-
var EnvValidationError = class extends Error {
|
|
34
|
-
issues;
|
|
35
|
-
name = "EnvValidationError";
|
|
36
|
-
constructor(issues) {
|
|
37
|
-
super(`the Worker's environment is invalid:\n ${issues.join("\n ")}`);
|
|
38
|
-
this.issues = issues;
|
|
39
|
-
}
|
|
40
|
-
};
|
|
41
17
|
function journeyWorker(options) {
|
|
42
|
-
const
|
|
43
|
-
const emailIds = require_templates.indexTemplates("email", config.templates?.email);
|
|
44
|
-
require_email.assertEmailConfig(config, emailIds.values());
|
|
45
|
-
const checked = /* @__PURE__ */ new WeakMap();
|
|
46
|
-
const checkEnv = (env) => {
|
|
47
|
-
if (options.env === void 0) return env;
|
|
48
|
-
const cached = checked.get(env);
|
|
49
|
-
if (cached !== void 0) return cached;
|
|
50
|
-
const result = zod_mini.safeParse(options.env, env);
|
|
51
|
-
if (!result.success) throw new EnvValidationError(result.error.issues.map((issue) => `${issue.path.join(".") || "(root)"}: ${issue.message}`));
|
|
52
|
-
const merged = {
|
|
53
|
-
...env,
|
|
54
|
-
...result.data
|
|
55
|
-
};
|
|
56
|
-
checked.set(env, merged);
|
|
57
|
-
return merged;
|
|
58
|
-
};
|
|
59
|
-
const bundle = () => require_config.compileConfig(config);
|
|
60
|
-
const emails = config.templates?.email;
|
|
61
|
-
const database = { schema: config.database?.schema ?? "workflow" };
|
|
62
|
-
const lease = (env) => {
|
|
63
|
-
const opened = options.store === void 0 ? require_cloudflare_d1_env.d1Store(env) : options.store(env, database);
|
|
64
|
-
const noop = async () => {};
|
|
65
|
-
const factor = require_cloudflare_channels.runtimeFromConfig(config, env).timeScale;
|
|
66
|
-
return "store" in opened ? {
|
|
67
|
-
store: require_engine_time_scale.scaleSegmentWindows(opened.store, factor),
|
|
68
|
-
close: opened.close ?? noop
|
|
69
|
-
} : {
|
|
70
|
-
store: require_engine_time_scale.scaleSegmentWindows(opened, factor),
|
|
71
|
-
close: noop
|
|
72
|
-
};
|
|
73
|
-
};
|
|
18
|
+
const host = require_host_journey_host.createJourneyHost(options);
|
|
74
19
|
class Runner extends require_cloudflare_runner.JourneyRunner {
|
|
75
20
|
leases = /* @__PURE__ */ new WeakMap();
|
|
76
|
-
current;
|
|
77
21
|
createStore() {
|
|
78
|
-
const { store, close } = lease(checkEnv(this.env));
|
|
22
|
+
const { store, close } = host.lease(host.checkEnv(this.env));
|
|
79
23
|
this.leases.set(store, close);
|
|
80
|
-
this.current = store;
|
|
81
24
|
return store;
|
|
82
25
|
}
|
|
83
26
|
async closeStore(store) {
|
|
84
27
|
await this.leases.get(store)?.();
|
|
85
28
|
}
|
|
86
|
-
|
|
87
|
-
return
|
|
88
|
-
}
|
|
89
|
-
createMessageValidator() {
|
|
90
|
-
return require_templates.registryValidator(config.templates ?? {});
|
|
91
|
-
}
|
|
92
|
-
createMessageSender() {
|
|
93
|
-
if (this.current === void 0) return super.createMessageSender();
|
|
94
|
-
if (options.messages !== void 0) return options.messages(checkEnv(this.env), this.current);
|
|
95
|
-
return require_cloudflare_channels.channelsFromConfig(config, checkEnv(this.env), { store: this.current });
|
|
96
|
-
}
|
|
97
|
-
createObserver(store) {
|
|
98
|
-
if (options.observer !== void 0) return options.observer(checkEnv(this.env), store);
|
|
99
|
-
return store.reporting === void 0 ? void 0 : require_reporting_observer.reportingObserver(store.reporting, checkEnv(this.env).WORKFLOW_ORIGIN);
|
|
100
|
-
}
|
|
101
|
-
runtime(params) {
|
|
102
|
-
const env = checkEnv(this.env);
|
|
103
|
-
if (options.runtime !== void 0) return options.runtime(env, params);
|
|
104
|
-
return require_cloudflare_channels.runtimeFromConfig(config, env);
|
|
105
|
-
}
|
|
106
|
-
actions() {
|
|
107
|
-
return config.actions ?? [];
|
|
29
|
+
createRunParts(store, params) {
|
|
30
|
+
return host.runParts(host.checkEnv(this.env), store, params);
|
|
108
31
|
}
|
|
109
32
|
}
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
headers: { "content-type": "application/json" }
|
|
113
|
-
});
|
|
114
|
-
const authorized = (request, env) => env.API_TOKEN === void 0 || request.headers.get("authorization") === `Bearer ${env.API_TOKEN}`;
|
|
115
|
-
const preview = emails === void 0 ? void 0 : require_react_email.registryRenderer(emails, config.email);
|
|
116
|
-
let applying;
|
|
117
|
-
let retryAt = 0;
|
|
118
|
-
const applyOwnBundle = (env) => {
|
|
33
|
+
/** CF's version metadata: the bundle compiled into each new version goes live by itself. */
|
|
34
|
+
const ownVersion = (env) => {
|
|
119
35
|
const metadata = env.CF_VERSION_METADATA;
|
|
120
|
-
if (metadata === void 0
|
|
121
|
-
|
|
36
|
+
if (metadata === void 0) return void 0;
|
|
37
|
+
return {
|
|
122
38
|
id: metadata.id,
|
|
123
39
|
createdAt: Date.parse(metadata.timestamp)
|
|
124
40
|
};
|
|
125
|
-
if (Number.isNaN(version.createdAt)) return void 0;
|
|
126
|
-
if (applying !== void 0 || Date.now() < retryAt) return void 0;
|
|
127
|
-
applying = (async () => {
|
|
128
|
-
const { store, close } = lease(env);
|
|
129
|
-
try {
|
|
130
|
-
if (store.liveBundleVersion === void 0) return;
|
|
131
|
-
const live = await store.liveBundleVersion();
|
|
132
|
-
if (live !== void 0 && (live.id === version.id || live.createdAt >= version.createdAt)) return;
|
|
133
|
-
if ((await require_cloudflare_router.deployBundle(env, bundle(), store, version)).applied) console.log(`[workflow] version ${version.id} is live`);
|
|
134
|
-
} finally {
|
|
135
|
-
await close();
|
|
136
|
-
}
|
|
137
|
-
})().catch((error) => {
|
|
138
|
-
applying = void 0;
|
|
139
|
-
retryAt = Date.now() + 6e4;
|
|
140
|
-
console.error(`[workflow] could not make version ${version.id} live`, error);
|
|
141
|
-
});
|
|
142
|
-
return applying;
|
|
143
41
|
};
|
|
144
42
|
const fetch = async (request, rawEnv, ctx) => {
|
|
145
43
|
let env;
|
|
146
44
|
try {
|
|
147
|
-
env = checkEnv(rawEnv);
|
|
45
|
+
env = host.checkEnv(rawEnv);
|
|
148
46
|
} catch (error) {
|
|
149
|
-
if (!(error instanceof EnvValidationError)) throw error;
|
|
47
|
+
if (!(error instanceof require_host_journey_host.EnvValidationError)) throw error;
|
|
150
48
|
console.error(`[workflow] ${error.message}`);
|
|
151
|
-
return
|
|
49
|
+
return new Response(JSON.stringify({ error: error.message }), {
|
|
50
|
+
status: 500,
|
|
51
|
+
headers: { "content-type": "application/json" }
|
|
52
|
+
});
|
|
152
53
|
}
|
|
153
|
-
const pending = applyOwnBundle(env);
|
|
54
|
+
const pending = host.applyOwnBundle(env, ownVersion(env));
|
|
154
55
|
if (pending !== void 0) {
|
|
155
56
|
if (ctx === void 0) await pending;
|
|
156
57
|
else ctx.waitUntil(pending);
|
|
157
58
|
}
|
|
158
|
-
const
|
|
159
|
-
const { store, close } = lease(env);
|
|
59
|
+
const { store, close } = host.lease(env);
|
|
160
60
|
try {
|
|
161
|
-
|
|
162
|
-
const openedEvent = config.webPush?.openedEvent;
|
|
163
|
-
const tracked = await require_reporting_tracking.handleTracking(request, store.reporting, {
|
|
164
|
-
legacyUnsubscribeUrl: config.email?.unsubscribeUrl,
|
|
165
|
-
product: config.title,
|
|
166
|
-
quickMs: config.email?.tracking?.quickClickMs,
|
|
167
|
-
pushOpened: openedEvent === void 0 ? void 0 : async ({ messageId, userId, at }) => {
|
|
168
|
-
const payload = { message_id: messageId };
|
|
169
|
-
const contract = config.contract;
|
|
170
|
-
const verdict = contract === void 0 ? void 0 : require_contract.checkEvent(contract, openedEvent, payload);
|
|
171
|
-
if (verdict !== void 0 && !verdict.ok) throw new require_contract.ContractValidationError(openedEvent, verdict.issues);
|
|
172
|
-
await require_cloudflare_router.ingestEvent(env, {
|
|
173
|
-
id: `${openedEvent}:${messageId}`,
|
|
174
|
-
userId,
|
|
175
|
-
event: openedEvent,
|
|
176
|
-
payload: verdict?.data ?? payload,
|
|
177
|
-
ts: at
|
|
178
|
-
}, store);
|
|
179
|
-
}
|
|
180
|
-
});
|
|
181
|
-
if (tracked !== void 0) return tracked;
|
|
182
|
-
}
|
|
183
|
-
if (url.pathname === "/segments/sweep") {
|
|
184
|
-
if (!authorized(request, env)) return json({ error: "unauthorized" }, 401);
|
|
185
|
-
if (request.method !== "POST") return json({ error: "method not allowed" }, 405);
|
|
186
|
-
return json(await require_cloudflare_router.sweepSegments(env, store));
|
|
187
|
-
}
|
|
188
|
-
if (url.pathname === "/bundle") {
|
|
189
|
-
if (!authorized(request, env)) return json({ error: "unauthorized" }, 401);
|
|
190
|
-
if (request.method !== "GET") return json({ error: "method not allowed" }, 405);
|
|
191
|
-
return json(bundle());
|
|
192
|
-
}
|
|
193
|
-
if (url.pathname === "/bundle/deploy") {
|
|
194
|
-
if (!authorized(request, env)) return json({ error: "unauthorized" }, 401);
|
|
195
|
-
if (request.method !== "POST") return json({ error: "method not allowed" }, 405);
|
|
196
|
-
try {
|
|
197
|
-
return json(await require_cloudflare_router.deployBundle(env, bundle(), store));
|
|
198
|
-
} catch (error) {
|
|
199
|
-
if (!(error instanceof require_store_index.SchemaMismatchError)) throw error;
|
|
200
|
-
return json({
|
|
201
|
-
error: error.message,
|
|
202
|
-
problems: error.problems
|
|
203
|
-
}, 503);
|
|
204
|
-
}
|
|
205
|
-
}
|
|
206
|
-
if (emails !== void 0 && preview !== void 0 && url.pathname.startsWith("/preview/")) {
|
|
207
|
-
if (!authorized(request, env)) return json({ error: "unauthorized" }, 401);
|
|
208
|
-
if (request.method !== "GET") return json({ error: "method not allowed" }, 405);
|
|
209
|
-
const key = url.pathname.slice(9);
|
|
210
|
-
if (!emailIds.has(key)) return json({ error: `unknown email template: ${key}` }, 404);
|
|
211
|
-
let props = {};
|
|
212
|
-
try {
|
|
213
|
-
props = JSON.parse(url.searchParams.get("props") ?? "{}");
|
|
214
|
-
} catch {
|
|
215
|
-
return json({ error: "props must be a JSON object" }, 400);
|
|
216
|
-
}
|
|
217
|
-
if (typeof props !== "object" || props === null || Array.isArray(props)) return json({ error: "props must be a JSON object" }, 400);
|
|
218
|
-
try {
|
|
219
|
-
const { subject, html } = await preview(key, props);
|
|
220
|
-
return new Response(html, { headers: {
|
|
221
|
-
"content-type": "text/html; charset=utf-8",
|
|
222
|
-
"x-subject": subject
|
|
223
|
-
} });
|
|
224
|
-
} catch (error) {
|
|
225
|
-
if (error instanceof require_templates.TemplateValidationError) return json({
|
|
226
|
-
error: error.message,
|
|
227
|
-
issues: error.issues
|
|
228
|
-
}, 400);
|
|
229
|
-
throw error;
|
|
230
|
-
}
|
|
231
|
-
}
|
|
232
|
-
return await require_cloudflare_router.handleRequest(request, env, store, {
|
|
233
|
-
contract: config.contract,
|
|
234
|
-
mode: options.contractMode
|
|
235
|
-
});
|
|
61
|
+
return await host.handle(request, env, store);
|
|
236
62
|
} finally {
|
|
237
63
|
if (ctx === void 0) await close();
|
|
238
64
|
else ctx.waitUntil(close());
|
|
239
65
|
}
|
|
240
66
|
};
|
|
241
|
-
const sweep =
|
|
242
|
-
const env = checkEnv(rawEnv);
|
|
243
|
-
const { store, close } = lease(env);
|
|
244
|
-
try {
|
|
245
|
-
return await require_cloudflare_router.sweepSegments(env, store);
|
|
246
|
-
} finally {
|
|
247
|
-
await close();
|
|
248
|
-
}
|
|
249
|
-
};
|
|
67
|
+
const sweep = (rawEnv) => host.sweep(host.checkEnv(rawEnv));
|
|
250
68
|
const scheduled = async (_controller, env, ctx) => {
|
|
251
69
|
ctx.waitUntil((async () => {
|
|
252
|
-
|
|
70
|
+
const checked = host.checkEnv(env);
|
|
71
|
+
await host.applyOwnBundle(checked, ownVersion(checked));
|
|
253
72
|
await sweep(env);
|
|
254
73
|
})());
|
|
255
74
|
};
|
|
@@ -261,7 +80,7 @@ function journeyWorker(options) {
|
|
|
261
80
|
};
|
|
262
81
|
}
|
|
263
82
|
//#endregion
|
|
264
|
-
exports.EnvValidationError = EnvValidationError;
|
|
83
|
+
exports.EnvValidationError = require_host_journey_host.EnvValidationError;
|
|
265
84
|
exports.journeyWorker = journeyWorker;
|
|
266
85
|
|
|
267
86
|
//# sourceMappingURL=worker.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"worker.cjs","names":["indexTemplates","z","compileConfig","d1Store","runtimeFromConfig","scaleSegmentWindows","JourneyRunner","registryValidator","channelsFromConfig","reportingObserver","registryRenderer","deployBundle","handleTracking","checkEvent","ContractValidationError","ingestEvent","sweepSegments","SchemaMismatchError","TemplateValidationError","handleRequest"],"sources":["../../src/cloudflare/worker.ts"],"sourcesContent":["import * as z from 'zod/mini';\nimport type * as core from 'zod/v4/core';\nimport { type JourneyConfig, compileConfig } from '../config';\nimport { type Contract, ContractValidationError, checkEvent } from '../contract';\nimport { assertEmailConfig } from '../email';\nimport type { RegisteredAction } from '../engine/actions';\nimport type { JourneyObserver, MessageSender, MessageValidator } from '../engine/ports';\nimport { scaleSegmentWindows } from '../engine/time-scale';\nimport type { ScalarIR } from '../ir';\nimport { registryRenderer } from '../react-email';\nimport { reportingObserver } from '../reporting/observer';\nimport { handleTracking } from '../reporting/tracking';\nimport type { JourneyRuntimeOptions } from '../runtime';\nimport {\n type BundleVersion,\n type JourneyStore,\n SchemaMismatchError,\n type StoreLease,\n} from '../store/index';\nimport { DEFAULT_SCHEMA } from '../store/tables';\nimport { TemplateValidationError, indexTemplates, registryValidator } from '../templates';\nimport type { JourneyEnv, JourneyParams } from './bindings';\nimport { channelsFromConfig, runtimeFromConfig } from './channels';\nimport { d1Store } from './d1-env';\nimport {\n type SweepResult,\n deployBundle,\n handleRequest,\n ingestEvent,\n sweepSegments,\n} from './router';\nimport { JourneyRunner } from './runner';\n\n/** The fetch handler's execution context (the subset used). */\nexport interface ExecutionContextLike {\n waitUntil(promise: Promise<unknown>): void;\n}\n\nexport interface JourneyWorkerOptions<Env extends JourneyEnv> {\n /**\n * The project's `workflow.config.ts` (`defineConfig`): what the Worker\n * deploys (`GET /bundle`, `POST /bundle/deploy` serve `compileConfig`), the\n * template registries the senders, the validator and `/preview` render from,\n * the contract every `/events` and `/identify` is checked against, the email\n * envelope, actions, and the runtime / APNs / FCM settings. Secrets come\n * from the env under the names `JourneyEnv` documents.\n */\n config: JourneyConfig;\n /**\n * The data plane, opened per request and per workflow run. Return a\n * `StoreLease` when the client must be released afterwards (a Postgres\n * connection); defaults to D1 + KV from the env's bindings. `database` is\n * the config's (`schema` defaulting to `workflow`), for a Postgres store:\n *\n * store: (env, database) => postgresJsStore(env.HYPERDRIVE.connectionString, database)\n */\n store?: (env: Env, database: { schema: string }) => JourneyStore | StoreLease;\n /**\n * Reporting hooks: node arrivals, every copy sent (with the tracking it should carry — click\n * links and an open pixel for email, payload data for a push), and how each instance ended.\n * By default a store that keeps reporting (Postgres) records them itself, and the Worker\n * serves the email tracking routes (`/t/...`, on `WORKFLOW_ORIGIN`); override to report\n * elsewhere.\n */\n observer?: (env: Env, store: JourneyStore) => JourneyObserver;\n /** Message outlet override (tests, a custom gateway); by default the channels the config describes. */\n messages?: (env: Env, store: JourneyStore) => MessageSender;\n /** Per-instance runtime options override (a tester in fast-forward); by default `config.runtime` for `ENVIRONMENT`. */\n runtime?: (env: Env, params: JourneyParams) => JourneyRuntimeOptions;\n /**\n * The host's environment, as a zod schema over its vars and secrets (bindings pass through\n * untouched): checked the first time the Worker runs, so a missing or malformed variable fails\n * there — every problem listed — not at the send that needs it. Webhook templates read their\n * URL from the parsed values: `webhook: (env: Env) => env.WEBHOOK_DISCORD_ALERTS`.\n */\n env?: core.$ZodType<object>;\n /** 'enforce' (default) answers an event or patch the contract refuses with a 400; 'report' only logs it. */\n contractMode?: 'enforce' | 'report';\n}\n\nexport interface JourneyWorker<Env extends JourneyEnv> {\n /** Export this under the `class_name` of the wrangler `workflows` binding. */\n Runner: new (...args: never[]) => JourneyRunner;\n /** The Worker's fetch handler: `/health`, `/events`, `/identify`, `/devices`, `/messages/opened`, `/segments/sweep`, `/t/*` (email tracking), `/deploy`, `/bundle`, `/bundle/deploy` and `/preview/*`. */\n fetch: (request: Request, env: Env, ctx?: ExecutionContextLike) => Promise<Response>;\n /**\n * The segment sweep (`sweepSegments`): membership of every deployed segment for every known\n * user, the journeys of segments entered by the clock alone (`not(performed(e.login, { within:\n * '7 days' }))` has no event to ride on), and each segment's size. Also served as\n * `POST /segments/sweep`.\n */\n sweep: (env: Env) => Promise<SweepResult>;\n /** The cron trigger's handler: runs the sweep. Export it beside `fetch` with a `triggers.crons` entry. */\n scheduled: (controller: unknown, env: Env, ctx: ExecutionContextLike) => Promise<void>;\n}\n\n/**\n * A complete journey host in one call: the runner class with the app's store,\n * outlets and runtime plugged in, and the fetch handler that serves the ingest\n * API around them.\n *\n * const journeys = journeyWorker<Env>({\n * config, // workflow.config.ts (defineConfig)\n * store: (env, database) => postgresJsStore(env.HYPERDRIVE.connectionString, database),\n * });\n * export const MyRunner = journeys.Runner;\n * export default { fetch: journeys.fetch };\n */\n/** The Worker's environment failed its schema (`journeyWorker({ env })`). */\nexport class EnvValidationError extends Error {\n override readonly name = 'EnvValidationError';\n constructor(readonly issues: readonly string[]) {\n super(`the Worker's environment is invalid:\\n ${issues.join('\\n ')}`);\n }\n}\n\nexport function journeyWorker<Env extends JourneyEnv>(\n options: JourneyWorkerOptions<Env>\n): JourneyWorker<Env> {\n const { config } = options;\n const emailIds = indexTemplates('email', config.templates?.email);\n // A config still on `emails` (its sender would be silently dropped) fails here, not at a send\n assertEmailConfig(config, emailIds.values());\n // Parsed once per env object: the runtime hands the same one to every request of an isolate\n const checked = new WeakMap<object, Env>();\n const checkEnv = (env: Env): Env => {\n if (options.env === undefined) return env;\n const cached = checked.get(env);\n if (cached !== undefined) return cached;\n const result = z.safeParse(options.env, env);\n if (!result.success) {\n // Names and reasons only: the values are secrets\n throw new EnvValidationError(\n result.error.issues.map((issue) => `${issue.path.join('.') || '(root)'}: ${issue.message}`)\n );\n }\n const merged: Env = { ...env, ...result.data };\n checked.set(env, merged);\n return merged;\n };\n const bundle = () => compileConfig(config);\n const emails = config.templates?.email;\n const database = { schema: config.database?.schema ?? DEFAULT_SCHEMA };\n // In fast-forward, segment windows scale with the journeys reading them (time-scale.ts)\n const lease = (env: Env): Required<StoreLease> => {\n const opened = options.store === undefined ? d1Store(env) : options.store(env, database);\n const noop = async () => {};\n const factor = runtimeFromConfig(config, env).timeScale;\n return 'store' in opened\n ? { store: scaleSegmentWindows(opened.store, factor), close: opened.close ?? noop }\n : { store: scaleSegmentWindows(opened, factor), close: noop };\n };\n\n class Runner extends JourneyRunner {\n private readonly leases = new WeakMap<JourneyStore, () => Promise<void>>();\n private current: JourneyStore | undefined;\n\n protected override createStore(): JourneyStore {\n // A run fails at its start on an invalid environment, not at the send that needs it\n const { store, close } = lease(checkEnv(this.env as Env));\n this.leases.set(store, close);\n this.current = store;\n return store;\n }\n\n protected override async closeStore(store: JourneyStore): Promise<void> {\n await this.leases.get(store)?.();\n }\n\n protected override contract(): Contract | undefined {\n return config.contract;\n }\n\n protected override createMessageValidator(): MessageValidator {\n return registryValidator(config.templates ?? {});\n }\n\n protected override createMessageSender(): MessageSender {\n if (this.current === undefined) return super.createMessageSender();\n if (options.messages !== undefined) {\n return options.messages(checkEnv(this.env as Env), this.current);\n }\n return channelsFromConfig(config, checkEnv(this.env as Env), { store: this.current });\n }\n\n protected override createObserver(store: JourneyStore): JourneyObserver | undefined {\n if (options.observer !== undefined) return options.observer(checkEnv(this.env as Env), store);\n return store.reporting === undefined\n ? undefined\n : reportingObserver(store.reporting, checkEnv(this.env as Env).WORKFLOW_ORIGIN);\n }\n\n protected override runtime(params: JourneyParams): JourneyRuntimeOptions {\n const env = checkEnv(this.env as Env);\n if (options.runtime !== undefined) return options.runtime(env, params);\n return runtimeFromConfig(config, env);\n }\n\n protected override actions(): readonly RegisteredAction[] {\n // An action ref is a registered action structurally: the same object carries name, code and hash\n return config.actions ?? [];\n }\n }\n\n const json = (body: unknown, status = 200): Response =>\n new Response(JSON.stringify(body), {\n status,\n headers: { 'content-type': 'application/json' },\n });\n\n const authorized = (request: Request, env: Env): boolean =>\n env.API_TOKEN === undefined ||\n request.headers.get('authorization') === `Bearer ${env.API_TOKEN}`;\n\n const preview = emails === undefined ? undefined : registryRenderer(emails, config.email);\n\n // The bundle compiled into this version goes live on its own, once per isolate: the first\n // request or cron run reads which version's bundle is live (one row) and applies this one only\n // over an older version, so one still serving during a rollout can never put its routes back.\n // A failure (say the migrations have not run) is logged and retried a minute later. Only the\n // request that starts the check gets its promise back: a request context cannot wait on\n // another's I/O.\n let applying: Promise<void> | undefined;\n let retryAt = 0;\n const applyOwnBundle = (env: Env): Promise<void> | undefined => {\n const metadata = env.CF_VERSION_METADATA;\n if (metadata === undefined || metadata.id === '') return undefined;\n const version: BundleVersion = { id: metadata.id, createdAt: Date.parse(metadata.timestamp) };\n if (Number.isNaN(version.createdAt)) return undefined;\n if (applying !== undefined || Date.now() < retryAt) return undefined;\n applying = (async () => {\n const { store, close } = lease(env);\n try {\n if (store.liveBundleVersion === undefined) return; // D1: POST /bundle/deploy\n const live = await store.liveBundleVersion();\n if (live !== undefined && (live.id === version.id || live.createdAt >= version.createdAt))\n return;\n const result = await deployBundle(env, bundle(), store, version);\n if (result.applied) console.log(`[workflow] version ${version.id} is live`);\n } finally {\n await close();\n }\n })().catch((error: unknown) => {\n applying = undefined;\n retryAt = Date.now() + 60_000;\n console.error(`[workflow] could not make version ${version.id} live`, error);\n });\n return applying;\n };\n\n const fetch = async (request: Request, rawEnv: Env, ctx?: ExecutionContextLike) => {\n // Everything below reads the validated environment (the schema's defaults applied)\n let env: Env;\n try {\n env = checkEnv(rawEnv);\n } catch (error) {\n if (!(error instanceof EnvValidationError)) throw error;\n console.error(`[workflow] ${error.message}`);\n return json({ error: error.message }, 500);\n }\n const pending = applyOwnBundle(env);\n // In the background where the runtime has waitUntil; inline otherwise (tests)\n if (pending !== undefined) {\n if (ctx === undefined) await pending;\n else ctx.waitUntil(pending);\n }\n const url = new URL(request.url);\n const { store, close } = lease(env);\n try {\n // Opened by recipients, so no bearer: the routes only record, and redirect to recorded URLs\n if (url.pathname.startsWith('/t/') && store.reporting !== undefined) {\n const openedEvent = config.webPush?.openedEvent;\n const tracked = await handleTracking(request, store.reporting, {\n legacyUnsubscribeUrl: config.email?.unsubscribeUrl,\n product: config.title,\n quickMs: config.email?.tracking?.quickClickMs,\n pushOpened:\n openedEvent === undefined\n ? undefined\n : async ({ messageId, userId, at }) => {\n const payload: Record<string, unknown> = { message_id: messageId };\n const contract = config.contract;\n const verdict =\n contract === undefined ? undefined : checkEvent(contract, openedEvent, payload);\n if (verdict !== undefined && !verdict.ok) {\n throw new ContractValidationError(openedEvent, verdict.issues);\n }\n // Keyed by the copy: a second tap of the same notification is the same event\n await ingestEvent(\n env,\n {\n id: `${openedEvent}:${messageId}`,\n userId,\n event: openedEvent,\n payload: verdict?.data ?? payload,\n ts: at,\n },\n store\n );\n },\n });\n if (tracked !== undefined) return tracked;\n }\n if (url.pathname === '/segments/sweep') {\n if (!authorized(request, env)) return json({ error: 'unauthorized' }, 401);\n if (request.method !== 'POST') return json({ error: 'method not allowed' }, 405);\n return json(await sweepSegments(env, store));\n }\n if (url.pathname === '/bundle') {\n if (!authorized(request, env)) return json({ error: 'unauthorized' }, 401);\n if (request.method !== 'GET') return json({ error: 'method not allowed' }, 405);\n return json(bundle());\n }\n if (url.pathname === '/bundle/deploy') {\n if (!authorized(request, env)) return json({ error: 'unauthorized' }, 401);\n if (request.method !== 'POST') return json({ error: 'method not allowed' }, 405);\n try {\n return json(await deployBundle(env, bundle(), store));\n } catch (error) {\n if (!(error instanceof SchemaMismatchError)) throw error;\n return json({ error: error.message, problems: error.problems }, 503);\n }\n }\n if (emails !== undefined && preview !== undefined && url.pathname.startsWith('/preview/')) {\n if (!authorized(request, env)) return json({ error: 'unauthorized' }, 401);\n if (request.method !== 'GET') return json({ error: 'method not allowed' }, 405);\n const key = url.pathname.slice('/preview/'.length);\n if (!emailIds.has(key)) return json({ error: `unknown email template: ${key}` }, 404);\n // `?props=<JSON object>`: JSON keeps numbers and booleans typed, which query strings cannot\n let props: unknown = {};\n try {\n props = JSON.parse(url.searchParams.get('props') ?? '{}');\n } catch {\n return json({ error: 'props must be a JSON object' }, 400);\n }\n if (typeof props !== 'object' || props === null || Array.isArray(props)) {\n return json({ error: 'props must be a JSON object' }, 400);\n }\n try {\n const { subject, html } = await preview(key, props as Record<string, ScalarIR>);\n return new Response(html, {\n headers: { 'content-type': 'text/html; charset=utf-8', 'x-subject': subject },\n });\n } catch (error) {\n // The query string is the props: a refusal is the caller's to fix, with the field paths\n if (error instanceof TemplateValidationError) {\n return json({ error: error.message, issues: error.issues }, 400);\n }\n throw error;\n }\n }\n return await handleRequest(request, env, store, {\n contract: config.contract,\n mode: options.contractMode,\n });\n } finally {\n // After the response when the runtime offers waitUntil, inline otherwise (tests).\n if (ctx === undefined) await close();\n else ctx.waitUntil(close());\n }\n };\n\n const sweep = async (rawEnv: Env) => {\n const env = checkEnv(rawEnv);\n const { store, close } = lease(env);\n try {\n return await sweepSegments(env, store);\n } finally {\n await close();\n }\n };\n\n const scheduled = async (_controller: unknown, env: Env, ctx: ExecutionContextLike) => {\n // A version with no traffic yet goes live here; the sweep then reads its segments\n ctx.waitUntil(\n (async () => {\n await applyOwnBundle(checkEnv(env));\n await sweep(env);\n })()\n );\n };\n\n return { Runner, fetch, sweep, scheduled };\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"worker.cjs","names":["createJourneyHost","JourneyRunner","EnvValidationError"],"sources":["../../src/cloudflare/worker.ts"],"sourcesContent":["import type { JourneyRunParts } from '../host/execute';\nimport {\n EnvValidationError,\n type JourneyHostOptions,\n createJourneyHost,\n} from '../host/journey-host';\nimport type { SweepResult } from '../host/router';\nimport type { BundleVersion, JourneyStore } from '../store/index';\nimport type { JourneyEnv, JourneyParams } from './bindings';\nimport { JourneyRunner } from './runner';\n\nexport { EnvValidationError } from '../host/journey-host';\n\n/** The fetch handler's execution context (the subset used). */\nexport interface ExecutionContextLike {\n waitUntil(promise: Promise<unknown>): void;\n}\n\n/**\n * `journeyWorker`'s options: the host's (config, store, email transport, observer, ...). The\n * email transport is the host's choice — `cloudflareEmailTransport({ binding: env.EMAIL })` for\n * Cloudflare Email, `sesEmailTransport(...)` for SES.\n */\nexport type JourneyWorkerOptions<Env extends JourneyEnv> = JourneyHostOptions<Env>;\n\nexport interface JourneyWorker<Env extends JourneyEnv> {\n /** Export this under the `class_name` of the wrangler `workflows` binding. */\n Runner: new (...args: never[]) => JourneyRunner;\n /** The Worker's fetch handler: `/health`, `/events`, `/identify`, `/devices`, `/messages/opened`, `/segments/sweep`, `/t/*` (email tracking), `/deploy`, `/bundle`, `/bundle/deploy` and `/preview/*`. */\n fetch: (request: Request, env: Env, ctx?: ExecutionContextLike) => Promise<Response>;\n /**\n * The segment sweep (`sweepSegments`): membership of every deployed segment for every known\n * user, the journeys of segments entered by the clock alone (`not(performed(e.login, { within:\n * '7 days' }))` has no event to ride on), and each segment's size. Also served as\n * `POST /segments/sweep`.\n */\n sweep: (env: Env) => Promise<SweepResult>;\n /** The cron trigger's handler: runs the sweep. Export it beside `fetch` with a `triggers.crons` entry. */\n scheduled: (controller: unknown, env: Env, ctx: ExecutionContextLike) => Promise<void>;\n}\n\n/**\n * A complete journey host in one call: the runner class with the app's store,\n * outlets and runtime plugged in, and the fetch handler that serves the ingest\n * API around them.\n *\n * const journeys = journeyWorker<Env>({\n * config, // workflow.config.ts (defineConfig)\n * store: (env, database) => postgresJsStore(env.HYPERDRIVE.connectionString, database),\n * });\n * export const MyRunner = journeys.Runner;\n * export default { fetch: journeys.fetch };\n */\nexport function journeyWorker<Env extends JourneyEnv>(\n options: JourneyWorkerOptions<Env>\n): JourneyWorker<Env> {\n const host = createJourneyHost(options);\n\n class Runner extends JourneyRunner {\n private readonly leases = new WeakMap<JourneyStore, () => Promise<void>>();\n\n protected override createStore(): JourneyStore {\n // A run fails at its start on an invalid environment, not at the send that needs it\n const { store, close } = host.lease(host.checkEnv(this.env as Env));\n this.leases.set(store, close);\n return store;\n }\n\n protected override async closeStore(store: JourneyStore): Promise<void> {\n await this.leases.get(store)?.();\n }\n\n protected override createRunParts(store: JourneyStore, params: JourneyParams): JourneyRunParts {\n return host.runParts(host.checkEnv(this.env as Env), store, params);\n }\n }\n\n /** CF's version metadata: the bundle compiled into each new version goes live by itself. */\n const ownVersion = (env: Env): BundleVersion | undefined => {\n const metadata = env.CF_VERSION_METADATA;\n if (metadata === undefined) return undefined;\n return { id: metadata.id, createdAt: Date.parse(metadata.timestamp) };\n };\n\n const fetch = async (request: Request, rawEnv: Env, ctx?: ExecutionContextLike) => {\n // Everything below reads the validated environment (the schema's defaults applied)\n let env: Env;\n try {\n env = host.checkEnv(rawEnv);\n } catch (error) {\n if (!(error instanceof EnvValidationError)) throw error;\n console.error(`[workflow] ${error.message}`);\n return new Response(JSON.stringify({ error: error.message }), {\n status: 500,\n headers: { 'content-type': 'application/json' },\n });\n }\n const pending = host.applyOwnBundle(env, ownVersion(env));\n // In the background where the runtime has waitUntil; inline otherwise (tests)\n if (pending !== undefined) {\n if (ctx === undefined) await pending;\n else ctx.waitUntil(pending);\n }\n const { store, close } = host.lease(env);\n try {\n return await host.handle(request, env, store);\n } finally {\n // After the response when the runtime offers waitUntil, inline otherwise (tests).\n if (ctx === undefined) await close();\n else ctx.waitUntil(close());\n }\n };\n\n const sweep = (rawEnv: Env) => host.sweep(host.checkEnv(rawEnv));\n\n const scheduled = async (_controller: unknown, env: Env, ctx: ExecutionContextLike) => {\n // A version with no traffic yet goes live here; the sweep then reads its segments\n ctx.waitUntil(\n (async () => {\n const checked = host.checkEnv(env);\n await host.applyOwnBundle(checked, ownVersion(checked));\n await sweep(env);\n })()\n );\n };\n\n return { Runner, fetch, sweep, scheduled };\n}\n"],"mappings":";;;;;;;;;;;;;;;;AAqDA,SAAgB,cACd,SACoB;CACpB,MAAM,OAAOA,0BAAAA,kBAAkB,OAAO;CAEtC,MAAM,eAAeC,0BAAAA,cAAc;EACjC,yBAA0B,IAAI,QAA2C;EAEzE,cAA+C;GAE7C,MAAM,EAAE,OAAO,UAAU,KAAK,MAAM,KAAK,SAAS,KAAK,GAAU,CAAC;GAClE,KAAK,OAAO,IAAI,OAAO,KAAK;GAC5B,OAAO;EACT;EAEA,MAAyB,WAAW,OAAoC;GACtE,MAAM,KAAK,OAAO,IAAI,KAAK,CAAC,GAAG;EACjC;EAEA,eAAkC,OAAqB,QAAwC;GAC7F,OAAO,KAAK,SAAS,KAAK,SAAS,KAAK,GAAU,GAAG,OAAO,MAAM;EACpE;CACF;;CAGA,MAAM,cAAc,QAAwC;EAC1D,MAAM,WAAW,IAAI;EACrB,IAAI,aAAa,KAAA,GAAW,OAAO,KAAA;EACnC,OAAO;GAAE,IAAI,SAAS;GAAI,WAAW,KAAK,MAAM,SAAS,SAAS;EAAE;CACtE;CAEA,MAAM,QAAQ,OAAO,SAAkB,QAAa,QAA+B;EAEjF,IAAI;EACJ,IAAI;GACF,MAAM,KAAK,SAAS,MAAM;EAC5B,SAAS,OAAO;GACd,IAAI,EAAE,iBAAiBC,0BAAAA,qBAAqB,MAAM;GAClD,QAAQ,MAAM,cAAc,MAAM,SAAS;GAC3C,OAAO,IAAI,SAAS,KAAK,UAAU,EAAE,OAAO,MAAM,QAAQ,CAAC,GAAG;IAC5D,QAAQ;IACR,SAAS,EAAE,gBAAgB,mBAAmB;GAChD,CAAC;EACH;EACA,MAAM,UAAU,KAAK,eAAe,KAAK,WAAW,GAAG,CAAC;EAExD,IAAI,YAAY,KAAA,GAAW;GACzB,IAAI,QAAQ,KAAA,GAAW,MAAM;QACxB,IAAI,UAAU,OAAO;EAC5B;EACA,MAAM,EAAE,OAAO,UAAU,KAAK,MAAM,GAAG;EACvC,IAAI;GACF,OAAO,MAAM,KAAK,OAAO,SAAS,KAAK,KAAK;EAC9C,UAAU;GAER,IAAI,QAAQ,KAAA,GAAW,MAAM,MAAM;QAC9B,IAAI,UAAU,MAAM,CAAC;EAC5B;CACF;CAEA,MAAM,SAAS,WAAgB,KAAK,MAAM,KAAK,SAAS,MAAM,CAAC;CAE/D,MAAM,YAAY,OAAO,aAAsB,KAAU,QAA8B;EAErF,IAAI,WACD,YAAY;GACX,MAAM,UAAU,KAAK,SAAS,GAAG;GACjC,MAAM,KAAK,eAAe,SAAS,WAAW,OAAO,CAAC;GACtD,MAAM,MAAM,GAAG;EACjB,EAAA,CAAG,CACL;CACF;CAEA,OAAO;EAAE;EAAQ;EAAO;EAAO;CAAU;AAC3C"}
|